您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
重新排版知乎的问答,专栏或想法,使“印象笔记·剪藏”只保存需要的内容。
当前为
// ==UserScript== // @name 知乎重排for印象笔记 // @namespace http://tampermonkey.net/ // @version 0.14 // @description 重新排版知乎的问答,专栏或想法,使“印象笔记·剪藏”只保存需要的内容。 // @author twchen // @include https://www.zhihu.com/question/*/answer/* // @include https://zhuanlan.zhihu.com/p/* // @include https://www.zhihu.com/pin/* // @run-at document-idle // @inject-into auto // @grant GM.xmlHttpRequest // @grant GM_xmlhttpRequest // @grant GM_registerMenuCommand // @grant GM.getValue // @grant GM_getValue // @grant GM.setValue // @grant GM_setValue // @connect lens.zhihu.com // @supportURL https://twchen.github.io/zhihu-formatter // ==/UserScript== /** * 更新日志 * v0.10 * 1. 适应新专栏封面 * 2. 缩小专栏标题字体 * 3. 新功能:重排后可返回原页面 * * v0.10.2 * 1. 适应新导航栏 * * v0.11 * 1. 图片居中 * 2. 保留figcaption * 3. 增加默认图片质量配置 * * v0.12 * 1. 新增设置界面 * 2. 支持重排想法 * * v0.13 * 1. 把重定向链接改为直链 * 2. 解决一些链接在印象笔记客户端无法点击的问题 * * v0.14 * 1. 兼容Greasemonkey 4 API * 2. 在右下角新增设置按钮 */ // GM 4 API polyfill if (typeof GM == "undefined") { this.GM = {}; [["getValue", GM_getValue], ["setValue", GM_setValue]].forEach(([newFunc, oldFunc]) => { GM[newFunc] = (...args) => { return new Promise((resolve, reject) => { try { resolve(oldFunc(...args)); } catch (error) { reject(error); } }); }; }); GM.xmlHttpRequest = GM_xmlhttpRequest; } (function() { "use strict"; const SETTING_ICON_HTML = `<svg t="1567388644978" viewBox="0 0 1024 1024" version="1.1" p-id="1116" width="24" height="24" fill="currentColor"> <defs><style type="text/css"></style></defs> <path d="M1020.1856 443.045888c-4.01408-21.634048-25.494528-43.657216-47.776768-48.529408l-16.662528-3.702784c-39.144448-11.49952-73.873408-36.640768-95.955968-73.670656-22.081536-37.225472-27.300864-79.517696-17.665024-118.301696l5.219328-15.20128c6.62528-21.049344-2.00704-50.087936-19.472384-64.510976 0 0-15.657984-12.862464-59.82208-37.614592-44.164096-24.556544-63.235072-31.378432-63.235072-31.378432-21.479424-7.600128-51.591168-0.38912-67.249152 15.787008l-11.64288 12.0832c-29.710336 27.285504-69.658624 43.851776-113.82272 43.851776-44.164096 0-84.513792-16.760832-114.224128-44.240896l-11.241472-11.69408C371.177472 49.74592 340.865024 42.534912 319.3856 50.13504c0 0-19.27168 6.821888-63.435776 31.378432-44.164096 24.946688-59.621376 37.810176-59.621376 37.810176-17.46432 14.227456-26.09664 43.071488-19.472384 64.315392l4.81792 15.396864c9.435136 38.784 4.416512 80.88064-17.665024 118.106112-22.08256 37.225472-57.212928 62.56128-96.559104 73.865216l-16.059392 3.508224C29.308928 399.388672 7.6288 421.21728 3.61472 443.045888c0 0-3.613696 19.488768-3.613696 68.992 0 49.504256 3.613696 68.993024 3.613696 68.993024 4.01408 21.828608 25.494528 43.657216 47.776768 48.529408l15.657984 3.508224c39.346176 11.303936 74.677248 36.639744 96.759808 74.059776 22.081536 37.225472 27.300864 79.517696 17.665024 118.301696l-4.617216 15.00672c-6.62528 21.049344 2.00704 50.087936 19.472384 64.510976 0 0 15.657984 12.862464 59.82208 37.614592 44.164096 24.751104 63.235072 31.377408 63.235072 31.377408 21.479424 7.601152 51.591168 0.390144 67.249152-15.785984l11.040768-11.49952c29.91104-27.480064 70.060032-44.240896 114.424832-44.240896 44.3648 0 84.714496 16.956416 114.424832 44.43648l11.040768 11.49952c15.45728 16.175104 45.769728 23.387136 67.249152 15.785984 0 0 19.27168-6.821888 63.435776-31.378432 44.164096-24.751104 59.621376-37.614592 59.621376-37.614592 17.46432-14.227456 26.09664-43.267072 19.472384-64.509952l-4.81792-15.592448c-9.435136-38.58944-4.416512-80.68608 17.665024-117.715968 22.08256-37.225472 57.413632-62.756864 96.759808-74.0608l15.65696-3.508224c22.08256-4.872192 43.762688-26.7008 47.777792-48.528384 0 0 3.613696-19.489792 3.613696-68.993024-0.200704-49.698816-3.8144-69.187584-3.8144-69.187584zM512.100352 710.2464c-112.617472 0-204.157952-88.677376-204.157952-198.208512 0-109.335552 91.339776-198.012928 204.157952-198.012928 112.617472 0 204.157952 88.677376 204.157952 198.208512C716.0576 621.568 624.717824 710.2464 512.100352 710.2464z" p-id="1117"></path> </svg>`; class Settings { constructor() { this.settings = {}; this.div = null; if (typeof GM_registerMenuCommand !== "undefined") { GM_registerMenuCommand("设置", this.show.bind(this)); } const cornerButtons = document.querySelector(".CornerButtons"); if (cornerButtons) { const div = document.createElement("div"); div.classList.add("CornerAnimayedFlex"); const button = document.createElement("button"); button.classList.add("Button", "CornerButton", "Button--plain"); button.setAttribute("type", "button"); button.setAttribute("data-tooltip", "设置知乎重排"); button.setAttribute("data-tooltip-position", "left"); button.innerHTML = SETTING_ICON_HTML; button.onclick = this.show.bind(this); div.appendChild(button); cornerButtons.prepend(div); } } async get(key) { return await GM.getValue(key, this.settings[key].defaultOption); } async set(key, value) { return await GM.setValue(key, value); } add_setting(key, desc, options, defaultOption) { this.settings[key] = { desc, options, defaultOption }; } async show() { this.close(); this.div = document.createElement("div"); Object.assign(this.div.style, { position: "fixed", top: "50%", left: "50%", transform: "translate(-50%, -50%)", backgroundColor: "white", border: "1px solid black", borderRadius: "5px", padding: "0.8rem", width: "24rem", zIndex: 999 }); for (let key in this.settings) { if (this.div.children.length > 0) { this.div.appendChild(document.createElement("br")); } const { desc, options } = this.settings[key]; const descSpan = document.createElement("span"); descSpan.innerText = `${desc}: `; this.div.appendChild(descSpan); // the setting is binary if (options.length === 2 && options.includes(true) && options.includes(false)) { const checkbox = document.createElement("input"); checkbox.type = "checkbox"; if (await this.get(key)) { checkbox.setAttribute("checked", "checked"); } checkbox.onchange = event => { this.set(key, event.target.checked); }; this.div.appendChild(checkbox); } else { const savedOption = await this.get(key); for (let option of options) { const radio = document.createElement("input"); const id = `${key}-${option}`; radio.id = id; radio.type = "radio"; radio.setAttribute("name", key); radio.setAttribute("value", option); if (option === savedOption) { radio.setAttribute("checked", "checked"); } radio.onchange = () => { this.set(key, option); }; this.div.appendChild(radio); const label = document.createElement("label"); label.setAttribute("for", id); label.innerText = option; this.div.appendChild(label); } } } if (this.div.children.length > 0) { const closeBtn = document.createElement("span"); closeBtn.innerText = "X"; closeBtn.onclick = this.close.bind(this); Object.assign(closeBtn.style, { position: "absolute", top: "0.5rem", right: "0.5rem", cursor: "pointer" }); this.div.appendChild(closeBtn); document.body.appendChild(this.div); } } close() { if (this.div !== null) { this.div.remove(); this.div = null; } } } const root = document.querySelector("#root"); function addLinkToNav(onclick) { const nav = document.querySelector(".AppHeader-Tabs"); const li = nav.querySelector("li").cloneNode(true); const a = li.querySelector("a"); a.href = "#"; a.text = "重排"; a.onclick = event => { event.preventDefault(); onclick(); }; nav.appendChild(li); } function addBtnToNav() { const pageHeader = document.querySelector("div.ColumnPageHeader-Button"); const btn = document.createElement("button"); btn.setAttribute("type", "button"); btn.innerText = "重新排版"; btn.classList.add("Button", "Button--blue"); btn.style.marginRight = "1rem"; btn.onclick = formatZhuanlan; pageHeader.prepend(btn); } function formatAnswer() { root.style.display = "none"; let div = document.querySelector("#formatted"); if (div !== null) { div.remove(); } const showMoreBtn = document.querySelector("button.Button.QuestionRichText-more"); if (showMoreBtn !== null) showMoreBtn.click(); const title = document.querySelector("h1.QuestionHeader-title").cloneNode(true); const detail = document.querySelector("div.QuestionHeader-detail").cloneNode(true); const question = document.createElement("div"); question.appendChild(title); question.appendChild(detail); Object.assign(question.style, { backgroundColor: "white", margin: "0.8rem 0", padding: "0.2rem 1rem 1rem", borderRadius: "2px", boxShadow: "0 1px 3px rgba(26,26,26,.1)" }); const answer = document.querySelector("div.Card.AnswerCard").cloneNode(true); // remove non-working actions const actions = answer.querySelector(".ContentItem-actions"); actions.parentNode.removeChild(actions); div = document.createElement("div"); div.id = "formatted"; div.appendChild(question); div.appendChild(answer); // insert after root root.after(div); window.history.pushState("formatted", ""); postprocess(div); } function formatZhuanlan() { root.style.display = "none"; let div = document.querySelector("#formatted"); if (div !== null) { div.remove(); } const header = document.querySelector("header.Post-Header").cloneNode(true); const title = header.querySelector(".Post-Title"); Object.assign(title.style, { fontSize: "1.5rem", fontWeight: "bold", marginBottom: "1rem" }); const post = document.querySelector("div.Post-RichText").cloneNode(true); const time = document.querySelector("div.ContentItem-time").cloneNode(true); const topics = document.querySelector("div.Post-topicsAndReviewer").cloneNode(true); div = document.createElement("div"); div.id = "formatted"; let titleImage = document.querySelector(".TitleImage"); if (titleImage) { titleImage = titleImage.cloneNode(true); titleImage.style.maxWidth = "100%"; titleImage.style.cursor = "pointer"; titleImage.title = "点击删除图片"; titleImage.onclick = () => { div.removeChild(titleImage); }; div.appendChild(titleImage); } div.append(header, post, time, topics); div.style.margin = "1rem"; root.after(div); window.history.pushState("formatted", ""); postprocess(div); } async function formatPin() { let div = document.querySelector("#formatted"); if (div !== null) { div.remove(); } const pinItem = document.querySelector(".PinItem"); div = pinItem.cloneNode(true); div.id = "formatted"; div.style.margin = "1rem"; const remainContents = div.querySelectorAll(".PinItem-remainContentRichText"); remainContents.forEach(remainContent => { // assume only one .PinItem-remainContentRichText has a video or some images, otherwise the code may not run correctly. if (remainContent.querySelector(".RichText-video")) { // show video replaceVideosByLinks(remainContent); } const preview = remainContent.querySelector(".Image-Wrapper-Preview"); if (preview) { // show all images replaceThumbnailsByRealImages(preview); } }); if ( (await settings.get("keepComments")) === "否" || div.querySelector(".CommentListV2") === null || div.querySelector(".CommentListV2").children.length === 0 ) { const comments = div.querySelector(".Comments-container"); comments.style.display = "none"; } else { // hide the comment editor const commentEditor = div.querySelector(".CommentEditorV2--normal"); commentEditor.style.display = "none"; // get all remaining comments if ((await settings.get("keepComments")) === "此页后全部") { const commentsClone = div.querySelector(".CommentListV2"); (async () => { while (true) { const comments = await getNextPageComments(root); if (comments === null) break; for (let i = 0; i < comments.children.length; ++i) { commentsClone.appendChild(comments.children[i].cloneNode(true)); } } })(); } } root.after(div); root.style.display = "none"; window.history.pushState("formatted", ""); fixLinks(div); } async function replaceThumbnailsByRealImages(preview) { const realImages = document.createElement("div"); const thumbnails = document.querySelectorAll("#root .Thumbnail-Wrapper"); for (let i = 0; i < thumbnails.length; ++i) { const img = await getRealImage(thumbnails[i]); if (img) realImages.appendChild(img); } try { const surplusSign = document.querySelector("#root .Thumbnail-Surplus-Sign"); if (surplusSign !== null) { const numLeft = parseInt(surplusSign.innerText); thumbnails[thumbnails.length - 1].click(); const imageGallery = document.querySelector(".ImageGallery-Inner"); const arrowRight = document.querySelector("a.ImageGallery-arrow-right"); for (let i = 0; i < numLeft; ++i) { arrowRight.click(); const img = await getRealImage(imageGallery); if (img !== null) realImages.appendChild(img); } const close = document.querySelector("a.ImageGallery-close"); close.click(); } } catch (error) { console.error(`Error retrieving remaining images: ${error.message}`); } realImages.querySelectorAll("img").forEach(img => { Object.assign(img.style, { maxWidth: "100%", display: "block", margin: "1rem auto" }); }); preview.replaceWith(realImages); } function getNextPageComments(root) { return new Promise((resolve, reject) => { const nextPage = root.querySelector(".PaginationButton-next"); if (nextPage === null) { resolve(null); } else { nextPage.click(); const startTime = new Date().getTime(); const id = setInterval(() => { if (new Date().getTime() - startTime > 3000) { clearInterval(id); reject(new Error("Timeout")); return; } const comments = root.querySelector(".CommentListV2"); if (comments === null) return; clearInterval(id); resolve(comments); }, 100); } }); } function replaceVideosByLinks(el) { const videoDivs = el.querySelectorAll("div.RichText-video"); videoDivs.forEach(async div => { let href, title, thumbnail; try { const attr = div.attributes["data-za-extra-module"]; const videoId = JSON.parse(attr.value).card.content.video_id; href = "https://www.zhihu.com/video/" + videoId; const info = await getVideoInfo(videoId); title = info.title; thumbnail = info.thumbnail; } catch (error) { console.error(`Error getting video info: ${error.message}`); } if (!href) { return; } const a = document.createElement("a"); a.href = href; Object.assign(a.style, { color: "#337ab7", textDecoration: "underline" }); if (!title && !thumbnail) { a.innerText = "视频: " + href; } else { const span = document.createElement("span"); span.style.display = "block"; span.innerText = "视频: "; a.appendChild(span); if (title) { span.innerText += title; } if (thumbnail) { const img = document.createElement("img"); img.src = thumbnail; img.style.maxWidth = "100%"; a.appendChild(img); } } div.replaceWith(a); }); } function getVideoInfo(id) { return new Promise((resolve, reject) => { GM.xmlHttpRequest({ method: "GET", url: `https://lens.zhihu.com/api/videos/${id}`, headers: { "Content-Type": "application/json", Origin: "https://v.vzuu.com", Referer: `https://v.vzuu.com/video/${id}` }, onload: response => { try { const json = JSON.parse(response.responseText); const title = json.title; const thumbnail = json.cover_info.thumbnail; resolve({ title, thumbnail }); } catch (error) { reject(error); } }, onerror: response => { reject({ message: `Status: ${response.status}. StatusText: ${response.statusText}` }); } }); }); } function enableGIF(div) { try { const src = div.querySelector("img").src; const img = document.createElement("img"); const i = src.lastIndexOf("."); img.src = src.slice(0, i) + ".gif"; div.replaceWith(img); } catch (error) { console.error(`Error enabling gif: ${error.message}`); } } function getAttrValOfAnyDOM(root, attr) { const el = root.querySelector(`*[${attr}]`); return el ? el.getAttribute(attr) : null; } function getAttrValFromNoscript(div, attr) { const nos = div.querySelector("noscript"); let value = null; if (nos) { const content = nos.textContent || nos.innerText || nos.innerHTML; if (content) { const pattern = `${attr}="(.*?)"`; const re = new RegExp(pattern); const groups = content.match(re); if (groups) { value = groups[1]; } } } return value; } function getAttrVal(div, attr) { return getAttrValOfAnyDOM(div, attr) || getAttrValFromNoscript(div, attr); } async function getRealImage(el) { const imgSrcAttrs = ["data-original", "data-actualsrc", "data-src", "src"]; let imgSrcs = imgSrcAttrs .map(attr => getAttrVal(el, attr)) .filter(src => src != null && !src.toLowerCase().startsWith("data:")); // find unique filenames const filename2Src = {}; imgSrcs.forEach(src => { const groups = src.split("/"); const filename = groups[groups.length - 1]; filename2Src[filename] = src; }); imgSrcs = Object.values(filename2Src); if (imgSrcs.length > 0) { const img = document.createElement("img"); const suffix = quality2Suffix[await settings.get("imageQuality")]; let i = 0; while (i < imgSrcs.length && !imgSrcs[i].includes(suffix)) ++i; if (i === imgSrcs.length) i = 0; img.src = imgSrcs[i]; if (imgSrcs.length > 1) { img.onclick = () => { if (++i === imgSrcs.length) i = 0; img.src = imgSrcs[i]; }; img.onmouseover = event => { hint.style.display = "block"; hint.style.top = event.clientY + "px"; hint.style.left = event.clientX + 3 + "px"; }; img.onmouseleave = () => { hint.style.display = "none"; }; img.style.cursor = "pointer"; } return img; } else { return null; } } // enable all gifs and load images function loadAllFigures(el) { const figures = el.querySelectorAll("figure"); figures.forEach(async figure => { const gifDiv = figure.querySelector("div.RichText-gifPlaceholder"); if (gifDiv !== null) { enableGIF(gifDiv); } else { const img = await getRealImage(figure); if (img) { const el = figure.querySelector("img") || figure.querySelector("noscript"); if (el) { el.replaceWith(img); } else { figure.prepend(img); } } } const imgs = figure.querySelectorAll("img"); imgs.forEach(img => { Object.assign(img.style, { maxWidth: "100%", display: "block", margin: "auto" }); }); }); } function fixLinks(el) { const re = /https?:\/\/link\.zhihu\.com\/\?target=(.*)/i; const as = el.querySelectorAll("a"); as.forEach(a => { // fix indirect links const groups = re.exec(a.href); if (groups) { a.href = decodeURIComponent(groups[1]); } // fix links with hidden texts const ellipsis = a.querySelector(":scope > span.ellipsis"); if (ellipsis) { a.innerHTML = a.innerText.length > LINK_TEXT_MAX_LEN ? a.innerText.slice(0, LINK_TEXT_MAX_LEN) + "..." : a.innerText; } }); } function postprocess(el) { replaceVideosByLinks(el); loadAllFigures(el); fixLinks(el); } function injectToNav() { const url = window.location.href; if (url.includes("zhuanlan")) addBtnToNav(); else if (url.includes("answer")) addLinkToNav(formatAnswer); else addLinkToNav(formatPin); } injectToNav(); const hint = document.createElement("div"); hint.innerText = "点击图片换不同分辨率(如有)"; Object.assign(hint.style, { display: "none", position: "fixed", backgroundColor: "white", border: "1px solid black" }); const LINK_TEXT_MAX_LEN = 50; document.body.append(hint); window.addEventListener("popstate", function(event) { const div = document.querySelector("#formatted"); if (event.state === "formatted") { root.style.display = "none"; div.style.display = "block"; } else { root.style.display = "block"; div.style.display = "none"; } }); const settings = new Settings(); settings.add_setting("imageQuality", "默认图片质量", ["原始", "高清"], "原始"); settings.add_setting( "keepComments", "重排想法时保留评论", ["否", "仅此页", "此页后全部"], "仅此页" ); const quality2Suffix = { 原始: "_r.", 高清: "_hd." }; })();