overtype 1.2.4 → 1.2.5

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,6 @@
1
1
  # OverType
2
2
 
3
- A lightweight markdown editor library with perfect WYSIWYG alignment using an invisible textarea overlay technique. Includes optional toolbar. ~84KB minified with all features.
3
+ A lightweight markdown editor library with perfect WYSIWYG alignment using an invisible textarea overlay technique. Includes optional toolbar. ~86KB minified with all features.
4
4
 
5
5
  ## Live Examples
6
6
 
@@ -19,7 +19,7 @@ A lightweight markdown editor library with perfect WYSIWYG alignment using an in
19
19
  - ⌨️ **Keyboard shortcuts** - Common markdown shortcuts (Cmd/Ctrl+B for bold, etc.)
20
20
  - 📱 **Mobile optimized** - Responsive design with mobile-specific styles
21
21
  - 🔄 **DOM persistence aware** - Recovers from existing DOM (perfect for HyperClay and similar platforms)
22
- - 🚀 **Lightweight** - ~84KB minified
22
+ - 🚀 **Lightweight** - ~86KB minified
23
23
  - 🎯 **Optional toolbar** - Clean, minimal toolbar with all essential formatting
24
24
  - ✨ **Smart shortcuts** - Keyboard shortcuts with selection preservation
25
25
  - 📝 **Smart list continuation** - GitHub-style automatic list continuation on Enter
@@ -35,7 +35,7 @@ We overlap an invisible textarea on top of styled output, giving the illusion of
35
35
 
36
36
  | Feature | OverType | HyperMD | Milkdown | TUI Editor | EasyMDE |
37
37
  |---------|----------|---------|----------|------------|---------|
38
- | **Size** | ~84KB | 364.02 KB | 344.51 KB | 560.99 KB | 323.69 KB |
38
+ | **Size** | ~86KB | 364.02 KB | 344.51 KB | 560.99 KB | 323.69 KB |
39
39
  | **Dependencies** | Bundled | CodeMirror | ProseMirror + plugins | Multiple libs | CodeMirror |
40
40
  | **Setup** | Single file | Complex config | Build step required | Complex config | Moderate |
41
41
  | **Approach** | Invisible textarea | ContentEditable | ContentEditable | ContentEditable | CodeMirror |
@@ -583,7 +583,7 @@ MIT
583
583
  - **Pluggable parser system** - Support for any programming language or syntax
584
584
  - **Parser registry** - Automatic language detection by file extension or MIME type
585
585
  - **Cleaner separation** - Extracted the overlay technique without markdown-specific features
586
- - **Smaller footprint** - ~84KB minified (vs OverType's ~78KB)
586
+ - **Smaller footprint** - ~86KB minified (vs OverType's ~78KB)
587
587
 
588
588
  Key components extracted from OverType to Synesthesia:
589
589
  - The transparent textarea overlay technique for perfect WYSIWYG alignment
package/dist/overtype.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * OverType v1.2.4
2
+ * OverType v1.2.5
3
3
  * A lightweight markdown editor library with perfect WYSIWYG alignment
4
4
  * @license MIT
5
5
  * @author Demo User
@@ -152,7 +152,7 @@ var MarkdownParser = class {
152
152
  */
153
153
  static parseItalic(html) {
154
154
  html = html.replace(new RegExp("(?<!\\*)\\*(?!\\*)(.+?)(?<!\\*)\\*(?!\\*)", "g"), '<em><span class="syntax-marker">*</span>$1<span class="syntax-marker">*</span></em>');
155
- html = html.replace(new RegExp("(?<!_)_(?!_)(.+?)(?<!_)_(?!_)", "g"), '<em><span class="syntax-marker">_</span>$1<span class="syntax-marker">_</span></em>');
155
+ html = html.replace(new RegExp("(?<=^|\\s)_(?!_)(.+?)(?<!_)_(?!_)(?=\\s|$)", "g"), '<em><span class="syntax-marker">_</span>$1<span class="syntax-marker">_</span></em>');
156
156
  return html;
157
157
  }
158
158
  /**
@@ -209,31 +209,116 @@ var MarkdownParser = class {
209
209
  });
210
210
  }
211
211
  /**
212
- * Parse all inline elements in correct order
213
- * @param {string} text - Text with potential inline markdown
214
- * @returns {string} HTML with all inline styling
212
+ * Identify and protect sanctuaries (code and links) before parsing
213
+ * @param {string} text - Text with potential markdown
214
+ * @returns {Object} Object with protected text and sanctuary map
215
215
  */
216
- static parseInlineElements(text) {
217
- let html = text;
218
- html = this.parseInlineCode(html);
216
+ static identifyAndProtectSanctuaries(text) {
219
217
  const sanctuaries = /* @__PURE__ */ new Map();
220
- html = html.replace(/(<code>.*?<\/code>)/g, (match) => {
221
- const placeholder = `\uE000${sanctuaries.size}\uE001`;
222
- sanctuaries.set(placeholder, match);
223
- return placeholder;
218
+ let sanctuaryCounter = 0;
219
+ let protectedText = text;
220
+ const protectedRegions = [];
221
+ const linkRegex = /\[([^\]]+)\]\(([^)]+)\)/g;
222
+ let linkMatch;
223
+ while ((linkMatch = linkRegex.exec(text)) !== null) {
224
+ const bracketPos = linkMatch.index + linkMatch[0].indexOf("](");
225
+ const urlStart = bracketPos + 2;
226
+ const urlEnd = urlStart + linkMatch[2].length;
227
+ protectedRegions.push({ start: urlStart, end: urlEnd });
228
+ }
229
+ const codeRegex = new RegExp("(?<!`)(`+)(?!`)((?:(?!\\1).)+?)(\\1)(?!`)", "g");
230
+ let codeMatch;
231
+ const codeMatches = [];
232
+ while ((codeMatch = codeRegex.exec(text)) !== null) {
233
+ const codeStart = codeMatch.index;
234
+ const codeEnd = codeMatch.index + codeMatch[0].length;
235
+ const inProtectedRegion = protectedRegions.some(
236
+ (region) => codeStart >= region.start && codeEnd <= region.end
237
+ );
238
+ if (!inProtectedRegion) {
239
+ codeMatches.push({
240
+ match: codeMatch[0],
241
+ index: codeMatch.index,
242
+ openTicks: codeMatch[1],
243
+ content: codeMatch[2],
244
+ closeTicks: codeMatch[3]
245
+ });
246
+ }
247
+ }
248
+ codeMatches.sort((a, b) => b.index - a.index);
249
+ codeMatches.forEach((codeInfo) => {
250
+ const placeholder = `\uE000${sanctuaryCounter++}\uE001`;
251
+ sanctuaries.set(placeholder, {
252
+ type: "code",
253
+ original: codeInfo.match,
254
+ openTicks: codeInfo.openTicks,
255
+ content: codeInfo.content,
256
+ closeTicks: codeInfo.closeTicks
257
+ });
258
+ protectedText = protectedText.substring(0, codeInfo.index) + placeholder + protectedText.substring(codeInfo.index + codeInfo.match.length);
224
259
  });
225
- html = this.parseLinks(html);
226
- html = html.replace(/(<a[^>]*>.*?<\/a>)/g, (match) => {
227
- const placeholder = `\uE000${sanctuaries.size}\uE001`;
228
- sanctuaries.set(placeholder, match);
260
+ protectedText = protectedText.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (match, linkText, url) => {
261
+ const placeholder = `\uE000${sanctuaryCounter++}\uE001`;
262
+ sanctuaries.set(placeholder, {
263
+ type: "link",
264
+ original: match,
265
+ linkText,
266
+ url
267
+ });
229
268
  return placeholder;
230
269
  });
270
+ return { protectedText, sanctuaries };
271
+ }
272
+ /**
273
+ * Restore and transform sanctuaries back to HTML
274
+ * @param {string} html - HTML with sanctuary placeholders
275
+ * @param {Map} sanctuaries - Map of sanctuaries to restore
276
+ * @returns {string} HTML with sanctuaries restored and transformed
277
+ */
278
+ static restoreAndTransformSanctuaries(html, sanctuaries) {
279
+ const placeholders = Array.from(sanctuaries.keys()).sort((a, b) => {
280
+ const indexA = html.indexOf(a);
281
+ const indexB = html.indexOf(b);
282
+ return indexA - indexB;
283
+ });
284
+ placeholders.forEach((placeholder) => {
285
+ const sanctuary = sanctuaries.get(placeholder);
286
+ let replacement;
287
+ if (sanctuary.type === "code") {
288
+ replacement = `<code><span class="syntax-marker">${sanctuary.openTicks}</span>${this.escapeHtml(sanctuary.content)}<span class="syntax-marker">${sanctuary.closeTicks}</span></code>`;
289
+ } else if (sanctuary.type === "link") {
290
+ let processedLinkText = sanctuary.linkText;
291
+ sanctuaries.forEach((innerSanctuary, innerPlaceholder) => {
292
+ if (processedLinkText.includes(innerPlaceholder)) {
293
+ if (innerSanctuary.type === "code") {
294
+ const codeHtml = `<code><span class="syntax-marker">${innerSanctuary.openTicks}</span>${this.escapeHtml(innerSanctuary.content)}<span class="syntax-marker">${innerSanctuary.closeTicks}</span></code>`;
295
+ processedLinkText = processedLinkText.replace(innerPlaceholder, codeHtml);
296
+ }
297
+ }
298
+ });
299
+ processedLinkText = this.parseStrikethrough(processedLinkText);
300
+ processedLinkText = this.parseBold(processedLinkText);
301
+ processedLinkText = this.parseItalic(processedLinkText);
302
+ const anchorName = `--link-${this.linkIndex++}`;
303
+ const safeUrl = this.sanitizeUrl(sanctuary.url);
304
+ replacement = `<a href="${safeUrl}" style="anchor-name: ${anchorName}"><span class="syntax-marker">[</span>${processedLinkText}<span class="syntax-marker url-part">](${this.escapeHtml(sanctuary.url)})</span></a>`;
305
+ }
306
+ html = html.replace(placeholder, replacement);
307
+ });
308
+ return html;
309
+ }
310
+ /**
311
+ * Parse all inline elements in correct order
312
+ * @param {string} text - Text with potential inline markdown
313
+ * @returns {string} HTML with all inline styling
314
+ */
315
+ static parseInlineElements(text) {
316
+ const { protectedText, sanctuaries } = this.identifyAndProtectSanctuaries(text);
317
+ let html = protectedText;
231
318
  html = this.parseStrikethrough(html);
232
319
  html = this.parseBold(html);
233
320
  html = this.parseItalic(html);
234
- sanctuaries.forEach((content, placeholder) => {
235
- html = html.replace(placeholder, content);
236
- });
321
+ html = this.restoreAndTransformSanctuaries(html, sanctuaries);
237
322
  return html;
238
323
  }
239
324
  /**