diff --git a/web/src/components/Memo.tsx b/web/src/components/Memo.tsx index d6c0e2c8..5844cdaa 100644 --- a/web/src/components/Memo.tsx +++ b/web/src/components/Memo.tsx @@ -6,7 +6,6 @@ import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router-dom"; import "dayjs/locale/zh"; import { UNKNOWN_ID } from "../helpers/consts"; -import { DONE_BLOCK_REG, TODO_BLOCK_REG } from "../helpers/marked"; import { editorStateService, locationService, memoService, userService } from "../services"; import Icon from "./Icon"; import toastHelper from "./Toast"; @@ -134,7 +133,7 @@ const Memo: React.FC = (props: Props) => { for (const element of todoElementList) { if (element === targetEl) { const index = indexOf(todoElementList, element); - const tempList = memo.content.split(status === "DONE" ? DONE_BLOCK_REG : TODO_BLOCK_REG); + const tempList = memo.content.split(status === "DONE" ? /- \[x\] / : /- \[ \] /); let finalContent = ""; for (let i = 0; i < tempList.length; i++) { diff --git a/web/src/components/MemoCardDialog.tsx b/web/src/components/MemoCardDialog.tsx index 22674bec..74f2d5d8 100644 --- a/web/src/components/MemoCardDialog.tsx +++ b/web/src/components/MemoCardDialog.tsx @@ -4,7 +4,9 @@ import { editorStateService, memoService, userService } from "../services"; import { useAppSelector } from "../store"; import { UNKNOWN_ID, VISIBILITY_SELECTOR_ITEMS } from "../helpers/consts"; import * as utils from "../helpers/utils"; -import { formatMemoContent, MEMO_LINK_REG, parseHtmlToRawText } from "../helpers/marked"; +import { parseHTMLToRawText } from "../helpers/utils"; +import { marked } from "../labs/marked"; +import { MARK_REG } from "../labs/marked/parser"; import toastHelper from "./Toast"; import { generateDialog } from "./Dialog"; import Icon from "./Icon"; @@ -43,7 +45,7 @@ const MemoCardDialog: React.FC = (props: Props) => { const fetchLinkedMemos = async () => { try { const linkMemos: LinkedMemo[] = []; - const matchedArr = [...memo.content.matchAll(MEMO_LINK_REG)]; + const matchedArr = [...memo.content.matchAll(MARK_REG)]; for (const matchRes of matchedArr) { if (matchRes && matchRes.length === 3) { const id = Number(matchRes[2]); @@ -208,7 +210,7 @@ const MemoCardDialog: React.FC = (props: Props) => {

{linkMemos.length} related MEMO

{linkMemos.map((memo, index) => { - const rawtext = parseHtmlToRawText(formatMemoContent(memo.content)).replaceAll("\n", " "); + const rawtext = parseHTMLToRawText(marked(memo.content)).replaceAll("\n", " "); return (
handleLinkedMemoClick(memo)}> {memo.dateStr} @@ -222,7 +224,7 @@ const MemoCardDialog: React.FC = (props: Props) => {

{linkedMemos.length} linked MEMO

{linkedMemos.map((memo, index) => { - const rawtext = parseHtmlToRawText(formatMemoContent(memo.content)).replaceAll("\n", " "); + const rawtext = parseHTMLToRawText(marked(memo.content)).replaceAll("\n", " "); return (
handleLinkedMemoClick(memo)}> {memo.dateStr} diff --git a/web/src/components/MemoContent.tsx b/web/src/components/MemoContent.tsx index fca61234..11a963cf 100644 --- a/web/src/components/MemoContent.tsx +++ b/web/src/components/MemoContent.tsx @@ -1,5 +1,5 @@ import { useEffect, useRef, useState } from "react"; -import { formatMemoContent } from "../helpers/marked"; +import { marked } from "../labs/marked"; import Icon from "./Icon"; import "../less/memo-content.less"; @@ -79,7 +79,7 @@ const MemoContent: React.FC = (props: Props) => { className={`memo-content-text ${state.expandButtonStatus === 0 ? "expanded" : ""}`} onClick={handleMemoContentClick} onDoubleClick={handleMemoContentDoubleClick} - dangerouslySetInnerHTML={{ __html: formatMemoContent(content) }} + dangerouslySetInnerHTML={{ __html: marked(content) }} >
{state.expandButtonStatus !== -1 && (
diff --git a/web/src/components/MemoList.tsx b/web/src/components/MemoList.tsx index 54b4f1d6..eaf00af2 100644 --- a/web/src/components/MemoList.tsx +++ b/web/src/components/MemoList.tsx @@ -2,7 +2,7 @@ import { useEffect, useRef } from "react"; import { useTranslation } from "react-i18next"; import { memoService, shortcutService } from "../services"; import { useAppSelector } from "../store"; -import { IMAGE_URL_REG, LINK_URL_REG, MEMO_LINK_REG, TAG_REG } from "../helpers/marked"; +import { TAG_REG, LINK_REG } from "../labs/marked/parser"; import * as utils from "../helpers/utils"; import { checkShouldShowMemoWithFilters } from "../helpers/filter"; import toastHelper from "./Toast"; @@ -57,11 +57,7 @@ const MemoList = () => { if (memoType) { if (memoType === "NOT_TAGGED" && memo.content.match(TAG_REG) !== null) { shouldShow = false; - } else if (memoType === "LINKED" && memo.content.match(LINK_URL_REG) === null) { - shouldShow = false; - } else if (memoType === "IMAGED" && memo.content.match(IMAGE_URL_REG) === null) { - shouldShow = false; - } else if (memoType === "CONNECTED" && memo.content.match(MEMO_LINK_REG) === null) { + } else if (memoType === "LINKED" && memo.content.match(LINK_REG) === null) { shouldShow = false; } } diff --git a/web/src/helpers/filter.ts b/web/src/helpers/filter.ts index 13bcb469..7865d938 100644 --- a/web/src/helpers/filter.ts +++ b/web/src/helpers/filter.ts @@ -1,4 +1,4 @@ -import { IMAGE_URL_REG, LINK_URL_REG, MEMO_LINK_REG, TAG_REG } from "./marked"; +import { TAG_REG, LINK_REG } from "../labs/marked/parser"; export const relationConsts = [ { text: "And", value: "AND" }, @@ -34,10 +34,6 @@ export const filterConsts = { }, ], values: [ - { - text: "Connected", - value: "CONNECTED", - }, { text: "No tags", value: "NOT_TAGGED", @@ -46,10 +42,6 @@ export const filterConsts = { text: "Has links", value: "LINKED", }, - { - text: "Has images", - value: "IMAGED", - }, ], }, TEXT: { @@ -142,11 +134,7 @@ export const checkShouldShowMemo = (memo: Memo, filter: Filter) => { let matched = false; if (value === "NOT_TAGGED" && memo.content.match(TAG_REG) === null) { matched = true; - } else if (value === "LINKED" && memo.content.match(LINK_URL_REG) !== null) { - matched = true; - } else if (value === "IMAGED" && memo.content.match(IMAGE_URL_REG) !== null) { - matched = true; - } else if (value === "CONNECTED" && memo.content.match(MEMO_LINK_REG) !== null) { + } else if (value === "LINKED" && memo.content.match(LINK_REG) !== null) { matched = true; } if (operator === "IS_NOT") { diff --git a/web/src/helpers/marked.ts b/web/src/helpers/marked.ts deleted file mode 100644 index 63bfed1c..00000000 --- a/web/src/helpers/marked.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { escape } from "lodash-es"; - -const CODE_BLOCK_REG = /```([\s\S]*?)```\n?/g; -const BOLD_TEXT_REG = /\*\*(.+?)\*\*/g; -const EM_TEXT_REG = /\*(.+?)\*/g; -const DOT_LI_REG = /[*-] /g; -const NUM_LI_REG = /(\d+)\. /g; -export const TODO_BLOCK_REG = /- \[ \] /g; -export const DONE_BLOCK_REG = /- \[x\] /g; -// tag regex -export const TAG_REG = /#([^\s#]+?) /g; -// markdown image regex -export const IMAGE_URL_REG = /!\[.*?\]\((.+?)\)\n?/g; -// markdown link regex -export const LINK_URL_REG = /\[(.*?)\]\((.+?)\)/g; -// linked memo regex -export const MEMO_LINK_REG = /@\[(.+?)\]\((.+?)\)/g; - -const parseMarkedToHtml = (markedStr: string): string => { - const htmlText = markedStr - .replace(CODE_BLOCK_REG, "
$1
") - .replace(TODO_BLOCK_REG, "") - .replace(DONE_BLOCK_REG, "") - .replace(DOT_LI_REG, "") - .replace(NUM_LI_REG, "$1.") - .replace(BOLD_TEXT_REG, "$1") - .replace(EM_TEXT_REG, "$1"); - return htmlText; -}; - -const parseHtmlToRawText = (htmlStr: string): string => { - const tempEl = document.createElement("div"); - tempEl.className = "memo-content-text"; - tempEl.innerHTML = htmlStr; - const text = tempEl.innerText; - return text; -}; - -const formatMemoContent = (content: string) => { - const tempElement = document.createElement("div"); - tempElement.innerHTML = parseMarkedToHtml(escape(content)); - - return tempElement.innerHTML - .replace(IMAGE_URL_REG, "") - .replace(MEMO_LINK_REG, "$1") - .replace(LINK_URL_REG, "$1") - .replace(TAG_REG, "#$1 "); -}; - -export { formatMemoContent, parseHtmlToRawText }; diff --git a/web/src/helpers/utils.ts b/web/src/helpers/utils.ts index b28a8b1d..86541e1c 100644 --- a/web/src/helpers/utils.ts +++ b/web/src/helpers/utils.ts @@ -80,102 +80,6 @@ export function getDateTimeString(t: Date | number | string): string { return `${year}/${monthStr}/${dateStr} ${hoursStr}:${minsStr}:${secsStr}`; } -export function dedupe(data: T[]): T[] { - return Array.from(new Set(data)); -} - -export function dedupeObjectWithId(data: T[]): T[] { - const idSet = new Set(); - const result = []; - - for (const d of data) { - if (!idSet.has(d.id)) { - idSet.add(d.id); - result.push(d); - } - } - - return result; -} - -export function debounce(fn: FunctionType, delay: number) { - let timer: number | null = null; - - return () => { - if (timer) { - clearTimeout(timer); - timer = setTimeout(fn, delay); - } else { - timer = setTimeout(fn, delay); - } - }; -} - -export function throttle(fn: FunctionType, delay: number) { - let valid = true; - - return () => { - if (!valid) { - return false; - } - valid = false; - setTimeout(() => { - fn(); - valid = true; - }, delay); - }; -} - -export function filterObjectNullKeys(object: KVObject): KVObject { - if (!object) { - return {}; - } - - const finalObject: KVObject = {}; - const keys = Object.keys(object).sort(); - - for (const key of keys) { - const val = object[key]; - if (typeof val === "object") { - const temp = filterObjectNullKeys(JSON.parse(JSON.stringify(val))); - if (temp && Object.keys(temp).length > 0) { - finalObject[key] = temp; - } - } else { - if (val) { - finalObject[key] = val; - } - } - } - - return finalObject; -} - -export function getImageSize(src: string): Promise<{ width: number; height: number }> { - return new Promise((resolve) => { - const imgEl = new Image(); - - imgEl.onload = () => { - const { width, height } = imgEl; - - if (width > 0 && height > 0) { - resolve({ width, height }); - } else { - resolve({ width: 0, height: 0 }); - } - }; - - imgEl.onerror = () => { - resolve({ width: 0, height: 0 }); - }; - - imgEl.className = "hidden"; - imgEl.src = src; - document.body.appendChild(imgEl); - imgEl.remove(); - }); -} - export const getElementBounding = (element: HTMLElement, relativeEl?: HTMLElement) => { const scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop; const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft; @@ -224,3 +128,11 @@ export const getElementBounding = (element: HTMLElement, relativeEl?: HTMLElemen left: elementRect.left + scrollLeft, }); }; + +export const parseHTMLToRawText = (htmlStr: string): string => { + const tempEl = document.createElement("div"); + tempEl.className = "memo-content-text"; + tempEl.innerHTML = htmlStr; + const text = tempEl.innerText; + return text; +}; diff --git a/web/src/labs/marked/index.ts b/web/src/labs/marked/index.ts new file mode 100644 index 00000000..f2f58acf --- /dev/null +++ b/web/src/labs/marked/index.ts @@ -0,0 +1,18 @@ +import { parserList } from "./parser"; + +export const marked = (markdownStr: string, parsers = parserList) => { + for (const parser of parsers) { + const startIndex = markdownStr.search(parser.regex); + const matchedLength = parser.match(markdownStr); + + if (startIndex > -1 && matchedLength > 0) { + const prefixStr = markdownStr.slice(0, startIndex); + const matchedStr = markdownStr.slice(startIndex, startIndex + matchedLength); + const suffixStr = markdownStr.slice(startIndex + matchedLength); + markdownStr = marked(prefixStr, parsers) + parser.renderer(matchedStr) + marked(suffixStr, parsers); + break; + } + } + + return markdownStr; +}; diff --git a/web/src/labs/marked/marked.test.ts b/web/src/labs/marked/marked.test.ts new file mode 100644 index 00000000..a15499b6 --- /dev/null +++ b/web/src/labs/marked/marked.test.ts @@ -0,0 +1,89 @@ +import { describe, expect, test } from "@jest/globals"; +import { marked } from "."; + +describe("test marked parser", () => { + test("parse code block", () => { + const tests = [ + { + markdown: `\`\`\` +hello world! +\`\`\``, + want: `
+hello world!
+
`, + }, + { + markdown: `test code block + +\`\`\`js +console.log("hello world!") +\`\`\``, + want: `

test code block

+

+
+console.log("hello world!")
+
`, + }, + ]; + + for (const t of tests) { + expect(marked(t.markdown)).toBe(t.want); + } + }); + test("parse todo list block", () => { + const tests = [ + { + markdown: `My task: +- [ ] finish my homework +- [x] yahaha`, + want: `

My task:

+

finish my homework

+

yahaha

`, + }, + ]; + + for (const t of tests) { + expect(marked(t.markdown)).toBe(t.want); + } + }); + test("parse list block", () => { + const tests = [ + { + markdown: `This is a list +* list 123 +1. 123123`, + want: `

This is a list

+

list 123

+

1.123123

`, + }, + ]; + + for (const t of tests) { + expect(marked(t.markdown)).toBe(t.want); + } + }); + test("parse inline element", () => { + const tests = [ + { + markdown: `Link: [baidu](https://baidu.com)`, + want: `

Link: baidu

`, + }, + ]; + + for (const t of tests) { + expect(marked(t.markdown)).toBe(t.want); + } + }); + test("parse plain link", () => { + const tests = [ + { + markdown: `Link:https://baidu.com`, + want: `

Link:https://baidu.com

`, + }, + ]; + + for (const t of tests) { + expect(marked(t.markdown)).toBe(t.want); + } + }); +}); diff --git a/web/src/labs/marked/parser/Bold.ts b/web/src/labs/marked/parser/Bold.ts new file mode 100644 index 00000000..3f0c531c --- /dev/null +++ b/web/src/labs/marked/parser/Bold.ts @@ -0,0 +1,23 @@ +export const BOLD_REG = /\*\*([\S ]+?)\*\*/; + +const match = (rawStr: string): number => { + const matchResult = rawStr.match(BOLD_REG); + if (!matchResult) { + return 0; + } + + const matchStr = matchResult[0]; + return matchStr.length; +}; + +const renderer = (rawStr: string): string => { + const parsedStr = rawStr.replace(BOLD_REG, "$1"); + return parsedStr; +}; + +export default { + name: "bold", + regex: BOLD_REG, + match, + renderer, +}; diff --git a/web/src/labs/marked/parser/CodeBlock.ts b/web/src/labs/marked/parser/CodeBlock.ts new file mode 100644 index 00000000..4f5f731b --- /dev/null +++ b/web/src/labs/marked/parser/CodeBlock.ts @@ -0,0 +1,23 @@ +export const CODE_BLOCK_REG = /^```(\S*?)\s([\s\S]*?)```(\n?)/; + +const match = (rawStr: string): number => { + const matchResult = rawStr.match(CODE_BLOCK_REG); + if (!matchResult) { + return 0; + } + + const matchStr = matchResult[0]; + return matchStr.length; +}; + +const renderer = (rawStr: string): string => { + const parsedStr = rawStr.replace(CODE_BLOCK_REG, "
\n$2
$3"); + return parsedStr; +}; + +export default { + name: "code block", + regex: CODE_BLOCK_REG, + match, + renderer, +}; diff --git a/web/src/labs/marked/parser/DoneList.ts b/web/src/labs/marked/parser/DoneList.ts new file mode 100644 index 00000000..10f3783c --- /dev/null +++ b/web/src/labs/marked/parser/DoneList.ts @@ -0,0 +1,31 @@ +import { inlineElementParserList } from "."; +import { marked } from ".."; + +export const DONE_LIST_REG = /^- \[x\] ([\S ]+)(\n?)/; + +const match = (rawStr: string): number => { + const matchResult = rawStr.match(DONE_LIST_REG); + if (!matchResult) { + return 0; + } + + const matchStr = matchResult[0]; + return matchStr.length; +}; + +const renderer = (rawStr: string): string => { + const matchResult = rawStr.match(DONE_LIST_REG); + if (!matchResult) { + return rawStr; + } + + const parsedContent = marked(matchResult[1], inlineElementParserList); + return `

${parsedContent}

${matchResult[2]}`; +}; + +export default { + name: "done list", + regex: DONE_LIST_REG, + match, + renderer, +}; diff --git a/web/src/labs/marked/parser/Emphasis.ts b/web/src/labs/marked/parser/Emphasis.ts new file mode 100644 index 00000000..d749dcd1 --- /dev/null +++ b/web/src/labs/marked/parser/Emphasis.ts @@ -0,0 +1,23 @@ +export const EMPHASIS_REG = /\*([\S ]+?)\*/; + +const match = (rawStr: string): number => { + const matchResult = rawStr.match(EMPHASIS_REG); + if (!matchResult) { + return 0; + } + + const matchStr = matchResult[0]; + return matchStr.length; +}; + +const renderer = (rawStr: string): string => { + const parsedStr = rawStr.replace(EMPHASIS_REG, "$1"); + return parsedStr; +}; + +export default { + name: "emphasis", + regex: EMPHASIS_REG, + match, + renderer, +}; diff --git a/web/src/labs/marked/parser/Image.ts b/web/src/labs/marked/parser/Image.ts new file mode 100644 index 00000000..30959835 --- /dev/null +++ b/web/src/labs/marked/parser/Image.ts @@ -0,0 +1,23 @@ +export const IMAGE_REG = /!\[.*?\]\((.+?)\)/; + +const match = (rawStr: string): number => { + const matchResult = rawStr.match(IMAGE_REG); + if (!matchResult) { + return 0; + } + + const matchStr = matchResult[0]; + return matchStr.length; +}; + +const renderer = (rawStr: string): string => { + const parsedStr = rawStr.replace(IMAGE_REG, ""); + return parsedStr; +}; + +export default { + name: "image", + regex: IMAGE_REG, + match, + renderer, +}; diff --git a/web/src/labs/marked/parser/Link.ts b/web/src/labs/marked/parser/Link.ts new file mode 100644 index 00000000..a0599d82 --- /dev/null +++ b/web/src/labs/marked/parser/Link.ts @@ -0,0 +1,23 @@ +export const LINK_REG = /\[(.*?)\]\((.+?)\)/; + +const match = (rawStr: string): number => { + const matchResult = rawStr.match(LINK_REG); + if (!matchResult) { + return 0; + } + + const matchStr = matchResult[0]; + return matchStr.length; +}; + +const renderer = (rawStr: string): string => { + const parsedStr = rawStr.replace(LINK_REG, "$1"); + return parsedStr; +}; + +export default { + name: "link", + regex: LINK_REG, + match, + renderer, +}; diff --git a/web/src/labs/marked/parser/Mark.ts b/web/src/labs/marked/parser/Mark.ts new file mode 100644 index 00000000..9dbd3a4b --- /dev/null +++ b/web/src/labs/marked/parser/Mark.ts @@ -0,0 +1,23 @@ +export const MARK_REG = /@\[([\S ]+?)\]\((\S+?)\)/; + +const match = (rawStr: string): number => { + const matchResult = rawStr.match(MARK_REG); + if (!matchResult) { + return 0; + } + + const matchStr = matchResult[0]; + return matchStr.length; +}; + +const renderer = (rawStr: string): string => { + const parsedStr = rawStr.replace(MARK_REG, "$1"); + return parsedStr; +}; + +export default { + name: "mark", + regex: MARK_REG, + match, + renderer, +}; diff --git a/web/src/labs/marked/parser/OrderedList.ts b/web/src/labs/marked/parser/OrderedList.ts new file mode 100644 index 00000000..fe42ff9d --- /dev/null +++ b/web/src/labs/marked/parser/OrderedList.ts @@ -0,0 +1,31 @@ +import { inlineElementParserList } from "."; +import { marked } from ".."; + +export const ORDERED_LIST_REG = /^(\d+)\. ([\S ]+)(\n?)/; + +const match = (rawStr: string): number => { + const matchResult = rawStr.match(ORDERED_LIST_REG); + if (!matchResult) { + return 0; + } + + const matchStr = matchResult[0]; + return matchStr.length; +}; + +const renderer = (rawStr: string): string => { + const matchResult = rawStr.match(ORDERED_LIST_REG); + if (!matchResult) { + return rawStr; + } + + const parsedContent = marked(matchResult[2], inlineElementParserList); + return `

${matchResult[1]}.${parsedContent}

${matchResult[3]}`; +}; + +export default { + name: "ordered list", + regex: ORDERED_LIST_REG, + match, + renderer, +}; diff --git a/web/src/labs/marked/parser/Paragraph.ts b/web/src/labs/marked/parser/Paragraph.ts new file mode 100644 index 00000000..d85558b0 --- /dev/null +++ b/web/src/labs/marked/parser/Paragraph.ts @@ -0,0 +1,31 @@ +import { inlineElementParserList } from "."; +import { marked } from ".."; + +export const PARAGRAPH_REG = /^([\S ]*)(\n?)/; + +const match = (rawStr: string): number => { + const matchResult = rawStr.match(PARAGRAPH_REG); + if (!matchResult) { + return 0; + } + + const matchStr = matchResult[0]; + return matchStr.length; +}; + +const renderer = (rawStr: string): string => { + const matchResult = rawStr.match(PARAGRAPH_REG); + if (!matchResult) { + return rawStr; + } + + const parsedContent = marked(matchResult[1], inlineElementParserList); + return `

${parsedContent}

${matchResult[2]}`; +}; + +export default { + name: "ordered list", + regex: PARAGRAPH_REG, + match, + renderer, +}; diff --git a/web/src/labs/marked/parser/PlainLink.ts b/web/src/labs/marked/parser/PlainLink.ts new file mode 100644 index 00000000..b30f0d47 --- /dev/null +++ b/web/src/labs/marked/parser/PlainLink.ts @@ -0,0 +1,23 @@ +export const PLAIN_LINK_REG = /(https?:\/\/[^ ]+)/; + +const match = (rawStr: string): number => { + const matchResult = rawStr.match(PLAIN_LINK_REG); + if (!matchResult) { + return 0; + } + + const matchStr = matchResult[0]; + return matchStr.length; +}; + +const renderer = (rawStr: string): string => { + const parsedStr = rawStr.replace(PLAIN_LINK_REG, "$1"); + return parsedStr; +}; + +export default { + name: "plain link", + regex: PLAIN_LINK_REG, + match, + renderer, +}; diff --git a/web/src/labs/marked/parser/Tag.ts b/web/src/labs/marked/parser/Tag.ts new file mode 100644 index 00000000..f4fbc933 --- /dev/null +++ b/web/src/labs/marked/parser/Tag.ts @@ -0,0 +1,23 @@ +export const TAG_REG = /#([^\s#]+?) /; + +const match = (rawStr: string): number => { + const matchResult = rawStr.match(TAG_REG); + if (!matchResult) { + return 0; + } + + const matchStr = matchResult[0]; + return matchStr.length; +}; + +const renderer = (rawStr: string): string => { + const parsedStr = rawStr.replace(TAG_REG, "#$1 "); + return parsedStr; +}; + +export default { + name: "tag", + regex: TAG_REG, + match, + renderer, +}; diff --git a/web/src/labs/marked/parser/TodoList.ts b/web/src/labs/marked/parser/TodoList.ts new file mode 100644 index 00000000..541b2abb --- /dev/null +++ b/web/src/labs/marked/parser/TodoList.ts @@ -0,0 +1,31 @@ +import { inlineElementParserList } from "."; +import { marked } from ".."; + +export const TODO_LIST_REG = /^- \[ \] ([\S ]+)(\n?)/; + +const match = (rawStr: string): number => { + const matchResult = rawStr.match(TODO_LIST_REG); + if (!matchResult) { + return 0; + } + + const matchStr = matchResult[0]; + return matchStr.length; +}; + +const renderer = (rawStr: string): string => { + const matchResult = rawStr.match(TODO_LIST_REG); + if (!matchResult) { + return rawStr; + } + + const parsedContent = marked(matchResult[1], inlineElementParserList); + return `

${parsedContent}

${matchResult[2]}`; +}; + +export default { + name: "todo list", + regex: TODO_LIST_REG, + match, + renderer, +}; diff --git a/web/src/labs/marked/parser/UnorderedList.ts b/web/src/labs/marked/parser/UnorderedList.ts new file mode 100644 index 00000000..59f87cb8 --- /dev/null +++ b/web/src/labs/marked/parser/UnorderedList.ts @@ -0,0 +1,31 @@ +import { inlineElementParserList } from "."; +import { marked } from ".."; + +export const UNORDERED_LIST_REG = /^[*-] ([\S ]+)(\n?)/; + +const match = (rawStr: string): number => { + const matchResult = rawStr.match(UNORDERED_LIST_REG); + if (!matchResult) { + return 0; + } + + const matchStr = matchResult[0]; + return matchStr.length; +}; + +const renderer = (rawStr: string): string => { + const matchResult = rawStr.match(UNORDERED_LIST_REG); + if (!matchResult) { + return rawStr; + } + + const parsedContent = marked(matchResult[1], inlineElementParserList); + return `

${parsedContent}

${matchResult[2]}`; +}; + +export default { + name: "unordered list", + regex: UNORDERED_LIST_REG, + match, + renderer, +}; diff --git a/web/src/labs/marked/parser/index.ts b/web/src/labs/marked/parser/index.ts new file mode 100644 index 00000000..cff22365 --- /dev/null +++ b/web/src/labs/marked/parser/index.ts @@ -0,0 +1,31 @@ +import CodeBlock from "./CodeBlock"; +import TodoList from "./TodoList"; +import DoneList from "./DoneList"; +import OrderedList from "./OrderedList"; +import UnorderedList from "./UnorderedList"; +import Paragraph from "./Paragraph"; +import Tag from "./Tag"; +import Image from "./Image"; +import Link from "./Link"; +import Mark from "./Mark"; +import Bold from "./Bold"; +import Emphasis from "./Emphasis"; +import PlainLink from "./PlainLink"; + +export { CODE_BLOCK_REG } from "./CodeBlock"; +export { TODO_LIST_REG } from "./TodoList"; +export { DONE_LIST_REG } from "./DoneList"; +export { ORDERED_LIST_REG } from "./OrderedList"; +export { UNORDERED_LIST_REG } from "./UnorderedList"; +export { PARAGRAPH_REG } from "./Paragraph"; +export { TAG_REG } from "./Tag"; +export { IMAGE_REG } from "./Image"; +export { LINK_REG } from "./Link"; +export { MARK_REG } from "./Mark"; +export { BOLD_REG } from "./Bold"; +export { EMPHASIS_REG } from "./Emphasis"; + +// The order determines the order of execution. +export const blockElementParserList = [CodeBlock, TodoList, DoneList, OrderedList, UnorderedList, Paragraph]; +export const inlineElementParserList = [Image, Mark, Link, Bold, Emphasis, Tag, PlainLink]; +export const parserList = [...blockElementParserList, ...inlineElementParserList]; diff --git a/web/src/less/daily-memo.less b/web/src/less/daily-memo.less index 54eacf4d..5d385944 100644 --- a/web/src/less/daily-memo.less +++ b/web/src/less/daily-memo.less @@ -20,8 +20,8 @@ > .memo-container { @apply w-full overflow-x-hidden flex flex-col justify-start items-start; - > .memo-content-container { - @apply flex flex-col justify-start items-start w-full overflow-x-hidden p-0 text-base; + .memo-content-text { + margin-top: 3px; } } } diff --git a/web/src/less/memo-content.less b/web/src/less/memo-content.less index ab71b1d9..6f55ac71 100644 --- a/web/src/less/memo-content.less +++ b/web/src/less/memo-content.less @@ -4,7 +4,7 @@ @apply w-full flex flex-col justify-start items-start; > .memo-content-text { - @apply w-full whitespace-pre-wrap break-words text-base leading-7; + @apply w-full break-words text-base leading-7; &.expanded { display: -webkit-box; @@ -14,7 +14,8 @@ } > p { - @apply inline-block w-full h-auto mb-1 last:mb-0 text-base leading-7 whitespace-pre-wrap break-words; + @apply w-full h-auto mb-1 last:mb-0 text-base leading-6 whitespace-pre-wrap break-words; + min-height: 24px; } .img { @@ -30,7 +31,7 @@ } .link { - @apply inline-block text-blue-600 cursor-pointer underline break-all hover:opacity-80; + @apply text-blue-600 cursor-pointer underline break-all hover:opacity-80; } .counter-block,