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
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,10 @@
let contentError = false
let localSynced = false
let remoteSynced = false
let editorReady = false

$: loading = !localSynced && !remoteSynced
$: editable = !readonly && !contentError && remoteSynced && hasAccountRole(account, AccountRole.User)
$: loading = !localSynced
$: editable = !readonly && !contentError && remoteSynced && editorReady && hasAccountRole(account, AccountRole.User)

void localProvider.loaded.then(() => (localSynced = true))
void remoteProvider.loaded.then(() => (remoteSynced = true))
Expand Down Expand Up @@ -229,7 +230,7 @@
needFocus = false
}

$: if (editor !== undefined) {
$: if (editor !== undefined && editorReady && editable !== editor.isEditable) {
// When the content is invalid, we don't want to emit an update
// Preventing synchronization of the invalid content
const emitUpdate = !contentError
Expand Down Expand Up @@ -429,23 +430,31 @@
ydoc,
boundary,
popupContainer: editorPopupContainer,
requestSideSpace
requestSideSpace,
whenSync: remoteProvider.loaded
}
}
},
kitOptions
)

// Create editor immediately with cached content
// BUT keep it read-only until remote sync completes
// This prevents stale cached content from overwriting newer server content
editor = new Editor({
extensions: [kit],
element,
editable: false,
editorProps: {
attributes: mergeAttributes(defaultEditorAttributes, editorAttributes, { class: 'flex-grow' })
},
enableContentCheck: true,
parseOptions: {
preserveWhitespace: 'full'
},
onCreate: () => {
editorReady = true
},
onTransaction: () => {
// force re-render so `editor.isActive` works as expected
editor = editor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ export interface InlineCommentExtensionOptions {
minEditorWidth?: number

requestSideSpace?: (width: number) => void

whenSync?: Promise<void>
}

type InlineCommentDisplayMode = 'compact' | 'full'
Expand Down Expand Up @@ -243,11 +245,21 @@ function initCommentDecoratorView (options: InlineCommentExtensionOptions, view:
view.dispatch(setMeta(view.state.tr, { threads }))
}

commentMap.observe(commentMapObserver)
// Defer YMap observation until remote sync is complete to prevent race condition
// where stale cached comment positions overwrite the latest server state
const initObserver = async (): Promise<void> => {
if (options.whenSync !== undefined) {
await options.whenSync
}
commentMap.observe(commentMapObserver)
commentMapObserver()
}

void initObserver()

destructors.push(() => {
commentMap.unobserve(commentMapObserver)
})
commentMapObserver()

return {
destroy () {
Expand Down Expand Up @@ -688,7 +700,10 @@ function eqSets<T> (xs: Set<T>, ys: Set<T>): boolean {
}

function forceUpdateDecorations (view: EditorView): void {
view.dispatch(setMeta(view.state.tr, {}))
const state = getCommentDecoratorState(view.state)
if (state !== undefined) {
view.updateState(view.state)
}
}

function fetchCommentThreads (options: InlineCommentExtensionOptions): Map<string, Thread> {
Expand All @@ -711,15 +726,19 @@ function resolveThread (options: InlineCommentExtensionOptions, editorView: Edit
if (view === undefined) return

const commentMap = getYDocCommentMap(options)

const keys = Array.from(commentMap.keys())
for (const key of keys) {
const comment = commentMap.get(key)
if (comment?.thread === thread) commentMap.delete(key)
}

const { from, to } = view.props.range
const mark = view.props.mark ?? editorView.state.schema.marks['inline-comment']

options.ydoc.transact(() => {
const keys = Array.from(commentMap.keys())
for (const key of keys) {
const comment = commentMap.get(key)
if (comment?.thread === thread) {
commentMap.delete(key)
}
}
})

editorView.dispatch(setMeta(editorView.state.tr.removeMark(from, to, mark), {}))
}

Expand Down Expand Up @@ -764,14 +783,19 @@ function updateThreadComment (
if (view !== undefined) {
const { from, to } = view.props.range

let tr = setMeta(editorView.state.tr, {})
let tr = editorView.state.tr

if (thread.messages.length === 0) {
tr = tr.setSelection(TextSelection.create(editorView.state.doc, 0))
}

if (thread.messages.length === 1 && existingComment === undefined) {
const mark = editorView.state.schema.mark('inline-comment', { thread: thread._id })
tr = tr.addMark(from, to, mark)
}

tr = setMeta(tr, {})

editorView.dispatch(tr)
}
}
Expand Down
Loading