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.
@@ -0,0 +1,152 @@
1
+ /**
2
+ * List operations for bullet and numbered lists
3
+ */
4
+
5
+ import { expandSelectionToLine, newlinesToSurroundSelectedText } from '../core/selection.js'
6
+ import { insertText } from '../core/insertion.js'
7
+
8
+ /**
9
+ * Undo ordered list formatting
10
+ */
11
+ function undoOrderedListStyle(text) {
12
+ const lines = text.split('\n')
13
+ const orderedListRegex = /^\d+\.\s+/
14
+ const shouldUndoOrderedList = lines.every(line => orderedListRegex.test(line))
15
+ let result = lines
16
+ if (shouldUndoOrderedList) {
17
+ result = lines.map(line => line.replace(orderedListRegex, ''))
18
+ }
19
+
20
+ return {
21
+ text: result.join('\n'),
22
+ processed: shouldUndoOrderedList
23
+ }
24
+ }
25
+
26
+ /**
27
+ * Undo unordered list formatting
28
+ */
29
+ function undoUnorderedListStyle(text) {
30
+ const lines = text.split('\n')
31
+ const unorderedListPrefix = '- '
32
+ const shouldUndoUnorderedList = lines.every(line => line.startsWith(unorderedListPrefix))
33
+ let result = lines
34
+ if (shouldUndoUnorderedList) {
35
+ result = lines.map(line => line.slice(unorderedListPrefix.length))
36
+ }
37
+
38
+ return {
39
+ text: result.join('\n'),
40
+ processed: shouldUndoUnorderedList
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Make prefix for list item
46
+ */
47
+ function makePrefix(index, unorderedList) {
48
+ if (unorderedList) {
49
+ return '- '
50
+ } else {
51
+ return `${index + 1}. `
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Clear existing list style
57
+ */
58
+ function clearExistingListStyle(style, selectedText) {
59
+ let undoResult
60
+ let undoResultOppositeList
61
+ let pristineText
62
+
63
+ if (style.orderedList) {
64
+ undoResult = undoOrderedListStyle(selectedText)
65
+ undoResultOppositeList = undoUnorderedListStyle(undoResult.text)
66
+ pristineText = undoResultOppositeList.text
67
+ } else {
68
+ undoResult = undoUnorderedListStyle(selectedText)
69
+ undoResultOppositeList = undoOrderedListStyle(undoResult.text)
70
+ pristineText = undoResultOppositeList.text
71
+ }
72
+
73
+ return [undoResult, undoResultOppositeList, pristineText]
74
+ }
75
+
76
+ /**
77
+ * Apply list styling to selected text
78
+ */
79
+ export function listStyle(textarea, style) {
80
+ const noInitialSelection = textarea.selectionStart === textarea.selectionEnd
81
+ let selectionStart = textarea.selectionStart
82
+ let selectionEnd = textarea.selectionEnd
83
+
84
+ // Select whole line
85
+ expandSelectionToLine(textarea)
86
+
87
+ const selectedText = textarea.value.slice(textarea.selectionStart, textarea.selectionEnd)
88
+
89
+ // Clear any existing list formatting
90
+ const [undoResult, undoResultOppositeList, pristineText] = clearExistingListStyle(style, selectedText)
91
+
92
+ const prefixedLines = pristineText.split('\n').map((value, index) => {
93
+ return `${makePrefix(index, style.unorderedList)}${value}`
94
+ })
95
+
96
+ const totalPrefixLength = prefixedLines.reduce((previousValue, _currentValue, currentIndex) => {
97
+ return previousValue + makePrefix(currentIndex, style.unorderedList).length
98
+ }, 0)
99
+
100
+ const totalPrefixLengthOppositeList = prefixedLines.reduce((previousValue, _currentValue, currentIndex) => {
101
+ return previousValue + makePrefix(currentIndex, !style.unorderedList).length
102
+ }, 0)
103
+
104
+ // If we're undoing the same list type, just return the pristine text
105
+ if (undoResult.processed) {
106
+ if (noInitialSelection) {
107
+ selectionStart = Math.max(selectionStart - makePrefix(0, style.unorderedList).length, 0)
108
+ selectionEnd = selectionStart
109
+ } else {
110
+ selectionStart = textarea.selectionStart
111
+ selectionEnd = textarea.selectionEnd - totalPrefixLength
112
+ }
113
+ return { text: pristineText, selectionStart, selectionEnd }
114
+ }
115
+
116
+ // Apply new list formatting
117
+ const { newlinesToAppend, newlinesToPrepend } = newlinesToSurroundSelectedText(textarea)
118
+ const text = newlinesToAppend + prefixedLines.join('\n') + newlinesToPrepend
119
+
120
+ if (noInitialSelection) {
121
+ selectionStart = Math.max(selectionStart + makePrefix(0, style.unorderedList).length + newlinesToAppend.length, 0)
122
+ selectionEnd = selectionStart
123
+ } else {
124
+ if (undoResultOppositeList.processed) {
125
+ // Converting from one list type to another
126
+ selectionStart = Math.max(textarea.selectionStart + newlinesToAppend.length, 0)
127
+ selectionEnd = textarea.selectionEnd + newlinesToAppend.length + totalPrefixLength - totalPrefixLengthOppositeList
128
+ } else {
129
+ // Adding list formatting to plain text
130
+ selectionStart = Math.max(textarea.selectionStart + newlinesToAppend.length, 0)
131
+ selectionEnd = textarea.selectionEnd + newlinesToAppend.length + totalPrefixLength
132
+ }
133
+ }
134
+
135
+ return { text, selectionStart, selectionEnd }
136
+ }
137
+
138
+ /**
139
+ * Apply list style to textarea
140
+ */
141
+ export function applyListStyle(textarea, style) {
142
+ // Save the original selection boundaries before expansion
143
+ const originalStart = textarea.selectionStart
144
+ const originalEnd = textarea.selectionEnd
145
+
146
+ // Get the result which includes expandSelectionToLine
147
+ const result = listStyle(textarea, style)
148
+
149
+ // At this point, textarea selection has been expanded to full line(s)
150
+ // insertText will use the current (expanded) selection to know what to replace
151
+ insertText(textarea, result)
152
+ }