@dxos/react-ui-editor 0.3.9 → 0.3.10-main.04816d3

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 (128) hide show
  1. package/dist/lib/browser/index.mjs +952 -736
  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 +18 -0
  15. package/dist/types/src/components/TextEditor/extensions/comments.d.ts.map +1 -0
  16. package/dist/types/src/components/TextEditor/extensions/demo.d.ts +10 -0
  17. package/dist/types/src/components/TextEditor/extensions/demo.d.ts.map +1 -0
  18. package/dist/types/src/components/TextEditor/extensions/{hyperlink/hyperlinkWidget.d.ts → experimental.d.ts} +2 -2
  19. package/dist/types/src/components/TextEditor/extensions/experimental.d.ts.map +1 -0
  20. package/dist/types/src/components/TextEditor/extensions/index.d.ts +7 -2
  21. package/dist/types/src/components/TextEditor/extensions/index.d.ts.map +1 -1
  22. package/dist/types/src/components/TextEditor/extensions/link.d.ts +11 -0
  23. package/dist/types/src/components/TextEditor/extensions/link.d.ts.map +1 -0
  24. package/dist/types/src/components/TextEditor/extensions/listener.d.ts +9 -0
  25. package/dist/types/src/components/TextEditor/extensions/listener.d.ts.map +1 -0
  26. package/dist/types/src/components/TextEditor/extensions/markdown/bundle.d.ts.map +1 -1
  27. package/dist/types/src/components/TextEditor/extensions/markdown/highlight.d.ts +31 -0
  28. package/dist/types/src/components/TextEditor/extensions/markdown/highlight.d.ts.map +1 -0
  29. package/dist/types/src/components/TextEditor/extensions/markdown/index.d.ts +1 -1
  30. package/dist/types/src/components/TextEditor/extensions/markdown/index.d.ts.map +1 -1
  31. package/dist/types/src/components/TextEditor/extensions/tasklist.d.ts +2 -0
  32. package/dist/types/src/components/TextEditor/extensions/tasklist.d.ts.map +1 -0
  33. package/dist/types/src/components/TextEditor/extensions/tooltip.d.ts +12 -0
  34. package/dist/types/src/components/TextEditor/extensions/tooltip.d.ts.map +1 -0
  35. package/dist/types/src/components/TextEditor/index.d.ts +2 -0
  36. package/dist/types/src/components/TextEditor/index.d.ts.map +1 -1
  37. package/dist/types/src/components/TextEditor/themes/default.d.ts +20 -0
  38. package/dist/types/src/components/TextEditor/themes/default.d.ts.map +1 -0
  39. package/dist/types/src/components/TextEditor/themes/index.d.ts +2 -0
  40. package/dist/types/src/components/TextEditor/themes/index.d.ts.map +1 -0
  41. package/dist/types/src/components/TextEditor/yjs.stories.d.ts +1 -0
  42. package/dist/types/src/components/TextEditor/yjs.stories.d.ts.map +1 -1
  43. package/dist/types/src/hooks/automerge/PatchSemaphore.d.ts.map +1 -0
  44. package/dist/types/src/hooks/automerge/amToCodemirror.d.ts.map +1 -0
  45. package/dist/types/src/hooks/automerge/automerge.stories.d.ts.map +1 -0
  46. package/dist/types/src/hooks/automerge/codeMirrorToAm.d.ts.map +1 -0
  47. package/dist/types/src/hooks/automerge/handle.d.ts.map +1 -0
  48. package/dist/types/src/hooks/automerge/index.d.ts.map +1 -0
  49. package/dist/types/src/hooks/automerge/plugin.d.ts.map +1 -0
  50. package/dist/types/src/hooks/index.d.ts +0 -1
  51. package/dist/types/src/hooks/index.d.ts.map +1 -1
  52. package/dist/types/src/hooks/useTextModel.d.ts +4 -4
  53. package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
  54. package/dist/types/src/styles/markdown.d.ts +12 -10
  55. package/dist/types/src/styles/markdown.d.ts.map +1 -1
  56. package/dist/types/src/testing/replicator.d.ts.map +1 -1
  57. package/package.json +22 -32
  58. package/src/components/TextEditor/MarkdownEditor.stories.tsx +275 -0
  59. package/src/components/TextEditor/TextEditor.stories.tsx +46 -43
  60. package/src/components/TextEditor/TextEditor.tsx +79 -44
  61. package/src/components/TextEditor/extensions/autocomplete.ts +74 -0
  62. package/src/components/TextEditor/extensions/{basic/bundle.ts → basic.ts} +4 -2
  63. package/src/components/TextEditor/extensions/comments.ts +190 -0
  64. package/src/components/TextEditor/extensions/demo.ts +66 -0
  65. package/src/components/TextEditor/extensions/{hyperlink/hyperlinkWidget.tsx → experimental.tsx} +18 -2
  66. package/src/components/TextEditor/extensions/index.ts +7 -2
  67. package/src/components/TextEditor/extensions/{hyperlink/hyperlinkDecoration.ts → link.ts} +76 -31
  68. package/src/components/TextEditor/extensions/{change.ts → listener.ts} +4 -3
  69. package/src/components/TextEditor/extensions/markdown/bundle.ts +21 -25
  70. package/src/components/TextEditor/extensions/markdown/highlight.ts +167 -0
  71. package/src/components/TextEditor/extensions/markdown/index.ts +1 -1
  72. package/src/components/TextEditor/extensions/tasklist.ts +107 -0
  73. package/src/components/TextEditor/extensions/{hyperlink/hyperlinkTooltip.tsx → tooltip.tsx} +9 -9
  74. package/src/components/TextEditor/index.ts +3 -0
  75. package/src/components/TextEditor/themes/default.ts +185 -0
  76. package/src/components/TextEditor/themes/index.ts +5 -0
  77. package/src/components/TextEditor/yjs.stories.tsx +1 -0
  78. package/src/{automerge → hooks/automerge}/automerge.stories.tsx +2 -1
  79. package/src/hooks/index.ts +0 -1
  80. package/src/hooks/useTextModel.ts +52 -39
  81. package/src/styles/markdown.ts +26 -24
  82. package/src/testing/replicator.ts +6 -6
  83. package/dist/types/src/automerge/automerge-plugin/PatchSemaphore.d.ts.map +0 -1
  84. package/dist/types/src/automerge/automerge-plugin/amToCodemirror.d.ts.map +0 -1
  85. package/dist/types/src/automerge/automerge-plugin/codeMirrorToAm.d.ts.map +0 -1
  86. package/dist/types/src/automerge/automerge-plugin/handle.d.ts.map +0 -1
  87. package/dist/types/src/automerge/automerge-plugin/index.d.ts.map +0 -1
  88. package/dist/types/src/automerge/automerge-plugin/plugin.d.ts.map +0 -1
  89. package/dist/types/src/automerge/automerge.stories.d.ts.map +0 -1
  90. package/dist/types/src/components/TextEditor/extensions/basic/bundle.d.ts.map +0 -1
  91. package/dist/types/src/components/TextEditor/extensions/basic/index.d.ts +0 -3
  92. package/dist/types/src/components/TextEditor/extensions/basic/index.d.ts.map +0 -1
  93. package/dist/types/src/components/TextEditor/extensions/basic/theme.d.ts +0 -10
  94. package/dist/types/src/components/TextEditor/extensions/basic/theme.d.ts.map +0 -1
  95. package/dist/types/src/components/TextEditor/extensions/change.d.ts +0 -7
  96. package/dist/types/src/components/TextEditor/extensions/change.d.ts.map +0 -1
  97. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkDecoration.d.ts +0 -11
  98. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkDecoration.d.ts.map +0 -1
  99. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkTooltip.d.ts +0 -8
  100. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkTooltip.d.ts.map +0 -1
  101. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkWidget.d.ts.map +0 -1
  102. package/dist/types/src/components/TextEditor/extensions/hyperlink/index.d.ts +0 -4
  103. package/dist/types/src/components/TextEditor/extensions/hyperlink/index.d.ts.map +0 -1
  104. package/dist/types/src/components/TextEditor/extensions/markdown/tags.d.ts +0 -17
  105. package/dist/types/src/components/TextEditor/extensions/markdown/tags.d.ts.map +0 -1
  106. package/dist/types/src/components/TextEditor/extensions/markdown/theme.d.ts +0 -21
  107. package/dist/types/src/components/TextEditor/extensions/markdown/theme.d.ts.map +0 -1
  108. package/dist/types/src/hooks/useCollaboration.d.ts +0 -9
  109. package/dist/types/src/hooks/useCollaboration.d.ts.map +0 -1
  110. package/src/components/TextEditor/extensions/basic/index.ts +0 -6
  111. package/src/components/TextEditor/extensions/basic/theme.ts +0 -49
  112. package/src/components/TextEditor/extensions/hyperlink/index.ts +0 -7
  113. package/src/components/TextEditor/extensions/markdown/tags.ts +0 -38
  114. package/src/components/TextEditor/extensions/markdown/theme.ts +0 -265
  115. package/src/hooks/useCollaboration.ts +0 -45
  116. /package/dist/types/src/{automerge/automerge-plugin → hooks/automerge}/PatchSemaphore.d.ts +0 -0
  117. /package/dist/types/src/{automerge/automerge-plugin → hooks/automerge}/amToCodemirror.d.ts +0 -0
  118. /package/dist/types/src/{automerge → hooks/automerge}/automerge.stories.d.ts +0 -0
  119. /package/dist/types/src/{automerge/automerge-plugin → hooks/automerge}/codeMirrorToAm.d.ts +0 -0
  120. /package/dist/types/src/{automerge/automerge-plugin → hooks/automerge}/handle.d.ts +0 -0
  121. /package/dist/types/src/{automerge/automerge-plugin → hooks/automerge}/index.d.ts +0 -0
  122. /package/dist/types/src/{automerge/automerge-plugin → hooks/automerge}/plugin.d.ts +0 -0
  123. /package/src/{automerge/automerge-plugin → hooks/automerge}/PatchSemaphore.ts +0 -0
  124. /package/src/{automerge/automerge-plugin → hooks/automerge}/amToCodemirror.ts +0 -0
  125. /package/src/{automerge/automerge-plugin → hooks/automerge}/codeMirrorToAm.ts +0 -0
  126. /package/src/{automerge/automerge-plugin → hooks/automerge}/handle.ts +0 -0
  127. /package/src/{automerge/automerge-plugin → hooks/automerge}/index.ts +0 -0
  128. /package/src/{automerge/automerge-plugin → hooks/automerge}/plugin.ts +0 -0
@@ -1,519 +1,413 @@
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"
57
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/demo.ts
58
+ import { keymap as keymap2 } from "@codemirror/view";
59
+ var defaultItems = [
60
+ "hello world!",
61
+ "this is a test.",
62
+ "this is [DXOS](https://dxos.org)"
63
+ ];
64
+ var demo = ({ delay = 75, items = defaultItems } = {}) => {
65
+ let t;
66
+ let idx = 0;
67
+ return [
68
+ keymap2.of([
69
+ {
70
+ // Reset.
71
+ key: "alt-meta-'",
72
+ run: (view) => {
73
+ clearTimeout(t);
74
+ idx = 0;
75
+ return true;
76
+ }
77
+ },
78
+ {
79
+ // Next prompt.
80
+ // TODO(burdon): Press 1-9 to select prompt?
81
+ key: "shift-meta-'",
82
+ run: (view) => {
83
+ clearTimeout(t);
84
+ const text = items[idx++];
85
+ if (idx === items?.length) {
86
+ idx = 0;
87
+ }
88
+ let i = 0;
89
+ const insert = (d = 0) => {
90
+ t = setTimeout(() => {
91
+ const pos = view.state.selection.main.head;
92
+ view.dispatch({
93
+ changes: {
94
+ from: pos,
95
+ insert: text[i++]
96
+ },
97
+ selection: {
98
+ anchor: pos + 1
99
+ }
100
+ });
101
+ if (i < text.length) {
102
+ insert(Math.random() * delay * (text[i] === " " ? 2 : 1));
103
+ }
104
+ }, d);
105
+ };
106
+ insert();
107
+ return true;
108
+ }
109
+ }
110
+ ])
111
+ ];
34
112
  };
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
113
 
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"
114
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/comments.ts
115
+ import { keymap as keymap3, Decoration, EditorView as EditorView2, MatchDecorator, ViewPlugin, WidgetType } from "@codemirror/view";
116
+ import { debounce } from "@dxos/async";
117
+ import { invariant } from "@dxos/invariant";
118
+ var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/components/TextEditor/extensions/comments.ts";
119
+ var BookmarkWidget = class extends WidgetType {
120
+ constructor(_pos, _id) {
121
+ super();
122
+ this._pos = _pos;
123
+ this._id = _id;
124
+ invariant(this._id, void 0, {
125
+ F: __dxlog_file,
126
+ L: 41,
127
+ S: this,
128
+ A: [
129
+ "this._id",
130
+ ""
131
+ ]
132
+ });
133
+ }
134
+ eq(other) {
135
+ return this._id === other._id;
136
+ }
137
+ toDOM() {
138
+ const span = document.createElement("span");
139
+ span.className = "cm-bookmark";
140
+ span.textContent = "\u{1F4AC}";
141
+ return span;
77
142
  }
78
143
  };
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;
144
+ var styles = EditorView2.baseTheme({
145
+ "& .cm-bookmark": {
146
+ cursor: "pointer",
147
+ margin: "4px",
148
+ padding: "4px",
149
+ backgroundColor: "yellow"
150
+ },
151
+ "& .cm-bookmark-selected": {
152
+ backgroundColor: "orange"
89
153
  }
90
154
  });
155
+ var comments = (options = {}) => {
156
+ let active;
157
+ const bookmarkMatcher = new MatchDecorator({
158
+ regexp: /\[\^(\w+)\]/g,
159
+ decoration: (match, view, pos) => {
160
+ const id = match[1];
161
+ return Decoration.replace({
162
+ id,
163
+ widget: new BookmarkWidget(pos, id)
164
+ });
165
+ }
166
+ });
167
+ const bookmarks = ViewPlugin.fromClass(class {
168
+ constructor(view) {
169
+ this.bookmarks = bookmarkMatcher.createDeco(view);
170
+ }
171
+ update(update2) {
172
+ this.bookmarks = bookmarkMatcher.updateDeco(update2, this.bookmarks);
173
+ }
174
+ }, {
175
+ decorations: (instance) => instance.bookmarks,
176
+ provide: (plugin) => EditorView2.atomicRanges.of((view) => {
177
+ return view.plugin(plugin)?.bookmarks || Decoration.none;
178
+ })
179
+ });
180
+ const doUpdate = debounce((data) => {
181
+ options.onUpdate?.(data);
182
+ }, 200);
183
+ return [
184
+ keymap3.of([
185
+ {
186
+ key: options?.key ?? "shift-meta-c",
187
+ run: (view) => {
188
+ const id = options.onCreate?.();
189
+ if (id) {
190
+ const pos = view.state.selection.main.head;
191
+ const tag = `[^${id}]`;
192
+ view.dispatch({
193
+ changes: {
194
+ from: pos,
195
+ insert: tag
196
+ },
197
+ selection: {
198
+ anchor: pos + tag.length
199
+ }
200
+ });
201
+ return true;
202
+ }
203
+ return false;
204
+ }
205
+ }
206
+ ]),
207
+ bookmarks,
208
+ // Monitor cursor movement.
209
+ EditorView2.updateListener.of((update2) => {
210
+ active = void 0;
211
+ if (!options.onUpdate) {
212
+ return;
213
+ }
214
+ const view = update2.view;
215
+ const pos = view.state.selection.main.head;
216
+ const decorations = [];
217
+ const rangeSet = view.plugin(bookmarks)?.bookmarks;
218
+ let closest = Infinity;
219
+ const { from, to } = view.visibleRanges?.[0] ?? {
220
+ from: 0,
221
+ to: view.state.doc.length
222
+ };
223
+ rangeSet?.between(from, to, (from2, to2, value) => {
224
+ decorations.push({
225
+ from: from2,
226
+ to: to2,
227
+ value
228
+ });
229
+ const d = Math.min(Math.abs(pos - from2), Math.abs(pos - to2));
230
+ if (d < closest) {
231
+ active = value.spec.id;
232
+ closest = d;
233
+ }
234
+ });
235
+ if (decorations.length) {
236
+ doUpdate({
237
+ active,
238
+ items: decorations.map(({ from: from2, value: { spec: { id } } }) => ({
239
+ id,
240
+ pos: from2,
241
+ location: view.coordsAtPos(from2)
242
+ }))
243
+ });
244
+ }
245
+ }),
246
+ styles
247
+ ];
248
+ };
91
249
 
92
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/hyperlink/hyperlinkDecoration.ts
250
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/link.ts
93
251
  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) {
252
+ import { RangeSetBuilder, StateField } from "@codemirror/state";
253
+ import { Decoration as Decoration2, EditorView as EditorView3, WidgetType as WidgetType2 } from "@codemirror/view";
254
+ var LinkButton = class extends WidgetType2 {
255
+ constructor(_pos, _url, _onAttach) {
98
256
  super();
99
- this._anchor = _anchor;
257
+ this._pos = _pos;
258
+ this._url = _url;
259
+ this._onAttach = _onAttach;
260
+ }
261
+ eq(other) {
262
+ return this._pos === other._pos && this._url === other._url;
263
+ }
264
+ toDOM(view) {
265
+ const el = document.createElement("span");
266
+ this._onAttach(el, this._url);
267
+ return el;
268
+ }
269
+ };
270
+ var LinkText = class extends WidgetType2 {
271
+ constructor(_pos, _text, _url) {
272
+ super();
273
+ this._pos = _pos;
100
274
  this._text = _text;
101
275
  this._url = _url;
102
276
  }
103
277
  eq(other) {
104
- return this._text === other.text && this._url === other.url;
278
+ return this._pos === other._pos && this._url === other._url;
105
279
  }
106
280
  toDOM(view) {
107
- const link = document.createElement("a");
108
- link.setAttribute("class", "cm-link");
109
- link.textContent = this._text;
281
+ const link2 = document.createElement("a");
282
+ link2.setAttribute("class", "cm-link");
283
+ link2.textContent = this._text;
110
284
  if (this._url) {
111
- link.setAttribute("target", "_blank");
112
- link.href = this._url;
285
+ link2.setAttribute("rel", "noreferrer");
286
+ link2.setAttribute("target", "_blank");
287
+ link2.href = this._url;
113
288
  } else {
114
- link.onclick = () => {
289
+ link2.onclick = () => {
115
290
  view.dispatch({
116
291
  selection: {
117
- anchor: this._anchor
292
+ anchor: this._pos
118
293
  }
119
294
  });
120
295
  };
121
296
  }
122
- return link;
297
+ return link2;
123
298
  }
124
299
  };
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) => {
300
+ var update = (state, options) => {
133
301
  const builder = new RangeSetBuilder();
134
302
  const cursor = state.selection.main.head;
135
303
  syntaxTree(state).iterate({
136
304
  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
- }));
305
+ if (node.name === "Link") {
306
+ const marks = node.node.getChildren("LinkMark");
307
+ const text = marks.length >= 2 ? state.sliceDoc(marks[0].to, marks[1].from) : "";
308
+ const urlNode = node.node.getChild("URL");
309
+ const url = urlNode ? state.sliceDoc(urlNode.from, urlNode.to) : "";
310
+ if (!url) {
146
311
  return false;
147
312
  }
313
+ if (cursor < node.from || cursor > node.to) {
314
+ builder.add(node.from, node.to, Decoration2.replace({
315
+ widget: new LinkText(node.from, text, options.link ? url : void 0)
316
+ }));
317
+ }
318
+ if (options.onRender) {
319
+ builder.add(node.to, node.to, Decoration2.replace({
320
+ widget: new LinkButton(node.from, url, options.onRender)
321
+ }));
322
+ }
323
+ return false;
148
324
  }
149
- return true;
150
325
  }
151
326
  });
152
327
  return builder.finish();
153
328
  };
329
+ var link = (options = {}) => {
330
+ return StateField.define({
331
+ create: (state) => update(state, options),
332
+ update: (_, tr) => update(tr.state, options),
333
+ provide: (field) => EditorView3.decorations.from(field)
334
+ });
335
+ };
154
336
 
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;
337
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/listener.ts
338
+ import { StateField as StateField2 } from "@codemirror/state";
339
+ var listener = ({ onChange }) => StateField2.define({
340
+ create: () => null,
341
+ update: (_value, transaction) => {
342
+ if (transaction.docChanged && onChange) {
343
+ onChange(transaction.newDoc.toString());
173
344
  }
174
- idx += match[0].length;
175
- } while (true);
176
- if (!match) {
177
345
  return null;
178
346
  }
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
347
  });
197
348
 
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
349
  // packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/bundle.ts
292
350
  import { closeBrackets as closeBrackets2, closeBracketsKeymap } from "@codemirror/autocomplete";
293
- import { defaultKeymap, history, historyKeymap, indentWithTab } from "@codemirror/commands";
351
+ import { defaultKeymap, history, indentWithTab } from "@codemirror/commands";
294
352
  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";
353
+ import { bracketMatching as bracketMatching2, defaultHighlightStyle as defaultHighlightStyle2, indentOnInput, syntaxHighlighting as syntaxHighlighting2 } from "@codemirror/language";
296
354
  import { languages } from "@codemirror/language-data";
297
- import { lintKeymap } from "@codemirror/lint";
298
355
  import { highlightSelectionMatches, searchKeymap } from "@codemirror/search";
299
356
  import { EditorState } from "@codemirror/state";
300
357
  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";
358
+ import { crosshairCursor, drawSelection, dropCursor, EditorView as EditorView4, highlightActiveLine, highlightActiveLineGutter, highlightSpecialChars, keymap as keymap4, placeholder as placeholder2, rectangularSelection } from "@codemirror/view";
359
+
360
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/highlight.ts
361
+ import { markdownLanguage } from "@codemirror/lang-markdown";
362
+ import { HighlightStyle } from "@codemirror/language";
363
+ import { tags, styleTags, Tag } from "@lezer/highlight";
364
+ import get from "lodash.get";
365
+
366
+ // packages/ui/react-ui-editor/src/styles/markdown.ts
367
+ import { mx } from "@dxos/react-ui-theme";
368
+ var heading = {
369
+ 1: "mbs-4 mbe-2 font-medium text-inherit no-underline text-4xl",
370
+ 2: "mbs-4 mbe-2 font-medium text-inherit no-underline text-3xl",
371
+ 3: "mbs-4 mbe-2 font-medium text-inherit no-underline text-2xl",
372
+ 4: "mbs-4 mbe-2 font-medium text-inherit no-underline text-xl",
373
+ 5: "mbs-4 mbe-2 font-medium text-inherit no-underline text-lg",
374
+ 6: "mbs-4 mbe-2 font-medium text-inherit no-underline"
375
+ };
376
+ var light = "text-neutral-200 dark:text-neutral-800";
377
+ var mark = mx("!font-normal !no-underline !text-inherit opacity-40", light);
378
+ var bold = "font-bold";
379
+ var italic = "italic";
380
+ var strikethrough = "line-through";
381
+ var code = "font-mono !no-underline text-neutral-700 dark:text-neutral-300";
382
+ var codeMark = "font-mono text-primary-500";
383
+ var inlineUrl = mx(code, "px-1");
384
+ var blockquote = mx("pl-1 mr-1 border-is-4 border-primary-500/70 dark:border-primary-500/30", light);
385
+ var horizontalRule = "flex mlb-4 border-b text-neutral-100 dark:text-neutral-900 border-neutral-500/50";
386
+
387
+ // packages/ui/react-ui-editor/src/styles/tokens.ts
388
+ import { tailwindConfig } from "@dxos/react-ui-theme";
389
+ var tokens = tailwindConfig({}).theme;
302
390
 
303
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/tags.ts
304
- import { styleTags, Tag } from "@lezer/highlight";
391
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/highlight.ts
305
392
  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()
393
+ Blockquote: Tag.define(),
394
+ CodeMark: Tag.define(),
395
+ CodeText: Tag.define(),
396
+ EmphasisMark: Tag.define(),
397
+ HeaderMark: Tag.define(),
398
+ HorizontalRule: Tag.define(),
399
+ InlineCode: Tag.define(),
400
+ LinkLabel: Tag.define(),
401
+ LinkReference: Tag.define(),
402
+ ListMark: Tag.define(),
403
+ QuoteMark: Tag.define(),
404
+ URL: Tag.define()
317
405
  };
318
406
  var markdownTagsExtension = {
319
407
  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
- })
408
+ styleTags(markdownTags)
333
409
  ]
334
410
  };
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
411
  var markdownHighlightStyle = HighlightStyle.define([
518
412
  {
519
413
  tag: [
@@ -553,48 +447,22 @@ var markdownHighlightStyle = HighlightStyle.define([
553
447
  ],
554
448
  color: "inherit !important"
555
449
  },
450
+ // Markdown marks.
556
451
  {
557
452
  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
453
  tags.meta,
583
- tags.processingInstruction
454
+ tags.processingInstruction,
455
+ markdownTags.CodeMark,
456
+ markdownTags.EmphasisMark,
457
+ markdownTags.HeaderMark,
458
+ markdownTags.LinkLabel,
459
+ markdownTags.LinkReference,
460
+ markdownTags.ListMark,
461
+ markdownTags.QuoteMark
584
462
  ],
585
463
  class: mark
586
464
  },
587
- {
588
- tag: [
589
- markdownTags.codeText,
590
- markdownTags.inlineCode
591
- ],
592
- class: "font-mono"
593
- },
594
- {
595
- tag: tags.emphasis,
596
- class: italic
597
- },
465
+ // Headings.
598
466
  {
599
467
  tag: tags.heading1,
600
468
  class: heading[1]
@@ -619,18 +487,62 @@ var markdownHighlightStyle = HighlightStyle.define([
619
487
  tag: tags.heading6,
620
488
  class: heading[6]
621
489
  },
490
+ // Emphasis.
622
491
  {
623
- tag: tags.strikethrough,
624
- class: strikethrough
492
+ tag: tags.emphasis,
493
+ class: italic
625
494
  },
626
495
  {
627
496
  tag: tags.strong,
628
497
  class: bold
498
+ },
499
+ {
500
+ tag: tags.strikethrough,
501
+ class: strikethrough
502
+ },
503
+ // Naked URLs.
504
+ {
505
+ tag: [
506
+ markdownTags.URL
507
+ ],
508
+ class: inlineUrl
509
+ },
510
+ // E.g., code block language (after ```).
511
+ {
512
+ tag: [
513
+ tags.function(tags.variableName),
514
+ tags.labelName
515
+ ],
516
+ class: codeMark
517
+ },
518
+ // NOTE: The `markdown` extension configures extensions for `lezer` to parse markdown tokens (incl. below).
519
+ // However, since `codeLanguages` is also defined, the `lezer` will not parse fenced code blocks,
520
+ // when a language is specified. In this case, the syntax highlighting extensions will colorize
521
+ // the code, but all other CSS properties will be inherited.
522
+ // IMPORTANT: Therefore, the fenced code block will use the base editor font.
523
+ {
524
+ tag: [
525
+ markdownTags.CodeText,
526
+ markdownTags.InlineCode
527
+ ],
528
+ class: code
529
+ },
530
+ {
531
+ tag: [
532
+ markdownTags.QuoteMark
533
+ ],
534
+ class: blockquote
535
+ },
536
+ {
537
+ tag: [
538
+ markdownTags.HorizontalRule
539
+ ],
540
+ class: horizontalRule
629
541
  }
630
542
  ], {
631
543
  scope: markdownLanguage,
632
544
  all: {
633
- fontFamily: get2(tokens, "fontFamily.body", []).join(",")
545
+ fontFamily: get(tokens, "fontFamily.body", []).join(",")
634
546
  }
635
547
  });
636
548
 
@@ -642,7 +554,6 @@ var markdownBundle = ({ themeMode, placeholder: _placeholder }) => {
642
554
  _placeholder && placeholder2(_placeholder),
643
555
  EditorState.allowMultipleSelections.of(true),
644
556
  EditorView4.lineWrapping,
645
- // autocompletion(),
646
557
  crosshairCursor(),
647
558
  dropCursor(),
648
559
  drawSelection(),
@@ -653,65 +564,519 @@ var markdownBundle = ({ themeMode, placeholder: _placeholder }) => {
653
564
  history(),
654
565
  indentOnInput(),
655
566
  rectangularSelection(),
656
- keymap.of([
567
+ // TODO(burdon): Review.
568
+ keymap4.of([
657
569
  ...closeBracketsKeymap,
658
- // ...completionKeymap,
659
570
  ...defaultKeymap,
660
- ...foldKeymap,
661
- ...historyKeymap,
662
- ...lintKeymap,
571
+ // ...foldKeymap,
572
+ // ...historyKeymap,
573
+ // ...lintKeymap,
663
574
  ...searchKeymap,
664
575
  indentWithTab
665
576
  ]),
666
577
  // Main extension.
667
578
  // https://github.com/codemirror/lang-markdown
579
+ // https://codemirror.net/5/mode/markdown/index.html (demo).
668
580
  markdown({
581
+ // GRM by default (vs strict CommonMark):
582
+ // Table, TaskList, Strikethrough, and Autolink.
583
+ // NOTE: This extends the parser; it doesn't affect rendering.
584
+ // https://github.github.com/gfm
585
+ // https://github.com/lezer-parser/markdown?tab=readme-ov-file#github-flavored-markdown
669
586
  base: markdownLanguage2,
587
+ // Languages for syntax highlighting fenced code blocks.
670
588
  codeLanguages: languages,
589
+ // Parser extensions.
671
590
  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.
591
+ // GFM, // TODO(burdon): Provided by default?
678
592
  markdownTagsExtension
679
593
  ]
680
594
  }),
681
- // TODO(thure): All but one rule here apply to both themes; rename or refactor.
595
+ // Custom styles.
682
596
  syntaxHighlighting2(markdownHighlightStyle),
597
+ // TODO(thure): All but one rule here apply to both themes; rename or refactor.
683
598
  themeMode === "dark" ? syntaxHighlighting2(oneDarkHighlightStyle2) : syntaxHighlighting2(defaultHighlightStyle2)
684
599
  ].filter(Boolean);
685
600
  };
686
601
 
602
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/tasklist.ts
603
+ import { EditorView as EditorView5, Decoration as Decoration3, WidgetType as WidgetType3, MatchDecorator as MatchDecorator2, ViewPlugin as ViewPlugin2 } from "@codemirror/view";
604
+ var CheckboxWidget = class extends WidgetType3 {
605
+ constructor(_pos, _checked, _indent, _onCheck) {
606
+ super();
607
+ this._pos = _pos;
608
+ this._checked = _checked;
609
+ this._indent = _indent;
610
+ this._onCheck = _onCheck;
611
+ }
612
+ eq(other) {
613
+ return this._pos === other._pos && this._checked === other._checked && this._indent === other._indent;
614
+ }
615
+ toDOM(view) {
616
+ const wrap = document.createElement("span");
617
+ wrap.className = "cm-task-item";
618
+ wrap.setAttribute("aria-hidden", "true");
619
+ wrap.style.setProperty("margin-left", this._indent * 24 + "px");
620
+ const input = wrap.appendChild(document.createElement("input"));
621
+ input.type = "checkbox";
622
+ input.checked = this._checked;
623
+ input.onchange = (event) => {
624
+ this._onCheck(event.target.checked);
625
+ return true;
626
+ };
627
+ return wrap;
628
+ }
629
+ ignoreEvent() {
630
+ return false;
631
+ }
632
+ };
633
+ var styles2 = EditorView5.baseTheme({
634
+ "& .cm-task-item": {
635
+ paddingLeft: "4px",
636
+ paddingRight: "8px"
637
+ }
638
+ });
639
+ var tasklist = () => {
640
+ const taskMatcher = new MatchDecorator2({
641
+ regexp: /^(\s*)- \[([ xX])\]\s/g,
642
+ decoration: (match, view, pos) => {
643
+ const indent = Math.floor(match[1].length / 2);
644
+ const checked = match[2] === "x" || match[2] === "X";
645
+ return Decoration3.replace({
646
+ widget: new CheckboxWidget(pos, checked, indent, (checked2) => {
647
+ const idx = pos + match[0].indexOf("[") + 1;
648
+ view.dispatch({
649
+ changes: {
650
+ from: idx,
651
+ to: idx + 1,
652
+ insert: checked2 ? "x" : " "
653
+ },
654
+ // TODO(burdon): Restore cursor position? More useful to move to end of line (can indent).
655
+ selection: {
656
+ anchor: idx + 3
657
+ }
658
+ });
659
+ })
660
+ });
661
+ }
662
+ });
663
+ const tasks = ViewPlugin2.fromClass(class {
664
+ constructor(view) {
665
+ this.tasks = taskMatcher.createDeco(view);
666
+ }
667
+ update(update2) {
668
+ this.tasks = taskMatcher.updateDeco(update2, this.tasks);
669
+ }
670
+ }, {
671
+ decorations: (instance) => instance.tasks,
672
+ provide: (plugin) => EditorView5.atomicRanges.of((view) => {
673
+ return view.plugin(plugin)?.tasks || Decoration3.none;
674
+ })
675
+ });
676
+ return [
677
+ tasks,
678
+ styles2
679
+ ];
680
+ };
681
+
682
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/tooltip.tsx
683
+ import { hoverTooltip } from "@codemirror/view";
684
+ import { tooltipContent } from "@dxos/react-ui-theme";
685
+ var markdownLinkRegexp = /\[([^\]]+)]\(([^)]+)\)/;
686
+ var tooltip = ({ onHover, regexp = markdownLinkRegexp }) => hoverTooltip((view, pos) => {
687
+ const { from, text } = view.state.doc.lineAt(pos);
688
+ const p = pos - from;
689
+ let idx = 0;
690
+ let match;
691
+ do {
692
+ match = text.substring(idx).match(regexp);
693
+ if (!match) {
694
+ match = void 0;
695
+ break;
696
+ }
697
+ idx = text.indexOf(match[0]);
698
+ if (p >= idx && p < idx + match[0].length) {
699
+ break;
700
+ }
701
+ idx += match[0].length;
702
+ } while (true);
703
+ if (!match) {
704
+ return null;
705
+ }
706
+ const [, , url] = match ?? [];
707
+ return {
708
+ pos: idx + from,
709
+ end: idx + from + match[0].length,
710
+ above: true,
711
+ create: () => {
712
+ const el = document.createElement("div");
713
+ el.className = tooltipContent({}, "pli-2 plb-1");
714
+ onHover(el, url);
715
+ return {
716
+ dom: el,
717
+ offset: {
718
+ x: 0,
719
+ y: 12
720
+ }
721
+ };
722
+ }
723
+ };
724
+ });
725
+
726
+ // packages/ui/react-ui-editor/src/components/TextEditor/themes/default.ts
727
+ import get2 from "lodash.get";
728
+ var defaultTheme = {
729
+ //
730
+ // Main layout:
731
+ // <div class=".cm-editor .cm-focused">
732
+ // <div class=".cm-scroller">
733
+ // <div class=".cm-content" role="textbox" contenteditable="true">
734
+ // <div class=".cm-line" />
735
+ // </div>
736
+ // </div>
737
+ // </div>
738
+ //
739
+ "&": {},
740
+ "&.cm-focused": {
741
+ outline: "none"
742
+ },
743
+ "& .cm-scroller": {
744
+ overflow: "visible"
745
+ },
746
+ "& .cm-content": {
747
+ padding: 0,
748
+ // Base font size (otherwise defined by HTML tag, which might be different for storybook).
749
+ fontSize: "16px"
750
+ },
751
+ "&light .cm-content": {
752
+ color: get2(tokens, "extend.colors.neutral.900", "black"),
753
+ caretColor: "black"
754
+ },
755
+ "&dark .cm-content": {
756
+ color: get2(tokens, "extend.colors.neutral.100", "white"),
757
+ caretColor: "white"
758
+ },
759
+ //
760
+ // Cursor
761
+ //
762
+ "&light .cm-cursor": {
763
+ borderLeft: "2px solid black"
764
+ },
765
+ "&dark .cm-cursor": {
766
+ borderLeft: "2px solid white"
767
+ },
768
+ "& .cm-placeholder": {
769
+ fontWeight: 100
770
+ },
771
+ //
772
+ // line
773
+ //
774
+ "& .cm-line": {
775
+ paddingInline: 0
776
+ },
777
+ "& .cm-line *": {},
778
+ "& .cm-activeLine": {
779
+ backgroundColor: "transparent"
780
+ },
781
+ //
782
+ // selection
783
+ //
784
+ "&light .cm-selectionBackground, &light.cm-focused .cm-selectionBackground": {
785
+ background: get2(tokens, "extend.colors.primary.150", "#00ffff")
786
+ },
787
+ "&dark .cm-selectionBackground, &dark.cm-focused .cm-selectionBackground": {
788
+ background: get2(tokens, "extend.colors.primary.850", "#00ffff")
789
+ },
790
+ "&light .cm-selectionMatch": {
791
+ background: get2(tokens, "extend.colors.primary.250", "#00ffff") + "44"
792
+ },
793
+ "&dark .cm-selectionMatch": {
794
+ background: get2(tokens, "extend.colors.primary.600", "#00ffff") + "44"
795
+ },
796
+ //
797
+ // collaboration
798
+ // TODO(burdon): Review classnames (YJS dependent?)
799
+ //
800
+ "&light .cm-ySelection, &light .cm-yLineSelection": {
801
+ mixBlendMode: "multiply"
802
+ },
803
+ "&dark .cm-ySelection, &dark .cm-yLineSelection": {
804
+ mixBlendMode: "screen"
805
+ },
806
+ "& .cm-ySelectionInfo": {
807
+ padding: "2px 4px",
808
+ marginBlockStart: "-4px"
809
+ },
810
+ "& .cm-ySelection, & .cm-selectionMatch": {
811
+ paddingBlockStart: ".15em",
812
+ paddingBlockEnd: ".15em"
813
+ },
814
+ "& .cm-ySelectionCaret": {
815
+ display: "inline-block",
816
+ insetBlockStart: ".1em",
817
+ blockSize: "1.4em",
818
+ verticalAlign: "top"
819
+ },
820
+ "& .cm-yLineSelection": {
821
+ margin: 0
822
+ },
823
+ //
824
+ // link
825
+ //
826
+ "& .cm-link": {
827
+ color: get2(tokens, "extend.colors.primary.500"),
828
+ textDecorationLine: "underline",
829
+ textDecorationThickness: "1px",
830
+ textUnderlineOffset: "2px",
831
+ borderRadius: ".125rem",
832
+ fontFamily: get2(tokens, "fontFamily.body", []).join(",")
833
+ },
834
+ //
835
+ // tooltip
836
+ //
837
+ "& .cm-tooltip": {
838
+ border: "none"
839
+ },
840
+ // '& .cm-tooltip-below': {},
841
+ // '& .cm-tooltip-autocomplete': {},
842
+ //
843
+ // font size
844
+ // 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.
845
+ //
846
+ ...Object.keys(get2(tokens, "extend.fontSize", {})).reduce((acc, fontSize) => {
847
+ const height = get2(tokens, [
848
+ "extend",
849
+ "fontSize",
850
+ fontSize,
851
+ 1,
852
+ "lineHeight"
853
+ ]);
854
+ acc[`& .text-${fontSize} + .cm-ySelectionCaret`] = {
855
+ height
856
+ };
857
+ acc[`& .text-${fontSize} + .cm-ySelection + .cm-ySelectionCaret`] = {
858
+ height
859
+ };
860
+ acc[`& .text-${fontSize} + .cm-widgetBuffer + .cm-ySelectionCaret`] = {
861
+ height
862
+ };
863
+ return acc;
864
+ }, {})
865
+ };
866
+ var textTheme = {
867
+ "& .cm-scroller": {
868
+ fontFamily: get2(tokens, "fontFamily.body", []).join(",")
869
+ },
870
+ "& .cm-placeholder": {
871
+ fontFamily: get2(tokens, "fontFamily.body", []).join(",")
872
+ }
873
+ };
874
+ var markdownTheme = {
875
+ // NOTE: Must leave base font family as is (i.e., monospace) due to fenced code blocks.
876
+ "& .cm-placeholder": {
877
+ fontFamily: get2(tokens, "fontFamily.body", []).join(",")
878
+ }
879
+ };
880
+ var codeTheme = {
881
+ "& .cm-scroller": {
882
+ fontFamily: get2(tokens, "fontFamily.mono", []).join(",")
883
+ },
884
+ "& .cm-placeholder": {
885
+ fontFamily: get2(tokens, "fontFamily.mono", []).join(",")
886
+ }
887
+ };
888
+
687
889
  // packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx
688
890
  import { EditorState as EditorState2 } from "@codemirror/state";
689
- import { EditorView as EditorView5 } from "@codemirror/view";
891
+ import { EditorView as EditorView6 } from "@codemirror/view";
690
892
  import { useFocusableGroup } from "@fluentui/react-tabster";
691
893
  import { vim } from "@replit/codemirror-vim";
692
894
  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";
895
+ import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useState } from "react";
896
+ import { generateName } from "@dxos/display-name";
696
897
  import { useThemeContext } from "@dxos/react-ui";
898
+ import { getColorForValue, inputSurface, mx as mx2 } from "@dxos/react-ui-theme";
899
+ var EditorModes = [
900
+ "default",
901
+ "vim"
902
+ ];
903
+ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, extensions = [], slots = defaultSlots, editorMode }, forwardedRef) => {
904
+ const { themeMode } = useThemeContext();
905
+ const tabsterDOMAttribute = useFocusableGroup({
906
+ tabBehavior: "limited"
907
+ });
908
+ const [root, setRoot] = useState(null);
909
+ const [state, setState] = useState();
910
+ const [view, setView] = useState();
911
+ useImperativeHandle(forwardedRef, () => ({
912
+ root,
913
+ state,
914
+ view
915
+ }), [
916
+ view,
917
+ state,
918
+ root
919
+ ]);
920
+ const { awareness, peer } = model;
921
+ useEffect(() => {
922
+ if (awareness && peer) {
923
+ awareness.setLocalStateField("user", {
924
+ name: peer.name ?? generateName(peer.id),
925
+ color: getColorForValue({
926
+ value: peer.id,
927
+ type: "color"
928
+ }),
929
+ colorLight: getColorForValue({
930
+ value: peer.id,
931
+ themeMode,
932
+ type: "highlight"
933
+ })
934
+ });
935
+ }
936
+ }, [
937
+ awareness,
938
+ peer,
939
+ themeMode
940
+ ]);
941
+ useEffect(() => {
942
+ if (!root) {
943
+ return;
944
+ }
945
+ const state2 = EditorState2.create({
946
+ doc: model.text(),
947
+ extensions: [
948
+ // TODO(burdon): Factor out VIM mode?
949
+ editorMode === "vim" && vim(),
950
+ // Theme.
951
+ EditorView6.baseTheme(defaultTheme),
952
+ EditorView6.theme(slots?.editor?.theme ?? {}),
953
+ // TODO(burdon): themeMode doesn't change in storybooks.
954
+ EditorView6.darkTheme.of(themeMode === "dark"),
955
+ // Storage and replication.
956
+ model.extension,
957
+ // Custom.
958
+ ...extensions
959
+ ].filter(Boolean)
960
+ });
961
+ view?.destroy();
962
+ setView(new EditorView6({
963
+ state: state2,
964
+ parent: root
965
+ }));
966
+ setState(state2);
967
+ return () => {
968
+ view?.destroy();
969
+ setView(void 0);
970
+ setState(void 0);
971
+ };
972
+ }, [
973
+ root,
974
+ model,
975
+ themeMode,
976
+ editorMode
977
+ ]);
978
+ const handleKeyUp = useCallback((event) => {
979
+ const { key, altKey, shiftKey, metaKey, ctrlKey } = event;
980
+ switch (key) {
981
+ case "Enter": {
982
+ view?.contentDOM.focus();
983
+ break;
984
+ }
985
+ case "Escape": {
986
+ editorMode === "vim" && (altKey || shiftKey || metaKey || ctrlKey) && root?.focus();
987
+ break;
988
+ }
989
+ }
990
+ }, [
991
+ view,
992
+ editorMode
993
+ ]);
994
+ return /* @__PURE__ */ React.createElement("div", {
995
+ key: model.id,
996
+ ref: setRoot,
997
+ tabIndex: 0,
998
+ ...slots?.root,
999
+ ...editorMode !== "vim" && tabsterDOMAttribute,
1000
+ onKeyUp: handleKeyUp
1001
+ });
1002
+ });
1003
+ var maybeDebug = () => {
1004
+ const items = localStorage.getItem("dxos.composer.demo");
1005
+ if (items) {
1006
+ return demo({
1007
+ items: items.split(",")
1008
+ });
1009
+ }
1010
+ return [];
1011
+ };
1012
+ var TextEditor = /* @__PURE__ */ forwardRef(({ extensions = [], slots: _slots, ...props }, forwardedRef) => {
1013
+ const { themeMode } = useThemeContext();
1014
+ const slots = defaultsDeep({}, _slots, defaultTextSlots);
1015
+ return /* @__PURE__ */ React.createElement(BaseTextEditor, {
1016
+ ref: forwardedRef,
1017
+ extensions: [
1018
+ basicBundle({
1019
+ themeMode,
1020
+ placeholder: slots?.editor?.placeholder
1021
+ }),
1022
+ maybeDebug(),
1023
+ ...extensions
1024
+ ],
1025
+ slots,
1026
+ ...props
1027
+ });
1028
+ });
1029
+ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ extensions = [], slots: _slots, ...props }, forwardedRef) => {
1030
+ const { themeMode } = useThemeContext();
1031
+ const slots = defaultsDeep({}, _slots, defaultMarkdownSlots);
1032
+ return /* @__PURE__ */ React.createElement(BaseTextEditor, {
1033
+ ref: forwardedRef,
1034
+ extensions: [
1035
+ markdownBundle({
1036
+ themeMode,
1037
+ placeholder: slots?.editor?.placeholder
1038
+ }),
1039
+ maybeDebug(),
1040
+ ...extensions
1041
+ ],
1042
+ slots,
1043
+ ...props
1044
+ });
1045
+ });
1046
+ var defaultSlots = {
1047
+ root: {
1048
+ className: mx2("p-2", inputSurface)
1049
+ }
1050
+ };
1051
+ var defaultTextSlots = {
1052
+ ...defaultSlots,
1053
+ editor: {
1054
+ theme: textTheme
1055
+ }
1056
+ };
1057
+ var defaultMarkdownSlots = {
1058
+ ...defaultSlots,
1059
+ editor: {
1060
+ theme: markdownTheme
1061
+ }
1062
+ };
697
1063
 
698
- // packages/ui/react-ui-editor/src/hooks/useCollaboration.ts
699
- import { useEffect, useMemo } from "react";
1064
+ // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
1065
+ import get3 from "lodash.get";
1066
+ import { useEffect as useEffect2, useState as useState2 } from "react";
700
1067
  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";
1068
+ import { invariant as invariant2 } from "@dxos/invariant";
1069
+ import { getRawDoc, isActualAutomergeObject } from "@dxos/react-client/echo";
705
1070
 
706
- // packages/ui/react-ui-editor/src/automerge/automerge-plugin/plugin.ts
1071
+ // packages/ui/react-ui-editor/src/hooks/automerge/plugin.ts
707
1072
  import { Annotation, Facet, StateEffect, StateField as StateField3 } from "@codemirror/state";
708
- import { ViewPlugin as ViewPlugin2 } from "@codemirror/view";
1073
+ import { ViewPlugin as ViewPlugin3 } from "@codemirror/view";
709
1074
  import * as automerge2 from "@dxos/automerge/automerge";
710
1075
 
711
- // packages/ui/react-ui-editor/src/automerge/automerge-plugin/PatchSemaphore.ts
1076
+ // packages/ui/react-ui-editor/src/hooks/automerge/PatchSemaphore.ts
712
1077
  import { next as automerge } from "@dxos/automerge/automerge";
713
1078
 
714
- // packages/ui/react-ui-editor/src/automerge/automerge-plugin/amToCodemirror.ts
1079
+ // packages/ui/react-ui-editor/src/hooks/automerge/amToCodemirror.ts
715
1080
  import { ChangeSet } from "@codemirror/state";
716
1081
  var amToCodemirror_default = (view, selection, target, patches) => {
717
1082
  for (const patch of patches) {
@@ -818,7 +1183,7 @@ var charPath = (textPath, candidatePath) => {
818
1183
  return null;
819
1184
  };
820
1185
 
821
- // packages/ui/react-ui-editor/src/automerge/automerge-plugin/codeMirrorToAm.ts
1186
+ // packages/ui/react-ui-editor/src/hooks/automerge/codeMirrorToAm.ts
822
1187
  import { next as am } from "@dxos/automerge/automerge";
823
1188
  var codeMirrorToAm_default = (field, handle, transactions, state) => {
824
1189
  const { lastHeads, path } = state.field(field);
@@ -841,7 +1206,7 @@ var codeMirrorToAm_default = (field, handle, transactions, state) => {
841
1206
  return newHeads ?? void 0;
842
1207
  };
843
1208
 
844
- // packages/ui/react-ui-editor/src/automerge/automerge-plugin/PatchSemaphore.ts
1209
+ // packages/ui/react-ui-editor/src/hooks/automerge/PatchSemaphore.ts
845
1210
  var PatchSemaphore = class {
846
1211
  constructor(field) {
847
1212
  this._inReconcile = false;
@@ -882,7 +1247,7 @@ var PatchSemaphore = class {
882
1247
  }
883
1248
  };
884
1249
 
885
- // packages/ui/react-ui-editor/src/automerge/automerge-plugin/plugin.ts
1250
+ // packages/ui/react-ui-editor/src/hooks/automerge/plugin.ts
886
1251
  var effectType = StateEffect.define({});
887
1252
  var updateHeads = (newHeads) => effectType.of({
888
1253
  newHeads
@@ -923,7 +1288,7 @@ var automergePlugin = (handle, path) => {
923
1288
  }
924
1289
  });
925
1290
  const semaphore = new PatchSemaphore(stateField);
926
- const viewPlugin = ViewPlugin2.fromClass(class AutomergeCodemirrorViewPlugin {
1291
+ const viewPlugin = ViewPlugin3.fromClass(class AutomergeCodemirrorViewPlugin {
927
1292
  constructor(view) {
928
1293
  this._handleChange = () => {
929
1294
  this._view.state.facet(semaphoreFacet).reconcile(handle, this._view);
@@ -953,45 +1318,6 @@ var automergePlugin = (handle, path) => {
953
1318
  var reconcileAnnotationType = Annotation.define();
954
1319
  var isReconcileTx = (tr) => !!tr.annotation(reconcileAnnotationType);
955
1320
 
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
1321
  // packages/ui/react-ui-editor/src/hooks/yjs/cursors.ts
996
1322
  import * as random from "lib0/random";
997
1323
  var cursorColors = [
@@ -1036,7 +1362,7 @@ import * as encoding from "lib0/encoding";
1036
1362
  import { Observable } from "lib0/observable";
1037
1363
  import * as awarenessProtocol from "y-protocols/awareness";
1038
1364
  import { log } from "@dxos/log";
1039
- var __dxlog_file = "/home/circleci/project/packages/ui/react-ui-editor/src/hooks/yjs/space-provider.ts";
1365
+ var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/yjs/space-provider.ts";
1040
1366
  var messageAwareness = 1;
1041
1367
  var messageQueryAwareness = 3;
1042
1368
  var SpaceAwarenessProvider = class extends Observable {
@@ -1073,7 +1399,7 @@ var SpaceAwarenessProvider = class extends Observable {
1073
1399
  removed,
1074
1400
  origin
1075
1401
  }, {
1076
- F: __dxlog_file,
1402
+ F: __dxlog_file2,
1077
1403
  L: 67,
1078
1404
  S: this,
1079
1405
  C: (f, a) => f(...a)
@@ -1086,7 +1412,7 @@ var SpaceAwarenessProvider = class extends Observable {
1086
1412
  }
1087
1413
  _handleSpaceMessage({ payload }) {
1088
1414
  log("space message", payload, {
1089
- F: __dxlog_file,
1415
+ F: __dxlog_file2,
1090
1416
  L: 79,
1091
1417
  S: this,
1092
1418
  C: (f, a) => f(...a)
@@ -1131,185 +1457,80 @@ var SpaceAwarenessProvider = class extends Observable {
1131
1457
  };
1132
1458
 
1133
1459
  // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
1460
+ var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/useTextModel.ts";
1134
1461
  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({
1462
+ const [model, setModel] = useState2(() => createModel({
1463
+ identity,
1464
+ space,
1465
+ text
1466
+ }));
1467
+ useEffect2(() => {
1468
+ setModel(createModel({
1469
+ identity,
1143
1470
  space,
1144
- doc: text.doc,
1145
- channel: `yjs.awareness.${text.id}`
1146
- });
1471
+ text
1472
+ }));
1147
1473
  }, [
1148
1474
  identity,
1149
1475
  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
1476
  text
1164
1477
  ]);
1478
+ return model;
1479
+ };
1480
+ var createModel = (options) => {
1481
+ const { text } = options;
1165
1482
  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;
1483
+ return createAutomergeModel(options);
1484
+ } else {
1485
+ if (!text?.doc) {
1486
+ return void 0;
1487
+ }
1488
+ return createYjsModel(options);
1179
1489
  }
1490
+ };
1491
+ var createYjsModel = ({ identity, space, text }) => {
1492
+ invariant2(text?.doc && text?.content, void 0, {
1493
+ F: __dxlog_file3,
1494
+ L: 74,
1495
+ S: void 0,
1496
+ A: [
1497
+ "text?.doc && text?.content",
1498
+ ""
1499
+ ]
1500
+ });
1501
+ const provider = space ? new SpaceAwarenessProvider({
1502
+ space,
1503
+ doc: text.doc,
1504
+ channel: `yjs.awareness.${text.id}`
1505
+ }) : void 0;
1180
1506
  return {
1181
1507
  id: text.doc.guid,
1182
1508
  content: text.content,
1183
- provider,
1509
+ text: () => text.content.toString(),
1510
+ extension: yCollab(text.content, provider?.awareness),
1511
+ awareness: provider?.awareness,
1184
1512
  peer: identity ? {
1185
1513
  id: identity.identityKey.toHex(),
1186
1514
  name: identity.profile?.displayName
1187
1515
  } : void 0
1188
1516
  };
1189
1517
  };
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
1518
+ var createAutomergeModel = ({ identity, text }) => {
1519
+ const obj = text;
1520
+ const doc = getRawDoc(obj, [
1521
+ obj.field
1264
1522
  ]);
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
- });
1523
+ return {
1524
+ id: obj.id,
1525
+ content: doc,
1526
+ text: () => get3(doc.handle.docSync(), doc.path),
1527
+ extension: automergePlugin(doc.handle, doc.path),
1528
+ peer: identity ? {
1529
+ id: identity.identityKey.toHex(),
1530
+ name: identity.profile?.displayName
1531
+ } : void 0
1532
+ };
1533
+ };
1313
1534
  export {
1314
1535
  BaseTextEditor,
1315
1536
  Doc,
@@ -1318,34 +1539,29 @@ export {
1318
1539
  SpaceAwarenessProvider,
1319
1540
  TextEditor,
1320
1541
  TextKind,
1321
- YText2 as YText,
1542
+ YText,
1322
1543
  YXmlFragment,
1544
+ autocomplete,
1323
1545
  basicBundle,
1324
- basicTheme,
1325
- chalky,
1326
- coral,
1327
- createHyperlinkTooltip,
1546
+ codeTheme,
1547
+ comments,
1328
1548
  cursorColor,
1329
- cyan,
1330
- highlightBackground,
1331
- hyperLinkExtension,
1332
- hyperlinkDecoration,
1333
- hyperlinkStyle,
1334
- hyperlinkWidget,
1335
- invalid,
1336
- ivory,
1337
- malibu,
1549
+ defaultMarkdownSlots,
1550
+ defaultSlots,
1551
+ defaultTextSlots,
1552
+ defaultTheme,
1553
+ demo,
1554
+ link,
1555
+ listener,
1338
1556
  markdownBundle,
1339
1557
  markdownHighlightStyle,
1558
+ markdownTags,
1559
+ markdownTagsExtension,
1340
1560
  markdownTheme,
1341
- onChangeExtension,
1342
- sage,
1343
- stone,
1344
1561
  tags2 as tags,
1345
- tooltipBackground,
1346
- useCollaboration,
1347
- useTextModel,
1348
- violet,
1349
- whiskey
1562
+ tasklist,
1563
+ textTheme,
1564
+ tooltip,
1565
+ useTextModel
1350
1566
  };
1351
1567
  //# sourceMappingURL=index.mjs.map