oficina / slides Public

Clone
git clone https://amadan.net/oficina/slides

Plain git — no account needed to clone.

Download

Download this file

1// fixtures/link-dialog.mjs — Ctrl+K. The dialog is server-rendered
2// (templates/editor.html: a labelled URL field and three named
3// buttons, every string from the catalog); this only opens it, waits
4// for the answer, and applies it to the view the key was pressed in.
5//
6// A <dialog> and not window.prompt: a prompt's label is the browser's,
7// its one button cannot say "remove", and it cannot be styled to meet
8// the focus and target rules the rest of the page meets.
9import { linkAt, setLink, removeLink } from './text-keys.mjs'
10import { safeHref } from './schema.mjs'
11
12/**
13 * Wraps the dialog. open(current) shows it with the field seeded and
14 * resolves to {action, href} when it closes: action is the value of
15 * the button that closed it ("insert", "remove"), or "cancel" for
16 * Cancel and Escape alike. A second open() while the dialog is up
17 * joins the first: both resolve to the one answer, and the field is
18 * not reseeded under the person typing in it.
19 */
20export function mountLinkDialog (dialog) {
21 if (!dialog) return null
22 const input = dialog.querySelector('input[name="href"]')
23 let pending = null
24 dialog.addEventListener('close', () => {
25 const done = pending
26 pending = null
27 if (done) done.resolve({ action: dialog.returnValue || 'cancel', href: input.value.trim() })
28 })
29 return {
30 open (current) {
31 if (pending) return pending.promise
32 input.value = current || ''
33 dialog.returnValue = ''
34 dialog.showModal()
35 input.select()
36 let resolve
37 const promise = new Promise(r => { resolve = r })
38 pending = { promise, resolve }
39 return promise
40 },
41 get isOpen () { return pending !== null }
42 }
43}
44
45/**
46 * Opens the dialog for view's selection and applies what comes back.
47 * An address outside the allowlist is treated as no address: the
48 * dialog closes and the text is left as it was (setLink would refuse
49 * it anyway; checking here keeps the caret where it was too).
50 */
51export async function linkPrompt (view, dialog) {
52 if (!dialog) return
53 const { action, href } = await dialog.open(linkAt(view.state))
54 if (view.isDestroyed) return
55 if (action === 'insert' && safeHref(href)) setLink(href)(view.state, view.dispatch)
56 else if (action === 'remove') removeLink(view.state, view.dispatch)
57 view.focus()
58}
59