userscripts/Elements with ID lister.use...

245 lines
6.5 KiB
JavaScript

// ==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 <a name=...> 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();
}
})