Updated meilisearch
This commit is contained in:
		
							parent
							
								
									0f735c62cc
								
							
						
					
					
						commit
						5be25c656f
					
				| @ -46,7 +46,7 @@ module.exports = { | ||||
|     "func-style": ["warn", "expression"], | ||||
|     "grouped-accessor-pairs": "warn", | ||||
|     "guard-for-in": "warn", | ||||
|     "id-denylist": ["error", "data", "err", "e", "cb", "callback", "i"], | ||||
|     "id-denylist": ["error", "err", "e", "cb", "callback", "i"], | ||||
|     // "id-length": "warn",
 | ||||
|     "id-match": "warn", | ||||
|     "max-classes-per-file": ["error", 1], | ||||
|  | ||||
							
								
								
									
										28
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										28
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -20,7 +20,7 @@ | ||||
|         "markdown-to-jsx": "^7.2.0", | ||||
|         "marked": "^4.3.0", | ||||
|         "material-symbols": "^0.5.5", | ||||
|         "meilisearch": "^0.31.1", | ||||
|         "meilisearch": "^0.32.3", | ||||
|         "next": "^13.3.0", | ||||
|         "nodemailer": "^6.9.1", | ||||
|         "rc-slider": "^10.1.1", | ||||
| @ -60,7 +60,7 @@ | ||||
|         "eslint-plugin-import": "^2.27.5", | ||||
|         "graphql": "^16.6.0", | ||||
|         "graphql-request": "5.1.0", | ||||
|         "next-sitemap": "^4.0.6", | ||||
|         "next-sitemap": "^4.0.7", | ||||
|         "prettier": "^2.8.7", | ||||
|         "prettier-plugin-tailwindcss": "^0.2.7", | ||||
|         "tailwindcss": "^3.3.1", | ||||
| @ -8021,9 +8021,9 @@ | ||||
|       "integrity": "sha512-NFUsjEVBNZvcRRqslY0RWnmlGgjhJkpDQkQs42o52gT2AmIbaP6V7wTRgyTkLAoD5VtpgpIx9eoOAXcH2ynwkg==" | ||||
|     }, | ||||
|     "node_modules/meilisearch": { | ||||
|       "version": "0.31.1", | ||||
|       "resolved": "https://registry.npmjs.org/meilisearch/-/meilisearch-0.31.1.tgz", | ||||
|       "integrity": "sha512-ajMieU0e25lLkT+05J0snX0Ycow1UofxIy5sag03flERUbjXq8ouVwkrJkW27JsKftIeDeffRRRr89LasU9+0w==", | ||||
|       "version": "0.32.3", | ||||
|       "resolved": "https://registry.npmjs.org/meilisearch/-/meilisearch-0.32.3.tgz", | ||||
|       "integrity": "sha512-EOgfBuRE5SiIPIpEDYe2HO0D7a4z5bexIgaAdJFma/dH5hx1kwO+u/qb2g3qKyjG+iA3l8MlmTj/Xd72uahaAw==", | ||||
|       "dependencies": { | ||||
|         "cross-fetch": "^3.1.5" | ||||
|       } | ||||
| @ -8208,9 +8208,9 @@ | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/next-sitemap": { | ||||
|       "version": "4.0.6", | ||||
|       "resolved": "https://registry.npmjs.org/next-sitemap/-/next-sitemap-4.0.6.tgz", | ||||
|       "integrity": "sha512-pZ9tynYe6mRR189qZqcOlWVgM1Gxo07BJQW0AjerKmLwQOt+6FQMdaDgifgCt6jDT3Y3EG/+NUDDZRcd0gbPkA==", | ||||
|       "version": "4.0.7", | ||||
|       "resolved": "https://registry.npmjs.org/next-sitemap/-/next-sitemap-4.0.7.tgz", | ||||
|       "integrity": "sha512-S2g5IwJeO0+ecmFq981fb+Mw9YWmntOuN/qTCxclSkUibOJ8qKIOye0vn6NEJ1S4tKhbY+MTYKgJpNdFZYxLoA==", | ||||
|       "dev": true, | ||||
|       "funding": [ | ||||
|         { | ||||
| @ -16972,9 +16972,9 @@ | ||||
|       "integrity": "sha512-NFUsjEVBNZvcRRqslY0RWnmlGgjhJkpDQkQs42o52gT2AmIbaP6V7wTRgyTkLAoD5VtpgpIx9eoOAXcH2ynwkg==" | ||||
|     }, | ||||
|     "meilisearch": { | ||||
|       "version": "0.31.1", | ||||
|       "resolved": "https://registry.npmjs.org/meilisearch/-/meilisearch-0.31.1.tgz", | ||||
|       "integrity": "sha512-ajMieU0e25lLkT+05J0snX0Ycow1UofxIy5sag03flERUbjXq8ouVwkrJkW27JsKftIeDeffRRRr89LasU9+0w==", | ||||
|       "version": "0.32.3", | ||||
|       "resolved": "https://registry.npmjs.org/meilisearch/-/meilisearch-0.32.3.tgz", | ||||
|       "integrity": "sha512-EOgfBuRE5SiIPIpEDYe2HO0D7a4z5bexIgaAdJFma/dH5hx1kwO+u/qb2g3qKyjG+iA3l8MlmTj/Xd72uahaAw==", | ||||
|       "requires": { | ||||
|         "cross-fetch": "^3.1.5" | ||||
|       } | ||||
| @ -17092,9 +17092,9 @@ | ||||
|       } | ||||
|     }, | ||||
|     "next-sitemap": { | ||||
|       "version": "4.0.6", | ||||
|       "resolved": "https://registry.npmjs.org/next-sitemap/-/next-sitemap-4.0.6.tgz", | ||||
|       "integrity": "sha512-pZ9tynYe6mRR189qZqcOlWVgM1Gxo07BJQW0AjerKmLwQOt+6FQMdaDgifgCt6jDT3Y3EG/+NUDDZRcd0gbPkA==", | ||||
|       "version": "4.0.7", | ||||
|       "resolved": "https://registry.npmjs.org/next-sitemap/-/next-sitemap-4.0.7.tgz", | ||||
|       "integrity": "sha512-S2g5IwJeO0+ecmFq981fb+Mw9YWmntOuN/qTCxclSkUibOJ8qKIOye0vn6NEJ1S4tKhbY+MTYKgJpNdFZYxLoA==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "@corex/deepmerge": "^4.0.37", | ||||
|  | ||||
| @ -16,7 +16,7 @@ | ||||
|     "generate": "graphql-codegen --config graphql-codegen.config.js", | ||||
|     "tsc": "tsc", | ||||
|     "prettier": "prettier --end-of-line auto --write .", | ||||
|     "update": "ncu --interactive --format group" | ||||
|     "upgrade": "ncu --interactive --format group" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "@fontsource/opendyslexic": "^4.5.4", | ||||
| @ -33,7 +33,7 @@ | ||||
|     "markdown-to-jsx": "^7.2.0", | ||||
|     "marked": "^4.3.0", | ||||
|     "material-symbols": "^0.5.5", | ||||
|     "meilisearch": "^0.31.1", | ||||
|     "meilisearch": "^0.32.3", | ||||
|     "next": "^13.3.0", | ||||
|     "nodemailer": "^6.9.1", | ||||
|     "rc-slider": "^10.1.1", | ||||
| @ -73,7 +73,7 @@ | ||||
|     "eslint-plugin-import": "^2.27.5", | ||||
|     "graphql": "^16.6.0", | ||||
|     "graphql-request": "5.1.0", | ||||
|     "next-sitemap": "^4.0.6", | ||||
|     "next-sitemap": "^4.0.7", | ||||
|     "prettier": "^2.8.7", | ||||
|     "prettier-plugin-tailwindcss": "^0.2.7", | ||||
|     "tailwindcss": "^3.3.1", | ||||
|  | ||||
| @ -1,5 +1,4 @@ | ||||
| // eslint-disable-next-line import/named
 | ||||
| import { Placement } from "tippy.js"; | ||||
| import type { Placement } from "tippy.js"; | ||||
| import { Button } from "./Button"; | ||||
| import { ToolTip } from "components/ToolTip"; | ||||
| import { cJoin } from "helpers/className"; | ||||
|  | ||||
| @ -5,9 +5,14 @@ import { sendAnalytics } from "helpers/analytics"; | ||||
| import { atoms } from "contexts/atoms"; | ||||
| import { useAtomPair, useAtomSetter } from "helpers/atoms"; | ||||
| import { TextInput } from "components/Inputs/TextInput"; | ||||
| import { containsHighlight, CustomSearchResponse, meiliSearch } from "helpers/search"; | ||||
| import { | ||||
|   containsHighlight, | ||||
|   CustomSearchResponse, | ||||
|   filterHitsWithHighlight, | ||||
|   meiliMultiSearch, | ||||
| } from "helpers/search"; | ||||
| import { PreviewCard, TranslatedPreviewCard } from "components/PreviewCard"; | ||||
| import { filterDefined, filterHasAttributes, isDefined } from "helpers/asserts"; | ||||
| import { filterHasAttributes, isDefined } from "helpers/asserts"; | ||||
| import { | ||||
|   MeiliContent, | ||||
|   MeiliIndices, | ||||
| @ -35,160 +40,154 @@ const SEARCH_LIMIT = 8; | ||||
|  * ────────────────────────────────────────╯  COMPONENT  ╰────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface MultiResult { | ||||
|   libraryItems?: CustomSearchResponse<MeiliLibraryItem>; | ||||
|   contents?: CustomSearchResponse<MeiliContent>; | ||||
|   videos?: CustomSearchResponse<MeiliVideo>; | ||||
|   posts?: CustomSearchResponse<MeiliPost>; | ||||
|   wikiPages?: CustomSearchResponse<MeiliWikiPage>; | ||||
|   weapons?: CustomSearchResponse<MeiliWeapon>; | ||||
| } | ||||
| 
 | ||||
| export const SearchPopup = (): JSX.Element => { | ||||
|   const [isSearchOpened, setSearchOpened] = useAtomPair(atoms.layout.searchOpened); | ||||
|   const [query, setQuery] = useState(""); | ||||
|   const { format } = useFormat(); | ||||
|   const [libraryItems, setLibraryItems] = useState<CustomSearchResponse<MeiliLibraryItem>>(); | ||||
|   const [contents, setContents] = useState<CustomSearchResponse<MeiliContent>>(); | ||||
|   const [videos, setVideos] = useState<CustomSearchResponse<MeiliVideo>>(); | ||||
|   const [posts, setPosts] = useState<CustomSearchResponse<MeiliPost>>(); | ||||
|   const [wikiPages, setWikiPages] = useState<CustomSearchResponse<MeiliWikiPage>>(); | ||||
|   const [weapons, setWeapons] = useState<CustomSearchResponse<MeiliWeapon>>(); | ||||
|   const [multiResult, setMultiResult] = useState<MultiResult>({}); | ||||
| 
 | ||||
|   useEffect(() => { | ||||
|     const fetchLibraryItems = async () => { | ||||
|       const searchResult = await meiliSearch(MeiliIndices.LIBRARY_ITEM, query, { | ||||
|         limit: SEARCH_LIMIT, | ||||
|         attributesToRetrieve: [ | ||||
|           "title", | ||||
|           "subtitle", | ||||
|           "descriptions", | ||||
|           "id", | ||||
|           "slug", | ||||
|           "thumbnail", | ||||
|           "release_date", | ||||
|           "price", | ||||
|           "categories", | ||||
|           "metadata", | ||||
|         ], | ||||
|         attributesToHighlight: ["title", "subtitle", "descriptions"], | ||||
|         attributesToCrop: ["descriptions"], | ||||
|       }); | ||||
|       searchResult.hits = searchResult.hits.map((item) => { | ||||
|         if (Object.keys(item._matchesPosition).some((match) => match.startsWith("descriptions"))) { | ||||
|           item._formatted.descriptions = filterDefined(item._formatted.descriptions).filter( | ||||
|             (description) => containsHighlight(JSON.stringify(description)) | ||||
|           ); | ||||
|         } | ||||
|         return item; | ||||
|       }); | ||||
|       setLibraryItems(searchResult); | ||||
|     }; | ||||
|     const fetchMultiResult = async () => { | ||||
|       const searchResults = ( | ||||
|         await meiliMultiSearch([ | ||||
|           { | ||||
|             indexUid: MeiliIndices.LIBRARY_ITEM, | ||||
|             q: query, | ||||
|             limit: SEARCH_LIMIT, | ||||
|             attributesToRetrieve: [ | ||||
|               "title", | ||||
|               "subtitle", | ||||
|               "descriptions", | ||||
|               "id", | ||||
|               "slug", | ||||
|               "thumbnail", | ||||
|               "release_date", | ||||
|               "price", | ||||
|               "categories", | ||||
|               "metadata", | ||||
|             ], | ||||
|             attributesToHighlight: ["title", "subtitle", "descriptions"], | ||||
|             attributesToCrop: ["descriptions"], | ||||
|           }, | ||||
|           { | ||||
|             indexUid: MeiliIndices.CONTENT, | ||||
|             q: query, | ||||
|             limit: SEARCH_LIMIT, | ||||
|             attributesToRetrieve: ["translations", "id", "slug", "categories", "type", "thumbnail"], | ||||
|             attributesToHighlight: ["translations"], | ||||
|             attributesToCrop: ["translations.displayable_description"], | ||||
|           }, | ||||
|           { | ||||
|             indexUid: MeiliIndices.VIDEOS, | ||||
|             q: query, | ||||
|             limit: SEARCH_LIMIT, | ||||
|             attributesToRetrieve: [ | ||||
|               "title", | ||||
|               "channel", | ||||
|               "uid", | ||||
|               "published_date", | ||||
|               "views", | ||||
|               "duration", | ||||
|               "description", | ||||
|             ], | ||||
|             attributesToHighlight: ["title", "channel", "description"], | ||||
|             attributesToCrop: ["description"], | ||||
|           }, | ||||
|           { | ||||
|             indexUid: MeiliIndices.POST, | ||||
|             q: query, | ||||
|             limit: SEARCH_LIMIT, | ||||
|             attributesToRetrieve: ["translations", "thumbnail", "slug", "date", "categories"], | ||||
|             attributesToHighlight: [ | ||||
|               "translations.title", | ||||
|               "translations.excerpt", | ||||
|               "translations.body", | ||||
|             ], | ||||
|             attributesToCrop: ["translations.body"], | ||||
|             filter: ["hidden = false"], | ||||
|           }, | ||||
|           { | ||||
|             indexUid: MeiliIndices.WEAPON, | ||||
|             q: query, | ||||
|             limit: SEARCH_LIMIT, | ||||
|             attributesToHighlight: ["translations.description", "translations.names"], | ||||
|             attributesToCrop: ["translations.description"], | ||||
|             sort: ["slug:asc"], | ||||
|           }, | ||||
|           { | ||||
|             indexUid: MeiliIndices.WIKI_PAGE, | ||||
|             q: query, | ||||
|             limit: SEARCH_LIMIT, | ||||
|             attributesToHighlight: [ | ||||
|               "translations.title", | ||||
|               "translations.aliases", | ||||
|               "translations.summary", | ||||
|               "translations.displayable_description", | ||||
|             ], | ||||
|             attributesToCrop: ["translations.displayable_description"], | ||||
|           }, | ||||
|         ]) | ||||
|       ).results; | ||||
| 
 | ||||
|     const fetchContents = async () => { | ||||
|       const searchResult = await meiliSearch(MeiliIndices.CONTENT, query, { | ||||
|         limit: SEARCH_LIMIT, | ||||
|         attributesToRetrieve: ["translations", "id", "slug", "categories", "type", "thumbnail"], | ||||
|         attributesToHighlight: ["translations"], | ||||
|         attributesToCrop: ["translations.displayable_description"], | ||||
|       }); | ||||
|       searchResult.hits = searchResult.hits.map((item) => { | ||||
|         if (Object.keys(item._matchesPosition).some((match) => match.startsWith("translations"))) { | ||||
|           item._formatted.translations = filterDefined(item._formatted.translations).filter( | ||||
|             (translation) => containsHighlight(JSON.stringify(translation)) | ||||
|           ); | ||||
|         } | ||||
|         return item; | ||||
|       }); | ||||
|       setContents(searchResult); | ||||
|     }; | ||||
|       const result: MultiResult = {}; | ||||
| 
 | ||||
|     const fetchVideos = async () => { | ||||
|       const searchResult = await meiliSearch(MeiliIndices.VIDEOS, query, { | ||||
|         limit: SEARCH_LIMIT, | ||||
|         attributesToRetrieve: [ | ||||
|           "title", | ||||
|           "channel", | ||||
|           "uid", | ||||
|           "published_date", | ||||
|           "views", | ||||
|           "duration", | ||||
|           "description", | ||||
|         ], | ||||
|         attributesToHighlight: ["title", "channel", "description"], | ||||
|         attributesToCrop: ["description"], | ||||
|       }); | ||||
|       setVideos(searchResult); | ||||
|     }; | ||||
|       searchResults.map((searchResult) => { | ||||
|         switch (searchResult.indexUid) { | ||||
|           case MeiliIndices.LIBRARY_ITEM: { | ||||
|             result.libraryItems = filterHitsWithHighlight<MeiliLibraryItem>( | ||||
|               searchResult, | ||||
|               "descriptions" | ||||
|             ); | ||||
|             break; | ||||
|           } | ||||
| 
 | ||||
|     const fetchPosts = async () => { | ||||
|       const searchResult = await meiliSearch(MeiliIndices.POST, query, { | ||||
|         limit: SEARCH_LIMIT, | ||||
|         attributesToRetrieve: ["translations", "thumbnail", "slug", "date", "categories"], | ||||
|         attributesToHighlight: ["translations.title", "translations.excerpt", "translations.body"], | ||||
|         attributesToCrop: ["translations.body"], | ||||
|         filter: ["hidden = false"], | ||||
|       }); | ||||
|       searchResult.hits = searchResult.hits.map((item) => { | ||||
|         if (Object.keys(item._matchesPosition).some((match) => match.startsWith("translations"))) { | ||||
|           item._formatted.translations = filterDefined(item._formatted.translations).filter( | ||||
|             (translation) => JSON.stringify(translation).includes("</mark>") | ||||
|           ); | ||||
|         } | ||||
|         return item; | ||||
|       }); | ||||
|       setPosts(searchResult); | ||||
|     }; | ||||
|           case MeiliIndices.CONTENT: { | ||||
|             result.contents = filterHitsWithHighlight<MeiliContent>(searchResult, "translations"); | ||||
|             break; | ||||
|           } | ||||
| 
 | ||||
|     const fetchWeapons = async () => { | ||||
|       const searchResult = await meiliSearch(MeiliIndices.WEAPON, query, { | ||||
|         limit: SEARCH_LIMIT, | ||||
|         attributesToRetrieve: ["*"], | ||||
|         attributesToHighlight: ["translations.description", "translations.names"], | ||||
|         attributesToCrop: ["translations.description"], | ||||
|         sort: ["slug:asc"], | ||||
|       }); | ||||
|       searchResult.hits = searchResult.hits.map((item) => { | ||||
|         if (Object.keys(item._matchesPosition).some((match) => match.startsWith("translations"))) { | ||||
|           item._formatted.translations = filterDefined(item._formatted.translations).filter( | ||||
|             (translation) => JSON.stringify(translation).includes("</mark>") | ||||
|           ); | ||||
|         } | ||||
|         return item; | ||||
|       }); | ||||
|       setWeapons(searchResult); | ||||
|     }; | ||||
|           case MeiliIndices.VIDEOS: { | ||||
|             result.videos = filterHitsWithHighlight<MeiliVideo>(searchResult); | ||||
|             break; | ||||
|           } | ||||
| 
 | ||||
|     const fetchWikiPages = async () => { | ||||
|       const searchResult = await meiliSearch(MeiliIndices.WIKI_PAGE, query, { | ||||
|         limit: SEARCH_LIMIT, | ||||
|         attributesToHighlight: [ | ||||
|           "translations.title", | ||||
|           "translations.aliases", | ||||
|           "translations.summary", | ||||
|           "translations.displayable_description", | ||||
|         ], | ||||
|         attributesToCrop: ["translations.displayable_description"], | ||||
|       }); | ||||
|       searchResult.hits = searchResult.hits.map((item) => { | ||||
|         if ( | ||||
|           Object.keys(item._matchesPosition).filter((match) => match.startsWith("translations")) | ||||
|             .length > 0 | ||||
|         ) { | ||||
|           item._formatted.translations = filterDefined(item._formatted.translations).filter( | ||||
|             (translation) => JSON.stringify(translation).includes("</mark>") | ||||
|           ); | ||||
|           case MeiliIndices.POST: { | ||||
|             result.posts = filterHitsWithHighlight<MeiliPost>(searchResult, "translations"); | ||||
|             break; | ||||
|           } | ||||
| 
 | ||||
|           case MeiliIndices.WEAPON: { | ||||
|             result.weapons = filterHitsWithHighlight<MeiliWeapon>(searchResult, "translations"); | ||||
|             break; | ||||
|           } | ||||
| 
 | ||||
|           case MeiliIndices.WIKI_PAGE: { | ||||
|             result.wikiPages = filterHitsWithHighlight<MeiliWikiPage>(searchResult, "translations"); | ||||
|             break; | ||||
|           } | ||||
| 
 | ||||
|           default: { | ||||
|             console.log("What the fuck?"); | ||||
|           } | ||||
|         } | ||||
|         return item; | ||||
|       }); | ||||
|       setWikiPages(searchResult); | ||||
| 
 | ||||
|       setMultiResult(result); | ||||
|     }; | ||||
| 
 | ||||
|     if (query === "") { | ||||
|       setWikiPages(undefined); | ||||
|       setLibraryItems(undefined); | ||||
|       setContents(undefined); | ||||
|       setVideos(undefined); | ||||
|       setPosts(undefined); | ||||
|       setWeapons(undefined); | ||||
|       setMultiResult({}); | ||||
|     } else { | ||||
|       fetchWikiPages(); | ||||
|       fetchLibraryItems(); | ||||
|       fetchContents(); | ||||
|       fetchVideos(); | ||||
|       fetchPosts(); | ||||
|       fetchWeapons(); | ||||
|       fetchMultiResult(); | ||||
|     } | ||||
|   }, [query]); | ||||
| 
 | ||||
| @ -207,15 +206,15 @@ export const SearchPopup = (): JSX.Element => { | ||||
|       <TextInput onChange={setQuery} value={query} placeholder={format("search_title")} /> | ||||
| 
 | ||||
|       <div className="flex w-full flex-wrap gap-12 gap-x-16"> | ||||
|         {isDefined(libraryItems) && ( | ||||
|         {isDefined(multiResult.libraryItems) && ( | ||||
|           <SearchResultSection | ||||
|             title={format("library")} | ||||
|             icon="auto_stories" | ||||
|             href={`/library?page=1&query=${query}\ | ||||
| &sort=0&primary=true&secondary=true&subitems=true&status=all`}
 | ||||
|             totalHits={libraryItems.estimatedTotalHits}> | ||||
|             totalHits={multiResult.libraryItems.estimatedTotalHits}> | ||||
|             <div className="flex flex-wrap items-start gap-x-6 gap-y-8"> | ||||
|               {libraryItems.hits.map((item) => ( | ||||
|               {multiResult.libraryItems.hits.map((item) => ( | ||||
|                 <TranslatedPreviewCard | ||||
|                   key={item.id} | ||||
|                   className="w-56" | ||||
| @ -255,14 +254,14 @@ export const SearchPopup = (): JSX.Element => { | ||||
|           </SearchResultSection> | ||||
|         )} | ||||
| 
 | ||||
|         {isDefined(contents) && ( | ||||
|         {isDefined(multiResult.contents) && ( | ||||
|           <SearchResultSection | ||||
|             title={format("contents")} | ||||
|             icon="workspaces" | ||||
|             href={`/contents/all?page=1&query=${query}&sort=0`} | ||||
|             totalHits={contents.estimatedTotalHits}> | ||||
|             totalHits={multiResult.contents.estimatedTotalHits}> | ||||
|             <div className="flex flex-wrap items-start gap-x-6 gap-y-8"> | ||||
|               {contents.hits.map((item) => ( | ||||
|               {multiResult.contents.hits.map((item) => ( | ||||
|                 <TranslatedPreviewCard | ||||
|                   key={item.id} | ||||
|                   className="w-56" | ||||
| @ -300,14 +299,14 @@ export const SearchPopup = (): JSX.Element => { | ||||
|           </SearchResultSection> | ||||
|         )} | ||||
| 
 | ||||
|         {isDefined(wikiPages) && ( | ||||
|         {isDefined(multiResult.wikiPages) && ( | ||||
|           <SearchResultSection | ||||
|             title={format("wiki")} | ||||
|             icon="travel_explore" | ||||
|             href={`/wiki?page=1&query=${query}`} | ||||
|             totalHits={wikiPages.estimatedTotalHits}> | ||||
|             totalHits={multiResult.wikiPages.estimatedTotalHits}> | ||||
|             <div className="flex flex-wrap items-start gap-x-6 gap-y-8"> | ||||
|               {wikiPages.hits.map((item) => ( | ||||
|               {multiResult.wikiPages.hits.map((item) => ( | ||||
|                 <TranslatedPreviewCard | ||||
|                   key={item.id} | ||||
|                   className="w-56" | ||||
| @ -352,14 +351,14 @@ export const SearchPopup = (): JSX.Element => { | ||||
|           </SearchResultSection> | ||||
|         )} | ||||
| 
 | ||||
|         {isDefined(posts) && ( | ||||
|         {isDefined(multiResult.posts) && ( | ||||
|           <SearchResultSection | ||||
|             title={format("news")} | ||||
|             icon="newspaper" | ||||
|             href={`/news?page=1&query=${query}`} | ||||
|             totalHits={posts.estimatedTotalHits}> | ||||
|             totalHits={multiResult.posts.estimatedTotalHits}> | ||||
|             <div className="flex flex-wrap items-start gap-x-6 gap-y-8"> | ||||
|               {posts.hits.map((item) => ( | ||||
|               {multiResult.posts.hits.map((item) => ( | ||||
|                 <TranslatedPreviewCard | ||||
|                   className="w-56" | ||||
|                   key={item.id} | ||||
| @ -395,14 +394,14 @@ export const SearchPopup = (): JSX.Element => { | ||||
|           </SearchResultSection> | ||||
|         )} | ||||
| 
 | ||||
|         {isDefined(videos) && ( | ||||
|         {isDefined(multiResult.videos) && ( | ||||
|           <SearchResultSection | ||||
|             title={format("videos")} | ||||
|             icon="movie" | ||||
|             href={`/archives/videos?page=1&query=${query}&sort=1&gone=`} | ||||
|             totalHits={videos.estimatedTotalHits}> | ||||
|             totalHits={multiResult.videos.estimatedTotalHits}> | ||||
|             <div className="flex flex-wrap items-start gap-x-6 gap-y-8"> | ||||
|               {videos.hits.map((item) => ( | ||||
|               {multiResult.videos.hits.map((item) => ( | ||||
|                 <PreviewCard | ||||
|                   className="w-56" | ||||
|                   key={item.uid} | ||||
| @ -435,14 +434,14 @@ export const SearchPopup = (): JSX.Element => { | ||||
|           </SearchResultSection> | ||||
|         )} | ||||
| 
 | ||||
|         {isDefined(weapons) && ( | ||||
|         {isDefined(multiResult.weapons) && ( | ||||
|           <SearchResultSection | ||||
|             title={format("weapon", { count: Infinity })} | ||||
|             icon="shield" | ||||
|             href={`/wiki/weapons?page=1&query=${query}`} | ||||
|             totalHits={weapons.estimatedTotalHits}> | ||||
|             totalHits={multiResult.weapons.estimatedTotalHits}> | ||||
|             <div className="flex flex-wrap items-start gap-x-6 gap-y-8"> | ||||
|               {weapons.hits.map((item) => ( | ||||
|               {multiResult.weapons.hits.map((item) => ( | ||||
|                 <TranslatedPreviewCard | ||||
|                   key={item.id} | ||||
|                   className="w-56" | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| // eslint-disable-next-line import/named
 | ||||
| import Tippy, { TippyProps } from "@tippyjs/react"; | ||||
| import Tippy from "@tippyjs/react"; | ||||
| import type { TippyProps } from "@tippyjs/react"; | ||||
| import { cJoin } from "helpers/className"; | ||||
| import "tippy.js/animations/scale-subtle.css"; | ||||
| 
 | ||||
|  | ||||
| @ -1,7 +1,6 @@ | ||||
| import { atom, PrimitiveAtom, Atom, WritableAtom, useAtom } from "jotai"; | ||||
| import { Dispatch, SetStateAction } from "react"; | ||||
| 
 | ||||
| // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-arguments
 | ||||
| type AtomPair<T> = [Atom<T>, WritableAtom<null, [newText: T], void>]; | ||||
| 
 | ||||
| export const atomPairing = <T>(anAtom: PrimitiveAtom<T>): AtomPair<T> => { | ||||
|  | ||||
| @ -58,7 +58,6 @@ export const prettyInlineTitle = ( | ||||
|   return result; | ||||
| }; | ||||
| 
 | ||||
| /* eslint-disable id-denylist */ | ||||
| export const prettyItemSubType = ( | ||||
|   metadata: | ||||
|     | { | ||||
|  | ||||
| @ -4,7 +4,6 @@ export const isUntangibleGroupItem = ( | ||||
|   metadata: | ||||
|     | { | ||||
|         __typename: string; | ||||
|         // eslint-disable-next-line id-denylist
 | ||||
|         subtype?: { data?: { attributes?: { slug: string } | null } | null } | null; | ||||
|       } | ||||
|     | null | ||||
|  | ||||
| @ -2,28 +2,30 @@ type LoggerMode = "both" | "client" | "server"; | ||||
| 
 | ||||
| const isServer = typeof window === "undefined"; | ||||
| 
 | ||||
| // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
 | ||||
| export const getLogger = (prefix: string, mode: LoggerMode = "client") => { | ||||
| type Logger = { | ||||
|   error: (message?: unknown, ...optionalParams: unknown[]) => void; | ||||
|   warn: (message?: unknown, ...optionalParams: unknown[]) => void; | ||||
|   log: (message?: unknown, ...optionalParams: unknown[]) => void; | ||||
|   info: (message?: unknown, ...optionalParams: unknown[]) => void; | ||||
|   debug: (message?: unknown, ...optionalParams: unknown[]) => void; | ||||
| }; | ||||
| 
 | ||||
| export const getLogger = (prefix: string, mode: LoggerMode = "client"): Logger => { | ||||
|   if ((mode === "client" && isServer) || (mode === "server" && !isServer)) { | ||||
|     return { | ||||
|       error: () => null, | ||||
|       warn: () => null, | ||||
|       log: () => null, | ||||
|       info: () => null, | ||||
|       debug: () => null, | ||||
|       error: () => undefined, | ||||
|       warn: () => undefined, | ||||
|       log: () => undefined, | ||||
|       info: () => undefined, | ||||
|       debug: () => undefined, | ||||
|     }; | ||||
|   } | ||||
| 
 | ||||
|   return { | ||||
|     error: (message?: unknown, ...optionalParams: unknown[]) => | ||||
|       console.error(prefix, message, ...optionalParams), | ||||
|     warn: (message?: unknown, ...optionalParams: unknown[]) => | ||||
|       console.warn(prefix, message, ...optionalParams), | ||||
|     log: (message?: unknown, ...optionalParams: unknown[]) => | ||||
|       console.log(prefix, message, ...optionalParams), | ||||
|     info: (message?: unknown, ...optionalParams: unknown[]) => | ||||
|       console.info(prefix, message, ...optionalParams), | ||||
|     debug: (message?: unknown, ...optionalParams: unknown[]) => | ||||
|       console.debug(prefix, message, ...optionalParams), | ||||
|     error: (message, ...optionalParams) => console.error(prefix, message, ...optionalParams), | ||||
|     warn: (message, ...optionalParams) => console.warn(prefix, message, ...optionalParams), | ||||
|     log: (message, ...optionalParams) => console.log(prefix, message, ...optionalParams), | ||||
|     info: (message, ...optionalParams) => console.info(prefix, message, ...optionalParams), | ||||
|     debug: (message, ...optionalParams) => console.debug(prefix, message, ...optionalParams), | ||||
|   }; | ||||
| }; | ||||
|  | ||||
| @ -1,7 +1,13 @@ | ||||
| /* eslint-disable @typescript-eslint/no-explicit-any */ | ||||
| // eslint-disable-next-line import/named
 | ||||
| import { MatchesPosition, MeiliSearch, SearchParams, SearchResponse } from "meilisearch"; | ||||
| import { isDefined } from "./asserts"; | ||||
| import { MeiliSearch } from "meilisearch"; | ||||
| import type { | ||||
|   SearchParams, | ||||
|   MatchesPosition, | ||||
|   SearchResponse, | ||||
|   MultiSearchQuery, | ||||
|   MultiSearchResponse, | ||||
|   MultiSearchResult, | ||||
| } from "meilisearch"; | ||||
| import { filterDefined, isDefined } from "./asserts"; | ||||
| import { MeiliDocumentsType } from "shared/meilisearch-graphql-typings/meiliTypes"; | ||||
| 
 | ||||
| const meili = new MeiliSearch({ | ||||
| @ -12,20 +18,61 @@ const meili = new MeiliSearch({ | ||||
| interface CustomSearchParams | ||||
|   extends Omit< | ||||
|     SearchParams, | ||||
|     "cropMarker" | "highlightPostTag" | "highlightPreTag" | "q" | "showMatchesPosition" | ||||
|     | "cropLength" | ||||
|     | "cropMarker" | ||||
|     | "cropMarker" | ||||
|     | "highlightPostTag" | ||||
|     | "highlightPreTag" | ||||
|     | "q" | ||||
|     | "showMatchesPosition" | ||||
|   > {} | ||||
| 
 | ||||
| type CustomHit<T = Record<string, any>> = T & { | ||||
| type CustomHit<T = Record<string, unknown>> = T & { | ||||
|   _formatted: Partial<T>; | ||||
|   _matchesPosition: MatchesPosition<T>; | ||||
| }; | ||||
| 
 | ||||
| type CustomHits<T = Record<string, any>> = CustomHit<T>[]; | ||||
| type CustomHits<T = Record<string, unknown>> = CustomHit<T>[]; | ||||
| 
 | ||||
| export interface CustomSearchResponse<T> extends Omit<SearchResponse<T>, "hits"> { | ||||
|   hits: CustomHits<T>; | ||||
| } | ||||
| 
 | ||||
| export const meiliMultiSearch = async (queries: MultiSearchQuery[]): Promise<MultiSearchResponse> => | ||||
|   await meili.multiSearch({ | ||||
|     queries: queries.map((query) => ({ | ||||
|       attributesToHighlight: ["*"], | ||||
|       ...query, | ||||
|       highlightPreTag: "<mark>", | ||||
|       highlightPostTag: "</mark>", | ||||
|       showMatchesPosition: true, | ||||
|       cropLength: 20, | ||||
|       cropMarker: "...", | ||||
|     })), | ||||
|   }); | ||||
| 
 | ||||
| export const filterHitsWithHighlight = <T extends MeiliDocumentsType["documents"]>( | ||||
|   searchResult: CustomSearchResponse<T> | MultiSearchResult<Record<string, unknown>>, | ||||
|   keyToFilter?: keyof T | ||||
| ): CustomSearchResponse<T> => { | ||||
|   const result = searchResult as unknown as CustomSearchResponse<T>; | ||||
|   if (isDefined(keyToFilter)) { | ||||
|     result.hits = result.hits.map((item) => { | ||||
|       if ( | ||||
|         Object.keys(item._matchesPosition).some((match) => match.startsWith(keyToFilter as string)) | ||||
|       ) { | ||||
|         // eslint-disable-next-line @typescript-eslint/ban-ts-comment
 | ||||
|         // @ts-ignore
 | ||||
|         item._formatted[keyToFilter] = filterDefined(item._formatted[keyToFilter]).filter( | ||||
|           (translation) => JSON.stringify(translation).includes("</mark>") | ||||
|         ); | ||||
|       } | ||||
|       return item; | ||||
|     }); | ||||
|   } | ||||
|   return result; | ||||
| }; | ||||
| 
 | ||||
| // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
 | ||||
| export const meiliSearch = async <I extends MeiliDocumentsType["index"]>( | ||||
|   indexName: I, | ||||
|  | ||||
| @ -12,16 +12,16 @@ import { TextInput } from "components/Inputs/TextInput"; | ||||
| import { WithLabel } from "components/Inputs/WithLabel"; | ||||
| import { Button } from "components/Inputs/Button"; | ||||
| import { useDeviceSupportsHover } from "hooks/useMediaQuery"; | ||||
| import { | ||||
|   filterDefined, | ||||
|   filterHasAttributes, | ||||
|   isDefined, | ||||
|   isDefinedAndNotEmpty, | ||||
| } from "helpers/asserts"; | ||||
| import { filterHasAttributes, isDefined, isDefinedAndNotEmpty } from "helpers/asserts"; | ||||
| import { getOpenGraph } from "helpers/openGraph"; | ||||
| import { HorizontalLine } from "components/HorizontalLine"; | ||||
| import { sendAnalytics } from "helpers/analytics"; | ||||
| import { containsHighlight, CustomSearchResponse, meiliSearch } from "helpers/search"; | ||||
| import { | ||||
|   containsHighlight, | ||||
|   CustomSearchResponse, | ||||
|   filterHitsWithHighlight, | ||||
|   meiliSearch, | ||||
| } from "helpers/search"; | ||||
| import { MeiliContent, MeiliIndices } from "shared/meilisearch-graphql-typings/meiliTypes"; | ||||
| import { useTypedRouter } from "hooks/useTypedRouter"; | ||||
| import { TranslatedPreviewCard } from "components/PreviewCard"; | ||||
| @ -97,15 +97,7 @@ const Contents = (props: Props): JSX.Element => { | ||||
|         page, | ||||
|         sort: isDefined(currentSortingMethod) ? [currentSortingMethod.meiliAttribute] : undefined, | ||||
|       }); | ||||
|       searchResult.hits = searchResult.hits.map((item) => { | ||||
|         if (Object.keys(item._matchesPosition).some((match) => match.startsWith("translations"))) { | ||||
|           item._formatted.translations = filterDefined(item._formatted.translations).filter( | ||||
|             (translation) => containsHighlight(JSON.stringify(translation)) | ||||
|           ); | ||||
|         } | ||||
|         return item; | ||||
|       }); | ||||
|       setContents(searchResult); | ||||
|       setContents(filterHitsWithHighlight<MeiliContent>(searchResult, "translations")); | ||||
|     }; | ||||
|     fetchPosts(); | ||||
|   }, [query, page, sortingMethod, sortingMethods]); | ||||
|  | ||||
| @ -1,4 +1,3 @@ | ||||
| /* eslint-disable id-denylist */ | ||||
| import { GetStaticProps } from "next"; | ||||
| import { ReactNode, useState } from "react"; | ||||
| import Slider from "rc-slider"; | ||||
|  | ||||
| @ -14,17 +14,16 @@ import { TextInput } from "components/Inputs/TextInput"; | ||||
| import { Button } from "components/Inputs/Button"; | ||||
| import { useDeviceSupportsHover } from "hooks/useMediaQuery"; | ||||
| import { ButtonGroup } from "components/Inputs/ButtonGroup"; | ||||
| import { | ||||
|   filterDefined, | ||||
|   filterHasAttributes, | ||||
|   isDefined, | ||||
|   isDefinedAndNotEmpty, | ||||
|   isUndefined, | ||||
| } from "helpers/asserts"; | ||||
| import { filterHasAttributes, isDefined, isDefinedAndNotEmpty, isUndefined } from "helpers/asserts"; | ||||
| import { getOpenGraph } from "helpers/openGraph"; | ||||
| import { HorizontalLine } from "components/HorizontalLine"; | ||||
| import { sendAnalytics } from "helpers/analytics"; | ||||
| import { containsHighlight, CustomSearchResponse, meiliSearch } from "helpers/search"; | ||||
| import { | ||||
|   containsHighlight, | ||||
|   CustomSearchResponse, | ||||
|   filterHitsWithHighlight, | ||||
|   meiliSearch, | ||||
| } from "helpers/search"; | ||||
| import { MeiliIndices, MeiliLibraryItem } from "shared/meilisearch-graphql-typings/meiliTypes"; | ||||
| import { useTypedRouter } from "hooks/useTypedRouter"; | ||||
| import { TranslatedPreviewCard } from "components/PreviewCard"; | ||||
| @ -178,15 +177,7 @@ const Library = (props: Props): JSX.Element => { | ||||
|         sort: isDefined(currentSortingMethod) ? [currentSortingMethod.meiliAttribute] : undefined, | ||||
|         filter, | ||||
|       }); | ||||
|       searchResult.hits = searchResult.hits.map((item) => { | ||||
|         if (Object.keys(item._matchesPosition).some((match) => match.startsWith("descriptions"))) { | ||||
|           item._formatted.descriptions = filterDefined(item._formatted.descriptions).filter( | ||||
|             (description) => containsHighlight(JSON.stringify(description)) | ||||
|           ); | ||||
|         } | ||||
|         return item; | ||||
|       }); | ||||
|       setLibraryItems(searchResult); | ||||
|       setLibraryItems(filterHitsWithHighlight<MeiliLibraryItem>(searchResult, "descriptions")); | ||||
|     }; | ||||
|     fetchLibraryItems(); | ||||
|   }, [ | ||||
|  | ||||
| @ -11,12 +11,7 @@ import { WithLabel } from "components/Inputs/WithLabel"; | ||||
| import { TextInput } from "components/Inputs/TextInput"; | ||||
| import { Button } from "components/Inputs/Button"; | ||||
| import { useDeviceSupportsHover } from "hooks/useMediaQuery"; | ||||
| import { | ||||
|   filterDefined, | ||||
|   filterHasAttributes, | ||||
|   isDefined, | ||||
|   isDefinedAndNotEmpty, | ||||
| } from "helpers/asserts"; | ||||
| import { filterHasAttributes, isDefined, isDefinedAndNotEmpty } from "helpers/asserts"; | ||||
| import { getOpenGraph } from "helpers/openGraph"; | ||||
| import { TranslatedPreviewCard } from "components/PreviewCard"; | ||||
| import { HorizontalLine } from "components/HorizontalLine"; | ||||
| @ -24,7 +19,12 @@ import { sendAnalytics } from "helpers/analytics"; | ||||
| import { Terminal } from "components/Cli/Terminal"; | ||||
| import { atoms } from "contexts/atoms"; | ||||
| import { useAtomGetter } from "helpers/atoms"; | ||||
| import { containsHighlight, CustomSearchResponse, meiliSearch } from "helpers/search"; | ||||
| import { | ||||
|   containsHighlight, | ||||
|   CustomSearchResponse, | ||||
|   filterHitsWithHighlight, | ||||
|   meiliSearch, | ||||
| } from "helpers/search"; | ||||
| import { MeiliIndices, MeiliPost } from "shared/meilisearch-graphql-typings/meiliTypes"; | ||||
| import { useTypedRouter } from "hooks/useTypedRouter"; | ||||
| import { prettySlug } from "helpers/formatters"; | ||||
| @ -84,15 +84,7 @@ const News = ({ ...otherProps }: Props): JSX.Element => { | ||||
|         sort: ["sortable_date:desc"], | ||||
|         filter: ["hidden = false"], | ||||
|       }); | ||||
|       searchResult.hits = searchResult.hits.map((item) => { | ||||
|         if (Object.keys(item._matchesPosition).some((match) => match.startsWith("translations"))) { | ||||
|           item._formatted.translations = filterDefined(item._formatted.translations).filter( | ||||
|             (translation) => JSON.stringify(translation).includes("</mark>") | ||||
|           ); | ||||
|         } | ||||
|         return item; | ||||
|       }); | ||||
|       setPosts(searchResult); | ||||
|       setPosts(filterHitsWithHighlight<MeiliPost>(searchResult, "translations")); | ||||
|     }; | ||||
|     fetchPosts(); | ||||
|   }, [query, page]); | ||||
|  | ||||
| @ -10,16 +10,16 @@ import { PanelHeader } from "components/PanelComponents/PanelHeader"; | ||||
| import { TextInput } from "components/Inputs/TextInput"; | ||||
| import { useTypedRouter } from "hooks/useTypedRouter"; | ||||
| import { useFormat } from "hooks/useFormat"; | ||||
| import { | ||||
|   filterDefined, | ||||
|   filterHasAttributes, | ||||
|   isDefined, | ||||
|   isDefinedAndNotEmpty, | ||||
| } from "helpers/asserts"; | ||||
| import { filterHasAttributes, isDefined, isDefinedAndNotEmpty } from "helpers/asserts"; | ||||
| import { sendAnalytics } from "helpers/analytics"; | ||||
| import { Button } from "components/Inputs/Button"; | ||||
| import { HorizontalLine } from "components/HorizontalLine"; | ||||
| import { containsHighlight, CustomSearchResponse, meiliSearch } from "helpers/search"; | ||||
| import { | ||||
|   containsHighlight, | ||||
|   CustomSearchResponse, | ||||
|   filterHitsWithHighlight, | ||||
|   meiliSearch, | ||||
| } from "helpers/search"; | ||||
| import { MeiliIndices, MeiliWeapon } from "shared/meilisearch-graphql-typings/meiliTypes"; | ||||
| import { ContentPanel, ContentPanelWidthSizes } from "components/Containers/ContentPanel"; | ||||
| import { Paginator } from "components/Containers/Paginator"; | ||||
| @ -79,17 +79,7 @@ const Weapons = (props: Props): JSX.Element => { | ||||
|         attributesToCrop: ["translations.description"], | ||||
|         sort: ["slug:asc"], | ||||
|       }); | ||||
| 
 | ||||
|       searchResult.hits = searchResult.hits.map((item) => { | ||||
|         if (Object.keys(item._matchesPosition).some((match) => match.startsWith("translations"))) { | ||||
|           item._formatted.translations = filterDefined(item._formatted.translations).filter( | ||||
|             (translation) => JSON.stringify(translation).includes("</mark>") | ||||
|           ); | ||||
|         } | ||||
|         return item; | ||||
|       }); | ||||
| 
 | ||||
|       setWeapons(searchResult); | ||||
|       setWeapons(filterHitsWithHighlight<MeiliWeapon>(searchResult, "translations")); | ||||
|     }; | ||||
|     fetchPosts(); | ||||
|   }, [query, page]); | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 DrMint
						DrMint