@uiw/react-md-editor 4.1.0 → 4.1.2

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.
@@ -9,7 +9,7 @@
9
9
  */
10
10
 
11
11
  /**!
12
- * @uiw/copy-to-clipboard v1.0.20
12
+ * @uiw/copy-to-clipboard v1.0.21
13
13
  * Copy to clipboard.
14
14
  *
15
15
  * Copyright (c) 2026 Kenny Wang
@@ -17,64 +17,79 @@ function setGroupPopFalse(data) {
17
17
  return data;
18
18
  }
19
19
  export function createMDEditor(options) {
20
- var {
21
- MarkdownPreview,
22
- TextArea
23
- } = options;
20
+ var MarkdownPreview = options.MarkdownPreview,
21
+ TextArea = options.TextArea;
24
22
  var PreviewComponent = MarkdownPreview;
25
23
  var TextAreaComponent = TextArea;
26
24
  var InternalMDEditor = /*#__PURE__*/React.forwardRef((props, ref) => {
27
25
  var _ref = props || {},
28
- {
29
- prefixCls = 'w-md-editor',
30
- className,
31
- value: propsValue,
32
- commands = getCommands(),
33
- commandsFilter,
34
- direction,
35
- extraCommands = getExtraCommands(),
36
- height = 200,
37
- enableScroll = true,
38
- visibleDragbar = typeof props.visiableDragbar === 'boolean' ? props.visiableDragbar : true,
39
- highlightEnable = true,
40
- preview: previewType = 'live',
41
- fullscreen = false,
42
- overflow = true,
43
- previewOptions = {},
44
- textareaProps,
45
- maxHeight = 1200,
46
- minHeight = 100,
47
- autoFocus,
48
- autoFocusEnd = false,
49
- tabSize = 2,
50
- defaultTabEnable = false,
51
- onChange,
52
- onStatistics,
53
- onHeightChange,
54
- hideToolbar,
55
- toolbarBottom = false,
56
- components,
57
- renderTextarea
58
- } = _ref,
26
+ _ref$prefixCls = _ref.prefixCls,
27
+ prefixCls = _ref$prefixCls === void 0 ? 'w-md-editor' : _ref$prefixCls,
28
+ className = _ref.className,
29
+ propsValue = _ref.value,
30
+ _ref$commands = _ref.commands,
31
+ commands = _ref$commands === void 0 ? getCommands() : _ref$commands,
32
+ commandsFilter = _ref.commandsFilter,
33
+ direction = _ref.direction,
34
+ _ref$extraCommands = _ref.extraCommands,
35
+ extraCommands = _ref$extraCommands === void 0 ? getExtraCommands() : _ref$extraCommands,
36
+ _ref$height = _ref.height,
37
+ height = _ref$height === void 0 ? 200 : _ref$height,
38
+ _ref$enableScroll = _ref.enableScroll,
39
+ enableScroll = _ref$enableScroll === void 0 ? true : _ref$enableScroll,
40
+ _ref$visibleDragbar = _ref.visibleDragbar,
41
+ visibleDragbar = _ref$visibleDragbar === void 0 ? typeof props.visiableDragbar === 'boolean' ? props.visiableDragbar : true : _ref$visibleDragbar,
42
+ _ref$highlightEnable = _ref.highlightEnable,
43
+ highlightEnable = _ref$highlightEnable === void 0 ? true : _ref$highlightEnable,
44
+ _ref$preview = _ref.preview,
45
+ previewType = _ref$preview === void 0 ? 'live' : _ref$preview,
46
+ _ref$fullscreen = _ref.fullscreen,
47
+ fullscreen = _ref$fullscreen === void 0 ? false : _ref$fullscreen,
48
+ _ref$overflow = _ref.overflow,
49
+ overflow = _ref$overflow === void 0 ? true : _ref$overflow,
50
+ _ref$previewOptions = _ref.previewOptions,
51
+ previewOptions = _ref$previewOptions === void 0 ? {} : _ref$previewOptions,
52
+ textareaProps = _ref.textareaProps,
53
+ _ref$maxHeight = _ref.maxHeight,
54
+ maxHeight = _ref$maxHeight === void 0 ? 1200 : _ref$maxHeight,
55
+ _ref$minHeight = _ref.minHeight,
56
+ minHeight = _ref$minHeight === void 0 ? 100 : _ref$minHeight,
57
+ autoFocus = _ref.autoFocus,
58
+ _ref$autoFocusEnd = _ref.autoFocusEnd,
59
+ autoFocusEnd = _ref$autoFocusEnd === void 0 ? false : _ref$autoFocusEnd,
60
+ _ref$tabSize = _ref.tabSize,
61
+ tabSize = _ref$tabSize === void 0 ? 2 : _ref$tabSize,
62
+ _ref$defaultTabEnable = _ref.defaultTabEnable,
63
+ defaultTabEnable = _ref$defaultTabEnable === void 0 ? false : _ref$defaultTabEnable,
64
+ onChange = _ref.onChange,
65
+ onStatistics = _ref.onStatistics,
66
+ onHeightChange = _ref.onHeightChange,
67
+ hideToolbar = _ref.hideToolbar,
68
+ _ref$toolbarBottom = _ref.toolbarBottom,
69
+ toolbarBottom = _ref$toolbarBottom === void 0 ? false : _ref$toolbarBottom,
70
+ components = _ref.components,
71
+ renderTextarea = _ref.renderTextarea,
59
72
  other = _objectWithoutPropertiesLoose(_ref, _excluded);
60
73
  var cmds = commands.map(item => commandsFilter ? commandsFilter(item, false) : item).filter(Boolean);
61
74
  var extraCmds = extraCommands.map(item => commandsFilter ? commandsFilter(item, true) : item).filter(Boolean);
62
- var [state, dispatch] = useReducer(reducer, {
63
- markdown: propsValue,
64
- preview: previewType,
65
- components,
66
- height,
67
- minHeight,
68
- highlightEnable,
69
- tabSize,
70
- defaultTabEnable,
71
- scrollTop: 0,
72
- scrollTopPreview: 0,
73
- commands: cmds,
74
- extraCommands: extraCmds,
75
- fullscreen,
76
- barPopup: {}
77
- });
75
+ var _useReducer = useReducer(reducer, {
76
+ markdown: propsValue,
77
+ preview: previewType,
78
+ components,
79
+ height,
80
+ minHeight,
81
+ highlightEnable,
82
+ tabSize,
83
+ defaultTabEnable,
84
+ scrollTop: 0,
85
+ scrollTopPreview: 0,
86
+ commands: cmds,
87
+ extraCommands: extraCmds,
88
+ fullscreen,
89
+ barPopup: {}
90
+ }),
91
+ state = _useReducer[0],
92
+ dispatch = _useReducer[1];
78
93
  var container = useRef(null);
79
94
  var previewRef = useRef(null);
80
95
  var enableScrollRef = useRef(enableScroll);
@@ -12,10 +12,9 @@ export var makeList = (state, api, insertBefore) => {
12
12
  var breaksBefore = Array(breaksBeforeCount + 1).join('\n');
13
13
  var breaksAfterCount = getBreaksNeededForEmptyLineAfter(state1.text, state1.selection.end);
14
14
  var breaksAfter = Array(breaksAfterCount + 1).join('\n');
15
- var {
16
- modifiedText,
17
- insertionLength
18
- } = insertBeforeEachLine(state1.selectedText, insertBefore);
15
+ var _insertBeforeEachLine = insertBeforeEachLine(state1.selectedText, insertBefore),
16
+ modifiedText = _insertBeforeEachLine.modifiedText,
17
+ insertionLength = _insertBeforeEachLine.insertionLength;
19
18
  if (insertionLength < 0) {
20
19
  // Remove
21
20
  var selectionStart = state1.selection.start;
@@ -4,12 +4,11 @@ import { heading1 } from "./title1.js";
4
4
  import { selectLine, executeCommand } from "../utils/markdownUtils.js";
5
5
  import { jsx as _jsx } from "react/jsx-runtime";
6
6
  export function headingExecute(_ref) {
7
- var {
8
- state,
9
- api,
10
- prefix,
11
- suffix = prefix
12
- } = _ref;
7
+ var state = _ref.state,
8
+ api = _ref.api,
9
+ prefix = _ref.prefix,
10
+ _ref$suffix = _ref.suffix,
11
+ suffix = _ref$suffix === void 0 ? prefix : _ref$suffix;
13
12
  var newSelectionRange = selectLine({
14
13
  text: state.text,
15
14
  selection: state.selection
@@ -2,10 +2,9 @@ import React, { useEffect, useMemo, useRef } from 'react';
2
2
  import "./index.css";
3
3
  import { jsx as _jsx } from "react/jsx-runtime";
4
4
  var DragBar = props => {
5
- var {
6
- prefixCls,
7
- onChange
8
- } = props || {};
5
+ var _ref = props || {},
6
+ prefixCls = _ref.prefixCls,
7
+ onChange = _ref.onChange;
9
8
  var $dom = useRef(null);
10
9
  var dragRef = useRef();
11
10
  var heightRef = useRef(props.height);
@@ -14,14 +14,12 @@ function html2Escape(sHtml) {
14
14
  })[c]);
15
15
  }
16
16
  export default function Markdown(props) {
17
- var {
18
- prefixCls
19
- } = props;
20
- var {
21
- markdown = '',
22
- highlightEnable,
23
- dispatch
24
- } = useContext(EditorContext);
17
+ var prefixCls = props.prefixCls;
18
+ var _useContext = useContext(EditorContext),
19
+ _useContext$markdown = _useContext.markdown,
20
+ markdown = _useContext$markdown === void 0 ? '' : _useContext$markdown,
21
+ highlightEnable = _useContext.highlightEnable,
22
+ dispatch = _useContext.dispatch;
25
23
  var preRef = /*#__PURE__*/React.createRef();
26
24
  useEffect(() => {
27
25
  if (preRef.current && dispatch) {
@@ -21,14 +21,12 @@ function html2Escape(sHtml) {
21
21
  })[c]);
22
22
  }
23
23
  export default function Markdown(props) {
24
- var {
25
- prefixCls
26
- } = props;
27
- var {
28
- markdown = '',
29
- highlightEnable,
30
- dispatch
31
- } = useContext(EditorContext);
24
+ var prefixCls = props.prefixCls;
25
+ var _useContext = useContext(EditorContext),
26
+ _useContext$markdown = _useContext.markdown,
27
+ markdown = _useContext$markdown === void 0 ? '' : _useContext$markdown,
28
+ highlightEnable = _useContext.highlightEnable,
29
+ dispatch = _useContext.dispatch;
32
30
  var preRef = /*#__PURE__*/React.createRef();
33
31
  useEffect(() => {
34
32
  if (preRef.current && dispatch) {
@@ -10,25 +10,21 @@ import shortcuts from "./shortcuts.js";
10
10
  import "./index.css";
11
11
  import { jsx as _jsx } from "react/jsx-runtime";
12
12
  export default function Textarea(props) {
13
- var {
14
- prefixCls,
15
- onChange: _onChange
16
- } = props,
13
+ var prefixCls = props.prefixCls,
14
+ _onChange = props.onChange,
17
15
  other = _objectWithoutPropertiesLoose(props, _excluded);
18
16
  var _useContext = useContext(EditorContext),
19
- {
20
- markdown,
21
- commands,
22
- fullscreen,
23
- preview,
24
- highlightEnable,
25
- extraCommands,
26
- tabSize,
27
- defaultTabEnable,
28
- autoFocusEnd,
29
- textareaWarp,
30
- dispatch
31
- } = _useContext,
17
+ markdown = _useContext.markdown,
18
+ commands = _useContext.commands,
19
+ fullscreen = _useContext.fullscreen,
20
+ preview = _useContext.preview,
21
+ highlightEnable = _useContext.highlightEnable,
22
+ extraCommands = _useContext.extraCommands,
23
+ tabSize = _useContext.tabSize,
24
+ defaultTabEnable = _useContext.defaultTabEnable,
25
+ autoFocusEnd = _useContext.autoFocusEnd,
26
+ textareaWarp = _useContext.textareaWarp,
27
+ dispatch = _useContext.dispatch,
32
28
  otherStore = _objectWithoutPropertiesLoose(_useContext, _excluded2);
33
29
  var textRef = React.useRef(null);
34
30
  var executeRef = React.useRef();
@@ -14,22 +14,19 @@ export function createTextArea(options) {
14
14
  var useMinHeight = (_options$useMinHeight = options == null ? void 0 : options.useMinHeight) != null ? _options$useMinHeight : false;
15
15
  return function TextArea(props) {
16
16
  var _ref = props || {},
17
- {
18
- prefixCls,
19
- className,
20
- onScroll,
21
- renderTextarea
22
- } = _ref,
17
+ prefixCls = _ref.prefixCls,
18
+ className = _ref.className,
19
+ onScroll = _ref.onScroll,
20
+ renderTextarea = _ref.renderTextarea,
23
21
  otherProps = _objectWithoutPropertiesLoose(_ref, _excluded);
24
- var {
25
- markdown,
26
- scrollTop,
27
- commands,
28
- minHeight,
29
- highlightEnable,
30
- extraCommands,
31
- dispatch
32
- } = useContext(EditorContext);
22
+ var _useContext = useContext(EditorContext),
23
+ markdown = _useContext.markdown,
24
+ scrollTop = _useContext.scrollTop,
25
+ commands = _useContext.commands,
26
+ minHeight = _useContext.minHeight,
27
+ highlightEnable = _useContext.highlightEnable,
28
+ extraCommands = _useContext.extraCommands,
29
+ dispatch = _useContext.dispatch;
33
30
  var textRef = React.useRef(null);
34
31
  var executeRef = React.useRef();
35
32
  var warp = /*#__PURE__*/React.createRef();
@@ -5,15 +5,14 @@ import Toolbar from "./index.js";
5
5
  import { EditorContext } from "../../Context.js";
6
6
  import { jsx as _jsx } from "react/jsx-runtime";
7
7
  export default function Child(props) {
8
- var {
9
- prefixCls,
10
- groupName,
11
- commands,
12
- children
13
- } = props || {};
14
- var {
15
- barPopup = {}
16
- } = useContext(EditorContext);
8
+ var _ref = props || {},
9
+ prefixCls = _ref.prefixCls,
10
+ groupName = _ref.groupName,
11
+ commands = _ref.commands,
12
+ children = _ref.children;
13
+ var _useContext = useContext(EditorContext),
14
+ _useContext$barPopup = _useContext.barPopup,
15
+ barPopup = _useContext$barPopup === void 0 ? {} : _useContext$barPopup;
17
16
  return useMemo(() => /*#__PURE__*/_jsx("div", {
18
17
  className: prefixCls + "-toolbar-child " + (groupName && barPopup[groupName] ? 'active' : ''),
19
18
  onClick: e => e.stopPropagation(),
@@ -5,18 +5,16 @@ import Child from "./Child.js";
5
5
  import "./index.css";
6
6
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
7
7
  export function ToolbarItems(props) {
8
- var {
9
- prefixCls,
10
- overflow
11
- } = props;
12
- var {
13
- fullscreen,
14
- preview,
15
- barPopup = {},
16
- components,
17
- commandOrchestrator,
18
- dispatch
19
- } = useContext(EditorContext);
8
+ var prefixCls = props.prefixCls,
9
+ overflow = props.overflow;
10
+ var _useContext = useContext(EditorContext),
11
+ fullscreen = _useContext.fullscreen,
12
+ preview = _useContext.preview,
13
+ _useContext$barPopup = _useContext.barPopup,
14
+ barPopup = _useContext$barPopup === void 0 ? {} : _useContext$barPopup,
15
+ components = _useContext.components,
16
+ commandOrchestrator = _useContext.commandOrchestrator,
17
+ dispatch = _useContext.dispatch;
20
18
  var originalOverflow = useRef('');
21
19
  function handleClick(command, name) {
22
20
  if (!dispatch) return;
@@ -110,15 +108,13 @@ export default function Toolbar(props) {
110
108
  if (props === void 0) {
111
109
  props = {};
112
110
  }
113
- var {
114
- prefixCls,
115
- isChild,
116
- className
117
- } = props;
118
- var {
119
- commands,
120
- extraCommands
121
- } = useContext(EditorContext);
111
+ var _props = props,
112
+ prefixCls = _props.prefixCls,
113
+ isChild = _props.isChild,
114
+ className = _props.className;
115
+ var _useContext2 = useContext(EditorContext),
116
+ commands = _useContext2.commands,
117
+ extraCommands = _useContext2.extraCommands;
122
118
  return /*#__PURE__*/_jsxs("div", {
123
119
  className: prefixCls + "-toolbar " + className,
124
120
  children: [/*#__PURE__*/_jsx(ToolbarItems, _extends({}, props, {
@@ -129,13 +125,11 @@ export default function Toolbar(props) {
129
125
  });
130
126
  }
131
127
  export function ToolbarVisibility(props) {
132
- var {
133
- hideToolbar,
134
- toolbarBottom,
135
- placement,
136
- overflow,
137
- prefixCls
138
- } = props;
128
+ var hideToolbar = props.hideToolbar,
129
+ toolbarBottom = props.toolbarBottom,
130
+ placement = props.placement,
131
+ overflow = props.overflow,
132
+ prefixCls = props.prefixCls;
139
133
  if (hideToolbar || placement === 'bottom' && !toolbarBottom || placement === 'top' && toolbarBottom) {
140
134
  return null;
141
135
  }
@@ -1,10 +1,31 @@
1
+ // `*` is a prefix of `**`, so `**text**` is bold-only and `***text***` is bold+italic.
2
+ function isItalicInsideBoldOnly(text, start, end) {
3
+ var insideBold = start >= 2 && end + 2 <= text.length && text.slice(start - 2, start) === '**' && text.slice(end, end + 2) === '**';
4
+ if (!insideBold) {
5
+ return false;
6
+ }
7
+ var insideBoldItalic = start >= 3 && end + 3 <= text.length && text.slice(start - 3, start) === '***' && text.slice(end, end + 3) === '***';
8
+ return !insideBoldItalic;
9
+ }
10
+ function isAlreadyWrapped(selectedText, prefix, suffix) {
11
+ if (selectedText.length < prefix.length + suffix.length || !selectedText.startsWith(prefix) || !selectedText.endsWith(suffix)) {
12
+ return false;
13
+ }
14
+ if (prefix === '*' && suffix === '*') {
15
+ var isBold = selectedText.startsWith('**') && selectedText.endsWith('**');
16
+ var isBoldItalic = selectedText.startsWith('***') && selectedText.endsWith('***');
17
+ if (isBold && !isBoldItalic) {
18
+ return false;
19
+ }
20
+ }
21
+ return true;
22
+ }
1
23
  export function selectWord(_ref) {
2
- var {
3
- text,
4
- selection,
5
- prefix,
6
- suffix = prefix
7
- } = _ref;
24
+ var text = _ref.text,
25
+ selection = _ref.selection,
26
+ prefix = _ref.prefix,
27
+ _ref$suffix = _ref.suffix,
28
+ suffix = _ref$suffix === void 0 ? prefix : _ref$suffix;
8
29
  var result = selection;
9
30
  if (text && text.length && selection.start === selection.end) {
10
31
  result = getSurroundingWord(text, selection.start);
@@ -12,6 +33,9 @@ export function selectWord(_ref) {
12
33
  if (result.start >= prefix.length && result.end <= text.length - suffix.length) {
13
34
  var selectedTextContext = text.slice(result.start - prefix.length, result.end + suffix.length);
14
35
  if (selectedTextContext.startsWith(prefix) && selectedTextContext.endsWith(suffix)) {
36
+ if (prefix === '*' && suffix === '*' && isItalicInsideBoldOnly(text, result.start, result.end)) {
37
+ return result;
38
+ }
15
39
  return {
16
40
  start: result.start - prefix.length,
17
41
  end: result.end + suffix.length
@@ -21,10 +45,8 @@ export function selectWord(_ref) {
21
45
  return result;
22
46
  }
23
47
  export function selectLine(_ref2) {
24
- var {
25
- text,
26
- selection
27
- } = _ref2;
48
+ var text = _ref2.text,
49
+ selection = _ref2.selection;
28
50
  var start = text.slice(0, selection.start).lastIndexOf('\n') + 1;
29
51
  var end = text.slice(selection.end).indexOf('\n') + selection.end;
30
52
  if (end === selection.end - 1) {
@@ -133,14 +155,13 @@ export function getSurroundingWord(text, position) {
133
155
  };
134
156
  }
135
157
  export function executeCommand(_ref3) {
136
- var {
137
- api,
138
- selectedText,
139
- selection,
140
- prefix,
141
- suffix = prefix
142
- } = _ref3;
143
- if (selectedText.length >= prefix.length + suffix.length && selectedText.startsWith(prefix) && selectedText.endsWith(suffix)) {
158
+ var api = _ref3.api,
159
+ selectedText = _ref3.selectedText,
160
+ selection = _ref3.selection,
161
+ prefix = _ref3.prefix,
162
+ _ref3$suffix = _ref3.suffix,
163
+ suffix = _ref3$suffix === void 0 ? prefix : _ref3$suffix;
164
+ if (isAlreadyWrapped(selectedText, prefix, suffix)) {
144
165
  api.replaceSelection(selectedText.slice(prefix.length, suffix.length ? -suffix.length : undefined));
145
166
  api.setSelectionRange({
146
167
  start: selection.start - prefix.length,
@@ -10,6 +10,28 @@ exports.getSurroundingWord = getSurroundingWord;
10
10
  exports.insertBeforeEachLine = insertBeforeEachLine;
11
11
  exports.selectLine = selectLine;
12
12
  exports.selectWord = selectWord;
13
+ // `*` is a prefix of `**`, so `**text**` is bold-only and `***text***` is bold+italic.
14
+ function isItalicInsideBoldOnly(text, start, end) {
15
+ var insideBold = start >= 2 && end + 2 <= text.length && text.slice(start - 2, start) === '**' && text.slice(end, end + 2) === '**';
16
+ if (!insideBold) {
17
+ return false;
18
+ }
19
+ var insideBoldItalic = start >= 3 && end + 3 <= text.length && text.slice(start - 3, start) === '***' && text.slice(end, end + 3) === '***';
20
+ return !insideBoldItalic;
21
+ }
22
+ function isAlreadyWrapped(selectedText, prefix, suffix) {
23
+ if (selectedText.length < prefix.length + suffix.length || !selectedText.startsWith(prefix) || !selectedText.endsWith(suffix)) {
24
+ return false;
25
+ }
26
+ if (prefix === '*' && suffix === '*') {
27
+ var isBold = selectedText.startsWith('**') && selectedText.endsWith('**');
28
+ var isBoldItalic = selectedText.startsWith('***') && selectedText.endsWith('***');
29
+ if (isBold && !isBoldItalic) {
30
+ return false;
31
+ }
32
+ }
33
+ return true;
34
+ }
13
35
  function selectWord(_ref) {
14
36
  var text = _ref.text,
15
37
  selection = _ref.selection,
@@ -23,6 +45,9 @@ function selectWord(_ref) {
23
45
  if (result.start >= prefix.length && result.end <= text.length - suffix.length) {
24
46
  var selectedTextContext = text.slice(result.start - prefix.length, result.end + suffix.length);
25
47
  if (selectedTextContext.startsWith(prefix) && selectedTextContext.endsWith(suffix)) {
48
+ if (prefix === '*' && suffix === '*' && isItalicInsideBoldOnly(text, result.start, result.end)) {
49
+ return result;
50
+ }
26
51
  return {
27
52
  start: result.start - prefix.length,
28
53
  end: result.end + suffix.length
@@ -148,7 +173,7 @@ function executeCommand(_ref3) {
148
173
  prefix = _ref3.prefix,
149
174
  _ref3$suffix = _ref3.suffix,
150
175
  suffix = _ref3$suffix === void 0 ? prefix : _ref3$suffix;
151
- if (selectedText.length >= prefix.length + suffix.length && selectedText.startsWith(prefix) && selectedText.endsWith(suffix)) {
176
+ if (isAlreadyWrapped(selectedText, prefix, suffix)) {
152
177
  api.replaceSelection(selectedText.slice(prefix.length, suffix.length ? -suffix.length : undefined));
153
178
  api.setSelectionRange({
154
179
  start: selection.start - prefix.length,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uiw/react-md-editor",
3
- "version": "4.1.0",
3
+ "version": "4.1.2",
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",
@@ -5,6 +5,39 @@ export interface TextSection {
5
5
  selection: TextRange;
6
6
  }
7
7
 
8
+ // `*` is a prefix of `**`, so `**text**` is bold-only and `***text***` is bold+italic.
9
+ function isItalicInsideBoldOnly(text: string, start: number, end: number): boolean {
10
+ const insideBold =
11
+ start >= 2 && end + 2 <= text.length && text.slice(start - 2, start) === '**' && text.slice(end, end + 2) === '**';
12
+ if (!insideBold) {
13
+ return false;
14
+ }
15
+ const insideBoldItalic =
16
+ start >= 3 &&
17
+ end + 3 <= text.length &&
18
+ text.slice(start - 3, start) === '***' &&
19
+ text.slice(end, end + 3) === '***';
20
+ return !insideBoldItalic;
21
+ }
22
+
23
+ function isAlreadyWrapped(selectedText: string, prefix: string, suffix: string): boolean {
24
+ if (
25
+ selectedText.length < prefix.length + suffix.length ||
26
+ !selectedText.startsWith(prefix) ||
27
+ !selectedText.endsWith(suffix)
28
+ ) {
29
+ return false;
30
+ }
31
+ if (prefix === '*' && suffix === '*') {
32
+ const isBold = selectedText.startsWith('**') && selectedText.endsWith('**');
33
+ const isBoldItalic = selectedText.startsWith('***') && selectedText.endsWith('***');
34
+ if (isBold && !isBoldItalic) {
35
+ return false;
36
+ }
37
+ }
38
+ return true;
39
+ }
40
+
8
41
  export function selectWord({
9
42
  text,
10
43
  selection,
@@ -23,6 +56,9 @@ export function selectWord({
23
56
  if (result.start >= prefix.length && result.end <= text.length - suffix.length) {
24
57
  const selectedTextContext = text.slice(result.start - prefix.length, result.end + suffix.length);
25
58
  if (selectedTextContext.startsWith(prefix) && selectedTextContext.endsWith(suffix)) {
59
+ if (prefix === '*' && suffix === '*' && isItalicInsideBoldOnly(text, result.start, result.end)) {
60
+ return result;
61
+ }
26
62
  return { start: result.start - prefix.length, end: result.end + suffix.length };
27
63
  }
28
64
  }
@@ -139,11 +175,7 @@ export function executeCommand({
139
175
  prefix: string;
140
176
  suffix?: string;
141
177
  }) {
142
- if (
143
- selectedText.length >= prefix.length + suffix.length &&
144
- selectedText.startsWith(prefix) &&
145
- selectedText.endsWith(suffix)
146
- ) {
178
+ if (isAlreadyWrapped(selectedText, prefix, suffix)) {
147
179
  api.replaceSelection(selectedText.slice(prefix.length, suffix.length ? -suffix.length : undefined));
148
180
  api.setSelectionRange({ start: selection.start - prefix.length, end: selection.end - prefix.length });
149
181
  } else {