@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.
- package/README.md +2 -1
- package/esm/Context.d.ts +2 -0
- package/esm/Context.js.map +2 -2
- package/esm/Editor.d.ts +5 -1
- package/esm/Editor.js +6 -3
- package/esm/Editor.js.map +4 -2
- package/esm/commands/title1.js +5 -6
- package/esm/commands/title1.js.map +9 -5
- package/esm/commands/title2.js +5 -6
- package/esm/commands/title2.js.map +9 -5
- package/esm/commands/title3.js +5 -6
- package/esm/commands/title3.js.map +9 -5
- package/esm/commands/title4.js +5 -6
- package/esm/commands/title4.js.map +9 -5
- package/esm/commands/title5.js +5 -6
- package/esm/commands/title5.js.map +9 -5
- package/esm/commands/title6.js +5 -6
- package/esm/commands/title6.js.map +9 -5
- package/esm/components/TextArea/Markdown.js +1 -1
- package/esm/components/TextArea/Markdown.js.map +2 -2
- package/esm/components/TextArea/Textarea.js +2 -1
- package/esm/components/TextArea/Textarea.js.map +3 -2
- package/esm/components/TextArea/handleKeyDown.d.ts +1 -1
- package/esm/components/TextArea/handleKeyDown.js +6 -2
- package/esm/components/TextArea/handleKeyDown.js.map +3 -2
- package/esm/utils/InsertTextAtPosition.d.ts +7 -0
- package/esm/utils/InsertTextAtPosition.js +27 -1
- package/esm/utils/InsertTextAtPosition.js.map +14 -6
- package/lib/Context.d.ts +2 -0
- package/lib/Context.js.map +2 -2
- package/lib/Editor.d.ts +5 -1
- package/lib/Editor.js +9 -3
- package/lib/Editor.js.map +4 -2
- package/lib/commands/title1.js +6 -6
- package/lib/commands/title1.js.map +8 -5
- package/lib/commands/title2.js +6 -6
- package/lib/commands/title2.js.map +8 -5
- package/lib/commands/title3.js +6 -6
- package/lib/commands/title3.js.map +8 -5
- package/lib/commands/title4.js +6 -6
- package/lib/commands/title4.js.map +8 -5
- package/lib/commands/title5.js +6 -6
- package/lib/commands/title5.js.map +8 -5
- package/lib/commands/title6.js +6 -6
- package/lib/commands/title6.js.map +8 -5
- package/lib/components/TextArea/Markdown.js +1 -1
- package/lib/components/TextArea/Markdown.js.map +2 -2
- package/lib/components/TextArea/Textarea.js +2 -1
- package/lib/components/TextArea/Textarea.js.map +3 -2
- package/lib/components/TextArea/handleKeyDown.d.ts +1 -1
- package/lib/components/TextArea/handleKeyDown.js +2 -1
- package/lib/components/TextArea/handleKeyDown.js.map +3 -2
- package/lib/utils/InsertTextAtPosition.d.ts +7 -0
- package/lib/utils/InsertTextAtPosition.js +30 -0
- package/lib/utils/InsertTextAtPosition.js.map +14 -6
- package/package.json +8 -7
- package/src/Context.tsx +1 -0
- package/src/Editor.tsx +14 -4
- package/src/commands/title1.tsx +5 -4
- package/src/commands/title2.tsx +5 -4
- package/src/commands/title3.tsx +5 -4
- package/src/commands/title4.tsx +5 -4
- package/src/commands/title5.tsx +5 -4
- package/src/commands/title6.tsx +5 -4
- package/src/components/TextArea/Markdown.tsx +1 -1
- package/src/components/TextArea/Textarea.tsx +12 -3
- package/src/components/TextArea/handleKeyDown.tsx +3 -1
- 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
|
-
|
|
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
|
});
|
package/src/commands/title1.tsx
CHANGED
|
@@ -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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
};
|
package/src/commands/title2.tsx
CHANGED
|
@@ -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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
};
|
package/src/commands/title3.tsx
CHANGED
|
@@ -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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
};
|
package/src/commands/title4.tsx
CHANGED
|
@@ -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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
};
|
package/src/commands/title5.tsx
CHANGED
|
@@ -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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
};
|
package/src/commands/title6.tsx
CHANGED
|
@@ -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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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 {
|
|
14
|
-
|
|
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
|
|