mirror of https://github.com/usememos/memos
chore: deprecate marked
parent
bcfcd59642
commit
8095d94c97
@ -1,146 +0,0 @@
|
|||||||
import { matcher } from "./matcher";
|
|
||||||
import { blockElementParserList, inlineElementParserList } from "./parser";
|
|
||||||
|
|
||||||
type Parser = {
|
|
||||||
name: string;
|
|
||||||
regexp: RegExp;
|
|
||||||
renderer: (rawStr: string) => JSX.Element | string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const findMatchingParser = (parsers: Parser[], markdownStr: string): Parser | undefined => {
|
|
||||||
let matchedParser = undefined;
|
|
||||||
let matchedIndex = -1;
|
|
||||||
|
|
||||||
for (const parser of parsers) {
|
|
||||||
const matchResult = matcher(markdownStr, parser.regexp);
|
|
||||||
if (!matchResult) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parser.name === "plain text" && matchedParser !== undefined) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const startIndex = matchResult.index as number;
|
|
||||||
if (matchedParser === undefined || matchedIndex > startIndex) {
|
|
||||||
matchedParser = parser;
|
|
||||||
matchedIndex = startIndex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return matchedParser;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const marked = (
|
|
||||||
markdownStr: string,
|
|
||||||
blockParsers = blockElementParserList,
|
|
||||||
inlineParsers = inlineElementParserList
|
|
||||||
): string | JSX.Element => {
|
|
||||||
const matchedBlockParser = findMatchingParser(blockParsers, markdownStr);
|
|
||||||
if (matchedBlockParser) {
|
|
||||||
const matchResult = matcher(markdownStr, matchedBlockParser.regexp);
|
|
||||||
if (matchResult) {
|
|
||||||
const matchedStr = matchResult[0];
|
|
||||||
const retainContent = markdownStr.slice(matchedStr.length);
|
|
||||||
|
|
||||||
if (matchedBlockParser.name === "br") {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{matchedBlockParser.renderer(matchedStr)}
|
|
||||||
{marked(retainContent, blockParsers, inlineParsers)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
if (retainContent === "") {
|
|
||||||
return matchedBlockParser.renderer(matchedStr);
|
|
||||||
} else if (retainContent.startsWith("\n")) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{matchedBlockParser.renderer(matchedStr)}
|
|
||||||
{marked(retainContent.slice(1), blockParsers, inlineParsers)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const matchedInlineParser = findMatchingParser(inlineParsers, markdownStr);
|
|
||||||
if (matchedInlineParser) {
|
|
||||||
const matchResult = matcher(markdownStr, matchedInlineParser.regexp);
|
|
||||||
if (matchResult) {
|
|
||||||
const matchedStr = matchResult[0];
|
|
||||||
const matchedLength = matchedStr.length;
|
|
||||||
const mIndex = matchResult.index || 0;
|
|
||||||
const prefixStr = markdownStr.slice(0, mIndex);
|
|
||||||
const suffixStr = markdownStr.slice(mIndex + matchedLength);
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{marked(prefixStr, [], inlineParsers)}
|
|
||||||
{matchedInlineParser.renderer(matchedStr)}
|
|
||||||
{marked(suffixStr, [], inlineParsers)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return <>{markdownStr}</>;
|
|
||||||
};
|
|
||||||
|
|
||||||
interface MatchedNode {
|
|
||||||
parserName: string;
|
|
||||||
matchedContent: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getMatchedNodes = (markdownStr: string): MatchedNode[] => {
|
|
||||||
const matchedNodeList: MatchedNode[] = [];
|
|
||||||
|
|
||||||
const walkthrough = (markdownStr: string, blockParsers = blockElementParserList, inlineParsers = inlineElementParserList): string => {
|
|
||||||
const matchedBlockParser = findMatchingParser(blockParsers, markdownStr);
|
|
||||||
if (matchedBlockParser) {
|
|
||||||
const matchResult = matcher(markdownStr, matchedBlockParser.regexp);
|
|
||||||
if (matchResult) {
|
|
||||||
const matchedStr = matchResult[0];
|
|
||||||
const retainContent = markdownStr.slice(matchedStr.length);
|
|
||||||
matchedNodeList.push({
|
|
||||||
parserName: matchedBlockParser.name,
|
|
||||||
matchedContent: matchedStr,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (matchedBlockParser.name === "br") {
|
|
||||||
return walkthrough(retainContent, blockParsers, inlineParsers);
|
|
||||||
} else {
|
|
||||||
if (matchedBlockParser.name !== "code block") {
|
|
||||||
walkthrough(matchedStr, [], inlineParsers);
|
|
||||||
}
|
|
||||||
if (retainContent.startsWith("\n")) {
|
|
||||||
return walkthrough(retainContent.slice(1), blockParsers, inlineParsers);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const matchedInlineParser = findMatchingParser(inlineParsers, markdownStr);
|
|
||||||
if (matchedInlineParser) {
|
|
||||||
const matchResult = matcher(markdownStr, matchedInlineParser.regexp);
|
|
||||||
if (matchResult) {
|
|
||||||
const matchedStr = matchResult[0];
|
|
||||||
const matchedLength = matchedStr.length;
|
|
||||||
const mIndex = matchResult.index || 0;
|
|
||||||
const suffixStr = markdownStr.slice(mIndex + matchedLength);
|
|
||||||
matchedNodeList.push({
|
|
||||||
parserName: matchedInlineParser.name,
|
|
||||||
matchedContent: matchedStr,
|
|
||||||
});
|
|
||||||
return walkthrough(suffixStr, [], inlineParsers);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return markdownStr;
|
|
||||||
};
|
|
||||||
|
|
||||||
walkthrough(markdownStr);
|
|
||||||
|
|
||||||
return matchedNodeList;
|
|
||||||
};
|
|
@ -1,4 +0,0 @@
|
|||||||
export const matcher = (rawStr: string, regexp: RegExp) => {
|
|
||||||
const matchResult = rawStr.match(regexp);
|
|
||||||
return matchResult;
|
|
||||||
};
|
|
@ -1,39 +0,0 @@
|
|||||||
import TeX from "@matejmazur/react-katex";
|
|
||||||
import "katex/dist/katex.min.css";
|
|
||||||
import { matcher } from "../matcher";
|
|
||||||
|
|
||||||
const BLOCK_LATEX_REG = new RegExp(
|
|
||||||
"\\$\\$(\\s*[^\\$\\s][^\\$]*?)\\$\\$|\\\\\\[(.+?)\\\\\\]|\\\\begin{equation}([\\s\\S]+?)\\\\end{equation}"
|
|
||||||
);
|
|
||||||
|
|
||||||
const blockRenderer = (rawStr: string) => {
|
|
||||||
const matchResult = matcher(rawStr, BLOCK_LATEX_REG);
|
|
||||||
if (!matchResult) {
|
|
||||||
return <>{rawStr}</>;
|
|
||||||
}
|
|
||||||
|
|
||||||
let latexCode = "";
|
|
||||||
|
|
||||||
if (matchResult[1]) {
|
|
||||||
// $$
|
|
||||||
latexCode = matchResult[1];
|
|
||||||
} else if (matchResult[2]) {
|
|
||||||
// \[ and \]
|
|
||||||
latexCode = matchResult[2];
|
|
||||||
} else if (matchResult[3]) {
|
|
||||||
// \begin{equation} and \end{equation}
|
|
||||||
latexCode = matchResult[3];
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="w-full max-w-full overflow-x-auto">
|
|
||||||
<TeX block={true}>{latexCode}</TeX>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "blockLatex",
|
|
||||||
regexp: BLOCK_LATEX_REG,
|
|
||||||
renderer: blockRenderer,
|
|
||||||
};
|
|
@ -1,21 +0,0 @@
|
|||||||
import { inlineElementParserList } from ".";
|
|
||||||
import { marked } from "..";
|
|
||||||
import { matcher } from "../matcher";
|
|
||||||
|
|
||||||
export const BLOCKQUOTE_REG = /^> ([^\n]+)/;
|
|
||||||
|
|
||||||
const renderer = (rawStr: string) => {
|
|
||||||
const matchResult = matcher(rawStr, BLOCKQUOTE_REG);
|
|
||||||
if (!matchResult) {
|
|
||||||
return <>{rawStr}</>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const parsedContent = marked(matchResult[1], [], inlineElementParserList);
|
|
||||||
return <blockquote>{parsedContent}</blockquote>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "blockquote",
|
|
||||||
regexp: BLOCKQUOTE_REG,
|
|
||||||
renderer,
|
|
||||||
};
|
|
@ -1,22 +0,0 @@
|
|||||||
import { marked } from "..";
|
|
||||||
import { matcher } from "../matcher";
|
|
||||||
import Link from "./Link";
|
|
||||||
import PlainText from "./PlainText";
|
|
||||||
|
|
||||||
export const BOLD_REG = /\*\*(.+?)\*\*/;
|
|
||||||
|
|
||||||
const renderer = (rawStr: string) => {
|
|
||||||
const matchResult = matcher(rawStr, BOLD_REG);
|
|
||||||
if (!matchResult) {
|
|
||||||
return <>{rawStr}</>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const parsedContent = marked(matchResult[1], [], [Link, PlainText]);
|
|
||||||
return <strong>{parsedContent}</strong>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "bold",
|
|
||||||
regexp: BOLD_REG,
|
|
||||||
renderer,
|
|
||||||
};
|
|
@ -1,26 +0,0 @@
|
|||||||
import { marked } from "..";
|
|
||||||
import { matcher } from "../matcher";
|
|
||||||
import Link from "./Link";
|
|
||||||
import PlainText from "./PlainText";
|
|
||||||
|
|
||||||
export const BOLD_EMPHASIS_REG = /\*\*\*(.+?)\*\*\*/;
|
|
||||||
|
|
||||||
const renderer = (rawStr: string) => {
|
|
||||||
const matchResult = matcher(rawStr, BOLD_EMPHASIS_REG);
|
|
||||||
if (!matchResult) {
|
|
||||||
return rawStr;
|
|
||||||
}
|
|
||||||
|
|
||||||
const parsedContent = marked(matchResult[1], [], [Link, PlainText]);
|
|
||||||
return (
|
|
||||||
<strong>
|
|
||||||
<em>{parsedContent}</em>
|
|
||||||
</strong>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "bold emphasis",
|
|
||||||
regexp: BOLD_EMPHASIS_REG,
|
|
||||||
renderer,
|
|
||||||
};
|
|
@ -1,16 +0,0 @@
|
|||||||
export const BR_REG = /^(\n+)/;
|
|
||||||
|
|
||||||
const renderer = (rawStr: string) => {
|
|
||||||
const length = rawStr.split("\n").length - 1;
|
|
||||||
const brList = [];
|
|
||||||
for (let i = 0; i < length; i++) {
|
|
||||||
brList.push(<br key={i} />);
|
|
||||||
}
|
|
||||||
return <>{...brList}</>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "br",
|
|
||||||
regexp: BR_REG,
|
|
||||||
renderer,
|
|
||||||
};
|
|
@ -1,48 +0,0 @@
|
|||||||
import copy from "copy-to-clipboard";
|
|
||||||
import hljs from "highlight.js";
|
|
||||||
import { toast } from "react-hot-toast";
|
|
||||||
import { matcher } from "../matcher";
|
|
||||||
|
|
||||||
export const CODE_BLOCK_REG = /^```(\S*?)\s([\s\S]*?)```/;
|
|
||||||
|
|
||||||
const renderer = (rawStr: string) => {
|
|
||||||
const matchResult = matcher(rawStr, CODE_BLOCK_REG);
|
|
||||||
if (!matchResult) {
|
|
||||||
return <>{rawStr}</>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const language = matchResult[1] || "plaintext";
|
|
||||||
let highlightedCode = hljs.highlightAuto(matchResult[2]).value;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const temp = hljs.highlight(matchResult[2], {
|
|
||||||
language,
|
|
||||||
}).value;
|
|
||||||
highlightedCode = temp;
|
|
||||||
} catch (error) {
|
|
||||||
// do nth
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleCopyButtonClick = () => {
|
|
||||||
copy(matchResult[2]);
|
|
||||||
toast.success("Copied to clipboard!");
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<pre className="group">
|
|
||||||
<button
|
|
||||||
className="text-xs font-mono italic absolute top-0 right-0 px-2 leading-6 border btn-text rounded opacity-0 group-hover:opacity-60"
|
|
||||||
onClick={handleCopyButtonClick}
|
|
||||||
>
|
|
||||||
copy
|
|
||||||
</button>
|
|
||||||
<code className={`language-${language}`} dangerouslySetInnerHTML={{ __html: highlightedCode }}></code>
|
|
||||||
</pre>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "code block",
|
|
||||||
regexp: CODE_BLOCK_REG,
|
|
||||||
renderer,
|
|
||||||
};
|
|
@ -1,29 +0,0 @@
|
|||||||
import { inlineElementParserList } from ".";
|
|
||||||
import { marked } from "..";
|
|
||||||
import { matcher } from "../matcher";
|
|
||||||
|
|
||||||
export const DONE_LIST_REG = /^( *)- \[[xX]\] ([^\n]+)/;
|
|
||||||
|
|
||||||
const renderer = (rawStr: string) => {
|
|
||||||
const matchResult = matcher(rawStr, DONE_LIST_REG);
|
|
||||||
if (!matchResult) {
|
|
||||||
return rawStr;
|
|
||||||
}
|
|
||||||
const space = matchResult[1];
|
|
||||||
const parsedContent = marked(matchResult[2], [], inlineElementParserList);
|
|
||||||
return (
|
|
||||||
<p className="li-container">
|
|
||||||
<span className="whitespace-pre">{space}</span>
|
|
||||||
<span className="todo-block done" data-value="DONE">
|
|
||||||
✓
|
|
||||||
</span>
|
|
||||||
<span>{parsedContent}</span>
|
|
||||||
</p>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "done list",
|
|
||||||
regexp: DONE_LIST_REG,
|
|
||||||
renderer,
|
|
||||||
};
|
|
@ -1,23 +0,0 @@
|
|||||||
import { marked } from "..";
|
|
||||||
import { matcher } from "../matcher";
|
|
||||||
import Link from "./Link";
|
|
||||||
import PlainLink from "./PlainLink";
|
|
||||||
import PlainText from "./PlainText";
|
|
||||||
|
|
||||||
export const EMPHASIS_REG = /\*(.+?)\*/;
|
|
||||||
|
|
||||||
const renderer = (rawStr: string) => {
|
|
||||||
const matchResult = matcher(rawStr, EMPHASIS_REG);
|
|
||||||
if (!matchResult) {
|
|
||||||
return rawStr;
|
|
||||||
}
|
|
||||||
|
|
||||||
const parsedContent = marked(matchResult[1], [], [Link, PlainLink, PlainText]);
|
|
||||||
return <em>{parsedContent}</em>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "emphasis",
|
|
||||||
regexp: EMPHASIS_REG,
|
|
||||||
renderer,
|
|
||||||
};
|
|
@ -1,34 +0,0 @@
|
|||||||
import { marked } from "..";
|
|
||||||
import { matcher } from "../matcher";
|
|
||||||
import InlineCode from "./InlineCode";
|
|
||||||
import Link from "./Link";
|
|
||||||
import PlainLink from "./PlainLink";
|
|
||||||
import PlainText from "./PlainText";
|
|
||||||
|
|
||||||
export const HEADING_REG = /^(#+) ([^\n]+)/;
|
|
||||||
|
|
||||||
const renderer = (rawStr: string) => {
|
|
||||||
const matchResult = matcher(rawStr, HEADING_REG);
|
|
||||||
if (!matchResult) {
|
|
||||||
return rawStr;
|
|
||||||
}
|
|
||||||
|
|
||||||
const level = matchResult[1].length;
|
|
||||||
const parsedContent = marked(matchResult[2], [], [InlineCode, Link, PlainLink, PlainText]);
|
|
||||||
if (level === 1) {
|
|
||||||
return <h1>{parsedContent}</h1>;
|
|
||||||
} else if (level === 2) {
|
|
||||||
return <h2>{parsedContent}</h2>;
|
|
||||||
} else if (level === 3) {
|
|
||||||
return <h3>{parsedContent}</h3>;
|
|
||||||
} else if (level === 4) {
|
|
||||||
return <h4>{parsedContent}</h4>;
|
|
||||||
}
|
|
||||||
return <h5>{parsedContent}</h5>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "heading",
|
|
||||||
regexp: HEADING_REG,
|
|
||||||
renderer,
|
|
||||||
};
|
|
@ -1,12 +0,0 @@
|
|||||||
export const HORIZONTAL_RULES_REG = /^_{3}|^-{3}|^\*{3}/;
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
export const renderer = (rawStr: string) => {
|
|
||||||
return <hr />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "horizontal rules",
|
|
||||||
regexp: HORIZONTAL_RULES_REG,
|
|
||||||
renderer,
|
|
||||||
};
|
|
@ -1,20 +0,0 @@
|
|||||||
import { absolutifyLink } from "@/helpers/utils";
|
|
||||||
import { matcher } from "../matcher";
|
|
||||||
|
|
||||||
export const IMAGE_REG = /!\[.*?\]\((.+?)\)/;
|
|
||||||
|
|
||||||
const renderer = (rawStr: string) => {
|
|
||||||
const matchResult = matcher(rawStr, IMAGE_REG);
|
|
||||||
if (!matchResult) {
|
|
||||||
return rawStr;
|
|
||||||
}
|
|
||||||
|
|
||||||
const imageUrl = absolutifyLink(matchResult[1]);
|
|
||||||
return <img className="img" src={imageUrl} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "image",
|
|
||||||
regexp: IMAGE_REG,
|
|
||||||
renderer,
|
|
||||||
};
|
|
@ -1,18 +0,0 @@
|
|||||||
import { matcher } from "../matcher";
|
|
||||||
|
|
||||||
export const INLINE_CODE_REG = /`(.+?)`/;
|
|
||||||
|
|
||||||
const renderer = (rawStr: string) => {
|
|
||||||
const matchResult = matcher(rawStr, INLINE_CODE_REG);
|
|
||||||
if (!matchResult) {
|
|
||||||
return rawStr;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <code>{matchResult[1]}</code>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "inline code",
|
|
||||||
regexp: INLINE_CODE_REG,
|
|
||||||
renderer,
|
|
||||||
};
|
|
@ -1,28 +0,0 @@
|
|||||||
import TeX from "@matejmazur/react-katex";
|
|
||||||
import "katex/dist/katex.min.css";
|
|
||||||
|
|
||||||
export const LATEX_INLINE_REG = /\$(.+?)\$|\\\((.+?)\\\)/;
|
|
||||||
|
|
||||||
const inlineRenderer = (rawStr: string) => {
|
|
||||||
const matchResult = LATEX_INLINE_REG.exec(rawStr);
|
|
||||||
if (matchResult) {
|
|
||||||
let latexCode = "";
|
|
||||||
if (matchResult[1]) {
|
|
||||||
latexCode = matchResult[1];
|
|
||||||
} else if (matchResult[2]) {
|
|
||||||
latexCode = matchResult[2];
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<span className="max-w-full overflow-x-auto">
|
|
||||||
<TeX key={latexCode}>{latexCode}</TeX>
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return rawStr;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "inlineLatex",
|
|
||||||
regexp: LATEX_INLINE_REG,
|
|
||||||
renderer: inlineRenderer,
|
|
||||||
};
|
|
@ -1,28 +0,0 @@
|
|||||||
import { marked } from "..";
|
|
||||||
import { matcher } from "../matcher";
|
|
||||||
import Bold from "./Bold";
|
|
||||||
import BoldEmphasis from "./BoldEmphasis";
|
|
||||||
import Emphasis from "./Emphasis";
|
|
||||||
import InlineCode from "./InlineCode";
|
|
||||||
import PlainText from "./PlainText";
|
|
||||||
|
|
||||||
export const LINK_REG = /\[([^\]]+)\]\(([^)]+)\)/;
|
|
||||||
|
|
||||||
const renderer = (rawStr: string) => {
|
|
||||||
const matchResult = matcher(rawStr, LINK_REG);
|
|
||||||
if (!matchResult) {
|
|
||||||
return rawStr;
|
|
||||||
}
|
|
||||||
const parsedContent = marked(matchResult[1], [], [InlineCode, BoldEmphasis, Emphasis, Bold, PlainText]);
|
|
||||||
return (
|
|
||||||
<a className="link" target="_blank" href={matchResult[2]}>
|
|
||||||
{parsedContent}
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "link",
|
|
||||||
regexp: LINK_REG,
|
|
||||||
renderer,
|
|
||||||
};
|
|
@ -1,27 +0,0 @@
|
|||||||
import { inlineElementParserList } from ".";
|
|
||||||
import { marked } from "..";
|
|
||||||
import { matcher } from "../matcher";
|
|
||||||
|
|
||||||
export const ORDERED_LIST_REG = /^( *)(\d+)\. (.+)/;
|
|
||||||
|
|
||||||
const renderer = (rawStr: string) => {
|
|
||||||
const matchResult = matcher(rawStr, ORDERED_LIST_REG);
|
|
||||||
if (!matchResult) {
|
|
||||||
return rawStr;
|
|
||||||
}
|
|
||||||
const space = matchResult[1];
|
|
||||||
const parsedContent = marked(matchResult[3], [], inlineElementParserList);
|
|
||||||
return (
|
|
||||||
<p className="li-container">
|
|
||||||
<span className="whitespace-pre">{space}</span>
|
|
||||||
<span className="ol-block">{matchResult[2]}.</span>
|
|
||||||
<span>{parsedContent}</span>
|
|
||||||
</p>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "ordered list",
|
|
||||||
regexp: ORDERED_LIST_REG,
|
|
||||||
renderer,
|
|
||||||
};
|
|
@ -1,15 +0,0 @@
|
|||||||
import { inlineElementParserList } from ".";
|
|
||||||
import { marked } from "..";
|
|
||||||
|
|
||||||
export const PARAGRAPH_REG = /^([^\n]+)/;
|
|
||||||
|
|
||||||
const renderer = (rawStr: string) => {
|
|
||||||
const parsedContent = marked(rawStr, [], inlineElementParserList);
|
|
||||||
return <p>{parsedContent}</p>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "paragraph",
|
|
||||||
regexp: PARAGRAPH_REG,
|
|
||||||
renderer,
|
|
||||||
};
|
|
@ -1,22 +0,0 @@
|
|||||||
import { matcher } from "../matcher";
|
|
||||||
|
|
||||||
export const PLAIN_LINK_REG = /((?:https?|chrome|edge):\/\/[^ ]+)/;
|
|
||||||
|
|
||||||
const renderer = (rawStr: string) => {
|
|
||||||
const matchResult = matcher(rawStr, PLAIN_LINK_REG);
|
|
||||||
if (!matchResult) {
|
|
||||||
return rawStr;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<a className="link" target="_blank" href={matchResult[1]}>
|
|
||||||
{matchResult[1]}
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "plain link",
|
|
||||||
regexp: PLAIN_LINK_REG,
|
|
||||||
renderer,
|
|
||||||
};
|
|
@ -1,18 +0,0 @@
|
|||||||
import { matcher } from "../matcher";
|
|
||||||
|
|
||||||
export const PLAIN_TEXT_REG = /(.+)/;
|
|
||||||
|
|
||||||
const renderer = (rawStr: string): string => {
|
|
||||||
const matchResult = matcher(rawStr, PLAIN_TEXT_REG);
|
|
||||||
if (!matchResult) {
|
|
||||||
return rawStr;
|
|
||||||
}
|
|
||||||
|
|
||||||
return matchResult[1];
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "plain text",
|
|
||||||
regexp: PLAIN_TEXT_REG,
|
|
||||||
renderer,
|
|
||||||
};
|
|
@ -1,18 +0,0 @@
|
|||||||
import { matcher } from "../matcher";
|
|
||||||
|
|
||||||
export const STRIKETHROUGH_REG = /~~(.+?)~~/;
|
|
||||||
|
|
||||||
const renderer = (rawStr: string) => {
|
|
||||||
const matchResult = matcher(rawStr, STRIKETHROUGH_REG);
|
|
||||||
if (!matchResult) {
|
|
||||||
return rawStr;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <del>{matchResult[1]}</del>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "Strikethrough",
|
|
||||||
regexp: STRIKETHROUGH_REG,
|
|
||||||
renderer,
|
|
||||||
};
|
|
@ -1,81 +0,0 @@
|
|||||||
import { CSSProperties } from "react";
|
|
||||||
import { inlineElementParserList } from ".";
|
|
||||||
import { marked } from "..";
|
|
||||||
import { matcher } from "../matcher";
|
|
||||||
|
|
||||||
class TableRegExp extends RegExp {
|
|
||||||
[Symbol.match](str: string): RegExpMatchArray | null {
|
|
||||||
const result = RegExp.prototype[Symbol.match].call(this, str);
|
|
||||||
// regex will only be considered valid if headers and delimiters column count matches
|
|
||||||
if (!result || splitPipeDelimiter(result[1]).length != splitPipeDelimiter(result[2]).length) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const TABLE_REG = new TableRegExp(/^([^\n|]*\|[^\n]*)\n([ \t:-]*(?<!\\)\|[ \t:|-]*)((?:\n[^\n|]*\|[^\n]*)+)/);
|
|
||||||
|
|
||||||
const splitPipeDelimiter = (rawStr: string) => {
|
|
||||||
// loose pipe delimiter for markdown tables. escaped pipes are supported. some examples:
|
|
||||||
// | aaaa | bbbb | cc\|cc | => ["aaaa", "bbbb", "cc|cc"]
|
|
||||||
// aaaa | bbbb | cc\|cc => ["aaaa", "bbbb", "cc|cc"]
|
|
||||||
// |a|f => ["a", "f"]
|
|
||||||
// ||a|f| => ["", "a", "f"]
|
|
||||||
// |||| => ["", "", ""]
|
|
||||||
// |\||\||\|| => ["|", "|", "|"]
|
|
||||||
return (
|
|
||||||
rawStr
|
|
||||||
.replaceAll(/(?<!\\)\|/g, "| ")
|
|
||||||
.trim()
|
|
||||||
.match(/(?:\\\||[^|])+/g) || []
|
|
||||||
).map((cell) => cell.replaceAll("\\|", "|").trim());
|
|
||||||
// TODO: Need to move backslash escaping (to PlainText ?) for all characters
|
|
||||||
// described in markdown spec (\`*_{}[]()#+-.!), and not just the pipe symbol here
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderer = (rawStr: string) => {
|
|
||||||
const matchResult = matcher(rawStr, TABLE_REG);
|
|
||||||
if (!matchResult) {
|
|
||||||
return rawStr;
|
|
||||||
}
|
|
||||||
const headerContents = splitPipeDelimiter(matchResult[1]);
|
|
||||||
const cellStyles: CSSProperties[] = splitPipeDelimiter(matchResult[2]).map((cell) => {
|
|
||||||
const left = cell.startsWith(":");
|
|
||||||
const right = cell.endsWith(":");
|
|
||||||
// github markdown spec says that by default, content is left aligned
|
|
||||||
return {
|
|
||||||
textAlign: left && right ? "center" : right ? "right" : "left",
|
|
||||||
};
|
|
||||||
});
|
|
||||||
const rowContents = matchResult[3].substring(1).split(/\r?\n/).map(splitPipeDelimiter);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
{headerContents.map((header, index) => (
|
|
||||||
<th key={index}>{marked(header, [], inlineElementParserList)}</th>
|
|
||||||
))}
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{rowContents.map((row, rowIndex) => (
|
|
||||||
<tr key={rowIndex} className="dark:even:bg-zinc-600 even:bg-zinc-100">
|
|
||||||
{headerContents.map((_, cellIndex) => (
|
|
||||||
<td key={cellIndex} style={cellStyles[cellIndex]}>
|
|
||||||
{cellIndex < row.length ? marked(row[cellIndex], [], inlineElementParserList) : null}
|
|
||||||
</td>
|
|
||||||
))}
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "table",
|
|
||||||
regexp: TABLE_REG,
|
|
||||||
renderer,
|
|
||||||
};
|
|
@ -1,18 +0,0 @@
|
|||||||
import { matcher } from "../matcher";
|
|
||||||
|
|
||||||
export const TAG_REG = /#([^\s#,]+)/;
|
|
||||||
|
|
||||||
const renderer = (rawStr: string) => {
|
|
||||||
const matchResult = matcher(rawStr, TAG_REG);
|
|
||||||
if (!matchResult) {
|
|
||||||
return rawStr;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <span className="tag-span">#{matchResult[1]}</span>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "tag",
|
|
||||||
regexp: TAG_REG,
|
|
||||||
renderer,
|
|
||||||
};
|
|
@ -1,27 +0,0 @@
|
|||||||
import { inlineElementParserList } from ".";
|
|
||||||
import { marked } from "..";
|
|
||||||
import { matcher } from "../matcher";
|
|
||||||
|
|
||||||
export const TODO_LIST_REG = /^( *)- \[ \] ([^\n]+)/;
|
|
||||||
|
|
||||||
const renderer = (rawStr: string) => {
|
|
||||||
const matchResult = matcher(rawStr, TODO_LIST_REG);
|
|
||||||
if (!matchResult) {
|
|
||||||
return rawStr;
|
|
||||||
}
|
|
||||||
const space = matchResult[1];
|
|
||||||
const parsedContent = marked(matchResult[2], [], inlineElementParserList);
|
|
||||||
return (
|
|
||||||
<p className="li-container">
|
|
||||||
<span className="whitespace-pre">{space}</span>
|
|
||||||
<span className="todo-block todo" data-value="TODO"></span>
|
|
||||||
<span>{parsedContent}</span>
|
|
||||||
</p>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "todo list",
|
|
||||||
regexp: TODO_LIST_REG,
|
|
||||||
renderer,
|
|
||||||
};
|
|
@ -1,27 +0,0 @@
|
|||||||
import { inlineElementParserList } from ".";
|
|
||||||
import { marked } from "..";
|
|
||||||
import { matcher } from "../matcher";
|
|
||||||
|
|
||||||
export const UNORDERED_LIST_REG = /^( *)[*-] ([^\n]+)/;
|
|
||||||
|
|
||||||
const renderer = (rawStr: string) => {
|
|
||||||
const matchResult = matcher(rawStr, UNORDERED_LIST_REG);
|
|
||||||
if (!matchResult) {
|
|
||||||
return rawStr;
|
|
||||||
}
|
|
||||||
const space = matchResult[1];
|
|
||||||
const parsedContent = marked(matchResult[2], [], inlineElementParserList);
|
|
||||||
return (
|
|
||||||
<p className="li-container">
|
|
||||||
<span className="whitespace-pre">{space}</span>
|
|
||||||
<span className="ul-block">•</span>
|
|
||||||
<span>{parsedContent}</span>
|
|
||||||
</p>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "unordered list",
|
|
||||||
regexp: UNORDERED_LIST_REG,
|
|
||||||
renderer,
|
|
||||||
};
|
|
@ -1,56 +0,0 @@
|
|||||||
import BlockLatex from "./BlockLatex";
|
|
||||||
import Blockquote from "./Blockquote";
|
|
||||||
import Bold from "./Bold";
|
|
||||||
import BoldEmphasis from "./BoldEmphasis";
|
|
||||||
import Br from "./Br";
|
|
||||||
import CodeBlock from "./CodeBlock";
|
|
||||||
import DoneList from "./DoneList";
|
|
||||||
import Emphasis from "./Emphasis";
|
|
||||||
import Heading from "./Heading";
|
|
||||||
import HorizontalRules from "./HorizontalRules";
|
|
||||||
import Image from "./Image";
|
|
||||||
import InlineCode from "./InlineCode";
|
|
||||||
import InlineLatex from "./InlineLatex";
|
|
||||||
import Link from "./Link";
|
|
||||||
import OrderedList from "./OrderedList";
|
|
||||||
import Paragraph from "./Paragraph";
|
|
||||||
import PlainLink from "./PlainLink";
|
|
||||||
import PlainText from "./PlainText";
|
|
||||||
import Strikethrough from "./Strikethrough";
|
|
||||||
import Table from "./Table";
|
|
||||||
import Tag from "./Tag";
|
|
||||||
import TodoList from "./TodoList";
|
|
||||||
import UnorderedList from "./UnorderedList";
|
|
||||||
|
|
||||||
export { TAG_REG } from "./Tag";
|
|
||||||
export { LINK_REG } from "./Link";
|
|
||||||
export { PLAIN_LINK_REG } from "./PlainLink";
|
|
||||||
|
|
||||||
// The order determines the order of execution.
|
|
||||||
export const blockElementParserList = [
|
|
||||||
BlockLatex,
|
|
||||||
Br,
|
|
||||||
CodeBlock,
|
|
||||||
Blockquote,
|
|
||||||
Table,
|
|
||||||
Heading,
|
|
||||||
TodoList,
|
|
||||||
DoneList,
|
|
||||||
OrderedList,
|
|
||||||
UnorderedList,
|
|
||||||
HorizontalRules,
|
|
||||||
Paragraph,
|
|
||||||
];
|
|
||||||
export const inlineElementParserList = [
|
|
||||||
InlineLatex,
|
|
||||||
Image,
|
|
||||||
BoldEmphasis,
|
|
||||||
Bold,
|
|
||||||
Emphasis,
|
|
||||||
Link,
|
|
||||||
InlineCode,
|
|
||||||
PlainLink,
|
|
||||||
Strikethrough,
|
|
||||||
Tag,
|
|
||||||
PlainText,
|
|
||||||
];
|
|
@ -0,0 +1 @@
|
|||||||
|
export const TAG_REG = /#([^\s#,]+)/;
|
Loading…
Reference in New Issue