@pinkpixel/marzipan 1.0.5

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/docs/api.md ADDED
@@ -0,0 +1,749 @@
1
+ # Marzipan API Reference
2
+
3
+ A comprehensive guide to the core Marzipan class, bundled action toolkit, and first-party plugin exports.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Quick Start](#quick-start)
8
+ - [Constructor](#constructor)
9
+ - [Instance Methods](#instance-methods)
10
+ - [Static Methods](#static-methods)
11
+ - [Actions API](#actions-api)
12
+ - [Options](#options)
13
+ - [Themes](#themes)
14
+ - [Plugin Exports](#plugin-exports)
15
+ - [Events](#events)
16
+ - [Examples](#examples)
17
+
18
+ ## Quick Start
19
+
20
+ ```javascript
21
+ // Import Marzipan
22
+ import { Marzipan } from '@pinkpixel/marzipan';
23
+
24
+ // Initialize single editor
25
+ const [editor] = new Marzipan('#my-editor');
26
+
27
+ // Or use the static helper for multiple elements
28
+ const editors = Marzipan.init('.markdown-editor');
29
+ ```
30
+
31
+ ## Constructor
32
+
33
+ ### `new Marzipan(target, options)`
34
+
35
+ Creates one or more Marzipan editor instances.
36
+
37
+ **Parameters:**
38
+ - `target` *(string|Element|NodeList|Array)* - Target element(s) to initialize
39
+ - `string` - CSS selector
40
+ - `Element` - Single DOM element
41
+ - `NodeList` - Collection of DOM elements
42
+ - `Array` - Array of DOM elements
43
+ - `options` *(Object)* - Configuration options (see [Options](#options))
44
+
45
+ **Returns:** `Array<Marzipan>` - Array of Marzipan instances
46
+
47
+ **Example:**
48
+ ```javascript
49
+ // Single element by selector
50
+ const editors = new Marzipan('#editor');
51
+
52
+ // Multiple elements
53
+ const editors = new Marzipan('.markdown-editor');
54
+
55
+ // With options
56
+ const editors = new Marzipan('#editor', {
57
+ fontSize: '16px',
58
+ toolbar: true,
59
+ showStats: true
60
+ });
61
+ ```
62
+
63
+ ## Instance Methods
64
+
65
+ ### Content Management
66
+
67
+ #### `getValue()`
68
+ Get the current markdown content from the editor.
69
+
70
+ **Returns:** `string` - Current markdown content
71
+
72
+ ```javascript
73
+ const content = editor.getValue();
74
+ console.log(content); // "# Hello World\n\nThis is markdown content"
75
+ ```
76
+
77
+ #### `setValue(value)`
78
+ Set the markdown content in the editor.
79
+
80
+ **Parameters:**
81
+ - `value` *(string)* - Markdown content to set
82
+
83
+ ```javascript
84
+ editor.setValue('# New Content\n\nThis replaces the existing content.');
85
+ ```
86
+
87
+ #### `getRenderedHTML(options)`
88
+ Get the rendered HTML of the current content.
89
+
90
+ **Parameters:**
91
+ - `options` *(Object)* - Rendering options
92
+ - `cleanHTML` *(boolean)* - Remove syntax markers and Marzipan-specific classes
93
+
94
+ **Returns:** `string` - Rendered HTML
95
+
96
+ ```javascript
97
+ // Get HTML with syntax highlighting
98
+ const html = editor.getRenderedHTML();
99
+
100
+ // Get clean HTML for export
101
+ const cleanHtml = editor.getRenderedHTML({ cleanHTML: true });
102
+ ```
103
+
104
+ #### `getPreviewHTML()`
105
+ Get the current preview element's HTML (includes all syntax markers).
106
+
107
+ **Returns:** `string` - Current preview HTML as displayed
108
+
109
+ #### `getCleanHTML()`
110
+ Get clean HTML without any Marzipan-specific markup. Shorthand for `getRenderedHTML({ cleanHTML: true })`.
111
+
112
+ **Returns:** `string` - Clean HTML suitable for export
113
+
114
+ ### Focus Management
115
+
116
+ #### `focus()`
117
+ Focus the editor textarea.
118
+
119
+ ```javascript
120
+ editor.focus();
121
+ ```
122
+
123
+ #### `blur()`
124
+ Blur the editor textarea.
125
+
126
+ ```javascript
127
+ editor.blur();
128
+ ```
129
+
130
+ ### Display Modes
131
+
132
+ #### `showPlainTextarea(show)`
133
+ Toggle between plain textarea and overlay markdown preview.
134
+
135
+ **Parameters:**
136
+ - `show` *(boolean)* - `true` to show plain textarea, `false` to show overlay
137
+
138
+ **Returns:** `boolean` - Current plain textarea state
139
+
140
+ ```javascript
141
+ // Show plain textarea (hide markdown preview)
142
+ editor.showPlainTextarea(true);
143
+
144
+ // Show markdown overlay
145
+ editor.showPlainTextarea(false);
146
+ ```
147
+
148
+ #### `showPreviewMode(show)`
149
+ Toggle between edit and preview-only modes.
150
+
151
+ **Parameters:**
152
+ - `show` *(boolean)* - `true` for preview mode, `false` for edit mode
153
+
154
+ **Returns:** `boolean` - Current preview mode state
155
+
156
+ ```javascript
157
+ // Switch to preview-only mode
158
+ editor.showPreviewMode(true);
159
+
160
+ // Switch back to edit mode
161
+ editor.showPreviewMode(false);
162
+ ```
163
+
164
+ #### `showStats(show)`
165
+ Show or hide the statistics bar.
166
+
167
+ **Parameters:**
168
+ - `show` *(boolean)* - Whether to show statistics
169
+
170
+ ```javascript
171
+ editor.showStats(true); // Show character/word/line counts
172
+ editor.showStats(false); // Hide statistics
173
+ ```
174
+
175
+ ### Editor State
176
+
177
+ #### `updatePreview()`
178
+ Manually update the markdown preview. Usually called automatically.
179
+
180
+ ```javascript
181
+ editor.updatePreview();
182
+ ```
183
+
184
+ #### `isInitialized()`
185
+ Check if the editor is fully initialized.
186
+
187
+ **Returns:** `boolean` - Initialization status
188
+
189
+ ```javascript
190
+ if (editor.isInitialized()) {
191
+ console.log('Editor is ready!');
192
+ }
193
+ ```
194
+
195
+ #### `reinit(options)`
196
+ Re-initialize the editor with new options.
197
+
198
+ **Parameters:**
199
+ - `options` *(Object)* - New options to apply (merged with existing options)
200
+
201
+ ```javascript
202
+ editor.reinit({
203
+ fontSize: '18px',
204
+ toolbar: false
205
+ });
206
+ ```
207
+
208
+ #### `destroy()`
209
+ Destroy the editor instance and cleanup resources.
210
+
211
+ ```javascript
212
+ editor.destroy();
213
+ ```
214
+
215
+ ## Static Methods
216
+
217
+ ### `Marzipan.init(target, options)`
218
+ Convenience method to create new instances. Equivalent to `new Marzipan()`.
219
+
220
+ **Parameters:**
221
+ - `target` *(string|Element|NodeList|Array)* - Target element(s)
222
+ - `options` *(Object)* - Configuration options
223
+
224
+ **Returns:** `Array<Marzipan>` - Array of instances
225
+
226
+ ### `Marzipan.getInstance(element)`
227
+ Get existing Marzipan instance from a DOM element.
228
+
229
+ **Parameters:**
230
+ - `element` *(Element)* - DOM element
231
+
232
+ **Returns:** `Marzipan|null` - Instance or null if not found
233
+
234
+ ```javascript
235
+ const element = document.getElementById('my-editor');
236
+ const instance = Marzipan.getInstance(element);
237
+ ```
238
+
239
+ ### `Marzipan.destroyAll()`
240
+ Destroy all existing Marzipan instances.
241
+
242
+ ```javascript
243
+ Marzipan.destroyAll();
244
+ ```
245
+
246
+ ### `Marzipan.setTheme(theme, customColors)`
247
+ Set global theme for all Marzipan instances.
248
+
249
+ **Parameters:**
250
+ - `theme` *(string|Object)* - Theme name or custom theme object
251
+ - `customColors` *(Object)* - Optional color overrides
252
+
253
+ ```javascript
254
+ // Use built-in theme
255
+ Marzipan.setTheme('cave');
256
+
257
+ // Use custom theme
258
+ Marzipan.setTheme({
259
+ name: 'custom',
260
+ colors: {
261
+ background: '#1a1a1a',
262
+ text: '#ffffff'
263
+ }
264
+ });
265
+
266
+ // Override colors
267
+ Marzipan.setTheme('solar', {
268
+ background: '#002b36'
269
+ });
270
+ ```
271
+
272
+ ### `Marzipan.injectStyles(force)`
273
+ Manually inject Marzipan styles into the document.
274
+
275
+ **Parameters:**
276
+ - `force` *(boolean)* - Force re-injection even if already injected
277
+
278
+ ```javascript
279
+ Marzipan.injectStyles(true); // Force style re-injection
280
+ ```
281
+
282
+ ## Actions API
283
+
284
+ Marzipan ships a zero-dependency markdown action toolkit alongside the core class. Import the helpers from `@pinkpixel/marzipan` and pass the target `HTMLTextAreaElement`.
285
+
286
+ ```ts
287
+ import { actions } from '@pinkpixel/marzipan';
288
+
289
+ const textarea = document.querySelector('textarea');
290
+ if (textarea) {
291
+ actions.toggleBold(textarea);
292
+ }
293
+ ```
294
+
295
+ Available helpers include:
296
+ - `toggleBold(textarea)`
297
+ - `toggleItalic(textarea)`
298
+ - `toggleCode(textarea)`
299
+ - `insertLink(textarea, options?)`
300
+ - `toggleBulletList(textarea)`
301
+ - `toggleNumberedList(textarea)`
302
+ - `toggleQuote(textarea)`
303
+ - `toggleTaskList(textarea)`
304
+ - `toggleH1/H2/H3(textarea)`
305
+ - `applyCustomFormat(textarea, format)`
306
+ - `getActiveFormats(textarea)`
307
+ - `setUndoMethod(method)` / `getUndoMethod()`
308
+
309
+ These functions mirror the toolbar and keyboard shortcut behaviour, making it easy to build custom UI without extra dependencies.
310
+
311
+ ## Options
312
+
313
+ Default options and their descriptions:
314
+
315
+ ```javascript
316
+ const defaultOptions = {
317
+ // Typography
318
+ fontSize: '14px', // Font size
319
+ lineHeight: 1.6, // Line height multiplier
320
+ fontFamily: '...', // Monospace font stack
321
+ padding: '16px', // Internal padding
322
+
323
+ // Mobile-specific styles
324
+ mobile: {
325
+ fontSize: '16px', // Font size on mobile (prevents zoom)
326
+ padding: '12px', // Reduced padding on mobile
327
+ lineHeight: 1.5 // Adjusted line height
328
+ },
329
+
330
+ // Textarea properties
331
+ textareaProps: {}, // Native textarea attributes
332
+
333
+ // Behavior
334
+ autofocus: false, // Auto-focus on initialization
335
+ autoResize: false, // Auto-expand height with content
336
+ minHeight: '100px', // Minimum height (autoResize mode)
337
+ maxHeight: null, // Maximum height (autoResize mode)
338
+ placeholder: 'Start typing...', // Placeholder text
339
+ value: '', // Initial content
340
+
341
+ // Callbacks
342
+ onChange: null, // Content change callback
343
+ onKeydown: null, // Keydown event callback
344
+
345
+ // Features
346
+ showActiveLineRaw: false, // Show active line without markdown rendering
347
+ showStats: false, // Show statistics bar
348
+ toolbar: false, // Show toolbar (true/false or config object)
349
+ statsFormatter: null, // Custom stats formatter function
350
+ smartLists: true, // Enable smart list continuation
351
+
352
+ // Themes (applied per-instance)
353
+ theme: null // Instance-specific theme override
354
+ };
355
+ ```
356
+
357
+ ### Option Details
358
+
359
+ #### `textareaProps`
360
+ Apply native textarea attributes:
361
+
362
+ ```javascript
363
+ new Marzipan('#editor', {
364
+ textareaProps: {
365
+ 'data-testid': 'markdown-input',
366
+ 'aria-label': 'Markdown editor',
367
+ maxlength: 5000,
368
+ style: { border: '2px solid blue' }
369
+ }
370
+ });
371
+ ```
372
+
373
+ #### `onChange(value, instance)`
374
+ Called when content changes:
375
+
376
+ ```javascript
377
+ new Marzipan('#editor', {
378
+ onChange: (value, instance) => {
379
+ console.log('Content changed:', value.length, 'characters');
380
+ // Save to localStorage, send to server, etc.
381
+ }
382
+ });
383
+ ```
384
+
385
+ #### `onKeydown(event, instance)`
386
+ Called for keydown events:
387
+
388
+ ```javascript
389
+ new Marzipan('#editor', {
390
+ onKeydown: (event, instance) => {
391
+ if (event.key === 'Enter' && event.ctrlKey) {
392
+ console.log('Ctrl+Enter pressed');
393
+ // Custom save logic
394
+ }
395
+ }
396
+ });
397
+ ```
398
+
399
+ #### `statsFormatter(stats)`
400
+ Custom statistics display:
401
+
402
+ ```javascript
403
+ new Marzipan('#editor', {
404
+ showStats: true,
405
+ statsFormatter: (stats) => {
406
+ return `📝 ${stats.words} words • 📏 ${stats.chars} chars • 📄 ${stats.lines} lines`;
407
+ }
408
+ });
409
+ ```
410
+
411
+ Stats object contains:
412
+ - `chars` *(number)* - Character count
413
+ - `words` *(number)* - Word count
414
+ - `lines` *(number)* - Line count
415
+ - `line` *(number)* - Current line number
416
+ - `column` *(number)* - Current column number
417
+
418
+ #### `toolbar`
419
+ Enable toolbar with default buttons:
420
+
421
+ ```javascript
422
+ // Enable with defaults
423
+ new Marzipan('#editor', { toolbar: true });
424
+
425
+ // Custom button configuration
426
+ new Marzipan('#editor', {
427
+ toolbar: {
428
+ buttons: ['bold', 'italic', 'code', '|', 'link', 'quote', '|', 'bulletList', 'orderedList']
429
+ }
430
+ });
431
+ ```
432
+
433
+ Available toolbar buttons:
434
+ - `bold` – **Bold** text
435
+ - `italic` – *Italic* text
436
+ - `code` – `Inline code`
437
+ - `link` – Insert or edit links
438
+ - `quote` – Blockquotes
439
+ - `bulletList` – Unordered lists
440
+ - `orderedList` – Ordered lists
441
+ - `taskList` – Task list checkboxes
442
+ - `h1` / `h2` / `h3` – Heading levels
443
+ - `toggle-view-menu` – Opens the view dropdown (plain/preview/normal)
444
+ - `toggle-plain` – Direct plain/preview toggle (legacy name)
445
+ - `separator` (`|`) – Visual divider
446
+
447
+ ## Themes
448
+
449
+ Marzipan includes built-in themes and supports custom themes.
450
+
451
+ ### Built-in Themes
452
+
453
+ - `solar` - Light theme with warm colors (default)
454
+ - `cave` - Dark theme with cool colors
455
+
456
+ ### Using Themes
457
+
458
+ ```javascript
459
+ // Global theme (affects all instances)
460
+ Marzipan.setTheme('cave');
461
+
462
+ // Instance-specific theme
463
+ new Marzipan('#editor', {
464
+ theme: 'cave' // Only this instance uses cave theme
465
+ });
466
+
467
+ // Custom theme
468
+ const myTheme = {
469
+ name: 'purple',
470
+ colors: {
471
+ background: '#2d1b69',
472
+ text: '#e1d5f7',
473
+ comment: '#9c88c4',
474
+ keyword: '#bb9af7',
475
+ string: '#9ece6a',
476
+ number: '#ff9e64',
477
+ // ... more colors
478
+ }
479
+ };
480
+
481
+ Marzipan.setTheme(myTheme);
482
+ ```
483
+
484
+ ### Theme Color Properties
485
+
486
+ ```javascript
487
+ const themeColors = {
488
+ // Base colors
489
+ background: '#ffffff', // Main background
490
+ text: '#333333', // Primary text
491
+ textMuted: '#666666', // Secondary text
492
+
493
+ // Syntax highlighting
494
+ comment: '#93a1a1', // Comments
495
+ keyword: '#859900', // Keywords (bold, headers)
496
+ string: '#2aa198', // Strings and links
497
+ number: '#d33682', // Numbers
498
+ punctuation: '#586e75', // Punctuation
499
+
500
+ // UI elements
501
+ selection: '#eee8d5', // Text selection
502
+ border: '#e1e1e1', // Borders
503
+ toolbar: '#f8f8f8', // Toolbar background
504
+
505
+ // Interactive elements
506
+ linkHover: '#0066cc', // Link hover color
507
+ buttonActive: '#007acc', // Active button background
508
+ };
509
+ ```
510
+
511
+ ## Events
512
+
513
+ ### Content Events
514
+
515
+ #### Change Event
516
+ Triggered when content changes via the `onChange` callback:
517
+
518
+ ```javascript
519
+ new Marzipan('#editor', {
520
+ onChange: (value, instance) => {
521
+ console.log('Content:', value);
522
+ console.log('Instance ID:', instance.instanceId);
523
+ }
524
+ });
525
+ ```
526
+
527
+ #### Input Events
528
+ Access native textarea events through the `textarea` property:
529
+
530
+ ```javascript
531
+ const editor = new Marzipan('#editor')[0];
532
+
533
+ editor.textarea.addEventListener('focus', () => {
534
+ console.log('Editor focused');
535
+ });
536
+
537
+ editor.textarea.addEventListener('blur', () => {
538
+ console.log('Editor blurred');
539
+ });
540
+ ```
541
+
542
+ ### Keyboard Events
543
+
544
+ #### Global Keyboard Handler
545
+ Use the `onKeydown` callback:
546
+
547
+ ```javascript
548
+ new Marzipan('#editor', {
549
+ onKeydown: (event, instance) => {
550
+ // Handle custom shortcuts
551
+ if (event.ctrlKey && event.key === 's') {
552
+ event.preventDefault();
553
+ saveContent(instance.getValue());
554
+ }
555
+ }
556
+ });
557
+ ```
558
+
559
+ #### Built-in Shortcuts
560
+ Marzipan includes built-in keyboard shortcuts:
561
+
562
+ - `Tab` / `Shift+Tab` - Indent/outdent text
563
+ - `Enter` - Smart list continuation
564
+ - `Ctrl+B` / `Cmd+B` - Toggle bold (with toolbar)
565
+ - `Ctrl+I` / `Cmd+I` - Toggle italic (with toolbar)
566
+ - `Ctrl+K` / `Cmd+K` - Insert link (with toolbar)
567
+
568
+ ## Examples
569
+
570
+ ### Basic Editor
571
+
572
+ ```javascript
573
+ // Simple editor
574
+ const editors = new Marzipan('#editor', {
575
+ placeholder: 'Write your markdown here...',
576
+ value: '# Hello World\n\nStart writing!'
577
+ });
578
+ ```
579
+
580
+ ### Full-Featured Editor
581
+
582
+ ```javascript
583
+ // Editor with all features
584
+ const editors = new Marzipan('#advanced-editor', {
585
+ // Appearance
586
+ fontSize: '16px',
587
+ lineHeight: 1.7,
588
+ theme: 'cave',
589
+
590
+ // Features
591
+ toolbar: true,
592
+ showStats: true,
593
+ autoResize: true,
594
+ minHeight: '200px',
595
+ maxHeight: '600px',
596
+
597
+ // Behavior
598
+ autofocus: true,
599
+ smartLists: true,
600
+ showActiveLineRaw: true,
601
+
602
+ // Events
603
+ onChange: (value, instance) => {
604
+ localStorage.setItem('markdown-content', value);
605
+ },
606
+
607
+ // Custom stats
608
+ statsFormatter: ({ words, chars, lines, line, column }) => {
609
+ const readingTime = Math.ceil(words / 200);
610
+ return `${words} words (${readingTime} min read) • Line ${line}:${column}`;
611
+ }
612
+ });
613
+
614
+ const editor = editors[0];
615
+ ```
616
+
617
+ ### Multiple Editors
618
+
619
+ ```javascript
620
+ // Initialize multiple editors with different configs
621
+ const editors = Marzipan.init('.editor', {
622
+ toolbar: true,
623
+ showStats: true
624
+ });
625
+
626
+ // Customize individual editors
627
+ editors[0].reinit({ theme: 'solar' });
628
+ editors[1].reinit({ theme: 'cave' });
629
+
630
+ // Handle all editors
631
+ editors.forEach((editor, index) => {
632
+ editor.setValue(`# Editor ${index + 1}\n\nContent here...`);
633
+ });
634
+ ```
635
+
636
+ ### Custom Theme
637
+
638
+ ```javascript
639
+ // Create custom theme
640
+ const darkTheme = {
641
+ name: 'dark-purple',
642
+ colors: {
643
+ background: '#1a1a2e',
644
+ text: '#eee',
645
+ comment: '#888',
646
+ keyword: '#9d65ff',
647
+ string: '#5fb3d4',
648
+ number: '#f093a3',
649
+ selection: '#2d2d4d',
650
+ border: '#333',
651
+ toolbar: '#16213e'
652
+ }
653
+ };
654
+
655
+ // Apply globally
656
+ Marzipan.setTheme(darkTheme);
657
+
658
+ // Or per instance
659
+ new Marzipan('#editor', {
660
+ theme: darkTheme
661
+ });
662
+ ```
663
+
664
+ ### Integration Example
665
+
666
+ ```javascript
667
+ // Complete integration example
668
+ class MarkdownApp {
669
+ constructor() {
670
+ this.editors = new Marzipan('.markdown-editor', {
671
+ toolbar: true,
672
+ showStats: true,
673
+ autoResize: true,
674
+ onChange: this.handleChange.bind(this)
675
+ });
676
+
677
+ this.editor = this.editors[0];
678
+ this.setupCustomButtons();
679
+ }
680
+
681
+ handleChange(value, instance) {
682
+ // Auto-save
683
+ clearTimeout(this.saveTimeout);
684
+ this.saveTimeout = setTimeout(() => {
685
+ this.saveToServer(value);
686
+ }, 1000);
687
+
688
+ // Update word count display
689
+ this.updateWordCount(value);
690
+ }
691
+
692
+ setupCustomButtons() {
693
+ // Add custom save button
694
+ const toolbar = this.editor.container.querySelector('.marzipan-toolbar');
695
+ const saveBtn = document.createElement('button');
696
+ saveBtn.textContent = '💾 Save';
697
+ saveBtn.onclick = () => this.saveToServer(this.editor.getValue());
698
+ toolbar.appendChild(saveBtn);
699
+ }
700
+
701
+ saveToServer(content) {
702
+ fetch('/api/save', {
703
+ method: 'POST',
704
+ headers: { 'Content-Type': 'application/json' },
705
+ body: JSON.stringify({ content })
706
+ });
707
+ }
708
+
709
+ updateWordCount(content) {
710
+ const words = content.split(/\s+/).filter(w => w.length > 0).length;
711
+ document.getElementById('word-count').textContent = `${words} words`;
712
+ }
713
+ }
714
+
715
+ // Initialize app
716
+ const app = new MarkdownApp();
717
+ ```
718
+
719
+ ## Plugin Exports
720
+
721
+ Every plugin located in `src/plugins` publishes as `@pinkpixel/marzipan/plugins/<name>`. Each export is a factory so you can configure behaviour before passing it to the editor:
722
+
723
+ ```ts
724
+ import { tablePlugin } from '@pinkpixel/marzipan/plugins/tablePlugin';
725
+ import { mermaidPlugin } from '@pinkpixel/marzipan/plugins/mermaidPlugin';
726
+
727
+ new Marzipan('#editor', {
728
+ plugins: [tablePlugin(), mermaidPlugin({ theme: 'dark' })],
729
+ });
730
+ ```
731
+
732
+ Refer to [docs/plugins.md](./plugins.md) for the full catalogue and configuration options.
733
+
734
+ ## Browser Support
735
+
736
+ Marzipan supports modern browsers:
737
+
738
+ - Chrome 60+
739
+ - Firefox 55+
740
+ - Safari 11+
741
+ - Edge 79+
742
+
743
+ Key requirements:
744
+ - CSS Custom Properties (CSS Variables)
745
+ - ES6 Classes
746
+ - Template Literals
747
+ - Modern DOM APIs
748
+
749
+ For older browser support, consider using appropriate polyfills and transpilation.