// ==UserScript== // @name 知乎样式修改器 // @namespace http://tampermonkey.net/ // @version 1.0.1 // @description 知乎样式修改器-支持版心修改,左右模块位置调整、隐藏,夜间模式,logo隐藏,页面标头修改,自定义样式等 // @author super puffer fish // @match *://www.zhihu.com/* // @match *://zhuanlan.zhihu.com/* // @grant none // @run-at document-start // @require https://cdn.jsdelivr.net/npm/jquery@3.2.1/dist/jquery.min.js // @downloadURL none // ==/UserScript== (function () { 'use strict' let pfConfig = { versionHeart: '1000', // version heart positionAnswer: 'right', positionAnswerIndex: '1', // priority positionCreation: 'right', positionCreationIndex: '2', positionTable: 'right', positionTableIndex: '3', positionFavorites: 'right', positionFavoritesIndex: '4', positionFooter: 'right', positionFooterIndex: '5', stickyLeft: false, // left dom is sticky stickyRight: false, // right dom is sticky zoomAnswerImage: 'default', // zoom answer and special column image size hiddenAnswerRightFooter: false, // answer page is hidden right footer hiddenLogo: false, // hidden logo titleIco: '', // the logo at page title title: '', // the title at page title colorBackground: '#ffffff', // background color colorsBackground: [], colorTheme: '#0066ff', colorsTheme: [], customizeCss: '', } // Use location colors config to resolve question about version update colors list is not update const colorsLocation = { colorsBackground: ['#ffffff', '#15202b', '#000000'], colorsTheme: ['#0066ff'] // colorsTheme: ['#fff', '#15202b', '#000'] } // background text on choose tag const backgroundText = { '#ffffff': '默认', '#15202b': '黯淡', '#000000': '纯黑', } let thisPageTitle = '' // cache this page title let cacheColors = {} // cache color list let firstInitColors = true // is first init for color list let positionDoms = {} // cache dom for position let firstInitDoms = true // is first init for position let timeoutToFindCreator = null // timeout to find creator dom const openButton = '' // hidden modal function buttonModalHidden () { $('.pf-mark')[0].style.display = 'none' recoverScroll() } // show modal function buttonModalShow () { $('.pf-mark')[0].style.display = 'block' initScrollModal() stopScroll() } // export file of config bg txt function buttonExportConfig () { const url = 'data:text/csv;charset=utf-8,\ufeff' + encodeURIComponent(localStorage.getItem('pfConfig')) let link = document.createElement('a') link.href = url link.download = '配置.txt' document.body.appendChild(link) link.click() document.body.removeChild(link) } // import config function buttonImportConfig () { const configImport = $('[name=configImport]')[0].value const config = JSON.parse(configImport) pfConfig = formatPfConfig(config) localStorage.setItem('pfConfig', JSON.stringify(pfConfig)) initData() } // init data function initData () { thisPageTitle = document.title for (let even of $('.pf-input')) { // even.value = pfConfig[even.name] switch (even.type) { case 'radio': if (pfConfig[even.name] && even.value === pfConfig[even.name]) { even.checked = true } break case 'checkbox': if (pfConfig[even.name]) { even.checked = true } break } if (even.name === 'title') { even.value = pfConfig.title || document.title } if (even.name === 'customizeCss') { even.value = pfConfig['customizeCss'] } even.onchange = (e) => { switch (e.target.type) { case 'checkbox': throttle(changeConfigByCheckbox(e.target), 300) break case 'radio': case 'text': throttle(changeConfig(e.target), 300) break } } } initPositionPage() changeTitleIco() initColorsList() changeTitle() } // format pfConfig when init or import function getPfConfigAfterFormat (config) { const c = { ...pfConfig, ...config, } // color list is concat from location and storage Object.keys(colorsLocation).forEach((key) => { c[key] = getArrayRemoveSame([].concat(colorsLocation[key], config[key] || [])) }) return c } // arr to remove same one // item is string function getArrayRemoveSame (arr) { const nArr = [] arr.forEach((i) => { if (!nArr.includes(i)) { nArr.push(i) } }) return nArr } // init color list to choose them // can change some color ep:background, theme function initColorsList () { // init cache object when first init if (firstInitColors) { firstInitColors = false Object.keys(pfConfig).forEach((key) => { /^colors/.test(key) && initColorsHtml(key, pfConfig[key]) }) } else { // is not first init Object.keys(pfConfig).forEach((key) => { if (/^colors/.test(key)) { // if new config content is not same to cache colors list, reload this colors html by name pfConfig[key] !== cacheColors[key] && initColorsHtml(key, pfConfig[key]) } }) } } // init html about choose color function initColorsHtml (key, colors) { cacheColors[key] = pfConfig[key] $(`[name="${key}"]`).children().length > 0 && $(`[name="${key}"]`).empty() colors.forEach((item) => { const name = key.replace(/colors/, 'color') const dom = $(``) dom.find('input')[0].checked = item === pfConfig[name] dom.find('input')[0].onchange = (e) => { // throttle(changeConfig(e.target), 300) changeConfig(e.target) } // name is dom`s name of class pf-content $(`[name="${key}"]`).length && $(`[name="${key}"]`).append(dom) }) } // children dom in color html function colorHtmlItem (name, item) { const doms = { 'colorBackground': `${backgroundText[item]}` } return doms[name] || '' } // color reverse, use #ffffff, do not use #fff function colorReverse (OldColorValue) { var OldColorValue = "0x" + OldColorValue.replace(/#/g, "") var str = "000000" + (0xFFFFFF - OldColorValue).toString(16) return '#' + str.substring(str.length - 6, str.length) } // change config by checkbox function changeConfigByCheckbox (ev) { const { name, checked } = ev pfConfig[name] = checked localStorage.setItem('pfConfig', JSON.stringify(pfConfig)) const changerObj = { 'stickyLeft': () => stickyBetween(), 'stickyRight': () => stickyBetween(), 'hiddenAnswerRightFooter': () => changeVersion(), 'hiddenLogo': () => changeVersion(), } changerObj[name] && changerObj[name]() } // change config default (by radio, text) function changeConfig (ev) { const { name, value } = ev pfConfig[name] = value localStorage.setItem('pfConfig', JSON.stringify(pfConfig)) const changerObj = { 'versionHeart': () => changeVersion(), 'zoomAnswerImage': () => changeVersion(), 'titleIco': () => changeTitleIco(), 'colorBackground': () => changeColorBackground(), 'colorTheme': () => changeColorTheme(), 'title': () => changeTitle(), 'customizeCss': () => changeCustomCss() } if (/^position/.test(name)) { initPositionPage() } else { changerObj[name] && changerObj[name]() } } function changeCustomCss () { const cssCustom = `` $('#pf-css-custom') && $('#pf-css-custom').remove() $('head').append(cssCustom) } // change page title function changeTitle () { document.title = pfConfig.title || thisPageTitle } // change icon at page title function changeTitleIco () { const ico = { github: '', csdn: '', juejin: '', zhihu: '', } $('#pf-ico').length && $('#pf-ico').remove() ico[pfConfig.titleIco] && $('head').append(ico[pfConfig.titleIco]) } // init between box function initPositionPage () { if (firstInitDoms) { timeoutToFindCreator = setTimeout(() => { clearTimeout(timeoutToFindCreator) // have creator box if ($('.GlobalSideBar-creator').length) { firstInitDoms = false positionDoms = { positionAnswer: { class: 'GlobalWrite', even: $('.GlobalWrite') }, positionCreation: { class: 'CreatorEntrance', even: $('.GlobalSideBar-creator') }, positionTable: { class: 'GlobalSideBar-category', even: $('.GlobalSideBar-category') }, positionFavorites: { class: 'GlobalSideBar-navList', even: $('.GlobalSideBar-navList') }, positionFooter: { class: 'Footer', even: $('.Footer') }, } } initPositionPage() }, 100) return } // clean between box $('.pf-left-container .Sticky').empty() $('.GlobalSideBar .Sticky').empty() const leftDom = [] const rightDom = [] // append dom Object.keys(positionDoms).forEach((key) => { const e = { even: positionDoms[key].even, index: Number(pfConfig[`${key}Index`]) } if (pfConfig[key] === 'left') { leftDom.push(e) } else if (pfConfig[key] === 'right') { rightDom.push(e) } }) leftDom.sort((a, b) => a.index - b.index) rightDom.sort((a, b) => a.index - b.index) leftDom.forEach(({ even }) => { $('.pf-left-container .Sticky').append(even) }) rightDom.forEach(({ even }) => { $('.GlobalSideBar .Sticky').append(even) }) // detect between box is have children $('.pf-left-container')[0] && ($('.pf-left-container')[0].style.display = $('.pf-left-container .Sticky').children().length > 0 ? 'block' : 'none') $('.GlobalSideBar')[0] && ($('.GlobalSideBar')[0].style.display = $('.GlobalSideBar .Sticky').children().length > 0 ? 'block' : 'none') } // change version function changeVersion () { // the width about question main const qMByVersionHeart = { '1000': '694px', '1200': '894px', '1500': '1194px', } const cssVersion = '' $('#pf-css-version') && $('#pf-css-version').remove() $('head').append(cssVersion) } // change page color add css function changeColorBackground () { const filter = { '#ffffff': { invert: 0, 'hue-rotate': '0' }, '#15202b': { invert: 0.7, 'hue-rotate': '180deg', contrast: 1.7 }, '#000000': { invert: 1, 'hue-rotate': '180deg' }, } const fi = filter[pfConfig.colorBackground] // use filter to reverse color const cssColor = `` $('#pf-css-background') && $('#pf-css-background').remove() $('head').append(cssColor) } function filterObj (fi) { return `filter: ${Object.keys(fi).map((name) => `${name}(${fi[name]})`).join(' ')};` } // change page theme add css function changeColorTheme () { const objBg = getCssTheme() const cssColor = `` $('#pf-css-theme') && $('#pf-css-theme').remove() $('head').append(cssColor) // add css at iframe document.querySelector('.Iframe') && document.querySelector('.Iframe').contentWindow.document.querySelector('head').append(cssColor) } function throttle (fn, timeout = 300) { let canRun = true return function () { if (!canRun) return canRun = false setTimeout(() => { fn.apply(this, arguments) canRun = true }, timeout) } } // prevent background scroll when show modal function stopScroll () { let top = document.body.scrollTop || document.documentElement.scrollTop document.body.style.position = 'fixed' document.body.style.top = `${-1 * top}px` } // restore background scroll when hidden modal function recoverScroll () { let top = -parseInt(document.body.style.top) document.body.style.position = 'static' document.body.style.top = 0 window.scrollTo(0, top) } function stickyBetween () { window.scrollY > 0 ? fixedPosition() : inheritPosition() } function fixedPosition () { if (pfConfig.stickyLeft && $('.pf-left-container')[0]) { $('.pf-left-container .Sticky').css({ width: $('.pf-left-container')[0].offsetWidth, position: 'fixed', left: $('.pf-left-container')[0].offsetLeft, top: $('.pf-left-container')[0].offsetTop, }) } else { $('.pf-left-container .Sticky').removeAttr('style', '') } if (pfConfig.stickyRight && $('.GlobalSideBar')[0]) { $('.GlobalSideBar .Sticky').css({ width: $('.GlobalSideBar')[0].offsetWidth, position: 'fixed', right: $('.GlobalSideBar')[0].offsetRight, top: $('.GlobalSideBar')[0].offsetTop, }) } else { $('.GlobalSideBar .Sticky').removeAttr('style', '') $('.GlobalSideBar .Sticky')[0] && ($('.GlobalSideBar .Sticky')[0].style = 'position: inherit!important') } } function inheritPosition () { $('.pf-left-container .Sticky').removeAttr('style', '') $('.GlobalSideBar .Sticky').removeAttr('style', '') $('.GlobalSideBar .Sticky')[0] && ($('.GlobalSideBar .Sticky')[0].style = 'position: inherit!important') } //hex -> rgba function hexToRgba (hex, opacity) { return 'rgba(' + parseInt('0x' + hex.slice(1, 3)) + ',' + parseInt('0x' + hex.slice(3, 5)) + ',' + parseInt('0x' + hex.slice(5, 7)) + ',' + opacity + ')' } // reverse color at background function reverseCss (color, isImportant = false, name = 'color') { return pfConfig.colorBackground !== '#ffffff' ? `${name}: ${colorReverse(color)}${isImportant ? '!important' : ''};` : '' } // reverse color's content at background function reverseCssCon (color) { return pfConfig.colorBackground !== '#ffffff' ? colorReverse(color) : color } function getCssTheme () { const { colorTheme } = pfConfig return { bg () { return `` }, bg08 () { return `.QuestionType--active, html[data-theme=dark] .QuestionType--active {background: ${hexToRgba(colorTheme, '0.08')}!important;}` }, color () { return `.QuestionType--active, html[data-theme=dark] .QuestionType--active,.QuestionType--active .QuestionType-icon, html[data-theme=dark] .QuestionType--active .QuestionType-icon,.HotListNav-item.is-active,.HotListNav-sortableItem[data-hotlist-identifier=total].is-active, html[data-theme=dark] .HotListNav-sortableItem[data-hotlist-identifier=total].is-active,.TabNavBarItem-tab-MS9i.TabNavBarItem-isActive-1iXL,.pf-open-modal:hover{color: ${colorTheme}!important}` } } } // init html and css, init config function initHtml () { const dom = '' const htmlModal = $(dom) $('.AppHeader-userInfo').prepend(openButton) $('.ColumnPageHeader-Button').prepend(openButton) $('body').append(htmlModal) $('.pf-open-modal')[0] && ($('.pf-open-modal')[0].onclick = buttonModalShow) $('.pf-b-close')[0].onclick = buttonModalHidden $('.pf-export-config')[0].onclick = buttonExportConfig $('.pf-import-config')[0].onclick = buttonImportConfig $('.pf-customize-css-button')[0].onclick = () => changeConfig($('[name="customizeCss"]')[0]) // add left box at home page const leftDom = $('') $('.Topstory-container').prepend(leftDom) $('.QuestionWaiting').prepend(leftDom) initScrollHeader() // initScrollModal() } function initCss () { const cssOwn = '' $('head').append(cssOwn) } // init css or data when document start (function initStartDocument () { const config = localStorage.getItem('pfConfig') const nConfig = config ? JSON.parse(config) : {} pfConfig = getPfConfigAfterFormat(nConfig) initCss() changeVersion() changeColorBackground() changeColorTheme() changeCustomCss() })() // the html and data init when document onload window.onload = () => { initHtml() initData() } window.onscroll = throttle(() => { initScrollHeader() stickyBetween() }, 100) // init dom at page function initScrollHeader () { if ($('.TopstoryPageHeader-aside')[0] && !$('.TopstoryPageHeader-aside .pf-open-modal')[0]) { $('.TopstoryPageHeader-aside').prepend(openButton) $('.TopstoryPageHeader-aside .pf-open-modal')[0] && ($('.TopstoryPageHeader-aside .pf-open-modal')[0].onclick = buttonModalShow) } if ($('.PageHeader.is-shown .QuestionHeader-side')[0] && !$('.PageHeader.is-shown .QuestionHeader-side .pf-open-modal')[0]) { $('.PageHeader.is-shown .QuestionHeader-side').prepend(openButton) $('.PageHeader.is-shown .QuestionHeader-side .pf-open-modal')[0] && ($('.PageHeader.is-shown .QuestionHeader-side .pf-open-modal')[0].onclick = buttonModalShow) } } // init scroll about js modal to find id at function initScrollModal () { const hrefArr = [] for (let i of $('.pf-left a')) { const id = i.href.replace(/.*#/, '') hrefArr.push({ id, offsetTop: $(`#${id}`)[0].offsetTop }) } scrollModal(hrefArr) $('.pf-right')[0].onscroll = throttle(() => scrollModal(hrefArr), 100) } function scrollModal (hrefArr) { const scHere = $('.pf-right')[0].offsetHeight / 2 + $('.pf-right')[0].scrollTop const id = hrefArr.find((item, index) => item.offsetTop <= scHere && ((hrefArr[index + 1] && hrefArr[index + 1].offsetTop > scHere) || !hrefArr[index + 1])).id for (let i of $('.pf-left a')) { i.style = i.href.replace(/.*#/, '') === id ? `color: ${pfConfig.colorTheme}` : '' } } })()