overtype 1.2.1 → 1.2.3
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 +28 -3
- package/dist/overtype.cjs +274 -23
- package/dist/overtype.cjs.map +2 -2
- package/dist/overtype.esm.js +274 -23
- package/dist/overtype.esm.js.map +2 -2
- package/dist/overtype.js +274 -23
- package/dist/overtype.js.map +2 -2
- package/dist/overtype.min.js +60 -47
- package/package.json +5 -2
- package/src/overtype.js +148 -20
- package/src/parser.js +210 -19
- package/src/styles.js +6 -0
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. ~
|
|
3
|
+
A lightweight markdown editor library with perfect WYSIWYG alignment using an invisible textarea overlay technique. Includes optional toolbar. ~82KB minified with all features.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
@@ -9,9 +9,10 @@ A lightweight markdown editor library with perfect WYSIWYG alignment using an in
|
|
|
9
9
|
- ⌨️ **Keyboard shortcuts** - Common markdown shortcuts (Cmd/Ctrl+B for bold, etc.)
|
|
10
10
|
- 📱 **Mobile optimized** - Responsive design with mobile-specific styles
|
|
11
11
|
- 🔄 **DOM persistence aware** - Recovers from existing DOM (perfect for HyperClay and similar platforms)
|
|
12
|
-
- 🚀 **Lightweight** - ~
|
|
12
|
+
- 🚀 **Lightweight** - ~82KB minified
|
|
13
13
|
- 🎯 **Optional toolbar** - Clean, minimal toolbar with all essential formatting
|
|
14
14
|
- ✨ **Smart shortcuts** - Keyboard shortcuts with selection preservation
|
|
15
|
+
- 📝 **Smart list continuation** - GitHub-style automatic list continuation on Enter
|
|
15
16
|
- 🔧 **Framework agnostic** - Works with React, Vue, vanilla JS, and more
|
|
16
17
|
|
|
17
18
|
## How it works
|
|
@@ -24,7 +25,7 @@ We overlap an invisible textarea on top of styled output, giving the illusion of
|
|
|
24
25
|
|
|
25
26
|
| Feature | OverType | HyperMD | Milkdown | TUI Editor | EasyMDE |
|
|
26
27
|
|---------|----------|---------|----------|------------|---------|
|
|
27
|
-
| **Size** | ~
|
|
28
|
+
| **Size** | ~82KB | 364.02 KB | 344.51 KB | 560.99 KB | 323.69 KB |
|
|
28
29
|
| **Dependencies** | Bundled | CodeMirror | ProseMirror + plugins | Multiple libs | CodeMirror |
|
|
29
30
|
| **Setup** | Single file | Complex config | Build step required | Complex config | Moderate |
|
|
30
31
|
| **Approach** | Invisible textarea | ContentEditable | ContentEditable | ContentEditable | CodeMirror |
|
|
@@ -345,6 +346,9 @@ new OverType(target, options)
|
|
|
345
346
|
// Toolbar
|
|
346
347
|
toolbar: false, // Enable/disable toolbar with formatting buttons
|
|
347
348
|
|
|
349
|
+
// Smart lists
|
|
350
|
+
smartLists: true, // Enable GitHub-style list continuation on Enter
|
|
351
|
+
|
|
348
352
|
// Stats bar
|
|
349
353
|
showStats: false, // Enable/disable stats bar
|
|
350
354
|
statsFormatter: (stats) => { // Custom stats format
|
|
@@ -547,11 +551,32 @@ Special thanks to:
|
|
|
547
551
|
- [kbhomes](https://github.com/kbhomes) - Fixed text selection desynchronization during overscroll ([#17](https://github.com/panphora/overtype/pull/17))
|
|
548
552
|
- [merlinz01](https://github.com/merlinz01) - Initial TypeScript definitions implementation ([#20](https://github.com/panphora/overtype/pull/20))
|
|
549
553
|
- [Max Bernstein](https://github.com/tekknolagi) - Fixed typo in website ([#11](https://github.com/panphora/overtype/pull/11))
|
|
554
|
+
- [davidlazar](https://github.com/davidlazar) - Suggested view mode feature for toggling overlay and preview modes ([#24](https://github.com/panphora/overtype/issues/24))
|
|
550
555
|
|
|
551
556
|
## License
|
|
552
557
|
|
|
553
558
|
MIT
|
|
554
559
|
|
|
560
|
+
## Related Projects
|
|
561
|
+
|
|
562
|
+
### Synesthesia
|
|
563
|
+
|
|
564
|
+
[Synesthesia](https://github.com/panphora/synesthesia) is a lightweight syntax highlighting editor library that extracted and refined the core textarea overlay technique from OverType. While OverType is focused on markdown editing with toolbar features, Synesthesia provides a more generalized code editing solution with:
|
|
565
|
+
|
|
566
|
+
- **Pluggable parser system** - Support for any programming language or syntax
|
|
567
|
+
- **Parser registry** - Automatic language detection by file extension or MIME type
|
|
568
|
+
- **Cleaner separation** - Extracted the overlay technique without markdown-specific features
|
|
569
|
+
- **Smaller footprint** - ~82KB minified (vs OverType's ~78KB)
|
|
570
|
+
|
|
571
|
+
Key components extracted from OverType to Synesthesia:
|
|
572
|
+
- The transparent textarea overlay technique for perfect WYSIWYG alignment
|
|
573
|
+
- Theme system with CSS variable support
|
|
574
|
+
- DOM persistence and recovery mechanisms
|
|
575
|
+
- Auto-resize functionality
|
|
576
|
+
- Event delegation for efficient multi-instance support
|
|
577
|
+
|
|
578
|
+
If you need a markdown editor with toolbar and formatting features, use OverType. If you need a lightweight code editor with custom syntax highlighting, check out Synesthesia.
|
|
579
|
+
|
|
555
580
|
## Contributing
|
|
556
581
|
|
|
557
582
|
Contributions are welcome! Please feel free to submit a Pull Request.
|
package/dist/overtype.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* OverType v1.2.
|
|
2
|
+
* OverType v1.2.2
|
|
3
3
|
* A lightweight markdown editor library with perfect WYSIWYG alignment
|
|
4
4
|
* @license MIT
|
|
5
5
|
* @author Demo User
|
|
@@ -258,11 +258,22 @@ var MarkdownParser = class {
|
|
|
258
258
|
static parse(text, activeLine = -1, showActiveLineRaw = false) {
|
|
259
259
|
this.resetLinkIndex();
|
|
260
260
|
const lines = text.split("\n");
|
|
261
|
+
let inCodeBlock = false;
|
|
261
262
|
const parsedLines = lines.map((line, index) => {
|
|
262
263
|
if (showActiveLineRaw && index === activeLine) {
|
|
263
264
|
const content = this.escapeHtml(line) || " ";
|
|
264
265
|
return `<div class="raw-line">${content}</div>`;
|
|
265
266
|
}
|
|
267
|
+
const codeFenceRegex = /^```[^`]*$/;
|
|
268
|
+
if (codeFenceRegex.test(line)) {
|
|
269
|
+
inCodeBlock = !inCodeBlock;
|
|
270
|
+
return this.parseLine(line);
|
|
271
|
+
}
|
|
272
|
+
if (inCodeBlock) {
|
|
273
|
+
const escaped = this.escapeHtml(line);
|
|
274
|
+
const indented = this.preserveIndentation(escaped, line);
|
|
275
|
+
return `<div>${indented || " "}</div>`;
|
|
276
|
+
}
|
|
266
277
|
return this.parseLine(line);
|
|
267
278
|
});
|
|
268
279
|
const html = parsedLines.join("");
|
|
@@ -302,23 +313,22 @@ var MarkdownParser = class {
|
|
|
302
313
|
if (lang) {
|
|
303
314
|
codeElement.className = `language-${lang}`;
|
|
304
315
|
}
|
|
305
|
-
container.insertBefore(currentCodeBlock, child);
|
|
306
|
-
|
|
316
|
+
container.insertBefore(currentCodeBlock, child.nextSibling);
|
|
317
|
+
currentCodeBlock._codeElement = codeElement;
|
|
307
318
|
continue;
|
|
308
319
|
} else {
|
|
309
320
|
inCodeBlock = false;
|
|
310
321
|
currentCodeBlock = null;
|
|
311
|
-
child.remove();
|
|
312
322
|
continue;
|
|
313
323
|
}
|
|
314
324
|
}
|
|
315
325
|
}
|
|
316
326
|
if (inCodeBlock && currentCodeBlock && child.tagName === "DIV" && !child.querySelector(".code-fence")) {
|
|
317
|
-
const codeElement = currentCodeBlock.querySelector("code");
|
|
327
|
+
const codeElement = currentCodeBlock._codeElement || currentCodeBlock.querySelector("code");
|
|
318
328
|
if (codeElement.textContent.length > 0) {
|
|
319
329
|
codeElement.textContent += "\n";
|
|
320
330
|
}
|
|
321
|
-
const lineText = child.
|
|
331
|
+
const lineText = child.textContent.replace(/\u00A0/g, " ");
|
|
322
332
|
codeElement.textContent += lineText;
|
|
323
333
|
child.remove();
|
|
324
334
|
continue;
|
|
@@ -371,21 +381,163 @@ var MarkdownParser = class {
|
|
|
371
381
|
}
|
|
372
382
|
return match;
|
|
373
383
|
});
|
|
374
|
-
const codeBlockRegex = /<div><span class="code-fence"
|
|
375
|
-
processed = processed.replace(codeBlockRegex, (match,
|
|
384
|
+
const codeBlockRegex = /<div><span class="code-fence">(```[^<]*)<\/span><\/div>(.*?)<div><span class="code-fence">(```)<\/span><\/div>/gs;
|
|
385
|
+
processed = processed.replace(codeBlockRegex, (match, openFence, content, closeFence) => {
|
|
376
386
|
const lines = content.match(/<div>(.*?)<\/div>/gs) || [];
|
|
377
387
|
const codeContent = lines.map((line) => {
|
|
378
|
-
const text = line.replace(/<div>(.*?)<\/div>/s, "$1").replace(/ /g, " ")
|
|
388
|
+
const text = line.replace(/<div>(.*?)<\/div>/s, "$1").replace(/ /g, " ");
|
|
379
389
|
return text;
|
|
380
390
|
}).join("\n");
|
|
381
|
-
const
|
|
382
|
-
|
|
391
|
+
const lang = openFence.slice(3).trim();
|
|
392
|
+
const langClass = lang ? ` class="language-${lang}"` : "";
|
|
393
|
+
let result = `<div><span class="code-fence">${openFence}</span></div>`;
|
|
394
|
+
result += `<pre class="code-block"><code${langClass}>${codeContent}</code></pre>`;
|
|
395
|
+
result += `<div><span class="code-fence">${closeFence}</span></div>`;
|
|
396
|
+
return result;
|
|
383
397
|
});
|
|
384
398
|
return processed;
|
|
385
399
|
}
|
|
400
|
+
/**
|
|
401
|
+
* Get list context at cursor position
|
|
402
|
+
* @param {string} text - Full text content
|
|
403
|
+
* @param {number} cursorPosition - Current cursor position
|
|
404
|
+
* @returns {Object} List context information
|
|
405
|
+
*/
|
|
406
|
+
static getListContext(text, cursorPosition) {
|
|
407
|
+
const lines = text.split("\n");
|
|
408
|
+
let currentPos = 0;
|
|
409
|
+
let lineIndex = 0;
|
|
410
|
+
let lineStart = 0;
|
|
411
|
+
for (let i = 0; i < lines.length; i++) {
|
|
412
|
+
const lineLength = lines[i].length;
|
|
413
|
+
if (currentPos + lineLength >= cursorPosition) {
|
|
414
|
+
lineIndex = i;
|
|
415
|
+
lineStart = currentPos;
|
|
416
|
+
break;
|
|
417
|
+
}
|
|
418
|
+
currentPos += lineLength + 1;
|
|
419
|
+
}
|
|
420
|
+
const currentLine = lines[lineIndex];
|
|
421
|
+
const lineEnd = lineStart + currentLine.length;
|
|
422
|
+
const checkboxMatch = currentLine.match(this.LIST_PATTERNS.checkbox);
|
|
423
|
+
if (checkboxMatch) {
|
|
424
|
+
return {
|
|
425
|
+
inList: true,
|
|
426
|
+
listType: "checkbox",
|
|
427
|
+
indent: checkboxMatch[1],
|
|
428
|
+
marker: "-",
|
|
429
|
+
checked: checkboxMatch[2] === "x",
|
|
430
|
+
content: checkboxMatch[3],
|
|
431
|
+
lineStart,
|
|
432
|
+
lineEnd,
|
|
433
|
+
markerEndPos: lineStart + checkboxMatch[1].length + checkboxMatch[2].length + 5
|
|
434
|
+
// indent + "- [ ] "
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
const bulletMatch = currentLine.match(this.LIST_PATTERNS.bullet);
|
|
438
|
+
if (bulletMatch) {
|
|
439
|
+
return {
|
|
440
|
+
inList: true,
|
|
441
|
+
listType: "bullet",
|
|
442
|
+
indent: bulletMatch[1],
|
|
443
|
+
marker: bulletMatch[2],
|
|
444
|
+
content: bulletMatch[3],
|
|
445
|
+
lineStart,
|
|
446
|
+
lineEnd,
|
|
447
|
+
markerEndPos: lineStart + bulletMatch[1].length + bulletMatch[2].length + 1
|
|
448
|
+
// indent + marker + space
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
const numberedMatch = currentLine.match(this.LIST_PATTERNS.numbered);
|
|
452
|
+
if (numberedMatch) {
|
|
453
|
+
return {
|
|
454
|
+
inList: true,
|
|
455
|
+
listType: "numbered",
|
|
456
|
+
indent: numberedMatch[1],
|
|
457
|
+
marker: parseInt(numberedMatch[2]),
|
|
458
|
+
content: numberedMatch[3],
|
|
459
|
+
lineStart,
|
|
460
|
+
lineEnd,
|
|
461
|
+
markerEndPos: lineStart + numberedMatch[1].length + numberedMatch[2].length + 2
|
|
462
|
+
// indent + number + ". "
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
return {
|
|
466
|
+
inList: false,
|
|
467
|
+
listType: null,
|
|
468
|
+
indent: "",
|
|
469
|
+
marker: null,
|
|
470
|
+
content: currentLine,
|
|
471
|
+
lineStart,
|
|
472
|
+
lineEnd,
|
|
473
|
+
markerEndPos: lineStart
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Create a new list item based on context
|
|
478
|
+
* @param {Object} context - List context from getListContext
|
|
479
|
+
* @returns {string} New list item text
|
|
480
|
+
*/
|
|
481
|
+
static createNewListItem(context) {
|
|
482
|
+
switch (context.listType) {
|
|
483
|
+
case "bullet":
|
|
484
|
+
return `${context.indent}${context.marker} `;
|
|
485
|
+
case "numbered":
|
|
486
|
+
return `${context.indent}${context.marker + 1}. `;
|
|
487
|
+
case "checkbox":
|
|
488
|
+
return `${context.indent}- [ ] `;
|
|
489
|
+
default:
|
|
490
|
+
return "";
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Renumber all numbered lists in text
|
|
495
|
+
* @param {string} text - Text containing numbered lists
|
|
496
|
+
* @returns {string} Text with renumbered lists
|
|
497
|
+
*/
|
|
498
|
+
static renumberLists(text) {
|
|
499
|
+
const lines = text.split("\n");
|
|
500
|
+
const numbersByIndent = /* @__PURE__ */ new Map();
|
|
501
|
+
let inList = false;
|
|
502
|
+
const result = lines.map((line) => {
|
|
503
|
+
const match = line.match(this.LIST_PATTERNS.numbered);
|
|
504
|
+
if (match) {
|
|
505
|
+
const indent = match[1];
|
|
506
|
+
const indentLevel = indent.length;
|
|
507
|
+
const content = match[3];
|
|
508
|
+
if (!inList) {
|
|
509
|
+
numbersByIndent.clear();
|
|
510
|
+
}
|
|
511
|
+
const currentNumber = (numbersByIndent.get(indentLevel) || 0) + 1;
|
|
512
|
+
numbersByIndent.set(indentLevel, currentNumber);
|
|
513
|
+
for (const [level] of numbersByIndent) {
|
|
514
|
+
if (level > indentLevel) {
|
|
515
|
+
numbersByIndent.delete(level);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
inList = true;
|
|
519
|
+
return `${indent}${currentNumber}. ${content}`;
|
|
520
|
+
} else {
|
|
521
|
+
if (line.trim() === "" || !line.match(/^\s/)) {
|
|
522
|
+
inList = false;
|
|
523
|
+
numbersByIndent.clear();
|
|
524
|
+
}
|
|
525
|
+
return line;
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
return result.join("\n");
|
|
529
|
+
}
|
|
386
530
|
};
|
|
387
531
|
// Track link index for anchor naming
|
|
388
532
|
__publicField(MarkdownParser, "linkIndex", 0);
|
|
533
|
+
/**
|
|
534
|
+
* List pattern definitions
|
|
535
|
+
*/
|
|
536
|
+
__publicField(MarkdownParser, "LIST_PATTERNS", {
|
|
537
|
+
bullet: /^(\s*)([-*+])\s+(.*)$/,
|
|
538
|
+
numbered: /^(\s*)(\d+)\.\s+(.*)$/,
|
|
539
|
+
checkbox: /^(\s*)-\s+\[([ x])\]\s+(.*)$/
|
|
540
|
+
});
|
|
389
541
|
|
|
390
542
|
// node_modules/markdown-actions/dist/markdown-actions.esm.js
|
|
391
543
|
var __defProp2 = Object.defineProperty;
|
|
@@ -1520,11 +1672,17 @@ function generateStyles(options = {}) {
|
|
|
1520
1672
|
position: relative !important; /* Override reset - needed for absolute children */
|
|
1521
1673
|
overflow: visible !important; /* Allow dropdown to overflow container */
|
|
1522
1674
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important;
|
|
1675
|
+
text-align: left !important;
|
|
1523
1676
|
${themeVars ? `
|
|
1524
1677
|
/* Theme Variables */
|
|
1525
1678
|
${themeVars}` : ""}
|
|
1526
1679
|
}
|
|
1527
1680
|
|
|
1681
|
+
/* Force left alignment for all elements in the editor */
|
|
1682
|
+
.overtype-container .overtype-wrapper * {
|
|
1683
|
+
text-align: left !important;
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1528
1686
|
/* Auto-resize mode styles */
|
|
1529
1687
|
.overtype-container.overtype-auto-resize {
|
|
1530
1688
|
height: auto !important;
|
|
@@ -2852,7 +3010,9 @@ var _OverType = class _OverType {
|
|
|
2852
3010
|
showActiveLineRaw: false,
|
|
2853
3011
|
showStats: false,
|
|
2854
3012
|
toolbar: false,
|
|
2855
|
-
statsFormatter: null
|
|
3013
|
+
statsFormatter: null,
|
|
3014
|
+
smartLists: true
|
|
3015
|
+
// Enable smart list continuation
|
|
2856
3016
|
};
|
|
2857
3017
|
const { theme, colors, ...cleanOptions } = options;
|
|
2858
3018
|
return {
|
|
@@ -3079,17 +3239,6 @@ var _OverType = class _OverType {
|
|
|
3079
3239
|
closeFence.style.display = "block";
|
|
3080
3240
|
openParent.classList.add("code-block-line");
|
|
3081
3241
|
closeParent.classList.add("code-block-line");
|
|
3082
|
-
let currentDiv = openParent.nextElementSibling;
|
|
3083
|
-
while (currentDiv && currentDiv !== closeParent) {
|
|
3084
|
-
if (currentDiv.tagName === "DIV") {
|
|
3085
|
-
currentDiv.classList.add("code-block-line");
|
|
3086
|
-
const plainText = currentDiv.textContent;
|
|
3087
|
-
currentDiv.textContent = plainText;
|
|
3088
|
-
}
|
|
3089
|
-
currentDiv = currentDiv.nextElementSibling;
|
|
3090
|
-
if (!currentDiv)
|
|
3091
|
-
break;
|
|
3092
|
-
}
|
|
3093
3242
|
}
|
|
3094
3243
|
}
|
|
3095
3244
|
/**
|
|
@@ -3156,11 +3305,113 @@ var _OverType = class _OverType {
|
|
|
3156
3305
|
this.textarea.dispatchEvent(new Event("input", { bubbles: true }));
|
|
3157
3306
|
return;
|
|
3158
3307
|
}
|
|
3308
|
+
if (event.key === "Enter" && !event.shiftKey && !event.metaKey && !event.ctrlKey && this.options.smartLists) {
|
|
3309
|
+
if (this.handleSmartListContinuation()) {
|
|
3310
|
+
event.preventDefault();
|
|
3311
|
+
return;
|
|
3312
|
+
}
|
|
3313
|
+
}
|
|
3159
3314
|
const handled = this.shortcuts.handleKeydown(event);
|
|
3160
3315
|
if (!handled && this.options.onKeydown) {
|
|
3161
3316
|
this.options.onKeydown(event, this);
|
|
3162
3317
|
}
|
|
3163
3318
|
}
|
|
3319
|
+
/**
|
|
3320
|
+
* Handle smart list continuation
|
|
3321
|
+
* @returns {boolean} Whether the event was handled
|
|
3322
|
+
*/
|
|
3323
|
+
handleSmartListContinuation() {
|
|
3324
|
+
const textarea = this.textarea;
|
|
3325
|
+
const cursorPos = textarea.selectionStart;
|
|
3326
|
+
const context = MarkdownParser.getListContext(textarea.value, cursorPos);
|
|
3327
|
+
if (!context || !context.inList)
|
|
3328
|
+
return false;
|
|
3329
|
+
if (context.content.trim() === "" && cursorPos >= context.markerEndPos) {
|
|
3330
|
+
this.deleteListMarker(context);
|
|
3331
|
+
return true;
|
|
3332
|
+
}
|
|
3333
|
+
if (cursorPos > context.markerEndPos && cursorPos < context.lineEnd) {
|
|
3334
|
+
this.splitListItem(context, cursorPos);
|
|
3335
|
+
} else {
|
|
3336
|
+
this.insertNewListItem(context);
|
|
3337
|
+
}
|
|
3338
|
+
if (context.listType === "numbered") {
|
|
3339
|
+
this.scheduleNumberedListUpdate();
|
|
3340
|
+
}
|
|
3341
|
+
return true;
|
|
3342
|
+
}
|
|
3343
|
+
/**
|
|
3344
|
+
* Delete list marker and exit list
|
|
3345
|
+
* @private
|
|
3346
|
+
*/
|
|
3347
|
+
deleteListMarker(context) {
|
|
3348
|
+
this.textarea.setSelectionRange(context.lineStart, context.markerEndPos);
|
|
3349
|
+
document.execCommand("delete");
|
|
3350
|
+
this.textarea.dispatchEvent(new Event("input", { bubbles: true }));
|
|
3351
|
+
}
|
|
3352
|
+
/**
|
|
3353
|
+
* Insert new list item
|
|
3354
|
+
* @private
|
|
3355
|
+
*/
|
|
3356
|
+
insertNewListItem(context) {
|
|
3357
|
+
const newItem = MarkdownParser.createNewListItem(context);
|
|
3358
|
+
document.execCommand("insertText", false, "\n" + newItem);
|
|
3359
|
+
this.textarea.dispatchEvent(new Event("input", { bubbles: true }));
|
|
3360
|
+
}
|
|
3361
|
+
/**
|
|
3362
|
+
* Split list item at cursor position
|
|
3363
|
+
* @private
|
|
3364
|
+
*/
|
|
3365
|
+
splitListItem(context, cursorPos) {
|
|
3366
|
+
const textAfterCursor = context.content.substring(cursorPos - context.markerEndPos);
|
|
3367
|
+
this.textarea.setSelectionRange(cursorPos, context.lineEnd);
|
|
3368
|
+
document.execCommand("delete");
|
|
3369
|
+
const newItem = MarkdownParser.createNewListItem(context);
|
|
3370
|
+
document.execCommand("insertText", false, "\n" + newItem + textAfterCursor);
|
|
3371
|
+
const newCursorPos = this.textarea.selectionStart - textAfterCursor.length;
|
|
3372
|
+
this.textarea.setSelectionRange(newCursorPos, newCursorPos);
|
|
3373
|
+
this.textarea.dispatchEvent(new Event("input", { bubbles: true }));
|
|
3374
|
+
}
|
|
3375
|
+
/**
|
|
3376
|
+
* Schedule numbered list renumbering
|
|
3377
|
+
* @private
|
|
3378
|
+
*/
|
|
3379
|
+
scheduleNumberedListUpdate() {
|
|
3380
|
+
if (this.numberUpdateTimeout) {
|
|
3381
|
+
clearTimeout(this.numberUpdateTimeout);
|
|
3382
|
+
}
|
|
3383
|
+
this.numberUpdateTimeout = setTimeout(() => {
|
|
3384
|
+
this.updateNumberedLists();
|
|
3385
|
+
}, 10);
|
|
3386
|
+
}
|
|
3387
|
+
/**
|
|
3388
|
+
* Update/renumber all numbered lists
|
|
3389
|
+
* @private
|
|
3390
|
+
*/
|
|
3391
|
+
updateNumberedLists() {
|
|
3392
|
+
const value = this.textarea.value;
|
|
3393
|
+
const cursorPos = this.textarea.selectionStart;
|
|
3394
|
+
const newValue = MarkdownParser.renumberLists(value);
|
|
3395
|
+
if (newValue !== value) {
|
|
3396
|
+
let offset = 0;
|
|
3397
|
+
const oldLines = value.split("\n");
|
|
3398
|
+
const newLines = newValue.split("\n");
|
|
3399
|
+
let charCount = 0;
|
|
3400
|
+
for (let i = 0; i < oldLines.length && charCount < cursorPos; i++) {
|
|
3401
|
+
if (oldLines[i] !== newLines[i]) {
|
|
3402
|
+
const diff = newLines[i].length - oldLines[i].length;
|
|
3403
|
+
if (charCount + oldLines[i].length < cursorPos) {
|
|
3404
|
+
offset += diff;
|
|
3405
|
+
}
|
|
3406
|
+
}
|
|
3407
|
+
charCount += oldLines[i].length + 1;
|
|
3408
|
+
}
|
|
3409
|
+
this.textarea.value = newValue;
|
|
3410
|
+
const newCursorPos = cursorPos + offset;
|
|
3411
|
+
this.textarea.setSelectionRange(newCursorPos, newCursorPos);
|
|
3412
|
+
this.textarea.dispatchEvent(new Event("input", { bubbles: true }));
|
|
3413
|
+
}
|
|
3414
|
+
}
|
|
3164
3415
|
/**
|
|
3165
3416
|
* Handle scroll events
|
|
3166
3417
|
* @private
|