markdown-actions 1.0.0 → 1.0.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.
package/src/debug.js ADDED
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Debug utilities for markdown-actions
3
+ * Add console logging to track what's happening
4
+ */
5
+
6
+ // Debug mode flag - disabled by default
7
+ let debugMode = false;
8
+
9
+ /**
10
+ * Enable or disable debug mode
11
+ * @param {boolean} enabled - Whether to enable debug mode
12
+ */
13
+ export function setDebugMode(enabled) {
14
+ debugMode = enabled;
15
+ }
16
+
17
+ /**
18
+ * Get current debug mode status
19
+ * @returns {boolean} Whether debug mode is enabled
20
+ */
21
+ export function getDebugMode() {
22
+ return debugMode;
23
+ }
24
+
25
+ export function debugLog(funcName, message, data) {
26
+ if (!debugMode) return;
27
+
28
+ console.group(`🔍 ${funcName}`);
29
+ console.log(message);
30
+ if (data) {
31
+ console.log('Data:', data);
32
+ }
33
+ console.groupEnd();
34
+ }
35
+
36
+ export function debugSelection(textarea, label) {
37
+ if (!debugMode) return;
38
+
39
+ const selected = textarea.value.slice(textarea.selectionStart, textarea.selectionEnd);
40
+ console.group(`📍 Selection: ${label}`);
41
+ console.log('Position:', `${textarea.selectionStart}-${textarea.selectionEnd}`);
42
+ console.log('Selected text:', JSON.stringify(selected));
43
+ console.log('Length:', selected.length);
44
+
45
+ // Show context around selection
46
+ const before = textarea.value.slice(Math.max(0, textarea.selectionStart - 10), textarea.selectionStart);
47
+ const after = textarea.value.slice(textarea.selectionEnd, Math.min(textarea.value.length, textarea.selectionEnd + 10));
48
+ console.log('Context:', JSON.stringify(before) + '[SELECTION]' + JSON.stringify(after));
49
+ console.groupEnd();
50
+ }
51
+
52
+ export function debugResult(result) {
53
+ if (!debugMode) return;
54
+
55
+ console.group('📝 Result');
56
+ console.log('Text to insert:', JSON.stringify(result.text));
57
+ console.log('New selection:', `${result.selectionStart}-${result.selectionEnd}`);
58
+ console.groupEnd();
59
+ }
package/src/index.js CHANGED
@@ -5,19 +5,29 @@
5
5
 
6
6
  import { FORMATS, mergeWithDefaults } from './core/formats.js'
7
7
  import { insertText, setUndoMethod } from './core/insertion.js'
8
- import { preserveSelection, isMultipleLines } from './core/selection.js'
8
+ import { preserveSelection, isMultipleLines, expandSelectionToLine, applyLineOperation } from './core/selection.js'
9
9
  import { blockStyle, multilineStyle } from './operations/block.js'
10
10
  import { applyListStyle } from './operations/list.js'
11
11
  import { getActiveFormats as getActive, hasFormat as has, expandSelection as expand } from './core/detection.js'
12
+ import { debugLog, debugSelection, debugResult, setDebugMode, getDebugMode } from './debug.js'
12
13
 
13
14
  /**
14
15
  * Toggle bold formatting
15
16
  */
16
17
  export function toggleBold(textarea) {
17
18
  if (!textarea || textarea.disabled || textarea.readOnly) return
19
+
20
+ debugLog('toggleBold', 'Starting');
21
+ debugSelection(textarea, 'Before');
22
+
18
23
  const style = mergeWithDefaults(FORMATS.bold)
19
24
  const result = blockStyle(textarea, style)
25
+
26
+ debugResult(result);
27
+
20
28
  insertText(textarea, result)
29
+
30
+ debugSelection(textarea, 'After');
21
31
  }
22
32
 
23
33
  /**
@@ -103,29 +113,66 @@ export function toggleNumberedList(textarea) {
103
113
 
104
114
  /**
105
115
  * Toggle quote formatting
116
+ * Matches GitHub's implementation for quotes
106
117
  */
107
118
  export function toggleQuote(textarea) {
108
119
  if (!textarea || textarea.disabled || textarea.readOnly) return
120
+
121
+ debugLog('toggleQuote', 'Starting');
122
+ debugSelection(textarea, 'Initial');
123
+
109
124
  const style = mergeWithDefaults(FORMATS.quote)
110
- const result = multilineStyle(textarea, style)
125
+
126
+ // Use the reusable line operation helper
127
+ const result = applyLineOperation(
128
+ textarea,
129
+ (ta) => multilineStyle(ta, style),
130
+ { prefix: style.prefix }
131
+ )
132
+
133
+ debugResult(result);
134
+ insertText(textarea, result)
135
+ debugSelection(textarea, 'Final');
136
+ }
137
+
138
+ /**
139
+ * Toggle task list formatting
140
+ * Matches GitHub's implementation for task lists
141
+ */
142
+ export function toggleTaskList(textarea) {
143
+ if (!textarea || textarea.disabled || textarea.readOnly) return
144
+
145
+ const style = mergeWithDefaults(FORMATS.taskList)
146
+
147
+ // Use the reusable line operation helper
148
+ const result = applyLineOperation(
149
+ textarea,
150
+ (ta) => multilineStyle(ta, style),
151
+ { prefix: style.prefix }
152
+ )
153
+
111
154
  insertText(textarea, result)
112
155
  }
113
156
 
114
157
  /**
115
- * Insert header with specific level
158
+ * Insert or toggle header with specific level
116
159
  */
117
- export function insertHeader(textarea, level = 1) {
160
+ export function insertHeader(textarea, level = 1, toggle = false) {
118
161
  if (!textarea || textarea.disabled || textarea.readOnly) return
119
162
  if (level < 1 || level > 6) level = 1
120
163
 
164
+ // Save original cursor position before any expansion
165
+ const originalStart = textarea.selectionStart
166
+ const originalEnd = textarea.selectionEnd
167
+ const noInitialSelection = originalStart === originalEnd
168
+
121
169
  const headerKey = `header${level === 1 ? '1' : level}`
122
170
  const style = mergeWithDefaults(FORMATS[headerKey] || FORMATS.header1)
123
171
 
124
172
  // Select current line
125
173
  const value = textarea.value
126
- const start = textarea.selectionStart
127
- let lineStart = start
128
- let lineEnd = start
174
+ let lineStart = originalStart
175
+ let lineEnd = originalStart
129
176
 
130
177
  // Find line boundaries
131
178
  while (lineStart > 0 && value[lineStart - 1] !== '\n') {
@@ -140,20 +187,101 @@ export function insertHeader(textarea, level = 1) {
140
187
 
141
188
  const currentLine = value.slice(lineStart, lineEnd)
142
189
 
190
+ // Check if we're removing or changing existing header
191
+ const existingHeaderMatch = currentLine.match(/^(#{1,6})\s*/)
192
+ const existingPrefixLength = existingHeaderMatch ? existingHeaderMatch[0].length : 0
193
+ const existingLevel = existingHeaderMatch ? existingHeaderMatch[1].length : 0
194
+
143
195
  // Remove any existing header formatting
144
196
  const cleanedLine = currentLine.replace(/^#{1,6}\s*/, '')
145
197
 
146
- // Apply new header
147
- const newLine = style.prefix + cleanedLine
198
+ let newLine
199
+ let isRemoving = false
200
+
201
+ // If toggle mode and current line has the same header level, remove it
202
+ if (toggle && existingLevel === level) {
203
+ newLine = cleanedLine
204
+ isRemoving = true
205
+ } else {
206
+ // Apply new header
207
+ newLine = style.prefix + cleanedLine
208
+ }
209
+
210
+ let selectionStart, selectionEnd
211
+
212
+ if (noInitialSelection) {
213
+ // Calculate where cursor should be after the operation
214
+ const cursorOffsetInLine = originalStart - lineStart
215
+
216
+ if (isRemoving) {
217
+ // We're removing the header
218
+ if (cursorOffsetInLine < existingPrefixLength) {
219
+ // Cursor was in the header prefix, move to start of line
220
+ selectionStart = lineStart
221
+ } else {
222
+ // Cursor was in the text, move back by prefix length
223
+ selectionStart = originalStart - existingPrefixLength
224
+ }
225
+ } else if (existingPrefixLength > 0) {
226
+ // We're replacing an existing header
227
+ if (cursorOffsetInLine < existingPrefixLength) {
228
+ // Cursor was in the header prefix, keep it at the same position
229
+ selectionStart = lineStart + Math.min(cursorOffsetInLine, style.prefix.length)
230
+ } else {
231
+ // Cursor was in the text, adjust by the difference in prefix lengths
232
+ const prefixDiff = style.prefix.length - existingPrefixLength
233
+ selectionStart = originalStart + prefixDiff
234
+ }
235
+ } else {
236
+ // We're adding a new header
237
+ // Move cursor by the length of the new prefix
238
+ selectionStart = originalStart + style.prefix.length
239
+ }
240
+
241
+ // Ensure cursor doesn't go past end of line
242
+ selectionStart = Math.min(selectionStart, lineStart + newLine.length)
243
+ selectionEnd = selectionStart
244
+ } else {
245
+ // Keep selection if there was one
246
+ if (isRemoving) {
247
+ selectionStart = lineStart
248
+ selectionEnd = lineStart + newLine.length
249
+ } else {
250
+ selectionStart = lineStart + style.prefix.length
251
+ selectionEnd = lineStart + newLine.length
252
+ }
253
+ }
254
+
148
255
  const result = {
149
256
  text: newLine,
150
- selectionStart: lineStart + style.prefix.length,
151
- selectionEnd: lineStart + newLine.length
257
+ selectionStart,
258
+ selectionEnd
152
259
  }
153
260
 
154
261
  insertText(textarea, result)
155
262
  }
156
263
 
264
+ /**
265
+ * Toggle H1 header
266
+ */
267
+ export function toggleH1(textarea) {
268
+ insertHeader(textarea, 1, true)
269
+ }
270
+
271
+ /**
272
+ * Toggle H2 header
273
+ */
274
+ export function toggleH2(textarea) {
275
+ insertHeader(textarea, 2, true)
276
+ }
277
+
278
+ /**
279
+ * Toggle H3 header
280
+ */
281
+ export function toggleH3(textarea) {
282
+ insertHeader(textarea, 3, true)
283
+ }
284
+
157
285
  /**
158
286
  * Get active formats at cursor position
159
287
  */
@@ -208,6 +336,11 @@ export { preserveSelection }
208
336
  */
209
337
  export { setUndoMethod }
210
338
 
339
+ /**
340
+ * Debug mode control
341
+ */
342
+ export { setDebugMode, getDebugMode }
343
+
211
344
  /**
212
345
  * Default export with all functions
213
346
  */
@@ -219,11 +352,17 @@ export default {
219
352
  toggleBulletList,
220
353
  toggleNumberedList,
221
354
  toggleQuote,
355
+ toggleTaskList,
222
356
  insertHeader,
357
+ toggleH1,
358
+ toggleH2,
359
+ toggleH3,
223
360
  getActiveFormats,
224
361
  hasFormat,
225
362
  expandSelection,
226
363
  applyCustomFormat,
227
364
  preserveSelection,
228
- setUndoMethod
365
+ setUndoMethod,
366
+ setDebugMode,
367
+ getDebugMode
229
368
  }
@@ -102,7 +102,8 @@ export function styleSelectedText(textarea, style) {
102
102
  }
103
103
 
104
104
  /**
105
- * Apply multiline styling (quotes, etc)
105
+ * Apply multiline styling (quotes, task lists, etc)
106
+ * Note: This does NOT expand selection to line - that should be done by the caller if needed
106
107
  */
107
108
  export function multilineStyle(textarea, style) {
108
109
  const { prefix, suffix, surroundWithNewlines } = style