@dxos/react-ui-editor 0.3.10-next.6fe28e7 → 0.3.10-next.86bde39

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 (125) hide show
  1. package/dist/lib/browser/index.mjs +868 -738
  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 +38 -0
  5. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -0
  6. package/dist/types/src/components/TextEditor/TextEditor.d.ts +5 -2
  7. package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
  8. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +9 -4
  9. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
  10. package/dist/types/src/components/TextEditor/extensions/autocomplete.d.ts +10 -0
  11. package/dist/types/src/components/TextEditor/extensions/autocomplete.d.ts.map +1 -0
  12. package/dist/types/src/components/TextEditor/extensions/{basic/bundle.d.ts → basic.d.ts} +1 -1
  13. package/dist/types/src/components/TextEditor/extensions/basic.d.ts.map +1 -0
  14. package/dist/types/src/components/TextEditor/extensions/comments.d.ts +14 -0
  15. package/dist/types/src/components/TextEditor/extensions/comments.d.ts.map +1 -0
  16. package/dist/types/src/components/TextEditor/extensions/{hyperlink/hyperlinkWidget.d.ts → experimental.d.ts} +2 -2
  17. package/dist/types/src/components/TextEditor/extensions/experimental.d.ts.map +1 -0
  18. package/dist/types/src/components/TextEditor/extensions/index.d.ts +6 -2
  19. package/dist/types/src/components/TextEditor/extensions/index.d.ts.map +1 -1
  20. package/dist/types/src/components/TextEditor/extensions/link.d.ts +11 -0
  21. package/dist/types/src/components/TextEditor/extensions/link.d.ts.map +1 -0
  22. package/dist/types/src/components/TextEditor/extensions/listener.d.ts +7 -0
  23. package/dist/types/src/components/TextEditor/extensions/listener.d.ts.map +1 -0
  24. package/dist/types/src/components/TextEditor/extensions/markdown/bundle.d.ts.map +1 -1
  25. package/dist/types/src/components/TextEditor/extensions/markdown/highlight.d.ts +31 -0
  26. package/dist/types/src/components/TextEditor/extensions/markdown/highlight.d.ts.map +1 -0
  27. package/dist/types/src/components/TextEditor/extensions/markdown/index.d.ts +1 -1
  28. package/dist/types/src/components/TextEditor/extensions/markdown/index.d.ts.map +1 -1
  29. package/dist/types/src/components/TextEditor/extensions/tasklist.d.ts +2 -0
  30. package/dist/types/src/components/TextEditor/extensions/tasklist.d.ts.map +1 -0
  31. package/dist/types/src/components/TextEditor/extensions/tooltip.d.ts +12 -0
  32. package/dist/types/src/components/TextEditor/extensions/tooltip.d.ts.map +1 -0
  33. package/dist/types/src/components/TextEditor/index.d.ts +2 -0
  34. package/dist/types/src/components/TextEditor/index.d.ts.map +1 -1
  35. package/dist/types/src/components/TextEditor/themes/default.d.ts +20 -0
  36. package/dist/types/src/components/TextEditor/themes/default.d.ts.map +1 -0
  37. package/dist/types/src/components/TextEditor/themes/index.d.ts +2 -0
  38. package/dist/types/src/components/TextEditor/themes/index.d.ts.map +1 -0
  39. package/dist/types/src/components/TextEditor/yjs.stories.d.ts +1 -0
  40. package/dist/types/src/components/TextEditor/yjs.stories.d.ts.map +1 -1
  41. package/dist/types/src/hooks/automerge/PatchSemaphore.d.ts.map +1 -0
  42. package/dist/types/src/hooks/automerge/amToCodemirror.d.ts.map +1 -0
  43. package/dist/types/src/hooks/automerge/automerge.stories.d.ts.map +1 -0
  44. package/dist/types/src/hooks/automerge/codeMirrorToAm.d.ts.map +1 -0
  45. package/dist/types/src/hooks/automerge/handle.d.ts.map +1 -0
  46. package/dist/types/src/hooks/automerge/index.d.ts.map +1 -0
  47. package/dist/types/src/hooks/automerge/plugin.d.ts.map +1 -0
  48. package/dist/types/src/hooks/index.d.ts +0 -1
  49. package/dist/types/src/hooks/index.d.ts.map +1 -1
  50. package/dist/types/src/hooks/useTextModel.d.ts +4 -4
  51. package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
  52. package/dist/types/src/styles/markdown.d.ts +12 -10
  53. package/dist/types/src/styles/markdown.d.ts.map +1 -1
  54. package/dist/types/src/testing/replicator.d.ts.map +1 -1
  55. package/package.json +20 -32
  56. package/src/components/TextEditor/MarkdownEditor.stories.tsx +273 -0
  57. package/src/components/TextEditor/TextEditor.stories.tsx +46 -43
  58. package/src/components/TextEditor/TextEditor.tsx +65 -44
  59. package/src/components/TextEditor/extensions/autocomplete.ts +74 -0
  60. package/src/components/TextEditor/extensions/{basic/bundle.ts → basic.ts} +4 -2
  61. package/src/components/TextEditor/extensions/comments.ts +173 -0
  62. package/src/components/TextEditor/extensions/{hyperlink/hyperlinkWidget.tsx → experimental.tsx} +18 -2
  63. package/src/components/TextEditor/extensions/index.ts +6 -2
  64. package/src/components/TextEditor/extensions/{hyperlink/hyperlinkDecoration.ts → link.ts} +76 -31
  65. package/src/components/TextEditor/extensions/{change.ts → listener.ts} +4 -3
  66. package/src/components/TextEditor/extensions/markdown/bundle.ts +21 -25
  67. package/src/components/TextEditor/extensions/markdown/highlight.ts +167 -0
  68. package/src/components/TextEditor/extensions/markdown/index.ts +1 -1
  69. package/src/components/TextEditor/extensions/tasklist.ts +107 -0
  70. package/src/components/TextEditor/extensions/{hyperlink/hyperlinkTooltip.tsx → tooltip.tsx} +9 -9
  71. package/src/components/TextEditor/index.ts +3 -0
  72. package/src/components/TextEditor/themes/default.ts +185 -0
  73. package/src/components/TextEditor/themes/index.ts +5 -0
  74. package/src/components/TextEditor/yjs.stories.tsx +1 -0
  75. package/src/{automerge → hooks/automerge}/automerge.stories.tsx +2 -1
  76. package/src/hooks/index.ts +0 -1
  77. package/src/hooks/useTextModel.ts +52 -39
  78. package/src/styles/markdown.ts +26 -24
  79. package/src/testing/replicator.ts +6 -6
  80. package/dist/types/src/automerge/automerge-plugin/PatchSemaphore.d.ts.map +0 -1
  81. package/dist/types/src/automerge/automerge-plugin/amToCodemirror.d.ts.map +0 -1
  82. package/dist/types/src/automerge/automerge-plugin/codeMirrorToAm.d.ts.map +0 -1
  83. package/dist/types/src/automerge/automerge-plugin/handle.d.ts.map +0 -1
  84. package/dist/types/src/automerge/automerge-plugin/index.d.ts.map +0 -1
  85. package/dist/types/src/automerge/automerge-plugin/plugin.d.ts.map +0 -1
  86. package/dist/types/src/automerge/automerge.stories.d.ts.map +0 -1
  87. package/dist/types/src/components/TextEditor/extensions/basic/bundle.d.ts.map +0 -1
  88. package/dist/types/src/components/TextEditor/extensions/basic/index.d.ts +0 -3
  89. package/dist/types/src/components/TextEditor/extensions/basic/index.d.ts.map +0 -1
  90. package/dist/types/src/components/TextEditor/extensions/basic/theme.d.ts +0 -10
  91. package/dist/types/src/components/TextEditor/extensions/basic/theme.d.ts.map +0 -1
  92. package/dist/types/src/components/TextEditor/extensions/change.d.ts +0 -7
  93. package/dist/types/src/components/TextEditor/extensions/change.d.ts.map +0 -1
  94. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkDecoration.d.ts +0 -11
  95. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkDecoration.d.ts.map +0 -1
  96. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkTooltip.d.ts +0 -8
  97. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkTooltip.d.ts.map +0 -1
  98. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkWidget.d.ts.map +0 -1
  99. package/dist/types/src/components/TextEditor/extensions/hyperlink/index.d.ts +0 -4
  100. package/dist/types/src/components/TextEditor/extensions/hyperlink/index.d.ts.map +0 -1
  101. package/dist/types/src/components/TextEditor/extensions/markdown/tags.d.ts +0 -17
  102. package/dist/types/src/components/TextEditor/extensions/markdown/tags.d.ts.map +0 -1
  103. package/dist/types/src/components/TextEditor/extensions/markdown/theme.d.ts +0 -21
  104. package/dist/types/src/components/TextEditor/extensions/markdown/theme.d.ts.map +0 -1
  105. package/dist/types/src/hooks/useCollaboration.d.ts +0 -9
  106. package/dist/types/src/hooks/useCollaboration.d.ts.map +0 -1
  107. package/src/components/TextEditor/extensions/basic/index.ts +0 -6
  108. package/src/components/TextEditor/extensions/basic/theme.ts +0 -49
  109. package/src/components/TextEditor/extensions/hyperlink/index.ts +0 -7
  110. package/src/components/TextEditor/extensions/markdown/tags.ts +0 -38
  111. package/src/components/TextEditor/extensions/markdown/theme.ts +0 -265
  112. package/src/hooks/useCollaboration.ts +0 -45
  113. /package/dist/types/src/{automerge/automerge-plugin → hooks/automerge}/PatchSemaphore.d.ts +0 -0
  114. /package/dist/types/src/{automerge/automerge-plugin → hooks/automerge}/amToCodemirror.d.ts +0 -0
  115. /package/dist/types/src/{automerge → hooks/automerge}/automerge.stories.d.ts +0 -0
  116. /package/dist/types/src/{automerge/automerge-plugin → hooks/automerge}/codeMirrorToAm.d.ts +0 -0
  117. /package/dist/types/src/{automerge/automerge-plugin → hooks/automerge}/handle.d.ts +0 -0
  118. /package/dist/types/src/{automerge/automerge-plugin → hooks/automerge}/index.d.ts +0 -0
  119. /package/dist/types/src/{automerge/automerge-plugin → hooks/automerge}/plugin.d.ts +0 -0
  120. /package/src/{automerge/automerge-plugin → hooks/automerge}/PatchSemaphore.ts +0 -0
  121. /package/src/{automerge/automerge-plugin → hooks/automerge}/amToCodemirror.ts +0 -0
  122. /package/src/{automerge/automerge-plugin → hooks/automerge}/codeMirrorToAm.ts +0 -0
  123. /package/src/{automerge/automerge-plugin → hooks/automerge}/handle.ts +0 -0
  124. /package/src/{automerge/automerge-plugin → hooks/automerge}/index.ts +0 -0
  125. /package/src/{automerge/automerge-plugin → hooks/automerge}/plugin.ts +0 -0
@@ -1,519 +1,339 @@
1
1
  // packages/ui/react-ui-editor/src/index.ts
2
2
  import { TextKind } from "@dxos/protocols/proto/dxos/echo/model/text";
3
- import { Doc, YText as YText2, YXmlFragment } from "@dxos/text-model";
3
+ import { Doc, YText, YXmlFragment } from "@dxos/text-model";
4
4
 
5
5
  // packages/ui/react-ui-editor/src/components/TextEditor/index.ts
6
6
  import { tags as tags2 } from "@lezer/highlight";
7
7
 
8
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/basic/bundle.ts
8
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/autocomplete.ts
9
+ import { autocompletion, completionKeymap } from "@codemirror/autocomplete";
10
+ import { keymap } from "@codemirror/view";
11
+ var autocomplete = ({ onSearch }) => {
12
+ return [
13
+ // https://codemirror.net/docs/ref/#view.keymap
14
+ // https://discuss.codemirror.net/t/how-can-i-replace-the-default-autocompletion-keymap-v6/3322
15
+ keymap.of(completionKeymap),
16
+ // https://codemirror.net/examples/autocompletion
17
+ // https://codemirror.net/docs/ref/#autocomplete.autocompletion
18
+ autocompletion({
19
+ // TODO(burdon): Set custom keymap.
20
+ // defaultKeymap: false,
21
+ // Don't start unless key press.
22
+ activateOnTyping: false,
23
+ // TODO(burdon): Option to create new page?
24
+ // TODO(burdon): Optional decoration via addToOptions
25
+ override: [
26
+ (context) => {
27
+ const word = context.matchBefore(/\w*/);
28
+ if (!word || word.from === word.to && !context.explicit) {
29
+ return null;
30
+ }
31
+ return {
32
+ from: word.from,
33
+ options: onSearch(word.text.toLowerCase())
34
+ };
35
+ }
36
+ ]
37
+ })
38
+ ];
39
+ };
40
+
41
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/basic.ts
9
42
  import { closeBrackets } from "@codemirror/autocomplete";
10
43
  import { bracketMatching, defaultHighlightStyle, syntaxHighlighting } from "@codemirror/language";
11
44
  import { oneDarkHighlightStyle } from "@codemirror/theme-one-dark";
12
45
  import { EditorView, placeholder } from "@codemirror/view";
13
46
  var basicBundle = ({ themeMode, placeholder: _placeholder }) => [
47
+ // TODO(burdon): Compare with minimalSetup.
48
+ // https://codemirror.net/docs/ref/#codemirror.minimalSetup
14
49
  bracketMatching(),
15
50
  closeBrackets(),
16
- _placeholder && placeholder(_placeholder),
17
51
  EditorView.lineWrapping,
52
+ _placeholder && placeholder(_placeholder),
18
53
  // TODO(burdon): Is this required?
19
54
  themeMode === "dark" ? syntaxHighlighting(oneDarkHighlightStyle) : syntaxHighlighting(defaultHighlightStyle)
20
55
  ].filter(Boolean);
21
56
 
22
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/basic/theme.ts
23
- import get from "lodash.get";
24
-
25
- // packages/ui/react-ui-editor/src/styles/markdown.ts
26
- import { mx } from "@dxos/react-ui-theme";
27
- var heading = {
28
- 1: "mbs-4 mbe-2 text-4xl font-semibold text-inherit no-underline",
29
- 2: "mbs-4 mbe-2 text-3xl font-bold text-inherit no-underline",
30
- 3: "mbs-4 mbe-2 text-2xl font-bold text-inherit no-underline",
31
- 4: "mbs-4 mbe-2 text-xl font-extrabold text-inherit no-underline",
32
- 5: "mbs-4 mbe-2 text-lg font-extrabold text-inherit no-underline",
33
- 6: "mbs-4 mbe-2 font-black text-inherit no-underline"
34
- };
35
- var bold = "font-bold";
36
- var code = "font-mono bg-neutral-500/10 rounded pli-0.5 plb-0.5 -mlb-0.5";
37
- var codeWithoutMarks = mx(code, "pli-1.5 mli-0.5");
38
- var italic = "italic";
39
- var strikethrough = "line-through";
40
- var mark = "!font-normal !no-underline !text-inherit opacity-40";
41
-
42
- // packages/ui/react-ui-editor/src/styles/tokens.ts
43
- import { tailwindConfig } from "@dxos/react-ui-theme";
44
- var tokens = tailwindConfig({}).theme;
45
-
46
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/basic/theme.ts
47
- var basicTheme = {
48
- "&.cm-focused": {
49
- outline: "none"
50
- },
51
- "& .cm-line": {
52
- paddingInline: 0,
53
- lineHeight: 1.6,
54
- minBlockSize: "1.6em"
55
- },
56
- "& .cm-line *": {
57
- lineHeight: 1.6
58
- },
59
- "& .cm-scroller": {
60
- fontFamily: get(tokens, "fontFamily.body", []).join(","),
61
- overflow: "visible"
62
- },
63
- "& .cm-content": {
64
- padding: 0,
65
- caretColor: "black"
66
- },
67
- ".dark & .cm-content": {
68
- caretColor: "white"
69
- },
70
- "& .cm-tooltip": {
71
- backgroundColor: "transparent",
72
- border: "none"
73
- },
74
- "& .cm-link": {
75
- color: get(tokens, "extend.colors.primary.500"),
76
- textDecoration: "underline"
57
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/comments.ts
58
+ import { Decoration, EditorView as EditorView2, keymap as keymap2, MatchDecorator, ViewPlugin, WidgetType } from "@codemirror/view";
59
+ import { invariant } from "@dxos/invariant";
60
+ var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/components/TextEditor/extensions/comments.ts";
61
+ var BookmarkWidget = class extends WidgetType {
62
+ constructor(_pos, _id) {
63
+ super();
64
+ this._pos = _pos;
65
+ this._id = _id;
66
+ invariant(this._id, void 0, {
67
+ F: __dxlog_file,
68
+ L: 40,
69
+ S: this,
70
+ A: [
71
+ "this._id",
72
+ ""
73
+ ]
74
+ });
75
+ }
76
+ eq(other) {
77
+ return this._id === other._id;
78
+ }
79
+ // TODO(burdon): Click to select.
80
+ toDOM() {
81
+ const span = document.createElement("span");
82
+ span.className = "cm-bookmark";
83
+ span.textContent = "\u{1F4AC}";
84
+ return span;
77
85
  }
78
86
  };
79
-
80
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/change.ts
81
- import { StateField } from "@codemirror/state";
82
- var onChangeExtension = (onChange) => StateField.define({
83
- create: () => null,
84
- update: (_value, transaction) => {
85
- if (transaction.docChanged && onChange) {
86
- onChange(transaction.newDoc.toString());
87
- }
88
- return null;
87
+ var styles = EditorView2.baseTheme({
88
+ "& .cm-bookmark": {
89
+ cursor: "pointer",
90
+ margin: "4px",
91
+ padding: "4px",
92
+ backgroundColor: "yellow"
89
93
  }
90
94
  });
95
+ var comments = (options = {}) => {
96
+ const bookmarkMatcher = new MatchDecorator({
97
+ regexp: /\[\^(\w+)\]/g,
98
+ decoration: (match, view, pos) => {
99
+ const id = match[1];
100
+ return Decoration.replace({
101
+ id,
102
+ widget: new BookmarkWidget(pos, id)
103
+ });
104
+ }
105
+ });
106
+ const bookmarks = ViewPlugin.fromClass(class {
107
+ constructor(view) {
108
+ this.bookmarks = bookmarkMatcher.createDeco(view);
109
+ }
110
+ update(update2) {
111
+ this.bookmarks = bookmarkMatcher.updateDeco(update2, this.bookmarks);
112
+ }
113
+ }, {
114
+ decorations: (instance) => instance.bookmarks,
115
+ provide: (plugin) => EditorView2.atomicRanges.of((view) => {
116
+ return view.plugin(plugin)?.bookmarks || Decoration.none;
117
+ })
118
+ });
119
+ return [
120
+ keymap2.of([
121
+ {
122
+ key: options?.key ?? "alt-meta-c",
123
+ run: (view) => {
124
+ const id = options.onCreate?.();
125
+ if (id) {
126
+ const pos = view.state.selection.main.head;
127
+ const tag = `[^${id}]`;
128
+ view.dispatch({
129
+ changes: {
130
+ from: pos,
131
+ insert: tag
132
+ },
133
+ selection: {
134
+ anchor: pos + tag.length
135
+ }
136
+ });
137
+ return true;
138
+ }
139
+ return false;
140
+ }
141
+ }
142
+ ]),
143
+ bookmarks,
144
+ // Monitor cursor movement.
145
+ EditorView2.updateListener.of((update2) => {
146
+ const view = update2.view;
147
+ const pos = view.state.selection.main.head;
148
+ const decorations = [];
149
+ const rangeSet = view.plugin(bookmarks)?.bookmarks;
150
+ rangeSet?.between(pos, pos + 1, (from, to, value) => {
151
+ if (value.spec.widget) {
152
+ decorations.push({
153
+ from,
154
+ to,
155
+ value
156
+ });
157
+ }
158
+ });
159
+ if (decorations.length) {
160
+ const { from, value: { spec: { id } } } = decorations[0];
161
+ const location = view.coordsAtPos(from);
162
+ if (location) {
163
+ options.onUpdate?.({
164
+ items: decorations.map(({ value: { spec: { id: id2 } } }) => id2),
165
+ active: id,
166
+ pos,
167
+ location
168
+ });
169
+ }
170
+ }
171
+ }),
172
+ styles
173
+ ];
174
+ };
91
175
 
92
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/hyperlink/hyperlinkDecoration.ts
176
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/link.ts
93
177
  import { syntaxTree } from "@codemirror/language";
94
- import { RangeSetBuilder, StateField as StateField2 } from "@codemirror/state";
95
- import { Decoration, EditorView as EditorView2, WidgetType } from "@codemirror/view";
96
- var Link = class extends WidgetType {
97
- constructor(_anchor, _text, _url) {
178
+ import { RangeSetBuilder, StateField } from "@codemirror/state";
179
+ import { Decoration as Decoration2, EditorView as EditorView3, WidgetType as WidgetType2 } from "@codemirror/view";
180
+ var LinkButton = class extends WidgetType2 {
181
+ constructor(_pos, _url, _onAttach) {
98
182
  super();
99
- this._anchor = _anchor;
183
+ this._pos = _pos;
184
+ this._url = _url;
185
+ this._onAttach = _onAttach;
186
+ }
187
+ eq(other) {
188
+ return this._pos === other._pos && this._url === other._url;
189
+ }
190
+ toDOM(view) {
191
+ const el = document.createElement("span");
192
+ this._onAttach(el, this._url);
193
+ return el;
194
+ }
195
+ };
196
+ var LinkText = class extends WidgetType2 {
197
+ constructor(_pos, _text, _url) {
198
+ super();
199
+ this._pos = _pos;
100
200
  this._text = _text;
101
201
  this._url = _url;
102
202
  }
103
203
  eq(other) {
104
- return this._text === other.text && this._url === other.url;
204
+ return this._pos === other._pos && this._url === other._url;
105
205
  }
106
206
  toDOM(view) {
107
- const link = document.createElement("a");
108
- link.setAttribute("class", "cm-link");
109
- link.textContent = this._text;
207
+ const link2 = document.createElement("a");
208
+ link2.setAttribute("class", "cm-link");
209
+ link2.textContent = this._text;
110
210
  if (this._url) {
111
- link.setAttribute("target", "_blank");
112
- link.href = this._url;
211
+ link2.setAttribute("rel", "noreferrer");
212
+ link2.setAttribute("target", "_blank");
213
+ link2.href = this._url;
113
214
  } else {
114
- link.onclick = () => {
215
+ link2.onclick = () => {
115
216
  view.dispatch({
116
217
  selection: {
117
- anchor: this._anchor
218
+ anchor: this._pos
118
219
  }
119
220
  });
120
221
  };
121
222
  }
122
- return link;
223
+ return link2;
123
224
  }
124
225
  };
125
- var hyperlinkDecoration = ({ link } = {}) => {
126
- return StateField2.define({
127
- create: (state) => update(state, link),
128
- update: (_, tr) => update(tr.state, link),
129
- provide: (field) => EditorView2.decorations.from(field)
130
- });
131
- };
132
- var update = (state, link) => {
226
+ var update = (state, options) => {
133
227
  const builder = new RangeSetBuilder();
134
228
  const cursor = state.selection.main.head;
135
229
  syntaxTree(state).iterate({
136
230
  enter: (node) => {
137
- if (cursor < node.from || cursor > node.to) {
138
- if (node.name === "Link") {
139
- const marks = node.node.getChildren("LinkMark");
140
- const text = marks.length >= 2 ? state.sliceDoc(marks[0].to, marks[1].from) : "";
141
- const urlNode = node.node.getChild("URL");
142
- const url = urlNode ? state.sliceDoc(urlNode.from, urlNode.to) : "";
143
- builder.add(node.from, node.to, Decoration.replace({
144
- widget: new Link(node.from, text, link ? url : void 0)
145
- }));
231
+ if (node.name === "Link") {
232
+ const marks = node.node.getChildren("LinkMark");
233
+ const text = marks.length >= 2 ? state.sliceDoc(marks[0].to, marks[1].from) : "";
234
+ const urlNode = node.node.getChild("URL");
235
+ const url = urlNode ? state.sliceDoc(urlNode.from, urlNode.to) : "";
236
+ if (!url) {
146
237
  return false;
147
238
  }
239
+ if (cursor < node.from || cursor > node.to) {
240
+ builder.add(node.from, node.to, Decoration2.replace({
241
+ widget: new LinkText(node.from, text, options.link ? url : void 0)
242
+ }));
243
+ }
244
+ if (options.onRender) {
245
+ builder.add(node.to, node.to, Decoration2.replace({
246
+ widget: new LinkButton(node.from, url, options.onRender)
247
+ }));
248
+ }
249
+ return false;
148
250
  }
149
- return true;
150
251
  }
151
252
  });
152
253
  return builder.finish();
153
254
  };
255
+ var link = (options = {}) => {
256
+ return StateField.define({
257
+ create: (state) => update(state, options),
258
+ update: (_, tr) => update(tr.state, options),
259
+ provide: (field) => EditorView3.decorations.from(field)
260
+ });
261
+ };
154
262
 
155
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/hyperlink/hyperlinkTooltip.tsx
156
- import { hoverTooltip } from "@codemirror/view";
157
- import { tooltipContent } from "@dxos/react-ui-theme";
158
- var markdownLinkRegexp = /\[([^\]]+)]\(([^)]+)\)/;
159
- var createHyperlinkTooltip = (onHover, regexp = markdownLinkRegexp) => hoverTooltip((view, pos) => {
160
- const { from, text } = view.state.doc.lineAt(pos);
161
- const p = pos - from;
162
- let idx = 0;
163
- let match;
164
- do {
165
- match = text.substring(idx).match(regexp);
166
- if (!match) {
167
- match = void 0;
168
- break;
169
- }
170
- idx = text.indexOf(match[0]);
171
- if (p >= idx && p < idx + match[0].length) {
172
- break;
263
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/listener.ts
264
+ import { StateField as StateField2 } from "@codemirror/state";
265
+ var listener = (onChange) => StateField2.define({
266
+ create: () => null,
267
+ update: (_value, transaction) => {
268
+ if (transaction.docChanged && onChange) {
269
+ onChange(transaction.newDoc.toString());
173
270
  }
174
- idx += match[0].length;
175
- } while (true);
176
- if (!match) {
177
271
  return null;
178
272
  }
179
- const [, , url] = match ?? [];
180
- return {
181
- pos: idx + from,
182
- end: idx + from + match[0].length,
183
- above: true,
184
- create: () => {
185
- const el = document.createElement("a");
186
- el.innerText = "_";
187
- el.className = tooltipContent({}, "mb-2 p-1");
188
- el.setAttribute("target", "_blank");
189
- el.setAttribute("href", url);
190
- onHover(el, url);
191
- return {
192
- dom: el
193
- };
194
- }
195
- };
196
273
  });
197
274
 
198
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/hyperlink/hyperlinkWidget.tsx
199
- import { Decoration as Decoration2, EditorView as EditorView3, MatchDecorator, ViewPlugin } from "@codemirror/view";
200
- var markdownLinkRegexp2 = /\[([^\]]+)]\(([^)]+)\)(!?)/gi;
201
- var linkDecorator = () => new MatchDecorator({
202
- regexp: markdownLinkRegexp2,
203
- decorate: (add, from, to, match, view) => {
204
- const [_, label, url] = match;
205
- const p0 = {
206
- start: from,
207
- end: from + 1
208
- };
209
- const p1 = {
210
- start: from + 1,
211
- end: from + 1 + label.length
212
- };
213
- const p2 = {
214
- start: p1.end,
215
- end: p1.end + 1
216
- };
217
- const p3 = {
218
- start: p1.end + 1,
219
- end: p1.end + 1 + url.length + 2
220
- };
221
- add(p0.start, p0.end, Decoration2.mark({
222
- class: "cm-hyperlink-bracket",
223
- _attributes: {
224
- style: "display: none"
225
- }
226
- }));
227
- add(p1.start, p1.end, Decoration2.mark({
228
- class: "cm-hyperlink-label"
229
- }));
230
- add(p2.start, p2.end, Decoration2.mark({
231
- class: "cm-hyperlink-bracket",
232
- _attributes: {
233
- style: "display: none"
234
- }
235
- }));
236
- add(p3.start, p3.end, Decoration2.mark({
237
- class: "cm-hyperlink-url",
238
- _attributes: {
239
- style: "display: none"
240
- }
241
- }));
242
- }
243
- });
244
- function hyperLinkExtension() {
245
- return ViewPlugin.fromClass(class HyperLinkView {
246
- constructor(view) {
247
- this.decorator = linkDecorator();
248
- this.decorations = this.decorator.createDeco(view);
249
- }
250
- update(update2) {
251
- if (update2.docChanged || update2.viewportChanged) {
252
- this.decorations = this.decorator.updateDeco(update2, this.decorations);
253
- }
254
- }
255
- }, {
256
- decorations: (view) => view.decorations,
257
- eventHandlers: {
258
- // TODO(burdon): CLick to expand link.
259
- // https://discuss.codemirror.net/t/decorator-iteration-on-click-event/4226/2
260
- mousedown: function(event, view) {
261
- const pos = view.posAtDOM(event.target);
262
- if (pos !== 0) {
263
- console.log(this);
264
- }
265
- },
266
- // TODO(burdon): Hide when cursor moves away.
267
- keydown: (event, view) => {
268
- view.update([]);
269
- }
270
- }
271
- });
272
- }
273
- var hyperlinkStyle = EditorView3.baseTheme({
274
- ".cm-hyperlink-label": {
275
- cursor: "pointer",
276
- textDecoration: "underline"
277
- },
278
- ".cm-hyperlink-bracket": {
279
- opacity: 0.2
280
- },
281
- ".cm-hyperlink-url": {
282
- opacity: 0.5,
283
- fontFamily: "monospace"
284
- }
285
- });
286
- var hyperlinkWidget = [
287
- hyperLinkExtension(),
288
- hyperlinkStyle
289
- ];
290
-
291
275
  // packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/bundle.ts
292
276
  import { closeBrackets as closeBrackets2, closeBracketsKeymap } from "@codemirror/autocomplete";
293
- import { defaultKeymap, history, historyKeymap, indentWithTab } from "@codemirror/commands";
277
+ import { defaultKeymap, history, indentWithTab } from "@codemirror/commands";
294
278
  import { markdownLanguage as markdownLanguage2, markdown } from "@codemirror/lang-markdown";
295
- import { bracketMatching as bracketMatching2, defaultHighlightStyle as defaultHighlightStyle2, foldKeymap, indentOnInput, syntaxHighlighting as syntaxHighlighting2 } from "@codemirror/language";
279
+ import { bracketMatching as bracketMatching2, defaultHighlightStyle as defaultHighlightStyle2, indentOnInput, syntaxHighlighting as syntaxHighlighting2 } from "@codemirror/language";
296
280
  import { languages } from "@codemirror/language-data";
297
- import { lintKeymap } from "@codemirror/lint";
298
281
  import { highlightSelectionMatches, searchKeymap } from "@codemirror/search";
299
282
  import { EditorState } from "@codemirror/state";
300
283
  import { oneDarkHighlightStyle as oneDarkHighlightStyle2 } from "@codemirror/theme-one-dark";
301
- import { crosshairCursor, drawSelection, dropCursor, EditorView as EditorView4, highlightActiveLine, highlightActiveLineGutter, highlightSpecialChars, keymap, placeholder as placeholder2, rectangularSelection } from "@codemirror/view";
284
+ import { crosshairCursor, drawSelection, dropCursor, EditorView as EditorView4, highlightActiveLine, highlightActiveLineGutter, highlightSpecialChars, keymap as keymap3, placeholder as placeholder2, rectangularSelection } from "@codemirror/view";
285
+
286
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/highlight.ts
287
+ import { markdownLanguage } from "@codemirror/lang-markdown";
288
+ import { HighlightStyle } from "@codemirror/language";
289
+ import { tags, styleTags, Tag } from "@lezer/highlight";
290
+ import get from "lodash.get";
291
+
292
+ // packages/ui/react-ui-editor/src/styles/markdown.ts
293
+ import { mx } from "@dxos/react-ui-theme";
294
+ var heading = {
295
+ 1: "mbs-4 mbe-2 font-medium text-inherit no-underline text-4xl",
296
+ 2: "mbs-4 mbe-2 font-medium text-inherit no-underline text-3xl",
297
+ 3: "mbs-4 mbe-2 font-medium text-inherit no-underline text-2xl",
298
+ 4: "mbs-4 mbe-2 font-medium text-inherit no-underline text-xl",
299
+ 5: "mbs-4 mbe-2 font-medium text-inherit no-underline text-lg",
300
+ 6: "mbs-4 mbe-2 font-medium text-inherit no-underline"
301
+ };
302
+ var light = "text-neutral-200 dark:text-neutral-800";
303
+ var mark = mx("!font-normal !no-underline !text-inherit opacity-40", light);
304
+ var bold = "font-bold";
305
+ var italic = "italic";
306
+ var strikethrough = "line-through";
307
+ var code = "font-mono !no-underline text-neutral-700 dark:text-neutral-300";
308
+ var codeMark = "font-mono text-primary-500";
309
+ var inlineUrl = mx(code, "px-1");
310
+ var blockquote = mx("pl-1 mr-1 border-is-4 border-primary-500/70 dark:border-primary-500/30", light);
311
+ var horizontalRule = "flex mlb-4 border-b text-neutral-100 dark:text-neutral-900 border-neutral-500/50";
312
+
313
+ // packages/ui/react-ui-editor/src/styles/tokens.ts
314
+ import { tailwindConfig } from "@dxos/react-ui-theme";
315
+ var tokens = tailwindConfig({}).theme;
302
316
 
303
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/tags.ts
304
- import { styleTags, Tag } from "@lezer/highlight";
317
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/highlight.ts
305
318
  var markdownTags = {
306
- headingMark: Tag.define(),
307
- quoteMark: Tag.define(),
308
- listMark: Tag.define(),
309
- linkMark: Tag.define(),
310
- emphasisMark: Tag.define(),
311
- codeMark: Tag.define(),
312
- codeText: Tag.define(),
313
- inlineCode: Tag.define(),
314
- url: Tag.define(),
315
- linkReference: Tag.define(),
316
- linkLabel: Tag.define()
319
+ Blockquote: Tag.define(),
320
+ CodeMark: Tag.define(),
321
+ CodeText: Tag.define(),
322
+ EmphasisMark: Tag.define(),
323
+ HeaderMark: Tag.define(),
324
+ HorizontalRule: Tag.define(),
325
+ InlineCode: Tag.define(),
326
+ LinkLabel: Tag.define(),
327
+ LinkReference: Tag.define(),
328
+ ListMark: Tag.define(),
329
+ QuoteMark: Tag.define(),
330
+ URL: Tag.define()
317
331
  };
318
332
  var markdownTagsExtension = {
319
333
  props: [
320
- styleTags({
321
- HeaderMark: markdownTags.headingMark,
322
- QuoteMark: markdownTags.quoteMark,
323
- ListMark: markdownTags.listMark,
324
- LinkMark: markdownTags.linkMark,
325
- EmphasisMark: markdownTags.emphasisMark,
326
- CodeMark: markdownTags.codeMark,
327
- CodeText: markdownTags.codeText,
328
- InlineCode: markdownTags.inlineCode,
329
- URL: markdownTags.url,
330
- LinkReference: markdownTags.linkReference,
331
- LinkLabel: markdownTags.linkLabel
332
- })
334
+ styleTags(markdownTags)
333
335
  ]
334
336
  };
335
-
336
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/theme.ts
337
- import { markdownLanguage } from "@codemirror/lang-markdown";
338
- import { HighlightStyle } from "@codemirror/language";
339
- import { tags } from "@lezer/highlight";
340
- import get2 from "lodash.get";
341
- var chalky = "#e5c07b";
342
- var coral = "#e06c75";
343
- var cyan = "#56b6c2";
344
- var invalid = "#ffffff";
345
- var ivory = "#abb2bf";
346
- var stone = "#7d8799";
347
- var malibu = "#61afef";
348
- var sage = "#98c379";
349
- var whiskey = "#d19a66";
350
- var violet = "#c678dd";
351
- var highlightBackground = "#2c313a";
352
- var tooltipBackground = "#353a42";
353
- var monospace = get2(tokens, "fontFamily.mono", [
354
- "monospace"
355
- ]).join(",");
356
- var markdownTheme = {
357
- // TODO(thure): Consider whether these commented-out rules from one-dark-theme should be integrated.
358
- // '&': {
359
- // color: ivory,
360
- // backgroundColor: background
361
- // },
362
- // '.cm-cursor, .cm-dropCursor': { borderLeftColor: cursor },
363
- // '&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection': {
364
- // backgroundColor: selection
365
- // },
366
- // '.cm-panels': { backgroundColor: darkBackground, color: ivory },
367
- // '.cm-panels.cm-panels-top': { borderBottom: '2px solid black' },
368
- // '.cm-panels.cm-panels-bottom': { borderTop: '2px solid black' },
369
- // '.cm-searchMatch': {
370
- // backgroundColor: '#72a1ff59',
371
- // outline: '1px solid #457dff'
372
- // },
373
- // '.cm-searchMatch.cm-searchMatch-selected': {
374
- // backgroundColor: '#6199ff2f'
375
- // },
376
- // '.cm-activeLine': { backgroundColor: '#6699ff0b' },
377
- // '.cm-selectionMatch': { backgroundColor: '#aafe661a' },
378
- // '&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': {
379
- // backgroundColor: '#bad0f847'
380
- // },
381
- // '.cm-gutters': {
382
- // backgroundColor: background,
383
- // color: stone,
384
- // border: 'none'
385
- // },
386
- // '.cm-activeLineGutter': {
387
- // backgroundColor: highlightBackground
388
- // },
389
- // '.cm-foldPlaceholder': {
390
- // backgroundColor: 'transparent',
391
- // border: 'none',
392
- // color: '#ddd'
393
- // },
394
- "& .cm-tooltip": {
395
- backgroundColor: "transparent",
396
- border: "none"
397
- },
398
- // '.dark & .cm-tooltip': {
399
- // border: 'none',
400
- // backgroundColor: tooltipBackground,
401
- // },
402
- // '.dark & .cm-tooltip .cm-tooltip-arrow:before': {
403
- // borderTopColor: 'transparent',
404
- // borderBottomColor: 'transparent',
405
- // },
406
- // '.dark & .cm-tooltip .cm-tooltip-arrow:after': {
407
- // borderTopColor: tooltipBackground,
408
- // borderBottomColor: tooltipBackground,
409
- // },
410
- // '.dark & .cm-tooltip-autocomplete': {
411
- // '& > ul > li[aria-selected]': {
412
- // backgroundColor: highlightBackground,
413
- // color: ivory,
414
- // },
415
- // },
416
- "&.cm-focused": {
417
- outline: "none"
418
- },
419
- "& .cm-line": {
420
- paddingInline: 0,
421
- lineHeight: 1.6,
422
- minBlockSize: "1.6em"
423
- },
424
- "& .cm-line *": {
425
- lineHeight: 1.6
426
- },
427
- "&.cm-focused .cm-selectionBackground, & .cm-selectionBackground": {
428
- background: get2(tokens, "extend.colors.primary.150", "#00ffff")
429
- },
430
- ".dark & .cm-selectionBackground, .dark &.cm-focused .cm-selectionBackground": {
431
- background: get2(tokens, "extend.colors.primary.850", "#00ffff")
432
- },
433
- "& .cm-selectionMatch": {
434
- background: get2(tokens, "extend.colors.primary.250", "#00ffff") + "44"
435
- },
436
- ".dark & .cm-selectionMatch": {
437
- background: get2(tokens, "extend.colors.primary.600", "#00ffff") + "44"
438
- },
439
- "& .cm-content": {
440
- padding: 0,
441
- caretColor: "black"
442
- },
443
- ".dark & .cm-content": {
444
- caretColor: "white"
445
- },
446
- "& .cm-cursor": {
447
- borderLeftColor: "black"
448
- },
449
- ".dark & .cm-cursor": {
450
- borderLeftColor: "white"
451
- },
452
- ".cm-placeholder": {
453
- fontFamily: get2(tokens, "fontFamily.body", []).join(",")
454
- },
455
- "& .cm-scroller": {
456
- fontFamily: get2(tokens, "fontFamily.mono", []).join(","),
457
- overflow: "visible"
458
- },
459
- "& .cm-activeLine": {
460
- backgroundColor: "transparent"
461
- },
462
- ".dark & .cm-activeLine": {
463
- backgroundColor: "transparent"
464
- },
465
- "& .cm-ySelectionInfo": {
466
- fontFamily: get2(tokens, "fontFamily.body", []).join(","),
467
- padding: "2px 4px",
468
- marginBlockStart: "-4px"
469
- },
470
- "& .cm-ySelection, & .cm-selectionMatch": {
471
- paddingBlockStart: ".15em",
472
- paddingBlockEnd: ".15em"
473
- },
474
- "& .cm-ySelection, & .cm-yLineSelection": {
475
- mixBlendMode: "multiply"
476
- },
477
- ".dark & .cm-ySelection, .dark & .cm-yLineSelection": {
478
- mixBlendMode: "screen"
479
- },
480
- "& .cm-ySelectionCaret": {
481
- display: "inline-block",
482
- insetBlockStart: ".1em",
483
- blockSize: "1.4em",
484
- verticalAlign: "top"
485
- },
486
- "& .cm-yLineSelection": {
487
- margin: "0"
488
- },
489
- ".cm-link": {
490
- color: "rgb(20 89 208)",
491
- textDecorationLine: "underline",
492
- textDecorationThickness: "1px",
493
- textUnderlineOffset: "2px",
494
- borderRadius: ".125rem"
495
- },
496
- // TODO(burdon): Document.
497
- ...Object.keys(get2(tokens, "extend.fontSize", {})).reduce((acc, fontSize) => {
498
- const height = get2(tokens, [
499
- "extend",
500
- "fontSize",
501
- fontSize,
502
- 1,
503
- "lineHeight"
504
- ]);
505
- acc[`& .text-${fontSize} + .cm-ySelectionCaret`] = {
506
- height
507
- };
508
- acc[`& .text-${fontSize} + .cm-ySelection + .cm-ySelectionCaret`] = {
509
- height
510
- };
511
- acc[`& .text-${fontSize} + .cm-widgetBuffer + .cm-ySelectionCaret`] = {
512
- height
513
- };
514
- return acc;
515
- }, {})
516
- };
517
337
  var markdownHighlightStyle = HighlightStyle.define([
518
338
  {
519
339
  tag: [
@@ -553,48 +373,22 @@ var markdownHighlightStyle = HighlightStyle.define([
553
373
  ],
554
374
  color: "inherit !important"
555
375
  },
376
+ // Markdown marks.
556
377
  {
557
378
  tag: [
558
- tags.link,
559
- tags.url
560
- ],
561
- color: "inherit !important",
562
- textDecoration: "none !important"
563
- },
564
- {
565
- tag: [
566
- tags.function(tags.variableName),
567
- tags.labelName
568
- ],
569
- color: malibu,
570
- fontFamily: monospace
571
- },
572
- {
573
- tag: [
574
- markdownTags.codeMark,
575
- markdownTags.emphasisMark,
576
- markdownTags.headingMark,
577
- markdownTags.linkLabel,
578
- markdownTags.linkReference,
579
- markdownTags.listMark,
580
- markdownTags.quoteMark,
581
- markdownTags.url,
582
379
  tags.meta,
583
- tags.processingInstruction
380
+ tags.processingInstruction,
381
+ markdownTags.CodeMark,
382
+ markdownTags.EmphasisMark,
383
+ markdownTags.HeaderMark,
384
+ markdownTags.LinkLabel,
385
+ markdownTags.LinkReference,
386
+ markdownTags.ListMark,
387
+ markdownTags.QuoteMark
584
388
  ],
585
389
  class: mark
586
390
  },
587
- {
588
- tag: [
589
- markdownTags.codeText,
590
- markdownTags.inlineCode
591
- ],
592
- class: "font-mono"
593
- },
594
- {
595
- tag: tags.emphasis,
596
- class: italic
597
- },
391
+ // Headings.
598
392
  {
599
393
  tag: tags.heading1,
600
394
  class: heading[1]
@@ -619,18 +413,62 @@ var markdownHighlightStyle = HighlightStyle.define([
619
413
  tag: tags.heading6,
620
414
  class: heading[6]
621
415
  },
416
+ // Emphasis.
622
417
  {
623
- tag: tags.strikethrough,
624
- class: strikethrough
418
+ tag: tags.emphasis,
419
+ class: italic
625
420
  },
626
421
  {
627
422
  tag: tags.strong,
628
423
  class: bold
424
+ },
425
+ {
426
+ tag: tags.strikethrough,
427
+ class: strikethrough
428
+ },
429
+ // Naked URLs.
430
+ {
431
+ tag: [
432
+ markdownTags.URL
433
+ ],
434
+ class: inlineUrl
435
+ },
436
+ // E.g., code block language (after ```).
437
+ {
438
+ tag: [
439
+ tags.function(tags.variableName),
440
+ tags.labelName
441
+ ],
442
+ class: codeMark
443
+ },
444
+ // NOTE: The `markdown` extension configures extensions for `lezer` to parse markdown tokens (incl. below).
445
+ // However, since `codeLanguages` is also defined, the `lezer` will not parse fenced code blocks,
446
+ // when a language is specified. In this case, the syntax highlighting extensions will colorize
447
+ // the code, but all other CSS properties will be inherited.
448
+ // IMPORTANT: Therefore, the fenced code block will use the base editor font.
449
+ {
450
+ tag: [
451
+ markdownTags.CodeText,
452
+ markdownTags.InlineCode
453
+ ],
454
+ class: code
455
+ },
456
+ {
457
+ tag: [
458
+ markdownTags.QuoteMark
459
+ ],
460
+ class: blockquote
461
+ },
462
+ {
463
+ tag: [
464
+ markdownTags.HorizontalRule
465
+ ],
466
+ class: horizontalRule
629
467
  }
630
468
  ], {
631
469
  scope: markdownLanguage,
632
470
  all: {
633
- fontFamily: get2(tokens, "fontFamily.body", []).join(",")
471
+ fontFamily: get(tokens, "fontFamily.body", []).join(",")
634
472
  }
635
473
  });
636
474
 
@@ -642,7 +480,6 @@ var markdownBundle = ({ themeMode, placeholder: _placeholder }) => {
642
480
  _placeholder && placeholder2(_placeholder),
643
481
  EditorState.allowMultipleSelections.of(true),
644
482
  EditorView4.lineWrapping,
645
- // autocompletion(),
646
483
  crosshairCursor(),
647
484
  dropCursor(),
648
485
  drawSelection(),
@@ -653,65 +490,508 @@ var markdownBundle = ({ themeMode, placeholder: _placeholder }) => {
653
490
  history(),
654
491
  indentOnInput(),
655
492
  rectangularSelection(),
656
- keymap.of([
493
+ // TODO(burdon): Review.
494
+ keymap3.of([
657
495
  ...closeBracketsKeymap,
658
- // ...completionKeymap,
659
496
  ...defaultKeymap,
660
- ...foldKeymap,
661
- ...historyKeymap,
662
- ...lintKeymap,
497
+ // ...foldKeymap,
498
+ // ...historyKeymap,
499
+ // ...lintKeymap,
663
500
  ...searchKeymap,
664
501
  indentWithTab
665
502
  ]),
666
503
  // Main extension.
667
504
  // https://github.com/codemirror/lang-markdown
505
+ // https://codemirror.net/5/mode/markdown/index.html (demo).
668
506
  markdown({
507
+ // GRM by default (vs strict CommonMark):
508
+ // Table, TaskList, Strikethrough, and Autolink.
509
+ // NOTE: This extends the parser; it doesn't affect rendering.
510
+ // https://github.github.com/gfm
511
+ // https://github.com/lezer-parser/markdown?tab=readme-ov-file#github-flavored-markdown
669
512
  base: markdownLanguage2,
513
+ // Languages for syntax highlighting fenced code blocks.
670
514
  codeLanguages: languages,
515
+ // Parser extensions.
671
516
  extensions: [
672
- // TODO(burdon): This seems to upgrade the parser.
673
- // GitHub flavored markdown bundle: Table, TaskList, Strikethrough, and Autolink.
674
- // https://github.com/lezer-parser/markdown?tab=readme-ov-file#github-flavored-markdown
675
- // https://github.github.com/gfm
676
- // GFM,
677
- // Custom styling.
517
+ // GFM, // TODO(burdon): Provided by default?
678
518
  markdownTagsExtension
679
519
  ]
680
520
  }),
681
- // TODO(thure): All but one rule here apply to both themes; rename or refactor.
521
+ // Custom styles.
682
522
  syntaxHighlighting2(markdownHighlightStyle),
523
+ // TODO(thure): All but one rule here apply to both themes; rename or refactor.
683
524
  themeMode === "dark" ? syntaxHighlighting2(oneDarkHighlightStyle2) : syntaxHighlighting2(defaultHighlightStyle2)
684
525
  ].filter(Boolean);
685
526
  };
686
527
 
528
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/tasklist.ts
529
+ import { EditorView as EditorView5, Decoration as Decoration3, WidgetType as WidgetType3, MatchDecorator as MatchDecorator2, ViewPlugin as ViewPlugin2 } from "@codemirror/view";
530
+ var CheckboxWidget = class extends WidgetType3 {
531
+ constructor(_pos, _checked, _indent, _onCheck) {
532
+ super();
533
+ this._pos = _pos;
534
+ this._checked = _checked;
535
+ this._indent = _indent;
536
+ this._onCheck = _onCheck;
537
+ }
538
+ eq(other) {
539
+ return this._pos === other._pos && this._checked === other._checked && this._indent === other._indent;
540
+ }
541
+ toDOM(view) {
542
+ const wrap = document.createElement("span");
543
+ wrap.className = "cm-task-item";
544
+ wrap.setAttribute("aria-hidden", "true");
545
+ wrap.style.setProperty("margin-left", this._indent * 24 + "px");
546
+ const input = wrap.appendChild(document.createElement("input"));
547
+ input.type = "checkbox";
548
+ input.checked = this._checked;
549
+ input.onchange = (event) => {
550
+ this._onCheck(event.target.checked);
551
+ return true;
552
+ };
553
+ return wrap;
554
+ }
555
+ ignoreEvent() {
556
+ return false;
557
+ }
558
+ };
559
+ var styles2 = EditorView5.baseTheme({
560
+ "& .cm-task-item": {
561
+ paddingLeft: "4px",
562
+ paddingRight: "8px"
563
+ }
564
+ });
565
+ var tasklist = () => {
566
+ const taskMatcher = new MatchDecorator2({
567
+ regexp: /^(\s*)- \[([ xX])\]\s/g,
568
+ decoration: (match, view, pos) => {
569
+ const indent = Math.floor(match[1].length / 2);
570
+ const checked = match[2] === "x" || match[2] === "X";
571
+ return Decoration3.replace({
572
+ widget: new CheckboxWidget(pos, checked, indent, (checked2) => {
573
+ const idx = pos + match[0].indexOf("[") + 1;
574
+ view.dispatch({
575
+ changes: {
576
+ from: idx,
577
+ to: idx + 1,
578
+ insert: checked2 ? "x" : " "
579
+ },
580
+ // TODO(burdon): Restore cursor position? More useful to move to end of line (can indent).
581
+ selection: {
582
+ anchor: idx + 3
583
+ }
584
+ });
585
+ })
586
+ });
587
+ }
588
+ });
589
+ const tasks = ViewPlugin2.fromClass(class {
590
+ constructor(view) {
591
+ this.tasks = taskMatcher.createDeco(view);
592
+ }
593
+ update(update2) {
594
+ this.tasks = taskMatcher.updateDeco(update2, this.tasks);
595
+ }
596
+ }, {
597
+ decorations: (instance) => instance.tasks,
598
+ provide: (plugin) => EditorView5.atomicRanges.of((view) => {
599
+ return view.plugin(plugin)?.tasks || Decoration3.none;
600
+ })
601
+ });
602
+ return [
603
+ tasks,
604
+ styles2
605
+ ];
606
+ };
607
+
608
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/tooltip.tsx
609
+ import { hoverTooltip } from "@codemirror/view";
610
+ import { tooltipContent } from "@dxos/react-ui-theme";
611
+ var markdownLinkRegexp = /\[([^\]]+)]\(([^)]+)\)/;
612
+ var tooltip = ({ onHover, regexp = markdownLinkRegexp }) => hoverTooltip((view, pos) => {
613
+ const { from, text } = view.state.doc.lineAt(pos);
614
+ const p = pos - from;
615
+ let idx = 0;
616
+ let match;
617
+ do {
618
+ match = text.substring(idx).match(regexp);
619
+ if (!match) {
620
+ match = void 0;
621
+ break;
622
+ }
623
+ idx = text.indexOf(match[0]);
624
+ if (p >= idx && p < idx + match[0].length) {
625
+ break;
626
+ }
627
+ idx += match[0].length;
628
+ } while (true);
629
+ if (!match) {
630
+ return null;
631
+ }
632
+ const [, , url] = match ?? [];
633
+ return {
634
+ pos: idx + from,
635
+ end: idx + from + match[0].length,
636
+ above: true,
637
+ create: () => {
638
+ const el = document.createElement("div");
639
+ el.className = tooltipContent({}, "pli-2 plb-1");
640
+ onHover(el, url);
641
+ return {
642
+ dom: el,
643
+ offset: {
644
+ x: 0,
645
+ y: 12
646
+ }
647
+ };
648
+ }
649
+ };
650
+ });
651
+
652
+ // packages/ui/react-ui-editor/src/components/TextEditor/themes/default.ts
653
+ import get2 from "lodash.get";
654
+ var defaultTheme = {
655
+ //
656
+ // Main layout:
657
+ // <div class=".cm-editor .cm-focused">
658
+ // <div class=".cm-scroller">
659
+ // <div class=".cm-content" role="textbox" contenteditable="true">
660
+ // <div class=".cm-line" />
661
+ // </div>
662
+ // </div>
663
+ // </div>
664
+ //
665
+ "&": {},
666
+ "&.cm-focused": {
667
+ outline: "none"
668
+ },
669
+ "& .cm-scroller": {
670
+ overflow: "visible"
671
+ },
672
+ "& .cm-content": {
673
+ padding: 0,
674
+ // Base font size (otherwise defined by HTML tag, which might be different for storybook).
675
+ fontSize: "16px"
676
+ },
677
+ "&light .cm-content": {
678
+ color: get2(tokens, "extend.colors.neutral.900", "black"),
679
+ caretColor: "black"
680
+ },
681
+ "&dark .cm-content": {
682
+ color: get2(tokens, "extend.colors.neutral.100", "white"),
683
+ caretColor: "white"
684
+ },
685
+ //
686
+ // Cursor
687
+ //
688
+ "&light .cm-cursor": {
689
+ borderLeft: "2px solid black"
690
+ },
691
+ "&dark .cm-cursor": {
692
+ borderLeft: "2px solid white"
693
+ },
694
+ "& .cm-placeholder": {
695
+ fontWeight: 100
696
+ },
697
+ //
698
+ // line
699
+ //
700
+ "& .cm-line": {
701
+ paddingInline: 0
702
+ },
703
+ "& .cm-line *": {},
704
+ "& .cm-activeLine": {
705
+ backgroundColor: "transparent"
706
+ },
707
+ //
708
+ // selection
709
+ //
710
+ "&light .cm-selectionBackground, &light.cm-focused .cm-selectionBackground": {
711
+ background: get2(tokens, "extend.colors.primary.150", "#00ffff")
712
+ },
713
+ "&dark .cm-selectionBackground, &dark.cm-focused .cm-selectionBackground": {
714
+ background: get2(tokens, "extend.colors.primary.850", "#00ffff")
715
+ },
716
+ "&light .cm-selectionMatch": {
717
+ background: get2(tokens, "extend.colors.primary.250", "#00ffff") + "44"
718
+ },
719
+ "&dark .cm-selectionMatch": {
720
+ background: get2(tokens, "extend.colors.primary.600", "#00ffff") + "44"
721
+ },
722
+ //
723
+ // collaboration
724
+ // TODO(burdon): Review classnames (YJS dependent?)
725
+ //
726
+ "&light .cm-ySelection, &light .cm-yLineSelection": {
727
+ mixBlendMode: "multiply"
728
+ },
729
+ "&dark .cm-ySelection, &dark .cm-yLineSelection": {
730
+ mixBlendMode: "screen"
731
+ },
732
+ "& .cm-ySelectionInfo": {
733
+ padding: "2px 4px",
734
+ marginBlockStart: "-4px"
735
+ },
736
+ "& .cm-ySelection, & .cm-selectionMatch": {
737
+ paddingBlockStart: ".15em",
738
+ paddingBlockEnd: ".15em"
739
+ },
740
+ "& .cm-ySelectionCaret": {
741
+ display: "inline-block",
742
+ insetBlockStart: ".1em",
743
+ blockSize: "1.4em",
744
+ verticalAlign: "top"
745
+ },
746
+ "& .cm-yLineSelection": {
747
+ margin: 0
748
+ },
749
+ //
750
+ // link
751
+ //
752
+ "& .cm-link": {
753
+ color: get2(tokens, "extend.colors.primary.500"),
754
+ textDecorationLine: "underline",
755
+ textDecorationThickness: "1px",
756
+ textUnderlineOffset: "2px",
757
+ borderRadius: ".125rem",
758
+ fontFamily: get2(tokens, "fontFamily.body", []).join(",")
759
+ },
760
+ //
761
+ // tooltip
762
+ //
763
+ "& .cm-tooltip": {
764
+ border: "none"
765
+ },
766
+ // '& .cm-tooltip-below': {},
767
+ // '& .cm-tooltip-autocomplete': {},
768
+ //
769
+ // font size
770
+ // TODO(thure): This appears to be the best or only way to set selection caret heights, but it's far more verbose than it needs to be.
771
+ //
772
+ ...Object.keys(get2(tokens, "extend.fontSize", {})).reduce((acc, fontSize) => {
773
+ const height = get2(tokens, [
774
+ "extend",
775
+ "fontSize",
776
+ fontSize,
777
+ 1,
778
+ "lineHeight"
779
+ ]);
780
+ acc[`& .text-${fontSize} + .cm-ySelectionCaret`] = {
781
+ height
782
+ };
783
+ acc[`& .text-${fontSize} + .cm-ySelection + .cm-ySelectionCaret`] = {
784
+ height
785
+ };
786
+ acc[`& .text-${fontSize} + .cm-widgetBuffer + .cm-ySelectionCaret`] = {
787
+ height
788
+ };
789
+ return acc;
790
+ }, {})
791
+ };
792
+ var textTheme = {
793
+ "& .cm-scroller": {
794
+ fontFamily: get2(tokens, "fontFamily.body", []).join(",")
795
+ },
796
+ "& .cm-placeholder": {
797
+ fontFamily: get2(tokens, "fontFamily.body", []).join(",")
798
+ }
799
+ };
800
+ var markdownTheme = {
801
+ // NOTE: Must leave base font family as is (i.e., monospace) due to fenced code blocks.
802
+ "& .cm-placeholder": {
803
+ fontFamily: get2(tokens, "fontFamily.body", []).join(",")
804
+ }
805
+ };
806
+ var codeTheme = {
807
+ "& .cm-scroller": {
808
+ fontFamily: get2(tokens, "fontFamily.mono", []).join(",")
809
+ },
810
+ "& .cm-placeholder": {
811
+ fontFamily: get2(tokens, "fontFamily.mono", []).join(",")
812
+ }
813
+ };
814
+
687
815
  // packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx
688
816
  import { EditorState as EditorState2 } from "@codemirror/state";
689
- import { EditorView as EditorView5 } from "@codemirror/view";
817
+ import { EditorView as EditorView6 } from "@codemirror/view";
690
818
  import { useFocusableGroup } from "@fluentui/react-tabster";
691
819
  import { vim } from "@replit/codemirror-vim";
692
820
  import defaultsDeep from "lodash.defaultsdeep";
693
- import get3 from "lodash.get";
694
- import React, { forwardRef, useCallback, useEffect as useEffect2, useImperativeHandle, useState } from "react";
695
- import { isDocAccessor as isDocAccessor2 } from "@dxos/echo-schema";
821
+ import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useState } from "react";
822
+ import { generateName } from "@dxos/display-name";
696
823
  import { useThemeContext } from "@dxos/react-ui";
824
+ import { getColorForValue, inputSurface, mx as mx2 } from "@dxos/react-ui-theme";
825
+ var EditorModes = [
826
+ "default",
827
+ "vim"
828
+ ];
829
+ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, extensions = [], slots = defaultSlots, editorMode }, forwardedRef) => {
830
+ const { themeMode } = useThemeContext();
831
+ const tabsterDOMAttribute = useFocusableGroup({
832
+ tabBehavior: "limited"
833
+ });
834
+ const [root, setRoot] = useState(null);
835
+ const [state, setState] = useState();
836
+ const [view, setView] = useState();
837
+ useImperativeHandle(forwardedRef, () => ({
838
+ root,
839
+ state,
840
+ view
841
+ }), [
842
+ view,
843
+ state,
844
+ root
845
+ ]);
846
+ const { awareness, peer } = model;
847
+ useEffect(() => {
848
+ if (awareness && peer) {
849
+ awareness.setLocalStateField("user", {
850
+ name: peer.name ?? generateName(peer.id),
851
+ color: getColorForValue({
852
+ value: peer.id,
853
+ type: "color"
854
+ }),
855
+ colorLight: getColorForValue({
856
+ value: peer.id,
857
+ themeMode,
858
+ type: "highlight"
859
+ })
860
+ });
861
+ }
862
+ }, [
863
+ awareness,
864
+ peer,
865
+ themeMode
866
+ ]);
867
+ useEffect(() => {
868
+ if (!root) {
869
+ return;
870
+ }
871
+ const state2 = EditorState2.create({
872
+ doc: model.text(),
873
+ extensions: [
874
+ // TODO(burdon): Factor out VIM mode?
875
+ editorMode === "vim" && vim(),
876
+ // Theme.
877
+ EditorView6.baseTheme(defaultTheme),
878
+ EditorView6.theme(slots?.editor?.theme ?? {}),
879
+ // TODO(burdon): themeMode doesn't change in storybooks.
880
+ EditorView6.darkTheme.of(themeMode === "dark"),
881
+ // Storage and replication.
882
+ model.extension,
883
+ // Custom.
884
+ ...extensions
885
+ ].filter(Boolean)
886
+ });
887
+ view?.destroy();
888
+ setView(new EditorView6({
889
+ state: state2,
890
+ parent: root
891
+ }));
892
+ setState(state2);
893
+ return () => {
894
+ view?.destroy();
895
+ setView(void 0);
896
+ setState(void 0);
897
+ };
898
+ }, [
899
+ root,
900
+ model,
901
+ themeMode,
902
+ editorMode
903
+ ]);
904
+ const handleKeyUp = useCallback((event) => {
905
+ const { key, altKey, shiftKey, metaKey, ctrlKey } = event;
906
+ switch (key) {
907
+ case "Enter": {
908
+ view?.contentDOM.focus();
909
+ break;
910
+ }
911
+ case "Escape": {
912
+ editorMode === "vim" && (altKey || shiftKey || metaKey || ctrlKey) && root?.focus();
913
+ break;
914
+ }
915
+ }
916
+ }, [
917
+ view,
918
+ editorMode
919
+ ]);
920
+ return /* @__PURE__ */ React.createElement("div", {
921
+ key: model.id,
922
+ ref: setRoot,
923
+ tabIndex: 0,
924
+ ...slots?.root,
925
+ ...editorMode !== "vim" && tabsterDOMAttribute,
926
+ onKeyUp: handleKeyUp
927
+ });
928
+ });
929
+ var TextEditor = /* @__PURE__ */ forwardRef(({ extensions = [], slots: _slots, ...props }, forwardedRef) => {
930
+ const { themeMode } = useThemeContext();
931
+ const slots = defaultsDeep({}, _slots, defaultTextSlots);
932
+ return /* @__PURE__ */ React.createElement(BaseTextEditor, {
933
+ ref: forwardedRef,
934
+ extensions: [
935
+ basicBundle({
936
+ themeMode,
937
+ placeholder: slots?.editor?.placeholder
938
+ }),
939
+ ...extensions
940
+ ],
941
+ slots,
942
+ ...props
943
+ });
944
+ });
945
+ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ extensions = [], slots: _slots, ...props }, forwardedRef) => {
946
+ const { themeMode } = useThemeContext();
947
+ const slots = defaultsDeep({}, _slots, defaultMarkdownSlots);
948
+ return /* @__PURE__ */ React.createElement(BaseTextEditor, {
949
+ ref: forwardedRef,
950
+ extensions: [
951
+ markdownBundle({
952
+ themeMode,
953
+ placeholder: slots?.editor?.placeholder
954
+ }),
955
+ ...extensions
956
+ ],
957
+ slots,
958
+ ...props
959
+ });
960
+ });
961
+ var defaultSlots = {
962
+ root: {
963
+ className: mx2("p-2", inputSurface)
964
+ }
965
+ };
966
+ var defaultTextSlots = {
967
+ ...defaultSlots,
968
+ editor: {
969
+ theme: textTheme
970
+ }
971
+ };
972
+ var defaultMarkdownSlots = {
973
+ ...defaultSlots,
974
+ editor: {
975
+ theme: markdownTheme
976
+ }
977
+ };
697
978
 
698
- // packages/ui/react-ui-editor/src/hooks/useCollaboration.ts
699
- import { useEffect, useMemo } from "react";
979
+ // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
980
+ import get3 from "lodash.get";
981
+ import { useEffect as useEffect2, useState as useState2 } from "react";
700
982
  import { yCollab } from "y-codemirror.next";
701
- import { generateName } from "@dxos/display-name";
702
- import { isDocAccessor } from "@dxos/echo-schema";
703
- import { getColorForValue } from "@dxos/react-ui-theme";
704
- import { YText } from "@dxos/text-model";
983
+ import { invariant as invariant2 } from "@dxos/invariant";
984
+ import { getRawDoc, isActualAutomergeObject } from "@dxos/react-client/echo";
705
985
 
706
- // packages/ui/react-ui-editor/src/automerge/automerge-plugin/plugin.ts
986
+ // packages/ui/react-ui-editor/src/hooks/automerge/plugin.ts
707
987
  import { Annotation, Facet, StateEffect, StateField as StateField3 } from "@codemirror/state";
708
- import { ViewPlugin as ViewPlugin2 } from "@codemirror/view";
988
+ import { ViewPlugin as ViewPlugin3 } from "@codemirror/view";
709
989
  import * as automerge2 from "@dxos/automerge/automerge";
710
990
 
711
- // packages/ui/react-ui-editor/src/automerge/automerge-plugin/PatchSemaphore.ts
991
+ // packages/ui/react-ui-editor/src/hooks/automerge/PatchSemaphore.ts
712
992
  import { next as automerge } from "@dxos/automerge/automerge";
713
993
 
714
- // packages/ui/react-ui-editor/src/automerge/automerge-plugin/amToCodemirror.ts
994
+ // packages/ui/react-ui-editor/src/hooks/automerge/amToCodemirror.ts
715
995
  import { ChangeSet } from "@codemirror/state";
716
996
  var amToCodemirror_default = (view, selection, target, patches) => {
717
997
  for (const patch of patches) {
@@ -818,7 +1098,7 @@ var charPath = (textPath, candidatePath) => {
818
1098
  return null;
819
1099
  };
820
1100
 
821
- // packages/ui/react-ui-editor/src/automerge/automerge-plugin/codeMirrorToAm.ts
1101
+ // packages/ui/react-ui-editor/src/hooks/automerge/codeMirrorToAm.ts
822
1102
  import { next as am } from "@dxos/automerge/automerge";
823
1103
  var codeMirrorToAm_default = (field, handle, transactions, state) => {
824
1104
  const { lastHeads, path } = state.field(field);
@@ -841,7 +1121,7 @@ var codeMirrorToAm_default = (field, handle, transactions, state) => {
841
1121
  return newHeads ?? void 0;
842
1122
  };
843
1123
 
844
- // packages/ui/react-ui-editor/src/automerge/automerge-plugin/PatchSemaphore.ts
1124
+ // packages/ui/react-ui-editor/src/hooks/automerge/PatchSemaphore.ts
845
1125
  var PatchSemaphore = class {
846
1126
  constructor(field) {
847
1127
  this._inReconcile = false;
@@ -882,7 +1162,7 @@ var PatchSemaphore = class {
882
1162
  }
883
1163
  };
884
1164
 
885
- // packages/ui/react-ui-editor/src/automerge/automerge-plugin/plugin.ts
1165
+ // packages/ui/react-ui-editor/src/hooks/automerge/plugin.ts
886
1166
  var effectType = StateEffect.define({});
887
1167
  var updateHeads = (newHeads) => effectType.of({
888
1168
  newHeads
@@ -923,7 +1203,7 @@ var automergePlugin = (handle, path) => {
923
1203
  }
924
1204
  });
925
1205
  const semaphore = new PatchSemaphore(stateField);
926
- const viewPlugin = ViewPlugin2.fromClass(class AutomergeCodemirrorViewPlugin {
1206
+ const viewPlugin = ViewPlugin3.fromClass(class AutomergeCodemirrorViewPlugin {
927
1207
  constructor(view) {
928
1208
  this._handleChange = () => {
929
1209
  this._view.state.facet(semaphoreFacet).reconcile(handle, this._view);
@@ -953,45 +1233,6 @@ var automergePlugin = (handle, path) => {
953
1233
  var reconcileAnnotationType = Annotation.define();
954
1234
  var isReconcileTx = (tr) => !!tr.annotation(reconcileAnnotationType);
955
1235
 
956
- // packages/ui/react-ui-editor/src/hooks/useCollaboration.ts
957
- var useCollaboration = (model, themeMode) => {
958
- const { provider, peer, content } = model;
959
- const extension = useMemo(() => {
960
- if (content instanceof YText) {
961
- return yCollab(content, provider?.awareness);
962
- } else if (isDocAccessor(content)) {
963
- return automergePlugin(content.handle, content.path);
964
- } else {
965
- return void 0;
966
- }
967
- }, []);
968
- useEffect(() => {
969
- if (provider && peer) {
970
- provider.awareness.setLocalStateField("user", {
971
- name: peer.name ?? generateName(peer.id),
972
- color: getColorForValue({
973
- value: peer.id,
974
- type: "color"
975
- }),
976
- colorLight: getColorForValue({
977
- value: peer.id,
978
- themeMode,
979
- type: "highlight"
980
- })
981
- });
982
- }
983
- }, [
984
- provider,
985
- peer,
986
- themeMode
987
- ]);
988
- return extension;
989
- };
990
-
991
- // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
992
- import { useMemo as useMemo2 } from "react";
993
- import { getRawDoc, isActualAutomergeObject } from "@dxos/react-client/echo";
994
-
995
1236
  // packages/ui/react-ui-editor/src/hooks/yjs/cursors.ts
996
1237
  import * as random from "lib0/random";
997
1238
  var cursorColors = [
@@ -1036,7 +1277,7 @@ import * as encoding from "lib0/encoding";
1036
1277
  import { Observable } from "lib0/observable";
1037
1278
  import * as awarenessProtocol from "y-protocols/awareness";
1038
1279
  import { log } from "@dxos/log";
1039
- var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/yjs/space-provider.ts";
1280
+ var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/yjs/space-provider.ts";
1040
1281
  var messageAwareness = 1;
1041
1282
  var messageQueryAwareness = 3;
1042
1283
  var SpaceAwarenessProvider = class extends Observable {
@@ -1073,7 +1314,7 @@ var SpaceAwarenessProvider = class extends Observable {
1073
1314
  removed,
1074
1315
  origin
1075
1316
  }, {
1076
- F: __dxlog_file,
1317
+ F: __dxlog_file2,
1077
1318
  L: 67,
1078
1319
  S: this,
1079
1320
  C: (f, a) => f(...a)
@@ -1086,7 +1327,7 @@ var SpaceAwarenessProvider = class extends Observable {
1086
1327
  }
1087
1328
  _handleSpaceMessage({ payload }) {
1088
1329
  log("space message", payload, {
1089
- F: __dxlog_file,
1330
+ F: __dxlog_file2,
1090
1331
  L: 79,
1091
1332
  S: this,
1092
1333
  C: (f, a) => f(...a)
@@ -1131,185 +1372,80 @@ var SpaceAwarenessProvider = class extends Observable {
1131
1372
  };
1132
1373
 
1133
1374
  // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
1375
+ var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/useTextModel.ts";
1134
1376
  var useTextModel = ({ identity, space, text }) => {
1135
- const provider = useMemo2(() => {
1136
- if (isActualAutomergeObject(text)) {
1137
- return void 0;
1138
- }
1139
- if (!space || !text?.doc) {
1140
- return void 0;
1141
- }
1142
- return new SpaceAwarenessProvider({
1377
+ const [model, setModel] = useState2(() => createModel({
1378
+ identity,
1379
+ space,
1380
+ text
1381
+ }));
1382
+ useEffect2(() => {
1383
+ setModel(createModel({
1384
+ identity,
1143
1385
  space,
1144
- doc: text.doc,
1145
- channel: `yjs.awareness.${text.id}`
1146
- });
1386
+ text
1387
+ }));
1147
1388
  }, [
1148
1389
  identity,
1149
1390
  space,
1150
- text,
1151
- text?.doc
1152
- ]);
1153
- const content = useMemo2(() => {
1154
- if (isActualAutomergeObject(text)) {
1155
- const obj = text;
1156
- return getRawDoc(obj, [
1157
- obj.field
1158
- ]);
1159
- } else {
1160
- return text?.content;
1161
- }
1162
- }, [
1163
1391
  text
1164
1392
  ]);
1393
+ return model;
1394
+ };
1395
+ var createModel = (options) => {
1396
+ const { text } = options;
1165
1397
  if (isActualAutomergeObject(text)) {
1166
- const obj = text;
1167
- return {
1168
- id: obj.id,
1169
- content,
1170
- provider: void 0,
1171
- peer: identity ? {
1172
- id: identity.identityKey.toHex(),
1173
- name: identity.profile?.displayName
1174
- } : void 0
1175
- };
1176
- }
1177
- if (!text?.doc || !text?.content) {
1178
- return void 0;
1398
+ return createAutomergeModel(options);
1399
+ } else {
1400
+ if (!text?.doc) {
1401
+ return void 0;
1402
+ }
1403
+ return createYjsModel(options);
1179
1404
  }
1405
+ };
1406
+ var createYjsModel = ({ identity, space, text }) => {
1407
+ invariant2(text?.doc && text?.content, void 0, {
1408
+ F: __dxlog_file3,
1409
+ L: 74,
1410
+ S: void 0,
1411
+ A: [
1412
+ "text?.doc && text?.content",
1413
+ ""
1414
+ ]
1415
+ });
1416
+ const provider = space ? new SpaceAwarenessProvider({
1417
+ space,
1418
+ doc: text.doc,
1419
+ channel: `yjs.awareness.${text.id}`
1420
+ }) : void 0;
1180
1421
  return {
1181
1422
  id: text.doc.guid,
1182
1423
  content: text.content,
1183
- provider,
1424
+ text: () => text.content.toString(),
1425
+ extension: yCollab(text.content, provider?.awareness),
1426
+ awareness: provider?.awareness,
1184
1427
  peer: identity ? {
1185
1428
  id: identity.identityKey.toHex(),
1186
1429
  name: identity.profile?.displayName
1187
1430
  } : void 0
1188
1431
  };
1189
1432
  };
1190
-
1191
- // packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx
1192
- var EditorModes = [
1193
- "default",
1194
- "vim"
1195
- ];
1196
- var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, extensions = [], slots, editorMode }, forwardedRef) => {
1197
- const { themeMode } = useThemeContext();
1198
- const tabsterDOMAttribute = useFocusableGroup({
1199
- tabBehavior: "limited"
1200
- });
1201
- const [parent, setParent] = useState(null);
1202
- const [state, setState] = useState();
1203
- const [view, setView] = useState();
1204
- useImperativeHandle(forwardedRef, () => ({
1205
- editor: parent,
1206
- state,
1207
- view
1208
- }), [
1209
- view,
1210
- state,
1211
- parent
1212
- ]);
1213
- const collaboration = useCollaboration(model, themeMode);
1214
- useEffect2(() => {
1215
- if (!parent) {
1216
- return;
1217
- }
1218
- const initialText = isDocAccessor2(model.content) ? get3(model.content.handle.docSync(), model.content.path) : model.content?.toString();
1219
- const state2 = EditorState2.create({
1220
- doc: initialText,
1221
- extensions: [
1222
- // TODO(burdon): Factor out VIM mode?
1223
- editorMode === "vim" && vim(),
1224
- // Replication.
1225
- collaboration,
1226
- // Theme.
1227
- slots?.editor?.theme && EditorView5.theme(slots?.editor?.theme),
1228
- // Custom.
1229
- ...extensions
1230
- ].filter(Boolean)
1231
- });
1232
- setState(state2);
1233
- view?.destroy();
1234
- setView(new EditorView5({
1235
- state: state2,
1236
- parent
1237
- }));
1238
- return () => {
1239
- view?.destroy();
1240
- setView(void 0);
1241
- setState(void 0);
1242
- };
1243
- }, [
1244
- parent,
1245
- model.content,
1246
- themeMode,
1247
- editorMode
1248
- ]);
1249
- const handleKeyUp = useCallback((event) => {
1250
- const { key, altKey, shiftKey, metaKey, ctrlKey } = event;
1251
- switch (key) {
1252
- case "Enter": {
1253
- view?.contentDOM.focus();
1254
- break;
1255
- }
1256
- case "Escape": {
1257
- editorMode === "vim" && (altKey || shiftKey || metaKey || ctrlKey) && parent?.focus();
1258
- break;
1259
- }
1260
- }
1261
- }, [
1262
- view,
1263
- editorMode
1433
+ var createAutomergeModel = ({ identity, text }) => {
1434
+ const obj = text;
1435
+ const doc = getRawDoc(obj, [
1436
+ obj.field
1264
1437
  ]);
1265
- return /* @__PURE__ */ React.createElement("div", {
1266
- key: model.id,
1267
- ref: setParent,
1268
- tabIndex: 0,
1269
- ...slots?.root,
1270
- ...editorMode !== "vim" && tabsterDOMAttribute,
1271
- onKeyUp: handleKeyUp
1272
- });
1273
- });
1274
- var TextEditor = /* @__PURE__ */ forwardRef(({ extensions: _extensions, slots, ...props }, forwardedRef) => {
1275
- const { themeMode } = useThemeContext();
1276
- const extensions = [
1277
- ..._extensions ?? [],
1278
- basicBundle({
1279
- themeMode,
1280
- placeholder: slots?.editor?.placeholder
1281
- })
1282
- ];
1283
- return /* @__PURE__ */ React.createElement(BaseTextEditor, {
1284
- ref: forwardedRef,
1285
- extensions,
1286
- slots: defaultsDeep({}, slots, {
1287
- editor: {
1288
- theme: basicTheme
1289
- }
1290
- }),
1291
- ...props
1292
- });
1293
- });
1294
- var MarkdownEditor = /* @__PURE__ */ forwardRef(({ extensions: _extensions, slots, ...props }, forwardedRef) => {
1295
- const { themeMode } = useThemeContext();
1296
- const extensions = [
1297
- ..._extensions ?? [],
1298
- markdownBundle({
1299
- themeMode
1300
- })
1301
- ];
1302
- return /* @__PURE__ */ React.createElement(BaseTextEditor, {
1303
- ref: forwardedRef,
1304
- extensions,
1305
- slots: defaultsDeep({}, slots, {
1306
- editor: {
1307
- theme: markdownTheme
1308
- }
1309
- }),
1310
- ...props
1311
- });
1312
- });
1438
+ return {
1439
+ id: obj.id,
1440
+ content: doc,
1441
+ text: () => get3(doc.handle.docSync(), doc.path),
1442
+ extension: automergePlugin(doc.handle, doc.path),
1443
+ peer: identity ? {
1444
+ id: identity.identityKey.toHex(),
1445
+ name: identity.profile?.displayName
1446
+ } : void 0
1447
+ };
1448
+ };
1313
1449
  export {
1314
1450
  BaseTextEditor,
1315
1451
  Doc,
@@ -1318,34 +1454,28 @@ export {
1318
1454
  SpaceAwarenessProvider,
1319
1455
  TextEditor,
1320
1456
  TextKind,
1321
- YText2 as YText,
1457
+ YText,
1322
1458
  YXmlFragment,
1459
+ autocomplete,
1323
1460
  basicBundle,
1324
- basicTheme,
1325
- chalky,
1326
- coral,
1327
- createHyperlinkTooltip,
1461
+ codeTheme,
1462
+ comments,
1328
1463
  cursorColor,
1329
- cyan,
1330
- highlightBackground,
1331
- hyperLinkExtension,
1332
- hyperlinkDecoration,
1333
- hyperlinkStyle,
1334
- hyperlinkWidget,
1335
- invalid,
1336
- ivory,
1337
- malibu,
1464
+ defaultMarkdownSlots,
1465
+ defaultSlots,
1466
+ defaultTextSlots,
1467
+ defaultTheme,
1468
+ link,
1469
+ listener,
1338
1470
  markdownBundle,
1339
1471
  markdownHighlightStyle,
1472
+ markdownTags,
1473
+ markdownTagsExtension,
1340
1474
  markdownTheme,
1341
- onChangeExtension,
1342
- sage,
1343
- stone,
1344
1475
  tags2 as tags,
1345
- tooltipBackground,
1346
- useCollaboration,
1347
- useTextModel,
1348
- violet,
1349
- whiskey
1476
+ tasklist,
1477
+ textTheme,
1478
+ tooltip,
1479
+ useTextModel
1350
1480
  };
1351
1481
  //# sourceMappingURL=index.mjs.map