@dxos/react-ui-editor 0.3.11-main.dafb7f2 → 0.3.11-main.e0dc42b

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.
Files changed (46) hide show
  1. package/dist/lib/browser/index.mjs +137 -137
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts +9 -5
  5. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -1
  6. package/dist/types/src/components/TextEditor/TextEditor.d.ts +10 -10
  7. package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
  8. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +2 -2
  9. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
  10. package/dist/types/src/components/TextEditor/yjs.stories.d.ts +1 -1
  11. package/dist/types/src/extensions/autocomplete.d.ts.map +1 -1
  12. package/dist/types/src/extensions/automerge/index.d.ts +1 -0
  13. package/dist/types/src/extensions/automerge/index.d.ts.map +1 -1
  14. package/dist/types/src/extensions/code.d.ts.map +1 -1
  15. package/dist/types/src/extensions/comments.d.ts +1 -1
  16. package/dist/types/src/extensions/comments.d.ts.map +1 -1
  17. package/dist/types/src/extensions/hr.d.ts.map +1 -1
  18. package/dist/types/src/extensions/index.d.ts +0 -1
  19. package/dist/types/src/extensions/index.d.ts.map +1 -1
  20. package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -1
  21. package/dist/types/src/index.d.ts +3 -0
  22. package/dist/types/src/index.d.ts.map +1 -1
  23. package/dist/types/src/styles/tokens.d.ts +1 -0
  24. package/dist/types/src/styles/tokens.d.ts.map +1 -1
  25. package/package.json +23 -22
  26. package/src/components/TextEditor/MarkdownEditor.stories.tsx +18 -15
  27. package/src/components/TextEditor/TextEditor.stories.tsx +3 -4
  28. package/src/components/TextEditor/TextEditor.tsx +59 -45
  29. package/src/extensions/autocomplete.ts +18 -17
  30. package/src/extensions/automerge/index.ts +1 -0
  31. package/src/extensions/awareness.ts +14 -15
  32. package/src/extensions/code.ts +14 -7
  33. package/src/extensions/comments.ts +45 -47
  34. package/src/extensions/hr.ts +4 -5
  35. package/src/extensions/image.ts +2 -2
  36. package/src/extensions/index.ts +0 -1
  37. package/src/extensions/link.ts +5 -5
  38. package/src/extensions/markdown/highlight.ts +3 -4
  39. package/src/extensions/table.ts +6 -3
  40. package/src/extensions/tasklist.ts +2 -2
  41. package/src/hooks/useTextModel.ts +1 -1
  42. package/src/index.ts +3 -0
  43. package/src/styles/tokens.ts +3 -0
  44. package/dist/types/src/extensions/mermaid.d.ts +0 -3
  45. package/dist/types/src/extensions/mermaid.d.ts.map +0 -1
  46. package/src/extensions/mermaid.ts +0 -11
@@ -7,16 +7,15 @@ import {
7
7
  hoverTooltip,
8
8
  keymap,
9
9
  type Command,
10
- type DecorationSet,
11
- type ViewUpdate,
12
10
  Decoration,
11
+ type DecorationSet,
13
12
  EditorView,
14
13
  MatchDecorator,
14
+ type Rect,
15
+ type ViewUpdate,
15
16
  ViewPlugin,
16
17
  WidgetType,
17
- type Rect,
18
18
  } from '@codemirror/view';
19
- import get from 'lodash.get';
20
19
  import sortBy from 'lodash.sortby';
21
20
 
22
21
  import { debounce } from '@dxos/async';
@@ -25,7 +24,7 @@ import { log } from '@dxos/log';
25
24
  import { nonNullable } from '@dxos/util';
26
25
 
27
26
  import { type CommentRange, type EditorModel, type Range } from '../hooks';
28
- import { tokens } from '../styles';
27
+ import { getToken } from '../styles';
29
28
  import { callbackWrapper, CursorConverter } from '../util';
30
29
 
31
30
  // TODO(burdon): Handle delete, cut, copy, and paste (separately) text that includes comment range.
@@ -43,10 +42,10 @@ const styles = EditorView.baseTheme({
43
42
  backgroundColor: 'orange',
44
43
  },
45
44
  '& .cm-comment': {
46
- backgroundColor: get(tokens, 'extend.colors.yellow.50'),
45
+ backgroundColor: getToken('extend.colors.yellow.50'),
47
46
  },
48
47
  '& .cm-comment-active': {
49
- backgroundColor: get(tokens, 'extend.colors.yellow.100'),
48
+ backgroundColor: getToken('extend.colors.yellow.100'),
50
49
  },
51
50
  });
52
51
 
@@ -160,17 +159,13 @@ const highlightDecorations = EditorView.decorations.compute([commentsStateField]
160
159
  * May correspond to a markdown bookmark.
161
160
  */
162
161
  class BookmarkWidget extends WidgetType {
163
- constructor(
164
- private readonly _anchor: number,
165
- private readonly _id: string,
166
- private readonly _handleClick?: () => void,
167
- ) {
162
+ constructor(private readonly _id: string, private readonly _handleClick?: () => void) {
168
163
  super();
169
164
  invariant(this._id);
170
165
  }
171
166
 
172
- override eq(other: BookmarkWidget) {
173
- return this._anchor === other._anchor && this._id === other._id;
167
+ override eq(other: this) {
168
+ return this._id === other._id;
174
169
  }
175
170
 
176
171
  override toDOM() {
@@ -198,7 +193,7 @@ export type CommentsOptions = {
198
193
  /**
199
194
  * Called to render tooltip.
200
195
  */
201
- onHover?: (el: Element) => void;
196
+ onHover?: (el: Element, shortcut: string) => void;
202
197
  };
203
198
 
204
199
  /**
@@ -214,6 +209,8 @@ export type CommentsOptions = {
214
209
  * 6). Optionally, implements a hoverTooltip to show hints when creating a selection range.
215
210
  */
216
211
  export const comments = (options: CommentsOptions = {}): Extension => {
212
+ const { key: shortcut = "meta-'" } = options;
213
+
217
214
  const handleSelect = debounce((state: CommentsState) => options.onSelect?.(state), 200);
218
215
 
219
216
  /**
@@ -258,38 +255,37 @@ export const comments = (options: CommentsOptions = {}): Extension => {
258
255
  /**
259
256
  * Bookmark widget decoration.
260
257
  */
261
- const bookmarksViewPlugin = ViewPlugin.fromClass(
262
- class BookmarkViewPlugin {
263
- // Match markdown footnotes (option).
264
- // https://www.markdownguide.org/extended-syntax/#footnotes
265
- static bookmarkMatcher = new MatchDecorator({
266
- regexp: /\[\^(\w+)\]/g,
267
- decoration: (match, view, pos) => {
268
- const id = match[1];
269
- return Decoration.replace({
270
- id,
271
- widget: new BookmarkWidget(pos, id),
258
+ const bookmarksViewPlugin = options.footnote
259
+ ? ViewPlugin.fromClass(
260
+ class BookmarkViewPlugin {
261
+ // Match markdown footnotes (option).
262
+ // https://www.markdownguide.org/extended-syntax/#footnotes
263
+ static bookmarkMatcher = new MatchDecorator({
264
+ regexp: /\[\^(\w+)\]/g,
265
+ decoration: (match) => {
266
+ const id = match[1];
267
+ return Decoration.replace({ id, widget: new BookmarkWidget(id) });
268
+ },
272
269
  });
273
- },
274
- });
275
270
 
276
- bookmarks: DecorationSet;
277
- constructor(view: EditorView) {
278
- this.bookmarks = BookmarkViewPlugin.bookmarkMatcher.createDeco(view);
279
- }
271
+ bookmarks: DecorationSet;
272
+ constructor(view: EditorView) {
273
+ this.bookmarks = BookmarkViewPlugin.bookmarkMatcher.createDeco(view);
274
+ }
280
275
 
281
- update(update: ViewUpdate) {
282
- this.bookmarks = BookmarkViewPlugin.bookmarkMatcher.updateDeco(update, this.bookmarks);
283
- }
284
- },
285
- {
286
- decorations: (instance) => instance.bookmarks,
287
- provide: (plugin) =>
288
- EditorView.atomicRanges.of((view) => {
289
- return view.plugin(plugin)?.bookmarks || Decoration.none;
290
- }),
291
- },
292
- );
276
+ update(update: ViewUpdate) {
277
+ this.bookmarks = BookmarkViewPlugin.bookmarkMatcher.updateDeco(update, this.bookmarks);
278
+ }
279
+ },
280
+ {
281
+ decorations: (instance) => instance.bookmarks,
282
+ provide: (plugin) =>
283
+ EditorView.atomicRanges.of((view) => {
284
+ return view.plugin(plugin)?.bookmarks || Decoration.none;
285
+ }),
286
+ },
287
+ )
288
+ : [];
293
289
 
294
290
  return [
295
291
  bookmarksViewPlugin,
@@ -303,7 +299,7 @@ export const comments = (options: CommentsOptions = {}): Extension => {
303
299
  options.onCreate
304
300
  ? keymap.of([
305
301
  {
306
- key: options?.key ?? "meta-'",
302
+ key: shortcut,
307
303
  run: callbackWrapper(createCommentThread),
308
304
  },
309
305
  ])
@@ -325,7 +321,7 @@ export const comments = (options: CommentsOptions = {}): Extension => {
325
321
  create: () => {
326
322
  // TODO(burdon): Dispatch to react callback to render (or use SSR)?
327
323
  const el = document.createElement('div');
328
- options.onHover?.(el);
324
+ options.onHover?.(el, shortcut);
329
325
  return { dom: el, offset: { x: 0, y: 8 } };
330
326
  },
331
327
  };
@@ -393,6 +389,8 @@ export const comments = (options: CommentsOptions = {}): Extension => {
393
389
  }
394
390
  });
395
391
 
392
+ // TODO(burdon): Fire debounced callback if range selected (to show hint).
393
+
396
394
  if (selected.active !== active || selected.closest !== closest) {
397
395
  view.dispatch({ effects: setSelection.of(selected) });
398
396
 
@@ -417,5 +415,5 @@ const getRangeFromCursor = (cursorConverter: CursorConverter, cursor: string) =>
417
415
  const parts = cursor.split(':');
418
416
  const from = cursorConverter.fromCursor(parts[0]);
419
417
  const to = cursorConverter.fromCursor(parts[1]);
420
- return from && to ? { from, to } : undefined;
418
+ return from !== undefined && to !== undefined ? { from, to } : undefined;
421
419
  };
@@ -11,16 +11,15 @@ import {
11
11
  type ViewUpdate,
12
12
  WidgetType,
13
13
  } from '@codemirror/view';
14
- import get from 'lodash.get';
15
14
 
16
- import { tokens } from '../styles';
15
+ import { getToken } from '../styles';
17
16
 
18
17
  // TODO(burdon): Reconcile with theme.
19
18
  const styles = EditorView.baseTheme({
20
19
  '& .cm-hr': {
21
20
  // TODO(burdon): ???
22
21
  // Note that block-level decorations should not have vertical margins,
23
- borderBottom: `1px solid ${get(tokens, 'extend.colors.neutral.200')}`,
22
+ borderBottom: `1px solid ${getToken('extend.colors.neutral.200')}`,
24
23
  },
25
24
  });
26
25
 
@@ -29,11 +28,11 @@ class HorizontalRuleWidget extends WidgetType {
29
28
  super();
30
29
  }
31
30
 
32
- override eq(other: WidgetType) {
31
+ override eq(other: this) {
33
32
  return this._pos === (other as any as HorizontalRuleWidget)._pos;
34
33
  }
35
34
 
36
- toDOM(view: EditorView) {
35
+ override toDOM(view: EditorView) {
37
36
  // TODO(burdon): Create <hr> element? Does this clash with markdown parser?
38
37
  const el = document.createElement('div');
39
38
  el.className = 'cm-hr';
@@ -72,11 +72,11 @@ class ImageWidget extends WidgetType {
72
72
  super();
73
73
  }
74
74
 
75
- override eq(other: WidgetType) {
75
+ override eq(other: this) {
76
76
  return this._url === (other as any as ImageWidget)._url;
77
77
  }
78
78
 
79
- toDOM(view: EditorView) {
79
+ override toDOM(view: EditorView) {
80
80
  const img = document.createElement('img');
81
81
  img.setAttribute('src', this._url);
82
82
  img.setAttribute('class', 'cm-image');
@@ -15,7 +15,6 @@ export * from './link';
15
15
  export * from './listener';
16
16
  export * from './markdown';
17
17
  export * from './mention';
18
- export * from './mermaid';
19
18
  export * from './table';
20
19
  export * from './tasklist';
21
20
  export * from './typewriter';
@@ -93,11 +93,11 @@ class LinkButton extends WidgetType {
93
93
  super();
94
94
  }
95
95
 
96
- override eq(other: LinkButton) {
96
+ override eq(other: this) {
97
97
  return this._url === other._url;
98
98
  }
99
99
 
100
- toDOM(view: EditorView) {
100
+ override toDOM(view: EditorView) {
101
101
  const el = document.createElement('span');
102
102
  this._onAttach!(el, this._url);
103
103
  return el;
@@ -109,11 +109,11 @@ class LinkText extends WidgetType {
109
109
  super();
110
110
  }
111
111
 
112
- override eq(other: LinkText) {
112
+ override eq(other: this) {
113
113
  return this._url === other._url;
114
114
  }
115
115
 
116
- toDOM(view: EditorView) {
116
+ override toDOM(view: EditorView) {
117
117
  const link = document.createElement('a');
118
118
  link.setAttribute('class', 'cm-link');
119
119
  link.textContent = this._text;
@@ -158,7 +158,7 @@ const buildDecorations = (view: EditorView, options: LinkOptions): DecorationSet
158
158
  return false;
159
159
  }
160
160
 
161
- if (state.readOnly || cursor < node.from || cursor > node.to) {
161
+ if (!view.hasFocus || state.readOnly || cursor < node.from || cursor > node.to) {
162
162
  builder.add(
163
163
  node.from,
164
164
  node.to,
@@ -6,19 +6,18 @@ import { markdownLanguage } from '@codemirror/lang-markdown';
6
6
  import { HighlightStyle } from '@codemirror/language';
7
7
  import { tags, styleTags, Tag } from '@lezer/highlight';
8
8
  import { type MarkdownConfig, Table } from '@lezer/markdown';
9
- import get from 'lodash.get';
10
9
 
11
10
  import {
12
11
  blockquote,
13
12
  bold,
14
13
  code,
15
14
  codeMark,
15
+ getToken,
16
16
  heading,
17
17
  inlineUrl,
18
18
  italic,
19
19
  mark,
20
20
  strikethrough,
21
- tokens,
22
21
  } from '../../styles';
23
22
 
24
23
  /**
@@ -193,12 +192,12 @@ export const markdownHighlightStyle = (readonly?: boolean) => {
193
192
  // Main content, paragraphs, etc.
194
193
  // {
195
194
  // tag: [tags.content],
196
- // fontFamily: get(tokens, 'fontFamily.body', []).join(','),
195
+ // fontFamily: getToken('fontFamily.body', []).join(','),
197
196
  // },
198
197
  ],
199
198
  {
200
199
  scope: markdownLanguage,
201
- all: { fontFamily: get(tokens, 'fontFamily.body', []).join(',') },
200
+ all: { fontFamily: getToken('fontFamily.body', []).join(',') },
202
201
  },
203
202
  );
204
203
  };
@@ -81,7 +81,6 @@ const update = (state: EditorState, options: TableOptions) => {
81
81
  table.from,
82
82
  table.to,
83
83
  Decoration.replace({
84
- block: true,
85
84
  widget: new TableWidget(table),
86
85
  }),
87
86
  );
@@ -98,14 +97,14 @@ class TableWidget extends WidgetType {
98
97
  super();
99
98
  }
100
99
 
101
- override eq(other: TableWidget) {
100
+ override eq(other: this) {
102
101
  return (
103
102
  this._table.header?.join() === other._table.header?.join() &&
104
103
  this._table.rows?.join() === other._table.rows?.join()
105
104
  );
106
105
  }
107
106
 
108
- toDOM(view: EditorView) {
107
+ override toDOM(view: EditorView) {
109
108
  const table = document.createElement('table');
110
109
 
111
110
  {
@@ -132,4 +131,8 @@ class TableWidget extends WidgetType {
132
131
 
133
132
  return table;
134
133
  }
134
+
135
+ override ignoreEvent(e: Event) {
136
+ return !/^mouse/.test(e.type);
137
+ }
135
138
  }
@@ -29,11 +29,11 @@ class CheckboxWidget extends WidgetType {
29
29
  super();
30
30
  }
31
31
 
32
- override eq(other: CheckboxWidget) {
32
+ override eq(other: this) {
33
33
  return this._checked === other._checked && this._indent === other._indent;
34
34
  }
35
35
 
36
- toDOM(view: EditorView) {
36
+ override toDOM(view: EditorView) {
37
37
  const wrap = document.createElement('span');
38
38
  wrap.className = 'cm-task-item';
39
39
  wrap.setAttribute('aria-hidden', 'true');
@@ -66,7 +66,7 @@ export type UseTextModelProps = {
66
66
  // TODO(wittjosiah): Factor out to common package? @dxos/react-client?
67
67
  export const useTextModel = (props: UseTextModelProps): EditorModel | undefined => {
68
68
  const { identity, space, text } = props;
69
- const [model, setModel] = useState<EditorModel | undefined>(() => createModel(props));
69
+ const [model, setModel] = useState<EditorModel | undefined>();
70
70
  useEffect(() => setModel(createModel(props)), [identity, space, text]);
71
71
  return model;
72
72
  };
package/src/index.ts CHANGED
@@ -5,8 +5,11 @@
5
5
  export { TextKind } from '@dxos/protocols/proto/dxos/echo/model/text';
6
6
  export { Doc, YText, YXmlFragment } from '@dxos/text-model';
7
7
  export { type Extension } from '@codemirror/state';
8
+ export { type EditorView } from '@codemirror/view';
8
9
 
9
10
  export * from './components';
10
11
  export * from './extensions';
11
12
  export * from './hooks';
13
+ export { getToken } from './styles';
12
14
  export * from './themes';
15
+ export * from './util';
@@ -2,6 +2,7 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
+ import get from 'lodash.get';
5
6
  import type { StyleSpec } from 'style-mod';
6
7
 
7
8
  import { tailwindConfig, type TailwindConfig } from '@dxos/react-ui-theme';
@@ -12,3 +13,5 @@ export type ThemeStyles = {
12
13
 
13
14
  // TODO(thure): Why export the whole theme? Can this be done differently?
14
15
  export const tokens: TailwindConfig['theme'] = tailwindConfig({}).theme;
16
+
17
+ export const getToken = (path: string, defaultValue: any = undefined) => get(tokens, path, defaultValue);
@@ -1,3 +0,0 @@
1
- import { type Extension } from '@codemirror/state';
2
- export declare const mermaid: () => Extension;
3
- //# sourceMappingURL=mermaid.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"mermaid.d.ts","sourceRoot":"","sources":["../../../../src/extensions/mermaid.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAInD,eAAO,MAAM,OAAO,QAAO,SAE1B,CAAC"}
@@ -1,11 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- //
4
-
5
- import { type Extension } from '@codemirror/state';
6
-
7
- // TODO(burdon): How will this interact with the code extension?
8
- // TODO(burdon): Move to mermaid-plugin (which should contribute this extension).
9
- export const mermaid = (): Extension => {
10
- return [];
11
- };