living-documentation 1.1.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 +163 -214
- 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 +3 -2
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
|
|
|
@@ -11,19 +11,6 @@
|
|
|
11
11
|
tailwind.config = { darkMode: "class", theme: { extend: {} } };
|
|
12
12
|
</script>
|
|
13
13
|
|
|
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
|
|
20
|
-
bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100
|
|
21
|
-
placeholder:text-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 text-sm;
|
|
22
|
-
}
|
|
23
|
-
.field-hint {
|
|
24
|
-
@apply mt-1 text-xs text-gray-400 dark:text-gray-500;
|
|
25
|
-
}
|
|
26
|
-
</style>
|
|
27
14
|
</head>
|
|
28
15
|
|
|
29
16
|
<body
|
|
@@ -61,195 +48,124 @@
|
|
|
61
48
|
</header>
|
|
62
49
|
|
|
63
50
|
<!-- ── Page ── -->
|
|
64
|
-
<main class="max-w-
|
|
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>
|
|
51
|
+
<main class="max-w-6xl mx-auto px-6 py-10">
|
|
79
52
|
|
|
80
|
-
<!-- ──
|
|
81
|
-
<form id="config-form"
|
|
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>
|
|
53
|
+
<!-- ── Two-column layout ── -->
|
|
54
|
+
<form id="config-form" novalidate>
|
|
116
55
|
|
|
117
|
-
<!--
|
|
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
|
-
>
|
|
56
|
+
<!-- Title + Save on same row -->
|
|
57
|
+
<div class="flex items-center justify-between mb-8">
|
|
121
58
|
<div>
|
|
122
|
-
<
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
section.
|
|
59
|
+
<h2 class="text-xl font-bold text-gray-900 dark:text-gray-50">Configuration</h2>
|
|
60
|
+
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
|
61
|
+
Settings are saved to
|
|
62
|
+
<code class="text-xs bg-gray-100 dark:bg-gray-800 px-1.5 py-0.5 rounded">.living-doc.json</code>
|
|
63
|
+
in your docs folder.
|
|
128
64
|
</p>
|
|
129
65
|
</div>
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
↑ 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 -->
|
|
157
|
-
<div>
|
|
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>
|
|
66
|
+
<div class="flex items-center gap-4 shrink-0">
|
|
67
|
+
<div id="save-msg" class="text-sm"></div>
|
|
68
|
+
<button type="submit"
|
|
69
|
+
class="px-5 py-2 rounded-lg bg-blue-600 hover:bg-blue-700 text-white font-semibold text-sm transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-950">
|
|
70
|
+
Save changes
|
|
71
|
+
</button>
|
|
167
72
|
</div>
|
|
168
73
|
</div>
|
|
169
74
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
75
|
+
<div class="grid grid-cols-2 gap-8 items-start">
|
|
76
|
+
|
|
77
|
+
<!-- ── Left column ── -->
|
|
78
|
+
<div class="space-y-6">
|
|
79
|
+
|
|
80
|
+
<!-- Server Info -->
|
|
81
|
+
<div class="rounded-xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 p-5">
|
|
82
|
+
<h3 class="text-sm font-semibold text-gray-700 dark:text-gray-300 mb-4">Server Info</h3>
|
|
83
|
+
<dl class="grid grid-cols-[1fr_auto] gap-x-6 gap-y-3 text-sm">
|
|
84
|
+
<div>
|
|
85
|
+
<dt class="text-xs text-gray-400 uppercase tracking-wide mb-0.5">Docs Folder</dt>
|
|
86
|
+
<dd id="info-folder" class="font-mono text-xs text-gray-700 dark:text-gray-300 break-all">—</dd>
|
|
87
|
+
</div>
|
|
88
|
+
<div>
|
|
89
|
+
<dt class="text-xs text-gray-400 uppercase tracking-wide mb-0.5">Port</dt>
|
|
90
|
+
<dd id="info-port" class="font-mono text-xs text-gray-700 dark:text-gray-300">—</dd>
|
|
91
|
+
</div>
|
|
92
|
+
</dl>
|
|
93
|
+
</div>
|
|
177
94
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
95
|
+
<!-- Extra Files -->
|
|
96
|
+
<div class="rounded-xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 p-5 space-y-4">
|
|
97
|
+
<div>
|
|
98
|
+
<h3 class="text-sm font-semibold text-gray-700 dark:text-gray-300">General — Extra Files</h3>
|
|
99
|
+
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">Add Markdown files from outside the docs folder to the General section.</p>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
<!-- File browser -->
|
|
103
|
+
<div class="rounded-lg border border-gray-200 dark:border-gray-700 overflow-hidden">
|
|
104
|
+
<div class="flex items-center gap-2 px-3 py-2 bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700">
|
|
105
|
+
<button id="browse-up" onclick="browseUp()"
|
|
106
|
+
class="text-xs text-blue-600 dark:text-blue-400 hover:underline disabled:opacity-30 disabled:pointer-events-none shrink-0">
|
|
107
|
+
↑ Up
|
|
108
|
+
</button>
|
|
109
|
+
<span id="browse-path" class="font-mono text-xs text-gray-400 dark:text-gray-500 truncate flex-1 text-right"></span>
|
|
110
|
+
</div>
|
|
111
|
+
<div id="browse-list" class="divide-y divide-gray-100 dark:divide-gray-800 max-h-52 overflow-y-auto"></div>
|
|
112
|
+
</div>
|
|
113
|
+
|
|
114
|
+
<!-- Added files -->
|
|
115
|
+
<div>
|
|
116
|
+
<p class="text-xs font-medium text-gray-500 dark:text-gray-400 mb-2">Added files</p>
|
|
117
|
+
<div id="extra-files-list" class="space-y-1"></div>
|
|
118
|
+
<p id="extra-files-empty" class="text-xs text-gray-400 italic">No extra files added yet.</p>
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
192
121
|
|
|
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
122
|
</div>
|
|
205
123
|
|
|
206
|
-
<!--
|
|
207
|
-
<div>
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
>
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
<
|
|
222
|
-
>
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
124
|
+
<!-- ── Right column ── -->
|
|
125
|
+
<div class="space-y-6">
|
|
126
|
+
|
|
127
|
+
<!-- Appearance & Metadata -->
|
|
128
|
+
<div class="rounded-xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 divide-y divide-gray-100 dark:divide-gray-800">
|
|
129
|
+
<div class="px-5 py-4">
|
|
130
|
+
<h3 class="text-sm font-semibold text-gray-700 dark:text-gray-300">Appearance & Metadata</h3>
|
|
131
|
+
</div>
|
|
132
|
+
<div class="px-5 py-4 space-y-1.5">
|
|
133
|
+
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300" for="field-title">Site Title</label>
|
|
134
|
+
<input id="field-title" name="title" type="text"
|
|
135
|
+
class="w-full px-3 py-2 text-sm rounded-lg border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100 placeholder:text-gray-400 dark:placeholder:text-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
136
|
+
placeholder="Living Documentation" />
|
|
137
|
+
<p class="text-xs text-gray-400 dark:text-gray-500">Displayed in the browser tab and sidebar header.</p>
|
|
138
|
+
</div>
|
|
139
|
+
<div class="px-5 py-4 space-y-1.5">
|
|
140
|
+
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300" for="field-theme">Default Theme</label>
|
|
141
|
+
<select id="field-theme" name="theme"
|
|
142
|
+
class="w-full px-3 py-2 text-sm rounded-lg border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500">
|
|
143
|
+
<option value="system">System (follow OS preference)</option>
|
|
144
|
+
<option value="light">Light</option>
|
|
145
|
+
<option value="dark">Dark</option>
|
|
146
|
+
</select>
|
|
147
|
+
<p class="text-xs text-gray-400 dark:text-gray-500">Users can always override with the toggle button.</p>
|
|
148
|
+
</div>
|
|
149
|
+
<div class="px-5 py-4 space-y-1.5">
|
|
150
|
+
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300" for="field-pattern">Filename Pattern</label>
|
|
151
|
+
<input id="field-pattern" name="filenamePattern" type="text"
|
|
152
|
+
class="w-full px-3 py-2 text-sm rounded-lg border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100 placeholder:text-gray-400 dark:placeholder:text-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
153
|
+
placeholder="YYYY_MM_DD_[Category]_title" />
|
|
154
|
+
<p class="text-xs text-gray-400 dark:text-gray-500">
|
|
155
|
+
Parsed for date, category, and title.
|
|
156
|
+
<span 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
|
+
</p>
|
|
158
|
+
</div>
|
|
159
|
+
</div>
|
|
227
160
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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:
|
|
240
|
-
</p>
|
|
241
|
-
<div id="preview-rows" class="space-y-2 text-sm"></div>
|
|
242
|
-
</div>
|
|
161
|
+
<!-- Pattern Preview -->
|
|
162
|
+
<div id="pattern-preview" class="rounded-xl border border-blue-100 dark:border-blue-900 bg-blue-50 dark:bg-blue-950/30 p-5">
|
|
163
|
+
<h3 class="text-sm font-semibold text-blue-700 dark:text-blue-400 mb-3">Pattern Preview</h3>
|
|
164
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 mb-3">How your pattern parses example filenames:</p>
|
|
165
|
+
<div id="preview-rows" class="space-y-2 text-sm"></div>
|
|
166
|
+
</div>
|
|
243
167
|
|
|
244
|
-
|
|
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>
|
|
168
|
+
</div>
|
|
253
169
|
</div>
|
|
254
170
|
</form>
|
|
255
171
|
</main>
|
|
@@ -304,12 +220,22 @@
|
|
|
304
220
|
|
|
305
221
|
async function saveConfig(e) {
|
|
306
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
|
+
}
|
|
307
235
|
const payload = {
|
|
308
236
|
title: document.getElementById("field-title").value.trim(),
|
|
309
237
|
theme: document.getElementById("field-theme").value,
|
|
310
|
-
filenamePattern
|
|
311
|
-
.getElementById("field-pattern")
|
|
312
|
-
.value.trim(),
|
|
238
|
+
filenamePattern,
|
|
313
239
|
};
|
|
314
240
|
|
|
315
241
|
try {
|
|
@@ -347,29 +273,48 @@
|
|
|
347
273
|
"readme.md",
|
|
348
274
|
];
|
|
349
275
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
const
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
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");
|
|
364
296
|
}
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
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
|
+
}
|
|
312
|
+
}
|
|
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
|
+
}
|
|
373
318
|
}
|
|
374
319
|
return {
|
|
375
320
|
date: null,
|
|
@@ -387,9 +332,13 @@
|
|
|
387
332
|
}
|
|
388
333
|
|
|
389
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);
|
|
390
339
|
const rows = document.getElementById("preview-rows");
|
|
391
340
|
rows.innerHTML = EXAMPLES.map((f) => {
|
|
392
|
-
const p = parsePreview(f);
|
|
341
|
+
const p = parsePreview(f, patterns);
|
|
393
342
|
return `
|
|
394
343
|
<div class="rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 p-3">
|
|
395
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":";;;;;AAwDA,0CAqGC;AA7JD,qCAAoD;AACpD,4CAAoB;AACpB,gDAAwB;AACxB,mCAAgC;AAChC,0CAA2D;AAC3D,0CAA2C;AAE3C,SAAS,QAAQ,CAAC,QAAgB,EAAE,aAAuB,EAAE,EAAE,eAAwB;IACrF,qDAAqD;IACrD,MAAM,SAAS,GAAkB,EAAE,CAAC;IACpC,KAAK,MAAM,QAAQ,IAAI,UAAU,EAAE,CAAC;QAClC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,YAAE,CAAC,UAAU,CAAC,QAAQ,CAAC;YAAE,SAAS;QACpE,MAAM,QAAQ,GAAG,cAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QACzC,MAAM,IAAI,GAAG,IAAA,sBAAa,EAAC,QAAQ,EAAE,eAAe,CAAC,CAAC;QACtD,SAAS,CAAC,IAAI,CAAC;YACb,GAAG,IAAI;YACP,EAAE,EAAE,kBAAkB,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;YAC7C,QAAQ,EAAE,SAAS;SACpB,CAAC,CAAC;IACL,CAAC;IAED,uEAAuE;IACvE,MAAM,WAAW,GAAG,YAAE;SACnB,WAAW,CAAC,QAAQ,CAAC;SACrB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;SAC9C,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,IAAA,sBAAa,EAAC,QAAQ,EAAE,eAAe,CAAC,CAAC,CAAC;IAE/D,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACxB,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI;YAAE,OAAO,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAC1D,IAAI,CAAC,CAAC,IAAI;YAAE,OAAO,CAAC,CAAC,CAAC;QACtB,IAAI,CAAC,CAAC,IAAI;YAAE,OAAO,CAAC,CAAC;QACrB,OAAO,CAAC,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;IAEH,OAAO,CAAC,GAAG,SAAS,EAAE,GAAG,WAAW,CAAC,CAAC;AACxC,CAAC;AAED,SAAS,YAAY,CAAC,QAAgB,EAAE,QAAgB;IACtD,MAAM,QAAQ,GAAG,cAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAClD,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,cAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,cAAI,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IACzE,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,SAAS,cAAc,CACrB,QAAgB,EAChB,GAAgB,EAChB,UAAoB;IAEpB,MAAM,EAAE,GAAG,kBAAkB,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACtC,IAAI,cAAI,CAAC,UAAU,CAAC,EAAE,CAAC,EAAE,CAAC;QACxB,MAAM,QAAQ,GAAG,EAAE,GAAG,KAAK,CAAC;QAC5B,OAAO,UAAU,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC;IACzD,CAAC;IACD,OAAO,YAAY,CAAC,QAAQ,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC;AAC9C,CAAC;AAED,SAAgB,eAAe,CAAC,QAAgB;IAC9C,MAAM,MAAM,GAAG,IAAA,gBAAM,GAAE,CAAC;IAExB,mDAAmD;IACnD,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,IAAa,EAAE,GAAa,EAAE,EAAE;QAC/C,IAAI,CAAC;YACH,MAAM,EAAE,UAAU,GAAG,EAAE,EAAE,eAAe,EAAE,GAAG,IAAA,mBAAU,EAAC,QAAQ,CAAC,CAAC;YAClE,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,UAAU,EAAE,eAAe,CAAC,CAAC,CAAC;QAC5D,CAAC;QAAC,MAAM,CAAC;YACP,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,0BAA0B,EAAE,CAAC,CAAC;QAC9D,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,uDAAuD;IACvD,MAAM,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC,GAAY,EAAE,GAAa,EAAE,EAAE;QACpD,MAAM,KAAK,GAAG,CAAE,GAAG,CAAC,KAAK,CAAC,CAAY,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC;QACnE,IAAI,CAAC,KAAK;YAAE,OAAO,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAEhC,IAAI,CAAC;YACH,MAAM,EAAE,UAAU,GAAG,EAAE,EAAE,eAAe,EAAE,GAAG,IAAA,mBAAU,EAAC,QAAQ,CAAC,CAAC;YAClE,MAAM,IAAI,GAAG,QAAQ,CAAC,QAAQ,EAAE,UAAU,EAAE,eAAe,CAAC,CAAC;YAC7D,MAAM,OAAO,GAA0C,EAAE,CAAC;YAE1D,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;gBACvB,MAAM,QAAQ,GAAG,cAAc,CAAC,QAAQ,EAAE,GAAG,EAAE,UAAU,CAAC,CAAC;gBAC3D,IAAI,CAAC,QAAQ,IAAI,CAAC,YAAE,CAAC,UAAU,CAAC,QAAQ,CAAC;oBAAE,SAAS;gBAEpD,MAAM,OAAO,GAAG,YAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;gBACnD,MAAM,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;gBACxD,MAAM,UAAU,GAAG,GAAG,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;gBAC9D,MAAM,SAAS,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;gBAExD,IAAI,OAAO,IAAI,UAAU,IAAI,SAAS,EAAE,CAAC;oBACvC,IAAI,OAAO,GAAG,EAAE,CAAC;oBACjB,IAAI,SAAS,EAAE,CAAC;wBACd,MAAM,GAAG,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;wBACjD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,GAAG,EAAE,CAAC,CAAC;wBACpC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,GAAG,KAAK,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;wBAC9D,OAAO;4BACL,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;gCACtB,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE;gCACrD,CAAC,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;oBACtC,CAAC;oBACD,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC;gBACpC,CAAC;YACH,CAAC;YAED,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,eAAe,EAAE,CAAC,CAAC;QACnD,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,2EAA2E;IAC3E,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,EAAE;QACvD,MAAM,EAAE,GAAG,kBAAkB,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAC7C,MAAM,EAAE,UAAU,GAAG,EAAE,EAAE,eAAe,EAAE,GAAG,IAAA,mBAAU,EAAC,QAAQ,CAAC,CAAC;QAElE,iDAAiD;QACjD,IAAI,cAAI,CAAC,UAAU,CAAC,EAAE,CAAC,EAAE,CAAC;YACxB,MAAM,QAAQ,GAAG,EAAE,GAAG,KAAK,CAAC;YAC5B,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACnC,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC,CAAC;YAC1D,CAAC;YACD,IAAI,CAAC,YAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAC7B,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,oBAAoB,EAAE,CAAC,CAAC;YAC/D,CAAC;YACD,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,YAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;gBACnD,MAAM,IAAI,GAAG,IAAA,sBAAa,EAAC,cAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,eAAe,CAAC,CAAC;gBACrE,MAAM,IAAI,GAAG,eAAM,CAAC,KAAK,CAAC,OAAO,CAAW,CAAC;gBAC7C,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,IAAI,EAAE,EAAE,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;YAC/E,CAAC;YAAC,MAAM,CAAC;gBACP,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,yBAAyB,EAAE,CAAC,CAAC;YAC7D,CAAC;YACD,OAAO;QACT,CAAC;QAED,8BAA8B;QAC9B,MAAM,QAAQ,GAAG,EAAE,GAAG,KAAK,CAAC;QAC5B,MAAM,QAAQ,GAAG,YAAY,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAElD,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC,CAAC;QAC1D,CAAC;QAED,IAAI,CAAC,YAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC7B,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,oBAAoB,EAAE,CAAC,CAAC;QAC/D,CAAC;QAED,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,YAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;YACnD,MAAM,QAAQ,GAAG,IAAA,sBAAa,EAAC,QAAQ,EAAE,eAAe,CAAC,CAAC;YAC1D,MAAM,IAAI,GAAG,eAAM,CAAC,KAAK,CAAC,OAAO,CAAW,CAAC;YAC7C,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3C,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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "living-documentation",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "A CLI tool that serves a local Markdown documentation viewer",
|
|
5
5
|
"main": "dist/src/server.js",
|
|
6
6
|
"bin": {
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
],
|
|
13
13
|
"scripts": {
|
|
14
14
|
"build": "tsc && node scripts/copy-assets.js && chmod +x dist/bin/cli.js",
|
|
15
|
-
"dev": "ts-node bin/cli.ts",
|
|
15
|
+
"dev": "nodemon --watch src --watch bin --ext ts,html --exec 'ts-node bin/cli.ts'",
|
|
16
16
|
"start": "node dist/bin/cli.js",
|
|
17
17
|
"prepublishOnly": "npm run build"
|
|
18
18
|
},
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/express": "^4.17.21",
|
|
34
34
|
"@types/node": "^20.14.0",
|
|
35
|
+
"nodemon": "^3.1.14",
|
|
35
36
|
"ts-node": "^10.9.2",
|
|
36
37
|
"typescript": "^5.4.5"
|
|
37
38
|
},
|