living-documentation 1.0.1 → 1.1.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,402 @@
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>
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
7
 
8
- <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://cdn.tailwindcss.com"></script>
9
9
 
10
- <script>
11
- tailwind.config = { darkMode: 'class', theme: { extend: {} } };
12
- </script>
10
+ <script>
11
+ tailwind.config = { darkMode: "class", theme: { extend: {} } };
12
+ </script>
13
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
14
+ <style>
15
+ .field-label {
16
+ @apply block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1;
17
+ }
18
+ .field-input {
19
+ @apply w-full px-3 py-2 rounded-lg border border-gray-200 dark:border-gray-700
18
20
  bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100
19
21
  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">
22
+ }
23
+ .field-hint {
24
+ @apply mt-1 text-xs text-gray-400 dark:text-gray-500;
25
+ }
26
+ </style>
27
+ </head>
28
+
29
+ <body
30
+ class="h-full bg-gray-50 dark:bg-gray-950 text-gray-900 dark:text-gray-100"
31
+ >
32
+ <!-- ── Header ── -->
33
+ <header
34
+ 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"
35
+ >
36
+ <div class="flex items-center gap-3">
37
+ <span
38
+ class="text-blue-600 dark:text-blue-400 text-xl select-none"
39
+ aria-hidden="true"
40
+ >&#128218;</span
41
+ >
42
+ <h1 class="text-base font-semibold tracking-tight">Admin Panel</h1>
43
+ </div>
44
+
45
+ <div class="flex items-center gap-3">
46
+ <button
47
+ id="dark-toggle"
48
+ title="Toggle dark mode"
49
+ class="p-2 rounded-lg text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
50
+ >
51
+ <span id="dark-icon" class="text-lg leading-none">☾</span>
52
+ </button>
53
+ <span class="text-gray-300 dark:text-gray-700">|</span>
54
+ <a
55
+ href="/"
56
+ class="text-blue-600 dark:text-blue-400 hover:underline text-sm"
57
+ >
58
+ Back to docs &#8594;
59
+ </a>
60
+ </div>
61
+ </header>
62
+
63
+ <!-- ── Page ── -->
64
+ <main class="max-w-2xl mx-auto px-6 py-10">
65
+ <!-- Title -->
66
+ <div class="mb-8">
67
+ <h2 class="text-xl font-bold text-gray-900 dark:text-gray-50">
68
+ Configuration
69
+ </h2>
70
+ <p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
71
+ Settings are saved to
72
+ <code
73
+ class="text-xs bg-gray-100 dark:bg-gray-800 px-1.5 py-0.5 rounded"
74
+ >.living-doc.json</code
75
+ >
76
+ in your docs folder.
77
+ </p>
78
+ </div>
79
+
80
+ <!-- ── Form ── -->
81
+ <form id="config-form" class="space-y-6" novalidate>
82
+ <!-- Read-only info card -->
83
+ <div
84
+ class="rounded-xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 p-5"
85
+ >
86
+ <h3
87
+ class="text-sm font-semibold text-gray-700 dark:text-gray-300 mb-4"
88
+ >
89
+ Server Info
90
+ </h3>
91
+ <dl class="grid grid-cols-[1fr_auto] gap-x-6 gap-y-3 text-sm">
92
+ <div>
93
+ <dt class="text-xs text-gray-400 uppercase tracking-wide mb-0.5">
94
+ Docs Folder
95
+ </dt>
96
+ <dd
97
+ id="info-folder"
98
+ class="font-mono text-xs text-gray-700 dark:text-gray-300 break-all"
99
+ >
100
+
101
+ </dd>
102
+ </div>
103
+ <div>
104
+ <dt class="text-xs text-gray-400 uppercase tracking-wide mb-0.5">
105
+ Port
106
+ </dt>
107
+ <dd
108
+ id="info-port"
109
+ class="font-mono text-xs text-gray-700 dark:text-gray-300"
110
+ >
111
+
112
+ </dd>
113
+ </div>
114
+ </dl>
115
+ </div>
116
+
117
+ <!-- Extra Files -->
118
+ <div
119
+ class="rounded-xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 p-5 space-y-4"
120
+ >
66
121
  <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>
122
+ <h3 class="text-sm font-semibold text-gray-700 dark:text-gray-300">
123
+ General Extra Files
124
+ </h3>
125
+ <p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
126
+ Add Markdown files from outside the docs folder to the General
127
+ section.
128
+ </p>
69
129
  </div>
130
+
131
+ <!-- File browser -->
132
+ <div
133
+ class="rounded-lg border border-gray-200 dark:border-gray-700 overflow-hidden"
134
+ >
135
+ <div
136
+ 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"
137
+ >
138
+ <button
139
+ id="browse-up"
140
+ onclick="browseUp()"
141
+ class="text-xs text-blue-600 dark:text-blue-400 hover:underline disabled:opacity-30 disabled:pointer-events-none shrink-0"
142
+ >
143
+ &#8593; Up
144
+ </button>
145
+ <span
146
+ id="browse-path"
147
+ class="font-mono text-xs text-gray-400 dark:text-gray-500 truncate flex-1 text-right"
148
+ ></span>
149
+ </div>
150
+ <div
151
+ id="browse-list"
152
+ class="divide-y divide-gray-100 dark:divide-gray-800 max-h-52 overflow-y-auto"
153
+ ></div>
154
+ </div>
155
+
156
+ <!-- Added files -->
70
157
  <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>
158
+ <p
159
+ class="text-xs font-medium text-gray-500 dark:text-gray-400 mb-2"
160
+ >
161
+ Added files
162
+ </p>
163
+ <div id="extra-files-list" class="space-y-1"></div>
164
+ <p id="extra-files-empty" class="text-xs text-gray-400 italic">
165
+ No extra files added yet.
166
+ </p>
73
167
  </div>
74
- </dl>
75
- </div>
168
+ </div>
76
169
 
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>
170
+ <!-- Editable settings -->
171
+ <div
172
+ class="rounded-xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 p-5 space-y-5"
173
+ >
174
+ <h3 class="text-sm font-semibold text-gray-700 dark:text-gray-300">
175
+ Appearance & Metadata
176
+ </h3>
80
177
 
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>
178
+ <!-- Site Title -->
179
+ <div>
180
+ <label class="field-label" for="field-title">Site Title</label>
181
+ <input
182
+ id="field-title"
183
+ name="title"
184
+ type="text"
185
+ class="field-input"
186
+ placeholder="Living Documentation"
187
+ />
188
+ <p class="field-hint">
189
+ Displayed in the browser tab and sidebar header.
190
+ </p>
191
+ </div>
192
+
193
+ <!-- Theme -->
194
+ <div>
195
+ <label class="field-label" for="field-theme">Default Theme</label>
196
+ <select id="field-theme" name="theme" class="field-input">
197
+ <option value="system">System (follow OS preference)</option>
198
+ <option value="light">Light</option>
199
+ <option value="dark">Dark</option>
200
+ </select>
201
+ <p class="field-hint">
202
+ Users can always override this with the toggle button.
203
+ </p>
204
+ </div>
88
205
 
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>
206
+ <!-- Filename Pattern -->
207
+ <div>
208
+ <label class="field-label" for="field-pattern"
209
+ >Filename Pattern</label
210
+ >
211
+ <input
212
+ id="field-pattern"
213
+ name="filenamePattern"
214
+ type="text"
215
+ class="field-input"
216
+ placeholder="YYYY_MM_DD_[Category]_title"
217
+ />
218
+ <p class="field-hint">
219
+ Documents matching this pattern are parsed for date, category, and
220
+ title.
221
+ <span class="font-mono bg-gray-100 dark:bg-gray-800 px-1 rounded"
222
+ >YYYY_MM_DD_[Category]_title.md</span
223
+ >
224
+ </p>
225
+ </div>
98
226
  </div>
99
227
 
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>
228
+ <!-- Pattern preview -->
229
+ <div
230
+ id="pattern-preview"
231
+ class="rounded-xl border border-blue-100 dark:border-blue-900 bg-blue-50 dark:bg-blue-950/30 p-5"
232
+ >
233
+ <h3
234
+ class="text-sm font-semibold text-blue-700 dark:text-blue-400 mb-3"
235
+ >
236
+ Pattern Preview
237
+ </h3>
238
+ <p class="text-xs text-gray-500 dark:text-gray-400 mb-3">
239
+ How your pattern parses example filenames:
108
240
  </p>
241
+ <div id="preview-rows" class="space-y-2 text-sm"></div>
109
242
  </div>
110
- </div>
111
243
 
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>
244
+ <!-- Submit -->
245
+ <div class="flex items-center justify-between">
246
+ <div id="save-msg" class="text-sm"></div>
247
+ <button
248
+ type="submit"
249
+ 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-900"
250
+ >
251
+ Save changes
252
+ </button>
253
+ </div>
254
+ </form>
255
+ </main>
119
256
 
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 `
257
+ <script>
258
+ // ── Boot ───────────────────────────────────────────────────
259
+ document.addEventListener("DOMContentLoaded", async () => {
260
+ applyDarkMode(loadDarkPref());
261
+ setupDarkToggle();
262
+ await loadConfig();
263
+ setupPatternPreview();
264
+ document
265
+ .getElementById("config-form")
266
+ .addEventListener("submit", saveConfig);
267
+ });
268
+
269
+ // ── Dark mode ──────────────────────────────────────────────
270
+ function loadDarkPref() {
271
+ const saved = localStorage.getItem("ld-dark");
272
+ if (saved !== null) return saved === "true";
273
+ return window.matchMedia("(prefers-color-scheme: dark)").matches;
274
+ }
275
+ function applyDarkMode(dark) {
276
+ document.documentElement.classList.toggle("dark", dark);
277
+ document.getElementById("dark-icon").textContent = dark ? "☀" : "☾";
278
+ }
279
+ function setupDarkToggle() {
280
+ document.getElementById("dark-toggle").addEventListener("click", () => {
281
+ const isDark = document.documentElement.classList.toggle("dark");
282
+ localStorage.setItem("ld-dark", isDark);
283
+ document.getElementById("dark-icon").textContent = isDark ? "☀" : "☾";
284
+ });
285
+ }
286
+
287
+ // ── Config ─────────────────────────────────────────────────
288
+ async function loadConfig() {
289
+ try {
290
+ const cfg = await fetch("/api/config").then((r) => r.json());
291
+ document.getElementById("info-folder").textContent =
292
+ cfg.docsFolder || "—";
293
+ document.getElementById("info-port").textContent = cfg.port || "—";
294
+ document.getElementById("field-title").value = cfg.title || "";
295
+ document.getElementById("field-theme").value = cfg.theme || "system";
296
+ document.getElementById("field-pattern").value =
297
+ cfg.filenamePattern || "";
298
+ updatePreview(cfg.filenamePattern);
299
+ initExtraFiles(cfg);
300
+ } catch {
301
+ showMsg("Failed to load config.", "error");
302
+ }
303
+ }
304
+
305
+ async function saveConfig(e) {
306
+ e.preventDefault();
307
+ const payload = {
308
+ title: document.getElementById("field-title").value.trim(),
309
+ theme: document.getElementById("field-theme").value,
310
+ filenamePattern: document
311
+ .getElementById("field-pattern")
312
+ .value.trim(),
313
+ };
314
+
315
+ try {
316
+ const res = await fetch("/api/config", {
317
+ method: "PUT",
318
+ headers: { "Content-Type": "application/json" },
319
+ body: JSON.stringify(payload),
320
+ });
321
+ if (!res.ok) throw new Error(await res.text());
322
+ showMsg("Saved!", "ok");
323
+ } catch (err) {
324
+ showMsg("Save failed: " + err.message, "error");
325
+ }
326
+ }
327
+
328
+ function showMsg(text, type) {
329
+ const el = document.getElementById("save-msg");
330
+ el.textContent = text;
331
+ el.className =
332
+ "text-sm " +
333
+ (type === "ok"
334
+ ? "text-green-600 dark:text-green-400"
335
+ : "text-red-600 dark:text-red-400");
336
+ if (type === "ok")
337
+ setTimeout(() => {
338
+ el.textContent = "";
339
+ }, 3000);
340
+ }
341
+
342
+ // ── Pattern preview ────────────────────────────────────────
343
+ const EXAMPLES = [
344
+ "2024_01_15_[DevOps]_deploy_pipeline.md",
345
+ "2023_11_03_[Frontend]_react_hooks_guide.md",
346
+ "2025_06_20_meeting_notes.md",
347
+ "readme.md",
348
+ ];
349
+
350
+ // Parse like server does
351
+ const FULL_PAT = /^(\d{4}_\d{2}_\d{2})_\[([^\]]+)\]_(.+)\.md$/i;
352
+ const DATE_ONLY = /^(\d{4}_\d{2}_\d{2})_(.+)\.md$/i;
353
+
354
+ function parsePreview(filename) {
355
+ const full = filename.match(FULL_PAT);
356
+ if (full) {
357
+ const [, d, cat, t] = full;
358
+ return {
359
+ date: d.replace(/_/g, "-"),
360
+ category: cat,
361
+ title: titleCase(t),
362
+ match: true,
363
+ };
364
+ }
365
+ const d = filename.match(DATE_ONLY);
366
+ if (d) {
367
+ return {
368
+ date: d[1].replace(/_/g, "-"),
369
+ category: "General",
370
+ title: titleCase(d[2]),
371
+ match: true,
372
+ };
373
+ }
374
+ return {
375
+ date: null,
376
+ category: "General",
377
+ title: filename.replace(".md", ""),
378
+ match: false,
379
+ };
380
+ }
381
+
382
+ function titleCase(s) {
383
+ return s
384
+ .split("_")
385
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase())
386
+ .join(" ");
387
+ }
388
+
389
+ function updatePreview() {
390
+ const rows = document.getElementById("preview-rows");
391
+ rows.innerHTML = EXAMPLES.map((f) => {
392
+ const p = parsePreview(f);
393
+ return `
240
394
  <div class="rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 p-3">
241
395
  <p class="font-mono text-xs text-gray-500 dark:text-gray-400 mb-2 break-all">${esc(f)}</p>
242
396
  <div class="grid grid-cols-3 gap-2 text-xs">
243
397
  <div>
244
398
  <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>
399
+ <span class="${p.date ? "text-green-600 dark:text-green-400" : "text-gray-400"}">${p.date || "none"}</span>
246
400
  </div>
247
401
  <div>
248
402
  <span class="text-gray-400 block mb-0.5">Category</span>
@@ -254,18 +408,181 @@ function updatePreview() {
254
408
  </div>
255
409
  </div>
256
410
  </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>
411
+ }).join("");
412
+ }
413
+
414
+ function setupPatternPreview() {
415
+ document
416
+ .getElementById("field-pattern")
417
+ .addEventListener("input", updatePreview);
418
+ }
419
+
420
+ function esc(s) {
421
+ return String(s)
422
+ .replace(/&/g, "&amp;")
423
+ .replace(/</g, "&lt;")
424
+ .replace(/>/g, "&gt;")
425
+ .replace(/"/g, "&quot;")
426
+ .replace(/'/g, "&#39;");
427
+ }
428
+
429
+ // ── Extra Files / Browser ──────────────────────────────────
430
+ let extraFiles = [];
431
+ let browseParent = null;
432
+ let browseCurrent = "";
433
+ let docsFolder = "";
434
+
435
+ function initExtraFiles(cfg) {
436
+ extraFiles = cfg.extraFiles || [];
437
+ docsFolder = cfg.docsFolder || "";
438
+ renderExtraFiles();
439
+ loadBrowse(cfg.docsFolder || "/");
440
+ }
441
+
442
+ function relativeDisplay(filePath) {
443
+ if (!docsFolder) return filePath;
444
+ const fromParts = docsFolder.split("/").filter(Boolean);
445
+ const toParts = filePath.split("/").filter(Boolean);
446
+ let common = 0;
447
+ while (
448
+ common < fromParts.length &&
449
+ common < toParts.length &&
450
+ fromParts[common] === toParts[common]
451
+ )
452
+ common++;
453
+ const ups = fromParts.length - common;
454
+ const downs = toParts.slice(common).join("/");
455
+ const rel = "../".repeat(ups) + downs;
456
+ return "$DOCS_FOLDER/" + rel;
457
+ }
458
+
459
+ async function loadBrowse(dirPath) {
460
+ const list = document.getElementById("browse-list");
461
+ list.innerHTML =
462
+ '<p class="px-3 py-4 text-xs text-gray-400 text-center">Loading…</p>';
463
+ try {
464
+ const data = await fetch(
465
+ "/api/browse?path=" + encodeURIComponent(dirPath),
466
+ ).then((r) => r.json());
467
+ browseCurrent = data.current;
468
+ browseParent = data.parent;
469
+ document.getElementById("browse-path").textContent = relativeDisplay(data.current);
470
+ document.getElementById("browse-up").disabled = !data.parent;
471
+
472
+ const rows = [];
473
+
474
+ for (const dir of data.dirs) {
475
+ rows.push(`
476
+ <button data-path="${esc(dir.path)}" onclick="loadBrowse(this.dataset.path)"
477
+ class="w-full flex items-center gap-2 px-3 py-2 text-sm text-left
478
+ hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors">
479
+ <span class="text-gray-400 shrink-0">📁</span>
480
+ <span class="text-gray-700 dark:text-gray-300 truncate">${esc(dir.name)}</span>
481
+ </button>`);
482
+ }
483
+
484
+ for (const file of data.files) {
485
+ const added = extraFiles.includes(file.path);
486
+ rows.push(`
487
+ <div class="flex items-center justify-between gap-2 px-3 py-2 text-sm
488
+ hover:bg-gray-50 dark:hover:bg-gray-800">
489
+ <span class="flex items-center gap-2 text-gray-700 dark:text-gray-300 min-w-0">
490
+ <span class="text-gray-400 shrink-0">📄</span>
491
+ <span class="truncate">${esc(file.name)}</span>
492
+ </span>
493
+ ${
494
+ added
495
+ ? '<span class="text-xs text-green-600 dark:text-green-400 shrink-0">Added</span>'
496
+ : `<button data-path="${esc(file.path)}" onclick="addExtraFile(this.dataset.path)"
497
+ class="text-xs text-blue-600 dark:text-blue-400 hover:underline shrink-0">+ Add</button>`
498
+ }
499
+ </div>`);
500
+ }
501
+
502
+ if (rows.length === 0) {
503
+ list.innerHTML =
504
+ '<p class="px-3 py-4 text-xs text-gray-400 text-center">Empty directory</p>';
505
+ } else {
506
+ list.innerHTML = rows.join("");
507
+ }
508
+ } catch {
509
+ list.innerHTML =
510
+ '<p class="px-3 py-4 text-xs text-red-400 text-center">Cannot read directory</p>';
511
+ }
512
+ }
513
+
514
+ function browseUp() {
515
+ if (browseParent) loadBrowse(browseParent);
516
+ }
517
+
518
+ async function addExtraFile(filePath) {
519
+ if (extraFiles.includes(filePath)) return;
520
+ extraFiles = [...extraFiles, filePath];
521
+ await saveExtraFiles();
522
+ renderExtraFiles();
523
+ loadBrowse(browseCurrent);
524
+ }
525
+
526
+ async function removeExtraFile(filePath) {
527
+ extraFiles = extraFiles.filter((f) => f !== filePath);
528
+ await saveExtraFiles();
529
+ renderExtraFiles();
530
+ loadBrowse(browseCurrent);
531
+ }
532
+
533
+ async function saveExtraFiles() {
534
+ try {
535
+ const res = await fetch("/api/config", {
536
+ method: "PUT",
537
+ headers: { "Content-Type": "application/json" },
538
+ body: JSON.stringify({ extraFiles }),
539
+ });
540
+ if (!res.ok) throw new Error(await res.text());
541
+ } catch (err) {
542
+ showMsg("Failed to save: " + err.message, "error");
543
+ }
544
+ }
545
+
546
+ function moveExtraFile(index, direction) {
547
+ const target = index + direction;
548
+ if (target < 0 || target >= extraFiles.length) return;
549
+ const next = [...extraFiles];
550
+ [next[index], next[target]] = [next[target], next[index]];
551
+ extraFiles = next;
552
+ saveExtraFiles();
553
+ renderExtraFiles();
554
+ }
555
+
556
+ function renderExtraFiles() {
557
+ const list = document.getElementById("extra-files-list");
558
+ const empty = document.getElementById("extra-files-empty");
559
+ if (extraFiles.length === 0) {
560
+ list.innerHTML = "";
561
+ empty.style.display = "";
562
+ return;
563
+ }
564
+ empty.style.display = "none";
565
+ list.innerHTML = extraFiles
566
+ .map(
567
+ (f, i) => `
568
+ <div class="flex items-center gap-2 px-3 py-2 rounded-lg bg-gray-50 dark:bg-gray-800">
569
+ <div class="flex flex-col shrink-0">
570
+ <button data-i="${i}" data-d="-1" onclick="moveExtraFile(+this.dataset.i, +this.dataset.d)"
571
+ class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 leading-none disabled:opacity-20"
572
+ ${i === 0 ? "disabled" : ""}>&#8593;</button>
573
+ <button data-i="${i}" data-d="1" onclick="moveExtraFile(+this.dataset.i, +this.dataset.d)"
574
+ class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 leading-none disabled:opacity-20"
575
+ ${i === extraFiles.length - 1 ? "disabled" : ""}>&#8595;</button>
576
+ </div>
577
+ <div class="overflow-x-auto flex-1 min-w-0">
578
+ <span class="font-mono text-xs text-gray-600 dark:text-gray-400 whitespace-nowrap" title="${esc(f)}">${esc(relativeDisplay(f))}</span>
579
+ </div>
580
+ <button data-path="${esc(f)}" onclick="removeExtraFile(this.dataset.path)"
581
+ class="text-xs text-red-500 hover:text-red-700 dark:hover:text-red-400 shrink-0">Remove</button>
582
+ </div>`,
583
+ )
584
+ .join("");
585
+ }
586
+ </script>
587
+ </body>
271
588
  </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.1.0",
4
4
  "description": "A CLI tool that serves a local Markdown documentation viewer",
5
5
  "main": "dist/src/server.js",
6
6
  "bin": {