waveterm/src/app/common/elements/markdown.tsx
2024-05-08 07:54:40 +08:00

121 lines
3.9 KiB
TypeScript

// Copyright 2023, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import * as React from "react";
import * as mobxReact from "mobx-react";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import { clsx } from "clsx";
import { GlobalModel } from "@/models";
import { v4 as uuidv4 } from "uuid";
import "./markdown.less";
import { boundMethod } from "autobind-decorator";
function LinkRenderer(props: any): any {
let newUrl = "https://extern?" + encodeURIComponent(props.href);
return (
<a href={newUrl} target="_blank" rel={"noopener"}>
{props.children}
</a>
);
}
function HeaderRenderer(props: any, hnum: number): any {
return <div className={clsx("title", "is-" + hnum)}>{props.children}</div>;
}
function CodeRenderer(props: any): any {
return <code>{props.children}</code>;
}
@mobxReact.observer
class CodeBlockMarkdown extends React.Component<
{ children: React.ReactNode; codeSelectSelectedIndex?: number; uuid: string },
{}
> {
blockIndex: number;
blockRef: React.RefObject<HTMLPreElement>;
constructor(props) {
super(props);
this.blockRef = React.createRef();
this.blockIndex = GlobalModel.inputModel.addCodeBlockToCodeSelect(this.blockRef, this.props.uuid);
}
@boundMethod
handleClick(e: React.MouseEvent<HTMLPreElement>) {
console.log("this.blockIndex", this.blockIndex);
GlobalModel.inputModel.setCodeSelectSelectedCodeBlock(this.blockIndex);
}
render() {
console.log("this.blockIndex", this.blockIndex);
let selected = this.blockIndex == this.props.codeSelectSelectedIndex;
return (
<pre ref={this.blockRef} className={clsx({ selected: selected })} onClick={this.handleClick}>
{this.props.children}
</pre>
);
}
}
@mobxReact.observer
class Markdown extends React.Component<
{ text: string; style?: any; extraClassName?: string; codeSelect?: boolean },
{}
> {
curUuid: string;
constructor(props) {
super(props);
this.curUuid = uuidv4();
}
@boundMethod
codeBlockRenderer(props: any, codeSelect: boolean, codeSelectIndex: number, curUuid: string): any {
if (codeSelect) {
return (
<CodeBlockMarkdown codeSelectSelectedIndex={codeSelectIndex} uuid={curUuid}>
{props.children}
</CodeBlockMarkdown>
);
} else {
const clickHandler = (e: React.MouseEvent<HTMLElement>) => {
let blockText = (e.target as HTMLElement).innerText;
if (blockText) {
blockText = blockText.replace(/\n$/, ""); // remove trailing newline
navigator.clipboard.writeText(blockText);
}
};
return <pre onClick={(event) => clickHandler(event)}>{props.children}</pre>;
}
}
render() {
let { text } = this.props;
let codeSelect = this.props.codeSelect;
let curCodeSelectIndex = GlobalModel.inputModel.getCodeSelectSelectedIndex();
let markdownComponents = {
a: LinkRenderer,
h1: (props) => HeaderRenderer(props, 1),
h2: (props) => HeaderRenderer(props, 2),
h3: (props) => HeaderRenderer(props, 3),
h4: (props) => HeaderRenderer(props, 4),
h5: (props) => HeaderRenderer(props, 5),
h6: (props) => HeaderRenderer(props, 6),
code: (props) => CodeRenderer(props),
pre: (props) => this.codeBlockRenderer(props, codeSelect, curCodeSelectIndex, this.curUuid),
};
return (
<div className={clsx("markdown content", this.props.extraClassName)} style={this.props.style}>
<ReactMarkdown remarkPlugins={[remarkGfm]} components={markdownComponents}>
{text}
</ReactMarkdown>
</div>
);
}
}
export { Markdown };