@dxos/react-ui-editor 0.4.8-main.457648 → 0.4.8-main.4a229ac

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 (33) hide show
  1. package/dist/lib/browser/index.mjs +131 -44
  2. package/dist/lib/browser/index.mjs.map +3 -3
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +1 -0
  5. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
  6. package/dist/types/src/components/Toolbar/Toolbar.d.ts +11 -5
  7. package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -1
  8. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts +4 -2
  9. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts.map +1 -1
  10. package/dist/types/src/extensions/automerge/automerge.stories.d.ts +1 -0
  11. package/dist/types/src/extensions/automerge/automerge.stories.d.ts.map +1 -1
  12. package/dist/types/src/extensions/comments.d.ts.map +1 -1
  13. package/dist/types/src/extensions/markdown/formatting.d.ts +7 -4
  14. package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -1
  15. package/dist/types/src/extensions/markdown/image.d.ts +6 -0
  16. package/dist/types/src/extensions/markdown/image.d.ts.map +1 -1
  17. package/dist/types/src/hooks/useActionHandler.d.ts.map +1 -1
  18. package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -1
  19. package/dist/types/src/hooks/useTextEditor.stories.d.ts +1 -0
  20. package/dist/types/src/hooks/useTextEditor.stories.d.ts.map +1 -1
  21. package/dist/types/src/translations.d.ts +1 -0
  22. package/dist/types/src/translations.d.ts.map +1 -1
  23. package/package.json +25 -24
  24. package/src/components/TextEditor/TextEditor.tsx +2 -2
  25. package/src/components/Toolbar/Toolbar.stories.tsx +23 -24
  26. package/src/components/Toolbar/Toolbar.tsx +101 -35
  27. package/src/extensions/comments.ts +2 -2
  28. package/src/extensions/markdown/formatting.test.ts +13 -13
  29. package/src/extensions/markdown/formatting.ts +91 -83
  30. package/src/extensions/markdown/image.ts +6 -0
  31. package/src/hooks/useActionHandler.ts +5 -1
  32. package/src/hooks/useTextEditor.ts +3 -0
  33. package/src/translations.ts +1 -0
@@ -15,6 +15,7 @@ var translations_default = [
15
15
  "blockquote label": "Block quote",
16
16
  "codeblock label": "Code block",
17
17
  "comment label": "Create comment",
18
+ "image label": "Insert image",
18
19
  "heading label": "Heading level",
19
20
  "table label": "Create table"
20
21
  }
@@ -32,7 +33,7 @@ import { Doc, YText, YXmlFragment } from "@dxos/text-model";
32
33
  import { EditorState as EditorState2 } from "@codemirror/state";
33
34
  import { EditorView as EditorView17 } from "@codemirror/view";
34
35
  import { useFocusableGroup } from "@fluentui/react-tabster";
35
- import React, { forwardRef, useCallback, useEffect as useEffect2, useImperativeHandle, useRef, useState as useState2 } from "react";
36
+ import React, { forwardRef, useCallback, useEffect as useEffect2, useImperativeHandle, useRef, useState } from "react";
36
37
  import { log as log9 } from "@dxos/log";
37
38
  import { isNotFalsy as isNotFalsy4 } from "@dxos/util";
38
39
 
@@ -1831,14 +1832,14 @@ var focusComment = (view, id, center = true) => {
1831
1832
  });
1832
1833
  }
1833
1834
  };
1834
- var useComments = (view, id, comments2 = []) => {
1835
+ var useComments = (view, id, comments2) => {
1835
1836
  useEffect(() => {
1836
1837
  if (view) {
1837
1838
  if (id === view.state.facet(documentId)) {
1838
1839
  view.dispatch({
1839
1840
  effects: setComments.of({
1840
1841
  id,
1841
- comments: comments2
1842
+ comments: comments2 ?? []
1842
1843
  })
1843
1844
  });
1844
1845
  }
@@ -2796,8 +2797,9 @@ import { snippet } from "@codemirror/autocomplete";
2796
2797
  import { syntaxTree as syntaxTree2 } from "@codemirror/language";
2797
2798
  import { EditorSelection } from "@codemirror/state";
2798
2799
  import { EditorView as EditorView13, keymap as keymap6 } from "@codemirror/view";
2799
- import { useState, useMemo } from "react";
2800
- var compareFormatting = (a, b) => a.blockType === b.blockType && a.strong === b.strong && a.emphasis === b.emphasis && a.strikethrough === b.strikethrough && a.code === b.code && a.link === b.link && a.listStyle === b.listStyle && a.blockQuote === b.blockQuote;
2800
+ import { useMemo } from "react";
2801
+ import { useStateRef } from "@dxos/react-async";
2802
+ var formattingEquals = (a, b) => a.blockType === b.blockType && a.strong === b.strong && a.emphasis === b.emphasis && a.strikethrough === b.strikethrough && a.code === b.code && a.link === b.link && a.listStyle === b.listStyle && a.blockQuote === b.blockQuote;
2801
2803
  var Inline;
2802
2804
  (function(Inline2) {
2803
2805
  Inline2[Inline2["Strong"] = 0] = "Strong";
@@ -3163,7 +3165,7 @@ var removeLink = ({ state: state2, dispatch }) => {
3163
3165
  }));
3164
3166
  return true;
3165
3167
  };
3166
- var addLink = ({ state: state2, dispatch }) => {
3168
+ var addLink = ({ url, image: image2 } = {}) => ({ state: state2, dispatch }) => {
3167
3169
  const changes = state2.changeByRange((range) => {
3168
3170
  let { from, to } = range;
3169
3171
  const cutStyles = [];
@@ -3234,15 +3236,15 @@ var addLink = ({ state: state2, dispatch }) => {
3234
3236
  }
3235
3237
  changes2.push({
3236
3238
  from,
3237
- insert: "["
3239
+ insert: image2 ? "![" : "["
3238
3240
  }, {
3239
3241
  from: to,
3240
- insert: "]()"
3242
+ insert: `](${url ?? ""})`
3241
3243
  });
3242
3244
  const changeSet = state2.changes(changes2.concat(changesAfter));
3243
3245
  return {
3244
3246
  changes: changeSet,
3245
- range: EditorSelection.cursor(changeSet.mapPos(to, 1) - cursorOffset)
3247
+ range: EditorSelection.cursor(changeSet.mapPos(to, 1) - cursorOffset - (url ? url.length + 2 : 0))
3246
3248
  };
3247
3249
  });
3248
3250
  if (changes.changes.empty) {
@@ -3864,11 +3866,11 @@ var getFormatting = (state2) => {
3864
3866
  };
3865
3867
  };
3866
3868
  var useFormattingState = () => {
3867
- const [state2, setState] = useState(null);
3869
+ const [state2, setState, stateRef] = useStateRef();
3868
3870
  const observer = useMemo(() => EditorView13.updateListener.of((update2) => {
3869
3871
  if (update2.docChanged || update2.selectionSet) {
3870
3872
  const newState = getFormatting(update2.state);
3871
- if (!state2 || !compareFormatting(state2, newState)) {
3873
+ if (!stateRef.current || !formattingEquals(stateRef.current, newState)) {
3872
3874
  setState(newState);
3873
3875
  }
3874
3876
  }
@@ -3959,6 +3961,8 @@ var ImageWidget = class extends WidgetType4 {
3959
3961
  return img;
3960
3962
  }
3961
3963
  };
3964
+ var imageUpload = (options = {}) => {
3965
+ };
3962
3966
 
3963
3967
  // packages/ui/react-ui-editor/src/extensions/markdown/link.ts
3964
3968
  import { syntaxTree as syntaxTree4 } from "@codemirror/language";
@@ -4312,12 +4316,12 @@ var instanceCount = 0;
4312
4316
  var TextEditor = /* @__PURE__ */ forwardRef(({ id, doc, selection, extensions, className, autoFocus, scrollTo = EditorView17.scrollIntoView(0, {
4313
4317
  yMargin: 0
4314
4318
  }), moveToEndOfLine, debug, dataTestId }, forwardedRef) => {
4315
- const [instanceId] = useState2(() => `text-editor-${++instanceCount}`);
4319
+ const [instanceId] = useState(() => `text-editor-${++instanceCount}`);
4316
4320
  const tabsterDOMAttribute = useFocusableGroup({
4317
4321
  tabBehavior: "limited"
4318
4322
  });
4319
4323
  const rootRef = useRef(null);
4320
- const [view, setView] = useState2(null);
4324
+ const [view, setView] = useState(null);
4321
4325
  useImperativeHandle(forwardedRef, () => view, [
4322
4326
  view
4323
4327
  ]);
@@ -4330,7 +4334,7 @@ var TextEditor = /* @__PURE__ */ forwardRef(({ id, doc, selection, extensions, c
4330
4334
  autoFocus
4331
4335
  ]);
4332
4336
  useEffect2(() => {
4333
- log9("updating", {
4337
+ log9("create", {
4334
4338
  id,
4335
4339
  instanceId
4336
4340
  }, {
@@ -4390,8 +4394,16 @@ var TextEditor = /* @__PURE__ */ forwardRef(({ id, doc, selection, extensions, c
4390
4394
  }
4391
4395
  setView(view2);
4392
4396
  return () => {
4397
+ log9("destroy", {
4398
+ id,
4399
+ instanceId
4400
+ }, {
4401
+ F: __dxlog_file12,
4402
+ L: 150,
4403
+ S: void 0,
4404
+ C: (f, a) => f(...a)
4405
+ });
4393
4406
  view2?.destroy();
4394
- setView(null);
4395
4407
  };
4396
4408
  }, [
4397
4409
  doc,
@@ -4421,9 +4433,10 @@ var TextEditor = /* @__PURE__ */ forwardRef(({ id, doc, selection, extensions, c
4421
4433
  });
4422
4434
 
4423
4435
  // packages/ui/react-ui-editor/src/components/Toolbar/Toolbar.tsx
4424
- import { ChatText, Code, CodeBlock, Link, ListBullets, ListChecks, ListNumbers, Paragraph, TextStrikethrough, Table as Table2, TextB, TextHOne, TextHTwo, TextHThree, TextHFour, TextHFive, TextHSix, TextItalic, Quotes } from "@phosphor-icons/react";
4436
+ import { ChatText, Code, CodeBlock, Image, Link, ListBullets, ListChecks, ListNumbers, Paragraph, Quotes, TextStrikethrough, Table as Table2, TextB, TextHOne, TextHTwo, TextHThree, TextHFour, TextHFive, TextHSix, TextItalic } from "@phosphor-icons/react";
4425
4437
  import { createContext } from "@radix-ui/react-context";
4426
- import React2, { useRef as useRef2, useState as useState3 } from "react";
4438
+ import React2, { useEffect as useEffect3, useRef as useRef2, useState as useState2 } from "react";
4439
+ import { useDropzone } from "react-dropzone";
4427
4440
  import { DensityProvider, ElevationProvider, Select, Toolbar as NaturalToolbar, Tooltip, useTranslation } from "@dxos/react-ui";
4428
4441
  import { getSize } from "@dxos/react-ui-theme";
4429
4442
  var tooltipProps = {
@@ -4457,7 +4470,7 @@ var ToolbarRoot = ({ children, onAction, classNames, state: state2 }) => {
4457
4470
  };
4458
4471
  var buttonStyles = "min-bs-0 p-2";
4459
4472
  var iconStyles = getSize(5);
4460
- var ToolbarButton = ({ Icon, children, ...props }) => {
4473
+ var ToolbarToggleButton = ({ Icon, children, ...props }) => {
4461
4474
  return /* @__PURE__ */ React2.createElement(Tooltip.Root, null, /* @__PURE__ */ React2.createElement(Tooltip.Trigger, {
4462
4475
  asChild: true
4463
4476
  }, /* @__PURE__ */ React2.createElement(NaturalToolbar.ToggleGroupItem, {
@@ -4470,6 +4483,19 @@ var ToolbarButton = ({ Icon, children, ...props }) => {
4470
4483
  className: "sr-only"
4471
4484
  }, children))), /* @__PURE__ */ React2.createElement(Tooltip.Portal, null, /* @__PURE__ */ React2.createElement(Tooltip.Content, tooltipProps, children, /* @__PURE__ */ React2.createElement(Tooltip.Arrow, null))));
4472
4485
  };
4486
+ var ToolbarButton = ({ Icon, children, ...props }) => {
4487
+ return /* @__PURE__ */ React2.createElement(Tooltip.Root, null, /* @__PURE__ */ React2.createElement(Tooltip.Trigger, {
4488
+ asChild: true
4489
+ }, /* @__PURE__ */ React2.createElement(NaturalToolbar.Button, {
4490
+ variant: "ghost",
4491
+ ...props,
4492
+ classNames: buttonStyles
4493
+ }, /* @__PURE__ */ React2.createElement(Icon, {
4494
+ className: iconStyles
4495
+ }), /* @__PURE__ */ React2.createElement("span", {
4496
+ className: "sr-only"
4497
+ }, children))), /* @__PURE__ */ React2.createElement(Tooltip.Portal, null, /* @__PURE__ */ React2.createElement(Tooltip.Content, tooltipProps, children, /* @__PURE__ */ React2.createElement(Tooltip.Arrow, null))));
4498
+ };
4473
4499
  var ToolbarSeparator = () => /* @__PURE__ */ React2.createElement("div", {
4474
4500
  role: "separator",
4475
4501
  className: "grow"
@@ -4482,8 +4508,8 @@ var MarkdownHeading = () => {
4482
4508
  const value = header ? header[1] : blockType === "paragraph" || !blockType ? "0" : void 0;
4483
4509
  const HeadingIcon = HeadingIcons[value ?? "0"];
4484
4510
  const suppressNextTooltip = useRef2(false);
4485
- const [tooltipOpen, setTooltipOpen] = useState3(false);
4486
- const [selectOpen, setSelectOpen] = useState3(false);
4511
+ const [tooltipOpen, setTooltipOpen] = useState2(false);
4512
+ const [selectOpen, setSelectOpen] = useState2(false);
4487
4513
  return /* @__PURE__ */ React2.createElement(Tooltip.Root, {
4488
4514
  open: tooltipOpen,
4489
4515
  onOpenChange: (nextOpen) => {
@@ -4580,7 +4606,7 @@ var MarkdownStyles = () => {
4580
4606
  return /* @__PURE__ */ React2.createElement(NaturalToolbar.ToggleGroup, {
4581
4607
  type: "multiple",
4582
4608
  value: markdownStyles.filter(({ getState }) => state2 && getState(state2)).map(({ type }) => type)
4583
- }, markdownStyles.map(({ type, getState, Icon }) => /* @__PURE__ */ React2.createElement(ToolbarButton, {
4609
+ }, markdownStyles.map(({ type, getState, Icon }) => /* @__PURE__ */ React2.createElement(ToolbarToggleButton, {
4584
4610
  key: type,
4585
4611
  value: type,
4586
4612
  Icon,
@@ -4614,7 +4640,7 @@ var MarkdownLists = () => {
4614
4640
  return /* @__PURE__ */ React2.createElement(NaturalToolbar.ToggleGroup, {
4615
4641
  type: "single",
4616
4642
  value: state2?.listStyle ? `list-${state2.listStyle}` : ""
4617
- }, markdownLists.map(({ type, getState, Icon }) => /* @__PURE__ */ React2.createElement(ToolbarButton, {
4643
+ }, markdownLists.map(({ type, getState, Icon }) => /* @__PURE__ */ React2.createElement(ToolbarToggleButton, {
4618
4644
  key: type,
4619
4645
  value: type,
4620
4646
  Icon,
@@ -4649,7 +4675,7 @@ var MarkdownBlocks = () => {
4649
4675
  return /* @__PURE__ */ React2.createElement(NaturalToolbar.ToggleGroup, {
4650
4676
  type: "single",
4651
4677
  value: value?.type ?? ""
4652
- }, markdownBlocks.map(({ type, disabled, getState, Icon }) => /* @__PURE__ */ React2.createElement(ToolbarButton, {
4678
+ }, markdownBlocks.map(({ type, disabled, getState, Icon }) => /* @__PURE__ */ React2.createElement(ToolbarToggleButton, {
4653
4679
  key: type,
4654
4680
  value: type,
4655
4681
  Icon,
@@ -4661,30 +4687,68 @@ var MarkdownBlocks = () => {
4661
4687
  }, t(`${type} label`))));
4662
4688
  };
4663
4689
  var MarkdownStandard = () => /* @__PURE__ */ React2.createElement(React2.Fragment, null, /* @__PURE__ */ React2.createElement(MarkdownHeading, null), /* @__PURE__ */ React2.createElement(MarkdownStyles, null), /* @__PURE__ */ React2.createElement(MarkdownLists, null), /* @__PURE__ */ React2.createElement(MarkdownBlocks, null));
4664
- var MarkdownExtended = () => {
4690
+ var MarkdownCustom = ({ onUpload } = {}) => {
4665
4691
  const { onAction } = useToolbarContext("MarkdownStyles");
4666
4692
  const { t } = useTranslation(translationKey);
4667
- return /* @__PURE__ */ React2.createElement(Tooltip.Root, null, /* @__PURE__ */ React2.createElement(Tooltip.Trigger, {
4668
- asChild: true
4669
- }, /* @__PURE__ */ React2.createElement(NaturalToolbar.Button, {
4670
- variant: "ghost",
4693
+ const { acceptedFiles, getInputProps, open } = useDropzone({
4694
+ multiple: false,
4695
+ noDrag: true,
4696
+ accept: {
4697
+ "image/*": [
4698
+ ".jpg",
4699
+ ".jpeg",
4700
+ ".png",
4701
+ ".gif"
4702
+ ]
4703
+ }
4704
+ });
4705
+ useEffect3(() => {
4706
+ if (onUpload && acceptedFiles.length) {
4707
+ setTimeout(async () => {
4708
+ const f = acceptedFiles[0];
4709
+ const file = new File([
4710
+ f
4711
+ ], f.name, {
4712
+ type: f.type,
4713
+ lastModified: f.lastModified
4714
+ });
4715
+ const { url } = await onUpload(file);
4716
+ if (url) {
4717
+ onAction?.({
4718
+ type: "image",
4719
+ data: url
4720
+ });
4721
+ }
4722
+ });
4723
+ }
4724
+ }, [
4725
+ acceptedFiles
4726
+ ]);
4727
+ return /* @__PURE__ */ React2.createElement(React2.Fragment, null, /* @__PURE__ */ React2.createElement("input", getInputProps()), /* @__PURE__ */ React2.createElement(ToolbarButton, {
4728
+ value: "image",
4729
+ Icon: Image,
4730
+ onClick: () => open()
4731
+ }, t("image label")));
4732
+ };
4733
+ var MarkdownActions = () => {
4734
+ const { onAction } = useToolbarContext("MarkdownStyles");
4735
+ const { t } = useTranslation(translationKey);
4736
+ return /* @__PURE__ */ React2.createElement(React2.Fragment, null, /* @__PURE__ */ React2.createElement(ToolbarButton, {
4737
+ value: "comment",
4738
+ Icon: ChatText,
4671
4739
  "data-testid": "editor.toolbar.comment",
4672
4740
  onClick: () => onAction?.({
4673
4741
  type: "comment"
4674
- }),
4675
- classNames: buttonStyles
4676
- }, /* @__PURE__ */ React2.createElement(ChatText, {
4677
- className: iconStyles
4678
- }), /* @__PURE__ */ React2.createElement("span", {
4679
- className: "sr-only"
4680
- }, t("comment label")))), /* @__PURE__ */ React2.createElement(Tooltip.Portal, null, /* @__PURE__ */ React2.createElement(Tooltip.Content, tooltipProps, t("comment label"), /* @__PURE__ */ React2.createElement(Tooltip.Arrow, null))));
4742
+ })
4743
+ }, t("comment label")));
4681
4744
  };
4682
4745
  var Toolbar = {
4683
4746
  Root: ToolbarRoot,
4684
- Button: ToolbarButton,
4747
+ Button: ToolbarToggleButton,
4685
4748
  Separator: ToolbarSeparator,
4686
4749
  Markdown: MarkdownStandard,
4687
- Extended: MarkdownExtended
4750
+ Custom: MarkdownCustom,
4751
+ Actions: MarkdownActions
4688
4752
  };
4689
4753
 
4690
4754
  // packages/ui/react-ui-editor/src/hooks/useActionHandler.ts
@@ -4721,7 +4785,13 @@ var useActionHandler = (view) => {
4721
4785
  insertTable(view);
4722
4786
  break;
4723
4787
  case "link":
4724
- (action.data === false ? removeLink : addLink)(view);
4788
+ (action.data === false ? removeLink : addLink())(view);
4789
+ break;
4790
+ case "image":
4791
+ addLink({
4792
+ url: action.data,
4793
+ image: true
4794
+ })(view);
4725
4795
  break;
4726
4796
  case "comment":
4727
4797
  createComment(view);
@@ -4751,18 +4821,26 @@ var useDocAccessor = (text) => {
4751
4821
  // packages/ui/react-ui-editor/src/hooks/useTextEditor.ts
4752
4822
  import { EditorSelection as EditorSelection2, EditorState as EditorState3 } from "@codemirror/state";
4753
4823
  import { EditorView as EditorView18 } from "@codemirror/view";
4754
- import { useEffect as useEffect3, useMemo as useMemo3, useRef as useRef3, useState as useState4 } from "react";
4824
+ import { useEffect as useEffect4, useMemo as useMemo3, useRef as useRef3, useState as useState3 } from "react";
4755
4825
  import { log as log10 } from "@dxos/log";
4756
4826
  import { isNotFalsy as isNotFalsy5 } from "@dxos/util";
4757
4827
  var __dxlog_file13 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/useTextEditor.ts";
4758
4828
  var useTextEditor = (cb = () => ({}), deps = []) => {
4759
4829
  let { id, doc, selection, extensions, autoFocus, scrollTo, debug } = useMemo3(cb, deps ?? []);
4760
4830
  const onUpdate = useRef3();
4761
- const [view, setView] = useState4();
4831
+ const [view, setView] = useState3();
4762
4832
  const parentRef = useRef3(null);
4763
- useEffect3(() => {
4833
+ useEffect4(() => {
4764
4834
  let view2;
4765
4835
  if (parentRef.current) {
4836
+ log10("create", {
4837
+ id
4838
+ }, {
4839
+ F: __dxlog_file13,
4840
+ L: 36,
4841
+ S: void 0,
4842
+ C: (f, a) => f(...a)
4843
+ });
4766
4844
  const state2 = EditorState3.create({
4767
4845
  doc,
4768
4846
  selection,
@@ -4772,7 +4850,7 @@ var useTextEditor = (cb = () => ({}), deps = []) => {
4772
4850
  EditorView18.exceptionSink.of((err) => {
4773
4851
  log10.catch(err, void 0, {
4774
4852
  F: __dxlog_file13,
4775
- L: 44,
4853
+ L: 46,
4776
4854
  S: void 0,
4777
4855
  C: (f, a) => f(...a)
4778
4856
  });
@@ -4799,10 +4877,18 @@ var useTextEditor = (cb = () => ({}), deps = []) => {
4799
4877
  setView(view2);
4800
4878
  }
4801
4879
  return () => {
4880
+ log10("destroy", {
4881
+ id
4882
+ }, {
4883
+ F: __dxlog_file13,
4884
+ L: 74,
4885
+ S: void 0,
4886
+ C: (f, a) => f(...a)
4887
+ });
4802
4888
  view2?.destroy();
4803
4889
  };
4804
4890
  }, deps);
4805
- useEffect3(() => {
4891
+ useEffect4(() => {
4806
4892
  if (view) {
4807
4893
  if (!selection && !view.state.selection.main.anchor) {
4808
4894
  selection = EditorSelection2.single(view.state.doc.line(1).to);
@@ -4861,7 +4947,6 @@ export {
4861
4947
  callbackWrapper,
4862
4948
  command,
4863
4949
  comments,
4864
- compareFormatting,
4865
4950
  createBasicExtensions,
4866
4951
  createComment,
4867
4952
  createDataExtensions,
@@ -4879,10 +4964,12 @@ export {
4879
4964
  editorWithToolbarLayout,
4880
4965
  focusComment,
4881
4966
  focusEvent,
4967
+ formattingEquals,
4882
4968
  formattingKeymap,
4883
4969
  getFormatting,
4884
4970
  getToken,
4885
4971
  image,
4972
+ imageUpload,
4886
4973
  insertTable,
4887
4974
  keymap10 as keymap,
4888
4975
  linkTooltip,