对 DiscoTOC 的 toc-processor 做运行时补丁:有标题就自动视为开启 TOC
// ==UserScript==
// @name LINUX DO TOC
// @namespace https://linux.do/
// @version 0.2.0
// @description 对 DiscoTOC 的 toc-processor 做运行时补丁:有标题就自动视为开启 TOC
// @match https://linux.do/t/*
// @match https://linux.do/*/t/*
// @run-at document-start
// @grant none
// @license MIT
// ==/UserScript==
(() => {
"use strict";
const STATE_KEY = "__linuxdo_auto_toc_patched__";
function getContainer() {
try {
const app = window.require?.("discourse/app")?.default;
if (app?.__container__) return app.__container__;
} catch {}
try {
if (window.Discourse?.__container__) return window.Discourse.__container__;
} catch {}
return null;
}
function refreshTOC(container) {
try {
const toc = container.lookup?.("service:toc-processor");
const topicController = container.lookup?.("controller:topic");
if (toc && topicController?.model) {
toc.checkPostforTOC(topicController.model);
}
} catch {}
}
function patchOnce() {
if (window[STATE_KEY]) return true;
const container = getContainer();
if (!container) return false;
const toc = container.lookup?.("service:toc-processor");
if (!toc || typeof toc.containsTocMarkup !== "function") return false;
if (toc[STATE_KEY]) {
window[STATE_KEY] = true;
refreshTOC(container);
return true;
}
const originalContains = toc.containsTocMarkup.bind(toc);
toc.containsTocMarkup = function (content) {
if (originalContains(content)) return true;
if (typeof this.containsHeadings === "function" && this.containsHeadings(content)) {
return true;
}
return false;
};
if (typeof toc.containsHeadings === "function") {
const originalHasHeadings = toc.containsHeadings.bind(toc);
toc.containsHeadings = function (content) {
return originalHasHeadings(content) || content.includes("<h6");
};
}
toc[STATE_KEY] = true;
window[STATE_KEY] = true;
refreshTOC(container);
setTimeout(() => refreshTOC(container), 300);
setTimeout(() => refreshTOC(container), 1200);
return true;
}
function hookNavigation() {
if (window.__linuxdo_auto_toc_nav_hooked__) return;
window.__linuxdo_auto_toc_nav_hooked__ = true;
const schedule = () => setTimeout(patchOnce, 0);
const origPush = history.pushState;
history.pushState = function () {
const res = origPush.apply(this, arguments);
schedule();
return res;
};
const origReplace = history.replaceState;
history.replaceState = function () {
const res = origReplace.apply(this, arguments);
schedule();
return res;
};
window.addEventListener("popstate", schedule, true);
document.addEventListener("DOMContentLoaded", schedule, { once: true });
}
hookNavigation();
const mo = new MutationObserver(() => patchOnce());
mo.observe(document.documentElement, { childList: true, subtree: true });
patchOnce();
})();