This commit is contained in:
guanghechen 2024-07-10 16:34:28 +08:00
parent cfa6efc6ed
commit 94d944bd68
25 changed files with 131 additions and 118 deletions

View file

@ -2,7 +2,7 @@ import React from "react";
import Image from "next/image";
import { ReactMarkdown } from "@/components/Markdown";
interface ContextItemProps {
interface ContextItemProperties {
item: {
name: string;
url: string;
@ -16,7 +16,7 @@ interface ContextItemProps {
};
}
const ContextItem: React.FC<ContextItemProps> = ({ item }) => {
const ContextItem: React.FC<ContextItemProperties> = ({ item }) => {
return (
<div className="border p-4 rounded-lg mb-4 dark:border-gray-700">
<h4 className="font-bold text-black dark:text-white">{item.name}</h4>

View file

@ -5,30 +5,30 @@ import React from "react";
import type { INodeRenderer, INodeRendererMap } from "./context";
import { useNodeRendererContext } from "./context";
export interface INodesRendererProps {
export interface INodesRendererProperties {
/**
* Ast nodes.
*/
nodes?: Node[];
}
export const NodesRenderer: React.FC<INodesRendererProps> = props => {
const { nodes } = props;
export const NodesRenderer: React.FC<INodesRendererProperties> = properties => {
const { nodes } = properties;
const { viewmodel } = useNodeRendererContext();
const rendererMap: Readonly<INodeRendererMap> = useStateValue(viewmodel.rendererMap$);
if (!Array.isArray(nodes) || nodes.length <= 0) return <React.Fragment />;
return <NodesRendererInner nodes={nodes} rendererMap={rendererMap} />;
};
interface IProps {
interface IProperties {
nodes: Node[];
rendererMap: Readonly<INodeRendererMap>;
}
class NodesRendererInner extends React.Component<IProps> {
public override shouldComponentUpdate(nextProps: Readonly<IProps>): boolean {
const props = this.props;
return !isEqual(props.nodes, nextProps.nodes) || props.rendererMap !== nextProps.rendererMap;
class NodesRendererInner extends React.Component<IProperties> {
public override shouldComponentUpdate(nextProperties: Readonly<IProperties>): boolean {
const properties = this.props;
return !isEqual(properties.nodes, nextProperties.nodes) || properties.rendererMap !== nextProperties.rendererMap;
}
public override render(): React.ReactElement {

View file

@ -14,7 +14,7 @@ import { NodesRenderer } from "./NodesRenderer";
import { parser } from "./parser";
import { buildNodeRendererMap } from "./renderer";
export interface IMarkdownProps {
export interface IMarkdownProperties {
/**
* Text content of markdown.
*/
@ -52,7 +52,7 @@ export interface IMarkdownProps {
style?: React.CSSProperties;
}
export const ReactMarkdown: React.FC<IMarkdownProps> = props => {
export const ReactMarkdown: React.FC<IMarkdownProperties> = properties => {
const {
presetDefinitionMap,
customizedRendererMap,
@ -62,15 +62,15 @@ export const ReactMarkdown: React.FC<IMarkdownProps> = props => {
themeScheme = "lighten",
className,
style,
} = props;
} = properties;
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);
for (let index = 1; index < asts.length; ++index) {
root.children.push(...asts[index].children);
}
return root;
}, [text]);

View file

@ -4,7 +4,7 @@ import type { INodeRendererMap } from "./types";
export type IReactMarkdownThemeScheme = "lighten" | "darken" | string;
export interface IReactMarkdownViewModelProps {
export interface IReactMarkdownViewModelProperties {
/**
* Link / Image reference definitions.
*/
@ -37,10 +37,10 @@ export class ReactMarkdownViewModel extends ViewModel {
public readonly showCodeLineno$: State<boolean>;
public readonly themeScheme$: State<IReactMarkdownThemeScheme>;
constructor(props: IReactMarkdownViewModelProps) {
constructor(properties: IReactMarkdownViewModelProperties) {
super();
const { definitionMap, rendererMap, showCodeLineno, themeScheme } = props;
const { definitionMap, rendererMap, showCodeLineno, themeScheme } = properties;
this.definitionMap$ = new State(definitionMap);
this.rendererMap$ = new State(rendererMap);
this.showCodeLineno$ = new State<boolean>(showCodeLineno);

View file

@ -11,9 +11,9 @@ import { NodesRenderer } from "../NodesRenderer";
* @see https://www.npmjs.com/package/@yozora/tokenizer-blockquote
*/
export class BlockquoteRenderer extends React.Component<Blockquote> {
public override shouldComponentUpdate(nextProps: Readonly<Blockquote>): boolean {
const props = this.props;
return props.children !== nextProps.children;
public override shouldComponentUpdate(nextProperties: Readonly<Blockquote>): boolean {
const properties = this.props;
return properties.children !== nextProperties.children;
}
public override render(): React.ReactElement {

View file

@ -10,9 +10,9 @@ import { CodeRendererInner } from "./inner/CodeRendererInner";
* @see https://www.npmjs.com/package/@yozora/tokenizer-indented-code
* @see https://www.npmjs.com/package/@yozora/tokenizer-fenced-code
*/
export const CodeRenderer: INodeRenderer<Code> = props => {
const { lang } = props;
const value: string = props.value.replace(/[\n\r]+$/, ""); // Remove trailing line endings.
export const CodeRenderer: INodeRenderer<Code> = properties => {
const { lang } = properties;
const value: string = properties.value.replace(/[\n\r]+$/, ""); // Remove trailing line endings.
const { viewmodel } = useNodeRendererContext();
const preferCodeWrap: boolean = useStateValue(viewmodel.preferCodeWrap$);

View file

@ -11,9 +11,9 @@ import { NodesRenderer } from "../NodesRenderer";
* @see https://www.npmjs.com/package/@yozora/tokenizer-delete
*/
export class DeleteRenderer extends React.Component<Delete> {
public override shouldComponentUpdate(nextProps: Readonly<Delete>): boolean {
const props = this.props;
return props.children !== nextProps.children;
public override shouldComponentUpdate(nextProperties: Readonly<Delete>): boolean {
const properties = this.props;
return properties.children !== nextProperties.children;
}
public override render(): React.ReactElement {

View file

@ -11,9 +11,9 @@ import { NodesRenderer } from "../NodesRenderer";
* @see https://www.npmjs.com/package/@yozora/tokenizer-emphasis
*/
export class EmphasisRenderer extends React.Component<Emphasis> {
public override shouldComponentUpdate(nextProps: Readonly<Emphasis>): boolean {
const props = this.props;
return props.children !== nextProps.children;
public override shouldComponentUpdate(nextProperties: Readonly<Emphasis>): boolean {
const properties = this.props;
return properties.children !== nextProperties.children;
}
public override render(): React.ReactElement {

View file

@ -6,7 +6,7 @@ import { NodesRenderer } from "../NodesRenderer";
type IHeading = "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
interface IProps extends Heading {
interface IProperties extends Heading {
linkIcon?: React.ReactNode;
}
@ -16,14 +16,14 @@ interface IProps extends Heading {
* @see https://www.npmjs.com/package/@yozora/ast#heading
* @see https://www.npmjs.com/package/@yozora/tokenizer-heading
*/
export class HeadingRenderer extends React.Component<IProps> {
public override shouldComponentUpdate(nextProps: Readonly<IProps>): boolean {
const props = this.props;
export class HeadingRenderer extends React.Component<IProperties> {
public override shouldComponentUpdate(nextProperties: Readonly<IProperties>): boolean {
const properties = this.props;
return (
props.depth !== nextProps.depth ||
props.identifier !== nextProps.identifier ||
props.children !== nextProps.children ||
props.linkIcon !== nextProps.linkIcon
properties.depth !== nextProperties.depth ||
properties.identifier !== nextProperties.identifier ||
properties.children !== nextProperties.children ||
properties.linkIcon !== nextProperties.linkIcon
);
}

View file

@ -10,13 +10,20 @@ import { ImageRendererInner } from "./inner/ImageRendererInner";
* @see https://www.npmjs.com/package/@yozora/ast#image
* @see https://www.npmjs.com/package/@yozora/tokenizer-image
*/
export const ImageRenderer: INodeRenderer<Image> = props => {
const { url: src, alt, title, srcSet, sizes, loading } = props as Image & React.ImgHTMLAttributes<HTMLElement>;
export const ImageRenderer: INodeRenderer<Image> = properties => {
const {
url: source,
alt,
title,
srcSet,
sizes,
loading,
} = properties as Image & React.ImgHTMLAttributes<HTMLElement>;
return (
<ImageRendererInner
alt={alt}
src={src}
src={source}
title={title}
srcSet={srcSet}
sizes={sizes}

View file

@ -10,19 +10,19 @@ import { ImageRendererInner } from "./inner/ImageRendererInner";
* @see https://www.npmjs.com/package/@yozora/ast#imageReference
* @see https://www.npmjs.com/package/@yozora/tokenizer-image-reference
*/
export const ImageReferenceRenderer: INodeRenderer<ImageReference> = props => {
export const ImageReferenceRenderer: INodeRenderer<ImageReference> = properties => {
const { viewmodel } = useNodeRendererContext();
const definitionMap: Readonly<Record<string, Definition>> = useStateValue(viewmodel.definitionMap$);
const { alt, srcSet, sizes, loading } = props as ImageReference & React.ImgHTMLAttributes<HTMLElement>;
const { alt, srcSet, sizes, loading } = properties as ImageReference & React.ImgHTMLAttributes<HTMLElement>;
const definition = definitionMap[props.identifier];
const src: string = definition?.url ?? "";
const definition = definitionMap[properties.identifier];
const source: string = definition?.url ?? "";
const title: string | undefined = definition?.title;
return (
<ImageRendererInner
alt={alt}
src={src}
src={source}
title={title}
srcSet={srcSet}
sizes={sizes}

View file

@ -47,10 +47,10 @@ export function buildNodeRendererMap(
let hasChanged = false;
const result: INodeRendererMap = {} as unknown as INodeRendererMap;
for (const [key, val] of Object.entries(customizedRendererMap)) {
if (val && val !== defaultNodeRendererMap[key]) {
for (const [key, value] of Object.entries(customizedRendererMap)) {
if (value && value !== defaultNodeRendererMap[key]) {
hasChanged = true;
result[key] = val;
result[key] = value;
}
}

View file

@ -10,9 +10,9 @@ import { astClasses } from "../context";
* @see https://www.npmjs.com/package/@yozora/tokenizer-inline-code
*/
export class InlineCodeRenderer extends React.Component<InlineCode> {
public override shouldComponentUpdate(nextProps: Readonly<InlineCode>): boolean {
const props = this.props;
return props.value !== nextProps.value;
public override shouldComponentUpdate(nextProperties: Readonly<InlineCode>): boolean {
const properties = this.props;
return properties.value !== nextProperties.value;
}
public override render(): React.ReactElement {

View file

@ -4,7 +4,7 @@ import React from "react";
import { astClasses } from "../../context";
import { CopyButton } from "./CopyButton";
interface IProps {
interface IProperties {
darken: boolean;
lang: string;
value: string;
@ -16,7 +16,7 @@ interface IProps {
failedText?: string;
}
export class CodeRendererInner extends React.PureComponent<IProps> {
export class CodeRendererInner extends React.PureComponent<IProperties> {
public override render(): React.ReactElement {
const { calcContentForCopy } = this;
const { darken, lang, value, preferCodeWrap, showCodeLineno } = this.props;
@ -30,7 +30,7 @@ export class CodeRendererInner extends React.PureComponent<IProps> {
showLineNo={showCodeLineno && !preferCodeWrap}
darken={darken}
/>
<div className={copyBtnCls}>
<div className={copyButtonCls}>
<CopyButton calcContentForCopy={calcContentForCopy} />
</div>
</code>
@ -42,7 +42,7 @@ export class CodeRendererInner extends React.PureComponent<IProps> {
};
}
const copyBtnCls = css({
const copyButtonCls = css({
position: "absolute",
right: "4px",
top: "4px",
@ -58,7 +58,7 @@ const codeCls = cx(
borderRadius: "4px",
margin: "0px 0px 1.25em 0px",
backgroundColor: "var(--colorBgCode)",
[`&:hover > .${copyBtnCls}`]: {
[`&:hover > .${copyButtonCls}`]: {
display: "inline-block",
},
[`&&[data-wrap="true"] > div`]: {

View file

@ -1,7 +1,7 @@
import { css } from "@emotion/css";
import React from "react";
interface IProps {
interface IProperties {
src: string;
alt: string;
title: string | undefined;
@ -11,17 +11,17 @@ interface IProps {
className: string;
}
export class ImageRendererInner extends React.Component<IProps> {
public override shouldComponentUpdate(nextProps: IProps): boolean {
const props = this.props;
export class ImageRendererInner extends React.Component<IProperties> {
public override shouldComponentUpdate(nextProperties: IProperties): boolean {
const properties = this.props;
return (
props.src !== nextProps.src ||
props.alt !== nextProps.alt ||
props.title !== nextProps.title ||
props.srcSet !== nextProps.srcSet ||
props.sizes !== nextProps.sizes ||
props.loading !== nextProps.loading ||
props.className !== nextProps.className
properties.src !== nextProperties.src ||
properties.alt !== nextProperties.alt ||
properties.title !== nextProperties.title ||
properties.srcSet !== nextProperties.srcSet ||
properties.sizes !== nextProperties.sizes ||
properties.loading !== nextProperties.loading ||
properties.className !== nextProperties.className
);
}

View file

@ -3,21 +3,21 @@ import type { Node } from "@yozora/ast";
import React from "react";
import { NodesRenderer } from "../../NodesRenderer";
interface IProps {
interface IProperties {
url: string;
title: string | undefined;
childNodes: Node[] | undefined;
className: string;
}
export class LinkRendererInner extends React.Component<IProps> {
public override shouldComponentUpdate(nextProps: Readonly<IProps>): boolean {
const props = this.props;
export class LinkRendererInner extends React.Component<IProperties> {
public override shouldComponentUpdate(nextProperties: Readonly<IProperties>): boolean {
const properties = this.props;
return (
props.url !== nextProps.url ||
props.title !== nextProps.title ||
props.childNodes !== nextProps.childNodes ||
props.className !== nextProps.className
properties.url !== nextProperties.url ||
properties.title !== nextProperties.title ||
properties.childNodes !== nextProperties.childNodes ||
properties.className !== nextProperties.className
);
}

View file

@ -10,7 +10,7 @@ import { LinkRendererInner } from "./inner/LinkRendererInner";
* @see https://www.npmjs.com/package/@yozora/tokenizer-autolink
* @see https://www.npmjs.com/package/@yozora/tokenizer-autolink-extension
*/
export const LinkRenderer: INodeRenderer<Link> = props => {
const { url, title, children: childNodes } = props;
export const LinkRenderer: INodeRenderer<Link> = properties => {
const { url, title, children: childNodes } = properties;
return <LinkRendererInner url={url} title={title} childNodes={childNodes} className={astClasses.link} />;
};

View file

@ -9,11 +9,13 @@ import { LinkRendererInner } from "./inner/LinkRendererInner";
* @see https://www.npmjs.com/package/@yozora/ast#linkReference
* @see https://www.npmjs.com/package/@yozora/tokenizer-link-reference
*/
export const LinkReferenceRenderer: INodeRenderer<LinkReference> = props => {
export const LinkReferenceRenderer: INodeRenderer<LinkReference> = properties => {
const { viewmodel } = useNodeRendererContext();
const definitionMap: Readonly<Record<string, Definition>> = useStateValue(viewmodel.definitionMap$);
const definition = definitionMap[props.identifier];
const definition = definitionMap[properties.identifier];
const url: string = definition?.url ?? "";
const title: string | undefined = definition?.title;
return <LinkRendererInner url={url} title={title} childNodes={props.children} className={astClasses.linkReference} />;
return (
<LinkRendererInner url={url} title={title} childNodes={properties.children} className={astClasses.linkReference} />
);
};

View file

@ -11,13 +11,13 @@ import { NodesRenderer } from "../NodesRenderer";
* @see https://www.npmjs.com/package/@yozora/tokenizer-list
*/
export class ListRenderer extends React.Component<List> {
public override shouldComponentUpdate(nextProps: Readonly<List>): boolean {
const props = this.props;
public override shouldComponentUpdate(nextProperties: Readonly<List>): boolean {
const properties = this.props;
return (
props.ordered !== nextProps.ordered ||
props.orderType !== nextProps.orderType ||
props.start !== nextProps.start ||
props.children !== nextProps.children
properties.ordered !== nextProperties.ordered ||
properties.orderType !== nextProperties.orderType ||
properties.start !== nextProperties.start ||
properties.children !== nextProperties.children
);
}

View file

@ -11,9 +11,9 @@ import { NodesRenderer } from "../NodesRenderer";
* @see https://www.npmjs.com/package/@yozora/tokenizer-list-item
*/
export class ListItemRenderer extends React.Component<ListItem> {
public override shouldComponentUpdate(nextProps: Readonly<ListItem>): boolean {
const props = this.props;
return props.children !== nextProps.children;
public override shouldComponentUpdate(nextProperties: Readonly<ListItem>): boolean {
const properties = this.props;
return properties.children !== nextProperties.children;
}
public override render(): React.ReactElement {

View file

@ -12,9 +12,9 @@ import { NodesRenderer } from "../NodesRenderer";
* @see https://www.npmjs.com/package/@yozora/tokenizer-paragraph
*/
export class ParagraphRenderer extends React.Component<Paragraph> {
public override shouldComponentUpdate(nextProps: Readonly<Paragraph>): boolean {
const props = this.props;
return props.children !== nextProps.children;
public override shouldComponentUpdate(nextProperties: Readonly<Paragraph>): boolean {
const properties = this.props;
return properties.children !== nextProperties.children;
}
public override render(): React.ReactElement {

View file

@ -11,9 +11,9 @@ import { NodesRenderer } from "../NodesRenderer";
* @see https://www.npmjs.com/package/@yozora/tokenizer-emphasis
*/
export class StrongRenderer extends React.Component<Strong> {
public override shouldComponentUpdate(nextProps: Readonly<Strong>): boolean {
const props = this.props;
return props.children !== nextProps.children;
public override shouldComponentUpdate(nextProperties: Readonly<Strong>): boolean {
const properties = this.props;
return properties.children !== nextProperties.children;
}
public override render(): React.ReactElement {

View file

@ -16,23 +16,25 @@ import { NodesRenderer } from "../NodesRenderer";
* @see https://www.npmjs.com/package/@yozora/tokenizer-table-cell
*/
export class TableRenderer extends React.Component<Table> {
public override shouldComponentUpdate(nextProps: Readonly<Table>): boolean {
const props = this.props;
return !isEqual(props.columns, nextProps.columns) || !isEqual(props.children, nextProps.children);
public override shouldComponentUpdate(nextProperties: Readonly<Table>): boolean {
const properties = this.props;
return (
!isEqual(properties.columns, nextProperties.columns) || !isEqual(properties.children, nextProperties.children)
);
}
public override render(): React.ReactElement {
const { columns, children: rows } = this.props;
const aligns = columns.map(col => col.align ?? undefined);
const [ths, ...tds] = rows.map(row =>
row.children.map((cell, idx) => <NodesRenderer key={idx} nodes={cell.children} />),
row.children.map((cell, index) => <NodesRenderer key={index} nodes={cell.children} />),
);
return (
<table className={cls}>
<thead>
<tr>
{ths.map((children, idx) => (
<Th key={idx} align={aligns[idx]}>
{ths.map((children, index) => (
<Th key={index} align={aligns[index]}>
{children}
</Th>
))}
@ -41,8 +43,8 @@ export class TableRenderer extends React.Component<Table> {
<tbody>
{tds.map((row, rowIndex) => (
<tr key={rowIndex}>
{row.map((children, idx) => (
<td key={idx} align={aligns[idx]}>
{row.map((children, index) => (
<td key={index} align={aligns[index]}>
{children}
</td>
))}
@ -54,22 +56,22 @@ export class TableRenderer extends React.Component<Table> {
}
}
interface IThProps {
interface IThProperties {
align: "left" | "center" | "right" | undefined;
children: React.ReactNode;
}
class Th extends React.Component<IThProps> {
class Th extends React.Component<IThProperties> {
protected readonly ref: React.RefObject<HTMLTableCellElement>;
constructor(props: IThProps) {
super(props);
constructor(properties: IThProperties) {
super(properties);
this.ref = { current: null };
}
public override shouldComponentUpdate(nextProps: Readonly<IThProps>): boolean {
const props = this.props;
return props.align !== nextProps.align || props.children !== nextProps.children;
public override shouldComponentUpdate(nextProperties: Readonly<IThProperties>): boolean {
const properties = this.props;
return properties.align !== nextProperties.align || properties.children !== nextProperties.children;
}
public override render(): React.ReactElement {
@ -84,6 +86,7 @@ class Th extends React.Component<IThProps> {
public override componentDidMount(): void {
const th = this.ref.current;
if (th) {
// eslint-disable-next-line unicorn/prefer-dom-node-text-content
th.setAttribute("title", th.innerText);
}
}
@ -91,6 +94,7 @@ class Th extends React.Component<IThProps> {
public override componentDidUpdate(): void {
const th = this.ref.current;
if (th) {
// eslint-disable-next-line unicorn/prefer-dom-node-text-content
th.setAttribute("title", th.innerText);
}
}

View file

@ -8,9 +8,9 @@ import React from "react";
* @see https://www.npmjs.com/package/@yozora/tokenizer-text
*/
export class TextRenderer extends React.Component<Text> {
public override shouldComponentUpdate(nextProps: Readonly<Text>): boolean {
const props = this.props;
return props.value !== nextProps.value;
public override shouldComponentUpdate(nextProperties: Readonly<Text>): boolean {
const properties = this.props;
return properties.value !== nextProperties.value;
}
public override render(): React.ReactElement {

View file

@ -26,7 +26,7 @@ interface ContextItemType {
score?: number;
}
interface NewsDetailProps {
interface NewsDetailProperties {
news: {
title: string;
sections: {
@ -37,7 +37,7 @@ interface NewsDetailProps {
};
}
const NewsDetail: React.FC<NewsDetailProps> = ({ news }) => {
const NewsDetail: React.FC<NewsDetailProperties> = ({ news }) => {
return (
<article className="prose lg:prose-xl dark:prose-invert">
<h1 className="text-black dark:text-white">{news.title}</h1>
@ -47,8 +47,8 @@ const NewsDetail: React.FC<NewsDetailProps> = ({ news }) => {
<p className="text-black dark:text-white">{section.content}</p>
<div className="mt-4">
<h3 className="text-black dark:text-white">Related Context:</h3>
{section.context.map((item, i) => (
<ContextItem key={i} item={item} />
{section.context.map((item, index_) => (
<ContextItem key={index_} item={item} />
))}
</div>
</section>