@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.
Files changed (220) hide show
  1. package/dist/_basePickBy-BXyyc71p.js +152 -0
  2. package/dist/_basePickBy-BXyyc71p.js.map +1 -0
  3. package/dist/_baseUniq-Dde5pUWM.js +615 -0
  4. package/dist/_baseUniq-Dde5pUWM.js.map +1 -0
  5. package/dist/actions/core/detection.d.ts +11 -0
  6. package/dist/actions/core/detection.d.ts.map +1 -0
  7. package/dist/actions/core/detection.js +142 -0
  8. package/dist/actions/core/detection.js.map +1 -0
  9. package/dist/actions/core/formats.d.ts +5 -0
  10. package/dist/actions/core/formats.d.ts.map +1 -0
  11. package/dist/actions/core/formats.js +75 -0
  12. package/dist/actions/core/formats.js.map +1 -0
  13. package/dist/actions/core/insertion.d.ts +5 -0
  14. package/dist/actions/core/insertion.d.ts.map +1 -0
  15. package/dist/actions/core/insertion.js +116 -0
  16. package/dist/actions/core/insertion.js.map +1 -0
  17. package/dist/actions/core/selection.d.ts +13 -0
  18. package/dist/actions/core/selection.d.ts.map +1 -0
  19. package/dist/actions/core/selection.js +137 -0
  20. package/dist/actions/core/selection.js.map +1 -0
  21. package/dist/actions/debug.d.ts +10 -0
  22. package/dist/actions/debug.d.ts.map +1 -0
  23. package/dist/actions/debug.js +39 -0
  24. package/dist/actions/debug.js.map +1 -0
  25. package/dist/actions/index.d.ts +48 -0
  26. package/dist/actions/index.d.ts.map +1 -0
  27. package/dist/actions/index.js +237 -0
  28. package/dist/actions/index.js.map +1 -0
  29. package/dist/actions/operations/block.d.ts +5 -0
  30. package/dist/actions/operations/block.d.ts.map +1 -0
  31. package/dist/actions/operations/block.js +120 -0
  32. package/dist/actions/operations/block.js.map +1 -0
  33. package/dist/actions/operations/list.d.ts +4 -0
  34. package/dist/actions/operations/list.d.ts.map +1 -0
  35. package/dist/actions/operations/list.js +151 -0
  36. package/dist/actions/operations/list.js.map +1 -0
  37. package/dist/actions/types.d.ts +43 -0
  38. package/dist/actions/types.d.ts.map +1 -0
  39. package/dist/actions/types.js +2 -0
  40. package/dist/actions/types.js.map +1 -0
  41. package/dist/arc-CfMWIGxd.js +84 -0
  42. package/dist/arc-CfMWIGxd.js.map +1 -0
  43. package/dist/architectureDiagram-VXUJARFQ-Bb4i7x-6.js +4663 -0
  44. package/dist/architectureDiagram-VXUJARFQ-Bb4i7x-6.js.map +1 -0
  45. package/dist/blockDiagram-VD42YOAC-ldSoJhgA.js +2262 -0
  46. package/dist/blockDiagram-VD42YOAC-ldSoJhgA.js.map +1 -0
  47. package/dist/c4Diagram-YG6GDRKO-D-ENeA7h.js +1581 -0
  48. package/dist/c4Diagram-YG6GDRKO-D-ENeA7h.js.map +1 -0
  49. package/dist/channel-BCTxewZI.js +6 -0
  50. package/dist/channel-BCTxewZI.js.map +1 -0
  51. package/dist/chunk-4BX2VUAB-Cfh_4i4X.js +9 -0
  52. package/dist/chunk-4BX2VUAB-Cfh_4i4X.js.map +1 -0
  53. package/dist/chunk-55IACEB6-DNOIVRvW.js +9 -0
  54. package/dist/chunk-55IACEB6-DNOIVRvW.js.map +1 -0
  55. package/dist/chunk-B4BG7PRW-C_qWbbk6.js +1376 -0
  56. package/dist/chunk-B4BG7PRW-C_qWbbk6.js.map +1 -0
  57. package/dist/chunk-DI55MBZ5-D_yXJA03.js +1371 -0
  58. package/dist/chunk-DI55MBZ5-D_yXJA03.js.map +1 -0
  59. package/dist/chunk-FMBD7UC4-B9NJjOBf.js +20 -0
  60. package/dist/chunk-FMBD7UC4-B9NJjOBf.js.map +1 -0
  61. package/dist/chunk-QN33PNHL-CVrLUVsI.js +20 -0
  62. package/dist/chunk-QN33PNHL-CVrLUVsI.js.map +1 -0
  63. package/dist/chunk-QZHKN3VN-FvAf_VJR.js +16 -0
  64. package/dist/chunk-QZHKN3VN-FvAf_VJR.js.map +1 -0
  65. package/dist/chunk-TZMSLE5B-DxYTPD3t.js +65 -0
  66. package/dist/chunk-TZMSLE5B-DxYTPD3t.js.map +1 -0
  67. package/dist/classDiagram-2ON5EDUG-Cg641kit.js +17 -0
  68. package/dist/classDiagram-2ON5EDUG-Cg641kit.js.map +1 -0
  69. package/dist/classDiagram-v2-WZHVMYZB-Cg641kit.js +17 -0
  70. package/dist/classDiagram-v2-WZHVMYZB-Cg641kit.js.map +1 -0
  71. package/dist/clone-D062_nJ-.js +9 -0
  72. package/dist/clone-D062_nJ-.js.map +1 -0
  73. package/dist/cose-bilkent-S5V4N54A-DIb5rck_.js +2609 -0
  74. package/dist/cose-bilkent-S5V4N54A-DIb5rck_.js.map +1 -0
  75. package/dist/cytoscape.esm-DfdJODL8.js +18736 -0
  76. package/dist/cytoscape.esm-DfdJODL8.js.map +1 -0
  77. package/dist/dagre-6UL2VRFP-CZKadRq9.js +445 -0
  78. package/dist/dagre-6UL2VRFP-CZKadRq9.js.map +1 -0
  79. package/dist/defaultLocale-D7EN2tov.js +172 -0
  80. package/dist/defaultLocale-D7EN2tov.js.map +1 -0
  81. package/dist/diagram-PSM6KHXK-VDtnZynV.js +532 -0
  82. package/dist/diagram-PSM6KHXK-VDtnZynV.js.map +1 -0
  83. package/dist/diagram-QEK2KX5R-DxjNZqZ_.js +218 -0
  84. package/dist/diagram-QEK2KX5R-DxjNZqZ_.js.map +1 -0
  85. package/dist/diagram-S2PKOQOG-CCX3j4vi.js +143 -0
  86. package/dist/diagram-S2PKOQOG-CCX3j4vi.js.map +1 -0
  87. package/dist/erDiagram-Q2GNP2WA-nwmrf6lJ.js +842 -0
  88. package/dist/erDiagram-Q2GNP2WA-nwmrf6lJ.js.map +1 -0
  89. package/dist/flowDiagram-NV44I4VS-DQEdMlRX.js +1621 -0
  90. package/dist/flowDiagram-NV44I4VS-DQEdMlRX.js.map +1 -0
  91. package/dist/ganttDiagram-LVOFAZNH-WMiy-Gep.js +2506 -0
  92. package/dist/ganttDiagram-LVOFAZNH-WMiy-Gep.js.map +1 -0
  93. package/dist/gitGraphDiagram-NY62KEGX-BgmbSTgN.js +700 -0
  94. package/dist/gitGraphDiagram-NY62KEGX-BgmbSTgN.js.map +1 -0
  95. package/dist/graph-CSVEP8oS.js +248 -0
  96. package/dist/graph-CSVEP8oS.js.map +1 -0
  97. package/dist/icons.d.ts +17 -0
  98. package/dist/icons.d.ts.map +1 -0
  99. package/dist/icons.js +67 -0
  100. package/dist/icons.js.map +1 -0
  101. package/dist/index.d.ts +143 -0
  102. package/dist/index.d.ts.map +1 -0
  103. package/dist/index.js +1 -1
  104. package/dist/index.js.map +1 -1
  105. package/dist/infoDiagram-F6ZHWCRC-BzTgHHGF.js +25 -0
  106. package/dist/infoDiagram-F6ZHWCRC-BzTgHHGF.js.map +1 -0
  107. package/dist/init-DjUOC4st.js +17 -0
  108. package/dist/init-DjUOC4st.js.map +1 -0
  109. package/dist/journeyDiagram-XKPGCS4Q-B5P7hkT-.js +835 -0
  110. package/dist/journeyDiagram-XKPGCS4Q-B5P7hkT-.js.map +1 -0
  111. package/dist/kanban-definition-3W4ZIXB7-CLtnd0xR.js +720 -0
  112. package/dist/kanban-definition-3W4ZIXB7-CLtnd0xR.js.map +1 -0
  113. package/dist/katex-CmGeg_OO.js +11693 -0
  114. package/dist/katex-CmGeg_OO.js.map +1 -0
  115. package/dist/layout-CdTiJocX.js +1325 -0
  116. package/dist/layout-CdTiJocX.js.map +1 -0
  117. package/dist/linear-BhblBDrV.js +260 -0
  118. package/dist/linear-BhblBDrV.js.map +1 -0
  119. package/dist/link-tooltip.d.ts +27 -0
  120. package/dist/link-tooltip.d.ts.map +1 -0
  121. package/dist/link-tooltip.js +224 -0
  122. package/dist/link-tooltip.js.map +1 -0
  123. package/dist/marzipan.d.ts +472 -0
  124. package/dist/marzipan.d.ts.map +1 -0
  125. package/dist/marzipan.js +1185 -0
  126. package/dist/marzipan.js.map +1 -0
  127. package/dist/mermaid.core-DENutRS8.js +15249 -0
  128. package/dist/mermaid.core-DENutRS8.js.map +1 -0
  129. package/dist/mindmap-definition-VGOIOE7T-BXBE9eta.js +785 -0
  130. package/dist/mindmap-definition-VGOIOE7T-BXBE9eta.js.map +1 -0
  131. package/dist/ordinal-DfAQgscy.js +62 -0
  132. package/dist/ordinal-DfAQgscy.js.map +1 -0
  133. package/dist/parser.d.ts +234 -0
  134. package/dist/parser.d.ts.map +1 -0
  135. package/dist/parser.js +886 -0
  136. package/dist/parser.js.map +1 -0
  137. package/dist/pieDiagram-ADFJNKIX-BND3UUQ-.js +162 -0
  138. package/dist/pieDiagram-ADFJNKIX-BND3UUQ-.js.map +1 -0
  139. package/dist/plugins/accentSwatchPlugin.d.ts +15 -0
  140. package/dist/plugins/accentSwatchPlugin.d.ts.map +1 -0
  141. package/dist/plugins/accentSwatchPlugin.js +142 -0
  142. package/dist/plugins/accentSwatchPlugin.js.map +1 -0
  143. package/dist/plugins/imageManagerPlugin.d.ts +16 -0
  144. package/dist/plugins/imageManagerPlugin.d.ts.map +1 -0
  145. package/dist/plugins/imageManagerPlugin.js +161 -0
  146. package/dist/plugins/imageManagerPlugin.js.map +1 -0
  147. package/dist/plugins/imagePicker.d.ts +7 -0
  148. package/dist/plugins/imagePicker.d.ts.map +1 -0
  149. package/dist/plugins/imagePicker.js +33 -0
  150. package/dist/plugins/imagePicker.js.map +1 -0
  151. package/dist/plugins/imagePickerPlugin.d.ts +3 -0
  152. package/dist/plugins/imagePickerPlugin.d.ts.map +1 -0
  153. package/dist/plugins/imagePickerPlugin.js +12 -0
  154. package/dist/plugins/imagePickerPlugin.js.map +1 -0
  155. package/dist/plugins/mermaidExternal.d.ts +2 -0
  156. package/dist/plugins/mermaidExternal.d.ts.map +1 -0
  157. package/dist/plugins/mermaidExternal.js +44 -0
  158. package/dist/plugins/mermaidExternal.js.map +1 -0
  159. package/dist/plugins/mermaidPlugin.d.ts +2 -0
  160. package/dist/plugins/mermaidPlugin.d.ts.map +1 -0
  161. package/dist/plugins/mermaidPlugin.js +25 -0
  162. package/dist/plugins/mermaidPlugin.js.map +1 -0
  163. package/dist/plugins/tableGenerator.d.ts +2 -0
  164. package/dist/plugins/tableGenerator.d.ts.map +1 -0
  165. package/dist/plugins/tableGenerator.js +24 -0
  166. package/dist/plugins/tableGenerator.js.map +1 -0
  167. package/dist/plugins/tableGridPlugin.d.ts +14 -0
  168. package/dist/plugins/tableGridPlugin.d.ts.map +1 -0
  169. package/dist/plugins/tableGridPlugin.js +77 -0
  170. package/dist/plugins/tableGridPlugin.js.map +1 -0
  171. package/dist/plugins/tablePlugin.d.ts +2 -0
  172. package/dist/plugins/tablePlugin.d.ts.map +1 -0
  173. package/dist/plugins/tablePlugin.js +26 -0
  174. package/dist/plugins/tablePlugin.js.map +1 -0
  175. package/dist/plugins/tinyHighlight.d.ts +10 -0
  176. package/dist/plugins/tinyHighlight.d.ts.map +1 -0
  177. package/dist/plugins/tinyHighlight.js +109 -0
  178. package/dist/plugins/tinyHighlight.js.map +1 -0
  179. package/dist/plugins/utils/table.d.ts +3 -0
  180. package/dist/plugins/utils/table.d.ts.map +1 -0
  181. package/dist/plugins/utils/table.js +25 -0
  182. package/dist/plugins/utils/table.js.map +1 -0
  183. package/dist/quadrantDiagram-AYHSOK5B-CnLHbrEF.js +1023 -0
  184. package/dist/quadrantDiagram-AYHSOK5B-CnLHbrEF.js.map +1 -0
  185. package/dist/requirementDiagram-UZGBJVZJ-caC7K17a.js +851 -0
  186. package/dist/requirementDiagram-UZGBJVZJ-caC7K17a.js.map +1 -0
  187. package/dist/sankeyDiagram-TZEHDZUN-CV5-bwv8.js +811 -0
  188. package/dist/sankeyDiagram-TZEHDZUN-CV5-bwv8.js.map +1 -0
  189. package/dist/sequenceDiagram-WL72ISMW-Cq1otbHU.js +2512 -0
  190. package/dist/sequenceDiagram-WL72ISMW-Cq1otbHU.js.map +1 -0
  191. package/dist/shortcuts.d.ts +15 -0
  192. package/dist/shortcuts.d.ts.map +1 -0
  193. package/dist/shortcuts.js +78 -0
  194. package/dist/shortcuts.js.map +1 -0
  195. package/dist/stateDiagram-FKZM4ZOC-BJ5UwskL.js +264 -0
  196. package/dist/stateDiagram-FKZM4ZOC-BJ5UwskL.js.map +1 -0
  197. package/dist/stateDiagram-v2-4FDKWEC3-H1gqsSsO.js +17 -0
  198. package/dist/stateDiagram-v2-4FDKWEC3-H1gqsSsO.js.map +1 -0
  199. package/dist/styles.d.ts +11 -0
  200. package/dist/styles.d.ts.map +1 -0
  201. package/dist/styles.js +854 -0
  202. package/dist/styles.js.map +1 -0
  203. package/dist/table-DMIy93NJ.js +25 -0
  204. package/dist/table-DMIy93NJ.js.map +1 -0
  205. package/dist/themes.d.ts +197 -0
  206. package/dist/themes.d.ts.map +1 -0
  207. package/dist/themes.js +120 -0
  208. package/dist/themes.js.map +1 -0
  209. package/dist/timeline-definition-IT6M3QCI-CqPEUsh4.js +796 -0
  210. package/dist/timeline-definition-IT6M3QCI-CqPEUsh4.js.map +1 -0
  211. package/dist/toolbar.d.ts +37 -0
  212. package/dist/toolbar.d.ts.map +1 -0
  213. package/dist/toolbar.js +283 -0
  214. package/dist/toolbar.js.map +1 -0
  215. package/dist/treemap-75Q7IDZK-DmPg5GN7.js +12988 -0
  216. package/dist/treemap-75Q7IDZK-DmPg5GN7.js.map +1 -0
  217. package/dist/xychartDiagram-PRI3JC2R-C3_IO4Rm.js +1341 -0
  218. package/dist/xychartDiagram-PRI3JC2R-C3_IO4Rm.js.map +1 -0
  219. package/docs/types.d.ts +1 -1
  220. package/package.json +15 -11
@@ -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