overtype 1.2.3 → 1.2.4

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/README.md CHANGED
@@ -1,6 +1,16 @@
1
1
  # OverType
2
2
 
3
- A lightweight markdown editor library with perfect WYSIWYG alignment using an invisible textarea overlay technique. Includes optional toolbar. ~82KB minified with all features.
3
+ A lightweight markdown editor library with perfect WYSIWYG alignment using an invisible textarea overlay technique. Includes optional toolbar. ~84KB minified with all features.
4
+
5
+ ## Live Examples
6
+
7
+ 🎮 **Try it out**: [Interactive demos on overtype.dev](https://overtype.dev)
8
+ - [Basic Editor](https://overtype.dev/#basic-editor)
9
+ - [With Toolbar](https://overtype.dev/#toolbar)
10
+ - [Multiple Instances](https://overtype.dev/#multiple-instances)
11
+ - [View Modes](https://overtype.dev/#view-modes)
12
+ - [Custom Themes](https://overtype.dev/#custom-themes)
13
+ - [All Markdown Features](https://overtype.dev/#markdown-features)
4
14
 
5
15
  ## Features
6
16
 
@@ -9,7 +19,7 @@ A lightweight markdown editor library with perfect WYSIWYG alignment using an in
9
19
  - ⌨️ **Keyboard shortcuts** - Common markdown shortcuts (Cmd/Ctrl+B for bold, etc.)
10
20
  - 📱 **Mobile optimized** - Responsive design with mobile-specific styles
11
21
  - 🔄 **DOM persistence aware** - Recovers from existing DOM (perfect for HyperClay and similar platforms)
12
- - 🚀 **Lightweight** - ~82KB minified
22
+ - 🚀 **Lightweight** - ~84KB minified
13
23
  - 🎯 **Optional toolbar** - Clean, minimal toolbar with all essential formatting
14
24
  - ✨ **Smart shortcuts** - Keyboard shortcuts with selection preservation
15
25
  - 📝 **Smart list continuation** - GitHub-style automatic list continuation on Enter
@@ -25,7 +35,7 @@ We overlap an invisible textarea on top of styled output, giving the illusion of
25
35
 
26
36
  | Feature | OverType | HyperMD | Milkdown | TUI Editor | EasyMDE |
27
37
  |---------|----------|---------|----------|------------|---------|
28
- | **Size** | ~82KB | 364.02 KB | 344.51 KB | 560.99 KB | 323.69 KB |
38
+ | **Size** | ~84KB | 364.02 KB | 344.51 KB | 560.99 KB | 323.69 KB |
29
39
  | **Dependencies** | Bundled | CodeMirror | ProseMirror + plugins | Multiple libs | CodeMirror |
30
40
  | **Setup** | Single file | Complex config | Build step required | Complex config | Moderate |
31
41
  | **Approach** | Invisible textarea | ContentEditable | ContentEditable | ContentEditable | CodeMirror |
@@ -212,20 +222,26 @@ const [editor] = new OverType('#editor', {
212
222
  const markdown = editor.getValue();
213
223
  // Returns: "# Title\n\n**Bold** text with [links](https://example.com)"
214
224
 
215
- // Get rendered HTML for display
225
+ // Get rendered HTML with syntax markers (for debugging/inspection)
216
226
  const html = editor.getRenderedHTML();
217
- // Returns basic HTML with markdown elements
227
+ // Returns HTML with <span class="syntax-marker"> elements visible
218
228
 
219
- // Get HTML with post-processing (consolidated lists/code blocks)
220
- const processedHTML = editor.getRenderedHTML(true);
221
- // Returns HTML optimized for preview mode
229
+ // Get clean HTML for export (no OverType-specific markup)
230
+ const cleanHTML = editor.getRenderedHTML({ cleanHTML: true });
231
+ // Returns clean HTML suitable for saving/exporting
222
232
 
223
- // Get the current preview element's HTML
233
+ // Convenience method for clean HTML
234
+ const exportHTML = editor.getCleanHTML();
235
+ // Same as getRenderedHTML({ cleanHTML: true })
236
+
237
+ // Get the current preview element's HTML (actual DOM content)
224
238
  const previewHTML = editor.getPreviewHTML();
225
239
  // Returns exactly what's shown in the editor's preview layer
226
240
 
227
- // Example: Create external preview
228
- document.getElementById('external-preview').innerHTML = editor.getRenderedHTML(true);
241
+ // Example: Export clean HTML to server
242
+ const htmlToSave = editor.getCleanHTML(); // No syntax markers
243
+ // Example: Clone exact preview appearance
244
+ document.getElementById('clone').innerHTML = editor.getPreviewHTML();
229
245
  ```
230
246
 
231
247
  ### Stats Bar
@@ -371,11 +387,12 @@ editor.getValue()
371
387
  editor.setValue(markdown)
372
388
 
373
389
  // Get rendered HTML of the current content
374
- editor.getRenderedHTML() // Basic HTML rendering
375
- editor.getRenderedHTML(true) // With post-processing (consolidated lists/code blocks)
390
+ editor.getRenderedHTML() // With syntax markers (for debugging)
391
+ editor.getRenderedHTML({ cleanHTML: true }) // Clean HTML without OverType markup
392
+ editor.getCleanHTML() // Alias for getRenderedHTML({ cleanHTML: true })
376
393
 
377
394
  // Get the current preview element's HTML
378
- editor.getPreviewHTML() // Returns exactly what's displayed in the preview
395
+ editor.getPreviewHTML() // Actual DOM content from preview layer
379
396
 
380
397
  // Change theme
381
398
  editor.setTheme('cave') // Built-in theme name
@@ -566,7 +583,7 @@ MIT
566
583
  - **Pluggable parser system** - Support for any programming language or syntax
567
584
  - **Parser registry** - Automatic language detection by file extension or MIME type
568
585
  - **Cleaner separation** - Extracted the overlay technique without markdown-specific features
569
- - **Smaller footprint** - ~82KB minified (vs OverType's ~78KB)
586
+ - **Smaller footprint** - ~84KB minified (vs OverType's ~78KB)
570
587
 
571
588
  Key components extracted from OverType to Synesthesia:
572
589
  - The transparent textarea overlay technique for perfect WYSIWYG alignment
@@ -581,3 +598,7 @@ If you need a markdown editor with toolbar and formatting features, use OverType
581
598
 
582
599
  Contributions are welcome! Please feel free to submit a Pull Request.
583
600
 
601
+ ---
602
+
603
+ Ready for another radical idea?
604
+ [Let's remove every layer of the web application stack.](https://hyperclay.com)
package/dist/overtype.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * OverType v1.2.2
2
+ * OverType v1.2.4
3
3
  * A lightweight markdown editor library with perfect WYSIWYG alignment
4
4
  * @license MIT
5
5
  * @author Demo User
@@ -155,6 +155,17 @@ var MarkdownParser = class {
155
155
  html = html.replace(new RegExp("(?<!_)_(?!_)(.+?)(?<!_)_(?!_)", "g"), '<em><span class="syntax-marker">_</span>$1<span class="syntax-marker">_</span></em>');
156
156
  return html;
157
157
  }
158
+ /**
159
+ * Parse strikethrough text
160
+ * Supports both single (~) and double (~~) tildes, but rejects 3+ tildes
161
+ * @param {string} html - HTML with potential strikethrough markdown
162
+ * @returns {string} HTML with strikethrough styling
163
+ */
164
+ static parseStrikethrough(html) {
165
+ html = html.replace(new RegExp("(?<!~)~~(?!~)(.+?)(?<!~)~~(?!~)", "g"), '<del><span class="syntax-marker">~~</span>$1<span class="syntax-marker">~~</span></del>');
166
+ html = html.replace(new RegExp("(?<!~)~(?!~)(.+?)(?<!~)~(?!~)", "g"), '<del><span class="syntax-marker">~</span>$1<span class="syntax-marker">~</span></del>');
167
+ return html;
168
+ }
158
169
  /**
159
170
  * Parse inline code
160
171
  * @param {string} html - HTML with potential code markdown
@@ -217,6 +228,7 @@ var MarkdownParser = class {
217
228
  sanctuaries.set(placeholder, match);
218
229
  return placeholder;
219
230
  });
231
+ html = this.parseStrikethrough(html);
220
232
  html = this.parseBold(html);
221
233
  html = this.parseItalic(html);
222
234
  sanctuaries.forEach((content, placeholder) => {
@@ -351,6 +363,17 @@ var MarkdownParser = class {
351
363
  container.insertBefore(currentList, child);
352
364
  listType = newType;
353
365
  }
366
+ const indentationNodes = [];
367
+ for (const node of child.childNodes) {
368
+ if (node.nodeType === 3 && node.textContent.match(/^\u00A0+$/)) {
369
+ indentationNodes.push(node.cloneNode(true));
370
+ } else if (node === listItem) {
371
+ break;
372
+ }
373
+ }
374
+ indentationNodes.forEach((node) => {
375
+ listItem.insertBefore(node, listItem.firstChild);
376
+ });
354
377
  currentList.appendChild(listItem);
355
378
  child.remove();
356
379
  } else {
@@ -368,15 +391,35 @@ var MarkdownParser = class {
368
391
  static postProcessHTMLManual(html) {
369
392
  let processed = html;
370
393
  processed = processed.replace(/((?:<div>(?:&nbsp;)*<li class="bullet-list">.*?<\/li><\/div>\s*)+)/gs, (match) => {
371
- const items = match.match(/<li class="bullet-list">.*?<\/li>/gs) || [];
372
- if (items.length > 0) {
394
+ const divs = match.match(/<div>(?:&nbsp;)*<li class="bullet-list">.*?<\/li><\/div>/gs) || [];
395
+ if (divs.length > 0) {
396
+ const items = divs.map((div) => {
397
+ const indentMatch = div.match(/<div>((?:&nbsp;)*)<li/);
398
+ const listItemMatch = div.match(/<li class="bullet-list">.*?<\/li>/);
399
+ if (indentMatch && listItemMatch) {
400
+ const indentation = indentMatch[1];
401
+ const listItem = listItemMatch[0];
402
+ return listItem.replace(/<li class="bullet-list">/, `<li class="bullet-list">${indentation}`);
403
+ }
404
+ return listItemMatch ? listItemMatch[0] : "";
405
+ }).filter(Boolean);
373
406
  return "<ul>" + items.join("") + "</ul>";
374
407
  }
375
408
  return match;
376
409
  });
377
410
  processed = processed.replace(/((?:<div>(?:&nbsp;)*<li class="ordered-list">.*?<\/li><\/div>\s*)+)/gs, (match) => {
378
- const items = match.match(/<li class="ordered-list">.*?<\/li>/gs) || [];
379
- if (items.length > 0) {
411
+ const divs = match.match(/<div>(?:&nbsp;)*<li class="ordered-list">.*?<\/li><\/div>/gs) || [];
412
+ if (divs.length > 0) {
413
+ const items = divs.map((div) => {
414
+ const indentMatch = div.match(/<div>((?:&nbsp;)*)<li/);
415
+ const listItemMatch = div.match(/<li class="ordered-list">.*?<\/li>/);
416
+ if (indentMatch && listItemMatch) {
417
+ const indentation = indentMatch[1];
418
+ const listItem = listItemMatch[0];
419
+ return listItem.replace(/<li class="ordered-list">/, `<li class="ordered-list">${indentation}`);
420
+ }
421
+ return listItemMatch ? listItemMatch[0] : "";
422
+ }).filter(Boolean);
380
423
  return "<ol>" + items.join("") + "</ol>";
381
424
  }
382
425
  return match;
@@ -1918,6 +1961,14 @@ function generateStyles(options = {}) {
1918
1961
  font-style: italic !important;
1919
1962
  }
1920
1963
 
1964
+ /* Strikethrough text */
1965
+ .overtype-wrapper .overtype-preview del {
1966
+ color: var(--del, #ee964b) !important;
1967
+ text-decoration: line-through !important;
1968
+ text-decoration-color: var(--del, #ee964b) !important;
1969
+ text-decoration-thickness: 1px !important;
1970
+ }
1971
+
1921
1972
  /* Inline code */
1922
1973
  .overtype-wrapper .overtype-preview code {
1923
1974
  background: var(--code-bg, rgba(244, 211, 94, 0.4)) !important;
@@ -2061,10 +2112,10 @@ function generateStyles(options = {}) {
2061
2112
  height: 8px !important;
2062
2113
  background: #4caf50 !important;
2063
2114
  border-radius: 50% !important;
2064
- animation: pulse 2s infinite !important;
2115
+ animation: overtype-pulse 2s infinite !important;
2065
2116
  }
2066
2117
 
2067
- @keyframes pulse {
2118
+ @keyframes overtype-pulse {
2068
2119
  0%, 100% { opacity: 1; transform: scale(1); }
2069
2120
  50% { opacity: 0.6; transform: scale(1.2); }
2070
2121
  }
@@ -2072,19 +2123,19 @@ function generateStyles(options = {}) {
2072
2123
 
2073
2124
  /* Toolbar Styles */
2074
2125
  .overtype-toolbar {
2075
- display: flex;
2076
- align-items: center;
2077
- gap: 4px;
2126
+ display: flex !important;
2127
+ align-items: center !important;
2128
+ gap: 4px !important;
2078
2129
  padding: 8px !important; /* Override reset */
2079
2130
  background: var(--toolbar-bg, var(--bg-primary, #f8f9fa)) !important; /* Override reset */
2080
2131
  overflow-x: auto !important; /* Allow horizontal scrolling */
2081
2132
  overflow-y: hidden !important; /* Hide vertical overflow */
2082
- -webkit-overflow-scrolling: touch;
2083
- flex-shrink: 0;
2133
+ -webkit-overflow-scrolling: touch !important;
2134
+ flex-shrink: 0 !important;
2084
2135
  height: auto !important;
2085
2136
  grid-row: 1 !important; /* Always first row in grid */
2086
2137
  position: relative !important; /* Override reset */
2087
- z-index: 100; /* Ensure toolbar is above wrapper */
2138
+ z-index: 100 !important; /* Ensure toolbar is above wrapper */
2088
2139
  scrollbar-width: thin; /* Thin scrollbar on Firefox */
2089
2140
  }
2090
2141
 
@@ -2466,20 +2517,67 @@ var eyeIcon = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke
2466
2517
 
2467
2518
  // src/toolbar.js
2468
2519
  var Toolbar = class {
2469
- constructor(editor) {
2520
+ constructor(editor, buttonConfig = null) {
2470
2521
  this.editor = editor;
2471
2522
  this.container = null;
2472
2523
  this.buttons = {};
2524
+ this.buttonConfig = buttonConfig;
2525
+ }
2526
+ /**
2527
+ * Check if cursor/selection is inside a markdown link
2528
+ * @param {HTMLTextAreaElement} textarea - The textarea element
2529
+ * @returns {boolean} True if inside a link
2530
+ */
2531
+ isInsideLink(textarea) {
2532
+ const value = textarea.value;
2533
+ const start = textarea.selectionStart;
2534
+ const end = textarea.selectionEnd;
2535
+ let insideLink = false;
2536
+ let openBracket = -1;
2537
+ let closeBracket = -1;
2538
+ for (let i = start - 1; i >= 0; i--) {
2539
+ if (value[i] === "[") {
2540
+ openBracket = i;
2541
+ break;
2542
+ }
2543
+ if (value[i] === "\n") {
2544
+ break;
2545
+ }
2546
+ }
2547
+ if (openBracket >= 0) {
2548
+ for (let i = end; i < value.length - 1; i++) {
2549
+ if (value[i] === "]" && value[i + 1] === "(") {
2550
+ closeBracket = i;
2551
+ break;
2552
+ }
2553
+ if (value[i] === "\n") {
2554
+ break;
2555
+ }
2556
+ }
2557
+ }
2558
+ if (openBracket >= 0 && closeBracket >= 0) {
2559
+ for (let i = closeBracket + 2; i < value.length; i++) {
2560
+ if (value[i] === ")") {
2561
+ insideLink = true;
2562
+ break;
2563
+ }
2564
+ if (value[i] === "\n" || value[i] === " ") {
2565
+ break;
2566
+ }
2567
+ }
2568
+ }
2569
+ return insideLink;
2473
2570
  }
2474
2571
  /**
2475
2572
  * Create and attach toolbar to editor
2476
2573
  */
2477
2574
  create() {
2575
+ var _a;
2478
2576
  this.container = document.createElement("div");
2479
2577
  this.container.className = "overtype-toolbar";
2480
2578
  this.container.setAttribute("role", "toolbar");
2481
2579
  this.container.setAttribute("aria-label", "Text formatting");
2482
- const buttonConfig = [
2580
+ const buttonConfig = (_a = this.buttonConfig) != null ? _a : [
2483
2581
  { name: "bold", icon: boldIcon, title: "Bold (Ctrl+B)", action: "toggleBold" },
2484
2582
  { name: "italic", icon: italicIcon, title: "Italic (Ctrl+I)", action: "toggleItalic" },
2485
2583
  { separator: true },
@@ -2573,6 +2671,9 @@ var Toolbar = class {
2573
2671
  insertLink(textarea);
2574
2672
  break;
2575
2673
  case "toggleCode":
2674
+ if (this.isInsideLink(textarea)) {
2675
+ return;
2676
+ }
2576
2677
  toggleCode(textarea);
2577
2678
  break;
2578
2679
  case "toggleBulletList":
@@ -2788,29 +2889,29 @@ var LinkTooltip = class {
2788
2889
  position: absolute;
2789
2890
  position-anchor: var(--target-anchor, --link-0);
2790
2891
  position-area: block-end center;
2791
- margin-top: 8px;
2892
+ margin-top: 8px !important;
2792
2893
 
2793
- background: #333;
2794
- color: white;
2795
- padding: 6px 10px;
2796
- border-radius: 16px;
2797
- font-size: 12px;
2798
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
2799
- display: none;
2800
- z-index: 10000;
2801
- cursor: pointer;
2802
- box-shadow: 0 2px 8px rgba(0,0,0,0.3);
2803
- max-width: 300px;
2804
- white-space: nowrap;
2805
- overflow: hidden;
2806
- text-overflow: ellipsis;
2894
+ background: #333 !important;
2895
+ color: white !important;
2896
+ padding: 6px 10px !important;
2897
+ border-radius: 16px !important;
2898
+ font-size: 12px !important;
2899
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif !important;
2900
+ display: none !important;
2901
+ z-index: 10000 !important;
2902
+ cursor: pointer !important;
2903
+ box-shadow: 0 2px 8px rgba(0,0,0,0.3) !important;
2904
+ max-width: 300px !important;
2905
+ white-space: nowrap !important;
2906
+ overflow: hidden !important;
2907
+ text-overflow: ellipsis !important;
2807
2908
 
2808
2909
  position-try: most-width block-end inline-end, flip-inline, block-start center;
2809
2910
  position-visibility: anchors-visible;
2810
2911
  }
2811
2912
 
2812
2913
  .overtype-link-tooltip.visible {
2813
- display: flex;
2914
+ display: flex !important;
2814
2915
  }
2815
2916
  }
2816
2917
  `;
@@ -2958,7 +3059,8 @@ var _OverType = class _OverType {
2958
3059
  this.shortcuts = new ShortcutsManager(this);
2959
3060
  this.linkTooltip = new LinkTooltip(this);
2960
3061
  if (this.options.toolbar) {
2961
- this.toolbar = new Toolbar(this);
3062
+ const toolbarButtons = typeof this.options.toolbar === "object" ? this.options.toolbar.buttons : null;
3063
+ this.toolbar = new Toolbar(this, toolbarButtons);
2962
3064
  this.toolbar.create();
2963
3065
  this.textarea.addEventListener("selectionchange", () => {
2964
3066
  this.toolbar.updateButtonStates();
@@ -3440,24 +3542,36 @@ var _OverType = class _OverType {
3440
3542
  }
3441
3543
  /**
3442
3544
  * Get the rendered HTML of the current content
3443
- * @param {boolean} processForPreview - If true, post-processes HTML for preview mode (consolidates lists/code blocks)
3545
+ * @param {Object} options - Rendering options
3546
+ * @param {boolean} options.cleanHTML - If true, removes syntax markers and OverType-specific classes
3444
3547
  * @returns {string} Rendered HTML
3445
3548
  */
3446
- getRenderedHTML(processForPreview = false) {
3549
+ getRenderedHTML(options = {}) {
3447
3550
  const markdown = this.getValue();
3448
3551
  let html = MarkdownParser.parse(markdown);
3449
- if (processForPreview) {
3450
- html = MarkdownParser.postProcessHTML(html);
3552
+ if (options.cleanHTML) {
3553
+ html = html.replace(/<span class="syntax-marker[^"]*">.*?<\/span>/g, "");
3554
+ html = html.replace(/\sclass="(bullet-list|ordered-list|code-fence|hr-marker|blockquote|url-part)"/g, "");
3555
+ html = html.replace(/\sclass=""/g, "");
3451
3556
  }
3452
3557
  return html;
3453
3558
  }
3454
3559
  /**
3455
3560
  * Get the current preview element's HTML
3561
+ * This includes all syntax markers and OverType styling
3456
3562
  * @returns {string} Current preview HTML (as displayed)
3457
3563
  */
3458
3564
  getPreviewHTML() {
3459
3565
  return this.preview.innerHTML;
3460
3566
  }
3567
+ /**
3568
+ * Get clean HTML without any OverType-specific markup
3569
+ * Useful for exporting to other formats or storage
3570
+ * @returns {string} Clean HTML suitable for export
3571
+ */
3572
+ getCleanHTML() {
3573
+ return this.getRenderedHTML({ cleanHTML: true });
3574
+ }
3461
3575
  /**
3462
3576
  * Focus the editor
3463
3577
  */
@@ -3776,9 +3890,6 @@ OverType.ShortcutsManager = ShortcutsManager;
3776
3890
  OverType.themes = { solar, cave: getTheme("cave") };
3777
3891
  OverType.getTheme = getTheme;
3778
3892
  OverType.currentTheme = solar;
3779
- if (typeof window !== "undefined" && typeof window.document !== "undefined") {
3780
- window.OverType = OverType;
3781
- }
3782
3893
  var overtype_default = OverType;
3783
3894
  // Annotate the CommonJS export names for ESM import in node:
3784
3895
  0 && (module.exports = {