living-documentation 1.2.0 → 1.4.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 +33 -12
- package/dist/src/frontend/admin.html +60 -27
- package/dist/src/frontend/index.html +11 -3
- package/dist/src/lib/parser.d.ts +1 -1
- package/dist/src/lib/parser.d.ts.map +1 -1
- package/dist/src/lib/parser.js +63 -29
- package/dist/src/lib/parser.js.map +1 -1
- package/dist/src/routes/config.d.ts.map +1 -1
- package/dist/src/routes/config.js +10 -0
- package/dist/src/routes/config.js.map +1 -1
- package/dist/src/routes/documents.js +10 -10
- package/dist/src/routes/documents.js.map +1 -1
- package/package.json +1 -1
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 #
|
|
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 **
|
|
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
|
-
│ │
|
|
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
|
|
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
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
const
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
date:
|
|
285
|
-
|
|
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>
|
|
@@ -222,10 +222,15 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|
|
222
222
|
await loadConfig();
|
|
223
223
|
await loadDocuments();
|
|
224
224
|
|
|
225
|
-
// Deep-link via ?doc=id
|
|
225
|
+
// Deep-link via ?doc=id, otherwise open first General doc
|
|
226
226
|
const params = new URLSearchParams(location.search);
|
|
227
227
|
const docId = params.get('doc');
|
|
228
|
-
if (docId)
|
|
228
|
+
if (docId) {
|
|
229
|
+
openDocument(docId, true);
|
|
230
|
+
} else {
|
|
231
|
+
const first = allDocs.find(d => d.category === 'General') ?? allDocs[0];
|
|
232
|
+
if (first) openDocument(first.id, true);
|
|
233
|
+
}
|
|
229
234
|
});
|
|
230
235
|
|
|
231
236
|
// ── Dark mode ──────────────────────────────────────────────
|
|
@@ -254,6 +259,9 @@ async function loadConfig() {
|
|
|
254
259
|
const cfg = await fetch('/api/config').then(r => r.json());
|
|
255
260
|
if (cfg.title) document.title = cfg.title;
|
|
256
261
|
document.getElementById('app-title').textContent = cfg.title || 'Living Documentation';
|
|
262
|
+
if (cfg.filenamePattern) {
|
|
263
|
+
document.getElementById('welcome-pattern').textContent = cfg.filenamePattern + '.md';
|
|
264
|
+
}
|
|
257
265
|
} catch { /* non-fatal */ }
|
|
258
266
|
}
|
|
259
267
|
|
package/dist/src/lib/parser.d.ts
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/src/lib/parser.js
CHANGED
|
@@ -1,10 +1,45 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.parseFilename = parseFilename;
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
const
|
|
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 =
|
|
27
|
-
if (
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
id,
|
|
45
|
-
|
|
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":";;
|
|
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,
|
|
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,
|
|
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"}
|
|
@@ -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
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"documents.js","sourceRoot":"","sources":["../../../src/routes/documents.ts"],"names":[],"mappings":";;;;;AAwDA,0CAqGC;AA7JD,qCAAoD;AACpD,4CAAoB;AACpB,gDAAwB;AACxB,mCAAgC;AAChC,0CAA2D;AAC3D,0CAA2C;AAE3C,SAAS,QAAQ,CAAC,QAAgB,EAAE,aAAuB,EAAE;
|
|
1
|
+
{"version":3,"file":"documents.js","sourceRoot":"","sources":["../../../src/routes/documents.ts"],"names":[],"mappings":"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"}
|