@uiw/react-md-editor 4.1.5 → 4.1.7
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 +16 -1
- package/dist/mdeditor.css +5 -0
- package/dist/mdeditor.js +2382 -1031
- package/dist/mdeditor.min.css +1 -1
- package/dist/mdeditor.min.js +1 -1
- package/esm/Editor.factory.js +85 -11
- package/esm/Types.d.ts +6 -0
- package/esm/commands/index.cn.d.ts +2 -1
- package/esm/commands/index.cn.js +2 -2
- package/esm/commands/index.d.ts +2 -2
- package/esm/commands/index.js +2 -2
- package/esm/commands/list.d.ts +1 -1
- package/esm/commands/list.js +9 -3
- package/esm/components/TextArea/Markdown.common.d.ts +3 -5
- package/esm/components/TextArea/Markdown.common.js +2 -56
- package/esm/components/TextArea/Markdown.d.ts +3 -5
- package/esm/components/TextArea/Markdown.factory.d.ts +6 -0
- package/esm/components/TextArea/Markdown.factory.js +138 -0
- package/esm/components/TextArea/Markdown.js +2 -63
- package/esm/components/TextArea/factory.js +6 -2
- package/esm/components/TextArea/index.css +5 -0
- package/esm/components/TextArea/index.less +5 -0
- package/esm/components/TextArea/splitBlocks.d.ts +13 -0
- package/esm/components/TextArea/splitBlocks.js +111 -0
- package/esm/package.json +1 -0
- package/lib/Editor.factory.js +93 -12
- package/lib/Types.d.ts +6 -0
- package/lib/commands/index.cn.d.ts +2 -1
- package/lib/commands/index.cn.js +8 -1
- package/lib/commands/index.d.ts +2 -2
- package/lib/commands/index.js +6 -0
- package/lib/commands/list.d.ts +1 -1
- package/lib/commands/list.js +5 -1
- package/lib/components/TextArea/Markdown.common.d.ts +3 -5
- package/lib/components/TextArea/Markdown.common.js +3 -60
- package/lib/components/TextArea/Markdown.d.ts +3 -5
- package/lib/components/TextArea/Markdown.factory.d.ts +6 -0
- package/lib/components/TextArea/Markdown.factory.js +148 -0
- package/lib/components/TextArea/Markdown.js +3 -67
- package/lib/components/TextArea/factory.js +6 -2
- package/lib/components/TextArea/index.less +5 -0
- package/lib/components/TextArea/splitBlocks.d.ts +13 -0
- package/lib/components/TextArea/splitBlocks.js +117 -0
- package/markdown-editor.css +5 -0
- package/package.json +18 -12
- package/src/Editor.factory.tsx +83 -4
- package/src/Types.ts +6 -0
- package/src/commands/index.cn.ts +2 -0
- package/src/commands/index.ts +2 -1
- package/src/commands/list.tsx +11 -2
- package/src/components/TextArea/Markdown.common.tsx +3 -46
- package/src/components/TextArea/Markdown.factory.tsx +132 -0
- package/src/components/TextArea/Markdown.tsx +3 -55
- package/src/components/TextArea/factory.tsx +7 -4
- package/src/components/TextArea/index.less +5 -0
- package/src/components/TextArea/splitBlocks.ts +114 -0
|
@@ -1,75 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault")["default"];
|
|
4
|
-
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard")["default"];
|
|
5
4
|
Object.defineProperty(exports, "__esModule", {
|
|
6
5
|
value: true
|
|
7
6
|
});
|
|
8
|
-
exports["default"] =
|
|
9
|
-
var _taggedTemplateLiteral2 = _interopRequireDefault(require("@babel/runtime/helpers/taggedTemplateLiteral"));
|
|
10
|
-
var _react = _interopRequireWildcard(require("react"));
|
|
11
|
-
var _rehype = require("rehype");
|
|
7
|
+
exports["default"] = void 0;
|
|
12
8
|
var _rehypePrismPlus = _interopRequireDefault(require("rehype-prism-plus"));
|
|
13
|
-
var
|
|
14
|
-
var
|
|
15
|
-
var _templateObject;
|
|
16
|
-
function html2Escape(sHtml) {
|
|
17
|
-
return sHtml
|
|
18
|
-
// .replace(/```(\w+)?([\s\S]*?)(\s.+)?```/g, (str: string) => {
|
|
19
|
-
// return str.replace(
|
|
20
|
-
// /[<&"]/g,
|
|
21
|
-
// (c: string) => (({ '<': '<', '>': '>', '&': '&', '"': '"' } as Record<string, string>)[c]),
|
|
22
|
-
// );
|
|
23
|
-
// })
|
|
24
|
-
.replace(/[<&"]/g, function (c) {
|
|
25
|
-
return {
|
|
26
|
-
'<': '<',
|
|
27
|
-
'>': '>',
|
|
28
|
-
'&': '&',
|
|
29
|
-
'"': '"'
|
|
30
|
-
}[c];
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
function Markdown(props) {
|
|
34
|
-
var prefixCls = props.prefixCls;
|
|
35
|
-
var _useContext = (0, _react.useContext)(_Context.EditorContext),
|
|
36
|
-
_useContext$markdown = _useContext.markdown,
|
|
37
|
-
markdown = _useContext$markdown === void 0 ? '' : _useContext$markdown,
|
|
38
|
-
highlightEnable = _useContext.highlightEnable,
|
|
39
|
-
dispatch = _useContext.dispatch;
|
|
40
|
-
var preRef = /*#__PURE__*/_react["default"].createRef();
|
|
41
|
-
(0, _react.useEffect)(function () {
|
|
42
|
-
if (preRef.current && dispatch) {
|
|
43
|
-
dispatch({
|
|
44
|
-
textareaPre: preRef.current
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
48
|
-
}, []);
|
|
49
|
-
if (!markdown) {
|
|
50
|
-
return /*#__PURE__*/(0, _jsxRuntime.jsx)("pre", {
|
|
51
|
-
ref: preRef,
|
|
52
|
-
className: "".concat(prefixCls, "-text-pre wmde-markdown-color")
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
var mdStr = "<pre class=\"language-markdown ".concat(prefixCls, "-text-pre wmde-markdown-color\"><code class=\"language-markdown\">").concat(html2Escape(String.raw(_templateObject || (_templateObject = (0, _taggedTemplateLiteral2["default"])(["", ""])), markdown)), "\n</code></pre>");
|
|
56
|
-
if (highlightEnable) {
|
|
57
|
-
try {
|
|
58
|
-
mdStr = (0, _rehype.rehype)().data('settings', {
|
|
59
|
-
fragment: true
|
|
60
|
-
})
|
|
61
|
-
// https://github.com/uiwjs/react-md-editor/issues/593
|
|
62
|
-
// @ts-ignore
|
|
63
|
-
.use(_rehypePrismPlus["default"], {
|
|
64
|
-
ignoreMissing: true
|
|
65
|
-
}).processSync(mdStr).toString();
|
|
66
|
-
} catch (error) {}
|
|
67
|
-
}
|
|
68
|
-
return /*#__PURE__*/_react["default"].createElement('div', {
|
|
69
|
-
className: 'wmde-markdown-color',
|
|
70
|
-
dangerouslySetInnerHTML: {
|
|
71
|
-
__html: mdStr || ''
|
|
72
|
-
}
|
|
73
|
-
});
|
|
74
|
-
}
|
|
9
|
+
var _Markdown = require("./Markdown.factory");
|
|
10
|
+
var _default = exports["default"] = (0, _Markdown.createMarkdown)(_rehypePrismPlus["default"]);
|
|
75
11
|
module.exports = exports.default;
|
|
@@ -63,6 +63,10 @@ function createTextArea(options) {
|
|
|
63
63
|
WebkitTextFillColor: 'initial',
|
|
64
64
|
overflow: 'auto'
|
|
65
65
|
};
|
|
66
|
+
var inputProps = (0, _objectSpread2["default"])({}, otherProps);
|
|
67
|
+
if (!inputProps['aria-label'] && !inputProps['aria-labelledby'] && !inputProps.id && !inputProps.title) {
|
|
68
|
+
inputProps['aria-label'] = 'Markdown editor';
|
|
69
|
+
}
|
|
66
70
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
|
|
67
71
|
ref: warp,
|
|
68
72
|
className: "".concat(prefixCls, "-area ").concat(className || ''),
|
|
@@ -72,7 +76,7 @@ function createTextArea(options) {
|
|
|
72
76
|
style: useMinHeight ? {
|
|
73
77
|
minHeight: minHeight
|
|
74
78
|
} : undefined,
|
|
75
|
-
children: renderTextarea ? (/*#__PURE__*/_react["default"].cloneElement(renderTextarea((0, _objectSpread2["default"])((0, _objectSpread2["default"])({},
|
|
79
|
+
children: renderTextarea ? (/*#__PURE__*/_react["default"].cloneElement(renderTextarea((0, _objectSpread2["default"])((0, _objectSpread2["default"])({}, inputProps), {}, {
|
|
76
80
|
value: markdown,
|
|
77
81
|
autoComplete: 'off',
|
|
78
82
|
spellCheck: 'false',
|
|
@@ -98,7 +102,7 @@ function createTextArea(options) {
|
|
|
98
102
|
prefixCls: prefixCls
|
|
99
103
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_Textarea["default"], (0, _objectSpread2["default"])((0, _objectSpread2["default"])({
|
|
100
104
|
prefixCls: prefixCls
|
|
101
|
-
},
|
|
105
|
+
}, inputProps), {}, {
|
|
102
106
|
style: textStyle
|
|
103
107
|
}))]
|
|
104
108
|
})
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Splits markdown into blocks that Prism's markdown grammar highlights the same way
|
|
3
|
+
* on their own as it does in the full document, so unchanged blocks can be cached.
|
|
4
|
+
* Joining the returned blocks always yields the original string.
|
|
5
|
+
*
|
|
6
|
+
* A block ends where a run of blank lines starts, so each block keeps its leading blank
|
|
7
|
+
* lines (indented code needs them). No split happens inside a ``` fence, front matter or
|
|
8
|
+
* multiline markup or link references, or right before a `---` line, which Prism
|
|
9
|
+
* would read as front matter. Whitespace-only lines stay in their block because
|
|
10
|
+
* Prism's inline emphasis can span them.
|
|
11
|
+
* Fences follow Prism's rule, not CommonMark's, so the split matches what Prism sees.
|
|
12
|
+
*/
|
|
13
|
+
export declare function splitBlocks(markdown: string): string[];
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.splitBlocks = splitBlocks;
|
|
7
|
+
var FENCE_OPEN = /^```/;
|
|
8
|
+
var FENCE_CLOSE = /^```\r?\n?$/;
|
|
9
|
+
var BLANK_LINE = /^[ \t]*\r?\n?$/;
|
|
10
|
+
var FRONT_MATTER_FENCE = /^---\r?\n?$/;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Splits markdown into blocks that Prism's markdown grammar highlights the same way
|
|
14
|
+
* on their own as it does in the full document, so unchanged blocks can be cached.
|
|
15
|
+
* Joining the returned blocks always yields the original string.
|
|
16
|
+
*
|
|
17
|
+
* A block ends where a run of blank lines starts, so each block keeps its leading blank
|
|
18
|
+
* lines (indented code needs them). No split happens inside a ``` fence, front matter or
|
|
19
|
+
* multiline markup or link references, or right before a `---` line, which Prism
|
|
20
|
+
* would read as front matter. Whitespace-only lines stay in their block because
|
|
21
|
+
* Prism's inline emphasis can span them.
|
|
22
|
+
* Fences follow Prism's rule, not CommonMark's, so the split matches what Prism sees.
|
|
23
|
+
*/
|
|
24
|
+
function splitBlocks(markdown) {
|
|
25
|
+
var lines = markdown.match(/[^\n]*\n|[^\n]+$/g) || [];
|
|
26
|
+
var blocks = [];
|
|
27
|
+
var current = '';
|
|
28
|
+
var pendingSplit = -1;
|
|
29
|
+
var inFence = false;
|
|
30
|
+
var inFrontMatter = /^(?:\s*[\r\n])?---(?:\r?\n|$)/.test(markdown);
|
|
31
|
+
var frontMatterStarted = false;
|
|
32
|
+
var syntax = {
|
|
33
|
+
end: '',
|
|
34
|
+
quote: '',
|
|
35
|
+
brackets: 0,
|
|
36
|
+
subset: 0
|
|
37
|
+
};
|
|
38
|
+
var previousIsBlank = true;
|
|
39
|
+
lines.forEach(function (line) {
|
|
40
|
+
var isBlank = BLANK_LINE.test(line);
|
|
41
|
+
var canSplit = !inFence && !inFrontMatter && !syntax.end && !syntax.brackets;
|
|
42
|
+
if (/^\r?\n$/.test(line) && !previousIsBlank && canSplit) {
|
|
43
|
+
pendingSplit = current.length;
|
|
44
|
+
}
|
|
45
|
+
if (!isBlank && pendingSplit > 0) {
|
|
46
|
+
if (!FRONT_MATTER_FENCE.test(line)) {
|
|
47
|
+
blocks.push(current.slice(0, pendingSplit));
|
|
48
|
+
current = current.slice(pendingSplit);
|
|
49
|
+
}
|
|
50
|
+
pendingSplit = -1;
|
|
51
|
+
}
|
|
52
|
+
current += line;
|
|
53
|
+
previousIsBlank = isBlank;
|
|
54
|
+
if (inFrontMatter) {
|
|
55
|
+
if (FRONT_MATTER_FENCE.test(line)) {
|
|
56
|
+
inFrontMatter = !frontMatterStarted;
|
|
57
|
+
frontMatterStarted = true;
|
|
58
|
+
}
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (inFence) {
|
|
62
|
+
inFence = !FENCE_CLOSE.test(line);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (!syntax.end && FENCE_OPEN.test(line)) {
|
|
66
|
+
inFence = true;
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
updateSyntax(line, syntax);
|
|
70
|
+
});
|
|
71
|
+
if (current) {
|
|
72
|
+
blocks.push(current);
|
|
73
|
+
}
|
|
74
|
+
return blocks;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Keep potential multiline Prism tokens together. Unclosed syntax conservatively
|
|
78
|
+
// keeps the rest of the document in one block until the user closes it.
|
|
79
|
+
function updateSyntax(line, state) {
|
|
80
|
+
var reference = false;
|
|
81
|
+
for (var i = 0; i < line.length; i++) {
|
|
82
|
+
var _char = line[i];
|
|
83
|
+
if (state.end && state.end !== '>') {
|
|
84
|
+
var end = line.indexOf(state.end, i);
|
|
85
|
+
if (end === -1) return;
|
|
86
|
+
i = end + state.end.length - 1;
|
|
87
|
+
state.end = '';
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (state.end === '>') {
|
|
91
|
+
if (state.quote) {
|
|
92
|
+
if (_char === state.quote) state.quote = '';
|
|
93
|
+
} else if (_char === '"' || _char === "'") {
|
|
94
|
+
state.quote = _char;
|
|
95
|
+
} else if (_char === '[') {
|
|
96
|
+
state.subset++;
|
|
97
|
+
} else if (_char === ']' && state.subset) {
|
|
98
|
+
state.subset--;
|
|
99
|
+
} else if (_char === '>' && !state.subset) {
|
|
100
|
+
state.end = '';
|
|
101
|
+
}
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (_char === '\\') {
|
|
105
|
+
i++;
|
|
106
|
+
} else if (_char === '<' && /[a-z!?/]/i.test(line[i + 1] || '')) {
|
|
107
|
+
state.end = line.startsWith('<!--', i) ? '-->' : line.startsWith('<![CDATA[', i) ? ']]>' : line.startsWith('<?', i) ? '?>' : '>';
|
|
108
|
+
} else if (_char === '[') {
|
|
109
|
+
state.brackets++;
|
|
110
|
+
} else if (_char === ']') {
|
|
111
|
+
state.brackets = Math.max(0, state.brackets - 1);
|
|
112
|
+
reference = line[i + 1] === ':';
|
|
113
|
+
} else if (reference && (_char === '"' || _char === "'" || _char === '(')) {
|
|
114
|
+
state.end = _char === '(' ? ')' : _char;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
package/markdown-editor.css
CHANGED
|
@@ -131,6 +131,11 @@
|
|
|
131
131
|
.w-md-editor-text-pre .token.bold {
|
|
132
132
|
font-weight: unset !important;
|
|
133
133
|
}
|
|
134
|
+
.w-md-editor-text-pre .token.list,
|
|
135
|
+
.w-md-editor-text-pre .token.table {
|
|
136
|
+
display: inline;
|
|
137
|
+
font-size: inherit;
|
|
138
|
+
}
|
|
134
139
|
.w-md-editor-text-pre .token.title {
|
|
135
140
|
line-height: unset !important;
|
|
136
141
|
font-size: unset !important;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uiw/react-md-editor",
|
|
3
|
-
"version": "4.1.
|
|
3
|
+
"version": "4.1.7",
|
|
4
4
|
"description": "A markdown editor with preview, implemented with React.js and TypeScript.",
|
|
5
5
|
"homepage": "https://uiwjs.github.io/react-md-editor/",
|
|
6
6
|
"funding": "https://jaywcjlove.github.io/#/sponsor",
|
|
@@ -12,29 +12,34 @@
|
|
|
12
12
|
"./package.json": "./package.json",
|
|
13
13
|
"./markdown-editor.css": "./markdown-editor.css",
|
|
14
14
|
".": {
|
|
15
|
-
"import": "./esm/index.js",
|
|
16
15
|
"types": "./lib/index.d.ts",
|
|
17
|
-
"
|
|
16
|
+
"import": "./esm/index.js",
|
|
17
|
+
"require": "./lib/index.js",
|
|
18
|
+
"default": "./esm/index.js"
|
|
18
19
|
},
|
|
19
20
|
"./common": {
|
|
20
|
-
"import": "./esm/index.common.js",
|
|
21
21
|
"types": "./lib/index.common.d.ts",
|
|
22
|
-
"
|
|
22
|
+
"import": "./esm/index.common.js",
|
|
23
|
+
"require": "./lib/index.common.js",
|
|
24
|
+
"default": "./esm/index.common.js"
|
|
23
25
|
},
|
|
24
26
|
"./nohighlight": {
|
|
25
|
-
"import": "./esm/index.nohighlight.js",
|
|
26
27
|
"types": "./lib/index.nohighlight.d.ts",
|
|
27
|
-
"
|
|
28
|
+
"import": "./esm/index.nohighlight.js",
|
|
29
|
+
"require": "./lib/index.nohighlight.js",
|
|
30
|
+
"default": "./esm/index.nohighlight.js"
|
|
28
31
|
},
|
|
29
32
|
"./commands": {
|
|
30
|
-
"import": "./esm/commands/index.js",
|
|
31
33
|
"types": "./lib/commands/index.d.ts",
|
|
32
|
-
"
|
|
34
|
+
"import": "./esm/commands/index.js",
|
|
35
|
+
"require": "./lib/commands/index.js",
|
|
36
|
+
"default": "./esm/commands/index.js"
|
|
33
37
|
},
|
|
34
38
|
"./commands-cn": {
|
|
35
|
-
"import": "./esm/commands/index.cn.js",
|
|
36
39
|
"types": "./lib/commands/index.cn.d.ts",
|
|
37
|
-
"
|
|
40
|
+
"import": "./esm/commands/index.cn.js",
|
|
41
|
+
"require": "./lib/commands/index.cn.js",
|
|
42
|
+
"default": "./esm/commands/index.cn.js"
|
|
38
43
|
}
|
|
39
44
|
},
|
|
40
45
|
"repository": {
|
|
@@ -79,5 +84,6 @@
|
|
|
79
84
|
"uiw",
|
|
80
85
|
"uiwjs",
|
|
81
86
|
"code"
|
|
82
|
-
]
|
|
87
|
+
],
|
|
88
|
+
"types": "lib/index.d.ts"
|
|
83
89
|
}
|
package/src/Editor.factory.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useEffect, useReducer, useMemo, useRef, useImperativeHandle } from 'react';
|
|
1
|
+
import React, { useEffect, useLayoutEffect, useReducer, useMemo, useRef, useImperativeHandle } from 'react';
|
|
2
2
|
import { ToolbarVisibility } from './components/Toolbar/';
|
|
3
3
|
import DragBar from './components/DragBar/';
|
|
4
4
|
import { getCommands, getExtraCommands, type ICommand, type TextState, TextAreaCommandOrchestrator } from './commands/';
|
|
@@ -12,6 +12,45 @@ function setGroupPopFalse(data: Record<string, boolean> = {}) {
|
|
|
12
12
|
return data;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
const DEFAULT_PREVIEW_OPTIONS: NonNullable<MDEditorProps['previewOptions']> = {};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Lets the preview render after the textarea on React 18+, so typing in a large
|
|
19
|
+
* document stays responsive. Older React versions render it right away, as before.
|
|
20
|
+
*/
|
|
21
|
+
const useDeferredValue: <T>(value: T) => T = (React as any).useDeferredValue || ((value) => value);
|
|
22
|
+
|
|
23
|
+
const useIsomorphicLayoutEffect = typeof window === 'undefined' ? useEffect : useLayoutEffect;
|
|
24
|
+
|
|
25
|
+
const SLOW_PREVIEW_MS = 50;
|
|
26
|
+
const MAX_PREVIEW_DELAY_MS = 500;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Keeps the preview in step with every keystroke while it renders fast. Once a preview
|
|
30
|
+
* render takes longer than SLOW_PREVIEW_MS, it waits for a pause in typing as long as
|
|
31
|
+
* that render (at most MAX_PREVIEW_DELAY_MS) before updating.
|
|
32
|
+
* https://github.com/uiwjs/react-md-editor/issues/620
|
|
33
|
+
*/
|
|
34
|
+
function usePreviewSource(markdown: string, previewRenderMs: React.MutableRefObject<number>) {
|
|
35
|
+
const [, rerender] = useReducer((count: number) => count + 1, 0);
|
|
36
|
+
const shownRef = useRef(markdown);
|
|
37
|
+
if (previewRenderMs.current < SLOW_PREVIEW_MS) {
|
|
38
|
+
shownRef.current = markdown;
|
|
39
|
+
}
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
if (shownRef.current === markdown) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const delay = Math.min(previewRenderMs.current, MAX_PREVIEW_DELAY_MS);
|
|
45
|
+
const timer = setTimeout(() => {
|
|
46
|
+
shownRef.current = markdown;
|
|
47
|
+
rerender();
|
|
48
|
+
}, delay);
|
|
49
|
+
return () => clearTimeout(timer);
|
|
50
|
+
}, [markdown, previewRenderMs]);
|
|
51
|
+
return shownRef.current;
|
|
52
|
+
}
|
|
53
|
+
|
|
15
54
|
export interface RefMDEditor extends ContextStore {}
|
|
16
55
|
|
|
17
56
|
type PreviewComponent = React.ComponentType<any>;
|
|
@@ -25,6 +64,15 @@ export function createMDEditor<
|
|
|
25
64
|
const PreviewComponent = MarkdownPreview as React.ComponentType<any>;
|
|
26
65
|
const TextAreaComponent = TextArea as React.ComponentType<any>;
|
|
27
66
|
|
|
67
|
+
function TimedPreview(props: { renderMsRef: React.MutableRefObject<number>; [key: string]: any }) {
|
|
68
|
+
const { renderMsRef, ...previewProps } = props;
|
|
69
|
+
const renderStart = performance.now();
|
|
70
|
+
useIsomorphicLayoutEffect(() => {
|
|
71
|
+
renderMsRef.current = performance.now() - renderStart;
|
|
72
|
+
});
|
|
73
|
+
return <PreviewComponent {...previewProps} />;
|
|
74
|
+
}
|
|
75
|
+
|
|
28
76
|
const InternalMDEditor = React.forwardRef<RefMDEditor, MDEditorProps>(
|
|
29
77
|
(props: MDEditorProps, ref: React.ForwardedRef<RefMDEditor>) => {
|
|
30
78
|
const {
|
|
@@ -42,8 +90,20 @@ export function createMDEditor<
|
|
|
42
90
|
preview: previewType = 'live',
|
|
43
91
|
fullscreen = false,
|
|
44
92
|
overflow = true,
|
|
45
|
-
previewOptions =
|
|
93
|
+
previewOptions = DEFAULT_PREVIEW_OPTIONS,
|
|
46
94
|
textareaProps,
|
|
95
|
+
id,
|
|
96
|
+
name,
|
|
97
|
+
required,
|
|
98
|
+
disabled,
|
|
99
|
+
readOnly,
|
|
100
|
+
placeholder,
|
|
101
|
+
'aria-label': ariaLabel,
|
|
102
|
+
'aria-labelledby': ariaLabelledBy,
|
|
103
|
+
'aria-describedby': ariaDescribedBy,
|
|
104
|
+
'aria-invalid': ariaInvalid,
|
|
105
|
+
'aria-required': ariaRequired,
|
|
106
|
+
'aria-errormessage': ariaErrorMessage,
|
|
47
107
|
maxHeight = 1200,
|
|
48
108
|
minHeight = 100,
|
|
49
109
|
autoFocus,
|
|
@@ -192,13 +252,20 @@ export function createMDEditor<
|
|
|
192
252
|
|
|
193
253
|
const previewClassName = `${prefixCls}-preview ${previewOptions.className || ''}`;
|
|
194
254
|
const handlePreviewScroll = (e: React.UIEvent<HTMLDivElement, UIEvent>) => handleScroll(e, 'preview');
|
|
255
|
+
const previewRenderMs = useRef(0);
|
|
256
|
+
const previewMarkdown = useDeferredValue(usePreviewSource(state.markdown || '', previewRenderMs));
|
|
195
257
|
let mdPreview = useMemo(
|
|
196
258
|
() => (
|
|
197
259
|
<div ref={previewRef} className={previewClassName}>
|
|
198
|
-
<
|
|
260
|
+
<TimedPreview
|
|
261
|
+
{...previewOptions}
|
|
262
|
+
renderMsRef={previewRenderMs}
|
|
263
|
+
onScroll={handlePreviewScroll}
|
|
264
|
+
source={previewMarkdown}
|
|
265
|
+
/>
|
|
199
266
|
</div>
|
|
200
267
|
),
|
|
201
|
-
[previewClassName, previewOptions,
|
|
268
|
+
[previewClassName, previewOptions, previewMarkdown],
|
|
202
269
|
);
|
|
203
270
|
const preview = components?.preview && components?.preview(state.markdown || '', state, dispatch);
|
|
204
271
|
if (preview && React.isValidElement(preview)) {
|
|
@@ -244,6 +311,18 @@ export function createMDEditor<
|
|
|
244
311
|
className={`${prefixCls}-input`}
|
|
245
312
|
prefixCls={prefixCls}
|
|
246
313
|
autoFocus={autoFocus}
|
|
314
|
+
id={id}
|
|
315
|
+
name={name}
|
|
316
|
+
required={required}
|
|
317
|
+
disabled={disabled}
|
|
318
|
+
readOnly={readOnly}
|
|
319
|
+
placeholder={placeholder}
|
|
320
|
+
aria-label={ariaLabel}
|
|
321
|
+
aria-labelledby={ariaLabelledBy}
|
|
322
|
+
aria-describedby={ariaDescribedBy}
|
|
323
|
+
aria-invalid={ariaInvalid}
|
|
324
|
+
aria-required={ariaRequired}
|
|
325
|
+
aria-errormessage={ariaErrorMessage}
|
|
247
326
|
{...textareaProps}
|
|
248
327
|
onChange={changeHandle}
|
|
249
328
|
renderTextarea={components?.textarea || renderTextarea}
|
package/src/Types.ts
CHANGED
|
@@ -21,6 +21,12 @@ export interface MDEditorProps extends Omit<React.HTMLAttributes<HTMLDivElement>
|
|
|
21
21
|
* The Markdown value.
|
|
22
22
|
*/
|
|
23
23
|
value?: string;
|
|
24
|
+
/** Native textarea form attributes. */
|
|
25
|
+
name?: React.TextareaHTMLAttributes<HTMLTextAreaElement>['name'];
|
|
26
|
+
required?: React.TextareaHTMLAttributes<HTMLTextAreaElement>['required'];
|
|
27
|
+
disabled?: React.TextareaHTMLAttributes<HTMLTextAreaElement>['disabled'];
|
|
28
|
+
readOnly?: React.TextareaHTMLAttributes<HTMLTextAreaElement>['readOnly'];
|
|
29
|
+
placeholder?: React.TextareaHTMLAttributes<HTMLTextAreaElement>['placeholder'];
|
|
24
30
|
/**
|
|
25
31
|
* Event handler for the `onChange` event.
|
|
26
32
|
*/
|
package/src/commands/index.cn.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { italic as italicInit } from './italic';
|
|
|
12
12
|
import { link as linkInit } from './link';
|
|
13
13
|
import {
|
|
14
14
|
checkedListCommand as checkedListCommandInit,
|
|
15
|
+
makeList,
|
|
15
16
|
orderedListCommand as orderedListCommandInit,
|
|
16
17
|
unorderedListCommand as unorderedListCommandInit,
|
|
17
18
|
} from './list';
|
|
@@ -177,6 +178,7 @@ export {
|
|
|
177
178
|
unorderedListCommand,
|
|
178
179
|
orderedListCommand,
|
|
179
180
|
checkedListCommand,
|
|
181
|
+
makeList,
|
|
180
182
|
table,
|
|
181
183
|
issue,
|
|
182
184
|
help,
|
package/src/commands/index.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { hr } from './hr';
|
|
|
11
11
|
import { image } from './image';
|
|
12
12
|
import { italic } from './italic';
|
|
13
13
|
import { link } from './link';
|
|
14
|
-
import { checkedListCommand, orderedListCommand, unorderedListCommand } from './list';
|
|
14
|
+
import { checkedListCommand, makeList, orderedListCommand, unorderedListCommand } from './list';
|
|
15
15
|
import { codeEdit, codeLive, codePreview } from './preview';
|
|
16
16
|
import { quote } from './quote';
|
|
17
17
|
import { strikethrough } from './strikeThrough';
|
|
@@ -210,6 +210,7 @@ export {
|
|
|
210
210
|
unorderedListCommand,
|
|
211
211
|
orderedListCommand,
|
|
212
212
|
checkedListCommand,
|
|
213
|
+
makeList,
|
|
213
214
|
table,
|
|
214
215
|
issue,
|
|
215
216
|
help,
|
package/src/commands/list.tsx
CHANGED
|
@@ -2,14 +2,23 @@ import React from 'react';
|
|
|
2
2
|
import { type ICommand, type ExecuteState, TextAreaTextApi } from './';
|
|
3
3
|
import {
|
|
4
4
|
selectWord,
|
|
5
|
+
selectLine,
|
|
5
6
|
getBreaksNeededForEmptyLineBefore,
|
|
6
7
|
getBreaksNeededForEmptyLineAfter,
|
|
7
8
|
insertBeforeEachLine,
|
|
8
9
|
AlterLineFunction,
|
|
9
10
|
} from '../utils/markdownUtils';
|
|
10
11
|
|
|
11
|
-
export const makeList = (
|
|
12
|
-
|
|
12
|
+
export const makeList = (
|
|
13
|
+
state: ExecuteState,
|
|
14
|
+
api: TextAreaTextApi,
|
|
15
|
+
insertBefore: string | AlterLineFunction,
|
|
16
|
+
selectionStrategy: 'word' | 'line' = 'word',
|
|
17
|
+
) => {
|
|
18
|
+
const newSelectionRange =
|
|
19
|
+
selectionStrategy === 'line'
|
|
20
|
+
? selectLine({ text: state.text, selection: state.selection })
|
|
21
|
+
: selectWord({ text: state.text, selection: state.selection, prefix: state.command.prefix! });
|
|
13
22
|
const state1 = api.setSelectionRange(newSelectionRange);
|
|
14
23
|
|
|
15
24
|
const breaksBeforeCount = getBreaksNeededForEmptyLineBefore(state1.text, state1.selection.start);
|
|
@@ -1,49 +1,6 @@
|
|
|
1
|
-
import React, { useContext, useEffect } from 'react';
|
|
2
|
-
import { rehype } from 'rehype';
|
|
3
1
|
import rehypePrism from 'rehype-prism-plus/common';
|
|
4
|
-
import {
|
|
5
|
-
import { EditorContext } from '../../Context';
|
|
2
|
+
import { createMarkdown } from './Markdown.factory';
|
|
6
3
|
|
|
7
|
-
|
|
8
|
-
return sHtml.replace(
|
|
9
|
-
/[<&"]/g,
|
|
10
|
-
(c: string) => (({ '<': '<', '>': '>', '&': '&', '"': '"' }) as Record<string, string>)[c],
|
|
11
|
-
);
|
|
12
|
-
}
|
|
4
|
+
export type { MarkdownProps } from './Markdown.factory';
|
|
13
5
|
|
|
14
|
-
export
|
|
15
|
-
|
|
16
|
-
export default function Markdown(props: MarkdownProps) {
|
|
17
|
-
const { prefixCls } = props;
|
|
18
|
-
const { markdown = '', highlightEnable, dispatch } = useContext(EditorContext);
|
|
19
|
-
const preRef = React.createRef<HTMLPreElement>();
|
|
20
|
-
useEffect(() => {
|
|
21
|
-
if (preRef.current && dispatch) {
|
|
22
|
-
dispatch({ textareaPre: preRef.current });
|
|
23
|
-
}
|
|
24
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
25
|
-
}, []);
|
|
26
|
-
if (!markdown) {
|
|
27
|
-
return <pre ref={preRef} className={`${prefixCls}-text-pre wmde-markdown-color`} />;
|
|
28
|
-
}
|
|
29
|
-
let mdStr = `<pre class="language-markdown ${prefixCls}-text-pre wmde-markdown-color"><code class="language-markdown">${html2Escape(
|
|
30
|
-
String.raw`${markdown}`,
|
|
31
|
-
)}\n</code></pre>`;
|
|
32
|
-
|
|
33
|
-
if (highlightEnable) {
|
|
34
|
-
try {
|
|
35
|
-
mdStr = rehype()
|
|
36
|
-
.data('settings', { fragment: true })
|
|
37
|
-
// https://github.com/uiwjs/react-md-editor/issues/593
|
|
38
|
-
// @ts-ignore
|
|
39
|
-
.use(rehypePrism, { ignoreMissing: true })
|
|
40
|
-
.processSync(mdStr)
|
|
41
|
-
.toString();
|
|
42
|
-
} catch (error) {}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return React.createElement('div', {
|
|
46
|
-
className: 'wmde-markdown-color',
|
|
47
|
-
dangerouslySetInnerHTML: { __html: mdStr || '' },
|
|
48
|
-
});
|
|
49
|
-
}
|
|
6
|
+
export default createMarkdown(rehypePrism);
|