@hongmaple0820/scale-engine 0.50.1 → 0.50.2
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.en.md +2 -2
- package/README.md +2 -2
- package/dist/api/http.js +3 -1
- package/dist/api/http.js.map +1 -1
- package/dist/cli/cortexCommands.d.ts +16 -0
- package/dist/cli/cortexCommands.js +47 -4
- package/dist/cli/cortexCommands.js.map +1 -1
- package/dist/cortex/InstinctStore.d.ts +13 -1
- package/dist/cortex/InstinctStore.js +90 -11
- package/dist/cortex/InstinctStore.js.map +1 -1
- package/dist/cortex/SessionInjector.js +39 -2
- package/dist/cortex/SessionInjector.js.map +1 -1
- package/dist/dashboard/DashboardServer.d.ts +158 -0
- package/dist/dashboard/DashboardServer.js +753 -13
- package/dist/dashboard/DashboardServer.js.map +1 -1
- package/dist/dashboard/spa/assets/index-VYBCLBje.js +11 -0
- package/dist/dashboard/spa/assets/index-VhwY_ac1.css +1 -0
- package/dist/dashboard/spa/assets/naive-ui-BQy2AJkt.js +3340 -0
- package/dist/dashboard/spa/assets/vendor-BPU6aOYA.js +3 -0
- package/dist/dashboard/spa/assets/vue-CQQMb5Wi.js +17 -0
- package/dist/dashboard/spa/index.html +15 -462
- package/dist/memory/MemoryFabric.d.ts +13 -1
- package/dist/memory/MemoryFabric.js +60 -0
- package/dist/memory/MemoryFabric.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/workflow/ASSESSMENT_INDEX.md +326 -0
- package/docs/workflow/COMPARATIVE_ANALYSIS.md +422 -0
- package/docs/workflow/EXECUTIVE_SUMMARY.md +310 -0
- package/docs/workflow/IMPROVEMENT_CHECKLIST.md +518 -0
- package/docs/workflow/IMPROVEMENT_ROADMAP.md +707 -0
- package/docs/workflow/README.md +8 -0
- package/package.json +6 -2
- package/dist/dashboard/spa/app.js +0 -515
- package/dist/dashboard/spa/components/DataTable.js +0 -53
- package/dist/dashboard/spa/components/EventStream.js +0 -66
- package/dist/dashboard/spa/components/LoadingState.js +0 -39
- package/dist/dashboard/spa/components/MetricCard.js +0 -30
- package/dist/dashboard/spa/components/Panel.js +0 -27
- package/dist/dashboard/spa/components/StatusBadge.js +0 -51
- package/dist/dashboard/spa/i18n.js +0 -767
- package/dist/dashboard/spa/pages/costs.js +0 -522
- package/dist/dashboard/spa/pages/documents.js +0 -540
- package/dist/dashboard/spa/pages/knowledge.js +0 -457
- package/dist/dashboard/spa/pages/monitoring.js +0 -361
- package/dist/dashboard/spa/pages/overview.js +0 -301
- package/dist/dashboard/spa/pages/topology-renderers.js +0 -251
- package/dist/dashboard/spa/pages/topology.js +0 -370
- package/dist/dashboard/spa/pages/workflow-renderers.js +0 -239
- package/dist/dashboard/spa/pages/workflow.js +0 -217
|
@@ -1,540 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Documents Page v2 - Markdown rendering, full-text search, doc metadata, favorites.
|
|
3
|
-
*/
|
|
4
|
-
;(() => {
|
|
5
|
-
'use strict'
|
|
6
|
-
|
|
7
|
-
const { copyText, downloadText, fetchJSON, formatTime, t, $, $$, dom } = window.Dashboard
|
|
8
|
-
const { autoRefreshControl, dataNote, el, emptyState, panel, renderText } = dom
|
|
9
|
-
|
|
10
|
-
let currentDoc = null
|
|
11
|
-
let allDocs = []
|
|
12
|
-
let favorites = new Set(JSON.parse(localStorage.getItem('scale-doc-favorites') || '[]'))
|
|
13
|
-
|
|
14
|
-
async function renderDocuments() {
|
|
15
|
-
const app = $('#app')
|
|
16
|
-
const search = el('input', {
|
|
17
|
-
id: 'doc-search',
|
|
18
|
-
type: 'text',
|
|
19
|
-
className: 'search-box',
|
|
20
|
-
placeholder: t('documents.searchPlaceholder'),
|
|
21
|
-
style: { flex: '1', maxWidth: '400px' },
|
|
22
|
-
})
|
|
23
|
-
const refresh = el('button', { id: 'doc-refresh', className: 'topo-btn', text: t('common.refresh') })
|
|
24
|
-
const copyIndex = el('button', { id: 'doc-copy-index', className: 'topo-btn', text: t('documents.copyIndex') })
|
|
25
|
-
const downloadIndex = el('button', { id: 'doc-download-index', className: 'topo-btn', text: t('documents.downloadIndex') })
|
|
26
|
-
async function loadDocs() {
|
|
27
|
-
allDocs = await fetchJSON('/api/documents') ?? []
|
|
28
|
-
if (!$('#doc-tree')) return
|
|
29
|
-
renderDocTree(allDocs)
|
|
30
|
-
renderDocDataNote(allDocs)
|
|
31
|
-
renderPrototypeGallery(allDocs)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
app.replaceChildren(
|
|
35
|
-
el('div', { className: 'page-toolbar' }, [
|
|
36
|
-
search,
|
|
37
|
-
refresh,
|
|
38
|
-
copyIndex,
|
|
39
|
-
downloadIndex,
|
|
40
|
-
autoRefreshControl(loadDocs),
|
|
41
|
-
el('div', { className: 'toolbar-spacer' }),
|
|
42
|
-
el('span', { className: 'text-muted text-sm', id: 'doc-count' }),
|
|
43
|
-
]),
|
|
44
|
-
el('div', { id: 'doc-data-note' }),
|
|
45
|
-
panel(t('documents.prototypeGallery'), 'doc-prototypes'),
|
|
46
|
-
el('div', { className: 'doc-layout' }, [
|
|
47
|
-
el('div', { className: 'doc-tree', id: 'doc-tree' }, [
|
|
48
|
-
el('div', { className: 'loading-placeholder', text: t('common.loading'), style: { height: '200px' } }),
|
|
49
|
-
]),
|
|
50
|
-
el('div', { className: 'doc-renderer', id: 'doc-renderer' }, [
|
|
51
|
-
emptyStateWithHint(t('documents.selectHint'), `${t('documents.supportedTypes')}. ${t('documents.prototypeHint')}`, '\uD83D\uDCC4'),
|
|
52
|
-
]),
|
|
53
|
-
])
|
|
54
|
-
)
|
|
55
|
-
|
|
56
|
-
await loadDocs()
|
|
57
|
-
|
|
58
|
-
search.addEventListener('input', (event) => {
|
|
59
|
-
const query = event.target.value.toLowerCase()
|
|
60
|
-
const docs = query
|
|
61
|
-
? allDocs.filter(doc => doc.name.toLowerCase().includes(query) || doc.path.toLowerCase().includes(query))
|
|
62
|
-
: allDocs
|
|
63
|
-
renderDocTree(docs)
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
refresh.addEventListener('click', loadDocs)
|
|
67
|
-
copyIndex.addEventListener('click', () => copyText(documentIndexText(allDocs), copyIndex))
|
|
68
|
-
downloadIndex.addEventListener('click', () => {
|
|
69
|
-
downloadText(`scale-documents-${Date.now()}.json`, JSON.stringify(documentIndexPayload(allDocs), null, 2), 'application/json;charset=utf-8')
|
|
70
|
-
})
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function renderDocDataNote(docs) {
|
|
74
|
-
const node = $('#doc-data-note')
|
|
75
|
-
if (!node) return
|
|
76
|
-
const htmlCount = docs.filter(doc => doc.type === 'html').length
|
|
77
|
-
node.replaceChildren(dataNote([
|
|
78
|
-
{ strong: true, text: t('common.snapshot') },
|
|
79
|
-
`${t('common.lastLoaded')}: ${formatTime(Date.now())}`,
|
|
80
|
-
t('documents.dataHint'),
|
|
81
|
-
t('documents.previewable', { count: htmlCount }),
|
|
82
|
-
]))
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function renderPrototypeGallery(docs) {
|
|
86
|
-
const container = $('#doc-prototypes')
|
|
87
|
-
if (!container) return
|
|
88
|
-
const prototypes = docs.filter(doc => doc.type === 'html')
|
|
89
|
-
if (prototypes.length === 0) {
|
|
90
|
-
container.replaceChildren(emptyStateWithHint(t('documents.noPrototypes'), t('documents.noPrototypesHint'), '\u25cc'))
|
|
91
|
-
return
|
|
92
|
-
}
|
|
93
|
-
container.replaceChildren(el('div', { className: 'prototype-grid' }, prototypes.map(doc => {
|
|
94
|
-
const preview = el('button', { className: 'topo-btn', text: t('documents.preview') })
|
|
95
|
-
preview.addEventListener('click', () => selectDocument(doc.path, doc.type))
|
|
96
|
-
const open = el('button', { className: 'topo-btn', text: t('common.newTab') })
|
|
97
|
-
open.addEventListener('click', () => window.open(documentUrl(doc.path), '_blank'))
|
|
98
|
-
const copy = el('button', { className: 'topo-btn', text: t('documents.copyLink') })
|
|
99
|
-
copy.addEventListener('click', () => copyText(new URL(documentUrl(doc.path), window.location.origin).href, copy))
|
|
100
|
-
return el('div', { className: 'prototype-card' }, [
|
|
101
|
-
el('div', { className: 'prototype-preview' }, [
|
|
102
|
-
el('iframe', { attrs: { src: documentUrl(doc.path), title: doc.name } }),
|
|
103
|
-
]),
|
|
104
|
-
el('div', { className: 'prototype-body' }, [
|
|
105
|
-
el('div', { className: 'prototype-title', text: doc.name, title: doc.path }),
|
|
106
|
-
el('div', { className: 'prototype-meta', text: `${doc.path} · ${formatSize(doc.size ?? 0)}` }),
|
|
107
|
-
el('div', { className: 'action-row' }, [preview, open, copy]),
|
|
108
|
-
]),
|
|
109
|
-
])
|
|
110
|
-
})))
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function renderDocTree(docs) {
|
|
114
|
-
const tree = $('#doc-tree')
|
|
115
|
-
if (!tree) return
|
|
116
|
-
const count = $('#doc-count')
|
|
117
|
-
if (count) count.textContent = t('documents.docCount', { count: docs.length })
|
|
118
|
-
|
|
119
|
-
if (docs.length === 0) {
|
|
120
|
-
tree.replaceChildren(el('div', {
|
|
121
|
-
className: 'text-muted text-sm',
|
|
122
|
-
text: t('documents.noDocuments'),
|
|
123
|
-
style: { padding: '12px' },
|
|
124
|
-
}))
|
|
125
|
-
return
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const children = []
|
|
129
|
-
const favDocs = docs.filter(doc => favorites.has(doc.path))
|
|
130
|
-
if (favDocs.length > 0) {
|
|
131
|
-
children.push(el('div', { className: 'doc-tree-folder', text: `\u2605 ${t('documents.favorites')}` }))
|
|
132
|
-
children.push(...favDocs.map(file => renderDocItem(file)))
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
for (const [folder, files] of sortedFolders(docs)) {
|
|
136
|
-
children.push(el('div', { className: 'doc-tree-folder', text: folder === 'root' ? '/' : folder }))
|
|
137
|
-
children.push(...sortDocs(files).map(file => renderDocItem(file)))
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
tree.replaceChildren(...children)
|
|
141
|
-
wireTree(tree, docs)
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function sortedFolders(docs) {
|
|
145
|
-
const folders = {}
|
|
146
|
-
for (const doc of docs) {
|
|
147
|
-
const parts = doc.path.split('/')
|
|
148
|
-
const folder = parts.length > 1 ? parts.slice(0, -1).join('/') : 'root'
|
|
149
|
-
if (!folders[folder]) folders[folder] = []
|
|
150
|
-
folders[folder].push(doc)
|
|
151
|
-
}
|
|
152
|
-
return Object.entries(folders).sort(([left], [right]) => {
|
|
153
|
-
const leftHasFav = folders[left].some(doc => favorites.has(doc.path))
|
|
154
|
-
const rightHasFav = folders[right].some(doc => favorites.has(doc.path))
|
|
155
|
-
if (leftHasFav && !rightHasFav) return -1
|
|
156
|
-
if (!leftHasFav && rightHasFav) return 1
|
|
157
|
-
return left.localeCompare(right)
|
|
158
|
-
})
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function sortDocs(files) {
|
|
162
|
-
return [...files].sort((left, right) => {
|
|
163
|
-
const leftFav = favorites.has(left.path) ? 0 : 1
|
|
164
|
-
const rightFav = favorites.has(right.path) ? 0 : 1
|
|
165
|
-
return leftFav - rightFav || left.name.localeCompare(right.name)
|
|
166
|
-
})
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function renderDocItem(file) {
|
|
170
|
-
const isFav = favorites.has(file.path)
|
|
171
|
-
return el('div', {
|
|
172
|
-
className: ['doc-tree-item', currentDoc === file.path ? 'active' : ''].filter(Boolean).join(' '),
|
|
173
|
-
dataset: { path: file.path, type: file.type },
|
|
174
|
-
style: { display: 'flex', alignItems: 'center', gap: '6px' },
|
|
175
|
-
}, [
|
|
176
|
-
el('span', { text: getDocIcon(file.type) }),
|
|
177
|
-
el('span', {
|
|
178
|
-
text: file.name,
|
|
179
|
-
style: { flex: '1', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' },
|
|
180
|
-
}),
|
|
181
|
-
el('span', { className: 'text-muted text-sm', text: formatSize(file.size), style: { flexShrink: '0' } }),
|
|
182
|
-
el('span', {
|
|
183
|
-
className: 'doc-fav-btn',
|
|
184
|
-
text: isFav ? '\u2605' : '\u2606',
|
|
185
|
-
title: isFav ? t('documents.removeFromFavorites') : t('documents.addToFavorites'),
|
|
186
|
-
dataset: { path: file.path },
|
|
187
|
-
style: { cursor: 'pointer', color: isFav ? '#ffaa00' : 'var(--text-2)', fontSize: '12px' },
|
|
188
|
-
}),
|
|
189
|
-
])
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
function wireTree(tree, docs) {
|
|
193
|
-
$$('.doc-tree-item', tree).forEach((item) => {
|
|
194
|
-
item.addEventListener('click', (event) => {
|
|
195
|
-
if (event.target.classList.contains('doc-fav-btn')) return
|
|
196
|
-
selectDocument(item.dataset.path, item.dataset.type)
|
|
197
|
-
})
|
|
198
|
-
})
|
|
199
|
-
$$('.doc-fav-btn', tree).forEach((button) => {
|
|
200
|
-
button.addEventListener('click', (event) => {
|
|
201
|
-
event.stopPropagation()
|
|
202
|
-
const path = button.dataset.path
|
|
203
|
-
if (favorites.has(path)) favorites.delete(path)
|
|
204
|
-
else favorites.add(path)
|
|
205
|
-
localStorage.setItem('scale-doc-favorites', JSON.stringify([...favorites]))
|
|
206
|
-
renderDocTree(docs)
|
|
207
|
-
})
|
|
208
|
-
})
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
function selectDocument(path, type) {
|
|
212
|
-
const tree = $('#doc-tree')
|
|
213
|
-
if (tree) {
|
|
214
|
-
$$('.doc-tree-item', tree).forEach(node => node.classList.toggle('active', node.dataset.path === path))
|
|
215
|
-
}
|
|
216
|
-
loadDocument(path, type)
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
function getDocIcon(type) {
|
|
220
|
-
switch (type) {
|
|
221
|
-
case 'html': return '\uD83C\uDF10'
|
|
222
|
-
case 'json': return '\uD83D\uDCC4'
|
|
223
|
-
case 'md': return '\uD83D\uDCDD'
|
|
224
|
-
default: return '\uD83D\uDCC4'
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
async function loadDocument(path, type) {
|
|
229
|
-
const renderer = $('#doc-renderer')
|
|
230
|
-
if (!renderer) return
|
|
231
|
-
renderer.replaceChildren(el('div', { className: 'loading-placeholder', text: t('common.loading') }))
|
|
232
|
-
|
|
233
|
-
try {
|
|
234
|
-
const res = await fetch(documentUrl(path))
|
|
235
|
-
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`)
|
|
236
|
-
const text = await res.text()
|
|
237
|
-
if (!renderer.isConnected || $('#doc-renderer') !== renderer) return
|
|
238
|
-
|
|
239
|
-
if (type === 'html') renderHtmlDocument(renderer, path, text)
|
|
240
|
-
else if (type === 'md') renderMarkdownDocument(renderer, path, text)
|
|
241
|
-
else if (type === 'json') renderJsonDocument(renderer, path, text)
|
|
242
|
-
else renderPlainDocument(renderer, path, text)
|
|
243
|
-
|
|
244
|
-
currentDoc = path
|
|
245
|
-
} catch (error) {
|
|
246
|
-
renderer.replaceChildren(emptyState(`${t('documents.failedToLoad')}: ${errorMessage(error)}`, '\u26A0'))
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
function renderHtmlDocument(renderer, path, text) {
|
|
251
|
-
const openButton = el('button', {
|
|
252
|
-
className: 'topo-btn doc-open-ext',
|
|
253
|
-
text: `\u2197 ${t('common.newTab')}`,
|
|
254
|
-
title: t('common.newTab'),
|
|
255
|
-
})
|
|
256
|
-
openButton.addEventListener('click', () => window.open(documentUrl(path), '_blank'))
|
|
257
|
-
renderer.replaceChildren(
|
|
258
|
-
docHeader(path, formatSize(text.length), documentActions(path, text, [openButton])),
|
|
259
|
-
el('iframe', {
|
|
260
|
-
attrs: { src: documentUrl(path) },
|
|
261
|
-
style: {
|
|
262
|
-
width: '100%',
|
|
263
|
-
height: 'calc(100% - 50px)',
|
|
264
|
-
minHeight: '550px',
|
|
265
|
-
border: '1px solid var(--border)',
|
|
266
|
-
borderRadius: 'var(--radius)',
|
|
267
|
-
},
|
|
268
|
-
})
|
|
269
|
-
)
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
function renderMarkdownDocument(renderer, path, text) {
|
|
273
|
-
renderer.replaceChildren(
|
|
274
|
-
docHeader(path, formatSize(text.length), documentActions(path, text)),
|
|
275
|
-
renderMarkdown(text)
|
|
276
|
-
)
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
function renderJsonDocument(renderer, path, text) {
|
|
280
|
-
try {
|
|
281
|
-
const json = JSON.parse(text)
|
|
282
|
-
const formatted = JSON.stringify(json, null, 2)
|
|
283
|
-
renderer.replaceChildren(
|
|
284
|
-
docHeader(path, `${Object.keys(json).length} ${t('documents.keys')} \u00b7 ${formatSize(text.length)}`, documentActions(path, formatted)),
|
|
285
|
-
renderJsonPre(formatted)
|
|
286
|
-
)
|
|
287
|
-
} catch (error) {
|
|
288
|
-
observeRecoverableError(error)
|
|
289
|
-
renderPlainDocument(renderer, path, text)
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
function renderPlainDocument(renderer, path, text) {
|
|
294
|
-
renderer.replaceChildren(
|
|
295
|
-
docHeader(path, formatSize(text.length), documentActions(path, text)),
|
|
296
|
-
el('pre', {
|
|
297
|
-
text,
|
|
298
|
-
style: { fontSize: '13px', whiteSpace: 'pre-wrap', color: 'var(--text-1)', lineHeight: '1.5' },
|
|
299
|
-
})
|
|
300
|
-
)
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
function docHeader(path, meta, actions = []) {
|
|
304
|
-
return el('div', {
|
|
305
|
-
style: { marginBottom: '12px', display: 'flex', justifyContent: 'space-between', alignItems: 'center' },
|
|
306
|
-
}, [
|
|
307
|
-
el('div', {}, [
|
|
308
|
-
el('span', { text: fileName(path), style: { fontWeight: '600' } }),
|
|
309
|
-
el('span', { className: 'text-muted text-sm', text: meta, style: { marginLeft: '8px' } }),
|
|
310
|
-
]),
|
|
311
|
-
actions.length ? el('div', { style: { display: 'flex', gap: '8px' } }, actions) : null,
|
|
312
|
-
])
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
function documentActions(path, text, extraActions = []) {
|
|
316
|
-
const copyButton = el('button', { className: 'topo-btn doc-copy', text: t('common.copy'), title: t('common.copy') })
|
|
317
|
-
copyButton.addEventListener('click', async () => {
|
|
318
|
-
try {
|
|
319
|
-
await copyText(text, copyButton)
|
|
320
|
-
} catch (error) {
|
|
321
|
-
observeRecoverableError(error)
|
|
322
|
-
}
|
|
323
|
-
})
|
|
324
|
-
const downloadButton = el('button', { className: 'topo-btn doc-download', text: t('common.download'), title: t('common.download') })
|
|
325
|
-
downloadButton.addEventListener('click', () => downloadText(fileName(path), text, contentTypeForPath(path)))
|
|
326
|
-
return [copyButton, downloadButton, ...extraActions]
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
function documentIndexPayload(docs) {
|
|
330
|
-
return {
|
|
331
|
-
exportedAt: new Date().toISOString(),
|
|
332
|
-
count: docs.length,
|
|
333
|
-
previewableHtml: docs.filter(doc => doc.type === 'html').length,
|
|
334
|
-
documents: docs.map(doc => ({
|
|
335
|
-
name: doc.name,
|
|
336
|
-
path: doc.path,
|
|
337
|
-
type: doc.type,
|
|
338
|
-
size: doc.size,
|
|
339
|
-
})),
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
function documentIndexText(docs) {
|
|
344
|
-
return documentIndexPayload(docs).documents
|
|
345
|
-
.map(doc => `${doc.type}\t${formatSize(doc.size ?? 0)}\t${doc.path}`)
|
|
346
|
-
.join('\n')
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
function contentTypeForPath(path) {
|
|
350
|
-
const lower = String(path).toLowerCase()
|
|
351
|
-
if (lower.endsWith('.json')) return 'application/json;charset=utf-8'
|
|
352
|
-
if (lower.endsWith('.html')) return 'text/html;charset=utf-8'
|
|
353
|
-
if (lower.endsWith('.md')) return 'text/markdown;charset=utf-8'
|
|
354
|
-
return 'text/plain;charset=utf-8'
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
function renderMarkdown(markdown) {
|
|
358
|
-
const root = el('div', { className: 'markdown-body', style: { fontSize: '14px', lineHeight: '1.7', color: 'var(--text-0)' } })
|
|
359
|
-
const lines = markdown.replace(/\r\n/g, '\n').split('\n')
|
|
360
|
-
let codeLines = []
|
|
361
|
-
let codeLang = ''
|
|
362
|
-
let paragraph = []
|
|
363
|
-
let list = null
|
|
364
|
-
|
|
365
|
-
const flushParagraph = () => {
|
|
366
|
-
if (paragraph.length === 0) return
|
|
367
|
-
const p = el('p', { style: { margin: '8px 0' } })
|
|
368
|
-
appendInline(p, paragraph.join(' '))
|
|
369
|
-
root.appendChild(p)
|
|
370
|
-
paragraph = []
|
|
371
|
-
}
|
|
372
|
-
const flushList = () => {
|
|
373
|
-
if (!list) return
|
|
374
|
-
root.appendChild(list)
|
|
375
|
-
list = null
|
|
376
|
-
}
|
|
377
|
-
const flushCode = () => {
|
|
378
|
-
const code = el('code', { text: codeLines.join('\n') })
|
|
379
|
-
const pre = el('pre', {
|
|
380
|
-
attrs: codeLang ? { 'data-lang': codeLang } : {},
|
|
381
|
-
style: { background: 'var(--bg-2)', padding: '12px', borderRadius: 'var(--radius)', fontSize: '13px', lineHeight: '1.5', overflow: 'auto', margin: '12px 0' },
|
|
382
|
-
}, [code])
|
|
383
|
-
root.appendChild(pre)
|
|
384
|
-
codeLines = []
|
|
385
|
-
codeLang = ''
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
for (const line of lines) {
|
|
389
|
-
const fence = line.match(/^```(\w*)\s*$/)
|
|
390
|
-
if (fence && codeLang === '' && codeLines.length === 0) {
|
|
391
|
-
flushParagraph()
|
|
392
|
-
flushList()
|
|
393
|
-
codeLang = fence[1] || 'plain'
|
|
394
|
-
codeLines = ['__OPEN__']
|
|
395
|
-
continue
|
|
396
|
-
}
|
|
397
|
-
if (fence && codeLines.length > 0) {
|
|
398
|
-
codeLines.shift()
|
|
399
|
-
flushCode()
|
|
400
|
-
continue
|
|
401
|
-
}
|
|
402
|
-
if (codeLines.length > 0) {
|
|
403
|
-
codeLines.push(line)
|
|
404
|
-
continue
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
if (!line.trim()) {
|
|
408
|
-
flushParagraph()
|
|
409
|
-
flushList()
|
|
410
|
-
continue
|
|
411
|
-
}
|
|
412
|
-
const heading = line.match(/^(#{1,3})\s+(.+)$/)
|
|
413
|
-
if (heading) {
|
|
414
|
-
flushParagraph()
|
|
415
|
-
flushList()
|
|
416
|
-
root.appendChild(headingNode(heading[1].length, heading[2]))
|
|
417
|
-
continue
|
|
418
|
-
}
|
|
419
|
-
if (/^---+$/.test(line.trim())) {
|
|
420
|
-
flushParagraph()
|
|
421
|
-
flushList()
|
|
422
|
-
root.appendChild(el('hr', { style: { border: 'none', borderTop: '1px solid var(--border)', margin: '20px 0' } }))
|
|
423
|
-
continue
|
|
424
|
-
}
|
|
425
|
-
const item = line.match(/^[-*]\s+(.+)$/)
|
|
426
|
-
if (item) {
|
|
427
|
-
flushParagraph()
|
|
428
|
-
if (!list) list = el('ul', { style: { margin: '8px 0 8px 20px' } })
|
|
429
|
-
const li = el('li', { style: { padding: '2px 0' } })
|
|
430
|
-
appendInline(li, item[1])
|
|
431
|
-
list.appendChild(li)
|
|
432
|
-
continue
|
|
433
|
-
}
|
|
434
|
-
paragraph.push(line.trim())
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
if (codeLines.length > 0) {
|
|
438
|
-
codeLines.shift()
|
|
439
|
-
flushCode()
|
|
440
|
-
}
|
|
441
|
-
flushParagraph()
|
|
442
|
-
flushList()
|
|
443
|
-
return root
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
function headingNode(level, text) {
|
|
447
|
-
const sizes = { 1: '22px', 2: '18px', 3: '16px' }
|
|
448
|
-
const margins = { 1: '28px 0 12px', 2: '24px 0 10px', 3: '20px 0 8px' }
|
|
449
|
-
const node = el(`h${level}`, { style: { margin: margins[level], fontSize: sizes[level], color: 'var(--text-0)' } })
|
|
450
|
-
appendInline(node, text)
|
|
451
|
-
return node
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
function appendInline(parent, text) {
|
|
455
|
-
const pattern = /(\*\*[^*]+\*\*|`[^`]+`|\[[^\]]+\]\([^)]+\)|\*[^*]+\*)/g
|
|
456
|
-
let lastIndex = 0
|
|
457
|
-
for (const match of text.matchAll(pattern)) {
|
|
458
|
-
if (match.index > lastIndex) parent.appendChild(document.createTextNode(text.slice(lastIndex, match.index)))
|
|
459
|
-
parent.appendChild(inlineNode(match[0]))
|
|
460
|
-
lastIndex = match.index + match[0].length
|
|
461
|
-
}
|
|
462
|
-
if (lastIndex < text.length) parent.appendChild(document.createTextNode(text.slice(lastIndex)))
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
function inlineNode(token) {
|
|
466
|
-
if (token.startsWith('**')) return el('strong', { text: token.slice(2, -2) })
|
|
467
|
-
if (token.startsWith('`')) return el('code', { text: token.slice(1, -1), style: { background: 'var(--bg-2)', padding: '2px 6px', borderRadius: '3px', fontSize: '12px' } })
|
|
468
|
-
if (token.startsWith('*')) return el('em', { text: token.slice(1, -1) })
|
|
469
|
-
const link = token.match(/^\[([^\]]+)\]\(([^)]+)\)$/)
|
|
470
|
-
if (!link) return document.createTextNode(token)
|
|
471
|
-
return el('a', { text: link[1], attrs: { href: safeHref(link[2]), target: '_blank', rel: 'noopener noreferrer' } })
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
function renderJsonPre(formatted) {
|
|
475
|
-
const pre = el('pre', {
|
|
476
|
-
style: { fontSize: '13px', lineHeight: '1.5', whiteSpace: 'pre-wrap', color: 'var(--text-1)', background: 'var(--bg-2)', padding: '16px', borderRadius: 'var(--radius)', overflow: 'auto', maxHeight: 'calc(100% - 50px)' },
|
|
477
|
-
})
|
|
478
|
-
const tokenPattern = /"(?:\\.|[^"\\])*"(?=\s*:)|"(?:\\.|[^"\\])*"|-?\b\d+(?:\.\d+)?(?:e[+-]?\d+)?\b|\btrue\b|\bfalse\b|\bnull\b/gi
|
|
479
|
-
let lastIndex = 0
|
|
480
|
-
for (const match of formatted.matchAll(tokenPattern)) {
|
|
481
|
-
if (match.index > lastIndex) pre.appendChild(document.createTextNode(formatted.slice(lastIndex, match.index)))
|
|
482
|
-
pre.appendChild(jsonToken(match[0], formatted.slice(match.index + match[0].length)))
|
|
483
|
-
lastIndex = match.index + match[0].length
|
|
484
|
-
}
|
|
485
|
-
if (lastIndex < formatted.length) pre.appendChild(document.createTextNode(formatted.slice(lastIndex)))
|
|
486
|
-
return pre
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
function jsonToken(token, tail) {
|
|
490
|
-
const color = token.startsWith('"')
|
|
491
|
-
? (tail.trimStart().startsWith(':') ? '#5588ff' : '#00dc82')
|
|
492
|
-
: (/^(true|false|null)$/i.test(token) ? '#aa88ff' : '#ffaa00')
|
|
493
|
-
return el('span', { text: token, style: { color } })
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
function emptyStateWithHint(message, hint, icon) {
|
|
497
|
-
const state = emptyState(message, icon)
|
|
498
|
-
state.appendChild(el('p', { className: 'text-muted text-sm', text: hint, style: { marginTop: '8px' } }))
|
|
499
|
-
return state
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
function documentUrl(path) {
|
|
503
|
-
return `/api/documents/${String(path).split('/').map(segment => encodeURIComponent(segment)).join('/')}`
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
function safeHref(value) {
|
|
507
|
-
const href = String(value || '')
|
|
508
|
-
if (/^(https?:|mailto:|#|\/)/i.test(href)) return href
|
|
509
|
-
return '#'
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
function fileName(path) {
|
|
513
|
-
return String(path).split('/').pop() || path
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
function formatSize(bytes) {
|
|
517
|
-
if (bytes < 1024) return bytes + 'B'
|
|
518
|
-
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + 'KB'
|
|
519
|
-
return (bytes / (1024 * 1024)).toFixed(1) + 'MB'
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
function errorMessage(error) {
|
|
523
|
-
return error instanceof Error ? error.message : String(error || 'Unknown error')
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
function observeRecoverableError(error) {
|
|
527
|
-
void error
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
window.addEventListener('search', (event) => {
|
|
531
|
-
const query = event.detail.toLowerCase()
|
|
532
|
-
$$('.doc-tree-item').forEach((item) => {
|
|
533
|
-
const name = item.textContent.toLowerCase()
|
|
534
|
-
item.style.display = name.includes(query) ? '' : 'none'
|
|
535
|
-
})
|
|
536
|
-
})
|
|
537
|
-
|
|
538
|
-
window.DashboardPages = window.DashboardPages || {}
|
|
539
|
-
window.DashboardPages.documents = renderDocuments
|
|
540
|
-
})()
|