UNPKG

auto-cms-server

Version:

Auto turn any webpage into editable CMS without coding.

1,112 lines (1,108 loc) 38.7 kB
"use strict"; (() => { // userscript/api.ts async function fetch_json(url, init) { return fetch(url, init).then((res) => res.json().catch((err) => ({ error: res.statusText }))).then((json) => { if (json.error) { throw json.error; } return json; }); } async function resolveFilePathname(pathname) { let json = await fetch_json( "/auto-cms/file", { method: "OPTIONS", headers: { "X-Pathname": encodeURIComponent(pathname) } } ); return json; } // userscript/i18n.ts var cjk_min_char = String.fromCharCode(parseInt("4E00", 16)); var cjk_max_char = String.fromCharCode(parseInt("9FFF", 16)); var allowedSymbols = ",.!?;\uFF0C\u3002\uFF01\uFF1F\uFF1B"; function isSymbol(char) { if ("a" <= char && char <= "z") return false; if ("A" <= char && char <= "Z") return false; if (cjk_min_char <= char && char <= cjk_max_char) return false; if (allowedSymbols.includes(char)) return false; return true; } function wrapText(text) { let chars = []; for (let char of text) { chars.push(char); } let start_index = chars.findIndex((char) => !isSymbol(char)); if (start_index == -1) return false; let end_index = text.length; for (; end_index > start_index; end_index--) { if (!isSymbol(chars[end_index - 1])) { break; } } let mid = chars.slice(start_index, end_index).join(""); if (!mid.split("").some((char) => !allowedSymbols.includes(char) && !isSymbol(char))) { return false; } while (start_index - 1 >= 0 && chars[start_index - 1].trim()) { start_index--; } while (end_index < chars.length && chars[end_index].trim()) { end_index++; } let before = chars.slice(0, start_index).join(""); mid = chars.slice(start_index, end_index).join(""); let after = chars.slice(end_index).join(""); return before + "{{" + mid + "}}" + after; } // userscript/string.ts function extractPadding(fullText, trimmedText) { let leading = ""; let tailing = ""; if (trimmedText.length > 0) { let index = fullText.indexOf(trimmedText[0]); leading = fullText.substring(0, index); index = fullText.lastIndexOf(trimmedText[trimmedText.length - 1]); tailing = fullText.substring(index + 1); } return { leading, tailing }; } // userscript/auto-cms.ts var version = "0.1.16"; var win = window; win.auto_cms = { version }; window.addEventListener("contextmenu", onContextMenu, { capture: true, passive: false }); window.addEventListener("click", onContextMenu, { capture: true, passive: false }); function onContextMenu(event) { if (!(event.altKey || event.ctrlKey)) { return; } let target = event.target; if (!(target instanceof HTMLElement) && !(target instanceof SVGElement)) { return; } event.preventDefault(); event.stopImmediatePropagation(); let menu = new AutoCMSMenu(); menu.show(event, target); } function ask(message, e, key, flag) { if (e instanceof Element && e.hasAttribute(key)) { let ans = prompt(message, e.getAttribute(key)); if (ans == null) return; if (ans) { e.setAttribute(key, ans); } else if (flag == "remove") { e.removeAttribute(key); } } else { let ans = prompt(message, e[key]); if (ans) { ; e[key] = ans; } } } function exportHTML() { let doc = document.documentElement.cloneNode(true); let body = doc.querySelector("body"); doc.querySelector("iframe#cmdline_iframe")?.remove(); doc.querySelector(".TridactylStatusIndicator")?.remove(); for (let style of doc.querySelectorAll("style")) { let text = style.textContent; if (text && text.includes("@media print") && text.includes(".TridactylStatusIndicator")) { style.remove(); continue; } if (text && text.includes(".cleanslate") && text.includes(".TridactylStatusIndicator")) { style.remove(); continue; } } if (body.classList.contains("vsc-initialized")) { body.classList.remove("vsc-initialized"); if (body.classList.length == 0) { body.removeAttribute("class"); } } doc.querySelector('script[src="/auto-cms.js"]')?.remove(); doc.querySelector("auto-cms-status")?.remove(); doc.querySelector("auto-cms-menu")?.remove(); doc.querySelector("style[auto-cms]")?.remove(); let html = "<!DOCTYPE html>\n" + doc.outerHTML; html = html.replace("<head", "\n <head"); while (html.includes("\n</body>")) { html = html.replace("\n</body>", "</body>"); } html = html.replace("</body></html>", "</body>\n</html>"); html = html.replace(/(<link .*?)>/g, (_, match) => match + " />"); html = html.replace(/(<meta .*?)>/g, (_, match) => match + " />"); html = html.replace(/(<img .*?)>/g, (_, match) => match + " />"); html = html.replace(/(<input .*?)>/g, (_, match) => match + " />"); return html; } function toTagText(target) { return target.outerHTML.replace(target.innerHTML, "").split("</")[0]; } function appendToHead(node) { if (!document.head) { document.body.parentElement.appendChild(document.createElement("head")); } document.head.appendChild(node); } function getHighestZIndex() { let max = 0; function walk(element) { let current = +getComputedStyle(element).zIndex; if (current > max) { max = current; } for (let child of element.children) { walk(child); } } walk(document.body); return max; } function addBootstrap() { addBootstrapCSS(); addBootstrapJS(); } function addFontAwesome() { addStyleSheetLink({ url: "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css", search: "font-awesome", integrity: "sha512-Evv84Mr4kqVGRNSgIGL/F/aIDqQb7xQ2vcrdIwxfjThSH8CSR7PBEakCr51Ck+w+/U6swU2Im1vVX0SVk9ABhg==" }); } function addBootstrapCSS() { addStyleSheetLink({ url: "https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css", search: "bootstrap", integrity: "sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" }); } function addBootstrapJS() { addScriptLink({ url: "https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js", search: "bootstrap", integrity: "sha384-kenU1KFdBIe4zVF0s0G1M5b4hcpxyD9F7jL+jjXkk+Q2h455rYXK/7HAuoJl+0I4" }); } function addStyleSheetLink(options) { let links = document.querySelectorAll( 'link[rel="stylesheet"]' ); for (let link2 of links) { if (link2.href.includes(options.search)) { return; } } let link = document.createElement("link"); link.setAttribute("href", options.url); link.setAttribute("rel", "stylesheet"); link.setAttribute("integrity", options.integrity); link.setAttribute("crossorigin", "anonymous"); appendToHead(link); } function addScriptLink(options) { let scripts = document.querySelectorAll("script[src]"); for (let script2 of scripts) { if (script2.src.includes(options.search)) { return; } } let script = document.createElement("script"); script.setAttribute("src", options.url); script.setAttribute("integrity", options.integrity); script.setAttribute("crossorigin", "anonymous"); document.body.appendChild(script); } var AutoCMSMenu = class _AutoCMSMenu extends HTMLElement { static instance; shadowRoot; target; teardownFns = []; constructor() { super(); this.shadowRoot = this.attachShadow({ mode: "open" }); let style = document.createElement("style"); style.innerHTML = /* css */ ` :host { position: fixed; border: 1px solid black; border-radius: 0.25rem; background-color: white; color: black; overflow: hidden; z-index: ${getHighestZIndex() + 1}; overflow: auto; } .auto-cms-menu--section { padding: 0.25rem; } .auto-cms-menu--section:hover { background-color: wheat; } .auto-cms-menu--list { margin: 0; padding: 0; list-style: none; } .auto-cms-menu--list button { width: 100%; padding: 0.25rem 0.5rem; } .auto-cms-menu--title { font-weight: bold; margin-top: 0.5rem; margin-bottom: 0.25rem; } `; this.appendChild(style); } flushTeardownFns() { this.teardownFns.forEach((fn) => fn()); this.teardownFns.length = 0; } wrapTeardownFn(fn) { let toggle = () => { let index = this.teardownFns.indexOf(fn); if (index == -1) { this.teardownFns.push(fn); } else { this.teardownFns.splice(index, 1); } }; return { toggle, fn }; } appendChild(node) { return this.shadowRoot.appendChild(node); } connectedCallback() { const target = this.target; if (!target) return; window.addEventListener("click", this.handleWindowClick, { capture: true, passive: false }); let updateSection = this.addSection("Update"); for (let node of target.childNodes) { if (node instanceof HTMLBRElement) { let br = node; let remove = this.wrapTeardownFn(() => { br.remove(); button.remove(); }); let { button } = this.addMenuItem( updateSection, "Remove <br>", (event) => { if (!br.hidden) { br.hidden = true; button.textContent = "Undo"; } else { br.hidden = false; button.textContent = "Remove <br>"; } remove.toggle(); } ); } else if (node.nodeType === Node.TEXT_NODE && node.nodeValue?.trim()) { let text = node.nodeValue.trim(); if (text.length > 7) { text = text.slice(0, 7) + "..."; } this.addMenuItem(updateSection, "Text: " + text, (event) => { let ans = prompt("text content (empty to remove)", node.nodeValue); if (ans == null) return; if (ans) { node.nodeValue = ans; } else { node.remove(); } }); this.addMenuItem(updateSection, "Add <br> and text", (event) => { let br = document.createElement("br"); node.after(br); let ans = prompt("new text"); if (ans) { br.after(ans); } }); } } const a = target.closest("a"); if (a) { this.addMenuItem(updateSection, "Link", (event) => { ask("hyperlink", a, "href"); }); } if (target instanceof HTMLImageElement) { this.addMenuItem(updateSection, "Image", (event) => { ask("image src", target, "src"); ask("image srcset", target, "srcset", "remove"); }); } if (target instanceof HTMLAudioElement) { this.addMenuItem(updateSection, "Audio", (event) => { ask("audio link", target, "src"); }); } if (target instanceof HTMLVideoElement) { this.addMenuItem(updateSection, "Video", (event) => { ask("video link", target, "src"); }); } let copySection = this.addSection("Copy"); this.addMenuItem(copySection, "Advanced Mode", (event) => { alert( 'right click > inspect > right-click element > click "Edit As HTML" > copy and paste' ); }); this.addMenuItem(copySection, "Easy Mode", (event) => { let addTarget = (target2, index) => { if (target2 == document.body) return; let targetText = `${index}: ${toTagText(target2)}`; let clonedTarget = null; let { button } = this.addMenuItem(copySection, targetText, (event2) => { if (clonedTarget) { clonedTarget.remove(); clonedTarget = null; button.textContent = targetText; } else { clonedTarget = target2.cloneNode(true); let parent = target2.parentElement; if (parent.lastElementChild === target2) { parent.appendChild(clonedTarget); } else { target2.insertAdjacentElement("afterend", clonedTarget); } button.textContent = "Undo"; } }); button.style.textAlign = "start"; if (target2.parentElement) { addTarget(target2.parentElement, index + 1); } }; addTarget(target, 1); }); let removeSection = this.addSection("Remove"); this.addMenuItem(removeSection, "Advanced Mode", (event) => { alert('right click > inspect > right-click element > click "Delete Node"'); }); this.addMenuItem(removeSection, "Easy Mode", (event) => { let addTarget = (target2, index) => { if (target2 == document.body) return; let targetText = `${index}: ${toTagText(target2)}`; let remove = this.wrapTeardownFn(() => { target2.remove(); button.remove(); }); let { button } = this.addMenuItem(removeSection, targetText, (event2) => { if (!target2.hasAttribute("hidden")) { target2.setAttribute("hidden", ""); button.textContent = "Undo"; } else { target2.removeAttribute("hidden"); button.textContent = targetText; } remove.toggle(); }); button.style.textAlign = "start"; if (target2.parentElement) { addTarget(target2.parentElement, index + 1); } }; addTarget(target, 1); }); let hideSection = this.addSection("Hide"); this.addMenuItem(hideSection, "Advanced Mode", (event) => { alert( 'right click > inspect > left-click element > edit style > set "display: none"' ); }); this.addMenuItem(hideSection, "Easy Mode", (event) => { let addTarget = (target2, index) => { if (target2 == document.body) return; let targetText = `${index}: ${toTagText(target2)}`; let { button } = this.addMenuItem(hideSection, targetText, (event2) => { if (!target2.hasAttribute("hidden")) { target2.setAttribute("hidden", ""); button.textContent = "Undo"; } else { target2.removeAttribute("hidden"); button.textContent = targetText; } }); button.style.textAlign = "start"; if (target2.parentElement) { addTarget(target2.parentElement, index + 1); } }; addTarget(target, 1); }); let removeChildrenSection = this.addSection("Remove Children"); this.addMenuItem(removeChildrenSection, "Advanced Mode", (event) => { alert(`right click > inspect > run "$0.textContent = ''"`); }); this.addMenuItem(removeChildrenSection, "Easy Mode", (event) => { let addTarget = (target2, index) => { if (target2 == document.body) return; let targetText = `${index}: ${toTagText(target2)}`; let innerHTML = target2.innerHTML; let remove = this.wrapTeardownFn(() => { target2.innerHTML = ""; button.remove(); }); let { button } = this.addMenuItem( removeChildrenSection, targetText, (event2) => { if (!target2.hasAttribute("hidden")) { target2.textContent = ""; button.textContent = "Undo"; } else { target2.innerHTML = innerHTML; button.textContent = targetText; } remove.toggle(); } ); button.style.textAlign = "start"; if (target2.parentElement) { addTarget(target2.parentElement, index + 1); } }; addTarget(target, 1); }); let i18nSection = this.addSection("i18n"); this.addMenuItem(i18nSection, "Extract Text", (event) => { let span = document.createElement("span"); document.body.appendChild(span); let extractText = (node) => { if (node.nodeType === Node.TEXT_NODE) { let fullText = node.nodeValue; if (!fullText) return; span.textContent = fullText; let trimmedText = span.innerText.trim(); if (!trimmedText.trim()) return; if (trimmedText.includes("{[") && trimmedText.includes("]}")) return; if (trimmedText.includes("{{") && trimmedText.includes("}}")) return; let padding = extractPadding(fullText, trimmedText); let text = padding.leading + trimmedText + padding.tailing; let wrappedText = wrapText(text); if (wrappedText) { node.nodeValue = wrappedText; } return; } if (node instanceof HTMLScriptElement || node instanceof SVGScriptElement || node instanceof HTMLStyleElement || node instanceof SVGStyleElement || node.nodeName.toLocaleLowerCase() == "noscript" || node instanceof AutoCMSStatus) { return; } for (let child of node.childNodes) { extractText(child); } }; extractText(document.body); span.remove(); }); this.addMenuItem(i18nSection, "Edit Translations", (event) => { let params = new URLSearchParams({ pathname: location.pathname }); let url = `/auto-cms/multi-lang?${params}`; window.open(url, "_blank"); }); let iframeSection = this.addSection("Iframe"); this.addMenuItem(iframeSection, "Expand", (event) => { let iFrames = document.getElementsByTagName("iframe"); for (let iframe of iFrames) { let outerHTML = iframe.outerHTML; let { button } = this.addMenuItem(iframeSection, outerHTML, (event2) => { let div = document.createElement("div"); { let n = iframe.attributes.length; for (let i = 0; i < n; i++) { let attr = iframe.attributes.item(i); div.setAttribute(attr.name, attr.value); } } { let innerHTML = iframe.contentWindow?.document.body.innerHTML; if (innerHTML) { div.innerHTML = innerHTML; } } iframe.replaceWith(div); button.remove(); }); } }); let metaSection = this.addSection("Meta for SEO"); this.addMenuItem(metaSection, "Page Title", (event) => { let og_meta = document.querySelector('meta[property="og:title"]'); let twitter_meta = document.querySelector('meta[name="twitter:title"]'); let ans = prompt( "Page title for SEO (keep it under 60 characters):", twitter_meta?.getAttribute("content") || og_meta?.getAttribute("content") || document.title ); if (!ans) return; twitter_meta?.remove(); og_meta?.remove(); document.title = ans; }); this.addMenuItem(metaSection, "Page Description", (event) => { let meta = document.querySelector('meta[name="description"]'); let og_meta = document.querySelector('meta[property="og:description"]'); let twitter_meta = document.querySelector( 'meta[name="twitter:description"]' ); let ans = prompt( "Page description for SEO (keep it between 155 - 160 characters):", twitter_meta?.getAttribute("content") || og_meta?.getAttribute("content") || meta?.getAttribute("content") || "" ); if (!ans) return; twitter_meta?.remove(); og_meta?.remove(); if (!meta) { meta = document.createElement("meta"); meta.setAttribute("name", "description"); appendToHead(meta); } meta.setAttribute("content", ans); }); this.addMenuItem(metaSection, "Preview Image", (event) => { let og_meta = document.querySelector('meta[property="og:image"]'); let twitter_meta = document.querySelector('meta[name="twitter:image"]'); let ans = prompt( "Preview image for SEO (recommended 1200x630px):", twitter_meta?.getAttribute("content") || og_meta?.getAttribute("content") || "" ); if (!ans) return; twitter_meta?.remove(); if (!og_meta) { og_meta = document.createElement("meta"); og_meta.setAttribute("property", "og:image"); appendToHead(og_meta); } og_meta.setAttribute("content", ans); }); let uiLibrarySection = this.addSection("UI Library"); this.addMenuItem(uiLibrarySection, "Add Font Awesome", (event) => { addFontAwesome(); }); this.addMenuItem(uiLibrarySection, "Add Bootstrap", (event) => { addBootstrap(); }); this.addMenuItem(uiLibrarySection, "Add Navbar", (event) => { let header = document.body.querySelector("header"); if (!header) { header = document.createElement("header"); document.body.prepend(header); } addBootstrap(); let nav = document.createElement("nav"); header.prepend(nav); nav.outerHTML = /* html */ ` <nav class="navbar navbar-expand-lg bg-light"> <div class="container-fluid"> <a class="navbar-brand" href="#">Navbar</a> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarSupportedContent"> <ul class="navbar-nav me-auto mb-2 mb-lg-0"> <li class="nav-item"> <a class="nav-link active" aria-current="page" href="#">Home</a> </li> <li class="nav-item"> <a class="nav-link" href="#">Link</a> </li> <li class="nav-item dropdown"> <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false"> Dropdown </a> <ul class="dropdown-menu"> <li><a class="dropdown-item" href="#">Action</a></li> <li><a class="dropdown-item" href="#">Another action</a></li> <li><hr class="dropdown-divider"></li> <li><a class="dropdown-item" href="#">Something else here</a></li> </ul> </li> <li class="nav-item"> <a class="nav-link disabled">Disabled</a> </li> </ul> <form class="d-flex" role="search"> <input class="form-control me-2" type="search" placeholder="Search" aria-label="Search"> <button class="btn btn-outline-success" type="submit">Search</button> </form> </div> </div> </nav> `; }); let miscSection = this.addSection("Misc"); this.addMenuItem(miscSection, "Favicon", async (event) => { let nodes = Array.from( document.querySelectorAll( 'link[rel="icon"],link[rel="shortcut icon"],link[rel="mask-icon"]' ) ); if (nodes.length == 0) { let link2 = document.createElement("link"); link2.setAttribute("rel", "icon"); link2.setAttribute("href", "/favicon.ico"); nodes.push(link2); document.head.appendChild(link2); } let link = nodes[0]; let href = link.getAttribute("href") || void 0; if (href && href[0] != "/") { href = "/" + href; } let ans = prompt("Image for favicon (recommended 32x32px):", href); if (ans == null) return; if (ans == "") { link.remove(); } else { link.setAttribute("href", ans); } for (let i = 1; i < nodes.length; i++) { nodes[i].remove(); } }); this.addMenuItem(miscSection, "PWA Icon", async (event) => { let nodes = Array.from( document.querySelectorAll('link[rel="apple-touch-icon"]') ); if (nodes.length == 0) { let link2 = document.createElement("link"); link2.setAttribute("rel", "apple-touch-icon"); nodes.push(link2); document.head.appendChild(link2); } let link = nodes[0]; let href = link.getAttribute("href") || void 0; if (href && href[0] != "/") { href = "/" + href; } let ans = prompt("Image for PWA icon (recommended 180x180px):", href); if (ans == null) return; if (ans == "") { link.remove(); } else { link.setAttribute("href", ans); } for (let i = 1; i < nodes.length; i++) { nodes[i].remove(); } }); this.addMenuItem(miscSection, "Deduplicate Selector", async (event) => { let button = event.target; let selector = prompt("selector: "); if (!selector) return; let nodes = Array.from(document.querySelectorAll(selector)).slice(1); for (let node of nodes) { node.remove(); } button.textContent = `Deduplicated ${nodes.length} elements by selector`; }); this.addMenuItem(miscSection, "Deduplicate Scripts", async (event) => { let button = event.target; let n = 0; let set = /* @__PURE__ */ new Set(); function check(selector, extractFn) { let nodes = document.querySelectorAll(selector); for (let node of nodes) { let str = extractFn(node); if (!str) { let next = node.nextElementSibling; if (next && next.matches(selector) && !extractFn(next)) { node.remove(); n++; } continue; } if (!set.has(str)) { set.add(str); continue; } node.remove(); n++; } } check("script", (node) => node.src || node.textContent); check("style", (node) => node.textContent); check('link[rel="stylesheet"][href]', (node) => node.href); check( 'link[rel="stylesheet"]:not([href])[id]', (node) => node.id ); check('link[rel="preconnect"][href]', (node) => node.href); check( 'link[rel="dns-prefetch"][href]', (node) => node.href ); check( 'meta[http-equiv="origin-trial"]', (node) => node.content ); let selectors = [ ".wistia_injected_style", 'iframe[owner="archetype"][title="archetype"]' ]; for (let selector of selectors) { for (let node of document.querySelectorAll(selector)) { node.remove(); n++; } } button.textContent = `Deduplicated ${n} scripts`; }); this.addMenuItem(miscSection, "Deduplicate Class", async (event) => { let button = event.target; let n = 0; let nodes = document.querySelectorAll("[class]"); for (let node of nodes) { let className = Array.from(node.classList).join(" "); if (node.getAttribute("class") != className) { node.setAttribute("class", className); n++; } } button.textContent = `Deduplicated ${n} classes`; }); this.addMenuItem(miscSection, "Remove Invisible IFrames", async (event) => { let button = event.target; let n = 0; let selectors = ['iframe[width="0"]', 'iframe[height="0"]']; for (let selector of selectors) { let nodes = document.querySelectorAll(selector); for (let node of nodes) { node.remove(); n++; } } for (let node of document.querySelectorAll("iframe")) { if (node.style.opacity == "0") { node.remove(); n++; } } for (let node of document.querySelectorAll("noscript")) { if (/<img(.|\n)*src="https:\/\/www\.facebook\.com\/tr\?/.test( node.innerText )) { node.remove(); n++; } } button.textContent = `Removed ${n} iframes`; }); this.addMenuItem(miscSection, "Remove Tracking Scripts", async (event) => { let button = event.target; let n = 0; let selectors = [ 'script[src*="://www.googleadservices.com/pagead/conversion/"]', 'script[src*="://googleads.g.doubleclick.net/"]', 'script[src*="://static.doubleclick.net/"]', 'iframe[src*="://td.doubleclick.net/"]', 'script[src*="://www.googletagmanager.com/gtag/"]', 'script[src*="://connect.facebook.net/signals/config/"]', 'script[src*="://connect.facebook.net/en_US/fbevents.js"]', 'script[src*="://js.callrail.com/"]', 'script[src*="//cdn.callrail.com/"]', 'script[src*="://utt.impactcdn.com/"]', 'script[src*="://browser.sentry-cdn.com/"]', 'script[src*="://js.sentry-cdn.com/"]', 'script[src*="://scripts.kissmetrics.io/"]', 'script[src*="://www.clickcease.com/"]', 'script[src*="://cdn.mida.so/js/"]', 'script[src*="://static.hotjar.com/"]' ]; for (let selector of selectors) { let nodes = document.querySelectorAll(selector); for (let node of nodes) { node.remove(); n++; } } let keywords = [ // facebook tracking pixel "https://www.facebook.com/tr?", 'https://connect.facebook.net/en_US/fbevents.js"', // google tag manager "https://www.googletagmanager.com/gtm.js", "gtag('js', new Date())", "gtag('config', '", "https://utt.impactcdn.com/", "https://monitor.clickcease.com", "https://www.clickcease.com/monitor/stat.js", "https://cdn.mida.so/js/optimize.js?", "//scripts.kissmetrics.io/" ]; for (let node of document.querySelectorAll( "script,noscript" )) { let text = node.textContent; if (!text) continue; for (let keyword of keywords) { if (text.includes(keyword)) { node.remove(); n++; break; } } } button.textContent = `Removed ${n} scripts`; }); this.addMenuItem(miscSection, "Add Stylesheet Link", async (event) => { this.addStyleSheetLink(); }); this.addMenuItem(miscSection, "Add Script Link", async (event) => { this.addInlineScript("script", "Input Javascript Code:"); }); this.addMenuItem(miscSection, "Add Inline Style", async (event) => { this.addInlineScript("style", "Input CSS Code:"); }); this.addMenuItem(miscSection, "Add Inline Script", async (event) => { this.addInlineScript("script", "Input Javascript Code:"); }); this.addMenuItem(miscSection, "Rearrange head & body", async (event) => { let button = event.target; let n = 0; let selectors = ["body title", "body meta", "body link"]; for (let selector of selectors) { for (let node of document.querySelectorAll(selector)) { document.head.appendChild(node); n++; } } button.textContent = `Rearranged ${n} elements`; }); let cmsSection = this.addSection("CMS"); this.addMenuItem(cmsSection, "Save", async (event) => { let button = event.target; try { button.textContent = "Saving"; this.flushTeardownFns(); await fetch_json("/auto-cms/file", { method: "PUT", headers: { "Content-Type": "text/html; charset=utf-8", "X-Pathname": location.pathname }, body: exportHTML() }); button.textContent = "Saved"; } catch (error) { alert(String(error)); button.textContent = "Save"; } }); this.addMenuItem(cmsSection, "Save Unmodified As", async (event) => { let button = event.target; let pathname = prompt( "Pathname:", decodeURI(location.pathname).replaceAll(/_bk[0-9T]{15}/g, "") ); if (!pathname) return; let path = await resolveFilePathname(pathname); pathname = path.pathname; if (path.exists) { let ans = confirm(`Confirm to override file: ${pathname} ?`); if (!ans) return; } else { let ans = confirm(`Confirm to save to new file" ${pathname} ?`); if (!ans) return; } try { button.textContent = "Saving"; await fetch_json("/auto-cms/file/copy", { method: "PUT", headers: { "X-From-Pathname": location.pathname, "X-To-Pathname": encodeURIComponent(pathname) } }); button.textContent = "Saved"; let ans = confirm(`Open ${pathname} ?`); if (ans) { window.open(location.origin + pathname, "_blank"); } } catch (error) { alert(String(error)); button.textContent = "Save Unmodified As"; } }); this.addMenuItem(cmsSection, "Show Files", (event) => { let url = location.origin + location.pathname; if (!url.endsWith("/")) { url += "/"; } url += "__list__"; window.open(url, "_blank"); }); } addSection(title) { let section = document.createElement("div"); section.className = "auto-cms-menu--section"; let titleNode = document.createElement("div"); titleNode.className = "auto-cms-menu--title"; titleNode.textContent = title; section.appendChild(titleNode); let ul = document.createElement("ul"); ul.className = "auto-cms-menu--list"; section.appendChild(ul); this.appendChild(section); return ul; } addMenuItem(ul, text, onclick) { let li = document.createElement("li"); let button = document.createElement("button"); button.textContent = text; button.onclick = onclick; li.appendChild(button); ul.appendChild(li); return { li, button }; } addInlineScript(tag, message) { let code = prompt(message); if (!code?.trim()) return; let script = document.createElement(tag); script.textContent = code; let id = prompt("id (optional):"); if (id) { script.id = id; } let desc = prompt("description (optional):"); if (desc) { script.setAttribute("description", desc); } let dest = prompt("Destination (head or body):"); if (dest == "head") { document.head.appendChild(script); } else { document.body.appendChild(script); } } askLink() { let url = prompt("URL:"); if (!url) return; url = url.replace(location.origin, ""); return url; } addStyleSheetLink() { let url = this.askLink(); if (!url) return; let target = prompt("Destination (head or body):"); let link = document.createElement("link"); link.setAttribute("rel", "stylesheet"); link.setAttribute("href", url); if (target == "head") { appendToHead(link); } else { document.body.appendChild(link); } } addScriptLink() { let url = this.askLink(); if (!url) return; let script = document.createElement("script"); script.setAttribute("src", url); document.body.appendChild(script); } disconnectedCallback() { this.flushTeardownFns(); window.removeEventListener("click", this.handleWindowClick, { capture: true }); } handleWindowClick = (event) => { let e = event.target; if (e instanceof HTMLElement && !e.closest("auto-cms-menu")) { event.stopImmediatePropagation(); event.preventDefault(); this.remove(); } }; show(event, target) { _AutoCMSMenu.instance?.remove(); _AutoCMSMenu.instance = this; if (event.y < window.innerHeight / 2) { let top = `${event.y}px + 1rem`; this.style.top = `calc(${top})`; this.style.maxHeight = `calc(100dvh - (${top}))`; } else { let bottom = `${window.innerHeight - event.y}px + 1rem`; this.style.bottom = `calc(${bottom})`; this.style.maxHeight = `calc(100dvh - (${bottom}))`; } if (event.x < window.innerWidth / 2) { this.style.left = `calc(${event.x}px + 1rem)`; } else { this.style.right = `calc(${window.innerWidth - event.x}px + 1rem)`; } this.target = target; document.body.appendChild(this); } }; customElements.define("auto-cms-menu", AutoCMSMenu); var AutoCMSStatus = class extends HTMLElement { connectedCallback() { this.innerHTML = /* html */ ` <style> auto-cms-status { position: fixed; top: 1rem; right: 1rem; border: 1px solid black; padding: 0.5rem; border-radius: 0.5rem; background-color: white; opacity: 0.8; z-index: ${getHighestZIndex() + 1}; } auto-cms-status:hover { opacity: 0; } .auto-cms-status--text { user-select: none; color: black; } </style> <span class="auto-cms-status--text">auto-cms enabled</span> `; } }; customElements.define("auto-cms-status", AutoCMSStatus); setTimeout(() => { document.body.appendChild(new AutoCMSStatus()); }); { let style = document.createElement("style"); style.setAttribute("auto-cms", ""); style.innerHTML = /* css */ ` [hidden] { display: none !important; } `; document.body.appendChild(style); } })();