openvibers/openvibe.shared dock-share.js

dock-share.js

main 6.0 KB · 92 lines · javascript

1/**
2 * openvibe-shared/dock-share.js: the Dock's "Send to…" (4.144.0). dock.js loads it the first time something is sent
3 * from a page (OpenVibeDock.share, the share sheet's OpenVibe Chat), so the Dock that every signed-in page carries
4 * stays small.
5 *
6 * OpenVibeDockShare.bar({ draft, h, chatCall, open, cancel, host }) → the bar, drawn above the person's conversations
7 * OpenVibeDockShare.ready({ draft, h, panel, form, box, fit }) the text in a conversation's box, unsent, said so
8 *
9 * The bar says what is being sent and lets the person write to someone new by their OpenVibe name: Chat's
10 * /api/dm/users/search finds them, POST /api/dm/conversations starts the conversation, both with the person's own
11 * token (dock.js's chatCall). Nothing is sent from here: the message waits in the conversation's box until the person
12 * presses Send there.
13 */
14(function (root) {
15 'use strict';
16 if (typeof document === 'undefined' || root.OpenVibeDockShare) return;
17
18 const CSS = `
19.ovdk-share { flex: none; display: grid; gap: 8px; padding: 12px 14px; border-bottom: 1px solid var(--border, rgba(255,255,255,.08)); background: color-mix(in srgb, var(--accent, #3b82f6) 9%, transparent); }
20.ovdk-share-h { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
21.ovdk-share-h b { font-size: 14px; color: var(--text-primary, #ececf1); }
22.ovdk-share-x { min-height: 32px; padding: 0 10px; border: 0; border-radius: 8px; background: none; color: var(--text-secondary, #b6b6c0); font: inherit; font-size: 13px; font-weight: 700; cursor: pointer; }
23.ovdk-share-x:hover { color: var(--text-primary, #ececf1); }
24.ovdk-share-what { margin: 0; font-size: 13px; color: var(--text-secondary, #b6b6c0); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
25.ovdk-to { display: flex; gap: 8px; }
26.ovdk-to input { flex: 1; min-width: 0; box-sizing: border-box; min-height: 40px; padding: 0 12px; border-radius: 12px; border: 1px solid var(--border-light, var(--border, rgba(255,255,255,.14))); background: var(--bg-primary, #12141b); color: var(--text-primary, #ececf1); font: inherit; font-size: 14px; }
27.ovdk-to input:focus { outline: none; border-color: var(--accent, #3b82f6); }
28.ovdk-to-go { flex: none; min-height: 40px; padding: 0 14px; border: 0; border-radius: 12px; cursor: pointer; font: inherit; font-size: 14px; font-weight: 700; color: var(--on-accent-strong, var(--on-accent, #fff)); background: var(--accent-strong, var(--accent, #3472d8)); }
29.ovdk-to-go:disabled { opacity: .55; cursor: default; }
30.ovdk-share .ovdk-err { margin: 0; }
31.ovdk-ready { flex: none; margin: 6px 14px 0; font-size: 12.5px; color: var(--text-secondary, #b6b6c0); }
32`;
33 function style(host) {
34 const st = (host && host.ownerDocument && host.ownerDocument.getElementById('ovdk-styles')) || document.getElementById('ovdk-styles');
35 if (st && !st.textContent.includes('.ovdk-share {')) st.textContent += CSS;
36 }
37
38 function bar(c) {
39 const { draft, h, chatCall, open, cancel, host } = c;
40 style(host);
41 let busy = false;
42 const el = h('div', 'ovdk-share');
43 const top = h('div', 'ovdk-share-h');
44 const x = h('button', 'ovdk-share-x', 'Cancel'); x.type = 'button';
45 x.addEventListener('click', () => cancel());
46 top.append(h('b', '', 'Send to…'), x);
47 const what = h('p', 'ovdk-share-what', draft.title || draft.text); what.title = draft.text;
48 const to = h('form', 'ovdk-to');
49 const name = h('input'); name.type = 'text'; name.maxLength = 40; name.autocomplete = 'off'; name.spellcheck = false;
50 name.placeholder = 'Someone new: their OpenVibe name'; name.setAttribute('aria-label', 'Their OpenVibe name');
51 const next = h('button', 'ovdk-to-go', 'Next'); next.type = 'submit';
52 const err = h('p', 'ovdk-err'); err.hidden = true;
53 to.append(name, next);
54 to.addEventListener('submit', async (e) => {
55 e.preventDefault();
56 const want = name.value.trim().replace(/^@/, '');
57 if (want.length < 2 || busy) return;
58 busy = true; next.disabled = true; err.hidden = true;
59 try {
60 const found = (await chatCall(`/api/dm/users/search?q=${encodeURIComponent(want)}`)).users || [];
61 const exact = found.find((u) => String(u.username || '').toLowerCase() === want.toLowerCase());
62 if (!exact) {
63 err.textContent = found.length ? `Nobody is called @${want}. Did you mean @${found[0].username}?` : `Nobody on OpenVibe is called @${want}.`;
64 err.hidden = false;
65 return;
66 }
67 const made = await chatCall('/api/dm/conversations', { method: 'POST', body: JSON.stringify({ user_ids: [exact.id] }) });
68 const id = (made.conversation && made.conversation.id) || made.id;
69 if (!id) throw new Error('no conversation');
70 await open(id);
71 } catch (z) {
72 err.textContent = z.status === 429 ? 'Too many new conversations just now. Try again in a little while.' : z.status === 403 ? (z.message || 'You can’t start a conversation with them.') : 'That did not work. Try again.';
73 err.hidden = false;
74 } finally { busy = false; next.disabled = false; }
75 });
76 el.append(top, what, to, err);
77 return el;
78 }
79
80 /** The conversation the person picked: the text in its box, and a line saying nothing is sent until they send it. */
81 function ready({ draft, h, panel, form, box, fit }) {
82 box.value = draft;
83 const line = h('p', 'ovdk-ready', 'Ready to send: change it if you like, then press Send.');
84 panel.insertBefore(line, form);
85 box.addEventListener('input', () => line.remove(), { once: true });
86 form.addEventListener('submit', () => line.remove(), { once: true });
87 setTimeout(fit, 0);
88 }
89
90 root.OpenVibeDockShare = { bar, ready };
91})(typeof window !== 'undefined' ? window : globalThis);
92