From 19a697e7872713e3bd8f1062ddb16c17f0955bc4 Mon Sep 17 00:00:00 2001 From: GauthierWebDev Date: Fri, 11 Apr 2025 11:43:20 +0200 Subject: [PATCH] feat: Update search functionality with FlexSearchService --- app/components/syntax/Hero.tsx | 2 +- app/components/syntax/Search.telefunc.ts | 16 ++++++++++++---- app/components/syntax/Search.tsx | 19 +++++++++---------- app/lib/search.ts | 17 +++++++++++------ app/package.json | 1 + app/pnpm-lock.yaml | 8 ++++++++ app/services/DocsService.ts | 5 +++++ app/services/FlexSearchService.ts | 12 +++++++++--- app/tsconfig.json | 2 +- 9 files changed, 57 insertions(+), 25 deletions(-) diff --git a/app/components/syntax/Hero.tsx b/app/components/syntax/Hero.tsx index 4c3aa03..34b70ad 100644 --- a/app/components/syntax/Hero.tsx +++ b/app/components/syntax/Hero.tsx @@ -50,7 +50,7 @@ export function Hero() { Souviens-toi que tu développeras.

- Découvrez des ressources essentielles pour améliorer vos compétences en développement. + Découvrez des ressources essentielles pour améliorer tes compétences en développement.

diff --git a/app/components/syntax/Search.telefunc.ts b/app/components/syntax/Search.telefunc.ts index 7adefc9..41c292f 100644 --- a/app/components/syntax/Search.telefunc.ts +++ b/app/components/syntax/Search.telefunc.ts @@ -1,8 +1,16 @@ -import type { SearchResult } from "@/lib/search"; +// import type { SearchResult } from "@/lib/search"; +import type { SearchResult } from "@/services/FlexSearchService"; -import { buildSearchIndex, search } from "@/lib/search"; +// import { buildSearchIndex, search } from "@/lib/search"; +import { buildFlexSearch } from "@/services/FlexSearchService"; +import { docsService } from "@/services/DocsService"; export const onSearch = async (query: string): Promise => { - const searchIndex = buildSearchIndex("./app/docs"); - return search(searchIndex, query); + // const searchIndex = buildSearchIndex("./data/docs"); + // return search(searchIndex, query); + + const search = buildFlexSearch(await docsService.fetchDocs()); + const results = search(query); + + return results; }; diff --git a/app/components/syntax/Search.tsx b/app/components/syntax/Search.tsx index 0c8912a..26d8241 100644 --- a/app/components/syntax/Search.tsx +++ b/app/components/syntax/Search.tsx @@ -1,3 +1,6 @@ +// import type { SearchResult } from "@/lib/search"; +import type { SearchResult } from "@/services/FlexSearchService"; + import { forwardRef, Fragment, Suspense, useCallback, useEffect, useId, useRef, useState } from "react"; import Highlighter from "react-highlight-words"; import { usePageContext } from "vike-react/usePageContext"; @@ -15,8 +18,6 @@ import clsx from "clsx"; import { navigation } from "@/lib/navigation"; import { onSearch } from "./Search.telefunc"; -import type { SearchResult } from "@/lib/search"; - type EmptyObject = Record; type Autocomplete = AutocompleteApi; @@ -39,7 +40,7 @@ function useAutocomplete({ close }: { close: (autocomplete: Autocomplete) => voi return; } - // router.push(itemUrl); + routerNavigate(itemUrl); if (itemUrl === window.location.pathname + window.location.search + window.location.hash) { close(autocomplete); @@ -66,8 +67,6 @@ function useAutocomplete({ close }: { close: (autocomplete: Autocomplete) => voi { sourceId: "documentation", getItems() { - console.log({ searchResult }); - return []; return searchResult; }, getItemUrl({ item }) { @@ -118,9 +117,9 @@ function SearchResult({ collection, query, }: { - result: Result; + result: SearchResult; autocomplete: Autocomplete; - collection: AutocompleteCollection; + collection: AutocompleteCollection; query: string; }) { let id = useId(); @@ -173,7 +172,7 @@ function SearchResults({ }: { autocomplete: Autocomplete; query: string; - collection: AutocompleteCollection; + collection: AutocompleteCollection; }) { if (collection.items.length === 0) { return ( @@ -204,7 +203,7 @@ const SearchInput = forwardRef< React.ComponentRef<"input">, { autocomplete: Autocomplete; - autocompleteState: AutocompleteState | EmptyObject; + autocompleteState: AutocompleteState | EmptyObject; onClose: () => void; } >(function SearchInput({ autocomplete, autocompleteState, onClose }, inputRef) { @@ -391,7 +390,7 @@ export function Search() { {...buttonProps} > - Search docs + Rechercher... {modifierKey && ( {modifierKey} diff --git a/app/lib/search.ts b/app/lib/search.ts index afbe78c..0677dab 100644 --- a/app/lib/search.ts +++ b/app/lib/search.ts @@ -33,7 +33,7 @@ export interface SearchResult { function toString(node: Node): string { let str = node.type === "text" && typeof node.attributes?.content === "string" ? node.attributes.content : ""; if ("children" in node) { - for (let child of node.children) { + for (let child of node.children!) { str += toString(child); } } @@ -46,14 +46,14 @@ function extractSections(node: Node, sections: Section[], isRoot: boolean = true } if (node.type === "heading" || node.type === "paragraph") { let content = toString(node).trim(); - if (node.type === "heading" && node.attributes?.level <= 2) { + if (node.type === "heading" && node.attributes?.level! <= 2) { let hash = node.attributes?.id ?? slugify(content); sections.push({ content, hash, subsections: [] }); } else { sections[sections.length - 1].subsections.push(content); } } else if ("children" in node) { - for (let child of node.children) { + for (let child of node.children!) { extractSections(child, sections, false); } } @@ -85,6 +85,7 @@ export function buildSearchIndex(pagesDir: string): FlexSearch.Document = {}, ): SearchResult[] { - const result = sectionIndex.search(query, { + const results = sectionIndex.search(query, { ...options, enrich: true, }); - if (result.length === 0) { + + // console.log({ sectionIndex, query, options, results }); + + if (results.length === 0) { return []; } - return result[0].result.map((item: any) => ({ + + return results[0].result.map((item: any) => ({ url: item.id, title: item.doc.title, pageTitle: item.doc.pageTitle, diff --git a/app/package.json b/app/package.json index b09d34d..105c918 100644 --- a/app/package.json +++ b/app/package.json @@ -53,6 +53,7 @@ "tsx": "^4.19.3", "typescript": "^5.8.2", "typescript-eslint": "^8.26.0", + "user-agent-data-types": "^0.4.2", "vite": "^6.2.1" }, "type": "module" diff --git a/app/pnpm-lock.yaml b/app/pnpm-lock.yaml index 1d582d4..52d3206 100644 --- a/app/pnpm-lock.yaml +++ b/app/pnpm-lock.yaml @@ -144,6 +144,9 @@ importers: typescript-eslint: specifier: ^8.26.0 version: 8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2) + user-agent-data-types: + specifier: ^0.4.2 + version: 0.4.2 vite: specifier: ^6.2.1 version: 6.2.1(@types/node@18.19.80)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3) @@ -2676,6 +2679,9 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + user-agent-data-types@0.4.2: + resolution: {integrity: sha512-jXep3kO/dGNmDOkbDa8ccp4QArgxR4I76m3QVcJ1aOF0B9toc+YtSXtX5gLdDTZXyWlpQYQrABr6L1L2GZOghw==} + util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -5464,6 +5470,8 @@ snapshots: dependencies: punycode: 2.3.1 + user-agent-data-types@0.4.2: {} + util-deprecate@1.0.2: {} vike-react@0.5.13(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(vike@0.4.225(react-streaming@0.3.50(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(vite@6.2.1(@types/node@18.19.80)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3))): diff --git a/app/services/DocsService.ts b/app/services/DocsService.ts index f9fca1c..b5ce264 100644 --- a/app/services/DocsService.ts +++ b/app/services/DocsService.ts @@ -100,6 +100,11 @@ class DocsService { return { key, sections }; }); + return data; + } + + public async buildSearch() { + const data = await this.fetchDocs(); this.search = buildFlexSearch(data); } diff --git a/app/services/FlexSearchService.ts b/app/services/FlexSearchService.ts index 577493f..bf6d481 100644 --- a/app/services/FlexSearchService.ts +++ b/app/services/FlexSearchService.ts @@ -2,7 +2,7 @@ import type { FlexSearchData } from "./DocsService"; import FlexSearch from "flexsearch"; -interface SearchResult { +interface NativeSearchResult { id: string; doc: { title: string; @@ -10,6 +10,12 @@ interface SearchResult { }; } +export type SearchResult = { + url: string; + title: string; + pageTitle?: string; +}; + export function buildFlexSearch(data: FlexSearchData) { const sectionIndex = new FlexSearch.Document({ tokenize: "full", @@ -36,7 +42,7 @@ export function buildFlexSearch(data: FlexSearchData) { } } - return function search(query: string) { + return function search(query: string): SearchResult[] { const result = sectionIndex.search(query, 5, { enrich: true, }); @@ -44,7 +50,7 @@ export function buildFlexSearch(data: FlexSearchData) { if (result.length === 0) return []; return result[0].result.map((rawItem) => { - const item = rawItem as unknown as SearchResult; + const item = rawItem as unknown as NativeSearchResult; return { url: item.id, diff --git a/app/tsconfig.json b/app/tsconfig.json index 2eec5d4..8703a13 100644 --- a/app/tsconfig.json +++ b/app/tsconfig.json @@ -11,7 +11,7 @@ "moduleResolution": "Bundler", "target": "ES2022", "lib": ["DOM", "DOM.Iterable", "ESNext"], - "types": ["vite/client", "vike-react"], + "types": ["vite/client", "vike-react", "user-agent-data-types"], "jsx": "preserve", "jsxImportSource": "react", "baseUrl": "./",