@uiw/react-md-editor 3.23.6 → 3.24.1

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 (105) hide show
  1. package/README.md +29 -4
  2. package/dist/mdeditor.css +2 -0
  3. package/dist/mdeditor.js +234 -237
  4. package/dist/mdeditor.min.css +1 -1
  5. package/dist/mdeditor.min.js +1 -1
  6. package/dist/mdeditor.min.js.LICENSE.txt +1 -1
  7. package/esm/commands/bold.js +9 -13
  8. package/esm/commands/code.d.ts +1 -1
  9. package/esm/commands/code.js +75 -68
  10. package/esm/commands/comment.js +20 -18
  11. package/esm/commands/help.d.ts +2 -0
  12. package/esm/commands/help.js +22 -0
  13. package/esm/commands/hr.js +33 -2
  14. package/esm/commands/image.js +39 -16
  15. package/esm/commands/index.d.ts +7 -2
  16. package/esm/commands/index.js +5 -2
  17. package/esm/commands/issue.d.ts +2 -0
  18. package/esm/commands/issue.js +41 -0
  19. package/esm/commands/italic.js +9 -13
  20. package/esm/commands/link.js +40 -14
  21. package/esm/commands/list.d.ts +3 -10
  22. package/esm/commands/list.js +39 -41
  23. package/esm/commands/quote.js +8 -8
  24. package/esm/commands/strikeThrough.js +9 -13
  25. package/esm/commands/table.d.ts +2 -0
  26. package/esm/commands/table.js +57 -0
  27. package/esm/commands/title.d.ts +7 -1
  28. package/esm/commands/title.js +21 -0
  29. package/esm/commands/title1.js +9 -7
  30. package/esm/commands/title2.js +9 -7
  31. package/esm/commands/title3.js +9 -7
  32. package/esm/commands/title4.js +9 -7
  33. package/esm/commands/title5.js +9 -7
  34. package/esm/commands/title6.js +9 -7
  35. package/esm/components/TextArea/handleKeyDown.js +83 -5
  36. package/esm/utils/InsertTextAtPosition.d.ts +0 -7
  37. package/esm/utils/InsertTextAtPosition.js +6 -24
  38. package/esm/utils/markdownUtils.d.ts +23 -1
  39. package/esm/utils/markdownUtils.js +83 -4
  40. package/lib/Context.js +2 -3
  41. package/lib/Editor.js +1 -2
  42. package/lib/commands/bold.js +10 -15
  43. package/lib/commands/code.d.ts +1 -1
  44. package/lib/commands/code.js +75 -70
  45. package/lib/commands/comment.js +21 -20
  46. package/lib/commands/divider.js +2 -3
  47. package/lib/commands/fullscreen.js +2 -3
  48. package/lib/commands/group.js +2 -3
  49. package/lib/commands/help.d.ts +2 -0
  50. package/lib/commands/help.js +29 -0
  51. package/lib/commands/hr.js +35 -5
  52. package/lib/commands/image.js +40 -18
  53. package/lib/commands/index.d.ts +7 -2
  54. package/lib/commands/index.js +27 -10
  55. package/lib/commands/issue.d.ts +2 -0
  56. package/lib/commands/issue.js +48 -0
  57. package/lib/commands/italic.js +10 -15
  58. package/lib/commands/link.js +41 -16
  59. package/lib/commands/list.d.ts +3 -10
  60. package/lib/commands/list.js +43 -52
  61. package/lib/commands/preview.js +4 -7
  62. package/lib/commands/quote.js +9 -10
  63. package/lib/commands/strikeThrough.js +10 -15
  64. package/lib/commands/table.d.ts +2 -0
  65. package/lib/commands/table.js +64 -0
  66. package/lib/commands/title.d.ts +7 -1
  67. package/lib/commands/title.js +23 -3
  68. package/lib/commands/title1.js +11 -10
  69. package/lib/commands/title2.js +11 -10
  70. package/lib/commands/title3.js +11 -10
  71. package/lib/commands/title4.js +11 -10
  72. package/lib/commands/title5.js +11 -10
  73. package/lib/commands/title6.js +11 -10
  74. package/lib/components/DragBar/index.js +1 -2
  75. package/lib/components/TextArea/handleKeyDown.js +84 -6
  76. package/lib/index.js +1 -2
  77. package/lib/utils/InsertTextAtPosition.d.ts +0 -7
  78. package/lib/utils/InsertTextAtPosition.js +6 -26
  79. package/lib/utils/markdownUtils.d.ts +23 -1
  80. package/lib/utils/markdownUtils.js +84 -4
  81. package/package.json +4 -1
  82. package/src/commands/bold.tsx +13 -12
  83. package/src/commands/code.tsx +72 -71
  84. package/src/commands/comment.tsx +20 -15
  85. package/src/commands/help.tsx +19 -0
  86. package/src/commands/hr.tsx +33 -2
  87. package/src/commands/image.tsx +38 -15
  88. package/src/commands/index.ts +12 -1
  89. package/src/commands/issue.tsx +36 -0
  90. package/src/commands/italic.tsx +13 -12
  91. package/src/commands/link.tsx +39 -12
  92. package/src/commands/list.tsx +35 -53
  93. package/src/commands/quote.tsx +14 -13
  94. package/src/commands/strikeThrough.tsx +13 -12
  95. package/src/commands/table.tsx +52 -0
  96. package/src/commands/title.tsx +18 -1
  97. package/src/commands/title1.tsx +6 -9
  98. package/src/commands/title2.tsx +6 -9
  99. package/src/commands/title3.tsx +6 -9
  100. package/src/commands/title4.tsx +6 -9
  101. package/src/commands/title5.tsx +6 -9
  102. package/src/commands/title6.tsx +6 -9
  103. package/src/components/TextArea/handleKeyDown.tsx +54 -5
  104. package/src/utils/InsertTextAtPosition.ts +7 -28
  105. package/src/utils/markdownUtils.ts +94 -4
@@ -22,6 +22,9 @@ import { title3 } from './title3';
22
22
  import { title4 } from './title4';
23
23
  import { title5 } from './title5';
24
24
  import { title6 } from './title6';
25
+ import { table } from './table';
26
+ import { issue } from './issue';
27
+ import { help } from './help';
25
28
 
26
29
  export interface CommandOrchestrator {
27
30
  executeCommand(command: ICommand): void;
@@ -48,7 +51,9 @@ export interface ICommandBase<T> {
48
51
  shortcuts?: string;
49
52
  groupName?: string;
50
53
  icon?: React.ReactElement;
51
- value?: T;
54
+ value?: string;
55
+ prefix?: string;
56
+ suffix?: string;
52
57
  position?: 'right';
53
58
  liProps?: React.LiHTMLAttributes<HTMLLIElement>;
54
59
  buttonProps?: React.ButtonHTMLAttributes<HTMLButtonElement> | null;
@@ -99,10 +104,13 @@ const getCommands: () => ICommand[] = () => [
99
104
  codeBlock,
100
105
  comment,
101
106
  image,
107
+ table,
102
108
  divider,
103
109
  unorderedListCommand,
104
110
  orderedListCommand,
105
111
  checkedListCommand,
112
+ divider,
113
+ help,
106
114
  ];
107
115
 
108
116
  const getExtraCommands: () => ICommand[] = () => [codeEdit, codeLive, codePreview, divider, fullscreen];
@@ -195,6 +203,9 @@ export {
195
203
  unorderedListCommand,
196
204
  orderedListCommand,
197
205
  checkedListCommand,
206
+ table,
207
+ issue,
208
+ help,
198
209
  codeEdit,
199
210
  codeLive,
200
211
  codePreview,
@@ -0,0 +1,36 @@
1
+ import React from 'react';
2
+ import { ICommand, ExecuteState, TextAreaTextApi } from './';
3
+ import { selectWord, executeCommand } from '../utils/markdownUtils';
4
+
5
+ export const issue: ICommand = {
6
+ name: 'issue',
7
+ keyCommand: 'issue',
8
+ prefix: '#',
9
+ suffix: '',
10
+ buttonProps: { 'aria-label': 'Add issue', title: 'Add issue' },
11
+ icon: (
12
+ <svg role="img" width="12" height="12" viewBox="0 0 448 512">
13
+ <path
14
+ fill="currentColor"
15
+ d="M181.3 32.4c17.4 2.9 29.2 19.4 26.3 36.8L197.8 128h95.1l11.5-69.3c2.9-17.4 19.4-29.2 36.8-26.3s29.2 19.4 26.3 36.8L357.8 128H416c17.7 0 32 14.3 32 32s-14.3 32-32 32H347.1L325.8 320H384c17.7 0 32 14.3 32 32s-14.3 32-32 32H315.1l-11.5 69.3c-2.9 17.4-19.4 29.2-36.826.3s-29.2-19.4-26.3-36.8l9.8-58.7H155.1l-11.5 69.3c-2.9 17.4-19.4 29.2-36.8 26.3s-29.2-19.4-26.3-36.8L90.2 384H32c-17.7 0-32-14.3-32-32s14.3-32 32-32h68.9l21.3-128H64c-17.7 0-32-14.3-32-32s14.3-32 32-32h68.9l11.5-69.3c2.9-17.4 19.4-29.2 36.8-26.3zM187.1 192L165.8320h95.1l21.3-128H187.1z"
16
+ //Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com
17
+ />
18
+ </svg>
19
+ ),
20
+ execute: (state: ExecuteState, api: TextAreaTextApi) => {
21
+ const newSelectionRange = selectWord({
22
+ text: state.text,
23
+ selection: state.selection,
24
+ prefix: state.command.prefix!,
25
+ suffix: state.command.suffix,
26
+ });
27
+ const state1 = api.setSelectionRange(newSelectionRange);
28
+ executeCommand({
29
+ api,
30
+ selectedText: state1.selectedText,
31
+ selection: state.selection,
32
+ prefix: state.command.prefix!,
33
+ suffix: state.command.suffix,
34
+ });
35
+ },
36
+ };
@@ -1,12 +1,12 @@
1
1
  import React from 'react';
2
2
  import { ICommand, ExecuteState, TextAreaTextApi } from './';
3
- import { selectWord } from '../utils/markdownUtils';
3
+ import { selectWord, executeCommand } from '../utils/markdownUtils';
4
4
 
5
5
  export const italic: ICommand = {
6
6
  name: 'italic',
7
7
  keyCommand: 'italic',
8
8
  shortcuts: 'ctrlcmd+i',
9
- value: '*{{text}}*',
9
+ prefix: '*',
10
10
  buttonProps: { 'aria-label': 'Add italic text (ctrl + i)', title: 'Add italic text (ctrl + i)' },
11
11
  icon: (
12
12
  <svg data-name="italic" width="12" height="12" role="img" viewBox="0 0 320 512">
@@ -17,16 +17,17 @@ export const italic: ICommand = {
17
17
  </svg>
18
18
  ),
19
19
  execute: (state: ExecuteState, api: TextAreaTextApi) => {
20
- // Adjust the selection to encompass the whole word if the caret is inside one
21
- const newSelectionRange = selectWord({ text: state.text, selection: state.selection });
20
+ const newSelectionRange = selectWord({
21
+ text: state.text,
22
+ selection: state.selection,
23
+ prefix: state.command.prefix!,
24
+ });
22
25
  const state1 = api.setSelectionRange(newSelectionRange);
23
- // Replaces the current selection with the bold mark up
24
- const val = state.command.value || '';
25
- api.replaceSelection(val.replace(/({{text}})/gi, state1.selectedText));
26
-
27
- const start = state1.selection.start + val.indexOf('{{text}}');
28
- const end = state1.selection.start + val.indexOf('{{text}}') + (state1.selection.end - state1.selection.start);
29
- // Adjust the selection to not contain the **
30
- api.setSelectionRange({ start, end });
26
+ executeCommand({
27
+ api,
28
+ selectedText: state1.selectedText,
29
+ selection: state.selection,
30
+ prefix: state.command.prefix!,
31
+ });
31
32
  },
32
33
  };
@@ -1,12 +1,13 @@
1
1
  import React from 'react';
2
2
  import { ICommand, ExecuteState, TextAreaTextApi } from './';
3
- import { selectWord } from '../utils/markdownUtils';
3
+ import { selectWord, executeCommand } from '../utils/markdownUtils';
4
4
 
5
5
  export const link: ICommand = {
6
6
  name: 'link',
7
7
  keyCommand: 'link',
8
8
  shortcuts: 'ctrlcmd+l',
9
- value: '[{{text}}](URL Here)',
9
+ prefix: '[',
10
+ suffix: '](url)',
10
11
  buttonProps: { 'aria-label': 'Add a link (ctrl + l)', title: 'Add a link (ctrl + l)' },
11
12
  icon: (
12
13
  <svg data-name="italic" width="12" height="12" role="img" viewBox="0 0 520 520">
@@ -17,15 +18,41 @@ export const link: ICommand = {
17
18
  </svg>
18
19
  ),
19
20
  execute: (state: ExecuteState, api: TextAreaTextApi) => {
20
- // Adjust the selection to encompass the whole word if the caret is inside one
21
- const newSelectionRange = selectWord({ text: state.text, selection: state.selection });
22
- const state1 = api.setSelectionRange(newSelectionRange);
23
- const val = state.command.value || '';
24
- // Replaces the current selection with the bold mark up
25
- api.replaceSelection(val.replace(/({{text}})/gi, state1.selectedText));
26
- const start = state1.selection.start + val.indexOf('{{text}}');
27
- const end = state1.selection.start + val.indexOf('{{text}}') + (state1.selection.end - state1.selection.start);
28
- // Adjust the selection to not contain the **
29
- api.setSelectionRange({ start, end });
21
+ let newSelectionRange = selectWord({
22
+ text: state.text,
23
+ selection: state.selection,
24
+ prefix: state.command.prefix!,
25
+ suffix: state.command.suffix,
26
+ });
27
+ let state1 = api.setSelectionRange(newSelectionRange);
28
+ if (state1.selectedText.includes('http') || state1.selectedText.includes('www')) {
29
+ newSelectionRange = selectWord({ text: state.text, selection: state.selection, prefix: '[](', suffix: ')' });
30
+ state1 = api.setSelectionRange(newSelectionRange);
31
+ executeCommand({
32
+ api,
33
+ selectedText: state1.selectedText,
34
+ selection: state.selection,
35
+ prefix: '[](',
36
+ suffix: ')',
37
+ });
38
+ } else {
39
+ if (state1.selectedText.length === 0) {
40
+ executeCommand({
41
+ api,
42
+ selectedText: state1.selectedText,
43
+ selection: state.selection,
44
+ prefix: '[title',
45
+ suffix: '](url)',
46
+ });
47
+ } else {
48
+ executeCommand({
49
+ api,
50
+ selectedText: state1.selectedText,
51
+ selection: state.selection,
52
+ prefix: state.command.prefix!,
53
+ suffix: state.command.suffix,
54
+ });
55
+ }
56
+ }
30
57
  },
31
58
  };
@@ -1,43 +1,15 @@
1
1
  import React from 'react';
2
- import { ICommand, TextState, TextAreaTextApi } from './';
2
+ import { ICommand, ExecuteState, TextAreaTextApi } from './';
3
3
  import {
4
4
  selectWord,
5
5
  getBreaksNeededForEmptyLineBefore,
6
6
  getBreaksNeededForEmptyLineAfter,
7
+ insertBeforeEachLine,
8
+ AlterLineFunction,
7
9
  } from '../utils/markdownUtils';
8
10
 
9
- export type AlterLineFunction = (line: string, index: number) => string;
10
-
11
- /**
12
- * Inserts insertionString before each line
13
- */
14
- export function insertBeforeEachLine(
15
- selectedText: string,
16
- insertBefore: string | AlterLineFunction,
17
- ): { modifiedText: string; insertionLength: number } {
18
- const lines = selectedText.split(/\n/);
19
-
20
- let insertionLength = 0;
21
- const modifiedText = lines
22
- .map((item, index) => {
23
- if (typeof insertBefore === 'string') {
24
- insertionLength += insertBefore.length;
25
- return insertBefore + item;
26
- } else if (typeof insertBefore === 'function') {
27
- const insertionResult = insertBefore(item, index);
28
- insertionLength += insertionResult.length;
29
- return insertBefore(item, index) + item;
30
- }
31
- throw Error('insertion is expected to be either a string or a function');
32
- })
33
- .join('\n');
34
-
35
- return { modifiedText, insertionLength };
36
- }
37
-
38
- export const makeList = (state: TextState, api: TextAreaTextApi, insertBefore: string | AlterLineFunction) => {
39
- // Adjust the selection to encompass the whole word if the caret is inside one
40
- const newSelectionRange = selectWord({ text: state.text, selection: state.selection });
11
+ export const makeList = (state: ExecuteState, api: TextAreaTextApi, insertBefore: string | AlterLineFunction) => {
12
+ const newSelectionRange = selectWord({ text: state.text, selection: state.selection, prefix: state.command.prefix! });
41
13
  const state1 = api.setSelectionRange(newSelectionRange);
42
14
 
43
15
  const breaksBeforeCount = getBreaksNeededForEmptyLineBefore(state1.text, state1.selection.start);
@@ -46,28 +18,38 @@ export const makeList = (state: TextState, api: TextAreaTextApi, insertBefore: s
46
18
  const breaksAfterCount = getBreaksNeededForEmptyLineAfter(state1.text, state1.selection.end);
47
19
  const breaksAfter = Array(breaksAfterCount + 1).join('\n');
48
20
 
49
- const modifiedText = insertBeforeEachLine(state1.selectedText, insertBefore);
50
-
51
- api.replaceSelection(`${breaksBefore}${modifiedText.modifiedText}${breaksAfter}`);
52
-
53
- // Specifically when the text has only one line, we can exclude the "- ", for example, from the selection
54
- const oneLinerOffset = state1.selectedText.indexOf('\n') === -1 ? modifiedText.insertionLength : 0;
55
-
56
- const selectionStart = state1.selection.start + breaksBeforeCount + oneLinerOffset;
57
- const selectionEnd = selectionStart + modifiedText.modifiedText.length - oneLinerOffset;
21
+ const { modifiedText, insertionLength } = insertBeforeEachLine(state1.selectedText, insertBefore);
22
+ if (insertionLength < 0) {
23
+ // Remove
24
+ let selectionStart = state1.selection.start;
25
+ let selectionEnd = state1.selection.end;
26
+ if (state1.selection.start > 0 && state.text.slice(state1.selection.start - 1, state1.selection.start) === '\n') {
27
+ selectionStart -= 1;
28
+ }
29
+ if (
30
+ state1.selection.end < state.text.length - 1 &&
31
+ state.text.slice(state1.selection.end, state1.selection.end + 1) === '\n'
32
+ ) {
33
+ selectionEnd += 1;
34
+ }
58
35
 
59
- // Adjust the selection to not contain the **
60
- api.setSelectionRange({
61
- start: selectionStart,
62
- end: selectionEnd,
63
- });
36
+ api.setSelectionRange({ start: selectionStart, end: selectionEnd });
37
+ api.replaceSelection(`${modifiedText}`);
38
+ api.setSelectionRange({ start: selectionStart, end: selectionStart + modifiedText.length });
39
+ } else {
40
+ // Add
41
+ api.replaceSelection(`${breaksBefore}${modifiedText}${breaksAfter}`);
42
+ const selectionStart = state1.selection.start + breaksBeforeCount;
43
+ const selectionEnd = selectionStart + modifiedText.length;
44
+ api.setSelectionRange({ start: selectionStart, end: selectionEnd });
45
+ }
64
46
  };
65
47
 
66
48
  export const unorderedListCommand: ICommand = {
67
49
  name: 'unordered-list',
68
50
  keyCommand: 'list',
69
51
  shortcuts: 'ctrl+shift+u',
70
- value: '- ',
52
+ prefix: '- ',
71
53
  buttonProps: {
72
54
  'aria-label': 'Add unordered list (ctrl + shift + u)',
73
55
  title: 'Add unordered list (ctrl + shift + u)',
@@ -80,7 +62,7 @@ export const unorderedListCommand: ICommand = {
80
62
  />
81
63
  </svg>
82
64
  ),
83
- execute: (state: TextState, api: TextAreaTextApi) => {
65
+ execute: (state: ExecuteState, api: TextAreaTextApi) => {
84
66
  makeList(state, api, '- ');
85
67
  },
86
68
  };
@@ -89,7 +71,7 @@ export const orderedListCommand: ICommand = {
89
71
  name: 'ordered-list',
90
72
  keyCommand: 'list',
91
73
  shortcuts: 'ctrl+shift+o',
92
- value: '1. ',
74
+ prefix: '1. ',
93
75
  buttonProps: { 'aria-label': 'Add ordered list (ctrl + shift + o)', title: 'Add ordered list (ctrl + shift + o)' },
94
76
  icon: (
95
77
  <svg data-name="ordered-list" width="12" height="12" role="img" viewBox="0 0 512 512">
@@ -99,7 +81,7 @@ export const orderedListCommand: ICommand = {
99
81
  />
100
82
  </svg>
101
83
  ),
102
- execute: (state: TextState, api: TextAreaTextApi) => {
84
+ execute: (state: ExecuteState, api: TextAreaTextApi) => {
103
85
  makeList(state, api, (item, index) => `${index + 1}. `);
104
86
  },
105
87
  };
@@ -108,7 +90,7 @@ export const checkedListCommand: ICommand = {
108
90
  name: 'checked-list',
109
91
  keyCommand: 'list',
110
92
  shortcuts: 'ctrl+shift+c',
111
- value: '- [x] ',
93
+ prefix: '- [ ] ',
112
94
  buttonProps: { 'aria-label': 'Add checked list (ctrl + shift + c)', title: 'Add checked list (ctrl + shift + c)' },
113
95
  icon: (
114
96
  <svg data-name="checked-list" width="12" height="12" role="img" viewBox="0 0 512 512">
@@ -118,7 +100,7 @@ export const checkedListCommand: ICommand = {
118
100
  />
119
101
  </svg>
120
102
  ),
121
- execute: (state: TextState, api: TextAreaTextApi) => {
103
+ execute: (state: ExecuteState, api: TextAreaTextApi) => {
122
104
  makeList(state, api, (item, index) => `- [ ] `);
123
105
  },
124
106
  };
@@ -1,15 +1,17 @@
1
1
  import React from 'react';
2
- import { ICommand, TextState, TextAreaTextApi } from './';
2
+ import { ICommand, ExecuteState, TextAreaTextApi } from './';
3
3
  import {
4
4
  getBreaksNeededForEmptyLineBefore,
5
5
  getBreaksNeededForEmptyLineAfter,
6
6
  selectWord,
7
+ insertBeforeEachLine,
7
8
  } from '../utils/markdownUtils';
8
9
 
9
10
  export const quote: ICommand = {
10
11
  name: 'quote',
11
12
  keyCommand: 'quote',
12
13
  shortcuts: 'ctrlcmd+q',
14
+ prefix: '> ',
13
15
  buttonProps: { 'aria-label': 'Insert a quote (ctrl + q)', title: 'Insert a quote (ctrl + q)' },
14
16
  icon: (
15
17
  <svg width="12" height="12" viewBox="0 0 520 520">
@@ -19,9 +21,12 @@ export const quote: ICommand = {
19
21
  />
20
22
  </svg>
21
23
  ),
22
- execute: (state: TextState, api: TextAreaTextApi) => {
23
- // Adjust the selection to encompass the whole word if the caret is inside one
24
- const newSelectionRange = selectWord({ text: state.text, selection: state.selection });
24
+ execute: (state: ExecuteState, api: TextAreaTextApi) => {
25
+ const newSelectionRange = selectWord({
26
+ text: state.text,
27
+ selection: state.selection,
28
+ prefix: state.command.prefix!,
29
+ });
25
30
  const state1 = api.setSelectionRange(newSelectionRange);
26
31
  const breaksBeforeCount = getBreaksNeededForEmptyLineBefore(state1.text, state1.selection.start);
27
32
  const breaksBefore = Array(breaksBeforeCount + 1).join('\n');
@@ -29,15 +34,11 @@ export const quote: ICommand = {
29
34
  const breaksAfterCount = getBreaksNeededForEmptyLineAfter(state1.text, state1.selection.end);
30
35
  const breaksAfter = Array(breaksAfterCount + 1).join('\n');
31
36
 
32
- // Replaces the current selection with the quote mark up
33
- api.replaceSelection(`${breaksBefore}> ${state1.selectedText}${breaksAfter}`);
34
-
35
- const selectionStart = state1.selection.start + breaksBeforeCount + 2;
36
- const selectionEnd = selectionStart + state1.selectedText.length;
37
+ const modifiedText = insertBeforeEachLine(state1.selectedText, state.command.prefix!);
38
+ api.replaceSelection(`${breaksBefore}${modifiedText.modifiedText}${breaksAfter}`);
37
39
 
38
- api.setSelectionRange({
39
- start: selectionStart,
40
- end: selectionEnd,
41
- });
40
+ const selectionStart = state1.selection.start + breaksBeforeCount;
41
+ const selectionEnd = selectionStart + modifiedText.modifiedText.length;
42
+ api.setSelectionRange({ start: selectionStart, end: selectionEnd });
42
43
  },
43
44
  };
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import { ICommand, ExecuteState, TextAreaTextApi } from './';
3
- import { selectWord } from '../utils/markdownUtils';
3
+ import { selectWord, executeCommand } from '../utils/markdownUtils';
4
4
 
5
5
  export const strikethrough: ICommand = {
6
6
  name: 'strikethrough',
@@ -10,7 +10,7 @@ export const strikethrough: ICommand = {
10
10
  'aria-label': 'Add strikethrough text (ctrl + shift + x)',
11
11
  title: 'Add strikethrough text (ctrl + shift + x)',
12
12
  },
13
- value: '~~{{text}}~~',
13
+ prefix: '~~',
14
14
  icon: (
15
15
  <svg data-name="strikethrough" width="12" height="12" role="img" viewBox="0 0 512 512">
16
16
  <path
@@ -20,16 +20,17 @@ export const strikethrough: ICommand = {
20
20
  </svg>
21
21
  ),
22
22
  execute: (state: ExecuteState, api: TextAreaTextApi) => {
23
- // Adjust the selection to encompass the whole word if the caret is inside one
24
- const newSelectionRange = selectWord({ text: state.text, selection: state.selection });
23
+ const newSelectionRange = selectWord({
24
+ text: state.text,
25
+ selection: state.selection,
26
+ prefix: state.command.prefix!,
27
+ });
25
28
  const state1 = api.setSelectionRange(newSelectionRange);
26
- // Replaces the current selection with the bold mark up
27
- const val = state.command.value || '';
28
- api.replaceSelection(val.replace(/({{text}})/gi, state1.selectedText));
29
-
30
- const start = state1.selection.start + val.indexOf('{{text}}');
31
- const end = state1.selection.start + val.indexOf('{{text}}') + (state1.selection.end - state1.selection.start);
32
- // Adjust the selection to not contain the **
33
- api.setSelectionRange({ start, end });
29
+ executeCommand({
30
+ api,
31
+ selectedText: state1.selectedText,
32
+ selection: state.selection,
33
+ prefix: state.command.prefix!,
34
+ });
34
35
  },
35
36
  };
@@ -0,0 +1,52 @@
1
+ import React from 'react';
2
+ import { ICommand, ExecuteState, TextAreaTextApi } from './';
3
+ import { selectWord, executeCommand } from '../utils/markdownUtils';
4
+
5
+ export const table: ICommand = {
6
+ name: 'table',
7
+ keyCommand: 'table',
8
+ prefix: '\n| Header | Header |\n|--------|--------|\n| Cell | Cell |\n| Cell | Cell |\n| Cell | Cell |\n\n',
9
+ suffix: '',
10
+ buttonProps: { 'aria-label': 'Add table', title: 'Add table' },
11
+ icon: (
12
+ <svg role="img" width="12" height="12" viewBox="0 0 512 512">
13
+ <path
14
+ fill="currentColor"
15
+ d="M64 256V160H224v96H64zm0 64H224v96H64V320zm224 96V320H448v96H288zM448 256H288V160H448v96zM64 32C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64H448c35.3 0 64-28.7 64-64V96c0-35.3-28.7-64-64-64H64z"
16
+ //Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com
17
+ />
18
+ </svg>
19
+ ),
20
+ execute: (state: ExecuteState, api: TextAreaTextApi) => {
21
+ const newSelectionRange = selectWord({
22
+ text: state.text,
23
+ selection: state.selection,
24
+ prefix: state.command.prefix!,
25
+ suffix: state.command.suffix,
26
+ });
27
+ let state1 = api.setSelectionRange(newSelectionRange);
28
+ if (
29
+ state1.selectedText.length >= state.command.prefix!.length + state.command.suffix!.length &&
30
+ state1.selectedText.startsWith(state.command.prefix!)
31
+ ) {
32
+ // Remove
33
+ executeCommand({
34
+ api,
35
+ selectedText: state1.selectedText,
36
+ selection: state.selection,
37
+ prefix: state.command.prefix!,
38
+ suffix: state.command.suffix,
39
+ });
40
+ } else {
41
+ // Add
42
+ state1 = api.setSelectionRange({ start: state.selection.start, end: state.selection.start });
43
+ executeCommand({
44
+ api,
45
+ selectedText: state1.selectedText,
46
+ selection: state.selection,
47
+ prefix: state.command.prefix!,
48
+ suffix: state.command.suffix,
49
+ });
50
+ }
51
+ },
52
+ };
@@ -1,6 +1,23 @@
1
1
  import React from 'react';
2
- import { ICommand } from './';
2
+ import { ICommand, ExecuteState, TextAreaTextApi } from './';
3
3
  import { title1 } from './title1';
4
+ import { selectLine, executeCommand } from '../utils/markdownUtils';
5
+
6
+ export function titleExecute({
7
+ state,
8
+ api,
9
+ prefix,
10
+ suffix = prefix,
11
+ }: {
12
+ state: ExecuteState;
13
+ api: TextAreaTextApi;
14
+ prefix: string;
15
+ suffix?: string;
16
+ }) {
17
+ const newSelectionRange = selectLine({ text: state.text, selection: state.selection });
18
+ const state1 = api.setSelectionRange(newSelectionRange);
19
+ executeCommand({ api, selectedText: state1.selectedText, selection: state.selection, prefix, suffix });
20
+ }
4
21
 
5
22
  export const title: ICommand = {
6
23
  ...title1,
@@ -1,19 +1,16 @@
1
1
  import React from 'react';
2
- import { insertAtLineStart } from '../utils/InsertTextAtPosition';
3
- import { ICommand, TextState, TextAreaTextApi } from './';
2
+ import { titleExecute } from '../commands/title';
3
+ import { ICommand, ExecuteState, TextAreaTextApi } from './';
4
4
 
5
5
  export const title1: ICommand = {
6
6
  name: 'title1',
7
7
  keyCommand: 'title1',
8
8
  shortcuts: 'ctrlcmd+1',
9
- value: 'title1',
9
+ prefix: '# ',
10
+ suffix: '',
10
11
  buttonProps: { 'aria-label': 'Insert title1 (ctrl + 1)', title: 'Insert title1 (ctrl + 1)' },
11
12
  icon: <div style={{ fontSize: 18, textAlign: 'left' }}>Title 1</div>,
12
- execute: (state: TextState, api: TextAreaTextApi) => {
13
- if (state.selection.start === 0 || /\n$/.test(state.text)) {
14
- api.replaceSelection('# ');
15
- } else {
16
- insertAtLineStart('# ', state.selection.start, api.textArea);
17
- }
13
+ execute: (state: ExecuteState, api: TextAreaTextApi) => {
14
+ titleExecute({ state, api, prefix: state.command.prefix!, suffix: state.command.suffix });
18
15
  },
19
16
  };
@@ -1,19 +1,16 @@
1
1
  import React from 'react';
2
- import { insertAtLineStart } from '../utils/InsertTextAtPosition';
3
- import { ICommand, TextState, TextAreaTextApi } from './';
2
+ import { titleExecute } from '../commands/title';
3
+ import { ICommand, ExecuteState, TextAreaTextApi } from './';
4
4
 
5
5
  export const title2: ICommand = {
6
6
  name: 'title2',
7
7
  keyCommand: 'title2',
8
8
  shortcuts: 'ctrlcmd+2',
9
- value: 'title2',
9
+ prefix: '## ',
10
+ suffix: '',
10
11
  buttonProps: { 'aria-label': 'Insert title2 (ctrl + 2)', title: 'Insert title2 (ctrl + 2)' },
11
12
  icon: <div style={{ fontSize: 16, textAlign: 'left' }}>Title 2</div>,
12
- execute: (state: TextState, api: TextAreaTextApi) => {
13
- if (state.selection.start === 0 || /\n$/.test(state.text)) {
14
- api.replaceSelection('## ');
15
- } else {
16
- insertAtLineStart('## ', state.selection.start, api.textArea);
17
- }
13
+ execute: (state: ExecuteState, api: TextAreaTextApi) => {
14
+ titleExecute({ state, api, prefix: state.command.prefix!, suffix: state.command.suffix });
18
15
  },
19
16
  };
@@ -1,19 +1,16 @@
1
1
  import React from 'react';
2
- import { insertAtLineStart } from '../utils/InsertTextAtPosition';
3
- import { ICommand, TextState, TextAreaTextApi } from './';
2
+ import { titleExecute } from '../commands/title';
3
+ import { ICommand, ExecuteState, TextAreaTextApi } from './';
4
4
 
5
5
  export const title3: ICommand = {
6
6
  name: 'title3',
7
7
  keyCommand: 'title3',
8
8
  shortcuts: 'ctrlcmd+3',
9
- value: 'title3',
9
+ prefix: '### ',
10
+ suffix: '',
10
11
  buttonProps: { 'aria-label': 'Insert title3 (ctrl + 3)', title: 'Insert title3 (ctrl + 3)' },
11
12
  icon: <div style={{ fontSize: 15, textAlign: 'left' }}>Title 3</div>,
12
- execute: (state: TextState, api: TextAreaTextApi) => {
13
- if (state.selection.start === 0 || /\n$/.test(state.text)) {
14
- api.replaceSelection('### ');
15
- } else {
16
- insertAtLineStart('### ', state.selection.start, api.textArea);
17
- }
13
+ execute: (state: ExecuteState, api: TextAreaTextApi) => {
14
+ titleExecute({ state, api, prefix: state.command.prefix!, suffix: state.command.suffix });
18
15
  },
19
16
  };
@@ -1,19 +1,16 @@
1
1
  import React from 'react';
2
- import { insertAtLineStart } from '../utils/InsertTextAtPosition';
3
- import { ICommand, TextState, TextAreaTextApi } from './';
2
+ import { titleExecute } from '../commands/title';
3
+ import { ICommand, ExecuteState, TextAreaTextApi } from './';
4
4
 
5
5
  export const title4: ICommand = {
6
6
  name: 'title4',
7
7
  keyCommand: 'title4',
8
8
  shortcuts: 'ctrlcmd+4',
9
- value: 'title4',
9
+ prefix: '#### ',
10
+ suffix: '',
10
11
  buttonProps: { 'aria-label': 'Insert title4 (ctrl + 4)', title: 'Insert title4 (ctrl + 4)' },
11
12
  icon: <div style={{ fontSize: 14, textAlign: 'left' }}>Title 4</div>,
12
- execute: (state: TextState, api: TextAreaTextApi) => {
13
- if (state.selection.start === 0 || /\n$/.test(state.text)) {
14
- api.replaceSelection('#### ');
15
- } else {
16
- insertAtLineStart('#### ', state.selection.start, api.textArea);
17
- }
13
+ execute: (state: ExecuteState, api: TextAreaTextApi) => {
14
+ titleExecute({ state, api, prefix: state.command.prefix!, suffix: state.command.suffix });
18
15
  },
19
16
  };
@@ -1,19 +1,16 @@
1
1
  import React from 'react';
2
- import { insertAtLineStart } from '../utils/InsertTextAtPosition';
3
- import { ICommand, TextState, TextAreaTextApi } from './';
2
+ import { titleExecute } from '../commands/title';
3
+ import { ICommand, ExecuteState, TextAreaTextApi } from './';
4
4
 
5
5
  export const title5: ICommand = {
6
6
  name: 'title5',
7
7
  keyCommand: 'title5',
8
8
  shortcuts: 'ctrlcmd+5',
9
- value: 'title5',
9
+ prefix: '##### ',
10
+ suffix: '',
10
11
  buttonProps: { 'aria-label': 'Insert title5 (ctrl + 5)', title: 'Insert title5 (ctrl + 5)' },
11
12
  icon: <div style={{ fontSize: 12, textAlign: 'left' }}>Title 5</div>,
12
- execute: (state: TextState, api: TextAreaTextApi) => {
13
- if (state.selection.start === 0 || /\n$/.test(state.text)) {
14
- api.replaceSelection('##### ');
15
- } else {
16
- insertAtLineStart('##### ', state.selection.start, api.textArea);
17
- }
13
+ execute: (state: ExecuteState, api: TextAreaTextApi) => {
14
+ titleExecute({ state, api, prefix: state.command.prefix!, suffix: state.command.suffix });
18
15
  },
19
16
  };