diff --git a/apps/client/src/components/note_context.ts b/apps/client/src/components/note_context.ts index 81eae41e1..8cca352c7 100644 --- a/apps/client/src/components/note_context.ts +++ b/apps/client/src/components/note_context.ts @@ -261,14 +261,33 @@ class NoteContext extends Component implements EventListener<"entitiesReloaded"> return true; } - const blob = await this.note.getBlob(); - if (!blob) { - return false; + // Store the initial decision about read-only status in the viewScope + // This will be "remembered" until the viewScope is refreshed + if (!this.viewScope) { + this.resetViewScope(); } - const sizeLimit = this.note.type === "text" ? options.getInt("autoReadonlySizeText") : options.getInt("autoReadonlySizeCode"); + // We've ensured viewScope exists by calling resetViewScope() if needed + const viewScope = this.viewScope as ViewScope; - return sizeLimit && blob.contentLength > sizeLimit && !this.note.isLabelTruthy("autoReadOnlyDisabled"); + if (viewScope.readOnlyDecision === undefined) { + const blob = await this.note.getBlob(); + if (!blob) { + viewScope.readOnlyDecision = false; + return false; + } + + const sizeLimit = this.note.type === "text" + ? options.getInt("autoReadonlySizeText") + : options.getInt("autoReadonlySizeCode"); + + viewScope.readOnlyDecision = Boolean(sizeLimit && + blob.contentLength > sizeLimit && + !this.note.isLabelTruthy("autoReadOnlyDisabled")); + } + + // Return the cached decision, which won't change until viewScope is reset + return viewScope.readOnlyDecision || false; } async entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) { diff --git a/apps/client/src/services/link.ts b/apps/client/src/services/link.ts index a0d464741..dcfbd16e2 100644 --- a/apps/client/src/services/link.ts +++ b/apps/client/src/services/link.ts @@ -48,6 +48,7 @@ export interface ViewScope { viewMode?: ViewMode; attachmentId?: string; readOnlyTemporarilyDisabled?: boolean; + readOnlyDecision?: boolean; highlightsListPreviousVisible?: boolean; highlightsListTemporarilyHidden?: boolean; tocTemporarilyHidden?: boolean; diff --git a/apps/client/src/widgets/note_detail.ts b/apps/client/src/widgets/note_detail.ts index 39a7509d7..238683809 100644 --- a/apps/client/src/widgets/note_detail.ts +++ b/apps/client/src/widgets/note_detail.ts @@ -127,51 +127,12 @@ export default class NoteDetailWidget extends NoteContextAwareWidget { if (data === undefined) { return; } - - // Check if note is read-only before attempting to save - const isReadOnlyBefore = await this.noteContext.isReadOnly(); - - // If the note is read-only due to size but user is still editing, - // we need to temporarily disable read-only mode - if (isReadOnlyBefore && !this.noteContext.viewScope?.readOnlyTemporarilyDisabled) { - // Auto-enable temporary edit mode (same as "Edit this note" button) - if (this.noteContext.viewScope) { - this.noteContext.viewScope.readOnlyTemporarilyDisabled = true; - appContext.triggerEvent("readOnlyTemporarilyDisabled", { noteContext: this.noteContext }); - } - } protectedSessionHolder.touchProtectedSessionIfNecessary(note); - try { - await server.put(`notes/${noteId}/data`, data, this.componentId); - this.getTypeWidget().dataSaved(); - - // Check if this save operation made the note cross the threshold - if (!isReadOnlyBefore && (await this.noteContext.isReadOnly())) { - // Note just became read-only after this save - enable temporary edit mode - if (this.noteContext.viewScope) { - this.noteContext.viewScope.readOnlyTemporarilyDisabled = true; - appContext.triggerEvent("readOnlyTemporarilyDisabled", { noteContext: this.noteContext }); - } - } - } catch (err) { - // If save failed because the note just became read-only, - // enable temporary edit mode and try again - if (!isReadOnlyBefore && (await this.noteContext.isReadOnly())) { - if (this.noteContext.viewScope) { - this.noteContext.viewScope.readOnlyTemporarilyDisabled = true; - appContext.triggerEvent("readOnlyTemporarilyDisabled", { noteContext: this.noteContext }); - - // Try saving again with temporary edit mode enabled - await server.put(`notes/${noteId}/data`, data, this.componentId); - this.getTypeWidget().dataSaved(); - } - } else { - // For other errors, just rethrow - throw err; - } - } + await server.put(`notes/${noteId}/data`, data, this.componentId); + + this.getTypeWidget().dataSaved(); }); appContext.addBeforeUnloadListener(this); @@ -403,73 +364,9 @@ export default class NoteDetailWidget extends NoteContextAwareWidget { return this.spacedUpdate.isAllSavedAndTriggerUpdate(); } - async readOnlyTemporarilyDisabledEvent({ noteContext }: EventData<"readOnlyTemporarilyDisabled">) { + readOnlyTemporarilyDisabledEvent({ noteContext }: EventData<"readOnlyTemporarilyDisabled">) { if (this.isNoteContext(noteContext.ntxId)) { - // Check if we're dealing with a text note/editor type - const isTextNote = this.type === "editableText" || this.type === "readOnlyText"; - - if (isTextNote) { - // Get current editor and selection before refresh - const currentEditor = await this.noteContext?.getTextEditor(); - let cursorInfo = null; - - if (currentEditor) { - try { - const selection = currentEditor.model.document.selection; - if (selection) { - const cursorPos = selection.getFirstPosition(); - if (cursorPos) { - // Store minimal info about cursor position to restore later - cursorInfo = { - // Store paragraph index and character offset - paraIndex: cursorPos.path[0] || 0, - offset: cursorPos.offset - }; - } - } - } catch (e) { - // Ignore errors in getting selection - } - } - - // Perform refresh to transition editor - await this.refresh(); - - // Immediately attempt to restore cursor position without delay - if (cursorInfo) { - const newEditor = await this.noteContext?.getTextEditor(); - if (newEditor) { - try { - newEditor.model.change(writer => { - const root = newEditor.model.document.getRoot(); - if (!root) return; - - // Find the paragraph at the same index or closest available - const targetParaIndex = Math.min(cursorInfo.paraIndex, root.childCount - 1); - const paragraph = root.getChild(targetParaIndex); - - if (paragraph) { - // Use an explicit cast to access maxOffset - const maxOffset = (paragraph as any).maxOffset || 0; - const safeOffset = Math.min(cursorInfo.offset, maxOffset); - - // Set cursor at the preserved position - const position = writer.createPositionAt(paragraph, safeOffset); - writer.setSelection(position); - } - }); - - // Focus editor to make cursor visible immediately - newEditor.editing.view.focus(); - } catch (e) { - // Silently fail if we can't restore cursor - } - } - } - } else { - // Not a text editor, just do a regular refresh - await this.refresh(); - } + this.refresh(); } }