Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 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 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | 19x 57x 57x 53x 53x 53x 53x 18x 18x 1x 1x 1x 1x 59x 57x 57x 57x 57x 57x 57x 57x 57x 57x 57x 57x 57x 57x 57x 57x 28x 28x 57x 57x | import * as bootstrap from "bootstrap";
/**
* Passing notices: something happened that is worth knowing and needs nothing doing about it.
* The error dialog is for the other kind.
*/
let nextQuietId = 0;
function toastContainer() {
const existing = document.querySelector(".toast-container");
if (existing) return existing;
const container = document.createElement("div");
container.className = "toast-container position-fixed bottom-0 end-0 p-3";
document.body.appendChild(container);
return container;
}
// Storage can be unreachable or full, and a notice that nothing needs doing about is not worth
// failing over: forget the answer and say the thing again.
function remembered(key) {
try {
return !!window.localStorage.getItem(key);
} catch (e) {
console.log(`Unable to read ${key}: ${e}`);
return false;
}
}
function remember(key, wanted) {
try {
if (wanted) window.localStorage.setItem(key, "yes");
else Ewindow.localStorage.removeItem(key);
} catch (e) {
console.log(`Unable to remember ${key}: ${e}`);
}
}
/**
* @param {string} message
* @param {object} [options]
* @param {string} [options.title] a heading, for a notice whose message does not say where it came from
* @param {string} [options.quietKey] offer to stop showing this kind of notice, remembering the answer here
*/
export function toast(message, { title = "", quietKey = "" } = {}) {
if (quietKey && remembered(quietKey)) return;
const element = document.createElement("div");
element.className = "toast text-bg-dark";
element.setAttribute("role", "status");
element.setAttribute("aria-live", "polite");
element.setAttribute("aria-atomic", "true");
const quietId = `toast-quiet-${nextQuietId++}`;
element.innerHTML = `
<div class="toast-header text-bg-dark">
<strong class="me-auto"></strong>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
<div class="toast-body">
<div class="message"></div>
<div class="form-check mt-2">
<input class="form-check-input" type="checkbox" id="${quietId}" />
<label class="form-check-label small" for="${quietId}">Stop telling me this</label>
</div>
</div>`;
// Disc names and the like come from the outside world, so they are set as text, never as markup.
element.querySelector(".message").textContent = message;
element.querySelector(".toast-header strong").textContent = title;
element.querySelector(".toast-header").hidden = !title;
const quiet = element.querySelector(".form-check");
quiet.hidden = !quietKey;
quiet.querySelector("input").addEventListener("change", (event) => remember(quietKey, event.target.checked));
toastContainer().appendChild(element);
// Bootstrap keys its instances by element, so one only removed stays held.
element.addEventListener("hidden.bs.toast", () => {
bootstrap.Toast.getInstance(element)?.dispose();
element.remove();
});
bootstrap.Toast.getOrCreateInstance(element).show();
return element;
}
|