文章11
标签16
分类0
Hexo踩坑记录

Hexo踩坑记录

💻渲染器

其实主要也就三种,自带的 hexo-renderer-marked,插件丰富的 hexo-renderer-markdown-it,还有自定义选项的文档写得特别好的 hexo-renderer-showdown

自带的以前对gfm支持不太行(现在不知道咋样了),marked本身也不太优雅,所以换用 markdown-it,乱七八糟的插件也多,就很舒服。

那么为什么不用 showdown 呢?

因为它直接报错啊

Options

三种引擎的一些选项(在_config.yml内),以防以后用得到,懒得再查文档,先直接xjb粘贴:

# hexo-renderer-marked config
# see https://marked.js.org/using_advanced#options
marked:
  gfm: true #  Enables GitHub flavored markdown
  pedantic: false # Conform to obscure parts of `markdown.pl` as much as possible. Don't fix any of the original markdown bugs or poor behavior.
  breaks: true # Enable GFM line breaks. This option requires the `gfm` option to be true.
  smartLists: true # Use smarter list behavior than the original markdown.
  smartypants: true # Use "smart" typograhic punctuation for things like quotes and dashes.
  modifyAnchors: 0 # Transform the anchorIds into lower case (`1`) or upper case (`2`).
  autolink: true # Enable autolink for URLs. E.g. `https://hexo.io` will become `<a href="https://hexo.io">https://hexo.io</a>`.
  sanitizeUrl: false # Remove URLs that start with `javascript:`, `vbscript:` and `data:`.
  headerIds: true # Insert header id, e.g. `<h1 id="value">text</h1>`. Useful for inserting anchor link to each paragraph with a heading.
  prependRoot: false # Prepend root value to (internal) image path. 
                     ##     Example `_config.yml`:
                     ###                          `root: /blog/`
                     ##     ![text](/path/to/image.jpg) becomes <img src="/blog/path/to/image.jpg" alt="text">
  external_link: # 
    enable: true # Open external links in a new tab.
    exclude: [] # Exclude hostname. Specify subdomain when applicable, including `www`. 
                ##      Example:`[foo](http://bar.com) becomes <a href="http://bar.com" target="_blank" rel="noopener">foo</a>`
    nofollow: true # Add rel="noopener external nofollow noreferrer" to all external links for security, privacy and SEO.
                    # This can be enabled regardless of `external_link.enable`
                    ##      Example: `[foo](http://bar.com)` becomes `<a href="http://bar.com" rel="noopener external nofollow noreferrer">foo</a>`

# hexo-renderer-markdown-it config
markdown:
  preset: 'default'
  render:
    html: true # Whether or not HTML content inside the document should be escaped or passed to the final result.
    xhtmlOut: false # Whether the parser will export fully XHTML compatible tags. This only needs to be true for complete [CommonMark] support.
    langPrefix: 'language-'
    breaks: true # Whether the parser will produces `<br>` tags every time there is a line break in the source document.
    linkify: false # Whether the parser will returns text links as proper links inlined with the paragraph.
    typographer: true # Whether enables the substitution for common typography elements like ©, curly quotes, dashes, etc.
    quotes: '“”‘’' # "double" will be turned into “double”, 'single' will be turned into ‘single’
  enable_rules:
  disable_rules:
  plugins:
    - name: 'markdown-it-task-checkbox' # checkbox插件
      options:
        liClass: task-checkbox-list-item # 用于CSS选择
    - name: 'markdown-it-emoji' # 使用emoji表情
# Automatic Headline ID's
# Enables you to automatically create ID's for the headings so you can link back to them. 
# A valid html document cannot have two elements with duplicate id value, 
# for example if title id is already used, 
# subsequent title values will be updated to title-2, title-3 and so on.
  anchors:
    level: 2 # Minimum level for ID creation. (Ex. h2 to h6)
    collisionSuffix: '' # A suffix that is prepended to the number given if the ID is repeated.
    permalink: false # If `true`, creates an anchor tag with a permalink besides the heading.
    permalinkClass: 'header-anchor' # Class used for the permalink anchor tag.
    permalinkSide: 'left' # Set to 'right' to add permalink after heading
    permalinkSymbol: '¶' # The symbol used to make the permalink
    case: 0 # Transform anchor to (1) lower case; (2) upper case
    separator: '-' # Replace space with a character
# hexo-renderer-showdown config
# see https://github.com/showdownjs/showdown/wiki/Showdown-Options#valid-options
markdown:
  "[tasklists**]": true # 注:key值内有*号,不清楚hexo是如何解析的,可能并不需要"[]"包裹

markdown-it 最好还得附带装上两插件,一个是 markdown-it-task-checkbox,一个是 markdown-it-emoji,npm库里都有,直接装就完事。

插件

checkbox/check lists/todo list的支持

其实也就是 - [ ] 或者 - [x]

marked将gfm设为true后即可支持,而markdown-it需要安装插件并启用才能支持。一般用的是 markdown-it-checkbox 或者 markdown-it-task-lists,前者比较正常,后者很蛋疼的把 - [ ]- [x] 换成了 [ ][x]和 hexo-asset-image 有着异曲同工之妙

但是无论是 marked 开启 gfm 还是 markeddown-it 加插件,渲染出来的样式都会在复选框前加一个点(根据浏览器不同渲染样式也不一样),略微蛋疼。

翻了一通没找到什么好使的插件,干脆曲线救国,找了个叫 markdown-it-task-checkbox 的插件,它相比 markdown-it-checkbox 有多那么几个可以设置的选项,方便折腾,就不需要动它的源码了。

Screenshot_2021-01-08 linsir markdown-it-task-checkbox

接下来自然就是人民群众喜闻乐见的曲线救国环节:

在plugins内添加插件,然后修改生成的列表项的class,因为担心可能和其他冲突,导致那些没有 checkbox 的无序列表也失去小圆点,所以不使用原来的默认值 task-list-item,而是改成 task-checkbox-list-item,同理 task-list 也一并改成 task-checkbox-list

plugins:
  - name: 'markdown-it-task-checkbox' # checkbox插件
    options:
      ulClass: task-checkbox-list # 用于CSS选择
      liClass: task-checkbox-list-item # 用于CSS选择

列表转成html后大概这样:

<ul class="task-checkbox-list">
    <li class="task-checkbox-list-item"><input type="checkbox" id="cbx_0" checked="true" disabled="true"><label for="cbx_0"> 测试文字</label></li>
    <li class="task-checkbox-list-item"><input type="checkbox" id="cbx_1" disabled="true"><label for="cbx_1"> 测试文字2</label></li>
</ul>

然后再改一下主题的样式文件(source/css/style.styl),把 task-checkbox-list-item 类的样式改掉:

.task-checkbox-list-item {
    list-style: none;
}

修改前bfr
修改后aft

发现圆点虽然不见了,但是还是占了位置,于是再暴力一回,让它缩回去:

.task-checkbox-list {
    /* 原本继承的值为 0 0 0 2em */
    padding: 0 0 0 1em;
}

修改后aft2

🎄主题

nexmoe/mako

原版:theme-nexmoe/hexo-theme-nexmoe

修改版:DogTorrent/hexo-theme-mako

请求合并

众所周知,把多个请求合并成一个可以大大提升加载速度,所以原repo在一次优化过程中将本来暴露在 _config.yml 中的各种js/css链接全部写死在了ejs模板文件内:

split

这样做虽然不太优雅,但是其实一般也不会影响到什么。问题就在于写死的代码高亮主题css不知道为何,遇到yaml就嗝屁了,一片灰色,于是只能修改所用的主题。但是如果每次我要修改主题都要翻找一遍ejs文件,那也未免太过麻烦了…

好吧,动手。

先把 _config.yaml 里的键加回来,因为全都是走jsdelivr,所以后面的操作也变得很简单:

cdn: # 这里可以修改站点使用的库的CDN
  mdui:
    css: https://cdn.jsdelivr.net/npm/mdui@1.0.1/dist/css/mdui.min.css
    js: https://cdn.jsdelivr.net/npm/mdui@1.0.1/dist/js/mdui.min.js
  lazysizes:
    js: https://cdn.jsdelivr.net/npm/lazysizes@5.3.0/lazysizes.min.js
  highlight:
    css: https://cdn.jsdelivr.net/npm/highlight.js@10.5.0/styles/darcula.css
    js: https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@10.5.0/build/highlight.min.js
  fancybox:
    css: https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.css
    js: https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.js
  jquery:
    js: https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js

然后在script/helper文件夹添加一个辅助函数,用正则匹配一下jsdelivr的域名,全部合并了:

'use strict';

function combineJsd(strList) {
    var cdnCombineJs = "";
    var cdnCombineCss = "";
    var result = "";
    var regex = "https://cdn.jsdelivr.net/";
    for (let str of strList) {
        if (str.endsWith('.js')) {
            if (str.startsWith(regex)) {
                if (!cdnCombineJs) {
                    cdnCombineJs = "https://cdn.jsdelivr.net/combine/" + str.replace(regex,"");
                } else { 
                    cdnCombineJs += str.replace(regex,",")
                } 
            } else { 
                result += `<script src="${str}"></script> \n `;
            }
        } else if (str.endsWith('.css')) {
            if (str.startsWith(regex)) {
                if (!cdnCombineCss) {
                    cdnCombineCss = "https://cdn.jsdelivr.net/combine/" + str.replace(regex,"");
                } else { 
                    cdnCombineCss += str.replace(regex,",")
                } 
            } else { 
                result += `<link rel="stylesheet" href="${str}" crossorigin> \n `;
            }
        }
    }
    if (cdnCombineJs) { 
        result += `<script src="${cdnCombineJs}"></script> \n `;
    }
    if (cdnCombineCss) {
        result += `<link rel="stylesheet" href="${cdnCombineCss}" crossorigin> \n `;
    }
    return result.trim();
}
hexo.extend.helper.register('combine_jsdelivr_cdn', combineJsd);

接着是css,找到 layout.ejs,删掉原来的,调用前面的辅助函数:
(这里不合并mdui,因为合并后mdui.min.css在找自带的Roboto字体时会找不到)

<% var cssList = [theme.cdn[theme.highlight].css, theme.cdn.fancybox.css] -%>
<%- combine_jsdelivr_cdn(cssList); %>

js同理,找到 after-footer.ejs ,照猫画虎:
(这里也不合并Prism,因为Prism-core和autoloader合并到一起时会出错)

<% 
var jsList = [theme.cdn.mdui.js, theme.cdn.lazysizes.js, theme.cdn.jquery.js, theme.cdn.fancybox.js, theme.cdn.pjax.js];
jsList.push.apply(jsList, theme.cdn.more.js);
-%>
<%- combine_jsdelivr_cdn(jsList); %>

好耶

代码高亮

除去hexo自带的高亮,代码高亮其实主要有两个引擎,一个是hljs,一个是Prism,hljs主题比较丰富,而Prism高亮得比较规范

  • EJS

    有点无语,highlight.js居然没有支持EJS的高亮,翻了一下github发现了这个:eta-dev/highlightjs-eta,好吧

    把eta改成ejs,加几个别名,然后直接往js文件夹丢:

    hljs.registerLanguage('ejs', function () {
        "use strict";
        return function (e) {
            return {
                name: 'EJS',
                aliases:["embeddedjs","eta"],
                subLanguage: 'xml',
                contains: [{
                    begin: '<%[-_]?\\s*?(~|=)?',
                    end: '[-_]?%>',
                    subLanguage: 'javascript',
                    excludeBegin: !0,
                    excludeEnd: !0
                }]
            }
        }
    }());
    

    这样子其实还是会遇到很多高亮不了的情况,那么还有更暴力更sb的方法吗?

    你好,有的:

    // tmd暴力高亮
    hljs.registerLanguage('ejs-quote', function () {
        "use strict";
        return function (e) {
            return {
                className: 'ejs-quote',
                name: 'ejs-quote',
                begin:/<%[-_=]?/,
                end:/[-_]?%>/,
                subLanguage: 'javascript',
            }
        }
    }());
    hljs.registerLanguage('ejs', function () {
        "use strict";
        return function (e) {
            return {
                name: 'ejs',
                className: 'ejs',
                aliases:["embeddedjs"],
                subLanguage: [
                    'xml',
                    'ejs-quote'
                ]
            }
        }
    }());
    

    当然,先缩一下,变成min版,然后在 initHighlightingOnLoad() 前添加它:

    <script src="/js/ejs.min.js"></script>
    <script>hljs.initHighlightingOnLoad();</script>
    

    不过,这样子治标不治本,能高亮的代码有限,除非自己重新写一份规则,或者直接换用Prism来高亮。

pjax支持

  • 添加pjax

    眼馋pjax挺久了,最近现学了点JS和jQuery的知识,于是打算动手给博客的主题加上pjax。

    pjax其实就是ajax加上了pushstate,主要有两种,一种是要依赖jQuery的 defunkt/jquery-pjax,一种是纯JS的 MoOx/pjax。但是由于jQuery太过臃肿,博客一直用的是slim版的,经过实际测试slim版的jQuery加上jquery-pjax会报错,所以还是选择无依赖版的好了。

    先把cdn链接放进配置文件:

    pjax:
      js: https://cdn.jsdelivr.net/npm/pjax@0.2.8/pjax.min.js
    

    然后在after-footer.ejs里更新一下要组合的js列表,加上pjax:

    - var jsList = [theme.cdn.mdui.js, theme.cdn.highlight.js, theme.cdn.lazysizes.js, theme.cdn.jquery.js, theme.cdn.fancybox.js]
    + var jsList = [theme.cdn.mdui.js, theme.cdn.highlight.js, theme.cdn.lazysizes.js, theme.cdn.jquery.js, theme.cdn.fancybox.js, theme.cdn.pjax.js]
    

    分析一下页面的结构,原本的nexmoe主题已经把需要更新的部分全部放进 <div id="nexmoe-content"></div> 内了,很舒服。

    image-20210117152204532

    那么直接冲就完事了,把a标签都选上,更新id为nexmoe-content的部分:

    var pjax = new Pjax({
        elements: "a[href]:not([href^='#'])", // default is "a[href], form[action]"
        selectors: ["#nexmoe-content"]
    })
    
  • JS加载修复

    果不其然,评论系统炸了,同时炸的还有识别当前页面的代码、高亮代码的脚本、图片灯箱代码…

    不急,先整()个刷新js的函数,把pjax-reload类的脚本全部重新加载。为了防止浏览器用缓存,在src后加个 ?v=[时间] (其实没什么所谓,应该没影响):

    function reloadJs(scriptsToBeReload=document.querySelectorAll("script.pjax-reload, .pjax-reload script")){
        for (var element of scriptsToBeReload) {
            var id = element.id || "";
            var src = element.src || "";
            var code = element.text || element.textContent || element.innerHTML || "";
            var parent = element.parentNode;
            var script = document.createElement("script");
            var className = element.className || "";
            parent.removeChild(element);
            if (id !== "") { script.id = element.id; }
            if (className !== "") { script.className=className; }
            if (src !== "") { script.src = src.replace(/\?v=[0-9]*/i,"") + "?v=" + Number(new Date()); }
            if (code !== "") { script.appendChild(document.createTextNode(code)); }
            parent.appendChild(script);
        }
    }
    

    然后把其他写在app.js内的代码也用函数封装起来(因为不好直接刷新app.js):

    function setListItemClass() {...}
    setListItemClass();
    function addNexmoeAlbumClass() {...}
    addNexmoeAlbumClass();
    function highlightCodes() {
        if(typeof Prism != "undefined") {
            Prism.highlightAll();
        }else if(typeof hljs != "undefined") {
            document.querySelectorAll('pre code').forEach((block) => {
                hljs.highlightBlock(block);
            });
        }
    }
    highlightCodes();
        ...
    

    然后放一个事件监听器,页面变化完成后重新执行这些函数和脚本:

    document.addEventListener('pjax:complete', function(){
        reloadJs();
        highlightCodes();
        setListItemClass();
        addNexmoeAlbumClass();
    });
    
  • 评论系统修复

    valine炸得很彻底,原来的nexmoe的逻辑是:comment.ejs加载_comment文件夹下的不同评论系统的ejs文件,以实现在页底评论block下方直接嵌入评论系统的js代码。但是pjax可不管这些,刷新后内部的script标签直接消失,那么script标签只能放在pjax刷新的块的外面,为此只能大改了。

    先把_comment文件夹给删了,原地建一个_after-footer文件夹,然后在_after-footer文件夹内放不同评论系统的脚本(以ejs模板文件的形式,只放script标签,因为一些参数需要从config内读取)。其实实现的也就是将评论系统的script放到#nexmoe-content外(after-footer),同时保持评论系统的块仍在#nexmoe-content内。

    因为自己用的是valine,所以先做它的,其他评论系统有空再说。

    在_after-footer文件夹内新建valine.ejs,主要做两个修改(参考 #138 ):

    1. 把av-min.js单独拿出来,这个脚本内部有些东西不能重复定义,否则会出错
    2. 为防止评论串页,新增path项,设置为window.location.pathname
    <script src="//cdn1.lncld.net/static/js/3.0.4/av-min.js"></script>
    <script src='<%- theme.cdn.valine.js %>'></script>
    <script class='pjax-reload'>
        var option = {
            el: '.valine',
            appId: '<%= theme.valine.appId %>',
            appKey: '<%= theme.valine.appKey %>',
            placeholder: '<%= theme.valine.placeholder %>',
            visitor: <%= theme.valine.visitor %>,
            verify: <%= theme.valine.verify %>,
            recordIP: <%= theme.valine.recordIP %>,
            requiredFields: '<%= theme.valine.requiredFields %>'.split(','),
            path: window.location.pathname,
        };
        function load_valine() {
            var valine = new Valine(option);
        };
        load_valine();
    </script>
    

    然后修改一下comment.ejs:

    <section class="nexmoe-comment">
    -    <%- partial('_comment/' + theme.comment) %>
    +    <div class="<%= theme.comment %>" id="<%= theme.comment %>"></div>
    </section>
    

    最后修改after-footer.ejs,在加载app.js前把_after-footer文件夹里的模板文件(只生成script标签)给加载了:

    +<%- partial('_after-footer/' + theme.comment) %>
    <%- js_auto_version('js/app') %>
    
  • 顶部进度条

    这里引入一条轻量级的js:https://cdn.jsdelivr.net/gh/buunguyen/topbar@0.1.3/topbar.min.js

    源代码:buunguyen/topbar

    然后监听事件,执行方法:

    document.addEventListener('pjax:send', function(){
        if(typeof topbar != "undefined") topbar.show(); //pjax换页时显示顶部进度条
    });
    document.addEventListener('pjax:complete', function(){
        if(typeof topbar != "undefined") topbar.hide(); //pjax换页完成后隐藏顶部进度条
    });
    

主题颜色

每次想要切换主题颜色的时候都要去一条条的修改样式的值,过于麻烦,于是考虑给主题加入一个统一的主题色。

  • CSS变量

    因为主题用上了stylus做预处理,所以可以用stylus的变量。但是为了后续的开发,有可能会加入需要动态、批量的修改主题颜色的功能,所以干脆采用css的变量:

    :root{
        --themeColor: rgb(255,78,106);
    }
    

    然后在各处都引用它:

    .xxxxx {
    	background: var(--themeColor)
    }
    

    但是如果需要根据一个主题颜色修改alpha通道的值,那就不好办了。一番搜索发现CSS Color Module Level 4曾经有过一个叫color-mod(后来改做color,与现在的color函数不一样)的函数可以实现这个功能。

    那为什么要说曾呢,因为这个模块已经无了034b063

    虽然Level 5又已经在折腾这个功能了,但是它离很多浏览器都还很遥远。那就只能暂时曲线救国了:

    参考:How do I apply opacity to a CSS color variable?

    :root{
        --themeColor: 255, 78, 106;
    }
    

    然后这样用:

    .xxxxx {
    	background: rgba(var(--themeColor), 1)
    }
    

    因为stylus自己就带了rgba()这个函数,这里会因为参数不正确报错,所以要让它以原css代码的方式输出:

    .xxxxx {
    	background: @css {rgba(var(--themeColor), 1)}
    }
    
本文作者:.torrent
本文链接:https://blog.hitachimako.top/2021/hexo-fucking-diary/
版权声明:本文采用 CC BY-NC-SA 3.0 CN 协议进行许可