overtype 1.2.7 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,4785 @@
1
+ /**
2
+ * OverType v2.0.0
3
+ * A lightweight markdown editor library with perfect WYSIWYG alignment
4
+ * @license MIT
5
+ * @author Demo User
6
+ * https://github.com/demo/overtype
7
+ */
8
+ var OverTypeEditor = (() => {
9
+ var __defProp = Object.defineProperty;
10
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
11
+ var __getOwnPropNames = Object.getOwnPropertyNames;
12
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
13
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
14
+ var __export = (target, all) => {
15
+ for (var name in all)
16
+ __defProp(target, name, { get: all[name], enumerable: true });
17
+ };
18
+ var __copyProps = (to, from, except, desc) => {
19
+ if (from && typeof from === "object" || typeof from === "function") {
20
+ for (let key of __getOwnPropNames(from))
21
+ if (!__hasOwnProp.call(to, key) && key !== except)
22
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
23
+ }
24
+ return to;
25
+ };
26
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
27
+ var __publicField = (obj, key, value) => {
28
+ __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
29
+ return value;
30
+ };
31
+
32
+ // src/overtype-webcomponent.js
33
+ var overtype_webcomponent_exports = {};
34
+ __export(overtype_webcomponent_exports, {
35
+ default: () => overtype_webcomponent_default
36
+ });
37
+
38
+ // src/parser.js
39
+ var MarkdownParser = class {
40
+ /**
41
+ * Reset link index (call before parsing a new document)
42
+ */
43
+ static resetLinkIndex() {
44
+ this.linkIndex = 0;
45
+ }
46
+ /**
47
+ * Set global code highlighter function
48
+ * @param {Function|null} highlighter - Function that takes (code, language) and returns highlighted HTML
49
+ */
50
+ static setCodeHighlighter(highlighter) {
51
+ this.codeHighlighter = highlighter;
52
+ }
53
+ /**
54
+ * Escape HTML special characters
55
+ * @param {string} text - Raw text to escape
56
+ * @returns {string} Escaped HTML-safe text
57
+ */
58
+ static escapeHtml(text) {
59
+ const map = {
60
+ "&": "&",
61
+ "<": "&lt;",
62
+ ">": "&gt;",
63
+ '"': "&quot;",
64
+ "'": "&#39;"
65
+ };
66
+ return text.replace(/[&<>"']/g, (m) => map[m]);
67
+ }
68
+ /**
69
+ * Preserve leading spaces as non-breaking spaces
70
+ * @param {string} html - HTML string
71
+ * @param {string} originalLine - Original line with spaces
72
+ * @returns {string} HTML with preserved indentation
73
+ */
74
+ static preserveIndentation(html, originalLine) {
75
+ const leadingSpaces = originalLine.match(/^(\s*)/)[1];
76
+ const indentation = leadingSpaces.replace(/ /g, "&nbsp;");
77
+ return html.replace(/^\s*/, indentation);
78
+ }
79
+ /**
80
+ * Parse headers (h1-h3 only)
81
+ * @param {string} html - HTML line to parse
82
+ * @returns {string} Parsed HTML with header styling
83
+ */
84
+ static parseHeader(html) {
85
+ return html.replace(/^(#{1,3})\s(.+)$/, (match, hashes, content) => {
86
+ const level = hashes.length;
87
+ return `<h${level}><span class="syntax-marker">${hashes} </span>${content}</h${level}>`;
88
+ });
89
+ }
90
+ /**
91
+ * Parse horizontal rules
92
+ * @param {string} html - HTML line to parse
93
+ * @returns {string|null} Parsed horizontal rule or null
94
+ */
95
+ static parseHorizontalRule(html) {
96
+ if (html.match(/^(-{3,}|\*{3,}|_{3,})$/)) {
97
+ return `<div><span class="hr-marker">${html}</span></div>`;
98
+ }
99
+ return null;
100
+ }
101
+ /**
102
+ * Parse blockquotes
103
+ * @param {string} html - HTML line to parse
104
+ * @returns {string} Parsed blockquote
105
+ */
106
+ static parseBlockquote(html) {
107
+ return html.replace(/^&gt; (.+)$/, (match, content) => {
108
+ return `<span class="blockquote"><span class="syntax-marker">&gt;</span> ${content}</span>`;
109
+ });
110
+ }
111
+ /**
112
+ * Parse bullet lists
113
+ * @param {string} html - HTML line to parse
114
+ * @returns {string} Parsed bullet list item
115
+ */
116
+ static parseBulletList(html) {
117
+ return html.replace(/^((?:&nbsp;)*)([-*])\s(.+)$/, (match, indent, marker, content) => {
118
+ return `${indent}<li class="bullet-list"><span class="syntax-marker">${marker} </span>${content}</li>`;
119
+ });
120
+ }
121
+ /**
122
+ * Parse task lists (GitHub Flavored Markdown checkboxes)
123
+ * @param {string} html - HTML line to parse
124
+ * @param {boolean} isPreviewMode - Whether to render actual checkboxes (preview) or keep syntax visible (normal)
125
+ * @returns {string} Parsed task list item
126
+ */
127
+ static parseTaskList(html, isPreviewMode = false) {
128
+ return html.replace(/^((?:&nbsp;)*)-\s+\[([ xX])\]\s+(.+)$/, (match, indent, checked, content) => {
129
+ if (isPreviewMode) {
130
+ const isChecked = checked.toLowerCase() === "x";
131
+ return `${indent}<li class="task-list"><input type="checkbox" disabled ${isChecked ? "checked" : ""}> ${content}</li>`;
132
+ } else {
133
+ return `${indent}<li class="task-list"><span class="syntax-marker">- [${checked}] </span>${content}</li>`;
134
+ }
135
+ });
136
+ }
137
+ /**
138
+ * Parse numbered lists
139
+ * @param {string} html - HTML line to parse
140
+ * @returns {string} Parsed numbered list item
141
+ */
142
+ static parseNumberedList(html) {
143
+ return html.replace(/^((?:&nbsp;)*)(\d+\.)\s(.+)$/, (match, indent, marker, content) => {
144
+ return `${indent}<li class="ordered-list"><span class="syntax-marker">${marker} </span>${content}</li>`;
145
+ });
146
+ }
147
+ /**
148
+ * Parse code blocks (markers only)
149
+ * @param {string} html - HTML line to parse
150
+ * @returns {string|null} Parsed code fence or null
151
+ */
152
+ static parseCodeBlock(html) {
153
+ const codeFenceRegex = /^`{3}[^`]*$/;
154
+ if (codeFenceRegex.test(html)) {
155
+ return `<div><span class="code-fence">${html}</span></div>`;
156
+ }
157
+ return null;
158
+ }
159
+ /**
160
+ * Parse bold text
161
+ * @param {string} html - HTML with potential bold markdown
162
+ * @returns {string} HTML with bold styling
163
+ */
164
+ static parseBold(html) {
165
+ html = html.replace(/\*\*(.+?)\*\*/g, '<strong><span class="syntax-marker">**</span>$1<span class="syntax-marker">**</span></strong>');
166
+ html = html.replace(/__(.+?)__/g, '<strong><span class="syntax-marker">__</span>$1<span class="syntax-marker">__</span></strong>');
167
+ return html;
168
+ }
169
+ /**
170
+ * Parse italic text
171
+ * Note: Uses lookbehind assertions - requires modern browsers
172
+ * @param {string} html - HTML with potential italic markdown
173
+ * @returns {string} HTML with italic styling
174
+ */
175
+ static parseItalic(html) {
176
+ html = html.replace(new RegExp("(?<!\\*)\\*(?!\\*)(.+?)(?<!\\*)\\*(?!\\*)", "g"), '<em><span class="syntax-marker">*</span>$1<span class="syntax-marker">*</span></em>');
177
+ html = html.replace(new RegExp("(?<=^|\\s)_(?!_)(.+?)(?<!_)_(?!_)(?=\\s|$)", "g"), '<em><span class="syntax-marker">_</span>$1<span class="syntax-marker">_</span></em>');
178
+ return html;
179
+ }
180
+ /**
181
+ * Parse strikethrough text
182
+ * Supports both single (~) and double (~~) tildes, but rejects 3+ tildes
183
+ * @param {string} html - HTML with potential strikethrough markdown
184
+ * @returns {string} HTML with strikethrough styling
185
+ */
186
+ static parseStrikethrough(html) {
187
+ html = html.replace(new RegExp("(?<!~)~~(?!~)(.+?)(?<!~)~~(?!~)", "g"), '<del><span class="syntax-marker">~~</span>$1<span class="syntax-marker">~~</span></del>');
188
+ html = html.replace(new RegExp("(?<!~)~(?!~)(.+?)(?<!~)~(?!~)", "g"), '<del><span class="syntax-marker">~</span>$1<span class="syntax-marker">~</span></del>');
189
+ return html;
190
+ }
191
+ /**
192
+ * Parse inline code
193
+ * @param {string} html - HTML with potential code markdown
194
+ * @returns {string} HTML with code styling
195
+ */
196
+ static parseInlineCode(html) {
197
+ return html.replace(new RegExp("(?<!`)(`+)(?!`)((?:(?!\\1).)+?)(\\1)(?!`)", "g"), '<code><span class="syntax-marker">$1</span>$2<span class="syntax-marker">$3</span></code>');
198
+ }
199
+ /**
200
+ * Sanitize URL to prevent XSS attacks
201
+ * @param {string} url - URL to sanitize
202
+ * @returns {string} Safe URL or '#' if dangerous
203
+ */
204
+ static sanitizeUrl(url) {
205
+ const trimmed = url.trim();
206
+ const lower = trimmed.toLowerCase();
207
+ const safeProtocols = [
208
+ "http://",
209
+ "https://",
210
+ "mailto:",
211
+ "ftp://",
212
+ "ftps://"
213
+ ];
214
+ const hasSafeProtocol = safeProtocols.some((protocol) => lower.startsWith(protocol));
215
+ const isRelative = trimmed.startsWith("/") || trimmed.startsWith("#") || trimmed.startsWith("?") || trimmed.startsWith(".") || !trimmed.includes(":") && !trimmed.includes("//");
216
+ if (hasSafeProtocol || isRelative) {
217
+ return url;
218
+ }
219
+ return "#";
220
+ }
221
+ /**
222
+ * Parse links
223
+ * @param {string} html - HTML with potential link markdown
224
+ * @returns {string} HTML with link styling
225
+ */
226
+ static parseLinks(html) {
227
+ return html.replace(/\[(.+?)\]\((.+?)\)/g, (match, text, url) => {
228
+ const anchorName = `--link-${this.linkIndex++}`;
229
+ const safeUrl = this.sanitizeUrl(url);
230
+ return `<a href="${safeUrl}" style="anchor-name: ${anchorName}"><span class="syntax-marker">[</span>${text}<span class="syntax-marker url-part">](${url})</span></a>`;
231
+ });
232
+ }
233
+ /**
234
+ * Identify and protect sanctuaries (code and links) before parsing
235
+ * @param {string} text - Text with potential markdown
236
+ * @returns {Object} Object with protected text and sanctuary map
237
+ */
238
+ static identifyAndProtectSanctuaries(text) {
239
+ const sanctuaries = /* @__PURE__ */ new Map();
240
+ let sanctuaryCounter = 0;
241
+ let protectedText = text;
242
+ const protectedRegions = [];
243
+ const linkRegex = /\[([^\]]+)\]\(([^)]+)\)/g;
244
+ let linkMatch;
245
+ while ((linkMatch = linkRegex.exec(text)) !== null) {
246
+ const bracketPos = linkMatch.index + linkMatch[0].indexOf("](");
247
+ const urlStart = bracketPos + 2;
248
+ const urlEnd = urlStart + linkMatch[2].length;
249
+ protectedRegions.push({ start: urlStart, end: urlEnd });
250
+ }
251
+ const codeRegex = new RegExp("(?<!`)(`+)(?!`)((?:(?!\\1).)+?)(\\1)(?!`)", "g");
252
+ let codeMatch;
253
+ const codeMatches = [];
254
+ while ((codeMatch = codeRegex.exec(text)) !== null) {
255
+ const codeStart = codeMatch.index;
256
+ const codeEnd = codeMatch.index + codeMatch[0].length;
257
+ const inProtectedRegion = protectedRegions.some(
258
+ (region) => codeStart >= region.start && codeEnd <= region.end
259
+ );
260
+ if (!inProtectedRegion) {
261
+ codeMatches.push({
262
+ match: codeMatch[0],
263
+ index: codeMatch.index,
264
+ openTicks: codeMatch[1],
265
+ content: codeMatch[2],
266
+ closeTicks: codeMatch[3]
267
+ });
268
+ }
269
+ }
270
+ codeMatches.sort((a, b) => b.index - a.index);
271
+ codeMatches.forEach((codeInfo) => {
272
+ const placeholder = `\uE000${sanctuaryCounter++}\uE001`;
273
+ sanctuaries.set(placeholder, {
274
+ type: "code",
275
+ original: codeInfo.match,
276
+ openTicks: codeInfo.openTicks,
277
+ content: codeInfo.content,
278
+ closeTicks: codeInfo.closeTicks
279
+ });
280
+ protectedText = protectedText.substring(0, codeInfo.index) + placeholder + protectedText.substring(codeInfo.index + codeInfo.match.length);
281
+ });
282
+ protectedText = protectedText.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (match, linkText, url) => {
283
+ const placeholder = `\uE000${sanctuaryCounter++}\uE001`;
284
+ sanctuaries.set(placeholder, {
285
+ type: "link",
286
+ original: match,
287
+ linkText,
288
+ url
289
+ });
290
+ return placeholder;
291
+ });
292
+ return { protectedText, sanctuaries };
293
+ }
294
+ /**
295
+ * Restore and transform sanctuaries back to HTML
296
+ * @param {string} html - HTML with sanctuary placeholders
297
+ * @param {Map} sanctuaries - Map of sanctuaries to restore
298
+ * @returns {string} HTML with sanctuaries restored and transformed
299
+ */
300
+ static restoreAndTransformSanctuaries(html, sanctuaries) {
301
+ const placeholders = Array.from(sanctuaries.keys()).sort((a, b) => {
302
+ const indexA = html.indexOf(a);
303
+ const indexB = html.indexOf(b);
304
+ return indexA - indexB;
305
+ });
306
+ placeholders.forEach((placeholder) => {
307
+ const sanctuary = sanctuaries.get(placeholder);
308
+ let replacement;
309
+ if (sanctuary.type === "code") {
310
+ replacement = `<code><span class="syntax-marker">${sanctuary.openTicks}</span>${sanctuary.content}<span class="syntax-marker">${sanctuary.closeTicks}</span></code>`;
311
+ } else if (sanctuary.type === "link") {
312
+ let processedLinkText = sanctuary.linkText;
313
+ sanctuaries.forEach((innerSanctuary, innerPlaceholder) => {
314
+ if (processedLinkText.includes(innerPlaceholder)) {
315
+ if (innerSanctuary.type === "code") {
316
+ const codeHtml = `<code><span class="syntax-marker">${innerSanctuary.openTicks}</span>${innerSanctuary.content}<span class="syntax-marker">${innerSanctuary.closeTicks}</span></code>`;
317
+ processedLinkText = processedLinkText.replace(innerPlaceholder, codeHtml);
318
+ }
319
+ }
320
+ });
321
+ processedLinkText = this.parseStrikethrough(processedLinkText);
322
+ processedLinkText = this.parseBold(processedLinkText);
323
+ processedLinkText = this.parseItalic(processedLinkText);
324
+ const anchorName = `--link-${this.linkIndex++}`;
325
+ const safeUrl = this.sanitizeUrl(sanctuary.url);
326
+ replacement = `<a href="${safeUrl}" style="anchor-name: ${anchorName}"><span class="syntax-marker">[</span>${processedLinkText}<span class="syntax-marker url-part">](${sanctuary.url})</span></a>`;
327
+ }
328
+ html = html.replace(placeholder, replacement);
329
+ });
330
+ return html;
331
+ }
332
+ /**
333
+ * Parse all inline elements in correct order
334
+ * @param {string} text - Text with potential inline markdown
335
+ * @returns {string} HTML with all inline styling
336
+ */
337
+ static parseInlineElements(text) {
338
+ const { protectedText, sanctuaries } = this.identifyAndProtectSanctuaries(text);
339
+ let html = protectedText;
340
+ html = this.parseStrikethrough(html);
341
+ html = this.parseBold(html);
342
+ html = this.parseItalic(html);
343
+ html = this.restoreAndTransformSanctuaries(html, sanctuaries);
344
+ return html;
345
+ }
346
+ /**
347
+ * Parse a single line of markdown
348
+ * @param {string} line - Raw markdown line
349
+ * @returns {string} Parsed HTML line
350
+ */
351
+ static parseLine(line, isPreviewMode = false) {
352
+ let html = this.escapeHtml(line);
353
+ html = this.preserveIndentation(html, line);
354
+ const horizontalRule = this.parseHorizontalRule(html);
355
+ if (horizontalRule)
356
+ return horizontalRule;
357
+ const codeBlock = this.parseCodeBlock(html);
358
+ if (codeBlock)
359
+ return codeBlock;
360
+ html = this.parseHeader(html);
361
+ html = this.parseBlockquote(html);
362
+ html = this.parseTaskList(html, isPreviewMode);
363
+ html = this.parseBulletList(html);
364
+ html = this.parseNumberedList(html);
365
+ html = this.parseInlineElements(html);
366
+ if (html.trim() === "") {
367
+ return "<div>&nbsp;</div>";
368
+ }
369
+ return `<div>${html}</div>`;
370
+ }
371
+ /**
372
+ * Parse full markdown text
373
+ * @param {string} text - Full markdown text
374
+ * @param {number} activeLine - Currently active line index (optional)
375
+ * @param {boolean} showActiveLineRaw - Show raw markdown on active line
376
+ * @param {Function} instanceHighlighter - Instance-specific code highlighter (optional, overrides global if provided)
377
+ * @returns {string} Parsed HTML
378
+ */
379
+ static parse(text, activeLine = -1, showActiveLineRaw = false, instanceHighlighter, isPreviewMode = false) {
380
+ this.resetLinkIndex();
381
+ const lines = text.split("\n");
382
+ let inCodeBlock = false;
383
+ const parsedLines = lines.map((line, index) => {
384
+ if (showActiveLineRaw && index === activeLine) {
385
+ const content = this.escapeHtml(line) || "&nbsp;";
386
+ return `<div class="raw-line">${content}</div>`;
387
+ }
388
+ const codeFenceRegex = /^```[^`]*$/;
389
+ if (codeFenceRegex.test(line)) {
390
+ inCodeBlock = !inCodeBlock;
391
+ return this.parseLine(line, isPreviewMode);
392
+ }
393
+ if (inCodeBlock) {
394
+ const escaped = this.escapeHtml(line);
395
+ const indented = this.preserveIndentation(escaped, line);
396
+ return `<div>${indented || "&nbsp;"}</div>`;
397
+ }
398
+ return this.parseLine(line, isPreviewMode);
399
+ });
400
+ const html = parsedLines.join("");
401
+ return this.postProcessHTML(html, instanceHighlighter);
402
+ }
403
+ /**
404
+ * Post-process HTML to consolidate lists and code blocks
405
+ * @param {string} html - HTML to post-process
406
+ * @param {Function} instanceHighlighter - Instance-specific code highlighter (optional, overrides global if provided)
407
+ * @returns {string} Post-processed HTML with consolidated lists and code blocks
408
+ */
409
+ static postProcessHTML(html, instanceHighlighter) {
410
+ if (typeof document === "undefined" || !document) {
411
+ return this.postProcessHTMLManual(html, instanceHighlighter);
412
+ }
413
+ const container = document.createElement("div");
414
+ container.innerHTML = html;
415
+ let currentList = null;
416
+ let listType = null;
417
+ let currentCodeBlock = null;
418
+ let inCodeBlock = false;
419
+ const children = Array.from(container.children);
420
+ for (let i = 0; i < children.length; i++) {
421
+ const child = children[i];
422
+ if (!child.parentNode)
423
+ continue;
424
+ const codeFence = child.querySelector(".code-fence");
425
+ if (codeFence) {
426
+ const fenceText = codeFence.textContent;
427
+ if (fenceText.startsWith("```")) {
428
+ if (!inCodeBlock) {
429
+ inCodeBlock = true;
430
+ currentCodeBlock = document.createElement("pre");
431
+ const codeElement = document.createElement("code");
432
+ currentCodeBlock.appendChild(codeElement);
433
+ currentCodeBlock.className = "code-block";
434
+ const lang = fenceText.slice(3).trim();
435
+ if (lang) {
436
+ codeElement.className = `language-${lang}`;
437
+ }
438
+ container.insertBefore(currentCodeBlock, child.nextSibling);
439
+ currentCodeBlock._codeElement = codeElement;
440
+ currentCodeBlock._language = lang;
441
+ currentCodeBlock._codeContent = "";
442
+ continue;
443
+ } else {
444
+ const highlighter = instanceHighlighter || this.codeHighlighter;
445
+ if (currentCodeBlock && highlighter && currentCodeBlock._codeContent) {
446
+ try {
447
+ const result = highlighter(
448
+ currentCodeBlock._codeContent,
449
+ currentCodeBlock._language || ""
450
+ );
451
+ if (result && typeof result.then === "function") {
452
+ console.warn("Async highlighters are not supported in parse() because it returns an HTML string. The caller creates new DOM elements from that string, breaking references to the elements we would update. Use synchronous highlighters only.");
453
+ } else {
454
+ if (result && typeof result === "string" && result.trim()) {
455
+ currentCodeBlock._codeElement.innerHTML = result;
456
+ }
457
+ }
458
+ } catch (error) {
459
+ console.warn("Code highlighting failed:", error);
460
+ }
461
+ }
462
+ inCodeBlock = false;
463
+ currentCodeBlock = null;
464
+ continue;
465
+ }
466
+ }
467
+ }
468
+ if (inCodeBlock && currentCodeBlock && child.tagName === "DIV" && !child.querySelector(".code-fence")) {
469
+ const codeElement = currentCodeBlock._codeElement || currentCodeBlock.querySelector("code");
470
+ if (currentCodeBlock._codeContent.length > 0) {
471
+ currentCodeBlock._codeContent += "\n";
472
+ }
473
+ const lineText = child.textContent.replace(/\u00A0/g, " ");
474
+ currentCodeBlock._codeContent += lineText;
475
+ if (codeElement.textContent.length > 0) {
476
+ codeElement.textContent += "\n";
477
+ }
478
+ codeElement.textContent += lineText;
479
+ child.remove();
480
+ continue;
481
+ }
482
+ let listItem = null;
483
+ if (child.tagName === "DIV") {
484
+ listItem = child.querySelector("li");
485
+ }
486
+ if (listItem) {
487
+ const isBullet = listItem.classList.contains("bullet-list");
488
+ const isOrdered = listItem.classList.contains("ordered-list");
489
+ if (!isBullet && !isOrdered) {
490
+ currentList = null;
491
+ listType = null;
492
+ continue;
493
+ }
494
+ const newType = isBullet ? "ul" : "ol";
495
+ if (!currentList || listType !== newType) {
496
+ currentList = document.createElement(newType);
497
+ container.insertBefore(currentList, child);
498
+ listType = newType;
499
+ }
500
+ const indentationNodes = [];
501
+ for (const node of child.childNodes) {
502
+ if (node.nodeType === 3 && node.textContent.match(/^\u00A0+$/)) {
503
+ indentationNodes.push(node.cloneNode(true));
504
+ } else if (node === listItem) {
505
+ break;
506
+ }
507
+ }
508
+ indentationNodes.forEach((node) => {
509
+ listItem.insertBefore(node, listItem.firstChild);
510
+ });
511
+ currentList.appendChild(listItem);
512
+ child.remove();
513
+ } else {
514
+ currentList = null;
515
+ listType = null;
516
+ }
517
+ }
518
+ return container.innerHTML;
519
+ }
520
+ /**
521
+ * Manual post-processing for Node.js environments (without DOM)
522
+ * @param {string} html - HTML to post-process
523
+ * @param {Function} instanceHighlighter - Instance-specific code highlighter (optional, overrides global if provided)
524
+ * @returns {string} Post-processed HTML
525
+ */
526
+ static postProcessHTMLManual(html, instanceHighlighter) {
527
+ let processed = html;
528
+ processed = processed.replace(/((?:<div>(?:&nbsp;)*<li class="bullet-list">.*?<\/li><\/div>\s*)+)/gs, (match) => {
529
+ const divs = match.match(/<div>(?:&nbsp;)*<li class="bullet-list">.*?<\/li><\/div>/gs) || [];
530
+ if (divs.length > 0) {
531
+ const items = divs.map((div) => {
532
+ const indentMatch = div.match(/<div>((?:&nbsp;)*)<li/);
533
+ const listItemMatch = div.match(/<li class="bullet-list">.*?<\/li>/);
534
+ if (indentMatch && listItemMatch) {
535
+ const indentation = indentMatch[1];
536
+ const listItem = listItemMatch[0];
537
+ return listItem.replace(/<li class="bullet-list">/, `<li class="bullet-list">${indentation}`);
538
+ }
539
+ return listItemMatch ? listItemMatch[0] : "";
540
+ }).filter(Boolean);
541
+ return "<ul>" + items.join("") + "</ul>";
542
+ }
543
+ return match;
544
+ });
545
+ processed = processed.replace(/((?:<div>(?:&nbsp;)*<li class="ordered-list">.*?<\/li><\/div>\s*)+)/gs, (match) => {
546
+ const divs = match.match(/<div>(?:&nbsp;)*<li class="ordered-list">.*?<\/li><\/div>/gs) || [];
547
+ if (divs.length > 0) {
548
+ const items = divs.map((div) => {
549
+ const indentMatch = div.match(/<div>((?:&nbsp;)*)<li/);
550
+ const listItemMatch = div.match(/<li class="ordered-list">.*?<\/li>/);
551
+ if (indentMatch && listItemMatch) {
552
+ const indentation = indentMatch[1];
553
+ const listItem = listItemMatch[0];
554
+ return listItem.replace(/<li class="ordered-list">/, `<li class="ordered-list">${indentation}`);
555
+ }
556
+ return listItemMatch ? listItemMatch[0] : "";
557
+ }).filter(Boolean);
558
+ return "<ol>" + items.join("") + "</ol>";
559
+ }
560
+ return match;
561
+ });
562
+ const codeBlockRegex = /<div><span class="code-fence">(```[^<]*)<\/span><\/div>(.*?)<div><span class="code-fence">(```)<\/span><\/div>/gs;
563
+ processed = processed.replace(codeBlockRegex, (match, openFence, content, closeFence) => {
564
+ const lines = content.match(/<div>(.*?)<\/div>/gs) || [];
565
+ const codeContent = lines.map((line) => {
566
+ const text = line.replace(/<div>(.*?)<\/div>/s, "$1").replace(/&nbsp;/g, " ");
567
+ return text;
568
+ }).join("\n");
569
+ const lang = openFence.slice(3).trim();
570
+ const langClass = lang ? ` class="language-${lang}"` : "";
571
+ let highlightedContent = codeContent;
572
+ const highlighter = instanceHighlighter || this.codeHighlighter;
573
+ if (highlighter) {
574
+ try {
575
+ const decodedCode = codeContent.replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
576
+ const result2 = highlighter(decodedCode, lang);
577
+ if (result2 && typeof result2.then === "function") {
578
+ console.warn("Async highlighters are not supported in Node.js (non-DOM) context. Use synchronous highlighters for server-side rendering.");
579
+ } else {
580
+ if (result2 && typeof result2 === "string" && result2.trim()) {
581
+ highlightedContent = result2;
582
+ }
583
+ }
584
+ } catch (error) {
585
+ console.warn("Code highlighting failed:", error);
586
+ }
587
+ }
588
+ let result = `<div><span class="code-fence">${openFence}</span></div>`;
589
+ result += `<pre class="code-block"><code${langClass}>${highlightedContent}</code></pre>`;
590
+ result += `<div><span class="code-fence">${closeFence}</span></div>`;
591
+ return result;
592
+ });
593
+ return processed;
594
+ }
595
+ /**
596
+ * Get list context at cursor position
597
+ * @param {string} text - Full text content
598
+ * @param {number} cursorPosition - Current cursor position
599
+ * @returns {Object} List context information
600
+ */
601
+ static getListContext(text, cursorPosition) {
602
+ const lines = text.split("\n");
603
+ let currentPos = 0;
604
+ let lineIndex = 0;
605
+ let lineStart = 0;
606
+ for (let i = 0; i < lines.length; i++) {
607
+ const lineLength = lines[i].length;
608
+ if (currentPos + lineLength >= cursorPosition) {
609
+ lineIndex = i;
610
+ lineStart = currentPos;
611
+ break;
612
+ }
613
+ currentPos += lineLength + 1;
614
+ }
615
+ const currentLine = lines[lineIndex];
616
+ const lineEnd = lineStart + currentLine.length;
617
+ const checkboxMatch = currentLine.match(this.LIST_PATTERNS.checkbox);
618
+ if (checkboxMatch) {
619
+ return {
620
+ inList: true,
621
+ listType: "checkbox",
622
+ indent: checkboxMatch[1],
623
+ marker: "-",
624
+ checked: checkboxMatch[2] === "x",
625
+ content: checkboxMatch[3],
626
+ lineStart,
627
+ lineEnd,
628
+ markerEndPos: lineStart + checkboxMatch[1].length + checkboxMatch[2].length + 5
629
+ // indent + "- [ ] "
630
+ };
631
+ }
632
+ const bulletMatch = currentLine.match(this.LIST_PATTERNS.bullet);
633
+ if (bulletMatch) {
634
+ return {
635
+ inList: true,
636
+ listType: "bullet",
637
+ indent: bulletMatch[1],
638
+ marker: bulletMatch[2],
639
+ content: bulletMatch[3],
640
+ lineStart,
641
+ lineEnd,
642
+ markerEndPos: lineStart + bulletMatch[1].length + bulletMatch[2].length + 1
643
+ // indent + marker + space
644
+ };
645
+ }
646
+ const numberedMatch = currentLine.match(this.LIST_PATTERNS.numbered);
647
+ if (numberedMatch) {
648
+ return {
649
+ inList: true,
650
+ listType: "numbered",
651
+ indent: numberedMatch[1],
652
+ marker: parseInt(numberedMatch[2]),
653
+ content: numberedMatch[3],
654
+ lineStart,
655
+ lineEnd,
656
+ markerEndPos: lineStart + numberedMatch[1].length + numberedMatch[2].length + 2
657
+ // indent + number + ". "
658
+ };
659
+ }
660
+ return {
661
+ inList: false,
662
+ listType: null,
663
+ indent: "",
664
+ marker: null,
665
+ content: currentLine,
666
+ lineStart,
667
+ lineEnd,
668
+ markerEndPos: lineStart
669
+ };
670
+ }
671
+ /**
672
+ * Create a new list item based on context
673
+ * @param {Object} context - List context from getListContext
674
+ * @returns {string} New list item text
675
+ */
676
+ static createNewListItem(context) {
677
+ switch (context.listType) {
678
+ case "bullet":
679
+ return `${context.indent}${context.marker} `;
680
+ case "numbered":
681
+ return `${context.indent}${context.marker + 1}. `;
682
+ case "checkbox":
683
+ return `${context.indent}- [ ] `;
684
+ default:
685
+ return "";
686
+ }
687
+ }
688
+ /**
689
+ * Renumber all numbered lists in text
690
+ * @param {string} text - Text containing numbered lists
691
+ * @returns {string} Text with renumbered lists
692
+ */
693
+ static renumberLists(text) {
694
+ const lines = text.split("\n");
695
+ const numbersByIndent = /* @__PURE__ */ new Map();
696
+ let inList = false;
697
+ const result = lines.map((line) => {
698
+ const match = line.match(this.LIST_PATTERNS.numbered);
699
+ if (match) {
700
+ const indent = match[1];
701
+ const indentLevel = indent.length;
702
+ const content = match[3];
703
+ if (!inList) {
704
+ numbersByIndent.clear();
705
+ }
706
+ const currentNumber = (numbersByIndent.get(indentLevel) || 0) + 1;
707
+ numbersByIndent.set(indentLevel, currentNumber);
708
+ for (const [level] of numbersByIndent) {
709
+ if (level > indentLevel) {
710
+ numbersByIndent.delete(level);
711
+ }
712
+ }
713
+ inList = true;
714
+ return `${indent}${currentNumber}. ${content}`;
715
+ } else {
716
+ if (line.trim() === "" || !line.match(/^\s/)) {
717
+ inList = false;
718
+ numbersByIndent.clear();
719
+ }
720
+ return line;
721
+ }
722
+ });
723
+ return result.join("\n");
724
+ }
725
+ };
726
+ // Track link index for anchor naming
727
+ __publicField(MarkdownParser, "linkIndex", 0);
728
+ // Global code highlighter function
729
+ __publicField(MarkdownParser, "codeHighlighter", null);
730
+ /**
731
+ * List pattern definitions
732
+ */
733
+ __publicField(MarkdownParser, "LIST_PATTERNS", {
734
+ bullet: /^(\s*)([-*+])\s+(.*)$/,
735
+ numbered: /^(\s*)(\d+)\.\s+(.*)$/,
736
+ checkbox: /^(\s*)-\s+\[([ x])\]\s+(.*)$/
737
+ });
738
+
739
+ // node_modules/markdown-actions/dist/markdown-actions.esm.js
740
+ var __defProp2 = Object.defineProperty;
741
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
742
+ var __hasOwnProp2 = Object.prototype.hasOwnProperty;
743
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
744
+ var __defNormalProp2 = (obj, key, value) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
745
+ var __spreadValues = (a, b) => {
746
+ for (var prop in b || (b = {}))
747
+ if (__hasOwnProp2.call(b, prop))
748
+ __defNormalProp2(a, prop, b[prop]);
749
+ if (__getOwnPropSymbols)
750
+ for (var prop of __getOwnPropSymbols(b)) {
751
+ if (__propIsEnum.call(b, prop))
752
+ __defNormalProp2(a, prop, b[prop]);
753
+ }
754
+ return a;
755
+ };
756
+ var FORMATS = {
757
+ bold: {
758
+ prefix: "**",
759
+ suffix: "**",
760
+ trimFirst: true
761
+ },
762
+ italic: {
763
+ prefix: "_",
764
+ suffix: "_",
765
+ trimFirst: true
766
+ },
767
+ code: {
768
+ prefix: "`",
769
+ suffix: "`",
770
+ blockPrefix: "```",
771
+ blockSuffix: "```"
772
+ },
773
+ link: {
774
+ prefix: "[",
775
+ suffix: "](url)",
776
+ replaceNext: "url",
777
+ scanFor: "https?://"
778
+ },
779
+ bulletList: {
780
+ prefix: "- ",
781
+ multiline: true,
782
+ unorderedList: true
783
+ },
784
+ numberedList: {
785
+ prefix: "1. ",
786
+ multiline: true,
787
+ orderedList: true
788
+ },
789
+ quote: {
790
+ prefix: "> ",
791
+ multiline: true,
792
+ surroundWithNewlines: true
793
+ },
794
+ taskList: {
795
+ prefix: "- [ ] ",
796
+ multiline: true,
797
+ surroundWithNewlines: true
798
+ },
799
+ header1: { prefix: "# " },
800
+ header2: { prefix: "## " },
801
+ header3: { prefix: "### " },
802
+ header4: { prefix: "#### " },
803
+ header5: { prefix: "##### " },
804
+ header6: { prefix: "###### " }
805
+ };
806
+ function getDefaultStyle() {
807
+ return {
808
+ prefix: "",
809
+ suffix: "",
810
+ blockPrefix: "",
811
+ blockSuffix: "",
812
+ multiline: false,
813
+ replaceNext: "",
814
+ prefixSpace: false,
815
+ scanFor: "",
816
+ surroundWithNewlines: false,
817
+ orderedList: false,
818
+ unorderedList: false,
819
+ trimFirst: false
820
+ };
821
+ }
822
+ function mergeWithDefaults(format) {
823
+ return __spreadValues(__spreadValues({}, getDefaultStyle()), format);
824
+ }
825
+ var debugMode = false;
826
+ function getDebugMode() {
827
+ return debugMode;
828
+ }
829
+ function debugLog(funcName, message, data) {
830
+ if (!debugMode)
831
+ return;
832
+ console.group(`\u{1F50D} ${funcName}`);
833
+ console.log(message);
834
+ if (data) {
835
+ console.log("Data:", data);
836
+ }
837
+ console.groupEnd();
838
+ }
839
+ function debugSelection(textarea, label) {
840
+ if (!debugMode)
841
+ return;
842
+ const selected = textarea.value.slice(textarea.selectionStart, textarea.selectionEnd);
843
+ console.group(`\u{1F4CD} Selection: ${label}`);
844
+ console.log("Position:", `${textarea.selectionStart}-${textarea.selectionEnd}`);
845
+ console.log("Selected text:", JSON.stringify(selected));
846
+ console.log("Length:", selected.length);
847
+ const before = textarea.value.slice(Math.max(0, textarea.selectionStart - 10), textarea.selectionStart);
848
+ const after = textarea.value.slice(textarea.selectionEnd, Math.min(textarea.value.length, textarea.selectionEnd + 10));
849
+ console.log("Context:", JSON.stringify(before) + "[SELECTION]" + JSON.stringify(after));
850
+ console.groupEnd();
851
+ }
852
+ function debugResult(result) {
853
+ if (!debugMode)
854
+ return;
855
+ console.group("\u{1F4DD} Result");
856
+ console.log("Text to insert:", JSON.stringify(result.text));
857
+ console.log("New selection:", `${result.selectionStart}-${result.selectionEnd}`);
858
+ console.groupEnd();
859
+ }
860
+ var canInsertText = null;
861
+ function insertText(textarea, { text, selectionStart, selectionEnd }) {
862
+ const debugMode2 = getDebugMode();
863
+ if (debugMode2) {
864
+ console.group("\u{1F527} insertText");
865
+ console.log("Current selection:", `${textarea.selectionStart}-${textarea.selectionEnd}`);
866
+ console.log("Text to insert:", JSON.stringify(text));
867
+ console.log("New selection to set:", selectionStart, "-", selectionEnd);
868
+ }
869
+ textarea.focus();
870
+ const originalSelectionStart = textarea.selectionStart;
871
+ const originalSelectionEnd = textarea.selectionEnd;
872
+ const before = textarea.value.slice(0, originalSelectionStart);
873
+ const after = textarea.value.slice(originalSelectionEnd);
874
+ if (debugMode2) {
875
+ console.log("Before text (last 20):", JSON.stringify(before.slice(-20)));
876
+ console.log("After text (first 20):", JSON.stringify(after.slice(0, 20)));
877
+ console.log("Selected text being replaced:", JSON.stringify(textarea.value.slice(originalSelectionStart, originalSelectionEnd)));
878
+ }
879
+ const originalValue = textarea.value;
880
+ const hasSelection = originalSelectionStart !== originalSelectionEnd;
881
+ if (canInsertText === null || canInsertText === true) {
882
+ textarea.contentEditable = "true";
883
+ try {
884
+ canInsertText = document.execCommand("insertText", false, text);
885
+ if (debugMode2)
886
+ console.log("execCommand returned:", canInsertText, "for text with", text.split("\n").length, "lines");
887
+ } catch (error) {
888
+ canInsertText = false;
889
+ if (debugMode2)
890
+ console.log("execCommand threw error:", error);
891
+ }
892
+ textarea.contentEditable = "false";
893
+ }
894
+ if (debugMode2) {
895
+ console.log("canInsertText before:", canInsertText);
896
+ console.log("execCommand result:", canInsertText);
897
+ }
898
+ if (canInsertText) {
899
+ const expectedValue = before + text + after;
900
+ const actualValue = textarea.value;
901
+ if (debugMode2) {
902
+ console.log("Expected length:", expectedValue.length);
903
+ console.log("Actual length:", actualValue.length);
904
+ }
905
+ if (actualValue !== expectedValue) {
906
+ if (debugMode2) {
907
+ console.log("execCommand changed the value but not as expected");
908
+ console.log("Expected:", JSON.stringify(expectedValue.slice(0, 100)));
909
+ console.log("Actual:", JSON.stringify(actualValue.slice(0, 100)));
910
+ }
911
+ }
912
+ }
913
+ if (!canInsertText) {
914
+ if (debugMode2)
915
+ console.log("Using manual insertion");
916
+ if (textarea.value === originalValue) {
917
+ if (debugMode2)
918
+ console.log("Value unchanged, doing manual replacement");
919
+ try {
920
+ document.execCommand("ms-beginUndoUnit");
921
+ } catch (e) {
922
+ }
923
+ textarea.value = before + text + after;
924
+ try {
925
+ document.execCommand("ms-endUndoUnit");
926
+ } catch (e) {
927
+ }
928
+ textarea.dispatchEvent(new CustomEvent("input", { bubbles: true, cancelable: true }));
929
+ } else {
930
+ if (debugMode2)
931
+ console.log("Value was changed by execCommand, skipping manual insertion");
932
+ }
933
+ }
934
+ if (debugMode2)
935
+ console.log("Setting selection range:", selectionStart, selectionEnd);
936
+ if (selectionStart != null && selectionEnd != null) {
937
+ textarea.setSelectionRange(selectionStart, selectionEnd);
938
+ } else {
939
+ textarea.setSelectionRange(originalSelectionStart, textarea.selectionEnd);
940
+ }
941
+ if (debugMode2) {
942
+ console.log("Final value length:", textarea.value.length);
943
+ console.groupEnd();
944
+ }
945
+ }
946
+ function isMultipleLines(string) {
947
+ return string.trim().split("\n").length > 1;
948
+ }
949
+ function wordSelectionStart(text, i) {
950
+ let index = i;
951
+ while (text[index] && text[index - 1] != null && !text[index - 1].match(/\s/)) {
952
+ index--;
953
+ }
954
+ return index;
955
+ }
956
+ function wordSelectionEnd(text, i, multiline) {
957
+ let index = i;
958
+ const breakpoint = multiline ? /\n/ : /\s/;
959
+ while (text[index] && !text[index].match(breakpoint)) {
960
+ index++;
961
+ }
962
+ return index;
963
+ }
964
+ function expandSelectionToLine(textarea) {
965
+ const lines = textarea.value.split("\n");
966
+ let counter = 0;
967
+ for (let index = 0; index < lines.length; index++) {
968
+ const lineLength = lines[index].length + 1;
969
+ if (textarea.selectionStart >= counter && textarea.selectionStart < counter + lineLength) {
970
+ textarea.selectionStart = counter;
971
+ }
972
+ if (textarea.selectionEnd >= counter && textarea.selectionEnd < counter + lineLength) {
973
+ if (index === lines.length - 1) {
974
+ textarea.selectionEnd = Math.min(counter + lines[index].length, textarea.value.length);
975
+ } else {
976
+ textarea.selectionEnd = counter + lineLength - 1;
977
+ }
978
+ }
979
+ counter += lineLength;
980
+ }
981
+ }
982
+ function expandSelectedText(textarea, prefixToUse, suffixToUse, multiline = false) {
983
+ if (textarea.selectionStart === textarea.selectionEnd) {
984
+ textarea.selectionStart = wordSelectionStart(textarea.value, textarea.selectionStart);
985
+ textarea.selectionEnd = wordSelectionEnd(textarea.value, textarea.selectionEnd, multiline);
986
+ } else {
987
+ const expandedSelectionStart = textarea.selectionStart - prefixToUse.length;
988
+ const expandedSelectionEnd = textarea.selectionEnd + suffixToUse.length;
989
+ const beginsWithPrefix = textarea.value.slice(expandedSelectionStart, textarea.selectionStart) === prefixToUse;
990
+ const endsWithSuffix = textarea.value.slice(textarea.selectionEnd, expandedSelectionEnd) === suffixToUse;
991
+ if (beginsWithPrefix && endsWithSuffix) {
992
+ textarea.selectionStart = expandedSelectionStart;
993
+ textarea.selectionEnd = expandedSelectionEnd;
994
+ }
995
+ }
996
+ return textarea.value.slice(textarea.selectionStart, textarea.selectionEnd);
997
+ }
998
+ function newlinesToSurroundSelectedText(textarea) {
999
+ const beforeSelection = textarea.value.slice(0, textarea.selectionStart);
1000
+ const afterSelection = textarea.value.slice(textarea.selectionEnd);
1001
+ const breaksBefore = beforeSelection.match(/\n*$/);
1002
+ const breaksAfter = afterSelection.match(/^\n*/);
1003
+ const newlinesBeforeSelection = breaksBefore ? breaksBefore[0].length : 0;
1004
+ const newlinesAfterSelection = breaksAfter ? breaksAfter[0].length : 0;
1005
+ let newlinesToAppend = "";
1006
+ let newlinesToPrepend = "";
1007
+ if (beforeSelection.match(/\S/) && newlinesBeforeSelection < 2) {
1008
+ newlinesToAppend = "\n".repeat(2 - newlinesBeforeSelection);
1009
+ }
1010
+ if (afterSelection.match(/\S/) && newlinesAfterSelection < 2) {
1011
+ newlinesToPrepend = "\n".repeat(2 - newlinesAfterSelection);
1012
+ }
1013
+ return { newlinesToAppend, newlinesToPrepend };
1014
+ }
1015
+ function applyLineOperation(textarea, operation, options = {}) {
1016
+ const originalStart = textarea.selectionStart;
1017
+ const originalEnd = textarea.selectionEnd;
1018
+ const noInitialSelection = originalStart === originalEnd;
1019
+ const value = textarea.value;
1020
+ let lineStart = originalStart;
1021
+ while (lineStart > 0 && value[lineStart - 1] !== "\n") {
1022
+ lineStart--;
1023
+ }
1024
+ if (noInitialSelection) {
1025
+ let lineEnd = originalStart;
1026
+ while (lineEnd < value.length && value[lineEnd] !== "\n") {
1027
+ lineEnd++;
1028
+ }
1029
+ textarea.selectionStart = lineStart;
1030
+ textarea.selectionEnd = lineEnd;
1031
+ } else {
1032
+ expandSelectionToLine(textarea);
1033
+ }
1034
+ const result = operation(textarea);
1035
+ if (options.adjustSelection) {
1036
+ const selectedText = textarea.value.slice(textarea.selectionStart, textarea.selectionEnd);
1037
+ const isRemoving = selectedText.startsWith(options.prefix);
1038
+ const adjusted = options.adjustSelection(isRemoving, originalStart, originalEnd, lineStart);
1039
+ result.selectionStart = adjusted.start;
1040
+ result.selectionEnd = adjusted.end;
1041
+ } else if (options.prefix) {
1042
+ const selectedText = textarea.value.slice(textarea.selectionStart, textarea.selectionEnd);
1043
+ const isRemoving = selectedText.startsWith(options.prefix);
1044
+ if (noInitialSelection) {
1045
+ if (isRemoving) {
1046
+ result.selectionStart = Math.max(originalStart - options.prefix.length, lineStart);
1047
+ result.selectionEnd = result.selectionStart;
1048
+ } else {
1049
+ result.selectionStart = originalStart + options.prefix.length;
1050
+ result.selectionEnd = result.selectionStart;
1051
+ }
1052
+ } else {
1053
+ if (isRemoving) {
1054
+ result.selectionStart = Math.max(originalStart - options.prefix.length, lineStart);
1055
+ result.selectionEnd = Math.max(originalEnd - options.prefix.length, lineStart);
1056
+ } else {
1057
+ result.selectionStart = originalStart + options.prefix.length;
1058
+ result.selectionEnd = originalEnd + options.prefix.length;
1059
+ }
1060
+ }
1061
+ }
1062
+ return result;
1063
+ }
1064
+ function blockStyle(textarea, style) {
1065
+ let newlinesToAppend;
1066
+ let newlinesToPrepend;
1067
+ const { prefix, suffix, blockPrefix, blockSuffix, replaceNext, prefixSpace, scanFor, surroundWithNewlines, trimFirst } = style;
1068
+ const originalSelectionStart = textarea.selectionStart;
1069
+ const originalSelectionEnd = textarea.selectionEnd;
1070
+ let selectedText = textarea.value.slice(textarea.selectionStart, textarea.selectionEnd);
1071
+ let prefixToUse = isMultipleLines(selectedText) && blockPrefix && blockPrefix.length > 0 ? `${blockPrefix}
1072
+ ` : prefix;
1073
+ let suffixToUse = isMultipleLines(selectedText) && blockSuffix && blockSuffix.length > 0 ? `
1074
+ ${blockSuffix}` : suffix;
1075
+ if (prefixSpace) {
1076
+ const beforeSelection = textarea.value[textarea.selectionStart - 1];
1077
+ if (textarea.selectionStart !== 0 && beforeSelection != null && !beforeSelection.match(/\s/)) {
1078
+ prefixToUse = ` ${prefixToUse}`;
1079
+ }
1080
+ }
1081
+ selectedText = expandSelectedText(textarea, prefixToUse, suffixToUse, style.multiline);
1082
+ let selectionStart = textarea.selectionStart;
1083
+ let selectionEnd = textarea.selectionEnd;
1084
+ const hasReplaceNext = replaceNext && replaceNext.length > 0 && suffixToUse.indexOf(replaceNext) > -1 && selectedText.length > 0;
1085
+ if (surroundWithNewlines) {
1086
+ const ref = newlinesToSurroundSelectedText(textarea);
1087
+ newlinesToAppend = ref.newlinesToAppend;
1088
+ newlinesToPrepend = ref.newlinesToPrepend;
1089
+ prefixToUse = newlinesToAppend + prefix;
1090
+ suffixToUse += newlinesToPrepend;
1091
+ }
1092
+ if (selectedText.startsWith(prefixToUse) && selectedText.endsWith(suffixToUse)) {
1093
+ const replacementText = selectedText.slice(prefixToUse.length, selectedText.length - suffixToUse.length);
1094
+ if (originalSelectionStart === originalSelectionEnd) {
1095
+ let position = originalSelectionStart - prefixToUse.length;
1096
+ position = Math.max(position, selectionStart);
1097
+ position = Math.min(position, selectionStart + replacementText.length);
1098
+ selectionStart = selectionEnd = position;
1099
+ } else {
1100
+ selectionEnd = selectionStart + replacementText.length;
1101
+ }
1102
+ return { text: replacementText, selectionStart, selectionEnd };
1103
+ } else if (!hasReplaceNext) {
1104
+ let replacementText = prefixToUse + selectedText + suffixToUse;
1105
+ selectionStart = originalSelectionStart + prefixToUse.length;
1106
+ selectionEnd = originalSelectionEnd + prefixToUse.length;
1107
+ const whitespaceEdges = selectedText.match(/^\s*|\s*$/g);
1108
+ if (trimFirst && whitespaceEdges) {
1109
+ const leadingWhitespace = whitespaceEdges[0] || "";
1110
+ const trailingWhitespace = whitespaceEdges[1] || "";
1111
+ replacementText = leadingWhitespace + prefixToUse + selectedText.trim() + suffixToUse + trailingWhitespace;
1112
+ selectionStart += leadingWhitespace.length;
1113
+ selectionEnd -= trailingWhitespace.length;
1114
+ }
1115
+ return { text: replacementText, selectionStart, selectionEnd };
1116
+ } else if (scanFor && scanFor.length > 0 && selectedText.match(scanFor)) {
1117
+ suffixToUse = suffixToUse.replace(replaceNext, selectedText);
1118
+ const replacementText = prefixToUse + suffixToUse;
1119
+ selectionStart = selectionEnd = selectionStart + prefixToUse.length;
1120
+ return { text: replacementText, selectionStart, selectionEnd };
1121
+ } else {
1122
+ const replacementText = prefixToUse + selectedText + suffixToUse;
1123
+ selectionStart = selectionStart + prefixToUse.length + selectedText.length + suffixToUse.indexOf(replaceNext);
1124
+ selectionEnd = selectionStart + replaceNext.length;
1125
+ return { text: replacementText, selectionStart, selectionEnd };
1126
+ }
1127
+ }
1128
+ function multilineStyle(textarea, style) {
1129
+ const { prefix, suffix, surroundWithNewlines } = style;
1130
+ let text = textarea.value.slice(textarea.selectionStart, textarea.selectionEnd);
1131
+ let selectionStart = textarea.selectionStart;
1132
+ let selectionEnd = textarea.selectionEnd;
1133
+ const lines = text.split("\n");
1134
+ const undoStyle = lines.every((line) => line.startsWith(prefix) && (!suffix || line.endsWith(suffix)));
1135
+ if (undoStyle) {
1136
+ text = lines.map((line) => {
1137
+ let result = line.slice(prefix.length);
1138
+ if (suffix) {
1139
+ result = result.slice(0, result.length - suffix.length);
1140
+ }
1141
+ return result;
1142
+ }).join("\n");
1143
+ selectionEnd = selectionStart + text.length;
1144
+ } else {
1145
+ text = lines.map((line) => prefix + line + (suffix || "")).join("\n");
1146
+ if (surroundWithNewlines) {
1147
+ const { newlinesToAppend, newlinesToPrepend } = newlinesToSurroundSelectedText(textarea);
1148
+ selectionStart += newlinesToAppend.length;
1149
+ selectionEnd = selectionStart + text.length;
1150
+ text = newlinesToAppend + text + newlinesToPrepend;
1151
+ }
1152
+ }
1153
+ return { text, selectionStart, selectionEnd };
1154
+ }
1155
+ function undoOrderedListStyle(text) {
1156
+ const lines = text.split("\n");
1157
+ const orderedListRegex = /^\d+\.\s+/;
1158
+ const shouldUndoOrderedList = lines.every((line) => orderedListRegex.test(line));
1159
+ let result = lines;
1160
+ if (shouldUndoOrderedList) {
1161
+ result = lines.map((line) => line.replace(orderedListRegex, ""));
1162
+ }
1163
+ return {
1164
+ text: result.join("\n"),
1165
+ processed: shouldUndoOrderedList
1166
+ };
1167
+ }
1168
+ function undoUnorderedListStyle(text) {
1169
+ const lines = text.split("\n");
1170
+ const unorderedListPrefix = "- ";
1171
+ const shouldUndoUnorderedList = lines.every((line) => line.startsWith(unorderedListPrefix));
1172
+ let result = lines;
1173
+ if (shouldUndoUnorderedList) {
1174
+ result = lines.map((line) => line.slice(unorderedListPrefix.length));
1175
+ }
1176
+ return {
1177
+ text: result.join("\n"),
1178
+ processed: shouldUndoUnorderedList
1179
+ };
1180
+ }
1181
+ function makePrefix(index, unorderedList) {
1182
+ if (unorderedList) {
1183
+ return "- ";
1184
+ } else {
1185
+ return `${index + 1}. `;
1186
+ }
1187
+ }
1188
+ function clearExistingListStyle(style, selectedText) {
1189
+ let undoResult;
1190
+ let undoResultOppositeList;
1191
+ let pristineText;
1192
+ if (style.orderedList) {
1193
+ undoResult = undoOrderedListStyle(selectedText);
1194
+ undoResultOppositeList = undoUnorderedListStyle(undoResult.text);
1195
+ pristineText = undoResultOppositeList.text;
1196
+ } else {
1197
+ undoResult = undoUnorderedListStyle(selectedText);
1198
+ undoResultOppositeList = undoOrderedListStyle(undoResult.text);
1199
+ pristineText = undoResultOppositeList.text;
1200
+ }
1201
+ return [undoResult, undoResultOppositeList, pristineText];
1202
+ }
1203
+ function listStyle(textarea, style) {
1204
+ const noInitialSelection = textarea.selectionStart === textarea.selectionEnd;
1205
+ let selectionStart = textarea.selectionStart;
1206
+ let selectionEnd = textarea.selectionEnd;
1207
+ expandSelectionToLine(textarea);
1208
+ const selectedText = textarea.value.slice(textarea.selectionStart, textarea.selectionEnd);
1209
+ const [undoResult, undoResultOppositeList, pristineText] = clearExistingListStyle(style, selectedText);
1210
+ const prefixedLines = pristineText.split("\n").map((value, index) => {
1211
+ return `${makePrefix(index, style.unorderedList)}${value}`;
1212
+ });
1213
+ const totalPrefixLength = prefixedLines.reduce((previousValue, _currentValue, currentIndex) => {
1214
+ return previousValue + makePrefix(currentIndex, style.unorderedList).length;
1215
+ }, 0);
1216
+ const totalPrefixLengthOppositeList = prefixedLines.reduce((previousValue, _currentValue, currentIndex) => {
1217
+ return previousValue + makePrefix(currentIndex, !style.unorderedList).length;
1218
+ }, 0);
1219
+ if (undoResult.processed) {
1220
+ if (noInitialSelection) {
1221
+ selectionStart = Math.max(selectionStart - makePrefix(0, style.unorderedList).length, 0);
1222
+ selectionEnd = selectionStart;
1223
+ } else {
1224
+ selectionStart = textarea.selectionStart;
1225
+ selectionEnd = textarea.selectionEnd - totalPrefixLength;
1226
+ }
1227
+ return { text: pristineText, selectionStart, selectionEnd };
1228
+ }
1229
+ const { newlinesToAppend, newlinesToPrepend } = newlinesToSurroundSelectedText(textarea);
1230
+ const text = newlinesToAppend + prefixedLines.join("\n") + newlinesToPrepend;
1231
+ if (noInitialSelection) {
1232
+ selectionStart = Math.max(selectionStart + makePrefix(0, style.unorderedList).length + newlinesToAppend.length, 0);
1233
+ selectionEnd = selectionStart;
1234
+ } else {
1235
+ if (undoResultOppositeList.processed) {
1236
+ selectionStart = Math.max(textarea.selectionStart + newlinesToAppend.length, 0);
1237
+ selectionEnd = textarea.selectionEnd + newlinesToAppend.length + totalPrefixLength - totalPrefixLengthOppositeList;
1238
+ } else {
1239
+ selectionStart = Math.max(textarea.selectionStart + newlinesToAppend.length, 0);
1240
+ selectionEnd = textarea.selectionEnd + newlinesToAppend.length + totalPrefixLength;
1241
+ }
1242
+ }
1243
+ return { text, selectionStart, selectionEnd };
1244
+ }
1245
+ function applyListStyle(textarea, style) {
1246
+ const result = applyLineOperation(
1247
+ textarea,
1248
+ (ta) => listStyle(ta, style),
1249
+ {
1250
+ // Custom selection adjustment for lists
1251
+ adjustSelection: (isRemoving, selStart, selEnd, lineStart) => {
1252
+ const currentLine = textarea.value.slice(lineStart, textarea.selectionEnd);
1253
+ const orderedListRegex = /^\d+\.\s+/;
1254
+ const unorderedListRegex = /^- /;
1255
+ const hasOrderedList = orderedListRegex.test(currentLine);
1256
+ const hasUnorderedList = unorderedListRegex.test(currentLine);
1257
+ const isRemovingCurrent = style.orderedList && hasOrderedList || style.unorderedList && hasUnorderedList;
1258
+ if (selStart === selEnd) {
1259
+ if (isRemovingCurrent) {
1260
+ const prefixMatch = currentLine.match(style.orderedList ? orderedListRegex : unorderedListRegex);
1261
+ const prefixLength = prefixMatch ? prefixMatch[0].length : 0;
1262
+ return {
1263
+ start: Math.max(selStart - prefixLength, lineStart),
1264
+ end: Math.max(selStart - prefixLength, lineStart)
1265
+ };
1266
+ } else if (hasOrderedList || hasUnorderedList) {
1267
+ const oldPrefixMatch = currentLine.match(hasOrderedList ? orderedListRegex : unorderedListRegex);
1268
+ const oldPrefixLength = oldPrefixMatch ? oldPrefixMatch[0].length : 0;
1269
+ const newPrefixLength = style.unorderedList ? 2 : 3;
1270
+ const adjustment = newPrefixLength - oldPrefixLength;
1271
+ return {
1272
+ start: selStart + adjustment,
1273
+ end: selStart + adjustment
1274
+ };
1275
+ } else {
1276
+ const prefixLength = style.unorderedList ? 2 : 3;
1277
+ return {
1278
+ start: selStart + prefixLength,
1279
+ end: selStart + prefixLength
1280
+ };
1281
+ }
1282
+ } else {
1283
+ if (isRemovingCurrent) {
1284
+ const prefixMatch = currentLine.match(style.orderedList ? orderedListRegex : unorderedListRegex);
1285
+ const prefixLength = prefixMatch ? prefixMatch[0].length : 0;
1286
+ return {
1287
+ start: Math.max(selStart - prefixLength, lineStart),
1288
+ end: Math.max(selEnd - prefixLength, lineStart)
1289
+ };
1290
+ } else if (hasOrderedList || hasUnorderedList) {
1291
+ const oldPrefixMatch = currentLine.match(hasOrderedList ? orderedListRegex : unorderedListRegex);
1292
+ const oldPrefixLength = oldPrefixMatch ? oldPrefixMatch[0].length : 0;
1293
+ const newPrefixLength = style.unorderedList ? 2 : 3;
1294
+ const adjustment = newPrefixLength - oldPrefixLength;
1295
+ return {
1296
+ start: selStart + adjustment,
1297
+ end: selEnd + adjustment
1298
+ };
1299
+ } else {
1300
+ const prefixLength = style.unorderedList ? 2 : 3;
1301
+ return {
1302
+ start: selStart + prefixLength,
1303
+ end: selEnd + prefixLength
1304
+ };
1305
+ }
1306
+ }
1307
+ }
1308
+ }
1309
+ );
1310
+ insertText(textarea, result);
1311
+ }
1312
+ function getActiveFormats(textarea) {
1313
+ if (!textarea)
1314
+ return [];
1315
+ const formats = [];
1316
+ const { selectionStart, selectionEnd, value } = textarea;
1317
+ const lines = value.split("\n");
1318
+ let lineStart = 0;
1319
+ let currentLine = "";
1320
+ for (const line of lines) {
1321
+ if (selectionStart >= lineStart && selectionStart <= lineStart + line.length) {
1322
+ currentLine = line;
1323
+ break;
1324
+ }
1325
+ lineStart += line.length + 1;
1326
+ }
1327
+ if (currentLine.startsWith("- ")) {
1328
+ if (currentLine.startsWith("- [ ] ") || currentLine.startsWith("- [x] ")) {
1329
+ formats.push("task-list");
1330
+ } else {
1331
+ formats.push("bullet-list");
1332
+ }
1333
+ }
1334
+ if (/^\d+\.\s/.test(currentLine)) {
1335
+ formats.push("numbered-list");
1336
+ }
1337
+ if (currentLine.startsWith("> ")) {
1338
+ formats.push("quote");
1339
+ }
1340
+ if (currentLine.startsWith("# "))
1341
+ formats.push("header");
1342
+ if (currentLine.startsWith("## "))
1343
+ formats.push("header-2");
1344
+ if (currentLine.startsWith("### "))
1345
+ formats.push("header-3");
1346
+ const lookBehind = Math.max(0, selectionStart - 10);
1347
+ const lookAhead = Math.min(value.length, selectionEnd + 10);
1348
+ const surrounding = value.slice(lookBehind, lookAhead);
1349
+ if (surrounding.includes("**")) {
1350
+ const beforeCursor = value.slice(Math.max(0, selectionStart - 100), selectionStart);
1351
+ const afterCursor = value.slice(selectionEnd, Math.min(value.length, selectionEnd + 100));
1352
+ const lastOpenBold = beforeCursor.lastIndexOf("**");
1353
+ const nextCloseBold = afterCursor.indexOf("**");
1354
+ if (lastOpenBold !== -1 && nextCloseBold !== -1) {
1355
+ formats.push("bold");
1356
+ }
1357
+ }
1358
+ if (surrounding.includes("_")) {
1359
+ const beforeCursor = value.slice(Math.max(0, selectionStart - 100), selectionStart);
1360
+ const afterCursor = value.slice(selectionEnd, Math.min(value.length, selectionEnd + 100));
1361
+ const lastOpenItalic = beforeCursor.lastIndexOf("_");
1362
+ const nextCloseItalic = afterCursor.indexOf("_");
1363
+ if (lastOpenItalic !== -1 && nextCloseItalic !== -1) {
1364
+ formats.push("italic");
1365
+ }
1366
+ }
1367
+ if (surrounding.includes("`")) {
1368
+ const beforeCursor = value.slice(Math.max(0, selectionStart - 100), selectionStart);
1369
+ const afterCursor = value.slice(selectionEnd, Math.min(value.length, selectionEnd + 100));
1370
+ if (beforeCursor.includes("`") && afterCursor.includes("`")) {
1371
+ formats.push("code");
1372
+ }
1373
+ }
1374
+ if (surrounding.includes("[") && surrounding.includes("]")) {
1375
+ const beforeCursor = value.slice(Math.max(0, selectionStart - 100), selectionStart);
1376
+ const afterCursor = value.slice(selectionEnd, Math.min(value.length, selectionEnd + 100));
1377
+ const lastOpenBracket = beforeCursor.lastIndexOf("[");
1378
+ const nextCloseBracket = afterCursor.indexOf("]");
1379
+ if (lastOpenBracket !== -1 && nextCloseBracket !== -1) {
1380
+ const afterBracket = value.slice(selectionEnd + nextCloseBracket + 1, selectionEnd + nextCloseBracket + 10);
1381
+ if (afterBracket.startsWith("(")) {
1382
+ formats.push("link");
1383
+ }
1384
+ }
1385
+ }
1386
+ return formats;
1387
+ }
1388
+ function toggleBold(textarea) {
1389
+ if (!textarea || textarea.disabled || textarea.readOnly)
1390
+ return;
1391
+ debugLog("toggleBold", "Starting");
1392
+ debugSelection(textarea, "Before");
1393
+ const style = mergeWithDefaults(FORMATS.bold);
1394
+ const result = blockStyle(textarea, style);
1395
+ debugResult(result);
1396
+ insertText(textarea, result);
1397
+ debugSelection(textarea, "After");
1398
+ }
1399
+ function toggleItalic(textarea) {
1400
+ if (!textarea || textarea.disabled || textarea.readOnly)
1401
+ return;
1402
+ const style = mergeWithDefaults(FORMATS.italic);
1403
+ const result = blockStyle(textarea, style);
1404
+ insertText(textarea, result);
1405
+ }
1406
+ function toggleCode(textarea) {
1407
+ if (!textarea || textarea.disabled || textarea.readOnly)
1408
+ return;
1409
+ const style = mergeWithDefaults(FORMATS.code);
1410
+ const result = blockStyle(textarea, style);
1411
+ insertText(textarea, result);
1412
+ }
1413
+ function insertLink(textarea, options = {}) {
1414
+ if (!textarea || textarea.disabled || textarea.readOnly)
1415
+ return;
1416
+ const selectedText = textarea.value.slice(textarea.selectionStart, textarea.selectionEnd);
1417
+ let style = mergeWithDefaults(FORMATS.link);
1418
+ const isURL = selectedText && selectedText.match(/^https?:\/\//);
1419
+ if (isURL && !options.url) {
1420
+ style.suffix = `](${selectedText})`;
1421
+ style.replaceNext = "";
1422
+ } else if (options.url) {
1423
+ style.suffix = `](${options.url})`;
1424
+ style.replaceNext = "";
1425
+ }
1426
+ if (options.text && !selectedText) {
1427
+ const pos = textarea.selectionStart;
1428
+ textarea.value = textarea.value.slice(0, pos) + options.text + textarea.value.slice(pos);
1429
+ textarea.selectionStart = pos;
1430
+ textarea.selectionEnd = pos + options.text.length;
1431
+ }
1432
+ const result = blockStyle(textarea, style);
1433
+ insertText(textarea, result);
1434
+ }
1435
+ function toggleBulletList(textarea) {
1436
+ if (!textarea || textarea.disabled || textarea.readOnly)
1437
+ return;
1438
+ const style = mergeWithDefaults(FORMATS.bulletList);
1439
+ applyListStyle(textarea, style);
1440
+ }
1441
+ function toggleNumberedList(textarea) {
1442
+ if (!textarea || textarea.disabled || textarea.readOnly)
1443
+ return;
1444
+ const style = mergeWithDefaults(FORMATS.numberedList);
1445
+ applyListStyle(textarea, style);
1446
+ }
1447
+ function toggleQuote(textarea) {
1448
+ if (!textarea || textarea.disabled || textarea.readOnly)
1449
+ return;
1450
+ debugLog("toggleQuote", "Starting");
1451
+ debugSelection(textarea, "Initial");
1452
+ const style = mergeWithDefaults(FORMATS.quote);
1453
+ const result = applyLineOperation(
1454
+ textarea,
1455
+ (ta) => multilineStyle(ta, style),
1456
+ { prefix: style.prefix }
1457
+ );
1458
+ debugResult(result);
1459
+ insertText(textarea, result);
1460
+ debugSelection(textarea, "Final");
1461
+ }
1462
+ function toggleTaskList(textarea) {
1463
+ if (!textarea || textarea.disabled || textarea.readOnly)
1464
+ return;
1465
+ const style = mergeWithDefaults(FORMATS.taskList);
1466
+ const result = applyLineOperation(
1467
+ textarea,
1468
+ (ta) => multilineStyle(ta, style),
1469
+ { prefix: style.prefix }
1470
+ );
1471
+ insertText(textarea, result);
1472
+ }
1473
+ function insertHeader(textarea, level = 1, toggle = false) {
1474
+ if (!textarea || textarea.disabled || textarea.readOnly)
1475
+ return;
1476
+ if (level < 1 || level > 6)
1477
+ level = 1;
1478
+ debugLog("insertHeader", `============ START ============`);
1479
+ debugLog("insertHeader", `Level: ${level}, Toggle: ${toggle}`);
1480
+ debugLog("insertHeader", `Initial cursor: ${textarea.selectionStart}-${textarea.selectionEnd}`);
1481
+ const headerKey = `header${level === 1 ? "1" : level}`;
1482
+ const style = mergeWithDefaults(FORMATS[headerKey] || FORMATS.header1);
1483
+ debugLog("insertHeader", `Style prefix: "${style.prefix}"`);
1484
+ const value = textarea.value;
1485
+ const originalStart = textarea.selectionStart;
1486
+ const originalEnd = textarea.selectionEnd;
1487
+ let lineStart = originalStart;
1488
+ while (lineStart > 0 && value[lineStart - 1] !== "\n") {
1489
+ lineStart--;
1490
+ }
1491
+ let lineEnd = originalEnd;
1492
+ while (lineEnd < value.length && value[lineEnd] !== "\n") {
1493
+ lineEnd++;
1494
+ }
1495
+ const currentLineContent = value.slice(lineStart, lineEnd);
1496
+ debugLog("insertHeader", `Current line (before): "${currentLineContent}"`);
1497
+ const existingHeaderMatch = currentLineContent.match(/^(#{1,6})\s*/);
1498
+ const existingLevel = existingHeaderMatch ? existingHeaderMatch[1].length : 0;
1499
+ const existingPrefixLength = existingHeaderMatch ? existingHeaderMatch[0].length : 0;
1500
+ debugLog("insertHeader", `Existing header check:`);
1501
+ debugLog("insertHeader", ` - Match: ${existingHeaderMatch ? `"${existingHeaderMatch[0]}"` : "none"}`);
1502
+ debugLog("insertHeader", ` - Existing level: ${existingLevel}`);
1503
+ debugLog("insertHeader", ` - Existing prefix length: ${existingPrefixLength}`);
1504
+ debugLog("insertHeader", ` - Target level: ${level}`);
1505
+ const shouldToggleOff = toggle && existingLevel === level;
1506
+ debugLog("insertHeader", `Should toggle OFF: ${shouldToggleOff} (toggle=${toggle}, existingLevel=${existingLevel}, level=${level})`);
1507
+ const result = applyLineOperation(
1508
+ textarea,
1509
+ (ta) => {
1510
+ const currentLine = ta.value.slice(ta.selectionStart, ta.selectionEnd);
1511
+ debugLog("insertHeader", `Line in operation: "${currentLine}"`);
1512
+ const cleanedLine = currentLine.replace(/^#{1,6}\s*/, "");
1513
+ debugLog("insertHeader", `Cleaned line: "${cleanedLine}"`);
1514
+ let newLine;
1515
+ if (shouldToggleOff) {
1516
+ debugLog("insertHeader", "ACTION: Toggling OFF - removing header");
1517
+ newLine = cleanedLine;
1518
+ } else if (existingLevel > 0) {
1519
+ debugLog("insertHeader", `ACTION: Replacing H${existingLevel} with H${level}`);
1520
+ newLine = style.prefix + cleanedLine;
1521
+ } else {
1522
+ debugLog("insertHeader", "ACTION: Adding new header");
1523
+ newLine = style.prefix + cleanedLine;
1524
+ }
1525
+ debugLog("insertHeader", `New line: "${newLine}"`);
1526
+ return {
1527
+ text: newLine,
1528
+ selectionStart: ta.selectionStart,
1529
+ selectionEnd: ta.selectionEnd
1530
+ };
1531
+ },
1532
+ {
1533
+ prefix: style.prefix,
1534
+ // Custom selection adjustment for headers
1535
+ adjustSelection: (isRemoving, selStart, selEnd, lineStartPos) => {
1536
+ debugLog("insertHeader", `Adjusting selection:`);
1537
+ debugLog("insertHeader", ` - isRemoving param: ${isRemoving}`);
1538
+ debugLog("insertHeader", ` - shouldToggleOff: ${shouldToggleOff}`);
1539
+ debugLog("insertHeader", ` - selStart: ${selStart}, selEnd: ${selEnd}`);
1540
+ debugLog("insertHeader", ` - lineStartPos: ${lineStartPos}`);
1541
+ if (shouldToggleOff) {
1542
+ const adjustment = Math.max(selStart - existingPrefixLength, lineStartPos);
1543
+ debugLog("insertHeader", ` - Removing header, adjusting by -${existingPrefixLength}`);
1544
+ return {
1545
+ start: adjustment,
1546
+ end: selStart === selEnd ? adjustment : Math.max(selEnd - existingPrefixLength, lineStartPos)
1547
+ };
1548
+ } else if (existingPrefixLength > 0) {
1549
+ const prefixDiff = style.prefix.length - existingPrefixLength;
1550
+ debugLog("insertHeader", ` - Replacing header, adjusting by ${prefixDiff}`);
1551
+ return {
1552
+ start: selStart + prefixDiff,
1553
+ end: selEnd + prefixDiff
1554
+ };
1555
+ } else {
1556
+ debugLog("insertHeader", ` - Adding header, adjusting by +${style.prefix.length}`);
1557
+ return {
1558
+ start: selStart + style.prefix.length,
1559
+ end: selEnd + style.prefix.length
1560
+ };
1561
+ }
1562
+ }
1563
+ }
1564
+ );
1565
+ debugLog("insertHeader", `Final result: text="${result.text}", cursor=${result.selectionStart}-${result.selectionEnd}`);
1566
+ debugLog("insertHeader", `============ END ============`);
1567
+ insertText(textarea, result);
1568
+ }
1569
+ function toggleH1(textarea) {
1570
+ insertHeader(textarea, 1, true);
1571
+ }
1572
+ function toggleH2(textarea) {
1573
+ insertHeader(textarea, 2, true);
1574
+ }
1575
+ function toggleH3(textarea) {
1576
+ insertHeader(textarea, 3, true);
1577
+ }
1578
+ function getActiveFormats2(textarea) {
1579
+ return getActiveFormats(textarea);
1580
+ }
1581
+
1582
+ // src/shortcuts.js
1583
+ var ShortcutsManager = class {
1584
+ constructor(editor) {
1585
+ this.editor = editor;
1586
+ this.textarea = editor.textarea;
1587
+ }
1588
+ /**
1589
+ * Handle keydown events - called by OverType
1590
+ * @param {KeyboardEvent} event - The keyboard event
1591
+ * @returns {boolean} Whether the event was handled
1592
+ */
1593
+ handleKeydown(event) {
1594
+ const isMac = navigator.platform.toLowerCase().includes("mac");
1595
+ const modKey = isMac ? event.metaKey : event.ctrlKey;
1596
+ if (!modKey)
1597
+ return false;
1598
+ let action = null;
1599
+ switch (event.key.toLowerCase()) {
1600
+ case "b":
1601
+ if (!event.shiftKey) {
1602
+ action = "toggleBold";
1603
+ }
1604
+ break;
1605
+ case "i":
1606
+ if (!event.shiftKey) {
1607
+ action = "toggleItalic";
1608
+ }
1609
+ break;
1610
+ case "k":
1611
+ if (!event.shiftKey) {
1612
+ action = "insertLink";
1613
+ }
1614
+ break;
1615
+ case "7":
1616
+ if (event.shiftKey) {
1617
+ action = "toggleNumberedList";
1618
+ }
1619
+ break;
1620
+ case "8":
1621
+ if (event.shiftKey) {
1622
+ action = "toggleBulletList";
1623
+ }
1624
+ break;
1625
+ }
1626
+ if (action) {
1627
+ event.preventDefault();
1628
+ if (this.editor.toolbar) {
1629
+ this.editor.toolbar.handleAction(action);
1630
+ } else {
1631
+ this.handleAction(action);
1632
+ }
1633
+ return true;
1634
+ }
1635
+ return false;
1636
+ }
1637
+ /**
1638
+ * Handle action - fallback when no toolbar exists
1639
+ * This duplicates toolbar.handleAction for consistency
1640
+ */
1641
+ async handleAction(action) {
1642
+ const textarea = this.textarea;
1643
+ if (!textarea)
1644
+ return;
1645
+ textarea.focus();
1646
+ try {
1647
+ switch (action) {
1648
+ case "toggleBold":
1649
+ toggleBold(textarea);
1650
+ break;
1651
+ case "toggleItalic":
1652
+ toggleItalic(textarea);
1653
+ break;
1654
+ case "insertLink":
1655
+ insertLink(textarea);
1656
+ break;
1657
+ case "toggleBulletList":
1658
+ toggleBulletList(textarea);
1659
+ break;
1660
+ case "toggleNumberedList":
1661
+ toggleNumberedList(textarea);
1662
+ break;
1663
+ }
1664
+ textarea.dispatchEvent(new Event("input", { bubbles: true }));
1665
+ } catch (error) {
1666
+ console.error("Error in markdown action:", error);
1667
+ }
1668
+ }
1669
+ /**
1670
+ * Cleanup
1671
+ */
1672
+ destroy() {
1673
+ }
1674
+ };
1675
+
1676
+ // src/themes.js
1677
+ var solar = {
1678
+ name: "solar",
1679
+ colors: {
1680
+ bgPrimary: "#faf0ca",
1681
+ // Lemon Chiffon - main background
1682
+ bgSecondary: "#ffffff",
1683
+ // White - editor background
1684
+ text: "#0d3b66",
1685
+ // Yale Blue - main text
1686
+ h1: "#f95738",
1687
+ // Tomato - h1 headers
1688
+ h2: "#ee964b",
1689
+ // Sandy Brown - h2 headers
1690
+ h3: "#3d8a51",
1691
+ // Forest green - h3 headers
1692
+ strong: "#ee964b",
1693
+ // Sandy Brown - bold text
1694
+ em: "#f95738",
1695
+ // Tomato - italic text
1696
+ link: "#0d3b66",
1697
+ // Yale Blue - links
1698
+ code: "#0d3b66",
1699
+ // Yale Blue - inline code
1700
+ codeBg: "rgba(244, 211, 94, 0.4)",
1701
+ // Naples Yellow with transparency
1702
+ blockquote: "#5a7a9b",
1703
+ // Muted blue - blockquotes
1704
+ hr: "#5a7a9b",
1705
+ // Muted blue - horizontal rules
1706
+ syntaxMarker: "rgba(13, 59, 102, 0.52)",
1707
+ // Yale Blue with transparency
1708
+ cursor: "#f95738",
1709
+ // Tomato - cursor
1710
+ selection: "rgba(244, 211, 94, 0.4)",
1711
+ // Naples Yellow with transparency
1712
+ listMarker: "#ee964b",
1713
+ // Sandy Brown - list markers
1714
+ // Toolbar colors
1715
+ toolbarBg: "#ffffff",
1716
+ // White - toolbar background
1717
+ toolbarBorder: "rgba(13, 59, 102, 0.15)",
1718
+ // Yale Blue border
1719
+ toolbarIcon: "#0d3b66",
1720
+ // Yale Blue - icon color
1721
+ toolbarHover: "#f5f5f5",
1722
+ // Light gray - hover background
1723
+ toolbarActive: "#faf0ca"
1724
+ // Lemon Chiffon - active button background
1725
+ }
1726
+ };
1727
+ var cave = {
1728
+ name: "cave",
1729
+ colors: {
1730
+ bgPrimary: "#141E26",
1731
+ // Deep ocean - main background
1732
+ bgSecondary: "#1D2D3E",
1733
+ // Darker charcoal - editor background
1734
+ text: "#c5dde8",
1735
+ // Light blue-gray - main text
1736
+ h1: "#d4a5ff",
1737
+ // Rich lavender - h1 headers
1738
+ h2: "#f6ae2d",
1739
+ // Hunyadi Yellow - h2 headers
1740
+ h3: "#9fcfec",
1741
+ // Brighter blue - h3 headers
1742
+ strong: "#f6ae2d",
1743
+ // Hunyadi Yellow - bold text
1744
+ em: "#9fcfec",
1745
+ // Brighter blue - italic text
1746
+ link: "#9fcfec",
1747
+ // Brighter blue - links
1748
+ code: "#c5dde8",
1749
+ // Light blue-gray - inline code
1750
+ codeBg: "#1a232b",
1751
+ // Very dark blue - code background
1752
+ blockquote: "#9fcfec",
1753
+ // Brighter blue - same as italic
1754
+ hr: "#c5dde8",
1755
+ // Light blue-gray - horizontal rules
1756
+ syntaxMarker: "rgba(159, 207, 236, 0.73)",
1757
+ // Brighter blue semi-transparent
1758
+ cursor: "#f26419",
1759
+ // Orange Pantone - cursor
1760
+ selection: "rgba(51, 101, 138, 0.4)",
1761
+ // Lapis Lazuli with transparency
1762
+ listMarker: "#f6ae2d",
1763
+ // Hunyadi Yellow - list markers
1764
+ // Toolbar colors for dark theme
1765
+ toolbarBg: "#1D2D3E",
1766
+ // Darker charcoal - toolbar background
1767
+ toolbarBorder: "rgba(197, 221, 232, 0.1)",
1768
+ // Light blue-gray border
1769
+ toolbarIcon: "#c5dde8",
1770
+ // Light blue-gray - icon color
1771
+ toolbarHover: "#243546",
1772
+ // Slightly lighter charcoal - hover background
1773
+ toolbarActive: "#2a3f52"
1774
+ // Even lighter - active button background
1775
+ }
1776
+ };
1777
+ var themes = {
1778
+ solar,
1779
+ cave,
1780
+ // Aliases for backward compatibility
1781
+ light: solar,
1782
+ dark: cave
1783
+ };
1784
+ function getTheme(theme) {
1785
+ if (typeof theme === "string") {
1786
+ const themeObj = themes[theme] || themes.solar;
1787
+ return { ...themeObj, name: theme };
1788
+ }
1789
+ return theme;
1790
+ }
1791
+ function themeToCSSVars(colors) {
1792
+ const vars = [];
1793
+ for (const [key, value] of Object.entries(colors)) {
1794
+ const varName = key.replace(/([A-Z])/g, "-$1").toLowerCase();
1795
+ vars.push(`--${varName}: ${value};`);
1796
+ }
1797
+ return vars.join("\n");
1798
+ }
1799
+ function mergeTheme(baseTheme, customColors = {}) {
1800
+ return {
1801
+ ...baseTheme,
1802
+ colors: {
1803
+ ...baseTheme.colors,
1804
+ ...customColors
1805
+ }
1806
+ };
1807
+ }
1808
+
1809
+ // src/styles.js
1810
+ function generateStyles(options = {}) {
1811
+ const {
1812
+ fontSize = "14px",
1813
+ lineHeight = 1.6,
1814
+ /* System-first, guaranteed monospaced; avoids Android 'ui-monospace' pitfalls */
1815
+ 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',
1816
+ padding = "20px",
1817
+ theme = null,
1818
+ mobile = {}
1819
+ } = options;
1820
+ const mobileStyles = Object.keys(mobile).length > 0 ? `
1821
+ @media (max-width: 640px) {
1822
+ .overtype-wrapper .overtype-input,
1823
+ .overtype-wrapper .overtype-preview {
1824
+ ${Object.entries(mobile).map(([prop, val]) => {
1825
+ const cssProp = prop.replace(/([A-Z])/g, "-$1").toLowerCase();
1826
+ return `${cssProp}: ${val} !important;`;
1827
+ }).join("\n ")}
1828
+ }
1829
+ }
1830
+ ` : "";
1831
+ const themeVars = theme && theme.colors ? themeToCSSVars(theme.colors) : "";
1832
+ return `
1833
+ /* OverType Editor Styles */
1834
+
1835
+ /* Middle-ground CSS Reset - Prevent parent styles from leaking in */
1836
+ .overtype-container * {
1837
+ /* Box model - these commonly leak */
1838
+ margin: 0 !important;
1839
+ padding: 0 !important;
1840
+ border: 0 !important;
1841
+
1842
+ /* Layout - these can break our layout */
1843
+ /* Don't reset position - it breaks dropdowns */
1844
+ float: none !important;
1845
+ clear: none !important;
1846
+
1847
+ /* Typography - only reset decorative aspects */
1848
+ text-decoration: none !important;
1849
+ text-transform: none !important;
1850
+ letter-spacing: normal !important;
1851
+
1852
+ /* Visual effects that can interfere */
1853
+ box-shadow: none !important;
1854
+ text-shadow: none !important;
1855
+
1856
+ /* Ensure box-sizing is consistent */
1857
+ box-sizing: border-box !important;
1858
+
1859
+ /* Keep inheritance for these */
1860
+ /* font-family, color, line-height, font-size - inherit */
1861
+ }
1862
+
1863
+ /* Container base styles after reset */
1864
+ .overtype-container {
1865
+ display: grid !important;
1866
+ grid-template-rows: auto 1fr auto !important;
1867
+ width: 100% !important;
1868
+ height: 100% !important;
1869
+ position: relative !important; /* Override reset - needed for absolute children */
1870
+ overflow: visible !important; /* Allow dropdown to overflow container */
1871
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important;
1872
+ text-align: left !important;
1873
+ ${themeVars ? `
1874
+ /* Theme Variables */
1875
+ ${themeVars}` : ""}
1876
+ }
1877
+
1878
+ /* Force left alignment for all elements in the editor */
1879
+ .overtype-container .overtype-wrapper * {
1880
+ text-align: left !important;
1881
+ }
1882
+
1883
+ /* Auto-resize mode styles */
1884
+ .overtype-container.overtype-auto-resize {
1885
+ height: auto !important;
1886
+ grid-template-rows: auto auto auto !important;
1887
+ }
1888
+
1889
+ .overtype-container.overtype-auto-resize .overtype-wrapper {
1890
+ height: auto !important;
1891
+ min-height: 60px !important;
1892
+ overflow: visible !important;
1893
+ }
1894
+
1895
+ .overtype-wrapper {
1896
+ position: relative !important; /* Override reset - needed for absolute children */
1897
+ width: 100% !important;
1898
+ height: 100% !important; /* Take full height of grid cell */
1899
+ min-height: 60px !important; /* Minimum usable height */
1900
+ overflow: hidden !important;
1901
+ background: var(--bg-secondary, #ffffff) !important;
1902
+ grid-row: 2 !important; /* Always second row in grid */
1903
+ z-index: 1; /* Below toolbar and dropdown */
1904
+ }
1905
+
1906
+ /* Critical alignment styles - must be identical for both layers */
1907
+ .overtype-wrapper .overtype-input,
1908
+ .overtype-wrapper .overtype-preview {
1909
+ /* Positioning - must be identical */
1910
+ position: absolute !important; /* Override reset - required for overlay */
1911
+ top: 0 !important;
1912
+ left: 0 !important;
1913
+ width: 100% !important;
1914
+ height: 100% !important;
1915
+
1916
+ /* Font properties - any difference breaks alignment */
1917
+ font-family: ${fontFamily} !important;
1918
+ font-variant-ligatures: none !important; /* keep metrics stable for code */
1919
+ font-size: var(--instance-font-size, ${fontSize}) !important;
1920
+ line-height: var(--instance-line-height, ${lineHeight}) !important;
1921
+ font-weight: normal !important;
1922
+ font-style: normal !important;
1923
+ font-variant: normal !important;
1924
+ font-stretch: normal !important;
1925
+ font-kerning: none !important;
1926
+ font-feature-settings: normal !important;
1927
+
1928
+ /* Box model - must match exactly */
1929
+ padding: var(--instance-padding, ${padding}) !important;
1930
+ margin: 0 !important;
1931
+ border: none !important;
1932
+ outline: none !important;
1933
+ box-sizing: border-box !important;
1934
+
1935
+ /* Text layout - critical for character positioning */
1936
+ white-space: pre-wrap !important;
1937
+ word-wrap: break-word !important;
1938
+ word-break: normal !important;
1939
+ overflow-wrap: break-word !important;
1940
+ tab-size: 2 !important;
1941
+ -moz-tab-size: 2 !important;
1942
+ text-align: left !important;
1943
+ text-indent: 0 !important;
1944
+ letter-spacing: normal !important;
1945
+ word-spacing: normal !important;
1946
+
1947
+ /* Text rendering */
1948
+ text-transform: none !important;
1949
+ text-rendering: auto !important;
1950
+ -webkit-font-smoothing: auto !important;
1951
+ -webkit-text-size-adjust: 100% !important;
1952
+
1953
+ /* Direction and writing */
1954
+ direction: ltr !important;
1955
+ writing-mode: horizontal-tb !important;
1956
+ unicode-bidi: normal !important;
1957
+ text-orientation: mixed !important;
1958
+
1959
+ /* Visual effects that could shift perception */
1960
+ text-shadow: none !important;
1961
+ filter: none !important;
1962
+ transform: none !important;
1963
+ zoom: 1 !important;
1964
+
1965
+ /* Vertical alignment */
1966
+ vertical-align: baseline !important;
1967
+
1968
+ /* Size constraints */
1969
+ min-width: 0 !important;
1970
+ min-height: 0 !important;
1971
+ max-width: none !important;
1972
+ max-height: none !important;
1973
+
1974
+ /* Overflow */
1975
+ overflow-y: auto !important;
1976
+ overflow-x: auto !important;
1977
+ /* overscroll-behavior removed to allow scroll-through to parent */
1978
+ scrollbar-width: auto !important;
1979
+ scrollbar-gutter: auto !important;
1980
+
1981
+ /* Animation/transition - disabled to prevent movement */
1982
+ animation: none !important;
1983
+ transition: none !important;
1984
+ }
1985
+
1986
+ /* Input layer styles */
1987
+ .overtype-wrapper .overtype-input {
1988
+ /* Layer positioning */
1989
+ z-index: 1 !important;
1990
+
1991
+ /* Text visibility */
1992
+ color: transparent !important;
1993
+ caret-color: var(--cursor, #f95738) !important;
1994
+ background-color: transparent !important;
1995
+
1996
+ /* Textarea-specific */
1997
+ resize: none !important;
1998
+ appearance: none !important;
1999
+ -webkit-appearance: none !important;
2000
+ -moz-appearance: none !important;
2001
+
2002
+ /* Prevent mobile zoom on focus */
2003
+ touch-action: manipulation !important;
2004
+
2005
+ /* Disable autofill and spellcheck */
2006
+ autocomplete: off !important;
2007
+ autocorrect: off !important;
2008
+ autocapitalize: off !important;
2009
+ spellcheck: false !important;
2010
+ }
2011
+
2012
+ .overtype-wrapper .overtype-input::selection {
2013
+ background-color: var(--selection, rgba(244, 211, 94, 0.4));
2014
+ }
2015
+
2016
+ /* Preview layer styles */
2017
+ .overtype-wrapper .overtype-preview {
2018
+ /* Layer positioning */
2019
+ z-index: 0 !important;
2020
+ pointer-events: none !important;
2021
+ color: var(--text, #0d3b66) !important;
2022
+ background-color: transparent !important;
2023
+
2024
+ /* Prevent text selection */
2025
+ user-select: none !important;
2026
+ -webkit-user-select: none !important;
2027
+ -moz-user-select: none !important;
2028
+ -ms-user-select: none !important;
2029
+ }
2030
+
2031
+ /* Defensive styles for preview child divs */
2032
+ .overtype-wrapper .overtype-preview div {
2033
+ /* Reset any inherited styles */
2034
+ margin: 0 !important;
2035
+ padding: 0 !important;
2036
+ border: none !important;
2037
+ text-align: left !important;
2038
+ text-indent: 0 !important;
2039
+ display: block !important;
2040
+ position: static !important;
2041
+ transform: none !important;
2042
+ min-height: 0 !important;
2043
+ max-height: none !important;
2044
+ line-height: inherit !important;
2045
+ font-size: inherit !important;
2046
+ font-family: inherit !important;
2047
+ }
2048
+
2049
+ /* Markdown element styling - NO SIZE CHANGES */
2050
+ .overtype-wrapper .overtype-preview .header {
2051
+ font-weight: bold !important;
2052
+ }
2053
+
2054
+ /* Header colors */
2055
+ .overtype-wrapper .overtype-preview .h1 {
2056
+ color: var(--h1, #f95738) !important;
2057
+ }
2058
+ .overtype-wrapper .overtype-preview .h2 {
2059
+ color: var(--h2, #ee964b) !important;
2060
+ }
2061
+ .overtype-wrapper .overtype-preview .h3 {
2062
+ color: var(--h3, #3d8a51) !important;
2063
+ }
2064
+
2065
+ /* Semantic headers - flatten in edit mode */
2066
+ .overtype-wrapper .overtype-preview h1,
2067
+ .overtype-wrapper .overtype-preview h2,
2068
+ .overtype-wrapper .overtype-preview h3 {
2069
+ font-size: inherit !important;
2070
+ font-weight: bold !important;
2071
+ margin: 0 !important;
2072
+ padding: 0 !important;
2073
+ display: inline !important;
2074
+ line-height: inherit !important;
2075
+ }
2076
+
2077
+ /* Header colors for semantic headers */
2078
+ .overtype-wrapper .overtype-preview h1 {
2079
+ color: var(--h1, #f95738) !important;
2080
+ }
2081
+ .overtype-wrapper .overtype-preview h2 {
2082
+ color: var(--h2, #ee964b) !important;
2083
+ }
2084
+ .overtype-wrapper .overtype-preview h3 {
2085
+ color: var(--h3, #3d8a51) !important;
2086
+ }
2087
+
2088
+ /* Lists - remove styling in edit mode */
2089
+ .overtype-wrapper .overtype-preview ul,
2090
+ .overtype-wrapper .overtype-preview ol {
2091
+ list-style: none !important;
2092
+ margin: 0 !important;
2093
+ padding: 0 !important;
2094
+ display: block !important; /* Lists need to be block for line breaks */
2095
+ }
2096
+
2097
+ .overtype-wrapper .overtype-preview li {
2098
+ display: block !important; /* Each item on its own line */
2099
+ margin: 0 !important;
2100
+ padding: 0 !important;
2101
+ /* Don't set list-style here - let ul/ol control it */
2102
+ }
2103
+
2104
+ /* Bold text */
2105
+ .overtype-wrapper .overtype-preview strong {
2106
+ color: var(--strong, #ee964b) !important;
2107
+ font-weight: bold !important;
2108
+ }
2109
+
2110
+ /* Italic text */
2111
+ .overtype-wrapper .overtype-preview em {
2112
+ color: var(--em, #f95738) !important;
2113
+ text-decoration-color: var(--em, #f95738) !important;
2114
+ text-decoration-thickness: 1px !important;
2115
+ font-style: italic !important;
2116
+ }
2117
+
2118
+ /* Strikethrough text */
2119
+ .overtype-wrapper .overtype-preview del {
2120
+ color: var(--del, #ee964b) !important;
2121
+ text-decoration: line-through !important;
2122
+ text-decoration-color: var(--del, #ee964b) !important;
2123
+ text-decoration-thickness: 1px !important;
2124
+ }
2125
+
2126
+ /* Inline code */
2127
+ .overtype-wrapper .overtype-preview code {
2128
+ background: var(--code-bg, rgba(244, 211, 94, 0.4)) !important;
2129
+ color: var(--code, #0d3b66) !important;
2130
+ padding: 0 !important;
2131
+ border-radius: 2px !important;
2132
+ font-family: inherit !important;
2133
+ font-size: inherit !important;
2134
+ line-height: inherit !important;
2135
+ font-weight: normal !important;
2136
+ }
2137
+
2138
+ /* Code blocks - consolidated pre blocks */
2139
+ .overtype-wrapper .overtype-preview pre {
2140
+ padding: 0 !important;
2141
+ margin: 0 !important;
2142
+ border-radius: 4px !important;
2143
+ overflow-x: auto !important;
2144
+ }
2145
+
2146
+ /* Code block styling in normal mode - yellow background */
2147
+ .overtype-wrapper .overtype-preview pre.code-block {
2148
+ background: var(--code-bg, rgba(244, 211, 94, 0.4)) !important;
2149
+ white-space: break-spaces !important; /* Prevent horizontal scrollbar that breaks alignment */
2150
+ }
2151
+
2152
+ /* Code inside pre blocks - remove background */
2153
+ .overtype-wrapper .overtype-preview pre code {
2154
+ background: transparent !important;
2155
+ color: var(--code, #0d3b66) !important;
2156
+ font-family: ${fontFamily} !important; /* Match textarea font exactly for alignment */
2157
+ }
2158
+
2159
+ /* Blockquotes */
2160
+ .overtype-wrapper .overtype-preview .blockquote {
2161
+ color: var(--blockquote, #5a7a9b) !important;
2162
+ padding: 0 !important;
2163
+ margin: 0 !important;
2164
+ border: none !important;
2165
+ }
2166
+
2167
+ /* Links */
2168
+ .overtype-wrapper .overtype-preview a {
2169
+ color: var(--link, #0d3b66) !important;
2170
+ text-decoration: underline !important;
2171
+ font-weight: normal !important;
2172
+ }
2173
+
2174
+ .overtype-wrapper .overtype-preview a:hover {
2175
+ text-decoration: underline !important;
2176
+ color: var(--link, #0d3b66) !important;
2177
+ }
2178
+
2179
+ /* Lists - no list styling */
2180
+ .overtype-wrapper .overtype-preview ul,
2181
+ .overtype-wrapper .overtype-preview ol {
2182
+ list-style: none !important;
2183
+ margin: 0 !important;
2184
+ padding: 0 !important;
2185
+ }
2186
+
2187
+
2188
+ /* Horizontal rules */
2189
+ .overtype-wrapper .overtype-preview hr {
2190
+ border: none !important;
2191
+ color: var(--hr, #5a7a9b) !important;
2192
+ margin: 0 !important;
2193
+ padding: 0 !important;
2194
+ }
2195
+
2196
+ .overtype-wrapper .overtype-preview .hr-marker {
2197
+ color: var(--hr, #5a7a9b) !important;
2198
+ opacity: 0.6 !important;
2199
+ }
2200
+
2201
+ /* Code fence markers - with background when not in code block */
2202
+ .overtype-wrapper .overtype-preview .code-fence {
2203
+ color: var(--code, #0d3b66) !important;
2204
+ background: var(--code-bg, rgba(244, 211, 94, 0.4)) !important;
2205
+ }
2206
+
2207
+ /* Code block lines - background for entire code block */
2208
+ .overtype-wrapper .overtype-preview .code-block-line {
2209
+ background: var(--code-bg, rgba(244, 211, 94, 0.4)) !important;
2210
+ }
2211
+
2212
+ /* Remove background from code fence when inside code block line */
2213
+ .overtype-wrapper .overtype-preview .code-block-line .code-fence {
2214
+ background: transparent !important;
2215
+ }
2216
+
2217
+ /* Raw markdown line */
2218
+ .overtype-wrapper .overtype-preview .raw-line {
2219
+ color: var(--raw-line, #5a7a9b) !important;
2220
+ font-style: normal !important;
2221
+ font-weight: normal !important;
2222
+ }
2223
+
2224
+ /* Syntax markers */
2225
+ .overtype-wrapper .overtype-preview .syntax-marker {
2226
+ color: var(--syntax-marker, rgba(13, 59, 102, 0.52)) !important;
2227
+ opacity: 0.7 !important;
2228
+ }
2229
+
2230
+ /* List markers */
2231
+ .overtype-wrapper .overtype-preview .list-marker {
2232
+ color: var(--list-marker, #ee964b) !important;
2233
+ }
2234
+
2235
+ /* Stats bar */
2236
+
2237
+ /* Stats bar - positioned by grid, not absolute */
2238
+ .overtype-stats {
2239
+ height: 40px !important;
2240
+ padding: 0 20px !important;
2241
+ background: #f8f9fa !important;
2242
+ border-top: 1px solid #e0e0e0 !important;
2243
+ display: flex !important;
2244
+ justify-content: space-between !important;
2245
+ align-items: center !important;
2246
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif !important;
2247
+ font-size: 0.85rem !important;
2248
+ color: #666 !important;
2249
+ grid-row: 3 !important; /* Always third row in grid */
2250
+ }
2251
+
2252
+ /* Dark theme stats bar */
2253
+ .overtype-container[data-theme="cave"] .overtype-stats {
2254
+ background: var(--bg-secondary, #1D2D3E) !important;
2255
+ border-top: 1px solid rgba(197, 221, 232, 0.1) !important;
2256
+ color: var(--text, #c5dde8) !important;
2257
+ }
2258
+
2259
+ .overtype-stats .overtype-stat {
2260
+ display: flex !important;
2261
+ align-items: center !important;
2262
+ gap: 5px !important;
2263
+ white-space: nowrap !important;
2264
+ }
2265
+
2266
+ .overtype-stats .live-dot {
2267
+ width: 8px !important;
2268
+ height: 8px !important;
2269
+ background: #4caf50 !important;
2270
+ border-radius: 50% !important;
2271
+ animation: overtype-pulse 2s infinite !important;
2272
+ }
2273
+
2274
+ @keyframes overtype-pulse {
2275
+ 0%, 100% { opacity: 1; transform: scale(1); }
2276
+ 50% { opacity: 0.6; transform: scale(1.2); }
2277
+ }
2278
+
2279
+
2280
+ /* Toolbar Styles */
2281
+ .overtype-toolbar {
2282
+ display: flex !important;
2283
+ align-items: center !important;
2284
+ gap: 4px !important;
2285
+ padding: 8px !important; /* Override reset */
2286
+ background: var(--toolbar-bg, var(--bg-primary, #f8f9fa)) !important; /* Override reset */
2287
+ overflow-x: auto !important; /* Allow horizontal scrolling */
2288
+ overflow-y: hidden !important; /* Hide vertical overflow */
2289
+ -webkit-overflow-scrolling: touch !important;
2290
+ flex-shrink: 0 !important;
2291
+ height: auto !important;
2292
+ grid-row: 1 !important; /* Always first row in grid */
2293
+ position: relative !important; /* Override reset */
2294
+ z-index: 100 !important; /* Ensure toolbar is above wrapper */
2295
+ scrollbar-width: thin; /* Thin scrollbar on Firefox */
2296
+ }
2297
+
2298
+ /* Thin scrollbar styling */
2299
+ .overtype-toolbar::-webkit-scrollbar {
2300
+ height: 4px;
2301
+ }
2302
+
2303
+ .overtype-toolbar::-webkit-scrollbar-track {
2304
+ background: transparent;
2305
+ }
2306
+
2307
+ .overtype-toolbar::-webkit-scrollbar-thumb {
2308
+ background: rgba(0, 0, 0, 0.2);
2309
+ border-radius: 2px;
2310
+ }
2311
+
2312
+ .overtype-toolbar-button {
2313
+ display: flex;
2314
+ align-items: center;
2315
+ justify-content: center;
2316
+ width: 32px;
2317
+ height: 32px;
2318
+ padding: 0;
2319
+ border: none;
2320
+ border-radius: 6px;
2321
+ background: transparent;
2322
+ color: var(--toolbar-icon, var(--text-secondary, #666));
2323
+ cursor: pointer;
2324
+ transition: all 0.2s ease;
2325
+ flex-shrink: 0;
2326
+ }
2327
+
2328
+ .overtype-toolbar-button svg {
2329
+ width: 20px;
2330
+ height: 20px;
2331
+ fill: currentColor;
2332
+ }
2333
+
2334
+ .overtype-toolbar-button:hover {
2335
+ background: var(--toolbar-hover, var(--bg-secondary, #e9ecef));
2336
+ color: var(--toolbar-icon, var(--text-primary, #333));
2337
+ }
2338
+
2339
+ .overtype-toolbar-button:active {
2340
+ transform: scale(0.95);
2341
+ }
2342
+
2343
+ .overtype-toolbar-button.active {
2344
+ background: var(--toolbar-active, var(--primary, #007bff));
2345
+ color: var(--toolbar-icon, var(--text-primary, #333));
2346
+ }
2347
+
2348
+ .overtype-toolbar-button:disabled {
2349
+ opacity: 0.5;
2350
+ cursor: not-allowed;
2351
+ }
2352
+
2353
+ .overtype-toolbar-separator {
2354
+ width: 1px;
2355
+ height: 24px;
2356
+ background: var(--border, #e0e0e0);
2357
+ margin: 0 4px;
2358
+ flex-shrink: 0;
2359
+ }
2360
+
2361
+ /* Adjust wrapper when toolbar is present */
2362
+ .overtype-container .overtype-toolbar + .overtype-wrapper {
2363
+ }
2364
+
2365
+ /* Mobile toolbar adjustments */
2366
+ @media (max-width: 640px) {
2367
+ .overtype-toolbar {
2368
+ padding: 6px;
2369
+ gap: 2px;
2370
+ }
2371
+
2372
+ .overtype-toolbar-button {
2373
+ width: 36px;
2374
+ height: 36px;
2375
+ }
2376
+
2377
+ .overtype-toolbar-separator {
2378
+ margin: 0 2px;
2379
+ }
2380
+ }
2381
+
2382
+ /* Plain mode - hide preview and show textarea text */
2383
+ .overtype-container[data-mode="plain"] .overtype-preview {
2384
+ display: none !important;
2385
+ }
2386
+
2387
+ .overtype-container[data-mode="plain"] .overtype-input {
2388
+ color: var(--text, #0d3b66) !important;
2389
+ /* Use system font stack for better plain text readability */
2390
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
2391
+ "Helvetica Neue", Arial, sans-serif !important;
2392
+ }
2393
+
2394
+ /* Ensure textarea remains transparent in overlay mode */
2395
+ .overtype-container:not([data-mode="plain"]) .overtype-input {
2396
+ color: transparent !important;
2397
+ }
2398
+
2399
+ /* Dropdown menu styles */
2400
+ .overtype-toolbar-button {
2401
+ position: relative !important; /* Override reset - needed for dropdown */
2402
+ }
2403
+
2404
+ .overtype-toolbar-button.dropdown-active {
2405
+ background: var(--toolbar-active, var(--hover-bg, #f0f0f0));
2406
+ }
2407
+
2408
+ .overtype-dropdown-menu {
2409
+ position: fixed !important; /* Fixed positioning relative to viewport */
2410
+ background: var(--bg-secondary, white) !important; /* Override reset */
2411
+ border: 1px solid var(--border, #e0e0e0) !important; /* Override reset */
2412
+ border-radius: 6px;
2413
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1) !important; /* Override reset */
2414
+ z-index: 10000; /* Very high z-index to ensure visibility */
2415
+ min-width: 150px;
2416
+ padding: 4px 0 !important; /* Override reset */
2417
+ /* Position will be set via JavaScript based on button position */
2418
+ }
2419
+
2420
+ .overtype-dropdown-item {
2421
+ display: flex;
2422
+ align-items: center;
2423
+ width: 100%;
2424
+ padding: 8px 12px;
2425
+ border: none;
2426
+ background: none;
2427
+ text-align: left;
2428
+ cursor: pointer;
2429
+ font-size: 14px;
2430
+ color: var(--text, #333);
2431
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
2432
+ }
2433
+
2434
+ .overtype-dropdown-item:hover {
2435
+ background: var(--hover-bg, #f0f0f0);
2436
+ }
2437
+
2438
+ .overtype-dropdown-item.active {
2439
+ font-weight: 600;
2440
+ }
2441
+
2442
+ .overtype-dropdown-check {
2443
+ width: 16px;
2444
+ margin-right: 8px;
2445
+ color: var(--h1, #007bff);
2446
+ }
2447
+
2448
+ .overtype-dropdown-icon {
2449
+ width: 20px;
2450
+ margin-right: 8px;
2451
+ text-align: center;
2452
+ }
2453
+
2454
+ /* Preview mode styles */
2455
+ .overtype-container[data-mode="preview"] .overtype-input {
2456
+ display: none !important;
2457
+ }
2458
+
2459
+ .overtype-container[data-mode="preview"] .overtype-preview {
2460
+ pointer-events: auto !important;
2461
+ user-select: text !important;
2462
+ cursor: text !important;
2463
+ }
2464
+
2465
+ /* Hide syntax markers in preview mode */
2466
+ .overtype-container[data-mode="preview"] .syntax-marker {
2467
+ display: none !important;
2468
+ }
2469
+
2470
+ /* Hide URL part of links in preview mode - extra specificity */
2471
+ .overtype-container[data-mode="preview"] .syntax-marker.url-part,
2472
+ .overtype-container[data-mode="preview"] .url-part {
2473
+ display: none !important;
2474
+ }
2475
+
2476
+ /* Hide all syntax markers inside links too */
2477
+ .overtype-container[data-mode="preview"] a .syntax-marker {
2478
+ display: none !important;
2479
+ }
2480
+
2481
+ /* Headers - restore proper sizing in preview mode */
2482
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview h1,
2483
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview h2,
2484
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview h3 {
2485
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important;
2486
+ font-weight: 600 !important;
2487
+ margin: 0 !important;
2488
+ display: block !important;
2489
+ color: inherit !important; /* Use parent text color */
2490
+ line-height: 1 !important; /* Tight line height for headings */
2491
+ }
2492
+
2493
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview h1 {
2494
+ font-size: 2em !important;
2495
+ }
2496
+
2497
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview h2 {
2498
+ font-size: 1.5em !important;
2499
+ }
2500
+
2501
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview h3 {
2502
+ font-size: 1.17em !important;
2503
+ }
2504
+
2505
+ /* Lists - restore list styling in preview mode */
2506
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview ul {
2507
+ display: block !important;
2508
+ list-style: disc !important;
2509
+ padding-left: 2em !important;
2510
+ margin: 1em 0 !important;
2511
+ }
2512
+
2513
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview ol {
2514
+ display: block !important;
2515
+ list-style: decimal !important;
2516
+ padding-left: 2em !important;
2517
+ margin: 1em 0 !important;
2518
+ }
2519
+
2520
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview li {
2521
+ display: list-item !important;
2522
+ margin: 0 !important;
2523
+ padding: 0 !important;
2524
+ }
2525
+
2526
+ /* Task list checkboxes - only in preview mode */
2527
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview li.task-list {
2528
+ list-style: none !important;
2529
+ position: relative !important;
2530
+ }
2531
+
2532
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview li.task-list input[type="checkbox"] {
2533
+ margin-right: 0.5em !important;
2534
+ cursor: default !important;
2535
+ vertical-align: middle !important;
2536
+ }
2537
+
2538
+ /* Task list in normal mode - keep syntax visible */
2539
+ .overtype-container:not([data-mode="preview"]) .overtype-wrapper .overtype-preview li.task-list {
2540
+ list-style: none !important;
2541
+ }
2542
+
2543
+ .overtype-container:not([data-mode="preview"]) .overtype-wrapper .overtype-preview li.task-list .syntax-marker {
2544
+ color: var(--syntax, #999999) !important;
2545
+ font-weight: normal !important;
2546
+ }
2547
+
2548
+ /* Links - make clickable in preview mode */
2549
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview a {
2550
+ pointer-events: auto !important;
2551
+ cursor: pointer !important;
2552
+ color: var(--link, #0066cc) !important;
2553
+ text-decoration: underline !important;
2554
+ }
2555
+
2556
+ /* Code blocks - proper pre/code styling in preview mode */
2557
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview pre.code-block {
2558
+ background: #2d2d2d !important;
2559
+ color: #f8f8f2 !important;
2560
+ padding: 1.2em !important;
2561
+ border-radius: 3px !important;
2562
+ overflow-x: auto !important;
2563
+ margin: 0 !important;
2564
+ display: block !important;
2565
+ }
2566
+
2567
+ /* Cave theme code block background in preview mode */
2568
+ .overtype-container[data-theme="cave"][data-mode="preview"] .overtype-wrapper .overtype-preview pre.code-block {
2569
+ background: #11171F !important;
2570
+ }
2571
+
2572
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview pre.code-block code {
2573
+ background: transparent !important;
2574
+ color: inherit !important;
2575
+ padding: 0 !important;
2576
+ font-family: ${fontFamily} !important;
2577
+ font-size: 0.9em !important;
2578
+ line-height: 1.4 !important;
2579
+ }
2580
+
2581
+ /* Hide old code block lines and fences in preview mode */
2582
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview .code-block-line {
2583
+ display: none !important;
2584
+ }
2585
+
2586
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview .code-fence {
2587
+ display: none !important;
2588
+ }
2589
+
2590
+ /* Blockquotes - enhanced styling in preview mode */
2591
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview .blockquote {
2592
+ display: block !important;
2593
+ border-left: 4px solid var(--blockquote, #ddd) !important;
2594
+ padding-left: 1em !important;
2595
+ margin: 1em 0 !important;
2596
+ font-style: italic !important;
2597
+ }
2598
+
2599
+ /* Typography improvements in preview mode */
2600
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview {
2601
+ font-family: Georgia, 'Times New Roman', serif !important;
2602
+ font-size: 16px !important;
2603
+ line-height: 1.8 !important;
2604
+ color: var(--text, #333) !important; /* Consistent text color */
2605
+ }
2606
+
2607
+ /* Inline code in preview mode - keep monospace */
2608
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview code {
2609
+ font-family: ${fontFamily} !important;
2610
+ font-size: 0.9em !important;
2611
+ background: rgba(135, 131, 120, 0.15) !important;
2612
+ padding: 0.2em 0.4em !important;
2613
+ border-radius: 3px !important;
2614
+ }
2615
+
2616
+ /* Strong and em elements in preview mode */
2617
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview strong {
2618
+ font-weight: 700 !important;
2619
+ color: inherit !important; /* Use parent text color */
2620
+ }
2621
+
2622
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview em {
2623
+ font-style: italic !important;
2624
+ color: inherit !important; /* Use parent text color */
2625
+ }
2626
+
2627
+ /* HR in preview mode */
2628
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview .hr-marker {
2629
+ display: block !important;
2630
+ border-top: 2px solid var(--hr, #ddd) !important;
2631
+ text-indent: -9999px !important;
2632
+ height: 2px !important;
2633
+ }
2634
+
2635
+ /* Link Tooltip - CSS Anchor Positioning */
2636
+ @supports (position-anchor: --x) and (position-area: center) {
2637
+ .overtype-link-tooltip {
2638
+ position: absolute;
2639
+ position-anchor: var(--target-anchor, --link-0);
2640
+ position-area: block-end center;
2641
+ margin-top: 8px !important;
2642
+
2643
+ background: #333 !important;
2644
+ color: white !important;
2645
+ padding: 6px 10px !important;
2646
+ border-radius: 16px !important;
2647
+ font-size: 12px !important;
2648
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif !important;
2649
+ display: none !important;
2650
+ z-index: 10000 !important;
2651
+ cursor: pointer !important;
2652
+ box-shadow: 0 2px 8px rgba(0,0,0,0.3) !important;
2653
+ max-width: 300px !important;
2654
+ white-space: nowrap !important;
2655
+ overflow: hidden !important;
2656
+ text-overflow: ellipsis !important;
2657
+
2658
+ position-try: most-width block-end inline-end, flip-inline, block-start center;
2659
+ position-visibility: anchors-visible;
2660
+ }
2661
+
2662
+ .overtype-link-tooltip.visible {
2663
+ display: flex !important;
2664
+ }
2665
+ }
2666
+
2667
+ ${mobileStyles}
2668
+ `;
2669
+ }
2670
+
2671
+ // src/toolbar.js
2672
+ var Toolbar = class {
2673
+ constructor(editor, options = {}) {
2674
+ this.editor = editor;
2675
+ this.container = null;
2676
+ this.buttons = {};
2677
+ this.toolbarButtons = options.toolbarButtons || [];
2678
+ }
2679
+ /**
2680
+ * Create and render toolbar
2681
+ */
2682
+ create() {
2683
+ this.container = document.createElement("div");
2684
+ this.container.className = "overtype-toolbar";
2685
+ this.container.setAttribute("role", "toolbar");
2686
+ this.container.setAttribute("aria-label", "Formatting toolbar");
2687
+ this.toolbarButtons.forEach((buttonConfig) => {
2688
+ if (buttonConfig.name === "separator") {
2689
+ const separator = this.createSeparator();
2690
+ this.container.appendChild(separator);
2691
+ } else {
2692
+ const button = this.createButton(buttonConfig);
2693
+ this.buttons[buttonConfig.name] = button;
2694
+ this.container.appendChild(button);
2695
+ }
2696
+ });
2697
+ this.editor.wrapper.insertBefore(this.container, this.editor.wrapper.firstChild);
2698
+ }
2699
+ /**
2700
+ * Create a toolbar separator
2701
+ */
2702
+ createSeparator() {
2703
+ const separator = document.createElement("div");
2704
+ separator.className = "overtype-toolbar-separator";
2705
+ separator.setAttribute("role", "separator");
2706
+ return separator;
2707
+ }
2708
+ /**
2709
+ * Create a toolbar button
2710
+ */
2711
+ createButton(buttonConfig) {
2712
+ const button = document.createElement("button");
2713
+ button.className = "overtype-toolbar-button";
2714
+ button.type = "button";
2715
+ button.setAttribute("data-button", buttonConfig.name);
2716
+ button.title = buttonConfig.title || "";
2717
+ button.setAttribute("aria-label", buttonConfig.title || buttonConfig.name);
2718
+ button.innerHTML = this.sanitizeSVG(buttonConfig.icon || "");
2719
+ if (buttonConfig.name === "viewMode") {
2720
+ button.classList.add("has-dropdown");
2721
+ button.dataset.dropdown = "true";
2722
+ button.addEventListener("click", (e) => {
2723
+ e.preventDefault();
2724
+ this.toggleViewModeDropdown(button);
2725
+ });
2726
+ return button;
2727
+ }
2728
+ button._clickHandler = async (e) => {
2729
+ e.preventDefault();
2730
+ this.editor.textarea.focus();
2731
+ try {
2732
+ if (buttonConfig.action) {
2733
+ await buttonConfig.action({
2734
+ editor: this.editor,
2735
+ getValue: () => this.editor.getValue(),
2736
+ setValue: (value) => this.editor.setValue(value),
2737
+ event: e
2738
+ });
2739
+ }
2740
+ } catch (error) {
2741
+ console.error(`Button "${buttonConfig.name}" error:`, error);
2742
+ this.editor.wrapper.dispatchEvent(new CustomEvent("button-error", {
2743
+ detail: { buttonName: buttonConfig.name, error }
2744
+ }));
2745
+ button.classList.add("button-error");
2746
+ button.style.animation = "buttonError 0.3s";
2747
+ setTimeout(() => {
2748
+ button.classList.remove("button-error");
2749
+ button.style.animation = "";
2750
+ }, 300);
2751
+ }
2752
+ };
2753
+ button.addEventListener("click", button._clickHandler);
2754
+ return button;
2755
+ }
2756
+ /**
2757
+ * Sanitize SVG to prevent XSS
2758
+ */
2759
+ sanitizeSVG(svg) {
2760
+ if (typeof svg !== "string")
2761
+ return "";
2762
+ const cleaned = svg.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "").replace(/\son\w+\s*=\s*["'][^"']*["']/gi, "").replace(/\son\w+\s*=\s*[^\s>]*/gi, "");
2763
+ return cleaned;
2764
+ }
2765
+ /**
2766
+ * Toggle view mode dropdown (internal implementation)
2767
+ * Not exposed to users - viewMode button behavior is fixed
2768
+ */
2769
+ toggleViewModeDropdown(button) {
2770
+ const existingDropdown = document.querySelector(".overtype-dropdown-menu");
2771
+ if (existingDropdown) {
2772
+ existingDropdown.remove();
2773
+ button.classList.remove("dropdown-active");
2774
+ return;
2775
+ }
2776
+ button.classList.add("dropdown-active");
2777
+ const dropdown = this.createViewModeDropdown(button);
2778
+ const rect = button.getBoundingClientRect();
2779
+ dropdown.style.position = "absolute";
2780
+ dropdown.style.top = `${rect.bottom + 5}px`;
2781
+ dropdown.style.left = `${rect.left}px`;
2782
+ document.body.appendChild(dropdown);
2783
+ this.handleDocumentClick = (e) => {
2784
+ if (!dropdown.contains(e.target) && !button.contains(e.target)) {
2785
+ dropdown.remove();
2786
+ button.classList.remove("dropdown-active");
2787
+ document.removeEventListener("click", this.handleDocumentClick);
2788
+ }
2789
+ };
2790
+ setTimeout(() => {
2791
+ document.addEventListener("click", this.handleDocumentClick);
2792
+ }, 0);
2793
+ }
2794
+ /**
2795
+ * Create view mode dropdown menu (internal implementation)
2796
+ */
2797
+ createViewModeDropdown(button) {
2798
+ const dropdown = document.createElement("div");
2799
+ dropdown.className = "overtype-dropdown-menu";
2800
+ const items = [
2801
+ { id: "normal", label: "Normal Edit", icon: "\u2713" },
2802
+ { id: "plain", label: "Plain Textarea", icon: "\u2713" },
2803
+ { id: "preview", label: "Preview Mode", icon: "\u2713" }
2804
+ ];
2805
+ const currentMode = this.editor.container.dataset.mode || "normal";
2806
+ items.forEach((item) => {
2807
+ const menuItem = document.createElement("button");
2808
+ menuItem.className = "overtype-dropdown-item";
2809
+ menuItem.type = "button";
2810
+ menuItem.textContent = item.label;
2811
+ if (item.id === currentMode) {
2812
+ menuItem.classList.add("active");
2813
+ menuItem.setAttribute("aria-current", "true");
2814
+ const checkmark = document.createElement("span");
2815
+ checkmark.className = "overtype-dropdown-icon";
2816
+ checkmark.textContent = item.icon;
2817
+ menuItem.prepend(checkmark);
2818
+ }
2819
+ menuItem.addEventListener("click", (e) => {
2820
+ e.preventDefault();
2821
+ switch (item.id) {
2822
+ case "plain":
2823
+ this.editor.showPlainTextarea();
2824
+ break;
2825
+ case "preview":
2826
+ this.editor.showPreviewMode();
2827
+ break;
2828
+ case "normal":
2829
+ default:
2830
+ this.editor.showNormalEditMode();
2831
+ break;
2832
+ }
2833
+ dropdown.remove();
2834
+ button.classList.remove("dropdown-active");
2835
+ document.removeEventListener("click", this.handleDocumentClick);
2836
+ });
2837
+ dropdown.appendChild(menuItem);
2838
+ });
2839
+ return dropdown;
2840
+ }
2841
+ /**
2842
+ * Update active states of toolbar buttons
2843
+ */
2844
+ updateButtonStates() {
2845
+ var _a;
2846
+ try {
2847
+ const activeFormats = ((_a = getActiveFormats2) == null ? void 0 : _a(
2848
+ this.editor.textarea,
2849
+ this.editor.textarea.selectionStart
2850
+ )) || [];
2851
+ Object.entries(this.buttons).forEach(([name, button]) => {
2852
+ if (name === "viewMode")
2853
+ return;
2854
+ let isActive = false;
2855
+ switch (name) {
2856
+ case "bold":
2857
+ isActive = activeFormats.includes("bold");
2858
+ break;
2859
+ case "italic":
2860
+ isActive = activeFormats.includes("italic");
2861
+ break;
2862
+ case "code":
2863
+ isActive = false;
2864
+ break;
2865
+ case "bulletList":
2866
+ isActive = activeFormats.includes("bullet-list");
2867
+ break;
2868
+ case "orderedList":
2869
+ isActive = activeFormats.includes("numbered-list");
2870
+ break;
2871
+ case "taskList":
2872
+ isActive = activeFormats.includes("task-list");
2873
+ break;
2874
+ case "quote":
2875
+ isActive = activeFormats.includes("quote");
2876
+ break;
2877
+ case "h1":
2878
+ isActive = activeFormats.includes("header");
2879
+ break;
2880
+ case "h2":
2881
+ isActive = activeFormats.includes("header-2");
2882
+ break;
2883
+ case "h3":
2884
+ isActive = activeFormats.includes("header-3");
2885
+ break;
2886
+ }
2887
+ button.classList.toggle("active", isActive);
2888
+ button.setAttribute("aria-pressed", isActive.toString());
2889
+ });
2890
+ } catch (error) {
2891
+ }
2892
+ }
2893
+ /**
2894
+ * Destroy toolbar and cleanup
2895
+ */
2896
+ destroy() {
2897
+ if (this.container) {
2898
+ if (this.handleDocumentClick) {
2899
+ document.removeEventListener("click", this.handleDocumentClick);
2900
+ }
2901
+ Object.values(this.buttons).forEach((button) => {
2902
+ if (button._clickHandler) {
2903
+ button.removeEventListener("click", button._clickHandler);
2904
+ delete button._clickHandler;
2905
+ }
2906
+ });
2907
+ this.container.remove();
2908
+ this.container = null;
2909
+ this.buttons = {};
2910
+ }
2911
+ }
2912
+ };
2913
+
2914
+ // src/link-tooltip.js
2915
+ var LinkTooltip = class {
2916
+ constructor(editor) {
2917
+ this.editor = editor;
2918
+ this.tooltip = null;
2919
+ this.currentLink = null;
2920
+ this.hideTimeout = null;
2921
+ this.visibilityChangeHandler = null;
2922
+ this.init();
2923
+ }
2924
+ init() {
2925
+ this.createTooltip();
2926
+ this.editor.textarea.addEventListener("selectionchange", () => this.checkCursorPosition());
2927
+ this.editor.textarea.addEventListener("keyup", (e) => {
2928
+ if (e.key.includes("Arrow") || e.key === "Home" || e.key === "End") {
2929
+ this.checkCursorPosition();
2930
+ }
2931
+ });
2932
+ this.editor.textarea.addEventListener("input", () => this.hide());
2933
+ this.editor.textarea.addEventListener("scroll", () => this.hide());
2934
+ this.editor.textarea.addEventListener("blur", () => this.hide());
2935
+ this.visibilityChangeHandler = () => {
2936
+ if (document.hidden) {
2937
+ this.hide();
2938
+ }
2939
+ };
2940
+ document.addEventListener("visibilitychange", this.visibilityChangeHandler);
2941
+ this.tooltip.addEventListener("mouseenter", () => this.cancelHide());
2942
+ this.tooltip.addEventListener("mouseleave", () => this.scheduleHide());
2943
+ }
2944
+ createTooltip() {
2945
+ this.tooltip = document.createElement("div");
2946
+ this.tooltip.className = "overtype-link-tooltip";
2947
+ this.tooltip.innerHTML = `
2948
+ <span style="display: flex; align-items: center; gap: 6px;">
2949
+ <svg width="12" height="12" viewBox="0 0 20 20" fill="currentColor" style="flex-shrink: 0;">
2950
+ <path d="M11 3a1 1 0 100 2h2.586l-6.293 6.293a1 1 0 101.414 1.414L15 6.414V9a1 1 0 102 0V4a1 1 0 00-1-1h-5z"></path>
2951
+ <path d="M5 5a2 2 0 00-2 2v8a2 2 0 002 2h8a2 2 0 002-2v-3a1 1 0 10-2 0v3H5V7h3a1 1 0 000-2H5z"></path>
2952
+ </svg>
2953
+ <span class="overtype-link-tooltip-url"></span>
2954
+ </span>
2955
+ `;
2956
+ this.tooltip.addEventListener("click", (e) => {
2957
+ e.preventDefault();
2958
+ e.stopPropagation();
2959
+ if (this.currentLink) {
2960
+ window.open(this.currentLink.url, "_blank");
2961
+ this.hide();
2962
+ }
2963
+ });
2964
+ this.editor.container.appendChild(this.tooltip);
2965
+ }
2966
+ checkCursorPosition() {
2967
+ const cursorPos = this.editor.textarea.selectionStart;
2968
+ const text = this.editor.textarea.value;
2969
+ const linkInfo = this.findLinkAtPosition(text, cursorPos);
2970
+ if (linkInfo) {
2971
+ if (!this.currentLink || this.currentLink.url !== linkInfo.url || this.currentLink.index !== linkInfo.index) {
2972
+ this.show(linkInfo);
2973
+ }
2974
+ } else {
2975
+ this.scheduleHide();
2976
+ }
2977
+ }
2978
+ findLinkAtPosition(text, position) {
2979
+ const linkRegex = /\[([^\]]+)\]\(([^)]+)\)/g;
2980
+ let match;
2981
+ let linkIndex = 0;
2982
+ while ((match = linkRegex.exec(text)) !== null) {
2983
+ const start = match.index;
2984
+ const end = match.index + match[0].length;
2985
+ if (position >= start && position <= end) {
2986
+ return {
2987
+ text: match[1],
2988
+ url: match[2],
2989
+ index: linkIndex,
2990
+ start,
2991
+ end
2992
+ };
2993
+ }
2994
+ linkIndex++;
2995
+ }
2996
+ return null;
2997
+ }
2998
+ show(linkInfo) {
2999
+ this.currentLink = linkInfo;
3000
+ this.cancelHide();
3001
+ const urlSpan = this.tooltip.querySelector(".overtype-link-tooltip-url");
3002
+ urlSpan.textContent = linkInfo.url;
3003
+ this.tooltip.style.setProperty("--target-anchor", `--link-${linkInfo.index}`);
3004
+ this.tooltip.classList.add("visible");
3005
+ }
3006
+ hide() {
3007
+ this.tooltip.classList.remove("visible");
3008
+ this.currentLink = null;
3009
+ }
3010
+ scheduleHide() {
3011
+ this.cancelHide();
3012
+ this.hideTimeout = setTimeout(() => this.hide(), 300);
3013
+ }
3014
+ cancelHide() {
3015
+ if (this.hideTimeout) {
3016
+ clearTimeout(this.hideTimeout);
3017
+ this.hideTimeout = null;
3018
+ }
3019
+ }
3020
+ destroy() {
3021
+ this.cancelHide();
3022
+ if (this.visibilityChangeHandler) {
3023
+ document.removeEventListener("visibilitychange", this.visibilityChangeHandler);
3024
+ this.visibilityChangeHandler = null;
3025
+ }
3026
+ if (this.tooltip && this.tooltip.parentNode) {
3027
+ this.tooltip.parentNode.removeChild(this.tooltip);
3028
+ }
3029
+ this.tooltip = null;
3030
+ this.currentLink = null;
3031
+ }
3032
+ };
3033
+
3034
+ // src/icons.js
3035
+ var boldIcon = `<svg viewBox="0 0 18 18">
3036
+ <path stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5,4H9.5A2.5,2.5,0,0,1,12,6.5v0A2.5,2.5,0,0,1,9.5,9H5A0,0,0,0,1,5,9V4A0,0,0,0,1,5,4Z"></path>
3037
+ <path stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5,9h5.5A2.5,2.5,0,0,1,13,11.5v0A2.5,2.5,0,0,1,10.5,14H5a0,0,0,0,1,0,0V9A0,0,0,0,1,5,9Z"></path>
3038
+ </svg>`;
3039
+ var italicIcon = `<svg viewBox="0 0 18 18">
3040
+ <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="7" x2="13" y1="4" y2="4"></line>
3041
+ <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="5" x2="11" y1="14" y2="14"></line>
3042
+ <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="8" x2="10" y1="14" y2="4"></line>
3043
+ </svg>`;
3044
+ var h1Icon = `<svg viewBox="0 0 18 18">
3045
+ <path fill="currentColor" d="M10,4V14a1,1,0,0,1-2,0V10H3v4a1,1,0,0,1-2,0V4A1,1,0,0,1,3,4V8H8V4a1,1,0,0,1,2,0Zm6.06787,9.209H14.98975V7.59863a.54085.54085,0,0,0-.605-.60547h-.62744a1.01119,1.01119,0,0,0-.748.29688L11.645,8.56641a.5435.5435,0,0,0-.022.8584l.28613.30762a.53861.53861,0,0,0,.84717.0332l.09912-.08789a1.2137,1.2137,0,0,0,.2417-.35254h.02246s-.01123.30859-.01123.60547V13.209H12.041a.54085.54085,0,0,0-.605.60547v.43945a.54085.54085,0,0,0,.605.60547h4.02686a.54085.54085,0,0,0,.605-.60547v-.43945A.54085.54085,0,0,0,16.06787,13.209Z"></path>
3046
+ </svg>`;
3047
+ var h2Icon = `<svg viewBox="0 0 18 18">
3048
+ <path fill="currentColor" d="M16.73975,13.81445v.43945a.54085.54085,0,0,1-.605.60547H11.855a.58392.58392,0,0,1-.64893-.60547V14.0127c0-2.90527,3.39941-3.42187,3.39941-4.55469a.77675.77675,0,0,0-.84717-.78125,1.17684,1.17684,0,0,0-.83594.38477c-.2749.26367-.561.374-.85791.13184l-.4292-.34082c-.30811-.24219-.38525-.51758-.1543-.81445a2.97155,2.97155,0,0,1,2.45361-1.17676,2.45393,2.45393,0,0,1,2.68408,2.40918c0,2.45312-3.1792,2.92676-3.27832,3.93848h2.79443A.54085.54085,0,0,1,16.73975,13.81445ZM9,3A.99974.99974,0,0,0,8,4V8H3V4A1,1,0,0,0,1,4V14a1,1,0,0,0,2,0V10H8v4a1,1,0,0,0,2,0V4A.99974.99974,0,0,0,9,3Z"></path>
3049
+ </svg>`;
3050
+ var h3Icon = `<svg viewBox="0 0 18 18">
3051
+ <path fill="currentColor" d="M16.65186,12.30664a2.6742,2.6742,0,0,1-2.915,2.68457,3.96592,3.96592,0,0,1-2.25537-.6709.56007.56007,0,0,1-.13232-.83594L11.64648,13c.209-.34082.48389-.36328.82471-.1543a2.32654,2.32654,0,0,0,1.12256.33008c.71484,0,1.12207-.35156,1.12207-.78125,0-.61523-.61621-.86816-1.46338-.86816H13.2085a.65159.65159,0,0,1-.68213-.41895l-.05518-.10937a.67114.67114,0,0,1,.14307-.78125l.71533-.86914a8.55289,8.55289,0,0,1,.68213-.7373V8.58887a3.93913,3.93913,0,0,1-.748.05469H11.9873a.54085.54085,0,0,1-.605-.60547V7.59863a.54085.54085,0,0,1,.605-.60547h3.75146a.53773.53773,0,0,1,.60547.59375v.17676a1.03723,1.03723,0,0,1-.27539.748L14.74854,10.0293A2.31132,2.31132,0,0,1,16.65186,12.30664ZM9,3A.99974.99974,0,0,0,8,4V8H3V4A1,1,0,0,0,1,4V14a1,1,0,0,0,2,0V10H8v4a1,1,0,0,0,2,0V4A.99974.99974,0,0,0,9,3Z"></path>
3052
+ </svg>`;
3053
+ var linkIcon = `<svg viewBox="0 0 18 18">
3054
+ <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="7" x2="11" y1="7" y2="11"></line>
3055
+ <path stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.9,4.577a3.476,3.476,0,0,1,.36,4.679A3.476,3.476,0,0,1,4.577,8.9C3.185,7.5,2.035,6.4,4.217,4.217S7.5,3.185,8.9,4.577Z"></path>
3056
+ <path stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.423,9.1a3.476,3.476,0,0,0-4.679-.36,3.476,3.476,0,0,0,.36,4.679c1.392,1.392,2.5,2.542,4.679.36S14.815,10.5,13.423,9.1Z"></path>
3057
+ </svg>`;
3058
+ var codeIcon = `<svg viewBox="0 0 18 18">
3059
+ <polyline stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" points="5 7 3 9 5 11"></polyline>
3060
+ <polyline stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" points="13 7 15 9 13 11"></polyline>
3061
+ <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="10" x2="8" y1="5" y2="13"></line>
3062
+ </svg>`;
3063
+ var bulletListIcon = `<svg viewBox="0 0 18 18">
3064
+ <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="6" x2="15" y1="4" y2="4"></line>
3065
+ <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="6" x2="15" y1="9" y2="9"></line>
3066
+ <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="6" x2="15" y1="14" y2="14"></line>
3067
+ <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="3" x2="3" y1="4" y2="4"></line>
3068
+ <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="3" x2="3" y1="9" y2="9"></line>
3069
+ <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="3" x2="3" y1="14" y2="14"></line>
3070
+ </svg>`;
3071
+ var orderedListIcon = `<svg viewBox="0 0 18 18">
3072
+ <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="7" x2="15" y1="4" y2="4"></line>
3073
+ <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="7" x2="15" y1="9" y2="9"></line>
3074
+ <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="7" x2="15" y1="14" y2="14"></line>
3075
+ <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" x1="2.5" x2="4.5" y1="5.5" y2="5.5"></line>
3076
+ <path fill="currentColor" d="M3.5,6A0.5,0.5,0,0,1,3,5.5V3.085l-0.276.138A0.5,0.5,0,0,1,2.053,3c-0.124-.247-0.023-0.324.224-0.447l1-.5A0.5,0.5,0,0,1,4,2.5v3A0.5,0.5,0,0,1,3.5,6Z"></path>
3077
+ <path stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" d="M4.5,10.5h-2c0-.234,1.85-1.076,1.85-2.234A0.959,0.959,0,0,0,2.5,8.156"></path>
3078
+ <path stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" d="M2.5,14.846a0.959,0.959,0,0,0,1.85-.109A0.7,0.7,0,0,0,3.75,14a0.688,0.688,0,0,0,.6-0.736,0.959,0.959,0,0,0-1.85-.109"></path>
3079
+ </svg>`;
3080
+ var quoteIcon = `<svg viewBox="2 2 20 20">
3081
+ <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 10.8182L9 10.8182C8.80222 10.8182 8.60888 10.7649 8.44443 10.665C8.27998 10.5651 8.15181 10.4231 8.07612 10.257C8.00043 10.0909 7.98063 9.90808 8.01922 9.73174C8.0578 9.55539 8.15304 9.39341 8.29289 9.26627C8.43275 9.13913 8.61093 9.05255 8.80491 9.01747C8.99889 8.98239 9.19996 9.00039 9.38268 9.0692C9.56541 9.13801 9.72159 9.25453 9.83147 9.40403C9.94135 9.55353 10 9.72929 10 9.90909L10 12.1818C10 12.664 9.78929 13.1265 9.41421 13.4675C9.03914 13.8084 8.53043 14 8 14"></path>
3082
+ <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 10.8182L15 10.8182C14.8022 10.8182 14.6089 10.7649 14.4444 10.665C14.28 10.5651 14.1518 10.4231 14.0761 10.257C14.0004 10.0909 13.9806 9.90808 14.0192 9.73174C14.0578 9.55539 14.153 9.39341 14.2929 9.26627C14.4327 9.13913 14.6109 9.05255 14.8049 9.01747C14.9989 8.98239 15.2 9.00039 15.3827 9.0692C15.5654 9.13801 15.7216 9.25453 15.8315 9.40403C15.9414 9.55353 16 9.72929 16 9.90909L16 12.1818C16 12.664 15.7893 13.1265 15.4142 13.4675C15.0391 13.8084 14.5304 14 14 14"></path>
3083
+ </svg>`;
3084
+ var taskListIcon = `<svg viewBox="0 0 18 18">
3085
+ <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="8" x2="16" y1="4" y2="4"></line>
3086
+ <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="8" x2="16" y1="9" y2="9"></line>
3087
+ <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="8" x2="16" y1="14" y2="14"></line>
3088
+ <rect stroke="currentColor" fill="none" stroke-width="1.5" x="2" y="3" width="3" height="3" rx="0.5"></rect>
3089
+ <rect stroke="currentColor" fill="none" stroke-width="1.5" x="2" y="13" width="3" height="3" rx="0.5"></rect>
3090
+ <polyline stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" points="2.65 9.5 3.5 10.5 5 8.5"></polyline>
3091
+ </svg>`;
3092
+ var eyeIcon = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
3093
+ <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" fill="none"></path>
3094
+ <circle cx="12" cy="12" r="3" fill="none"></circle>
3095
+ </svg>`;
3096
+
3097
+ // src/toolbar-buttons.js
3098
+ var toolbarButtons = {
3099
+ bold: {
3100
+ name: "bold",
3101
+ icon: boldIcon,
3102
+ title: "Bold (Ctrl+B)",
3103
+ action: ({ editor, event }) => {
3104
+ toggleBold(editor.textarea);
3105
+ editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
3106
+ }
3107
+ },
3108
+ italic: {
3109
+ name: "italic",
3110
+ icon: italicIcon,
3111
+ title: "Italic (Ctrl+I)",
3112
+ action: ({ editor, event }) => {
3113
+ toggleItalic(editor.textarea);
3114
+ editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
3115
+ }
3116
+ },
3117
+ code: {
3118
+ name: "code",
3119
+ icon: codeIcon,
3120
+ title: "Inline Code",
3121
+ action: ({ editor, event }) => {
3122
+ toggleCode(editor.textarea);
3123
+ editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
3124
+ }
3125
+ },
3126
+ separator: {
3127
+ name: "separator"
3128
+ // No icon, title, or action - special separator element
3129
+ },
3130
+ link: {
3131
+ name: "link",
3132
+ icon: linkIcon,
3133
+ title: "Insert Link",
3134
+ action: ({ editor, event }) => {
3135
+ insertLink(editor.textarea);
3136
+ editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
3137
+ }
3138
+ },
3139
+ h1: {
3140
+ name: "h1",
3141
+ icon: h1Icon,
3142
+ title: "Heading 1",
3143
+ action: ({ editor, event }) => {
3144
+ toggleH1(editor.textarea);
3145
+ editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
3146
+ }
3147
+ },
3148
+ h2: {
3149
+ name: "h2",
3150
+ icon: h2Icon,
3151
+ title: "Heading 2",
3152
+ action: ({ editor, event }) => {
3153
+ toggleH2(editor.textarea);
3154
+ editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
3155
+ }
3156
+ },
3157
+ h3: {
3158
+ name: "h3",
3159
+ icon: h3Icon,
3160
+ title: "Heading 3",
3161
+ action: ({ editor, event }) => {
3162
+ toggleH3(editor.textarea);
3163
+ editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
3164
+ }
3165
+ },
3166
+ bulletList: {
3167
+ name: "bulletList",
3168
+ icon: bulletListIcon,
3169
+ title: "Bullet List",
3170
+ action: ({ editor, event }) => {
3171
+ toggleBulletList(editor.textarea);
3172
+ editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
3173
+ }
3174
+ },
3175
+ orderedList: {
3176
+ name: "orderedList",
3177
+ icon: orderedListIcon,
3178
+ title: "Numbered List",
3179
+ action: ({ editor, event }) => {
3180
+ toggleNumberedList(editor.textarea);
3181
+ editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
3182
+ }
3183
+ },
3184
+ taskList: {
3185
+ name: "taskList",
3186
+ icon: taskListIcon,
3187
+ title: "Task List",
3188
+ action: ({ editor, event }) => {
3189
+ if (toggleTaskList) {
3190
+ toggleTaskList(editor.textarea);
3191
+ editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
3192
+ }
3193
+ }
3194
+ },
3195
+ quote: {
3196
+ name: "quote",
3197
+ icon: quoteIcon,
3198
+ title: "Quote",
3199
+ action: ({ editor, event }) => {
3200
+ toggleQuote(editor.textarea);
3201
+ editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
3202
+ }
3203
+ },
3204
+ viewMode: {
3205
+ name: "viewMode",
3206
+ icon: eyeIcon,
3207
+ title: "View mode"
3208
+ // Special: handled internally by Toolbar class as dropdown
3209
+ // No action property - dropdown behavior is internal
3210
+ }
3211
+ };
3212
+ var defaultToolbarButtons = [
3213
+ toolbarButtons.bold,
3214
+ toolbarButtons.italic,
3215
+ toolbarButtons.code,
3216
+ toolbarButtons.separator,
3217
+ toolbarButtons.link,
3218
+ toolbarButtons.separator,
3219
+ toolbarButtons.h1,
3220
+ toolbarButtons.h2,
3221
+ toolbarButtons.h3,
3222
+ toolbarButtons.separator,
3223
+ toolbarButtons.bulletList,
3224
+ toolbarButtons.orderedList,
3225
+ toolbarButtons.taskList,
3226
+ toolbarButtons.separator,
3227
+ toolbarButtons.quote,
3228
+ toolbarButtons.separator,
3229
+ toolbarButtons.viewMode
3230
+ ];
3231
+
3232
+ // src/overtype.js
3233
+ var _OverType = class _OverType {
3234
+ /**
3235
+ * Constructor - Always returns an array of instances
3236
+ * @param {string|Element|NodeList|Array} target - Target element(s)
3237
+ * @param {Object} options - Configuration options
3238
+ * @returns {Array} Array of OverType instances
3239
+ */
3240
+ constructor(target, options = {}) {
3241
+ let elements;
3242
+ if (typeof target === "string") {
3243
+ elements = document.querySelectorAll(target);
3244
+ if (elements.length === 0) {
3245
+ throw new Error(`No elements found for selector: ${target}`);
3246
+ }
3247
+ elements = Array.from(elements);
3248
+ } else if (target instanceof Element) {
3249
+ elements = [target];
3250
+ } else if (target instanceof NodeList) {
3251
+ elements = Array.from(target);
3252
+ } else if (Array.isArray(target)) {
3253
+ elements = target;
3254
+ } else {
3255
+ throw new Error("Invalid target: must be selector string, Element, NodeList, or Array");
3256
+ }
3257
+ const instances = elements.map((element) => {
3258
+ if (element.overTypeInstance) {
3259
+ element.overTypeInstance.reinit(options);
3260
+ return element.overTypeInstance;
3261
+ }
3262
+ const instance = Object.create(_OverType.prototype);
3263
+ instance._init(element, options);
3264
+ element.overTypeInstance = instance;
3265
+ _OverType.instances.set(element, instance);
3266
+ return instance;
3267
+ });
3268
+ return instances;
3269
+ }
3270
+ /**
3271
+ * Internal initialization
3272
+ * @private
3273
+ */
3274
+ _init(element, options = {}) {
3275
+ this.element = element;
3276
+ this.instanceTheme = options.theme || null;
3277
+ this.options = this._mergeOptions(options);
3278
+ this.instanceId = ++_OverType.instanceCount;
3279
+ this.initialized = false;
3280
+ _OverType.injectStyles();
3281
+ _OverType.initGlobalListeners();
3282
+ const container = element.querySelector(".overtype-container");
3283
+ const wrapper = element.querySelector(".overtype-wrapper");
3284
+ if (container || wrapper) {
3285
+ this._recoverFromDOM(container, wrapper);
3286
+ } else {
3287
+ this._buildFromScratch();
3288
+ }
3289
+ this.shortcuts = new ShortcutsManager(this);
3290
+ this.linkTooltip = new LinkTooltip(this);
3291
+ this.initialized = true;
3292
+ if (this.options.onChange) {
3293
+ this.options.onChange(this.getValue(), this);
3294
+ }
3295
+ }
3296
+ /**
3297
+ * Merge user options with defaults
3298
+ * @private
3299
+ */
3300
+ _mergeOptions(options) {
3301
+ const defaults = {
3302
+ // Typography
3303
+ fontSize: "14px",
3304
+ lineHeight: 1.6,
3305
+ /* System-first, guaranteed monospaced; avoids Android 'ui-monospace' pitfalls */
3306
+ 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',
3307
+ padding: "16px",
3308
+ // Mobile styles
3309
+ mobile: {
3310
+ fontSize: "16px",
3311
+ // Prevent zoom on iOS
3312
+ padding: "12px",
3313
+ lineHeight: 1.5
3314
+ },
3315
+ // Native textarea properties
3316
+ textareaProps: {},
3317
+ // Behavior
3318
+ autofocus: false,
3319
+ autoResize: false,
3320
+ // Auto-expand height with content
3321
+ minHeight: "100px",
3322
+ // Minimum height for autoResize mode
3323
+ maxHeight: null,
3324
+ // Maximum height for autoResize mode (null = unlimited)
3325
+ placeholder: "Start typing...",
3326
+ value: "",
3327
+ // Callbacks
3328
+ onChange: null,
3329
+ onKeydown: null,
3330
+ // Features
3331
+ showActiveLineRaw: false,
3332
+ showStats: false,
3333
+ toolbar: false,
3334
+ toolbarButtons: null,
3335
+ // Defaults to defaultToolbarButtons if toolbar: true
3336
+ statsFormatter: null,
3337
+ smartLists: true,
3338
+ // Enable smart list continuation
3339
+ codeHighlighter: null
3340
+ // Per-instance code highlighter
3341
+ };
3342
+ const { theme, colors, ...cleanOptions } = options;
3343
+ return {
3344
+ ...defaults,
3345
+ ...cleanOptions
3346
+ };
3347
+ }
3348
+ /**
3349
+ * Recover from existing DOM structure
3350
+ * @private
3351
+ */
3352
+ _recoverFromDOM(container, wrapper) {
3353
+ if (container && container.classList.contains("overtype-container")) {
3354
+ this.container = container;
3355
+ this.wrapper = container.querySelector(".overtype-wrapper");
3356
+ } else if (wrapper) {
3357
+ this.wrapper = wrapper;
3358
+ this.container = document.createElement("div");
3359
+ this.container.className = "overtype-container";
3360
+ const themeToUse = this.instanceTheme || _OverType.currentTheme || solar;
3361
+ const themeName = typeof themeToUse === "string" ? themeToUse : themeToUse.name;
3362
+ if (themeName) {
3363
+ this.container.setAttribute("data-theme", themeName);
3364
+ }
3365
+ if (this.instanceTheme) {
3366
+ const themeObj = typeof this.instanceTheme === "string" ? getTheme(this.instanceTheme) : this.instanceTheme;
3367
+ if (themeObj && themeObj.colors) {
3368
+ const cssVars = themeToCSSVars(themeObj.colors);
3369
+ this.container.style.cssText += cssVars;
3370
+ }
3371
+ }
3372
+ wrapper.parentNode.insertBefore(this.container, wrapper);
3373
+ this.container.appendChild(wrapper);
3374
+ }
3375
+ if (!this.wrapper) {
3376
+ if (container)
3377
+ container.remove();
3378
+ if (wrapper)
3379
+ wrapper.remove();
3380
+ this._buildFromScratch();
3381
+ return;
3382
+ }
3383
+ this.textarea = this.wrapper.querySelector(".overtype-input");
3384
+ this.preview = this.wrapper.querySelector(".overtype-preview");
3385
+ if (!this.textarea || !this.preview) {
3386
+ this.container.remove();
3387
+ this._buildFromScratch();
3388
+ return;
3389
+ }
3390
+ this.wrapper._instance = this;
3391
+ if (this.options.fontSize) {
3392
+ this.wrapper.style.setProperty("--instance-font-size", this.options.fontSize);
3393
+ }
3394
+ if (this.options.lineHeight) {
3395
+ this.wrapper.style.setProperty("--instance-line-height", String(this.options.lineHeight));
3396
+ }
3397
+ if (this.options.padding) {
3398
+ this.wrapper.style.setProperty("--instance-padding", this.options.padding);
3399
+ }
3400
+ this._configureTextarea();
3401
+ this._applyOptions();
3402
+ }
3403
+ /**
3404
+ * Build editor from scratch
3405
+ * @private
3406
+ */
3407
+ _buildFromScratch() {
3408
+ const content = this._extractContent();
3409
+ this.element.innerHTML = "";
3410
+ this._createDOM();
3411
+ if (content || this.options.value) {
3412
+ this.setValue(content || this.options.value);
3413
+ }
3414
+ this._applyOptions();
3415
+ }
3416
+ /**
3417
+ * Extract content from element
3418
+ * @private
3419
+ */
3420
+ _extractContent() {
3421
+ const textarea = this.element.querySelector(".overtype-input");
3422
+ if (textarea)
3423
+ return textarea.value;
3424
+ return this.element.textContent || "";
3425
+ }
3426
+ /**
3427
+ * Create DOM structure
3428
+ * @private
3429
+ */
3430
+ _createDOM() {
3431
+ this.container = document.createElement("div");
3432
+ this.container.className = "overtype-container";
3433
+ const themeToUse = this.instanceTheme || _OverType.currentTheme || solar;
3434
+ const themeName = typeof themeToUse === "string" ? themeToUse : themeToUse.name;
3435
+ if (themeName) {
3436
+ this.container.setAttribute("data-theme", themeName);
3437
+ }
3438
+ if (this.instanceTheme) {
3439
+ const themeObj = typeof this.instanceTheme === "string" ? getTheme(this.instanceTheme) : this.instanceTheme;
3440
+ if (themeObj && themeObj.colors) {
3441
+ const cssVars = themeToCSSVars(themeObj.colors);
3442
+ this.container.style.cssText += cssVars;
3443
+ }
3444
+ }
3445
+ this.wrapper = document.createElement("div");
3446
+ this.wrapper.className = "overtype-wrapper";
3447
+ if (this.options.fontSize) {
3448
+ this.wrapper.style.setProperty("--instance-font-size", this.options.fontSize);
3449
+ }
3450
+ if (this.options.lineHeight) {
3451
+ this.wrapper.style.setProperty("--instance-line-height", String(this.options.lineHeight));
3452
+ }
3453
+ if (this.options.padding) {
3454
+ this.wrapper.style.setProperty("--instance-padding", this.options.padding);
3455
+ }
3456
+ this.wrapper._instance = this;
3457
+ this.textarea = document.createElement("textarea");
3458
+ this.textarea.className = "overtype-input";
3459
+ this.textarea.placeholder = this.options.placeholder;
3460
+ this._configureTextarea();
3461
+ if (this.options.textareaProps) {
3462
+ Object.entries(this.options.textareaProps).forEach(([key, value]) => {
3463
+ if (key === "className" || key === "class") {
3464
+ this.textarea.className += " " + value;
3465
+ } else if (key === "style" && typeof value === "object") {
3466
+ Object.assign(this.textarea.style, value);
3467
+ } else {
3468
+ this.textarea.setAttribute(key, value);
3469
+ }
3470
+ });
3471
+ }
3472
+ this.preview = document.createElement("div");
3473
+ this.preview.className = "overtype-preview";
3474
+ this.preview.setAttribute("aria-hidden", "true");
3475
+ this.wrapper.appendChild(this.textarea);
3476
+ this.wrapper.appendChild(this.preview);
3477
+ this.container.appendChild(this.wrapper);
3478
+ if (this.options.showStats) {
3479
+ this.statsBar = document.createElement("div");
3480
+ this.statsBar.className = "overtype-stats";
3481
+ this.container.appendChild(this.statsBar);
3482
+ this._updateStats();
3483
+ }
3484
+ this.element.appendChild(this.container);
3485
+ if (window.location.pathname.includes("demo.html")) {
3486
+ console.log("_createDOM completed:", {
3487
+ elementId: this.element.id,
3488
+ autoResize: this.options.autoResize,
3489
+ containerClasses: this.container.className,
3490
+ hasStats: !!this.statsBar,
3491
+ hasToolbar: this.options.toolbar
3492
+ });
3493
+ }
3494
+ if (this.options.autoResize) {
3495
+ this._setupAutoResize();
3496
+ } else {
3497
+ this.container.classList.remove("overtype-auto-resize");
3498
+ if (window.location.pathname.includes("demo.html")) {
3499
+ console.log("Removed auto-resize class from:", this.element.id);
3500
+ }
3501
+ }
3502
+ }
3503
+ /**
3504
+ * Configure textarea attributes
3505
+ * @private
3506
+ */
3507
+ _configureTextarea() {
3508
+ this.textarea.setAttribute("autocomplete", "off");
3509
+ this.textarea.setAttribute("autocorrect", "off");
3510
+ this.textarea.setAttribute("autocapitalize", "off");
3511
+ this.textarea.setAttribute("spellcheck", "false");
3512
+ this.textarea.setAttribute("data-gramm", "false");
3513
+ this.textarea.setAttribute("data-gramm_editor", "false");
3514
+ this.textarea.setAttribute("data-enable-grammarly", "false");
3515
+ }
3516
+ /**
3517
+ * Create and setup toolbar
3518
+ * @private
3519
+ */
3520
+ _createToolbar() {
3521
+ const toolbarButtons2 = this.options.toolbarButtons || defaultToolbarButtons;
3522
+ this.toolbar = new Toolbar(this, { toolbarButtons: toolbarButtons2 });
3523
+ this.toolbar.create();
3524
+ this._toolbarSelectionListener = () => {
3525
+ if (this.toolbar) {
3526
+ this.toolbar.updateButtonStates();
3527
+ }
3528
+ };
3529
+ this._toolbarInputListener = () => {
3530
+ if (this.toolbar) {
3531
+ this.toolbar.updateButtonStates();
3532
+ }
3533
+ };
3534
+ this.textarea.addEventListener("selectionchange", this._toolbarSelectionListener);
3535
+ this.textarea.addEventListener("input", this._toolbarInputListener);
3536
+ }
3537
+ /**
3538
+ * Cleanup toolbar event listeners
3539
+ * @private
3540
+ */
3541
+ _cleanupToolbarListeners() {
3542
+ if (this._toolbarSelectionListener) {
3543
+ this.textarea.removeEventListener("selectionchange", this._toolbarSelectionListener);
3544
+ this._toolbarSelectionListener = null;
3545
+ }
3546
+ if (this._toolbarInputListener) {
3547
+ this.textarea.removeEventListener("input", this._toolbarInputListener);
3548
+ this._toolbarInputListener = null;
3549
+ }
3550
+ }
3551
+ /**
3552
+ * Apply options to the editor
3553
+ * @private
3554
+ */
3555
+ _applyOptions() {
3556
+ if (this.options.autofocus) {
3557
+ this.textarea.focus();
3558
+ }
3559
+ if (this.options.autoResize) {
3560
+ if (!this.container.classList.contains("overtype-auto-resize")) {
3561
+ this._setupAutoResize();
3562
+ }
3563
+ } else {
3564
+ this.container.classList.remove("overtype-auto-resize");
3565
+ }
3566
+ if (this.options.toolbar && !this.toolbar) {
3567
+ this._createToolbar();
3568
+ } else if (!this.options.toolbar && this.toolbar) {
3569
+ this._cleanupToolbarListeners();
3570
+ this.toolbar.destroy();
3571
+ this.toolbar = null;
3572
+ }
3573
+ this.updatePreview();
3574
+ }
3575
+ /**
3576
+ * Update preview with parsed markdown
3577
+ */
3578
+ updatePreview() {
3579
+ const text = this.textarea.value;
3580
+ const cursorPos = this.textarea.selectionStart;
3581
+ const activeLine = this._getCurrentLine(text, cursorPos);
3582
+ const isPreviewMode = this.container.dataset.mode === "preview";
3583
+ const html = MarkdownParser.parse(text, activeLine, this.options.showActiveLineRaw, this.options.codeHighlighter, isPreviewMode);
3584
+ this.preview.innerHTML = html || '<span style="color: #808080;">Start typing...</span>';
3585
+ this._applyCodeBlockBackgrounds();
3586
+ if (this.options.showStats && this.statsBar) {
3587
+ this._updateStats();
3588
+ }
3589
+ if (this.options.onChange && this.initialized) {
3590
+ this.options.onChange(text, this);
3591
+ }
3592
+ }
3593
+ /**
3594
+ * Apply background styling to code blocks
3595
+ * @private
3596
+ */
3597
+ _applyCodeBlockBackgrounds() {
3598
+ const codeFences = this.preview.querySelectorAll(".code-fence");
3599
+ for (let i = 0; i < codeFences.length - 1; i += 2) {
3600
+ const openFence = codeFences[i];
3601
+ const closeFence = codeFences[i + 1];
3602
+ const openParent = openFence.parentElement;
3603
+ const closeParent = closeFence.parentElement;
3604
+ if (!openParent || !closeParent)
3605
+ continue;
3606
+ openFence.style.display = "block";
3607
+ closeFence.style.display = "block";
3608
+ openParent.classList.add("code-block-line");
3609
+ closeParent.classList.add("code-block-line");
3610
+ }
3611
+ }
3612
+ /**
3613
+ * Get current line number from cursor position
3614
+ * @private
3615
+ */
3616
+ _getCurrentLine(text, cursorPos) {
3617
+ const lines = text.substring(0, cursorPos).split("\n");
3618
+ return lines.length - 1;
3619
+ }
3620
+ /**
3621
+ * Handle input events
3622
+ * @private
3623
+ */
3624
+ handleInput(event) {
3625
+ this.updatePreview();
3626
+ }
3627
+ /**
3628
+ * Handle keydown events
3629
+ * @private
3630
+ */
3631
+ handleKeydown(event) {
3632
+ if (event.key === "Tab") {
3633
+ event.preventDefault();
3634
+ const start = this.textarea.selectionStart;
3635
+ const end = this.textarea.selectionEnd;
3636
+ const value = this.textarea.value;
3637
+ if (start !== end && event.shiftKey) {
3638
+ const before = value.substring(0, start);
3639
+ const selection = value.substring(start, end);
3640
+ const after = value.substring(end);
3641
+ const lines = selection.split("\n");
3642
+ const outdented = lines.map((line) => line.replace(/^ /, "")).join("\n");
3643
+ if (document.execCommand) {
3644
+ this.textarea.setSelectionRange(start, end);
3645
+ document.execCommand("insertText", false, outdented);
3646
+ } else {
3647
+ this.textarea.value = before + outdented + after;
3648
+ this.textarea.selectionStart = start;
3649
+ this.textarea.selectionEnd = start + outdented.length;
3650
+ }
3651
+ } else if (start !== end) {
3652
+ const before = value.substring(0, start);
3653
+ const selection = value.substring(start, end);
3654
+ const after = value.substring(end);
3655
+ const lines = selection.split("\n");
3656
+ const indented = lines.map((line) => " " + line).join("\n");
3657
+ if (document.execCommand) {
3658
+ this.textarea.setSelectionRange(start, end);
3659
+ document.execCommand("insertText", false, indented);
3660
+ } else {
3661
+ this.textarea.value = before + indented + after;
3662
+ this.textarea.selectionStart = start;
3663
+ this.textarea.selectionEnd = start + indented.length;
3664
+ }
3665
+ } else {
3666
+ if (document.execCommand) {
3667
+ document.execCommand("insertText", false, " ");
3668
+ } else {
3669
+ this.textarea.value = value.substring(0, start) + " " + value.substring(end);
3670
+ this.textarea.selectionStart = this.textarea.selectionEnd = start + 2;
3671
+ }
3672
+ }
3673
+ this.textarea.dispatchEvent(new Event("input", { bubbles: true }));
3674
+ return;
3675
+ }
3676
+ if (event.key === "Enter" && !event.shiftKey && !event.metaKey && !event.ctrlKey && this.options.smartLists) {
3677
+ if (this.handleSmartListContinuation()) {
3678
+ event.preventDefault();
3679
+ return;
3680
+ }
3681
+ }
3682
+ const handled = this.shortcuts.handleKeydown(event);
3683
+ if (!handled && this.options.onKeydown) {
3684
+ this.options.onKeydown(event, this);
3685
+ }
3686
+ }
3687
+ /**
3688
+ * Handle smart list continuation
3689
+ * @returns {boolean} Whether the event was handled
3690
+ */
3691
+ handleSmartListContinuation() {
3692
+ const textarea = this.textarea;
3693
+ const cursorPos = textarea.selectionStart;
3694
+ const context = MarkdownParser.getListContext(textarea.value, cursorPos);
3695
+ if (!context || !context.inList)
3696
+ return false;
3697
+ if (context.content.trim() === "" && cursorPos >= context.markerEndPos) {
3698
+ this.deleteListMarker(context);
3699
+ return true;
3700
+ }
3701
+ if (cursorPos > context.markerEndPos && cursorPos < context.lineEnd) {
3702
+ this.splitListItem(context, cursorPos);
3703
+ } else {
3704
+ this.insertNewListItem(context);
3705
+ }
3706
+ if (context.listType === "numbered") {
3707
+ this.scheduleNumberedListUpdate();
3708
+ }
3709
+ return true;
3710
+ }
3711
+ /**
3712
+ * Delete list marker and exit list
3713
+ * @private
3714
+ */
3715
+ deleteListMarker(context) {
3716
+ this.textarea.setSelectionRange(context.lineStart, context.markerEndPos);
3717
+ document.execCommand("delete");
3718
+ this.textarea.dispatchEvent(new Event("input", { bubbles: true }));
3719
+ }
3720
+ /**
3721
+ * Insert new list item
3722
+ * @private
3723
+ */
3724
+ insertNewListItem(context) {
3725
+ const newItem = MarkdownParser.createNewListItem(context);
3726
+ document.execCommand("insertText", false, "\n" + newItem);
3727
+ this.textarea.dispatchEvent(new Event("input", { bubbles: true }));
3728
+ }
3729
+ /**
3730
+ * Split list item at cursor position
3731
+ * @private
3732
+ */
3733
+ splitListItem(context, cursorPos) {
3734
+ const textAfterCursor = context.content.substring(cursorPos - context.markerEndPos);
3735
+ this.textarea.setSelectionRange(cursorPos, context.lineEnd);
3736
+ document.execCommand("delete");
3737
+ const newItem = MarkdownParser.createNewListItem(context);
3738
+ document.execCommand("insertText", false, "\n" + newItem + textAfterCursor);
3739
+ const newCursorPos = this.textarea.selectionStart - textAfterCursor.length;
3740
+ this.textarea.setSelectionRange(newCursorPos, newCursorPos);
3741
+ this.textarea.dispatchEvent(new Event("input", { bubbles: true }));
3742
+ }
3743
+ /**
3744
+ * Schedule numbered list renumbering
3745
+ * @private
3746
+ */
3747
+ scheduleNumberedListUpdate() {
3748
+ if (this.numberUpdateTimeout) {
3749
+ clearTimeout(this.numberUpdateTimeout);
3750
+ }
3751
+ this.numberUpdateTimeout = setTimeout(() => {
3752
+ this.updateNumberedLists();
3753
+ }, 10);
3754
+ }
3755
+ /**
3756
+ * Update/renumber all numbered lists
3757
+ * @private
3758
+ */
3759
+ updateNumberedLists() {
3760
+ const value = this.textarea.value;
3761
+ const cursorPos = this.textarea.selectionStart;
3762
+ const newValue = MarkdownParser.renumberLists(value);
3763
+ if (newValue !== value) {
3764
+ let offset = 0;
3765
+ const oldLines = value.split("\n");
3766
+ const newLines = newValue.split("\n");
3767
+ let charCount = 0;
3768
+ for (let i = 0; i < oldLines.length && charCount < cursorPos; i++) {
3769
+ if (oldLines[i] !== newLines[i]) {
3770
+ const diff = newLines[i].length - oldLines[i].length;
3771
+ if (charCount + oldLines[i].length < cursorPos) {
3772
+ offset += diff;
3773
+ }
3774
+ }
3775
+ charCount += oldLines[i].length + 1;
3776
+ }
3777
+ this.textarea.value = newValue;
3778
+ const newCursorPos = cursorPos + offset;
3779
+ this.textarea.setSelectionRange(newCursorPos, newCursorPos);
3780
+ this.textarea.dispatchEvent(new Event("input", { bubbles: true }));
3781
+ }
3782
+ }
3783
+ /**
3784
+ * Handle scroll events
3785
+ * @private
3786
+ */
3787
+ handleScroll(event) {
3788
+ this.preview.scrollTop = this.textarea.scrollTop;
3789
+ this.preview.scrollLeft = this.textarea.scrollLeft;
3790
+ }
3791
+ /**
3792
+ * Get editor content
3793
+ * @returns {string} Current markdown content
3794
+ */
3795
+ getValue() {
3796
+ return this.textarea.value;
3797
+ }
3798
+ /**
3799
+ * Set editor content
3800
+ * @param {string} value - Markdown content to set
3801
+ */
3802
+ setValue(value) {
3803
+ this.textarea.value = value;
3804
+ this.updatePreview();
3805
+ if (this.options.autoResize) {
3806
+ this._updateAutoHeight();
3807
+ }
3808
+ }
3809
+ /**
3810
+ * Get the rendered HTML of the current content
3811
+ * @param {Object} options - Rendering options
3812
+ * @param {boolean} options.cleanHTML - If true, removes syntax markers and OverType-specific classes
3813
+ * @returns {string} Rendered HTML
3814
+ */
3815
+ getRenderedHTML(options = {}) {
3816
+ const markdown = this.getValue();
3817
+ let html = MarkdownParser.parse(markdown, -1, false, this.options.codeHighlighter);
3818
+ if (options.cleanHTML) {
3819
+ html = html.replace(/<span class="syntax-marker[^"]*">.*?<\/span>/g, "");
3820
+ html = html.replace(/\sclass="(bullet-list|ordered-list|code-fence|hr-marker|blockquote|url-part)"/g, "");
3821
+ html = html.replace(/\sclass=""/g, "");
3822
+ }
3823
+ return html;
3824
+ }
3825
+ /**
3826
+ * Get the current preview element's HTML
3827
+ * This includes all syntax markers and OverType styling
3828
+ * @returns {string} Current preview HTML (as displayed)
3829
+ */
3830
+ getPreviewHTML() {
3831
+ return this.preview.innerHTML;
3832
+ }
3833
+ /**
3834
+ * Get clean HTML without any OverType-specific markup
3835
+ * Useful for exporting to other formats or storage
3836
+ * @returns {string} Clean HTML suitable for export
3837
+ */
3838
+ getCleanHTML() {
3839
+ return this.getRenderedHTML({ cleanHTML: true });
3840
+ }
3841
+ /**
3842
+ * Focus the editor
3843
+ */
3844
+ focus() {
3845
+ this.textarea.focus();
3846
+ }
3847
+ /**
3848
+ * Blur the editor
3849
+ */
3850
+ blur() {
3851
+ this.textarea.blur();
3852
+ }
3853
+ /**
3854
+ * Check if editor is initialized
3855
+ * @returns {boolean}
3856
+ */
3857
+ isInitialized() {
3858
+ return this.initialized;
3859
+ }
3860
+ /**
3861
+ * Re-initialize with new options
3862
+ * @param {Object} options - New options to apply
3863
+ */
3864
+ reinit(options = {}) {
3865
+ this.options = this._mergeOptions({ ...this.options, ...options });
3866
+ this._applyOptions();
3867
+ this.updatePreview();
3868
+ }
3869
+ /**
3870
+ * Set theme for this instance
3871
+ * @param {string|Object} theme - Theme name or custom theme object
3872
+ * @returns {this} Returns this for chaining
3873
+ */
3874
+ setTheme(theme) {
3875
+ this.instanceTheme = theme;
3876
+ const themeObj = typeof theme === "string" ? getTheme(theme) : theme;
3877
+ const themeName = typeof themeObj === "string" ? themeObj : themeObj.name;
3878
+ if (themeName) {
3879
+ this.container.setAttribute("data-theme", themeName);
3880
+ }
3881
+ if (themeObj && themeObj.colors) {
3882
+ const cssVars = themeToCSSVars(themeObj.colors);
3883
+ this.container.style.cssText += cssVars;
3884
+ }
3885
+ this.updatePreview();
3886
+ return this;
3887
+ }
3888
+ /**
3889
+ * Set instance-specific code highlighter
3890
+ * @param {Function|null} highlighter - Function that takes (code, language) and returns highlighted HTML
3891
+ */
3892
+ setCodeHighlighter(highlighter) {
3893
+ this.options.codeHighlighter = highlighter;
3894
+ this.updatePreview();
3895
+ }
3896
+ /**
3897
+ * Update stats bar
3898
+ * @private
3899
+ */
3900
+ _updateStats() {
3901
+ if (!this.statsBar)
3902
+ return;
3903
+ const value = this.textarea.value;
3904
+ const lines = value.split("\n");
3905
+ const chars = value.length;
3906
+ const words = value.split(/\s+/).filter((w) => w.length > 0).length;
3907
+ const selectionStart = this.textarea.selectionStart;
3908
+ const beforeCursor = value.substring(0, selectionStart);
3909
+ const linesBeforeCursor = beforeCursor.split("\n");
3910
+ const currentLine = linesBeforeCursor.length;
3911
+ const currentColumn = linesBeforeCursor[linesBeforeCursor.length - 1].length + 1;
3912
+ if (this.options.statsFormatter) {
3913
+ this.statsBar.innerHTML = this.options.statsFormatter({
3914
+ chars,
3915
+ words,
3916
+ lines: lines.length,
3917
+ line: currentLine,
3918
+ column: currentColumn
3919
+ });
3920
+ } else {
3921
+ this.statsBar.innerHTML = `
3922
+ <div class="overtype-stat">
3923
+ <span class="live-dot"></span>
3924
+ <span>${chars} chars, ${words} words, ${lines.length} lines</span>
3925
+ </div>
3926
+ <div class="overtype-stat">Line ${currentLine}, Col ${currentColumn}</div>
3927
+ `;
3928
+ }
3929
+ }
3930
+ /**
3931
+ * Setup auto-resize functionality
3932
+ * @private
3933
+ */
3934
+ _setupAutoResize() {
3935
+ this.container.classList.add("overtype-auto-resize");
3936
+ this.previousHeight = null;
3937
+ this._updateAutoHeight();
3938
+ this.textarea.addEventListener("input", () => this._updateAutoHeight());
3939
+ window.addEventListener("resize", () => this._updateAutoHeight());
3940
+ }
3941
+ /**
3942
+ * Update height based on scrollHeight
3943
+ * @private
3944
+ */
3945
+ _updateAutoHeight() {
3946
+ if (!this.options.autoResize)
3947
+ return;
3948
+ const textarea = this.textarea;
3949
+ const preview = this.preview;
3950
+ const wrapper = this.wrapper;
3951
+ const computed = window.getComputedStyle(textarea);
3952
+ const paddingTop = parseFloat(computed.paddingTop);
3953
+ const paddingBottom = parseFloat(computed.paddingBottom);
3954
+ const scrollTop = textarea.scrollTop;
3955
+ textarea.style.setProperty("height", "auto", "important");
3956
+ let newHeight = textarea.scrollHeight;
3957
+ if (this.options.minHeight) {
3958
+ const minHeight = parseInt(this.options.minHeight);
3959
+ newHeight = Math.max(newHeight, minHeight);
3960
+ }
3961
+ let overflow = "hidden";
3962
+ if (this.options.maxHeight) {
3963
+ const maxHeight = parseInt(this.options.maxHeight);
3964
+ if (newHeight > maxHeight) {
3965
+ newHeight = maxHeight;
3966
+ overflow = "auto";
3967
+ }
3968
+ }
3969
+ const heightPx = newHeight + "px";
3970
+ textarea.style.setProperty("height", heightPx, "important");
3971
+ textarea.style.setProperty("overflow-y", overflow, "important");
3972
+ preview.style.setProperty("height", heightPx, "important");
3973
+ preview.style.setProperty("overflow-y", overflow, "important");
3974
+ wrapper.style.setProperty("height", heightPx, "important");
3975
+ textarea.scrollTop = scrollTop;
3976
+ preview.scrollTop = scrollTop;
3977
+ if (this.previousHeight !== newHeight) {
3978
+ this.previousHeight = newHeight;
3979
+ }
3980
+ }
3981
+ /**
3982
+ * Show or hide stats bar
3983
+ * @param {boolean} show - Whether to show stats
3984
+ */
3985
+ showStats(show) {
3986
+ this.options.showStats = show;
3987
+ if (show && !this.statsBar) {
3988
+ this.statsBar = document.createElement("div");
3989
+ this.statsBar.className = "overtype-stats";
3990
+ this.container.appendChild(this.statsBar);
3991
+ this._updateStats();
3992
+ } else if (!show && this.statsBar) {
3993
+ this.statsBar.remove();
3994
+ this.statsBar = null;
3995
+ }
3996
+ }
3997
+ /**
3998
+ * Show normal edit mode (overlay with markdown preview)
3999
+ * @returns {this} Returns this for chaining
4000
+ */
4001
+ showNormalEditMode() {
4002
+ this.container.dataset.mode = "normal";
4003
+ requestAnimationFrame(() => {
4004
+ this.textarea.scrollTop = this.preview.scrollTop;
4005
+ this.textarea.scrollLeft = this.preview.scrollLeft;
4006
+ });
4007
+ return this;
4008
+ }
4009
+ /**
4010
+ * Show plain textarea mode (no overlay)
4011
+ * @returns {this} Returns this for chaining
4012
+ */
4013
+ showPlainTextarea() {
4014
+ this.container.dataset.mode = "plain";
4015
+ if (this.toolbar) {
4016
+ const toggleBtn = this.container.querySelector('[data-action="toggle-plain"]');
4017
+ if (toggleBtn) {
4018
+ toggleBtn.classList.remove("active");
4019
+ toggleBtn.title = "Show markdown preview";
4020
+ }
4021
+ }
4022
+ return this;
4023
+ }
4024
+ /**
4025
+ * Show preview mode (read-only view)
4026
+ * @returns {this} Returns this for chaining
4027
+ */
4028
+ showPreviewMode() {
4029
+ this.container.dataset.mode = "preview";
4030
+ return this;
4031
+ }
4032
+ /**
4033
+ * Destroy the editor instance
4034
+ */
4035
+ destroy() {
4036
+ this.element.overTypeInstance = null;
4037
+ _OverType.instances.delete(this.element);
4038
+ if (this.shortcuts) {
4039
+ this.shortcuts.destroy();
4040
+ }
4041
+ if (this.wrapper) {
4042
+ const content = this.getValue();
4043
+ this.wrapper.remove();
4044
+ this.element.textContent = content;
4045
+ }
4046
+ this.initialized = false;
4047
+ }
4048
+ // ===== Static Methods =====
4049
+ /**
4050
+ * Initialize multiple editors (static convenience method)
4051
+ * @param {string|Element|NodeList|Array} target - Target element(s)
4052
+ * @param {Object} options - Configuration options
4053
+ * @returns {Array} Array of OverType instances
4054
+ */
4055
+ static init(target, options = {}) {
4056
+ return new _OverType(target, options);
4057
+ }
4058
+ /**
4059
+ * Get instance from element
4060
+ * @param {Element} element - DOM element
4061
+ * @returns {OverType|null} OverType instance or null
4062
+ */
4063
+ static getInstance(element) {
4064
+ return element.overTypeInstance || _OverType.instances.get(element) || null;
4065
+ }
4066
+ /**
4067
+ * Destroy all instances
4068
+ */
4069
+ static destroyAll() {
4070
+ const elements = document.querySelectorAll("[data-overtype-instance]");
4071
+ elements.forEach((element) => {
4072
+ const instance = _OverType.getInstance(element);
4073
+ if (instance) {
4074
+ instance.destroy();
4075
+ }
4076
+ });
4077
+ }
4078
+ /**
4079
+ * Inject styles into the document
4080
+ * @param {boolean} force - Force re-injection
4081
+ */
4082
+ static injectStyles(force = false) {
4083
+ if (_OverType.stylesInjected && !force)
4084
+ return;
4085
+ const existing = document.querySelector("style.overtype-styles");
4086
+ if (existing) {
4087
+ existing.remove();
4088
+ }
4089
+ const theme = _OverType.currentTheme || solar;
4090
+ const styles = generateStyles({ theme });
4091
+ const styleEl = document.createElement("style");
4092
+ styleEl.className = "overtype-styles";
4093
+ styleEl.textContent = styles;
4094
+ document.head.appendChild(styleEl);
4095
+ _OverType.stylesInjected = true;
4096
+ }
4097
+ /**
4098
+ * Set global theme for all OverType instances
4099
+ * @param {string|Object} theme - Theme name or custom theme object
4100
+ * @param {Object} customColors - Optional color overrides
4101
+ */
4102
+ static setTheme(theme, customColors = null) {
4103
+ let themeObj = typeof theme === "string" ? getTheme(theme) : theme;
4104
+ if (customColors) {
4105
+ themeObj = mergeTheme(themeObj, customColors);
4106
+ }
4107
+ _OverType.currentTheme = themeObj;
4108
+ _OverType.injectStyles(true);
4109
+ document.querySelectorAll(".overtype-container").forEach((container) => {
4110
+ const themeName2 = typeof themeObj === "string" ? themeObj : themeObj.name;
4111
+ if (themeName2) {
4112
+ container.setAttribute("data-theme", themeName2);
4113
+ }
4114
+ });
4115
+ document.querySelectorAll(".overtype-wrapper").forEach((wrapper) => {
4116
+ if (!wrapper.closest(".overtype-container")) {
4117
+ const themeName2 = typeof themeObj === "string" ? themeObj : themeObj.name;
4118
+ if (themeName2) {
4119
+ wrapper.setAttribute("data-theme", themeName2);
4120
+ }
4121
+ }
4122
+ const instance = wrapper._instance;
4123
+ if (instance) {
4124
+ instance.updatePreview();
4125
+ }
4126
+ });
4127
+ const themeName = typeof themeObj === "string" ? themeObj : themeObj.name;
4128
+ document.querySelectorAll("overtype-editor").forEach((webComponent) => {
4129
+ if (themeName && typeof webComponent.setAttribute === "function") {
4130
+ webComponent.setAttribute("theme", themeName);
4131
+ }
4132
+ if (typeof webComponent.refreshTheme === "function") {
4133
+ webComponent.refreshTheme();
4134
+ }
4135
+ });
4136
+ }
4137
+ /**
4138
+ * Set global code highlighter for all OverType instances
4139
+ * @param {Function|null} highlighter - Function that takes (code, language) and returns highlighted HTML
4140
+ */
4141
+ static setCodeHighlighter(highlighter) {
4142
+ MarkdownParser.setCodeHighlighter(highlighter);
4143
+ document.querySelectorAll(".overtype-wrapper").forEach((wrapper) => {
4144
+ const instance = wrapper._instance;
4145
+ if (instance && instance.updatePreview) {
4146
+ instance.updatePreview();
4147
+ }
4148
+ });
4149
+ document.querySelectorAll("overtype-editor").forEach((webComponent) => {
4150
+ if (typeof webComponent.getEditor === "function") {
4151
+ const instance = webComponent.getEditor();
4152
+ if (instance && instance.updatePreview) {
4153
+ instance.updatePreview();
4154
+ }
4155
+ }
4156
+ });
4157
+ }
4158
+ /**
4159
+ * Initialize global event listeners
4160
+ */
4161
+ static initGlobalListeners() {
4162
+ if (_OverType.globalListenersInitialized)
4163
+ return;
4164
+ document.addEventListener("input", (e) => {
4165
+ if (e.target && e.target.classList && e.target.classList.contains("overtype-input")) {
4166
+ const wrapper = e.target.closest(".overtype-wrapper");
4167
+ const instance = wrapper == null ? void 0 : wrapper._instance;
4168
+ if (instance)
4169
+ instance.handleInput(e);
4170
+ }
4171
+ });
4172
+ document.addEventListener("keydown", (e) => {
4173
+ if (e.target && e.target.classList && e.target.classList.contains("overtype-input")) {
4174
+ const wrapper = e.target.closest(".overtype-wrapper");
4175
+ const instance = wrapper == null ? void 0 : wrapper._instance;
4176
+ if (instance)
4177
+ instance.handleKeydown(e);
4178
+ }
4179
+ });
4180
+ document.addEventListener("scroll", (e) => {
4181
+ if (e.target && e.target.classList && e.target.classList.contains("overtype-input")) {
4182
+ const wrapper = e.target.closest(".overtype-wrapper");
4183
+ const instance = wrapper == null ? void 0 : wrapper._instance;
4184
+ if (instance)
4185
+ instance.handleScroll(e);
4186
+ }
4187
+ }, true);
4188
+ document.addEventListener("selectionchange", (e) => {
4189
+ const activeElement = document.activeElement;
4190
+ if (activeElement && activeElement.classList.contains("overtype-input")) {
4191
+ const wrapper = activeElement.closest(".overtype-wrapper");
4192
+ const instance = wrapper == null ? void 0 : wrapper._instance;
4193
+ if (instance) {
4194
+ if (instance.options.showStats && instance.statsBar) {
4195
+ instance._updateStats();
4196
+ }
4197
+ clearTimeout(instance._selectionTimeout);
4198
+ instance._selectionTimeout = setTimeout(() => {
4199
+ instance.updatePreview();
4200
+ }, 50);
4201
+ }
4202
+ }
4203
+ });
4204
+ _OverType.globalListenersInitialized = true;
4205
+ }
4206
+ };
4207
+ // Static properties
4208
+ __publicField(_OverType, "instances", /* @__PURE__ */ new WeakMap());
4209
+ __publicField(_OverType, "stylesInjected", false);
4210
+ __publicField(_OverType, "globalListenersInitialized", false);
4211
+ __publicField(_OverType, "instanceCount", 0);
4212
+ var OverType = _OverType;
4213
+ OverType.MarkdownParser = MarkdownParser;
4214
+ OverType.ShortcutsManager = ShortcutsManager;
4215
+ OverType.themes = { solar, cave: getTheme("cave") };
4216
+ OverType.getTheme = getTheme;
4217
+ OverType.currentTheme = solar;
4218
+ var overtype_default = OverType;
4219
+
4220
+ // src/overtype-webcomponent.js
4221
+ var CONTAINER_CLASS = "overtype-webcomponent-container";
4222
+ var DEFAULT_PLACEHOLDER = "Start typing...";
4223
+ var OBSERVED_ATTRIBUTES = [
4224
+ "value",
4225
+ "theme",
4226
+ "toolbar",
4227
+ "height",
4228
+ "min-height",
4229
+ "max-height",
4230
+ "placeholder",
4231
+ "font-size",
4232
+ "line-height",
4233
+ "padding",
4234
+ "auto-resize",
4235
+ "autofocus",
4236
+ "show-stats",
4237
+ "smart-lists",
4238
+ "readonly"
4239
+ ];
4240
+ var OverTypeEditor = class extends HTMLElement {
4241
+ constructor() {
4242
+ super();
4243
+ this.attachShadow({ mode: "open" });
4244
+ this._editor = null;
4245
+ this._initialized = false;
4246
+ this._pendingOptions = {};
4247
+ this._styleVersion = 0;
4248
+ this._baseStyleElement = null;
4249
+ this._selectionChangeHandler = null;
4250
+ this._isConnected = false;
4251
+ this._handleChange = this._handleChange.bind(this);
4252
+ this._handleKeydown = this._handleKeydown.bind(this);
4253
+ }
4254
+ /**
4255
+ * Decode common escape sequences from attribute string values
4256
+ * @private
4257
+ * @param {string|null|undefined} str
4258
+ * @returns {string}
4259
+ */
4260
+ _decodeValue(str) {
4261
+ if (typeof str !== "string")
4262
+ return "";
4263
+ return str.replace(/\\r/g, "\r").replace(/\\n/g, "\n").replace(/\\t/g, " ");
4264
+ }
4265
+ // Note: _encodeValue removed as it's currently unused
4266
+ // Can be re-added if needed for future attribute encoding
4267
+ /**
4268
+ * Define observed attributes for reactive updates
4269
+ */
4270
+ static get observedAttributes() {
4271
+ return OBSERVED_ATTRIBUTES;
4272
+ }
4273
+ /**
4274
+ * Component connected to DOM - initialize editor
4275
+ */
4276
+ connectedCallback() {
4277
+ this._isConnected = true;
4278
+ this._initializeEditor();
4279
+ }
4280
+ /**
4281
+ * Component disconnected from DOM - cleanup
4282
+ */
4283
+ disconnectedCallback() {
4284
+ this._isConnected = false;
4285
+ this._cleanup();
4286
+ }
4287
+ /**
4288
+ * Attribute changed callback - update editor options
4289
+ */
4290
+ attributeChangedCallback(name, oldValue, newValue) {
4291
+ if (oldValue === newValue)
4292
+ return;
4293
+ if (this._silentUpdate)
4294
+ return;
4295
+ if (!this._initialized) {
4296
+ this._pendingOptions[name] = newValue;
4297
+ return;
4298
+ }
4299
+ this._updateOption(name, newValue);
4300
+ }
4301
+ /**
4302
+ * Initialize the OverType editor inside shadow DOM
4303
+ * @private
4304
+ */
4305
+ _initializeEditor() {
4306
+ if (this._initialized || !this._isConnected)
4307
+ return;
4308
+ try {
4309
+ const container = document.createElement("div");
4310
+ container.className = CONTAINER_CLASS;
4311
+ const height = this.getAttribute("height");
4312
+ const minHeight = this.getAttribute("min-height");
4313
+ const maxHeight = this.getAttribute("max-height");
4314
+ if (height)
4315
+ container.style.height = height;
4316
+ if (minHeight)
4317
+ container.style.minHeight = minHeight;
4318
+ if (maxHeight)
4319
+ container.style.maxHeight = maxHeight;
4320
+ this._injectStyles();
4321
+ this.shadowRoot.appendChild(container);
4322
+ const options = this._getOptionsFromAttributes();
4323
+ const editorInstances = new overtype_default(container, options);
4324
+ this._editor = editorInstances[0];
4325
+ this._initialized = true;
4326
+ if (this._editor && this._editor.textarea) {
4327
+ this._editor.textarea.addEventListener("scroll", () => {
4328
+ if (this._editor && this._editor.preview && this._editor.textarea) {
4329
+ this._editor.preview.scrollTop = this._editor.textarea.scrollTop;
4330
+ this._editor.preview.scrollLeft = this._editor.textarea.scrollLeft;
4331
+ }
4332
+ });
4333
+ this._editor.textarea.addEventListener("input", (e) => {
4334
+ if (this._editor && this._editor.handleInput) {
4335
+ this._editor.handleInput(e);
4336
+ }
4337
+ });
4338
+ this._editor.textarea.addEventListener("keydown", (e) => {
4339
+ if (this._editor && this._editor.handleKeydown) {
4340
+ this._editor.handleKeydown(e);
4341
+ }
4342
+ });
4343
+ this._selectionChangeHandler = () => {
4344
+ if (document.activeElement === this) {
4345
+ const shadowActiveElement = this.shadowRoot.activeElement;
4346
+ if (shadowActiveElement && shadowActiveElement === this._editor.textarea) {
4347
+ if (this._editor.options.showStats && this._editor.statsBar) {
4348
+ this._editor._updateStats();
4349
+ }
4350
+ if (this._editor.linkTooltip && this._editor.linkTooltip.checkCursorPosition) {
4351
+ this._editor.linkTooltip.checkCursorPosition();
4352
+ }
4353
+ }
4354
+ }
4355
+ };
4356
+ document.addEventListener("selectionchange", this._selectionChangeHandler);
4357
+ }
4358
+ this._applyPendingOptions();
4359
+ this._dispatchEvent("ready", { editor: this._editor });
4360
+ } catch (error) {
4361
+ const message = error && error.message ? error.message : String(error);
4362
+ console.warn("OverType Web Component initialization failed:", message);
4363
+ this._dispatchEvent("error", { error: { message } });
4364
+ }
4365
+ }
4366
+ /**
4367
+ * Inject styles into shadow DOM for complete isolation
4368
+ * @private
4369
+ */
4370
+ _injectStyles() {
4371
+ const style = document.createElement("style");
4372
+ const themeAttr = this.getAttribute("theme") || "solar";
4373
+ const theme = getTheme(themeAttr);
4374
+ const options = this._getOptionsFromAttributes();
4375
+ const styles = generateStyles({ ...options, theme });
4376
+ const webComponentStyles = `
4377
+ /* Web Component Host Styles */
4378
+ :host {
4379
+ display: block;
4380
+ position: relative;
4381
+ width: 100%;
4382
+ height: 100%;
4383
+ contain: layout style;
4384
+ }
4385
+
4386
+ .overtype-webcomponent-container {
4387
+ width: 100%;
4388
+ height: 100%;
4389
+ position: relative;
4390
+ }
4391
+
4392
+ /* Override container grid layout for web component */
4393
+ .overtype-container {
4394
+ height: 100% !important;
4395
+ }
4396
+ `;
4397
+ this._styleVersion += 1;
4398
+ const versionBanner = `
4399
+ /* overtype-webcomponent styles v${this._styleVersion} */
4400
+ `;
4401
+ style.textContent = versionBanner + styles + webComponentStyles;
4402
+ this._baseStyleElement = style;
4403
+ this.shadowRoot.appendChild(style);
4404
+ }
4405
+ /**
4406
+ * Extract options from HTML attributes
4407
+ * @private
4408
+ * @returns {Object} OverType options object
4409
+ */
4410
+ _getOptionsFromAttributes() {
4411
+ const options = {
4412
+ // Allow authoring multi-line content via escaped sequences in attributes
4413
+ // and fall back to light DOM text content if attribute is absent
4414
+ value: this.getAttribute("value") !== null ? this._decodeValue(this.getAttribute("value")) : (this.textContent || "").trim(),
4415
+ placeholder: this.getAttribute("placeholder") || DEFAULT_PLACEHOLDER,
4416
+ toolbar: this.hasAttribute("toolbar"),
4417
+ autofocus: this.hasAttribute("autofocus"),
4418
+ autoResize: this.hasAttribute("auto-resize"),
4419
+ showStats: this.hasAttribute("show-stats"),
4420
+ smartLists: !this.hasAttribute("smart-lists") || this.getAttribute("smart-lists") !== "false",
4421
+ onChange: this._handleChange,
4422
+ onKeydown: this._handleKeydown
4423
+ };
4424
+ const fontSize = this.getAttribute("font-size");
4425
+ if (fontSize)
4426
+ options.fontSize = fontSize;
4427
+ const lineHeight = this.getAttribute("line-height");
4428
+ if (lineHeight)
4429
+ options.lineHeight = parseFloat(lineHeight) || 1.6;
4430
+ const padding = this.getAttribute("padding");
4431
+ if (padding)
4432
+ options.padding = padding;
4433
+ const minHeight = this.getAttribute("min-height");
4434
+ if (minHeight)
4435
+ options.minHeight = minHeight;
4436
+ const maxHeight = this.getAttribute("max-height");
4437
+ if (maxHeight)
4438
+ options.maxHeight = maxHeight;
4439
+ return options;
4440
+ }
4441
+ /**
4442
+ * Apply pending option changes after initialization
4443
+ * @private
4444
+ */
4445
+ _applyPendingOptions() {
4446
+ for (const [attr, value] of Object.entries(this._pendingOptions)) {
4447
+ this._updateOption(attr, value);
4448
+ }
4449
+ this._pendingOptions = {};
4450
+ }
4451
+ /**
4452
+ * Update a single editor option
4453
+ * @private
4454
+ * @param {string} attribute - Attribute name
4455
+ * @param {string} value - New value
4456
+ */
4457
+ _updateOption(attribute, value) {
4458
+ if (!this._editor)
4459
+ return;
4460
+ switch (attribute) {
4461
+ case "value":
4462
+ {
4463
+ const decoded = this._decodeValue(value);
4464
+ if (this._editor.getValue() !== decoded) {
4465
+ this._editor.setValue(decoded || "");
4466
+ }
4467
+ }
4468
+ break;
4469
+ case "theme":
4470
+ this._reinjectStyles();
4471
+ break;
4472
+ case "placeholder":
4473
+ if (this._editor.textarea) {
4474
+ this._editor.textarea.placeholder = value || "";
4475
+ }
4476
+ break;
4477
+ case "readonly":
4478
+ if (this._editor.textarea) {
4479
+ this._editor.textarea.readOnly = this.hasAttribute("readonly");
4480
+ }
4481
+ break;
4482
+ case "height":
4483
+ case "min-height":
4484
+ case "max-height":
4485
+ this._updateContainerHeight();
4486
+ break;
4487
+ case "toolbar":
4488
+ if (!!this.hasAttribute("toolbar") === !!this._editor.options.toolbar)
4489
+ return;
4490
+ this._reinitializeEditor();
4491
+ break;
4492
+ case "auto-resize":
4493
+ if (!!this.hasAttribute("auto-resize") === !!this._editor.options.autoResize)
4494
+ return;
4495
+ this._reinitializeEditor();
4496
+ break;
4497
+ case "show-stats":
4498
+ if (!!this.hasAttribute("show-stats") === !!this._editor.options.showStats)
4499
+ return;
4500
+ this._reinitializeEditor();
4501
+ break;
4502
+ case "font-size": {
4503
+ if (this._updateFontSize(value)) {
4504
+ this._reinjectStyles();
4505
+ }
4506
+ break;
4507
+ }
4508
+ case "line-height": {
4509
+ if (this._updateLineHeight(value)) {
4510
+ this._reinjectStyles();
4511
+ }
4512
+ break;
4513
+ }
4514
+ case "padding":
4515
+ this._reinjectStyles();
4516
+ break;
4517
+ case "smart-lists": {
4518
+ const newSmartLists = !this.hasAttribute("smart-lists") || this.getAttribute("smart-lists") !== "false";
4519
+ if (!!this._editor.options.smartLists === !!newSmartLists)
4520
+ return;
4521
+ this._reinitializeEditor();
4522
+ break;
4523
+ }
4524
+ }
4525
+ }
4526
+ /**
4527
+ * Update container height from attributes
4528
+ * @private
4529
+ */
4530
+ _updateContainerHeight() {
4531
+ const container = this.shadowRoot.querySelector(`.${CONTAINER_CLASS}`);
4532
+ if (!container)
4533
+ return;
4534
+ const height = this.getAttribute("height");
4535
+ const minHeight = this.getAttribute("min-height");
4536
+ const maxHeight = this.getAttribute("max-height");
4537
+ container.style.height = height || "";
4538
+ container.style.minHeight = minHeight || "";
4539
+ container.style.maxHeight = maxHeight || "";
4540
+ }
4541
+ /**
4542
+ * Update font size efficiently
4543
+ * @private
4544
+ * @param {string} value - New font size value
4545
+ * @returns {boolean} True if direct update succeeded
4546
+ */
4547
+ _updateFontSize(value) {
4548
+ if (this._editor && this._editor.wrapper) {
4549
+ this._editor.options.fontSize = value || "";
4550
+ this._editor.wrapper.style.setProperty("--instance-font-size", this._editor.options.fontSize);
4551
+ this._editor.updatePreview();
4552
+ return true;
4553
+ }
4554
+ return false;
4555
+ }
4556
+ /**
4557
+ * Update line height efficiently
4558
+ * @private
4559
+ * @param {string} value - New line height value
4560
+ * @returns {boolean} True if direct update succeeded
4561
+ */
4562
+ _updateLineHeight(value) {
4563
+ if (this._editor && this._editor.wrapper) {
4564
+ const numeric = parseFloat(value);
4565
+ const lineHeight = Number.isFinite(numeric) ? numeric : this._editor.options.lineHeight;
4566
+ this._editor.options.lineHeight = lineHeight;
4567
+ this._editor.wrapper.style.setProperty("--instance-line-height", String(lineHeight));
4568
+ this._editor.updatePreview();
4569
+ return true;
4570
+ }
4571
+ return false;
4572
+ }
4573
+ /**
4574
+ * Re-inject styles (useful for theme changes)
4575
+ * @private
4576
+ */
4577
+ _reinjectStyles() {
4578
+ if (this._baseStyleElement && this._baseStyleElement.parentNode) {
4579
+ this._baseStyleElement.remove();
4580
+ }
4581
+ this._injectStyles();
4582
+ }
4583
+ /**
4584
+ * Reinitialize the entire editor (for major option changes)
4585
+ * @private
4586
+ */
4587
+ _reinitializeEditor() {
4588
+ const currentValue = this._editor ? this._editor.getValue() : "";
4589
+ this._cleanup();
4590
+ this._initialized = false;
4591
+ this.shadowRoot.innerHTML = "";
4592
+ if (currentValue && !this.getAttribute("value")) {
4593
+ this.setAttribute("value", currentValue);
4594
+ }
4595
+ this._initializeEditor();
4596
+ }
4597
+ /**
4598
+ * Handle content changes from OverType
4599
+ * @private
4600
+ * @param {string} value - New editor value
4601
+ */
4602
+ _handleChange(value) {
4603
+ this._updateValueAttribute(value);
4604
+ if (!this._initialized || !this._editor) {
4605
+ return;
4606
+ }
4607
+ this._dispatchEvent("change", {
4608
+ value,
4609
+ editor: this._editor
4610
+ });
4611
+ }
4612
+ /**
4613
+ * Handle keydown events from OverType
4614
+ * @private
4615
+ * @param {KeyboardEvent} event - Keyboard event
4616
+ */
4617
+ _handleKeydown(event) {
4618
+ this._dispatchEvent("keydown", {
4619
+ event,
4620
+ editor: this._editor
4621
+ });
4622
+ }
4623
+ /**
4624
+ * Update value attribute without triggering observer
4625
+ * @private
4626
+ * @param {string} value - New value
4627
+ */
4628
+ _updateValueAttribute(value) {
4629
+ const currentAttrValue = this.getAttribute("value");
4630
+ if (currentAttrValue !== value) {
4631
+ this._silentUpdate = true;
4632
+ this.setAttribute("value", value);
4633
+ this._silentUpdate = false;
4634
+ }
4635
+ }
4636
+ /**
4637
+ * Dispatch custom events
4638
+ * @private
4639
+ * @param {string} eventName - Event name
4640
+ * @param {Object} detail - Event detail
4641
+ */
4642
+ _dispatchEvent(eventName, detail = {}) {
4643
+ const event = new CustomEvent(eventName, {
4644
+ detail,
4645
+ bubbles: true,
4646
+ composed: true
4647
+ });
4648
+ this.dispatchEvent(event);
4649
+ }
4650
+ /**
4651
+ * Cleanup editor and remove listeners
4652
+ * @private
4653
+ */
4654
+ _cleanup() {
4655
+ if (this._selectionChangeHandler) {
4656
+ document.removeEventListener("selectionchange", this._selectionChangeHandler);
4657
+ this._selectionChangeHandler = null;
4658
+ }
4659
+ if (this._editor && typeof this._editor.destroy === "function") {
4660
+ this._editor.destroy();
4661
+ }
4662
+ this._editor = null;
4663
+ this._initialized = false;
4664
+ if (this.shadowRoot) {
4665
+ this.shadowRoot.innerHTML = "";
4666
+ }
4667
+ }
4668
+ // ===== PUBLIC API METHODS =====
4669
+ /**
4670
+ * Refresh theme styles (useful when theme object is updated without changing theme name)
4671
+ * @public
4672
+ */
4673
+ refreshTheme() {
4674
+ if (this._initialized) {
4675
+ this._reinjectStyles();
4676
+ }
4677
+ }
4678
+ /**
4679
+ * Get current editor value
4680
+ * @returns {string} Current markdown content
4681
+ */
4682
+ getValue() {
4683
+ return this._editor ? this._editor.getValue() : this.getAttribute("value") || "";
4684
+ }
4685
+ /**
4686
+ * Set editor value
4687
+ * @param {string} value - New markdown content
4688
+ */
4689
+ setValue(value) {
4690
+ if (this._editor) {
4691
+ this._editor.setValue(value);
4692
+ } else {
4693
+ this.setAttribute("value", value);
4694
+ }
4695
+ }
4696
+ /**
4697
+ * Get rendered HTML
4698
+ * @returns {string} Rendered HTML
4699
+ */
4700
+ getHTML() {
4701
+ return this._editor ? this._editor.getRenderedHTML(false) : "";
4702
+ }
4703
+ /**
4704
+ * Insert text at cursor position
4705
+ * @param {string} text - Text to insert
4706
+ */
4707
+ insertText(text) {
4708
+ if (!this._editor || typeof text !== "string") {
4709
+ return;
4710
+ }
4711
+ this._editor.insertText(text);
4712
+ }
4713
+ /**
4714
+ * Focus the editor
4715
+ */
4716
+ focus() {
4717
+ if (this._editor && this._editor.textarea) {
4718
+ this._editor.textarea.focus();
4719
+ }
4720
+ }
4721
+ /**
4722
+ * Blur the editor
4723
+ */
4724
+ blur() {
4725
+ if (this._editor && this._editor.textarea) {
4726
+ this._editor.textarea.blur();
4727
+ }
4728
+ }
4729
+ /**
4730
+ * Get editor statistics
4731
+ * @returns {Object} Statistics object
4732
+ */
4733
+ getStats() {
4734
+ if (!this._editor || !this._editor.textarea)
4735
+ return null;
4736
+ const value = this._editor.textarea.value;
4737
+ const lines = value.split("\n");
4738
+ const chars = value.length;
4739
+ const words = value.split(/\s+/).filter((w) => w.length > 0).length;
4740
+ const selectionStart = this._editor.textarea.selectionStart;
4741
+ const beforeCursor = value.substring(0, selectionStart);
4742
+ const linesBefore = beforeCursor.split("\n");
4743
+ const currentLine = linesBefore.length;
4744
+ const currentColumn = linesBefore[linesBefore.length - 1].length + 1;
4745
+ return {
4746
+ characters: chars,
4747
+ words,
4748
+ lines: lines.length,
4749
+ line: currentLine,
4750
+ column: currentColumn
4751
+ };
4752
+ }
4753
+ /**
4754
+ * Check if editor is ready
4755
+ * @returns {boolean} True if editor is initialized
4756
+ */
4757
+ isReady() {
4758
+ return this._initialized && this._editor !== null;
4759
+ }
4760
+ /**
4761
+ * Get the internal OverType instance
4762
+ * @returns {OverType} The OverType editor instance
4763
+ */
4764
+ getEditor() {
4765
+ return this._editor;
4766
+ }
4767
+ };
4768
+ if (!customElements.get("overtype-editor")) {
4769
+ customElements.define("overtype-editor", OverTypeEditor);
4770
+ }
4771
+ var overtype_webcomponent_default = OverTypeEditor;
4772
+ return __toCommonJS(overtype_webcomponent_exports);
4773
+ })();
4774
+ /**
4775
+ * OverType - A lightweight markdown editor library with perfect WYSIWYG alignment
4776
+ * @version 1.0.0
4777
+ * @license MIT
4778
+ */
4779
+ /**
4780
+ * OverType Web Component
4781
+ * A custom element wrapper for the OverType markdown editor with Shadow DOM isolation
4782
+ * @version 1.0.0
4783
+ * @license MIT
4784
+ */
4785
+ //# sourceMappingURL=overtype-webcomponent.js.map