living-documentation 7.6.0 → 7.7.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.
- package/dist/src/frontend/accuracy-gauge.js +47 -0
- package/dist/src/frontend/documents.js +5 -0
- package/dist/src/frontend/i18n/en.json +14 -0
- package/dist/src/frontend/i18n/fr.json +14 -0
- package/dist/src/frontend/index.html +148 -0
- package/dist/src/frontend/metadata.js +292 -0
- package/dist/src/frontend/sidebar.js +8 -8
- package/dist/src/lib/hash.d.ts +2 -0
- package/dist/src/lib/hash.d.ts.map +1 -0
- package/dist/src/lib/hash.js +18 -0
- package/dist/src/lib/hash.js.map +1 -0
- package/dist/src/lib/metadata.d.ts +30 -0
- package/dist/src/lib/metadata.d.ts.map +1 -0
- package/dist/src/lib/metadata.js +109 -0
- package/dist/src/lib/metadata.js.map +1 -0
- package/dist/src/mcp/server.d.ts.map +1 -1
- package/dist/src/mcp/server.js +93 -0
- package/dist/src/mcp/server.js.map +1 -1
- package/dist/src/mcp/tools/metadata.d.ts +34 -0
- package/dist/src/mcp/tools/metadata.d.ts.map +1 -0
- package/dist/src/mcp/tools/metadata.js +76 -0
- package/dist/src/mcp/tools/metadata.js.map +1 -0
- package/dist/src/routes/browse-source.d.ts +3 -0
- package/dist/src/routes/browse-source.d.ts.map +1 -0
- package/dist/src/routes/browse-source.js +79 -0
- package/dist/src/routes/browse-source.js.map +1 -0
- package/dist/src/routes/metadata.d.ts +3 -0
- package/dist/src/routes/metadata.d.ts.map +1 -0
- package/dist/src/routes/metadata.js +107 -0
- package/dist/src/routes/metadata.js.map +1 -0
- package/dist/src/server.d.ts.map +1 -1
- package/dist/src/server.js +4 -0
- package/dist/src/server.js.map +1 -1
- package/dist/starting-doc/.annotations.json +1 -13
- package/dist/starting-doc/.metadata.json +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// ── Accuracy gauge ──────────────────────────────────────────────────────────
|
|
2
|
+
// Shown in the sticky document header (right-aligned, own row) when the
|
|
3
|
+
// current document has at least one metadata entry.
|
|
4
|
+
|
|
5
|
+
function accuracyColor(ratio) {
|
|
6
|
+
const pct = Math.max(0, Math.min(1, ratio)) * 100;
|
|
7
|
+
if (pct > 80) return "#16a34a"; // green-600
|
|
8
|
+
if (pct >= 60) return "#eab308"; // yellow-500
|
|
9
|
+
if (pct >= 40) return "#f97316"; // orange-500
|
|
10
|
+
return "#dc2626"; // red-600
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function renderAccuracyGauge(report) {
|
|
14
|
+
const wrap = document.getElementById("accuracy-gauge");
|
|
15
|
+
if (!wrap) return;
|
|
16
|
+
if (!report || report.total === 0) {
|
|
17
|
+
wrap.classList.add("hidden");
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
wrap.classList.remove("hidden");
|
|
21
|
+
|
|
22
|
+
const pct = Math.round(report.accuracy * 100);
|
|
23
|
+
const color = accuracyColor(report.accuracy);
|
|
24
|
+
|
|
25
|
+
const label = document.getElementById("accuracy-gauge-label");
|
|
26
|
+
const bar = document.getElementById("accuracy-gauge-bar");
|
|
27
|
+
const value = document.getElementById("accuracy-gauge-value");
|
|
28
|
+
|
|
29
|
+
if (label) label.textContent = window.t("accuracy.label");
|
|
30
|
+
if (bar) {
|
|
31
|
+
bar.style.width = pct + "%";
|
|
32
|
+
bar.style.backgroundColor = color;
|
|
33
|
+
}
|
|
34
|
+
if (value) {
|
|
35
|
+
value.textContent = pct + "%";
|
|
36
|
+
value.style.color = color;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
wrap.title = window
|
|
40
|
+
.t("accuracy.tooltip")
|
|
41
|
+
.replace("{unchanged}", String(report.unchanged))
|
|
42
|
+
.replace("{modified}", String(report.modified))
|
|
43
|
+
.replace("{missing}", String(report.missing))
|
|
44
|
+
.replace("{total}", String(report.total));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
window.renderAccuracyGauge = renderAccuracyGauge;
|
|
@@ -164,6 +164,11 @@ async function openDocument(id, skipHistory = false, fromLink = false, anchor =
|
|
|
164
164
|
// Load annotations for this document
|
|
165
165
|
loadAnnotations(id);
|
|
166
166
|
|
|
167
|
+
// Load source-file metadata report (drives the accuracy gauge)
|
|
168
|
+
if (typeof loadMetadataReport === "function") {
|
|
169
|
+
loadMetadataReport(id);
|
|
170
|
+
}
|
|
171
|
+
|
|
167
172
|
// Add IDs to headings for anchor navigation
|
|
168
173
|
contentEl.querySelectorAll("h1, h2, h3, h4, h5, h6").forEach((h) => {
|
|
169
174
|
if (!h.id) {
|
|
@@ -69,6 +69,20 @@
|
|
|
69
69
|
"doc.failed_to_load": "Failed to load document: ",
|
|
70
70
|
"doc.copied": "✓ Copied!",
|
|
71
71
|
|
|
72
|
+
"metadata.button": "Metadata",
|
|
73
|
+
"metadata.button_title": "Manage source-file metadata",
|
|
74
|
+
"metadata.title": "Source metadata",
|
|
75
|
+
"metadata.subtitle": "Attach source files to track whether this document is still accurate.",
|
|
76
|
+
"metadata.add": "Add source file",
|
|
77
|
+
"metadata.refresh": "Refresh hashes",
|
|
78
|
+
"metadata.remove": "Remove",
|
|
79
|
+
"metadata.empty": "No metadata yet — add a source file to start tracking accuracy.",
|
|
80
|
+
"metadata.status.unchanged": "Unchanged",
|
|
81
|
+
"metadata.status.modified": "Modified",
|
|
82
|
+
"metadata.status.missing": "Missing",
|
|
83
|
+
"accuracy.label": "Accuracy",
|
|
84
|
+
"accuracy.tooltip": "Unchanged: {unchanged} · Modified: {modified} · Missing: {missing} · Total: {total}",
|
|
85
|
+
|
|
72
86
|
"modal.new_folder.title": "New folder",
|
|
73
87
|
"modal.new_folder.name_label": "Folder name",
|
|
74
88
|
"modal.new_folder.name_placeholder": "my-folder",
|
|
@@ -69,6 +69,20 @@
|
|
|
69
69
|
"doc.failed_to_load": "Impossible de charger le document : ",
|
|
70
70
|
"doc.copied": "✓ Copié !",
|
|
71
71
|
|
|
72
|
+
"metadata.button": "Métadonnées",
|
|
73
|
+
"metadata.button_title": "Gérer les métadonnées des fichiers source",
|
|
74
|
+
"metadata.title": "Métadonnées des sources",
|
|
75
|
+
"metadata.subtitle": "Rattachez des fichiers source pour suivre si ce document reste à jour.",
|
|
76
|
+
"metadata.add": "Ajouter un fichier source",
|
|
77
|
+
"metadata.refresh": "Rafraîchir les hashs",
|
|
78
|
+
"metadata.remove": "Retirer",
|
|
79
|
+
"metadata.empty": "Aucune métadonnée — ajoutez un fichier source pour commencer à suivre la justesse.",
|
|
80
|
+
"metadata.status.unchanged": "Inchangé",
|
|
81
|
+
"metadata.status.modified": "Modifié",
|
|
82
|
+
"metadata.status.missing": "Manquant",
|
|
83
|
+
"accuracy.label": "Justesse",
|
|
84
|
+
"accuracy.tooltip": "Inchangés : {unchanged} · Modifiés : {modified} · Manquants : {missing} · Total : {total}",
|
|
85
|
+
|
|
72
86
|
"modal.new_folder.title": "Nouveau dossier",
|
|
73
87
|
"modal.new_folder.name_label": "Nom du dossier",
|
|
74
88
|
"modal.new_folder.name_placeholder": "mon-dossier",
|
|
@@ -47,6 +47,8 @@
|
|
|
47
47
|
<script defer src="/misc.js"></script>
|
|
48
48
|
<script defer src="/snippets.js"></script>
|
|
49
49
|
<script defer src="/annotations.js"></script>
|
|
50
|
+
<script defer src="/metadata.js"></script>
|
|
51
|
+
<script defer src="/accuracy-gauge.js"></script>
|
|
50
52
|
<script defer src="/export.js"></script>
|
|
51
53
|
<script defer src="/diagram-link-modal.js"></script>
|
|
52
54
|
<script defer src="/new-folder-modal.js"></script>
|
|
@@ -573,6 +575,15 @@
|
|
|
573
575
|
>
|
|
574
576
|
<i class="fa-solid fa-link"></i> <span data-i18n="doc.copy_link_btn">Copy link</span>
|
|
575
577
|
</button>
|
|
578
|
+
<button
|
|
579
|
+
onclick="openMetadataModal()"
|
|
580
|
+
id="metadata-btn"
|
|
581
|
+
data-i18n-title="metadata.button_title"
|
|
582
|
+
title="Manage source-file metadata"
|
|
583
|
+
class="no-print 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"
|
|
584
|
+
>
|
|
585
|
+
<i class="fa-solid fa-code-compare"></i> <span data-i18n="metadata.button">Metadata</span>
|
|
586
|
+
</button>
|
|
576
587
|
<button
|
|
577
588
|
onclick="enterEditMode()"
|
|
578
589
|
data-i18n-title="doc.edit"
|
|
@@ -620,6 +631,33 @@
|
|
|
620
631
|
</div>
|
|
621
632
|
</div>
|
|
622
633
|
</div>
|
|
634
|
+
<!-- Accuracy gauge (shown when the current doc has metadata entries) -->
|
|
635
|
+
<div
|
|
636
|
+
id="accuracy-gauge"
|
|
637
|
+
onclick="openMetadataModal()"
|
|
638
|
+
class="hidden no-print mt-3 flex items-center gap-3 ml-auto w-full sm:w-80 cursor-pointer select-none"
|
|
639
|
+
>
|
|
640
|
+
<span
|
|
641
|
+
id="accuracy-gauge-label"
|
|
642
|
+
data-i18n="accuracy.label"
|
|
643
|
+
class="text-xs text-gray-500 dark:text-gray-400 shrink-0"
|
|
644
|
+
>Accuracy</span>
|
|
645
|
+
<div
|
|
646
|
+
class="flex-1 h-2 rounded-full bg-gray-200 dark:bg-gray-700 overflow-hidden"
|
|
647
|
+
>
|
|
648
|
+
<div
|
|
649
|
+
id="accuracy-gauge-bar"
|
|
650
|
+
class="h-full transition-all"
|
|
651
|
+
style="width: 0%; background-color: #9ca3af"
|
|
652
|
+
></div>
|
|
653
|
+
</div>
|
|
654
|
+
<span
|
|
655
|
+
id="accuracy-gauge-value"
|
|
656
|
+
class="text-xs font-semibold shrink-0 tabular-nums"
|
|
657
|
+
style="color: #9ca3af"
|
|
658
|
+
>0%</span>
|
|
659
|
+
</div>
|
|
660
|
+
|
|
623
661
|
<!-- Navigation history back-links -->
|
|
624
662
|
<div
|
|
625
663
|
id="doc-back"
|
|
@@ -660,6 +698,116 @@
|
|
|
660
698
|
</div>
|
|
661
699
|
<!-- end root -->
|
|
662
700
|
|
|
701
|
+
<!-- ── Metadata modal ── -->
|
|
702
|
+
<div
|
|
703
|
+
id="metadata-modal"
|
|
704
|
+
class="hidden fixed inset-0 z-50 flex items-center justify-center bg-black/50"
|
|
705
|
+
>
|
|
706
|
+
<div
|
|
707
|
+
class="bg-white dark:bg-gray-900 rounded-xl shadow-xl w-full max-w-2xl mx-4 p-6 space-y-4"
|
|
708
|
+
>
|
|
709
|
+
<div class="flex items-start justify-between gap-4">
|
|
710
|
+
<div>
|
|
711
|
+
<h3 class="text-base font-semibold text-gray-900 dark:text-gray-50">
|
|
712
|
+
<i class="fa-solid fa-code-compare mr-1"></i>
|
|
713
|
+
<span data-i18n="metadata.title">Source metadata</span>
|
|
714
|
+
</h3>
|
|
715
|
+
<p
|
|
716
|
+
data-i18n="metadata.subtitle"
|
|
717
|
+
class="text-xs text-gray-500 dark:text-gray-400 mt-1"
|
|
718
|
+
>
|
|
719
|
+
Attach source files to track whether this document is still accurate.
|
|
720
|
+
</p>
|
|
721
|
+
</div>
|
|
722
|
+
<button
|
|
723
|
+
onclick="closeMetadataModal()"
|
|
724
|
+
data-i18n-title="common.close"
|
|
725
|
+
title="Close"
|
|
726
|
+
class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 transition-colors"
|
|
727
|
+
>
|
|
728
|
+
<i class="fa-solid fa-xmark text-lg"></i>
|
|
729
|
+
</button>
|
|
730
|
+
</div>
|
|
731
|
+
|
|
732
|
+
<!-- Summary -->
|
|
733
|
+
<div
|
|
734
|
+
id="metadata-summary"
|
|
735
|
+
class="text-xs text-gray-600 dark:text-gray-300 bg-gray-50 dark:bg-gray-800 rounded-lg px-3 py-2"
|
|
736
|
+
></div>
|
|
737
|
+
|
|
738
|
+
<!-- List -->
|
|
739
|
+
<div class="border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden">
|
|
740
|
+
<div
|
|
741
|
+
id="metadata-list"
|
|
742
|
+
class="max-h-64 overflow-y-auto"
|
|
743
|
+
></div>
|
|
744
|
+
<div
|
|
745
|
+
id="metadata-empty"
|
|
746
|
+
data-i18n="metadata.empty"
|
|
747
|
+
class="hidden px-3 py-4 text-center text-xs text-gray-400"
|
|
748
|
+
>
|
|
749
|
+
No metadata yet — add a source file to start tracking accuracy.
|
|
750
|
+
</div>
|
|
751
|
+
</div>
|
|
752
|
+
|
|
753
|
+
<!-- Actions -->
|
|
754
|
+
<div class="flex items-center gap-2 flex-wrap">
|
|
755
|
+
<button
|
|
756
|
+
onclick="metadataToggleBrowser()"
|
|
757
|
+
class="text-sm px-3 py-1.5 rounded-lg border border-blue-200 dark:border-blue-700 text-blue-600 dark:text-blue-400 hover:bg-blue-50 dark:hover:bg-blue-900/30 transition-colors"
|
|
758
|
+
>
|
|
759
|
+
<i class="fa-solid fa-plus"></i> <span data-i18n="metadata.add">Add source file</span>
|
|
760
|
+
</button>
|
|
761
|
+
<button
|
|
762
|
+
onclick="metadataRefresh()"
|
|
763
|
+
id="metadata-refresh-btn"
|
|
764
|
+
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"
|
|
765
|
+
>
|
|
766
|
+
<i class="fa-solid fa-arrows-rotate"></i> <span data-i18n="metadata.refresh">Refresh hashes</span>
|
|
767
|
+
</button>
|
|
768
|
+
</div>
|
|
769
|
+
|
|
770
|
+
<!-- Source browser -->
|
|
771
|
+
<div
|
|
772
|
+
id="metadata-browser"
|
|
773
|
+
class="hidden border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden text-sm"
|
|
774
|
+
>
|
|
775
|
+
<div
|
|
776
|
+
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"
|
|
777
|
+
>
|
|
778
|
+
<button
|
|
779
|
+
id="metadata-browse-up"
|
|
780
|
+
onclick="metadataBrowseUp()"
|
|
781
|
+
data-i18n="common.up"
|
|
782
|
+
class="text-xs text-blue-600 dark:text-blue-400 hover:underline disabled:opacity-30 disabled:pointer-events-none shrink-0"
|
|
783
|
+
>
|
|
784
|
+
↑ Up
|
|
785
|
+
</button>
|
|
786
|
+
<span
|
|
787
|
+
id="metadata-browse-path"
|
|
788
|
+
class="text-xs text-gray-500 dark:text-gray-400 font-mono truncate flex-1"
|
|
789
|
+
></span>
|
|
790
|
+
</div>
|
|
791
|
+
<div
|
|
792
|
+
id="metadata-browse-list"
|
|
793
|
+
class="max-h-56 overflow-y-auto divide-y divide-gray-100 dark:divide-gray-800"
|
|
794
|
+
></div>
|
|
795
|
+
</div>
|
|
796
|
+
|
|
797
|
+
<p id="metadata-error" class="hidden text-xs text-red-500"></p>
|
|
798
|
+
|
|
799
|
+
<div class="flex justify-end">
|
|
800
|
+
<button
|
|
801
|
+
onclick="closeMetadataModal()"
|
|
802
|
+
data-i18n="common.close"
|
|
803
|
+
class="text-sm px-4 py-2 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"
|
|
804
|
+
>
|
|
805
|
+
Close
|
|
806
|
+
</button>
|
|
807
|
+
</div>
|
|
808
|
+
</div>
|
|
809
|
+
</div>
|
|
810
|
+
|
|
663
811
|
<!-- ── New Folder modal ── -->
|
|
664
812
|
<div
|
|
665
813
|
id="new-folder-modal"
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
// ── Metadata (source-file dependencies) ─────────────────────────────────────
|
|
2
|
+
// Exposes: openMetadataModal(), closeMetadataModal(),
|
|
3
|
+
// metadataRefresh(), metadataAddPath(), metadataRemovePath(),
|
|
4
|
+
// loadMetadataReport(docId) → used by accuracy-gauge.js
|
|
5
|
+
|
|
6
|
+
let metadataReport = null;
|
|
7
|
+
let metadataBrowseCurrent = ""; // relative to sourceRoot
|
|
8
|
+
let metadataBrowseCache = null;
|
|
9
|
+
|
|
10
|
+
function metadataCurrentDocId() {
|
|
11
|
+
return typeof currentDocId !== "undefined" ? currentDocId : null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function loadMetadataReport(docId) {
|
|
15
|
+
if (!docId) {
|
|
16
|
+
metadataReport = null;
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
const r = await fetch(
|
|
21
|
+
"/api/metadata/" + encodeURIComponent(docId),
|
|
22
|
+
);
|
|
23
|
+
if (!r.ok) throw new Error(r.statusText);
|
|
24
|
+
metadataReport = await r.json();
|
|
25
|
+
} catch {
|
|
26
|
+
metadataReport = null;
|
|
27
|
+
}
|
|
28
|
+
if (typeof renderAccuracyGauge === "function") {
|
|
29
|
+
renderAccuracyGauge(metadataReport);
|
|
30
|
+
}
|
|
31
|
+
return metadataReport;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function statusBadge(status) {
|
|
35
|
+
if (status === "unchanged") {
|
|
36
|
+
return `<span class="inline-flex items-center gap-1 text-xs font-semibold px-2 py-0.5 rounded-full bg-green-100 text-green-700 dark:bg-green-900/40 dark:text-green-300">
|
|
37
|
+
<i class="fa-solid fa-check"></i>
|
|
38
|
+
<span data-i18n="metadata.status.unchanged">Unchanged</span>
|
|
39
|
+
</span>`;
|
|
40
|
+
}
|
|
41
|
+
if (status === "modified") {
|
|
42
|
+
return `<span class="inline-flex items-center gap-1 text-xs font-semibold px-2 py-0.5 rounded-full bg-amber-100 text-amber-700 dark:bg-amber-900/40 dark:text-amber-300">
|
|
43
|
+
<i class="fa-solid fa-triangle-exclamation"></i>
|
|
44
|
+
<span data-i18n="metadata.status.modified">Modified</span>
|
|
45
|
+
</span>`;
|
|
46
|
+
}
|
|
47
|
+
return `<span class="inline-flex items-center gap-1 text-xs font-semibold px-2 py-0.5 rounded-full bg-red-100 text-red-700 dark:bg-red-900/40 dark:text-red-300">
|
|
48
|
+
<i class="fa-solid fa-circle-xmark"></i>
|
|
49
|
+
<span data-i18n="metadata.status.missing">Missing</span>
|
|
50
|
+
</span>`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function renderMetadataList() {
|
|
54
|
+
const listEl = document.getElementById("metadata-list");
|
|
55
|
+
const emptyEl = document.getElementById("metadata-empty");
|
|
56
|
+
if (!listEl || !emptyEl) return;
|
|
57
|
+
|
|
58
|
+
const items = (metadataReport && metadataReport.items) || [];
|
|
59
|
+
if (items.length === 0) {
|
|
60
|
+
listEl.innerHTML = "";
|
|
61
|
+
emptyEl.classList.remove("hidden");
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
emptyEl.classList.add("hidden");
|
|
65
|
+
|
|
66
|
+
listEl.innerHTML = items
|
|
67
|
+
.map((it) => {
|
|
68
|
+
const safePath = esc(it.path);
|
|
69
|
+
return `<div class="flex items-center gap-2 px-3 py-2 border-b border-gray-100 dark:border-gray-800">
|
|
70
|
+
<div class="flex-1 min-w-0">
|
|
71
|
+
<div class="text-sm font-mono truncate text-gray-800 dark:text-gray-200" title="${safePath}">${safePath}</div>
|
|
72
|
+
</div>
|
|
73
|
+
${statusBadge(it.status)}
|
|
74
|
+
<button
|
|
75
|
+
onclick="metadataRemovePath('${safePath.replace(/'/g, "\\'")}')"
|
|
76
|
+
data-i18n-title="metadata.remove"
|
|
77
|
+
title="Remove"
|
|
78
|
+
class="text-xs px-2 py-1 rounded-lg border border-red-200 dark:border-red-700 text-red-500 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/40 transition-colors"
|
|
79
|
+
>
|
|
80
|
+
<i class="fa-solid fa-trash"></i>
|
|
81
|
+
</button>
|
|
82
|
+
</div>`;
|
|
83
|
+
})
|
|
84
|
+
.join("");
|
|
85
|
+
|
|
86
|
+
if (typeof window.applyI18n === "function") window.applyI18n();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function renderMetadataSummary() {
|
|
90
|
+
const el = document.getElementById("metadata-summary");
|
|
91
|
+
if (!el) return;
|
|
92
|
+
if (!metadataReport || metadataReport.total === 0) {
|
|
93
|
+
el.textContent = "";
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const pct = Math.round(metadataReport.accuracy * 100);
|
|
97
|
+
const { total, unchanged, modified, missing } = metadataReport;
|
|
98
|
+
el.innerHTML = `<span class="font-semibold">${pct}%</span> · ${unchanged}/${total} ${window.t("metadata.status.unchanged")} · ${modified} ${window.t("metadata.status.modified")} · ${missing} ${window.t("metadata.status.missing")}`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function openMetadataModal() {
|
|
102
|
+
const docId = metadataCurrentDocId();
|
|
103
|
+
if (!docId) return;
|
|
104
|
+
await loadMetadataReport(docId);
|
|
105
|
+
renderMetadataList();
|
|
106
|
+
renderMetadataSummary();
|
|
107
|
+
document.getElementById("metadata-modal").classList.remove("hidden");
|
|
108
|
+
document.getElementById("metadata-error").classList.add("hidden");
|
|
109
|
+
// Reset browser
|
|
110
|
+
metadataBrowseCurrent = "";
|
|
111
|
+
metadataBrowseCache = null;
|
|
112
|
+
document.getElementById("metadata-browser").classList.add("hidden");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function closeMetadataModal() {
|
|
116
|
+
document.getElementById("metadata-modal").classList.add("hidden");
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function metadataRefresh() {
|
|
120
|
+
const docId = metadataCurrentDocId();
|
|
121
|
+
if (!docId) return;
|
|
122
|
+
const btn = document.getElementById("metadata-refresh-btn");
|
|
123
|
+
if (btn) btn.disabled = true;
|
|
124
|
+
try {
|
|
125
|
+
const r = await fetch(
|
|
126
|
+
"/api/metadata/" + encodeURIComponent(docId) + "/refresh",
|
|
127
|
+
{ method: "POST" },
|
|
128
|
+
);
|
|
129
|
+
if (!r.ok) throw new Error(r.statusText);
|
|
130
|
+
metadataReport = await r.json();
|
|
131
|
+
renderMetadataList();
|
|
132
|
+
renderMetadataSummary();
|
|
133
|
+
if (typeof renderAccuracyGauge === "function") {
|
|
134
|
+
renderAccuracyGauge(metadataReport);
|
|
135
|
+
}
|
|
136
|
+
} catch (err) {
|
|
137
|
+
showMetadataError(err.message);
|
|
138
|
+
} finally {
|
|
139
|
+
if (btn) btn.disabled = false;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function metadataRemovePath(path) {
|
|
144
|
+
const docId = metadataCurrentDocId();
|
|
145
|
+
if (!docId) return;
|
|
146
|
+
try {
|
|
147
|
+
const r = await fetch(
|
|
148
|
+
"/api/metadata/" + encodeURIComponent(docId),
|
|
149
|
+
{
|
|
150
|
+
method: "DELETE",
|
|
151
|
+
headers: { "Content-Type": "application/json" },
|
|
152
|
+
body: JSON.stringify({ path }),
|
|
153
|
+
},
|
|
154
|
+
);
|
|
155
|
+
if (!r.ok) throw new Error(r.statusText);
|
|
156
|
+
metadataReport = await r.json();
|
|
157
|
+
renderMetadataList();
|
|
158
|
+
renderMetadataSummary();
|
|
159
|
+
if (typeof renderAccuracyGauge === "function") {
|
|
160
|
+
renderAccuracyGauge(metadataReport);
|
|
161
|
+
}
|
|
162
|
+
refreshBrowserIfOpen();
|
|
163
|
+
} catch (err) {
|
|
164
|
+
showMetadataError(err.message);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async function metadataAddPath(relPath) {
|
|
169
|
+
const docId = metadataCurrentDocId();
|
|
170
|
+
if (!docId) return;
|
|
171
|
+
try {
|
|
172
|
+
const r = await fetch(
|
|
173
|
+
"/api/metadata/" + encodeURIComponent(docId),
|
|
174
|
+
{
|
|
175
|
+
method: "POST",
|
|
176
|
+
headers: { "Content-Type": "application/json" },
|
|
177
|
+
body: JSON.stringify({ path: relPath }),
|
|
178
|
+
},
|
|
179
|
+
);
|
|
180
|
+
if (!r.ok) {
|
|
181
|
+
const body = await r.json().catch(() => ({}));
|
|
182
|
+
throw new Error(body.error || r.statusText);
|
|
183
|
+
}
|
|
184
|
+
metadataReport = await r.json();
|
|
185
|
+
renderMetadataList();
|
|
186
|
+
renderMetadataSummary();
|
|
187
|
+
if (typeof renderAccuracyGauge === "function") {
|
|
188
|
+
renderAccuracyGauge(metadataReport);
|
|
189
|
+
}
|
|
190
|
+
refreshBrowserIfOpen();
|
|
191
|
+
} catch (err) {
|
|
192
|
+
showMetadataError(err.message);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function refreshBrowserIfOpen() {
|
|
197
|
+
const b = document.getElementById("metadata-browser");
|
|
198
|
+
if (b && !b.classList.contains("hidden")) {
|
|
199
|
+
metadataBrowseLoad(metadataBrowseCurrent);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function showMetadataError(msg) {
|
|
204
|
+
const el = document.getElementById("metadata-error");
|
|
205
|
+
if (!el) return;
|
|
206
|
+
el.textContent = window.t("common.error_prefix") + msg;
|
|
207
|
+
el.classList.remove("hidden");
|
|
208
|
+
setTimeout(() => el.classList.add("hidden"), 5000);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ── Source browser ─────────────────────────────────────────────────────────
|
|
212
|
+
|
|
213
|
+
function metadataToggleBrowser() {
|
|
214
|
+
const b = document.getElementById("metadata-browser");
|
|
215
|
+
if (b.classList.contains("hidden")) {
|
|
216
|
+
b.classList.remove("hidden");
|
|
217
|
+
metadataBrowseLoad("");
|
|
218
|
+
} else {
|
|
219
|
+
b.classList.add("hidden");
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async function metadataBrowseLoad(relPath) {
|
|
224
|
+
const listEl = document.getElementById("metadata-browse-list");
|
|
225
|
+
const pathEl = document.getElementById("metadata-browse-path");
|
|
226
|
+
const upBtn = document.getElementById("metadata-browse-up");
|
|
227
|
+
if (!listEl) return;
|
|
228
|
+
listEl.innerHTML = `<div class="px-3 py-2 text-xs text-gray-400">${esc(window.t("common.loading"))}</div>`;
|
|
229
|
+
try {
|
|
230
|
+
const r = await fetch(
|
|
231
|
+
"/api/browse-source?path=" + encodeURIComponent(relPath || ""),
|
|
232
|
+
);
|
|
233
|
+
if (!r.ok) {
|
|
234
|
+
const body = await r.json().catch(() => ({}));
|
|
235
|
+
throw new Error(body.error || r.statusText);
|
|
236
|
+
}
|
|
237
|
+
const data = await r.json();
|
|
238
|
+
metadataBrowseCache = data;
|
|
239
|
+
metadataBrowseCurrent = data.current || "";
|
|
240
|
+
pathEl.textContent = data.current ? "/" + data.current : "/ (sourceRoot)";
|
|
241
|
+
upBtn.disabled = data.parent === null;
|
|
242
|
+
upBtn.classList.toggle("opacity-30", data.parent === null);
|
|
243
|
+
upBtn.classList.toggle("pointer-events-none", data.parent === null);
|
|
244
|
+
|
|
245
|
+
const dirRows = data.dirs.map(
|
|
246
|
+
(d) => `<button
|
|
247
|
+
onclick="metadataBrowseLoad('${d.path.replace(/'/g, "\\'")}')"
|
|
248
|
+
class="w-full text-left px-3 py-1.5 text-sm hover:bg-gray-50 dark:hover:bg-gray-800 flex items-center gap-2"
|
|
249
|
+
>
|
|
250
|
+
<i class="fa-solid fa-folder text-yellow-500"></i>
|
|
251
|
+
<span class="truncate">${esc(d.name)}</span>
|
|
252
|
+
</button>`,
|
|
253
|
+
);
|
|
254
|
+
const attached = new Set(
|
|
255
|
+
((metadataReport && metadataReport.items) || []).map((it) => it.path),
|
|
256
|
+
);
|
|
257
|
+
const fileRows = data.files
|
|
258
|
+
.filter((f) => !attached.has(f.path))
|
|
259
|
+
.map(
|
|
260
|
+
(f) => `<button
|
|
261
|
+
onclick="metadataAddPath('${f.path.replace(/'/g, "\\'")}')"
|
|
262
|
+
class="w-full text-left px-3 py-1.5 text-sm hover:bg-blue-50 dark:hover:bg-blue-900/30 flex items-center gap-2"
|
|
263
|
+
>
|
|
264
|
+
<i class="fa-solid fa-file text-gray-400"></i>
|
|
265
|
+
<span class="truncate">${esc(f.name)}</span>
|
|
266
|
+
<i class="fa-solid fa-plus ml-auto text-blue-500 text-xs"></i>
|
|
267
|
+
</button>`,
|
|
268
|
+
);
|
|
269
|
+
const rows = [...dirRows, ...fileRows];
|
|
270
|
+
listEl.innerHTML = rows.length
|
|
271
|
+
? rows.join("")
|
|
272
|
+
: `<div class="px-3 py-2 text-xs text-gray-400" data-i18n="common.empty_dir">${esc(window.t("common.empty_dir"))}</div>`;
|
|
273
|
+
} catch (err) {
|
|
274
|
+
listEl.innerHTML = `<div class="px-3 py-2 text-xs text-red-500">${esc(err.message)}</div>`;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function metadataBrowseUp() {
|
|
279
|
+
if (!metadataBrowseCache || metadataBrowseCache.parent === null) return;
|
|
280
|
+
metadataBrowseLoad(metadataBrowseCache.parent);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Expose
|
|
284
|
+
window.openMetadataModal = openMetadataModal;
|
|
285
|
+
window.closeMetadataModal = closeMetadataModal;
|
|
286
|
+
window.metadataRefresh = metadataRefresh;
|
|
287
|
+
window.metadataRemovePath = metadataRemovePath;
|
|
288
|
+
window.metadataAddPath = metadataAddPath;
|
|
289
|
+
window.metadataToggleBrowser = metadataToggleBrowser;
|
|
290
|
+
window.metadataBrowseLoad = metadataBrowseLoad;
|
|
291
|
+
window.metadataBrowseUp = metadataBrowseUp;
|
|
292
|
+
window.loadMetadataReport = loadMetadataReport;
|
|
@@ -250,10 +250,10 @@ function toggleHideCategories() {
|
|
|
250
250
|
function applyHideCategoriesButtonState() {
|
|
251
251
|
const btn = document.getElementById("toggle-categories-btn");
|
|
252
252
|
if (!btn) return;
|
|
253
|
-
btn.classList.toggle("text-blue-500", hideCategories);
|
|
254
|
-
btn.classList.toggle("dark:text-blue-400", hideCategories);
|
|
255
|
-
btn.classList.toggle("text-gray-400",
|
|
256
|
-
btn.classList.toggle("dark:text-gray-500",
|
|
253
|
+
btn.classList.toggle("text-blue-500", !hideCategories);
|
|
254
|
+
btn.classList.toggle("dark:text-blue-400", !hideCategories);
|
|
255
|
+
btn.classList.toggle("text-gray-400", hideCategories);
|
|
256
|
+
btn.classList.toggle("dark:text-gray-500", hideCategories);
|
|
257
257
|
}
|
|
258
258
|
|
|
259
259
|
function toggleHideAttachments() {
|
|
@@ -270,8 +270,8 @@ function toggleHideAttachments() {
|
|
|
270
270
|
function applyHideAttachmentsButtonState() {
|
|
271
271
|
const btn = document.getElementById("toggle-attachments-btn");
|
|
272
272
|
if (!btn) return;
|
|
273
|
-
btn.classList.toggle("text-blue-500", hideAttachments);
|
|
274
|
-
btn.classList.toggle("dark:text-blue-400", hideAttachments);
|
|
275
|
-
btn.classList.toggle("text-gray-400",
|
|
276
|
-
btn.classList.toggle("dark:text-gray-500",
|
|
273
|
+
btn.classList.toggle("text-blue-500", !hideAttachments);
|
|
274
|
+
btn.classList.toggle("dark:text-blue-400", !hideAttachments);
|
|
275
|
+
btn.classList.toggle("text-gray-400", hideAttachments);
|
|
276
|
+
btn.classList.toggle("dark:text-gray-500", hideAttachments);
|
|
277
277
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../../../src/lib/hash.ts"],"names":[],"mappings":"AAGA,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAOzD"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.sha256File = sha256File;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
9
|
+
function sha256File(absPath) {
|
|
10
|
+
try {
|
|
11
|
+
const buf = fs_1.default.readFileSync(absPath);
|
|
12
|
+
return crypto_1.default.createHash("sha256").update(buf).digest("hex");
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=hash.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hash.js","sourceRoot":"","sources":["../../../src/lib/hash.ts"],"names":[],"mappings":";;;;;AAGA,gCAOC;AAVD,4CAAoB;AACpB,oDAA4B;AAE5B,SAAgB,UAAU,CAAC,OAAe;IACxC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,YAAE,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;QACrC,OAAO,gBAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC/D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface MetadataEntry {
|
|
2
|
+
path: string;
|
|
3
|
+
hash: string;
|
|
4
|
+
}
|
|
5
|
+
export type MetadataStore = Record<string, MetadataEntry[]>;
|
|
6
|
+
export type MetadataStatus = "unchanged" | "modified" | "missing";
|
|
7
|
+
export interface MetadataItem {
|
|
8
|
+
path: string;
|
|
9
|
+
storedHash: string;
|
|
10
|
+
currentHash: string | null;
|
|
11
|
+
status: MetadataStatus;
|
|
12
|
+
}
|
|
13
|
+
export interface AccuracyReport {
|
|
14
|
+
items: MetadataItem[];
|
|
15
|
+
total: number;
|
|
16
|
+
unchanged: number;
|
|
17
|
+
modified: number;
|
|
18
|
+
missing: number;
|
|
19
|
+
accuracy: number;
|
|
20
|
+
}
|
|
21
|
+
export declare function metadataPath(docsPath: string): string;
|
|
22
|
+
export declare function readMetadataStore(docsPath: string): MetadataStore;
|
|
23
|
+
export declare function writeMetadataStore(docsPath: string, store: MetadataStore): void;
|
|
24
|
+
export declare function resolveSourceRoot(docsPath: string): string;
|
|
25
|
+
export declare function assertUnderSourceRoot(relOrAbs: string, sourceRoot: string): string;
|
|
26
|
+
export declare function classifyEntry(entry: MetadataEntry, sourceRoot: string): MetadataItem;
|
|
27
|
+
export declare function buildReport(entries: MetadataEntry[], sourceRoot: string): AccuracyReport;
|
|
28
|
+
export declare function getDocEntries(docsPath: string, docId: string): MetadataEntry[];
|
|
29
|
+
export declare function setDocEntries(docsPath: string, docId: string, entries: MetadataEntry[]): void;
|
|
30
|
+
//# sourceMappingURL=metadata.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metadata.d.ts","sourceRoot":"","sources":["../../../src/lib/metadata.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,aAAa,EAAE,CAAC,CAAC;AAE5D,MAAM,MAAM,cAAc,GAAG,WAAW,GAAG,UAAU,GAAG,SAAS,CAAC;AAElE,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,EAAE,cAAc,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAID,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAErD;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,aAAa,CAQjE;AAED,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,aAAa,GACnB,IAAI,CAMN;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAO1D;AAED,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,GACjB,MAAM,CAUR;AAED,wBAAgB,aAAa,CAC3B,KAAK,EAAE,aAAa,EACpB,UAAU,EAAE,MAAM,GACjB,YAAY,CAmBd;AAGD,wBAAgB,WAAW,CACzB,OAAO,EAAE,aAAa,EAAE,EACxB,UAAU,EAAE,MAAM,GACjB,cAAc,CAmBhB;AAED,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,GACZ,aAAa,EAAE,CAGjB;AAED,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,aAAa,EAAE,GACvB,IAAI,CAKN"}
|