living-documentation 3.8.0 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of living-documentation might be problematic. Click here for more details.
- package/README.md +2 -0
- package/dist/src/frontend/diagram/clipboard.js +11 -1
- package/dist/src/frontend/diagram/groups.js +99 -0
- package/dist/src/frontend/diagram/link-panel.js +138 -0
- package/dist/src/frontend/diagram/main.js +8 -1
- package/dist/src/frontend/diagram/network.js +63 -7
- package/dist/src/frontend/diagram/node-rendering.js +68 -1
- package/dist/src/frontend/diagram/persistence.js +6 -1
- package/dist/src/frontend/diagram.html +81 -3
- package/dist/src/frontend/index.html +124 -252
- package/dist/src/frontend/wordcloud.js +321 -0
- package/dist/src/routes/wordcloud.d.ts +3 -0
- package/dist/src/routes/wordcloud.d.ts.map +1 -0
- package/dist/src/routes/wordcloud.js +73 -0
- package/dist/src/routes/wordcloud.js.map +1 -0
- package/dist/src/server.d.ts.map +1 -1
- package/dist/src/server.js +2 -0
- package/dist/src/server.js.map +1 -1
- package/package.json +1 -1
|
@@ -265,10 +265,10 @@
|
|
|
265
265
|
<header
|
|
266
266
|
class="flex items-center gap-1 px-2 h-12 shrink-0 border-b border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 shadow-sm z-10"
|
|
267
267
|
>
|
|
268
|
-
<
|
|
269
|
-
|
|
268
|
+
<button
|
|
269
|
+
onclick="history.back()"
|
|
270
270
|
class="tool-btn !w-auto px-2 text-xs font-medium text-gray-500 dark:text-gray-400"
|
|
271
|
-
>←
|
|
271
|
+
>← Back</button
|
|
272
272
|
>
|
|
273
273
|
<div class="w-px h-6 bg-gray-200 dark:bg-gray-700 mx-0.5"></div>
|
|
274
274
|
<button id="btnSidebar" class="tool-btn" title="Mes diagrammes">
|
|
@@ -685,6 +685,16 @@
|
|
|
685
685
|
>
|
|
686
686
|
✎
|
|
687
687
|
</button>
|
|
688
|
+
<button
|
|
689
|
+
id="btnNodeLink"
|
|
690
|
+
class="tool-btn !w-6 !h-6"
|
|
691
|
+
title="Ajouter / modifier un lien"
|
|
692
|
+
>
|
|
693
|
+
<svg width="13" height="13" viewBox="0 0 13 13" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round">
|
|
694
|
+
<path d="M5 7.5a3.5 3.5 0 0 0 5 0l1.5-1.5a3.5 3.5 0 0 0-5-5L5 2.5"/>
|
|
695
|
+
<path d="M8 5.5a3.5 3.5 0 0 0-5 0L1.5 7a3.5 3.5 0 0 0 5 5L8 10.5"/>
|
|
696
|
+
</svg>
|
|
697
|
+
</button>
|
|
688
698
|
<div class="panel-sep"></div>
|
|
689
699
|
<button
|
|
690
700
|
id="btnNodeFontDecrease"
|
|
@@ -924,6 +934,74 @@
|
|
|
924
934
|
</svg>
|
|
925
935
|
PNG
|
|
926
936
|
</button>
|
|
937
|
+
|
|
938
|
+
<div class="panel-sep"></div>
|
|
939
|
+
|
|
940
|
+
<!-- Group -->
|
|
941
|
+
<button
|
|
942
|
+
id="btnGroup"
|
|
943
|
+
class="tool-btn !w-8 !h-7"
|
|
944
|
+
title="Grouper la sélection"
|
|
945
|
+
>
|
|
946
|
+
<svg width="22" height="14" viewBox="0 0 22 14" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round">
|
|
947
|
+
<rect x="0.7" y="1" width="20.6" height="12" rx="2" stroke-dasharray="2.5,1.5"/>
|
|
948
|
+
<rect x="4" y="4.5" width="3" height="5" rx="1" fill="currentColor"/>
|
|
949
|
+
<rect x="15" y="4.5" width="3" height="5" rx="1" fill="currentColor"/>
|
|
950
|
+
</svg>
|
|
951
|
+
</button>
|
|
952
|
+
|
|
953
|
+
<!-- Ungroup -->
|
|
954
|
+
<button
|
|
955
|
+
id="btnUngroup"
|
|
956
|
+
class="tool-btn !w-8 !h-7"
|
|
957
|
+
title="Dégrouper"
|
|
958
|
+
>
|
|
959
|
+
<svg width="26" height="14" viewBox="0 0 26 14" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round">
|
|
960
|
+
<rect x="0.7" y="1" width="9" height="12" rx="2" stroke-dasharray="2.5,1.5"/>
|
|
961
|
+
<rect x="3" y="4.5" width="3" height="5" rx="1" fill="currentColor"/>
|
|
962
|
+
<rect x="16.3" y="1" width="9" height="12" rx="2" stroke-dasharray="2.5,1.5"/>
|
|
963
|
+
<rect x="19" y="4.5" width="3" height="5" rx="1" fill="currentColor"/>
|
|
964
|
+
</svg>
|
|
965
|
+
</button>
|
|
966
|
+
</div>
|
|
967
|
+
|
|
968
|
+
<!-- Link panel -->
|
|
969
|
+
<div id="linkPanel" class="float-panel hidden" style="min-width:260px; flex-direction:column; align-items:stretch; gap:0.5rem; padding:0.75rem;">
|
|
970
|
+
<div class="text-xs font-semibold text-gray-500 dark:text-gray-400 mb-1">Lien sur cette forme</div>
|
|
971
|
+
|
|
972
|
+
<label class="flex items-center gap-2 text-xs cursor-pointer">
|
|
973
|
+
<input type="radio" name="linkType" id="linkTypeUrl" value="url" class="accent-orange-500"/>
|
|
974
|
+
URL externe
|
|
975
|
+
</label>
|
|
976
|
+
<div id="linkUrlRow">
|
|
977
|
+
<input id="linkUrlInput" type="url" placeholder="https://…"
|
|
978
|
+
class="w-full text-xs border border-gray-300 dark:border-gray-600 rounded px-2 py-1 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-1 focus:ring-orange-400"/>
|
|
979
|
+
</div>
|
|
980
|
+
|
|
981
|
+
<label class="flex items-center gap-2 text-xs cursor-pointer">
|
|
982
|
+
<input type="radio" name="linkType" id="linkTypeDiagram" value="diagram" class="accent-orange-500"/>
|
|
983
|
+
Diagramme existant
|
|
984
|
+
</label>
|
|
985
|
+
<div id="linkDiagramRow" class="hidden">
|
|
986
|
+
<select id="linkDiagramSelect"
|
|
987
|
+
class="w-full text-xs border border-gray-300 dark:border-gray-600 rounded px-2 py-1 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-1 focus:ring-orange-400">
|
|
988
|
+
</select>
|
|
989
|
+
</div>
|
|
990
|
+
|
|
991
|
+
<label class="flex items-center gap-2 text-xs cursor-pointer">
|
|
992
|
+
<input type="radio" name="linkType" id="linkTypeNew" value="new" class="accent-orange-500"/>
|
|
993
|
+
Nouveau diagramme
|
|
994
|
+
</label>
|
|
995
|
+
<div id="linkNewRow" class="hidden">
|
|
996
|
+
<input id="linkNewName" type="text" placeholder="Nom du diagramme…"
|
|
997
|
+
class="w-full text-xs border border-gray-300 dark:border-gray-600 rounded px-2 py-1 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-1 focus:ring-orange-400"/>
|
|
998
|
+
</div>
|
|
999
|
+
|
|
1000
|
+
<div class="flex gap-1 mt-1">
|
|
1001
|
+
<button id="btnLinkSave" class="flex-1 text-xs bg-orange-500 hover:bg-orange-600 text-white rounded px-2 py-1">Enregistrer</button>
|
|
1002
|
+
<button id="btnLinkRemove" class="text-xs border border-gray-300 dark:border-gray-600 rounded px-2 py-1 hover:bg-red-50 dark:hover:bg-red-900/20 hover:text-red-600">Retirer</button>
|
|
1003
|
+
<button id="btnLinkCancel" class="text-xs border border-gray-300 dark:border-gray-600 rounded px-2 py-1 hover:bg-gray-100 dark:hover:bg-gray-700">✕</button>
|
|
1004
|
+
</div>
|
|
927
1005
|
</div>
|
|
928
1006
|
|
|
929
1007
|
<!-- Edge panel -->
|
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
|
|
18
18
|
<!-- WordCloud2.js — vendored from npm, no CDN dependency -->
|
|
19
19
|
<script src="/vendor/wordcloud2.js"></script>
|
|
20
|
+
<!-- Word Cloud logic — stop words, browser, rendering -->
|
|
21
|
+
<script src="/wordcloud.js"></script>
|
|
20
22
|
|
|
21
23
|
<script>
|
|
22
24
|
tailwind.config = {
|
|
@@ -281,7 +283,7 @@
|
|
|
281
283
|
<article id="doc-view" class="hidden max-w-4xl mx-auto px-6 py-8">
|
|
282
284
|
<!-- Doc header -->
|
|
283
285
|
<header
|
|
284
|
-
class="mb-8 pb-6 border-b border-gray-
|
|
286
|
+
class="sticky top-0 z-10 bg-gray-50 dark:bg-gray-950 -mx-6 px-6 pt-8 mb-8 pb-6 border-b border-gray-300 dark:border-gray-800"
|
|
285
287
|
>
|
|
286
288
|
<div class="flex items-start justify-between gap-4 flex-wrap">
|
|
287
289
|
<div class="flex-1 min-w-0">
|
|
@@ -348,14 +350,16 @@
|
|
|
348
350
|
</div>
|
|
349
351
|
</div>
|
|
350
352
|
</div>
|
|
353
|
+
<!-- Search match notice (inside sticky header) -->
|
|
354
|
+
<div
|
|
355
|
+
id="search-notice"
|
|
356
|
+
class="hidden mt-4 rounded-lg bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 text-sm text-yellow-800 dark:text-yellow-300 overflow-hidden"
|
|
357
|
+
>
|
|
358
|
+
<div id="search-notice-title" class="px-3 py-2 font-medium border-b border-yellow-200 dark:border-yellow-800"></div>
|
|
359
|
+
<ol id="search-notice-list" class="max-h-40 overflow-y-auto divide-y divide-yellow-100 dark:divide-yellow-900/40 list-none m-0 p-0"></ol>
|
|
360
|
+
</div>
|
|
351
361
|
</header>
|
|
352
362
|
|
|
353
|
-
<!-- Search match notice -->
|
|
354
|
-
<div
|
|
355
|
-
id="search-notice"
|
|
356
|
-
class="hidden mb-4 px-3 py-2 rounded-lg bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 text-sm text-yellow-800 dark:text-yellow-300"
|
|
357
|
-
></div>
|
|
358
|
-
|
|
359
363
|
<!-- Rendered markdown -->
|
|
360
364
|
<div
|
|
361
365
|
id="doc-content"
|
|
@@ -391,6 +395,73 @@
|
|
|
391
395
|
✕ Close
|
|
392
396
|
</button>
|
|
393
397
|
</div>
|
|
398
|
+
<!-- Search root toolbar -->
|
|
399
|
+
<div class="border-b border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 shrink-0">
|
|
400
|
+
<!-- Row 1: path + browse + launch -->
|
|
401
|
+
<div class="flex items-center gap-3 px-6 py-3">
|
|
402
|
+
<label class="text-xs font-medium text-gray-500 dark:text-gray-400 shrink-0">Search root</label>
|
|
403
|
+
<input
|
|
404
|
+
id="wc-root"
|
|
405
|
+
type="text"
|
|
406
|
+
readonly
|
|
407
|
+
spellcheck="false"
|
|
408
|
+
class="flex-1 px-3 py-1.5 text-sm rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100 font-mono cursor-default focus:outline-none"
|
|
409
|
+
/>
|
|
410
|
+
<button
|
|
411
|
+
onclick="wcToggleBrowser()"
|
|
412
|
+
class="text-sm px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-700 text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors shrink-0"
|
|
413
|
+
>
|
|
414
|
+
📁 Browse
|
|
415
|
+
</button>
|
|
416
|
+
<button
|
|
417
|
+
onclick="launchWordCloud()"
|
|
418
|
+
class="text-sm px-4 py-1.5 rounded-lg bg-blue-600 hover:bg-blue-700 text-white font-semibold transition-colors shrink-0"
|
|
419
|
+
>
|
|
420
|
+
▶ Launch
|
|
421
|
+
</button>
|
|
422
|
+
</div>
|
|
423
|
+
<!-- Row 2: extension checkboxes -->
|
|
424
|
+
<div class="flex items-center gap-x-4 gap-y-2 px-6 pb-3 flex-wrap">
|
|
425
|
+
<span class="text-xs font-medium text-gray-500 dark:text-gray-400 shrink-0">Extensions</span>
|
|
426
|
+
<label class="flex items-center gap-1.5 text-xs text-gray-700 dark:text-gray-300 cursor-pointer"><input type="checkbox" class="wc-ext" value="md" checked /> .md</label>
|
|
427
|
+
<label class="flex items-center gap-1.5 text-xs text-gray-700 dark:text-gray-300 cursor-pointer"><input type="checkbox" class="wc-ext" value="txt" /> .txt</label>
|
|
428
|
+
<span class="text-gray-200 dark:text-gray-700 text-xs">|</span>
|
|
429
|
+
<label class="flex items-center gap-1.5 text-xs text-gray-700 dark:text-gray-300 cursor-pointer"><input type="checkbox" class="wc-ext" value="ts" /> .ts</label>
|
|
430
|
+
<label class="flex items-center gap-1.5 text-xs text-gray-700 dark:text-gray-300 cursor-pointer"><input type="checkbox" class="wc-ext" value="tsx" /> .tsx</label>
|
|
431
|
+
<label class="flex items-center gap-1.5 text-xs text-gray-700 dark:text-gray-300 cursor-pointer"><input type="checkbox" class="wc-ext" value="js" /> .js</label>
|
|
432
|
+
<label class="flex items-center gap-1.5 text-xs text-gray-700 dark:text-gray-300 cursor-pointer"><input type="checkbox" class="wc-ext" value="jsx" /> .jsx</label>
|
|
433
|
+
<span class="text-gray-200 dark:text-gray-700 text-xs">|</span>
|
|
434
|
+
<label class="flex items-center gap-1.5 text-xs text-gray-700 dark:text-gray-300 cursor-pointer"><input type="checkbox" class="wc-ext" value="java" /> .java</label>
|
|
435
|
+
<label class="flex items-center gap-1.5 text-xs text-gray-700 dark:text-gray-300 cursor-pointer"><input type="checkbox" class="wc-ext" value="kt" /> .kt</label>
|
|
436
|
+
<label class="flex items-center gap-1.5 text-xs text-gray-700 dark:text-gray-300 cursor-pointer"><input type="checkbox" class="wc-ext" value="py" /> .py</label>
|
|
437
|
+
<label class="flex items-center gap-1.5 text-xs text-gray-700 dark:text-gray-300 cursor-pointer"><input type="checkbox" class="wc-ext" value="go" /> .go</label>
|
|
438
|
+
<label class="flex items-center gap-1.5 text-xs text-gray-700 dark:text-gray-300 cursor-pointer"><input type="checkbox" class="wc-ext" value="rs" /> .rs</label>
|
|
439
|
+
<label class="flex items-center gap-1.5 text-xs text-gray-700 dark:text-gray-300 cursor-pointer"><input type="checkbox" class="wc-ext" value="cs" /> .cs</label>
|
|
440
|
+
<label class="flex items-center gap-1.5 text-xs text-gray-700 dark:text-gray-300 cursor-pointer"><input type="checkbox" class="wc-ext" value="swift" /> .swift</label>
|
|
441
|
+
<label class="flex items-center gap-1.5 text-xs text-gray-700 dark:text-gray-300 cursor-pointer"><input type="checkbox" class="wc-ext" value="rb" /> .rb</label>
|
|
442
|
+
<span class="text-gray-200 dark:text-gray-700 text-xs">|</span>
|
|
443
|
+
<label class="flex items-center gap-1.5 text-xs text-gray-700 dark:text-gray-300 cursor-pointer"><input type="checkbox" class="wc-ext" value="html" /> .html</label>
|
|
444
|
+
<label class="flex items-center gap-1.5 text-xs text-gray-700 dark:text-gray-300 cursor-pointer"><input type="checkbox" class="wc-ext" value="css" /> .css</label>
|
|
445
|
+
<label class="flex items-center gap-1.5 text-xs text-gray-700 dark:text-gray-300 cursor-pointer"><input type="checkbox" class="wc-ext" value="scss" /> .scss</label>
|
|
446
|
+
<span class="text-gray-200 dark:text-gray-700 text-xs">|</span>
|
|
447
|
+
<label class="flex items-center gap-1.5 text-xs text-gray-700 dark:text-gray-300 cursor-pointer"><input type="checkbox" class="wc-ext" value="yml" /> .yml</label>
|
|
448
|
+
<label class="flex items-center gap-1.5 text-xs text-gray-700 dark:text-gray-300 cursor-pointer"><input type="checkbox" class="wc-ext" value="yaml" /> .yaml</label>
|
|
449
|
+
<label class="flex items-center gap-1.5 text-xs text-gray-700 dark:text-gray-300 cursor-pointer"><input type="checkbox" class="wc-ext" value="json" /> .json</label>
|
|
450
|
+
<label class="flex items-center gap-1.5 text-xs text-gray-700 dark:text-gray-300 cursor-pointer"><input type="checkbox" class="wc-ext" value="xml" /> .xml</label>
|
|
451
|
+
<label class="flex items-center gap-1.5 text-xs text-gray-700 dark:text-gray-300 cursor-pointer"><input type="checkbox" class="wc-ext" value="toml" /> .toml</label>
|
|
452
|
+
</div>
|
|
453
|
+
<!-- Row 3: inline folder browser (collapsed by default) -->
|
|
454
|
+
<div id="wc-browser" class="hidden border-t border-gray-200 dark:border-gray-700">
|
|
455
|
+
<div class="flex items-center gap-2 px-3 py-2 bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700">
|
|
456
|
+
<button id="wc-browse-up" onclick="wcBrowseUp()"
|
|
457
|
+
class="text-xs text-blue-600 dark:text-blue-400 hover:underline disabled:opacity-30 disabled:pointer-events-none shrink-0">
|
|
458
|
+
↑ Up
|
|
459
|
+
</button>
|
|
460
|
+
<span id="wc-browse-path" class="font-mono text-xs text-gray-400 dark:text-gray-500 truncate flex-1 text-right"></span>
|
|
461
|
+
</div>
|
|
462
|
+
<div id="wc-browse-list" class="divide-y divide-gray-100 dark:divide-gray-800 max-h-52 overflow-y-auto"></div>
|
|
463
|
+
</div>
|
|
464
|
+
</div>
|
|
394
465
|
<div
|
|
395
466
|
id="wc-body"
|
|
396
467
|
class="flex-1 relative overflow-hidden flex items-center justify-center"
|
|
@@ -419,6 +490,7 @@
|
|
|
419
490
|
applyDarkMode(loadDarkPref());
|
|
420
491
|
setupDarkToggle();
|
|
421
492
|
setupSearch();
|
|
493
|
+
wcRestorePrefs();
|
|
422
494
|
await loadConfig();
|
|
423
495
|
await loadDocuments();
|
|
424
496
|
|
|
@@ -622,8 +694,8 @@
|
|
|
622
694
|
// Highlight search matches in content
|
|
623
695
|
const notice = document.getElementById("search-notice");
|
|
624
696
|
if (searchQuery) {
|
|
625
|
-
highlightMatches(contentEl, searchQuery);
|
|
626
|
-
|
|
697
|
+
const matches = highlightMatches(contentEl, searchQuery);
|
|
698
|
+
buildSearchNotice(matches, searchQuery);
|
|
627
699
|
notice.classList.remove("hidden");
|
|
628
700
|
} else {
|
|
629
701
|
notice.classList.add("hidden");
|
|
@@ -685,7 +757,10 @@
|
|
|
685
757
|
}
|
|
686
758
|
|
|
687
759
|
// ── Edit mode ──────────────────────────────────────────────
|
|
760
|
+
let _editScrollTop = 0;
|
|
761
|
+
|
|
688
762
|
function enterEditMode() {
|
|
763
|
+
_editScrollTop = document.getElementById("content-area").scrollTop;
|
|
689
764
|
const editor = document.getElementById("doc-editor");
|
|
690
765
|
editor.value = currentDocContent;
|
|
691
766
|
document.getElementById("doc-content").classList.add("hidden");
|
|
@@ -704,6 +779,7 @@
|
|
|
704
779
|
document.getElementById("edit-actions").classList.add("hidden");
|
|
705
780
|
document.getElementById("view-actions").classList.remove("hidden");
|
|
706
781
|
document.getElementById("edit-save-msg").textContent = "";
|
|
782
|
+
document.getElementById("content-area").scrollTop = _editScrollTop;
|
|
707
783
|
}
|
|
708
784
|
|
|
709
785
|
async function handleEditorPaste(e) {
|
|
@@ -816,16 +892,50 @@
|
|
|
816
892
|
const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
|
|
817
893
|
const nodes = [];
|
|
818
894
|
while (walker.nextNode()) nodes.push(walker.currentNode);
|
|
819
|
-
const re = new RegExp(
|
|
820
|
-
|
|
821
|
-
"gi",
|
|
822
|
-
);
|
|
895
|
+
const re = new RegExp(`(${q.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})`, "gi");
|
|
896
|
+
let idx = 0;
|
|
823
897
|
nodes.forEach((node) => {
|
|
824
898
|
if (!node.textContent.toLowerCase().includes(q.toLowerCase())) return;
|
|
825
899
|
const span = document.createElement("span");
|
|
826
|
-
span.innerHTML = node.textContent.replace(re, "
|
|
900
|
+
span.innerHTML = node.textContent.replace(re, (m) => `<mark id="match-${idx++}">${m}</mark>`);
|
|
827
901
|
node.parentNode.replaceChild(span, node);
|
|
828
902
|
});
|
|
903
|
+
// Build snippet list from inserted marks
|
|
904
|
+
const matches = [];
|
|
905
|
+
el.querySelectorAll("mark[id^='match-']").forEach((mark) => {
|
|
906
|
+
const block = mark.closest("p, li, td, h1, h2, h3, h4, h5, h6, pre") || mark.parentElement;
|
|
907
|
+
const full = block.textContent.trim();
|
|
908
|
+
const pos = full.toLowerCase().indexOf(q.toLowerCase());
|
|
909
|
+
const start = Math.max(0, pos - 40);
|
|
910
|
+
const end = Math.min(full.length, pos + q.length + 40);
|
|
911
|
+
const snippet = (start > 0 ? "…" : "") + full.slice(start, end) + (end < full.length ? "…" : "");
|
|
912
|
+
matches.push({ id: mark.id, snippet });
|
|
913
|
+
});
|
|
914
|
+
return matches;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
function buildSearchNotice(matches, q) {
|
|
918
|
+
document.getElementById("search-notice-title").textContent =
|
|
919
|
+
`Showing ${matches.length} match${matches.length === 1 ? "" : "es"} for "${q}"`;
|
|
920
|
+
const list = document.getElementById("search-notice-list");
|
|
921
|
+
list.innerHTML = matches.map((m, i) =>
|
|
922
|
+
`<li>
|
|
923
|
+
<button onclick="scrollToMatch('${m.id}')"
|
|
924
|
+
class="w-full text-left px-3 py-1.5 hover:bg-yellow-100 dark:hover:bg-yellow-900/30 transition-colors">
|
|
925
|
+
<span class="text-yellow-600 dark:text-yellow-500 font-mono text-xs mr-2">${i + 1}.</span
|
|
926
|
+
><span class="text-xs">${esc(m.snippet)}</span>
|
|
927
|
+
</button>
|
|
928
|
+
</li>`
|
|
929
|
+
).join("");
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
function scrollToMatch(id) {
|
|
933
|
+
const mark = document.getElementById(id);
|
|
934
|
+
const container = document.getElementById("content-area");
|
|
935
|
+
if (!mark || !container) return;
|
|
936
|
+
const markTop = mark.getBoundingClientRect().top;
|
|
937
|
+
const containerTop = container.getBoundingClientRect().top;
|
|
938
|
+
container.scrollTop += markTop - containerTop - container.clientHeight / 3;
|
|
829
939
|
}
|
|
830
940
|
|
|
831
941
|
function esc(str) {
|
|
@@ -837,244 +947,6 @@
|
|
|
837
947
|
.replace(/'/g, "'");
|
|
838
948
|
}
|
|
839
949
|
|
|
840
|
-
// ── Word Cloud ─────────────────────────────────────────────
|
|
841
|
-
const WC_STOP_WORDS = new Set([
|
|
842
|
-
// English
|
|
843
|
-
"the",
|
|
844
|
-
"and",
|
|
845
|
-
"for",
|
|
846
|
-
"are",
|
|
847
|
-
"but",
|
|
848
|
-
"not",
|
|
849
|
-
"you",
|
|
850
|
-
"all",
|
|
851
|
-
"this",
|
|
852
|
-
"that",
|
|
853
|
-
"with",
|
|
854
|
-
"have",
|
|
855
|
-
"from",
|
|
856
|
-
"they",
|
|
857
|
-
"will",
|
|
858
|
-
"one",
|
|
859
|
-
"been",
|
|
860
|
-
"can",
|
|
861
|
-
"has",
|
|
862
|
-
"was",
|
|
863
|
-
"more",
|
|
864
|
-
"also",
|
|
865
|
-
"when",
|
|
866
|
-
"there",
|
|
867
|
-
"their",
|
|
868
|
-
"what",
|
|
869
|
-
"about",
|
|
870
|
-
"which",
|
|
871
|
-
"would",
|
|
872
|
-
"into",
|
|
873
|
-
"than",
|
|
874
|
-
"then",
|
|
875
|
-
"each",
|
|
876
|
-
"just",
|
|
877
|
-
"over",
|
|
878
|
-
"after",
|
|
879
|
-
"such",
|
|
880
|
-
"here",
|
|
881
|
-
"its",
|
|
882
|
-
"your",
|
|
883
|
-
"our",
|
|
884
|
-
"some",
|
|
885
|
-
"were",
|
|
886
|
-
"very",
|
|
887
|
-
"only",
|
|
888
|
-
"out",
|
|
889
|
-
"had",
|
|
890
|
-
"she",
|
|
891
|
-
"his",
|
|
892
|
-
"her",
|
|
893
|
-
"him",
|
|
894
|
-
"who",
|
|
895
|
-
"how",
|
|
896
|
-
"any",
|
|
897
|
-
"other",
|
|
898
|
-
"these",
|
|
899
|
-
"those",
|
|
900
|
-
"being",
|
|
901
|
-
"may",
|
|
902
|
-
"use",
|
|
903
|
-
"used",
|
|
904
|
-
"using",
|
|
905
|
-
"should",
|
|
906
|
-
"could",
|
|
907
|
-
"would",
|
|
908
|
-
"shall",
|
|
909
|
-
"must",
|
|
910
|
-
"need",
|
|
911
|
-
"via",
|
|
912
|
-
"per",
|
|
913
|
-
"like",
|
|
914
|
-
// French
|
|
915
|
-
"les",
|
|
916
|
-
"des",
|
|
917
|
-
"une",
|
|
918
|
-
"pour",
|
|
919
|
-
"pas",
|
|
920
|
-
"sur",
|
|
921
|
-
"par",
|
|
922
|
-
"est",
|
|
923
|
-
"qui",
|
|
924
|
-
"que",
|
|
925
|
-
"dans",
|
|
926
|
-
"avec",
|
|
927
|
-
"sont",
|
|
928
|
-
"plus",
|
|
929
|
-
"tout",
|
|
930
|
-
"aux",
|
|
931
|
-
"mais",
|
|
932
|
-
"comme",
|
|
933
|
-
"vous",
|
|
934
|
-
"nous",
|
|
935
|
-
"leur",
|
|
936
|
-
"lui",
|
|
937
|
-
"elle",
|
|
938
|
-
"ils",
|
|
939
|
-
"elles",
|
|
940
|
-
"ces",
|
|
941
|
-
"ses",
|
|
942
|
-
"mon",
|
|
943
|
-
"ton",
|
|
944
|
-
"son",
|
|
945
|
-
"mes",
|
|
946
|
-
"tes",
|
|
947
|
-
"ainsi",
|
|
948
|
-
"donc",
|
|
949
|
-
"alors",
|
|
950
|
-
"car",
|
|
951
|
-
"peut",
|
|
952
|
-
"fait",
|
|
953
|
-
"encore",
|
|
954
|
-
"bien",
|
|
955
|
-
"aussi",
|
|
956
|
-
"très",
|
|
957
|
-
"même",
|
|
958
|
-
"entre",
|
|
959
|
-
"vers",
|
|
960
|
-
"dont",
|
|
961
|
-
"sans",
|
|
962
|
-
"sous",
|
|
963
|
-
]);
|
|
964
|
-
|
|
965
|
-
async function openWordCloud() {
|
|
966
|
-
const overlay = document.getElementById("wc-overlay");
|
|
967
|
-
const status = document.getElementById("wc-status");
|
|
968
|
-
const canvas = document.getElementById("wc-canvas");
|
|
969
|
-
overlay.classList.remove("hidden");
|
|
970
|
-
status.textContent = "Loading documents…";
|
|
971
|
-
status.classList.remove("hidden");
|
|
972
|
-
canvas.classList.add("hidden");
|
|
973
|
-
|
|
974
|
-
try {
|
|
975
|
-
const docs = allDocs.length
|
|
976
|
-
? allDocs
|
|
977
|
-
: await fetch("/api/documents").then((r) => r.json());
|
|
978
|
-
status.textContent = `Analyzing ${docs.length} documents…`;
|
|
979
|
-
|
|
980
|
-
const results = await Promise.all(
|
|
981
|
-
docs.map((doc) =>
|
|
982
|
-
fetch("/api/documents/" + doc.id)
|
|
983
|
-
.then((r) => (r.ok ? r.json() : null))
|
|
984
|
-
.catch(() => null),
|
|
985
|
-
),
|
|
986
|
-
);
|
|
987
|
-
|
|
988
|
-
const freq = {};
|
|
989
|
-
for (const doc of results) {
|
|
990
|
-
console.log("doc = ", doc);
|
|
991
|
-
if (!doc?.content) continue;
|
|
992
|
-
for (const w of extractWordsFromMarkdown(doc.content)) {
|
|
993
|
-
freq[w] = (freq[w] || 0) + 1;
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
|
-
|
|
997
|
-
const list = Object.entries(freq)
|
|
998
|
-
.filter(([, n]) => n >= 2)
|
|
999
|
-
.sort((a, b) => b[1] - a[1])
|
|
1000
|
-
.slice(0, 150);
|
|
1001
|
-
|
|
1002
|
-
if (!list.length) {
|
|
1003
|
-
status.textContent = "Not enough words found.";
|
|
1004
|
-
return;
|
|
1005
|
-
}
|
|
1006
|
-
|
|
1007
|
-
renderWordCloud(list);
|
|
1008
|
-
} catch (err) {
|
|
1009
|
-
status.textContent = "Error: " + err.message;
|
|
1010
|
-
}
|
|
1011
|
-
}
|
|
1012
|
-
|
|
1013
|
-
function extractWordsFromMarkdown(text) {
|
|
1014
|
-
return text
|
|
1015
|
-
.replace(/```[\s\S]*?```/g, "")
|
|
1016
|
-
.replace(/`[^`\n]+`/g, "")
|
|
1017
|
-
.replace(/\[([^\]]*)\]\([^)]*\)/g, "$1")
|
|
1018
|
-
.replace(/https?:\/\/\S+/g, "")
|
|
1019
|
-
.replace(/[#*_~>`|!\[\](){}=\-+]/g, " ")
|
|
1020
|
-
.toLowerCase()
|
|
1021
|
-
.split(/[^a-zàâäéèêëïîôùûü']+/)
|
|
1022
|
-
.map((w) => w.replace(/^'+|'+$/g, ""))
|
|
1023
|
-
.filter((w) => w.length > 3 && !WC_STOP_WORDS.has(w));
|
|
1024
|
-
}
|
|
1025
|
-
|
|
1026
|
-
function renderWordCloud(list) {
|
|
1027
|
-
const canvas = document.getElementById("wc-canvas");
|
|
1028
|
-
const body = document.getElementById("wc-body");
|
|
1029
|
-
canvas.width = body.clientWidth;
|
|
1030
|
-
canvas.height = body.clientHeight;
|
|
1031
|
-
|
|
1032
|
-
const isDark = document.documentElement.classList.contains("dark");
|
|
1033
|
-
const colors = isDark
|
|
1034
|
-
? [
|
|
1035
|
-
"#60a5fa",
|
|
1036
|
-
"#34d399",
|
|
1037
|
-
"#f9a8d4",
|
|
1038
|
-
"#a78bfa",
|
|
1039
|
-
"#fbbf24",
|
|
1040
|
-
"#6ee7b7",
|
|
1041
|
-
"#93c5fd",
|
|
1042
|
-
"#fb923c",
|
|
1043
|
-
]
|
|
1044
|
-
: [
|
|
1045
|
-
"#1d4ed8",
|
|
1046
|
-
"#047857",
|
|
1047
|
-
"#7c3aed",
|
|
1048
|
-
"#b45309",
|
|
1049
|
-
"#be123c",
|
|
1050
|
-
"#0369a1",
|
|
1051
|
-
"#4338ca",
|
|
1052
|
-
"#c2410c",
|
|
1053
|
-
];
|
|
1054
|
-
|
|
1055
|
-
const maxFreq = list[0][1];
|
|
1056
|
-
const wordList = list.map(([w, n]) => [
|
|
1057
|
-
w,
|
|
1058
|
-
Math.max(10, Math.round((72 * n) / maxFreq)),
|
|
1059
|
-
]);
|
|
1060
|
-
|
|
1061
|
-
document.getElementById("wc-status").classList.add("hidden");
|
|
1062
|
-
canvas.classList.remove("hidden");
|
|
1063
|
-
|
|
1064
|
-
WordCloud(canvas, {
|
|
1065
|
-
list: wordList,
|
|
1066
|
-
gridSize: Math.round((8 * canvas.width) / 1024),
|
|
1067
|
-
fontFamily: "ui-sans-serif, system-ui, sans-serif",
|
|
1068
|
-
color: () => colors[Math.floor(Math.random() * colors.length)],
|
|
1069
|
-
backgroundColor: isDark ? "#030712" : "#ffffff",
|
|
1070
|
-
rotateRatio: 0.3,
|
|
1071
|
-
minSize: 10,
|
|
1072
|
-
});
|
|
1073
|
-
}
|
|
1074
|
-
|
|
1075
|
-
function closeWordCloud() {
|
|
1076
|
-
document.getElementById("wc-overlay").classList.add("hidden");
|
|
1077
|
-
}
|
|
1078
950
|
|
|
1079
951
|
// Browser back/forward
|
|
1080
952
|
window.addEventListener("popstate", (e) => {
|