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