overtype 1.2.3 → 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 +36 -15
- package/dist/overtype.cjs +253 -57
- package/dist/overtype.cjs.map +2 -2
- package/dist/overtype.d.ts +169 -0
- package/dist/overtype.esm.js +253 -57
- package/dist/overtype.esm.js.map +2 -2
- package/dist/overtype.js +258 -57
- package/dist/overtype.js.map +2 -2
- package/dist/overtype.min.js +58 -42
- package/package.json +5 -2
- package/src/link-tooltip.js +16 -16
- package/src/overtype.d.ts +23 -1
- package/src/overtype.js +22 -12
- package/src/parser.js +272 -100
- package/src/styles.js +16 -8
- package/src/toolbar.js +63 -2
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. ~
|
|
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
|
+
|
|
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** - ~
|
|
22
|
+
- 🚀 **Lightweight** - ~86KB 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** | ~
|
|
38
|
+
| **Size** | ~86KB | 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
|
|
225
|
+
// Get rendered HTML with syntax markers (for debugging/inspection)
|
|
216
226
|
const html = editor.getRenderedHTML();
|
|
217
|
-
// Returns
|
|
227
|
+
// Returns HTML with <span class="syntax-marker"> elements visible
|
|
218
228
|
|
|
219
|
-
// Get HTML
|
|
220
|
-
const
|
|
221
|
-
// Returns HTML
|
|
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
|
-
//
|
|
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:
|
|
228
|
-
|
|
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()
|
|
375
|
-
editor.getRenderedHTML(true)
|
|
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() //
|
|
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** - ~
|
|
586
|
+
- **Smaller footprint** - ~86KB 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
|
+
* 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,18 @@ 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("(
|
|
155
|
+
html = html.replace(new RegExp("(?<=^|\\s)_(?!_)(.+?)(?<!_)_(?!_)(?=\\s|$)", "g"), '<em><span class="syntax-marker">_</span>$1<span class="syntax-marker">_</span></em>');
|
|
156
|
+
return html;
|
|
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>');
|
|
156
167
|
return html;
|
|
157
168
|
}
|
|
158
169
|
/**
|
|
@@ -198,30 +209,116 @@ var MarkdownParser = class {
|
|
|
198
209
|
});
|
|
199
210
|
}
|
|
200
211
|
/**
|
|
201
|
-
*
|
|
202
|
-
* @param {string} text - Text with potential
|
|
203
|
-
* @returns {
|
|
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
|
|
204
215
|
*/
|
|
205
|
-
static
|
|
206
|
-
let html = text;
|
|
207
|
-
html = this.parseInlineCode(html);
|
|
216
|
+
static identifyAndProtectSanctuaries(text) {
|
|
208
217
|
const sanctuaries = /* @__PURE__ */ new Map();
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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);
|
|
213
259
|
});
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
+
});
|
|
218
268
|
return placeholder;
|
|
219
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;
|
|
318
|
+
html = this.parseStrikethrough(html);
|
|
220
319
|
html = this.parseBold(html);
|
|
221
320
|
html = this.parseItalic(html);
|
|
222
|
-
|
|
223
|
-
html = html.replace(placeholder, content);
|
|
224
|
-
});
|
|
321
|
+
html = this.restoreAndTransformSanctuaries(html, sanctuaries);
|
|
225
322
|
return html;
|
|
226
323
|
}
|
|
227
324
|
/**
|
|
@@ -351,6 +448,17 @@ var MarkdownParser = class {
|
|
|
351
448
|
container.insertBefore(currentList, child);
|
|
352
449
|
listType = newType;
|
|
353
450
|
}
|
|
451
|
+
const indentationNodes = [];
|
|
452
|
+
for (const node of child.childNodes) {
|
|
453
|
+
if (node.nodeType === 3 && node.textContent.match(/^\u00A0+$/)) {
|
|
454
|
+
indentationNodes.push(node.cloneNode(true));
|
|
455
|
+
} else if (node === listItem) {
|
|
456
|
+
break;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
indentationNodes.forEach((node) => {
|
|
460
|
+
listItem.insertBefore(node, listItem.firstChild);
|
|
461
|
+
});
|
|
354
462
|
currentList.appendChild(listItem);
|
|
355
463
|
child.remove();
|
|
356
464
|
} else {
|
|
@@ -368,15 +476,35 @@ var MarkdownParser = class {
|
|
|
368
476
|
static postProcessHTMLManual(html) {
|
|
369
477
|
let processed = html;
|
|
370
478
|
processed = processed.replace(/((?:<div>(?: )*<li class="bullet-list">.*?<\/li><\/div>\s*)+)/gs, (match) => {
|
|
371
|
-
const
|
|
372
|
-
if (
|
|
479
|
+
const divs = match.match(/<div>(?: )*<li class="bullet-list">.*?<\/li><\/div>/gs) || [];
|
|
480
|
+
if (divs.length > 0) {
|
|
481
|
+
const items = divs.map((div) => {
|
|
482
|
+
const indentMatch = div.match(/<div>((?: )*)<li/);
|
|
483
|
+
const listItemMatch = div.match(/<li class="bullet-list">.*?<\/li>/);
|
|
484
|
+
if (indentMatch && listItemMatch) {
|
|
485
|
+
const indentation = indentMatch[1];
|
|
486
|
+
const listItem = listItemMatch[0];
|
|
487
|
+
return listItem.replace(/<li class="bullet-list">/, `<li class="bullet-list">${indentation}`);
|
|
488
|
+
}
|
|
489
|
+
return listItemMatch ? listItemMatch[0] : "";
|
|
490
|
+
}).filter(Boolean);
|
|
373
491
|
return "<ul>" + items.join("") + "</ul>";
|
|
374
492
|
}
|
|
375
493
|
return match;
|
|
376
494
|
});
|
|
377
495
|
processed = processed.replace(/((?:<div>(?: )*<li class="ordered-list">.*?<\/li><\/div>\s*)+)/gs, (match) => {
|
|
378
|
-
const
|
|
379
|
-
if (
|
|
496
|
+
const divs = match.match(/<div>(?: )*<li class="ordered-list">.*?<\/li><\/div>/gs) || [];
|
|
497
|
+
if (divs.length > 0) {
|
|
498
|
+
const items = divs.map((div) => {
|
|
499
|
+
const indentMatch = div.match(/<div>((?: )*)<li/);
|
|
500
|
+
const listItemMatch = div.match(/<li class="ordered-list">.*?<\/li>/);
|
|
501
|
+
if (indentMatch && listItemMatch) {
|
|
502
|
+
const indentation = indentMatch[1];
|
|
503
|
+
const listItem = listItemMatch[0];
|
|
504
|
+
return listItem.replace(/<li class="ordered-list">/, `<li class="ordered-list">${indentation}`);
|
|
505
|
+
}
|
|
506
|
+
return listItemMatch ? listItemMatch[0] : "";
|
|
507
|
+
}).filter(Boolean);
|
|
380
508
|
return "<ol>" + items.join("") + "</ol>";
|
|
381
509
|
}
|
|
382
510
|
return match;
|
|
@@ -1918,6 +2046,14 @@ function generateStyles(options = {}) {
|
|
|
1918
2046
|
font-style: italic !important;
|
|
1919
2047
|
}
|
|
1920
2048
|
|
|
2049
|
+
/* Strikethrough text */
|
|
2050
|
+
.overtype-wrapper .overtype-preview del {
|
|
2051
|
+
color: var(--del, #ee964b) !important;
|
|
2052
|
+
text-decoration: line-through !important;
|
|
2053
|
+
text-decoration-color: var(--del, #ee964b) !important;
|
|
2054
|
+
text-decoration-thickness: 1px !important;
|
|
2055
|
+
}
|
|
2056
|
+
|
|
1921
2057
|
/* Inline code */
|
|
1922
2058
|
.overtype-wrapper .overtype-preview code {
|
|
1923
2059
|
background: var(--code-bg, rgba(244, 211, 94, 0.4)) !important;
|
|
@@ -2061,10 +2197,10 @@ function generateStyles(options = {}) {
|
|
|
2061
2197
|
height: 8px !important;
|
|
2062
2198
|
background: #4caf50 !important;
|
|
2063
2199
|
border-radius: 50% !important;
|
|
2064
|
-
animation: pulse 2s infinite !important;
|
|
2200
|
+
animation: overtype-pulse 2s infinite !important;
|
|
2065
2201
|
}
|
|
2066
2202
|
|
|
2067
|
-
@keyframes pulse {
|
|
2203
|
+
@keyframes overtype-pulse {
|
|
2068
2204
|
0%, 100% { opacity: 1; transform: scale(1); }
|
|
2069
2205
|
50% { opacity: 0.6; transform: scale(1.2); }
|
|
2070
2206
|
}
|
|
@@ -2072,19 +2208,19 @@ function generateStyles(options = {}) {
|
|
|
2072
2208
|
|
|
2073
2209
|
/* Toolbar Styles */
|
|
2074
2210
|
.overtype-toolbar {
|
|
2075
|
-
display: flex;
|
|
2076
|
-
align-items: center;
|
|
2077
|
-
gap: 4px;
|
|
2211
|
+
display: flex !important;
|
|
2212
|
+
align-items: center !important;
|
|
2213
|
+
gap: 4px !important;
|
|
2078
2214
|
padding: 8px !important; /* Override reset */
|
|
2079
2215
|
background: var(--toolbar-bg, var(--bg-primary, #f8f9fa)) !important; /* Override reset */
|
|
2080
2216
|
overflow-x: auto !important; /* Allow horizontal scrolling */
|
|
2081
2217
|
overflow-y: hidden !important; /* Hide vertical overflow */
|
|
2082
|
-
-webkit-overflow-scrolling: touch;
|
|
2083
|
-
flex-shrink: 0;
|
|
2218
|
+
-webkit-overflow-scrolling: touch !important;
|
|
2219
|
+
flex-shrink: 0 !important;
|
|
2084
2220
|
height: auto !important;
|
|
2085
2221
|
grid-row: 1 !important; /* Always first row in grid */
|
|
2086
2222
|
position: relative !important; /* Override reset */
|
|
2087
|
-
z-index: 100; /* Ensure toolbar is above wrapper */
|
|
2223
|
+
z-index: 100 !important; /* Ensure toolbar is above wrapper */
|
|
2088
2224
|
scrollbar-width: thin; /* Thin scrollbar on Firefox */
|
|
2089
2225
|
}
|
|
2090
2226
|
|
|
@@ -2466,20 +2602,67 @@ var eyeIcon = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke
|
|
|
2466
2602
|
|
|
2467
2603
|
// src/toolbar.js
|
|
2468
2604
|
var Toolbar = class {
|
|
2469
|
-
constructor(editor) {
|
|
2605
|
+
constructor(editor, buttonConfig = null) {
|
|
2470
2606
|
this.editor = editor;
|
|
2471
2607
|
this.container = null;
|
|
2472
2608
|
this.buttons = {};
|
|
2609
|
+
this.buttonConfig = buttonConfig;
|
|
2610
|
+
}
|
|
2611
|
+
/**
|
|
2612
|
+
* Check if cursor/selection is inside a markdown link
|
|
2613
|
+
* @param {HTMLTextAreaElement} textarea - The textarea element
|
|
2614
|
+
* @returns {boolean} True if inside a link
|
|
2615
|
+
*/
|
|
2616
|
+
isInsideLink(textarea) {
|
|
2617
|
+
const value = textarea.value;
|
|
2618
|
+
const start = textarea.selectionStart;
|
|
2619
|
+
const end = textarea.selectionEnd;
|
|
2620
|
+
let insideLink = false;
|
|
2621
|
+
let openBracket = -1;
|
|
2622
|
+
let closeBracket = -1;
|
|
2623
|
+
for (let i = start - 1; i >= 0; i--) {
|
|
2624
|
+
if (value[i] === "[") {
|
|
2625
|
+
openBracket = i;
|
|
2626
|
+
break;
|
|
2627
|
+
}
|
|
2628
|
+
if (value[i] === "\n") {
|
|
2629
|
+
break;
|
|
2630
|
+
}
|
|
2631
|
+
}
|
|
2632
|
+
if (openBracket >= 0) {
|
|
2633
|
+
for (let i = end; i < value.length - 1; i++) {
|
|
2634
|
+
if (value[i] === "]" && value[i + 1] === "(") {
|
|
2635
|
+
closeBracket = i;
|
|
2636
|
+
break;
|
|
2637
|
+
}
|
|
2638
|
+
if (value[i] === "\n") {
|
|
2639
|
+
break;
|
|
2640
|
+
}
|
|
2641
|
+
}
|
|
2642
|
+
}
|
|
2643
|
+
if (openBracket >= 0 && closeBracket >= 0) {
|
|
2644
|
+
for (let i = closeBracket + 2; i < value.length; i++) {
|
|
2645
|
+
if (value[i] === ")") {
|
|
2646
|
+
insideLink = true;
|
|
2647
|
+
break;
|
|
2648
|
+
}
|
|
2649
|
+
if (value[i] === "\n" || value[i] === " ") {
|
|
2650
|
+
break;
|
|
2651
|
+
}
|
|
2652
|
+
}
|
|
2653
|
+
}
|
|
2654
|
+
return insideLink;
|
|
2473
2655
|
}
|
|
2474
2656
|
/**
|
|
2475
2657
|
* Create and attach toolbar to editor
|
|
2476
2658
|
*/
|
|
2477
2659
|
create() {
|
|
2660
|
+
var _a;
|
|
2478
2661
|
this.container = document.createElement("div");
|
|
2479
2662
|
this.container.className = "overtype-toolbar";
|
|
2480
2663
|
this.container.setAttribute("role", "toolbar");
|
|
2481
2664
|
this.container.setAttribute("aria-label", "Text formatting");
|
|
2482
|
-
const buttonConfig = [
|
|
2665
|
+
const buttonConfig = (_a = this.buttonConfig) != null ? _a : [
|
|
2483
2666
|
{ name: "bold", icon: boldIcon, title: "Bold (Ctrl+B)", action: "toggleBold" },
|
|
2484
2667
|
{ name: "italic", icon: italicIcon, title: "Italic (Ctrl+I)", action: "toggleItalic" },
|
|
2485
2668
|
{ separator: true },
|
|
@@ -2573,6 +2756,9 @@ var Toolbar = class {
|
|
|
2573
2756
|
insertLink(textarea);
|
|
2574
2757
|
break;
|
|
2575
2758
|
case "toggleCode":
|
|
2759
|
+
if (this.isInsideLink(textarea)) {
|
|
2760
|
+
return;
|
|
2761
|
+
}
|
|
2576
2762
|
toggleCode(textarea);
|
|
2577
2763
|
break;
|
|
2578
2764
|
case "toggleBulletList":
|
|
@@ -2788,29 +2974,29 @@ var LinkTooltip = class {
|
|
|
2788
2974
|
position: absolute;
|
|
2789
2975
|
position-anchor: var(--target-anchor, --link-0);
|
|
2790
2976
|
position-area: block-end center;
|
|
2791
|
-
margin-top: 8px;
|
|
2977
|
+
margin-top: 8px !important;
|
|
2792
2978
|
|
|
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;
|
|
2979
|
+
background: #333 !important;
|
|
2980
|
+
color: white !important;
|
|
2981
|
+
padding: 6px 10px !important;
|
|
2982
|
+
border-radius: 16px !important;
|
|
2983
|
+
font-size: 12px !important;
|
|
2984
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif !important;
|
|
2985
|
+
display: none !important;
|
|
2986
|
+
z-index: 10000 !important;
|
|
2987
|
+
cursor: pointer !important;
|
|
2988
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.3) !important;
|
|
2989
|
+
max-width: 300px !important;
|
|
2990
|
+
white-space: nowrap !important;
|
|
2991
|
+
overflow: hidden !important;
|
|
2992
|
+
text-overflow: ellipsis !important;
|
|
2807
2993
|
|
|
2808
2994
|
position-try: most-width block-end inline-end, flip-inline, block-start center;
|
|
2809
2995
|
position-visibility: anchors-visible;
|
|
2810
2996
|
}
|
|
2811
2997
|
|
|
2812
2998
|
.overtype-link-tooltip.visible {
|
|
2813
|
-
display: flex;
|
|
2999
|
+
display: flex !important;
|
|
2814
3000
|
}
|
|
2815
3001
|
}
|
|
2816
3002
|
`;
|
|
@@ -2958,7 +3144,8 @@ var _OverType = class _OverType {
|
|
|
2958
3144
|
this.shortcuts = new ShortcutsManager(this);
|
|
2959
3145
|
this.linkTooltip = new LinkTooltip(this);
|
|
2960
3146
|
if (this.options.toolbar) {
|
|
2961
|
-
this.toolbar
|
|
3147
|
+
const toolbarButtons = typeof this.options.toolbar === "object" ? this.options.toolbar.buttons : null;
|
|
3148
|
+
this.toolbar = new Toolbar(this, toolbarButtons);
|
|
2962
3149
|
this.toolbar.create();
|
|
2963
3150
|
this.textarea.addEventListener("selectionchange", () => {
|
|
2964
3151
|
this.toolbar.updateButtonStates();
|
|
@@ -3440,24 +3627,36 @@ var _OverType = class _OverType {
|
|
|
3440
3627
|
}
|
|
3441
3628
|
/**
|
|
3442
3629
|
* Get the rendered HTML of the current content
|
|
3443
|
-
* @param {
|
|
3630
|
+
* @param {Object} options - Rendering options
|
|
3631
|
+
* @param {boolean} options.cleanHTML - If true, removes syntax markers and OverType-specific classes
|
|
3444
3632
|
* @returns {string} Rendered HTML
|
|
3445
3633
|
*/
|
|
3446
|
-
getRenderedHTML(
|
|
3634
|
+
getRenderedHTML(options = {}) {
|
|
3447
3635
|
const markdown = this.getValue();
|
|
3448
3636
|
let html = MarkdownParser.parse(markdown);
|
|
3449
|
-
if (
|
|
3450
|
-
html =
|
|
3637
|
+
if (options.cleanHTML) {
|
|
3638
|
+
html = html.replace(/<span class="syntax-marker[^"]*">.*?<\/span>/g, "");
|
|
3639
|
+
html = html.replace(/\sclass="(bullet-list|ordered-list|code-fence|hr-marker|blockquote|url-part)"/g, "");
|
|
3640
|
+
html = html.replace(/\sclass=""/g, "");
|
|
3451
3641
|
}
|
|
3452
3642
|
return html;
|
|
3453
3643
|
}
|
|
3454
3644
|
/**
|
|
3455
3645
|
* Get the current preview element's HTML
|
|
3646
|
+
* This includes all syntax markers and OverType styling
|
|
3456
3647
|
* @returns {string} Current preview HTML (as displayed)
|
|
3457
3648
|
*/
|
|
3458
3649
|
getPreviewHTML() {
|
|
3459
3650
|
return this.preview.innerHTML;
|
|
3460
3651
|
}
|
|
3652
|
+
/**
|
|
3653
|
+
* Get clean HTML without any OverType-specific markup
|
|
3654
|
+
* Useful for exporting to other formats or storage
|
|
3655
|
+
* @returns {string} Clean HTML suitable for export
|
|
3656
|
+
*/
|
|
3657
|
+
getCleanHTML() {
|
|
3658
|
+
return this.getRenderedHTML({ cleanHTML: true });
|
|
3659
|
+
}
|
|
3461
3660
|
/**
|
|
3462
3661
|
* Focus the editor
|
|
3463
3662
|
*/
|
|
@@ -3776,9 +3975,6 @@ OverType.ShortcutsManager = ShortcutsManager;
|
|
|
3776
3975
|
OverType.themes = { solar, cave: getTheme("cave") };
|
|
3777
3976
|
OverType.getTheme = getTheme;
|
|
3778
3977
|
OverType.currentTheme = solar;
|
|
3779
|
-
if (typeof window !== "undefined" && typeof window.document !== "undefined") {
|
|
3780
|
-
window.OverType = OverType;
|
|
3781
|
-
}
|
|
3782
3978
|
var overtype_default = OverType;
|
|
3783
3979
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3784
3980
|
0 && (module.exports = {
|