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.
Accédez aux ressources
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": "./",