1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
|
import { html, render, useEffect, useRef } from './vendor/htm-preact.js';
import { t } from './i18n.js';
/**
* `confirm` and `alert`, drawn by the page instead of the browser.
*
* In the desktop client a native `confirm()` leaves the window unable to type:
* once it closes, `document.hasFocus()` stays false, a click still moves
* `activeElement` to a field, and every keystroke after it goes nowhere — until
* the window loses and regains focus. Found as "the Create group fields are
* frozen" right after removing a member, and reproduced against Electron 44 by
* real X input events (`test_no_native_dialogs_in_the_spa.py`). `prompt` was
* already banned for throwing; the other two fail more quietly, and later.
*
* Both return a promise, so a call site reads as it did: `if (!(await
* ask(msg))) return;`.
*/
function Dialog({ message, cancellable, onDone }) {
const okRef = useRef(null);
useEffect(() => { if (okRef.current) okRef.current.focus(); }, []);
const onKeyDown = (e) => {
if (e.key === 'Escape') { e.preventDefault(); onDone(!cancellable); }
};
return html`
<div class="video-overlay" onClick=${(e) => {
if (e.target.classList.contains('video-overlay')) onDone(!cancellable);
}}>
<form class="music-detail playlist-modal" role="alertdialog" aria-modal="true"
onKeyDown=${onKeyDown}
onSubmit=${(e) => { e.preventDefault(); onDone(true); }}>
<div class="playlist-modal-body">
<div class="ask-message">${message}</div>
<div class="playlist-modal-actions">
${cancellable && html`
<button type="button" class="tb-btn" onClick=${() => onDone(false)}>
${t('dialog.cancel')}</button>`}
<button type="submit" class="admin-btn" ref=${okRef}>${t('dialog.ok')}</button>
</div>
</div>
</form>
</div>
`;
}
function open(message, cancellable) {
return new Promise((resolve) => {
const host = document.createElement('div');
document.body.appendChild(host);
const previous = document.activeElement;
let settled = false;
const onDone = (value) => {
if (settled) return;
settled = true;
render(null, host);
host.remove();
if (previous && previous.isConnected && typeof previous.focus === 'function') {
previous.focus();
}
resolve(value);
};
render(html`<${Dialog} message=${String(message)} cancellable=${cancellable}
onDone=${onDone} />`, host);
});
}
export const ask = (message) => open(message, true);
export const tell = (message) => open(message, false);
|