living-documentation 7.0.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.
Files changed (173) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +329 -0
  3. package/dist/bin/cli.d.ts +3 -0
  4. package/dist/bin/cli.d.ts.map +1 -0
  5. package/dist/bin/cli.js +62 -0
  6. package/dist/bin/cli.js.map +1 -0
  7. package/dist/src/frontend/admin.html +1073 -0
  8. package/dist/src/frontend/annotations.js +546 -0
  9. package/dist/src/frontend/boot.js +90 -0
  10. package/dist/src/frontend/config.js +19 -0
  11. package/dist/src/frontend/dark-mode.js +20 -0
  12. package/dist/src/frontend/diagram/alignment.js +161 -0
  13. package/dist/src/frontend/diagram/clipboard.js +172 -0
  14. package/dist/src/frontend/diagram/constants.js +109 -0
  15. package/dist/src/frontend/diagram/debug.js +43 -0
  16. package/dist/src/frontend/diagram/edge-panel.js +260 -0
  17. package/dist/src/frontend/diagram/edge-rendering.js +12 -0
  18. package/dist/src/frontend/diagram/grid.js +78 -0
  19. package/dist/src/frontend/diagram/groups.js +102 -0
  20. package/dist/src/frontend/diagram/history.js +153 -0
  21. package/dist/src/frontend/diagram/image-name-modal.js +48 -0
  22. package/dist/src/frontend/diagram/image-upload.js +36 -0
  23. package/dist/src/frontend/diagram/label-editor.js +115 -0
  24. package/dist/src/frontend/diagram/link-panel.js +144 -0
  25. package/dist/src/frontend/diagram/main.js +299 -0
  26. package/dist/src/frontend/diagram/network.js +1473 -0
  27. package/dist/src/frontend/diagram/node-panel.js +267 -0
  28. package/dist/src/frontend/diagram/node-rendering.js +773 -0
  29. package/dist/src/frontend/diagram/persistence.js +161 -0
  30. package/dist/src/frontend/diagram/ports.js +386 -0
  31. package/dist/src/frontend/diagram/selection-overlay.js +336 -0
  32. package/dist/src/frontend/diagram/state.js +39 -0
  33. package/dist/src/frontend/diagram/t.js +3 -0
  34. package/dist/src/frontend/diagram/toast.js +21 -0
  35. package/dist/src/frontend/diagram/unlock-hold.js +182 -0
  36. package/dist/src/frontend/diagram/zoom.js +20 -0
  37. package/dist/src/frontend/diagram-link-modal.js +137 -0
  38. package/dist/src/frontend/diagram.html +1279 -0
  39. package/dist/src/frontend/documents.js +373 -0
  40. package/dist/src/frontend/export.js +338 -0
  41. package/dist/src/frontend/i18n/en.json +406 -0
  42. package/dist/src/frontend/i18n/fr.json +406 -0
  43. package/dist/src/frontend/i18n.js +32 -0
  44. package/dist/src/frontend/image-paste.js +101 -0
  45. package/dist/src/frontend/index.html +2314 -0
  46. package/dist/src/frontend/misc.js +25 -0
  47. package/dist/src/frontend/new-doc-modal.js +260 -0
  48. package/dist/src/frontend/new-folder-modal.js +174 -0
  49. package/dist/src/frontend/search.js +157 -0
  50. package/dist/src/frontend/sidebar-helpers.js +58 -0
  51. package/dist/src/frontend/sidebar.js +182 -0
  52. package/dist/src/frontend/snippet-detect.js +25 -0
  53. package/dist/src/frontend/snippet-table.js +85 -0
  54. package/dist/src/frontend/snippet-tree.js +94 -0
  55. package/dist/src/frontend/snippets.js +534 -0
  56. package/dist/src/frontend/state.js +28 -0
  57. package/dist/src/frontend/utils.js +21 -0
  58. package/dist/src/frontend/vendor/wordcloud2.js +1187 -0
  59. package/dist/src/frontend/wordcloud.js +693 -0
  60. package/dist/src/lib/config.d.ts +17 -0
  61. package/dist/src/lib/config.d.ts.map +1 -0
  62. package/dist/src/lib/config.js +79 -0
  63. package/dist/src/lib/config.js.map +1 -0
  64. package/dist/src/lib/parser.d.ts +11 -0
  65. package/dist/src/lib/parser.d.ts.map +1 -0
  66. package/dist/src/lib/parser.js +111 -0
  67. package/dist/src/lib/parser.js.map +1 -0
  68. package/dist/src/mcp/server.d.ts +3 -0
  69. package/dist/src/mcp/server.d.ts.map +1 -0
  70. package/dist/src/mcp/server.js +986 -0
  71. package/dist/src/mcp/server.js.map +1 -0
  72. package/dist/src/mcp/tools/diagrams.d.ts +44 -0
  73. package/dist/src/mcp/tools/diagrams.d.ts.map +1 -0
  74. package/dist/src/mcp/tools/diagrams.js +245 -0
  75. package/dist/src/mcp/tools/diagrams.js.map +1 -0
  76. package/dist/src/mcp/tools/documents.d.ts +26 -0
  77. package/dist/src/mcp/tools/documents.d.ts.map +1 -0
  78. package/dist/src/mcp/tools/documents.js +127 -0
  79. package/dist/src/mcp/tools/documents.js.map +1 -0
  80. package/dist/src/mcp/tools/source.d.ts +29 -0
  81. package/dist/src/mcp/tools/source.d.ts.map +1 -0
  82. package/dist/src/mcp/tools/source.js +200 -0
  83. package/dist/src/mcp/tools/source.js.map +1 -0
  84. package/dist/src/routes/annotations.d.ts +3 -0
  85. package/dist/src/routes/annotations.d.ts.map +1 -0
  86. package/dist/src/routes/annotations.js +83 -0
  87. package/dist/src/routes/annotations.js.map +1 -0
  88. package/dist/src/routes/browse.d.ts +3 -0
  89. package/dist/src/routes/browse.d.ts.map +1 -0
  90. package/dist/src/routes/browse.js +75 -0
  91. package/dist/src/routes/browse.js.map +1 -0
  92. package/dist/src/routes/config.d.ts +3 -0
  93. package/dist/src/routes/config.d.ts.map +1 -0
  94. package/dist/src/routes/config.js +97 -0
  95. package/dist/src/routes/config.js.map +1 -0
  96. package/dist/src/routes/diagrams.d.ts +3 -0
  97. package/dist/src/routes/diagrams.d.ts.map +1 -0
  98. package/dist/src/routes/diagrams.js +69 -0
  99. package/dist/src/routes/diagrams.js.map +1 -0
  100. package/dist/src/routes/documents.d.ts +8 -0
  101. package/dist/src/routes/documents.d.ts.map +1 -0
  102. package/dist/src/routes/documents.js +332 -0
  103. package/dist/src/routes/documents.js.map +1 -0
  104. package/dist/src/routes/export.d.ts +3 -0
  105. package/dist/src/routes/export.d.ts.map +1 -0
  106. package/dist/src/routes/export.js +277 -0
  107. package/dist/src/routes/export.js.map +1 -0
  108. package/dist/src/routes/images.d.ts +3 -0
  109. package/dist/src/routes/images.d.ts.map +1 -0
  110. package/dist/src/routes/images.js +49 -0
  111. package/dist/src/routes/images.js.map +1 -0
  112. package/dist/src/routes/wordcloud.d.ts +3 -0
  113. package/dist/src/routes/wordcloud.d.ts.map +1 -0
  114. package/dist/src/routes/wordcloud.js +95 -0
  115. package/dist/src/routes/wordcloud.js.map +1 -0
  116. package/dist/src/server.d.ts +7 -0
  117. package/dist/src/server.d.ts.map +1 -0
  118. package/dist/src/server.js +76 -0
  119. package/dist/src/server.js.map +1 -0
  120. package/dist/starting-doc/.annotations.json +3 -0
  121. package/dist/starting-doc/.diagrams.json +1884 -0
  122. package/dist/starting-doc/.living-doc.json +39 -0
  123. package/dist/starting-doc/1_tutorial/2026_04_11_13_25_[General]_crer_vos_dossiers.md +16 -0
  124. package/dist/starting-doc/1_tutorial/2026_04_11_18_58_[General]_creer_un_document_dans_un_dossier.md +9 -0
  125. package/dist/starting-doc/1_tutorial/2026_04_12_09_00_[General]_editer_et_sauvegarder.md +39 -0
  126. package/dist/starting-doc/1_tutorial/2026_04_12_10_00_[General]_utiliser_les_snippets.md +71 -0
  127. package/dist/starting-doc/2026_04_08_20_52_[General]_welcome.md +17 -0
  128. package/dist/starting-doc/2026_04_11_12_55_[General]_premiers_pas.md +271 -0
  129. package/dist/starting-doc/2_guide/2026_04_08_00_04_[DOCUMENT]_utilisation_des_images_plein_ecran_lien_clickable.md +40 -0
  130. package/dist/starting-doc/2_guide/2026_04_08_23_38_[Configuration]_demarrage_de_living_documentation.md +32 -0
  131. package/dist/starting-doc/2_guide/2026_04_09_09_00_[NAVIGATION]_recherche_plein_texte.md +65 -0
  132. package/dist/starting-doc/2_guide/2026_04_09_10_00_[EXPORT]_exporter_en_pdf.md +43 -0
  133. package/dist/starting-doc/2_guide/2026_04_09_11_00_[Configuration]_configurer_le_panneau_admin.md +55 -0
  134. package/dist/starting-doc/2_guide/2026_04_09_12_00_[Configuration]_extra_files.md +68 -0
  135. package/dist/starting-doc/2_guide/2026_04_09_13_00_[WORDCLOUD]_word_cloud.md +54 -0
  136. package/dist/starting-doc/2_guide/2026_04_09_14_00_[DIAGRAM]_creer_et_lier_un_diagramme.md +77 -0
  137. package/dist/starting-doc/3_concept/2026_04_08_20_58_[DOCUMENTING]_ADRS.md +20 -0
  138. package/dist/starting-doc/3_concept/2026_04_08_22_15_[DOCUMENTING]_living_documentation.md +17 -0
  139. package/dist/starting-doc/3_concept/2026_04_08_22_46_[METHODOLOGY]_diataxis_architecture_du_contenu.md +16 -0
  140. package/dist/starting-doc/4_reference/2026_04_08_23_14_[FUNDAMENTALS]_the_living_documentation_tool.md +41 -0
  141. package/dist/starting-doc/4_reference/2026_04_09_01_00_[REFERENCE]_raccourcis_clavier.md +61 -0
  142. package/dist/starting-doc/4_reference/2026_04_09_02_00_[REFERENCE]_tokens_pattern_nommage.md +75 -0
  143. package/dist/starting-doc/4_reference/2026_04_09_03_00_[REFERENCE]_types_de_snippets.md +68 -0
  144. package/dist/starting-doc/4_reference/2026_04_11_17_31_[FUNDAMENTALS]_architecturer_une_documentation.md +12 -0
  145. package/dist/starting-doc/4_reference/2026_04_12_14_07_[FUNDAMENTALS]_dossiers_et_catgories.md +89 -0
  146. package/dist/starting-doc/images/admin_screenshot.png +0 -0
  147. package/dist/starting-doc/images/ajout-document.png +0 -0
  148. package/dist/starting-doc/images/ajouter-document-categorie.png +0 -0
  149. package/dist/starting-doc/images/ajouter_un_document_dans_un_dossier.png +0 -0
  150. package/dist/starting-doc/images/architecturer_une_documentation_reference.png +0 -0
  151. package/dist/starting-doc/images/cr_er_un_document.png +0 -0
  152. package/dist/starting-doc/images/creation-nouveau-dossier.png +0 -0
  153. package/dist/starting-doc/images/creer-document-context-engineering.png +0 -0
  154. package/dist/starting-doc/images/creer-dossier-only-tutoriel.png +0 -0
  155. package/dist/starting-doc/images/creer-dossier-tutoriel.png +0 -0
  156. package/dist/starting-doc/images/creer-dossiers-done.png +0 -0
  157. package/dist/starting-doc/images/creer-un-document.png +0 -0
  158. package/dist/starting-doc/images/creer-vos-dossiers-tutoriel.png +0 -0
  159. package/dist/starting-doc/images/creer-vos-dossiers.png +0 -0
  160. package/dist/starting-doc/images/decouverte_adrs.png +0 -0
  161. package/dist/starting-doc/images/diataxis.png +0 -0
  162. package/dist/starting-doc/images/diataxis_callout.png +0 -0
  163. package/dist/starting-doc/images/document-cree.png +0 -0
  164. package/dist/starting-doc/images/liens_snippets.png +0 -0
  165. package/dist/starting-doc/images/living_documentation.png +0 -0
  166. package/dist/starting-doc/images/npm_logo.png +0 -0
  167. package/dist/starting-doc/images/popup-creer-document.png +0 -0
  168. package/dist/starting-doc/images/popup-creer-dossier.png +0 -0
  169. package/dist/starting-doc/images/popup-dossier-cree.png +0 -0
  170. package/dist/starting-doc/images/quatre-dossiers-crees.png +0 -0
  171. package/dist/starting-doc/images/screenshot-living-doc.png +0 -0
  172. package/dist/starting-doc/images/the_living_documentation_tool.png +0 -0
  173. package/package.json +49 -0
@@ -0,0 +1,373 @@
1
+ // ── Documents: load, open, edit, save, delete, navigate ─────────────────────
2
+ // Depends on globals from state.js, utils.js, search.js, sidebar.js,
3
+ // annotations (loadAnnotations, applyAnnotationHighlights, renderElevator),
4
+ // and image-paste.js (handleEditorPaste).
5
+
6
+ async function loadDocuments() {
7
+ try {
8
+ [allDocs] = await Promise.all([
9
+ fetch("/api/documents").then((r) => r.json()),
10
+ ]);
11
+ // Also fetch all directories so empty folders appear in the sidebar
12
+ try {
13
+ const cfg = await fetch("/api/config").then((r) => r.json());
14
+ if (cfg.docsFolder) {
15
+ allFolderPaths = await fetch(
16
+ "/api/browse/alldirs?path=" +
17
+ encodeURIComponent(cfg.docsFolder),
18
+ ).then((r) => r.json());
19
+ }
20
+ } catch {
21
+ allFolderPaths = [];
22
+ }
23
+ await refreshAnnotationCounts();
24
+ renderSidebar(allDocs);
25
+ } catch {
26
+ document.getElementById("category-tree").innerHTML =
27
+ `<p class="px-4 py-4 text-sm text-red-500">${window.t('sidebar.failed_to_load')}</p>`;
28
+ }
29
+ }
30
+
31
+ async function refreshAnnotationCounts() {
32
+ try {
33
+ const raw = await fetch("/api/annotations").then((r) => r.json());
34
+ annotationCounts = {};
35
+ for (const [docId, n] of Object.entries(raw || {})) {
36
+ annotationCounts[docId] = n;
37
+ try {
38
+ annotationCounts[encodeURIComponent(docId)] = n;
39
+ } catch {}
40
+ }
41
+ } catch {
42
+ annotationCounts = {};
43
+ }
44
+ }
45
+
46
+ async function openDocument(id, skipHistory = false, fromLink = false) {
47
+ // Track navigation history for breadcrumb trail
48
+ // fromLink===true : forward navigation via in-doc link → push current to stack
49
+ // fromLink==="restore" : back navigation via history breadcrumb → stack already trimmed, don't touch
50
+ // fromLink===false : sidebar/direct navigation → reset stack
51
+ if (fromLink === true && currentDocId && currentDocId !== id) {
52
+ const prev = allDocs && allDocs.find((d) => d.id === currentDocId);
53
+ navHistory.push({
54
+ id: currentDocId,
55
+ title: prev ? prev.title : currentDocId,
56
+ });
57
+ } else if (!fromLink) {
58
+ navHistory = [];
59
+ }
60
+
61
+ // Update back-link banner
62
+ const backEl = document.getElementById("doc-back");
63
+ if (navHistory.length > 0) {
64
+ backEl.innerHTML = navHistory
65
+ .map(
66
+ (entry, i) =>
67
+ `<button onclick="goBackToIndex(${i})"
68
+ class="no-print text-blue-600 dark:text-blue-400 hover:underline">&#8592; ${esc(entry.title)}</button>`,
69
+ )
70
+ .join(
71
+ '<span class="text-gray-300 dark:text-gray-600 mx-1">·</span>',
72
+ );
73
+ backEl.classList.remove("hidden");
74
+ } else {
75
+ backEl.classList.add("hidden");
76
+ backEl.innerHTML = "";
77
+ }
78
+
79
+ currentDocId = id;
80
+
81
+ // Expand sidebar path to reveal the document
82
+ const doc = allDocs && allDocs.find((d) => d.id === id);
83
+ if (doc) {
84
+ const folder = doc.folder || [];
85
+ // Expand every ancestor folder
86
+ for (let i = 0; i < folder.length; i++) {
87
+ expandedFolders.add(folder.slice(0, i + 1).join("|"));
88
+ }
89
+ // Expand the category at this folder level
90
+ expandedCategories.add([...folder, doc.category].join("|"));
91
+ refreshSidebar();
92
+ }
93
+
94
+ // Update active state in sidebar
95
+ document
96
+ .querySelectorAll(".doc-item")
97
+ .forEach((el) => el.classList.remove("active"));
98
+ const activeItem = document.getElementById("item-" + id);
99
+ if (activeItem) {
100
+ activeItem.classList.add("active");
101
+ activeItem.scrollIntoView({ block: "nearest", behavior: "smooth" });
102
+ }
103
+
104
+ // Update URL
105
+ if (!skipHistory) {
106
+ const url = new URL(location.href);
107
+ url.searchParams.set("doc", id);
108
+ history.pushState({ docId: id }, "", url);
109
+ }
110
+
111
+ document.getElementById("welcome").classList.add("hidden");
112
+ const docView = document.getElementById("doc-view");
113
+ docView.classList.remove("hidden");
114
+ document.getElementById("doc-content").innerHTML =
115
+ `<p class="animate-pulse text-gray-400">${window.t('common.loading')}</p>`;
116
+
117
+ try {
118
+ const doc = await fetch("/api/documents/" + id).then((r) => {
119
+ if (!r.ok) throw new Error(r.statusText);
120
+ return r.json();
121
+ });
122
+
123
+ currentDocContent = doc.content;
124
+ exitEditMode();
125
+
126
+ document.getElementById("doc-title").textContent = doc.title;
127
+ {
128
+ const crumbs = document.getElementById("doc-breadcrumbs");
129
+ const folderPills = (doc.folder || []).map(
130
+ (seg) =>
131
+ `<span title="${esc(seg)}" class="inline-block text-xs font-semibold px-2.5 py-0.5 rounded-full bg-violet-100 text-violet-700 dark:bg-violet-900/40 dark:text-violet-300">${esc(folderLabel(seg))}</span>`,
132
+ );
133
+ const catPill = `<span class="inline-block text-xs font-semibold px-2.5 py-0.5 rounded-full bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-300">${esc(doc.category)}</span>`;
134
+ crumbs.innerHTML = [...folderPills, catPill].join("");
135
+ }
136
+ document.getElementById("doc-date").textContent =
137
+ doc.formattedDate || "";
138
+
139
+ const contentEl = document.getElementById("doc-content");
140
+ contentEl.innerHTML = doc.html;
141
+
142
+ // Load annotations for this document
143
+ loadAnnotations(id);
144
+
145
+ // Add IDs to headings for anchor navigation
146
+ contentEl.querySelectorAll("h1, h2, h3, h4, h5, h6").forEach((h) => {
147
+ if (!h.id) {
148
+ h.id = h.textContent
149
+ .toLowerCase()
150
+ .replace(/[^\w\s-]/g, "")
151
+ .trim()
152
+ .replace(/\s+/g, "-");
153
+ }
154
+ });
155
+
156
+ // Syntax highlighting
157
+ contentEl.querySelectorAll("pre code").forEach((block) => {
158
+ hljs.highlightElement(block);
159
+ });
160
+
161
+ // Intercept inter-doc links (?doc=X) to stay in SPA and track origin
162
+ contentEl.querySelectorAll("a[href]").forEach((a) => {
163
+ const href = a.getAttribute("href");
164
+ const m = href && href.match(/[?&]doc=([^&#]+)/);
165
+ if (!m) return;
166
+ a.addEventListener("click", (e) => {
167
+ e.preventDefault();
168
+ openDocument(decodeURIComponent(m[1]), false, true);
169
+ });
170
+ });
171
+
172
+ // Intercept pure-anchor links (#foo) to offset for the sticky header
173
+ contentEl.querySelectorAll('a[href^="#"]').forEach((a) => {
174
+ const href = a.getAttribute("href");
175
+ if (!href || href.length < 2) return;
176
+ a.addEventListener("click", (e) => {
177
+ e.preventDefault();
178
+ scrollToAnchor(href.slice(1));
179
+ });
180
+ });
181
+
182
+ // Make tables responsive
183
+ contentEl.querySelectorAll("table").forEach((t) => {
184
+ const wrapper = document.createElement("div");
185
+ wrapper.className = "overflow-x-auto";
186
+ t.parentNode.insertBefore(wrapper, t);
187
+ wrapper.appendChild(t);
188
+ });
189
+
190
+ // Highlight search matches in content
191
+ const notice = document.getElementById("search-notice");
192
+ if (searchQuery) {
193
+ const matches = highlightMatches(contentEl, searchQuery);
194
+ buildSearchNotice(matches, searchQuery);
195
+ notice.classList.remove("hidden");
196
+ } else {
197
+ notice.classList.add("hidden");
198
+ }
199
+
200
+ document.title = doc.title;
201
+
202
+ // Scroll to anchor if present in URL
203
+ const hash = window.location.hash;
204
+ if (hash && hash.length > 1) {
205
+ scrollToAnchor(hash.slice(1));
206
+ } else {
207
+ document.getElementById("content-area").scrollTop = 0;
208
+ }
209
+ } catch (err) {
210
+ document.getElementById("doc-content").innerHTML =
211
+ `<p class="text-red-500">${window.t('doc.failed_to_load')}${err.message}</p>`;
212
+ }
213
+ }
214
+
215
+ // ── Edit mode ────────────────────────────────────────────────────────────────
216
+ let _editScrollTop = 0;
217
+
218
+ function enterEditMode() {
219
+ _editScrollTop = document.getElementById("content-area").scrollTop;
220
+ const editor = document.getElementById("doc-editor");
221
+ editor.value = currentDocContent;
222
+ document.getElementById("doc-content").classList.add("hidden");
223
+ editor.classList.remove("hidden");
224
+ document.getElementById("view-actions").classList.add("hidden");
225
+ document.getElementById("edit-actions").classList.remove("hidden");
226
+ editor.focus();
227
+ editor.addEventListener("paste", handleEditorPaste);
228
+ }
229
+
230
+ function exitEditMode() {
231
+ const editor = document.getElementById("doc-editor");
232
+ editor.removeEventListener("paste", handleEditorPaste);
233
+ editor.classList.add("hidden");
234
+ document.getElementById("doc-content").classList.remove("hidden");
235
+ document.getElementById("edit-actions").classList.add("hidden");
236
+ document.getElementById("view-actions").classList.remove("hidden");
237
+ document.getElementById("edit-save-msg").textContent = "";
238
+ document.getElementById("content-area").scrollTop = _editScrollTop;
239
+ }
240
+
241
+ // ── Delete ───────────────────────────────────────────────────────────────────
242
+ function askDeleteDocument() {
243
+ if (!currentDocId) return;
244
+ const doc = allDocs.find((d) => d.id === currentDocId);
245
+ const titleEl = document.getElementById("doc-confirm-delete-title");
246
+ if (titleEl) titleEl.textContent = doc ? doc.title : "";
247
+ document.getElementById("doc-confirm-delete").classList.remove("hidden");
248
+ }
249
+
250
+ function cancelDeleteDocument(e) {
251
+ if (e && e.target && e.target.id && e.target.id !== "doc-confirm-delete") {
252
+ // clicked inside the card, not the backdrop
253
+ return;
254
+ }
255
+ document.getElementById("doc-confirm-delete").classList.add("hidden");
256
+ }
257
+
258
+ async function confirmDeleteDocument() {
259
+ if (!currentDocId) return;
260
+ const deletedId = currentDocId;
261
+ try {
262
+ const r = await fetch(
263
+ "/api/documents/" + encodeURIComponent(deletedId),
264
+ { method: "DELETE" },
265
+ );
266
+ if (!r.ok) throw new Error("delete failed");
267
+ } catch {
268
+ document.getElementById("doc-confirm-delete").classList.add("hidden");
269
+ return;
270
+ }
271
+ document.getElementById("doc-confirm-delete").classList.add("hidden");
272
+
273
+ // Drop from local state
274
+ allDocs = allDocs.filter((d) => d.id !== deletedId);
275
+ if (Array.isArray(searchResults)) {
276
+ searchResults = searchResults.filter((d) => d.id !== deletedId);
277
+ }
278
+ delete annotationCounts[deletedId];
279
+ try {
280
+ delete annotationCounts[decodeURIComponent(deletedId)];
281
+ } catch {}
282
+ currentDocId = null;
283
+
284
+ // Return to welcome screen
285
+ document.getElementById("doc-view").classList.add("hidden");
286
+ document.getElementById("welcome").classList.remove("hidden");
287
+ history.pushState({}, "", window.location.pathname);
288
+
289
+ refreshSidebar();
290
+ }
291
+
292
+ // ── Save (in-place edit) ─────────────────────────────────────────────────────
293
+ async function saveDocument() {
294
+ if (!currentDocId) return;
295
+ const content = document.getElementById("doc-editor").value;
296
+ const msgEl = document.getElementById("edit-save-msg");
297
+ msgEl.textContent = window.t('doc.saving');
298
+ msgEl.className = "text-xs text-gray-400";
299
+
300
+ try {
301
+ const res = await fetch("/api/documents/" + currentDocId, {
302
+ method: "PUT",
303
+ headers: { "Content-Type": "application/json" },
304
+ body: JSON.stringify({ content }),
305
+ });
306
+ if (!res.ok) throw new Error(await res.text());
307
+
308
+ currentDocContent = content;
309
+
310
+ // Re-fetch rendered HTML and update view
311
+ const doc = await fetch("/api/documents/" + currentDocId).then((r) =>
312
+ r.json(),
313
+ );
314
+ const contentEl = document.getElementById("doc-content");
315
+ contentEl.innerHTML = doc.html;
316
+ contentEl
317
+ .querySelectorAll("pre code")
318
+ .forEach((block) => hljs.highlightElement(block));
319
+
320
+ contentEl.querySelectorAll("h1, h2, h3, h4, h5, h6").forEach((h) => {
321
+ if (!h.id) {
322
+ h.id = h.textContent
323
+ .toLowerCase()
324
+ .replace(/[^\w\s-]/g, "")
325
+ .trim()
326
+ .replace(/\s+/g, "-");
327
+ }
328
+ });
329
+
330
+ contentEl.querySelectorAll("a[href]").forEach((a) => {
331
+ const href = a.getAttribute("href");
332
+ const m = href && href.match(/[?&]doc=([^&#]+)/);
333
+ if (!m) return;
334
+ a.addEventListener("click", (e) => {
335
+ e.preventDefault();
336
+ openDocument(decodeURIComponent(m[1]), false, true);
337
+ });
338
+ });
339
+
340
+ contentEl.querySelectorAll('a[href^="#"]').forEach((a) => {
341
+ const href = a.getAttribute("href");
342
+ if (!href || href.length < 2) return;
343
+ a.addEventListener("click", (e) => {
344
+ e.preventDefault();
345
+ scrollToAnchor(href.slice(1));
346
+ });
347
+ });
348
+
349
+ contentEl.querySelectorAll("table").forEach((t) => {
350
+ if (t.parentNode.classList.contains("overflow-x-auto")) return;
351
+ const wrapper = document.createElement("div");
352
+ wrapper.className = "overflow-x-auto";
353
+ t.parentNode.insertBefore(wrapper, t);
354
+ wrapper.appendChild(t);
355
+ });
356
+
357
+ applyAnnotationHighlights();
358
+ renderElevator();
359
+
360
+ exitEditMode();
361
+ } catch (err) {
362
+ msgEl.textContent = window.t('error.save') + err.message;
363
+ msgEl.className = "text-xs text-red-500 dark:text-red-400";
364
+ }
365
+ }
366
+
367
+ // ── Back breadcrumb navigation ──────────────────────────────────────────────
368
+ function goBackToIndex(i) {
369
+ const entry = navHistory[i];
370
+ if (!entry) return;
371
+ navHistory = navHistory.slice(0, i); // drop this entry and everything after
372
+ openDocument(entry.id, false, "restore");
373
+ }