openvibers/openvibe.shared dock-card.js

dock-card.js

main 4.3 KB · 72 lines · javascript

1/**
2 * openvibe-shared/dock-card.js: a known OpenVibe link's card under a message in the Dock (4.147.0).
3 *
4 * OpenVibe.Chat resolves the card when a message is sent (its server/chat/link-cards.js) and the DM API carries it on
5 * the message as `card`: { kind, url, title, line, note?, image, action: { label, href } | null }, for a video on
6 * OpenVibe.Video ("Watch together"), a party invite ("Join the party"), a shared voice or a skill on OpenVibe.Actor.
7 * dock.js loads this file the first time an open conversation shows a message with a card, so the Dock that every
8 * signed-in page carries stays small.
9 *
10 * OpenVibeDockCard(card, h) → the card's node, or null when it is not a card this can draw
11 *
12 * Every field is data: set as text, cut to size, a link only when it is https, a picture only from openvibe.media.
13 * The card is compact (a 72 px picture beside the words) because the Dock is narrow; the message stays the thing said.
14 */
15(function (root) {
16 'use strict';
17 if (typeof document === 'undefined' || root.OpenVibeDockCard) return;
18
19 const CSS = `
20.ovdk-card { display: flex; align-items: center; gap: 10px; box-sizing: border-box; max-width: min(100%, 300px); margin-top: 4px; padding: 8px 10px 8px 8px; border-radius: 14px; border: 1px solid var(--border, rgba(255,255,255,.1)); background: var(--bg-primary, #12141b); text-align: left; }
21.ovdk-card-pic { flex: none; width: 72px; aspect-ratio: 16 / 9; object-fit: cover; border-radius: 8px; background: color-mix(in srgb, var(--text-primary, #ececf1) 7%, transparent); }
22.ovdk-card-body { min-width: 0; display: grid; justify-items: start; gap: 2px; }
23.ovdk-card-t { font-size: 13.5px; font-weight: 700; line-height: 1.3; color: var(--text-primary, #ececf1); text-decoration: none; overflow-wrap: anywhere; }
24a.ovdk-card-t:hover { text-decoration: underline; }
25.ovdk-card-l { font-size: 12.5px; line-height: 1.35; color: var(--text-secondary, #b6b6c0); overflow-wrap: anywhere; }
26.ovdk-card-go { display: inline-flex; align-items: center; min-height: 32px; margin-top: 4px; padding: 0 12px; border-radius: 999px; font-size: 13px; font-weight: 700; text-decoration: none; color: var(--on-accent-strong, var(--on-accent, #fff)); background: var(--accent-strong, var(--accent, #3472d8)); }
27.ovdk-card-go:hover { filter: brightness(1.08); }
28@media (pointer: coarse) { .ovdk-card-go { min-height: 44px; padding: 0 16px; } }
29`;
30
31 const https = (u) => (typeof u === 'string' && /^https:\/\/[^\s"'<>]+$/.test(u) ? u : null);
32 const cut = (v, n) => Array.from(String(v)).slice(0, n).join('');
33
34 function styles() {
35 if (document.getElementById('ovdk-card-styles')) return;
36 const st = document.createElement('style');
37 st.id = 'ovdk-card-styles';
38 st.textContent = CSS;
39 document.head.appendChild(st);
40 }
41
42 function card(c, h) {
43 if (!c || typeof c !== 'object' || typeof c.title !== 'string' || !c.title || typeof h !== 'function') return null;
44 styles();
45 const url = https(c.url);
46 const box = h('div', 'ovdk-card');
47 if (c.kind) box.dataset.kind = cut(c.kind, 20);
48 const pic = typeof c.image === 'string' && /^https:\/\/openvibe\.media\/[^\s"'<>]+$/.test(c.image) ? c.image : null;
49 if (pic) {
50 const img = h('img', 'ovdk-card-pic');
51 img.src = pic; img.alt = ''; img.loading = 'lazy'; img.decoding = 'async';
52 box.append(img);
53 }
54 const body = h('div', 'ovdk-card-body');
55 const title = h(url ? 'a' : 'b', 'ovdk-card-t', cut(c.title, 120));
56 if (url) { title.href = url; title.target = '_blank'; title.rel = 'noopener nofollow'; }
57 body.append(title);
58 if (typeof c.line === 'string' && c.line) body.append(h('span', 'ovdk-card-l', cut(c.line, 200)));
59 if (typeof c.note === 'string' && c.note) body.append(h('span', 'ovdk-card-l', cut(c.note, 160)));
60 const go = c.action && typeof c.action === 'object' && https(c.action.href) && typeof c.action.label === 'string' && c.action.label ? c.action : null;
61 if (go) {
62 const a = h('a', 'ovdk-card-go', cut(go.label, 40));
63 a.href = https(go.href); a.target = '_blank'; a.rel = 'noopener nofollow';
64 body.append(a);
65 }
66 box.append(body);
67 return box;
68 }
69
70 root.OpenVibeDockCard = card;
71})(typeof window !== 'undefined' ? window : globalThis);
72