@dxos/react-ui-editor 0.3.9-main.7cfe653 → 0.3.9-main.8045d90

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 (51) hide show
  1. package/dist/lib/browser/index.mjs +310 -35
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/types/src/automerge/automerge-plugin/index.d.ts +1 -1
  5. package/dist/types/src/automerge/automerge-plugin/index.d.ts.map +1 -1
  6. package/dist/types/src/automerge/automerge.stories.d.ts.map +1 -1
  7. package/dist/types/src/components/Editor/Editor.stories.d.ts +3 -2
  8. package/dist/types/src/components/Editor/Editor.stories.d.ts.map +1 -1
  9. package/dist/types/src/components/Markdown/Markdown.d.ts +2 -1
  10. package/dist/types/src/components/Markdown/Markdown.d.ts.map +1 -1
  11. package/dist/types/src/components/Markdown/Markdown.stories.d.ts +2 -1
  12. package/dist/types/src/components/Markdown/Markdown.stories.d.ts.map +1 -1
  13. package/dist/types/src/components/Markdown/markdownTheme.d.ts +2 -16
  14. package/dist/types/src/components/Markdown/markdownTheme.d.ts.map +1 -1
  15. package/dist/types/src/components/RichText/RichText.stories.d.ts +2 -1
  16. package/dist/types/src/components/RichText/RichText.stories.d.ts.map +1 -1
  17. package/dist/types/src/components/TextEditor/TextEditor.d.ts +39 -0
  18. package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -0
  19. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +32 -0
  20. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -0
  21. package/dist/types/src/components/TextEditor/extensions/hyperlink.d.ts +10 -0
  22. package/dist/types/src/components/TextEditor/extensions/hyperlink.d.ts.map +1 -0
  23. package/dist/types/src/components/TextEditor/extensions/hyperlinkTooltip.d.ts +9 -0
  24. package/dist/types/src/components/TextEditor/extensions/hyperlinkTooltip.d.ts.map +1 -0
  25. package/dist/types/src/components/TextEditor/extensions/index.d.ts +3 -0
  26. package/dist/types/src/components/TextEditor/extensions/index.d.ts.map +1 -0
  27. package/dist/types/src/components/TextEditor/index.d.ts +6 -0
  28. package/dist/types/src/components/TextEditor/index.d.ts.map +1 -0
  29. package/dist/types/src/components/TextEditor/theme.d.ts +8 -0
  30. package/dist/types/src/components/TextEditor/theme.d.ts.map +1 -0
  31. package/dist/types/src/components/index.d.ts +1 -0
  32. package/dist/types/src/components/index.d.ts.map +1 -1
  33. package/dist/types/src/testing/replicator.d.ts.map +1 -1
  34. package/package.json +21 -17
  35. package/src/automerge/automerge-plugin/index.ts +1 -1
  36. package/src/automerge/automerge.stories.tsx +1 -2
  37. package/src/components/Editor/Editor.stories.tsx +4 -2
  38. package/src/components/Editor/Editor.tsx +2 -2
  39. package/src/components/Markdown/Markdown.stories.tsx +3 -0
  40. package/src/components/Markdown/Markdown.tsx +17 -9
  41. package/src/components/Markdown/markdownTheme.ts +20 -16
  42. package/src/components/RichText/RichText.stories.tsx +2 -0
  43. package/src/components/TextEditor/TextEditor.stories.tsx +83 -0
  44. package/src/components/TextEditor/TextEditor.tsx +130 -0
  45. package/src/components/TextEditor/extensions/hyperlink.tsx +108 -0
  46. package/src/components/TextEditor/extensions/hyperlinkTooltip.tsx +76 -0
  47. package/src/components/TextEditor/extensions/index.ts +6 -0
  48. package/src/components/TextEditor/index.ts +11 -0
  49. package/src/components/TextEditor/theme.ts +33 -0
  50. package/src/components/index.ts +1 -0
  51. package/src/testing/replicator.ts +0 -1
@@ -261,10 +261,7 @@ import { tailwindConfig } from "@dxos/react-ui-theme";
261
261
  var tokens = tailwindConfig({}).theme;
262
262
 
263
263
  // packages/ui/react-ui-editor/src/components/Markdown/markdownTheme.ts
264
- var ivory = "#abb2bf";
265
264
  var malibu = "#61afef";
266
- var highlightBackground = "#2c313a";
267
- var tooltipBackground = "#353a42";
268
265
  var cursor = "#ffffff";
269
266
  var monospace = get(tokens, "fontFamily.mono", [
270
267
  "monospace"
@@ -307,24 +304,28 @@ var markdownTheme = {
307
304
  // border: 'none',
308
305
  // color: '#ddd'
309
306
  // },
310
- ".dark & .cm-tooltip": {
311
- border: "none",
312
- backgroundColor: tooltipBackground
313
- },
314
- ".dark & .cm-tooltip .cm-tooltip-arrow:before": {
315
- borderTopColor: "transparent",
316
- borderBottomColor: "transparent"
317
- },
318
- ".dark & .cm-tooltip .cm-tooltip-arrow:after": {
319
- borderTopColor: tooltipBackground,
320
- borderBottomColor: tooltipBackground
321
- },
322
- ".dark & .cm-tooltip-autocomplete": {
323
- "& > ul > li[aria-selected]": {
324
- backgroundColor: highlightBackground,
325
- color: ivory
326
- }
307
+ ".cm-tooltip": {
308
+ backgroundColor: "transparent",
309
+ border: "none"
327
310
  },
311
+ // '.dark & .cm-tooltip': {
312
+ // border: 'none',
313
+ // backgroundColor: tooltipBackground,
314
+ // },
315
+ // '.dark & .cm-tooltip .cm-tooltip-arrow:before': {
316
+ // borderTopColor: 'transparent',
317
+ // borderBottomColor: 'transparent',
318
+ // },
319
+ // '.dark & .cm-tooltip .cm-tooltip-arrow:after': {
320
+ // borderTopColor: tooltipBackground,
321
+ // borderBottomColor: tooltipBackground,
322
+ // },
323
+ // '.dark & .cm-tooltip-autocomplete': {
324
+ // '& > ul > li[aria-selected]': {
325
+ // backgroundColor: highlightBackground,
326
+ // color: ivory,
327
+ // },
328
+ // },
328
329
  "&.cm-focused": {
329
330
  outline: "none"
330
331
  },
@@ -541,7 +542,7 @@ var EditorModes = [
541
542
  "default",
542
543
  "vim"
543
544
  ];
544
- var MarkdownEditor = /* @__PURE__ */ forwardRef(({ model, slots = {}, onChange, editorMode }, forwardedRef) => {
545
+ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ model, slots = {}, editorMode, extensions = [], onChange }, forwardedRef) => {
545
546
  const { id, content, provider, peer } = model ?? {};
546
547
  const { themeMode } = useThemeContext();
547
548
  const tabsterDOMAttribute = useFocusableGroup({
@@ -628,7 +629,7 @@ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ model, slots = {}, onChange,
628
629
  indentWithTab
629
630
  ]),
630
631
  EditorView.lineWrapping,
631
- // Theme
632
+ // Themes.
632
633
  markdown({
633
634
  base: markdownLanguage2,
634
635
  codeLanguages: languages,
@@ -647,10 +648,12 @@ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ model, slots = {}, onChange,
647
648
  ],
648
649
  // TODO(thure): All but one rule here apply to both themes; rename or refactor.
649
650
  syntaxHighlighting(markdownDarkHighlighting),
650
- // Collaboration
651
+ // Replication and awareness (incl. remote selection).
651
652
  ...content instanceof YText ? [
652
653
  yCollab(content, provider?.awareness)
653
- ] : []
654
+ ] : [],
655
+ // Custom.
656
+ ...extensions
654
657
  ]
655
658
  });
656
659
  setState(state2);
@@ -671,14 +674,17 @@ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ model, slots = {}, onChange,
671
674
  themeMode,
672
675
  editorMode
673
676
  ]);
674
- const handleKeyUp = useCallback(({ key, altKey, shiftKey, metaKey, ctrlKey }) => {
677
+ const handleKeyUp = useCallback((event) => {
678
+ const { key, altKey, shiftKey, metaKey, ctrlKey } = event;
675
679
  switch (key) {
676
- case "Enter":
680
+ case "Enter": {
677
681
  view?.contentDOM.focus();
678
682
  break;
679
- case "Escape":
683
+ }
684
+ case "Escape": {
680
685
  editorMode === "vim" && (altKey || shiftKey || metaKey || ctrlKey) && parent?.focus();
681
686
  break;
687
+ }
682
688
  }
683
689
  }, [
684
690
  view,
@@ -686,11 +692,11 @@ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ model, slots = {}, onChange,
686
692
  ]);
687
693
  return /* @__PURE__ */ React.createElement("div", {
688
694
  tabIndex: 0,
695
+ ref: setParent,
689
696
  key: id,
690
697
  ...slots.root,
691
- onKeyUp: handleKeyUp,
692
698
  ...editorMode !== "vim" ? tabsterDOMAttribute : {},
693
- ref: setParent
699
+ onKeyUp: handleKeyUp
694
700
  });
695
701
  });
696
702
 
@@ -706,7 +712,7 @@ import StarterKit from "@tiptap/starter-kit";
706
712
  import React2, { forwardRef as forwardRef2, useImperativeHandle as useImperativeHandle2, useMemo as useMemo3 } from "react";
707
713
  import { generateName as generateName2 } from "@dxos/display-name";
708
714
  import { mx as mx2 } from "@dxos/react-ui-theme";
709
- var useEditor = ({ model, placeholder: placeholder2 = "Enter text\u2026", slots = {} }) => {
715
+ var useEditor = ({ model, placeholder: placeholder3 = "Enter text\u2026", slots = {} }) => {
710
716
  const extensions = useMemo3(() => [
711
717
  StarterKit.configure({
712
718
  // Extensions
@@ -811,7 +817,7 @@ var useEditor = ({ model, placeholder: placeholder2 = "Enter text\u2026", slots
811
817
  })
812
818
  ] : [],
813
819
  Placeholder.configure({
814
- placeholder: placeholder2,
820
+ placeholder: placeholder3,
815
821
  emptyEditorClass: "before:content-[attr(data-placeholder)] before:absolute opacity-50 cursor-text"
816
822
  })
817
823
  ], [
@@ -842,8 +848,8 @@ var RichTextEditor = /* @__PURE__ */ forwardRef2((props, ref) => {
842
848
  });
843
849
 
844
850
  // packages/ui/react-ui-editor/src/components/Editor/Editor.tsx
845
- var Editor = /* @__PURE__ */ memo(/* @__PURE__ */ forwardRef3(({ slots, ...options }, forwardedRef) => {
846
- const model = useTextModel(options);
851
+ var Editor = /* @__PURE__ */ memo(/* @__PURE__ */ forwardRef3(({ slots, ...params }, forwardedRef) => {
852
+ const model = useTextModel(params);
847
853
  if (model?.content instanceof YXmlFragment) {
848
854
  return /* @__PURE__ */ React3.createElement(RichTextEditor, {
849
855
  ref: forwardedRef,
@@ -859,18 +865,287 @@ var Editor = /* @__PURE__ */ memo(/* @__PURE__ */ forwardRef3(({ slots, ...optio
859
865
  }
860
866
  }));
861
867
 
868
+ // packages/ui/react-ui-editor/src/components/TextEditor/index.ts
869
+ import { tags as tags2 } from "@lezer/highlight";
870
+
871
+ // packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx
872
+ import { closeBrackets as closeBrackets2 } from "@codemirror/autocomplete";
873
+ import { bracketMatching as bracketMatching2, defaultHighlightStyle as defaultHighlightStyle2, syntaxHighlighting as syntaxHighlighting2 } from "@codemirror/language";
874
+ import { EditorState as EditorState2 } from "@codemirror/state";
875
+ import { oneDarkHighlightStyle as oneDarkHighlightStyle2 } from "@codemirror/theme-one-dark";
876
+ import { EditorView as EditorView2, placeholder as placeholder2 } from "@codemirror/view";
877
+ import React4, { forwardRef as forwardRef4, useEffect as useEffect2, useImperativeHandle as useImperativeHandle3, useState as useState2, useCallback as useCallback2 } from "react";
878
+ import { yCollab as yCollab2 } from "y-codemirror.next";
879
+ import { useThemeContext as useThemeContext2 } from "@dxos/react-ui";
880
+ import { YText as YText2 } from "@dxos/text-model";
881
+
882
+ // packages/ui/react-ui-editor/src/components/TextEditor/theme.ts
883
+ import get2 from "lodash.get";
884
+ import { tailwindConfig as tailwindConfig2 } from "@dxos/react-ui-theme";
885
+ var tokens2 = tailwindConfig2({}).theme;
886
+ var defaultStyles = {
887
+ "&.cm-focused": {
888
+ outline: "none"
889
+ },
890
+ ".cm-placeholder": {
891
+ fontFamily: get2(tokens2, "fontFamily.body", []).join(",")
892
+ },
893
+ "& .cm-scroller": {
894
+ fontFamily: get2(tokens2, "fontFamily.body", []).join(","),
895
+ overflow: "visible"
896
+ },
897
+ ".cm-tooltip": {
898
+ backgroundColor: "transparent",
899
+ border: "none"
900
+ }
901
+ };
902
+
903
+ // packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx
904
+ var TextEditor = /* @__PURE__ */ forwardRef4(({ model, extensions = [], theme = defaultStyles, slots = {}, onKeyDown, ...props }, forwardedRef) => {
905
+ const { id, content } = model ?? {};
906
+ const { themeMode } = useThemeContext2();
907
+ const [parent, setParent] = useState2(null);
908
+ const [state, setState] = useState2();
909
+ const [view, setView] = useState2();
910
+ useImperativeHandle3(forwardedRef, () => {
911
+ return {
912
+ editor: parent,
913
+ state,
914
+ view
915
+ };
916
+ }, [
917
+ view
918
+ ]);
919
+ useEffect2(() => {
920
+ if (!parent) {
921
+ return;
922
+ }
923
+ view?.destroy();
924
+ const state2 = EditorState2.create({
925
+ doc: content?.toString(),
926
+ extensions: [
927
+ bracketMatching2(),
928
+ closeBrackets2(),
929
+ placeholder2(slots.editor?.placeholder ?? ""),
930
+ EditorView2.lineWrapping,
931
+ // Themes.
932
+ EditorView2.theme(theme),
933
+ ...themeMode === "dark" ? [
934
+ syntaxHighlighting2(oneDarkHighlightStyle2)
935
+ ] : [
936
+ syntaxHighlighting2(defaultHighlightStyle2)
937
+ ],
938
+ // Replication.
939
+ ...content instanceof YText2 ? [
940
+ yCollab2(content, void 0)
941
+ ] : [],
942
+ // Custom.
943
+ ...extensions
944
+ ]
945
+ });
946
+ setState(state2);
947
+ setView(new EditorView2({
948
+ state: state2,
949
+ parent
950
+ }));
951
+ return () => {
952
+ view?.destroy();
953
+ setView(void 0);
954
+ setState(void 0);
955
+ };
956
+ }, [
957
+ parent,
958
+ content,
959
+ themeMode
960
+ ]);
961
+ const handleKeyDown = useCallback2((event) => {
962
+ if (view) {
963
+ const { head, from, to } = view.state.selection.ranges[0];
964
+ const { number } = view.state.doc.lineAt(head);
965
+ const after = view.state.sliceDoc(from);
966
+ onKeyDown?.(event, {
967
+ from,
968
+ to,
969
+ line: number,
970
+ lines: view.state.doc.lines,
971
+ after
972
+ });
973
+ }
974
+ }, [
975
+ view
976
+ ]);
977
+ return /* @__PURE__ */ React4.createElement("div", {
978
+ key: id,
979
+ ref: setParent,
980
+ ...slots.root,
981
+ onKeyDown: handleKeyDown,
982
+ ...props
983
+ });
984
+ });
985
+
986
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/hyperlink.tsx
987
+ import { Decoration, EditorView as EditorView3, MatchDecorator, ViewPlugin } from "@codemirror/view";
988
+ var markdownLinkRegexp = /\[([^\]]+)]\(([^)]+)\)(!?)/gi;
989
+ var linkDecorator = () => new MatchDecorator({
990
+ regexp: markdownLinkRegexp,
991
+ decorate: (add, from, to, match, view) => {
992
+ const [_, label, url] = match;
993
+ const p0 = {
994
+ start: from,
995
+ end: from + 1
996
+ };
997
+ const p1 = {
998
+ start: from + 1,
999
+ end: from + 1 + label.length
1000
+ };
1001
+ const p2 = {
1002
+ start: p1.end,
1003
+ end: p1.end + 1
1004
+ };
1005
+ const p3 = {
1006
+ start: p1.end + 1,
1007
+ end: p1.end + 1 + url.length + 2
1008
+ };
1009
+ add(p0.start, p0.end, Decoration.mark({
1010
+ class: "cm-hyperlink-bracket"
1011
+ }));
1012
+ add(p1.start, p1.end, Decoration.mark({
1013
+ class: "cm-hyperlink-label"
1014
+ }));
1015
+ add(p2.start, p2.end, Decoration.mark({
1016
+ class: "cm-hyperlink-bracket"
1017
+ }));
1018
+ add(p3.start, p3.end, Decoration.mark({
1019
+ class: "cm-hyperlink-url",
1020
+ __attributes: {
1021
+ style: "display: none"
1022
+ }
1023
+ }));
1024
+ }
1025
+ });
1026
+ function hyperLinkExtension() {
1027
+ return ViewPlugin.fromClass(class HyperLinkView {
1028
+ constructor(view) {
1029
+ this.decorator = linkDecorator();
1030
+ this.decorations = this.decorator.createDeco(view);
1031
+ }
1032
+ update(update) {
1033
+ if (update.docChanged || update.viewportChanged) {
1034
+ this.decorations = this.decorator.updateDeco(update, this.decorations);
1035
+ }
1036
+ }
1037
+ }, {
1038
+ decorations: (view) => view.decorations,
1039
+ eventHandlers: {
1040
+ // TODO(burdon): CLick to expand link.
1041
+ // https://discuss.codemirror.net/t/decorator-iteration-on-click-event/4226/2
1042
+ mousedown: function(event, view) {
1043
+ const pos = view.posAtDOM(event.target);
1044
+ if (pos !== 0) {
1045
+ console.log(this);
1046
+ }
1047
+ },
1048
+ // TODO(burdon): Hide when cursor moves away.
1049
+ keydown: (event, view) => {
1050
+ view.update([]);
1051
+ }
1052
+ }
1053
+ });
1054
+ }
1055
+ var hyperLinkStyle = EditorView3.baseTheme({
1056
+ ".cm-hyperlink-label": {
1057
+ cursor: "pointer",
1058
+ textDecoration: "underline"
1059
+ },
1060
+ ".cm-hyperlink-bracket": {
1061
+ opacity: 0.2
1062
+ },
1063
+ ".cm-hyperlink-url": {
1064
+ opacity: 0.5,
1065
+ fontFamily: "monospace"
1066
+ }
1067
+ });
1068
+ var hyperlink = [
1069
+ hyperLinkExtension(),
1070
+ hyperLinkStyle
1071
+ ];
1072
+
1073
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/hyperlinkTooltip.tsx
1074
+ import { hoverTooltip } from "@codemirror/view";
1075
+ import { ArrowCircleUp } from "@phosphor-icons/react";
1076
+ import React5, { StrictMode } from "react";
1077
+ import { createRoot } from "react-dom/client";
1078
+ import { getSize, mx as mx3, tooltipContent } from "@dxos/react-ui-theme";
1079
+ var markdownLinkRegexp2 = /\[([^\]]+)]\(([^)]+)\)/;
1080
+ var createHyperlinkTooltip = (onHover, regexp = markdownLinkRegexp2) => hoverTooltip((view, pos) => {
1081
+ const { from, text } = view.state.doc.lineAt(pos);
1082
+ const p = pos - from;
1083
+ let idx = 0;
1084
+ let match;
1085
+ do {
1086
+ match = text.substring(idx).match(regexp);
1087
+ if (!match) {
1088
+ match = void 0;
1089
+ break;
1090
+ }
1091
+ idx = text.indexOf(match[0]);
1092
+ if (p >= idx && p < idx + match[0].length) {
1093
+ break;
1094
+ }
1095
+ idx += match[0].length;
1096
+ } while (true);
1097
+ if (!match) {
1098
+ return null;
1099
+ }
1100
+ const [, , url] = match ?? [];
1101
+ return {
1102
+ pos: idx + from,
1103
+ end: idx + from + match[0].length,
1104
+ above: true,
1105
+ create: () => {
1106
+ const el = document.createElement("a");
1107
+ el.innerText = "_";
1108
+ el.className = tooltipContent({}, "mb-2 p-1");
1109
+ el.setAttribute("target", "_blank");
1110
+ el.setAttribute("href", url);
1111
+ onHover(el, url);
1112
+ return {
1113
+ dom: el
1114
+ };
1115
+ }
1116
+ };
1117
+ });
1118
+ var defaultHyperLinkTooltip = createHyperlinkTooltip((el, url) => {
1119
+ const web = new URL(url);
1120
+ createRoot(el).render(/* @__PURE__ */ React5.createElement(StrictMode, null, /* @__PURE__ */ React5.createElement("div", {
1121
+ className: "flex gap-1 items-center"
1122
+ }, /* @__PURE__ */ React5.createElement(ArrowCircleUp, {
1123
+ className: mx3(getSize(6), "text-blue-500")
1124
+ }), /* @__PURE__ */ React5.createElement("p", {
1125
+ className: "pr-1"
1126
+ }, web.origin))));
1127
+ });
1128
+
862
1129
  // packages/ui/react-ui-editor/src/index.ts
863
1130
  import { TextKind } from "@dxos/protocols/proto/dxos/echo/model/text";
864
- import { Doc, YText as YText2, YXmlFragment as YXmlFragment2 } from "@dxos/text-model";
1131
+ import { Doc, YText as YText3, YXmlFragment as YXmlFragment2 } from "@dxos/text-model";
865
1132
  export {
866
1133
  Doc,
867
1134
  Editor,
868
1135
  EditorModes,
869
1136
  MarkdownEditor,
870
1137
  RichTextEditor,
1138
+ TextEditor,
871
1139
  TextKind,
872
- YText2 as YText,
1140
+ YText3 as YText,
873
1141
  YXmlFragment2 as YXmlFragment,
1142
+ createHyperlinkTooltip,
1143
+ defaultHyperLinkTooltip,
1144
+ defaultStyles,
1145
+ hyperLinkExtension,
1146
+ hyperLinkStyle,
1147
+ hyperlink,
1148
+ tags2 as tags,
874
1149
  useTextModel
875
1150
  };
876
1151
  //# sourceMappingURL=index.mjs.map