From a1d3356bf55b8ebb4f70d37e2987f115609afde6 Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Thu, 23 Apr 2026 20:13:29 +0530 Subject: [PATCH] feat(editor): add reserveToolbarSpace option to enhance toolbar visibility management --- .../components/editor-panel/editor-panel.tsx | 1 + .../components/editor/editor-save-context.tsx | 3 +++ .../components/editor/plate-editor.tsx | 6 +++++- .../editor/plugins/fixed-toolbar-kit.tsx | 11 +++++++++-- .../components/report-panel/report-panel.tsx | 19 ++++++++++++++++++- 5 files changed, 36 insertions(+), 4 deletions(-) diff --git a/surfsense_web/components/editor-panel/editor-panel.tsx b/surfsense_web/components/editor-panel/editor-panel.tsx index 50ee158c4..d125ec143 100644 --- a/surfsense_web/components/editor-panel/editor-panel.tsx +++ b/surfsense_web/components/editor-panel/editor-panel.tsx @@ -572,6 +572,7 @@ export function EditorPanelContent({ placeholder="Start writing..." editorVariant="default" allowModeToggle={false} + reserveToolbarSpace defaultEditing={isEditing} className="[&_[role=toolbar]]:!bg-sidebar" /> diff --git a/surfsense_web/components/editor/editor-save-context.tsx b/surfsense_web/components/editor/editor-save-context.tsx index d53a4adce..b4b3935a4 100644 --- a/surfsense_web/components/editor/editor-save-context.tsx +++ b/surfsense_web/components/editor/editor-save-context.tsx @@ -11,12 +11,15 @@ interface EditorSaveContextValue { isSaving: boolean; /** Whether the user can toggle between editing and viewing modes */ canToggleMode: boolean; + /** Whether fixed-toolbar space should be reserved even when controls are hidden */ + reserveToolbarSpace: boolean; } export const EditorSaveContext = createContext({ hasUnsavedChanges: false, isSaving: false, canToggleMode: false, + reserveToolbarSpace: false, }); export function useEditorSave() { diff --git a/surfsense_web/components/editor/plate-editor.tsx b/surfsense_web/components/editor/plate-editor.tsx index 371326bd3..481a420fb 100644 --- a/surfsense_web/components/editor/plate-editor.tsx +++ b/surfsense_web/components/editor/plate-editor.tsx @@ -44,6 +44,8 @@ export interface PlateEditorProps { isSaving?: boolean; /** Whether edit/view mode toggle UI should be available in toolbars. */ allowModeToggle?: boolean; + /** Reserve fixed-toolbar vertical space even when controls are hidden. */ + reserveToolbarSpace?: boolean; /** Start the editor in editing mode instead of viewing mode. Ignored when readOnly is true. */ defaultEditing?: boolean; /** @@ -94,6 +96,7 @@ export function PlateEditor({ hasUnsavedChanges = false, isSaving = false, allowModeToggle = true, + reserveToolbarSpace = false, defaultEditing = false, preset = "full", extraPlugins = [], @@ -185,8 +188,9 @@ export function PlateEditor({ hasUnsavedChanges, isSaving, canToggleMode, + reserveToolbarSpace, }), - [onSave, hasUnsavedChanges, isSaving, canToggleMode] + [onSave, hasUnsavedChanges, isSaving, canToggleMode, reserveToolbarSpace] ); return ( diff --git a/surfsense_web/components/editor/plugins/fixed-toolbar-kit.tsx b/surfsense_web/components/editor/plugins/fixed-toolbar-kit.tsx index 8b776a456..bdda0263d 100644 --- a/surfsense_web/components/editor/plugins/fixed-toolbar-kit.tsx +++ b/surfsense_web/components/editor/plugins/fixed-toolbar-kit.tsx @@ -9,12 +9,19 @@ import { FixedToolbarButtons } from "@/components/ui/fixed-toolbar-buttons"; function ConditionalFixedToolbar() { const readOnly = useEditorReadOnly(); - const { onSave, hasUnsavedChanges, canToggleMode } = useEditorSave(); + const { onSave, hasUnsavedChanges, canToggleMode, reserveToolbarSpace } = useEditorSave(); const hasVisibleControls = !readOnly || canToggleMode || (!!onSave && hasUnsavedChanges && !readOnly); - if (!hasVisibleControls) return null; + if (!hasVisibleControls) { + if (!reserveToolbarSpace) return null; + return ( + +
+ + ); + } return ( diff --git a/surfsense_web/components/report-panel/report-panel.tsx b/surfsense_web/components/report-panel/report-panel.tsx index 709b10467..0f6614ebf 100644 --- a/surfsense_web/components/report-panel/report-panel.tsx +++ b/surfsense_web/components/report-panel/report-panel.tsx @@ -116,6 +116,7 @@ export function ReportPanelContent({ const [exporting, setExporting] = useState(null); const [saving, setSaving] = useState(false); const copyTimerRef = useRef | undefined>(undefined); + const changeCountRef = useRef(0); useEffect(() => { return () => { @@ -190,8 +191,21 @@ export function ReportPanelContent({ useEffect(() => { setEditedMarkdown(null); setIsEditing(false); + changeCountRef.current = 0; }, [activeReportId]); + const handleReportMarkdownChange = useCallback( + (nextMarkdown: string) => { + if (!isEditing) return; + changeCountRef.current += 1; + // Plate may emit an initial normalize/serialize change on mount. + if (changeCountRef.current <= 1) return; + const savedMarkdown = reportContent?.content ?? ""; + setEditedMarkdown(nextMarkdown === savedMarkdown ? null : nextMarkdown); + }, + [isEditing, reportContent?.content] + ); + // Copy markdown content (uses latest editor content) const handleCopy = useCallback(async () => { if (!currentMarkdown) return; @@ -299,6 +313,7 @@ export function ReportPanelContent({ const handleCancelEditing = useCallback(() => { setEditedMarkdown(null); + changeCountRef.current = 0; setIsEditing(false); }, []); @@ -436,6 +451,7 @@ export function ReportPanelContent({ className="size-6" onClick={() => { setEditedMarkdown(null); + changeCountRef.current = 0; setIsEditing(true); }} > @@ -473,11 +489,12 @@ export function ReportPanelContent({ key={`report-${activeReportId}-${isEditing ? "editing" : "viewing"}`} preset="full" markdown={reportContent.content} - onMarkdownChange={setEditedMarkdown} + onMarkdownChange={handleReportMarkdownChange} readOnly={!isEditing} placeholder="Report content..." editorVariant="default" allowModeToggle={false} + reserveToolbarSpace defaultEditing={isEditing} className="[&_[role=toolbar]]:!bg-sidebar" />