feat: meilisearch
This commit is contained in:
14
package.json
14
package.json
@@ -20,6 +20,7 @@
|
||||
"@mantine/core": "^7.6.2",
|
||||
"@mantine/hooks": "^7.6.2",
|
||||
"@mantine/notifications": "^7.6.2",
|
||||
"@meilisearch/instant-meilisearch": "^0.18.1",
|
||||
"@reduxjs/toolkit": "^2.2.1",
|
||||
"@sentry/react": "^7.108.0",
|
||||
"@sentry/vite-plugin": "^2.16.0",
|
||||
@@ -28,14 +29,23 @@
|
||||
"dayjs": "^1.11.10",
|
||||
"i18next": "^23.10.1",
|
||||
"i18next-browser-languagedetector": "^7.2.0",
|
||||
"instantsearch.js": "^4.71.1",
|
||||
"lodash": "^4.17.21",
|
||||
"meilisearch": "^0.40.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-i18next": "^14.1.0",
|
||||
"react-instantsearch": "^7.11.1",
|
||||
"react-redux": "^9.1.0",
|
||||
"react-router-dom": "^6.22.3",
|
||||
"remark": "^15.0.1",
|
||||
"remark-html": "^16.0.1"
|
||||
"rehype-highlight": "^7.0.0",
|
||||
"rehype-sanitize": "^6.0.0",
|
||||
"rehype-stringify": "^10.0.0",
|
||||
"remark-gfm": "^4.0.0",
|
||||
"remark-html": "^16.0.1",
|
||||
"remark-parse": "^11.0.0",
|
||||
"remark-rehype": "^11.1.0",
|
||||
"unified": "^11.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.24.1",
|
||||
|
||||
557
pnpm-lock.yaml
generated
557
pnpm-lock.yaml
generated
@@ -14,6 +14,9 @@ dependencies:
|
||||
'@mantine/notifications':
|
||||
specifier: ^7.6.2
|
||||
version: 7.6.2(@mantine/core@7.6.2)(@mantine/hooks@7.6.2)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@meilisearch/instant-meilisearch':
|
||||
specifier: ^0.18.1
|
||||
version: 0.18.1
|
||||
'@reduxjs/toolkit':
|
||||
specifier: ^2.2.1
|
||||
version: 2.2.1(react-redux@9.1.0)(react@18.2.0)
|
||||
@@ -38,9 +41,15 @@ dependencies:
|
||||
i18next-browser-languagedetector:
|
||||
specifier: ^7.2.0
|
||||
version: 7.2.0
|
||||
instantsearch.js:
|
||||
specifier: ^4.71.1
|
||||
version: 4.71.1(algoliasearch@4.23.3)
|
||||
lodash:
|
||||
specifier: ^4.17.21
|
||||
version: 4.17.21
|
||||
meilisearch:
|
||||
specifier: ^0.40.0
|
||||
version: 0.40.0
|
||||
react:
|
||||
specifier: ^18.2.0
|
||||
version: 18.2.0
|
||||
@@ -50,18 +59,39 @@ dependencies:
|
||||
react-i18next:
|
||||
specifier: ^14.1.0
|
||||
version: 14.1.0(i18next@23.10.1)(react-dom@18.2.0)(react@18.2.0)
|
||||
react-instantsearch:
|
||||
specifier: ^7.11.1
|
||||
version: 7.11.1(algoliasearch@4.23.3)(react-dom@18.2.0)(react@18.2.0)
|
||||
react-redux:
|
||||
specifier: ^9.1.0
|
||||
version: 9.1.0(@types/react@18.2.67)(react@18.2.0)(redux@5.0.1)
|
||||
react-router-dom:
|
||||
specifier: ^6.22.3
|
||||
version: 6.22.3(react-dom@18.2.0)(react@18.2.0)
|
||||
remark:
|
||||
specifier: ^15.0.1
|
||||
version: 15.0.1
|
||||
rehype-highlight:
|
||||
specifier: ^7.0.0
|
||||
version: 7.0.0
|
||||
rehype-sanitize:
|
||||
specifier: ^6.0.0
|
||||
version: 6.0.0
|
||||
rehype-stringify:
|
||||
specifier: ^10.0.0
|
||||
version: 10.0.0
|
||||
remark-gfm:
|
||||
specifier: ^4.0.0
|
||||
version: 4.0.0
|
||||
remark-html:
|
||||
specifier: ^16.0.1
|
||||
version: 16.0.1
|
||||
remark-parse:
|
||||
specifier: ^11.0.0
|
||||
version: 11.0.0
|
||||
remark-rehype:
|
||||
specifier: ^11.1.0
|
||||
version: 11.1.0
|
||||
unified:
|
||||
specifier: ^11.0.4
|
||||
version: 11.0.4
|
||||
|
||||
devDependencies:
|
||||
'@babel/core':
|
||||
@@ -235,6 +265,116 @@ packages:
|
||||
resolution: {integrity: sha512-rE0Pygv0sEZ4vBWHlAgJLGDU7Pm8xoO6p3wsEceb7GYAjScrOHpEo8KK/eVkAcnSM+slAEtXjA2JpdjLp4fJQQ==}
|
||||
dev: true
|
||||
|
||||
/@algolia/cache-browser-local-storage@4.23.3:
|
||||
resolution: {integrity: sha512-vRHXYCpPlTDE7i6UOy2xE03zHF2C8MEFjPN2v7fRbqVpcOvAUQK81x3Kc21xyb5aSIpYCjWCZbYZuz8Glyzyyg==}
|
||||
dependencies:
|
||||
'@algolia/cache-common': 4.23.3
|
||||
dev: false
|
||||
|
||||
/@algolia/cache-common@4.23.3:
|
||||
resolution: {integrity: sha512-h9XcNI6lxYStaw32pHpB1TMm0RuxphF+Ik4o7tcQiodEdpKK+wKufY6QXtba7t3k8eseirEMVB83uFFF3Nu54A==}
|
||||
dev: false
|
||||
|
||||
/@algolia/cache-in-memory@4.23.3:
|
||||
resolution: {integrity: sha512-yvpbuUXg/+0rbcagxNT7un0eo3czx2Uf0y4eiR4z4SD7SiptwYTpbuS0IHxcLHG3lq22ukx1T6Kjtk/rT+mqNg==}
|
||||
dependencies:
|
||||
'@algolia/cache-common': 4.23.3
|
||||
dev: false
|
||||
|
||||
/@algolia/client-account@4.23.3:
|
||||
resolution: {integrity: sha512-hpa6S5d7iQmretHHF40QGq6hz0anWEHGlULcTIT9tbUssWUriN9AUXIFQ8Ei4w9azD0hc1rUok9/DeQQobhQMA==}
|
||||
dependencies:
|
||||
'@algolia/client-common': 4.23.3
|
||||
'@algolia/client-search': 4.23.3
|
||||
'@algolia/transporter': 4.23.3
|
||||
dev: false
|
||||
|
||||
/@algolia/client-analytics@4.23.3:
|
||||
resolution: {integrity: sha512-LBsEARGS9cj8VkTAVEZphjxTjMVCci+zIIiRhpFun9jGDUlS1XmhCW7CTrnaWeIuCQS/2iPyRqSy1nXPjcBLRA==}
|
||||
dependencies:
|
||||
'@algolia/client-common': 4.23.3
|
||||
'@algolia/client-search': 4.23.3
|
||||
'@algolia/requester-common': 4.23.3
|
||||
'@algolia/transporter': 4.23.3
|
||||
dev: false
|
||||
|
||||
/@algolia/client-common@4.23.3:
|
||||
resolution: {integrity: sha512-l6EiPxdAlg8CYhroqS5ybfIczsGUIAC47slLPOMDeKSVXYG1n0qGiz4RjAHLw2aD0xzh2EXZ7aRguPfz7UKDKw==}
|
||||
dependencies:
|
||||
'@algolia/requester-common': 4.23.3
|
||||
'@algolia/transporter': 4.23.3
|
||||
dev: false
|
||||
|
||||
/@algolia/client-personalization@4.23.3:
|
||||
resolution: {integrity: sha512-3E3yF3Ocr1tB/xOZiuC3doHQBQ2zu2MPTYZ0d4lpfWads2WTKG7ZzmGnsHmm63RflvDeLK/UVx7j2b3QuwKQ2g==}
|
||||
dependencies:
|
||||
'@algolia/client-common': 4.23.3
|
||||
'@algolia/requester-common': 4.23.3
|
||||
'@algolia/transporter': 4.23.3
|
||||
dev: false
|
||||
|
||||
/@algolia/client-search@4.23.3:
|
||||
resolution: {integrity: sha512-P4VAKFHqU0wx9O+q29Q8YVuaowaZ5EM77rxfmGnkHUJggh28useXQdopokgwMeYw2XUht49WX5RcTQ40rZIabw==}
|
||||
dependencies:
|
||||
'@algolia/client-common': 4.23.3
|
||||
'@algolia/requester-common': 4.23.3
|
||||
'@algolia/transporter': 4.23.3
|
||||
dev: false
|
||||
|
||||
/@algolia/events@4.0.1:
|
||||
resolution: {integrity: sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ==}
|
||||
dev: false
|
||||
|
||||
/@algolia/logger-common@4.23.3:
|
||||
resolution: {integrity: sha512-y9kBtmJwiZ9ZZ+1Ek66P0M68mHQzKRxkW5kAAXYN/rdzgDN0d2COsViEFufxJ0pb45K4FRcfC7+33YB4BLrZ+g==}
|
||||
dev: false
|
||||
|
||||
/@algolia/logger-console@4.23.3:
|
||||
resolution: {integrity: sha512-8xoiseoWDKuCVnWP8jHthgaeobDLolh00KJAdMe9XPrWPuf1by732jSpgy2BlsLTaT9m32pHI8CRfrOqQzHv3A==}
|
||||
dependencies:
|
||||
'@algolia/logger-common': 4.23.3
|
||||
dev: false
|
||||
|
||||
/@algolia/recommend@4.23.3:
|
||||
resolution: {integrity: sha512-9fK4nXZF0bFkdcLBRDexsnGzVmu4TSYZqxdpgBW2tEyfuSSY54D4qSRkLmNkrrz4YFvdh2GM1gA8vSsnZPR73w==}
|
||||
dependencies:
|
||||
'@algolia/cache-browser-local-storage': 4.23.3
|
||||
'@algolia/cache-common': 4.23.3
|
||||
'@algolia/cache-in-memory': 4.23.3
|
||||
'@algolia/client-common': 4.23.3
|
||||
'@algolia/client-search': 4.23.3
|
||||
'@algolia/logger-common': 4.23.3
|
||||
'@algolia/logger-console': 4.23.3
|
||||
'@algolia/requester-browser-xhr': 4.23.3
|
||||
'@algolia/requester-common': 4.23.3
|
||||
'@algolia/requester-node-http': 4.23.3
|
||||
'@algolia/transporter': 4.23.3
|
||||
dev: false
|
||||
|
||||
/@algolia/requester-browser-xhr@4.23.3:
|
||||
resolution: {integrity: sha512-jDWGIQ96BhXbmONAQsasIpTYWslyjkiGu0Quydjlowe+ciqySpiDUrJHERIRfELE5+wFc7hc1Q5hqjGoV7yghw==}
|
||||
dependencies:
|
||||
'@algolia/requester-common': 4.23.3
|
||||
dev: false
|
||||
|
||||
/@algolia/requester-common@4.23.3:
|
||||
resolution: {integrity: sha512-xloIdr/bedtYEGcXCiF2muajyvRhwop4cMZo+K2qzNht0CMzlRkm8YsDdj5IaBhshqfgmBb3rTg4sL4/PpvLYw==}
|
||||
dev: false
|
||||
|
||||
/@algolia/requester-node-http@4.23.3:
|
||||
resolution: {integrity: sha512-zgu++8Uj03IWDEJM3fuNl34s746JnZOWn1Uz5taV1dFyJhVM/kTNw9Ik7YJWiUNHJQXcaD8IXD1eCb0nq/aByA==}
|
||||
dependencies:
|
||||
'@algolia/requester-common': 4.23.3
|
||||
dev: false
|
||||
|
||||
/@algolia/transporter@4.23.3:
|
||||
resolution: {integrity: sha512-Wjl5gttqnf/gQKJA+dafnD0Y6Yw97yvfY8R9h0dQltX1GXTgNs1zWgvtWW0tHl1EgMdhAyw189uWiZMnL3QebQ==}
|
||||
dependencies:
|
||||
'@algolia/cache-common': 4.23.3
|
||||
'@algolia/logger-common': 4.23.3
|
||||
'@algolia/requester-common': 4.23.3
|
||||
dev: false
|
||||
|
||||
/@ampproject/remapping@2.3.0:
|
||||
resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
@@ -2207,6 +2347,14 @@ packages:
|
||||
react: 18.2.0
|
||||
dev: true
|
||||
|
||||
/@meilisearch/instant-meilisearch@0.18.1:
|
||||
resolution: {integrity: sha512-KxBoEaI1+CQQaSbSZZEllIUwsNofALH0RG+/bFMkNhavuzJn982zOTC7xYtYmXC6nsj32MmAe+rDBtazycDJQQ==}
|
||||
dependencies:
|
||||
meilisearch: 0.40.0
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
dev: false
|
||||
|
||||
/@ndelangen/get-tarball@3.0.9:
|
||||
resolution: {integrity: sha512-9JKTEik4vq+yGosHYhZ1tiH/3WpUS0Nh0kej4Agndhox8pAdWhEx5knFVRcb/ya9knCRCs1rPxNrSXTDdfVqpA==}
|
||||
dependencies:
|
||||
@@ -3618,6 +3766,10 @@ packages:
|
||||
resolution: {integrity: sha512-w5jZ0ee+HaPOaX25X2/2oGR/7rgAQSYII7X7pp0m9KgBfMP7uKfMfTvcpl5Dj+eDBbpxKGiqE+flqDr6XTd2RA==}
|
||||
dev: true
|
||||
|
||||
/@types/dom-speech-recognition@0.0.1:
|
||||
resolution: {integrity: sha512-udCxb8DvjcDKfk1WTBzDsxFbLgYxmQGKrE/ricoMqHRNjSlSUCcamVTA5lIQqzY10mY5qCY0QDwBfFEwhfoDPw==}
|
||||
dev: false
|
||||
|
||||
/@types/ejs@3.1.5:
|
||||
resolution: {integrity: sha512-nv+GSx77ZtXiJzwKdsASqi+YQ5Z7vwHsTP0JY2SiQgjGckkBRKZnk8nIM+7oUZ1VCtuTz0+By4qVR7fqzp/Dfg==}
|
||||
dev: true
|
||||
@@ -3674,6 +3826,10 @@ packages:
|
||||
resolution: {integrity: sha512-frsJrz2t/CeGifcu/6uRo4b+SzAwT4NYCVPu1GN8IB9XTzrpPkGuV0tmh9mN+/L0PklAlsC3u5Fxt0ju00LXIw==}
|
||||
dev: true
|
||||
|
||||
/@types/google.maps@3.55.10:
|
||||
resolution: {integrity: sha512-XbDu2MIvcKgN+MBrufjWcsQRtXTbrBGBKperbhMLnPSq4770+pvlR66Oqq/Ub4AVkmGc9QciCfwPZpVCLaKAOw==}
|
||||
dev: false
|
||||
|
||||
/@types/graceful-fs@4.1.9:
|
||||
resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==}
|
||||
dependencies:
|
||||
@@ -3685,6 +3841,10 @@ packages:
|
||||
dependencies:
|
||||
'@types/unist': 3.0.2
|
||||
|
||||
/@types/hogan.js@3.0.5:
|
||||
resolution: {integrity: sha512-/uRaY3HGPWyLqOyhgvW9Aa43BNnLZrNeQxl2p8wqId4UHMfPKolSB+U7BlZyO1ng7MkLnyEAItsBzCG0SDhqrA==}
|
||||
dev: false
|
||||
|
||||
/@types/http-errors@2.0.4:
|
||||
resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==}
|
||||
dev: true
|
||||
@@ -3779,7 +3939,6 @@ packages:
|
||||
|
||||
/@types/qs@6.9.13:
|
||||
resolution: {integrity: sha512-iLR+1vTTJ3p0QaOUq6ACbY1mzKTODFDT/XedZI8BksOotFmL4ForwDfRQ/DZeuTHR7/2i4lI1D203gdfxuqTlA==}
|
||||
dev: true
|
||||
|
||||
/@types/range-parser@1.2.7:
|
||||
resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==}
|
||||
@@ -4245,6 +4404,10 @@ packages:
|
||||
deprecated: Use your platform's native atob() and btoa() methods instead
|
||||
dev: true
|
||||
|
||||
/abbrev@1.1.1:
|
||||
resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}
|
||||
dev: false
|
||||
|
||||
/accepts@1.3.8:
|
||||
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
|
||||
engines: {node: '>= 0.6'}
|
||||
@@ -4372,6 +4535,35 @@ packages:
|
||||
uri-js: 4.4.1
|
||||
dev: true
|
||||
|
||||
/algoliasearch-helper@3.21.0(algoliasearch@4.23.3):
|
||||
resolution: {integrity: sha512-hjVOrL15I3Y3K8xG0icwG1/tWE+MocqBrhW6uVBWpU+/kVEMK0BnM2xdssj6mZM61eJ4iRxHR0djEI3ENOpR8w==}
|
||||
peerDependencies:
|
||||
algoliasearch: '>= 3.1 < 6'
|
||||
dependencies:
|
||||
'@algolia/events': 4.0.1
|
||||
algoliasearch: 4.23.3
|
||||
dev: false
|
||||
|
||||
/algoliasearch@4.23.3:
|
||||
resolution: {integrity: sha512-Le/3YgNvjW9zxIQMRhUHuhiUjAlKY/zsdZpfq4dlLqg6mEm0nL6yk+7f2hDOtLpxsgE4jSzDmvHL7nXdBp5feg==}
|
||||
dependencies:
|
||||
'@algolia/cache-browser-local-storage': 4.23.3
|
||||
'@algolia/cache-common': 4.23.3
|
||||
'@algolia/cache-in-memory': 4.23.3
|
||||
'@algolia/client-account': 4.23.3
|
||||
'@algolia/client-analytics': 4.23.3
|
||||
'@algolia/client-common': 4.23.3
|
||||
'@algolia/client-personalization': 4.23.3
|
||||
'@algolia/client-search': 4.23.3
|
||||
'@algolia/logger-common': 4.23.3
|
||||
'@algolia/logger-console': 4.23.3
|
||||
'@algolia/recommend': 4.23.3
|
||||
'@algolia/requester-browser-xhr': 4.23.3
|
||||
'@algolia/requester-common': 4.23.3
|
||||
'@algolia/requester-node-http': 4.23.3
|
||||
'@algolia/transporter': 4.23.3
|
||||
dev: false
|
||||
|
||||
/ansi-escapes@4.3.2:
|
||||
resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -5242,6 +5434,14 @@ packages:
|
||||
- ts-node
|
||||
dev: true
|
||||
|
||||
/cross-fetch@3.1.8:
|
||||
resolution: {integrity: sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==}
|
||||
dependencies:
|
||||
node-fetch: 2.7.0
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
dev: false
|
||||
|
||||
/cross-fetch@4.0.0:
|
||||
resolution: {integrity: sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==}
|
||||
dependencies:
|
||||
@@ -5973,6 +6173,11 @@ packages:
|
||||
engines: {node: '>=10'}
|
||||
dev: true
|
||||
|
||||
/escape-string-regexp@5.0.0:
|
||||
resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==}
|
||||
engines: {node: '>=12'}
|
||||
dev: false
|
||||
|
||||
/escodegen@2.1.0:
|
||||
resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==}
|
||||
engines: {node: '>=6.0'}
|
||||
@@ -6962,7 +7167,6 @@ packages:
|
||||
resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==}
|
||||
dependencies:
|
||||
'@types/hast': 3.0.4
|
||||
dev: true
|
||||
|
||||
/hast-util-parse-selector@4.0.0:
|
||||
resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==}
|
||||
@@ -7031,6 +7235,15 @@ packages:
|
||||
'@types/hast': 3.0.4
|
||||
dev: true
|
||||
|
||||
/hast-util-to-text@4.0.2:
|
||||
resolution: {integrity: sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==}
|
||||
dependencies:
|
||||
'@types/hast': 3.0.4
|
||||
'@types/unist': 3.0.2
|
||||
hast-util-is-element: 3.0.0
|
||||
unist-util-find-after: 5.0.0
|
||||
dev: false
|
||||
|
||||
/hast-util-whitespace@3.0.0:
|
||||
resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==}
|
||||
dependencies:
|
||||
@@ -7047,6 +7260,19 @@ packages:
|
||||
space-separated-tokens: 2.0.2
|
||||
dev: false
|
||||
|
||||
/highlight.js@11.9.0:
|
||||
resolution: {integrity: sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
dev: false
|
||||
|
||||
/hogan.js@3.0.2:
|
||||
resolution: {integrity: sha512-RqGs4wavGYJWE07t35JQccByczmNUXQT0E12ZYV1VKYu5UiAU9lsos/yBAcf840+zrUQQxgVduCR5/B8nNtibg==}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
mkdirp: 0.3.0
|
||||
nopt: 1.0.10
|
||||
dev: false
|
||||
|
||||
/hoist-non-react-statics@3.3.2:
|
||||
resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==}
|
||||
dependencies:
|
||||
@@ -7057,6 +7283,10 @@ packages:
|
||||
resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==}
|
||||
dev: true
|
||||
|
||||
/htm@3.1.1:
|
||||
resolution: {integrity: sha512-983Vyg8NwUE7JkZ6NmOqpCZ+sh1bKv2iYTlUkzlWmA5JD2acKoxd4KVxbMmxX/85mtfdnDmTFoNKcg5DGAvxNQ==}
|
||||
dev: false
|
||||
|
||||
/html-encoding-sniffer@3.0.0:
|
||||
resolution: {integrity: sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -7218,6 +7448,32 @@ packages:
|
||||
semver: 6.3.1
|
||||
dev: true
|
||||
|
||||
/instantsearch-ui-components@0.6.0:
|
||||
resolution: {integrity: sha512-Jj3F9D46ef8VtzVZTgWsy79P25Q5nhI5XzK0NqfUVVI5yI3vA/3NkvKYtBHBlz50DDyBm6t9kIn/ZfpOwENm2A==}
|
||||
dependencies:
|
||||
'@babel/runtime': 7.24.1
|
||||
dev: false
|
||||
|
||||
/instantsearch.js@4.71.1(algoliasearch@4.23.3):
|
||||
resolution: {integrity: sha512-4AvEPadnDBf0NsCCw+a1GjmMFEZ3zQzQhCe51cFPLYRXnRyKw5bLvRVaNQckiDG+vl7bPyJrWn5YAh5UhBwA+w==}
|
||||
peerDependencies:
|
||||
algoliasearch: '>= 3.1 < 6'
|
||||
dependencies:
|
||||
'@algolia/events': 4.0.1
|
||||
'@types/dom-speech-recognition': 0.0.1
|
||||
'@types/google.maps': 3.55.10
|
||||
'@types/hogan.js': 3.0.5
|
||||
'@types/qs': 6.9.13
|
||||
algoliasearch: 4.23.3
|
||||
algoliasearch-helper: 3.21.0(algoliasearch@4.23.3)
|
||||
hogan.js: 3.0.2
|
||||
htm: 3.1.1
|
||||
instantsearch-ui-components: 0.6.0
|
||||
preact: 10.22.0
|
||||
qs: 6.9.7
|
||||
search-insights: 2.14.0
|
||||
dev: false
|
||||
|
||||
/internal-slot@1.0.7:
|
||||
resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -8348,6 +8604,14 @@ packages:
|
||||
get-func-name: 2.0.2
|
||||
dev: true
|
||||
|
||||
/lowlight@3.1.0:
|
||||
resolution: {integrity: sha512-CEbNVoSikAxwDMDPjXlqlFYiZLkDJHwyGu/MfOsJnF3d7f3tds5J3z8s/l9TMXhzfsJCCJEAsD78842mwmg0PQ==}
|
||||
dependencies:
|
||||
'@types/hast': 3.0.4
|
||||
devlop: 1.1.0
|
||||
highlight.js: 11.9.0
|
||||
dev: false
|
||||
|
||||
/lru-cache@10.2.0:
|
||||
resolution: {integrity: sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==}
|
||||
engines: {node: 14 || >=16.14}
|
||||
@@ -8425,6 +8689,10 @@ packages:
|
||||
resolution: {integrity: sha512-0aF7ZmVon1igznGI4VS30yugpduQW3y3GkcgGJOp7d8x8QrizhigUxjI/m2UojsXXto+jLAH3KSz+xOJTiORjg==}
|
||||
dev: true
|
||||
|
||||
/markdown-table@3.0.3:
|
||||
resolution: {integrity: sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==}
|
||||
dev: false
|
||||
|
||||
/markdown-to-jsx@7.3.2(react@18.2.0):
|
||||
resolution: {integrity: sha512-B+28F5ucp83aQm+OxNrPkS8z0tMKaeHiy0lHJs3LqCyDQFtWuenaIrkaVTgAm1pf1AU85LXltva86hlaT17i8Q==}
|
||||
engines: {node: '>= 10'}
|
||||
@@ -8434,6 +8702,15 @@ packages:
|
||||
react: 18.2.0
|
||||
dev: true
|
||||
|
||||
/mdast-util-find-and-replace@3.0.1:
|
||||
resolution: {integrity: sha512-SG21kZHGC3XRTSUhtofZkBzZTJNM5ecCi0SK2IMKmSXR8vO3peL+kb1O0z7Zl83jKtutG4k5Wv/W7V3/YHvzPA==}
|
||||
dependencies:
|
||||
'@types/mdast': 4.0.3
|
||||
escape-string-regexp: 5.0.0
|
||||
unist-util-is: 6.0.0
|
||||
unist-util-visit-parents: 6.0.1
|
||||
dev: false
|
||||
|
||||
/mdast-util-from-markdown@2.0.0:
|
||||
resolution: {integrity: sha512-n7MTOr/z+8NAX/wmhhDji8O3bRvPTV/U0oTCaZJkjhPSKTPhS3xufVhKGF8s1pJ7Ox4QgoIU7KHseh09S+9rTA==}
|
||||
dependencies:
|
||||
@@ -8453,6 +8730,75 @@ packages:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/mdast-util-gfm-autolink-literal@2.0.0:
|
||||
resolution: {integrity: sha512-FyzMsduZZHSc3i0Px3PQcBT4WJY/X/RCtEJKuybiC6sjPqLv7h1yqAkmILZtuxMSsUyaLUWNp71+vQH2zqp5cg==}
|
||||
dependencies:
|
||||
'@types/mdast': 4.0.3
|
||||
ccount: 2.0.1
|
||||
devlop: 1.1.0
|
||||
mdast-util-find-and-replace: 3.0.1
|
||||
micromark-util-character: 2.1.0
|
||||
dev: false
|
||||
|
||||
/mdast-util-gfm-footnote@2.0.0:
|
||||
resolution: {integrity: sha512-5jOT2boTSVkMnQ7LTrd6n/18kqwjmuYqo7JUPe+tRCY6O7dAuTFMtTPauYYrMPpox9hlN0uOx/FL8XvEfG9/mQ==}
|
||||
dependencies:
|
||||
'@types/mdast': 4.0.3
|
||||
devlop: 1.1.0
|
||||
mdast-util-from-markdown: 2.0.0
|
||||
mdast-util-to-markdown: 2.1.0
|
||||
micromark-util-normalize-identifier: 2.0.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/mdast-util-gfm-strikethrough@2.0.0:
|
||||
resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==}
|
||||
dependencies:
|
||||
'@types/mdast': 4.0.3
|
||||
mdast-util-from-markdown: 2.0.0
|
||||
mdast-util-to-markdown: 2.1.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/mdast-util-gfm-table@2.0.0:
|
||||
resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==}
|
||||
dependencies:
|
||||
'@types/mdast': 4.0.3
|
||||
devlop: 1.1.0
|
||||
markdown-table: 3.0.3
|
||||
mdast-util-from-markdown: 2.0.0
|
||||
mdast-util-to-markdown: 2.1.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/mdast-util-gfm-task-list-item@2.0.0:
|
||||
resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==}
|
||||
dependencies:
|
||||
'@types/mdast': 4.0.3
|
||||
devlop: 1.1.0
|
||||
mdast-util-from-markdown: 2.0.0
|
||||
mdast-util-to-markdown: 2.1.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/mdast-util-gfm@3.0.0:
|
||||
resolution: {integrity: sha512-dgQEX5Amaq+DuUqf26jJqSK9qgixgd6rYDHAv4aTBuA92cTknZlKpPfa86Z/s8Dj8xsAQpFfBmPUHWJBWqS4Bw==}
|
||||
dependencies:
|
||||
mdast-util-from-markdown: 2.0.0
|
||||
mdast-util-gfm-autolink-literal: 2.0.0
|
||||
mdast-util-gfm-footnote: 2.0.0
|
||||
mdast-util-gfm-strikethrough: 2.0.0
|
||||
mdast-util-gfm-table: 2.0.0
|
||||
mdast-util-gfm-task-list-item: 2.0.0
|
||||
mdast-util-to-markdown: 2.1.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/mdast-util-phrasing@4.1.0:
|
||||
resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==}
|
||||
dependencies:
|
||||
@@ -8498,6 +8844,14 @@ packages:
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: true
|
||||
|
||||
/meilisearch@0.40.0:
|
||||
resolution: {integrity: sha512-BoRhQMr2mBFLEeCfsvPluksGb01IaOiWvV3Deu3iEY+yYJ4jdGTu+IQi5FCjKlNQ7/TMWSN2XUToSgvH1tj0BQ==}
|
||||
dependencies:
|
||||
cross-fetch: 3.1.8
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
dev: false
|
||||
|
||||
/memoizee@0.4.15:
|
||||
resolution: {integrity: sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ==}
|
||||
dependencies:
|
||||
@@ -8556,6 +8910,78 @@ packages:
|
||||
micromark-util-types: 2.0.0
|
||||
dev: false
|
||||
|
||||
/micromark-extension-gfm-autolink-literal@2.0.0:
|
||||
resolution: {integrity: sha512-rTHfnpt/Q7dEAK1Y5ii0W8bhfJlVJFnJMHIPisfPK3gpVNuOP0VnRl96+YJ3RYWV/P4gFeQoGKNlT3RhuvpqAg==}
|
||||
dependencies:
|
||||
micromark-util-character: 2.1.0
|
||||
micromark-util-sanitize-uri: 2.0.0
|
||||
micromark-util-symbol: 2.0.0
|
||||
micromark-util-types: 2.0.0
|
||||
dev: false
|
||||
|
||||
/micromark-extension-gfm-footnote@2.0.0:
|
||||
resolution: {integrity: sha512-6Rzu0CYRKDv3BfLAUnZsSlzx3ak6HAoI85KTiijuKIz5UxZxbUI+pD6oHgw+6UtQuiRwnGRhzMmPRv4smcz0fg==}
|
||||
dependencies:
|
||||
devlop: 1.1.0
|
||||
micromark-core-commonmark: 2.0.0
|
||||
micromark-factory-space: 2.0.0
|
||||
micromark-util-character: 2.1.0
|
||||
micromark-util-normalize-identifier: 2.0.0
|
||||
micromark-util-sanitize-uri: 2.0.0
|
||||
micromark-util-symbol: 2.0.0
|
||||
micromark-util-types: 2.0.0
|
||||
dev: false
|
||||
|
||||
/micromark-extension-gfm-strikethrough@2.0.0:
|
||||
resolution: {integrity: sha512-c3BR1ClMp5fxxmwP6AoOY2fXO9U8uFMKs4ADD66ahLTNcwzSCyRVU4k7LPV5Nxo/VJiR4TdzxRQY2v3qIUceCw==}
|
||||
dependencies:
|
||||
devlop: 1.1.0
|
||||
micromark-util-chunked: 2.0.0
|
||||
micromark-util-classify-character: 2.0.0
|
||||
micromark-util-resolve-all: 2.0.0
|
||||
micromark-util-symbol: 2.0.0
|
||||
micromark-util-types: 2.0.0
|
||||
dev: false
|
||||
|
||||
/micromark-extension-gfm-table@2.0.0:
|
||||
resolution: {integrity: sha512-PoHlhypg1ItIucOaHmKE8fbin3vTLpDOUg8KAr8gRCF1MOZI9Nquq2i/44wFvviM4WuxJzc3demT8Y3dkfvYrw==}
|
||||
dependencies:
|
||||
devlop: 1.1.0
|
||||
micromark-factory-space: 2.0.0
|
||||
micromark-util-character: 2.1.0
|
||||
micromark-util-symbol: 2.0.0
|
||||
micromark-util-types: 2.0.0
|
||||
dev: false
|
||||
|
||||
/micromark-extension-gfm-tagfilter@2.0.0:
|
||||
resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==}
|
||||
dependencies:
|
||||
micromark-util-types: 2.0.0
|
||||
dev: false
|
||||
|
||||
/micromark-extension-gfm-task-list-item@2.0.1:
|
||||
resolution: {integrity: sha512-cY5PzGcnULaN5O7T+cOzfMoHjBW7j+T9D2sucA5d/KbsBTPcYdebm9zUd9zzdgJGCwahV+/W78Z3nbulBYVbTw==}
|
||||
dependencies:
|
||||
devlop: 1.1.0
|
||||
micromark-factory-space: 2.0.0
|
||||
micromark-util-character: 2.1.0
|
||||
micromark-util-symbol: 2.0.0
|
||||
micromark-util-types: 2.0.0
|
||||
dev: false
|
||||
|
||||
/micromark-extension-gfm@3.0.0:
|
||||
resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==}
|
||||
dependencies:
|
||||
micromark-extension-gfm-autolink-literal: 2.0.0
|
||||
micromark-extension-gfm-footnote: 2.0.0
|
||||
micromark-extension-gfm-strikethrough: 2.0.0
|
||||
micromark-extension-gfm-table: 2.0.0
|
||||
micromark-extension-gfm-tagfilter: 2.0.0
|
||||
micromark-extension-gfm-task-list-item: 2.0.1
|
||||
micromark-util-combine-extensions: 2.0.0
|
||||
micromark-util-types: 2.0.0
|
||||
dev: false
|
||||
|
||||
/micromark-factory-destination@2.0.0:
|
||||
resolution: {integrity: sha512-j9DGrQLm/Uhl2tCzcbLhy5kXsgkHUrjJHg4fFAeoMRwJmJerT9aw4FEhIbZStWN8A3qMwOp1uzHr4UL8AInxtA==}
|
||||
dependencies:
|
||||
@@ -8813,6 +9239,11 @@ packages:
|
||||
resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==}
|
||||
dev: true
|
||||
|
||||
/mkdirp@0.3.0:
|
||||
resolution: {integrity: sha512-OHsdUcVAQ6pOtg5JYWpCBo9W/GySVuwvP9hueRMW7UqshC0tbfzLv8wjySTPm3tfUZ/21CE9E1pJagOA91Pxew==}
|
||||
deprecated: Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)
|
||||
dev: false
|
||||
|
||||
/mkdirp@1.0.4:
|
||||
resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -8886,6 +9317,13 @@ packages:
|
||||
/node-releases@2.0.14:
|
||||
resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==}
|
||||
|
||||
/nopt@1.0.10:
|
||||
resolution: {integrity: sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
abbrev: 1.1.1
|
||||
dev: false
|
||||
|
||||
/normalize-package-data@2.5.0:
|
||||
resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==}
|
||||
dependencies:
|
||||
@@ -9357,6 +9795,10 @@ packages:
|
||||
source-map-js: 1.2.0
|
||||
dev: true
|
||||
|
||||
/preact@10.22.0:
|
||||
resolution: {integrity: sha512-RRurnSjJPj4rp5K6XoP45Ui33ncb7e4H7WiOHVpjbkvqvA3U+N8Z6Qbo0AE6leGYBV66n8EhEaFixvIu3SkxFw==}
|
||||
dev: false
|
||||
|
||||
/prelude-ls@1.2.1:
|
||||
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
@@ -9498,6 +9940,11 @@ packages:
|
||||
side-channel: 1.0.6
|
||||
dev: true
|
||||
|
||||
/qs@6.9.7:
|
||||
resolution: {integrity: sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==}
|
||||
engines: {node: '>=0.6'}
|
||||
dev: false
|
||||
|
||||
/querystringify@2.2.0:
|
||||
resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==}
|
||||
dev: true
|
||||
@@ -9582,6 +10029,36 @@ packages:
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
|
||||
/react-instantsearch-core@7.11.1(algoliasearch@4.23.3)(react@18.2.0):
|
||||
resolution: {integrity: sha512-FsfEvefr3AZtsN4NYLxrqWFvzlrR73ejNX9f/1NBDERxaM+dZFKRfWpRXrA1QSwFeAppRmyC5+TMmga8OK8M0g==}
|
||||
peerDependencies:
|
||||
algoliasearch: '>= 3.1 < 5'
|
||||
react: '>= 16.8.0 < 19'
|
||||
dependencies:
|
||||
'@babel/runtime': 7.24.1
|
||||
algoliasearch: 4.23.3
|
||||
algoliasearch-helper: 3.21.0(algoliasearch@4.23.3)
|
||||
instantsearch.js: 4.71.1(algoliasearch@4.23.3)
|
||||
react: 18.2.0
|
||||
use-sync-external-store: 1.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/react-instantsearch@7.11.1(algoliasearch@4.23.3)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-XiCH2SfifJAk5kfdT1Mi3HxX3MzeJL72oF1kUltgmD7fqEi8HsK+vn2wGxAvSGhuLzltV2Lg3xIXd4OAocjeWA==}
|
||||
peerDependencies:
|
||||
algoliasearch: '>= 3.1 < 5'
|
||||
react: '>= 16.8.0 < 19'
|
||||
react-dom: '>= 16.8.0 < 19'
|
||||
dependencies:
|
||||
'@babel/runtime': 7.24.1
|
||||
algoliasearch: 4.23.3
|
||||
instantsearch-ui-components: 0.6.0
|
||||
instantsearch.js: 4.71.1(algoliasearch@4.23.3)
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
react-instantsearch-core: 7.11.1(algoliasearch@4.23.3)(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/react-is@16.13.1:
|
||||
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
|
||||
|
||||
@@ -9904,6 +10381,23 @@ packages:
|
||||
unist-util-visit: 5.0.0
|
||||
dev: true
|
||||
|
||||
/rehype-highlight@7.0.0:
|
||||
resolution: {integrity: sha512-QtobgRgYoQaK6p1eSr2SD1i61f7bjF2kZHAQHxeCHAuJf7ZUDMvQ7owDq9YTkmar5m5TSUol+2D3bp3KfJf/oA==}
|
||||
dependencies:
|
||||
'@types/hast': 3.0.4
|
||||
hast-util-to-text: 4.0.2
|
||||
lowlight: 3.1.0
|
||||
unist-util-visit: 5.0.0
|
||||
vfile: 6.0.1
|
||||
dev: false
|
||||
|
||||
/rehype-sanitize@6.0.0:
|
||||
resolution: {integrity: sha512-CsnhKNsyI8Tub6L4sm5ZFsme4puGfc6pYylvXo1AeqaGbjOYyzNv3qZPwvs0oMJ39eryyeOdmxwUIo94IpEhqg==}
|
||||
dependencies:
|
||||
'@types/hast': 3.0.4
|
||||
hast-util-sanitize: 5.0.1
|
||||
dev: false
|
||||
|
||||
/rehype-slug@6.0.0:
|
||||
resolution: {integrity: sha512-lWyvf/jwu+oS5+hL5eClVd3hNdmwM1kAC0BUvEGD19pajQMIzcNUd/k9GsfQ+FfECvX+JE+e9/btsKH0EjJT6A==}
|
||||
dependencies:
|
||||
@@ -9914,6 +10408,27 @@ packages:
|
||||
unist-util-visit: 5.0.0
|
||||
dev: true
|
||||
|
||||
/rehype-stringify@10.0.0:
|
||||
resolution: {integrity: sha512-1TX1i048LooI9QoecrXy7nGFFbFSufxVRAfc6Y9YMRAi56l+oB0zP51mLSV312uRuvVLPV1opSlJmslozR1XHQ==}
|
||||
dependencies:
|
||||
'@types/hast': 3.0.4
|
||||
hast-util-to-html: 9.0.0
|
||||
unified: 11.0.4
|
||||
dev: false
|
||||
|
||||
/remark-gfm@4.0.0:
|
||||
resolution: {integrity: sha512-U92vJgBPkbw4Zfu/IiW2oTZLSL3Zpv+uI7My2eq8JxKgqraFdU8YUGicEJCEgSbeaG+QDFqIcwwfMTOEelPxuA==}
|
||||
dependencies:
|
||||
'@types/mdast': 4.0.3
|
||||
mdast-util-gfm: 3.0.0
|
||||
micromark-extension-gfm: 3.0.0
|
||||
remark-parse: 11.0.0
|
||||
remark-stringify: 11.0.0
|
||||
unified: 11.0.4
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/remark-html@16.0.1:
|
||||
resolution: {integrity: sha512-B9JqA5i0qZe0Nsf49q3OXyGvyXuZFDzAP2iOFLEumymuYJITVpiH1IgsTEwTpdptDmZlMDMWeDmSawdaJIGCXQ==}
|
||||
dependencies:
|
||||
@@ -9935,6 +10450,16 @@ packages:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/remark-rehype@11.1.0:
|
||||
resolution: {integrity: sha512-z3tJrAs2kIs1AqIIy6pzHmAHlF1hWQ+OdY4/hv+Wxe35EhyLKcajL33iUEn3ScxtFox9nUvRufR/Zre8Q08H/g==}
|
||||
dependencies:
|
||||
'@types/hast': 3.0.4
|
||||
'@types/mdast': 4.0.3
|
||||
mdast-util-to-hast: 13.1.0
|
||||
unified: 11.0.4
|
||||
vfile: 6.0.1
|
||||
dev: false
|
||||
|
||||
/remark-stringify@11.0.0:
|
||||
resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==}
|
||||
dependencies:
|
||||
@@ -9943,17 +10468,6 @@ packages:
|
||||
unified: 11.0.4
|
||||
dev: false
|
||||
|
||||
/remark@15.0.1:
|
||||
resolution: {integrity: sha512-Eht5w30ruCXgFmxVUSlNWQ9iiimq07URKeFS3hNc8cUWy1llX4KDWfyEDZRycMc+znsN9Ux5/tJ/BFdgdOwA3A==}
|
||||
dependencies:
|
||||
'@types/mdast': 4.0.3
|
||||
remark-parse: 11.0.0
|
||||
remark-stringify: 11.0.0
|
||||
unified: 11.0.4
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/require-directory@2.1.1:
|
||||
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -10139,6 +10653,10 @@ packages:
|
||||
ajv-keywords: 5.1.0(ajv@8.12.0)
|
||||
dev: true
|
||||
|
||||
/search-insights@2.14.0:
|
||||
resolution: {integrity: sha512-OLN6MsPMCghDOqlCtsIsYgtsC0pnwVTyT9Mu6A3ewOj1DxvzZF6COrn2g86E/c05xbktB0XN04m/t1Z+n+fTGw==}
|
||||
dev: false
|
||||
|
||||
/semver@5.7.2:
|
||||
resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==}
|
||||
hasBin: true
|
||||
@@ -11041,6 +11559,13 @@ packages:
|
||||
crypto-random-string: 2.0.0
|
||||
dev: true
|
||||
|
||||
/unist-util-find-after@5.0.0:
|
||||
resolution: {integrity: sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==}
|
||||
dependencies:
|
||||
'@types/unist': 3.0.2
|
||||
unist-util-is: 6.0.0
|
||||
dev: false
|
||||
|
||||
/unist-util-is@6.0.0:
|
||||
resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==}
|
||||
dependencies:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// eslint-disable-next-line no-undef
|
||||
export default {
|
||||
module.exports = {
|
||||
plugins: {
|
||||
"postcss-preset-mantine": {},
|
||||
"postcss-simple-vars": {
|
||||
1
public/_redirects
Normal file
1
public/_redirects
Normal file
@@ -0,0 +1 @@
|
||||
/* /index.html 200
|
||||
19
src/component/Hits/Hits.module.css
Normal file
19
src/component/Hits/Hits.module.css
Normal file
@@ -0,0 +1,19 @@
|
||||
.img-wrapper{
|
||||
column-count: 3;
|
||||
column-gap: 10px;
|
||||
|
||||
counter-reset: count;
|
||||
margin: 0 auto;
|
||||
|
||||
@media (max-width: $mantine-breakpoint-md) {
|
||||
column-count: 2;
|
||||
}
|
||||
@media (max-width: $mantine-breakpoint-sm) {
|
||||
column-count: 1;
|
||||
}
|
||||
|
||||
&>div{
|
||||
position: relative;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
17
src/component/Hits/Hits.tsx
Normal file
17
src/component/Hits/Hits.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { Box } from "@mantine/core";
|
||||
import { useHits, UseHitsProps } from "react-instantsearch";
|
||||
import { ParagraphCard } from "../ParagraphCard/ParagraphCard";
|
||||
import styles from "./Hits.module.css";
|
||||
|
||||
export default function Hits(props: UseHitsProps<Paragraph>) {
|
||||
const { results } = useHits(props);
|
||||
return (
|
||||
<Box className={styles["img-wrapper"]}>
|
||||
{results?.hits?.map((hit) => (
|
||||
<Box key={hit.id}>
|
||||
<ParagraphCard {...hit} key={`paragraph-card-${hit.id}`} />
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
18
src/component/Pagination/Pagination.tsx
Normal file
18
src/component/Pagination/Pagination.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Center, Group, Pagination as MantinePagination } from "@mantine/core";
|
||||
import { usePagination, UsePaginationProps } from "react-instantsearch";
|
||||
|
||||
export default function Pagination(props: UsePaginationProps) {
|
||||
const { currentRefinement, nbPages, refine } = usePagination(props);
|
||||
|
||||
return (
|
||||
<Center>
|
||||
<Group>
|
||||
<MantinePagination
|
||||
total={nbPages}
|
||||
value={currentRefinement + 1}
|
||||
onChange={(value) => refine(value - 1)}
|
||||
/>
|
||||
</Group>
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
@@ -8,47 +8,33 @@ dayjs.extend(relativeTime);
|
||||
export function ParagraphCard({
|
||||
cover,
|
||||
title,
|
||||
"@timestamp": time,
|
||||
time,
|
||||
author,
|
||||
tags,
|
||||
_id,
|
||||
id,
|
||||
}: Paragraph) {
|
||||
const url = `/paragraph/${_id}`;
|
||||
const url = `/paragraph/${id}`;
|
||||
return (
|
||||
<Card withBorder radius="md" padding="lg" shadow="sm">
|
||||
<Card.Section>
|
||||
<Link to={url}>
|
||||
<Link to={url} target="_blank">
|
||||
{cover && <Image src={cover} height={140} width={140} />}
|
||||
</Link>
|
||||
</Card.Section>
|
||||
<Group justify="space-between" mt="md" mb="xs">
|
||||
<Text component={Link} to={url}>
|
||||
<Text component={Link} to={url} target="_blank">
|
||||
{title}
|
||||
</Text>
|
||||
<Group>
|
||||
{tags.map((tag, index) => (
|
||||
<>
|
||||
<Badge
|
||||
component={Link}
|
||||
key={index}
|
||||
to={`/tag/${encodeURIComponent(tag)}`}
|
||||
>
|
||||
{tag}
|
||||
</Badge>
|
||||
</>
|
||||
<Badge key={index}>{tag}</Badge>
|
||||
))}
|
||||
</Group>
|
||||
</Group>
|
||||
|
||||
<Group>
|
||||
<Group>
|
||||
<Text
|
||||
size="xs"
|
||||
component={Link}
|
||||
to={`/author/${encodeURIComponent(author)}`}
|
||||
>
|
||||
{author}
|
||||
</Text>
|
||||
<Text size="xs">{author}</Text>
|
||||
</Group>
|
||||
<Text size="xs" c="dimmed">
|
||||
•
|
||||
|
||||
123
src/component/Refinement/Refinement.tsx
Normal file
123
src/component/Refinement/Refinement.tsx
Normal file
@@ -0,0 +1,123 @@
|
||||
import { SourceLabelMap } from "@/constants";
|
||||
import {
|
||||
Badge,
|
||||
Box,
|
||||
CheckIcon,
|
||||
ComboboxItem,
|
||||
ComboboxLikeRenderOptionInput,
|
||||
Group,
|
||||
MultiSelect,
|
||||
rem,
|
||||
Select,
|
||||
} from "@mantine/core";
|
||||
import { useEffect } from "react";
|
||||
import {
|
||||
useHitsPerPage,
|
||||
useRefinementList,
|
||||
useSortBy,
|
||||
} from "react-instantsearch";
|
||||
|
||||
const sortItems = [{ value: "paragraph:time:desc", label: "Newest" }];
|
||||
const hitsPerPageItems = [
|
||||
{ value: 20, label: "20", default: true },
|
||||
{ value: 40, label: "40" },
|
||||
{ value: 60, label: "60" },
|
||||
{ value: 100, label: "100" },
|
||||
];
|
||||
|
||||
export default function Refinement() {
|
||||
const { items, refine } = useRefinementList({ attribute: "source" });
|
||||
const { currentRefinement, refine: refineSortBy } = useSortBy({
|
||||
items: sortItems,
|
||||
});
|
||||
const hitsPerPage = useHitsPerPage({
|
||||
items: hitsPerPageItems,
|
||||
});
|
||||
|
||||
const currentVal = items
|
||||
.filter((item) => item.isRefined)
|
||||
.map((item) => item.value);
|
||||
|
||||
useEffect(() => {
|
||||
refineSortBy(sortItems[0].value);
|
||||
}, [refineSortBy]);
|
||||
|
||||
function SelectItem(props: ComboboxLikeRenderOptionInput<ComboboxItem>) {
|
||||
return (
|
||||
<Group justify="space-between" w="100%">
|
||||
<Box
|
||||
style={{
|
||||
gap: "0.5em",
|
||||
display: "flex",
|
||||
justifyContent: "flex-start",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
{currentVal.includes(props.option.value) && (
|
||||
<CheckIcon
|
||||
style={{
|
||||
opacity: "0.4",
|
||||
width: "0.8em",
|
||||
minWidth: "0.8em",
|
||||
height: "0.8em",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{props.option.label}
|
||||
</Box>
|
||||
<Badge>
|
||||
{items.find((item) => item.value === props.option.value)?.count}
|
||||
</Badge>
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Group justify="space-between" align="center" my="md">
|
||||
<Group>
|
||||
<Select
|
||||
data={sortItems}
|
||||
value={currentRefinement}
|
||||
defaultValue={sortItems[0].value}
|
||||
onChange={(value) => value && refineSortBy(value)}
|
||||
label="Sort by"
|
||||
/>
|
||||
<MultiSelect
|
||||
styles={{
|
||||
wrapper: {
|
||||
width: rem(300),
|
||||
},
|
||||
}}
|
||||
data={items.map((item) => ({
|
||||
value: item.label,
|
||||
label: SourceLabelMap[item.label],
|
||||
}))}
|
||||
renderOption={SelectItem}
|
||||
value={currentVal}
|
||||
label="Source"
|
||||
clearable
|
||||
onChange={(values) => {
|
||||
console.log(values);
|
||||
const old = new Set(currentVal);
|
||||
const new_ = new Set(values);
|
||||
const diff = old.difference(new_).union(new_.difference(old));
|
||||
diff.forEach((value) => refine(value));
|
||||
}}
|
||||
/>
|
||||
</Group>
|
||||
<Group w={rem(96)}>
|
||||
<Select
|
||||
data={hitsPerPageItems.map((item) => ({
|
||||
value: item.value.toString(),
|
||||
label: item.label.toString(),
|
||||
}))}
|
||||
value={hitsPerPage.items
|
||||
.find((item) => item.isRefined)
|
||||
?.value.toString()}
|
||||
onChange={(value) => value && hitsPerPage.refine(parseInt(value))}
|
||||
label="Hits per page"
|
||||
/>
|
||||
</Group>
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
25
src/component/ScrollTop/ScrollTop.tsx
Normal file
25
src/component/ScrollTop/ScrollTop.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import { Affix, Button, rem, Transition } from "@mantine/core";
|
||||
import { useWindowScroll } from "@mantine/hooks";
|
||||
import { IconArrowUp } from "@tabler/icons-react";
|
||||
|
||||
export default function ScrollTop() {
|
||||
const [scroll, scrollTo] = useWindowScroll();
|
||||
|
||||
return (
|
||||
<Affix position={{ bottom: 20, right: 20 }}>
|
||||
<Transition transition="slide-up" mounted={scroll.y > 0}>
|
||||
{(transitionStyles) => (
|
||||
<Button
|
||||
leftSection={
|
||||
<IconArrowUp style={{ width: rem(16), height: rem(16) }} />
|
||||
}
|
||||
style={transitionStyles}
|
||||
onClick={() => scrollTo({ y: 0 })}
|
||||
>
|
||||
Scroll to top
|
||||
</Button>
|
||||
)}
|
||||
</Transition>
|
||||
</Affix>
|
||||
);
|
||||
}
|
||||
43
src/component/SearchBox/SearchBox.tsx
Normal file
43
src/component/SearchBox/SearchBox.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import { rem, TextInput } from "@mantine/core";
|
||||
import { IconSearch } from "@tabler/icons-react";
|
||||
import { useCallback, useState } from "react";
|
||||
import { useSearchBox, UseSearchBoxProps } from "react-instantsearch";
|
||||
|
||||
export default function SearchBox(props: UseSearchBoxProps) {
|
||||
const { query, refine } = useSearchBox(props);
|
||||
const [inputValue, setInputValue] = useState(query);
|
||||
|
||||
const setQuery = useCallback(
|
||||
(value: string) => {
|
||||
setInputValue(value);
|
||||
refine(value);
|
||||
},
|
||||
[refine, setInputValue],
|
||||
);
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<TextInput
|
||||
placeholder="Search"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
type="search"
|
||||
leftSection={
|
||||
<IconSearch
|
||||
style={{ width: rem(16), height: rem(16) }}
|
||||
stroke={1.5}
|
||||
/>
|
||||
}
|
||||
value={inputValue}
|
||||
onChange={(event) => setQuery(event.currentTarget.value)}
|
||||
/>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
9
src/constants.ts
Normal file
9
src/constants.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export const SourceLabelMap: Record<string, string> = {
|
||||
tttang: "跳跳糖",
|
||||
secin: "Sec-In",
|
||||
seebug: "Seebug",
|
||||
wechat: "微信公众号",
|
||||
xianzhi: "先知",
|
||||
anquanke: "安全客",
|
||||
freebuf: "FreeBuf",
|
||||
};
|
||||
@@ -1,72 +0,0 @@
|
||||
import { notifications } from "@mantine/notifications";
|
||||
import axios, { AxiosResponse } from "axios";
|
||||
import { merge } from "lodash";
|
||||
|
||||
export interface PaginationParams {
|
||||
skip?: number;
|
||||
take?: number;
|
||||
}
|
||||
|
||||
const api = axios.create({
|
||||
withCredentials: false,
|
||||
auth: {
|
||||
username: "viewer",
|
||||
password: "publicviewer1",
|
||||
},
|
||||
});
|
||||
|
||||
api.interceptors.response.use(
|
||||
(value: AxiosResponse<any, any>) => {
|
||||
if (value.data.error) {
|
||||
notifications.show({
|
||||
title: "API Error on " + value.config.url,
|
||||
message: value.data.error,
|
||||
color: "red",
|
||||
autoClose: true,
|
||||
});
|
||||
}
|
||||
return value;
|
||||
},
|
||||
(error: any) => {
|
||||
const value: AxiosResponse<any, any> = error.response;
|
||||
if (value.data.status !== 200) {
|
||||
notifications.show({
|
||||
title: "API Error on " + value.config.url,
|
||||
message: JSON.stringify(value.data),
|
||||
color: "red",
|
||||
autoClose: true,
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
export class SearchApi {
|
||||
static async search(
|
||||
baseUrl: string,
|
||||
query: ZincQueryForSDK,
|
||||
): Promise<SearchResponse> {
|
||||
const { data } = await api.post(
|
||||
new URL("/api/paragraph/_search", baseUrl).toString(),
|
||||
query,
|
||||
);
|
||||
return data;
|
||||
}
|
||||
static wrapParagraph(s3Url: string, paragraph: Paragraph) {
|
||||
const RE = /https:\/\/s3\.yoshino-s\.xyz/g;
|
||||
if (paragraph.cover) {
|
||||
paragraph.cover = paragraph.cover.replace(RE, s3Url);
|
||||
}
|
||||
paragraph.content = paragraph.content?.replace(RE, s3Url);
|
||||
|
||||
return paragraph;
|
||||
}
|
||||
static async getParagraph(baseUrl: string, id: string) {
|
||||
const { data } = await api.get(
|
||||
new URL(`/api/paragraph/_doc/${id}`, baseUrl).toString(),
|
||||
);
|
||||
return merge(data._source, {
|
||||
_id: data._id,
|
||||
"@timestamp": data["@timestamp"],
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
import { Pagination } from "@mantine/core";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
|
||||
import { useOptionsState } from "@/store/module/options";
|
||||
import { useDebounceCallback, useMediaQuery } from "@mantine/hooks";
|
||||
import { merge } from "lodash";
|
||||
import { SearchApi } from "./api";
|
||||
|
||||
export function usePaginationData<T>(query: ZincQueryForSDK) {
|
||||
const [params, setParams] = useSearchParams({
|
||||
page: "1",
|
||||
size: "10",
|
||||
});
|
||||
const { state: options } = useOptionsState();
|
||||
const [total, setTotal] = useState(0);
|
||||
const [data, setData] = useState<T[]>([]);
|
||||
const [take, _] = useState(parseInt(params.get("size") || "10"));
|
||||
const [page, setPage] = useState(parseInt(params.get("page") || "1"));
|
||||
const isMobile = useMediaQuery("(max-width: 768px)");
|
||||
|
||||
const update = useDebounceCallback(async function update() {
|
||||
console.log("query", query, page, take, options);
|
||||
const resp = await SearchApi.search(
|
||||
options.zincsearchUrl,
|
||||
merge(
|
||||
{
|
||||
search_type: "matchall",
|
||||
sort_fields: ["-@timestamp"],
|
||||
_source: ["title", "cover", "author", "tags"],
|
||||
},
|
||||
query,
|
||||
{
|
||||
from: (page - 1) * take,
|
||||
max_results: take,
|
||||
},
|
||||
),
|
||||
);
|
||||
setTotal(resp.hits.total.value);
|
||||
setData(
|
||||
resp.hits.hits.map((hit) =>
|
||||
SearchApi.wrapParagraph(
|
||||
options.s3Url,
|
||||
merge({ _id: hit._id, "@timestamp": hit["@timestamp"] }, hit._source),
|
||||
),
|
||||
) as T[],
|
||||
);
|
||||
}, 200);
|
||||
|
||||
useEffect(update, [query, page, take, options, update]);
|
||||
|
||||
useEffect(() => {
|
||||
setParams({
|
||||
size: take.toString(),
|
||||
page: page.toString(),
|
||||
});
|
||||
}, [take, page, setParams]);
|
||||
|
||||
return {
|
||||
data,
|
||||
page,
|
||||
pagination: (
|
||||
<Pagination
|
||||
size={isMobile ? "sm" : "md"}
|
||||
total={Math.ceil(total / take)}
|
||||
onChange={setPage}
|
||||
value={page}
|
||||
/>
|
||||
),
|
||||
};
|
||||
}
|
||||
@@ -1,55 +1,69 @@
|
||||
import SearchBox from "@/component/SearchBox/SearchBox";
|
||||
import {
|
||||
Affix,
|
||||
AppShell,
|
||||
Avatar,
|
||||
Button,
|
||||
Center,
|
||||
Group,
|
||||
Text,
|
||||
TextInput,
|
||||
Transition,
|
||||
UnstyledButton,
|
||||
rem,
|
||||
UnstyledButton,
|
||||
} from "@mantine/core";
|
||||
import { Suspense, useCallback, useState } from "react";
|
||||
import { Outlet, useNavigate } from "react-router";
|
||||
import { Suspense, useEffect, useState } from "react";
|
||||
import { Outlet, useLocation } from "react-router";
|
||||
|
||||
import { TitleContext } from "@/component/Header/Header";
|
||||
import ScrollTop from "@/component/ScrollTop/ScrollTop";
|
||||
import Loading from "@/page/Loading";
|
||||
|
||||
import { useForm } from "@mantine/form";
|
||||
import { useHeadroom, useMediaQuery, useWindowScroll } from "@mantine/hooks";
|
||||
import { IconArrowUp, IconSearch, IconSettings } from "@tabler/icons-react";
|
||||
import { useHeadroom } from "@mantine/hooks";
|
||||
import { IconSettings } from "@tabler/icons-react";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
import { useAppSelector } from "@/store";
|
||||
import {
|
||||
instantMeiliSearch,
|
||||
InstantMeiliSearchInstance,
|
||||
} from "@meilisearch/instant-meilisearch";
|
||||
import { InstantSearch } from "react-instantsearch";
|
||||
|
||||
import { singleIndex } from "instantsearch.js/es/lib/stateMappings";
|
||||
|
||||
export default function MainLayout() {
|
||||
const [title, setTitle] = useState("");
|
||||
const pinned = useHeadroom({ fixedAt: 60 });
|
||||
const selector = useAppSelector((state) => state.options);
|
||||
|
||||
const [scroll, scrollTo] = useWindowScroll();
|
||||
const isMobile = useMediaQuery("(max-width: 768px)");
|
||||
const path = useLocation().pathname;
|
||||
|
||||
const navigate = useNavigate();
|
||||
const isSearchPage = path === "/";
|
||||
|
||||
const form = useForm({
|
||||
initialValues: {
|
||||
search: "",
|
||||
const [searchClient, setSearchClient] =
|
||||
useState<InstantMeiliSearchInstance>();
|
||||
|
||||
useEffect(() => {
|
||||
const { meilisearchUrl, meilisearchToken } = selector;
|
||||
const { searchClient } = instantMeiliSearch(
|
||||
meilisearchUrl,
|
||||
meilisearchToken,
|
||||
{
|
||||
finitePagination: true,
|
||||
meiliSearchParams: {
|
||||
hybrid: {},
|
||||
attributesToRetrieve: [
|
||||
"cover",
|
||||
"title",
|
||||
"time",
|
||||
"author",
|
||||
"tags",
|
||||
"id",
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const search = useCallback(
|
||||
function submit({ search }: { search: string }) {
|
||||
console.log(search);
|
||||
navigate(`/search/${encodeURIComponent(search)}`);
|
||||
},
|
||||
[navigate],
|
||||
);
|
||||
setSearchClient(searchClient);
|
||||
}, [selector, setSearchClient]);
|
||||
|
||||
return (
|
||||
<TitleContext.Provider value={[title, setTitle]}>
|
||||
const shell = (
|
||||
<>
|
||||
<AppShell
|
||||
header={{ height: 60, collapsed: !pinned, offset: false }}
|
||||
padding="md"
|
||||
h="100vh"
|
||||
>
|
||||
<AppShell.Header>
|
||||
@@ -58,26 +72,9 @@ export default function MainLayout() {
|
||||
<Avatar fw={700} component={Link} to="/">
|
||||
DS
|
||||
</Avatar>
|
||||
{!isMobile && (
|
||||
<Text size="lg" fw={700} ml="sm">
|
||||
{title}
|
||||
</Text>
|
||||
)}
|
||||
</Group>
|
||||
|
||||
<Group>
|
||||
<form onSubmit={form.onSubmit(search)}>
|
||||
<TextInput
|
||||
placeholder="Search"
|
||||
{...form.getInputProps("search")}
|
||||
leftSection={
|
||||
<IconSearch
|
||||
style={{ width: rem(16), height: rem(16) }}
|
||||
stroke={1.5}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</form>
|
||||
{isSearchPage && <SearchBox />}
|
||||
<UnstyledButton component={Link} to="/settings">
|
||||
<Center>
|
||||
<IconSettings />
|
||||
@@ -92,21 +89,26 @@ export default function MainLayout() {
|
||||
</Suspense>
|
||||
</AppShell.Main>
|
||||
</AppShell>
|
||||
<Affix position={{ bottom: 20, right: 20 }}>
|
||||
<Transition transition="slide-up" mounted={scroll.y > 0}>
|
||||
{(transitionStyles) => (
|
||||
<Button
|
||||
leftSection={
|
||||
<IconArrowUp style={{ width: rem(16), height: rem(16) }} />
|
||||
}
|
||||
style={transitionStyles}
|
||||
onClick={() => scrollTo({ y: 0 })}
|
||||
<ScrollTop />
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{searchClient &&
|
||||
(isSearchPage ? (
|
||||
<InstantSearch
|
||||
searchClient={searchClient!}
|
||||
indexName="paragraph"
|
||||
routing={{
|
||||
stateMapping: singleIndex("paragraph"),
|
||||
}}
|
||||
>
|
||||
Scroll to top
|
||||
</Button>
|
||||
)}
|
||||
</Transition>
|
||||
</Affix>
|
||||
</TitleContext.Provider>
|
||||
{shell}
|
||||
</InstantSearch>
|
||||
) : (
|
||||
shell
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { TitleContext } from "@/component/Header/Header";
|
||||
import {
|
||||
Badge,
|
||||
Container,
|
||||
@@ -9,7 +8,6 @@ import {
|
||||
} from "@mantine/core";
|
||||
import dayjs from "dayjs";
|
||||
import relativeTime from "dayjs/plugin/relativeTime";
|
||||
import { useContext, useEffect } from "react";
|
||||
import { useLoaderData } from "react-router";
|
||||
|
||||
import { Link } from "react-router-dom";
|
||||
@@ -55,12 +53,10 @@ function stripStyles(content: string) {
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
export default function ParagraphPage() {
|
||||
const [_title, setTitle] = useContext(TitleContext);
|
||||
const paragraph = useLoaderData() as Paragraph;
|
||||
console.log(paragraph);
|
||||
|
||||
useEffect(() => {
|
||||
setTitle(paragraph.title);
|
||||
}, [setTitle, paragraph.title]);
|
||||
const content = stripStyles(paragraph.content);
|
||||
|
||||
return (
|
||||
<Container py="2rem">
|
||||
@@ -69,7 +65,7 @@ export default function ParagraphPage() {
|
||||
<Group>
|
||||
<Text size="sm" c="dimmed">
|
||||
{" "}
|
||||
{dayjs().to(dayjs(paragraph["@timestamp"]))}
|
||||
{dayjs().to(dayjs(paragraph.time))}
|
||||
</Text>
|
||||
<Text
|
||||
ml="1rem"
|
||||
@@ -109,7 +105,7 @@ export default function ParagraphPage() {
|
||||
lineBreak: "anywhere",
|
||||
}}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: stripStyles(paragraph.content),
|
||||
__html: content,
|
||||
}}
|
||||
/>
|
||||
</TypographyStylesProvider>
|
||||
|
||||
@@ -1,51 +1,19 @@
|
||||
import { Container, Grid, Group } from "@mantine/core";
|
||||
import { useContext, useEffect } from "react";
|
||||
import { useLoaderData, useLocation, useParams } from "react-router";
|
||||
import { Container, Grid, Stack } from "@mantine/core";
|
||||
|
||||
import { ParagraphCard } from "../component/ParagraphCard/ParagraphCard";
|
||||
|
||||
import { TitleContext } from "@/component/Header/Header";
|
||||
import { usePaginationData } from "@/helper/hooks";
|
||||
import Hits from "@/component/Hits/Hits";
|
||||
import Pagination from "@/component/Pagination/Pagination";
|
||||
import Refinement from "@/component/Refinement/Refinement";
|
||||
|
||||
export default function SearchPage() {
|
||||
const [_title, setTitle] = useContext(TitleContext);
|
||||
|
||||
const params = useLoaderData() as ZincQueryForSDK;
|
||||
|
||||
const {
|
||||
page,
|
||||
pagination,
|
||||
data: paragraphs,
|
||||
} = usePaginationData<Paragraph>(params);
|
||||
|
||||
const location = useLocation();
|
||||
const param = useParams();
|
||||
|
||||
useEffect(() => {
|
||||
let action = "Index";
|
||||
if (location.pathname.startsWith("/search/")) {
|
||||
action = "Search";
|
||||
} else if (location.pathname.startsWith("/tag/")) {
|
||||
action = `Tag ${param.tag}`;
|
||||
} else if (location.pathname.startsWith("/author/")) {
|
||||
action = `Author ${param.author}`;
|
||||
}
|
||||
const title = `${action} Page ${page}`;
|
||||
setTitle(title);
|
||||
}, [page, location, param, setTitle]);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Stack>
|
||||
<Refinement />
|
||||
<Grid my="md">
|
||||
{paragraphs.map((paragraph) => {
|
||||
return (
|
||||
<Grid.Col span={{ base: 12, sm: 6 }} key={paragraph._id}>
|
||||
<ParagraphCard {...paragraph} key={`${paragraph._id}_card`} />
|
||||
</Grid.Col>
|
||||
);
|
||||
})}
|
||||
<Hits />
|
||||
</Grid>
|
||||
<Group justify="center">{pagination}</Group>
|
||||
<Pagination />
|
||||
</Stack>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,11 @@ import { TitleContext } from "@/component/Header/Header";
|
||||
import { ThemeSetting } from "@/component/Settings/Theme";
|
||||
import store from "@/store";
|
||||
import { useOptionsState } from "@/store/module/options";
|
||||
import { setS3Url, setZincsearchUrl } from "@/store/reducer/options";
|
||||
import {
|
||||
setMeilisearchToken,
|
||||
setMeilisearchUrl,
|
||||
setS3Url,
|
||||
} from "@/store/reducer/options";
|
||||
|
||||
interface SettingItem {
|
||||
title: string;
|
||||
@@ -40,13 +44,25 @@ export default function SettingsPage() {
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Zincsearch URL",
|
||||
description: "The URL of your Zincsearch instance",
|
||||
title: "Meilisearch URL",
|
||||
description: "The URL of your Meilisearch instance",
|
||||
value: (
|
||||
<TextInput
|
||||
value={options.zincsearchUrl}
|
||||
value={options.meilisearchUrl}
|
||||
onChange={(e) => {
|
||||
store.dispatch(setZincsearchUrl(e.currentTarget.value));
|
||||
store.dispatch(setMeilisearchUrl(e.currentTarget.value));
|
||||
}}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Meilisearch Token",
|
||||
description: "The token of your Meilisearch instance",
|
||||
value: (
|
||||
<TextInput
|
||||
value={options.meilisearchToken}
|
||||
onChange={(e) => {
|
||||
store.dispatch(setMeilisearchToken(e.currentTarget.value));
|
||||
}}
|
||||
/>
|
||||
),
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
/* eslint-disable react-refresh/only-export-components */
|
||||
import { lazy } from "react";
|
||||
import { createHashRouter } from "react-router-dom";
|
||||
import { remark } from "remark";
|
||||
import remarkHtml from "remark-html";
|
||||
import { createBrowserRouter } from "react-router-dom";
|
||||
|
||||
import { SearchApi } from "@/helper/api";
|
||||
import MainLayout from "@/layout/MainLayout";
|
||||
import SearchPage from "@/page/Search";
|
||||
import store from "@/store";
|
||||
import { markdownToHtml } from "@/utils/remark";
|
||||
import { MeiliSearch } from "meilisearch";
|
||||
|
||||
const NotFound = lazy(() => import("@/page/Exception/NotFound"));
|
||||
const ErrorPage = lazy(() => import("@/page/Exception/ErrorPage"));
|
||||
const LoadingPage = lazy(async () => import("@/page/Loading"));
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const ParagraphPage = lazy(async () => import("@/page/Paragraph"));
|
||||
const SettingsPage = lazy(async () => import("@/page/Settings"));
|
||||
|
||||
const router = createHashRouter([
|
||||
const router = createBrowserRouter(
|
||||
[
|
||||
{
|
||||
path: "/",
|
||||
element: <MainLayout />,
|
||||
@@ -31,51 +33,7 @@ const router = createHashRouter([
|
||||
path: "/settings",
|
||||
element: <SettingsPage />,
|
||||
},
|
||||
{
|
||||
path: "/tag/:tag",
|
||||
element: <SearchPage />,
|
||||
loader({ params: { tag } }) {
|
||||
if (!tag) {
|
||||
return { redirect: "/" };
|
||||
}
|
||||
return {
|
||||
search_type: "querystring",
|
||||
query: {
|
||||
term: `tags:${JSON.stringify(tag)}`,
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/author/:author",
|
||||
element: <SearchPage />,
|
||||
loader({ params: { author } }) {
|
||||
if (!author) {
|
||||
return { redirect: "/" };
|
||||
}
|
||||
return {
|
||||
search_type: "querystring",
|
||||
query: {
|
||||
term: `author:${JSON.stringify(author)}`,
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/search/:search",
|
||||
element: <SearchPage />,
|
||||
loader({ params: { search } }) {
|
||||
if (!search) {
|
||||
return { redirect: "/" };
|
||||
}
|
||||
return {
|
||||
search_type: "querystring",
|
||||
query: {
|
||||
term: search,
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
path: "/paragraph/:id",
|
||||
element: <ParagraphPage />,
|
||||
@@ -84,18 +42,19 @@ const router = createHashRouter([
|
||||
return { redirect: "/" };
|
||||
}
|
||||
|
||||
const paragraph = await SearchApi.getParagraph(
|
||||
store.getState().options.zincsearchUrl,
|
||||
id,
|
||||
).then((p) =>
|
||||
SearchApi.wrapParagraph(store.getState().options.s3Url, p),
|
||||
);
|
||||
const meilisearch = new MeiliSearch({
|
||||
host: store.getState().options.meilisearchUrl,
|
||||
apiKey: store.getState().options.meilisearchToken,
|
||||
});
|
||||
|
||||
const paragraph: Paragraph = await meilisearch
|
||||
.index("paragraph")
|
||||
.getDocument(id);
|
||||
|
||||
console.log(paragraph.markdown);
|
||||
if (paragraph.markdown) {
|
||||
paragraph.content = (
|
||||
await remark().use(remarkHtml).process(paragraph.content)
|
||||
).toString();
|
||||
paragraph.content = await markdownToHtml(paragraph.content);
|
||||
} else {
|
||||
paragraph.content = "NO HTML!";
|
||||
}
|
||||
|
||||
return paragraph;
|
||||
@@ -111,6 +70,8 @@ const router = createHashRouter([
|
||||
path: "*",
|
||||
element: <NotFound />,
|
||||
},
|
||||
]);
|
||||
],
|
||||
{},
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -1,29 +1,35 @@
|
||||
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
|
||||
|
||||
export interface OptionsState {
|
||||
zincsearchUrl: string;
|
||||
meilisearchUrl: string;
|
||||
meilisearchToken: string;
|
||||
s3Url: string;
|
||||
}
|
||||
|
||||
const ZINCSEARCH_URL = "https://zincsearch.yoshino-s.xyz";
|
||||
const MINIO_URL = "https://minio-hdd.yoshino-s.xyz";
|
||||
const initialState: OptionsState = {
|
||||
meilisearchUrl: "https://meilisearch.yoshino-s.xyz/",
|
||||
meilisearchToken:
|
||||
"a568afad53a4dd124c508b9acd26ec35ff65665c07020913533cd7b176a28a04",
|
||||
s3Url: "https://minio-hdd.yoshino-s.xyz",
|
||||
};
|
||||
|
||||
const optionsSlice = createSlice({
|
||||
name: "stats",
|
||||
initialState: {
|
||||
zincsearchUrl: ZINCSEARCH_URL,
|
||||
s3Url: MINIO_URL,
|
||||
} as OptionsState,
|
||||
initialState,
|
||||
reducers: {
|
||||
setZincsearchUrl: (state, action: PayloadAction<string | undefined>) => {
|
||||
state.zincsearchUrl = action.payload ?? ZINCSEARCH_URL;
|
||||
setMeilisearchUrl: (state, action: PayloadAction<string | undefined>) => {
|
||||
state.meilisearchUrl = action.payload ?? initialState.meilisearchUrl;
|
||||
},
|
||||
setMeilisearchToken: (state, action: PayloadAction<string | undefined>) => {
|
||||
state.meilisearchToken = action.payload ?? initialState.meilisearchToken;
|
||||
},
|
||||
setS3Url: (state, action: PayloadAction<string | undefined>) => {
|
||||
state.s3Url = action.payload ?? MINIO_URL;
|
||||
state.s3Url = action.payload ?? initialState.s3Url;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { setS3Url, setZincsearchUrl } = optionsSlice.actions;
|
||||
export const { setS3Url, setMeilisearchUrl, setMeilisearchToken } =
|
||||
optionsSlice.actions;
|
||||
|
||||
export default optionsSlice.reducer;
|
||||
|
||||
4
src/types/paragraph.d.ts
vendored
4
src/types/paragraph.d.ts
vendored
@@ -1,6 +1,6 @@
|
||||
declare interface Paragraph {
|
||||
_id: string;
|
||||
"@timestamp": string;
|
||||
id: string;
|
||||
time: string;
|
||||
content: string;
|
||||
markdown: string;
|
||||
title: string;
|
||||
|
||||
71
src/utils/remark.ts
Normal file
71
src/utils/remark.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import rehypeHighlight from "rehype-highlight";
|
||||
import rehypeStringify from "rehype-stringify";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import remarkParse from "remark-parse";
|
||||
import remarkRehype from "remark-rehype";
|
||||
import { unified } from "unified";
|
||||
|
||||
export async function markdownToHtml(markdown: string) {
|
||||
const result = await unified()
|
||||
.use(remarkParse)
|
||||
.use(remarkRehype)
|
||||
.use(remarkGfm)
|
||||
.use(rehypeHighlight, {
|
||||
detect: true,
|
||||
})
|
||||
// .use(rehypeSanitize, {
|
||||
// ...defaultSchema,
|
||||
// attributes: {
|
||||
// ...defaultSchema.attributes,
|
||||
// span: [
|
||||
// ...(defaultSchema.attributes?.span || []),
|
||||
// [
|
||||
// "className",
|
||||
// "hljs-addition",
|
||||
// "hljs-attr",
|
||||
// "hljs-attribute",
|
||||
// "hljs-built_in",
|
||||
// "hljs-bullet",
|
||||
// "hljs-char",
|
||||
// "hljs-code",
|
||||
// "hljs-comment",
|
||||
// "hljs-deletion",
|
||||
// "hljs-doctag",
|
||||
// "hljs-emphasis",
|
||||
// "hljs-formula",
|
||||
// "hljs-keyword",
|
||||
// "hljs-link",
|
||||
// "hljs-literal",
|
||||
// "hljs-meta",
|
||||
// "hljs-name",
|
||||
// "hljs-number",
|
||||
// "hljs-operator",
|
||||
// "hljs-params",
|
||||
// "hljs-property",
|
||||
// "hljs-punctuation",
|
||||
// "hljs-quote",
|
||||
// "hljs-regexp",
|
||||
// "hljs-section",
|
||||
// "hljs-selector-attr",
|
||||
// "hljs-selector-class",
|
||||
// "hljs-selector-id",
|
||||
// "hljs-selector-pseudo",
|
||||
// "hljs-selector-tag",
|
||||
// "hljs-string",
|
||||
// "hljs-strong",
|
||||
// "hljs-subst",
|
||||
// "hljs-symbol",
|
||||
// "hljs-tag",
|
||||
// "hljs-template-tag",
|
||||
// "hljs-template-variable",
|
||||
// "hljs-title",
|
||||
// "hljs-type",
|
||||
// "hljs-variable",
|
||||
// ],
|
||||
// ],
|
||||
// },
|
||||
// })
|
||||
.use(rehypeStringify)
|
||||
.process(markdown);
|
||||
return result.toString();
|
||||
}
|
||||
Reference in New Issue
Block a user