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

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 (36) hide show
  1. package/dist/lib/browser/index.mjs +183 -102
  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/Editor/Editor.d.ts +0 -1
  5. package/dist/types/src/components/Editor/Editor.d.ts.map +1 -1
  6. package/dist/types/src/components/Markdown/Markdown.d.ts +2 -1
  7. package/dist/types/src/components/Markdown/Markdown.d.ts.map +1 -1
  8. package/dist/types/src/components/Markdown/Markdown.stories.d.ts.map +1 -1
  9. package/dist/types/src/components/Markdown/markdownTheme.d.ts +8 -1
  10. package/dist/types/src/components/Markdown/markdownTheme.d.ts.map +1 -1
  11. package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
  12. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +0 -14
  13. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
  14. package/dist/types/src/components/TextEditor/extensions/hyperlinkDecoration.d.ts +11 -0
  15. package/dist/types/src/components/TextEditor/extensions/hyperlinkDecoration.d.ts.map +1 -0
  16. package/dist/types/src/components/TextEditor/extensions/hyperlinkTooltip.d.ts +0 -1
  17. package/dist/types/src/components/TextEditor/extensions/hyperlinkTooltip.d.ts.map +1 -1
  18. package/dist/types/src/components/TextEditor/extensions/{hyperlink.d.ts → hyperlinkWidget.d.ts} +3 -3
  19. package/dist/types/src/components/TextEditor/extensions/hyperlinkWidget.d.ts.map +1 -0
  20. package/dist/types/src/components/TextEditor/extensions/index.d.ts +2 -1
  21. package/dist/types/src/components/TextEditor/extensions/index.d.ts.map +1 -1
  22. package/dist/types/src/components/TextEditor/theme.d.ts +1 -0
  23. package/dist/types/src/components/TextEditor/theme.d.ts.map +1 -1
  24. package/package.json +18 -18
  25. package/src/components/Editor/Editor.tsx +4 -1
  26. package/src/components/Markdown/Markdown.stories.tsx +12 -1
  27. package/src/components/Markdown/Markdown.tsx +47 -22
  28. package/src/components/Markdown/markdownTheme.ts +12 -1
  29. package/src/components/TextEditor/TextEditor.stories.tsx +43 -54
  30. package/src/components/TextEditor/TextEditor.tsx +1 -0
  31. package/src/components/TextEditor/extensions/hyperlinkDecoration.ts +99 -0
  32. package/src/components/TextEditor/extensions/hyperlinkTooltip.tsx +2 -17
  33. package/src/components/TextEditor/extensions/{hyperlink.tsx → hyperlinkWidget.tsx} +21 -10
  34. package/src/components/TextEditor/extensions/index.ts +2 -1
  35. package/src/components/TextEditor/theme.ts +6 -4
  36. package/dist/types/src/components/TextEditor/extensions/hyperlink.d.ts.map +0 -1
@@ -397,6 +397,13 @@ var markdownTheme = {
397
397
  "& .cm-yLineSelection": {
398
398
  margin: "0"
399
399
  },
400
+ ".cm-link": {
401
+ color: "rgb(20 89 208)",
402
+ textDecorationLine: "underline",
403
+ textDecorationThickness: "1px",
404
+ textUnderlineOffset: "2px",
405
+ borderRadius: ".125rem"
406
+ },
400
407
  ...Object.keys(get(tokens, "extend.fontSize", {})).reduce((acc, fontSize) => {
401
408
  const height = get(tokens, [
402
409
  "extend",
@@ -417,7 +424,7 @@ var markdownTheme = {
417
424
  return acc;
418
425
  }, {})
419
426
  };
420
- var markdownDarkHighlighting = HighlightStyle.define([
427
+ var markdownHighlightStyle = HighlightStyle.define([
421
428
  {
422
429
  tag: [
423
430
  tags.keyword,
@@ -594,49 +601,56 @@ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ model, slots = {}, editorMode
594
601
  const state2 = EditorState.create({
595
602
  doc: content?.toString(),
596
603
  extensions: [
597
- // Based on https://github.com/codemirror/dev/issues/44#issuecomment-789093799.
604
+ // Based on https://github.com/codemirror/dev/issues/44#issuecomment-789093799
598
605
  listenChangesExtension,
599
- ...editorMode === "vim" ? [
600
- vim()
601
- ] : [],
606
+ // TODO(burdon): Create custom bundle to make this class more reusable and retire simpler TextEditor component.
602
607
  // All of https://github.com/codemirror/basic-setup minus line numbers and fold gutter.
608
+ // https://codemirror.net/docs/ref/#codemirror.basicSetup
609
+ autocompletion(),
610
+ bracketMatching(),
611
+ closeBrackets(),
612
+ crosshairCursor(),
613
+ dropCursor(),
614
+ drawSelection(),
615
+ highlightActiveLine(),
603
616
  highlightActiveLineGutter(),
617
+ highlightSelectionMatches(),
604
618
  highlightSpecialChars(),
605
619
  history(),
606
- drawSelection(),
607
- dropCursor(),
608
- EditorState.allowMultipleSelections.of(true),
609
620
  indentOnInput(),
621
+ placeholder(slots.editor?.placeholder ?? ""),
622
+ rectangularSelection(),
610
623
  syntaxHighlighting(defaultHighlightStyle, {
611
624
  fallback: true
612
625
  }),
613
- bracketMatching(),
614
- closeBrackets(),
615
- autocompletion(),
616
- rectangularSelection(),
617
- crosshairCursor(),
618
- highlightActiveLine(),
619
- highlightSelectionMatches(),
620
- placeholder(slots.editor?.placeholder ?? ""),
626
+ EditorState.allowMultipleSelections.of(true),
627
+ EditorView.lineWrapping,
621
628
  keymap.of([
622
629
  ...closeBracketsKeymap,
630
+ ...completionKeymap,
623
631
  ...defaultKeymap,
624
- ...searchKeymap,
625
- ...historyKeymap,
626
632
  ...foldKeymap,
627
- ...completionKeymap,
633
+ ...historyKeymap,
628
634
  ...lintKeymap,
635
+ ...searchKeymap,
629
636
  indentWithTab
630
637
  ]),
631
- EditorView.lineWrapping,
632
- // Themes.
638
+ // Main extension.
639
+ // https://github.com/codemirror/lang-markdown
633
640
  markdown({
634
641
  base: markdownLanguage2,
635
642
  codeLanguages: languages,
636
643
  extensions: [
644
+ // TODO(burdon): This seems to upgrade the parser.
645
+ // GitHub flavored markdown bundle: Table, TaskList, Strikethrough, and Autolink.
646
+ // https://github.com/lezer-parser/markdown?tab=readme-ov-file#github-flavored-markdown
647
+ // https://github.github.com/gfm
648
+ // GFM,
649
+ // Custom styling.
637
650
  markdownTagsExtension
638
651
  ]
639
652
  }),
653
+ // Theme.
640
654
  EditorView.theme({
641
655
  ...markdownTheme,
642
656
  ...slots.editor?.markdownTheme
@@ -647,8 +661,14 @@ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ model, slots = {}, editorMode
647
661
  syntaxHighlighting(defaultHighlightStyle)
648
662
  ],
649
663
  // TODO(thure): All but one rule here apply to both themes; rename or refactor.
650
- syntaxHighlighting(markdownDarkHighlighting),
664
+ syntaxHighlighting(markdownHighlightStyle),
665
+ // Settings.
666
+ ...editorMode === "vim" ? [
667
+ vim()
668
+ ] : [],
669
+ // TODO(burdon): Move to extensions.
651
670
  // Replication and awareness (incl. remote selection).
671
+ // https://codemirror.net/docs/ref/#collab
652
672
  ...content instanceof YText ? [
653
673
  yCollab(content, provider?.awareness)
654
674
  ] : [],
@@ -850,6 +870,9 @@ var RichTextEditor = /* @__PURE__ */ forwardRef2((props, ref) => {
850
870
  // packages/ui/react-ui-editor/src/components/Editor/Editor.tsx
851
871
  var Editor = /* @__PURE__ */ memo(/* @__PURE__ */ forwardRef3(({ slots, ...params }, forwardedRef) => {
852
872
  const model = useTextModel(params);
873
+ if (!model) {
874
+ return null;
875
+ }
853
876
  if (model?.content instanceof YXmlFragment) {
854
877
  return /* @__PURE__ */ React3.createElement(RichTextEditor, {
855
878
  ref: forwardedRef,
@@ -887,16 +910,17 @@ var defaultStyles = {
887
910
  "&.cm-focused": {
888
911
  outline: "none"
889
912
  },
890
- ".cm-placeholder": {
891
- fontFamily: get2(tokens2, "fontFamily.body", []).join(",")
892
- },
893
- "& .cm-scroller": {
913
+ ".cm-scroller": {
894
914
  fontFamily: get2(tokens2, "fontFamily.body", []).join(","),
895
915
  overflow: "visible"
896
916
  },
897
917
  ".cm-tooltip": {
898
918
  backgroundColor: "transparent",
899
919
  border: "none"
920
+ },
921
+ ".cm-link": {
922
+ color: get2(tokens2, "extend.colors.primary.500"),
923
+ textDecoration: "underline"
900
924
  }
901
925
  };
902
926
 
@@ -936,6 +960,7 @@ var TextEditor = /* @__PURE__ */ forwardRef4(({ model, extensions = [], theme =
936
960
  syntaxHighlighting2(defaultHighlightStyle2)
937
961
  ],
938
962
  // Replication.
963
+ // TODO(burdon): Move to default extensions.
939
964
  ...content instanceof YText2 ? [
940
965
  yCollab2(content, void 0)
941
966
  ] : [],
@@ -983,11 +1008,117 @@ var TextEditor = /* @__PURE__ */ forwardRef4(({ model, extensions = [], theme =
983
1008
  });
984
1009
  });
985
1010
 
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;
1011
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/hyperlinkDecoration.ts
1012
+ import { syntaxTree } from "@codemirror/language";
1013
+ import { RangeSetBuilder, StateField as StateField2 } from "@codemirror/state";
1014
+ import { Decoration, EditorView as EditorView3, WidgetType } from "@codemirror/view";
1015
+ var Link = class extends WidgetType {
1016
+ constructor(_anchor, _text, _url) {
1017
+ super();
1018
+ this._anchor = _anchor;
1019
+ this._text = _text;
1020
+ this._url = _url;
1021
+ }
1022
+ eq(other) {
1023
+ return this._text === other.text && this._url === other.url;
1024
+ }
1025
+ toDOM(view) {
1026
+ const link = document.createElement("a");
1027
+ link.setAttribute("class", "cm-link");
1028
+ link.textContent = this._text;
1029
+ if (this._url) {
1030
+ link.setAttribute("target", "_blank");
1031
+ link.href = this._url;
1032
+ } else {
1033
+ link.onclick = () => {
1034
+ view.dispatch({
1035
+ selection: {
1036
+ anchor: this._anchor
1037
+ }
1038
+ });
1039
+ };
1040
+ }
1041
+ return link;
1042
+ }
1043
+ };
1044
+ var hyperlinkDecoration = ({ link } = {}) => {
1045
+ return StateField2.define({
1046
+ create: (state) => update(state, link),
1047
+ update: (_, tr) => update(tr.state, link),
1048
+ provide: (field) => EditorView3.decorations.from(field)
1049
+ });
1050
+ };
1051
+ var update = (state, link) => {
1052
+ const builder = new RangeSetBuilder();
1053
+ const cursor2 = state.selection.main.head;
1054
+ syntaxTree(state).iterate({
1055
+ enter: (node) => {
1056
+ if (cursor2 < node.from || cursor2 > node.to) {
1057
+ if (node.name === "Link") {
1058
+ const marks = node.node.getChildren("LinkMark");
1059
+ const text = marks.length >= 2 ? state.sliceDoc(marks[0].to, marks[1].from) : "";
1060
+ const urlNode = node.node.getChild("URL");
1061
+ const url = urlNode ? state.sliceDoc(urlNode.from, urlNode.to) : "";
1062
+ builder.add(node.from, node.to, Decoration.replace({
1063
+ widget: new Link(node.from, text, link ? url : void 0)
1064
+ }));
1065
+ return false;
1066
+ }
1067
+ }
1068
+ return true;
1069
+ }
1070
+ });
1071
+ return builder.finish();
1072
+ };
1073
+
1074
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/hyperlinkTooltip.tsx
1075
+ import { hoverTooltip } from "@codemirror/view";
1076
+ import { tooltipContent } from "@dxos/react-ui-theme";
1077
+ var markdownLinkRegexp = /\[([^\]]+)]\(([^)]+)\)/;
1078
+ var createHyperlinkTooltip = (onHover, regexp = markdownLinkRegexp) => hoverTooltip((view, pos) => {
1079
+ const { from, text } = view.state.doc.lineAt(pos);
1080
+ const p = pos - from;
1081
+ let idx = 0;
1082
+ let match;
1083
+ do {
1084
+ match = text.substring(idx).match(regexp);
1085
+ if (!match) {
1086
+ match = void 0;
1087
+ break;
1088
+ }
1089
+ idx = text.indexOf(match[0]);
1090
+ if (p >= idx && p < idx + match[0].length) {
1091
+ break;
1092
+ }
1093
+ idx += match[0].length;
1094
+ } while (true);
1095
+ if (!match) {
1096
+ return null;
1097
+ }
1098
+ const [, , url] = match ?? [];
1099
+ return {
1100
+ pos: idx + from,
1101
+ end: idx + from + match[0].length,
1102
+ above: true,
1103
+ create: () => {
1104
+ const el = document.createElement("a");
1105
+ el.innerText = "_";
1106
+ el.className = tooltipContent({}, "mb-2 p-1");
1107
+ el.setAttribute("target", "_blank");
1108
+ el.setAttribute("href", url);
1109
+ onHover(el, url);
1110
+ return {
1111
+ dom: el
1112
+ };
1113
+ }
1114
+ };
1115
+ });
1116
+
1117
+ // packages/ui/react-ui-editor/src/components/TextEditor/extensions/hyperlinkWidget.tsx
1118
+ import { Decoration as Decoration2, EditorView as EditorView4, MatchDecorator, ViewPlugin } from "@codemirror/view";
1119
+ var markdownLinkRegexp2 = /\[([^\]]+)]\(([^)]+)\)(!?)/gi;
989
1120
  var linkDecorator = () => new MatchDecorator({
990
- regexp: markdownLinkRegexp,
1121
+ regexp: markdownLinkRegexp2,
991
1122
  decorate: (add, from, to, match, view) => {
992
1123
  const [_, label, url] = match;
993
1124
  const p0 = {
@@ -1006,18 +1137,24 @@ var linkDecorator = () => new MatchDecorator({
1006
1137
  start: p1.end + 1,
1007
1138
  end: p1.end + 1 + url.length + 2
1008
1139
  };
1009
- add(p0.start, p0.end, Decoration.mark({
1010
- class: "cm-hyperlink-bracket"
1140
+ add(p0.start, p0.end, Decoration2.mark({
1141
+ class: "cm-hyperlink-bracket",
1142
+ _attributes: {
1143
+ style: "display: none"
1144
+ }
1011
1145
  }));
1012
- add(p1.start, p1.end, Decoration.mark({
1146
+ add(p1.start, p1.end, Decoration2.mark({
1013
1147
  class: "cm-hyperlink-label"
1014
1148
  }));
1015
- add(p2.start, p2.end, Decoration.mark({
1016
- class: "cm-hyperlink-bracket"
1149
+ add(p2.start, p2.end, Decoration2.mark({
1150
+ class: "cm-hyperlink-bracket",
1151
+ _attributes: {
1152
+ style: "display: none"
1153
+ }
1017
1154
  }));
1018
- add(p3.start, p3.end, Decoration.mark({
1155
+ add(p3.start, p3.end, Decoration2.mark({
1019
1156
  class: "cm-hyperlink-url",
1020
- __attributes: {
1157
+ _attributes: {
1021
1158
  style: "display: none"
1022
1159
  }
1023
1160
  }));
@@ -1029,9 +1166,9 @@ function hyperLinkExtension() {
1029
1166
  this.decorator = linkDecorator();
1030
1167
  this.decorations = this.decorator.createDeco(view);
1031
1168
  }
1032
- update(update) {
1033
- if (update.docChanged || update.viewportChanged) {
1034
- this.decorations = this.decorator.updateDeco(update, this.decorations);
1169
+ update(update2) {
1170
+ if (update2.docChanged || update2.viewportChanged) {
1171
+ this.decorations = this.decorator.updateDeco(update2, this.decorations);
1035
1172
  }
1036
1173
  }
1037
1174
  }, {
@@ -1052,7 +1189,7 @@ function hyperLinkExtension() {
1052
1189
  }
1053
1190
  });
1054
1191
  }
1055
- var hyperLinkStyle = EditorView3.baseTheme({
1192
+ var hyperlinkStyle = EditorView4.baseTheme({
1056
1193
  ".cm-hyperlink-label": {
1057
1194
  cursor: "pointer",
1058
1195
  textDecoration: "underline"
@@ -1065,67 +1202,11 @@ var hyperLinkStyle = EditorView3.baseTheme({
1065
1202
  fontFamily: "monospace"
1066
1203
  }
1067
1204
  });
1068
- var hyperlink = [
1205
+ var hyperlinkWidget = [
1069
1206
  hyperLinkExtension(),
1070
- hyperLinkStyle
1207
+ hyperlinkStyle
1071
1208
  ];
1072
1209
 
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
-
1129
1210
  // packages/ui/react-ui-editor/src/index.ts
1130
1211
  import { TextKind } from "@dxos/protocols/proto/dxos/echo/model/text";
1131
1212
  import { Doc, YText as YText3, YXmlFragment as YXmlFragment2 } from "@dxos/text-model";
@@ -1140,11 +1221,11 @@ export {
1140
1221
  YText3 as YText,
1141
1222
  YXmlFragment2 as YXmlFragment,
1142
1223
  createHyperlinkTooltip,
1143
- defaultHyperLinkTooltip,
1144
1224
  defaultStyles,
1145
1225
  hyperLinkExtension,
1146
- hyperLinkStyle,
1147
- hyperlink,
1226
+ hyperlinkDecoration,
1227
+ hyperlinkStyle,
1228
+ hyperlinkWidget,
1148
1229
  tags2 as tags,
1149
1230
  useTextModel
1150
1231
  };