import { memo, useEffect, useRef, useState } from "react"; import { escape, indexOf } from "lodash-es"; import { IMAGE_URL_REG, LINK_REG, MEMO_LINK_REG, TAG_REG, UNKNOWN_ID } from "../helpers/consts"; import { DONE_BLOCK_REG, parseMarkedToHtml, TODO_BLOCK_REG } from "../helpers/marked"; import * as utils from "../helpers/utils"; import { editorStateService, locationService, memoService } from "../services"; import Only from "./common/OnlyWhen"; import Image from "./Image"; import showMemoCardDialog from "./MemoCardDialog"; import showShareMemoImageDialog from "./ShareMemoImageDialog"; import toastHelper from "./Toast"; import "../less/memo.less"; const MAX_MEMO_CONTAINER_HEIGHT = 384; interface Props { memo: Memo; } type ExpandButtonStatus = -1 | 0 | 1; interface State { expandButtonStatus: ExpandButtonStatus; } const Memo: React.FC = (props: Props) => { const { memo: propsMemo } = props; const memo = { ...propsMemo, createdAtStr: utils.getDateTimeString(propsMemo.createdTs), }; const [state, setState] = useState({ expandButtonStatus: -1, }); const memoContainerRef = useRef(null); const imageUrls = Array.from(memo.content.match(IMAGE_URL_REG) ?? []).map((s) => s.replace(IMAGE_URL_REG, "$1")); useEffect(() => { if (!memoContainerRef) { return; } if (Number(memoContainerRef.current?.clientHeight) > MAX_MEMO_CONTAINER_HEIGHT) { setState({ ...state, expandButtonStatus: 0, }); } }, []); const handleShowMemoStoryDialog = () => { showMemoCardDialog(memo); }; const handleTogglePinMemoBtnClick = async () => { try { if (memo.pinned) { await memoService.unpinMemo(memo.id); } else { await memoService.pinMemo(memo.id); } } catch (error) { // do nth } }; const handleMarkMemoClick = () => { editorStateService.setMarkMemoWithId(memo.id); }; const handleEditMemoClick = () => { editorStateService.setEditMemoWithId(memo.id); }; const handleArchiveMemoClick = async () => { try { await memoService.patchMemo({ id: memo.id, rowStatus: "ARCHIVED", }); } catch (error: any) { toastHelper.error(error.message); } if (editorStateService.getState().editMemoId === memo.id) { editorStateService.clearEditMemo(); } }; const handleGenMemoImageBtnClick = () => { showShareMemoImageDialog(memo); }; const handleMemoContentClick = async (e: React.MouseEvent) => { const targetEl = e.target as HTMLElement; if (targetEl.className === "memo-link-text") { const memoId = targetEl.dataset?.value; const memoTemp = memoService.getMemoById(Number(memoId) ?? UNKNOWN_ID); if (memoTemp) { showMemoCardDialog(memoTemp); } else { toastHelper.error("MEMO Not Found"); targetEl.classList.remove("memo-link-text"); } } else if (targetEl.className === "tag-span") { const tagName = targetEl.innerText.slice(1); const currTagQuery = locationService.getState().query?.tag; if (currTagQuery === tagName) { locationService.setTagQuery(undefined); } else { locationService.setTagQuery(tagName); } } else if (targetEl.className === "todo-block") { const status = targetEl.dataset?.value; const todoElementList = [...(memoContainerRef.current?.querySelectorAll(`span.todo-block[data-value=${status}]`) ?? [])]; 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); let finalContent = ""; for (let i = 0; i < tempList.length; i++) { if (i === 0) { finalContent += `${tempList[i]}`; } else { if (i === index + 1) { finalContent += status === "DONE" ? "- [ ] " : "- [x] "; } else { finalContent += status === "DONE" ? "- [x] " : "- [ ] "; } finalContent += `${tempList[i]}`; } } await memoService.patchMemo({ id: memo.id, content: finalContent, }); } } } }; const handleShowMoreBtnClick = () => { setState({ ...state, expandButtonStatus: Number(Boolean(!state.expandButtonStatus)) as ExpandButtonStatus, }); }; return (
{memo.createdAtStr} PINNED
{memo.pinned ? "Unpin" : "Pin"}
Edit
Share
Mark View Story Archive
{state.expandButtonStatus !== -1 && (
{state.expandButtonStatus === 0 ? "Expand" : "Fold"}
)} 0}>
{imageUrls.map((imgUrl, idx) => ( ))}
); }; export function formatMemoContent(content: string) { const tempElement = document.createElement("div"); tempElement.innerHTML = parseMarkedToHtml(escape(content)); return tempElement.innerHTML .replace(IMAGE_URL_REG, "") .replace(TAG_REG, "#$1 ") .replace(LINK_REG, "$1") .replace(MEMO_LINK_REG, "$1"); } export default memo(Memo);