@dxos/react-ui-editor 0.3.9-main.9c71aa7 → 0.3.9-main.9da1d41

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 (147) hide show
  1. package/dist/lib/browser/index.mjs +674 -613
  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 +1 -0
  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 +3 -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/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/hyperlink/hyperlinkTooltip.d.ts +8 -0
  33. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkTooltip.d.ts.map +1 -0
  34. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkWidget.d.ts +10 -0
  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 +5 -0
  39. package/dist/types/src/components/TextEditor/extensions/index.d.ts.map +1 -0
  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 -2
  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} +1 -13
  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 +32 -26
  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 +31 -46
  76. package/src/components/TextEditor/TextEditor.stories.tsx +51 -7
  77. package/src/components/TextEditor/TextEditor.tsx +108 -56
  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/hyperlink/hyperlinkTooltip.tsx +61 -0
  85. package/src/components/TextEditor/extensions/hyperlink/hyperlinkWidget.tsx +119 -0
  86. package/src/components/TextEditor/extensions/hyperlink/index.ts +7 -0
  87. package/src/components/TextEditor/extensions/index.ts +8 -0
  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} +42 -27
  91. package/src/components/TextEditor/index.ts +1 -2
  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 +35 -0
  96. package/src/{model.ts → hooks/useTextModel.ts} +4 -14
  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 -28
  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 -19
  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 -19
  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 -107
  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 -26
  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/theme.d.ts +0 -8
  124. package/dist/types/src/components/TextEditor/theme.d.ts.map +0 -1
  125. package/dist/types/src/model.d.ts.map +0 -1
  126. package/dist/types/src/yjs/cursors.d.ts.map +0 -1
  127. package/dist/types/src/yjs/index.d.ts.map +0 -1
  128. package/dist/types/src/yjs/space-provider.d.ts.map +0 -1
  129. package/dist/types/stories/html/code-mirror.stories.d.ts.map +0 -1
  130. package/src/automerge/demo.ts +0 -106
  131. package/src/components/Editor/Editor.stories.tsx +0 -80
  132. package/src/components/Editor/Editor.tsx +0 -33
  133. package/src/components/Editor/index.ts +0 -5
  134. package/src/components/Markdown/Markdown.stories.tsx +0 -57
  135. package/src/components/Markdown/Markdown.tsx +0 -210
  136. package/src/components/Markdown/index.ts +0 -5
  137. package/src/components/RichText/RichText.stories.tsx +0 -68
  138. package/src/components/RichText/RichText.tsx +0 -181
  139. package/src/components/RichText/index.ts +0 -5
  140. package/src/components/TextEditor/theme.ts +0 -29
  141. /package/dist/types/src/{yjs → hooks/yjs}/cursors.d.ts +0 -0
  142. /package/dist/types/src/{yjs → hooks/yjs}/index.d.ts +0 -0
  143. /package/dist/types/src/{yjs → hooks/yjs}/space-provider.d.ts +0 -0
  144. /package/src/components/{Markdown/markdownTags.ts → TextEditor/extensions/markdown/tags.ts} +0 -0
  145. /package/src/{yjs → hooks/yjs}/cursors.ts +0 -0
  146. /package/src/{yjs → hooks/yjs}/index.ts +0 -0
  147. /package/src/{yjs → hooks/yjs}/space-provider.ts +0 -0
@@ -1,199 +1,305 @@
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 { 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);
20
+
21
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/basic/theme.ts
22
+ import get from "lodash.get";
23
+
24
+ // packages/ui/react-ui-editor/src/styles/markdown.ts
25
+ import { mx } from "@dxos/react-ui-theme";
26
+ var heading = {
27
+ 1: "mbs-4 mbe-2 text-4xl font-semibold text-inherit no-underline",
28
+ 2: "mbs-4 mbe-2 text-3xl font-bold text-inherit no-underline",
29
+ 3: "mbs-4 mbe-2 text-2xl font-bold text-inherit no-underline",
30
+ 4: "mbs-4 mbe-2 text-xl font-extrabold text-inherit no-underline",
31
+ 5: "mbs-4 mbe-2 text-lg font-extrabold text-inherit no-underline",
32
+ 6: "mbs-4 mbe-2 font-black text-inherit no-underline"
33
+ };
34
+ var bold = "font-bold";
35
+ var code = "font-mono bg-neutral-500/10 rounded pli-0.5 plb-0.5 -mlb-0.5";
36
+ var codeWithoutMarks = mx(code, "pli-1.5 mli-0.5");
37
+ var italic = "italic";
38
+ var strikethrough = "line-through";
39
+ var mark = "!font-normal !no-underline !text-inherit opacity-40";
40
+
41
+ // packages/ui/react-ui-editor/src/styles/tokens.ts
42
+ import { tailwindConfig } from "@dxos/react-ui-theme";
43
+ var tokens = tailwindConfig({}).theme;
44
+
45
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/basic/theme.ts
46
+ var basicTheme = {
47
+ "&.cm-focused": {
48
+ outline: "none"
14
49
  },
15
- {
16
- color: "#6eeb83",
17
- light: "#6eeb8333"
50
+ "& .cm-line": {
51
+ paddingInline: 0,
52
+ lineHeight: 1.6,
53
+ minBlockSize: "1.6em"
18
54
  },
19
- {
20
- color: "#ffbc42",
21
- light: "#ffbc4233"
55
+ "& .cm-line *": {
56
+ lineHeight: 1.6
22
57
  },
23
- {
24
- color: "#ecd444",
25
- light: "#ecd44433"
58
+ "& .cm-scroller": {
59
+ fontFamily: get(tokens, "fontFamily.body", []).join(","),
60
+ overflow: "visible"
26
61
  },
27
- {
28
- color: "#ee6352",
29
- light: "#ee635233"
62
+ "& .cm-content": {
63
+ padding: 0,
64
+ caretColor: "black"
30
65
  },
31
- {
32
- color: "#9ac2c9",
33
- light: "#9ac2c933"
66
+ ".dark & .cm-content": {
67
+ caretColor: "white"
34
68
  },
35
- {
36
- color: "#8acb88",
37
- light: "#8acb8833"
69
+ "& .cm-tooltip": {
70
+ backgroundColor: "transparent",
71
+ border: "none"
38
72
  },
39
- {
40
- color: "#1be7ff",
41
- light: "#1be7ff33"
73
+ "& .cm-link": {
74
+ color: get(tokens, "extend.colors.primary.500"),
75
+ textDecoration: "underline"
42
76
  }
43
- ];
44
- var cursorColor = cursorColors[random.uint32() % cursorColors.length];
77
+ };
45
78
 
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));
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());
68
86
  }
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));
87
+ return null;
78
88
  }
79
- get awareness() {
80
- return this._awareness;
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;
81
101
  }
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));
102
+ eq(other) {
103
+ return this._text === other.text && this._url === other.url;
99
104
  }
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));
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
+ };
111
120
  }
121
+ return link;
112
122
  }
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;
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
+ }
132
147
  }
148
+ return true;
133
149
  }
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
- }
150
+ });
151
+ return builder.finish();
144
152
  };
145
153
 
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;
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;
151
168
  }
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;
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;
164
177
  }
178
+ const [, , url] = match ?? [];
165
179
  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
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
+ }
173
194
  };
174
- };
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
+ ];
175
289
 
176
- // packages/ui/react-ui-editor/src/components/Markdown/Markdown.tsx
177
- import { autocompletion, completionKeymap, closeBrackets, closeBracketsKeymap } from "@codemirror/autocomplete";
290
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/bundle.ts
291
+ import { closeBrackets as closeBrackets2, closeBracketsKeymap } from "@codemirror/autocomplete";
178
292
  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";
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";
181
295
  import { languages } from "@codemirror/language-data";
182
296
  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";
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";
195
301
 
196
- // packages/ui/react-ui-editor/src/components/Markdown/markdownTags.ts
302
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/tags.ts
197
303
  import { styleTags, Tag } from "@lezer/highlight";
198
304
  var markdownTags = {
199
305
  headingMark: Tag.define(),
@@ -226,51 +332,28 @@ var markdownTagsExtension = {
226
332
  ]
227
333
  };
228
334
 
229
- // packages/ui/react-ui-editor/src/components/Markdown/markdownTheme.ts
335
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/theme.ts
230
336
  import { markdownLanguage } from "@codemirror/lang-markdown";
231
337
  import { HighlightStyle } from "@codemirror/language";
232
338
  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
339
+ import get2 from "lodash.get";
340
+ var chalky = "#e5c07b";
341
+ var coral = "#e06c75";
342
+ var cyan = "#56b6c2";
343
+ var invalid = "#ffffff";
264
344
  var ivory = "#abb2bf";
345
+ var stone = "#7d8799";
265
346
  var malibu = "#61afef";
347
+ var sage = "#98c379";
348
+ var whiskey = "#d19a66";
349
+ var violet = "#c678dd";
266
350
  var highlightBackground = "#2c313a";
267
351
  var tooltipBackground = "#353a42";
268
- var cursor = "#ffffff";
269
- var monospace = get(tokens, "fontFamily.mono", [
352
+ var monospace = get2(tokens, "fontFamily.mono", [
270
353
  "monospace"
271
354
  ]).join(",");
272
355
  var markdownTheme = {
273
- // TODO(thure): consider whether these commented-out rules from one-dark-theme should be integrated.
356
+ // TODO(thure): Consider whether these commented-out rules from one-dark-theme should be integrated.
274
357
  // '&': {
275
358
  // color: ivory,
276
359
  // backgroundColor: background
@@ -307,63 +390,69 @@ var markdownTheme = {
307
390
  // border: 'none',
308
391
  // color: '#ddd'
309
392
  // },
310
- ".dark & .cm-tooltip": {
311
- border: "none",
312
- backgroundColor: tooltipBackground
313
- },
314
- ".dark & .cm-tooltip .cm-tooltip-arrow:before": {
315
- borderTopColor: "transparent",
316
- borderBottomColor: "transparent"
317
- },
318
- ".dark & .cm-tooltip .cm-tooltip-arrow:after": {
319
- borderTopColor: tooltipBackground,
320
- borderBottomColor: tooltipBackground
321
- },
322
- ".dark & .cm-tooltip-autocomplete": {
323
- "& > ul > li[aria-selected]": {
324
- backgroundColor: highlightBackground,
325
- color: ivory
326
- }
393
+ "& .cm-tooltip": {
394
+ backgroundColor: "transparent",
395
+ border: "none"
327
396
  },
328
- "&.cm-focused": {
329
- outline: "none"
397
+ // '.dark & .cm-tooltip': {
398
+ // border: 'none',
399
+ // backgroundColor: tooltipBackground,
400
+ // },
401
+ // '.dark & .cm-tooltip .cm-tooltip-arrow:before': {
402
+ // borderTopColor: 'transparent',
403
+ // borderBottomColor: 'transparent',
404
+ // },
405
+ // '.dark & .cm-tooltip .cm-tooltip-arrow:after': {
406
+ // borderTopColor: tooltipBackground,
407
+ // borderBottomColor: tooltipBackground,
408
+ // },
409
+ // '.dark & .cm-tooltip-autocomplete': {
410
+ // '& > ul > li[aria-selected]': {
411
+ // backgroundColor: highlightBackground,
412
+ // color: ivory,
413
+ // },
414
+ // },
415
+ "&.cm-focused": {
416
+ outline: "none"
330
417
  },
331
418
  "& .cm-line": {
332
419
  paddingInline: 0,
420
+ lineHeight: 1.6,
333
421
  minBlockSize: "1.6em"
334
422
  },
335
423
  "& .cm-line *": {
336
424
  lineHeight: 1.6
337
425
  },
338
426
  "&.cm-focused .cm-selectionBackground, & .cm-selectionBackground": {
339
- background: get(tokens, "extend.colors.primary.150", "#00ffff")
427
+ background: get2(tokens, "extend.colors.primary.150", "#00ffff")
340
428
  },
341
429
  ".dark & .cm-selectionBackground, .dark &.cm-focused .cm-selectionBackground": {
342
- background: get(tokens, "extend.colors.primary.850", "#00ffff")
430
+ background: get2(tokens, "extend.colors.primary.850", "#00ffff")
343
431
  },
344
432
  "& .cm-selectionMatch": {
345
- background: get(tokens, "extend.colors.primary.250", "#00ffff") + "44"
433
+ background: get2(tokens, "extend.colors.primary.250", "#00ffff") + "44"
346
434
  },
347
435
  ".dark & .cm-selectionMatch": {
348
- background: get(tokens, "extend.colors.primary.600", "#00ffff") + "44"
436
+ background: get2(tokens, "extend.colors.primary.600", "#00ffff") + "44"
349
437
  },
350
438
  "& .cm-content": {
439
+ padding: 0,
351
440
  caretColor: "black"
352
441
  },
353
442
  ".dark & .cm-content": {
354
- caretColor: cursor
443
+ caretColor: "white"
355
444
  },
356
445
  "& .cm-cursor": {
357
446
  borderLeftColor: "black"
358
447
  },
359
448
  ".dark & .cm-cursor": {
360
- borderLeftColor: cursor
449
+ borderLeftColor: "white"
361
450
  },
362
451
  ".cm-placeholder": {
363
- fontFamily: get(tokens, "fontFamily.body", []).join(",")
452
+ fontFamily: get2(tokens, "fontFamily.body", []).join(",")
364
453
  },
365
454
  "& .cm-scroller": {
366
- fontFamily: get(tokens, "fontFamily.mono", []).join(","),
455
+ fontFamily: get2(tokens, "fontFamily.mono", []).join(","),
367
456
  overflow: "visible"
368
457
  },
369
458
  "& .cm-activeLine": {
@@ -373,7 +462,7 @@ var markdownTheme = {
373
462
  backgroundColor: "transparent"
374
463
  },
375
464
  "& .cm-ySelectionInfo": {
376
- fontFamily: get(tokens, "fontFamily.body", []).join(","),
465
+ fontFamily: get2(tokens, "fontFamily.body", []).join(","),
377
466
  padding: "2px 4px",
378
467
  marginBlockStart: "-4px"
379
468
  },
@@ -396,8 +485,16 @@ var markdownTheme = {
396
485
  "& .cm-yLineSelection": {
397
486
  margin: "0"
398
487
  },
399
- ...Object.keys(get(tokens, "extend.fontSize", {})).reduce((acc, fontSize) => {
400
- 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, [
401
498
  "extend",
402
499
  "fontSize",
403
500
  fontSize,
@@ -416,7 +513,7 @@ var markdownTheme = {
416
513
  return acc;
417
514
  }, {})
418
515
  };
419
- var markdownDarkHighlighting = HighlightStyle.define([
516
+ var markdownHighlightStyle = HighlightStyle.define([
420
517
  {
421
518
  tag: [
422
519
  tags.keyword,
@@ -532,40 +629,78 @@ var markdownDarkHighlighting = HighlightStyle.define([
532
629
  ], {
533
630
  scope: markdownLanguage,
534
631
  all: {
535
- fontFamily: get(tokens, "fontFamily.body", []).join(",")
632
+ fontFamily: get2(tokens, "fontFamily.body", []).join(",")
536
633
  }
537
634
  });
538
635
 
539
- // packages/ui/react-ui-editor/src/components/Markdown/Markdown.tsx
540
- var EditorModes = [
541
- "default",
542
- "vim"
543
- ];
544
- var MarkdownEditor = /* @__PURE__ */ forwardRef(({ model, slots = {}, editorMode, onChange }, forwardedRef) => {
545
- const { id, content, provider, peer } = model ?? {};
546
- const { themeMode } = useThemeContext();
547
- const tabsterDOMAttribute = useFocusableGroup({
548
- tabBehavior: "limited"
549
- });
550
- const [parent, setParent] = useState(null);
551
- const [state, setState] = useState();
552
- const [view, setView] = useState();
553
- useImperativeHandle(forwardedRef, () => ({
554
- editor: parent,
555
- state,
556
- view
557
- }));
558
- const listenChangesExtension = useMemo2(() => StateField.define({
559
- create: () => null,
560
- update: (_value, transaction) => {
561
- if (transaction.docChanged && onChange) {
562
- onChange(transaction.newDoc);
563
- }
564
- return null;
565
- }
566
- }), [
567
- onChange
568
- ]);
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 React, { forwardRef, useCallback, useEffect as useEffect2, useImperativeHandle, useState } from "react";
693
+ import { useThemeContext } from "@dxos/react-ui";
694
+
695
+ // packages/ui/react-ui-editor/src/hooks/useCollaboration.ts
696
+ import { useEffect, useMemo } from "react";
697
+ import { yCollab } from "y-codemirror.next";
698
+ import { generateName } from "@dxos/display-name";
699
+ import { getColorForValue } from "@dxos/react-ui-theme";
700
+ import { YText } from "@dxos/text-model";
701
+ var useCollaboration = (model, themeMode) => {
702
+ const { provider, peer, content } = model;
703
+ const extension = useMemo(() => content instanceof YText ? yCollab(content, provider?.awareness) : void 0, []);
569
704
  useEffect(() => {
570
705
  if (provider && peer) {
571
706
  provider.awareness.setLocalStateField("user", {
@@ -586,76 +721,223 @@ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ model, slots = {}, editorMode
586
721
  peer,
587
722
  themeMode
588
723
  ]);
589
- useEffect(() => {
724
+ return extension;
725
+ };
726
+
727
+ // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
728
+ import { useMemo as useMemo2 } from "react";
729
+
730
+ // packages/ui/react-ui-editor/src/hooks/yjs/cursors.ts
731
+ import * as random from "lib0/random";
732
+ var cursorColors = [
733
+ {
734
+ color: "#30bced",
735
+ light: "#30bced33"
736
+ },
737
+ {
738
+ color: "#6eeb83",
739
+ light: "#6eeb8333"
740
+ },
741
+ {
742
+ color: "#ffbc42",
743
+ light: "#ffbc4233"
744
+ },
745
+ {
746
+ color: "#ecd444",
747
+ light: "#ecd44433"
748
+ },
749
+ {
750
+ color: "#ee6352",
751
+ light: "#ee635233"
752
+ },
753
+ {
754
+ color: "#9ac2c9",
755
+ light: "#9ac2c933"
756
+ },
757
+ {
758
+ color: "#8acb88",
759
+ light: "#8acb8833"
760
+ },
761
+ {
762
+ color: "#1be7ff",
763
+ light: "#1be7ff33"
764
+ }
765
+ ];
766
+ var cursorColor = cursorColors[random.uint32() % cursorColors.length];
767
+
768
+ // packages/ui/react-ui-editor/src/hooks/yjs/space-provider.ts
769
+ import * as decoding from "lib0/decoding";
770
+ import * as encoding from "lib0/encoding";
771
+ import { Observable } from "lib0/observable";
772
+ import * as awarenessProtocol from "y-protocols/awareness";
773
+ import { log } from "@dxos/log";
774
+ var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/yjs/space-provider.ts";
775
+ var messageAwareness = 1;
776
+ var messageQueryAwareness = 3;
777
+ var SpaceAwarenessProvider = class extends Observable {
778
+ constructor({ space, doc, channel, awareness }) {
779
+ super();
780
+ this._space = space;
781
+ this._awareness = awareness ?? new awarenessProtocol.Awareness(doc);
782
+ this._channel = channel;
783
+ this._clientId = doc.clientID;
784
+ this._awareness.on("update", this._handleAwarenessUpdate.bind(this));
785
+ this._space.listen(this._channel, this._handleSpaceMessage.bind(this));
786
+ if (typeof window !== "undefined") {
787
+ window.addEventListener("beforeunload", this._handleBeforeUnload.bind(this));
788
+ } else if (typeof process !== "undefined") {
789
+ process.on("exit", this._handleBeforeUnload.bind(this));
790
+ }
791
+ const encoderAwarenessQuery = encoding.createEncoder();
792
+ encoding.writeVarUint(encoderAwarenessQuery, messageQueryAwareness);
793
+ void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwarenessQuery));
794
+ const encoderAwarenessState = encoding.createEncoder();
795
+ encoding.writeVarUint(encoderAwarenessState, messageAwareness);
796
+ encoding.writeVarUint8Array(encoderAwarenessState, awarenessProtocol.encodeAwarenessUpdate(this.awareness, [
797
+ this._clientId
798
+ ]));
799
+ void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwarenessState));
800
+ }
801
+ get awareness() {
802
+ return this._awareness;
803
+ }
804
+ _handleAwarenessUpdate({ added, updated, removed }, origin) {
805
+ log("awareness update", {
806
+ added,
807
+ updated,
808
+ removed,
809
+ origin
810
+ }, {
811
+ F: __dxlog_file,
812
+ L: 67,
813
+ S: this,
814
+ C: (f, a) => f(...a)
815
+ });
816
+ const changedClients = added.concat(updated).concat(removed);
817
+ const encoderAwareness = encoding.createEncoder();
818
+ encoding.writeVarUint(encoderAwareness, messageAwareness);
819
+ encoding.writeVarUint8Array(encoderAwareness, awarenessProtocol.encodeAwarenessUpdate(this._awareness, changedClients));
820
+ void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwareness));
821
+ }
822
+ _handleSpaceMessage({ payload }) {
823
+ log("space message", payload, {
824
+ F: __dxlog_file,
825
+ L: 79,
826
+ S: this,
827
+ C: (f, a) => f(...a)
828
+ });
829
+ const data = new Uint8Array(Array.from(Object.values(payload)));
830
+ const encoder = this._readMessage(data);
831
+ if (encoder) {
832
+ void this._space.postMessage(this._channel, encoding.toUint8Array(encoder));
833
+ }
834
+ }
835
+ _readMessage(message) {
836
+ const decoder = decoding.createDecoder(message);
837
+ const encoder = encoding.createEncoder();
838
+ const messageType = decoding.readVarUint(decoder);
839
+ let sendReply = false;
840
+ switch (messageType) {
841
+ case messageAwareness: {
842
+ awarenessProtocol.applyAwarenessUpdate(this._awareness, decoding.readVarUint8Array(decoder), this);
843
+ break;
844
+ }
845
+ case messageQueryAwareness: {
846
+ encoding.writeVarUint(encoder, messageAwareness);
847
+ encoding.writeVarUint8Array(encoder, awarenessProtocol.encodeAwarenessUpdate(this._awareness, Array.from(this._awareness.getStates().keys())));
848
+ sendReply = true;
849
+ break;
850
+ }
851
+ default: {
852
+ console.error("Invalid message:", messageType);
853
+ return encoder;
854
+ }
855
+ }
856
+ if (!sendReply) {
857
+ return null;
858
+ }
859
+ return encoder;
860
+ }
861
+ _handleBeforeUnload() {
862
+ awarenessProtocol.removeAwarenessStates(this._awareness, [
863
+ this._clientId
864
+ ], "window unload");
865
+ }
866
+ };
867
+
868
+ // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
869
+ var useTextModel = ({ identity, space, text }) => {
870
+ const provider = useMemo2(() => {
871
+ if (!space || !text?.doc) {
872
+ return void 0;
873
+ }
874
+ return new SpaceAwarenessProvider({
875
+ space,
876
+ doc: text.doc,
877
+ channel: `yjs.awareness.${text.id}`
878
+ });
879
+ }, [
880
+ identity,
881
+ space,
882
+ text?.doc
883
+ ]);
884
+ if (!text?.doc || !text?.content) {
885
+ return void 0;
886
+ }
887
+ return {
888
+ id: text.doc.guid,
889
+ content: text.content,
890
+ provider,
891
+ peer: identity ? {
892
+ id: identity.identityKey.toHex(),
893
+ name: identity.profile?.displayName
894
+ } : void 0
895
+ };
896
+ };
897
+
898
+ // packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx
899
+ var EditorModes = [
900
+ "default",
901
+ "vim"
902
+ ];
903
+ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, extensions = [], slots, editorMode }, forwardedRef) => {
904
+ const { themeMode } = useThemeContext();
905
+ const tabsterDOMAttribute = useFocusableGroup({
906
+ tabBehavior: "limited"
907
+ });
908
+ const [parent, setParent] = useState(null);
909
+ const [state, setState] = useState();
910
+ const [view, setView] = useState();
911
+ useImperativeHandle(forwardedRef, () => ({
912
+ editor: parent,
913
+ state,
914
+ view
915
+ }), [
916
+ view,
917
+ state,
918
+ parent
919
+ ]);
920
+ const collaboration = useCollaboration(model, themeMode);
921
+ useEffect2(() => {
590
922
  if (!parent) {
591
923
  return;
592
924
  }
593
- const state2 = EditorState.create({
594
- doc: content?.toString(),
925
+ const state2 = EditorState2.create({
926
+ doc: model.content?.toString(),
595
927
  extensions: [
596
- // Based on https://github.com/codemirror/dev/issues/44#issuecomment-789093799.
597
- listenChangesExtension,
598
- ...editorMode === "vim" ? [
599
- vim()
600
- ] : [],
601
- // All of https://github.com/codemirror/basic-setup minus line numbers and fold gutter.
602
- highlightActiveLineGutter(),
603
- highlightSpecialChars(),
604
- history(),
605
- drawSelection(),
606
- dropCursor(),
607
- EditorState.allowMultipleSelections.of(true),
608
- indentOnInput(),
609
- syntaxHighlighting(defaultHighlightStyle, {
610
- fallback: true
611
- }),
612
- bracketMatching(),
613
- closeBrackets(),
614
- autocompletion(),
615
- rectangularSelection(),
616
- crosshairCursor(),
617
- highlightActiveLine(),
618
- highlightSelectionMatches(),
619
- placeholder(slots.editor?.placeholder ?? ""),
620
- keymap.of([
621
- ...closeBracketsKeymap,
622
- ...defaultKeymap,
623
- ...searchKeymap,
624
- ...historyKeymap,
625
- ...foldKeymap,
626
- ...completionKeymap,
627
- ...lintKeymap,
628
- indentWithTab
629
- ]),
630
- EditorView.lineWrapping,
631
- // Themes.
632
- markdown({
633
- base: markdownLanguage2,
634
- codeLanguages: languages,
635
- extensions: [
636
- markdownTagsExtension
637
- ]
638
- }),
639
- EditorView.theme({
640
- ...markdownTheme,
641
- ...slots.editor?.markdownTheme
642
- }),
643
- ...themeMode === "dark" ? [
644
- syntaxHighlighting(oneDarkHighlightStyle)
645
- ] : [
646
- syntaxHighlighting(defaultHighlightStyle)
647
- ],
648
- // TODO(thure): All but one rule here apply to both themes; rename or refactor.
649
- syntaxHighlighting(markdownDarkHighlighting),
650
- // Replication and awareness (incl. remote selection).
651
- ...content instanceof YText ? [
652
- yCollab(content, provider?.awareness)
653
- ] : []
654
- ]
928
+ // TODO(burdon): Factor out VIM mode?
929
+ editorMode === "vim" && vim(),
930
+ // Replication.
931
+ collaboration,
932
+ // Theme.
933
+ slots?.editor?.theme && EditorView4.theme(slots?.editor?.theme),
934
+ // Custom.
935
+ ...extensions
936
+ ].filter(Boolean)
655
937
  });
656
938
  setState(state2);
657
939
  view?.destroy();
658
- setView(new EditorView({
940
+ setView(new EditorView4({
659
941
  state: state2,
660
942
  parent
661
943
  }));
@@ -666,8 +948,7 @@ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ model, slots = {}, editorMode
666
948
  };
667
949
  }, [
668
950
  parent,
669
- content,
670
- provider?.awareness,
951
+ model.content,
671
952
  themeMode,
672
953
  editorMode
673
954
  ]);
@@ -688,309 +969,89 @@ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ model, slots = {}, editorMode
688
969
  editorMode
689
970
  ]);
690
971
  return /* @__PURE__ */ React.createElement("div", {
691
- tabIndex: 0,
972
+ key: model.id,
692
973
  ref: setParent,
693
- key: id,
694
- ...slots.root,
695
- ...editorMode !== "vim" ? tabsterDOMAttribute : {},
974
+ tabIndex: 0,
975
+ ...slots?.root,
976
+ ...editorMode !== "vim" && tabsterDOMAttribute,
696
977
  onKeyUp: handleKeyUp
697
978
  });
698
979
  });
699
-
700
- // packages/ui/react-ui-editor/src/components/RichText/RichText.tsx
701
- import { mergeAttributes } from "@tiptap/core";
702
- import Collaboration from "@tiptap/extension-collaboration";
703
- import CollaborationCursor from "@tiptap/extension-collaboration-cursor";
704
- import Heading from "@tiptap/extension-heading";
705
- import ListItem from "@tiptap/extension-list-item";
706
- import Placeholder from "@tiptap/extension-placeholder";
707
- import { EditorContent, useEditor as useNaturalEditor } from "@tiptap/react";
708
- import StarterKit from "@tiptap/starter-kit";
709
- import React2, { forwardRef as forwardRef2, useImperativeHandle as useImperativeHandle2, useMemo as useMemo3 } from "react";
710
- import { generateName as generateName2 } from "@dxos/display-name";
711
- import { mx as mx2 } from "@dxos/react-ui-theme";
712
- var useEditor = ({ model, placeholder: placeholder3 = "Enter text\u2026", slots = {} }) => {
713
- const extensions = useMemo3(() => [
714
- StarterKit.configure({
715
- // Extensions
716
- history: false,
717
- // Nodes
718
- blockquote: {
719
- HTMLAttributes: {
720
- class: blockquote
721
- }
722
- },
723
- bulletList: {
724
- HTMLAttributes: {
725
- class: unorderedList
726
- }
727
- },
728
- codeBlock: {
729
- HTMLAttributes: {
730
- class: codeBlock
731
- }
732
- },
733
- heading: false,
734
- horizontalRule: {
735
- HTMLAttributes: {
736
- class: horizontalRule
737
- }
738
- },
739
- listItem: false,
740
- orderedList: {
741
- HTMLAttributes: {
742
- class: orderedList
743
- }
744
- },
745
- paragraph: {
746
- HTMLAttributes: {
747
- class: paragraph
748
- }
749
- },
750
- // Marks
751
- bold: {
752
- HTMLAttributes: {
753
- class: bold
754
- }
755
- },
756
- code: {
757
- HTMLAttributes: {
758
- class: codeWithoutMarks
759
- }
760
- },
761
- italic: {
762
- HTMLAttributes: {
763
- class: italic
764
- }
765
- },
766
- strike: {
767
- HTMLAttributes: {
768
- class: strikethrough
769
- }
770
- }
771
- }),
772
- Heading.extend({
773
- renderHTML({ node, HTMLAttributes }) {
774
- const hasLevel = this.options.levels.includes(node.attrs.level);
775
- const level = hasLevel ? node.attrs.level : this.options.levels[0];
776
- return [
777
- `h${level}`,
778
- mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {
779
- class: heading[level]
780
- }),
781
- 0
782
- ];
783
- }
784
- }),
785
- ListItem.extend({
786
- renderHTML: ({ HTMLAttributes }) => [
787
- "li",
788
- mergeAttributes(HTMLAttributes, {
789
- marker: "\u2022 ",
790
- class: listItem
791
- }),
792
- [
793
- "div",
794
- {
795
- role: "none"
796
- },
797
- 0
798
- ]
799
- ]
800
- }),
801
- // https://github.com/ueberdosis/tiptap/tree/main/packages/extension-collaboration
802
- ...model && typeof model.content !== "string" ? [
803
- Collaboration.configure({
804
- fragment: model.content
805
- })
806
- ] : [],
807
- ...model?.provider ? [
808
- CollaborationCursor.configure({
809
- provider: model.provider,
810
- user: model.peer && {
811
- name: model.peer.name ?? generateName2(model.peer.id),
812
- color: cursorColor.color
813
- }
814
- })
815
- ] : [],
816
- Placeholder.configure({
817
- placeholder: placeholder3,
818
- emptyEditorClass: "before:content-[attr(data-placeholder)] before:absolute opacity-50 cursor-text"
980
+ var TextEditor = /* @__PURE__ */ forwardRef(({ extensions: _extensions, slots, ...props }, forwardedRef) => {
981
+ const { themeMode } = useThemeContext();
982
+ const extensions = [
983
+ ..._extensions ?? [],
984
+ basicBundle({
985
+ themeMode,
986
+ placeholder: slots?.editor?.placeholder
819
987
  })
820
- ], [
821
- model?.id
822
- ]);
823
- return useNaturalEditor({
988
+ ];
989
+ return /* @__PURE__ */ React.createElement(BaseTextEditor, {
990
+ ref: forwardedRef,
824
991
  extensions,
825
- editorProps: {
826
- attributes: {
827
- class: mx2("focus:outline-none focus-visible:outline-none", slots.editor?.className),
828
- spellcheck: slots.editor?.spellCheck === false ? "false" : "true",
829
- tabindex: slots.editor?.tabIndex ? String(slots.editor?.tabIndex) : "0"
992
+ slots: defaultsDeep({}, slots, {
993
+ editor: {
994
+ theme: basicTheme
830
995
  }
831
- }
832
- }, [
833
- extensions
834
- ]);
835
- };
836
- var RichTextEditor = /* @__PURE__ */ forwardRef2((props, ref) => {
837
- const editor = useEditor(props);
838
- useImperativeHandle2(ref, () => editor, [
839
- editor
840
- ]);
841
- return /* @__PURE__ */ React2.createElement(EditorContent, {
842
- ...props.slots?.root,
843
- editor
996
+ }),
997
+ ...props
844
998
  });
845
999
  });
846
-
847
- // packages/ui/react-ui-editor/src/components/Editor/Editor.tsx
848
- var Editor = /* @__PURE__ */ memo(/* @__PURE__ */ forwardRef3(({ slots, ...params }, forwardedRef) => {
849
- const model = useTextModel(params);
850
- if (model?.content instanceof YXmlFragment) {
851
- return /* @__PURE__ */ React3.createElement(RichTextEditor, {
852
- ref: forwardedRef,
853
- model,
854
- slots
855
- });
856
- } else {
857
- return /* @__PURE__ */ React3.createElement(MarkdownEditor, {
858
- ref: forwardedRef,
859
- model,
860
- slots
861
- });
862
- }
863
- }));
864
-
865
- // packages/ui/react-ui-editor/src/components/TextEditor/index.ts
866
- import { tags as tags2 } from "@lezer/highlight";
867
-
868
- // packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx
869
- import { closeBrackets as closeBrackets2 } from "@codemirror/autocomplete";
870
- import { bracketMatching as bracketMatching2, defaultHighlightStyle as defaultHighlightStyle2, syntaxHighlighting as syntaxHighlighting2 } from "@codemirror/language";
871
- import { EditorState as EditorState2 } from "@codemirror/state";
872
- import { oneDarkHighlightStyle as oneDarkHighlightStyle2 } from "@codemirror/theme-one-dark";
873
- import { EditorView as EditorView2, placeholder as placeholder2 } from "@codemirror/view";
874
- import React4, { forwardRef as forwardRef4, useEffect as useEffect2, useImperativeHandle as useImperativeHandle3, useState as useState2, useCallback as useCallback2 } from "react";
875
- import { yCollab as yCollab2 } from "y-codemirror.next";
876
- import { useThemeContext as useThemeContext2 } from "@dxos/react-ui";
877
- import { YText as YText2 } from "@dxos/text-model";
878
-
879
- // packages/ui/react-ui-editor/src/components/TextEditor/theme.ts
880
- import get2 from "lodash.get";
881
- import { tailwindConfig as tailwindConfig2 } from "@dxos/react-ui-theme";
882
- var tokens2 = tailwindConfig2({}).theme;
883
- var defaultStyles = {
884
- "&.cm-focused": {
885
- outline: "none"
886
- },
887
- ".cm-placeholder": {
888
- fontFamily: get2(tokens2, "fontFamily.body", []).join(",")
889
- },
890
- "& .cm-scroller": {
891
- fontFamily: get2(tokens2, "fontFamily.body", []).join(","),
892
- overflow: "visible"
893
- }
894
- };
895
-
896
- // packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx
897
- var TextEditor = /* @__PURE__ */ forwardRef4(({ model, extensions = [], theme = defaultStyles, slots = {}, onKeyDown, ...props }, forwardedRef) => {
898
- const { id, content } = model ?? {};
899
- const { themeMode } = useThemeContext2();
900
- const [parent, setParent] = useState2(null);
901
- const [state, setState] = useState2();
902
- const [view, setView] = useState2();
903
- useImperativeHandle3(forwardedRef, () => {
904
- return {
905
- editor: parent,
906
- state,
907
- view
908
- };
909
- }, [
910
- view
911
- ]);
912
- useEffect2(() => {
913
- if (!parent) {
914
- return;
915
- }
916
- view?.destroy();
917
- const state2 = EditorState2.create({
918
- doc: content?.toString(),
919
- extensions: [
920
- bracketMatching2(),
921
- closeBrackets2(),
922
- placeholder2(slots.editor?.placeholder ?? ""),
923
- EditorView2.lineWrapping,
924
- // Themes.
925
- EditorView2.theme(theme),
926
- ...themeMode === "dark" ? [
927
- syntaxHighlighting2(oneDarkHighlightStyle2)
928
- ] : [
929
- syntaxHighlighting2(defaultHighlightStyle2)
930
- ],
931
- // Replication.
932
- ...content instanceof YText2 ? [
933
- yCollab2(content, void 0)
934
- ] : [],
935
- // Custom.
936
- ...extensions
937
- ]
938
- });
939
- setState(state2);
940
- setView(new EditorView2({
941
- state: state2,
942
- parent
943
- }));
944
- return () => {
945
- view?.destroy();
946
- setView(void 0);
947
- setState(void 0);
948
- };
949
- }, [
950
- parent,
951
- content,
952
- themeMode
953
- ]);
954
- const handleKeyDown = useCallback2((event) => {
955
- if (view) {
956
- const { head, from, to } = view.state.selection.ranges[0];
957
- const { number } = view.state.doc.lineAt(head);
958
- const after = view.state.sliceDoc(from);
959
- onKeyDown?.(event, {
960
- from,
961
- to,
962
- line: number,
963
- lines: view.state.doc.lines,
964
- after
965
- });
966
- }
967
- }, [
968
- view
969
- ]);
970
- return /* @__PURE__ */ React4.createElement("div", {
971
- key: id,
972
- ref: setParent,
973
- ...slots.root,
974
- onKeyDown: handleKeyDown,
1000
+ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ extensions: _extensions, slots, ...props }, forwardedRef) => {
1001
+ const { themeMode } = useThemeContext();
1002
+ const extensions = [
1003
+ ..._extensions ?? [],
1004
+ markdownBundle({
1005
+ themeMode
1006
+ })
1007
+ ];
1008
+ return /* @__PURE__ */ React.createElement(BaseTextEditor, {
1009
+ ref: forwardedRef,
1010
+ extensions,
1011
+ slots: defaultsDeep({}, slots, {
1012
+ editor: {
1013
+ theme: markdownTheme
1014
+ }
1015
+ }),
975
1016
  ...props
976
1017
  });
977
1018
  });
978
-
979
- // packages/ui/react-ui-editor/src/index.ts
980
- import { TextKind } from "@dxos/protocols/proto/dxos/echo/model/text";
981
- import { Doc, YText as YText3, YXmlFragment as YXmlFragment2 } from "@dxos/text-model";
982
1019
  export {
1020
+ BaseTextEditor,
983
1021
  Doc,
984
- Editor,
985
1022
  EditorModes,
986
1023
  MarkdownEditor,
987
- RichTextEditor,
1024
+ SpaceAwarenessProvider,
988
1025
  TextEditor,
989
1026
  TextKind,
990
- YText3 as YText,
991
- YXmlFragment2 as YXmlFragment,
992
- defaultStyles,
1027
+ YText2 as YText,
1028
+ YXmlFragment,
1029
+ basicBundle,
1030
+ basicTheme,
1031
+ chalky,
1032
+ coral,
1033
+ createHyperlinkTooltip,
1034
+ cursorColor,
1035
+ cyan,
1036
+ highlightBackground,
1037
+ hyperLinkExtension,
1038
+ hyperlinkDecoration,
1039
+ hyperlinkStyle,
1040
+ hyperlinkWidget,
1041
+ invalid,
1042
+ ivory,
1043
+ malibu,
1044
+ markdownBundle,
1045
+ markdownHighlightStyle,
1046
+ markdownTheme,
1047
+ onChangeExtension,
1048
+ sage,
1049
+ stone,
993
1050
  tags2 as tags,
994
- useTextModel
1051
+ tooltipBackground,
1052
+ useCollaboration,
1053
+ useTextModel,
1054
+ violet,
1055
+ whiskey
995
1056
  };
996
1057
  //# sourceMappingURL=index.mjs.map