feat: support markdown

This commit is contained in:
guanghechen 2024-07-10 16:17:23 +08:00
parent bd230ddd4f
commit 85d144a1e9
34 changed files with 2350 additions and 4 deletions

View file

@ -0,0 +1,66 @@
import { css } from "@emotion/css";
export const astClasses = {
root: css({
"--colorBgBlockquote": "none",
"--colorBgTableHead": "hsl(0deg, 0%, 94%)",
"--colorBgTableEvenRow": "hsl(0deg, 0%, 96%)",
"--colorBgTableOddRow": "hsl(0deg, 0%, 100%)",
"--colorBorderBlockquote": "hsl(210deg, 13%, 85%)",
"--colorBorderHeading": "hsl(0deg, 0%, 80%)",
"--colorBorderImage": "hsl(277deg, 19%, 47%)",
"--colorBorderTable": "hsl(220deg, 7%, 90%)",
"--colorBgCode": "#f5f7f9",
"--colorDelete": "hsl(210deg, 8%, 65%)",
"--colorHeading": "hsl(0deg, 0%, 25%)",
"--colorImageTitle": "hsl(0deg, 0%, 50%)",
"--colorInlineCode": "hsl(348deg, 60%, 47%)",
"--colorLink": "hsl(206deg, 53%, 47%)",
"--colorLinkActive": "hsl(206deg, 53%, 52%)",
"--colorLinkHover": "hsl(206deg, 53%, 52%)",
"--colorLinkVisited": "hsl(206deg, 53%, 47%)",
"--fontFamilyCode": "Consolas, 'Source Code Pro', 'Roboto Mono', monospace, sans-serif",
"--fontFamilyHeading": "Consolas, 'Source Code Pro', 'Roboto Mono', monospace, sans-serif",
}),
rootDarken: css({
"&&": {
"--colorBgBlockquote": "none",
"--colorBgTableHead": "hsl(200deg, 10%, 16%)",
"--colorBgTableEvenRow": "hsl(200deg, 10%, 16%)",
"--colorBgTableOddRow": "hsl(0deg, 0%, 9%)",
"--colorBorderBlockquote": "hsl(207deg, 7%, 45%)",
"--colorBorderHeading": "hsla(0deg, 0%, 30%, 0.8)",
"--colorBorderImage": "hsl(290deg, 15%, 49%)",
"--colorBorderTable": "hsl(0deg, 0%, 50%)",
"--colorBgCode": "hsl(0deg, 0%, 12%)",
"--colorDelete": "hsl(220deg, 5%, 68%)",
"--colorHeading": "hsl(0deg, 0%, 65%)",
"--colorImageTitle": "hsl(0deg, 0%, 50%)",
"--colorInlineCode": "hsl(348deg, 70%, 52%)",
"--colorLink": "hsl(207deg, 53%, 50%)",
"--colorLinkActive": "hsl(207deg, 53%, 50%)",
"--colorLinkHover": "hsl(207deg, 53%, 50%)",
"--colorLinkVisited": "hsl(207deg, 53%, 50%)",
"--fontFamilyCode": "Consolas, 'Source Code Pro', 'Roboto Mono', monospace, sans-serif",
"--fontFamilyHeading": "Consolas, 'Source Code Pro', 'Roboto Mono', monospace, sans-serif",
},
}),
blockquote: css({}),
break: css({}),
code: css({}),
delete: css({}),
emphasis: css({}),
heading: css({}),
image: css({}),
imageReference: css({}),
inlineCode: css({}),
link: css({}),
linkReference: css({}),
list: css({}),
listItem: css({}),
paragraph: css({}),
strong: css({}),
table: css({}),
text: css({}),
thematicBreak: css({}),
};

View file

@ -0,0 +1,15 @@
import React from "react";
import type { ReactMarkdownViewModel } from "./viewmodel";
export interface INodeRendererContext {
readonly viewmodel: ReactMarkdownViewModel;
}
export const NodeRendererContextType = React.createContext<INodeRendererContext>(
null as unknown as INodeRendererContext,
);
NodeRendererContextType.displayName = "NodeRendererContextType";
export const useNodeRendererContext = (): INodeRendererContext => {
return React.useContext(NodeRendererContextType);
};

View file

@ -0,0 +1,4 @@
export * from "./constant";
export * from "./context";
export * from "./types";
export * from "./viewmodel";

View file

@ -0,0 +1,73 @@
import type {
Definition,
Blockquote,
BlockquoteType,
Break,
BreakType,
Code,
CodeType,
DefinitionType,
Delete,
DeleteType,
Emphasis,
EmphasisType,
Heading,
HeadingType,
Image,
ImageReference,
ImageReferenceType,
ImageType,
InlineCode,
InlineCodeType,
Link,
LinkReference,
LinkReferenceType,
LinkType,
List,
ListItem,
ListItemType,
ListType,
Node,
Paragraph,
ParagraphType,
Strong,
StrongType,
Table,
TableType,
Text,
TextType,
ThematicBreak,
ThematicBreakType,
} from "@yozora/ast";
import type React from "react";
// Renderer for markdown AST node.
export type INodeRenderer<T extends Node = Node> = React.ComponentType<T> | React.FC<T>;
/**
* Renderer map.
*/
export interface INodeRendererMap {
[BlockquoteType]: INodeRenderer<Blockquote>;
[BreakType]: INodeRenderer<Break>;
[CodeType]: INodeRenderer<Code>;
[DefinitionType]: INodeRenderer<Definition>;
[DeleteType]: INodeRenderer<Delete>;
[EmphasisType]: INodeRenderer<Emphasis>;
[HeadingType]: INodeRenderer<Heading>;
[ImageType]: INodeRenderer<Image>;
[ImageReferenceType]: INodeRenderer<ImageReference>;
[InlineCodeType]: INodeRenderer<InlineCode>;
[LinkType]: INodeRenderer<Link>;
[LinkReferenceType]: INodeRenderer<LinkReference>;
[ListType]: INodeRenderer<List>;
[ListItemType]: INodeRenderer<ListItem>;
[ParagraphType]: INodeRenderer<Paragraph>;
[StrongType]: INodeRenderer<Strong>;
[TableType]: INodeRenderer<Table>;
[TextType]: INodeRenderer<Text>;
[ThematicBreakType]: INodeRenderer<ThematicBreak>;
_fallback: INodeRenderer;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[key: string]: INodeRenderer<Node & any>;
}

View file

@ -0,0 +1,49 @@
import { State, ViewModel } from "@guanghechen/viewmodel";
import type { Definition } from "@yozora/ast";
import type { INodeRendererMap } from "./types";
export type IReactMarkdownThemeScheme = "lighten" | "darken" | string;
export interface IReactMarkdownViewModelProps {
/**
* Link / Image reference definitions.
*/
readonly definitionMap: 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`.
*/
readonly preferCodeWrap: boolean;
/**
* Ast node renderer map.
*/
readonly rendererMap: Readonly<INodeRendererMap>;
/**
* Whether if show code lineno.
*/
readonly showCodeLineno: boolean;
/**
* React markdown theme scheme.
*/
readonly themeScheme: IReactMarkdownThemeScheme;
}
export class ReactMarkdownViewModel extends ViewModel {
public readonly definitionMap$: State<Readonly<Record<string, Definition>>>;
public readonly preferCodeWrap$ = new State<boolean>(false);
public readonly rendererMap$: State<Readonly<INodeRendererMap>>;
public readonly showCodeLineno$: State<boolean>;
public readonly themeScheme$: State<IReactMarkdownThemeScheme>;
constructor(props: IReactMarkdownViewModelProps) {
super();
const { definitionMap, rendererMap, showCodeLineno, themeScheme } = props;
this.definitionMap$ = new State(definitionMap);
this.rendererMap$ = new State(rendererMap);
this.showCodeLineno$ = new State<boolean>(showCodeLineno);
this.themeScheme$ = new State<IReactMarkdownThemeScheme>(themeScheme);
}
}