@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.
Files changed (56) hide show
  1. package/README.md +16 -1
  2. package/dist/mdeditor.css +5 -0
  3. package/dist/mdeditor.js +2382 -1031
  4. package/dist/mdeditor.min.css +1 -1
  5. package/dist/mdeditor.min.js +1 -1
  6. package/esm/Editor.factory.js +85 -11
  7. package/esm/Types.d.ts +6 -0
  8. package/esm/commands/index.cn.d.ts +2 -1
  9. package/esm/commands/index.cn.js +2 -2
  10. package/esm/commands/index.d.ts +2 -2
  11. package/esm/commands/index.js +2 -2
  12. package/esm/commands/list.d.ts +1 -1
  13. package/esm/commands/list.js +9 -3
  14. package/esm/components/TextArea/Markdown.common.d.ts +3 -5
  15. package/esm/components/TextArea/Markdown.common.js +2 -56
  16. package/esm/components/TextArea/Markdown.d.ts +3 -5
  17. package/esm/components/TextArea/Markdown.factory.d.ts +6 -0
  18. package/esm/components/TextArea/Markdown.factory.js +138 -0
  19. package/esm/components/TextArea/Markdown.js +2 -63
  20. package/esm/components/TextArea/factory.js +6 -2
  21. package/esm/components/TextArea/index.css +5 -0
  22. package/esm/components/TextArea/index.less +5 -0
  23. package/esm/components/TextArea/splitBlocks.d.ts +13 -0
  24. package/esm/components/TextArea/splitBlocks.js +111 -0
  25. package/esm/package.json +1 -0
  26. package/lib/Editor.factory.js +93 -12
  27. package/lib/Types.d.ts +6 -0
  28. package/lib/commands/index.cn.d.ts +2 -1
  29. package/lib/commands/index.cn.js +8 -1
  30. package/lib/commands/index.d.ts +2 -2
  31. package/lib/commands/index.js +6 -0
  32. package/lib/commands/list.d.ts +1 -1
  33. package/lib/commands/list.js +5 -1
  34. package/lib/components/TextArea/Markdown.common.d.ts +3 -5
  35. package/lib/components/TextArea/Markdown.common.js +3 -60
  36. package/lib/components/TextArea/Markdown.d.ts +3 -5
  37. package/lib/components/TextArea/Markdown.factory.d.ts +6 -0
  38. package/lib/components/TextArea/Markdown.factory.js +148 -0
  39. package/lib/components/TextArea/Markdown.js +3 -67
  40. package/lib/components/TextArea/factory.js +6 -2
  41. package/lib/components/TextArea/index.less +5 -0
  42. package/lib/components/TextArea/splitBlocks.d.ts +13 -0
  43. package/lib/components/TextArea/splitBlocks.js +117 -0
  44. package/markdown-editor.css +5 -0
  45. package/package.json +18 -12
  46. package/src/Editor.factory.tsx +83 -4
  47. package/src/Types.ts +6 -0
  48. package/src/commands/index.cn.ts +2 -0
  49. package/src/commands/index.ts +2 -1
  50. package/src/commands/list.tsx +11 -2
  51. package/src/components/TextArea/Markdown.common.tsx +3 -46
  52. package/src/components/TextArea/Markdown.factory.tsx +132 -0
  53. package/src/components/TextArea/Markdown.tsx +3 -55
  54. package/src/components/TextArea/factory.tsx +7 -4
  55. package/src/components/TextArea/index.less +5 -0
  56. 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"] = Markdown;
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 _Context = require("../../Context");
14
- var _jsxRuntime = require("react/jsx-runtime");
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) => (({ '<': '&lt;', '>': '&gt;', '&': '&amp;', '"': '&quot;' } as Record<string, string>)[c]),
22
- // );
23
- // })
24
- .replace(/[<&"]/g, function (c) {
25
- return {
26
- '<': '&lt;',
27
- '>': '&gt;',
28
- '&': '&amp;',
29
- '"': '&quot;'
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"])({}, otherProps), {}, {
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
- }, otherProps), {}, {
105
+ }, inputProps), {}, {
102
106
  style: textStyle
103
107
  }))]
104
108
  })
@@ -119,6 +119,11 @@
119
119
  .token.bold {
120
120
  font-weight: unset !important;
121
121
  }
122
+ .token.list,
123
+ .token.table {
124
+ display: inline;
125
+ font-size: inherit;
126
+ }
122
127
  .token.title {
123
128
  line-height: unset !important;
124
129
  font-size: unset !important;
@@ -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
+ }
@@ -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.5",
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
- "require": "./lib/index.js"
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
- "require": "./lib/index.common.js"
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
- "require": "./lib/index.nohighlight.js"
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
- "require": "./lib/commands/index.js"
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
- "require": "./lib/commands/index.cn.js"
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
  }
@@ -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
- <PreviewComponent {...previewOptions} onScroll={handlePreviewScroll} source={state.markdown || ''} />
260
+ <TimedPreview
261
+ {...previewOptions}
262
+ renderMsRef={previewRenderMs}
263
+ onScroll={handlePreviewScroll}
264
+ source={previewMarkdown}
265
+ />
199
266
  </div>
200
267
  ),
201
- [previewClassName, previewOptions, state.markdown],
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
  */
@@ -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,
@@ -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,
@@ -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 = (state: ExecuteState, api: TextAreaTextApi, insertBefore: string | AlterLineFunction) => {
12
- const newSelectionRange = selectWord({ text: state.text, selection: state.selection, prefix: state.command.prefix! });
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 { type IProps } from '../../Types';
5
- import { EditorContext } from '../../Context';
2
+ import { createMarkdown } from './Markdown.factory';
6
3
 
7
- function html2Escape(sHtml: string) {
8
- return sHtml.replace(
9
- /[<&"]/g,
10
- (c: string) => (({ '<': '&lt;', '>': '&gt;', '&': '&amp;', '"': '&quot;' }) as Record<string, string>)[c],
11
- );
12
- }
4
+ export type { MarkdownProps } from './Markdown.factory';
13
5
 
14
- export interface MarkdownProps extends IProps, React.HTMLAttributes<HTMLPreElement> {}
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);