From 5cdf6e55bbd58bb9bcdb4dc82447c8de2ba13944 Mon Sep 17 00:00:00 2001 From: Darshan Date: Sat, 18 Oct 2025 13:40:34 +0530 Subject: [PATCH] add: ctrl/cmd + s to save the document. --- .../(components)/editor/helpers/keymaps.ts | 19 ++++++- .../(components)/editor/view.svelte | 55 ++++++++++--------- .../spreadsheet.svelte | 1 + 3 files changed, 48 insertions(+), 27 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/(components)/editor/helpers/keymaps.ts b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/(components)/editor/helpers/keymaps.ts index 46d4e1d84..5fa8123ed 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/(components)/editor/helpers/keymaps.ts +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/(components)/editor/helpers/keymaps.ts @@ -6,14 +6,29 @@ import { defaultKeymap, historyKeymap, indentLess, indentMore } from '@codemirro // main editor keymaps, // these require functions from the component export function createEditorKeymaps( - insertNewlineKeepIndent: (view: EditorView) => boolean + insertNewlineKeepIndent: (view: EditorView) => boolean, + onSave?: () => Promise | void ): KeyBinding[] { - return [ + const keymaps: KeyBinding[] = [ { key: 'Tab', run: indentMore }, { key: 'Enter', run: insertNewlineKeepIndent }, { key: 'Shift-Enter', run: insertNewlineKeepIndent }, { key: 'Shift-Tab', run: indentLess } ]; + + // Add Cmd/Ctrl+S save shortcut if save handler is provided + if (onSave) { + keymaps.push({ + key: 'Mod-s', + preventDefault: true, + run: () => { + onSave(); + return true; + } + }); + } + + return keymaps; } // Secondary keymaps - these are standard CodeMirror keymaps diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/(components)/editor/view.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/(components)/editor/view.svelte index fa20bf5d3..4b89132b1 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/(components)/editor/view.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/(components)/editor/view.svelte @@ -79,6 +79,7 @@ readonly?: boolean; wrapLines?: boolean; errorInPlace?: boolean; + ctrlSave?: boolean; } let { @@ -90,7 +91,8 @@ loading = false, readonly = false, wrapLines = true, - errorInPlace = true + errorInPlace = true, + ctrlSave = false }: Props = $props(); let editorContainer: HTMLDivElement = $state(null); @@ -119,7 +121,7 @@ let wasNew = isNew; // Generate a stable ID once for new documents - const generatedId = ID.unique(); + let generatedId = $state(ID.unique()); // Get $id from data const documentId = $derived( @@ -694,6 +696,25 @@ } } + // Handle save logic - called from both button and keyboard shortcut + async function handleSave(): Promise { + if (!hasDataChanged) return; + + isSaving = true; + + let dataToSave = data; + if (isNew && typeof data === 'object' && data !== null && !Array.isArray(data)) { + const dataObj = data; + if (!dataObj['$id']) { + dataToSave = { $id: generatedId, ...dataObj }; + } + } + + await sleep(2500); + await onSave?.(dataToSave); + isSaving = false; + } + onMount(() => { if (!editorContainer) return; @@ -729,7 +750,9 @@ } }), // Override Enter and Shift-Enter to keep current indent, no extra +indentUnit - keymap.of(createEditorKeymaps(insertNewlineKeepIndent)), + keymap.of( + createEditorKeymaps(insertNewlineKeepIndent, ctrlSave ? handleSave : undefined) + ), keymap.of(secondaryKeymaps), javascript(), customSyntaxHighlighting, @@ -804,6 +827,7 @@ $effect(() => { if (isNew && !wasNew) { originalData = $state.snapshot(data); + generatedId = ID.unique(); } wasNew = isNew; }); @@ -857,10 +881,11 @@ }); // React to read-only prop changes via Compartment reconfigure + // Also make editor read-only while saving to prevent concurrent edits $effect(() => { if (!editorView) return; editorView.dispatch({ - effects: readOnlyCompartment.reconfigure(EditorState.readOnly.of(readonly)) + effects: readOnlyCompartment.reconfigure(EditorState.readOnly.of(readonly || isSaving)) }); }); @@ -901,27 +926,7 @@ size="xs" disabled={!hasDataChanged} class="icon-button" - on:click={async () => { - isSaving = true; - - // For new documents, ensure $id is added to data before saving - let dataToSave = data; - if ( - isNew && - typeof data === 'object' && - data !== null && - !Array.isArray(data) - ) { - const dataObj = data; - if (!dataObj['$id']) { - dataToSave = { $id: generatedId, ...dataObj }; - } - } - - await sleep(2500); - await onSave?.(dataToSave); - isSaving = false; - }}> + on:click={handleSave}> {#if isSaving} {:else} diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/spreadsheet.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/spreadsheet.svelte index 106ce6486..0ad75696c 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/spreadsheet.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/spreadsheet.svelte @@ -651,6 +651,7 @@ {#snippet noSqlEditor()} await createOrUpdateDocument(document)} />