openvibers/openvibe.shared dock-share.js
dock-share.js
| 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 | ; |
| 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 |