diff --git a/web/src/components/ActivityCalendar.tsx b/web/src/components/ActivityCalendar.tsx index 996894d8..fcf3cd5c 100644 --- a/web/src/components/ActivityCalendar.tsx +++ b/web/src/components/ActivityCalendar.tsx @@ -18,11 +18,11 @@ const getCellAdditionalStyles = (count: number, maxCount: number) => { const ratio = count / maxCount; if (ratio > 0.7) { - return "bg-teal-600 text-gray-100 dark:opacity-80"; + return "bg-teal-700 text-gray-100 dark:opacity-80"; } else if (ratio > 0.4) { - return "bg-teal-400 text-gray-100 dark:opacity-80"; + return "bg-teal-600 text-gray-100 dark:opacity-80"; } else { - return "bg-teal-300 text-gray-100 dark:opacity-80"; + return "bg-teal-500 text-gray-100 dark:opacity-70"; } }; @@ -58,29 +58,36 @@ const ActivityCalendar = (props: Props) => { const tooltipText = count ? t("memo.count-memos-in-date", { count: count, date: date }) : date; const isSelected = new Date(props.selectedDate).toDateString() === new Date(date).toDateString(); return day ? ( - + count > 0 ? ( + +
count && onClick && onClick(new Date(date).toDateString())} + > + {day} +
+
+ ) : (
0 ? "cursor-pointer" : "cursor-default", )} - onClick={() => count && onClick && onClick(new Date(date).toDateString())} > {day}
-
+ ) ) : ( -
+
); })} diff --git a/web/src/components/MemoActionMenu.tsx b/web/src/components/MemoActionMenu.tsx index 6a8471b5..502c8cc5 100644 --- a/web/src/components/MemoActionMenu.tsx +++ b/web/src/components/MemoActionMenu.tsx @@ -9,7 +9,6 @@ import { useMemoStore } from "@/store/v1"; import { RowStatus } from "@/types/proto/api/v1/common"; import { Memo } from "@/types/proto/api/v1/memo_service"; import { useTranslate } from "@/utils/i18n"; -import showMemoEditorDialog from "./MemoEditor/MemoEditorDialog"; interface Props { memo: Memo; @@ -55,12 +54,6 @@ const MemoActionMenu = (props: Props) => { props.onEdit(); return; } - - // TODO: remove me later. - showMemoEditorDialog({ - memoName: memo.name, - cacheKey: `${memo.name}-${memo.updateTime}`, - }); }; const handleToggleMemoStatusClick = async () => { @@ -125,7 +118,7 @@ const MemoActionMenu = (props: Props) => { {memo.pinned ? t("common.unpin") : t("common.pin")} )} - {!hiddenActions?.includes("edit") && ( + {!hiddenActions?.includes("edit") && props.onEdit && ( {t("common.edit")} diff --git a/web/src/components/MemoEditor/MemoEditorDialog.tsx b/web/src/components/MemoEditor/MemoEditorDialog.tsx deleted file mode 100644 index 3adb7fca..00000000 --- a/web/src/components/MemoEditor/MemoEditorDialog.tsx +++ /dev/null @@ -1,103 +0,0 @@ -import { IconButton } from "@mui/joy"; -import clsx from "clsx"; -import { useEffect, useRef } from "react"; -import useCurrentUser from "@/hooks/useCurrentUser"; -import useDateTime from "@/hooks/useDateTime"; -import { useMemoStore, useTagStore } from "@/store/v1"; -import { Memo } from "@/types/proto/api/v1/memo_service"; -import MemoEditor, { Props as MemoEditorProps } from "."; -import { generateDialog } from "../Dialog"; -import Icon from "../Icon"; - -interface Props extends DialogProps, MemoEditorProps {} - -const MemoEditorDialog: React.FC = ({ - memoName, - parentMemoName, - placeholder, - cacheKey, - relationList, - onConfirm, - destroy, -}: Props) => { - const tagStore = useTagStore(); - const memoStore = useMemoStore(); - const { setDateTime, displayDateTime, datePickerDateTime } = useDateTime(memoStore.getMemoByName(memoName || "")?.displayTime); - const memoPatchRef = useRef>({ - displayTime: memoStore.getMemoByName(memoName || "")?.displayTime, - }); - const user = useCurrentUser(); - - useEffect(() => { - tagStore.fetchTags({ user }, { skipCache: false }); - }, []); - - const updateDisplayTime = (displayTime: string) => { - setDateTime(displayTime); - memoPatchRef.current.displayTime = new Date(displayTime); - }; - - const handleCloseBtnClick = () => { - destroy(); - }; - - const handleConfirm = (memoName: string) => { - handleCloseBtnClick(); - if (onConfirm) { - onConfirm(memoName); - } - }; - - return ( - <> -
-
- {displayDateTime ? ( -
- {displayDateTime} - e.target.showPicker()} - onChange={(e) => updateDisplayTime(e.target.value)} - /> -
- ) : ( - <> - -

Memos

- - )} -
- - - -
-
- -
- - ); -}; - -export default function showMemoEditorDialog(props: Partial = {}): void { - generateDialog( - { - className: "memo-editor-dialog", - dialogName: "memo-editor-dialog", - }, - MemoEditorDialog, - props, - ); -} diff --git a/web/src/components/MemoEditor/index.tsx b/web/src/components/MemoEditor/index.tsx index a728a8da..5ad0e155 100644 --- a/web/src/components/MemoEditor/index.tsx +++ b/web/src/components/MemoEditor/index.tsx @@ -1,4 +1,5 @@ import { Select, Option, Button, Divider } from "@mui/joy"; +import { isEqual } from "lodash-es"; import React, { useEffect, useMemo, useRef, useState } from "react"; import { toast } from "react-hot-toast"; import { useTranslation } from "react-i18next"; @@ -6,6 +7,7 @@ import useLocalStorage from "react-use/lib/useLocalStorage"; import { memoServiceClient } from "@/grpcweb"; import { TAB_SPACE_WIDTH } from "@/helpers/consts"; import { isValidUrl } from "@/helpers/utils"; +import useAsyncEffect from "@/hooks/useAsyncEffect"; import useCurrentUser from "@/hooks/useCurrentUser"; import { useMemoStore, useResourceStore, useUserStore, useWorkspaceSettingStore } from "@/store/v1"; import { MemoRelation, MemoRelation_Type } from "@/types/proto/api/v1/memo_relation_service"; @@ -32,11 +34,11 @@ export interface Props { className?: string; cacheKey?: string; placeholder?: string; + // The name of the memo to be edited. memoName?: string; + // The name of the parent memo if the memo is a comment. parentMemoName?: string; - relationList?: MemoRelation[]; autoFocus?: boolean; - memoPatchRef?: React.MutableRefObject>; onConfirm?: (memoName: string) => void; onCancel?: () => void; } @@ -62,11 +64,12 @@ const MemoEditor = (props: Props) => { const [state, setState] = useState({ memoVisibility: Visibility.PRIVATE, resourceList: [], - relationList: props.relationList ?? [], + relationList: [], isUploadingResource: false, isRequesting: false, isComposing: false, }); + const [displayTime, setDisplayTime] = useState(); const [hasContent, setHasContent] = useState(false); const editorRef = useRef(null); const userSetting = userStore.userSetting as UserSetting; @@ -102,22 +105,24 @@ const MemoEditor = (props: Props) => { })); }, [userSetting.memoVisibility, workspaceMemoRelatedSetting.disallowPublicVisibility]); - useEffect(() => { - if (memoName) { - memoStore.getOrFetchMemoByName(memoName).then((memo) => { - if (memo) { - handleEditorFocus(); - setState((prevState) => ({ - ...prevState, - memoVisibility: memo.visibility, - resourceList: memo.resources, - relationList: memo.relations, - })); - if (!contentCache) { - editorRef.current?.setContent(memo.content ?? ""); - } - } - }); + useAsyncEffect(async () => { + if (!memoName) { + return; + } + + const memo = await memoStore.getOrFetchMemoByName(memoName); + if (memo) { + handleEditorFocus(); + setState((prevState) => ({ + ...prevState, + memoVisibility: memo.visibility, + resourceList: memo.resources, + relationList: memo.relations, + })); + setDisplayTime(memo.displayTime); + if (!contentCache) { + editorRef.current?.setContent(memo.content ?? ""); + } } }, [memoName]); @@ -289,18 +294,16 @@ const MemoEditor = (props: Props) => { const prevMemo = await memoStore.getOrFetchMemoByName(memoName); if (prevMemo) { const updateMask = ["content", "visibility"]; - if (props.memoPatchRef?.current?.displayTime) { + const memoPatch: Partial = { + name: prevMemo.name, + content, + visibility: state.memoVisibility, + }; + if (!isEqual(displayTime, prevMemo.displayTime)) { updateMask.push("display_time"); + memoPatch.displayTime = displayTime; } - const memo = await memoStore.updateMemo( - { - name: prevMemo.name, - content, - visibility: state.memoVisibility, - ...props.memoPatchRef?.current, - }, - updateMask, - ); + const memo = await memoStore.updateMemo(memoPatch, updateMask); await memoServiceClient.setMemoResources({ name: memo.name, resources: state.resourceList, @@ -409,6 +412,18 @@ const MemoEditor = (props: Props) => { onCompositionStart={handleCompositionStart} onCompositionEnd={handleCompositionEnd} > + {memoName && displayTime && ( +
+ {displayTime.toLocaleString()} + e.target.showPicker()} + onChange={(e) => setDisplayTime(new Date(e.target.value))} + /> +
+ )} diff --git a/web/src/components/MemoRelationListView.tsx b/web/src/components/MemoRelationListView.tsx index 4cbcb733..587d0280 100644 --- a/web/src/components/MemoRelationListView.tsx +++ b/web/src/components/MemoRelationListView.tsx @@ -44,7 +44,7 @@ const MemoRelationListView = (props: Props) => { } return ( -
+
{referencingMemoList.length > 0 && ( )} {referencedMemoList.length > 0 && ( @@ -68,6 +69,7 @@ const MemoRelationListView = (props: Props) => { > Referenced by + ({referencedMemoList.length}) )}
@@ -81,7 +83,7 @@ const MemoRelationListView = (props: Props) => { to={`/m/${memo.uid}`} unstable_viewTransition > - + {memo.snippet} ); @@ -98,7 +100,7 @@ const MemoRelationListView = (props: Props) => { to={`/m/${memo.uid}`} unstable_viewTransition > - + {memo.snippet} ); diff --git a/web/src/components/MemoView.tsx b/web/src/components/MemoView.tsx index 9ef3f0ef..910eaa82 100644 --- a/web/src/components/MemoView.tsx +++ b/web/src/components/MemoView.tsx @@ -106,77 +106,6 @@ const MemoView: React.FC = (props: Props) => { )} ref={memoContainerRef} > -
-
- {props.showCreator && creator ? ( -
- - - -
- - {creator.nickname || creator.username} - -
- {displayTime} -
-
-
- ) : ( -
- {displayTime} -
- )} -
- {!showEditor && ( -
-
- {props.showVisibility && memo.visibility !== Visibility.PRIVATE && ( - - - - - - )} - {currentUser && } -
- {!isInMemoDetailPage && ( - - - {commentAmount > 0 && {commentAmount}} - - )} - {props.showPinned && memo.pinned && ( - - - - )} - {!readonly && ( - setShowEditor(true)} - /> - )} -
- )} -
- {showEditor ? ( = (props: Props) => { /> ) : ( <> +
+
+ {props.showCreator && creator ? ( +
+ + + +
+ + {creator.nickname || creator.username} + +
+ {displayTime} +
+
+
+ ) : ( +
+ {displayTime} +
+ )} +
+
+
+ {props.showVisibility && memo.visibility !== Visibility.PRIVATE && ( + + + + + + )} + {currentUser && } +
+ {!isInMemoDetailPage && ( + + + {commentAmount > 0 && {commentAmount}} + + )} + {props.showPinned && memo.pinned && ( + + + + )} + {!readonly && ( + setShowEditor(true)} + /> + )} +
+
{ const memoStore = useMemoStore(); const filterStore = useFilterStore(); const [memoAmount, setMemoAmount] = useState(0); - const [isRequesting, setIsRequesting] = useState(false); const [memoStats, setMemoStats] = useState({ link: 0, taskList: 0, code: 0, incompleteTasks: 0 }); const [activityStats, setActivityStats] = useState>({}); - const monthString = dayjs(new Date().toDateString()).format("YYYY-MM"); + const [selectedDate] = useState(new Date()); + const [monthString, setMonthString] = useState(dayjs(selectedDate.toDateString()).format("YYYY-MM")); const days = Math.ceil((Date.now() - currentUser.createTime!.getTime()) / 86400000); const filter = filterStore.state; useAsyncEffect(async () => { - setIsRequesting(true); const { properties } = await memoServiceClient.listMemoProperties({ name: `memos/-`, }); @@ -62,7 +62,6 @@ const UserStatisticsView = () => { timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, filter: filters.join(" && "), }); - setActivityStats( Object.fromEntries( Object.entries(stats).filter(([date]) => { @@ -70,7 +69,6 @@ const UserStatisticsView = () => { }), ), ); - setIsRequesting(false); }, [memoStore.stateId]); const handleRebuildMemoTags = async () => { @@ -83,92 +81,94 @@ const UserStatisticsView = () => { return (
-
-

- {new Date().toLocaleDateString(i18n.language, { month: "long", day: "numeric" })} -

-
- - - +
+
+ + {new Date(monthString).toLocaleString(i18n.language, { year: "numeric", month: "long" })} + e.target.showPicker()} + onChange={(e) => setMonthString(e.target.value)} + /> +
+
+ + + + + + + +
-
- +
+ + {memoAmount > 0 && ( +

+ {memoAmount} memos in {days} days +

+ )}
-
-
-
- - Days + +
+
filterStore.setMemoPropertyFilter({ hasLink: !filter.memoPropertyFilter?.hasLink })} + > +
+ + {t("memo.links")}
- {days} + {memoStats.link}
-
-
- - Memos -
- {isRequesting ? : {memoAmount}} -
- -
-
filterStore.setMemoPropertyFilter({ hasLink: !filter.memoPropertyFilter?.hasLink })} - > -
- - {t("memo.links")} -
- {memoStats.link} -
-
filterStore.setMemoPropertyFilter({ hasTaskList: !filter.memoPropertyFilter?.hasTaskList })} - > -
- {memoStats.incompleteTasks > 0 ? ( - - ) : ( - - )} - {t("memo.to-do")} -
+
filterStore.setMemoPropertyFilter({ hasTaskList: !filter.memoPropertyFilter?.hasTaskList })} + > +
{memoStats.incompleteTasks > 0 ? ( - -
- {memoStats.taskList - memoStats.incompleteTasks} - / - {memoStats.taskList} -
-
+ ) : ( - {memoStats.taskList} + )} + {t("memo.to-do")}
-
filterStore.setMemoPropertyFilter({ hasCode: !filter.memoPropertyFilter?.hasCode })} - > -
- - {t("memo.code")} -
- {memoStats.code} + {memoStats.incompleteTasks > 0 ? ( + +
+ {memoStats.taskList - memoStats.incompleteTasks} + / + {memoStats.taskList} +
+
+ ) : ( + {memoStats.taskList} + )} +
+
filterStore.setMemoPropertyFilter({ hasCode: !filter.memoPropertyFilter?.hasCode })} + > +
+ + {t("memo.code")}
+ {memoStats.code}
diff --git a/web/src/hooks/index.ts b/web/src/hooks/index.ts index b8af6852..881f9458 100644 --- a/web/src/hooks/index.ts +++ b/web/src/hooks/index.ts @@ -4,4 +4,3 @@ export * from "./useNavigateTo"; export * from "./useAsyncEffect"; export * from "./useFilterWithUrlParams"; export * from "./useResponsiveWidth"; -export * from "./useDateTime"; diff --git a/web/src/hooks/useDateTime.ts b/web/src/hooks/useDateTime.ts deleted file mode 100644 index 0c5aaec9..00000000 --- a/web/src/hooks/useDateTime.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { useState } from "react"; - -const useDateTime = (initalState?: Date) => { - const [dateTime, setDateTimeInternal] = useState(initalState && new Date(initalState)); - - return { - setDateTime: (dateTimeString: string) => setDateTimeInternal(new Date(dateTimeString)), - displayDateTime: dateTime && dateTime.toLocaleString(), - datePickerDateTime: dateTime && new Date(dateTime.getTime() - dateTime.getTimezoneOffset() * 60000).toISOString().split(".")[0], - }; -}; - -export default useDateTime;