Added meilisearch #89
| @ -1,4 +1,5 @@ | ||||
| import { useEffect, useState } from "react"; | ||||
| import { MaterialSymbol } from "material-symbols"; | ||||
| import { Popup } from "components/Containers/Popup"; | ||||
| import { sendAnalytics } from "helpers/analytics"; | ||||
| import { atoms } from "contexts/atoms"; | ||||
| @ -46,16 +47,25 @@ export const SearchPopup = (): JSX.Element => { | ||||
|     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 ( | ||||
|           isDefined(item._formatted) && | ||||
|           item._matchesPosition.descriptions && | ||||
|           item._matchesPosition.descriptions.length > 0 | ||||
|         ) { | ||||
|         if (Object.keys(item._matchesPosition).some((match) => match.startsWith("descriptions"))) { | ||||
|           item._formatted.descriptions = filterDefined(item._formatted.descriptions).filter( | ||||
|             (description) => description.includes("</mark>") | ||||
|             (description) => containsHighlight(JSON.stringify(description)) | ||||
|           ); | ||||
|         } | ||||
|         return item; | ||||
| @ -65,16 +75,15 @@ export const SearchPopup = (): JSX.Element => { | ||||
| 
 | ||||
|     const fetchContents = async () => { | ||||
|       const searchResult = await meiliSearch(MeiliIndices.CONTENT, query, { | ||||
|         attributesToCrop: ["translations.description"], | ||||
|         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).filter((match) => match.startsWith("translations")) | ||||
|             .length > 0 | ||||
|         ) { | ||||
|         if (Object.keys(item._matchesPosition).some((match) => match.startsWith("translations"))) { | ||||
|           item._formatted.translations = filterDefined(item._formatted.translations).filter( | ||||
|             (translation) => JSON.stringify(translation).includes("</mark>") | ||||
|             (translation) => containsHighlight(JSON.stringify(translation)) | ||||
|           ); | ||||
|         } | ||||
|         return item; | ||||
| @ -94,8 +103,8 @@ export const SearchPopup = (): JSX.Element => { | ||||
|           "duration", | ||||
|           "description", | ||||
|         ], | ||||
|         attributesToHighlight: ["title", "channel", "description"], | ||||
|         attributesToCrop: ["description"], | ||||
|         sort: ["sortable_published_date:desc"], | ||||
|       }); | ||||
|       setVideos(searchResult); | ||||
|     }; | ||||
| @ -106,9 +115,16 @@ export const SearchPopup = (): JSX.Element => { | ||||
|         attributesToRetrieve: ["translations", "thumbnail", "slug", "date", "categories"], | ||||
|         attributesToHighlight: ["translations.title", "translations.excerpt", "translations.body"], | ||||
|         attributesToCrop: ["translations.body"], | ||||
|         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); | ||||
|     }; | ||||
| 
 | ||||
| @ -123,6 +139,17 @@ export const SearchPopup = (): JSX.Element => { | ||||
|         ], | ||||
|         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>") | ||||
|           ); | ||||
|         } | ||||
|         return item; | ||||
|       }); | ||||
|       setWikiPages(searchResult); | ||||
|     }; | ||||
| 
 | ||||
| @ -159,22 +186,27 @@ export const SearchPopup = (): JSX.Element => { | ||||
|         {isDefined(libraryItems) && ( | ||||
|           <SearchResultSection | ||||
|             title={langui.library} | ||||
|             href={`/library?page=1&query=${query}&sort=0&primary=true&secondary=true&subitems=true&status=all`} | ||||
|             icon="auto_stories" | ||||
|             href={`/library?page=1&query=${query}\ | ||||
| &sort=0&primary=true&secondary=true&subitems=true&status=all`}
 | ||||
|             totalHits={libraryItems.estimatedTotalHits}> | ||||
|             <div className="flex flex-wrap items-start gap-x-6 gap-y-8"> | ||||
|               {libraryItems.hits.map((item) => ( | ||||
|                 <PreviewCard | ||||
|                 <TranslatedPreviewCard | ||||
|                   key={item.id} | ||||
|                   className="w-52" | ||||
|                   className="w-56" | ||||
|                   href={`/library/${item.slug}`} | ||||
|                   title={item._formatted.title} | ||||
|                   subtitle={item._formatted.subtitle} | ||||
|                   description={ | ||||
|                     item._matchesPosition.descriptions && | ||||
|                     item._matchesPosition.descriptions.length > 0 | ||||
|                       ? item._formatted.descriptions?.[0] | ||||
|                       : undefined | ||||
|                   } | ||||
|                   translations={filterHasAttributes(item._formatted.descriptions, [ | ||||
|                     "language.data.attributes.code", | ||||
|                   ] as const).map((translation) => ({ | ||||
|                     language: translation.language.data.attributes.code, | ||||
|                     title: item.title, | ||||
|                     subtitle: item.subtitle, | ||||
|                     description: containsHighlight(translation.description) | ||||
|                       ? translation.description | ||||
|                       : undefined, | ||||
|                   }))} | ||||
|                   fallback={{ title: item._formatted.title, subtitle: item._formatted.subtitle }} | ||||
|                   thumbnail={item.thumbnail?.data?.attributes} | ||||
|                   thumbnailAspectRatio="21/29.7" | ||||
|                   thumbnailRounded={false} | ||||
| @ -201,18 +233,25 @@ export const SearchPopup = (): JSX.Element => { | ||||
|         {isDefined(contents) && ( | ||||
|           <SearchResultSection | ||||
|             title={langui.contents} | ||||
|             icon="workspaces" | ||||
|             href={`/contents/all?page=1&query=${query}&sort=0`} | ||||
|             totalHits={contents.estimatedTotalHits}> | ||||
|             <div className="flex flex-wrap items-start gap-x-6 gap-y-8"> | ||||
|               {contents.hits.map((item) => ( | ||||
|                 <PreviewCard | ||||
|                 <TranslatedPreviewCard | ||||
|                   key={item.id} | ||||
|                   className="w-56" | ||||
|                   href={`/contents/${item.slug}`} | ||||
|                   pre_title={item._formatted.translations?.[0]?.pre_title} | ||||
|                   title={item._formatted.translations?.[0]?.title} | ||||
|                   subtitle={item._formatted.translations?.[0]?.subtitle} | ||||
|                   description={item._formatted.translations?.[0]?.description} | ||||
|                   translations={filterHasAttributes(item._formatted.translations, [ | ||||
|                     "language.data.attributes.code", | ||||
|                   ] as const).map(({ displayable_description, language, ...otherAttributes }) => ({ | ||||
|                     ...otherAttributes, | ||||
|                     description: containsHighlight(displayable_description) | ||||
|                       ? displayable_description | ||||
|                       : undefined, | ||||
|                     language: language.data.attributes.code, | ||||
|                   }))} | ||||
|                   fallback={{ title: prettySlug(item.slug) }} | ||||
|                   thumbnail={item.thumbnail?.data?.attributes} | ||||
|                   thumbnailAspectRatio="3/2" | ||||
|                   thumbnailForceAspectRatio | ||||
| @ -238,7 +277,8 @@ export const SearchPopup = (): JSX.Element => { | ||||
|         {isDefined(wikiPages) && ( | ||||
|           <SearchResultSection | ||||
|             title={langui.wiki} | ||||
|             href={"/wiki"} | ||||
|             icon="travel_explore" | ||||
|             href={`/wiki?page=1&query=${query}`} | ||||
|             totalHits={wikiPages.estimatedTotalHits}> | ||||
|             <div className="flex flex-wrap items-start gap-x-6 gap-y-8"> | ||||
|               {wikiPages.hits.map((item) => ( | ||||
| @ -288,6 +328,7 @@ export const SearchPopup = (): JSX.Element => { | ||||
|         {isDefined(posts) && ( | ||||
|           <SearchResultSection | ||||
|             title={langui.news} | ||||
|             icon="newspaper" | ||||
|             href={`/news?page=1&query=${query}`} | ||||
|             totalHits={posts.estimatedTotalHits}> | ||||
|             <div className="flex flex-wrap items-start gap-x-6 gap-y-8"> | ||||
| @ -329,6 +370,7 @@ export const SearchPopup = (): JSX.Element => { | ||||
|         {isDefined(videos) && ( | ||||
|           <SearchResultSection | ||||
|             title={langui.videos} | ||||
|             icon="movie" | ||||
|             href={`/archives/videos?page=1&query=${query}&sort=1&gone=`} | ||||
|             totalHits={videos.estimatedTotalHits}> | ||||
|             <div className="flex flex-wrap items-start gap-x-6 gap-y-8"> | ||||
| @ -370,21 +412,34 @@ export const SearchPopup = (): JSX.Element => { | ||||
| 
 | ||||
| interface SearchResultSectionProps { | ||||
|   title?: string | null; | ||||
|   icon: MaterialSymbol; | ||||
|   href: string; | ||||
|   totalHits?: number; | ||||
|   children: React.ReactNode; | ||||
| } | ||||
| 
 | ||||
| const SearchResultSection = ({ title, href, totalHits, children }: SearchResultSectionProps) => ( | ||||
| const SearchResultSection = ({ | ||||
|   title, | ||||
|   icon, | ||||
|   href, | ||||
|   totalHits, | ||||
|   children, | ||||
| }: SearchResultSectionProps) => ( | ||||
|   <> | ||||
|     {isDefined(totalHits) && totalHits > 0 && ( | ||||
|       <div> | ||||
|         <div className="mb-6 grid place-content-start"> | ||||
|           <UpPressable className="px-6 py-4" href={href}> | ||||
|             <p className="font-headers text-lg">{title}</p> | ||||
|             {isDefined(totalHits) && totalHits > SEARCH_LIMIT && ( | ||||
|               <p className="text-sm">{`(showing ${SEARCH_LIMIT} out of ${totalHits} results)`}</p> | ||||
|             )} | ||||
|           <UpPressable | ||||
|             className="grid grid-cols-[auto_1fr] place-items-center gap-6 px-6 py-4" | ||||
|             href={href}> | ||||
|             <Ico icon={icon} className="!text-3xl" isFilled /> | ||||
|             <div> | ||||
|               <p className="font-headers text-lg">{title}</p> | ||||
|               {isDefined(totalHits) && totalHits > SEARCH_LIMIT && ( | ||||
|                 /* TODO: Langui */ | ||||
|                 <p className="text-sm">{`(showing ${SEARCH_LIMIT} out of ${totalHits} results)`}</p> | ||||
|               )} | ||||
|             </div> | ||||
|           </UpPressable> | ||||
|         </div> | ||||
|         {children} | ||||
|  | ||||
| @ -123,8 +123,8 @@ const Channel = ({ channel, ...otherProps }: Props): JSX.Element => { | ||||
|           "duration", | ||||
|           "description", | ||||
|         ], | ||||
|         attributesToHighlight: ["title", "channel", "description"], | ||||
|         attributesToCrop: ["description"], | ||||
|         attributesToHighlight: ["*"], | ||||
|         sort: isDefined(currentSortingMethod) ? [currentSortingMethod.meiliAttribute] : undefined, | ||||
|         filter: [onlyShowGone ? "gone = true" : "", `channel_uid = ${channel.uid}`], | ||||
|       }); | ||||
| @ -147,7 +147,6 @@ const Channel = ({ channel, ...otherProps }: Props): JSX.Element => { | ||||
| 
 | ||||
|   useEffect(() => { | ||||
|     if (router.isReady) { | ||||
|       console.log(router.query); | ||||
|       if (isDefined(router.query.page)) setPage(router.query.page); | ||||
|       if (isDefined(router.query.query)) setQuery(router.query.query); | ||||
|       if (isDefined(router.query.sort)) setSortingMethod(router.query.sort); | ||||
|  | ||||
| @ -117,8 +117,8 @@ const Videos = ({ ...otherProps }: Props): JSX.Element => { | ||||
|           "duration", | ||||
|           "description", | ||||
|         ], | ||||
|         attributesToHighlight: ["title", "channel", "description"], | ||||
|         attributesToCrop: ["description"], | ||||
|         attributesToHighlight: ["*"], | ||||
|         sort: isDefined(currentSortingMethod) ? [currentSortingMethod.meiliAttribute] : undefined, | ||||
|         filter: onlyShowGone ? ["gone = true"] : undefined, | ||||
|       }); | ||||
| @ -141,7 +141,6 @@ const Videos = ({ ...otherProps }: Props): JSX.Element => { | ||||
| 
 | ||||
|   useEffect(() => { | ||||
|     if (router.isReady) { | ||||
|       console.log(router.query); | ||||
|       if (isDefined(router.query.page)) setPage(router.query.page); | ||||
|       if (isDefined(router.query.query)) setQuery(router.query.query); | ||||
|       if (isDefined(router.query.sort)) setSortingMethod(router.query.sort); | ||||
|  | ||||
| @ -109,7 +109,8 @@ const Video = ({ video, ...otherProps }: Props): JSX.Element => { | ||||
|               <h2 className="text-2xl">{langui.channel}</h2> | ||||
|               <div> | ||||
|                 <Button | ||||
|                   href={`/archives/videos/c/${video.channel.data.attributes.uid}?page=1&query=&sort=1&gone=`} | ||||
|                   href={`/archives/videos/c/${video.channel.data.attributes.uid}\ | ||||
| ?page=1&query=&sort=1&gone=`}
 | ||||
|                   text={video.channel.data.attributes.title} | ||||
|                 /> | ||||
|                 <p> | ||||
|  | ||||
| @ -88,16 +88,15 @@ const Contents = (props: Props): JSX.Element => { | ||||
|     const fetchPosts = async () => { | ||||
|       const currentSortingMethod = sortingMethods[sortingMethod]; | ||||
|       const searchResult = await meiliSearch(MeiliIndices.CONTENT, query, { | ||||
|         attributesToCrop: ["translations.description"], | ||||
|         attributesToRetrieve: ["translations", "id", "slug", "categories", "type", "thumbnail"], | ||||
|         attributesToHighlight: ["translations"], | ||||
|         attributesToCrop: ["translations.displayable_description"], | ||||
|         hitsPerPage: 25, | ||||
|         page, | ||||
|         sort: isDefined(currentSortingMethod) ? [currentSortingMethod.meiliAttribute] : undefined, | ||||
|       }); | ||||
|       searchResult.hits = searchResult.hits.map((item) => { | ||||
|         if ( | ||||
|           Object.keys(item._matchesPosition).filter((match) => match.startsWith("translations")) | ||||
|             .length > 0 | ||||
|         ) { | ||||
|         if (Object.keys(item._matchesPosition).some((match) => match.startsWith("translations"))) { | ||||
|           item._formatted.translations = filterDefined(item._formatted.translations).filter( | ||||
|             (translation) => containsHighlight(JSON.stringify(translation)) | ||||
|           ); | ||||
| @ -212,9 +211,11 @@ const Contents = (props: Props): JSX.Element => { | ||||
|               href={`/contents/${item.slug}`} | ||||
|               translations={filterHasAttributes(item._formatted.translations, [ | ||||
|                 "language.data.attributes.code", | ||||
|               ] as const).map(({ description, language, ...otherAttributes }) => ({ | ||||
|               ] as const).map(({ displayable_description, language, ...otherAttributes }) => ({ | ||||
|                 ...otherAttributes, | ||||
|                 description: containsHighlight(description) ? description : undefined, | ||||
|                 description: containsHighlight(displayable_description) | ||||
|                   ? displayable_description | ||||
|                   : undefined, | ||||
|                 language: language.data.attributes.code, | ||||
|               }))} | ||||
|               fallback={{ title: prettySlug(item.slug) }} | ||||
|  | ||||
| @ -14,17 +14,23 @@ 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, isDefined, isDefinedAndNotEmpty, isUndefined } from "helpers/asserts"; | ||||
| import { | ||||
|   filterDefined, | ||||
|   filterHasAttributes, | ||||
|   isDefined, | ||||
|   isDefinedAndNotEmpty, | ||||
|   isUndefined, | ||||
| } from "helpers/asserts"; | ||||
| import { getOpenGraph } from "helpers/openGraph"; | ||||
| import { HorizontalLine } from "components/HorizontalLine"; | ||||
| import { getLangui } from "graphql/fetchLocalData"; | ||||
| import { sendAnalytics } from "helpers/analytics"; | ||||
| import { atoms } from "contexts/atoms"; | ||||
| import { useAtomGetter } from "helpers/atoms"; | ||||
| import { CustomSearchResponse, meiliSearch } from "helpers/search"; | ||||
| import { containsHighlight, CustomSearchResponse, meiliSearch } from "helpers/search"; | ||||
| import { MeiliIndices, MeiliLibraryItem } from "shared/meilisearch-graphql-typings/meiliTypes"; | ||||
| import { useTypedRouter } from "hooks/useTypedRouter"; | ||||
| import { PreviewCard } from "components/PreviewCard"; | ||||
| import { TranslatedPreviewCard } from "components/PreviewCard"; | ||||
| import { prettyItemSubType } from "helpers/formatters"; | ||||
| import { isUntangibleGroupItem } from "helpers/libraryItem"; | ||||
| import { PreviewCardCTAs } from "components/Library/PreviewCardCTAs"; | ||||
| @ -156,18 +162,27 @@ const Library = (props: Props): JSX.Element => { | ||||
|       const searchResult = await meiliSearch(MeiliIndices.LIBRARY_ITEM, query, { | ||||
|         hitsPerPage: 25, | ||||
|         page, | ||||
|         attributesToRetrieve: [ | ||||
|           "title", | ||||
|           "subtitle", | ||||
|           "descriptions", | ||||
|           "id", | ||||
|           "slug", | ||||
|           "thumbnail", | ||||
|           "release_date", | ||||
|           "price", | ||||
|           "categories", | ||||
|           "metadata", | ||||
|         ], | ||||
|         attributesToHighlight: ["title", "subtitle", "descriptions"], | ||||
|         attributesToCrop: ["descriptions"], | ||||
|         sort: isDefined(currentSortingMethod) ? [currentSortingMethod.meiliAttribute] : undefined, | ||||
|         filter, | ||||
|       }); | ||||
|       searchResult.hits = searchResult.hits.map((item) => { | ||||
|         if ( | ||||
|           isDefined(item._formatted) && | ||||
|           item._matchesPosition.descriptions && | ||||
|           item._matchesPosition.descriptions.length > 0 | ||||
|         ) { | ||||
|         if (Object.keys(item._matchesPosition).some((match) => match.startsWith("descriptions"))) { | ||||
|           item._formatted.descriptions = filterDefined(item._formatted.descriptions).filter( | ||||
|             (description) => description.includes("</mark>") | ||||
|             (description) => containsHighlight(JSON.stringify(description)) | ||||
|           ); | ||||
|         } | ||||
|         return item; | ||||
| @ -188,16 +203,17 @@ const Library = (props: Props): JSX.Element => { | ||||
|   ]); | ||||
| 
 | ||||
|   useEffect(() => { | ||||
|     if (router.isReady) console.log(router.query, filterUserStatus); | ||||
|     router.updateQuery({ | ||||
|       page, | ||||
|       query, | ||||
|       sort: sortingMethod, | ||||
|       primary: showPrimaryItems, | ||||
|       secondary: showSecondaryItems, | ||||
|       subitems: showSubitems, | ||||
|       status: fromLibraryItemUserStatusToString(filterUserStatus), | ||||
|     }); | ||||
|     if (router.isReady) { | ||||
|       router.updateQuery({ | ||||
|         page, | ||||
|         query, | ||||
|         sort: sortingMethod, | ||||
|         primary: showPrimaryItems, | ||||
|         secondary: showSecondaryItems, | ||||
|         subitems: showSubitems, | ||||
|         status: fromLibraryItemUserStatusToString(filterUserStatus), | ||||
|       }); | ||||
|     } | ||||
|     // eslint-disable-next-line react-hooks/exhaustive-deps
 | ||||
|   }, [ | ||||
|     page, | ||||
| @ -212,7 +228,6 @@ const Library = (props: Props): JSX.Element => { | ||||
| 
 | ||||
|   useEffect(() => { | ||||
|     if (router.isReady) { | ||||
|       console.log(router.query); | ||||
|       if (isDefined(router.query.page)) setPage(router.query.page); | ||||
|       if (isDefined(router.query.query)) setQuery(router.query.query); | ||||
|       if (isDefined(router.query.sort)) setSortingMethod(router.query.sort); | ||||
| @ -387,16 +402,20 @@ const Library = (props: Props): JSX.Element => { | ||||
|           className="grid grid-cols-[repeat(auto-fill,_minmax(12rem,1fr))] items-end | ||||
|               gap-x-6 gap-y-8"> | ||||
|           {libraryItems?.hits.map((item) => ( | ||||
|             <PreviewCard | ||||
|             <TranslatedPreviewCard | ||||
|               key={item.id} | ||||
|               href={`/library/${item.slug}`} | ||||
|               title={item._formatted.title} | ||||
|               subtitle={item._formatted.subtitle} | ||||
|               description={ | ||||
|                 item._matchesPosition.descriptions && item._matchesPosition.descriptions.length > 0 | ||||
|                   ? item._formatted.descriptions?.[0] | ||||
|                   : undefined | ||||
|               } | ||||
|               translations={filterHasAttributes(item._formatted.descriptions, [ | ||||
|                 "language.data.attributes.code", | ||||
|               ] as const).map((translation) => ({ | ||||
|                 language: translation.language.data.attributes.code, | ||||
|                 title: item.title, | ||||
|                 subtitle: item.subtitle, | ||||
|                 description: containsHighlight(translation.description) | ||||
|                   ? translation.description | ||||
|                   : undefined, | ||||
|               }))} | ||||
|               fallback={{ title: item._formatted.title, subtitle: item._formatted.subtitle }} | ||||
|               thumbnail={item.thumbnail?.data?.attributes} | ||||
|               thumbnailAspectRatio="21/29.7" | ||||
|               thumbnailRounded={false} | ||||
|  | ||||
| @ -11,7 +11,12 @@ import { WithLabel } from "components/Inputs/WithLabel"; | ||||
| import { TextInput } from "components/Inputs/TextInput"; | ||||
| import { Button } from "components/Inputs/Button"; | ||||
| import { useDeviceSupportsHover } from "hooks/useMediaQuery"; | ||||
| import { filterHasAttributes, isDefined, isDefinedAndNotEmpty } from "helpers/asserts"; | ||||
| import { | ||||
|   filterDefined, | ||||
|   filterHasAttributes, | ||||
|   isDefined, | ||||
|   isDefinedAndNotEmpty, | ||||
| } from "helpers/asserts"; | ||||
| import { getOpenGraph } from "helpers/openGraph"; | ||||
| import { TranslatedPreviewCard } from "components/PreviewCard"; | ||||
| import { HorizontalLine } from "components/HorizontalLine"; | ||||
| @ -78,6 +83,14 @@ 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); | ||||
|     }; | ||||
|     fetchPosts(); | ||||
|  | ||||
| @ -11,9 +11,8 @@ import { | ||||
|   WikiPageAttributesFragment, | ||||
| } from "./generated"; | ||||
| 
 | ||||
| export interface MeiliLibraryItem extends Omit<LibraryItemAttributesFragment, "descriptions"> { | ||||
| export interface MeiliLibraryItem extends LibraryItemAttributesFragment { | ||||
|   id: string; | ||||
|   descriptions: string[]; | ||||
|   sortable_name: string; | ||||
|   sortable_price: number | undefined; | ||||
|   sortable_date: number | undefined; | ||||
| @ -23,20 +22,12 @@ export interface MeiliLibraryItem extends Omit<LibraryItemAttributesFragment, "d | ||||
| export interface MeiliContent | ||||
|   extends Omit<ContentAttributesFragment, "translations" | "updatedAt"> { | ||||
|   id: string; | ||||
|   translations?: Array<{ | ||||
|     __typename?: "ComponentTranslationsTitle"; | ||||
|     pre_title?: string | null; | ||||
|     title: string; | ||||
|     subtitle?: string | null; | ||||
|     description?: string | null; | ||||
|     language?: { | ||||
|       __typename?: "LanguageEntityResponse"; | ||||
|       data?: { | ||||
|         __typename?: "LanguageEntity"; | ||||
|         attributes?: { __typename?: "Language"; code: string } | null; | ||||
|       } | null; | ||||
|     } | null; | ||||
|   } | null> | null; | ||||
|   translations: (Omit< | ||||
|     NonNullable<NonNullable<ContentAttributesFragment["translations"]>[number]>, | ||||
|     "text_set" | "description" | ||||
|   > & { | ||||
|     displayable_description?: string | null; | ||||
|   })[]; | ||||
|   sortable_updated_date: number; | ||||
| } | ||||
| 
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user