living-documentation 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +178 -0
- package/dist/bin/cli.d.ts +3 -0
- package/dist/bin/cli.d.ts.map +1 -0
- package/dist/bin/cli.js +38 -0
- package/dist/bin/cli.js.map +1 -0
- package/dist/src/frontend/admin.html +268 -0
- package/dist/src/frontend/index.html +508 -0
- package/dist/src/lib/config.d.ts +11 -0
- package/dist/src/lib/config.d.ts.map +1 -0
- package/dist/src/lib/config.js +43 -0
- package/dist/src/lib/config.js.map +1 -0
- package/dist/src/lib/parser.d.ts +10 -0
- package/dist/src/lib/parser.d.ts.map +1 -0
- package/dist/src/lib/parser.js +63 -0
- package/dist/src/lib/parser.js.map +1 -0
- package/dist/src/routes/config.d.ts +3 -0
- package/dist/src/routes/config.d.ts.map +1 -0
- package/dist/src/routes/config.js +42 -0
- package/dist/src/routes/config.js.map +1 -0
- package/dist/src/routes/documents.d.ts +3 -0
- package/dist/src/routes/documents.d.ts.map +1 -0
- package/dist/src/routes/documents.js +106 -0
- package/dist/src/routes/documents.js.map +1 -0
- package/dist/src/server.d.ts +7 -0
- package/dist/src/server.d.ts.map +1 -0
- package/dist/src/server.js +54 -0
- package/dist/src/server.js.map +1 -0
- package/package.json +41 -0
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en" class="h-full">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Living Documentation</title>
|
|
7
|
+
|
|
8
|
+
<!-- Tailwind CSS + Typography plugin via CDN -->
|
|
9
|
+
<script src="https://cdn.tailwindcss.com?plugins=typography"></script>
|
|
10
|
+
|
|
11
|
+
<!-- Highlight.js — syntax highlighting -->
|
|
12
|
+
<link id="hljs-light" rel="stylesheet"
|
|
13
|
+
href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css" />
|
|
14
|
+
<link id="hljs-dark" rel="stylesheet" disabled
|
|
15
|
+
href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css" />
|
|
16
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
|
17
|
+
|
|
18
|
+
<script>
|
|
19
|
+
tailwind.config = {
|
|
20
|
+
darkMode: 'class',
|
|
21
|
+
theme: { extend: {} }
|
|
22
|
+
};
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<style>
|
|
26
|
+
/* Smooth sidebar transitions */
|
|
27
|
+
.category-docs { transition: max-height 0.2s ease, opacity 0.2s ease; }
|
|
28
|
+
.category-docs.collapsed { max-height: 0; opacity: 0; overflow: hidden; }
|
|
29
|
+
.category-docs.expanded { max-height: 9999px; opacity: 1; }
|
|
30
|
+
|
|
31
|
+
/* Active doc highlight */
|
|
32
|
+
.doc-item.active {
|
|
33
|
+
background-color: rgba(59,130,246,0.12);
|
|
34
|
+
color: rgb(59,130,246);
|
|
35
|
+
font-weight: 600;
|
|
36
|
+
}
|
|
37
|
+
.dark .doc-item.active {
|
|
38
|
+
background-color: rgba(96,165,250,0.15);
|
|
39
|
+
color: rgb(96,165,250);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* Prose overrides for correct dark mode */
|
|
43
|
+
.dark .prose { color: #d1d5db; }
|
|
44
|
+
.dark .prose h1, .dark .prose h2, .dark .prose h3,
|
|
45
|
+
.dark .prose h4, .dark .prose h5, .dark .prose h6 { color: #f9fafb; }
|
|
46
|
+
.dark .prose a { color: #60a5fa; }
|
|
47
|
+
.dark .prose strong { color: #f3f4f6; }
|
|
48
|
+
.dark .prose code:not(pre code) {
|
|
49
|
+
background: #374151; color: #f87171; padding: 0.1em 0.35em;
|
|
50
|
+
border-radius: 0.25em; font-size: 0.875em;
|
|
51
|
+
}
|
|
52
|
+
.dark .prose pre { background: #1f2937; border: 1px solid #374151; }
|
|
53
|
+
.dark .prose blockquote { border-color: #4b5563; color: #9ca3af; }
|
|
54
|
+
.dark .prose hr { border-color: #374151; }
|
|
55
|
+
.dark .prose table th { background: #1f2937; color: #f9fafb; }
|
|
56
|
+
.dark .prose table td { border-color: #374151; }
|
|
57
|
+
.dark .prose table tr:nth-child(even) { background: #111827; }
|
|
58
|
+
|
|
59
|
+
/* Print / Export PDF */
|
|
60
|
+
@media print {
|
|
61
|
+
#sidebar, #header { display: none !important; }
|
|
62
|
+
#content-area { margin: 0 !important; }
|
|
63
|
+
.no-print { display: none !important; }
|
|
64
|
+
.prose { max-width: 100% !important; }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* Scrollbar thin */
|
|
68
|
+
::-webkit-scrollbar { width: 6px; height: 6px; }
|
|
69
|
+
::-webkit-scrollbar-track { background: transparent; }
|
|
70
|
+
::-webkit-scrollbar-thumb { background: #d1d5db; border-radius: 3px; }
|
|
71
|
+
.dark ::-webkit-scrollbar-thumb { background: #4b5563; }
|
|
72
|
+
|
|
73
|
+
/* Search highlight */
|
|
74
|
+
mark { background: #fef08a; color: inherit; border-radius: 2px; padding: 0 2px; }
|
|
75
|
+
.dark mark { background: #713f12; color: #fef9c3; }
|
|
76
|
+
</style>
|
|
77
|
+
</head>
|
|
78
|
+
|
|
79
|
+
<body class="h-full bg-gray-50 dark:bg-gray-950 text-gray-900 dark:text-gray-100 transition-colors duration-200">
|
|
80
|
+
<div class="h-full flex flex-col">
|
|
81
|
+
|
|
82
|
+
<!-- ── Header ── -->
|
|
83
|
+
<header id="header"
|
|
84
|
+
class="no-print flex items-center justify-between px-4 h-14 border-b border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 shrink-0 z-10 shadow-sm">
|
|
85
|
+
|
|
86
|
+
<div class="flex items-center gap-3">
|
|
87
|
+
<span class="text-blue-600 dark:text-blue-400 text-xl select-none" aria-hidden="true">📚</span>
|
|
88
|
+
<span id="app-title" class="font-semibold text-base tracking-tight">Living Documentation</span>
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
<div class="flex items-center gap-2">
|
|
92
|
+
<!-- Search (desktop — mirrors sidebar search) -->
|
|
93
|
+
<div class="relative hidden sm:block">
|
|
94
|
+
<input id="header-search" type="search" placeholder="Search docs…"
|
|
95
|
+
class="w-56 pl-8 pr-3 py-1.5 text-sm rounded-lg border border-gray-200 dark:border-gray-700
|
|
96
|
+
bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100
|
|
97
|
+
placeholder:text-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500" />
|
|
98
|
+
<span class="absolute left-2.5 top-2 text-gray-400 text-sm pointer-events-none">🔍</span>
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
<!-- Dark mode toggle -->
|
|
102
|
+
<button id="dark-toggle" title="Toggle dark mode"
|
|
103
|
+
class="p-2 rounded-lg text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors">
|
|
104
|
+
<span id="dark-icon" class="text-lg leading-none">☾</span>
|
|
105
|
+
</button>
|
|
106
|
+
|
|
107
|
+
<!-- Admin link -->
|
|
108
|
+
<a href="/admin"
|
|
109
|
+
class="text-sm px-3 py-1.5 rounded-lg text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors font-medium">
|
|
110
|
+
⚙ Admin
|
|
111
|
+
</a>
|
|
112
|
+
</div>
|
|
113
|
+
</header>
|
|
114
|
+
|
|
115
|
+
<!-- ── Body ── -->
|
|
116
|
+
<div class="flex flex-1 overflow-hidden">
|
|
117
|
+
|
|
118
|
+
<!-- ── Sidebar ── -->
|
|
119
|
+
<aside id="sidebar"
|
|
120
|
+
class="no-print w-72 shrink-0 flex flex-col border-r border-gray-200 dark:border-gray-800
|
|
121
|
+
bg-white dark:bg-gray-900 overflow-y-auto">
|
|
122
|
+
|
|
123
|
+
<!-- Mobile / sidebar search -->
|
|
124
|
+
<div class="p-3 border-b border-gray-100 dark:border-gray-800">
|
|
125
|
+
<div class="relative sm:hidden">
|
|
126
|
+
<input id="sidebar-search" type="search" placeholder="Search…"
|
|
127
|
+
class="w-full pl-8 pr-3 py-1.5 text-sm rounded-lg border border-gray-200 dark:border-gray-700
|
|
128
|
+
bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100
|
|
129
|
+
placeholder:text-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500" />
|
|
130
|
+
<span class="absolute left-2.5 top-2 text-gray-400 text-sm pointer-events-none">🔍</span>
|
|
131
|
+
</div>
|
|
132
|
+
|
|
133
|
+
<!-- Stats -->
|
|
134
|
+
<p id="doc-count" class="text-xs text-gray-400 dark:text-gray-500 mt-2 sm:mt-0"></p>
|
|
135
|
+
</div>
|
|
136
|
+
|
|
137
|
+
<!-- Category tree -->
|
|
138
|
+
<nav id="category-tree" class="flex-1 py-2 space-y-0.5">
|
|
139
|
+
<p class="px-4 py-8 text-sm text-gray-400 text-center">Loading…</p>
|
|
140
|
+
</nav>
|
|
141
|
+
</aside>
|
|
142
|
+
|
|
143
|
+
<!-- ── Main content ── -->
|
|
144
|
+
<main id="content-area" class="flex-1 overflow-y-auto">
|
|
145
|
+
|
|
146
|
+
<!-- Welcome / empty state -->
|
|
147
|
+
<div id="welcome" class="h-full flex items-center justify-center p-8">
|
|
148
|
+
<div class="max-w-md text-center">
|
|
149
|
+
<div class="text-5xl mb-4 select-none" aria-hidden="true">📚</div>
|
|
150
|
+
<h2 class="text-xl font-semibold text-gray-700 dark:text-gray-300 mb-2">
|
|
151
|
+
Select a document
|
|
152
|
+
</h2>
|
|
153
|
+
<p class="text-sm text-gray-500 dark:text-gray-500">
|
|
154
|
+
Choose a document from the sidebar to start reading.
|
|
155
|
+
</p>
|
|
156
|
+
<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">
|
|
157
|
+
YYYY_MM_DD_[Category]_title.md
|
|
158
|
+
</p>
|
|
159
|
+
<p class="mt-2 text-xs text-gray-400">Expected filename pattern</p>
|
|
160
|
+
</div>
|
|
161
|
+
</div>
|
|
162
|
+
|
|
163
|
+
<!-- Document viewer -->
|
|
164
|
+
<article id="doc-view" class="hidden max-w-4xl mx-auto px-6 py-8">
|
|
165
|
+
<!-- Doc header -->
|
|
166
|
+
<header class="mb-8 pb-6 border-b border-gray-100 dark:border-gray-800">
|
|
167
|
+
<div class="flex items-start justify-between gap-4 flex-wrap">
|
|
168
|
+
<div class="flex-1 min-w-0">
|
|
169
|
+
<div class="flex items-center gap-2 mb-2 flex-wrap">
|
|
170
|
+
<span id="doc-category"
|
|
171
|
+
class="inline-block text-xs font-semibold px-2.5 py-0.5 rounded-full
|
|
172
|
+
bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-300">
|
|
173
|
+
</span>
|
|
174
|
+
<span id="doc-date" class="text-xs text-gray-400 dark:text-gray-500"></span>
|
|
175
|
+
</div>
|
|
176
|
+
<h1 id="doc-title" class="text-2xl font-bold text-gray-900 dark:text-gray-50 leading-tight"></h1>
|
|
177
|
+
</div>
|
|
178
|
+
<!-- Actions -->
|
|
179
|
+
<div class="flex items-center gap-2 shrink-0">
|
|
180
|
+
<button onclick="exportPDF()" title="Export as PDF"
|
|
181
|
+
class="no-print text-sm px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-700
|
|
182
|
+
text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors">
|
|
183
|
+
📄 Export PDF
|
|
184
|
+
</button>
|
|
185
|
+
<button onclick="copyLink()" id="copy-link-btn" title="Copy link"
|
|
186
|
+
class="no-print text-sm px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-700
|
|
187
|
+
text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors">
|
|
188
|
+
🔗 Copy link
|
|
189
|
+
</button>
|
|
190
|
+
</div>
|
|
191
|
+
</div>
|
|
192
|
+
</header>
|
|
193
|
+
|
|
194
|
+
<!-- Search match notice -->
|
|
195
|
+
<div id="search-notice" class="hidden mb-4 px-3 py-2 rounded-lg bg-yellow-50 dark:bg-yellow-900/20
|
|
196
|
+
border border-yellow-200 dark:border-yellow-800 text-sm text-yellow-800 dark:text-yellow-300">
|
|
197
|
+
</div>
|
|
198
|
+
|
|
199
|
+
<!-- Rendered markdown -->
|
|
200
|
+
<div id="doc-content"
|
|
201
|
+
class="prose prose-gray dark:prose-invert max-w-none
|
|
202
|
+
prose-headings:scroll-mt-4 prose-a:text-blue-600 dark:prose-a:text-blue-400
|
|
203
|
+
prose-code:before:content-none prose-code:after:content-none
|
|
204
|
+
prose-pre:bg-gray-50 prose-pre:border prose-pre:border-gray-200
|
|
205
|
+
dark:prose-pre:bg-gray-900 dark:prose-pre:border-gray-700">
|
|
206
|
+
</div>
|
|
207
|
+
</article>
|
|
208
|
+
|
|
209
|
+
</main>
|
|
210
|
+
</div><!-- end body row -->
|
|
211
|
+
</div><!-- end root -->
|
|
212
|
+
|
|
213
|
+
<script>
|
|
214
|
+
// ── State ──────────────────────────────────────────────────
|
|
215
|
+
let allDocs = [];
|
|
216
|
+
let currentDocId = null;
|
|
217
|
+
let searchQuery = '';
|
|
218
|
+
let expandedCategories = new Set();
|
|
219
|
+
|
|
220
|
+
// ── Boot ───────────────────────────────────────────────────
|
|
221
|
+
document.addEventListener('DOMContentLoaded', async () => {
|
|
222
|
+
applyDarkMode(loadDarkPref());
|
|
223
|
+
setupDarkToggle();
|
|
224
|
+
setupSearch();
|
|
225
|
+
await loadConfig();
|
|
226
|
+
await loadDocuments();
|
|
227
|
+
|
|
228
|
+
// Deep-link via ?doc=id
|
|
229
|
+
const params = new URLSearchParams(location.search);
|
|
230
|
+
const docId = params.get('doc');
|
|
231
|
+
if (docId) openDocument(docId, true);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// ── Dark mode ──────────────────────────────────────────────
|
|
235
|
+
function loadDarkPref() {
|
|
236
|
+
const saved = localStorage.getItem('ld-dark');
|
|
237
|
+
if (saved !== null) return saved === 'true';
|
|
238
|
+
return window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function applyDarkMode(dark) {
|
|
242
|
+
document.documentElement.classList.toggle('dark', dark);
|
|
243
|
+
document.getElementById('dark-icon').textContent = dark ? '☀' : '☾';
|
|
244
|
+
document.getElementById('hljs-light').disabled = dark;
|
|
245
|
+
document.getElementById('hljs-dark').disabled = !dark;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function setupDarkToggle() {
|
|
249
|
+
document.getElementById('dark-toggle').addEventListener('click', () => {
|
|
250
|
+
const isDark = document.documentElement.classList.toggle('dark');
|
|
251
|
+
localStorage.setItem('ld-dark', isDark);
|
|
252
|
+
document.getElementById('dark-icon').textContent = isDark ? '☀' : '☾';
|
|
253
|
+
document.getElementById('hljs-light').disabled = isDark;
|
|
254
|
+
document.getElementById('hljs-dark').disabled = !isDark;
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// ── Config ─────────────────────────────────────────────────
|
|
259
|
+
async function loadConfig() {
|
|
260
|
+
try {
|
|
261
|
+
const cfg = await fetch('/api/config').then(r => r.json());
|
|
262
|
+
if (cfg.title) document.title = cfg.title;
|
|
263
|
+
document.getElementById('app-title').textContent = cfg.title || 'Living Documentation';
|
|
264
|
+
} catch { /* non-fatal */ }
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// ── Documents ──────────────────────────────────────────────
|
|
268
|
+
async function loadDocuments() {
|
|
269
|
+
try {
|
|
270
|
+
allDocs = await fetch('/api/documents').then(r => r.json());
|
|
271
|
+
renderSidebar(allDocs);
|
|
272
|
+
} catch {
|
|
273
|
+
document.getElementById('category-tree').innerHTML =
|
|
274
|
+
'<p class="px-4 py-4 text-sm text-red-500">Failed to load documents.</p>';
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function renderSidebar(docs) {
|
|
279
|
+
const tree = document.getElementById('category-tree');
|
|
280
|
+
const countEl = document.getElementById('doc-count');
|
|
281
|
+
|
|
282
|
+
if (!docs.length) {
|
|
283
|
+
tree.innerHTML = '<p class="px-4 py-8 text-sm text-gray-400 text-center">No documents found.</p>';
|
|
284
|
+
countEl.textContent = '';
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
countEl.textContent = `${docs.length} document${docs.length !== 1 ? 's' : ''}`;
|
|
289
|
+
|
|
290
|
+
// Group by category
|
|
291
|
+
const groups = {};
|
|
292
|
+
for (const doc of docs) {
|
|
293
|
+
if (!groups[doc.category]) groups[doc.category] = [];
|
|
294
|
+
groups[doc.category].push(doc);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Sort categories: Uncategorized last
|
|
298
|
+
const cats = Object.keys(groups).sort((a, b) => {
|
|
299
|
+
if (a === 'Uncategorized') return 1;
|
|
300
|
+
if (b === 'Uncategorized') return -1;
|
|
301
|
+
return a.localeCompare(b);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
// Auto-expand all initially
|
|
305
|
+
if (expandedCategories.size === 0) cats.forEach(c => expandedCategories.add(c));
|
|
306
|
+
|
|
307
|
+
tree.innerHTML = cats.map(cat => {
|
|
308
|
+
const catDocs = groups[cat];
|
|
309
|
+
const isExpanded = expandedCategories.has(cat);
|
|
310
|
+
const catId = 'cat-' + cat.replace(/\W/g, '-');
|
|
311
|
+
return `
|
|
312
|
+
<div class="mb-1">
|
|
313
|
+
<button onclick="toggleCategory('${esc(cat)}')"
|
|
314
|
+
class="w-full flex items-center justify-between px-3 py-1.5 text-xs font-semibold
|
|
315
|
+
text-gray-500 dark:text-gray-400 uppercase tracking-wider
|
|
316
|
+
hover:bg-gray-50 dark:hover:bg-gray-800/60 rounded-md transition-colors group">
|
|
317
|
+
<span>${esc(cat)}</span>
|
|
318
|
+
<span class="flex items-center gap-1.5">
|
|
319
|
+
<span class="font-normal normal-case text-gray-400">${catDocs.length}</span>
|
|
320
|
+
<span class="transition-transform duration-200 ${isExpanded ? 'rotate-90' : ''}" id="arrow-${catId}">▸</span>
|
|
321
|
+
</span>
|
|
322
|
+
</button>
|
|
323
|
+
<div id="${catId}" class="category-docs pl-2 ${isExpanded ? 'expanded' : 'collapsed'}">
|
|
324
|
+
${catDocs.map(doc => renderDocItem(doc)).join('')}
|
|
325
|
+
</div>
|
|
326
|
+
</div>`;
|
|
327
|
+
}).join('');
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function renderDocItem(doc) {
|
|
331
|
+
const isActive = doc.id === currentDocId;
|
|
332
|
+
return `
|
|
333
|
+
<button onclick="openDocument('${esc(doc.id)}')"
|
|
334
|
+
id="item-${esc(doc.id)}"
|
|
335
|
+
class="doc-item w-full text-left px-3 py-1.5 rounded-md text-sm transition-colors
|
|
336
|
+
text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-800/60
|
|
337
|
+
${isActive ? 'active' : ''}">
|
|
338
|
+
<div class="leading-snug truncate">${esc(doc.title)}</div>
|
|
339
|
+
${doc.formattedDate ? `<div class="text-xs text-gray-400 dark:text-gray-500 mt-0.5">${esc(doc.formattedDate)}</div>` : ''}
|
|
340
|
+
</button>`;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function toggleCategory(cat) {
|
|
344
|
+
const catId = 'cat-' + cat.replace(/\W/g, '-');
|
|
345
|
+
const el = document.getElementById(catId);
|
|
346
|
+
const arrow = document.getElementById('arrow-' + catId);
|
|
347
|
+
if (!el) return;
|
|
348
|
+
const expanding = el.classList.contains('collapsed');
|
|
349
|
+
el.classList.toggle('collapsed', !expanding);
|
|
350
|
+
el.classList.toggle('expanded', expanding);
|
|
351
|
+
if (arrow) arrow.style.transform = expanding ? 'rotate(90deg)' : '';
|
|
352
|
+
if (expanding) expandedCategories.add(cat); else expandedCategories.delete(cat);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// ── Open document ──────────────────────────────────────────
|
|
356
|
+
async function openDocument(id, skipHistory = false) {
|
|
357
|
+
currentDocId = id;
|
|
358
|
+
|
|
359
|
+
// Update active state in sidebar
|
|
360
|
+
document.querySelectorAll('.doc-item').forEach(el => el.classList.remove('active'));
|
|
361
|
+
const activeItem = document.getElementById('item-' + id);
|
|
362
|
+
if (activeItem) {
|
|
363
|
+
activeItem.classList.add('active');
|
|
364
|
+
activeItem.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Update URL
|
|
368
|
+
if (!skipHistory) {
|
|
369
|
+
const url = new URL(location.href);
|
|
370
|
+
url.searchParams.set('doc', id);
|
|
371
|
+
history.pushState({ docId: id }, '', url);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
document.getElementById('welcome').classList.add('hidden');
|
|
375
|
+
const docView = document.getElementById('doc-view');
|
|
376
|
+
docView.classList.remove('hidden');
|
|
377
|
+
document.getElementById('doc-content').innerHTML =
|
|
378
|
+
'<p class="animate-pulse text-gray-400">Loading…</p>';
|
|
379
|
+
|
|
380
|
+
try {
|
|
381
|
+
const doc = await fetch('/api/documents/' + id).then(r => {
|
|
382
|
+
if (!r.ok) throw new Error(r.statusText);
|
|
383
|
+
return r.json();
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
document.getElementById('doc-title').textContent = doc.title;
|
|
387
|
+
document.getElementById('doc-category').textContent = doc.category;
|
|
388
|
+
document.getElementById('doc-date').textContent = doc.formattedDate || '';
|
|
389
|
+
|
|
390
|
+
const contentEl = document.getElementById('doc-content');
|
|
391
|
+
contentEl.innerHTML = doc.html;
|
|
392
|
+
|
|
393
|
+
// Syntax highlighting
|
|
394
|
+
contentEl.querySelectorAll('pre code').forEach(block => {
|
|
395
|
+
hljs.highlightElement(block);
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
// Make tables responsive
|
|
399
|
+
contentEl.querySelectorAll('table').forEach(t => {
|
|
400
|
+
const wrapper = document.createElement('div');
|
|
401
|
+
wrapper.className = 'overflow-x-auto';
|
|
402
|
+
t.parentNode.insertBefore(wrapper, t);
|
|
403
|
+
wrapper.appendChild(t);
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
// Highlight search matches in content
|
|
407
|
+
const notice = document.getElementById('search-notice');
|
|
408
|
+
if (searchQuery) {
|
|
409
|
+
highlightMatches(contentEl, searchQuery);
|
|
410
|
+
notice.textContent = `Showing matches for "${searchQuery}"`;
|
|
411
|
+
notice.classList.remove('hidden');
|
|
412
|
+
} else {
|
|
413
|
+
notice.classList.add('hidden');
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
document.title = doc.title;
|
|
417
|
+
docView.scrollTop = 0;
|
|
418
|
+
} catch (err) {
|
|
419
|
+
document.getElementById('doc-content').innerHTML =
|
|
420
|
+
`<p class="text-red-500">Failed to load document: ${err.message}</p>`;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// ── Search ─────────────────────────────────────────────────
|
|
425
|
+
let searchTimer = null;
|
|
426
|
+
|
|
427
|
+
function setupSearch() {
|
|
428
|
+
['header-search', 'sidebar-search'].forEach(id => {
|
|
429
|
+
const el = document.getElementById(id);
|
|
430
|
+
if (!el) return;
|
|
431
|
+
el.addEventListener('input', e => {
|
|
432
|
+
const q = e.target.value.trim();
|
|
433
|
+
// Sync the other input
|
|
434
|
+
['header-search', 'sidebar-search'].forEach(oid => {
|
|
435
|
+
if (oid !== id) {
|
|
436
|
+
const other = document.getElementById(oid);
|
|
437
|
+
if (other) other.value = q;
|
|
438
|
+
}
|
|
439
|
+
});
|
|
440
|
+
clearTimeout(searchTimer);
|
|
441
|
+
if (!q) {
|
|
442
|
+
searchQuery = '';
|
|
443
|
+
renderSidebar(allDocs);
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
searchQuery = q;
|
|
447
|
+
// Immediate client-side filter for snappy UX
|
|
448
|
+
const local = allDocs.filter(d =>
|
|
449
|
+
d.title.toLowerCase().includes(q.toLowerCase()) ||
|
|
450
|
+
d.category.toLowerCase().includes(q.toLowerCase())
|
|
451
|
+
);
|
|
452
|
+
renderSidebar(local);
|
|
453
|
+
// Then full-text search from server
|
|
454
|
+
searchTimer = setTimeout(() => doSearch(q), 350);
|
|
455
|
+
});
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
async function doSearch(q) {
|
|
460
|
+
try {
|
|
461
|
+
const results = await fetch('/api/documents/search?q=' + encodeURIComponent(q))
|
|
462
|
+
.then(r => r.json());
|
|
463
|
+
renderSidebar(results);
|
|
464
|
+
} catch { /* ignore */ }
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// ── Helpers ────────────────────────────────────────────────
|
|
468
|
+
function exportPDF() { window.print(); }
|
|
469
|
+
|
|
470
|
+
function copyLink() {
|
|
471
|
+
navigator.clipboard.writeText(location.href).then(() => {
|
|
472
|
+
const btn = document.getElementById('copy-link-btn');
|
|
473
|
+
const orig = btn.innerHTML;
|
|
474
|
+
btn.innerHTML = '✓ Copied!';
|
|
475
|
+
setTimeout(() => { btn.innerHTML = orig; }, 1800);
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function highlightMatches(el, q) {
|
|
480
|
+
const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
|
|
481
|
+
const nodes = [];
|
|
482
|
+
while (walker.nextNode()) nodes.push(walker.currentNode);
|
|
483
|
+
const re = new RegExp(`(${q.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi');
|
|
484
|
+
nodes.forEach(node => {
|
|
485
|
+
if (!node.textContent.toLowerCase().includes(q.toLowerCase())) return;
|
|
486
|
+
const span = document.createElement('span');
|
|
487
|
+
span.innerHTML = node.textContent.replace(re, '<mark>$1</mark>');
|
|
488
|
+
node.parentNode.replaceChild(span, node);
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function esc(str) {
|
|
493
|
+
return String(str)
|
|
494
|
+
.replace(/&/g, '&')
|
|
495
|
+
.replace(/</g, '<')
|
|
496
|
+
.replace(/>/g, '>')
|
|
497
|
+
.replace(/"/g, '"')
|
|
498
|
+
.replace(/'/g, ''');
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Browser back/forward
|
|
502
|
+
window.addEventListener('popstate', e => {
|
|
503
|
+
const id = e.state?.docId || new URLSearchParams(location.search).get('doc');
|
|
504
|
+
if (id) openDocument(id, true);
|
|
505
|
+
});
|
|
506
|
+
</script>
|
|
507
|
+
</body>
|
|
508
|
+
</html>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface LivingDocConfig {
|
|
2
|
+
docsFolder: string;
|
|
3
|
+
filenamePattern: string;
|
|
4
|
+
title: string;
|
|
5
|
+
theme: 'light' | 'dark' | 'system';
|
|
6
|
+
port: number;
|
|
7
|
+
}
|
|
8
|
+
export declare function getConfigPath(docsPath: string): string;
|
|
9
|
+
export declare function readConfig(docsPath: string): LivingDocConfig;
|
|
10
|
+
export declare function writeConfig(docsPath: string, patch: Partial<LivingDocConfig>): LivingDocConfig;
|
|
11
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/lib/config.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC;IACnC,IAAI,EAAE,MAAM,CAAC;CACd;AAYD,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,eAAe,CAY5D;AAED,wBAAgB,WAAW,CACzB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,OAAO,CAAC,eAAe,CAAC,GAC9B,eAAe,CAMjB"}
|
|
@@ -0,0 +1,43 @@
|
|
|
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.getConfigPath = getConfigPath;
|
|
7
|
+
exports.readConfig = readConfig;
|
|
8
|
+
exports.writeConfig = writeConfig;
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const CONFIG_FILENAME = '.living-doc.json';
|
|
12
|
+
const DEFAULTS = {
|
|
13
|
+
docsFolder: '.',
|
|
14
|
+
filenamePattern: 'YYYY_MM_DD_[Category]_title',
|
|
15
|
+
title: 'Living Documentation',
|
|
16
|
+
theme: 'system',
|
|
17
|
+
port: 4321,
|
|
18
|
+
};
|
|
19
|
+
function getConfigPath(docsPath) {
|
|
20
|
+
return path_1.default.join(docsPath, CONFIG_FILENAME);
|
|
21
|
+
}
|
|
22
|
+
function readConfig(docsPath) {
|
|
23
|
+
const configPath = getConfigPath(docsPath);
|
|
24
|
+
try {
|
|
25
|
+
if (fs_1.default.existsSync(configPath)) {
|
|
26
|
+
const raw = fs_1.default.readFileSync(configPath, 'utf-8');
|
|
27
|
+
const parsed = JSON.parse(raw);
|
|
28
|
+
return { ...DEFAULTS, ...parsed };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// corrupt config — return defaults
|
|
33
|
+
}
|
|
34
|
+
return { ...DEFAULTS, docsFolder: docsPath };
|
|
35
|
+
}
|
|
36
|
+
function writeConfig(docsPath, patch) {
|
|
37
|
+
const current = readConfig(docsPath);
|
|
38
|
+
const updated = { ...current, ...patch };
|
|
39
|
+
const configPath = getConfigPath(docsPath);
|
|
40
|
+
fs_1.default.writeFileSync(configPath, JSON.stringify(updated, null, 2), 'utf-8');
|
|
41
|
+
return updated;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../../src/lib/config.ts"],"names":[],"mappings":";;;;;AAqBA,sCAEC;AAED,gCAYC;AAED,kCASC;AAhDD,4CAAoB;AACpB,gDAAwB;AAUxB,MAAM,eAAe,GAAG,kBAAkB,CAAC;AAE3C,MAAM,QAAQ,GAAoB;IAChC,UAAU,EAAE,GAAG;IACf,eAAe,EAAE,6BAA6B;IAC9C,KAAK,EAAE,sBAAsB;IAC7B,KAAK,EAAE,QAAQ;IACf,IAAI,EAAE,IAAI;CACX,CAAC;AAEF,SAAgB,aAAa,CAAC,QAAgB;IAC5C,OAAO,cAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,eAAe,CAAC,CAAC;AAC9C,CAAC;AAED,SAAgB,UAAU,CAAC,QAAgB;IACzC,MAAM,UAAU,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;IAC3C,IAAI,CAAC;QACH,IAAI,YAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YAC9B,MAAM,GAAG,GAAG,YAAE,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;YACjD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAA6B,CAAC;YAC3D,OAAO,EAAE,GAAG,QAAQ,EAAE,GAAG,MAAM,EAAE,CAAC;QACpC,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,mCAAmC;IACrC,CAAC;IACD,OAAO,EAAE,GAAG,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC;AAC/C,CAAC;AAED,SAAgB,WAAW,CACzB,QAAgB,EAChB,KAA+B;IAE/B,MAAM,OAAO,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;IACrC,MAAM,OAAO,GAAoB,EAAE,GAAG,OAAO,EAAE,GAAG,KAAK,EAAE,CAAC;IAC1D,MAAM,UAAU,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;IAC3C,YAAE,CAAC,aAAa,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;IACxE,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface DocMetadata {
|
|
2
|
+
id: string;
|
|
3
|
+
filename: string;
|
|
4
|
+
title: string;
|
|
5
|
+
category: string;
|
|
6
|
+
date: string | null;
|
|
7
|
+
formattedDate: string | null;
|
|
8
|
+
}
|
|
9
|
+
export declare function parseFilename(filename: string): DocMetadata;
|
|
10
|
+
//# sourceMappingURL=parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../../src/lib/parser.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AA0BD,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,WAAW,CAyC3D"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseFilename = parseFilename;
|
|
4
|
+
// Matches: YYYY_MM_DD_[Category]_title_words.md
|
|
5
|
+
const FULL_PATTERN = /^(\d{4}_\d{2}_\d{2})_\[([^\]]+)\]_(.+)\.md$/i;
|
|
6
|
+
// Matches: YYYY_MM_DD_title_words.md (no category)
|
|
7
|
+
const DATE_ONLY_PATTERN = /^(\d{4}_\d{2}_\d{2})_(.+)\.md$/i;
|
|
8
|
+
function formatDate(iso) {
|
|
9
|
+
const [year, month, day] = iso.split('-').map(Number);
|
|
10
|
+
const d = new Date(Date.UTC(year, month - 1, day));
|
|
11
|
+
return d.toLocaleDateString('en-US', {
|
|
12
|
+
year: 'numeric',
|
|
13
|
+
month: 'long',
|
|
14
|
+
day: 'numeric',
|
|
15
|
+
timeZone: 'UTC',
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
function titleCase(raw) {
|
|
19
|
+
return raw
|
|
20
|
+
.split('_')
|
|
21
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
22
|
+
.join(' ');
|
|
23
|
+
}
|
|
24
|
+
function parseFilename(filename) {
|
|
25
|
+
const id = encodeURIComponent(filename.slice(0, -3));
|
|
26
|
+
const full = filename.match(FULL_PATTERN);
|
|
27
|
+
if (full) {
|
|
28
|
+
const [, dateStr, category, titlePart] = full;
|
|
29
|
+
const date = dateStr.replace(/_/g, '-');
|
|
30
|
+
return {
|
|
31
|
+
id,
|
|
32
|
+
filename,
|
|
33
|
+
title: titleCase(titlePart),
|
|
34
|
+
category,
|
|
35
|
+
date,
|
|
36
|
+
formattedDate: formatDate(date),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
const dateOnly = filename.match(DATE_ONLY_PATTERN);
|
|
40
|
+
if (dateOnly) {
|
|
41
|
+
const [, dateStr, titlePart] = dateOnly;
|
|
42
|
+
const date = dateStr.replace(/_/g, '-');
|
|
43
|
+
return {
|
|
44
|
+
id,
|
|
45
|
+
filename,
|
|
46
|
+
title: titleCase(titlePart),
|
|
47
|
+
category: 'Uncategorized',
|
|
48
|
+
date,
|
|
49
|
+
formattedDate: formatDate(date),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
// Fallback — no date, no category
|
|
53
|
+
const rawTitle = filename.slice(0, -3).replace(/[_-]+/g, ' ');
|
|
54
|
+
return {
|
|
55
|
+
id,
|
|
56
|
+
filename,
|
|
57
|
+
title: rawTitle.charAt(0).toUpperCase() + rawTitle.slice(1),
|
|
58
|
+
category: 'Uncategorized',
|
|
59
|
+
date: null,
|
|
60
|
+
formattedDate: null,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.js","sourceRoot":"","sources":["../../../src/lib/parser.ts"],"names":[],"mappings":";;AAiCA,sCAyCC;AAjED,gDAAgD;AAChD,MAAM,YAAY,GAAG,8CAA8C,CAAC;AAEpE,mDAAmD;AACnD,MAAM,iBAAiB,GAAG,iCAAiC,CAAC;AAE5D,SAAS,UAAU,CAAC,GAAW;IAC7B,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACtD,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;IACnD,OAAO,CAAC,CAAC,kBAAkB,CAAC,OAAO,EAAE;QACnC,IAAI,EAAE,SAAS;QACf,KAAK,EAAE,MAAM;QACb,GAAG,EAAE,SAAS;QACd,QAAQ,EAAE,KAAK;KAChB,CAAC,CAAC;AACL,CAAC;AAED,SAAS,SAAS,CAAC,GAAW;IAC5B,OAAO,GAAG;SACP,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;SACzE,IAAI,CAAC,GAAG,CAAC,CAAC;AACf,CAAC;AAED,SAAgB,aAAa,CAAC,QAAgB;IAC5C,MAAM,EAAE,GAAG,kBAAkB,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAErD,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;IAC1C,IAAI,IAAI,EAAE,CAAC;QACT,MAAM,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,CAAC,GAAG,IAAI,CAAC;QAC9C,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QACxC,OAAO;YACL,EAAE;YACF,QAAQ;YACR,KAAK,EAAE,SAAS,CAAC,SAAS,CAAC;YAC3B,QAAQ;YACR,IAAI;YACJ,aAAa,EAAE,UAAU,CAAC,IAAI,CAAC;SAChC,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;IACnD,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,CAAC,EAAE,OAAO,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAC;QACxC,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QACxC,OAAO;YACL,EAAE;YACF,QAAQ;YACR,KAAK,EAAE,SAAS,CAAC,SAAS,CAAC;YAC3B,QAAQ,EAAE,eAAe;YACzB,IAAI;YACJ,aAAa,EAAE,UAAU,CAAC,IAAI,CAAC;SAChC,CAAC;IACJ,CAAC;IAED,kCAAkC;IAClC,MAAM,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IAC9D,OAAO;QACL,EAAE;QACF,QAAQ;QACR,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;QAC3D,QAAQ,EAAE,eAAe;QACzB,IAAI,EAAE,IAAI;QACV,aAAa,EAAE,IAAI;KACpB,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/routes/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAqB,MAAM,SAAS,CAAC;AAGpD,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAoCrD"}
|