fix(ai): espaces dans les noms de fichiers et chemins des liens (BUG-042)
Les noms/paths de l'assistant peuvent contenir des espaces ; la detection et la resolution les prennent desormais en charge de bout en bout : - liens markdown : cible avec espaces, encadree par <...> ou avec un "titre", et URL percent-encodees (%20) decodees ; - code inline : `Ma note.md` / `Mon dossier/Ma note.md` reconnus (_looksLikePath accepte les espaces, jeu de caracteres strict pour rejeter extraits de code et commandes shell) ; - mentions brutes : liees uniquement si presentes dans l'index du vault (_linkifySpacePaths + _confirmPathInCache), avec acceptation du plus long suffixe aligne sur un mot -> le mot de prose precedent n'est pas avale ; - _normalizeLinkPath : trim ; _renderMdLink : decodage %20. Tests : frontend IA 57/57 (+5), 9 suites JSDOM, validate-imports 36 modules, pytest 963 passed / 6 skipped.
This commit is contained in:
@@ -415,6 +415,65 @@ async function main() {
|
||||
assert.equal(b._classifyPath("projets/2026"), "dir", "dir path → reveal");
|
||||
});
|
||||
|
||||
// ── 12b. Spaces in file names / paths ──
|
||||
await test("_looksLikePath accepts spaced names but rejects code snippets", () => {
|
||||
const b = new BooksLM();
|
||||
assert.equal(b._looksLikePath("Ma note.md"), true, "spaced filename is a path");
|
||||
assert.equal(b._looksLikePath("Mon dossier/Ma note.md"), true, "spaced path is a path");
|
||||
assert.equal(b._looksLikePath("const x = 1;"), false, "code snippet rejected");
|
||||
assert.equal(b._looksLikePath("npm run test"), false, "shell command rejected");
|
||||
});
|
||||
|
||||
await test("_renderMarkdown linkifies inline code containing spaces", () => {
|
||||
const b = new BooksLM();
|
||||
const html = b._renderMarkdown("Voir `Mon dossier/Ma note.md` pour le détail.");
|
||||
assert.ok(html.includes('data-path="Mon dossier/Ma note.md"'), "spaced path kept intact");
|
||||
assert.ok(html.includes('data-kind="file"'), "spaced file classified as file");
|
||||
});
|
||||
|
||||
await test("_renderMarkdown linkifies markdown links with spaces and %20", () => {
|
||||
const b = new BooksLM();
|
||||
const spaced = b._renderMarkdown("[Ma note](Mon dossier/Ma note.md)");
|
||||
assert.ok(spaced.includes('data-path="Mon dossier/Ma note.md"'), "raw spaced target kept");
|
||||
|
||||
const encoded = b._renderMarkdown("[Ma note](Mon%20dossier/Ma%20note.md)");
|
||||
assert.ok(encoded.includes('data-path="Mon dossier/Ma note.md"'), "%20 decoded to space");
|
||||
});
|
||||
|
||||
await test("bare spaced paths link only when present in the vault index", () => {
|
||||
const b = new BooksLM();
|
||||
|
||||
// No index → no link (prose must not become a dead link).
|
||||
b._pathCache = [];
|
||||
const noCache = b._renderMarkdown("Ouvre Mon dossier/Ma note.md pour la suite");
|
||||
assert.ok(!noCache.includes("bookslm-path-link"), "no link without index");
|
||||
|
||||
// Indexed path → link, and the leading prose word is not swallowed.
|
||||
b._pathCache = [{ path: "Mon dossier/Ma note.md", name: "Ma note.md", type: "file" }];
|
||||
const html = b._renderMarkdown("Ouvre Mon dossier/Ma note.md pour la suite");
|
||||
assert.ok(html.includes('data-path="Mon dossier/Ma note.md"'), "indexed spaced path linkified");
|
||||
assert.ok(html.includes(">Mon dossier/Ma note.md</a>"), "link text excludes leading word");
|
||||
assert.ok(html.includes("Ouvre Mon") === false || !html.includes(">Ouvre Mon"), "prose word kept outside the link");
|
||||
});
|
||||
|
||||
await test("clicking a spaced file link resolves against the index", async () => {
|
||||
const b = new BooksLM();
|
||||
b._activeVault = () => "TestVault";
|
||||
const opened = [];
|
||||
const revealed = [];
|
||||
b._openFileLink = (p) => { opened.push(p); return Promise.resolve(); };
|
||||
b._revealPath = (p) => { revealed.push(p); return Promise.resolve(); };
|
||||
b._ensurePaths = async () => ([
|
||||
{ path: "Mon dossier/Ma note.md", name: "Ma note.md", type: "file" },
|
||||
{ path: "Mon dossier/Sous dossier", name: "Sous dossier", type: "directory" },
|
||||
]);
|
||||
|
||||
await b._activatePath("Ma note.md", "file");
|
||||
await b._activatePath("Sous dossier", "dir");
|
||||
assert.deepEqual(opened, ["Mon dossier/Ma note.md"], "spaced filename resolves to its real path");
|
||||
assert.deepEqual(revealed, ["Mon dossier/Sous dossier"], "spaced directory resolves too");
|
||||
});
|
||||
|
||||
await test("_renderMarkdown keeps external links external", () => {
|
||||
const b = new BooksLM();
|
||||
const html = b._renderMarkdown("[site](https://example.com/page)");
|
||||
|
||||
Reference in New Issue
Block a user