添加五线谱(abcjs
)支持
就是玩!本次我们添加一个五线谱的支持。本次我们采用一种不一样的实现。当然也可以直接采用 remark-abcjs
插件来实现,但是我们这次手写一个实现。我们需要借助abcjs
这个库来实现
abcjs
Github地址:https://github.com/paulrosen/abcjsabcjs
官网:https://paulrosen.github.io/abcjs/remark-abcjs
:别人已经实现的一款remarkjs的插件。与我们要实现的功能相似。Github地址:https://github.com/breqdev/remark-abcjs
首先我们添加一个 remark-abcjs
(我们自己实现的)的插件,在这个插件中我们仅对Markdown语法树种的abcjs代码块进行识别,然后剩下的交给 Abcjs
这个MDX组件来实现。也即我们需要实现以下内容
src/remark/remark-abcjs
:识别abcjs代码块src/components/Abcjs
:渲染abcjs代码
接下来我们来实现他们
安装abcjs
第一步:安装 abcjs
- npm
- Yarn
- pnpm
npm install abcjs --save
yarn add abcjs
pnpm add abcjs
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;
}
参考了源码中的 docusaurus-mdx-loader/src/remark/mermaid/index.ts
的实现。
实现Abcjs组件
- src/components/Abcjs/client/index.ts
- src/components/Abcjs/index.tsx
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;
}
src/components/Abcjs/index.tsx
import ErrorBoundary from '@docusaurus/ErrorBoundary';
import React, {useEffect, useRef} from 'react';
import type {ReactNode} from 'react';
import {ErrorBoundaryErrorMessageFallback} from '@docusaurus/theme-common';
import {RenderResult, useAbcjsRenderResult} from "./client";
export interface Props {
value: string;
}
function AbcjsRenderResult({renderResult}: {
renderResult: RenderResult
}): JSX.Element {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
const div = ref.current!;
renderResult.bindFunctions?.(div);
}, [renderResult]);
return (
<div
ref={ref}
className={`abcjs-container`}
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{__html: renderResult.html}}
/>
);
}
function AbcjsRenderer({value}: Props): ReactNode {
const renderResult = useAbcjsRenderResult({text: value});
if (renderResult === null) {
return null;
}
return <AbcjsRenderResult renderResult={renderResult} />
}
export default function Abcjs(props: Props): JSX.Element {
return (
<ErrorBoundary
fallback={(params) => <ErrorBoundaryErrorMessageFallback {...params} />}>
<AbcjsRenderer {...props} />
</ErrorBoundary>
);
}
配置
首先我们将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
};
配置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,
],
// ...
},
},
],
],
};
效果展示
```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:|
```