chore: deprecate marked

This commit is contained in:
Steven 2023-12-17 11:02:16 +08:00
parent bcfcd59642
commit 8095d94c97
30 changed files with 5 additions and 828 deletions

View file

@ -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;
}

View file

@ -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";

View file

@ -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;
};

View file

@ -1,4 +0,0 @@
export const matcher = (rawStr: string, regexp: RegExp) => {
const matchResult = rawStr.match(regexp);
return matchResult;
};

View file

@ -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,
};

View file

@ -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,
};

View file

@ -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,
};

View file

@ -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,
};

View file

@ -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,
};

View file

@ -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,
};

View file

@ -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,
};

View file

@ -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,
};

View file

@ -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,
};

View file

@ -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,
};

View file

@ -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,
};

View file

@ -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,
};

View file

@ -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,
};

View file

@ -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,
};

View file

@ -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,
};

View file

@ -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,
};

View file

@ -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,
};

View file

@ -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,
};

View file

@ -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,
};

View file

@ -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,
};

View file

@ -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,
};

View file

@ -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,
};

View file

@ -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,
};

View file

@ -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,
];

View file

@ -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
View file

@ -0,0 +1 @@
export const TAG_REG = /#([^\s#,]+)/;