living-documentation 7.5.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/annotations.js +66 -27
- package/dist/src/frontend/boot.js +3 -0
- package/dist/src/frontend/documents.js +28 -1
- package/dist/src/frontend/i18n/en.json +18 -0
- package/dist/src/frontend/i18n/fr.json +18 -0
- package/dist/src/frontend/index.html +157 -0
- package/dist/src/frontend/metadata.js +292 -0
- package/dist/src/frontend/misc.js +24 -2
- package/dist/src/frontend/new-doc-modal.js +10 -0
- package/dist/src/frontend/new-folder-modal.js +6 -0
- package/dist/src/frontend/sidebar-helpers.js +38 -0
- package/dist/src/frontend/sidebar.js +38 -5
- package/dist/src/frontend/state.js +8 -0
- 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/browse.d.ts +1 -1
- package/dist/src/routes/browse.d.ts.map +1 -1
- package/dist/src/routes/browse.js +19 -3
- package/dist/src/routes/browse.js.map +1 -1
- package/dist/src/routes/documents.d.ts.map +1 -1
- package/dist/src/routes/documents.js +32 -0
- package/dist/src/routes/documents.js.map +1 -1
- 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 +5 -1
- package/dist/src/server.js.map +1 -1
- package/dist/starting-doc/.annotations.json +1 -3
- package/dist/starting-doc/.metadata.json +1 -0
- package/package.json +1 -1
- package/dist/starting-doc/2026_04_21_19_47_[General]_tata.md +0 -6
- package/dist/starting-doc/2026_04_21_19_47_[General]_tutu.md +0 -11
- package/dist/starting-doc/2026_04_21_19_52_[General]_titi.md +0 -5
|
@@ -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;
|
|
@@ -10,21 +10,25 @@ let stabiloDeleteTargetId = null;
|
|
|
10
10
|
let stabiloReadPopupAnnotationId = null;
|
|
11
11
|
let stabiloReadHideTimer = null;
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
function toggleMarker() {
|
|
13
|
+
function applyMarkerVisualState() {
|
|
15
14
|
const btn = document.getElementById("stabilo-btn");
|
|
15
|
+
if (!btn) return;
|
|
16
16
|
const fills = btn.querySelectorAll("rect, polygon");
|
|
17
17
|
const crossEl = document.getElementById("stabilo-cross");
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
19
|
+
btn.classList.remove(
|
|
20
|
+
"border-gray-200",
|
|
21
|
+
"dark:border-gray-700",
|
|
22
|
+
"text-gray-600",
|
|
23
|
+
"dark:text-gray-400",
|
|
24
|
+
"border-yellow-400",
|
|
25
|
+
"bg-yellow-100",
|
|
26
|
+
"dark:bg-yellow-900/40",
|
|
27
|
+
"text-yellow-700",
|
|
28
|
+
"dark:text-yellow-300",
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
if (stabiloActive) {
|
|
28
32
|
btn.classList.add(
|
|
29
33
|
"border-yellow-400",
|
|
30
34
|
"bg-yellow-100",
|
|
@@ -39,17 +43,7 @@ function toggleMarker() {
|
|
|
39
43
|
),
|
|
40
44
|
);
|
|
41
45
|
crossEl.style.display = "none";
|
|
42
|
-
} else
|
|
43
|
-
// active → hidden
|
|
44
|
-
stabiloActive = false;
|
|
45
|
-
stabiloHidden = true;
|
|
46
|
-
btn.classList.remove(
|
|
47
|
-
"border-yellow-400",
|
|
48
|
-
"bg-yellow-100",
|
|
49
|
-
"dark:bg-yellow-900/40",
|
|
50
|
-
"text-yellow-700",
|
|
51
|
-
"dark:text-yellow-300",
|
|
52
|
-
);
|
|
46
|
+
} else {
|
|
53
47
|
btn.classList.add(
|
|
54
48
|
"border-gray-200",
|
|
55
49
|
"dark:border-gray-700",
|
|
@@ -66,14 +60,55 @@ function toggleMarker() {
|
|
|
66
60
|
: "#bfdbfe",
|
|
67
61
|
),
|
|
68
62
|
);
|
|
69
|
-
crossEl.style.display = "block";
|
|
70
|
-
|
|
71
|
-
|
|
63
|
+
crossEl.style.display = stabiloHidden ? "block" : "none";
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function persistMarkerState() {
|
|
68
|
+
const state = stabiloActive ? "active" : stabiloHidden ? "hidden" : "normal";
|
|
69
|
+
try {
|
|
70
|
+
localStorage.setItem("ld-marker-state", state);
|
|
71
|
+
} catch {
|
|
72
|
+
/* ignore */
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function initMarkerState() {
|
|
77
|
+
let saved = "normal";
|
|
78
|
+
try {
|
|
79
|
+
saved = localStorage.getItem("ld-marker-state") || "normal";
|
|
80
|
+
} catch {
|
|
81
|
+
/* ignore */
|
|
82
|
+
}
|
|
83
|
+
stabiloActive = saved === "active";
|
|
84
|
+
stabiloHidden = saved === "hidden";
|
|
85
|
+
applyMarkerVisualState();
|
|
86
|
+
if (stabiloHidden) setHighlightsVisible(false);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Cycles: normal → active → hidden → normal
|
|
90
|
+
function toggleMarker() {
|
|
91
|
+
const wasHidden = stabiloHidden;
|
|
92
|
+
|
|
93
|
+
if (!stabiloActive && !stabiloHidden) {
|
|
94
|
+
stabiloActive = true;
|
|
95
|
+
} else if (stabiloActive) {
|
|
96
|
+
stabiloActive = false;
|
|
97
|
+
stabiloHidden = true;
|
|
72
98
|
} else {
|
|
73
|
-
// hidden → normal
|
|
74
99
|
stabiloHidden = false;
|
|
75
|
-
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
applyMarkerVisualState();
|
|
103
|
+
persistMarkerState();
|
|
104
|
+
|
|
105
|
+
if (!wasHidden && stabiloHidden) {
|
|
106
|
+
closeMarkerPopup();
|
|
107
|
+
setHighlightsVisible(false);
|
|
108
|
+
refreshSidebar();
|
|
109
|
+
} else if (wasHidden && !stabiloHidden) {
|
|
76
110
|
setHighlightsVisible(true);
|
|
111
|
+
refreshSidebar();
|
|
77
112
|
}
|
|
78
113
|
}
|
|
79
114
|
|
|
@@ -129,6 +164,8 @@ function applyAnnotationHighlights() {
|
|
|
129
164
|
.forEach((mark) => {
|
|
130
165
|
if (!mark.textContent.trim()) mark.remove();
|
|
131
166
|
});
|
|
167
|
+
|
|
168
|
+
if (stabiloHidden) setHighlightsVisible(false);
|
|
132
169
|
}
|
|
133
170
|
|
|
134
171
|
function highlightAnnotation(contentEl, ann) {
|
|
@@ -543,4 +580,6 @@ function renderElevator() {
|
|
|
543
580
|
|
|
544
581
|
elevator.appendChild(pill);
|
|
545
582
|
}
|
|
583
|
+
|
|
584
|
+
if (stabiloHidden) elevator.style.visibility = "hidden";
|
|
546
585
|
}
|
|
@@ -13,8 +13,11 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
|
13
13
|
wcRestorePrefs();
|
|
14
14
|
if (typeof initFileAttach === "function") initFileAttach();
|
|
15
15
|
await loadConfig();
|
|
16
|
+
if (typeof initMarkerState === "function") initMarkerState();
|
|
17
|
+
if (typeof initFullWidthState === "function") initFullWidthState();
|
|
16
18
|
await loadDocuments();
|
|
17
19
|
applyHideCategoriesButtonState();
|
|
20
|
+
applyHideAttachmentsButtonState();
|
|
18
21
|
|
|
19
22
|
// Deep-link via ?doc=id, otherwise open first General doc
|
|
20
23
|
const params = new URLSearchParams(location.search);
|
|
@@ -20,7 +20,10 @@ async function loadDocuments() {
|
|
|
20
20
|
} catch {
|
|
21
21
|
allFolderPaths = [];
|
|
22
22
|
}
|
|
23
|
-
await
|
|
23
|
+
await Promise.all([
|
|
24
|
+
refreshAnnotationCounts(),
|
|
25
|
+
refreshFileAttachmentCounts(),
|
|
26
|
+
]);
|
|
24
27
|
renderSidebar(allDocs);
|
|
25
28
|
} catch {
|
|
26
29
|
document.getElementById("category-tree").innerHTML =
|
|
@@ -43,6 +46,18 @@ async function refreshAnnotationCounts() {
|
|
|
43
46
|
}
|
|
44
47
|
}
|
|
45
48
|
|
|
49
|
+
async function refreshFileAttachmentCounts() {
|
|
50
|
+
try {
|
|
51
|
+
const raw = await fetch("/api/documents/file-counts").then((r) => r.json());
|
|
52
|
+
fileAttachmentCounts = {};
|
|
53
|
+
for (const [docId, n] of Object.entries(raw || {})) {
|
|
54
|
+
fileAttachmentCounts[docId] = n;
|
|
55
|
+
}
|
|
56
|
+
} catch {
|
|
57
|
+
fileAttachmentCounts = {};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
46
61
|
async function openDocument(id, skipHistory = false, fromLink = false, anchor = null) {
|
|
47
62
|
// Track navigation history for breadcrumb trail
|
|
48
63
|
// fromLink===true : forward navigation via in-doc link → push current to stack
|
|
@@ -149,6 +164,11 @@ async function openDocument(id, skipHistory = false, fromLink = false, anchor =
|
|
|
149
164
|
// Load annotations for this document
|
|
150
165
|
loadAnnotations(id);
|
|
151
166
|
|
|
167
|
+
// Load source-file metadata report (drives the accuracy gauge)
|
|
168
|
+
if (typeof loadMetadataReport === "function") {
|
|
169
|
+
loadMetadataReport(id);
|
|
170
|
+
}
|
|
171
|
+
|
|
152
172
|
// Add IDs to headings for anchor navigation
|
|
153
173
|
contentEl.querySelectorAll("h1, h2, h3, h4, h5, h6").forEach((h) => {
|
|
154
174
|
if (!h.id) {
|
|
@@ -289,6 +309,7 @@ async function confirmDeleteDocument() {
|
|
|
289
309
|
try {
|
|
290
310
|
delete annotationCounts[decodeURIComponent(deletedId)];
|
|
291
311
|
} catch {}
|
|
312
|
+
delete fileAttachmentCounts[deletedId];
|
|
292
313
|
currentDocId = null;
|
|
293
314
|
|
|
294
315
|
// Return to welcome screen
|
|
@@ -369,6 +390,12 @@ async function saveDocument() {
|
|
|
369
390
|
applyAnnotationHighlights();
|
|
370
391
|
renderElevator();
|
|
371
392
|
|
|
393
|
+
const fileLinkMatches = content.match(/\]\(\s*\.?\/files\/[^)\s]+/g);
|
|
394
|
+
const fileLinkCount = fileLinkMatches ? fileLinkMatches.length : 0;
|
|
395
|
+
if (fileLinkCount > 0) fileAttachmentCounts[currentDocId] = fileLinkCount;
|
|
396
|
+
else delete fileAttachmentCounts[currentDocId];
|
|
397
|
+
refreshSidebar();
|
|
398
|
+
|
|
372
399
|
exitEditMode();
|
|
373
400
|
} catch (err) {
|
|
374
401
|
msgEl.textContent = window.t('error.save') + err.message;
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"nav.toggle_dark": "Toggle dark mode",
|
|
22
22
|
"nav.export": "Export",
|
|
23
23
|
"nav.toggle_categories": "Show/hide category grouping",
|
|
24
|
+
"nav.toggle_attachments": "Show/hide attachments",
|
|
24
25
|
"nav.export_html": "Export folders as HTML ZIP",
|
|
25
26
|
"nav.export_all_pdf": "Export all documents as PDF",
|
|
26
27
|
"nav.new_folder": "New folder",
|
|
@@ -37,6 +38,8 @@
|
|
|
37
38
|
"sidebar.no_docs": "No documents found.",
|
|
38
39
|
"sidebar.annotation_badge": "annotation",
|
|
39
40
|
"sidebar.annotated_docs_badge": "document with annotations",
|
|
41
|
+
"sidebar.file_attachment_badge": "attachment",
|
|
42
|
+
"sidebar.file_attached_docs_badge": "document with attachments",
|
|
40
43
|
|
|
41
44
|
"doc.marker_mode": "Marker mode — highlight to annotate",
|
|
42
45
|
"doc.marker_btn": "Marker",
|
|
@@ -66,6 +69,20 @@
|
|
|
66
69
|
"doc.failed_to_load": "Failed to load document: ",
|
|
67
70
|
"doc.copied": "✓ Copied!",
|
|
68
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
|
+
|
|
69
86
|
"modal.new_folder.title": "New folder",
|
|
70
87
|
"modal.new_folder.name_label": "Folder name",
|
|
71
88
|
"modal.new_folder.name_placeholder": "my-folder",
|
|
@@ -78,6 +95,7 @@
|
|
|
78
95
|
"modal.new_folder.creating_btn": "Creating…",
|
|
79
96
|
"modal.new_folder.error_empty": "Please enter a folder name.",
|
|
80
97
|
"modal.new_folder.error_invalid_chars": "Invalid characters in folder name.",
|
|
98
|
+
"modal.new_folder.error_reserved": "\"files\" and \"images\" are reserved folder names at the docs root.",
|
|
81
99
|
"modal.new_folder.no_subfolders": "No subfolders",
|
|
82
100
|
"modal.new_folder.error_loading": "Error loading",
|
|
83
101
|
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"nav.toggle_dark": "Basculer le mode sombre",
|
|
22
22
|
"nav.export": "Exporter",
|
|
23
23
|
"nav.toggle_categories": "Afficher/masquer le regroupement par catégorie",
|
|
24
|
+
"nav.toggle_attachments": "Afficher/masquer les pièces jointes",
|
|
24
25
|
"nav.export_html": "Exporter les dossiers en HTML (ZIP)",
|
|
25
26
|
"nav.export_all_pdf": "Exporter tous les documents en PDF",
|
|
26
27
|
"nav.new_folder": "Nouveau dossier",
|
|
@@ -37,6 +38,8 @@
|
|
|
37
38
|
"sidebar.no_docs": "Aucun document trouvé.",
|
|
38
39
|
"sidebar.annotation_badge": "annotation",
|
|
39
40
|
"sidebar.annotated_docs_badge": "document contenant des annotations",
|
|
41
|
+
"sidebar.file_attachment_badge": "pièce jointe",
|
|
42
|
+
"sidebar.file_attached_docs_badge": "document contenant des pièces jointes",
|
|
40
43
|
|
|
41
44
|
"doc.marker_mode": "Mode Marqueur — surligner pour annoter",
|
|
42
45
|
"doc.marker_btn": "Marqueur",
|
|
@@ -66,6 +69,20 @@
|
|
|
66
69
|
"doc.failed_to_load": "Impossible de charger le document : ",
|
|
67
70
|
"doc.copied": "✓ Copié !",
|
|
68
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
|
+
|
|
69
86
|
"modal.new_folder.title": "Nouveau dossier",
|
|
70
87
|
"modal.new_folder.name_label": "Nom du dossier",
|
|
71
88
|
"modal.new_folder.name_placeholder": "mon-dossier",
|
|
@@ -78,6 +95,7 @@
|
|
|
78
95
|
"modal.new_folder.creating_btn": "Création…",
|
|
79
96
|
"modal.new_folder.error_empty": "Veuillez saisir un nom de dossier.",
|
|
80
97
|
"modal.new_folder.error_invalid_chars": "Caractères non autorisés dans le nom du dossier.",
|
|
98
|
+
"modal.new_folder.error_reserved": "\"files\" et \"images\" sont des noms de dossiers réservés à la racine des documents.",
|
|
81
99
|
"modal.new_folder.no_subfolders": "Aucun sous-dossier",
|
|
82
100
|
"modal.new_folder.error_loading": "Erreur de chargement",
|
|
83
101
|
|
|
@@ -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>
|
|
@@ -347,6 +349,15 @@
|
|
|
347
349
|
class="text-xs text-gray-400 dark:text-gray-500"
|
|
348
350
|
></p>
|
|
349
351
|
<div class="flex items-center gap-2">
|
|
352
|
+
<button
|
|
353
|
+
id="toggle-attachments-btn"
|
|
354
|
+
onclick="toggleHideAttachments()"
|
|
355
|
+
data-i18n-title="nav.toggle_attachments"
|
|
356
|
+
title="Toggle attachments"
|
|
357
|
+
class="text-gray-400 hover:text-blue-500 dark:text-gray-500 dark:hover:text-blue-400 transition-colors leading-none"
|
|
358
|
+
>
|
|
359
|
+
<i class="fa-solid fa-paperclip"></i>
|
|
360
|
+
</button>
|
|
350
361
|
<button
|
|
351
362
|
id="toggle-categories-btn"
|
|
352
363
|
onclick="toggleHideCategories()"
|
|
@@ -564,6 +575,15 @@
|
|
|
564
575
|
>
|
|
565
576
|
<i class="fa-solid fa-link"></i> <span data-i18n="doc.copy_link_btn">Copy link</span>
|
|
566
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>
|
|
567
587
|
<button
|
|
568
588
|
onclick="enterEditMode()"
|
|
569
589
|
data-i18n-title="doc.edit"
|
|
@@ -611,6 +631,33 @@
|
|
|
611
631
|
</div>
|
|
612
632
|
</div>
|
|
613
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
|
+
|
|
614
661
|
<!-- Navigation history back-links -->
|
|
615
662
|
<div
|
|
616
663
|
id="doc-back"
|
|
@@ -651,6 +698,116 @@
|
|
|
651
698
|
</div>
|
|
652
699
|
<!-- end root -->
|
|
653
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
|
+
|
|
654
811
|
<!-- ── New Folder modal ── -->
|
|
655
812
|
<div
|
|
656
813
|
id="new-folder-modal"
|