💻渲染器
其实主要也就三种,自带的 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 有多那么几个可以设置的选项,方便折腾,就不需要动它的源码了。
接下来自然就是人民群众喜闻乐见的曲线救国环节:
在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;
}
修改前
修改后
发现圆点虽然不见了,但是还是占了位置,于是再暴力一回,让它缩回去:
.task-checkbox-list {
/* 原本继承的值为 0 0 0 2em */
padding: 0 0 0 1em;
}
修改后
🎄主题
nexmoe/mako
原版:theme-nexmoe/hexo-theme-nexmoe
修改版:DogTorrent/hexo-theme-mako
请求合并
众所周知,把多个请求合并成一个可以大大提升加载速度,所以原repo在一次优化过程中将本来暴露在 _config.yml
中的各种js/css链接全部写死在了ejs模板文件内:
这样做虽然不太优雅,但是其实一般也不会影响到什么。问题就在于写死的代码高亮主题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>
内了,很舒服。那么直接冲就完事了,把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 ):
- 把av-min.js单独拿出来,这个脚本内部有些东西不能重复定义,否则会出错
- 为防止评论串页,新增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)} }