博客园Markdown编辑器实现多语言代码块
public class Main {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
print("Hello, World!")
#include <iostream>
int main() {
std::cout << "Hello, World!" << std::endl;
return 0;
}
最终效果
步骤
0xFF 前置条件
需要在 博客后台#基本设置 中申请JS权限
0x00 代码块HTML格式
对于一个 .md 格式的代码块
```java
public class Main {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
```
在发布后会被markdown.toHTML(input)
函数转换为如下HTML标记
<pre data-highlight-status="highlighted" class="highlighter-hljs" data-dark-theme="true">
<code class="language-java highlighter-hljs hljs" data-dark-theme="true"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Main</span> {
<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> {
System.out.println(<span class="hljs-string">"Hello, World!"</span>);
}
}
</code>
</pre>
对于不同的格式设置,标记对应的 prop 和 attr 也会有所不同,但总体都能抽象为如下结构
<pre>
<code class="language-*">
<!--...-->
</code>
</pre>
这里不对没有指定 Syntax Highlighting 的情况作讨论,因此我们可以一般地认为一个代码块的DOM,可以用 jQuery 选择器通过code[class^=language-]
模糊查询定位到
0x01 .md 定义
理论上,单纯的通过 Markdown 对 web 的支持,可以不通过 JS 就实现多语言代码块这个功能,但这样泛用性就会大打折扣,每一次使用应都会复写一定量前端代码
因此我们简单定义<div class=__tabs />
为多语言代码块的容器,当其中仅包含指定 Syntax Highlighting 的 Markdown 代码块时,通过 JS 将其转换成多语言代码块
即该文最终效果对应的 .md 源代码应为
<div class=__tabs>
```java
public class Main {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
```
```python
print("Hello, World!")
```
```cpp
#include <iostream>
int main() {
std::cout << "Hello, World!" << std::endl;
return 0;
}
```
</div>
0x02 常量定义
const __prefix = 'language-' // 定位 code 标签其 class 属性的前缀
const __tabs__nav__height = '40px' // 多语言代码块 Header 高度
const __regularExp = new RegExp(`\\b${__prefix}\\w+\\b`) // 截取包含前缀的 class 所用的正则表达式
/*
* parse(clazz):根据 class 获取 nav item 的 label
* other:自定义 label 的对应关系
*/
const __label = {
'language-java': 'Java',
parse: function(clazz) {
return this[clazz] ?? clazz.slice(__prefix.length)
}
}
0x03 完整代码
由于自定义页脚 HTML 至少是在 DOM 完全就绪后才插入的,因此直接操作<div class=__tabs />
即可,对应代码
<script type="text/javascript">
const __prefix = 'language-'
const __tabs__nav__height = '40px'
const __regularExp = new RegExp(`\\b${__prefix}\\w+\\b`)
const __label = {
'language-java': 'Java',
parse: function(clazz) {
return this[clazz] ?? clazz.slice(__prefix.length)
}
}
$('.__tabs').each(function() {
let flag = true
const containers = []
$(this).find(`code[class*=${__prefix}]`).each(function() {
const matched = $(this).attr('class').match(__regularExp)
if (!matched || matched.length != 1) {
flag = false
}
const container = $(this).parent()
if (container.prop('tagName') !== 'PRE') {
flag = false
}
if (!flag) return false
containers.push({
label: __label.parse(matched[0]),
dom: container
})
})
if (!flag || !containers.length) return true
const header = $('<ul>', {
class: '__tabs__nav-wrap',
css: {
listStyle: 'none',
userSelect: 'none',
height: __tabs__nav__height,
fontSize: 0,
margin: 0,
padding: 0
}
})
let actived;
$.each(containers, function(index, container) {
const item = $('<li>', {
text: container.label,
class: index ? '__tabs__item' : '__tabs__item is-active',
css: {
display: 'inline-block',
position: 'relative',
lineHeight: __tabs__nav__height,
fontSize: '16px',
margin: 0,
padding: '0 8px'
}
})
container.dom.css('margin-top', 0)
if (index) container.dom.hide()
else actived = item
item.click(function() {
if (actived == item) return
$.each(containers, (_, container) => container.dom.hide())
actived.removeClass('is-active')
item.addClass('is-active')
actived = item
container.dom.show()
})
header.append(item)
})
$(this).prepend(header)
})
</script>
稍微在页面定制 CSS 代码修饰一下
.__tabs {
border: 1px solid #e5e5e5;
border-radius: .5rem;
overflow: hidden;
}
.__tabs__nav-wrap {
color: #3c3c434d;
}
.__tabs__item {
padding: 0 12px !important;
}
.__tabs__item.is-active, .__tabs__item:hover {
color: #262626;
}
.__tabs__item~.__tabs__item:before {
background-color: #0000000d;
content: " ";
height: 12px;
left: 0;
position: absolute;
top: calc(50% - 6px);
width: 1px;
}
需要自取,当然能标明出处更好