living-ai-documentation 1.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 (203) hide show
  1. package/LICENSE +661 -0
  2. package/README.fr.md +344 -0
  3. package/README.md +344 -0
  4. package/dist/bin/cli.d.ts +3 -0
  5. package/dist/bin/cli.d.ts.map +1 -0
  6. package/dist/bin/cli.js +262 -0
  7. package/dist/bin/cli.js.map +1 -0
  8. package/dist/src/frontend/accuracy-gauge.js +70 -0
  9. package/dist/src/frontend/admin.html +1532 -0
  10. package/dist/src/frontend/annotations.js +585 -0
  11. package/dist/src/frontend/boot.js +101 -0
  12. package/dist/src/frontend/config.js +29 -0
  13. package/dist/src/frontend/confirm-modal.js +82 -0
  14. package/dist/src/frontend/context.html +1252 -0
  15. package/dist/src/frontend/dark-mode.js +20 -0
  16. package/dist/src/frontend/diagram/alignment.js +161 -0
  17. package/dist/src/frontend/diagram/clipboard.js +187 -0
  18. package/dist/src/frontend/diagram/constants.js +109 -0
  19. package/dist/src/frontend/diagram/custom-shapes.js +104 -0
  20. package/dist/src/frontend/diagram/debug.js +43 -0
  21. package/dist/src/frontend/diagram/drawio-export.js +649 -0
  22. package/dist/src/frontend/diagram/edge-panel.js +293 -0
  23. package/dist/src/frontend/diagram/edge-rendering.js +12 -0
  24. package/dist/src/frontend/diagram/evidence.js +146 -0
  25. package/dist/src/frontend/diagram/grid.js +78 -0
  26. package/dist/src/frontend/diagram/groups.js +102 -0
  27. package/dist/src/frontend/diagram/history.js +157 -0
  28. package/dist/src/frontend/diagram/image-name-modal.js +48 -0
  29. package/dist/src/frontend/diagram/image-upload.js +36 -0
  30. package/dist/src/frontend/diagram/label-editor.js +115 -0
  31. package/dist/src/frontend/diagram/link-panel.js +144 -0
  32. package/dist/src/frontend/diagram/main.js +364 -0
  33. package/dist/src/frontend/diagram/network.js +2214 -0
  34. package/dist/src/frontend/diagram/node-panel.js +389 -0
  35. package/dist/src/frontend/diagram/node-rendering.js +964 -0
  36. package/dist/src/frontend/diagram/persistence.js +168 -0
  37. package/dist/src/frontend/diagram/ports.js +421 -0
  38. package/dist/src/frontend/diagram/selection-overlay.js +387 -0
  39. package/dist/src/frontend/diagram/state.js +43 -0
  40. package/dist/src/frontend/diagram/t.js +3 -0
  41. package/dist/src/frontend/diagram/toast.js +21 -0
  42. package/dist/src/frontend/diagram/unlock-hold.js +206 -0
  43. package/dist/src/frontend/diagram/zoom.js +20 -0
  44. package/dist/src/frontend/diagram-link-modal.js +137 -0
  45. package/dist/src/frontend/diagram.html +1494 -0
  46. package/dist/src/frontend/documents.js +479 -0
  47. package/dist/src/frontend/export.js +338 -0
  48. package/dist/src/frontend/file-attach.js +178 -0
  49. package/dist/src/frontend/files-modal.js +243 -0
  50. package/dist/src/frontend/i18n/en.json +624 -0
  51. package/dist/src/frontend/i18n/fr.json +624 -0
  52. package/dist/src/frontend/i18n.js +32 -0
  53. package/dist/src/frontend/image-paste.js +126 -0
  54. package/dist/src/frontend/index.html +2806 -0
  55. package/dist/src/frontend/local-search.js +476 -0
  56. package/dist/src/frontend/metadata.js +318 -0
  57. package/dist/src/frontend/misc.js +92 -0
  58. package/dist/src/frontend/new-doc-modal.js +285 -0
  59. package/dist/src/frontend/new-folder-modal.js +169 -0
  60. package/dist/src/frontend/search.js +194 -0
  61. package/dist/src/frontend/shape-editor.html +685 -0
  62. package/dist/src/frontend/sidebar-helpers.js +96 -0
  63. package/dist/src/frontend/sidebar-resize.js +98 -0
  64. package/dist/src/frontend/sidebar.js +351 -0
  65. package/dist/src/frontend/snippet-detect.js +25 -0
  66. package/dist/src/frontend/snippet-table.js +85 -0
  67. package/dist/src/frontend/snippet-tree.js +94 -0
  68. package/dist/src/frontend/snippets.js +1146 -0
  69. package/dist/src/frontend/state.js +46 -0
  70. package/dist/src/frontend/utils.js +21 -0
  71. package/dist/src/frontend/validate.js +107 -0
  72. package/dist/src/frontend/vendor/wordcloud2.js +1187 -0
  73. package/dist/src/frontend/wordcloud.js +693 -0
  74. package/dist/src/lib/config.d.ts +26 -0
  75. package/dist/src/lib/config.d.ts.map +1 -0
  76. package/dist/src/lib/config.js +195 -0
  77. package/dist/src/lib/config.js.map +1 -0
  78. package/dist/src/lib/hash.d.ts +2 -0
  79. package/dist/src/lib/hash.d.ts.map +1 -0
  80. package/dist/src/lib/hash.js +18 -0
  81. package/dist/src/lib/hash.js.map +1 -0
  82. package/dist/src/lib/metadata.d.ts +31 -0
  83. package/dist/src/lib/metadata.d.ts.map +1 -0
  84. package/dist/src/lib/metadata.js +128 -0
  85. package/dist/src/lib/metadata.js.map +1 -0
  86. package/dist/src/lib/parser.d.ts +11 -0
  87. package/dist/src/lib/parser.d.ts.map +1 -0
  88. package/dist/src/lib/parser.js +111 -0
  89. package/dist/src/lib/parser.js.map +1 -0
  90. package/dist/src/lib/status.d.ts +9 -0
  91. package/dist/src/lib/status.d.ts.map +1 -0
  92. package/dist/src/lib/status.js +72 -0
  93. package/dist/src/lib/status.js.map +1 -0
  94. package/dist/src/mcp/server.d.ts +3 -0
  95. package/dist/src/mcp/server.d.ts.map +1 -0
  96. package/dist/src/mcp/server.js +2046 -0
  97. package/dist/src/mcp/server.js.map +1 -0
  98. package/dist/src/mcp/tools/diagrams.d.ts +82 -0
  99. package/dist/src/mcp/tools/diagrams.d.ts.map +1 -0
  100. package/dist/src/mcp/tools/diagrams.js +594 -0
  101. package/dist/src/mcp/tools/diagrams.js.map +1 -0
  102. package/dist/src/mcp/tools/documents.d.ts +44 -0
  103. package/dist/src/mcp/tools/documents.d.ts.map +1 -0
  104. package/dist/src/mcp/tools/documents.js +186 -0
  105. package/dist/src/mcp/tools/documents.js.map +1 -0
  106. package/dist/src/mcp/tools/git.d.ts +10 -0
  107. package/dist/src/mcp/tools/git.d.ts.map +1 -0
  108. package/dist/src/mcp/tools/git.js +217 -0
  109. package/dist/src/mcp/tools/git.js.map +1 -0
  110. package/dist/src/mcp/tools/metadata.d.ts +57 -0
  111. package/dist/src/mcp/tools/metadata.d.ts.map +1 -0
  112. package/dist/src/mcp/tools/metadata.js +222 -0
  113. package/dist/src/mcp/tools/metadata.js.map +1 -0
  114. package/dist/src/mcp/tools/source.d.ts +29 -0
  115. package/dist/src/mcp/tools/source.d.ts.map +1 -0
  116. package/dist/src/mcp/tools/source.js +196 -0
  117. package/dist/src/mcp/tools/source.js.map +1 -0
  118. package/dist/src/routes/annotations.d.ts +3 -0
  119. package/dist/src/routes/annotations.d.ts.map +1 -0
  120. package/dist/src/routes/annotations.js +83 -0
  121. package/dist/src/routes/annotations.js.map +1 -0
  122. package/dist/src/routes/browse-source.d.ts +3 -0
  123. package/dist/src/routes/browse-source.d.ts.map +1 -0
  124. package/dist/src/routes/browse-source.js +79 -0
  125. package/dist/src/routes/browse-source.js.map +1 -0
  126. package/dist/src/routes/browse.d.ts +3 -0
  127. package/dist/src/routes/browse.d.ts.map +1 -0
  128. package/dist/src/routes/browse.js +91 -0
  129. package/dist/src/routes/browse.js.map +1 -0
  130. package/dist/src/routes/config.d.ts +3 -0
  131. package/dist/src/routes/config.d.ts.map +1 -0
  132. package/dist/src/routes/config.js +145 -0
  133. package/dist/src/routes/config.js.map +1 -0
  134. package/dist/src/routes/context.d.ts +3 -0
  135. package/dist/src/routes/context.d.ts.map +1 -0
  136. package/dist/src/routes/context.js +287 -0
  137. package/dist/src/routes/context.js.map +1 -0
  138. package/dist/src/routes/diagrams.d.ts +3 -0
  139. package/dist/src/routes/diagrams.d.ts.map +1 -0
  140. package/dist/src/routes/diagrams.js +69 -0
  141. package/dist/src/routes/diagrams.js.map +1 -0
  142. package/dist/src/routes/documents.d.ts +11 -0
  143. package/dist/src/routes/documents.d.ts.map +1 -0
  144. package/dist/src/routes/documents.js +450 -0
  145. package/dist/src/routes/documents.js.map +1 -0
  146. package/dist/src/routes/export.d.ts +3 -0
  147. package/dist/src/routes/export.d.ts.map +1 -0
  148. package/dist/src/routes/export.js +280 -0
  149. package/dist/src/routes/export.js.map +1 -0
  150. package/dist/src/routes/files.d.ts +3 -0
  151. package/dist/src/routes/files.d.ts.map +1 -0
  152. package/dist/src/routes/files.js +180 -0
  153. package/dist/src/routes/files.js.map +1 -0
  154. package/dist/src/routes/images.d.ts +3 -0
  155. package/dist/src/routes/images.d.ts.map +1 -0
  156. package/dist/src/routes/images.js +49 -0
  157. package/dist/src/routes/images.js.map +1 -0
  158. package/dist/src/routes/metadata.d.ts +3 -0
  159. package/dist/src/routes/metadata.d.ts.map +1 -0
  160. package/dist/src/routes/metadata.js +131 -0
  161. package/dist/src/routes/metadata.js.map +1 -0
  162. package/dist/src/routes/shape-libraries.d.ts +3 -0
  163. package/dist/src/routes/shape-libraries.d.ts.map +1 -0
  164. package/dist/src/routes/shape-libraries.js +118 -0
  165. package/dist/src/routes/shape-libraries.js.map +1 -0
  166. package/dist/src/routes/wordcloud.d.ts +3 -0
  167. package/dist/src/routes/wordcloud.d.ts.map +1 -0
  168. package/dist/src/routes/wordcloud.js +95 -0
  169. package/dist/src/routes/wordcloud.js.map +1 -0
  170. package/dist/src/server.d.ts +7 -0
  171. package/dist/src/server.d.ts.map +1 -0
  172. package/dist/src/server.js +93 -0
  173. package/dist/src/server.js.map +1 -0
  174. package/dist/starter-doc/.living-doc.json +52 -0
  175. package/dist/starter-doc/ADRS/2026_01_01_[ADR]_example_architecture_decision.md +59 -0
  176. package/dist/starter-doc/AI/2026_01_01_how_to.md +112 -0
  177. package/dist/starter-doc/AI/PROJECT-INSTRUCTIONS.md +172 -0
  178. package/dist/starter-doc/AI/PROJECT-STACK.md +77 -0
  179. package/dist/starter-doc/AI/PROJECT-USEFUL-COMMANDS.md +80 -0
  180. package/dist/starter-doc/AI/default/AGENTS.md +31 -0
  181. package/dist/starter-doc/AI/default/CLAUDE.md +31 -0
  182. package/dist/starter-doc/AI/default/MEMORY.md +24 -0
  183. package/dist/starter-doc/AI/rules/no-magic-numbers.md +18 -0
  184. package/dist/starter-doc/AI/rules/track-current-work.md +23 -0
  185. package/dist/starter-doc/WORKLOG/current-task.md +57 -0
  186. package/dist/starter-doc-fr/.living-doc.json +52 -0
  187. package/dist/starter-doc-fr/ADRS/2026_01_01_[ADR]_example_architecture_decision.md +59 -0
  188. package/dist/starter-doc-fr/AI/2026_01_01_how_to.md +100 -0
  189. package/dist/starter-doc-fr/AI/PROJECT-INSTRUCTIONS.md +172 -0
  190. package/dist/starter-doc-fr/AI/PROJECT-STACK.md +77 -0
  191. package/dist/starter-doc-fr/AI/PROJECT-USEFUL-COMMANDS.md +80 -0
  192. package/dist/starter-doc-fr/AI/default/AGENTS.md +31 -0
  193. package/dist/starter-doc-fr/AI/default/CLAUDE.md +31 -0
  194. package/dist/starter-doc-fr/AI/default/MEMORY.md +24 -0
  195. package/dist/starter-doc-fr/AI/rules/no-magic-numbers.md +18 -0
  196. package/dist/starter-doc-fr/AI/rules/track-current-work.md +23 -0
  197. package/dist/starter-doc-fr/WORKLOG/current-task.md +57 -0
  198. package/images/living_documentation.jpg +0 -0
  199. package/images/readme-extra-files.png +0 -0
  200. package/images/readme-filename-pattern.png +0 -0
  201. package/images/readme-intelligent-search-demo.jpg +0 -0
  202. package/images/readme-sidebar.png +0 -0
  203. package/package.json +72 -0
@@ -0,0 +1,280 @@
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, markdownSoftBreaks } = (0, config_1.readConfig)(docsPath);
170
+ const markedOpts = { breaks: !!markdownSoftBreaks };
171
+ const docs = (0, documents_1.listDocs)(docsPath, extraFiles, filenamePattern);
172
+ // Filter to selected groups.
173
+ const selectedDocs = docs.filter((doc) => folders.includes(docGroup(doc)));
174
+ if (!selectedDocs.length) {
175
+ return res.status(404).json({ error: 'No documents found for selected folders' });
176
+ }
177
+ res.setHeader('Content-Type', 'application/zip');
178
+ res.setHeader('Content-Disposition', 'attachment; filename="export.zip"');
179
+ const archive = (0, archiver_1.default)('zip', { zlib: { level: 6 } });
180
+ archive.on('error', (err) => {
181
+ console.error('[export] archive error:', err);
182
+ });
183
+ archive.pipe(res);
184
+ // Track which images have already been added per group to avoid duplicates.
185
+ const addedImages = new Set();
186
+ for (const doc of selectedDocs) {
187
+ const group = docGroup(doc);
188
+ // Resolve file path.
189
+ let filePath;
190
+ const id = decodeURIComponent(doc.id);
191
+ if (path_1.default.isAbsolute(id)) {
192
+ const abs = id + '.md';
193
+ filePath = extraFiles.includes(abs) ? abs : null;
194
+ }
195
+ else {
196
+ filePath = (0, documents_1.safeFilePath)(docsPath, doc.filename);
197
+ }
198
+ if (!filePath || !fs_1.default.existsSync(filePath))
199
+ continue;
200
+ const raw = fs_1.default.readFileSync(filePath, 'utf-8');
201
+ const bodyHtml = marked_1.marked.parse((0, documents_1.stripFrontmatter)(raw), markedOpts)
202
+ // Strip the local-search widget placeholder — feature is viewer-only
203
+ .replace(/<div\s+data-ld-local-search(?:="[^"]*")?\s*>\s*<\/div>/gi, '');
204
+ const baseName = sanitizeFilename(path_1.default.basename(doc.filename, '.md'));
205
+ const htmlFilename = baseName + '.html';
206
+ // Notion: group/page.html + group/image.png (images at same level as HTML)
207
+ // Confluence: group/page.html + group/page/image.png (images in subfolder named after page)
208
+ const isConfluence = mode === 'confluence';
209
+ const mediaSubfolder = isConfluence ? baseName : undefined;
210
+ const { html: processedHtml, images } = processHtml(bodyHtml, mediaSubfolder);
211
+ const fullHtml = wrapHtml(doc.title, processedHtml);
212
+ archive.append(fullHtml, { name: `${group}/${htmlFilename}` });
213
+ for (const imageName of images) {
214
+ const imageDir = isConfluence ? `${group}/${baseName}` : group;
215
+ const key = `${imageDir}/${imageName}`;
216
+ if (addedImages.has(key))
217
+ continue;
218
+ addedImages.add(key);
219
+ const imagePath = path_1.default.join(docsPath, 'images', imageName);
220
+ if (fs_1.default.existsSync(imagePath)) {
221
+ archive.file(imagePath, { name: key });
222
+ }
223
+ }
224
+ }
225
+ await archive.finalize();
226
+ });
227
+ /**
228
+ * POST /api/export/markdown
229
+ * Body: {} (no filter — exports all documents)
230
+ *
231
+ * ZIP structure: group/page.md + group/image.png (images at same level as MD files)
232
+ * Internal ?doc= links are rewritten to relative .md paths.
233
+ */
234
+ router.post('/markdown', async (req, res) => {
235
+ const { extraFiles = [], filenamePattern } = (0, config_1.readConfig)(docsPath);
236
+ const docs = (0, documents_1.listDocs)(docsPath, extraFiles, filenamePattern);
237
+ if (!docs.length) {
238
+ return res.status(404).json({ error: 'No documents found' });
239
+ }
240
+ res.setHeader('Content-Type', 'application/zip');
241
+ res.setHeader('Content-Disposition', 'attachment; filename="export-markdown.zip"');
242
+ const archive = (0, archiver_1.default)('zip', { zlib: { level: 6 } });
243
+ archive.on('error', (err) => {
244
+ console.error('[export] archive error:', err);
245
+ });
246
+ archive.pipe(res);
247
+ const addedImages = new Set();
248
+ for (const doc of docs) {
249
+ const group = docGroup(doc);
250
+ let filePath;
251
+ const id = decodeURIComponent(doc.id);
252
+ if (path_1.default.isAbsolute(id)) {
253
+ const abs = id + '.md';
254
+ filePath = extraFiles.includes(abs) ? abs : null;
255
+ }
256
+ else {
257
+ filePath = (0, documents_1.safeFilePath)(docsPath, doc.filename);
258
+ }
259
+ if (!filePath || !fs_1.default.existsSync(filePath))
260
+ continue;
261
+ const raw = fs_1.default.readFileSync(filePath, 'utf-8');
262
+ const { md: processedMd, images } = processMarkdown(raw, group);
263
+ const baseName = sanitizeFilename(path_1.default.basename(doc.filename, '.md'));
264
+ archive.append(processedMd, { name: `${group}/${baseName}.md` });
265
+ for (const imageName of images) {
266
+ const key = `${group}/${imageName}`;
267
+ if (addedImages.has(key))
268
+ continue;
269
+ addedImages.add(key);
270
+ const imagePath = path_1.default.join(docsPath, 'images', imageName);
271
+ if (fs_1.default.existsSync(imagePath)) {
272
+ archive.file(imagePath, { name: key });
273
+ }
274
+ }
275
+ }
276
+ await archive.finalize();
277
+ });
278
+ return router;
279
+ }
280
+ //# sourceMappingURL=export.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"export.js","sourceRoot":"","sources":["../../../src/routes/export.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { Router } from 'express';
2
+ export declare function filesRouter(docsPath: string): Router;
3
+ //# sourceMappingURL=files.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"files.d.ts","sourceRoot":"","sources":["../../../src/routes/files.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAqB,MAAM,SAAS,CAAC;AAiCpD,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAoKpD"}
@@ -0,0 +1,180 @@
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.filesRouter = filesRouter;
7
+ const express_1 = require("express");
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const config_1 = require("../lib/config");
11
+ const MAX_FILE_BYTES = 19 * 1024 * 1024; // keep below Express 20mb body limit
12
+ function slugify(name) {
13
+ return name
14
+ .normalize('NFKD')
15
+ .replace(/[\u0300-\u036f]/g, '')
16
+ .replace(/[^a-zA-Z0-9_\-]+/g, '_')
17
+ .replace(/_+/g, '_')
18
+ .replace(/^_|_$/g, '')
19
+ .slice(0, 80);
20
+ }
21
+ function isSafeFilename(filename) {
22
+ return typeof filename === 'string'
23
+ && filename.length > 0
24
+ && !/[\\\/]/.test(filename)
25
+ && !filename.startsWith('.')
26
+ && filename !== '..';
27
+ }
28
+ function resolveFilePathSafe(filesDir, filename) {
29
+ if (!isSafeFilename(filename))
30
+ return null;
31
+ const resolvedDir = path_1.default.resolve(filesDir);
32
+ const resolved = path_1.default.resolve(filesDir, filename);
33
+ if (resolved !== path_1.default.join(resolvedDir, filename))
34
+ return null;
35
+ return resolved;
36
+ }
37
+ function filesRouter(docsPath) {
38
+ const router = (0, express_1.Router)();
39
+ // GET /api/files — list files in DOCS_FOLDER/files/ (sorted lex = chronological
40
+ // because filenames start with YYYYMMDDHHmmss).
41
+ router.get('/', (_req, res) => {
42
+ const filesDir = path_1.default.join(docsPath, 'files');
43
+ if (!fs_1.default.existsSync(filesDir)) {
44
+ return res.json({ files: [] });
45
+ }
46
+ const entries = fs_1.default
47
+ .readdirSync(filesDir, { withFileTypes: true })
48
+ .filter((e) => e.isFile())
49
+ .map((e) => e.name)
50
+ .sort((a, b) => a.localeCompare(b));
51
+ const files = entries.map((filename) => {
52
+ const stat = fs_1.default.statSync(path_1.default.join(filesDir, filename));
53
+ const match = filename.match(/^(\d{14})_[a-z0-9]{4}_(.+)$/);
54
+ let uploadedAt = null;
55
+ let displayName = filename;
56
+ if (match) {
57
+ const ts = match[1];
58
+ const d = new Date(parseInt(ts.slice(0, 4), 10), parseInt(ts.slice(4, 6), 10) - 1, parseInt(ts.slice(6, 8), 10), parseInt(ts.slice(8, 10), 10), parseInt(ts.slice(10, 12), 10), parseInt(ts.slice(12, 14), 10));
59
+ uploadedAt = isNaN(d.getTime()) ? null : d.toISOString();
60
+ displayName = match[2];
61
+ }
62
+ return {
63
+ filename,
64
+ displayName,
65
+ uploadedAt,
66
+ size: stat.size,
67
+ url: `/files/${filename}`,
68
+ };
69
+ });
70
+ res.json({ files });
71
+ });
72
+ // PUT /api/files/:filename — overwrite existing file (same filename, no history).
73
+ router.put('/:filename', (req, res) => {
74
+ const filename = req.params.filename;
75
+ const { data } = req.body;
76
+ const filesDir = path_1.default.join(docsPath, 'files');
77
+ const filePath = resolveFilePathSafe(filesDir, filename);
78
+ if (!filePath) {
79
+ return res.status(400).json({ error: 'Invalid filename' });
80
+ }
81
+ if (typeof data !== 'string' || !data) {
82
+ return res.status(400).json({ error: 'data is required' });
83
+ }
84
+ if (!fs_1.default.existsSync(filePath)) {
85
+ return res.status(404).json({ error: 'File not found' });
86
+ }
87
+ const base64 = data.replace(/^data:[^;]+;base64,/, '');
88
+ const buffer = Buffer.from(base64, 'base64');
89
+ if (buffer.length > MAX_FILE_BYTES) {
90
+ return res.status(413).json({
91
+ error: `File too large (${(buffer.length / 1024 / 1024).toFixed(1)} MB). Maximum is ${(MAX_FILE_BYTES / 1024 / 1024).toFixed(0)} MB.`,
92
+ maxBytes: MAX_FILE_BYTES,
93
+ });
94
+ }
95
+ try {
96
+ fs_1.default.writeFileSync(filePath, buffer);
97
+ const stat = fs_1.default.statSync(filePath);
98
+ res.json({ filename, size: stat.size, url: `/files/${filename}` });
99
+ }
100
+ catch {
101
+ res.status(500).json({ error: 'Failed to replace file' });
102
+ }
103
+ });
104
+ // DELETE /api/files/:filename — remove a file from DOCS_FOLDER/files/.
105
+ router.delete('/:filename', (req, res) => {
106
+ const filename = req.params.filename;
107
+ const filesDir = path_1.default.join(docsPath, 'files');
108
+ const filePath = resolveFilePathSafe(filesDir, filename);
109
+ if (!filePath) {
110
+ return res.status(400).json({ error: 'Invalid filename' });
111
+ }
112
+ if (!fs_1.default.existsSync(filePath)) {
113
+ return res.status(404).json({ error: 'File not found' });
114
+ }
115
+ try {
116
+ fs_1.default.unlinkSync(filePath);
117
+ res.json({ ok: true });
118
+ }
119
+ catch {
120
+ res.status(500).json({ error: 'Failed to delete file' });
121
+ }
122
+ });
123
+ // POST /api/files/upload — base64-encoded arbitrary file saved to DOCS_FOLDER/files/
124
+ router.post('/upload', (req, res) => {
125
+ const { data, name } = req.body;
126
+ if (typeof data !== 'string' || !data) {
127
+ return res.status(400).json({ error: 'data is required' });
128
+ }
129
+ if (typeof name !== 'string' || !name.trim()) {
130
+ return res.status(400).json({ error: 'name is required' });
131
+ }
132
+ const originalName = name.trim();
133
+ const rawExt = (path_1.default.extname(originalName) || '').replace(/^\./, '').toLowerCase();
134
+ if (!rawExt || !/^[a-z0-9]+$/.test(rawExt)) {
135
+ return res.status(400).json({ error: 'File must have a simple alphanumeric extension' });
136
+ }
137
+ const config = (0, config_1.readConfig)(docsPath);
138
+ const blocked = (config.blockedFileExtensions || []).map((e) => e.toLowerCase());
139
+ if (blocked.includes(rawExt)) {
140
+ return res.status(400).json({
141
+ error: `Extension ".${rawExt}" is blocked by server configuration`,
142
+ blockedExtension: rawExt,
143
+ });
144
+ }
145
+ const base64 = data.replace(/^data:[^;]+;base64,/, '');
146
+ const buffer = Buffer.from(base64, 'base64');
147
+ if (buffer.length > MAX_FILE_BYTES) {
148
+ return res.status(413).json({
149
+ error: `File too large (${(buffer.length / 1024 / 1024).toFixed(1)} MB). Maximum is ${(MAX_FILE_BYTES / 1024 / 1024).toFixed(0)} MB.`,
150
+ maxBytes: MAX_FILE_BYTES,
151
+ });
152
+ }
153
+ const filesDir = path_1.default.join(docsPath, 'files');
154
+ if (!fs_1.default.existsSync(filesDir)) {
155
+ fs_1.default.mkdirSync(filesDir, { recursive: true });
156
+ }
157
+ const baseWithoutExt = slugify(path_1.default.basename(originalName, path_1.default.extname(originalName))) || 'file';
158
+ const now = new Date();
159
+ const pad = (n) => String(n).padStart(2, '0');
160
+ const timestamp = `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}` +
161
+ `${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
162
+ const random = Math.random().toString(36).slice(2, 6);
163
+ const filename = `${timestamp}_${random}_${baseWithoutExt}.${rawExt}`;
164
+ const filePath = path_1.default.join(filesDir, filename);
165
+ try {
166
+ fs_1.default.writeFileSync(filePath, buffer);
167
+ res.json({
168
+ filename,
169
+ url: `/files/${filename}`,
170
+ originalName,
171
+ size: buffer.length,
172
+ });
173
+ }
174
+ catch {
175
+ res.status(500).json({ error: 'Failed to save file' });
176
+ }
177
+ });
178
+ return router;
179
+ }
180
+ //# sourceMappingURL=files.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"files.js","sourceRoot":"","sources":["../../../src/routes/files.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { Router } from 'express';
2
+ export declare function imagesRouter(docsPath: string): Router;
3
+ //# sourceMappingURL=images.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"images.d.ts","sourceRoot":"","sources":["../../../src/routes/images.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAqB,MAAM,SAAS,CAAC;AAIpD,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CA6CrD"}
@@ -0,0 +1,49 @@
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.imagesRouter = imagesRouter;
7
+ const express_1 = require("express");
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
10
+ function imagesRouter(docsPath) {
11
+ const router = (0, express_1.Router)();
12
+ // POST /api/images/upload — save a base64-encoded image to DOCS_FOLDER/images/
13
+ router.post('/upload', (req, res) => {
14
+ const { data, ext, name } = req.body;
15
+ if (typeof data !== 'string' || !data) {
16
+ return res.status(400).json({ error: 'data is required' });
17
+ }
18
+ const safeExt = (typeof ext === 'string' ? ext.replace(/[^a-z0-9]/gi, '') : 'png').toLowerCase() || 'png';
19
+ // Strip base64 data URL prefix if present
20
+ const base64 = data.replace(/^data:image\/[^;]+;base64,/, '');
21
+ const imagesDir = path_1.default.join(docsPath, 'images');
22
+ if (!fs_1.default.existsSync(imagesDir)) {
23
+ fs_1.default.mkdirSync(imagesDir, { recursive: true });
24
+ }
25
+ let baseName;
26
+ if (typeof name === 'string' && name.trim()) {
27
+ baseName = name.trim().replace(/[^a-z0-9_\-]/gi, '_').replace(/_+/g, '_').replace(/^_|_$/g, '');
28
+ }
29
+ else {
30
+ const now = new Date();
31
+ const pad = (n) => String(n).padStart(2, '0');
32
+ const timestamp = `${now.getFullYear()}_${pad(now.getMonth() + 1)}_${pad(now.getDate())}` +
33
+ `_${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
34
+ const random = Math.random().toString(36).slice(2, 6);
35
+ baseName = `image_${timestamp}_${random}`;
36
+ }
37
+ const filename = `${baseName}.${safeExt}`;
38
+ const filePath = path_1.default.join(imagesDir, filename);
39
+ try {
40
+ fs_1.default.writeFileSync(filePath, Buffer.from(base64, 'base64'));
41
+ res.json({ filename });
42
+ }
43
+ catch {
44
+ res.status(500).json({ error: 'Failed to save image' });
45
+ }
46
+ });
47
+ return router;
48
+ }
49
+ //# sourceMappingURL=images.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"images.js","sourceRoot":"","sources":["../../../src/routes/images.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { Router } from "express";
2
+ export declare function metadataRouter(docsPath: string): Router;
3
+ //# sourceMappingURL=metadata.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metadata.d.ts","sourceRoot":"","sources":["../../../src/routes/metadata.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAqB,MAAM,SAAS,CAAC;AAmCpD,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAiGvD"}