Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 22 additions & 10 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ import {
runEditorToolbarCommandWorkflow,
scheduleEditorToolbarSync,
} from './features/editor/editorToolbarWorkflow'
import { readEditorMarkdownSnapshot } from './features/editor/editorMarkdownSnapshot'
import {
getEditorToolbarSettings,
} from './features/editor/editorToolbarSettings'
Expand Down Expand Up @@ -488,12 +489,23 @@ function normalizeEditorMarkdown(markdown: string) {
return markdown === '\n' ? '' : markdown
}

function syncDocumentFromEditor(markDirty = false) {
function getEditorMarkdownSnapshot(flushPending = false) {
if (!editor) {
return ''
}

return readEditorMarkdownSnapshot(editor, {
flushPending,
normalizeMarkdown: normalizeEditorMarkdown,
})
}

function syncDocumentFromEditor(markDirty = false, flushPending = false) {
if (!editor) {
return documentState.value
}

const value = normalizeEditorMarkdown(editor.getMarkdown())
const value = getEditorMarkdownSnapshot(flushPending)
documentState.value = updateDocumentMarkdown(documentState.value, value, { markDirty })
return documentState.value
}
Expand All @@ -504,7 +516,7 @@ function syncMarkdown(nextStatus: unknown = 'Edited') {
}

const resolvedStatus = typeof nextStatus === 'string' ? nextStatus : 'Edited'
const nextMarkdown = normalizeEditorMarkdown(editor.getMarkdown())
const nextMarkdown = getEditorMarkdownSnapshot()
const markDirty = resolvedStatus === 'Edited'
documentState.value = updateDocumentMarkdown(documentState.value, nextMarkdown, { markDirty })
if (markDirty && documentState.value.autosaveTarget === 'android-document') {
Expand Down Expand Up @@ -1108,7 +1120,7 @@ async function saveAndroidDocument() {
return true
}

const value = normalizeEditorMarkdown(editor.getMarkdown())
const value = getEditorMarkdownSnapshot(true)
const startResult = createSaveAndroidDocumentWorkflowStart({
documentState: documentState.value,
markdown: value,
Expand Down Expand Up @@ -1199,7 +1211,7 @@ async function saveLocalDraftToAndroidDocument(options: { returnHomeAfterSave?:
}

saveDraft()
const draftDocument = syncDocumentFromEditor(false)
const draftDocument = syncDocumentFromEditor(false, true)

const reopenPromptOnCancel = draftExitPromptOpen.value && options.returnHomeAfterSave === true
draftExitPromptOpen.value = false
Expand Down Expand Up @@ -1273,7 +1285,7 @@ async function saveAndroidDocumentCopy(options: { returnHomeAfterSave?: boolean
}

const originalSourceUri = documentState.value.sourceUri
const copySourceDocument = syncDocumentFromEditor(documentState.value.isDirty)
const copySourceDocument = syncDocumentFromEditor(documentState.value.isDirty, true)

savingAndroidDocumentCopy.value = true
status.value = 'Choose a location'
Expand Down Expand Up @@ -1338,7 +1350,7 @@ async function shareCurrentMarkdownDocument() {
return false
}

const currentDocument = syncDocumentFromEditor(documentState.value.isDirty)
const currentDocument = syncDocumentFromEditor(documentState.value.isDirty, true)
if (currentDocument.autosaveTarget === 'local-draft') {
saveDraft()
}
Expand Down Expand Up @@ -1387,7 +1399,7 @@ function saveDraft() {
return
}

const value = normalizeEditorMarkdown(editor.getMarkdown())
const value = getEditorMarkdownSnapshot(true)
const localDraftAutosave = createLocalDraftAutosaveResult(
documentState.value,
value,
Expand Down Expand Up @@ -1738,7 +1750,7 @@ async function preserveCurrentDocumentBeforeIncomingOpen() {
appLog.info('preserve current document before incoming Android document')

if (preservationAction.kind === 'save-android-document') {
syncDocumentFromEditor(documentState.value.isDirty)
syncDocumentFromEditor(documentState.value.isDirty, true)
const sourceUri = documentState.value.sourceUri
const saved = await saveAndroidDocument()
if (sourceUri && shouldKeepAndroidRecoveryAfterPreserveFailure(saved, sourceUri, documentState.value.markdown)) {
Expand Down Expand Up @@ -1969,7 +1981,7 @@ async function showHome() {

function keepAndroidRecoveryAndShowHome() {
const sourceUri = documentState.value.sourceUri
const currentDocument = syncDocumentFromEditor(documentState.value.isDirty)
const currentDocument = syncDocumentFromEditor(documentState.value.isDirty, true)
if (sourceUri && currentDocument.markdown.trim() && canPersistAndroidRecoveryDrafts()) {
persistAndroidRecoveryDraft(sourceUri, currentDocument.markdown)
homeNotice.value = ANDROID_EXIT_RECOVERY_MESSAGE
Expand Down
50 changes: 50 additions & 0 deletions src/features/editor/editorMarkdownSnapshot.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { describe, expect, it, vi } from 'vitest'
import { readEditorMarkdownSnapshot } from './editorMarkdownSnapshot'

function createEditorSnapshotSource() {
let markdown = 'stale'
const editor = {
flush: vi.fn(() => {
markdown = 'fresh\n'
}),
getMarkdown: vi.fn(() => markdown),
}
return editor
}

describe('readEditorMarkdownSnapshot', () => {
it('flushes pending Muya edits before reading markdown for save paths', () => {
const editor = createEditorSnapshotSource()

const markdown = readEditorMarkdownSnapshot(editor, { flushPending: true })

expect(markdown).toBe('fresh\n')
expect(editor.flush).toHaveBeenCalledTimes(1)
expect(editor.getMarkdown).toHaveBeenCalledTimes(1)
const flushOrder = vi.mocked(editor.flush).mock.invocationCallOrder[0]
const getMarkdownOrder = vi.mocked(editor.getMarkdown).mock.invocationCallOrder[0]
expect(flushOrder).toBeDefined()
expect(getMarkdownOrder).toBeDefined()
expect(flushOrder as number).toBeLessThan(getMarkdownOrder as number)
})

it('keeps non-save reads unflushed', () => {
const editor = createEditorSnapshotSource()

const markdown = readEditorMarkdownSnapshot(editor)

expect(markdown).toBe('stale')
expect(editor.flush).not.toHaveBeenCalled()
})

it('normalizes the flushed markdown snapshot', () => {
const editor = createEditorSnapshotSource()

const markdown = readEditorMarkdownSnapshot(editor, {
flushPending: true,
normalizeMarkdown: value => value.trim(),
})

expect(markdown).toBe('fresh')
})
})
21 changes: 21 additions & 0 deletions src/features/editor/editorMarkdownSnapshot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export interface EditorMarkdownSnapshotSource {
flush?: () => void
getMarkdown: () => string
}

interface ReadEditorMarkdownSnapshotOptions {
flushPending?: boolean
normalizeMarkdown?: (markdown: string) => string
}

export function readEditorMarkdownSnapshot(
editor: EditorMarkdownSnapshotSource,
options: ReadEditorMarkdownSnapshotOptions = {},
) {
if (options.flushPending) {
editor.flush?.()
}

const markdown = editor.getMarkdown()
return options.normalizeMarkdown ? options.normalizeMarkdown(markdown) : markdown
}
1 change: 1 addition & 0 deletions src/types/muya-core.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ declare module '@muyajs/core' {
on(event: string, listener: (...args: unknown[]) => void): void
off(event: string, listener: (...args: unknown[]) => void): void
getMarkdown(): string
flush(): void
getTOC(): ITocItem[]
getHistory(): unknown
setHistory(history: unknown): void
Expand Down
Loading