feat: optimize filters sync (#4260)

* refactor: add bi-directional filters sync between filterStore and searchParams

* fix: tag redirection from memos detail page, https://github.com/usememos/memos/issues/4232
This commit is contained in:
Chris Curry
2025-01-04 23:42:49 +08:00
committed by GitHub
parent e3d1967db8
commit d81174ad7c
13 changed files with 121 additions and 50 deletions

View File

@ -75,7 +75,7 @@ const EmbeddedMemo = ({ resourceId: uid, params: paramsStr }: Props) => {
<span className="text-xs opacity-60 leading-5 cursor-pointer hover:opacity-80" onClick={() => copyMemoUid(memo.uid)}> <span className="text-xs opacity-60 leading-5 cursor-pointer hover:opacity-80" onClick={() => copyMemoUid(memo.uid)}>
{memo.uid.slice(0, 6)} {memo.uid.slice(0, 6)}
</span> </span>
<Link className="opacity-60 hover:opacity-80" to={`/m/${memo.uid}`} viewTransition> <Link className="opacity-60 hover:opacity-80" to={`/m/${memo.uid}`} state={{ from: context.parentPage }} viewTransition>
<ArrowUpRightIcon className="w-5 h-auto" /> <ArrowUpRightIcon className="w-5 h-auto" />
</Link> </Link>
</div> </div>

View File

@ -1,7 +1,8 @@
import { useEffect } from "react"; import { useContext, useEffect } from "react";
import useLoading from "@/hooks/useLoading"; import useLoading from "@/hooks/useLoading";
import useNavigateTo from "@/hooks/useNavigateTo"; import useNavigateTo from "@/hooks/useNavigateTo";
import { useMemoStore } from "@/store/v1"; import { useMemoStore } from "@/store/v1";
import { RendererContext } from "../types";
import Error from "./Error"; import Error from "./Error";
interface Props { interface Props {
@ -15,6 +16,7 @@ const ReferencedMemo = ({ resourceId: uid, params: paramsStr }: Props) => {
const memoStore = useMemoStore(); const memoStore = useMemoStore();
const memo = memoStore.getMemoByUid(uid); const memo = memoStore.getMemoByUid(uid);
const params = new URLSearchParams(paramsStr); const params = new URLSearchParams(paramsStr);
const context = useContext(RendererContext);
useEffect(() => { useEffect(() => {
memoStore.fetchMemoByUid(uid).finally(() => loadingState.setFinish()); memoStore.fetchMemoByUid(uid).finally(() => loadingState.setFinish());
@ -31,7 +33,11 @@ const ReferencedMemo = ({ resourceId: uid, params: paramsStr }: Props) => {
const displayContent = paramsText || (memo.snippet.length > 12 ? `${memo.snippet.slice(0, 12)}...` : memo.snippet); const displayContent = paramsText || (memo.snippet.length > 12 ? `${memo.snippet.slice(0, 12)}...` : memo.snippet);
const handleGotoMemoDetailPage = () => { const handleGotoMemoDetailPage = () => {
navigateTo(`/m/${memo.uid}`); navigateTo(`/m/${memo.uid}`, {
state: {
from: context.parentPage,
},
});
}; };
return ( return (

View File

@ -1,6 +1,9 @@
import clsx from "clsx"; import clsx from "clsx";
import { useContext } from "react"; import { useContext } from "react";
import { useMemoFilterStore } from "@/store/v1"; import { useLocation } from "react-router-dom";
import useNavigateTo from "@/hooks/useNavigateTo";
import { Routes } from "@/router";
import { stringifyFilters, useMemoFilterStore } from "@/store/v1";
import { RendererContext } from "./types"; import { RendererContext } from "./types";
interface Props { interface Props {
@ -10,12 +13,24 @@ interface Props {
const Tag: React.FC<Props> = ({ content }: Props) => { const Tag: React.FC<Props> = ({ content }: Props) => {
const context = useContext(RendererContext); const context = useContext(RendererContext);
const memoFilterStore = useMemoFilterStore(); const memoFilterStore = useMemoFilterStore();
const location = useLocation();
const navigateTo = useNavigateTo();
const handleTagClick = () => { const handleTagClick = () => {
if (context.disableFilter) { if (context.disableFilter) {
return; return;
} }
// If the tag is clicked in a memo detail page, we should navigate to the memo list page.
if (location.pathname.startsWith("/m")) {
const pathname = context.parentPage || Routes.ROOT;
const searchParams = new URLSearchParams();
searchParams.set("filter", stringifyFilters([{ factor: "tagSearch", value: content }]));
navigateTo(`${pathname}?${searchParams.toString()}`);
return;
}
const isActive = memoFilterStore.getFiltersByFactor("tagSearch").some((filter) => filter.value === content); const isActive = memoFilterStore.getFiltersByFactor("tagSearch").some((filter) => filter.value === content);
if (isActive) { if (isActive) {
memoFilterStore.removeFilter((f) => f.factor === "tagSearch" && f.value === content); memoFilterStore.removeFilter((f) => f.factor === "tagSearch" && f.value === content);

View File

@ -24,6 +24,7 @@ interface Props {
contentClassName?: string; contentClassName?: string;
onClick?: (e: React.MouseEvent) => void; onClick?: (e: React.MouseEvent) => void;
onDoubleClick?: (e: React.MouseEvent) => void; onDoubleClick?: (e: React.MouseEvent) => void;
parentPage?: string;
} }
type ContentCompactView = "ALL" | "SNIPPET"; type ContentCompactView = "ALL" | "SNIPPET";
@ -79,6 +80,7 @@ const MemoContent: React.FC<Props> = (props: Props) => {
readonly: !allowEdit, readonly: !allowEdit,
disableFilter: props.disableFilter, disableFilter: props.disableFilter,
embeddedMemos: embeddedMemos || new Set(), embeddedMemos: embeddedMemos || new Set(),
parentPage: props.parentPage,
}} }}
> >
<div className={`w-full flex flex-col justify-start items-start text-gray-800 dark:text-gray-400 ${className || ""}`}> <div className={`w-full flex flex-col justify-start items-start text-gray-800 dark:text-gray-400 ${className || ""}`}>

View File

@ -9,6 +9,7 @@ interface Context {
memoName?: string; memoName?: string;
readonly?: boolean; readonly?: boolean;
disableFilter?: boolean; disableFilter?: boolean;
parentPage?: string;
} }
export const RendererContext = createContext<Context>({ export const RendererContext = createContext<Context>({

View File

@ -9,9 +9,10 @@ import MemoRelationForceGraph from "../MemoRelationForceGraph";
interface Props { interface Props {
memo: Memo; memo: Memo;
className?: string; className?: string;
parentPage?: string;
} }
const MemoDetailSidebar = ({ memo, className }: Props) => { const MemoDetailSidebar = ({ memo, className, parentPage }: Props) => {
const t = useTranslate(); const t = useTranslate();
const property = MemoProperty.fromPartial(memo.property || {}); const property = MemoProperty.fromPartial(memo.property || {});
const hasSpecialProperty = property.hasLink || property.hasTaskList || property.hasCode || property.hasIncompleteTasks; const hasSpecialProperty = property.hasLink || property.hasTaskList || property.hasCode || property.hasIncompleteTasks;
@ -27,7 +28,7 @@ const MemoDetailSidebar = ({ memo, className }: Props) => {
<div className="flex flex-col justify-start items-start w-full px-1 gap-2 h-auto shrink-0 flex-nowrap hide-scrollbar"> <div className="flex flex-col justify-start items-start w-full px-1 gap-2 h-auto shrink-0 flex-nowrap hide-scrollbar">
{shouldShowRelationGraph && ( {shouldShowRelationGraph && (
<div className="relative w-full h-36 border rounded-lg bg-zinc-50 dark:bg-zinc-900 dark:border-zinc-800"> <div className="relative w-full h-36 border rounded-lg bg-zinc-50 dark:bg-zinc-900 dark:border-zinc-800">
<MemoRelationForceGraph className="w-full h-full" memo={memo} /> <MemoRelationForceGraph className="w-full h-full" memo={memo} parentPage={parentPage} />
<div className="absolute top-1 left-2 text-xs opacity-60 font-mono gap-1 flex flex-row items-center"> <div className="absolute top-1 left-2 text-xs opacity-60 font-mono gap-1 flex flex-row items-center">
<span>Relations</span> <span>Relations</span>
<span className="text-xs opacity-60">(Beta)</span> <span className="text-xs opacity-60">(Beta)</span>

View File

@ -8,9 +8,10 @@ import MemoDetailSidebar from "./MemoDetailSidebar";
interface Props { interface Props {
memo: Memo; memo: Memo;
parentPage?: string;
} }
const MemoDetailSidebarDrawer = ({ memo }: Props) => { const MemoDetailSidebarDrawer = ({ memo, parentPage }: Props) => {
const location = useLocation(); const location = useLocation();
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
@ -32,7 +33,7 @@ const MemoDetailSidebarDrawer = ({ memo }: Props) => {
</Button> </Button>
<Drawer anchor="right" size="sm" open={open} onClose={toggleDrawer(false)}> <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"> <div className="w-full h-full px-4 bg-zinc-100 dark:bg-zinc-900">
<MemoDetailSidebar className="py-4" memo={memo} /> <MemoDetailSidebar className="py-4" memo={memo} parentPage={parentPage} />
</div> </div>
</Drawer> </Drawer>
</> </>

View File

@ -1,47 +1,61 @@
import { isEqual } from "lodash-es"; import { isEqual } from "lodash-es";
import { CalendarIcon, CheckCircleIcon, CodeIcon, EyeIcon, FilterIcon, LinkIcon, SearchIcon, TagIcon, XIcon } from "lucide-react"; import { CalendarIcon, CheckCircleIcon, CodeIcon, EyeIcon, FilterIcon, LinkIcon, SearchIcon, TagIcon, XIcon } from "lucide-react";
import { useEffect } from "react"; import { useEffect, useRef } from "react";
import { useSearchParams } from "react-router-dom"; import { useSearchParams } from "react-router-dom";
import usePrevious from "react-use/lib/usePrevious";
import { FilterFactor, getMemoFilterKey, MemoFilter, parseFilterQuery, stringifyFilters, useMemoFilterStore } from "@/store/v1"; import { FilterFactor, getMemoFilterKey, MemoFilter, parseFilterQuery, stringifyFilters, useMemoFilterStore } from "@/store/v1";
const MemoFilters = () => { const MemoFilters = () => {
const [searchParams, setSearchParams] = useSearchParams(); const [searchParams, setSearchParams] = useSearchParams();
const memoFilterStore = useMemoFilterStore(); const memoFilterStore = useMemoFilterStore();
const filters = memoFilterStore.filters; const filters = memoFilterStore.filters;
const prevFilters = usePrevious(filters);
const orderByTimeAsc = memoFilterStore.orderByTimeAsc; const orderByTimeAsc = memoFilterStore.orderByTimeAsc;
const prevOrderByTimeAsc = usePrevious(orderByTimeAsc); const lastUpdateRef = useRef<"url" | "store">("url");
// Sync the filters and orderByTimeAsc to the search params. // set lastUpdateRef to store when filters or orderByTimeAsc changes
useEffect(() => { useEffect(() => {
lastUpdateRef.current = "store";
}, [filters, orderByTimeAsc]);
// set lastUpdateRef to url when searchParams changes
useEffect(() => {
lastUpdateRef.current = "url";
}, [searchParams]);
const checkAndSync = () => {
const filtersInURL = searchParams.get("filter") || "";
const orderByTimeAscInURL = searchParams.get("orderBy") === "asc";
const storeMatchesURL = filtersInURL === stringifyFilters(filters) && orderByTimeAscInURL === orderByTimeAsc;
if (!storeMatchesURL) {
if (lastUpdateRef.current === "url") {
// Sync URL -> Store
memoFilterStore.setState({
filters: parseFilterQuery(filtersInURL),
orderByTimeAsc: orderByTimeAscInURL,
});
} else if (lastUpdateRef.current === "store") {
// Sync Store -> URL
const newSearchParams = new URLSearchParams(searchParams); const newSearchParams = new URLSearchParams(searchParams);
if (prevOrderByTimeAsc !== orderByTimeAsc) {
if (orderByTimeAsc) { if (orderByTimeAsc) {
newSearchParams.set("orderBy", "asc"); newSearchParams.set("orderBy", "asc");
} else { } else {
newSearchParams.delete("orderBy"); newSearchParams.delete("orderBy");
} }
}
if (prevFilters && stringifyFilters(prevFilters) !== stringifyFilters(filters)) {
if (filters.length > 0) { if (filters.length > 0) {
newSearchParams.set("filter", stringifyFilters(filters)); newSearchParams.set("filter", stringifyFilters(filters));
} else { } else {
newSearchParams.delete("filter"); newSearchParams.delete("filter");
} }
}
setSearchParams(newSearchParams); setSearchParams(newSearchParams);
}, [prevOrderByTimeAsc, orderByTimeAsc, prevFilters, filters, searchParams]); }
}
};
// Sync the search params to the filters and orderByTimeAsc when the component is mounted. // Watch both URL and store changes
useEffect(() => { useEffect(checkAndSync, [searchParams, filters, orderByTimeAsc]);
const newFilters = parseFilterQuery(searchParams.get("filter"));
const newOrderByTimeAsc = searchParams.get("orderBy") === "asc";
memoFilterStore.setState({ filters: newFilters, orderByTimeAsc: newOrderByTimeAsc });
}, []);
const getFilterDisplayText = (filter: MemoFilter) => { const getFilterDisplayText = (filter: MemoFilter) => {
if (filter.value) { if (filter.value) {

View File

@ -11,12 +11,13 @@ import { convertMemoRelationsToGraphData } from "./utils";
interface Props { interface Props {
memo: Memo; memo: Memo;
className?: string; className?: string;
parentPage?: string;
} }
const MAIN_NODE_COLOR = "#14b8a6"; const MAIN_NODE_COLOR = "#14b8a6";
const DEFAULT_NODE_COLOR = "#a1a1aa"; const DEFAULT_NODE_COLOR = "#a1a1aa";
const MemoRelationForceGraph = ({ className, memo }: Props) => { const MemoRelationForceGraph = ({ className, memo, parentPage }: Props) => {
const navigateTo = useNavigateTo(); const navigateTo = useNavigateTo();
const { mode } = useColorScheme(); const { mode } = useColorScheme();
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
@ -30,7 +31,11 @@ const MemoRelationForceGraph = ({ className, memo }: Props) => {
const onNodeClick = (node: NodeObject<NodeType>) => { const onNodeClick = (node: NodeObject<NodeType>) => {
if (node.memo.uid === memo.uid) return; if (node.memo.uid === memo.uid) return;
navigateTo(`/m/${node.memo.uid}`); navigateTo(`/m/${node.memo.uid}`, {
state: {
from: parentPage,
},
});
}; };
return ( return (

View File

@ -8,10 +8,11 @@ import { Memo } from "@/types/proto/api/v1/memo_service";
interface Props { interface Props {
memo: Memo; memo: Memo;
relations: MemoRelation[]; relations: MemoRelation[];
parentPage?: string;
} }
const MemoRelationListView = (props: Props) => { const MemoRelationListView = (props: Props) => {
const { memo, relations: relationList } = props; const { memo, relations: relationList, parentPage } = props;
const referencingMemoList = relationList const referencingMemoList = relationList
.filter((relation) => relation.memo?.name === memo.name && relation.relatedMemo?.name !== memo.name) .filter((relation) => relation.memo?.name === memo.name && relation.relatedMemo?.name !== memo.name)
.map((relation) => relation.relatedMemo!); .map((relation) => relation.relatedMemo!);
@ -65,6 +66,9 @@ const MemoRelationListView = (props: Props) => {
className="w-auto max-w-full flex flex-row justify-start items-center text-sm leading-5 text-gray-600 dark:text-gray-400 dark:border-zinc-700 dark:bg-zinc-900 hover:underline" className="w-auto max-w-full flex flex-row justify-start items-center text-sm leading-5 text-gray-600 dark:text-gray-400 dark:border-zinc-700 dark:bg-zinc-900 hover:underline"
to={`/m/${memo.uid}`} to={`/m/${memo.uid}`}
viewTransition viewTransition
state={{
from: parentPage,
}}
> >
<span className="text-xs opacity-60 leading-4 border font-mono px-1 rounded-full mr-1 dark:border-zinc-700"> <span className="text-xs opacity-60 leading-4 border font-mono px-1 rounded-full mr-1 dark:border-zinc-700">
{memo.uid.slice(0, 6)} {memo.uid.slice(0, 6)}
@ -84,6 +88,9 @@ const MemoRelationListView = (props: Props) => {
className="w-auto max-w-full flex flex-row justify-start items-center text-sm leading-5 text-gray-600 dark:text-gray-400 dark:border-zinc-700 dark:bg-zinc-900 hover:underline" className="w-auto max-w-full flex flex-row justify-start items-center text-sm leading-5 text-gray-600 dark:text-gray-400 dark:border-zinc-700 dark:bg-zinc-900 hover:underline"
to={`/m/${memo.uid}`} to={`/m/${memo.uid}`}
viewTransition viewTransition
state={{
from: parentPage,
}}
> >
<span className="text-xs opacity-60 leading-4 border font-mono px-1 rounded-full mr-1 dark:border-zinc-700"> <span className="text-xs opacity-60 leading-4 border font-mono px-1 rounded-full mr-1 dark:border-zinc-700">
{memo.uid.slice(0, 6)} {memo.uid.slice(0, 6)}

View File

@ -35,6 +35,7 @@ interface Props {
showVisibility?: boolean; showVisibility?: boolean;
showPinned?: boolean; showPinned?: boolean;
className?: string; className?: string;
parentPage?: string;
} }
const MemoView: React.FC<Props> = (props: Props) => { const MemoView: React.FC<Props> = (props: Props) => {
@ -60,6 +61,7 @@ const MemoView: React.FC<Props> = (props: Props) => {
const relativeTimeFormat = Date.now() - memo.displayTime!.getTime() > 1000 * 60 * 60 * 24 ? "datetime" : "auto"; const relativeTimeFormat = Date.now() - memo.displayTime!.getTime() > 1000 * 60 * 60 * 24 ? "datetime" : "auto";
const readonly = memo.creator !== user?.name && !isSuperUser(user); const readonly = memo.creator !== user?.name && !isSuperUser(user);
const isInMemoDetailPage = location.pathname.startsWith(`/m/${memo.uid}`); const isInMemoDetailPage = location.pathname.startsWith(`/m/${memo.uid}`);
const parentPage = props.parentPage || location.pathname;
// Initial related data: creator. // Initial related data: creator.
useAsyncEffect(async () => { useAsyncEffect(async () => {
@ -68,8 +70,12 @@ const MemoView: React.FC<Props> = (props: Props) => {
}, []); }, []);
const handleGotoMemoDetailPage = useCallback(() => { const handleGotoMemoDetailPage = useCallback(() => {
navigateTo(`/m/${memo.uid}`); navigateTo(`/m/${memo.uid}`, {
}, [memo.uid]); state: {
from: parentPage,
},
});
}, [memo.uid, parentPage]);
const handleMemoContentClick = useCallback(async (e: React.MouseEvent) => { const handleMemoContentClick = useCallback(async (e: React.MouseEvent) => {
const targetEl = e.target as HTMLElement; const targetEl = e.target as HTMLElement;
@ -217,6 +223,9 @@ const MemoView: React.FC<Props> = (props: Props) => {
)} )}
to={`/m/${memo.uid}#comments`} to={`/m/${memo.uid}#comments`}
viewTransition viewTransition
state={{
from: parentPage,
}}
> >
<MessageCircleMoreIcon className="w-4 h-4 mx-auto text-gray-500 dark:text-gray-400" /> <MessageCircleMoreIcon className="w-4 h-4 mx-auto text-gray-500 dark:text-gray-400" />
{commentAmount > 0 && <span className="text-xs text-gray-500 dark:text-gray-400">{commentAmount}</span>} {commentAmount > 0 && <span className="text-xs text-gray-500 dark:text-gray-400">{commentAmount}</span>}
@ -242,10 +251,11 @@ const MemoView: React.FC<Props> = (props: Props) => {
onClick={handleMemoContentClick} onClick={handleMemoContentClick}
onDoubleClick={handleMemoContentDoubleClick} onDoubleClick={handleMemoContentDoubleClick}
compact={props.compact && workspaceMemoRelatedSetting.enableAutoCompact} compact={props.compact && workspaceMemoRelatedSetting.enableAutoCompact}
parentPage={parentPage}
/> />
{memo.location && <MemoLocationView location={memo.location} />} {memo.location && <MemoLocationView location={memo.location} />}
<MemoResourceListView resources={memo.resources} /> <MemoResourceListView resources={memo.resources} />
<MemoRelationListView memo={memo} relations={referencedMemos} /> <MemoRelationListView memo={memo} relations={referencedMemos} parentPage={parentPage} />
<MemoReactionistView memo={memo} reactions={memo.reactions} /> <MemoReactionistView memo={memo} reactions={memo.reactions} />
</> </>
)} )}

View File

@ -1,15 +1,15 @@
import { useNavigate } from "react-router-dom"; import { NavigateOptions, useNavigate } from "react-router-dom";
const useNavigateTo = () => { const useNavigateTo = () => {
const navigateTo = useNavigate(); const navigateTo = useNavigate();
const navigateToWithViewTransition = (to: string) => { const navigateToWithViewTransition = (to: string, options?: NavigateOptions) => {
const document = window.document as any; const document = window.document as any;
if (!document.startViewTransition) { if (!document.startViewTransition) {
navigateTo(to); navigateTo(to, options);
} else { } else {
document.startViewTransition(() => { document.startViewTransition(() => {
navigateTo(to); navigateTo(to, options);
}); });
} }
}; };

View File

@ -4,7 +4,7 @@ import { ArrowUpLeftFromCircleIcon, MessageCircleIcon } from "lucide-react";
import { ClientError } from "nice-grpc-web"; import { ClientError } from "nice-grpc-web";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { toast } from "react-hot-toast"; import { toast } from "react-hot-toast";
import { Link, useParams } from "react-router-dom"; import { Link, useLocation, useParams } from "react-router-dom";
import { MemoDetailSidebar, MemoDetailSidebarDrawer } from "@/components/MemoDetailSidebar"; import { MemoDetailSidebar, MemoDetailSidebarDrawer } from "@/components/MemoDetailSidebar";
import MemoEditor from "@/components/MemoEditor"; import MemoEditor from "@/components/MemoEditor";
import MemoView from "@/components/MemoView"; import MemoView from "@/components/MemoView";
@ -23,6 +23,7 @@ const MemoDetail = () => {
const { md } = useResponsiveWidth(); const { md } = useResponsiveWidth();
const params = useParams(); const params = useParams();
const navigateTo = useNavigateTo(); const navigateTo = useNavigateTo();
const { state: locationState } = useLocation();
const workspaceSettingStore = useWorkspaceSettingStore(); const workspaceSettingStore = useWorkspaceSettingStore();
const currentUser = useCurrentUser(); const currentUser = useCurrentUser();
const memoStore = useMemoStore(); const memoStore = useMemoStore();
@ -86,7 +87,7 @@ const MemoDetail = () => {
<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"> <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 && ( {!md && (
<MobileHeader> <MobileHeader>
<MemoDetailSidebarDrawer memo={memo} /> <MemoDetailSidebarDrawer memo={memo} parentPage={locationState?.from} />
</MobileHeader> </MobileHeader>
)} )}
<div className={clsx("w-full flex flex-row justify-start items-start px-4 sm:px-6 gap-4")}> <div className={clsx("w-full flex flex-row justify-start items-start px-4 sm:px-6 gap-4")}>
@ -96,6 +97,7 @@ const MemoDetail = () => {
<Link <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" 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}`} to={`/m/${parentMemo.uid}`}
state={locationState}
viewTransition viewTransition
> >
<ArrowUpLeftFromCircleIcon className="w-4 h-auto shrink-0 opacity-60 mr-2" /> <ArrowUpLeftFromCircleIcon className="w-4 h-auto shrink-0 opacity-60 mr-2" />
@ -108,6 +110,7 @@ const MemoDetail = () => {
className="shadow hover:shadow-md transition-all" className="shadow hover:shadow-md transition-all"
memo={memo} memo={memo}
compact={false} compact={false}
parentPage={locationState?.from}
showCreator showCreator
showVisibility showVisibility
showPinned showPinned
@ -141,7 +144,13 @@ const MemoDetail = () => {
)} )}
</div> </div>
{comments.map((comment) => ( {comments.map((comment) => (
<MemoView key={`${comment.name}-${comment.displayTime}`} memo={comment} showCreator compact /> <MemoView
key={`${comment.name}-${comment.displayTime}`}
memo={comment}
parentPage={locationState?.from}
showCreator
compact
/>
))} ))}
</> </>
)} )}
@ -162,7 +171,7 @@ const MemoDetail = () => {
</div> </div>
{md && ( {md && (
<div className="sticky top-0 left-0 shrink-0 -mt-6 w-56 h-full"> <div className="sticky top-0 left-0 shrink-0 -mt-6 w-56 h-full">
<MemoDetailSidebar className="py-6" memo={memo} /> <MemoDetailSidebar className="py-6" memo={memo} parentPage={locationState?.from} />
</div> </div>
)} )}
</div> </div>