diff --git a/web/src/components/AppSidebar/AppSidebar.tsx b/web/src/components/AppSidebar/AppSidebar.tsx index 51e950f5..be91ed05 100644 --- a/web/src/components/AppSidebar/AppSidebar.tsx +++ b/web/src/components/AppSidebar/AppSidebar.tsx @@ -270,7 +270,6 @@ const MemoDetailSidebarContent = () => { memo={memoDetail.memo} parentMemo={memoDetail.parentMemo} parentPage={memoDetail.from} - parentScope={memoDetail.fromScope} hasExplicitOrigin={memoDetail.hasExplicitOrigin} commentCount={memoDetail.commentCount} forceReadonly={memoDetail.readonly} diff --git a/web/src/components/AttachmentLibrary/AttachmentLibraryPrimitives.tsx b/web/src/components/AttachmentLibrary/AttachmentLibraryPrimitives.tsx index 854e1b84..b0015bbc 100644 --- a/web/src/components/AttachmentLibrary/AttachmentLibraryPrimitives.tsx +++ b/web/src/components/AttachmentLibrary/AttachmentLibraryPrimitives.tsx @@ -63,7 +63,7 @@ export const AttachmentSourceChip = ({ return ( {t("attachment-library.labels.memo")} diff --git a/web/src/components/MemoCommentSection.tsx b/web/src/components/MemoCommentSection.tsx index 6bfe6842..5d028763 100644 --- a/web/src/components/MemoCommentSection.tsx +++ b/web/src/components/MemoCommentSection.tsx @@ -3,7 +3,6 @@ import { type ComponentType, forwardRef, useCallback, useImperativeHandle, useRe import { loadMemoEditor } from "@/components/MemoEditor/loader"; import type { MemoEditorProps } from "@/components/MemoEditor/types"; import MemoView from "@/components/MemoView"; -import type { MemoOriginScope } from "@/components/MemoView/navigation"; import { Button } from "@/components/ui/button"; import useCurrentUser from "@/hooks/useCurrentUser"; import { MEMO_COMMENTS_ANCHOR_ID } from "@/lib/memo-comments"; @@ -17,7 +16,6 @@ interface Props { comments: Memo[]; commentCount?: number; parentPage?: string; - parentScope?: MemoOriginScope; hasMoreComments?: boolean; isFetchingMoreComments?: boolean; onLoadMoreComments?: () => void; @@ -29,19 +27,7 @@ export interface MemoCommentSectionHandle { } const MemoCommentSection = forwardRef( - ( - { - memo, - comments, - commentCount = comments.length, - parentPage, - parentScope, - hasMoreComments, - isFetchingMoreComments, - onLoadMoreComments, - }, - ref, - ) => { + ({ memo, comments, commentCount = comments.length, parentPage, hasMoreComments, isFetchingMoreComments, onLoadMoreComments }, ref) => { const t = useTranslate(); const currentUser = useCurrentUser(); const [showEditor, setShowEditor] = useState(false); @@ -154,7 +140,7 @@ const MemoCommentSection = forwardRef( )} {comments.map((comment) => (
- +
))} {hasMoreComments && ( diff --git a/web/src/components/MemoContent/MemoMarkdownRenderer.tsx b/web/src/components/MemoContent/MemoMarkdownRenderer.tsx index ec456e54..44486c74 100644 --- a/web/src/components/MemoContent/MemoMarkdownRenderer.tsx +++ b/web/src/components/MemoContent/MemoMarkdownRenderer.tsx @@ -7,7 +7,6 @@ import { isMentionElement, isTagElement, isTaskListItemElement } from "@/types/m import type { Attachment } from "@/types/proto/api/v1/attachment_service_pb"; import { lazyWithReload } from "@/utils/lazy"; import { resolveManagedAttachmentImageSource } from "@/utils/managed-attachment"; -import type { MemoOriginScope } from "../MemoView/navigation"; import { CodeBlock } from "./CodeBlock"; import { MarkdownRenderContext, rootMarkdownRenderContext } from "./MarkdownRenderContext"; import { Mention } from "./Mention"; @@ -26,7 +25,6 @@ export interface MemoMarkdownRendererProps { memoName?: string; /** Collection page that opened the memo detail. */ parentPage?: string; - parentScope?: MemoOriginScope; /** Whether the memo is rendered as a collapsed feed card. */ compact?: boolean; } @@ -67,7 +65,6 @@ export const MemoMarkdownRendererCore = ({ resolvedMentionUsernames, memoName, parentPage, - parentScope, compact, mathRemarkPlugins = [], mathRehypePlugins = [], @@ -134,7 +131,7 @@ export const MemoMarkdownRendererCore = ({ // than opening a new tab; everything else is treated as an external link. if (typeof href === "string" && href.startsWith("#")) { return ( - + {children} ); @@ -195,7 +192,6 @@ export const MemoMarkdownRenderer = memo( previous.attachments === next.attachments && previous.memoName === next.memoName && previous.parentPage === next.parentPage && - previous.parentScope === next.parentScope && previous.compact === next.compact && haveEqualResolvedMentions(previous.resolvedMentionUsernames, next.resolvedMentionUsernames), ); diff --git a/web/src/components/MemoContent/index.tsx b/web/src/components/MemoContent/index.tsx index a28192a3..740c5543 100644 --- a/web/src/components/MemoContent/index.tsx +++ b/web/src/components/MemoContent/index.tsx @@ -40,7 +40,6 @@ const MemoContent = (props: MemoContentProps) => { resolvedMentionUsernames={resolvedMentionUsernames} memoName={props.memoName} parentPage={props.parentPage} - parentScope={props.parentScope} compact={Boolean(props.compact)} /> diff --git a/web/src/components/MemoContent/markdown/AnchorLink.tsx b/web/src/components/MemoContent/markdown/AnchorLink.tsx index 75be8db4..b4cd27d2 100644 --- a/web/src/components/MemoContent/markdown/AnchorLink.tsx +++ b/web/src/components/MemoContent/markdown/AnchorLink.tsx @@ -2,7 +2,7 @@ import { Link } from "react-router-dom"; import { markdownStyles } from "@/lib/markdownStyles"; import { cn } from "@/lib/utils"; import { findAnchorTarget } from "@/utils/markdown-manipulation"; -import { createMemoNavigationState, type MemoOriginScope } from "../../MemoView/navigation"; +import { createMemoNavigationState } from "../../MemoView/navigation"; import type { ReactMarkdownProps } from "./types"; interface AnchorLinkProps extends React.AnchorHTMLAttributes, ReactMarkdownProps { @@ -11,7 +11,6 @@ interface AnchorLinkProps extends React.AnchorHTMLAttributes, memoName?: string; /** Collection page that rendered the enclosing memo. */ parentPage?: string; - parentScope?: MemoOriginScope; /** Whether the memo is rendered as a collapsed feed card. */ compact?: boolean; children: React.ReactNode; @@ -26,17 +25,7 @@ interface AnchorLinkProps extends React.AnchorHTMLAttributes, * below the fold, so we fall back to navigating to the memo detail page (with the hash), where * MemoDetail scrolls the target into view. */ -export const AnchorLink = ({ - href, - memoName, - parentPage, - parentScope, - compact, - children, - className, - node: _node, - ...props -}: AnchorLinkProps) => { +export const AnchorLink = ({ href, memoName, parentPage, compact, children, className, node: _node, ...props }: AnchorLinkProps) => { const handleClick = (event: React.MouseEvent) => { if (compact) return; // Let the link navigate to the detail page. const id = decodeURIComponent(href.slice(1)); @@ -57,7 +46,7 @@ export const AnchorLink = ({ return ( { const { ref, title } = useOverflowTitle(snippet); @@ -74,7 +71,7 @@ const BacklinkRow = ({ aria-label={`${referencedByLabel}: ${snippet}`} className={cn(SIDEBAR_ROW_CLASSES, "text-muted-foreground hover:bg-sidebar-accent/65 hover:text-foreground")} to={`/${relatedMemo.name}`} - state={parentPage && parentScope ? createMemoNavigationState(parentPage, parentScope) : undefined} + state={parentPage ? createMemoNavigationState(parentPage) : undefined} title={title} viewTransition > @@ -90,7 +87,6 @@ const MemoDetailSidebar = ({ memo, parentMemo, parentPage, - parentScope, hasExplicitOrigin = false, commentCount, className, @@ -219,7 +215,7 @@ const MemoDetailSidebar = ({ aria-label={`${t("memo.parent-memo")}: ${parentSnippet}`} className={cn(SIDEBAR_ROW_CLASSES, "text-muted-foreground hover:bg-sidebar-accent/65 hover:text-foreground")} to={`/${parentMemo.name}`} - state={parentPage && parentScope ? createMemoNavigationState(parentPage, parentScope) : undefined} + state={parentPage ? createMemoNavigationState(parentPage) : undefined} title={parentSnippet} viewTransition > @@ -235,7 +231,6 @@ const MemoDetailSidebar = ({ relation={relation} snippet={backlinkSnippet(relation)} parentPage={parentPage} - parentScope={parentScope} referencedByLabel={t("common.referenced-by")} /> ); diff --git a/web/src/components/MemoMetadata/Relation/RelationCard.tsx b/web/src/components/MemoMetadata/Relation/RelationCard.tsx index 83cb6d27..4cfeba9f 100644 --- a/web/src/components/MemoMetadata/Relation/RelationCard.tsx +++ b/web/src/components/MemoMetadata/Relation/RelationCard.tsx @@ -1,21 +1,20 @@ import MemoSnippetLink from "@/components/MemoView/components/MemoSnippetLink"; -import { createMemoNavigationState, type MemoOriginScope } from "@/components/MemoView/navigation"; +import { createMemoNavigationState } from "@/components/MemoView/navigation"; import type { MemoRelation_Memo } from "@/types/proto/api/v1/memo_service_pb"; interface RelationCardProps { memo: MemoRelation_Memo; parentPage?: string; - parentScope?: MemoOriginScope; className?: string; } -const RelationCard = ({ memo, parentPage, parentScope, className }: RelationCardProps) => { +const RelationCard = ({ memo, parentPage, className }: RelationCardProps) => { return ( ); diff --git a/web/src/components/MemoMetadata/Relation/RelationListView.tsx b/web/src/components/MemoMetadata/Relation/RelationListView.tsx index 1e1c7e3e..3030080c 100644 --- a/web/src/components/MemoMetadata/Relation/RelationListView.tsx +++ b/web/src/components/MemoMetadata/Relation/RelationListView.tsx @@ -1,7 +1,6 @@ import { LinkIcon, MilestoneIcon } from "lucide-react"; import { useMemo, useState } from "react"; import MetadataSection from "@/components/MemoMetadata/MetadataSection"; -import type { MemoOriginScope } from "@/components/MemoView/navigation"; import { useNearViewport } from "@/hooks/useNearViewport"; import type { MemoRelation } from "@/types/proto/api/v1/memo_service_pb"; import { useTranslate } from "@/utils/i18n"; @@ -13,11 +12,10 @@ interface RelationListViewProps { relations: MemoRelation[]; currentMemoName?: string; parentPage?: string; - parentScope?: MemoOriginScope; className?: string; } -function RelationListView({ relations, currentMemoName, parentPage, parentScope, className }: RelationListViewProps) { +function RelationListView({ relations, currentMemoName, parentPage, className }: RelationListViewProps) { const t = useTranslate(); const [activeTab, setActiveTab] = useState<"referencing" | "referenced">("referencing"); const { ref: viewportRef, isNearViewport } = useNearViewport(); @@ -81,12 +79,7 @@ function RelationListView({ relations, currentMemoName, parentPage, parentScope, return null; } return ( - + ); })} diff --git a/web/src/components/MemoPreview/MemoPreview.tsx b/web/src/components/MemoPreview/MemoPreview.tsx index 279c420b..2663071e 100644 --- a/web/src/components/MemoPreview/MemoPreview.tsx +++ b/web/src/components/MemoPreview/MemoPreview.tsx @@ -29,7 +29,6 @@ const STUB_CONTEXT: MemoViewContextValue = { creator: undefined, currentUser: undefined, parentPage: "/", - parentScope: "all", cardWidth: 0, isArchived: false, readonly: true, diff --git a/web/src/components/MemoView/MemoView.tsx b/web/src/components/MemoView/MemoView.tsx index af1ae51e..e9904f7a 100644 --- a/web/src/components/MemoView/MemoView.tsx +++ b/web/src/components/MemoView/MemoView.tsx @@ -25,7 +25,7 @@ import { MemoBody, MemoCommentListView, MemoHeader } from "./components"; import { MEMO_CARD_BASE_CLASSES } from "./constants"; import { useImagePreview } from "./hooks"; import { computeCommentAmount, MemoViewContext } from "./MemoViewContext"; -import { isMemoDetailPath, resolveMemoOrigin } from "./navigation"; +import { isMemoDetailPath, resolveMemoParentPage } from "./navigation"; import type { MemoViewHandle, MemoViewProps } from "./types"; const MemoShareImageDialog = lazyWithReload(() => import("../MemoActionMenu/MemoShareImageDialog")); @@ -36,7 +36,6 @@ const MemoView = forwardRef((props, ref) => { memo: memoData, className, parentPage: parentPageProp, - parentScope: parentScopeProp, compact, timeDisplay, showCreator, @@ -55,9 +54,8 @@ const MemoView = forwardRef((props, ref) => { const isArchived = memoData.state === State.ARCHIVED; const readonly = memoData.creator !== currentUser?.name && !isSuperUser(currentUser); const location = useLocation(); - const { parentPage, parentScope } = resolveMemoOrigin({ + const parentPage = resolveMemoParentPage({ explicitParentPage: parentPageProp, - explicitParentScope: parentScopeProp, pathname: location.pathname, search: location.search, memoName: memoData.name, @@ -135,7 +133,6 @@ const MemoView = forwardRef((props, ref) => { creator, currentUser, parentPage, - parentScope, cardWidth, isArchived, readonly, @@ -150,7 +147,6 @@ const MemoView = forwardRef((props, ref) => { creator, currentUser, parentPage, - parentScope, cardWidth, isArchived, readonly, diff --git a/web/src/components/MemoView/MemoViewContext.tsx b/web/src/components/MemoView/MemoViewContext.tsx index f682b1d6..432692f7 100644 --- a/web/src/components/MemoView/MemoViewContext.tsx +++ b/web/src/components/MemoView/MemoViewContext.tsx @@ -7,14 +7,13 @@ import { MemoRelation_Type } from "@/types/proto/api/v1/memo_service_pb"; import type { User } from "@/types/proto/api/v1/user_service_pb"; import type { PreviewMediaItem } from "@/utils/media-item"; import { RELATIVE_TIME_THRESHOLD_MS } from "./constants"; -import { isMemoDetailPath, type MemoOriginScope } from "./navigation"; +import { isMemoDetailPath } from "./navigation"; export interface MemoViewContextValue { memo: Memo; creator: User | undefined; currentUser: User | undefined; parentPage: string; - parentScope: MemoOriginScope; cardWidth: number; isArchived: boolean; readonly: boolean; diff --git a/web/src/components/MemoView/components/MemoBody.tsx b/web/src/components/MemoView/components/MemoBody.tsx index c73ee458..ccd05fe2 100644 --- a/web/src/components/MemoView/components/MemoBody.tsx +++ b/web/src/components/MemoView/components/MemoBody.tsx @@ -32,8 +32,7 @@ const BlurOverlay: React.FC<{ onClick?: () => void }> = ({ onClick }) => { }; const MemoBody: React.FC = ({ compact }) => { - const { memo, parentPage, parentScope, showBlurredContent, blurred, readonly, openEditor, openPreview, toggleBlurVisibility } = - useMemoViewContext(); + const { memo, parentPage, showBlurredContent, blurred, readonly, openEditor, openPreview, toggleBlurVisibility } = useMemoViewContext(); const { handleMemoContentClick, handleMemoContentDoubleClick } = useMemoHandlers({ readonly, openEditor, openPreview }); @@ -58,7 +57,6 @@ const MemoBody: React.FC = ({ compact }) => { = ({ compact }) => { compact={Boolean(compact)} /> - + {memo.location && } diff --git a/web/src/components/MemoView/components/MemoCommentListView.tsx b/web/src/components/MemoView/components/MemoCommentListView.tsx index 5b06b0b6..0e6f900b 100644 --- a/web/src/components/MemoView/components/MemoCommentListView.tsx +++ b/web/src/components/MemoView/components/MemoCommentListView.tsx @@ -10,7 +10,7 @@ import { useMemoViewContext, useMemoViewDerived } from "../MemoViewContext"; import { createMemoNavigationState } from "../navigation"; const MemoCommentListView: React.FC = () => { - const { memo, parentPage, parentScope } = useMemoViewContext(); + const { memo, parentPage } = useMemoViewContext(); const { isInMemoDetailPage, commentAmount } = useMemoViewDerived(); const { ref: viewportRef, isNearViewport } = useNearViewport(); @@ -32,7 +32,7 @@ const MemoCommentListView: React.FC = () => { Comments{commentAmount > 1 ? ` (${commentAmount})` : ""} View all @@ -46,7 +46,7 @@ const MemoCommentListView: React.FC = () => { diff --git a/web/src/components/MemoView/components/MemoHeader.tsx b/web/src/components/MemoView/components/MemoHeader.tsx index ca7abe08..7cbf3f29 100644 --- a/web/src/components/MemoView/components/MemoHeader.tsx +++ b/web/src/components/MemoView/components/MemoHeader.tsx @@ -29,15 +29,15 @@ const MemoHeader: React.FC = ({ timeDisplay = "relative", showC const t = useTranslate(); const [reactionSelectorOpen, setReactionSelectorOpen] = useState(false); - const { memo, creator, currentUser, parentPage, parentScope, isArchived, readonly, openEditor } = useMemoViewContext(); + const { memo, creator, currentUser, parentPage, isArchived, readonly, openEditor } = useMemoViewContext(); const { createTime, updateTime, displayTime: memoDisplayTime, isDisplayingUpdatedTime, relativeTimeFormat } = useMemoViewDerived(); const { newMemoName } = useNewMemo(); const visibilityOption = getVisibilityOption(memo.visibility); const navigateTo = useNavigateTo(); const handleGotoMemoDetailPage = useCallback(() => { - navigateTo(`/${memo.name}`, { state: createMemoNavigationState(parentPage, parentScope) }); - }, [memo.name, parentPage, parentScope, navigateTo]); + navigateTo(`/${memo.name}`, { state: createMemoNavigationState(parentPage) }); + }, [memo.name, parentPage, navigateTo]); const { unpinMemo } = useMemoActions(memo); diff --git a/web/src/components/MemoView/navigation.ts b/web/src/components/MemoView/navigation.ts index be0a9931..9cf8ce45 100644 --- a/web/src/components/MemoView/navigation.ts +++ b/web/src/components/MemoView/navigation.ts @@ -1,11 +1,8 @@ -import { getProfileUsername, isMemoScopeRoute } from "@/lib/memo-views"; -import { ROUTES, resolveCollectionRoute } from "@/router/routes"; - -export type MemoOriginScope = "all" | "preserve"; +import { getProfileUsername } from "@/lib/memo-views"; +import { ROUTES } from "@/router/routes"; export interface MemoNavigationState { from: string; - fromScope: MemoOriginScope; } interface ResolveMemoDetailOriginOptions { @@ -14,7 +11,6 @@ interface ResolveMemoDetailOriginOptions { interface ResolveMemoParentPageOptions { explicitParentPage?: string; - explicitParentScope?: MemoOriginScope; pathname: string; search: string; memoName: string; @@ -40,32 +36,13 @@ export const isMemoResourcePath = (pathname: string): boolean => { return memoID.length > 0 && !memoID.includes("/"); }; -/** Whether this origin represents a collection whose complete URL should be preserved. */ -export const isMemoCollectionOrigin = (page: string): boolean => { - const pathname = page.split(/[?#]/, 1)[0] || ROUTES.HOME; - return isMemoScopeRoute(pathname) || resolveCollectionRoute(pathname).isCollection; -}; +export const createMemoNavigationState = (from: string): MemoNavigationState => ({ from }); -export const createMemoNavigationState = (from: string, fromScope: MemoOriginScope): MemoNavigationState => ({ from, fromScope }); - -/** Reads router state while remaining compatible with older `{ from }` links. */ -export const resolveMemoDetailOrigin = ( - state: unknown, - options: ResolveMemoDetailOriginOptions = {}, -): { parentPage: string; parentScope: MemoOriginScope } => { - const value = state && typeof state === "object" ? (state as { from?: unknown; fromScope?: unknown }) : undefined; +/** Reads the origin page out of router state; without one, a detail returns to its collection. */ +export const resolveMemoDetailOrigin = (state: unknown, options: ResolveMemoDetailOriginOptions = {}): string => { + const value = state && typeof state === "object" ? (state as { from?: unknown }) : undefined; const explicitParentPage = typeof value?.from === "string" ? value.from : undefined; - const hasExplicitParent = explicitParentPage !== undefined; - const parentPage = explicitParentPage || (options.memoArchived ? ROUTES.ARCHIVED : ROUTES.HOME); - const parentScope = - value?.fromScope === "all" || value?.fromScope === "preserve" - ? value.fromScope - : hasExplicitParent && isMemoCollectionOrigin(parentPage) - ? "preserve" - : options.memoArchived - ? "preserve" - : "all"; - return { parentPage, parentScope }; + return explicitParentPage || (options.memoArchived ? ROUTES.ARCHIVED : ROUTES.HOME); }; /** Whether the current route is the canonical detail page for this memo. */ @@ -79,33 +56,12 @@ export const isMemoDetailPath = (pathname: string, memoName: string): boolean => * Captures the list page that opened a memo so canonical detail routes can * return to the real collection lens instead of assuming Home. */ -export const resolveMemoOrigin = ({ - explicitParentPage, - explicitParentScope, - pathname, - search, - memoName, -}: ResolveMemoParentPageOptions): { parentPage: string; parentScope: MemoOriginScope } => { - if (explicitParentPage !== undefined) { - const parentPage = explicitParentPage || ROUTES.HOME; - return { - parentPage, - parentScope: explicitParentScope ?? (isMemoCollectionOrigin(parentPage) ? "preserve" : "all"), - }; - } - - if (isMemoDetailPath(pathname, memoName)) { - return { parentPage: ROUTES.HOME, parentScope: "all" }; - } - - return { - parentPage: `${pathname}${search}`, - parentScope: isMemoCollectionOrigin(pathname) ? "preserve" : "all", - }; +export const resolveMemoParentPage = ({ explicitParentPage, pathname, search, memoName }: ResolveMemoParentPageOptions): string => { + if (explicitParentPage !== undefined) return explicitParentPage || ROUTES.HOME; + if (isMemoDetailPath(pathname, memoName)) return ROUTES.HOME; + return `${pathname}${search}`; }; -export const resolveMemoParentPage = (options: ResolveMemoParentPageOptions): string => resolveMemoOrigin(options).parentPage; - /** Replaces only the memo filter while preserving the rest of the origin query. */ export const withMemoFilter = (page: string, filter: string): string => { const [pathAndSearch] = page.split("#", 1); diff --git a/web/src/components/MemoView/types.ts b/web/src/components/MemoView/types.ts index 4857ebbe..487b992f 100644 --- a/web/src/components/MemoView/types.ts +++ b/web/src/components/MemoView/types.ts @@ -1,5 +1,4 @@ import type { Memo } from "@/types/proto/api/v1/memo_service_pb"; -import type { MemoOriginScope } from "./navigation"; /** How the header names the memo's time: relative to now, or just the clock time for lists that already name the day. */ export type MemoTimeDisplay = "relative" | "time"; @@ -14,7 +13,6 @@ export interface MemoViewProps { showSpace?: boolean; className?: string; parentPage?: string; - parentScope?: MemoOriginScope; shareImageDialogOpen?: boolean; onShareImageDialogOpenChange?: (open: boolean) => void; } diff --git a/web/src/components/UserMemoMap/UserMemoMap.tsx b/web/src/components/UserMemoMap/UserMemoMap.tsx index 857913c9..a14467d6 100644 --- a/web/src/components/UserMemoMap/UserMemoMap.tsx +++ b/web/src/components/UserMemoMap/UserMemoMap.tsx @@ -157,7 +157,7 @@ const UserMemoMap = ({ creator, className }: Props) => { Open diff --git a/web/src/contexts/AppSidebarContext.tsx b/web/src/contexts/AppSidebarContext.tsx index 9cb25f3c..64213502 100644 --- a/web/src/contexts/AppSidebarContext.tsx +++ b/web/src/contexts/AppSidebarContext.tsx @@ -1,6 +1,5 @@ import { createContext, type ReactNode, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react"; import { useLocation } from "react-router-dom"; -import type { MemoOriginScope } from "@/components/MemoView/navigation"; import type { PrimaryMemoScope } from "@/lib/memo-views"; import type { Memo } from "@/types/proto/api/v1/memo_service_pb"; @@ -11,7 +10,6 @@ export interface MemoDetailSidebarDescriptor { memo: Memo; parentMemo?: Memo; from?: string; - fromScope?: MemoOriginScope; hasExplicitOrigin?: boolean; commentCount?: number; readonly?: boolean; diff --git a/web/src/lib/memo-views.ts b/web/src/lib/memo-views.ts index d162fcc1..52c31ea6 100644 --- a/web/src/lib/memo-views.ts +++ b/web/src/lib/memo-views.ts @@ -41,8 +41,6 @@ export const isCalendarRoute = (pathname: string): boolean => { /** * Routes that render a memo collection the sidebar can narrow: the scope routes, a user * profile and the calendar. Views, calendar days and tags apply in place on all of them. - * This is a different question from `isMemoCollectionOrigin` (MemoView/navigation.ts), - * which asks whether a memo origin is a collection whose full URL is worth returning to. */ export const isMemoCollectionRoute = (pathname: string): boolean => isMemoScopeRoute(pathname) || getProfileUsername(pathname) !== undefined || isCalendarRoute(pathname); diff --git a/web/src/pages/MemoDetail.tsx b/web/src/pages/MemoDetail.tsx index 630e4db0..9a569f4a 100644 --- a/web/src/pages/MemoDetail.tsx +++ b/web/src/pages/MemoDetail.tsx @@ -6,7 +6,7 @@ import MemoCommentSection, { type MemoCommentSectionHandle } from "@/components/ import { MentionResolutionProvider } from "@/components/MemoContent/MentionResolutionContext"; import MemoView, { type MemoViewHandle } from "@/components/MemoView"; import { computeCommentAmount } from "@/components/MemoView/MemoViewContext"; -import { createMemoNavigationState, type MemoOriginScope, resolveMemoDetailOrigin } from "@/components/MemoView/navigation"; +import { createMemoNavigationState, resolveMemoDetailOrigin } from "@/components/MemoView/navigation"; import { useAppSidebar } from "@/contexts/AppSidebarContext"; import { useAuth } from "@/contexts/AuthContext"; import { useInstance } from "@/contexts/InstanceContext"; @@ -25,7 +25,6 @@ const MemoSidebarRegistration = ({ memo, parentMemo, from, - fromScope, hasExplicitOrigin, commentCount, readonly, @@ -37,7 +36,6 @@ const MemoSidebarRegistration = ({ memo: Memo; parentMemo?: Memo; from: string; - fromScope: MemoOriginScope; hasExplicitOrigin: boolean; commentCount?: number; readonly: boolean; @@ -53,7 +51,6 @@ const MemoSidebarRegistration = ({ memo, parentMemo, from, - fromScope, hasExplicitOrigin, commentCount, readonly, @@ -65,7 +62,6 @@ const MemoSidebarRegistration = ({ }, [ commentCount, from, - fromScope, hasExplicitOrigin, memo, onCommentCreate, @@ -113,8 +109,7 @@ const MemoDetail = () => { const hasExplicitOrigin = !!locationState && typeof locationState === "object" && typeof (locationState as { from?: unknown }).from === "string"; const resolvedOrigin = resolveMemoDetailOrigin(locationState, { memoArchived: memo?.state === State.ARCHIVED }); - const parentPage = !hasExplicitOrigin && !currentUser && memo?.state !== State.ARCHIVED ? ROUTES.EXPLORE : resolvedOrigin.parentPage; - const parentScope = resolvedOrigin.parentScope; + const parentPage = !hasExplicitOrigin && !currentUser && memo?.state !== State.ARCHIVED ? ROUTES.EXPLORE : resolvedOrigin; const memoName = memo?.name ?? memoNameFromParams; const displayMemo = useReactMemo(() => { if (!memo) return undefined; @@ -197,7 +192,6 @@ const MemoDetail = () => { memo={displayMemo} parentMemo={parentMemo} from={parentPage} - fromScope={parentScope} hasExplicitOrigin={hasExplicitOrigin} commentCount={isShareMode ? undefined : commentCount} readonly={isShareMode} @@ -213,7 +207,7 @@ const MemoDetail = () => { @@ -227,7 +221,6 @@ const MemoDetail = () => { memo={displayMemo} compact={false} parentPage={parentPage} - parentScope={parentScope} shareImageDialogOpen={shareImageDialogOpen} showCreator showVisibility @@ -242,7 +235,6 @@ const MemoDetail = () => { comments={comments} commentCount={commentCount} parentPage={parentPage} - parentScope={parentScope} hasMoreComments={hasNextComments} isFetchingMoreComments={isFetchingNextComments} onLoadMoreComments={fetchNextComments} diff --git a/web/tests/attachment-library-source-link.test.tsx b/web/tests/attachment-library-source-link.test.tsx index 8af19305..884be2bf 100644 --- a/web/tests/attachment-library-source-link.test.tsx +++ b/web/tests/attachment-library-source-link.test.tsx @@ -67,9 +67,7 @@ describe("", () => { fireEvent.click(memoLink); expect(onPreview).not.toHaveBeenCalled(); - expect(screen.getByTestId("location-state")).toHaveTextContent( - '{"from":"/spaces/product/attachments?filter=tagSearch%3Awork","fromScope":"preserve"}', - ); + expect(screen.getByTestId("location-state")).toHaveTextContent('{"from":"/spaces/product/attachments?filter=tagSearch%3Awork"}'); }); it("keeps video duration inside the existing play badge", () => { diff --git a/web/tests/memo-content-footnotes.test.tsx b/web/tests/memo-content-footnotes.test.tsx index a4db6103..eed1ede1 100644 --- a/web/tests/memo-content-footnotes.test.tsx +++ b/web/tests/memo-content-footnotes.test.tsx @@ -24,7 +24,6 @@ const renderFootnote = (compact = false, parentPage?: string) => resolvedMentionUsernames={new Set()} memoName="memos/abc123" parentPage={parentPage} - parentScope={parentPage ? "preserve" : undefined} compact={compact} /> diff --git a/web/tests/memo-detail-sidebar.test.tsx b/web/tests/memo-detail-sidebar.test.tsx index 73bf6bf2..f9c2bdf0 100644 --- a/web/tests/memo-detail-sidebar.test.tsx +++ b/web/tests/memo-detail-sidebar.test.tsx @@ -83,7 +83,6 @@ describe("MemoDetailSidebar", () => { memo={memo} parentMemo={parentMemo} parentPage="/explore?filter=tagSearch%3Awork" - parentScope="preserve" hasExplicitOrigin commentCount={3} onEdit={onEdit} @@ -144,9 +143,7 @@ describe("MemoDetailSidebar", () => { relations: [createIncomingReference("memos/public")], }); - renderSidebar( - , - ); + renderSidebar(); expect(screen.queryByRole("button", { name: "common.edit" })).not.toBeInTheDocument(); expect(screen.getByRole("button", { name: "memo.comment.write-a-comment" })).toBeInTheDocument(); @@ -163,9 +160,7 @@ describe("MemoDetailSidebar", () => { content: "Body", }); - renderSidebar( - , - ); + renderSidebar(); expect(screen.getByRole("link", { name: "memo.go-to:common.home" })).toHaveAttribute("href", "/"); expect(screen.queryByText("memo.on-this-memo")).not.toBeInTheDocument(); @@ -187,7 +182,7 @@ describe("MemoDetailSidebar", () => { content: "Body", }); - renderSidebar(); + renderSidebar(); fireEvent.click(screen.getByRole("button", { name: "common.share" })); expect(await screen.findByRole("menuitem", { name: "memo.copy-link" })).toBeInTheDocument(); @@ -202,16 +197,7 @@ describe("MemoDetailSidebar", () => { content: "Archived body", }); - renderSidebar( - , - ); + renderSidebar(); expect(screen.getByRole("link", { name: "memo.go-to:common.archived" })).toBeInTheDocument(); expect(screen.getByRole("link", { name: /memo.comment.self/ })).toHaveTextContent("2"); @@ -237,7 +223,6 @@ describe("MemoDetailSidebar", () => { memo={memo} parentMemo={parentMemo} parentPage="/explore" - parentScope="all" hasExplicitOrigin commentCount={4} forceReadonly diff --git a/web/tests/memo-filter-context.test.tsx b/web/tests/memo-filter-context.test.tsx index a0d96283..9490ee28 100644 --- a/web/tests/memo-filter-context.test.tsx +++ b/web/tests/memo-filter-context.test.tsx @@ -81,7 +81,7 @@ describe("MemoFilterProvider", () => { ); render(); fireEvent.click(screen.getByRole("button", { name: "Select Tasks" })); - const state = { from: origin, fromScope: "preserve" }; + const state = { from: origin }; await act(() => router.navigate("/memos/1", { state })); await waitFor(() => expect(screen.getByTestId("filters")).toHaveTextContent("[]")); expect(router.state.location.search).toBe(""); diff --git a/web/tests/memo-header-navigation.test.tsx b/web/tests/memo-header-navigation.test.tsx index 04fd1f83..e0b83f00 100644 --- a/web/tests/memo-header-navigation.test.tsx +++ b/web/tests/memo-header-navigation.test.tsx @@ -62,7 +62,6 @@ vi.mock("@/components/MemoView/MemoViewContext", () => ({ creator: state.creator, currentUser: state.currentUser, parentPage: "/explore?filter=tagSearch%3Awork", - parentScope: "preserve", isArchived: false, readonly: false, openEditor: vi.fn(), @@ -142,9 +141,7 @@ describe("MemoHeader navigation", () => { expect(timestamp).toHaveFocus(); fireEvent.click(timestamp); - expect(screen.getByTestId("location")).toHaveTextContent( - '/memos/123|{"from":"/explore?filter=tagSearch%3Awork","fromScope":"preserve"}', - ); + expect(screen.getByTestId("location")).toHaveTextContent('/memos/123|{"from":"/explore?filter=tagSearch%3Awork"}'); }); it.each([false, true])("keeps the Space pill beside the timestamp when showCreator=%s", (showCreator) => { diff --git a/web/tests/memo-view-navigation.test.ts b/web/tests/memo-view-navigation.test.ts index c574ffc2..a0f9e162 100644 --- a/web/tests/memo-view-navigation.test.ts +++ b/web/tests/memo-view-navigation.test.ts @@ -1,11 +1,9 @@ import { describe, expect, it } from "vitest"; import { createMemoNavigationState, - isMemoCollectionOrigin, isMemoDetailPath, isMemoResourcePath, resolveMemoDetailOrigin, - resolveMemoOrigin, resolveMemoParentPage, withMemoFilter, } from "@/components/MemoView/navigation"; @@ -32,53 +30,25 @@ describe("memo view navigation", () => { ).toBe("/explore?filter=contentSearch%3Aroadmap"); }); - it("marks collection cards as preserving the remembered scope", () => { - expect(resolveMemoOrigin({ pathname: "/explore", search: "?filter=tagSearch%3Awork", memoName: "memos/123" })).toEqual({ - parentPage: "/explore?filter=tagSearch%3Awork", - parentScope: "preserve", - }); + it("keeps a Profile map origin intact", () => { + expect(resolveMemoParentPage({ pathname: "/u/alice", search: "?view=map", memoName: "memos/123" })).toBe("/u/alice?view=map"); }); - it("marks Profile cards as global without losing their Profile origin", () => { - expect(resolveMemoOrigin({ pathname: "/u/alice", search: "?view=map", memoName: "memos/123" })).toEqual({ - parentPage: "/u/alice?view=map", - parentScope: "all", - }); + it("uses Home for direct and shared resource entries", () => { + expect(resolveMemoDetailOrigin(undefined)).toBe("/"); + expect(resolveMemoDetailOrigin({ unrelated: true })).toBe("/"); }); - it("uses an All Home origin for direct and shared resource entries", () => { - expect(resolveMemoDetailOrigin(undefined)).toEqual({ parentPage: "/", parentScope: "all" }); - expect(resolveMemoDetailOrigin({ unrelated: true })).toEqual({ parentPage: "/", parentScope: "all" }); - }); - - it("uses a user-level Archived origin without changing the remembered Space", () => { - expect(resolveMemoDetailOrigin(undefined, { memoArchived: true })).toEqual({ parentPage: "/archived", parentScope: "preserve" }); + it("uses the user archive for a direct entry to an archived memo", () => { + expect(resolveMemoDetailOrigin(undefined, { memoArchived: true })).toBe("/archived"); }); it("keeps an explicit origin ahead of the archived fallback", () => { - expect(resolveMemoDetailOrigin(createMemoNavigationState("/u/alice", "all"), { memoArchived: true })).toEqual({ - parentPage: "/u/alice", - parentScope: "all", - }); + expect(resolveMemoDetailOrigin(createMemoNavigationState("/u/alice"), { memoArchived: true })).toBe("/u/alice"); }); - it("round-trips an explicit origin policy through router state", () => { - const state = createMemoNavigationState("/", "preserve"); - expect(resolveMemoDetailOrigin(state)).toEqual({ parentPage: "/", parentScope: "preserve" }); - }); - - it("infers policy for legacy from-only router state", () => { - expect(resolveMemoDetailOrigin({ from: "/archived" })).toEqual({ parentPage: "/archived", parentScope: "preserve" }); - expect(resolveMemoDetailOrigin({ from: "/u/alice" })).toEqual({ parentPage: "/u/alice", parentScope: "all" }); - }); - - it.each([ - "/", - "/explore?filter=tagSearch%3Awork", - "/archived", - "/attachments", - ])("recognizes %s as a remembered collection origin", (page) => { - expect(isMemoCollectionOrigin(page)).toBe(true); + it("round-trips an explicit origin through router state", () => { + expect(resolveMemoDetailOrigin(createMemoNavigationState("/explore?filter=tagSearch%3Awork"))).toBe("/explore?filter=tagSearch%3Awork"); }); it.each([ diff --git a/web/tests/space-routes.test.tsx b/web/tests/space-routes.test.tsx index e6fab18a..78552782 100644 --- a/web/tests/space-routes.test.tsx +++ b/web/tests/space-routes.test.tsx @@ -5,7 +5,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; import { resolveQuickFindSubmission } from "@/components/AppSidebar/QuickFindDialog"; import { getRouteActionPolicy, getSidebarRouteKind } from "@/components/AppSidebar/routes"; import { CalendarHeader } from "@/components/CalendarView/CalendarHeader"; -import { resolveMemoDetailOrigin, resolveMemoOrigin } from "@/components/MemoView/navigation"; +import { resolveMemoDetailOrigin, resolveMemoParentPage } from "@/components/MemoView/navigation"; import Calendar from "@/pages/Calendar"; import { buildCollectionPath, getSpaceSwitchPath, resolveCollectionRoute } from "@/router/routes"; import { SpaceRoute } from "@/router/SpaceRoute"; @@ -59,10 +59,10 @@ describe("Space route contract", () => { it("carries the full calendar origin through a permanent memo link", () => { const parentPage = "/spaces/a/calendar/2026/09/06?filter=tagSearch%3Awork"; expect( - resolveMemoOrigin({ pathname: "/spaces/a/calendar/2026/09/06", search: "?filter=tagSearch%3Awork", memoName: "memos/x" }), - ).toEqual({ parentPage, parentScope: "preserve" }); - expect(resolveMemoDetailOrigin({ from: parentPage })).toEqual({ parentPage, parentScope: "preserve" }); - expect(resolveMemoDetailOrigin(undefined)).toEqual({ parentPage: "/", parentScope: "all" }); + resolveMemoParentPage({ pathname: "/spaces/a/calendar/2026/09/06", search: "?filter=tagSearch%3Awork", memoName: "memos/x" }), + ).toBe(parentPage); + expect(resolveMemoDetailOrigin({ from: parentPage })).toBe(parentPage); + expect(resolveMemoDetailOrigin(undefined)).toBe("/"); }); });