mirror of
https://github.com/usememos/memos.git
synced 2025-03-03 16:53:30 +08:00
chore: deprecate marked
This commit is contained in:
parent
bcfcd59642
commit
8095d94c97
30 changed files with 5 additions and 828 deletions
|
@ -3,10 +3,9 @@ import React, { useEffect, useState } from "react";
|
|||
import { toast } from "react-hot-toast";
|
||||
import { tagServiceClient } from "@/grpcweb";
|
||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
||||
import { matcher } from "@/labs/marked/matcher";
|
||||
import Tag from "@/labs/marked/parser/Tag";
|
||||
import { useTagStore } from "@/store/module";
|
||||
import { useTranslate } from "@/utils/i18n";
|
||||
import { TAG_REG } from "@/utils/tag";
|
||||
import { generateDialog } from "./Dialog";
|
||||
import Icon from "./Icon";
|
||||
import OverflowTip from "./kit/OverflowTip";
|
||||
|
@ -14,7 +13,7 @@ import OverflowTip from "./kit/OverflowTip";
|
|||
type Props = DialogProps;
|
||||
|
||||
const validateTagName = (tagName: string): boolean => {
|
||||
const matchResult = matcher(`#${tagName}`, Tag.regexp);
|
||||
const matchResult = `#${tagName}`.match(TAG_REG);
|
||||
if (!matchResult || matchResult[1] !== tagName) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -5,10 +5,10 @@ import MemoFilter from "@/components/MemoFilter";
|
|||
import { DEFAULT_MEMO_LIMIT } from "@/helpers/consts";
|
||||
import { getTimeStampByDate } from "@/helpers/datetime";
|
||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
||||
import { TAG_REG } from "@/labs/marked/parser";
|
||||
import { useFilterStore, useMemoStore } from "@/store/module";
|
||||
import { extractUsernameFromName } from "@/store/v1";
|
||||
import { useTranslate } from "@/utils/i18n";
|
||||
import { TAG_REG } from "@/utils/tag";
|
||||
import Empty from "./Empty";
|
||||
import Memo from "./Memo";
|
||||
|
||||
|
|
|
@ -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,
|
||||
];
|
|
@ -5,9 +5,9 @@ import Memo from "@/components/Memo";
|
|||
import MemoFilter from "@/components/MemoFilter";
|
||||
import MobileHeader from "@/components/MobileHeader";
|
||||
import { DEFAULT_MEMO_LIMIT } from "@/helpers/consts";
|
||||
import { TAG_REG } from "@/labs/marked/parser";
|
||||
import { useFilterStore, useMemoStore } from "@/store/module";
|
||||
import { useTranslate } from "@/utils/i18n";
|
||||
import { TAG_REG } from "@/utils/tag";
|
||||
|
||||
const Explore = () => {
|
||||
const t = useTranslate();
|
||||
|
|
1
web/src/utils/tag.ts
Normal file
1
web/src/utils/tag.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export const TAG_REG = /#([^\s#,]+)/;
|
Loading…
Reference in a new issue