@dxos/react-ui-editor 0.6.8-staging.77f93a3 → 0.6.8-staging.dec6b33

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.
@@ -37,1625 +37,1635 @@ import { keymap as keymap11 } from "@codemirror/view";
37
37
  import { tags as tags2 } from "@lezer/highlight";
38
38
  import { TextKind } from "@dxos/protocols/proto/dxos/echo/model/text";
39
39
 
40
- // packages/ui/react-ui-editor/src/components/Toolbar/Toolbar.tsx
41
- import { ChatText, Code, CodeBlock, Image, Link, ListBullets, ListChecks, ListNumbers, Paragraph, Quotes, TextStrikethrough, Table as Table2, TextB, TextHOne, TextHTwo, TextHThree, TextHFour, TextHFive, TextHSix, TextItalic, CaretDown, Check, PencilSimpleSlash, MarkdownLogo, PencilSimple } from "@phosphor-icons/react";
42
- import { createContext } from "@radix-ui/react-context";
43
- import React3, { useEffect as useEffect2, useRef, useState as useState3 } from "react";
44
- import { useDropzone } from "react-dropzone";
45
- import { Button, DensityProvider, DropdownMenu, ElevationProvider, Toolbar as NaturalToolbar, Tooltip, useTranslation } from "@dxos/react-ui";
46
- import { getSize as getSize2 } from "@dxos/react-ui-theme";
47
-
48
- // packages/ui/react-ui-editor/src/extensions/annotations.ts
49
- import { StateField } from "@codemirror/state";
50
- import { Decoration, EditorView } from "@codemirror/view";
51
- import { isNotFalsy } from "@dxos/util";
52
-
53
- // packages/ui/react-ui-editor/src/extensions/cursor.ts
54
- import { Facet } from "@codemirror/state";
55
- var defaultCursorConverter = {
56
- toCursor: (position) => position.toString(),
57
- fromCursor: (cursor) => parseInt(cursor)
40
+ // packages/ui/react-ui-editor/src/styles/markdown.ts
41
+ import { mx } from "@dxos/react-ui-theme";
42
+ var headings = {
43
+ 1: "text-4xl",
44
+ 2: "text-3xl",
45
+ 3: "text-2xl",
46
+ 4: "text-xl",
47
+ 5: "text-lg",
48
+ 6: "text-md"
58
49
  };
59
- var Cursor = class _Cursor {
60
- static {
61
- this.converter = Facet.define({
62
- combine: (providers) => {
63
- return providers[0] ?? defaultCursorConverter;
64
- }
65
- });
66
- }
67
- static {
68
- this.getCursorFromRange = (state2, range) => {
69
- const cursorConverter2 = state2.facet(_Cursor.converter);
70
- const from = cursorConverter2.toCursor(range.from);
71
- const to = cursorConverter2.toCursor(range.to, -1);
72
- return [
73
- from,
74
- to
75
- ].join(":");
76
- };
77
- }
78
- static {
79
- this.getRangeFromCursor = (state2, cursor) => {
80
- const cursorConverter2 = state2.facet(_Cursor.converter);
81
- const parts = cursor.split(":");
82
- const from = cursorConverter2.fromCursor(parts[0]);
83
- const to = cursorConverter2.fromCursor(parts[1]);
84
- return from !== void 0 && to !== void 0 ? {
85
- from,
86
- to
87
- } : void 0;
88
- };
50
+ var theme = {
51
+ mark: "opacity-50",
52
+ code: "font-mono !no-underline text-neutral-700 dark:text-neutral-300",
53
+ codeMark: "font-mono text-primary-500",
54
+ // TODO(burdon): Replace with widget.
55
+ blockquote: "pl-1 mr-1 border-is-4 border-orange-500 dark:border-orange-500 dark:text-neutral-500",
56
+ heading: (level) => {
57
+ return mx(headings[level], "dark:text-primary-400");
89
58
  }
90
59
  };
91
60
 
92
- // packages/ui/react-ui-editor/src/extensions/annotations.ts
93
- var annotationMark = Decoration.mark({
94
- class: "cm-annotation"
95
- });
96
- var annotations = (options = {}) => {
97
- const match = (state2) => {
98
- const annotations2 = [];
99
- const text = state2.doc.toString();
100
- if (options.match) {
101
- const matches = text.matchAll(options.match);
102
- for (const match2 of matches) {
103
- const from = match2.index;
104
- const to = from + match2[0].length;
105
- const cursor = Cursor.getCursorFromRange(state2, {
106
- from,
107
- to
108
- });
109
- annotations2.push({
110
- cursor
111
- });
112
- }
113
- }
114
- return annotations2;
115
- };
116
- const annotationsState = StateField.define({
117
- create: (state2) => {
118
- return match(state2);
119
- },
120
- update: (value, tr) => {
121
- if (!tr.changes.empty) {
122
- return match(tr.state);
123
- }
124
- return value;
125
- }
126
- });
127
- return [
128
- annotationsState,
129
- EditorView.decorations.compute([
130
- annotationsState
131
- ], (state2) => {
132
- const annotations2 = state2.field(annotationsState);
133
- const decorations = annotations2.map((annotation) => {
134
- const range = Cursor.getRangeFromCursor(state2, annotation.cursor);
135
- return range && annotationMark.range(range.from, range.to);
136
- }).filter(isNotFalsy);
137
- return Decoration.set(decorations);
138
- }),
139
- styles
140
- ];
141
- };
142
- var styles = EditorView.baseTheme({
143
- ".cm-annotation": {
144
- textDecoration: "underline",
145
- textDecorationStyle: "wavy",
146
- textDecorationColor: "red"
147
- }
148
- });
149
-
150
- // packages/ui/react-ui-editor/src/extensions/autocomplete.ts
151
- import { autocompletion, completionKeymap } from "@codemirror/autocomplete";
152
- import { markdownLanguage } from "@codemirror/lang-markdown";
153
- import { keymap } from "@codemirror/view";
154
- var autocomplete = ({ activateOnTyping, onSearch } = {}) => {
155
- const extentions = [
156
- // https://codemirror.net/docs/ref/#view.keymap
157
- // https://discuss.codemirror.net/t/how-can-i-replace-the-default-autocompletion-keymap-v6/3322
158
- // TODO(burdon): Set custom keymap.
159
- keymap.of(completionKeymap),
160
- // https://codemirror.net/examples/autocompletion
161
- // https://codemirror.net/docs/ref/#autocomplete.autocompletion
162
- autocompletion({
163
- activateOnTyping,
164
- // closeOnBlur: false,
165
- // defaultKeymap: false,
166
- // TODO(burdon): Styles/fragments.
167
- tooltipClass: () => "shadow rounded"
168
- })
169
- ];
170
- if (onSearch) {
171
- extentions.push(
172
- // TODO(burdon): Optional decoration via addToOptions
173
- markdownLanguage.data.of({
174
- autocomplete: (context) => {
175
- const match = context.matchBefore(/\w*/);
176
- if (!match || match.from === match.to && !context.explicit) {
177
- return null;
178
- }
179
- return {
180
- from: match.from,
181
- options: onSearch(match.text.toLowerCase())
182
- };
183
- }
184
- })
185
- );
186
- }
187
- return extentions;
61
+ // packages/ui/react-ui-editor/src/styles/tokens.ts
62
+ import get from "lodash.get";
63
+ import { tailwindConfig } from "@dxos/react-ui-theme";
64
+ var tokens = tailwindConfig({}).theme;
65
+ var getToken = (path, defaultValue) => {
66
+ const value = get(tokens, path, defaultValue);
67
+ return value?.toString() ?? "";
188
68
  };
189
69
 
190
- // packages/ui/react-ui-editor/src/extensions/automerge/automerge.ts
191
- import { StateField as StateField2 } from "@codemirror/state";
192
- import { EditorView as EditorView2, ViewPlugin } from "@codemirror/view";
193
- import { next as A3 } from "@dxos/automerge/automerge";
194
-
195
- // packages/ui/react-ui-editor/src/extensions/automerge/cursor.ts
196
- import { log } from "@dxos/log";
197
- import { fromCursor, toCursor } from "@dxos/react-client/echo";
198
- var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/automerge/cursor.ts";
199
- var cursorConverter = (accessor) => ({
200
- toCursor: (pos, assoc) => {
201
- try {
202
- return toCursor(accessor, pos, assoc);
203
- } catch (err) {
204
- log.catch(err, void 0, {
205
- F: __dxlog_file,
206
- L: 15,
207
- S: void 0,
208
- C: (f, a) => f(...a)
209
- });
210
- return "";
211
- }
70
+ // packages/ui/react-ui-editor/src/styles/theme.ts
71
+ var defaultTheme = {
72
+ "&": {},
73
+ "&.cm-focused": {
74
+ outline: "none"
212
75
  },
213
- fromCursor: (cursor) => {
214
- try {
215
- return fromCursor(accessor, cursor);
216
- } catch (err) {
217
- log.catch(err, void 0, {
218
- F: __dxlog_file,
219
- L: 24,
220
- S: void 0,
221
- C: (f, a) => f(...a)
222
- });
223
- return 0;
224
- }
225
- }
226
- });
227
-
228
- // packages/ui/react-ui-editor/src/extensions/automerge/defs.ts
229
- import { Annotation, StateEffect } from "@codemirror/state";
230
- var getPath = (state2, field) => state2.field(field).path;
231
- var getLastHeads = (state2, field) => state2.field(field).lastHeads;
232
- var updateHeadsEffect = StateEffect.define({});
233
- var updateHeads = (newHeads) => updateHeadsEffect.of({
234
- newHeads
235
- });
236
- var reconcileAnnotation = Annotation.define();
237
- var isReconcile = (tr) => {
238
- return !!tr.annotation(reconcileAnnotation);
239
- };
240
-
241
- // packages/ui/react-ui-editor/src/extensions/automerge/sync.ts
242
- import { next as A2 } from "@dxos/automerge/automerge";
243
-
244
- // packages/ui/react-ui-editor/src/extensions/automerge/update-automerge.ts
245
- import { next as A } from "@dxos/automerge/automerge";
246
- var updateAutomerge = (field, handle, transactions, state2) => {
247
- const { lastHeads, path } = state2.field(field);
248
- let hasChanges = false;
249
- for (const tr of transactions) {
250
- tr.changes.iterChanges(() => {
251
- hasChanges = true;
252
- });
253
- }
254
- if (!hasChanges) {
255
- return void 0;
256
- }
257
- const newHeads = handle.changeAt(lastHeads, (doc) => {
258
- const invertedTransactions = [];
259
- for (const tr of transactions) {
260
- tr.changes.iterChanges((fromA, toA, _fromB, _toB, insert) => {
261
- invertedTransactions.push({
262
- from: fromA,
263
- del: toA - fromA,
264
- insert
265
- });
266
- });
267
- }
268
- invertedTransactions.reverse().forEach(({ from, del, insert }) => {
269
- A.splice(doc, path.slice(), from, del, insert.toString());
270
- });
271
- });
272
- return newHeads ?? void 0;
273
- };
274
-
275
- // packages/ui/react-ui-editor/src/extensions/automerge/update-codemirror.ts
276
- import { ChangeSet } from "@codemirror/state";
277
- var updateCodeMirror = (view, selection, target, patches) => {
278
- for (const patch of patches) {
279
- const changeSpec = handlePatch(patch, target, view.state);
280
- if (changeSpec != null) {
281
- const changeSet = ChangeSet.of(changeSpec, view.state.doc.length, "\n");
282
- selection = selection.map(changeSet, 1);
283
- view.dispatch({
284
- changes: changeSet,
285
- annotations: reconcileAnnotation.of(false)
286
- });
287
- }
288
- }
289
- view.dispatch({
290
- selection,
291
- annotations: reconcileAnnotation.of(false)
292
- });
293
- };
294
- var handlePatch = (patch, target, state2) => {
295
- if (patch.action === "insert") {
296
- return handleInsert(target, patch);
297
- } else if (patch.action === "splice") {
298
- return handleSplice(target, patch);
299
- } else if (patch.action === "del") {
300
- return handleDel(target, patch);
301
- } else if (patch.action === "put") {
302
- return handlePut(target, patch, state2);
303
- } else {
304
- return null;
305
- }
306
- };
307
- var handleInsert = (target, patch) => {
308
- const index = charPath(target, patch.path);
309
- if (index == null) {
310
- return [];
311
- }
312
- const text = patch.values.map((value) => value ? value.toString() : "").join("");
313
- return [
314
- {
315
- from: index,
316
- to: index,
317
- insert: text
318
- }
319
- ];
320
- };
321
- var handleSplice = (target, patch) => {
322
- const index = charPath(target, patch.path);
323
- if (index == null) {
324
- return [];
325
- }
326
- return [
327
- {
328
- from: index,
329
- insert: patch.value
330
- }
331
- ];
332
- };
333
- var handleDel = (target, patch) => {
334
- const index = charPath(target, patch.path);
335
- if (index == null) {
336
- return [];
337
- }
338
- const length = patch.length || 1;
339
- return [
340
- {
341
- from: index,
342
- to: index + length
76
+ /**
77
+ * Scroller
78
+ */
79
+ ".cm-scroller": {
80
+ overflowY: "auto"
81
+ },
82
+ /**
83
+ * Content
84
+ * NOTE: Apply margins to content so that scrollbar is at the edge of the container.
85
+ */
86
+ ".cm-content": {
87
+ padding: "unset",
88
+ // NOTE: Base font size (otherwise defined by HTML tag, which might be different for storybook).
89
+ fontSize: "16px",
90
+ fontFamily: getToken("fontFamily.body"),
91
+ lineHeight: 1.5
92
+ },
93
+ "&light .cm-content": {
94
+ color: getToken("extend.semanticColors.base.fg.light", "black")
95
+ },
96
+ "&dark .cm-content": {
97
+ color: getToken("extend.semanticColors.base.fg.dark", "white")
98
+ },
99
+ /**
100
+ * Gutters
101
+ * NOTE: Gutters should have the same top margin as the content.
102
+ */
103
+ ".cm-gutters": {
104
+ background: "transparent"
105
+ },
106
+ ".cm-gutter": {},
107
+ ".cm-gutterElement": {
108
+ lineHeight: 1.5
109
+ },
110
+ //
111
+ // Cursor
112
+ //
113
+ "&light .cm-cursor, &light .cm-dropCursor": {
114
+ borderLeft: "2px solid black"
115
+ },
116
+ "&dark .cm-cursor, &dark .cm-dropCursor": {
117
+ borderLeft: "2px solid white"
118
+ },
119
+ "&light .cm-placeholder": {
120
+ color: getToken("extend.semanticColors.description.light", "rgba(0,0,0,.2)")
121
+ },
122
+ "&dark .cm-placeholder": {
123
+ color: getToken("extend.semanticColors.description.dark", "rgba(255,255,255,.2)")
124
+ },
125
+ //
126
+ // line
127
+ //
128
+ ".cm-line": {
129
+ paddingInline: 0
130
+ },
131
+ ".cm-activeLine": {
132
+ background: "transparent"
133
+ },
134
+ //
135
+ // gutter
136
+ //
137
+ ".cm-lineNumbers": {
138
+ minWidth: "36px"
139
+ },
140
+ //
141
+ // Selection
142
+ //
143
+ "&light .cm-selectionBackground": {
144
+ background: getToken("extend.colors.primary.100")
145
+ },
146
+ "&light.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground": {
147
+ background: getToken("extend.colors.primary.200")
148
+ },
149
+ "&dark .cm-selectionBackground": {
150
+ background: getToken("extend.colors.primary.700")
151
+ },
152
+ "&dark.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground": {
153
+ background: getToken("extend.colors.primary.600")
154
+ },
155
+ //
156
+ // Search
157
+ //
158
+ "&light .cm-searchMatch": {
159
+ backgroundColor: getToken("extend.colors.yellow.100")
160
+ },
161
+ "&dark .cm-searchMatch": {
162
+ backgroundColor: getToken("extend.colors.yellow.700")
163
+ },
164
+ //
165
+ // link
166
+ //
167
+ ".cm-link": {
168
+ textDecorationLine: "underline",
169
+ textDecorationThickness: "1px",
170
+ textUnderlineOffset: "2px",
171
+ borderRadius: ".125rem",
172
+ fontFamily: getToken("fontFamily.body")
173
+ },
174
+ "&light .cm-link > span": {
175
+ color: getToken("extend.colors.primary.600")
176
+ },
177
+ "&dark .cm-link > span": {
178
+ color: getToken("extend.colors.primary.400")
179
+ },
180
+ //
181
+ // tooltip
182
+ //
183
+ ".cm-tooltip": {},
184
+ "&light .cm-tooltip": {
185
+ background: `${getToken("extend.colors.neutral.100")} !important`
186
+ },
187
+ "&dark .cm-tooltip": {
188
+ background: `${getToken("extend.colors.neutral.900")} !important`
189
+ },
190
+ ".cm-tooltip-below": {},
191
+ //
192
+ // autocomplete
193
+ // https://github.com/codemirror/autocomplete/blob/main/src/completion.ts
194
+ //
195
+ ".cm-tooltip.cm-tooltip-autocomplete": {
196
+ marginTop: "4px",
197
+ marginLeft: "-3px"
198
+ },
199
+ ".cm-tooltip.cm-tooltip-autocomplete > ul": {
200
+ maxHeight: "20em !important"
201
+ },
202
+ ".cm-tooltip.cm-tooltip-autocomplete > ul > li": {},
203
+ ".cm-tooltip.cm-tooltip-autocomplete > ul > li[aria-selected]": {},
204
+ ".cm-tooltip.cm-tooltip-autocomplete > ul > completion-section": {
205
+ paddingLeft: "4px !important",
206
+ borderBottom: "none !important",
207
+ color: getToken("extend.colors.primary.500")
208
+ },
209
+ ".cm-tooltip.cm-completionInfo": {
210
+ border: getToken("extend.colors.neutral.500"),
211
+ width: "360px !important",
212
+ margin: "-10px 1px 0 1px",
213
+ padding: "8px !important"
214
+ },
215
+ ".cm-completionIcon": {
216
+ display: "none"
217
+ },
218
+ ".cm-completionLabel": {
219
+ fontFamily: getToken("fontFamily.body")
220
+ },
221
+ ".cm-completionMatchedText": {
222
+ textDecoration: "none !important",
223
+ opacity: 0.5
224
+ },
225
+ // TODO(burdon): Override vars --cm-background.
226
+ // https://www.npmjs.com/package/codemirror-theme-vars
227
+ /**
228
+ * Panels
229
+ * TODO(burdon): Needs styling attention (esp. dark mode).
230
+ * https://github.com/codemirror/search/blob/main/src/search.ts#L745
231
+ *
232
+ * Find/replace panel.
233
+ * <div class="cm-announced">...</div>
234
+ * <div class="cm-scroller">...</div>
235
+ * <div class="cm-panels cm-panels-bottom">
236
+ * <div class="cm-search cm-panel">
237
+ * <input class="cm-textfield" />
238
+ * <button class="cm-button">...</button>
239
+ * <label><input type="checkbox" />...</label>
240
+ * </div>
241
+ * </div
242
+ */
243
+ ".cm-panels": {},
244
+ ".cm-panel": {
245
+ fontFamily: getToken("fontFamily.body")
246
+ },
247
+ ".cm-panel input[type=checkbox]": {
248
+ marginRight: "0.4rem !important"
249
+ },
250
+ "&light .cm-panel": {
251
+ background: getToken("extend.colors.neutral.50")
252
+ },
253
+ "&dark .cm-panel": {
254
+ background: getToken("extend.colors.neutral.850")
255
+ },
256
+ ".cm-button": {
257
+ margin: "4px",
258
+ fontFamily: getToken("fontFamily.body"),
259
+ backgroundImage: "none",
260
+ border: "none",
261
+ "&:active": {
262
+ backgroundImage: "none"
343
263
  }
344
- ];
345
- };
346
- var handlePut = (target, patch, state2) => {
347
- const index = charPath(target, [
348
- ...patch.path,
349
- 0
350
- ]);
351
- if (index == null) {
352
- return [];
353
- }
354
- const length = state2.doc.length;
355
- if (typeof patch.value !== "string") {
356
- return [];
357
- }
358
- return [
359
- {
360
- from: 0,
361
- to: length,
362
- insert: patch.value
264
+ },
265
+ "&light .cm-button": {
266
+ background: getToken("extend.colors.neutral.100"),
267
+ "&:hover": {
268
+ background: getToken("extend.colors.neutral.200")
269
+ },
270
+ "&:active": {
271
+ background: getToken("extend.colors.neutral.300")
363
272
  }
364
- ];
365
- };
366
- var charPath = (textPath, candidatePath) => {
367
- if (candidatePath.length !== textPath.length + 1) {
368
- return null;
369
- }
370
- for (let i = 0; i < textPath.length; i++) {
371
- if (textPath[i] !== candidatePath[i]) {
372
- return null;
273
+ },
274
+ "&dark .cm-button": {
275
+ background: getToken("extend.colors.neutral.800"),
276
+ "&:hover": {
277
+ background: getToken("extend.colors.neutral.700")
278
+ },
279
+ "&:active": {
280
+ background: getToken("extend.colors.neutral.600")
373
281
  }
282
+ },
283
+ // TODO(burdon): Factor out element specific logic.
284
+ //
285
+ // table
286
+ //
287
+ ".cm-table *": {
288
+ fontFamily: `${getToken("fontFamily.mono")} !important`,
289
+ textDecoration: "none !important"
290
+ },
291
+ ".cm-table-head": {
292
+ padding: "2px 16px 2px 0px",
293
+ textAlign: "left",
294
+ borderBottom: `1px solid ${getToken("extend.colors.primary.500")}`,
295
+ color: getToken("extend.colors.neutral.500")
296
+ },
297
+ ".cm-table-cell": {
298
+ padding: "2px 16px 2px 0px"
299
+ },
300
+ //
301
+ // image
302
+ //
303
+ ".cm-image": {
304
+ display: "block",
305
+ height: "0"
306
+ },
307
+ ".cm-image.cm-loaded-image": {
308
+ height: "auto",
309
+ borderTop: "0.5rem solid transparent",
310
+ borderBottom: "0.5rem solid transparent"
374
311
  }
375
- const index = candidatePath[candidatePath.length - 1];
376
- if (typeof index === "number") {
377
- return index;
378
- }
379
- return null;
380
312
  };
381
313
 
382
- // packages/ui/react-ui-editor/src/extensions/automerge/sync.ts
383
- var Syncer = class {
384
- // prettier-ignore
385
- constructor(_handle, _state) {
386
- this._handle = _handle;
387
- this._state = _state;
388
- this._pending = false;
389
- }
390
- reconcile(view, editor) {
391
- if (this._pending) {
392
- return;
393
- }
394
- this._pending = true;
395
- if (editor) {
396
- this.onEditorChange(view);
397
- } else {
398
- this.onAutomergeChange(view);
399
- }
400
- this._pending = false;
314
+ // packages/ui/react-ui-editor/src/components/Toolbar/Toolbar.tsx
315
+ import { ChatText, Code, CodeBlock, Image, Link, ListBullets, ListChecks, ListNumbers, Paragraph, Quotes, TextStrikethrough, Table as Table2, TextB, TextHOne, TextHTwo, TextHThree, TextHFour, TextHFive, TextHSix, TextItalic, CaretDown, Check, PencilSimpleSlash, MarkdownLogo, PencilSimple } from "@phosphor-icons/react";
316
+ import { createContext } from "@radix-ui/react-context";
317
+ import React3, { useEffect as useEffect2, useRef, useState as useState3 } from "react";
318
+ import { useDropzone } from "react-dropzone";
319
+ import { Button, DensityProvider, DropdownMenu, ElevationProvider, Toolbar as NaturalToolbar, Tooltip, useTranslation } from "@dxos/react-ui";
320
+ import { getSize as getSize2 } from "@dxos/react-ui-theme";
321
+
322
+ // packages/ui/react-ui-editor/src/extensions/annotations.ts
323
+ import { StateField } from "@codemirror/state";
324
+ import { Decoration, EditorView } from "@codemirror/view";
325
+ import { isNotFalsy } from "@dxos/util";
326
+
327
+ // packages/ui/react-ui-editor/src/extensions/cursor.ts
328
+ import { Facet } from "@codemirror/state";
329
+ var defaultCursorConverter = {
330
+ toCursor: (position) => position.toString(),
331
+ fromCursor: (cursor) => parseInt(cursor)
332
+ };
333
+ var Cursor = class _Cursor {
334
+ static {
335
+ this.converter = Facet.define({
336
+ combine: (providers) => {
337
+ return providers[0] ?? defaultCursorConverter;
338
+ }
339
+ });
401
340
  }
402
- onEditorChange(view) {
403
- const transactions = view.state.field(this._state).unreconciledTransactions.filter((tx) => !isReconcile(tx));
404
- const newHeads = updateAutomerge(this._state, this._handle, transactions, view.state);
405
- if (newHeads) {
406
- view.dispatch({
407
- effects: updateHeads(newHeads),
408
- annotations: reconcileAnnotation.of(false)
409
- });
410
- }
341
+ static {
342
+ this.getCursorFromRange = (state2, range) => {
343
+ const cursorConverter2 = state2.facet(_Cursor.converter);
344
+ const from = cursorConverter2.toCursor(range.from);
345
+ const to = cursorConverter2.toCursor(range.to, -1);
346
+ return [
347
+ from,
348
+ to
349
+ ].join(":");
350
+ };
411
351
  }
412
- onAutomergeChange(view) {
413
- const oldHeads = getLastHeads(view.state, this._state);
414
- const newHeads = A2.getHeads(this._handle.docSync());
415
- const diff = A2.equals(oldHeads, newHeads) ? [] : A2.diff(this._handle.docSync(), oldHeads, newHeads);
416
- const selection = view.state.selection;
417
- const path = getPath(view.state, this._state);
418
- updateCodeMirror(view, selection, path, diff);
419
- view.dispatch({
420
- effects: updateHeads(newHeads),
421
- annotations: reconcileAnnotation.of(false)
422
- });
352
+ static {
353
+ this.getRangeFromCursor = (state2, cursor) => {
354
+ const cursorConverter2 = state2.facet(_Cursor.converter);
355
+ const parts = cursor.split(":");
356
+ const from = cursorConverter2.fromCursor(parts[0]);
357
+ const to = cursorConverter2.fromCursor(parts[1]);
358
+ return from !== void 0 && to !== void 0 ? {
359
+ from,
360
+ to
361
+ } : void 0;
362
+ };
423
363
  }
424
364
  };
425
365
 
426
- // packages/ui/react-ui-editor/src/extensions/automerge/automerge.ts
427
- var automerge = (accessor) => {
428
- const syncState = StateField2.define({
429
- create: () => ({
430
- path: accessor.path.slice(),
431
- lastHeads: A3.getHeads(accessor.handle.docSync()),
432
- unreconciledTransactions: []
433
- }),
434
- update: (value, tr) => {
435
- const result = {
436
- path: accessor.path.slice(),
437
- lastHeads: value.lastHeads,
438
- unreconciledTransactions: value.unreconciledTransactions.slice()
439
- };
440
- let clearUnreconciled = false;
441
- for (const effect of tr.effects) {
442
- if (effect.is(updateHeadsEffect)) {
443
- result.lastHeads = effect.value.newHeads;
444
- clearUnreconciled = true;
445
- }
366
+ // packages/ui/react-ui-editor/src/extensions/annotations.ts
367
+ var annotationMark = Decoration.mark({
368
+ class: "cm-annotation"
369
+ });
370
+ var annotations = (options = {}) => {
371
+ const match = (state2) => {
372
+ const annotations2 = [];
373
+ const text = state2.doc.toString();
374
+ if (options.match) {
375
+ const matches = text.matchAll(options.match);
376
+ for (const match2 of matches) {
377
+ const from = match2.index;
378
+ const to = from + match2[0].length;
379
+ const cursor = Cursor.getCursorFromRange(state2, {
380
+ from,
381
+ to
382
+ });
383
+ annotations2.push({
384
+ cursor
385
+ });
446
386
  }
447
- if (clearUnreconciled) {
448
- result.unreconciledTransactions = [];
449
- } else {
450
- if (!isReconcile(tr)) {
451
- result.unreconciledTransactions.push(tr);
452
- }
387
+ }
388
+ return annotations2;
389
+ };
390
+ const annotationsState = StateField.define({
391
+ create: (state2) => {
392
+ return match(state2);
393
+ },
394
+ update: (value, tr) => {
395
+ if (!tr.changes.empty) {
396
+ return match(tr.state);
453
397
  }
454
- return result;
398
+ return value;
455
399
  }
456
400
  });
457
- const syncer = new Syncer(accessor.handle, syncState);
458
401
  return [
459
- Cursor.converter.of(cursorConverter(accessor)),
460
- // Track heads.
461
- syncState,
462
- // Reconcile external updates.
463
- ViewPlugin.fromClass(class {
464
- constructor(_view) {
465
- this._view = _view;
466
- this._handleChange = () => {
467
- syncer.reconcile(this._view, false);
468
- };
469
- accessor.handle.addListener("change", this._handleChange);
470
- }
471
- destroy() {
472
- accessor.handle.removeListener("change", this._handleChange);
473
- }
402
+ annotationsState,
403
+ EditorView.decorations.compute([
404
+ annotationsState
405
+ ], (state2) => {
406
+ const annotations2 = state2.field(annotationsState);
407
+ const decorations = annotations2.map((annotation) => {
408
+ const range = Cursor.getRangeFromCursor(state2, annotation.cursor);
409
+ return range && annotationMark.range(range.from, range.to);
410
+ }).filter(isNotFalsy);
411
+ return Decoration.set(decorations);
474
412
  }),
475
- // Reconcile local updates.
476
- EditorView2.updateListener.of(({ view, changes }) => {
477
- if (!changes.empty) {
478
- syncer.reconcile(view, true);
479
- }
480
- })
413
+ styles
481
414
  ];
482
415
  };
483
-
484
- // packages/ui/react-ui-editor/src/extensions/awareness/awareness.ts
485
- import { Annotation as Annotation2, Facet as Facet2, RangeSet } from "@codemirror/state";
486
- import { Decoration as Decoration2, EditorView as EditorView3, ViewPlugin as ViewPlugin2, WidgetType } from "@codemirror/view";
487
- import { Event } from "@dxos/async";
488
- import { Context } from "@dxos/context";
489
- var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/awareness/awareness.ts";
490
- var dummyProvider = {
491
- remoteStateChange: new Event(),
492
- open: () => {
493
- },
494
- close: () => {
495
- },
496
- getRemoteStates: () => [],
497
- update: () => {
416
+ var styles = EditorView.baseTheme({
417
+ ".cm-annotation": {
418
+ textDecoration: "underline",
419
+ textDecorationStyle: "wavy",
420
+ textDecorationColor: "red"
498
421
  }
499
- };
500
- var awarenessProvider = Facet2.define({
501
- combine: (providers) => providers[0] ?? dummyProvider
502
422
  });
503
- var RemoteSelectionChangedAnnotation = Annotation2.define();
504
- var awareness = (provider = dummyProvider) => {
505
- return [
506
- awarenessProvider.of(provider),
507
- ViewPlugin2.fromClass(RemoteSelectionsDecorator, {
508
- decorations: (value) => value.decorations
509
- }),
510
- styles2
423
+
424
+ // packages/ui/react-ui-editor/src/extensions/autocomplete.ts
425
+ import { autocompletion, completionKeymap } from "@codemirror/autocomplete";
426
+ import { markdownLanguage } from "@codemirror/lang-markdown";
427
+ import { keymap } from "@codemirror/view";
428
+ var autocomplete = ({ activateOnTyping, onSearch } = {}) => {
429
+ const extentions = [
430
+ // https://codemirror.net/docs/ref/#view.keymap
431
+ // https://discuss.codemirror.net/t/how-can-i-replace-the-default-autocompletion-keymap-v6/3322
432
+ // TODO(burdon): Set custom keymap.
433
+ keymap.of(completionKeymap),
434
+ // https://codemirror.net/examples/autocompletion
435
+ // https://codemirror.net/docs/ref/#autocomplete.autocompletion
436
+ autocompletion({
437
+ activateOnTyping,
438
+ // closeOnBlur: false,
439
+ // defaultKeymap: false,
440
+ // TODO(burdon): Styles/fragments.
441
+ tooltipClass: () => "shadow rounded"
442
+ })
511
443
  ];
444
+ if (onSearch) {
445
+ extentions.push(
446
+ // TODO(burdon): Optional decoration via addToOptions
447
+ markdownLanguage.data.of({
448
+ autocomplete: (context) => {
449
+ const match = context.matchBefore(/\w*/);
450
+ if (!match || match.from === match.to && !context.explicit) {
451
+ return null;
452
+ }
453
+ return {
454
+ from: match.from,
455
+ options: onSearch(match.text.toLowerCase())
456
+ };
457
+ }
458
+ })
459
+ );
460
+ }
461
+ return extentions;
512
462
  };
513
- var RemoteSelectionsDecorator = class {
514
- constructor(view) {
515
- this._ctx = new Context(void 0, {
516
- F: __dxlog_file2,
517
- L: 82
518
- });
519
- this.decorations = RangeSet.of([]);
520
- this._cursorConverter = view.state.facet(Cursor.converter);
521
- this._provider = view.state.facet(awarenessProvider);
522
- this._provider.open();
523
- this._provider.remoteStateChange.on(this._ctx, () => {
524
- view.dispatch({
525
- annotations: [
526
- RemoteSelectionChangedAnnotation.of([])
527
- ]
463
+
464
+ // packages/ui/react-ui-editor/src/extensions/automerge/automerge.ts
465
+ import { StateField as StateField2 } from "@codemirror/state";
466
+ import { EditorView as EditorView2, ViewPlugin } from "@codemirror/view";
467
+ import { next as A3 } from "@dxos/automerge/automerge";
468
+
469
+ // packages/ui/react-ui-editor/src/extensions/automerge/cursor.ts
470
+ import { log } from "@dxos/log";
471
+ import { fromCursor, toCursor } from "@dxos/react-client/echo";
472
+ var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/automerge/cursor.ts";
473
+ var cursorConverter = (accessor) => ({
474
+ toCursor: (pos, assoc) => {
475
+ try {
476
+ return toCursor(accessor, pos, assoc);
477
+ } catch (err) {
478
+ log.catch(err, void 0, {
479
+ F: __dxlog_file,
480
+ L: 15,
481
+ S: void 0,
482
+ C: (f, a) => f(...a)
528
483
  });
529
- });
530
- }
531
- destroy() {
532
- void this._ctx.dispose();
533
- this._provider.close();
534
- }
535
- update(update2) {
536
- this._updateLocalSelection(update2.view);
537
- this._updateRemoteSelections(update2.view);
538
- }
539
- _updateLocalSelection(view) {
540
- const hasFocus = view.hasFocus && view.dom.ownerDocument.hasFocus();
541
- const { anchor = void 0, head = void 0 } = hasFocus ? view.state.selection.main : {};
542
- if (this._lastAnchor === anchor && this._lastHead === head) {
543
- return;
484
+ return "";
544
485
  }
545
- this._lastAnchor = anchor;
546
- this._lastHead = head;
547
- this._provider.update(anchor !== void 0 && head !== void 0 ? {
548
- anchor: this._cursorConverter.toCursor(anchor),
549
- head: this._cursorConverter.toCursor(head, -1)
550
- } : void 0);
551
- }
552
- _updateRemoteSelections(view) {
553
- const decorations = [];
554
- const awarenessStates = this._provider.getRemoteStates();
555
- for (const state2 of awarenessStates) {
556
- const anchor = state2.position?.anchor ? this._cursorConverter.fromCursor(state2.position.anchor) : null;
557
- const head = state2.position?.head ? this._cursorConverter.fromCursor(state2.position.head) : null;
558
- if (anchor == null || head == null) {
559
- continue;
560
- }
561
- const start = Math.min(Math.min(anchor, head), view.state.doc.length);
562
- const end = Math.min(Math.max(anchor, head), view.state.doc.length);
563
- const startLine = view.state.doc.lineAt(start);
564
- const endLine = view.state.doc.lineAt(end);
565
- const darkColor = state2.info.darkColor;
566
- const lightColor = state2.info.lightColor;
567
- if (startLine.number === endLine.number) {
568
- decorations.push({
569
- from: start,
570
- to: end,
571
- value: Decoration2.mark({
572
- attributes: {
573
- style: `background-color: ${lightColor}`
574
- },
575
- class: "cm-collab-selection"
576
- })
577
- });
578
- } else {
579
- decorations.push({
580
- from: start,
581
- to: startLine.from + startLine.length,
582
- value: Decoration2.mark({
583
- attributes: {
584
- style: `background-color: ${lightColor}`
585
- },
586
- class: "cm-collab-selection"
587
- })
588
- });
589
- decorations.push({
590
- from: endLine.from,
591
- to: end,
592
- value: Decoration2.mark({
593
- attributes: {
594
- style: `background-color: ${lightColor}`
595
- },
596
- class: "cm-collab-selection"
597
- })
598
- });
599
- for (let i = startLine.number + 1; i < endLine.number; i++) {
600
- const linePos = view.state.doc.line(i).from;
601
- decorations.push({
602
- from: linePos,
603
- to: linePos,
604
- value: Decoration2.line({
605
- attributes: {
606
- style: `background-color: ${lightColor}`,
607
- class: "cm-collab-selectionLine"
608
- }
609
- })
610
- });
611
- }
612
- }
613
- decorations.push({
614
- from: head,
615
- to: head,
616
- value: Decoration2.widget({
617
- side: head - anchor > 0 ? -1 : 1,
618
- block: false,
619
- widget: new RemoteCaretWidget(state2.info.displayName ?? "Anonymous", darkColor)
620
- })
486
+ },
487
+ fromCursor: (cursor) => {
488
+ try {
489
+ return fromCursor(accessor, cursor);
490
+ } catch (err) {
491
+ log.catch(err, void 0, {
492
+ F: __dxlog_file,
493
+ L: 24,
494
+ S: void 0,
495
+ C: (f, a) => f(...a)
621
496
  });
497
+ return 0;
622
498
  }
623
- this.decorations = Decoration2.set(decorations, true);
624
499
  }
500
+ });
501
+
502
+ // packages/ui/react-ui-editor/src/extensions/automerge/defs.ts
503
+ import { Annotation, StateEffect } from "@codemirror/state";
504
+ var getPath = (state2, field) => state2.field(field).path;
505
+ var getLastHeads = (state2, field) => state2.field(field).lastHeads;
506
+ var updateHeadsEffect = StateEffect.define({});
507
+ var updateHeads = (newHeads) => updateHeadsEffect.of({
508
+ newHeads
509
+ });
510
+ var reconcileAnnotation = Annotation.define();
511
+ var isReconcile = (tr) => {
512
+ return !!tr.annotation(reconcileAnnotation);
625
513
  };
626
- var RemoteCaretWidget = class extends WidgetType {
627
- constructor(_name, _color) {
628
- super();
629
- this._name = _name;
630
- this._color = _color;
631
- }
632
- toDOM() {
633
- const span = document.createElement("span");
634
- span.className = "cm-collab-selectionCaret";
635
- span.style.backgroundColor = this._color;
636
- span.style.borderColor = this._color;
637
- const dot = document.createElement("div");
638
- dot.className = "cm-collab-selectionCaretDot";
639
- const info = document.createElement("div");
640
- info.className = "cm-collab-selectionInfo";
641
- info.innerText = this._name;
642
- span.appendChild(document.createTextNode("\u2060"));
643
- span.appendChild(dot);
644
- span.appendChild(document.createTextNode("\u2060"));
645
- span.appendChild(info);
646
- span.appendChild(document.createTextNode("\u2060"));
647
- return span;
514
+
515
+ // packages/ui/react-ui-editor/src/extensions/automerge/sync.ts
516
+ import { next as A2 } from "@dxos/automerge/automerge";
517
+
518
+ // packages/ui/react-ui-editor/src/extensions/automerge/update-automerge.ts
519
+ import { next as A } from "@dxos/automerge/automerge";
520
+ var updateAutomerge = (field, handle, transactions, state2) => {
521
+ const { lastHeads, path } = state2.field(field);
522
+ let hasChanges = false;
523
+ for (const tr of transactions) {
524
+ tr.changes.iterChanges(() => {
525
+ hasChanges = true;
526
+ });
648
527
  }
649
- updateDOM() {
650
- return false;
528
+ if (!hasChanges) {
529
+ return void 0;
651
530
  }
652
- eq(widget) {
653
- return widget._color === this._color;
531
+ const newHeads = handle.changeAt(lastHeads, (doc) => {
532
+ const invertedTransactions = [];
533
+ for (const tr of transactions) {
534
+ tr.changes.iterChanges((fromA, toA, _fromB, _toB, insert) => {
535
+ invertedTransactions.push({
536
+ from: fromA,
537
+ del: toA - fromA,
538
+ insert
539
+ });
540
+ });
541
+ }
542
+ invertedTransactions.reverse().forEach(({ from, del, insert }) => {
543
+ A.splice(doc, path.slice(), from, del, insert.toString());
544
+ });
545
+ });
546
+ return newHeads ?? void 0;
547
+ };
548
+
549
+ // packages/ui/react-ui-editor/src/extensions/automerge/update-codemirror.ts
550
+ import { ChangeSet } from "@codemirror/state";
551
+ var updateCodeMirror = (view, selection, target, patches) => {
552
+ for (const patch of patches) {
553
+ const changeSpec = handlePatch(patch, target, view.state);
554
+ if (changeSpec != null) {
555
+ const changeSet = ChangeSet.of(changeSpec, view.state.doc.length, "\n");
556
+ selection = selection.map(changeSet, 1);
557
+ view.dispatch({
558
+ changes: changeSet,
559
+ annotations: reconcileAnnotation.of(false)
560
+ });
561
+ }
654
562
  }
655
- get estimatedHeight() {
656
- return -1;
563
+ view.dispatch({
564
+ selection,
565
+ annotations: reconcileAnnotation.of(false)
566
+ });
567
+ };
568
+ var handlePatch = (patch, target, state2) => {
569
+ if (patch.action === "insert") {
570
+ return handleInsert(target, patch);
571
+ } else if (patch.action === "splice") {
572
+ return handleSplice(target, patch);
573
+ } else if (patch.action === "del") {
574
+ return handleDel(target, patch);
575
+ } else if (patch.action === "put") {
576
+ return handlePut(target, patch, state2);
577
+ } else {
578
+ return null;
657
579
  }
658
- ignoreEvent() {
659
- return true;
580
+ };
581
+ var handleInsert = (target, patch) => {
582
+ const index = charPath(target, patch.path);
583
+ if (index == null) {
584
+ return [];
660
585
  }
586
+ const text = patch.values.map((value) => value ? value.toString() : "").join("");
587
+ return [
588
+ {
589
+ from: index,
590
+ to: index,
591
+ insert: text
592
+ }
593
+ ];
661
594
  };
662
- var styles2 = EditorView3.baseTheme({
663
- ".cm-collab-selection": {},
664
- ".cm-collab-selectionLine": {
665
- padding: 0,
666
- margin: "0px 2px 0px 4px"
667
- },
668
- ".cm-collab-selectionCaret": {
669
- position: "relative",
670
- borderLeft: "1px solid black",
671
- borderRight: "1px solid black",
672
- marginLeft: "-1px",
673
- marginRight: "-1px",
674
- boxSizing: "border-box",
675
- display: "inline",
676
- cursor: "pointer"
677
- },
678
- ".cm-collab-selectionCaretDot": {
679
- borderRadius: "50%",
680
- position: "absolute",
681
- width: ".5em",
682
- height: ".5em",
683
- top: "-.25em",
684
- left: "-.25em",
685
- backgroundColor: "inherit",
686
- transition: "transform .3s ease-in-out",
687
- boxSizing: "border-box"
688
- },
689
- ".cm-collab-selectionCaret:hover > .cm-collab-selectionCaretDot": {
690
- transform: "scale(0)",
691
- transformOrigin: "center"
692
- },
693
- ".cm-collab-selectionInfo": {
694
- position: "absolute",
695
- transform: "translate(-50%, 0)",
696
- top: "-20px",
697
- left: 0,
698
- fontSize: ".75em",
699
- fontFamily: "sans-serif",
700
- fontStyle: "normal",
701
- fontWeight: "normal",
702
- lineHeight: "normal",
703
- userSelect: "none",
704
- color: "white",
705
- padding: "2px 6px",
706
- zIndex: 101,
707
- transition: "opacity .3s ease-in-out",
708
- backgroundColor: "inherit",
709
- borderRadius: "2px",
710
- opacity: 0,
711
- transitionDelay: "0s",
712
- whiteSpace: "nowrap"
713
- },
714
- ".cm-collab-selectionCaret:hover > .cm-collab-selectionInfo": {
715
- opacity: 1,
716
- transitionDelay: "0s"
595
+ var handleSplice = (target, patch) => {
596
+ const index = charPath(target, patch.path);
597
+ if (index == null) {
598
+ return [];
717
599
  }
718
- });
719
-
720
- // packages/ui/react-ui-editor/src/extensions/awareness/awareness-provider.ts
721
- import { DeferredTask, Event as Event2, sleep } from "@dxos/async";
722
- import { Context as Context2 } from "@dxos/context";
723
- import { invariant } from "@dxos/invariant";
724
- import { log as log2 } from "@dxos/log";
725
- var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/awareness/awareness-provider.ts";
726
- var DEBOUNCE_INTERVAL = 100;
727
- var SpaceAwarenessProvider = class {
728
- constructor(params) {
729
- this._remoteStates = /* @__PURE__ */ new Map();
730
- this.remoteStateChange = new Event2();
731
- this._space = params.space;
732
- this._channel = params.channel;
733
- this._peerId = params.peerId;
734
- this._info = params.info;
600
+ return [
601
+ {
602
+ from: index,
603
+ insert: patch.value
604
+ }
605
+ ];
606
+ };
607
+ var handleDel = (target, patch) => {
608
+ const index = charPath(target, patch.path);
609
+ if (index == null) {
610
+ return [];
735
611
  }
736
- open() {
737
- this._ctx = new Context2(void 0, {
738
- F: __dxlog_file3,
739
- L: 57
740
- });
741
- this._postTask = new DeferredTask(this._ctx, async () => {
742
- if (this._localState) {
743
- await this._space.postMessage(this._channel, {
744
- kind: "post",
745
- state: this._localState
746
- });
747
- await sleep(DEBOUNCE_INTERVAL);
748
- }
749
- });
750
- this._ctx.onDispose(this._space.listen(this._channel, (message) => {
751
- switch (message.payload.kind) {
752
- case "query": {
753
- this._handleQueryMessage();
754
- break;
755
- }
756
- case "post": {
757
- this._handlePostMessage(message.payload);
758
- break;
759
- }
760
- }
761
- }));
762
- void this._space.postMessage(this._channel, {
763
- kind: "query"
764
- }).catch((err) => {
765
- log2.debug("failed to query awareness", {
766
- err
767
- }, {
768
- F: __dxlog_file3,
769
- L: 91,
770
- S: this,
771
- C: (f, a) => f(...a)
772
- });
773
- });
612
+ const length = patch.length || 1;
613
+ return [
614
+ {
615
+ from: index,
616
+ to: index + length
617
+ }
618
+ ];
619
+ };
620
+ var handlePut = (target, patch, state2) => {
621
+ const index = charPath(target, [
622
+ ...patch.path,
623
+ 0
624
+ ]);
625
+ if (index == null) {
626
+ return [];
774
627
  }
775
- close() {
776
- void this._ctx?.dispose();
777
- this._ctx = void 0;
778
- this._postTask = void 0;
628
+ const length = state2.doc.length;
629
+ if (typeof patch.value !== "string") {
630
+ return [];
779
631
  }
780
- getRemoteStates() {
781
- return Array.from(this._remoteStates.values());
632
+ return [
633
+ {
634
+ from: 0,
635
+ to: length,
636
+ insert: patch.value
637
+ }
638
+ ];
639
+ };
640
+ var charPath = (textPath, candidatePath) => {
641
+ if (candidatePath.length !== textPath.length + 1) {
642
+ return null;
782
643
  }
783
- update(position) {
784
- invariant(this._postTask, void 0, {
785
- F: __dxlog_file3,
786
- L: 106,
787
- S: this,
788
- A: [
789
- "this._postTask",
790
- ""
791
- ]
792
- });
793
- this._localState = {
794
- peerId: this._peerId,
795
- position,
796
- info: this._info
797
- };
798
- this._postTask.schedule();
644
+ for (let i = 0; i < textPath.length; i++) {
645
+ if (textPath[i] !== candidatePath[i]) {
646
+ return null;
647
+ }
799
648
  }
800
- _handleQueryMessage() {
801
- invariant(this._postTask, void 0, {
802
- F: __dxlog_file3,
803
- L: 117,
804
- S: this,
805
- A: [
806
- "this._postTask",
807
- ""
808
- ]
809
- });
810
- this._postTask.schedule();
649
+ const index = candidatePath[candidatePath.length - 1];
650
+ if (typeof index === "number") {
651
+ return index;
811
652
  }
812
- _handlePostMessage(message) {
813
- invariant(message.kind === "post", void 0, {
814
- F: __dxlog_file3,
815
- L: 122,
816
- S: this,
817
- A: [
818
- "message.kind === 'post'",
819
- ""
820
- ]
653
+ return null;
654
+ };
655
+
656
+ // packages/ui/react-ui-editor/src/extensions/automerge/sync.ts
657
+ var Syncer = class {
658
+ // prettier-ignore
659
+ constructor(_handle, _state) {
660
+ this._handle = _handle;
661
+ this._state = _state;
662
+ this._pending = false;
663
+ }
664
+ reconcile(view, editor) {
665
+ if (this._pending) {
666
+ return;
667
+ }
668
+ this._pending = true;
669
+ if (editor) {
670
+ this.onEditorChange(view);
671
+ } else {
672
+ this.onAutomergeChange(view);
673
+ }
674
+ this._pending = false;
675
+ }
676
+ onEditorChange(view) {
677
+ const transactions = view.state.field(this._state).unreconciledTransactions.filter((tx) => !isReconcile(tx));
678
+ const newHeads = updateAutomerge(this._state, this._handle, transactions, view.state);
679
+ if (newHeads) {
680
+ view.dispatch({
681
+ effects: updateHeads(newHeads),
682
+ annotations: reconcileAnnotation.of(false)
683
+ });
684
+ }
685
+ }
686
+ onAutomergeChange(view) {
687
+ const oldHeads = getLastHeads(view.state, this._state);
688
+ const newHeads = A2.getHeads(this._handle.docSync());
689
+ const diff = A2.equals(oldHeads, newHeads) ? [] : A2.diff(this._handle.docSync(), oldHeads, newHeads);
690
+ const selection = view.state.selection;
691
+ const path = getPath(view.state, this._state);
692
+ updateCodeMirror(view, selection, path, diff);
693
+ view.dispatch({
694
+ effects: updateHeads(newHeads),
695
+ annotations: reconcileAnnotation.of(false)
821
696
  });
822
- this._remoteStates.set(message.state.peerId, message.state);
823
- this.remoteStateChange.emit();
824
697
  }
825
698
  };
826
699
 
827
- // packages/ui/react-ui-editor/src/extensions/blast.ts
828
- import { EditorView as EditorView4, keymap as keymap2 } from "@codemirror/view";
829
- import defaultsDeep from "lodash.defaultsdeep";
830
- import { invariant as invariant2 } from "@dxos/invariant";
831
- var __dxlog_file4 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/blast.ts";
832
- var defaultOptions = {
833
- effect: 2,
834
- maxParticles: 200,
835
- particleGravity: 0.08,
836
- particleAlphaFadeout: 0.996,
837
- particleSize: {
838
- min: 2,
839
- max: 8
840
- },
841
- particleNumRange: {
842
- min: 5,
843
- max: 10
844
- },
845
- particleVelocityRange: {
846
- x: [
847
- -1,
848
- 1
849
- ],
850
- y: [
851
- -3.5,
852
- -1.5
853
- ]
854
- },
855
- particleShrinkRate: 0.95,
856
- shakeIntensity: 5
857
- };
858
- var blast = (options = defaultOptions) => {
859
- let blaster;
860
- let last = 0;
861
- const getPoint = (view, pos) => {
862
- const { left: x = 0, top: y = 0 } = view.coordsForChar(pos) ?? {};
863
- const element = document.elementFromPoint(x, y);
864
- const { offsetLeft: left = 0, offsetTop: top = 0 } = view.scrollDOM.parentElement ?? {};
865
- const point = {
866
- x: x - left,
867
- y: y - top
868
- };
869
- return {
870
- element,
871
- point
872
- };
873
- };
874
- return [
875
- // Cursor moved.
876
- EditorView4.updateListener.of((update2) => {
877
- if (blaster?.node !== update2.view.scrollDOM) {
878
- if (blaster) {
879
- blaster.destroy();
700
+ // packages/ui/react-ui-editor/src/extensions/automerge/automerge.ts
701
+ var automerge = (accessor) => {
702
+ const syncState = StateField2.define({
703
+ create: () => ({
704
+ path: accessor.path.slice(),
705
+ lastHeads: A3.getHeads(accessor.handle.docSync()),
706
+ unreconciledTransactions: []
707
+ }),
708
+ update: (value, tr) => {
709
+ const result = {
710
+ path: accessor.path.slice(),
711
+ lastHeads: value.lastHeads,
712
+ unreconciledTransactions: value.unreconciledTransactions.slice()
713
+ };
714
+ let clearUnreconciled = false;
715
+ for (const effect of tr.effects) {
716
+ if (effect.is(updateHeadsEffect)) {
717
+ result.lastHeads = effect.value.newHeads;
718
+ clearUnreconciled = true;
880
719
  }
881
- blaster = new Blaster(update2.view.scrollDOM, defaultsDeep({
882
- particleGravity: 0.2,
883
- particleShrinkRate: 0.995,
884
- color: () => [
885
- 100 + Math.random() * 100,
886
- 0,
887
- 0
888
- ]
889
- }, options, defaultOptions));
890
- blaster.initialize();
891
- blaster.start();
892
- } else {
893
- blaster.resize();
894
720
  }
895
- const current = update2.state.selection.main.head;
896
- if (current !== last) {
897
- last = current;
898
- const { element, point } = getPoint(update2.view, current - 1);
899
- if (element && point) {
900
- blaster.spawn({
901
- element,
902
- point
903
- });
721
+ if (clearUnreconciled) {
722
+ result.unreconciledTransactions = [];
723
+ } else {
724
+ if (!isReconcile(tr)) {
725
+ result.unreconciledTransactions.push(tr);
904
726
  }
905
727
  }
728
+ return result;
729
+ }
730
+ });
731
+ const syncer = new Syncer(accessor.handle, syncState);
732
+ return [
733
+ Cursor.converter.of(cursorConverter(accessor)),
734
+ // Track heads.
735
+ syncState,
736
+ // Reconcile external updates.
737
+ ViewPlugin.fromClass(class {
738
+ constructor(_view) {
739
+ this._view = _view;
740
+ this._handleChange = () => {
741
+ syncer.reconcile(this._view, false);
742
+ };
743
+ accessor.handle.addListener("change", this._handleChange);
744
+ }
745
+ destroy() {
746
+ accessor.handle.removeListener("change", this._handleChange);
747
+ }
906
748
  }),
907
- keymap2.of([
908
- {
909
- any: (_, event) => {
910
- if (blaster) {
911
- if (event.key === "Enter" && event.shiftKey) {
912
- blaster.shake({
913
- time: 0.8
914
- });
915
- return true;
916
- }
917
- }
918
- return false;
919
- }
749
+ // Reconcile local updates.
750
+ EditorView2.updateListener.of(({ view, changes }) => {
751
+ if (!changes.empty) {
752
+ syncer.reconcile(view, true);
920
753
  }
921
- ])
754
+ })
922
755
  ];
923
756
  };
924
- var Blaster = class {
925
- constructor(_node, _options) {
926
- this._node = _node;
927
- this._options = _options;
928
- this._running = false;
929
- this._shakeTime = 0;
930
- this._shakeTimeMax = 0;
931
- this._particles = [];
932
- this._particlePointer = 0;
933
- this._lastPoint = {
934
- x: 0,
935
- y: 0
936
- };
937
- this.shake = throttle(({ time }) => {
938
- this._shakeTime = this._shakeTimeMax || time;
939
- this._shakeTimeMax = time;
940
- }, 100);
941
- this.spawn = throttle(({ element, point }) => {
942
- const color = getRGBComponents(element, this._options.color);
943
- const numParticles = random(this._options.particleNumRange.min, this._options.particleNumRange.max);
944
- const dir = this._lastPoint.x === point.x ? 0 : this._lastPoint.x < point.x ? 1 : -1;
945
- this._lastPoint = point;
946
- for (let i = numParticles; i--; i > 0) {
947
- this._particles[this._particlePointer] = this._effect.create(point.x - dir * 16, point.y, color);
948
- this._particlePointer = (this._particlePointer + 1) % this._options.maxParticles;
949
- }
950
- }, 100);
951
- this._effect = this._options.effect === 1 ? new Effect1(_options) : new Effect2(_options);
952
- }
953
- get node() {
954
- return this._node;
757
+
758
+ // packages/ui/react-ui-editor/src/extensions/awareness/awareness.ts
759
+ import { Annotation as Annotation2, Facet as Facet2, RangeSet } from "@codemirror/state";
760
+ import { Decoration as Decoration2, EditorView as EditorView3, ViewPlugin as ViewPlugin2, WidgetType } from "@codemirror/view";
761
+ import { Event } from "@dxos/async";
762
+ import { Context } from "@dxos/context";
763
+ var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/awareness/awareness.ts";
764
+ var dummyProvider = {
765
+ remoteStateChange: new Event(),
766
+ open: () => {
767
+ },
768
+ close: () => {
769
+ },
770
+ getRemoteStates: () => [],
771
+ update: () => {
955
772
  }
956
- initialize() {
957
- invariant2(!this._canvas && !this._ctx, void 0, {
958
- F: __dxlog_file4,
959
- L: 141,
960
- S: this,
961
- A: [
962
- "!this._canvas && !this._ctx",
963
- ""
964
- ]
773
+ };
774
+ var awarenessProvider = Facet2.define({
775
+ combine: (providers) => providers[0] ?? dummyProvider
776
+ });
777
+ var RemoteSelectionChangedAnnotation = Annotation2.define();
778
+ var awareness = (provider = dummyProvider) => {
779
+ return [
780
+ awarenessProvider.of(provider),
781
+ ViewPlugin2.fromClass(RemoteSelectionsDecorator, {
782
+ decorations: (value) => value.decorations
783
+ }),
784
+ styles2
785
+ ];
786
+ };
787
+ var RemoteSelectionsDecorator = class {
788
+ constructor(view) {
789
+ this._ctx = new Context(void 0, {
790
+ F: __dxlog_file2,
791
+ L: 82
792
+ });
793
+ this.decorations = RangeSet.of([]);
794
+ this._cursorConverter = view.state.facet(Cursor.converter);
795
+ this._provider = view.state.facet(awarenessProvider);
796
+ this._provider.open();
797
+ this._provider.remoteStateChange.on(this._ctx, () => {
798
+ view.dispatch({
799
+ annotations: [
800
+ RemoteSelectionChangedAnnotation.of([])
801
+ ]
802
+ });
965
803
  });
966
- this._canvas = document.createElement("canvas");
967
- this._canvas.id = "code-blast-canvas";
968
- this._canvas.style.position = "absolute";
969
- this._canvas.style.zIndex = "0";
970
- this._canvas.style.pointerEvents = "none";
971
- this._ctx = this._canvas.getContext("2d");
972
- const parent = this._node.parentElement?.parentElement;
973
- parent?.appendChild(this._canvas);
974
- this.resize();
975
804
  }
976
805
  destroy() {
977
- this.stop();
978
- if (this._canvas) {
979
- this._canvas.remove();
980
- this._canvas = void 0;
981
- this._ctx = void 0;
982
- }
983
- }
984
- resize() {
985
- if (this._node.parentElement && this._canvas) {
986
- const { offsetLeft: x, offsetTop: y, offsetWidth: width, offsetHeight: height } = this._node.parentElement;
987
- this._canvas.style.top = `${y}px`;
988
- this._canvas.style.left = `${x}px`;
989
- this._canvas.width = width;
990
- this._canvas.height = height;
991
- }
992
- }
993
- start() {
994
- invariant2(this._canvas && this._ctx, void 0, {
995
- F: __dxlog_file4,
996
- L: 180,
997
- S: this,
998
- A: [
999
- "this._canvas && this._ctx",
1000
- ""
1001
- ]
1002
- });
1003
- this._running = true;
1004
- this.loop();
806
+ void this._ctx.dispose();
807
+ this._provider.close();
1005
808
  }
1006
- stop() {
1007
- this._running = false;
1008
- this._node.style.transform = "translate(0px, 0px)";
809
+ update(update2) {
810
+ this._updateLocalSelection(update2.view);
811
+ this._updateRemoteSelections(update2.view);
1009
812
  }
1010
- loop() {
1011
- if (!this._running || !this._canvas || !this._ctx) {
813
+ _updateLocalSelection(view) {
814
+ const hasFocus = view.hasFocus && view.dom.ownerDocument.hasFocus();
815
+ const { anchor = void 0, head = void 0 } = hasFocus ? view.state.selection.main : {};
816
+ if (this._lastAnchor === anchor && this._lastHead === head) {
1012
817
  return;
1013
818
  }
1014
- this._ctx.clearRect(0, 0, this._canvas.width ?? 0, this._canvas.height ?? 0);
1015
- const now = (/* @__PURE__ */ new Date()).getTime();
1016
- this._lastTime ??= now;
1017
- const dt = (now - this._lastTime) / 1e3;
1018
- this._lastTime = now;
1019
- if (this._shakeTime > 0) {
1020
- this._shakeTime -= dt;
1021
- const magnitude = this._shakeTime / this._shakeTimeMax * this._options.shakeIntensity;
1022
- const shakeX = random(-magnitude, magnitude);
1023
- const shakeY = random(-magnitude, magnitude);
1024
- this._node.style.transform = `translate(${shakeX}px,${shakeY}px)`;
1025
- }
1026
- this.drawParticles();
1027
- requestAnimationFrame(this.loop.bind(this));
819
+ this._lastAnchor = anchor;
820
+ this._lastHead = head;
821
+ this._provider.update(anchor !== void 0 && head !== void 0 ? {
822
+ anchor: this._cursorConverter.toCursor(anchor),
823
+ head: this._cursorConverter.toCursor(head, -1)
824
+ } : void 0);
1028
825
  }
1029
- drawParticles() {
1030
- for (let i = this._particles.length; i--; i > 0) {
1031
- const particle = this._particles[i];
1032
- if (!particle) {
826
+ _updateRemoteSelections(view) {
827
+ const decorations = [];
828
+ const awarenessStates = this._provider.getRemoteStates();
829
+ for (const state2 of awarenessStates) {
830
+ const anchor = state2.position?.anchor ? this._cursorConverter.fromCursor(state2.position.anchor) : null;
831
+ const head = state2.position?.head ? this._cursorConverter.fromCursor(state2.position.head) : null;
832
+ if (anchor == null || head == null) {
1033
833
  continue;
1034
834
  }
1035
- if (particle.alpha < 0.01 || particle.size <= 0.5) {
1036
- continue;
835
+ const start = Math.min(Math.min(anchor, head), view.state.doc.length);
836
+ const end = Math.min(Math.max(anchor, head), view.state.doc.length);
837
+ const startLine = view.state.doc.lineAt(start);
838
+ const endLine = view.state.doc.lineAt(end);
839
+ const darkColor = state2.info.darkColor;
840
+ const lightColor = state2.info.lightColor;
841
+ if (startLine.number === endLine.number) {
842
+ decorations.push({
843
+ from: start,
844
+ to: end,
845
+ value: Decoration2.mark({
846
+ attributes: {
847
+ style: `background-color: ${lightColor}`
848
+ },
849
+ class: "cm-collab-selection"
850
+ })
851
+ });
852
+ } else {
853
+ decorations.push({
854
+ from: start,
855
+ to: startLine.from + startLine.length,
856
+ value: Decoration2.mark({
857
+ attributes: {
858
+ style: `background-color: ${lightColor}`
859
+ },
860
+ class: "cm-collab-selection"
861
+ })
862
+ });
863
+ decorations.push({
864
+ from: endLine.from,
865
+ to: end,
866
+ value: Decoration2.mark({
867
+ attributes: {
868
+ style: `background-color: ${lightColor}`
869
+ },
870
+ class: "cm-collab-selection"
871
+ })
872
+ });
873
+ for (let i = startLine.number + 1; i < endLine.number; i++) {
874
+ const linePos = view.state.doc.line(i).from;
875
+ decorations.push({
876
+ from: linePos,
877
+ to: linePos,
878
+ value: Decoration2.line({
879
+ attributes: {
880
+ style: `background-color: ${lightColor}`,
881
+ class: "cm-collab-selectionLine"
882
+ }
883
+ })
884
+ });
885
+ }
1037
886
  }
1038
- this._effect.update(this._ctx, particle);
887
+ decorations.push({
888
+ from: head,
889
+ to: head,
890
+ value: Decoration2.widget({
891
+ side: head - anchor > 0 ? -1 : 1,
892
+ block: false,
893
+ widget: new RemoteCaretWidget(state2.info.displayName ?? "Anonymous", darkColor)
894
+ })
895
+ });
1039
896
  }
897
+ this.decorations = Decoration2.set(decorations, true);
1040
898
  }
1041
899
  };
1042
- var Effect = class {
1043
- constructor(_options) {
1044
- this._options = _options;
1045
- }
1046
- };
1047
- var Effect1 = class extends Effect {
1048
- create(x, y, color) {
1049
- return {
1050
- x,
1051
- y: y + 10,
1052
- vx: random(this._options.particleVelocityRange.x[0], this._options.particleVelocityRange.x[1]),
1053
- vy: random(this._options.particleVelocityRange.y[0], this._options.particleVelocityRange.y[1]),
1054
- size: random(this._options.particleSize.min, this._options.particleSize.max),
1055
- color,
1056
- alpha: 1
1057
- };
1058
- }
1059
- update(ctx, particle) {
1060
- particle.vy += this._options.particleGravity;
1061
- particle.x += particle.vx;
1062
- particle.y += particle.vy;
1063
- particle.alpha *= this._options.particleAlphaFadeout;
1064
- ctx.fillStyle = `rgba(${particle.color[0]},${particle.color[1]},${particle.color[2]},${particle.alpha})`;
1065
- ctx.fillRect(Math.round(particle.x - 1), Math.round(particle.y - 1), particle.size, particle.size);
1066
- }
1067
- };
1068
- var Effect2 = class extends Effect {
1069
- create(x, y, color) {
1070
- return {
1071
- x,
1072
- y: y + 10,
1073
- vx: random(this._options.particleVelocityRange.x[0], this._options.particleVelocityRange.x[1]),
1074
- vy: random(this._options.particleVelocityRange.y[0], this._options.particleVelocityRange.y[1]),
1075
- size: random(this._options.particleSize.min, this._options.particleSize.max),
1076
- color,
1077
- alpha: 1,
1078
- theta: random(0, 360) * Math.PI / 180,
1079
- drag: 0.92,
1080
- wander: 0.15
1081
- };
900
+ var RemoteCaretWidget = class extends WidgetType {
901
+ constructor(_name, _color) {
902
+ super();
903
+ this._name = _name;
904
+ this._color = _color;
1082
905
  }
1083
- update(ctx, particle) {
1084
- particle.vy += this._options.particleGravity;
1085
- particle.x += particle.vx;
1086
- particle.y += particle.vy;
1087
- particle.vx *= particle.drag;
1088
- particle.vy *= particle.drag;
1089
- particle.theta += random(-0.5, 0.5);
1090
- particle.vx += Math.sin(particle.theta) * 0.1;
1091
- particle.vy += Math.cos(particle.theta) * 0.1;
1092
- particle.size *= this._options.particleShrinkRate;
1093
- particle.alpha *= this._options.particleAlphaFadeout;
1094
- ctx.fillStyle = `rgba(${particle.color[0]},${particle.color[1]},${particle.color[2]},${particle.alpha})`;
1095
- ctx.beginPath();
1096
- ctx.arc(Math.round(particle.x - 1), Math.round(particle.y - 1), particle.size, 0, 2 * Math.PI);
1097
- ctx.fill();
906
+ toDOM() {
907
+ const span = document.createElement("span");
908
+ span.className = "cm-collab-selectionCaret";
909
+ span.style.backgroundColor = this._color;
910
+ span.style.borderColor = this._color;
911
+ const dot = document.createElement("div");
912
+ dot.className = "cm-collab-selectionCaretDot";
913
+ const info = document.createElement("div");
914
+ info.className = "cm-collab-selectionInfo";
915
+ info.innerText = this._name;
916
+ span.appendChild(document.createTextNode("\u2060"));
917
+ span.appendChild(dot);
918
+ span.appendChild(document.createTextNode("\u2060"));
919
+ span.appendChild(info);
920
+ span.appendChild(document.createTextNode("\u2060"));
921
+ return span;
1098
922
  }
1099
- };
1100
- function throttle(callback, limit) {
1101
- let wait = false;
1102
- return function(...args) {
1103
- if (!wait) {
1104
- callback.apply(this, args);
1105
- wait = true;
1106
- setTimeout(() => {
1107
- wait = false;
1108
- }, limit);
1109
- }
1110
- };
1111
- }
1112
- var getRGBComponents = (node, color) => {
1113
- if (typeof color === "function") {
1114
- return color();
923
+ updateDOM() {
924
+ return false;
1115
925
  }
1116
- const bgColor = getComputedStyle(node).color;
1117
- if (bgColor) {
1118
- const x = bgColor.match(/(\d+), (\d+), (\d+)/)?.slice(1);
1119
- if (x) {
1120
- return x;
1121
- }
926
+ eq(widget) {
927
+ return widget._color === this._color;
1122
928
  }
1123
- return [
1124
- 50,
1125
- 50,
1126
- 50
1127
- ];
1128
- };
1129
- var random = (min, max) => {
1130
- if (!max) {
1131
- max = min;
1132
- min = 0;
929
+ get estimatedHeight() {
930
+ return -1;
931
+ }
932
+ ignoreEvent() {
933
+ return true;
1133
934
  }
1134
- return min + ~~(Math.random() * (max - min + 1));
1135
935
  };
1136
-
1137
- // packages/ui/react-ui-editor/src/extensions/command/command.ts
1138
- import { EditorView as EditorView6, keymap as keymap3 } from "@codemirror/view";
1139
-
1140
- // packages/ui/react-ui-editor/src/extensions/command/hint.ts
1141
- import { RangeSetBuilder } from "@codemirror/state";
1142
- import { Decoration as Decoration3, EditorView as EditorView5, ViewPlugin as ViewPlugin3, WidgetType as WidgetType2 } from "@codemirror/view";
1143
-
1144
- // packages/ui/react-ui-editor/src/extensions/command/state.ts
1145
- import { Facet as Facet3, StateEffect as StateEffect2, StateField as StateField3 } from "@codemirror/state";
1146
- import { showTooltip } from "@codemirror/view";
1147
- var commandConfig = Facet3.define({
1148
- combine: (providers) => providers[0]
1149
- });
1150
- var commandState = StateField3.define({
1151
- create: () => ({}),
1152
- update: (state2, tr) => {
1153
- for (const effect of tr.effects) {
1154
- if (effect.is(closeEffect)) {
1155
- return {};
1156
- }
1157
- if (effect.is(openEffect)) {
1158
- const options = tr.state.facet(commandConfig);
1159
- const { pos, fullWidth } = effect.value;
1160
- const tooltip = {
1161
- pos,
1162
- above: false,
1163
- arrow: false,
1164
- strictSide: true,
1165
- create: (view) => {
1166
- const dom = document.createElement("div");
1167
- const tooltipView = {
1168
- dom,
1169
- mount: (view2) => {
1170
- if (fullWidth) {
1171
- const parent = dom.parentElement;
1172
- const { paddingLeft, paddingRight } = window.getComputedStyle(parent);
1173
- const widthWithoutPadding = parent.clientWidth - parseFloat(paddingLeft) - parseFloat(paddingRight);
1174
- dom.style.width = `${widthWithoutPadding}px`;
1175
- }
1176
- options.onRender(dom, (action) => {
1177
- view2.dispatch({
1178
- effects: closeEffect.of(null)
1179
- });
1180
- if (action?.insert?.length) {
1181
- view2.dispatch({
1182
- changes: {
1183
- from: pos,
1184
- insert: action.insert
1185
- },
1186
- selection: {
1187
- anchor: pos + action.insert.length
1188
- }
1189
- });
1190
- }
1191
- requestAnimationFrame(() => view2.focus());
1192
- });
1193
- }
1194
- };
1195
- return tooltipView;
1196
- }
1197
- };
1198
- return {
1199
- tooltip
1200
- };
1201
- }
1202
- }
1203
- return state2;
936
+ var styles2 = EditorView3.baseTheme({
937
+ ".cm-collab-selection": {},
938
+ ".cm-collab-selectionLine": {
939
+ padding: 0,
940
+ margin: "0px 2px 0px 4px"
1204
941
  },
1205
- provide: (field) => [
1206
- showTooltip.from(field, (value) => value.tooltip ?? null)
1207
- ]
942
+ ".cm-collab-selectionCaret": {
943
+ position: "relative",
944
+ borderLeft: "1px solid black",
945
+ borderRight: "1px solid black",
946
+ marginLeft: "-1px",
947
+ marginRight: "-1px",
948
+ boxSizing: "border-box",
949
+ display: "inline",
950
+ cursor: "pointer"
951
+ },
952
+ ".cm-collab-selectionCaretDot": {
953
+ borderRadius: "50%",
954
+ position: "absolute",
955
+ width: ".5em",
956
+ height: ".5em",
957
+ top: "-.25em",
958
+ left: "-.25em",
959
+ backgroundColor: "inherit",
960
+ transition: "transform .3s ease-in-out",
961
+ boxSizing: "border-box"
962
+ },
963
+ ".cm-collab-selectionCaret:hover > .cm-collab-selectionCaretDot": {
964
+ transform: "scale(0)",
965
+ transformOrigin: "center"
966
+ },
967
+ ".cm-collab-selectionInfo": {
968
+ position: "absolute",
969
+ transform: "translate(-50%, 0)",
970
+ top: "-20px",
971
+ left: 0,
972
+ fontSize: ".75em",
973
+ fontFamily: "sans-serif",
974
+ fontStyle: "normal",
975
+ fontWeight: "normal",
976
+ lineHeight: "normal",
977
+ userSelect: "none",
978
+ color: "white",
979
+ padding: "2px 6px",
980
+ zIndex: 101,
981
+ transition: "opacity .3s ease-in-out",
982
+ backgroundColor: "inherit",
983
+ borderRadius: "2px",
984
+ opacity: 0,
985
+ transitionDelay: "0s",
986
+ whiteSpace: "nowrap"
987
+ },
988
+ ".cm-collab-selectionCaret:hover > .cm-collab-selectionInfo": {
989
+ opacity: 1,
990
+ transitionDelay: "0s"
991
+ }
1208
992
  });
1209
- var openEffect = StateEffect2.define();
1210
- var closeEffect = StateEffect2.define();
1211
- var openCommand = (view) => {
1212
- if (view.state.field(commandState, false)) {
1213
- const selection = view.state.selection.main;
1214
- const line = view.state.doc.lineAt(selection.from);
1215
- if (line.from === selection.from && line.from === line.to) {
1216
- view.dispatch({
1217
- effects: openEffect.of({
1218
- pos: selection.anchor,
1219
- fullWidth: true
1220
- })
993
+
994
+ // packages/ui/react-ui-editor/src/extensions/awareness/awareness-provider.ts
995
+ import { DeferredTask, Event as Event2, sleep } from "@dxos/async";
996
+ import { Context as Context2 } from "@dxos/context";
997
+ import { invariant } from "@dxos/invariant";
998
+ import { log as log2 } from "@dxos/log";
999
+ var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/awareness/awareness-provider.ts";
1000
+ var DEBOUNCE_INTERVAL = 100;
1001
+ var SpaceAwarenessProvider = class {
1002
+ constructor(params) {
1003
+ this._remoteStates = /* @__PURE__ */ new Map();
1004
+ this.remoteStateChange = new Event2();
1005
+ this._space = params.space;
1006
+ this._channel = params.channel;
1007
+ this._peerId = params.peerId;
1008
+ this._info = params.info;
1009
+ }
1010
+ open() {
1011
+ this._ctx = new Context2(void 0, {
1012
+ F: __dxlog_file3,
1013
+ L: 57
1014
+ });
1015
+ this._postTask = new DeferredTask(this._ctx, async () => {
1016
+ if (this._localState) {
1017
+ await this._space.postMessage(this._channel, {
1018
+ kind: "post",
1019
+ state: this._localState
1020
+ });
1021
+ await sleep(DEBOUNCE_INTERVAL);
1022
+ }
1023
+ });
1024
+ this._ctx.onDispose(this._space.listen(this._channel, (message) => {
1025
+ switch (message.payload.kind) {
1026
+ case "query": {
1027
+ this._handleQueryMessage();
1028
+ break;
1029
+ }
1030
+ case "post": {
1031
+ this._handlePostMessage(message.payload);
1032
+ break;
1033
+ }
1034
+ }
1035
+ }));
1036
+ void this._space.postMessage(this._channel, {
1037
+ kind: "query"
1038
+ }).catch((err) => {
1039
+ log2.debug("failed to query awareness", {
1040
+ err
1041
+ }, {
1042
+ F: __dxlog_file3,
1043
+ L: 91,
1044
+ S: this,
1045
+ C: (f, a) => f(...a)
1221
1046
  });
1222
- return true;
1223
- }
1047
+ });
1224
1048
  }
1225
- return false;
1226
- };
1227
- var closeCommand = (view) => {
1228
- if (view.state.field(commandState, false)) {
1229
- view.dispatch({
1230
- effects: closeEffect.of(null)
1049
+ close() {
1050
+ void this._ctx?.dispose();
1051
+ this._ctx = void 0;
1052
+ this._postTask = void 0;
1053
+ }
1054
+ getRemoteStates() {
1055
+ return Array.from(this._remoteStates.values());
1056
+ }
1057
+ update(position) {
1058
+ invariant(this._postTask, void 0, {
1059
+ F: __dxlog_file3,
1060
+ L: 106,
1061
+ S: this,
1062
+ A: [
1063
+ "this._postTask",
1064
+ ""
1065
+ ]
1231
1066
  });
1232
- return true;
1067
+ this._localState = {
1068
+ peerId: this._peerId,
1069
+ position,
1070
+ info: this._info
1071
+ };
1072
+ this._postTask.schedule();
1233
1073
  }
1234
- return false;
1235
- };
1236
- var commandKeyBindings = [
1237
- {
1238
- key: "/",
1239
- run: openCommand
1240
- },
1241
- {
1242
- key: "Escape",
1243
- run: closeCommand
1074
+ _handleQueryMessage() {
1075
+ invariant(this._postTask, void 0, {
1076
+ F: __dxlog_file3,
1077
+ L: 117,
1078
+ S: this,
1079
+ A: [
1080
+ "this._postTask",
1081
+ ""
1082
+ ]
1083
+ });
1084
+ this._postTask.schedule();
1244
1085
  }
1245
- ];
1086
+ _handlePostMessage(message) {
1087
+ invariant(message.kind === "post", void 0, {
1088
+ F: __dxlog_file3,
1089
+ L: 122,
1090
+ S: this,
1091
+ A: [
1092
+ "message.kind === 'post'",
1093
+ ""
1094
+ ]
1095
+ });
1096
+ this._remoteStates.set(message.state.peerId, message.state);
1097
+ this.remoteStateChange.emit();
1098
+ }
1099
+ };
1246
1100
 
1247
- // packages/ui/react-ui-editor/src/extensions/util/dom.ts
1248
- var flattenRect = (rect, left) => {
1249
- const x = left ? rect.left : rect.right;
1250
- return {
1251
- left: x,
1252
- right: x,
1253
- top: rect.top,
1254
- bottom: rect.bottom
1255
- };
1101
+ // packages/ui/react-ui-editor/src/extensions/blast.ts
1102
+ import { EditorView as EditorView4, keymap as keymap2 } from "@codemirror/view";
1103
+ import defaultsDeep from "lodash.defaultsdeep";
1104
+ import { invariant as invariant2 } from "@dxos/invariant";
1105
+ var __dxlog_file4 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/blast.ts";
1106
+ var defaultOptions = {
1107
+ effect: 2,
1108
+ maxParticles: 200,
1109
+ particleGravity: 0.08,
1110
+ particleAlphaFadeout: 0.996,
1111
+ particleSize: {
1112
+ min: 2,
1113
+ max: 8
1114
+ },
1115
+ particleNumRange: {
1116
+ min: 5,
1117
+ max: 10
1118
+ },
1119
+ particleVelocityRange: {
1120
+ x: [
1121
+ -1,
1122
+ 1
1123
+ ],
1124
+ y: [
1125
+ -3.5,
1126
+ -1.5
1127
+ ]
1128
+ },
1129
+ particleShrinkRate: 0.95,
1130
+ shakeIntensity: 5
1256
1131
  };
1257
- var scratchRange;
1258
- var textRange = (node, from, to = from) => {
1259
- const range = scratchRange || (scratchRange = document.createRange());
1260
- range.setEnd(node, to);
1261
- range.setStart(node, from);
1262
- return range;
1132
+ var blast = (options = defaultOptions) => {
1133
+ let blaster;
1134
+ let last = 0;
1135
+ const getPoint = (view, pos) => {
1136
+ const { left: x = 0, top: y = 0 } = view.coordsForChar(pos) ?? {};
1137
+ const element = document.elementFromPoint(x, y);
1138
+ const { offsetLeft: left = 0, offsetTop: top = 0 } = view.scrollDOM.parentElement ?? {};
1139
+ const point = {
1140
+ x: x - left,
1141
+ y: y - top
1142
+ };
1143
+ return {
1144
+ element,
1145
+ point
1146
+ };
1147
+ };
1148
+ return [
1149
+ // Cursor moved.
1150
+ EditorView4.updateListener.of((update2) => {
1151
+ if (blaster?.node !== update2.view.scrollDOM) {
1152
+ if (blaster) {
1153
+ blaster.destroy();
1154
+ }
1155
+ blaster = new Blaster(update2.view.scrollDOM, defaultsDeep({
1156
+ particleGravity: 0.2,
1157
+ particleShrinkRate: 0.995,
1158
+ color: () => [
1159
+ 100 + Math.random() * 100,
1160
+ 0,
1161
+ 0
1162
+ ]
1163
+ }, options, defaultOptions));
1164
+ blaster.initialize();
1165
+ blaster.start();
1166
+ } else {
1167
+ blaster.resize();
1168
+ }
1169
+ const current = update2.state.selection.main.head;
1170
+ if (current !== last) {
1171
+ last = current;
1172
+ const { element, point } = getPoint(update2.view, current - 1);
1173
+ if (element && point) {
1174
+ blaster.spawn({
1175
+ element,
1176
+ point
1177
+ });
1178
+ }
1179
+ }
1180
+ }),
1181
+ keymap2.of([
1182
+ {
1183
+ any: (_, event) => {
1184
+ if (blaster) {
1185
+ if (event.key === "Enter" && event.shiftKey) {
1186
+ blaster.shake({
1187
+ time: 0.8
1188
+ });
1189
+ return true;
1190
+ }
1191
+ }
1192
+ return false;
1193
+ }
1194
+ }
1195
+ ])
1196
+ ];
1263
1197
  };
1264
- var clientRectsFor = (dom) => {
1265
- if (dom.nodeType === 3) {
1266
- return textRange(dom, 0, dom.nodeValue.length).getClientRects();
1267
- } else if (dom.nodeType === 1) {
1268
- return dom.getClientRects();
1269
- } else {
1270
- return [];
1198
+ var Blaster = class {
1199
+ constructor(_node, _options) {
1200
+ this._node = _node;
1201
+ this._options = _options;
1202
+ this._running = false;
1203
+ this._shakeTime = 0;
1204
+ this._shakeTimeMax = 0;
1205
+ this._particles = [];
1206
+ this._particlePointer = 0;
1207
+ this._lastPoint = {
1208
+ x: 0,
1209
+ y: 0
1210
+ };
1211
+ this.shake = throttle(({ time }) => {
1212
+ this._shakeTime = this._shakeTimeMax || time;
1213
+ this._shakeTimeMax = time;
1214
+ }, 100);
1215
+ this.spawn = throttle(({ element, point }) => {
1216
+ const color = getRGBComponents(element, this._options.color);
1217
+ const numParticles = random(this._options.particleNumRange.min, this._options.particleNumRange.max);
1218
+ const dir = this._lastPoint.x === point.x ? 0 : this._lastPoint.x < point.x ? 1 : -1;
1219
+ this._lastPoint = point;
1220
+ for (let i = numParticles; i--; i > 0) {
1221
+ this._particles[this._particlePointer] = this._effect.create(point.x - dir * 16, point.y, color);
1222
+ this._particlePointer = (this._particlePointer + 1) % this._options.maxParticles;
1223
+ }
1224
+ }, 100);
1225
+ this._effect = this._options.effect === 1 ? new Effect1(_options) : new Effect2(_options);
1226
+ }
1227
+ get node() {
1228
+ return this._node;
1229
+ }
1230
+ initialize() {
1231
+ invariant2(!this._canvas && !this._ctx, void 0, {
1232
+ F: __dxlog_file4,
1233
+ L: 141,
1234
+ S: this,
1235
+ A: [
1236
+ "!this._canvas && !this._ctx",
1237
+ ""
1238
+ ]
1239
+ });
1240
+ this._canvas = document.createElement("canvas");
1241
+ this._canvas.id = "code-blast-canvas";
1242
+ this._canvas.style.position = "absolute";
1243
+ this._canvas.style.zIndex = "0";
1244
+ this._canvas.style.pointerEvents = "none";
1245
+ this._ctx = this._canvas.getContext("2d");
1246
+ const parent = this._node.parentElement?.parentElement;
1247
+ parent?.appendChild(this._canvas);
1248
+ this.resize();
1271
1249
  }
1272
- };
1273
-
1274
- // packages/ui/react-ui-editor/src/extensions/command/hint.ts
1275
- var CommandHint = class extends WidgetType2 {
1276
- constructor(content) {
1277
- super();
1278
- this.content = content;
1250
+ destroy() {
1251
+ this.stop();
1252
+ if (this._canvas) {
1253
+ this._canvas.remove();
1254
+ this._canvas = void 0;
1255
+ this._ctx = void 0;
1256
+ }
1279
1257
  }
1280
- toDOM() {
1281
- const wrap = document.createElement("span");
1282
- wrap.className = "cm-placeholder";
1283
- wrap.style.pointerEvents = "none";
1284
- wrap.appendChild(typeof this.content === "string" ? document.createTextNode(this.content) : this.content);
1285
- if (typeof this.content === "string") {
1286
- wrap.setAttribute("aria-label", "placeholder " + this.content);
1287
- } else {
1288
- wrap.setAttribute("aria-hidden", "true");
1258
+ resize() {
1259
+ if (this._node.parentElement && this._canvas) {
1260
+ const { offsetLeft: x, offsetTop: y, offsetWidth: width, offsetHeight: height } = this._node.parentElement;
1261
+ this._canvas.style.top = `${y}px`;
1262
+ this._canvas.style.left = `${x}px`;
1263
+ this._canvas.width = width;
1264
+ this._canvas.height = height;
1289
1265
  }
1290
- return wrap;
1291
1266
  }
1292
- coordsAt(dom) {
1293
- const rects = dom.firstChild ? clientRectsFor(dom.firstChild) : [];
1294
- if (!rects.length) {
1295
- return null;
1267
+ start() {
1268
+ invariant2(this._canvas && this._ctx, void 0, {
1269
+ F: __dxlog_file4,
1270
+ L: 180,
1271
+ S: this,
1272
+ A: [
1273
+ "this._canvas && this._ctx",
1274
+ ""
1275
+ ]
1276
+ });
1277
+ this._running = true;
1278
+ this.loop();
1279
+ }
1280
+ stop() {
1281
+ this._running = false;
1282
+ this._node.style.transform = "translate(0px, 0px)";
1283
+ }
1284
+ loop() {
1285
+ if (!this._running || !this._canvas || !this._ctx) {
1286
+ return;
1296
1287
  }
1297
- const style = window.getComputedStyle(dom.parentNode);
1298
- const rect = flattenRect(rects[0], style.direction !== "rtl");
1299
- const lineHeight = parseInt(style.lineHeight);
1300
- if (rect.bottom - rect.top > lineHeight * 1.5) {
1301
- return {
1302
- left: rect.left,
1303
- right: rect.right,
1304
- top: rect.top,
1305
- bottom: rect.top + lineHeight
1306
- };
1288
+ this._ctx.clearRect(0, 0, this._canvas.width ?? 0, this._canvas.height ?? 0);
1289
+ const now = (/* @__PURE__ */ new Date()).getTime();
1290
+ this._lastTime ??= now;
1291
+ const dt = (now - this._lastTime) / 1e3;
1292
+ this._lastTime = now;
1293
+ if (this._shakeTime > 0) {
1294
+ this._shakeTime -= dt;
1295
+ const magnitude = this._shakeTime / this._shakeTimeMax * this._options.shakeIntensity;
1296
+ const shakeX = random(-magnitude, magnitude);
1297
+ const shakeY = random(-magnitude, magnitude);
1298
+ this._node.style.transform = `translate(${shakeX}px,${shakeY}px)`;
1307
1299
  }
1308
- return rect;
1300
+ this.drawParticles();
1301
+ requestAnimationFrame(this.loop.bind(this));
1309
1302
  }
1310
- ignoreEvent() {
1311
- return false;
1303
+ drawParticles() {
1304
+ for (let i = this._particles.length; i--; i > 0) {
1305
+ const particle = this._particles[i];
1306
+ if (!particle) {
1307
+ continue;
1308
+ }
1309
+ if (particle.alpha < 0.01 || particle.size <= 0.5) {
1310
+ continue;
1311
+ }
1312
+ this._effect.update(this._ctx, particle);
1313
+ }
1312
1314
  }
1313
1315
  };
1314
- var hintViewPlugin = ({ onHint }) => ViewPlugin3.fromClass(class {
1315
- constructor() {
1316
- this.deco = Decoration3.none;
1316
+ var Effect = class {
1317
+ constructor(_options) {
1318
+ this._options = _options;
1317
1319
  }
1318
- update(update2) {
1319
- const builder = new RangeSetBuilder();
1320
- const cState = update2.view.state.field(commandState, false);
1321
- if (!cState?.tooltip) {
1322
- const selection = update2.view.state.selection.main;
1323
- const line = update2.view.state.doc.lineAt(selection.from);
1324
- if (selection.from === selection.to && line.from === line.to) {
1325
- const hint = onHint();
1326
- if (hint) {
1327
- builder.add(selection.from, selection.to, Decoration3.widget({
1328
- widget: new CommandHint(hint)
1329
- }));
1330
- }
1331
- }
1332
- }
1333
- this.deco = builder.finish();
1320
+ };
1321
+ var Effect1 = class extends Effect {
1322
+ create(x, y, color) {
1323
+ return {
1324
+ x,
1325
+ y: y + 10,
1326
+ vx: random(this._options.particleVelocityRange.x[0], this._options.particleVelocityRange.x[1]),
1327
+ vy: random(this._options.particleVelocityRange.y[0], this._options.particleVelocityRange.y[1]),
1328
+ size: random(this._options.particleSize.min, this._options.particleSize.max),
1329
+ color,
1330
+ alpha: 1
1331
+ };
1332
+ }
1333
+ update(ctx, particle) {
1334
+ particle.vy += this._options.particleGravity;
1335
+ particle.x += particle.vx;
1336
+ particle.y += particle.vy;
1337
+ particle.alpha *= this._options.particleAlphaFadeout;
1338
+ ctx.fillStyle = `rgba(${particle.color[0]},${particle.color[1]},${particle.color[2]},${particle.alpha})`;
1339
+ ctx.fillRect(Math.round(particle.x - 1), Math.round(particle.y - 1), particle.size, particle.size);
1334
1340
  }
1335
- }, {
1336
- provide: (plugin) => [
1337
- EditorView5.decorations.of((view) => view.plugin(plugin)?.deco ?? Decoration3.none)
1338
- ]
1339
- });
1340
-
1341
- // packages/ui/react-ui-editor/src/extensions/command/command.ts
1342
- var command = (options) => {
1343
- return [
1344
- commandConfig.of(options),
1345
- commandState,
1346
- keymap3.of(commandKeyBindings),
1347
- hintViewPlugin(options),
1348
- EditorView6.focusChangeEffect.of((_, focusing) => {
1349
- return focusing ? closeEffect.of(null) : null;
1350
- })
1351
- ];
1352
1341
  };
1353
-
1354
- // packages/ui/react-ui-editor/src/extensions/comments.ts
1355
- import { invertedEffects } from "@codemirror/commands";
1356
- import { Facet as Facet4, StateEffect as StateEffect3, StateField as StateField4 } from "@codemirror/state";
1357
- import { hoverTooltip, keymap as keymap4, Decoration as Decoration4, EditorView as EditorView7, ViewPlugin as ViewPlugin4 } from "@codemirror/view";
1358
- import sortBy from "lodash.sortby";
1359
- import { useEffect, useMemo, useState } from "react";
1360
- import { debounce } from "@dxos/async";
1361
- import { log as log5 } from "@dxos/log";
1362
- import { nonNullable } from "@dxos/util";
1363
-
1364
- // packages/ui/react-ui-editor/src/extensions/util/error.ts
1365
- import { log as log3 } from "@dxos/log";
1366
- var __dxlog_file5 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/util/error.ts";
1367
- var wrapWithCatch = (fn) => {
1368
- return (...args) => {
1369
- try {
1370
- return fn(...args);
1371
- } catch (err) {
1372
- log3.catch(err, void 0, {
1373
- F: __dxlog_file5,
1374
- L: 12,
1375
- S: void 0,
1376
- C: (f, a) => f(...a)
1377
- });
1342
+ var Effect2 = class extends Effect {
1343
+ create(x, y, color) {
1344
+ return {
1345
+ x,
1346
+ y: y + 10,
1347
+ vx: random(this._options.particleVelocityRange.x[0], this._options.particleVelocityRange.x[1]),
1348
+ vy: random(this._options.particleVelocityRange.y[0], this._options.particleVelocityRange.y[1]),
1349
+ size: random(this._options.particleSize.min, this._options.particleSize.max),
1350
+ color,
1351
+ alpha: 1,
1352
+ theta: random(0, 360) * Math.PI / 180,
1353
+ drag: 0.92,
1354
+ wander: 0.15
1355
+ };
1356
+ }
1357
+ update(ctx, particle) {
1358
+ particle.vy += this._options.particleGravity;
1359
+ particle.x += particle.vx;
1360
+ particle.y += particle.vy;
1361
+ particle.vx *= particle.drag;
1362
+ particle.vy *= particle.drag;
1363
+ particle.theta += random(-0.5, 0.5);
1364
+ particle.vx += Math.sin(particle.theta) * 0.1;
1365
+ particle.vy += Math.cos(particle.theta) * 0.1;
1366
+ particle.size *= this._options.particleShrinkRate;
1367
+ particle.alpha *= this._options.particleAlphaFadeout;
1368
+ ctx.fillStyle = `rgba(${particle.color[0]},${particle.color[1]},${particle.color[2]},${particle.alpha})`;
1369
+ ctx.beginPath();
1370
+ ctx.arc(Math.round(particle.x - 1), Math.round(particle.y - 1), particle.size, 0, 2 * Math.PI);
1371
+ ctx.fill();
1372
+ }
1373
+ };
1374
+ function throttle(callback, limit) {
1375
+ let wait = false;
1376
+ return function(...args) {
1377
+ if (!wait) {
1378
+ callback.apply(this, args);
1379
+ wait = true;
1380
+ setTimeout(() => {
1381
+ wait = false;
1382
+ }, limit);
1378
1383
  }
1379
1384
  };
1385
+ }
1386
+ var getRGBComponents = (node, color) => {
1387
+ if (typeof color === "function") {
1388
+ return color();
1389
+ }
1390
+ const bgColor = getComputedStyle(node).color;
1391
+ if (bgColor) {
1392
+ const x = bgColor.match(/(\d+), (\d+), (\d+)/)?.slice(1);
1393
+ if (x) {
1394
+ return x;
1395
+ }
1396
+ }
1397
+ return [
1398
+ 50,
1399
+ 50,
1400
+ 50
1401
+ ];
1380
1402
  };
1381
-
1382
- // packages/ui/react-ui-editor/src/extensions/util/overlap.ts
1383
- var overlap = (a, b) => a.from <= b.to && a.to >= b.from;
1384
-
1385
- // packages/ui/react-ui-editor/src/extensions/util/react.tsx
1386
- import React from "react";
1387
- import { createRoot } from "react-dom/client";
1388
- import { ThemeProvider } from "@dxos/react-ui";
1389
- import { defaultTx } from "@dxos/react-ui-theme";
1390
- var renderRoot = (root, node) => {
1391
- createRoot(root).render(/* @__PURE__ */ React.createElement(ThemeProvider, {
1392
- tx: defaultTx
1393
- }, node));
1394
- return root;
1395
- };
1396
-
1397
- // packages/ui/react-ui-editor/src/styles/markdown.ts
1398
- import { mx } from "@dxos/react-ui-theme";
1399
- var headings = {
1400
- 1: "text-4xl",
1401
- 2: "text-3xl",
1402
- 3: "text-2xl",
1403
- 4: "text-xl",
1404
- 5: "text-lg",
1405
- 6: "text-md"
1406
- };
1407
- var theme = {
1408
- mark: "opacity-50",
1409
- code: "font-mono !no-underline text-neutral-700 dark:text-neutral-300",
1410
- codeMark: "font-mono text-primary-500",
1411
- // TODO(burdon): Replace with widget.
1412
- blockquote: "pl-1 mr-1 border-is-4 border-orange-500 dark:border-orange-500 dark:text-neutral-500",
1413
- heading: (level) => {
1414
- return mx(headings[level], "dark:text-primary-400");
1403
+ var random = (min, max) => {
1404
+ if (!max) {
1405
+ max = min;
1406
+ min = 0;
1415
1407
  }
1408
+ return min + ~~(Math.random() * (max - min + 1));
1416
1409
  };
1417
1410
 
1418
- // packages/ui/react-ui-editor/src/styles/tokens.ts
1419
- import get from "lodash.get";
1420
- import { tailwindConfig } from "@dxos/react-ui-theme";
1421
- var tokens = tailwindConfig({}).theme;
1422
- var getToken = (path, defaultValue) => {
1423
- const value = get(tokens, path, defaultValue);
1424
- return value?.toString() ?? "";
1425
- };
1411
+ // packages/ui/react-ui-editor/src/extensions/command/command.ts
1412
+ import { EditorView as EditorView6, keymap as keymap3 } from "@codemirror/view";
1426
1413
 
1427
- // packages/ui/react-ui-editor/src/styles/theme.ts
1428
- var defaultTheme = {
1429
- "&": {},
1430
- "&.cm-focused": {
1431
- outline: "none"
1432
- },
1433
- // Scroller.
1434
- // NOTE: See https://codemirror.net/docs/guide (DOM Structure).
1435
- ".cm-scroller": {
1436
- overflowY: "auto"
1437
- },
1438
- // Content.
1439
- ".cm-content": {
1440
- padding: "unset",
1441
- // NOTE: Base font size (otherwise defined by HTML tag, which might be different for storybook).
1442
- fontSize: "16px",
1443
- fontFamily: getToken("fontFamily.body"),
1444
- lineHeight: 1.5
1445
- },
1446
- "&light .cm-content": {
1447
- color: getToken("extend.semanticColors.base.fg.light", "black")
1448
- },
1449
- "&dark .cm-content": {
1450
- color: getToken("extend.semanticColors.base.fg.dark", "white")
1451
- },
1452
- //
1453
- // Cursor
1454
- //
1455
- "&light .cm-cursor, &light .cm-dropCursor": {
1456
- borderLeft: "2px solid black"
1457
- },
1458
- "&dark .cm-cursor, &dark .cm-dropCursor": {
1459
- borderLeft: "2px solid white"
1460
- },
1461
- "&light .cm-placeholder": {
1462
- color: getToken("extend.semanticColors.description.light", "rgba(0,0,0,.2)")
1463
- },
1464
- "&dark .cm-placeholder": {
1465
- color: getToken("extend.semanticColors.description.dark", "rgba(255,255,255,.2)")
1466
- },
1467
- //
1468
- // line
1469
- //
1470
- ".cm-line": {
1471
- paddingInline: 0
1472
- },
1473
- ".cm-activeLine": {
1474
- background: "transparent"
1475
- },
1476
- //
1477
- // gutter
1478
- //
1479
- ".cm-lineNumbers": {
1480
- minWidth: "36px"
1481
- },
1482
- //
1483
- // Selection
1484
- //
1485
- "&light .cm-selectionBackground": {
1486
- background: getToken("extend.colors.primary.100")
1487
- },
1488
- "&light.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground": {
1489
- background: getToken("extend.colors.primary.200")
1490
- },
1491
- "&dark .cm-selectionBackground": {
1492
- background: getToken("extend.colors.primary.700")
1493
- },
1494
- "&dark.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground": {
1495
- background: getToken("extend.colors.primary.600")
1496
- },
1497
- //
1498
- // Search
1499
- //
1500
- "&light .cm-searchMatch": {
1501
- backgroundColor: getToken("extend.colors.yellow.100")
1502
- },
1503
- "&dark .cm-searchMatch": {
1504
- backgroundColor: getToken("extend.colors.yellow.700")
1505
- },
1506
- //
1507
- // link
1508
- //
1509
- ".cm-link": {
1510
- textDecorationLine: "underline",
1511
- textDecorationThickness: "1px",
1512
- textUnderlineOffset: "2px",
1513
- borderRadius: ".125rem",
1514
- fontFamily: getToken("fontFamily.body")
1515
- },
1516
- "&light .cm-link > span": {
1517
- color: getToken("extend.colors.primary.600")
1518
- },
1519
- "&dark .cm-link > span": {
1520
- color: getToken("extend.colors.primary.400")
1521
- },
1522
- //
1523
- // tooltip
1524
- //
1525
- ".cm-tooltip": {},
1526
- "&light .cm-tooltip": {
1527
- background: `${getToken("extend.colors.neutral.100")} !important`
1528
- },
1529
- "&dark .cm-tooltip": {
1530
- background: `${getToken("extend.colors.neutral.900")} !important`
1531
- },
1532
- ".cm-tooltip-below": {},
1533
- //
1534
- // autocomplete
1535
- // https://github.com/codemirror/autocomplete/blob/main/src/completion.ts
1536
- //
1537
- ".cm-tooltip.cm-tooltip-autocomplete": {
1538
- marginTop: "4px",
1539
- marginLeft: "-3px"
1540
- },
1541
- ".cm-tooltip.cm-tooltip-autocomplete > ul": {
1542
- maxHeight: "20em !important"
1543
- },
1544
- ".cm-tooltip.cm-tooltip-autocomplete > ul > li": {},
1545
- ".cm-tooltip.cm-tooltip-autocomplete > ul > li[aria-selected]": {},
1546
- ".cm-tooltip.cm-tooltip-autocomplete > ul > completion-section": {
1547
- paddingLeft: "4px !important",
1548
- borderBottom: "none !important",
1549
- color: getToken("extend.colors.primary.500")
1550
- },
1551
- ".cm-tooltip.cm-completionInfo": {
1552
- border: getToken("extend.colors.neutral.500"),
1553
- width: "360px !important",
1554
- margin: "-10px 1px 0 1px",
1555
- padding: "8px !important"
1556
- },
1557
- ".cm-completionIcon": {
1558
- display: "none"
1559
- },
1560
- ".cm-completionLabel": {
1561
- fontFamily: getToken("fontFamily.body")
1562
- },
1563
- ".cm-completionMatchedText": {
1564
- textDecoration: "none !important",
1565
- opacity: 0.5
1566
- },
1567
- //
1568
- // table
1569
- //
1570
- ".cm-table *": {
1571
- fontFamily: `${getToken("fontFamily.mono")} !important`,
1572
- textDecoration: "none !important"
1573
- },
1574
- ".cm-table-head": {
1575
- padding: "2px 16px 2px 0px",
1576
- textAlign: "left",
1577
- borderBottom: `1px solid ${getToken("extend.colors.primary.500")}`,
1578
- color: getToken("extend.colors.neutral.500")
1579
- },
1580
- ".cm-table-cell": {
1581
- padding: "2px 16px 2px 0px"
1582
- },
1583
- //
1584
- // image
1585
- //
1586
- ".cm-image": {
1587
- display: "block",
1588
- height: "0"
1589
- },
1590
- ".cm-image.cm-loaded-image": {
1591
- height: "auto",
1592
- borderTop: "0.5rem solid transparent",
1593
- borderBottom: "0.5rem solid transparent"
1594
- },
1595
- // TODO(burdon): Override vars --cm-background.
1596
- // https://www.npmjs.com/package/codemirror-theme-vars
1597
- /**
1598
- * Gutters
1599
- */
1600
- ".cm-gutters": {
1601
- background: "transparent"
1602
- },
1603
- /**
1604
- * Panels
1605
- * TODO(burdon): Needs styling attention (esp. dark mode).
1606
- * https://github.com/codemirror/search/blob/main/src/search.ts#L745
1607
- *
1608
- * Find/replace panel.
1609
- * <div class="cm-announced">...</div>
1610
- * <div class="cm-scroller">...</div>
1611
- * <div class="cm-panels cm-panels-bottom">
1612
- * <div class="cm-search cm-panel">
1613
- * <input class="cm-textfield" />
1614
- * <button class="cm-button">...</button>
1615
- * <label><input type="checkbox" />...</label>
1616
- * </div>
1617
- * </div
1618
- */
1619
- ".cm-panels": {},
1620
- ".cm-panel": {
1621
- fontFamily: getToken("fontFamily.body")
1622
- },
1623
- ".cm-panel input[type=checkbox]": {
1624
- marginRight: "0.4rem !important"
1625
- },
1626
- "&light .cm-panel": {
1627
- background: getToken("extend.colors.neutral.50")
1628
- },
1629
- "&dark .cm-panel": {
1630
- background: getToken("extend.colors.neutral.850")
1631
- },
1632
- ".cm-button": {
1633
- margin: "4px",
1634
- fontFamily: getToken("fontFamily.body"),
1635
- backgroundImage: "none",
1636
- border: "none",
1637
- "&:active": {
1638
- backgroundImage: "none"
1414
+ // packages/ui/react-ui-editor/src/extensions/command/hint.ts
1415
+ import { RangeSetBuilder } from "@codemirror/state";
1416
+ import { Decoration as Decoration3, EditorView as EditorView5, ViewPlugin as ViewPlugin3, WidgetType as WidgetType2 } from "@codemirror/view";
1417
+
1418
+ // packages/ui/react-ui-editor/src/extensions/command/state.ts
1419
+ import { Facet as Facet3, StateEffect as StateEffect2, StateField as StateField3 } from "@codemirror/state";
1420
+ import { showTooltip } from "@codemirror/view";
1421
+ var commandConfig = Facet3.define({
1422
+ combine: (providers) => providers[0]
1423
+ });
1424
+ var commandState = StateField3.define({
1425
+ create: () => ({}),
1426
+ update: (state2, tr) => {
1427
+ for (const effect of tr.effects) {
1428
+ if (effect.is(closeEffect)) {
1429
+ return {};
1430
+ }
1431
+ if (effect.is(openEffect)) {
1432
+ const options = tr.state.facet(commandConfig);
1433
+ const { pos, fullWidth } = effect.value;
1434
+ const tooltip = {
1435
+ pos,
1436
+ above: false,
1437
+ arrow: false,
1438
+ strictSide: true,
1439
+ create: (view) => {
1440
+ const dom = document.createElement("div");
1441
+ const tooltipView = {
1442
+ dom,
1443
+ mount: (view2) => {
1444
+ if (fullWidth) {
1445
+ const parent = dom.parentElement;
1446
+ const { paddingLeft, paddingRight } = window.getComputedStyle(parent);
1447
+ const widthWithoutPadding = parent.clientWidth - parseFloat(paddingLeft) - parseFloat(paddingRight);
1448
+ dom.style.width = `${widthWithoutPadding}px`;
1449
+ }
1450
+ options.onRender(dom, (action) => {
1451
+ view2.dispatch({
1452
+ effects: closeEffect.of(null)
1453
+ });
1454
+ if (action?.insert?.length) {
1455
+ view2.dispatch({
1456
+ changes: {
1457
+ from: pos,
1458
+ insert: action.insert
1459
+ },
1460
+ selection: {
1461
+ anchor: pos + action.insert.length
1462
+ }
1463
+ });
1464
+ }
1465
+ requestAnimationFrame(() => view2.focus());
1466
+ });
1467
+ }
1468
+ };
1469
+ return tooltipView;
1470
+ }
1471
+ };
1472
+ return {
1473
+ tooltip
1474
+ };
1475
+ }
1639
1476
  }
1477
+ return state2;
1640
1478
  },
1641
- "&light .cm-button": {
1642
- background: getToken("extend.colors.neutral.100"),
1643
- "&:hover": {
1644
- background: getToken("extend.colors.neutral.200")
1645
- },
1646
- "&:active": {
1647
- background: getToken("extend.colors.neutral.300")
1479
+ provide: (field) => [
1480
+ showTooltip.from(field, (value) => value.tooltip ?? null)
1481
+ ]
1482
+ });
1483
+ var openEffect = StateEffect2.define();
1484
+ var closeEffect = StateEffect2.define();
1485
+ var openCommand = (view) => {
1486
+ if (view.state.field(commandState, false)) {
1487
+ const selection = view.state.selection.main;
1488
+ const line = view.state.doc.lineAt(selection.from);
1489
+ if (line.from === selection.from && line.from === line.to) {
1490
+ view.dispatch({
1491
+ effects: openEffect.of({
1492
+ pos: selection.anchor,
1493
+ fullWidth: true
1494
+ })
1495
+ });
1496
+ return true;
1648
1497
  }
1498
+ }
1499
+ return false;
1500
+ };
1501
+ var closeCommand = (view) => {
1502
+ if (view.state.field(commandState, false)) {
1503
+ view.dispatch({
1504
+ effects: closeEffect.of(null)
1505
+ });
1506
+ return true;
1507
+ }
1508
+ return false;
1509
+ };
1510
+ var commandKeyBindings = [
1511
+ {
1512
+ key: "/",
1513
+ run: openCommand
1649
1514
  },
1650
- "&dark .cm-button": {
1651
- background: getToken("extend.colors.neutral.800"),
1652
- "&:hover": {
1653
- background: getToken("extend.colors.neutral.700")
1654
- },
1655
- "&:active": {
1656
- background: getToken("extend.colors.neutral.600")
1515
+ {
1516
+ key: "Escape",
1517
+ run: closeCommand
1518
+ }
1519
+ ];
1520
+
1521
+ // packages/ui/react-ui-editor/src/extensions/util/dom.ts
1522
+ var flattenRect = (rect, left) => {
1523
+ const x = left ? rect.left : rect.right;
1524
+ return {
1525
+ left: x,
1526
+ right: x,
1527
+ top: rect.top,
1528
+ bottom: rect.bottom
1529
+ };
1530
+ };
1531
+ var scratchRange;
1532
+ var textRange = (node, from, to = from) => {
1533
+ const range = scratchRange || (scratchRange = document.createRange());
1534
+ range.setEnd(node, to);
1535
+ range.setStart(node, from);
1536
+ return range;
1537
+ };
1538
+ var clientRectsFor = (dom) => {
1539
+ if (dom.nodeType === 3) {
1540
+ return textRange(dom, 0, dom.nodeValue.length).getClientRects();
1541
+ } else if (dom.nodeType === 1) {
1542
+ return dom.getClientRects();
1543
+ } else {
1544
+ return [];
1545
+ }
1546
+ };
1547
+
1548
+ // packages/ui/react-ui-editor/src/extensions/command/hint.ts
1549
+ var CommandHint = class extends WidgetType2 {
1550
+ constructor(content) {
1551
+ super();
1552
+ this.content = content;
1553
+ }
1554
+ toDOM() {
1555
+ const wrap = document.createElement("span");
1556
+ wrap.className = "cm-placeholder";
1557
+ wrap.style.pointerEvents = "none";
1558
+ wrap.appendChild(typeof this.content === "string" ? document.createTextNode(this.content) : this.content);
1559
+ if (typeof this.content === "string") {
1560
+ wrap.setAttribute("aria-label", "placeholder " + this.content);
1561
+ } else {
1562
+ wrap.setAttribute("aria-hidden", "true");
1563
+ }
1564
+ return wrap;
1565
+ }
1566
+ coordsAt(dom) {
1567
+ const rects = dom.firstChild ? clientRectsFor(dom.firstChild) : [];
1568
+ if (!rects.length) {
1569
+ return null;
1570
+ }
1571
+ const style = window.getComputedStyle(dom.parentNode);
1572
+ const rect = flattenRect(rects[0], style.direction !== "rtl");
1573
+ const lineHeight = parseInt(style.lineHeight);
1574
+ if (rect.bottom - rect.top > lineHeight * 1.5) {
1575
+ return {
1576
+ left: rect.left,
1577
+ right: rect.right,
1578
+ top: rect.top,
1579
+ bottom: rect.top + lineHeight
1580
+ };
1581
+ }
1582
+ return rect;
1583
+ }
1584
+ ignoreEvent() {
1585
+ return false;
1586
+ }
1587
+ };
1588
+ var hintViewPlugin = ({ onHint }) => ViewPlugin3.fromClass(class {
1589
+ constructor() {
1590
+ this.deco = Decoration3.none;
1591
+ }
1592
+ update(update2) {
1593
+ const builder = new RangeSetBuilder();
1594
+ const cState = update2.view.state.field(commandState, false);
1595
+ if (!cState?.tooltip) {
1596
+ const selection = update2.view.state.selection.main;
1597
+ const line = update2.view.state.doc.lineAt(selection.from);
1598
+ if (selection.from === selection.to && line.from === line.to) {
1599
+ const hint = onHint();
1600
+ if (hint) {
1601
+ builder.add(selection.from, selection.to, Decoration3.widget({
1602
+ widget: new CommandHint(hint)
1603
+ }));
1604
+ }
1605
+ }
1657
1606
  }
1607
+ this.deco = builder.finish();
1658
1608
  }
1609
+ }, {
1610
+ provide: (plugin) => [
1611
+ EditorView5.decorations.of((view) => view.plugin(plugin)?.deco ?? Decoration3.none)
1612
+ ]
1613
+ });
1614
+
1615
+ // packages/ui/react-ui-editor/src/extensions/command/command.ts
1616
+ var command = (options) => {
1617
+ return [
1618
+ commandConfig.of(options),
1619
+ commandState,
1620
+ keymap3.of(commandKeyBindings),
1621
+ hintViewPlugin(options),
1622
+ EditorView6.focusChangeEffect.of((_, focusing) => {
1623
+ return focusing ? closeEffect.of(null) : null;
1624
+ })
1625
+ ];
1626
+ };
1627
+
1628
+ // packages/ui/react-ui-editor/src/extensions/comments.ts
1629
+ import { invertedEffects } from "@codemirror/commands";
1630
+ import { Facet as Facet4, StateEffect as StateEffect3, StateField as StateField4 } from "@codemirror/state";
1631
+ import { hoverTooltip, keymap as keymap4, Decoration as Decoration4, EditorView as EditorView7, ViewPlugin as ViewPlugin4 } from "@codemirror/view";
1632
+ import sortBy from "lodash.sortby";
1633
+ import { useEffect, useMemo, useState } from "react";
1634
+ import { debounce } from "@dxos/async";
1635
+ import { log as log5 } from "@dxos/log";
1636
+ import { nonNullable } from "@dxos/util";
1637
+
1638
+ // packages/ui/react-ui-editor/src/extensions/util/error.ts
1639
+ import { log as log3 } from "@dxos/log";
1640
+ var __dxlog_file5 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/util/error.ts";
1641
+ var wrapWithCatch = (fn) => {
1642
+ return (...args) => {
1643
+ try {
1644
+ return fn(...args);
1645
+ } catch (err) {
1646
+ log3.catch(err, void 0, {
1647
+ F: __dxlog_file5,
1648
+ L: 12,
1649
+ S: void 0,
1650
+ C: (f, a) => f(...a)
1651
+ });
1652
+ }
1653
+ };
1654
+ };
1655
+
1656
+ // packages/ui/react-ui-editor/src/extensions/util/overlap.ts
1657
+ var overlap = (a, b) => a.from <= b.to && a.to >= b.from;
1658
+
1659
+ // packages/ui/react-ui-editor/src/extensions/util/react.tsx
1660
+ import React from "react";
1661
+ import { createRoot } from "react-dom/client";
1662
+ import { ThemeProvider } from "@dxos/react-ui";
1663
+ import { defaultTx } from "@dxos/react-ui-theme";
1664
+ var renderRoot = (root, node) => {
1665
+ createRoot(root).render(/* @__PURE__ */ React.createElement(ThemeProvider, {
1666
+ tx: defaultTx
1667
+ }, node));
1668
+ return root;
1659
1669
  };
1660
1670
 
1661
1671
  // packages/ui/react-ui-editor/src/util.ts
@@ -4144,8 +4154,9 @@ var CheckboxWidget = class extends WidgetType5 {
4144
4154
  }
4145
4155
  toDOM(view) {
4146
4156
  const input = document.createElement("input");
4147
- input.className = "cm-task-checkbox ch-checkbox ch-focus-ring";
4157
+ input.className = "cm-task-checkbox ch-checkbox";
4148
4158
  input.type = "checkbox";
4159
+ input.tabIndex = -1;
4149
4160
  input.checked = this._checked;
4150
4161
  if (view.state.readOnly) {
4151
4162
  input.setAttribute("disabled", "true");
@@ -4233,7 +4244,7 @@ var buildDecorations2 = (view, options, focus) => {
4233
4244
  const getHeaderLevels = (node, level) => {
4234
4245
  invariant4(level > 0, void 0, {
4235
4246
  F: __dxlog_file10,
4236
- L: 158,
4247
+ L: 159,
4237
4248
  S: void 0,
4238
4249
  A: [
4239
4250
  "level > 0",
@@ -4272,7 +4283,7 @@ var buildDecorations2 = (view, options, focus) => {
4272
4283
  const getCurrentList = () => {
4273
4284
  invariant4(listLevels.length, void 0, {
4274
4285
  F: __dxlog_file10,
4275
- L: 180,
4286
+ L: 181,
4276
4287
  S: void 0,
4277
4288
  A: [
4278
4289
  "listLevels.length",
@@ -5245,6 +5256,11 @@ var editorGutter = EditorView16.baseTheme({
5245
5256
  backgroundColor: "transparent !important"
5246
5257
  }
5247
5258
  });
5259
+ var editorMonospace = EditorView16.baseTheme({
5260
+ ".cm-content": {
5261
+ fontFamily: `${getToken("fontFamily.mono")} !important`
5262
+ }
5263
+ });
5248
5264
 
5249
5265
  // packages/ui/react-ui-editor/src/hooks/useActionHandler.ts
5250
5266
  var useActionHandler = (view) => {
@@ -5444,6 +5460,7 @@ export {
5444
5460
  editorContent,
5445
5461
  editorGutter,
5446
5462
  editorInputMode,
5463
+ editorMonospace,
5447
5464
  editorWithToolbarLayout,
5448
5465
  focusEvent,
5449
5466
  folding,