feat: support markdown
This commit is contained in:
parent
bd230ddd4f
commit
85d144a1e9
34 changed files with 2350 additions and 4 deletions
134
ui/components/Markdown/ReactMarkdown.tsx
Normal file
134
ui/components/Markdown/ReactMarkdown.tsx
Normal file
|
@ -0,0 +1,134 @@
|
|||
import { css, cx } from "@emotion/css";
|
||||
import { Definition, Root } from "@yozora/ast";
|
||||
import { calcDefinitionMap } from "@yozora/ast-util";
|
||||
import React from "react";
|
||||
import {
|
||||
INodeRendererContext,
|
||||
INodeRendererMap,
|
||||
IReactMarkdownThemeScheme,
|
||||
NodeRendererContextType,
|
||||
ReactMarkdownViewModel,
|
||||
astClasses,
|
||||
} from "./context";
|
||||
import { NodesRenderer } from "./NodesRenderer";
|
||||
import { parser } from "./parser";
|
||||
import { buildNodeRendererMap } from "./renderer";
|
||||
|
||||
export interface IMarkdownProps {
|
||||
/**
|
||||
* Text content of markdown.
|
||||
*/
|
||||
text: string | string[];
|
||||
/**
|
||||
* Customized node renderer mpa.
|
||||
*/
|
||||
customizedRendererMap?: Readonly<Partial<INodeRendererMap>>;
|
||||
/**
|
||||
* React markdown theme scheme.
|
||||
*/
|
||||
themeScheme?: IReactMarkdownThemeScheme;
|
||||
/**
|
||||
* Preset Link / Image reference definitions.
|
||||
*/
|
||||
presetDefinitionMap?: Readonly<Record<string, Definition>>;
|
||||
/**
|
||||
* Prefer code wrap.
|
||||
*
|
||||
* !!! Since the lineno would be weird if the code is wrapped,
|
||||
* !!! so the lineno will be hidden if the `preferCodeWrap` set to `true`.
|
||||
*/
|
||||
preferCodeWrap?: boolean;
|
||||
/**
|
||||
* Whether if show lineno for code block.
|
||||
*/
|
||||
showCodeLineno?: boolean;
|
||||
/**
|
||||
* Root css class of the component.
|
||||
*/
|
||||
className?: string;
|
||||
/**
|
||||
* Root css style.
|
||||
*/
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
export const ReactMarkdown: React.FC<IMarkdownProps> = props => {
|
||||
const {
|
||||
presetDefinitionMap,
|
||||
customizedRendererMap,
|
||||
preferCodeWrap = false,
|
||||
showCodeLineno = true,
|
||||
text,
|
||||
themeScheme = "lighten",
|
||||
className,
|
||||
style,
|
||||
} = props;
|
||||
const ast: Root = React.useMemo(() => {
|
||||
const asts: Root[] = Array.isArray(text) ? text.map(t => parser.parse(t)) : [parser.parse(text)];
|
||||
if (asts.length === 0) {
|
||||
return parser.parse("");
|
||||
}
|
||||
const root: Root = asts[0];
|
||||
for (let i = 1; i < asts.length; ++i) {
|
||||
root.children.push(...asts[i].children);
|
||||
}
|
||||
return root;
|
||||
}, [text]);
|
||||
const definitionMap: Record<string, Readonly<Definition>> = React.useMemo(
|
||||
() => calcDefinitionMap(ast).definitionMap,
|
||||
[ast],
|
||||
);
|
||||
|
||||
const [viewmodel] = React.useState<ReactMarkdownViewModel>(
|
||||
() =>
|
||||
new ReactMarkdownViewModel({
|
||||
definitionMap: {
|
||||
...presetDefinitionMap,
|
||||
...definitionMap,
|
||||
},
|
||||
rendererMap: buildNodeRendererMap(customizedRendererMap),
|
||||
preferCodeWrap,
|
||||
showCodeLineno,
|
||||
themeScheme,
|
||||
}),
|
||||
);
|
||||
const context = React.useMemo<INodeRendererContext>(() => ({ viewmodel }), [viewmodel]);
|
||||
|
||||
const cls: string = cx(rootCls, themeScheme === "darken" && astClasses.rootDarken, className);
|
||||
|
||||
React.useEffect(() => {
|
||||
viewmodel.preferCodeWrap$.next(preferCodeWrap);
|
||||
}, [viewmodel, preferCodeWrap]);
|
||||
|
||||
React.useEffect(() => {
|
||||
viewmodel.showCodeLineno$.next(showCodeLineno);
|
||||
}, [viewmodel, showCodeLineno]);
|
||||
|
||||
React.useEffect(() => {
|
||||
viewmodel.themeScheme$.next(themeScheme);
|
||||
}, [viewmodel, themeScheme]);
|
||||
|
||||
return (
|
||||
<div className={cls} style={style}>
|
||||
<NodeRendererContextType.Provider value={context}>
|
||||
<NodesRenderer nodes={ast.children} />
|
||||
</NodeRendererContextType.Provider>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const rootCls = cx(
|
||||
astClasses.root,
|
||||
css({
|
||||
wordBreak: "break-all",
|
||||
userSelect: "unset",
|
||||
[astClasses.listItem]: {
|
||||
[`> ${astClasses.list}`]: {
|
||||
marginLeft: "1.2em",
|
||||
},
|
||||
},
|
||||
"> :last-child": {
|
||||
marginBottom: 0,
|
||||
},
|
||||
}),
|
||||
);
|
Loading…
Add table
Add a link
Reference in a new issue