@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
@@ -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 title6: ICommand = {
6
6
  name: 'title6',
7
7
  keyCommand: 'title6',
8
8
  shortcuts: 'ctrlcmd+6',
9
- value: 'title6',
9
+ prefix: '###### ',
10
+ suffix: '',
10
11
  buttonProps: { 'aria-label': 'Insert title6 (ctrl + 6)', title: 'Insert title6 (ctrl + 6)' },
11
12
  icon: <div style={{ fontSize: 12, textAlign: 'left' }}>Title 6</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,6 +1,6 @@
1
1
  import { insertTextAtPosition } from '../../utils/InsertTextAtPosition';
2
+ import { insertBeforeEachLine, selectLine } from '../../utils/markdownUtils';
2
3
  import { TextAreaTextApi } from '../../commands';
3
- import { insertBeforeEachLine } from '../../commands/list';
4
4
 
5
5
  /**
6
6
  * - `13` - `Enter`
@@ -11,6 +11,38 @@ function stopPropagation(e: KeyboardEvent | React.KeyboardEvent<HTMLTextAreaElem
11
11
  e.preventDefault();
12
12
  }
13
13
 
14
+ function handleLineMove(e: KeyboardEvent | React.KeyboardEvent<HTMLTextAreaElement>, direction: number) {
15
+ stopPropagation(e);
16
+ const target = e.target as HTMLTextAreaElement;
17
+ const textArea = new TextAreaTextApi(target);
18
+ let selection = { start: target.selectionStart, end: target.selectionEnd };
19
+ selection = selectLine({ text: target.value, selection });
20
+ if ((direction < 0 && selection.start <= 0) || (direction > 0 && selection.end >= target.value.length)) {
21
+ return;
22
+ }
23
+
24
+ const blockText = target.value.slice(selection.start, selection.end);
25
+ if (direction < 0) {
26
+ const prevLineSelection = selectLine({
27
+ text: target.value,
28
+ selection: { start: selection.start - 1, end: selection.start - 1 },
29
+ });
30
+ const prevLineText = target.value.slice(prevLineSelection.start, prevLineSelection.end);
31
+ textArea.setSelectionRange({ start: prevLineSelection.start, end: selection.end });
32
+ insertTextAtPosition(target, `${blockText}\n${prevLineText}`);
33
+ textArea.setSelectionRange({ start: prevLineSelection.start, end: prevLineSelection.start + blockText.length });
34
+ } else {
35
+ const nextLineSelection = selectLine({
36
+ text: target.value,
37
+ selection: { start: selection.end + 1, end: selection.end + 1 },
38
+ });
39
+ const nextLineText = target.value.slice(nextLineSelection.start, nextLineSelection.end);
40
+ textArea.setSelectionRange({ start: selection.start, end: nextLineSelection.end });
41
+ insertTextAtPosition(target, `${nextLineText}\n${blockText}`);
42
+ textArea.setSelectionRange({ start: nextLineSelection.end - blockText.length, end: nextLineSelection.end });
43
+ }
44
+ }
45
+
14
46
  export default function handleKeyDown(
15
47
  e: KeyboardEvent | React.KeyboardEvent<HTMLTextAreaElement>,
16
48
  tabSize: number = 2,
@@ -70,7 +102,8 @@ export default function handleKeyDown(
70
102
  } else if (
71
103
  e.keyCode === 13 &&
72
104
  e.code.toLowerCase() === 'enter' &&
73
- (/^(-|\*)\s/.test(currentLineStr) || /^\d+.\s/.test(currentLineStr))
105
+ (/^(-|\*)\s/.test(currentLineStr) || /^\d+.\s/.test(currentLineStr)) &&
106
+ !e.shiftKey
74
107
  ) {
75
108
  /**
76
109
  * `13` - `Enter`
@@ -82,15 +115,31 @@ export default function handleKeyDown(
82
115
  startStr = '\n* ';
83
116
  }
84
117
 
85
- if (currentLineStr.startsWith('- [ ]')) {
118
+ if (
119
+ currentLineStr.startsWith('- [ ]') ||
120
+ currentLineStr.startsWith('- [X]') ||
121
+ currentLineStr.startsWith('- [x]')
122
+ ) {
86
123
  startStr = '\n- [ ] ';
87
- } else if (currentLineStr.startsWith('- [X]')) {
88
- startStr = '\n- [X] ';
89
124
  }
90
125
 
91
126
  if (/^\d+.\s/.test(currentLineStr)) {
92
127
  startStr = `\n${parseInt(currentLineStr) + 1}. `;
93
128
  }
94
129
  return insertTextAtPosition(target, startStr);
130
+ } else if (e.code && e.code.toLowerCase() === 'keyd' && e.ctrlKey) {
131
+ // Duplicate lines
132
+ stopPropagation(e);
133
+ let selection = { start: target.selectionStart, end: target.selectionEnd };
134
+ const savedSelection = selection;
135
+ selection = selectLine({ text: target.value, selection });
136
+ const textToDuplicate = target.value.slice(selection.start, selection.end);
137
+ textArea.setSelectionRange({ start: selection.end, end: selection.end });
138
+ insertTextAtPosition(target, `\n${textToDuplicate}`);
139
+ textArea.setSelectionRange({ start: savedSelection.start, end: savedSelection.end });
140
+ } else if (e.code && e.code.toLowerCase() === 'arrowup' && e.altKey) {
141
+ handleLineMove(e, -1);
142
+ } else if (e.code && e.code.toLowerCase() === 'arrowdown' && e.altKey) {
143
+ handleLineMove(e, 1);
95
144
  }
96
145
  }
@@ -22,33 +22,6 @@ function canManipulateViaTextNodes(input: HTMLTextAreaElement | HTMLInputElement
22
22
  return browserSupportsTextareaTextNodes;
23
23
  }
24
24
 
25
- /**
26
- * @param {string} val
27
- * @param {number} cursorIdx
28
- * @param {HTMLTextAreaElement|HTMLInputElement} input
29
- * @return {void}
30
- */
31
- export const insertAtLineStart = (
32
- val: string,
33
- cursorIdx: number,
34
- input: HTMLTextAreaElement | HTMLInputElement,
35
- ): void => {
36
- const content = input.value;
37
- let startIdx = 0;
38
-
39
- while (cursorIdx--) {
40
- let char = content[cursorIdx];
41
- if (char === '\n') {
42
- startIdx = cursorIdx + 1;
43
- break;
44
- }
45
- }
46
-
47
- input.focus();
48
- input.setRangeText(val, startIdx, startIdx);
49
- input.dispatchEvent(new Event('input', { bubbles: true }));
50
- };
51
-
52
25
  /**
53
26
  * @param {HTMLTextAreaElement|HTMLInputElement} input
54
27
  * @param {string} text
@@ -71,7 +44,13 @@ export function insertTextAtPosition(input: HTMLTextAreaElement | HTMLInputEleme
71
44
  }
72
45
 
73
46
  // Webkit + Edge
74
- const isSuccess = document.execCommand && document.execCommand('insertText', false, text);
47
+ let isSuccess = false;
48
+ if (text !== '') {
49
+ isSuccess = document.execCommand && document.execCommand('insertText', false, text);
50
+ } else {
51
+ isSuccess = document.execCommand && document.execCommand('delete', false);
52
+ }
53
+
75
54
  if (!isSuccess) {
76
55
  const start = input.selectionStart!;
77
56
  const end = input.selectionEnd!;
@@ -1,16 +1,42 @@
1
1
  import { TextRange } from '../commands';
2
+ import { TextAreaTextApi, ExecuteState } from '../commands';
2
3
 
3
4
  export interface TextSection {
4
5
  text: string;
5
6
  selection: TextRange;
6
7
  }
7
8
 
8
- export function selectWord({ text, selection }: TextSection): TextRange {
9
+ export function selectWord({
10
+ text,
11
+ selection,
12
+ prefix,
13
+ suffix = prefix,
14
+ }: {
15
+ text: string;
16
+ selection: TextRange;
17
+ prefix: string;
18
+ suffix?: string;
19
+ }): TextRange {
20
+ let result = selection;
9
21
  if (text && text.length && selection.start === selection.end) {
10
- // the user is pointing to a word
11
- return getSurroundingWord(text, selection.start);
22
+ result = getSurroundingWord(text, selection.start);
23
+ }
24
+ if (result.start >= prefix.length && result.end <= text.length - suffix.length) {
25
+ const selectedTextContext = text.slice(result.start - prefix.length, result.end + suffix.length);
26
+ if (selectedTextContext.startsWith(prefix) && selectedTextContext.endsWith(suffix)) {
27
+ return { start: result.start - prefix.length, end: result.end + suffix.length };
28
+ }
12
29
  }
13
- return selection;
30
+ return result;
31
+ }
32
+
33
+ export function selectLine({ text, selection }: TextSection): TextRange {
34
+ const start = text.slice(0, selection.start).lastIndexOf('\n') + 1;
35
+ let end = text.slice(selection.end).indexOf('\n') + selection.end;
36
+ if (end === selection.end - 1) {
37
+ end = text.length;
38
+ }
39
+ return { start, end };
14
40
  }
15
41
 
16
42
  /**
@@ -100,3 +126,67 @@ export function getSurroundingWord(text: string, position: number): TextRange {
100
126
 
101
127
  return { start, end };
102
128
  }
129
+
130
+ export function executeCommand({
131
+ api,
132
+ selectedText,
133
+ selection,
134
+ prefix,
135
+ suffix = prefix,
136
+ }: {
137
+ api: TextAreaTextApi;
138
+ selectedText: string;
139
+ selection: TextRange;
140
+ prefix: string;
141
+ suffix?: string;
142
+ }) {
143
+ if (
144
+ selectedText.length >= prefix.length + suffix.length &&
145
+ selectedText.startsWith(prefix) &&
146
+ selectedText.endsWith(suffix)
147
+ ) {
148
+ api.replaceSelection(selectedText.slice(prefix.length, suffix.length ? -suffix.length : undefined));
149
+ api.setSelectionRange({ start: selection.start - prefix.length, end: selection.end - prefix.length });
150
+ } else {
151
+ api.replaceSelection(`${prefix}${selectedText}${suffix}`);
152
+ api.setSelectionRange({ start: selection.start + prefix.length, end: selection.end + prefix.length });
153
+ }
154
+ }
155
+
156
+ export type AlterLineFunction = (line: string, index: number) => string;
157
+
158
+ /**
159
+ * Inserts insertionString before each line
160
+ */
161
+ export function insertBeforeEachLine(
162
+ selectedText: string,
163
+ insertBefore: string | AlterLineFunction,
164
+ ): { modifiedText: string; insertionLength: number } {
165
+ const lines = selectedText.split(/\n/);
166
+
167
+ let insertionLength = 0;
168
+ const modifiedText = lines
169
+ .map((item, index) => {
170
+ if (typeof insertBefore === 'string') {
171
+ if (item.startsWith(insertBefore)) {
172
+ insertionLength -= insertBefore.length;
173
+ return item.slice(insertBefore.length);
174
+ }
175
+ insertionLength += insertBefore.length;
176
+ return insertBefore + item;
177
+ }
178
+ if (typeof insertBefore === 'function') {
179
+ if (item.startsWith(insertBefore(item, index))) {
180
+ insertionLength -= insertBefore(item, index).length;
181
+ return item.slice(insertBefore(item, index).length);
182
+ }
183
+ const insertionResult = insertBefore(item, index);
184
+ insertionLength += insertionResult.length;
185
+ return insertBefore(item, index) + item;
186
+ }
187
+ throw Error('insertion is expected to be either a string or a function');
188
+ })
189
+ .join('\n');
190
+
191
+ return { modifiedText, insertionLength };
192
+ }