@pinkpixel/marzipan 1.0.5 → 1.0.7
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/dist/_basePickBy-BXyyc71p.js +152 -0
- package/dist/_basePickBy-BXyyc71p.js.map +1 -0
- package/dist/_baseUniq-Dde5pUWM.js +615 -0
- package/dist/_baseUniq-Dde5pUWM.js.map +1 -0
- package/dist/actions/core/detection.d.ts +11 -0
- package/dist/actions/core/detection.d.ts.map +1 -0
- package/dist/actions/core/detection.js +142 -0
- package/dist/actions/core/detection.js.map +1 -0
- package/dist/actions/core/formats.d.ts +5 -0
- package/dist/actions/core/formats.d.ts.map +1 -0
- package/dist/actions/core/formats.js +75 -0
- package/dist/actions/core/formats.js.map +1 -0
- package/dist/actions/core/insertion.d.ts +5 -0
- package/dist/actions/core/insertion.d.ts.map +1 -0
- package/dist/actions/core/insertion.js +116 -0
- package/dist/actions/core/insertion.js.map +1 -0
- package/dist/actions/core/selection.d.ts +13 -0
- package/dist/actions/core/selection.d.ts.map +1 -0
- package/dist/actions/core/selection.js +137 -0
- package/dist/actions/core/selection.js.map +1 -0
- package/dist/actions/debug.d.ts +10 -0
- package/dist/actions/debug.d.ts.map +1 -0
- package/dist/actions/debug.js +39 -0
- package/dist/actions/debug.js.map +1 -0
- package/dist/actions/index.d.ts +48 -0
- package/dist/actions/index.d.ts.map +1 -0
- package/dist/actions/index.js +237 -0
- package/dist/actions/index.js.map +1 -0
- package/dist/actions/operations/block.d.ts +5 -0
- package/dist/actions/operations/block.d.ts.map +1 -0
- package/dist/actions/operations/block.js +120 -0
- package/dist/actions/operations/block.js.map +1 -0
- package/dist/actions/operations/list.d.ts +4 -0
- package/dist/actions/operations/list.d.ts.map +1 -0
- package/dist/actions/operations/list.js +151 -0
- package/dist/actions/operations/list.js.map +1 -0
- package/dist/actions/types.d.ts +43 -0
- package/dist/actions/types.d.ts.map +1 -0
- package/dist/actions/types.js +2 -0
- package/dist/actions/types.js.map +1 -0
- package/dist/arc-CfMWIGxd.js +84 -0
- package/dist/arc-CfMWIGxd.js.map +1 -0
- package/dist/architectureDiagram-VXUJARFQ-Bb4i7x-6.js +4663 -0
- package/dist/architectureDiagram-VXUJARFQ-Bb4i7x-6.js.map +1 -0
- package/dist/blockDiagram-VD42YOAC-ldSoJhgA.js +2262 -0
- package/dist/blockDiagram-VD42YOAC-ldSoJhgA.js.map +1 -0
- package/dist/c4Diagram-YG6GDRKO-D-ENeA7h.js +1581 -0
- package/dist/c4Diagram-YG6GDRKO-D-ENeA7h.js.map +1 -0
- package/dist/channel-BCTxewZI.js +6 -0
- package/dist/channel-BCTxewZI.js.map +1 -0
- package/dist/chunk-4BX2VUAB-Cfh_4i4X.js +9 -0
- package/dist/chunk-4BX2VUAB-Cfh_4i4X.js.map +1 -0
- package/dist/chunk-55IACEB6-DNOIVRvW.js +9 -0
- package/dist/chunk-55IACEB6-DNOIVRvW.js.map +1 -0
- package/dist/chunk-B4BG7PRW-C_qWbbk6.js +1376 -0
- package/dist/chunk-B4BG7PRW-C_qWbbk6.js.map +1 -0
- package/dist/chunk-DI55MBZ5-D_yXJA03.js +1371 -0
- package/dist/chunk-DI55MBZ5-D_yXJA03.js.map +1 -0
- package/dist/chunk-FMBD7UC4-B9NJjOBf.js +20 -0
- package/dist/chunk-FMBD7UC4-B9NJjOBf.js.map +1 -0
- package/dist/chunk-QN33PNHL-CVrLUVsI.js +20 -0
- package/dist/chunk-QN33PNHL-CVrLUVsI.js.map +1 -0
- package/dist/chunk-QZHKN3VN-FvAf_VJR.js +16 -0
- package/dist/chunk-QZHKN3VN-FvAf_VJR.js.map +1 -0
- package/dist/chunk-TZMSLE5B-DxYTPD3t.js +65 -0
- package/dist/chunk-TZMSLE5B-DxYTPD3t.js.map +1 -0
- package/dist/classDiagram-2ON5EDUG-Cg641kit.js +17 -0
- package/dist/classDiagram-2ON5EDUG-Cg641kit.js.map +1 -0
- package/dist/classDiagram-v2-WZHVMYZB-Cg641kit.js +17 -0
- package/dist/classDiagram-v2-WZHVMYZB-Cg641kit.js.map +1 -0
- package/dist/clone-D062_nJ-.js +9 -0
- package/dist/clone-D062_nJ-.js.map +1 -0
- package/dist/cose-bilkent-S5V4N54A-DIb5rck_.js +2609 -0
- package/dist/cose-bilkent-S5V4N54A-DIb5rck_.js.map +1 -0
- package/dist/cytoscape.esm-DfdJODL8.js +18736 -0
- package/dist/cytoscape.esm-DfdJODL8.js.map +1 -0
- package/dist/dagre-6UL2VRFP-CZKadRq9.js +445 -0
- package/dist/dagre-6UL2VRFP-CZKadRq9.js.map +1 -0
- package/dist/defaultLocale-D7EN2tov.js +172 -0
- package/dist/defaultLocale-D7EN2tov.js.map +1 -0
- package/dist/diagram-PSM6KHXK-VDtnZynV.js +532 -0
- package/dist/diagram-PSM6KHXK-VDtnZynV.js.map +1 -0
- package/dist/diagram-QEK2KX5R-DxjNZqZ_.js +218 -0
- package/dist/diagram-QEK2KX5R-DxjNZqZ_.js.map +1 -0
- package/dist/diagram-S2PKOQOG-CCX3j4vi.js +143 -0
- package/dist/diagram-S2PKOQOG-CCX3j4vi.js.map +1 -0
- package/dist/erDiagram-Q2GNP2WA-nwmrf6lJ.js +842 -0
- package/dist/erDiagram-Q2GNP2WA-nwmrf6lJ.js.map +1 -0
- package/dist/flowDiagram-NV44I4VS-DQEdMlRX.js +1621 -0
- package/dist/flowDiagram-NV44I4VS-DQEdMlRX.js.map +1 -0
- package/dist/ganttDiagram-LVOFAZNH-WMiy-Gep.js +2506 -0
- package/dist/ganttDiagram-LVOFAZNH-WMiy-Gep.js.map +1 -0
- package/dist/gitGraphDiagram-NY62KEGX-BgmbSTgN.js +700 -0
- package/dist/gitGraphDiagram-NY62KEGX-BgmbSTgN.js.map +1 -0
- package/dist/graph-CSVEP8oS.js +248 -0
- package/dist/graph-CSVEP8oS.js.map +1 -0
- package/dist/icons.d.ts +17 -0
- package/dist/icons.d.ts.map +1 -0
- package/dist/icons.js +67 -0
- package/dist/icons.js.map +1 -0
- package/dist/index.d.ts +143 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/infoDiagram-F6ZHWCRC-BzTgHHGF.js +25 -0
- package/dist/infoDiagram-F6ZHWCRC-BzTgHHGF.js.map +1 -0
- package/dist/init-DjUOC4st.js +17 -0
- package/dist/init-DjUOC4st.js.map +1 -0
- package/dist/journeyDiagram-XKPGCS4Q-B5P7hkT-.js +835 -0
- package/dist/journeyDiagram-XKPGCS4Q-B5P7hkT-.js.map +1 -0
- package/dist/kanban-definition-3W4ZIXB7-CLtnd0xR.js +720 -0
- package/dist/kanban-definition-3W4ZIXB7-CLtnd0xR.js.map +1 -0
- package/dist/katex-CmGeg_OO.js +11693 -0
- package/dist/katex-CmGeg_OO.js.map +1 -0
- package/dist/layout-CdTiJocX.js +1325 -0
- package/dist/layout-CdTiJocX.js.map +1 -0
- package/dist/linear-BhblBDrV.js +260 -0
- package/dist/linear-BhblBDrV.js.map +1 -0
- package/dist/link-tooltip.d.ts +27 -0
- package/dist/link-tooltip.d.ts.map +1 -0
- package/dist/link-tooltip.js +224 -0
- package/dist/link-tooltip.js.map +1 -0
- package/dist/marzipan.d.ts +472 -0
- package/dist/marzipan.d.ts.map +1 -0
- package/dist/marzipan.js +1185 -0
- package/dist/marzipan.js.map +1 -0
- package/dist/mermaid.core-DENutRS8.js +15249 -0
- package/dist/mermaid.core-DENutRS8.js.map +1 -0
- package/dist/mindmap-definition-VGOIOE7T-BXBE9eta.js +785 -0
- package/dist/mindmap-definition-VGOIOE7T-BXBE9eta.js.map +1 -0
- package/dist/ordinal-DfAQgscy.js +62 -0
- package/dist/ordinal-DfAQgscy.js.map +1 -0
- package/dist/parser.d.ts +234 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +886 -0
- package/dist/parser.js.map +1 -0
- package/dist/pieDiagram-ADFJNKIX-BND3UUQ-.js +162 -0
- package/dist/pieDiagram-ADFJNKIX-BND3UUQ-.js.map +1 -0
- package/dist/plugins/accentSwatchPlugin.d.ts +15 -0
- package/dist/plugins/accentSwatchPlugin.d.ts.map +1 -0
- package/dist/plugins/accentSwatchPlugin.js +142 -0
- package/dist/plugins/accentSwatchPlugin.js.map +1 -0
- package/dist/plugins/imageManagerPlugin.d.ts +16 -0
- package/dist/plugins/imageManagerPlugin.d.ts.map +1 -0
- package/dist/plugins/imageManagerPlugin.js +161 -0
- package/dist/plugins/imageManagerPlugin.js.map +1 -0
- package/dist/plugins/imagePicker.d.ts +7 -0
- package/dist/plugins/imagePicker.d.ts.map +1 -0
- package/dist/plugins/imagePicker.js +33 -0
- package/dist/plugins/imagePicker.js.map +1 -0
- package/dist/plugins/imagePickerPlugin.d.ts +3 -0
- package/dist/plugins/imagePickerPlugin.d.ts.map +1 -0
- package/dist/plugins/imagePickerPlugin.js +12 -0
- package/dist/plugins/imagePickerPlugin.js.map +1 -0
- package/dist/plugins/mermaidExternal.d.ts +2 -0
- package/dist/plugins/mermaidExternal.d.ts.map +1 -0
- package/dist/plugins/mermaidExternal.js +44 -0
- package/dist/plugins/mermaidExternal.js.map +1 -0
- package/dist/plugins/mermaidPlugin.d.ts +2 -0
- package/dist/plugins/mermaidPlugin.d.ts.map +1 -0
- package/dist/plugins/mermaidPlugin.js +25 -0
- package/dist/plugins/mermaidPlugin.js.map +1 -0
- package/dist/plugins/tableGenerator.d.ts +2 -0
- package/dist/plugins/tableGenerator.d.ts.map +1 -0
- package/dist/plugins/tableGenerator.js +24 -0
- package/dist/plugins/tableGenerator.js.map +1 -0
- package/dist/plugins/tableGridPlugin.d.ts +14 -0
- package/dist/plugins/tableGridPlugin.d.ts.map +1 -0
- package/dist/plugins/tableGridPlugin.js +77 -0
- package/dist/plugins/tableGridPlugin.js.map +1 -0
- package/dist/plugins/tablePlugin.d.ts +2 -0
- package/dist/plugins/tablePlugin.d.ts.map +1 -0
- package/dist/plugins/tablePlugin.js +26 -0
- package/dist/plugins/tablePlugin.js.map +1 -0
- package/dist/plugins/tinyHighlight.d.ts +10 -0
- package/dist/plugins/tinyHighlight.d.ts.map +1 -0
- package/dist/plugins/tinyHighlight.js +109 -0
- package/dist/plugins/tinyHighlight.js.map +1 -0
- package/dist/plugins/utils/table.d.ts +3 -0
- package/dist/plugins/utils/table.d.ts.map +1 -0
- package/dist/plugins/utils/table.js +25 -0
- package/dist/plugins/utils/table.js.map +1 -0
- package/dist/quadrantDiagram-AYHSOK5B-CnLHbrEF.js +1023 -0
- package/dist/quadrantDiagram-AYHSOK5B-CnLHbrEF.js.map +1 -0
- package/dist/requirementDiagram-UZGBJVZJ-caC7K17a.js +851 -0
- package/dist/requirementDiagram-UZGBJVZJ-caC7K17a.js.map +1 -0
- package/dist/sankeyDiagram-TZEHDZUN-CV5-bwv8.js +811 -0
- package/dist/sankeyDiagram-TZEHDZUN-CV5-bwv8.js.map +1 -0
- package/dist/sequenceDiagram-WL72ISMW-Cq1otbHU.js +2512 -0
- package/dist/sequenceDiagram-WL72ISMW-Cq1otbHU.js.map +1 -0
- package/dist/shortcuts.d.ts +15 -0
- package/dist/shortcuts.d.ts.map +1 -0
- package/dist/shortcuts.js +78 -0
- package/dist/shortcuts.js.map +1 -0
- package/dist/stateDiagram-FKZM4ZOC-BJ5UwskL.js +264 -0
- package/dist/stateDiagram-FKZM4ZOC-BJ5UwskL.js.map +1 -0
- package/dist/stateDiagram-v2-4FDKWEC3-H1gqsSsO.js +17 -0
- package/dist/stateDiagram-v2-4FDKWEC3-H1gqsSsO.js.map +1 -0
- package/dist/styles.d.ts +11 -0
- package/dist/styles.d.ts.map +1 -0
- package/dist/styles.js +854 -0
- package/dist/styles.js.map +1 -0
- package/dist/table-DMIy93NJ.js +25 -0
- package/dist/table-DMIy93NJ.js.map +1 -0
- package/dist/themes.d.ts +197 -0
- package/dist/themes.d.ts.map +1 -0
- package/dist/themes.js +120 -0
- package/dist/themes.js.map +1 -0
- package/dist/timeline-definition-IT6M3QCI-CqPEUsh4.js +796 -0
- package/dist/timeline-definition-IT6M3QCI-CqPEUsh4.js.map +1 -0
- package/dist/toolbar.d.ts +37 -0
- package/dist/toolbar.d.ts.map +1 -0
- package/dist/toolbar.js +283 -0
- package/dist/toolbar.js.map +1 -0
- package/dist/treemap-75Q7IDZK-DmPg5GN7.js +12988 -0
- package/dist/treemap-75Q7IDZK-DmPg5GN7.js.map +1 -0
- package/dist/xychartDiagram-PRI3JC2R-C3_IO4Rm.js +1341 -0
- package/dist/xychartDiagram-PRI3JC2R-C3_IO4Rm.js.map +1 -0
- package/docs/types.d.ts +1 -1
- package/package.json +15 -11
package/dist/marzipan.js
ADDED
|
@@ -0,0 +1,1185 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* Marzipan - A lightweight markdown editor and viewer
|
|
4
|
+
* @version 1.0.6
|
|
5
|
+
* @license Apache-2.0
|
|
6
|
+
*/
|
|
7
|
+
import { MarkdownParser } from './parser';
|
|
8
|
+
import { ShortcutsManager } from './shortcuts';
|
|
9
|
+
import { generateStyles } from './styles';
|
|
10
|
+
import { getTheme, mergeTheme, solar, themeToCSSVars } from './themes';
|
|
11
|
+
import { Toolbar } from './toolbar';
|
|
12
|
+
import { LinkTooltip } from './link-tooltip';
|
|
13
|
+
/**
|
|
14
|
+
* Marzipan Editor Class
|
|
15
|
+
*/
|
|
16
|
+
class Marzipan {
|
|
17
|
+
/**
|
|
18
|
+
* Constructor - Always returns an array of instances
|
|
19
|
+
* @param {string|Element|NodeList|Array} target - Target element(s)
|
|
20
|
+
* @param {Object} options - Configuration options
|
|
21
|
+
* @returns {Array} Array of Marzipan instances
|
|
22
|
+
*/
|
|
23
|
+
constructor(target, options = {}) {
|
|
24
|
+
// Convert target to array of elements
|
|
25
|
+
let elements;
|
|
26
|
+
if (typeof target === 'string') {
|
|
27
|
+
elements = document.querySelectorAll(target);
|
|
28
|
+
if (elements.length === 0) {
|
|
29
|
+
throw new Error(`No elements found for selector: ${target}`);
|
|
30
|
+
}
|
|
31
|
+
elements = Array.from(elements);
|
|
32
|
+
}
|
|
33
|
+
else if (target instanceof Element) {
|
|
34
|
+
elements = [target];
|
|
35
|
+
}
|
|
36
|
+
else if (target instanceof NodeList) {
|
|
37
|
+
elements = Array.from(target);
|
|
38
|
+
}
|
|
39
|
+
else if (Array.isArray(target)) {
|
|
40
|
+
elements = target;
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
throw new Error('Invalid target: must be selector string, Element, NodeList, or Array');
|
|
44
|
+
}
|
|
45
|
+
// Initialize all elements and return array
|
|
46
|
+
const instances = elements.map(element => {
|
|
47
|
+
// Check for existing instance
|
|
48
|
+
if (element.MarzipanInstance) {
|
|
49
|
+
// Re-init existing instance
|
|
50
|
+
element.MarzipanInstance.reinit(options);
|
|
51
|
+
return element.MarzipanInstance;
|
|
52
|
+
}
|
|
53
|
+
// Create new instance
|
|
54
|
+
const instance = Object.create(Marzipan.prototype);
|
|
55
|
+
instance._init(element, options);
|
|
56
|
+
element.MarzipanInstance = instance;
|
|
57
|
+
Marzipan.instances.set(element, instance);
|
|
58
|
+
return instance;
|
|
59
|
+
});
|
|
60
|
+
return instances;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Internal initialization
|
|
64
|
+
* @private
|
|
65
|
+
*/
|
|
66
|
+
_init(element, options = {}) {
|
|
67
|
+
this.element = element;
|
|
68
|
+
// Store the original theme option before merging
|
|
69
|
+
this.instanceTheme = options.theme || null;
|
|
70
|
+
this.options = this._mergeOptions(options);
|
|
71
|
+
this.instanceId = ++Marzipan.instanceCount;
|
|
72
|
+
this.initialized = false;
|
|
73
|
+
this._autoResizeInputHandler = null;
|
|
74
|
+
this._autoResizeResizeHandler = null;
|
|
75
|
+
this._minAutoHeight = null;
|
|
76
|
+
this._maxAutoHeight = null;
|
|
77
|
+
// Inject styles if needed
|
|
78
|
+
Marzipan.injectStyles();
|
|
79
|
+
// Initialize global listeners
|
|
80
|
+
Marzipan.initGlobalListeners();
|
|
81
|
+
// Check for existing Marzipan DOM structure
|
|
82
|
+
const container = element.querySelector('.marzipan-container');
|
|
83
|
+
const wrapper = element.querySelector('.marzipan-wrapper');
|
|
84
|
+
if (container || wrapper) {
|
|
85
|
+
this._recoverFromDOM(container, wrapper);
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
this._buildFromScratch();
|
|
89
|
+
}
|
|
90
|
+
// Setup shortcuts manager
|
|
91
|
+
this.shortcuts = new ShortcutsManager(this);
|
|
92
|
+
// Setup link tooltip
|
|
93
|
+
this.linkTooltip = new LinkTooltip(this);
|
|
94
|
+
// Setup toolbar if enabled
|
|
95
|
+
if (this.options.toolbar) {
|
|
96
|
+
const toolbarButtons = typeof this.options.toolbar === 'object' ? this.options.toolbar.buttons : null;
|
|
97
|
+
this.toolbar = new Toolbar(this, toolbarButtons);
|
|
98
|
+
this.toolbar.create();
|
|
99
|
+
// Update toolbar states on selection change
|
|
100
|
+
this.textarea.addEventListener('selectionchange', () => {
|
|
101
|
+
this.toolbar?.updateButtonStates?.();
|
|
102
|
+
});
|
|
103
|
+
this.textarea.addEventListener('input', () => {
|
|
104
|
+
this.toolbar?.updateButtonStates?.();
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
const pluginsApplied = this._applyPlugins();
|
|
108
|
+
if (pluginsApplied) {
|
|
109
|
+
this.updatePreview();
|
|
110
|
+
}
|
|
111
|
+
// Mark as initialized
|
|
112
|
+
this.initialized = true;
|
|
113
|
+
// Call onChange if provided
|
|
114
|
+
if (this.options.onChange) {
|
|
115
|
+
this.options.onChange(this.getValue(), this);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Merge user options with defaults
|
|
120
|
+
* @private
|
|
121
|
+
*/
|
|
122
|
+
_mergeOptions(options) {
|
|
123
|
+
const defaults = {
|
|
124
|
+
// Typography
|
|
125
|
+
fontSize: '14px',
|
|
126
|
+
lineHeight: 1.6,
|
|
127
|
+
/* System-first, guaranteed monospaced; avoids Android 'ui-monospace' pitfalls */
|
|
128
|
+
fontFamily: '"SF Mono", SFMono-Regular, Menlo, Monaco, "Cascadia Code", Consolas, "Roboto Mono", "Noto Sans Mono", "Droid Sans Mono", "Ubuntu Mono", "DejaVu Sans Mono", "Liberation Mono", "Courier New", Courier, monospace',
|
|
129
|
+
padding: '16px',
|
|
130
|
+
// Mobile styles
|
|
131
|
+
mobile: {
|
|
132
|
+
fontSize: '16px', // Prevent zoom on iOS
|
|
133
|
+
padding: '12px',
|
|
134
|
+
lineHeight: 1.5
|
|
135
|
+
},
|
|
136
|
+
// Native textarea properties
|
|
137
|
+
textareaProps: {},
|
|
138
|
+
// Behavior
|
|
139
|
+
autofocus: false,
|
|
140
|
+
autoResize: false, // Auto-expand height with content
|
|
141
|
+
minHeight: '100px', // Minimum height for autoResize mode
|
|
142
|
+
maxHeight: null, // Maximum height for autoResize mode (null = unlimited)
|
|
143
|
+
placeholder: 'Start typing...',
|
|
144
|
+
value: '',
|
|
145
|
+
// Callbacks
|
|
146
|
+
onChange: null,
|
|
147
|
+
onKeydown: null,
|
|
148
|
+
// Features
|
|
149
|
+
showActiveLineRaw: false,
|
|
150
|
+
showStats: false,
|
|
151
|
+
toolbar: false,
|
|
152
|
+
statsFormatter: null,
|
|
153
|
+
smartLists: true // Enable smart list continuation
|
|
154
|
+
};
|
|
155
|
+
// Remove theme and colors from options - these are now global
|
|
156
|
+
const { theme, colors, hooks, plugins, ...cleanOptions } = options;
|
|
157
|
+
return {
|
|
158
|
+
...defaults,
|
|
159
|
+
...cleanOptions,
|
|
160
|
+
hooks: hooks ? { ...hooks } : undefined,
|
|
161
|
+
plugins: Array.isArray(plugins) ? [...plugins] : undefined
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Apply plugins attached to this editor instance
|
|
166
|
+
* @private
|
|
167
|
+
*/
|
|
168
|
+
_applyPlugins() {
|
|
169
|
+
if (!Array.isArray(this.options?.plugins) || !this.options.plugins.length) {
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
if (!this._appliedPlugins) {
|
|
173
|
+
this._appliedPlugins = new Set();
|
|
174
|
+
}
|
|
175
|
+
let applied = false;
|
|
176
|
+
for (const plugin of this.options.plugins) {
|
|
177
|
+
if (typeof plugin !== 'function')
|
|
178
|
+
continue;
|
|
179
|
+
if (this._appliedPlugins.has(plugin))
|
|
180
|
+
continue;
|
|
181
|
+
this._appliedPlugins.add(plugin);
|
|
182
|
+
try {
|
|
183
|
+
plugin(this);
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
console.error('[Marzipan] Plugin error:', error);
|
|
187
|
+
}
|
|
188
|
+
applied = true;
|
|
189
|
+
}
|
|
190
|
+
return applied;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Recover from existing DOM structure
|
|
194
|
+
* @private
|
|
195
|
+
*/
|
|
196
|
+
_recoverFromDOM(container, wrapper) {
|
|
197
|
+
// Handle old structure (wrapper only) or new structure (container + wrapper)
|
|
198
|
+
if (container && container.classList.contains('marzipan-container')) {
|
|
199
|
+
this.container = container;
|
|
200
|
+
this.wrapper = container.querySelector('.marzipan-wrapper');
|
|
201
|
+
}
|
|
202
|
+
else if (wrapper) {
|
|
203
|
+
// Old structure - just wrapper, no container
|
|
204
|
+
this.wrapper = wrapper;
|
|
205
|
+
// Wrap it in a container for consistency
|
|
206
|
+
this.container = document.createElement('div');
|
|
207
|
+
this.container.className = 'marzipan-container';
|
|
208
|
+
// Use instance theme if provided, otherwise use global theme
|
|
209
|
+
const themeToUse = this.instanceTheme || Marzipan.currentTheme || solar;
|
|
210
|
+
const themeName = typeof themeToUse === 'string' ? themeToUse : themeToUse.name;
|
|
211
|
+
if (themeName) {
|
|
212
|
+
this.container.setAttribute('data-theme', themeName);
|
|
213
|
+
}
|
|
214
|
+
// If using instance theme, apply CSS variables to container
|
|
215
|
+
if (this.instanceTheme) {
|
|
216
|
+
const themeObj = typeof this.instanceTheme === 'string' ? getTheme(this.instanceTheme) : this.instanceTheme;
|
|
217
|
+
if (themeObj && themeObj.colors) {
|
|
218
|
+
const cssVars = themeToCSSVars(themeObj.colors);
|
|
219
|
+
this.container.style.cssText += cssVars;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
wrapper.parentNode.insertBefore(this.container, wrapper);
|
|
223
|
+
this.container.appendChild(wrapper);
|
|
224
|
+
}
|
|
225
|
+
if (this.container) {
|
|
226
|
+
this.container.setAttribute('data-marzipan-instance', String(this.instanceId));
|
|
227
|
+
}
|
|
228
|
+
if (!this.wrapper) {
|
|
229
|
+
// No valid structure found
|
|
230
|
+
if (container)
|
|
231
|
+
container.remove();
|
|
232
|
+
if (wrapper)
|
|
233
|
+
wrapper.remove();
|
|
234
|
+
this._buildFromScratch();
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
this.textarea = this.wrapper.querySelector('.marzipan-input');
|
|
238
|
+
this.preview = this.wrapper.querySelector('.marzipan-preview');
|
|
239
|
+
if (!this.textarea || !this.preview) {
|
|
240
|
+
// Partial DOM - clear and rebuild
|
|
241
|
+
this.container.remove();
|
|
242
|
+
this._buildFromScratch();
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
// Store reference on wrapper
|
|
246
|
+
this.wrapper._instance = this;
|
|
247
|
+
// Apply instance-specific styles via CSS custom properties
|
|
248
|
+
if (this.options.fontSize) {
|
|
249
|
+
this.wrapper.style.setProperty('--instance-font-size', this.options.fontSize);
|
|
250
|
+
}
|
|
251
|
+
if (this.options.lineHeight) {
|
|
252
|
+
this.wrapper.style.setProperty('--instance-line-height', String(this.options.lineHeight));
|
|
253
|
+
}
|
|
254
|
+
if (this.options.padding) {
|
|
255
|
+
this.wrapper.style.setProperty('--instance-padding', this.options.padding);
|
|
256
|
+
}
|
|
257
|
+
// Disable autofill, spellcheck, and extensions
|
|
258
|
+
this._configureTextarea();
|
|
259
|
+
// Apply any new options
|
|
260
|
+
this._applyOptions();
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Build editor from scratch
|
|
264
|
+
* @private
|
|
265
|
+
*/
|
|
266
|
+
_buildFromScratch() {
|
|
267
|
+
// Extract any existing content
|
|
268
|
+
const content = this._extractContent();
|
|
269
|
+
// Clear element
|
|
270
|
+
this.element.innerHTML = '';
|
|
271
|
+
// Create DOM structure
|
|
272
|
+
this._createDOM();
|
|
273
|
+
// Set initial content
|
|
274
|
+
if (content || this.options.value) {
|
|
275
|
+
this.setValue(content || this.options.value);
|
|
276
|
+
}
|
|
277
|
+
// Apply options
|
|
278
|
+
this._applyOptions();
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Extract content from element
|
|
282
|
+
* @private
|
|
283
|
+
*/
|
|
284
|
+
_extractContent() {
|
|
285
|
+
// Look for existing Marzipan textarea
|
|
286
|
+
const textarea = this.element.querySelector('.marzipan-input');
|
|
287
|
+
if (textarea)
|
|
288
|
+
return textarea.value;
|
|
289
|
+
// Use element's text content as fallback
|
|
290
|
+
return this.element.textContent || '';
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Create DOM structure
|
|
294
|
+
* @private
|
|
295
|
+
*/
|
|
296
|
+
_createDOM() {
|
|
297
|
+
// Create container that will hold toolbar and editor
|
|
298
|
+
this.container = document.createElement('div');
|
|
299
|
+
this.container.className = 'marzipan-container';
|
|
300
|
+
this.container.setAttribute('data-marzipan-instance', String(this.instanceId));
|
|
301
|
+
// Set theme on container - use instance theme if provided
|
|
302
|
+
const themeToUse = this.instanceTheme || Marzipan.currentTheme || solar;
|
|
303
|
+
const themeName = typeof themeToUse === 'string' ? themeToUse : themeToUse.name;
|
|
304
|
+
if (themeName) {
|
|
305
|
+
this.container.setAttribute('data-theme', themeName);
|
|
306
|
+
}
|
|
307
|
+
// If using instance theme, apply CSS variables to container
|
|
308
|
+
if (this.instanceTheme) {
|
|
309
|
+
const themeObj = typeof this.instanceTheme === 'string' ? getTheme(this.instanceTheme) : this.instanceTheme;
|
|
310
|
+
if (themeObj && themeObj.colors) {
|
|
311
|
+
const cssVars = themeToCSSVars(themeObj.colors);
|
|
312
|
+
this.container.style.cssText += cssVars;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
// Create wrapper for editor
|
|
316
|
+
this.wrapper = document.createElement('div');
|
|
317
|
+
this.wrapper.className = 'marzipan-wrapper';
|
|
318
|
+
// Apply instance-specific styles via CSS custom properties
|
|
319
|
+
if (this.options.fontSize) {
|
|
320
|
+
this.wrapper.style.setProperty('--instance-font-size', this.options.fontSize);
|
|
321
|
+
}
|
|
322
|
+
if (this.options.lineHeight) {
|
|
323
|
+
this.wrapper.style.setProperty('--instance-line-height', String(this.options.lineHeight));
|
|
324
|
+
}
|
|
325
|
+
if (this.options.padding) {
|
|
326
|
+
this.wrapper.style.setProperty('--instance-padding', this.options.padding);
|
|
327
|
+
}
|
|
328
|
+
this.wrapper._instance = this;
|
|
329
|
+
// Create textarea
|
|
330
|
+
this.textarea = document.createElement('textarea');
|
|
331
|
+
this.textarea.className = 'marzipan-input';
|
|
332
|
+
this.textarea.placeholder = this.options.placeholder;
|
|
333
|
+
this._configureTextarea();
|
|
334
|
+
// Apply any native textarea properties
|
|
335
|
+
if (this.options.textareaProps) {
|
|
336
|
+
Object.entries(this.options.textareaProps).forEach(([key, value]) => {
|
|
337
|
+
if (key === 'className' || key === 'class') {
|
|
338
|
+
this.textarea.className += ' ' + value;
|
|
339
|
+
}
|
|
340
|
+
else if (key === 'style' && typeof value === 'object') {
|
|
341
|
+
Object.assign(this.textarea.style, value);
|
|
342
|
+
}
|
|
343
|
+
else {
|
|
344
|
+
this.textarea.setAttribute(key, value);
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
// Create preview div
|
|
349
|
+
this.preview = document.createElement('div');
|
|
350
|
+
this.preview.className = 'marzipan-preview';
|
|
351
|
+
this.preview.setAttribute('aria-hidden', 'true');
|
|
352
|
+
// Assemble DOM
|
|
353
|
+
this.wrapper.appendChild(this.textarea);
|
|
354
|
+
this.wrapper.appendChild(this.preview);
|
|
355
|
+
// No need to prevent link clicks - pointer-events handles this
|
|
356
|
+
// Add wrapper to container first
|
|
357
|
+
this.container.appendChild(this.wrapper);
|
|
358
|
+
// Add stats bar at the end (bottom) if enabled
|
|
359
|
+
if (this.options.showStats) {
|
|
360
|
+
this.statsBar = document.createElement('div');
|
|
361
|
+
this.statsBar.className = 'marzipan-stats';
|
|
362
|
+
this.container.appendChild(this.statsBar);
|
|
363
|
+
this._updateStats();
|
|
364
|
+
}
|
|
365
|
+
// Add container to element
|
|
366
|
+
this.element.appendChild(this.container);
|
|
367
|
+
// Setup auto-resize if enabled
|
|
368
|
+
if (this.options.autoResize) {
|
|
369
|
+
this._setupAutoResize();
|
|
370
|
+
}
|
|
371
|
+
else {
|
|
372
|
+
// Ensure auto-resize class is removed if not using auto-resize
|
|
373
|
+
this.container.classList.remove('marzipan-auto-resize');
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Configure textarea attributes
|
|
378
|
+
* @private
|
|
379
|
+
*/
|
|
380
|
+
_configureTextarea() {
|
|
381
|
+
this.textarea.setAttribute('autocomplete', 'off');
|
|
382
|
+
this.textarea.setAttribute('autocorrect', 'off');
|
|
383
|
+
this.textarea.setAttribute('autocapitalize', 'off');
|
|
384
|
+
this.textarea.setAttribute('spellcheck', 'false');
|
|
385
|
+
this.textarea.setAttribute('data-gramm', 'false');
|
|
386
|
+
this.textarea.setAttribute('data-gramm_editor', 'false');
|
|
387
|
+
this.textarea.setAttribute('data-enable-grammarly', 'false');
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Apply options to the editor
|
|
391
|
+
* @private
|
|
392
|
+
*/
|
|
393
|
+
_applyOptions() {
|
|
394
|
+
// Apply autofocus
|
|
395
|
+
if (this.options.autofocus) {
|
|
396
|
+
this.textarea.focus();
|
|
397
|
+
}
|
|
398
|
+
// Setup or remove auto-resize
|
|
399
|
+
if (this.options.autoResize) {
|
|
400
|
+
this._setupAutoResize();
|
|
401
|
+
}
|
|
402
|
+
else {
|
|
403
|
+
this._teardownAutoResize();
|
|
404
|
+
}
|
|
405
|
+
// Update preview with initial content
|
|
406
|
+
this.updatePreview();
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Update preview with parsed markdown
|
|
410
|
+
*/
|
|
411
|
+
updatePreview() {
|
|
412
|
+
const text = this.textarea.value;
|
|
413
|
+
const cursorPos = this.textarea.selectionStart;
|
|
414
|
+
const activeLine = this._getCurrentLine(text, cursorPos);
|
|
415
|
+
// Parse markdown
|
|
416
|
+
const html = MarkdownParser.parse(text, activeLine, this.options.showActiveLineRaw);
|
|
417
|
+
this.preview.innerHTML = html || '<span style="color: #808080;">Start typing...</span>';
|
|
418
|
+
if (this.options?.hooks?.afterPreviewRender) {
|
|
419
|
+
try {
|
|
420
|
+
this.options.hooks.afterPreviewRender(this.preview, this);
|
|
421
|
+
}
|
|
422
|
+
catch (error) {
|
|
423
|
+
console.error('[Marzipan] afterPreviewRender hook error:', error);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
// Apply code block backgrounds
|
|
427
|
+
this._applyCodeBlockBackgrounds();
|
|
428
|
+
// Links always have real hrefs now - no need to update them
|
|
429
|
+
// Update stats if enabled
|
|
430
|
+
if (this.options.showStats && this.statsBar) {
|
|
431
|
+
this._updateStats();
|
|
432
|
+
}
|
|
433
|
+
// Trigger onChange callback
|
|
434
|
+
if (this.options.onChange && this.initialized) {
|
|
435
|
+
this.options.onChange(text, this);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Apply background styling to code blocks
|
|
440
|
+
* @private
|
|
441
|
+
*/
|
|
442
|
+
_applyCodeBlockBackgrounds() {
|
|
443
|
+
// Find all code fence elements
|
|
444
|
+
const codeFences = this.preview.querySelectorAll('.code-fence');
|
|
445
|
+
// Process pairs of code fences
|
|
446
|
+
for (let i = 0; i < codeFences.length - 1; i += 2) {
|
|
447
|
+
const openFence = codeFences[i];
|
|
448
|
+
const closeFence = codeFences[i + 1];
|
|
449
|
+
// Get parent divs
|
|
450
|
+
const openParent = openFence.parentElement;
|
|
451
|
+
const closeParent = closeFence.parentElement;
|
|
452
|
+
if (!openParent || !closeParent)
|
|
453
|
+
continue;
|
|
454
|
+
// Make fences display: block
|
|
455
|
+
openFence.style.display = 'block';
|
|
456
|
+
closeFence.style.display = 'block';
|
|
457
|
+
// Apply class to parent divs
|
|
458
|
+
openParent.classList.add('code-block-line');
|
|
459
|
+
closeParent.classList.add('code-block-line');
|
|
460
|
+
// With the new structure, there's a <pre> block between fences, not DIVs
|
|
461
|
+
// We don't need to process anything between the fences anymore
|
|
462
|
+
// The <pre><code> structure already handles the content correctly
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Get current line number from cursor position
|
|
467
|
+
* @private
|
|
468
|
+
*/
|
|
469
|
+
_getCurrentLine(text, cursorPos) {
|
|
470
|
+
const lines = text.substring(0, cursorPos).split('\n');
|
|
471
|
+
return lines.length - 1;
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Handle input events
|
|
475
|
+
* @private
|
|
476
|
+
*/
|
|
477
|
+
handleInput(event) {
|
|
478
|
+
this.updatePreview();
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Handle keydown events
|
|
482
|
+
* @private
|
|
483
|
+
*/
|
|
484
|
+
handleKeydown(event) {
|
|
485
|
+
// Handle Tab key to prevent focus loss and insert spaces
|
|
486
|
+
if (event.key === 'Tab') {
|
|
487
|
+
event.preventDefault();
|
|
488
|
+
const start = this.textarea.selectionStart;
|
|
489
|
+
const end = this.textarea.selectionEnd;
|
|
490
|
+
const value = this.textarea.value;
|
|
491
|
+
// If there's a selection, indent/outdent based on shift key
|
|
492
|
+
if (start !== end && event.shiftKey) {
|
|
493
|
+
// Outdent: remove 2 spaces from start of each selected line
|
|
494
|
+
const before = value.substring(0, start);
|
|
495
|
+
const selection = value.substring(start, end);
|
|
496
|
+
const after = value.substring(end);
|
|
497
|
+
const lines = selection.split('\n');
|
|
498
|
+
const outdented = lines.map(line => line.replace(/^ /, '')).join('\n');
|
|
499
|
+
// Try to use execCommand first to preserve undo history
|
|
500
|
+
if (document.execCommand) {
|
|
501
|
+
// Select the text that needs to be replaced
|
|
502
|
+
this.textarea.setSelectionRange(start, end);
|
|
503
|
+
document.execCommand('insertText', false, outdented);
|
|
504
|
+
}
|
|
505
|
+
else {
|
|
506
|
+
// Fallback to direct manipulation
|
|
507
|
+
this.textarea.value = before + outdented + after;
|
|
508
|
+
this.textarea.selectionStart = start;
|
|
509
|
+
this.textarea.selectionEnd = start + outdented.length;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
else if (start !== end) {
|
|
513
|
+
// Indent: add 2 spaces to start of each selected line
|
|
514
|
+
const before = value.substring(0, start);
|
|
515
|
+
const selection = value.substring(start, end);
|
|
516
|
+
const after = value.substring(end);
|
|
517
|
+
const lines = selection.split('\n');
|
|
518
|
+
const indented = lines.map(line => ' ' + line).join('\n');
|
|
519
|
+
// Try to use execCommand first to preserve undo history
|
|
520
|
+
if (document.execCommand) {
|
|
521
|
+
// Select the text that needs to be replaced
|
|
522
|
+
this.textarea.setSelectionRange(start, end);
|
|
523
|
+
document.execCommand('insertText', false, indented);
|
|
524
|
+
}
|
|
525
|
+
else {
|
|
526
|
+
// Fallback to direct manipulation
|
|
527
|
+
this.textarea.value = before + indented + after;
|
|
528
|
+
this.textarea.selectionStart = start;
|
|
529
|
+
this.textarea.selectionEnd = start + indented.length;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
else {
|
|
533
|
+
// No selection: just insert 2 spaces
|
|
534
|
+
// Use execCommand to preserve undo history
|
|
535
|
+
if (document.execCommand) {
|
|
536
|
+
document.execCommand('insertText', false, ' ');
|
|
537
|
+
}
|
|
538
|
+
else {
|
|
539
|
+
// Fallback to direct manipulation
|
|
540
|
+
this.textarea.value = value.substring(0, start) + ' ' + value.substring(end);
|
|
541
|
+
this.textarea.selectionStart = this.textarea.selectionEnd = start + 2;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
// Trigger input event to update preview
|
|
545
|
+
this.textarea.dispatchEvent(new Event('input', { bubbles: true }));
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
// Handle Enter key for smart list continuation
|
|
549
|
+
if (event.key === 'Enter' && !event.shiftKey && !event.metaKey && !event.ctrlKey && this.options.smartLists) {
|
|
550
|
+
if (this.handleSmartListContinuation()) {
|
|
551
|
+
event.preventDefault();
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
// Let shortcuts manager handle other keys
|
|
556
|
+
const handled = this.shortcuts.handleKeydown(event);
|
|
557
|
+
// Call user callback if provided
|
|
558
|
+
if (!handled && this.options.onKeydown) {
|
|
559
|
+
this.options.onKeydown(event, this);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Handle smart list continuation
|
|
564
|
+
* @returns {boolean} Whether the event was handled
|
|
565
|
+
*/
|
|
566
|
+
handleSmartListContinuation() {
|
|
567
|
+
const textarea = this.textarea;
|
|
568
|
+
const cursorPos = textarea.selectionStart;
|
|
569
|
+
const context = MarkdownParser.getListContext(textarea.value, cursorPos);
|
|
570
|
+
if (!context || !context.inList)
|
|
571
|
+
return false;
|
|
572
|
+
// Handle empty list item (exit list)
|
|
573
|
+
if (context.content.trim() === '' && cursorPos >= context.markerEndPos) {
|
|
574
|
+
this.deleteListMarker(context);
|
|
575
|
+
return true;
|
|
576
|
+
}
|
|
577
|
+
// Handle text splitting if cursor is in middle of content
|
|
578
|
+
if (cursorPos > context.markerEndPos && cursorPos < context.lineEnd) {
|
|
579
|
+
this.splitListItem(context, cursorPos);
|
|
580
|
+
}
|
|
581
|
+
else {
|
|
582
|
+
// Just add new item after current line
|
|
583
|
+
this.insertNewListItem(context);
|
|
584
|
+
}
|
|
585
|
+
// Handle numbered list renumbering
|
|
586
|
+
if (context.listType === 'numbered') {
|
|
587
|
+
this.scheduleNumberedListUpdate();
|
|
588
|
+
}
|
|
589
|
+
return true;
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* Delete list marker and exit list
|
|
593
|
+
* @private
|
|
594
|
+
*/
|
|
595
|
+
deleteListMarker(context) {
|
|
596
|
+
// Select from line start to marker end
|
|
597
|
+
this.textarea.setSelectionRange(context.lineStart, context.markerEndPos);
|
|
598
|
+
document.execCommand('delete');
|
|
599
|
+
// Trigger input event
|
|
600
|
+
this.textarea.dispatchEvent(new Event('input', { bubbles: true }));
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* Insert new list item
|
|
604
|
+
* @private
|
|
605
|
+
*/
|
|
606
|
+
insertNewListItem(context) {
|
|
607
|
+
const newItem = MarkdownParser.createNewListItem(context);
|
|
608
|
+
document.execCommand('insertText', false, '\n' + newItem);
|
|
609
|
+
// Trigger input event
|
|
610
|
+
this.textarea.dispatchEvent(new Event('input', { bubbles: true }));
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* Split list item at cursor position
|
|
614
|
+
* @private
|
|
615
|
+
*/
|
|
616
|
+
splitListItem(context, cursorPos) {
|
|
617
|
+
// Get text after cursor
|
|
618
|
+
const textAfterCursor = context.content.substring(cursorPos - context.markerEndPos);
|
|
619
|
+
// Delete text after cursor
|
|
620
|
+
this.textarea.setSelectionRange(cursorPos, context.lineEnd);
|
|
621
|
+
document.execCommand('delete');
|
|
622
|
+
// Insert new list item with remaining text
|
|
623
|
+
const newItem = MarkdownParser.createNewListItem(context);
|
|
624
|
+
document.execCommand('insertText', false, '\n' + newItem + textAfterCursor);
|
|
625
|
+
// Position cursor after new list marker
|
|
626
|
+
const newCursorPos = this.textarea.selectionStart - textAfterCursor.length;
|
|
627
|
+
this.textarea.setSelectionRange(newCursorPos, newCursorPos);
|
|
628
|
+
// Trigger input event
|
|
629
|
+
this.textarea.dispatchEvent(new Event('input', { bubbles: true }));
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* Schedule numbered list renumbering
|
|
633
|
+
* @private
|
|
634
|
+
*/
|
|
635
|
+
scheduleNumberedListUpdate() {
|
|
636
|
+
// Clear any pending update
|
|
637
|
+
if (this.numberUpdateTimeout) {
|
|
638
|
+
clearTimeout(this.numberUpdateTimeout);
|
|
639
|
+
}
|
|
640
|
+
// Schedule update after current input cycle
|
|
641
|
+
this.numberUpdateTimeout = setTimeout(() => {
|
|
642
|
+
this.updateNumberedLists();
|
|
643
|
+
}, 10);
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Update/renumber all numbered lists
|
|
647
|
+
* @private
|
|
648
|
+
*/
|
|
649
|
+
updateNumberedLists() {
|
|
650
|
+
const value = this.textarea.value;
|
|
651
|
+
const cursorPos = this.textarea.selectionStart;
|
|
652
|
+
const newValue = MarkdownParser.renumberLists(value);
|
|
653
|
+
if (newValue !== value) {
|
|
654
|
+
// Calculate cursor offset
|
|
655
|
+
let offset = 0;
|
|
656
|
+
const oldLines = value.split('\n');
|
|
657
|
+
const newLines = newValue.split('\n');
|
|
658
|
+
let charCount = 0;
|
|
659
|
+
for (let i = 0; i < oldLines.length && charCount < cursorPos; i++) {
|
|
660
|
+
if (oldLines[i] !== newLines[i]) {
|
|
661
|
+
const diff = newLines[i].length - oldLines[i].length;
|
|
662
|
+
if (charCount + oldLines[i].length < cursorPos) {
|
|
663
|
+
offset += diff;
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
charCount += oldLines[i].length + 1; // +1 for newline
|
|
667
|
+
}
|
|
668
|
+
// Update textarea
|
|
669
|
+
this.textarea.value = newValue;
|
|
670
|
+
const newCursorPos = cursorPos + offset;
|
|
671
|
+
this.textarea.setSelectionRange(newCursorPos, newCursorPos);
|
|
672
|
+
// Trigger update
|
|
673
|
+
this.textarea.dispatchEvent(new Event('input', { bubbles: true }));
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
/**
|
|
677
|
+
* Handle scroll events
|
|
678
|
+
* @private
|
|
679
|
+
*/
|
|
680
|
+
handleScroll(event) {
|
|
681
|
+
// Sync preview scroll with textarea
|
|
682
|
+
this.preview.scrollTop = this.textarea.scrollTop;
|
|
683
|
+
this.preview.scrollLeft = this.textarea.scrollLeft;
|
|
684
|
+
}
|
|
685
|
+
/**
|
|
686
|
+
* Get editor content
|
|
687
|
+
* @returns {string} Current markdown content
|
|
688
|
+
*/
|
|
689
|
+
getValue() {
|
|
690
|
+
return this.textarea.value;
|
|
691
|
+
}
|
|
692
|
+
/**
|
|
693
|
+
* Set editor content
|
|
694
|
+
* @param {string} value - Markdown content to set
|
|
695
|
+
*/
|
|
696
|
+
setValue(value) {
|
|
697
|
+
this.textarea.value = value;
|
|
698
|
+
this.updatePreview();
|
|
699
|
+
// Update height if auto-resize is enabled
|
|
700
|
+
if (this.options.autoResize) {
|
|
701
|
+
this._updateAutoHeight();
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Get the rendered HTML of the current content
|
|
706
|
+
* @param {Object} options - Rendering options
|
|
707
|
+
* @param {boolean} options.cleanHTML - If true, removes syntax markers and Marzipan-specific classes
|
|
708
|
+
* @returns {string} Rendered HTML
|
|
709
|
+
*/
|
|
710
|
+
getRenderedHTML(options = {}) {
|
|
711
|
+
const markdown = this.getValue();
|
|
712
|
+
let html = MarkdownParser.parse(markdown);
|
|
713
|
+
if (options.cleanHTML) {
|
|
714
|
+
// Remove all syntax marker spans for clean HTML export
|
|
715
|
+
html = html.replace(/<span class="syntax-marker[^"]*">.*?<\/span>/g, '');
|
|
716
|
+
// Remove Marzipan-specific classes
|
|
717
|
+
html = html.replace(/\sclass="(bullet-list|ordered-list|code-fence|hr-marker|blockquote|url-part)"/g, '');
|
|
718
|
+
// Clean up empty class attributes
|
|
719
|
+
html = html.replace(/\sclass=""/g, '');
|
|
720
|
+
}
|
|
721
|
+
return html;
|
|
722
|
+
}
|
|
723
|
+
/**
|
|
724
|
+
* Get the current preview element's HTML
|
|
725
|
+
* This includes all syntax markers and Marzipan styling
|
|
726
|
+
* @returns {string} Current preview HTML (as displayed)
|
|
727
|
+
*/
|
|
728
|
+
getPreviewHTML() {
|
|
729
|
+
return this.preview.innerHTML;
|
|
730
|
+
}
|
|
731
|
+
/**
|
|
732
|
+
* Get clean HTML without any Marzipan-specific markup
|
|
733
|
+
* Useful for exporting to other formats or storage
|
|
734
|
+
* @returns {string} Clean HTML suitable for export
|
|
735
|
+
*/
|
|
736
|
+
getCleanHTML() {
|
|
737
|
+
return this.getRenderedHTML({ cleanHTML: true });
|
|
738
|
+
}
|
|
739
|
+
/**
|
|
740
|
+
* Focus the editor
|
|
741
|
+
*/
|
|
742
|
+
focus() {
|
|
743
|
+
this.textarea.focus();
|
|
744
|
+
}
|
|
745
|
+
/**
|
|
746
|
+
* Blur the editor
|
|
747
|
+
*/
|
|
748
|
+
blur() {
|
|
749
|
+
this.textarea.blur();
|
|
750
|
+
}
|
|
751
|
+
/**
|
|
752
|
+
* Check if editor is initialized
|
|
753
|
+
* @returns {boolean}
|
|
754
|
+
*/
|
|
755
|
+
isInitialized() {
|
|
756
|
+
return this.initialized;
|
|
757
|
+
}
|
|
758
|
+
/**
|
|
759
|
+
* Re-initialize with new options
|
|
760
|
+
* @param {Object} options - New options to apply
|
|
761
|
+
*/
|
|
762
|
+
reinit(options = {}) {
|
|
763
|
+
this.options = this._mergeOptions({ ...this.options, ...options });
|
|
764
|
+
this._applyOptions();
|
|
765
|
+
this._applyPlugins();
|
|
766
|
+
this.updatePreview();
|
|
767
|
+
}
|
|
768
|
+
/**
|
|
769
|
+
* Update stats bar
|
|
770
|
+
* @private
|
|
771
|
+
*/
|
|
772
|
+
_updateStats() {
|
|
773
|
+
if (!this.statsBar)
|
|
774
|
+
return;
|
|
775
|
+
const value = this.textarea.value;
|
|
776
|
+
const lines = value.split('\n');
|
|
777
|
+
const chars = value.length;
|
|
778
|
+
const words = value.split(/\s+/).filter(w => w.length > 0).length;
|
|
779
|
+
// Calculate line and column
|
|
780
|
+
const selectionStart = this.textarea.selectionStart;
|
|
781
|
+
const beforeCursor = value.substring(0, selectionStart);
|
|
782
|
+
const linesBeforeCursor = beforeCursor.split('\n');
|
|
783
|
+
const currentLine = linesBeforeCursor.length;
|
|
784
|
+
const currentColumn = linesBeforeCursor[linesBeforeCursor.length - 1].length + 1;
|
|
785
|
+
// Use custom formatter if provided
|
|
786
|
+
if (this.options.statsFormatter) {
|
|
787
|
+
this.statsBar.innerHTML = this.options.statsFormatter({
|
|
788
|
+
chars,
|
|
789
|
+
words,
|
|
790
|
+
lines: lines.length,
|
|
791
|
+
line: currentLine,
|
|
792
|
+
column: currentColumn
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
else {
|
|
796
|
+
// Default format with live dot
|
|
797
|
+
this.statsBar.innerHTML = `
|
|
798
|
+
<div class="marzipan-stat">
|
|
799
|
+
<span class="live-dot"></span>
|
|
800
|
+
<span>${chars} chars, ${words} words, ${lines.length} lines</span>
|
|
801
|
+
</div>
|
|
802
|
+
<div class="marzipan-stat">Line ${currentLine}, Col ${currentColumn}</div>
|
|
803
|
+
`;
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
/**
|
|
807
|
+
* Setup auto-resize functionality
|
|
808
|
+
* @private
|
|
809
|
+
*/
|
|
810
|
+
_setupAutoResize() {
|
|
811
|
+
if (!this.container || !this.textarea || !this.preview || !this.wrapper) {
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
this._teardownAutoResize();
|
|
815
|
+
this.container.classList.add('marzipan-auto-resize');
|
|
816
|
+
this.previousHeight = null;
|
|
817
|
+
this._refreshAutoResizeConstraints();
|
|
818
|
+
this._autoResizeInputHandler = () => this._updateAutoHeight();
|
|
819
|
+
this.textarea.addEventListener('input', this._autoResizeInputHandler);
|
|
820
|
+
if (typeof window !== 'undefined') {
|
|
821
|
+
this._autoResizeResizeHandler = () => this._updateAutoHeight();
|
|
822
|
+
window.addEventListener('resize', this._autoResizeResizeHandler);
|
|
823
|
+
}
|
|
824
|
+
else {
|
|
825
|
+
this._autoResizeResizeHandler = null;
|
|
826
|
+
}
|
|
827
|
+
this._updateAutoHeight();
|
|
828
|
+
}
|
|
829
|
+
_teardownAutoResize() {
|
|
830
|
+
if (this.container) {
|
|
831
|
+
this.container.classList.remove('marzipan-auto-resize');
|
|
832
|
+
}
|
|
833
|
+
if (this._autoResizeInputHandler && this.textarea) {
|
|
834
|
+
this.textarea.removeEventListener('input', this._autoResizeInputHandler);
|
|
835
|
+
}
|
|
836
|
+
this._autoResizeInputHandler = null;
|
|
837
|
+
if (this._autoResizeResizeHandler && typeof window !== 'undefined') {
|
|
838
|
+
window.removeEventListener('resize', this._autoResizeResizeHandler);
|
|
839
|
+
}
|
|
840
|
+
this._autoResizeResizeHandler = null;
|
|
841
|
+
if (this.textarea) {
|
|
842
|
+
this.textarea.style.removeProperty('height');
|
|
843
|
+
this.textarea.style.removeProperty('overflow-y');
|
|
844
|
+
}
|
|
845
|
+
if (this.preview) {
|
|
846
|
+
this.preview.style.removeProperty('height');
|
|
847
|
+
this.preview.style.removeProperty('overflow-y');
|
|
848
|
+
}
|
|
849
|
+
if (this.wrapper) {
|
|
850
|
+
this.wrapper.style.removeProperty('height');
|
|
851
|
+
}
|
|
852
|
+
this.previousHeight = null;
|
|
853
|
+
this._minAutoHeight = null;
|
|
854
|
+
this._maxAutoHeight = null;
|
|
855
|
+
}
|
|
856
|
+
/**
|
|
857
|
+
* Update height based on scrollHeight
|
|
858
|
+
* @private
|
|
859
|
+
*/
|
|
860
|
+
_updateAutoHeight() {
|
|
861
|
+
if (!this.options.autoResize)
|
|
862
|
+
return;
|
|
863
|
+
const textarea = this.textarea;
|
|
864
|
+
const preview = this.preview;
|
|
865
|
+
const wrapper = this.wrapper;
|
|
866
|
+
// Store scroll positions
|
|
867
|
+
const scrollTop = textarea.scrollTop;
|
|
868
|
+
// Reset height to get accurate scrollHeight
|
|
869
|
+
textarea.style.setProperty('height', 'auto', 'important');
|
|
870
|
+
// Calculate new height based on scrollHeight
|
|
871
|
+
let newHeight = textarea.scrollHeight;
|
|
872
|
+
let overflow = 'hidden';
|
|
873
|
+
const minHeight = this._minAutoHeight;
|
|
874
|
+
if (typeof minHeight === 'number' && minHeight > 0) {
|
|
875
|
+
newHeight = Math.max(newHeight, minHeight);
|
|
876
|
+
}
|
|
877
|
+
const maxHeight = this._maxAutoHeight;
|
|
878
|
+
if (typeof maxHeight === 'number' && maxHeight > 0 && newHeight > maxHeight) {
|
|
879
|
+
newHeight = maxHeight;
|
|
880
|
+
overflow = 'auto';
|
|
881
|
+
}
|
|
882
|
+
// Apply the new height to all elements with !important to override base styles
|
|
883
|
+
const heightPx = newHeight + 'px';
|
|
884
|
+
textarea.style.setProperty('height', heightPx, 'important');
|
|
885
|
+
textarea.style.setProperty('overflow-y', overflow, 'important');
|
|
886
|
+
preview.style.setProperty('height', heightPx, 'important');
|
|
887
|
+
preview.style.setProperty('overflow-y', overflow, 'important');
|
|
888
|
+
wrapper.style.setProperty('height', heightPx, 'important');
|
|
889
|
+
// Restore scroll position
|
|
890
|
+
textarea.scrollTop = scrollTop;
|
|
891
|
+
preview.scrollTop = scrollTop;
|
|
892
|
+
// Track if height changed
|
|
893
|
+
if (this.previousHeight !== newHeight) {
|
|
894
|
+
this.previousHeight = newHeight;
|
|
895
|
+
// Could dispatch a custom event here if needed
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
_refreshAutoResizeConstraints() {
|
|
899
|
+
this._minAutoHeight = this._resolveSizeToPixels(this.options.minHeight);
|
|
900
|
+
this._maxAutoHeight = this._resolveSizeToPixels(this.options.maxHeight);
|
|
901
|
+
}
|
|
902
|
+
_resolveSizeToPixels(value) {
|
|
903
|
+
if (value == null || value === '') {
|
|
904
|
+
return null;
|
|
905
|
+
}
|
|
906
|
+
if (typeof value === 'number') {
|
|
907
|
+
return value > 0 ? value : null;
|
|
908
|
+
}
|
|
909
|
+
const trimmed = String(value).trim();
|
|
910
|
+
if (!trimmed) {
|
|
911
|
+
return null;
|
|
912
|
+
}
|
|
913
|
+
const numeric = Number(trimmed);
|
|
914
|
+
if (Number.isFinite(numeric) && numeric > 0) {
|
|
915
|
+
return numeric;
|
|
916
|
+
}
|
|
917
|
+
if (typeof document === 'undefined' || !document.body) {
|
|
918
|
+
return null;
|
|
919
|
+
}
|
|
920
|
+
const probe = document.createElement('div');
|
|
921
|
+
probe.style.position = 'absolute';
|
|
922
|
+
probe.style.visibility = 'hidden';
|
|
923
|
+
probe.style.height = trimmed;
|
|
924
|
+
probe.style.pointerEvents = 'none';
|
|
925
|
+
document.body.appendChild(probe);
|
|
926
|
+
const pixels = probe.getBoundingClientRect().height;
|
|
927
|
+
probe.remove();
|
|
928
|
+
if (!Number.isFinite(pixels) || pixels <= 0) {
|
|
929
|
+
return null;
|
|
930
|
+
}
|
|
931
|
+
return pixels;
|
|
932
|
+
}
|
|
933
|
+
/**
|
|
934
|
+
* Show or hide stats bar
|
|
935
|
+
* @param {boolean} show - Whether to show stats
|
|
936
|
+
*/
|
|
937
|
+
showStats(show) {
|
|
938
|
+
this.options.showStats = show;
|
|
939
|
+
if (show && !this.statsBar) {
|
|
940
|
+
// Create stats bar (add to container, not wrapper)
|
|
941
|
+
this.statsBar = document.createElement('div');
|
|
942
|
+
this.statsBar.className = 'marzipan-stats';
|
|
943
|
+
this.container.appendChild(this.statsBar);
|
|
944
|
+
this._updateStats();
|
|
945
|
+
}
|
|
946
|
+
else if (!show && this.statsBar) {
|
|
947
|
+
// Remove stats bar
|
|
948
|
+
this.statsBar.remove();
|
|
949
|
+
this.statsBar = null;
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
/**
|
|
953
|
+
* Show or hide the plain textarea (toggle overlay visibility)
|
|
954
|
+
* @param {boolean} show - true to show plain textarea (hide overlay), false to show overlay
|
|
955
|
+
* @returns {boolean} Current plain textarea state
|
|
956
|
+
*/
|
|
957
|
+
showPlainTextarea(show) {
|
|
958
|
+
if (show) {
|
|
959
|
+
// Show plain textarea mode (hide overlay)
|
|
960
|
+
this.container.classList.add('plain-mode');
|
|
961
|
+
}
|
|
962
|
+
else {
|
|
963
|
+
// Show overlay mode (hide plain textarea text)
|
|
964
|
+
this.container.classList.remove('plain-mode');
|
|
965
|
+
}
|
|
966
|
+
// Update toolbar button if exists
|
|
967
|
+
if (this.toolbar) {
|
|
968
|
+
const toggleBtn = this.container.querySelector('[data-action="toggle-plain"]');
|
|
969
|
+
if (toggleBtn) {
|
|
970
|
+
// Button is active when showing overlay (not plain mode)
|
|
971
|
+
toggleBtn.classList.toggle('active', !show);
|
|
972
|
+
toggleBtn.title = show ? 'Show markdown preview' : 'Show plain textarea';
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
return show;
|
|
976
|
+
}
|
|
977
|
+
/**
|
|
978
|
+
* Show/hide preview mode
|
|
979
|
+
* @param {boolean} show - Show preview mode if true, edit mode if false
|
|
980
|
+
* @returns {boolean} Current preview mode state
|
|
981
|
+
*/
|
|
982
|
+
showPreviewMode(show) {
|
|
983
|
+
if (show) {
|
|
984
|
+
// Show preview mode (hide textarea, make preview interactive)
|
|
985
|
+
this.container.classList.add('preview-mode');
|
|
986
|
+
}
|
|
987
|
+
else {
|
|
988
|
+
// Show edit mode
|
|
989
|
+
this.container.classList.remove('preview-mode');
|
|
990
|
+
}
|
|
991
|
+
return show;
|
|
992
|
+
}
|
|
993
|
+
/**
|
|
994
|
+
* Destroy the editor instance
|
|
995
|
+
*/
|
|
996
|
+
destroy() {
|
|
997
|
+
// Remove instance reference
|
|
998
|
+
this.element.MarzipanInstance = null;
|
|
999
|
+
Marzipan.instances.delete(this.element);
|
|
1000
|
+
this._teardownAutoResize();
|
|
1001
|
+
if (this.linkTooltip) {
|
|
1002
|
+
this.linkTooltip.destroy();
|
|
1003
|
+
this.linkTooltip = null;
|
|
1004
|
+
}
|
|
1005
|
+
if (this.toolbar) {
|
|
1006
|
+
this.toolbar.destroy();
|
|
1007
|
+
this.toolbar = null;
|
|
1008
|
+
}
|
|
1009
|
+
// Cleanup shortcuts
|
|
1010
|
+
if (this.shortcuts) {
|
|
1011
|
+
this.shortcuts.destroy();
|
|
1012
|
+
}
|
|
1013
|
+
// Remove DOM if created by us
|
|
1014
|
+
if (this.wrapper) {
|
|
1015
|
+
const content = this.getValue();
|
|
1016
|
+
this.wrapper.remove();
|
|
1017
|
+
// Restore original content
|
|
1018
|
+
this.element.textContent = content;
|
|
1019
|
+
}
|
|
1020
|
+
if (this.container) {
|
|
1021
|
+
this.container.removeAttribute('data-marzipan-instance');
|
|
1022
|
+
}
|
|
1023
|
+
this.initialized = false;
|
|
1024
|
+
}
|
|
1025
|
+
// ===== Static Methods =====
|
|
1026
|
+
/**
|
|
1027
|
+
* Initialize multiple editors (static convenience method)
|
|
1028
|
+
* @param {string|Element|NodeList|Array} target - Target element(s)
|
|
1029
|
+
* @param {Object} options - Configuration options
|
|
1030
|
+
* @returns {Array} Array of Marzipan instances
|
|
1031
|
+
*/
|
|
1032
|
+
static init(target, options = {}) {
|
|
1033
|
+
return new Marzipan(target, options);
|
|
1034
|
+
}
|
|
1035
|
+
/**
|
|
1036
|
+
* Get instance from element
|
|
1037
|
+
* @param {Element} element - DOM element
|
|
1038
|
+
* @returns {Marzipan|null} Marzipan instance or null
|
|
1039
|
+
*/
|
|
1040
|
+
static getInstance(element) {
|
|
1041
|
+
return element.MarzipanInstance || Marzipan.instances.get(element) || null;
|
|
1042
|
+
}
|
|
1043
|
+
/**
|
|
1044
|
+
* Destroy all instances
|
|
1045
|
+
*/
|
|
1046
|
+
static destroyAll() {
|
|
1047
|
+
const elements = document.querySelectorAll('[data-marzipan-instance]');
|
|
1048
|
+
elements.forEach(element => {
|
|
1049
|
+
const instance = Marzipan.getInstance(element);
|
|
1050
|
+
if (instance) {
|
|
1051
|
+
instance.destroy();
|
|
1052
|
+
}
|
|
1053
|
+
});
|
|
1054
|
+
}
|
|
1055
|
+
/**
|
|
1056
|
+
* Inject styles into the document
|
|
1057
|
+
* @param {boolean} force - Force re-injection
|
|
1058
|
+
*/
|
|
1059
|
+
static injectStyles(force = false) {
|
|
1060
|
+
if (Marzipan.stylesInjected && !force)
|
|
1061
|
+
return;
|
|
1062
|
+
// Remove any existing Marzipan styles
|
|
1063
|
+
const existing = document.querySelector('style.marzipan-styles');
|
|
1064
|
+
if (existing) {
|
|
1065
|
+
existing.remove();
|
|
1066
|
+
}
|
|
1067
|
+
// Generate and inject new styles with current theme
|
|
1068
|
+
const theme = Marzipan.currentTheme || solar;
|
|
1069
|
+
const styles = generateStyles({ theme });
|
|
1070
|
+
const styleEl = document.createElement('style');
|
|
1071
|
+
styleEl.className = 'marzipan-styles';
|
|
1072
|
+
styleEl.textContent = styles;
|
|
1073
|
+
document.head.appendChild(styleEl);
|
|
1074
|
+
Marzipan.stylesInjected = true;
|
|
1075
|
+
}
|
|
1076
|
+
/**
|
|
1077
|
+
* Set global theme for all Marzipan instances
|
|
1078
|
+
* @param {string|Object} theme - Theme name or custom theme object
|
|
1079
|
+
* @param {Object} customColors - Optional color overrides
|
|
1080
|
+
*/
|
|
1081
|
+
static setTheme(theme, customColors = null) {
|
|
1082
|
+
// Process theme
|
|
1083
|
+
let themeObj = typeof theme === 'string' ? getTheme(theme) : theme;
|
|
1084
|
+
// Apply custom colors if provided
|
|
1085
|
+
if (customColors) {
|
|
1086
|
+
themeObj = mergeTheme(themeObj, customColors);
|
|
1087
|
+
}
|
|
1088
|
+
// Store as current theme
|
|
1089
|
+
Marzipan.currentTheme = themeObj;
|
|
1090
|
+
// Re-inject styles with new theme
|
|
1091
|
+
Marzipan.injectStyles(true);
|
|
1092
|
+
// Update all existing instances - update container theme attribute
|
|
1093
|
+
document.querySelectorAll('.marzipan-container').forEach(container => {
|
|
1094
|
+
const themeName = typeof themeObj === 'string' ? themeObj : themeObj.name;
|
|
1095
|
+
if (themeName) {
|
|
1096
|
+
container.setAttribute('data-theme', themeName);
|
|
1097
|
+
}
|
|
1098
|
+
});
|
|
1099
|
+
// Also handle any old-style wrappers without containers
|
|
1100
|
+
document.querySelectorAll('.marzipan-wrapper').forEach(wrapper => {
|
|
1101
|
+
if (!wrapper.closest('.marzipan-container')) {
|
|
1102
|
+
const themeName = typeof themeObj === 'string' ? themeObj : themeObj.name;
|
|
1103
|
+
if (themeName) {
|
|
1104
|
+
wrapper.setAttribute('data-theme', themeName);
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
// Trigger preview update for the instance
|
|
1108
|
+
const instance = wrapper._instance;
|
|
1109
|
+
if (instance) {
|
|
1110
|
+
instance.updatePreview();
|
|
1111
|
+
}
|
|
1112
|
+
});
|
|
1113
|
+
}
|
|
1114
|
+
/**
|
|
1115
|
+
* Initialize global event listeners
|
|
1116
|
+
*/
|
|
1117
|
+
static initGlobalListeners() {
|
|
1118
|
+
if (Marzipan.globalListenersInitialized)
|
|
1119
|
+
return;
|
|
1120
|
+
// Input event
|
|
1121
|
+
document.addEventListener('input', (e) => {
|
|
1122
|
+
if (e.target && e.target.classList && e.target.classList.contains('marzipan-input')) {
|
|
1123
|
+
const wrapper = e.target.closest('.marzipan-wrapper');
|
|
1124
|
+
const instance = wrapper?._instance;
|
|
1125
|
+
if (instance)
|
|
1126
|
+
instance.handleInput(e);
|
|
1127
|
+
}
|
|
1128
|
+
});
|
|
1129
|
+
// Keydown event
|
|
1130
|
+
document.addEventListener('keydown', (e) => {
|
|
1131
|
+
if (e.target && e.target.classList && e.target.classList.contains('marzipan-input')) {
|
|
1132
|
+
const wrapper = e.target.closest('.marzipan-wrapper');
|
|
1133
|
+
const instance = wrapper?._instance;
|
|
1134
|
+
if (instance)
|
|
1135
|
+
instance.handleKeydown(e);
|
|
1136
|
+
}
|
|
1137
|
+
});
|
|
1138
|
+
// Scroll event
|
|
1139
|
+
document.addEventListener('scroll', (e) => {
|
|
1140
|
+
if (e.target && e.target.classList && e.target.classList.contains('marzipan-input')) {
|
|
1141
|
+
const wrapper = e.target.closest('.marzipan-wrapper');
|
|
1142
|
+
const instance = wrapper?._instance;
|
|
1143
|
+
if (instance)
|
|
1144
|
+
instance.handleScroll(e);
|
|
1145
|
+
}
|
|
1146
|
+
}, true);
|
|
1147
|
+
// Selection change event
|
|
1148
|
+
document.addEventListener('selectionchange', (e) => {
|
|
1149
|
+
const activeElement = document.activeElement;
|
|
1150
|
+
if (activeElement && activeElement.classList.contains('marzipan-input')) {
|
|
1151
|
+
const wrapper = activeElement.closest('.marzipan-wrapper');
|
|
1152
|
+
const instance = wrapper?._instance;
|
|
1153
|
+
if (instance) {
|
|
1154
|
+
// Update stats bar for cursor position
|
|
1155
|
+
if (instance.options.showStats && instance.statsBar) {
|
|
1156
|
+
instance._updateStats();
|
|
1157
|
+
}
|
|
1158
|
+
// Debounce updates
|
|
1159
|
+
clearTimeout(instance._selectionTimeout);
|
|
1160
|
+
instance._selectionTimeout = setTimeout(() => {
|
|
1161
|
+
instance.updatePreview();
|
|
1162
|
+
}, 50);
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
});
|
|
1166
|
+
Marzipan.globalListenersInitialized = true;
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
// Static properties
|
|
1170
|
+
Marzipan.instances = new WeakMap();
|
|
1171
|
+
Marzipan.stylesInjected = false;
|
|
1172
|
+
Marzipan.globalListenersInitialized = false;
|
|
1173
|
+
Marzipan.instanceCount = 0;
|
|
1174
|
+
// Export classes for advanced usage
|
|
1175
|
+
Marzipan.MarkdownParser = MarkdownParser;
|
|
1176
|
+
Marzipan.ShortcutsManager = ShortcutsManager;
|
|
1177
|
+
// Export theme utilities
|
|
1178
|
+
Marzipan.themes = { solar, cave: getTheme('cave') };
|
|
1179
|
+
Marzipan.getTheme = getTheme;
|
|
1180
|
+
// Set default theme
|
|
1181
|
+
Marzipan.currentTheme = solar;
|
|
1182
|
+
// Export for module systems
|
|
1183
|
+
export default Marzipan;
|
|
1184
|
+
export { Marzipan };
|
|
1185
|
+
//# sourceMappingURL=marzipan.js.map
|