在 《一篇带你用 VuePress + Github Pages 搭建博客》[1]中,我们使用 VuePress 搭建了一个博客,最终的效果查看:TypeScript 中文文档[2]。
如果我们浏览过 TypeScript 官方文档[3],我们会发现一个很好用的功能,那就是很多代码块,在悬浮上去的时候都会出现一个 Try
按钮:
点击就会跳转到对应的 Playground,比如图示的按钮跳转的就是这个链接[4],我们可以在这个 Playground 修改并验证代码效果。
如果我们要实现这样的功能,该怎么实现呢?
我们很容易想到,写一个 VuePress 插件来实现它,这个效果看起来有点像代码复制插件,但细细一想,又非如此。
代码复制插件的实现方式,参考 《从零实现一个 VuePress 插件》,可以在页面渲染完成后,遍历每一个代码块然后插入一个复制按钮,点击复制的时候将代码写入剪切板,但是代码块跳转就不一样了,代码跳转需要我们先写入一个链接地址,然后再渲染按钮,问题是这个链接的地址写在哪里呢?要知道,我们能写的只是一个普通的 markdown 文件呀……
于是我们就想到,是否可以拓展 markdown 的语法呢?就比如正常的代码块写法是:
` ` `typescript
const message = "Hello World!";
` ` `
为了实现这个效果,我们是否可以这样写:
` ` `typescript
// try-link: https://www.baidu.com
const message = 'Hello World!';
` ` `
但是渲染的时候,并不渲染 try-link 这行注释,而是变成这样的效果:
当点击 Try
的时候,跳转到对应的链接。
当然效果更好的话,可以在鼠标悬浮在代码块上方的时候,才显示这个 Try
按钮,类似于这种效果:
查阅 VuePress 的官方文档[5],我们可以知道:VuePress 使用 markdown-it
来渲染 Markdown,那markdown-it
是什么呢?查阅 markdown-it 的 Github 仓库[6],可以看到这样一段介绍:
Markdown parser done right. Fast and easy to extend.
简单的来说,markdown-it
就是一个 markdown 渲染器,可以将 markdown 渲染成 html 等,而且 markdown-it 支持写插件拓展功能,实际上,VuePress 项目中的 markdown 文件为什么能支持写 Vue 组件,就是因为 VuePress 写了插件支持了 Vue 语法,那我们是不是也可以拓展 markdown 的语法呢?
还好在 VuePress 文档[7]里,提供了配置,可以自定义 markdown-it 插件:
VuePress 使用 markdown-it (opens new window)来渲染 Markdown,上述大多数的拓展也都是通过自定义的插件实现的。想要进一步的话,你可以通过 .vuepress/config.js 的 markdown 选项,来对当前的 markdown-it 实例做一些自定义的配置:
module.exports = {
markdown: {
// markdown-it-anchor 的选项
anchor: { permalink: false },
// markdown-it-toc 的选项
toc: { includeLevel: [1, 2] },
extendMarkdown: md => {
// 使用更多的 markdown-it 插件!
md.use(require('markdown-it-xxx'))
}
}
}
引入的方法知道了,但怎么写这个 markdown-it 插件呢?
查阅 markdown-it
的Github 仓库代码[8]和文档[9],我们可以大致了解到 markdown-it
的工作原理,其转换过程类似于 Babel,先转换成抽象语法树,然后生成对应的代码,简单的概括就是分为 Parse 和 Render 两个过程。
这点我们查看源码[10]也可以看到:
MarkdownIt.prototype.render = function (src, env) {
env = env || {};
return this.renderer.render(this.parse(src, env), this.options, env);
};
所以这里我们解决问题的思路有两个,一个是在 Parse 过程中处理,一个在 Render 过程中处理,为了简单起见,我决定直接处理 Render 过程,查看 Render 的源码[11],我们可以看到 Render 里其实已经根据一些固定的类型写了默认 Rules(渲染规则),就比如关于代码块:
default_rules.fence = function (tokens, idx, options, env, slf) {
var token = tokens[idx],
info = token.info ? unescapeAll(token.info).trim() : '',
langName = '',
langAttrs = '',
highlighted, i, arr, tmpAttrs, tmpToken;
if (info) {
// ...
}
if (options.highlight) {
highlighted = options.highlight(token.content, langName, langAttrs) || escapeHtml(token.content);
} else {
highlighted = escapeHtml(token.content);
}
if (highlighted.indexOf('<pre') === 0) {
return highlighted + '\n';
}
if (info) {
//...
}
return '<pre><code' + slf.renderAttrs(token) + '>'
+ highlighted
+ '</code></pre>\n';
};
我们可以覆盖这个规则,参照 markdown-it 提供的插件编写原则[12],我们可以这样写:
# 获取 md 实例后
md.renderer.rules.fence = function (tokens, idx, options, env, self) {
// ...
};
为了再省事一点,我准备直接获取最后渲染的 HTML 结果,它是一个字符串,然后匹配 //try-link: xxx
生成的 HTML,替换成一个 <a>
链接,我们查看下 //try-link: xxx
这句注释生成的 HTML:
修改下 config.js
文件:
module.exports = {
markdown: {
extendMarkdown: md => {
md.use(function(md) {
const fence = md.renderer.rules.fence
md.renderer.rules.fence = (...args) => {
let rawCode = fence(...args);
rawCode = rawCode.replace(/<span class="token comment">\/\/ try-link https:\/\/(.*)<\/span>\n/ig, '<a href="$1" class="try-button" target="_blank">Try</a>');
return `${rawCode}`
}
})
}
}
}
这里为了简洁,我没有将 <a>
链接的样式直接内联写入其中,而是加了一个类,那在哪里写这个类的样式呢?
VuePress 提供了 docs/.vuepress/styles/index.styl
文件,作为将会被自动应用的全局样式文件,会生成在最终的 CSS 文件结尾,具有比默认样式更高的优先级。
所以我们在 index.styl
文件下写入样式:
// 默认样式
.try-button {
position: absolute;
bottom: 1em;
right: 1em;
font-weight: 100;
border: 1px solid #719af4;
border-radius: 4px;
color: #719af4;
padding: 2px 8px;
text-decoration: none;
transition-timing-function: ease;
transition: opacity .3s;
opacity: 0;
}
// hover 样式
.content__default:not(.custom) a.try-button:hover {
background-color: #719af4;
color: #fff;
text-decoration: none;
}
有的时候,自动编译可能不会生效,我们可以重新运行 yarn run docs:dev
。
此时已经可以正常显示按钮了(默认样式透明度为 0,这里为了截图强行设置透明度为 1):
接下来我们要实现,鼠标悬浮在代码块的时候,才显示这个按钮,这里我们可以借助 《从零实现一个 VuePress 插件》[13]中的方法,在页面 mounted
的时候,获取所有的代码块元素,然后添加事件,我们再修改下 config.js
文件:
module.exports = {
plugins: [
(options, ctx) => {
return {
name: 'vuepress-plugin-code-try',
clientRootMixin: path.resolve(__dirname, 'vuepress-plugin-code-try/index.js')
}
}
],
markdown: {
extendMarkdown: md => {
md.use(function(md) {
const fence = md.renderer.rules.fence
md.renderer.rules.fence = (...args) => {
let rawCode = fence(...args);
rawCode = rawCode.replace(/<span class="token comment">\/\/ try-link https:\/\/(.*)<\/span>\n/ig, '<a href="$1" class="try-button" target="_blank">Try</a>');
return `${rawCode}`
}
})
}
}
}
然后在同级目录config.js
下新建一个 vuepress-plugin-code-try
目录,然后新建一个 index.js
文件:
export default {
mounted () {
setTimeout(() => {
document.querySelectorAll('div[class*="language-"] pre').forEach(el => {
if (el.querySelector('.try-button')) {
el.addEventListener('mouseover', () => {
el.querySelector('.try-button').style.opacity = '1';
})
el.addEventListener('mouseout', () => {
el.querySelector('.try-button').style.opacity = '0';
})
}
})
}, 100)
}
}
此时,再运行项目,我们就实现了最初想要的效果:
[1]《一篇带你用 VuePress + Github Pages 搭建博客》: https://github.com/mqyqingfeng/Blog/issues/235
[2]TypeScript 中文文档: http://ts.yayujs.com/
[3]官方文档: https://www.typescriptlang.org/docs/handbook/2/everyday-types.html
[4]这个链接: https://www.typescriptlang.org/play#code/PTAEAEFMCdoe2gZwFygEwGYAsBWAsAFAAmkAxgDYCG0koAZgK4B2pALgJZxOgDmNkrABRNKAW0ipEraOyY8AlKgBucdkQDchEKAC0e0g1Z6dWsAHU4DckVAAjWpVDRmHcaBjxoodnXcAPMkNIIgBCQj5IAUEsNHl1IA
[5]VuePress 的官方文档: https://v1.vuepress.vuejs.org/zh/guide/markdown.html#%E8%BF%9B%E9%98%B6%E9%85%8D%E7%BD%AE
[6]markdown-it 的 Github 仓库: https://github.com/markdown-it/markdown-it
[7]VuePress 文档: https://v1.vuepress.vuejs.org/zh/guide/markdown.html#%E8%BF%9B%E9%98%B6%E9%85%8D%E7%BD%AE
[8]Github 仓库代码: https://github.com/markdown-it/markdown-it
[9]文档: https://markdown-it.github.io/markdown-it/#Core
[10]源码: https://github.com/markdown-it/markdown-it/blob/master/lib/index.js#L541
[11]Render 的源码: https://github.com/markdown-it/markdown-it/blob/master/lib/renderer.js
[12]插件编写原则: https://github.com/markdown-it/markdown-it/blob/master/docs/architecture.md
[13]《从零实现一个 VuePress 插件》: https://github.com/mqyqingfeng/Blog/issues/250
本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/9qWocL_1JbbXs9KxqJteKA
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。