2024-03-03 21:23:37 +01:00
|
|
|
// Spells are a simplistic, composition-based replacement for the is="" attribute, which is not
|
|
|
|
// supported on Webkit due to Apple's engineering being a bunch of obstinate idiots who explicitly
|
|
|
|
// choose not to follow web standards.
|
|
|
|
|
|
|
|
// Limitations:
|
|
|
|
// - The data-cast attribute cannot be added dynamically.
|
|
|
|
// - There is no disconnectedCallback. Only a constructor which initializes a spell.
|
|
|
|
|
|
|
|
let spells = new Map();
|
|
|
|
let elementsWithUnknownSpells = new Map();
|
|
|
|
|
|
|
|
const sSpells = Symbol("spells");
|
|
|
|
|
|
|
|
function castSpellOnElement(spellName, element) {
|
|
|
|
element[sSpells] ??= new Map();
|
|
|
|
if (!elementsWithUnknownSpells.has(spellName)) {
|
|
|
|
elementsWithUnknownSpells.set(spellName, new Set());
|
|
|
|
}
|
|
|
|
|
|
|
|
let Spell = spells.get(spellName);
|
|
|
|
if (Spell != null && !element[sSpells].has(Spell)) {
|
|
|
|
element[sSpells].set(Spell, new Spell(element));
|
|
|
|
elementsWithUnknownSpells.get(spellName).delete(element);
|
|
|
|
} else {
|
|
|
|
elementsWithUnknownSpells.get(spellName).add(element);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function applySpells(elements) {
|
|
|
|
for (let element of elements) {
|
|
|
|
if (element instanceof Element) {
|
|
|
|
let spellListString = element.getAttribute("data-cast");
|
|
|
|
if (spellListString != null) {
|
|
|
|
let spellList = spellListString.split(' ');
|
|
|
|
for (let spellName of spellList) {
|
|
|
|
castSpellOnElement(spellName, element);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function addSpell(name, spell) {
|
|
|
|
spells.set(name, spell);
|
|
|
|
let elementsWithThisSpell = elementsWithUnknownSpells.get(name);
|
|
|
|
if (elementsWithThisSpell != null) {
|
|
|
|
for (let element of elementsWithThisSpell) {
|
|
|
|
castSpellOnElement(name, element);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Returns a spell's data. Gotchas: the spell needs to already be on the element.
|
|
|
|
// Therefore, if this is used from within a spell, the requested spell must have already been
|
|
|
|
// applied by this point.
|
|
|
|
// Someday I may change this to an async function that resumes whenever the spell is available to
|
|
|
|
// iron over this limitation. But today is not that day.
|
|
|
|
export function spell(element, spell) {
|
|
|
|
return element[sSpells].get(spell);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Apply spells to elements which have them and have been loaded so far.
|
|
|
|
let loadedSoFar = document.querySelectorAll("[data-cast]");
|
|
|
|
applySpells(loadedSoFar);
|
|
|
|
|
|
|
|
// For all other elements, add a mutation observer that will handle them.
|
|
|
|
let mutationObserver = new MutationObserver(records => {
|
|
|
|
for (let record of records) {
|
|
|
|
let mutatedNodes = new Set();
|
|
|
|
// NOTE: Added nodes may contain children which also need to be processed.
|
|
|
|
// Collect those that have [data-cast] on them and apply spells to them.
|
|
|
|
for (let addedNode of record.addedNodes) {
|
2024-03-24 18:08:47 +01:00
|
|
|
if (!(addedNode instanceof Text)) {
|
|
|
|
if (addedNode.getAttribute("data-cast") != null) {
|
|
|
|
mutatedNodes.add(addedNode);
|
|
|
|
}
|
|
|
|
addedNode.querySelectorAll("[data-cast]").forEach(element => mutatedNodes.add(element));
|
2024-03-05 23:33:27 +01:00
|
|
|
}
|
2024-03-03 21:23:37 +01:00
|
|
|
}
|
|
|
|
applySpells(mutatedNodes);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
mutationObserver.observe(document, { subtree: true, childList: true });
|
|
|
|
|
|
|
|
// ------------ Common spells ------------
|
|
|
|
|
|
|
|
// js makes things visible only when JavaScript is enabled.
|
|
|
|
addSpell("js", function (element) {
|
|
|
|
element.style.display = "block";
|
|
|
|
});
|