@dxos/react-ui-editor 0.3.9 → 0.3.10-main.11cdcf4

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 (89) hide show
  1. package/dist/lib/browser/index.mjs +722 -701
  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 +35 -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 +4 -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 +9 -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/{hyperlink/hyperlinkWidget.d.ts → experimental.d.ts} +2 -2
  15. package/dist/types/src/components/TextEditor/extensions/experimental.d.ts.map +1 -0
  16. package/dist/types/src/components/TextEditor/extensions/index.d.ts +5 -2
  17. package/dist/types/src/components/TextEditor/extensions/index.d.ts.map +1 -1
  18. package/dist/types/src/components/TextEditor/extensions/link.d.ts +11 -0
  19. package/dist/types/src/components/TextEditor/extensions/link.d.ts.map +1 -0
  20. package/dist/types/src/components/TextEditor/extensions/listener.d.ts +7 -0
  21. package/dist/types/src/components/TextEditor/extensions/listener.d.ts.map +1 -0
  22. package/dist/types/src/components/TextEditor/extensions/markdown/bundle.d.ts.map +1 -1
  23. package/dist/types/src/components/TextEditor/extensions/markdown/highlight.d.ts +31 -0
  24. package/dist/types/src/components/TextEditor/extensions/markdown/highlight.d.ts.map +1 -0
  25. package/dist/types/src/components/TextEditor/extensions/markdown/index.d.ts +1 -1
  26. package/dist/types/src/components/TextEditor/extensions/markdown/index.d.ts.map +1 -1
  27. package/dist/types/src/components/TextEditor/extensions/tasklist.d.ts +5 -0
  28. package/dist/types/src/components/TextEditor/extensions/tasklist.d.ts.map +1 -0
  29. package/dist/types/src/components/TextEditor/extensions/tooltip.d.ts +12 -0
  30. package/dist/types/src/components/TextEditor/extensions/tooltip.d.ts.map +1 -0
  31. package/dist/types/src/components/TextEditor/index.d.ts +2 -0
  32. package/dist/types/src/components/TextEditor/index.d.ts.map +1 -1
  33. package/dist/types/src/components/TextEditor/themes/default.d.ts +17 -0
  34. package/dist/types/src/components/TextEditor/themes/default.d.ts.map +1 -0
  35. package/dist/types/src/components/TextEditor/themes/index.d.ts +2 -0
  36. package/dist/types/src/components/TextEditor/themes/index.d.ts.map +1 -0
  37. package/dist/types/src/components/TextEditor/yjs.stories.d.ts +1 -0
  38. package/dist/types/src/components/TextEditor/yjs.stories.d.ts.map +1 -1
  39. package/dist/types/src/hooks/useTextModel.d.ts +1 -0
  40. package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
  41. package/dist/types/src/styles/markdown.d.ts +12 -10
  42. package/dist/types/src/styles/markdown.d.ts.map +1 -1
  43. package/dist/types/src/testing/replicator.d.ts.map +1 -1
  44. package/package.json +21 -32
  45. package/src/components/TextEditor/MarkdownEditor.stories.tsx +242 -0
  46. package/src/components/TextEditor/TextEditor.stories.tsx +46 -43
  47. package/src/components/TextEditor/TextEditor.tsx +62 -45
  48. package/src/components/TextEditor/extensions/autocomplete.ts +72 -0
  49. package/src/components/TextEditor/extensions/{basic/bundle.ts → basic.ts} +4 -2
  50. package/src/components/TextEditor/extensions/{hyperlink/hyperlinkWidget.tsx → experimental.tsx} +17 -2
  51. package/src/components/TextEditor/extensions/index.ts +5 -2
  52. package/src/components/TextEditor/extensions/{hyperlink/hyperlinkDecoration.ts → link.ts} +68 -31
  53. package/src/components/TextEditor/extensions/{change.ts → listener.ts} +3 -3
  54. package/src/components/TextEditor/extensions/markdown/bundle.ts +15 -11
  55. package/src/components/TextEditor/extensions/markdown/highlight.ts +166 -0
  56. package/src/components/TextEditor/extensions/markdown/index.ts +1 -1
  57. package/src/components/TextEditor/extensions/tasklist.ts +113 -0
  58. package/src/components/TextEditor/extensions/{hyperlink/hyperlinkTooltip.tsx → tooltip.tsx} +9 -9
  59. package/src/components/TextEditor/index.ts +3 -0
  60. package/src/components/TextEditor/themes/default.ts +170 -0
  61. package/src/components/TextEditor/themes/index.ts +5 -0
  62. package/src/components/TextEditor/yjs.stories.tsx +1 -0
  63. package/src/hooks/useCollaboration.ts +4 -4
  64. package/src/hooks/useTextModel.ts +10 -5
  65. package/src/styles/markdown.ts +26 -24
  66. package/src/testing/replicator.ts +1 -0
  67. package/dist/types/src/components/TextEditor/extensions/basic/bundle.d.ts.map +0 -1
  68. package/dist/types/src/components/TextEditor/extensions/basic/index.d.ts +0 -3
  69. package/dist/types/src/components/TextEditor/extensions/basic/index.d.ts.map +0 -1
  70. package/dist/types/src/components/TextEditor/extensions/basic/theme.d.ts +0 -10
  71. package/dist/types/src/components/TextEditor/extensions/basic/theme.d.ts.map +0 -1
  72. package/dist/types/src/components/TextEditor/extensions/change.d.ts +0 -7
  73. package/dist/types/src/components/TextEditor/extensions/change.d.ts.map +0 -1
  74. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkDecoration.d.ts +0 -11
  75. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkDecoration.d.ts.map +0 -1
  76. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkTooltip.d.ts +0 -8
  77. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkTooltip.d.ts.map +0 -1
  78. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkWidget.d.ts.map +0 -1
  79. package/dist/types/src/components/TextEditor/extensions/hyperlink/index.d.ts +0 -4
  80. package/dist/types/src/components/TextEditor/extensions/hyperlink/index.d.ts.map +0 -1
  81. package/dist/types/src/components/TextEditor/extensions/markdown/tags.d.ts +0 -17
  82. package/dist/types/src/components/TextEditor/extensions/markdown/tags.d.ts.map +0 -1
  83. package/dist/types/src/components/TextEditor/extensions/markdown/theme.d.ts +0 -21
  84. package/dist/types/src/components/TextEditor/extensions/markdown/theme.d.ts.map +0 -1
  85. package/src/components/TextEditor/extensions/basic/index.ts +0 -6
  86. package/src/components/TextEditor/extensions/basic/theme.ts +0 -49
  87. package/src/components/TextEditor/extensions/hyperlink/index.ts +0 -7
  88. package/src/components/TextEditor/extensions/markdown/tags.ts +0 -38
  89. package/src/components/TextEditor/extensions/markdown/theme.ts +0 -265
@@ -5,145 +5,124 @@ import { Doc, YText as YText2, YXmlFragment } from "@dxos/text-model";
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 = ({ getOptions }) => {
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: getOptions(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"
77
- }
78
- };
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;
89
- }
90
- });
91
-
92
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/hyperlink/hyperlinkDecoration.ts
57
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/link.ts
93
58
  import { syntaxTree } from "@codemirror/language";
94
- import { RangeSetBuilder, StateField as StateField2 } from "@codemirror/state";
59
+ import { RangeSetBuilder, StateField } from "@codemirror/state";
95
60
  import { Decoration, EditorView as EditorView2, WidgetType } from "@codemirror/view";
96
- var Link = class extends WidgetType {
97
- constructor(_anchor, _text, _url) {
61
+ var LinkButton = class extends WidgetType {
62
+ constructor(_pos, _url, _onAttach) {
63
+ super();
64
+ this._pos = _pos;
65
+ this._url = _url;
66
+ this._onAttach = _onAttach;
67
+ }
68
+ eq(other) {
69
+ return this._pos === other._pos && this._url === other._url;
70
+ }
71
+ toDOM(view) {
72
+ const el = document.createElement("span");
73
+ this._onAttach(el, this._url);
74
+ return el;
75
+ }
76
+ };
77
+ var LinkText = class extends WidgetType {
78
+ constructor(_pos, _text, _url) {
98
79
  super();
99
- this._anchor = _anchor;
80
+ this._pos = _pos;
100
81
  this._text = _text;
101
82
  this._url = _url;
102
83
  }
103
84
  eq(other) {
104
- return this._text === other.text && this._url === other.url;
85
+ return this._pos === other._pos && this._url === other._url;
105
86
  }
106
87
  toDOM(view) {
107
- const link = document.createElement("a");
108
- link.setAttribute("class", "cm-link");
109
- link.textContent = this._text;
88
+ const link2 = document.createElement("a");
89
+ link2.setAttribute("class", "cm-link");
90
+ link2.textContent = this._text;
110
91
  if (this._url) {
111
- link.setAttribute("target", "_blank");
112
- link.href = this._url;
92
+ link2.setAttribute("rel", "noreferrer");
93
+ link2.setAttribute("target", "_blank");
94
+ link2.href = this._url;
113
95
  } else {
114
- link.onclick = () => {
96
+ link2.onclick = () => {
115
97
  view.dispatch({
116
98
  selection: {
117
- anchor: this._anchor
99
+ anchor: this._pos
118
100
  }
119
101
  });
120
102
  };
121
103
  }
122
- return link;
104
+ return link2;
123
105
  }
124
106
  };
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) => {
107
+ var update = (state, options) => {
133
108
  const builder = new RangeSetBuilder();
134
109
  const cursor = state.selection.main.head;
135
110
  syntaxTree(state).iterate({
136
111
  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) : "";
112
+ if (node.name === "Link") {
113
+ const marks = node.node.getChildren("LinkMark");
114
+ const text = marks.length >= 2 ? state.sliceDoc(marks[0].to, marks[1].from) : "";
115
+ const urlNode = node.node.getChild("URL");
116
+ const url = urlNode ? state.sliceDoc(urlNode.from, urlNode.to) : "";
117
+ if (cursor < node.from || cursor > node.to) {
143
118
  builder.add(node.from, node.to, Decoration.replace({
144
- widget: new Link(node.from, text, link ? url : void 0)
119
+ widget: new LinkText(node.from, text, options.link ? url : void 0)
120
+ }));
121
+ }
122
+ if (options.onRender) {
123
+ builder.add(node.to, node.to, Decoration.replace({
124
+ widget: new LinkButton(node.from, url, options.onRender)
145
125
  }));
146
- return false;
147
126
  }
148
127
  }
149
128
  return true;
@@ -151,142 +130,25 @@ var update = (state, link) => {
151
130
  });
152
131
  return builder.finish();
153
132
  };
133
+ var link = (options = {}) => {
134
+ return StateField.define({
135
+ create: (state) => update(state, options),
136
+ update: (_, tr) => update(tr.state, options),
137
+ provide: (field) => EditorView2.decorations.from(field)
138
+ });
139
+ };
154
140
 
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;
141
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/listener.ts
142
+ import { StateField as StateField2 } from "@codemirror/state";
143
+ var listener = (onChange) => StateField2.define({
144
+ create: () => null,
145
+ update: (_value, transaction) => {
146
+ if (transaction.docChanged && onChange) {
147
+ onChange(transaction.newDoc.toString());
173
148
  }
174
- idx += match[0].length;
175
- } while (true);
176
- if (!match) {
177
149
  return null;
178
150
  }
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
- });
197
-
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
151
  });
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
152
 
291
153
  // packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/bundle.ts
292
154
  import { closeBrackets as closeBrackets2, closeBracketsKeymap } from "@codemirror/autocomplete";
@@ -298,222 +160,59 @@ import { lintKeymap } from "@codemirror/lint";
298
160
  import { highlightSelectionMatches, searchKeymap } from "@codemirror/search";
299
161
  import { EditorState } from "@codemirror/state";
300
162
  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";
163
+ import { crosshairCursor, drawSelection, dropCursor, EditorView as EditorView3, highlightActiveLine, highlightActiveLineGutter, highlightSpecialChars, keymap as keymap2, placeholder as placeholder2, rectangularSelection } from "@codemirror/view";
302
164
 
303
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/tags.ts
304
- import { styleTags, Tag } from "@lezer/highlight";
165
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/highlight.ts
166
+ import { markdownLanguage } from "@codemirror/lang-markdown";
167
+ import { HighlightStyle } from "@codemirror/language";
168
+ import { tags, styleTags, Tag } from "@lezer/highlight";
169
+ import get from "lodash.get";
170
+
171
+ // packages/ui/react-ui-editor/src/styles/markdown.ts
172
+ import { mx } from "@dxos/react-ui-theme";
173
+ var heading = {
174
+ 1: "mbs-4 mbe-2 font-medium text-inherit no-underline text-4xl",
175
+ 2: "mbs-4 mbe-2 font-medium text-inherit no-underline text-3xl",
176
+ 3: "mbs-4 mbe-2 font-medium text-inherit no-underline text-2xl",
177
+ 4: "mbs-4 mbe-2 font-medium text-inherit no-underline text-xl",
178
+ 5: "mbs-4 mbe-2 font-medium text-inherit no-underline text-lg",
179
+ 6: "mbs-4 mbe-2 font-medium text-inherit no-underline"
180
+ };
181
+ var light = "text-neutral-200 dark:text-neutral-800";
182
+ var mark = mx("!font-normal !no-underline !text-inherit opacity-40", light);
183
+ var bold = "font-bold";
184
+ var italic = "italic";
185
+ var strikethrough = "line-through";
186
+ var code = "font-mono !no-underline text-neutral-700 dark:text-neutral-300";
187
+ var codeMark = "font-mono text-primary-500";
188
+ var inlineUrl = mx(code, "px-1");
189
+ var blockquote = mx("pl-1 mr-1 border-is-4 border-primary-500/70 dark:border-primary-500/30", light);
190
+ var horizontalRule = "flex mlb-4 border-b text-neutral-100 dark:text-neutral-900 border-neutral-500/50";
191
+
192
+ // packages/ui/react-ui-editor/src/styles/tokens.ts
193
+ import { tailwindConfig } from "@dxos/react-ui-theme";
194
+ var tokens = tailwindConfig({}).theme;
195
+
196
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/highlight.ts
305
197
  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()
198
+ Blockquote: Tag.define(),
199
+ CodeMark: Tag.define(),
200
+ CodeText: Tag.define(),
201
+ EmphasisMark: Tag.define(),
202
+ HeaderMark: Tag.define(),
203
+ HorizontalRule: Tag.define(),
204
+ InlineCode: Tag.define(),
205
+ LinkLabel: Tag.define(),
206
+ LinkReference: Tag.define(),
207
+ ListMark: Tag.define(),
208
+ QuoteMark: Tag.define(),
209
+ URL: Tag.define()
317
210
  };
318
211
  var markdownTagsExtension = {
319
212
  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
- })
213
+ styleTags(markdownTags)
333
214
  ]
334
215
  };
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
216
  var markdownHighlightStyle = HighlightStyle.define([
518
217
  {
519
218
  tag: [
@@ -553,48 +252,22 @@ var markdownHighlightStyle = HighlightStyle.define([
553
252
  ],
554
253
  color: "inherit !important"
555
254
  },
255
+ // Markdown marks.
556
256
  {
557
257
  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
258
  tags.meta,
583
- tags.processingInstruction
259
+ tags.processingInstruction,
260
+ markdownTags.CodeMark,
261
+ markdownTags.EmphasisMark,
262
+ markdownTags.HeaderMark,
263
+ markdownTags.LinkLabel,
264
+ markdownTags.LinkReference,
265
+ markdownTags.ListMark,
266
+ markdownTags.QuoteMark
584
267
  ],
585
268
  class: mark
586
269
  },
587
- {
588
- tag: [
589
- markdownTags.codeText,
590
- markdownTags.inlineCode
591
- ],
592
- class: "font-mono"
593
- },
594
- {
595
- tag: tags.emphasis,
596
- class: italic
597
- },
270
+ // Headings.
598
271
  {
599
272
  tag: tags.heading1,
600
273
  class: heading[1]
@@ -619,69 +292,397 @@ var markdownHighlightStyle = HighlightStyle.define([
619
292
  tag: tags.heading6,
620
293
  class: heading[6]
621
294
  },
295
+ // Emphasis.
622
296
  {
623
- tag: tags.strikethrough,
624
- class: strikethrough
297
+ tag: tags.emphasis,
298
+ class: italic
625
299
  },
626
300
  {
627
301
  tag: tags.strong,
628
302
  class: bold
303
+ },
304
+ {
305
+ tag: tags.strikethrough,
306
+ class: strikethrough
307
+ },
308
+ // Naked URLs.
309
+ {
310
+ tag: [
311
+ markdownTags.URL
312
+ ],
313
+ class: inlineUrl
314
+ },
315
+ // E.g., code block language (after ```).
316
+ {
317
+ tag: [
318
+ tags.function(tags.variableName),
319
+ tags.labelName
320
+ ],
321
+ class: codeMark
322
+ },
323
+ // The `markdown` extension configures extensions for `lezer` to parse markdown tokens (incl. below).
324
+ // However, since `codeLanguages` is also defined, the `lezer` will not parse fenced code blocks,
325
+ // when a language is specified. In this case, the syntax highlighting extensions will colorize
326
+ // the code, but all other CSS properties will be inherited.
327
+ {
328
+ tag: [
329
+ markdownTags.CodeText,
330
+ markdownTags.InlineCode
331
+ ],
332
+ class: code
333
+ },
334
+ {
335
+ tag: [
336
+ markdownTags.QuoteMark
337
+ ],
338
+ class: blockquote
339
+ },
340
+ {
341
+ tag: [
342
+ markdownTags.HorizontalRule
343
+ ],
344
+ class: horizontalRule
629
345
  }
630
346
  ], {
631
347
  scope: markdownLanguage,
632
348
  all: {
349
+ fontFamily: get(tokens, "fontFamily.body", []).join(",")
350
+ }
351
+ });
352
+
353
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/bundle.ts
354
+ var markdownBundle = ({ themeMode, placeholder: _placeholder }) => {
355
+ return [
356
+ bracketMatching2(),
357
+ closeBrackets2(),
358
+ _placeholder && placeholder2(_placeholder),
359
+ EditorState.allowMultipleSelections.of(true),
360
+ EditorView3.lineWrapping,
361
+ // autocompletion(),
362
+ crosshairCursor(),
363
+ dropCursor(),
364
+ drawSelection(),
365
+ highlightActiveLine(),
366
+ highlightActiveLineGutter(),
367
+ highlightSelectionMatches(),
368
+ highlightSpecialChars(),
369
+ history(),
370
+ indentOnInput(),
371
+ rectangularSelection(),
372
+ keymap2.of([
373
+ ...closeBracketsKeymap,
374
+ // ...completionKeymap,
375
+ ...defaultKeymap,
376
+ ...foldKeymap,
377
+ ...historyKeymap,
378
+ ...lintKeymap,
379
+ ...searchKeymap,
380
+ indentWithTab
381
+ ]),
382
+ // Main extension.
383
+ // https://github.com/codemirror/lang-markdown
384
+ // https://codemirror.net/5/mode/markdown/index.html (demo).
385
+ markdown({
386
+ // GRM by default (vs strict CommonMark):
387
+ // Table, TaskList, Strikethrough, and Autolink.
388
+ // NOTE: This extends the parser; it doesn't affect rendering.
389
+ // https://github.github.com/gfm
390
+ // https://github.com/lezer-parser/markdown?tab=readme-ov-file#github-flavored-markdown
391
+ base: markdownLanguage2,
392
+ // Languages for syntax highlighting fenced code blocks.
393
+ codeLanguages: languages,
394
+ // Parser extensions.
395
+ extensions: [
396
+ // GFM, // TODO(burdon): Provided by default?
397
+ markdownTagsExtension
398
+ ]
399
+ }),
400
+ // Custom styles.
401
+ syntaxHighlighting2(markdownHighlightStyle),
402
+ // TODO(thure): All but one rule here apply to both themes; rename or refactor.
403
+ themeMode === "dark" ? syntaxHighlighting2(oneDarkHighlightStyle2) : syntaxHighlighting2(defaultHighlightStyle2)
404
+ ].filter(Boolean);
405
+ };
406
+
407
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/tasklist.ts
408
+ import { syntaxTree as syntaxTree2 } from "@codemirror/language";
409
+ import { RangeSetBuilder as RangeSetBuilder2, StateField as StateField3 } from "@codemirror/state";
410
+ import { EditorView as EditorView4, Decoration as Decoration2, WidgetType as WidgetType2 } from "@codemirror/view";
411
+ var CheckboxWidget = class extends WidgetType2 {
412
+ constructor(_pos, _getCursor, _checked) {
413
+ super();
414
+ this._pos = _pos;
415
+ this._getCursor = _getCursor;
416
+ this._checked = _checked;
417
+ }
418
+ eq(other) {
419
+ return this._pos === other._pos && this._checked === other._checked;
420
+ }
421
+ toDOM(view) {
422
+ const wrap = document.createElement("span");
423
+ wrap.setAttribute("aria-hidden", "true");
424
+ wrap.className = "cm-task-item";
425
+ const box = wrap.appendChild(document.createElement("input"));
426
+ box.type = "checkbox";
427
+ box.checked = this._checked;
428
+ box.onclick = (event) => {
429
+ this._checked = !isChecked(view.state, this._pos);
430
+ view.dispatch({
431
+ changes: {
432
+ from: this._pos + 1,
433
+ to: this._pos + 2,
434
+ insert: this._checked ? "x" : " "
435
+ },
436
+ // TODO(burdon): Restore cursor position? More useful to move to end of line (can indent).
437
+ selection: {
438
+ anchor: this._pos + 4
439
+ }
440
+ });
441
+ return true;
442
+ };
443
+ return wrap;
444
+ }
445
+ // TODO(burdon): Remove listener?
446
+ // override destroy() {
447
+ // console.log('destroy', this._pos);
448
+ // }
449
+ ignoreEvent() {
450
+ return false;
451
+ }
452
+ };
453
+ var isChecked = (state, pos) => {
454
+ return state.sliceDoc(pos, pos + 3).toLowerCase() === "[x]";
455
+ };
456
+ var statefield = () => {
457
+ let lastPosition = 0;
458
+ const listener2 = EditorView4.updateListener.of((update2) => {
459
+ lastPosition = update2.startState.selection.main.head;
460
+ });
461
+ const checkbox = (state) => {
462
+ const builder = new RangeSetBuilder2();
463
+ syntaxTree2(state).iterate({
464
+ enter: (node) => {
465
+ if (node.name === "TaskMarker") {
466
+ builder.add(node.from, node.to, Decoration2.replace({
467
+ widget: new CheckboxWidget(node.from, () => lastPosition, isChecked(state, node.from))
468
+ }));
469
+ }
470
+ }
471
+ });
472
+ return builder.finish();
473
+ };
474
+ return [
475
+ listener2,
476
+ StateField3.define({
477
+ create: (state) => checkbox(state),
478
+ update: (_, tr) => checkbox(tr.state),
479
+ provide: (field) => EditorView4.decorations.from(field)
480
+ })
481
+ ];
482
+ };
483
+ var styles = EditorView4.baseTheme({
484
+ "& .cm-task-item": {
485
+ padding: "0 4px"
486
+ }
487
+ });
488
+ var tasklist = () => [
489
+ statefield(),
490
+ styles
491
+ ];
492
+
493
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/tooltip.tsx
494
+ import { hoverTooltip } from "@codemirror/view";
495
+ import { tooltipContent } from "@dxos/react-ui-theme";
496
+ var markdownLinkRegexp = /\[([^\]]+)]\(([^)]+)\)/;
497
+ var tooltip = ({ onHover, regexp = markdownLinkRegexp }) => hoverTooltip((view, pos) => {
498
+ const { from, text } = view.state.doc.lineAt(pos);
499
+ const p = pos - from;
500
+ let idx = 0;
501
+ let match;
502
+ do {
503
+ match = text.substring(idx).match(regexp);
504
+ if (!match) {
505
+ match = void 0;
506
+ break;
507
+ }
508
+ idx = text.indexOf(match[0]);
509
+ if (p >= idx && p < idx + match[0].length) {
510
+ break;
511
+ }
512
+ idx += match[0].length;
513
+ } while (true);
514
+ if (!match) {
515
+ return null;
516
+ }
517
+ const [, , url] = match ?? [];
518
+ return {
519
+ pos: idx + from,
520
+ end: idx + from + match[0].length,
521
+ above: true,
522
+ create: () => {
523
+ const el = document.createElement("div");
524
+ el.className = tooltipContent({}, "pli-2 plb-1");
525
+ onHover(el, url);
526
+ return {
527
+ dom: el,
528
+ offset: {
529
+ x: 0,
530
+ y: 12
531
+ }
532
+ };
533
+ }
534
+ };
535
+ });
536
+
537
+ // packages/ui/react-ui-editor/src/components/TextEditor/themes/default.ts
538
+ import get2 from "lodash.get";
539
+ var defaultTheme = {
540
+ //
541
+ // Main layout:
542
+ // <div class=".cm-editor .cm-focused">
543
+ // <div class=".cm-scroller">
544
+ // <div class=".cm-content" role="textbox" contenteditable="true">
545
+ // <div class=".cm-line" />
546
+ // </div>
547
+ // </div>
548
+ // </div>
549
+ //
550
+ "&": {},
551
+ "&.cm-focused": {
552
+ outline: "none"
553
+ },
554
+ "& .cm-scroller": {
555
+ overflow: "visible"
556
+ },
557
+ "& .cm-content": {
558
+ padding: 0,
559
+ // Base font size (otherwise defined by HTML tag, which might be different for storybook).
560
+ fontSize: "16px"
561
+ },
562
+ "&light .cm-content": {
563
+ color: get2(tokens, "extend.colors.neutral.900", "black"),
564
+ caretColor: "black"
565
+ },
566
+ "&dark .cm-content": {
567
+ color: get2(tokens, "extend.colors.neutral.100", "white"),
568
+ caretColor: "white"
569
+ },
570
+ //
571
+ // Cursor
572
+ //
573
+ "&light .cm-cursor": {
574
+ borderLeft: "2px solid black"
575
+ },
576
+ "&dark .cm-cursor": {
577
+ borderLeft: "2px solid white"
578
+ },
579
+ "& .cm-placeholder": {
580
+ fontWeight: 100
581
+ },
582
+ //
583
+ // line
584
+ //
585
+ "& .cm-line": {
586
+ paddingInline: 0
587
+ },
588
+ "& .cm-line *": {},
589
+ "& .cm-activeLine": {
590
+ backgroundColor: "transparent"
591
+ },
592
+ //
593
+ // selection
594
+ //
595
+ "&light .cm-selectionBackground, &light.cm-focused .cm-selectionBackground": {
596
+ background: get2(tokens, "extend.colors.primary.150", "#00ffff")
597
+ },
598
+ "&dark .cm-selectionBackground, &dark.cm-focused .cm-selectionBackground": {
599
+ background: get2(tokens, "extend.colors.primary.850", "#00ffff")
600
+ },
601
+ "&light .cm-selectionMatch": {
602
+ background: get2(tokens, "extend.colors.primary.250", "#00ffff") + "44"
603
+ },
604
+ "&dark .cm-selectionMatch": {
605
+ background: get2(tokens, "extend.colors.primary.600", "#00ffff") + "44"
606
+ },
607
+ //
608
+ // collaboration
609
+ // TODO(burdon): Review classnames (YJS dependent?)
610
+ //
611
+ "&light .cm-ySelection, &light .cm-yLineSelection": {
612
+ mixBlendMode: "multiply"
613
+ },
614
+ "&dark .cm-ySelection, &dark .cm-yLineSelection": {
615
+ mixBlendMode: "screen"
616
+ },
617
+ "& .cm-ySelectionInfo": {
618
+ padding: "2px 4px",
619
+ marginBlockStart: "-4px"
620
+ },
621
+ "& .cm-ySelection, & .cm-selectionMatch": {
622
+ paddingBlockStart: ".15em",
623
+ paddingBlockEnd: ".15em"
624
+ },
625
+ "& .cm-ySelectionCaret": {
626
+ display: "inline-block",
627
+ insetBlockStart: ".1em",
628
+ blockSize: "1.4em",
629
+ verticalAlign: "top"
630
+ },
631
+ "& .cm-yLineSelection": {
632
+ margin: 0
633
+ },
634
+ //
635
+ // link
636
+ //
637
+ "& .cm-link": {
638
+ color: get2(tokens, "extend.colors.primary.500"),
639
+ textDecorationLine: "underline",
640
+ textDecorationThickness: "1px",
641
+ textUnderlineOffset: "2px",
642
+ borderRadius: ".125rem",
643
+ fontFamily: get2(tokens, "fontFamily.body", []).join(",")
644
+ },
645
+ //
646
+ // tooltip
647
+ //
648
+ "& .cm-tooltip": {
649
+ border: "none"
650
+ },
651
+ // '& .cm-tooltip-below': {},
652
+ // '& .cm-tooltip-autocomplete': {},
653
+ //
654
+ // font size
655
+ // 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.
656
+ //
657
+ ...Object.keys(get2(tokens, "extend.fontSize", {})).reduce((acc, fontSize) => {
658
+ const height = get2(tokens, [
659
+ "extend",
660
+ "fontSize",
661
+ fontSize,
662
+ 1,
663
+ "lineHeight"
664
+ ]);
665
+ acc[`& .text-${fontSize} + .cm-ySelectionCaret`] = {
666
+ height
667
+ };
668
+ acc[`& .text-${fontSize} + .cm-ySelection + .cm-ySelectionCaret`] = {
669
+ height
670
+ };
671
+ acc[`& .text-${fontSize} + .cm-widgetBuffer + .cm-ySelectionCaret`] = {
672
+ height
673
+ };
674
+ return acc;
675
+ }, {})
676
+ };
677
+ var textTheme = {
678
+ "& .cm-scroller": {
633
679
  fontFamily: get2(tokens, "fontFamily.body", []).join(",")
634
680
  }
635
- });
636
-
637
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/bundle.ts
638
- var markdownBundle = ({ themeMode, placeholder: _placeholder }) => {
639
- return [
640
- bracketMatching2(),
641
- closeBrackets2(),
642
- _placeholder && placeholder2(_placeholder),
643
- EditorState.allowMultipleSelections.of(true),
644
- EditorView4.lineWrapping,
645
- // autocompletion(),
646
- crosshairCursor(),
647
- dropCursor(),
648
- drawSelection(),
649
- highlightActiveLine(),
650
- highlightActiveLineGutter(),
651
- highlightSelectionMatches(),
652
- highlightSpecialChars(),
653
- history(),
654
- indentOnInput(),
655
- rectangularSelection(),
656
- keymap.of([
657
- ...closeBracketsKeymap,
658
- // ...completionKeymap,
659
- ...defaultKeymap,
660
- ...foldKeymap,
661
- ...historyKeymap,
662
- ...lintKeymap,
663
- ...searchKeymap,
664
- indentWithTab
665
- ]),
666
- // Main extension.
667
- // https://github.com/codemirror/lang-markdown
668
- markdown({
669
- base: markdownLanguage2,
670
- codeLanguages: languages,
671
- 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.
678
- markdownTagsExtension
679
- ]
680
- }),
681
- // TODO(thure): All but one rule here apply to both themes; rename or refactor.
682
- syntaxHighlighting2(markdownHighlightStyle),
683
- themeMode === "dark" ? syntaxHighlighting2(oneDarkHighlightStyle2) : syntaxHighlighting2(defaultHighlightStyle2)
684
- ].filter(Boolean);
681
+ };
682
+ var codeTheme = {
683
+ "& .cm-scroller": {
684
+ fontFamily: get2(tokens, "fontFamily.mono", []).join(",")
685
+ }
685
686
  };
686
687
 
687
688
  // packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx
@@ -690,22 +691,170 @@ import { EditorView as EditorView5 } from "@codemirror/view";
690
691
  import { useFocusableGroup } from "@fluentui/react-tabster";
691
692
  import { vim } from "@replit/codemirror-vim";
692
693
  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";
694
+ import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useState } from "react";
695
+ import { yCollab } from "y-codemirror.next";
696
+ import { generateName } from "@dxos/display-name";
696
697
  import { useThemeContext } from "@dxos/react-ui";
698
+ import { getColorForValue, inputSurface, mx as mx2 } from "@dxos/react-ui-theme";
699
+ var EditorModes = [
700
+ "default",
701
+ "vim"
702
+ ];
703
+ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, extensions = [], slots = defaultSlots, editorMode }, forwardedRef) => {
704
+ const { themeMode } = useThemeContext();
705
+ const tabsterDOMAttribute = useFocusableGroup({
706
+ tabBehavior: "limited"
707
+ });
708
+ const { provider, peer } = model;
709
+ useEffect(() => {
710
+ if (provider && peer) {
711
+ provider.awareness.setLocalStateField("user", {
712
+ name: peer.name ?? generateName(peer.id),
713
+ color: getColorForValue({
714
+ value: peer.id,
715
+ type: "color"
716
+ }),
717
+ colorLight: getColorForValue({
718
+ value: peer.id,
719
+ themeMode,
720
+ type: "highlight"
721
+ })
722
+ });
723
+ }
724
+ }, [
725
+ provider,
726
+ peer,
727
+ themeMode
728
+ ]);
729
+ const [root, setRoot] = useState(null);
730
+ const [state, setState] = useState();
731
+ const [view, setView] = useState();
732
+ useImperativeHandle(forwardedRef, () => ({
733
+ root,
734
+ state,
735
+ view
736
+ }), [
737
+ view,
738
+ state,
739
+ root
740
+ ]);
741
+ useEffect(() => {
742
+ if (!root) {
743
+ return;
744
+ }
745
+ const state2 = EditorState2.create({
746
+ doc: model.text(),
747
+ extensions: [
748
+ // TODO(burdon): Factor out VIM mode?
749
+ editorMode === "vim" && vim(),
750
+ // Theme.
751
+ EditorView5.baseTheme(defaultTheme),
752
+ EditorView5.theme(slots?.editor?.theme ?? {}),
753
+ // TODO(burdon): themeMode doesn't change in storybooks.
754
+ EditorView5.darkTheme.of(themeMode === "dark"),
755
+ // Storage and replication.
756
+ yCollab(model.content, model.provider?.awareness),
757
+ // Custom.
758
+ ...extensions
759
+ ].filter(Boolean)
760
+ });
761
+ view?.destroy();
762
+ setView(new EditorView5({
763
+ state: state2,
764
+ parent: root
765
+ }));
766
+ setState(state2);
767
+ return () => {
768
+ view?.destroy();
769
+ setView(void 0);
770
+ setState(void 0);
771
+ };
772
+ }, [
773
+ root,
774
+ model.content,
775
+ themeMode,
776
+ editorMode
777
+ ]);
778
+ const handleKeyUp = useCallback((event) => {
779
+ const { key, altKey, shiftKey, metaKey, ctrlKey } = event;
780
+ switch (key) {
781
+ case "Enter": {
782
+ view?.contentDOM.focus();
783
+ break;
784
+ }
785
+ case "Escape": {
786
+ editorMode === "vim" && (altKey || shiftKey || metaKey || ctrlKey) && root?.focus();
787
+ break;
788
+ }
789
+ }
790
+ }, [
791
+ view,
792
+ editorMode
793
+ ]);
794
+ return /* @__PURE__ */ React.createElement("div", {
795
+ key: model.id,
796
+ ref: setRoot,
797
+ tabIndex: 0,
798
+ ...slots?.root,
799
+ ...editorMode !== "vim" && tabsterDOMAttribute,
800
+ onKeyUp: handleKeyUp
801
+ });
802
+ });
803
+ var TextEditor = /* @__PURE__ */ forwardRef(({ extensions = [], slots: _slots, ...props }, forwardedRef) => {
804
+ const { themeMode } = useThemeContext();
805
+ const slots = defaultsDeep({}, _slots, defaultTextSlots);
806
+ return /* @__PURE__ */ React.createElement(BaseTextEditor, {
807
+ ref: forwardedRef,
808
+ extensions: [
809
+ basicBundle({
810
+ themeMode,
811
+ placeholder: slots?.editor?.placeholder
812
+ }),
813
+ ...extensions
814
+ ],
815
+ slots,
816
+ ...props
817
+ });
818
+ });
819
+ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ extensions = [], slots: _slots, ...props }, forwardedRef) => {
820
+ const { themeMode } = useThemeContext();
821
+ const slots = defaultsDeep({}, _slots, defaultSlots);
822
+ return /* @__PURE__ */ React.createElement(BaseTextEditor, {
823
+ ref: forwardedRef,
824
+ extensions: [
825
+ markdownBundle({
826
+ themeMode,
827
+ placeholder: slots?.editor?.placeholder
828
+ }),
829
+ ...extensions
830
+ ],
831
+ slots,
832
+ ...props
833
+ });
834
+ });
835
+ var defaultSlots = {
836
+ root: {
837
+ className: mx2("p-2", inputSurface)
838
+ }
839
+ };
840
+ var defaultTextSlots = {
841
+ ...defaultSlots,
842
+ editor: {
843
+ theme: textTheme
844
+ }
845
+ };
697
846
 
698
847
  // packages/ui/react-ui-editor/src/hooks/useCollaboration.ts
699
- import { useEffect, useMemo } from "react";
700
- import { yCollab } from "y-codemirror.next";
701
- import { generateName } from "@dxos/display-name";
848
+ import { useEffect as useEffect2, useState as useState2 } from "react";
849
+ import { yCollab as yCollab2 } from "y-codemirror.next";
850
+ import { generateName as generateName2 } from "@dxos/display-name";
702
851
  import { isDocAccessor } from "@dxos/echo-schema";
703
- import { getColorForValue } from "@dxos/react-ui-theme";
852
+ import { getColorForValue as getColorForValue2 } from "@dxos/react-ui-theme";
704
853
  import { YText } from "@dxos/text-model";
705
854
 
706
855
  // packages/ui/react-ui-editor/src/automerge/automerge-plugin/plugin.ts
707
- import { Annotation, Facet, StateEffect, StateField as StateField3 } from "@codemirror/state";
708
- import { ViewPlugin as ViewPlugin2 } from "@codemirror/view";
856
+ import { Annotation, Facet, StateEffect, StateField as StateField4 } from "@codemirror/state";
857
+ import { ViewPlugin } from "@codemirror/view";
709
858
  import * as automerge2 from "@dxos/automerge/automerge";
710
859
 
711
860
  // packages/ui/react-ui-editor/src/automerge/automerge-plugin/PatchSemaphore.ts
@@ -893,7 +1042,7 @@ var semaphoreFacet = Facet.define({
893
1042
  combine: (values) => values.at(-1)
894
1043
  });
895
1044
  var automergePlugin = (handle, path) => {
896
- const stateField = StateField3.define({
1045
+ const stateField = StateField4.define({
897
1046
  create: () => ({
898
1047
  lastHeads: automerge2.getHeads(handle.docSync()),
899
1048
  unreconciledTransactions: [],
@@ -923,7 +1072,7 @@ var automergePlugin = (handle, path) => {
923
1072
  }
924
1073
  });
925
1074
  const semaphore = new PatchSemaphore(stateField);
926
- const viewPlugin = ViewPlugin2.fromClass(class AutomergeCodemirrorViewPlugin {
1075
+ const viewPlugin = ViewPlugin.fromClass(class AutomergeCodemirrorViewPlugin {
927
1076
  constructor(view) {
928
1077
  this._handleChange = () => {
929
1078
  this._view.state.facet(semaphoreFacet).reconcile(handle, this._view);
@@ -955,25 +1104,25 @@ var isReconcileTx = (tr) => !!tr.annotation(reconcileAnnotationType);
955
1104
 
956
1105
  // packages/ui/react-ui-editor/src/hooks/useCollaboration.ts
957
1106
  var useCollaboration = (model, themeMode) => {
958
- const { provider, peer, content } = model;
959
- const extension = useMemo(() => {
1107
+ const { content, provider, peer } = model;
1108
+ const [extension] = useState2(() => {
960
1109
  if (content instanceof YText) {
961
- return yCollab(content, provider?.awareness);
1110
+ return yCollab2(content, provider?.awareness);
962
1111
  } else if (isDocAccessor(content)) {
963
1112
  return automergePlugin(content.handle, content.path);
964
1113
  } else {
965
1114
  return void 0;
966
1115
  }
967
- }, []);
968
- useEffect(() => {
1116
+ });
1117
+ useEffect2(() => {
969
1118
  if (provider && peer) {
970
1119
  provider.awareness.setLocalStateField("user", {
971
- name: peer.name ?? generateName(peer.id),
972
- color: getColorForValue({
1120
+ name: peer.name ?? generateName2(peer.id),
1121
+ color: getColorForValue2({
973
1122
  value: peer.id,
974
1123
  type: "color"
975
1124
  }),
976
- colorLight: getColorForValue({
1125
+ colorLight: getColorForValue2({
977
1126
  value: peer.id,
978
1127
  themeMode,
979
1128
  type: "highlight"
@@ -989,7 +1138,7 @@ var useCollaboration = (model, themeMode) => {
989
1138
  };
990
1139
 
991
1140
  // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
992
- import { useMemo as useMemo2 } from "react";
1141
+ import { useMemo } from "react";
993
1142
  import { getRawDoc, isActualAutomergeObject } from "@dxos/react-client/echo";
994
1143
 
995
1144
  // packages/ui/react-ui-editor/src/hooks/yjs/cursors.ts
@@ -1036,7 +1185,7 @@ import * as encoding from "lib0/encoding";
1036
1185
  import { Observable } from "lib0/observable";
1037
1186
  import * as awarenessProtocol from "y-protocols/awareness";
1038
1187
  import { log } from "@dxos/log";
1039
- var __dxlog_file = "/home/circleci/project/packages/ui/react-ui-editor/src/hooks/yjs/space-provider.ts";
1188
+ var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/yjs/space-provider.ts";
1040
1189
  var messageAwareness = 1;
1041
1190
  var messageQueryAwareness = 3;
1042
1191
  var SpaceAwarenessProvider = class extends Observable {
@@ -1132,7 +1281,7 @@ var SpaceAwarenessProvider = class extends Observable {
1132
1281
 
1133
1282
  // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
1134
1283
  var useTextModel = ({ identity, space, text }) => {
1135
- const provider = useMemo2(() => {
1284
+ const provider = useMemo(() => {
1136
1285
  if (isActualAutomergeObject(text)) {
1137
1286
  return void 0;
1138
1287
  }
@@ -1150,7 +1299,7 @@ var useTextModel = ({ identity, space, text }) => {
1150
1299
  text,
1151
1300
  text?.doc
1152
1301
  ]);
1153
- const content = useMemo2(() => {
1302
+ const content = useMemo(() => {
1154
1303
  if (isActualAutomergeObject(text)) {
1155
1304
  const obj = text;
1156
1305
  return getRawDoc(obj, [
@@ -1162,11 +1311,15 @@ var useTextModel = ({ identity, space, text }) => {
1162
1311
  }, [
1163
1312
  text
1164
1313
  ]);
1314
+ if (!text?.doc || !text?.content) {
1315
+ return void 0;
1316
+ }
1165
1317
  if (isActualAutomergeObject(text)) {
1166
1318
  const obj = text;
1167
1319
  return {
1168
1320
  id: obj.id,
1169
1321
  content,
1322
+ text: () => content.toString(),
1170
1323
  provider: void 0,
1171
1324
  peer: identity ? {
1172
1325
  id: identity.identityKey.toHex(),
@@ -1174,142 +1327,16 @@ var useTextModel = ({ identity, space, text }) => {
1174
1327
  } : void 0
1175
1328
  };
1176
1329
  }
1177
- if (!text?.doc || !text?.content) {
1178
- return void 0;
1179
- }
1180
1330
  return {
1181
1331
  id: text.doc.guid,
1182
1332
  content: text.content,
1183
- provider,
1333
+ text: () => text.content.toString(),
1184
1334
  peer: identity ? {
1185
1335
  id: identity.identityKey.toHex(),
1186
1336
  name: identity.profile?.displayName
1187
1337
  } : void 0
1188
1338
  };
1189
1339
  };
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
1264
- ]);
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
- });
1313
1340
  export {
1314
1341
  BaseTextEditor,
1315
1342
  Doc,
@@ -1320,32 +1347,26 @@ export {
1320
1347
  TextKind,
1321
1348
  YText2 as YText,
1322
1349
  YXmlFragment,
1350
+ autocomplete,
1323
1351
  basicBundle,
1324
- basicTheme,
1325
- chalky,
1326
- coral,
1327
- createHyperlinkTooltip,
1352
+ codeTheme,
1328
1353
  cursorColor,
1329
- cyan,
1330
- highlightBackground,
1331
- hyperLinkExtension,
1332
- hyperlinkDecoration,
1333
- hyperlinkStyle,
1334
- hyperlinkWidget,
1335
- invalid,
1336
- ivory,
1337
- malibu,
1354
+ defaultSlots,
1355
+ defaultTextSlots,
1356
+ defaultTheme,
1357
+ link,
1358
+ listener,
1338
1359
  markdownBundle,
1339
1360
  markdownHighlightStyle,
1340
- markdownTheme,
1341
- onChangeExtension,
1342
- sage,
1343
- stone,
1361
+ markdownTags,
1362
+ markdownTagsExtension,
1363
+ statefield,
1364
+ styles,
1344
1365
  tags2 as tags,
1345
- tooltipBackground,
1366
+ tasklist,
1367
+ textTheme,
1368
+ tooltip,
1346
1369
  useCollaboration,
1347
- useTextModel,
1348
- violet,
1349
- whiskey
1370
+ useTextModel
1350
1371
  };
1351
1372
  //# sourceMappingURL=index.mjs.map