anentrypoint-design 0.0.26 → 0.0.28

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "anentrypoint-design",
3
- "version": "0.0.26",
3
+ "version": "0.0.28",
4
4
  "description": "247420 design system SDK — webjsx + modified ripple-ui, single-file ESM bundle for reproducible use of the AnEntrypoint design.",
5
5
  "type": "module",
6
6
  "main": "./dist/247420.js",
package/src/components.js CHANGED
@@ -278,23 +278,154 @@ export function HomeView({ state, onNav, onToggleWork, works, posts, manifesto,
278
278
 
279
279
  // ---------- chat ----------
280
280
 
281
- export function ChatMessage({ who = 'them', avatar, text, time, typing, key, aicat }) {
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 }) {
282
390
  const cls = 'chat-msg ' + who + (aicat && who === 'them' ? ' aicat' : '');
283
391
  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', {},
289
- body,
290
- meta
291
- );
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);
292
421
  return h('div', { key, class: cls },
293
422
  who === 'you' ? stack : av,
294
423
  who === 'you' ? av : stack
295
424
  );
296
425
  }
297
426
 
427
+ export { renderInline, renderMd, fmtBytes };
428
+
298
429
  export function ChatComposer({ value, onInput, onSend, placeholder = 'message…', disabled }) {
299
430
  const send = () => {
300
431
  const v = (value || '').trim();