diff --git a/README.md b/README.md index 513c93bc..54d855c6 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@
diff --git a/web/src/components/MemoEditor.tsx b/web/src/components/MemoEditor.tsx index 968b9b0e..4281ab82 100644 --- a/web/src/components/MemoEditor.tsx +++ b/web/src/components/MemoEditor.tsx @@ -1,6 +1,7 @@ -import { isNumber, last, toLower } from "lodash"; +import { isNumber, last, toLower, uniq } from "lodash"; import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; +import { getMatchedNodes } from "../labs/marked"; import { deleteMemoResource, upsertMemoResource } from "../helpers/api"; import { TAB_SPACE_WIDTH, UNKNOWN_ID, VISIBILITY_SELECTOR_ITEMS } from "../helpers/consts"; import { useEditorStore, useLocationStore, useMemoStore, useResourceStore, useTagStore, useUserStore } from "../store/module"; @@ -326,6 +327,13 @@ const MemoEditor = () => { toastHelper.error(error.response.data.message); } + // Upsert tag based with content. + const matchedNodes = getMatchedNodes(content); + const tagNameList = uniq(matchedNodes.filter((node) => node.parserName === "tag").map((node) => node.matchedContent.slice(1))); + for (const tagName of tagNameList) { + await tagStore.upsertTag(tagName); + } + setState((state) => { return { ...state, diff --git a/web/src/labs/marked/index.ts b/web/src/labs/marked/index.ts index f06dc262..436b9936 100644 --- a/web/src/labs/marked/index.ts +++ b/web/src/labs/marked/index.ts @@ -53,3 +53,75 @@ export const marked = (markdownStr: string, blockParsers = blockElementParserLis return markdownStr; }; + +interface MatchedNode { + parserName: string; + matchedContent: string; +} + +export const getMatchedNodes = (markdownStr: string): MatchedNode[] => { + const matchedNodeList: MatchedNode[] = []; + + const walkthough = (markdownStr: string, blockParsers = blockElementParserList, inlineParsers = inlineElementParserList): string => { + for (const parser of blockParsers) { + const matchResult = parser.matcher(markdownStr); + if (!matchResult) { + continue; + } + const matchedStr = matchResult[0]; + const retainContent = markdownStr.slice(matchedStr.length); + matchedNodeList.push({ + parserName: parser.name, + matchedContent: matchedStr, + }); + + if (parser.name === "br") { + return walkthough(retainContent, blockParsers, inlineParsers); + } else { + if (retainContent.startsWith("\n")) { + return walkthough(retainContent.slice(1), blockParsers, inlineParsers); + } + } + } + + let matchedInlineParser = undefined; + let matchedIndex = -1; + + for (const parser of inlineParsers) { + const matchResult = parser.matcher(markdownStr); + if (!matchResult) { + continue; + } + + if (parser.name === "plain text" && matchedInlineParser !== undefined) { + continue; + } + + const startIndex = matchResult.index as number; + if (matchedInlineParser === undefined || matchedIndex > startIndex) { + matchedInlineParser = parser; + matchedIndex = startIndex; + } + } + + if (matchedInlineParser) { + const matchResult = matchedInlineParser.matcher(markdownStr); + if (matchResult) { + const matchedStr = matchResult[0]; + const matchedLength = matchedStr.length; + const suffixStr = markdownStr.slice(matchedIndex + matchedLength); + matchedNodeList.push({ + parserName: matchedInlineParser.name, + matchedContent: matchedStr, + }); + return walkthough(suffixStr, [], inlineParsers); + } + } + + return markdownStr; + }; + + walkthough(markdownStr); + + return matchedNodeList; +};