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.
- package/LICENSE +661 -0
- package/README.md +329 -0
- package/dist/bin/cli.d.ts +3 -0
- package/dist/bin/cli.d.ts.map +1 -0
- package/dist/bin/cli.js +62 -0
- package/dist/bin/cli.js.map +1 -0
- package/dist/src/frontend/admin.html +1073 -0
- package/dist/src/frontend/annotations.js +546 -0
- package/dist/src/frontend/boot.js +90 -0
- package/dist/src/frontend/config.js +19 -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 +172 -0
- package/dist/src/frontend/diagram/constants.js +109 -0
- package/dist/src/frontend/diagram/debug.js +43 -0
- package/dist/src/frontend/diagram/edge-panel.js +260 -0
- package/dist/src/frontend/diagram/edge-rendering.js +12 -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 +153 -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 +299 -0
- package/dist/src/frontend/diagram/network.js +1473 -0
- package/dist/src/frontend/diagram/node-panel.js +267 -0
- package/dist/src/frontend/diagram/node-rendering.js +773 -0
- package/dist/src/frontend/diagram/persistence.js +161 -0
- package/dist/src/frontend/diagram/ports.js +386 -0
- package/dist/src/frontend/diagram/selection-overlay.js +336 -0
- package/dist/src/frontend/diagram/state.js +39 -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 +182 -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 +1279 -0
- package/dist/src/frontend/documents.js +373 -0
- package/dist/src/frontend/export.js +338 -0
- package/dist/src/frontend/i18n/en.json +406 -0
- package/dist/src/frontend/i18n/fr.json +406 -0
- package/dist/src/frontend/i18n.js +32 -0
- package/dist/src/frontend/image-paste.js +101 -0
- package/dist/src/frontend/index.html +2314 -0
- package/dist/src/frontend/misc.js +25 -0
- package/dist/src/frontend/new-doc-modal.js +260 -0
- package/dist/src/frontend/new-folder-modal.js +174 -0
- package/dist/src/frontend/search.js +157 -0
- package/dist/src/frontend/sidebar-helpers.js +58 -0
- package/dist/src/frontend/sidebar.js +182 -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 +534 -0
- package/dist/src/frontend/state.js +28 -0
- package/dist/src/frontend/utils.js +21 -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 +17 -0
- package/dist/src/lib/config.d.ts.map +1 -0
- package/dist/src/lib/config.js +79 -0
- package/dist/src/lib/config.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/mcp/server.d.ts +3 -0
- package/dist/src/mcp/server.d.ts.map +1 -0
- package/dist/src/mcp/server.js +986 -0
- package/dist/src/mcp/server.js.map +1 -0
- package/dist/src/mcp/tools/diagrams.d.ts +44 -0
- package/dist/src/mcp/tools/diagrams.d.ts.map +1 -0
- package/dist/src/mcp/tools/diagrams.js +245 -0
- package/dist/src/mcp/tools/diagrams.js.map +1 -0
- package/dist/src/mcp/tools/documents.d.ts +26 -0
- package/dist/src/mcp/tools/documents.d.ts.map +1 -0
- package/dist/src/mcp/tools/documents.js +127 -0
- package/dist/src/mcp/tools/documents.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 +200 -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.d.ts +3 -0
- package/dist/src/routes/browse.d.ts.map +1 -0
- package/dist/src/routes/browse.js +75 -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 +97 -0
- package/dist/src/routes/config.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 +8 -0
- package/dist/src/routes/documents.d.ts.map +1 -0
- package/dist/src/routes/documents.js +332 -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 +277 -0
- package/dist/src/routes/export.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/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 +76 -0
- package/dist/src/server.js.map +1 -0
- package/dist/starting-doc/.annotations.json +3 -0
- package/dist/starting-doc/.diagrams.json +1884 -0
- package/dist/starting-doc/.living-doc.json +39 -0
- package/dist/starting-doc/1_tutorial/2026_04_11_13_25_[General]_crer_vos_dossiers.md +16 -0
- package/dist/starting-doc/1_tutorial/2026_04_11_18_58_[General]_creer_un_document_dans_un_dossier.md +9 -0
- package/dist/starting-doc/1_tutorial/2026_04_12_09_00_[General]_editer_et_sauvegarder.md +39 -0
- package/dist/starting-doc/1_tutorial/2026_04_12_10_00_[General]_utiliser_les_snippets.md +71 -0
- package/dist/starting-doc/2026_04_08_20_52_[General]_welcome.md +17 -0
- package/dist/starting-doc/2026_04_11_12_55_[General]_premiers_pas.md +271 -0
- package/dist/starting-doc/2_guide/2026_04_08_00_04_[DOCUMENT]_utilisation_des_images_plein_ecran_lien_clickable.md +40 -0
- package/dist/starting-doc/2_guide/2026_04_08_23_38_[Configuration]_demarrage_de_living_documentation.md +32 -0
- package/dist/starting-doc/2_guide/2026_04_09_09_00_[NAVIGATION]_recherche_plein_texte.md +65 -0
- package/dist/starting-doc/2_guide/2026_04_09_10_00_[EXPORT]_exporter_en_pdf.md +43 -0
- package/dist/starting-doc/2_guide/2026_04_09_11_00_[Configuration]_configurer_le_panneau_admin.md +55 -0
- package/dist/starting-doc/2_guide/2026_04_09_12_00_[Configuration]_extra_files.md +68 -0
- package/dist/starting-doc/2_guide/2026_04_09_13_00_[WORDCLOUD]_word_cloud.md +54 -0
- package/dist/starting-doc/2_guide/2026_04_09_14_00_[DIAGRAM]_creer_et_lier_un_diagramme.md +77 -0
- package/dist/starting-doc/3_concept/2026_04_08_20_58_[DOCUMENTING]_ADRS.md +20 -0
- package/dist/starting-doc/3_concept/2026_04_08_22_15_[DOCUMENTING]_living_documentation.md +17 -0
- package/dist/starting-doc/3_concept/2026_04_08_22_46_[METHODOLOGY]_diataxis_architecture_du_contenu.md +16 -0
- package/dist/starting-doc/4_reference/2026_04_08_23_14_[FUNDAMENTALS]_the_living_documentation_tool.md +41 -0
- package/dist/starting-doc/4_reference/2026_04_09_01_00_[REFERENCE]_raccourcis_clavier.md +61 -0
- package/dist/starting-doc/4_reference/2026_04_09_02_00_[REFERENCE]_tokens_pattern_nommage.md +75 -0
- package/dist/starting-doc/4_reference/2026_04_09_03_00_[REFERENCE]_types_de_snippets.md +68 -0
- package/dist/starting-doc/4_reference/2026_04_11_17_31_[FUNDAMENTALS]_architecturer_une_documentation.md +12 -0
- package/dist/starting-doc/4_reference/2026_04_12_14_07_[FUNDAMENTALS]_dossiers_et_catgories.md +89 -0
- package/dist/starting-doc/images/admin_screenshot.png +0 -0
- package/dist/starting-doc/images/ajout-document.png +0 -0
- package/dist/starting-doc/images/ajouter-document-categorie.png +0 -0
- package/dist/starting-doc/images/ajouter_un_document_dans_un_dossier.png +0 -0
- package/dist/starting-doc/images/architecturer_une_documentation_reference.png +0 -0
- package/dist/starting-doc/images/cr_er_un_document.png +0 -0
- package/dist/starting-doc/images/creation-nouveau-dossier.png +0 -0
- package/dist/starting-doc/images/creer-document-context-engineering.png +0 -0
- package/dist/starting-doc/images/creer-dossier-only-tutoriel.png +0 -0
- package/dist/starting-doc/images/creer-dossier-tutoriel.png +0 -0
- package/dist/starting-doc/images/creer-dossiers-done.png +0 -0
- package/dist/starting-doc/images/creer-un-document.png +0 -0
- package/dist/starting-doc/images/creer-vos-dossiers-tutoriel.png +0 -0
- package/dist/starting-doc/images/creer-vos-dossiers.png +0 -0
- package/dist/starting-doc/images/decouverte_adrs.png +0 -0
- package/dist/starting-doc/images/diataxis.png +0 -0
- package/dist/starting-doc/images/diataxis_callout.png +0 -0
- package/dist/starting-doc/images/document-cree.png +0 -0
- package/dist/starting-doc/images/liens_snippets.png +0 -0
- package/dist/starting-doc/images/living_documentation.png +0 -0
- package/dist/starting-doc/images/npm_logo.png +0 -0
- package/dist/starting-doc/images/popup-creer-document.png +0 -0
- package/dist/starting-doc/images/popup-creer-dossier.png +0 -0
- package/dist/starting-doc/images/popup-dossier-cree.png +0 -0
- package/dist/starting-doc/images/quatre-dossiers-crees.png +0 -0
- package/dist/starting-doc/images/screenshot-living-doc.png +0 -0
- package/dist/starting-doc/images/the_living_documentation_tool.png +0 -0
- package/package.json +49 -0
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
// ── Export modal (Markdown / HTML / PDF) + exportAllPDF ────────────────────
|
|
2
|
+
// Depends on globals from state.js (allDocs) and utils.js (esc).
|
|
3
|
+
|
|
4
|
+
async function exportAllPDF() {
|
|
5
|
+
const btn = document.getElementById("export-all-pdf-btn");
|
|
6
|
+
const originalHtml = btn.innerHTML;
|
|
7
|
+
btn.innerHTML = "⏳";
|
|
8
|
+
btn.disabled = true;
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
const docs =
|
|
12
|
+
allDocs && allDocs.length
|
|
13
|
+
? allDocs
|
|
14
|
+
: await fetch("/api/documents").then((r) => r.json());
|
|
15
|
+
|
|
16
|
+
// Batch-fetch full HTML content (5 at a time), keyed by id
|
|
17
|
+
const htmlById = {};
|
|
18
|
+
const batchSize = 5;
|
|
19
|
+
for (let i = 0; i < docs.length; i += batchSize) {
|
|
20
|
+
const batch = docs.slice(i, i + batchSize);
|
|
21
|
+
const results = await Promise.all(
|
|
22
|
+
batch.map((d) =>
|
|
23
|
+
fetch("/api/documents/" + d.id)
|
|
24
|
+
.then((r) => (r.ok ? r.json() : null))
|
|
25
|
+
.catch(() => null),
|
|
26
|
+
),
|
|
27
|
+
);
|
|
28
|
+
for (const r of results) {
|
|
29
|
+
if (r) htmlById[r.id ?? docs[i]?.id] = r;
|
|
30
|
+
}
|
|
31
|
+
// re-key by id using the batch metadata
|
|
32
|
+
batch.forEach((d, j) => {
|
|
33
|
+
if (results[j]) htmlById[d.id] = results[j];
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Build folder tree identical to sidebar's buildFolderTree
|
|
38
|
+
function buildTree(list) {
|
|
39
|
+
const root = { categories: {}, children: {} };
|
|
40
|
+
for (const doc of list) {
|
|
41
|
+
let node = root;
|
|
42
|
+
for (const seg of doc.folder || []) {
|
|
43
|
+
if (!node.children[seg])
|
|
44
|
+
node.children[seg] = { categories: {}, children: {} };
|
|
45
|
+
node = node.children[seg];
|
|
46
|
+
}
|
|
47
|
+
if (!node.categories[doc.category])
|
|
48
|
+
node.categories[doc.category] = [];
|
|
49
|
+
node.categories[doc.category].push(doc);
|
|
50
|
+
}
|
|
51
|
+
return root;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Traverse in sidebar order: General → subfolders (alpha) → other cats (alpha)
|
|
55
|
+
function orderedDocs(node) {
|
|
56
|
+
const result = [];
|
|
57
|
+
if (node.categories["General"])
|
|
58
|
+
result.push(...node.categories["General"]);
|
|
59
|
+
const childKeys = Object.keys(node.children).sort((a, b) =>
|
|
60
|
+
a.localeCompare(b),
|
|
61
|
+
);
|
|
62
|
+
for (const key of childKeys)
|
|
63
|
+
result.push(...orderedDocs(node.children[key]));
|
|
64
|
+
const otherCats = Object.keys(node.categories)
|
|
65
|
+
.filter((c) => c !== "General")
|
|
66
|
+
.sort((a, b) => a.localeCompare(b));
|
|
67
|
+
for (const cat of otherCats) result.push(...node.categories[cat]);
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Strip leading numeric prefix, same as folderLabel()
|
|
72
|
+
function fLabel(seg) {
|
|
73
|
+
return seg
|
|
74
|
+
.replace(/^\d+_/, "")
|
|
75
|
+
.replace(/[_-]+/g, " ")
|
|
76
|
+
.replace(/\b\w/g, (c) => c.toUpperCase());
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Build TOC HTML mirroring sidebar structure
|
|
80
|
+
function buildTocNode(node, depth) {
|
|
81
|
+
let html = "";
|
|
82
|
+
const indent = depth * 1.25;
|
|
83
|
+
const renderCatItems = (cat, catDocs) => {
|
|
84
|
+
const catLabel =
|
|
85
|
+
depth === 0 && cat === "General" ? "General" : cat;
|
|
86
|
+
html += `<li style="margin-left:${indent}rem;margin-top:${depth === 0 ? "0.75" : "0.4"}rem;">`;
|
|
87
|
+
html += `<span style="font-size:0.7rem;font-weight:700;text-transform:uppercase;letter-spacing:0.05em;color:#6b7280;">${catLabel}</span>`;
|
|
88
|
+
html += `<ul style="margin:0.2rem 0 0 0;padding:0;list-style:none;">`;
|
|
89
|
+
for (const doc of catDocs) {
|
|
90
|
+
html += `<li style="margin-left:${indent + 0.75}rem;padding:0.15rem 0;">`;
|
|
91
|
+
html += `<a href="#doc-${doc.id}" style="color:#1d4ed8;text-decoration:none;font-size:0.875rem;">${esc(doc.title)}</a>`;
|
|
92
|
+
if (doc.formattedDate)
|
|
93
|
+
html += `<span style="color:#9ca3af;font-size:0.7rem;margin-left:0.5rem;">${esc(doc.formattedDate)}</span>`;
|
|
94
|
+
html += `</li>`;
|
|
95
|
+
}
|
|
96
|
+
html += `</ul></li>`;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
if (node.categories["General"])
|
|
100
|
+
renderCatItems("General", node.categories["General"]);
|
|
101
|
+
|
|
102
|
+
const childKeys = Object.keys(node.children).sort((a, b) =>
|
|
103
|
+
a.localeCompare(b),
|
|
104
|
+
);
|
|
105
|
+
for (const key of childKeys) {
|
|
106
|
+
html += `<li style="margin-left:${indent}rem;margin-top:0.75rem;">`;
|
|
107
|
+
html += `<span style="font-size:0.7rem;font-weight:700;text-transform:uppercase;letter-spacing:0.05em;color:#7c3aed;">📁 ${fLabel(key)}</span>`;
|
|
108
|
+
html += `<ul style="margin:0.2rem 0 0 0;padding:0;list-style:none;">`;
|
|
109
|
+
html += buildTocNode(node.children[key], depth + 1);
|
|
110
|
+
html += `</ul></li>`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const otherCats = Object.keys(node.categories)
|
|
114
|
+
.filter((c) => c !== "General")
|
|
115
|
+
.sort((a, b) => a.localeCompare(b));
|
|
116
|
+
for (const cat of otherCats)
|
|
117
|
+
renderCatItems(cat, node.categories[cat]);
|
|
118
|
+
|
|
119
|
+
return html;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Rewrite ?doc=X links to internal anchors #doc-X
|
|
123
|
+
function rewriteDocLinks(html) {
|
|
124
|
+
return html.replace(
|
|
125
|
+
/href="[^"]*\?doc=([^"&#]+)(?:#[^"]*)?"/g,
|
|
126
|
+
(match, docId) => `href="#doc-${docId}"`,
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const appTitle = esc(document.title || "Living Documentation");
|
|
131
|
+
const tree = buildTree(docs);
|
|
132
|
+
const ordered = orderedDocs(tree);
|
|
133
|
+
|
|
134
|
+
const tocHtml = `
|
|
135
|
+
<div style="page-break-after:always;">
|
|
136
|
+
<h2 style="font-size:1.25rem;font-weight:700;color:#111827;margin-bottom:1.5rem;padding-bottom:0.5rem;border-bottom:2px solid #e5e7eb;">${window.t('pdf.toc_title')}</h2>
|
|
137
|
+
<ul style="list-style:none;padding:0;margin:0;">
|
|
138
|
+
${buildTocNode(tree, 0)}
|
|
139
|
+
</ul>
|
|
140
|
+
</div>`;
|
|
141
|
+
|
|
142
|
+
const sectionsHtml = ordered
|
|
143
|
+
.map((doc, idx) => {
|
|
144
|
+
const full = htmlById[doc.id];
|
|
145
|
+
if (!full) return "";
|
|
146
|
+
const html = rewriteDocLinks(full.html);
|
|
147
|
+
const folderPath = Array.isArray(doc.folder)
|
|
148
|
+
? doc.folder.map((f) => esc(fLabel(f))).join(" › ") + " › "
|
|
149
|
+
: "";
|
|
150
|
+
const meta = `${folderPath}${esc(doc.category)}${doc.formattedDate ? " · " + esc(doc.formattedDate) : ""}`;
|
|
151
|
+
const pageBreak =
|
|
152
|
+
idx > 0
|
|
153
|
+
? '<div style="page-break-before:always;height:0;"></div>'
|
|
154
|
+
: "";
|
|
155
|
+
return `${pageBreak}
|
|
156
|
+
<section id="doc-${doc.id}" style="padding:2rem 0;">
|
|
157
|
+
<div style="border-bottom:2px solid #e5e7eb;padding-bottom:0.75rem;margin-bottom:1.5rem;">
|
|
158
|
+
<div style="font-size:0.7rem;color:#6b7280;margin-bottom:0.25rem;">${meta}</div>
|
|
159
|
+
<h1 style="font-size:1.75rem;font-weight:800;color:#111827;margin:0;">${esc(doc.title)}</h1>
|
|
160
|
+
</div>
|
|
161
|
+
<div class="prose max-w-none">${html}</div>
|
|
162
|
+
</section>`;
|
|
163
|
+
})
|
|
164
|
+
.join("\n");
|
|
165
|
+
|
|
166
|
+
const fullHtml = `<!DOCTYPE html>
|
|
167
|
+
<html>
|
|
168
|
+
<head>
|
|
169
|
+
<meta charset="UTF-8">
|
|
170
|
+
<title>${appTitle} — Export PDF</title>
|
|
171
|
+
<script src="https://cdn.tailwindcss.com?plugins=typography"><\/script>
|
|
172
|
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css">
|
|
173
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"><\/script>
|
|
174
|
+
<style>
|
|
175
|
+
body { font-family: system-ui,sans-serif; max-width: 860px; margin: 0 auto; padding: 2rem; color: #111827; background: #fff; }
|
|
176
|
+
.prose pre { border-radius: 0.5rem; }
|
|
177
|
+
.prose img { max-width: 100%; }
|
|
178
|
+
@media print {
|
|
179
|
+
body { padding: 0; }
|
|
180
|
+
a { color: #1d4ed8; text-decoration: underline; }
|
|
181
|
+
}
|
|
182
|
+
</style>
|
|
183
|
+
</head>
|
|
184
|
+
<body>
|
|
185
|
+
<div style="text-align:center;margin-bottom:3rem;padding-bottom:2rem;border-bottom:3px solid #e5e7eb;page-break-after:avoid;">
|
|
186
|
+
<h1 style="font-size:2rem;font-weight:900;color:#111827;margin:0;">${appTitle}</h1>
|
|
187
|
+
</div>
|
|
188
|
+
${tocHtml}
|
|
189
|
+
${sectionsHtml}
|
|
190
|
+
<script>
|
|
191
|
+
document.querySelectorAll('pre code').forEach(b => hljs.highlightElement(b));
|
|
192
|
+
setTimeout(() => window.print(), 800);
|
|
193
|
+
<\/script>
|
|
194
|
+
</body>
|
|
195
|
+
</html>`;
|
|
196
|
+
|
|
197
|
+
const w = window.open("", "_blank");
|
|
198
|
+
if (!w) {
|
|
199
|
+
alert(window.t('error.pdf_popup'));
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
w.document.write(fullHtml);
|
|
203
|
+
w.document.close();
|
|
204
|
+
} catch (err) {
|
|
205
|
+
alert(window.t('error.pdf_export') + err.message);
|
|
206
|
+
} finally {
|
|
207
|
+
btn.innerHTML = originalHtml;
|
|
208
|
+
btn.disabled = false;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// ── Export modal (tabs + download actions) ──────────────────────────────────
|
|
213
|
+
let _exportCurrentTab = 'markdown';
|
|
214
|
+
|
|
215
|
+
function switchExportTab(tab) {
|
|
216
|
+
_exportCurrentTab = tab;
|
|
217
|
+
['markdown', 'html', 'pdf'].forEach((t) => {
|
|
218
|
+
const tabBtn = document.getElementById(`export-tab-${t}`);
|
|
219
|
+
const content = document.getElementById(`export-content-${t}`);
|
|
220
|
+
const isActive = t === tab;
|
|
221
|
+
tabBtn.classList.toggle('border-b-2', isActive);
|
|
222
|
+
tabBtn.classList.toggle('border-blue-500', isActive);
|
|
223
|
+
tabBtn.classList.toggle('text-blue-600', isActive);
|
|
224
|
+
tabBtn.classList.toggle('dark:text-blue-400', isActive);
|
|
225
|
+
tabBtn.classList.toggle('font-semibold', isActive);
|
|
226
|
+
tabBtn.classList.toggle('text-gray-500', !isActive);
|
|
227
|
+
tabBtn.classList.toggle('dark:text-gray-400', !isActive);
|
|
228
|
+
content.classList.toggle('hidden', !isActive);
|
|
229
|
+
});
|
|
230
|
+
// Folder list: visible for html only (lazy-loaded on first switch)
|
|
231
|
+
document.getElementById('export-folder-section').classList.toggle('hidden', tab !== 'html');
|
|
232
|
+
if (tab === 'html') loadExportFolders();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
let _exportFoldersLoaded = false;
|
|
236
|
+
|
|
237
|
+
async function loadExportFolders() {
|
|
238
|
+
if (_exportFoldersLoaded) return;
|
|
239
|
+
const list = document.getElementById('export-folder-list');
|
|
240
|
+
list.innerHTML = `<p class="text-sm text-gray-400 py-2">${window.t('common.loading')}</p>`;
|
|
241
|
+
try {
|
|
242
|
+
const docs = await fetch('/api/documents').then((r) => r.json());
|
|
243
|
+
const groups = new Set();
|
|
244
|
+
docs.forEach((doc) => groups.add(doc.folder?.[0] ?? doc.category ?? 'General'));
|
|
245
|
+
const sorted = [...groups].sort((a, b) => {
|
|
246
|
+
if (a === 'General') return -1;
|
|
247
|
+
if (b === 'General') return 1;
|
|
248
|
+
return a.localeCompare(b);
|
|
249
|
+
});
|
|
250
|
+
list.innerHTML = sorted.map((g) => `
|
|
251
|
+
<label class="flex items-center gap-3 px-1 py-1.5 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800 cursor-pointer">
|
|
252
|
+
<input type="checkbox" value="${g}"
|
|
253
|
+
class="w-4 h-4 rounded border-gray-300 dark:border-gray-600 text-blue-600 focus:ring-blue-500">
|
|
254
|
+
<span class="text-sm text-gray-700 dark:text-gray-300">${g}</span>
|
|
255
|
+
</label>`).join('');
|
|
256
|
+
_exportFoldersLoaded = true;
|
|
257
|
+
} catch {
|
|
258
|
+
list.innerHTML = `<p class="text-sm text-red-500">${window.t('common.error_prefix')}failed to load folders</p>`;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function openExportModal() {
|
|
263
|
+
document.getElementById('export-modal').classList.remove('hidden');
|
|
264
|
+
switchExportTab('markdown');
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function closeExportModal() {
|
|
268
|
+
document.getElementById('export-modal').classList.add('hidden');
|
|
269
|
+
_exportFoldersLoaded = false;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async function exportMarkdown() {
|
|
273
|
+
const btn = document.getElementById('export-md-btn');
|
|
274
|
+
const orig = btn.innerHTML;
|
|
275
|
+
btn.innerHTML = '⏳';
|
|
276
|
+
btn.disabled = true;
|
|
277
|
+
|
|
278
|
+
try {
|
|
279
|
+
const res = await fetch('/api/export/markdown', {
|
|
280
|
+
method: 'POST',
|
|
281
|
+
headers: { 'Content-Type': 'application/json' },
|
|
282
|
+
body: '{}',
|
|
283
|
+
});
|
|
284
|
+
if (!res.ok) throw new Error(await res.text());
|
|
285
|
+
const blob = await res.blob();
|
|
286
|
+
const url = URL.createObjectURL(blob);
|
|
287
|
+
const a = document.createElement('a');
|
|
288
|
+
a.href = url;
|
|
289
|
+
a.download = 'export-markdown.zip';
|
|
290
|
+
a.click();
|
|
291
|
+
URL.revokeObjectURL(url);
|
|
292
|
+
closeExportModal();
|
|
293
|
+
} catch (err) {
|
|
294
|
+
alert(window.t('common.error_prefix') + err.message);
|
|
295
|
+
} finally {
|
|
296
|
+
btn.innerHTML = orig;
|
|
297
|
+
btn.disabled = false;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
async function exportHtml(mode) {
|
|
302
|
+
const checked = [...document.querySelectorAll('#export-folder-list input:checked')];
|
|
303
|
+
const folders = checked.map((cb) => cb.value);
|
|
304
|
+
if (!folders.length) return;
|
|
305
|
+
|
|
306
|
+
const btnId = mode === 'confluence' ? 'export-confluence-btn' : 'export-notion-btn';
|
|
307
|
+
const btn = document.getElementById(btnId);
|
|
308
|
+
const orig = btn.innerHTML;
|
|
309
|
+
btn.innerHTML = '⏳';
|
|
310
|
+
btn.disabled = true;
|
|
311
|
+
|
|
312
|
+
try {
|
|
313
|
+
const res = await fetch('/api/export/html', {
|
|
314
|
+
method: 'POST',
|
|
315
|
+
headers: { 'Content-Type': 'application/json' },
|
|
316
|
+
body: JSON.stringify({ folders, mode }),
|
|
317
|
+
});
|
|
318
|
+
if (!res.ok) throw new Error(await res.text());
|
|
319
|
+
const blob = await res.blob();
|
|
320
|
+
const url = URL.createObjectURL(blob);
|
|
321
|
+
const a = document.createElement('a');
|
|
322
|
+
a.href = url;
|
|
323
|
+
a.download = `export-${mode}.zip`;
|
|
324
|
+
a.click();
|
|
325
|
+
URL.revokeObjectURL(url);
|
|
326
|
+
closeExportModal();
|
|
327
|
+
} catch (err) {
|
|
328
|
+
alert(window.t('common.error_prefix') + err.message);
|
|
329
|
+
} finally {
|
|
330
|
+
btn.innerHTML = orig;
|
|
331
|
+
btn.disabled = false;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
async function exportAllPdfFromModal() {
|
|
336
|
+
closeExportModal();
|
|
337
|
+
exportAllPDF();
|
|
338
|
+
}
|