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],
|
||||
|
|
|
@ -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…
Reference in New Issue