@eccenca/gui-elements 24.1.0-rc.4 → 24.1.0-rc.5

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 (47) hide show
  1. package/CHANGELOG.md +23 -2
  2. package/dist/cjs/cmem/ActivityControl/ActivityControlWidget.js +7 -2
  3. package/dist/cjs/cmem/ActivityControl/ActivityControlWidget.js.map +1 -1
  4. package/dist/cjs/components/Icon/canonicalIconNames.js +10 -0
  5. package/dist/cjs/components/Icon/canonicalIconNames.js.map +1 -1
  6. package/dist/cjs/components/Typography/OverflowText.js +1 -1
  7. package/dist/cjs/components/Typography/OverflowText.js.map +1 -1
  8. package/dist/cjs/extensions/codemirror/CodeMirror.js +35 -7
  9. package/dist/cjs/extensions/codemirror/CodeMirror.js.map +1 -1
  10. package/dist/cjs/extensions/codemirror/toolbars/commands/markdown.command.js +278 -0
  11. package/dist/cjs/extensions/codemirror/toolbars/commands/markdown.command.js.map +1 -0
  12. package/dist/cjs/extensions/codemirror/toolbars/markdown.toolbar.js +47 -0
  13. package/dist/cjs/extensions/codemirror/toolbars/markdown.toolbar.js.map +1 -0
  14. package/dist/esm/cmem/ActivityControl/ActivityControlWidget.js +7 -2
  15. package/dist/esm/cmem/ActivityControl/ActivityControlWidget.js.map +1 -1
  16. package/dist/esm/components/Icon/canonicalIconNames.js +10 -0
  17. package/dist/esm/components/Icon/canonicalIconNames.js.map +1 -1
  18. package/dist/esm/components/Typography/OverflowText.js +1 -1
  19. package/dist/esm/components/Typography/OverflowText.js.map +1 -1
  20. package/dist/esm/extensions/codemirror/CodeMirror.js +36 -8
  21. package/dist/esm/extensions/codemirror/CodeMirror.js.map +1 -1
  22. package/dist/esm/extensions/codemirror/toolbars/commands/markdown.command.js +283 -0
  23. package/dist/esm/extensions/codemirror/toolbars/commands/markdown.command.js.map +1 -0
  24. package/dist/esm/extensions/codemirror/toolbars/markdown.toolbar.js +41 -0
  25. package/dist/esm/extensions/codemirror/toolbars/markdown.toolbar.js.map +1 -0
  26. package/dist/types/cmem/ActivityControl/ActivityControlWidget.d.ts +1 -1
  27. package/dist/types/components/Icon/canonicalIconNames.d.ts +10 -0
  28. package/dist/types/components/Typography/OverflowText.d.ts +23 -2
  29. package/dist/types/extensions/codemirror/CodeMirror.d.ts +10 -1
  30. package/dist/types/extensions/codemirror/toolbars/commands/markdown.command.d.ts +55 -0
  31. package/dist/types/extensions/codemirror/toolbars/markdown.toolbar.d.ts +12 -0
  32. package/package.json +21 -19
  33. package/src/cmem/ActivityControl/ActivityControlWidget.tsx +5 -2
  34. package/src/components/CodeAutocompleteField/CodeAutocompleteField.stories.tsx +3 -2
  35. package/src/components/ContextOverlay/ContextOverlay.stories.tsx +15 -4
  36. package/src/components/Dialog/stories/AlertDialog.stories.tsx +5 -1
  37. package/src/components/Dialog/stories/Modal.stories.tsx +4 -2
  38. package/src/components/Dialog/stories/SimpleDialog.stories.tsx +5 -2
  39. package/src/components/Icon/canonicalIconNames.tsx +10 -0
  40. package/src/components/Select/Select.stories.tsx +4 -1
  41. package/src/components/Typography/OverflowText.tsx +24 -3
  42. package/src/components/Typography/stories/OverflowText.stories.tsx +33 -0
  43. package/src/extensions/codemirror/CodeMirror.stories.tsx +5 -17
  44. package/src/extensions/codemirror/CodeMirror.tsx +65 -7
  45. package/src/extensions/codemirror/_codemirror.scss +35 -2
  46. package/src/extensions/codemirror/toolbars/commands/markdown.command.ts +340 -0
  47. package/src/extensions/codemirror/toolbars/markdown.toolbar.tsx +117 -0
@@ -19,13 +19,17 @@ export default {
19
19
  },
20
20
  } as Meta<typeof CodeEditor>;
21
21
 
22
- const TemplateFull: StoryFn<typeof CodeEditor> = (args) => <CodeEditor {...args} />;
22
+ let forcedUpdateKey = 0; // @see https://github.com/storybookjs/storybook/issues/13375#issuecomment-1291011856
23
+ const TemplateFull: StoryFn<typeof CodeEditor> = (args) => <CodeEditor {...args} key={++forcedUpdateKey} />;
23
24
 
24
25
  export const BasicExample = TemplateFull.bind({});
25
26
  BasicExample.args = {
26
27
  name: "codeinput",
27
28
  mode: "markdown",
28
29
  defaultValue: "**test me**",
30
+ useToolbar: true,
31
+ disabled: false,
32
+ readOnly: true,
29
33
  };
30
34
 
31
35
  export const LinterExample = TemplateFull.bind({});
@@ -36,19 +40,3 @@ LinterExample.args = {
36
40
  useLinting: true,
37
41
  autoFocus: true,
38
42
  };
39
-
40
- export const DisabledExample = TemplateFull.bind({});
41
- DisabledExample.args = {
42
- name: "codeinput",
43
- defaultValue: "**test me**",
44
- mode: "javascript",
45
- disabled: true,
46
- };
47
-
48
- export const IntentExample = TemplateFull.bind({});
49
- IntentExample.args = {
50
- name: "codeinput",
51
- defaultValue: "**test me**",
52
- mode: "javascript",
53
- intent: "warning",
54
- };
@@ -8,6 +8,8 @@ import { minimalSetup } from "codemirror";
8
8
  import { IntentTypes } from "../../common/Intent";
9
9
  import { markField } from "../../components/AutoSuggestion/extensions/markText";
10
10
  import { TestableComponent } from "../../components/interfaces";
11
+ import { MarkdownToolbar } from "./toolbars/markdown.toolbar";
12
+ import { Markdown } from "../../cmem/markdown/Markdown";
11
13
  import { CLASSPREFIX as eccgui } from "../../configuration/constants";
12
14
 
13
15
  //hooks
@@ -27,8 +29,8 @@ import {
27
29
  adaptedHighlightActiveLine,
28
30
  adaptedHighlightSpecialChars,
29
31
  adaptedLineNumbers,
30
- adaptedPlaceholder,
31
32
  adaptedLintGutter,
33
+ adaptedPlaceholder,
32
34
  } from "./tests/codemirrorTestHelper";
33
35
  import { ExtensionCreator } from "./types";
34
36
 
@@ -77,7 +79,6 @@ export interface CodeEditorProps extends TestableComponent {
77
79
  /**
78
80
  * Syntax mode of the code editor.
79
81
  */
80
-
81
82
  mode?: SupportedCodeEditorModes;
82
83
  /**
83
84
  * Default value used first when the editor is instanciated.
@@ -156,6 +157,15 @@ export interface CodeEditorProps extends TestableComponent {
156
157
  * Disables the editor.
157
158
  */
158
159
  disabled?: boolean;
160
+ /**
161
+ * Add toolbar for mode.
162
+ * Currently only `markdown` is supported.
163
+ */
164
+ useToolbar?: boolean;
165
+ /**
166
+ * Get the translation for a specific key
167
+ */
168
+ translate?: (key: string) => string | false;
159
169
  }
160
170
 
161
171
  const addExtensionsFor = (flag: boolean, ...extensions: Extension[]) => (flag ? [...extensions] : []);
@@ -168,6 +178,8 @@ const ModeLinterMap: ReadonlyMap<SupportedCodeEditorModes, ReadonlyArray<Extensi
168
178
  ["javascript", [jsLinter]],
169
179
  ]);
170
180
 
181
+ const ModeToolbarSupport: ReadonlyArray<SupportedCodeEditorModes> = ["markdown"];
182
+
171
183
  /**
172
184
  * Includes a code editor, currently we use CodeMirror library as base.
173
185
  */
@@ -203,9 +215,13 @@ export const CodeEditor = ({
203
215
  autoFocus = false,
204
216
  disabled = false,
205
217
  intent,
218
+ useToolbar,
219
+ translate,
206
220
  ...otherCodeEditorProps
207
221
  }: CodeEditorProps) => {
208
222
  const parent = useRef<any>(undefined);
223
+ const [view, setView] = React.useState<EditorView | undefined>();
224
+ const [showPreview, setShowPreview] = React.useState<boolean>(false);
209
225
 
210
226
  const linters = useMemo(() => {
211
227
  if (!mode) {
@@ -241,6 +257,14 @@ export const CodeEditor = ({
241
257
  }
242
258
  };
243
259
 
260
+ const getTranslation = (key: string): string | false => {
261
+ if (translate && typeof translate === "function") {
262
+ return translate(key);
263
+ }
264
+
265
+ return false;
266
+ };
267
+
244
268
  React.useEffect(() => {
245
269
  const tabIndent =
246
270
  !!(tabIntentStyle === "tab" && mode && !(tabForceSpaceForModes ?? []).includes(mode)) || enableTab;
@@ -280,8 +304,8 @@ export const CodeEditor = ({
280
304
  if (onSelection)
281
305
  onSelection(v.state.selection.ranges.filter((r) => !r.empty).map(({ from, to }) => ({ from, to })));
282
306
 
283
- if (onFocusChange) {
284
- v.view.dom.className += ` ${eccgui}-intent--${intent}`;
307
+ if (onFocusChange && intent && !v.view.dom.classList?.contains(`${eccgui}-intent--${intent}`)) {
308
+ v.view.dom.classList.add(`${eccgui}-intent--${intent}`);
285
309
  }
286
310
 
287
311
  if (onCursorChange) {
@@ -319,6 +343,7 @@ export const CodeEditor = ({
319
343
  }),
320
344
  parent: parent.current,
321
345
  });
346
+ setView(view);
322
347
 
323
348
  if (view?.dom) {
324
349
  if (height) {
@@ -346,9 +371,39 @@ export const CodeEditor = ({
346
371
  view.destroy();
347
372
  if (setEditorView) {
348
373
  setEditorView(undefined);
374
+ setView(undefined);
349
375
  }
350
376
  };
351
- }, [parent.current, mode, preventLineNumbers]);
377
+ }, [parent.current, mode, preventLineNumbers, wrapLines]);
378
+
379
+ const hasToolbarSupport = mode && ModeToolbarSupport.indexOf(mode) > -1 && useToolbar;
380
+
381
+ const editorToolbar = (mode?: SupportedCodeEditorModes): JSX.Element => {
382
+ switch (mode) {
383
+ case "markdown":
384
+ return (
385
+ <div>
386
+ <div className={`${eccgui}-codeeditor__toolbar`}>
387
+ <MarkdownToolbar
388
+ view={view}
389
+ togglePreviewStatus={() => setShowPreview((p) => !p)}
390
+ showPreview={showPreview}
391
+ translate={getTranslation}
392
+ disabled={disabled}
393
+ readonly={readOnly}
394
+ />
395
+ </div>
396
+ {showPreview && (
397
+ <div className={`${eccgui}-codeeditor__preview`}>
398
+ <Markdown>{view?.state.doc.toString() ?? ""}</Markdown>
399
+ </div>
400
+ )}
401
+ </div>
402
+ );
403
+ default:
404
+ return <></>;
405
+ }
406
+ };
352
407
 
353
408
  return (
354
409
  <div
@@ -360,10 +415,13 @@ export const CodeEditor = ({
360
415
  data-test-id={dataTestId ? dataTestId : "codemirror-wrapper"}
361
416
  className={
362
417
  `${eccgui}-codeeditor ${eccgui}-codeeditor--mode-${mode}` +
363
- (outerDivAttributes?.className ? ` ${outerDivAttributes?.className}` : "")
418
+ (outerDivAttributes?.className ? ` ${outerDivAttributes?.className}` : "") +
419
+ (hasToolbarSupport ? ` ${eccgui}-codeeditor--has-toolbar` : "")
364
420
  }
365
421
  {...otherCodeEditorProps}
366
- />
422
+ >
423
+ {hasToolbarSupport && editorToolbar(mode)}
424
+ </div>
367
425
  );
368
426
  };
369
427
 
@@ -2,6 +2,9 @@
2
2
 
3
3
  // own vars
4
4
  $eccgui-color-codeeditor-background: $eccgui-color-textfield-background !default;
5
+ $eccgui-color-codeeditor-separation: $eccgui-color-separation-divider !default;
6
+ $eccgui-size-codeeditor-height: 20rem !default;
7
+ $eccgui-size-codeeditor-toolbar-height: $button-height !default;
5
8
 
6
9
  // adjustments
7
10
  // stylelint-disable selector-class-pattern
@@ -14,9 +17,39 @@ $eccgui-color-codeeditor-background: $eccgui-color-textfield-background !default
14
17
  width: 100%;
15
18
  }
16
19
 
20
+ &__toolbar {
21
+ position: absolute;
22
+ z-index: 3;
23
+ left: 1px;
24
+ right: 1px;
25
+ top: 1px;
26
+ border-radius: $pt-border-radius $pt-border-radius 0 0;
27
+ border-bottom: solid 1px $eccgui-color-codeeditor-separation;
28
+ background-color: $eccgui-color-codeeditor-background;
29
+ }
30
+
31
+ &--has-toolbar {
32
+ .cm-scroller {
33
+ margin-top: $eccgui-size-codeeditor-toolbar-height !important;
34
+ }
35
+ }
36
+
37
+ &__preview {
38
+ position: absolute;
39
+ top: calc(#{$eccgui-size-codeeditor-toolbar-height} + 1px) !important;
40
+ left: 1px;
41
+ right: 1px;
42
+ bottom: 1px;
43
+ z-index: 2;
44
+ padding: $button-padding;
45
+ overflow-y: auto;
46
+ background-color: $eccgui-color-codeeditor-background;
47
+ border-radius: 0 0 $pt-border-radius $pt-border-radius;
48
+ }
49
+
17
50
  .cm-editor {
18
51
  width: 100%;
19
- height: 290px;
52
+ height: $eccgui-size-codeeditor-height;
20
53
  clip-path: unset !important; // we may check later why they set inset(0) now
21
54
  background-color: $eccgui-color-codeeditor-background;
22
55
  border-radius: $pt-border-radius;
@@ -27,7 +60,7 @@ $eccgui-color-codeeditor-background: $eccgui-color-textfield-background !default
27
60
  &.#{eccgui}-disabled {
28
61
  @extend .#{$ns}-input, .#{$ns}-disabled;
29
62
 
30
- height: 290px;
63
+ height: $eccgui-size-codeeditor-height;
31
64
  padding: 0;
32
65
  }
33
66
 
@@ -0,0 +1,340 @@
1
+ import { type ChangeSpec, EditorSelection } from "@codemirror/state";
2
+ import { EditorView } from "codemirror";
3
+
4
+ import { ValidIconName } from "../../../../components/Icon/canonicalIconNames";
5
+
6
+ enum Commands {
7
+ header1 = "Heading 1",
8
+ header2 = "Heading 2",
9
+ header3 = "Heading 3",
10
+ header4 = "Heading 4",
11
+ header5 = "Heading 5",
12
+ header6 = "Heading 6",
13
+ codeBlock = "Code block",
14
+ quote = "Block quote",
15
+ bold = "Bold",
16
+ italic = "Italic",
17
+ strike = "StrikeThrough",
18
+ inlineCode = "Inline code",
19
+ unorderedList = "Unordered list",
20
+ orderedList = "Ordered list",
21
+ todoList = "Todo list",
22
+ link = "Link",
23
+ image = "Image",
24
+ }
25
+
26
+ type formatConfig = { start: number; startDelimiter: string; stop?: number; endDelimiter?: string };
27
+ type headerLevels = 1 | 2 | 3 | 4 | 5 | 6;
28
+ type ListType = "ul" | "ol" | "todo";
29
+
30
+ //contains all utilities for markdown toolbar
31
+ export default class MarkdownCommand {
32
+ private view: EditorView | null = null;
33
+
34
+ //list of supported commands as well as the valid icon names.
35
+ public static commands = {
36
+ paragraphs: [
37
+ Commands.header1,
38
+ Commands.header2,
39
+ Commands.header3,
40
+ Commands.header4,
41
+ Commands.header5,
42
+ Commands.header6,
43
+ Commands.quote,
44
+ Commands.codeBlock,
45
+ ],
46
+ basic: [
47
+ { title: Commands.bold, icon: "operation-format-text-bold" },
48
+ { title: Commands.italic, icon: "operation-format-text-italic" },
49
+ { title: Commands.strike, icon: "operation-format-text-strikethrough" },
50
+ { title: Commands.inlineCode, icon: "operation-format-text-code" },
51
+ ] as { title: Commands; icon: ValidIconName }[],
52
+ lists: [
53
+ { title: Commands.unorderedList, icon: "operation-format-list-bullet", moniker: "ul" },
54
+ { title: Commands.orderedList, icon: "operation-format-list-numbered", moniker: "ol" },
55
+ { title: Commands.todoList, icon: "operation-format-list-checked", moniker: "todo" },
56
+ ] as { title: Commands; icon: ValidIconName; moniker: string }[],
57
+ attachments: [
58
+ { title: Commands.link, icon: "operation-link" },
59
+ { title: Commands.image, icon: "item-image" },
60
+ ] as { title: Commands; icon: ValidIconName }[],
61
+ } as const;
62
+
63
+ constructor(view: EditorView) {
64
+ this.view = view;
65
+ }
66
+
67
+ /**
68
+ * Supported list types are ol, ul, todo.
69
+ * utility helps to determine which at the start of the line
70
+ */
71
+ private getListTypeOfLine = (text: string): [ListType, number?] | undefined => {
72
+ if (!text) return;
73
+ text = text?.trimStart();
74
+
75
+ if (text.startsWith("- ")) {
76
+ if (text.startsWith("- [ ] ") || text.startsWith("- [x] ")) return ["todo"];
77
+ return ["ul"];
78
+ }
79
+
80
+ const v = text.match(/^(\d+)\. /);
81
+
82
+ return v ? ["ol", Number.parseInt(v[1], 10)] : undefined;
83
+ };
84
+
85
+ //inserts the list delimiters of "-", "- [ ]" and "{number}."
86
+ private createListDelimiter(text: string, type: string, orderedList: { currentIndex: number }) {
87
+ return text.replace(/^(( *)(-( \[[x ]])?|\d+\.) )?/, (...args) => {
88
+ const { space = "" } = args[args.length - 1];
89
+
90
+ let newFlag = "- ";
91
+
92
+ if (type === "ol") {
93
+ newFlag = `${orderedList.currentIndex}. `;
94
+ orderedList.currentIndex++;
95
+ } else if (type === "todo") {
96
+ newFlag = "- [ ] ";
97
+ }
98
+
99
+ return space + newFlag;
100
+ });
101
+ }
102
+
103
+ //factory for different list types.
104
+ private createList = (type: ListType) => {
105
+ if (!this.view) return;
106
+ const view = this.view;
107
+ const doc = view.state.doc;
108
+
109
+ const orderedList = { currentIndex: 1 };
110
+
111
+ view.dispatch(
112
+ view.state.changeByRange((range) => {
113
+ const text = doc.slice(range.from, range.to);
114
+ const changes: ChangeSpec[] = [];
115
+
116
+ let selectionStart: number = range.from;
117
+ let selectionLength: number = range.to - range.from;
118
+
119
+ Array.from({ length: text.lines }).forEach((_, index) => {
120
+ const line = doc.line(doc.lineAt(range.from).number + index);
121
+
122
+ const currentListType = this.getListTypeOfLine(line.text);
123
+
124
+ if (currentListType && currentListType[0] === type) {
125
+ if (currentListType[0] === "ol" && currentListType[1]) {
126
+ orderedList.currentIndex = currentListType[1];
127
+ }
128
+
129
+ return;
130
+ }
131
+ const content = this.createListDelimiter(line.text, type, orderedList);
132
+
133
+ const diffLength = content.length - line.length;
134
+
135
+ changes.push({
136
+ from: line.from,
137
+ to: line.to,
138
+ insert: content,
139
+ });
140
+
141
+ if (index === 0) {
142
+ selectionStart = selectionStart + diffLength;
143
+ } else {
144
+ selectionLength = selectionLength + diffLength;
145
+ }
146
+ });
147
+
148
+ return {
149
+ changes,
150
+ range: EditorSelection.range(selectionStart, selectionStart + selectionLength),
151
+ };
152
+ })
153
+ );
154
+
155
+ view.focus();
156
+ };
157
+
158
+ private enforceCursorFocus = (cursorPosition: number) => {
159
+ if (!this.view) return;
160
+ const view = this.view;
161
+ setTimeout(() => {
162
+ view.dispatch({
163
+ selection: EditorSelection.cursor(cursorPosition),
164
+ });
165
+ view.focus();
166
+ }, 50);
167
+ };
168
+
169
+ //supported headers from h1-h6, h6 being the smallest
170
+ private createHeading = (level: headerLevels) => {
171
+ if (!this.view) return;
172
+ const view = this.view;
173
+ const state = view.state;
174
+
175
+ const flags = "#".repeat(level) + " ";
176
+
177
+ let lastCursorPosition = 0;
178
+
179
+ view.dispatch(
180
+ state.changeByRange((range) => {
181
+ const line = state.doc.lineAt(range.from);
182
+
183
+ const content = line.text.replace(/^((#+) )?/, flags);
184
+
185
+ const diffLength = content.length - line.length;
186
+ lastCursorPosition = line.to + diffLength;
187
+ return {
188
+ changes: {
189
+ from: line.from,
190
+ to: line.to,
191
+ insert: content,
192
+ },
193
+ range: EditorSelection.range(range.anchor + diffLength, range.head + diffLength),
194
+ };
195
+ })
196
+ );
197
+
198
+ this.enforceCursorFocus(lastCursorPosition);
199
+ };
200
+
201
+ private applyFormatting = ({
202
+ start,
203
+ startDelimiter,
204
+ endDelimiter = startDelimiter,
205
+ stop = start,
206
+ }: formatConfig) => {
207
+ if (!this.view) return;
208
+ const view = this.view;
209
+ const { from, to } = view.state.selection.main;
210
+ const text = view.state.sliceDoc(from, to);
211
+ view.dispatch(
212
+ view.state.changeByRange((range) => {
213
+ return {
214
+ changes: [{ from: range.from, to: range.to, insert: `${startDelimiter}${text}${endDelimiter}` }],
215
+ range: EditorSelection.range(range.from + start, range.to + stop),
216
+ };
217
+ })
218
+ );
219
+ view.focus();
220
+ };
221
+
222
+ private applyAttachment = (type: Commands.link | Commands.image) => {
223
+ if (!this.view) return;
224
+ const view = this.view;
225
+ const { state } = view;
226
+ const isImageAttachmentType = type === Commands.image;
227
+
228
+ const { doc } = state;
229
+
230
+ view.dispatch(
231
+ state.changeByRange((range) => {
232
+ const { from, to } = range;
233
+
234
+ const text = doc.sliceString(from, to);
235
+
236
+ const link = `${isImageAttachmentType ? `!` : ""}[${text}]()`;
237
+
238
+ const cursor = from + (text.length ? 3 + text.length : 1 + Number(isImageAttachmentType));
239
+
240
+ return {
241
+ changes: [
242
+ {
243
+ from,
244
+ to,
245
+ insert: link,
246
+ },
247
+ ],
248
+ range: EditorSelection.range(cursor, cursor),
249
+ };
250
+ })
251
+ );
252
+
253
+ view.focus();
254
+ };
255
+
256
+ private applyQuoteFormatting = () => {
257
+ if (!this.view) return;
258
+ const view = this.view;
259
+ const { state } = view;
260
+ const { doc } = state;
261
+
262
+ let lastCursorPosition = 0;
263
+
264
+ view.dispatch(
265
+ view.state.changeByRange((range) => {
266
+ const startLine = doc.lineAt(range.from);
267
+
268
+ const text = doc.slice(range.from, range.to);
269
+
270
+ const lineCount = text.lines;
271
+
272
+ const changes: ChangeSpec[] = [];
273
+
274
+ let selectionStart: number = range.from;
275
+ let selectionLength: number = range.to - range.from;
276
+
277
+ new Array(lineCount).fill(0).forEach((_, index) => {
278
+ const line = doc.line(startLine.number + index);
279
+
280
+ if (line.text.startsWith("> ")) {
281
+ return;
282
+ }
283
+ changes.push({
284
+ from: line.from,
285
+ insert: "> ",
286
+ });
287
+
288
+ if (index === 0) {
289
+ selectionStart = selectionStart + 2;
290
+ } else {
291
+ selectionLength += 2;
292
+ }
293
+ });
294
+
295
+ lastCursorPosition = selectionStart + selectionLength;
296
+
297
+ return {
298
+ changes,
299
+ range: EditorSelection.range(selectionStart, selectionStart + selectionLength),
300
+ };
301
+ })
302
+ );
303
+ this.enforceCursorFocus(lastCursorPosition);
304
+ };
305
+
306
+ executeCommand = (command: Commands): true | void => {
307
+ switch (command) {
308
+ case Commands.bold:
309
+ return this.applyFormatting({ start: 2, startDelimiter: "**" });
310
+ case Commands.italic:
311
+ return this.applyFormatting({ start: 1, startDelimiter: "*" });
312
+ case Commands.codeBlock:
313
+ return this.applyFormatting({ start: 3, startDelimiter: "```\n", endDelimiter: "\n```" });
314
+ case Commands.strike:
315
+ return this.applyFormatting({ start: 2, startDelimiter: "~~" });
316
+ case Commands.inlineCode:
317
+ return this.applyFormatting({ start: 1, startDelimiter: "`" });
318
+ case Commands.header1:
319
+ case Commands.header2:
320
+ case Commands.header3:
321
+ case Commands.header4:
322
+ case Commands.header5:
323
+ case Commands.header6:
324
+ return this.createHeading(Number(command.slice(-1)) as headerLevels);
325
+ case Commands.unorderedList:
326
+ case Commands.orderedList:
327
+ case Commands.todoList:
328
+ return this.createList(
329
+ MarkdownCommand.commands.lists.find((l) => l.title === command)?.moniker as ListType
330
+ );
331
+ case Commands.image:
332
+ case Commands.link:
333
+ return this.applyAttachment(command);
334
+ case Commands.quote:
335
+ return this.applyQuoteFormatting();
336
+ default:
337
+ return; //do nothing;
338
+ }
339
+ };
340
+ }