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.
- package/LICENSE +661 -0
- package/README.fr.md +344 -0
- package/README.md +344 -0
- package/dist/bin/cli.d.ts +3 -0
- package/dist/bin/cli.d.ts.map +1 -0
- package/dist/bin/cli.js +262 -0
- package/dist/bin/cli.js.map +1 -0
- package/dist/src/frontend/accuracy-gauge.js +70 -0
- package/dist/src/frontend/admin.html +1532 -0
- package/dist/src/frontend/annotations.js +585 -0
- package/dist/src/frontend/boot.js +101 -0
- package/dist/src/frontend/config.js +29 -0
- package/dist/src/frontend/confirm-modal.js +82 -0
- package/dist/src/frontend/context.html +1252 -0
- package/dist/src/frontend/dark-mode.js +20 -0
- package/dist/src/frontend/diagram/alignment.js +161 -0
- package/dist/src/frontend/diagram/clipboard.js +187 -0
- package/dist/src/frontend/diagram/constants.js +109 -0
- package/dist/src/frontend/diagram/custom-shapes.js +104 -0
- package/dist/src/frontend/diagram/debug.js +43 -0
- package/dist/src/frontend/diagram/drawio-export.js +649 -0
- package/dist/src/frontend/diagram/edge-panel.js +293 -0
- package/dist/src/frontend/diagram/edge-rendering.js +12 -0
- package/dist/src/frontend/diagram/evidence.js +146 -0
- package/dist/src/frontend/diagram/grid.js +78 -0
- package/dist/src/frontend/diagram/groups.js +102 -0
- package/dist/src/frontend/diagram/history.js +157 -0
- package/dist/src/frontend/diagram/image-name-modal.js +48 -0
- package/dist/src/frontend/diagram/image-upload.js +36 -0
- package/dist/src/frontend/diagram/label-editor.js +115 -0
- package/dist/src/frontend/diagram/link-panel.js +144 -0
- package/dist/src/frontend/diagram/main.js +364 -0
- package/dist/src/frontend/diagram/network.js +2214 -0
- package/dist/src/frontend/diagram/node-panel.js +389 -0
- package/dist/src/frontend/diagram/node-rendering.js +964 -0
- package/dist/src/frontend/diagram/persistence.js +168 -0
- package/dist/src/frontend/diagram/ports.js +421 -0
- package/dist/src/frontend/diagram/selection-overlay.js +387 -0
- package/dist/src/frontend/diagram/state.js +43 -0
- package/dist/src/frontend/diagram/t.js +3 -0
- package/dist/src/frontend/diagram/toast.js +21 -0
- package/dist/src/frontend/diagram/unlock-hold.js +206 -0
- package/dist/src/frontend/diagram/zoom.js +20 -0
- package/dist/src/frontend/diagram-link-modal.js +137 -0
- package/dist/src/frontend/diagram.html +1494 -0
- package/dist/src/frontend/documents.js +479 -0
- package/dist/src/frontend/export.js +338 -0
- package/dist/src/frontend/file-attach.js +178 -0
- package/dist/src/frontend/files-modal.js +243 -0
- package/dist/src/frontend/i18n/en.json +624 -0
- package/dist/src/frontend/i18n/fr.json +624 -0
- package/dist/src/frontend/i18n.js +32 -0
- package/dist/src/frontend/image-paste.js +126 -0
- package/dist/src/frontend/index.html +2806 -0
- package/dist/src/frontend/local-search.js +476 -0
- package/dist/src/frontend/metadata.js +318 -0
- package/dist/src/frontend/misc.js +92 -0
- package/dist/src/frontend/new-doc-modal.js +285 -0
- package/dist/src/frontend/new-folder-modal.js +169 -0
- package/dist/src/frontend/search.js +194 -0
- package/dist/src/frontend/shape-editor.html +685 -0
- package/dist/src/frontend/sidebar-helpers.js +96 -0
- package/dist/src/frontend/sidebar-resize.js +98 -0
- package/dist/src/frontend/sidebar.js +351 -0
- package/dist/src/frontend/snippet-detect.js +25 -0
- package/dist/src/frontend/snippet-table.js +85 -0
- package/dist/src/frontend/snippet-tree.js +94 -0
- package/dist/src/frontend/snippets.js +1146 -0
- package/dist/src/frontend/state.js +46 -0
- package/dist/src/frontend/utils.js +21 -0
- package/dist/src/frontend/validate.js +107 -0
- package/dist/src/frontend/vendor/wordcloud2.js +1187 -0
- package/dist/src/frontend/wordcloud.js +693 -0
- package/dist/src/lib/config.d.ts +26 -0
- package/dist/src/lib/config.d.ts.map +1 -0
- package/dist/src/lib/config.js +195 -0
- package/dist/src/lib/config.js.map +1 -0
- package/dist/src/lib/hash.d.ts +2 -0
- package/dist/src/lib/hash.d.ts.map +1 -0
- package/dist/src/lib/hash.js +18 -0
- package/dist/src/lib/hash.js.map +1 -0
- package/dist/src/lib/metadata.d.ts +31 -0
- package/dist/src/lib/metadata.d.ts.map +1 -0
- package/dist/src/lib/metadata.js +128 -0
- package/dist/src/lib/metadata.js.map +1 -0
- package/dist/src/lib/parser.d.ts +11 -0
- package/dist/src/lib/parser.d.ts.map +1 -0
- package/dist/src/lib/parser.js +111 -0
- package/dist/src/lib/parser.js.map +1 -0
- package/dist/src/lib/status.d.ts +9 -0
- package/dist/src/lib/status.d.ts.map +1 -0
- package/dist/src/lib/status.js +72 -0
- package/dist/src/lib/status.js.map +1 -0
- package/dist/src/mcp/server.d.ts +3 -0
- package/dist/src/mcp/server.d.ts.map +1 -0
- package/dist/src/mcp/server.js +2046 -0
- package/dist/src/mcp/server.js.map +1 -0
- package/dist/src/mcp/tools/diagrams.d.ts +82 -0
- package/dist/src/mcp/tools/diagrams.d.ts.map +1 -0
- package/dist/src/mcp/tools/diagrams.js +594 -0
- package/dist/src/mcp/tools/diagrams.js.map +1 -0
- package/dist/src/mcp/tools/documents.d.ts +44 -0
- package/dist/src/mcp/tools/documents.d.ts.map +1 -0
- package/dist/src/mcp/tools/documents.js +186 -0
- package/dist/src/mcp/tools/documents.js.map +1 -0
- package/dist/src/mcp/tools/git.d.ts +10 -0
- package/dist/src/mcp/tools/git.d.ts.map +1 -0
- package/dist/src/mcp/tools/git.js +217 -0
- package/dist/src/mcp/tools/git.js.map +1 -0
- package/dist/src/mcp/tools/metadata.d.ts +57 -0
- package/dist/src/mcp/tools/metadata.d.ts.map +1 -0
- package/dist/src/mcp/tools/metadata.js +222 -0
- package/dist/src/mcp/tools/metadata.js.map +1 -0
- package/dist/src/mcp/tools/source.d.ts +29 -0
- package/dist/src/mcp/tools/source.d.ts.map +1 -0
- package/dist/src/mcp/tools/source.js +196 -0
- package/dist/src/mcp/tools/source.js.map +1 -0
- package/dist/src/routes/annotations.d.ts +3 -0
- package/dist/src/routes/annotations.d.ts.map +1 -0
- package/dist/src/routes/annotations.js +83 -0
- package/dist/src/routes/annotations.js.map +1 -0
- package/dist/src/routes/browse-source.d.ts +3 -0
- package/dist/src/routes/browse-source.d.ts.map +1 -0
- package/dist/src/routes/browse-source.js +79 -0
- package/dist/src/routes/browse-source.js.map +1 -0
- package/dist/src/routes/browse.d.ts +3 -0
- package/dist/src/routes/browse.d.ts.map +1 -0
- package/dist/src/routes/browse.js +91 -0
- package/dist/src/routes/browse.js.map +1 -0
- package/dist/src/routes/config.d.ts +3 -0
- package/dist/src/routes/config.d.ts.map +1 -0
- package/dist/src/routes/config.js +145 -0
- package/dist/src/routes/config.js.map +1 -0
- package/dist/src/routes/context.d.ts +3 -0
- package/dist/src/routes/context.d.ts.map +1 -0
- package/dist/src/routes/context.js +287 -0
- package/dist/src/routes/context.js.map +1 -0
- package/dist/src/routes/diagrams.d.ts +3 -0
- package/dist/src/routes/diagrams.d.ts.map +1 -0
- package/dist/src/routes/diagrams.js +69 -0
- package/dist/src/routes/diagrams.js.map +1 -0
- package/dist/src/routes/documents.d.ts +11 -0
- package/dist/src/routes/documents.d.ts.map +1 -0
- package/dist/src/routes/documents.js +450 -0
- package/dist/src/routes/documents.js.map +1 -0
- package/dist/src/routes/export.d.ts +3 -0
- package/dist/src/routes/export.d.ts.map +1 -0
- package/dist/src/routes/export.js +280 -0
- package/dist/src/routes/export.js.map +1 -0
- package/dist/src/routes/files.d.ts +3 -0
- package/dist/src/routes/files.d.ts.map +1 -0
- package/dist/src/routes/files.js +180 -0
- package/dist/src/routes/files.js.map +1 -0
- package/dist/src/routes/images.d.ts +3 -0
- package/dist/src/routes/images.d.ts.map +1 -0
- package/dist/src/routes/images.js +49 -0
- package/dist/src/routes/images.js.map +1 -0
- package/dist/src/routes/metadata.d.ts +3 -0
- package/dist/src/routes/metadata.d.ts.map +1 -0
- package/dist/src/routes/metadata.js +131 -0
- package/dist/src/routes/metadata.js.map +1 -0
- package/dist/src/routes/shape-libraries.d.ts +3 -0
- package/dist/src/routes/shape-libraries.d.ts.map +1 -0
- package/dist/src/routes/shape-libraries.js +118 -0
- package/dist/src/routes/shape-libraries.js.map +1 -0
- package/dist/src/routes/wordcloud.d.ts +3 -0
- package/dist/src/routes/wordcloud.d.ts.map +1 -0
- package/dist/src/routes/wordcloud.js +95 -0
- package/dist/src/routes/wordcloud.js.map +1 -0
- package/dist/src/server.d.ts +7 -0
- package/dist/src/server.d.ts.map +1 -0
- package/dist/src/server.js +93 -0
- package/dist/src/server.js.map +1 -0
- package/dist/starter-doc/.living-doc.json +52 -0
- package/dist/starter-doc/ADRS/2026_01_01_[ADR]_example_architecture_decision.md +59 -0
- package/dist/starter-doc/AI/2026_01_01_how_to.md +112 -0
- package/dist/starter-doc/AI/PROJECT-INSTRUCTIONS.md +172 -0
- package/dist/starter-doc/AI/PROJECT-STACK.md +77 -0
- package/dist/starter-doc/AI/PROJECT-USEFUL-COMMANDS.md +80 -0
- package/dist/starter-doc/AI/default/AGENTS.md +31 -0
- package/dist/starter-doc/AI/default/CLAUDE.md +31 -0
- package/dist/starter-doc/AI/default/MEMORY.md +24 -0
- package/dist/starter-doc/AI/rules/no-magic-numbers.md +18 -0
- package/dist/starter-doc/AI/rules/track-current-work.md +23 -0
- package/dist/starter-doc/WORKLOG/current-task.md +57 -0
- package/dist/starter-doc-fr/.living-doc.json +52 -0
- package/dist/starter-doc-fr/ADRS/2026_01_01_[ADR]_example_architecture_decision.md +59 -0
- package/dist/starter-doc-fr/AI/2026_01_01_how_to.md +100 -0
- package/dist/starter-doc-fr/AI/PROJECT-INSTRUCTIONS.md +172 -0
- package/dist/starter-doc-fr/AI/PROJECT-STACK.md +77 -0
- package/dist/starter-doc-fr/AI/PROJECT-USEFUL-COMMANDS.md +80 -0
- package/dist/starter-doc-fr/AI/default/AGENTS.md +31 -0
- package/dist/starter-doc-fr/AI/default/CLAUDE.md +31 -0
- package/dist/starter-doc-fr/AI/default/MEMORY.md +24 -0
- package/dist/starter-doc-fr/AI/rules/no-magic-numbers.md +18 -0
- package/dist/starter-doc-fr/AI/rules/track-current-work.md +23 -0
- package/dist/starter-doc-fr/WORKLOG/current-task.md +57 -0
- package/images/living_documentation.jpg +0 -0
- package/images/readme-extra-files.png +0 -0
- package/images/readme-filename-pattern.png +0 -0
- package/images/readme-intelligent-search-demo.jpg +0 -0
- package/images/readme-sidebar.png +0 -0
- 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, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
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:  → 
|
|
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 ``;
|
|
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":";;;;;AAwLA,oCA+IC;AAvUD,qCAAoD;AACpD,4CAAoB;AACpB,gDAAwB;AACxB,wDAAgC;AAChC,mCAAgC;AAChC,0CAA2C;AAC3C,2CAAuE;AAEvE,kFAAkF;AAElF,SAAS,UAAU,CAAC,CAAS;IAC3B,OAAO,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AACtG,CAAC;AAED,SAAS,gBAAgB,CAAC,IAAY;IACpC,OAAO,IAAI;SACR,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC;SACpB,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC;SACzB,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;SACnB,OAAO,CAAC,gBAAgB,EAAE,EAAE,CAAC,IAAI,UAAU,CAAC;AACjD,CAAC;AAED,iEAAiE;AACjE,SAAS,QAAQ,CAAC,GAAoD;IACpE,OAAO,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,QAAQ,IAAI,SAAS,CAAC;AACtD,CAAC;AAED,+CAA+C;AAC/C,SAAS,QAAQ,CAAC,KAAa,EAAE,IAAY;IAC3C,OAAO;;;;;WAKE,UAAU,CAAC,KAAK,CAAC;;;;;;;;;;;;;;;;;;;;;;;EAuB1B,IAAI;;QAEE,CAAC;AACT,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,WAAW,CAAC,IAAY,EAAE,cAAuB;IACxD,MAAM,MAAM,GAAG,IAAI,GAAG,EAAU,CAAC;IACjC,MAAM,SAAS,GAAG,cAAc,CAAC,CAAC,CAAC,KAAK,cAAc,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;IAEjE,iFAAiF;IACjF,IAAI,GAAG,IAAI,CAAC,OAAO,CACjB,oEAAoE,EACpE,IAAI,CACL,CAAC;IAEF,sDAAsD;IACtD,IAAI,GAAG,IAAI,CAAC,OAAO,CACjB,kEAAkE,EAClE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE;QACrC,MAAM,QAAQ,GAAG,cAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QACzC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACrB,OAAO,GAAG,MAAM,QAAQ,SAAS,GAAG,QAAQ,GAAG,CAAC;IAClD,CAAC,CACF,CAAC;IACF,sEAAsE;IACtE,IAAI,GAAG,IAAI,CAAC,OAAO,CACjB,+DAA+D,EAC/D,CAAC,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE;QACzC,MAAM,QAAQ,GAAG,cAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QACzC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACrB,OAAO,GAAG,GAAG,GAAG,KAAK,QAAQ,SAAS,GAAG,QAAQ,GAAG,CAAC;IACvD,CAAC,CACF,CAAC;IAEF,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;AAC1B,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,eAAe,CAAC,EAAU,EAAE,YAAoB;IACvD,OAAO,EAAE,CAAC,OAAO,CACf,gCAAgC,EAChC,CAAC,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE;QACxB,IAAI,CAAC;YACH,gEAAgE;YAChE,IAAI,KAAa,CAAC;YAClB,IAAI,CAAC;gBACH,KAAK,GAAG,kBAAkB,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC,CAAC;YAC1D,CAAC;YAAC,MAAM,CAAC;gBACP,KAAK,GAAG,kBAAkB,CAAC,OAAO,CAAC,CAAC;YACtC,CAAC;YACD,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YACpC,MAAM,WAAW,GAAG,QAAQ,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;YAC3E,MAAM,OAAO,GAAO,QAAQ,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC;YACxE,MAAM,SAAS,GAAK,gBAAgB,CAAC,cAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,GAAG,KAAK,CAAC;YACrE,MAAM,GAAG,GAAG,WAAW,KAAK,YAAY;gBACtC,CAAC,CAAC,KAAK,SAAS,EAAE;gBAClB,CAAC,CAAC,MAAM,WAAW,IAAI,SAAS,EAAE,CAAC;YACrC,OAAO,IAAI,IAAI,KAAK,GAAG,GAAG,CAAC;QAC7B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,MAAM,CAAC,CAAC,oCAAoC;QACrD,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,SAAS,eAAe,CAAC,EAAU,EAAE,YAAoB;IACvD,MAAM,MAAM,GAAG,IAAI,GAAG,EAAU,CAAC;IAEjC,kEAAkE;IAClE,EAAE,GAAG,eAAe,CAAC,EAAE,EAAE,YAAY,CAAC,CAAC;IAEvC,gGAAgG;IAChG,EAAE,GAAG,EAAE,CAAC,OAAO,CACb,8DAA8D,EAC9D,CAAC,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE;QACxC,MAAM,QAAQ,GAAG,cAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QACzC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACrB,OAAO,KAAK,GAAG,OAAO,QAAQ,GAAG,IAAI,GAAG,CAAC;IAC3C,CAAC,CACF,CAAC;IAEF,iGAAiG;IACjG,EAAE,GAAG,EAAE,CAAC,OAAO,CACb,kEAAkE,EAClE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE;QACrC,MAAM,QAAQ,GAAG,cAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QACzC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACrB,OAAO,GAAG,MAAM,UAAU,QAAQ,GAAG,CAAC;IACxC,CAAC,CACF,CAAC;IACF,EAAE,GAAG,EAAE,CAAC,OAAO,CACb,+DAA+D,EAC/D,CAAC,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE;QACzC,MAAM,QAAQ,GAAG,cAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QACzC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACrB,OAAO,GAAG,GAAG,GAAG,KAAK,UAAU,QAAQ,GAAG,CAAC;IAC7C,CAAC,CACF,CAAC;IAEF,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC;AACxB,CAAC;AAED,iFAAiF;AAEjF,SAAgB,YAAY,CAAC,QAAgB;IAC3C,MAAM,MAAM,GAAG,IAAA,gBAAM,GAAE,CAAC;IAExB;;;;;;OAMG;IACH,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,EAAE;QACzD,MAAM,EAAE,OAAO,EAAE,IAAI,GAAG,QAAQ,EAAE,GAAG,GAAG,CAAC,IAA6C,CAAC;QACvF,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,CAAC;YACrB,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,qBAAqB,EAAE,CAAC,CAAC;QAChE,CAAC;QAED,MAAM,EAAE,UAAU,GAAG,EAAE,EAAE,eAAe,EAAE,kBAAkB,EAAE,GAAG,IAAA,mBAAU,EAAC,QAAQ,CAAC,CAAC;QACtF,MAAM,UAAU,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC,kBAAkB,EAAE,CAAC;QACpD,MAAM,IAAI,GAAG,IAAA,oBAAQ,EAAC,QAAQ,EAAE,UAAU,EAAE,eAAe,CAAC,CAAC;QAE7D,6BAA6B;QAC7B,MAAM,YAAY,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAC3E,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,CAAC;YACzB,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,yCAAyC,EAAE,CAAC,CAAC;QACpF,CAAC;QAED,GAAG,CAAC,SAAS,CAAC,cAAc,EAAE,iBAAiB,CAAC,CAAC;QACjD,GAAG,CAAC,SAAS,CAAC,qBAAqB,EAAE,mCAAmC,CAAC,CAAC;QAE1E,MAAM,OAAO,GAAG,IAAA,kBAAQ,EAAC,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;QACxD,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;YAC1B,OAAO,CAAC,KAAK,CAAC,yBAAyB,EAAE,GAAG,CAAC,CAAC;QAChD,CAAC,CAAC,CAAC;QACH,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAElB,4EAA4E;QAC5E,MAAM,WAAW,GAAG,IAAI,GAAG,EAAU,CAAC;QAEtC,KAAK,MAAM,GAAG,IAAI,YAAY,EAAE,CAAC;YAC/B,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;YAE5B,qBAAqB;YACrB,IAAI,QAAuB,CAAC;YAC5B,MAAM,EAAE,GAAG,kBAAkB,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACtC,IAAI,cAAI,CAAC,UAAU,CAAC,EAAE,CAAC,EAAE,CAAC;gBACxB,MAAM,GAAG,GAAG,EAAE,GAAG,KAAK,CAAC;gBACvB,QAAQ,GAAG,UAAU,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;YACnD,CAAC;iBAAM,CAAC;gBACN,QAAQ,GAAG,IAAA,wBAAY,EAAC,QAAQ,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC;YAClD,CAAC;YACD,IAAI,CAAC,QAAQ,IAAI,CAAC,YAAE,CAAC,UAAU,CAAC,QAAQ,CAAC;gBAAE,SAAS;YAEpD,MAAM,GAAG,GAAG,YAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;YAC/C,MAAM,QAAQ,GAAI,eAAM,CAAC,KAAK,CAAC,IAAA,4BAAgB,EAAC,GAAG,CAAC,EAAE,UAAU,CAAY;gBAC1E,qEAAqE;iBACpE,OAAO,CAAC,0DAA0D,EAAE,EAAE,CAAC,CAAC;YAE3E,MAAM,QAAQ,GAAM,gBAAgB,CAAC,cAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC;YACzE,MAAM,YAAY,GAAG,QAAQ,GAAG,OAAO,CAAC;YAExC,uFAAuF;YACvF,+FAA+F;YAC/F,MAAM,YAAY,GAAG,IAAI,KAAK,YAAY,CAAC;YAC3C,MAAM,cAAc,GAAG,YAAY,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC;YAC3D,MAAM,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,EAAE,GAAG,WAAW,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC;YAC9E,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC;YAEpD,OAAO,CAAC,MAAM,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,GAAG,KAAK,IAAI,YAAY,EAAE,EAAE,CAAC,CAAC;YAE/D,KAAK,MAAM,SAAS,IAAI,MAAM,EAAE,CAAC;gBAC/B,MAAM,QAAQ,GAAG,YAAY,CAAC,CAAC,CAAC,GAAG,KAAK,IAAI,QAAQ,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;gBAC/D,MAAM,GAAG,GAAG,GAAG,QAAQ,IAAI,SAAS,EAAE,CAAC;gBACvC,IAAI,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC;oBAAE,SAAS;gBACnC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;gBACrB,MAAM,SAAS,GAAG,cAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;gBAC3D,IAAI,YAAE,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;oBAC7B,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;gBACzC,CAAC;YACH,CAAC;QACH,CAAC;QAED,MAAM,OAAO,CAAC,QAAQ,EAAE,CAAC;IAC3B,CAAC,CAAC,CAAC;IAEH;;;;;;OAMG;IACH,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,EAAE;QAC7D,MAAM,EAAE,UAAU,GAAG,EAAE,EAAE,eAAe,EAAE,GAAG,IAAA,mBAAU,EAAC,QAAQ,CAAC,CAAC;QAClE,MAAM,IAAI,GAAG,IAAA,oBAAQ,EAAC,QAAQ,EAAE,UAAU,EAAE,eAAe,CAAC,CAAC;QAC7D,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YACjB,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,oBAAoB,EAAE,CAAC,CAAC;QAC/D,CAAC;QAED,GAAG,CAAC,SAAS,CAAC,cAAc,EAAE,iBAAiB,CAAC,CAAC;QACjD,GAAG,CAAC,SAAS,CAAC,qBAAqB,EAAE,4CAA4C,CAAC,CAAC;QAEnF,MAAM,OAAO,GAAG,IAAA,kBAAQ,EAAC,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;QACxD,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;YAC1B,OAAO,CAAC,KAAK,CAAC,yBAAyB,EAAE,GAAG,CAAC,CAAC;QAChD,CAAC,CAAC,CAAC;QACH,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAElB,MAAM,WAAW,GAAG,IAAI,GAAG,EAAU,CAAC;QAEtC,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;YAE5B,IAAI,QAAuB,CAAC;YAC5B,MAAM,EAAE,GAAG,kBAAkB,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACtC,IAAI,cAAI,CAAC,UAAU,CAAC,EAAE,CAAC,EAAE,CAAC;gBACxB,MAAM,GAAG,GAAG,EAAE,GAAG,KAAK,CAAC;gBACvB,QAAQ,GAAG,UAAU,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;YACnD,CAAC;iBAAM,CAAC;gBACN,QAAQ,GAAG,IAAA,wBAAY,EAAC,QAAQ,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC;YAClD,CAAC;YACD,IAAI,CAAC,QAAQ,IAAI,CAAC,YAAE,CAAC,UAAU,CAAC,QAAQ,CAAC;gBAAE,SAAS;YAEpD,MAAM,GAAG,GAAG,YAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;YAC/C,MAAM,EAAE,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,eAAe,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;YAChE,MAAM,QAAQ,GAAG,gBAAgB,CAAC,cAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC;YAEtE,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,EAAE,IAAI,EAAE,GAAG,KAAK,IAAI,QAAQ,KAAK,EAAE,CAAC,CAAC;YAEjE,KAAK,MAAM,SAAS,IAAI,MAAM,EAAE,CAAC;gBAC/B,MAAM,GAAG,GAAG,GAAG,KAAK,IAAI,SAAS,EAAE,CAAC;gBACpC,IAAI,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC;oBAAE,SAAS;gBACnC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;gBACrB,MAAM,SAAS,GAAG,cAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;gBAC3D,IAAI,YAAE,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;oBAC7B,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;gBACzC,CAAC;YACH,CAAC;QACH,CAAC;QAED,MAAM,OAAO,CAAC,QAAQ,EAAE,CAAC;IAC3B,CAAC,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -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 @@
|
|
|
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 @@
|
|
|
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"}
|