mirror of
https://github.com/usememos/memos.git
synced 2024-12-01 06:34:35 +03:00
refactor: return jsx element instead of string in marked (#910)
* refactor: return jsx element instead of string in marked * chore: update
This commit is contained in:
parent
491859bbf6
commit
0f8ce3dd16
@ -2,6 +2,7 @@ import { TextField } from "@mui/joy";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTagStore } from "../store/module";
|
||||
import { getTagSuggestionList } from "../helpers/api";
|
||||
import { matcher } from "../labs/marked/matcher";
|
||||
import Tag from "../labs/marked/parser/Tag";
|
||||
import Icon from "./Icon";
|
||||
import toastHelper from "./Toast";
|
||||
@ -10,7 +11,7 @@ import { generateDialog } from "./Dialog";
|
||||
type Props = DialogProps;
|
||||
|
||||
const validateTagName = (tagName: string): boolean => {
|
||||
const matchResult = Tag.matcher(`#${tagName}`);
|
||||
const matchResult = matcher(`#${tagName}`, Tag.regexp);
|
||||
if (!matchResult || matchResult[1] !== tagName) {
|
||||
return false;
|
||||
}
|
||||
|
@ -15,7 +15,6 @@ import "../less/memo.less";
|
||||
|
||||
interface Props {
|
||||
memo: Memo;
|
||||
highlightWord?: string;
|
||||
}
|
||||
|
||||
export const getFormatedMemoTimeStr = (time: number, locale = "en"): string => {
|
||||
@ -27,7 +26,7 @@ export const getFormatedMemoTimeStr = (time: number, locale = "en"): string => {
|
||||
};
|
||||
|
||||
const Memo: React.FC<Props> = (props: Props) => {
|
||||
const { memo, highlightWord } = props;
|
||||
const { memo } = props;
|
||||
const { t, i18n } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const editorStore = useEditorStore();
|
||||
@ -143,9 +142,6 @@ const Memo: React.FC<Props> = (props: Props) => {
|
||||
if (imgUrl) {
|
||||
showPreviewImageDialog([imgUrl], 0);
|
||||
}
|
||||
} else if (targetEl.tagName === "BUTTON" && targetEl.className === "codeblock-copy-btn") {
|
||||
copy(targetEl.parentElement?.children[1].textContent ?? "");
|
||||
toastHelper.success(t("message.succeed-copy-code"));
|
||||
}
|
||||
};
|
||||
|
||||
@ -228,7 +224,6 @@ const Memo: React.FC<Props> = (props: Props) => {
|
||||
</div>
|
||||
<MemoContent
|
||||
content={memo.content}
|
||||
highlightWord={highlightWord}
|
||||
onMemoContentClick={handleMemoContentClick}
|
||||
onMemoContentDoubleClick={handleMemoContentDoubleClick}
|
||||
/>
|
||||
|
@ -2,7 +2,6 @@ import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useUserStore } from "../store/module";
|
||||
import { marked } from "../labs/marked";
|
||||
import { highlightWithWord } from "../labs/highlighter";
|
||||
import Icon from "./Icon";
|
||||
import "../less/memo-content.less";
|
||||
|
||||
@ -12,7 +11,6 @@ export interface DisplayConfig {
|
||||
|
||||
interface Props {
|
||||
content: string;
|
||||
highlightWord?: string;
|
||||
className?: string;
|
||||
displayConfig?: Partial<DisplayConfig>;
|
||||
onMemoContentClick?: (e: React.MouseEvent) => void;
|
||||
@ -30,14 +28,14 @@ const defaultDisplayConfig: DisplayConfig = {
|
||||
};
|
||||
|
||||
const MemoContent: React.FC<Props> = (props: Props) => {
|
||||
const { className, content, highlightWord, onMemoContentClick, onMemoContentDoubleClick } = props;
|
||||
const { className, content, onMemoContentClick, onMemoContentDoubleClick } = props;
|
||||
const { t } = useTranslation();
|
||||
const userStore = useUserStore();
|
||||
const user = userStore.state.user;
|
||||
const foldedContent = useMemo(() => {
|
||||
const firstHorizontalRuleIndex = content.search(/^---$|^\*\*\*$|^___$/m);
|
||||
return firstHorizontalRuleIndex !== -1 ? content.slice(0, firstHorizontalRuleIndex) : content;
|
||||
}, [content]);
|
||||
const { t } = useTranslation();
|
||||
const userStore = useUserStore();
|
||||
const user = userStore.state.user;
|
||||
|
||||
const [state, setState] = useState<State>({
|
||||
expandButtonStatus: -1,
|
||||
@ -97,10 +95,9 @@ const MemoContent: React.FC<Props> = (props: Props) => {
|
||||
className={`memo-content-text ${state.expandButtonStatus === 0 ? "expanded" : ""}`}
|
||||
onClick={handleMemoContentClick}
|
||||
onDoubleClick={handleMemoContentDoubleClick}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: highlightWithWord(marked(state.expandButtonStatus === 0 ? foldedContent : content), highlightWord),
|
||||
}}
|
||||
></div>
|
||||
>
|
||||
{marked(state.expandButtonStatus === 0 ? foldedContent : content)}
|
||||
</div>
|
||||
{state.expandButtonStatus !== -1 && (
|
||||
<div className="expand-btn-container">
|
||||
<span className={`btn ${state.expandButtonStatus === 0 ? "expand-btn" : "fold-btn"}`} onClick={handleExpandBtnClick}>
|
||||
|
@ -19,7 +19,6 @@ const MemoList = () => {
|
||||
const memoDisplayTsOption = userStore.state.user?.setting.memoDisplayTsOption;
|
||||
const { memos, isFetching } = memoStore.state;
|
||||
const [isComplete, setIsComplete] = useState<boolean>(false);
|
||||
const [highlightWord, setHighlightWord] = useState<string | undefined>("");
|
||||
|
||||
const { tag: tagQuery, duration, type: memoType, text: textQuery, shortcutId, visibility } = query ?? {};
|
||||
const shortcut = shortcutId ? shortcutStore.getShortcutById(shortcutId) : null;
|
||||
@ -107,7 +106,6 @@ const MemoList = () => {
|
||||
if (pageWrapper) {
|
||||
pageWrapper.scrollTo(0, 0);
|
||||
}
|
||||
setHighlightWord(query?.text);
|
||||
}, [query]);
|
||||
|
||||
useEffect(() => {
|
||||
@ -136,7 +134,7 @@ const MemoList = () => {
|
||||
return (
|
||||
<div className="memo-list-container">
|
||||
{sortedMemos.map((memo) => (
|
||||
<Memo key={`${memo.id}-${memo.displayTs}`} memo={memo} highlightWord={highlightWord} />
|
||||
<Memo key={`${memo.id}-${memo.displayTs}`} memo={memo} />
|
||||
))}
|
||||
{isFetching ? (
|
||||
<div className="status-text-container fetching-tip">
|
||||
|
@ -1,24 +0,0 @@
|
||||
import { escape } from "lodash";
|
||||
|
||||
const walkthroughNodeWithKeyword = (node: HTMLElement, keyword: string) => {
|
||||
if (node.nodeType === 3) {
|
||||
const span = document.createElement("span");
|
||||
span.innerHTML = node.nodeValue?.replace(new RegExp(keyword, "g"), `<mark>${keyword}</mark>`) ?? "";
|
||||
node.parentNode?.insertBefore(span, node);
|
||||
node.parentNode?.removeChild(node);
|
||||
}
|
||||
for (const child of Array.from(node.childNodes)) {
|
||||
walkthroughNodeWithKeyword(<HTMLElement>child, keyword);
|
||||
}
|
||||
return node.innerHTML;
|
||||
};
|
||||
|
||||
export const highlightWithWord = (html: string, keyword?: string): string => {
|
||||
if (!keyword) {
|
||||
return html;
|
||||
}
|
||||
keyword = escape(keyword);
|
||||
const wrap = document.createElement("div");
|
||||
wrap.innerHTML = escape(html);
|
||||
return walkthroughNodeWithKeyword(wrap, keyword);
|
||||
};
|
@ -1,8 +1,13 @@
|
||||
import { matcher } from "./matcher";
|
||||
import { blockElementParserList, inlineElementParserList } from "./parser";
|
||||
|
||||
export const marked = (markdownStr: string, blockParsers = blockElementParserList, inlineParsers = inlineElementParserList): string => {
|
||||
export const marked = (
|
||||
markdownStr: string,
|
||||
blockParsers = blockElementParserList,
|
||||
inlineParsers = inlineElementParserList
|
||||
): string | JSX.Element => {
|
||||
for (const parser of blockParsers) {
|
||||
const matchResult = parser.matcher(markdownStr);
|
||||
const matchResult = matcher(markdownStr, parser.regexp);
|
||||
if (!matchResult) {
|
||||
continue;
|
||||
}
|
||||
@ -10,12 +15,22 @@ export const marked = (markdownStr: string, blockParsers = blockElementParserLis
|
||||
const retainContent = markdownStr.slice(matchedStr.length);
|
||||
|
||||
if (parser.name === "br") {
|
||||
return parser.renderer(matchedStr) + marked(retainContent, blockParsers, inlineParsers);
|
||||
return (
|
||||
<>
|
||||
{parser.renderer(matchedStr)}
|
||||
{marked(retainContent, blockParsers, inlineParsers)}
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
if (retainContent === "") {
|
||||
return parser.renderer(matchedStr);
|
||||
} else if (retainContent.startsWith("\n")) {
|
||||
return parser.renderer(matchedStr) + marked(retainContent.slice(1), blockParsers, inlineParsers);
|
||||
return (
|
||||
<>
|
||||
{parser.renderer(matchedStr)}
|
||||
{marked(retainContent.slice(1), blockParsers, inlineParsers)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -24,7 +39,7 @@ export const marked = (markdownStr: string, blockParsers = blockElementParserLis
|
||||
let matchedIndex = -1;
|
||||
|
||||
for (const parser of inlineParsers) {
|
||||
const matchResult = parser.matcher(markdownStr);
|
||||
const matchResult = matcher(markdownStr, parser.regexp);
|
||||
if (!matchResult) {
|
||||
continue;
|
||||
}
|
||||
@ -41,17 +56,23 @@ export const marked = (markdownStr: string, blockParsers = blockElementParserLis
|
||||
}
|
||||
|
||||
if (matchedInlineParser) {
|
||||
const matchResult = matchedInlineParser.matcher(markdownStr);
|
||||
const matchResult = matcher(markdownStr, matchedInlineParser.regexp);
|
||||
if (matchResult) {
|
||||
const matchedStr = matchResult[0];
|
||||
const matchedLength = matchedStr.length;
|
||||
const prefixStr = markdownStr.slice(0, matchedIndex);
|
||||
const suffixStr = markdownStr.slice(matchedIndex + matchedLength);
|
||||
return marked(prefixStr, [], inlineParsers) + matchedInlineParser.renderer(matchedStr) + marked(suffixStr, [], inlineParsers);
|
||||
return (
|
||||
<>
|
||||
{marked(prefixStr, [], inlineParsers)}
|
||||
{matchedInlineParser.renderer(matchedStr)}
|
||||
{marked(suffixStr, [], inlineParsers)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return markdownStr;
|
||||
return <>{markdownStr}</>;
|
||||
};
|
||||
|
||||
interface MatchedNode {
|
||||
@ -64,7 +85,7 @@ export const getMatchedNodes = (markdownStr: string): MatchedNode[] => {
|
||||
|
||||
const walkthough = (markdownStr: string, blockParsers = blockElementParserList, inlineParsers = inlineElementParserList): string => {
|
||||
for (const parser of blockParsers) {
|
||||
const matchResult = parser.matcher(markdownStr);
|
||||
const matchResult = matcher(markdownStr, parser.regexp);
|
||||
if (!matchResult) {
|
||||
continue;
|
||||
}
|
||||
@ -79,6 +100,7 @@ export const getMatchedNodes = (markdownStr: string): MatchedNode[] => {
|
||||
return walkthough(retainContent, blockParsers, inlineParsers);
|
||||
} else {
|
||||
if (retainContent.startsWith("\n")) {
|
||||
walkthough(matchedStr, [], inlineParsers);
|
||||
return walkthough(retainContent.slice(1), blockParsers, inlineParsers);
|
||||
}
|
||||
}
|
||||
@ -88,7 +110,7 @@ export const getMatchedNodes = (markdownStr: string): MatchedNode[] => {
|
||||
let matchedIndex = -1;
|
||||
|
||||
for (const parser of inlineParsers) {
|
||||
const matchResult = parser.matcher(markdownStr);
|
||||
const matchResult = matcher(markdownStr, parser.regexp);
|
||||
if (!matchResult) {
|
||||
continue;
|
||||
}
|
||||
@ -105,7 +127,7 @@ export const getMatchedNodes = (markdownStr: string): MatchedNode[] => {
|
||||
}
|
||||
|
||||
if (matchedInlineParser) {
|
||||
const matchResult = matchedInlineParser.matcher(markdownStr);
|
||||
const matchResult = matcher(markdownStr, matchedInlineParser.regexp);
|
||||
if (matchResult) {
|
||||
const matchedStr = matchResult[0];
|
||||
const matchedLength = matchedStr.length;
|
@ -1,174 +0,0 @@
|
||||
/* eslint-disable no-irregular-whitespace */
|
||||
import { describe, expect, test } from "@jest/globals";
|
||||
import { unescape } from "lodash-es";
|
||||
import { marked } from ".";
|
||||
|
||||
describe("test marked parser", () => {
|
||||
test("horizontal rule", () => {
|
||||
const tests = [
|
||||
{
|
||||
markdown: `---
|
||||
This is some text after the horizontal rule.
|
||||
___
|
||||
This is some text after the horizontal rule.
|
||||
***
|
||||
This is some text after the horizontal rule.`,
|
||||
want: `<hr><p>This is some text after the horizontal rule.</p><hr><p>This is some text after the horizontal rule.</p><hr><p>This is some text after the horizontal rule.</p>`,
|
||||
},
|
||||
];
|
||||
for (const t of tests) {
|
||||
expect(unescape(marked(t.markdown))).toBe(t.want);
|
||||
}
|
||||
});
|
||||
test("parse code block", () => {
|
||||
const tests = [
|
||||
{
|
||||
markdown: `\`\`\`
|
||||
hello world!
|
||||
\`\`\``,
|
||||
want: `<pre><button class="codeblock-copy-btn">copy</button><code class="language-plaintext">hello world!
|
||||
</code></pre>`,
|
||||
},
|
||||
{
|
||||
markdown: `test code block
|
||||
|
||||
\`\`\`js
|
||||
console.log("hello world!")
|
||||
\`\`\``,
|
||||
want: `<p>test code block</p><br><pre><button class="codeblock-copy-btn">copy</button><code class="language-js"><span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">"hello world!"</span>)
|
||||
</code></pre>`,
|
||||
},
|
||||
];
|
||||
|
||||
for (const t of tests) {
|
||||
expect(unescape(marked(t.markdown))).toBe(t.want);
|
||||
}
|
||||
});
|
||||
test("parse todo list block", () => {
|
||||
const tests = [
|
||||
{
|
||||
markdown: `My task:
|
||||
- [ ] finish my homework
|
||||
- [x] yahaha`,
|
||||
want: `<p>My task:</p><p class='li-container'><span class='todo-block todo' data-value='TODO'></span><span>finish my homework</span></p><p class='li-container'><span class='todo-block done' data-value='DONE'>✓</span><span>yahaha</span></p>`,
|
||||
},
|
||||
];
|
||||
|
||||
for (const t of tests) {
|
||||
expect(unescape(marked(t.markdown))).toBe(t.want);
|
||||
}
|
||||
});
|
||||
test("parse list block", () => {
|
||||
const tests = [
|
||||
{
|
||||
markdown: `This is a list
|
||||
* list 123
|
||||
1. 123123`,
|
||||
want: `<p>This is a list</p><p class='li-container'><span class='ul-block'>•</span><span>list 123</span></p><p class='li-container'><span class='ol-block'>1.</span><span>123123</span></p>`,
|
||||
},
|
||||
];
|
||||
|
||||
for (const t of tests) {
|
||||
expect(unescape(marked(t.markdown))).toBe(t.want);
|
||||
}
|
||||
});
|
||||
test("parse inline element", () => {
|
||||
const tests = [
|
||||
{
|
||||
markdown: `Link: [baidu](https://baidu.com#1231)`,
|
||||
want: `<p>Link: <a class='link' target='_blank' rel='noreferrer' href='https://baidu.com#1231'>baidu</a></p>`,
|
||||
},
|
||||
];
|
||||
|
||||
for (const t of tests) {
|
||||
expect(unescape(marked(t.markdown))).toBe(t.want);
|
||||
}
|
||||
});
|
||||
test("parse inline code within inline element", () => {
|
||||
const tests = [
|
||||
{
|
||||
markdown: `Link: [\`baidu\`](https://baidu.com)`,
|
||||
want: `<p>Link: <a class='link' target='_blank' rel='noreferrer' href='https://baidu.com'><code>baidu</code></a></p>`,
|
||||
},
|
||||
];
|
||||
|
||||
for (const t of tests) {
|
||||
expect(unescape(marked(t.markdown))).toBe(t.want);
|
||||
}
|
||||
});
|
||||
test("parse plain link", () => {
|
||||
const tests = [
|
||||
{
|
||||
markdown: `Link:https://baidu.com#1231`,
|
||||
want: `<p>Link:<a class='link' target='_blank' rel='noreferrer' href='https://baidu.com#1231'>https://baidu.com#1231</a></p>`,
|
||||
},
|
||||
];
|
||||
|
||||
for (const t of tests) {
|
||||
expect(unescape(marked(t.markdown))).toBe(t.want);
|
||||
}
|
||||
});
|
||||
test("parse inline code", () => {
|
||||
const tests = [
|
||||
{
|
||||
markdown: `Code: \`console.log("Hello world!")\``,
|
||||
want: `<p>Code: <code>console.log("Hello world!")</code></p>`,
|
||||
},
|
||||
];
|
||||
|
||||
for (const t of tests) {
|
||||
expect(unescape(marked(t.markdown))).toBe(t.want);
|
||||
}
|
||||
});
|
||||
test("parse bold and em text", () => {
|
||||
const tests = [
|
||||
{
|
||||
markdown: `Important: **Minecraft**`,
|
||||
want: `<p>Important: <strong>Minecraft</strong></p>`,
|
||||
},
|
||||
{
|
||||
markdown: `Em: *Minecraft*`,
|
||||
want: `<p>Em: <em>Minecraft</em></p>`,
|
||||
},
|
||||
{
|
||||
markdown: `Important: ***Minecraft/123***`,
|
||||
want: `<p>Important: <strong><em>Minecraft/123</em></strong></p>`,
|
||||
},
|
||||
{
|
||||
markdown: `Important: ***[baidu](https://baidu.com)***`,
|
||||
want: `<p>Important: <strong><em><a class='link' target='_blank' rel='noreferrer' href='https://baidu.com'>baidu</a></em></strong></p>`,
|
||||
},
|
||||
];
|
||||
|
||||
for (const t of tests) {
|
||||
expect(unescape(marked(t.markdown))).toBe(t.want);
|
||||
}
|
||||
});
|
||||
test("parse full width space", () => {
|
||||
const tests = [
|
||||
{
|
||||
markdown: ` line1
|
||||
line2`,
|
||||
want: `<p> line1</p><p> line2</p>`,
|
||||
},
|
||||
];
|
||||
for (const t of tests) {
|
||||
expect(unescape(marked(t.markdown))).toBe(t.want);
|
||||
}
|
||||
});
|
||||
test("parse heading", () => {
|
||||
const tests = [
|
||||
{
|
||||
markdown: `# 123 `,
|
||||
want: `<h1>123 </h1>`,
|
||||
},
|
||||
{
|
||||
markdown: `## 123 `,
|
||||
want: `<h2>123 </h2>`,
|
||||
},
|
||||
];
|
||||
for (const t of tests) {
|
||||
expect(unescape(marked(t.markdown))).toBe(t.want);
|
||||
}
|
||||
});
|
||||
});
|
4
web/src/labs/marked/matcher.ts
Normal file
4
web/src/labs/marked/matcher.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export const matcher = (rawStr: string, regexp: RegExp) => {
|
||||
const matchResult = rawStr.match(regexp);
|
||||
return matchResult;
|
||||
};
|
@ -1,24 +0,0 @@
|
||||
import { escape } from "lodash";
|
||||
|
||||
export const BLOCKQUOTE_REG = /^> ([^\n]+)/;
|
||||
|
||||
const matcher = (rawStr: string) => {
|
||||
const matchResult = rawStr.match(BLOCKQUOTE_REG);
|
||||
return matchResult;
|
||||
};
|
||||
|
||||
const renderer = (rawStr: string): string => {
|
||||
const matchResult = matcher(rawStr);
|
||||
if (!matchResult) {
|
||||
return rawStr;
|
||||
}
|
||||
|
||||
return `<blockquote>${escape(matchResult[1])}</blockquote>`;
|
||||
};
|
||||
|
||||
export default {
|
||||
name: "blockquote",
|
||||
regex: BLOCKQUOTE_REG,
|
||||
matcher,
|
||||
renderer,
|
||||
};
|
19
web/src/labs/marked/parser/Blockquote.tsx
Normal file
19
web/src/labs/marked/parser/Blockquote.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
import { escape } from "lodash";
|
||||
import { matcher } from "../matcher";
|
||||
|
||||
export const BLOCKQUOTE_REG = /^> ([^\n]+)/;
|
||||
|
||||
const renderer = (rawStr: string) => {
|
||||
const matchResult = matcher(rawStr, BLOCKQUOTE_REG);
|
||||
if (!matchResult) {
|
||||
return <>{rawStr}</>;
|
||||
}
|
||||
|
||||
return <blockquote>{escape(matchResult[1])}</blockquote>;
|
||||
};
|
||||
|
||||
export default {
|
||||
name: "blockquote",
|
||||
regexp: BLOCKQUOTE_REG,
|
||||
renderer,
|
||||
};
|
@ -1,27 +0,0 @@
|
||||
import { marked } from "..";
|
||||
import Link from "./Link";
|
||||
import PlainText from "./PlainText";
|
||||
|
||||
export const BOLD_REG = /\*\*(.+?)\*\*/;
|
||||
|
||||
const matcher = (rawStr: string) => {
|
||||
const matchResult = rawStr.match(BOLD_REG);
|
||||
return matchResult;
|
||||
};
|
||||
|
||||
const renderer = (rawStr: string): string => {
|
||||
const matchResult = matcher(rawStr);
|
||||
if (!matchResult) {
|
||||
return rawStr;
|
||||
}
|
||||
|
||||
const parsedContent = marked(matchResult[1], [], [Link, PlainText]);
|
||||
return `<strong>${parsedContent}</strong>`;
|
||||
};
|
||||
|
||||
export default {
|
||||
name: "bold",
|
||||
regex: BOLD_REG,
|
||||
matcher,
|
||||
renderer,
|
||||
};
|
22
web/src/labs/marked/parser/Bold.tsx
Normal file
22
web/src/labs/marked/parser/Bold.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
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,27 +1,26 @@
|
||||
import { marked } from "..";
|
||||
import { matcher } from "../matcher";
|
||||
import Link from "./Link";
|
||||
import PlainText from "./PlainText";
|
||||
|
||||
export const BOLD_EMPHASIS_REG = /\*\*\*(.+?)\*\*\*/;
|
||||
|
||||
const matcher = (rawStr: string) => {
|
||||
const matchResult = rawStr.match(BOLD_EMPHASIS_REG);
|
||||
return matchResult;
|
||||
};
|
||||
|
||||
const renderer = (rawStr: string): string => {
|
||||
const matchResult = matcher(rawStr);
|
||||
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>`;
|
||||
return (
|
||||
<strong>
|
||||
<em>${parsedContent}</em>
|
||||
</strong>
|
||||
);
|
||||
};
|
||||
|
||||
export default {
|
||||
name: "bold emphasis",
|
||||
regex: BOLD_EMPHASIS_REG,
|
||||
matcher,
|
||||
regexp: BOLD_EMPHASIS_REG,
|
||||
renderer,
|
||||
};
|
@ -1,17 +0,0 @@
|
||||
export const BR_REG = /^(\n+)/;
|
||||
|
||||
const matcher = (rawStr: string) => {
|
||||
const matchResult = rawStr.match(BR_REG);
|
||||
return matchResult;
|
||||
};
|
||||
|
||||
const renderer = (rawStr: string): string => {
|
||||
return rawStr.replaceAll("\n", "<br>");
|
||||
};
|
||||
|
||||
export default {
|
||||
name: "br",
|
||||
regex: BR_REG,
|
||||
matcher,
|
||||
renderer,
|
||||
};
|
16
web/src/labs/marked/parser/Br.tsx
Normal file
16
web/src/labs/marked/parser/Br.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
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 />);
|
||||
}
|
||||
return <>{...brList}</>;
|
||||
};
|
||||
|
||||
export default {
|
||||
name: "br",
|
||||
regexp: BR_REG,
|
||||
renderer,
|
||||
};
|
@ -1,37 +0,0 @@
|
||||
import { escape } from "lodash-es";
|
||||
import hljs from "highlight.js";
|
||||
|
||||
export const CODE_BLOCK_REG = /^```(\S*?)\s([\s\S]*?)```/;
|
||||
|
||||
const matcher = (rawStr: string) => {
|
||||
const matchResult = rawStr.match(CODE_BLOCK_REG);
|
||||
return matchResult;
|
||||
};
|
||||
|
||||
const renderer = (rawStr: string): string => {
|
||||
const matchResult = matcher(rawStr);
|
||||
if (!matchResult) {
|
||||
return rawStr;
|
||||
}
|
||||
|
||||
const language = escape(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
|
||||
}
|
||||
|
||||
return `<pre><button class="codeblock-copy-btn">copy</button><code class="language-${language}">${highlightedCode}</code></pre>`;
|
||||
};
|
||||
|
||||
export default {
|
||||
name: "code block",
|
||||
regex: CODE_BLOCK_REG,
|
||||
matcher,
|
||||
renderer,
|
||||
};
|
51
web/src/labs/marked/parser/CodeBlock.tsx
Normal file
51
web/src/labs/marked/parser/CodeBlock.tsx
Normal file
@ -0,0 +1,51 @@
|
||||
import copy from "copy-to-clipboard";
|
||||
import { escape } from "lodash-es";
|
||||
import hljs from "highlight.js";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { matcher } from "../matcher";
|
||||
import toastHelper from "../../../components/Toast";
|
||||
|
||||
export const CODE_BLOCK_REG = /^```(\S*?)\s([\s\S]*?)```/;
|
||||
|
||||
const renderer = (rawStr: string) => {
|
||||
const { t } = useTranslation();
|
||||
const matchResult = matcher(rawStr, CODE_BLOCK_REG);
|
||||
if (!matchResult) {
|
||||
return <>{rawStr}</>;
|
||||
}
|
||||
|
||||
const language = escape(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]);
|
||||
toastHelper.success(t("message.succeed-copy-code"));
|
||||
};
|
||||
|
||||
return (
|
||||
<pre>
|
||||
<button
|
||||
className="text-xs font-mono italic absolute top-0 right-0 px-2 leading-6 border btn-text rounded opacity-60"
|
||||
onClick={handleCopyButtonClick}
|
||||
>
|
||||
copy
|
||||
</button>
|
||||
<code className={`language-${language}`}>{highlightedCode}</code>
|
||||
</pre>
|
||||
);
|
||||
};
|
||||
|
||||
export default {
|
||||
name: "code block",
|
||||
regexp: CODE_BLOCK_REG,
|
||||
renderer,
|
||||
};
|
@ -1,26 +0,0 @@
|
||||
import { inlineElementParserList } from ".";
|
||||
import { marked } from "..";
|
||||
|
||||
export const DONE_LIST_REG = /^- \[[xX]\] ([^\n]+)/;
|
||||
|
||||
const matcher = (rawStr: string) => {
|
||||
const matchResult = rawStr.match(DONE_LIST_REG);
|
||||
return matchResult;
|
||||
};
|
||||
|
||||
const renderer = (rawStr: string): string => {
|
||||
const matchResult = matcher(rawStr);
|
||||
if (!matchResult) {
|
||||
return rawStr;
|
||||
}
|
||||
|
||||
const parsedContent = marked(matchResult[1], [], inlineElementParserList);
|
||||
return `<p class='li-container'><span class='todo-block done' data-value='DONE'>✓</span><span>${parsedContent}</span></p>`;
|
||||
};
|
||||
|
||||
export default {
|
||||
name: "done list",
|
||||
regex: DONE_LIST_REG,
|
||||
matcher,
|
||||
renderer,
|
||||
};
|
28
web/src/labs/marked/parser/DoneList.tsx
Normal file
28
web/src/labs/marked/parser/DoneList.tsx
Normal file
@ -0,0 +1,28 @@
|
||||
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 parsedContent = marked(matchResult[1], [], inlineElementParserList);
|
||||
return (
|
||||
<p className="li-container">
|
||||
<span className="todo-block done" data-value="DONE">
|
||||
✓
|
||||
</span>
|
||||
<span>{parsedContent}</span>
|
||||
</p>
|
||||
);
|
||||
};
|
||||
|
||||
export default {
|
||||
name: "done list",
|
||||
regexp: DONE_LIST_REG,
|
||||
renderer,
|
||||
};
|
@ -1,27 +1,22 @@
|
||||
import { marked } from "..";
|
||||
import { matcher } from "../matcher";
|
||||
import Link from "./Link";
|
||||
import PlainText from "./PlainText";
|
||||
|
||||
export const EMPHASIS_REG = /\*(.+?)\*/;
|
||||
|
||||
const matcher = (rawStr: string) => {
|
||||
const matchResult = rawStr.match(EMPHASIS_REG);
|
||||
return matchResult;
|
||||
};
|
||||
|
||||
const renderer = (rawStr: string): string => {
|
||||
const matchResult = matcher(rawStr);
|
||||
const renderer = (rawStr: string) => {
|
||||
const matchResult = matcher(rawStr, EMPHASIS_REG);
|
||||
if (!matchResult) {
|
||||
return rawStr;
|
||||
}
|
||||
|
||||
const parsedContent = marked(matchResult[1], [], [Link, PlainText]);
|
||||
return `<em>${parsedContent}</em>`;
|
||||
return <em>{parsedContent}</em>;
|
||||
};
|
||||
|
||||
export default {
|
||||
name: "emphasis",
|
||||
regex: EMPHASIS_REG,
|
||||
matcher,
|
||||
regexp: EMPHASIS_REG,
|
||||
renderer,
|
||||
};
|
@ -1,25 +0,0 @@
|
||||
import { escape } from "lodash";
|
||||
|
||||
export const HEADING_REG = /^(#+) ([^\n]+)/;
|
||||
|
||||
const matcher = (rawStr: string) => {
|
||||
const matchResult = rawStr.match(HEADING_REG);
|
||||
return matchResult;
|
||||
};
|
||||
|
||||
const renderer = (rawStr: string): string => {
|
||||
const matchResult = matcher(rawStr);
|
||||
if (!matchResult) {
|
||||
return rawStr;
|
||||
}
|
||||
|
||||
const level = matchResult[1].length;
|
||||
return `<h${level}>${escape(matchResult[2])}</h${level}>`;
|
||||
};
|
||||
|
||||
export default {
|
||||
name: "heading",
|
||||
regex: HEADING_REG,
|
||||
matcher,
|
||||
renderer,
|
||||
};
|
29
web/src/labs/marked/parser/Heading.tsx
Normal file
29
web/src/labs/marked/parser/Heading.tsx
Normal file
@ -0,0 +1,29 @@
|
||||
import { escape } from "lodash";
|
||||
import { matcher } from "../matcher";
|
||||
|
||||
export const HEADING_REG = /^(#+) ([^\n]+)/;
|
||||
|
||||
const renderer = (rawStr: string) => {
|
||||
const matchResult = matcher(rawStr, HEADING_REG);
|
||||
if (!matchResult) {
|
||||
return rawStr;
|
||||
}
|
||||
|
||||
const level = matchResult[1].length;
|
||||
if (level === 1) {
|
||||
return <h1>{escape(matchResult[2])}</h1>;
|
||||
} else if (level === 2) {
|
||||
return <h2>{escape(matchResult[2])}</h2>;
|
||||
} else if (level === 3) {
|
||||
return <h3>{escape(matchResult[2])}</h3>;
|
||||
} else if (level === 4) {
|
||||
return <h4>{escape(matchResult[2])}</h4>;
|
||||
}
|
||||
return <h5>{escape(matchResult[2])}</h5>;
|
||||
};
|
||||
|
||||
export default {
|
||||
name: "heading",
|
||||
regexp: HEADING_REG,
|
||||
renderer,
|
||||
};
|
@ -1,18 +0,0 @@
|
||||
export const HORIZONTAL_RULES_REG = /^_{3}|^-{3}|^\*{3}/;
|
||||
|
||||
const matcher = (rawStr: string) => {
|
||||
const matchResult = rawStr.match(HORIZONTAL_RULES_REG);
|
||||
return matchResult;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export const renderer = (rawStr: string): string => {
|
||||
return `<hr>`;
|
||||
};
|
||||
|
||||
export default {
|
||||
name: "horizontal rules",
|
||||
regex: HORIZONTAL_RULES_REG,
|
||||
matcher,
|
||||
renderer,
|
||||
};
|
12
web/src/labs/marked/parser/HorizontalRules.tsx
Normal file
12
web/src/labs/marked/parser/HorizontalRules.tsx
Normal file
@ -0,0 +1,12 @@
|
||||
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,26 +1,21 @@
|
||||
import { escape } from "lodash-es";
|
||||
import { absolutifyLink } from "../../../helpers/utils";
|
||||
import { matcher } from "../matcher";
|
||||
|
||||
export const IMAGE_REG = /!\[.*?\]\((.+?)\)/;
|
||||
|
||||
const matcher = (rawStr: string) => {
|
||||
const matchResult = rawStr.match(IMAGE_REG);
|
||||
return matchResult;
|
||||
};
|
||||
|
||||
const renderer = (rawStr: string): string => {
|
||||
const matchResult = matcher(rawStr);
|
||||
const renderer = (rawStr: string) => {
|
||||
const matchResult = matcher(rawStr, IMAGE_REG);
|
||||
if (!matchResult) {
|
||||
return rawStr;
|
||||
}
|
||||
|
||||
const imageUrl = absolutifyLink(escape(matchResult[1]));
|
||||
return `<img class='img' src='${imageUrl}' />`;
|
||||
return <img className="img" src={imageUrl} />;
|
||||
};
|
||||
|
||||
export default {
|
||||
name: "image",
|
||||
regex: IMAGE_REG,
|
||||
matcher,
|
||||
regexp: IMAGE_REG,
|
||||
renderer,
|
||||
};
|
@ -1,24 +0,0 @@
|
||||
import { escape } from "lodash-es";
|
||||
|
||||
export const INLINE_CODE_REG = /`(.+?)`/;
|
||||
|
||||
const matcher = (rawStr: string) => {
|
||||
const matchResult = rawStr.match(INLINE_CODE_REG);
|
||||
return matchResult;
|
||||
};
|
||||
|
||||
const renderer = (rawStr: string): string => {
|
||||
const matchResult = matcher(rawStr);
|
||||
if (!matchResult) {
|
||||
return rawStr;
|
||||
}
|
||||
|
||||
return `<code>${escape(matchResult[1])}</code>`;
|
||||
};
|
||||
|
||||
export default {
|
||||
name: "inline code",
|
||||
regex: INLINE_CODE_REG,
|
||||
matcher,
|
||||
renderer,
|
||||
};
|
19
web/src/labs/marked/parser/InlineCode.tsx
Normal file
19
web/src/labs/marked/parser/InlineCode.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
import { escape } from "lodash-es";
|
||||
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>{escape(matchResult[1])}</code>;
|
||||
};
|
||||
|
||||
export default {
|
||||
name: "inline code",
|
||||
regexp: INLINE_CODE_REG,
|
||||
renderer,
|
||||
};
|
@ -5,26 +5,25 @@ import { marked } from "..";
|
||||
import InlineCode from "./InlineCode";
|
||||
import BoldEmphasis from "./BoldEmphasis";
|
||||
import PlainText from "./PlainText";
|
||||
import { matcher } from "../matcher";
|
||||
|
||||
export const LINK_REG = /\[(.*?)\]\((.+?)\)+/;
|
||||
|
||||
const matcher = (rawStr: string) => {
|
||||
const matchResult = rawStr.match(LINK_REG);
|
||||
return matchResult;
|
||||
};
|
||||
|
||||
const renderer = (rawStr: string): string => {
|
||||
const matchResult = matcher(rawStr);
|
||||
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 class='link' target='_blank' rel='noreferrer' href='${escape(matchResult[2])}'>${parsedContent}</a>`;
|
||||
return (
|
||||
<a className="link" target="_blank" rel="noreferrer" href={escape(matchResult[2])}>
|
||||
{parsedContent}
|
||||
</a>
|
||||
);
|
||||
};
|
||||
|
||||
export default {
|
||||
name: "link",
|
||||
regex: LINK_REG,
|
||||
matcher,
|
||||
regexp: LINK_REG,
|
||||
renderer,
|
||||
};
|
@ -1,26 +0,0 @@
|
||||
import { inlineElementParserList } from ".";
|
||||
import { marked } from "..";
|
||||
|
||||
export const ORDERED_LIST_REG = /^(\d+)\. (.+)/;
|
||||
|
||||
const matcher = (rawStr: string) => {
|
||||
const matchResult = rawStr.match(ORDERED_LIST_REG);
|
||||
return matchResult;
|
||||
};
|
||||
|
||||
const renderer = (rawStr: string): string => {
|
||||
const matchResult = matcher(rawStr);
|
||||
if (!matchResult) {
|
||||
return rawStr;
|
||||
}
|
||||
|
||||
const parsedContent = marked(matchResult[2], [], inlineElementParserList);
|
||||
return `<p class='li-container'><span class='ol-block'>${matchResult[1]}.</span><span>${parsedContent}</span></p>`;
|
||||
};
|
||||
|
||||
export default {
|
||||
name: "ordered list",
|
||||
regex: ORDERED_LIST_REG,
|
||||
matcher,
|
||||
renderer,
|
||||
};
|
26
web/src/labs/marked/parser/OrderedList.tsx
Normal file
26
web/src/labs/marked/parser/OrderedList.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
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 parsedContent = marked(matchResult[2], [], inlineElementParserList);
|
||||
return (
|
||||
<p className="li-container">
|
||||
<span className="ol-block">{matchResult[1]}.</span>
|
||||
<span>{parsedContent}</span>
|
||||
</p>
|
||||
);
|
||||
};
|
||||
|
||||
export default {
|
||||
name: "ordered list",
|
||||
regexp: ORDERED_LIST_REG,
|
||||
renderer,
|
||||
};
|
@ -3,19 +3,13 @@ import { marked } from "..";
|
||||
|
||||
export const PARAGRAPH_REG = /^([^\n]+)/;
|
||||
|
||||
const matcher = (rawStr: string) => {
|
||||
const matchResult = rawStr.match(PARAGRAPH_REG);
|
||||
return matchResult;
|
||||
};
|
||||
|
||||
const renderer = (rawStr: string): string => {
|
||||
const renderer = (rawStr: string) => {
|
||||
const parsedContent = marked(rawStr, [], inlineElementParserList);
|
||||
return `<p>${parsedContent}</p>`;
|
||||
return <p>{parsedContent}</p>;
|
||||
};
|
||||
|
||||
export default {
|
||||
name: "paragraph",
|
||||
regex: PARAGRAPH_REG,
|
||||
matcher,
|
||||
regexp: PARAGRAPH_REG,
|
||||
renderer,
|
||||
};
|
@ -1,24 +0,0 @@
|
||||
import { escape } from "lodash-es";
|
||||
|
||||
export const PLAIN_LINK_REG = /(https?:\/\/[^ ]+)/;
|
||||
|
||||
const matcher = (rawStr: string) => {
|
||||
const matchResult = rawStr.match(PLAIN_LINK_REG);
|
||||
return matchResult;
|
||||
};
|
||||
|
||||
const renderer = (rawStr: string): string => {
|
||||
const matchResult = matcher(rawStr);
|
||||
if (!matchResult) {
|
||||
return rawStr;
|
||||
}
|
||||
|
||||
return `<a class='link' target='_blank' rel='noreferrer' href='${escape(matchResult[1])}'>${escape(matchResult[1])}</a>`;
|
||||
};
|
||||
|
||||
export default {
|
||||
name: "plain link",
|
||||
regex: PLAIN_LINK_REG,
|
||||
matcher,
|
||||
renderer,
|
||||
};
|
23
web/src/labs/marked/parser/PlainLink.tsx
Normal file
23
web/src/labs/marked/parser/PlainLink.tsx
Normal file
@ -0,0 +1,23 @@
|
||||
import { escape } from "lodash-es";
|
||||
import { matcher } from "../matcher";
|
||||
|
||||
export const PLAIN_LINK_REG = /(https?:\/\/[^ ]+)/;
|
||||
|
||||
const renderer = (rawStr: string) => {
|
||||
const matchResult = matcher(rawStr, PLAIN_LINK_REG);
|
||||
if (!matchResult) {
|
||||
return rawStr;
|
||||
}
|
||||
|
||||
return (
|
||||
<a className="link" target="_blank" rel="noreferrer" href={escape(matchResult[1])}>
|
||||
{escape(matchResult[1])}
|
||||
</a>
|
||||
);
|
||||
};
|
||||
|
||||
export default {
|
||||
name: "plain link",
|
||||
regexp: PLAIN_LINK_REG,
|
||||
renderer,
|
||||
};
|
@ -1,14 +1,10 @@
|
||||
import { escape } from "lodash-es";
|
||||
import { matcher } from "../matcher";
|
||||
|
||||
export const PLAIN_TEXT_REG = /(.+)/;
|
||||
|
||||
const matcher = (rawStr: string) => {
|
||||
const matchResult = rawStr.match(PLAIN_TEXT_REG);
|
||||
return matchResult;
|
||||
};
|
||||
|
||||
const renderer = (rawStr: string): string => {
|
||||
const matchResult = matcher(rawStr);
|
||||
const matchResult = matcher(rawStr, PLAIN_TEXT_REG);
|
||||
if (!matchResult) {
|
||||
return rawStr;
|
||||
}
|
||||
@ -18,7 +14,6 @@ const renderer = (rawStr: string): string => {
|
||||
|
||||
export default {
|
||||
name: "plain text",
|
||||
regex: PLAIN_TEXT_REG,
|
||||
matcher,
|
||||
regexp: PLAIN_TEXT_REG,
|
||||
renderer,
|
||||
};
|
@ -1,24 +0,0 @@
|
||||
import { escape } from "lodash";
|
||||
|
||||
export const STRIKETHROUGH_REG = /~~(.+?)~~/;
|
||||
|
||||
const matcher = (rawStr: string) => {
|
||||
const matchResult = rawStr.match(STRIKETHROUGH_REG);
|
||||
return matchResult;
|
||||
};
|
||||
|
||||
const renderer = (rawStr: string): string => {
|
||||
const matchResult = matcher(rawStr);
|
||||
if (!matchResult) {
|
||||
return rawStr;
|
||||
}
|
||||
|
||||
return `<del>${escape(matchResult[1])}</del>`;
|
||||
};
|
||||
|
||||
export default {
|
||||
name: "Strikethrough",
|
||||
regex: STRIKETHROUGH_REG,
|
||||
matcher,
|
||||
renderer,
|
||||
};
|
19
web/src/labs/marked/parser/Strikethrough.tsx
Normal file
19
web/src/labs/marked/parser/Strikethrough.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
import { escape } from "lodash";
|
||||
import { matcher } from "../matcher";
|
||||
|
||||
export const STRIKETHROUGH_REG = /~~(.+?)~~/;
|
||||
|
||||
const renderer = (rawStr: string) => {
|
||||
const matchResult = matcher(rawStr, STRIKETHROUGH_REG);
|
||||
if (!matchResult) {
|
||||
return rawStr;
|
||||
}
|
||||
|
||||
return <del>{escape(matchResult[1])}</del>;
|
||||
};
|
||||
|
||||
export default {
|
||||
name: "Strikethrough",
|
||||
regexp: STRIKETHROUGH_REG,
|
||||
renderer,
|
||||
};
|
@ -1,27 +0,0 @@
|
||||
import { escape } from "lodash-es";
|
||||
|
||||
export const TAG_REG = /#([^\s#]+)/;
|
||||
|
||||
export const matcher = (rawStr: string) => {
|
||||
const matchResult = rawStr.match(TAG_REG);
|
||||
if (matchResult) {
|
||||
return matchResult;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const renderer = (rawStr: string): string => {
|
||||
const matchResult = matcher(rawStr);
|
||||
if (!matchResult) {
|
||||
return rawStr;
|
||||
}
|
||||
|
||||
return `<span class='tag-span'>#${escape(matchResult[1])}</span>`;
|
||||
};
|
||||
|
||||
export default {
|
||||
name: "tag",
|
||||
regex: TAG_REG,
|
||||
matcher,
|
||||
renderer,
|
||||
};
|
19
web/src/labs/marked/parser/Tag.tsx
Normal file
19
web/src/labs/marked/parser/Tag.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
import { escape } from "lodash-es";
|
||||
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">#{escape(matchResult[1])}</span>;
|
||||
};
|
||||
|
||||
export default {
|
||||
name: "tag",
|
||||
regexp: TAG_REG,
|
||||
renderer,
|
||||
};
|
@ -1,26 +0,0 @@
|
||||
import { inlineElementParserList } from ".";
|
||||
import { marked } from "..";
|
||||
|
||||
export const TODO_LIST_REG = /^- \[ \] ([^\n]+)/;
|
||||
|
||||
const matcher = (rawStr: string) => {
|
||||
const matchResult = rawStr.match(TODO_LIST_REG);
|
||||
return matchResult;
|
||||
};
|
||||
|
||||
const renderer = (rawStr: string): string => {
|
||||
const matchResult = matcher(rawStr);
|
||||
if (!matchResult) {
|
||||
return rawStr;
|
||||
}
|
||||
|
||||
const parsedContent = marked(matchResult[1], [], inlineElementParserList);
|
||||
return `<p class='li-container'><span class='todo-block todo' data-value='TODO'></span><span>${parsedContent}</span></p>`;
|
||||
};
|
||||
|
||||
export default {
|
||||
name: "todo list",
|
||||
regex: TODO_LIST_REG,
|
||||
matcher,
|
||||
renderer,
|
||||
};
|
26
web/src/labs/marked/parser/TodoList.tsx
Normal file
26
web/src/labs/marked/parser/TodoList.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
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 parsedContent = marked(matchResult[1], [], inlineElementParserList);
|
||||
return (
|
||||
<p className="li-container">
|
||||
<span className="todo-block todo" data-value="TODO"></span>
|
||||
<span>{parsedContent}</span>
|
||||
</p>
|
||||
);
|
||||
};
|
||||
|
||||
export default {
|
||||
name: "todo list",
|
||||
regexp: TODO_LIST_REG,
|
||||
renderer,
|
||||
};
|
@ -1,26 +0,0 @@
|
||||
import { inlineElementParserList } from ".";
|
||||
import { marked } from "..";
|
||||
|
||||
export const UNORDERED_LIST_REG = /^[*-] ([^\n]+)/;
|
||||
|
||||
const matcher = (rawStr: string) => {
|
||||
const matchResult = rawStr.match(UNORDERED_LIST_REG);
|
||||
return matchResult;
|
||||
};
|
||||
|
||||
const renderer = (rawStr: string): string => {
|
||||
const matchResult = matcher(rawStr);
|
||||
if (!matchResult) {
|
||||
return rawStr;
|
||||
}
|
||||
|
||||
const parsedContent = marked(matchResult[1], [], inlineElementParserList);
|
||||
return `<p class='li-container'><span class='ul-block'>•</span><span>${parsedContent}</span></p>`;
|
||||
};
|
||||
|
||||
export default {
|
||||
name: "unordered list",
|
||||
regex: UNORDERED_LIST_REG,
|
||||
matcher,
|
||||
renderer,
|
||||
};
|
26
web/src/labs/marked/parser/UnorderedList.tsx
Normal file
26
web/src/labs/marked/parser/UnorderedList.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
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 parsedContent = marked(matchResult[1], [], inlineElementParserList);
|
||||
return (
|
||||
<p className="li-container">
|
||||
<span className="ul-block">•</span>
|
||||
<span>${parsedContent}</span>
|
||||
</p>
|
||||
);
|
||||
};
|
||||
|
||||
export default {
|
||||
name: "unordered list",
|
||||
regexp: UNORDERED_LIST_REG,
|
||||
renderer,
|
||||
};
|
Loading…
Reference in New Issue
Block a user