@uiw/react-md-editor 3.8.2 → 3.9.0

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 (68) hide show
  1. package/README.md +2 -1
  2. package/esm/Context.d.ts +2 -0
  3. package/esm/Context.js.map +2 -2
  4. package/esm/Editor.d.ts +5 -1
  5. package/esm/Editor.js +6 -3
  6. package/esm/Editor.js.map +4 -2
  7. package/esm/commands/title1.js +5 -6
  8. package/esm/commands/title1.js.map +9 -5
  9. package/esm/commands/title2.js +5 -6
  10. package/esm/commands/title2.js.map +9 -5
  11. package/esm/commands/title3.js +5 -6
  12. package/esm/commands/title3.js.map +9 -5
  13. package/esm/commands/title4.js +5 -6
  14. package/esm/commands/title4.js.map +9 -5
  15. package/esm/commands/title5.js +5 -6
  16. package/esm/commands/title5.js.map +9 -5
  17. package/esm/commands/title6.js +5 -6
  18. package/esm/commands/title6.js.map +9 -5
  19. package/esm/components/TextArea/Markdown.js +1 -1
  20. package/esm/components/TextArea/Markdown.js.map +2 -2
  21. package/esm/components/TextArea/Textarea.js +2 -1
  22. package/esm/components/TextArea/Textarea.js.map +3 -2
  23. package/esm/components/TextArea/handleKeyDown.d.ts +1 -1
  24. package/esm/components/TextArea/handleKeyDown.js +6 -2
  25. package/esm/components/TextArea/handleKeyDown.js.map +3 -2
  26. package/esm/utils/InsertTextAtPosition.d.ts +7 -0
  27. package/esm/utils/InsertTextAtPosition.js +27 -1
  28. package/esm/utils/InsertTextAtPosition.js.map +14 -6
  29. package/lib/Context.d.ts +2 -0
  30. package/lib/Context.js.map +2 -2
  31. package/lib/Editor.d.ts +5 -1
  32. package/lib/Editor.js +9 -3
  33. package/lib/Editor.js.map +4 -2
  34. package/lib/commands/title1.js +6 -6
  35. package/lib/commands/title1.js.map +8 -5
  36. package/lib/commands/title2.js +6 -6
  37. package/lib/commands/title2.js.map +8 -5
  38. package/lib/commands/title3.js +6 -6
  39. package/lib/commands/title3.js.map +8 -5
  40. package/lib/commands/title4.js +6 -6
  41. package/lib/commands/title4.js.map +8 -5
  42. package/lib/commands/title5.js +6 -6
  43. package/lib/commands/title5.js.map +8 -5
  44. package/lib/commands/title6.js +6 -6
  45. package/lib/commands/title6.js.map +8 -5
  46. package/lib/components/TextArea/Markdown.js +1 -1
  47. package/lib/components/TextArea/Markdown.js.map +2 -2
  48. package/lib/components/TextArea/Textarea.js +2 -1
  49. package/lib/components/TextArea/Textarea.js.map +3 -2
  50. package/lib/components/TextArea/handleKeyDown.d.ts +1 -1
  51. package/lib/components/TextArea/handleKeyDown.js +2 -1
  52. package/lib/components/TextArea/handleKeyDown.js.map +3 -2
  53. package/lib/utils/InsertTextAtPosition.d.ts +7 -0
  54. package/lib/utils/InsertTextAtPosition.js +30 -0
  55. package/lib/utils/InsertTextAtPosition.js.map +14 -6
  56. package/package.json +8 -7
  57. package/src/Context.tsx +1 -0
  58. package/src/Editor.tsx +14 -4
  59. package/src/commands/title1.tsx +5 -4
  60. package/src/commands/title2.tsx +5 -4
  61. package/src/commands/title3.tsx +5 -4
  62. package/src/commands/title4.tsx +5 -4
  63. package/src/commands/title5.tsx +5 -4
  64. package/src/commands/title6.tsx +5 -4
  65. package/src/components/TextArea/Markdown.tsx +1 -1
  66. package/src/components/TextArea/Textarea.tsx +12 -3
  67. package/src/components/TextArea/handleKeyDown.tsx +3 -1
  68. package/src/utils/InsertTextAtPosition.ts +29 -2
package/src/Editor.tsx CHANGED
@@ -76,6 +76,10 @@ export interface MDEditorProps extends Omit<React.HTMLAttributes<HTMLDivElement>
76
76
  * Default `2` spaces.
77
77
  */
78
78
  tabSize?: number;
79
+ /**
80
+ * If `false`, the `tab` key inserts a tab character into the textarea. If `true`, the `tab` key executes default behavior e.g. focus shifts to next element.
81
+ */
82
+ defaultTabEnable?: boolean;
79
83
  /**
80
84
  * You can create your own commands or reuse existing commands.
81
85
  */
@@ -84,7 +88,7 @@ export interface MDEditorProps extends Omit<React.HTMLAttributes<HTMLDivElement>
84
88
  * Filter or modify your commands.
85
89
  * https://github.com/uiwjs/react-md-editor/issues/296
86
90
  */
87
- commandsFilter?: (command: ICommand) => false | ICommand;
91
+ commandsFilter?: (command: ICommand, isExtra: boolean) => false | ICommand;
88
92
  /**
89
93
  * You can create your own commands or reuse existing commands.
90
94
  */
@@ -128,23 +132,29 @@ const InternalMDEditor = (
128
132
  minHeight = 100,
129
133
  autoFocus,
130
134
  tabSize = 2,
135
+ defaultTabEnable = false,
131
136
  onChange,
132
137
  hideToolbar,
133
138
  renderTextarea,
134
139
  ...other
135
140
  } = props || {};
136
-
137
- const cmds = commands.map((item) => (commandsFilter ? commandsFilter(item) : item)).filter(Boolean) as ICommand[];
141
+ const cmds = commands
142
+ .map((item) => (commandsFilter ? commandsFilter(item, false) : item))
143
+ .filter(Boolean) as ICommand[];
144
+ const extraCmds = extraCommands
145
+ .map((item) => (commandsFilter ? commandsFilter(item, true) : item))
146
+ .filter(Boolean) as ICommand[];
138
147
  let [state, dispatch] = useReducer(reducer, {
139
148
  markdown: propsValue,
140
149
  preview: previewType,
141
150
  height,
142
151
  highlightEnable,
143
152
  tabSize,
153
+ defaultTabEnable,
144
154
  scrollTop: 0,
145
155
  scrollTopPreview: 0,
146
156
  commands: cmds,
147
- extraCommands,
157
+ extraCommands: extraCmds,
148
158
  fullscreen,
149
159
  barPopup: {},
150
160
  });
@@ -1,4 +1,5 @@
1
1
  import React from 'react';
2
+ import { insertAtLineStart } from '../utils/InsertTextAtPosition';
2
3
  import { ICommand, TextState, TextAreaTextApi } from './';
3
4
 
4
5
  export const title1: ICommand = {
@@ -8,10 +9,10 @@ export const title1: ICommand = {
8
9
  buttonProps: { 'aria-label': 'Insert title 1', title: 'Insert title 1' },
9
10
  icon: <div style={{ fontSize: 18, textAlign: 'left' }}>Title 1</div>,
10
11
  execute: (state: TextState, api: TextAreaTextApi) => {
11
- let modifyText = `# ${state.selectedText}\n`;
12
- if (!state.selectedText) {
13
- modifyText = `# `;
12
+ if (state.selection.start === 0 || /\n$/.test(state.text)) {
13
+ api.replaceSelection('# ');
14
+ } else {
15
+ insertAtLineStart('# ', state.selection.start, api.textArea);
14
16
  }
15
- api.replaceSelection(modifyText);
16
17
  },
17
18
  };
@@ -1,4 +1,5 @@
1
1
  import * as React from 'react';
2
+ import { insertAtLineStart } from '../utils/InsertTextAtPosition';
2
3
  import { ICommand, TextState, TextAreaTextApi } from './';
3
4
 
4
5
  export const title2: ICommand = {
@@ -8,10 +9,10 @@ export const title2: ICommand = {
8
9
  buttonProps: { 'aria-label': 'Insert title2', title: 'Insert title 2' },
9
10
  icon: <div style={{ fontSize: 16, textAlign: 'left' }}>Title 2</div>,
10
11
  execute: (state: TextState, api: TextAreaTextApi) => {
11
- let modifyText = `## ${state.selectedText}\n`;
12
- if (!state.selectedText) {
13
- modifyText = `## `;
12
+ if (state.selection.start === 0 || /\n$/.test(state.text)) {
13
+ api.replaceSelection('## ');
14
+ } else {
15
+ insertAtLineStart('## ', state.selection.start, api.textArea);
14
16
  }
15
- api.replaceSelection(modifyText);
16
17
  },
17
18
  };
@@ -1,4 +1,5 @@
1
1
  import * as React from 'react';
2
+ import { insertAtLineStart } from '../utils/InsertTextAtPosition';
2
3
  import { ICommand, TextState, TextAreaTextApi } from './';
3
4
 
4
5
  export const title3: ICommand = {
@@ -8,10 +9,10 @@ export const title3: ICommand = {
8
9
  buttonProps: { 'aria-label': 'Insert title3', title: 'Insert title 3' },
9
10
  icon: <div style={{ fontSize: 15, textAlign: 'left' }}>Title 3</div>,
10
11
  execute: (state: TextState, api: TextAreaTextApi) => {
11
- let modifyText = `### ${state.selectedText}\n`;
12
- if (!state.selectedText) {
13
- modifyText = `### `;
12
+ if (state.selection.start === 0 || /\n$/.test(state.text)) {
13
+ api.replaceSelection('### ');
14
+ } else {
15
+ insertAtLineStart('### ', state.selection.start, api.textArea);
14
16
  }
15
- api.replaceSelection(modifyText);
16
17
  },
17
18
  };
@@ -1,4 +1,5 @@
1
1
  import * as React from 'react';
2
+ import { insertAtLineStart } from '../utils/InsertTextAtPosition';
2
3
  import { ICommand, TextState, TextAreaTextApi } from './';
3
4
 
4
5
  export const title4: ICommand = {
@@ -8,10 +9,10 @@ export const title4: ICommand = {
8
9
  buttonProps: { 'aria-label': 'Insert title4', title: 'Insert title 4' },
9
10
  icon: <div style={{ fontSize: 14, textAlign: 'left' }}>Title 4</div>,
10
11
  execute: (state: TextState, api: TextAreaTextApi) => {
11
- let modifyText = `#### ${state.selectedText}\n`;
12
- if (!state.selectedText) {
13
- modifyText = `#### `;
12
+ if (state.selection.start === 0 || /\n$/.test(state.text)) {
13
+ api.replaceSelection('#### ');
14
+ } else {
15
+ insertAtLineStart('#### ', state.selection.start, api.textArea);
14
16
  }
15
- api.replaceSelection(modifyText);
16
17
  },
17
18
  };
@@ -1,4 +1,5 @@
1
1
  import * as React from 'react';
2
+ import { insertAtLineStart } from '../utils/InsertTextAtPosition';
2
3
  import { ICommand, TextState, TextAreaTextApi } from './';
3
4
 
4
5
  export const title5: ICommand = {
@@ -8,10 +9,10 @@ export const title5: ICommand = {
8
9
  buttonProps: { 'aria-label': 'Insert title5', title: 'Insert title 5' },
9
10
  icon: <div style={{ fontSize: 12, textAlign: 'left' }}>Title 5</div>,
10
11
  execute: (state: TextState, api: TextAreaTextApi) => {
11
- let modifyText = `##### ${state.selectedText}\n`;
12
- if (!state.selectedText) {
13
- modifyText = `##### `;
12
+ if (state.selection.start === 0 || /\n$/.test(state.text)) {
13
+ api.replaceSelection('##### ');
14
+ } else {
15
+ insertAtLineStart('##### ', state.selection.start, api.textArea);
14
16
  }
15
- api.replaceSelection(modifyText);
16
17
  },
17
18
  };
@@ -1,4 +1,5 @@
1
1
  import * as React from 'react';
2
+ import { insertAtLineStart } from '../utils/InsertTextAtPosition';
2
3
  import { ICommand, TextState, TextAreaTextApi } from './';
3
4
 
4
5
  export const title6: ICommand = {
@@ -8,10 +9,10 @@ export const title6: ICommand = {
8
9
  buttonProps: { 'aria-label': 'Insert title6', title: 'Insert title 6' },
9
10
  icon: <div style={{ fontSize: 12, textAlign: 'left' }}>Title 6</div>,
10
11
  execute: (state: TextState, api: TextAreaTextApi) => {
11
- let modifyText = `###### ${state.selectedText}\n`;
12
- if (!state.selectedText) {
13
- modifyText = `###### `;
12
+ if (state.selection.start === 0 || /\n$/.test(state.text)) {
13
+ api.replaceSelection('###### ');
14
+ } else {
15
+ insertAtLineStart('###### ', state.selection.start, api.textArea);
14
16
  }
15
- api.replaceSelection(modifyText);
16
17
  },
17
18
  };
@@ -41,7 +41,7 @@ export default function Markdown(props: MarkdownProps) {
41
41
  .processSync(
42
42
  `<pre class="language-markdown ${prefixCls}-text-pre wmde-markdown-color"><code class="language-markdown">${html2Escape(
43
43
  markdown,
44
- )}</code></pre>`,
44
+ )}\n</code></pre>`,
45
45
  );
46
46
  return React.createElement('div', {
47
47
  className: 'wmde-markdown-color',
@@ -10,8 +10,17 @@ export interface TextAreaProps extends Omit<React.TextareaHTMLAttributes<HTMLTex
10
10
 
11
11
  export default function Textarea(props: TextAreaProps) {
12
12
  const { prefixCls, onChange, ...other } = props;
13
- const { markdown, commands, fullscreen, preview, highlightEnable, extraCommands, tabSize, dispatch } =
14
- useContext(EditorContext);
13
+ const {
14
+ markdown,
15
+ commands,
16
+ fullscreen,
17
+ preview,
18
+ highlightEnable,
19
+ extraCommands,
20
+ tabSize,
21
+ defaultTabEnable,
22
+ dispatch,
23
+ } = useContext(EditorContext);
15
24
  const textRef = React.useRef<HTMLTextAreaElement>(null);
16
25
  const executeRef = React.useRef<TextAreaCommandOrchestrator>();
17
26
  const statesRef = React.useRef<ExecuteCommandState>({ fullscreen, preview });
@@ -30,7 +39,7 @@ export default function Textarea(props: TextAreaProps) {
30
39
  }, []);
31
40
 
32
41
  const onKeyDown = (e: KeyboardEvent | React.KeyboardEvent<HTMLTextAreaElement>) => {
33
- handleKeyDown(e, tabSize);
42
+ handleKeyDown(e, tabSize, defaultTabEnable);
34
43
  shortcuts(e, [...(commands || []), ...(extraCommands || [])], executeRef.current, dispatch, statesRef.current);
35
44
  };
36
45
  useEffect(() => {
@@ -14,16 +14,18 @@ function stopPropagation(e: KeyboardEvent | React.KeyboardEvent<HTMLTextAreaElem
14
14
  export default function handleKeyDown(
15
15
  e: KeyboardEvent | React.KeyboardEvent<HTMLTextAreaElement>,
16
16
  tabSize: number = 2,
17
+ defaultTabEnable: boolean = false,
17
18
  ) {
18
19
  const target = e.target as HTMLTextAreaElement;
19
20
  const starVal = target.value.substr(0, target.selectionStart);
20
21
  const valArr = starVal.split('\n');
21
22
  const currentLineStr = valArr[valArr.length - 1];
22
23
  const textArea = new TextAreaTextApi(target);
24
+
23
25
  /**
24
26
  * `9` - `Tab`
25
27
  */
26
- if (e.code && e.code.toLowerCase() === 'tab') {
28
+ if (!defaultTabEnable && e.code && e.code.toLowerCase() === 'tab') {
27
29
  stopPropagation(e);
28
30
  const space = new Array(tabSize + 1).join(' ');
29
31
  if (target.selectionStart !== target.selectionEnd) {
@@ -10,7 +10,7 @@ let browserSupportsTextareaTextNodes: any;
10
10
  * @param {HTMLElement} input
11
11
  * @return {boolean}
12
12
  */
13
- function canManipulateViaTextNodes(input: HTMLTextAreaElement | HTMLInputElement) {
13
+ function canManipulateViaTextNodes(input: HTMLTextAreaElement | HTMLInputElement): boolean {
14
14
  if (input.nodeName !== 'TEXTAREA') {
15
15
  return false;
16
16
  }
@@ -22,12 +22,39 @@ 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
+
25
52
  /**
26
53
  * @param {HTMLTextAreaElement|HTMLInputElement} input
27
54
  * @param {string} text
28
55
  * @returns {void}
29
56
  */
30
- export function insertTextAtPosition(input: HTMLTextAreaElement | HTMLInputElement, text: string) {
57
+ export function insertTextAtPosition(input: HTMLTextAreaElement | HTMLInputElement, text: string): void {
31
58
  // Most of the used APIs only work with the field selected
32
59
  input.focus();
33
60