• vue使用marked.js实现markdown转html并提取标题生成目录


    html:

    <template>
        <div class="wrapper">
            <div class="container">
                <div class="menu">
                    <ul class="menu-list">
                        <li v-for="(nav, index) in navList" 
                  :key
    ="index"
                  :class
    ="{on: activeIndex === index}"
                  @click
    ="currentClick(index)"> <a href="javascript:;" @click="pageJump(nav.index)">{{nav.title}}</a> <div v-if="nav.children.length > 0 && activeIndex === index"
                    class
    ="menu-children-list"> <ul class="nav-list"> <li v-for="(item, idx) in nav.children"
                         :key
    ="idx"
                         :class
    ="{on: childrenActiveIndex === idx}"
                         @click.stop
    ="childrenCurrentClick(idx)"> <a href="javascript:;" @click="pageJump(item.index)">
                            
    {{item.title}}
                         
    </a> </li> </ul> </div> </li> </ul> </div> <div class="help-center-content" v-html="compiledMarkdown"
           ref="helpDocs" @scroll="docsScroll"></div> </div> </div> </template>

    js部分:

    <script>
    
    import marked from 'marked';
    
    let rendererMD = new marked.Renderer();
    marked.setOptions({
        renderer: rendererMD,
        gfm: true,
        tables: true,
        breaks: false,
        pedantic: false,
        sanitize: false,
        smartLists: true,
        smartypants: false
    });
    
    export default {
        props: ['mdContent'],
        data() {
            return {
                navList: [],
                activeIndex: 0,
                docsFirstLevels: [],
                docsSecondLevels: [],
                childrenActiveIndex: 0
            }
        },
        mounted() {
            this.navList = this.handleNavTree();
            this.getDocsFirstLevels(0);
        },
        methods: {
            childrenCurrentClick(index) {
                this.childrenActiveIndex = index
            },
            getDocsFirstLevels(times) {
                // 解决图片加载会影响高度问题
                setTimeout(() => {
                    let firstLevels = [];
                    Array.from(document.querySelectorAll('h1'), element => {
                        firstLevels.push(element.offsetTop - 60)
                    })
                    this.docsFirstLevels = firstLevels;
    
                    if (times < 8) {
                        this.getDocsFirstLevels(times + 1);
                    }
                }, 500);
            },
            getDocsSecondLevels(parentActiveIndex) {
                let idx = parentActiveIndex;
                let secondLevels = [];
                let navChildren = this.navList[idx].children
    
                if(navChildren.length > 0) {
                    secondLevels = navChildren.map((item)=>{
                        return this.$el.querySelector(`#data-${item.index}`).offsetTop - 60
                    })
                    this.docsSecondLevels = secondLevels;
                }
            },
            docsScroll() {
                if (this.titleClickScroll) {
                    return;
                }
    
                let scrollTop = this.$refs.helpDocs.scrollTop
                let firstLevelIndex = this.getLevelActiveIndex(scrollTop, this.docsFirstLevels)
                this.currentClick(firstLevelIndex)
    
                let secondLevelIndex = this.getLevelActiveIndex(scrollTop, this.docsSecondLevels)
                this.childrenCurrentClick(secondLevelIndex)
            },
            getLevelActiveIndex(scrollTop, docsLevels) {
                let currentIdx = null;
                let nowActive = docsLevels.some((currentValue, index) => {
                    if(currentValue >= scrollTop) {
                        currentIdx = index
                        return true
                    }
                })
    
                currentIdx = currentIdx - 1
                
                if (nowActive && currentIdx === -1) {
                    currentIdx = 0
                } else if (!nowActive && currentIdx === -1) {
                    currentIdx = docsLevels.length - 1
                }
                return currentIdx     
            },
            pageJump(id) {
                this.titleClickScroll = true;
                this.$refs.helpDocs.scrollTop = this.$el.querySelector(`#data-${id}`).offsetTop - 40;
                setTimeout(() => this.titleClickScroll = false, 100);
            },
            currentClick(index) {
               this.activeIndex = index
               this.getDocsSecondLevels(index)
            },
            getTitle(content) {
                let nav = [];
    
                let tempArr = [];
                content.replace(/(#+)[^#][^
    ]*?(?:
    )/g, function(match, m1, m2) {
                        let title = match.replace('
    ', '');
                        let level = m1.length;
                        tempArr.push({
                            title: title.replace(/^#+/, '').replace(/([^)]*?)/, ''),
                            level: level,
                            children: [],
                        });
                    });
    
                // 只处理一级二级标题,以及添加与id对应的index值
                nav = tempArr.filter(item => item.level <= 2);
                let index = 0;
                return nav = nav.map(item => {
                    item.index = index++;
                    return item;
                });
            },
            // 将一级二级标题数据处理成树结构
            handleNavTree() {
                let navs = this.getTitle(this.content)
                let navLevel = [1, 2];
                let retNavs = [];
                let toAppendNavList;
    
                navLevel.forEach(level => {
                    // 遍历一级二级标题,将同一级的标题组成新数组
                    toAppendNavList = this.find(navs, {
                        level: level
                    });
                    
                    if (retNavs.length === 0) {
                        // 处理一级标题                    
                        retNavs = retNavs.concat(toAppendNavList);
                    } else {
                        // 处理二级标题,并将二级标题添加到对应的父级标题的children中    
                        toAppendNavList.forEach(item => {
                            item = Object.assign(item);
                            let parentNavIndex = this.getParentIndex(navs, item.index);
                            return this.appendToParentNav(retNavs, parentNavIndex, item);
                        });
                    }
                });
                return retNavs;
            },
            find(arr, condition) {
                return arr.filter(item => {
                    for (let key in condition) {
                        if (condition.hasOwnProperty(key) && condition[key] !== item[key]) {
                            return false;
                        }
                    }
                    return true;
                });
            },
            getParentIndex(nav, endIndex) {
                for (var i = endIndex - 1; i >= 0; i--) {
                    if (nav[endIndex].level > nav[i].level) {
                        return nav[i].index;
                    }
                }
            },
            appendToParentNav(nav, parentIndex, newNav) {
                let index = this.findIndex(nav, {
                    index: parentIndex
                });
                nav[index].children = nav[index].children.concat(newNav);
            },
            findIndex(arr, condition) {
                let ret = -1;
                arr.forEach((item, index) => {
                    for (var key in condition) {
                        if (condition.hasOwnProperty(key) && condition[key] !== item[key]) { 
                            return false;
                        }
                    }
                    ret = index;
                });
                return ret;
            },
        },
        computed: {
            content() {
                return this.mdContent
            },
            compiledMarkdown: function() {
                let index = 0;
                rendererMD.heading = function(text, level) {
                    if (level <= 2) {
                        return `<h${level} id="data-${index++}">${text}</h${level}>`;
                    } else {
                        return `<h${level}>${text}</h${level}>`;
                    }
                };
                rendererMD.code = function(code, language) {  
                    code = code.replace(/
    /g,"<br>")
                    code = code.replace(/
    /g,"<br>");
                    return `<div class="text">${code}</div>`;
                };
                return marked(this.content);
            }
        }
    }
    </script>

    参考链接: 

    https://github.com/markedjs/marked

    https://www.jianshu.com/p/d182ea991609

    https://hk.saowen.com/a/bf975e4296e33a14e2d0ad50aa7cbf24fbfb4a9fb851de171b4c71da54eb95e5

  • 相关阅读:
    display:block;inline;inline-block大总结
    img图片inline-block总结
    定时器
    获取样式
    UmiJS
    vue 点击当前路由重新加载该路由
    Support for the experimental syntax 'decorators-legacy' isn't currently enab -- 装饰器@
    js 改变匹配到的字符串的颜色
    with
    页面从输入 URL 到页面加载显示完成
  • 原文地址:https://www.cnblogs.com/yhquan/p/10276811.html
Copyright © 2020-2023  润新知