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,332 @@
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.collectMdFiles = collectMdFiles;
7
+ exports.listDocs = listDocs;
8
+ exports.safeFilePath = safeFilePath;
9
+ exports.stripFrontmatter = stripFrontmatter;
10
+ exports.documentsRouter = documentsRouter;
11
+ const express_1 = require("express");
12
+ const fs_1 = __importDefault(require("fs"));
13
+ const path_1 = __importDefault(require("path"));
14
+ const marked_1 = require("marked");
15
+ const parser_1 = require("../lib/parser");
16
+ const config_1 = require("../lib/config");
17
+ function collectMdFiles(dir, baseDir) {
18
+ const results = [];
19
+ for (const entry of fs_1.default.readdirSync(dir, { withFileTypes: true })) {
20
+ if (entry.name.startsWith("."))
21
+ continue;
22
+ const fullPath = path_1.default.join(dir, entry.name);
23
+ if (entry.isDirectory()) {
24
+ results.push(...collectMdFiles(fullPath, baseDir));
25
+ }
26
+ else if (entry.isFile() && entry.name.toLowerCase().endsWith(".md")) {
27
+ results.push(path_1.default.relative(baseDir, fullPath));
28
+ }
29
+ }
30
+ return results;
31
+ }
32
+ function listDocs(docsPath, extraFiles = [], filenamePattern) {
33
+ // Extra files first, in config order, always General
34
+ const extraDocs = [];
35
+ for (const filePath of extraFiles) {
36
+ if (!filePath.endsWith(".md") || !fs_1.default.existsSync(filePath))
37
+ continue;
38
+ const filename = path_1.default.basename(filePath);
39
+ const meta = (0, parser_1.parseFilename)(filename, filenamePattern);
40
+ extraDocs.push({
41
+ ...meta,
42
+ id: encodeURIComponent(filePath.slice(0, -3)),
43
+ category: "General",
44
+ folder: null,
45
+ });
46
+ }
47
+ // Regular docs: recursive scan, sorted by relative path
48
+ const regularDocs = collectMdFiles(docsPath, docsPath).map((relPath) => {
49
+ const filename = path_1.default.basename(relPath);
50
+ const subdir = path_1.default.dirname(relPath);
51
+ const meta = (0, parser_1.parseFilename)(filename, filenamePattern);
52
+ const id = encodeURIComponent(relPath.slice(0, -3));
53
+ const folder = subdir !== "."
54
+ ? subdir.split(path_1.default.sep)
55
+ : null;
56
+ return { ...meta, id, filename: relPath, folder };
57
+ });
58
+ regularDocs.sort((a, b) => a.filename.localeCompare(b.filename));
59
+ return [...extraDocs, ...regularDocs];
60
+ }
61
+ function buildFilename(filenamePattern, title, category, date) {
62
+ const [year, month, day] = date.split('-');
63
+ const now = new Date();
64
+ const hours = String(now.getHours()).padStart(2, '0');
65
+ const minutes = String(now.getMinutes()).padStart(2, '0');
66
+ const titleSlug = title.trim()
67
+ .toLowerCase()
68
+ .replace(/\s+/g, '_')
69
+ .replace(/[^a-z0-9_]/g, '')
70
+ .replace(/_+/g, '_')
71
+ .replace(/^_|_$/g, '') || 'document';
72
+ return (filenamePattern || 'YYYY_MM_DD_HH_mm_[Category]_title')
73
+ .replace('YYYY', year)
74
+ .replace('MM', month)
75
+ .replace('DD', day)
76
+ .replace('HH', hours)
77
+ .replace('mm', minutes)
78
+ .replace(/\[Category\]/i, `[${category}]`)
79
+ .replace(/(?<![a-z0-9])(?:title_words|title)(?![a-z0-9])/i, titleSlug) + '.md';
80
+ }
81
+ function safeFilePath(docsPath, filename) {
82
+ const resolved = path_1.default.resolve(docsPath, filename);
83
+ if (!resolved.startsWith(path_1.default.resolve(docsPath) + path_1.default.sep))
84
+ return null;
85
+ return resolved;
86
+ }
87
+ function resolveDocPath(docsPath, doc, extraFiles) {
88
+ const id = decodeURIComponent(doc.id);
89
+ if (path_1.default.isAbsolute(id)) {
90
+ const filePath = id + ".md";
91
+ return extraFiles.includes(filePath) ? filePath : null;
92
+ }
93
+ return safeFilePath(docsPath, doc.filename);
94
+ }
95
+ function stripFrontmatter(content) {
96
+ if (!content.startsWith("---"))
97
+ return content;
98
+ const end = content.indexOf("\n---", 3);
99
+ if (end === -1)
100
+ return content;
101
+ return content.slice(end + 4).replace(/^\n/, "");
102
+ }
103
+ function documentsRouter(docsPath) {
104
+ const router = (0, express_1.Router)();
105
+ // GET /api/documents — list all docs with metadata
106
+ router.get("/", (_req, res) => {
107
+ try {
108
+ const { extraFiles = [], filenamePattern } = (0, config_1.readConfig)(docsPath);
109
+ res.json(listDocs(docsPath, extraFiles, filenamePattern));
110
+ }
111
+ catch {
112
+ res.status(500).json({ error: "Failed to list documents" });
113
+ }
114
+ });
115
+ // GET /api/documents/search?q=query — full-text search
116
+ router.get("/search", (req, res) => {
117
+ const query = (req.query.q ?? "").toLowerCase().trim();
118
+ if (!query)
119
+ return res.json([]);
120
+ try {
121
+ const { extraFiles = [], filenamePattern } = (0, config_1.readConfig)(docsPath);
122
+ const docs = listDocs(docsPath, extraFiles, filenamePattern);
123
+ const results = [];
124
+ for (const doc of docs) {
125
+ const filePath = resolveDocPath(docsPath, doc, extraFiles);
126
+ if (!filePath || !fs_1.default.existsSync(filePath))
127
+ continue;
128
+ const content = fs_1.default.readFileSync(filePath, "utf-8");
129
+ const inTitle = doc.title.toLowerCase().includes(query);
130
+ const inCategory = doc.category.toLowerCase().includes(query);
131
+ const inContent = content.toLowerCase().includes(query);
132
+ if (inTitle || inCategory || inContent) {
133
+ let excerpt = "";
134
+ if (inContent) {
135
+ const idx = content.toLowerCase().indexOf(query);
136
+ const start = Math.max(0, idx - 60);
137
+ const end = Math.min(content.length, idx + query.length + 90);
138
+ excerpt =
139
+ (start > 0 ? "…" : "") +
140
+ content.slice(start, end).replace(/\n+/g, " ").trim() +
141
+ (end < content.length ? "…" : "");
142
+ }
143
+ results.push({ ...doc, excerpt });
144
+ }
145
+ }
146
+ res.json(results);
147
+ }
148
+ catch {
149
+ res.status(500).json({ error: "Search failed" });
150
+ }
151
+ });
152
+ // GET /api/documents/:id — get a single document (content + rendered HTML)
153
+ router.get("/:id", async (req, res) => {
154
+ const id = decodeURIComponent(req.params.id);
155
+ const { extraFiles = [], filenamePattern } = (0, config_1.readConfig)(docsPath);
156
+ // Extra file: id is an absolute path without .md
157
+ if (path_1.default.isAbsolute(id)) {
158
+ const filePath = id + ".md";
159
+ if (!extraFiles.includes(filePath)) {
160
+ return res.status(403).json({ error: "Access denied" });
161
+ }
162
+ if (!fs_1.default.existsSync(filePath)) {
163
+ return res.status(404).json({ error: "Document not found" });
164
+ }
165
+ try {
166
+ const content = fs_1.default.readFileSync(filePath, "utf-8");
167
+ const meta = (0, parser_1.parseFilename)(path_1.default.basename(filePath), filenamePattern);
168
+ const html = marked_1.marked.parse(stripFrontmatter(content));
169
+ res.json({
170
+ ...meta,
171
+ id: req.params.id,
172
+ category: "General",
173
+ content,
174
+ html,
175
+ });
176
+ }
177
+ catch {
178
+ res.status(500).json({ error: "Failed to read document" });
179
+ }
180
+ return;
181
+ }
182
+ // Normal file inside docsPath
183
+ const filename = id + ".md";
184
+ const filePath = safeFilePath(docsPath, filename);
185
+ if (!filePath) {
186
+ return res.status(403).json({ error: "Access denied" });
187
+ }
188
+ if (!fs_1.default.existsSync(filePath)) {
189
+ return res.status(404).json({ error: "Document not found" });
190
+ }
191
+ try {
192
+ const content = fs_1.default.readFileSync(filePath, "utf-8");
193
+ const metadata = (0, parser_1.parseFilename)(path_1.default.basename(filename), filenamePattern);
194
+ const subdir = path_1.default.dirname(id);
195
+ const folder = subdir !== "."
196
+ ? subdir.split("/")
197
+ : null;
198
+ const html = marked_1.marked.parse(stripFrontmatter(content));
199
+ res.json({ ...metadata, folder, content, html });
200
+ }
201
+ catch {
202
+ res.status(500).json({ error: "Failed to read document" });
203
+ }
204
+ });
205
+ // PUT /api/documents/:id — update document content
206
+ router.put("/:id", (req, res) => {
207
+ const id = decodeURIComponent(req.params.id);
208
+ const { content } = req.body;
209
+ if (typeof content !== "string") {
210
+ return res.status(400).json({ error: "content is required" });
211
+ }
212
+ const { extraFiles = [] } = (0, config_1.readConfig)(docsPath);
213
+ // Extra file: id is an absolute path without .md
214
+ if (path_1.default.isAbsolute(id)) {
215
+ const filePath = id + ".md";
216
+ if (!extraFiles.includes(filePath)) {
217
+ return res.status(403).json({ error: "Access denied" });
218
+ }
219
+ try {
220
+ fs_1.default.writeFileSync(filePath, content, "utf-8");
221
+ return res.json({ ok: true });
222
+ }
223
+ catch {
224
+ return res.status(500).json({ error: "Failed to write document" });
225
+ }
226
+ }
227
+ // Normal file inside docsPath
228
+ const filename = id + ".md";
229
+ const filePath = safeFilePath(docsPath, filename);
230
+ if (!filePath) {
231
+ return res.status(403).json({ error: "Access denied" });
232
+ }
233
+ try {
234
+ fs_1.default.writeFileSync(filePath, content, "utf-8");
235
+ return res.json({ ok: true });
236
+ }
237
+ catch {
238
+ return res.status(500).json({ error: "Failed to write document" });
239
+ }
240
+ });
241
+ // DELETE /api/documents/:id — delete a document permanently
242
+ router.delete("/:id", (req, res) => {
243
+ const id = decodeURIComponent(req.params.id);
244
+ const { extraFiles = [] } = (0, config_1.readConfig)(docsPath);
245
+ if (path_1.default.isAbsolute(id)) {
246
+ const filePath = id + ".md";
247
+ if (!extraFiles.includes(filePath)) {
248
+ return res.status(403).json({ error: "Access denied" });
249
+ }
250
+ if (!fs_1.default.existsSync(filePath)) {
251
+ return res.status(404).json({ error: "Document not found" });
252
+ }
253
+ try {
254
+ fs_1.default.unlinkSync(filePath);
255
+ return res.json({ ok: true });
256
+ }
257
+ catch {
258
+ return res.status(500).json({ error: "Failed to delete document" });
259
+ }
260
+ }
261
+ const filename = id + ".md";
262
+ const filePath = safeFilePath(docsPath, filename);
263
+ if (!filePath)
264
+ return res.status(403).json({ error: "Access denied" });
265
+ if (!fs_1.default.existsSync(filePath)) {
266
+ return res.status(404).json({ error: "Document not found" });
267
+ }
268
+ try {
269
+ fs_1.default.unlinkSync(filePath);
270
+ // Also drop its annotations, if any
271
+ const annPath = path_1.default.join(docsPath, ".annotations.json");
272
+ if (fs_1.default.existsSync(annPath)) {
273
+ try {
274
+ const store = JSON.parse(fs_1.default.readFileSync(annPath, "utf-8"));
275
+ if (store && typeof store === "object" && store[id]) {
276
+ delete store[id];
277
+ fs_1.default.writeFileSync(annPath, JSON.stringify(store, null, 2), "utf-8");
278
+ }
279
+ }
280
+ catch {
281
+ /* non-fatal */
282
+ }
283
+ }
284
+ return res.json({ ok: true });
285
+ }
286
+ catch {
287
+ return res.status(500).json({ error: "Failed to delete document" });
288
+ }
289
+ });
290
+ // POST /api/documents — create a new document
291
+ router.post('/', (req, res) => {
292
+ const { title, category = 'General', folder = '' } = req.body;
293
+ if (!title || !title.trim()) {
294
+ return res.status(400).json({ error: 'title is required' });
295
+ }
296
+ const { filenamePattern } = (0, config_1.readConfig)(docsPath);
297
+ const today = new Date().toISOString().slice(0, 10);
298
+ const filename = buildFilename(filenamePattern || 'YYYY_MM_DD_HH_mm_[Category]_title', title.trim(), (category.trim() || 'General'), today);
299
+ // Resolve target directory, constrained to docsPath
300
+ let targetDir = path_1.default.resolve(docsPath);
301
+ if (folder && folder.trim()) {
302
+ const resolved = path_1.default.resolve(docsPath, folder.trim());
303
+ if (!resolved.startsWith(path_1.default.resolve(docsPath) + path_1.default.sep) && resolved !== path_1.default.resolve(docsPath)) {
304
+ return res.status(403).json({ error: 'Access denied' });
305
+ }
306
+ targetDir = resolved;
307
+ }
308
+ if (!fs_1.default.existsSync(targetDir)) {
309
+ fs_1.default.mkdirSync(targetDir, { recursive: true });
310
+ }
311
+ const filePath = path_1.default.join(targetDir, filename);
312
+ if (fs_1.default.existsSync(filePath)) {
313
+ return res.status(409).json({ error: 'A document with this name already exists' });
314
+ }
315
+ const content = `# ${title.trim()}\n`;
316
+ try {
317
+ fs_1.default.writeFileSync(filePath, content, 'utf-8');
318
+ }
319
+ catch {
320
+ return res.status(500).json({ error: 'Failed to create document' });
321
+ }
322
+ const relPath = path_1.default.relative(docsPath, filePath);
323
+ const meta = (0, parser_1.parseFilename)(filename, filenamePattern);
324
+ const subdir = path_1.default.dirname(relPath);
325
+ const folderSegments = subdir !== '.'
326
+ ? subdir.split(path_1.default.sep)
327
+ : null;
328
+ res.json({ ...meta, id: encodeURIComponent(relPath.slice(0, -3)), filename: relPath, folder: folderSegments });
329
+ });
330
+ return router;
331
+ }
332
+ //# sourceMappingURL=documents.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"documents.js","sourceRoot":"","sources":["../../../src/routes/documents.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { Router } from 'express';
2
+ export declare function exportRouter(docsPath: string): Router;
3
+ //# sourceMappingURL=export.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"export.d.ts","sourceRoot":"","sources":["../../../src/routes/export.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAqB,MAAM,SAAS,CAAC;AAwLpD,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CA4IrD"}
@@ -0,0 +1,277 @@
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.exportRouter = exportRouter;
7
+ const express_1 = require("express");
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const archiver_1 = __importDefault(require("archiver"));
11
+ const marked_1 = require("marked");
12
+ const config_1 = require("../lib/config");
13
+ const documents_1 = require("./documents");
14
+ // ── Helpers ────────────────────────────────────────────────────────────────────
15
+ function escapeHtml(s) {
16
+ return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
17
+ }
18
+ function sanitizeFilename(name) {
19
+ return name
20
+ .replace(/\s+/g, '-')
21
+ .replace(/[^\w\-\.]/g, '')
22
+ .replace(/-+/g, '-')
23
+ .replace(/^[-_]+|[-_]+$/g, '') || 'document';
24
+ }
25
+ /** Group key used to decide which ZIP folder a doc goes into. */
26
+ function docGroup(doc) {
27
+ return doc.folder?.[0] ?? doc.category ?? 'General';
28
+ }
29
+ /** Minimal HTML wrapper for exported pages. */
30
+ function wrapHtml(title, body) {
31
+ return `<!DOCTYPE html>
32
+ <html lang="en">
33
+ <head>
34
+ <meta charset="UTF-8">
35
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
36
+ <title>${escapeHtml(title)}</title>
37
+ <style>
38
+ body{font-family:system-ui,-apple-system,Segoe UI,sans-serif;max-width:900px;margin:0 auto;padding:2rem;color:#111827;line-height:1.7}
39
+ h1,h2,h3,h4,h5,h6{font-weight:600;margin-top:1.5em;line-height:1.3}
40
+ h1{font-size:2em;border-bottom:2px solid #e5e7eb;padding-bottom:.4em;margin-top:0}
41
+ h2{font-size:1.5em}h3{font-size:1.2em}
42
+ p{margin:.8em 0}
43
+ code{background:#f3f4f6;padding:.15em .4em;border-radius:4px;font-size:.9em;font-family:ui-monospace,Menlo,monospace}
44
+ pre{background:#1f2937;color:#f9fafb;padding:1.2rem;border-radius:8px;overflow-x:auto}
45
+ pre code{background:none;padding:0;color:inherit;font-size:.85em}
46
+ table{border-collapse:collapse;width:100%;margin:1em 0}
47
+ th,td{border:1px solid #e5e7eb;padding:.5rem 1rem;text-align:left}
48
+ th{background:#f9fafb;font-weight:600}
49
+ tr:nth-child(even){background:#f9fafb}
50
+ img{max-width:100%;height:auto;border-radius:4px}
51
+ blockquote{border-left:4px solid #d1d5db;margin:1em 0;padding:.5em 1em;background:#f9fafb;color:#4b5563}
52
+ a{color:#2563eb;text-decoration:none}
53
+ a:hover{text-decoration:underline}
54
+ ul,ol{padding-left:1.5em}li{margin:.3em 0}
55
+ hr{border:none;border-top:1px solid #e5e7eb;margin:2em 0}
56
+ </style>
57
+ </head>
58
+ <body>
59
+ ${body}
60
+ </body>
61
+ </html>`;
62
+ }
63
+ /**
64
+ * Process rendered HTML:
65
+ * 1. Remove <a> wrappers around diagram links (keep the inner <img>).
66
+ * 2. Rewrite image src from ./images/xxx or /images/xxx to ./{mediaSubfolder}/xxx (or ./xxx if no subfolder).
67
+ * 3. Collect referenced image basenames.
68
+ *
69
+ * @param mediaSubfolder Optional subfolder name for media (used by Confluence mode).
70
+ * When provided, images are referenced as `./{mediaSubfolder}/{basename}`.
71
+ */
72
+ function processHtml(html, mediaSubfolder) {
73
+ const images = new Set();
74
+ const imgPrefix = mediaSubfolder ? `./${mediaSubfolder}/` : './';
75
+ // Remove diagram link wrappers but keep inner content (e.g. the screenshot img).
76
+ html = html.replace(/<a\s[^>]*href=["'][^"']*\/diagram[^"']*["'][^>]*>([\s\S]*?)<\/a>/gi, '$1');
77
+ // Rewrite image src attributes and collect basenames.
78
+ html = html.replace(/(<img\b[^>]*?\s)src=["']((?:\.\/|\/)?images\/([^"'?#\s]+))["']/gi, (_match, before, _fullSrc, filename) => {
79
+ const basename = path_1.default.basename(filename);
80
+ images.add(basename);
81
+ return `${before}src="${imgPrefix}${basename}"`;
82
+ });
83
+ // Also handle src= at the start of the tag (no preceding attributes).
84
+ html = html.replace(/(<img\b)(\s+)src=["']((?:\.\/|\/)?images\/([^"'?#\s]+))["']/gi, (_match, tag, space, _fullSrc, filename) => {
85
+ const basename = path_1.default.basename(filename);
86
+ images.add(basename);
87
+ return `${tag}${space}src="${imgPrefix}${basename}"`;
88
+ });
89
+ return { html, images };
90
+ }
91
+ /**
92
+ * Rewrite internal `?doc=VALUE` links to relative `.md` file links.
93
+ *
94
+ * The VALUE in the URL is double-encoded (e.g. `%252F` = `/`, `%255B` = `[`).
95
+ * After double-decoding we get the doc id, e.g. `4_reference/2026_..._[CATEGORY]_title`.
96
+ * We split on the first `/` to get the target group, then sanitize the basename the same
97
+ * way the export does, and compute a relative path from currentGroup.
98
+ */
99
+ function rewriteDocLinks(md, currentGroup) {
100
+ return md.replace(/\[([^\]]*)\]\(\?doc=([^)]+)\)/g, (_match, text, encoded) => {
101
+ try {
102
+ // Try double-decode first (standard for URLs built by the app).
103
+ let docId;
104
+ try {
105
+ docId = decodeURIComponent(decodeURIComponent(encoded));
106
+ }
107
+ catch {
108
+ docId = decodeURIComponent(encoded);
109
+ }
110
+ const slashIdx = docId.indexOf('/');
111
+ const targetGroup = slashIdx === -1 ? 'General' : docId.slice(0, slashIdx);
112
+ const rawName = slashIdx === -1 ? docId : docId.slice(slashIdx + 1);
113
+ const sanitized = sanitizeFilename(path_1.default.basename(rawName)) + '.md';
114
+ const rel = targetGroup === currentGroup
115
+ ? `./${sanitized}`
116
+ : `../${targetGroup}/${sanitized}`;
117
+ return `[${text}](${rel})`;
118
+ }
119
+ catch {
120
+ return _match; // leave untouched if decoding fails
121
+ }
122
+ });
123
+ }
124
+ /**
125
+ * Process raw Markdown for export:
126
+ * 1. Rewrite ?doc= internal links to relative .md file links.
127
+ * 2. Rewrite markdown image syntax from ./images/xxx or /images/xxx to ./xxx.
128
+ * 3. Rewrite HTML <img src="./images/xxx"> to <img src="./xxx">.
129
+ * 4. Collect referenced image basenames.
130
+ */
131
+ function processMarkdown(md, currentGroup) {
132
+ const images = new Set();
133
+ // Rewrite ?doc= internal navigation links to relative file links.
134
+ md = rewriteDocLinks(md, currentGroup);
135
+ // Rewrite markdown image syntax: ![alt](./images/name.png "title") → ![alt](./name.png "title")
136
+ md = md.replace(/!\[([^\]]*)\]\(((?:\.\/|\/)?images\/([^)"'\s#?]+))([^)]*)\)/g, (_match, alt, _fullSrc, filename, rest) => {
137
+ const basename = path_1.default.basename(filename);
138
+ images.add(basename);
139
+ return `![${alt}](./${basename}${rest})`;
140
+ });
141
+ // Rewrite HTML img tags embedded in markdown — two patterns (with/without preceding attributes).
142
+ md = md.replace(/(<img\b[^>]*?\s)src=["']((?:\.\/|\/)?images\/([^"'?#\s]+))["']/gi, (_match, before, _fullSrc, filename) => {
143
+ const basename = path_1.default.basename(filename);
144
+ images.add(basename);
145
+ return `${before}src="./${basename}"`;
146
+ });
147
+ md = md.replace(/(<img\b)(\s+)src=["']((?:\.\/|\/)?images\/([^"'?#\s]+))["']/gi, (_match, tag, space, _fullSrc, filename) => {
148
+ const basename = path_1.default.basename(filename);
149
+ images.add(basename);
150
+ return `${tag}${space}src="./${basename}"`;
151
+ });
152
+ return { md, images };
153
+ }
154
+ // ── Route ─────────────────────────────────────────────────────────────────────
155
+ function exportRouter(docsPath) {
156
+ const router = (0, express_1.Router)();
157
+ /**
158
+ * POST /api/export/html
159
+ * Body: { folders: string[], mode: 'notion' | 'confluence' }
160
+ *
161
+ * Notion → group/page.html + group/image.png (flat per group)
162
+ * Confluence → group/page/page.html + group/page/image.png (one sub-folder per page)
163
+ */
164
+ router.post('/html', async (req, res) => {
165
+ const { folders, mode = 'notion' } = req.body;
166
+ if (!folders?.length) {
167
+ return res.status(400).json({ error: 'No folders selected' });
168
+ }
169
+ const { extraFiles = [], filenamePattern } = (0, config_1.readConfig)(docsPath);
170
+ const docs = (0, documents_1.listDocs)(docsPath, extraFiles, filenamePattern);
171
+ // Filter to selected groups.
172
+ const selectedDocs = docs.filter((doc) => folders.includes(docGroup(doc)));
173
+ if (!selectedDocs.length) {
174
+ return res.status(404).json({ error: 'No documents found for selected folders' });
175
+ }
176
+ res.setHeader('Content-Type', 'application/zip');
177
+ res.setHeader('Content-Disposition', 'attachment; filename="export.zip"');
178
+ const archive = (0, archiver_1.default)('zip', { zlib: { level: 6 } });
179
+ archive.on('error', (err) => {
180
+ console.error('[export] archive error:', err);
181
+ });
182
+ archive.pipe(res);
183
+ // Track which images have already been added per group to avoid duplicates.
184
+ const addedImages = new Set();
185
+ for (const doc of selectedDocs) {
186
+ const group = docGroup(doc);
187
+ // Resolve file path.
188
+ let filePath;
189
+ const id = decodeURIComponent(doc.id);
190
+ if (path_1.default.isAbsolute(id)) {
191
+ const abs = id + '.md';
192
+ filePath = extraFiles.includes(abs) ? abs : null;
193
+ }
194
+ else {
195
+ filePath = (0, documents_1.safeFilePath)(docsPath, doc.filename);
196
+ }
197
+ if (!filePath || !fs_1.default.existsSync(filePath))
198
+ continue;
199
+ const raw = fs_1.default.readFileSync(filePath, 'utf-8');
200
+ const bodyHtml = marked_1.marked.parse((0, documents_1.stripFrontmatter)(raw));
201
+ const baseName = sanitizeFilename(path_1.default.basename(doc.filename, '.md'));
202
+ const htmlFilename = baseName + '.html';
203
+ // Notion: group/page.html + group/image.png (images at same level as HTML)
204
+ // Confluence: group/page.html + group/page/image.png (images in subfolder named after page)
205
+ const isConfluence = mode === 'confluence';
206
+ const mediaSubfolder = isConfluence ? baseName : undefined;
207
+ const { html: processedHtml, images } = processHtml(bodyHtml, mediaSubfolder);
208
+ const fullHtml = wrapHtml(doc.title, processedHtml);
209
+ archive.append(fullHtml, { name: `${group}/${htmlFilename}` });
210
+ for (const imageName of images) {
211
+ const imageDir = isConfluence ? `${group}/${baseName}` : group;
212
+ const key = `${imageDir}/${imageName}`;
213
+ if (addedImages.has(key))
214
+ continue;
215
+ addedImages.add(key);
216
+ const imagePath = path_1.default.join(docsPath, 'images', imageName);
217
+ if (fs_1.default.existsSync(imagePath)) {
218
+ archive.file(imagePath, { name: key });
219
+ }
220
+ }
221
+ }
222
+ await archive.finalize();
223
+ });
224
+ /**
225
+ * POST /api/export/markdown
226
+ * Body: {} (no filter — exports all documents)
227
+ *
228
+ * ZIP structure: group/page.md + group/image.png (images at same level as MD files)
229
+ * Internal ?doc= links are rewritten to relative .md paths.
230
+ */
231
+ router.post('/markdown', async (req, res) => {
232
+ const { extraFiles = [], filenamePattern } = (0, config_1.readConfig)(docsPath);
233
+ const docs = (0, documents_1.listDocs)(docsPath, extraFiles, filenamePattern);
234
+ if (!docs.length) {
235
+ return res.status(404).json({ error: 'No documents found' });
236
+ }
237
+ res.setHeader('Content-Type', 'application/zip');
238
+ res.setHeader('Content-Disposition', 'attachment; filename="export-markdown.zip"');
239
+ const archive = (0, archiver_1.default)('zip', { zlib: { level: 6 } });
240
+ archive.on('error', (err) => {
241
+ console.error('[export] archive error:', err);
242
+ });
243
+ archive.pipe(res);
244
+ const addedImages = new Set();
245
+ for (const doc of docs) {
246
+ const group = docGroup(doc);
247
+ let filePath;
248
+ const id = decodeURIComponent(doc.id);
249
+ if (path_1.default.isAbsolute(id)) {
250
+ const abs = id + '.md';
251
+ filePath = extraFiles.includes(abs) ? abs : null;
252
+ }
253
+ else {
254
+ filePath = (0, documents_1.safeFilePath)(docsPath, doc.filename);
255
+ }
256
+ if (!filePath || !fs_1.default.existsSync(filePath))
257
+ continue;
258
+ const raw = fs_1.default.readFileSync(filePath, 'utf-8');
259
+ const { md: processedMd, images } = processMarkdown(raw, group);
260
+ const baseName = sanitizeFilename(path_1.default.basename(doc.filename, '.md'));
261
+ archive.append(processedMd, { name: `${group}/${baseName}.md` });
262
+ for (const imageName of images) {
263
+ const key = `${group}/${imageName}`;
264
+ if (addedImages.has(key))
265
+ continue;
266
+ addedImages.add(key);
267
+ const imagePath = path_1.default.join(docsPath, 'images', imageName);
268
+ if (fs_1.default.existsSync(imagePath)) {
269
+ archive.file(imagePath, { name: key });
270
+ }
271
+ }
272
+ }
273
+ await archive.finalize();
274
+ });
275
+ return router;
276
+ }
277
+ //# sourceMappingURL=export.js.map