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.
package/dist/overtype.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * OverType v1.2.7
2
+ * OverType v2.0.0
3
3
  * A lightweight markdown editor library with perfect WYSIWYG alignment
4
4
  * @license MIT
5
5
  * @author Demo User
@@ -32,7 +32,9 @@ var __publicField = (obj, key, value) => {
32
32
  var overtype_exports = {};
33
33
  __export(overtype_exports, {
34
34
  OverType: () => OverType,
35
- default: () => overtype_default
35
+ default: () => overtype_default,
36
+ defaultToolbarButtons: () => defaultToolbarButtons,
37
+ toolbarButtons: () => toolbarButtons
36
38
  });
37
39
  module.exports = __toCommonJS(overtype_exports);
38
40
 
@@ -44,6 +46,13 @@ var MarkdownParser = class {
44
46
  static resetLinkIndex() {
45
47
  this.linkIndex = 0;
46
48
  }
49
+ /**
50
+ * Set global code highlighter function
51
+ * @param {Function|null} highlighter - Function that takes (code, language) and returns highlighted HTML
52
+ */
53
+ static setCodeHighlighter(highlighter) {
54
+ this.codeHighlighter = highlighter;
55
+ }
47
56
  /**
48
57
  * Escape HTML special characters
49
58
  * @param {string} text - Raw text to escape
@@ -112,6 +121,22 @@ var MarkdownParser = class {
112
121
  return `${indent}<li class="bullet-list"><span class="syntax-marker">${marker} </span>${content}</li>`;
113
122
  });
114
123
  }
124
+ /**
125
+ * Parse task lists (GitHub Flavored Markdown checkboxes)
126
+ * @param {string} html - HTML line to parse
127
+ * @param {boolean} isPreviewMode - Whether to render actual checkboxes (preview) or keep syntax visible (normal)
128
+ * @returns {string} Parsed task list item
129
+ */
130
+ static parseTaskList(html, isPreviewMode = false) {
131
+ return html.replace(/^((?:&nbsp;)*)-\s+\[([ xX])\]\s+(.+)$/, (match, indent, checked, content) => {
132
+ if (isPreviewMode) {
133
+ const isChecked = checked.toLowerCase() === "x";
134
+ return `${indent}<li class="task-list"><input type="checkbox" disabled ${isChecked ? "checked" : ""}> ${content}</li>`;
135
+ } else {
136
+ return `${indent}<li class="task-list"><span class="syntax-marker">- [${checked}] </span>${content}</li>`;
137
+ }
138
+ });
139
+ }
115
140
  /**
116
141
  * Parse numbered lists
117
142
  * @param {string} html - HTML line to parse
@@ -301,7 +326,7 @@ var MarkdownParser = class {
301
326
  processedLinkText = this.parseItalic(processedLinkText);
302
327
  const anchorName = `--link-${this.linkIndex++}`;
303
328
  const safeUrl = this.sanitizeUrl(sanctuary.url);
304
- replacement = `<a href="${safeUrl}" style="anchor-name: ${anchorName}"><span class="syntax-marker">[</span>${processedLinkText}<span class="syntax-marker url-part">](${this.escapeHtml(sanctuary.url)})</span></a>`;
329
+ replacement = `<a href="${safeUrl}" style="anchor-name: ${anchorName}"><span class="syntax-marker">[</span>${processedLinkText}<span class="syntax-marker url-part">](${sanctuary.url})</span></a>`;
305
330
  }
306
331
  html = html.replace(placeholder, replacement);
307
332
  });
@@ -326,7 +351,7 @@ var MarkdownParser = class {
326
351
  * @param {string} line - Raw markdown line
327
352
  * @returns {string} Parsed HTML line
328
353
  */
329
- static parseLine(line) {
354
+ static parseLine(line, isPreviewMode = false) {
330
355
  let html = this.escapeHtml(line);
331
356
  html = this.preserveIndentation(html, line);
332
357
  const horizontalRule = this.parseHorizontalRule(html);
@@ -337,6 +362,7 @@ var MarkdownParser = class {
337
362
  return codeBlock;
338
363
  html = this.parseHeader(html);
339
364
  html = this.parseBlockquote(html);
365
+ html = this.parseTaskList(html, isPreviewMode);
340
366
  html = this.parseBulletList(html);
341
367
  html = this.parseNumberedList(html);
342
368
  html = this.parseInlineElements(html);
@@ -350,9 +376,10 @@ var MarkdownParser = class {
350
376
  * @param {string} text - Full markdown text
351
377
  * @param {number} activeLine - Currently active line index (optional)
352
378
  * @param {boolean} showActiveLineRaw - Show raw markdown on active line
379
+ * @param {Function} instanceHighlighter - Instance-specific code highlighter (optional, overrides global if provided)
353
380
  * @returns {string} Parsed HTML
354
381
  */
355
- static parse(text, activeLine = -1, showActiveLineRaw = false) {
382
+ static parse(text, activeLine = -1, showActiveLineRaw = false, instanceHighlighter, isPreviewMode = false) {
356
383
  this.resetLinkIndex();
357
384
  const lines = text.split("\n");
358
385
  let inCodeBlock = false;
@@ -364,26 +391,27 @@ var MarkdownParser = class {
364
391
  const codeFenceRegex = /^```[^`]*$/;
365
392
  if (codeFenceRegex.test(line)) {
366
393
  inCodeBlock = !inCodeBlock;
367
- return this.parseLine(line);
394
+ return this.parseLine(line, isPreviewMode);
368
395
  }
369
396
  if (inCodeBlock) {
370
397
  const escaped = this.escapeHtml(line);
371
398
  const indented = this.preserveIndentation(escaped, line);
372
399
  return `<div>${indented || "&nbsp;"}</div>`;
373
400
  }
374
- return this.parseLine(line);
401
+ return this.parseLine(line, isPreviewMode);
375
402
  });
376
403
  const html = parsedLines.join("");
377
- return this.postProcessHTML(html);
404
+ return this.postProcessHTML(html, instanceHighlighter);
378
405
  }
379
406
  /**
380
407
  * Post-process HTML to consolidate lists and code blocks
381
408
  * @param {string} html - HTML to post-process
409
+ * @param {Function} instanceHighlighter - Instance-specific code highlighter (optional, overrides global if provided)
382
410
  * @returns {string} Post-processed HTML with consolidated lists and code blocks
383
411
  */
384
- static postProcessHTML(html) {
412
+ static postProcessHTML(html, instanceHighlighter) {
385
413
  if (typeof document === "undefined" || !document) {
386
- return this.postProcessHTMLManual(html);
414
+ return this.postProcessHTMLManual(html, instanceHighlighter);
387
415
  }
388
416
  const container = document.createElement("div");
389
417
  container.innerHTML = html;
@@ -412,8 +440,28 @@ var MarkdownParser = class {
412
440
  }
413
441
  container.insertBefore(currentCodeBlock, child.nextSibling);
414
442
  currentCodeBlock._codeElement = codeElement;
443
+ currentCodeBlock._language = lang;
444
+ currentCodeBlock._codeContent = "";
415
445
  continue;
416
446
  } else {
447
+ const highlighter = instanceHighlighter || this.codeHighlighter;
448
+ if (currentCodeBlock && highlighter && currentCodeBlock._codeContent) {
449
+ try {
450
+ const result = highlighter(
451
+ currentCodeBlock._codeContent,
452
+ currentCodeBlock._language || ""
453
+ );
454
+ if (result && typeof result.then === "function") {
455
+ 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.");
456
+ } else {
457
+ if (result && typeof result === "string" && result.trim()) {
458
+ currentCodeBlock._codeElement.innerHTML = result;
459
+ }
460
+ }
461
+ } catch (error) {
462
+ console.warn("Code highlighting failed:", error);
463
+ }
464
+ }
417
465
  inCodeBlock = false;
418
466
  currentCodeBlock = null;
419
467
  continue;
@@ -422,10 +470,14 @@ var MarkdownParser = class {
422
470
  }
423
471
  if (inCodeBlock && currentCodeBlock && child.tagName === "DIV" && !child.querySelector(".code-fence")) {
424
472
  const codeElement = currentCodeBlock._codeElement || currentCodeBlock.querySelector("code");
473
+ if (currentCodeBlock._codeContent.length > 0) {
474
+ currentCodeBlock._codeContent += "\n";
475
+ }
476
+ const lineText = child.textContent.replace(/\u00A0/g, " ");
477
+ currentCodeBlock._codeContent += lineText;
425
478
  if (codeElement.textContent.length > 0) {
426
479
  codeElement.textContent += "\n";
427
480
  }
428
- const lineText = child.textContent.replace(/\u00A0/g, " ");
429
481
  codeElement.textContent += lineText;
430
482
  child.remove();
431
483
  continue;
@@ -471,9 +523,10 @@ var MarkdownParser = class {
471
523
  /**
472
524
  * Manual post-processing for Node.js environments (without DOM)
473
525
  * @param {string} html - HTML to post-process
526
+ * @param {Function} instanceHighlighter - Instance-specific code highlighter (optional, overrides global if provided)
474
527
  * @returns {string} Post-processed HTML
475
528
  */
476
- static postProcessHTMLManual(html) {
529
+ static postProcessHTMLManual(html, instanceHighlighter) {
477
530
  let processed = html;
478
531
  processed = processed.replace(/((?:<div>(?:&nbsp;)*<li class="bullet-list">.*?<\/li><\/div>\s*)+)/gs, (match) => {
479
532
  const divs = match.match(/<div>(?:&nbsp;)*<li class="bullet-list">.*?<\/li><\/div>/gs) || [];
@@ -518,8 +571,25 @@ var MarkdownParser = class {
518
571
  }).join("\n");
519
572
  const lang = openFence.slice(3).trim();
520
573
  const langClass = lang ? ` class="language-${lang}"` : "";
574
+ let highlightedContent = codeContent;
575
+ const highlighter = instanceHighlighter || this.codeHighlighter;
576
+ if (highlighter) {
577
+ try {
578
+ const decodedCode = codeContent.replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
579
+ const result2 = highlighter(decodedCode, lang);
580
+ if (result2 && typeof result2.then === "function") {
581
+ console.warn("Async highlighters are not supported in Node.js (non-DOM) context. Use synchronous highlighters for server-side rendering.");
582
+ } else {
583
+ if (result2 && typeof result2 === "string" && result2.trim()) {
584
+ highlightedContent = result2;
585
+ }
586
+ }
587
+ } catch (error) {
588
+ console.warn("Code highlighting failed:", error);
589
+ }
590
+ }
521
591
  let result = `<div><span class="code-fence">${openFence}</span></div>`;
522
- result += `<pre class="code-block"><code${langClass}>${codeContent}</code></pre>`;
592
+ result += `<pre class="code-block"><code${langClass}>${highlightedContent}</code></pre>`;
523
593
  result += `<div><span class="code-fence">${closeFence}</span></div>`;
524
594
  return result;
525
595
  });
@@ -658,6 +728,8 @@ var MarkdownParser = class {
658
728
  };
659
729
  // Track link index for anchor naming
660
730
  __publicField(MarkdownParser, "linkIndex", 0);
731
+ // Global code highlighter function
732
+ __publicField(MarkdownParser, "codeHighlighter", null);
661
733
  /**
662
734
  * List pattern definitions
663
735
  */
@@ -2077,12 +2149,14 @@ function generateStyles(options = {}) {
2077
2149
  /* Code block styling in normal mode - yellow background */
2078
2150
  .overtype-wrapper .overtype-preview pre.code-block {
2079
2151
  background: var(--code-bg, rgba(244, 211, 94, 0.4)) !important;
2152
+ white-space: break-spaces !important; /* Prevent horizontal scrollbar that breaks alignment */
2080
2153
  }
2081
2154
 
2082
2155
  /* Code inside pre blocks - remove background */
2083
2156
  .overtype-wrapper .overtype-preview pre code {
2084
2157
  background: transparent !important;
2085
2158
  color: var(--code, #0d3b66) !important;
2159
+ font-family: ${fontFamily} !important; /* Match textarea font exactly for alignment */
2086
2160
  }
2087
2161
 
2088
2162
  /* Blockquotes */
@@ -2309,11 +2383,11 @@ function generateStyles(options = {}) {
2309
2383
  }
2310
2384
 
2311
2385
  /* Plain mode - hide preview and show textarea text */
2312
- .overtype-container.plain-mode .overtype-preview {
2386
+ .overtype-container[data-mode="plain"] .overtype-preview {
2313
2387
  display: none !important;
2314
2388
  }
2315
2389
 
2316
- .overtype-container.plain-mode .overtype-input {
2390
+ .overtype-container[data-mode="plain"] .overtype-input {
2317
2391
  color: var(--text, #0d3b66) !important;
2318
2392
  /* Use system font stack for better plain text readability */
2319
2393
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
@@ -2321,7 +2395,7 @@ function generateStyles(options = {}) {
2321
2395
  }
2322
2396
 
2323
2397
  /* Ensure textarea remains transparent in overlay mode */
2324
- .overtype-container:not(.plain-mode) .overtype-input {
2398
+ .overtype-container:not([data-mode="plain"]) .overtype-input {
2325
2399
  color: transparent !important;
2326
2400
  }
2327
2401
 
@@ -2374,37 +2448,43 @@ function generateStyles(options = {}) {
2374
2448
  color: var(--h1, #007bff);
2375
2449
  }
2376
2450
 
2451
+ .overtype-dropdown-icon {
2452
+ width: 20px;
2453
+ margin-right: 8px;
2454
+ text-align: center;
2455
+ }
2456
+
2377
2457
  /* Preview mode styles */
2378
- .overtype-container.preview-mode .overtype-input {
2458
+ .overtype-container[data-mode="preview"] .overtype-input {
2379
2459
  display: none !important;
2380
2460
  }
2381
2461
 
2382
- .overtype-container.preview-mode .overtype-preview {
2462
+ .overtype-container[data-mode="preview"] .overtype-preview {
2383
2463
  pointer-events: auto !important;
2384
2464
  user-select: text !important;
2385
2465
  cursor: text !important;
2386
2466
  }
2387
2467
 
2388
2468
  /* Hide syntax markers in preview mode */
2389
- .overtype-container.preview-mode .syntax-marker {
2469
+ .overtype-container[data-mode="preview"] .syntax-marker {
2390
2470
  display: none !important;
2391
2471
  }
2392
2472
 
2393
2473
  /* Hide URL part of links in preview mode - extra specificity */
2394
- .overtype-container.preview-mode .syntax-marker.url-part,
2395
- .overtype-container.preview-mode .url-part {
2474
+ .overtype-container[data-mode="preview"] .syntax-marker.url-part,
2475
+ .overtype-container[data-mode="preview"] .url-part {
2396
2476
  display: none !important;
2397
2477
  }
2398
2478
 
2399
2479
  /* Hide all syntax markers inside links too */
2400
- .overtype-container.preview-mode a .syntax-marker {
2480
+ .overtype-container[data-mode="preview"] a .syntax-marker {
2401
2481
  display: none !important;
2402
2482
  }
2403
2483
 
2404
2484
  /* Headers - restore proper sizing in preview mode */
2405
- .overtype-container.preview-mode .overtype-wrapper .overtype-preview h1,
2406
- .overtype-container.preview-mode .overtype-wrapper .overtype-preview h2,
2407
- .overtype-container.preview-mode .overtype-wrapper .overtype-preview h3 {
2485
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview h1,
2486
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview h2,
2487
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview h3 {
2408
2488
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important;
2409
2489
  font-weight: 600 !important;
2410
2490
  margin: 0 !important;
@@ -2413,41 +2493,63 @@ function generateStyles(options = {}) {
2413
2493
  line-height: 1 !important; /* Tight line height for headings */
2414
2494
  }
2415
2495
 
2416
- .overtype-container.preview-mode .overtype-wrapper .overtype-preview h1 {
2496
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview h1 {
2417
2497
  font-size: 2em !important;
2418
2498
  }
2419
2499
 
2420
- .overtype-container.preview-mode .overtype-wrapper .overtype-preview h2 {
2500
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview h2 {
2421
2501
  font-size: 1.5em !important;
2422
2502
  }
2423
2503
 
2424
- .overtype-container.preview-mode .overtype-wrapper .overtype-preview h3 {
2504
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview h3 {
2425
2505
  font-size: 1.17em !important;
2426
2506
  }
2427
2507
 
2428
2508
  /* Lists - restore list styling in preview mode */
2429
- .overtype-container.preview-mode .overtype-wrapper .overtype-preview ul {
2509
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview ul {
2430
2510
  display: block !important;
2431
2511
  list-style: disc !important;
2432
2512
  padding-left: 2em !important;
2433
2513
  margin: 1em 0 !important;
2434
2514
  }
2435
2515
 
2436
- .overtype-container.preview-mode .overtype-wrapper .overtype-preview ol {
2516
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview ol {
2437
2517
  display: block !important;
2438
2518
  list-style: decimal !important;
2439
2519
  padding-left: 2em !important;
2440
2520
  margin: 1em 0 !important;
2441
2521
  }
2442
2522
 
2443
- .overtype-container.preview-mode .overtype-wrapper .overtype-preview li {
2523
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview li {
2444
2524
  display: list-item !important;
2445
2525
  margin: 0 !important;
2446
2526
  padding: 0 !important;
2447
2527
  }
2448
2528
 
2529
+ /* Task list checkboxes - only in preview mode */
2530
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview li.task-list {
2531
+ list-style: none !important;
2532
+ position: relative !important;
2533
+ }
2534
+
2535
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview li.task-list input[type="checkbox"] {
2536
+ margin-right: 0.5em !important;
2537
+ cursor: default !important;
2538
+ vertical-align: middle !important;
2539
+ }
2540
+
2541
+ /* Task list in normal mode - keep syntax visible */
2542
+ .overtype-container:not([data-mode="preview"]) .overtype-wrapper .overtype-preview li.task-list {
2543
+ list-style: none !important;
2544
+ }
2545
+
2546
+ .overtype-container:not([data-mode="preview"]) .overtype-wrapper .overtype-preview li.task-list .syntax-marker {
2547
+ color: var(--syntax, #999999) !important;
2548
+ font-weight: normal !important;
2549
+ }
2550
+
2449
2551
  /* Links - make clickable in preview mode */
2450
- .overtype-container.preview-mode .overtype-wrapper .overtype-preview a {
2552
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview a {
2451
2553
  pointer-events: auto !important;
2452
2554
  cursor: pointer !important;
2453
2555
  color: var(--link, #0066cc) !important;
@@ -2455,7 +2557,7 @@ function generateStyles(options = {}) {
2455
2557
  }
2456
2558
 
2457
2559
  /* Code blocks - proper pre/code styling in preview mode */
2458
- .overtype-container.preview-mode .overtype-wrapper .overtype-preview pre.code-block {
2560
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview pre.code-block {
2459
2561
  background: #2d2d2d !important;
2460
2562
  color: #f8f8f2 !important;
2461
2563
  padding: 1.2em !important;
@@ -2466,11 +2568,11 @@ function generateStyles(options = {}) {
2466
2568
  }
2467
2569
 
2468
2570
  /* Cave theme code block background in preview mode */
2469
- .overtype-container[data-theme="cave"].preview-mode .overtype-wrapper .overtype-preview pre.code-block {
2571
+ .overtype-container[data-theme="cave"][data-mode="preview"] .overtype-wrapper .overtype-preview pre.code-block {
2470
2572
  background: #11171F !important;
2471
2573
  }
2472
2574
 
2473
- .overtype-container.preview-mode .overtype-wrapper .overtype-preview pre.code-block code {
2575
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview pre.code-block code {
2474
2576
  background: transparent !important;
2475
2577
  color: inherit !important;
2476
2578
  padding: 0 !important;
@@ -2480,16 +2582,16 @@ function generateStyles(options = {}) {
2480
2582
  }
2481
2583
 
2482
2584
  /* Hide old code block lines and fences in preview mode */
2483
- .overtype-container.preview-mode .overtype-wrapper .overtype-preview .code-block-line {
2585
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview .code-block-line {
2484
2586
  display: none !important;
2485
2587
  }
2486
2588
 
2487
- .overtype-container.preview-mode .overtype-wrapper .overtype-preview .code-fence {
2589
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview .code-fence {
2488
2590
  display: none !important;
2489
2591
  }
2490
2592
 
2491
2593
  /* Blockquotes - enhanced styling in preview mode */
2492
- .overtype-container.preview-mode .overtype-wrapper .overtype-preview .blockquote {
2594
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview .blockquote {
2493
2595
  display: block !important;
2494
2596
  border-left: 4px solid var(--blockquote, #ddd) !important;
2495
2597
  padding-left: 1em !important;
@@ -2498,7 +2600,7 @@ function generateStyles(options = {}) {
2498
2600
  }
2499
2601
 
2500
2602
  /* Typography improvements in preview mode */
2501
- .overtype-container.preview-mode .overtype-wrapper .overtype-preview {
2603
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview {
2502
2604
  font-family: Georgia, 'Times New Roman', serif !important;
2503
2605
  font-size: 16px !important;
2504
2606
  line-height: 1.8 !important;
@@ -2506,7 +2608,7 @@ function generateStyles(options = {}) {
2506
2608
  }
2507
2609
 
2508
2610
  /* Inline code in preview mode - keep monospace */
2509
- .overtype-container.preview-mode .overtype-wrapper .overtype-preview code {
2611
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview code {
2510
2612
  font-family: ${fontFamily} !important;
2511
2613
  font-size: 0.9em !important;
2512
2614
  background: rgba(135, 131, 120, 0.15) !important;
@@ -2515,236 +2617,243 @@ function generateStyles(options = {}) {
2515
2617
  }
2516
2618
 
2517
2619
  /* Strong and em elements in preview mode */
2518
- .overtype-container.preview-mode .overtype-wrapper .overtype-preview strong {
2620
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview strong {
2519
2621
  font-weight: 700 !important;
2520
2622
  color: inherit !important; /* Use parent text color */
2521
2623
  }
2522
2624
 
2523
- .overtype-container.preview-mode .overtype-wrapper .overtype-preview em {
2625
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview em {
2524
2626
  font-style: italic !important;
2525
2627
  color: inherit !important; /* Use parent text color */
2526
2628
  }
2527
2629
 
2528
2630
  /* HR in preview mode */
2529
- .overtype-container.preview-mode .overtype-wrapper .overtype-preview .hr-marker {
2631
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview .hr-marker {
2530
2632
  display: block !important;
2531
2633
  border-top: 2px solid var(--hr, #ddd) !important;
2532
2634
  text-indent: -9999px !important;
2533
2635
  height: 2px !important;
2534
2636
  }
2535
2637
 
2638
+ /* Link Tooltip - CSS Anchor Positioning */
2639
+ @supports (position-anchor: --x) and (position-area: center) {
2640
+ .overtype-link-tooltip {
2641
+ position: absolute;
2642
+ position-anchor: var(--target-anchor, --link-0);
2643
+ position-area: block-end center;
2644
+ margin-top: 8px !important;
2645
+
2646
+ background: #333 !important;
2647
+ color: white !important;
2648
+ padding: 6px 10px !important;
2649
+ border-radius: 16px !important;
2650
+ font-size: 12px !important;
2651
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif !important;
2652
+ display: none !important;
2653
+ z-index: 10000 !important;
2654
+ cursor: pointer !important;
2655
+ box-shadow: 0 2px 8px rgba(0,0,0,0.3) !important;
2656
+ max-width: 300px !important;
2657
+ white-space: nowrap !important;
2658
+ overflow: hidden !important;
2659
+ text-overflow: ellipsis !important;
2660
+
2661
+ position-try: most-width block-end inline-end, flip-inline, block-start center;
2662
+ position-visibility: anchors-visible;
2663
+ }
2664
+
2665
+ .overtype-link-tooltip.visible {
2666
+ display: flex !important;
2667
+ }
2668
+ }
2669
+
2536
2670
  ${mobileStyles}
2537
2671
  `;
2538
2672
  }
2539
2673
 
2540
- // src/icons.js
2541
- var boldIcon = `<svg viewBox="0 0 18 18">
2542
- <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>
2543
- <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>
2544
- </svg>`;
2545
- var italicIcon = `<svg viewBox="0 0 18 18">
2546
- <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="7" x2="13" y1="4" y2="4"></line>
2547
- <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="5" x2="11" y1="14" y2="14"></line>
2548
- <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="8" x2="10" y1="14" y2="4"></line>
2549
- </svg>`;
2550
- var h1Icon = `<svg viewBox="0 0 18 18">
2551
- <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>
2552
- </svg>`;
2553
- var h2Icon = `<svg viewBox="0 0 18 18">
2554
- <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>
2555
- </svg>`;
2556
- var h3Icon = `<svg viewBox="0 0 18 18">
2557
- <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>
2558
- </svg>`;
2559
- var linkIcon = `<svg viewBox="0 0 18 18">
2560
- <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="7" x2="11" y1="7" y2="11"></line>
2561
- <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>
2562
- <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>
2563
- </svg>`;
2564
- var codeIcon = `<svg viewBox="0 0 18 18">
2565
- <polyline stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" points="5 7 3 9 5 11"></polyline>
2566
- <polyline stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" points="13 7 15 9 13 11"></polyline>
2567
- <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="10" x2="8" y1="5" y2="13"></line>
2568
- </svg>`;
2569
- var bulletListIcon = `<svg viewBox="0 0 18 18">
2570
- <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="6" x2="15" y1="4" y2="4"></line>
2571
- <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="6" x2="15" y1="9" y2="9"></line>
2572
- <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="6" x2="15" y1="14" y2="14"></line>
2573
- <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="3" x2="3" y1="4" y2="4"></line>
2574
- <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="3" x2="3" y1="9" y2="9"></line>
2575
- <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="3" x2="3" y1="14" y2="14"></line>
2576
- </svg>`;
2577
- var orderedListIcon = `<svg viewBox="0 0 18 18">
2578
- <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="7" x2="15" y1="4" y2="4"></line>
2579
- <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="7" x2="15" y1="9" y2="9"></line>
2580
- <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="7" x2="15" y1="14" y2="14"></line>
2581
- <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>
2582
- <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>
2583
- <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>
2584
- <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>
2585
- </svg>`;
2586
- var quoteIcon = `<svg viewBox="2 2 20 20">
2587
- <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>
2588
- <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>
2589
- </svg>`;
2590
- var taskListIcon = `<svg viewBox="0 0 18 18">
2591
- <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="8" x2="16" y1="4" y2="4"></line>
2592
- <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="8" x2="16" y1="9" y2="9"></line>
2593
- <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="8" x2="16" y1="14" y2="14"></line>
2594
- <rect stroke="currentColor" fill="none" stroke-width="1.5" x="2" y="3" width="3" height="3" rx="0.5"></rect>
2595
- <rect stroke="currentColor" fill="none" stroke-width="1.5" x="2" y="13" width="3" height="3" rx="0.5"></rect>
2596
- <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>
2597
- </svg>`;
2598
- var eyeIcon = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2599
- <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" fill="none"></path>
2600
- <circle cx="12" cy="12" r="3" fill="none"></circle>
2601
- </svg>`;
2602
-
2603
2674
  // src/toolbar.js
2604
2675
  var Toolbar = class {
2605
- constructor(editor, buttonConfig = null) {
2676
+ constructor(editor, options = {}) {
2606
2677
  this.editor = editor;
2607
2678
  this.container = null;
2608
2679
  this.buttons = {};
2609
- this.buttonConfig = buttonConfig;
2680
+ this.toolbarButtons = options.toolbarButtons || [];
2610
2681
  }
2611
2682
  /**
2612
- * Create and attach toolbar to editor
2683
+ * Create and render toolbar
2613
2684
  */
2614
2685
  create() {
2615
- var _a;
2616
2686
  this.container = document.createElement("div");
2617
2687
  this.container.className = "overtype-toolbar";
2618
2688
  this.container.setAttribute("role", "toolbar");
2619
- this.container.setAttribute("aria-label", "Text formatting");
2620
- const buttonConfig = (_a = this.buttonConfig) != null ? _a : [
2621
- { name: "bold", icon: boldIcon, title: "Bold (Ctrl+B)", action: "toggleBold" },
2622
- { name: "italic", icon: italicIcon, title: "Italic (Ctrl+I)", action: "toggleItalic" },
2623
- { separator: true },
2624
- { name: "h1", icon: h1Icon, title: "Heading 1", action: "insertH1" },
2625
- { name: "h2", icon: h2Icon, title: "Heading 2", action: "insertH2" },
2626
- { name: "h3", icon: h3Icon, title: "Heading 3", action: "insertH3" },
2627
- { separator: true },
2628
- { name: "link", icon: linkIcon, title: "Insert Link (Ctrl+K)", action: "insertLink" },
2629
- { name: "code", icon: codeIcon, title: "Code (Ctrl+`)", action: "toggleCode" },
2630
- { separator: true },
2631
- { name: "quote", icon: quoteIcon, title: "Quote", action: "toggleQuote" },
2632
- { separator: true },
2633
- { name: "bulletList", icon: bulletListIcon, title: "Bullet List", action: "toggleBulletList" },
2634
- { name: "orderedList", icon: orderedListIcon, title: "Numbered List", action: "toggleNumberedList" },
2635
- { name: "taskList", icon: taskListIcon, title: "Task List", action: "toggleTaskList" },
2636
- { separator: true },
2637
- { name: "viewMode", icon: eyeIcon, title: "View mode", action: "toggle-view-menu", hasDropdown: true }
2638
- ];
2639
- buttonConfig.forEach((config) => {
2640
- if (config.separator) {
2641
- const separator = document.createElement("div");
2642
- separator.className = "overtype-toolbar-separator";
2643
- separator.setAttribute("role", "separator");
2689
+ this.container.setAttribute("aria-label", "Formatting toolbar");
2690
+ this.toolbarButtons.forEach((buttonConfig) => {
2691
+ if (buttonConfig.name === "separator") {
2692
+ const separator = this.createSeparator();
2644
2693
  this.container.appendChild(separator);
2645
2694
  } else {
2646
- const button = this.createButton(config);
2647
- this.buttons[config.name] = button;
2695
+ const button = this.createButton(buttonConfig);
2696
+ this.buttons[buttonConfig.name] = button;
2648
2697
  this.container.appendChild(button);
2649
2698
  }
2650
2699
  });
2651
- const container = this.editor.element.querySelector(".overtype-container");
2652
- const wrapper = this.editor.element.querySelector(".overtype-wrapper");
2653
- if (container && wrapper) {
2654
- container.insertBefore(this.container, wrapper);
2655
- }
2656
- return this.container;
2700
+ this.editor.wrapper.insertBefore(this.container, this.editor.wrapper.firstChild);
2657
2701
  }
2658
2702
  /**
2659
- * Create individual toolbar button
2703
+ * Create a toolbar separator
2660
2704
  */
2661
- createButton(config) {
2705
+ createSeparator() {
2706
+ const separator = document.createElement("div");
2707
+ separator.className = "overtype-toolbar-separator";
2708
+ separator.setAttribute("role", "separator");
2709
+ return separator;
2710
+ }
2711
+ /**
2712
+ * Create a toolbar button
2713
+ */
2714
+ createButton(buttonConfig) {
2662
2715
  const button = document.createElement("button");
2663
2716
  button.className = "overtype-toolbar-button";
2664
2717
  button.type = "button";
2665
- button.title = config.title;
2666
- button.setAttribute("aria-label", config.title);
2667
- button.setAttribute("data-action", config.action);
2668
- button.innerHTML = config.icon;
2669
- if (config.hasDropdown) {
2718
+ button.setAttribute("data-button", buttonConfig.name);
2719
+ button.title = buttonConfig.title || "";
2720
+ button.setAttribute("aria-label", buttonConfig.title || buttonConfig.name);
2721
+ button.innerHTML = this.sanitizeSVG(buttonConfig.icon || "");
2722
+ if (buttonConfig.name === "viewMode") {
2670
2723
  button.classList.add("has-dropdown");
2671
- if (config.name === "viewMode") {
2672
- this.viewModeButton = button;
2673
- }
2724
+ button.dataset.dropdown = "true";
2725
+ button.addEventListener("click", (e) => {
2726
+ e.preventDefault();
2727
+ this.toggleViewModeDropdown(button);
2728
+ });
2729
+ return button;
2674
2730
  }
2675
- button.addEventListener("click", (e) => {
2731
+ button._clickHandler = async (e) => {
2676
2732
  e.preventDefault();
2677
- this.handleAction(config.action, button);
2678
- });
2733
+ this.editor.textarea.focus();
2734
+ try {
2735
+ if (buttonConfig.action) {
2736
+ await buttonConfig.action({
2737
+ editor: this.editor,
2738
+ getValue: () => this.editor.getValue(),
2739
+ setValue: (value) => this.editor.setValue(value),
2740
+ event: e
2741
+ });
2742
+ }
2743
+ } catch (error) {
2744
+ console.error(`Button "${buttonConfig.name}" error:`, error);
2745
+ this.editor.wrapper.dispatchEvent(new CustomEvent("button-error", {
2746
+ detail: { buttonName: buttonConfig.name, error }
2747
+ }));
2748
+ button.classList.add("button-error");
2749
+ button.style.animation = "buttonError 0.3s";
2750
+ setTimeout(() => {
2751
+ button.classList.remove("button-error");
2752
+ button.style.animation = "";
2753
+ }, 300);
2754
+ }
2755
+ };
2756
+ button.addEventListener("click", button._clickHandler);
2679
2757
  return button;
2680
2758
  }
2681
2759
  /**
2682
- * Handle toolbar button actions
2760
+ * Sanitize SVG to prevent XSS
2683
2761
  */
2684
- async handleAction(action, button) {
2685
- const textarea = this.editor.textarea;
2686
- if (!textarea)
2687
- return;
2688
- if (action === "toggle-view-menu") {
2689
- this.toggleViewDropdown(button);
2762
+ sanitizeSVG(svg) {
2763
+ if (typeof svg !== "string")
2764
+ return "";
2765
+ const cleaned = svg.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "").replace(/\son\w+\s*=\s*["'][^"']*["']/gi, "").replace(/\son\w+\s*=\s*[^\s>]*/gi, "");
2766
+ return cleaned;
2767
+ }
2768
+ /**
2769
+ * Toggle view mode dropdown (internal implementation)
2770
+ * Not exposed to users - viewMode button behavior is fixed
2771
+ */
2772
+ toggleViewModeDropdown(button) {
2773
+ const existingDropdown = document.querySelector(".overtype-dropdown-menu");
2774
+ if (existingDropdown) {
2775
+ existingDropdown.remove();
2776
+ button.classList.remove("dropdown-active");
2690
2777
  return;
2691
2778
  }
2692
- textarea.focus();
2693
- try {
2694
- switch (action) {
2695
- case "toggleBold":
2696
- toggleBold(textarea);
2697
- break;
2698
- case "toggleItalic":
2699
- toggleItalic(textarea);
2700
- break;
2701
- case "insertH1":
2702
- toggleH1(textarea);
2703
- break;
2704
- case "insertH2":
2705
- toggleH2(textarea);
2706
- break;
2707
- case "insertH3":
2708
- toggleH3(textarea);
2709
- break;
2710
- case "insertLink":
2711
- insertLink(textarea);
2712
- break;
2713
- case "toggleCode":
2714
- toggleCode(textarea);
2715
- break;
2716
- case "toggleBulletList":
2717
- toggleBulletList(textarea);
2718
- break;
2719
- case "toggleNumberedList":
2720
- toggleNumberedList(textarea);
2721
- break;
2722
- case "toggleQuote":
2723
- toggleQuote(textarea);
2724
- break;
2725
- case "toggleTaskList":
2726
- toggleTaskList(textarea);
2727
- break;
2728
- case "toggle-plain":
2729
- const isPlain = this.editor.container.classList.contains("plain-mode");
2730
- this.editor.showPlainTextarea(!isPlain);
2731
- break;
2779
+ button.classList.add("dropdown-active");
2780
+ const dropdown = this.createViewModeDropdown(button);
2781
+ const rect = button.getBoundingClientRect();
2782
+ dropdown.style.position = "absolute";
2783
+ dropdown.style.top = `${rect.bottom + 5}px`;
2784
+ dropdown.style.left = `${rect.left}px`;
2785
+ document.body.appendChild(dropdown);
2786
+ this.handleDocumentClick = (e) => {
2787
+ if (!dropdown.contains(e.target) && !button.contains(e.target)) {
2788
+ dropdown.remove();
2789
+ button.classList.remove("dropdown-active");
2790
+ document.removeEventListener("click", this.handleDocumentClick);
2732
2791
  }
2733
- textarea.dispatchEvent(new Event("input", { bubbles: true }));
2734
- } catch (error) {
2735
- console.error("Error loading markdown-actions:", error);
2736
- }
2792
+ };
2793
+ setTimeout(() => {
2794
+ document.addEventListener("click", this.handleDocumentClick);
2795
+ }, 0);
2737
2796
  }
2738
2797
  /**
2739
- * Update toolbar button states based on current selection
2798
+ * Create view mode dropdown menu (internal implementation)
2740
2799
  */
2741
- async updateButtonStates() {
2742
- const textarea = this.editor.textarea;
2743
- if (!textarea)
2744
- return;
2800
+ createViewModeDropdown(button) {
2801
+ const dropdown = document.createElement("div");
2802
+ dropdown.className = "overtype-dropdown-menu";
2803
+ const items = [
2804
+ { id: "normal", label: "Normal Edit", icon: "\u2713" },
2805
+ { id: "plain", label: "Plain Textarea", icon: "\u2713" },
2806
+ { id: "preview", label: "Preview Mode", icon: "\u2713" }
2807
+ ];
2808
+ const currentMode = this.editor.container.dataset.mode || "normal";
2809
+ items.forEach((item) => {
2810
+ const menuItem = document.createElement("button");
2811
+ menuItem.className = "overtype-dropdown-item";
2812
+ menuItem.type = "button";
2813
+ menuItem.textContent = item.label;
2814
+ if (item.id === currentMode) {
2815
+ menuItem.classList.add("active");
2816
+ menuItem.setAttribute("aria-current", "true");
2817
+ const checkmark = document.createElement("span");
2818
+ checkmark.className = "overtype-dropdown-icon";
2819
+ checkmark.textContent = item.icon;
2820
+ menuItem.prepend(checkmark);
2821
+ }
2822
+ menuItem.addEventListener("click", (e) => {
2823
+ e.preventDefault();
2824
+ switch (item.id) {
2825
+ case "plain":
2826
+ this.editor.showPlainTextarea();
2827
+ break;
2828
+ case "preview":
2829
+ this.editor.showPreviewMode();
2830
+ break;
2831
+ case "normal":
2832
+ default:
2833
+ this.editor.showNormalEditMode();
2834
+ break;
2835
+ }
2836
+ dropdown.remove();
2837
+ button.classList.remove("dropdown-active");
2838
+ document.removeEventListener("click", this.handleDocumentClick);
2839
+ });
2840
+ dropdown.appendChild(menuItem);
2841
+ });
2842
+ return dropdown;
2843
+ }
2844
+ /**
2845
+ * Update active states of toolbar buttons
2846
+ */
2847
+ updateButtonStates() {
2848
+ var _a;
2745
2849
  try {
2746
- const activeFormats = getActiveFormats2(textarea);
2850
+ const activeFormats = ((_a = getActiveFormats2) == null ? void 0 : _a(
2851
+ this.editor.textarea,
2852
+ this.editor.textarea.selectionStart
2853
+ )) || [];
2747
2854
  Object.entries(this.buttons).forEach(([name, button]) => {
2855
+ if (name === "viewMode")
2856
+ return;
2748
2857
  let isActive = false;
2749
2858
  switch (name) {
2750
2859
  case "bold":
@@ -2762,12 +2871,12 @@ var Toolbar = class {
2762
2871
  case "orderedList":
2763
2872
  isActive = activeFormats.includes("numbered-list");
2764
2873
  break;
2765
- case "quote":
2766
- isActive = activeFormats.includes("quote");
2767
- break;
2768
2874
  case "taskList":
2769
2875
  isActive = activeFormats.includes("task-list");
2770
2876
  break;
2877
+ case "quote":
2878
+ isActive = activeFormats.includes("quote");
2879
+ break;
2771
2880
  case "h1":
2772
2881
  isActive = activeFormats.includes("header");
2773
2882
  break;
@@ -2777,9 +2886,6 @@ var Toolbar = class {
2777
2886
  case "h3":
2778
2887
  isActive = activeFormats.includes("header-3");
2779
2888
  break;
2780
- case "togglePlain":
2781
- isActive = !this.editor.container.classList.contains("plain-mode");
2782
- break;
2783
2889
  }
2784
2890
  button.classList.toggle("active", isActive);
2785
2891
  button.setAttribute("aria-pressed", isActive.toString());
@@ -2788,101 +2894,19 @@ var Toolbar = class {
2788
2894
  }
2789
2895
  }
2790
2896
  /**
2791
- * Toggle view mode dropdown menu
2792
- */
2793
- toggleViewDropdown(button) {
2794
- const existingDropdown = document.querySelector(".overtype-dropdown-menu");
2795
- if (existingDropdown) {
2796
- existingDropdown.remove();
2797
- button.classList.remove("dropdown-active");
2798
- document.removeEventListener("click", this.handleDocumentClick);
2799
- return;
2800
- }
2801
- const dropdown = this.createViewDropdown();
2802
- const rect = button.getBoundingClientRect();
2803
- dropdown.style.top = `${rect.bottom + 4}px`;
2804
- dropdown.style.left = `${rect.left}px`;
2805
- document.body.appendChild(dropdown);
2806
- button.classList.add("dropdown-active");
2807
- this.handleDocumentClick = (e) => {
2808
- if (!button.contains(e.target) && !dropdown.contains(e.target)) {
2809
- dropdown.remove();
2810
- button.classList.remove("dropdown-active");
2811
- document.removeEventListener("click", this.handleDocumentClick);
2812
- }
2813
- };
2814
- setTimeout(() => {
2815
- document.addEventListener("click", this.handleDocumentClick);
2816
- }, 0);
2817
- }
2818
- /**
2819
- * Create view mode dropdown menu
2820
- */
2821
- createViewDropdown() {
2822
- const dropdown = document.createElement("div");
2823
- dropdown.className = "overtype-dropdown-menu";
2824
- const isPlain = this.editor.container.classList.contains("plain-mode");
2825
- const isPreview = this.editor.container.classList.contains("preview-mode");
2826
- const currentMode = isPreview ? "preview" : isPlain ? "plain" : "normal";
2827
- const modes = [
2828
- { id: "normal", label: "Normal Edit", icon: "\u2713" },
2829
- { id: "plain", label: "Plain Textarea", icon: "\u2713" },
2830
- { id: "preview", label: "Preview Mode", icon: "\u2713" }
2831
- ];
2832
- modes.forEach((mode) => {
2833
- const item = document.createElement("button");
2834
- item.className = "overtype-dropdown-item";
2835
- item.type = "button";
2836
- const check = document.createElement("span");
2837
- check.className = "overtype-dropdown-check";
2838
- check.textContent = currentMode === mode.id ? mode.icon : "";
2839
- const label = document.createElement("span");
2840
- label.textContent = mode.label;
2841
- item.appendChild(check);
2842
- item.appendChild(label);
2843
- if (currentMode === mode.id) {
2844
- item.classList.add("active");
2845
- }
2846
- item.addEventListener("click", (e) => {
2847
- e.stopPropagation();
2848
- this.setViewMode(mode.id);
2849
- dropdown.remove();
2850
- this.viewModeButton.classList.remove("dropdown-active");
2851
- document.removeEventListener("click", this.handleDocumentClick);
2852
- });
2853
- dropdown.appendChild(item);
2854
- });
2855
- return dropdown;
2856
- }
2857
- /**
2858
- * Set view mode
2859
- */
2860
- setViewMode(mode) {
2861
- this.editor.container.classList.remove("plain-mode", "preview-mode");
2862
- switch (mode) {
2863
- case "plain":
2864
- this.editor.showPlainTextarea(true);
2865
- break;
2866
- case "preview":
2867
- this.editor.showPreviewMode(true);
2868
- break;
2869
- case "normal":
2870
- default:
2871
- this.editor.showPlainTextarea(false);
2872
- if (typeof this.editor.showPreviewMode === "function") {
2873
- this.editor.showPreviewMode(false);
2874
- }
2875
- break;
2876
- }
2877
- }
2878
- /**
2879
- * Destroy toolbar
2897
+ * Destroy toolbar and cleanup
2880
2898
  */
2881
2899
  destroy() {
2882
2900
  if (this.container) {
2883
2901
  if (this.handleDocumentClick) {
2884
2902
  document.removeEventListener("click", this.handleDocumentClick);
2885
2903
  }
2904
+ Object.values(this.buttons).forEach((button) => {
2905
+ if (button._clickHandler) {
2906
+ button.removeEventListener("click", button._clickHandler);
2907
+ delete button._clickHandler;
2908
+ }
2909
+ });
2886
2910
  this.container.remove();
2887
2911
  this.container = null;
2888
2912
  this.buttons = {};
@@ -2897,13 +2921,10 @@ var LinkTooltip = class {
2897
2921
  this.tooltip = null;
2898
2922
  this.currentLink = null;
2899
2923
  this.hideTimeout = null;
2924
+ this.visibilityChangeHandler = null;
2900
2925
  this.init();
2901
2926
  }
2902
2927
  init() {
2903
- const supportsAnchor = CSS.supports("position-anchor: --x") && CSS.supports("position-area: center");
2904
- if (!supportsAnchor) {
2905
- return;
2906
- }
2907
2928
  this.createTooltip();
2908
2929
  this.editor.textarea.addEventListener("selectionchange", () => this.checkCursorPosition());
2909
2930
  this.editor.textarea.addEventListener("keyup", (e) => {
@@ -2913,46 +2934,19 @@ var LinkTooltip = class {
2913
2934
  });
2914
2935
  this.editor.textarea.addEventListener("input", () => this.hide());
2915
2936
  this.editor.textarea.addEventListener("scroll", () => this.hide());
2937
+ this.editor.textarea.addEventListener("blur", () => this.hide());
2938
+ this.visibilityChangeHandler = () => {
2939
+ if (document.hidden) {
2940
+ this.hide();
2941
+ }
2942
+ };
2943
+ document.addEventListener("visibilitychange", this.visibilityChangeHandler);
2916
2944
  this.tooltip.addEventListener("mouseenter", () => this.cancelHide());
2917
2945
  this.tooltip.addEventListener("mouseleave", () => this.scheduleHide());
2918
2946
  }
2919
2947
  createTooltip() {
2920
2948
  this.tooltip = document.createElement("div");
2921
2949
  this.tooltip.className = "overtype-link-tooltip";
2922
- const tooltipStyles = document.createElement("style");
2923
- tooltipStyles.textContent = `
2924
- @supports (position-anchor: --x) and (position-area: center) {
2925
- .overtype-link-tooltip {
2926
- position: absolute;
2927
- position-anchor: var(--target-anchor, --link-0);
2928
- position-area: block-end center;
2929
- margin-top: 8px !important;
2930
-
2931
- background: #333 !important;
2932
- color: white !important;
2933
- padding: 6px 10px !important;
2934
- border-radius: 16px !important;
2935
- font-size: 12px !important;
2936
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif !important;
2937
- display: none !important;
2938
- z-index: 10000 !important;
2939
- cursor: pointer !important;
2940
- box-shadow: 0 2px 8px rgba(0,0,0,0.3) !important;
2941
- max-width: 300px !important;
2942
- white-space: nowrap !important;
2943
- overflow: hidden !important;
2944
- text-overflow: ellipsis !important;
2945
-
2946
- position-try: most-width block-end inline-end, flip-inline, block-start center;
2947
- position-visibility: anchors-visible;
2948
- }
2949
-
2950
- .overtype-link-tooltip.visible {
2951
- display: flex !important;
2952
- }
2953
- }
2954
- `;
2955
- document.head.appendChild(tooltipStyles);
2956
2950
  this.tooltip.innerHTML = `
2957
2951
  <span style="display: flex; align-items: center; gap: 6px;">
2958
2952
  <svg width="12" height="12" viewBox="0 0 20 20" fill="currentColor" style="flex-shrink: 0;">
@@ -3028,6 +3022,10 @@ var LinkTooltip = class {
3028
3022
  }
3029
3023
  destroy() {
3030
3024
  this.cancelHide();
3025
+ if (this.visibilityChangeHandler) {
3026
+ document.removeEventListener("visibilitychange", this.visibilityChangeHandler);
3027
+ this.visibilityChangeHandler = null;
3028
+ }
3031
3029
  if (this.tooltip && this.tooltip.parentNode) {
3032
3030
  this.tooltip.parentNode.removeChild(this.tooltip);
3033
3031
  }
@@ -3036,6 +3034,204 @@ var LinkTooltip = class {
3036
3034
  }
3037
3035
  };
3038
3036
 
3037
+ // src/icons.js
3038
+ var boldIcon = `<svg viewBox="0 0 18 18">
3039
+ <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>
3040
+ <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>
3041
+ </svg>`;
3042
+ var italicIcon = `<svg viewBox="0 0 18 18">
3043
+ <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="7" x2="13" y1="4" y2="4"></line>
3044
+ <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="5" x2="11" y1="14" y2="14"></line>
3045
+ <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="8" x2="10" y1="14" y2="4"></line>
3046
+ </svg>`;
3047
+ var h1Icon = `<svg viewBox="0 0 18 18">
3048
+ <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>
3049
+ </svg>`;
3050
+ var h2Icon = `<svg viewBox="0 0 18 18">
3051
+ <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>
3052
+ </svg>`;
3053
+ var h3Icon = `<svg viewBox="0 0 18 18">
3054
+ <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>
3055
+ </svg>`;
3056
+ var linkIcon = `<svg viewBox="0 0 18 18">
3057
+ <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="7" x2="11" y1="7" y2="11"></line>
3058
+ <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>
3059
+ <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>
3060
+ </svg>`;
3061
+ var codeIcon = `<svg viewBox="0 0 18 18">
3062
+ <polyline stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" points="5 7 3 9 5 11"></polyline>
3063
+ <polyline stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" points="13 7 15 9 13 11"></polyline>
3064
+ <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="10" x2="8" y1="5" y2="13"></line>
3065
+ </svg>`;
3066
+ var bulletListIcon = `<svg viewBox="0 0 18 18">
3067
+ <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="6" x2="15" y1="4" y2="4"></line>
3068
+ <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="6" x2="15" y1="9" y2="9"></line>
3069
+ <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="6" x2="15" y1="14" y2="14"></line>
3070
+ <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="3" x2="3" y1="4" y2="4"></line>
3071
+ <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="3" x2="3" y1="9" y2="9"></line>
3072
+ <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="3" x2="3" y1="14" y2="14"></line>
3073
+ </svg>`;
3074
+ var orderedListIcon = `<svg viewBox="0 0 18 18">
3075
+ <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="7" x2="15" y1="4" y2="4"></line>
3076
+ <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="7" x2="15" y1="9" y2="9"></line>
3077
+ <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="7" x2="15" y1="14" y2="14"></line>
3078
+ <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>
3079
+ <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>
3080
+ <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>
3081
+ <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>
3082
+ </svg>`;
3083
+ var quoteIcon = `<svg viewBox="2 2 20 20">
3084
+ <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>
3085
+ <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>
3086
+ </svg>`;
3087
+ var taskListIcon = `<svg viewBox="0 0 18 18">
3088
+ <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="8" x2="16" y1="4" y2="4"></line>
3089
+ <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="8" x2="16" y1="9" y2="9"></line>
3090
+ <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="8" x2="16" y1="14" y2="14"></line>
3091
+ <rect stroke="currentColor" fill="none" stroke-width="1.5" x="2" y="3" width="3" height="3" rx="0.5"></rect>
3092
+ <rect stroke="currentColor" fill="none" stroke-width="1.5" x="2" y="13" width="3" height="3" rx="0.5"></rect>
3093
+ <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>
3094
+ </svg>`;
3095
+ var eyeIcon = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
3096
+ <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" fill="none"></path>
3097
+ <circle cx="12" cy="12" r="3" fill="none"></circle>
3098
+ </svg>`;
3099
+
3100
+ // src/toolbar-buttons.js
3101
+ var toolbarButtons = {
3102
+ bold: {
3103
+ name: "bold",
3104
+ icon: boldIcon,
3105
+ title: "Bold (Ctrl+B)",
3106
+ action: ({ editor, event }) => {
3107
+ toggleBold(editor.textarea);
3108
+ editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
3109
+ }
3110
+ },
3111
+ italic: {
3112
+ name: "italic",
3113
+ icon: italicIcon,
3114
+ title: "Italic (Ctrl+I)",
3115
+ action: ({ editor, event }) => {
3116
+ toggleItalic(editor.textarea);
3117
+ editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
3118
+ }
3119
+ },
3120
+ code: {
3121
+ name: "code",
3122
+ icon: codeIcon,
3123
+ title: "Inline Code",
3124
+ action: ({ editor, event }) => {
3125
+ toggleCode(editor.textarea);
3126
+ editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
3127
+ }
3128
+ },
3129
+ separator: {
3130
+ name: "separator"
3131
+ // No icon, title, or action - special separator element
3132
+ },
3133
+ link: {
3134
+ name: "link",
3135
+ icon: linkIcon,
3136
+ title: "Insert Link",
3137
+ action: ({ editor, event }) => {
3138
+ insertLink(editor.textarea);
3139
+ editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
3140
+ }
3141
+ },
3142
+ h1: {
3143
+ name: "h1",
3144
+ icon: h1Icon,
3145
+ title: "Heading 1",
3146
+ action: ({ editor, event }) => {
3147
+ toggleH1(editor.textarea);
3148
+ editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
3149
+ }
3150
+ },
3151
+ h2: {
3152
+ name: "h2",
3153
+ icon: h2Icon,
3154
+ title: "Heading 2",
3155
+ action: ({ editor, event }) => {
3156
+ toggleH2(editor.textarea);
3157
+ editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
3158
+ }
3159
+ },
3160
+ h3: {
3161
+ name: "h3",
3162
+ icon: h3Icon,
3163
+ title: "Heading 3",
3164
+ action: ({ editor, event }) => {
3165
+ toggleH3(editor.textarea);
3166
+ editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
3167
+ }
3168
+ },
3169
+ bulletList: {
3170
+ name: "bulletList",
3171
+ icon: bulletListIcon,
3172
+ title: "Bullet List",
3173
+ action: ({ editor, event }) => {
3174
+ toggleBulletList(editor.textarea);
3175
+ editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
3176
+ }
3177
+ },
3178
+ orderedList: {
3179
+ name: "orderedList",
3180
+ icon: orderedListIcon,
3181
+ title: "Numbered List",
3182
+ action: ({ editor, event }) => {
3183
+ toggleNumberedList(editor.textarea);
3184
+ editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
3185
+ }
3186
+ },
3187
+ taskList: {
3188
+ name: "taskList",
3189
+ icon: taskListIcon,
3190
+ title: "Task List",
3191
+ action: ({ editor, event }) => {
3192
+ if (toggleTaskList) {
3193
+ toggleTaskList(editor.textarea);
3194
+ editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
3195
+ }
3196
+ }
3197
+ },
3198
+ quote: {
3199
+ name: "quote",
3200
+ icon: quoteIcon,
3201
+ title: "Quote",
3202
+ action: ({ editor, event }) => {
3203
+ toggleQuote(editor.textarea);
3204
+ editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
3205
+ }
3206
+ },
3207
+ viewMode: {
3208
+ name: "viewMode",
3209
+ icon: eyeIcon,
3210
+ title: "View mode"
3211
+ // Special: handled internally by Toolbar class as dropdown
3212
+ // No action property - dropdown behavior is internal
3213
+ }
3214
+ };
3215
+ var defaultToolbarButtons = [
3216
+ toolbarButtons.bold,
3217
+ toolbarButtons.italic,
3218
+ toolbarButtons.code,
3219
+ toolbarButtons.separator,
3220
+ toolbarButtons.link,
3221
+ toolbarButtons.separator,
3222
+ toolbarButtons.h1,
3223
+ toolbarButtons.h2,
3224
+ toolbarButtons.h3,
3225
+ toolbarButtons.separator,
3226
+ toolbarButtons.bulletList,
3227
+ toolbarButtons.orderedList,
3228
+ toolbarButtons.taskList,
3229
+ toolbarButtons.separator,
3230
+ toolbarButtons.quote,
3231
+ toolbarButtons.separator,
3232
+ toolbarButtons.viewMode
3233
+ ];
3234
+
3039
3235
  // src/overtype.js
3040
3236
  var _OverType = class _OverType {
3041
3237
  /**
@@ -3095,17 +3291,6 @@ var _OverType = class _OverType {
3095
3291
  }
3096
3292
  this.shortcuts = new ShortcutsManager(this);
3097
3293
  this.linkTooltip = new LinkTooltip(this);
3098
- if (this.options.toolbar) {
3099
- const toolbarButtons = typeof this.options.toolbar === "object" ? this.options.toolbar.buttons : null;
3100
- this.toolbar = new Toolbar(this, toolbarButtons);
3101
- this.toolbar.create();
3102
- this.textarea.addEventListener("selectionchange", () => {
3103
- this.toolbar.updateButtonStates();
3104
- });
3105
- this.textarea.addEventListener("input", () => {
3106
- this.toolbar.updateButtonStates();
3107
- });
3108
- }
3109
3294
  this.initialized = true;
3110
3295
  if (this.options.onChange) {
3111
3296
  this.options.onChange(this.getValue(), this);
@@ -3149,9 +3334,13 @@ var _OverType = class _OverType {
3149
3334
  showActiveLineRaw: false,
3150
3335
  showStats: false,
3151
3336
  toolbar: false,
3337
+ toolbarButtons: null,
3338
+ // Defaults to defaultToolbarButtons if toolbar: true
3152
3339
  statsFormatter: null,
3153
- smartLists: true
3340
+ smartLists: true,
3154
3341
  // Enable smart list continuation
3342
+ codeHighlighter: null
3343
+ // Per-instance code highlighter
3155
3344
  };
3156
3345
  const { theme, colors, ...cleanOptions } = options;
3157
3346
  return {
@@ -3327,6 +3516,41 @@ var _OverType = class _OverType {
3327
3516
  this.textarea.setAttribute("data-gramm_editor", "false");
3328
3517
  this.textarea.setAttribute("data-enable-grammarly", "false");
3329
3518
  }
3519
+ /**
3520
+ * Create and setup toolbar
3521
+ * @private
3522
+ */
3523
+ _createToolbar() {
3524
+ const toolbarButtons2 = this.options.toolbarButtons || defaultToolbarButtons;
3525
+ this.toolbar = new Toolbar(this, { toolbarButtons: toolbarButtons2 });
3526
+ this.toolbar.create();
3527
+ this._toolbarSelectionListener = () => {
3528
+ if (this.toolbar) {
3529
+ this.toolbar.updateButtonStates();
3530
+ }
3531
+ };
3532
+ this._toolbarInputListener = () => {
3533
+ if (this.toolbar) {
3534
+ this.toolbar.updateButtonStates();
3535
+ }
3536
+ };
3537
+ this.textarea.addEventListener("selectionchange", this._toolbarSelectionListener);
3538
+ this.textarea.addEventListener("input", this._toolbarInputListener);
3539
+ }
3540
+ /**
3541
+ * Cleanup toolbar event listeners
3542
+ * @private
3543
+ */
3544
+ _cleanupToolbarListeners() {
3545
+ if (this._toolbarSelectionListener) {
3546
+ this.textarea.removeEventListener("selectionchange", this._toolbarSelectionListener);
3547
+ this._toolbarSelectionListener = null;
3548
+ }
3549
+ if (this._toolbarInputListener) {
3550
+ this.textarea.removeEventListener("input", this._toolbarInputListener);
3551
+ this._toolbarInputListener = null;
3552
+ }
3553
+ }
3330
3554
  /**
3331
3555
  * Apply options to the editor
3332
3556
  * @private
@@ -3342,6 +3566,13 @@ var _OverType = class _OverType {
3342
3566
  } else {
3343
3567
  this.container.classList.remove("overtype-auto-resize");
3344
3568
  }
3569
+ if (this.options.toolbar && !this.toolbar) {
3570
+ this._createToolbar();
3571
+ } else if (!this.options.toolbar && this.toolbar) {
3572
+ this._cleanupToolbarListeners();
3573
+ this.toolbar.destroy();
3574
+ this.toolbar = null;
3575
+ }
3345
3576
  this.updatePreview();
3346
3577
  }
3347
3578
  /**
@@ -3351,7 +3582,8 @@ var _OverType = class _OverType {
3351
3582
  const text = this.textarea.value;
3352
3583
  const cursorPos = this.textarea.selectionStart;
3353
3584
  const activeLine = this._getCurrentLine(text, cursorPos);
3354
- const html = MarkdownParser.parse(text, activeLine, this.options.showActiveLineRaw);
3585
+ const isPreviewMode = this.container.dataset.mode === "preview";
3586
+ const html = MarkdownParser.parse(text, activeLine, this.options.showActiveLineRaw, this.options.codeHighlighter, isPreviewMode);
3355
3587
  this.preview.innerHTML = html || '<span style="color: #808080;">Start typing...</span>';
3356
3588
  this._applyCodeBlockBackgrounds();
3357
3589
  if (this.options.showStats && this.statsBar) {
@@ -3585,7 +3817,7 @@ var _OverType = class _OverType {
3585
3817
  */
3586
3818
  getRenderedHTML(options = {}) {
3587
3819
  const markdown = this.getValue();
3588
- let html = MarkdownParser.parse(markdown);
3820
+ let html = MarkdownParser.parse(markdown, -1, false, this.options.codeHighlighter);
3589
3821
  if (options.cleanHTML) {
3590
3822
  html = html.replace(/<span class="syntax-marker[^"]*">.*?<\/span>/g, "");
3591
3823
  html = html.replace(/\sclass="(bullet-list|ordered-list|code-fence|hr-marker|blockquote|url-part)"/g, "");
@@ -3637,6 +3869,33 @@ var _OverType = class _OverType {
3637
3869
  this._applyOptions();
3638
3870
  this.updatePreview();
3639
3871
  }
3872
+ /**
3873
+ * Set theme for this instance
3874
+ * @param {string|Object} theme - Theme name or custom theme object
3875
+ * @returns {this} Returns this for chaining
3876
+ */
3877
+ setTheme(theme) {
3878
+ this.instanceTheme = theme;
3879
+ const themeObj = typeof theme === "string" ? getTheme(theme) : theme;
3880
+ const themeName = typeof themeObj === "string" ? themeObj : themeObj.name;
3881
+ if (themeName) {
3882
+ this.container.setAttribute("data-theme", themeName);
3883
+ }
3884
+ if (themeObj && themeObj.colors) {
3885
+ const cssVars = themeToCSSVars(themeObj.colors);
3886
+ this.container.style.cssText += cssVars;
3887
+ }
3888
+ this.updatePreview();
3889
+ return this;
3890
+ }
3891
+ /**
3892
+ * Set instance-specific code highlighter
3893
+ * @param {Function|null} highlighter - Function that takes (code, language) and returns highlighted HTML
3894
+ */
3895
+ setCodeHighlighter(highlighter) {
3896
+ this.options.codeHighlighter = highlighter;
3897
+ this.updatePreview();
3898
+ }
3640
3899
  /**
3641
3900
  * Update stats bar
3642
3901
  * @private
@@ -3739,37 +3998,39 @@ var _OverType = class _OverType {
3739
3998
  }
3740
3999
  }
3741
4000
  /**
3742
- * Show or hide the plain textarea (toggle overlay visibility)
3743
- * @param {boolean} show - true to show plain textarea (hide overlay), false to show overlay
3744
- * @returns {boolean} Current plain textarea state
4001
+ * Show normal edit mode (overlay with markdown preview)
4002
+ * @returns {this} Returns this for chaining
3745
4003
  */
3746
- showPlainTextarea(show) {
3747
- if (show) {
3748
- this.container.classList.add("plain-mode");
3749
- } else {
3750
- this.container.classList.remove("plain-mode");
3751
- }
4004
+ showNormalEditMode() {
4005
+ this.container.dataset.mode = "normal";
4006
+ requestAnimationFrame(() => {
4007
+ this.textarea.scrollTop = this.preview.scrollTop;
4008
+ this.textarea.scrollLeft = this.preview.scrollLeft;
4009
+ });
4010
+ return this;
4011
+ }
4012
+ /**
4013
+ * Show plain textarea mode (no overlay)
4014
+ * @returns {this} Returns this for chaining
4015
+ */
4016
+ showPlainTextarea() {
4017
+ this.container.dataset.mode = "plain";
3752
4018
  if (this.toolbar) {
3753
4019
  const toggleBtn = this.container.querySelector('[data-action="toggle-plain"]');
3754
4020
  if (toggleBtn) {
3755
- toggleBtn.classList.toggle("active", !show);
3756
- toggleBtn.title = show ? "Show markdown preview" : "Show plain textarea";
4021
+ toggleBtn.classList.remove("active");
4022
+ toggleBtn.title = "Show markdown preview";
3757
4023
  }
3758
4024
  }
3759
- return show;
4025
+ return this;
3760
4026
  }
3761
4027
  /**
3762
- * Show/hide preview mode
3763
- * @param {boolean} show - Show preview mode if true, edit mode if false
3764
- * @returns {boolean} Current preview mode state
4028
+ * Show preview mode (read-only view)
4029
+ * @returns {this} Returns this for chaining
3765
4030
  */
3766
- showPreviewMode(show) {
3767
- if (show) {
3768
- this.container.classList.add("preview-mode");
3769
- } else {
3770
- this.container.classList.remove("preview-mode");
3771
- }
3772
- return show;
4031
+ showPreviewMode() {
4032
+ this.container.dataset.mode = "preview";
4033
+ return this;
3773
4034
  }
3774
4035
  /**
3775
4036
  * Destroy the editor instance
@@ -3849,16 +4110,16 @@ var _OverType = class _OverType {
3849
4110
  _OverType.currentTheme = themeObj;
3850
4111
  _OverType.injectStyles(true);
3851
4112
  document.querySelectorAll(".overtype-container").forEach((container) => {
3852
- const themeName = typeof themeObj === "string" ? themeObj : themeObj.name;
3853
- if (themeName) {
3854
- container.setAttribute("data-theme", themeName);
4113
+ const themeName2 = typeof themeObj === "string" ? themeObj : themeObj.name;
4114
+ if (themeName2) {
4115
+ container.setAttribute("data-theme", themeName2);
3855
4116
  }
3856
4117
  });
3857
4118
  document.querySelectorAll(".overtype-wrapper").forEach((wrapper) => {
3858
4119
  if (!wrapper.closest(".overtype-container")) {
3859
- const themeName = typeof themeObj === "string" ? themeObj : themeObj.name;
3860
- if (themeName) {
3861
- wrapper.setAttribute("data-theme", themeName);
4120
+ const themeName2 = typeof themeObj === "string" ? themeObj : themeObj.name;
4121
+ if (themeName2) {
4122
+ wrapper.setAttribute("data-theme", themeName2);
3862
4123
  }
3863
4124
  }
3864
4125
  const instance = wrapper._instance;
@@ -3866,6 +4127,36 @@ var _OverType = class _OverType {
3866
4127
  instance.updatePreview();
3867
4128
  }
3868
4129
  });
4130
+ const themeName = typeof themeObj === "string" ? themeObj : themeObj.name;
4131
+ document.querySelectorAll("overtype-editor").forEach((webComponent) => {
4132
+ if (themeName && typeof webComponent.setAttribute === "function") {
4133
+ webComponent.setAttribute("theme", themeName);
4134
+ }
4135
+ if (typeof webComponent.refreshTheme === "function") {
4136
+ webComponent.refreshTheme();
4137
+ }
4138
+ });
4139
+ }
4140
+ /**
4141
+ * Set global code highlighter for all OverType instances
4142
+ * @param {Function|null} highlighter - Function that takes (code, language) and returns highlighted HTML
4143
+ */
4144
+ static setCodeHighlighter(highlighter) {
4145
+ MarkdownParser.setCodeHighlighter(highlighter);
4146
+ document.querySelectorAll(".overtype-wrapper").forEach((wrapper) => {
4147
+ const instance = wrapper._instance;
4148
+ if (instance && instance.updatePreview) {
4149
+ instance.updatePreview();
4150
+ }
4151
+ });
4152
+ document.querySelectorAll("overtype-editor").forEach((webComponent) => {
4153
+ if (typeof webComponent.getEditor === "function") {
4154
+ const instance = webComponent.getEditor();
4155
+ if (instance && instance.updatePreview) {
4156
+ instance.updatePreview();
4157
+ }
4158
+ }
4159
+ });
3869
4160
  }
3870
4161
  /**
3871
4162
  * Initialize global event listeners
@@ -3930,7 +4221,9 @@ OverType.currentTheme = solar;
3930
4221
  var overtype_default = OverType;
3931
4222
  // Annotate the CommonJS export names for ESM import in node:
3932
4223
  0 && (module.exports = {
3933
- OverType
4224
+ OverType,
4225
+ defaultToolbarButtons,
4226
+ toolbarButtons
3934
4227
  });
3935
4228
  /**
3936
4229
  * OverType - A lightweight markdown editor library with perfect WYSIWYG alignment