import { MutationType, NoteMutator, SNNote } from '@standardnotes/models' import { AlertService, InfoStrings, ItemManagerInterface, MutatorClientInterface, SessionsClientInterface, SyncServiceInterface, } from '@standardnotes/snjs' import { Deferred } from '@standardnotes/utils' import { EditorSaveTimeoutDebounce } from '../Components/NoteView/Controller/EditorSaveTimeoutDebounce' import { IsNativeMobileWeb } from '@standardnotes/ui-services' const NotePreviewCharLimit = 160 export type NoteSaveFunctionParams = { title?: string text?: string bypassDebouncer?: boolean isUserModified?: boolean dontGeneratePreviews?: boolean previews?: { previewPlain: string; previewHtml?: string } customMutate?: (mutator: NoteMutator) => void onLocalPropagationComplete?: () => void onRemoteSyncComplete?: () => void } export class NoteSyncController { savingLocallyPromise: ReturnType> | null = null private saveTimeout?: ReturnType constructor( private item: SNNote, private items: ItemManagerInterface, private mutator: MutatorClientInterface, private sessions: SessionsClientInterface, private sync: SyncServiceInterface, private alerts: AlertService, private _isNativeMobileWeb: IsNativeMobileWeb, ) {} setItem(item: SNNote) { this.item = item } deinit() { if (this.saveTimeout) { clearTimeout(this.saveTimeout) } if (this.savingLocallyPromise) { this.savingLocallyPromise.reject() } this.savingLocallyPromise = null this.saveTimeout = undefined ;(this.item as unknown) = undefined } public async saveAndAwaitLocalPropagation(params: NoteSaveFunctionParams): Promise { this.savingLocallyPromise = Deferred() if (this.saveTimeout) { clearTimeout(this.saveTimeout) } const noDebounce = params.bypassDebouncer || this.sessions.isSignedOut() const syncDebouceMs = noDebounce ? EditorSaveTimeoutDebounce.ImmediateChange : this._isNativeMobileWeb.execute().getValue() ? EditorSaveTimeoutDebounce.NativeMobileWeb : EditorSaveTimeoutDebounce.Desktop return new Promise((resolve) => { this.saveTimeout = setTimeout(() => { void this.undebouncedSave({ ...params, onLocalPropagationComplete: () => { if (this.savingLocallyPromise) { this.savingLocallyPromise.resolve() } resolve() }, }) }, syncDebouceMs) }) } private async undebouncedSave(params: NoteSaveFunctionParams): Promise { if (!this.items.findItem(this.item.uuid)) { void this.alerts.alert(InfoStrings.InvalidNote) return } await this.mutator.changeItem( this.item, (mutator) => { const noteMutator = mutator as NoteMutator if (params.customMutate) { params.customMutate(noteMutator) } if (params.title != undefined) { noteMutator.title = params.title } if (params.text != undefined) { noteMutator.text = params.text } if (params.previews) { noteMutator.preview_plain = params.previews.previewPlain noteMutator.preview_html = params.previews.previewHtml } else if (!params.dontGeneratePreviews && params.text != undefined) { const noteText = params.text || '' const truncate = noteText.length > NotePreviewCharLimit const substring = noteText.substring(0, NotePreviewCharLimit) const previewPlain = substring + (truncate ? '...' : '') noteMutator.preview_plain = previewPlain noteMutator.preview_html = undefined } }, params.isUserModified ? MutationType.UpdateUserTimestamps : MutationType.NoUpdateUserTimestamps, ) void this.sync.sync().then(() => { params.onRemoteSyncComplete?.() }) params.onLocalPropagationComplete?.() } }