From 06b509213cf506c7ddd6f6eaabd2a648f8d5dbca Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Thu, 23 Apr 2026 19:52:55 +0530 Subject: [PATCH] feat(editor): add mode toggle functionality and improve editor state management --- .../components/editor-panel/editor-panel.tsx | 123 +++++++++++++----- .../components/editor/plate-editor.tsx | 5 +- .../editor/plugins/fixed-toolbar-kit.tsx | 24 +++- 3 files changed, 116 insertions(+), 36 deletions(-) diff --git a/surfsense_web/components/editor-panel/editor-panel.tsx b/surfsense_web/components/editor-panel/editor-panel.tsx index b83c4b1d7..0170d13da 100644 --- a/surfsense_web/components/editor-panel/editor-panel.tsx +++ b/surfsense_web/components/editor-panel/editor-panel.tsx @@ -88,7 +88,7 @@ export function EditorPanelContent({ const [error, setError] = useState(null); const [saving, setSaving] = useState(false); const [downloading, setDownloading] = useState(false); - const [isSourceEditing, setIsSourceEditing] = useState(false); + const [isEditing, setIsEditing] = useState(false); const [editedMarkdown, setEditedMarkdown] = useState(null); const [localFileContent, setLocalFileContent] = useState(""); @@ -111,7 +111,7 @@ export function EditorPanelContent({ setEditedMarkdown(null); setLocalFileContent(""); setHasCopied(false); - setIsSourceEditing(false); + setIsEditing(false); initialLoadDone.current = false; changeCountRef.current = 0; @@ -295,10 +295,18 @@ export function EditorPanelContent({ : false; const hasUnsavedChanges = editedMarkdown !== null; const showDesktopHeader = !!onClose; - const isSourceCodeMode = editorRenderMode === "source_code"; - const showEditingActions = isSourceCodeMode && isSourceEditing; + const showEditingActions = isEditableType && isEditing; const localFileLanguage = inferMonacoLanguageFromPath(localFilePath); + const handleCancelEditing = useCallback(() => { + const savedContent = editorDoc?.source_markdown ?? ""; + markdownRef.current = savedContent; + setLocalFileContent(savedContent); + setEditedMarkdown(null); + changeCountRef.current = 0; + setIsEditing(false); + }, [editorDoc?.source_markdown]); + return ( <> {showDesktopHeader ? ( @@ -323,13 +331,7 @@ export function EditorPanelContent({ variant="ghost" size="sm" className="h-6 px-2 text-xs" - onClick={() => { - const savedContent = editorDoc?.source_markdown ?? ""; - markdownRef.current = savedContent; - setLocalFileContent(savedContent); - setEditedMarkdown(null); - setIsSourceEditing(false); - }} + onClick={handleCancelEditing} disabled={saving} > Cancel @@ -340,7 +342,7 @@ export function EditorPanelContent({ className="relative h-6 w-[56px] px-0 text-xs" onClick={async () => { const saveSucceeded = await handleSave({ silent: true }); - if (saveSucceeded) setIsSourceEditing(false); + if (saveSucceeded) setIsEditing(false); }} disabled={saving || !hasUnsavedChanges} > @@ -364,15 +366,19 @@ export function EditorPanelContent({ {hasCopied ? "Copied file contents" : "Copy file contents"} - {isSourceCodeMode && ( + {isEditableType && ( )} @@ -389,11 +395,69 @@ export function EditorPanelContent({

{displayTitle}

- {!isLocalFileMode && editorDoc?.document_type && documentId && ( - + {showEditingActions ? ( + <> + + + + ) : ( + <> + + {isEditableType && ( + + )} + {!isLocalFileMode && editorDoc?.document_type && documentId && ( + + )} + )}
@@ -489,7 +553,7 @@ export function EditorPanelContent({ onSave={() => { void handleSave({ silent: true }); }} - readOnly={!isSourceEditing} + readOnly={!isEditing} onChange={(next) => { markdownRef.current = next; setLocalFileContent(next); @@ -500,19 +564,15 @@ export function EditorPanelContent({ ) : isEditableType ? ( { - void handleSave(); - }} - hasUnsavedChanges={editedMarkdown !== null} - isSaving={saving} - defaultEditing={true} + allowModeToggle={false} + defaultEditing={isEditing} className="[&_[role=toolbar]]:!bg-sidebar" /> ) : ( @@ -561,6 +621,8 @@ function MobileEditorDrawer() { const panelState = useAtomValue(editorPanelAtom); const closePanel = useSetAtom(closeEditorPanelAtom); + if (panelState.kind === "local_file") return null; + const hasTarget = panelState.kind === "document" ? !!panelState.documentId && !!panelState.searchSpaceId @@ -604,6 +666,7 @@ export function EditorPanel() { : !!panelState.localFilePath; if (!panelState.isOpen || !hasTarget) return null; + if (!isDesktop && panelState.kind === "local_file") return null; if (isDesktop) { return ; @@ -620,7 +683,7 @@ export function MobileEditorPanel() { ? !!panelState.documentId && !!panelState.searchSpaceId : !!panelState.localFilePath; - if (isDesktop || !panelState.isOpen || !hasTarget) return null; + if (isDesktop || !panelState.isOpen || !hasTarget || panelState.kind === "local_file") return null; return ; } diff --git a/surfsense_web/components/editor/plate-editor.tsx b/surfsense_web/components/editor/plate-editor.tsx index 61f84126c..371326bd3 100644 --- a/surfsense_web/components/editor/plate-editor.tsx +++ b/surfsense_web/components/editor/plate-editor.tsx @@ -42,6 +42,8 @@ export interface PlateEditorProps { hasUnsavedChanges?: boolean; /** Whether a save is in progress */ isSaving?: boolean; + /** Whether edit/view mode toggle UI should be available in toolbars. */ + allowModeToggle?: boolean; /** Start the editor in editing mode instead of viewing mode. Ignored when readOnly is true. */ defaultEditing?: boolean; /** @@ -91,6 +93,7 @@ export function PlateEditor({ onSave, hasUnsavedChanges = false, isSaving = false, + allowModeToggle = true, defaultEditing = false, preset = "full", extraPlugins = [], @@ -174,7 +177,7 @@ export function PlateEditor({ }, [html, markdown, editor]); // When not forced read-only, the user can toggle between editing/viewing. - const canToggleMode = !readOnly; + const canToggleMode = !readOnly && allowModeToggle; const contextProviderValue = useMemo( () => ({ diff --git a/surfsense_web/components/editor/plugins/fixed-toolbar-kit.tsx b/surfsense_web/components/editor/plugins/fixed-toolbar-kit.tsx index 85e0a08f2..8b776a456 100644 --- a/surfsense_web/components/editor/plugins/fixed-toolbar-kit.tsx +++ b/surfsense_web/components/editor/plugins/fixed-toolbar-kit.tsx @@ -1,19 +1,33 @@ "use client"; import { createPlatePlugin } from "platejs/react"; +import { useEditorReadOnly } from "platejs/react"; +import { useEditorSave } from "@/components/editor/editor-save-context"; import { FixedToolbar } from "@/components/ui/fixed-toolbar"; import { FixedToolbarButtons } from "@/components/ui/fixed-toolbar-buttons"; +function ConditionalFixedToolbar() { + const readOnly = useEditorReadOnly(); + const { onSave, hasUnsavedChanges, canToggleMode } = useEditorSave(); + + const hasVisibleControls = + !readOnly || canToggleMode || (!!onSave && hasUnsavedChanges && !readOnly); + + if (!hasVisibleControls) return null; + + return ( + + + + ); +} + export const FixedToolbarKit = [ createPlatePlugin({ key: "fixed-toolbar", render: { - beforeEditable: () => ( - - - - ), + beforeEditable: () => , }, }), ];