CVE-2023-0107 (Other, published 2023-01-07T04:15Z)
Description: value: Cross-site Scripting (XSS) - Stored in GitHub repository usememos/memos prior to 0.10.0.
Severity: nan (CVSS v3 base score: 5.4)
Weakness: CWE-79 \u2014 Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
Repository: https://github.com/usememos/memos
Files changed:
web/src/components/CreateTagDialog.tsx
web/src/components/Memo.tsx
web/src/components/MemoContent.tsx
web/src/components/MemoList.tsx
web/src/labs/marked/index.tsx
web/src/labs/marked/matcher.ts
web/src/labs/marked/parser/Blockquote.tsx
web/src/labs/marked/parser/Bold.tsx
web/src/labs/marked/parser/BoldEmphasis.tsx
web/src/labs/marked/parser/Br.tsx
web/src/labs/marked/parser/CodeBlock.tsx
web/src/labs/marked/parser/DoneList.tsx
web/src/labs/marked/parser/Emphasis.tsx
web/src/labs/marked/parser/Heading.tsx
web/src/labs/marked/parser/HorizontalRules.tsx
web/src/labs/marked/parser/Image.tsx
web/src/labs/marked/parser/InlineCode.tsx
web/src/labs/marked/parser/Link.tsx
web/src/labs/marked/parser/OrderedList.tsx
web/src/labs/marked/parser/Paragraph.tsx
web/src/labs/marked/parser/PlainLink.tsx
web/src/labs/marked/parser/PlainText.tsx
web/src/labs/marked/parser/Strikethrough.tsx
web/src/labs/marked/parser/Tag.tsx
web/src/labs/marked/parser/TodoList.tsx
web/src/labs/marked/parser/UnorderedList.tsx
Commit message: refactor: return jsx element instead of string in marked (#910)

* refactor: return jsx element instead of string in marked

* chore: update

Vulnerable code:
  const matchResult = Tag.matcher(`#${tagName}`);
  highlightWord?: string;
  const { memo, highlightWord } = props;
    } else if (targetEl.tagName === "BUTTON" && targetEl.className === "codeblock-copy-btn") {
      copy(targetEl.parentElement?.children[1].textContent ?? "");
      toastHelper.success(t("message.succeed-copy-code"));
        highlightWord={highlightWord}
import { highlightWithWord } from "../labs/highlighter";
  highlightWord?: string;
  const { className, content, highlightWord, onMemoContentClick, onMemoContentDoubleClick } = props;
  const { t } = useTranslation();
  const userStore = useUserStore();
  const user = userStore.state.user;
        dangerouslySetInnerHTML={{
          __html: highlightWithWord(marked(state.expandButtonStatus === 0 ? foldedContent : content), highlightWord),
        }}
      ></div>
  const [highlightWord, setHighlightWord] = useState<string | undefined>("");
    setHighlightWord(query?.text);
        <Memo key={`${memo.id}-${memo.displayTs}`} memo={memo} highlightWord={highlightWord} />
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);
};
export const marked = (markdownStr: string, blockParsers = blockElementParserList, inlineParsers = inlineElementParserList): string => {
    const matchResult = parser.matcher(markdownStr);
      return parser.renderer(matchedStr) + marked(retainContent, blockParsers, inlineParsers);
        return parser.renderer(matchedStr) + marked(retainContent.slice(1), blockParsers, inlineParsers);
    const matchResult = parser.matcher(markdownStr);
    const matchResult = matchedInlineParser.matcher(markdownStr);
      return marked(prefixStr, [], inlineParsers) + matchedInlineParser.renderer(matchedStr) + marked(suffixStr, [], inlineParsers);
  return markdownStr;
      const matchResult = parser.matcher(markdownStr);
      const matchResult = parser.matcher(markdownStr);
      const matchResult = matchedInlineParser.matcher(markdownStr);
/* 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);
    }
  });
});
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,
};
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,
};
const matcher = (rawStr: string) => {
  const matchResult = rawStr.match(BOLD_EMPHASIS_REG);
  return matchResult;
};

const renderer = (rawStr: string): string => {
  const matchResult = matcher(rawStr);
  return `<strong><em>${parsedContent}</em></strong>`;
  regex: BOLD_EMPHASIS_REG,
  matcher,
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,
};
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,
};
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,
};
const matcher = (rawStr: string) => {
  const matchResult = rawStr.match(EMPHASIS_REG);
  return matchResult;
};

const renderer = (rawStr: string): string => {
  const matchResult = matcher(rawStr);
  return `<em>${parsedContent}</em>`;
  regex: EMPHASIS_REG,
  matcher,
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,
};
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,
};
const matcher = (rawStr: string) => {
  const matchResult = rawStr.match(IMAGE_REG);
  return matchResult;
};

const renderer = (rawStr: string): string => {
  const matchResult = matcher(rawStr);
  return `<img class='img' src='${imageUrl}' />`;
  regex: IMAGE_REG,
  matcher,
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,
};
const matcher = (rawStr: string) => {
  const matchResult = rawStr.match(LINK_REG);
  return matchResult;
};

const renderer = (rawStr: string): string => {
  const matchResult = matcher(rawStr);
  return `<a class='link' target='_blank' rel='noreferrer' href='${escape(matchResult[2])}'>${parsedContent}</a>`;
  regex: LINK_REG,
  matcher,
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,
};
const matcher = (rawStr: string) => {
  const matchResult = rawStr.match(PARAGRAPH_REG);
  return matchResult;
};

const renderer = (rawStr: string): string => {
  return `<p>${parsedContent}</p>`;
  regex: PARAGRAPH_REG,
  matcher,
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,
};
const matcher = (rawStr: string) => {
  const matchResult = rawStr.match(PLAIN_TEXT_REG);
  return matchResult;
};

  const matchResult = matcher(rawStr);
  regex: PLAIN_TEXT_REG,
  matcher,
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,
};
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,
};
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,
};
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,
};

Fixed code:
import { matcher } from "../labs/marked/matcher";
  const matchResult = matcher(`#${tagName}`, Tag.regexp);
  const { memo } = props;
  const { className, content, onMemoContentClick, onMemoContentDoubleClick } = props;
  const { t } = useTranslation();
  const userStore = useUserStore();
  const user = userStore.state.user;
      >
        {marked(state.expandButtonStatus === 0 ? foldedContent : content)}
      </div>
        <Memo key={`${memo.id}-${memo.displayTs}`} memo={memo} />
import { matcher } from "./matcher";
export const marked = (
  markdownStr: string,
  blockParsers = blockElementParserList,
  inlineParsers = inlineElementParserList
): string | JSX.Element => {
    const matchResult = matcher(markdownStr, parser.regexp);
      return (
        <>
          {parser.renderer(matchedStr)}
          {marked(retainContent, blockParsers, inlineParsers)}
        </>
      );
        return (
          <>
            {parser.renderer(matchedStr)}
            {marked(retainContent.slice(1), blockParsers, inlineParsers)}
          </>
        );
    const matchResult = matcher(markdownStr, parser.regexp);
    const matchResult = matcher(markdownStr, matchedInlineParser.regexp);
      return (
        <>
          {marked(prefixStr, [], inlineParsers)}
          {matchedInlineParser.renderer(matchedStr)}
          {marked(suffixStr, [], inlineParsers)}
        </>
      );
  return <>{markdownStr}</>;
      const matchResult = matcher(markdownStr, parser.regexp);
          walkthough(matchedStr, [], inlineParsers);
      const matchResult = matcher(markdownStr, parser.regexp);
      const matchResult = matcher(markdownStr, matchedInlineParser.regexp);
export const matcher = (rawStr: string, regexp: RegExp) => {
  const matchResult = rawStr.match(regexp);
  return matchResult;
};
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,
};
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,
};
import { matcher } from "../matcher";
const renderer = (rawStr: string) => {
  const matchResult = matcher(rawStr, BOLD_EMPHASIS_REG);
  return (
    <strong>
      <em>${parsedContent}</em>
    </strong>
  );
  regexp: BOLD_EMPHASIS_REG,
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,
};
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,
};
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,
};
import { matcher } from "../matcher";
const renderer = (rawStr: string) => {
  const matchResult = matcher(rawStr, EMPHASIS_REG);
  return <em>{parsedContent}</em>;
  regexp: EMPHASIS_REG,
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,
};
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,
};
import { matcher } from "../matcher";
const renderer = (rawStr: string) => {
  const matchResult = matcher(rawStr, IMAGE_REG);
  return <img className="img" src={imageUrl} />;
  regexp: IMAGE_REG,
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,
};
import { matcher } from "../matcher";
const renderer = (rawStr: string) => {
  const matchResult = matcher(rawStr, LINK_REG);
  return (
    <a className="link" target="_blank" rel="noreferrer" href={escape(matchResult[2])}>
      {parsedContent}
    </a>
  );
  regexp: LINK_REG,
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,
};
const renderer = (rawStr: string) => {
  return <p>{parsedContent}</p>;
  regexp: PARAGRAPH_REG,
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,
};
import { matcher } from "../matcher";
  const matchResult = matcher(rawStr, PLAIN_TEXT_REG);
  regexp: PLAIN_TEXT_REG,
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,
};
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,
};
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,
};
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,
};