feat: Update search functionality with FlexSearchService
This commit is contained in:
parent
8c29d760db
commit
19a697e787
@ -50,7 +50,7 @@ export function Hero() {
|
||||
Souviens-toi que tu développeras.
|
||||
</p>
|
||||
<p className="mt-3 text-2xl tracking-tight text-slate-400">
|
||||
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.
|
||||
</p>
|
||||
<div className="mt-8 flex gap-4 md:justify-center lg:justify-start">
|
||||
<Button href="/">Accédez aux ressources</Button>
|
||||
|
||||
@ -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<SearchResult[]> => {
|
||||
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;
|
||||
};
|
||||
|
||||
@ -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<string, never>;
|
||||
|
||||
type Autocomplete = AutocompleteApi<SearchResult, React.SyntheticEvent, React.MouseEvent, React.KeyboardEvent>;
|
||||
@ -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<Result>;
|
||||
collection: AutocompleteCollection<SearchResult>;
|
||||
query: string;
|
||||
}) {
|
||||
let id = useId();
|
||||
@ -173,7 +172,7 @@ function SearchResults({
|
||||
}: {
|
||||
autocomplete: Autocomplete;
|
||||
query: string;
|
||||
collection: AutocompleteCollection<Result>;
|
||||
collection: AutocompleteCollection<SearchResult>;
|
||||
}) {
|
||||
if (collection.items.length === 0) {
|
||||
return (
|
||||
@ -204,7 +203,7 @@ const SearchInput = forwardRef<
|
||||
React.ComponentRef<"input">,
|
||||
{
|
||||
autocomplete: Autocomplete;
|
||||
autocompleteState: AutocompleteState<Result> | EmptyObject;
|
||||
autocompleteState: AutocompleteState<SearchResult> | EmptyObject;
|
||||
onClose: () => void;
|
||||
}
|
||||
>(function SearchInput({ autocomplete, autocompleteState, onClose }, inputRef) {
|
||||
@ -391,7 +390,7 @@ export function Search() {
|
||||
{...buttonProps}
|
||||
>
|
||||
<SearchIcon className="h-5 w-5 flex-none fill-slate-400 group-hover:fill-slate-500 md:group-hover:fill-slate-400 dark:fill-slate-500" />
|
||||
<span className="sr-only md:not-sr-only md:ml-2 md:text-slate-500 md:dark:text-slate-400">Search docs</span>
|
||||
<span className="sr-only md:not-sr-only md:ml-2 md:text-slate-500 md:dark:text-slate-400">Rechercher...</span>
|
||||
{modifierKey && (
|
||||
<kbd className="ml-auto hidden font-medium text-slate-400 md:block dark:text-slate-500">
|
||||
<kbd className="font-sans">{modifierKey}</kbd>
|
||||
|
||||
@ -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<SearchRe
|
||||
sections = cache.get(file)![1];
|
||||
} else {
|
||||
const ast = Markdoc.parse(md);
|
||||
console.log(ast.attributes);
|
||||
const title = ast.attributes?.frontmatter?.match(/^title:\s*(.*?)\s*$/m)?.[1];
|
||||
sections = [{ content: title ?? "", subsections: [] }];
|
||||
extractSections(ast, sections);
|
||||
@ -113,14 +114,18 @@ export function search(
|
||||
query: string,
|
||||
options: Record<string, any> = {},
|
||||
): 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,
|
||||
|
||||
@ -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"
|
||||
|
||||
8
app/pnpm-lock.yaml
generated
8
app/pnpm-lock.yaml
generated
@ -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))):
|
||||
|
||||
@ -100,6 +100,11 @@ class DocsService {
|
||||
return { key, sections };
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public async buildSearch() {
|
||||
const data = await this.fetchDocs();
|
||||
this.search = buildFlexSearch(data);
|
||||
}
|
||||
|
||||
|
||||
@ -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<true>(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,
|
||||
|
||||
@ -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": "./",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user