living-documentation 1.2.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -11,11 +11,14 @@ No cloud, no database, no build step — just point it at a folder of `.md` file
11
11
  ## Features
12
12
 
13
13
  - **Sidebar** grouped by category, sorted by date (newest first)
14
+ - **General section** — always first, always expanded; holds uncategorized docs and extra files
15
+ - **Extra files** — include Markdown files from outside the docs folder (e.g. `README.md`, `CLAUDE.md`)
14
16
  - **Full-text search** — instant filter + server-side content search
15
17
  - **Dark mode** — follows system preference, manually toggleable
18
+ - **Syntax highlighting** — always dark, high-contrast code blocks
16
19
  - **Export to PDF** — print-friendly layout via `window.print()`
17
20
  - **Deep links** — share a direct URL to any document (`?doc=…`)
18
- - **Admin panel** — configure title, theme, filename pattern in the browser
21
+ - **Admin panel** — configure title, theme, filename pattern, and extra files in the browser
19
22
  - **Zero frontend build** — Tailwind and highlight.js loaded from CDN
20
23
 
21
24
  ---
@@ -51,7 +54,7 @@ living-documentation ./docs
51
54
  git clone <repo>
52
55
  cd living-documentation
53
56
  npm install
54
- npm run dev -- ./docs # runs via ts-node, no build needed
57
+ npm run dev -- ./docs # nodemon + ts-node, auto-restarts on changes
55
58
  ```
56
59
 
57
60
  ---
@@ -103,7 +106,7 @@ YYYY_MM_DD_[Category]_title_words.md
103
106
  2023_11_03_[Backend]_api_versioning_strategy.md
104
107
  ```
105
108
 
106
- Files that don't match the pattern are still shown — they appear under **Uncategorized** with the filename as the title.
109
+ Files that don't match the pattern are still shown — they appear under **General** with the filename as the title.
107
110
 
108
111
  ---
109
112
 
@@ -117,12 +120,28 @@ A `.living-doc.json` file is created automatically in your docs folder on first
117
120
  "filenamePattern": "YYYY_MM_DD_[Category]_title",
118
121
  "title": "Living Documentation",
119
122
  "theme": "system",
120
- "port": 4321
123
+ "port": 4321,
124
+ "extraFiles": []
121
125
  }
122
126
  ```
123
127
 
124
128
  You can edit it manually or use the **Admin panel** at [http://localhost:4321/admin](http://localhost:4321/admin).
125
129
 
130
+ ### Extra files
131
+
132
+ The `extraFiles` field accepts an ordered list of absolute paths to `.md` files located **outside** the docs folder. These files always appear in the **General** section, before regular General documents, in the order defined.
133
+
134
+ ```json
135
+ {
136
+ "extraFiles": [
137
+ "/path/to/project/README.md",
138
+ "/path/to/project/CLAUDE.md"
139
+ ]
140
+ }
141
+ ```
142
+
143
+ Use the Admin panel's **General — Extra Files** section to browse the filesystem and manage this list without editing the config manually.
144
+
126
145
  ---
127
146
 
128
147
  ## Project structure
@@ -135,7 +154,8 @@ living-documentation/
135
154
  │ ├── server.ts Express app
136
155
  │ ├── routes/
137
156
  │ │ ├── documents.ts Documents API
138
- │ │ └── config.ts Config API
157
+ │ │ ├── config.ts Config API
158
+ │ │ └── browse.ts Filesystem browser API
139
159
  │ ├── lib/
140
160
  │ │ ├── parser.ts Filename parser
141
161
  │ │ └── config.ts Config management
@@ -152,13 +172,14 @@ living-documentation/
152
172
 
153
173
  ## API reference
154
174
 
155
- | Method | Endpoint | Description |
156
- | ------ | -------------------------- | --------------------------------------------------- |
157
- | `GET` | `/api/documents` | List all documents with metadata |
158
- | `GET` | `/api/documents/:id` | Get document content + rendered HTML |
159
- | `GET` | `/api/documents/search?q=` | Full-text search |
160
- | `GET` | `/api/config` | Read config |
161
- | `PUT` | `/api/config` | Update config (`title`, `theme`, `filenamePattern`) |
175
+ | Method | Endpoint | Description |
176
+ | ------ | -------------------------- | ------------------------------------------------------------------ |
177
+ | `GET` | `/api/documents` | List all documents with metadata (includes extra files) |
178
+ | `GET` | `/api/documents/:id` | Get document content + rendered HTML |
179
+ | `GET` | `/api/documents/search?q=` | Full-text search |
180
+ | `GET` | `/api/config` | Read config |
181
+ | `PUT` | `/api/config` | Update config (`title`, `theme`, `filenamePattern`, `extraFiles`) |
182
+ | `GET` | `/api/browse?path=` | List directories and `.md` files at a given filesystem path |
162
183
 
163
184
  ---
164
185
 
@@ -153,7 +153,7 @@
153
153
  placeholder="YYYY_MM_DD_[Category]_title" />
154
154
  <p class="text-xs text-gray-400 dark:text-gray-500">
155
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>
156
+ <span id="pattern-desc-example" 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
157
  </p>
158
158
  </div>
159
159
  </div>
@@ -220,12 +220,22 @@
220
220
 
221
221
  async function saveConfig(e) {
222
222
  e.preventDefault();
223
+ const filenamePattern = document.getElementById("field-pattern").value.trim();
224
+ if (filenamePattern) {
225
+ const catCount = (filenamePattern.match(/\[Category\]/gi) || []).length;
226
+ if (catCount === 0) {
227
+ showMsg("Filename pattern must contain [Category]", "error");
228
+ return;
229
+ }
230
+ if (catCount > 1) {
231
+ showMsg("Filename pattern must contain [Category] exactly once", "error");
232
+ return;
233
+ }
234
+ }
223
235
  const payload = {
224
236
  title: document.getElementById("field-title").value.trim(),
225
237
  theme: document.getElementById("field-theme").value,
226
- filenamePattern: document
227
- .getElementById("field-pattern")
228
- .value.trim(),
238
+ filenamePattern,
229
239
  };
230
240
 
231
241
  try {
@@ -263,29 +273,48 @@
263
273
  "readme.md",
264
274
  ];
265
275
 
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
- };
276
+ function buildPatternsFromFormat(patternStr) {
277
+ if (!patternStr) patternStr = "YYYY_MM_DD_[Category]_title";
278
+ const hasDate = /YYYY.*MM.*DD/.test(patternStr);
279
+ const hasCategory = /\[Category\]/i.test(patternStr);
280
+ const dateGroup = "(\\d{4}_\\d{2}_\\d{2})";
281
+ const catGroup = "\\[([^\\]]+)\\]";
282
+ const catBeforeDate =
283
+ hasDate && hasCategory &&
284
+ patternStr.search(/\[Category\]/i) < patternStr.search(/YYYY/i);
285
+ let full = null, dateOnly = null;
286
+ if (hasDate && hasCategory) {
287
+ const ordered = catBeforeDate
288
+ ? catGroup + "_" + dateGroup
289
+ : dateGroup + "_" + catGroup;
290
+ full = new RegExp("^" + ordered + "_(.+)\\.md$", "i");
291
+ dateOnly = new RegExp("^" + dateGroup + "_(.+)\\.md$", "i");
292
+ } else if (hasDate) {
293
+ dateOnly = new RegExp("^" + dateGroup + "_(.+)\\.md$", "i");
294
+ } else if (hasCategory) {
295
+ full = new RegExp("^" + catGroup + "_(.+)\\.md$", "i");
296
+ }
297
+ return { full, dateOnly, hasDate, hasCategory, catBeforeDate };
298
+ }
299
+
300
+ function parsePreview(filename, patterns) {
301
+ if (patterns.full) {
302
+ const m = filename.match(patterns.full);
303
+ if (m) {
304
+ if (patterns.hasDate && patterns.hasCategory) {
305
+ const dateStr = patterns.catBeforeDate ? m[2] : m[1];
306
+ const category = patterns.catBeforeDate ? m[1] : m[2];
307
+ return { date: dateStr.replace(/_/g, "-"), category, title: titleCase(m[3]), match: true };
308
+ } else if (patterns.hasCategory) {
309
+ return { date: null, category: m[1], title: titleCase(m[2]), match: true };
310
+ }
311
+ }
280
312
  }
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
- };
313
+ if (patterns.dateOnly) {
314
+ const m = filename.match(patterns.dateOnly);
315
+ if (m) {
316
+ return { date: m[1].replace(/_/g, "-"), category: "General", title: titleCase(m[2]), match: true };
317
+ }
289
318
  }
290
319
  return {
291
320
  date: null,
@@ -303,9 +332,13 @@
303
332
  }
304
333
 
305
334
  function updatePreview() {
335
+ const patternVal = document.getElementById("field-pattern").value.trim();
336
+ const descSpan = document.getElementById("pattern-desc-example");
337
+ descSpan.textContent = (patternVal || "YYYY_MM_DD_[Category]_title") + ".md";
338
+ const patterns = buildPatternsFromFormat(patternVal);
306
339
  const rows = document.getElementById("preview-rows");
307
340
  rows.innerHTML = EXAMPLES.map((f) => {
308
- const p = parsePreview(f);
341
+ const p = parsePreview(f, patterns);
309
342
  return `
310
343
  <div class="rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 p-3">
311
344
  <p class="font-mono text-xs text-gray-500 dark:text-gray-400 mb-2 break-all">${esc(f)}</p>
@@ -151,7 +151,7 @@
151
151
  <p class="text-sm text-gray-500 dark:text-gray-500">
152
152
  Choose a document from the sidebar to start reading.
153
153
  </p>
154
- <p class="mt-4 text-xs text-gray-400 dark:text-gray-600 font-mono bg-gray-100 dark:bg-gray-800 rounded-lg px-3 py-2 inline-block">
154
+ <p id="welcome-pattern" class="mt-4 text-xs text-gray-400 dark:text-gray-600 font-mono bg-gray-100 dark:bg-gray-800 rounded-lg px-3 py-2 inline-block">
155
155
  YYYY_MM_DD_[Category]_title.md
156
156
  </p>
157
157
  <p class="mt-2 text-xs text-gray-400">Expected filename pattern</p>
@@ -175,16 +175,37 @@
175
175
  </div>
176
176
  <!-- Actions -->
177
177
  <div class="flex items-center gap-2 shrink-0">
178
- <button onclick="exportPDF()" title="Export as PDF"
179
- class="no-print text-sm px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-700
180
- text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors">
181
- &#128196; Export PDF
182
- </button>
183
- <button onclick="copyLink()" id="copy-link-btn" title="Copy link"
184
- class="no-print text-sm px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-700
185
- text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors">
186
- &#128279; Copy link
187
- </button>
178
+ <!-- View mode actions -->
179
+ <div id="view-actions" class="flex items-center gap-2">
180
+ <button onclick="exportPDF()" title="Export as PDF"
181
+ class="no-print text-sm px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-700
182
+ text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors">
183
+ &#128196; Export PDF
184
+ </button>
185
+ <button onclick="copyLink()" id="copy-link-btn" title="Copy link"
186
+ class="no-print text-sm px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-700
187
+ text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors">
188
+ &#128279; Copy link
189
+ </button>
190
+ <button onclick="enterEditMode()" title="Edit document"
191
+ class="no-print text-sm px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-700
192
+ text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors">
193
+ &#9998; Edit
194
+ </button>
195
+ </div>
196
+ <!-- Edit mode actions -->
197
+ <div id="edit-actions" class="hidden flex items-center gap-2">
198
+ <span id="edit-save-msg" class="text-xs"></span>
199
+ <button onclick="exitEditMode()" title="Cancel editing"
200
+ class="text-sm px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-700
201
+ text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors">
202
+ Cancel
203
+ </button>
204
+ <button onclick="saveDocument()" title="Save document"
205
+ class="text-sm px-4 py-1.5 rounded-lg bg-blue-600 hover:bg-blue-700 text-white font-semibold transition-colors">
206
+ Save
207
+ </button>
208
+ </div>
188
209
  </div>
189
210
  </div>
190
211
  </header>
@@ -201,6 +222,14 @@
201
222
  prose-code:before:content-none prose-code:after:content-none
202
223
  prose-pre:bg-[#0d1117] prose-pre:border prose-pre:border-gray-700">
203
224
  </div>
225
+
226
+ <!-- Markdown editor -->
227
+ <textarea id="doc-editor"
228
+ class="hidden w-full min-h-[70vh] px-4 py-3 text-sm font-mono leading-relaxed
229
+ rounded-lg border border-gray-300 dark:border-gray-600
230
+ bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100
231
+ focus:outline-none focus:ring-2 focus:ring-blue-500 resize-y"
232
+ spellcheck="false"></textarea>
204
233
  </article>
205
234
 
206
235
  </main>
@@ -211,6 +240,7 @@
211
240
  // ── State ──────────────────────────────────────────────────
212
241
  let allDocs = [];
213
242
  let currentDocId = null;
243
+ let currentDocContent = '';
214
244
  let searchQuery = '';
215
245
  let expandedCategories = new Set();
216
246
 
@@ -222,10 +252,15 @@ document.addEventListener('DOMContentLoaded', async () => {
222
252
  await loadConfig();
223
253
  await loadDocuments();
224
254
 
225
- // Deep-link via ?doc=id
255
+ // Deep-link via ?doc=id, otherwise open first General doc
226
256
  const params = new URLSearchParams(location.search);
227
257
  const docId = params.get('doc');
228
- if (docId) openDocument(docId, true);
258
+ if (docId) {
259
+ openDocument(docId, true);
260
+ } else {
261
+ const first = allDocs.find(d => d.category === 'General') ?? allDocs[0];
262
+ if (first) openDocument(first.id, true);
263
+ }
229
264
  });
230
265
 
231
266
  // ── Dark mode ──────────────────────────────────────────────
@@ -254,6 +289,9 @@ async function loadConfig() {
254
289
  const cfg = await fetch('/api/config').then(r => r.json());
255
290
  if (cfg.title) document.title = cfg.title;
256
291
  document.getElementById('app-title').textContent = cfg.title || 'Living Documentation';
292
+ if (cfg.filenamePattern) {
293
+ document.getElementById('welcome-pattern').textContent = cfg.filenamePattern + '.md';
294
+ }
257
295
  } catch { /* non-fatal */ }
258
296
  }
259
297
 
@@ -376,6 +414,9 @@ async function openDocument(id, skipHistory = false) {
376
414
  return r.json();
377
415
  });
378
416
 
417
+ currentDocContent = doc.content;
418
+ exitEditMode();
419
+
379
420
  document.getElementById('doc-title').textContent = doc.title;
380
421
  document.getElementById('doc-category').textContent = doc.category;
381
422
  document.getElementById('doc-date').textContent = doc.formattedDate || '';
@@ -457,6 +498,112 @@ async function doSearch(q) {
457
498
  } catch { /* ignore */ }
458
499
  }
459
500
 
501
+ // ── Edit mode ──────────────────────────────────────────────
502
+ function enterEditMode() {
503
+ const editor = document.getElementById('doc-editor');
504
+ editor.value = currentDocContent;
505
+ document.getElementById('doc-content').classList.add('hidden');
506
+ editor.classList.remove('hidden');
507
+ document.getElementById('view-actions').classList.add('hidden');
508
+ document.getElementById('edit-actions').classList.remove('hidden');
509
+ editor.focus();
510
+ editor.addEventListener('paste', handleEditorPaste);
511
+ }
512
+
513
+ function exitEditMode() {
514
+ const editor = document.getElementById('doc-editor');
515
+ editor.removeEventListener('paste', handleEditorPaste);
516
+ editor.classList.add('hidden');
517
+ document.getElementById('doc-content').classList.remove('hidden');
518
+ document.getElementById('edit-actions').classList.add('hidden');
519
+ document.getElementById('view-actions').classList.remove('hidden');
520
+ document.getElementById('edit-save-msg').textContent = '';
521
+ }
522
+
523
+ async function handleEditorPaste(e) {
524
+ const items = Array.from(e.clipboardData?.items ?? []);
525
+ const imageItem = items.find(item => item.type.startsWith('image/'));
526
+ if (!imageItem) return;
527
+
528
+ e.preventDefault();
529
+
530
+ const ext = imageItem.type.split('/')[1].replace('jpeg', 'jpg') || 'png';
531
+ const blob = imageItem.getAsFile();
532
+ if (!blob) return;
533
+
534
+ const editor = document.getElementById('doc-editor');
535
+ const msgEl = document.getElementById('edit-save-msg');
536
+
537
+ // Insert placeholder at cursor while uploading
538
+ const placeholder = `![image](uploading...)`;
539
+ const start = editor.selectionStart;
540
+ const before = editor.value.slice(0, start);
541
+ const after = editor.value.slice(editor.selectionEnd);
542
+ editor.value = before + placeholder + after;
543
+ editor.selectionStart = editor.selectionEnd = start + placeholder.length;
544
+
545
+ msgEl.textContent = 'Uploading image…';
546
+ msgEl.className = 'text-xs text-gray-400';
547
+
548
+ try {
549
+ const base64 = await new Promise((resolve, reject) => {
550
+ const reader = new FileReader();
551
+ reader.onload = () => resolve(reader.result);
552
+ reader.onerror = reject;
553
+ reader.readAsDataURL(blob);
554
+ });
555
+
556
+ const res = await fetch('/api/images/upload', {
557
+ method: 'POST',
558
+ headers: { 'Content-Type': 'application/json' },
559
+ body: JSON.stringify({ data: base64, ext }),
560
+ });
561
+ if (!res.ok) throw new Error(await res.text());
562
+
563
+ const { filename } = await res.json();
564
+ const mdImage = `![image](./images/${filename})`;
565
+
566
+ // Replace placeholder with final markdown
567
+ editor.value = editor.value.replace(placeholder, mdImage);
568
+ msgEl.textContent = '';
569
+ } catch (err) {
570
+ // Remove placeholder on failure
571
+ editor.value = editor.value.replace(placeholder, '');
572
+ msgEl.textContent = 'Image upload failed: ' + err.message;
573
+ msgEl.className = 'text-xs text-red-500 dark:text-red-400';
574
+ }
575
+ }
576
+
577
+ async function saveDocument() {
578
+ if (!currentDocId) return;
579
+ const content = document.getElementById('doc-editor').value;
580
+ const msgEl = document.getElementById('edit-save-msg');
581
+ msgEl.textContent = 'Saving…';
582
+ msgEl.className = 'text-xs text-gray-400';
583
+
584
+ try {
585
+ const res = await fetch('/api/documents/' + currentDocId, {
586
+ method: 'PUT',
587
+ headers: { 'Content-Type': 'application/json' },
588
+ body: JSON.stringify({ content }),
589
+ });
590
+ if (!res.ok) throw new Error(await res.text());
591
+
592
+ currentDocContent = content;
593
+
594
+ // Re-fetch rendered HTML and update view
595
+ const doc = await fetch('/api/documents/' + currentDocId).then(r => r.json());
596
+ const contentEl = document.getElementById('doc-content');
597
+ contentEl.innerHTML = doc.html;
598
+ contentEl.querySelectorAll('pre code').forEach(block => hljs.highlightElement(block));
599
+
600
+ exitEditMode();
601
+ } catch (err) {
602
+ msgEl.textContent = 'Save failed: ' + err.message;
603
+ msgEl.className = 'text-xs text-red-500 dark:text-red-400';
604
+ }
605
+ }
606
+
460
607
  // ── Helpers ────────────────────────────────────────────────
461
608
  function exportPDF() { window.print(); }
462
609
 
@@ -6,5 +6,5 @@ export interface DocMetadata {
6
6
  date: string | null;
7
7
  formattedDate: string | null;
8
8
  }
9
- export declare function parseFilename(filename: string): DocMetadata;
9
+ export declare function parseFilename(filename: string, filenamePattern?: string): DocMetadata;
10
10
  //# sourceMappingURL=parser.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../../src/lib/parser.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AA0BD,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,WAAW,CAyC3D"}
1
+ {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../../src/lib/parser.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAkED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,eAAe,CAAC,EAAE,MAAM,GAAG,WAAW,CAwCrF"}
@@ -1,10 +1,45 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.parseFilename = parseFilename;
4
- // Matches: YYYY_MM_DD_[Category]_title_words.md
5
- const FULL_PATTERN = /^(\d{4}_\d{2}_\d{2})_\[([^\]]+)\]_(.+)\.md$/i;
6
- // Matches: YYYY_MM_DD_title_words.md (no category)
7
- const DATE_ONLY_PATTERN = /^(\d{4}_\d{2}_\d{2})_(.+)\.md$/i;
4
+ function buildPatternsFromFormat(patternStr) {
5
+ const hasDate = /YYYY.*MM.*DD/.test(patternStr);
6
+ const hasCategory = /\[Category\]/i.test(patternStr);
7
+ const dateGroup = '(\\d{4}_\\d{2}_\\d{2})';
8
+ const catGroup = '\\[([^\\]]+)\\]';
9
+ const catBeforeDate = hasDate && hasCategory &&
10
+ patternStr.search(/\[Category\]/i) < patternStr.search(/YYYY/i);
11
+ if (hasDate && hasCategory) {
12
+ const ordered = catBeforeDate
13
+ ? `${catGroup}_${dateGroup}`
14
+ : `${dateGroup}_${catGroup}`;
15
+ return {
16
+ full: new RegExp(`^${ordered}_(.+)\\.md$`, 'i'),
17
+ dateOnly: new RegExp(`^${dateGroup}_(.+)\\.md$`, 'i'),
18
+ hasDate,
19
+ hasCategory,
20
+ catBeforeDate,
21
+ };
22
+ }
23
+ else if (hasDate) {
24
+ return {
25
+ full: null,
26
+ dateOnly: new RegExp(`^${dateGroup}_(.+)\\.md$`, 'i'),
27
+ hasDate,
28
+ hasCategory,
29
+ catBeforeDate: false,
30
+ };
31
+ }
32
+ else if (hasCategory) {
33
+ return {
34
+ full: new RegExp(`^${catGroup}_(.+)\\.md$`, 'i'),
35
+ dateOnly: null,
36
+ hasDate,
37
+ hasCategory,
38
+ catBeforeDate: false,
39
+ };
40
+ }
41
+ return { full: null, dateOnly: null, hasDate, hasCategory, catBeforeDate: false };
42
+ }
8
43
  function formatDate(iso) {
9
44
  const [year, month, day] = iso.split('-').map(Number);
10
45
  const d = new Date(Date.UTC(year, month - 1, day));
@@ -21,33 +56,32 @@ function titleCase(raw) {
21
56
  .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
22
57
  .join(' ');
23
58
  }
24
- function parseFilename(filename) {
59
+ function parseFilename(filename, filenamePattern) {
25
60
  const id = encodeURIComponent(filename.slice(0, -3));
26
- const full = filename.match(FULL_PATTERN);
27
- if (full) {
28
- const [, dateStr, category, titlePart] = full;
29
- const date = dateStr.replace(/_/g, '-');
30
- return {
31
- id,
32
- filename,
33
- title: titleCase(titlePart),
34
- category,
35
- date,
36
- formattedDate: formatDate(date),
37
- };
61
+ const { full: FULL_PAT, dateOnly: DATE_ONLY_PAT, hasDate, hasCategory, catBeforeDate } = buildPatternsFromFormat(filenamePattern ?? 'YYYY_MM_DD_[Category]_title');
62
+ if (FULL_PAT) {
63
+ const full = filename.match(FULL_PAT);
64
+ if (full) {
65
+ if (hasDate && hasCategory) {
66
+ const dateStr = catBeforeDate ? full[2] : full[1];
67
+ const category = catBeforeDate ? full[1] : full[2];
68
+ const titlePart = full[3];
69
+ const date = dateStr.replace(/_/g, '-');
70
+ return { id, filename, title: titleCase(titlePart), category, date, formattedDate: formatDate(date) };
71
+ }
72
+ else if (hasCategory) {
73
+ const [, category, titlePart] = full;
74
+ return { id, filename, title: titleCase(titlePart), category, date: null, formattedDate: null };
75
+ }
76
+ }
38
77
  }
39
- const dateOnly = filename.match(DATE_ONLY_PATTERN);
40
- if (dateOnly) {
41
- const [, dateStr, titlePart] = dateOnly;
42
- const date = dateStr.replace(/_/g, '-');
43
- return {
44
- id,
45
- filename,
46
- title: titleCase(titlePart),
47
- category: 'General',
48
- date,
49
- formattedDate: formatDate(date),
50
- };
78
+ if (DATE_ONLY_PAT) {
79
+ const dateOnly = filename.match(DATE_ONLY_PAT);
80
+ if (dateOnly) {
81
+ const [, dateStr, titlePart] = dateOnly;
82
+ const date = dateStr.replace(/_/g, '-');
83
+ return { id, filename, title: titleCase(titlePart), category: 'General', date, formattedDate: formatDate(date) };
84
+ }
51
85
  }
52
86
  // Fallback — no date, no category
53
87
  const rawTitle = filename.slice(0, -3).replace(/[_-]+/g, ' ');
@@ -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,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"}
1
+ {"version":3,"file":"parser.js","sourceRoot":"","sources":["../../../src/lib/parser.ts"],"names":[],"mappings":"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"}
@@ -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;AAIpD,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CA6CrD"}
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,CAuDrD"}
@@ -34,6 +34,16 @@ function configRouter(docsPath) {
34
34
  safe[key] = patch[key];
35
35
  }
36
36
  }
37
+ // filenamePattern: must contain exactly one [Category]
38
+ if ('filenamePattern' in safe && typeof safe.filenamePattern === 'string') {
39
+ const matches = safe.filenamePattern.match(/\[Category\]/gi);
40
+ if (!matches || matches.length === 0) {
41
+ return res.status(400).json({ error: 'filenamePattern must contain [Category]' });
42
+ }
43
+ if (matches.length > 1) {
44
+ return res.status(400).json({ error: 'filenamePattern must contain [Category] exactly once' });
45
+ }
46
+ }
37
47
  // extraFiles: only absolute .md paths
38
48
  if ('extraFiles' in patch && Array.isArray(patch.extraFiles)) {
39
49
  safe.extraFiles = patch.extraFiles.filter((f) => typeof f === 'string' &&
@@ -1 +1 @@
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
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../../src/routes/config.ts"],"names":[],"mappings":";;;;;AAIA,oCAuDC;AA3DD,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,uDAAuD;YACvD,IAAI,iBAAiB,IAAI,IAAI,IAAI,OAAO,IAAI,CAAC,eAAe,KAAK,QAAQ,EAAE,CAAC;gBAC1E,MAAM,OAAO,GAAG,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC;gBAC7D,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;oBACrC,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,yCAAyC,EAAE,CAAC,CAAC;gBACpF,CAAC;gBACD,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBACvB,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,sDAAsD,EAAE,CAAC,CAAC;gBACjG,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;AAwDpD,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAqGxD"}
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,CA8IxD"}
@@ -10,14 +10,14 @@ const path_1 = __importDefault(require("path"));
10
10
  const marked_1 = require("marked");
11
11
  const parser_1 = require("../lib/parser");
12
12
  const config_1 = require("../lib/config");
13
- function listDocs(docsPath, extraFiles = []) {
13
+ function listDocs(docsPath, extraFiles = [], filenamePattern) {
14
14
  // Extra files first, in config order, always General
15
15
  const extraDocs = [];
16
16
  for (const filePath of extraFiles) {
17
17
  if (!filePath.endsWith('.md') || !fs_1.default.existsSync(filePath))
18
18
  continue;
19
19
  const filename = path_1.default.basename(filePath);
20
- const meta = (0, parser_1.parseFilename)(filename);
20
+ const meta = (0, parser_1.parseFilename)(filename, filenamePattern);
21
21
  extraDocs.push({
22
22
  ...meta,
23
23
  id: encodeURIComponent(filePath.slice(0, -3)),
@@ -28,7 +28,7 @@ function listDocs(docsPath, extraFiles = []) {
28
28
  const regularDocs = fs_1.default
29
29
  .readdirSync(docsPath)
30
30
  .filter((f) => f.toLowerCase().endsWith('.md'))
31
- .map((filename) => (0, parser_1.parseFilename)(filename));
31
+ .map((filename) => (0, parser_1.parseFilename)(filename, filenamePattern));
32
32
  regularDocs.sort((a, b) => {
33
33
  if (a.date && b.date)
34
34
  return b.date.localeCompare(a.date);
@@ -59,8 +59,8 @@ function documentsRouter(docsPath) {
59
59
  // GET /api/documents — list all docs with metadata
60
60
  router.get('/', (_req, res) => {
61
61
  try {
62
- const { extraFiles = [] } = (0, config_1.readConfig)(docsPath);
63
- res.json(listDocs(docsPath, extraFiles));
62
+ const { extraFiles = [], filenamePattern } = (0, config_1.readConfig)(docsPath);
63
+ res.json(listDocs(docsPath, extraFiles, filenamePattern));
64
64
  }
65
65
  catch {
66
66
  res.status(500).json({ error: 'Failed to list documents' });
@@ -72,8 +72,8 @@ function documentsRouter(docsPath) {
72
72
  if (!query)
73
73
  return res.json([]);
74
74
  try {
75
- const { extraFiles = [] } = (0, config_1.readConfig)(docsPath);
76
- const docs = listDocs(docsPath, extraFiles);
75
+ const { extraFiles = [], filenamePattern } = (0, config_1.readConfig)(docsPath);
76
+ const docs = listDocs(docsPath, extraFiles, filenamePattern);
77
77
  const results = [];
78
78
  for (const doc of docs) {
79
79
  const filePath = resolveDocPath(docsPath, doc, extraFiles);
@@ -106,7 +106,7 @@ function documentsRouter(docsPath) {
106
106
  // GET /api/documents/:id — get a single document (content + rendered HTML)
107
107
  router.get('/:id', async (req, res) => {
108
108
  const id = decodeURIComponent(req.params.id);
109
- const { extraFiles = [] } = (0, config_1.readConfig)(docsPath);
109
+ const { extraFiles = [], filenamePattern } = (0, config_1.readConfig)(docsPath);
110
110
  // Extra file: id is an absolute path without .md
111
111
  if (path_1.default.isAbsolute(id)) {
112
112
  const filePath = id + '.md';
@@ -118,7 +118,7 @@ function documentsRouter(docsPath) {
118
118
  }
119
119
  try {
120
120
  const content = fs_1.default.readFileSync(filePath, 'utf-8');
121
- const meta = (0, parser_1.parseFilename)(path_1.default.basename(filePath));
121
+ const meta = (0, parser_1.parseFilename)(path_1.default.basename(filePath), filenamePattern);
122
122
  const html = marked_1.marked.parse(content);
123
123
  res.json({ ...meta, id: req.params.id, category: 'General', content, html });
124
124
  }
@@ -138,7 +138,7 @@ function documentsRouter(docsPath) {
138
138
  }
139
139
  try {
140
140
  const content = fs_1.default.readFileSync(filePath, 'utf-8');
141
- const metadata = (0, parser_1.parseFilename)(filename);
141
+ const metadata = (0, parser_1.parseFilename)(filename, filenamePattern);
142
142
  const html = marked_1.marked.parse(content);
143
143
  res.json({ ...metadata, content, html });
144
144
  }
@@ -146,6 +146,42 @@ function documentsRouter(docsPath) {
146
146
  res.status(500).json({ error: 'Failed to read document' });
147
147
  }
148
148
  });
149
+ // PUT /api/documents/:id — update document content
150
+ router.put('/:id', (req, res) => {
151
+ const id = decodeURIComponent(req.params.id);
152
+ const { content } = req.body;
153
+ if (typeof content !== 'string') {
154
+ return res.status(400).json({ error: 'content is required' });
155
+ }
156
+ const { extraFiles = [] } = (0, config_1.readConfig)(docsPath);
157
+ // Extra file: id is an absolute path without .md
158
+ if (path_1.default.isAbsolute(id)) {
159
+ const filePath = id + '.md';
160
+ if (!extraFiles.includes(filePath)) {
161
+ return res.status(403).json({ error: 'Access denied' });
162
+ }
163
+ try {
164
+ fs_1.default.writeFileSync(filePath, content, 'utf-8');
165
+ return res.json({ ok: true });
166
+ }
167
+ catch {
168
+ return res.status(500).json({ error: 'Failed to write document' });
169
+ }
170
+ }
171
+ // Normal file inside docsPath
172
+ const filename = id + '.md';
173
+ const filePath = safeFilePath(docsPath, filename);
174
+ if (!filePath) {
175
+ return res.status(403).json({ error: 'Access denied' });
176
+ }
177
+ try {
178
+ fs_1.default.writeFileSync(filePath, content, 'utf-8');
179
+ return res.json({ ok: true });
180
+ }
181
+ catch {
182
+ return res.status(500).json({ error: 'Failed to write document' });
183
+ }
184
+ });
149
185
  return router;
150
186
  }
151
187
  //# sourceMappingURL=documents.js.map
@@ -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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"}
@@ -0,0 +1,3 @@
1
+ import { Router } from 'express';
2
+ export declare function imagesRouter(docsPath: string): Router;
3
+ //# sourceMappingURL=images.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"images.d.ts","sourceRoot":"","sources":["../../../src/routes/images.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAqB,MAAM,SAAS,CAAC;AAIpD,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAuCrD"}
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.imagesRouter = imagesRouter;
7
+ const express_1 = require("express");
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
10
+ function imagesRouter(docsPath) {
11
+ const router = (0, express_1.Router)();
12
+ // POST /api/images/upload — save a base64-encoded image to DOCS_FOLDER/images/
13
+ router.post('/upload', (req, res) => {
14
+ const { data, ext } = req.body;
15
+ if (typeof data !== 'string' || !data) {
16
+ return res.status(400).json({ error: 'data is required' });
17
+ }
18
+ const safeExt = (typeof ext === 'string' ? ext.replace(/[^a-z0-9]/gi, '') : 'png').toLowerCase() || 'png';
19
+ // Strip base64 data URL prefix if present
20
+ const base64 = data.replace(/^data:image\/[^;]+;base64,/, '');
21
+ const imagesDir = path_1.default.join(docsPath, 'images');
22
+ if (!fs_1.default.existsSync(imagesDir)) {
23
+ fs_1.default.mkdirSync(imagesDir, { recursive: true });
24
+ }
25
+ const now = new Date();
26
+ const pad = (n) => String(n).padStart(2, '0');
27
+ const timestamp = `${now.getFullYear()}_${pad(now.getMonth() + 1)}_${pad(now.getDate())}` +
28
+ `_${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
29
+ const random = Math.random().toString(36).slice(2, 6);
30
+ const filename = `image_${timestamp}_${random}.${safeExt}`;
31
+ const filePath = path_1.default.join(imagesDir, filename);
32
+ try {
33
+ fs_1.default.writeFileSync(filePath, Buffer.from(base64, 'base64'));
34
+ res.json({ filename });
35
+ }
36
+ catch {
37
+ res.status(500).json({ error: 'Failed to save image' });
38
+ }
39
+ });
40
+ return router;
41
+ }
42
+ //# sourceMappingURL=images.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"images.js","sourceRoot":"","sources":["../../../src/routes/images.ts"],"names":[],"mappings":";;;;;AAIA,oCAuCC;AA3CD,qCAAoD;AACpD,4CAAoB;AACpB,gDAAwB;AAExB,SAAgB,YAAY,CAAC,QAAgB;IAC3C,MAAM,MAAM,GAAG,IAAA,gBAAM,GAAE,CAAC;IAExB,+EAA+E;IAC/E,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,GAAY,EAAE,GAAa,EAAE,EAAE;QACrD,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,GAAG,CAAC,IAAuC,CAAC;QAElE,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,CAAC,IAAI,EAAE,CAAC;YACtC,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,kBAAkB,EAAE,CAAC,CAAC;QAC7D,CAAC;QAED,MAAM,OAAO,GAAG,CAAC,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,WAAW,EAAE,IAAI,KAAK,CAAC;QAE1G,0CAA0C;QAC1C,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,4BAA4B,EAAE,EAAE,CAAC,CAAC;QAE9D,MAAM,SAAS,GAAG,cAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAChD,IAAI,CAAC,YAAE,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAC9B,YAAE,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC/C,CAAC;QAED,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,MAAM,GAAG,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;QACtD,MAAM,SAAS,GACb,GAAG,GAAG,CAAC,WAAW,EAAE,IAAI,GAAG,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,IAAI,GAAG,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,EAAE;YACvE,IAAI,GAAG,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC,EAAE,CAAC;QAC5E,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACtD,MAAM,QAAQ,GAAG,SAAS,SAAS,IAAI,MAAM,IAAI,OAAO,EAAE,CAAC;QAC3D,MAAM,QAAQ,GAAG,cAAI,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;QAEhD,IAAI,CAAC;YACH,YAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;YAC1D,GAAG,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC;QACzB,CAAC;QAAC,MAAM,CAAC;YACP,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,sBAAsB,EAAE,CAAC,CAAC;QAC1D,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/server.ts"],"names":[],"mappings":"AASA,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,CAgD/B"}
@@ -10,19 +10,23 @@ const child_process_1 = require("child_process");
10
10
  const documents_1 = require("./routes/documents");
11
11
  const config_1 = require("./routes/config");
12
12
  const browse_1 = require("./routes/browse");
13
+ const images_1 = require("./routes/images");
13
14
  const config_2 = require("./lib/config");
14
15
  async function startServer({ docsPath, port, openBrowser = false, }) {
15
16
  const app = (0, express_1.default)();
16
- app.use(express_1.default.json());
17
+ app.use(express_1.default.json({ limit: '20mb' }));
17
18
  // Persist initial state to .living-doc.json
18
19
  (0, config_2.writeConfig)(docsPath, { docsFolder: docsPath, port });
19
20
  // API
20
21
  app.use('/api/documents', (0, documents_1.documentsRouter)(docsPath));
21
22
  app.use('/api/config', (0, config_1.configRouter)(docsPath));
22
23
  app.use('/api/browse', (0, browse_1.browseRouter)());
24
+ app.use('/api/images', (0, images_1.imagesRouter)(docsPath));
23
25
  // Static frontend assets
24
26
  const frontendPath = path_1.default.join(__dirname, 'frontend');
25
27
  app.use(express_1.default.static(frontendPath));
28
+ // Static assets from docs folder (images, etc.)
29
+ app.use('/images', express_1.default.static(path_1.default.join(docsPath, 'images')));
26
30
  app.get('/', (_req, res) => res.sendFile(path_1.default.join(frontendPath, 'index.html')));
27
31
  app.get('/admin', (_req, res) => res.sendFile(path_1.default.join(frontendPath, 'admin.html')));
28
32
  return new Promise((resolve) => {
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/server.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "living-documentation",
3
- "version": "1.2.0",
3
+ "version": "2.0.0",
4
4
  "description": "A CLI tool that serves a local Markdown documentation viewer",
5
5
  "main": "dist/src/server.js",
6
6
  "bin": {