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,534 @@
1
+ // ── Snippets modal: type switching, preview, insert, parse ──────────────────
2
+ // Depends on globals from state.js (allDocs, currentDocId, currentDocContent),
3
+ // utils.js (esc), snippet-detect.js (detectSnippetType), snippet-table.js
4
+ // (_tableData, tableInit, tableRenderGrid, buildTableMarkdown) and
5
+ // snippet-tree.js (_treeItems, treeInit, treeRenderList, buildTreeMarkdown).
6
+
7
+ let _snippetSelStart = 0;
8
+ let _snippetSelEnd = 0;
9
+ const _SNIPPET_PANELS = [
10
+ "collapsible",
11
+ "link",
12
+ "doc-link",
13
+ "anchor-link",
14
+ "anchor-doc-link",
15
+ "code-block",
16
+ "image",
17
+ "table",
18
+ "tree",
19
+ "diagram",
20
+ "colored-section",
21
+ "colored-text",
22
+ ];
23
+
24
+ const _COLOR_SWATCHES = {
25
+ info: { bg: "#eff6ff", border: "#3b82f6", text: "#1e3a5f" },
26
+ success: { bg: "#f0fdf4", border: "#22c55e", text: "#14532d" },
27
+ warning: { bg: "#fffbeb", border: "#f59e0b", text: "#451a03" },
28
+ danger: { bg: "#fef2f2", border: "#ef4444", text: "#450a0a" },
29
+ note: { bg: "#f5f3ff", border: "#8b5cf6", text: "#2e1065" },
30
+ neutral: { bg: "#f9fafb", border: "#6b7280", text: "#111827" },
31
+ };
32
+ let _colorSectionSwatch = "info";
33
+ let _colorTextSwatch = "info";
34
+
35
+ function colorSectionPickSwatch(btn) {
36
+ document.querySelectorAll(".color-swatch-btn").forEach((b) => {
37
+ b.classList.remove("selected-swatch", "ring-offset-2");
38
+ });
39
+ btn.classList.add("selected-swatch", "ring-offset-2");
40
+ const color = btn.getAttribute("data-color-swatch");
41
+ // Apply the matching ring color
42
+ const ringMap = { info: "ring-blue-400", success: "ring-green-400", warning: "ring-amber-400", danger: "ring-red-400", note: "ring-purple-400", neutral: "ring-gray-400" };
43
+ btn.classList.add(ringMap[color] || "ring-blue-400");
44
+ _colorSectionSwatch = color;
45
+ snippetUpdatePreview();
46
+ }
47
+
48
+ function colorTextPickSwatch(btn) {
49
+ document.querySelectorAll(".color-text-swatch-btn").forEach((b) => {
50
+ b.classList.remove("selected-text-swatch", "ring-offset-2");
51
+ });
52
+ btn.classList.add("selected-text-swatch", "ring-offset-2");
53
+ const color = btn.getAttribute("data-color-text-swatch");
54
+ const ringMap = { info: "ring-blue-400", success: "ring-green-400", warning: "ring-amber-400", danger: "ring-red-400", note: "ring-purple-400", neutral: "ring-gray-400" };
55
+ btn.classList.add(ringMap[color] || "ring-blue-400");
56
+ _colorTextSwatch = color;
57
+ snippetUpdatePreview();
58
+ }
59
+
60
+ function openSnippetsModal() {
61
+ const editor = document.getElementById("doc-editor");
62
+ _snippetSelStart = editor.selectionStart;
63
+ _snippetSelEnd = editor.selectionEnd;
64
+
65
+ const docOpts = allDocs
66
+ .map((d) => `<option value="${d.id}">${d.title}</option>`)
67
+ .join("");
68
+ document.getElementById("snip-doc-select").innerHTML = docOpts;
69
+ document.getElementById("snip-anchor-doc-select").innerHTML = docOpts;
70
+
71
+ const msgEl = document.getElementById("snippet-detect-msg");
72
+ const selectedText = editor.value.slice(
73
+ _snippetSelStart,
74
+ _snippetSelEnd,
75
+ );
76
+
77
+ if (selectedText) {
78
+ const detected = detectSnippetType(selectedText);
79
+ if (detected) {
80
+ document.getElementById("snippet-type").value = detected;
81
+ snippetTypeChanged();
82
+ parseAndFillSnippet(selectedText, detected);
83
+ const labels = {
84
+ collapsible: window.t('snippet.collapsible'),
85
+ link: window.t('snippet.link'),
86
+ "doc-link": window.t('snippet.link_doc'),
87
+ "anchor-link": window.t('snippet.link_anchor'),
88
+ "anchor-doc-link": window.t('snippet.link_doc_anchor'),
89
+ "ordered-list": window.t('snippet.numbered_list'),
90
+ "unordered-list": window.t('snippet.bullet_list'),
91
+ "code-block": window.t('snippet.code_block'),
92
+ blockquote: window.t('snippet.blockquote'),
93
+ separator: window.t('snippet.separator'),
94
+ image: window.t('snippet.image'),
95
+ table: window.t('snippet.table'),
96
+ tree: window.t('snippet.tree'),
97
+ "colored-section": window.t('snippet.colored_section'),
98
+ "colored-text": window.t('snippet.colored_text'),
99
+ };
100
+ msgEl.textContent = window.t('snippet.detected_msg').replace('{type}', labels[detected] ?? detected);
101
+ msgEl.className =
102
+ "rounded-lg px-3 py-2 text-xs bg-green-50 dark:bg-green-900/20 text-green-700 dark:text-green-300 border border-green-200 dark:border-green-800";
103
+ } else {
104
+ document.getElementById("snippet-type").value = "diagram";
105
+ snippetTypeChanged();
106
+ msgEl.textContent = window.t('snippet.unknown_type_msg');
107
+ msgEl.className =
108
+ "rounded-lg px-3 py-2 text-xs bg-amber-50 dark:bg-amber-900/20 text-amber-700 dark:text-amber-300 border border-amber-200 dark:border-amber-800";
109
+ }
110
+ msgEl.classList.remove("hidden");
111
+ } else {
112
+ msgEl.classList.add("hidden");
113
+ document.getElementById("snippet-type").value = "diagram";
114
+ snippetTypeChanged();
115
+ }
116
+
117
+ document.getElementById("snippets-modal").classList.remove("hidden");
118
+ }
119
+
120
+ function closeSnippetsModal() {
121
+ document.getElementById("snippets-modal").classList.add("hidden");
122
+ }
123
+
124
+ function snippetTypeChanged() {
125
+ const type = document.getElementById("snippet-type").value;
126
+ _SNIPPET_PANELS.forEach((p) => {
127
+ const panel = document.getElementById("snip-panel-" + p);
128
+ if (panel) panel.classList.toggle("hidden", p !== type);
129
+ });
130
+ if (type === "table") tableInit();
131
+ else if (type === "tree") treeInit();
132
+ else if (type === "diagram") snippetDiagInit();
133
+ else snippetUpdatePreview();
134
+ }
135
+
136
+ async function snippetDiagInit() {
137
+ let diagrams = [];
138
+ try {
139
+ diagrams = await fetch("/api/diagrams").then((r) => r.json());
140
+ } catch {
141
+ diagrams = [];
142
+ }
143
+ const sel = document.getElementById("snip-diag-select");
144
+ sel.innerHTML = diagrams.length
145
+ ? diagrams
146
+ .map(
147
+ (d) => `<option value="${esc(d.id)}">${esc(d.title)}</option>`,
148
+ )
149
+ .join("")
150
+ : `<option value="" disabled>${window.t('snippet.diagram_no_diagrams')}</option>`;
151
+
152
+ // Pre-fill image name from current doc title
153
+ const docTitle = document
154
+ .getElementById("doc-title")
155
+ .textContent.trim();
156
+ const slug = docTitle
157
+ .toLowerCase()
158
+ .replace(/[^a-z0-9]+/g, "_")
159
+ .replace(/^_|_$/g, "");
160
+ document.getElementById("snip-diag-img-name").value = slug
161
+ ? slug + ".png"
162
+ : "diagram.png";
163
+ document.getElementById("snip-diag-new-name").value = docTitle
164
+ ? docTitle + " Diagram"
165
+ : "";
166
+
167
+ document.getElementById("snip-diag-mode-existing").checked = true;
168
+ snippetDiagModeChanged();
169
+ }
170
+
171
+ function snippetDiagModeChanged() {
172
+ const isNew = document.getElementById("snip-diag-mode-new").checked;
173
+ document
174
+ .getElementById("snip-diag-existing-section")
175
+ .classList.toggle("hidden", isNew);
176
+ document
177
+ .getElementById("snip-diag-new-section")
178
+ .classList.toggle("hidden", !isNew);
179
+ snippetDiagSyncImgName();
180
+ snippetUpdatePreview();
181
+ }
182
+
183
+ function snippetDiagSyncImgName() {
184
+ const isNew = document.getElementById("snip-diag-mode-new").checked;
185
+ let label;
186
+ if (isNew) {
187
+ label = document.getElementById("snip-diag-new-name").value.trim();
188
+ } else {
189
+ const sel = document.getElementById("snip-diag-select");
190
+ label = sel.options[sel.selectedIndex]?.text ?? "";
191
+ }
192
+ const slug = label
193
+ .toLowerCase()
194
+ .replace(/[^a-z0-9]+/g, "_")
195
+ .replace(/^_|_$/g, "");
196
+ document.getElementById("snip-diag-img-name").value = slug
197
+ ? slug + ".png"
198
+ : "diagram.png";
199
+ }
200
+
201
+ function buildSnippetMarkdown() {
202
+ const type = document.getElementById("snippet-type").value;
203
+ switch (type) {
204
+ case "collapsible": {
205
+ const summary =
206
+ document.getElementById("snip-collapsible-summary").value ||
207
+ window.t('snippet.collapsible_summary_value');
208
+ return `<details>\n<summary>${summary}</summary>\n\n## Titre\n\nTexte\n\n</details>`;
209
+ }
210
+ case "link": {
211
+ const text =
212
+ document.getElementById("snip-link-text").value ||
213
+ window.t('snippet.link_text_placeholder');
214
+ const url =
215
+ document.getElementById("snip-link-url").value || "https://...";
216
+ return `<a href="${url}" target="_blank">${text}</a>`; // [${text}](${url})
217
+ }
218
+ case "doc-link": {
219
+ const sel = document.getElementById("snip-doc-select");
220
+ const docId = sel.value;
221
+ const customText =
222
+ document.getElementById("snip-doc-link-text").value;
223
+ const docTitle = sel.options[sel.selectedIndex]?.text ?? docId;
224
+ const text = customText || docTitle;
225
+ return `[${text}](?doc=${encodeURIComponent(docId)})`;
226
+ }
227
+ case "anchor-link": {
228
+ const text =
229
+ document.getElementById("snip-anchor-text").value ||
230
+ window.t('snippet.link_section_placeholder');
231
+ const anchor =
232
+ document.getElementById("snip-anchor-id").value ||
233
+ window.t('snippet.link_anchor_placeholder');
234
+ return `[${text}](#${anchor})`;
235
+ }
236
+ case "anchor-doc-link": {
237
+ const sel = document.getElementById("snip-anchor-doc-select");
238
+ const docId = sel.value;
239
+ const text =
240
+ document.getElementById("snip-anchor-doc-text").value ||
241
+ window.t('snippet.link_section_placeholder');
242
+ const anchor =
243
+ document.getElementById("snip-anchor-doc-id").value ||
244
+ window.t('snippet.link_anchor_placeholder');
245
+ return `[${text}](?doc=${encodeURIComponent(docId)}#${anchor})`;
246
+ }
247
+ case "ordered-list":
248
+ return [
249
+ "1. Élément 1",
250
+ "2. Élément 2",
251
+ " 1. Sous-élément 2.1",
252
+ " 2. Sous-élément 2.2",
253
+ "3. Élément 3",
254
+ " 1. Sous-élément 3.1",
255
+ " 1. Sous-sous-élément 3.1.1",
256
+ ].join("\n");
257
+ case "unordered-list":
258
+ return [
259
+ "- Élément 1",
260
+ "- Élément 2",
261
+ " - Sous-élément 2.1",
262
+ " - Sous-élément 2.2",
263
+ "- Élément 3",
264
+ " - Sous-élément 3.1",
265
+ " - Sous-sous-élément 3.1.1",
266
+ ].join("\n");
267
+ case "code-block": {
268
+ const lang = document.getElementById("snip-code-lang").value || "";
269
+ return `\`\`\`${lang}\n// code ici\n\`\`\``;
270
+ }
271
+ case "blockquote":
272
+ return `> Citation ici\n>\n> — Auteur`;
273
+ case "separator":
274
+ return `\n---\n`;
275
+ case "image": {
276
+ const alt =
277
+ document.getElementById("snip-image-alt").value || "image";
278
+ const url =
279
+ document.getElementById("snip-image-url").value ||
280
+ "./images/mon-image.png";
281
+ return `![${alt}](${url})`;
282
+ }
283
+ case "table":
284
+ return buildTableMarkdown();
285
+ case "tree":
286
+ return buildTreeMarkdown();
287
+ case "diagram": {
288
+ const isNew = document.getElementById("snip-diag-mode-new").checked;
289
+ const imgName =
290
+ document.getElementById("snip-diag-img-name").value.trim() ||
291
+ "diagram.png";
292
+ let diagId, diagLabel;
293
+ if (isNew) {
294
+ diagId = "d" + Date.now();
295
+ diagLabel =
296
+ document.getElementById("snip-diag-new-name").value.trim() ||
297
+ "Diagram";
298
+ } else {
299
+ const sel = document.getElementById("snip-diag-select");
300
+ diagId = sel.value;
301
+ diagLabel = sel.options[sel.selectedIndex]?.text || "Diagram";
302
+ }
303
+ return `[![${diagLabel}](./images/${imgName})](/diagram?id=${diagId})`;
304
+ }
305
+ case "colored-text": {
306
+ const c = _COLOR_SWATCHES[_colorTextSwatch] || _COLOR_SWATCHES.info;
307
+ const content = document.getElementById("snip-colored-text-content").value || window.t('snippet.colored_text_content_placeholder');
308
+ return `<span style="color:${c.border};">${content}</span>`;
309
+ }
310
+ case "colored-section": {
311
+ const c = _COLOR_SWATCHES[_colorSectionSwatch] || _COLOR_SWATCHES.info;
312
+ const content = document.getElementById("snip-colored-content").value || window.t('snippet.colored_section_content_placeholder');
313
+ return `<div style="background:${c.bg};border-left:4px solid ${c.border};color:${c.text};padding:1rem 1.25rem;border-radius:0.375rem;margin:1rem 0;">\n\n${content}\n\n</div>`;
314
+ }
315
+ default:
316
+ return "";
317
+ }
318
+ }
319
+
320
+ function snippetUpdatePreview() {
321
+ document.getElementById("snippet-preview").textContent =
322
+ buildSnippetMarkdown();
323
+ }
324
+
325
+ function insertSnippet() {
326
+ const type = document.getElementById("snippet-type").value;
327
+ if (type === "diagram") {
328
+ insertDiagramSnippet();
329
+ return;
330
+ }
331
+ const text = buildSnippetMarkdown();
332
+ closeSnippetsModal();
333
+ const editor = document.getElementById("doc-editor");
334
+ const before = editor.value.slice(0, _snippetSelStart);
335
+ const after = editor.value.slice(_snippetSelEnd);
336
+ editor.value = before + text + after;
337
+ editor.selectionStart = editor.selectionEnd =
338
+ _snippetSelStart + text.length;
339
+ editor.focus();
340
+ }
341
+
342
+ async function insertDiagramSnippet() {
343
+ const isNew = document.getElementById("snip-diag-mode-new").checked;
344
+ const imgName =
345
+ document.getElementById("snip-diag-img-name").value.trim() ||
346
+ "diagram.png";
347
+ let diagId, diagLabel;
348
+ if (isNew) {
349
+ diagId = "d" + Date.now();
350
+ diagLabel =
351
+ document.getElementById("snip-diag-new-name").value.trim() ||
352
+ "Diagram";
353
+ try {
354
+ await fetch(`/api/diagrams/${diagId}`, {
355
+ method: "PUT",
356
+ headers: { "Content-Type": "application/json" },
357
+ body: JSON.stringify({ title: diagLabel, nodes: [], edges: [] }),
358
+ });
359
+ } catch (err) {
360
+ alert(window.t('error.create_diagram') + err.message);
361
+ return;
362
+ }
363
+ } else {
364
+ const sel = document.getElementById("snip-diag-select");
365
+ diagId = sel.value;
366
+ diagLabel = sel.options[sel.selectedIndex]?.text || "Diagram";
367
+ }
368
+
369
+ // Insert at cursor
370
+ const md = `[![${diagLabel}](./images/${imgName})](/diagram?id=${diagId})`;
371
+ closeSnippetsModal();
372
+ const editor = document.getElementById("doc-editor");
373
+ const before = editor.value.slice(0, _snippetSelStart);
374
+ const after = editor.value.slice(_snippetSelEnd);
375
+ editor.value = before + md + after;
376
+
377
+ // Auto-save then redirect to diagram editor
378
+ try {
379
+ const newContent = editor.value;
380
+ const res = await fetch("/api/documents/" + currentDocId, {
381
+ method: "PUT",
382
+ headers: { "Content-Type": "application/json" },
383
+ body: JSON.stringify({ content: newContent }),
384
+ });
385
+ if (!res.ok) throw new Error(await res.text());
386
+ currentDocContent = newContent;
387
+ } catch (err) {
388
+ alert("Erreur lors de la sauvegarde : " + err.message);
389
+ return;
390
+ }
391
+
392
+ window.location.href = `/diagram?id=${diagId}&img=${encodeURIComponent(imgName)}`;
393
+ }
394
+
395
+ // ── Snippet parsing (detection lives in /snippet-detect.js) ────────────────
396
+ function parseAndFillSnippet(text, type) {
397
+ const t = text.trim();
398
+ switch (type) {
399
+ case "collapsible": {
400
+ const m = t.match(/<summary>([\s\S]*?)<\/summary>/i);
401
+ if (m)
402
+ document.getElementById("snip-collapsible-summary").value =
403
+ m[1].trim();
404
+ break;
405
+ }
406
+ case "link": {
407
+ const m = t.match(/^\[([\s\S]*?)\]\(([\s\S]*?)\)$/);
408
+ if (m) {
409
+ document.getElementById("snip-link-text").value = m[1];
410
+ document.getElementById("snip-link-url").value = m[2];
411
+ }
412
+ break;
413
+ }
414
+ case "doc-link": {
415
+ const m = t.match(/^\[([\s\S]*?)\]\(\?doc=([\s\S]*?)\)$/);
416
+ if (m) {
417
+ const docId = decodeURIComponent(m[1] === "" ? m[2] : m[2]);
418
+ const sel = document.getElementById("snip-doc-select");
419
+ for (const opt of sel.options) {
420
+ if (opt.value === decodeURIComponent(m[2])) {
421
+ sel.value = opt.value;
422
+ break;
423
+ }
424
+ }
425
+ const autoTitle = sel.options[sel.selectedIndex]?.text ?? "";
426
+ document.getElementById("snip-doc-link-text").value =
427
+ m[1] === autoTitle ? "" : m[1];
428
+ }
429
+ break;
430
+ }
431
+ case "anchor-link": {
432
+ const m = t.match(/^\[([\s\S]*?)\]\(#([\s\S]*?)\)$/);
433
+ if (m) {
434
+ document.getElementById("snip-anchor-text").value = m[1];
435
+ document.getElementById("snip-anchor-id").value = m[2];
436
+ }
437
+ break;
438
+ }
439
+ case "anchor-doc-link": {
440
+ const m = t.match(
441
+ /^\[([\s\S]*?)\]\(\?doc=([\s\S]*?)#([\s\S]*?)\)$/,
442
+ );
443
+ if (m) {
444
+ const sel = document.getElementById("snip-anchor-doc-select");
445
+ for (const opt of sel.options) {
446
+ if (opt.value === decodeURIComponent(m[2])) {
447
+ sel.value = opt.value;
448
+ break;
449
+ }
450
+ }
451
+ document.getElementById("snip-anchor-doc-text").value = m[1];
452
+ document.getElementById("snip-anchor-doc-id").value = m[3];
453
+ }
454
+ break;
455
+ }
456
+ case "code-block": {
457
+ const m = t.match(/^```(\w*)\n/);
458
+ document.getElementById("snip-code-lang").value = m ? m[1] : "";
459
+ break;
460
+ }
461
+ case "image": {
462
+ const m = t.match(/^!\[([\s\S]*?)\]\(([\s\S]*?)\)$/);
463
+ if (m) {
464
+ document.getElementById("snip-image-alt").value = m[1];
465
+ document.getElementById("snip-image-url").value = m[2];
466
+ }
467
+ break;
468
+ }
469
+ case "table": {
470
+ const allLines = t
471
+ .split("\n")
472
+ .filter((l) => /^\|.*\|$/.test(l.trim()));
473
+ const dataLines = allLines.filter(
474
+ (l) => !/^\| *[-: ][-| :]*\|/.test(l),
475
+ );
476
+ _tableData = dataLines.map((line) =>
477
+ line
478
+ .split("|")
479
+ .slice(1, -1)
480
+ .map((c) => c.trim()),
481
+ );
482
+ const maxCols = Math.max(..._tableData.map((r) => r.length));
483
+ _tableData.forEach((row) => {
484
+ while (row.length < maxCols) row.push("");
485
+ });
486
+ tableRenderGrid();
487
+ break;
488
+ }
489
+ case "tree": {
490
+ const inner = t.replace(/^```text\n/, "").replace(/\n```$/, "");
491
+ _treeItems = inner.split("\n").map((line) => {
492
+ const m = line.match(/^((?:│ | )*)(?:├── |└── )([\s\S]+)$/);
493
+ if (m) return { name: m[2], depth: m[1].length / 4 + 1 };
494
+ return { name: line, depth: 0 };
495
+ });
496
+ treeRenderList();
497
+ break;
498
+ }
499
+ case "colored-text": {
500
+ const m = t.match(/^<span\s[^>]*color:([^;>"]+)[^>]*>([\s\S]*)<\/span>$/);
501
+ if (m) {
502
+ const col = m[1].trim();
503
+ const found = Object.entries(_COLOR_SWATCHES).find(([, v]) => v.border === col);
504
+ if (found) {
505
+ _colorTextSwatch = found[0];
506
+ document.querySelectorAll(".color-text-swatch-btn").forEach((b) => b.classList.remove("selected-text-swatch", "ring-offset-2"));
507
+ const activeBtn = document.querySelector(`[data-color-text-swatch="${_colorTextSwatch}"]`);
508
+ if (activeBtn) activeBtn.classList.add("selected-text-swatch", "ring-offset-2");
509
+ }
510
+ document.getElementById("snip-colored-text-content").value = m[2];
511
+ }
512
+ break;
513
+ }
514
+ case "colored-section": {
515
+ // Extract border color from inline style to guess the swatch
516
+ const borderM = t.match(/border-left:[^;]*solid\s+(#[0-9a-fA-F]{6})/);
517
+ if (borderM) {
518
+ const found = Object.entries(_COLOR_SWATCHES).find(([, v]) => v.border === borderM[1]);
519
+ if (found) {
520
+ _colorSectionSwatch = found[0];
521
+ document.querySelectorAll(".color-swatch-btn").forEach((b) => {
522
+ b.classList.remove("selected-swatch", "ring-offset-2");
523
+ });
524
+ const activeBtn = document.querySelector(`[data-color-swatch="${_colorSectionSwatch}"]`);
525
+ if (activeBtn) activeBtn.classList.add("selected-swatch", "ring-offset-2");
526
+ }
527
+ }
528
+ const contentM = t.match(/^<div[^>]*>\n\n([\s\S]*?)\n\n<\/div>$/);
529
+ if (contentM) document.getElementById("snip-colored-content").value = contentM[1];
530
+ break;
531
+ }
532
+ }
533
+ snippetUpdatePreview();
534
+ }
@@ -0,0 +1,28 @@
1
+ // ── Shared module state ──────────────────────────────────────────────────────
2
+ // Loaded first (defer, before all other modules). All symbols are globals.
3
+
4
+ let allDocs = [];
5
+ let allFolderPaths = [];
6
+ let annotationCounts = {};
7
+ let currentDocId = null;
8
+ let currentDocContent = "";
9
+ let searchQuery = "";
10
+ let searchResults = null;
11
+ let navHistory = []; // stack of { id, title } visited via in-doc links
12
+ let expandedCategories = new Set();
13
+ let expandedFolders = new Set();
14
+
15
+ function filteredDocs() {
16
+ if (!searchQuery) return allDocs;
17
+ if (Array.isArray(searchResults)) return searchResults;
18
+ const q = searchQuery.toLowerCase();
19
+ return allDocs.filter(
20
+ (d) =>
21
+ d.title.toLowerCase().includes(q) ||
22
+ d.category.toLowerCase().includes(q),
23
+ );
24
+ }
25
+
26
+ function refreshSidebar() {
27
+ renderSidebar(filteredDocs());
28
+ }
@@ -0,0 +1,21 @@
1
+ // ── Pure utilities shared across index.html ──────────────────────────────────
2
+ // Loaded as a classic script; all symbols are global.
3
+
4
+ function esc(str) {
5
+ return String(str)
6
+ .replace(/&/g, "&amp;")
7
+ .replace(/</g, "&lt;")
8
+ .replace(/>/g, "&gt;")
9
+ .replace(/"/g, "&quot;")
10
+ .replace(/'/g, "&#39;");
11
+ }
12
+
13
+ // Strip a leading numeric sort prefix (e.g. "1_tutorial" → "Tutorial").
14
+ // Underscores/hyphens become spaces, result is title-cased.
15
+ // The full original name is preserved in the tooltip.
16
+ function folderLabel(seg) {
17
+ return seg
18
+ .replace(/^\d+_/, "")
19
+ .replace(/[_-]+/g, " ")
20
+ .replace(/\b\w/g, (c) => c.toUpperCase());
21
+ }