mirror of
https://github.com/usememos/memos.git
synced 2025-06-05 22:09:59 +02:00
chore: update navigator
This commit is contained in:
@@ -1,12 +1,10 @@
|
|||||||
import dayjs from "dayjs";
|
|
||||||
import { isEqual } from "lodash-es";
|
import { isEqual } from "lodash-es";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
import { cn } from "@/utils";
|
import { cn } from "@/utils";
|
||||||
|
|
||||||
// Helper function to convert Date to local datetime string.
|
// Helper function to convert Date to local datetime string.
|
||||||
const toLocalDateTimeString = (date: Date | undefined): string => {
|
const toLocalDateTimeString = (date: Date | undefined): string => {
|
||||||
if (!date) return "";
|
return date?.toLocaleString() || "";
|
||||||
return dayjs(date).format("YYYY-MM-DDTHH:mm:ss");
|
|
||||||
};
|
};
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -20,7 +18,7 @@ const DateTimeInput: React.FC<Props> = ({ value, originalValue, onChange }) => {
|
|||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
className={cn(
|
className={cn(
|
||||||
"w-auto px-1 bg-transparent rounded text-xs transition-all",
|
"px-1 bg-transparent rounded text-xs transition-all",
|
||||||
"border-transparent outline-none focus:border-gray-300 dark:focus:border-zinc-700",
|
"border-transparent outline-none focus:border-gray-300 dark:focus:border-zinc-700",
|
||||||
!isEqual(value, originalValue) && "border-gray-300 dark:border-zinc-700",
|
!isEqual(value, originalValue) && "border-gray-300 dark:border-zinc-700",
|
||||||
"border",
|
"border",
|
||||||
|
@@ -1,50 +1,25 @@
|
|||||||
import { last } from "lodash-es";
|
import { last } from "lodash-es";
|
||||||
import { Globe2Icon, HomeIcon } from "lucide-react";
|
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { matchPath, NavLink, useLocation } from "react-router-dom";
|
import { matchPath, useLocation } from "react-router-dom";
|
||||||
import useDebounce from "react-use/lib/useDebounce";
|
import useDebounce from "react-use/lib/useDebounce";
|
||||||
import SearchBar from "@/components/SearchBar";
|
import SearchBar from "@/components/SearchBar";
|
||||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
import useCurrentUser from "@/hooks/useCurrentUser";
|
||||||
import { Routes } from "@/router";
|
import { Routes } from "@/router";
|
||||||
import { memoStore, userStore } from "@/store/v2";
|
import { memoStore, userStore } from "@/store/v2";
|
||||||
import { cn } from "@/utils";
|
import { cn } from "@/utils";
|
||||||
import { useTranslate } from "@/utils/i18n";
|
|
||||||
import MemoFilters from "../MemoFilters";
|
import MemoFilters from "../MemoFilters";
|
||||||
import StatisticsView from "../StatisticsView";
|
import StatisticsView from "../StatisticsView";
|
||||||
import ShortcutsSection from "./ShortcutsSection";
|
import ShortcutsSection from "./ShortcutsSection";
|
||||||
import TagsSection from "./TagsSection";
|
import TagsSection from "./TagsSection";
|
||||||
|
|
||||||
interface NavLinkItem {
|
|
||||||
id: string;
|
|
||||||
path: string;
|
|
||||||
title: string;
|
|
||||||
icon: React.ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const HomeSidebar = observer((props: Props) => {
|
const HomeSidebar = observer((props: Props) => {
|
||||||
const t = useTranslate();
|
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const currentUser = useCurrentUser();
|
const currentUser = useCurrentUser();
|
||||||
|
|
||||||
const homeNavLink: NavLinkItem = {
|
|
||||||
id: "header-home",
|
|
||||||
path: Routes.ROOT,
|
|
||||||
title: t("common.home"),
|
|
||||||
icon: <HomeIcon className="w-4 h-auto opacity-70 shrink-0" />,
|
|
||||||
};
|
|
||||||
const exploreNavLink: NavLinkItem = {
|
|
||||||
id: "header-explore",
|
|
||||||
path: Routes.EXPLORE,
|
|
||||||
title: t("common.explore"),
|
|
||||||
icon: <Globe2Icon className="w-4 h-auto opacity-70 shrink-0" />,
|
|
||||||
};
|
|
||||||
|
|
||||||
const navLinks: NavLinkItem[] = currentUser ? [homeNavLink, exploreNavLink] : [exploreNavLink];
|
|
||||||
|
|
||||||
useDebounce(
|
useDebounce(
|
||||||
async () => {
|
async () => {
|
||||||
let parent: string | undefined = undefined;
|
let parent: string | undefined = undefined;
|
||||||
@@ -65,30 +40,7 @@ const HomeSidebar = observer((props: Props) => {
|
|||||||
return (
|
return (
|
||||||
<aside className={cn("relative w-full h-full overflow-auto flex flex-col justify-start items-start", props.className)}>
|
<aside className={cn("relative w-full h-full overflow-auto flex flex-col justify-start items-start", props.className)}>
|
||||||
<SearchBar />
|
<SearchBar />
|
||||||
<div className="mt-2 w-full space-y-1">
|
<div className="mt-1 px-1 w-full">
|
||||||
{navLinks.map((navLink) => (
|
|
||||||
<NavLink
|
|
||||||
key={navLink.id}
|
|
||||||
className={({ isActive }) =>
|
|
||||||
cn(
|
|
||||||
"w-full px-2 rounded-xl border flex flex-row items-center justify-between text-sm text-zinc-600 dark:text-gray-400 hover:bg-white hover:border-gray-200 dark:hover:border-zinc-700 dark:hover:bg-zinc-800",
|
|
||||||
isActive ? "bg-white drop-shadow-sm dark:bg-zinc-800 border-gray-200 dark:border-zinc-700" : "border-transparent",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
to={navLink.path}
|
|
||||||
viewTransition
|
|
||||||
>
|
|
||||||
<div className="flex flex-row items-center">
|
|
||||||
{navLink.icon}
|
|
||||||
<span className="ml-2 truncate leading-8">{navLink.title}</span>
|
|
||||||
</div>
|
|
||||||
{navLink.path === Routes.ROOT && currentUser && userStore.state.currentUserStats && (
|
|
||||||
<span className="font-mono text-xs opacity-80">{userStore.state.currentUserStats.totalMemoCount}</span>
|
|
||||||
)}
|
|
||||||
</NavLink>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<div className="px-2 w-full">
|
|
||||||
<StatisticsView />
|
<StatisticsView />
|
||||||
<MemoFilters />
|
<MemoFilters />
|
||||||
{currentUser && <ShortcutsSection />}
|
{currentUser && <ShortcutsSection />}
|
||||||
|
@@ -548,7 +548,7 @@ const MemoEditor = observer((props: Props) => {
|
|||||||
|
|
||||||
{/* Show memo metadata if memoName is provided */}
|
{/* Show memo metadata if memoName is provided */}
|
||||||
{memoName && (
|
{memoName && (
|
||||||
<div className="w-full mb-4 text-xs leading-5 px-4 opacity-60 font-mono text-gray-500 dark:text-zinc-500">
|
<div className="w-full -mt-1 mb-4 text-xs leading-5 px-4 opacity-60 font-mono text-gray-500 dark:text-zinc-500">
|
||||||
<div className="grid grid-cols-[auto_1fr] gap-x-4 gap-y-0.5 items-center">
|
<div className="grid grid-cols-[auto_1fr] gap-x-4 gap-y-0.5 items-center">
|
||||||
{!isEqual(createTime, updateTime) && (
|
{!isEqual(createTime, updateTime) && (
|
||||||
<>
|
<>
|
||||||
|
@@ -1,12 +1,11 @@
|
|||||||
import { Tooltip } from "@mui/joy";
|
import { Tooltip } from "@mui/joy";
|
||||||
import { BellIcon, PaperclipIcon, SettingsIcon, UserCircleIcon } from "lucide-react";
|
import { EarthIcon, LibraryIcon, PaperclipIcon, UserCircleIcon } from "lucide-react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { NavLink } from "react-router-dom";
|
import { NavLink } from "react-router-dom";
|
||||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
import useCurrentUser from "@/hooks/useCurrentUser";
|
||||||
import { Routes } from "@/router";
|
import { Routes } from "@/router";
|
||||||
import { userStore } from "@/store/v2";
|
import { userStore } from "@/store/v2";
|
||||||
import { Inbox_Status } from "@/types/proto/api/v1/inbox_service";
|
|
||||||
import { cn } from "@/utils";
|
import { cn } from "@/utils";
|
||||||
import { useTranslate } from "@/utils/i18n";
|
import { useTranslate } from "@/utils/i18n";
|
||||||
import BrandBanner from "./BrandBanner";
|
import BrandBanner from "./BrandBanner";
|
||||||
@@ -28,7 +27,6 @@ const Navigation = observer((props: Props) => {
|
|||||||
const { collapsed, className } = props;
|
const { collapsed, className } = props;
|
||||||
const t = useTranslate();
|
const t = useTranslate();
|
||||||
const currentUser = useCurrentUser();
|
const currentUser = useCurrentUser();
|
||||||
const hasUnreadInbox = userStore.state.inboxes.some((inbox) => inbox.status === Inbox_Status.UNREAD);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!currentUser) {
|
if (!currentUser) {
|
||||||
@@ -38,31 +36,24 @@ const Navigation = observer((props: Props) => {
|
|||||||
userStore.fetchInboxes();
|
userStore.fetchInboxes();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const homeNavLink: NavLinkItem = {
|
||||||
|
id: "header-memos",
|
||||||
|
path: Routes.ROOT,
|
||||||
|
title: t("common.memos"),
|
||||||
|
icon: <LibraryIcon className="w-6 h-auto opacity-70 shrink-0" />,
|
||||||
|
};
|
||||||
|
const exploreNavLink: NavLinkItem = {
|
||||||
|
id: "header-explore",
|
||||||
|
path: Routes.EXPLORE,
|
||||||
|
title: t("common.explore"),
|
||||||
|
icon: <EarthIcon className="w-6 h-auto opacity-70 shrink-0" />,
|
||||||
|
};
|
||||||
const resourcesNavLink: NavLinkItem = {
|
const resourcesNavLink: NavLinkItem = {
|
||||||
id: "header-resources",
|
id: "header-resources",
|
||||||
path: Routes.RESOURCES,
|
path: Routes.RESOURCES,
|
||||||
title: t("common.resources"),
|
title: t("common.resources"),
|
||||||
icon: <PaperclipIcon className="w-6 h-auto opacity-70 shrink-0" />,
|
icon: <PaperclipIcon className="w-6 h-auto opacity-70 shrink-0" />,
|
||||||
};
|
};
|
||||||
const inboxNavLink: NavLinkItem = {
|
|
||||||
id: "header-inbox",
|
|
||||||
path: Routes.INBOX,
|
|
||||||
title: t("common.inbox"),
|
|
||||||
icon: (
|
|
||||||
<>
|
|
||||||
<div className="relative">
|
|
||||||
<BellIcon className="w-6 h-auto opacity-70 shrink-0" />
|
|
||||||
{hasUnreadInbox && <div className="absolute top-0 left-5 w-2 h-2 rounded-full bg-blue-500"></div>}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
};
|
|
||||||
const settingNavLink: NavLinkItem = {
|
|
||||||
id: "header-setting",
|
|
||||||
path: Routes.SETTING,
|
|
||||||
title: t("common.settings"),
|
|
||||||
icon: <SettingsIcon className="w-6 h-auto opacity-70 shrink-0" />,
|
|
||||||
};
|
|
||||||
const signInNavLink: NavLinkItem = {
|
const signInNavLink: NavLinkItem = {
|
||||||
id: "header-auth",
|
id: "header-auth",
|
||||||
path: Routes.AUTH,
|
path: Routes.AUTH,
|
||||||
@@ -70,7 +61,7 @@ const Navigation = observer((props: Props) => {
|
|||||||
icon: <UserCircleIcon className="w-6 h-auto opacity-70 shrink-0" />,
|
icon: <UserCircleIcon className="w-6 h-auto opacity-70 shrink-0" />,
|
||||||
};
|
};
|
||||||
|
|
||||||
const navLinks: NavLinkItem[] = currentUser ? [resourcesNavLink, inboxNavLink, settingNavLink] : [signInNavLink];
|
const navLinks: NavLinkItem[] = currentUser ? [homeNavLink, exploreNavLink, resourcesNavLink] : [exploreNavLink, signInNavLink];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header
|
<header
|
||||||
@@ -80,7 +71,7 @@ const Navigation = observer((props: Props) => {
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="w-full px-1 py-1 flex flex-col justify-start items-start space-y-2 overflow-auto hide-scrollbar shrink">
|
<div className="w-full px-1 py-1 flex flex-col justify-start items-start space-y-2 overflow-auto hide-scrollbar shrink">
|
||||||
<NavLink className="mb-2" to={currentUser ? Routes.ROOT : Routes.EXPLORE}>
|
<NavLink className="mb-2 cursor-default" to={currentUser ? Routes.ROOT : Routes.EXPLORE}>
|
||||||
<BrandBanner collapsed={collapsed} />
|
<BrandBanner collapsed={collapsed} />
|
||||||
</NavLink>
|
</NavLink>
|
||||||
{navLinks.map((navLink) => (
|
{navLinks.map((navLink) => (
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { CalendarIcon, ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
|
import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
|
||||||
import i18n from "@/i18n";
|
import i18n from "@/i18n";
|
||||||
import type { MonthNavigatorProps } from "@/types/statistics";
|
import type { MonthNavigatorProps } from "@/types/statistics";
|
||||||
|
|
||||||
@@ -16,15 +16,14 @@ export const MonthNavigator = ({ visibleMonth, onMonthChange }: MonthNavigatorPr
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full mb-1 flex flex-row justify-between items-center gap-1">
|
<div className="w-full mb-1 flex flex-row justify-between items-center gap-1">
|
||||||
<div className="relative text-sm inline-flex flex-row items-center w-auto gap-2 dark:text-gray-400">
|
<span className="relative text-sm dark:text-gray-400">
|
||||||
<CalendarIcon className="w-4 h-auto opacity-70 ml-px" />
|
|
||||||
{currentMonth.toLocaleString(i18n.language, { year: "numeric", month: "long" })}
|
{currentMonth.toLocaleString(i18n.language, { year: "numeric", month: "long" })}
|
||||||
</div>
|
</span>
|
||||||
<div className="flex justify-end items-center shrink-0 gap-1">
|
<div className="flex justify-end items-center shrink-0 gap-1">
|
||||||
<button className="p-1 cursor-pointer hover:opacity-80 transition-opacity" onClick={handlePrevMonth} aria-label="Previous month">
|
<button className="cursor-pointer hover:opacity-80 transition-opacity" onClick={handlePrevMonth} aria-label="Previous month">
|
||||||
<ChevronLeftIcon className="w-5 h-auto shrink-0 opacity-40" />
|
<ChevronLeftIcon className="w-5 h-auto shrink-0 opacity-40" />
|
||||||
</button>
|
</button>
|
||||||
<button className="p-1 cursor-pointer hover:opacity-80 transition-opacity" onClick={handleNextMonth} aria-label="Next month">
|
<button className="cursor-pointer hover:opacity-80 transition-opacity" onClick={handleNextMonth} aria-label="Next month">
|
||||||
<ChevronRightIcon className="w-5 h-auto shrink-0 opacity-40" />
|
<ChevronRightIcon className="w-5 h-auto shrink-0 opacity-40" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -14,9 +14,9 @@ export const StatCard = ({ icon, label, count, onClick, tooltip, className }: St
|
|||||||
>
|
>
|
||||||
<div className="w-auto flex justify-start items-center mr-1">
|
<div className="w-auto flex justify-start items-center mr-1">
|
||||||
{icon}
|
{icon}
|
||||||
<span className="block text-sm">{label}</span>
|
<span className="block text-xs opacity-80">{label}</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-sm truncate">{count}</span>
|
<span className="text-xs truncate opacity-80">{count}</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@@ -49,7 +49,7 @@ const StatisticsView = observer(() => {
|
|||||||
<div className="pt-1 w-full flex flex-row justify-start items-center gap-1 flex-wrap">
|
<div className="pt-1 w-full flex flex-row justify-start items-center gap-1 flex-wrap">
|
||||||
{isRootPath && hasPinnedMemos && (
|
{isRootPath && hasPinnedMemos && (
|
||||||
<StatCard
|
<StatCard
|
||||||
icon={<BookmarkIcon className="w-4 h-auto mr-1" />}
|
icon={<BookmarkIcon className="w-3 h-auto mr-1 opacity-70" />}
|
||||||
label={t("common.pinned")}
|
label={t("common.pinned")}
|
||||||
count={userStore.state.currentUserStats!.pinnedMemos.length}
|
count={userStore.state.currentUserStats!.pinnedMemos.length}
|
||||||
onClick={() => handleFilterClick("pinned")}
|
onClick={() => handleFilterClick("pinned")}
|
||||||
@@ -57,7 +57,7 @@ const StatisticsView = observer(() => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<StatCard
|
<StatCard
|
||||||
icon={<LinkIcon className="w-4 h-auto mr-1" />}
|
icon={<LinkIcon className="w-3 h-auto mr-1 opacity-70" />}
|
||||||
label={t("memo.links")}
|
label={t("memo.links")}
|
||||||
count={memoTypeStats.linkCount}
|
count={memoTypeStats.linkCount}
|
||||||
onClick={() => handleFilterClick("property.hasLink")}
|
onClick={() => handleFilterClick("property.hasLink")}
|
||||||
@@ -65,7 +65,11 @@ const StatisticsView = observer(() => {
|
|||||||
|
|
||||||
<StatCard
|
<StatCard
|
||||||
icon={
|
icon={
|
||||||
memoTypeStats.undoCount > 0 ? <ListTodoIcon className="w-4 h-auto mr-1" /> : <CheckCircleIcon className="w-4 h-auto mr-1" />
|
memoTypeStats.undoCount > 0 ? (
|
||||||
|
<ListTodoIcon className="w-3 h-auto mr-1 opacity-70" />
|
||||||
|
) : (
|
||||||
|
<CheckCircleIcon className="w-3 h-auto mr-1 opacity-70" />
|
||||||
|
)
|
||||||
}
|
}
|
||||||
label={t("memo.to-do")}
|
label={t("memo.to-do")}
|
||||||
count={
|
count={
|
||||||
@@ -84,7 +88,7 @@ const StatisticsView = observer(() => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<StatCard
|
<StatCard
|
||||||
icon={<Code2Icon className="w-4 h-auto mr-1" />}
|
icon={<Code2Icon className="w-3 h-auto mr-1 opacity-70" />}
|
||||||
label={t("memo.code")}
|
label={t("memo.code")}
|
||||||
count={memoTypeStats.codeCount}
|
count={memoTypeStats.codeCount}
|
||||||
onClick={() => handleFilterClick("property.hasCode")}
|
onClick={() => handleFilterClick("property.hasCode")}
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { Dropdown, Menu, MenuButton, MenuItem } from "@mui/joy";
|
import { Dropdown, Menu, MenuButton, MenuItem } from "@mui/joy";
|
||||||
import { ArchiveIcon, LogOutIcon, User2Icon, SquareUserIcon } from "lucide-react";
|
import { ArchiveIcon, LogOutIcon, User2Icon, SquareUserIcon, SettingsIcon, BellIcon } from "lucide-react";
|
||||||
import { authServiceClient } from "@/grpcweb";
|
import { authServiceClient } from "@/grpcweb";
|
||||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
import useCurrentUser from "@/hooks/useCurrentUser";
|
||||||
import useNavigateTo from "@/hooks/useNavigateTo";
|
import useNavigateTo from "@/hooks/useNavigateTo";
|
||||||
@@ -45,7 +45,7 @@ const UserBanner = (props: Props) => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</MenuButton>
|
</MenuButton>
|
||||||
<Menu placement="bottom-start" style={{ zIndex: "9999" }}>
|
<Menu size="sm" placement="bottom-start" style={{ zIndex: "9999" }}>
|
||||||
<MenuItem onClick={() => navigateTo(`/u/${encodeURIComponent(currentUser.username)}`)}>
|
<MenuItem onClick={() => navigateTo(`/u/${encodeURIComponent(currentUser.username)}`)}>
|
||||||
<SquareUserIcon className="w-4 h-auto opacity-60" />
|
<SquareUserIcon className="w-4 h-auto opacity-60" />
|
||||||
<span className="truncate">{t("common.profile")}</span>
|
<span className="truncate">{t("common.profile")}</span>
|
||||||
@@ -54,6 +54,14 @@ const UserBanner = (props: Props) => {
|
|||||||
<ArchiveIcon className="w-4 h-auto opacity-60" />
|
<ArchiveIcon className="w-4 h-auto opacity-60" />
|
||||||
<span className="truncate">{t("common.archived")}</span>
|
<span className="truncate">{t("common.archived")}</span>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
<MenuItem onClick={() => navigateTo(Routes.INBOX)}>
|
||||||
|
<BellIcon className="w-4 h-auto opacity-60" />
|
||||||
|
<span className="truncate">{t("common.inbox")}</span>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem onClick={() => navigateTo(Routes.SETTING)}>
|
||||||
|
<SettingsIcon className="w-4 h-auto opacity-60" />
|
||||||
|
<span className="truncate">{t("common.settings")}</span>
|
||||||
|
</MenuItem>
|
||||||
<MenuItem onClick={handleSignOut}>
|
<MenuItem onClick={handleSignOut}>
|
||||||
<LogOutIcon className="w-4 h-auto opacity-60" />
|
<LogOutIcon className="w-4 h-auto opacity-60" />
|
||||||
<span className="truncate">{t("common.sign-out")}</span>
|
<span className="truncate">{t("common.sign-out")}</span>
|
||||||
|
@@ -1,50 +1,20 @@
|
|||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { useMemo } from "react";
|
|
||||||
import MemoView from "@/components/MemoView";
|
import MemoView from "@/components/MemoView";
|
||||||
|
import MobileHeader from "@/components/MobileHeader";
|
||||||
import PagedMemoList from "@/components/PagedMemoList";
|
import PagedMemoList from "@/components/PagedMemoList";
|
||||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
import useResponsiveWidth from "@/hooks/useResponsiveWidth";
|
||||||
import { viewStore } from "@/store/v2";
|
import { viewStore } from "@/store/v2";
|
||||||
import memoFilterStore from "@/store/v2/memoFilter";
|
|
||||||
import { Direction, State } from "@/types/proto/api/v1/common";
|
import { Direction, State } from "@/types/proto/api/v1/common";
|
||||||
import { Memo } from "@/types/proto/api/v1/memo_service";
|
import { Memo } from "@/types/proto/api/v1/memo_service";
|
||||||
|
|
||||||
const Explore = observer(() => {
|
const Explore = observer(() => {
|
||||||
const user = useCurrentUser();
|
const { md } = useResponsiveWidth();
|
||||||
|
|
||||||
const memoListFilter = useMemo(() => {
|
|
||||||
const conditions = [];
|
|
||||||
const contentSearch: string[] = [];
|
|
||||||
const tagSearch: string[] = [];
|
|
||||||
for (const filter of memoFilterStore.filters) {
|
|
||||||
if (filter.factor === "contentSearch") {
|
|
||||||
contentSearch.push(`"${filter.value}"`);
|
|
||||||
} else if (filter.factor === "tagSearch") {
|
|
||||||
tagSearch.push(`"${filter.value}"`);
|
|
||||||
} else if (filter.factor === "property.hasLink") {
|
|
||||||
conditions.push(`has_link == true`);
|
|
||||||
} else if (filter.factor === "property.hasTaskList") {
|
|
||||||
conditions.push(`has_task_list == true`);
|
|
||||||
} else if (filter.factor === "property.hasCode") {
|
|
||||||
conditions.push(`has_code == true`);
|
|
||||||
} else if (filter.factor === "displayTime") {
|
|
||||||
const filterDate = new Date(filter.value);
|
|
||||||
const filterUtcTimestamp = filterDate.getTime() + filterDate.getTimezoneOffset() * 60 * 1000;
|
|
||||||
const timestampAfter = filterUtcTimestamp / 1000;
|
|
||||||
conditions.push(`display_time_after == ${timestampAfter}`);
|
|
||||||
conditions.push(`display_time_before == ${timestampAfter + 60 * 60 * 24}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (contentSearch.length > 0) {
|
|
||||||
conditions.push(`content_search == [${contentSearch.join(", ")}]`);
|
|
||||||
}
|
|
||||||
if (tagSearch.length > 0) {
|
|
||||||
conditions.push(`tag_search == [${tagSearch.join(", ")}]`);
|
|
||||||
}
|
|
||||||
return conditions.join(" && ");
|
|
||||||
}, [user, memoFilterStore.filters, viewStore.state.orderByTimeAsc]);
|
|
||||||
|
|
||||||
return (
|
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">
|
||||||
|
{!md && <MobileHeader />}
|
||||||
|
<div className="w-full px-4 sm:px-6">
|
||||||
<PagedMemoList
|
<PagedMemoList
|
||||||
renderer={(memo: Memo) => <MemoView key={`${memo.name}-${memo.updateTime}`} memo={memo} showCreator showVisibility compact />}
|
renderer={(memo: Memo) => <MemoView key={`${memo.name}-${memo.updateTime}`} memo={memo} showCreator showVisibility compact />}
|
||||||
listSort={(memos: Memo[]) =>
|
listSort={(memos: Memo[]) =>
|
||||||
@@ -57,8 +27,9 @@ const Explore = observer(() => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
direction={viewStore.state.orderByTimeAsc ? Direction.ASC : Direction.DESC}
|
direction={viewStore.state.orderByTimeAsc ? Direction.ASC : Direction.DESC}
|
||||||
oldFilter={memoListFilter}
|
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -84,14 +84,6 @@ const router = createBrowserRouter([
|
|||||||
path: "",
|
path: "",
|
||||||
element: <Home />,
|
element: <Home />,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: Routes.EXPLORE,
|
|
||||||
element: (
|
|
||||||
<Suspense fallback={<Loading />}>
|
|
||||||
<Explore />
|
|
||||||
</Suspense>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: Routes.ARCHIVED,
|
path: Routes.ARCHIVED,
|
||||||
element: (
|
element: (
|
||||||
@@ -110,6 +102,14 @@ const router = createBrowserRouter([
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: Routes.EXPLORE,
|
||||||
|
element: (
|
||||||
|
<Suspense fallback={<Loading />}>
|
||||||
|
<Explore />
|
||||||
|
</Suspense>
|
||||||
|
),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: Routes.RESOURCES,
|
path: Routes.RESOURCES,
|
||||||
element: (
|
element: (
|
||||||
|
Reference in New Issue
Block a user