); unchanged ones are listed in one line.
*/
function fields(rows, { unchanged = true } = {}) {
let html = '';
const kept = [];
rows.forEach(({ label, from, to, collapsed, hint }) => {
const a = text(from), b = text(to);
if (a === b) {
if (a) kept.push(esc(label) + (a.length < 40 && !a.includes('\n') ? ` (${esc(a)})` : ''));
return;
}
const block = `${words(a, b)}`;
const note = hint ? ` (${esc(hint)})` : '';
html += collapsed
? `${esc(label)}${note}
${block} `
: `${esc(label)}${note}
${block}
`;
});
if (unchanged && kept.length) html += `Unchanged: ${kept.join(', ')}.
`;
return html;
}
/** Count chips: {added, removed, changed, unchanged}. */
function counts({ added = 0, removed = 0, changed = 0, unchanged = 0 }) {
const chip = (cls, label) => `${label}`;
return `${
chip('bg-green-900/40 text-green-400', `+${added} added`)}${
chip('bg-red-900/40 text-red-400', `−${removed} removed`)}${
chip('bg-yellow-900/40 text-yellow-400', `~${changed} changed`)}${
chip('bg-gray-800 text-gray-500', `${unchanged} unchanged`)}
`;
}
/**
* Wire a diff modal. render(api) fills api.body; it runs on open and when
* the From / to pickers change. Returns {open(from, to), close(), body,
* from, to, loading(), error(message)}.
*/
function modal(id, render) {
const root = document.getElementById(id);
if (!root) throw new Error(`VersionDiff.modal: no #${id} on the page (include version_diff_modal.html before the script that wires it)`);
const from = root.querySelector('[data-diff-from]');
const to = root.querySelector('[data-diff-to]');
const api = {
root, from, to,
body: root.querySelector('[data-diff-body]'),
open(a, b) {
if (from && a != null) from.value = a;
if (to && b != null) to.value = b;
root.classList.remove('hidden');
(to || root.querySelector('[data-diff-close]')).focus();
render(api);
},
close() { root.classList.add('hidden'); },
loading() { api.body.innerHTML = 'Loading…
'; },
error(message) { api.body.innerHTML = `${esc(message)}
`; },
};
[from, to].forEach(select => select && select.addEventListener('change', () => render(api)));
root.addEventListener('click', e => {
if (e.target === root || e.target.closest('[data-diff-close]')) api.close();
});
document.addEventListener('keydown', e => {
if (e.key === 'Escape' && !root.classList.contains('hidden')) api.close();
});
return api;
}
window.VersionDiff = { words, fields, counts, modal };
})();