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.
@@ -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
@@ -20,6 +20,13 @@ var MarkdownParser = class {
20
20
  static resetLinkIndex() {
21
21
  this.linkIndex = 0;
22
22
  }
23
+ /**
24
+ * Set global code highlighter function
25
+ * @param {Function|null} highlighter - Function that takes (code, language) and returns highlighted HTML
26
+ */
27
+ static setCodeHighlighter(highlighter) {
28
+ this.codeHighlighter = highlighter;
29
+ }
23
30
  /**
24
31
  * Escape HTML special characters
25
32
  * @param {string} text - Raw text to escape
@@ -88,6 +95,22 @@ var MarkdownParser = class {
88
95
  return `${indent}<li class="bullet-list"><span class="syntax-marker">${marker} </span>${content}</li>`;
89
96
  });
90
97
  }
98
+ /**
99
+ * Parse task lists (GitHub Flavored Markdown checkboxes)
100
+ * @param {string} html - HTML line to parse
101
+ * @param {boolean} isPreviewMode - Whether to render actual checkboxes (preview) or keep syntax visible (normal)
102
+ * @returns {string} Parsed task list item
103
+ */
104
+ static parseTaskList(html, isPreviewMode = false) {
105
+ return html.replace(/^((?:&nbsp;)*)-\s+\[([ xX])\]\s+(.+)$/, (match, indent, checked, content) => {
106
+ if (isPreviewMode) {
107
+ const isChecked = checked.toLowerCase() === "x";
108
+ return `${indent}<li class="task-list"><input type="checkbox" disabled ${isChecked ? "checked" : ""}> ${content}</li>`;
109
+ } else {
110
+ return `${indent}<li class="task-list"><span class="syntax-marker">- [${checked}] </span>${content}</li>`;
111
+ }
112
+ });
113
+ }
91
114
  /**
92
115
  * Parse numbered lists
93
116
  * @param {string} html - HTML line to parse
@@ -277,7 +300,7 @@ var MarkdownParser = class {
277
300
  processedLinkText = this.parseItalic(processedLinkText);
278
301
  const anchorName = `--link-${this.linkIndex++}`;
279
302
  const safeUrl = this.sanitizeUrl(sanctuary.url);
280
- 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>`;
303
+ replacement = `<a href="${safeUrl}" style="anchor-name: ${anchorName}"><span class="syntax-marker">[</span>${processedLinkText}<span class="syntax-marker url-part">](${sanctuary.url})</span></a>`;
281
304
  }
282
305
  html = html.replace(placeholder, replacement);
283
306
  });
@@ -302,7 +325,7 @@ var MarkdownParser = class {
302
325
  * @param {string} line - Raw markdown line
303
326
  * @returns {string} Parsed HTML line
304
327
  */
305
- static parseLine(line) {
328
+ static parseLine(line, isPreviewMode = false) {
306
329
  let html = this.escapeHtml(line);
307
330
  html = this.preserveIndentation(html, line);
308
331
  const horizontalRule = this.parseHorizontalRule(html);
@@ -313,6 +336,7 @@ var MarkdownParser = class {
313
336
  return codeBlock;
314
337
  html = this.parseHeader(html);
315
338
  html = this.parseBlockquote(html);
339
+ html = this.parseTaskList(html, isPreviewMode);
316
340
  html = this.parseBulletList(html);
317
341
  html = this.parseNumberedList(html);
318
342
  html = this.parseInlineElements(html);
@@ -326,9 +350,10 @@ var MarkdownParser = class {
326
350
  * @param {string} text - Full markdown text
327
351
  * @param {number} activeLine - Currently active line index (optional)
328
352
  * @param {boolean} showActiveLineRaw - Show raw markdown on active line
353
+ * @param {Function} instanceHighlighter - Instance-specific code highlighter (optional, overrides global if provided)
329
354
  * @returns {string} Parsed HTML
330
355
  */
331
- static parse(text, activeLine = -1, showActiveLineRaw = false) {
356
+ static parse(text, activeLine = -1, showActiveLineRaw = false, instanceHighlighter, isPreviewMode = false) {
332
357
  this.resetLinkIndex();
333
358
  const lines = text.split("\n");
334
359
  let inCodeBlock = false;
@@ -340,26 +365,27 @@ var MarkdownParser = class {
340
365
  const codeFenceRegex = /^```[^`]*$/;
341
366
  if (codeFenceRegex.test(line)) {
342
367
  inCodeBlock = !inCodeBlock;
343
- return this.parseLine(line);
368
+ return this.parseLine(line, isPreviewMode);
344
369
  }
345
370
  if (inCodeBlock) {
346
371
  const escaped = this.escapeHtml(line);
347
372
  const indented = this.preserveIndentation(escaped, line);
348
373
  return `<div>${indented || "&nbsp;"}</div>`;
349
374
  }
350
- return this.parseLine(line);
375
+ return this.parseLine(line, isPreviewMode);
351
376
  });
352
377
  const html = parsedLines.join("");
353
- return this.postProcessHTML(html);
378
+ return this.postProcessHTML(html, instanceHighlighter);
354
379
  }
355
380
  /**
356
381
  * Post-process HTML to consolidate lists and code blocks
357
382
  * @param {string} html - HTML to post-process
383
+ * @param {Function} instanceHighlighter - Instance-specific code highlighter (optional, overrides global if provided)
358
384
  * @returns {string} Post-processed HTML with consolidated lists and code blocks
359
385
  */
360
- static postProcessHTML(html) {
386
+ static postProcessHTML(html, instanceHighlighter) {
361
387
  if (typeof document === "undefined" || !document) {
362
- return this.postProcessHTMLManual(html);
388
+ return this.postProcessHTMLManual(html, instanceHighlighter);
363
389
  }
364
390
  const container = document.createElement("div");
365
391
  container.innerHTML = html;
@@ -388,8 +414,28 @@ var MarkdownParser = class {
388
414
  }
389
415
  container.insertBefore(currentCodeBlock, child.nextSibling);
390
416
  currentCodeBlock._codeElement = codeElement;
417
+ currentCodeBlock._language = lang;
418
+ currentCodeBlock._codeContent = "";
391
419
  continue;
392
420
  } else {
421
+ const highlighter = instanceHighlighter || this.codeHighlighter;
422
+ if (currentCodeBlock && highlighter && currentCodeBlock._codeContent) {
423
+ try {
424
+ const result = highlighter(
425
+ currentCodeBlock._codeContent,
426
+ currentCodeBlock._language || ""
427
+ );
428
+ if (result && typeof result.then === "function") {
429
+ 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.");
430
+ } else {
431
+ if (result && typeof result === "string" && result.trim()) {
432
+ currentCodeBlock._codeElement.innerHTML = result;
433
+ }
434
+ }
435
+ } catch (error) {
436
+ console.warn("Code highlighting failed:", error);
437
+ }
438
+ }
393
439
  inCodeBlock = false;
394
440
  currentCodeBlock = null;
395
441
  continue;
@@ -398,10 +444,14 @@ var MarkdownParser = class {
398
444
  }
399
445
  if (inCodeBlock && currentCodeBlock && child.tagName === "DIV" && !child.querySelector(".code-fence")) {
400
446
  const codeElement = currentCodeBlock._codeElement || currentCodeBlock.querySelector("code");
447
+ if (currentCodeBlock._codeContent.length > 0) {
448
+ currentCodeBlock._codeContent += "\n";
449
+ }
450
+ const lineText = child.textContent.replace(/\u00A0/g, " ");
451
+ currentCodeBlock._codeContent += lineText;
401
452
  if (codeElement.textContent.length > 0) {
402
453
  codeElement.textContent += "\n";
403
454
  }
404
- const lineText = child.textContent.replace(/\u00A0/g, " ");
405
455
  codeElement.textContent += lineText;
406
456
  child.remove();
407
457
  continue;
@@ -447,9 +497,10 @@ var MarkdownParser = class {
447
497
  /**
448
498
  * Manual post-processing for Node.js environments (without DOM)
449
499
  * @param {string} html - HTML to post-process
500
+ * @param {Function} instanceHighlighter - Instance-specific code highlighter (optional, overrides global if provided)
450
501
  * @returns {string} Post-processed HTML
451
502
  */
452
- static postProcessHTMLManual(html) {
503
+ static postProcessHTMLManual(html, instanceHighlighter) {
453
504
  let processed = html;
454
505
  processed = processed.replace(/((?:<div>(?:&nbsp;)*<li class="bullet-list">.*?<\/li><\/div>\s*)+)/gs, (match) => {
455
506
  const divs = match.match(/<div>(?:&nbsp;)*<li class="bullet-list">.*?<\/li><\/div>/gs) || [];
@@ -494,8 +545,25 @@ var MarkdownParser = class {
494
545
  }).join("\n");
495
546
  const lang = openFence.slice(3).trim();
496
547
  const langClass = lang ? ` class="language-${lang}"` : "";
548
+ let highlightedContent = codeContent;
549
+ const highlighter = instanceHighlighter || this.codeHighlighter;
550
+ if (highlighter) {
551
+ try {
552
+ const decodedCode = codeContent.replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
553
+ const result2 = highlighter(decodedCode, lang);
554
+ if (result2 && typeof result2.then === "function") {
555
+ console.warn("Async highlighters are not supported in Node.js (non-DOM) context. Use synchronous highlighters for server-side rendering.");
556
+ } else {
557
+ if (result2 && typeof result2 === "string" && result2.trim()) {
558
+ highlightedContent = result2;
559
+ }
560
+ }
561
+ } catch (error) {
562
+ console.warn("Code highlighting failed:", error);
563
+ }
564
+ }
497
565
  let result = `<div><span class="code-fence">${openFence}</span></div>`;
498
- result += `<pre class="code-block"><code${langClass}>${codeContent}</code></pre>`;
566
+ result += `<pre class="code-block"><code${langClass}>${highlightedContent}</code></pre>`;
499
567
  result += `<div><span class="code-fence">${closeFence}</span></div>`;
500
568
  return result;
501
569
  });
@@ -634,6 +702,8 @@ var MarkdownParser = class {
634
702
  };
635
703
  // Track link index for anchor naming
636
704
  __publicField(MarkdownParser, "linkIndex", 0);
705
+ // Global code highlighter function
706
+ __publicField(MarkdownParser, "codeHighlighter", null);
637
707
  /**
638
708
  * List pattern definitions
639
709
  */
@@ -2053,12 +2123,14 @@ function generateStyles(options = {}) {
2053
2123
  /* Code block styling in normal mode - yellow background */
2054
2124
  .overtype-wrapper .overtype-preview pre.code-block {
2055
2125
  background: var(--code-bg, rgba(244, 211, 94, 0.4)) !important;
2126
+ white-space: break-spaces !important; /* Prevent horizontal scrollbar that breaks alignment */
2056
2127
  }
2057
2128
 
2058
2129
  /* Code inside pre blocks - remove background */
2059
2130
  .overtype-wrapper .overtype-preview pre code {
2060
2131
  background: transparent !important;
2061
2132
  color: var(--code, #0d3b66) !important;
2133
+ font-family: ${fontFamily} !important; /* Match textarea font exactly for alignment */
2062
2134
  }
2063
2135
 
2064
2136
  /* Blockquotes */
@@ -2285,11 +2357,11 @@ function generateStyles(options = {}) {
2285
2357
  }
2286
2358
 
2287
2359
  /* Plain mode - hide preview and show textarea text */
2288
- .overtype-container.plain-mode .overtype-preview {
2360
+ .overtype-container[data-mode="plain"] .overtype-preview {
2289
2361
  display: none !important;
2290
2362
  }
2291
2363
 
2292
- .overtype-container.plain-mode .overtype-input {
2364
+ .overtype-container[data-mode="plain"] .overtype-input {
2293
2365
  color: var(--text, #0d3b66) !important;
2294
2366
  /* Use system font stack for better plain text readability */
2295
2367
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
@@ -2297,7 +2369,7 @@ function generateStyles(options = {}) {
2297
2369
  }
2298
2370
 
2299
2371
  /* Ensure textarea remains transparent in overlay mode */
2300
- .overtype-container:not(.plain-mode) .overtype-input {
2372
+ .overtype-container:not([data-mode="plain"]) .overtype-input {
2301
2373
  color: transparent !important;
2302
2374
  }
2303
2375
 
@@ -2350,37 +2422,43 @@ function generateStyles(options = {}) {
2350
2422
  color: var(--h1, #007bff);
2351
2423
  }
2352
2424
 
2425
+ .overtype-dropdown-icon {
2426
+ width: 20px;
2427
+ margin-right: 8px;
2428
+ text-align: center;
2429
+ }
2430
+
2353
2431
  /* Preview mode styles */
2354
- .overtype-container.preview-mode .overtype-input {
2432
+ .overtype-container[data-mode="preview"] .overtype-input {
2355
2433
  display: none !important;
2356
2434
  }
2357
2435
 
2358
- .overtype-container.preview-mode .overtype-preview {
2436
+ .overtype-container[data-mode="preview"] .overtype-preview {
2359
2437
  pointer-events: auto !important;
2360
2438
  user-select: text !important;
2361
2439
  cursor: text !important;
2362
2440
  }
2363
2441
 
2364
2442
  /* Hide syntax markers in preview mode */
2365
- .overtype-container.preview-mode .syntax-marker {
2443
+ .overtype-container[data-mode="preview"] .syntax-marker {
2366
2444
  display: none !important;
2367
2445
  }
2368
2446
 
2369
2447
  /* Hide URL part of links in preview mode - extra specificity */
2370
- .overtype-container.preview-mode .syntax-marker.url-part,
2371
- .overtype-container.preview-mode .url-part {
2448
+ .overtype-container[data-mode="preview"] .syntax-marker.url-part,
2449
+ .overtype-container[data-mode="preview"] .url-part {
2372
2450
  display: none !important;
2373
2451
  }
2374
2452
 
2375
2453
  /* Hide all syntax markers inside links too */
2376
- .overtype-container.preview-mode a .syntax-marker {
2454
+ .overtype-container[data-mode="preview"] a .syntax-marker {
2377
2455
  display: none !important;
2378
2456
  }
2379
2457
 
2380
2458
  /* Headers - restore proper sizing in preview mode */
2381
- .overtype-container.preview-mode .overtype-wrapper .overtype-preview h1,
2382
- .overtype-container.preview-mode .overtype-wrapper .overtype-preview h2,
2383
- .overtype-container.preview-mode .overtype-wrapper .overtype-preview h3 {
2459
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview h1,
2460
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview h2,
2461
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview h3 {
2384
2462
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important;
2385
2463
  font-weight: 600 !important;
2386
2464
  margin: 0 !important;
@@ -2389,41 +2467,63 @@ function generateStyles(options = {}) {
2389
2467
  line-height: 1 !important; /* Tight line height for headings */
2390
2468
  }
2391
2469
 
2392
- .overtype-container.preview-mode .overtype-wrapper .overtype-preview h1 {
2470
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview h1 {
2393
2471
  font-size: 2em !important;
2394
2472
  }
2395
2473
 
2396
- .overtype-container.preview-mode .overtype-wrapper .overtype-preview h2 {
2474
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview h2 {
2397
2475
  font-size: 1.5em !important;
2398
2476
  }
2399
2477
 
2400
- .overtype-container.preview-mode .overtype-wrapper .overtype-preview h3 {
2478
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview h3 {
2401
2479
  font-size: 1.17em !important;
2402
2480
  }
2403
2481
 
2404
2482
  /* Lists - restore list styling in preview mode */
2405
- .overtype-container.preview-mode .overtype-wrapper .overtype-preview ul {
2483
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview ul {
2406
2484
  display: block !important;
2407
2485
  list-style: disc !important;
2408
2486
  padding-left: 2em !important;
2409
2487
  margin: 1em 0 !important;
2410
2488
  }
2411
2489
 
2412
- .overtype-container.preview-mode .overtype-wrapper .overtype-preview ol {
2490
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview ol {
2413
2491
  display: block !important;
2414
2492
  list-style: decimal !important;
2415
2493
  padding-left: 2em !important;
2416
2494
  margin: 1em 0 !important;
2417
2495
  }
2418
2496
 
2419
- .overtype-container.preview-mode .overtype-wrapper .overtype-preview li {
2497
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview li {
2420
2498
  display: list-item !important;
2421
2499
  margin: 0 !important;
2422
2500
  padding: 0 !important;
2423
2501
  }
2424
2502
 
2503
+ /* Task list checkboxes - only in preview mode */
2504
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview li.task-list {
2505
+ list-style: none !important;
2506
+ position: relative !important;
2507
+ }
2508
+
2509
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview li.task-list input[type="checkbox"] {
2510
+ margin-right: 0.5em !important;
2511
+ cursor: default !important;
2512
+ vertical-align: middle !important;
2513
+ }
2514
+
2515
+ /* Task list in normal mode - keep syntax visible */
2516
+ .overtype-container:not([data-mode="preview"]) .overtype-wrapper .overtype-preview li.task-list {
2517
+ list-style: none !important;
2518
+ }
2519
+
2520
+ .overtype-container:not([data-mode="preview"]) .overtype-wrapper .overtype-preview li.task-list .syntax-marker {
2521
+ color: var(--syntax, #999999) !important;
2522
+ font-weight: normal !important;
2523
+ }
2524
+
2425
2525
  /* Links - make clickable in preview mode */
2426
- .overtype-container.preview-mode .overtype-wrapper .overtype-preview a {
2526
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview a {
2427
2527
  pointer-events: auto !important;
2428
2528
  cursor: pointer !important;
2429
2529
  color: var(--link, #0066cc) !important;
@@ -2431,7 +2531,7 @@ function generateStyles(options = {}) {
2431
2531
  }
2432
2532
 
2433
2533
  /* Code blocks - proper pre/code styling in preview mode */
2434
- .overtype-container.preview-mode .overtype-wrapper .overtype-preview pre.code-block {
2534
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview pre.code-block {
2435
2535
  background: #2d2d2d !important;
2436
2536
  color: #f8f8f2 !important;
2437
2537
  padding: 1.2em !important;
@@ -2442,11 +2542,11 @@ function generateStyles(options = {}) {
2442
2542
  }
2443
2543
 
2444
2544
  /* Cave theme code block background in preview mode */
2445
- .overtype-container[data-theme="cave"].preview-mode .overtype-wrapper .overtype-preview pre.code-block {
2545
+ .overtype-container[data-theme="cave"][data-mode="preview"] .overtype-wrapper .overtype-preview pre.code-block {
2446
2546
  background: #11171F !important;
2447
2547
  }
2448
2548
 
2449
- .overtype-container.preview-mode .overtype-wrapper .overtype-preview pre.code-block code {
2549
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview pre.code-block code {
2450
2550
  background: transparent !important;
2451
2551
  color: inherit !important;
2452
2552
  padding: 0 !important;
@@ -2456,16 +2556,16 @@ function generateStyles(options = {}) {
2456
2556
  }
2457
2557
 
2458
2558
  /* Hide old code block lines and fences in preview mode */
2459
- .overtype-container.preview-mode .overtype-wrapper .overtype-preview .code-block-line {
2559
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview .code-block-line {
2460
2560
  display: none !important;
2461
2561
  }
2462
2562
 
2463
- .overtype-container.preview-mode .overtype-wrapper .overtype-preview .code-fence {
2563
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview .code-fence {
2464
2564
  display: none !important;
2465
2565
  }
2466
2566
 
2467
2567
  /* Blockquotes - enhanced styling in preview mode */
2468
- .overtype-container.preview-mode .overtype-wrapper .overtype-preview .blockquote {
2568
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview .blockquote {
2469
2569
  display: block !important;
2470
2570
  border-left: 4px solid var(--blockquote, #ddd) !important;
2471
2571
  padding-left: 1em !important;
@@ -2474,7 +2574,7 @@ function generateStyles(options = {}) {
2474
2574
  }
2475
2575
 
2476
2576
  /* Typography improvements in preview mode */
2477
- .overtype-container.preview-mode .overtype-wrapper .overtype-preview {
2577
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview {
2478
2578
  font-family: Georgia, 'Times New Roman', serif !important;
2479
2579
  font-size: 16px !important;
2480
2580
  line-height: 1.8 !important;
@@ -2482,7 +2582,7 @@ function generateStyles(options = {}) {
2482
2582
  }
2483
2583
 
2484
2584
  /* Inline code in preview mode - keep monospace */
2485
- .overtype-container.preview-mode .overtype-wrapper .overtype-preview code {
2585
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview code {
2486
2586
  font-family: ${fontFamily} !important;
2487
2587
  font-size: 0.9em !important;
2488
2588
  background: rgba(135, 131, 120, 0.15) !important;
@@ -2491,236 +2591,243 @@ function generateStyles(options = {}) {
2491
2591
  }
2492
2592
 
2493
2593
  /* Strong and em elements in preview mode */
2494
- .overtype-container.preview-mode .overtype-wrapper .overtype-preview strong {
2594
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview strong {
2495
2595
  font-weight: 700 !important;
2496
2596
  color: inherit !important; /* Use parent text color */
2497
2597
  }
2498
2598
 
2499
- .overtype-container.preview-mode .overtype-wrapper .overtype-preview em {
2599
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview em {
2500
2600
  font-style: italic !important;
2501
2601
  color: inherit !important; /* Use parent text color */
2502
2602
  }
2503
2603
 
2504
2604
  /* HR in preview mode */
2505
- .overtype-container.preview-mode .overtype-wrapper .overtype-preview .hr-marker {
2605
+ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview .hr-marker {
2506
2606
  display: block !important;
2507
2607
  border-top: 2px solid var(--hr, #ddd) !important;
2508
2608
  text-indent: -9999px !important;
2509
2609
  height: 2px !important;
2510
2610
  }
2511
2611
 
2612
+ /* Link Tooltip - CSS Anchor Positioning */
2613
+ @supports (position-anchor: --x) and (position-area: center) {
2614
+ .overtype-link-tooltip {
2615
+ position: absolute;
2616
+ position-anchor: var(--target-anchor, --link-0);
2617
+ position-area: block-end center;
2618
+ margin-top: 8px !important;
2619
+
2620
+ background: #333 !important;
2621
+ color: white !important;
2622
+ padding: 6px 10px !important;
2623
+ border-radius: 16px !important;
2624
+ font-size: 12px !important;
2625
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif !important;
2626
+ display: none !important;
2627
+ z-index: 10000 !important;
2628
+ cursor: pointer !important;
2629
+ box-shadow: 0 2px 8px rgba(0,0,0,0.3) !important;
2630
+ max-width: 300px !important;
2631
+ white-space: nowrap !important;
2632
+ overflow: hidden !important;
2633
+ text-overflow: ellipsis !important;
2634
+
2635
+ position-try: most-width block-end inline-end, flip-inline, block-start center;
2636
+ position-visibility: anchors-visible;
2637
+ }
2638
+
2639
+ .overtype-link-tooltip.visible {
2640
+ display: flex !important;
2641
+ }
2642
+ }
2643
+
2512
2644
  ${mobileStyles}
2513
2645
  `;
2514
2646
  }
2515
2647
 
2516
- // src/icons.js
2517
- var boldIcon = `<svg viewBox="0 0 18 18">
2518
- <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>
2519
- <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>
2520
- </svg>`;
2521
- var italicIcon = `<svg viewBox="0 0 18 18">
2522
- <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="7" x2="13" y1="4" y2="4"></line>
2523
- <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="5" x2="11" y1="14" y2="14"></line>
2524
- <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="8" x2="10" y1="14" y2="4"></line>
2525
- </svg>`;
2526
- var h1Icon = `<svg viewBox="0 0 18 18">
2527
- <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>
2528
- </svg>`;
2529
- var h2Icon = `<svg viewBox="0 0 18 18">
2530
- <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>
2531
- </svg>`;
2532
- var h3Icon = `<svg viewBox="0 0 18 18">
2533
- <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>
2534
- </svg>`;
2535
- var linkIcon = `<svg viewBox="0 0 18 18">
2536
- <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="7" x2="11" y1="7" y2="11"></line>
2537
- <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>
2538
- <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>
2539
- </svg>`;
2540
- var codeIcon = `<svg viewBox="0 0 18 18">
2541
- <polyline stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" points="5 7 3 9 5 11"></polyline>
2542
- <polyline stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" points="13 7 15 9 13 11"></polyline>
2543
- <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="10" x2="8" y1="5" y2="13"></line>
2544
- </svg>`;
2545
- var bulletListIcon = `<svg viewBox="0 0 18 18">
2546
- <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="6" x2="15" y1="4" y2="4"></line>
2547
- <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="6" x2="15" y1="9" y2="9"></line>
2548
- <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="6" x2="15" y1="14" y2="14"></line>
2549
- <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="3" x2="3" y1="4" y2="4"></line>
2550
- <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="3" x2="3" y1="9" y2="9"></line>
2551
- <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="3" x2="3" y1="14" y2="14"></line>
2552
- </svg>`;
2553
- var orderedListIcon = `<svg viewBox="0 0 18 18">
2554
- <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="7" x2="15" y1="4" y2="4"></line>
2555
- <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="7" x2="15" y1="9" y2="9"></line>
2556
- <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="7" x2="15" y1="14" y2="14"></line>
2557
- <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>
2558
- <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>
2559
- <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>
2560
- <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>
2561
- </svg>`;
2562
- var quoteIcon = `<svg viewBox="2 2 20 20">
2563
- <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>
2564
- <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>
2565
- </svg>`;
2566
- var taskListIcon = `<svg viewBox="0 0 18 18">
2567
- <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="8" x2="16" y1="4" y2="4"></line>
2568
- <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="8" x2="16" y1="9" y2="9"></line>
2569
- <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="8" x2="16" y1="14" y2="14"></line>
2570
- <rect stroke="currentColor" fill="none" stroke-width="1.5" x="2" y="3" width="3" height="3" rx="0.5"></rect>
2571
- <rect stroke="currentColor" fill="none" stroke-width="1.5" x="2" y="13" width="3" height="3" rx="0.5"></rect>
2572
- <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>
2573
- </svg>`;
2574
- var eyeIcon = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2575
- <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" fill="none"></path>
2576
- <circle cx="12" cy="12" r="3" fill="none"></circle>
2577
- </svg>`;
2578
-
2579
2648
  // src/toolbar.js
2580
2649
  var Toolbar = class {
2581
- constructor(editor, buttonConfig = null) {
2650
+ constructor(editor, options = {}) {
2582
2651
  this.editor = editor;
2583
2652
  this.container = null;
2584
2653
  this.buttons = {};
2585
- this.buttonConfig = buttonConfig;
2654
+ this.toolbarButtons = options.toolbarButtons || [];
2586
2655
  }
2587
2656
  /**
2588
- * Create and attach toolbar to editor
2657
+ * Create and render toolbar
2589
2658
  */
2590
2659
  create() {
2591
- var _a;
2592
2660
  this.container = document.createElement("div");
2593
2661
  this.container.className = "overtype-toolbar";
2594
2662
  this.container.setAttribute("role", "toolbar");
2595
- this.container.setAttribute("aria-label", "Text formatting");
2596
- const buttonConfig = (_a = this.buttonConfig) != null ? _a : [
2597
- { name: "bold", icon: boldIcon, title: "Bold (Ctrl+B)", action: "toggleBold" },
2598
- { name: "italic", icon: italicIcon, title: "Italic (Ctrl+I)", action: "toggleItalic" },
2599
- { separator: true },
2600
- { name: "h1", icon: h1Icon, title: "Heading 1", action: "insertH1" },
2601
- { name: "h2", icon: h2Icon, title: "Heading 2", action: "insertH2" },
2602
- { name: "h3", icon: h3Icon, title: "Heading 3", action: "insertH3" },
2603
- { separator: true },
2604
- { name: "link", icon: linkIcon, title: "Insert Link (Ctrl+K)", action: "insertLink" },
2605
- { name: "code", icon: codeIcon, title: "Code (Ctrl+`)", action: "toggleCode" },
2606
- { separator: true },
2607
- { name: "quote", icon: quoteIcon, title: "Quote", action: "toggleQuote" },
2608
- { separator: true },
2609
- { name: "bulletList", icon: bulletListIcon, title: "Bullet List", action: "toggleBulletList" },
2610
- { name: "orderedList", icon: orderedListIcon, title: "Numbered List", action: "toggleNumberedList" },
2611
- { name: "taskList", icon: taskListIcon, title: "Task List", action: "toggleTaskList" },
2612
- { separator: true },
2613
- { name: "viewMode", icon: eyeIcon, title: "View mode", action: "toggle-view-menu", hasDropdown: true }
2614
- ];
2615
- buttonConfig.forEach((config) => {
2616
- if (config.separator) {
2617
- const separator = document.createElement("div");
2618
- separator.className = "overtype-toolbar-separator";
2619
- separator.setAttribute("role", "separator");
2663
+ this.container.setAttribute("aria-label", "Formatting toolbar");
2664
+ this.toolbarButtons.forEach((buttonConfig) => {
2665
+ if (buttonConfig.name === "separator") {
2666
+ const separator = this.createSeparator();
2620
2667
  this.container.appendChild(separator);
2621
2668
  } else {
2622
- const button = this.createButton(config);
2623
- this.buttons[config.name] = button;
2669
+ const button = this.createButton(buttonConfig);
2670
+ this.buttons[buttonConfig.name] = button;
2624
2671
  this.container.appendChild(button);
2625
2672
  }
2626
2673
  });
2627
- const container = this.editor.element.querySelector(".overtype-container");
2628
- const wrapper = this.editor.element.querySelector(".overtype-wrapper");
2629
- if (container && wrapper) {
2630
- container.insertBefore(this.container, wrapper);
2631
- }
2632
- return this.container;
2674
+ this.editor.wrapper.insertBefore(this.container, this.editor.wrapper.firstChild);
2633
2675
  }
2634
2676
  /**
2635
- * Create individual toolbar button
2677
+ * Create a toolbar separator
2636
2678
  */
2637
- createButton(config) {
2679
+ createSeparator() {
2680
+ const separator = document.createElement("div");
2681
+ separator.className = "overtype-toolbar-separator";
2682
+ separator.setAttribute("role", "separator");
2683
+ return separator;
2684
+ }
2685
+ /**
2686
+ * Create a toolbar button
2687
+ */
2688
+ createButton(buttonConfig) {
2638
2689
  const button = document.createElement("button");
2639
2690
  button.className = "overtype-toolbar-button";
2640
2691
  button.type = "button";
2641
- button.title = config.title;
2642
- button.setAttribute("aria-label", config.title);
2643
- button.setAttribute("data-action", config.action);
2644
- button.innerHTML = config.icon;
2645
- if (config.hasDropdown) {
2692
+ button.setAttribute("data-button", buttonConfig.name);
2693
+ button.title = buttonConfig.title || "";
2694
+ button.setAttribute("aria-label", buttonConfig.title || buttonConfig.name);
2695
+ button.innerHTML = this.sanitizeSVG(buttonConfig.icon || "");
2696
+ if (buttonConfig.name === "viewMode") {
2646
2697
  button.classList.add("has-dropdown");
2647
- if (config.name === "viewMode") {
2648
- this.viewModeButton = button;
2649
- }
2698
+ button.dataset.dropdown = "true";
2699
+ button.addEventListener("click", (e) => {
2700
+ e.preventDefault();
2701
+ this.toggleViewModeDropdown(button);
2702
+ });
2703
+ return button;
2650
2704
  }
2651
- button.addEventListener("click", (e) => {
2705
+ button._clickHandler = async (e) => {
2652
2706
  e.preventDefault();
2653
- this.handleAction(config.action, button);
2654
- });
2707
+ this.editor.textarea.focus();
2708
+ try {
2709
+ if (buttonConfig.action) {
2710
+ await buttonConfig.action({
2711
+ editor: this.editor,
2712
+ getValue: () => this.editor.getValue(),
2713
+ setValue: (value) => this.editor.setValue(value),
2714
+ event: e
2715
+ });
2716
+ }
2717
+ } catch (error) {
2718
+ console.error(`Button "${buttonConfig.name}" error:`, error);
2719
+ this.editor.wrapper.dispatchEvent(new CustomEvent("button-error", {
2720
+ detail: { buttonName: buttonConfig.name, error }
2721
+ }));
2722
+ button.classList.add("button-error");
2723
+ button.style.animation = "buttonError 0.3s";
2724
+ setTimeout(() => {
2725
+ button.classList.remove("button-error");
2726
+ button.style.animation = "";
2727
+ }, 300);
2728
+ }
2729
+ };
2730
+ button.addEventListener("click", button._clickHandler);
2655
2731
  return button;
2656
2732
  }
2657
2733
  /**
2658
- * Handle toolbar button actions
2734
+ * Sanitize SVG to prevent XSS
2659
2735
  */
2660
- async handleAction(action, button) {
2661
- const textarea = this.editor.textarea;
2662
- if (!textarea)
2663
- return;
2664
- if (action === "toggle-view-menu") {
2665
- this.toggleViewDropdown(button);
2736
+ sanitizeSVG(svg) {
2737
+ if (typeof svg !== "string")
2738
+ return "";
2739
+ const cleaned = svg.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "").replace(/\son\w+\s*=\s*["'][^"']*["']/gi, "").replace(/\son\w+\s*=\s*[^\s>]*/gi, "");
2740
+ return cleaned;
2741
+ }
2742
+ /**
2743
+ * Toggle view mode dropdown (internal implementation)
2744
+ * Not exposed to users - viewMode button behavior is fixed
2745
+ */
2746
+ toggleViewModeDropdown(button) {
2747
+ const existingDropdown = document.querySelector(".overtype-dropdown-menu");
2748
+ if (existingDropdown) {
2749
+ existingDropdown.remove();
2750
+ button.classList.remove("dropdown-active");
2666
2751
  return;
2667
2752
  }
2668
- textarea.focus();
2669
- try {
2670
- switch (action) {
2671
- case "toggleBold":
2672
- toggleBold(textarea);
2673
- break;
2674
- case "toggleItalic":
2675
- toggleItalic(textarea);
2676
- break;
2677
- case "insertH1":
2678
- toggleH1(textarea);
2679
- break;
2680
- case "insertH2":
2681
- toggleH2(textarea);
2682
- break;
2683
- case "insertH3":
2684
- toggleH3(textarea);
2685
- break;
2686
- case "insertLink":
2687
- insertLink(textarea);
2688
- break;
2689
- case "toggleCode":
2690
- toggleCode(textarea);
2691
- break;
2692
- case "toggleBulletList":
2693
- toggleBulletList(textarea);
2694
- break;
2695
- case "toggleNumberedList":
2696
- toggleNumberedList(textarea);
2697
- break;
2698
- case "toggleQuote":
2699
- toggleQuote(textarea);
2700
- break;
2701
- case "toggleTaskList":
2702
- toggleTaskList(textarea);
2703
- break;
2704
- case "toggle-plain":
2705
- const isPlain = this.editor.container.classList.contains("plain-mode");
2706
- this.editor.showPlainTextarea(!isPlain);
2707
- break;
2753
+ button.classList.add("dropdown-active");
2754
+ const dropdown = this.createViewModeDropdown(button);
2755
+ const rect = button.getBoundingClientRect();
2756
+ dropdown.style.position = "absolute";
2757
+ dropdown.style.top = `${rect.bottom + 5}px`;
2758
+ dropdown.style.left = `${rect.left}px`;
2759
+ document.body.appendChild(dropdown);
2760
+ this.handleDocumentClick = (e) => {
2761
+ if (!dropdown.contains(e.target) && !button.contains(e.target)) {
2762
+ dropdown.remove();
2763
+ button.classList.remove("dropdown-active");
2764
+ document.removeEventListener("click", this.handleDocumentClick);
2708
2765
  }
2709
- textarea.dispatchEvent(new Event("input", { bubbles: true }));
2710
- } catch (error) {
2711
- console.error("Error loading markdown-actions:", error);
2712
- }
2766
+ };
2767
+ setTimeout(() => {
2768
+ document.addEventListener("click", this.handleDocumentClick);
2769
+ }, 0);
2713
2770
  }
2714
2771
  /**
2715
- * Update toolbar button states based on current selection
2772
+ * Create view mode dropdown menu (internal implementation)
2716
2773
  */
2717
- async updateButtonStates() {
2718
- const textarea = this.editor.textarea;
2719
- if (!textarea)
2720
- return;
2774
+ createViewModeDropdown(button) {
2775
+ const dropdown = document.createElement("div");
2776
+ dropdown.className = "overtype-dropdown-menu";
2777
+ const items = [
2778
+ { id: "normal", label: "Normal Edit", icon: "\u2713" },
2779
+ { id: "plain", label: "Plain Textarea", icon: "\u2713" },
2780
+ { id: "preview", label: "Preview Mode", icon: "\u2713" }
2781
+ ];
2782
+ const currentMode = this.editor.container.dataset.mode || "normal";
2783
+ items.forEach((item) => {
2784
+ const menuItem = document.createElement("button");
2785
+ menuItem.className = "overtype-dropdown-item";
2786
+ menuItem.type = "button";
2787
+ menuItem.textContent = item.label;
2788
+ if (item.id === currentMode) {
2789
+ menuItem.classList.add("active");
2790
+ menuItem.setAttribute("aria-current", "true");
2791
+ const checkmark = document.createElement("span");
2792
+ checkmark.className = "overtype-dropdown-icon";
2793
+ checkmark.textContent = item.icon;
2794
+ menuItem.prepend(checkmark);
2795
+ }
2796
+ menuItem.addEventListener("click", (e) => {
2797
+ e.preventDefault();
2798
+ switch (item.id) {
2799
+ case "plain":
2800
+ this.editor.showPlainTextarea();
2801
+ break;
2802
+ case "preview":
2803
+ this.editor.showPreviewMode();
2804
+ break;
2805
+ case "normal":
2806
+ default:
2807
+ this.editor.showNormalEditMode();
2808
+ break;
2809
+ }
2810
+ dropdown.remove();
2811
+ button.classList.remove("dropdown-active");
2812
+ document.removeEventListener("click", this.handleDocumentClick);
2813
+ });
2814
+ dropdown.appendChild(menuItem);
2815
+ });
2816
+ return dropdown;
2817
+ }
2818
+ /**
2819
+ * Update active states of toolbar buttons
2820
+ */
2821
+ updateButtonStates() {
2822
+ var _a;
2721
2823
  try {
2722
- const activeFormats = getActiveFormats2(textarea);
2824
+ const activeFormats = ((_a = getActiveFormats2) == null ? void 0 : _a(
2825
+ this.editor.textarea,
2826
+ this.editor.textarea.selectionStart
2827
+ )) || [];
2723
2828
  Object.entries(this.buttons).forEach(([name, button]) => {
2829
+ if (name === "viewMode")
2830
+ return;
2724
2831
  let isActive = false;
2725
2832
  switch (name) {
2726
2833
  case "bold":
@@ -2738,12 +2845,12 @@ var Toolbar = class {
2738
2845
  case "orderedList":
2739
2846
  isActive = activeFormats.includes("numbered-list");
2740
2847
  break;
2741
- case "quote":
2742
- isActive = activeFormats.includes("quote");
2743
- break;
2744
2848
  case "taskList":
2745
2849
  isActive = activeFormats.includes("task-list");
2746
2850
  break;
2851
+ case "quote":
2852
+ isActive = activeFormats.includes("quote");
2853
+ break;
2747
2854
  case "h1":
2748
2855
  isActive = activeFormats.includes("header");
2749
2856
  break;
@@ -2753,9 +2860,6 @@ var Toolbar = class {
2753
2860
  case "h3":
2754
2861
  isActive = activeFormats.includes("header-3");
2755
2862
  break;
2756
- case "togglePlain":
2757
- isActive = !this.editor.container.classList.contains("plain-mode");
2758
- break;
2759
2863
  }
2760
2864
  button.classList.toggle("active", isActive);
2761
2865
  button.setAttribute("aria-pressed", isActive.toString());
@@ -2764,101 +2868,19 @@ var Toolbar = class {
2764
2868
  }
2765
2869
  }
2766
2870
  /**
2767
- * Toggle view mode dropdown menu
2768
- */
2769
- toggleViewDropdown(button) {
2770
- const existingDropdown = document.querySelector(".overtype-dropdown-menu");
2771
- if (existingDropdown) {
2772
- existingDropdown.remove();
2773
- button.classList.remove("dropdown-active");
2774
- document.removeEventListener("click", this.handleDocumentClick);
2775
- return;
2776
- }
2777
- const dropdown = this.createViewDropdown();
2778
- const rect = button.getBoundingClientRect();
2779
- dropdown.style.top = `${rect.bottom + 4}px`;
2780
- dropdown.style.left = `${rect.left}px`;
2781
- document.body.appendChild(dropdown);
2782
- button.classList.add("dropdown-active");
2783
- this.handleDocumentClick = (e) => {
2784
- if (!button.contains(e.target) && !dropdown.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
2796
- */
2797
- createViewDropdown() {
2798
- const dropdown = document.createElement("div");
2799
- dropdown.className = "overtype-dropdown-menu";
2800
- const isPlain = this.editor.container.classList.contains("plain-mode");
2801
- const isPreview = this.editor.container.classList.contains("preview-mode");
2802
- const currentMode = isPreview ? "preview" : isPlain ? "plain" : "normal";
2803
- const modes = [
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
- modes.forEach((mode) => {
2809
- const item = document.createElement("button");
2810
- item.className = "overtype-dropdown-item";
2811
- item.type = "button";
2812
- const check = document.createElement("span");
2813
- check.className = "overtype-dropdown-check";
2814
- check.textContent = currentMode === mode.id ? mode.icon : "";
2815
- const label = document.createElement("span");
2816
- label.textContent = mode.label;
2817
- item.appendChild(check);
2818
- item.appendChild(label);
2819
- if (currentMode === mode.id) {
2820
- item.classList.add("active");
2821
- }
2822
- item.addEventListener("click", (e) => {
2823
- e.stopPropagation();
2824
- this.setViewMode(mode.id);
2825
- dropdown.remove();
2826
- this.viewModeButton.classList.remove("dropdown-active");
2827
- document.removeEventListener("click", this.handleDocumentClick);
2828
- });
2829
- dropdown.appendChild(item);
2830
- });
2831
- return dropdown;
2832
- }
2833
- /**
2834
- * Set view mode
2835
- */
2836
- setViewMode(mode) {
2837
- this.editor.container.classList.remove("plain-mode", "preview-mode");
2838
- switch (mode) {
2839
- case "plain":
2840
- this.editor.showPlainTextarea(true);
2841
- break;
2842
- case "preview":
2843
- this.editor.showPreviewMode(true);
2844
- break;
2845
- case "normal":
2846
- default:
2847
- this.editor.showPlainTextarea(false);
2848
- if (typeof this.editor.showPreviewMode === "function") {
2849
- this.editor.showPreviewMode(false);
2850
- }
2851
- break;
2852
- }
2853
- }
2854
- /**
2855
- * Destroy toolbar
2871
+ * Destroy toolbar and cleanup
2856
2872
  */
2857
2873
  destroy() {
2858
2874
  if (this.container) {
2859
2875
  if (this.handleDocumentClick) {
2860
2876
  document.removeEventListener("click", this.handleDocumentClick);
2861
2877
  }
2878
+ Object.values(this.buttons).forEach((button) => {
2879
+ if (button._clickHandler) {
2880
+ button.removeEventListener("click", button._clickHandler);
2881
+ delete button._clickHandler;
2882
+ }
2883
+ });
2862
2884
  this.container.remove();
2863
2885
  this.container = null;
2864
2886
  this.buttons = {};
@@ -2873,13 +2895,10 @@ var LinkTooltip = class {
2873
2895
  this.tooltip = null;
2874
2896
  this.currentLink = null;
2875
2897
  this.hideTimeout = null;
2898
+ this.visibilityChangeHandler = null;
2876
2899
  this.init();
2877
2900
  }
2878
2901
  init() {
2879
- const supportsAnchor = CSS.supports("position-anchor: --x") && CSS.supports("position-area: center");
2880
- if (!supportsAnchor) {
2881
- return;
2882
- }
2883
2902
  this.createTooltip();
2884
2903
  this.editor.textarea.addEventListener("selectionchange", () => this.checkCursorPosition());
2885
2904
  this.editor.textarea.addEventListener("keyup", (e) => {
@@ -2889,46 +2908,19 @@ var LinkTooltip = class {
2889
2908
  });
2890
2909
  this.editor.textarea.addEventListener("input", () => this.hide());
2891
2910
  this.editor.textarea.addEventListener("scroll", () => this.hide());
2911
+ this.editor.textarea.addEventListener("blur", () => this.hide());
2912
+ this.visibilityChangeHandler = () => {
2913
+ if (document.hidden) {
2914
+ this.hide();
2915
+ }
2916
+ };
2917
+ document.addEventListener("visibilitychange", this.visibilityChangeHandler);
2892
2918
  this.tooltip.addEventListener("mouseenter", () => this.cancelHide());
2893
2919
  this.tooltip.addEventListener("mouseleave", () => this.scheduleHide());
2894
2920
  }
2895
2921
  createTooltip() {
2896
2922
  this.tooltip = document.createElement("div");
2897
2923
  this.tooltip.className = "overtype-link-tooltip";
2898
- const tooltipStyles = document.createElement("style");
2899
- tooltipStyles.textContent = `
2900
- @supports (position-anchor: --x) and (position-area: center) {
2901
- .overtype-link-tooltip {
2902
- position: absolute;
2903
- position-anchor: var(--target-anchor, --link-0);
2904
- position-area: block-end center;
2905
- margin-top: 8px !important;
2906
-
2907
- background: #333 !important;
2908
- color: white !important;
2909
- padding: 6px 10px !important;
2910
- border-radius: 16px !important;
2911
- font-size: 12px !important;
2912
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif !important;
2913
- display: none !important;
2914
- z-index: 10000 !important;
2915
- cursor: pointer !important;
2916
- box-shadow: 0 2px 8px rgba(0,0,0,0.3) !important;
2917
- max-width: 300px !important;
2918
- white-space: nowrap !important;
2919
- overflow: hidden !important;
2920
- text-overflow: ellipsis !important;
2921
-
2922
- position-try: most-width block-end inline-end, flip-inline, block-start center;
2923
- position-visibility: anchors-visible;
2924
- }
2925
-
2926
- .overtype-link-tooltip.visible {
2927
- display: flex !important;
2928
- }
2929
- }
2930
- `;
2931
- document.head.appendChild(tooltipStyles);
2932
2924
  this.tooltip.innerHTML = `
2933
2925
  <span style="display: flex; align-items: center; gap: 6px;">
2934
2926
  <svg width="12" height="12" viewBox="0 0 20 20" fill="currentColor" style="flex-shrink: 0;">
@@ -3004,6 +2996,10 @@ var LinkTooltip = class {
3004
2996
  }
3005
2997
  destroy() {
3006
2998
  this.cancelHide();
2999
+ if (this.visibilityChangeHandler) {
3000
+ document.removeEventListener("visibilitychange", this.visibilityChangeHandler);
3001
+ this.visibilityChangeHandler = null;
3002
+ }
3007
3003
  if (this.tooltip && this.tooltip.parentNode) {
3008
3004
  this.tooltip.parentNode.removeChild(this.tooltip);
3009
3005
  }
@@ -3012,6 +3008,204 @@ var LinkTooltip = class {
3012
3008
  }
3013
3009
  };
3014
3010
 
3011
+ // src/icons.js
3012
+ var boldIcon = `<svg viewBox="0 0 18 18">
3013
+ <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>
3014
+ <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>
3015
+ </svg>`;
3016
+ var italicIcon = `<svg viewBox="0 0 18 18">
3017
+ <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="7" x2="13" y1="4" y2="4"></line>
3018
+ <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="5" x2="11" y1="14" y2="14"></line>
3019
+ <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="8" x2="10" y1="14" y2="4"></line>
3020
+ </svg>`;
3021
+ var h1Icon = `<svg viewBox="0 0 18 18">
3022
+ <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>
3023
+ </svg>`;
3024
+ var h2Icon = `<svg viewBox="0 0 18 18">
3025
+ <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>
3026
+ </svg>`;
3027
+ var h3Icon = `<svg viewBox="0 0 18 18">
3028
+ <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>
3029
+ </svg>`;
3030
+ var linkIcon = `<svg viewBox="0 0 18 18">
3031
+ <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="7" x2="11" y1="7" y2="11"></line>
3032
+ <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>
3033
+ <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>
3034
+ </svg>`;
3035
+ var codeIcon = `<svg viewBox="0 0 18 18">
3036
+ <polyline stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" points="5 7 3 9 5 11"></polyline>
3037
+ <polyline stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" points="13 7 15 9 13 11"></polyline>
3038
+ <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="10" x2="8" y1="5" y2="13"></line>
3039
+ </svg>`;
3040
+ var bulletListIcon = `<svg viewBox="0 0 18 18">
3041
+ <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="6" x2="15" y1="4" y2="4"></line>
3042
+ <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="6" x2="15" y1="9" y2="9"></line>
3043
+ <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="6" x2="15" y1="14" y2="14"></line>
3044
+ <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="3" x2="3" y1="4" y2="4"></line>
3045
+ <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="3" x2="3" y1="9" y2="9"></line>
3046
+ <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="3" x2="3" y1="14" y2="14"></line>
3047
+ </svg>`;
3048
+ var orderedListIcon = `<svg viewBox="0 0 18 18">
3049
+ <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="7" x2="15" y1="4" y2="4"></line>
3050
+ <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="7" x2="15" y1="9" y2="9"></line>
3051
+ <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="7" x2="15" y1="14" y2="14"></line>
3052
+ <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>
3053
+ <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>
3054
+ <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>
3055
+ <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>
3056
+ </svg>`;
3057
+ var quoteIcon = `<svg viewBox="2 2 20 20">
3058
+ <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>
3059
+ <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>
3060
+ </svg>`;
3061
+ var taskListIcon = `<svg viewBox="0 0 18 18">
3062
+ <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="8" x2="16" y1="4" y2="4"></line>
3063
+ <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="8" x2="16" y1="9" y2="9"></line>
3064
+ <line stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="8" x2="16" y1="14" y2="14"></line>
3065
+ <rect stroke="currentColor" fill="none" stroke-width="1.5" x="2" y="3" width="3" height="3" rx="0.5"></rect>
3066
+ <rect stroke="currentColor" fill="none" stroke-width="1.5" x="2" y="13" width="3" height="3" rx="0.5"></rect>
3067
+ <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>
3068
+ </svg>`;
3069
+ var eyeIcon = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
3070
+ <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" fill="none"></path>
3071
+ <circle cx="12" cy="12" r="3" fill="none"></circle>
3072
+ </svg>`;
3073
+
3074
+ // src/toolbar-buttons.js
3075
+ var toolbarButtons = {
3076
+ bold: {
3077
+ name: "bold",
3078
+ icon: boldIcon,
3079
+ title: "Bold (Ctrl+B)",
3080
+ action: ({ editor, event }) => {
3081
+ toggleBold(editor.textarea);
3082
+ editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
3083
+ }
3084
+ },
3085
+ italic: {
3086
+ name: "italic",
3087
+ icon: italicIcon,
3088
+ title: "Italic (Ctrl+I)",
3089
+ action: ({ editor, event }) => {
3090
+ toggleItalic(editor.textarea);
3091
+ editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
3092
+ }
3093
+ },
3094
+ code: {
3095
+ name: "code",
3096
+ icon: codeIcon,
3097
+ title: "Inline Code",
3098
+ action: ({ editor, event }) => {
3099
+ toggleCode(editor.textarea);
3100
+ editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
3101
+ }
3102
+ },
3103
+ separator: {
3104
+ name: "separator"
3105
+ // No icon, title, or action - special separator element
3106
+ },
3107
+ link: {
3108
+ name: "link",
3109
+ icon: linkIcon,
3110
+ title: "Insert Link",
3111
+ action: ({ editor, event }) => {
3112
+ insertLink(editor.textarea);
3113
+ editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
3114
+ }
3115
+ },
3116
+ h1: {
3117
+ name: "h1",
3118
+ icon: h1Icon,
3119
+ title: "Heading 1",
3120
+ action: ({ editor, event }) => {
3121
+ toggleH1(editor.textarea);
3122
+ editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
3123
+ }
3124
+ },
3125
+ h2: {
3126
+ name: "h2",
3127
+ icon: h2Icon,
3128
+ title: "Heading 2",
3129
+ action: ({ editor, event }) => {
3130
+ toggleH2(editor.textarea);
3131
+ editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
3132
+ }
3133
+ },
3134
+ h3: {
3135
+ name: "h3",
3136
+ icon: h3Icon,
3137
+ title: "Heading 3",
3138
+ action: ({ editor, event }) => {
3139
+ toggleH3(editor.textarea);
3140
+ editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
3141
+ }
3142
+ },
3143
+ bulletList: {
3144
+ name: "bulletList",
3145
+ icon: bulletListIcon,
3146
+ title: "Bullet List",
3147
+ action: ({ editor, event }) => {
3148
+ toggleBulletList(editor.textarea);
3149
+ editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
3150
+ }
3151
+ },
3152
+ orderedList: {
3153
+ name: "orderedList",
3154
+ icon: orderedListIcon,
3155
+ title: "Numbered List",
3156
+ action: ({ editor, event }) => {
3157
+ toggleNumberedList(editor.textarea);
3158
+ editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
3159
+ }
3160
+ },
3161
+ taskList: {
3162
+ name: "taskList",
3163
+ icon: taskListIcon,
3164
+ title: "Task List",
3165
+ action: ({ editor, event }) => {
3166
+ if (toggleTaskList) {
3167
+ toggleTaskList(editor.textarea);
3168
+ editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
3169
+ }
3170
+ }
3171
+ },
3172
+ quote: {
3173
+ name: "quote",
3174
+ icon: quoteIcon,
3175
+ title: "Quote",
3176
+ action: ({ editor, event }) => {
3177
+ toggleQuote(editor.textarea);
3178
+ editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
3179
+ }
3180
+ },
3181
+ viewMode: {
3182
+ name: "viewMode",
3183
+ icon: eyeIcon,
3184
+ title: "View mode"
3185
+ // Special: handled internally by Toolbar class as dropdown
3186
+ // No action property - dropdown behavior is internal
3187
+ }
3188
+ };
3189
+ var defaultToolbarButtons = [
3190
+ toolbarButtons.bold,
3191
+ toolbarButtons.italic,
3192
+ toolbarButtons.code,
3193
+ toolbarButtons.separator,
3194
+ toolbarButtons.link,
3195
+ toolbarButtons.separator,
3196
+ toolbarButtons.h1,
3197
+ toolbarButtons.h2,
3198
+ toolbarButtons.h3,
3199
+ toolbarButtons.separator,
3200
+ toolbarButtons.bulletList,
3201
+ toolbarButtons.orderedList,
3202
+ toolbarButtons.taskList,
3203
+ toolbarButtons.separator,
3204
+ toolbarButtons.quote,
3205
+ toolbarButtons.separator,
3206
+ toolbarButtons.viewMode
3207
+ ];
3208
+
3015
3209
  // src/overtype.js
3016
3210
  var _OverType = class _OverType {
3017
3211
  /**
@@ -3071,17 +3265,6 @@ var _OverType = class _OverType {
3071
3265
  }
3072
3266
  this.shortcuts = new ShortcutsManager(this);
3073
3267
  this.linkTooltip = new LinkTooltip(this);
3074
- if (this.options.toolbar) {
3075
- const toolbarButtons = typeof this.options.toolbar === "object" ? this.options.toolbar.buttons : null;
3076
- this.toolbar = new Toolbar(this, toolbarButtons);
3077
- this.toolbar.create();
3078
- this.textarea.addEventListener("selectionchange", () => {
3079
- this.toolbar.updateButtonStates();
3080
- });
3081
- this.textarea.addEventListener("input", () => {
3082
- this.toolbar.updateButtonStates();
3083
- });
3084
- }
3085
3268
  this.initialized = true;
3086
3269
  if (this.options.onChange) {
3087
3270
  this.options.onChange(this.getValue(), this);
@@ -3125,9 +3308,13 @@ var _OverType = class _OverType {
3125
3308
  showActiveLineRaw: false,
3126
3309
  showStats: false,
3127
3310
  toolbar: false,
3311
+ toolbarButtons: null,
3312
+ // Defaults to defaultToolbarButtons if toolbar: true
3128
3313
  statsFormatter: null,
3129
- smartLists: true
3314
+ smartLists: true,
3130
3315
  // Enable smart list continuation
3316
+ codeHighlighter: null
3317
+ // Per-instance code highlighter
3131
3318
  };
3132
3319
  const { theme, colors, ...cleanOptions } = options;
3133
3320
  return {
@@ -3303,6 +3490,41 @@ var _OverType = class _OverType {
3303
3490
  this.textarea.setAttribute("data-gramm_editor", "false");
3304
3491
  this.textarea.setAttribute("data-enable-grammarly", "false");
3305
3492
  }
3493
+ /**
3494
+ * Create and setup toolbar
3495
+ * @private
3496
+ */
3497
+ _createToolbar() {
3498
+ const toolbarButtons2 = this.options.toolbarButtons || defaultToolbarButtons;
3499
+ this.toolbar = new Toolbar(this, { toolbarButtons: toolbarButtons2 });
3500
+ this.toolbar.create();
3501
+ this._toolbarSelectionListener = () => {
3502
+ if (this.toolbar) {
3503
+ this.toolbar.updateButtonStates();
3504
+ }
3505
+ };
3506
+ this._toolbarInputListener = () => {
3507
+ if (this.toolbar) {
3508
+ this.toolbar.updateButtonStates();
3509
+ }
3510
+ };
3511
+ this.textarea.addEventListener("selectionchange", this._toolbarSelectionListener);
3512
+ this.textarea.addEventListener("input", this._toolbarInputListener);
3513
+ }
3514
+ /**
3515
+ * Cleanup toolbar event listeners
3516
+ * @private
3517
+ */
3518
+ _cleanupToolbarListeners() {
3519
+ if (this._toolbarSelectionListener) {
3520
+ this.textarea.removeEventListener("selectionchange", this._toolbarSelectionListener);
3521
+ this._toolbarSelectionListener = null;
3522
+ }
3523
+ if (this._toolbarInputListener) {
3524
+ this.textarea.removeEventListener("input", this._toolbarInputListener);
3525
+ this._toolbarInputListener = null;
3526
+ }
3527
+ }
3306
3528
  /**
3307
3529
  * Apply options to the editor
3308
3530
  * @private
@@ -3318,6 +3540,13 @@ var _OverType = class _OverType {
3318
3540
  } else {
3319
3541
  this.container.classList.remove("overtype-auto-resize");
3320
3542
  }
3543
+ if (this.options.toolbar && !this.toolbar) {
3544
+ this._createToolbar();
3545
+ } else if (!this.options.toolbar && this.toolbar) {
3546
+ this._cleanupToolbarListeners();
3547
+ this.toolbar.destroy();
3548
+ this.toolbar = null;
3549
+ }
3321
3550
  this.updatePreview();
3322
3551
  }
3323
3552
  /**
@@ -3327,7 +3556,8 @@ var _OverType = class _OverType {
3327
3556
  const text = this.textarea.value;
3328
3557
  const cursorPos = this.textarea.selectionStart;
3329
3558
  const activeLine = this._getCurrentLine(text, cursorPos);
3330
- const html = MarkdownParser.parse(text, activeLine, this.options.showActiveLineRaw);
3559
+ const isPreviewMode = this.container.dataset.mode === "preview";
3560
+ const html = MarkdownParser.parse(text, activeLine, this.options.showActiveLineRaw, this.options.codeHighlighter, isPreviewMode);
3331
3561
  this.preview.innerHTML = html || '<span style="color: #808080;">Start typing...</span>';
3332
3562
  this._applyCodeBlockBackgrounds();
3333
3563
  if (this.options.showStats && this.statsBar) {
@@ -3561,7 +3791,7 @@ var _OverType = class _OverType {
3561
3791
  */
3562
3792
  getRenderedHTML(options = {}) {
3563
3793
  const markdown = this.getValue();
3564
- let html = MarkdownParser.parse(markdown);
3794
+ let html = MarkdownParser.parse(markdown, -1, false, this.options.codeHighlighter);
3565
3795
  if (options.cleanHTML) {
3566
3796
  html = html.replace(/<span class="syntax-marker[^"]*">.*?<\/span>/g, "");
3567
3797
  html = html.replace(/\sclass="(bullet-list|ordered-list|code-fence|hr-marker|blockquote|url-part)"/g, "");
@@ -3613,6 +3843,33 @@ var _OverType = class _OverType {
3613
3843
  this._applyOptions();
3614
3844
  this.updatePreview();
3615
3845
  }
3846
+ /**
3847
+ * Set theme for this instance
3848
+ * @param {string|Object} theme - Theme name or custom theme object
3849
+ * @returns {this} Returns this for chaining
3850
+ */
3851
+ setTheme(theme) {
3852
+ this.instanceTheme = theme;
3853
+ const themeObj = typeof theme === "string" ? getTheme(theme) : theme;
3854
+ const themeName = typeof themeObj === "string" ? themeObj : themeObj.name;
3855
+ if (themeName) {
3856
+ this.container.setAttribute("data-theme", themeName);
3857
+ }
3858
+ if (themeObj && themeObj.colors) {
3859
+ const cssVars = themeToCSSVars(themeObj.colors);
3860
+ this.container.style.cssText += cssVars;
3861
+ }
3862
+ this.updatePreview();
3863
+ return this;
3864
+ }
3865
+ /**
3866
+ * Set instance-specific code highlighter
3867
+ * @param {Function|null} highlighter - Function that takes (code, language) and returns highlighted HTML
3868
+ */
3869
+ setCodeHighlighter(highlighter) {
3870
+ this.options.codeHighlighter = highlighter;
3871
+ this.updatePreview();
3872
+ }
3616
3873
  /**
3617
3874
  * Update stats bar
3618
3875
  * @private
@@ -3715,37 +3972,39 @@ var _OverType = class _OverType {
3715
3972
  }
3716
3973
  }
3717
3974
  /**
3718
- * Show or hide the plain textarea (toggle overlay visibility)
3719
- * @param {boolean} show - true to show plain textarea (hide overlay), false to show overlay
3720
- * @returns {boolean} Current plain textarea state
3975
+ * Show normal edit mode (overlay with markdown preview)
3976
+ * @returns {this} Returns this for chaining
3721
3977
  */
3722
- showPlainTextarea(show) {
3723
- if (show) {
3724
- this.container.classList.add("plain-mode");
3725
- } else {
3726
- this.container.classList.remove("plain-mode");
3727
- }
3978
+ showNormalEditMode() {
3979
+ this.container.dataset.mode = "normal";
3980
+ requestAnimationFrame(() => {
3981
+ this.textarea.scrollTop = this.preview.scrollTop;
3982
+ this.textarea.scrollLeft = this.preview.scrollLeft;
3983
+ });
3984
+ return this;
3985
+ }
3986
+ /**
3987
+ * Show plain textarea mode (no overlay)
3988
+ * @returns {this} Returns this for chaining
3989
+ */
3990
+ showPlainTextarea() {
3991
+ this.container.dataset.mode = "plain";
3728
3992
  if (this.toolbar) {
3729
3993
  const toggleBtn = this.container.querySelector('[data-action="toggle-plain"]');
3730
3994
  if (toggleBtn) {
3731
- toggleBtn.classList.toggle("active", !show);
3732
- toggleBtn.title = show ? "Show markdown preview" : "Show plain textarea";
3995
+ toggleBtn.classList.remove("active");
3996
+ toggleBtn.title = "Show markdown preview";
3733
3997
  }
3734
3998
  }
3735
- return show;
3999
+ return this;
3736
4000
  }
3737
4001
  /**
3738
- * Show/hide preview mode
3739
- * @param {boolean} show - Show preview mode if true, edit mode if false
3740
- * @returns {boolean} Current preview mode state
4002
+ * Show preview mode (read-only view)
4003
+ * @returns {this} Returns this for chaining
3741
4004
  */
3742
- showPreviewMode(show) {
3743
- if (show) {
3744
- this.container.classList.add("preview-mode");
3745
- } else {
3746
- this.container.classList.remove("preview-mode");
3747
- }
3748
- return show;
4005
+ showPreviewMode() {
4006
+ this.container.dataset.mode = "preview";
4007
+ return this;
3749
4008
  }
3750
4009
  /**
3751
4010
  * Destroy the editor instance
@@ -3825,16 +4084,16 @@ var _OverType = class _OverType {
3825
4084
  _OverType.currentTheme = themeObj;
3826
4085
  _OverType.injectStyles(true);
3827
4086
  document.querySelectorAll(".overtype-container").forEach((container) => {
3828
- const themeName = typeof themeObj === "string" ? themeObj : themeObj.name;
3829
- if (themeName) {
3830
- container.setAttribute("data-theme", themeName);
4087
+ const themeName2 = typeof themeObj === "string" ? themeObj : themeObj.name;
4088
+ if (themeName2) {
4089
+ container.setAttribute("data-theme", themeName2);
3831
4090
  }
3832
4091
  });
3833
4092
  document.querySelectorAll(".overtype-wrapper").forEach((wrapper) => {
3834
4093
  if (!wrapper.closest(".overtype-container")) {
3835
- const themeName = typeof themeObj === "string" ? themeObj : themeObj.name;
3836
- if (themeName) {
3837
- wrapper.setAttribute("data-theme", themeName);
4094
+ const themeName2 = typeof themeObj === "string" ? themeObj : themeObj.name;
4095
+ if (themeName2) {
4096
+ wrapper.setAttribute("data-theme", themeName2);
3838
4097
  }
3839
4098
  }
3840
4099
  const instance = wrapper._instance;
@@ -3842,6 +4101,36 @@ var _OverType = class _OverType {
3842
4101
  instance.updatePreview();
3843
4102
  }
3844
4103
  });
4104
+ const themeName = typeof themeObj === "string" ? themeObj : themeObj.name;
4105
+ document.querySelectorAll("overtype-editor").forEach((webComponent) => {
4106
+ if (themeName && typeof webComponent.setAttribute === "function") {
4107
+ webComponent.setAttribute("theme", themeName);
4108
+ }
4109
+ if (typeof webComponent.refreshTheme === "function") {
4110
+ webComponent.refreshTheme();
4111
+ }
4112
+ });
4113
+ }
4114
+ /**
4115
+ * Set global code highlighter for all OverType instances
4116
+ * @param {Function|null} highlighter - Function that takes (code, language) and returns highlighted HTML
4117
+ */
4118
+ static setCodeHighlighter(highlighter) {
4119
+ MarkdownParser.setCodeHighlighter(highlighter);
4120
+ document.querySelectorAll(".overtype-wrapper").forEach((wrapper) => {
4121
+ const instance = wrapper._instance;
4122
+ if (instance && instance.updatePreview) {
4123
+ instance.updatePreview();
4124
+ }
4125
+ });
4126
+ document.querySelectorAll("overtype-editor").forEach((webComponent) => {
4127
+ if (typeof webComponent.getEditor === "function") {
4128
+ const instance = webComponent.getEditor();
4129
+ if (instance && instance.updatePreview) {
4130
+ instance.updatePreview();
4131
+ }
4132
+ }
4133
+ });
3845
4134
  }
3846
4135
  /**
3847
4136
  * Initialize global event listeners
@@ -3906,7 +4195,9 @@ OverType.currentTheme = solar;
3906
4195
  var overtype_default = OverType;
3907
4196
  export {
3908
4197
  OverType,
3909
- overtype_default as default
4198
+ overtype_default as default,
4199
+ defaultToolbarButtons,
4200
+ toolbarButtons
3910
4201
  };
3911
4202
  /**
3912
4203
  * OverType - A lightweight markdown editor library with perfect WYSIWYG alignment