@dxos/react-ui-editor 0.6.1 → 0.6.2-main.000b1cc

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.
@@ -101,29 +101,48 @@ export const commentsState = StateField.define<CommentsState>({
101
101
  //
102
102
 
103
103
  const styles = EditorView.baseTheme({
104
- '&light .cm-comment, &light .cm-comment-current': { mixBlendMode: 'darken' },
105
- '&dark .cm-comment, &dark .cm-comment-current': { mixBlendMode: 'plus-lighter' },
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-current': {
110
- backgroundColor: getToken('extend.colors.yellow.100'),
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 commentMark = Decoration.mark({ class: 'cm-comment', attributes: { 'data-testid': 'cm-comment' } });
123
- const commentCurrentMark = Decoration.mark({
124
- class: 'cm-comment-current',
125
- attributes: { 'data-testid': 'cm-comment' },
126
- });
137
+ const createCommentMark = (id: string, isCurrent: boolean) => {
138
+ return Decoration.mark({
139
+ class: isCurrent ? 'cm-comment-current' : 'cm-comment',
140
+ attributes: {
141
+ 'data-testid': 'cm-comment',
142
+ 'data-comment-id': id,
143
+ },
144
+ });
145
+ };
127
146
 
128
147
  /**
129
148
  * Decorate ranges.
@@ -145,17 +164,39 @@ const commentsDecorations = EditorView.decorations.compute([commentsState], (sta
145
164
  return undefined;
146
165
  }
147
166
 
148
- if (comment.comment.id === current) {
149
- return commentCurrentMark.range(range.from, range.to);
150
- } else {
151
- return commentMark.range(range.from, range.to);
152
- }
167
+ const mark = createCommentMark(comment.comment.id, comment.comment.id === current);
168
+ return mark.range(range.from, range.to);
153
169
  })
154
170
  .filter(nonNullable);
155
171
 
156
172
  return Decoration.set(decorations);
157
173
  });
158
174
 
175
+ const commentClickedEffect = StateEffect.define<string>();
176
+
177
+ const handleCommentClick = EditorView.domEventHandlers({
178
+ click: (event, view) => {
179
+ let target = event.target as HTMLElement;
180
+ const editorRoot = view.dom;
181
+
182
+ // Traverse up the DOM tree looking for an element with data-comment-id
183
+ // Stop if we reach the editor root or find the comment id
184
+ while (target && target !== editorRoot && !target.hasAttribute('data-comment-id')) {
185
+ target = target.parentElement as HTMLElement;
186
+ }
187
+
188
+ // Check if we found a comment id and are still within the editor
189
+ if (target && target !== editorRoot) {
190
+ const commentId = target.getAttribute('data-comment-id');
191
+ if (commentId) {
192
+ view.dispatch({ effects: commentClickedEffect.of(commentId) });
193
+ return true;
194
+ }
195
+ }
196
+
197
+ return false;
198
+ },
199
+ });
159
200
  //
160
201
  // Cut-and-paste.
161
202
  //
@@ -372,6 +413,7 @@ export const comments = (options: CommentsOptions = {}): Extension => {
372
413
  documentId.of(options.id),
373
414
  commentsState,
374
415
  commentsDecorations,
416
+ handleCommentClick,
375
417
  styles,
376
418
 
377
419
  //
@@ -533,6 +575,13 @@ export const selectionOverlapsComment = (state: EditorState): boolean => {
533
575
  return false;
534
576
  };
535
577
 
578
+ /**
579
+ * Check if there is one or more active (non-empty) selections in the editor state.
580
+ */
581
+ const hasActiveSelection = (state: EditorState): boolean => {
582
+ return state.selection.ranges.some((range) => !range.empty);
583
+ };
584
+
536
585
  /**
537
586
  * Update comments state field.
538
587
  */
@@ -554,18 +603,45 @@ export const useComments = (view: EditorView | null | undefined, id: string, com
554
603
  * Hook provides an extension to compute the current comment state under the selection.
555
604
  * NOTE(Zan): I think this conceptually belongs in 'formatting.ts' but we can't import ESM modules there atm.
556
605
  */
557
- export const useCommentState = (): [boolean, Extension] => {
558
- const [comment, setComment] = useState<boolean>(false);
606
+ export const useCommentState = (): [{ comment: boolean; selection: boolean }, Extension] => {
607
+ const [state, setState] = useState<{ comment: boolean; selection: boolean }>({
608
+ comment: false,
609
+ selection: false,
610
+ });
559
611
 
560
612
  const observer = useMemo(
561
613
  () =>
562
614
  EditorView.updateListener.of((update) => {
563
615
  if (update.docChanged || update.selectionSet) {
564
- setComment(() => selectionOverlapsComment(update.state));
616
+ setState({
617
+ comment: selectionOverlapsComment(update.state),
618
+ selection: hasActiveSelection(update.state),
619
+ });
565
620
  }
566
621
  }),
567
622
  [],
568
623
  );
569
624
 
570
- return [comment, observer];
625
+ return [state, observer];
626
+ };
627
+
628
+ /**
629
+ * Hook provides an extension to listen for comment clicks and invoke a handler.
630
+ */
631
+ export const useCommentClickListener = (onCommentClick: (commentId: string) => void): Extension => {
632
+ const observer = useMemo(
633
+ () =>
634
+ EditorView.updateListener.of((update) => {
635
+ update.transactions.forEach((transaction) => {
636
+ transaction.effects.forEach((effect) => {
637
+ if (effect.is(commentClickedEffect)) {
638
+ onCommentClick(effect.value);
639
+ }
640
+ });
641
+ });
642
+ }),
643
+ [onCommentClick],
644
+ );
645
+
646
+ return observer;
571
647
  };