mirror of
https://github.com/usememos/memos.git
synced 2025-03-18 03:30:07 +01:00
feat: implement memo detail sidebar
This commit is contained in:
parent
05c6edfe2f
commit
291b815653
@ -26,7 +26,7 @@ const ExploreSidebarDrawer = () => {
|
||||
<Icon.Search className="w-5 h-auto dark:text-gray-400" />
|
||||
</IconButton>
|
||||
<Drawer anchor="right" size="sm" open={open} onClose={toggleDrawer(false)}>
|
||||
<div className="w-full h-full px-5 bg-zinc-100 dark:bg-zinc-900">
|
||||
<div className="w-full h-full px-4 bg-zinc-100 dark:bg-zinc-900">
|
||||
<ExploreSidebar className="py-4" />
|
||||
</div>
|
||||
</Drawer>
|
||||
|
@ -26,7 +26,7 @@ const HomeSidebarDrawer = () => {
|
||||
<Icon.Search className="w-5 h-auto dark:text-gray-400" />
|
||||
</IconButton>
|
||||
<Drawer anchor="right" size="sm" open={open} onClose={toggleDrawer(false)}>
|
||||
<div className="w-full h-full px-5 bg-zinc-100 dark:bg-zinc-900">
|
||||
<div className="w-full h-full px-4 bg-zinc-100 dark:bg-zinc-900">
|
||||
<HomeSidebar className="py-4" />
|
||||
</div>
|
||||
</Drawer>
|
||||
|
48
web/src/components/MemoDetailSidebar/MemoDetailSidebar.tsx
Normal file
48
web/src/components/MemoDetailSidebar/MemoDetailSidebar.tsx
Normal file
@ -0,0 +1,48 @@
|
||||
import clsx from "clsx";
|
||||
import { Memo } from "@/types/proto/api/v1/memo_service";
|
||||
import { useTranslate } from "@/utils/i18n";
|
||||
import Icon from "../Icon";
|
||||
|
||||
interface Props {
|
||||
memo: Memo;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const MemoDetailSidebar = ({ memo, className }: Props) => {
|
||||
const t = useTranslate();
|
||||
|
||||
if (!memo.property) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<aside
|
||||
className={clsx(
|
||||
"relative w-full h-auto max-h-screen overflow-auto hide-scrollbar flex flex-col justify-start items-start",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<div className="flex flex-col justify-start items-start w-full mt-1 px-1 h-auto shrink-0 flex-nowrap hide-scrollbar">
|
||||
<div className="flex flex-row justify-start items-center w-full gap-1 mb-1 text-sm leading-6 text-gray-400 select-none">
|
||||
<span>{t("common.tags")}</span>
|
||||
{memo.property.tags.length > 0 && <span className="shrink-0">({memo.property.tags.length})</span>}
|
||||
</div>
|
||||
<div className="w-full flex flex-row justify-start items-center relative flex-wrap gap-x-2 gap-y-1">
|
||||
{memo.property.tags.map((tag) => (
|
||||
<div
|
||||
key={tag}
|
||||
className="shrink-0 w-auto max-w-full text-sm rounded-md leading-6 flex flex-row justify-start items-center select-none hover:opacity-80 text-gray-600 dark:text-gray-400 dark:border-zinc-800"
|
||||
>
|
||||
<Icon.Hash className="group-hover:hidden w-4 h-auto shrink-0 opacity-40" />
|
||||
<div className={clsx("inline-flex flex-nowrap ml-0.5 gap-0.5 cursor-pointer max-w-[calc(100%-16px)]")}>
|
||||
<span className="truncate dark:opacity-80">{tag}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
};
|
||||
|
||||
export default MemoDetailSidebar;
|
@ -0,0 +1,41 @@
|
||||
import { Drawer, IconButton } from "@mui/joy";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { Memo } from "@/types/proto/api/v1/memo_service";
|
||||
import Icon from "../Icon";
|
||||
import MemoDetailSidebar from "./MemoDetailSidebar";
|
||||
|
||||
interface Props {
|
||||
memo: Memo;
|
||||
}
|
||||
|
||||
const MemoDetailSidebarDrawer = ({ memo }: Props) => {
|
||||
const location = useLocation();
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setOpen(false);
|
||||
}, [location.pathname]);
|
||||
|
||||
const toggleDrawer = (inOpen: boolean) => (event: React.KeyboardEvent | React.MouseEvent) => {
|
||||
if (event.type === "keydown" && ((event as React.KeyboardEvent).key === "Tab" || (event as React.KeyboardEvent).key === "Shift")) {
|
||||
return;
|
||||
}
|
||||
setOpen(inOpen);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<IconButton onClick={toggleDrawer(true)}>
|
||||
<Icon.GanttChart className="w-5 h-auto dark:text-gray-400" />
|
||||
</IconButton>
|
||||
<Drawer anchor="right" size="sm" open={open} onClose={toggleDrawer(false)}>
|
||||
<div className="w-full h-full px-4 bg-zinc-100 dark:bg-zinc-900">
|
||||
<MemoDetailSidebar className="py-4" memo={memo} />
|
||||
</div>
|
||||
</Drawer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default MemoDetailSidebarDrawer;
|
4
web/src/components/MemoDetailSidebar/index.ts
Normal file
4
web/src/components/MemoDetailSidebar/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import MemoDetailSidebar from "./MemoDetailSidebar";
|
||||
import MemoDetailSidebarDrawer from "./MemoDetailSidebarDrawer";
|
||||
|
||||
export { MemoDetailSidebar, MemoDetailSidebarDrawer };
|
@ -26,7 +26,7 @@ const TimelineSidebarDrawer = () => {
|
||||
<Icon.Search className="w-5 h-auto dark:text-gray-400" />
|
||||
</IconButton>
|
||||
<Drawer anchor="right" size="sm" open={open} onClose={toggleDrawer(false)}>
|
||||
<div className="w-full h-full px-5 bg-zinc-100 dark:bg-zinc-900">
|
||||
<div className="w-full h-full px-4 bg-zinc-100 dark:bg-zinc-900">
|
||||
<TimelineSidebar className="py-4" />
|
||||
</div>
|
||||
</Drawer>
|
||||
|
@ -62,10 +62,10 @@ const UserStatisticsView = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full border mt-2 py-2 px-3 rounded-lg space-y-0.5 text-gray-500 dark:text-gray-400 bg-zinc-50 dark:bg-zinc-900 dark:border-zinc-800">
|
||||
<div className="group w-full border mt-2 py-2 px-3 rounded-lg space-y-0.5 text-gray-500 dark:text-gray-400 bg-zinc-50 dark:bg-zinc-900 dark:border-zinc-800">
|
||||
<div className="w-full mb-1 flex flex-row justify-between items-center">
|
||||
<p className="text-sm font-medium leading-6 dark:text-gray-500">{t("common.statistics")}</p>
|
||||
<div className="">
|
||||
<div className="group-hover:block hidden">
|
||||
<Tooltip title={"Refresh"} placement="top">
|
||||
<Icon.RefreshCcw
|
||||
className="text-gray-400 w-4 h-auto cursor-pointer opacity-60 hover:opacity-100"
|
||||
|
@ -1,14 +1,17 @@
|
||||
import { Button } from "@mui/joy";
|
||||
import clsx from "clsx";
|
||||
import { ClientError } from "nice-grpc-web";
|
||||
import { useEffect, useState } from "react";
|
||||
import { toast } from "react-hot-toast";
|
||||
import { Link, useParams } from "react-router-dom";
|
||||
import Icon from "@/components/Icon";
|
||||
import { MemoDetailSidebar, MemoDetailSidebarDrawer } from "@/components/MemoDetailSidebar";
|
||||
import showMemoEditorDialog from "@/components/MemoEditor/MemoEditorDialog";
|
||||
import MemoView from "@/components/MemoView";
|
||||
import MobileHeader from "@/components/MobileHeader";
|
||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
||||
import useNavigateTo from "@/hooks/useNavigateTo";
|
||||
import useResponsiveWidth from "@/hooks/useResponsiveWidth";
|
||||
import { useMemoStore } from "@/store/v1";
|
||||
import { MemoRelation_Type } from "@/types/proto/api/v1/memo_relation_service";
|
||||
import { Memo } from "@/types/proto/api/v1/memo_service";
|
||||
@ -16,6 +19,7 @@ import { useTranslate } from "@/utils/i18n";
|
||||
|
||||
const MemoDetail = () => {
|
||||
const t = useTranslate();
|
||||
const { md } = useResponsiveWidth();
|
||||
const params = useParams();
|
||||
const navigateTo = useNavigateTo();
|
||||
const currentUser = useCurrentUser();
|
||||
@ -77,66 +81,77 @@ const MemoDetail = () => {
|
||||
|
||||
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 />
|
||||
<div className="w-full px-4 sm:px-6">
|
||||
{parentMemo && (
|
||||
<div className="w-auto inline-block mb-2">
|
||||
<Link
|
||||
className="px-3 py-1 border rounded-lg max-w-xs w-auto text-sm flex flex-row justify-start items-center flex-nowrap text-gray-600 dark:text-gray-400 dark:border-gray-500 hover:shadow hover:opacity-80"
|
||||
to={`/m/${parentMemo.uid}`}
|
||||
unstable_viewTransition
|
||||
>
|
||||
<Icon.ArrowUpLeftFromCircle className="w-4 h-auto shrink-0 opacity-60 mr-2" />
|
||||
<span className="truncate">{parentMemo.content}</span>
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
<MemoView
|
||||
key={`${memo.name}-${memo.displayTime}`}
|
||||
className="shadow hover:shadow-xl transition-all"
|
||||
memo={memo}
|
||||
compact={false}
|
||||
showCreator
|
||||
showVisibility
|
||||
showPinned
|
||||
/>
|
||||
<div className="pt-8 pb-16 w-full">
|
||||
<h2 id="comments" className="sr-only">
|
||||
{t("memo.comment.self")}
|
||||
</h2>
|
||||
<div className="relative mx-auto flex-grow w-full min-h-full flex flex-col justify-start items-start gap-y-1">
|
||||
{comments.length === 0 ? (
|
||||
currentUser && (
|
||||
<div className="w-full flex flex-row justify-center items-center py-6">
|
||||
<Button
|
||||
variant="plain"
|
||||
color="neutral"
|
||||
endDecorator={<Icon.MessageCircle className="w-5 h-auto text-gray-500" />}
|
||||
onClick={handleShowCommentEditor}
|
||||
>
|
||||
<span className="font-normal text-gray-500">{t("memo.comment.write-a-comment")}</span>
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<>
|
||||
<div className="w-full flex flex-row justify-between items-center px-3 mb-2">
|
||||
<div className="flex flex-row justify-start items-center">
|
||||
<Icon.MessageCircle className="w-5 h-auto text-gray-400 mr-1" />
|
||||
<span className="text-gray-400 text-sm">{t("memo.comment.self")}</span>
|
||||
<span className="text-gray-400 text-sm ml-1">({comments.length})</span>
|
||||
{!md && (
|
||||
<MobileHeader>
|
||||
<MemoDetailSidebarDrawer memo={memo} />
|
||||
</MobileHeader>
|
||||
)}
|
||||
<div className={clsx("w-full flex flex-row justify-start items-start px-4 sm:px-6 gap-4")}>
|
||||
<div className={clsx(md ? "w-[calc(100%-15rem)]" : "w-full")}>
|
||||
{parentMemo && (
|
||||
<div className="w-auto inline-block mb-2">
|
||||
<Link
|
||||
className="px-3 py-1 border rounded-lg max-w-xs w-auto text-sm flex flex-row justify-start items-center flex-nowrap text-gray-600 dark:text-gray-400 dark:border-gray-500 hover:shadow hover:opacity-80"
|
||||
to={`/m/${parentMemo.uid}`}
|
||||
unstable_viewTransition
|
||||
>
|
||||
<Icon.ArrowUpLeftFromCircle className="w-4 h-auto shrink-0 opacity-60 mr-2" />
|
||||
<span className="truncate">{parentMemo.content}</span>
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
<MemoView
|
||||
key={`${memo.name}-${memo.displayTime}`}
|
||||
className="shadow hover:shadow-md transition-all"
|
||||
memo={memo}
|
||||
compact={false}
|
||||
showCreator
|
||||
showVisibility
|
||||
showPinned
|
||||
/>
|
||||
<div className="pt-8 pb-16 w-full">
|
||||
<h2 id="comments" className="sr-only">
|
||||
{t("memo.comment.self")}
|
||||
</h2>
|
||||
<div className="relative mx-auto flex-grow w-full min-h-full flex flex-col justify-start items-start gap-y-1">
|
||||
{comments.length === 0 ? (
|
||||
currentUser && (
|
||||
<div className="w-full flex flex-row justify-center items-center py-6">
|
||||
<Button
|
||||
variant="plain"
|
||||
color="neutral"
|
||||
endDecorator={<Icon.MessageCircle className="w-5 h-auto text-gray-500" />}
|
||||
onClick={handleShowCommentEditor}
|
||||
>
|
||||
<span className="font-normal text-gray-500">{t("memo.comment.write-a-comment")}</span>
|
||||
</Button>
|
||||
</div>
|
||||
<Button variant="plain" color="neutral" onClick={handleShowCommentEditor}>
|
||||
<span className="font-normal text-gray-500">{t("memo.comment.write-a-comment")}</span>
|
||||
</Button>
|
||||
</div>
|
||||
{comments.map((comment) => (
|
||||
<MemoView key={`${comment.name}-${comment.displayTime}`} memo={comment} showCreator compact />
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
)
|
||||
) : (
|
||||
<>
|
||||
<div className="w-full flex flex-row justify-between items-center px-3 mb-2">
|
||||
<div className="flex flex-row justify-start items-center">
|
||||
<Icon.MessageCircle className="w-5 h-auto text-gray-400 mr-1" />
|
||||
<span className="text-gray-400 text-sm">{t("memo.comment.self")}</span>
|
||||
<span className="text-gray-400 text-sm ml-1">({comments.length})</span>
|
||||
</div>
|
||||
<Button variant="plain" color="neutral" onClick={handleShowCommentEditor}>
|
||||
<span className="font-normal text-gray-500">{t("memo.comment.write-a-comment")}</span>
|
||||
</Button>
|
||||
</div>
|
||||
{comments.map((comment) => (
|
||||
<MemoView key={`${comment.name}-${comment.displayTime}`} memo={comment} showCreator compact />
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{md && (
|
||||
<div className="sticky top-0 left-0 shrink-0 -mt-6 w-56 h-full">
|
||||
<MemoDetailSidebar className="py-6" memo={memo} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
Loading…
x
Reference in New Issue
Block a user