Discourse 新标签页

专注优化 Discourse 论坛多种情况下点击链接的体验,可在新标签页打开主题帖等页面,支持大量可自定义细节,自动识别 Discourse 站点

目前為 2025-10-17 提交的版本,檢視 最新版本

// ==UserScript==
// @name         Discourse 新标签页
// @name:en      Discourse New Tab
// @namespace    https://github.com/selaky/discourse-new-tab
// @version      1.1.0
// @description  专注优化 Discourse 论坛多种情况下点击链接的体验,可在新标签页打开主题帖等页面,支持大量可自定义细节,自动识别 Discourse 站点
// @description:en Optimize link-click experience on Discourse: open topics and related pages in new tabs, highly customizable, auto-detects Discourse
// @author       selaky
// @homepageURL  https://github.com/selaky/discourse-new-tab
// @supportURL   https://github.com/selaky/discourse-new-tab/issues
// @match        http*://*/*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// @run-at       document-start
// @license      MIT
// ==/UserScript==

(() => {
  var __defProp = Object.defineProperty;
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
  var __getOwnPropNames = Object.getOwnPropertyNames;
  var __hasOwnProp = Object.prototype.hasOwnProperty;
  var __esm = (fn, res) => function __init() {
    return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
  };
  var __export = (target, all) => {
    for (var name in all)
      __defProp(target, name, { get: all[name], enumerable: true });
  };
  var __copyProps = (to, from, except, desc) => {
    if (from && typeof from === "object" || typeof from === "function") {
      for (let key of __getOwnPropNames(from))
        if (!__hasOwnProp.call(to, key) && key !== except)
          __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
    }
    return to;
  };
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);

  // src/detectors/siteDetector.ts
  function detectDiscourse(doc = document, win = window) {
    const url = win.location?.href || "";
    const signals = [
      // 强信号:meta generator 包含 Discourse(官方默认输出)
      metaGeneratorSignal(doc),
      // 强信号:窗口上暴露 Discourse 对象(不少站点保留)
      windowDiscourseSignal(win),
      // 中等信号:常见的 Discourse 专用 meta
      metaDiscourseSpecificSignal(doc),
      // 中等信号:常见的 DOM 结构(保守选择)
      domStructureSignal(doc),
      // 弱信号:URL 路径包含 Discourse 常见路由段
      urlPathPatternSignal(url)
    ];
    const matchedSignals = signals.filter((s) => s.matched).map(({ name, weight, note }) => ({ name, weight, note }));
    const score = matchedSignals.reduce((sum, s) => sum + s.weight, 0);
    const isDiscourse = score >= THRESHOLD;
    return { isDiscourse, score, threshold: THRESHOLD, matchedSignals };
  }
  function metaGeneratorSignal(doc) {
    try {
      const meta = doc.querySelector('meta[name="generator"]');
      const content = meta?.content?.toLowerCase?.() || "";
      const matched = content.includes("discourse");
      return { name: "meta:generator=Discourse", weight: 3, matched, note: content || void 0 };
    } catch {
      return { name: "meta:generator=Discourse", weight: 3, matched: false };
    }
  }
  function windowDiscourseSignal(win) {
    try {
      const matched = typeof win.Discourse !== "undefined";
      return { name: "window.Discourse \u5B58\u5728", weight: 3, matched };
    } catch {
      return { name: "window.Discourse \u5B58\u5728", weight: 3, matched: false };
    }
  }
  function metaDiscourseSpecificSignal(doc) {
    try {
      const metas = Array.from(doc.querySelectorAll("meta[name]"));
      const names = metas.map((m) => m.getAttribute("name") || "");
      const hasDiscourseMeta = names.some((n) => n.startsWith("discourse_")) || !!doc.querySelector('meta[name="application-name"][content*="Discourse" i]');
      return { name: "meta:discourse_* \u6216 application-name=Discourse", weight: 2, matched: !!hasDiscourseMeta };
    } catch {
      return { name: "meta:discourse_* \u6216 application-name=Discourse", weight: 2, matched: false };
    }
  }
  function domStructureSignal(doc) {
    try {
      const matched = !!(doc.getElementById("main-outlet") || doc.querySelector(".topic-list") || doc.querySelector('meta[property="og:site_name"]'));
      return { name: "DOM: #main-outlet/.topic-list/og:site_name", weight: 2, matched };
    } catch {
      return { name: "DOM: #main-outlet/.topic-list/og:site_name", weight: 2, matched: false };
    }
  }
  function urlPathPatternSignal(url) {
    try {
      const u = new URL(url);
      const p = u.pathname.toLowerCase();
      const patterns = ["/t/", "/u/", "/c/", "/tags", "/latest", "/top"];
      const matched = patterns.some((s) => p.includes(s));
      return { name: "URL \u8DEF\u5F84\u5305\u542B Discourse \u5E38\u89C1\u6BB5", weight: 1, matched, note: p };
    } catch {
      return { name: "URL \u8DEF\u5F84\u5305\u542B Discourse \u5E38\u89C1\u6BB5", weight: 1, matched: false };
    }
  }
  var THRESHOLD;
  var init_siteDetector = __esm({
    "src/detectors/siteDetector.ts"() {
      THRESHOLD = 3;
    }
  });

  // src/storage/gm.ts
  function isPromise(v) {
    return v && typeof v.then === "function";
  }
  async function gmGet(key, def) {
    try {
      const gmg = globalThis.GM_getValue;
      if (typeof gmg === "function") {
        const r = gmg(key, def);
        return isPromise(r) ? await r : r;
      }
      const GM = globalThis.GM;
      if (GM?.getValue) {
        return await GM.getValue(key, def);
      }
    } catch {
    }
    try {
      const raw = localStorage.getItem(`dnt:${key}`);
      return raw == null ? def : JSON.parse(raw);
    } catch {
      return def;
    }
  }
  async function gmSet(key, value) {
    try {
      const gms = globalThis.GM_setValue;
      if (typeof gms === "function") {
        const r = gms(key, value);
        if (isPromise(r)) await r;
        return;
      }
      const GM = globalThis.GM;
      if (GM?.setValue) {
        await GM.setValue(key, value);
        return;
      }
    } catch {
    }
    try {
      localStorage.setItem(`dnt:${key}`, JSON.stringify(value));
    } catch {
    }
  }
  function gmRegisterMenu(label, cb) {
    try {
      const reg = globalThis.GM_registerMenuCommand;
      if (typeof reg === "function") {
        reg(label, cb);
        return;
      }
    } catch {
    }
  }
  var init_gm = __esm({
    "src/storage/gm.ts"() {
    }
  });

  // src/storage/domainLists.ts
  function normalizeDomain(input) {
    try {
      const s = (input || "").trim().toLowerCase();
      if (/^https?:\/\//i.test(s)) {
        return new URL(s).hostname;
      }
      return s.split(":")[0];
    } catch {
      return (input || "").trim().toLowerCase();
    }
  }
  function uniqSort(arr) {
    return Array.from(new Set(arr.filter(Boolean).map(normalizeDomain))).sort();
  }
  async function getLists() {
    const whitelist = await gmGet(KEY_WHITE, []) || [];
    const blacklist = await gmGet(KEY_BLACK, []) || [];
    return { whitelist: uniqSort(whitelist), blacklist: uniqSort(blacklist) };
  }
  async function addToWhitelist(domain) {
    const { whitelist } = await getLists();
    const d = normalizeDomain(domain);
    if (!whitelist.includes(d)) {
      whitelist.push(d);
      await gmSet(KEY_WHITE, uniqSort(whitelist));
      return { added: true, list: uniqSort(whitelist) };
    }
    return { added: false, list: whitelist };
  }
  async function removeFromWhitelist(domain) {
    const { whitelist } = await getLists();
    const d = normalizeDomain(domain);
    const next = whitelist.filter((x) => x !== d);
    const removed = next.length !== whitelist.length;
    if (removed) await gmSet(KEY_WHITE, uniqSort(next));
    return { removed, list: uniqSort(next) };
  }
  async function addToBlacklist(domain) {
    const { blacklist } = await getLists();
    const d = normalizeDomain(domain);
    if (!blacklist.includes(d)) {
      blacklist.push(d);
      await gmSet(KEY_BLACK, uniqSort(blacklist));
      return { added: true, list: uniqSort(blacklist) };
    }
    return { added: false, list: blacklist };
  }
  async function removeFromBlacklist(domain) {
    const { blacklist } = await getLists();
    const d = normalizeDomain(domain);
    const next = blacklist.filter((x) => x !== d);
    const removed = next.length !== blacklist.length;
    if (removed) await gmSet(KEY_BLACK, uniqSort(next));
    return { removed, list: uniqSort(next) };
  }
  function getCurrentHostname() {
    try {
      return location.hostname.toLowerCase();
    } catch {
      return "";
    }
  }
  async function getEnablement(autoIsDiscourse, host) {
    const { whitelist, blacklist } = await getLists();
    const h = normalizeDomain(host || getCurrentHostname());
    if (blacklist.includes(h)) return { enabled: false, reason: "blacklist" };
    if (whitelist.includes(h)) return { enabled: true, reason: "whitelist" };
    if (autoIsDiscourse) return { enabled: true, reason: "auto" };
    return { enabled: false, reason: "disabled" };
  }
  var KEY_WHITE, KEY_BLACK;
  var init_domainLists = __esm({
    "src/storage/domainLists.ts"() {
      init_gm();
      KEY_WHITE = "whitelist";
      KEY_BLACK = "blacklist";
    }
  });

  // src/storage/settings.ts
  async function getRuleFlags() {
    const saved = await gmGet(KEY_RULES, {}) || {};
    return { ...DEFAULTS, ...saved };
  }
  async function getRuleEnabled(ruleId) {
    const flags = await getRuleFlags();
    const v = flags[ruleId];
    return typeof v === "boolean" ? v : DEFAULTS[ruleId] ?? true;
  }
  async function setRuleEnabled(ruleId, enabled) {
    const flags = await getRuleFlags();
    flags[ruleId] = enabled;
    await gmSet(KEY_RULES, flags);
  }
  var RULE_TOPIC_OPEN_NEW_TAB, RULE_TOPIC_IN_TOPIC_OPEN_OTHER, RULE_TOPIC_SAME_TOPIC_KEEP_NATIVE, RULE_USER_OPEN_NEW_TAB, RULE_USER_IN_PROFILE_OPEN_OTHER, RULE_USER_SAME_PROFILE_KEEP_NATIVE, RULE_ATTACHMENT_KEEP_NATIVE, RULE_POPUP_USER_CARD, RULE_POPUP_USER_MENU, RULE_POPUP_SEARCH_MENU, RULE_SIDEBAR_NON_TOPIC_KEEP_NATIVE, RULE_SIDEBAR_IN_TOPIC_NEW_TAB, DEFAULTS, KEY_RULES;
  var init_settings = __esm({
    "src/storage/settings.ts"() {
      init_gm();
      RULE_TOPIC_OPEN_NEW_TAB = "topic:open-new-tab";
      RULE_TOPIC_IN_TOPIC_OPEN_OTHER = "topic:in-topic-open-other";
      RULE_TOPIC_SAME_TOPIC_KEEP_NATIVE = "topic:same-topic-keep-native";
      RULE_USER_OPEN_NEW_TAB = "user:open-new-tab";
      RULE_USER_IN_PROFILE_OPEN_OTHER = "user:in-profile-open-other";
      RULE_USER_SAME_PROFILE_KEEP_NATIVE = "user:same-profile-keep-native";
      RULE_ATTACHMENT_KEEP_NATIVE = "attachment:keep-native";
      RULE_POPUP_USER_CARD = "popup:user-card";
      RULE_POPUP_USER_MENU = "popup:user-menu";
      RULE_POPUP_SEARCH_MENU = "popup:search-menu-results";
      RULE_SIDEBAR_NON_TOPIC_KEEP_NATIVE = "sidebar:non-topic-keep-native";
      RULE_SIDEBAR_IN_TOPIC_NEW_TAB = "sidebar:in-topic-open-new-tab";
      DEFAULTS = {
        [RULE_TOPIC_OPEN_NEW_TAB]: true,
        [RULE_TOPIC_IN_TOPIC_OPEN_OTHER]: true,
        [RULE_TOPIC_SAME_TOPIC_KEEP_NATIVE]: true,
        [RULE_USER_OPEN_NEW_TAB]: true,
        [RULE_USER_IN_PROFILE_OPEN_OTHER]: true,
        [RULE_USER_SAME_PROFILE_KEEP_NATIVE]: true,
        [RULE_ATTACHMENT_KEEP_NATIVE]: true,
        [RULE_POPUP_USER_CARD]: true,
        [RULE_POPUP_USER_MENU]: true,
        [RULE_POPUP_SEARCH_MENU]: true,
        [RULE_SIDEBAR_NON_TOPIC_KEEP_NATIVE]: true,
        [RULE_SIDEBAR_IN_TOPIC_NEW_TAB]: true
      };
      KEY_RULES = "ruleFlags";
    }
  });

  // src/debug/settings.ts
  async function getDebugEnabled() {
    const v = await gmGet(KEY_DEBUG_ENABLED, false);
    return !!v;
  }
  async function setDebugEnabled(enabled) {
    await gmSet(KEY_DEBUG_ENABLED, !!enabled);
  }
  async function getDebugCategories() {
    const saved = await gmGet(KEY_DEBUG_CATEGORIES, {}) || {};
    return { ...DEFAULT_DEBUG_CATEGORIES, ...saved };
  }
  async function setDebugCategory(cat, enabled) {
    const cats = await getDebugCategories();
    cats[cat] = !!enabled;
    await gmSet(KEY_DEBUG_CATEGORIES, cats);
  }
  async function setAllDebugCategories(enabled) {
    const all = {
      site: enabled,
      click: enabled,
      link: enabled,
      rules: enabled,
      final: enabled
    };
    await gmSet(KEY_DEBUG_CATEGORIES, all);
  }
  var DEBUG_LABEL, KEY_DEBUG_ENABLED, KEY_DEBUG_CATEGORIES, DEFAULT_DEBUG_CATEGORIES;
  var init_settings2 = __esm({
    "src/debug/settings.ts"() {
      init_gm();
      DEBUG_LABEL = "[discourse-new-tab]";
      KEY_DEBUG_ENABLED = "debug:enabled";
      KEY_DEBUG_CATEGORIES = "debug:categories";
      DEFAULT_DEBUG_CATEGORIES = {
        site: true,
        click: true,
        link: true,
        rules: true,
        final: true
      };
    }
  });

  // src/ui/theme.ts
  async function initTheme() {
    currentTheme = await gmGet(KEY_THEME) || "auto";
    applyTheme();
  }
  function getTheme() {
    return currentTheme;
  }
  async function setTheme(theme) {
    currentTheme = theme;
    await gmSet(KEY_THEME, theme);
    applyTheme();
  }
  async function toggleTheme() {
    const idx = THEMES.indexOf(currentTheme);
    const next = THEMES[(idx + 1) % THEMES.length];
    await setTheme(next);
  }
  function applyTheme() {
    const root = document.documentElement;
    if (currentTheme === "auto") {
      const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
      root.setAttribute("data-dnt-theme", prefersDark ? "dark" : "light");
    } else {
      root.setAttribute("data-dnt-theme", currentTheme);
    }
  }
  var KEY_THEME, THEMES, ThemeIcon, currentTheme;
  var init_theme = __esm({
    "src/ui/theme.ts"() {
      init_gm();
      KEY_THEME = "ui-theme";
      THEMES = ["light", "dark", "auto"];
      ThemeIcon = {
        light: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
    <circle cx="12" cy="12" r="5"></circle>
    <line x1="12" y1="1" x2="12" y2="3"></line>
    <line x1="12" y1="21" x2="12" y2="23"></line>
    <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
    <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
    <line x1="1" y1="12" x2="3" y2="12"></line>
    <line x1="21" y1="12" x2="23" y2="12"></line>
    <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
    <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
  </svg>`,
        dark: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
    <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
  </svg>`,
        auto: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
    <circle cx="12" cy="12" r="10"></circle>
    <path d="M12 2 A 10 10 0 0 1 12 22 Z" fill="currentColor"></path>
  </svg>`
      };
      currentTheme = "auto";
      if (window.matchMedia) {
        window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", () => {
          if (currentTheme === "auto") {
            applyTheme();
          }
        });
      }
    }
  });

  // src/ui/i18n.ts
  async function initI18n() {
    currentLang = await gmGet(KEY_LANG) || "zh";
  }
  function getLanguage() {
    return currentLang;
  }
  async function setLanguage(lang) {
    currentLang = lang;
    await gmSet(KEY_LANG, lang);
  }
  async function toggleLanguage() {
    const idx = LANGUAGES.indexOf(currentLang);
    const next = LANGUAGES[(idx + 1) % LANGUAGES.length];
    await setLanguage(next);
  }
  function t(key) {
    const keys = key.split(".");
    let obj = translations[currentLang];
    for (const k of keys) {
      if (obj && typeof obj === "object") {
        obj = obj[k];
      } else {
        return key;
      }
    }
    return typeof obj === "string" ? obj : key;
  }
  var KEY_LANG, LANGUAGES, LanguageIcon, currentLang, translations;
  var init_i18n = __esm({
    "src/ui/i18n.ts"() {
      init_gm();
      KEY_LANG = "ui-language";
      LANGUAGES = ["zh", "en"];
      LanguageIcon = {
        zh: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
    <rect x="3" y="6" width="18" height="12" rx="2" ry="2"></rect>
    <text x="12" y="16" text-anchor="middle" font-size="11" font-weight="bold" fill="currentColor" stroke="none">\u4E2D</text>
  </svg>`,
        en: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
    <rect x="3" y="6" width="18" height="12" rx="2" ry="2"></rect>
    <text x="12" y="16" text-anchor="middle" font-size="9" font-weight="bold" fill="currentColor" stroke="none">EN</text>
  </svg>`
      };
      currentLang = "zh";
      translations = {
        zh: {
          settings: {
            title: "\u8BBE\u7F6E",
            close: "\u5173\u95ED",
            theme: {
              light: "\u65E5\u95F4\u6A21\u5F0F",
              dark: "\u591C\u95F4\u6A21\u5F0F",
              auto: "\u81EA\u52A8\u6A21\u5F0F"
            },
            language: {
              zh: "\u4E2D\u6587",
              en: "English"
            },
            status: {
              title: "\u5F53\u524D\u72B6\u6001",
              domain: "\u5F53\u524D\u57DF\u540D",
              enabled: "\u5DF2\u542F\u7528",
              disabled: "\u672A\u542F\u7528",
              reason: {
                auto: "\u81EA\u52A8\u8BC6\u522B",
                whitelist: "\u767D\u540D\u5355",
                blacklist: "\u9ED1\u540D\u5355",
                disabled: "\u672A\u8BC6\u522B\u4E3A Discourse"
              }
            },
            domain: {
              title: "\u8BBA\u575B\u8BC6\u522B",
              whitelist: "\u767D\u540D\u5355 - \u5F3A\u5236\u542F\u7528\u811A\u672C",
              blacklist: "\u9ED1\u540D\u5355 - \u5F3A\u5236\u7981\u7528\u811A\u672C",
              placeholder: "\u8F93\u5165\u57DF\u540D",
              add: "\u6DFB\u52A0",
              addCurrent: "\u6DFB\u52A0\u5F53\u524D\u57DF\u540D",
              edit: "\u7F16\u8F91",
              delete: "\u5220\u9664",
              empty: "\u6682\u65E0\u57DF\u540D"
            },
            rules: {
              title: "\u8DF3\u8F6C\u89C4\u5219",
              topic: {
                title: "\u4E3B\u9898\u5E16",
                openNewTab: "\u4ECE\u4EFB\u610F\u9875\u9762\u6253\u5F00\u4E3B\u9898\u5E16\u65F6\uFF0C\u7528\u65B0\u6807\u7B7E\u9875\u6253\u5F00",
                inTopicOpenOther: "\u5728\u4E3B\u9898\u5E16\u5185\u90E8\u70B9\u51FB\u5176\u4ED6\u94FE\u63A5\u65F6,\u7528\u65B0\u6807\u7B7E\u9875\u6253\u5F00",
                sameTopicKeepNative: "\u697C\u5C42\u8DF3\u8F6C\u65F6\u4FDD\u7559\u539F\u751F\u8DF3\u8F6C\u65B9\u5F0F"
              },
              user: {
                title: "\u4E2A\u4EBA\u4E3B\u9875",
                openNewTab: "\u4ECE\u4EFB\u610F\u9875\u9762\u6253\u5F00\u7528\u6237\u4E2A\u4EBA\u4E3B\u9875\u65F6,\u7528\u65B0\u6807\u7B7E\u9875\u6253\u5F00",
                inProfileOpenOther: "\u5728\u7528\u6237\u4E2A\u4EBA\u4E3B\u9875\u5185\u70B9\u51FB\u5176\u4ED6\u94FE\u63A5\u65F6,\u7528\u65B0\u6807\u7B7E\u9875\u6253\u5F00",
                sameProfileKeepNative: "\u540C\u4E00\u7528\u6237\u4E3B\u9875\u5185\u8DF3\u8F6C\u65F6\u4FDD\u7559\u539F\u751F\u65B9\u5F0F"
              },
              attachment: {
                title: "\u9644\u4EF6",
                keepNative: "\u6253\u5F00\u56FE\u7247\u7B49\u9644\u4EF6\u65F6,\u4FDD\u7559\u539F\u751F\u8DF3\u8F6C\u65B9\u5F0F"
              },
              popup: {
                title: "\u5F39\u7A97",
                userCard: "\u7528\u6237\u5361\u7247\u5185\u94FE\u63A5\u7528\u65B0\u6807\u7B7E\u9875\u6253\u5F00",
                userMenu: "\u7528\u6237\u83DC\u5355\u5185\u94FE\u63A5\u7528\u65B0\u6807\u7B7E\u9875\u6253\u5F00",
                // 新增:搜索框结果与“更多”按钮
                searchMenu: "\u641C\u7D22\u6846\u94FE\u63A5\u7528\u65B0\u6807\u7B7E\u9875\u6253\u5F00"
              },
              sidebar: {
                title: "\u4FA7\u8FB9\u680F",
                nonTopicKeepNative: "\u975E\u4E3B\u9898\u5E16\u5185\u4FA7\u8FB9\u680F\u7528\u539F\u751F\u65B9\u5F0F",
                inTopicNewTab: "\u4E3B\u9898\u5E16\u5185\u4FA7\u8FB9\u680F\u7528\u65B0\u6807\u7B7E\u9875\u6253\u5F00"
              }
            },
            debug: {
              title: "\u8C03\u8BD5",
              enable: "\u8C03\u8BD5\u6A21\u5F0F",
              allOn: "\u5168\u90E8\u5F00\u542F",
              allOff: "\u5168\u90E8\u5173\u95ED",
              categories: {
                site: "\u7AD9\u70B9\u8BC6\u522B",
                click: "\u70B9\u51FB\u8FC7\u6EE4\u539F\u56E0",
                link: "\u94FE\u63A5\u4FE1\u606F",
                rules: "\u89C4\u5219\u7EC6\u8282",
                final: "\u6700\u7EC8\u89C4\u5219\u4E0E\u52A8\u4F5C"
              }
            }
          }
        },
        en: {
          settings: {
            title: "Settings",
            close: "Close",
            theme: {
              light: "Light Mode",
              dark: "Dark Mode",
              auto: "Auto Mode"
            },
            language: {
              zh: "\u4E2D\u6587",
              en: "English"
            },
            status: {
              title: "Current Status",
              domain: "Current Domain",
              enabled: "Enabled",
              disabled: "Disabled",
              reason: {
                auto: "Auto-detected",
                whitelist: "Whitelist",
                blacklist: "Blacklist",
                disabled: "Not a Discourse forum"
              }
            },
            domain: {
              title: "Forum Recognition",
              whitelist: "Whitelist - Force Enable Script",
              blacklist: "Blacklist - Force Disable Script",
              placeholder: "Enter domain",
              add: "Add",
              addCurrent: "Add Current Domain",
              edit: "Edit",
              delete: "Delete",
              empty: "No domains"
            },
            rules: {
              title: "Navigation Rules",
              topic: {
                title: "Topics",
                openNewTab: "Open topics in new tab from any page",
                inTopicOpenOther: "Open other links in new tab within topics",
                sameTopicKeepNative: "Keep native behavior for floor jumps"
              },
              user: {
                title: "User Profiles",
                openNewTab: "Open user profiles in new tab from any page",
                inProfileOpenOther: "Open other links in new tab within profiles",
                sameProfileKeepNative: "Keep native behavior within same profile"
              },
              attachment: {
                title: "Attachments",
                keepNative: "Keep native behavior for images and attachments"
              },
              popup: {
                title: "Popups",
                userCard: "Open user card links in new tab",
                userMenu: "Open user menu links in new tab",
                // New: search popup results and "more" button
                searchMenu: "Open search box links in new tab"
              },
              sidebar: {
                title: "Sidebar",
                nonTopicKeepNative: "Keep native behavior in non-topic pages",
                inTopicNewTab: "Open sidebar links in new tab within topics"
              }
            },
            debug: {
              title: "Debug",
              enable: "Debug Mode",
              allOn: "Enable All",
              allOff: "Disable All",
              categories: {
                site: "Site Detection",
                click: "Click Filter Reasons",
                link: "Link Info",
                rules: "Rule Details",
                final: "Final Rule & Action"
              }
            }
          }
        }
      };
    }
  });

  // src/ui/sections/status.ts
  function renderStatusSection() {
    const section = document.createElement("div");
    section.className = "dnt-section";
    const title = document.createElement("h3");
    title.className = "dnt-section-title";
    title.textContent = t("settings.status.title");
    section.appendChild(title);
    const content = document.createElement("div");
    content.className = "dnt-status-content";
    content.id = STATUS_CONTENT_ID;
    section.appendChild(content);
    updateStatusContent(content);
    return section;
  }
  async function updateStatusContent(content) {
    const host = getCurrentHostname();
    const result = detectDiscourse();
    const enable = await getEnablement(result.isDiscourse, host);
    content.innerHTML = "";
    const domainRow = document.createElement("div");
    domainRow.className = "dnt-status-row";
    const domainLabel = document.createElement("span");
    domainLabel.className = "dnt-status-label";
    domainLabel.textContent = t("settings.status.domain") + ":";
    const domainValue = document.createElement("span");
    domainValue.className = "dnt-status-value dnt-domain-text";
    domainValue.textContent = host;
    domainRow.appendChild(domainLabel);
    domainRow.appendChild(domainValue);
    content.appendChild(domainRow);
    const statusRow = document.createElement("div");
    statusRow.className = "dnt-status-row";
    const statusLabel = document.createElement("span");
    statusLabel.className = "dnt-status-label";
    statusLabel.textContent = t(enable.enabled ? "settings.status.enabled" : "settings.status.disabled");
    const reasonBadge = document.createElement("span");
    reasonBadge.className = `dnt-badge dnt-badge-${enable.reason}`;
    reasonBadge.textContent = t(`settings.status.reason.${enable.reason}`);
    statusRow.appendChild(statusLabel);
    statusRow.appendChild(reasonBadge);
    content.appendChild(statusRow);
  }
  async function refreshStatusSection() {
    const content = document.getElementById(STATUS_CONTENT_ID);
    if (content) {
      await updateStatusContent(content);
    }
  }
  var STATUS_CONTENT_ID;
  var init_status = __esm({
    "src/ui/sections/status.ts"() {
      init_domainLists();
      init_siteDetector();
      init_i18n();
      STATUS_CONTENT_ID = "dnt-status-content";
    }
  });

  // src/ui/sections/domain.ts
  function renderDomainSection() {
    const section = document.createElement("div");
    section.className = "dnt-section";
    const title = document.createElement("h3");
    title.className = "dnt-section-title";
    title.textContent = t("settings.domain.title");
    section.appendChild(title);
    const content = document.createElement("div");
    content.className = "dnt-domain-content";
    const whitelistBlock = createListBlock("whitelist");
    content.appendChild(whitelistBlock);
    const blacklistBlock = createListBlock("blacklist");
    content.appendChild(blacklistBlock);
    section.appendChild(content);
    return section;
  }
  function createListBlock(type) {
    const block = document.createElement("div");
    block.className = "dnt-list-block";
    const subtitle = document.createElement("h4");
    subtitle.className = "dnt-list-subtitle";
    subtitle.textContent = t(`settings.domain.${type}`);
    block.appendChild(subtitle);
    const list = document.createElement("div");
    list.className = "dnt-domain-list";
    list.id = `dnt-${type}`;
    block.appendChild(list);
    const inputRow = document.createElement("div");
    inputRow.className = "dnt-input-row";
    const input = document.createElement("input");
    input.type = "text";
    input.className = "dnt-input";
    input.placeholder = t("settings.domain.placeholder");
    inputRow.appendChild(input);
    const addBtn = document.createElement("button");
    addBtn.className = "dnt-btn dnt-btn-primary";
    addBtn.textContent = t("settings.domain.add");
    addBtn.addEventListener("click", async () => {
      const domain = input.value.trim();
      if (domain) {
        await handleAdd(type, domain);
        input.value = "";
      }
    });
    inputRow.appendChild(addBtn);
    block.appendChild(inputRow);
    const addCurrentBtn = document.createElement("button");
    addCurrentBtn.className = "dnt-btn dnt-btn-secondary";
    addCurrentBtn.textContent = t("settings.domain.addCurrent");
    addCurrentBtn.addEventListener("click", () => {
      const host = getCurrentHostname();
      handleAdd(type, host);
    });
    block.appendChild(addCurrentBtn);
    refreshList(type);
    return block;
  }
  async function refreshList(type) {
    const lists = await getLists();
    const domains = lists[type];
    const container = document.getElementById(`dnt-${type}`);
    if (!container) return;
    container.innerHTML = "";
    if (domains.length === 0) {
      const empty = document.createElement("div");
      empty.className = "dnt-empty-text";
      empty.textContent = t("settings.domain.empty");
      container.appendChild(empty);
      return;
    }
    domains.forEach((domain) => {
      const item = document.createElement("div");
      item.className = "dnt-domain-item";
      const text = document.createElement("span");
      text.className = "dnt-domain-text";
      text.textContent = domain;
      item.appendChild(text);
      const deleteBtn = document.createElement("button");
      deleteBtn.className = "dnt-btn dnt-btn-danger dnt-btn-sm";
      deleteBtn.textContent = t("settings.domain.delete");
      deleteBtn.addEventListener("click", () => handleDelete(type, domain));
      item.appendChild(deleteBtn);
      container.appendChild(item);
    });
  }
  async function handleAdd(type, domain) {
    if (!domain) return;
    const fn = type === "whitelist" ? addToWhitelist : addToBlacklist;
    const result = await fn(domain);
    if (result.added) {
      await refreshList(type);
      await refreshStatusSection();
    }
  }
  async function handleDelete(type, domain) {
    const fn = type === "whitelist" ? removeFromWhitelist : removeFromBlacklist;
    const result = await fn(domain);
    if (result.removed) {
      await refreshList(type);
      await refreshStatusSection();
    }
  }
  var init_domain = __esm({
    "src/ui/sections/domain.ts"() {
      init_domainLists();
      init_i18n();
      init_status();
    }
  });

  // src/ui/sections/rules.ts
  function renderRulesSection() {
    const section = document.createElement("div");
    section.className = "dnt-section";
    const title = document.createElement("h3");
    title.className = "dnt-section-title";
    title.textContent = t("settings.rules.title");
    section.appendChild(title);
    const content = document.createElement("div");
    content.className = "dnt-rules-content";
    (async () => {
      const flags = await getRuleFlags();
      RULE_GROUPS.forEach((group) => {
        const groupBlock = document.createElement("div");
        groupBlock.className = "dnt-rule-group";
        const groupTitle = document.createElement("h4");
        groupTitle.className = "dnt-rule-group-title";
        groupTitle.textContent = t(group.title);
        groupBlock.appendChild(groupTitle);
        group.rules.forEach((rule) => {
          const ruleItem = createRuleItem(rule.id, t(rule.label), flags[rule.id] ?? true);
          groupBlock.appendChild(ruleItem);
        });
        content.appendChild(groupBlock);
      });
    })();
    section.appendChild(content);
    return section;
  }
  function createRuleItem(ruleId, label, enabled) {
    const item = document.createElement("div");
    item.className = "dnt-rule-item";
    const labelEl = document.createElement("label");
    labelEl.className = "dnt-rule-label";
    labelEl.textContent = label;
    const toggle = createToggle(ruleId, enabled);
    item.appendChild(labelEl);
    item.appendChild(toggle);
    return item;
  }
  function createToggle(ruleId, enabled) {
    const toggle = document.createElement("div");
    toggle.className = `dnt-toggle ${enabled ? "dnt-toggle-on" : "dnt-toggle-off"}`;
    toggle.setAttribute("data-rule-id", ruleId);
    const track = document.createElement("div");
    track.className = "dnt-toggle-track";
    const thumb = document.createElement("div");
    thumb.className = "dnt-toggle-thumb";
    track.appendChild(thumb);
    toggle.appendChild(track);
    toggle.addEventListener("click", async () => {
      const currentState = toggle.classList.contains("dnt-toggle-on");
      const newState = !currentState;
      await setRuleEnabled(ruleId, newState);
      toggle.classList.remove("dnt-toggle-on", "dnt-toggle-off");
      toggle.classList.add(newState ? "dnt-toggle-on" : "dnt-toggle-off");
    });
    return toggle;
  }
  var RULE_GROUPS;
  var init_rules = __esm({
    "src/ui/sections/rules.ts"() {
      init_settings();
      init_settings();
      init_i18n();
      RULE_GROUPS = [
        {
          title: "settings.rules.topic.title",
          rules: [
            { id: RULE_TOPIC_OPEN_NEW_TAB, label: "settings.rules.topic.openNewTab" },
            { id: RULE_TOPIC_IN_TOPIC_OPEN_OTHER, label: "settings.rules.topic.inTopicOpenOther" },
            { id: RULE_TOPIC_SAME_TOPIC_KEEP_NATIVE, label: "settings.rules.topic.sameTopicKeepNative" }
          ]
        },
        {
          title: "settings.rules.user.title",
          rules: [
            { id: RULE_USER_OPEN_NEW_TAB, label: "settings.rules.user.openNewTab" },
            { id: RULE_USER_IN_PROFILE_OPEN_OTHER, label: "settings.rules.user.inProfileOpenOther" },
            { id: RULE_USER_SAME_PROFILE_KEEP_NATIVE, label: "settings.rules.user.sameProfileKeepNative" }
          ]
        },
        {
          title: "settings.rules.attachment.title",
          rules: [{ id: RULE_ATTACHMENT_KEEP_NATIVE, label: "settings.rules.attachment.keepNative" }]
        },
        {
          title: "settings.rules.popup.title",
          rules: [
            { id: RULE_POPUP_USER_CARD, label: "settings.rules.popup.userCard" },
            { id: RULE_POPUP_USER_MENU, label: "settings.rules.popup.userMenu" },
            { id: RULE_POPUP_SEARCH_MENU, label: "settings.rules.popup.searchMenu" }
          ]
        },
        {
          title: "settings.rules.sidebar.title",
          rules: [
            { id: RULE_SIDEBAR_NON_TOPIC_KEEP_NATIVE, label: "settings.rules.sidebar.nonTopicKeepNative" },
            { id: RULE_SIDEBAR_IN_TOPIC_NEW_TAB, label: "settings.rules.sidebar.inTopicNewTab" }
          ]
        }
      ];
    }
  });

  // src/ui/sections/debug.ts
  function renderDebugSection() {
    const section = document.createElement("div");
    section.className = "dnt-section";
    const title = document.createElement("h3");
    title.className = "dnt-section-title";
    title.textContent = t("settings.debug.title");
    section.appendChild(title);
    const content = document.createElement("div");
    content.className = "dnt-rules-content";
    const mainRow = document.createElement("div");
    mainRow.className = "dnt-rule-item";
    const mainLabel = document.createElement("label");
    mainLabel.className = "dnt-rule-label";
    mainLabel.textContent = t("settings.debug.enable");
    const mainToggle = createToggle2(false, async (on) => {
      await setDebugEnabled(on);
      detailsBlock.style.display = on ? "" : "none";
    });
    mainToggle.id = "dnt-debug-main-toggle";
    mainRow.appendChild(mainLabel);
    mainRow.appendChild(mainToggle);
    content.appendChild(mainRow);
    const detailsBlock = document.createElement("div");
    detailsBlock.style.marginTop = "8px";
    const opsRow = document.createElement("div");
    opsRow.className = "dnt-input-row";
    const allOn = document.createElement("button");
    allOn.className = "dnt-btn dnt-btn-secondary";
    allOn.textContent = t("settings.debug.allOn");
    allOn.addEventListener("click", async () => {
      await setAllDebugCategories(true);
      refreshDetailToggles(detailsBlock);
    });
    const allOff = document.createElement("button");
    allOff.className = "dnt-btn dnt-btn-secondary";
    allOff.textContent = t("settings.debug.allOff");
    allOff.addEventListener("click", async () => {
      await setAllDebugCategories(false);
      refreshDetailToggles(detailsBlock);
    });
    opsRow.appendChild(allOn);
    opsRow.appendChild(allOff);
    detailsBlock.appendChild(opsRow);
    const cats = [
      { key: "site", label: t("settings.debug.categories.site") },
      { key: "click", label: t("settings.debug.categories.click") },
      { key: "link", label: t("settings.debug.categories.link") },
      { key: "rules", label: t("settings.debug.categories.rules") },
      { key: "final", label: t("settings.debug.categories.final") }
    ];
    const listBlock = document.createElement("div");
    listBlock.className = "dnt-rule-group";
    cats.forEach((c) => {
      const row = document.createElement("div");
      row.className = "dnt-rule-item";
      const l = document.createElement("label");
      l.className = "dnt-rule-label";
      l.textContent = c.label;
      const toggle = createToggle2(true, async (on) => {
        await setDebugCategory(c.key, on);
      });
      toggle.setAttribute("data-debug-cat", c.key);
      row.appendChild(l);
      row.appendChild(toggle);
      listBlock.appendChild(row);
    });
    detailsBlock.appendChild(listBlock);
    content.appendChild(detailsBlock);
    (async () => {
      const on = await getDebugEnabled();
      setToggleVisual(mainToggle, on);
      detailsBlock.style.display = on ? "" : "none";
      await refreshDetailToggles(detailsBlock);
    })();
    section.appendChild(content);
    return section;
  }
  async function refreshDetailToggles(container) {
    const cats = await getDebugCategories();
    container.querySelectorAll("[data-debug-cat]").forEach((el) => {
      const key = el.getAttribute("data-debug-cat");
      const on = cats[key] ?? true;
      setToggleVisual(el, on);
    });
  }
  function createToggle2(initial, onChange) {
    const toggle = document.createElement("div");
    toggle.className = `dnt-toggle ${initial ? "dnt-toggle-on" : "dnt-toggle-off"}`;
    const track = document.createElement("div");
    track.className = "dnt-toggle-track";
    const thumb = document.createElement("div");
    thumb.className = "dnt-toggle-thumb";
    track.appendChild(thumb);
    toggle.appendChild(track);
    toggle.addEventListener("click", async () => {
      const current = toggle.classList.contains("dnt-toggle-on");
      const next = !current;
      await onChange(next);
      setToggleVisual(toggle, next);
    });
    return toggle;
  }
  function setToggleVisual(el, on) {
    el.classList.remove("dnt-toggle-on", "dnt-toggle-off");
    el.classList.add(on ? "dnt-toggle-on" : "dnt-toggle-off");
  }
  var init_debug = __esm({
    "src/ui/sections/debug.ts"() {
      init_i18n();
      init_settings2();
    }
  });

  // src/ui/panel.ts
  function createSettingsPanel() {
    const overlay = document.createElement("div");
    overlay.id = "dnt-settings-overlay";
    overlay.className = "dnt-overlay";
    const dialog = document.createElement("div");
    dialog.className = "dnt-dialog";
    const header = createHeader();
    dialog.appendChild(header);
    const content = document.createElement("div");
    content.className = "dnt-content";
    content.appendChild(renderStatusSection());
    content.appendChild(renderDomainSection());
    content.appendChild(renderRulesSection());
    content.appendChild(renderDebugSection());
    dialog.appendChild(content);
    overlay.appendChild(dialog);
    overlay.addEventListener("click", (e) => {
      if (e.target === overlay) {
        closeSettings();
      }
    });
    return overlay;
  }
  function createHeader() {
    const header = document.createElement("div");
    header.className = "dnt-header";
    const title = document.createElement("h2");
    title.className = "dnt-title";
    title.textContent = t("settings.title");
    header.appendChild(title);
    const controls = document.createElement("div");
    controls.className = "dnt-controls";
    const themeBtn = document.createElement("button");
    themeBtn.className = "dnt-icon-btn";
    themeBtn.title = t(`settings.theme.${getTheme()}`);
    themeBtn.innerHTML = ThemeIcon[getTheme()];
    themeBtn.addEventListener("click", () => {
      toggleTheme();
      themeBtn.innerHTML = ThemeIcon[getTheme()];
      themeBtn.title = t(`settings.theme.${getTheme()}`);
    });
    controls.appendChild(themeBtn);
    const langBtn = document.createElement("button");
    langBtn.className = "dnt-icon-btn";
    langBtn.title = t(`settings.language.${getLanguage()}`);
    langBtn.innerHTML = LanguageIcon[getLanguage()];
    langBtn.addEventListener("click", () => {
      toggleLanguage();
      langBtn.innerHTML = LanguageIcon[getLanguage()];
      closeSettings();
      const { openSettings: openSettings2 } = (init_settings3(), __toCommonJS(settings_exports));
      openSettings2();
    });
    controls.appendChild(langBtn);
    const closeBtn = document.createElement("button");
    closeBtn.className = "dnt-icon-btn";
    closeBtn.title = t("settings.close");
    closeBtn.innerHTML = `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
    <line x1="18" y1="6" x2="6" y2="18"></line>
    <line x1="6" y1="6" x2="18" y2="18"></line>
  </svg>`;
    closeBtn.addEventListener("click", closeSettings);
    controls.appendChild(closeBtn);
    header.appendChild(controls);
    return header;
  }
  var init_panel = __esm({
    "src/ui/panel.ts"() {
      init_settings3();
      init_theme();
      init_i18n();
      init_i18n();
      init_status();
      init_domain();
      init_rules();
      init_debug();
    }
  });

  // src/ui/styles.css
  var styles_default;
  var init_styles = __esm({
    "src/ui/styles.css"() {
      styles_default = '/* \u8BBE\u7F6E\u754C\u9762\u6837\u5F0F - \u7B80\u7EA6\u8BBE\u8BA1 */\r\n\r\n/* CSS\u53D8\u91CF - \u65E5\u95F4\u4E3B\u9898 */\r\n:root[data-dnt-theme="light"] {\r\n  --dnt-bg-overlay: rgba(0, 0, 0, 0.5);\r\n  --dnt-bg-dialog: #ffffff;\r\n  --dnt-bg-section: #f8f9fa;\r\n  --dnt-bg-input: #ffffff;\r\n  --dnt-bg-hover: #f0f0f0;\r\n\r\n  --dnt-text-primary: #2c3e50;\r\n  --dnt-text-secondary: #6c757d;\r\n  --dnt-text-muted: #999999;\r\n\r\n  --dnt-border: #e1e4e8;\r\n  --dnt-border-focus: #67c23a;\r\n\r\n  --dnt-primary: #67c23a;\r\n  --dnt-primary-hover: #85ce61;\r\n  --dnt-danger: #f56c6c;\r\n  --dnt-danger-hover: #f78989;\r\n\r\n  --dnt-badge-auto: #409eff;\r\n  --dnt-badge-whitelist: #67c23a;\r\n  --dnt-badge-blacklist: #f56c6c;\r\n  --dnt-badge-disabled: #909399;\r\n\r\n  --dnt-toggle-on: #67c23a;\r\n  --dnt-toggle-off: #dcdfe6;\r\n  --dnt-toggle-thumb: #ffffff;\r\n\r\n  --dnt-shadow: 0 2px 12px rgba(0, 0, 0, 0.15);\r\n}\r\n\r\n/* CSS\u53D8\u91CF - \u591C\u95F4\u4E3B\u9898 */\r\n:root[data-dnt-theme="dark"] {\r\n  --dnt-bg-overlay: rgba(0, 0, 0, 0.7);\r\n  --dnt-bg-dialog: #1e1e1e;\r\n  --dnt-bg-section: #2a2a2a;\r\n  --dnt-bg-input: #363636;\r\n  --dnt-bg-hover: #3a3a3a;\r\n\r\n  --dnt-text-primary: #e4e4e4;\r\n  --dnt-text-secondary: #b0b0b0;\r\n  --dnt-text-muted: #888888;\r\n\r\n  --dnt-border: #404040;\r\n  --dnt-border-focus: #67c23a;\r\n\r\n  --dnt-primary: #67c23a;\r\n  --dnt-primary-hover: #85ce61;\r\n  --dnt-danger: #f56c6c;\r\n  --dnt-danger-hover: #f78989;\r\n\r\n  --dnt-badge-auto: #409eff;\r\n  --dnt-badge-whitelist: #67c23a;\r\n  --dnt-badge-blacklist: #f56c6c;\r\n  --dnt-badge-disabled: #909399;\r\n\r\n  --dnt-toggle-on: #67c23a;\r\n  --dnt-toggle-off: #4a4a4a;\r\n  --dnt-toggle-thumb: #ffffff;\r\n\r\n  --dnt-shadow: 0 2px 12px rgba(0, 0, 0, 0.5);\r\n}\r\n\r\n/* \u91CD\u7F6E\u6837\u5F0F */\r\n.dnt-overlay *,\r\n.dnt-overlay *::before,\r\n.dnt-overlay *::after {\r\n  box-sizing: border-box;\r\n  margin: 0;\r\n  padding: 0;\r\n}\r\n\r\n/* \u906E\u7F69\u5C42 */\r\n.dnt-overlay {\r\n  position: fixed;\r\n  top: 0;\r\n  left: 0;\r\n  right: 0;\r\n  bottom: 0;\r\n  background: var(--dnt-bg-overlay);\r\n  display: flex;\r\n  align-items: center;\r\n  justify-content: center;\r\n  z-index: 999999;\r\n  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;\r\n  font-size: 14px;\r\n  line-height: 1.6;\r\n}\r\n\r\n/* \u5BF9\u8BDD\u6846 */\r\n.dnt-dialog {\r\n  background: var(--dnt-bg-dialog);\r\n  border-radius: 8px;\r\n  box-shadow: var(--dnt-shadow);\r\n  width: 90%;\r\n  max-width: 680px;\r\n  max-height: 85vh;\r\n  display: flex;\r\n  flex-direction: column;\r\n  overflow: hidden;\r\n}\r\n\r\n/* \u5934\u90E8 */\r\n.dnt-header {\r\n  display: flex;\r\n  align-items: center;\r\n  justify-content: space-between;\r\n  padding: 20px 24px;\r\n  border-bottom: 1px solid var(--dnt-border);\r\n}\r\n\r\n.dnt-title {\r\n  font-size: 20px;\r\n  font-weight: 600;\r\n  color: var(--dnt-text-primary);\r\n}\r\n\r\n.dnt-controls {\r\n  display: flex;\r\n  gap: 8px;\r\n}\r\n\r\n.dnt-icon-btn {\r\n  width: 36px;\r\n  height: 36px;\r\n  border: none;\r\n  background: transparent;\r\n  color: var(--dnt-text-secondary);\r\n  cursor: pointer;\r\n  border-radius: 4px;\r\n  display: flex;\r\n  align-items: center;\r\n  justify-content: center;\r\n  transition: all 0.2s;\r\n}\r\n\r\n.dnt-icon-btn:hover {\r\n  background: var(--dnt-bg-hover);\r\n  color: var(--dnt-text-primary);\r\n}\r\n\r\n/* \u5185\u5BB9\u533A */\r\n.dnt-content {\r\n  flex: 1;\r\n  overflow-y: auto;\r\n  padding: 20px 24px;\r\n}\r\n\r\n.dnt-content::-webkit-scrollbar {\r\n  width: 8px;\r\n}\r\n\r\n.dnt-content::-webkit-scrollbar-track {\r\n  background: transparent;\r\n}\r\n\r\n.dnt-content::-webkit-scrollbar-thumb {\r\n  background: var(--dnt-border);\r\n  border-radius: 4px;\r\n}\r\n\r\n.dnt-content::-webkit-scrollbar-thumb:hover {\r\n  background: var(--dnt-text-muted);\r\n}\r\n\r\n/* \u533A\u5757 */\r\n.dnt-section {\r\n  margin-bottom: 24px;\r\n}\r\n\r\n.dnt-section:last-child {\r\n  margin-bottom: 0;\r\n}\r\n\r\n.dnt-section-title {\r\n  font-size: 16px;\r\n  font-weight: 600;\r\n  color: var(--dnt-text-primary);\r\n  margin-bottom: 12px;\r\n  padding-bottom: 8px;\r\n  border-bottom: 2px solid var(--dnt-primary);\r\n}\r\n\r\n/* \u72B6\u6001\u533A\u57DF */\r\n.dnt-status-content {\r\n  background: var(--dnt-bg-section);\r\n  border-radius: 6px;\r\n  padding: 16px;\r\n}\r\n\r\n.dnt-status-row {\r\n  display: flex;\r\n  align-items: center;\r\n  gap: 12px;\r\n  margin-bottom: 8px;\r\n}\r\n\r\n.dnt-status-row:last-child {\r\n  margin-bottom: 0;\r\n}\r\n\r\n.dnt-status-label {\r\n  color: var(--dnt-text-primary);\r\n  font-weight: 500;\r\n}\r\n\r\n.dnt-status-value {\r\n  color: var(--dnt-text-secondary);\r\n}\r\n\r\n.dnt-domain-text {\r\n  font-family: "Consolas", "Monaco", monospace;\r\n  font-size: 13px;\r\n}\r\n\r\n/* \u5FBD\u7AE0 */\r\n.dnt-badge {\r\n  display: inline-block;\r\n  padding: 2px 10px;\r\n  border-radius: 12px;\r\n  font-size: 12px;\r\n  font-weight: 500;\r\n  color: #ffffff;\r\n}\r\n\r\n.dnt-badge-auto {\r\n  background: var(--dnt-badge-auto);\r\n}\r\n\r\n.dnt-badge-whitelist {\r\n  background: var(--dnt-badge-whitelist);\r\n}\r\n\r\n.dnt-badge-blacklist {\r\n  background: var(--dnt-badge-blacklist);\r\n}\r\n\r\n.dnt-badge-disabled {\r\n  background: var(--dnt-badge-disabled);\r\n}\r\n\r\n/* \u57DF\u540D\u7BA1\u7406\u533A\u57DF */\r\n.dnt-domain-content {\r\n  display: flex;\r\n  flex-direction: column;\r\n  gap: 20px;\r\n}\r\n\r\n.dnt-list-block {\r\n  background: var(--dnt-bg-section);\r\n  border-radius: 6px;\r\n  padding: 16px;\r\n}\r\n\r\n.dnt-list-subtitle {\r\n  font-size: 14px;\r\n  font-weight: 600;\r\n  color: var(--dnt-text-primary);\r\n  margin-bottom: 12px;\r\n}\r\n\r\n.dnt-domain-list {\r\n  margin-bottom: 12px;\r\n  min-height: 40px;\r\n  max-height: 180px;\r\n  overflow-y: auto;\r\n}\r\n\r\n.dnt-domain-list::-webkit-scrollbar {\r\n  width: 6px;\r\n}\r\n\r\n.dnt-domain-list::-webkit-scrollbar-thumb {\r\n  background: var(--dnt-border);\r\n  border-radius: 3px;\r\n}\r\n\r\n.dnt-domain-item {\r\n  display: flex;\r\n  align-items: center;\r\n  justify-content: space-between;\r\n  padding: 8px 12px;\r\n  background: var(--dnt-bg-input);\r\n  border: 1px solid var(--dnt-border);\r\n  border-radius: 4px;\r\n  margin-bottom: 8px;\r\n}\r\n\r\n.dnt-domain-item:last-child {\r\n  margin-bottom: 0;\r\n}\r\n\r\n.dnt-empty-text {\r\n  color: var(--dnt-text-muted);\r\n  font-size: 13px;\r\n  text-align: center;\r\n  padding: 20px;\r\n}\r\n\r\n/* \u8F93\u5165\u6846 */\r\n.dnt-input-row {\r\n  display: flex;\r\n  gap: 8px;\r\n  margin-bottom: 8px;\r\n}\r\n\r\n.dnt-input {\r\n  flex: 1;\r\n  padding: 8px 12px;\r\n  border: 1px solid var(--dnt-border);\r\n  border-radius: 4px;\r\n  background: var(--dnt-bg-input);\r\n  color: var(--dnt-text-primary);\r\n  font-size: 14px;\r\n  outline: none;\r\n  transition: border-color 0.2s;\r\n}\r\n\r\n.dnt-input:focus {\r\n  border-color: var(--dnt-border-focus);\r\n}\r\n\r\n.dnt-input::placeholder {\r\n  color: var(--dnt-text-muted);\r\n}\r\n\r\n/* \u6309\u94AE */\r\n.dnt-btn {\r\n  padding: 8px 16px;\r\n  border: none;\r\n  border-radius: 4px;\r\n  font-size: 14px;\r\n  font-weight: 500;\r\n  cursor: pointer;\r\n  transition: all 0.2s;\r\n  outline: none;\r\n}\r\n\r\n.dnt-btn-primary {\r\n  background: var(--dnt-primary);\r\n  color: #ffffff;\r\n}\r\n\r\n.dnt-btn-primary:hover {\r\n  background: var(--dnt-primary-hover);\r\n}\r\n\r\n.dnt-btn-secondary {\r\n  background: var(--dnt-bg-input);\r\n  color: var(--dnt-text-primary);\r\n  border: 1px solid var(--dnt-border);\r\n  width: 100%;\r\n}\r\n\r\n.dnt-btn-secondary:hover {\r\n  background: var(--dnt-bg-hover);\r\n}\r\n\r\n.dnt-btn-danger {\r\n  background: var(--dnt-danger);\r\n  color: #ffffff;\r\n}\r\n\r\n.dnt-btn-danger:hover {\r\n  background: var(--dnt-danger-hover);\r\n}\r\n\r\n.dnt-btn-sm {\r\n  padding: 4px 12px;\r\n  font-size: 13px;\r\n}\r\n\r\n/* \u89C4\u5219\u533A\u57DF */\r\n.dnt-rules-content {\r\n  display: flex;\r\n  flex-direction: column;\r\n  gap: 16px;\r\n}\r\n\r\n.dnt-rule-group {\r\n  background: var(--dnt-bg-section);\r\n  border-radius: 6px;\r\n  padding: 16px;\r\n}\r\n\r\n.dnt-rule-group-title {\r\n  font-size: 14px;\r\n  font-weight: 600;\r\n  color: var(--dnt-text-primary);\r\n  margin-bottom: 12px;\r\n}\r\n\r\n.dnt-rule-item {\r\n  display: flex;\r\n  align-items: center;\r\n  justify-content: space-between;\r\n  padding: 10px 0;\r\n  border-bottom: 1px solid var(--dnt-border);\r\n}\r\n\r\n.dnt-rule-item:last-child {\r\n  border-bottom: none;\r\n  padding-bottom: 0;\r\n}\r\n\r\n.dnt-rule-label {\r\n  color: var(--dnt-text-primary);\r\n  font-size: 14px;\r\n  flex: 1;\r\n  cursor: default;\r\n}\r\n\r\n/* \u5F00\u5173 */\r\n.dnt-toggle {\r\n  width: 44px;\r\n  height: 24px;\r\n  border-radius: 12px;\r\n  cursor: pointer;\r\n  position: relative;\r\n  transition: background-color 0.3s;\r\n}\r\n\r\n.dnt-toggle-on {\r\n  background: var(--dnt-toggle-on);\r\n}\r\n\r\n.dnt-toggle-off {\r\n  background: var(--dnt-toggle-off);\r\n}\r\n\r\n.dnt-toggle-track {\r\n  width: 100%;\r\n  height: 100%;\r\n  position: relative;\r\n}\r\n\r\n.dnt-toggle-thumb {\r\n  width: 20px;\r\n  height: 20px;\r\n  border-radius: 50%;\r\n  background: var(--dnt-toggle-thumb);\r\n  position: absolute;\r\n  top: 2px;\r\n  transition: left 0.3s;\r\n  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);\r\n}\r\n\r\n.dnt-toggle-on .dnt-toggle-thumb {\r\n  left: 22px;\r\n}\r\n\r\n.dnt-toggle-off .dnt-toggle-thumb {\r\n  left: 2px;\r\n}\r\n\r\n/* \u54CD\u5E94\u5F0F */\r\n@media (max-width: 768px) {\r\n  .dnt-dialog {\r\n    width: 95%;\r\n    max-height: 90vh;\r\n  }\r\n\r\n  .dnt-header,\r\n  .dnt-content {\r\n    padding: 16px;\r\n  }\r\n\r\n  .dnt-title {\r\n    font-size: 18px;\r\n  }\r\n}\r\n';
    }
  });

  // src/ui/inject-styles.ts
  function injectStyles() {
    if (injected) return;
    const styleEl = document.createElement("style");
    styleEl.id = "dnt-settings-styles";
    styleEl.textContent = styles_default;
    document.head.appendChild(styleEl);
    injected = true;
  }
  var injected;
  var init_inject_styles = __esm({
    "src/ui/inject-styles.ts"() {
      init_styles();
      injected = false;
    }
  });

  // src/ui/settings.ts
  var settings_exports = {};
  __export(settings_exports, {
    closeSettings: () => closeSettings,
    openSettings: () => openSettings
  });
  async function openSettings() {
    injectStyles();
    await initTheme();
    await initI18n();
    const panel = createSettingsPanel();
    document.body.appendChild(panel);
  }
  function closeSettings() {
    const existing = document.getElementById("dnt-settings-overlay");
    if (existing) {
      existing.remove();
    }
  }
  var init_settings3 = __esm({
    "src/ui/settings.ts"() {
      init_panel();
      init_theme();
      init_i18n();
      init_inject_styles();
    }
  });

  // src/main.ts
  init_siteDetector();
  init_gm();
  init_domainLists();

  // src/decision/engine.ts
  init_settings();

  // src/debug/logger.ts
  init_settings2();

  // src/utils/url.ts
  function toAbsoluteUrl(href, base) {
    try {
      if (!href || typeof href !== "string") return null;
      return new URL(href, base);
    } catch {
      return null;
    }
  }
  function extractTopicId(pathname) {
    try {
      const p = (pathname || "").toLowerCase();
      const patterns = [
        /\/t\/[\w%\-\.]+\/(\d+)(?:\/|$)/i,
        // 带 slug
        /\/t\/(\d+)(?:\/|$)/i
        // 仅 id
      ];
      for (const re of patterns) {
        const m = p.match(re);
        if (m && m[1]) {
          const id = parseInt(m[1], 10);
          if (!Number.isNaN(id)) return id;
        }
      }
    } catch {
    }
    return void 0;
  }
  function extractUsername(pathname) {
    try {
      const p = (pathname || "").toLowerCase();
      const m = p.match(/\/u\/([\w%\-\.]+)/i);
      if (m && m[1]) return decodeURIComponent(m[1]);
    } catch {
    }
    return void 0;
  }
  function isLikelyAttachment(pathname) {
    const p = (pathname || "").toLowerCase();
    if (p.includes("/uploads/")) return true;
    if (/\.(png|jpe?g|gif|webp|svg|zip|rar|7z|pdf|mp4|mp3)$/i.test(p)) return true;
    return false;
  }

  // src/debug/logger.ts
  async function shouldLog(category) {
    const enabled = await getDebugEnabled();
    if (!enabled) return false;
    const cats = await getDebugCategories();
    return !!cats[category];
  }
  async function logSiteDetection(result) {
    if (!await shouldLog("site")) return;
    try {
      const head = `${DEBUG_LABEL} \u8BC6\u522B\u4E3A Discourse \u7AD9\u70B9\uFF08\u5F97\u5206\uFF1A${result.score}/${result.threshold}\uFF09`;
      const signals = result.matchedSignals.map((s) => `${s.name}(+${s.weight})`).join(" | ");
      console.log(head + "\u3002");
      if (signals) console.log(signals);
    } catch {
    }
  }
  async function logClickFilter(reason) {
    if (!await shouldLog("click")) return;
    try {
      console.log(`${DEBUG_LABEL} \u70B9\u51FB\u4E8B\u4EF6\u5FFD\u7565\uFF1A${reason}`);
    } catch {
    }
  }
  async function logLinkInfo(ctx) {
    if (!await shouldLog("link")) return;
    try {
      const current = ctx.currentUrl?.href;
      const target = ctx.targetUrl?.href;
      const currentTopicId = extractTopicId(ctx.currentUrl.pathname);
      const targetTopicId = extractTopicId(ctx.targetUrl.pathname);
      const currentUser = extractUsername(ctx.currentUrl.pathname);
      const targetUser = extractUsername(ctx.targetUrl.pathname);
      const parts = [];
      if (currentTopicId != null) parts.push(`currentTopicId=${currentTopicId}`);
      if (targetTopicId != null) parts.push(`targetTopicId=${targetTopicId}`);
      if (currentUser) parts.push(`currentUser=${currentUser}`);
      if (targetUser) parts.push(`targetUser=${targetUser}`);
      const extra = parts.length ? `\uFF08${parts.join("\uFF0C")}\uFF09` : "";
      console.log(`${DEBUG_LABEL} \u94FE\u63A5\uFF1A\u5F53\u524D ${current} \u2192 \u76EE\u6807 ${target}${extra}`);
    } catch {
    }
  }
  async function logRuleDetail(rule, enabled, matched, action, meta) {
    if (!await shouldLog("rules")) return;
    try {
      const cats = await getDebugCategories();
      void cats;
      const enabledText = enabled ? "\u5F00" : "\u5173";
      const hit = matched ? "\u547D\u4E2D" : "\u672A\u547D\u4E2D";
      const act = action ? action === "new_tab" ? "\u65B0\u6807\u7B7E\u9875" : action === "same_tab" ? "\u540C\u9875" : "\u4FDD\u7559\u539F\u751F" : "\u2014";
      console.log(`${DEBUG_LABEL} \u89C4\u5219\uFF1A${rule.name}\uFF08${enabledText}\uFF09 \u2192 ${hit}${action ? `\uFF0C\u52A8\u4F5C\uFF1A${act}` : ""}`);
      if (matched && meta && (meta.note || meta.data)) {
        const note = meta.note ? `\u8BF4\u660E\uFF1A${meta.note}` : "";
        const data = meta.data ? `\u6570\u636E\uFF1A${safeInline(meta.data)}` : "";
        const line = [note, data].filter(Boolean).join("\uFF1B");
        if (line) console.log(`${DEBUG_LABEL} ${line}`);
      }
    } catch {
    }
  }
  async function logFinalDecision(ruleId, action) {
    if (!await shouldLog("final")) return;
    try {
      const act = action === "new_tab" ? "\u65B0\u6807\u7B7E\u9875" : action === "same_tab" ? "\u540C\u9875" : "\u4FDD\u7559\u539F\u751F";
      console.log(`${DEBUG_LABEL} \u6700\u7EC8\u89C4\u5219\u4E0E\u52A8\u4F5C\uFF1A${ruleId} \u2192 ${act}`);
    } catch {
    }
  }
  function safeInline(obj) {
    try {
      const parts = [];
      for (const k of Object.keys(obj)) {
        const v = obj[k];
        if (v == null) continue;
        parts.push(`${k}=${String(v)}`);
      }
      return parts.join(", ");
    } catch {
      return "";
    }
  }

  // src/decision/engine.ts
  async function evaluateRules(rules, ctx) {
    let lastDecision = null;
    for (const rule of rules) {
      let match = null;
      try {
        match = rule.match(ctx);
      } catch {
      }
      const enabled = await getRuleEnabled(rule.id);
      if (!match) {
        await logRuleDetail(rule, enabled, false, void 0, void 0);
        continue;
      }
      const action = enabled ? rule.enabledAction : rule.disabledAction;
      lastDecision = {
        action,
        ruleId: rule.id
      };
      await logRuleDetail(rule, enabled, true, action, match);
    }
    if (!lastDecision) {
      return { action: "keep_native", ruleId: "default" };
    }
    return lastDecision;
  }

  // src/rules/topic.ts
  init_settings();
  var ruleTopicOpenNewTab = {
    id: RULE_TOPIC_OPEN_NEW_TAB,
    name: "\u4ECE\u4EFB\u610F\u9875\u9762\u6253\u5F00\u4E3B\u9898\u5E16\uFF1A\u65B0\u6807\u7B7E\u9875",
    enabledAction: "new_tab",
    disabledAction: "keep_native",
    match: (ctx) => {
      const tId = extractTopicId(ctx.targetUrl.pathname);
      if (tId == null) return null;
      return { matched: true, data: { targetTopicId: tId } };
    }
  };
  var ruleInTopicOpenOther = {
    id: RULE_TOPIC_IN_TOPIC_OPEN_OTHER,
    name: "\u4E3B\u9898\u5E16\u5185\u90E8\u70B9\u51FB\u5176\u4ED6\u94FE\u63A5\uFF1A\u65B0\u6807\u7B7E\u9875",
    enabledAction: "new_tab",
    disabledAction: "keep_native",
    match: (ctx) => {
      const currentTopicId = extractTopicId(ctx.currentUrl.pathname);
      if (currentTopicId == null) return null;
      const targetTopicId = extractTopicId(ctx.targetUrl.pathname);
      if (targetTopicId && targetTopicId === currentTopicId) return null;
      return { matched: true, data: { currentTopicId, targetTopicId: targetTopicId ?? null } };
    }
  };
  var ruleSameTopicKeepNative = {
    id: RULE_TOPIC_SAME_TOPIC_KEEP_NATIVE,
    name: "\u540C\u4E00\u4E3B\u9898\u5185\u697C\u5C42\u8DF3\u8F6C\uFF1A\u4FDD\u7559\u539F\u751F",
    enabledAction: "keep_native",
    disabledAction: "new_tab",
    match: (ctx) => {
      const currentTopicId = extractTopicId(ctx.currentUrl.pathname);
      const targetTopicId = extractTopicId(ctx.targetUrl.pathname);
      if (currentTopicId == null || targetTopicId == null) return null;
      if (currentTopicId !== targetTopicId) return null;
      return {
        matched: true,
        note: "\u540C\u4E00\u4E3B\u9898\u7F16\u53F7\uFF08\u5E38\u89C1\u4E3A\u697C\u5C42\u8DF3\u8F6C\uFF09",
        data: { currentTopicId, targetTopicId }
      };
    }
  };
  var topicRules = [
    // 越靠后优先级越高(规则 3 覆盖规则 1/2)
    ruleTopicOpenNewTab,
    ruleInTopicOpenOther,
    ruleSameTopicKeepNative
  ];

  // src/rules/user.ts
  init_settings();
  var ruleUserOpenNewTab = {
    id: RULE_USER_OPEN_NEW_TAB,
    name: "\u4ECE\u4EFB\u610F\u9875\u9762\u6253\u5F00\u4E2A\u4EBA\u4E3B\u9875\uFF1A\u65B0\u6807\u7B7E\u9875",
    enabledAction: "new_tab",
    disabledAction: "keep_native",
    match: (ctx) => {
      const uname = extractUsername(ctx.targetUrl.pathname);
      if (!uname) return null;
      return { matched: true, data: { targetUser: uname } };
    }
  };
  var ruleInProfileOpenOther = {
    id: RULE_USER_IN_PROFILE_OPEN_OTHER,
    name: "\u4E2A\u4EBA\u4E3B\u9875\u5185\u90E8\u70B9\u51FB\u5176\u4ED6\u94FE\u63A5\uFF1A\u65B0\u6807\u7B7E\u9875",
    enabledAction: "new_tab",
    disabledAction: "keep_native",
    match: (ctx) => {
      const currentUser = extractUsername(ctx.currentUrl.pathname);
      if (!currentUser) return null;
      const targetUser = extractUsername(ctx.targetUrl.pathname);
      if (targetUser && targetUser === currentUser) return null;
      return { matched: true, data: { currentUser, targetUser: targetUser ?? null } };
    }
  };
  var ruleSameProfileKeepNative = {
    id: RULE_USER_SAME_PROFILE_KEEP_NATIVE,
    name: "\u540C\u4E00\u7528\u6237\u4E3B\u9875\uFF1A\u4FDD\u7559\u539F\u751F",
    enabledAction: "keep_native",
    disabledAction: "new_tab",
    match: (ctx) => {
      const currentUser = extractUsername(ctx.currentUrl.pathname);
      const targetUser = extractUsername(ctx.targetUrl.pathname);
      if (!currentUser || !targetUser) return null;
      if (currentUser !== targetUser) return null;
      return { matched: true, data: { currentUser, targetUser } };
    }
  };
  var userRules = [
    // 越靠后优先级越高(规则 3 覆盖规则 1/2)
    ruleUserOpenNewTab,
    ruleInProfileOpenOther,
    ruleSameProfileKeepNative
  ];

  // src/rules/attachment.ts
  init_settings();
  var ruleAttachmentKeepNative = {
    id: RULE_ATTACHMENT_KEEP_NATIVE,
    name: "\u9644\u4EF6\u94FE\u63A5\uFF1A\u4FDD\u7559\u539F\u751F",
    enabledAction: "keep_native",
    disabledAction: "new_tab",
    match: (ctx) => {
      const p = ctx.targetUrl.pathname || "";
      if (!isLikelyAttachment(p)) return null;
      return { matched: true, data: { pathname: p } };
    }
  };
  var attachmentRules = [ruleAttachmentKeepNative];

  // src/rules/popup.ts
  init_settings();

  // src/utils/dom.ts
  function closestAny(el, selectors) {
    if (!el) return null;
    for (const sel of selectors) {
      const hit = el.closest?.(sel);
      if (hit) return hit;
    }
    return null;
  }
  var USER_CARD_SELECTORS = ["#user-card", ".user-card", ".user-card-container"];
  var USER_MENU_SELECTORS = [
    "#user-menu",
    ".user-menu",
    ".user-menu-panel",
    "#user-menu .quick-access-panel",
    ".user-menu .quick-access-panel",
    "#user-menu .menu-panel",
    ".user-menu .menu-panel"
  ];
  var HEADER_SELECTORS = ["header", ".d-header", "#site-header"];
  var USER_MENU_NAV_SELECTORS = [
    ".user-menu .navigation",
    '.user-menu [role="tablist"]',
    ".user-menu .menu-tabs",
    ".user-menu .categories",
    "#user-menu .navigation"
  ];
  function isInUserCard(el) {
    return !!closestAny(el, USER_CARD_SELECTORS);
  }
  function isInUserMenu(el) {
    return !!closestAny(el, USER_MENU_SELECTORS);
  }
  function isInHeader(el) {
    return !!closestAny(el, HEADER_SELECTORS);
  }
  function isInUserMenuNav(el) {
    return !!closestAny(el, USER_MENU_NAV_SELECTORS);
  }
  var SIDEBAR_SELECTORS = [
    "#sidebar",
    ".sidebar",
    ".d-sidebar",
    ".sidebar-container",
    ".discourse-sidebar",
    ".sidebar-section",
    ".sidebar-wrapper"
  ];
  function isInSidebar(el) {
    return !!closestAny(el, SIDEBAR_SELECTORS);
  }
  function isUserCardTrigger(a) {
    if (!a) return false;
    if (a.hasAttribute("data-user-card")) return true;
    const cls = (a.className || "").toString().toLowerCase();
    if (/user-card|avatar|trigger-user-card/.test(cls) && a.pathname?.toLowerCase?.().startsWith("/u/")) {
      return true;
    }
    return false;
  }
  function isUserMenuTrigger(a) {
    if (!a) return false;
    if (!isInHeader(a)) return false;
    if (a.hasAttribute("aria-haspopup") || a.hasAttribute("aria-expanded")) return true;
    const cls = (a.className || "").toString().toLowerCase();
    if (/current-user|header-dropdown-toggle|user-menu|avatar/.test(cls)) return true;
    return false;
  }
  function isActiveTab(a) {
    if (!a) return false;
    if (a.getAttribute("aria-selected") === "true") return true;
    const cls = (a.className || "").toString().toLowerCase();
    return /active|selected/.test(cls);
  }
  var SEARCH_MENU_SELECTORS = [
    "#search-menu",
    ".search-menu",
    ".header .search-menu",
    ".d-header .search-menu"
  ];
  var SEARCH_RESULTS_SELECTORS = [
    "#search-menu .results",
    ".search-menu .results",
    "#search-menu .search-results",
    ".search-menu .search-results",
    ".quick-access-panel .results",
    ".menu-panel .results",
    ".menu-panel .search-results"
  ];
  function isInSearchMenu(el) {
    return !!closestAny(el, SEARCH_MENU_SELECTORS);
  }
  function isInSearchResults(el) {
    if (!isInSearchMenu(el)) return false;
    return !!closestAny(el, SEARCH_RESULTS_SELECTORS);
  }
  function resolveSearchResultLink(a) {
    if (!a) return null;
    if (!isInSearchResults(a)) return null;
    const attrNames = ["data-url", "data-href", "data-link", "data-topic-url"];
    const readAttrs = (el) => {
      if (!el) return null;
      for (const k of attrNames) {
        const v = el.getAttribute?.(k);
        if (v) return v;
      }
      const topicId = el.getAttribute?.("data-topic-id") || el.getAttribute?.("data-topicid");
      if (topicId && /\d+/.test(topicId)) return `/t/${topicId}`;
      return null;
    };
    let node = a;
    for (let i = 0; i < 4 && node; i++) {
      const v = readAttrs(node);
      if (v) return v;
      node = node.parentElement;
    }
    const container = a.closest?.(".search-link, .search-result, .fps-result, li, article, .search-row") || a.parentElement;
    if (container) {
      const inner = container.querySelector?.("a[href]");
      if (inner && inner.getAttribute("href")) return inner.getAttribute("href");
      const v = readAttrs(container);
      if (v) return v;
    }
    return null;
  }

  // src/rules/popup.ts
  var ruleUserCardTriggerKeepNative = {
    id: RULE_POPUP_USER_CARD,
    name: "\u7528\u6237\u5361\u7247\uFF1A\u89E6\u53D1\u94FE\u63A5=\u4FDD\u7559\u539F\u751F",
    enabledAction: "keep_native",
    disabledAction: "keep_native",
    // 关闭时也保留原生
    match: (ctx) => {
      const a = ctx.anchor;
      if (!a) return null;
      if (isUserCardTrigger(a) && !isInUserCard(a)) {
        return { matched: true, note: "\u7528\u6237\u5361\u7247\u89E6\u53D1\u94FE\u63A5" };
      }
      return null;
    }
  };
  var ruleUserCardInsideNewTab = {
    id: RULE_POPUP_USER_CARD,
    name: "\u7528\u6237\u5361\u7247\uFF1A\u5361\u7247\u5185\u94FE\u63A5=\u65B0\u6807\u7B7E",
    enabledAction: "new_tab",
    disabledAction: "keep_native",
    match: (ctx) => {
      const a = ctx.anchor;
      if (!a) return null;
      if (isInUserCard(a)) {
        return { matched: true, note: "\u7528\u6237\u5361\u7247\u5185\u94FE\u63A5" };
      }
      return null;
    }
  };
  var ruleUserMenuTriggerKeepNative = {
    id: RULE_POPUP_USER_MENU,
    name: "\u7528\u6237\u83DC\u5355\uFF1A\u89E6\u53D1\u94FE\u63A5=\u4FDD\u7559\u539F\u751F",
    enabledAction: "keep_native",
    disabledAction: "keep_native",
    match: (ctx) => {
      const a = ctx.anchor;
      if (!a) return null;
      if (isUserMenuTrigger(a) && !isInUserMenu(a)) {
        return { matched: true, note: "\u7528\u6237\u83DC\u5355\u89E6\u53D1\u94FE\u63A5" };
      }
      return null;
    }
  };
  var ruleUserMenuNavKeepOrNew = {
    id: RULE_POPUP_USER_MENU,
    name: "\u7528\u6237\u83DC\u5355\uFF1A\u5BFC\u822A\u533A\u70B9\u51FB\uFF08\u6FC0\u6D3B=\u65B0\u6807\u7B7E/\u672A\u6FC0\u6D3B=\u539F\u751F\uFF09",
    enabledAction: "keep_native",
    // 默认保留原生,下面在激活情况下用后置规则覆盖
    disabledAction: "keep_native",
    match: (ctx) => {
      const a = ctx.anchor;
      if (!a) return null;
      if (!isInUserMenu(a)) return null;
      if (!isInUserMenuNav(a)) return null;
      if (!isActiveTab(a)) {
        return { matched: true, note: "\u7528\u6237\u83DC\u5355\u5BFC\u822A\uFF08\u672A\u6FC0\u6D3B\uFF09" };
      }
      return null;
    }
  };
  var ruleUserMenuNavActiveNewTab = {
    id: RULE_POPUP_USER_MENU,
    name: "\u7528\u6237\u83DC\u5355\uFF1A\u5BFC\u822A\u533A\u6FC0\u6D3B\u9879=\u65B0\u6807\u7B7E",
    enabledAction: "new_tab",
    disabledAction: "keep_native",
    match: (ctx) => {
      const a = ctx.anchor;
      if (!a) return null;
      if (!isInUserMenu(a)) return null;
      if (!isInUserMenuNav(a)) return null;
      if (isActiveTab(a)) {
        return { matched: true, note: "\u7528\u6237\u83DC\u5355\u5BFC\u822A\uFF08\u6FC0\u6D3B\uFF09" };
      }
      return null;
    }
  };
  var ruleUserMenuContentNewTab = {
    id: RULE_POPUP_USER_MENU,
    name: "\u7528\u6237\u83DC\u5355\uFF1A\u5185\u5BB9\u533A\u94FE\u63A5=\u65B0\u6807\u7B7E",
    enabledAction: "new_tab",
    disabledAction: "keep_native",
    match: (ctx) => {
      const a = ctx.anchor;
      if (!a) return null;
      if (!isInUserMenu(a)) return null;
      if (isInUserMenuNav(a)) return null;
      return { matched: true, note: "\u7528\u6237\u83DC\u5355\u5185\u5BB9\u533A\u94FE\u63A5" };
    }
  };
  var popupRules = [
    // 用户卡片(触发→保留;卡片内→新标签)
    ruleUserCardTriggerKeepNative,
    ruleUserCardInsideNewTab,
    // 用户菜单(触发→保留;导航未激活→保留;导航激活→新标签;内容区→新标签)
    ruleUserMenuTriggerKeepNative,
    ruleUserMenuNavKeepOrNew,
    ruleUserMenuNavActiveNewTab,
    ruleUserMenuContentNewTab,
    // 搜索弹窗(结果列表与底部“更多”按钮 → 新标签;其余保持原生)
    // 说明:搜索历史、建议项等(不在结果区内)一律不改写。
    {
      id: RULE_POPUP_SEARCH_MENU,
      name: "\u641C\u7D22\u5F39\u7A97\uFF1A\u7ED3\u679C\u4E0E\u201C\u66F4\u591A\u201D=\u65B0\u6807\u7B7E",
      enabledAction: "new_tab",
      disabledAction: "keep_native",
      match: (ctx) => {
        const a = ctx.anchor;
        if (!a) return null;
        if (!isInSearchResults(a)) return null;
        const p = ctx.targetUrl?.pathname || "";
        if (/\/t\//.test(p) || p.startsWith("/search")) {
          return { matched: true, note: "\u641C\u7D22\u5F39\u7A97\u7ED3\u679C\u6216\u66F4\u591A" };
        }
        return null;
      }
    }
  ];

  // src/rules/sidebar.ts
  init_settings();
  var ruleSidebarNonTopicKeepNative = {
    id: RULE_SIDEBAR_NON_TOPIC_KEEP_NATIVE,
    name: "\u975E\u4E3B\u9898\u9875-\u4FA7\u8FB9\u680F\u94FE\u63A5\uFF1A\u4FDD\u7559\u539F\u751F",
    enabledAction: "keep_native",
    disabledAction: "new_tab",
    match: (ctx) => {
      const a = ctx.anchor;
      if (!a) return null;
      if (!isInSidebar(a)) return null;
      const currentTopicId = extractTopicId(ctx.currentUrl.pathname);
      if (currentTopicId != null) return null;
      return { matched: true, note: "\u975E\u4E3B\u9898\u9875\u7684\u4FA7\u8FB9\u680F\u94FE\u63A5" };
    }
  };
  var ruleSidebarInTopicNewTab = {
    id: RULE_SIDEBAR_IN_TOPIC_NEW_TAB,
    name: "\u4E3B\u9898\u9875-\u4FA7\u8FB9\u680F\u94FE\u63A5\uFF1A\u65B0\u6807\u7B7E\u9875",
    enabledAction: "new_tab",
    disabledAction: "keep_native",
    match: (ctx) => {
      const a = ctx.anchor;
      if (!a) return null;
      if (!isInSidebar(a)) return null;
      const currentTopicId = extractTopicId(ctx.currentUrl.pathname);
      if (currentTopicId == null) return null;
      return { matched: true, note: "\u4E3B\u9898\u9875\u5185\u7684\u4FA7\u8FB9\u680F\u94FE\u63A5", data: { currentTopicId } };
    }
  };
  var sidebarRules = [
    // 顺序与《需求文档》一致:非主题页→保留原生;主题页→新标签
    ruleSidebarNonTopicKeepNative,
    ruleSidebarInTopicNewTab
  ];

  // src/rules/index.ts
  function getAllRules() {
    return [
      ...topicRules,
      ...userRules,
      ...attachmentRules,
      ...popupRules,
      ...sidebarRules
    ];
  }

  // src/listeners/click.ts
  function isPlainLeftClick(ev) {
    return ev.button === 0 && !ev.ctrlKey && !ev.metaKey && !ev.shiftKey && !ev.altKey;
  }
  function findAnchor(el) {
    let node = el;
    while (node) {
      const elem = node;
      if (elem && elem.tagName === "A") return elem;
      node = elem && elem.parentElement ? elem.parentElement : null;
    }
    return null;
  }
  function attachClickListener(label = "[discourse-new-tab]") {
    const handler = async (ev) => {
      try {
        if (!isPlainLeftClick(ev)) {
          await logClickFilter("\u975E\u5DE6\u952E\u6216\u7EC4\u5408\u952E\u70B9\u51FB");
          return;
        }
        const a = findAnchor(ev.target);
        if (!a) {
          await logClickFilter("\u672A\u627E\u5230 <a> \u5143\u7D20");
          return;
        }
        let rawHref = a.getAttribute("href") || a.href || "";
        if (!rawHref || rawHref === "#") {
          if (isInSearchResults(a)) {
            const fallback = resolveSearchResultLink(a);
            if (fallback) rawHref = fallback;
          }
        }
        if (!rawHref) {
          await logClickFilter("\u94FE\u63A5\u65E0 href");
          return;
        }
        if (a.hasAttribute("download")) {
          await logClickFilter("\u4E0B\u8F7D\u94FE\u63A5");
          return;
        }
        if (a.getAttribute("data-dnt-ignore") === "1") {
          await logClickFilter("\u663E\u5F0F\u5FFD\u7565\u6807\u8BB0 data-dnt-ignore=1");
          return;
        }
        const targetUrl = toAbsoluteUrl(rawHref, location.href);
        if (!targetUrl) {
          await logClickFilter("\u76EE\u6807 URL \u89E3\u6790\u5931\u8D25");
          return;
        }
        const ctx = { anchor: a, targetUrl, currentUrl: new URL(location.href) };
        await logLinkInfo(ctx);
        const decision = await evaluateRules(getAllRules(), ctx);
        if (decision.action === "new_tab") {
          ev.preventDefault();
          ev.stopImmediatePropagation();
          ev.stopPropagation();
          window.open(targetUrl.href, "_blank", "noopener");
          a.setAttribute("data-dnt-handled", "1");
          await logFinalDecision(decision.ruleId, decision.action);
          return;
        } else if (decision.action === "same_tab") {
          await logFinalDecision(decision.ruleId, decision.action);
        } else {
          await logFinalDecision(decision.ruleId, decision.action);
        }
      } catch (err) {
      }
    };
    document.addEventListener("click", handler, true);
  }

  // src/main.ts
  (async () => {
    const label = "[discourse-new-tab]";
    const isTop = (() => {
      try {
        return window.top === window;
      } catch {
        return true;
      }
    })();
    if (!isTop) return;
    const result = detectDiscourse();
    await logSiteDetection(result);
    const host = getCurrentHostname();
    const enable = await getEnablement(result.isDiscourse, host);
    if (enable.enabled) {
      attachClickListener(label);
    }
    gmRegisterMenu("\u8BBE\u7F6E", async () => {
      const { openSettings: openSettings2 } = await Promise.resolve().then(() => (init_settings3(), settings_exports));
      await openSettings2();
    });
  })();
})();