@dxos/react-ui-editor 0.6.2-main.fb91371 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/browser/index.mjs +99 -35
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/types/src/components/Toolbar/Toolbar.d.ts +1 -0
- package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -1
- package/dist/types/src/extensions/comments.d.ts +8 -1
- package/dist/types/src/extensions/comments.d.ts.map +1 -1
- package/package.json +25 -25
- package/src/components/Toolbar/Toolbar.tsx +2 -2
- package/src/extensions/comments.ts +114 -28
|
@@ -101,29 +101,47 @@ export const commentsState = StateField.define<CommentsState>({
|
|
|
101
101
|
//
|
|
102
102
|
|
|
103
103
|
const styles = EditorView.baseTheme({
|
|
104
|
-
'
|
|
105
|
-
|
|
104
|
+
'.cm-comment, .cm-comment-current': {
|
|
105
|
+
cursor: 'pointer',
|
|
106
|
+
borderWidth: '1px',
|
|
107
|
+
borderStyle: 'solid',
|
|
108
|
+
borderRadius: '2px',
|
|
109
|
+
transition: 'background-color 0.1s ease',
|
|
110
|
+
},
|
|
111
|
+
'&light .cm-comment, &light .cm-comment-current': {
|
|
112
|
+
mixBlendMode: 'darken',
|
|
113
|
+
borderColor: getToken('extend.colors.yellow.100'),
|
|
114
|
+
},
|
|
115
|
+
'&dark .cm-comment, &dark .cm-comment-current': {
|
|
116
|
+
mixBlendMode: 'plus-lighter',
|
|
117
|
+
borderColor: getToken('extend.colors.yellow.900'),
|
|
118
|
+
},
|
|
106
119
|
'&light .cm-comment': {
|
|
107
120
|
backgroundColor: getToken('extend.colors.yellow.50'),
|
|
108
121
|
},
|
|
109
|
-
'&light .cm-comment
|
|
110
|
-
|
|
111
|
-
},
|
|
122
|
+
'&light .cm-comment:hover': { backgroundColor: getToken('extend.colors.yellow.100') },
|
|
123
|
+
'&light .cm-comment-current': { backgroundColor: getToken('extend.colors.yellow.100') },
|
|
124
|
+
'&light .cm-comment-current:hover': { backgroundColor: getToken('extend.colors.yellow.150') },
|
|
112
125
|
'&dark .cm-comment': {
|
|
113
126
|
color: getToken('extend.colors.yellow.50'),
|
|
114
127
|
backgroundColor: getToken('extend.colors.yellow.900'),
|
|
115
128
|
},
|
|
129
|
+
'&dark .cm-comment:hover': { backgroundColor: getToken('extend.colors.yellow.800') },
|
|
116
130
|
'&dark .cm-comment-current': {
|
|
117
131
|
color: getToken('extend.colors.yellow.100'),
|
|
118
132
|
backgroundColor: getToken('extend.colors.yellow.950'),
|
|
119
133
|
},
|
|
134
|
+
'&dark .cm-comment-current:hover': { backgroundColor: getToken('extend.colors.yellow.900') },
|
|
120
135
|
});
|
|
121
136
|
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
137
|
+
const createCommentMark = (id: string, isCurrent: boolean) =>
|
|
138
|
+
Decoration.mark({
|
|
139
|
+
class: isCurrent ? 'cm-comment-current' : 'cm-comment',
|
|
140
|
+
attributes: {
|
|
141
|
+
'data-testid': 'cm-comment',
|
|
142
|
+
'data-comment-id': id,
|
|
143
|
+
},
|
|
144
|
+
});
|
|
127
145
|
|
|
128
146
|
/**
|
|
129
147
|
* Decorate ranges.
|
|
@@ -145,17 +163,39 @@ const commentsDecorations = EditorView.decorations.compute([commentsState], (sta
|
|
|
145
163
|
return undefined;
|
|
146
164
|
}
|
|
147
165
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
} else {
|
|
151
|
-
return commentMark.range(range.from, range.to);
|
|
152
|
-
}
|
|
166
|
+
const mark = createCommentMark(comment.comment.id, comment.comment.id === current);
|
|
167
|
+
return mark.range(range.from, range.to);
|
|
153
168
|
})
|
|
154
169
|
.filter(nonNullable);
|
|
155
170
|
|
|
156
171
|
return Decoration.set(decorations);
|
|
157
172
|
});
|
|
158
173
|
|
|
174
|
+
const commentClickedEffect = StateEffect.define<string>();
|
|
175
|
+
|
|
176
|
+
const handleCommentClick = EditorView.domEventHandlers({
|
|
177
|
+
click: (event, view) => {
|
|
178
|
+
let target = event.target as HTMLElement;
|
|
179
|
+
const editorRoot = view.dom;
|
|
180
|
+
|
|
181
|
+
// Traverse up the DOM tree looking for an element with data-comment-id
|
|
182
|
+
// Stop if we reach the editor root or find the comment id
|
|
183
|
+
while (target && target !== editorRoot && !target.hasAttribute('data-comment-id')) {
|
|
184
|
+
target = target.parentElement as HTMLElement;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Check if we found a comment id and are still within the editor
|
|
188
|
+
if (target && target !== editorRoot) {
|
|
189
|
+
const commentId = target.getAttribute('data-comment-id');
|
|
190
|
+
if (commentId) {
|
|
191
|
+
view.dispatch({ effects: commentClickedEffect.of(commentId) });
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return false;
|
|
197
|
+
},
|
|
198
|
+
});
|
|
159
199
|
//
|
|
160
200
|
// Cut-and-paste.
|
|
161
201
|
//
|
|
@@ -372,6 +412,7 @@ export const comments = (options: CommentsOptions = {}): Extension => {
|
|
|
372
412
|
documentId.of(options.id),
|
|
373
413
|
commentsState,
|
|
374
414
|
commentsDecorations,
|
|
415
|
+
handleCommentClick,
|
|
375
416
|
styles,
|
|
376
417
|
|
|
377
418
|
//
|
|
@@ -503,17 +544,28 @@ export const scrollThreadIntoView = (view: EditorView, id: string, center = true
|
|
|
503
544
|
if (!comment?.comment.cursor) {
|
|
504
545
|
return;
|
|
505
546
|
}
|
|
506
|
-
|
|
507
547
|
const range = Cursor.getRangeFromCursor(view.state, comment.comment.cursor);
|
|
508
548
|
if (range) {
|
|
509
|
-
view.
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
549
|
+
const currentSelection = view.state.selection.main;
|
|
550
|
+
const currentScrollPosition = view.scrollDOM.scrollTop;
|
|
551
|
+
const targetScrollPosition = view.coordsAtPos(range.from)?.top;
|
|
552
|
+
|
|
553
|
+
const needsScroll =
|
|
554
|
+
targetScrollPosition !== undefined &&
|
|
555
|
+
(targetScrollPosition < currentScrollPosition ||
|
|
556
|
+
targetScrollPosition > currentScrollPosition + view.scrollDOM.clientHeight);
|
|
557
|
+
|
|
558
|
+
const needsSelectionUpdate = currentSelection.from !== range.from || currentSelection.to !== range.from;
|
|
559
|
+
|
|
560
|
+
if (needsScroll || needsSelectionUpdate) {
|
|
561
|
+
view.dispatch({
|
|
562
|
+
selection: needsSelectionUpdate ? { anchor: range.from } : undefined,
|
|
563
|
+
effects: [
|
|
564
|
+
needsScroll ? EditorView.scrollIntoView(range.from, center ? { y: 'center' } : undefined) : [],
|
|
565
|
+
needsSelectionUpdate ? setSelection.of({ current: id }) : [],
|
|
566
|
+
].flat(),
|
|
567
|
+
});
|
|
568
|
+
}
|
|
517
569
|
}
|
|
518
570
|
};
|
|
519
571
|
|
|
@@ -533,6 +585,13 @@ export const selectionOverlapsComment = (state: EditorState): boolean => {
|
|
|
533
585
|
return false;
|
|
534
586
|
};
|
|
535
587
|
|
|
588
|
+
/**
|
|
589
|
+
* Check if there is one or more active (non-empty) selections in the editor state.
|
|
590
|
+
*/
|
|
591
|
+
const hasActiveSelection = (state: EditorState): boolean => {
|
|
592
|
+
return state.selection.ranges.some((range) => !range.empty);
|
|
593
|
+
};
|
|
594
|
+
|
|
536
595
|
/**
|
|
537
596
|
* Update comments state field.
|
|
538
597
|
*/
|
|
@@ -554,18 +613,45 @@ export const useComments = (view: EditorView | null | undefined, id: string, com
|
|
|
554
613
|
* Hook provides an extension to compute the current comment state under the selection.
|
|
555
614
|
* NOTE(Zan): I think this conceptually belongs in 'formatting.ts' but we can't import ESM modules there atm.
|
|
556
615
|
*/
|
|
557
|
-
export const useCommentState = (): [boolean, Extension] => {
|
|
558
|
-
const [
|
|
616
|
+
export const useCommentState = (): [{ comment: boolean; selection: boolean }, Extension] => {
|
|
617
|
+
const [state, setState] = useState<{ comment: boolean; selection: boolean }>({
|
|
618
|
+
comment: false,
|
|
619
|
+
selection: false,
|
|
620
|
+
});
|
|
559
621
|
|
|
560
622
|
const observer = useMemo(
|
|
561
623
|
() =>
|
|
562
624
|
EditorView.updateListener.of((update) => {
|
|
563
625
|
if (update.docChanged || update.selectionSet) {
|
|
564
|
-
|
|
626
|
+
setState({
|
|
627
|
+
comment: selectionOverlapsComment(update.state),
|
|
628
|
+
selection: hasActiveSelection(update.state),
|
|
629
|
+
});
|
|
565
630
|
}
|
|
566
631
|
}),
|
|
567
632
|
[],
|
|
568
633
|
);
|
|
569
634
|
|
|
570
|
-
return [
|
|
635
|
+
return [state, observer];
|
|
636
|
+
};
|
|
637
|
+
|
|
638
|
+
/**
|
|
639
|
+
* Hook provides an extension to listen for comment clicks and invoke a handler.
|
|
640
|
+
*/
|
|
641
|
+
export const useCommentClickListener = (onCommentClick: (commentId: string) => void): Extension => {
|
|
642
|
+
const observer = useMemo(
|
|
643
|
+
() =>
|
|
644
|
+
EditorView.updateListener.of((update) => {
|
|
645
|
+
update.transactions.forEach((transaction) => {
|
|
646
|
+
transaction.effects.forEach((effect) => {
|
|
647
|
+
if (effect.is(commentClickedEffect)) {
|
|
648
|
+
onCommentClick(effect.value);
|
|
649
|
+
}
|
|
650
|
+
});
|
|
651
|
+
});
|
|
652
|
+
}),
|
|
653
|
+
[onCommentClick],
|
|
654
|
+
);
|
|
655
|
+
|
|
656
|
+
return observer;
|
|
571
657
|
};
|