@dxos/react-ui-editor 0.3.10 → 0.3.11-main.1caa3af

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/dist/lib/browser/index.mjs +1928 -1717
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts +16 -9
  5. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -1
  6. package/dist/types/src/components/TextEditor/TextEditor.d.ts +2 -1
  7. package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
  8. package/dist/types/src/components/TextEditor/extensions/code.d.ts.map +1 -1
  9. package/dist/types/src/components/TextEditor/extensions/comments.d.ts +38 -8
  10. package/dist/types/src/components/TextEditor/extensions/comments.d.ts.map +1 -1
  11. package/dist/types/src/components/TextEditor/extensions/hr.d.ts +2 -0
  12. package/dist/types/src/components/TextEditor/extensions/hr.d.ts.map +1 -0
  13. package/dist/types/src/components/TextEditor/extensions/index.d.ts +3 -2
  14. package/dist/types/src/components/TextEditor/extensions/index.d.ts.map +1 -1
  15. package/dist/types/src/components/TextEditor/extensions/markdown/highlight.d.ts +0 -1
  16. package/dist/types/src/components/TextEditor/extensions/markdown/highlight.d.ts.map +1 -1
  17. package/dist/types/src/components/TextEditor/extensions/mermaid.d.ts +3 -0
  18. package/dist/types/src/components/TextEditor/extensions/mermaid.d.ts.map +1 -0
  19. package/dist/types/src/components/TextEditor/extensions/table.d.ts.map +1 -1
  20. package/dist/types/src/components/TextEditor/extensions/tooltip.d.ts.map +1 -1
  21. package/dist/types/src/components/TextEditor/extensions/{demo.d.ts → typewriter.d.ts} +2 -3
  22. package/dist/types/src/components/TextEditor/extensions/typewriter.d.ts.map +1 -0
  23. package/dist/types/src/components/TextEditor/extensions/util.d.ts +5 -0
  24. package/dist/types/src/components/TextEditor/extensions/util.d.ts.map +1 -0
  25. package/dist/types/src/components/TextEditor/themes/default.d.ts +5 -13
  26. package/dist/types/src/components/TextEditor/themes/default.d.ts.map +1 -1
  27. package/dist/types/src/hooks/automerge/automerge.stories.d.ts +2 -3
  28. package/dist/types/src/hooks/automerge/automerge.stories.d.ts.map +1 -1
  29. package/dist/types/src/hooks/automerge/plugin.d.ts.map +1 -1
  30. package/dist/types/src/hooks/automerge/{PatchSemaphore.d.ts → semaphore.d.ts} +1 -1
  31. package/dist/types/src/hooks/automerge/semaphore.d.ts.map +1 -0
  32. package/dist/types/src/hooks/automerge/update-automerge.d.ts +6 -0
  33. package/dist/types/src/hooks/automerge/update-automerge.d.ts.map +1 -0
  34. package/dist/types/src/hooks/automerge/update-codemirror.d.ts +5 -0
  35. package/dist/types/src/hooks/automerge/update-codemirror.d.ts.map +1 -0
  36. package/dist/types/src/hooks/useTextModel.d.ts +14 -6
  37. package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
  38. package/dist/types/src/hooks/yjs/yjs.test.d.ts +2 -0
  39. package/dist/types/src/hooks/yjs/yjs.test.d.ts.map +1 -0
  40. package/dist/types/src/index.d.ts +1 -0
  41. package/dist/types/src/index.d.ts.map +1 -1
  42. package/dist/types/src/testing/replicator.d.ts.map +1 -1
  43. package/package.json +24 -20
  44. package/src/components/TextEditor/MarkdownEditor.stories.tsx +128 -67
  45. package/src/components/TextEditor/TextEditor.stories.tsx +1 -1
  46. package/src/components/TextEditor/TextEditor.tsx +26 -22
  47. package/src/components/TextEditor/extensions/code.ts +12 -12
  48. package/src/components/TextEditor/extensions/comments.ts +307 -92
  49. package/src/components/TextEditor/extensions/hr.ts +74 -0
  50. package/src/components/TextEditor/extensions/image.ts +3 -1
  51. package/src/components/TextEditor/extensions/index.ts +3 -2
  52. package/src/components/TextEditor/extensions/markdown/highlight.ts +5 -8
  53. package/src/components/TextEditor/extensions/mermaid.ts +11 -0
  54. package/src/components/TextEditor/extensions/table.ts +14 -1
  55. package/src/components/TextEditor/extensions/{tooltip.tsx → tooltip.ts} +1 -1
  56. package/src/components/TextEditor/extensions/{demo.ts → typewriter.ts} +1 -2
  57. package/src/components/TextEditor/extensions/util.ts +18 -0
  58. package/src/components/TextEditor/themes/default.ts +61 -38
  59. package/src/hooks/automerge/automerge.stories.tsx +2 -3
  60. package/src/hooks/automerge/plugin.ts +1 -1
  61. package/src/hooks/automerge/{PatchSemaphore.ts → semaphore.ts} +4 -3
  62. package/src/hooks/automerge/{translation/codemirror-to-automerge.ts → update-automerge.ts} +3 -3
  63. package/src/hooks/automerge/{translation/automerge-to-codemirror.ts → update-codemirror.ts} +2 -7
  64. package/src/hooks/useTextModel.ts +59 -22
  65. package/src/hooks/yjs/yjs.test.ts +59 -0
  66. package/src/index.ts +1 -0
  67. package/src/testing/replicator.ts +0 -1
  68. package/dist/types/src/components/TextEditor/extensions/demo.d.ts.map +0 -1
  69. package/dist/types/src/components/TextEditor/extensions/experimental.d.ts +0 -10
  70. package/dist/types/src/components/TextEditor/extensions/experimental.d.ts.map +0 -1
  71. package/dist/types/src/components/TextEditor/extensions/highlight.d.ts +0 -24
  72. package/dist/types/src/components/TextEditor/extensions/highlight.d.ts.map +0 -1
  73. package/dist/types/src/hooks/automerge/PatchSemaphore.d.ts.map +0 -1
  74. package/dist/types/src/hooks/automerge/translation/automerge-to-codemirror.d.ts +0 -5
  75. package/dist/types/src/hooks/automerge/translation/automerge-to-codemirror.d.ts.map +0 -1
  76. package/dist/types/src/hooks/automerge/translation/codemirror-to-automerge.d.ts +0 -6
  77. package/dist/types/src/hooks/automerge/translation/codemirror-to-automerge.d.ts.map +0 -1
  78. package/dist/types/src/hooks/automerge/translation/index.d.ts +0 -3
  79. package/dist/types/src/hooks/automerge/translation/index.d.ts.map +0 -1
  80. package/src/components/TextEditor/extensions/experimental.tsx +0 -114
  81. package/src/components/TextEditor/extensions/highlight.ts +0 -128
  82. package/src/hooks/automerge/translation/index.ts +0 -6
@@ -59,7 +59,7 @@ var basicBundle = ({ readonly, themeMode, placeholder: _placeholder }) => [
59
59
  import { EditorView as EditorView2, keymap as keymap2 } from "@codemirror/view";
60
60
  import defaultsDeep from "lodash.defaultsdeep";
61
61
  import { invariant } from "@dxos/invariant";
62
- var __dxlog_file = "/home/circleci/project/packages/ui/react-ui-editor/src/components/TextEditor/extensions/blast.ts";
62
+ var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/components/TextEditor/extensions/blast.ts";
63
63
  var defaultOptions = {
64
64
  effect: 2,
65
65
  maxParticles: 200,
@@ -411,7 +411,6 @@ var code = "font-mono !no-underline text-neutral-700 dark:text-neutral-300";
411
411
  var codeMark = "font-mono text-primary-500";
412
412
  var inlineUrl = mx(code, "px-1");
413
413
  var blockquote = mx("pl-1 mr-1 border-is-4 border-primary-500/70 dark:border-primary-500/30", light);
414
- var horizontalRule = "flex mlb-4 border-b text-neutral-100 dark:text-neutral-900 border-neutral-200 dark:border-neutral-800";
415
414
 
416
415
  // packages/ui/react-ui-editor/src/styles/tokens.ts
417
416
  import { tailwindConfig } from "@dxos/react-ui-theme";
@@ -420,22 +419,25 @@ var tokens = tailwindConfig({}).theme;
420
419
  // packages/ui/react-ui-editor/src/components/TextEditor/extensions/code.ts
421
420
  var CODE_REGEX = /```[\s\S]*?```/gs;
422
421
  var styles = EditorView3.baseTheme({
423
- "& .cm-code-block": {
424
- display: "block",
425
- paddingInline: "4px !important"
422
+ "& .cm-codeblock": {
423
+ fontFamily: get(tokens, "fontFamily.mono", []).join(",")
426
424
  },
427
- "&light .cm-code-block": {
425
+ '&light [aria-readonly="true"] .cm-codeblock': {
428
426
  background: get(tokens, "extend.colors.neutral.50")
429
427
  },
430
- "&dark .cm-code-block": {
428
+ '&dark [aria-readonly="true"] .cm-codeblock': {
431
429
  background: get(tokens, "extend.colors.neutral.850")
432
430
  },
433
- "& .cm-code-block-first": {
431
+ '& [aria-readonly="true"] .cm-codeblock': {
432
+ display: "block",
433
+ paddingInline: "4px !important"
434
+ },
435
+ '& [aria-readonly="true"] .cm-codeblock-first': {
434
436
  paddingTop: "4px !important",
435
437
  borderTopLeftRadius: "4px",
436
438
  borderTopRightRadius: "4px"
437
439
  },
438
- "& .cm-code-block-last": {
440
+ '& [aria-readonly="true"] .cm-codeblock-last': {
439
441
  paddingBottom: "4px !important",
440
442
  borderBottomLeftRadius: "4px",
441
443
  borderBottomRightRadius: "4px"
@@ -461,7 +463,7 @@ var code2 = () => {
461
463
  for (let i = range[0]; i <= range[1]; i++) {
462
464
  const block = blocks[i];
463
465
  decorations.push(Decoration.line({
464
- class: mx2("cm-code-block", i === range[0] && "cm-code-block-first", i === range[1] && "cm-code-block-last")
466
+ class: mx2("cm-codeblock", i === range[0] && "cm-codeblock-first", i === range[1] && "cm-codeblock-last")
465
467
  }).range(block.from));
466
468
  }
467
469
  }
@@ -485,1853 +487,2062 @@ var code2 = () => {
485
487
  ];
486
488
  };
487
489
 
488
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/demo.ts
489
- import { keymap as keymap3 } from "@codemirror/view";
490
- var defaultItems = [
491
- "hello world!",
492
- "this is a test.",
493
- "this is [DXOS](https://dxos.org)"
494
- ];
495
- var demo = ({ delay = 75, items = defaultItems } = {}) => {
496
- let t;
497
- let idx = 0;
498
- return [
499
- keymap3.of([
500
- {
501
- // Reset.
502
- key: "alt-meta-'",
503
- run: (view) => {
504
- clearTimeout(t);
505
- idx = 0;
506
- return true;
507
- }
508
- },
509
- {
510
- // Next prompt.
511
- // TODO(burdon): Press 1-9 to select prompt?
512
- key: "shift-meta-'",
513
- run: (view) => {
514
- clearTimeout(t);
515
- const text = items[idx++];
516
- if (idx === items?.length) {
517
- idx = 0;
518
- }
519
- let i = 0;
520
- const insert = (d = 0) => {
521
- t = setTimeout(() => {
522
- const pos = view.state.selection.main.head;
523
- view.dispatch({
524
- changes: {
525
- from: pos,
526
- insert: text[i++]
527
- },
528
- selection: {
529
- anchor: pos + 1
530
- }
531
- });
532
- if (i < text.length) {
533
- insert(Math.random() * delay * (text[i] === " " ? 2 : 1));
534
- }
535
- }, d);
536
- };
537
- insert();
538
- return true;
539
- }
540
- }
541
- ])
542
- ];
543
- };
544
-
545
490
  // packages/ui/react-ui-editor/src/components/TextEditor/extensions/comments.ts
546
- import { keymap as keymap4, Decoration as Decoration2, EditorView as EditorView4, MatchDecorator, ViewPlugin as ViewPlugin2, WidgetType } from "@codemirror/view";
491
+ import { StateEffect as StateEffect2, StateField as StateField3 } from "@codemirror/state";
492
+ import { hoverTooltip, keymap as keymap3, Decoration as Decoration2, EditorView as EditorView4, MatchDecorator, ViewPlugin as ViewPlugin3, WidgetType } from "@codemirror/view";
493
+ import sortBy from "lodash.sortby";
547
494
  import { debounce } from "@dxos/async";
548
- import { invariant as invariant2 } from "@dxos/invariant";
549
- var __dxlog_file2 = "/home/circleci/project/packages/ui/react-ui-editor/src/components/TextEditor/extensions/comments.ts";
550
- var styles2 = EditorView4.baseTheme({
551
- "& .cm-bookmark": {
552
- cursor: "pointer",
553
- margin: "4px",
554
- padding: "4px",
555
- backgroundColor: "yellow"
556
- },
557
- "& .cm-bookmark-selected": {
558
- backgroundColor: "orange"
559
- }
560
- });
561
- var BookmarkWidget = class extends WidgetType {
562
- constructor(_pos, _id, _handleClick) {
563
- super();
564
- this._pos = _pos;
565
- this._id = _id;
566
- this._handleClick = _handleClick;
567
- invariant2(this._id, void 0, {
495
+ import { invariant as invariant3 } from "@dxos/invariant";
496
+ import { log as log3 } from "@dxos/log";
497
+ import { nonNullable } from "@dxos/util";
498
+
499
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/util.ts
500
+ import { log } from "@dxos/log";
501
+ var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/components/TextEditor/extensions/util.ts";
502
+ var callbackWrapper = (fn) => (...args) => {
503
+ try {
504
+ return fn(...args);
505
+ } catch (error) {
506
+ log.catch(error, void 0, {
568
507
  F: __dxlog_file2,
569
- L: 37,
570
- S: this,
571
- A: [
572
- "this._id",
573
- ""
574
- ]
508
+ L: 16,
509
+ S: void 0,
510
+ C: (f, a) => f(...a)
575
511
  });
576
512
  }
577
- eq(other) {
578
- return this._id === other._id;
513
+ };
514
+
515
+ // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
516
+ import { StateField as StateField2 } from "@codemirror/state";
517
+ import get2 from "lodash.get";
518
+ import { useEffect, useState } from "react";
519
+ import { yCollab } from "y-codemirror.next";
520
+ import * as Y from "yjs";
521
+ import { invariant as invariant2 } from "@dxos/invariant";
522
+ import { getRawDoc, isActualAutomergeObject } from "@dxos/react-client/echo";
523
+ import { arrayToString, stringToArray } from "@dxos/util";
524
+
525
+ // packages/ui/react-ui-editor/src/hooks/automerge/plugin.ts
526
+ import { Annotation, Facet, StateEffect, StateField } from "@codemirror/state";
527
+ import { ViewPlugin as ViewPlugin2 } from "@codemirror/view";
528
+ import * as automerge2 from "@dxos/automerge/automerge";
529
+
530
+ // packages/ui/react-ui-editor/src/hooks/automerge/semaphore.ts
531
+ import { next as automerge } from "@dxos/automerge/automerge";
532
+
533
+ // packages/ui/react-ui-editor/src/hooks/automerge/update-automerge.ts
534
+ import { next as am } from "@dxos/automerge/automerge";
535
+ var updateAutomerge = (field, handle, transactions, state) => {
536
+ const { lastHeads, path } = state.field(field);
537
+ let hasChanges = false;
538
+ for (const tr of transactions) {
539
+ tr.changes.iterChanges(() => {
540
+ hasChanges = true;
541
+ });
579
542
  }
580
- toDOM() {
581
- const span = document.createElement("span");
582
- span.className = "cm-bookmark";
583
- span.textContent = "\xA7";
584
- span.onclick = () => this._handleClick();
585
- return span;
543
+ if (!hasChanges) {
544
+ return void 0;
586
545
  }
587
- };
588
- var comments = (options = {}) => {
589
- let active;
590
- const bookmarkMatcher = new MatchDecorator({
591
- regexp: /\[\^(\w+)\]/g,
592
- decoration: (match, view, pos) => {
593
- const id = match[1];
594
- return Decoration2.replace({
595
- id,
596
- widget: new BookmarkWidget(pos, id, () => handleUpdate({
597
- active: id
598
- }))
546
+ const newHeads = handle.changeAt(lastHeads, (doc) => {
547
+ for (const tr of transactions) {
548
+ tr.changes.iterChanges((fromA, toA, _fromB, _toB, inserted) => {
549
+ am.splice(doc, path, fromA, toA - fromA, inserted.toString());
599
550
  });
600
551
  }
601
552
  });
602
- const bookmarks = ViewPlugin2.fromClass(class {
603
- constructor(view) {
604
- this.bookmarks = bookmarkMatcher.createDeco(view);
605
- }
606
- update(update4) {
607
- this.bookmarks = bookmarkMatcher.updateDeco(update4, this.bookmarks);
608
- }
609
- }, {
610
- decorations: (instance) => instance.bookmarks,
611
- provide: (plugin) => EditorView4.atomicRanges.of((view) => {
612
- return view.plugin(plugin)?.bookmarks || Decoration2.none;
613
- })
614
- });
615
- const handleUpdate = debounce((data) => {
616
- options.onUpdate?.(data);
617
- }, 200);
618
- return [
619
- keymap4.of([
620
- {
621
- key: options?.key ?? "shift-meta-c",
622
- run: (view) => {
623
- const { from, to, head } = view.state.selection.main;
624
- const id = options.onCreate?.(from, to);
625
- if (id) {
626
- const tag = `[^${id}]`;
627
- view.dispatch({
628
- changes: {
629
- from: head,
630
- insert: tag
631
- },
632
- selection: {
633
- anchor: head + tag.length
634
- }
635
- });
636
- return true;
637
- }
638
- return false;
639
- }
640
- }
641
- ]),
642
- bookmarks,
643
- // Monitor cursor movement.
644
- EditorView4.updateListener.of((update4) => {
645
- active = void 0;
646
- if (!options.onUpdate) {
647
- return;
648
- }
649
- const { view, state } = update4;
650
- const pos = state.selection.main.head;
651
- const decorations = [];
652
- const rangeSet = view.plugin(bookmarks)?.bookmarks;
653
- let closest = Infinity;
654
- const { from, to } = (
655
- /* view.visibleRanges?.[0] ?? */
656
- {
657
- from: 0,
658
- to: state.doc.length
659
- }
660
- );
661
- rangeSet?.between(from, to, (from2, to2, value) => {
662
- decorations.push({
663
- from: from2,
664
- to: to2,
665
- value
666
- });
667
- const d = Math.min(Math.abs(pos - from2), Math.abs(pos - to2));
668
- if (d < closest) {
669
- active = value.spec.id;
670
- closest = d;
671
- }
672
- });
673
- if (decorations.length) {
674
- handleUpdate({
675
- active,
676
- items: decorations.map(({ from: from2, value: { spec: { id } } }) => ({
677
- id,
678
- pos: from2,
679
- location: view.coordsAtPos(from2)
680
- }))
681
- });
682
- }
683
- }),
684
- styles2
685
- ];
553
+ return newHeads ?? void 0;
686
554
  };
687
555
 
688
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/highlight.ts
689
- import { StateEffect, StateField } from "@codemirror/state";
690
- import { Decoration as Decoration3, EditorView as EditorView5 } from "@codemirror/view";
691
- import sortBy from "lodash.sortby";
692
- import { useEffect } from "react";
693
- var styles3 = EditorView5.baseTheme({
694
- "& .cm-highlight": {
695
- backgroundColor: "yellow"
696
- },
697
- "& .cm-highlight::before": {
698
- content: "]"
699
- },
700
- "& .cm-highlight-active": {
701
- backgroundColor: "lime"
556
+ // packages/ui/react-ui-editor/src/hooks/automerge/update-codemirror.ts
557
+ import { ChangeSet } from "@codemirror/state";
558
+ var updateCodeMirror = (view, selection, target, patches) => {
559
+ for (const patch of patches) {
560
+ const changeSpec = handlePatch(patch, target, view.state);
561
+ if (changeSpec != null) {
562
+ const changeSet = ChangeSet.of(changeSpec, view.state.doc.length, "\n");
563
+ selection = selection.map(changeSet, 1);
564
+ view.dispatch({
565
+ changes: changeSet,
566
+ annotations: reconcileAnnotationType.of({})
567
+ });
568
+ }
702
569
  }
703
- });
704
- var marks = {
705
- highlight: Decoration3.mark({
706
- class: "cm-highlight"
707
- }),
708
- HighlightActive: Decoration3.mark({
709
- class: "cm-highlight-active"
710
- })
570
+ view.dispatch({
571
+ selection,
572
+ annotations: reconcileAnnotationType.of({})
573
+ });
711
574
  };
712
- var setHighlights = StateEffect.define();
713
- var useHighlights = (view, ranges = []) => {
714
- useEffect(() => {
715
- view?.dispatch({
716
- effects: setHighlights.of(ranges)
717
- });
718
- }, [
719
- view,
720
- ranges
721
- ]);
575
+ var handlePatch = (patch, target, state) => {
576
+ if (patch.action === "insert") {
577
+ return handleInsert(target, patch);
578
+ } else if (patch.action === "splice") {
579
+ return handleSplice(target, patch);
580
+ } else if (patch.action === "del") {
581
+ return handleDel(target, patch);
582
+ } else if (patch.action === "put") {
583
+ return handlePut(target, patch, state);
584
+ } else {
585
+ return null;
586
+ }
722
587
  };
723
- var highlightStateField = StateField.define({
724
- create: () => [],
725
- update: (threads, tr) => {
726
- for (const effect of tr.effects) {
727
- if (effect.is(setHighlights)) {
728
- return effect.value;
729
- }
730
- }
731
- return threads;
588
+ var handleInsert = (target, patch) => {
589
+ const index = charPath(target, patch.path);
590
+ if (index == null) {
591
+ return [];
732
592
  }
733
- });
734
- var highlightDecorations = EditorView5.decorations.compute([
735
- highlightStateField
736
- ], (state) => {
737
- const selectionRanges = state.field(highlightStateField);
738
- const decorations = sortBy(selectionRanges ?? [], (selection) => selection.from)?.flatMap((selection) => {
739
- const range = {
740
- from: selection.from,
741
- to: selection.to + 1
742
- };
743
- if (selection.to > selection.from) {
744
- if (selection.active) {
745
- return marks.HighlightActive.range(range.from, range.to);
746
- } else {
747
- return marks.highlight.range(range.from, range.to);
748
- }
749
- } else {
750
- return [];
751
- }
752
- }) ?? [];
753
- return Decoration3.set(decorations);
754
- });
755
- var listener = (options) => {
756
- return EditorView5.updateListener.of((update4) => {
757
- const { view, state } = update4;
758
- const { head } = state.selection.main;
759
- const ranges = state.field(highlightStateField);
760
- let mod = false;
761
- const newRanges = ranges.map((range) => {
762
- const active = head >= range.from && head <= range.to;
763
- if (active !== range.active) {
764
- mod = true;
765
- return {
766
- ...range,
767
- active
768
- };
769
- } else {
770
- return range;
771
- }
772
- });
773
- if (mod) {
774
- view.dispatch({
775
- effects: setHighlights.of(newRanges)
776
- });
777
- options?.onChange?.(newRanges.find((range) => range.active)?.id);
593
+ const text = patch.values.map((v) => v ? v.toString() : "").join("");
594
+ return [
595
+ {
596
+ from: index,
597
+ to: index,
598
+ insert: text
778
599
  }
779
- });
600
+ ];
780
601
  };
781
- var highlight = (options = {}) => {
602
+ var handleSplice = (target, patch) => {
603
+ const index = charPath(target, patch.path);
604
+ if (index == null) {
605
+ return [];
606
+ }
782
607
  return [
783
- highlightStateField,
784
- highlightDecorations,
785
- listener(options),
786
- styles3
608
+ {
609
+ from: index,
610
+ insert: patch.value
611
+ }
787
612
  ];
788
613
  };
789
-
790
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/image.ts
791
- import { syntaxTree } from "@codemirror/language";
792
- import { RangeSetBuilder, StateField as StateField2 } from "@codemirror/state";
793
- import { Decoration as Decoration4, EditorView as EditorView6, WidgetType as WidgetType2 } from "@codemirror/view";
794
- var image = (options = {}) => {
795
- return StateField2.define({
796
- create: (state) => update(state, options),
797
- update: (_, tr) => update(tr.state, options),
798
- provide: (field) => EditorView6.decorations.from(field)
799
- });
614
+ var handleDel = (target, patch) => {
615
+ const index = charPath(target, patch.path);
616
+ if (index == null) {
617
+ return [];
618
+ }
619
+ const length = patch.length || 1;
620
+ return [
621
+ {
622
+ from: index,
623
+ to: index + length
624
+ }
625
+ ];
800
626
  };
801
- var update = (state, options) => {
802
- const builder = new RangeSetBuilder();
803
- const cursor = state.selection.main.head;
804
- syntaxTree(state).iterate({
805
- enter: (node) => {
806
- if (node.name === "Image") {
807
- const urlNode = node.node.getChild("URL");
808
- if (urlNode) {
809
- const hide = state.readOnly || cursor < node.from || cursor > node.to;
810
- const url = state.sliceDoc(urlNode.from, urlNode.to);
811
- builder.add(hide ? node.from : node.to, node.to, Decoration4.replace({
812
- widget: new ImageWidget(url)
813
- }));
814
- }
815
- }
816
- }
817
- });
818
- return builder.finish();
819
- };
820
- var ImageWidget = class extends WidgetType2 {
821
- constructor(_url) {
822
- super();
823
- this._url = _url;
824
- }
825
- eq(other) {
826
- return this._url === other?._url;
627
+ var handlePut = (target, patch, state) => {
628
+ const index = charPath(target, [
629
+ ...patch.path,
630
+ 0
631
+ ]);
632
+ if (index == null) {
633
+ return [];
827
634
  }
828
- toDOM(view) {
829
- const img = document.createElement("img");
830
- img.setAttribute("src", this._url);
831
- img.setAttribute("class", "cm-image");
832
- return img;
635
+ const length = state.doc.length;
636
+ if (typeof patch.value !== "string") {
637
+ return [];
833
638
  }
639
+ return [
640
+ {
641
+ from: 0,
642
+ to: length,
643
+ insert: patch.value
644
+ }
645
+ ];
834
646
  };
835
-
836
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/link.ts
837
- import { syntaxTree as syntaxTree2 } from "@codemirror/language";
838
- import { RangeSetBuilder as RangeSetBuilder2, StateField as StateField3 } from "@codemirror/state";
839
- import { Decoration as Decoration5, EditorView as EditorView7, WidgetType as WidgetType3 } from "@codemirror/view";
840
- var link = (options = {}) => {
841
- return StateField3.define({
842
- create: (state) => update2(state, options),
843
- update: (_, tr) => update2(tr.state, options),
844
- provide: (field) => EditorView7.decorations.from(field)
845
- });
846
- };
847
- var LinkButton = class extends WidgetType3 {
848
- constructor(_pos, _url, _onAttach) {
849
- super();
850
- this._pos = _pos;
851
- this._url = _url;
852
- this._onAttach = _onAttach;
647
+ var charPath = (textPath, candidatePath) => {
648
+ if (candidatePath.length !== textPath.length + 1) {
649
+ return null;
853
650
  }
854
- eq(other) {
855
- return this._pos === other._pos && this._url === other._url;
651
+ for (let i = 0; i < textPath.length; i++) {
652
+ if (textPath[i] !== candidatePath[i]) {
653
+ return null;
654
+ }
856
655
  }
857
- toDOM(view) {
858
- const el = document.createElement("span");
859
- this._onAttach(el, this._url);
860
- return el;
656
+ const index = candidatePath[candidatePath.length - 1];
657
+ if (typeof index === "number") {
658
+ return index;
861
659
  }
660
+ return null;
862
661
  };
863
- var LinkText = class extends WidgetType3 {
864
- constructor(_pos, _text, _url) {
865
- super();
866
- this._pos = _pos;
867
- this._text = _text;
868
- this._url = _url;
869
- }
870
- eq(other) {
871
- return this._pos === other._pos && this._url === other._url;
872
- }
873
- toDOM(view) {
874
- const link2 = document.createElement("a");
875
- link2.setAttribute("class", "cm-link");
876
- link2.textContent = this._text;
877
- if (this._url) {
878
- link2.setAttribute("rel", "noreferrer");
879
- link2.setAttribute("target", "_blank");
880
- link2.href = this._url;
881
- } else {
882
- link2.onclick = () => {
662
+
663
+ // packages/ui/react-ui-editor/src/hooks/automerge/semaphore.ts
664
+ var PatchSemaphore = class {
665
+ constructor(field) {
666
+ this._inReconcile = false;
667
+ this._queue = [];
668
+ this.reconcile = (handle, view) => {
669
+ if (this._inReconcile) {
670
+ return;
671
+ }
672
+ this._inReconcile = true;
673
+ const path = getPath(view.state, this._field);
674
+ const oldHeads = getLastHeads(view.state, this._field);
675
+ let selection = view.state.selection;
676
+ const transactions = view.state.field(this._field).unreconciledTransactions.filter((tx) => !isReconcileTx(tx));
677
+ const toInvert = transactions.slice().reverse();
678
+ for (const tx of toInvert) {
679
+ const inverted = tx.changes.invert(tx.startState.doc);
680
+ selection = selection.map(inverted);
883
681
  view.dispatch({
884
- selection: {
885
- anchor: this._pos
886
- }
682
+ changes: inverted,
683
+ annotations: reconcileAnnotationType.of(true)
887
684
  });
888
- };
685
+ }
686
+ let newHeads = updateAutomerge(this._field, handle, transactions, view.state);
687
+ if (newHeads === null || newHeads === void 0) {
688
+ newHeads = automerge.getHeads(handle.docSync());
689
+ }
690
+ const diff = automerge.equals(oldHeads, newHeads) ? [] : automerge.diff(handle.docSync(), oldHeads, newHeads);
691
+ updateCodeMirror(view, selection, path, diff);
692
+ view.dispatch({
693
+ effects: updateHeads(newHeads),
694
+ annotations: reconcileAnnotationType.of({})
695
+ });
696
+ this._inReconcile = false;
697
+ };
698
+ if (field !== void 0) {
699
+ this._field = field;
889
700
  }
890
- return link2;
891
701
  }
892
702
  };
893
- var update2 = (state, options) => {
894
- const builder = new RangeSetBuilder2();
895
- const cursor = state.selection.main.head;
896
- syntaxTree2(state).iterate({
897
- enter: (node) => {
898
- if (node.name === "Link") {
899
- const marks2 = node.node.getChildren("LinkMark");
900
- const text = marks2.length >= 2 ? state.sliceDoc(marks2[0].to, marks2[1].from) : "";
901
- const urlNode = node.node.getChild("URL");
902
- const url = urlNode ? state.sliceDoc(urlNode.from, urlNode.to) : "";
903
- if (!url) {
904
- return false;
905
- }
906
- if (state.readOnly || cursor < node.from || cursor > node.to) {
907
- builder.add(node.from, node.to, Decoration5.replace({
908
- widget: new LinkText(node.from, text, options.link ? url : void 0)
909
- }));
703
+
704
+ // packages/ui/react-ui-editor/src/hooks/automerge/plugin.ts
705
+ var effectType = StateEffect.define({});
706
+ var updateHeads = (newHeads) => effectType.of({
707
+ newHeads
708
+ });
709
+ var getLastHeads = (state, field) => state.field(field).lastHeads;
710
+ var getPath = (state, field) => state.field(field).path;
711
+ var semaphoreFacet = Facet.define({
712
+ combine: (values) => values.at(-1)
713
+ });
714
+ var automergePlugin = (handle, path) => {
715
+ const stateField = StateField.define({
716
+ create: () => ({
717
+ lastHeads: automerge2.getHeads(handle.docSync()),
718
+ unreconciledTransactions: [],
719
+ path: path.slice()
720
+ }),
721
+ update: (value, tr) => {
722
+ const result = {
723
+ lastHeads: value.lastHeads,
724
+ unreconciledTransactions: value.unreconciledTransactions.slice(),
725
+ path: path.slice()
726
+ };
727
+ let clearUnreconciled = false;
728
+ for (const effect of tr.effects) {
729
+ if (effect.is(effectType)) {
730
+ result.lastHeads = effect.value.newHeads;
731
+ clearUnreconciled = true;
910
732
  }
911
- if (options.onRender) {
912
- builder.add(node.to, node.to, Decoration5.replace({
913
- widget: new LinkButton(node.from, url, options.onRender)
914
- }));
733
+ }
734
+ if (clearUnreconciled) {
735
+ result.unreconciledTransactions = [];
736
+ } else {
737
+ if (!isReconcileTx(tr)) {
738
+ result.unreconciledTransactions.push(tr);
915
739
  }
916
- return false;
917
740
  }
741
+ return result;
918
742
  }
919
743
  });
920
- return builder.finish();
921
- };
922
-
923
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/listener.ts
924
- import { StateField as StateField4 } from "@codemirror/state";
925
- var listener2 = ({ onChange }) => {
926
- return StateField4.define({
927
- create: () => null,
928
- update: (_value, transaction) => {
929
- if (transaction.docChanged && onChange) {
930
- onChange(transaction.newDoc.toString());
744
+ const semaphore = new PatchSemaphore(stateField);
745
+ const viewPlugin = ViewPlugin2.fromClass(class AutomergeCodemirrorViewPlugin {
746
+ constructor(view) {
747
+ this._handleChange = () => {
748
+ this._view.state.facet(semaphoreFacet).reconcile(handle, this._view);
749
+ };
750
+ this._view = view;
751
+ handle.addListener("change", this._handleChange);
752
+ }
753
+ update(update4) {
754
+ if (update4.transactions.length > 0 && update4.transactions.some((t) => !isReconcileTx(t))) {
755
+ queueMicrotask(() => {
756
+ this._view.state.facet(semaphoreFacet).reconcile(handle, this._view);
757
+ });
931
758
  }
932
- return null;
759
+ }
760
+ destroy() {
761
+ handle.addListener("change", this._handleChange);
933
762
  }
934
763
  });
764
+ return {
765
+ extension: [
766
+ stateField,
767
+ semaphoreFacet.of(semaphore),
768
+ viewPlugin
769
+ ]
770
+ };
935
771
  };
772
+ var reconcileAnnotationType = Annotation.define();
773
+ var isReconcileTx = (tr) => !!tr.annotation(reconcileAnnotationType);
936
774
 
937
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/bundle.ts
938
- import { closeBrackets as closeBrackets2, closeBracketsKeymap } from "@codemirror/autocomplete";
939
- import { history, historyKeymap, indentWithTab, standardKeymap } from "@codemirror/commands";
940
- import { markdownLanguage as markdownLanguage2, markdown } from "@codemirror/lang-markdown";
941
- import { bracketMatching as bracketMatching2, defaultHighlightStyle as defaultHighlightStyle2, indentOnInput, syntaxHighlighting as syntaxHighlighting2 } from "@codemirror/language";
942
- import { languages } from "@codemirror/language-data";
943
- import { searchKeymap } from "@codemirror/search";
944
- import { EditorState } from "@codemirror/state";
945
- import { oneDarkHighlightStyle as oneDarkHighlightStyle2 } from "@codemirror/theme-one-dark";
946
- import { crosshairCursor, drawSelection as drawSelection2, dropCursor, EditorView as EditorView8, highlightActiveLine, keymap as keymap5, placeholder as placeholder2 } from "@codemirror/view";
947
-
948
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/highlight.ts
949
- import { markdownLanguage } from "@codemirror/lang-markdown";
950
- import { HighlightStyle } from "@codemirror/language";
951
- import { tags, styleTags, Tag } from "@lezer/highlight";
952
- import { Table } from "@lezer/markdown";
953
- import get2 from "lodash.get";
954
- import { mx as mx3 } from "@dxos/react-ui-theme";
955
- var markdownTags = {
956
- Blockquote: Tag.define(),
957
- CodeMark: Tag.define(),
958
- CodeText: Tag.define(),
959
- EmphasisMark: Tag.define(),
960
- HeaderMark: Tag.define(),
961
- HorizontalRule: Tag.define(),
962
- InlineCode: Tag.define(),
963
- LinkLabel: Tag.define(),
964
- LinkReference: Tag.define(),
965
- ListMark: Tag.define(),
966
- QuoteMark: Tag.define(),
967
- URL: Tag.define(),
968
- // Custom.
969
- TableCell: Tag.define()
970
- };
971
- Table.defineNodes?.forEach((node) => {
972
- switch (node?.name) {
973
- case "TableCell": {
974
- node.style = markdownTags.TableCell;
975
- break;
775
+ // packages/ui/react-ui-editor/src/hooks/yjs/cursors.ts
776
+ import * as random2 from "lib0/random";
777
+ var cursorColors = [
778
+ {
779
+ color: "#30bced",
780
+ light: "#30bced33"
781
+ },
782
+ {
783
+ color: "#6eeb83",
784
+ light: "#6eeb8333"
785
+ },
786
+ {
787
+ color: "#ffbc42",
788
+ light: "#ffbc4233"
789
+ },
790
+ {
791
+ color: "#ecd444",
792
+ light: "#ecd44433"
793
+ },
794
+ {
795
+ color: "#ee6352",
796
+ light: "#ee635233"
797
+ },
798
+ {
799
+ color: "#9ac2c9",
800
+ light: "#9ac2c933"
801
+ },
802
+ {
803
+ color: "#8acb88",
804
+ light: "#8acb8833"
805
+ },
806
+ {
807
+ color: "#1be7ff",
808
+ light: "#1be7ff33"
809
+ }
810
+ ];
811
+ var cursorColor = cursorColors[random2.uint32() % cursorColors.length];
812
+
813
+ // packages/ui/react-ui-editor/src/hooks/yjs/space-provider.ts
814
+ import * as decoding from "lib0/decoding";
815
+ import * as encoding from "lib0/encoding";
816
+ import { Observable } from "lib0/observable";
817
+ import * as awarenessProtocol from "y-protocols/awareness";
818
+ import { log as log2 } from "@dxos/log";
819
+ var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/yjs/space-provider.ts";
820
+ var messageAwareness = 1;
821
+ var messageQueryAwareness = 3;
822
+ var SpaceAwarenessProvider = class extends Observable {
823
+ constructor({ space, doc, channel, awareness }) {
824
+ super();
825
+ this._space = space;
826
+ this._awareness = awareness ?? new awarenessProtocol.Awareness(doc);
827
+ this._channel = channel;
828
+ this._clientId = doc.clientID;
829
+ this._awareness.on("update", this._handleAwarenessUpdate.bind(this));
830
+ this._space.listen(this._channel, this._handleSpaceMessage.bind(this));
831
+ if (typeof window !== "undefined") {
832
+ window.addEventListener("beforeunload", this._handleBeforeUnload.bind(this));
833
+ } else if (typeof process !== "undefined") {
834
+ process.on("exit", this._handleBeforeUnload.bind(this));
835
+ }
836
+ const encoderAwarenessQuery = encoding.createEncoder();
837
+ encoding.writeVarUint(encoderAwarenessQuery, messageQueryAwareness);
838
+ void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwarenessQuery));
839
+ const encoderAwarenessState = encoding.createEncoder();
840
+ encoding.writeVarUint(encoderAwarenessState, messageAwareness);
841
+ encoding.writeVarUint8Array(encoderAwarenessState, awarenessProtocol.encodeAwarenessUpdate(this.awareness, [
842
+ this._clientId
843
+ ]));
844
+ void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwarenessState));
845
+ }
846
+ get awareness() {
847
+ return this._awareness;
848
+ }
849
+ _handleAwarenessUpdate({ added, updated, removed }, origin) {
850
+ log2("awareness update", {
851
+ added,
852
+ updated,
853
+ removed,
854
+ origin
855
+ }, {
856
+ F: __dxlog_file3,
857
+ L: 67,
858
+ S: this,
859
+ C: (f, a) => f(...a)
860
+ });
861
+ const changedClients = added.concat(updated).concat(removed);
862
+ const encoderAwareness = encoding.createEncoder();
863
+ encoding.writeVarUint(encoderAwareness, messageAwareness);
864
+ encoding.writeVarUint8Array(encoderAwareness, awarenessProtocol.encodeAwarenessUpdate(this._awareness, changedClients));
865
+ void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwareness));
866
+ }
867
+ _handleSpaceMessage({ payload }) {
868
+ log2("space message", payload, {
869
+ F: __dxlog_file3,
870
+ L: 79,
871
+ S: this,
872
+ C: (f, a) => f(...a)
873
+ });
874
+ const data = new Uint8Array(Array.from(Object.values(payload)));
875
+ const encoder = this._readMessage(data);
876
+ if (encoder) {
877
+ void this._space.postMessage(this._channel, encoding.toUint8Array(encoder));
878
+ }
879
+ }
880
+ _readMessage(message) {
881
+ const decoder = decoding.createDecoder(message);
882
+ const encoder = encoding.createEncoder();
883
+ const messageType = decoding.readVarUint(decoder);
884
+ let sendReply = false;
885
+ switch (messageType) {
886
+ case messageAwareness: {
887
+ awarenessProtocol.applyAwarenessUpdate(this._awareness, decoding.readVarUint8Array(decoder), this);
888
+ break;
889
+ }
890
+ case messageQueryAwareness: {
891
+ encoding.writeVarUint(encoder, messageAwareness);
892
+ encoding.writeVarUint8Array(encoder, awarenessProtocol.encodeAwarenessUpdate(this._awareness, Array.from(this._awareness.getStates().keys())));
893
+ sendReply = true;
894
+ break;
895
+ }
896
+ default: {
897
+ console.error("Invalid message:", messageType);
898
+ return encoder;
899
+ }
900
+ }
901
+ if (!sendReply) {
902
+ return null;
976
903
  }
904
+ return encoder;
905
+ }
906
+ _handleBeforeUnload() {
907
+ awarenessProtocol.removeAwarenessStates(this._awareness, [
908
+ this._clientId
909
+ ], "window unload");
977
910
  }
911
+ };
912
+
913
+ // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
914
+ var __dxlog_file4 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/useTextModel.ts";
915
+ var modelState = StateField2.define({
916
+ create: () => void 0,
917
+ update: (model) => model
978
918
  });
979
- var markdownTagsExtensions = [
980
- Table,
981
- {
982
- props: [
983
- styleTags(markdownTags)
984
- ]
919
+ var useTextModel = (props) => {
920
+ const { identity, space, text } = props;
921
+ const [model, setModel] = useState(() => createModel(props));
922
+ useEffect(() => setModel(createModel(props)), [
923
+ identity,
924
+ space,
925
+ text
926
+ ]);
927
+ return model;
928
+ };
929
+ var createModel = (props) => {
930
+ const { text } = props;
931
+ if (isActualAutomergeObject(text)) {
932
+ return createAutomergeModel(props);
933
+ } else {
934
+ if (!text?.doc) {
935
+ return void 0;
936
+ }
937
+ return createYjsModel(props);
985
938
  }
986
- ];
987
- var markdownHighlightStyle = (readonly) => {
988
- return HighlightStyle.define([
989
- {
990
- tag: [
991
- tags.keyword,
992
- tags.name,
993
- tags.deleted,
994
- tags.character,
995
- tags.propertyName,
996
- tags.macroName,
997
- tags.color,
998
- tags.constant(tags.name),
999
- tags.standard(tags.name),
1000
- tags.definition(tags.name),
1001
- tags.separator,
1002
- tags.typeName,
1003
- tags.className,
1004
- tags.number,
1005
- tags.changed,
1006
- tags.annotation,
1007
- tags.modifier,
1008
- tags.self,
1009
- tags.namespace,
1010
- tags.operator,
1011
- tags.operatorKeyword,
1012
- tags.escape,
1013
- tags.regexp,
1014
- tags.special(tags.string),
1015
- tags.meta,
1016
- tags.comment,
1017
- tags.atom,
1018
- tags.bool,
1019
- tags.special(tags.variableName),
1020
- tags.processingInstruction,
1021
- tags.string,
1022
- tags.inserted,
1023
- tags.invalid
1024
- ],
1025
- color: "inherit !important"
1026
- },
1027
- // Markdown marks.
1028
- {
1029
- tag: [
1030
- tags.meta,
1031
- tags.processingInstruction,
1032
- markdownTags.LinkLabel,
1033
- markdownTags.LinkReference,
1034
- markdownTags.ListMark
1035
- ],
1036
- class: mark
1037
- },
1038
- // Markdown marks.
1039
- {
1040
- tag: [
1041
- markdownTags.CodeMark,
1042
- markdownTags.HeaderMark,
1043
- markdownTags.QuoteMark,
1044
- markdownTags.EmphasisMark
1045
- ],
1046
- class: readonly ? "hidden" : mark
1047
- },
1048
- // E.g., code block language (after ```).
1049
- {
1050
- tag: [
1051
- tags.function(tags.variableName),
1052
- tags.labelName
1053
- ],
1054
- class: readonly ? "hidden" : codeMark
1055
- },
1056
- {
1057
- tag: [
1058
- tags.monospace
1059
- ],
1060
- class: "font-mono"
1061
- },
1062
- // Headings.
1063
- {
1064
- tag: tags.heading1,
1065
- class: heading(1, readonly)
939
+ };
940
+ var createYjsModel = ({ identity, space, text }) => {
941
+ invariant2(text?.doc && text?.content, void 0, {
942
+ F: __dxlog_file4,
943
+ L: 97,
944
+ S: void 0,
945
+ A: [
946
+ "text?.doc && text?.content",
947
+ ""
948
+ ]
949
+ });
950
+ const provider = space ? new SpaceAwarenessProvider({
951
+ space,
952
+ doc: text.doc,
953
+ channel: `yjs.awareness.${text.id}`
954
+ }) : void 0;
955
+ const model = {
956
+ id: text.doc.guid,
957
+ content: text.content,
958
+ text: () => text.content.toString(),
959
+ // https://github.com/yjs/yjs?tab=readme-ov-file#relative-positions
960
+ getCursorFromRange: (range) => {
961
+ const from = Y.encodeRelativePosition(Y.createRelativePositionFromTypeIndex(text.content, range.from));
962
+ const to = Y.encodeRelativePosition(Y.createRelativePositionFromTypeIndex(text.content, range.to, -1));
963
+ return [
964
+ arrayToString(from),
965
+ arrayToString(to)
966
+ ].join(":");
1066
967
  },
1067
- {
1068
- tag: tags.heading2,
1069
- class: heading(2, readonly)
968
+ getRangeFromCursor: (cursor) => {
969
+ const parts = cursor.split(":");
970
+ const from = Y.createAbsolutePositionFromRelativePosition(Y.decodeRelativePosition(stringToArray(parts[0])), text.doc);
971
+ const to = Y.createAbsolutePositionFromRelativePosition(Y.decodeRelativePosition(stringToArray(parts[1])), text.doc);
972
+ return {
973
+ from: from.index,
974
+ to: to.index
975
+ };
1070
976
  },
1071
- {
1072
- tag: tags.heading3,
1073
- class: heading(3, readonly)
1074
- },
1075
- {
1076
- tag: tags.heading4,
1077
- class: heading(4, readonly)
1078
- },
1079
- {
1080
- tag: tags.heading5,
1081
- class: heading(5, readonly)
1082
- },
1083
- {
1084
- tag: tags.heading6,
1085
- class: heading(6, readonly)
1086
- },
1087
- // Emphasis.
1088
- {
1089
- tag: tags.emphasis,
1090
- class: italic
1091
- },
1092
- {
1093
- tag: tags.strong,
1094
- class: bold
1095
- },
1096
- {
1097
- tag: tags.strikethrough,
1098
- class: strikethrough
1099
- },
1100
- // Naked URLs.
1101
- {
1102
- tag: [
1103
- markdownTags.URL
1104
- ],
1105
- class: inlineUrl
1106
- },
1107
- // NOTE: The `markdown` extension configures extensions for `lezer` to parse markdown tokens (incl. below).
1108
- // However, since `codeLanguages` is also defined, the `lezer` will not parse fenced code blocks,
1109
- // when a language is specified. In this case, the syntax highlighting extensions will colorize
1110
- // the code, but all other CSS properties will be inherited.
1111
- // IMPORTANT: Therefore, the fenced code block will use the base editor font.
1112
- {
1113
- tag: [
1114
- markdownTags.CodeText,
1115
- markdownTags.InlineCode
1116
- ],
1117
- class: code
1118
- },
1119
- {
1120
- tag: [
1121
- markdownTags.QuoteMark
1122
- ],
1123
- class: blockquote
1124
- },
1125
- // TODO(burdon): Replace with extension.
1126
- {
1127
- tag: [
1128
- markdownTags.HorizontalRule
1129
- ],
1130
- class: mx3(horizontalRule, readonly && "-indent-[100rem]")
1131
- },
1132
- // TODO(burdon): Only if being edited.
1133
- {
1134
- tag: [
1135
- markdownTags.TableCell
1136
- ],
1137
- class: "font-mono"
1138
- }
1139
- ], {
1140
- scope: markdownLanguage,
1141
- all: {
1142
- fontFamily: get2(tokens, "fontFamily.body", []).join(",")
1143
- }
1144
- });
977
+ extension: [
978
+ yCollab(text.content, provider?.awareness),
979
+ modelState.init(() => model)
980
+ ],
981
+ awareness: provider?.awareness,
982
+ peer: identity ? {
983
+ id: identity.identityKey.toHex(),
984
+ name: identity.profile?.displayName
985
+ } : void 0
986
+ };
987
+ return model;
1145
988
  };
1146
-
1147
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/bundle.ts
1148
- var markdownBundle = ({ readonly, themeMode, placeholder: _placeholder }) => {
1149
- return [
1150
- EditorState.allowMultipleSelections.of(true),
1151
- EditorState.tabSize.of(2),
1152
- EditorView8.lineWrapping,
1153
- customKeymap(),
1154
- bracketMatching2(),
1155
- closeBrackets2(),
1156
- crosshairCursor(),
1157
- dropCursor(),
1158
- drawSelection2(),
1159
- highlightActiveLine(),
1160
- history(),
1161
- indentOnInput(),
1162
- _placeholder && placeholder2(_placeholder),
1163
- // Main extension.
1164
- // https://github.com/codemirror/lang-markdown
1165
- // https://codemirror.net/5/mode/markdown/index.html (demo).
1166
- markdown({
1167
- // GRM by default (vs strict CommonMark):
1168
- // Table, TaskList, Strikethrough, and Autolink.
1169
- // NOTE: This extends the parser; it doesn't affect rendering.
1170
- // https://github.github.com/gfm
1171
- // https://github.com/lezer-parser/markdown?tab=readme-ov-file#github-flavored-markdown
1172
- base: markdownLanguage2,
1173
- // Languages for syntax highlighting fenced code blocks.
1174
- codeLanguages: languages,
1175
- // Parser extensions.
1176
- extensions: [
1177
- // GFM provided by default.
1178
- markdownTagsExtensions
1179
- ]
1180
- }),
1181
- // https://github.com/codemirror/theme-one-dark
1182
- themeMode === "dark" ? syntaxHighlighting2(oneDarkHighlightStyle2) : syntaxHighlighting2(defaultHighlightStyle2),
1183
- // Custom styles.
1184
- syntaxHighlighting2(markdownHighlightStyle(readonly))
1185
- ].filter(Boolean);
989
+ var createAutomergeModel = ({ identity, text }) => {
990
+ const obj = text;
991
+ const doc = getRawDoc(obj, [
992
+ obj.field
993
+ ]);
994
+ const model = {
995
+ id: obj.id,
996
+ content: doc,
997
+ text: () => get2(doc.handle.docSync(), doc.path),
998
+ // TODO(burdon): https://automerge.org/automerge/api-docs/js/functions/next.getCursor.html
999
+ extension: [
1000
+ automergePlugin(doc.handle, doc.path),
1001
+ modelState.init(() => model)
1002
+ ],
1003
+ peer: identity ? {
1004
+ id: identity.identityKey.toHex(),
1005
+ name: identity.profile?.displayName
1006
+ } : void 0
1007
+ };
1008
+ return model;
1186
1009
  };
1187
- var customKeymap = () => keymap5.of([
1188
- // https://codemirror.net/docs/ref/#commands.indentWithTab
1189
- indentWithTab,
1190
- // https://codemirror.net/docs/ref/#commands.standardKeymap
1191
- ...standardKeymap,
1192
- // https://codemirror.net/docs/ref/#commands.historyKeymap
1193
- ...historyKeymap,
1194
- // https://codemirror.net/docs/ref/#autocomplete.closeBracketsKeymap
1195
- ...closeBracketsKeymap,
1196
- // https://codemirror.net/docs/ref/#search.searchKeymap
1197
- ...searchKeymap
1198
- ]);
1199
1010
 
1200
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/mention.ts
1201
- import { autocompletion as autocompletion2 } from "@codemirror/autocomplete";
1202
- var mention = ({ onSearch }) => {
1203
- return autocompletion2({
1204
- closeOnBlur: false,
1205
- override: [
1206
- (context) => {
1207
- const match = context.matchBefore(/@(\w+)?/);
1208
- if (!match || match.from === match.to && !context.explicit) {
1209
- return null;
1210
- }
1211
- console.log(match);
1011
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/comments.ts
1012
+ var __dxlog_file5 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/components/TextEditor/extensions/comments.ts";
1013
+ var styles2 = EditorView4.baseTheme({
1014
+ "& .cm-bookmark": {
1015
+ cursor: "pointer",
1016
+ margin: "4px",
1017
+ padding: "4px",
1018
+ backgroundColor: "yellow"
1019
+ },
1020
+ "& .cm-bookmark-selected": {
1021
+ backgroundColor: "orange"
1022
+ },
1023
+ "& .cm-comment": {
1024
+ backgroundColor: "yellow"
1025
+ },
1026
+ "& .cm-comment-active": {
1027
+ backgroundColor: "orange"
1028
+ }
1029
+ });
1030
+ var marks = {
1031
+ highlight: Decoration2.mark({
1032
+ class: "cm-comment"
1033
+ }),
1034
+ highlightActive: Decoration2.mark({
1035
+ class: "cm-comment-active"
1036
+ })
1037
+ };
1038
+ var setCommentRange = StateEffect2.define();
1039
+ var setSelection = StateEffect2.define();
1040
+ var setCommentState = StateEffect2.define();
1041
+ var commentsStateField = StateField3.define({
1042
+ create: () => ({
1043
+ ranges: []
1044
+ }),
1045
+ update: (value, tr) => {
1046
+ for (const effect of tr.effects) {
1047
+ if (effect.is(setSelection)) {
1212
1048
  return {
1213
- from: match.from,
1214
- options: onSearch(match.text.slice(1).toLowerCase()).map((value) => ({
1215
- label: `@${value}`
1216
- }))
1049
+ ...value,
1050
+ ...effect.value
1217
1051
  };
1218
1052
  }
1219
- ]
1220
- });
1221
- };
1222
-
1223
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/table.ts
1224
- import { syntaxTree as syntaxTree3 } from "@codemirror/language";
1225
- import { RangeSetBuilder as RangeSetBuilder3, StateField as StateField5 } from "@codemirror/state";
1226
- import { Decoration as Decoration6, EditorView as EditorView9, WidgetType as WidgetType4 } from "@codemirror/view";
1227
- var table = (options = {}) => {
1228
- return StateField5.define({
1229
- create: (state) => update3(state, options),
1230
- update: (_, tr) => update3(tr.state, options),
1231
- provide: (field) => EditorView9.decorations.from(field)
1232
- });
1233
- };
1234
- var update3 = (state, options) => {
1235
- const builder = new RangeSetBuilder3();
1236
- const cursor = state.selection.main.head;
1237
- const tables = [];
1238
- const getTable = () => tables[tables.length - 1];
1239
- const getRow = () => {
1240
- const table2 = getTable();
1241
- return table2.rows?.[table2.rows.length - 1];
1242
- };
1243
- syntaxTree3(state).iterate({
1244
- enter: (node) => {
1245
- switch (node.name) {
1246
- case "Table": {
1247
- tables.push({
1248
- from: node.from,
1249
- to: node.to
1250
- });
1251
- break;
1252
- }
1253
- case "TableHeader": {
1254
- getTable().header = [];
1255
- break;
1256
- }
1257
- case "TableRow": {
1258
- (getTable().rows ??= []).push([]);
1259
- break;
1260
- }
1261
- case "TableCell": {
1262
- const row = getRow();
1263
- if (row) {
1264
- row.push(state.sliceDoc(node.from, node.to));
1265
- } else {
1266
- getTable().header?.push(state.sliceDoc(node.from, node.to));
1267
- }
1268
- break;
1269
- }
1053
+ if (effect.is(setCommentRange)) {
1054
+ const { model, comments: comments2 } = effect.value;
1055
+ const ranges = comments2.map((comment) => {
1056
+ const range = model.getRangeFromCursor(comment.cursor);
1057
+ return {
1058
+ ...comment,
1059
+ ...range
1060
+ };
1061
+ });
1062
+ return {
1063
+ ...value,
1064
+ ranges
1065
+ };
1066
+ }
1067
+ if (effect.is(setCommentState)) {
1068
+ return effect.value;
1270
1069
  }
1271
1070
  }
1272
- });
1273
- tables.forEach((table2) => {
1274
- const hide = state.readOnly || cursor < table2.from || cursor > table2.to;
1275
- hide && builder.add(table2.from, table2.to, Decoration6.replace({
1276
- widget: new TableWidget(table2)
1277
- }));
1278
- builder.add(table2.from, table2.to, Decoration6.mark({
1279
- class: "cm-table"
1280
- }));
1281
- });
1282
- return builder.finish();
1283
- };
1284
- var TableWidget = class extends WidgetType4 {
1285
- constructor(_table) {
1286
- super();
1287
- this._table = _table;
1288
- }
1289
- eq(other) {
1290
- return this._table.from === other?._table?.from;
1071
+ return value;
1291
1072
  }
1292
- toDOM(view) {
1293
- const table2 = document.createElement("table");
1294
- {
1295
- const header = table2.appendChild(document.createElement("thead"));
1296
- const tr = header.appendChild(document.createElement("tr"));
1297
- this._table.header?.forEach((cell) => {
1298
- const th = document.createElement("th");
1299
- th.setAttribute("class", "cm-table-head");
1300
- tr.appendChild(th).textContent = cell;
1301
- });
1073
+ });
1074
+ var highlightDecorations = EditorView4.decorations.compute([
1075
+ commentsStateField
1076
+ ], (state) => {
1077
+ const { active, ranges } = state.field(commentsStateField);
1078
+ const decorations = sortBy(ranges ?? [], (range) => range.from)?.flatMap((selection) => {
1079
+ if (selection.from === selection.to) {
1080
+ return void 0;
1302
1081
  }
1303
- {
1304
- const body = table2.appendChild(document.createElement("tbody"));
1305
- this._table.rows?.forEach((row) => {
1306
- const tr = body.appendChild(document.createElement("tr"));
1307
- row.forEach((cell) => {
1308
- const td = document.createElement("td");
1309
- td.setAttribute("class", "cm-table-cell");
1310
- tr.appendChild(td).textContent = cell;
1311
- });
1312
- });
1082
+ const range = {
1083
+ from: selection.from,
1084
+ to: selection.to
1085
+ };
1086
+ if (selection.id === active) {
1087
+ return marks.highlightActive.range(range.from, range.to);
1088
+ } else {
1089
+ return marks.highlight.range(range.from, range.to);
1313
1090
  }
1314
- return table2;
1315
- }
1316
- };
1317
-
1318
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/tasklist.ts
1319
- import { EditorView as EditorView10, Decoration as Decoration7, WidgetType as WidgetType5, MatchDecorator as MatchDecorator2, ViewPlugin as ViewPlugin3 } from "@codemirror/view";
1320
- var styles4 = EditorView10.baseTheme({
1321
- "& .cm-task-item": {
1322
- paddingLeft: "4px",
1323
- paddingRight: "8px"
1324
- }
1091
+ }).filter(nonNullable) ?? [];
1092
+ return Decoration2.set(decorations);
1325
1093
  });
1326
- var CheckboxWidget = class extends WidgetType5 {
1327
- constructor(_pos, _checked, _indent, _onCheck) {
1094
+ var BookmarkWidget = class extends WidgetType {
1095
+ constructor(_anchor, _id, _handleClick) {
1328
1096
  super();
1329
- this._pos = _pos;
1330
- this._checked = _checked;
1331
- this._indent = _indent;
1332
- this._onCheck = _onCheck;
1097
+ this._anchor = _anchor;
1098
+ this._id = _id;
1099
+ this._handleClick = _handleClick;
1100
+ invariant3(this._id, void 0, {
1101
+ F: __dxlog_file5,
1102
+ L: 150,
1103
+ S: this,
1104
+ A: [
1105
+ "this._id",
1106
+ ""
1107
+ ]
1108
+ });
1333
1109
  }
1334
1110
  eq(other) {
1335
- return this._pos === other._pos && this._checked === other._checked && this._indent === other._indent;
1111
+ return this._anchor === other._anchor && this._id === other._id;
1336
1112
  }
1337
- toDOM(view) {
1338
- const wrap = document.createElement("span");
1339
- wrap.className = "cm-task-item";
1340
- wrap.setAttribute("aria-hidden", "true");
1341
- wrap.style.setProperty("margin-left", this._indent * 24 + "px");
1342
- const input = wrap.appendChild(document.createElement("input"));
1343
- input.type = "checkbox";
1344
- input.checked = this._checked;
1345
- if (!this._onCheck) {
1346
- input.setAttribute("disabled", "true");
1347
- }
1348
- if (this._onCheck) {
1349
- input.onchange = (event) => {
1350
- this._onCheck?.(event.target.checked);
1351
- return true;
1352
- };
1113
+ toDOM() {
1114
+ const span = document.createElement("span");
1115
+ span.className = "cm-bookmark";
1116
+ span.textContent = "\u203B";
1117
+ if (this._handleClick) {
1118
+ span.onclick = () => this._handleClick();
1353
1119
  }
1354
- return wrap;
1355
- }
1356
- ignoreEvent() {
1357
- return false;
1120
+ return span;
1358
1121
  }
1359
1122
  };
1360
- var tasklist = (options = {}) => {
1361
- const taskMatcher = new MatchDecorator2({
1362
- regexp: /^(\s*)- \[([ xX])\]\s/g,
1363
- decoration: (match, view, pos) => {
1364
- const indent = Math.floor(match[1].length / 2);
1365
- const checked = match[2] === "x" || match[2] === "X";
1366
- return Decoration7.replace({
1367
- widget: new CheckboxWidget(pos, checked, indent, view.state.readOnly ? void 0 : (checked2) => {
1368
- const idx = pos + match[0].indexOf("[") + 1;
1123
+ var comments = (options = {}) => {
1124
+ const handleSelect = debounce((state) => options.onSelect?.(state), 200);
1125
+ const createCommentThread = (view) => {
1126
+ invariant3(options.onCreate, void 0, {
1127
+ F: __dxlog_file5,
1128
+ L: 204,
1129
+ S: void 0,
1130
+ A: [
1131
+ "options.onCreate",
1132
+ ""
1133
+ ]
1134
+ });
1135
+ const { head, from, to } = view.state.selection.main;
1136
+ if (from === to) {
1137
+ return false;
1138
+ }
1139
+ const model = view.state.field(modelState);
1140
+ const relPos = model?.getCursorFromRange?.({
1141
+ from,
1142
+ to
1143
+ });
1144
+ if (relPos) {
1145
+ const id = callbackWrapper(options.onCreate)(relPos);
1146
+ if (id) {
1147
+ view.dispatch({
1148
+ effects: setSelection.of({
1149
+ active: id
1150
+ }),
1151
+ selection: {
1152
+ anchor: from
1153
+ }
1154
+ });
1155
+ if (options.footnote) {
1156
+ const tag = `[^${id}]`;
1369
1157
  view.dispatch({
1370
1158
  changes: {
1371
- from: idx,
1372
- to: idx + 1,
1373
- insert: checked2 ? "x" : " "
1159
+ from: head,
1160
+ insert: tag
1374
1161
  },
1375
- // TODO(burdon): Restore cursor position? More useful to move to end of line (can indent).
1376
1162
  selection: {
1377
- anchor: idx + 3
1163
+ anchor: head + tag.length
1378
1164
  }
1379
1165
  });
1380
- })
1166
+ }
1167
+ return true;
1168
+ }
1169
+ }
1170
+ return false;
1171
+ };
1172
+ const bookmarksViewPlugin = ViewPlugin3.fromClass(class BookmarkViewPlugin {
1173
+ static {
1174
+ // Match markdown footnotes (option).
1175
+ // https://www.markdownguide.org/extended-syntax/#footnotes
1176
+ this.bookmarkMatcher = new MatchDecorator({
1177
+ regexp: /\[\^(\w+)\]/g,
1178
+ decoration: (match, view, pos) => {
1179
+ const id = match[1];
1180
+ return Decoration2.replace({
1181
+ id,
1182
+ widget: new BookmarkWidget(pos, id)
1183
+ });
1184
+ }
1381
1185
  });
1382
1186
  }
1383
- });
1384
- const tasks = ViewPlugin3.fromClass(class {
1385
1187
  constructor(view) {
1386
- this.tasks = taskMatcher.createDeco(view);
1188
+ this.bookmarks = BookmarkViewPlugin.bookmarkMatcher.createDeco(view);
1387
1189
  }
1388
1190
  update(update4) {
1389
- this.tasks = taskMatcher.updateDeco(update4, this.tasks);
1191
+ this.bookmarks = BookmarkViewPlugin.bookmarkMatcher.updateDeco(update4, this.bookmarks);
1390
1192
  }
1391
1193
  }, {
1392
- decorations: (value) => value.tasks,
1393
- provide: (plugin) => EditorView10.atomicRanges.of((view) => {
1394
- return view.plugin(plugin)?.tasks || Decoration7.none;
1194
+ decorations: (instance) => instance.bookmarks,
1195
+ provide: (plugin) => EditorView4.atomicRanges.of((view) => {
1196
+ return view.plugin(plugin)?.bookmarks || Decoration2.none;
1395
1197
  })
1396
1198
  });
1397
1199
  return [
1398
- tasks,
1399
- styles4
1200
+ bookmarksViewPlugin,
1201
+ commentsStateField,
1202
+ highlightDecorations,
1203
+ styles2,
1204
+ //
1205
+ // Keymap.
1206
+ //
1207
+ options.onCreate ? keymap3.of([
1208
+ {
1209
+ key: options?.key ?? "meta-'",
1210
+ run: createCommentThread
1211
+ }
1212
+ ]) : [],
1213
+ //
1214
+ // Hover tooltip (for key shortcut hints, etc.)
1215
+ // TODO(burdon): Factor out to generic hints extension for current selection/line.
1216
+ //
1217
+ options.onHover ? hoverTooltip((view, pos) => {
1218
+ const selection = view.state.selection.main;
1219
+ if (selection && pos >= selection.from && pos <= selection.to) {
1220
+ return {
1221
+ pos: selection.from,
1222
+ end: selection.to,
1223
+ above: true,
1224
+ create: () => {
1225
+ const el = document.createElement("div");
1226
+ options.onHover?.(el);
1227
+ return {
1228
+ dom: el,
1229
+ offset: {
1230
+ x: 0,
1231
+ y: 8
1232
+ }
1233
+ };
1234
+ }
1235
+ };
1236
+ }
1237
+ return null;
1238
+ }, {
1239
+ hideOnChange: true,
1240
+ hoverTime: 1e3
1241
+ }) : [],
1242
+ //
1243
+ // Monitor cursor movement and text updates.
1244
+ // TODO(burdon): Is there a better (finer grained) way to do this?
1245
+ //
1246
+ EditorView4.updateListener.of(({ view, state, startState, changes }) => {
1247
+ {
1248
+ let mod = false;
1249
+ const model = state.field(modelState);
1250
+ const { active, ranges } = state.field(commentsStateField);
1251
+ changes.iterChanges((from, to, from2, to2) => {
1252
+ invariant3(model, void 0, {
1253
+ F: __dxlog_file5,
1254
+ L: 333,
1255
+ S: void 0,
1256
+ A: [
1257
+ "model",
1258
+ ""
1259
+ ]
1260
+ });
1261
+ ranges.forEach((range) => {
1262
+ if (from2 === to2) {
1263
+ const newRange = model.getRangeFromCursor(range.cursor);
1264
+ if (newRange.to - newRange.from === 0) {
1265
+ log3.info("deleted comment", {
1266
+ thread: range.id
1267
+ }, {
1268
+ F: __dxlog_file5,
1269
+ L: 339,
1270
+ S: void 0,
1271
+ C: (f, a) => f(...a)
1272
+ });
1273
+ }
1274
+ }
1275
+ if (from <= range.to) {
1276
+ const newRange = model.getRangeFromCursor(range.cursor);
1277
+ Object.assign(range, newRange);
1278
+ mod = true;
1279
+ }
1280
+ });
1281
+ });
1282
+ if (mod) {
1283
+ view.dispatch({
1284
+ effects: setCommentState.of({
1285
+ active,
1286
+ ranges
1287
+ })
1288
+ });
1289
+ }
1290
+ }
1291
+ {
1292
+ const { head } = state.selection.main;
1293
+ let min = Infinity;
1294
+ const { active, closest, ranges } = state.field(commentsStateField);
1295
+ const selected = {
1296
+ active: void 0,
1297
+ closest: void 0
1298
+ };
1299
+ ranges.forEach((comment) => {
1300
+ const d = Math.min(Math.abs(head - comment.from), Math.abs(head - comment.to));
1301
+ if (head >= comment.from && head <= comment.to) {
1302
+ selected.active = comment.id;
1303
+ }
1304
+ if (d < min) {
1305
+ selected.closest = comment.id;
1306
+ min = d;
1307
+ }
1308
+ });
1309
+ if (selected.active !== active || selected.closest !== closest) {
1310
+ view.dispatch({
1311
+ effects: setSelection.of(selected)
1312
+ });
1313
+ handleSelect({
1314
+ ...selected,
1315
+ ranges: ranges.map((range) => ({
1316
+ ...range,
1317
+ location: view.coordsAtPos(range.from)
1318
+ }))
1319
+ });
1320
+ }
1321
+ }
1322
+ })
1400
1323
  ];
1401
1324
  };
1402
1325
 
1403
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/tooltip.tsx
1404
- import { hoverTooltip } from "@codemirror/view";
1405
- import { tooltipContent } from "@dxos/react-ui-theme";
1406
- var markdownLinkRegexp = /\[([^\]]+)]\(([^)]+)\)/;
1407
- var tooltip = ({ onHover, regexp = markdownLinkRegexp }) => hoverTooltip((view, pos) => {
1408
- const { from, text } = view.state.doc.lineAt(pos);
1409
- const p = pos - from;
1410
- let idx = 0;
1411
- let match;
1412
- do {
1413
- match = text.substring(idx).match(regexp);
1414
- if (!match) {
1415
- match = void 0;
1416
- break;
1326
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/hr.ts
1327
+ import { Decoration as Decoration3, EditorView as EditorView5, MatchDecorator as MatchDecorator2, ViewPlugin as ViewPlugin4, WidgetType as WidgetType2 } from "@codemirror/view";
1328
+ import get3 from "lodash.get";
1329
+ var styles3 = EditorView5.baseTheme({
1330
+ "& .cm-hr": {
1331
+ // TODO(burdon): ???
1332
+ // Note that block-level decorations should not have vertical margins,
1333
+ borderBottom: `1px solid ${get3(tokens, "extend.colors.neutral.200")}`
1334
+ }
1335
+ });
1336
+ var HorizontalRuleWidget = class extends WidgetType2 {
1337
+ constructor(_pos) {
1338
+ super();
1339
+ this._pos = _pos;
1340
+ }
1341
+ eq(other) {
1342
+ return this._pos === other._pos;
1343
+ }
1344
+ toDOM(view) {
1345
+ const el = document.createElement("div");
1346
+ el.className = "cm-hr";
1347
+ return el;
1348
+ }
1349
+ };
1350
+ var placeholderMatcher = new MatchDecorator2({
1351
+ regexp: /^---$/g,
1352
+ decoration: (match, view, pos) => Decoration3.replace({
1353
+ widget: new HorizontalRuleWidget(pos)
1354
+ })
1355
+ });
1356
+ var hr = () => [
1357
+ styles3,
1358
+ ViewPlugin4.fromClass(class {
1359
+ constructor(view) {
1360
+ this.rules = placeholderMatcher.createDeco(view);
1417
1361
  }
1418
- idx = text.indexOf(match[0]);
1419
- if (p >= idx && p < idx + match[0].length) {
1420
- break;
1362
+ update(update4) {
1363
+ this.rules = placeholderMatcher.updateDeco(update4, this.rules);
1421
1364
  }
1422
- idx += match[0].length;
1423
- } while (true);
1424
- if (!match) {
1425
- return null;
1426
- }
1427
- const [, , url] = match ?? [];
1428
- return {
1429
- pos: idx + from,
1430
- end: idx + from + match[0].length,
1431
- above: true,
1432
- create: () => {
1433
- const el = document.createElement("div");
1434
- el.className = tooltipContent({}, "pli-2 plb-1");
1435
- onHover(el, url);
1436
- return {
1437
- dom: el,
1438
- offset: {
1439
- x: 0,
1440
- y: 12
1365
+ }, {
1366
+ decorations: (instance) => instance.rules,
1367
+ // TODO(burdon): Is this really atomic (cursor can move into ---).
1368
+ provide: (plugin) => EditorView5.atomicRanges.of((view) => {
1369
+ return view.plugin(plugin)?.rules || Decoration3.none;
1370
+ })
1371
+ })
1372
+ ];
1373
+
1374
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/image.ts
1375
+ import { syntaxTree } from "@codemirror/language";
1376
+ import { RangeSetBuilder, StateField as StateField4 } from "@codemirror/state";
1377
+ import { Decoration as Decoration4, EditorView as EditorView6, WidgetType as WidgetType3 } from "@codemirror/view";
1378
+ var image = (options = {}) => {
1379
+ return StateField4.define({
1380
+ create: (state) => update(state, options),
1381
+ update: (_, tr) => update(tr.state, options),
1382
+ provide: (field) => EditorView6.decorations.from(field)
1383
+ });
1384
+ };
1385
+ var update = (state, options) => {
1386
+ const builder = new RangeSetBuilder();
1387
+ const cursor = state.selection.main.head;
1388
+ syntaxTree(state).iterate({
1389
+ enter: (node) => {
1390
+ if (node.name === "Image") {
1391
+ const urlNode = node.node.getChild("URL");
1392
+ if (urlNode) {
1393
+ const hide = state.readOnly || cursor < node.from || cursor > node.to;
1394
+ const url = state.sliceDoc(urlNode.from, urlNode.to);
1395
+ builder.add(hide ? node.from : node.to, node.to, Decoration4.replace({
1396
+ block: true,
1397
+ widget: new ImageWidget(url)
1398
+ }));
1441
1399
  }
1442
- };
1400
+ }
1443
1401
  }
1444
- };
1445
- });
1402
+ });
1403
+ return builder.finish();
1404
+ };
1405
+ var ImageWidget = class extends WidgetType3 {
1406
+ constructor(_url) {
1407
+ super();
1408
+ this._url = _url;
1409
+ }
1410
+ // TODO(burdon): Does this need to be unique for each position?
1411
+ eq(other) {
1412
+ return this._url === other._url;
1413
+ }
1414
+ toDOM(view) {
1415
+ const img = document.createElement("img");
1416
+ img.setAttribute("src", this._url);
1417
+ img.setAttribute("class", "cm-image");
1418
+ return img;
1419
+ }
1420
+ };
1446
1421
 
1447
- // packages/ui/react-ui-editor/src/components/TextEditor/themes/default.ts
1448
- import get3 from "lodash.get";
1449
- var defaultTheme = {
1450
- //
1451
- // Main layout:
1452
- // <div class=".cm-editor .cm-focused">
1453
- // <div class=".cm-scroller">
1454
- // <div class=".cm-content" role="textbox" contenteditable="true">
1455
- // <div class=".cm-line" />
1456
- // </div>
1457
- // </div>
1458
- // </div>
1459
- //
1460
- "&": {},
1461
- "&.cm-focused": {
1462
- outline: "none"
1463
- },
1464
- "& .cm-scroller": {
1465
- overflow: "visible",
1466
- fontFamily: get3(tokens, "fontFamily.mono", []).join(",")
1467
- },
1468
- "& .cm-content": {
1469
- padding: 0,
1470
- // Base font size (otherwise defined by HTML tag, which might be different for storybook).
1471
- fontSize: "16px"
1472
- },
1473
- "&light .cm-content": {
1474
- color: get3(tokens, "extend.colors.neutral.900", "black"),
1475
- caretColor: "black"
1476
- },
1477
- "&dark .cm-content": {
1478
- color: get3(tokens, "extend.colors.neutral.100", "white"),
1479
- caretColor: "white"
1480
- },
1481
- //
1482
- // Cursor
1483
- //
1484
- "&light .cm-cursor, &light .cm-dropCursor": {
1485
- borderLeft: "2px solid black"
1486
- },
1487
- "&dark .cm-cursor, &dark .cm-dropCursor": {
1488
- borderLeft: "2px solid white"
1489
- },
1490
- "& .cm-placeholder": {
1491
- fontWeight: 100
1492
- },
1493
- //
1494
- // line
1495
- //
1496
- "& .cm-line": {
1497
- paddingInline: 0
1498
- },
1499
- "& .cm-line *": {},
1500
- "& .cm-activeLine": {
1501
- backgroundColor: "transparent"
1502
- },
1503
- //
1504
- // selection
1505
- //
1506
- "&light .cm-selectionBackground, &light.cm-focused .cm-selectionBackground": {
1507
- background: get3(tokens, "extend.colors.primary.150", "#00ffff")
1508
- },
1509
- "&dark .cm-selectionBackground, &dark.cm-focused .cm-selectionBackground": {
1510
- background: get3(tokens, "extend.colors.primary.850", "#00ffff")
1511
- },
1512
- "&light .cm-selectionMatch": {
1513
- background: get3(tokens, "extend.colors.primary.250", "#00ffff") + "44"
1514
- },
1515
- "&dark .cm-selectionMatch": {
1516
- background: get3(tokens, "extend.colors.primary.600", "#00ffff") + "44"
1517
- },
1518
- //
1519
- // collaboration
1520
- // TODO(burdon): Review classnames (YJS dependent?)
1521
- //
1522
- "&light .cm-ySelection, &light .cm-yLineSelection": {
1523
- mixBlendMode: "multiply"
1524
- },
1525
- "&dark .cm-ySelection, &dark .cm-yLineSelection": {
1526
- mixBlendMode: "screen"
1527
- },
1528
- "& .cm-ySelectionInfo": {
1529
- padding: "2px 4px",
1530
- marginBlockStart: "-4px"
1531
- },
1532
- "& .cm-ySelection, & .cm-selectionMatch": {
1533
- paddingBlockStart: ".15em",
1534
- paddingBlockEnd: ".15em"
1535
- },
1536
- "& .cm-ySelectionCaret": {
1537
- display: "inline-block",
1538
- insetBlockStart: ".1em",
1539
- blockSize: "1.4em",
1540
- verticalAlign: "top"
1541
- },
1542
- "& .cm-yLineSelection": {
1543
- margin: 0
1544
- },
1545
- //
1546
- // link
1547
- //
1548
- "& .cm-link": {
1549
- color: get3(tokens, "extend.colors.primary.500"),
1550
- textDecorationLine: "underline",
1551
- textDecorationThickness: "1px",
1552
- textUnderlineOffset: "2px",
1553
- borderRadius: ".125rem",
1554
- fontFamily: get3(tokens, "fontFamily.body", []).join(",")
1555
- },
1556
- //
1557
- // tooltip
1558
- //
1559
- "& .cm-tooltip": {
1560
- border: "none"
1561
- },
1562
- "& .cm-tooltip-below": {},
1563
- //
1564
- // autocomplete
1565
- //
1566
- "& .cm-tooltip-autocomplete": {
1567
- marginTop: "4px",
1568
- marginLeft: "-3px"
1569
- },
1570
- "& .cm-tooltip-autocomplete ul li": {},
1571
- "& .cm-tooltip-autocomplete ul li[aria-selected]": {},
1572
- "& .cm-completionIcon": {
1573
- display: "none"
1574
- },
1575
- "& .cm-completionLabel": {
1576
- fontFamily: get3(tokens, "fontFamily.body", []).join(",")
1577
- },
1578
- "& .cm-completionMatchedText": {
1579
- textDecoration: "none"
1580
- },
1581
- //
1582
- // widgets
1583
- //
1584
- "& .cm-widgetBuffer": {
1585
- display: "none",
1586
- height: 0
1587
- },
1588
- //
1589
- // table
1590
- //
1591
- "& .cm-table *": {
1592
- fontFamily: `${get3(tokens, "fontFamily.mono", []).join(",")} !important`,
1593
- textDecoration: "none !important"
1594
- },
1595
- "& .cm-table-head": {
1596
- padding: "2px 16px 2px 0px",
1597
- borderBottom: `1px solid ${get3(tokens, "extend.colors.neutral.500")}`,
1598
- fontWeight: 100,
1599
- textAlign: "left",
1600
- color: get3(tokens, "extend.colors.neutral.500")
1601
- },
1602
- "& .cm-table-cell": {
1603
- padding: "2px 16px 2px 0px"
1604
- },
1605
- //
1606
- // image
1607
- //
1608
- "& .cm-image": {
1609
- margin: "0.5rem 0"
1610
- },
1611
- //
1612
- // font size
1613
- // TODO(thure): This appears to be the best or only way to set selection caret heights, but it's far more verbose than it needs to be.
1614
- //
1615
- ...Object.keys(get3(tokens, "extend.fontSize", {})).reduce((acc, fontSize) => {
1616
- const height = get3(tokens, [
1617
- "extend",
1618
- "fontSize",
1619
- fontSize,
1620
- 1,
1621
- "lineHeight"
1622
- ]);
1623
- acc[`& .text-${fontSize} + .cm-ySelectionCaret`] = {
1624
- height
1625
- };
1626
- acc[`& .text-${fontSize} + .cm-ySelection + .cm-ySelectionCaret`] = {
1627
- height
1628
- };
1629
- acc[`& .text-${fontSize} + .cm-widgetBuffer + .cm-ySelectionCaret`] = {
1630
- height
1631
- };
1632
- return acc;
1633
- }, {}),
1634
- /**
1635
- * Panels
1636
- * https://github.com/codemirror/search/blob/main/src/search.ts#L745
1637
- *
1638
- * Find/replace panel.
1639
- * <div class="cm-announced">...</div>
1640
- * <div class="cm-scroller">...</div>
1641
- * <div class="cm-panels cm-panels-bottom">
1642
- * <div class="cm-search cm-panel">
1643
- * <input class="cm-textfield" />
1644
- * <button class="cm-button">...</button>
1645
- * <label><input type="checkbox" />...</label>
1646
- * </div>
1647
- * </div
1648
- */
1649
- "& .cm-panels": {
1650
- border: `1px solid ${get3(tokens, "extend.colors.neutral.200")}`,
1651
- borderBottom: "none"
1652
- },
1653
- "& .cm-panel": {
1654
- background: get3(tokens, "extend.colors.neutral.50"),
1655
- fontFamily: get3(tokens, "fontFamily.body", []).join(",")
1656
- },
1657
- "& .cm-button": {
1658
- margin: "4px",
1659
- fontFamily: get3(tokens, "fontFamily.body", []).join(","),
1660
- background: get3(tokens, "extend.colors.neutral.100"),
1661
- border: "none"
1662
- },
1663
- "& .cm-searchMatch": {
1664
- backgroundColor: get3(tokens, "extend.colors.yellow.100")
1665
- },
1666
- "& .cm-searchMatch.cm-searchMatch-selected": {
1667
- backgroundColor: get3(tokens, "extend.colors.primary.100")
1422
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/link.ts
1423
+ import { syntaxTree as syntaxTree2 } from "@codemirror/language";
1424
+ import { RangeSetBuilder as RangeSetBuilder2, StateField as StateField5 } from "@codemirror/state";
1425
+ import { Decoration as Decoration5, EditorView as EditorView7, WidgetType as WidgetType4 } from "@codemirror/view";
1426
+ var link = (options = {}) => {
1427
+ return StateField5.define({
1428
+ create: (state) => update2(state, options),
1429
+ update: (_, tr) => update2(tr.state, options),
1430
+ provide: (field) => EditorView7.decorations.from(field)
1431
+ });
1432
+ };
1433
+ var LinkButton = class extends WidgetType4 {
1434
+ constructor(_pos, _url, _onAttach) {
1435
+ super();
1436
+ this._pos = _pos;
1437
+ this._url = _url;
1438
+ this._onAttach = _onAttach;
1439
+ }
1440
+ eq(other) {
1441
+ return this._pos === other._pos && this._url === other._url;
1442
+ }
1443
+ toDOM(view) {
1444
+ const el = document.createElement("span");
1445
+ this._onAttach(el, this._url);
1446
+ return el;
1447
+ }
1448
+ };
1449
+ var LinkText = class extends WidgetType4 {
1450
+ constructor(_pos, _text, _url) {
1451
+ super();
1452
+ this._pos = _pos;
1453
+ this._text = _text;
1454
+ this._url = _url;
1455
+ }
1456
+ eq(other) {
1457
+ return this._pos === other._pos && this._url === other._url;
1458
+ }
1459
+ toDOM(view) {
1460
+ const link2 = document.createElement("a");
1461
+ link2.setAttribute("class", "cm-link");
1462
+ link2.textContent = this._text;
1463
+ if (this._url) {
1464
+ link2.setAttribute("rel", "noreferrer");
1465
+ link2.setAttribute("target", "_blank");
1466
+ link2.href = this._url;
1467
+ } else {
1468
+ link2.onclick = () => {
1469
+ view.dispatch({
1470
+ selection: {
1471
+ anchor: this._pos
1472
+ }
1473
+ });
1474
+ };
1475
+ }
1476
+ return link2;
1668
1477
  }
1669
1478
  };
1670
- var textTheme = {
1671
- "& .cm-scroller": {
1672
- fontFamily: get3(tokens, "fontFamily.body", []).join(",")
1673
- },
1674
- "& .cm-placeholder": {
1675
- fontFamily: get3(tokens, "fontFamily.body", []).join(",")
1676
- }
1479
+ var update2 = (state, options) => {
1480
+ const builder = new RangeSetBuilder2();
1481
+ const cursor = state.selection.main.head;
1482
+ syntaxTree2(state).iterate({
1483
+ enter: (node) => {
1484
+ if (node.name === "Link") {
1485
+ const marks2 = node.node.getChildren("LinkMark");
1486
+ const text = marks2.length >= 2 ? state.sliceDoc(marks2[0].to, marks2[1].from) : "";
1487
+ const urlNode = node.node.getChild("URL");
1488
+ const url = urlNode ? state.sliceDoc(urlNode.from, urlNode.to) : "";
1489
+ if (!url) {
1490
+ return false;
1491
+ }
1492
+ if (state.readOnly || cursor < node.from || cursor > node.to) {
1493
+ builder.add(node.from, node.to, Decoration5.replace({
1494
+ widget: new LinkText(node.from, text, options.link ? url : void 0)
1495
+ }));
1496
+ }
1497
+ if (options.onRender) {
1498
+ builder.add(node.to, node.to, Decoration5.replace({
1499
+ widget: new LinkButton(node.from, url, options.onRender)
1500
+ }));
1501
+ }
1502
+ return false;
1503
+ }
1504
+ }
1505
+ });
1506
+ return builder.finish();
1507
+ };
1508
+
1509
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/listener.ts
1510
+ import { StateField as StateField6 } from "@codemirror/state";
1511
+ var listener = ({ onChange }) => {
1512
+ return StateField6.define({
1513
+ create: () => null,
1514
+ update: (_value, transaction) => {
1515
+ if (transaction.docChanged && onChange) {
1516
+ onChange(transaction.newDoc.toString());
1517
+ }
1518
+ return null;
1519
+ }
1520
+ });
1521
+ };
1522
+
1523
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/bundle.ts
1524
+ import { closeBrackets as closeBrackets2, closeBracketsKeymap } from "@codemirror/autocomplete";
1525
+ import { history, historyKeymap, indentWithTab, standardKeymap } from "@codemirror/commands";
1526
+ import { markdownLanguage as markdownLanguage2, markdown } from "@codemirror/lang-markdown";
1527
+ import { bracketMatching as bracketMatching2, defaultHighlightStyle as defaultHighlightStyle2, indentOnInput, syntaxHighlighting as syntaxHighlighting2 } from "@codemirror/language";
1528
+ import { languages } from "@codemirror/language-data";
1529
+ import { searchKeymap } from "@codemirror/search";
1530
+ import { EditorState } from "@codemirror/state";
1531
+ import { oneDarkHighlightStyle as oneDarkHighlightStyle2 } from "@codemirror/theme-one-dark";
1532
+ import { crosshairCursor, drawSelection as drawSelection2, dropCursor, EditorView as EditorView8, highlightActiveLine, keymap as keymap4, placeholder as placeholder2 } from "@codemirror/view";
1533
+
1534
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/highlight.ts
1535
+ import { markdownLanguage } from "@codemirror/lang-markdown";
1536
+ import { HighlightStyle } from "@codemirror/language";
1537
+ import { tags, styleTags, Tag } from "@lezer/highlight";
1538
+ import { Table } from "@lezer/markdown";
1539
+ import get4 from "lodash.get";
1540
+ var markdownTags = {
1541
+ Blockquote: Tag.define(),
1542
+ CodeMark: Tag.define(),
1543
+ CodeText: Tag.define(),
1544
+ EmphasisMark: Tag.define(),
1545
+ HeaderMark: Tag.define(),
1546
+ // HorizontalRule: Tag.define(),
1547
+ InlineCode: Tag.define(),
1548
+ LinkLabel: Tag.define(),
1549
+ LinkReference: Tag.define(),
1550
+ ListMark: Tag.define(),
1551
+ QuoteMark: Tag.define(),
1552
+ URL: Tag.define(),
1553
+ // Custom.
1554
+ TableCell: Tag.define()
1555
+ };
1556
+ Table.defineNodes?.forEach((node) => {
1557
+ switch (node?.name) {
1558
+ case "TableCell": {
1559
+ node.style = markdownTags.TableCell;
1560
+ break;
1561
+ }
1562
+ }
1563
+ });
1564
+ var markdownTagsExtensions = [
1565
+ Table,
1566
+ {
1567
+ props: [
1568
+ styleTags(markdownTags)
1569
+ ]
1570
+ }
1571
+ ];
1572
+ var markdownHighlightStyle = (readonly) => {
1573
+ return HighlightStyle.define([
1574
+ {
1575
+ tag: [
1576
+ tags.keyword,
1577
+ tags.name,
1578
+ tags.deleted,
1579
+ tags.character,
1580
+ tags.propertyName,
1581
+ tags.macroName,
1582
+ tags.color,
1583
+ tags.constant(tags.name),
1584
+ tags.standard(tags.name),
1585
+ tags.definition(tags.name),
1586
+ tags.separator,
1587
+ tags.typeName,
1588
+ tags.className,
1589
+ tags.number,
1590
+ tags.changed,
1591
+ tags.annotation,
1592
+ tags.modifier,
1593
+ tags.self,
1594
+ tags.namespace,
1595
+ tags.operator,
1596
+ tags.operatorKeyword,
1597
+ tags.escape,
1598
+ tags.regexp,
1599
+ tags.special(tags.string),
1600
+ tags.meta,
1601
+ tags.comment,
1602
+ tags.atom,
1603
+ tags.bool,
1604
+ tags.special(tags.variableName),
1605
+ tags.processingInstruction,
1606
+ tags.string,
1607
+ tags.inserted,
1608
+ tags.invalid
1609
+ ],
1610
+ color: "inherit !important"
1611
+ },
1612
+ // Markdown marks.
1613
+ {
1614
+ tag: [
1615
+ tags.meta,
1616
+ tags.processingInstruction,
1617
+ markdownTags.LinkLabel,
1618
+ markdownTags.LinkReference,
1619
+ markdownTags.ListMark
1620
+ ],
1621
+ class: mark
1622
+ },
1623
+ // Markdown marks.
1624
+ {
1625
+ tag: [
1626
+ markdownTags.CodeMark,
1627
+ markdownTags.HeaderMark,
1628
+ markdownTags.QuoteMark,
1629
+ markdownTags.EmphasisMark
1630
+ ],
1631
+ class: readonly ? "hidden" : mark
1632
+ },
1633
+ // E.g., code block language (after ```).
1634
+ {
1635
+ tag: [
1636
+ tags.function(tags.variableName),
1637
+ tags.labelName
1638
+ ],
1639
+ class: readonly ? "hidden" : codeMark
1640
+ },
1641
+ {
1642
+ tag: [
1643
+ tags.monospace
1644
+ ],
1645
+ class: "font-mono"
1646
+ },
1647
+ // Headings.
1648
+ {
1649
+ tag: tags.heading1,
1650
+ class: heading(1, readonly)
1651
+ },
1652
+ {
1653
+ tag: tags.heading2,
1654
+ class: heading(2, readonly)
1655
+ },
1656
+ {
1657
+ tag: tags.heading3,
1658
+ class: heading(3, readonly)
1659
+ },
1660
+ {
1661
+ tag: tags.heading4,
1662
+ class: heading(4, readonly)
1663
+ },
1664
+ {
1665
+ tag: tags.heading5,
1666
+ class: heading(5, readonly)
1667
+ },
1668
+ {
1669
+ tag: tags.heading6,
1670
+ class: heading(6, readonly)
1671
+ },
1672
+ // Emphasis.
1673
+ {
1674
+ tag: tags.emphasis,
1675
+ class: italic
1676
+ },
1677
+ {
1678
+ tag: tags.strong,
1679
+ class: bold
1680
+ },
1681
+ {
1682
+ tag: tags.strikethrough,
1683
+ class: strikethrough
1684
+ },
1685
+ // Naked URLs.
1686
+ {
1687
+ tag: [
1688
+ markdownTags.URL
1689
+ ],
1690
+ class: inlineUrl
1691
+ },
1692
+ // NOTE: The `markdown` extension configures extensions for `lezer` to parse markdown tokens (incl. below).
1693
+ // However, since `codeLanguages` is also defined, the `lezer` will not parse fenced code blocks,
1694
+ // when a language is specified. In this case, the syntax highlighting extensions will colorize
1695
+ // the code, but all other CSS properties will be inherited.
1696
+ // IMPORTANT: Therefore, the fenced code block will use the base editor font.
1697
+ {
1698
+ tag: [
1699
+ markdownTags.CodeText,
1700
+ markdownTags.InlineCode
1701
+ ],
1702
+ class: code
1703
+ },
1704
+ {
1705
+ tag: [
1706
+ markdownTags.QuoteMark
1707
+ ],
1708
+ class: blockquote
1709
+ },
1710
+ // TODO(burdon): Replace with extension.
1711
+ // {
1712
+ // tag: [markdownTags.HorizontalRule],
1713
+ // class: mx(horizontalRule, readonly && '-indent-[100rem]'),
1714
+ // },
1715
+ // TODO(burdon): Only if being edited.
1716
+ {
1717
+ tag: [
1718
+ markdownTags.TableCell
1719
+ ],
1720
+ class: "font-mono"
1721
+ }
1722
+ ], {
1723
+ scope: markdownLanguage,
1724
+ all: {
1725
+ fontFamily: get4(tokens, "fontFamily.body", []).join(",")
1726
+ }
1727
+ });
1728
+ };
1729
+
1730
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/bundle.ts
1731
+ var markdownBundle = ({ readonly, themeMode, placeholder: _placeholder }) => {
1732
+ return [
1733
+ EditorState.allowMultipleSelections.of(true),
1734
+ EditorState.tabSize.of(2),
1735
+ EditorView8.lineWrapping,
1736
+ customKeymap(),
1737
+ bracketMatching2(),
1738
+ closeBrackets2(),
1739
+ crosshairCursor(),
1740
+ dropCursor(),
1741
+ drawSelection2(),
1742
+ highlightActiveLine(),
1743
+ history(),
1744
+ indentOnInput(),
1745
+ _placeholder && placeholder2(_placeholder),
1746
+ // Main extension.
1747
+ // https://github.com/codemirror/lang-markdown
1748
+ // https://codemirror.net/5/mode/markdown/index.html (demo).
1749
+ markdown({
1750
+ // GRM by default (vs strict CommonMark):
1751
+ // Table, TaskList, Strikethrough, and Autolink.
1752
+ // NOTE: This extends the parser; it doesn't affect rendering.
1753
+ // https://github.github.com/gfm
1754
+ // https://github.com/lezer-parser/markdown?tab=readme-ov-file#github-flavored-markdown
1755
+ base: markdownLanguage2,
1756
+ // Languages for syntax highlighting fenced code blocks.
1757
+ codeLanguages: languages,
1758
+ // Parser extensions.
1759
+ extensions: [
1760
+ // GFM provided by default.
1761
+ markdownTagsExtensions
1762
+ ]
1763
+ }),
1764
+ // https://github.com/codemirror/theme-one-dark
1765
+ themeMode === "dark" ? syntaxHighlighting2(oneDarkHighlightStyle2) : syntaxHighlighting2(defaultHighlightStyle2),
1766
+ // Custom styles.
1767
+ syntaxHighlighting2(markdownHighlightStyle(readonly))
1768
+ ].filter(Boolean);
1677
1769
  };
1678
- var markdownTheme = {
1679
- // NOTE: Must leave base font family as is (i.e., monospace) due to fenced code blocks.
1680
- "& .cm-placeholder": {
1681
- fontFamily: get3(tokens, "fontFamily.body", []).join(",")
1682
- }
1770
+ var customKeymap = () => keymap4.of([
1771
+ // https://codemirror.net/docs/ref/#commands.indentWithTab
1772
+ indentWithTab,
1773
+ // https://codemirror.net/docs/ref/#commands.standardKeymap
1774
+ ...standardKeymap,
1775
+ // https://codemirror.net/docs/ref/#commands.historyKeymap
1776
+ ...historyKeymap,
1777
+ // https://codemirror.net/docs/ref/#autocomplete.closeBracketsKeymap
1778
+ ...closeBracketsKeymap,
1779
+ // https://codemirror.net/docs/ref/#search.searchKeymap
1780
+ ...searchKeymap
1781
+ ]);
1782
+
1783
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/mention.ts
1784
+ import { autocompletion as autocompletion2 } from "@codemirror/autocomplete";
1785
+ var mention = ({ onSearch }) => {
1786
+ return autocompletion2({
1787
+ closeOnBlur: false,
1788
+ override: [
1789
+ (context) => {
1790
+ const match = context.matchBefore(/@(\w+)?/);
1791
+ if (!match || match.from === match.to && !context.explicit) {
1792
+ return null;
1793
+ }
1794
+ console.log(match);
1795
+ return {
1796
+ from: match.from,
1797
+ options: onSearch(match.text.slice(1).toLowerCase()).map((value) => ({
1798
+ label: `@${value}`
1799
+ }))
1800
+ };
1801
+ }
1802
+ ]
1803
+ });
1683
1804
  };
1684
- var codeTheme = {
1685
- "& .cm-scroller": {
1686
- fontFamily: get3(tokens, "fontFamily.mono", []).join(",")
1687
- },
1688
- "& .cm-placeholder": {
1689
- fontFamily: get3(tokens, "fontFamily.mono", []).join(",")
1690
- }
1805
+
1806
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/mermaid.ts
1807
+ var mermaid = () => {
1808
+ return [];
1691
1809
  };
1692
1810
 
1693
- // packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx
1694
- import { EditorState as EditorState2 } from "@codemirror/state";
1695
- import { EditorView as EditorView11 } from "@codemirror/view";
1696
- import { useFocusableGroup } from "@fluentui/react-tabster";
1697
- import { vim } from "@replit/codemirror-vim";
1698
- import defaultsDeep2 from "lodash.defaultsdeep";
1699
- import React, { forwardRef, useCallback, useEffect as useEffect2, useImperativeHandle, useState } from "react";
1700
- import { generateName } from "@dxos/display-name";
1701
- import { useThemeContext } from "@dxos/react-ui";
1702
- import { getColorForValue, inputSurface, mx as mx4 } from "@dxos/react-ui-theme";
1703
- var EditorModes = [
1704
- "default",
1705
- "vim"
1706
- ];
1707
- var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, editorMode, extensions = [], slots = defaultSlots }, forwardedRef) => {
1708
- const { themeMode } = useThemeContext();
1709
- const tabsterDOMAttribute = useFocusableGroup({
1710
- tabBehavior: "limited"
1811
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/table.ts
1812
+ import { syntaxTree as syntaxTree3 } from "@codemirror/language";
1813
+ import { RangeSetBuilder as RangeSetBuilder3, StateField as StateField7 } from "@codemirror/state";
1814
+ import { Decoration as Decoration6, EditorView as EditorView9, WidgetType as WidgetType5 } from "@codemirror/view";
1815
+ var table = (options = {}) => {
1816
+ return StateField7.define({
1817
+ create: (state) => update3(state, options),
1818
+ update: (_, tr) => update3(tr.state, options),
1819
+ provide: (field) => EditorView9.decorations.from(field)
1711
1820
  });
1712
- const [root, setRoot] = useState(null);
1713
- const [state, setState] = useState();
1714
- const [view, setView] = useState();
1715
- useImperativeHandle(forwardedRef, () => ({
1716
- root,
1717
- state,
1718
- view
1719
- }), [
1720
- view,
1721
- state,
1722
- root
1723
- ]);
1724
- const { awareness, peer } = model;
1725
- useEffect2(() => {
1726
- if (awareness && peer) {
1727
- awareness.setLocalStateField("user", {
1728
- name: peer.name ?? generateName(peer.id),
1729
- color: getColorForValue({
1730
- value: peer.id,
1731
- type: "color"
1732
- }),
1733
- colorLight: getColorForValue({
1734
- value: peer.id,
1735
- themeMode,
1736
- type: "highlight"
1737
- })
1738
- });
1739
- }
1740
- }, [
1741
- awareness,
1742
- peer,
1743
- themeMode
1744
- ]);
1745
- useEffect2(() => {
1746
- if (!root) {
1747
- return;
1748
- }
1749
- const state2 = EditorState2.create({
1750
- doc: model.text(),
1751
- extensions: [
1752
- readonly && EditorState2.readOnly.of(readonly),
1753
- // TODO(burdon): Factor out VIM mode?
1754
- editorMode === "vim" && vim(),
1755
- // Theme.
1756
- // TODO(burdon): Make optional.
1757
- EditorView11.baseTheme(defaultTheme),
1758
- EditorView11.theme(slots?.editor?.theme ?? {}),
1759
- // TODO(burdon): themeMode doesn't change in storybooks.
1760
- EditorView11.darkTheme.of(themeMode === "dark"),
1761
- // Storage and replication.
1762
- model.extension,
1763
- // Custom.
1764
- ...extensions
1765
- ].filter(Boolean)
1766
- });
1767
- view?.destroy();
1768
- setView(new EditorView11({
1769
- state: state2,
1770
- parent: root
1771
- }));
1772
- setState(state2);
1773
- return () => {
1774
- view?.destroy();
1775
- setView(void 0);
1776
- setState(void 0);
1777
- };
1778
- }, [
1779
- root,
1780
- model,
1781
- themeMode,
1782
- readonly,
1783
- editorMode
1784
- ]);
1785
- const handleKeyUp = useCallback((event) => {
1786
- const { key, altKey, shiftKey, metaKey, ctrlKey } = event;
1787
- switch (key) {
1788
- case "Enter": {
1789
- view?.contentDOM.focus();
1790
- break;
1791
- }
1792
- case "Escape": {
1793
- editorMode === "vim" && (altKey || shiftKey || metaKey || ctrlKey) && root?.focus();
1794
- break;
1821
+ };
1822
+ var update3 = (state, options) => {
1823
+ const builder = new RangeSetBuilder3();
1824
+ const cursor = state.selection.main.head;
1825
+ const tables = [];
1826
+ const getTable = () => tables[tables.length - 1];
1827
+ const getRow = () => {
1828
+ const table2 = getTable();
1829
+ return table2.rows?.[table2.rows.length - 1];
1830
+ };
1831
+ syntaxTree3(state).iterate({
1832
+ enter: (node) => {
1833
+ switch (node.name) {
1834
+ case "Table": {
1835
+ tables.push({
1836
+ from: node.from,
1837
+ to: node.to
1838
+ });
1839
+ break;
1840
+ }
1841
+ case "TableHeader": {
1842
+ getTable().header = [];
1843
+ break;
1844
+ }
1845
+ case "TableRow": {
1846
+ (getTable().rows ??= []).push([]);
1847
+ break;
1848
+ }
1849
+ case "TableCell": {
1850
+ const row = getRow();
1851
+ if (row) {
1852
+ row.push(state.sliceDoc(node.from, node.to));
1853
+ } else {
1854
+ getTable().header?.push(state.sliceDoc(node.from, node.to));
1855
+ }
1856
+ break;
1857
+ }
1795
1858
  }
1796
1859
  }
1797
- }, [
1798
- view,
1799
- editorMode
1800
- ]);
1801
- return /* @__PURE__ */ React.createElement("div", {
1802
- key: model.id,
1803
- ref: setRoot,
1804
- tabIndex: 0,
1805
- ...slots?.root,
1806
- ...editorMode !== "vim" && tabsterDOMAttribute,
1807
- onKeyUp: handleKeyUp
1808
- });
1809
- });
1810
- var TextEditor = /* @__PURE__ */ forwardRef(({ readonly, extensions = [], slots: _slots, ...props }, forwardedRef) => {
1811
- const { themeMode } = useThemeContext();
1812
- const slots = defaultsDeep2({}, _slots, defaultTextSlots);
1813
- return /* @__PURE__ */ React.createElement(BaseTextEditor, {
1814
- ref: forwardedRef,
1815
- readonly,
1816
- extensions: [
1817
- basicBundle({
1818
- readonly,
1819
- themeMode,
1820
- placeholder: slots?.editor?.placeholder
1821
- }),
1822
- ...extensions
1823
- ],
1824
- slots,
1825
- ...props
1826
1860
  });
1827
- });
1828
- var MarkdownEditor = /* @__PURE__ */ forwardRef(({ readonly, extensions = [], slots: _slots, ...props }, forwardedRef) => {
1829
- const { themeMode } = useThemeContext();
1830
- const slots = defaultsDeep2({}, _slots, defaultMarkdownSlots);
1831
- return /* @__PURE__ */ React.createElement(BaseTextEditor, {
1832
- ref: forwardedRef,
1833
- readonly,
1834
- extensions: [
1835
- markdownBundle({
1836
- readonly,
1837
- themeMode,
1838
- placeholder: slots?.editor?.placeholder
1839
- }),
1840
- ...extensions
1841
- ],
1842
- slots,
1843
- ...props
1861
+ tables.forEach((table2) => {
1862
+ const hide = state.readOnly || cursor < table2.from || cursor > table2.to;
1863
+ hide && builder.add(table2.from, table2.to, Decoration6.replace({
1864
+ block: true,
1865
+ widget: new TableWidget(table2)
1866
+ }));
1867
+ builder.add(table2.from, table2.to, Decoration6.mark({
1868
+ class: "cm-table"
1869
+ }));
1844
1870
  });
1845
- });
1846
- var defaultSlots = {
1847
- root: {
1848
- className: mx4("p-2", inputSurface)
1849
- }
1871
+ return builder.finish();
1850
1872
  };
1851
- var defaultTextSlots = {
1852
- ...defaultSlots,
1853
- editor: {
1854
- theme: textTheme
1873
+ var TableWidget = class extends WidgetType5 {
1874
+ constructor(_table) {
1875
+ super();
1876
+ this._table = _table;
1855
1877
  }
1856
- };
1857
- var defaultMarkdownSlots = {
1858
- ...defaultSlots,
1859
- editor: {
1860
- theme: markdownTheme
1878
+ eq(other) {
1879
+ return this._table.from === other?._table?.from;
1861
1880
  }
1862
- };
1863
-
1864
- // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
1865
- import get4 from "lodash.get";
1866
- import { useEffect as useEffect3, useState as useState2 } from "react";
1867
- import { yCollab } from "y-codemirror.next";
1868
- import { invariant as invariant3 } from "@dxos/invariant";
1869
- import { getRawDoc, isActualAutomergeObject } from "@dxos/react-client/echo";
1870
-
1871
- // packages/ui/react-ui-editor/src/hooks/automerge/plugin.ts
1872
- import { Annotation, Facet, StateEffect as StateEffect2, StateField as StateField6 } from "@codemirror/state";
1873
- import { ViewPlugin as ViewPlugin4 } from "@codemirror/view";
1874
- import * as automerge2 from "@dxos/automerge/automerge";
1875
-
1876
- // packages/ui/react-ui-editor/src/hooks/automerge/PatchSemaphore.ts
1877
- import { next as automerge } from "@dxos/automerge/automerge";
1878
-
1879
- // packages/ui/react-ui-editor/src/hooks/automerge/translation/automerge-to-codemirror.ts
1880
- import { ChangeSet } from "@codemirror/state";
1881
- var automergeToCodemirror = (view, selection, target, patches) => {
1882
- for (const patch of patches) {
1883
- const changeSpec = handlePatch(patch, target, view.state);
1884
- if (changeSpec != null) {
1885
- const changeSet = ChangeSet.of(changeSpec, view.state.doc.length, "\n");
1886
- selection = selection.map(changeSet, 1);
1887
- view.dispatch({
1888
- changes: changeSet,
1889
- annotations: reconcileAnnotationType.of({})
1881
+ toDOM(view) {
1882
+ const table2 = document.createElement("table");
1883
+ {
1884
+ const header = table2.appendChild(document.createElement("thead"));
1885
+ const tr = header.appendChild(document.createElement("tr"));
1886
+ this._table.header?.forEach((cell) => {
1887
+ const th = document.createElement("th");
1888
+ th.setAttribute("class", "cm-table-head");
1889
+ tr.appendChild(th).textContent = cell;
1890
+ });
1891
+ }
1892
+ {
1893
+ const body = table2.appendChild(document.createElement("tbody"));
1894
+ this._table.rows?.forEach((row) => {
1895
+ const tr = body.appendChild(document.createElement("tr"));
1896
+ row.forEach((cell) => {
1897
+ const td = document.createElement("td");
1898
+ td.setAttribute("class", "cm-table-cell");
1899
+ tr.appendChild(td).textContent = cell;
1900
+ });
1890
1901
  });
1891
1902
  }
1903
+ return table2;
1892
1904
  }
1893
- view.dispatch({
1894
- selection,
1895
- annotations: reconcileAnnotationType.of({})
1896
- });
1897
1905
  };
1898
- var handlePatch = (patch, target, state) => {
1899
- if (patch.action === "insert") {
1900
- return handleInsert(target, patch);
1901
- } else if (patch.action === "splice") {
1902
- return handleSplice(target, patch);
1903
- } else if (patch.action === "del") {
1904
- return handleDel(target, patch);
1905
- } else if (patch.action === "put") {
1906
- return handlePut(target, patch, state);
1907
- } else {
1908
- return null;
1906
+
1907
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/tasklist.ts
1908
+ import { EditorView as EditorView10, Decoration as Decoration7, WidgetType as WidgetType6, MatchDecorator as MatchDecorator3, ViewPlugin as ViewPlugin5 } from "@codemirror/view";
1909
+ var styles4 = EditorView10.baseTheme({
1910
+ "& .cm-task-item": {
1911
+ paddingLeft: "4px",
1912
+ paddingRight: "8px"
1909
1913
  }
1910
- };
1911
- var handleInsert = (target, patch) => {
1912
- const index = charPath(target, patch.path);
1913
- if (index == null) {
1914
- return [];
1914
+ });
1915
+ var CheckboxWidget = class extends WidgetType6 {
1916
+ constructor(_pos, _checked, _indent, _onCheck) {
1917
+ super();
1918
+ this._pos = _pos;
1919
+ this._checked = _checked;
1920
+ this._indent = _indent;
1921
+ this._onCheck = _onCheck;
1915
1922
  }
1916
- const text = patch.values.map((v) => v ? v.toString() : "").join("");
1917
- return [
1918
- {
1919
- from: index,
1920
- to: index,
1921
- insert: text
1922
- }
1923
- ];
1924
- };
1925
- var handleSplice = (target, patch) => {
1926
- const index = charPath(target, patch.path);
1927
- if (index == null) {
1928
- return [];
1923
+ eq(other) {
1924
+ return this._pos === other._pos && this._checked === other._checked && this._indent === other._indent;
1929
1925
  }
1930
- return [
1931
- {
1932
- from: index,
1933
- insert: patch.value
1926
+ toDOM(view) {
1927
+ const wrap = document.createElement("span");
1928
+ wrap.className = "cm-task-item";
1929
+ wrap.setAttribute("aria-hidden", "true");
1930
+ wrap.style.setProperty("margin-left", this._indent * 24 + "px");
1931
+ const input = wrap.appendChild(document.createElement("input"));
1932
+ input.type = "checkbox";
1933
+ input.checked = this._checked;
1934
+ if (!this._onCheck) {
1935
+ input.setAttribute("disabled", "true");
1934
1936
  }
1935
- ];
1936
- };
1937
- var handleDel = (target, patch) => {
1938
- const index = charPath(target, patch.path);
1939
- if (index == null) {
1940
- return [];
1937
+ if (this._onCheck) {
1938
+ input.onchange = (event) => {
1939
+ this._onCheck?.(event.target.checked);
1940
+ return true;
1941
+ };
1942
+ }
1943
+ return wrap;
1941
1944
  }
1942
- const length = patch.length || 1;
1943
- return [
1944
- {
1945
- from: index,
1946
- to: index + length
1945
+ ignoreEvent() {
1946
+ return false;
1947
+ }
1948
+ };
1949
+ var tasklist = (options = {}) => {
1950
+ const taskMatcher = new MatchDecorator3({
1951
+ regexp: /^(\s*)- \[([ xX])\]\s/g,
1952
+ decoration: (match, view, pos) => {
1953
+ const indent = Math.floor(match[1].length / 2);
1954
+ const checked = match[2] === "x" || match[2] === "X";
1955
+ return Decoration7.replace({
1956
+ widget: new CheckboxWidget(pos, checked, indent, view.state.readOnly ? void 0 : (checked2) => {
1957
+ const idx = pos + match[0].indexOf("[") + 1;
1958
+ view.dispatch({
1959
+ changes: {
1960
+ from: idx,
1961
+ to: idx + 1,
1962
+ insert: checked2 ? "x" : " "
1963
+ },
1964
+ // TODO(burdon): Restore cursor position? More useful to move to end of line (can indent).
1965
+ selection: {
1966
+ anchor: idx + 3
1967
+ }
1968
+ });
1969
+ })
1970
+ });
1971
+ }
1972
+ });
1973
+ const tasks = ViewPlugin5.fromClass(class {
1974
+ constructor(view) {
1975
+ this.tasks = taskMatcher.createDeco(view);
1976
+ }
1977
+ update(update4) {
1978
+ this.tasks = taskMatcher.updateDeco(update4, this.tasks);
1947
1979
  }
1980
+ }, {
1981
+ decorations: (value) => value.tasks,
1982
+ provide: (plugin) => EditorView10.atomicRanges.of((view) => {
1983
+ return view.plugin(plugin)?.tasks || Decoration7.none;
1984
+ })
1985
+ });
1986
+ return [
1987
+ tasks,
1988
+ styles4
1948
1989
  ];
1949
1990
  };
1950
- var handlePut = (target, patch, state) => {
1951
- const index = charPath(target, [
1952
- ...patch.path,
1953
- 0
1954
- ]);
1955
- if (index == null) {
1956
- return [];
1957
- }
1958
- const length = state.doc.length;
1959
- if (typeof patch.value !== "string") {
1960
- return [];
1991
+
1992
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/tooltip.ts
1993
+ import { hoverTooltip as hoverTooltip2 } from "@codemirror/view";
1994
+ import { tooltipContent } from "@dxos/react-ui-theme";
1995
+ var markdownLinkRegexp = /\[([^\]]+)]\(([^)]+)\)/;
1996
+ var tooltip = ({ onHover, regexp = markdownLinkRegexp }) => hoverTooltip2((view, pos) => {
1997
+ const { from, text } = view.state.doc.lineAt(pos);
1998
+ const p = pos - from;
1999
+ let idx = 0;
2000
+ let match;
2001
+ do {
2002
+ match = text.substring(idx).match(regexp);
2003
+ if (!match) {
2004
+ match = void 0;
2005
+ break;
2006
+ }
2007
+ idx = text.indexOf(match[0]);
2008
+ if (p >= idx && p < idx + match[0].length) {
2009
+ break;
2010
+ }
2011
+ idx += match[0].length;
2012
+ } while (true);
2013
+ if (!match) {
2014
+ return null;
1961
2015
  }
1962
- return [
1963
- {
1964
- from: 0,
1965
- to: length,
1966
- insert: patch.value
2016
+ const [, , url] = match ?? [];
2017
+ return {
2018
+ pos: idx + from,
2019
+ end: idx + from + match[0].length,
2020
+ above: true,
2021
+ create: () => {
2022
+ const el = document.createElement("div");
2023
+ el.className = tooltipContent({}, "pli-2 plb-1");
2024
+ onHover(el, url);
2025
+ return {
2026
+ dom: el,
2027
+ offset: {
2028
+ x: 0,
2029
+ y: 4
2030
+ }
2031
+ };
1967
2032
  }
2033
+ };
2034
+ });
2035
+
2036
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/typewriter.ts
2037
+ import { keymap as keymap5 } from "@codemirror/view";
2038
+ var defaultItems = [
2039
+ "hello world!",
2040
+ "this is a test.",
2041
+ "this is [DXOS](https://dxos.org)"
2042
+ ];
2043
+ var typewriter = ({ delay = 75, items = defaultItems } = {}) => {
2044
+ let t;
2045
+ let idx = 0;
2046
+ return [
2047
+ keymap5.of([
2048
+ {
2049
+ // Reset.
2050
+ key: "alt-meta-'",
2051
+ run: (view) => {
2052
+ clearTimeout(t);
2053
+ idx = 0;
2054
+ return true;
2055
+ }
2056
+ },
2057
+ {
2058
+ // Next prompt.
2059
+ // TODO(burdon): Press 1-9 to select prompt?
2060
+ key: "shift-meta-'",
2061
+ run: (view) => {
2062
+ clearTimeout(t);
2063
+ const text = items[idx++];
2064
+ if (idx === items?.length) {
2065
+ idx = 0;
2066
+ }
2067
+ let i = 0;
2068
+ const insert = (d = 0) => {
2069
+ t = setTimeout(() => {
2070
+ const pos = view.state.selection.main.head;
2071
+ view.dispatch({
2072
+ changes: {
2073
+ from: pos,
2074
+ insert: text[i++]
2075
+ },
2076
+ selection: {
2077
+ anchor: pos + 1
2078
+ }
2079
+ });
2080
+ if (i < text.length) {
2081
+ insert(Math.random() * delay * (text[i] === " " ? 2 : 1));
2082
+ }
2083
+ }, d);
2084
+ };
2085
+ insert();
2086
+ return true;
2087
+ }
2088
+ }
2089
+ ])
1968
2090
  ];
1969
2091
  };
1970
- var charPath = (textPath, candidatePath) => {
1971
- if (candidatePath.length !== textPath.length + 1) {
1972
- return null;
1973
- }
1974
- for (let i = 0; i < textPath.length; i++) {
1975
- if (textPath[i] !== candidatePath[i]) {
1976
- return null;
2092
+
2093
+ // packages/ui/react-ui-editor/src/components/TextEditor/themes/default.ts
2094
+ import get5 from "lodash.get";
2095
+ var defaultTheme = {
2096
+ //
2097
+ // Main layout:
2098
+ // https://codemirror.net/examples/styling
2099
+ //
2100
+ // <div class="cm-editor [cm-focused] [generated classes]">
2101
+ // <div class="cm-scroller">
2102
+ // <div class="cm-gutters">
2103
+ // <div class="cm-gutter [...]">
2104
+ // <div class="cm-gutterElement">...</div>
2105
+ // </div>
2106
+ // </div>
2107
+ // <div class="cm-content" role="textbox" contenteditable="true">
2108
+ // <div class="cm-line"></div>
2109
+ // </div>
2110
+ // <div class="cm-selectionLayer">
2111
+ // <div class="cm-selectionBackground"></div>
2112
+ // </div>
2113
+ // <div class="cm-cursorLayer">
2114
+ // <div class="cm-cursor"></div>
2115
+ // </div>
2116
+ // </div>
2117
+ // </div>
2118
+ //
2119
+ "&": {},
2120
+ "&.cm-focused": {
2121
+ outline: "none"
2122
+ },
2123
+ "& .cm-scroller": {
2124
+ overflow: "visible",
2125
+ fontFamily: get5(tokens, "fontFamily.mono", []).join(",")
2126
+ },
2127
+ "& .cm-content": {
2128
+ padding: 0,
2129
+ // NOTE: Base font size (otherwise defined by HTML tag, which might be different for storybook).
2130
+ fontSize: "16px"
2131
+ },
2132
+ "&light .cm-content": {
2133
+ color: get5(tokens, "extend.colors.neutral.900", "black"),
2134
+ caretColor: "black"
2135
+ },
2136
+ "&dark .cm-content": {
2137
+ color: get5(tokens, "extend.colors.neutral.100", "white"),
2138
+ caretColor: "white"
2139
+ },
2140
+ //
2141
+ // Cursor
2142
+ //
2143
+ "&light .cm-cursor, &light .cm-dropCursor": {
2144
+ borderLeft: "2px solid black"
2145
+ },
2146
+ "&dark .cm-cursor, &dark .cm-dropCursor": {
2147
+ borderLeft: "2px solid white"
2148
+ },
2149
+ "& .cm-placeholder": {
2150
+ fontWeight: 100
2151
+ },
2152
+ //
2153
+ // line
2154
+ //
2155
+ "& .cm-line": {
2156
+ paddingInline: 0
2157
+ },
2158
+ "& .cm-activeLine": {
2159
+ background: "inherit"
2160
+ },
2161
+ //
2162
+ // Selection
2163
+ //
2164
+ "&light .cm-selectionBackground": {
2165
+ background: get5(tokens, "extend.colors.primary.100")
2166
+ },
2167
+ "&light.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground": {
2168
+ background: get5(tokens, "extend.colors.primary.200")
2169
+ },
2170
+ "&dark .cm-selectionBackground": {
2171
+ background: get5(tokens, "extend.colors.primary.700")
2172
+ },
2173
+ "&dark.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground": {
2174
+ background: get5(tokens, "extend.colors.primary.600")
2175
+ },
2176
+ //
2177
+ // Search
2178
+ //
2179
+ "&light .cm-searchMatch": {
2180
+ backgroundColor: get5(tokens, "extend.colors.yellow.100")
2181
+ },
2182
+ "&dark .cm-searchMatch": {
2183
+ backgroundColor: get5(tokens, "extend.colors.yellow.700")
2184
+ },
2185
+ //
2186
+ // collaboration
2187
+ // TODO(burdon): Review classnames (YJS dependent?) Note: e2e tests depend on this class.
2188
+ //
2189
+ "&light .cm-ySelection, &light .cm-yLineSelection": {
2190
+ mixBlendMode: "multiply"
2191
+ },
2192
+ "&dark .cm-ySelection, &dark .cm-yLineSelection": {
2193
+ mixBlendMode: "screen"
2194
+ },
2195
+ "& .cm-ySelectionInfo": {
2196
+ padding: "2px 4px",
2197
+ marginBlockStart: "-4px"
2198
+ },
2199
+ "& .cm-ySelection, & .cm-selectionMatch": {
2200
+ paddingBlockStart: ".15em",
2201
+ paddingBlockEnd: ".15em"
2202
+ },
2203
+ "& .cm-ySelectionCaret": {
2204
+ display: "inline-block",
2205
+ insetBlockStart: ".1em",
2206
+ blockSize: "1.4em",
2207
+ verticalAlign: "top"
2208
+ },
2209
+ "& .cm-yLineSelection": {
2210
+ margin: 0
2211
+ },
2212
+ //
2213
+ // link
2214
+ //
2215
+ "& .cm-link": {
2216
+ color: get5(tokens, "extend.colors.primary.500"),
2217
+ textDecorationLine: "underline",
2218
+ textDecorationThickness: "1px",
2219
+ textUnderlineOffset: "2px",
2220
+ borderRadius: ".125rem",
2221
+ fontFamily: get5(tokens, "fontFamily.body", []).join(",")
2222
+ },
2223
+ //
2224
+ // tooltip
2225
+ //
2226
+ "& .cm-tooltip": {
2227
+ border: "none"
2228
+ },
2229
+ "& .cm-tooltip-below": {},
2230
+ //
2231
+ // autocomplete
2232
+ //
2233
+ "& .cm-tooltip-autocomplete": {
2234
+ marginTop: "4px",
2235
+ marginLeft: "-3px"
2236
+ },
2237
+ "& .cm-tooltip-autocomplete ul li": {},
2238
+ "& .cm-tooltip-autocomplete ul li[aria-selected]": {},
2239
+ "& .cm-completionIcon": {
2240
+ display: "none"
2241
+ },
2242
+ "& .cm-completionLabel": {
2243
+ fontFamily: get5(tokens, "fontFamily.body", []).join(",")
2244
+ },
2245
+ "& .cm-completionMatchedText": {
2246
+ textDecoration: "none"
2247
+ },
2248
+ //
2249
+ // widgets
2250
+ //
2251
+ "& .cm-widgetBuffer": {
2252
+ display: "none",
2253
+ height: 0
2254
+ },
2255
+ //
2256
+ // table
2257
+ //
2258
+ "& .cm-table *": {
2259
+ fontFamily: `${get5(tokens, "fontFamily.mono", []).join(",")} !important`,
2260
+ textDecoration: "none !important"
2261
+ },
2262
+ "& .cm-table-head": {
2263
+ padding: "2px 16px 2px 0px",
2264
+ borderBottom: `1px solid ${get5(tokens, "extend.colors.neutral.500")}`,
2265
+ fontWeight: 100,
2266
+ textAlign: "left",
2267
+ color: get5(tokens, "extend.colors.neutral.500")
2268
+ },
2269
+ "& .cm-table-cell": {
2270
+ padding: "2px 16px 2px 0px"
2271
+ },
2272
+ //
2273
+ // image
2274
+ //
2275
+ "& .cm-image": {
2276
+ margin: "0.5rem 0"
2277
+ },
2278
+ //
2279
+ // font size
2280
+ // TODO(thure): This appears to be the best or only way to set selection caret heights, but it's far more verbose than it needs to be.
2281
+ //
2282
+ ...Object.keys(get5(tokens, "extend.fontSize", {})).reduce((acc, fontSize) => {
2283
+ const height = get5(tokens, [
2284
+ "extend",
2285
+ "fontSize",
2286
+ fontSize,
2287
+ 1,
2288
+ "lineHeight"
2289
+ ]);
2290
+ acc[`& .text-${fontSize} + .cm-ySelectionCaret`] = {
2291
+ height
2292
+ };
2293
+ acc[`& .text-${fontSize} + .cm-ySelection + .cm-ySelectionCaret`] = {
2294
+ height
2295
+ };
2296
+ acc[`& .text-${fontSize} + .cm-widgetBuffer + .cm-ySelectionCaret`] = {
2297
+ height
2298
+ };
2299
+ return acc;
2300
+ }, {}),
2301
+ /**
2302
+ * Panels
2303
+ * TODO(burdon): Needs styling attention (esp. dark mode).
2304
+ * https://github.com/codemirror/search/blob/main/src/search.ts#L745
2305
+ *
2306
+ * Find/replace panel.
2307
+ * <div class="cm-announced">...</div>
2308
+ * <div class="cm-scroller">...</div>
2309
+ * <div class="cm-panels cm-panels-bottom">
2310
+ * <div class="cm-search cm-panel">
2311
+ * <input class="cm-textfield" />
2312
+ * <button class="cm-button">...</button>
2313
+ * <label><input type="checkbox" />...</label>
2314
+ * </div>
2315
+ * </div
2316
+ */
2317
+ "& .cm-panels": {
2318
+ border: `1px solid ${get5(tokens, "extend.colors.neutral.200")}`
2319
+ },
2320
+ "& .cm-panel": {
2321
+ background: get5(tokens, "extend.colors.neutral.50"),
2322
+ fontFamily: get5(tokens, "fontFamily.body", []).join(",")
2323
+ },
2324
+ "& .cm-button": {
2325
+ margin: "4px",
2326
+ fontFamily: get5(tokens, "fontFamily.body", []).join(","),
2327
+ background: get5(tokens, "extend.colors.neutral.100"),
2328
+ backgroundImage: "none",
2329
+ border: "none",
2330
+ "&:hover": {
2331
+ background: get5(tokens, "extend.colors.neutral.200")
2332
+ },
2333
+ "&:active": {
2334
+ background: get5(tokens, "extend.colors.neutral.300"),
2335
+ backgroundImage: "none"
1977
2336
  }
2337
+ },
2338
+ "& .cm-panel input[type=checkbox]": {
2339
+ marginRight: "0.4rem !important"
1978
2340
  }
1979
- const index = candidatePath[candidatePath.length - 1];
1980
- if (typeof index === "number") {
1981
- return index;
1982
- }
1983
- return null;
1984
2341
  };
1985
-
1986
- // packages/ui/react-ui-editor/src/hooks/automerge/translation/codemirror-to-automerge.ts
1987
- import { next as am } from "@dxos/automerge/automerge";
1988
- var codemirrorToAutomerge = (field, handle, transactions, state) => {
1989
- const { lastHeads, path } = state.field(field);
1990
- let hasChanges = false;
1991
- for (const tr of transactions) {
1992
- tr.changes.iterChanges(() => {
1993
- hasChanges = true;
1994
- });
1995
- }
1996
- if (!hasChanges) {
1997
- return void 0;
2342
+ var textTheme = {
2343
+ "& .cm-scroller": {
2344
+ fontFamily: get5(tokens, "fontFamily.body", []).join(",")
2345
+ },
2346
+ "& .cm-placeholder": {
2347
+ fontFamily: get5(tokens, "fontFamily.body", []).join(",")
1998
2348
  }
1999
- const newHeads = handle.changeAt(lastHeads, (doc) => {
2000
- for (const tr of transactions) {
2001
- tr.changes.iterChanges((fromA, toA, _fromB, _toB, inserted) => {
2002
- am.splice(doc, path, fromA, toA - fromA, inserted.toString());
2003
- });
2004
- }
2005
- });
2006
- return newHeads ?? void 0;
2007
2349
  };
2008
-
2009
- // packages/ui/react-ui-editor/src/hooks/automerge/PatchSemaphore.ts
2010
- var PatchSemaphore = class {
2011
- constructor(field) {
2012
- this._inReconcile = false;
2013
- this._queue = [];
2014
- this.reconcile = (handle, view) => {
2015
- if (this._inReconcile) {
2016
- return;
2017
- }
2018
- this._inReconcile = true;
2019
- const path = getPath(view.state, this._field);
2020
- const oldHeads = getLastHeads(view.state, this._field);
2021
- let selection = view.state.selection;
2022
- const transactions = view.state.field(this._field).unreconciledTransactions.filter((tx) => !isReconcileTx(tx));
2023
- const toInvert = transactions.slice().reverse();
2024
- for (const tx of toInvert) {
2025
- const inverted = tx.changes.invert(tx.startState.doc);
2026
- selection = selection.map(inverted);
2027
- view.dispatch({
2028
- changes: inverted,
2029
- annotations: reconcileAnnotationType.of(true)
2030
- });
2031
- }
2032
- let newHeads = codemirrorToAutomerge(this._field, handle, transactions, view.state);
2033
- if (newHeads === null || newHeads === void 0) {
2034
- newHeads = automerge.getHeads(handle.docSync());
2035
- }
2036
- const diff = automerge.equals(oldHeads, newHeads) ? [] : automerge.diff(handle.docSync(), oldHeads, newHeads);
2037
- automergeToCodemirror(view, selection, path, diff);
2038
- view.dispatch({
2039
- effects: updateHeads(newHeads),
2040
- annotations: reconcileAnnotationType.of({})
2041
- });
2042
- this._inReconcile = false;
2043
- };
2044
- if (field !== void 0) {
2045
- this._field = field;
2046
- }
2350
+ var markdownTheme = {
2351
+ // NOTE: Must leave base font family as is (i.e., monospace) due to fenced code blocks.
2352
+ "& .cm-placeholder": {
2353
+ fontFamily: get5(tokens, "fontFamily.body", []).join(",")
2047
2354
  }
2048
2355
  };
2049
-
2050
- // packages/ui/react-ui-editor/src/hooks/automerge/plugin.ts
2051
- var effectType = StateEffect2.define({});
2052
- var updateHeads = (newHeads) => effectType.of({
2053
- newHeads
2054
- });
2055
- var getLastHeads = (state, field) => state.field(field).lastHeads;
2056
- var getPath = (state, field) => state.field(field).path;
2057
- var semaphoreFacet = Facet.define({
2058
- combine: (values) => values.at(-1)
2059
- });
2060
- var automergePlugin = (handle, path) => {
2061
- const stateField = StateField6.define({
2062
- create: () => ({
2063
- lastHeads: automerge2.getHeads(handle.docSync()),
2064
- unreconciledTransactions: [],
2065
- path: path.slice()
2066
- }),
2067
- update: (value, tr) => {
2068
- const result = {
2069
- lastHeads: value.lastHeads,
2070
- unreconciledTransactions: value.unreconciledTransactions.slice(),
2071
- path: path.slice()
2072
- };
2073
- let clearUnreconciled = false;
2074
- for (const effect of tr.effects) {
2075
- if (effect.is(effectType)) {
2076
- result.lastHeads = effect.value.newHeads;
2077
- clearUnreconciled = true;
2078
- }
2079
- }
2080
- if (clearUnreconciled) {
2081
- result.unreconciledTransactions = [];
2082
- } else {
2083
- if (!isReconcileTx(tr)) {
2084
- result.unreconciledTransactions.push(tr);
2085
- }
2086
- }
2087
- return result;
2088
- }
2089
- });
2090
- const semaphore = new PatchSemaphore(stateField);
2091
- const viewPlugin = ViewPlugin4.fromClass(class AutomergeCodemirrorViewPlugin {
2092
- constructor(view) {
2093
- this._handleChange = () => {
2094
- this._view.state.facet(semaphoreFacet).reconcile(handle, this._view);
2095
- };
2096
- this._view = view;
2097
- handle.addListener("change", this._handleChange);
2098
- }
2099
- update(update4) {
2100
- if (update4.transactions.length > 0 && update4.transactions.some((t) => !isReconcileTx(t))) {
2101
- queueMicrotask(() => {
2102
- this._view.state.facet(semaphoreFacet).reconcile(handle, this._view);
2103
- });
2104
- }
2105
- }
2106
- destroy() {
2107
- handle.addListener("change", this._handleChange);
2108
- }
2109
- });
2110
- return {
2111
- extension: [
2112
- stateField,
2113
- semaphoreFacet.of(semaphore),
2114
- viewPlugin
2115
- ]
2116
- };
2117
- };
2118
- var reconcileAnnotationType = Annotation.define();
2119
- var isReconcileTx = (tr) => !!tr.annotation(reconcileAnnotationType);
2120
-
2121
- // packages/ui/react-ui-editor/src/hooks/yjs/cursors.ts
2122
- import * as random2 from "lib0/random";
2123
- var cursorColors = [
2124
- {
2125
- color: "#30bced",
2126
- light: "#30bced33"
2127
- },
2128
- {
2129
- color: "#6eeb83",
2130
- light: "#6eeb8333"
2131
- },
2132
- {
2133
- color: "#ffbc42",
2134
- light: "#ffbc4233"
2135
- },
2136
- {
2137
- color: "#ecd444",
2138
- light: "#ecd44433"
2139
- },
2140
- {
2141
- color: "#ee6352",
2142
- light: "#ee635233"
2143
- },
2144
- {
2145
- color: "#9ac2c9",
2146
- light: "#9ac2c933"
2147
- },
2148
- {
2149
- color: "#8acb88",
2150
- light: "#8acb8833"
2356
+ var codeTheme = {
2357
+ "& .cm-scroller": {
2358
+ fontFamily: get5(tokens, "fontFamily.mono", []).join(",")
2151
2359
  },
2152
- {
2153
- color: "#1be7ff",
2154
- light: "#1be7ff33"
2360
+ "& .cm-placeholder": {
2361
+ fontFamily: get5(tokens, "fontFamily.mono", []).join(",")
2155
2362
  }
2156
- ];
2157
- var cursorColor = cursorColors[random2.uint32() % cursorColors.length];
2363
+ };
2158
2364
 
2159
- // packages/ui/react-ui-editor/src/hooks/yjs/space-provider.ts
2160
- import * as decoding from "lib0/decoding";
2161
- import * as encoding from "lib0/encoding";
2162
- import { Observable } from "lib0/observable";
2163
- import * as awarenessProtocol from "y-protocols/awareness";
2164
- import { log } from "@dxos/log";
2165
- var __dxlog_file3 = "/home/circleci/project/packages/ui/react-ui-editor/src/hooks/yjs/space-provider.ts";
2166
- var messageAwareness = 1;
2167
- var messageQueryAwareness = 3;
2168
- var SpaceAwarenessProvider = class extends Observable {
2169
- constructor({ space, doc, channel, awareness }) {
2170
- super();
2171
- this._space = space;
2172
- this._awareness = awareness ?? new awarenessProtocol.Awareness(doc);
2173
- this._channel = channel;
2174
- this._clientId = doc.clientID;
2175
- this._awareness.on("update", this._handleAwarenessUpdate.bind(this));
2176
- this._space.listen(this._channel, this._handleSpaceMessage.bind(this));
2177
- if (typeof window !== "undefined") {
2178
- window.addEventListener("beforeunload", this._handleBeforeUnload.bind(this));
2179
- } else if (typeof process !== "undefined") {
2180
- process.on("exit", this._handleBeforeUnload.bind(this));
2365
+ // packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx
2366
+ import { EditorState as EditorState2 } from "@codemirror/state";
2367
+ import { EditorView as EditorView11 } from "@codemirror/view";
2368
+ import { useFocusableGroup } from "@fluentui/react-tabster";
2369
+ import { vim } from "@replit/codemirror-vim";
2370
+ import defaultsDeep2 from "lodash.defaultsdeep";
2371
+ import React, { forwardRef, useCallback, useEffect as useEffect2, useImperativeHandle, useState as useState2 } from "react";
2372
+ import { generateName } from "@dxos/display-name";
2373
+ import { useThemeContext } from "@dxos/react-ui";
2374
+ import { getColorForValue, inputSurface, mx as mx3 } from "@dxos/react-ui-theme";
2375
+ var EditorModes = [
2376
+ "default",
2377
+ "vim"
2378
+ ];
2379
+ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, comments: comments2, readonly, editorMode, extensions = [], slots = defaultSlots }, forwardedRef) => {
2380
+ const { themeMode } = useThemeContext();
2381
+ const tabsterDOMAttribute = useFocusableGroup({
2382
+ tabBehavior: "limited"
2383
+ });
2384
+ const [root, setRoot] = useState2(null);
2385
+ const [{ state = void 0, view = void 0 } = {}, setEditor] = useState2();
2386
+ useImperativeHandle(forwardedRef, () => ({
2387
+ root,
2388
+ state,
2389
+ view
2390
+ }), [
2391
+ view,
2392
+ state,
2393
+ root
2394
+ ]);
2395
+ const { awareness, peer } = model;
2396
+ useEffect2(() => {
2397
+ if (awareness && peer) {
2398
+ awareness.setLocalStateField("user", {
2399
+ name: peer.name ?? generateName(peer.id),
2400
+ color: getColorForValue({
2401
+ value: peer.id,
2402
+ type: "color"
2403
+ }),
2404
+ colorLight: getColorForValue({
2405
+ value: peer.id,
2406
+ themeMode,
2407
+ type: "highlight"
2408
+ })
2409
+ });
2181
2410
  }
2182
- const encoderAwarenessQuery = encoding.createEncoder();
2183
- encoding.writeVarUint(encoderAwarenessQuery, messageQueryAwareness);
2184
- void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwarenessQuery));
2185
- const encoderAwarenessState = encoding.createEncoder();
2186
- encoding.writeVarUint(encoderAwarenessState, messageAwareness);
2187
- encoding.writeVarUint8Array(encoderAwarenessState, awarenessProtocol.encodeAwarenessUpdate(this.awareness, [
2188
- this._clientId
2189
- ]));
2190
- void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwarenessState));
2191
- }
2192
- get awareness() {
2193
- return this._awareness;
2194
- }
2195
- _handleAwarenessUpdate({ added, updated, removed }, origin) {
2196
- log("awareness update", {
2197
- added,
2198
- updated,
2199
- removed,
2200
- origin
2201
- }, {
2202
- F: __dxlog_file3,
2203
- L: 67,
2204
- S: this,
2205
- C: (f, a) => f(...a)
2411
+ }, [
2412
+ awareness,
2413
+ peer,
2414
+ themeMode
2415
+ ]);
2416
+ useEffect2(() => {
2417
+ if (view && comments2?.length) {
2418
+ view.dispatch({
2419
+ effects: setCommentRange.of({
2420
+ model,
2421
+ comments: comments2
2422
+ })
2423
+ });
2424
+ }
2425
+ }, [
2426
+ view,
2427
+ comments2
2428
+ ]);
2429
+ useEffect2(() => {
2430
+ if (!root) {
2431
+ return;
2432
+ }
2433
+ const state2 = EditorState2.create({
2434
+ doc: model.text(),
2435
+ extensions: [
2436
+ readonly && EditorState2.readOnly.of(readonly),
2437
+ // TODO(burdon): Factor out VIM mode? (manage via MarkdownPlugin).
2438
+ editorMode === "vim" && vim(),
2439
+ // Theme.
2440
+ // TODO(burdon): Make theme configurable.
2441
+ EditorView11.baseTheme(defaultTheme),
2442
+ EditorView11.theme(slots?.editor?.theme ?? {}),
2443
+ EditorView11.darkTheme.of(themeMode === "dark"),
2444
+ // Storage and replication.
2445
+ model.extension,
2446
+ // Custom.
2447
+ ...extensions
2448
+ ].filter(Boolean)
2206
2449
  });
2207
- const changedClients = added.concat(updated).concat(removed);
2208
- const encoderAwareness = encoding.createEncoder();
2209
- encoding.writeVarUint(encoderAwareness, messageAwareness);
2210
- encoding.writeVarUint8Array(encoderAwareness, awarenessProtocol.encodeAwarenessUpdate(this._awareness, changedClients));
2211
- void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwareness));
2212
- }
2213
- _handleSpaceMessage({ payload }) {
2214
- log("space message", payload, {
2215
- F: __dxlog_file3,
2216
- L: 79,
2217
- S: this,
2218
- C: (f, a) => f(...a)
2450
+ view?.destroy();
2451
+ setEditor({
2452
+ state: state2,
2453
+ view: new EditorView11({
2454
+ state: state2,
2455
+ parent: root
2456
+ })
2219
2457
  });
2220
- const data = new Uint8Array(Array.from(Object.values(payload)));
2221
- const encoder = this._readMessage(data);
2222
- if (encoder) {
2223
- void this._space.postMessage(this._channel, encoding.toUint8Array(encoder));
2224
- }
2225
- }
2226
- _readMessage(message) {
2227
- const decoder = decoding.createDecoder(message);
2228
- const encoder = encoding.createEncoder();
2229
- const messageType = decoding.readVarUint(decoder);
2230
- let sendReply = false;
2231
- switch (messageType) {
2232
- case messageAwareness: {
2233
- awarenessProtocol.applyAwarenessUpdate(this._awareness, decoding.readVarUint8Array(decoder), this);
2458
+ return () => {
2459
+ view?.destroy();
2460
+ setEditor(void 0);
2461
+ };
2462
+ }, [
2463
+ root,
2464
+ model,
2465
+ readonly,
2466
+ editorMode,
2467
+ themeMode
2468
+ ]);
2469
+ const handleKeyUp = useCallback((event) => {
2470
+ const { key, altKey, shiftKey, metaKey, ctrlKey } = event;
2471
+ switch (key) {
2472
+ case "Enter": {
2473
+ view?.contentDOM.focus();
2234
2474
  break;
2235
2475
  }
2236
- case messageQueryAwareness: {
2237
- encoding.writeVarUint(encoder, messageAwareness);
2238
- encoding.writeVarUint8Array(encoder, awarenessProtocol.encodeAwarenessUpdate(this._awareness, Array.from(this._awareness.getStates().keys())));
2239
- sendReply = true;
2476
+ case "Escape": {
2477
+ editorMode === "vim" && (altKey || shiftKey || metaKey || ctrlKey) && root?.focus();
2240
2478
  break;
2241
2479
  }
2242
- default: {
2243
- console.error("Invalid message:", messageType);
2244
- return encoder;
2245
- }
2246
- }
2247
- if (!sendReply) {
2248
- return null;
2249
2480
  }
2250
- return encoder;
2251
- }
2252
- _handleBeforeUnload() {
2253
- awarenessProtocol.removeAwarenessStates(this._awareness, [
2254
- this._clientId
2255
- ], "window unload");
2256
- }
2257
- };
2258
-
2259
- // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
2260
- var __dxlog_file4 = "/home/circleci/project/packages/ui/react-ui-editor/src/hooks/useTextModel.ts";
2261
- var useTextModel = ({ identity, space, text }) => {
2262
- const [model, setModel] = useState2(() => createModel({
2263
- identity,
2264
- space,
2265
- text
2266
- }));
2267
- useEffect3(() => {
2268
- setModel(createModel({
2269
- identity,
2270
- space,
2271
- text
2272
- }));
2273
2481
  }, [
2274
- identity,
2275
- space,
2276
- text
2482
+ view,
2483
+ editorMode
2277
2484
  ]);
2278
- return model;
2279
- };
2280
- var createModel = (options) => {
2281
- const { text } = options;
2282
- if (isActualAutomergeObject(text)) {
2283
- return createAutomergeModel(options);
2284
- } else {
2285
- if (!text?.doc) {
2286
- return void 0;
2287
- }
2288
- return createYjsModel(options);
2485
+ return /* @__PURE__ */ React.createElement("div", {
2486
+ key: model.id,
2487
+ ref: setRoot,
2488
+ tabIndex: 0,
2489
+ ...slots?.root,
2490
+ ...editorMode !== "vim" && tabsterDOMAttribute,
2491
+ onKeyUp: handleKeyUp
2492
+ });
2493
+ });
2494
+ var TextEditor = /* @__PURE__ */ forwardRef(({ readonly, extensions = [], slots: _slots, ...props }, forwardedRef) => {
2495
+ const { themeMode } = useThemeContext();
2496
+ const slots = defaultsDeep2({}, _slots, defaultTextSlots);
2497
+ return /* @__PURE__ */ React.createElement(BaseTextEditor, {
2498
+ ref: forwardedRef,
2499
+ readonly,
2500
+ extensions: [
2501
+ basicBundle({
2502
+ readonly,
2503
+ themeMode,
2504
+ placeholder: slots?.editor?.placeholder
2505
+ }),
2506
+ ...extensions
2507
+ ],
2508
+ slots,
2509
+ ...props
2510
+ });
2511
+ });
2512
+ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ readonly, extensions = [], slots: _slots, ...props }, forwardedRef) => {
2513
+ const { themeMode } = useThemeContext();
2514
+ const slots = defaultsDeep2({}, _slots, defaultMarkdownSlots);
2515
+ return /* @__PURE__ */ React.createElement(BaseTextEditor, {
2516
+ ref: forwardedRef,
2517
+ readonly,
2518
+ extensions: [
2519
+ markdownBundle({
2520
+ readonly,
2521
+ themeMode,
2522
+ placeholder: slots?.editor?.placeholder
2523
+ }),
2524
+ ...extensions
2525
+ ],
2526
+ slots,
2527
+ ...props
2528
+ });
2529
+ });
2530
+ var defaultSlots = {
2531
+ root: {
2532
+ className: mx3("p-2", inputSurface)
2289
2533
  }
2290
2534
  };
2291
- var createYjsModel = ({ identity, space, text }) => {
2292
- invariant3(text?.doc && text?.content, void 0, {
2293
- F: __dxlog_file4,
2294
- L: 81,
2295
- S: void 0,
2296
- A: [
2297
- "text?.doc && text?.content",
2298
- ""
2299
- ]
2300
- });
2301
- const provider = space ? new SpaceAwarenessProvider({
2302
- space,
2303
- doc: text.doc,
2304
- channel: `yjs.awareness.${text.id}`
2305
- }) : void 0;
2306
- return {
2307
- id: text.doc.guid,
2308
- content: text.content,
2309
- text: () => text.content.toString(),
2310
- ranges: [],
2311
- extension: yCollab(text.content, provider?.awareness),
2312
- awareness: provider?.awareness,
2313
- peer: identity ? {
2314
- id: identity.identityKey.toHex(),
2315
- name: identity.profile?.displayName
2316
- } : void 0
2317
- };
2535
+ var defaultTextSlots = {
2536
+ ...defaultSlots,
2537
+ editor: {
2538
+ theme: textTheme
2539
+ }
2318
2540
  };
2319
- var createAutomergeModel = ({ identity, text }) => {
2320
- const obj = text;
2321
- const doc = getRawDoc(obj, [
2322
- obj.field
2323
- ]);
2324
- return {
2325
- id: obj.id,
2326
- content: doc,
2327
- text: () => get4(doc.handle.docSync(), doc.path),
2328
- ranges: [],
2329
- extension: automergePlugin(doc.handle, doc.path),
2330
- peer: identity ? {
2331
- id: identity.identityKey.toHex(),
2332
- name: identity.profile?.displayName
2333
- } : void 0
2334
- };
2541
+ var defaultMarkdownSlots = {
2542
+ ...defaultSlots,
2543
+ editor: {
2544
+ theme: markdownTheme
2545
+ }
2335
2546
  };
2336
2547
  export {
2337
2548
  BaseTextEditor,
@@ -2355,25 +2566,25 @@ export {
2355
2566
  defaultSlots,
2356
2567
  defaultTextSlots,
2357
2568
  defaultTheme,
2358
- demo,
2359
- highlight,
2360
- highlightDecorations,
2361
- highlightStateField,
2569
+ hr,
2362
2570
  image,
2363
2571
  link,
2364
- listener2 as listener,
2572
+ listener,
2365
2573
  markdownBundle,
2366
2574
  markdownHighlightStyle,
2367
2575
  markdownTags,
2368
2576
  markdownTagsExtensions,
2369
2577
  markdownTheme,
2370
2578
  mention,
2579
+ mermaid,
2580
+ modelState,
2581
+ setCommentRange,
2371
2582
  table,
2372
2583
  tags2 as tags,
2373
2584
  tasklist,
2374
2585
  textTheme,
2375
2586
  tooltip,
2376
- useHighlights,
2587
+ typewriter,
2377
2588
  useTextModel
2378
2589
  };
2379
2590
  //# sourceMappingURL=index.mjs.map