@dxos/react-ui-editor 0.3.9-main.2dc53cf → 0.3.9-main.318af16

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 (149) hide show
  1. package/dist/lib/browser/index.mjs +1026 -827
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/types/src/automerge/automerge-plugin/PatchSemaphore.d.ts +2 -2
  5. package/dist/types/src/automerge/automerge-plugin/PatchSemaphore.d.ts.map +1 -1
  6. package/dist/types/src/automerge/automerge-plugin/codeMirrorToAm.d.ts +2 -2
  7. package/dist/types/src/automerge/automerge-plugin/codeMirrorToAm.d.ts.map +1 -1
  8. package/dist/types/src/automerge/automerge-plugin/handle.d.ts +9 -0
  9. package/dist/types/src/automerge/automerge-plugin/handle.d.ts.map +1 -0
  10. package/dist/types/src/automerge/automerge-plugin/index.d.ts +2 -2
  11. package/dist/types/src/automerge/automerge-plugin/index.d.ts.map +1 -1
  12. package/dist/types/src/automerge/automerge-plugin/plugin.d.ts +6 -5
  13. package/dist/types/src/automerge/automerge-plugin/plugin.d.ts.map +1 -1
  14. package/dist/types/src/automerge/automerge.stories.d.ts +2 -1
  15. package/dist/types/src/automerge/automerge.stories.d.ts.map +1 -1
  16. package/dist/types/src/components/TextEditor/TextEditor.d.ts +24 -20
  17. package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
  18. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +7 -24
  19. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
  20. package/dist/types/{stories/html/code-mirror.stories.d.ts → src/components/TextEditor/codemirror.stories.d.ts} +2 -2
  21. package/dist/types/src/components/TextEditor/codemirror.stories.d.ts.map +1 -0
  22. package/dist/types/src/components/TextEditor/extensions/basic/bundle.d.ts +8 -0
  23. package/dist/types/src/components/TextEditor/extensions/basic/bundle.d.ts.map +1 -0
  24. package/dist/types/src/components/TextEditor/extensions/basic/index.d.ts +3 -0
  25. package/dist/types/src/components/TextEditor/extensions/basic/index.d.ts.map +1 -0
  26. package/dist/types/src/components/TextEditor/extensions/basic/theme.d.ts +10 -0
  27. package/dist/types/src/components/TextEditor/extensions/basic/theme.d.ts.map +1 -0
  28. package/dist/types/src/components/TextEditor/extensions/change.d.ts +7 -0
  29. package/dist/types/src/components/TextEditor/extensions/change.d.ts.map +1 -0
  30. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkDecoration.d.ts +11 -0
  31. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkDecoration.d.ts.map +1 -0
  32. package/dist/types/src/components/TextEditor/extensions/{hyperlinkTooltip.d.ts → hyperlink/hyperlinkTooltip.d.ts} +0 -1
  33. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkTooltip.d.ts.map +1 -0
  34. package/dist/types/src/components/TextEditor/extensions/{hyperlink.d.ts → hyperlink/hyperlinkWidget.d.ts} +3 -3
  35. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkWidget.d.ts.map +1 -0
  36. package/dist/types/src/components/TextEditor/extensions/hyperlink/index.d.ts +4 -0
  37. package/dist/types/src/components/TextEditor/extensions/hyperlink/index.d.ts.map +1 -0
  38. package/dist/types/src/components/TextEditor/extensions/index.d.ts +3 -1
  39. package/dist/types/src/components/TextEditor/extensions/index.d.ts.map +1 -1
  40. package/dist/types/src/components/TextEditor/extensions/markdown/bundle.d.ts +8 -0
  41. package/dist/types/src/components/TextEditor/extensions/markdown/bundle.d.ts.map +1 -0
  42. package/dist/types/src/components/TextEditor/extensions/markdown/index.d.ts +3 -0
  43. package/dist/types/src/components/TextEditor/extensions/markdown/index.d.ts.map +1 -0
  44. package/dist/types/src/components/{Markdown/markdownTags.d.ts → TextEditor/extensions/markdown/tags.d.ts} +1 -1
  45. package/dist/types/src/components/TextEditor/extensions/markdown/tags.d.ts.map +1 -0
  46. package/dist/types/src/components/TextEditor/extensions/markdown/theme.d.ts +21 -0
  47. package/dist/types/src/components/TextEditor/extensions/markdown/theme.d.ts.map +1 -0
  48. package/dist/types/src/components/TextEditor/index.d.ts +1 -3
  49. package/dist/types/src/components/TextEditor/index.d.ts.map +1 -1
  50. package/dist/types/src/components/TextEditor/yjs.stories.d.ts +11 -0
  51. package/dist/types/src/components/TextEditor/yjs.stories.d.ts.map +1 -0
  52. package/dist/types/src/components/index.d.ts +0 -3
  53. package/dist/types/src/components/index.d.ts.map +1 -1
  54. package/dist/types/src/hooks/index.d.ts +4 -0
  55. package/dist/types/src/hooks/index.d.ts.map +1 -0
  56. package/dist/types/src/hooks/useCollaboration.d.ts +9 -0
  57. package/dist/types/src/hooks/useCollaboration.d.ts.map +1 -0
  58. package/dist/types/src/{model.d.ts → hooks/useTextModel.d.ts} +3 -15
  59. package/dist/types/src/hooks/useTextModel.d.ts.map +1 -0
  60. package/dist/types/src/hooks/yjs/cursors.d.ts.map +1 -0
  61. package/dist/types/src/hooks/yjs/index.d.ts.map +1 -0
  62. package/dist/types/src/hooks/yjs/space-provider.d.ts.map +1 -0
  63. package/dist/types/src/index.d.ts +2 -2
  64. package/dist/types/src/index.d.ts.map +1 -1
  65. package/dist/types/src/styles/tokens.d.ts +4 -0
  66. package/dist/types/src/styles/tokens.d.ts.map +1 -1
  67. package/dist/types/src/testing/replicator.d.ts +1 -1
  68. package/dist/types/src/testing/replicator.d.ts.map +1 -1
  69. package/package.json +31 -28
  70. package/src/automerge/automerge-plugin/PatchSemaphore.ts +5 -5
  71. package/src/automerge/automerge-plugin/codeMirrorToAm.ts +10 -7
  72. package/src/automerge/automerge-plugin/handle.ts +14 -0
  73. package/src/automerge/automerge-plugin/index.ts +2 -2
  74. package/src/automerge/automerge-plugin/plugin.ts +39 -10
  75. package/src/automerge/automerge.stories.tsx +32 -47
  76. package/src/components/TextEditor/TextEditor.stories.tsx +51 -55
  77. package/src/components/TextEditor/TextEditor.tsx +113 -55
  78. package/src/components/TextEditor/codemirror.stories.ts +114 -0
  79. package/src/components/TextEditor/extensions/basic/bundle.ts +26 -0
  80. package/src/components/TextEditor/extensions/basic/index.ts +6 -0
  81. package/src/components/TextEditor/extensions/basic/theme.ts +49 -0
  82. package/src/components/TextEditor/extensions/change.ts +22 -0
  83. package/src/components/TextEditor/extensions/hyperlink/hyperlinkDecoration.ts +99 -0
  84. package/src/components/TextEditor/extensions/{hyperlinkTooltip.tsx → hyperlink/hyperlinkTooltip.tsx} +2 -17
  85. package/src/components/TextEditor/extensions/{hyperlink.tsx → hyperlink/hyperlinkWidget.tsx} +21 -10
  86. package/src/components/TextEditor/extensions/hyperlink/index.ts +7 -0
  87. package/src/components/TextEditor/extensions/index.ts +3 -1
  88. package/src/components/TextEditor/extensions/markdown/bundle.ts +99 -0
  89. package/src/components/TextEditor/extensions/markdown/index.ts +6 -0
  90. package/src/components/{Markdown/markdownTheme.ts → TextEditor/extensions/markdown/theme.ts} +23 -12
  91. package/src/components/TextEditor/index.ts +1 -4
  92. package/src/components/TextEditor/yjs.stories.tsx +55 -0
  93. package/src/components/index.ts +0 -3
  94. package/src/hooks/index.ts +7 -0
  95. package/src/hooks/useCollaboration.ts +45 -0
  96. package/src/{model.ts → hooks/useTextModel.ts} +42 -17
  97. package/src/index.ts +3 -3
  98. package/src/styles/tokens.ts +6 -0
  99. package/src/testing/replicator.ts +1 -2
  100. package/dist/types/src/automerge/demo.d.ts +0 -25
  101. package/dist/types/src/automerge/demo.d.ts.map +0 -1
  102. package/dist/types/src/components/Editor/Editor.d.ts +0 -15
  103. package/dist/types/src/components/Editor/Editor.d.ts.map +0 -1
  104. package/dist/types/src/components/Editor/Editor.stories.d.ts +0 -29
  105. package/dist/types/src/components/Editor/Editor.stories.d.ts.map +0 -1
  106. package/dist/types/src/components/Editor/index.d.ts +0 -2
  107. package/dist/types/src/components/Editor/index.d.ts.map +0 -1
  108. package/dist/types/src/components/Markdown/Markdown.d.ts +0 -20
  109. package/dist/types/src/components/Markdown/Markdown.d.ts.map +0 -1
  110. package/dist/types/src/components/Markdown/Markdown.stories.d.ts +0 -20
  111. package/dist/types/src/components/Markdown/Markdown.stories.d.ts.map +0 -1
  112. package/dist/types/src/components/Markdown/index.d.ts +0 -2
  113. package/dist/types/src/components/Markdown/index.d.ts.map +0 -1
  114. package/dist/types/src/components/Markdown/markdownTags.d.ts.map +0 -1
  115. package/dist/types/src/components/Markdown/markdownTheme.d.ts +0 -93
  116. package/dist/types/src/components/Markdown/markdownTheme.d.ts.map +0 -1
  117. package/dist/types/src/components/RichText/RichText.d.ts +0 -20
  118. package/dist/types/src/components/RichText/RichText.d.ts.map +0 -1
  119. package/dist/types/src/components/RichText/RichText.stories.d.ts +0 -27
  120. package/dist/types/src/components/RichText/RichText.stories.d.ts.map +0 -1
  121. package/dist/types/src/components/RichText/index.d.ts +0 -2
  122. package/dist/types/src/components/RichText/index.d.ts.map +0 -1
  123. package/dist/types/src/components/TextEditor/extensions/hyperlink.d.ts.map +0 -1
  124. package/dist/types/src/components/TextEditor/extensions/hyperlinkTooltip.d.ts.map +0 -1
  125. package/dist/types/src/components/TextEditor/theme.d.ts +0 -8
  126. package/dist/types/src/components/TextEditor/theme.d.ts.map +0 -1
  127. package/dist/types/src/model.d.ts.map +0 -1
  128. package/dist/types/src/yjs/cursors.d.ts.map +0 -1
  129. package/dist/types/src/yjs/index.d.ts.map +0 -1
  130. package/dist/types/src/yjs/space-provider.d.ts.map +0 -1
  131. package/dist/types/stories/html/code-mirror.stories.d.ts.map +0 -1
  132. package/src/automerge/demo.ts +0 -106
  133. package/src/components/Editor/Editor.stories.tsx +0 -82
  134. package/src/components/Editor/Editor.tsx +0 -33
  135. package/src/components/Editor/index.ts +0 -5
  136. package/src/components/Markdown/Markdown.stories.tsx +0 -59
  137. package/src/components/Markdown/Markdown.tsx +0 -214
  138. package/src/components/Markdown/index.ts +0 -5
  139. package/src/components/RichText/RichText.stories.tsx +0 -70
  140. package/src/components/RichText/RichText.tsx +0 -181
  141. package/src/components/RichText/index.ts +0 -5
  142. package/src/components/TextEditor/theme.ts +0 -33
  143. /package/dist/types/src/{yjs → hooks/yjs}/cursors.d.ts +0 -0
  144. /package/dist/types/src/{yjs → hooks/yjs}/index.d.ts +0 -0
  145. /package/dist/types/src/{yjs → hooks/yjs}/space-provider.d.ts +0 -0
  146. /package/src/components/{Markdown/markdownTags.ts → TextEditor/extensions/markdown/tags.ts} +0 -0
  147. /package/src/{yjs → hooks/yjs}/cursors.ts +0 -0
  148. /package/src/{yjs → hooks/yjs}/index.ts +0 -0
  149. /package/src/{yjs → hooks/yjs}/space-provider.ts +0 -0
@@ -1,235 +1,24 @@
1
- // packages/ui/react-ui-editor/src/components/Editor/Editor.tsx
2
- import React3, { forwardRef as forwardRef3, memo } from "react";
3
- import { YXmlFragment } from "@dxos/text-model";
4
-
5
- // packages/ui/react-ui-editor/src/model.ts
6
- import { useMemo } from "react";
7
-
8
- // packages/ui/react-ui-editor/src/yjs/cursors.ts
9
- import * as random from "lib0/random";
10
- var cursorColors = [
11
- {
12
- color: "#30bced",
13
- light: "#30bced33"
14
- },
15
- {
16
- color: "#6eeb83",
17
- light: "#6eeb8333"
18
- },
19
- {
20
- color: "#ffbc42",
21
- light: "#ffbc4233"
22
- },
23
- {
24
- color: "#ecd444",
25
- light: "#ecd44433"
26
- },
27
- {
28
- color: "#ee6352",
29
- light: "#ee635233"
30
- },
31
- {
32
- color: "#9ac2c9",
33
- light: "#9ac2c933"
34
- },
35
- {
36
- color: "#8acb88",
37
- light: "#8acb8833"
38
- },
39
- {
40
- color: "#1be7ff",
41
- light: "#1be7ff33"
42
- }
43
- ];
44
- var cursorColor = cursorColors[random.uint32() % cursorColors.length];
45
-
46
- // packages/ui/react-ui-editor/src/yjs/space-provider.ts
47
- import * as decoding from "lib0/decoding";
48
- import * as encoding from "lib0/encoding";
49
- import { Observable } from "lib0/observable";
50
- import * as awarenessProtocol from "y-protocols/awareness";
51
- import { log } from "@dxos/log";
52
- var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/yjs/space-provider.ts";
53
- var messageAwareness = 1;
54
- var messageQueryAwareness = 3;
55
- var SpaceAwarenessProvider = class extends Observable {
56
- constructor({ space, doc, channel, awareness }) {
57
- super();
58
- this._space = space;
59
- this._awareness = awareness ?? new awarenessProtocol.Awareness(doc);
60
- this._channel = channel;
61
- this._clientId = doc.clientID;
62
- this._awareness.on("update", this._handleAwarenessUpdate.bind(this));
63
- this._space.listen(this._channel, this._handleSpaceMessage.bind(this));
64
- if (typeof window !== "undefined") {
65
- window.addEventListener("beforeunload", this._handleBeforeUnload.bind(this));
66
- } else if (typeof process !== "undefined") {
67
- process.on("exit", this._handleBeforeUnload.bind(this));
68
- }
69
- const encoderAwarenessQuery = encoding.createEncoder();
70
- encoding.writeVarUint(encoderAwarenessQuery, messageQueryAwareness);
71
- void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwarenessQuery));
72
- const encoderAwarenessState = encoding.createEncoder();
73
- encoding.writeVarUint(encoderAwarenessState, messageAwareness);
74
- encoding.writeVarUint8Array(encoderAwarenessState, awarenessProtocol.encodeAwarenessUpdate(this.awareness, [
75
- this._clientId
76
- ]));
77
- void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwarenessState));
78
- }
79
- get awareness() {
80
- return this._awareness;
81
- }
82
- _handleAwarenessUpdate({ added, updated, removed }, origin) {
83
- log("awareness update", {
84
- added,
85
- updated,
86
- removed,
87
- origin
88
- }, {
89
- F: __dxlog_file,
90
- L: 67,
91
- S: this,
92
- C: (f, a) => f(...a)
93
- });
94
- const changedClients = added.concat(updated).concat(removed);
95
- const encoderAwareness = encoding.createEncoder();
96
- encoding.writeVarUint(encoderAwareness, messageAwareness);
97
- encoding.writeVarUint8Array(encoderAwareness, awarenessProtocol.encodeAwarenessUpdate(this._awareness, changedClients));
98
- void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwareness));
99
- }
100
- _handleSpaceMessage({ payload }) {
101
- log("space message", payload, {
102
- F: __dxlog_file,
103
- L: 79,
104
- S: this,
105
- C: (f, a) => f(...a)
106
- });
107
- const data = new Uint8Array(Array.from(Object.values(payload)));
108
- const encoder = this._readMessage(data);
109
- if (encoder) {
110
- void this._space.postMessage(this._channel, encoding.toUint8Array(encoder));
111
- }
112
- }
113
- _readMessage(message) {
114
- const decoder = decoding.createDecoder(message);
115
- const encoder = encoding.createEncoder();
116
- const messageType = decoding.readVarUint(decoder);
117
- let sendReply = false;
118
- switch (messageType) {
119
- case messageAwareness: {
120
- awarenessProtocol.applyAwarenessUpdate(this._awareness, decoding.readVarUint8Array(decoder), this);
121
- break;
122
- }
123
- case messageQueryAwareness: {
124
- encoding.writeVarUint(encoder, messageAwareness);
125
- encoding.writeVarUint8Array(encoder, awarenessProtocol.encodeAwarenessUpdate(this._awareness, Array.from(this._awareness.getStates().keys())));
126
- sendReply = true;
127
- break;
128
- }
129
- default: {
130
- console.error("Invalid message:", messageType);
131
- return encoder;
132
- }
133
- }
134
- if (!sendReply) {
135
- return null;
136
- }
137
- return encoder;
138
- }
139
- _handleBeforeUnload() {
140
- awarenessProtocol.removeAwarenessStates(this._awareness, [
141
- this._clientId
142
- ], "window unload");
143
- }
144
- };
1
+ // packages/ui/react-ui-editor/src/index.ts
2
+ import { TextKind } from "@dxos/protocols/proto/dxos/echo/model/text";
3
+ import { Doc, YText as YText2, YXmlFragment } from "@dxos/text-model";
145
4
 
146
- // packages/ui/react-ui-editor/src/model.ts
147
- var useTextModel = ({ identity, space, text }) => {
148
- const provider = useMemo(() => {
149
- if (!space || !text?.doc) {
150
- return void 0;
151
- }
152
- return new SpaceAwarenessProvider({
153
- space,
154
- doc: text.doc,
155
- channel: `yjs.awareness.${text.id}`
156
- });
157
- }, [
158
- identity,
159
- space,
160
- text?.doc
161
- ]);
162
- if (!text?.doc || !text?.content) {
163
- return void 0;
164
- }
165
- return {
166
- id: text.doc.guid,
167
- content: text.content,
168
- provider,
169
- peer: identity ? {
170
- id: identity.identityKey.toHex(),
171
- name: identity.profile?.displayName
172
- } : void 0
173
- };
174
- };
5
+ // packages/ui/react-ui-editor/src/components/TextEditor/index.ts
6
+ import { tags as tags2 } from "@lezer/highlight";
175
7
 
176
- // packages/ui/react-ui-editor/src/components/Markdown/Markdown.tsx
177
- import { autocompletion, completionKeymap, closeBrackets, closeBracketsKeymap } from "@codemirror/autocomplete";
178
- import { defaultKeymap, history, historyKeymap, indentWithTab } from "@codemirror/commands";
179
- import { markdown, markdownLanguage as markdownLanguage2 } from "@codemirror/lang-markdown";
180
- import { bracketMatching, defaultHighlightStyle, foldKeymap, indentOnInput, syntaxHighlighting } from "@codemirror/language";
181
- import { languages } from "@codemirror/language-data";
182
- import { lintKeymap } from "@codemirror/lint";
183
- import { searchKeymap, highlightSelectionMatches } from "@codemirror/search";
184
- import { EditorState, StateField } from "@codemirror/state";
8
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/basic/bundle.ts
9
+ import { closeBrackets } from "@codemirror/autocomplete";
10
+ import { bracketMatching, defaultHighlightStyle, syntaxHighlighting } from "@codemirror/language";
185
11
  import { oneDarkHighlightStyle } from "@codemirror/theme-one-dark";
186
- import { keymap, crosshairCursor, drawSelection, dropCursor, highlightActiveLine, highlightActiveLineGutter, highlightSpecialChars, placeholder, rectangularSelection, EditorView } from "@codemirror/view";
187
- import { useFocusableGroup } from "@fluentui/react-tabster";
188
- import { vim } from "@replit/codemirror-vim";
189
- import React, { forwardRef, useEffect, useImperativeHandle, useState, useMemo as useMemo2, useCallback } from "react";
190
- import { yCollab } from "y-codemirror.next";
191
- import { generateName } from "@dxos/display-name";
192
- import { useThemeContext } from "@dxos/react-ui";
193
- import { getColorForValue } from "@dxos/react-ui-theme";
194
- import { YText } from "@dxos/text-model";
195
-
196
- // packages/ui/react-ui-editor/src/components/Markdown/markdownTags.ts
197
- import { styleTags, Tag } from "@lezer/highlight";
198
- var markdownTags = {
199
- headingMark: Tag.define(),
200
- quoteMark: Tag.define(),
201
- listMark: Tag.define(),
202
- linkMark: Tag.define(),
203
- emphasisMark: Tag.define(),
204
- codeMark: Tag.define(),
205
- codeText: Tag.define(),
206
- inlineCode: Tag.define(),
207
- url: Tag.define(),
208
- linkReference: Tag.define(),
209
- linkLabel: Tag.define()
210
- };
211
- var markdownTagsExtension = {
212
- props: [
213
- styleTags({
214
- HeaderMark: markdownTags.headingMark,
215
- QuoteMark: markdownTags.quoteMark,
216
- ListMark: markdownTags.listMark,
217
- LinkMark: markdownTags.linkMark,
218
- EmphasisMark: markdownTags.emphasisMark,
219
- CodeMark: markdownTags.codeMark,
220
- CodeText: markdownTags.codeText,
221
- InlineCode: markdownTags.inlineCode,
222
- URL: markdownTags.url,
223
- LinkReference: markdownTags.linkReference,
224
- LinkLabel: markdownTags.linkLabel
225
- })
226
- ]
227
- };
12
+ import { placeholder } from "@codemirror/view";
13
+ var basicBundle = ({ themeMode, placeholder: _placeholder }) => [
14
+ bracketMatching(),
15
+ closeBrackets(),
16
+ _placeholder && placeholder(_placeholder),
17
+ // TODO(burdon): Is this required?
18
+ themeMode === "dark" ? syntaxHighlighting(oneDarkHighlightStyle) : syntaxHighlighting(defaultHighlightStyle)
19
+ ].filter(Boolean);
228
20
 
229
- // packages/ui/react-ui-editor/src/components/Markdown/markdownTheme.ts
230
- import { markdownLanguage } from "@codemirror/lang-markdown";
231
- import { HighlightStyle } from "@codemirror/language";
232
- import { tags } from "@lezer/highlight";
21
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/basic/theme.ts
233
22
  import get from "lodash.get";
234
23
 
235
24
  // packages/ui/react-ui-editor/src/styles/markdown.ts
@@ -242,13 +31,6 @@ var heading = {
242
31
  5: "mbs-4 mbe-2 text-lg font-extrabold text-inherit no-underline",
243
32
  6: "mbs-4 mbe-2 font-black text-inherit no-underline"
244
33
  };
245
- var blockquote = "mlb-2 border-is-4 border-neutral-500/50 pis-5";
246
- var unorderedList = "mlb-2 grid grid-cols-[min-content_1fr] [&>li:before]:content-[attr(marker)] [&>li:before]:mlb-1 [&>li:before]:mie-2";
247
- var orderedList = 'mlb-2 grid grid-cols-[min-content_1fr] [&>li:before]:content-[counters(section,_".")_"._"] [counter-reset:section] [&>li:before]:mlb-1';
248
- var listItem = "contents before:[counter-increment:section]";
249
- var codeBlock = "mlb-2 font-mono bg-neutral-500/10 p-3 rounded";
250
- var horizontalRule = "mlb-4 border-neutral-500/50";
251
- var paragraph = "mlb-1";
252
34
  var bold = "font-bold";
253
35
  var code = "font-mono bg-neutral-500/10 rounded pli-0.5 plb-0.5 -mlb-0.5";
254
36
  var codeWithoutMarks = mx(code, "pli-1.5 mli-0.5");
@@ -260,53 +42,357 @@ var mark = "!font-normal !no-underline !text-inherit opacity-40";
260
42
  import { tailwindConfig } from "@dxos/react-ui-theme";
261
43
  var tokens = tailwindConfig({}).theme;
262
44
 
263
- // packages/ui/react-ui-editor/src/components/Markdown/markdownTheme.ts
264
- var malibu = "#61afef";
265
- var cursor = "#ffffff";
266
- var monospace = get(tokens, "fontFamily.mono", [
267
- "monospace"
268
- ]).join(",");
269
- var markdownTheme = {
270
- // TODO(thure): consider whether these commented-out rules from one-dark-theme should be integrated.
271
- // '&': {
272
- // color: ivory,
273
- // backgroundColor: background
274
- // },
275
- // '.cm-cursor, .cm-dropCursor': { borderLeftColor: cursor },
276
- // '&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection': {
277
- // backgroundColor: selection
278
- // },
279
- // '.cm-panels': { backgroundColor: darkBackground, color: ivory },
280
- // '.cm-panels.cm-panels-top': { borderBottom: '2px solid black' },
281
- // '.cm-panels.cm-panels-bottom': { borderTop: '2px solid black' },
282
- // '.cm-searchMatch': {
283
- // backgroundColor: '#72a1ff59',
284
- // outline: '1px solid #457dff'
285
- // },
286
- // '.cm-searchMatch.cm-searchMatch-selected': {
287
- // backgroundColor: '#6199ff2f'
288
- // },
289
- // '.cm-activeLine': { backgroundColor: '#6699ff0b' },
290
- // '.cm-selectionMatch': { backgroundColor: '#aafe661a' },
291
- // '&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': {
292
- // backgroundColor: '#bad0f847'
293
- // },
294
- // '.cm-gutters': {
295
- // backgroundColor: background,
296
- // color: stone,
297
- // border: 'none'
298
- // },
299
- // '.cm-activeLineGutter': {
300
- // backgroundColor: highlightBackground
301
- // },
302
- // '.cm-foldPlaceholder': {
303
- // backgroundColor: 'transparent',
304
- // border: 'none',
305
- // color: '#ddd'
306
- // },
307
- ".cm-tooltip": {
308
- backgroundColor: "transparent",
309
- border: "none"
45
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/basic/theme.ts
46
+ var basicTheme = {
47
+ "&.cm-focused": {
48
+ outline: "none"
49
+ },
50
+ "& .cm-line": {
51
+ paddingInline: 0,
52
+ lineHeight: 1.6,
53
+ minBlockSize: "1.6em"
54
+ },
55
+ "& .cm-line *": {
56
+ lineHeight: 1.6
57
+ },
58
+ "& .cm-scroller": {
59
+ fontFamily: get(tokens, "fontFamily.body", []).join(","),
60
+ overflow: "visible"
61
+ },
62
+ "& .cm-content": {
63
+ padding: 0,
64
+ caretColor: "black"
65
+ },
66
+ ".dark & .cm-content": {
67
+ caretColor: "white"
68
+ },
69
+ "& .cm-tooltip": {
70
+ backgroundColor: "transparent",
71
+ border: "none"
72
+ },
73
+ "& .cm-link": {
74
+ color: get(tokens, "extend.colors.primary.500"),
75
+ textDecoration: "underline"
76
+ }
77
+ };
78
+
79
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/change.ts
80
+ import { StateField } from "@codemirror/state";
81
+ var onChangeExtension = (onChange) => StateField.define({
82
+ create: () => null,
83
+ update: (_value, transaction) => {
84
+ if (transaction.docChanged && onChange) {
85
+ onChange(transaction.newDoc.toString());
86
+ }
87
+ return null;
88
+ }
89
+ });
90
+
91
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/hyperlink/hyperlinkDecoration.ts
92
+ import { syntaxTree } from "@codemirror/language";
93
+ import { RangeSetBuilder, StateField as StateField2 } from "@codemirror/state";
94
+ import { Decoration, EditorView, WidgetType } from "@codemirror/view";
95
+ var Link = class extends WidgetType {
96
+ constructor(_anchor, _text, _url) {
97
+ super();
98
+ this._anchor = _anchor;
99
+ this._text = _text;
100
+ this._url = _url;
101
+ }
102
+ eq(other) {
103
+ return this._text === other.text && this._url === other.url;
104
+ }
105
+ toDOM(view) {
106
+ const link = document.createElement("a");
107
+ link.setAttribute("class", "cm-link");
108
+ link.textContent = this._text;
109
+ if (this._url) {
110
+ link.setAttribute("target", "_blank");
111
+ link.href = this._url;
112
+ } else {
113
+ link.onclick = () => {
114
+ view.dispatch({
115
+ selection: {
116
+ anchor: this._anchor
117
+ }
118
+ });
119
+ };
120
+ }
121
+ return link;
122
+ }
123
+ };
124
+ var hyperlinkDecoration = ({ link } = {}) => {
125
+ return StateField2.define({
126
+ create: (state) => update(state, link),
127
+ update: (_, tr) => update(tr.state, link),
128
+ provide: (field) => EditorView.decorations.from(field)
129
+ });
130
+ };
131
+ var update = (state, link) => {
132
+ const builder = new RangeSetBuilder();
133
+ const cursor = state.selection.main.head;
134
+ syntaxTree(state).iterate({
135
+ enter: (node) => {
136
+ if (cursor < node.from || cursor > node.to) {
137
+ if (node.name === "Link") {
138
+ const marks = node.node.getChildren("LinkMark");
139
+ const text = marks.length >= 2 ? state.sliceDoc(marks[0].to, marks[1].from) : "";
140
+ const urlNode = node.node.getChild("URL");
141
+ const url = urlNode ? state.sliceDoc(urlNode.from, urlNode.to) : "";
142
+ builder.add(node.from, node.to, Decoration.replace({
143
+ widget: new Link(node.from, text, link ? url : void 0)
144
+ }));
145
+ return false;
146
+ }
147
+ }
148
+ return true;
149
+ }
150
+ });
151
+ return builder.finish();
152
+ };
153
+
154
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/hyperlink/hyperlinkTooltip.tsx
155
+ import { hoverTooltip } from "@codemirror/view";
156
+ import { tooltipContent } from "@dxos/react-ui-theme";
157
+ var markdownLinkRegexp = /\[([^\]]+)]\(([^)]+)\)/;
158
+ var createHyperlinkTooltip = (onHover, regexp = markdownLinkRegexp) => hoverTooltip((view, pos) => {
159
+ const { from, text } = view.state.doc.lineAt(pos);
160
+ const p = pos - from;
161
+ let idx = 0;
162
+ let match;
163
+ do {
164
+ match = text.substring(idx).match(regexp);
165
+ if (!match) {
166
+ match = void 0;
167
+ break;
168
+ }
169
+ idx = text.indexOf(match[0]);
170
+ if (p >= idx && p < idx + match[0].length) {
171
+ break;
172
+ }
173
+ idx += match[0].length;
174
+ } while (true);
175
+ if (!match) {
176
+ return null;
177
+ }
178
+ const [, , url] = match ?? [];
179
+ return {
180
+ pos: idx + from,
181
+ end: idx + from + match[0].length,
182
+ above: true,
183
+ create: () => {
184
+ const el = document.createElement("a");
185
+ el.innerText = "_";
186
+ el.className = tooltipContent({}, "mb-2 p-1");
187
+ el.setAttribute("target", "_blank");
188
+ el.setAttribute("href", url);
189
+ onHover(el, url);
190
+ return {
191
+ dom: el
192
+ };
193
+ }
194
+ };
195
+ });
196
+
197
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/hyperlink/hyperlinkWidget.tsx
198
+ import { Decoration as Decoration2, EditorView as EditorView2, MatchDecorator, ViewPlugin } from "@codemirror/view";
199
+ var markdownLinkRegexp2 = /\[([^\]]+)]\(([^)]+)\)(!?)/gi;
200
+ var linkDecorator = () => new MatchDecorator({
201
+ regexp: markdownLinkRegexp2,
202
+ decorate: (add, from, to, match, view) => {
203
+ const [_, label, url] = match;
204
+ const p0 = {
205
+ start: from,
206
+ end: from + 1
207
+ };
208
+ const p1 = {
209
+ start: from + 1,
210
+ end: from + 1 + label.length
211
+ };
212
+ const p2 = {
213
+ start: p1.end,
214
+ end: p1.end + 1
215
+ };
216
+ const p3 = {
217
+ start: p1.end + 1,
218
+ end: p1.end + 1 + url.length + 2
219
+ };
220
+ add(p0.start, p0.end, Decoration2.mark({
221
+ class: "cm-hyperlink-bracket",
222
+ _attributes: {
223
+ style: "display: none"
224
+ }
225
+ }));
226
+ add(p1.start, p1.end, Decoration2.mark({
227
+ class: "cm-hyperlink-label"
228
+ }));
229
+ add(p2.start, p2.end, Decoration2.mark({
230
+ class: "cm-hyperlink-bracket",
231
+ _attributes: {
232
+ style: "display: none"
233
+ }
234
+ }));
235
+ add(p3.start, p3.end, Decoration2.mark({
236
+ class: "cm-hyperlink-url",
237
+ _attributes: {
238
+ style: "display: none"
239
+ }
240
+ }));
241
+ }
242
+ });
243
+ function hyperLinkExtension() {
244
+ return ViewPlugin.fromClass(class HyperLinkView {
245
+ constructor(view) {
246
+ this.decorator = linkDecorator();
247
+ this.decorations = this.decorator.createDeco(view);
248
+ }
249
+ update(update2) {
250
+ if (update2.docChanged || update2.viewportChanged) {
251
+ this.decorations = this.decorator.updateDeco(update2, this.decorations);
252
+ }
253
+ }
254
+ }, {
255
+ decorations: (view) => view.decorations,
256
+ eventHandlers: {
257
+ // TODO(burdon): CLick to expand link.
258
+ // https://discuss.codemirror.net/t/decorator-iteration-on-click-event/4226/2
259
+ mousedown: function(event, view) {
260
+ const pos = view.posAtDOM(event.target);
261
+ if (pos !== 0) {
262
+ console.log(this);
263
+ }
264
+ },
265
+ // TODO(burdon): Hide when cursor moves away.
266
+ keydown: (event, view) => {
267
+ view.update([]);
268
+ }
269
+ }
270
+ });
271
+ }
272
+ var hyperlinkStyle = EditorView2.baseTheme({
273
+ ".cm-hyperlink-label": {
274
+ cursor: "pointer",
275
+ textDecoration: "underline"
276
+ },
277
+ ".cm-hyperlink-bracket": {
278
+ opacity: 0.2
279
+ },
280
+ ".cm-hyperlink-url": {
281
+ opacity: 0.5,
282
+ fontFamily: "monospace"
283
+ }
284
+ });
285
+ var hyperlinkWidget = [
286
+ hyperLinkExtension(),
287
+ hyperlinkStyle
288
+ ];
289
+
290
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/bundle.ts
291
+ import { closeBrackets as closeBrackets2, closeBracketsKeymap } from "@codemirror/autocomplete";
292
+ import { defaultKeymap, history, historyKeymap, indentWithTab } from "@codemirror/commands";
293
+ import { markdownLanguage as markdownLanguage2, markdown } from "@codemirror/lang-markdown";
294
+ import { bracketMatching as bracketMatching2, defaultHighlightStyle as defaultHighlightStyle2, foldKeymap, indentOnInput, syntaxHighlighting as syntaxHighlighting2 } from "@codemirror/language";
295
+ import { languages } from "@codemirror/language-data";
296
+ import { lintKeymap } from "@codemirror/lint";
297
+ import { highlightSelectionMatches, searchKeymap } from "@codemirror/search";
298
+ import { EditorState } from "@codemirror/state";
299
+ import { oneDarkHighlightStyle as oneDarkHighlightStyle2 } from "@codemirror/theme-one-dark";
300
+ import { crosshairCursor, drawSelection, dropCursor, EditorView as EditorView3, highlightActiveLine, highlightActiveLineGutter, highlightSpecialChars, keymap, placeholder as placeholder2, rectangularSelection } from "@codemirror/view";
301
+
302
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/tags.ts
303
+ import { styleTags, Tag } from "@lezer/highlight";
304
+ var markdownTags = {
305
+ headingMark: Tag.define(),
306
+ quoteMark: Tag.define(),
307
+ listMark: Tag.define(),
308
+ linkMark: Tag.define(),
309
+ emphasisMark: Tag.define(),
310
+ codeMark: Tag.define(),
311
+ codeText: Tag.define(),
312
+ inlineCode: Tag.define(),
313
+ url: Tag.define(),
314
+ linkReference: Tag.define(),
315
+ linkLabel: Tag.define()
316
+ };
317
+ var markdownTagsExtension = {
318
+ props: [
319
+ styleTags({
320
+ HeaderMark: markdownTags.headingMark,
321
+ QuoteMark: markdownTags.quoteMark,
322
+ ListMark: markdownTags.listMark,
323
+ LinkMark: markdownTags.linkMark,
324
+ EmphasisMark: markdownTags.emphasisMark,
325
+ CodeMark: markdownTags.codeMark,
326
+ CodeText: markdownTags.codeText,
327
+ InlineCode: markdownTags.inlineCode,
328
+ URL: markdownTags.url,
329
+ LinkReference: markdownTags.linkReference,
330
+ LinkLabel: markdownTags.linkLabel
331
+ })
332
+ ]
333
+ };
334
+
335
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/theme.ts
336
+ import { markdownLanguage } from "@codemirror/lang-markdown";
337
+ import { HighlightStyle } from "@codemirror/language";
338
+ import { tags } from "@lezer/highlight";
339
+ import get2 from "lodash.get";
340
+ var chalky = "#e5c07b";
341
+ var coral = "#e06c75";
342
+ var cyan = "#56b6c2";
343
+ var invalid = "#ffffff";
344
+ var ivory = "#abb2bf";
345
+ var stone = "#7d8799";
346
+ var malibu = "#61afef";
347
+ var sage = "#98c379";
348
+ var whiskey = "#d19a66";
349
+ var violet = "#c678dd";
350
+ var highlightBackground = "#2c313a";
351
+ var tooltipBackground = "#353a42";
352
+ var monospace = get2(tokens, "fontFamily.mono", [
353
+ "monospace"
354
+ ]).join(",");
355
+ var markdownTheme = {
356
+ // TODO(thure): Consider whether these commented-out rules from one-dark-theme should be integrated.
357
+ // '&': {
358
+ // color: ivory,
359
+ // backgroundColor: background
360
+ // },
361
+ // '.cm-cursor, .cm-dropCursor': { borderLeftColor: cursor },
362
+ // '&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection': {
363
+ // backgroundColor: selection
364
+ // },
365
+ // '.cm-panels': { backgroundColor: darkBackground, color: ivory },
366
+ // '.cm-panels.cm-panels-top': { borderBottom: '2px solid black' },
367
+ // '.cm-panels.cm-panels-bottom': { borderTop: '2px solid black' },
368
+ // '.cm-searchMatch': {
369
+ // backgroundColor: '#72a1ff59',
370
+ // outline: '1px solid #457dff'
371
+ // },
372
+ // '.cm-searchMatch.cm-searchMatch-selected': {
373
+ // backgroundColor: '#6199ff2f'
374
+ // },
375
+ // '.cm-activeLine': { backgroundColor: '#6699ff0b' },
376
+ // '.cm-selectionMatch': { backgroundColor: '#aafe661a' },
377
+ // '&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': {
378
+ // backgroundColor: '#bad0f847'
379
+ // },
380
+ // '.cm-gutters': {
381
+ // backgroundColor: background,
382
+ // color: stone,
383
+ // border: 'none'
384
+ // },
385
+ // '.cm-activeLineGutter': {
386
+ // backgroundColor: highlightBackground
387
+ // },
388
+ // '.cm-foldPlaceholder': {
389
+ // backgroundColor: 'transparent',
390
+ // border: 'none',
391
+ // color: '#ddd'
392
+ // },
393
+ "& .cm-tooltip": {
394
+ backgroundColor: "transparent",
395
+ border: "none"
310
396
  },
311
397
  // '.dark & .cm-tooltip': {
312
398
  // border: 'none',
@@ -331,40 +417,42 @@ var markdownTheme = {
331
417
  },
332
418
  "& .cm-line": {
333
419
  paddingInline: 0,
420
+ lineHeight: 1.6,
334
421
  minBlockSize: "1.6em"
335
422
  },
336
423
  "& .cm-line *": {
337
424
  lineHeight: 1.6
338
425
  },
339
426
  "&.cm-focused .cm-selectionBackground, & .cm-selectionBackground": {
340
- background: get(tokens, "extend.colors.primary.150", "#00ffff")
427
+ background: get2(tokens, "extend.colors.primary.150", "#00ffff")
341
428
  },
342
429
  ".dark & .cm-selectionBackground, .dark &.cm-focused .cm-selectionBackground": {
343
- background: get(tokens, "extend.colors.primary.850", "#00ffff")
430
+ background: get2(tokens, "extend.colors.primary.850", "#00ffff")
344
431
  },
345
432
  "& .cm-selectionMatch": {
346
- background: get(tokens, "extend.colors.primary.250", "#00ffff") + "44"
433
+ background: get2(tokens, "extend.colors.primary.250", "#00ffff") + "44"
347
434
  },
348
435
  ".dark & .cm-selectionMatch": {
349
- background: get(tokens, "extend.colors.primary.600", "#00ffff") + "44"
436
+ background: get2(tokens, "extend.colors.primary.600", "#00ffff") + "44"
350
437
  },
351
438
  "& .cm-content": {
439
+ padding: 0,
352
440
  caretColor: "black"
353
441
  },
354
442
  ".dark & .cm-content": {
355
- caretColor: cursor
443
+ caretColor: "white"
356
444
  },
357
445
  "& .cm-cursor": {
358
446
  borderLeftColor: "black"
359
447
  },
360
448
  ".dark & .cm-cursor": {
361
- borderLeftColor: cursor
449
+ borderLeftColor: "white"
362
450
  },
363
451
  ".cm-placeholder": {
364
- fontFamily: get(tokens, "fontFamily.body", []).join(",")
452
+ fontFamily: get2(tokens, "fontFamily.body", []).join(",")
365
453
  },
366
454
  "& .cm-scroller": {
367
- fontFamily: get(tokens, "fontFamily.mono", []).join(","),
455
+ fontFamily: get2(tokens, "fontFamily.mono", []).join(","),
368
456
  overflow: "visible"
369
457
  },
370
458
  "& .cm-activeLine": {
@@ -374,7 +462,7 @@ var markdownTheme = {
374
462
  backgroundColor: "transparent"
375
463
  },
376
464
  "& .cm-ySelectionInfo": {
377
- fontFamily: get(tokens, "fontFamily.body", []).join(","),
465
+ fontFamily: get2(tokens, "fontFamily.body", []).join(","),
378
466
  padding: "2px 4px",
379
467
  marginBlockStart: "-4px"
380
468
  },
@@ -397,8 +485,16 @@ var markdownTheme = {
397
485
  "& .cm-yLineSelection": {
398
486
  margin: "0"
399
487
  },
400
- ...Object.keys(get(tokens, "extend.fontSize", {})).reduce((acc, fontSize) => {
401
- const height = get(tokens, [
488
+ ".cm-link": {
489
+ color: "rgb(20 89 208)",
490
+ textDecorationLine: "underline",
491
+ textDecorationThickness: "1px",
492
+ textUnderlineOffset: "2px",
493
+ borderRadius: ".125rem"
494
+ },
495
+ // TODO(burdon): Document.
496
+ ...Object.keys(get2(tokens, "extend.fontSize", {})).reduce((acc, fontSize) => {
497
+ const height = get2(tokens, [
402
498
  "extend",
403
499
  "fontSize",
404
500
  fontSize,
@@ -417,7 +513,7 @@ var markdownTheme = {
417
513
  return acc;
418
514
  }, {})
419
515
  };
420
- var markdownDarkHighlighting = HighlightStyle.define([
516
+ var markdownHighlightStyle = HighlightStyle.define([
421
517
  {
422
518
  tag: [
423
519
  tags.keyword,
@@ -533,17 +629,570 @@ var markdownDarkHighlighting = HighlightStyle.define([
533
629
  ], {
534
630
  scope: markdownLanguage,
535
631
  all: {
536
- fontFamily: get(tokens, "fontFamily.body", []).join(",")
632
+ fontFamily: get2(tokens, "fontFamily.body", []).join(",")
537
633
  }
538
634
  });
539
635
 
540
- // packages/ui/react-ui-editor/src/components/Markdown/Markdown.tsx
636
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/bundle.ts
637
+ var markdownBundle = ({ themeMode, placeholder: _placeholder }) => {
638
+ return [
639
+ bracketMatching2(),
640
+ closeBrackets2(),
641
+ _placeholder && placeholder2(_placeholder),
642
+ EditorState.allowMultipleSelections.of(true),
643
+ EditorView3.lineWrapping,
644
+ // autocompletion(),
645
+ crosshairCursor(),
646
+ dropCursor(),
647
+ drawSelection(),
648
+ highlightActiveLine(),
649
+ highlightActiveLineGutter(),
650
+ highlightSelectionMatches(),
651
+ highlightSpecialChars(),
652
+ history(),
653
+ indentOnInput(),
654
+ rectangularSelection(),
655
+ keymap.of([
656
+ ...closeBracketsKeymap,
657
+ // ...completionKeymap,
658
+ ...defaultKeymap,
659
+ ...foldKeymap,
660
+ ...historyKeymap,
661
+ ...lintKeymap,
662
+ ...searchKeymap,
663
+ indentWithTab
664
+ ]),
665
+ // Main extension.
666
+ // https://github.com/codemirror/lang-markdown
667
+ markdown({
668
+ base: markdownLanguage2,
669
+ codeLanguages: languages,
670
+ extensions: [
671
+ // TODO(burdon): This seems to upgrade the parser.
672
+ // GitHub flavored markdown bundle: Table, TaskList, Strikethrough, and Autolink.
673
+ // https://github.com/lezer-parser/markdown?tab=readme-ov-file#github-flavored-markdown
674
+ // https://github.github.com/gfm
675
+ // GFM,
676
+ // Custom styling.
677
+ markdownTagsExtension
678
+ ]
679
+ }),
680
+ // TODO(thure): All but one rule here apply to both themes; rename or refactor.
681
+ syntaxHighlighting2(markdownHighlightStyle),
682
+ themeMode === "dark" ? syntaxHighlighting2(oneDarkHighlightStyle2) : syntaxHighlighting2(defaultHighlightStyle2)
683
+ ].filter(Boolean);
684
+ };
685
+
686
+ // packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx
687
+ import { EditorState as EditorState2 } from "@codemirror/state";
688
+ import { EditorView as EditorView4 } from "@codemirror/view";
689
+ import { useFocusableGroup } from "@fluentui/react-tabster";
690
+ import { vim } from "@replit/codemirror-vim";
691
+ import defaultsDeep from "lodash.defaultsdeep";
692
+ import get3 from "lodash.get";
693
+ import React, { forwardRef, useCallback, useEffect as useEffect2, useImperativeHandle, useState } from "react";
694
+ import { isDocAccessor as isDocAccessor2 } from "@dxos/echo-schema";
695
+ import { useThemeContext } from "@dxos/react-ui";
696
+
697
+ // packages/ui/react-ui-editor/src/hooks/useCollaboration.ts
698
+ import { useEffect, useMemo } from "react";
699
+ import { yCollab } from "y-codemirror.next";
700
+ import { generateName } from "@dxos/display-name";
701
+ import { isDocAccessor } from "@dxos/echo-schema";
702
+ import { getColorForValue } from "@dxos/react-ui-theme";
703
+ import { YText } from "@dxos/text-model";
704
+
705
+ // packages/ui/react-ui-editor/src/automerge/automerge-plugin/plugin.ts
706
+ import { Annotation, Facet, StateEffect, StateField as StateField3 } from "@codemirror/state";
707
+ import { ViewPlugin as ViewPlugin2 } from "@codemirror/view";
708
+ import * as automerge2 from "@dxos/automerge/automerge";
709
+
710
+ // packages/ui/react-ui-editor/src/automerge/automerge-plugin/PatchSemaphore.ts
711
+ import { next as automerge } from "@dxos/automerge/automerge";
712
+
713
+ // packages/ui/react-ui-editor/src/automerge/automerge-plugin/amToCodemirror.ts
714
+ import { ChangeSet } from "@codemirror/state";
715
+ var amToCodemirror_default = (view, selection, target, patches) => {
716
+ for (const patch of patches) {
717
+ const changeSpec = handlePatch(patch, target, view.state);
718
+ if (changeSpec != null) {
719
+ const changeSet = ChangeSet.of(changeSpec, view.state.doc.length, "\n");
720
+ selection = selection.map(changeSet, 1);
721
+ view.dispatch({
722
+ changes: changeSet,
723
+ annotations: reconcileAnnotationType.of({})
724
+ });
725
+ }
726
+ }
727
+ view.dispatch({
728
+ selection,
729
+ annotations: reconcileAnnotationType.of({})
730
+ });
731
+ };
732
+ var handlePatch = (patch, target, state) => {
733
+ if (patch.action === "insert") {
734
+ return handleInsert(target, patch);
735
+ } else if (patch.action === "splice") {
736
+ return handleSplice(target, patch);
737
+ } else if (patch.action === "del") {
738
+ return handleDel(target, patch);
739
+ } else if (patch.action === "put") {
740
+ return handlePut(target, patch, state);
741
+ } else {
742
+ return null;
743
+ }
744
+ };
745
+ var handleInsert = (target, patch) => {
746
+ const index = charPath(target, patch.path);
747
+ if (index == null) {
748
+ return [];
749
+ }
750
+ const text = patch.values.map((v) => v ? v.toString() : "").join("");
751
+ return [
752
+ {
753
+ from: index,
754
+ to: index,
755
+ insert: text
756
+ }
757
+ ];
758
+ };
759
+ var handleSplice = (target, patch) => {
760
+ const index = charPath(target, patch.path);
761
+ if (index == null) {
762
+ return [];
763
+ }
764
+ return [
765
+ {
766
+ from: index,
767
+ insert: patch.value
768
+ }
769
+ ];
770
+ };
771
+ var handleDel = (target, patch) => {
772
+ const index = charPath(target, patch.path);
773
+ if (index == null) {
774
+ return [];
775
+ }
776
+ const length = patch.length || 1;
777
+ return [
778
+ {
779
+ from: index,
780
+ to: index + length
781
+ }
782
+ ];
783
+ };
784
+ var handlePut = (target, patch, state) => {
785
+ const index = charPath(target, [
786
+ ...patch.path,
787
+ 0
788
+ ]);
789
+ if (index == null) {
790
+ return [];
791
+ }
792
+ const length = state.doc.length;
793
+ if (typeof patch.value !== "string") {
794
+ return [];
795
+ }
796
+ return [
797
+ {
798
+ from: 0,
799
+ to: length,
800
+ insert: patch.value
801
+ }
802
+ ];
803
+ };
804
+ var charPath = (textPath, candidatePath) => {
805
+ if (candidatePath.length !== textPath.length + 1) {
806
+ return null;
807
+ }
808
+ for (let i = 0; i < textPath.length; i++) {
809
+ if (textPath[i] !== candidatePath[i]) {
810
+ return null;
811
+ }
812
+ }
813
+ const index = candidatePath[candidatePath.length - 1];
814
+ if (typeof index === "number") {
815
+ return index;
816
+ }
817
+ return null;
818
+ };
819
+
820
+ // packages/ui/react-ui-editor/src/automerge/automerge-plugin/codeMirrorToAm.ts
821
+ import { next as am } from "@dxos/automerge/automerge";
822
+ var codeMirrorToAm_default = (field, handle, transactions, state) => {
823
+ const { lastHeads, path } = state.field(field);
824
+ let hasChanges = false;
825
+ for (const tr of transactions) {
826
+ tr.changes.iterChanges(() => {
827
+ hasChanges = true;
828
+ });
829
+ }
830
+ if (!hasChanges) {
831
+ return void 0;
832
+ }
833
+ const newHeads = handle.changeAt(lastHeads, (doc) => {
834
+ for (const tr of transactions) {
835
+ tr.changes.iterChanges((fromA, toA, _fromB, _toB, inserted) => {
836
+ am.splice(doc, path, fromA, toA - fromA, inserted.toString());
837
+ });
838
+ }
839
+ });
840
+ return newHeads ?? void 0;
841
+ };
842
+
843
+ // packages/ui/react-ui-editor/src/automerge/automerge-plugin/PatchSemaphore.ts
844
+ var PatchSemaphore = class {
845
+ constructor(field) {
846
+ this._inReconcile = false;
847
+ this._queue = [];
848
+ this.reconcile = (handle, view) => {
849
+ if (this._inReconcile) {
850
+ return;
851
+ }
852
+ this._inReconcile = true;
853
+ const path = getPath(view.state, this._field);
854
+ const oldHeads = getLastHeads(view.state, this._field);
855
+ let selection = view.state.selection;
856
+ const transactions = view.state.field(this._field).unreconciledTransactions.filter((tx) => !isReconcileTx(tx));
857
+ const toInvert = transactions.slice().reverse();
858
+ for (const tx of toInvert) {
859
+ const inverted = tx.changes.invert(tx.startState.doc);
860
+ selection = selection.map(inverted);
861
+ view.dispatch({
862
+ changes: inverted,
863
+ annotations: reconcileAnnotationType.of(true)
864
+ });
865
+ }
866
+ let newHeads = codeMirrorToAm_default(this._field, handle, transactions, view.state);
867
+ if (newHeads === null || newHeads === void 0) {
868
+ newHeads = automerge.getHeads(handle.docSync());
869
+ }
870
+ const diff = automerge.equals(oldHeads, newHeads) ? [] : automerge.diff(handle.docSync(), oldHeads, newHeads);
871
+ amToCodemirror_default(view, selection, path, diff);
872
+ view.dispatch({
873
+ effects: updateHeads(newHeads),
874
+ annotations: reconcileAnnotationType.of({})
875
+ });
876
+ this._inReconcile = false;
877
+ };
878
+ if (field !== void 0) {
879
+ this._field = field;
880
+ }
881
+ }
882
+ };
883
+
884
+ // packages/ui/react-ui-editor/src/automerge/automerge-plugin/plugin.ts
885
+ var effectType = StateEffect.define({});
886
+ var updateHeads = (newHeads) => effectType.of({
887
+ newHeads
888
+ });
889
+ var getLastHeads = (state, field) => state.field(field).lastHeads;
890
+ var getPath = (state, field) => state.field(field).path;
891
+ var semaphoreFacet = Facet.define({
892
+ combine: (values) => values.at(-1)
893
+ });
894
+ var automergePlugin = (handle, path) => {
895
+ const stateField = StateField3.define({
896
+ create: () => ({
897
+ lastHeads: automerge2.getHeads(handle.docSync()),
898
+ unreconciledTransactions: [],
899
+ path: path.slice()
900
+ }),
901
+ update: (value, tr) => {
902
+ const result = {
903
+ lastHeads: value.lastHeads,
904
+ unreconciledTransactions: value.unreconciledTransactions.slice(),
905
+ path: path.slice()
906
+ };
907
+ let clearUnreconciled = false;
908
+ for (const effect of tr.effects) {
909
+ if (effect.is(effectType)) {
910
+ result.lastHeads = effect.value.newHeads;
911
+ clearUnreconciled = true;
912
+ }
913
+ }
914
+ if (clearUnreconciled) {
915
+ result.unreconciledTransactions = [];
916
+ } else {
917
+ if (!isReconcileTx(tr)) {
918
+ result.unreconciledTransactions.push(tr);
919
+ }
920
+ }
921
+ return result;
922
+ }
923
+ });
924
+ const semaphore = new PatchSemaphore(stateField);
925
+ const viewPlugin = ViewPlugin2.fromClass(class AutomergeCodemirrorViewPlugin {
926
+ constructor(view) {
927
+ this._handleChange = () => {
928
+ this._view.state.facet(semaphoreFacet).reconcile(handle, this._view);
929
+ };
930
+ this._view = view;
931
+ handle.addListener("change", this._handleChange);
932
+ }
933
+ update(update2) {
934
+ if (update2.transactions.length > 0 && update2.transactions.some((t) => !isReconcileTx(t))) {
935
+ queueMicrotask(() => {
936
+ this._view.state.facet(semaphoreFacet).reconcile(handle, this._view);
937
+ });
938
+ }
939
+ }
940
+ destroy() {
941
+ handle.addListener("change", this._handleChange);
942
+ }
943
+ });
944
+ return {
945
+ extension: [
946
+ stateField,
947
+ semaphoreFacet.of(semaphore),
948
+ viewPlugin
949
+ ]
950
+ };
951
+ };
952
+ var reconcileAnnotationType = Annotation.define();
953
+ var isReconcileTx = (tr) => !!tr.annotation(reconcileAnnotationType);
954
+
955
+ // packages/ui/react-ui-editor/src/hooks/useCollaboration.ts
956
+ var useCollaboration = (model, themeMode) => {
957
+ const { provider, peer, content } = model;
958
+ const extension = useMemo(() => {
959
+ if (content instanceof YText) {
960
+ return yCollab(content, provider?.awareness);
961
+ } else if (isDocAccessor(content)) {
962
+ return automergePlugin(content.handle, content.path);
963
+ } else {
964
+ return void 0;
965
+ }
966
+ }, []);
967
+ useEffect(() => {
968
+ if (provider && peer) {
969
+ provider.awareness.setLocalStateField("user", {
970
+ name: peer.name ?? generateName(peer.id),
971
+ color: getColorForValue({
972
+ value: peer.id,
973
+ type: "color"
974
+ }),
975
+ colorLight: getColorForValue({
976
+ value: peer.id,
977
+ themeMode,
978
+ type: "highlight"
979
+ })
980
+ });
981
+ }
982
+ }, [
983
+ provider,
984
+ peer,
985
+ themeMode
986
+ ]);
987
+ return extension;
988
+ };
989
+
990
+ // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
991
+ import { useMemo as useMemo2 } from "react";
992
+ import { getRawDoc, isActualAutomergeObject } from "@dxos/react-client/echo";
993
+
994
+ // packages/ui/react-ui-editor/src/hooks/yjs/cursors.ts
995
+ import * as random from "lib0/random";
996
+ var cursorColors = [
997
+ {
998
+ color: "#30bced",
999
+ light: "#30bced33"
1000
+ },
1001
+ {
1002
+ color: "#6eeb83",
1003
+ light: "#6eeb8333"
1004
+ },
1005
+ {
1006
+ color: "#ffbc42",
1007
+ light: "#ffbc4233"
1008
+ },
1009
+ {
1010
+ color: "#ecd444",
1011
+ light: "#ecd44433"
1012
+ },
1013
+ {
1014
+ color: "#ee6352",
1015
+ light: "#ee635233"
1016
+ },
1017
+ {
1018
+ color: "#9ac2c9",
1019
+ light: "#9ac2c933"
1020
+ },
1021
+ {
1022
+ color: "#8acb88",
1023
+ light: "#8acb8833"
1024
+ },
1025
+ {
1026
+ color: "#1be7ff",
1027
+ light: "#1be7ff33"
1028
+ }
1029
+ ];
1030
+ var cursorColor = cursorColors[random.uint32() % cursorColors.length];
1031
+
1032
+ // packages/ui/react-ui-editor/src/hooks/yjs/space-provider.ts
1033
+ import * as decoding from "lib0/decoding";
1034
+ import * as encoding from "lib0/encoding";
1035
+ import { Observable } from "lib0/observable";
1036
+ import * as awarenessProtocol from "y-protocols/awareness";
1037
+ import { log } from "@dxos/log";
1038
+ var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/yjs/space-provider.ts";
1039
+ var messageAwareness = 1;
1040
+ var messageQueryAwareness = 3;
1041
+ var SpaceAwarenessProvider = class extends Observable {
1042
+ constructor({ space, doc, channel, awareness }) {
1043
+ super();
1044
+ this._space = space;
1045
+ this._awareness = awareness ?? new awarenessProtocol.Awareness(doc);
1046
+ this._channel = channel;
1047
+ this._clientId = doc.clientID;
1048
+ this._awareness.on("update", this._handleAwarenessUpdate.bind(this));
1049
+ this._space.listen(this._channel, this._handleSpaceMessage.bind(this));
1050
+ if (typeof window !== "undefined") {
1051
+ window.addEventListener("beforeunload", this._handleBeforeUnload.bind(this));
1052
+ } else if (typeof process !== "undefined") {
1053
+ process.on("exit", this._handleBeforeUnload.bind(this));
1054
+ }
1055
+ const encoderAwarenessQuery = encoding.createEncoder();
1056
+ encoding.writeVarUint(encoderAwarenessQuery, messageQueryAwareness);
1057
+ void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwarenessQuery));
1058
+ const encoderAwarenessState = encoding.createEncoder();
1059
+ encoding.writeVarUint(encoderAwarenessState, messageAwareness);
1060
+ encoding.writeVarUint8Array(encoderAwarenessState, awarenessProtocol.encodeAwarenessUpdate(this.awareness, [
1061
+ this._clientId
1062
+ ]));
1063
+ void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwarenessState));
1064
+ }
1065
+ get awareness() {
1066
+ return this._awareness;
1067
+ }
1068
+ _handleAwarenessUpdate({ added, updated, removed }, origin) {
1069
+ log("awareness update", {
1070
+ added,
1071
+ updated,
1072
+ removed,
1073
+ origin
1074
+ }, {
1075
+ F: __dxlog_file,
1076
+ L: 67,
1077
+ S: this,
1078
+ C: (f, a) => f(...a)
1079
+ });
1080
+ const changedClients = added.concat(updated).concat(removed);
1081
+ const encoderAwareness = encoding.createEncoder();
1082
+ encoding.writeVarUint(encoderAwareness, messageAwareness);
1083
+ encoding.writeVarUint8Array(encoderAwareness, awarenessProtocol.encodeAwarenessUpdate(this._awareness, changedClients));
1084
+ void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwareness));
1085
+ }
1086
+ _handleSpaceMessage({ payload }) {
1087
+ log("space message", payload, {
1088
+ F: __dxlog_file,
1089
+ L: 79,
1090
+ S: this,
1091
+ C: (f, a) => f(...a)
1092
+ });
1093
+ const data = new Uint8Array(Array.from(Object.values(payload)));
1094
+ const encoder = this._readMessage(data);
1095
+ if (encoder) {
1096
+ void this._space.postMessage(this._channel, encoding.toUint8Array(encoder));
1097
+ }
1098
+ }
1099
+ _readMessage(message) {
1100
+ const decoder = decoding.createDecoder(message);
1101
+ const encoder = encoding.createEncoder();
1102
+ const messageType = decoding.readVarUint(decoder);
1103
+ let sendReply = false;
1104
+ switch (messageType) {
1105
+ case messageAwareness: {
1106
+ awarenessProtocol.applyAwarenessUpdate(this._awareness, decoding.readVarUint8Array(decoder), this);
1107
+ break;
1108
+ }
1109
+ case messageQueryAwareness: {
1110
+ encoding.writeVarUint(encoder, messageAwareness);
1111
+ encoding.writeVarUint8Array(encoder, awarenessProtocol.encodeAwarenessUpdate(this._awareness, Array.from(this._awareness.getStates().keys())));
1112
+ sendReply = true;
1113
+ break;
1114
+ }
1115
+ default: {
1116
+ console.error("Invalid message:", messageType);
1117
+ return encoder;
1118
+ }
1119
+ }
1120
+ if (!sendReply) {
1121
+ return null;
1122
+ }
1123
+ return encoder;
1124
+ }
1125
+ _handleBeforeUnload() {
1126
+ awarenessProtocol.removeAwarenessStates(this._awareness, [
1127
+ this._clientId
1128
+ ], "window unload");
1129
+ }
1130
+ };
1131
+
1132
+ // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
1133
+ var useTextModel = ({ identity, space, text }) => {
1134
+ const provider = useMemo2(() => {
1135
+ if (isActualAutomergeObject(text)) {
1136
+ return void 0;
1137
+ }
1138
+ if (!space || !text?.doc) {
1139
+ return void 0;
1140
+ }
1141
+ return new SpaceAwarenessProvider({
1142
+ space,
1143
+ doc: text.doc,
1144
+ channel: `yjs.awareness.${text.id}`
1145
+ });
1146
+ }, [
1147
+ identity,
1148
+ space,
1149
+ text,
1150
+ text?.doc
1151
+ ]);
1152
+ const content = useMemo2(() => {
1153
+ if (isActualAutomergeObject(text)) {
1154
+ const obj = text;
1155
+ return getRawDoc(obj, [
1156
+ obj.field
1157
+ ]);
1158
+ } else {
1159
+ return text?.content;
1160
+ }
1161
+ }, [
1162
+ text
1163
+ ]);
1164
+ if (isActualAutomergeObject(text)) {
1165
+ const obj = text;
1166
+ return {
1167
+ id: obj.id,
1168
+ content,
1169
+ provider: void 0,
1170
+ peer: identity ? {
1171
+ id: identity.identityKey.toHex(),
1172
+ name: identity.profile?.displayName
1173
+ } : void 0
1174
+ };
1175
+ }
1176
+ if (!text?.doc || !text?.content) {
1177
+ return void 0;
1178
+ }
1179
+ return {
1180
+ id: text.doc.guid,
1181
+ content: text.content,
1182
+ provider,
1183
+ peer: identity ? {
1184
+ id: identity.identityKey.toHex(),
1185
+ name: identity.profile?.displayName
1186
+ } : void 0
1187
+ };
1188
+ };
1189
+
1190
+ // packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx
541
1191
  var EditorModes = [
542
1192
  "default",
543
1193
  "vim"
544
1194
  ];
545
- var MarkdownEditor = /* @__PURE__ */ forwardRef(({ model, slots = {}, editorMode, extensions = [], onChange }, forwardedRef) => {
546
- const { id, content, provider, peer } = model ?? {};
1195
+ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, extensions = [], slots, editorMode }, forwardedRef) => {
547
1196
  const { themeMode } = useThemeContext();
548
1197
  const tabsterDOMAttribute = useFocusableGroup({
549
1198
  tabBehavior: "limited"
@@ -555,110 +1204,33 @@ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ model, slots = {}, editorMode
555
1204
  editor: parent,
556
1205
  state,
557
1206
  view
558
- }));
559
- const listenChangesExtension = useMemo2(() => StateField.define({
560
- create: () => null,
561
- update: (_value, transaction) => {
562
- if (transaction.docChanged && onChange) {
563
- onChange(transaction.newDoc);
564
- }
565
- return null;
566
- }
567
1207
  }), [
568
- onChange
569
- ]);
570
- useEffect(() => {
571
- if (provider && peer) {
572
- provider.awareness.setLocalStateField("user", {
573
- name: peer.name ?? generateName(peer.id),
574
- color: getColorForValue({
575
- value: peer.id,
576
- type: "color"
577
- }),
578
- colorLight: getColorForValue({
579
- value: peer.id,
580
- themeMode,
581
- type: "highlight"
582
- })
583
- });
584
- }
585
- }, [
586
- provider,
587
- peer,
588
- themeMode
1208
+ view,
1209
+ state,
1210
+ parent
589
1211
  ]);
590
- useEffect(() => {
1212
+ const collaboration = useCollaboration(model, themeMode);
1213
+ useEffect2(() => {
591
1214
  if (!parent) {
592
1215
  return;
593
1216
  }
594
- const state2 = EditorState.create({
595
- doc: content?.toString(),
1217
+ const initialText = isDocAccessor2(model.content) ? get3(model.content.handle.docSync(), model.content.path) : model.content?.toString();
1218
+ const state2 = EditorState2.create({
1219
+ doc: initialText,
596
1220
  extensions: [
597
- // Based on https://github.com/codemirror/dev/issues/44#issuecomment-789093799.
598
- listenChangesExtension,
599
- ...editorMode === "vim" ? [
600
- vim()
601
- ] : [],
602
- // All of https://github.com/codemirror/basic-setup minus line numbers and fold gutter.
603
- highlightActiveLineGutter(),
604
- highlightSpecialChars(),
605
- history(),
606
- drawSelection(),
607
- dropCursor(),
608
- EditorState.allowMultipleSelections.of(true),
609
- indentOnInput(),
610
- syntaxHighlighting(defaultHighlightStyle, {
611
- fallback: true
612
- }),
613
- bracketMatching(),
614
- closeBrackets(),
615
- autocompletion(),
616
- rectangularSelection(),
617
- crosshairCursor(),
618
- highlightActiveLine(),
619
- highlightSelectionMatches(),
620
- placeholder(slots.editor?.placeholder ?? ""),
621
- keymap.of([
622
- ...closeBracketsKeymap,
623
- ...defaultKeymap,
624
- ...searchKeymap,
625
- ...historyKeymap,
626
- ...foldKeymap,
627
- ...completionKeymap,
628
- ...lintKeymap,
629
- indentWithTab
630
- ]),
631
- EditorView.lineWrapping,
632
- // Themes.
633
- markdown({
634
- base: markdownLanguage2,
635
- codeLanguages: languages,
636
- extensions: [
637
- markdownTagsExtension
638
- ]
639
- }),
640
- EditorView.theme({
641
- ...markdownTheme,
642
- ...slots.editor?.markdownTheme
643
- }),
644
- ...themeMode === "dark" ? [
645
- syntaxHighlighting(oneDarkHighlightStyle)
646
- ] : [
647
- syntaxHighlighting(defaultHighlightStyle)
648
- ],
649
- // TODO(thure): All but one rule here apply to both themes; rename or refactor.
650
- syntaxHighlighting(markdownDarkHighlighting),
651
- // Replication and awareness (incl. remote selection).
652
- ...content instanceof YText ? [
653
- yCollab(content, provider?.awareness)
654
- ] : [],
1221
+ // TODO(burdon): Factor out VIM mode?
1222
+ editorMode === "vim" && vim(),
1223
+ // Replication.
1224
+ collaboration,
1225
+ // Theme.
1226
+ slots?.editor?.theme && EditorView4.theme(slots?.editor?.theme),
655
1227
  // Custom.
656
1228
  ...extensions
657
- ]
1229
+ ].filter(Boolean)
658
1230
  });
659
1231
  setState(state2);
660
1232
  view?.destroy();
661
- setView(new EditorView({
1233
+ setView(new EditorView4({
662
1234
  state: state2,
663
1235
  parent
664
1236
  }));
@@ -669,8 +1241,7 @@ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ model, slots = {}, editorMode
669
1241
  };
670
1242
  }, [
671
1243
  parent,
672
- content,
673
- provider?.awareness,
1244
+ model.content,
674
1245
  themeMode,
675
1246
  editorMode
676
1247
  ]);
@@ -691,461 +1262,89 @@ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ model, slots = {}, editorMode
691
1262
  editorMode
692
1263
  ]);
693
1264
  return /* @__PURE__ */ React.createElement("div", {
694
- tabIndex: 0,
1265
+ key: model.id,
695
1266
  ref: setParent,
696
- key: id,
697
- ...slots.root,
698
- ...editorMode !== "vim" ? tabsterDOMAttribute : {},
1267
+ tabIndex: 0,
1268
+ ...slots?.root,
1269
+ ...editorMode !== "vim" && tabsterDOMAttribute,
699
1270
  onKeyUp: handleKeyUp
700
1271
  });
701
1272
  });
702
-
703
- // packages/ui/react-ui-editor/src/components/RichText/RichText.tsx
704
- import { mergeAttributes } from "@tiptap/core";
705
- import Collaboration from "@tiptap/extension-collaboration";
706
- import CollaborationCursor from "@tiptap/extension-collaboration-cursor";
707
- import Heading from "@tiptap/extension-heading";
708
- import ListItem from "@tiptap/extension-list-item";
709
- import Placeholder from "@tiptap/extension-placeholder";
710
- import { EditorContent, useEditor as useNaturalEditor } from "@tiptap/react";
711
- import StarterKit from "@tiptap/starter-kit";
712
- import React2, { forwardRef as forwardRef2, useImperativeHandle as useImperativeHandle2, useMemo as useMemo3 } from "react";
713
- import { generateName as generateName2 } from "@dxos/display-name";
714
- import { mx as mx2 } from "@dxos/react-ui-theme";
715
- var useEditor = ({ model, placeholder: placeholder3 = "Enter text\u2026", slots = {} }) => {
716
- const extensions = useMemo3(() => [
717
- StarterKit.configure({
718
- // Extensions
719
- history: false,
720
- // Nodes
721
- blockquote: {
722
- HTMLAttributes: {
723
- class: blockquote
724
- }
725
- },
726
- bulletList: {
727
- HTMLAttributes: {
728
- class: unorderedList
729
- }
730
- },
731
- codeBlock: {
732
- HTMLAttributes: {
733
- class: codeBlock
734
- }
735
- },
736
- heading: false,
737
- horizontalRule: {
738
- HTMLAttributes: {
739
- class: horizontalRule
740
- }
741
- },
742
- listItem: false,
743
- orderedList: {
744
- HTMLAttributes: {
745
- class: orderedList
746
- }
747
- },
748
- paragraph: {
749
- HTMLAttributes: {
750
- class: paragraph
751
- }
752
- },
753
- // Marks
754
- bold: {
755
- HTMLAttributes: {
756
- class: bold
757
- }
758
- },
759
- code: {
760
- HTMLAttributes: {
761
- class: codeWithoutMarks
762
- }
763
- },
764
- italic: {
765
- HTMLAttributes: {
766
- class: italic
767
- }
768
- },
769
- strike: {
770
- HTMLAttributes: {
771
- class: strikethrough
772
- }
773
- }
774
- }),
775
- Heading.extend({
776
- renderHTML({ node, HTMLAttributes }) {
777
- const hasLevel = this.options.levels.includes(node.attrs.level);
778
- const level = hasLevel ? node.attrs.level : this.options.levels[0];
779
- return [
780
- `h${level}`,
781
- mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {
782
- class: heading[level]
783
- }),
784
- 0
785
- ];
786
- }
787
- }),
788
- ListItem.extend({
789
- renderHTML: ({ HTMLAttributes }) => [
790
- "li",
791
- mergeAttributes(HTMLAttributes, {
792
- marker: "\u2022 ",
793
- class: listItem
794
- }),
795
- [
796
- "div",
797
- {
798
- role: "none"
799
- },
800
- 0
801
- ]
802
- ]
803
- }),
804
- // https://github.com/ueberdosis/tiptap/tree/main/packages/extension-collaboration
805
- ...model && typeof model.content !== "string" ? [
806
- Collaboration.configure({
807
- fragment: model.content
808
- })
809
- ] : [],
810
- ...model?.provider ? [
811
- CollaborationCursor.configure({
812
- provider: model.provider,
813
- user: model.peer && {
814
- name: model.peer.name ?? generateName2(model.peer.id),
815
- color: cursorColor.color
816
- }
817
- })
818
- ] : [],
819
- Placeholder.configure({
820
- placeholder: placeholder3,
821
- emptyEditorClass: "before:content-[attr(data-placeholder)] before:absolute opacity-50 cursor-text"
1273
+ var TextEditor = /* @__PURE__ */ forwardRef(({ extensions: _extensions, slots, ...props }, forwardedRef) => {
1274
+ const { themeMode } = useThemeContext();
1275
+ const extensions = [
1276
+ ..._extensions ?? [],
1277
+ basicBundle({
1278
+ themeMode,
1279
+ placeholder: slots?.editor?.placeholder
822
1280
  })
823
- ], [
824
- model?.id
825
- ]);
826
- return useNaturalEditor({
1281
+ ];
1282
+ return /* @__PURE__ */ React.createElement(BaseTextEditor, {
1283
+ ref: forwardedRef,
827
1284
  extensions,
828
- editorProps: {
829
- attributes: {
830
- class: mx2("focus:outline-none focus-visible:outline-none", slots.editor?.className),
831
- spellcheck: slots.editor?.spellCheck === false ? "false" : "true",
832
- tabindex: slots.editor?.tabIndex ? String(slots.editor?.tabIndex) : "0"
1285
+ slots: defaultsDeep({}, slots, {
1286
+ editor: {
1287
+ theme: basicTheme
833
1288
  }
834
- }
835
- }, [
836
- extensions
837
- ]);
838
- };
839
- var RichTextEditor = /* @__PURE__ */ forwardRef2((props, ref) => {
840
- const editor = useEditor(props);
841
- useImperativeHandle2(ref, () => editor, [
842
- editor
843
- ]);
844
- return /* @__PURE__ */ React2.createElement(EditorContent, {
845
- ...props.slots?.root,
846
- editor
847
- });
848
- });
849
-
850
- // packages/ui/react-ui-editor/src/components/Editor/Editor.tsx
851
- var Editor = /* @__PURE__ */ memo(/* @__PURE__ */ forwardRef3(({ slots, ...params }, forwardedRef) => {
852
- const model = useTextModel(params);
853
- if (model?.content instanceof YXmlFragment) {
854
- return /* @__PURE__ */ React3.createElement(RichTextEditor, {
855
- ref: forwardedRef,
856
- model,
857
- slots
858
- });
859
- } else {
860
- return /* @__PURE__ */ React3.createElement(MarkdownEditor, {
861
- ref: forwardedRef,
862
- model,
863
- slots
864
- });
865
- }
866
- }));
867
-
868
- // packages/ui/react-ui-editor/src/components/TextEditor/index.ts
869
- import { tags as tags2 } from "@lezer/highlight";
870
-
871
- // packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx
872
- import { closeBrackets as closeBrackets2 } from "@codemirror/autocomplete";
873
- import { bracketMatching as bracketMatching2, defaultHighlightStyle as defaultHighlightStyle2, syntaxHighlighting as syntaxHighlighting2 } from "@codemirror/language";
874
- import { EditorState as EditorState2 } from "@codemirror/state";
875
- import { oneDarkHighlightStyle as oneDarkHighlightStyle2 } from "@codemirror/theme-one-dark";
876
- import { EditorView as EditorView2, placeholder as placeholder2 } from "@codemirror/view";
877
- import React4, { forwardRef as forwardRef4, useEffect as useEffect2, useImperativeHandle as useImperativeHandle3, useState as useState2, useCallback as useCallback2 } from "react";
878
- import { yCollab as yCollab2 } from "y-codemirror.next";
879
- import { useThemeContext as useThemeContext2 } from "@dxos/react-ui";
880
- import { YText as YText2 } from "@dxos/text-model";
881
-
882
- // packages/ui/react-ui-editor/src/components/TextEditor/theme.ts
883
- import get2 from "lodash.get";
884
- import { tailwindConfig as tailwindConfig2 } from "@dxos/react-ui-theme";
885
- var tokens2 = tailwindConfig2({}).theme;
886
- var defaultStyles = {
887
- "&.cm-focused": {
888
- outline: "none"
889
- },
890
- ".cm-placeholder": {
891
- fontFamily: get2(tokens2, "fontFamily.body", []).join(",")
892
- },
893
- "& .cm-scroller": {
894
- fontFamily: get2(tokens2, "fontFamily.body", []).join(","),
895
- overflow: "visible"
896
- },
897
- ".cm-tooltip": {
898
- backgroundColor: "transparent",
899
- border: "none"
900
- }
901
- };
902
-
903
- // packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx
904
- var TextEditor = /* @__PURE__ */ forwardRef4(({ model, extensions = [], theme = defaultStyles, slots = {}, onKeyDown, ...props }, forwardedRef) => {
905
- const { id, content } = model ?? {};
906
- const { themeMode } = useThemeContext2();
907
- const [parent, setParent] = useState2(null);
908
- const [state, setState] = useState2();
909
- const [view, setView] = useState2();
910
- useImperativeHandle3(forwardedRef, () => {
911
- return {
912
- editor: parent,
913
- state,
914
- view
915
- };
916
- }, [
917
- view
918
- ]);
919
- useEffect2(() => {
920
- if (!parent) {
921
- return;
922
- }
923
- view?.destroy();
924
- const state2 = EditorState2.create({
925
- doc: content?.toString(),
926
- extensions: [
927
- bracketMatching2(),
928
- closeBrackets2(),
929
- placeholder2(slots.editor?.placeholder ?? ""),
930
- EditorView2.lineWrapping,
931
- // Themes.
932
- EditorView2.theme(theme),
933
- ...themeMode === "dark" ? [
934
- syntaxHighlighting2(oneDarkHighlightStyle2)
935
- ] : [
936
- syntaxHighlighting2(defaultHighlightStyle2)
937
- ],
938
- // Replication.
939
- ...content instanceof YText2 ? [
940
- yCollab2(content, void 0)
941
- ] : [],
942
- // Custom.
943
- ...extensions
944
- ]
945
- });
946
- setState(state2);
947
- setView(new EditorView2({
948
- state: state2,
949
- parent
950
- }));
951
- return () => {
952
- view?.destroy();
953
- setView(void 0);
954
- setState(void 0);
955
- };
956
- }, [
957
- parent,
958
- content,
959
- themeMode
960
- ]);
961
- const handleKeyDown = useCallback2((event) => {
962
- if (view) {
963
- const { head, from, to } = view.state.selection.ranges[0];
964
- const { number } = view.state.doc.lineAt(head);
965
- const after = view.state.sliceDoc(from);
966
- onKeyDown?.(event, {
967
- from,
968
- to,
969
- line: number,
970
- lines: view.state.doc.lines,
971
- after
972
- });
973
- }
974
- }, [
975
- view
976
- ]);
977
- return /* @__PURE__ */ React4.createElement("div", {
978
- key: id,
979
- ref: setParent,
980
- ...slots.root,
981
- onKeyDown: handleKeyDown,
1289
+ }),
982
1290
  ...props
983
1291
  });
984
1292
  });
985
-
986
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/hyperlink.tsx
987
- import { Decoration, EditorView as EditorView3, MatchDecorator, ViewPlugin } from "@codemirror/view";
988
- var markdownLinkRegexp = /\[([^\]]+)]\(([^)]+)\)(!?)/gi;
989
- var linkDecorator = () => new MatchDecorator({
990
- regexp: markdownLinkRegexp,
991
- decorate: (add, from, to, match, view) => {
992
- const [_, label, url] = match;
993
- const p0 = {
994
- start: from,
995
- end: from + 1
996
- };
997
- const p1 = {
998
- start: from + 1,
999
- end: from + 1 + label.length
1000
- };
1001
- const p2 = {
1002
- start: p1.end,
1003
- end: p1.end + 1
1004
- };
1005
- const p3 = {
1006
- start: p1.end + 1,
1007
- end: p1.end + 1 + url.length + 2
1008
- };
1009
- add(p0.start, p0.end, Decoration.mark({
1010
- class: "cm-hyperlink-bracket"
1011
- }));
1012
- add(p1.start, p1.end, Decoration.mark({
1013
- class: "cm-hyperlink-label"
1014
- }));
1015
- add(p2.start, p2.end, Decoration.mark({
1016
- class: "cm-hyperlink-bracket"
1017
- }));
1018
- add(p3.start, p3.end, Decoration.mark({
1019
- class: "cm-hyperlink-url",
1020
- __attributes: {
1021
- style: "display: none"
1022
- }
1023
- }));
1024
- }
1025
- });
1026
- function hyperLinkExtension() {
1027
- return ViewPlugin.fromClass(class HyperLinkView {
1028
- constructor(view) {
1029
- this.decorator = linkDecorator();
1030
- this.decorations = this.decorator.createDeco(view);
1031
- }
1032
- update(update) {
1033
- if (update.docChanged || update.viewportChanged) {
1034
- this.decorations = this.decorator.updateDeco(update, this.decorations);
1035
- }
1036
- }
1037
- }, {
1038
- decorations: (view) => view.decorations,
1039
- eventHandlers: {
1040
- // TODO(burdon): CLick to expand link.
1041
- // https://discuss.codemirror.net/t/decorator-iteration-on-click-event/4226/2
1042
- mousedown: function(event, view) {
1043
- const pos = view.posAtDOM(event.target);
1044
- if (pos !== 0) {
1045
- console.log(this);
1046
- }
1047
- },
1048
- // TODO(burdon): Hide when cursor moves away.
1049
- keydown: (event, view) => {
1050
- view.update([]);
1293
+ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ extensions: _extensions, slots, ...props }, forwardedRef) => {
1294
+ const { themeMode } = useThemeContext();
1295
+ const extensions = [
1296
+ ..._extensions ?? [],
1297
+ markdownBundle({
1298
+ themeMode
1299
+ })
1300
+ ];
1301
+ return /* @__PURE__ */ React.createElement(BaseTextEditor, {
1302
+ ref: forwardedRef,
1303
+ extensions,
1304
+ slots: defaultsDeep({}, slots, {
1305
+ editor: {
1306
+ theme: markdownTheme
1051
1307
  }
1052
- }
1308
+ }),
1309
+ ...props
1053
1310
  });
1054
- }
1055
- var hyperLinkStyle = EditorView3.baseTheme({
1056
- ".cm-hyperlink-label": {
1057
- cursor: "pointer",
1058
- textDecoration: "underline"
1059
- },
1060
- ".cm-hyperlink-bracket": {
1061
- opacity: 0.2
1062
- },
1063
- ".cm-hyperlink-url": {
1064
- opacity: 0.5,
1065
- fontFamily: "monospace"
1066
- }
1067
- });
1068
- var hyperlink = [
1069
- hyperLinkExtension(),
1070
- hyperLinkStyle
1071
- ];
1072
-
1073
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/hyperlinkTooltip.tsx
1074
- import { hoverTooltip } from "@codemirror/view";
1075
- import { ArrowCircleUp } from "@phosphor-icons/react";
1076
- import React5, { StrictMode } from "react";
1077
- import { createRoot } from "react-dom/client";
1078
- import { getSize, mx as mx3, tooltipContent } from "@dxos/react-ui-theme";
1079
- var markdownLinkRegexp2 = /\[([^\]]+)]\(([^)]+)\)/;
1080
- var createHyperlinkTooltip = (onHover, regexp = markdownLinkRegexp2) => hoverTooltip((view, pos) => {
1081
- const { from, text } = view.state.doc.lineAt(pos);
1082
- const p = pos - from;
1083
- let idx = 0;
1084
- let match;
1085
- do {
1086
- match = text.substring(idx).match(regexp);
1087
- if (!match) {
1088
- match = void 0;
1089
- break;
1090
- }
1091
- idx = text.indexOf(match[0]);
1092
- if (p >= idx && p < idx + match[0].length) {
1093
- break;
1094
- }
1095
- idx += match[0].length;
1096
- } while (true);
1097
- if (!match) {
1098
- return null;
1099
- }
1100
- const [, , url] = match ?? [];
1101
- return {
1102
- pos: idx + from,
1103
- end: idx + from + match[0].length,
1104
- above: true,
1105
- create: () => {
1106
- const el = document.createElement("a");
1107
- el.innerText = "_";
1108
- el.className = tooltipContent({}, "mb-2 p-1");
1109
- el.setAttribute("target", "_blank");
1110
- el.setAttribute("href", url);
1111
- onHover(el, url);
1112
- return {
1113
- dom: el
1114
- };
1115
- }
1116
- };
1117
1311
  });
1118
- var defaultHyperLinkTooltip = createHyperlinkTooltip((el, url) => {
1119
- const web = new URL(url);
1120
- createRoot(el).render(/* @__PURE__ */ React5.createElement(StrictMode, null, /* @__PURE__ */ React5.createElement("div", {
1121
- className: "flex gap-1 items-center"
1122
- }, /* @__PURE__ */ React5.createElement(ArrowCircleUp, {
1123
- className: mx3(getSize(6), "text-blue-500")
1124
- }), /* @__PURE__ */ React5.createElement("p", {
1125
- className: "pr-1"
1126
- }, web.origin))));
1127
- });
1128
-
1129
- // packages/ui/react-ui-editor/src/index.ts
1130
- import { TextKind } from "@dxos/protocols/proto/dxos/echo/model/text";
1131
- import { Doc, YText as YText3, YXmlFragment as YXmlFragment2 } from "@dxos/text-model";
1132
1312
  export {
1313
+ BaseTextEditor,
1133
1314
  Doc,
1134
- Editor,
1135
1315
  EditorModes,
1136
1316
  MarkdownEditor,
1137
- RichTextEditor,
1317
+ SpaceAwarenessProvider,
1138
1318
  TextEditor,
1139
1319
  TextKind,
1140
- YText3 as YText,
1141
- YXmlFragment2 as YXmlFragment,
1320
+ YText2 as YText,
1321
+ YXmlFragment,
1322
+ basicBundle,
1323
+ basicTheme,
1324
+ chalky,
1325
+ coral,
1142
1326
  createHyperlinkTooltip,
1143
- defaultHyperLinkTooltip,
1144
- defaultStyles,
1327
+ cursorColor,
1328
+ cyan,
1329
+ highlightBackground,
1145
1330
  hyperLinkExtension,
1146
- hyperLinkStyle,
1147
- hyperlink,
1331
+ hyperlinkDecoration,
1332
+ hyperlinkStyle,
1333
+ hyperlinkWidget,
1334
+ invalid,
1335
+ ivory,
1336
+ malibu,
1337
+ markdownBundle,
1338
+ markdownHighlightStyle,
1339
+ markdownTheme,
1340
+ onChangeExtension,
1341
+ sage,
1342
+ stone,
1148
1343
  tags2 as tags,
1149
- useTextModel
1344
+ tooltipBackground,
1345
+ useCollaboration,
1346
+ useTextModel,
1347
+ violet,
1348
+ whiskey
1150
1349
  };
1151
1350
  //# sourceMappingURL=index.mjs.map