@dxos/react-ui-editor 0.3.9-next.7522f7a → 0.3.9

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