LINUX DO TOC

对 DiscoTOC 的 toc-processor 做运行时补丁:有标题就自动视为开启 TOC

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

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