mirror of
https://github.com/docmost/docmost.git
synced 2026-05-23 10:42:42 +08:00
fix: page mode toggle no longer overwrites default preference (#1996)
The header edit/read toggle now controls only the current session's mode without saving it as the user's preference. The saved preference (set in profile settings) is applied once on initial load and sticks across page navigations within the session, so navigating to a new page no longer resets the mode mid-session. Fixes #1693
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import { atom } from "jotai";
|
import { atom } from "jotai";
|
||||||
import { Editor } from "@tiptap/core";
|
import { Editor } from "@tiptap/core";
|
||||||
|
import { PageEditMode } from "@/features/user/types/user.types.ts";
|
||||||
|
|
||||||
export const pageEditorAtom = atom<Editor | null>(null);
|
export const pageEditorAtom = atom<Editor | null>(null);
|
||||||
|
|
||||||
@@ -12,3 +13,7 @@ export const yjsConnectionStatusAtom = atom<string>("");
|
|||||||
export const showAiMenuAtom = atom(false);
|
export const showAiMenuAtom = atom(false);
|
||||||
|
|
||||||
export const showLinkMenuAtom = atom(false);
|
export const showLinkMenuAtom = atom(false);
|
||||||
|
|
||||||
|
// Current page's edit mode — initialized from the user's saved preference on
|
||||||
|
// first load, can be toggled locally without persisting to the server.
|
||||||
|
export const currentPageEditModeAtom = atom<PageEditMode>(PageEditMode.Edit);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import classes from "@/features/editor/styles/editor.module.css";
|
import classes from "@/features/editor/styles/editor.module.css";
|
||||||
import React from "react";
|
import React, { useEffect } from "react";
|
||||||
import { TitleEditor } from "@/features/editor/title-editor";
|
import { TitleEditor } from "@/features/editor/title-editor";
|
||||||
import PageEditor from "@/features/editor/page-editor";
|
import PageEditor from "@/features/editor/page-editor";
|
||||||
import {
|
import {
|
||||||
@@ -24,6 +24,7 @@ import { FixedToolbar } from "@/features/editor/components/fixed-toolbar/fixed-t
|
|||||||
import { PageEditMode } from "@/features/user/types/user.types.ts";
|
import { PageEditMode } from "@/features/user/types/user.types.ts";
|
||||||
import useToggleAside from "@/hooks/use-toggle-aside.tsx";
|
import useToggleAside from "@/hooks/use-toggle-aside.tsx";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
import { currentPageEditModeAtom } from "@/features/editor/atoms/editor-atoms.ts";
|
||||||
|
|
||||||
const MemoizedTitleEditor = React.memo(TitleEditor);
|
const MemoizedTitleEditor = React.memo(TitleEditor);
|
||||||
const MemoizedPageEditor = React.memo(PageEditor);
|
const MemoizedPageEditor = React.memo(PageEditor);
|
||||||
@@ -34,6 +35,10 @@ type PageCreator = {
|
|||||||
avatarUrl: string;
|
avatarUrl: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Module-level flag: survives component unmount/remount on page navigation,
|
||||||
|
// reset only on full page reload (i.e. a new app session).
|
||||||
|
let defaultEditModeApplied = false;
|
||||||
|
|
||||||
export interface FullEditorProps {
|
export interface FullEditorProps {
|
||||||
pageId: string;
|
pageId: string;
|
||||||
slugId: string;
|
slugId: string;
|
||||||
@@ -61,9 +66,19 @@ export function FullEditor({
|
|||||||
const fullPageWidth = user.settings?.preferences?.fullPageWidth;
|
const fullPageWidth = user.settings?.preferences?.fullPageWidth;
|
||||||
const editorToolbarEnabled =
|
const editorToolbarEnabled =
|
||||||
user.settings?.preferences?.editorToolbar ?? false;
|
user.settings?.preferences?.editorToolbar ?? false;
|
||||||
|
const [currentPageEditMode, setCurrentPageEditMode] = useAtom(currentPageEditModeAtom);
|
||||||
const userPageEditMode =
|
const userPageEditMode =
|
||||||
user.settings?.preferences?.pageEditMode ?? PageEditMode.Edit;
|
user.settings?.preferences?.pageEditMode ?? PageEditMode.Edit;
|
||||||
const isEditMode = userPageEditMode === PageEditMode.Edit;
|
const isEditMode = currentPageEditMode === PageEditMode.Edit;
|
||||||
|
|
||||||
|
// Apply the user's saved preference only once on initial load, not on every
|
||||||
|
// page navigation — so the mode sticks across navigations within a session.
|
||||||
|
useEffect(() => {
|
||||||
|
if (!defaultEditModeApplied) {
|
||||||
|
setCurrentPageEditMode(userPageEditMode);
|
||||||
|
defaultEditModeApplied = true;
|
||||||
|
}
|
||||||
|
}, [userPageEditMode, setCurrentPageEditMode]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container
|
<Container
|
||||||
|
|||||||
@@ -26,10 +26,11 @@ import {
|
|||||||
collabExtensions,
|
collabExtensions,
|
||||||
mainExtensions,
|
mainExtensions,
|
||||||
} from "@/features/editor/extensions/extensions";
|
} from "@/features/editor/extensions/extensions";
|
||||||
import { useAtom } from "jotai";
|
import { useAtom, useAtomValue } from "jotai";
|
||||||
import useCollaborationUrl from "@/features/editor/hooks/use-collaboration-url";
|
import useCollaborationUrl from "@/features/editor/hooks/use-collaboration-url";
|
||||||
import { currentUserAtom } from "@/features/user/atoms/current-user-atom";
|
import { currentUserAtom } from "@/features/user/atoms/current-user-atom";
|
||||||
import {
|
import {
|
||||||
|
currentPageEditModeAtom,
|
||||||
pageEditorAtom,
|
pageEditorAtom,
|
||||||
yjsConnectionStatusAtom,
|
yjsConnectionStatusAtom,
|
||||||
} from "@/features/editor/atoms/editor-atoms";
|
} from "@/features/editor/atoms/editor-atoms";
|
||||||
@@ -112,8 +113,7 @@ export default function PageEditor({
|
|||||||
const documentState = useDocumentVisibility();
|
const documentState = useDocumentVisibility();
|
||||||
const { pageSlug } = useParams();
|
const { pageSlug } = useParams();
|
||||||
const slugId = extractPageSlugId(pageSlug);
|
const slugId = extractPageSlugId(pageSlug);
|
||||||
const userPageEditMode =
|
const currentPageEditMode = useAtomValue(currentPageEditModeAtom);
|
||||||
currentUser?.user?.settings?.preferences?.pageEditMode ?? PageEditMode.Edit;
|
|
||||||
const canScroll = useCallback(
|
const canScroll = useCallback(
|
||||||
() => Boolean(isComponentMounted.current && editorRef.current),
|
() => Boolean(isComponentMounted.current && editorRef.current),
|
||||||
[isComponentMounted],
|
[isComponentMounted],
|
||||||
@@ -373,19 +373,9 @@ export default function PageEditor({
|
|||||||
return () => clearTimeout(timeout);
|
return () => clearTimeout(timeout);
|
||||||
}, [yjsConnectionStatus, isSynced]);
|
}, [yjsConnectionStatus, isSynced]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Only honor user default page edit mode preference and permissions
|
if (!editor) return;
|
||||||
if (editor) {
|
editor.setEditable(editable && currentPageEditMode === PageEditMode.Edit);
|
||||||
if (userPageEditMode && editable) {
|
}, [currentPageEditMode, editor, editable]);
|
||||||
if (userPageEditMode === PageEditMode.Edit) {
|
|
||||||
editor.setEditable(true);
|
|
||||||
} else if (userPageEditMode === PageEditMode.Read) {
|
|
||||||
editor.setEditable(false);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
editor.setEditable(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [userPageEditMode, editor, editable]);
|
|
||||||
|
|
||||||
const hasConnectedOnceRef = useRef(false);
|
const hasConnectedOnceRef = useRef(false);
|
||||||
const [showStatic, setShowStatic] = useState(true);
|
const [showStatic, setShowStatic] = useState(true);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { Text } from "@tiptap/extension-text";
|
|||||||
import { Placeholder } from "@tiptap/extension-placeholder";
|
import { Placeholder } from "@tiptap/extension-placeholder";
|
||||||
import { useAtomValue } from "jotai";
|
import { useAtomValue } from "jotai";
|
||||||
import {
|
import {
|
||||||
|
currentPageEditModeAtom,
|
||||||
pageEditorAtom,
|
pageEditorAtom,
|
||||||
titleEditorAtom,
|
titleEditorAtom,
|
||||||
} from "@/features/editor/atoms/editor-atoms";
|
} from "@/features/editor/atoms/editor-atoms";
|
||||||
@@ -24,7 +25,6 @@ import { useTranslation } from "react-i18next";
|
|||||||
import EmojiCommand from "@/features/editor/extensions/emoji-command.ts";
|
import EmojiCommand from "@/features/editor/extensions/emoji-command.ts";
|
||||||
import { UpdateEvent } from "@/features/websocket/types";
|
import { UpdateEvent } from "@/features/websocket/types";
|
||||||
import localEmitter from "@/lib/local-emitter.ts";
|
import localEmitter from "@/lib/local-emitter.ts";
|
||||||
import { currentUserAtom } from "@/features/user/atoms/current-user-atom.ts";
|
|
||||||
import { PageEditMode } from "@/features/user/types/user.types.ts";
|
import { PageEditMode } from "@/features/user/types/user.types.ts";
|
||||||
import { searchSpotlight } from "@/features/search/constants.ts";
|
import { searchSpotlight } from "@/features/search/constants.ts";
|
||||||
import { platformModifierKey } from "@/lib";
|
import { platformModifierKey } from "@/lib";
|
||||||
@@ -52,9 +52,7 @@ export function TitleEditor({
|
|||||||
const emit = useQueryEmit();
|
const emit = useQueryEmit();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [activePageId, setActivePageId] = useState(pageId);
|
const [activePageId, setActivePageId] = useState(pageId);
|
||||||
const [currentUser] = useAtom(currentUserAtom);
|
const currentPageEditMode = useAtomValue(currentPageEditModeAtom);
|
||||||
const userPageEditMode =
|
|
||||||
currentUser?.user?.settings?.preferences?.pageEditMode ?? PageEditMode.Edit;
|
|
||||||
|
|
||||||
const titleEditor = useEditor({
|
const titleEditor = useEditor({
|
||||||
extensions: [
|
extensions: [
|
||||||
@@ -172,18 +170,9 @@ export function TitleEditor({
|
|||||||
}, [pageId]);
|
}, [pageId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (titleEditor) {
|
if (!titleEditor) return;
|
||||||
if (userPageEditMode && editable) {
|
titleEditor.setEditable(editable && currentPageEditMode === PageEditMode.Edit);
|
||||||
if (userPageEditMode === PageEditMode.Edit) {
|
}, [currentPageEditMode, titleEditor, editable]);
|
||||||
titleEditor.setEditable(true);
|
|
||||||
} else if (userPageEditMode === PageEditMode.Read) {
|
|
||||||
titleEditor.setEditable(false);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
titleEditor.setEditable(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [userPageEditMode, titleEditor, editable]);
|
|
||||||
|
|
||||||
const openSearchDialog = () => {
|
const openSearchDialog = () => {
|
||||||
const event = new CustomEvent("openFindDialogFromEditor", {});
|
const event = new CustomEvent("openFindDialogFromEditor", {});
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ import {
|
|||||||
yjsConnectionStatusAtom,
|
yjsConnectionStatusAtom,
|
||||||
} from "@/features/editor/atoms/editor-atoms.ts";
|
} from "@/features/editor/atoms/editor-atoms.ts";
|
||||||
import { formattedDate } from "@/lib/time.ts";
|
import { formattedDate } from "@/lib/time.ts";
|
||||||
import { PageStateSegmentedControl } from "@/features/user/components/page-state-pref.tsx";
|
import { PageEditModeToggle } from "@/features/user/components/page-state-pref.tsx";
|
||||||
import MovePageModal from "@/features/page/components/move-page-modal.tsx";
|
import MovePageModal from "@/features/page/components/move-page-modal.tsx";
|
||||||
import { useTimeAgo } from "@/hooks/use-time-ago.tsx";
|
import { useTimeAgo } from "@/hooks/use-time-ago.tsx";
|
||||||
import { PageShareModal } from "@/ee/page-permission";
|
import { PageShareModal } from "@/ee/page-permission";
|
||||||
@@ -91,7 +91,7 @@ export default function PageHeaderMenu({ readOnly }: PageHeaderMenuProps) {
|
|||||||
<>
|
<>
|
||||||
<ConnectionWarning />
|
<ConnectionWarning />
|
||||||
|
|
||||||
{!readOnly && <PageStateSegmentedControl size="xs" />}
|
{!readOnly && <PageEditModeToggle size="xs" />}
|
||||||
|
|
||||||
<PageShareModal readOnly={readOnly} />
|
<PageShareModal readOnly={readOnly} />
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import React, { useCallback, useEffect, useState } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { PageEditMode } from "@/features/user/types/user.types.ts";
|
import { PageEditMode } from "@/features/user/types/user.types.ts";
|
||||||
import { ResponsiveSettingsRow, ResponsiveSettingsContent, ResponsiveSettingsControl } from "@/components/ui/responsive-settings-row";
|
import { ResponsiveSettingsRow, ResponsiveSettingsContent, ResponsiveSettingsControl } from "@/components/ui/responsive-settings-row";
|
||||||
|
import { currentPageEditModeAtom } from "@/features/editor/atoms/editor-atoms.ts";
|
||||||
|
|
||||||
export default function PageStatePref() {
|
export default function PageStatePref() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -71,3 +72,24 @@ export function PageStateSegmentedControl({
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Header variant: updates the current page's mode locally without persisting
|
||||||
|
// the preference to the server.
|
||||||
|
export function PageEditModeToggle({ size }: { size?: MantineSize }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [currentPageEditMode, setCurrentPageEditMode] = useAtom(
|
||||||
|
currentPageEditModeAtom,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SegmentedControl
|
||||||
|
size={size}
|
||||||
|
value={currentPageEditMode}
|
||||||
|
onChange={(v) => setCurrentPageEditMode(v as PageEditMode)}
|
||||||
|
data={[
|
||||||
|
{ label: t("Edit"), value: PageEditMode.Edit },
|
||||||
|
{ label: t("Read"), value: PageEditMode.Read },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user