Recherche plein texte dans le contenu des schémas
- /api/search cherche dans les noms et dans le texte des schémas (textes et libellés, noms de cadres, liens) ; tous les mots requis, casse et accents ignorés, correspondances sur le nom en premier. - Index en mémoire côté serveur : texte extrait à la première recherche, relu seulement si le fichier change (date ou taille), rien d'écrit sur le volume. - L'explorateur affiche l'extrait correspondant sous le nom. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H6J7vKwXx7uopxKhWfFJpm
This commit is contained in:
1 parent
9f9ec3b3a4
commit
c78df092c1
8 files changed
+329
-45
No files matched your search
@@ -10,6 +10,7 @@ import {
|
||||
stripExt,
|
||||
withExt,
|
||||
type Entry,
|
||||
type Hit,
|
||||
} from "./api";
|
||||
import {
|
||||
CloseIcon,
|
||||
@@ -84,7 +85,7 @@ export function Explorer({ current, refreshKey, onOpen, onMoved, onDeleted, onHi
|
||||
const [dir, setDir] = useState(() => (current ? dirname(current) : ""));
|
||||
const [entries, setEntries] = useState<Entry[]>([]);
|
||||
const [query, setQuery] = useState("");
|
||||
const [results, setResults] = useState<Entry[] | null>(null);
|
||||
const [results, setResults] = useState<Hit[] | null>(null);
|
||||
const [view, setView] = useState<View>(initialView);
|
||||
const [selected, setSelected] = useState<Set<string>>(new Set());
|
||||
const [anchor, setAnchor] = useState<string | null>(null); // origine d'une sélection Maj+clic
|
||||
@@ -436,7 +437,7 @@ export function Explorer({ current, refreshKey, onOpen, onMoved, onDeleted, onHi
|
||||
</div>
|
||||
);
|
||||
|
||||
const item = (e: Entry) => {
|
||||
const item = (e: Hit) => {
|
||||
const isSel = selected.has(e.path);
|
||||
const cls = [
|
||||
"item",
|
||||
@@ -472,6 +473,7 @@ export function Explorer({ current, refreshKey, onOpen, onMoved, onDeleted, onHi
|
||||
<span className="label">
|
||||
{e.isDir ? e.name : stripExt(e.name)}
|
||||
{results && <small className="muted where"> {dirname(e.path) || "/"}</small>}
|
||||
{e.snippet && <small className="snippet">{e.snippet}</small>}
|
||||
</span>
|
||||
</button>
|
||||
{!e.isDir && view === "list" && <span className="meta muted">{fmtDate(e.modTime)}</span>}
|
||||
@@ -551,7 +553,7 @@ export function Explorer({ current, refreshKey, onOpen, onMoved, onDeleted, onHi
|
||||
<SearchIcon />
|
||||
<input
|
||||
type="search"
|
||||
placeholder="Rechercher…"
|
||||
placeholder="Rechercher (noms, textes, IP…)"
|
||||
value={query}
|
||||
onChange={(ev) => setQuery(ev.target.value)}
|
||||
/>
|
||||
|
||||
+6
-1
@@ -8,6 +8,11 @@ export interface Entry {
|
||||
modTime: string;
|
||||
}
|
||||
|
||||
/** Résultat de recherche ; snippet = extrait du contenu qui correspond. */
|
||||
export interface Hit extends Entry {
|
||||
snippet?: string;
|
||||
}
|
||||
|
||||
export interface MoveResult {
|
||||
from: string;
|
||||
to?: string;
|
||||
@@ -72,7 +77,7 @@ export const api = {
|
||||
list: async (dir: string): Promise<Entry[]> =>
|
||||
(await request(`/api/list?path=${q(dir)}`)).json(),
|
||||
|
||||
search: async (text: string): Promise<Entry[]> =>
|
||||
search: async (text: string): Promise<Hit[]> =>
|
||||
(await request(`/api/search?q=${q(text)}`)).json(),
|
||||
|
||||
read: async (path: string): Promise<{ text: string; etag: string }> => {
|
||||
|
||||
@@ -1127,3 +1127,16 @@ li.active > .row {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Extrait du contenu qui correspond à la recherche. */
|
||||
.row .label .snippet {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 11.5px;
|
||||
color: var(--ex-on-primary-container);
|
||||
opacity: 0.8;
|
||||
}
|
||||
.entries.grid .snippet {
|
||||
text-align: center;
|
||||
}
|
||||
Reference in new issue
Block a user