living-documentation 1.0.1 → 1.2.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.
@@ -1,248 +1,318 @@
1
- <!DOCTYPE html>
1
+ <!doctype html>
2
2
  <html lang="en" class="h-full">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>Admin — Living Documentation</title>
7
-
8
- <script src="https://cdn.tailwindcss.com"></script>
9
-
10
- <script>
11
- tailwind.config = { darkMode: 'class', theme: { extend: {} } };
12
- </script>
13
-
14
- <style>
15
- .field-label { @apply block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1; }
16
- .field-input {
17
- @apply w-full px-3 py-2 rounded-lg border border-gray-200 dark:border-gray-700
18
- bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100
19
- placeholder:text-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 text-sm;
20
- }
21
- .field-hint { @apply mt-1 text-xs text-gray-400 dark:text-gray-500; }
22
- </style>
23
- </head>
24
-
25
- <body class="h-full bg-gray-50 dark:bg-gray-950 text-gray-900 dark:text-gray-100">
26
-
27
- <!-- ── Header ── -->
28
- <header class="flex items-center justify-between px-4 h-14 border-b border-gray-200 dark:border-gray-800
29
- bg-white dark:bg-gray-900 shadow-sm">
30
- <div class="flex items-center gap-3">
31
- <span class="text-blue-600 dark:text-blue-400 text-xl select-none" aria-hidden="true">&#128218;</span>
32
- <h1 class="text-base font-semibold tracking-tight">Admin Panel</h1>
33
- </div>
34
-
35
- <div class="flex items-center gap-3">
36
- <button id="dark-toggle" title="Toggle dark mode"
37
- class="p-2 rounded-lg text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors">
38
- <span id="dark-icon" class="text-lg leading-none">☾</span>
39
- </button>
40
- <span class="text-gray-300 dark:text-gray-700">|</span>
41
- <a href="/" class="text-blue-600 dark:text-blue-400 hover:underline text-sm">
42
- Back to docs &#8594;
43
- </a>
44
- </div>
45
- </header>
46
-
47
- <!-- ── Page ── -->
48
- <main class="max-w-2xl mx-auto px-6 py-10">
49
-
50
- <!-- Title -->
51
- <div class="mb-8">
52
- <h2 class="text-xl font-bold text-gray-900 dark:text-gray-50">Configuration</h2>
53
- <p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
54
- Settings are saved to <code class="text-xs bg-gray-100 dark:bg-gray-800 px-1.5 py-0.5 rounded">.living-doc.json</code>
55
- in your docs folder.
56
- </p>
57
- </div>
58
-
59
- <!-- ── Form ── -->
60
- <form id="config-form" class="space-y-6" novalidate>
61
-
62
- <!-- Read-only info card -->
63
- <div class="rounded-xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 p-5">
64
- <h3 class="text-sm font-semibold text-gray-700 dark:text-gray-300 mb-4">Server Info</h3>
65
- <dl class="grid grid-cols-2 gap-x-6 gap-y-3 text-sm">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Admin — Living Documentation</title>
7
+
8
+ <script src="https://cdn.tailwindcss.com"></script>
9
+
10
+ <script>
11
+ tailwind.config = { darkMode: "class", theme: { extend: {} } };
12
+ </script>
13
+
14
+ </head>
15
+
16
+ <body
17
+ class="h-full bg-gray-50 dark:bg-gray-950 text-gray-900 dark:text-gray-100"
18
+ >
19
+ <!-- ── Header ── -->
20
+ <header
21
+ class="flex items-center justify-between px-4 h-14 border-b border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 shadow-sm"
22
+ >
23
+ <div class="flex items-center gap-3">
24
+ <span
25
+ class="text-blue-600 dark:text-blue-400 text-xl select-none"
26
+ aria-hidden="true"
27
+ >&#128218;</span
28
+ >
29
+ <h1 class="text-base font-semibold tracking-tight">Admin Panel</h1>
30
+ </div>
31
+
32
+ <div class="flex items-center gap-3">
33
+ <button
34
+ id="dark-toggle"
35
+ title="Toggle dark mode"
36
+ class="p-2 rounded-lg text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
37
+ >
38
+ <span id="dark-icon" class="text-lg leading-none">☾</span>
39
+ </button>
40
+ <span class="text-gray-300 dark:text-gray-700">|</span>
41
+ <a
42
+ href="/"
43
+ class="text-blue-600 dark:text-blue-400 hover:underline text-sm"
44
+ >
45
+ Back to docs &#8594;
46
+ </a>
47
+ </div>
48
+ </header>
49
+
50
+ <!-- ── Page ── -->
51
+ <main class="max-w-6xl mx-auto px-6 py-10">
52
+
53
+ <!-- ── Two-column layout ── -->
54
+ <form id="config-form" novalidate>
55
+
56
+ <!-- Title + Save on same row -->
57
+ <div class="flex items-center justify-between mb-8">
66
58
  <div>
67
- <dt class="text-xs text-gray-400 uppercase tracking-wide mb-0.5">Docs Folder</dt>
68
- <dd id="info-folder" class="font-mono text-xs text-gray-700 dark:text-gray-300 break-all">—</dd>
59
+ <h2 class="text-xl font-bold text-gray-900 dark:text-gray-50">Configuration</h2>
60
+ <p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
61
+ Settings are saved to
62
+ <code class="text-xs bg-gray-100 dark:bg-gray-800 px-1.5 py-0.5 rounded">.living-doc.json</code>
63
+ in your docs folder.
64
+ </p>
69
65
  </div>
70
- <div>
71
- <dt class="text-xs text-gray-400 uppercase tracking-wide mb-0.5">Port</dt>
72
- <dd id="info-port" class="font-mono text-xs text-gray-700 dark:text-gray-300">—</dd>
66
+ <div class="flex items-center gap-4 shrink-0">
67
+ <div id="save-msg" class="text-sm"></div>
68
+ <button type="submit"
69
+ class="px-5 py-2 rounded-lg bg-blue-600 hover:bg-blue-700 text-white font-semibold text-sm transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-950">
70
+ Save changes
71
+ </button>
73
72
  </div>
74
- </dl>
75
- </div>
73
+ </div>
76
74
 
77
- <!-- Editable settings -->
78
- <div class="rounded-xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 p-5 space-y-5">
79
- <h3 class="text-sm font-semibold text-gray-700 dark:text-gray-300">Appearance & Metadata</h3>
75
+ <div class="grid grid-cols-2 gap-8 items-start">
80
76
 
81
- <!-- Site Title -->
82
- <div>
83
- <label class="field-label" for="field-title">Site Title</label>
84
- <input id="field-title" name="title" type="text" class="field-input"
85
- placeholder="Living Documentation" />
86
- <p class="field-hint">Displayed in the browser tab and sidebar header.</p>
87
- </div>
77
+ <!-- ── Left column ── -->
78
+ <div class="space-y-6">
88
79
 
89
- <!-- Theme -->
90
- <div>
91
- <label class="field-label" for="field-theme">Default Theme</label>
92
- <select id="field-theme" name="theme" class="field-input">
93
- <option value="system">System (follow OS preference)</option>
94
- <option value="light">Light</option>
95
- <option value="dark">Dark</option>
96
- </select>
97
- <p class="field-hint">Users can always override this with the toggle button.</p>
98
- </div>
80
+ <!-- Server Info -->
81
+ <div class="rounded-xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 p-5">
82
+ <h3 class="text-sm font-semibold text-gray-700 dark:text-gray-300 mb-4">Server Info</h3>
83
+ <dl class="grid grid-cols-[1fr_auto] gap-x-6 gap-y-3 text-sm">
84
+ <div>
85
+ <dt class="text-xs text-gray-400 uppercase tracking-wide mb-0.5">Docs Folder</dt>
86
+ <dd id="info-folder" class="font-mono text-xs text-gray-700 dark:text-gray-300 break-all">—</dd>
87
+ </div>
88
+ <div>
89
+ <dt class="text-xs text-gray-400 uppercase tracking-wide mb-0.5">Port</dt>
90
+ <dd id="info-port" class="font-mono text-xs text-gray-700 dark:text-gray-300">—</dd>
91
+ </div>
92
+ </dl>
93
+ </div>
94
+
95
+ <!-- Extra Files -->
96
+ <div class="rounded-xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 p-5 space-y-4">
97
+ <div>
98
+ <h3 class="text-sm font-semibold text-gray-700 dark:text-gray-300">General — Extra Files</h3>
99
+ <p class="mt-1 text-xs text-gray-500 dark:text-gray-400">Add Markdown files from outside the docs folder to the General section.</p>
100
+ </div>
101
+
102
+ <!-- File browser -->
103
+ <div class="rounded-lg border border-gray-200 dark:border-gray-700 overflow-hidden">
104
+ <div class="flex items-center gap-2 px-3 py-2 bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700">
105
+ <button id="browse-up" onclick="browseUp()"
106
+ class="text-xs text-blue-600 dark:text-blue-400 hover:underline disabled:opacity-30 disabled:pointer-events-none shrink-0">
107
+ &#8593; Up
108
+ </button>
109
+ <span id="browse-path" class="font-mono text-xs text-gray-400 dark:text-gray-500 truncate flex-1 text-right"></span>
110
+ </div>
111
+ <div id="browse-list" class="divide-y divide-gray-100 dark:divide-gray-800 max-h-52 overflow-y-auto"></div>
112
+ </div>
113
+
114
+ <!-- Added files -->
115
+ <div>
116
+ <p class="text-xs font-medium text-gray-500 dark:text-gray-400 mb-2">Added files</p>
117
+ <div id="extra-files-list" class="space-y-1"></div>
118
+ <p id="extra-files-empty" class="text-xs text-gray-400 italic">No extra files added yet.</p>
119
+ </div>
120
+ </div>
121
+
122
+ </div>
99
123
 
100
- <!-- Filename Pattern -->
101
- <div>
102
- <label class="field-label" for="field-pattern">Filename Pattern</label>
103
- <input id="field-pattern" name="filenamePattern" type="text" class="field-input"
104
- placeholder="YYYY_MM_DD_[Category]_title" />
105
- <p class="field-hint">
106
- Documents matching this pattern are parsed for date, category, and title.
107
- <span class="font-mono bg-gray-100 dark:bg-gray-800 px-1 rounded">YYYY_MM_DD_[Category]_title.md</span>
108
- </p>
124
+ <!-- ── Right column ── -->
125
+ <div class="space-y-6">
126
+
127
+ <!-- Appearance & Metadata -->
128
+ <div class="rounded-xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 divide-y divide-gray-100 dark:divide-gray-800">
129
+ <div class="px-5 py-4">
130
+ <h3 class="text-sm font-semibold text-gray-700 dark:text-gray-300">Appearance & Metadata</h3>
131
+ </div>
132
+ <div class="px-5 py-4 space-y-1.5">
133
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300" for="field-title">Site Title</label>
134
+ <input id="field-title" name="title" type="text"
135
+ class="w-full px-3 py-2 text-sm rounded-lg border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100 placeholder:text-gray-400 dark:placeholder:text-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-500"
136
+ placeholder="Living Documentation" />
137
+ <p class="text-xs text-gray-400 dark:text-gray-500">Displayed in the browser tab and sidebar header.</p>
138
+ </div>
139
+ <div class="px-5 py-4 space-y-1.5">
140
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300" for="field-theme">Default Theme</label>
141
+ <select id="field-theme" name="theme"
142
+ class="w-full px-3 py-2 text-sm rounded-lg border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500">
143
+ <option value="system">System (follow OS preference)</option>
144
+ <option value="light">Light</option>
145
+ <option value="dark">Dark</option>
146
+ </select>
147
+ <p class="text-xs text-gray-400 dark:text-gray-500">Users can always override with the toggle button.</p>
148
+ </div>
149
+ <div class="px-5 py-4 space-y-1.5">
150
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300" for="field-pattern">Filename Pattern</label>
151
+ <input id="field-pattern" name="filenamePattern" type="text"
152
+ class="w-full px-3 py-2 text-sm rounded-lg border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100 placeholder:text-gray-400 dark:placeholder:text-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-500"
153
+ placeholder="YYYY_MM_DD_[Category]_title" />
154
+ <p class="text-xs text-gray-400 dark:text-gray-500">
155
+ Parsed for date, category, and title.
156
+ <span class="font-mono bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 px-1 rounded">YYYY_MM_DD_[Category]_title.md</span>
157
+ </p>
158
+ </div>
159
+ </div>
160
+
161
+ <!-- Pattern Preview -->
162
+ <div id="pattern-preview" class="rounded-xl border border-blue-100 dark:border-blue-900 bg-blue-50 dark:bg-blue-950/30 p-5">
163
+ <h3 class="text-sm font-semibold text-blue-700 dark:text-blue-400 mb-3">Pattern Preview</h3>
164
+ <p class="text-xs text-gray-500 dark:text-gray-400 mb-3">How your pattern parses example filenames:</p>
165
+ <div id="preview-rows" class="space-y-2 text-sm"></div>
166
+ </div>
167
+
168
+ </div>
109
169
  </div>
110
- </div>
170
+ </form>
171
+ </main>
111
172
 
112
- <!-- Pattern preview -->
113
- <div id="pattern-preview"
114
- class="rounded-xl border border-blue-100 dark:border-blue-900 bg-blue-50 dark:bg-blue-950/30 p-5">
115
- <h3 class="text-sm font-semibold text-blue-700 dark:text-blue-400 mb-3">Pattern Preview</h3>
116
- <p class="text-xs text-gray-500 dark:text-gray-400 mb-3">How your pattern parses example filenames:</p>
117
- <div id="preview-rows" class="space-y-2 text-sm"></div>
118
- </div>
173
+ <script>
174
+ // ── Boot ───────────────────────────────────────────────────
175
+ document.addEventListener("DOMContentLoaded", async () => {
176
+ applyDarkMode(loadDarkPref());
177
+ setupDarkToggle();
178
+ await loadConfig();
179
+ setupPatternPreview();
180
+ document
181
+ .getElementById("config-form")
182
+ .addEventListener("submit", saveConfig);
183
+ });
119
184
 
120
- <!-- Submit -->
121
- <div class="flex items-center justify-between">
122
- <div id="save-msg" class="text-sm"></div>
123
- <button type="submit"
124
- class="px-5 py-2 rounded-lg bg-blue-600 hover:bg-blue-700 text-white font-semibold text-sm
125
- transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900">
126
- Save changes
127
- </button>
128
- </div>
129
- </form>
130
-
131
- </main>
132
-
133
- <script>
134
- // ── Boot ───────────────────────────────────────────────────
135
- document.addEventListener('DOMContentLoaded', async () => {
136
- applyDarkMode(loadDarkPref());
137
- setupDarkToggle();
138
- await loadConfig();
139
- setupPatternPreview();
140
- document.getElementById('config-form').addEventListener('submit', saveConfig);
141
- });
142
-
143
- // ── Dark mode ──────────────────────────────────────────────
144
- function loadDarkPref() {
145
- const saved = localStorage.getItem('ld-dark');
146
- if (saved !== null) return saved === 'true';
147
- return window.matchMedia('(prefers-color-scheme: dark)').matches;
148
- }
149
- function applyDarkMode(dark) {
150
- document.documentElement.classList.toggle('dark', dark);
151
- document.getElementById('dark-icon').textContent = dark ? '☀' : '☾';
152
- }
153
- function setupDarkToggle() {
154
- document.getElementById('dark-toggle').addEventListener('click', () => {
155
- const isDark = document.documentElement.classList.toggle('dark');
156
- localStorage.setItem('ld-dark', isDark);
157
- document.getElementById('dark-icon').textContent = isDark ? '☀' : '☾';
158
- });
159
- }
160
-
161
- // ── Config ─────────────────────────────────────────────────
162
- async function loadConfig() {
163
- try {
164
- const cfg = await fetch('/api/config').then(r => r.json());
165
- document.getElementById('info-folder').textContent = cfg.docsFolder || '—';
166
- document.getElementById('info-port').textContent = cfg.port || '—';
167
- document.getElementById('field-title').value = cfg.title || '';
168
- document.getElementById('field-theme').value = cfg.theme || 'system';
169
- document.getElementById('field-pattern').value = cfg.filenamePattern || '';
170
- updatePreview(cfg.filenamePattern);
171
- } catch {
172
- showMsg('Failed to load config.', 'error');
173
- }
174
- }
175
-
176
- async function saveConfig(e) {
177
- e.preventDefault();
178
- const payload = {
179
- title: document.getElementById('field-title').value.trim(),
180
- theme: document.getElementById('field-theme').value,
181
- filenamePattern: document.getElementById('field-pattern').value.trim(),
182
- };
183
-
184
- try {
185
- const res = await fetch('/api/config', {
186
- method: 'PUT',
187
- headers: { 'Content-Type': 'application/json' },
188
- body: JSON.stringify(payload),
189
- });
190
- if (!res.ok) throw new Error(await res.text());
191
- showMsg('Saved!', 'ok');
192
- } catch (err) {
193
- showMsg('Save failed: ' + err.message, 'error');
194
- }
195
- }
196
-
197
- function showMsg(text, type) {
198
- const el = document.getElementById('save-msg');
199
- el.textContent = text;
200
- el.className = 'text-sm ' + (type === 'ok'
201
- ? 'text-green-600 dark:text-green-400'
202
- : 'text-red-600 dark:text-red-400');
203
- if (type === 'ok') setTimeout(() => { el.textContent = ''; }, 3000);
204
- }
205
-
206
- // ── Pattern preview ────────────────────────────────────────
207
- const EXAMPLES = [
208
- '2024_01_15_[DevOps]_deploy_pipeline.md',
209
- '2023_11_03_[Frontend]_react_hooks_guide.md',
210
- '2025_06_20_meeting_notes.md',
211
- 'readme.md',
212
- ];
213
-
214
- // Parse like server does
215
- const FULL_PAT = /^(\d{4}_\d{2}_\d{2})_\[([^\]]+)\]_(.+)\.md$/i;
216
- const DATE_ONLY = /^(\d{4}_\d{2}_\d{2})_(.+)\.md$/i;
217
-
218
- function parsePreview(filename) {
219
- const full = filename.match(FULL_PAT);
220
- if (full) {
221
- const [, d, cat, t] = full;
222
- return { date: d.replace(/_/g,'-'), category: cat, title: titleCase(t), match: true };
223
- }
224
- const d = filename.match(DATE_ONLY);
225
- if (d) {
226
- return { date: d[1].replace(/_/g,'-'), category: 'Uncategorized', title: titleCase(d[2]), match: true };
227
- }
228
- return { date: null, category: 'Uncategorized', title: filename.replace('.md',''), match: false };
229
- }
230
-
231
- function titleCase(s) {
232
- return s.split('_').map(w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join(' ');
233
- }
234
-
235
- function updatePreview() {
236
- const rows = document.getElementById('preview-rows');
237
- rows.innerHTML = EXAMPLES.map(f => {
238
- const p = parsePreview(f);
239
- return `
185
+ // ── Dark mode ──────────────────────────────────────────────
186
+ function loadDarkPref() {
187
+ const saved = localStorage.getItem("ld-dark");
188
+ if (saved !== null) return saved === "true";
189
+ return window.matchMedia("(prefers-color-scheme: dark)").matches;
190
+ }
191
+ function applyDarkMode(dark) {
192
+ document.documentElement.classList.toggle("dark", dark);
193
+ document.getElementById("dark-icon").textContent = dark ? "☀" : "☾";
194
+ }
195
+ function setupDarkToggle() {
196
+ document.getElementById("dark-toggle").addEventListener("click", () => {
197
+ const isDark = document.documentElement.classList.toggle("dark");
198
+ localStorage.setItem("ld-dark", isDark);
199
+ document.getElementById("dark-icon").textContent = isDark ? "☀" : "☾";
200
+ });
201
+ }
202
+
203
+ // ── Config ─────────────────────────────────────────────────
204
+ async function loadConfig() {
205
+ try {
206
+ const cfg = await fetch("/api/config").then((r) => r.json());
207
+ document.getElementById("info-folder").textContent =
208
+ cfg.docsFolder || "—";
209
+ document.getElementById("info-port").textContent = cfg.port || "—";
210
+ document.getElementById("field-title").value = cfg.title || "";
211
+ document.getElementById("field-theme").value = cfg.theme || "system";
212
+ document.getElementById("field-pattern").value =
213
+ cfg.filenamePattern || "";
214
+ updatePreview(cfg.filenamePattern);
215
+ initExtraFiles(cfg);
216
+ } catch {
217
+ showMsg("Failed to load config.", "error");
218
+ }
219
+ }
220
+
221
+ async function saveConfig(e) {
222
+ e.preventDefault();
223
+ const payload = {
224
+ title: document.getElementById("field-title").value.trim(),
225
+ theme: document.getElementById("field-theme").value,
226
+ filenamePattern: document
227
+ .getElementById("field-pattern")
228
+ .value.trim(),
229
+ };
230
+
231
+ try {
232
+ const res = await fetch("/api/config", {
233
+ method: "PUT",
234
+ headers: { "Content-Type": "application/json" },
235
+ body: JSON.stringify(payload),
236
+ });
237
+ if (!res.ok) throw new Error(await res.text());
238
+ showMsg("Saved!", "ok");
239
+ } catch (err) {
240
+ showMsg("Save failed: " + err.message, "error");
241
+ }
242
+ }
243
+
244
+ function showMsg(text, type) {
245
+ const el = document.getElementById("save-msg");
246
+ el.textContent = text;
247
+ el.className =
248
+ "text-sm " +
249
+ (type === "ok"
250
+ ? "text-green-600 dark:text-green-400"
251
+ : "text-red-600 dark:text-red-400");
252
+ if (type === "ok")
253
+ setTimeout(() => {
254
+ el.textContent = "";
255
+ }, 3000);
256
+ }
257
+
258
+ // ── Pattern preview ────────────────────────────────────────
259
+ const EXAMPLES = [
260
+ "2024_01_15_[DevOps]_deploy_pipeline.md",
261
+ "2023_11_03_[Frontend]_react_hooks_guide.md",
262
+ "2025_06_20_meeting_notes.md",
263
+ "readme.md",
264
+ ];
265
+
266
+ // Parse like server does
267
+ const FULL_PAT = /^(\d{4}_\d{2}_\d{2})_\[([^\]]+)\]_(.+)\.md$/i;
268
+ const DATE_ONLY = /^(\d{4}_\d{2}_\d{2})_(.+)\.md$/i;
269
+
270
+ function parsePreview(filename) {
271
+ const full = filename.match(FULL_PAT);
272
+ if (full) {
273
+ const [, d, cat, t] = full;
274
+ return {
275
+ date: d.replace(/_/g, "-"),
276
+ category: cat,
277
+ title: titleCase(t),
278
+ match: true,
279
+ };
280
+ }
281
+ const d = filename.match(DATE_ONLY);
282
+ if (d) {
283
+ return {
284
+ date: d[1].replace(/_/g, "-"),
285
+ category: "General",
286
+ title: titleCase(d[2]),
287
+ match: true,
288
+ };
289
+ }
290
+ return {
291
+ date: null,
292
+ category: "General",
293
+ title: filename.replace(".md", ""),
294
+ match: false,
295
+ };
296
+ }
297
+
298
+ function titleCase(s) {
299
+ return s
300
+ .split("_")
301
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase())
302
+ .join(" ");
303
+ }
304
+
305
+ function updatePreview() {
306
+ const rows = document.getElementById("preview-rows");
307
+ rows.innerHTML = EXAMPLES.map((f) => {
308
+ const p = parsePreview(f);
309
+ return `
240
310
  <div class="rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 p-3">
241
311
  <p class="font-mono text-xs text-gray-500 dark:text-gray-400 mb-2 break-all">${esc(f)}</p>
242
312
  <div class="grid grid-cols-3 gap-2 text-xs">
243
313
  <div>
244
314
  <span class="text-gray-400 block mb-0.5">Date</span>
245
- <span class="${p.date ? 'text-green-600 dark:text-green-400' : 'text-gray-400'}">${p.date || 'none'}</span>
315
+ <span class="${p.date ? "text-green-600 dark:text-green-400" : "text-gray-400"}">${p.date || "none"}</span>
246
316
  </div>
247
317
  <div>
248
318
  <span class="text-gray-400 block mb-0.5">Category</span>
@@ -254,18 +324,181 @@ function updatePreview() {
254
324
  </div>
255
325
  </div>
256
326
  </div>`;
257
- }).join('');
258
- }
259
-
260
- function setupPatternPreview() {
261
- document.getElementById('field-pattern').addEventListener('input', updatePreview);
262
- }
263
-
264
- function esc(s) {
265
- return String(s)
266
- .replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')
267
- .replace(/"/g,'&quot;').replace(/'/g,'&#39;');
268
- }
269
- </script>
270
- </body>
327
+ }).join("");
328
+ }
329
+
330
+ function setupPatternPreview() {
331
+ document
332
+ .getElementById("field-pattern")
333
+ .addEventListener("input", updatePreview);
334
+ }
335
+
336
+ function esc(s) {
337
+ return String(s)
338
+ .replace(/&/g, "&amp;")
339
+ .replace(/</g, "&lt;")
340
+ .replace(/>/g, "&gt;")
341
+ .replace(/"/g, "&quot;")
342
+ .replace(/'/g, "&#39;");
343
+ }
344
+
345
+ // ── Extra Files / Browser ──────────────────────────────────
346
+ let extraFiles = [];
347
+ let browseParent = null;
348
+ let browseCurrent = "";
349
+ let docsFolder = "";
350
+
351
+ function initExtraFiles(cfg) {
352
+ extraFiles = cfg.extraFiles || [];
353
+ docsFolder = cfg.docsFolder || "";
354
+ renderExtraFiles();
355
+ loadBrowse(cfg.docsFolder || "/");
356
+ }
357
+
358
+ function relativeDisplay(filePath) {
359
+ if (!docsFolder) return filePath;
360
+ const fromParts = docsFolder.split("/").filter(Boolean);
361
+ const toParts = filePath.split("/").filter(Boolean);
362
+ let common = 0;
363
+ while (
364
+ common < fromParts.length &&
365
+ common < toParts.length &&
366
+ fromParts[common] === toParts[common]
367
+ )
368
+ common++;
369
+ const ups = fromParts.length - common;
370
+ const downs = toParts.slice(common).join("/");
371
+ const rel = "../".repeat(ups) + downs;
372
+ return "$DOCS_FOLDER/" + rel;
373
+ }
374
+
375
+ async function loadBrowse(dirPath) {
376
+ const list = document.getElementById("browse-list");
377
+ list.innerHTML =
378
+ '<p class="px-3 py-4 text-xs text-gray-400 text-center">Loading…</p>';
379
+ try {
380
+ const data = await fetch(
381
+ "/api/browse?path=" + encodeURIComponent(dirPath),
382
+ ).then((r) => r.json());
383
+ browseCurrent = data.current;
384
+ browseParent = data.parent;
385
+ document.getElementById("browse-path").textContent = relativeDisplay(data.current);
386
+ document.getElementById("browse-up").disabled = !data.parent;
387
+
388
+ const rows = [];
389
+
390
+ for (const dir of data.dirs) {
391
+ rows.push(`
392
+ <button data-path="${esc(dir.path)}" onclick="loadBrowse(this.dataset.path)"
393
+ class="w-full flex items-center gap-2 px-3 py-2 text-sm text-left
394
+ hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors">
395
+ <span class="text-gray-400 shrink-0">📁</span>
396
+ <span class="text-gray-700 dark:text-gray-300 truncate">${esc(dir.name)}</span>
397
+ </button>`);
398
+ }
399
+
400
+ for (const file of data.files) {
401
+ const added = extraFiles.includes(file.path);
402
+ rows.push(`
403
+ <div class="flex items-center justify-between gap-2 px-3 py-2 text-sm
404
+ hover:bg-gray-50 dark:hover:bg-gray-800">
405
+ <span class="flex items-center gap-2 text-gray-700 dark:text-gray-300 min-w-0">
406
+ <span class="text-gray-400 shrink-0">📄</span>
407
+ <span class="truncate">${esc(file.name)}</span>
408
+ </span>
409
+ ${
410
+ added
411
+ ? '<span class="text-xs text-green-600 dark:text-green-400 shrink-0">Added</span>'
412
+ : `<button data-path="${esc(file.path)}" onclick="addExtraFile(this.dataset.path)"
413
+ class="text-xs text-blue-600 dark:text-blue-400 hover:underline shrink-0">+ Add</button>`
414
+ }
415
+ </div>`);
416
+ }
417
+
418
+ if (rows.length === 0) {
419
+ list.innerHTML =
420
+ '<p class="px-3 py-4 text-xs text-gray-400 text-center">Empty directory</p>';
421
+ } else {
422
+ list.innerHTML = rows.join("");
423
+ }
424
+ } catch {
425
+ list.innerHTML =
426
+ '<p class="px-3 py-4 text-xs text-red-400 text-center">Cannot read directory</p>';
427
+ }
428
+ }
429
+
430
+ function browseUp() {
431
+ if (browseParent) loadBrowse(browseParent);
432
+ }
433
+
434
+ async function addExtraFile(filePath) {
435
+ if (extraFiles.includes(filePath)) return;
436
+ extraFiles = [...extraFiles, filePath];
437
+ await saveExtraFiles();
438
+ renderExtraFiles();
439
+ loadBrowse(browseCurrent);
440
+ }
441
+
442
+ async function removeExtraFile(filePath) {
443
+ extraFiles = extraFiles.filter((f) => f !== filePath);
444
+ await saveExtraFiles();
445
+ renderExtraFiles();
446
+ loadBrowse(browseCurrent);
447
+ }
448
+
449
+ async function saveExtraFiles() {
450
+ try {
451
+ const res = await fetch("/api/config", {
452
+ method: "PUT",
453
+ headers: { "Content-Type": "application/json" },
454
+ body: JSON.stringify({ extraFiles }),
455
+ });
456
+ if (!res.ok) throw new Error(await res.text());
457
+ } catch (err) {
458
+ showMsg("Failed to save: " + err.message, "error");
459
+ }
460
+ }
461
+
462
+ function moveExtraFile(index, direction) {
463
+ const target = index + direction;
464
+ if (target < 0 || target >= extraFiles.length) return;
465
+ const next = [...extraFiles];
466
+ [next[index], next[target]] = [next[target], next[index]];
467
+ extraFiles = next;
468
+ saveExtraFiles();
469
+ renderExtraFiles();
470
+ }
471
+
472
+ function renderExtraFiles() {
473
+ const list = document.getElementById("extra-files-list");
474
+ const empty = document.getElementById("extra-files-empty");
475
+ if (extraFiles.length === 0) {
476
+ list.innerHTML = "";
477
+ empty.style.display = "";
478
+ return;
479
+ }
480
+ empty.style.display = "none";
481
+ list.innerHTML = extraFiles
482
+ .map(
483
+ (f, i) => `
484
+ <div class="flex items-center gap-2 px-3 py-2 rounded-lg bg-gray-50 dark:bg-gray-800">
485
+ <div class="flex flex-col shrink-0">
486
+ <button data-i="${i}" data-d="-1" onclick="moveExtraFile(+this.dataset.i, +this.dataset.d)"
487
+ class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 leading-none disabled:opacity-20"
488
+ ${i === 0 ? "disabled" : ""}>&#8593;</button>
489
+ <button data-i="${i}" data-d="1" onclick="moveExtraFile(+this.dataset.i, +this.dataset.d)"
490
+ class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 leading-none disabled:opacity-20"
491
+ ${i === extraFiles.length - 1 ? "disabled" : ""}>&#8595;</button>
492
+ </div>
493
+ <div class="overflow-x-auto flex-1 min-w-0">
494
+ <span class="font-mono text-xs text-gray-600 dark:text-gray-400 whitespace-nowrap" title="${esc(f)}">${esc(relativeDisplay(f))}</span>
495
+ </div>
496
+ <button data-path="${esc(f)}" onclick="removeExtraFile(this.dataset.path)"
497
+ class="text-xs text-red-500 hover:text-red-700 dark:hover:text-red-400 shrink-0">Remove</button>
498
+ </div>`,
499
+ )
500
+ .join("");
501
+ }
502
+ </script>
503
+ </body>
271
504
  </html>
@@ -287,15 +287,15 @@ function renderSidebar(docs) {
287
287
  groups[doc.category].push(doc);
288
288
  }
289
289
 
290
- // Sort categories: Uncategorized last
290
+ // Sort categories: General first, then alphabetical
291
291
  const cats = Object.keys(groups).sort((a, b) => {
292
- if (a === 'Uncategorized') return 1;
293
- if (b === 'Uncategorized') return -1;
292
+ if (a === 'General') return -1;
293
+ if (b === 'General') return 1;
294
294
  return a.localeCompare(b);
295
295
  });
296
296
 
297
- // Auto-expand all initially
298
- if (expandedCategories.size === 0) cats.forEach(c => expandedCategories.add(c));
297
+ // Auto-expand only General initially
298
+ if (expandedCategories.size === 0) expandedCategories.add('General');
299
299
 
300
300
  tree.innerHTML = cats.map(cat => {
301
301
  const catDocs = groups[cat];
@@ -4,6 +4,7 @@ export interface LivingDocConfig {
4
4
  title: string;
5
5
  theme: 'light' | 'dark' | 'system';
6
6
  port: number;
7
+ extraFiles: string[];
7
8
  }
8
9
  export declare function getConfigPath(docsPath: string): string;
9
10
  export declare function readConfig(docsPath: string): LivingDocConfig;
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/lib/config.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC;IACnC,IAAI,EAAE,MAAM,CAAC;CACd;AAYD,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,eAAe,CAY5D;AAED,wBAAgB,WAAW,CACzB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,OAAO,CAAC,eAAe,CAAC,GAC9B,eAAe,CAMjB"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/lib/config.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAaD,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,eAAe,CAY5D;AAED,wBAAgB,WAAW,CACzB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,OAAO,CAAC,eAAe,CAAC,GAC9B,eAAe,CAMjB"}
@@ -15,6 +15,7 @@ const DEFAULTS = {
15
15
  title: 'Living Documentation',
16
16
  theme: 'system',
17
17
  port: 4321,
18
+ extraFiles: [],
18
19
  };
19
20
  function getConfigPath(docsPath) {
20
21
  return path_1.default.join(docsPath, CONFIG_FILENAME);
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../../../src/lib/config.ts"],"names":[],"mappings":";;;;;AAqBA,sCAEC;AAED,gCAYC;AAED,kCASC;AAhDD,4CAAoB;AACpB,gDAAwB;AAUxB,MAAM,eAAe,GAAG,kBAAkB,CAAC;AAE3C,MAAM,QAAQ,GAAoB;IAChC,UAAU,EAAE,GAAG;IACf,eAAe,EAAE,6BAA6B;IAC9C,KAAK,EAAE,sBAAsB;IAC7B,KAAK,EAAE,QAAQ;IACf,IAAI,EAAE,IAAI;CACX,CAAC;AAEF,SAAgB,aAAa,CAAC,QAAgB;IAC5C,OAAO,cAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,eAAe,CAAC,CAAC;AAC9C,CAAC;AAED,SAAgB,UAAU,CAAC,QAAgB;IACzC,MAAM,UAAU,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;IAC3C,IAAI,CAAC;QACH,IAAI,YAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YAC9B,MAAM,GAAG,GAAG,YAAE,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;YACjD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAA6B,CAAC;YAC3D,OAAO,EAAE,GAAG,QAAQ,EAAE,GAAG,MAAM,EAAE,CAAC;QACpC,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,mCAAmC;IACrC,CAAC;IACD,OAAO,EAAE,GAAG,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC;AAC/C,CAAC;AAED,SAAgB,WAAW,CACzB,QAAgB,EAChB,KAA+B;IAE/B,MAAM,OAAO,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;IACrC,MAAM,OAAO,GAAoB,EAAE,GAAG,OAAO,EAAE,GAAG,KAAK,EAAE,CAAC;IAC1D,MAAM,UAAU,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;IAC3C,YAAE,CAAC,aAAa,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;IACxE,OAAO,OAAO,CAAC;AACjB,CAAC"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../../src/lib/config.ts"],"names":[],"mappings":";;;;;AAuBA,sCAEC;AAED,gCAYC;AAED,kCASC;AAlDD,4CAAoB;AACpB,gDAAwB;AAWxB,MAAM,eAAe,GAAG,kBAAkB,CAAC;AAE3C,MAAM,QAAQ,GAAoB;IAChC,UAAU,EAAE,GAAG;IACf,eAAe,EAAE,6BAA6B;IAC9C,KAAK,EAAE,sBAAsB;IAC7B,KAAK,EAAE,QAAQ;IACf,IAAI,EAAE,IAAI;IACV,UAAU,EAAE,EAAE;CACf,CAAC;AAEF,SAAgB,aAAa,CAAC,QAAgB;IAC5C,OAAO,cAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,eAAe,CAAC,CAAC;AAC9C,CAAC;AAED,SAAgB,UAAU,CAAC,QAAgB;IACzC,MAAM,UAAU,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;IAC3C,IAAI,CAAC;QACH,IAAI,YAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YAC9B,MAAM,GAAG,GAAG,YAAE,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;YACjD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAA6B,CAAC;YAC3D,OAAO,EAAE,GAAG,QAAQ,EAAE,GAAG,MAAM,EAAE,CAAC;QACpC,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,mCAAmC;IACrC,CAAC;IACD,OAAO,EAAE,GAAG,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC;AAC/C,CAAC;AAED,SAAgB,WAAW,CACzB,QAAgB,EAChB,KAA+B;IAE/B,MAAM,OAAO,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;IACrC,MAAM,OAAO,GAAoB,EAAE,GAAG,OAAO,EAAE,GAAG,KAAK,EAAE,CAAC;IAC1D,MAAM,UAAU,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;IAC3C,YAAE,CAAC,aAAa,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;IACxE,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -44,7 +44,7 @@ function parseFilename(filename) {
44
44
  id,
45
45
  filename,
46
46
  title: titleCase(titlePart),
47
- category: 'Uncategorized',
47
+ category: 'General',
48
48
  date,
49
49
  formattedDate: formatDate(date),
50
50
  };
@@ -55,7 +55,7 @@ function parseFilename(filename) {
55
55
  id,
56
56
  filename,
57
57
  title: rawTitle.charAt(0).toUpperCase() + rawTitle.slice(1),
58
- category: 'Uncategorized',
58
+ category: 'General',
59
59
  date: null,
60
60
  formattedDate: null,
61
61
  };
@@ -1 +1 @@
1
- {"version":3,"file":"parser.js","sourceRoot":"","sources":["../../../src/lib/parser.ts"],"names":[],"mappings":";;AAiCA,sCAyCC;AAjED,gDAAgD;AAChD,MAAM,YAAY,GAAG,8CAA8C,CAAC;AAEpE,mDAAmD;AACnD,MAAM,iBAAiB,GAAG,iCAAiC,CAAC;AAE5D,SAAS,UAAU,CAAC,GAAW;IAC7B,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACtD,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;IACnD,OAAO,CAAC,CAAC,kBAAkB,CAAC,OAAO,EAAE;QACnC,IAAI,EAAE,SAAS;QACf,KAAK,EAAE,MAAM;QACb,GAAG,EAAE,SAAS;QACd,QAAQ,EAAE,KAAK;KAChB,CAAC,CAAC;AACL,CAAC;AAED,SAAS,SAAS,CAAC,GAAW;IAC5B,OAAO,GAAG;SACP,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;SACzE,IAAI,CAAC,GAAG,CAAC,CAAC;AACf,CAAC;AAED,SAAgB,aAAa,CAAC,QAAgB;IAC5C,MAAM,EAAE,GAAG,kBAAkB,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAErD,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;IAC1C,IAAI,IAAI,EAAE,CAAC;QACT,MAAM,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,CAAC,GAAG,IAAI,CAAC;QAC9C,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QACxC,OAAO;YACL,EAAE;YACF,QAAQ;YACR,KAAK,EAAE,SAAS,CAAC,SAAS,CAAC;YAC3B,QAAQ;YACR,IAAI;YACJ,aAAa,EAAE,UAAU,CAAC,IAAI,CAAC;SAChC,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;IACnD,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,CAAC,EAAE,OAAO,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAC;QACxC,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QACxC,OAAO;YACL,EAAE;YACF,QAAQ;YACR,KAAK,EAAE,SAAS,CAAC,SAAS,CAAC;YAC3B,QAAQ,EAAE,eAAe;YACzB,IAAI;YACJ,aAAa,EAAE,UAAU,CAAC,IAAI,CAAC;SAChC,CAAC;IACJ,CAAC;IAED,kCAAkC;IAClC,MAAM,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IAC9D,OAAO;QACL,EAAE;QACF,QAAQ;QACR,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;QAC3D,QAAQ,EAAE,eAAe;QACzB,IAAI,EAAE,IAAI;QACV,aAAa,EAAE,IAAI;KACpB,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"parser.js","sourceRoot":"","sources":["../../../src/lib/parser.ts"],"names":[],"mappings":";;AAiCA,sCAyCC;AAjED,gDAAgD;AAChD,MAAM,YAAY,GAAG,8CAA8C,CAAC;AAEpE,mDAAmD;AACnD,MAAM,iBAAiB,GAAG,iCAAiC,CAAC;AAE5D,SAAS,UAAU,CAAC,GAAW;IAC7B,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACtD,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;IACnD,OAAO,CAAC,CAAC,kBAAkB,CAAC,OAAO,EAAE;QACnC,IAAI,EAAE,SAAS;QACf,KAAK,EAAE,MAAM;QACb,GAAG,EAAE,SAAS;QACd,QAAQ,EAAE,KAAK;KAChB,CAAC,CAAC;AACL,CAAC;AAED,SAAS,SAAS,CAAC,GAAW;IAC5B,OAAO,GAAG;SACP,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;SACzE,IAAI,CAAC,GAAG,CAAC,CAAC;AACf,CAAC;AAED,SAAgB,aAAa,CAAC,QAAgB;IAC5C,MAAM,EAAE,GAAG,kBAAkB,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAErD,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;IAC1C,IAAI,IAAI,EAAE,CAAC;QACT,MAAM,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,CAAC,GAAG,IAAI,CAAC;QAC9C,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QACxC,OAAO;YACL,EAAE;YACF,QAAQ;YACR,KAAK,EAAE,SAAS,CAAC,SAAS,CAAC;YAC3B,QAAQ;YACR,IAAI;YACJ,aAAa,EAAE,UAAU,CAAC,IAAI,CAAC;SAChC,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;IACnD,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,CAAC,EAAE,OAAO,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAC;QACxC,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QACxC,OAAO;YACL,EAAE;YACF,QAAQ;YACR,KAAK,EAAE,SAAS,CAAC,SAAS,CAAC;YAC3B,QAAQ,EAAE,SAAS;YACnB,IAAI;YACJ,aAAa,EAAE,UAAU,CAAC,IAAI,CAAC;SAChC,CAAC;IACJ,CAAC;IAED,kCAAkC;IAClC,MAAM,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IAC9D,OAAO;QACL,EAAE;QACF,QAAQ;QACR,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;QAC3D,QAAQ,EAAE,SAAS;QACnB,IAAI,EAAE,IAAI;QACV,aAAa,EAAE,IAAI;KACpB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { Router } from 'express';
2
+ export declare function browseRouter(): Router;
3
+ //# sourceMappingURL=browse.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browse.d.ts","sourceRoot":"","sources":["../../../src/routes/browse.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAqB,MAAM,SAAS,CAAC;AAIpD,wBAAgB,YAAY,IAAI,MAAM,CA+BrC"}
@@ -0,0 +1,36 @@
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.browseRouter = browseRouter;
7
+ const express_1 = require("express");
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
10
+ function browseRouter() {
11
+ const router = (0, express_1.Router)();
12
+ // GET /api/browse?path=... — list dirs and .md files at a given path
13
+ router.get('/', (req, res) => {
14
+ const rawPath = req.query.path || '/';
15
+ const current = path_1.default.resolve(rawPath);
16
+ try {
17
+ const entries = fs_1.default.readdirSync(current, { withFileTypes: true });
18
+ const dirs = entries
19
+ .filter((e) => e.isDirectory() && !e.name.startsWith('.'))
20
+ .map((e) => ({ name: e.name, path: path_1.default.join(current, e.name) }))
21
+ .sort((a, b) => a.name.localeCompare(b.name));
22
+ const files = entries
23
+ .filter((e) => e.isFile() && e.name.toLowerCase().endsWith('.md'))
24
+ .map((e) => ({ name: e.name, path: path_1.default.join(current, e.name) }))
25
+ .sort((a, b) => a.name.localeCompare(b.name));
26
+ const parsed = path_1.default.parse(current);
27
+ const parent = current === parsed.root ? null : path_1.default.dirname(current);
28
+ res.json({ current, parent, dirs, files });
29
+ }
30
+ catch {
31
+ res.status(400).json({ error: 'Cannot read directory' });
32
+ }
33
+ });
34
+ return router;
35
+ }
36
+ //# sourceMappingURL=browse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browse.js","sourceRoot":"","sources":["../../../src/routes/browse.ts"],"names":[],"mappings":";;;;;AAIA,oCA+BC;AAnCD,qCAAoD;AACpD,4CAAoB;AACpB,gDAAwB;AAExB,SAAgB,YAAY;IAC1B,MAAM,MAAM,GAAG,IAAA,gBAAM,GAAE,CAAC;IAExB,qEAAqE;IACrE,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAY,EAAE,GAAa,EAAE,EAAE;QAC9C,MAAM,OAAO,GAAI,GAAG,CAAC,KAAK,CAAC,IAAe,IAAI,GAAG,CAAC;QAClD,MAAM,OAAO,GAAG,cAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAEtC,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,YAAE,CAAC,WAAW,CAAC,OAAO,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;YAEjE,MAAM,IAAI,GAAG,OAAO;iBACjB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;iBACzD,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,cAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;iBAChE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;YAEhD,MAAM,KAAK,GAAG,OAAO;iBAClB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;iBACjE,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,cAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;iBAChE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;YAEhD,MAAM,MAAM,GAAG,cAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YACnC,MAAM,MAAM,GAAG,OAAO,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,cAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;YAEtE,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QAC7C,CAAC;QAAC,MAAM,CAAC;YACP,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,uBAAuB,EAAE,CAAC,CAAC;QAC3D,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/routes/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAqB,MAAM,SAAS,CAAC;AAGpD,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAoCrD"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/routes/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAqB,MAAM,SAAS,CAAC;AAIpD,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CA6CrD"}
@@ -1,7 +1,11 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.configRouter = configRouter;
4
7
  const express_1 = require("express");
8
+ const path_1 = __importDefault(require("path"));
5
9
  const config_1 = require("../lib/config");
6
10
  function configRouter(docsPath) {
7
11
  const router = (0, express_1.Router)();
@@ -30,6 +34,12 @@ function configRouter(docsPath) {
30
34
  safe[key] = patch[key];
31
35
  }
32
36
  }
37
+ // extraFiles: only absolute .md paths
38
+ if ('extraFiles' in patch && Array.isArray(patch.extraFiles)) {
39
+ safe.extraFiles = patch.extraFiles.filter((f) => typeof f === 'string' &&
40
+ path_1.default.isAbsolute(f) &&
41
+ f.toLowerCase().endsWith('.md'));
42
+ }
33
43
  const updated = (0, config_1.writeConfig)(docsPath, safe);
34
44
  res.json(updated);
35
45
  }
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../../../src/routes/config.ts"],"names":[],"mappings":";;AAGA,oCAoCC;AAvCD,qCAAoD;AACpD,0CAAyE;AAEzE,SAAgB,YAAY,CAAC,QAAgB;IAC3C,MAAM,MAAM,GAAG,IAAA,gBAAM,GAAE,CAAC;IAExB,kBAAkB;IAClB,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,IAAa,EAAE,GAAa,EAAE,EAAE;QAC/C,IAAI,CAAC;YACH,GAAG,CAAC,IAAI,CAAC,IAAA,mBAAU,EAAC,QAAQ,CAAC,CAAC,CAAC;QACjC,CAAC;QAAC,MAAM,CAAC;YACP,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,uBAAuB,EAAE,CAAC,CAAC;QAC3D,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,kBAAkB;IAClB,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAY,EAAE,GAAa,EAAE,EAAE;QAC9C,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,GAAG,CAAC,IAAgC,CAAC;YACnD,6DAA6D;YAC7D,MAAM,OAAO,GAA8B;gBACzC,OAAO;gBACP,iBAAiB;gBACjB,OAAO;aACR,CAAC;YACF,MAAM,IAAI,GAA6B,EAAE,CAAC;YAC1C,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;gBAC1B,IAAI,GAAG,IAAI,KAAK,EAAE,CAAC;oBAChB,IAAgC,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;gBACtD,CAAC;YACH,CAAC;YACD,MAAM,OAAO,GAAG,IAAA,oBAAW,EAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;YAC5C,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACpB,CAAC;QAAC,MAAM,CAAC;YACP,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,yBAAyB,EAAE,CAAC,CAAC;QAC7D,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AAChB,CAAC"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../../src/routes/config.ts"],"names":[],"mappings":";;;;;AAIA,oCA6CC;AAjDD,qCAAoD;AACpD,gDAAwB;AACxB,0CAAyE;AAEzE,SAAgB,YAAY,CAAC,QAAgB;IAC3C,MAAM,MAAM,GAAG,IAAA,gBAAM,GAAE,CAAC;IAExB,kBAAkB;IAClB,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,IAAa,EAAE,GAAa,EAAE,EAAE;QAC/C,IAAI,CAAC;YACH,GAAG,CAAC,IAAI,CAAC,IAAA,mBAAU,EAAC,QAAQ,CAAC,CAAC,CAAC;QACjC,CAAC;QAAC,MAAM,CAAC;YACP,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,uBAAuB,EAAE,CAAC,CAAC;QAC3D,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,kBAAkB;IAClB,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAY,EAAE,GAAa,EAAE,EAAE;QAC9C,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,GAAG,CAAC,IAAgC,CAAC;YACnD,6DAA6D;YAC7D,MAAM,OAAO,GAA8B;gBACzC,OAAO;gBACP,iBAAiB;gBACjB,OAAO;aACR,CAAC;YACF,MAAM,IAAI,GAA6B,EAAE,CAAC;YAC1C,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;gBAC1B,IAAI,GAAG,IAAI,KAAK,EAAE,CAAC;oBAChB,IAAgC,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;gBACtD,CAAC;YACH,CAAC;YACD,sCAAsC;YACtC,IAAI,YAAY,IAAI,KAAK,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,CAAC;gBAC7D,IAAI,CAAC,UAAU,GAAI,KAAK,CAAC,UAAwB,CAAC,MAAM,CACtD,CAAC,CAAC,EAAe,EAAE,CACjB,OAAO,CAAC,KAAK,QAAQ;oBACrB,cAAI,CAAC,UAAU,CAAC,CAAC,CAAC;oBAClB,CAAC,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,CAClC,CAAC;YACJ,CAAC;YACD,MAAM,OAAO,GAAG,IAAA,oBAAW,EAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;YAC5C,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACpB,CAAC;QAAC,MAAM,CAAC;YACP,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,yBAAyB,EAAE,CAAC,CAAC;QAC7D,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"documents.d.ts","sourceRoot":"","sources":["../../../src/routes/documents.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAqB,MAAM,SAAS,CAAC;AA8BpD,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CA6ExD"}
1
+ {"version":3,"file":"documents.d.ts","sourceRoot":"","sources":["../../../src/routes/documents.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAqB,MAAM,SAAS,CAAC;AAwDpD,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAqGxD"}
@@ -9,13 +9,27 @@ const fs_1 = __importDefault(require("fs"));
9
9
  const path_1 = __importDefault(require("path"));
10
10
  const marked_1 = require("marked");
11
11
  const parser_1 = require("../lib/parser");
12
- function listDocs(docsPath) {
13
- const files = fs_1.default
12
+ const config_1 = require("../lib/config");
13
+ function listDocs(docsPath, extraFiles = []) {
14
+ // Extra files first, in config order, always General
15
+ const extraDocs = [];
16
+ for (const filePath of extraFiles) {
17
+ if (!filePath.endsWith('.md') || !fs_1.default.existsSync(filePath))
18
+ continue;
19
+ const filename = path_1.default.basename(filePath);
20
+ const meta = (0, parser_1.parseFilename)(filename);
21
+ extraDocs.push({
22
+ ...meta,
23
+ id: encodeURIComponent(filePath.slice(0, -3)),
24
+ category: 'General',
25
+ });
26
+ }
27
+ // Regular docs sorted: dated newest first, then undated alphabetically
28
+ const regularDocs = fs_1.default
14
29
  .readdirSync(docsPath)
15
- .filter((f) => f.toLowerCase().endsWith('.md'));
16
- const docs = files.map((filename) => (0, parser_1.parseFilename)(filename));
17
- // Sort: dated docs first (newest → oldest), then undated alphabetically
18
- docs.sort((a, b) => {
30
+ .filter((f) => f.toLowerCase().endsWith('.md'))
31
+ .map((filename) => (0, parser_1.parseFilename)(filename));
32
+ regularDocs.sort((a, b) => {
19
33
  if (a.date && b.date)
20
34
  return b.date.localeCompare(a.date);
21
35
  if (a.date)
@@ -24,7 +38,7 @@ function listDocs(docsPath) {
24
38
  return 1;
25
39
  return a.title.localeCompare(b.title);
26
40
  });
27
- return docs;
41
+ return [...extraDocs, ...regularDocs];
28
42
  }
29
43
  function safeFilePath(docsPath, filename) {
30
44
  const resolved = path_1.default.resolve(docsPath, filename);
@@ -32,12 +46,21 @@ function safeFilePath(docsPath, filename) {
32
46
  return null;
33
47
  return resolved;
34
48
  }
49
+ function resolveDocPath(docsPath, doc, extraFiles) {
50
+ const id = decodeURIComponent(doc.id);
51
+ if (path_1.default.isAbsolute(id)) {
52
+ const filePath = id + '.md';
53
+ return extraFiles.includes(filePath) ? filePath : null;
54
+ }
55
+ return safeFilePath(docsPath, doc.filename);
56
+ }
35
57
  function documentsRouter(docsPath) {
36
58
  const router = (0, express_1.Router)();
37
59
  // GET /api/documents — list all docs with metadata
38
60
  router.get('/', (_req, res) => {
39
61
  try {
40
- res.json(listDocs(docsPath));
62
+ const { extraFiles = [] } = (0, config_1.readConfig)(docsPath);
63
+ res.json(listDocs(docsPath, extraFiles));
41
64
  }
42
65
  catch {
43
66
  res.status(500).json({ error: 'Failed to list documents' });
@@ -49,10 +72,11 @@ function documentsRouter(docsPath) {
49
72
  if (!query)
50
73
  return res.json([]);
51
74
  try {
52
- const docs = listDocs(docsPath);
75
+ const { extraFiles = [] } = (0, config_1.readConfig)(docsPath);
76
+ const docs = listDocs(docsPath, extraFiles);
53
77
  const results = [];
54
78
  for (const doc of docs) {
55
- const filePath = safeFilePath(docsPath, doc.filename);
79
+ const filePath = resolveDocPath(docsPath, doc, extraFiles);
56
80
  if (!filePath || !fs_1.default.existsSync(filePath))
57
81
  continue;
58
82
  const content = fs_1.default.readFileSync(filePath, 'utf-8');
@@ -60,7 +84,6 @@ function documentsRouter(docsPath) {
60
84
  const inCategory = doc.category.toLowerCase().includes(query);
61
85
  const inContent = content.toLowerCase().includes(query);
62
86
  if (inTitle || inCategory || inContent) {
63
- // Extract a snippet around the first match in content
64
87
  let excerpt = '';
65
88
  if (inContent) {
66
89
  const idx = content.toLowerCase().indexOf(query);
@@ -83,6 +106,28 @@ function documentsRouter(docsPath) {
83
106
  // GET /api/documents/:id — get a single document (content + rendered HTML)
84
107
  router.get('/:id', async (req, res) => {
85
108
  const id = decodeURIComponent(req.params.id);
109
+ const { extraFiles = [] } = (0, config_1.readConfig)(docsPath);
110
+ // Extra file: id is an absolute path without .md
111
+ if (path_1.default.isAbsolute(id)) {
112
+ const filePath = id + '.md';
113
+ if (!extraFiles.includes(filePath)) {
114
+ return res.status(403).json({ error: 'Access denied' });
115
+ }
116
+ if (!fs_1.default.existsSync(filePath)) {
117
+ return res.status(404).json({ error: 'Document not found' });
118
+ }
119
+ try {
120
+ const content = fs_1.default.readFileSync(filePath, 'utf-8');
121
+ const meta = (0, parser_1.parseFilename)(path_1.default.basename(filePath));
122
+ const html = marked_1.marked.parse(content);
123
+ res.json({ ...meta, id: req.params.id, category: 'General', content, html });
124
+ }
125
+ catch {
126
+ res.status(500).json({ error: 'Failed to read document' });
127
+ }
128
+ return;
129
+ }
130
+ // Normal file inside docsPath
86
131
  const filename = id + '.md';
87
132
  const filePath = safeFilePath(docsPath, filename);
88
133
  if (!filePath) {
@@ -1 +1 @@
1
- {"version":3,"file":"documents.js","sourceRoot":"","sources":["../../../src/routes/documents.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"documents.js","sourceRoot":"","sources":["../../../src/routes/documents.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/server.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,wBAAsB,WAAW,CAAC,EAChC,QAAQ,EACR,IAAI,EACJ,WAAmB,GACpB,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CA2C/B"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/server.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,wBAAsB,WAAW,CAAC,EAChC,QAAQ,EACR,IAAI,EACJ,WAAmB,GACpB,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CA4C/B"}
@@ -9,6 +9,7 @@ const path_1 = __importDefault(require("path"));
9
9
  const child_process_1 = require("child_process");
10
10
  const documents_1 = require("./routes/documents");
11
11
  const config_1 = require("./routes/config");
12
+ const browse_1 = require("./routes/browse");
12
13
  const config_2 = require("./lib/config");
13
14
  async function startServer({ docsPath, port, openBrowser = false, }) {
14
15
  const app = (0, express_1.default)();
@@ -18,6 +19,7 @@ async function startServer({ docsPath, port, openBrowser = false, }) {
18
19
  // API
19
20
  app.use('/api/documents', (0, documents_1.documentsRouter)(docsPath));
20
21
  app.use('/api/config', (0, config_1.configRouter)(docsPath));
22
+ app.use('/api/browse', (0, browse_1.browseRouter)());
21
23
  // Static frontend assets
22
24
  const frontendPath = path_1.default.join(__dirname, 'frontend');
23
25
  app.use(express_1.default.static(frontendPath));
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/server.ts"],"names":[],"mappings":";;;;;AAaA,kCA+CC;AA5DD,sDAA8B;AAC9B,gDAAwB;AACxB,iDAAqC;AACrC,kDAAqD;AACrD,4CAA+C;AAC/C,yCAA2C;AAQpC,KAAK,UAAU,WAAW,CAAC,EAChC,QAAQ,EACR,IAAI,EACJ,WAAW,GAAG,KAAK,GACL;IACd,MAAM,GAAG,GAAG,IAAA,iBAAO,GAAE,CAAC;IAEtB,GAAG,CAAC,GAAG,CAAC,iBAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IAExB,4CAA4C;IAC5C,IAAA,oBAAW,EAAC,QAAQ,EAAE,EAAE,UAAU,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IAEtD,MAAM;IACN,GAAG,CAAC,GAAG,CAAC,gBAAgB,EAAE,IAAA,2BAAe,EAAC,QAAQ,CAAC,CAAC,CAAC;IACrD,GAAG,CAAC,GAAG,CAAC,aAAa,EAAE,IAAA,qBAAY,EAAC,QAAQ,CAAC,CAAC,CAAC;IAE/C,yBAAyB;IACzB,MAAM,YAAY,GAAG,cAAI,CAAC,IAAI,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;IACtD,GAAG,CAAC,GAAG,CAAC,iBAAO,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC;IAEtC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,CACzB,GAAG,CAAC,QAAQ,CAAC,cAAI,CAAC,IAAI,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC,CACpD,CAAC;IACF,GAAG,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,CAC9B,GAAG,CAAC,QAAQ,CAAC,cAAI,CAAC,IAAI,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC,CACpD,CAAC;IAEF,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE;YACpB,MAAM,GAAG,GAAG,oBAAoB,IAAI,EAAE,CAAC;YACvC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAChB,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;YACtC,OAAO,CAAC,GAAG,CAAC,qCAAqC,CAAC,CAAC;YACnD,OAAO,CAAC,GAAG,CAAC,cAAc,GAAG,EAAE,CAAC,CAAC;YACjC,OAAO,CAAC,GAAG,CAAC,cAAc,GAAG,QAAQ,CAAC,CAAC;YACvC,OAAO,CAAC,GAAG,CAAC,cAAc,QAAQ,EAAE,CAAC,CAAC;YACtC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAChB,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC;YACvC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAEhB,IAAI,WAAW,EAAE,CAAC;gBAChB,OAAO,CAAC,GAAG,CAAC,CAAC;YACf,CAAC;YAED,OAAO,EAAE,CAAC;QACZ,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,OAAO,CAAC,GAAW;IAC1B,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;IAClC,IAAI,QAAQ,KAAK,QAAQ;QAAE,IAAA,oBAAI,EAAC,SAAS,GAAG,GAAG,CAAC,CAAC;SAC5C,IAAI,QAAQ,KAAK,OAAO;QAAE,IAAA,oBAAI,EAAC,aAAa,GAAG,GAAG,CAAC,CAAC;;QACpD,IAAA,oBAAI,EAAC,aAAa,GAAG,GAAG,CAAC,CAAC;AACjC,CAAC"}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/server.ts"],"names":[],"mappings":";;;;;AAcA,kCAgDC;AA9DD,sDAA8B;AAC9B,gDAAwB;AACxB,iDAAqC;AACrC,kDAAqD;AACrD,4CAA+C;AAC/C,4CAA+C;AAC/C,yCAA2C;AAQpC,KAAK,UAAU,WAAW,CAAC,EAChC,QAAQ,EACR,IAAI,EACJ,WAAW,GAAG,KAAK,GACL;IACd,MAAM,GAAG,GAAG,IAAA,iBAAO,GAAE,CAAC;IAEtB,GAAG,CAAC,GAAG,CAAC,iBAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IAExB,4CAA4C;IAC5C,IAAA,oBAAW,EAAC,QAAQ,EAAE,EAAE,UAAU,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IAEtD,MAAM;IACN,GAAG,CAAC,GAAG,CAAC,gBAAgB,EAAE,IAAA,2BAAe,EAAC,QAAQ,CAAC,CAAC,CAAC;IACrD,GAAG,CAAC,GAAG,CAAC,aAAa,EAAE,IAAA,qBAAY,EAAC,QAAQ,CAAC,CAAC,CAAC;IAC/C,GAAG,CAAC,GAAG,CAAC,aAAa,EAAE,IAAA,qBAAY,GAAE,CAAC,CAAC;IAEvC,yBAAyB;IACzB,MAAM,YAAY,GAAG,cAAI,CAAC,IAAI,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;IACtD,GAAG,CAAC,GAAG,CAAC,iBAAO,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC;IAEtC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,CACzB,GAAG,CAAC,QAAQ,CAAC,cAAI,CAAC,IAAI,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC,CACpD,CAAC;IACF,GAAG,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,CAC9B,GAAG,CAAC,QAAQ,CAAC,cAAI,CAAC,IAAI,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC,CACpD,CAAC;IAEF,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE;YACpB,MAAM,GAAG,GAAG,oBAAoB,IAAI,EAAE,CAAC;YACvC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAChB,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;YACtC,OAAO,CAAC,GAAG,CAAC,qCAAqC,CAAC,CAAC;YACnD,OAAO,CAAC,GAAG,CAAC,cAAc,GAAG,EAAE,CAAC,CAAC;YACjC,OAAO,CAAC,GAAG,CAAC,cAAc,GAAG,QAAQ,CAAC,CAAC;YACvC,OAAO,CAAC,GAAG,CAAC,cAAc,QAAQ,EAAE,CAAC,CAAC;YACtC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAChB,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC;YACvC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAEhB,IAAI,WAAW,EAAE,CAAC;gBAChB,OAAO,CAAC,GAAG,CAAC,CAAC;YACf,CAAC;YAED,OAAO,EAAE,CAAC;QACZ,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,OAAO,CAAC,GAAW;IAC1B,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;IAClC,IAAI,QAAQ,KAAK,QAAQ;QAAE,IAAA,oBAAI,EAAC,SAAS,GAAG,GAAG,CAAC,CAAC;SAC5C,IAAI,QAAQ,KAAK,OAAO;QAAE,IAAA,oBAAI,EAAC,aAAa,GAAG,GAAG,CAAC,CAAC;;QACpD,IAAA,oBAAI,EAAC,aAAa,GAAG,GAAG,CAAC,CAAC;AACjC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "living-documentation",
3
- "version": "1.0.1",
3
+ "version": "1.2.0",
4
4
  "description": "A CLI tool that serves a local Markdown documentation viewer",
5
5
  "main": "dist/src/server.js",
6
6
  "bin": {
@@ -12,7 +12,7 @@
12
12
  ],
13
13
  "scripts": {
14
14
  "build": "tsc && node scripts/copy-assets.js && chmod +x dist/bin/cli.js",
15
- "dev": "ts-node bin/cli.ts",
15
+ "dev": "nodemon --watch src --watch bin --ext ts,html --exec 'ts-node bin/cli.ts'",
16
16
  "start": "node dist/bin/cli.js",
17
17
  "prepublishOnly": "npm run build"
18
18
  },
@@ -32,6 +32,7 @@
32
32
  "devDependencies": {
33
33
  "@types/express": "^4.17.21",
34
34
  "@types/node": "^20.14.0",
35
+ "nodemon": "^3.1.14",
35
36
  "ts-node": "^10.9.2",
36
37
  "typescript": "^5.4.5"
37
38
  },