mirror of
https://github.com/usememos/memos.git
synced 2025-06-05 22:09:59 +02:00
chore: update content parser (#97)
This commit is contained in:
@ -1,10 +1,10 @@
|
|||||||
import { memo, useEffect, useRef, useState } from "react";
|
import { memo, useEffect, useRef, useState } from "react";
|
||||||
import { escape } from "lodash-es";
|
import { escape } from "lodash-es";
|
||||||
import { IMAGE_URL_REG, LINK_REG, MEMO_LINK_REG, TAG_REG, UNKNOWN_ID } from "../helpers/consts";
|
import { IMAGE_URL_REG, LINK_REG, MEMO_LINK_REG, TAG_REG, UNKNOWN_ID } from "../helpers/consts";
|
||||||
import { parseMarkedToHtml, parseRawTextToHtml } from "../helpers/marked";
|
import { parseMarkedToHtml } from "../helpers/marked";
|
||||||
import * as utils from "../helpers/utils";
|
import * as utils from "../helpers/utils";
|
||||||
import useToggle from "../hooks/useToggle";
|
import useToggle from "../hooks/useToggle";
|
||||||
import { editorStateService, memoService } from "../services";
|
import { editorStateService, locationService, memoService } from "../services";
|
||||||
import Only from "./common/OnlyWhen";
|
import Only from "./common/OnlyWhen";
|
||||||
import Image from "./Image";
|
import Image from "./Image";
|
||||||
import showMemoCardDialog from "./MemoCardDialog";
|
import showMemoCardDialog from "./MemoCardDialog";
|
||||||
@ -116,8 +116,16 @@ const Memo: React.FC<Props> = (props: Props) => {
|
|||||||
toastHelper.error("MEMO Not Found");
|
toastHelper.error("MEMO Not Found");
|
||||||
targetEl.classList.remove("memo-link-text");
|
targetEl.classList.remove("memo-link-text");
|
||||||
}
|
}
|
||||||
|
} else if (targetEl.tagName === "SPAN" && targetEl.className === "tag-span") {
|
||||||
|
const tagName = targetEl.innerText.slice(1);
|
||||||
|
const currTagQuery = locationService.getState().query?.tag;
|
||||||
|
if (currTagQuery === tagName) {
|
||||||
|
locationService.setTagQuery("");
|
||||||
|
} else {
|
||||||
|
locationService.setTagQuery(tagName);
|
||||||
|
}
|
||||||
} else if (targetEl.className === "todo-block") {
|
} else if (targetEl.className === "todo-block") {
|
||||||
// do nth
|
// ...do nth
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -196,32 +204,10 @@ const Memo: React.FC<Props> = (props: Props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function formatMemoContent(content: string) {
|
export function formatMemoContent(content: string) {
|
||||||
content = escape(content);
|
const tempElement = document.createElement("div");
|
||||||
content = parseRawTextToHtml(content)
|
tempElement.innerHTML = parseMarkedToHtml(escape(content));
|
||||||
.split("<br>")
|
|
||||||
.map((t) => {
|
|
||||||
return `<p>${t !== "" ? t : "<br>"}</p>`;
|
|
||||||
})
|
|
||||||
.join("");
|
|
||||||
|
|
||||||
content = parseMarkedToHtml(content);
|
return tempElement.innerHTML
|
||||||
|
|
||||||
// Add space in english and chinese
|
|
||||||
content = content.replace(/([\u4e00-\u9fa5])([A-Za-z0-9?.,;[\]]+)/g, "$1 $2").replace(/([A-Za-z0-9?.,;[\]]+)([\u4e00-\u9fa5])/g, "$1 $2");
|
|
||||||
|
|
||||||
const tempDivContainer = document.createElement("div");
|
|
||||||
tempDivContainer.innerHTML = content;
|
|
||||||
for (let i = 0; i < tempDivContainer.children.length; i++) {
|
|
||||||
const c = tempDivContainer.children[i];
|
|
||||||
|
|
||||||
if (c.tagName === "P" && c.textContent === "" && c.firstElementChild?.tagName !== "BR") {
|
|
||||||
c.remove();
|
|
||||||
i--;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return tempDivContainer.innerHTML
|
|
||||||
.replace(IMAGE_URL_REG, "")
|
.replace(IMAGE_URL_REG, "")
|
||||||
.replace(TAG_REG, "<span class='tag-span'>#$1</span> ")
|
.replace(TAG_REG, "<span class='tag-span'>#$1</span> ")
|
||||||
.replace(LINK_REG, "<a class='link' target='_blank' rel='noreferrer' href='$1'>$1</a>")
|
.replace(LINK_REG, "<a class='link' target='_blank' rel='noreferrer' href='$1'>$1</a>")
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { locationService, memoService, shortcutService } from "../services";
|
import { memoService, shortcutService } from "../services";
|
||||||
import { useAppSelector } from "../store";
|
import { useAppSelector } from "../store";
|
||||||
import { IMAGE_URL_REG, LINK_REG, MEMO_LINK_REG, TAG_REG } from "../helpers/consts";
|
import { IMAGE_URL_REG, LINK_REG, MEMO_LINK_REG, TAG_REG } from "../helpers/consts";
|
||||||
import * as utils from "../helpers/utils";
|
import * as utils from "../helpers/utils";
|
||||||
@ -94,21 +94,8 @@ const MemoList: React.FC<Props> = () => {
|
|||||||
wrapperElement.current?.scrollTo({ top: 0 });
|
wrapperElement.current?.scrollTo({ top: 0 });
|
||||||
}, [query]);
|
}, [query]);
|
||||||
|
|
||||||
const handleMemoListClick = useCallback((event: React.MouseEvent) => {
|
|
||||||
const targetEl = event.target as HTMLElement;
|
|
||||||
if (targetEl.tagName === "SPAN" && targetEl.className === "tag-span") {
|
|
||||||
const tagName = targetEl.innerText.slice(1);
|
|
||||||
const currTagQuery = locationService.getState().query?.tag;
|
|
||||||
if (currTagQuery === tagName) {
|
|
||||||
locationService.setTagQuery("");
|
|
||||||
} else {
|
|
||||||
locationService.setTagQuery(tagName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`memo-list-container ${isFetching ? "" : "completed"}`} onClick={handleMemoListClick} ref={wrapperElement}>
|
<div className={`memo-list-container ${isFetching ? "" : "completed"}`} ref={wrapperElement}>
|
||||||
{sortedMemos.map((memo) => (
|
{sortedMemos.map((memo) => (
|
||||||
<Memo key={`${memo.id}-${memo.updatedTs}`} memo={memo} />
|
<Memo key={`${memo.id}-${memo.updatedTs}`} memo={memo} />
|
||||||
))}
|
))}
|
||||||
|
@ -1,27 +1,22 @@
|
|||||||
/**
|
|
||||||
* 实现一个简易版的 markdown 解析
|
|
||||||
* - 列表解析;
|
|
||||||
* - 代码块;
|
|
||||||
* - 加粗/斜体;
|
|
||||||
* - TODO;
|
|
||||||
*/
|
|
||||||
const CODE_BLOCK_REG = /```([\s\S]*?)```/g;
|
const CODE_BLOCK_REG = /```([\s\S]*?)```/g;
|
||||||
const BOLD_TEXT_REG = /\*\*(.+?)\*\*/g;
|
const BOLD_TEXT_REG = /\*\*(.+?)\*\*/g;
|
||||||
const EM_TEXT_REG = /\*(.+?)\*/g;
|
const EM_TEXT_REG = /\*(.+?)\*/g;
|
||||||
const TODO_BLOCK_REG = /\[ \] /g;
|
const TODO_BLOCK_REG = /- \[ \] /g;
|
||||||
const DONE_BLOCK_REG = /\[x\] /g;
|
const DONE_BLOCK_REG = /- \[x\] /g;
|
||||||
const DOT_LI_REG = /[*] /g;
|
const DOT_LI_REG = /[*-] /g;
|
||||||
const NUM_LI_REG = /(\d+)\. /g;
|
const NUM_LI_REG = /(\d+)\. /g;
|
||||||
|
|
||||||
const parseMarkedToHtml = (markedStr: string): string => {
|
const parseMarkedToHtml = (markedStr: string): string => {
|
||||||
const htmlText = markedStr
|
const htmlText = markedStr
|
||||||
.replace(CODE_BLOCK_REG, "<pre lang=''>$1</pre>")
|
.replace(CODE_BLOCK_REG, "<pre lang=''>$1</pre>")
|
||||||
.replace(DOT_LI_REG, "<span class='counter-block'>•</span>")
|
|
||||||
.replace(NUM_LI_REG, "<span class='counter-block'>$1.</span>")
|
|
||||||
.replace(TODO_BLOCK_REG, "<span class='todo-block' data-type='todo'>⬜</span>")
|
.replace(TODO_BLOCK_REG, "<span class='todo-block' data-type='todo'>⬜</span>")
|
||||||
.replace(DONE_BLOCK_REG, "<span class='todo-block' data-type='done'>✅</span>")
|
.replace(DONE_BLOCK_REG, "<span class='todo-block' data-type='done'>✅</span>")
|
||||||
|
.replace(DOT_LI_REG, "<span class='counter-block'>•</span>")
|
||||||
|
.replace(NUM_LI_REG, "<span class='counter-block'>$1.</span>")
|
||||||
.replace(BOLD_TEXT_REG, "<strong>$1</strong>")
|
.replace(BOLD_TEXT_REG, "<strong>$1</strong>")
|
||||||
.replace(EM_TEXT_REG, "<em>$1</em>");
|
.replace(EM_TEXT_REG, "<em>$1</em>")
|
||||||
|
.replace(/([\u4e00-\u9fa5])([A-Za-z0-9?.,;[\]]+)/g, "$1 $2")
|
||||||
|
.replace(/([A-Za-z0-9?.,;[\]]+)([\u4e00-\u9fa5])/g, "$1 $2");
|
||||||
|
|
||||||
return htmlText;
|
return htmlText;
|
||||||
};
|
};
|
||||||
@ -34,9 +29,4 @@ const parseHtmlToRawText = (htmlStr: string): string => {
|
|||||||
return text;
|
return text;
|
||||||
};
|
};
|
||||||
|
|
||||||
const parseRawTextToHtml = (rawTextStr: string): string => {
|
export { parseMarkedToHtml, parseHtmlToRawText };
|
||||||
const htmlText = rawTextStr.replace(/\n/g, "<br>");
|
|
||||||
return htmlText;
|
|
||||||
};
|
|
||||||
|
|
||||||
export { parseMarkedToHtml, parseHtmlToRawText, parseRawTextToHtml };
|
|
||||||
|
@ -21,12 +21,6 @@
|
|||||||
> .memo-content-container {
|
> .memo-content-container {
|
||||||
@apply flex flex-col justify-start items-start w-full p-0 text-base;
|
@apply flex flex-col justify-start items-start w-full p-0 text-base;
|
||||||
|
|
||||||
> .memo-content-text {
|
|
||||||
.tag-span {
|
|
||||||
cursor: unset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> .images-container {
|
> .images-container {
|
||||||
@apply flex flex-col justify-start items-start mt-1 w-full;
|
@apply flex flex-col justify-start items-start mt-1 w-full;
|
||||||
|
|
||||||
|
@ -41,11 +41,7 @@
|
|||||||
@apply w-full pt-2;
|
@apply w-full pt-2;
|
||||||
|
|
||||||
> .memo-content-text {
|
> .memo-content-text {
|
||||||
@apply w-full text-base leading-6;
|
@apply w-full text-base;
|
||||||
|
|
||||||
.tag-span {
|
|
||||||
@apply p-0 text-blue-600 text-base bg-transparent cursor-auto;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
> .images-wrapper {
|
> .images-wrapper {
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
@import "./mixin.less";
|
@import "./mixin.less";
|
||||||
|
|
||||||
.memo-content-text {
|
.memo-content-text {
|
||||||
.flex(column, flex-start, flex-start);
|
@apply w-full whitespace-pre-wrap break-words text-base leading-7;
|
||||||
@apply w-full whitespace-pre-wrap break-words;
|
|
||||||
|
|
||||||
> p {
|
> p {
|
||||||
@apply inline-block w-full h-auto mb-1 last:mb-0 text-base leading-7 whitespace-pre-wrap break-words;
|
@apply inline-block w-full h-auto mb-1 last:mb-0 text-base leading-7 whitespace-pre-wrap break-words;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag-span {
|
.tag-span {
|
||||||
@apply inline-block w-auto px-2 text-sm leading-6 border-none rounded cursor-pointer text-blue-600 bg-blue-50 hover:text-white hover:bg-blue-600;
|
@apply inline-block w-auto font-mono text-blue-600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.memo-link-text {
|
.memo-link-text {
|
||||||
|
@ -87,6 +87,10 @@
|
|||||||
|
|
||||||
> .memo-content-text {
|
> .memo-content-text {
|
||||||
@apply w-full h-auto transition-all;
|
@apply w-full h-auto transition-all;
|
||||||
|
|
||||||
|
.tag-span {
|
||||||
|
@apply cursor-pointer hover:opacity-80;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> .expand-btn-container {
|
> .expand-btn-container {
|
||||||
|
@ -76,10 +76,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
> .tag-tip-container {
|
> .tag-tip-container {
|
||||||
@apply w-full mt-2 pl-4 text-xs text-gray-400;
|
@apply w-full mt-2 pl-4 text-sm text-gray-400;
|
||||||
|
|
||||||
> .code-text {
|
> .code-text {
|
||||||
@apply p-1 mx-1 text-blue-400 font-mono whitespace-pre-line bg-blue-100 rounded;
|
@apply p-1 mx-1 text-blue-600 font-mono whitespace-pre-line bg-blue-100 rounded;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user