跳到主要内容

添加五线谱(abcjs)支持

就是玩!本次我们添加一个五线谱的支持。本次我们采用一种不一样的实现。当然也可以直接采用 remark-abcjs 插件来实现,但是我们这次手写一个实现。我们需要借助abcjs这个库来实现

首先我们添加一个 remark-abcjs(我们自己实现的)的插件,在这个插件中我们仅对Markdown语法树种的abcjs代码块进行识别,然后剩下的交给 Abcjs 这个MDX组件来实现。也即我们需要实现以下内容

  • src/remark/remark-abcjs:识别abcjs代码块
  • src/components/Abcjs:渲染abcjs代码

接下来我们来实现他们

安装abcjs

第一步:安装 abcjs

npm install abcjs --save
shell

remark-abcjs插件实现

src/remark/remark-abcjs.ts
import type {Transformer} from 'unified';
import type {Code} from 'mdast';
import type {Node} from 'unist';
import { visit } from 'unist-util-visit';

export default function plugin(): Transformer {
return async (root) => {
visit(root, 'code', (node: Code) => {
if (node.lang === 'abcjs') {
transformNode(node, {
type: 'abcjsCodeBlock',
data: {
hName: 'Abcjs',
type: 'mdxJsxFlowElement',
hProperties: {
value: node.value,
},
},
});
}
});
};
}

function transformNode<NewNode extends Node>(
node: Node,
newNode: NewNode,
): NewNode {
Object.keys(node).forEach((key) => {
delete node[key];
});
Object.keys(newNode).forEach((key) => {
node[key] = newNode[key];
});
return node as NewNode;
}
tsx

参考了源码中的 docusaurus-mdx-loader/src/remark/mermaid/index.ts 的实现。

实现Abcjs组件

import { useState, useEffect } from 'react';
import * as abcjs from 'abcjs';

export interface RenderResult {
html: string;
bindFunctions?: (element: Element) => void;
}

export interface AbcjsProps {
text: string
config?: abcjs.AbcVisualParams
}

async function doRenderAbcjs({text, config}: AbcjsProps): Promise<RenderResult> {
const element = document.createElement('div');
abcjs.renderAbc(element, text, config);
return {
html: element.outerHTML,
bindFunctions: (element) => {
}
};
}

async function renderAbcjs(props: AbcjsProps): Promise<RenderResult> {
try {
return await doRenderAbcjs(props);
} catch (e) {
throw e;
}
}

const defaultMermaidConfig = {};

export function useAbcjsRenderResult(
{
text,
config: providedConfig
}: {
text: string;
config?: abcjs.AbcVisualParams;
}): RenderResult | null {
const [result, setResult] = useState<RenderResult | null>(null);

const config = providedConfig ?? defaultMermaidConfig;

useEffect(() => {
renderAbcjs({text, config})
.then(renderResult => {
setResult(renderResult)
})
.catch((e) => {
setResult(() => {
throw e;
});
});
}, [text, config]);

return result;
}
tsx

配置

首先我们将Abcjs组件添加到 MDXComponents.js中。

Tip

如果不知道MDXComponents.js是什么可以参考《将自定义组件注册为全局组件》这一章节。

src/theme/MDXComponents.js
import React from 'react';
import MDXComponents from '@theme-original/MDXComponents';
import Abcjs from "@site/src/components/Abcjs";

export default {
// Re-use the default mapping
...MDXComponents,
//...
Abcjs
};
js

配置remarkPlugins

docusaurus.config.js
import remarkAbcjs from './src/remark/remark-abcjs'

export default {
// ...
presets: [
[
'@docusaurus/preset-classic',
{
docs: {
remarkPlugins: [
remarkAbcjs,
],
},
pages: {
remarkPlugins: [remarkAbcjs],
},
blog: {
remarkPlugins: [
remarkAbcjs,
],
// ...
},
},
],
],
};

ts

效果展示

```abcjs
X:1
T:Speed the Plough
M:4/4
C:Trad.
K:G
|:GABc dedB|dedB dedB|c2ec B2dB|c2A2 A2BA|
GABc dedB|dedB dedB|c2ec B2dB|A2F2 G4:|
|:g2gf gdBd|g2f2 e2d2|c2ec B2dB|c2A2 A2df|
g2gf g2Bd|g2f2 e2d2|c2ec B2dB|A2F2 G4:|
```
markdown