// ==UserScript== // @name Elements with ID lister // @namespace blankie-scripts // @match *://*/* // @grant GM_registerMenuCommand // @grant GM_getResourceURL // @require https://cdn.jsdelivr.net/npm/dialog-polyfill@0.5.6/dist/dialog-polyfill.min.js#sha256-cec1a2e320aab77e28bad4ad6bc5e532a6ef5757345c19bb5158aa880b7162a6 // @resource dialogPolyfillCSS https://cdn.jsdelivr.net/npm/dialog-polyfill@0.5.6/dist/dialog-polyfill.min.css#sha256-4dcb3ab62e545f30bf06a4824c253641ee889ca85ca28d5447590557922496ab // @version 1.0.14 // @author blankie // @description A userscript that adds a "Show elements popup" option to the Monkey Menu which lists all elements with an ID // @inject-into content // ==/UserScript== "use strict"; const DIALOG_WRAPPER_ID = "element_lister-dialog_wrapper"; const ACCENT_COLOR = "#962AC3"; const BRIGHT_ACCENT_COLOR = "#DE6DE6"; const DIALOG_WRAPPER_CSS = ` /* https://lamplightdev.com/blog/2019/03/26/why-is-my-web-component-inheriting-styles/ */ all: initial; position: fixed; top: 0; width: 100%; height: 100%; `; const CSS = ` dialog { position: fixed; min-width: 25em; max-height: 90%; max-width: 90%; font-size: 11pt; font-family: sans-serif; color-scheme: dark; overflow-y: auto; overflow-wrap: break-word; } dialog, dialog.polyfilled button { color: white; background-color: black; } dialog.polyfilled { top: 50%; left: 50%; transform: translate(-50%, -50%); } dialog::backdrop, dialog + .backdrop { background-color: rgba(0, 0, 0, .5); } .align-right { text-align: right; } button { /* https://stackoverflow.com/a/27722954 */ display: inline; } a { color: ${ACCENT_COLOR}; text-decoration: none; } a:hover { color: ${BRIGHT_ACCENT_COLOR}; text-decoration: underline; } `; function showElementList() { if (document.getElementById(DIALOG_WRAPPER_ID)) { return; } let dialogWrapper = document.createElement("div"); dialogWrapper.id = DIALOG_WRAPPER_ID; dialogWrapper.style = DIALOG_WRAPPER_CSS; let shadowRoot = dialogWrapper.attachShadow({mode: "closed"}); let link = document.createElement("link"); link.rel = "stylesheet"; link.href = GM_getResourceURL("dialogPolyfillCSS"); shadowRoot.append(link); let style = document.createElement("style"); style.textContent = CSS; shadowRoot.append(style); let dialog = document.createElement("dialog"); if (!dialog.showModal) { dialog.classList.add("polyfilled"); } dialogPolyfill.registerDialog(dialog); dialog.addEventListener("close", hideElementList, {once: true, passive: true}); dialogWrapper.addEventListener("click", function(e) { let rect = dialog.getBoundingClientRect(); if (rect.top <= e.clientY && rect.left <= e.clientX && rect.bottom >= e.clientY && rect.right >= e.clientX) { return; } hideElementList(); }, {passive: true}); let buttonWrapper = document.createElement("div"); buttonWrapper.classList.add("align-right"); let button = document.createElement("button"); button.textContent = "Close"; button.addEventListener("click", hideElementList, {once: true, passive: true}); buttonWrapper.append(button); dialog.append(buttonWrapper); let hr = document.createElement("hr"); dialog.append(hr); let elements = getElementList(); if (elements.length) { let ul = document.createElement("ul"); for (let i of getElementList()) { ul.append(i); } dialog.append(ul); } else { dialog.append("There are no elements with an ID."); } shadowRoot.append(dialog); document.body.append(dialogWrapper); dialog.showModal(); } function hideElementList() { let dialogWrapper = document.getElementById(DIALOG_WRAPPER_ID); if (!dialogWrapper) { return; } dialogWrapper.remove(); } function getElementList() { let elements = []; for (let element of document.body.querySelectorAll("[id], a[name]")) { let id = element.id; if (!id && element.localName === "a") { id = element.name; } if (!id) { continue; } if (shouldIgnoreElement(element)) { continue; } elements.push(getElementListItem(element, id)); } return elements; } function shouldIgnoreElement(element) { // Check if the element is not visible // https://wayland.freedesktop.org/docs/html/apb.html has elements with no size if (element.localName !== "a") { let rect = element.getBoundingClientRect(); if (rect.height === 0 || rect.width === 0) { return true; } } // Check if the element is a svg or a part of one // https://arstechnica.com/information-technology/2023/05/critics-say-googles-new-zip-and-mov-domains-will-be-a-boon-to-scammers/ while (element) { if (element.localName === "svg") { return true; } element = element.parentElement; } return false; } function getElementListItem(element, id) { let newLocation = new URL(location.href); let li = document.createElement("li"); let a = document.createElement("a"); newLocation.hash = a.innerText = "#" + id; a.href = newLocation.href; a.target = "_self"; a.addEventListener("click", function(e) { if (e.ctrlKey) { return; } hideElementList(); }, {passive: true}); li.append(a); let description = getElementDescription(element); if (description) { li.append(" (", description, ")"); } return li; } function getElementDescription(element) { let addEilipses = false; // Attempt to get text by going up the DOM, as https://wayland.freedesktop.org/docs/html/apb.html has elements with no content let text = ""; while (!text && element) { text = (element.innerText || "").trim(); element = element.parentElement; } let newlineIndex = text.indexOf("\n"); if (newlineIndex !== -1) { text = text.substring(0, newlineIndex); addEilipses = true; } if (text.length > 50) { text = text.substring(0, 50); addEilipses = true; } if (addEilipses) { text += "..."; } return text; } GM_registerMenuCommand("Show elements popup", function() { if (!document.getElementById(DIALOG_WRAPPER_ID)) { showElementList(); } else { hideElementList(); } })