// ==UserScript== // @name My知乎目录 // @namespace https://github.com/kongkongye/zh-category // @version 1.1.0 // @description 知乎长文目录,方便阅读。 // @author 空空叶 // @match https://www.zhihu.com/* // @license Apache // @require http://cdn.staticfile.org/jquery/3.4.1/jquery.min.js // @downloadURL https://update.greasyfork.icu/scripts/386722/My%E7%9F%A5%E4%B9%8E%E7%9B%AE%E5%BD%95.user.js // @updateURL https://update.greasyfork.icu/scripts/386722/My%E7%9F%A5%E4%B9%8E%E7%9B%AE%E5%BD%95.meta.js // ==/UserScript== (function() { 'use strict'; /** * 文章列表 */ let articles = [] let articlesMap = {} /** * 当前文章,可以没有 */ let currentArticle /** * 标题条 */ let titleBar /** * 最顶端那条的高度 */ let appHeaderHeight = 0; (() => { Array.prototype.removeIf = function(callback) { let i = 0; while (i < this.length) { if (callback(this[i], i)) { this.splice(i, 1); } else { ++i; } } }; })() /** * 常量 */ let Consts = { textColorSel: '#76839b',//文字选中颜色 textColorUnSel: '#8590a6',//文字未选中颜色 } /** * 实用类 */ let Utils = { // Generate four random hex digits. S4: () => (((1+Math.random())*0x10000)|0).toString(16).substring(1), // Generate a pseudo-GUID by concatenating random hexadecimal. guid: () => (Utils.S4()+Utils.S4()+"-"+Utils.S4()+"-"+Utils.S4()+"-"+Utils.S4()+"-"+Utils.S4()+Utils.S4()+Utils.S4()), //获取属性 getAttr: (ele, key) => $(ele).attr(key), //设置属性 setAttr: (ele, key, value) => $(ele).attr(key, value), //滚动 goToByScroll: ele => { $('html,body').animate({ scrollTop: $(ele).offset().top - appHeaderHeight }, 'slow'); }, goToEndByScroll: ele => { $('html,body').animate({ scrollTop: $(ele).offset().top + $(ele).height() - $(window).height() }, 'slow'); }, }; /** * 不同页面适配器 */ let Wrappers = { /** * 得到打开的问题列表 */ getArticleRoots: () => { let result let path = window.location.pathname if (path === '/' || path === '/follow') {//首页&关注 result = $('.TopstoryItem') }else if (path === '/search') {//搜索页 result = $('.SearchResult-Card') }else if (path.startsWith("/question")) { if (path.indexOf("answer") !== -1) {//问题的指定回答 //指定回答&更多回答 result = $('.AnswerCard,.List-item') }else {//问题的所有回答 result = $('.List-item') } }else if (path === '/topic' || path === '/explore') {//话题 result = $('.feed-item').filter((index, ele) => { let RichText = $('.zm-item-rich-text', ele) if (RichText && RichText.length > 0) { let children = RichText.children() if (children && children.length > 0 && children[0].nodeName.toLowerCase() === 'div' && $(children[0]).css('display') !== 'none') { return true } } return false }) return result }else { console.debug('[此页面无法解析]', path) } if (result) { result = result.filter((index, ele) => { let RichContent = $('.RichContent', ele) return RichContent && RichContent.length > 0 && !RichContent.hasClass('is-collapsed') }) } return result }, /** * 尝试解析文章数据 * @param $root 文章根元素 * @return Article,解析不出返回null */ parseArticle: $root => { let id = Utils.guid() let title //文章标题 //解析 let path = window.location.pathname if (path === '/' || path === '/follow') {//首页&关注 let $ContentItem = $('.ContentItem', $root) let data = JSON.parse($ContentItem.attr('data-zop')) title = data.title }else if (path === '/search') {//搜索页 let $ContentItemTitle = $('.ContentItem-title', $root) let $nameMeta = $('meta[itemprop="name"]', $ContentItemTitle) title = $nameMeta.attr('content') }else if (path.startsWith("/question")) { //不需要显示标题 }else if (path === '/topic' || path === '/explore') {//话题&发现(旧) let $questionLink = $('.question_link', $root) title = $questionLink.text() }else { console.debug('[此页面无法解析]', path) return null } return new Article({ id, title, $root, }) }, /** * 获取头部条 */ getHeader: () => { let path = window.location.pathname if (path === '/topic' || path === '/explore') {//话题&发现(旧) return $('.zu-top') }else {//其他(新) return $('.AppHeader') } }, /** * 获取侧边栏 */ getSideBar: () => { let path = window.location.pathname if (path === '/' || path === '/follow') {//首页&关注 return $('.GlobalSideBar') }else if (path === '/search') {//搜索页 return $('.SearchSideBar') }else if (path.startsWith("/question")) { return $('.Question-sideColumn') }else if (path === '/topic' || path === '/explore') {//话题&发现(旧) return $('.zu-main-sidebar') }else { console.debug('[此页面无法解析]', path) } }, /** * 获取文本容器 */ getTextWrapper: $root => { let path = window.location.pathname if (path === '/topic' || path === '/explore') {//话题&发现(旧) return $('.zm-item-rich-text', $root) }else {//其他(新) return $('.RichText', $root) } } } /** * 文章类 */ let Article = function({id, title, $root}) { this.id = id this.title = title //可以为null this.$root = $root this.titles = [] this.titlesMap = {} $root.on('mousemove', () => toggleCurrentArticle(this)) let $TextWrapper = Wrappers.getTextWrapper($root) $('h1,h2,h3,h4,h5,h6', $TextWrapper).each((index, $title) => { $title = $($title) let id = Utils.guid() let titleEle = { id: id, ele: $title, content: $title.text() } this.titles.push(titleEle) this.titlesMap[id] = titleEle }) console.debug(this.titles) } /** * 切换当前的文章 * @param article 可为null */ let toggleCurrentArticle = article => { if (currentArticle === article) { return } console.debug('[切换当前文章]', article) currentArticle = article refreshTitleBar() } /** * 刷新标题条(根据当前文章) */ let refreshTitleBar = () => { //当前没有文章 if (!currentArticle) { titleBar.hide() return } //有标题 let titleBarContent = $('
') //标题 if (currentArticle.title) { let titleLine = $('