diff --git a/src/prosemirror/plugins/comments.ts b/src/prosemirror/plugins/comments.ts index fb325b87..6dcad6b5 100644 --- a/src/prosemirror/plugins/comments.ts +++ b/src/prosemirror/plugins/comments.ts @@ -152,17 +152,24 @@ function reducer(tr: Transaction, state: CommentState): CommentState { }); const action = tr.getMeta(key) as (CommentAction & { docId?: string }) | undefined; const { selectedComment } = state; + // The docId might be updated by the action (unlikely) const docId = action?.docId ?? state.docId; + if (tr.getMeta('addToHistory') === false) { + // This is used in `prosemirror-collab`, other clients should not change our decoration state + // This early exits with only updating the mapped decorations + return { docId, selectedComment, decorations: nextDecorations }; + } if (!action) { // Check if we are in a comment! const around = decorations.find(tr.selection.from, tr.selection.to); - if (around.length === 0) { + if (around.length === 0 || !tr.selection.empty) { // We are not in a comment, and the action to select does not exist const hasSelectedComment = selectors.selectedSidenote(store.getState(), docId); if (hasSelectedComment) store.dispatch(actions.deselectSidenote(docId)); const selected = updateSelectedDecorations(nextDecorations, null, tr.doc); return { docId, selectedComment: null, decorations: selected }; } + // Otherwise, send a select action to sidenotes as we change state.selection! const { id, domId } = around[0].spec as DecorationSpec; const isSelected = selectors.isSidenoteSelected(store.getState(), docId, id); if (!isSelected) store.dispatch(actions.selectAnchor(docId, domId)); diff --git a/src/prosemirror/plugins/selection.tsx b/src/prosemirror/plugins/selection.tsx index 5bd040b0..1d329408 100644 --- a/src/prosemirror/plugins/selection.tsx +++ b/src/prosemirror/plugins/selection.tsx @@ -9,17 +9,15 @@ import { opts } from '../../connect'; class SelectionControl { dom: HTMLDivElement; - constructor(view: EditorView, stateKey: string) { + constructor(view: EditorView, stateKey: any) { this.dom = document.createElement('div'); this.dom.className = 'selection-control'; ReactDOM.render( { - opts.addComment(stateKey, view); - }} + aria-label="Add Comment" + onClick={() => opts.addComment(stateKey, view)} > @@ -31,27 +29,26 @@ class SelectionControl { } update(view: EditorView, lastState: EditorState | null) { - const { state } = view; - if (lastState && lastState.selection.eq?.(state.selection)) return; + const { selection } = view.state; + if (lastState?.selection.eq?.(selection)) return; // Hide the tooltip if the selection is empty - if (state.selection.empty) { + if (selection.empty) { this.dom.style.display = 'none'; return; } // Otherwise, reposition it and update its content this.dom.style.display = ''; - const { from, to } = state.selection; + // Use the first place you put the anchor, so the selection doesn't jump around + const { anchor } = selection; // These are in screen coordinates - const start = view.coordsAtPos(from); + const start = view.coordsAtPos(anchor); // The box in which the tooltip is positioned, to use as base if (!this.dom.offsetParent) { return; } const box = this.dom.offsetParent.getBoundingClientRect(); - // Find a center-ish x position from the selection endpoints (when - // crossing lines, end may be more to the left) this.dom.style.bottom = `${box.bottom - start.top - 30}px`; }