Skip to content

Commit

Permalink
chore: update archived page
Browse files Browse the repository at this point in the history
  • Loading branch information
johnnyjoygh committed Jan 10, 2025
1 parent 5d40f38 commit ac7121c
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 152 deletions.
112 changes: 62 additions & 50 deletions web/src/components/MemoActionMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,32 @@ import { useTranslate } from "@/utils/i18n";

interface Props {
memo: Memo;
readonly?: boolean;
className?: string;
hiddenActions?: ("edit" | "archive" | "delete" | "share" | "pin" | "remove_completed_task_list")[];
onEdit?: () => void;
}

const checkHasCompletedTaskList = (memo: Memo) => {
for (const node of memo.nodes) {
if (node.type === NodeType.LIST && node.listNode?.children && node.listNode?.children?.length > 0) {
for (let j = 0; j < node.listNode.children.length; j++) {
if (node.listNode.children[j].type === NodeType.TASK_LIST_ITEM && node.listNode.children[j].taskListItemNode?.complete) {
return true;
}
}
}
}
return false;
};

const MemoActionMenu = (props: Props) => {
const { memo, hiddenActions } = props;
const { memo, readonly } = props;
const t = useTranslate();
const location = useLocation();
const navigateTo = useNavigateTo();
const memoStore = useMemoStore();
const isArchived = memo.rowStatus === RowStatus.ARCHIVED;
const hasCompletedTaskList = checkHasCompletedTaskList(memo);
const isInMemoDetailPage = location.pathname.startsWith(`/m/${memo.uid}`);

const handleTogglePinMemoBtnClick = async () => {
Expand Down Expand Up @@ -69,26 +84,17 @@ const MemoActionMenu = (props: Props) => {
};

const handleToggleMemoStatusClick = async () => {
const status = memo.rowStatus === RowStatus.ARCHIVED ? RowStatus.ACTIVE : RowStatus.ARCHIVED;
const message = memo.rowStatus === RowStatus.ARCHIVED ? t("message.restored-successfully") : t("message.archived-successfully");
try {
if (memo.rowStatus === RowStatus.ARCHIVED) {
await memoStore.updateMemo(
{
name: memo.name,
rowStatus: RowStatus.ACTIVE,
},
["row_status"],
);
toast(t("message.restored-successfully"));
} else {
await memoStore.updateMemo(
{
name: memo.name,
rowStatus: RowStatus.ARCHIVED,
},
["row_status"],
);
toast.success(t("message.archived-successfully"));
}
await memoStore.updateMemo(
{
name: memo.name,
rowStatus: status,
},
["row_status"],
);
toast(message);
} catch (error: any) {
toast.error(error.details);
console.error(error);
Expand Down Expand Up @@ -155,38 +161,44 @@ const MemoActionMenu = (props: Props) => {
</span>
</MenuButton>
<Menu className="text-sm" size="sm" placement="bottom-end">
{!hiddenActions?.includes("pin") && (
<MenuItem onClick={handleTogglePinMemoBtnClick}>
{memo.pinned ? <BookmarkMinusIcon className="w-4 h-auto" /> : <BookmarkPlusIcon className="w-4 h-auto" />}
{memo.pinned ? t("common.unpin") : t("common.pin")}
</MenuItem>
{!readonly && !isArchived && (
<>
<MenuItem onClick={handleTogglePinMemoBtnClick}>
{memo.pinned ? <BookmarkMinusIcon className="w-4 h-auto" /> : <BookmarkPlusIcon className="w-4 h-auto" />}
{memo.pinned ? t("common.unpin") : t("common.pin")}
</MenuItem>
<MenuItem onClick={handleEditMemoClick}>
<Edit3Icon className="w-4 h-auto" />
{t("common.edit")}
</MenuItem>
</>
)}
{!hiddenActions?.includes("edit") && props.onEdit && (
<MenuItem onClick={handleEditMemoClick}>
<Edit3Icon className="w-4 h-auto" />
{t("common.edit")}
</MenuItem>
)}
{!hiddenActions?.includes("share") && (
<MenuItem onClick={handleCopyLink}>
<CopyIcon className="w-4 h-auto" />
{t("memo.copy-link")}
</MenuItem>
)}
<MenuItem color="warning" onClick={handleToggleMemoStatusClick}>
{memo.rowStatus === RowStatus.ARCHIVED ? <ArchiveRestoreIcon className="w-4 h-auto" /> : <ArchiveIcon className="w-4 h-auto" />}
{memo.rowStatus === RowStatus.ARCHIVED ? t("common.restore") : t("common.archive")}
<MenuItem onClick={handleCopyLink}>
<CopyIcon className="w-4 h-auto" />
{t("memo.copy-link")}
</MenuItem>
{!hiddenActions?.includes("remove_completed_task_list") && (
<MenuItem color="danger" onClick={handleRemoveCompletedTaskListItemsClick}>
<SquareCheckIcon className="w-4 h-auto" />
{t("memo.remove-completed-task-list-items")}
</MenuItem>
{!readonly && (
<>
{!isArchived && hasCompletedTaskList && (
<MenuItem color="danger" onClick={handleRemoveCompletedTaskListItemsClick}>
<SquareCheckIcon className="w-4 h-auto" />
{t("memo.remove-completed-task-list-items")}
</MenuItem>
)}
<MenuItem color="warning" onClick={handleToggleMemoStatusClick}>
{memo.rowStatus === RowStatus.ARCHIVED ? (
<ArchiveRestoreIcon className="w-4 h-auto" />
) : (
<ArchiveIcon className="w-4 h-auto" />
)}
{memo.rowStatus === RowStatus.ARCHIVED ? t("common.restore") : t("common.archive")}
</MenuItem>
<MenuItem color="danger" onClick={handleDeleteMemoClick}>
<TrashIcon className="w-4 h-auto" />
{t("common.delete")}
</MenuItem>
</>
)}
<MenuItem color="danger" onClick={handleDeleteMemoClick}>
<TrashIcon className="w-4 h-auto" />
{t("common.delete")}
</MenuItem>
</Menu>
</Dropdown>
);
Expand Down
4 changes: 3 additions & 1 deletion web/src/components/MemoReactionListView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { uniq } from "lodash-es";
import { memo, useEffect, useState } from "react";
import useCurrentUser from "@/hooks/useCurrentUser";
import { useUserStore } from "@/store/v1";
import { RowStatus } from "@/types/proto/api/v1/common";
import { Memo } from "@/types/proto/api/v1/memo_service";
import { Reaction } from "@/types/proto/api/v1/reaction_service";
import { User } from "@/types/proto/api/v1/user_service";
Expand All @@ -18,6 +19,7 @@ const MemoReactionListView = (props: Props) => {
const currentUser = useCurrentUser();
const userStore = useUserStore();
const [reactionGroup, setReactionGroup] = useState<Map<string, User[]>>(new Map());
const readonly = memo.rowStatus === RowStatus.ARCHIVED;

useEffect(() => {
(async () => {
Expand All @@ -38,7 +40,7 @@ const MemoReactionListView = (props: Props) => {
{Array.from(reactionGroup).map(([reactionType, users]) => {
return <ReactionView key={`${reactionType.toString()} ${users.length}`} memo={memo} reactionType={reactionType} users={users} />;
})}
{currentUser && <ReactionSelector memo={memo} />}
{!readonly && currentUser && <ReactionSelector memo={memo} />}
</div>
)
);
Expand Down
51 changes: 9 additions & 42 deletions web/src/components/MemoView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import useAsyncEffect from "@/hooks/useAsyncEffect";
import useCurrentUser from "@/hooks/useCurrentUser";
import useNavigateTo from "@/hooks/useNavigateTo";
import { useUserStore, useWorkspaceSettingStore, useMemoStore } from "@/store/v1";
import { NodeType } from "@/types/proto/api/v1/markdown_service";
import { RowStatus } from "@/types/proto/api/v1/common";
import { MemoRelation_Type } from "@/types/proto/api/v1/memo_relation_service";
import { Memo, Visibility } from "@/types/proto/api/v1/memo_service";
import { WorkspaceMemoRelatedSetting } from "@/types/proto/api/v1/workspace_setting_service";
Expand All @@ -29,7 +29,6 @@ import VisibilityIcon from "./VisibilityIcon";

interface Props {
memo: Memo;
displayTimeFormat?: "auto" | "time";
compact?: boolean;
showCreator?: boolean;
showVisibility?: boolean;
Expand Down Expand Up @@ -59,6 +58,7 @@ const MemoView: React.FC<Props> = (props: Props) => {
(relation) => relation.type === MemoRelation_Type.COMMENT && relation.relatedMemo?.name === memo.name,
).length;
const relativeTimeFormat = Date.now() - memo.displayTime!.getTime() > 1000 * 60 * 60 * 24 ? "datetime" : "auto";
const isArchived = memo.rowStatus === RowStatus.ARCHIVED;
const readonly = memo.creator !== user?.name && !isSuperUser(user);
const isInMemoDetailPage = location.pathname.startsWith(`/m/${memo.uid}`);
const parentPage = props.parentPage || location.pathname;
Expand Down Expand Up @@ -115,42 +115,11 @@ const MemoView: React.FC<Props> = (props: Props) => {
}
};

const displayTime =
props.displayTimeFormat === "time" ? (
memo.displayTime?.toLocaleTimeString()
) : (
<relative-time datetime={memo.displayTime?.toISOString()} format={relativeTimeFormat}></relative-time>
);

const handleHiddenActions = () => {
const hiddenActions: ("edit" | "archive" | "delete" | "share" | "pin" | "remove_completed_task_list")[] = [];
if (!props.showPinned) {
hiddenActions.push("pin");
}
// check if the content has done tasks
let hasCompletedTaskList = false;
const newNodes = JSON.parse(JSON.stringify(memo.nodes));
for (let i = 0; i < newNodes.length; i++) {
if (hasCompletedTaskList) {
break;
}
if (newNodes[i].type === NodeType.LIST && newNodes[i].listNode?.children?.length > 0) {
for (let j = 0; j < newNodes[i].listNode.children.length; j++) {
if (
newNodes[i].listNode.children[j].type === NodeType.TASK_LIST_ITEM &&
newNodes[i].listNode.children[j].taskListItemNode?.complete
) {
hasCompletedTaskList = true;
break;
}
}
}
}
if (!hasCompletedTaskList) {
hiddenActions.push("remove_completed_task_list");
}
return hiddenActions;
};
const displayTime = isArchived ? (
memo.displayTime?.toLocaleString()
) : (
<relative-time datetime={memo.displayTime?.toISOString()} format={relativeTimeFormat}></relative-time>
);

return (
<div
Expand Down Expand Up @@ -213,7 +182,7 @@ const MemoView: React.FC<Props> = (props: Props) => {
</span>
</Tooltip>
)}
{currentUser && <ReactionSelector className="border-none w-auto h-auto" memo={memo} />}
{currentUser && !isArchived && <ReactionSelector className="border-none w-auto h-auto" memo={memo} />}
</div>
{!isInMemoDetailPage && (workspaceMemoRelatedSetting.enableComment || commentAmount > 0) && (
<Link
Expand All @@ -238,9 +207,7 @@ const MemoView: React.FC<Props> = (props: Props) => {
</span>
</Tooltip>
)}
{!readonly && (
<MemoActionMenu className="-ml-1" memo={memo} hiddenActions={handleHiddenActions()} onEdit={() => setShowEditor(true)} />
)}
<MemoActionMenu className="-ml-1" memo={memo} readonly={readonly} onEdit={() => setShowEditor(true)} />
</div>
</div>
<MemoContent
Expand Down
6 changes: 4 additions & 2 deletions web/src/components/ReactionView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import clsx from "clsx";
import { memoServiceClient } from "@/grpcweb";
import useCurrentUser from "@/hooks/useCurrentUser";
import { useMemoStore } from "@/store/v1";
import { RowStatus } from "@/types/proto/api/v1/common";
import { Memo } from "@/types/proto/api/v1/memo_service";
import { User } from "@/types/proto/api/v1/user_service";

Expand Down Expand Up @@ -32,9 +33,10 @@ const ReactionView = (props: Props) => {
const currentUser = useCurrentUser();
const memoStore = useMemoStore();
const hasReaction = users.some((user) => currentUser && user.username === currentUser.username);
const readonly = memo.rowStatus === RowStatus.ARCHIVED;

const handleReactionClick = async () => {
if (!currentUser) {
if (!currentUser || readonly) {
return;
}

Expand Down Expand Up @@ -68,7 +70,7 @@ const ReactionView = (props: Props) => {
className={clsx(
"h-7 border px-2 py-0.5 rounded-full flex flex-row justify-center items-center gap-1 dark:border-zinc-700",
"text-sm text-gray-600 dark:text-gray-400",
currentUser && "cursor-pointer",
currentUser && !readonly && "cursor-pointer",
hasReaction && "bg-blue-100 border-blue-200 dark:bg-zinc-900",
)}
onClick={handleReactionClick}
Expand Down
61 changes: 4 additions & 57 deletions web/src/pages/Archived.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,20 @@
import { Tooltip } from "@mui/joy";
import dayjs from "dayjs";
import { ArchiveIcon, ArchiveRestoreIcon, TrashIcon } from "lucide-react";
import { ClientError } from "nice-grpc-web";
import { ArchiveIcon } from "lucide-react";
import { useMemo } from "react";
import toast from "react-hot-toast";
import MemoContent from "@/components/MemoContent";
import MemoFilters from "@/components/MemoFilters";
import MemoView from "@/components/MemoView";
import MobileHeader from "@/components/MobileHeader";
import PagedMemoList from "@/components/PagedMemoList";
import SearchBar from "@/components/SearchBar";
import useCurrentUser from "@/hooks/useCurrentUser";
import { useMemoFilterStore, useMemoStore } from "@/store/v1";
import { useMemoFilterStore } 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";

const Archived = () => {
const t = useTranslate();
const user = useCurrentUser();
const memoStore = useMemoStore();
const memoFilterStore = useMemoFilterStore();

const memoListFilter = useMemo(() => {
Expand All @@ -44,29 +40,6 @@ const Archived = () => {
return filters.join(" && ");
}, [user, memoFilterStore.filters]);

const handleDeleteMemoClick = async (memo: Memo) => {
const confirmed = window.confirm(t("memo.delete-confirm"));
if (confirmed) {
await memoStore.deleteMemo(memo.name);
}
};

const handleRestoreMemoClick = async (memo: Memo) => {
try {
await memoStore.updateMemo(
{
name: memo.name,
rowStatus: RowStatus.ACTIVE,
},
["row_status"],
);
toast(t("message.restored-successfully"));
} catch (error: unknown) {
console.error(error);
toast.error((error as ClientError).details);
}
};

return (
<section className="@container w-full max-w-5xl min-h-full flex flex-col justify-start items-center sm:pt-3 md:pt-6 pb-8">
<MobileHeader />
Expand All @@ -83,33 +56,7 @@ const Archived = () => {
</div>
<MemoFilters />
<PagedMemoList
renderer={(memo: Memo) => (
<div
key={memo.name}
className="relative flex flex-col justify-start items-start w-full p-4 pt-3 mb-2 bg-white dark:bg-zinc-800 rounded-lg"
>
<div className="w-full mb-1 flex flex-row justify-between items-center">
<div className="w-full max-w-[calc(100%-20px)] flex flex-row justify-start items-center mr-1">
<div className="text-sm leading-6 text-gray-400 select-none">
<relative-time datetime={memo.displayTime?.toISOString()}></relative-time>
</div>
</div>
<div className="flex flex-row justify-end items-center gap-x-2">
<Tooltip title={t("common.restore")} placement="top">
<button onClick={() => handleRestoreMemoClick(memo)}>
<ArchiveRestoreIcon className="w-4 h-auto cursor-pointer text-gray-500 dark:text-gray-400" />
</button>
</Tooltip>
<Tooltip title={t("common.delete")} placement="top">
<button onClick={() => handleDeleteMemoClick(memo)} className="text-gray-500 dark:text-gray-400">
<TrashIcon className="w-4 h-auto cursor-pointer" />
</button>
</Tooltip>
</div>
</div>
<MemoContent key={`${memo.name}-${memo.displayTime}`} memoName={memo.name} nodes={memo.nodes} readonly={true} />
</div>
)}
renderer={(memo: Memo) => <MemoView key={`${memo.name}-${memo.updateTime}`} memo={memo} showVisibility compact />}
listSort={(memos: Memo[]) =>
memos
.filter((memo) => memo.rowStatus === RowStatus.ARCHIVED)
Expand Down

0 comments on commit ac7121c

Please sign in to comment.