My知乎目录

知乎长文目录,方便阅读。

目前為 2019-06-23 提交的版本,檢視 最新版本

// ==UserScript==
// @name         My知乎目录
// @namespace    http://tampermonkey.net/
// @version      1.0.0
// @description  知乎长文目录,方便阅读。
// @author       空空叶
// @match        https://www.zhihu.com
// @grant        none
// @require http://cdn.staticfile.org/jquery/3.4.1/jquery.min.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 Article = function({id, $root}) {
        this.id = id
        this.$root = $root
        this.data = {}
        /**
         * 类型:
         * Question问题(忽略)
         * Post
         * Answer
         */
        // this.type
        this.titles = []
        this.titlesMap = {}

        $root.css('border-radius', '5px')
        $root.css('border', 'dashed 1px transparent')
        $root.css('margin', '-1px')

        $root.hover(() => toggleCurrentArticle(this), () => {})

        //文章数据
        let $ContentItem = $('.ContentItem', $root)
        let data = $ContentItem.attr('data-zop')
        this.data = JSON.parse(data)


        // let $Feed = $('.Feed', $root)
        // let data = $Feed.attr('data-za-extra-module')
        // data = JSON.parse(data)
        // this.type = data.card.content.type

        // if (this.type === 'Post' || this.type === 'Answer') {
            let $RichText = $('.RichText', $root)
            $('h1,h2,h3,h4,h5,h6', $RichText).each((index, $title) => {
                $title = $($title)
                let id = Utils.guid()
                let title = {
                    id: id,
                    ele: $title,
                    content: $title.text()
                }
                this.titles.push(title)
                this.titlesMap[id] = title
            })
            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 = $('<div style="padding: 5px 10px;display: flex;flex-direction: column;"></div>')
        //标题
        let titleLine = $('<div style="align-self: center;text-align: center;">'+currentArticle.data.title+'</div>')
        titleLine.css('color', Consts.textColorSel)
        titleBarContent.append(titleLine)
        //到顶部
        let toTop = $('<a href="javascript: void(0)" style="align-self: center;margin-top: 5px;">↑到顶部↑</a>')
        toTop.on('click', () => Utils.goToByScroll(currentArticle.$root))
        toTop.hover(() => toTop.css('color', Consts.textColorSel), () => toTop.css('color', Consts.textColorUnSel))
        titleBarContent.append(toTop)
        //中间标题
        currentArticle.titles.forEach(item => {
            let titleEle = $('<a href="javascript: void(0)" ' +
                'style="border-radius: 5px;border: solid 1px lightgrey;padding: 5px 10px;margin-top: 5px;"' +
                '>'+item.content+'</a>')
            titleEle.hover(() => {
                titleEle.css('border-style', 'inset')
                titleEle.css('color', Consts.textColorSel)
            }, () => {
                titleEle.css('border-style', 'solid')
                titleEle.css('color', Consts.textColorUnSel)
            })
            titleEle.on('click', () => Utils.goToByScroll(item.ele))
            titleBarContent.append(titleEle)
        })
        //到底部
        let toBottom = $('<a href="javascript: void(0)" style="align-self: center;margin-top: 5px;margin-bottom: 5px;">↓到底部↓</a>')
        toBottom.on('click', () => Utils.goToEndByScroll(currentArticle.$root))
        toBottom.hover(() => toBottom.css('color', Consts.textColorSel), () => toBottom.css('color', Consts.textColorUnSel))
        titleBarContent.append(toBottom)

        //更新标题内容
        titleBar.children().remove()
        titleBar.append(titleBarContent)
        titleBar.show()
    }

    /**
     * 刷新
     */
    let refresh = () => {
        console.debug('[刷新]')
        //得到打开的问题列表
        let $TopstoryItems = $('.TopstoryItem').filter((index, ele) => {
            let RichContent = $('.RichContent', ele)
            return RichContent && RichContent.length > 0 && !RichContent.hasClass('is-collapsed')
        })

        //全部初始化
        let activeIds = {}
        $TopstoryItems.each((index, ele) => {
            let id = init($(ele))
            if (id) {
                activeIds[id] = true
            }
        })
        articles.removeIf(e => {
            if (!activeIds[e.id]) {
                //注销事件
                e.$root.off()
                return true
            }
        })
        articlesMap = {}
        articles.forEach(article => articlesMap[article.id] = article)
        //检测刷新当前文章
        if (currentArticle && !articlesMap[currentArticle.id]) {
            toggleCurrentArticle(null)
        }
        //日志
        console.debug('[打开的文章]', articles)
    }

    /**
     * 初始化
     * @param $TopstoryItem 打开的项
     * @return id
     */
    let init = $TopstoryItem => {
        let id = $TopstoryItem.attr('id')
        //已经有id了
        if (id) {
            if (articlesMap[id]) {//缓存有效
                console.debug('[已经有缓存了]', id)
                return id
            }else {
                //缓存失效了
            }
        }

        //生成id
        id = Utils.guid()
        //设置
        $TopstoryItem.attr('id', id)
        try { //生成文章
            let article = new Article({
                id,
                $root: $TopstoryItem
            })
            //添加缓存
            articles.push(article)
            articlesMap[id] = article
            //日志
            console.info('[初始化]', id, article, $TopstoryItem)
            return id
        } catch (e) {
            console.debug('[初始化失败]', $TopstoryItem)
        }
    }

    /**
     * 获取有指定类名的父类
     * @param element 当前元素
     * @param className 类名
     * @return 未找到返回null(如果本身就有此类名,则返回本身)
     */
    let _getParent = (element, className) => {
        //元素为null
        if (!element || $(element).length === 0) {
            return null
        }

        //本身
        if ($(element).hasClass(className)) {
            return $(element)
        }

        //父
        return _getParent($(element).parent(), className)
    }

    $(document).click(({target}) => {
        console.debug('[点击]', target)
        setTimeout(refresh)
    })

    //ready时运行
    $(() => {
        //计算头部高度
        appHeaderHeight = $('.AppHeader').height()

        //初始化标题条
        let GlobalSideBar = $('.GlobalSideBar')
        let topBottomMargin = appHeaderHeight+30;
        titleBar = $('<div id="tagsTitleBar" style="position: fixed;top: '+topBottomMargin+'px;bottom: '+topBottomMargin+'px;background-color: rgba(255, 255, 255, 0.95);z-index: 180;opacity: 0.5;border-radius: 5px;overflow-y: auto;box-shadow: rgba(152, 152, 152, 0.5) 0px 1px 3px;word-break: break-word;"></div>')
        titleBar.css('color', Consts.textColorUnSel)
        titleBar.width(GlobalSideBar.width())
        titleBar.hover(() => {
            titleBar.css('opacity', '1')
        }, () => {
            titleBar.css('opacity', '0.5')

        })
        titleBar.hide()
        GlobalSideBar.append(titleBar)
    })
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址