@dxos/react-ui-editor 0.6.3-next.2f65b78 → 0.6.3-staging.0f23fb2

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.
@@ -12,11 +12,21 @@ import {
12
12
  type ChangeDesc,
13
13
  type EditorState,
14
14
  } from '@codemirror/state';
15
- import { hoverTooltip, keymap, type Command, Decoration, EditorView, type Rect } from '@codemirror/view';
15
+ import {
16
+ hoverTooltip,
17
+ keymap,
18
+ type Command,
19
+ Decoration,
20
+ EditorView,
21
+ type Rect,
22
+ type PluginValue,
23
+ ViewPlugin,
24
+ } from '@codemirror/view';
16
25
  import sortBy from 'lodash.sortby';
17
26
  import { useEffect, useMemo, useState } from 'react';
18
27
 
19
- import { debounce } from '@dxos/async';
28
+ import { type ThreadType } from '@braneframe/types';
29
+ import { debounce, type UnsubscribeCallback } from '@dxos/async';
20
30
  import { log } from '@dxos/log';
21
31
  import { nonNullable } from '@dxos/util';
22
32
 
@@ -108,30 +118,42 @@ const styles = EditorView.baseTheme({
108
118
  borderRadius: '2px',
109
119
  transition: 'background-color 0.1s ease',
110
120
  },
111
- '&light .cm-comment, &light .cm-comment-current': {
121
+ // Light theme.
122
+ '&light .cm-comment': {
123
+ backgroundColor: getToken('extend.colors.yellow.50'),
112
124
  mixBlendMode: 'darken',
113
125
  borderColor: getToken('extend.colors.yellow.100'),
114
126
  },
115
- '&dark .cm-comment, &dark .cm-comment-current': {
116
- mixBlendMode: 'plus-lighter',
117
- borderColor: getToken('extend.colors.yellow.900'),
127
+ '&light .cm-comment:hover': { backgroundColor: getToken('extend.colors.yellow.100') },
128
+ '&light .cm-comment-current': {
129
+ backgroundColor: getToken('extend.colors.primary.100'),
130
+ borderColor: getToken('extend.colors.primary.200'),
118
131
  },
119
- '&light .cm-comment': {
120
- backgroundColor: getToken('extend.colors.yellow.50'),
132
+ '&light .cm-comment-current:hover': {
133
+ backgroundColor: getToken('extend.colors.primary.150'),
134
+ borderColor: getToken('extend.colors.primary.250'),
121
135
  },
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') },
136
+
137
+ // Dark theme.
125
138
  '&dark .cm-comment': {
126
139
  color: getToken('extend.colors.yellow.50'),
127
- backgroundColor: getToken('extend.colors.yellow.900'),
140
+ backgroundColor: getToken('extend.colors.yellow.800'),
141
+ borderColor: getToken('extend.colors.yellow.700'),
142
+ mixBlendMode: 'plus-lighter',
143
+ },
144
+ '&dark .cm-comment:hover': {
145
+ backgroundColor: getToken('extend.colors.yellow.700'),
146
+ borderColor: getToken('extend.colors.yellow.650'),
128
147
  },
129
- '&dark .cm-comment:hover': { backgroundColor: getToken('extend.colors.yellow.800') },
130
148
  '&dark .cm-comment-current': {
131
- color: getToken('extend.colors.yellow.100'),
132
- backgroundColor: getToken('extend.colors.yellow.950'),
149
+ color: getToken('extend.colors.primary.50'),
150
+ backgroundColor: getToken('extend.colors.primary.800'),
151
+ borderColor: getToken('extend.colors.primary.700'),
152
+ },
153
+ '&dark .cm-comment-current:hover': {
154
+ backgroundColor: getToken('extend.colors.primary.700'),
155
+ borderColor: getToken('extend.colors.primary.650'),
133
156
  },
134
- '&dark .cm-comment-current:hover': { backgroundColor: getToken('extend.colors.yellow.900') },
135
157
  });
136
158
 
137
159
  const createCommentMark = (id: string, isCurrent: boolean) =>
@@ -585,34 +607,52 @@ export const selectionOverlapsComment = (state: EditorState): boolean => {
585
607
  return false;
586
608
  };
587
609
 
588
- /**
589
- * Check if there is one or more active (non-empty) selections in the editor state.
590
- */
591
610
  const hasActiveSelection = (state: EditorState): boolean => {
592
611
  return state.selection.ranges.some((range) => !range.empty);
593
612
  };
594
613
 
595
- /**
596
- * Update comments state field.
597
- */
598
- export const useComments = (view: EditorView | null | undefined, id: string, comments?: Comment[]) => {
599
- useEffect(() => {
600
- if (view) {
601
- // Check same document.
602
- // NOTE: Hook might be called before editor state is updated.
614
+ class ExternalCommentSync implements PluginValue {
615
+ private unsubscribe: () => void;
616
+
617
+ constructor(
618
+ view: EditorView,
619
+ id: string,
620
+ subscribe: (sink: () => void) => UnsubscribeCallback,
621
+ getThreads: () => ThreadType[],
622
+ ) {
623
+ const updateComments = () => {
624
+ const threads = getThreads();
625
+ const comments = threads
626
+ .filter(nonNullable)
627
+ .filter((thread) => thread.anchor)
628
+ .map((thread) => ({ id: thread.id, cursor: thread.anchor! }));
629
+
603
630
  if (id === view.state.facet(documentId)) {
604
- view.dispatch({
605
- effects: setComments.of({ id, comments: comments ?? [] }),
606
- });
631
+ queueMicrotask(() => view.dispatch({ effects: setComments.of({ id, comments }) }));
607
632
  }
608
- }
609
- }, [id, view, comments]);
610
- };
633
+ };
634
+
635
+ this.unsubscribe = subscribe(updateComments);
636
+ }
637
+
638
+ destroy = () => {
639
+ this.unsubscribe();
640
+ };
641
+ }
642
+
643
+ export const createExternalCommentSync = (
644
+ id: string,
645
+ subscribe: (sink: () => void) => UnsubscribeCallback,
646
+ getThreads: () => ThreadType[],
647
+ ): Extension =>
648
+ ViewPlugin.fromClass(
649
+ class {
650
+ constructor(view: EditorView) {
651
+ return new ExternalCommentSync(view, id, subscribe, getThreads);
652
+ }
653
+ },
654
+ );
611
655
 
612
- /**
613
- * Hook provides an extension to compute the current comment state under the selection.
614
- * NOTE(Zan): I think this conceptually belongs in 'formatting.ts' but we can't import ESM modules there atm.
615
- */
616
656
  export const useCommentState = (): [{ comment: boolean; selection: boolean }, Extension] => {
617
657
  const [state, setState] = useState<{ comment: boolean; selection: boolean }>({
618
658
  comment: false,
@@ -635,6 +675,24 @@ export const useCommentState = (): [{ comment: boolean; selection: boolean }, Ex
635
675
  return [state, observer];
636
676
  };
637
677
 
678
+ /**
679
+ * @deprecated This hook will be removed in future versions. Use the new comment sync extension instead.
680
+ * Update comments state field.
681
+ */
682
+ export const useComments = (view: EditorView | null | undefined, id: string, comments?: Comment[]) => {
683
+ useEffect(() => {
684
+ if (view) {
685
+ // Check same document.
686
+ // NOTE: Hook might be called before editor state is updated.
687
+ if (id === view.state.facet(documentId)) {
688
+ view.dispatch({
689
+ effects: setComments.of({ id, comments: comments ?? [] }),
690
+ });
691
+ }
692
+ }
693
+ });
694
+ };
695
+
638
696
  /**
639
697
  * Hook provides an extension to listen for comment clicks and invoke a handler.
640
698
  */