mirror of
https://github.com/usememos/memos.git
synced 2025-02-14 18:30:42 +01:00
feat: update timeline page
This commit is contained in:
parent
33133ea1a3
commit
1d99dad435
@ -6,6 +6,7 @@ import { useTranslate } from "@/utils/i18n";
|
||||
interface Props {
|
||||
// Format: 2021-1
|
||||
month: string;
|
||||
selectedDate: string;
|
||||
data: Record<string, number>;
|
||||
onClick?: (date: string) => void;
|
||||
}
|
||||
@ -28,8 +29,8 @@ const getCellAdditionalStyles = (count: number, maxCount: number) => {
|
||||
const ActivityCalendar = (props: Props) => {
|
||||
const t = useTranslate();
|
||||
const { month: monthStr, data, onClick } = props;
|
||||
const year = new Date(monthStr).getUTCFullYear();
|
||||
const month = new Date(monthStr).getUTCMonth() + 1;
|
||||
const year = new Date(monthStr).getFullYear();
|
||||
const month = new Date(monthStr).getMonth() + 1;
|
||||
const dayInMonth = new Date(year, month, 0).getDate();
|
||||
const firstDay = new Date(year, month - 1, 1).getDay();
|
||||
const lastDay = new Date(year, month - 1, dayInMonth).getDay();
|
||||
@ -55,15 +56,19 @@ const ActivityCalendar = (props: Props) => {
|
||||
const count = data[date] || 0;
|
||||
const isToday = new Date().toDateString() === new Date(date).toDateString();
|
||||
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 ? (
|
||||
<Tooltip className="shrink-0" key={`${date}-${index}`} title={tooltipText} placement="top" arrow>
|
||||
<div
|
||||
className={clsx(
|
||||
"w-4 h-4 text-[9px] rounded-md flex justify-center items-center border border-transparent",
|
||||
"w-4 h-4 text-[9px] rounded-md flex justify-center items-center border",
|
||||
getCellAdditionalStyles(count, maxCount),
|
||||
isToday && "border-gray-600 dark:!border-gray-500",
|
||||
isToday && "border-gray-600 dark:border-zinc-300",
|
||||
isSelected && "font-bold border-gray-600 dark:border-zinc-300",
|
||||
!isToday && !isSelected && "border-transparent",
|
||||
count > 0 && "cursor-pointer",
|
||||
)}
|
||||
onClick={() => count && onClick && onClick(date)}
|
||||
onClick={() => count && onClick && onClick(new Date(date).toDateString())}
|
||||
>
|
||||
{day}
|
||||
</div>
|
||||
|
@ -1,105 +0,0 @@
|
||||
import { Button, IconButton, Input } from "@mui/joy";
|
||||
import { useEffect, useState } from "react";
|
||||
import { toast } from "react-hot-toast";
|
||||
import { getNormalizedTimeString } from "@/helpers/datetime";
|
||||
import { MemoNamePrefix, useMemoStore } from "@/store/v1";
|
||||
import { useTranslate } from "@/utils/i18n";
|
||||
import { generateDialog } from "./Dialog";
|
||||
import Icon from "./Icon";
|
||||
|
||||
interface Props extends DialogProps {
|
||||
memoId: number;
|
||||
}
|
||||
|
||||
const ChangeMemoCreatedTsDialog: React.FC<Props> = (props: Props) => {
|
||||
const t = useTranslate();
|
||||
const { destroy, memoId } = props;
|
||||
const memoStore = useMemoStore();
|
||||
const [createdAt, setCreatedAt] = useState("");
|
||||
const maxDatetimeValue = getNormalizedTimeString();
|
||||
|
||||
useEffect(() => {
|
||||
memoStore.getOrFetchMemoByName(`${MemoNamePrefix}${memoId}`).then((memo) => {
|
||||
if (memo) {
|
||||
const datetime = getNormalizedTimeString(memo.createTime);
|
||||
setCreatedAt(datetime);
|
||||
} else {
|
||||
toast.error(t("message.memo-not-found"));
|
||||
destroy();
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleCloseBtnClick = () => {
|
||||
destroy();
|
||||
};
|
||||
|
||||
const handleDatetimeInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const datetime = e.target.value as string;
|
||||
setCreatedAt(datetime);
|
||||
};
|
||||
|
||||
const handleSaveBtnClick = async () => {
|
||||
try {
|
||||
await memoStore.updateMemo(
|
||||
{
|
||||
name: `${MemoNamePrefix}${memoId}`,
|
||||
createTime: new Date(createdAt),
|
||||
},
|
||||
["created_ts"],
|
||||
);
|
||||
toast.success("Updated memo created time successfully.");
|
||||
handleCloseBtnClick();
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
toast.error(error.response.data.message);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="dialog-header-container">
|
||||
<p className="title-text">{t("message.change-memo-created-time")}</p>
|
||||
<IconButton size="sm" onClick={handleCloseBtnClick}>
|
||||
<Icon.X className="w-5 h-auto" />
|
||||
</IconButton>
|
||||
</div>
|
||||
<div className="flex flex-col justify-start items-start !w-72 max-w-full">
|
||||
<Input
|
||||
className="w-full"
|
||||
type="datetime-local"
|
||||
value={createdAt}
|
||||
slotProps={{
|
||||
input: {
|
||||
max: maxDatetimeValue,
|
||||
},
|
||||
}}
|
||||
onChange={handleDatetimeInputChange}
|
||||
/>
|
||||
<div className="flex flex-row justify-end items-center mt-4 w-full gap-x-2">
|
||||
<Button color="neutral" variant="plain" onClick={handleCloseBtnClick}>
|
||||
{t("common.cancel")}
|
||||
</Button>
|
||||
<Button color="primary" onClick={handleSaveBtnClick}>
|
||||
{t("common.save")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
function showChangeMemoCreatedTsDialog(memoId: number) {
|
||||
generateDialog(
|
||||
{
|
||||
className: "change-memo-created-ts-dialog",
|
||||
dialogName: "change-memo-created-ts-dialog",
|
||||
},
|
||||
ChangeMemoCreatedTsDialog,
|
||||
{
|
||||
memoId,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export default showChangeMemoCreatedTsDialog;
|
@ -94,8 +94,12 @@ const TagContainer: React.FC<TagContainerProps> = (props: TagContainerProps) =>
|
||||
style: "danger",
|
||||
dialogName: "delete-tag-dialog",
|
||||
onConfirm: async () => {
|
||||
await tagStore.deleteTag(tag);
|
||||
tagStore.fetchTags({ skipCache: true });
|
||||
await memoServiceClient.deleteMemoTag({
|
||||
parent: "memos/-",
|
||||
tag: tag,
|
||||
});
|
||||
await tagStore.fetchTags({ skipCache: true });
|
||||
toast.success(t("message.deleted-successfully"));
|
||||
},
|
||||
});
|
||||
};
|
||||
|
@ -4,12 +4,11 @@ import { memo, useCallback, useEffect, useRef, useState } from "react";
|
||||
import { Link, useLocation } from "react-router-dom";
|
||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
||||
import useNavigateTo from "@/hooks/useNavigateTo";
|
||||
import { extractMemoIdFromName, useUserStore } from "@/store/v1";
|
||||
import { useUserStore } from "@/store/v1";
|
||||
import { MemoRelation_Type } from "@/types/proto/api/v1/memo_relation_service";
|
||||
import { Memo, Visibility } from "@/types/proto/api/v1/memo_service";
|
||||
import { useTranslate } from "@/utils/i18n";
|
||||
import { convertVisibilityToString } from "@/utils/memo";
|
||||
import showChangeMemoCreatedTsDialog from "./ChangeMemoCreatedTsDialog";
|
||||
import Icon from "./Icon";
|
||||
import MemoActionMenu from "./MemoActionMenu";
|
||||
import MemoContent from "./MemoContent";
|
||||
@ -56,12 +55,8 @@ const MemoView: React.FC<Props> = (props: Props) => {
|
||||
})();
|
||||
}, []);
|
||||
|
||||
const handleGotoMemoDetailPage = (event: React.MouseEvent<HTMLDivElement>) => {
|
||||
if (event.altKey) {
|
||||
showChangeMemoCreatedTsDialog(extractMemoIdFromName(memo.name));
|
||||
} else {
|
||||
navigateTo(`/m/${memo.uid}`);
|
||||
}
|
||||
const handleGotoMemoDetailPage = () => {
|
||||
navigateTo(`/m/${memo.uid}`);
|
||||
};
|
||||
|
||||
const handleMemoContentClick = useCallback(async (e: React.MouseEvent) => {
|
||||
|
@ -31,9 +31,14 @@ const Archived = () => {
|
||||
.sort((a, b) => getTimeStampByDate(b.displayTime) - getTimeStampByDate(a.displayTime));
|
||||
|
||||
useEffect(() => {
|
||||
setIsRequesting(true);
|
||||
nextPageTokenRef.current = undefined;
|
||||
memoList.reset();
|
||||
fetchMemos();
|
||||
setTimeout(async () => {
|
||||
memoList.reset();
|
||||
const nextPageToken = await fetchMemos();
|
||||
nextPageTokenRef.current = nextPageToken;
|
||||
setIsRequesting(false);
|
||||
});
|
||||
}, [tagQuery, textQuery]);
|
||||
|
||||
const fetchMemos = async () => {
|
||||
@ -48,14 +53,12 @@ const Archived = () => {
|
||||
if (tagQuery) {
|
||||
filters.push(`tag == "${tagQuery}"`);
|
||||
}
|
||||
setIsRequesting(true);
|
||||
const data = await memoStore.fetchMemos({
|
||||
const { nextPageToken } = await memoStore.fetchMemos({
|
||||
pageSize: DEFAULT_LIST_MEMOS_PAGE_SIZE,
|
||||
filter: filters.join(" && "),
|
||||
pageToken: nextPageTokenRef.current,
|
||||
});
|
||||
setIsRequesting(false);
|
||||
nextPageTokenRef.current = data.nextPageToken;
|
||||
return nextPageToken;
|
||||
};
|
||||
|
||||
const handleDeleteMemoClick = async (memo: Memo) => {
|
||||
|
@ -27,9 +27,14 @@ const Explore = () => {
|
||||
const sortedMemos = memoList.value.sort((a, b) => getTimeStampByDate(b.displayTime) - getTimeStampByDate(a.displayTime));
|
||||
|
||||
useEffect(() => {
|
||||
setIsRequesting(true);
|
||||
nextPageTokenRef.current = undefined;
|
||||
memoList.reset();
|
||||
fetchMemos();
|
||||
setTimeout(async () => {
|
||||
memoList.reset();
|
||||
const nextPageToken = await fetchMemos();
|
||||
nextPageTokenRef.current = nextPageToken;
|
||||
setIsRequesting(false);
|
||||
});
|
||||
}, [tagQuery, textQuery]);
|
||||
|
||||
const fetchMemos = async () => {
|
||||
@ -44,14 +49,12 @@ const Explore = () => {
|
||||
if (tagQuery) {
|
||||
filters.push(`tag == "${tagQuery}"`);
|
||||
}
|
||||
setIsRequesting(true);
|
||||
const data = await memoStore.fetchMemos({
|
||||
const { nextPageToken } = await memoStore.fetchMemos({
|
||||
pageSize: DEFAULT_LIST_MEMOS_PAGE_SIZE,
|
||||
filter: filters.join(" && "),
|
||||
pageToken: nextPageTokenRef.current,
|
||||
});
|
||||
setIsRequesting(false);
|
||||
nextPageTokenRef.current = data.nextPageToken;
|
||||
return nextPageToken;
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -33,9 +33,14 @@ const Home = () => {
|
||||
.sort((a, b) => Number(b.pinned) - Number(a.pinned));
|
||||
|
||||
useEffect(() => {
|
||||
setIsRequesting(true);
|
||||
nextPageTokenRef.current = undefined;
|
||||
memoList.reset();
|
||||
fetchMemos();
|
||||
setTimeout(async () => {
|
||||
memoList.reset();
|
||||
const nextPageToken = await fetchMemos();
|
||||
nextPageTokenRef.current = nextPageToken;
|
||||
setIsRequesting(false);
|
||||
});
|
||||
}, [tagQuery, textQuery]);
|
||||
|
||||
const fetchMemos = async () => {
|
||||
@ -50,14 +55,12 @@ const Home = () => {
|
||||
if (tagQuery) {
|
||||
filters.push(`tag == "${tagQuery}"`);
|
||||
}
|
||||
setIsRequesting(true);
|
||||
const data = await memoStore.fetchMemos({
|
||||
const { nextPageToken } = await memoStore.fetchMemos({
|
||||
pageSize: DEFAULT_LIST_MEMOS_PAGE_SIZE,
|
||||
filter: filters.join(" && "),
|
||||
pageToken: nextPageTokenRef.current,
|
||||
});
|
||||
setIsRequesting(false);
|
||||
nextPageTokenRef.current = data.nextPageToken;
|
||||
return nextPageToken;
|
||||
};
|
||||
|
||||
const handleEditPrevious = useCallback(() => {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Button, Divider, IconButton } from "@mui/joy";
|
||||
import { Button, IconButton } from "@mui/joy";
|
||||
import clsx from "clsx";
|
||||
import { Fragment, useEffect, useRef, useState } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import ActivityCalendar from "@/components/ActivityCalendar";
|
||||
import Empty from "@/components/Empty";
|
||||
import Icon from "@/components/Icon";
|
||||
@ -11,58 +11,38 @@ import MobileHeader from "@/components/MobileHeader";
|
||||
import { TimelineSidebar, TimelineSidebarDrawer } from "@/components/TimelineSidebar";
|
||||
import { memoServiceClient } from "@/grpcweb";
|
||||
import { DAILY_TIMESTAMP, DEFAULT_LIST_MEMOS_PAGE_SIZE } from "@/helpers/consts";
|
||||
import { getNormalizedTimeString, getTimeStampByDate } from "@/helpers/datetime";
|
||||
import { getTimeStampByDate } from "@/helpers/datetime";
|
||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
||||
import useFilterWithUrlParams from "@/hooks/useFilterWithUrlParams";
|
||||
import useResponsiveWidth from "@/hooks/useResponsiveWidth";
|
||||
import i18n from "@/i18n";
|
||||
import { useMemoList, useMemoStore } from "@/store/v1";
|
||||
import { Memo } from "@/types/proto/api/v1/memo_service";
|
||||
import { useTranslate } from "@/utils/i18n";
|
||||
|
||||
interface GroupedByMonthItem {
|
||||
// Format: 2021-1
|
||||
month: string;
|
||||
data: Record<string, number>;
|
||||
memos: Memo[];
|
||||
}
|
||||
|
||||
const groupByMonth = (dateCountMap: Record<string, number>, memos: Memo[]): GroupedByMonthItem[] => {
|
||||
const groupedByMonth: GroupedByMonthItem[] = [];
|
||||
|
||||
Object.entries(dateCountMap).forEach(([date, count]) => {
|
||||
const month = date.split("-").slice(0, 2).join("-");
|
||||
const existingMonth = groupedByMonth.find((group) => group.month === month);
|
||||
if (existingMonth) {
|
||||
existingMonth.data[date] = count;
|
||||
} else {
|
||||
const monthMemos = memos.filter((memo) => getNormalizedTimeString(memo.displayTime).startsWith(month));
|
||||
groupedByMonth.push({ month, data: { [date]: count }, memos: monthMemos });
|
||||
}
|
||||
});
|
||||
|
||||
return groupedByMonth.filter((group) => group.memos.length > 0).sort((a, b) => getTimeStampByDate(b.month) - getTimeStampByDate(a.month));
|
||||
};
|
||||
|
||||
const Timeline = () => {
|
||||
const t = useTranslate();
|
||||
const { md } = useResponsiveWidth();
|
||||
const user = useCurrentUser();
|
||||
const memoStore = useMemoStore();
|
||||
const memoList = useMemoList();
|
||||
const { tag: tagQuery, text: textQuery } = useFilterWithUrlParams();
|
||||
const [activityStats, setActivityStats] = useState<Record<string, number>>({});
|
||||
const [selectedDay, setSelectedDay] = useState<string | undefined>();
|
||||
const [selectedDateString, setSelectedDateString] = useState<string>(new Date().toDateString());
|
||||
const [isRequesting, setIsRequesting] = useState(true);
|
||||
const nextPageTokenRef = useRef<string | undefined>(undefined);
|
||||
const { tag: tagQuery, text: textQuery } = useFilterWithUrlParams();
|
||||
const sortedMemos = memoList.value.sort((a, b) => getTimeStampByDate(b.displayTime) - getTimeStampByDate(a.displayTime));
|
||||
const groupedByMonth = groupByMonth(activityStats, sortedMemos);
|
||||
const sortedMemos = memoList.value.sort((a, b) => getTimeStampByDate(a.displayTime) - getTimeStampByDate(b.displayTime));
|
||||
const monthString = new Date(selectedDateString).getFullYear() + "-" + (new Date(selectedDateString).getMonth() + 1);
|
||||
|
||||
useEffect(() => {
|
||||
setIsRequesting(true);
|
||||
nextPageTokenRef.current = undefined;
|
||||
memoList.reset();
|
||||
fetchMemos();
|
||||
}, [selectedDay, tagQuery, textQuery]);
|
||||
setTimeout(async () => {
|
||||
memoList.reset();
|
||||
const nextPageToken = await fetchMemos();
|
||||
nextPageTokenRef.current = nextPageToken;
|
||||
setIsRequesting(false);
|
||||
});
|
||||
}, [selectedDateString, tagQuery, textQuery]);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
@ -82,7 +62,15 @@ const Timeline = () => {
|
||||
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||
filter: filters.join(" && "),
|
||||
});
|
||||
setActivityStats(stats);
|
||||
|
||||
setActivityStats(
|
||||
Object.fromEntries(
|
||||
Object.entries(stats).filter(([date]) => {
|
||||
const d = new Date(date);
|
||||
return `${d.getFullYear()}-${d.getMonth() + 1}` === monthString;
|
||||
}),
|
||||
),
|
||||
);
|
||||
})();
|
||||
}, [sortedMemos.length]);
|
||||
|
||||
@ -98,20 +86,18 @@ const Timeline = () => {
|
||||
if (tagQuery) {
|
||||
filters.push(`tag == "${tagQuery}"`);
|
||||
}
|
||||
if (selectedDay) {
|
||||
const selectedDateStamp = getTimeStampByDate(selectedDay) + new Date().getTimezoneOffset() * 60 * 1000;
|
||||
if (selectedDateString) {
|
||||
const selectedDateStamp = getTimeStampByDate(selectedDateString);
|
||||
filters.push(
|
||||
...[`display_time_after == ${selectedDateStamp / 1000}`, `display_time_before == ${(selectedDateStamp + DAILY_TIMESTAMP) / 1000}`],
|
||||
);
|
||||
}
|
||||
setIsRequesting(true);
|
||||
const data = await memoStore.fetchMemos({
|
||||
const { nextPageToken } = await memoStore.fetchMemos({
|
||||
pageSize: DEFAULT_LIST_MEMOS_PAGE_SIZE,
|
||||
filter: filters.join(" && "),
|
||||
pageToken: nextPageTokenRef.current,
|
||||
});
|
||||
setIsRequesting(false);
|
||||
nextPageTokenRef.current = data.nextPageToken;
|
||||
return nextPageToken;
|
||||
};
|
||||
|
||||
const handleNewMemo = () => {
|
||||
@ -132,7 +118,7 @@ const Timeline = () => {
|
||||
<div>
|
||||
<div
|
||||
className="py-1 flex flex-row justify-start items-center select-none opacity-80"
|
||||
onClick={() => setSelectedDay(undefined)}
|
||||
onClick={() => setSelectedDateString(new Date().toDateString())}
|
||||
>
|
||||
<Icon.GanttChartSquare className="w-6 h-auto mr-1 opacity-80" />
|
||||
<span className="text-lg">{t("timeline.title")}</span>
|
||||
@ -145,43 +131,44 @@ const Timeline = () => {
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full h-auto flex flex-col justify-start items-start">
|
||||
<MemoFilter className="px-2 my-4" />
|
||||
<MemoFilter className="p-2 my-2 rounded-lg dark:bg-zinc-900" />
|
||||
|
||||
{groupedByMonth.map((group, index) => (
|
||||
<Fragment key={group.month}>
|
||||
<div className={clsx("flex flex-col justify-start items-start w-full mt-2 last:mb-4")}>
|
||||
<div className={clsx("flex shrink-0 flex-row w-full pl-1 mt-2 mb-2")}>
|
||||
<div className={clsx("w-full flex flex-col")}>
|
||||
<span className="font-medium text-3xl leading-tight mb-1">
|
||||
{new Date(group.month).toLocaleString(i18n.language, { month: "short", timeZone: "UTC" })}
|
||||
</span>
|
||||
<span className="opacity-60">{new Date(group.month).getUTCFullYear()}</span>
|
||||
</div>
|
||||
<ActivityCalendar month={group.month} data={group.data} onClick={(date) => setSelectedDay(date)} />
|
||||
</div>
|
||||
|
||||
<div className={clsx("w-full flex flex-col justify-start items-start")}>
|
||||
{group.memos.map((memo, index) => (
|
||||
<div
|
||||
key={`${memo.name}-${memo.displayTime}`}
|
||||
className={clsx("relative w-full flex flex-col justify-start items-start pl-4 sm:pl-10 pt-0")}
|
||||
>
|
||||
<MemoView className="!border max-w-full !border-gray-100 dark:!border-zinc-700" memo={memo} />
|
||||
<div className="absolute -left-2 sm:left-2 top-4 h-full">
|
||||
{index !== group.memos.length - 1 && (
|
||||
<div className="absolute top-2 left-[7px] h-full w-0.5 bg-gray-200 dark:bg-gray-700 block"></div>
|
||||
)}
|
||||
<div className="border-4 rounded-full border-white relative dark:border-zinc-800">
|
||||
<Icon.Circle className="w-2 h-auto bg-gray-200 text-gray-200 dark:bg-gray-700 dark:text-gray-700 rounded-full" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className={clsx("flex flex-col justify-start items-start w-full mt-2 last:mb-4")}>
|
||||
<div className={clsx("flex shrink-0 flex-row w-full pl-1 mt-2 mb-2")}>
|
||||
<div className={clsx("w-full flex flex-col")}>
|
||||
<span className="font-medium text-3xl sm:text-4xl">
|
||||
{new Date(selectedDateString).toLocaleDateString(i18n.language, { month: "short", day: "numeric" })}
|
||||
</span>
|
||||
<span className="opacity-60 text-lg">{new Date(monthString).getFullYear()}</span>
|
||||
</div>
|
||||
{index !== groupedByMonth.length - 1 && <Divider className="w-full !my-4 md:!mb-8 !bg-gray-100 dark:!bg-zinc-700" />}
|
||||
</Fragment>
|
||||
))}
|
||||
<ActivityCalendar
|
||||
month={monthString}
|
||||
selectedDate={selectedDateString}
|
||||
data={activityStats}
|
||||
onClick={(date) => setSelectedDateString(date)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={clsx("w-full flex flex-col justify-start items-start")}>
|
||||
{sortedMemos.map((memo, index) => (
|
||||
<div
|
||||
key={`${memo.name}-${memo.displayTime}`}
|
||||
className={clsx("relative w-full flex flex-col justify-start items-start pl-4 sm:pl-10 pt-0")}
|
||||
>
|
||||
<MemoView className="!border max-w-full !border-gray-100 dark:!border-zinc-700" memo={memo} />
|
||||
<div className="absolute -left-2 sm:left-2 top-4 h-full">
|
||||
{index !== sortedMemos.length - 1 && (
|
||||
<div className="absolute top-2 left-[7px] h-full w-0.5 bg-gray-200 dark:bg-gray-700 block"></div>
|
||||
)}
|
||||
<div className="border-4 rounded-full border-white relative dark:border-zinc-800">
|
||||
<Icon.Circle className="w-2 h-auto bg-gray-200 text-gray-200 dark:bg-gray-700 dark:text-gray-700 rounded-full" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isRequesting ? (
|
||||
<div className="flex flex-row justify-center items-center w-full my-4 text-gray-400">
|
||||
<Icon.Loader className="w-4 h-auto animate-spin mr-1" />
|
||||
|
@ -59,9 +59,14 @@ const UserProfile = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsRequesting(true);
|
||||
nextPageTokenRef.current = undefined;
|
||||
memoList.reset();
|
||||
fetchMemos();
|
||||
setTimeout(async () => {
|
||||
memoList.reset();
|
||||
const nextPageToken = await fetchMemos();
|
||||
nextPageTokenRef.current = nextPageToken;
|
||||
setIsRequesting(false);
|
||||
});
|
||||
}, [user, tagQuery, textQuery]);
|
||||
|
||||
const fetchMemos = async () => {
|
||||
@ -80,14 +85,12 @@ const UserProfile = () => {
|
||||
if (tagQuery) {
|
||||
filters.push(`tag == "${tagQuery}"`);
|
||||
}
|
||||
setIsRequesting(true);
|
||||
const data = await memoStore.fetchMemos({
|
||||
const { nextPageToken } = await memoStore.fetchMemos({
|
||||
pageSize: DEFAULT_LIST_MEMOS_PAGE_SIZE,
|
||||
filter: filters.join(" && "),
|
||||
pageToken: nextPageTokenRef.current,
|
||||
});
|
||||
setIsRequesting(false);
|
||||
nextPageTokenRef.current = data.nextPageToken;
|
||||
return nextPageToken;
|
||||
};
|
||||
|
||||
const handleCopyProfileLink = () => {
|
||||
|
@ -28,14 +28,5 @@ export const useTagStore = create(
|
||||
const { tagAmounts } = await memoServiceClient.listMemoTags({ parent: "memos/-" });
|
||||
set({ tagAmounts });
|
||||
},
|
||||
deleteTag: async (tagName: string) => {
|
||||
await memoServiceClient.deleteMemoTag({
|
||||
parent: "memos/-",
|
||||
tag: tagName,
|
||||
});
|
||||
const { tagAmounts } = get();
|
||||
delete tagAmounts[tagName];
|
||||
set({ tagAmounts });
|
||||
},
|
||||
})),
|
||||
);
|
||||
|
Loading…
x
Reference in New Issue
Block a user