您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
电脑浏览器访问知乎时,没有历史记录功能,故本脚本模拟实现知乎历史记录功能。
当前为
// ==UserScript== // @name 知乎历史记录助手 // @namespace [email protected] // @version 1.0 // @description 电脑浏览器访问知乎时,没有历史记录功能,故本脚本模拟实现知乎历史记录功能。 // @author QJX // @match https://*.zhihu.com/* // @exclude https://video.zhihu.com/* // @icon https://www.google.com/s2/favicons?sz=64&domain=zhihu.com // @grant none // @license GPLv3 License // ==/UserScript== (function() { 'use strict'; if (window.parent != window) return; // 仅在顶层窗口执行脚本,内嵌窗口不执行该脚本 // 全局常量 const historyTag = '_history_zhihu_by_QJX_'; const configurations = { maxRecordsCount: 100, // 历史记录最大记录数目 maxBriefContentCharacterCount: 100, // 最大简要内容文字个数 }; function getAncestorElementByClassName(element, className){ // 根据类名找祖先 for (; element && !element.classList.contains(className); element = element.parentElement) {} return element; } function getHistory() { // 获取历史记录 return JSON.parse(window.localStorage.getItem(historyTag) || '[]'); } function saveHistory(history) { // 保存历史记录 if (history.length > configurations.maxRecordsCount) history.length = configurations.maxRecordsCount; window.localStorage.setItem(historyTag, JSON.stringify(history)); } function Record({type = '', title = '', url = '', authorName = '', content = '', imgURL = ''} = {}) { // 记录 this.type = type; // 'question'|'answer'|'article' this.title = title; this.url = url; // 以下两项仅在type为answer或article时有效 this.authorName = authorName; this.content = content; // 以下项仅在type为answer或article且内容中有图片时有效 this.imgURL = imgURL; } function addRecord(history, record) { // 添加一条记录到历史记录中 history.unshift(record); } function injectHistoryElement() { // 将历史记录按钮插入到HTML文档中 let element = document.createElement('button'); element.innerHTML = 'H'; element.style.setProperty('width', '50px'); element.style.setProperty('height', '50px'); element.style.setProperty('color', '#FFF'); element.style.setProperty('background-color', '#0066FF'); element.style.setProperty('border-radius', '50%'); element.style.setProperty('position', 'fixed'); element.style.setProperty('right', '50px'); element.style.setProperty('bottom', '50px'); element.style.setProperty('text-align', 'center'); element.addEventListener('click', () => openHistoryWindow()); document.body.append(element); } function openHistoryWindow() { // 打开历史记录窗口 let historyWindow = window.open('', '_blank'); historyWindow.document.open(); historyWindow.document.write(` <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>知乎历史记录</title> <style> html { font-size: 16px; } body { background-color: #f7f7f7; } .link, .link:hover, .link:visited, .link:active { position: relative; width: 70%; display: block; margin: .5rem auto; border: 1px solid black; border-radius: 4px; padding: .5rem; color: #000; background-color: #fff; text-decoration:none; } .title { margin: .5rem; font-size: 1.2rem; font-weight: 650; text-overflow: ellipsis; white-space: nowrap; overflow: hidden; } .answer { margin: .5rem; text-overflow: ellipsis; white-space: nowrap; overflow: hidden; } .author { font-weight: 550; } img { float: right; width: 8rem; height: 4rem;; object-fit: cover; } </style> </head> <body> </body> </html> `); historyWindow.document.close(); const history = getHistory(); let fragment = new DocumentFragment(); history.forEach(h => { /** * <a href=${url} class="link"> * <img src="${imgURL}"> * <p class="title">${title}</p> * <p class="answer"><span class="author">${authorName}: </span>${content}</p> * </a> */ const a = historyWindow.document.createElement('a'); const titleParagraph = historyWindow.document.createElement('p'); a.setAttribute('href', h.url); a.classList .add('link'); titleParagraph.classList.add('title'); titleParagraph.innerHTML = h.title; if (h.imgURL) { const imageElement = historyWindow.document.createElement('img'); imageElement.setAttribute('src', h.imgURL); a.append(imageElement); } a.append(titleParagraph); if (['answer', 'article'].includes(h.type)) { const answerParagraph = historyWindow.document.createElement('p'); const authorElement = historyWindow.document.createElement('span'); answerParagraph.classList.add('answer'); authorElement.classList.add('author'); authorElement.innerHTML = `${h.authorName}: `; answerParagraph.append(authorElement); answerParagraph.append(h.content); a.append(answerParagraph); } fragment.append(a); }); historyWindow.document.body.append(fragment); } /** * 有两种改变浏览记录的方式: * 1. 查看当前页面的URL,URL为以下三种模式改变浏览记录: * 1.1 路径为"/question/<number question>",表示某个问题 * 1.2 路径为"/question/<number question>/answer/<number answer>",表示某个问题下的某个回答,主要信息: * <div class="AuthorInfo"> * <meta itemprop="name" content="<author name>"> * <meta itemprop="image" content="<author image url>"> * </div> * 1.3 路径为"/p/<number article>",表示某篇专栏文章 * 2. 监听用户点击"阅读全文",主要查看以下标签改变浏览记录: * <div class="ContentItem" data-zop="{"authorName":<author name>,"title":<title>,"type":<type="answer"|"article">}"> * <meta itemprop="url" content="//www.zhihu.com/p/<number article>"> * <div class="RichContent"> * <div class="RichContent-inner"> * </div> * </div> * </div> */ let history = getHistory(); const questionPattern = /^\/question\/\d+$/; const answerPattern = /^\/question\/\d+\/answer\/\d+$/; const articlePattern = /^\/p\/\d+$/; const path = window.location.pathname; if (questionPattern.test(path)) { let record = new Record(); record.type = 'question'; record.title = document.querySelector('.QuestionHeader-title')?.textContent ?? ''; record.url = location.href; addRecord(history, record); saveHistory(history); } else if (answerPattern.test(path)) { let record = new Record(); record.type = 'answer'; const contentItem = document.querySelector('.ContentItem'); const info = JSON.parse(contentItem.dataset.zop); record.authorName = info.authorName; record.title = info.title; const text = contentItem.querySelector('.RichContent')?.textContent ?? ''; record.content = text.length < configurations.maxBriefContentCharacterCount ? text : text.slice(0, configurations.maxBriefContentCharacterCount); record.url = location.href; const imgURL = contentItem.querySelector('.RichContent-inner figure > img')?.getAttribute('data-actualsrc') ?? ''; record.imgURL = imgURL; addRecord(history, record); saveHistory(history); } else if (articlePattern.test(path)) { let record = new Record(); record.type = 'article'; const postContent = document.querySelector('.Post-content'); const info = JSON.parse(postContent.dataset.zop); record.authorName = info.authorName; record.title = info.title; const text = document.querySelector('.Post-RichText')?.textContent ?? ''; record.content = text.length < configurations.maxBriefContentCharacterCount ? text : text.slice(0, configurations.maxBriefContentCharacterCount); record.url = location.href; addRecord(history, record); saveHistory(history); } // 监听用户点击"阅读原文" document.body.addEventListener('click', e => { if (e.target.classList.contains('ContentItem-more') || // 点击"阅读原文"按钮 e.target.classList.contains('RichContent-inner')) { // 点击收缩起来的文本 const contentItem = getAncestorElementByClassName(e.target, 'ContentItem'); const info = JSON.parse(contentItem.dataset.zop); let record = new Record(); record.type = info.type; record.authorName = info.authorName; record.title = info.title; const meta = contentItem.querySelector(':scope > meta[itemprop="url"]'); record.url = meta.getAttribute('content'); let text = contentItem.querySelector('.RichContent-inner')?.textContent ?? ''; // WEIRD: const index = text.indexOf(': '); const begin = index === -1 ? 0 : (index + 2); record.content = text.length < configurations.maxBriefContentCharacterCount ? text.slice(begin) : text.slice(begin, begin + configurations.maxBriefContentCharacterCount); addRecord(history, record); saveHistory(history); } }); injectHistoryElement(); })();