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:
boojack 2023-01-07 00:13:49 +08:00 committed by GitHub
parent 491859bbf6
commit 0f8ce3dd16
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 417 additions and 637 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

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

View File

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

View File

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

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

View File

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

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

View File

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

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

View File

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

View File

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

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

View File

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

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

View File

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

View File

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

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

View File

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

View File

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

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

View File

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

View File

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

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

View File

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

View File

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

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

View File

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

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

View File

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

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

View File

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

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