您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
MetaFilter: navigate through users' comments, and highlight comments by OP and yourself
// ==UserScript== // @name MeFi Navigator Redux // @namespace https://github.com/klipspringr/mefi-userscripts // @version 2025-05-05 // @description MetaFilter: navigate through users' comments, and highlight comments by OP and yourself // @author Klipspringer // @supportURL https://github.com/klipspringr/mefi-userscripts // @license MIT // @match *://*.metafilter.com/* // @grant none // ==/UserScript== const SVG_UP = `<svg xmlns="http://www.w3.org/2000/svg" hidden style="display:none"><path id="mfnr-up" fill="currentColor" d="M 0 93.339 L 50 6.661 L 100 93.339 L 50 64.399 L 0 93.339 Z" /></svg>`; const SVG_DOWN = `<svg xmlns="http://www.w3.org/2000/svg" hidden style="display:none"><path id="mfnr-down" fill="currentColor" d="M 100 6.69 L 50 93.31 L 0 6.69 L 50 35.607 L 100 6.69 Z" /></svg>`; // CSS notes: // - mfnr-op needs to play nicely with .mod in threads where OP is a mod // - classic theme has different margins from modern, so we can't change margin-left without knowing what theme we're on // - relative positioning seems to work better const CLASSES = `<style> .mfnr-op { border-left: 5px solid #0004 !important; padding-left: 10px !important; position: relative !important; left: -15px !important; } @media (max-width: 550px) { .mfnr-op { left: -5px !important; } } .mfnr-self-badge { background-color: #C8E0A1; border-radius: 2px; color: #000; font-size: 0.8em; margin-left: 4px; padding: 0 4px; cursor: default; } .mfnr-nav { white-space: nowrap; } .mfnr-nav svg { vertical-align: middle; top: -1px; } </style>`; const ATTR_BYLINE = "data-mfnr-byline"; const getCookie = (key) => { const s = RegExp(key + "=([^;]+)").exec(document.cookie); if (!s || !s[1]) return null; return decodeURIComponent(s[1]); }; const markSelf = (targetNode) => { const span = document.createElement("span"); span.classList.add("mfnr-self-badge"); span.textContent = "me"; targetNode.after(span); }; const markOP = (targetNode) => targetNode.parentElement.parentElement.classList.add("mfnr-op"); const createLink = (href, svgHref) => { const a = document.createElement("a"); a.setAttribute("href", "#" + href); const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); svg.setAttribute("width", "1em"); svg.setAttribute("viewBox", "0 0 100 100"); svg.setAttribute("class", "mfnr-nav"); const use = document.createElementNS("http://www.w3.org/2000/svg", "use"); use.setAttribute("href", "#" + svgHref); svg.appendChild(use); a.appendChild(svg); return a; }; const processByline = ( bylineNode, user, anchor, anchors, firstRun, self = null, op = null ) => { // don't mark self or OP more than once if (firstRun || !bylineNode.hasAttribute(ATTR_BYLINE)) { if (self !== null && user === self) markSelf(bylineNode); if (op !== null && user === op) markOP(bylineNode); bylineNode.setAttribute(ATTR_BYLINE, ""); } if (anchors.length <= 1) return; const i = anchors.indexOf(anchor); const previous = anchors[i - 1]; const next = anchors[i + 1]; const navigator = document.createElement("span"); navigator.setAttribute("class", "mfnr-nav"); const nodes = ["["]; if (previous) nodes.push(createLink(previous, "mfnr-up")); nodes.push(anchors.length); if (next) nodes.push(createLink(next, "mfnr-down")); nodes.push("]"); navigator.append(...nodes); bylineNode.parentElement.appendChild(navigator); }; const run = (subsite, self, firstRun) => { const start = performance.now(); const opHighlight = subsite !== "ask" && subsite !== "projects"; // don't highlight OP on subsites with this built in // if not first run, remove any existing navigators (from both post and comments) if (!firstRun) document.querySelectorAll("span.mfnr-nav").forEach((n) => n.remove()); // post node // tested on all subsites, modern and classic, 2025-04-10 const postNode = document.querySelector( "div.copy > span.smallcopy > a:first-child" ); const op = postNode.textContent.trim(); // initialise with post const bylines = [[op, "top"]]; const mapUsersAnchors = new Map([[op, ["top"]]]); // comment nodes, excluding live preview // tested on all subsites, modern and classic, 2025-04-10 const commentNodes = document.querySelectorAll( "div.comments:not(#commentform *) > span.smallcopy > a:first-child" ); for (const node of commentNodes) { const user = node.textContent.trim(); const anchorElement = node.parentElement.parentElement.previousElementSibling; const anchor = anchorElement.getAttribute("name"); bylines.push([user, anchor]); const anchors = mapUsersAnchors.get(user) ?? []; mapUsersAnchors.set(user, anchors.concat(anchor)); } for (const [i, bylineNode] of [postNode, ...commentNodes].entries()) processByline( bylineNode, bylines[i][0], bylines[i][1], mapUsersAnchors.get(bylines[i][0]), firstRun, self, opHighlight && i > 0 ? op : null ); console.log( "mefi-navigator-redux", firstRun ? "first-run" : "new-comments", 1 + commentNodes.length, Math.round(performance.now() - start) + "ms" ); }; (() => { if ( !/^\/(\d|comments\.mefi)/.test(window.location.pathname) || /rss$/.test(window.location.pathname) ) return; document.body.insertAdjacentHTML("beforeend", SVG_UP + SVG_DOWN); document.body.insertAdjacentHTML("beforeend", CLASSES); const subsite = window.location.hostname.split(".", 1)[0]; const self = getCookie("USER_NAME"); const newCommentsElement = document.getElementById("newcomments"); if (newCommentsElement) { const observer = new MutationObserver(() => run(subsite, self, false)); observer.observe(newCommentsElement, { childList: true }); } run(subsite, self, true); })();