@dxos/react-ui-editor 0.3.9-main.f8fc6b9 → 0.3.9-next.35264cb

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 (45) hide show
  1. package/dist/lib/browser/index.mjs +284 -48
  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/Editor/Editor.stories.d.ts +3 -2
  7. package/dist/types/src/components/Editor/Editor.stories.d.ts.map +1 -1
  8. package/dist/types/src/components/Markdown/Markdown.d.ts +4 -2
  9. package/dist/types/src/components/Markdown/Markdown.d.ts.map +1 -1
  10. package/dist/types/src/components/Markdown/Markdown.stories.d.ts +2 -1
  11. package/dist/types/src/components/Markdown/Markdown.stories.d.ts.map +1 -1
  12. package/dist/types/src/components/Markdown/markdownTheme.d.ts +10 -17
  13. package/dist/types/src/components/Markdown/markdownTheme.d.ts.map +1 -1
  14. package/dist/types/src/components/RichText/RichText.stories.d.ts +2 -1
  15. package/dist/types/src/components/RichText/RichText.stories.d.ts.map +1 -1
  16. package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
  17. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +1 -0
  18. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
  19. package/dist/types/src/components/TextEditor/extensions/hyperlinkDecoration.d.ts +11 -0
  20. package/dist/types/src/components/TextEditor/extensions/hyperlinkDecoration.d.ts.map +1 -0
  21. package/dist/types/src/components/TextEditor/extensions/hyperlinkTooltip.d.ts +8 -0
  22. package/dist/types/src/components/TextEditor/extensions/hyperlinkTooltip.d.ts.map +1 -0
  23. package/dist/types/src/components/TextEditor/extensions/hyperlinkWidget.d.ts +10 -0
  24. package/dist/types/src/components/TextEditor/extensions/hyperlinkWidget.d.ts.map +1 -0
  25. package/dist/types/src/components/TextEditor/extensions/index.d.ts +4 -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 +1 -0
  28. package/dist/types/src/components/TextEditor/index.d.ts.map +1 -1
  29. package/dist/types/src/components/TextEditor/theme.d.ts +1 -0
  30. package/dist/types/src/components/TextEditor/theme.d.ts.map +1 -1
  31. package/package.json +19 -16
  32. package/src/components/Editor/Editor.stories.tsx +2 -0
  33. package/src/components/Editor/Editor.tsx +4 -1
  34. package/src/components/Markdown/Markdown.stories.tsx +14 -1
  35. package/src/components/Markdown/Markdown.tsx +53 -24
  36. package/src/components/Markdown/markdownTheme.ts +32 -17
  37. package/src/components/RichText/RichText.stories.tsx +2 -0
  38. package/src/components/TextEditor/TextEditor.stories.tsx +47 -6
  39. package/src/components/TextEditor/TextEditor.tsx +1 -0
  40. package/src/components/TextEditor/extensions/hyperlinkDecoration.ts +99 -0
  41. package/src/components/TextEditor/extensions/hyperlinkTooltip.tsx +61 -0
  42. package/src/components/TextEditor/extensions/hyperlinkWidget.tsx +119 -0
  43. package/src/components/TextEditor/extensions/index.ts +7 -0
  44. package/src/components/TextEditor/index.ts +2 -0
  45. package/src/components/TextEditor/theme.ts +10 -4
@@ -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
  },
@@ -396,6 +397,13 @@ var markdownTheme = {
396
397
  "& .cm-yLineSelection": {
397
398
  margin: "0"
398
399
  },
400
+ ".cm-link": {
401
+ color: "rgb(20 89 208)",
402
+ textDecorationLine: "underline",
403
+ textDecorationThickness: "1px",
404
+ textUnderlineOffset: "2px",
405
+ borderRadius: ".125rem"
406
+ },
399
407
  ...Object.keys(get(tokens, "extend.fontSize", {})).reduce((acc, fontSize) => {
400
408
  const height = get(tokens, [
401
409
  "extend",
@@ -416,7 +424,7 @@ var markdownTheme = {
416
424
  return acc;
417
425
  }, {})
418
426
  };
419
- var markdownDarkHighlighting = HighlightStyle.define([
427
+ var markdownHighlightStyle = HighlightStyle.define([
420
428
  {
421
429
  tag: [
422
430
  tags.keyword,
@@ -541,7 +549,7 @@ var EditorModes = [
541
549
  "default",
542
550
  "vim"
543
551
  ];
544
- var MarkdownEditor = /* @__PURE__ */ forwardRef(({ model, slots = {}, editorMode, onChange }, forwardedRef) => {
552
+ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ model, slots = {}, editorMode, extensions = [], onChange }, forwardedRef) => {
545
553
  const { id, content, provider, peer } = model ?? {};
546
554
  const { themeMode } = useThemeContext();
547
555
  const tabsterDOMAttribute = useFocusableGroup({
@@ -593,49 +601,56 @@ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ model, slots = {}, editorMode
593
601
  const state2 = EditorState.create({
594
602
  doc: content?.toString(),
595
603
  extensions: [
596
- // Based on https://github.com/codemirror/dev/issues/44#issuecomment-789093799.
604
+ // Based on https://github.com/codemirror/dev/issues/44#issuecomment-789093799
597
605
  listenChangesExtension,
598
- ...editorMode === "vim" ? [
599
- vim()
600
- ] : [],
606
+ // TODO(burdon): Create custom bundle to make this class more reusable and retire simpler TextEditor component.
601
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(),
602
616
  highlightActiveLineGutter(),
617
+ highlightSelectionMatches(),
603
618
  highlightSpecialChars(),
604
619
  history(),
605
- drawSelection(),
606
- dropCursor(),
607
- EditorState.allowMultipleSelections.of(true),
608
620
  indentOnInput(),
621
+ placeholder(slots.editor?.placeholder ?? ""),
622
+ rectangularSelection(),
609
623
  syntaxHighlighting(defaultHighlightStyle, {
610
624
  fallback: true
611
625
  }),
612
- bracketMatching(),
613
- closeBrackets(),
614
- autocompletion(),
615
- rectangularSelection(),
616
- crosshairCursor(),
617
- highlightActiveLine(),
618
- highlightSelectionMatches(),
619
- placeholder(slots.editor?.placeholder ?? ""),
626
+ EditorState.allowMultipleSelections.of(true),
627
+ EditorView.lineWrapping,
620
628
  keymap.of([
621
629
  ...closeBracketsKeymap,
630
+ ...completionKeymap,
622
631
  ...defaultKeymap,
623
- ...searchKeymap,
624
- ...historyKeymap,
625
632
  ...foldKeymap,
626
- ...completionKeymap,
633
+ ...historyKeymap,
627
634
  ...lintKeymap,
635
+ ...searchKeymap,
628
636
  indentWithTab
629
637
  ]),
630
- EditorView.lineWrapping,
631
- // Themes.
638
+ // Main extension.
639
+ // https://github.com/codemirror/lang-markdown
632
640
  markdown({
633
641
  base: markdownLanguage2,
634
642
  codeLanguages: languages,
635
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.
636
650
  markdownTagsExtension
637
651
  ]
638
652
  }),
653
+ // Theme.
639
654
  EditorView.theme({
640
655
  ...markdownTheme,
641
656
  ...slots.editor?.markdownTheme
@@ -646,11 +661,19 @@ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ model, slots = {}, editorMode
646
661
  syntaxHighlighting(defaultHighlightStyle)
647
662
  ],
648
663
  // TODO(thure): All but one rule here apply to both themes; rename or refactor.
649
- syntaxHighlighting(markdownDarkHighlighting),
664
+ syntaxHighlighting(markdownHighlightStyle),
665
+ // Settings.
666
+ ...editorMode === "vim" ? [
667
+ vim()
668
+ ] : [],
669
+ // TODO(burdon): Move to extensions.
650
670
  // Replication and awareness (incl. remote selection).
671
+ // https://codemirror.net/docs/ref/#collab
651
672
  ...content instanceof YText ? [
652
673
  yCollab(content, provider?.awareness)
653
- ] : []
674
+ ] : [],
675
+ // Custom.
676
+ ...extensions
654
677
  ]
655
678
  });
656
679
  setState(state2);
@@ -847,6 +870,9 @@ var RichTextEditor = /* @__PURE__ */ forwardRef2((props, ref) => {
847
870
  // packages/ui/react-ui-editor/src/components/Editor/Editor.tsx
848
871
  var Editor = /* @__PURE__ */ memo(/* @__PURE__ */ forwardRef3(({ slots, ...params }, forwardedRef) => {
849
872
  const model = useTextModel(params);
873
+ if (!model) {
874
+ return null;
875
+ }
850
876
  if (model?.content instanceof YXmlFragment) {
851
877
  return /* @__PURE__ */ React3.createElement(RichTextEditor, {
852
878
  ref: forwardedRef,
@@ -884,12 +910,17 @@ var defaultStyles = {
884
910
  "&.cm-focused": {
885
911
  outline: "none"
886
912
  },
887
- ".cm-placeholder": {
888
- fontFamily: get2(tokens2, "fontFamily.body", []).join(",")
889
- },
890
- "& .cm-scroller": {
913
+ ".cm-scroller": {
891
914
  fontFamily: get2(tokens2, "fontFamily.body", []).join(","),
892
915
  overflow: "visible"
916
+ },
917
+ ".cm-tooltip": {
918
+ backgroundColor: "transparent",
919
+ border: "none"
920
+ },
921
+ ".cm-link": {
922
+ color: get2(tokens2, "extend.colors.primary.500"),
923
+ textDecoration: "underline"
893
924
  }
894
925
  };
895
926
 
@@ -929,6 +960,7 @@ var TextEditor = /* @__PURE__ */ forwardRef4(({ model, extensions = [], theme =
929
960
  syntaxHighlighting2(defaultHighlightStyle2)
930
961
  ],
931
962
  // Replication.
963
+ // TODO(burdon): Move to default extensions.
932
964
  ...content instanceof YText2 ? [
933
965
  yCollab2(content, void 0)
934
966
  ] : [],
@@ -976,6 +1008,205 @@ var TextEditor = /* @__PURE__ */ forwardRef4(({ model, extensions = [], theme =
976
1008
  });
977
1009
  });
978
1010
 
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;
1120
+ var linkDecorator = () => new MatchDecorator({
1121
+ regexp: markdownLinkRegexp2,
1122
+ decorate: (add, from, to, match, view) => {
1123
+ const [_, label, url] = match;
1124
+ const p0 = {
1125
+ start: from,
1126
+ end: from + 1
1127
+ };
1128
+ const p1 = {
1129
+ start: from + 1,
1130
+ end: from + 1 + label.length
1131
+ };
1132
+ const p2 = {
1133
+ start: p1.end,
1134
+ end: p1.end + 1
1135
+ };
1136
+ const p3 = {
1137
+ start: p1.end + 1,
1138
+ end: p1.end + 1 + url.length + 2
1139
+ };
1140
+ add(p0.start, p0.end, Decoration2.mark({
1141
+ class: "cm-hyperlink-bracket",
1142
+ _attributes: {
1143
+ style: "display: none"
1144
+ }
1145
+ }));
1146
+ add(p1.start, p1.end, Decoration2.mark({
1147
+ class: "cm-hyperlink-label"
1148
+ }));
1149
+ add(p2.start, p2.end, Decoration2.mark({
1150
+ class: "cm-hyperlink-bracket",
1151
+ _attributes: {
1152
+ style: "display: none"
1153
+ }
1154
+ }));
1155
+ add(p3.start, p3.end, Decoration2.mark({
1156
+ class: "cm-hyperlink-url",
1157
+ _attributes: {
1158
+ style: "display: none"
1159
+ }
1160
+ }));
1161
+ }
1162
+ });
1163
+ function hyperLinkExtension() {
1164
+ return ViewPlugin.fromClass(class HyperLinkView {
1165
+ constructor(view) {
1166
+ this.decorator = linkDecorator();
1167
+ this.decorations = this.decorator.createDeco(view);
1168
+ }
1169
+ update(update2) {
1170
+ if (update2.docChanged || update2.viewportChanged) {
1171
+ this.decorations = this.decorator.updateDeco(update2, this.decorations);
1172
+ }
1173
+ }
1174
+ }, {
1175
+ decorations: (view) => view.decorations,
1176
+ eventHandlers: {
1177
+ // TODO(burdon): CLick to expand link.
1178
+ // https://discuss.codemirror.net/t/decorator-iteration-on-click-event/4226/2
1179
+ mousedown: function(event, view) {
1180
+ const pos = view.posAtDOM(event.target);
1181
+ if (pos !== 0) {
1182
+ console.log(this);
1183
+ }
1184
+ },
1185
+ // TODO(burdon): Hide when cursor moves away.
1186
+ keydown: (event, view) => {
1187
+ view.update([]);
1188
+ }
1189
+ }
1190
+ });
1191
+ }
1192
+ var hyperlinkStyle = EditorView4.baseTheme({
1193
+ ".cm-hyperlink-label": {
1194
+ cursor: "pointer",
1195
+ textDecoration: "underline"
1196
+ },
1197
+ ".cm-hyperlink-bracket": {
1198
+ opacity: 0.2
1199
+ },
1200
+ ".cm-hyperlink-url": {
1201
+ opacity: 0.5,
1202
+ fontFamily: "monospace"
1203
+ }
1204
+ });
1205
+ var hyperlinkWidget = [
1206
+ hyperLinkExtension(),
1207
+ hyperlinkStyle
1208
+ ];
1209
+
979
1210
  // packages/ui/react-ui-editor/src/index.ts
980
1211
  import { TextKind } from "@dxos/protocols/proto/dxos/echo/model/text";
981
1212
  import { Doc, YText as YText3, YXmlFragment as YXmlFragment2 } from "@dxos/text-model";
@@ -989,7 +1220,12 @@ export {
989
1220
  TextKind,
990
1221
  YText3 as YText,
991
1222
  YXmlFragment2 as YXmlFragment,
1223
+ createHyperlinkTooltip,
992
1224
  defaultStyles,
1225
+ hyperLinkExtension,
1226
+ hyperlinkDecoration,
1227
+ hyperlinkStyle,
1228
+ hyperlinkWidget,
993
1229
  tags2 as tags,
994
1230
  useTextModel
995
1231
  };