Format a webpage on zhihu.com so that web clippers such as Evernote Web Clipper can save only useful information.
// ==UserScript== // @name 知乎重排for印象笔记 // @name:en Zhihu Formatter // @namespace http://tampermonkey.net/ // @version 1.3.3 // @description 重新排版知乎的网页,使例如"印象笔记·剪藏"的web clippers只保存需要的内容。 // @description:en Format a webpage on zhihu.com so that web clippers such as Evernote Web Clipper can save only useful information. // @author twchen // @match https://www.zhihu.com/question/*/answer/* // @match https://zhuanlan.zhihu.com/p/* // @match https://www.zhihu.com/pin/* // @run-at document-idle // @inject-into auto // @grant GM.xmlHttpRequest // @grant GM_xmlhttpRequest // @grant GM.getValue // @grant GM_getValue // @grant GM.setValue // @grant GM_setValue // @grant GM_addStyle // @connect lens.zhihu.com // @connect api.zhihu.com // @connect www.zhihu.com // @connect unpkg.zhimg.com // @supportURL https://github.com/twchen/zhihu-formatter/issues // ==/UserScript== // GM 4 API polyfill if (typeof GM_addStyle == "undefined") { this.GM_addStyle = css => { const style = document.createElement("style"); style.textContent = css; document.documentElement.appendChild(style); return style; }; } 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; } GM.asyncHttpRequest = args => { return new Promise((resolve, reject) => { GM.xmlHttpRequest({ ...args, onload: resolve, onerror: response => { reject({ message: `Status:${response.status}. StatusText: ${response.statusText}`, }); }, }); }); }; function createElement(type, props, ...children) { const element = document.createElement(type); Object.entries(props || {}).forEach(([name, value]) => { if (name.startsWith("on")) { const eventName = name.slice(2); element.addEventListener(eventName, value); } else if (name == "style" && typeof value !== "string") { Object.assign(element.style, value); } else { element.setAttribute(name, value); } }); children .map(child => typeof child === "string" ? document.createTextNode(child) : child ) .forEach(child => element.appendChild(child)); return element; } // for debugging // unsafeWindow.GM = GM; (async function () { "use strict"; GM_addStyle(` .fmt-comments-container { box-sizing: border-box; border: 1px solid rgb(235, 235, 235); border-radius: 4px; align-items: stretch; margin-top: 1em; padding: 1.2em; display: flex; flex-direction: column; } .fmt-comments-count { font-size: 15px; font-weight: 600; padding-bottom: 1em; border-bottom: 1px solid rgb(235, 235, 235); } .fmt-root-comment-container { display: flex; flex-direction: column; padding-bottom: 1em; border-top: 1px solid rgb(235, 235, 235); } .fmt-comment { padding-top: 1em; display: flex; flex-direction: row; } .fmt-content-column { display: flex; flex-direction: column; margin-left: 0.5em; } .fmt-author-row { font-weight: 600; } .fmt-comment-content { margin: 0.5em 0; color: rgb(68, 68, 68); } .fmt-comment-info { color: rgb(153, 153, 153); } .fmt-emoticon { width: 1.4em; height: 1.4em; margin-bottom: -0.3em; } .fmt-comment-img { display: block; max-width: 10em; max-height: 20em; } `); 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 button = createElement("button", { type: "button", class: "Button Button--blue", style: { "margin-right": "1rem", }, onclick: formatZhuanlan, }); button.innerText = "重新排版"; pageHeader.prepend(button); } 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(".QuestionHeader-title") .cloneNode(true); const question = createElement( "div", { style: { backgroundColor: "white", margin: "0.8rem 0", padding: "0.2rem 1rem 1rem", borderRadius: "2px", boxShadow: "0 1px 3px rgba(26,26,26,.1)", }, }, title ); const detail = document.querySelector( ".QuestionHeader-main .QuestionRichText" ); if (detail) question.appendChild(detail.cloneNode(true)); const answer = document .querySelector("div.Card.AnswerCard") .cloneNode(true); // remove non-working actions const actions = answer.querySelector(".ContentItem-actions"); actions.style.display = "none"; div = createElement( "div", { id: "formatted", }, question, answer ); const answerContent = answer.querySelector(".RichContent"); const answerId = window.location.href.substring( window.location.href.lastIndexOf("/") + 1 ); const commentsUrl = `https://www.zhihu.com/api/v4/comment_v5/answers/${answerId}/root_comment?order_by=score&limit=20&offset=`; renderComments(commentsUrl, answerContent); root.after(div); window.history.pushState("formatted", ""); postprocess(div); } async 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); const titleImage = document.querySelector(".TitleImage"); div = createElement("div", { id: "formatted", style: { padding: "1rem", "background-color": "white", }, }); if (titleImage) { const img = (await getRealImage(titleImage)) || titleImage.cloneNode(true); div.appendChild(img); } div.append(header, post, time, topics); const articleId = window.location.href.substring( window.location.href.lastIndexOf("/") + 1 ); const commentsUrl = `https://www.zhihu.com/api/v4/comment_v5/articles/${articleId}/root_comment?order_by=score&limit=20&offset=`; renderComments(commentsUrl, div); 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 either the original pin or the repost pin has non-text content (video/image), not both. // 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); } }); const comments = div.querySelector(".Comments-container"); comments.style.display = "none"; const pinId = window.location.href.substring( window.location.href.lastIndexOf("/") + 1 ); const commentsUrl = `https://www.zhihu.com/api/v4/comment_v5/pins/${pinId}/root_comment?order_by=score&limit=20&offset=`; renderComments(commentsUrl, div); root.after(div); root.style.display = "none"; window.history.pushState("formatted", ""); fixLinks(div); convertEquations(div); } async function replaceThumbnailsByRealImages(preview) { try { const groups = /^\/pin\/(\d+)/.exec(window.location.pathname); const pinId = groups[1]; const response = await GM.asyncHttpRequest({ method: "GET", url: "https://api.zhihu.com/pins/" + pinId, }); const pinInfo = JSON.parse(response.responseText); const content = (pinInfo.origin_pin || pinInfo).content; const images = await Promise.all( content .filter(item => item.type === "image") .map(item => createImgFromURL(item.url)) ); const div = createElement("div", {}, ...images); preview.replaceWith(div); } catch (error) { console.error(`Error getting all images: ${error.message}`); } } function replaceVideosByLinks(el) { let videoDivs = el.querySelectorAll(".RichText-video"); if (el.classList.contains("RichText-video")) { videoDivs = [...videoDivs, el]; } const newTitle = createElement("div", { style: { margin: "0.5rem auto" } }); newTitle.innerText = "视频"; videoDivs.forEach(async div => { try { const attr = div.attributes["data-za-extra-module"]; const videoId = JSON.parse(attr.value).card.content.video_id; const href = "https://www.zhihu.com/video/" + videoId; const response = await GM.asyncHttpRequest({ method: "GET", url: "https://lens.zhihu.com/api/videos/" + videoId, headers: { "Content-Type": "application/json", Origin: "https://v.vzuu.com", Referer: "https://v.vzuu.com/video/" + videoId, }, }); const videoInfo = JSON.parse(response.responseText); const thumbnail = videoInfo.cover_info.thumbnail; const layout = div.querySelector(".VideoCard-layout"); layout.style.textAlign = "center"; layout.prepend(newTitle.cloneNode(true)); const title = layout.querySelector(".VideoCard-title"); if (title) { layout.children[0].innerText = "视频:" + title.innerText; title.parentNode.remove(); } const video = layout.querySelector(".VideoCard-video"); const a = createElement( "a", { href: href, style: { width: "100%" } }, createElement("img", { src: thumbnail, style: { "max-width": "100%" }, }) ); video.replaceWith(a); } catch (error) { console.error(`Error getting video info: ${error.message}`); } }); } function enableGIF(div) { try { const src = div.querySelector("img").src; const i = src.lastIndexOf("."); const img = createElement("img", { src: src.slice(0, i + 1) + GIF_EXT, style: { maxWidth: "100%", display: "block", margin: "auto", }, }); div.replaceWith(img); } catch (error) { console.error(`Error enabling gif: ${error.message}`); } } function getAttrValOfAnyDOM(el, attr) { const res = el.querySelector(`*[${attr}]`) || el; return res.getAttribute(attr); } function getAttrValFromNoscript(el, attr) { let noscripts = el.querySelectorAll("noscript"); const re = new RegExp(`${attr}="(.*?)"`); if (el.tagName === "NOSCRIPT") { noscripts = [...noscripts, el]; } for (let i = 0; i < noscripts.length; ++i) { const nos = noscripts[i]; const content = nos.textContent || nos.innerText || nos.innerHTML; if (content) { const groups = re.exec(content); if (groups) { return groups[1]; } } } return null; } function getAttrVal(el, attr) { return getAttrValOfAnyDOM(el, attr) || getAttrValFromNoscript(el, 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 && IMG_SRC_REG_EX.test(src)); return imgSrcs.length > 0 ? await createImgFromURL(imgSrcs[0]) : null; } async function createImgFromURL(url) { const suffix = QUALITY_TO_SUFFIX[await settings.get("imageQuality")]; const image = new ZhihuImage(url, suffix); const img = createElement("img", { src: image.next(), style: { maxWidth: "100%", display: "block", margin: "1rem auto", cursor: "pointer", }, onclick: () => { img.src = image.next(); }, onmouseover: hint.show, onmouseleave: hint.hide, }); return img; } // 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"); el ? el.replaceWith(img) : figure.prepend(img); } } }); } function fixLinks(el) { el.querySelectorAll("a").forEach(a => { // fix redirect links const groups = REDIRECT_LINK_REG_EX.exec(a.href); if (groups) { a.href = decodeURIComponent(groups[1]); } // fix links with hidden texts const spans = a.querySelectorAll( ":scope > span.invisible, :scope > span.visible, :scope > span.ellipsis" ); if (spans.length === a.children.length) { a.innerHTML = a.innerText.length > LINK_TEXT_MAX_LEN ? a.innerText.slice(0, LINK_TEXT_MAX_LEN) + "..." : a.innerText; } }); } async function convertEquation(img) { const canvas = createElement("canvas", { width: EQ_IMG_SCALING_FACTOR * img.width, height: EQ_IMG_SCALING_FACTOR * img.height, }); const ctx = canvas.getContext("2d"); ctx.fillStyle = "white"; ctx.fillRect(0, 0, canvas.width, canvas.height); Object.assign(img.style, { width: img.width + "px", height: img.height + "px", }); // 直接用img会出现因为cross origin而导致的"Tainted canvases may not be exported"错误 // 如果window.location.href不是www.zhihu.com/*的话才会出现 const response = await GM.asyncHttpRequest({ method: "GET", url: img.src, }); const svgXML = response.responseText; const svgImg = createElement("img", { src: "data:image/svg+xml," + encodeURIComponent(svgXML), onload: () => { ctx.drawImage(svgImg, 0, 0, canvas.width, canvas.height); img.src = canvas.toDataURL("image/png"); }, }); } // Equations are converted to PNG images by the clipper, but the images have low resolutions // This function converts equations to PNG images in higher resolutions. function convertEquations(el) { const equationImgs = el.querySelectorAll( 'img[src^="https://www.zhihu.com/equation"]' ); equationImgs.forEach(img => { const id = setInterval(() => { if (img.complete) { clearInterval(id); convertEquation(img); } }, 100); }); } function postprocess(el) { replaceVideosByLinks(el); loadAllFigures(el); fixLinks(el); convertEquations(el); } class ZhihuImage { constructor(src, defaultSuffix) { const groups = IMG_SRC_REG_EX.exec(src); this.prefix = groups[1]; this.ext = groups[3]; this.i = SUFFIX.indexOf(defaultSuffix); if (this.i === -1) this.i = 0; } next() { const src = `${this.prefix}_${SUFFIX[this.i]}.${this.ext}`; if (++this.i === SUFFIX.length) this.i = 0; return src; } } async function getComments(url, numPages) { let comments = []; let currURL = url; for (let i = 0; numPages < 0 || i < numPages; ++i) { const obj = await getJson(currURL); comments = comments.concat(obj.data); if (obj.paging.is_end) break; currURL = obj.paging.next; } return comments; } async function getRootComments(url) { const rootCommentsObjs = await getComments( url, parseInt(await settings.get("numRootCommentPages")) ); for (const obj of rootCommentsObjs) { if (obj.child_comment_count > obj.child_comments.length) { const childrenURL = `https://www.zhihu.com/api/v4/comment_v5/comment/${obj.id}/child_comment?order_by=ts&limit=20&offset=`; obj.child_comments = await getComments( childrenURL, parseInt(await settings.get("numChildCommentPages")) ); } } return rootCommentsObjs; } function formatDate(date) { const str = date.toISOString(); return str.substring(0, 10) + " " + str.substring(11, 16); } function createCommentNode(comment, isChild, stickers) { const authorURL = `https://www.zhihu.com/people/${comment.author.id}`; const authorAvatar = createElement( "div", { class: "fmt-author-avatar" }, createElement( "a", { href: authorURL }, createElement("img", { src: comment.author.avatar_url, style: { width: "1.5em", height: "1.5em", }, }) ) ); const authorRowNodes = [ createElement("a", { href: authorURL }, comment.author.name), ]; if ("reply_to_author" in comment) { authorRowNodes.push( document.createTextNode(" > "), createElement( "a", { href: `https://www.zhihu.com/people/${comment.reply_to_author.id}`, }, comment.reply_to_author.name ) ); } const authorRow = createElement( "div", { class: "fmt-author-row" }, ...authorRowNodes ); const content = createElement("div", { class: "fmt-comment-content" }); content.innerHTML = comment.content.replace( /\[.*?\]/g, (match, offset, string) => { if (match in stickers) { return `<img src="${stickers[match]}" class="fmt-emoticon">`; } else { return match; } } ); content .querySelectorAll(".comment_sticker, .comment_img") .forEach(stickerLink => { const stickerImg = createElement("img", { class: "fmt-comment-img", src: stickerLink.href, }); stickerLink.innerText = ""; stickerLink.appendChild(stickerImg); }); const formattedDate = formatDate(new Date(comment.created_time * 1000)); const infoStrings = [formattedDate]; for (const tag of comment.comment_tag) { if (tag.type === "ip_info") { infoStrings.push(tag.text); break; } } infoStrings.push(`${comment.like_count}个赞同`); const info = createElement( "div", { class: "fmt-comment-info" }, infoStrings.join(" · ") ); const contentColumn = createElement( "div", { class: "fmt-content-column" }, authorRow, content, info ); const node = createElement( "div", { class: isChild ? "fmt-comment" : "fmt-root-comment fmt-comment", style: { marginLeft: isChild ? "2em" : "0", }, }, authorAvatar, contentColumn ); return node; } async function renderComments(commentsUrl, parent) { if (parseInt(await settings.get("numRootCommentPages")) == 0) return; const rootComments = await getRootComments(commentsUrl); const stickers = await getStickers(); let numComments = 0; let commentNodes = []; for (const comment of rootComments) { numComments += 1 + comment.child_comments.length; const rootCommentNode = createCommentNode(comment, false, stickers); const childCommentNodes = comment.child_comments.map(childComment => createCommentNode(childComment, true, stickers) ); const rootCommentContainer = createElement( "div", { class: "fmt-root-comment-container", }, rootCommentNode, ...childCommentNodes ); commentNodes.push(rootCommentContainer); } const commentsContainer = createElement( "div", { class: "fmt-comments-container", }, createElement( "div", { class: "fmt-comments-count" }, `${numComments} 条评论` ), ...commentNodes ); parent.appendChild(commentsContainer); } async function getJson(url) { const resp = await GM.asyncHttpRequest({ method: "GET", url: url, }); return JSON.parse(resp.responseText); } const getStickers = (() => { let stickers = null; return async () => { if (stickers !== null) return stickers; if (window.zh_emoticon === undefined) { const resp = await GM.asyncHttpRequest({ method: "GET", url: "https://unpkg.zhimg.com/@cfe/emoticon/lib/emoticon.js", }); eval(resp.responseText); } stickers = {}; for (const group of window.zh_emoticon) { for (const sticker of group.stickers) { if (sticker.placeholder in stickers) continue; stickers[sticker.placeholder] = sticker.static_image_url; } } return stickers; }; })(); class Settings { constructor() { this.settings = {}; this.div = null; const cornerButtons = document.querySelector(".CornerButtons"); if (cornerButtons) { const button = createElement("button", { type: "button", class: "Button CornerButton Button--plain", "data-tooltip": "设置知乎重排", "data-tooltip-position": "left", }); button.innerHTML = SETTING_ICON_HTML; button.onclick = this.show.bind(this); const div = createElement( "div", { class: "CornerAnimayedFlex" }, 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 = createElement("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); if (options.length == 0) { const input = createElement("input", { id: key, type: "text", name: key, onchange: event => { this.set(key, event.target.value); }, }); input.value = await this.get(key); this.div.appendChild(input); } else if ( options.length === 2 && options.includes(true) && options.includes(false) ) { // the setting is binary const checkbox = createElement("input", { type: "checkbox", onchange: event => { this.set(key, event.target.checked); }, }); if (await this.get(key)) { checkbox.setAttribute("checked", "checked"); } this.div.appendChild(checkbox); } else { const savedOption = await this.get(key); for (let option of options) { const radio = createElement("input", { id: `${key}-${option}`, type: "radio", name: key, value: option, onchange: () => { this.set(key, option); }, }); if (option === savedOption) { radio.setAttribute("checked", "checked"); } this.div.appendChild(radio); const label = createElement("label", { for: `${key}-${option}` }); label.innerText = option; this.div.appendChild(label); } } } if (this.div.children.length > 0) { const closeBtn = createElement("span", { style: { position: "absolute", top: "0.5rem", right: "0.5rem", cursor: "pointer", }, onclick: this.close.bind(this), }); closeBtn.innerText = "X"; this.div.appendChild(closeBtn); document.body.appendChild(this.div); } } close() { if (this.div !== null) { this.div.remove(); this.div = null; } } } // global constants const QUALITY_TO_SUFFIX = { 原始: "r", 高清: "hd", 缩略: "b", }; const EQ_IMG_SCALING_FACTOR = 2; // scaling factor for equation images const IMG_SRC_REG_EX = /^(https?:\/\/.+[a-z0-9]{32})(_\w+)?\.(\w+)/; const REDIRECT_LINK_REG_EX = /https?:\/\/link\.zhihu\.com\/\?target=(.*)/i; const SUFFIX = ["r", "hd", "b"]; //const SUFFIX = ["r", "hd", "b", "xl", "t", "l", "m", "s"]; const GIF_EXT = "gif"; // can be changed to .webp, but Evernote does not support it. const LINK_TEXT_MAX_LEN = 50; const SETTING_ICON_HTML = `<svg t="1567388644978" viewBox="0 0 #### ####" 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>`; // global variables const root = document.querySelector("#root"); const hint = createElement("div", { style: { display: "none", position: "fixed", backgroundColor: "white", border: "1px solid black", }, }); hint.innerText = "点击图片更换分辨率(如有)"; hint.show = event => { hint.style.display = "block"; hint.style.top = event.clientY + "px"; hint.style.left = event.clientX + 3 + "px"; }; hint.hide = () => { hint.style.display = "none"; }; let settings; function main() { // inject format button/link const url = window.location.href; if (url.includes("zhuanlan")) addBtnToNav(); else if (url.includes("answer")) addLinkToNav(formatAnswer); else addLinkToNav(formatPin); settings = new Settings(); settings.add_setting( "imageQuality", "默认图片质量", ["原始", "高清", "缩略"], "原始" ); settings.add_setting( "numRootCommentPages", "最大评论页数 (每页20个评论, -1表示无限制, 0表示不存评论)", [], "0" ); settings.add_setting( "numChildCommentPages", "最大子评论页数 (每页20个评论, -1表示无限制, 0表示不存评论)", [], "0" ); // handle backward/forward events 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"; } }); document.body.append(hint); } setTimeout(main, 1500); })();