anentrypoint-design 0.0.28 → 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/src/components.js CHANGED
@@ -1,524 +1,21 @@
1
1
  import * as webjsx from '../vendor/webjsx/index.js';
2
- const h = webjsx.createElement;
3
-
4
- // ---------- primitives ----------
5
-
6
- export function Brand({ name = '247420', leaf } = {}) {
7
- return h('span', { class: 'brand' }, name,
8
- leaf ? h('span', { class: 'slash' }, ' / ') : null,
9
- leaf || null);
10
- }
11
-
12
- export function Chip({ tone = '', children }) {
13
- return h('span', { class: 'chip' + (tone ? ' ' + tone : '') }, children);
14
- }
15
-
16
- export function Btn({ href = '#', primary, children, onClick }) {
17
- return h('a', {
18
- class: primary ? 'btn-primary' : 'btn',
19
- href,
20
- onclick: onClick
21
- }, children);
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
- function fmtBytes(n) {
282
- if (n == null) return '';
283
- if (n < 1024) return n + ' B';
284
- if (n < 1024 * 1024) return (n / 1024).toFixed(1) + ' KB';
285
- if (n < 1024 * 1024 * 1024) return (n / (1024 * 1024)).toFixed(1) + ' MB';
286
- return (n / (1024 * 1024 * 1024)).toFixed(2) + ' GB';
287
- }
288
-
289
- function renderInline(text) {
290
- if (text == null) return [];
291
- const out = [];
292
- const re = /(\*\*([^*]+)\*\*|\*([^*]+)\*|`([^`]+)`|\[([^\]]+)\]\(([^)]+)\))/g;
293
- let last = 0;
294
- let m;
295
- let i = 0;
296
- const push = (node) => out.push(node);
297
- while ((m = re.exec(text)) !== null) {
298
- if (m.index > last) push(h('span', { key: 's' + i + 'a' }, text.slice(last, m.index)));
299
- if (m[2] != null) push(h('strong', { key: 's' + i }, m[2]));
300
- else if (m[3] != null) push(h('em', { key: 's' + i }, m[3]));
301
- else if (m[4] != null) push(h('code', { key: 's' + i, class: 'chat-tick' }, m[4]));
302
- else if (m[5] != null) push(h('a', { key: 's' + i, href: m[6], target: '_blank', rel: 'noopener' }, m[5]));
303
- last = m.index + m[0].length;
304
- i += 1;
305
- }
306
- if (last < text.length) push(h('span', { key: 's' + i + 'a' }, text.slice(last)));
307
- return out;
308
- }
309
-
310
- function renderMd(text) {
311
- // markdown-lite block: blank line = paragraph, line starting with "- " = bullet.
312
- const lines = String(text || '').split('\n');
313
- const blocks = [];
314
- let para = [];
315
- let bullets = [];
316
- const flush = () => {
317
- if (para.length) { blocks.push({ kind: 'p', text: para.join(' ') }); para = []; }
318
- if (bullets.length) { blocks.push({ kind: 'ul', items: bullets.slice() }); bullets = []; }
319
- };
320
- lines.forEach((raw) => {
321
- const ln = raw.trim();
322
- if (!ln) { flush(); return; }
323
- if (/^[-*]\s+/.test(ln)) { if (para.length) flush(); bullets.push(ln.replace(/^[-*]\s+/, '')); return; }
324
- if (bullets.length) flush();
325
- para.push(ln);
326
- });
327
- flush();
328
- return blocks.map((b, i) =>
329
- b.kind === 'p'
330
- ? h('p', { key: 'b' + i }, ...renderInline(b.text))
331
- : h('ul', { key: 'b' + i }, ...b.items.map((it, j) => h('li', { key: 'l' + j }, ...renderInline(it))))
332
- );
333
- }
334
-
335
- const FILE_GLYPHS = { pdf: '▤', zip: '▦', tar: '▦', gz: '▦', mp4: '▶', mp3: '♪', wav: '♪', csv: '⊞', json: '{}', md: '§', txt: '§', default: '◫' };
336
- function fileGlyph(name) {
337
- const ext = String(name || '').split('.').pop().toLowerCase();
338
- return FILE_GLYPHS[ext] || FILE_GLYPHS.default;
339
- }
340
-
341
- const PART_RENDERERS = {
342
- text: (p) => h('div', { class: 'chat-bubble' }, ...renderInline(p.text || '')),
343
- md: (p) => h('div', { class: 'chat-bubble chat-md' }, ...renderMd(p.text || '')),
344
- code: (p) => h('div', { class: 'chat-bubble chat-code' },
345
- h('div', { class: 'chat-code-head' },
346
- h('span', { class: 'lang' }, p.lang || 'code'),
347
- p.filename ? h('span', { class: 'name' }, p.filename) : null
348
- ),
349
- h('pre', {}, h('code', { class: p.lang ? 'lang-' + p.lang : '' }, p.code || ''))
350
- ),
351
- image: (p) => h('a', { class: 'chat-image', href: p.href || p.src, target: '_blank', rel: 'noopener' },
352
- h('img', { src: p.src, alt: p.alt || '', loading: 'lazy' }),
353
- p.caption ? h('span', { class: 'cap' }, p.caption) : null
354
- ),
355
- pdf: (p) => h('div', { class: 'chat-pdf' },
356
- h('div', { class: 'chat-pdf-head' },
357
- h('span', { class: 'glyph' }, '▤'),
358
- h('span', { class: 'name' }, p.name || 'document.pdf'),
359
- p.size != null ? h('span', { class: 'size' }, fmtBytes(p.size)) : null,
360
- h('a', { class: 'open', href: p.src, target: '_blank', rel: 'noopener' }, 'open ↗')
361
- ),
362
- h('embed', { src: p.src, type: 'application/pdf' })
363
- ),
364
- file: (p) => h('a', { class: 'chat-file', href: p.src, target: '_blank', rel: 'noopener', download: p.name || true },
365
- h('span', { class: 'glyph' }, fileGlyph(p.name)),
366
- h('span', { class: 'meta' },
367
- h('span', { class: 'name' }, p.name || 'attachment'),
368
- h('span', { class: 'size' }, [p.kindLabel || (p.name || '').split('.').pop().toUpperCase(), p.size != null ? fmtBytes(p.size) : null].filter(Boolean).join(' · '))
369
- ),
370
- h('span', { class: 'go' }, '↓')
371
- ),
372
- link: (p) => h('a', { class: 'chat-link', href: p.href, target: '_blank', rel: 'noopener' },
373
- p.thumb ? h('img', { class: 'thumb', src: p.thumb, alt: '' }) : null,
374
- h('span', { class: 'meta' },
375
- h('span', { class: 'host' }, p.host || (() => { try { return new URL(p.href).host; } catch { return ''; } })()),
376
- h('span', { class: 'title' }, p.title || p.href),
377
- p.desc ? h('span', { class: 'desc' }, p.desc) : null
378
- )
379
- )
380
- };
381
-
382
- function renderPart(p, key) {
383
- const fn = PART_RENDERERS[p.kind] || PART_RENDERERS.text;
384
- const node = fn(p);
385
- if (node && typeof node === 'object') node.props = { ...(node.props || {}), key: 'p' + key };
386
- return node;
387
- }
388
-
389
- export function ChatMessage({ who = 'them', avatar, text, parts, time, typing, key, aicat, reactions, receipt, name }) {
390
- const cls = 'chat-msg ' + who + (aicat && who === 'them' ? ' aicat' : '');
391
- const av = h('span', { class: 'chat-avatar' }, avatar || (who === 'you' ? 'u' : '?'));
392
-
393
- let bodyNodes;
394
- if (typing) {
395
- bodyNodes = [h('span', { class: 'chat-typing', key: 'typ' }, h('span'), h('span'), h('span'))];
396
- } else if (parts && parts.length) {
397
- bodyNodes = parts.map((p, i) => renderPart(p, i));
398
- } else {
399
- bodyNodes = [h('div', { class: 'chat-bubble', key: 't' }, ...renderInline(text || ''))];
400
- }
401
-
402
- const reactionRow = reactions && reactions.length
403
- ? h('div', { class: 'chat-reactions' },
404
- ...reactions.map((r, i) => h('span', { class: 'rxn' + (r.you ? ' you' : ''), key: 'r' + i },
405
- h('span', { class: 'e' }, r.emoji),
406
- h('span', { class: 'n' }, String(r.count))
407
- )))
408
- : null;
409
-
410
- const receiptMark = who === 'you' && receipt
411
- ? h('span', { class: 'tick' + (receipt === 'read' ? ' read' : '') }, receipt === 'read' ? '✓✓' : '✓')
412
- : null;
413
-
414
- const metaItems = [];
415
- if (name && who === 'them') metaItems.push(h('span', { class: 'who', key: 'w' }, name));
416
- if (time) metaItems.push(h('span', { class: 't', key: 'ti' }, time));
417
- if (receiptMark) metaItems.push(receiptMark);
418
- const meta = metaItems.length ? h('div', { class: 'chat-meta' }, ...metaItems) : null;
419
-
420
- const stack = h('div', { class: 'chat-stack' }, ...bodyNodes, reactionRow, meta);
421
- return h('div', { key, class: cls },
422
- who === 'you' ? stack : av,
423
- who === 'you' ? av : stack
424
- );
425
- }
426
-
427
- export { renderInline, renderMd, fmtBytes };
428
-
429
- export function ChatComposer({ value, onInput, onSend, placeholder = 'message…', disabled }) {
430
- const send = () => {
431
- const v = (value || '').trim();
432
- if (!v || disabled) return;
433
- if (onSend) onSend(v);
434
- };
435
- return h('div', { class: 'chat-composer' },
436
- h('textarea', {
437
- value: value || '',
438
- placeholder,
439
- rows: 1,
440
- oninput: (e) => onInput && onInput(e.target.value),
441
- onkeydown: (e) => {
442
- if (e.key === 'Enter' && !e.shiftKey) {
443
- e.preventDefault();
444
- send();
445
- }
446
- }
447
- }),
448
- h('button', {
449
- class: 'send',
450
- disabled: disabled || !(value && value.trim()),
451
- onclick: send
452
- }, '↑')
453
- );
454
- }
455
-
456
- export function Chat({ title = 'chat', sub, messages = [], composer, header } = {}) {
457
- return h('div', { class: 'chat' },
458
- header || h('div', { class: 'chat-head' },
459
- h('span', { class: 'dot' }),
460
- h('span', {}, title),
461
- sub ? h('span', { class: 'sub' }, ' · ' + sub) : null,
462
- h('span', { class: 'spread' }),
463
- h('span', { class: 'sub' }, String(messages.length).padStart(2, '0') + ' msgs')
464
- ),
465
- h('div', { class: 'chat-thread' },
466
- ...messages.map((m, i) => ChatMessage({ ...m, key: m.key != null ? m.key : i }))
467
- ),
468
- composer || null
469
- );
470
- }
471
-
472
- const AICAT_FACE = ` /\\_/\\\n( o.o )\n > ^ <`;
473
-
474
- export function AICatPortrait({ name = 'aicat', status = 'idle', face } = {}) {
475
- return h('div', { class: 'aicat-portrait' },
476
- h('pre', { class: 'aicat-face' }, face || AICAT_FACE),
477
- h('div', { class: 'aicat-meta' },
478
- h('span', { class: 'name' }, name),
479
- h('span', { class: 'status' },
480
- h('span', { class: 'dot' }, '● '),
481
- status
482
- )
483
- )
484
- );
485
- }
486
-
487
- export function AICat({ name = 'aicat', messages = [], thinking, composer, status = 'online · purring' } = {}) {
488
- const annotated = messages.map((m) =>
489
- m.who === 'them' ? { ...m, aicat: true, avatar: m.avatar || '=^.^=' } : m
490
- );
491
- const all = thinking
492
- ? [...annotated, { who: 'them', aicat: true, avatar: '=^.^=', typing: true, key: '_thinking' }]
493
- : annotated;
494
- return h('div', { class: 'chat' },
495
- h('div', { class: 'chat-head' },
496
- h('span', { class: 'dot' }),
497
- h('span', {}, name),
498
- h('span', { class: 'sub' }, ' · ' + status),
499
- h('span', { class: 'spread' }),
500
- h('span', { class: 'sub' }, String(messages.length).padStart(2, '0') + ' turns')
501
- ),
502
- h('div', { class: 'chat-thread' },
503
- ...all.map((m, i) => ChatMessage({ ...m, key: m.key != null ? m.key : i }))
504
- ),
505
- composer || null
506
- );
507
- }
508
-
509
- export { AICAT_FACE };
510
-
511
- export function ProjectView({ project, copied, onCopy }) {
512
- return [
513
- Heading({ level: 1, children: project.name }),
514
- Lede({ children: project.tagline }),
515
- Heading({ level: 3, children: 'install' }),
516
- Install({ cmd: project.install, copied, onCopy }),
517
- Heading({ level: 3, children: 'receipt' }),
518
- Receipt({ rows: project.receipt }),
519
- Heading({ level: 3, children: 'changelog' }),
520
- Changelog({ entries: project.changelog })
521
- ];
522
- }
523
-
524
- 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();
@@ -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
- export { webjsx, loadCss, scope, registerDeckStage, getDeckStage, components, motion };
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 { webjsx, loadCss, scope, installStyles, mount, h, applyDiff, registerDeckStage, getDeckStage, components, motion };
50
+ export default {
51
+ webjsx, loadCss, scope, installStyles, mount, h, applyDiff,
52
+ registerDeckStage, getDeckStage, components, motion, debug, mountKit,
53
+ renderMarkdown, ensurePrism, registerChatElement
54
+ };