Skip to content

Instantly share code, notes, and snippets.

@nbogie
Created June 9, 2026 13:03
Show Gist options
  • Select an option

  • Save nbogie/4ff4bf38522ae7411e891d7987451fd3 to your computer and use it in GitHub Desktop.

Select an option

Save nbogie/4ff4bf38522ae7411e891d7987451fd3 to your computer and use it in GitHub Desktop.
sort miro videos numerically - script for console
/*
* Sort Miro recording-library videos by their <h2> titles.
*
* Run by pasting the whole thing into the browser console on the
* authenticated Miro page that shows the video list.
*
* Strategy (resilient to Miro's hashed CSS class names, which change
* between sessions):
* 1. Find the <h2> titles that look like video titles (most start
* with a number, e.g. "22. Straw Poll").
* 2. Their lowest common ancestor is the list container.
* 3. Each video item is the direct child of that container holding one h2.
* 4. Sort: unnumbered intros first (alphabetically), then numbered
* items in numeric order. Re-append in that order.
*/
(function sortMiroVideos() {
const allH2 = Array.from(document.querySelectorAll('h2'))
.filter((h) => h.textContent.trim().length > 0);
// h2s that clearly belong to the video list: leading number like "22." / "00."
const numbered = allH2.filter((h) => /^\s*\d+[.)\s]/.test(h.textContent));
if (numbered.length < 2) {
console.warn('[sort-miro] Could not find numbered video titles — aborting.');
return;
}
// Lowest common ancestor of the numbered titles = the list container.
const chain = (el) => { const a = []; for (; el; el = el.parentElement) a.push(el); return a; };
const firstChain = chain(numbered[0]);
const container = firstChain.find((cand) => numbered.every((h) => cand.contains(h)));
if (!container) {
console.warn('[sort-miro] Could not locate a common list container — aborting.');
return;
}
// Direct child of `container` that holds a given descendant.
const itemFor = (h) => {
let el = h;
while (el.parentElement && el.parentElement !== container) el = el.parentElement;
return el.parentElement === container ? el : null;
};
// All h2s living inside the container (numbered + unnumbered intros), one item each.
const seen = new Set();
const items = [];
for (const h of allH2) {
if (!container.contains(h)) continue;
const item = itemFor(h);
if (!item || seen.has(item)) continue;
seen.add(item);
const title = h.textContent.trim();
const m = title.match(/^\s*(\d+)/);
items.push({ item, title, hasNum: !!m, num: m ? parseInt(m[1], 10) : 0 });
}
// Unnumbered intros first (alphabetical), then numbered ascending.
items.sort((a, b) => {
if (a.hasNum !== b.hasNum) return a.hasNum ? 1 : -1; // unnumbered first
if (!a.hasNum) return a.title.localeCompare(b.title); // alpha among intros
return a.num - b.num || a.title.localeCompare(b.title); // numeric among rest
});
// appendChild moves an existing node, so appending in order reorders in place.
items.forEach(({ item }) => container.appendChild(item));
console.log(`[sort-miro] Reordered ${items.length} videos:`);
console.table(items.map((x, i) => ({ position: i + 1, title: x.title })));
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment