markdown-actions 1.0.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/src/index.js ADDED
@@ -0,0 +1,229 @@
1
+ /**
2
+ * markdown-actions - Lightweight markdown toolbar functionality
3
+ * Based on GitHub's markdown-toolbar-element
4
+ */
5
+
6
+ import { FORMATS, mergeWithDefaults } from './core/formats.js'
7
+ import { insertText, setUndoMethod } from './core/insertion.js'
8
+ import { preserveSelection, isMultipleLines } from './core/selection.js'
9
+ import { blockStyle, multilineStyle } from './operations/block.js'
10
+ import { applyListStyle } from './operations/list.js'
11
+ import { getActiveFormats as getActive, hasFormat as has, expandSelection as expand } from './core/detection.js'
12
+
13
+ /**
14
+ * Toggle bold formatting
15
+ */
16
+ export function toggleBold(textarea) {
17
+ if (!textarea || textarea.disabled || textarea.readOnly) return
18
+ const style = mergeWithDefaults(FORMATS.bold)
19
+ const result = blockStyle(textarea, style)
20
+ insertText(textarea, result)
21
+ }
22
+
23
+ /**
24
+ * Toggle italic formatting
25
+ */
26
+ export function toggleItalic(textarea) {
27
+ if (!textarea || textarea.disabled || textarea.readOnly) return
28
+ const style = mergeWithDefaults(FORMATS.italic)
29
+ const result = blockStyle(textarea, style)
30
+ insertText(textarea, result)
31
+ }
32
+
33
+ /**
34
+ * Toggle code formatting
35
+ */
36
+ export function toggleCode(textarea) {
37
+ if (!textarea || textarea.disabled || textarea.readOnly) return
38
+ const selectedText = textarea.value.slice(textarea.selectionStart, textarea.selectionEnd)
39
+ const style = mergeWithDefaults(FORMATS.code)
40
+
41
+ // Use block style for single line, multiline style for multiple lines
42
+ let result
43
+ if (isMultipleLines(selectedText)) {
44
+ // For multiline, we want code blocks
45
+ const blockStyle = {
46
+ prefix: '```\n',
47
+ suffix: '\n```',
48
+ surroundWithNewlines: true
49
+ }
50
+ result = multilineStyle(textarea, mergeWithDefaults(blockStyle))
51
+ } else {
52
+ result = blockStyle(textarea, style)
53
+ }
54
+
55
+ insertText(textarea, result)
56
+ }
57
+
58
+ /**
59
+ * Insert or toggle link formatting
60
+ */
61
+ export function insertLink(textarea, options = {}) {
62
+ if (!textarea || textarea.disabled || textarea.readOnly) return
63
+
64
+ const selectedText = textarea.value.slice(textarea.selectionStart, textarea.selectionEnd)
65
+ let style = mergeWithDefaults(FORMATS.link)
66
+
67
+ // Override with custom URL if provided
68
+ if (options.url) {
69
+ style.suffix = `](${options.url})`
70
+ style.replaceNext = ''
71
+ }
72
+
73
+ // Override with custom text if provided
74
+ if (options.text && !selectedText) {
75
+ // Insert the text and select it
76
+ const pos = textarea.selectionStart
77
+ textarea.value = textarea.value.slice(0, pos) + options.text + textarea.value.slice(pos)
78
+ textarea.selectionStart = pos
79
+ textarea.selectionEnd = pos + options.text.length
80
+ }
81
+
82
+ const result = blockStyle(textarea, style)
83
+ insertText(textarea, result)
84
+ }
85
+
86
+ /**
87
+ * Toggle bullet list formatting
88
+ */
89
+ export function toggleBulletList(textarea) {
90
+ if (!textarea || textarea.disabled || textarea.readOnly) return
91
+ const style = mergeWithDefaults(FORMATS.bulletList)
92
+ applyListStyle(textarea, style)
93
+ }
94
+
95
+ /**
96
+ * Toggle numbered list formatting
97
+ */
98
+ export function toggleNumberedList(textarea) {
99
+ if (!textarea || textarea.disabled || textarea.readOnly) return
100
+ const style = mergeWithDefaults(FORMATS.numberedList)
101
+ applyListStyle(textarea, style)
102
+ }
103
+
104
+ /**
105
+ * Toggle quote formatting
106
+ */
107
+ export function toggleQuote(textarea) {
108
+ if (!textarea || textarea.disabled || textarea.readOnly) return
109
+ const style = mergeWithDefaults(FORMATS.quote)
110
+ const result = multilineStyle(textarea, style)
111
+ insertText(textarea, result)
112
+ }
113
+
114
+ /**
115
+ * Insert header with specific level
116
+ */
117
+ export function insertHeader(textarea, level = 1) {
118
+ if (!textarea || textarea.disabled || textarea.readOnly) return
119
+ if (level < 1 || level > 6) level = 1
120
+
121
+ const headerKey = `header${level === 1 ? '1' : level}`
122
+ const style = mergeWithDefaults(FORMATS[headerKey] || FORMATS.header1)
123
+
124
+ // Select current line
125
+ const value = textarea.value
126
+ const start = textarea.selectionStart
127
+ let lineStart = start
128
+ let lineEnd = start
129
+
130
+ // Find line boundaries
131
+ while (lineStart > 0 && value[lineStart - 1] !== '\n') {
132
+ lineStart--
133
+ }
134
+ while (lineEnd < value.length && value[lineEnd] !== '\n') {
135
+ lineEnd++
136
+ }
137
+
138
+ textarea.selectionStart = lineStart
139
+ textarea.selectionEnd = lineEnd
140
+
141
+ const currentLine = value.slice(lineStart, lineEnd)
142
+
143
+ // Remove any existing header formatting
144
+ const cleanedLine = currentLine.replace(/^#{1,6}\s*/, '')
145
+
146
+ // Apply new header
147
+ const newLine = style.prefix + cleanedLine
148
+ const result = {
149
+ text: newLine,
150
+ selectionStart: lineStart + style.prefix.length,
151
+ selectionEnd: lineStart + newLine.length
152
+ }
153
+
154
+ insertText(textarea, result)
155
+ }
156
+
157
+ /**
158
+ * Get active formats at cursor position
159
+ */
160
+ export function getActiveFormats(textarea) {
161
+ return getActive(textarea)
162
+ }
163
+
164
+ /**
165
+ * Check if format is active at cursor
166
+ */
167
+ export function hasFormat(textarea, format) {
168
+ return has(textarea, format)
169
+ }
170
+
171
+ /**
172
+ * Expand selection based on options
173
+ */
174
+ export function expandSelection(textarea, options = {}) {
175
+ expand(textarea, options)
176
+ }
177
+
178
+ /**
179
+ * Apply custom format
180
+ */
181
+ export function applyCustomFormat(textarea, format) {
182
+ if (!textarea || textarea.disabled || textarea.readOnly) return
183
+
184
+ const style = mergeWithDefaults(format)
185
+ let result
186
+
187
+ if (style.multiline) {
188
+ const selectedText = textarea.value.slice(textarea.selectionStart, textarea.selectionEnd)
189
+ if (isMultipleLines(selectedText)) {
190
+ result = multilineStyle(textarea, style)
191
+ } else {
192
+ result = blockStyle(textarea, style)
193
+ }
194
+ } else {
195
+ result = blockStyle(textarea, style)
196
+ }
197
+
198
+ insertText(textarea, result)
199
+ }
200
+
201
+ /**
202
+ * Preserve selection during callback
203
+ */
204
+ export { preserveSelection }
205
+
206
+ /**
207
+ * Configure undo method
208
+ */
209
+ export { setUndoMethod }
210
+
211
+ /**
212
+ * Default export with all functions
213
+ */
214
+ export default {
215
+ toggleBold,
216
+ toggleItalic,
217
+ toggleCode,
218
+ insertLink,
219
+ toggleBulletList,
220
+ toggleNumberedList,
221
+ toggleQuote,
222
+ insertHeader,
223
+ getActiveFormats,
224
+ hasFormat,
225
+ expandSelection,
226
+ applyCustomFormat,
227
+ preserveSelection,
228
+ setUndoMethod
229
+ }
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Block and multiline operations - EXACT match to GitHub's implementation
3
+ * Based on markdown-toolbar-element index.ts lines 581-670
4
+ */
5
+
6
+ import {
7
+ isMultipleLines,
8
+ expandSelectedText,
9
+ newlinesToSurroundSelectedText
10
+ } from '../core/selection-new.js'
11
+
12
+ /**
13
+ * Block style operation
14
+ * GitHub: index.ts lines 581-646
15
+ */
16
+ export function blockStyle(textarea, arg) {
17
+ let newlinesToAppend
18
+ let newlinesToPrepend
19
+
20
+ const { prefix, suffix, blockPrefix, blockSuffix, replaceNext, prefixSpace, scanFor, surroundWithNewlines } = arg
21
+ const originalSelectionStart = textarea.selectionStart
22
+ const originalSelectionEnd = textarea.selectionEnd
23
+
24
+ let selectedText = textarea.value.slice(textarea.selectionStart, textarea.selectionEnd)
25
+ let prefixToUse = isMultipleLines(selectedText) && blockPrefix.length > 0 ? `${blockPrefix}\n` : prefix
26
+ let suffixToUse = isMultipleLines(selectedText) && blockSuffix.length > 0 ? `\n${blockSuffix}` : suffix
27
+
28
+ if (prefixSpace) {
29
+ const beforeSelection = textarea.value[textarea.selectionStart - 1]
30
+ if (textarea.selectionStart !== 0 && beforeSelection != null && !beforeSelection.match(/\s/)) {
31
+ prefixToUse = ` ${prefixToUse}`
32
+ }
33
+ }
34
+ selectedText = expandSelectedText(textarea, prefixToUse, suffixToUse, arg.multiline)
35
+ let selectionStart = textarea.selectionStart
36
+ let selectionEnd = textarea.selectionEnd
37
+ const hasReplaceNext = replaceNext.length > 0 && suffixToUse.indexOf(replaceNext) > -1 && selectedText.length > 0
38
+ if (surroundWithNewlines) {
39
+ const ref = newlinesToSurroundSelectedText(textarea)
40
+ newlinesToAppend = ref.newlinesToAppend
41
+ newlinesToPrepend = ref.newlinesToPrepend
42
+ prefixToUse = newlinesToAppend + prefix
43
+ suffixToUse += newlinesToPrepend
44
+ }
45
+
46
+ if (selectedText.startsWith(prefixToUse) && selectedText.endsWith(suffixToUse)) {
47
+ const replacementText = selectedText.slice(prefixToUse.length, selectedText.length - suffixToUse.length)
48
+ if (originalSelectionStart === originalSelectionEnd) {
49
+ let position = originalSelectionStart - prefixToUse.length
50
+ position = Math.max(position, selectionStart)
51
+ position = Math.min(position, selectionStart + replacementText.length)
52
+ selectionStart = selectionEnd = position
53
+ } else {
54
+ selectionEnd = selectionStart + replacementText.length
55
+ }
56
+ return { text: replacementText, selectionStart, selectionEnd }
57
+ } else if (!hasReplaceNext) {
58
+ let replacementText = prefixToUse + selectedText + suffixToUse
59
+ selectionStart = originalSelectionStart + prefixToUse.length
60
+ selectionEnd = originalSelectionEnd + prefixToUse.length
61
+ const whitespaceEdges = selectedText.match(/^\s*|\s*$/g)
62
+ if (arg.trimFirst && whitespaceEdges) {
63
+ const leadingWhitespace = whitespaceEdges[0] || ''
64
+ const trailingWhitespace = whitespaceEdges[1] || ''
65
+ replacementText = leadingWhitespace + prefixToUse + selectedText.trim() + suffixToUse + trailingWhitespace
66
+ selectionStart += leadingWhitespace.length
67
+ selectionEnd -= trailingWhitespace.length
68
+ }
69
+ return { text: replacementText, selectionStart, selectionEnd }
70
+ } else if (scanFor.length > 0 && selectedText.match(scanFor)) {
71
+ suffixToUse = suffixToUse.replace(replaceNext, selectedText)
72
+ const replacementText = prefixToUse + selectedText + suffixToUse
73
+ selectionStart = selectionEnd = selectionStart + prefixToUse.length
74
+ return { text: replacementText, selectionStart, selectionEnd }
75
+ } else {
76
+ const replacementText = prefixToUse + selectedText + suffixToUse
77
+ selectionStart = selectionStart + prefixToUse.length + selectedText.length + suffixToUse.indexOf(replaceNext)
78
+ selectionEnd = selectionStart + replaceNext.length
79
+ return { text: replacementText, selectionStart, selectionEnd }
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Multiline style operation
85
+ * GitHub: index.ts lines 648-670
86
+ */
87
+ export function multilineStyle(textarea, arg) {
88
+ const { prefix, suffix, surroundWithNewlines } = arg
89
+ let text = textarea.value.slice(textarea.selectionStart, textarea.selectionEnd)
90
+ let selectionStart = textarea.selectionStart
91
+ let selectionEnd = textarea.selectionEnd
92
+ const lines = text.split('\n')
93
+ const undoStyle = lines.every(line => line.startsWith(prefix) && line.endsWith(suffix))
94
+
95
+ if (undoStyle) {
96
+ text = lines.map(line => line.slice(prefix.length, line.length - suffix.length)).join('\n')
97
+ selectionEnd = selectionStart + text.length
98
+ } else {
99
+ text = lines.map(line => prefix + line + suffix).join('\n')
100
+ if (surroundWithNewlines) {
101
+ const { newlinesToAppend, newlinesToPrepend } = newlinesToSurroundSelectedText(textarea)
102
+ selectionStart += newlinesToAppend.length
103
+ selectionEnd = selectionStart + text.length
104
+ text = newlinesToAppend + text + newlinesToPrepend
105
+ }
106
+ }
107
+
108
+ return { text, selectionStart, selectionEnd }
109
+ }
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Block-level text formatting operations
3
+ * Handles inline formats like bold, italic, code, and links
4
+ */
5
+
6
+ import { expandSelectedText, newlinesToSurroundSelectedText, isMultipleLines } from '../core/selection.js'
7
+ import { insertText } from '../core/insertion.js'
8
+
9
+ /**
10
+ * Apply block-level styling to selected text
11
+ */
12
+ export function blockStyle(textarea, style) {
13
+ let newlinesToAppend
14
+ let newlinesToPrepend
15
+
16
+ const { prefix, suffix, blockPrefix, blockSuffix, replaceNext, prefixSpace, scanFor, surroundWithNewlines, trimFirst } = style
17
+ const originalSelectionStart = textarea.selectionStart
18
+ const originalSelectionEnd = textarea.selectionEnd
19
+
20
+ let selectedText = textarea.value.slice(textarea.selectionStart, textarea.selectionEnd)
21
+ let prefixToUse = isMultipleLines(selectedText) && blockPrefix && blockPrefix.length > 0 ? `${blockPrefix}\n` : prefix
22
+ let suffixToUse = isMultipleLines(selectedText) && blockSuffix && blockSuffix.length > 0 ? `\n${blockSuffix}` : suffix
23
+
24
+ if (prefixSpace) {
25
+ const beforeSelection = textarea.value[textarea.selectionStart - 1]
26
+ if (textarea.selectionStart !== 0 && beforeSelection != null && !beforeSelection.match(/\s/)) {
27
+ prefixToUse = ` ${prefixToUse}`
28
+ }
29
+ }
30
+
31
+ selectedText = expandSelectedText(textarea, prefixToUse, suffixToUse, style.multiline)
32
+ let selectionStart = textarea.selectionStart
33
+ let selectionEnd = textarea.selectionEnd
34
+ const hasReplaceNext = replaceNext && replaceNext.length > 0 && suffixToUse.indexOf(replaceNext) > -1 && selectedText.length > 0
35
+
36
+ if (surroundWithNewlines) {
37
+ const ref = newlinesToSurroundSelectedText(textarea)
38
+ newlinesToAppend = ref.newlinesToAppend
39
+ newlinesToPrepend = ref.newlinesToPrepend
40
+ prefixToUse = newlinesToAppend + prefix
41
+ suffixToUse += newlinesToPrepend
42
+ }
43
+
44
+ // Check if we should remove formatting (toggle off)
45
+ if (selectedText.startsWith(prefixToUse) && selectedText.endsWith(suffixToUse)) {
46
+ const replacementText = selectedText.slice(prefixToUse.length, selectedText.length - suffixToUse.length)
47
+ if (originalSelectionStart === originalSelectionEnd) {
48
+ let position = originalSelectionStart - prefixToUse.length
49
+ position = Math.max(position, selectionStart)
50
+ position = Math.min(position, selectionStart + replacementText.length)
51
+ selectionStart = selectionEnd = position
52
+ } else {
53
+ selectionEnd = selectionStart + replacementText.length
54
+ }
55
+ return { text: replacementText, selectionStart, selectionEnd }
56
+ } else if (!hasReplaceNext) {
57
+ // Add formatting
58
+ let replacementText = prefixToUse + selectedText + suffixToUse
59
+ selectionStart = originalSelectionStart + prefixToUse.length
60
+ selectionEnd = originalSelectionEnd + prefixToUse.length
61
+ const whitespaceEdges = selectedText.match(/^\s*|\s*$/g)
62
+ if (trimFirst && whitespaceEdges) {
63
+ const leadingWhitespace = whitespaceEdges[0] || ''
64
+ const trailingWhitespace = whitespaceEdges[1] || ''
65
+ replacementText = leadingWhitespace + prefixToUse + selectedText.trim() + suffixToUse + trailingWhitespace
66
+ selectionStart += leadingWhitespace.length
67
+ selectionEnd -= trailingWhitespace.length
68
+ }
69
+ return { text: replacementText, selectionStart, selectionEnd }
70
+ } else if (scanFor && scanFor.length > 0 && selectedText.match(scanFor)) {
71
+ // Handle link/image with URL detection
72
+ suffixToUse = suffixToUse.replace(replaceNext, selectedText)
73
+ const replacementText = prefixToUse + suffixToUse
74
+ selectionStart = selectionEnd = selectionStart + prefixToUse.length
75
+ return { text: replacementText, selectionStart, selectionEnd }
76
+ } else {
77
+ // Handle link/image with placeholder
78
+ const replacementText = prefixToUse + selectedText + suffixToUse
79
+ selectionStart = selectionStart + prefixToUse.length + selectedText.length + suffixToUse.indexOf(replaceNext)
80
+ selectionEnd = selectionStart + replaceNext.length
81
+ return { text: replacementText, selectionStart, selectionEnd }
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Apply style to selected text in textarea
87
+ */
88
+ export function styleSelectedText(textarea, style) {
89
+ const text = textarea.value.slice(textarea.selectionStart, textarea.selectionEnd)
90
+
91
+ let result
92
+ if (style.orderedList || style.unorderedList) {
93
+ // Will be handled by list operations
94
+ return
95
+ } else if (style.multiline && isMultipleLines(text)) {
96
+ result = multilineStyle(textarea, style)
97
+ } else {
98
+ result = blockStyle(textarea, style)
99
+ }
100
+
101
+ insertText(textarea, result)
102
+ }
103
+
104
+ /**
105
+ * Apply multiline styling (quotes, etc)
106
+ */
107
+ export function multilineStyle(textarea, style) {
108
+ const { prefix, suffix, surroundWithNewlines } = style
109
+ let text = textarea.value.slice(textarea.selectionStart, textarea.selectionEnd)
110
+ let selectionStart = textarea.selectionStart
111
+ let selectionEnd = textarea.selectionEnd
112
+ const lines = text.split('\n')
113
+
114
+ // Check if we need to undo (all lines have the format)
115
+ const undoStyle = lines.every(line => line.startsWith(prefix) && (!suffix || line.endsWith(suffix)))
116
+
117
+ if (undoStyle) {
118
+ // Remove the formatting
119
+ text = lines.map(line => {
120
+ let result = line.slice(prefix.length)
121
+ if (suffix) {
122
+ result = result.slice(0, result.length - suffix.length)
123
+ }
124
+ return result
125
+ }).join('\n')
126
+ selectionEnd = selectionStart + text.length
127
+ } else {
128
+ // Apply the formatting
129
+ text = lines.map(line => prefix + line + (suffix || '')).join('\n')
130
+ if (surroundWithNewlines) {
131
+ const { newlinesToAppend, newlinesToPrepend } = newlinesToSurroundSelectedText(textarea)
132
+ selectionStart += newlinesToAppend.length
133
+ selectionEnd = selectionStart + text.length
134
+ text = newlinesToAppend + text + newlinesToPrepend
135
+ }
136
+ }
137
+
138
+ return { text, selectionStart, selectionEnd }
139
+ }
@@ -0,0 +1,137 @@
1
+ /**
2
+ * List operations - EXACT match to GitHub's implementation
3
+ * Based on markdown-toolbar-element index.ts lines 676-784
4
+ */
5
+
6
+ import { expandSelectionToLine, newlinesToSurroundSelectedText } from '../core/selection-new.js'
7
+
8
+ /**
9
+ * Undo ordered list style
10
+ * GitHub: index.ts lines 676-689
11
+ */
12
+ function undoOrderedListStyle(text) {
13
+ const lines = text.split('\n')
14
+ const orderedListRegex = /^\d+\.\s+/
15
+ const shouldUndoOrderedList = lines.every(line => orderedListRegex.test(line))
16
+ let result = lines
17
+ if (shouldUndoOrderedList) {
18
+ result = lines.map(line => line.replace(orderedListRegex, ''))
19
+ }
20
+
21
+ return {
22
+ text: result.join('\n'),
23
+ processed: shouldUndoOrderedList
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Undo unordered list style
29
+ * GitHub: index.ts lines 691-704
30
+ */
31
+ function undoUnorderedListStyle(text) {
32
+ const lines = text.split('\n')
33
+ const unorderedListPrefix = '- '
34
+ const shouldUndoUnorderedList = lines.every(line => line.startsWith(unorderedListPrefix))
35
+ let result = lines
36
+ if (shouldUndoUnorderedList) {
37
+ result = lines.map(line => line.slice(unorderedListPrefix.length, line.length))
38
+ }
39
+
40
+ return {
41
+ text: result.join('\n'),
42
+ processed: shouldUndoUnorderedList
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Make prefix for list item
48
+ * GitHub: index.ts lines 706-712
49
+ */
50
+ function makePrefix(index, unorderedList) {
51
+ if (unorderedList) {
52
+ return '- '
53
+ } else {
54
+ return `${index + 1}. `
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Clear existing list style
60
+ * GitHub: index.ts lines 714-728
61
+ */
62
+ function clearExistingListStyle(style, selectedText) {
63
+ let undoResultOpositeList
64
+ let undoResult
65
+ let pristineText
66
+ if (style.orderedList) {
67
+ undoResult = undoOrderedListStyle(selectedText)
68
+ undoResultOpositeList = undoUnorderedListStyle(undoResult.text)
69
+ pristineText = undoResultOpositeList.text
70
+ } else {
71
+ undoResult = undoUnorderedListStyle(selectedText)
72
+ undoResultOpositeList = undoOrderedListStyle(undoResult.text)
73
+ pristineText = undoResultOpositeList.text
74
+ }
75
+ return [undoResult, undoResultOpositeList, pristineText]
76
+ }
77
+
78
+ /**
79
+ * Apply list style to text
80
+ * GitHub: index.ts lines 730-784
81
+ * CRITICAL: This function modifies the textarea selection via expandSelectionToLine
82
+ */
83
+ export function listStyle(textarea, style) {
84
+ const noInitialSelection = textarea.selectionStart === textarea.selectionEnd
85
+ let selectionStart = textarea.selectionStart
86
+ let selectionEnd = textarea.selectionEnd
87
+
88
+ // Select whole line - MODIFIES textarea.selectionStart and textarea.selectionEnd
89
+ expandSelectionToLine(textarea)
90
+
91
+ const selectedText = textarea.value.slice(textarea.selectionStart, textarea.selectionEnd)
92
+
93
+ // If the user intent was to do an undo, we will stop after this.
94
+ // Otherwise, we will still undo to other list type to prevent list stacking
95
+ const [undoResult, undoResultOpositeList, pristineText] = clearExistingListStyle(style, selectedText)
96
+
97
+ const prefixedLines = pristineText.split('\n').map((value, index) => {
98
+ return `${makePrefix(index, style.unorderedList)}${value}`
99
+ })
100
+
101
+ const totalPrefixLength = prefixedLines.reduce((previousValue, _currentValue, currentIndex) => {
102
+ return previousValue + makePrefix(currentIndex, style.unorderedList).length
103
+ }, 0)
104
+
105
+ const totalPrefixLengthOpositeList = prefixedLines.reduce((previousValue, _currentValue, currentIndex) => {
106
+ return previousValue + makePrefix(currentIndex, !style.unorderedList).length
107
+ }, 0)
108
+
109
+ if (undoResult.processed) {
110
+ if (noInitialSelection) {
111
+ selectionStart = Math.max(selectionStart - makePrefix(0, style.unorderedList).length, 0)
112
+ selectionEnd = selectionStart
113
+ } else {
114
+ selectionStart = textarea.selectionStart
115
+ selectionEnd = textarea.selectionEnd - totalPrefixLength
116
+ }
117
+ return { text: pristineText, selectionStart, selectionEnd }
118
+ }
119
+
120
+ const { newlinesToAppend, newlinesToPrepend } = newlinesToSurroundSelectedText(textarea)
121
+ const text = newlinesToAppend + prefixedLines.join('\n') + newlinesToPrepend
122
+
123
+ if (noInitialSelection) {
124
+ selectionStart = Math.max(selectionStart + makePrefix(0, style.unorderedList).length + newlinesToAppend.length, 0)
125
+ selectionEnd = selectionStart
126
+ } else {
127
+ if (undoResultOpositeList.processed) {
128
+ selectionStart = Math.max(textarea.selectionStart + newlinesToAppend.length, 0)
129
+ selectionEnd = textarea.selectionEnd + newlinesToAppend.length + totalPrefixLength - totalPrefixLengthOpositeList
130
+ } else {
131
+ selectionStart = Math.max(textarea.selectionStart + newlinesToAppend.length, 0)
132
+ selectionEnd = textarea.selectionEnd + newlinesToAppend.length + totalPrefixLength
133
+ }
134
+ }
135
+
136
+ return { text, selectionStart, selectionEnd }
137
+ }