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