您需要先安装一个扩展,例如 篡改猴、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-04-10 // @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>`; const ATTR_BYLINE = "data-mfnr-byline"; const ATTR_NAVIGATOR = "data-mfnr-nav"; 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.style["margin-left"] = "4px"; span.style["padding"] = "0 4px"; span.style["border-radius"] = "2px"; span.style["background-color"] = "#C8E0A1"; span.style["color"] = "#000"; span.style["font-size"] = "0.8em"; span.textContent = "me"; targetNode.after(span); }; const markOP = (targetNode) => { const wrapper = targetNode.parentNode.parentNode; wrapper.style["border-left"] = "5px solid #0004"; wrapper.style["padding-left"] = "10px"; }; 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("style", "vertical-align: middle; top: -1px;"); 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(ATTR_NAVIGATOR, ""); 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 = (firstRun = false) => { const start = performance.now(); const subsite = window.location.hostname.split(".")[0]; const opHighlight = subsite !== "ask" && subsite !== "projects"; // don't highlight OP on subsites with this built in const self = getCookie("USER_NAME"); // if not first run, remove any existing navigators (from both post and comments) if (!firstRun) { document .querySelectorAll(`span[${ATTR_NAVIGATOR}]`) .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; // 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; 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].join("")); const newCommentsElement = document.getElementById("newcomments"); if (newCommentsElement) { const observer = new MutationObserver(() => run(false)); observer.observe(newCommentsElement, { childList: true }); } run(true); })();