anentrypoint-design 0.0.27 → 0.0.29
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/247420.app.js +3 -3
- package/dist/247420.css +294 -2
- package/dist/247420.js +37 -131
- package/package.json +3 -2
- package/src/bootstrap.js +38 -0
- package/src/components/chat.js +199 -0
- package/src/components/content.js +171 -0
- package/src/components/shell.js +113 -0
- package/src/components.js +20 -392
- package/src/debug.js +34 -0
- package/src/highlight.js +60 -0
- package/src/index.js +22 -5
- package/src/markdown.js +61 -0
- package/src/web-components/ds-chat.js +75 -0
package/src/components.js
CHANGED
|
@@ -1,393 +1,21 @@
|
|
|
1
1
|
import * as webjsx from '../vendor/webjsx/index.js';
|
|
2
|
-
const h = webjsx.createElement;
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function Glyph({ children, color }) {
|
|
25
|
-
return h('span', { class: 'glyph', style: color ? `color:${color}` : '' }, children);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// ---------- chrome ----------
|
|
29
|
-
|
|
30
|
-
export function Topbar({ brand = '247420', leaf = '', items = [], active = '', onNav, search } = {}) {
|
|
31
|
-
return h('header', { class: 'app-topbar' },
|
|
32
|
-
Brand({ name: brand, leaf }),
|
|
33
|
-
search ? h('label', { class: 'app-search' },
|
|
34
|
-
h('span', { class: 'icon' }, '⌕'),
|
|
35
|
-
h('input', { type: 'search', placeholder: search, 'aria-label': 'search' })
|
|
36
|
-
) : null,
|
|
37
|
-
h('nav', {}, ...items.map(([label, href]) =>
|
|
38
|
-
h('a', {
|
|
39
|
-
key: label,
|
|
40
|
-
href,
|
|
41
|
-
class: active === label.replace(' ↗', '') ? 'active' : '',
|
|
42
|
-
onclick: (e) => {
|
|
43
|
-
if (!String(href).startsWith('http') && onNav) {
|
|
44
|
-
e.preventDefault();
|
|
45
|
-
onNav(label.replace(' ↗', ''));
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}, label)
|
|
49
|
-
))
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export function Crumb({ trail = [], leaf = '', right } = {}) {
|
|
54
|
-
const parts = [];
|
|
55
|
-
trail.forEach((t, i) => {
|
|
56
|
-
parts.push(h('span', { key: 't' + i }, t));
|
|
57
|
-
parts.push(h('span', { key: 's' + i, class: 'sep' }, '›'));
|
|
58
|
-
});
|
|
59
|
-
parts.push(h('span', { key: 'leaf', class: 'leaf' }, leaf));
|
|
60
|
-
if (right) parts.push(h('span', { key: 'r', style: 'margin-left:auto;display:flex;gap:10px;align-items:center' }, ...(Array.isArray(right) ? right : [right])));
|
|
61
|
-
return h('div', { class: 'app-crumb' }, ...parts);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export function Side({ sections = [] } = {}) {
|
|
65
|
-
return h('aside', { class: 'app-side' }, ...sections.flatMap(sec => [
|
|
66
|
-
h('div', { class: 'group', key: sec.group }, sec.group),
|
|
67
|
-
...sec.items.map((item, i) => {
|
|
68
|
-
const { glyph, label, href = '#', active, count, color, onClick } = item;
|
|
69
|
-
return h('a', {
|
|
70
|
-
key: sec.group + i,
|
|
71
|
-
href,
|
|
72
|
-
class: active ? 'active' : '',
|
|
73
|
-
onclick: onClick
|
|
74
|
-
},
|
|
75
|
-
glyph != null ? Glyph({ children: glyph, color }) : null,
|
|
76
|
-
h('span', {}, label),
|
|
77
|
-
count != null ? h('span', { class: 'count' }, String(count)) : null
|
|
78
|
-
);
|
|
79
|
-
})
|
|
80
|
-
]));
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export function Status({ left = [], right = [] } = {}) {
|
|
84
|
-
return h('footer', { class: 'app-status' },
|
|
85
|
-
...left.map((t, i) => h('span', { key: 'l' + i, class: 'item' }, t)),
|
|
86
|
-
h('span', { class: 'spread' }),
|
|
87
|
-
...right.map((t, i) => h('span', { key: 'r' + i, class: 'item' }, t))
|
|
88
|
-
);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export function AppShell({ topbar, crumb, side, main, status, narrow } = {}) {
|
|
92
|
-
const hasSide = Boolean(side);
|
|
93
|
-
const sideMotionClass = hasSide
|
|
94
|
-
? ' animate__animated animate__fadeInLeft'
|
|
95
|
-
: ' animate__animated animate__fadeOutLeft';
|
|
96
|
-
const sideNode = hasSide
|
|
97
|
-
? side
|
|
98
|
-
: h('aside', { class: 'app-side', 'aria-hidden': 'true' });
|
|
99
|
-
|
|
100
|
-
return h('div', { class: 'app' },
|
|
101
|
-
topbar || null,
|
|
102
|
-
crumb || null,
|
|
103
|
-
h('div', { class: 'app-body' + (hasSide ? '' : ' no-side') },
|
|
104
|
-
h('div', { class: 'app-side-shell' + sideMotionClass }, sideNode),
|
|
105
|
-
h('main', { class: 'app-main' + (narrow ? ' narrow' : '') }, ...(Array.isArray(main) ? main : [main]))
|
|
106
|
-
),
|
|
107
|
-
status || null
|
|
108
|
-
);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// ---------- panels & rows ----------
|
|
112
|
-
|
|
113
|
-
export function Panel({ title, count, right, style = '', children }) {
|
|
114
|
-
return h('div', { class: 'panel', style },
|
|
115
|
-
title != null ? h('div', { class: 'panel-head' },
|
|
116
|
-
h('span', {}, title),
|
|
117
|
-
right != null ? right : (count != null ? h('span', {}, String(count)) : null)
|
|
118
|
-
) : null,
|
|
119
|
-
h('div', { class: 'panel-body' }, ...(Array.isArray(children) ? children : [children]))
|
|
120
|
-
);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
export function Row({ code, title, sub, meta, active, onClick, key, style }) {
|
|
124
|
-
return h('div', {
|
|
125
|
-
key,
|
|
126
|
-
class: 'row' + (active ? ' active' : ''),
|
|
127
|
-
onclick: onClick,
|
|
128
|
-
style
|
|
129
|
-
},
|
|
130
|
-
code != null ? h('span', { class: 'code' }, code) : null,
|
|
131
|
-
h('span', { class: 'title' }, title, sub ? h('span', { class: 'sub' }, sub) : null),
|
|
132
|
-
meta != null ? h('span', { class: 'meta' }, meta) : null
|
|
133
|
-
);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
export function RowLink({ code, title, sub, meta, href = '#', key }) {
|
|
137
|
-
return h('a', { key, class: 'row', href },
|
|
138
|
-
code != null ? h('span', { class: 'code' }, code) : null,
|
|
139
|
-
h('span', { class: 'title' }, title, sub ? h('span', { class: 'sub' }, sub) : null),
|
|
140
|
-
meta != null ? h('span', { class: 'meta' }, meta) : null
|
|
141
|
-
);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// ---------- content blocks ----------
|
|
145
|
-
|
|
146
|
-
export function Heading({ level = 1, children, style = '' }) {
|
|
147
|
-
return h('h' + level, { style }, children);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
export function Lede({ children }) {
|
|
151
|
-
return h('p', { class: 'lede' }, children);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
export function Hero({ title, body, accent, badge, badgeCount }) {
|
|
155
|
-
return h('div', { style: 'padding:32px 32px 24px 32px' },
|
|
156
|
-
h('h1', { style: 'font-size:36px;font-weight:600;margin:0 0 4px 0;color:var(--panel-text);letter-spacing:-0.01em' }, title),
|
|
157
|
-
body ? h('p', { style: 'font-size:14px;line-height:1.55;color:var(--panel-text-2);max-width:64ch;margin:0 0 20px 0' },
|
|
158
|
-
body,
|
|
159
|
-
accent ? h('span', { style: 'color:var(--panel-accent);font-weight:500' }, ' ' + accent) : null
|
|
160
|
-
) : null,
|
|
161
|
-
badge ? Panel({ title: badge, count: badgeCount, style: 'max-width:560px;margin:0', children: [] }) : null
|
|
162
|
-
);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
export function Install({ cmd, copied, onCopy }) {
|
|
166
|
-
return h('div', { class: 'cli' },
|
|
167
|
-
h('span', { class: 'prompt' }, '$'),
|
|
168
|
-
h('span', { class: 'cmd' }, cmd),
|
|
169
|
-
h('span', { class: 'copy', onclick: () => onCopy && onCopy(cmd) }, copied ? 'copied' : 'copy')
|
|
170
|
-
);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
export function Receipt({ rows = [] }) {
|
|
174
|
-
return h('table', { class: 'kv' },
|
|
175
|
-
h('tbody', {}, ...rows.map(([k, v], i) =>
|
|
176
|
-
h('tr', { key: i }, h('td', {}, k), h('td', {}, v))
|
|
177
|
-
))
|
|
178
|
-
);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
export function Changelog({ entries = [] }) {
|
|
182
|
-
return Panel({
|
|
183
|
-
style: 'max-width:900px',
|
|
184
|
-
children: entries.map((e, i) =>
|
|
185
|
-
h('div', { key: i, class: 'row', style: 'grid-template-columns:100px 70px 1fr' },
|
|
186
|
-
h('span', { class: 'code' }, e.date),
|
|
187
|
-
h('span', { style: 'color:var(--panel-accent);font-family:var(--ff-mono);font-size:14px' }, e.ver),
|
|
188
|
-
h('span', { class: 'title' }, e.msg)
|
|
189
|
-
)
|
|
190
|
-
)
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
export function WorksList({ works = [], openedIndex = -1, onToggle }) {
|
|
195
|
-
return Panel({
|
|
196
|
-
title: `works · ${String(works.length).padStart(2, '0')} of ~${works.length}`,
|
|
197
|
-
right: h('a', { href: 'https://github.com/AnEntrypoint', style: 'color:var(--panel-accent);text-decoration:none' }, 'all repos ↗'),
|
|
198
|
-
children: works.map((w, i) => {
|
|
199
|
-
const isOpen = openedIndex === i;
|
|
200
|
-
return h('div', { key: i },
|
|
201
|
-
Row({
|
|
202
|
-
code: w.code, title: w.title, sub: w.sub,
|
|
203
|
-
meta: w.meta + ' ' + (isOpen ? '−' : '+'),
|
|
204
|
-
active: isOpen,
|
|
205
|
-
onClick: () => onToggle && onToggle(isOpen ? -1 : i)
|
|
206
|
-
}),
|
|
207
|
-
isOpen ? h('div', {
|
|
208
|
-
class: 'work-detail',
|
|
209
|
-
'data-work-index': String(i),
|
|
210
|
-
style: 'padding:14px 20px 18px 86px;background:var(--panel-2);color:var(--panel-text);font-size:13px;line-height:1.6'
|
|
211
|
-
},
|
|
212
|
-
h('p', { style: 'margin:0 0 12px 0;max-width:64ch' }, w.body),
|
|
213
|
-
h('div', { style: 'display:flex;gap:8px' },
|
|
214
|
-
Btn({ primary: true, href: w.href || '#', children: 'open ↗' }),
|
|
215
|
-
Btn({ href: w.source || '#', children: 'source' })
|
|
216
|
-
)
|
|
217
|
-
) : null
|
|
218
|
-
);
|
|
219
|
-
})
|
|
220
|
-
});
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
export function WritingList({ posts = [] }) {
|
|
224
|
-
return Panel({
|
|
225
|
-
children: posts.map((p, i) =>
|
|
226
|
-
RowLink({ key: i, code: p.date, title: p.title, meta: '§ ' + p.tag, href: p.href || '#' })
|
|
227
|
-
)
|
|
228
|
-
});
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
export function Manifesto({ paragraphs = [], maxWidth = 820 }) {
|
|
232
|
-
return Panel({
|
|
233
|
-
style: `max-width:${maxWidth}px`,
|
|
234
|
-
children: h('div', { style: 'padding:20px 24px;font-size:14px;line-height:1.7;color:var(--panel-text)' },
|
|
235
|
-
...paragraphs.map((p, i) => h('p', {
|
|
236
|
-
key: i,
|
|
237
|
-
style: 'margin:0 0 12px 0' + (p.dim ? ';color:var(--panel-text-2)' : '')
|
|
238
|
-
}, p.text || p))
|
|
239
|
-
)
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
export function Section({ title, children }) {
|
|
244
|
-
return h('div', { style: 'padding:20px 32px' },
|
|
245
|
-
title ? h('h3', {}, title) : null,
|
|
246
|
-
...(Array.isArray(children) ? children : [children])
|
|
247
|
-
);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// ---------- assembled page views ----------
|
|
251
|
-
|
|
252
|
-
export function HomeView({ state, onNav, onToggleWork, works, posts, manifesto, currentlyShipping }) {
|
|
253
|
-
return [
|
|
254
|
-
Hero({
|
|
255
|
-
title: 'the creative department of the internet.',
|
|
256
|
-
body: '247420 is a collective of mercurials. we ship fast, break things on purpose, and document honestly.',
|
|
257
|
-
accent: 'humor is load-bearing.'
|
|
258
|
-
}),
|
|
259
|
-
currentlyShipping ? h('div', { style: 'padding:0 32px 24px 32px' },
|
|
260
|
-
Panel({
|
|
261
|
-
title: 'currently shipping',
|
|
262
|
-
count: currentlyShipping.length,
|
|
263
|
-
style: 'max-width:560px;margin:0',
|
|
264
|
-
children: currentlyShipping.map((row, i) =>
|
|
265
|
-
Row({
|
|
266
|
-
key: i,
|
|
267
|
-
code: h('span', { style: `color:${row.live ? 'var(--panel-accent)' : 'var(--panel-text-3)'}` }, row.live ? '●' : '○'),
|
|
268
|
-
title: row.title, sub: row.sub, meta: row.meta
|
|
269
|
-
})
|
|
270
|
-
)
|
|
271
|
-
})
|
|
272
|
-
) : null,
|
|
273
|
-
Section({ title: '// works', children: WorksList({ works, openedIndex: state.opened, onToggle: onToggleWork }) }),
|
|
274
|
-
Section({ title: '// recent writing', children: WritingList({ posts }) }),
|
|
275
|
-
Section({ title: '// manifesto · rough draft', children: Manifesto({ paragraphs: manifesto }) })
|
|
276
|
-
];
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// ---------- chat ----------
|
|
280
|
-
|
|
281
|
-
export function ChatMessage({ who = 'them', avatar, text, time, typing, key, aicat }) {
|
|
282
|
-
const cls = 'chat-msg ' + who + (aicat && who === 'them' ? ' aicat' : '');
|
|
283
|
-
const av = h('span', { class: 'chat-avatar' }, avatar || (who === 'you' ? 'u' : '?'));
|
|
284
|
-
const body = typing
|
|
285
|
-
? h('span', { class: 'chat-typing' }, h('span'), h('span'), h('span'))
|
|
286
|
-
: h('span', { class: 'chat-bubble' }, text);
|
|
287
|
-
const meta = time ? h('div', { class: 'chat-meta' }, time) : null;
|
|
288
|
-
const stack = h('div', { class: 'chat-stack' },
|
|
289
|
-
body,
|
|
290
|
-
meta
|
|
291
|
-
);
|
|
292
|
-
return h('div', { key, class: cls },
|
|
293
|
-
who === 'you' ? stack : av,
|
|
294
|
-
who === 'you' ? av : stack
|
|
295
|
-
);
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
export function ChatComposer({ value, onInput, onSend, placeholder = 'message…', disabled }) {
|
|
299
|
-
const send = () => {
|
|
300
|
-
const v = (value || '').trim();
|
|
301
|
-
if (!v || disabled) return;
|
|
302
|
-
if (onSend) onSend(v);
|
|
303
|
-
};
|
|
304
|
-
return h('div', { class: 'chat-composer' },
|
|
305
|
-
h('textarea', {
|
|
306
|
-
value: value || '',
|
|
307
|
-
placeholder,
|
|
308
|
-
rows: 1,
|
|
309
|
-
oninput: (e) => onInput && onInput(e.target.value),
|
|
310
|
-
onkeydown: (e) => {
|
|
311
|
-
if (e.key === 'Enter' && !e.shiftKey) {
|
|
312
|
-
e.preventDefault();
|
|
313
|
-
send();
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
}),
|
|
317
|
-
h('button', {
|
|
318
|
-
class: 'send',
|
|
319
|
-
disabled: disabled || !(value && value.trim()),
|
|
320
|
-
onclick: send
|
|
321
|
-
}, '↑')
|
|
322
|
-
);
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
export function Chat({ title = 'chat', sub, messages = [], composer, header } = {}) {
|
|
326
|
-
return h('div', { class: 'chat' },
|
|
327
|
-
header || h('div', { class: 'chat-head' },
|
|
328
|
-
h('span', { class: 'dot' }),
|
|
329
|
-
h('span', {}, title),
|
|
330
|
-
sub ? h('span', { class: 'sub' }, ' · ' + sub) : null,
|
|
331
|
-
h('span', { class: 'spread' }),
|
|
332
|
-
h('span', { class: 'sub' }, String(messages.length).padStart(2, '0') + ' msgs')
|
|
333
|
-
),
|
|
334
|
-
h('div', { class: 'chat-thread' },
|
|
335
|
-
...messages.map((m, i) => ChatMessage({ ...m, key: m.key != null ? m.key : i }))
|
|
336
|
-
),
|
|
337
|
-
composer || null
|
|
338
|
-
);
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
const AICAT_FACE = ` /\\_/\\\n( o.o )\n > ^ <`;
|
|
342
|
-
|
|
343
|
-
export function AICatPortrait({ name = 'aicat', status = 'idle', face } = {}) {
|
|
344
|
-
return h('div', { class: 'aicat-portrait' },
|
|
345
|
-
h('pre', { class: 'aicat-face' }, face || AICAT_FACE),
|
|
346
|
-
h('div', { class: 'aicat-meta' },
|
|
347
|
-
h('span', { class: 'name' }, name),
|
|
348
|
-
h('span', { class: 'status' },
|
|
349
|
-
h('span', { class: 'dot' }, '● '),
|
|
350
|
-
status
|
|
351
|
-
)
|
|
352
|
-
)
|
|
353
|
-
);
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
export function AICat({ name = 'aicat', messages = [], thinking, composer, status = 'online · purring' } = {}) {
|
|
357
|
-
const annotated = messages.map((m) =>
|
|
358
|
-
m.who === 'them' ? { ...m, aicat: true, avatar: m.avatar || '=^.^=' } : m
|
|
359
|
-
);
|
|
360
|
-
const all = thinking
|
|
361
|
-
? [...annotated, { who: 'them', aicat: true, avatar: '=^.^=', typing: true, key: '_thinking' }]
|
|
362
|
-
: annotated;
|
|
363
|
-
return h('div', { class: 'chat' },
|
|
364
|
-
h('div', { class: 'chat-head' },
|
|
365
|
-
h('span', { class: 'dot' }),
|
|
366
|
-
h('span', {}, name),
|
|
367
|
-
h('span', { class: 'sub' }, ' · ' + status),
|
|
368
|
-
h('span', { class: 'spread' }),
|
|
369
|
-
h('span', { class: 'sub' }, String(messages.length).padStart(2, '0') + ' turns')
|
|
370
|
-
),
|
|
371
|
-
h('div', { class: 'chat-thread' },
|
|
372
|
-
...all.map((m, i) => ChatMessage({ ...m, key: m.key != null ? m.key : i }))
|
|
373
|
-
),
|
|
374
|
-
composer || null
|
|
375
|
-
);
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
export { AICAT_FACE };
|
|
379
|
-
|
|
380
|
-
export function ProjectView({ project, copied, onCopy }) {
|
|
381
|
-
return [
|
|
382
|
-
Heading({ level: 1, children: project.name }),
|
|
383
|
-
Lede({ children: project.tagline }),
|
|
384
|
-
Heading({ level: 3, children: 'install' }),
|
|
385
|
-
Install({ cmd: project.install, copied, onCopy }),
|
|
386
|
-
Heading({ level: 3, children: 'receipt' }),
|
|
387
|
-
Receipt({ rows: project.receipt }),
|
|
388
|
-
Heading({ level: 3, children: 'changelog' }),
|
|
389
|
-
Changelog({ entries: project.changelog })
|
|
390
|
-
];
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
export { h };
|
|
2
|
+
export const h = webjsx.createElement;
|
|
3
|
+
|
|
4
|
+
export {
|
|
5
|
+
Brand, Chip, Btn, Glyph,
|
|
6
|
+
Topbar, Crumb, Side, Status, AppShell,
|
|
7
|
+
Heading, Lede
|
|
8
|
+
} from './components/shell.js';
|
|
9
|
+
|
|
10
|
+
export {
|
|
11
|
+
Panel, Row, RowLink,
|
|
12
|
+
Hero, Install, Receipt, Changelog,
|
|
13
|
+
WorksList, WritingList, Manifesto, Section,
|
|
14
|
+
HomeView, ProjectView
|
|
15
|
+
} from './components/content.js';
|
|
16
|
+
|
|
17
|
+
export {
|
|
18
|
+
fmtBytes, renderInline,
|
|
19
|
+
ChatMessage, ChatComposer, Chat,
|
|
20
|
+
AICAT_FACE, AICatPortrait, AICat
|
|
21
|
+
} from './components/chat.js';
|
package/src/debug.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const registry = new Map();
|
|
2
|
+
|
|
3
|
+
export function register(name, snapshot) {
|
|
4
|
+
if (typeof name !== 'string' || !name) throw new Error('debug.register: name required');
|
|
5
|
+
if (typeof snapshot !== 'function') throw new Error('debug.register: snapshot fn required');
|
|
6
|
+
registry.set(name, snapshot);
|
|
7
|
+
expose();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function unregister(name) { registry.delete(name); expose(); }
|
|
11
|
+
|
|
12
|
+
export function list() { return [...registry.keys()]; }
|
|
13
|
+
|
|
14
|
+
export function snapshot(name) {
|
|
15
|
+
const fn = registry.get(name);
|
|
16
|
+
if (!fn) return null;
|
|
17
|
+
try { return fn(); } catch (e) { return { error: String(e && e.message || e) }; }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function snapshotAll() {
|
|
21
|
+
const out = {};
|
|
22
|
+
for (const [k, fn] of registry) {
|
|
23
|
+
try { out[k] = fn(); } catch (e) { out[k] = { error: String(e && e.message || e) }; }
|
|
24
|
+
}
|
|
25
|
+
return out;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function expose() {
|
|
29
|
+
if (typeof window === 'undefined') return;
|
|
30
|
+
const api = { list, snapshot, snapshotAll, register, unregister };
|
|
31
|
+
Object.defineProperty(window, '__debug', { value: api, configurable: true, writable: false });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
expose();
|
package/src/highlight.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { register } from './debug.js';
|
|
2
|
+
|
|
3
|
+
const PRISM_BASE = 'https://cdn.jsdelivr.net/npm/prismjs@1.30.0';
|
|
4
|
+
const LANGS = ['markup', 'css', 'clike', 'javascript', 'jsx', 'tsx', 'typescript', 'json', 'bash', 'python'];
|
|
5
|
+
let _prism = null;
|
|
6
|
+
let _stats = { highlights: 0, errors: 0 };
|
|
7
|
+
|
|
8
|
+
function injectScript(src) {
|
|
9
|
+
return new Promise((resolve, reject) => {
|
|
10
|
+
const s = document.createElement('script');
|
|
11
|
+
s.src = src; s.async = false;
|
|
12
|
+
s.onload = () => resolve();
|
|
13
|
+
s.onerror = () => reject(new Error('script failed: ' + src));
|
|
14
|
+
document.head.appendChild(s);
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let _loadPromise = null;
|
|
19
|
+
export function ensurePrism() {
|
|
20
|
+
if (_prism) return Promise.resolve(_prism);
|
|
21
|
+
if (_loadPromise) return _loadPromise;
|
|
22
|
+
_loadPromise = (async () => {
|
|
23
|
+
if (!window.Prism) {
|
|
24
|
+
window.Prism = window.Prism || { manual: true, disableWorkerMessageHandler: true };
|
|
25
|
+
await injectScript(PRISM_BASE + '/prism.min.js');
|
|
26
|
+
}
|
|
27
|
+
for (const lang of LANGS) {
|
|
28
|
+
const has = window.Prism && window.Prism.languages && window.Prism.languages[lang];
|
|
29
|
+
if (has) continue;
|
|
30
|
+
try { await injectScript(`${PRISM_BASE}/components/prism-${lang}.min.js`); }
|
|
31
|
+
catch { _stats.errors += 1; }
|
|
32
|
+
}
|
|
33
|
+
_prism = window.Prism;
|
|
34
|
+
return _prism;
|
|
35
|
+
})();
|
|
36
|
+
return _loadPromise;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function highlightElement(el) {
|
|
40
|
+
const prism = await ensurePrism();
|
|
41
|
+
if (!el) return;
|
|
42
|
+
prism.highlightElement(el);
|
|
43
|
+
_stats.highlights += 1;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function highlightAllUnder(root) {
|
|
47
|
+
const prism = await ensurePrism();
|
|
48
|
+
const els = (root || document).querySelectorAll('pre code[class*="lang-"], pre code[class*="language-"]');
|
|
49
|
+
els.forEach((el) => {
|
|
50
|
+
const cls = el.className;
|
|
51
|
+
if (/lang-(\w+)/.test(cls) && !/language-/.test(cls)) {
|
|
52
|
+
const m = cls.match(/lang-(\w+)/);
|
|
53
|
+
if (m) el.classList.add('language-' + m[1]);
|
|
54
|
+
}
|
|
55
|
+
prism.highlightElement(el);
|
|
56
|
+
_stats.highlights += 1;
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
register('highlight', () => ({ loaded: !!_prism, langs: _prism ? Object.keys(_prism.languages || {}) : [], ...(_stats) }));
|
package/src/index.js
CHANGED
|
@@ -3,6 +3,11 @@ import { loadCss, scope } from './styles.js';
|
|
|
3
3
|
import { registerDeckStage, getDeckStage } from './deck-stage.js';
|
|
4
4
|
import * as components from './components.js';
|
|
5
5
|
import * as motion from './motion.js';
|
|
6
|
+
import * as debug from './debug.js';
|
|
7
|
+
import { renderMarkdown, ensureReady as ensureMarkdownReady } from './markdown.js';
|
|
8
|
+
import { ensurePrism, highlightAllUnder } from './highlight.js';
|
|
9
|
+
import { mountKit } from './bootstrap.js';
|
|
10
|
+
import { registerChatElement } from './web-components/ds-chat.js';
|
|
6
11
|
|
|
7
12
|
let _installed = false;
|
|
8
13
|
export async function installStyles(target) {
|
|
@@ -23,15 +28,27 @@ export function mount(rootEl, viewFn, { autoScope = true } = {}) {
|
|
|
23
28
|
}
|
|
24
29
|
const render = () => {
|
|
25
30
|
webjsx.applyDiff(rootEl, viewFn(render));
|
|
26
|
-
requestAnimationFrame(() =>
|
|
27
|
-
motion.animateTree(rootEl);
|
|
28
|
-
});
|
|
31
|
+
requestAnimationFrame(() => motion.animateTree(rootEl));
|
|
29
32
|
};
|
|
30
33
|
render();
|
|
31
34
|
return render;
|
|
32
35
|
}
|
|
33
36
|
|
|
34
|
-
|
|
37
|
+
if (typeof window !== 'undefined' && typeof customElements !== 'undefined') {
|
|
38
|
+
registerChatElement();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export {
|
|
42
|
+
webjsx, loadCss, scope, registerDeckStage, getDeckStage,
|
|
43
|
+
components, motion, debug, mountKit,
|
|
44
|
+
renderMarkdown, ensureMarkdownReady,
|
|
45
|
+
ensurePrism, highlightAllUnder,
|
|
46
|
+
registerChatElement
|
|
47
|
+
};
|
|
35
48
|
export const h = webjsx.createElement;
|
|
36
49
|
export const applyDiff = webjsx.applyDiff;
|
|
37
|
-
export default {
|
|
50
|
+
export default {
|
|
51
|
+
webjsx, loadCss, scope, installStyles, mount, h, applyDiff,
|
|
52
|
+
registerDeckStage, getDeckStage, components, motion, debug, mountKit,
|
|
53
|
+
renderMarkdown, ensurePrism, registerChatElement
|
|
54
|
+
};
|
package/src/markdown.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { register } from './debug.js';
|
|
2
|
+
|
|
3
|
+
let _marked = null;
|
|
4
|
+
let _purify = null;
|
|
5
|
+
let _stats = { renders: 0, sanitizedTags: 0 };
|
|
6
|
+
|
|
7
|
+
async function loadMarked() {
|
|
8
|
+
if (_marked) return _marked;
|
|
9
|
+
const mod = await import('https://cdn.jsdelivr.net/npm/marked@15.0.7/lib/marked.esm.js');
|
|
10
|
+
_marked = mod.marked;
|
|
11
|
+
_marked.setOptions({ breaks: true, gfm: true });
|
|
12
|
+
return _marked;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function loadPurify() {
|
|
16
|
+
if (_purify) return _purify;
|
|
17
|
+
const mod = await import('https://cdn.jsdelivr.net/npm/dompurify@3.4.1/+esm');
|
|
18
|
+
_purify = mod.default || mod;
|
|
19
|
+
if (!_purify.sanitize) throw new Error('dompurify did not load a sanitize fn');
|
|
20
|
+
_purify.addHook('uponSanitizeElement', (node, data) => {
|
|
21
|
+
if (data.tagName && data.allowedTags && !data.allowedTags[data.tagName]) _stats.sanitizedTags += 1;
|
|
22
|
+
});
|
|
23
|
+
return _purify;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let _ready = null;
|
|
27
|
+
export function ensureReady() {
|
|
28
|
+
if (_ready) return _ready;
|
|
29
|
+
_ready = Promise.all([loadMarked(), loadPurify()]).then(() => true);
|
|
30
|
+
return _ready;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function renderMarkdown(text) {
|
|
34
|
+
const [marked, purify] = await Promise.all([loadMarked(), loadPurify()]);
|
|
35
|
+
const dirty = marked.parse(String(text || ''));
|
|
36
|
+
const clean = purify.sanitize(dirty, {
|
|
37
|
+
FORBID_TAGS: ['script', 'iframe', 'object', 'embed', 'form'],
|
|
38
|
+
FORBID_ATTR: ['onerror', 'onclick', 'onload', 'onmouseover'],
|
|
39
|
+
ADD_ATTR: ['target', 'rel']
|
|
40
|
+
});
|
|
41
|
+
_stats.renders += 1;
|
|
42
|
+
return clean;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function renderMarkdownSync(text) {
|
|
46
|
+
if (!_marked || !_purify) return null;
|
|
47
|
+
const dirty = _marked.parse(String(text || ''));
|
|
48
|
+
const clean = _purify.sanitize(dirty, {
|
|
49
|
+
FORBID_TAGS: ['script', 'iframe', 'object', 'embed', 'form'],
|
|
50
|
+
FORBID_ATTR: ['onerror', 'onclick', 'onload', 'onmouseover'],
|
|
51
|
+
ADD_ATTR: ['target', 'rel']
|
|
52
|
+
});
|
|
53
|
+
_stats.renders += 1;
|
|
54
|
+
return clean;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
register('markdown', () => ({
|
|
58
|
+
loaded: { marked: !!_marked, dompurify: !!_purify },
|
|
59
|
+
renders: _stats.renders,
|
|
60
|
+
sanitizedTags: _stats.sanitizedTags
|
|
61
|
+
}));
|