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/LICENSE ADDED
@@ -0,0 +1,33 @@
1
+ MIT License
2
+
3
+ Original Work:
4
+ Copyright (c) 2017-2018 GitHub, Inc.
5
+
6
+ Derivative Work (markdown-actions):
7
+ Copyright (c) 2024
8
+
9
+ Permission is hereby granted, free of charge, to any person obtaining a copy
10
+ of this software and associated documentation files (the "Software"), to deal
11
+ in the Software without restriction, including without limitation the rights
12
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ copies of the Software, and to permit persons to whom the Software is
14
+ furnished to do so, subject to the following conditions:
15
+
16
+ The above copyright notice and this permission notice shall be included in all
17
+ copies or substantial portions of the Software.
18
+
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
+ SOFTWARE.
26
+
27
+ ---
28
+
29
+ This software is derived from markdown-toolbar-element by GitHub, Inc.
30
+ https://github.com/github/markdown-toolbar-element
31
+
32
+ The core text manipulation algorithms, selection handling logic, and approach
33
+ to markdown formatting were extracted and adapted from GitHub's implementation.
package/README.md ADDED
@@ -0,0 +1,379 @@
1
+ # markdown-actions
2
+
3
+ A lightweight, zero-dependency JavaScript library for adding markdown formatting functionality to any textarea element. Extracted and refined from [GitHub's markdown-toolbar-element](https://github.com/github/markdown-toolbar-element).
4
+
5
+ ## ๐ŸŽฏ What is this?
6
+
7
+ `markdown-actions` provides a simple, functional API for implementing markdown toolbar buttons that work with any `<textarea>`. It handles all the complex text manipulation, selection management, and cursor positioning that makes markdown editing feel natural.
8
+
9
+ ### Key Features
10
+
11
+ - ๐Ÿชถ **Lightweight** - ~3-4kb gzipped, zero dependencies
12
+ - ๐ŸŽจ **Framework agnostic** - Works with React, Vue, vanilla JS, or any framework
13
+ - โšก **Simple API** - Just pass a textarea element to any function
14
+ - ๐Ÿ”„ **Smart toggling** - Automatically detects and removes existing formatting
15
+ - ๐Ÿ“ **Native undo/redo** - Integrates with browser's built-in undo stack
16
+ - ๐ŸŽฏ **Precise cursor control** - Maintains logical cursor position after operations
17
+ - ๐ŸŒณ **Tree-shakeable** - Import only the functions you need
18
+
19
+ ## ๐Ÿ“– Origin Story
20
+
21
+ This library was born from the need for a simple, reliable markdown toolbar implementation. After discovering that building a tokenizer-based approach led to complex state management and text corruption issues, we studied GitHub's battle-tested [markdown-toolbar-element](https://github.com/github/markdown-toolbar-element) and extracted its core logic.
22
+
23
+ The key insight: **you don't need to parse markdown to manipulate it effectively**. Instead of tokenizing, this library uses direct string manipulation with smart selection expansion - a simpler, more reliable approach proven in production on GitHub.com.
24
+
25
+ ## ๐Ÿš€ Quick Start
26
+
27
+ ```html
28
+ <!-- Your HTML -->
29
+ <button id="bold-btn">Bold</button>
30
+ <textarea id="editor"></textarea>
31
+
32
+ <!-- Using the library -->
33
+ <script type="module">
34
+ import { toggleBold } from './markdown-actions/src/index.js'
35
+
36
+ const textarea = document.getElementById('editor')
37
+
38
+ document.getElementById('bold-btn').addEventListener('click', () => {
39
+ toggleBold(textarea)
40
+ textarea.focus()
41
+ })
42
+ </script>
43
+ ```
44
+
45
+ ## ๐Ÿ“š API Reference
46
+
47
+ ### Formatting Functions
48
+
49
+ All functions take a `HTMLTextAreaElement` as their first parameter:
50
+
51
+ ```javascript
52
+ toggleBold(textarea) // **text**
53
+ toggleItalic(textarea) // _text_
54
+ toggleCode(textarea) // `code` or ```block```
55
+ toggleQuote(textarea) // > quote
56
+ toggleBulletList(textarea) // - item
57
+ toggleNumberedList(textarea) // 1. item
58
+ insertLink(textarea, options) // [text](url)
59
+ insertHeader(textarea, level) // # Header
60
+ ```
61
+
62
+ ### Utility Functions
63
+
64
+ ```javascript
65
+ // Get active formats at cursor position
66
+ getActiveFormats(textarea) // Returns: ['bold', 'italic', ...]
67
+
68
+ // Check if specific format is active
69
+ hasFormat(textarea, 'bold') // Returns: true/false
70
+
71
+ // Expand selection to word or line boundaries
72
+ expandSelection(textarea, { toWord: true })
73
+
74
+ // Apply custom markdown format
75
+ applyCustomFormat(textarea, {
76
+ prefix: '==',
77
+ suffix: '==',
78
+ trimFirst: true
79
+ })
80
+ ```
81
+
82
+ ### Options
83
+
84
+ ```javascript
85
+ // Link options
86
+ insertLink(textarea, {
87
+ url: 'https://example.com', // Pre-fill URL
88
+ text: 'Link text' // Pre-fill text
89
+ })
90
+ ```
91
+
92
+ ## ๐Ÿ’ก Usage Examples
93
+
94
+ ### React Component
95
+
96
+ ```jsx
97
+ import { toggleBold, toggleItalic, getActiveFormats } from 'markdown-actions'
98
+
99
+ function MarkdownEditor() {
100
+ const textareaRef = useRef()
101
+ const [activeFormats, setActiveFormats] = useState([])
102
+
103
+ const updateFormats = () => {
104
+ setActiveFormats(getActiveFormats(textareaRef.current))
105
+ }
106
+
107
+ const handleBold = () => {
108
+ toggleBold(textareaRef.current)
109
+ updateFormats()
110
+ }
111
+
112
+ return (
113
+ <>
114
+ <button
115
+ className={activeFormats.includes('bold') ? 'active' : ''}
116
+ onClick={handleBold}>
117
+ Bold
118
+ </button>
119
+ <textarea
120
+ ref={textareaRef}
121
+ onSelect={updateFormats}
122
+ onKeyUp={updateFormats}
123
+ />
124
+ </>
125
+ )
126
+ }
127
+ ```
128
+
129
+ ### Vue Component
130
+
131
+ ```vue
132
+ <template>
133
+ <div>
134
+ <button @click="makeBold" :class="{ active: isBold }">Bold</button>
135
+ <textarea ref="editor" @select="checkFormats"></textarea>
136
+ </div>
137
+ </template>
138
+
139
+ <script>
140
+ import { toggleBold, hasFormat } from 'markdown-actions'
141
+
142
+ export default {
143
+ data() {
144
+ return {
145
+ isBold: false
146
+ }
147
+ },
148
+ methods: {
149
+ makeBold() {
150
+ toggleBold(this.$refs.editor)
151
+ this.checkFormats()
152
+ },
153
+ checkFormats() {
154
+ this.isBold = hasFormat(this.$refs.editor, 'bold')
155
+ }
156
+ }
157
+ }
158
+ </script>
159
+ ```
160
+
161
+ ### Keyboard Shortcuts
162
+
163
+ ```javascript
164
+ textarea.addEventListener('keydown', (e) => {
165
+ if (e.ctrlKey || e.metaKey) {
166
+ switch(e.key) {
167
+ case 'b':
168
+ e.preventDefault()
169
+ toggleBold(textarea)
170
+ break
171
+ case 'i':
172
+ e.preventDefault()
173
+ toggleItalic(textarea)
174
+ break
175
+ case 'k':
176
+ e.preventDefault()
177
+ insertLink(textarea)
178
+ break
179
+ }
180
+ }
181
+ })
182
+ ```
183
+
184
+ ### Custom Formats
185
+
186
+ ```javascript
187
+ // Highlight format
188
+ applyCustomFormat(textarea, {
189
+ prefix: '==',
190
+ suffix: '==',
191
+ trimFirst: true
192
+ })
193
+
194
+ // Spoiler format
195
+ applyCustomFormat(textarea, {
196
+ prefix: '||',
197
+ suffix: '||'
198
+ })
199
+
200
+ // Custom block format
201
+ applyCustomFormat(textarea, {
202
+ prefix: '>>> ',
203
+ multiline: true,
204
+ surroundWithNewlines: true
205
+ })
206
+ ```
207
+
208
+ ## ๐Ÿ—๏ธ How It Works
209
+
210
+ Unlike complex tokenizer-based approaches, `markdown-actions` uses a simpler, more reliable method:
211
+
212
+ 1. **Smart Selection** - Expands selections to word/line boundaries when needed
213
+ 2. **Pattern Matching** - Simple string checks to detect existing formatting
214
+ 3. **Direct Manipulation** - Modifies text directly without parsing
215
+ 4. **Position Tracking** - Calculates new cursor positions based on changes
216
+ 5. **Native Integration** - Uses `document.execCommand` for undo/redo support
217
+
218
+ This approach avoids the pitfalls of tokenization:
219
+ - No position corruption
220
+ - No text duplication
221
+ - No state management complexity
222
+ - No parsing edge cases
223
+
224
+ ### ๐ŸŽฏ Selection Preservation - How Text Stays Selected
225
+
226
+ One of the most polished features of `markdown-actions` is how it preserves text selection after formatting. When you click "Bold" in a toolbar, the text stays selected, allowing for immediate additional formatting. Here's the step-by-step process:
227
+
228
+ #### Step 1: Capture Original Selection
229
+ ```javascript
230
+ // In blockStyle() - src/operations/block.js:17-18
231
+ const originalSelectionStart = textarea.selectionStart
232
+ const originalSelectionEnd = textarea.selectionEnd
233
+ ```
234
+
235
+ #### Step 2: Calculate New Selection After Formatting
236
+ When adding formatting (e.g., `**text**`):
237
+ ```javascript
238
+ // src/operations/block.js:58-60
239
+ let replacementText = prefixToUse + selectedText + suffixToUse
240
+ selectionStart = originalSelectionStart + prefixToUse.length
241
+ selectionEnd = originalSelectionEnd + prefixToUse.length
242
+ ```
243
+
244
+ When removing formatting (toggle off):
245
+ ```javascript
246
+ // src/operations/block.js:46-54
247
+ const replacementText = selectedText.slice(prefixToUse.length, -suffixToUse.length)
248
+ if (originalSelectionStart === originalSelectionEnd) {
249
+ // Cursor position (no selection)
250
+ let position = originalSelectionStart - prefixToUse.length
251
+ selectionStart = selectionEnd = position
252
+ } else {
253
+ // Keep selection on unformatted text
254
+ selectionEnd = selectionStart + replacementText.length
255
+ }
256
+ ```
257
+
258
+ #### Step 3: Apply Text Change with Native Undo Support
259
+ ```javascript
260
+ // In insertText() - src/core/insertion.js:24-31
261
+ textarea.contentEditable = 'true'
262
+ try {
263
+ // Try native method first (preserves undo/redo)
264
+ canInsertText = document.execCommand('insertText', false, text)
265
+ } catch (error) {
266
+ canInsertText = false
267
+ }
268
+ textarea.contentEditable = 'false'
269
+ ```
270
+
271
+ #### Step 4: Restore Selection at Calculated Position
272
+ ```javascript
273
+ // src/core/insertion.js:56-60
274
+ if (selectionStart != null && selectionEnd != null) {
275
+ textarea.setSelectionRange(selectionStart, selectionEnd)
276
+ } else {
277
+ textarea.setSelectionRange(originalSelectionStart, textarea.selectionEnd)
278
+ }
279
+ ```
280
+
281
+ #### Why This Matters
282
+ This careful selection management creates a professional editing experience:
283
+ - **Multiple formats**: Apply bold, then immediately italic without reselecting
284
+ - **Visual feedback**: See exactly what text is affected
285
+ - **Keyboard friendly**: Selection persists for keyboard shortcuts
286
+ - **Undo/redo aware**: Selection state is part of the undo stack
287
+
288
+ #### Example Flow
289
+ 1. User selects "hello world"
290
+ 2. User clicks Bold button
291
+ 3. Text becomes "**hello world**"
292
+ 4. "hello world" remains selected (without the ** markers)
293
+ 5. User can immediately click Italic to get "**_hello world_**"
294
+
295
+ ## ๐Ÿงช Testing
296
+
297
+ Open `test.html` in a browser to run the comprehensive test suite with 38 test cases covering:
298
+
299
+ - All formatting functions
300
+ - Edge cases and boundary conditions
301
+ - Selection management
302
+ - Format detection
303
+ - Custom formats
304
+
305
+ ## ๐Ÿ“ฆ Installation
306
+
307
+ Currently, this is a standalone library. To use it:
308
+
309
+ 1. Copy the `markdown-actions` folder to your project
310
+ 2. Import the functions you need:
311
+
312
+ ```javascript
313
+ import { toggleBold, toggleItalic } from './markdown-actions/src/index.js'
314
+ ```
315
+
316
+ ### Future npm Package
317
+
318
+ ```bash
319
+ # Coming soon
320
+ npm install markdown-actions
321
+ ```
322
+
323
+ ## ๐Ÿค Contributing
324
+
325
+ Contributions are welcome! The codebase is intentionally simple and well-commented. Key files:
326
+
327
+ - `src/index.js` - Public API functions
328
+ - `src/core/` - Core utilities (selection, insertion, formats)
329
+ - `src/operations/` - Text manipulation logic
330
+ - `demo.html` - Interactive demonstration
331
+ - `test.html` - Test suite
332
+
333
+ ## ๐Ÿ“„ License
334
+
335
+ MIT License - see [LICENSE](LICENSE) file for details.
336
+
337
+ This project is derived from [GitHub's markdown-toolbar-element](https://github.com/github/markdown-toolbar-element), which is also MIT licensed.
338
+
339
+ ## ๐Ÿ™ Credits
340
+
341
+ This library is based on the excellent work by GitHub, Inc. on [markdown-toolbar-element](https://github.com/github/markdown-toolbar-element).
342
+
343
+ **Original Copyright:** Copyright (c) 2017-2018 GitHub, Inc.
344
+
345
+ The core text manipulation logic, selection handling algorithms, and the approach to markdown formatting were extracted and adapted from GitHub's implementation. We're grateful for their decision to open-source this battle-tested code that powers markdown editing on GitHub.com.
346
+
347
+ ### Key Contributors
348
+
349
+ - **GitHub, Inc.** - Original implementation and algorithms
350
+ - **markdown-toolbar-element contributors** - Ongoing improvements to the source implementation
351
+
352
+ ## ๐Ÿ”ฎ Future Plans
353
+
354
+ - [ ] npm package publication
355
+ - [ ] TypeScript definitions
356
+ - [ ] Table formatting support
357
+ - [ ] Footnote support
358
+ - [ ] More custom format templates
359
+ - [ ] Plugin system for extensions
360
+ - [ ] Framework-specific wrappers (React hooks, Vue composables)
361
+
362
+ ## ๐Ÿ› Known Issues
363
+
364
+ - Multi-cursor selection not supported (browser limitation)
365
+ - Some IME (Input Method Editor) edge cases with Asian languages
366
+ - Undo grouping may vary between browsers
367
+
368
+ ## ๐Ÿ“Š Browser Support
369
+
370
+ - โœ… Chrome/Edge (latest)
371
+ - โœ… Firefox (latest)
372
+ - โœ… Safari (latest)
373
+ - โœ… Mobile browsers (iOS Safari, Chrome Android)
374
+
375
+ Requires: `HTMLTextAreaElement.selectionStart/End` and `setSelectionRange()`
376
+
377
+ ---
378
+
379
+ Built with โค๏ธ using proven patterns from GitHub's markdown-toolbar-element