// ==UserScript== // @name 知乎++ // @description 清理标题括号数字,替换消息/私信按钮,收藏夹可配置,解除复制限制,隐藏侧边栏,并增强过滤干扰内容、调整布局,重新对阅读页面卡片化设计,美化界面极大提升阅读体验,新增护眼色开关(全局持久化) // @namespace http://tampermonkey.net/ // @icon https://www.zhihu.com/favicon.ico // @license MIT // @version 4.0 // @author ddrwin // @match *://*/* // @match https://*.zhihu.com/* // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @note 2026.03.22 V1.0 全局清理网页标题开头的未读数字提示(如 (3条消息)) // @note 2026.03.23 V1.2 将首页消息/私信按钮替换为收藏夹、最近浏览 // @note 2026.03.24 V1.3 隐藏右侧边栏及创作中心,清理消息/私信按钮上的通知数字 // @note 2026.03.25 V2.1 创作中心改为可配置的收藏设置,允许清空地址,解除复制版权限制,仅保留纯净文本 // @note 2026.03.25 V2.2 增加文章页居中并自适应宽度,自动点击“查看全部回答”,隐藏首页“热榜”和“视频”选项卡,只保留“推荐” // @note 2026.03.25 V3.0 首页、搜索结果页、问题页答案卡片美化,热榜条目卡片美化;过滤推荐页中的视频、文章、链接卡片、教育卡片、专栏、电商等干扰内容,过滤答案页中的视频答案,调整推荐页、问题页、搜索页主列宽度为900px,在问题页标题下方显示修改时间, 隐藏搜索框占位符,加大问题标题字体 // @note 2026.03.26 V3.1 统一卡片样式为 Baidu++ 风格(浅绿色背景、蓝色边框、阴影) // @note 2026.03.27 V3.5 动态更新页面类型类,改用 CSS 变量方案实现护眼色,彻底消除 SPA 路由切换后类残留导致的样式错乱失效的问题 // @note 2026.03.28 V3.9 彻底解决 SPA 路由切换后类残留导致的样式错乱;新增圈子页面(/ring)完整样式支持;优化专栏主页及个人主页卡片布局。 // @note 2026.03.28 V4.0 设置面板增加动画开关,新增 ANIM_KEY 存储键,默认值 true(默认开启动画),与护眼色独立存储、独立控制。 // @downloadURL https://update.greasyfork.icu/scripts/571059/%E7%9F%A5%E4%B9%8E%2B%2B.user.js // @updateURL https://update.greasyfork.icu/scripts/571059/%E7%9F%A5%E4%B9%8E%2B%2B.meta.js // ==/UserScript== (function() { 'use strict'; // ============================================================ // 1. 颜色配置:两套色板,面板切换时只改 CSS 变量,样式规则不变 // ============================================================ /** * 在这里集中管理两套颜色。 * - softEye:护眼绿色系 * - white:默认白色系 * * 新增/修改颜色时只需改这里,CSS 规则无需动。 */ const COLOR_PALETTES = { softEye: { cardBg: '#F5FCFA', cardBgHover: '#E9F7F3', cardBorder: '#3D7CD4', cardBorderHover: '#2c6fc7', cardShadow: '0 8px 20px rgba(0,0,0,0.15)', cardShadowHover: '0 12px 28px rgba(0,0,0,0.2)', }, white: { cardBg: '#ffffff', cardBgHover: '#ffffff', cardBorder: '#e1e8ed', cardBorderHover: '#cbd5e0', cardShadow: '0 2px 8px rgba(0,0,0,0.08)', cardShadowHover: '0 8px 20px rgba(0,0,0,0.12)', }, }; // ============================================================ // 2. 配置存储 // ============================================================ const STORAGE_KEY = 'zhihu_collection_url'; const SOFT_EYE_KEY = 'zhihu_soft_eye'; const ANIM_KEY = 'zhihu_anim'; const getCollectionUrl = () => GM_getValue(STORAGE_KEY, ''); const saveCollectionUrl = (url) => { if (url === null || url === undefined) return false; GM_setValue(STORAGE_KEY, url.trim()); return true; }; const getSoftEyeEnabled = () => GM_getValue(SOFT_EYE_KEY, true); const setSoftEyeEnabled = (enabled) => { GM_setValue(SOFT_EYE_KEY, enabled); applyTheme(); }; const getAnimEnabled = () => GM_getValue(ANIM_KEY, true); const setAnimEnabled = (enabled) => { GM_setValue(ANIM_KEY, enabled); applyTheme(); }; // ============================================================ // 3. 核心:用 CSS 变量驱动主题切换 // // CSS 变量写在 document.documentElement(即 )上, // 不依赖 body class,SPA 路由切换、框架重渲染都不会丢失, // 无需任何 MutationObserver 来"防御"。 // ============================================================ /** * 把对应色板的值写入 CSS 自定义属性。 * 调用一次即永久生效,直到页面关闭或再次调用。 */ const applyTheme = () => { const softEye = getSoftEyeEnabled(); const anim = getAnimEnabled(); const palette = COLOR_PALETTES[softEye ? 'softEye' : 'white']; const root = document.documentElement; root.style.setProperty('--zh-card-bg', palette.cardBg); root.style.setProperty('--zh-card-border', palette.cardBorder); root.style.setProperty('--zh-card-shadow', palette.cardShadow); if (anim) { root.style.setProperty('--zh-card-bg-hover', palette.cardBgHover); root.style.setProperty('--zh-card-border-hover', palette.cardBorderHover); root.style.setProperty('--zh-card-shadow-hover', palette.cardShadowHover); root.style.setProperty('--zh-card-transform-hover', 'translateY(-2px)'); root.style.setProperty('--zh-transition', 'all 0.3s ease'); } else { root.style.setProperty('--zh-card-bg-hover', palette.cardBg); root.style.setProperty('--zh-card-border-hover', palette.cardBorder); root.style.setProperty('--zh-card-shadow-hover', palette.cardShadow); root.style.setProperty('--zh-card-transform-hover', 'translateY(0)'); root.style.setProperty('--zh-transition', 'none'); } }; const applySoftEye = () => applyTheme(); // ============================================================ // 4. 设置面板 // ============================================================ const showSettingsPanel = () => { if (document.getElementById('zh-settings-panel')) return; const currentUrl = getCollectionUrl(); const overlay = document.createElement('div'); overlay.id = 'zh-settings-overlay'; overlay.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.4); z-index: 10000; display: flex; align-items: center; justify-content: center; `; const panel = document.createElement('div'); panel.id = 'zh-settings-panel'; panel.style.cssText = ` background: #fff; border-radius: 16px; box-shadow: 0 8px 24px rgba(0,0,0,0.15); width: 360px; padding: 24px 20px; text-align: left; font-family: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif; `; panel.innerHTML = `

知乎++ 设置

例如:https://www.zhihu.com/collection/12345678
留空并点击确认可清除地址
`; overlay.appendChild(panel); document.body.appendChild(overlay); const input = document.getElementById('collection-url-input'); const checkbox = document.getElementById('soft-eye-checkbox'); const animCheckbox = document.getElementById('anim-checkbox'); const confirmBtn = document.getElementById('confirm-settings-btn'); const cancelBtn = document.getElementById('close-settings-btn'); checkbox.checked = getSoftEyeEnabled(); animCheckbox.checked = getAnimEnabled(); confirmBtn.addEventListener('click', () => { saveCollectionUrl(input.value.trim()); GM_setValue(SOFT_EYE_KEY, checkbox.checked); GM_setValue(ANIM_KEY, animCheckbox.checked); applyTheme(); overlay.remove(); }); cancelBtn.addEventListener('click', () => overlay.remove()); }; // ============================================================ // 5. 全局标题清理 // ============================================================ const cleanTitle = () => { const titleElem = document.getElementsByTagName('title')[0]; if (!titleElem) return; const newTitle = titleElem.innerText.replace(/^\(\d+(?:[^)]*)?\)\s*/, ''); if (newTitle !== titleElem.innerText) titleElem.innerText = newTitle; }; cleanTitle(); const titleElem = document.querySelector('title'); if (titleElem) { const titleObserver = new MutationObserver(() => { titleObserver.disconnect(); cleanTitle(); titleObserver.observe(titleElem, { childList: true }); }); titleObserver.observe(titleElem, { childList: true }); } // ============================================================ // 6. 知乎专属功能 // ============================================================ if (window.location.hostname.includes('zhihu.com')) { // ---------- 6.1 解除复制版权限制 ---------- document.addEventListener('copy', (e) => { const selection = window.getSelection(); if (selection && selection.toString().trim() !== '') { const plainText = selection.toString(); e.clipboardData.setData('text/plain', plainText); e.clipboardData.setData('text/html', plainText.replace(/\n/g, '
')); e.preventDefault(); e.stopPropagation(); console.log('[知乎定制] 已拦截复制,仅保留纯净文本'); } }, { capture: true }); // ---------- 6.2 样式注入 ---------- // // 所有卡片颜色属性统一引用 CSS 变量(--zh-card-*)。 // 切换护眼色时只需改变量值,这段 CSS 无需修改。 // 新增颜色属性:在 COLOR_PALETTES 里加字段, // 在 applySoftEye 里加一行 setProperty,在这里加一处 var() 引用即可。 // const style = document.createElement('style'); style.textContent = ` /* 隐藏消息/私信弹窗 */ .PushNotifications-menuContainer, .Messages-menuContainer, [id^="Popover"][class*="PushNotifications"], [id^="Popover"][class*="Messages"] { display: none !important; } /* 隐藏右侧边栏 */ .Post-Row-Content-right, .css-1qyytj7, div[data-za-detail-view-path-module="RightSideBar"] { display: none !important; } /* 隐藏顶部导航栏的"付费咨询"和"知学堂"按钮 */ a[href="https://www.zhihu.com/consult"], a[href="https://www.zhihu.com/education/learning"] { display: none !important; } /* 内容宽度自适应 */ .Post-Row-Content-left { max-width: 100% !important; width: 90% !important; margin-left: 0 !important; margin-right: auto !important; } .Post-Row-Content-left-article { max-width: 100% !important; } /* 首页:只保留"推荐"选项卡 */ a[aria-controls="Topstory-hot"], a[aria-controls="Topstory-zvideo"], ul[class~="AppHeader-Tabs"] li:not(:first-child) { display: none !important; } /* 隐藏搜索框占位符 */ ::placeholder { color: transparent !important; } /* 推荐页过滤干扰内容 */ .TopstoryItem--advertCard, .TopstoryItem-isRecommend:has(.VideoAnswerPlayer-video), .TopstoryItem-isRecommend:has(.ZVideoItem-video), .TopstoryItem-isRecommend:has(.RichText-video), .TopstoryItem-isRecommend:has(.CopyrightRichText-richTex), .TopstoryItem-isRecommend:has(.RichText-LinkCardContainer), .TopstoryItem-isRecommend:has(.RichText-EduCardContainer), .TopstoryItem-isRecommend:has(div[data-za-extra-module*="Post"]), .TopstoryItem-isRecommend:has(.RichText-Ecommerce), .TopstoryItem-isRecommend:has(.ecommerce-ad-box) { display: none !important; } /* 问题页隐藏视频答案 */ .AnswerItem:has(.VideoCard-video-content), .AnswerItem:has(.VideoAnswerPlayer) { display: none !important; } /* 主列宽度调整 */ .Topstory-container, .Topstory-mainColumn, .Question-main, .Question-mainColumn, .Search-container, .SearchMain { width: 900px !important; } /* 问题页显示修改时间 */ meta[itemprop="dateModified"] { display: block; height: 20px; padding: 10px 0; margin-top: 10px; } meta[itemprop="dateModified"]::after { content: "修改时间: " attr(content); color: #8590a6; font-size: 14px; } /* 问题页标题字体加大 */ .ContentItem-title { font-size: x-large !important; } /* ===== 卡片样式:全部使用 CSS 变量,护眼/白色均由变量决定 ===== */ .TopstoryItem:not(.TopstoryItem-feedList), .SearchResult-Card, .Question-mainColumn .AnswerItem, .RelevantQuery, .HotItem, .css-i83kfi, .TopicFeedItem, .Post-Main.Post-NormalMain, .CollectionDetailPageItem { background: var(--zh-card-bg) !important; border: 1px solid var(--zh-card-border) !important; border-radius: 12px !important; box-shadow: var(--zh-card-shadow) !important; margin-bottom: 20px !important; padding: 20px 24px !important; transition: var(--zh-transition) !important; } .TopstoryItem:not(.TopstoryItem-feedList):hover, .SearchResult-Card:hover, .Question-mainColumn .AnswerItem:hover, .RelevantQuery:hover, .HotItem:hover, .css-i83kfi:hover, .TopicFeedItem:hover, .Post-Main.Post-NormalMain:hover, .CollectionDetailPageItem:hover { background: var(--zh-card-bg-hover) !important; border-color: var(--zh-card-border-hover) !important; box-shadow: var(--zh-card-shadow-hover) !important; transform: var(--zh-card-transform-hover) !important; } /* 问题页答案卡片特殊处理 */ .Question-mainColumn .AnswerItem { margin-bottom: 0px !important; } /* 专栏页已更内容特殊处理 */ .css-1a2rdla, .css-oarhve { margin-bottom: 20px !important; } /* 拼接答案卡片:与现有卡片共享 CSS 变量,hover 效果一致 */ .zh-injected-answer-card { background: var(--zh-card-bg) !important; border: 1px solid var(--zh-card-border) !important; border-radius: 12px !important; box-shadow: var(--zh-card-shadow) !important; margin-bottom: 20px !important; padding: 20px 24px !important; transition: var(--zh-transition) !important; } .zh-injected-answer-card:hover { background: var(--zh-card-bg-hover) !important; border-color: var(--zh-card-border-hover) !important; box-shadow: var(--zh-card-shadow-hover) !important; transform: var(--zh-card-transform-hover) !important; } /* 相关搜索模块内部样式 */ .RelevantQuery h2 { font-size: 18px !important; font-weight: 600 !important; margin-bottom: 16px !important; } .RelevantQuery ul { display: flex !important; flex-wrap: wrap !important; gap: 12px !important; list-style: none !important; padding-left: 0 !important; } .RelevantQuery li { margin: 0 !important; } .RelevantQuery li a { display: inline-block !important; padding: 6px 14px !important; background: #ffffff !important; border-radius: 20px !important; color: #3476d2 !important; text-decoration: none !important; font-size: 14px !important; transition: all 0.2s ease !important; } .RelevantQuery li a:hover { background: #eef2f6 !important; color: #0f4c81 !important; transform: scale(1.02) !important; } /* 隐藏搜索结果页右侧边栏 */ .css-knqde { display: none !important; } /* 浏览历史页主内容区域加宽至900px */ .css-9511cm { width: 900px !important; max-width: 900px !important; margin: 0 auto !important; } /* 收藏页主内容区域加宽至900px */ .CollectionsDetailPage-mainColumn { width: 900px !important; max-width: 900px !important; margin: 0 auto !important; } /* 专栏主页内容区域加宽至900px(仅 html.zh-column 生效) */ html.zh-column .css-1pariuy, html.zh-column .css-jqhguc, html.zh-column .css-44kk6u, html.zh-column .css-1u9sxdg{ width: 900px !important; max-width: 900px !important; } /* 专栏主页文章卡片样式(仅 html.zh-column 生效,不影响其他页面) */ html.zh-column .css-9w3zhd { width: 860px !important; background: var(--zh-card-bg) !important; border: 1px solid var(--zh-card-border) !important; border-radius: 12px !important; box-shadow: var(--zh-card-shadow) !important; margin-bottom: 20px !important; margin-left: 20px !important; padding: 20px 24px !important; transition: var(--zh-transition) !important; } html.zh-column .css-9w3zhd:hover { background: var(--zh-card-bg-hover) !important; border-color: var(--zh-card-border-hover) !important; box-shadow: var(--zh-card-shadow-hover) !important; transform: var(--zh-card-transform-hover) !important; } /* 专题页文章外壳容器:仅保留上下间距,不加卡片装饰 */ html.zh-column .ContentItem.ArticleItem { margin-bottom: 20px !important; background: transparent !important; border: none !important; box-shadow: none !important; padding: 0 !important; } /* 个人主页:页头宽度 900px */ html.zh-people .ProfileHeader, html.zh-people .Profile-main, html.zh-people .css-16mzn5c, html.zh-people .Profile-mainColumn { max-width: 900px !important; width: 900px !important; margin: 0 auto !important; } /* 个人主页:回答卡片样式 */ html.zh-people .ContentItem.AnswerItem, html.zh-people .ContentItem.ArticleItem, html.zh-people .ContentItem.PinItem { background: var(--zh-card-bg) !important; border: 1px solid var(--zh-card-border) !important; border-radius: 12px !important; box-shadow: var(--zh-card-shadow) !important; margin-bottom: 0px !important; padding: 20px 24px !important; transition: var(--zh-transition) !important; } html.zh-people .ContentItem.AnswerItem:hover, html.zh-people .ContentItem.ArticleItem:hover, html.zh-people .ContentItem.PinItem:hover { background: var(--zh-card-bg-hover) !important; border-color: var(--zh-card-border-hover) !important; box-shadow: var(--zh-card-shadow-hover) !important; transform: var(--zh-card-transform-hover) !important; } /* 圈子页面:页头宽度 900px */ html.zh-ring .css-1g878q7 { max-width: 900px !important; width: 900px !important; margin: 0 auto !important; } /* 圈子页面(/ring-feeds):PinItem 卡片样式 */ html.zh-ring .ContentItem.PinItem { background: var(--zh-card-bg) !important; border: 1px solid var(--zh-card-border) !important; border-radius: 12px !important; box-shadow: var(--zh-card-shadow) !important; margin-bottom: 0px !important; padding: 20px 24px !important; transition: var(--zh-transition) !important; } html.zh-ring .ContentItem.PinItem:hover { background: var(--zh-card-bg-hover) !important; border-color: var(--zh-card-border-hover) !important; box-shadow: var(--zh-card-shadow-hover) !important; transform: var(--zh-card-transform-hover) !important; } `; document.head.appendChild(style); // 页面加载时立即把对应色板写入 CSS 变量,一次搞定 applyTheme(); // ---------- 6.2.1 动态更新页面类型类(解决 SPA 路由切换后类残留问题) ---------- /** * 根据当前 URL 给 添加对应的类(zh-column / zh-people / zh-ring), * 并移除旧的类,确保样式只作用于正确页面。 */ const updatePageClass = () => { const host = window.location.hostname; const path = window.location.pathname; // 先移除所有可能已有的类(避免累积) document.documentElement.classList.remove('zh-column', 'zh-people', 'zh-ring'); // zh-column:专栏/专题/文章详情相关页面 if ( // zhuanlan.zhihu.com/c_xxx(专栏主页)、/p/xxx(文章详情) (host === 'zhuanlan.zhihu.com' && /^\/(c_|p\/)/.test(path)) // www.zhihu.com/column/c_xxx(专题主页)、/column-square(专栏广场) || (host === 'www.zhihu.com' && /^\/(column\/|column-square)/.test(path)) ) { document.documentElement.classList.add('zh-column'); } // zh-people:个人主页 if (host === 'www.zhihu.com' && /^\/people\//.test(path)) { document.documentElement.classList.add('zh-people'); } // zh-ring:圈子页面(/ring-feeds 和 /ring/host/xxx) if (host === 'www.zhihu.com' && /^\/ring/.test(path)) { document.documentElement.classList.add('zh-ring'); } }; // 初始调用一次 updatePageClass(); // 劫持 history.pushState 和 replaceState,以便在 SPA 路由切换时更新类 const originalPushState = history.pushState; const originalReplaceState = history.replaceState; history.pushState = function(...args) { originalPushState.apply(this, args); // 延迟更新,等待 DOM 变化完成 setTimeout(updatePageClass, 100); }; history.replaceState = function(...args) { originalReplaceState.apply(this, args); setTimeout(updatePageClass, 100); }; // 监听浏览器前进/后退事件 window.addEventListener('popstate', () => setTimeout(updatePageClass, 100)); // 补充:监听 title 变化作为"页面已渲染"信号(比 setTimeout 更准确) // 知乎每次 SPA 路由完成后都会更新 ,此时 DOM 已稳定 const titleElemForRoute = document.querySelector('title'); if (titleElemForRoute) { new MutationObserver(() => updatePageClass()).observe(titleElemForRoute, { childList: true }); } // ---------- 6.3 答案文章页:原地注入全部回答 + 无限滚动 ---------- // // 仅在单答案页 (/question/xxx/answer/xxx) 生效。 // - 拦截"查看全部回答"按钮,阻止跳转,改为在当前页尾部拼接答案列表。 // - 利用知乎 /api/v4/questions/{id}/feeds 接口分批加载, // 用 paging.next 游标驱动无限滚动,行为与问题页一致。 // const answerPageMatch = window.location.pathname.match(/\/question\/(\d+)\/answer\/\d+/); if (answerPageMatch) { const questionId = answerPageMatch[1]; let injected = false; let nextFeedUrl = null; let loadingMore = false; // 当前答案 ID,用于过滤拼接列表中的重复项 const currentAnswerId = String(window.location.pathname.match(/\/answer\/(\d+)/)[1]); // 修复懒加载图片:把 data-actualsrc 替换为真实 src const fixLazyImages = (container) => { // 方式一:处理 <img data-actualsrc="..."> container.querySelectorAll('img[data-actualsrc]').forEach(img => { img.src = img.dataset.actualsrc; img.removeAttribute('data-actualsrc'); }); // 方式二:处理被 <noscript> 包裹的图片(知乎 SSR 场景) container.querySelectorAll('noscript').forEach(ns => { const tmp = document.createElement('div'); tmp.innerHTML = ns.textContent; const img = tmp.querySelector('img'); if (img) { // 用 data-original 或 src 中第一个真实地址 const realSrc = img.dataset.original || img.src; if (realSrc && !realSrc.startsWith('data:')) { img.src = realSrc; img.removeAttribute('data-original'); ns.replaceWith(img); } } }); // 方式三:使用 IntersectionObserver 按需加载剩余懒图 const lazyImgs = container.querySelectorAll('img[data-original]'); if (lazyImgs.length === 0) return; const imgObserver = new IntersectionObserver((entries, obs) => { entries.forEach(entry => { if (!entry.isIntersecting) return; const img = entry.target; if (img.dataset.original) { img.src = img.dataset.original; img.removeAttribute('data-original'); } obs.unobserve(img); }); }, { rootMargin: '200px' }); lazyImgs.forEach(img => imgObserver.observe(img)); }; // 构建单条答案卡片 DOM(与现有卡片样式共享 CSS 变量) const buildAnswerCard = (ans) => { const author = ans.author || {}; const name = author.name || '匿名用户'; const avatar = author.avatar_url || ''; const profileUrl = author.url_token ? `https://www.zhihu.com/people/${author.url_token}` : '#'; const voteup = (ans.voteup_count || 0).toLocaleString(); const comments = ans.comment_count || 0; const rawContent = ans.content || ans.excerpt || ''; const answerUrl = `https://www.zhihu.com/question/${questionId}/answer/${ans.id}`; const timeStr = ans.updated_time ? new Date(ans.updated_time * 1000) .toLocaleDateString('zh-CN', { year:'numeric', month:'2-digit', day:'2-digit' }) : ''; const div = document.createElement('div'); div.className = 'zh-injected-answer-card'; // 头部:头像 + 作者信息 + 赞同数 const header = document.createElement('div'); header.style.cssText = 'display:flex;align-items:center;gap:10px;margin-bottom:14px'; header.innerHTML = ` ${avatar ? `<img src="${avatar}" style="width:36px;height:36px;border-radius:50%;flex-shrink:0" alt="${name}">` : `<div style="width:36px;height:36px;border-radius:50%;background:#e0e7ef;flex-shrink:0"></div>`} <div style="min-width:0"> <a href="${profileUrl}" target="_blank" style="font-weight:600;color:inherit;text-decoration:none;display:block">${name}</a> ${author.headline ? `<div style="font-size:12px;color:#8590a6;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">${author.headline}</div>` : ''} </div> <span style="margin-left:auto;color:#8590a6;font-size:13px;white-space:nowrap">▲ ${voteup}</span> `; div.appendChild(header); // 正文:用 innerHTML 注入,然后再修复图片(避免 innerHTML 字符串拼接破坏内容) const body = document.createElement('div'); body.className = 'RichText ztext'; body.style.cssText = 'font-size:15px;line-height:1.75;word-break:break-word'; body.innerHTML = rawContent; fixLazyImages(body); // 注入后立刻修复图片 div.appendChild(body); // 底部:时间 + 评论数 + 原答案链接 const footer = document.createElement('div'); footer.style.cssText = 'margin-top:14px;font-size:13px;color:#8590a6;display:flex;gap:16px;align-items:center;flex-wrap:wrap'; footer.innerHTML = ` ${timeStr ? `<span>${timeStr}</span>` : ''} <span>${comments} 条评论</span> <a href="${answerUrl}" target="_blank" style="color:#3D7CD4;text-decoration:none;margin-left:auto">查看原答案 →</a> `; div.appendChild(footer); return div; }; // 请求一批答案并追加到列表容器 const fetchAndAppend = async (list, url) => { if (loadingMore) return; loadingMore = true; const loader = document.createElement('div'); loader.style.cssText = 'text-align:center;padding:24px;color:#8590a6;font-size:14px'; loader.textContent = '加载中…'; list.appendChild(loader); try { const resp = await fetch(url); if (!resp.ok) throw new Error(`HTTP ${resp.status}`); const json = await resp.json(); loader.remove(); (json.data || []).forEach(item => { const t = item.target; // 跳过当前正在阅读的答案,避免重复展示 if (t && t.id && t.content !== undefined && String(t.id) !== currentAnswerId) { list.appendChild(buildAnswerCard(t)); } }); // paging.next 是下一页完整 URL,直接使用 nextFeedUrl = (json.paging && !json.paging.is_end) ? json.paging.next : null; if (!nextFeedUrl) { const end = document.createElement('div'); end.style.cssText = 'text-align:center;padding:24px;color:#8590a6;font-size:14px'; end.textContent = '— 已加载全部回答 —'; list.appendChild(end); } } catch (e) { loader.textContent = '⚠️ 加载失败,请稍后重试'; console.error('[知乎定制] 答案加载失败', e); nextFeedUrl = url; // 保留当前 URL,下次滚动可重试 } loadingMore = false; }; // 构建注入容器并启动首次加载 const injectAnswerSection = async () => { if (injected) return; injected = true; // 找到当前答案卡片作为锚点 const anchor = document.querySelector('.Card.AnswerCard') || document.querySelector('.QuestionAnswer-content')?.closest('.Card'); if (!anchor) { injected = false; return; } // 外层包装 const wrapper = document.createElement('div'); wrapper.id = 'zh-all-answers-wrapper'; wrapper.style.marginTop = '24px'; // 标题行 const heading = document.createElement('div'); heading.style.cssText = ` font-size: 18px; font-weight: 600; margin-bottom: 16px; padding-bottom: 12px; border-bottom: 2px solid var(--zh-card-border); color: inherit; `; heading.textContent = '全部回答'; wrapper.appendChild(heading); // 答案列表区域 const list = document.createElement('div'); list.id = 'zh-answer-list'; wrapper.appendChild(list); // 滚动哨兵(位于列表最底部) const sentinel = document.createElement('div'); sentinel.id = 'zh-answer-sentinel'; sentinel.style.height = '4px'; wrapper.appendChild(sentinel); anchor.parentNode.insertBefore(wrapper, anchor.nextSibling); // 首次加载 const include = encodeURIComponent( 'data[*].content,is_normal,author,voteup_count,comment_count,updated_time,excerpt' ); const firstUrl = `https://www.zhihu.com/api/v4/questions/${questionId}/feeds` + `?include=${include}&limit=5&offset=0&platform=desktop&sort_by=default`; await fetchAndAppend(list, firstUrl); // IntersectionObserver 驱动无限滚动 const scrollObserver = new IntersectionObserver((entries) => { if (entries[0].isIntersecting && nextFeedUrl && !loadingMore) { fetchAndAppend(list, nextFeedUrl); } }, { rootMargin: '400px' }); scrollObserver.observe(sentinel); }; // 拦截"查看全部回答"按钮:阻止跳转,改为原地注入 const interceptViewAllBtns = () => { document.querySelectorAll('.Card.ViewAll .QuestionMainAction.ViewAll-QuestionMainAction') .forEach(btn => { if (btn.dataset.zhIntercepted) return; btn.dataset.zhIntercepted = 'true'; btn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); injectAnswerSection().then(() => { setTimeout(() => { document.getElementById('zh-all-answers-wrapper') ?.scrollIntoView({ behavior: 'smooth', block: 'start' }); }, 100); }); }, { capture: true }); }); }; // 自动触发(无需手动点按钮) const autoInject = () => { interceptViewAllBtns(); injectAnswerSection(); }; if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', autoInject); } else { autoInject(); } // 按钮可能是异步渲染的,持续监听 const viewAllObserver = new MutationObserver(interceptViewAllBtns); viewAllObserver.observe(document.body, { childList: true, subtree: true }); setTimeout(() => viewAllObserver.disconnect(), 15 * 1000); } // ---------- 6.4 消息/私信按钮替换 ---------- const configs = [ { ariaLabel: '通知', svgClass: 'Bell', getUrl: () => getCollectionUrl(), newText: '收藏', newAriaLabel: '收藏夹', }, { ariaLabel: '私信', svgClass: 'Chat', getUrl: () => 'https://www.zhihu.com/recent-viewed', newText: '最近浏览', newAriaLabel: '最近浏览', }, ]; const modifiedContainers = new Set(); const findButton = (cfg) => { let btn = document.querySelector(`button[aria-label="${cfg.ariaLabel}"]`); if (!btn) { const svg = document.querySelector(`svg[class*="${cfg.svgClass}"]`); if (svg) btn = svg.closest('button'); } return btn; }; const hideBadge = (container) => { const badge = container.querySelector('.css-dkw0ir'); if (badge) badge.style.display = 'none'; }; const modifyButton = (cfg) => { const btn = findButton(cfg); if (!btn) return null; const container = btn.closest('.Popover') || btn; if (modifiedContainers.has(container)) return container; const fix = () => { const span = container.querySelector('.css-vurnku'); if (span) { if (span.textContent !== cfg.newText) span.textContent = cfg.newText; } else { for (let node of container.childNodes) { if (node.nodeType === Node.TEXT_NODE && node.textContent.trim() === cfg.ariaLabel) { if (node.textContent !== cfg.newText) node.textContent = cfg.newText; break; } } } if (container.getAttribute('aria-label') !== cfg.newAriaLabel) { container.setAttribute('aria-label', cfg.newAriaLabel); } hideBadge(container); }; fix(); container.addEventListener('click', (e) => { fix(); e.stopPropagation(); e.preventDefault(); const targetUrl = cfg.getUrl(); if (targetUrl && targetUrl.trim() !== '') { window.location.href = targetUrl; } else if (cfg.ariaLabel === '通知') { showSettingsPanel(); } else { console.warn('[知乎定制] 未获取到有效URL'); } }, { capture: true }); container.style.cursor = 'pointer'; modifiedContainers.add(container); console.log(`[知乎定制] 已修改"${cfg.ariaLabel}"按钮为"${cfg.newText}"`); return container; }; const initButtons = () => { for (const cfg of configs) modifyButton(cfg); }; if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initButtons); } else { initButtons(); } const btnObserver = new MutationObserver(() => { for (const cfg of configs) { const btn = findButton(cfg); if (!btn) continue; const container = btn.closest('.Popover') || btn; if (!modifiedContainers.has(container)) { modifyButton(cfg); } else { const span = container.querySelector('.css-vurnku'); let current = span ? span.textContent : ''; if (!span) { for (let node of container.childNodes) { if (node.nodeType === Node.TEXT_NODE && node.textContent.trim() === cfg.ariaLabel) { current = node.textContent.trim(); break; } } } if (current !== cfg.newText) { if (span) span.textContent = cfg.newText; else { for (let node of container.childNodes) { if (node.nodeType === Node.TEXT_NODE && node.textContent.trim() === cfg.ariaLabel) { node.textContent = cfg.newText; break; } } } container.setAttribute('aria-label', cfg.newAriaLabel); console.log(`[知乎定制] 修正了"${cfg.ariaLabel}"按钮文字(被重置)`); } hideBadge(container); } } }); btnObserver.observe(document.body, { childList: true, subtree: true }); setTimeout(() => btnObserver.disconnect(), 5 * 60 * 1000); // ---------- 6.5 创作中心改为"++设置"按钮 ---------- const modifyCreator = () => { const creatorBtn = document.querySelector('a[href="https://www.zhihu.com/creator"]'); if (!creatorBtn) return; if (creatorBtn.hasAttribute('data-customized')) return; creatorBtn.setAttribute('data-customized', 'true'); const textSpan = creatorBtn.querySelector('.css-vurnku'); if (textSpan) textSpan.textContent = '++设置'; else { for (let node of creatorBtn.childNodes) { if (node.nodeType === Node.TEXT_NODE && node.textContent.trim() === '创作中心') { node.textContent = '++设置'; break; } } } creatorBtn.setAttribute('aria-label', '设置'); creatorBtn.style.cursor = 'pointer'; creatorBtn.style.marginLeft = '12px'; creatorBtn.removeAttribute('href'); creatorBtn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); showSettingsPanel(); }); console.log('[知乎定制] 创作中心已改为"++设置"按钮'); }; modifyCreator(); const creatorObserver = new MutationObserver(() => modifyCreator()); creatorObserver.observe(document.body, { childList: true, subtree: true }); setTimeout(() => creatorObserver.disconnect(), 5 * 60 * 1000); } })();