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,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;
|
|
@@ -1,14 +1,36 @@
|
|
|
1
1
|
// ── Misc viewer helpers ─────────────────────────────────────────────────────
|
|
2
2
|
|
|
3
|
-
function
|
|
3
|
+
function applyFullWidthState(isWide) {
|
|
4
4
|
const article = document.getElementById("doc-view");
|
|
5
5
|
const btn = document.getElementById("full-width-btn");
|
|
6
|
-
|
|
6
|
+
if (!article || !btn) return;
|
|
7
|
+
article.classList.toggle("max-w-none", isWide);
|
|
7
8
|
article.classList.toggle("max-w-4xl", !isWide);
|
|
8
9
|
article.classList.toggle("mx-auto", !isWide);
|
|
9
10
|
btn.textContent = isWide ? window.t('doc.full_width_narrow_btn') : window.t('doc.full_width_btn');
|
|
10
11
|
}
|
|
11
12
|
|
|
13
|
+
function toggleFullWidth() {
|
|
14
|
+
const article = document.getElementById("doc-view");
|
|
15
|
+
const isWide = !article.classList.contains("max-w-none");
|
|
16
|
+
applyFullWidthState(isWide);
|
|
17
|
+
try {
|
|
18
|
+
localStorage.setItem("ld-full-width", isWide ? "1" : "0");
|
|
19
|
+
} catch {
|
|
20
|
+
/* ignore */
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function initFullWidthState() {
|
|
25
|
+
let isWide = false;
|
|
26
|
+
try {
|
|
27
|
+
isWide = localStorage.getItem("ld-full-width") === "1";
|
|
28
|
+
} catch {
|
|
29
|
+
/* ignore */
|
|
30
|
+
}
|
|
31
|
+
applyFullWidthState(isWide);
|
|
32
|
+
}
|
|
33
|
+
|
|
12
34
|
function copyLink() {
|
|
13
35
|
navigator.clipboard.writeText(location.href).then(() => {
|
|
14
36
|
const btn = document.getElementById("copy-link-btn");
|
|
@@ -158,6 +158,16 @@ function newDocCreateFolder() {
|
|
|
158
158
|
.value.trim();
|
|
159
159
|
if (!name) return;
|
|
160
160
|
const parent = _newDocBrowseCurrent || _newDocDocsFolder;
|
|
161
|
+
const atDocsRoot = parent === _newDocDocsFolder;
|
|
162
|
+
const errEl = document.getElementById("new-doc-error");
|
|
163
|
+
if (atDocsRoot && (name === "files" || name === "images")) {
|
|
164
|
+
if (errEl) {
|
|
165
|
+
errEl.textContent = window.t("modal.new_folder.error_reserved");
|
|
166
|
+
errEl.classList.remove("hidden");
|
|
167
|
+
}
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (errEl) errEl.classList.add("hidden");
|
|
161
171
|
const newRelPath =
|
|
162
172
|
(_newDocAbsToRel(parent) ? _newDocAbsToRel(parent) + "/" : "") + name;
|
|
163
173
|
_newDocSelectedFolder = newRelPath;
|
|
@@ -139,6 +139,12 @@ async function createNewFolder() {
|
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
const base = _newFolderSelectedPath || _newFolderDocsFolder;
|
|
142
|
+
const atDocsRoot = base === _newFolderDocsFolder;
|
|
143
|
+
if (atDocsRoot && (name === "files" || name === "images")) {
|
|
144
|
+
errEl.textContent = window.t('modal.new_folder.error_reserved');
|
|
145
|
+
errEl.classList.remove("hidden");
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
142
148
|
const fullPath = base.endsWith("/") ? base + name : base + "/" + name;
|
|
143
149
|
|
|
144
150
|
const btn = document.getElementById("new-folder-create-btn");
|
|
@@ -35,6 +35,7 @@ function countTreeAnnotatedDocs(node) {
|
|
|
35
35
|
|
|
36
36
|
function annotationBadge(count) {
|
|
37
37
|
if (!count) return "";
|
|
38
|
+
if (typeof stabiloHidden !== "undefined" && stabiloHidden) return "";
|
|
38
39
|
const label = window.t
|
|
39
40
|
? window.t("sidebar.annotation_badge")
|
|
40
41
|
: "annotation";
|
|
@@ -47,6 +48,7 @@ function annotationBadge(count) {
|
|
|
47
48
|
|
|
48
49
|
function annotatedDocsBadge(count) {
|
|
49
50
|
if (!count) return "";
|
|
51
|
+
if (typeof stabiloHidden !== "undefined" && stabiloHidden) return "";
|
|
50
52
|
const label = window.t
|
|
51
53
|
? window.t("sidebar.annotated_docs_badge")
|
|
52
54
|
: "document with annotations";
|
|
@@ -56,3 +58,39 @@ function annotatedDocsBadge(count) {
|
|
|
56
58
|
text-[10px] font-bold text-white
|
|
57
59
|
border border-orange-600 shadow-sm">${count}</span>`;
|
|
58
60
|
}
|
|
61
|
+
|
|
62
|
+
function countTreeFileAttachedDocs(node) {
|
|
63
|
+
let n = 0;
|
|
64
|
+
for (const arr of Object.values(node.categories)) {
|
|
65
|
+
for (const doc of arr) if (fileAttachmentCounts[doc.id] > 0) n += 1;
|
|
66
|
+
}
|
|
67
|
+
for (const child of Object.values(node.children))
|
|
68
|
+
n += countTreeFileAttachedDocs(child);
|
|
69
|
+
return n;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function fileAttachmentBadge(count) {
|
|
73
|
+
if (!count) return "";
|
|
74
|
+
if (typeof hideAttachments !== "undefined" && hideAttachments) return "";
|
|
75
|
+
const label = window.t
|
|
76
|
+
? window.t("sidebar.file_attachment_badge")
|
|
77
|
+
: "attachment";
|
|
78
|
+
return `<span title="${count} ${esc(label)}${count > 1 ? "s" : ""}"
|
|
79
|
+
class="inline-flex items-center justify-center min-w-[1.25rem] h-5 px-1.5
|
|
80
|
+
rounded-full bg-sky-200 dark:bg-sky-300
|
|
81
|
+
text-[10px] font-bold text-sky-900
|
|
82
|
+
border border-sky-500 shadow-sm">${count}</span>`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function fileAttachedDocsBadge(count) {
|
|
86
|
+
if (!count) return "";
|
|
87
|
+
if (typeof hideAttachments !== "undefined" && hideAttachments) return "";
|
|
88
|
+
const label = window.t
|
|
89
|
+
? window.t("sidebar.file_attached_docs_badge")
|
|
90
|
+
: "document with attachments";
|
|
91
|
+
return `<span title="${count} ${esc(label)}${count > 1 ? "s" : ""}"
|
|
92
|
+
class="inline-flex items-center justify-center min-w-[1.25rem] h-5 px-1.5
|
|
93
|
+
rounded-full bg-slate-400 dark:bg-slate-500
|
|
94
|
+
text-[10px] font-bold text-white
|
|
95
|
+
border border-slate-600 shadow-sm">${count}</span>`;
|
|
96
|
+
}
|
|
@@ -72,6 +72,7 @@ function renderTreeNode(node, folderPath) {
|
|
|
72
72
|
const isExpanded = expandedFolders.has(pathKey);
|
|
73
73
|
const docCount = countTreeDocs(node.children[key]);
|
|
74
74
|
const folderAnnotatedDocs = countTreeAnnotatedDocs(node.children[key]);
|
|
75
|
+
const folderFileDocs = countTreeFileAttachedDocs(node.children[key]);
|
|
75
76
|
html += `
|
|
76
77
|
<div class="mb-1">
|
|
77
78
|
<button onclick="toggleFolder('${esc(pathKey)}')"
|
|
@@ -81,6 +82,7 @@ function renderTreeNode(node, folderPath) {
|
|
|
81
82
|
<span class="flex items-center gap-2 min-w-0">
|
|
82
83
|
<span title="${esc(key)}" class="truncate">📁 ${esc(folderLabel(key))}</span>
|
|
83
84
|
${annotatedDocsBadge(folderAnnotatedDocs)}
|
|
85
|
+
${fileAttachedDocsBadge(folderFileDocs)}
|
|
84
86
|
</span>
|
|
85
87
|
<span class="flex items-center gap-1.5">
|
|
86
88
|
<span class="font-normal normal-case text-gray-400">${docCount}</span>
|
|
@@ -106,6 +108,10 @@ function renderTreeNode(node, folderPath) {
|
|
|
106
108
|
(s, d) => s + (annotationCounts[d.id] > 0 ? 1 : 0),
|
|
107
109
|
0,
|
|
108
110
|
);
|
|
111
|
+
const catFileDocs = node.categories[cat].reduce(
|
|
112
|
+
(s, d) => s + (fileAttachmentCounts[d.id] > 0 ? 1 : 0),
|
|
113
|
+
0,
|
|
114
|
+
);
|
|
109
115
|
return `
|
|
110
116
|
<div class="mb-0.5">
|
|
111
117
|
<button onclick="toggleCategory('${esc(catPathKey)}')"
|
|
@@ -115,6 +121,7 @@ function renderTreeNode(node, folderPath) {
|
|
|
115
121
|
<span class="flex items-center gap-2">
|
|
116
122
|
<span>${esc(cat)}</span>
|
|
117
123
|
${annotatedDocsBadge(catAnnotatedDocs)}
|
|
124
|
+
${fileAttachedDocsBadge(catFileDocs)}
|
|
118
125
|
</span>
|
|
119
126
|
<span class="flex items-center gap-1.5">
|
|
120
127
|
<span class="font-normal normal-case text-gray-400">${node.categories[cat].length}</span>
|
|
@@ -142,6 +149,7 @@ function renderTreeNode(node, folderPath) {
|
|
|
142
149
|
const isExpanded = expandedFolders.has(pathKey);
|
|
143
150
|
const docCount = countTreeDocs(node.children[key]);
|
|
144
151
|
const folderAnnotatedDocs = countTreeAnnotatedDocs(node.children[key]);
|
|
152
|
+
const folderFileDocs = countTreeFileAttachedDocs(node.children[key]);
|
|
145
153
|
html += `
|
|
146
154
|
<div class="mb-1">
|
|
147
155
|
<button onclick="toggleFolder('${esc(pathKey)}')"
|
|
@@ -151,6 +159,7 @@ function renderTreeNode(node, folderPath) {
|
|
|
151
159
|
<span class="flex items-center gap-2 min-w-0">
|
|
152
160
|
<span title="${esc(key)}" class="truncate">📁 ${esc(folderLabel(key))}</span>
|
|
153
161
|
${annotatedDocsBadge(folderAnnotatedDocs)}
|
|
162
|
+
${fileAttachedDocsBadge(folderFileDocs)}
|
|
154
163
|
</span>
|
|
155
164
|
<span class="flex items-center gap-1.5">
|
|
156
165
|
<span class="font-normal normal-case text-gray-400">${docCount}</span>
|
|
@@ -175,6 +184,7 @@ function renderTreeNode(node, folderPath) {
|
|
|
175
184
|
function renderDocItem(doc) {
|
|
176
185
|
const isActive = doc.id === currentDocId;
|
|
177
186
|
const annCount = annotationCounts[doc.id] || 0;
|
|
187
|
+
const fileCount = fileAttachmentCounts[doc.id] || 0;
|
|
178
188
|
return `
|
|
179
189
|
<button onclick="openDocument('${esc(doc.id)}')"
|
|
180
190
|
id="item-${esc(doc.id)}"
|
|
@@ -183,7 +193,10 @@ function renderDocItem(doc) {
|
|
|
183
193
|
${isActive ? "active" : ""}">
|
|
184
194
|
<div class="leading-snug flex items-center justify-between gap-2">
|
|
185
195
|
<span class="truncate">${esc(doc.title)}</span>
|
|
186
|
-
|
|
196
|
+
<span class="flex items-center gap-1 shrink-0">
|
|
197
|
+
${annotationBadge(annCount)}
|
|
198
|
+
${fileAttachmentBadge(fileCount)}
|
|
199
|
+
</span>
|
|
187
200
|
</div>
|
|
188
201
|
${doc.formattedDate ? `<div class="text-xs text-gray-400 dark:text-gray-500 mt-0.5">${esc(doc.formattedDate)}</div>` : ""}
|
|
189
202
|
</button>`;
|
|
@@ -237,8 +250,28 @@ function toggleHideCategories() {
|
|
|
237
250
|
function applyHideCategoriesButtonState() {
|
|
238
251
|
const btn = document.getElementById("toggle-categories-btn");
|
|
239
252
|
if (!btn) return;
|
|
240
|
-
btn.classList.toggle("text-blue-500", hideCategories);
|
|
241
|
-
btn.classList.toggle("dark:text-blue-400", hideCategories);
|
|
242
|
-
btn.classList.toggle("text-gray-400",
|
|
243
|
-
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
|
+
}
|
|
258
|
+
|
|
259
|
+
function toggleHideAttachments() {
|
|
260
|
+
hideAttachments = !hideAttachments;
|
|
261
|
+
try {
|
|
262
|
+
localStorage.setItem("ld-hide-attachments", hideAttachments ? "1" : "0");
|
|
263
|
+
} catch {
|
|
264
|
+
/* ignore */
|
|
265
|
+
}
|
|
266
|
+
applyHideAttachmentsButtonState();
|
|
267
|
+
refreshSidebar();
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function applyHideAttachmentsButtonState() {
|
|
271
|
+
const btn = document.getElementById("toggle-attachments-btn");
|
|
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", hideAttachments);
|
|
276
|
+
btn.classList.toggle("dark:text-gray-500", hideAttachments);
|
|
244
277
|
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
let allDocs = [];
|
|
5
5
|
let allFolderPaths = [];
|
|
6
6
|
let annotationCounts = {};
|
|
7
|
+
let fileAttachmentCounts = {};
|
|
7
8
|
let currentDocId = null;
|
|
8
9
|
let currentDocContent = "";
|
|
9
10
|
let searchQuery = "";
|
|
@@ -18,6 +19,13 @@ let hideCategories = (() => {
|
|
|
18
19
|
return false;
|
|
19
20
|
}
|
|
20
21
|
})();
|
|
22
|
+
let hideAttachments = (() => {
|
|
23
|
+
try {
|
|
24
|
+
return localStorage.getItem("ld-hide-attachments") === "1";
|
|
25
|
+
} catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
})();
|
|
21
29
|
|
|
22
30
|
function filteredDocs() {
|
|
23
31
|
if (!searchQuery) return allDocs;
|
|
@@ -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"}
|