Greasy Fork

Greasy Fork is available in English.

知乎历史记录

给知乎添加历史记录

当前为 2025-05-13 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name       知乎历史记录
// @namespace  https://maxchang.me
// @version    1.1.0
// @author     Max Chang
// @license    MIT
// @icon       https://static.zhihu.com/heifetz/favicon.ico
// @match      https://www.zhihu.com/
// @match      https://www.zhihu.com/follow*
// @grant      GM_addStyle
// @grant      GM_getValue
// @grant      GM_info
// @grant      GM_registerMenuCommand
// @grant      GM_setValue
// @grant      unsafeWindow
// @description 给知乎添加历史记录
// ==/UserScript==

(o=>{if(typeof GM_addStyle=="function"){GM_addStyle(o);return}const r=document.createElement("style");r.textContent=o,document.head.append(r)})(' :root{--primary-color: rgb(5, 109, 232);--primary-light: rgba(5, 109, 232, .5);--primary-bg: rgba(33, 150, 243, .2);--text-color: #333;--text-secondary: #666;--shadow-color: hsla(0, 0%, 7%, .1);--backdrop-color: hsla(0, 0%, 7%, .65);--border-radius-sm: 2px;--border-radius: 4px;--spacing-sm: 4px;--spacing-md: 8px;--spacing-lg: 16px;--spacing-xl: 25px;--font-size-sm: 13px;--font-size-md: 14px}._srOnly_1fok4_19{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}._historyCard_1fok4_31{background:#fff;border-radius:var(--border-radius-sm);box-shadow:0 1px 3px var(--shadow-color);margin-bottom:10px;padding:5px 0}._historyButton_1fok4_39{margin:0 18px;display:flex;justify-content:center;align-items:center;border:1px solid var(--primary-light);background:transparent;color:var(--primary-color);border-radius:var(--border-radius);height:40px;font-size:var(--font-size-md);cursor:pointer;width:calc(100% - 36px)}._dialog_1fok4_55,._dialog_1fok4_55::backdrop{transition:display .25s allow-discrete,overlay .25s allow-discrete,opacity .25s;opacity:0}._dialog_1fok4_55[open],._dialog_1fok4_55[open]::backdrop{opacity:1;scale:1}@starting-style{._dialog_1fok4_55[open],._dialog_1fok4_55[open]::backdrop{opacity:0}}._dialog_1fok4_55{padding:0;border:0;border-radius:var(--border-radius);box-shadow:0 4px 12px var(--shadow-color);background-color:#fff;max-width:800px;width:80%;overflow:hidden;-webkit-user-select:text!important;user-select:text!important}._dialog_1fok4_55::backdrop{background-color:var(--backdrop-color)}._dialogContent_1fok4_90{padding:var(--spacing-lg) var(--spacing-xl);outline:none}._dialogHeader_1fok4_95{display:flex;justify-content:space-between;align-items:center;margin-bottom:var(--spacing-sm);border-bottom:1px solid #eee;padding-bottom:var(--spacing-md)}._dialogTitle_1fok4_104{margin:0;font-size:18px;color:var(--text-color)}._searchContainer_1fok4_110{position:relative;flex:1;margin:0 var(--spacing-lg);display:flex;align-items:center}._searchInput_1fok4_118{flex:1;width:100%;padding:var(--spacing-sm) var(--spacing-md);padding-right:calc(var(--spacing-md) * 3);border:1px solid #eee;border-radius:var(--border-radius);font-size:var(--font-size-md);color:var(--text-color);background-color:#f9f9f9;transition:border-color .2s}._searchInput_1fok4_118:focus{outline:none;border-color:var(--primary-light)}._clearSearchButton_1fok4_136{position:absolute;right:var(--spacing-sm);background:none;border:none;cursor:pointer;color:var(--text-secondary);font-size:12px;padding:var(--spacing-sm);display:flex;align-items:center;justify-content:center;border-radius:50%;transition:background-color .2s}._clearSearchButton_1fok4_136:hover{background-color:#eee}._dialogBody_1fok4_156{max-height:70vh;overflow-y:auto}._searchInfo_1fok4_161{padding:var(--spacing-sm) var(--spacing-lg);font-size:var(--font-size-sm);color:var(--text-secondary);background-color:#f5f5f5;margin-bottom:var(--spacing-md);border-radius:var(--border-radius-sm)}._historyList_1fok4_170{list-style:none;margin:0;display:flex;flex-direction:column;padding:0 1.5em}._emptyState_1fok4_178{text-align:center;padding:var(--spacing-xl);color:var(--text-secondary);font-style:italic}._item_5lnzt_1{padding:var(--spacing-md) 0;border-bottom:1px solid #f0f0f0;display:flex;flex-direction:column}._item_5lnzt_1:last-child{border-bottom:none}._link_5lnzt_12{flex:1;color:var(--text-color);text-decoration:none;min-width:0}._link_5lnzt_12:hover,._link_5lnzt_12:focus{color:var(--primary-color)}._header_5lnzt_24{display:flex}._visitTime_5lnzt_28{color:var(--text-secondary);font-size:var(--font-size-sm);white-space:nowrap;flex-shrink:0}._title_5lnzt_35{flex:1;font-weight:500;transition:color .2s;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}._content_5lnzt_45{color:var(--text-secondary);font-size:var(--font-size-sm);margin:var(--spacing-sm) 0 0 0;overflow:hidden;text-overflow:ellipsis;word-break:break-word;display:-webkit-box;line-clamp:2;-webkit-box-orient:vertical;-webkit-line-clamp:2}._answer_5lnzt_59:before,._article_5lnzt_60:before,._pin_5lnzt_61:before{background-color:var(--primary-bg);font-weight:700;font-size:var(--font-size-sm);padding:1px var(--spacing-sm) 0;border-radius:var(--border-radius-sm);margin-right:var(--spacing-sm);display:inline-block}._answer_5lnzt_59:before{content:"\u95EE\u9898";color:#2196f3}._article_5lnzt_60:before{content:"\u6587\u7AE0";color:#004b87}._pin_5lnzt_61:before{content:"\u60F3\u6CD5";color:#60a912}._highlight_5lnzt_86{background-color:#ffe60066;border-radius:2px;padding:0 2px;margin:0 -2px;font-weight:500} ');

(function (require$$1, ReactDOM) {
  'use strict';

  var jsxRuntime = { exports: {} };
  var reactJsxRuntime_production_min = {};
  /*
  object-assign
  (c) Sindre Sorhus
  @license MIT
  */
  var objectAssign;
  var hasRequiredObjectAssign;
  function requireObjectAssign() {
    if (hasRequiredObjectAssign) return objectAssign;
    hasRequiredObjectAssign = 1;
    var getOwnPropertySymbols = Object.getOwnPropertySymbols;
    var hasOwnProperty = Object.prototype.hasOwnProperty;
    var propIsEnumerable = Object.prototype.propertyIsEnumerable;
    function toObject(val) {
      if (val === null || val === void 0) {
        throw new TypeError("Object.assign cannot be called with null or undefined");
      }
      return Object(val);
    }
    function shouldUseNative() {
      try {
        if (!Object.assign) {
          return false;
        }
        var test1 = new String("abc");
        test1[5] = "de";
        if (Object.getOwnPropertyNames(test1)[0] === "5") {
          return false;
        }
        var test2 = {};
        for (var i = 0; i < 10; i++) {
          test2["_" + String.fromCharCode(i)] = i;
        }
        var order2 = Object.getOwnPropertyNames(test2).map(function(n) {
          return test2[n];
        });
        if (order2.join("") !== "0123456789") {
          return false;
        }
        var test3 = {};
        "abcdefghijklmnopqrst".split("").forEach(function(letter) {
          test3[letter] = letter;
        });
        if (Object.keys(Object.assign({}, test3)).join("") !== "abcdefghijklmnopqrst") {
          return false;
        }
        return true;
      } catch (err) {
        return false;
      }
    }
    objectAssign = shouldUseNative() ? Object.assign : function(target, source) {
      var from;
      var to = toObject(target);
      var symbols;
      for (var s = 1; s < arguments.length; s++) {
        from = Object(arguments[s]);
        for (var key in from) {
          if (hasOwnProperty.call(from, key)) {
            to[key] = from[key];
          }
        }
        if (getOwnPropertySymbols) {
          symbols = getOwnPropertySymbols(from);
          for (var i = 0; i < symbols.length; i++) {
            if (propIsEnumerable.call(from, symbols[i])) {
              to[symbols[i]] = from[symbols[i]];
            }
          }
        }
      }
      return to;
    };
    return objectAssign;
  }
  /** @license React v17.0.2
   * react-jsx-runtime.production.min.js
   *
   * Copyright (c) Facebook, Inc. and its affiliates.
   *
   * This source code is licensed under the MIT license found in the
   * LICENSE file in the root directory of this source tree.
   */
  var hasRequiredReactJsxRuntime_production_min;
  function requireReactJsxRuntime_production_min() {
    if (hasRequiredReactJsxRuntime_production_min) return reactJsxRuntime_production_min;
    hasRequiredReactJsxRuntime_production_min = 1;
    requireObjectAssign();
    var f = require$$1, g = 60103;
    reactJsxRuntime_production_min.Fragment = 60107;
    if ("function" === typeof Symbol && Symbol.for) {
      var h = Symbol.for;
      g = h("react.element");
      reactJsxRuntime_production_min.Fragment = h("react.fragment");
    }
    var m = f.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner, n = Object.prototype.hasOwnProperty, p = { key: true, ref: true, __self: true, __source: true };
    function q(c, a, k) {
      var b, d = {}, e = null, l = null;
      void 0 !== k && (e = "" + k);
      void 0 !== a.key && (e = "" + a.key);
      void 0 !== a.ref && (l = a.ref);
      for (b in a) n.call(a, b) && !p.hasOwnProperty(b) && (d[b] = a[b]);
      if (c && c.defaultProps) for (b in a = c.defaultProps, a) void 0 === d[b] && (d[b] = a[b]);
      return { $$typeof: g, type: c, key: e, ref: l, props: d, _owner: m.current };
    }
    reactJsxRuntime_production_min.jsx = q;
    reactJsxRuntime_production_min.jsxs = q;
    return reactJsxRuntime_production_min;
  }
  var hasRequiredJsxRuntime;
  function requireJsxRuntime() {
    if (hasRequiredJsxRuntime) return jsxRuntime.exports;
    hasRequiredJsxRuntime = 1;
    {
      jsxRuntime.exports = requireReactJsxRuntime_production_min();
    }
    return jsxRuntime.exports;
  }
  var jsxRuntimeExports = requireJsxRuntime();
  var _GM_getValue = /* @__PURE__ */ (() => typeof GM_getValue != "undefined" ? GM_getValue : void 0)();
  var _GM_info = /* @__PURE__ */ (() => typeof GM_info != "undefined" ? GM_info : void 0)();
  var _GM_registerMenuCommand = /* @__PURE__ */ (() => typeof GM_registerMenuCommand != "undefined" ? GM_registerMenuCommand : void 0)();
  var _GM_setValue = /* @__PURE__ */ (() => typeof GM_setValue != "undefined" ? GM_setValue : void 0)();
  const srOnly = "_srOnly_1fok4_19";
  const historyCard = "_historyCard_1fok4_31";
  const historyButton = "_historyButton_1fok4_39";
  const dialog = "_dialog_1fok4_55";
  const dialogContent = "_dialogContent_1fok4_90";
  const dialogHeader = "_dialogHeader_1fok4_95";
  const dialogTitle = "_dialogTitle_1fok4_104";
  const searchContainer = "_searchContainer_1fok4_110";
  const searchInput = "_searchInput_1fok4_118";
  const clearSearchButton = "_clearSearchButton_1fok4_136";
  const dialogBody = "_dialogBody_1fok4_156";
  const searchInfo = "_searchInfo_1fok4_161";
  const historyList = "_historyList_1fok4_170";
  const emptyState = "_emptyState_1fok4_178";
  const styles = {
    srOnly,
    historyCard,
    historyButton,
    dialog,
    dialogContent,
    dialogHeader,
    dialogTitle,
    searchContainer,
    searchInput,
    clearSearchButton,
    dialogBody,
    searchInfo,
    historyList,
    emptyState
  };
  const item = "_item_5lnzt_1";
  const link = "_link_5lnzt_12";
  const header = "_header_5lnzt_24";
  const visitTime = "_visitTime_5lnzt_28";
  const title = "_title_5lnzt_35";
  const content = "_content_5lnzt_45";
  const answer = "_answer_5lnzt_59";
  const article = "_article_5lnzt_60";
  const pin = "_pin_5lnzt_61";
  const highlight = "_highlight_5lnzt_86";
  const Item = {
    item,
    link,
    header,
    visitTime,
    title,
    content,
    answer,
    article,
    pin,
    highlight
  };
  const formatTime = (date) => {
    const now = /* @__PURE__ */ new Date();
    const diff = Math.floor((now.getTime() - date.getTime()) / 1e3);
    if (diff < 60) return "刚刚";
    if (diff < 3600) return `${Math.floor(diff / 60)} 分钟前`;
    if (diff < 86400) return `${Math.floor(diff / 3600)} 小时前`;
    return date.toLocaleDateString("zh-CN");
  };
  const highlightTextWithPositions = (text, fieldPositions) => {
    if (!fieldPositions || fieldPositions.length === 0) return text;
    const highlightMarkers = new Array(text.length).fill(false);
    for (const { start, end } of fieldPositions) {
      const endIndex = Math.min(end, text.length);
      for (let i = start; i < endIndex; i++) {
        highlightMarkers[i] = true;
      }
    }
    const segments = [];
    let currentSegment = null;
    for (let i = 0; i < text.length; i++) {
      const shouldHighlight = highlightMarkers[i];
      if (!currentSegment || currentSegment.highlight !== shouldHighlight) {
        if (currentSegment) segments.push(currentSegment);
        currentSegment = {
          text: text[i],
          highlight: shouldHighlight
        };
        continue;
      }
      currentSegment.text += text[i];
    }
    if (currentSegment) segments.push(currentSegment);
    return segments.map(
      (segment, index) => segment.highlight ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: Item.highlight, children: segment.text }, index) : /* @__PURE__ */ jsxRuntimeExports.jsx(require$$1.Fragment, { children: segment.text }, index)
    );
  };
  const HistoryItem = require$$1.forwardRef(({ item: item2, searchResult }, ref) => {
    const contentTypeMap = {
      answer: "问题",
      article: "文章",
      pin: "想法"
    };
    const visitTime2 = !item2.visitTime ? null : new Date(item2.visitTime);
    const formattedVisitTime = !visitTime2 ? null : {
      short: formatTime(visitTime2),
      full: visitTime2.toLocaleString("zh-CN")
    };
    const highlightedTitle = require$$1.useMemo(
      () => {
        var _a;
        return highlightTextWithPositions(item2.title, (_a = searchResult == null ? void 0 : searchResult.matches) == null ? void 0 : _a.title);
      },
      [item2.title, searchResult]
    );
    const highlightedContent = require$$1.useMemo(() => {
      var _a;
      if (!item2.content) return null;
      return highlightTextWithPositions(item2.content, (_a = searchResult == null ? void 0 : searchResult.matches) == null ? void 0 : _a.content);
    }, [item2.content, searchResult]);
    const highlightedAuthorName = require$$1.useMemo(() => {
      var _a;
      if (formattedVisitTime || !searchResult) return null;
      return highlightTextWithPositions(item2.authorName, (_a = searchResult.matches) == null ? void 0 : _a.authorName);
    }, [item2.authorName, formattedVisitTime, searchResult]);
    return /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { className: Item.item, children: [
      /* @__PURE__ */ jsxRuntimeExports.jsxs("a", { href: item2.url, className: Item.link, ref, children: [
        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: styles.srOnly, children: contentTypeMap[item2.type] }),
        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: Item.header, children: [
          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `${Item.title} ${Item[item2.type]}`, children: highlightedTitle }),
          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: Item.visitTime, title: formattedVisitTime == null ? void 0 : formattedVisitTime.full, "aria-hidden": true, tabIndex: -1, children: (formattedVisitTime == null ? void 0 : formattedVisitTime.short) ?? (highlightedAuthorName || item2.authorName) })
        ] }),
        // 没有访问时间的是之前的历史记录,没有包含作者的 content,所以需要提示作者
        !formattedVisitTime && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: styles.srOnly, children: [
          "作者:",
          item2.authorName
        ] }),
        formattedVisitTime && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: styles.srOnly, children: [
          "浏览于",
          /* @__PURE__ */ jsxRuntimeExports.jsx("time", { dateTime: formattedVisitTime.short, children: formattedVisitTime.short })
        ] })
      ] }),
      item2.content && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: Item.content, children: highlightedContent })
    ] });
  });
  const SearchBox = ({ searchTerm, onSearchChange, placeholder = "搜索历史记录" }) => {
    const handleChange = (e) => {
      onSearchChange(e.target.value);
    };
    const clearSearch = () => {
      onSearchChange("");
    };
    return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: styles.searchContainer, children: [
      /* @__PURE__ */ jsxRuntimeExports.jsx(
        "input",
        {
          type: "text",
          placeholder,
          className: styles.searchInput,
          value: searchTerm,
          onChange: handleChange,
          "aria-label": placeholder
        }
      ),
      searchTerm && /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: styles.clearSearchButton, onClick: clearSearch, "aria-label": "清除搜索", children: "✕" })
    ] });
  };
  const SearchStatus = ({ totalCount, matchedCount }) => {
    if (totalCount === 0) return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: styles.emptyState, children: "暂无浏览历史" });
    if (matchedCount !== -1) {
      if (matchedCount === 0) return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: styles.emptyState, children: "没有找到匹配的历史记录" });
      return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: styles.searchInfo, children: [
        "找到 ",
        matchedCount,
        " 条匹配结果"
      ] });
    }
    return null;
  };
  function throttle(delay, callback, options) {
    var _ref = options || {}, _ref$noTrailing = _ref.noTrailing, noTrailing = _ref$noTrailing === void 0 ? false : _ref$noTrailing, _ref$noLeading = _ref.noLeading, noLeading = _ref$noLeading === void 0 ? false : _ref$noLeading, _ref$debounceMode = _ref.debounceMode, debounceMode = _ref$debounceMode === void 0 ? void 0 : _ref$debounceMode;
    var timeoutID;
    var cancelled = false;
    var lastExec = 0;
    function clearExistingTimeout() {
      if (timeoutID) {
        clearTimeout(timeoutID);
      }
    }
    function cancel(options2) {
      var _ref2 = options2 || {}, _ref2$upcomingOnly = _ref2.upcomingOnly, upcomingOnly = _ref2$upcomingOnly === void 0 ? false : _ref2$upcomingOnly;
      clearExistingTimeout();
      cancelled = !upcomingOnly;
    }
    function wrapper() {
      for (var _len = arguments.length, arguments_ = new Array(_len), _key = 0; _key < _len; _key++) {
        arguments_[_key] = arguments[_key];
      }
      var self = this;
      var elapsed = Date.now() - lastExec;
      if (cancelled) {
        return;
      }
      function exec() {
        lastExec = Date.now();
        callback.apply(self, arguments_);
      }
      function clear() {
        timeoutID = void 0;
      }
      if (!noLeading && debounceMode && !timeoutID) {
        exec();
      }
      clearExistingTimeout();
      if (debounceMode === void 0 && elapsed > delay) {
        if (noLeading) {
          lastExec = Date.now();
          if (!noTrailing) {
            timeoutID = setTimeout(debounceMode ? clear : exec, delay);
          }
        } else {
          exec();
        }
      } else if (noTrailing !== true) {
        timeoutID = setTimeout(debounceMode ? clear : exec, debounceMode === void 0 ? delay - elapsed : delay);
      }
    }
    wrapper.cancel = cancel;
    return wrapper;
  }
  function debounce(delay, callback, options) {
    var _ref = options || {}, _ref$atBegin = _ref.atBegin, atBegin = _ref$atBegin === void 0 ? false : _ref$atBegin;
    return throttle(delay, callback, {
      debounceMode: atBegin !== false
    });
  }
  function useDebouncedState(initialValue, delay = 300) {
    const [value, setValue] = require$$1.useState(initialValue);
    const [debouncedValue, setDebouncedValue] = require$$1.useState(initialValue);
    require$$1.useEffect(() => {
      const handler = debounce(delay, setDebouncedValue, {
        atBegin: true
      });
      handler(value);
      return () => {
        var _a;
        (_a = handler.cancel) == null ? void 0 : _a.call(handler);
      };
    }, [value, delay]);
    return [value, debouncedValue, setValue];
  }
  const log = (logMethod, tag, ...args) => {
    const colors = {
      log: "#2c3e50",
      error: "#ff4500",
      warn: "#f39c12"
    };
    const fontFamily = "font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;";
    console[logMethod](
      `%c ${_GM_info.script.name} %c ${tag} `,
      `padding: 2px 6px; border-radius: 3px 0 0 3px; color: #fff; background: #056de8; font-weight: bold; ${fontFamily}`,
      `padding: 2px 6px; border-radius: 0 3px 3px 0; color: #fff; background: ${colors[logMethod]}; font-weight: bold; ${fontFamily}`,
      ...args
    );
  };
  const logger = {
    log: (...args) => log("log", "日志", ...args),
    error: (...args) => log("error", "错误", ...args),
    warn: (...args) => log("warn", "警告", ...args)
  };
  const STORAGE_KEY = "ZH_HISTORY";
  const HISTORY_LIMIT_KEY = "HISTORY_LIMIT";
  const DEFAULT_HISTORY_LIMIT = 20;
  const HISTORY_LIMIT = _GM_getValue(HISTORY_LIMIT_KEY) || DEFAULT_HISTORY_LIMIT;
  const setHistoryLimit = (limit) => {
    const numericLimit = Number(limit);
    if (!Number.isNaN(numericLimit) && numericLimit > 0) {
      _GM_setValue(HISTORY_LIMIT_KEY, numericLimit);
      return [true, null];
    }
    return [false, "输入无效,请输入一个正整数"];
  };
  const saveHistory = (item2) => {
    try {
      const raw = _GM_getValue(STORAGE_KEY);
      const historyItems = raw ? JSON.parse(raw) : [];
      const existingIndex = historyItems.findIndex((i) => i.itemId === item2.itemId);
      if (existingIndex !== -1) {
        historyItems.splice(existingIndex, 1);
      }
      historyItems.push(item2);
      if (historyItems.length > HISTORY_LIMIT) {
        historyItems.splice(0, historyItems.length - HISTORY_LIMIT);
      }
      _GM_setValue(STORAGE_KEY, JSON.stringify(historyItems));
    } catch (error) {
      logger.error("保存浏览历史失败:", error);
    }
  };
  const migrateToGMStorage = () => {
    try {
      logger.log("检测到旧的浏览历史数据,正在转换...");
      const raw = localStorage.getItem(STORAGE_KEY);
      if (raw) {
        _GM_setValue(STORAGE_KEY, raw);
        localStorage.removeItem(STORAGE_KEY);
      }
      logger.log("转换浏览历史数据成功");
    } catch (error) {
      logger.error("转换浏览历史失败:", error);
    }
  };
  const getHistory = () => {
    try {
      if (localStorage.getItem(STORAGE_KEY) !== null) {
        migrateToGMStorage();
      }
      const raw = _GM_getValue(STORAGE_KEY);
      return raw ? JSON.parse(raw).reverse() : [];
    } catch (error) {
      logger.error("获取浏览历史失败:", error);
      return [];
    }
  };
  const clearHistory = () => {
    try {
      _GM_setValue(STORAGE_KEY, null);
    } catch (error) {
      logger.error("清空浏览历史失败:", error);
    }
  };
  const saveHistoryFromElement = (item2) => {
    var _a, _b;
    const zop = item2.dataset.zop;
    if (!zop) {
      logger.error("无法读取回答或文章信息", item2.dataset);
      return;
    }
    try {
      const data = JSON.parse(zop);
      if (data.type === "pin") {
        const userLinkEl = (_a = item2.closest(".Feed")) == null ? void 0 : _a.querySelector(".UserLink-link");
        if (userLinkEl) data.authorName = userLinkEl.innerText;
        data.url = `https://www.zhihu.com/pin/${data.itemId}`;
        const contentTextEl = (_b = item2.querySelector(`.RichText`)) == null ? void 0 : _b.innerText;
        if (contentTextEl) data.title = contentTextEl;
      } else {
        const linkEl = item2.querySelector(".ContentItem-title a");
        if (linkEl) data.url = linkEl.href;
        const contentEl = item2.querySelector(".RichText");
        if (contentEl) {
          let contentText = contentEl.innerText;
          if (!contentText.startsWith(data.authorName)) {
            contentText = `${data.authorName}:${contentText}`;
          }
          data.content = contentText.slice(0, 120) + "...";
        }
      }
      data.visitTime = Date.now();
      saveHistory(data);
    } catch (err) {
      logger.error("解析历史记录失败:", err);
    }
  };
  const trackHistory = () => {
    const container = document.querySelector("#TopstoryContent");
    if (!container) {
      logger.error("未找到首页推荐容器");
      return;
    }
    container.addEventListener("click", (e) => {
      const target = e.target;
      if (!(target instanceof HTMLElement)) return;
      const item2 = target.closest(".ContentItem");
      if (item2) saveHistoryFromElement(item2);
    });
  };
  const createSegmenter = () => {
    if (Intl == null ? void 0 : Intl.Segmenter) {
      const segmenterInstance = new Intl.Segmenter("zh", { granularity: "word" });
      return (text) => {
        const trimmedText = text.trim();
        if (!trimmedText) return [];
        const ignoreWords = /* @__PURE__ */ new Set([
          "的",
          "了",
          "是",
          "在",
          "和",
          "有",
          "就",
          "不",
          "也",
          "这",
          "那",
          "吗",
          "吧",
          "啊",
          "哦",
          "啦",
          "呀",
          "!",
          "?",
          ",",
          "。",
          "、",
          ";",
          ":",
          "“",
          "”",
          "‘",
          "’",
          "《",
          "》",
          "[",
          "]",
          "{",
          "}",
          ".",
          "(",
          ")",
          "【",
          "】",
          "——",
          "—",
          "…",
          "·"
        ]);
        const segments = Array.from(segmenterInstance.segment(trimmedText)).map((item2) => item2.segment.trim()).filter((word) => word && !ignoreWords.has(word));
        const uniqueTerms = /* @__PURE__ */ new Set([...segments, trimmedText]);
        return Array.from(uniqueTerms);
      };
    }
    return (text) => {
      const trimmedText = text.trim();
      if (!trimmedText) return [];
      const parts = trimmedText.split(/\s+/).map((part) => part.trim()).filter(Boolean);
      const uniqueTerms = /* @__PURE__ */ new Set([...parts, trimmedText]);
      return Array.from(uniqueTerms);
    };
  };
  const segmenter = createSegmenter();
  const isItemMatch = (item2, term) => {
    if (!term) return true;
    const lowerTerm = term.toLowerCase();
    const { title: title2, content: content2, authorName } = item2;
    if (title2.toLowerCase().includes(lowerTerm)) return true;
    if (content2 == null ? void 0 : content2.toLowerCase().includes(lowerTerm)) return true;
    return authorName.toLowerCase().includes(lowerTerm);
  };
  const findAllMatches = (text, searchTerm) => {
    if (!text) return [];
    const result = [];
    const termLower = searchTerm.toLowerCase();
    let startIndex = 0;
    let matchIndex;
    while ((matchIndex = text.toLowerCase().indexOf(termLower, startIndex)) !== -1) {
      result.push({
        start: matchIndex,
        end: matchIndex + searchTerm.length,
        term: searchTerm
      });
      startIndex = matchIndex + 1;
    }
    return result;
  };
  const searchItem = (items, term) => {
    if (!term) return /* @__PURE__ */ new Map();
    const result = /* @__PURE__ */ new Map();
    const searchTerms = segmenter(term);
    items.forEach((item2, index) => {
      let hasMatches = false;
      const itemResult = {
        terms: [],
        matches: {}
      };
      for (const searchTerm of searchTerms) {
        if (!isItemMatch(item2, searchTerm)) continue;
        if (!itemResult.terms.includes(searchTerm)) {
          itemResult.terms.push(searchTerm);
        }
        hasMatches = true;
        const fields = ["title", "authorName"];
        if (item2.content) fields.push("content");
        fields.forEach((field) => {
          const text = item2[field];
          const matches = findAllMatches(text, searchTerm);
          if (matches.length > 0) {
            if (!itemResult.matches[field]) {
              itemResult.matches[field] = [];
            }
            itemResult.matches[field].push(...matches);
          }
        });
      }
      if (hasMatches) result.set(index, itemResult);
    });
    return result;
  };
  const HistoryDialog = ({ isOpen, onClose }) => {
    const [searchTerm, debouncedValue, setSearchTerm] = useDebouncedState("", 300);
    const historyItems = getHistory();
    const matchedItems = require$$1.useMemo(() => searchItem(historyItems, debouncedValue), [historyItems, debouncedValue]);
    const dialogRef = require$$1.useRef(null);
    const firstItemRef = require$$1.useRef(null);
    require$$1.useEffect(() => {
      var _a;
      const dialogElement = dialogRef.current;
      if (!dialogElement) return;
      if (isOpen) {
        dialogElement.showModal();
        document.body.style.overflow = "hidden";
        (_a = firstItemRef.current) == null ? void 0 : _a.focus();
      } else if (dialogElement.open) {
        dialogElement.close();
        document.body.style.overflow = "";
      }
    }, [isOpen]);
    const handleClose = () => {
      onClose();
    };
    return /* @__PURE__ */ jsxRuntimeExports.jsx(
      "dialog",
      {
        ref: dialogRef,
        className: styles.dialog,
        onClose: handleClose,
        onClick: (e) => {
          if (e.target === dialogRef.current) {
            handleClose();
          }
        },
        onKeyDown: (e) => {
          if (e.key === "Escape") {
            handleClose();
          }
        },
        children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: styles.dialogContent, children: [
          /* @__PURE__ */ jsxRuntimeExports.jsxs("header", { className: styles.dialogHeader, children: [
            /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: styles.dialogTitle, children: "浏览历史" }),
            /* @__PURE__ */ jsxRuntimeExports.jsx(SearchBox, { searchTerm, onSearchChange: setSearchTerm })
          ] }),
          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: styles.dialogBody, children: [
            /* @__PURE__ */ jsxRuntimeExports.jsx(SearchStatus, { totalCount: historyItems.length, matchedCount: searchTerm ? matchedItems.size : -1 }),
            /* @__PURE__ */ jsxRuntimeExports.jsx("ul", { className: styles.historyList, children: historyItems.map((item2, i) => {
              const isMatch = !searchTerm || matchedItems.has(i);
              if (!isMatch) return null;
              return /* @__PURE__ */ jsxRuntimeExports.jsx(
                HistoryItem,
                {
                  item: item2,
                  searchResult: matchedItems.get(i),
                  ref: i === 0 ? firstItemRef : null
                },
                item2.itemId
              );
            }) })
          ] })
        ] })
      }
    );
  };
  const HistoryCard = () => {
    const [isDialogOpen, setIsDialogOpen] = require$$1.useState(false);
    require$$1.useEffect(() => {
      const handleKeyDown = (event) => {
        const target = event.target;
        const isEditableTarget = target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable || target.tagName === "SELECT";
        if (event.key === "h" && !isEditableTarget) {
          setIsDialogOpen((prev) => !prev);
        }
      };
      window.addEventListener("keydown", handleKeyDown);
      return () => {
        window.removeEventListener("keydown", handleKeyDown);
      };
    }, []);
    return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: styles.historyCard, role: "complementary", children: [
      /* @__PURE__ */ jsxRuntimeExports.jsx(
        "button",
        {
          className: styles.historyButton,
          onClick: () => setIsDialogOpen(true),
          "aria-label": "历史记录,打开后按 Esc 关闭",
          "aria-haspopup": "dialog",
          type: "button",
          children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "历史记录" })
        }
      ),
      /* @__PURE__ */ jsxRuntimeExports.jsx(HistoryDialog, { isOpen: isDialogOpen, onClose: () => setIsDialogOpen(false) })
    ] });
  };
  const App = () => {
    trackHistory();
    return /* @__PURE__ */ jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(HistoryCard, {}) });
  };
  const clearHistoryCommand = [
    "🗑 清空浏览历史记录",
    () => {
      clearHistory();
      alert("清空浏览历史成功");
    }
  ];
  const setHistoryLimitCommand = [
    `🔢 设置记录数量限制(当前:${HISTORY_LIMIT})`,
    () => {
      const input = prompt(`请输入新的历史记录最大数量(默认 ${DEFAULT_HISTORY_LIMIT})`);
      if (!input) return;
      const [isOK, message] = setHistoryLimit(input);
      if (isOK) {
        alert("设置成功");
      } else {
        alert(message);
      }
    }
  ];
  const registerMenuCommands = () => {
    Reflect.apply(_GM_registerMenuCommand, null, clearHistoryCommand);
    Reflect.apply(_GM_registerMenuCommand, null, setHistoryLimitCommand);
  };
  console.log(
    "%c知乎历史记录",
    "color:#1772F6; font-weight:bold; font-size:3em; padding:5px; text-shadow:1px 1px 3px rgba(0,0,0,0.7)"
  );
  const mountApp = () => {
    const container = document.createElement("div");
    container.id = "zh-history-root";
    const target = document.querySelector(".Topstory-container > div:nth-child(2) > div:nth-child(2)");
    if (!target) {
      logger.warn("未找到挂载点");
      return;
    }
    target.appendChild(container);
    ReactDOM.render(/* @__PURE__ */ jsxRuntimeExports.jsx(App, {}), container);
  };
  mountApp();
  registerMenuCommands();
  logger.log(`初始化成功,版本:${_GM_info.script.version}`);

})(unsafeWindow.React, unsafeWindow.ReactDOM);