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 +36 -15
- package/dist/overtype.cjs +150 -39
- package/dist/overtype.cjs.map +2 -2
- package/dist/overtype.d.ts +169 -0
- package/dist/overtype.esm.js +150 -39
- package/dist/overtype.esm.js.map +2 -2
- package/dist/overtype.js +155 -39
- package/dist/overtype.js.map +2 -2
- package/dist/overtype.min.js +84 -68
- package/package.json +4 -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 +127 -68
- package/src/styles.js +16 -8
- package/src/toolbar.js +63 -2
package/src/overtype.d.ts
CHANGED
|
@@ -71,7 +71,16 @@ export interface Options {
|
|
|
71
71
|
// Features
|
|
72
72
|
showActiveLineRaw?: boolean;
|
|
73
73
|
showStats?: boolean;
|
|
74
|
-
toolbar?: boolean
|
|
74
|
+
toolbar?: boolean | {
|
|
75
|
+
buttons?: Array<{
|
|
76
|
+
name?: string;
|
|
77
|
+
icon?: string;
|
|
78
|
+
title?: string;
|
|
79
|
+
action?: string;
|
|
80
|
+
separator?: boolean;
|
|
81
|
+
}>;
|
|
82
|
+
};
|
|
83
|
+
smartLists?: boolean; // v1.2.3+ Smart list continuation
|
|
75
84
|
statsFormatter?: (stats: Stats) => string;
|
|
76
85
|
|
|
77
86
|
// Theme (deprecated in favor of global theme)
|
|
@@ -107,6 +116,10 @@ export interface OverTypeConstructor {
|
|
|
107
116
|
getTheme(name: string): Theme;
|
|
108
117
|
}
|
|
109
118
|
|
|
119
|
+
export interface RenderOptions {
|
|
120
|
+
cleanHTML?: boolean;
|
|
121
|
+
}
|
|
122
|
+
|
|
110
123
|
export interface OverTypeInstance {
|
|
111
124
|
// Public properties
|
|
112
125
|
container: HTMLElement;
|
|
@@ -135,6 +148,15 @@ export interface OverTypeInstance {
|
|
|
135
148
|
showStats(show: boolean): void;
|
|
136
149
|
setTheme(theme: string | Theme): void;
|
|
137
150
|
updatePreview(): void;
|
|
151
|
+
|
|
152
|
+
// HTML output methods
|
|
153
|
+
getRenderedHTML(options?: RenderOptions): string;
|
|
154
|
+
getCleanHTML(): string;
|
|
155
|
+
getPreviewHTML(): string;
|
|
156
|
+
|
|
157
|
+
// View mode methods
|
|
158
|
+
showPlainTextarea(show: boolean): void;
|
|
159
|
+
showPreviewMode(show: boolean): void;
|
|
138
160
|
}
|
|
139
161
|
|
|
140
162
|
// Declare the constructor as a constant with proper typing
|
package/src/overtype.js
CHANGED
|
@@ -104,7 +104,8 @@ class OverType {
|
|
|
104
104
|
|
|
105
105
|
// Setup toolbar if enabled
|
|
106
106
|
if (this.options.toolbar) {
|
|
107
|
-
this.toolbar
|
|
107
|
+
const toolbarButtons = typeof this.options.toolbar === 'object' ? this.options.toolbar.buttons : null;
|
|
108
|
+
this.toolbar = new Toolbar(this, toolbarButtons);
|
|
108
109
|
this.toolbar.create();
|
|
109
110
|
|
|
110
111
|
// Update toolbar states on selection change
|
|
@@ -771,16 +772,21 @@ class OverType {
|
|
|
771
772
|
|
|
772
773
|
/**
|
|
773
774
|
* Get the rendered HTML of the current content
|
|
774
|
-
* @param {
|
|
775
|
+
* @param {Object} options - Rendering options
|
|
776
|
+
* @param {boolean} options.cleanHTML - If true, removes syntax markers and OverType-specific classes
|
|
775
777
|
* @returns {string} Rendered HTML
|
|
776
778
|
*/
|
|
777
|
-
getRenderedHTML(
|
|
779
|
+
getRenderedHTML(options = {}) {
|
|
778
780
|
const markdown = this.getValue();
|
|
779
781
|
let html = MarkdownParser.parse(markdown);
|
|
780
782
|
|
|
781
|
-
if (
|
|
782
|
-
//
|
|
783
|
-
html =
|
|
783
|
+
if (options.cleanHTML) {
|
|
784
|
+
// Remove all syntax marker spans for clean HTML export
|
|
785
|
+
html = html.replace(/<span class="syntax-marker[^"]*">.*?<\/span>/g, '');
|
|
786
|
+
// Remove OverType-specific classes
|
|
787
|
+
html = html.replace(/\sclass="(bullet-list|ordered-list|code-fence|hr-marker|blockquote|url-part)"/g, '');
|
|
788
|
+
// Clean up empty class attributes
|
|
789
|
+
html = html.replace(/\sclass=""/g, '');
|
|
784
790
|
}
|
|
785
791
|
|
|
786
792
|
return html;
|
|
@@ -788,11 +794,21 @@ class OverType {
|
|
|
788
794
|
|
|
789
795
|
/**
|
|
790
796
|
* Get the current preview element's HTML
|
|
797
|
+
* This includes all syntax markers and OverType styling
|
|
791
798
|
* @returns {string} Current preview HTML (as displayed)
|
|
792
799
|
*/
|
|
793
800
|
getPreviewHTML() {
|
|
794
801
|
return this.preview.innerHTML;
|
|
795
802
|
}
|
|
803
|
+
|
|
804
|
+
/**
|
|
805
|
+
* Get clean HTML without any OverType-specific markup
|
|
806
|
+
* Useful for exporting to other formats or storage
|
|
807
|
+
* @returns {string} Clean HTML suitable for export
|
|
808
|
+
*/
|
|
809
|
+
getCleanHTML() {
|
|
810
|
+
return this.getRenderedHTML({ cleanHTML: true });
|
|
811
|
+
}
|
|
796
812
|
|
|
797
813
|
/**
|
|
798
814
|
* Focus the editor
|
|
@@ -1209,12 +1225,6 @@ OverType.getTheme = getTheme;
|
|
|
1209
1225
|
// Set default theme
|
|
1210
1226
|
OverType.currentTheme = solar;
|
|
1211
1227
|
|
|
1212
|
-
// Only attach to global in browser environments (not Node.js)
|
|
1213
|
-
if (typeof window !== 'undefined' && typeof window.document !== 'undefined') {
|
|
1214
|
-
// Browser environment - attach to window
|
|
1215
|
-
window.OverType = OverType;
|
|
1216
|
-
}
|
|
1217
|
-
|
|
1218
1228
|
// Export for module systems
|
|
1219
1229
|
export default OverType;
|
|
1220
1230
|
export { OverType };
|
package/src/parser.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MarkdownParser - Parses markdown into HTML while preserving character alignment
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* Key principles:
|
|
5
5
|
* - Every character must occupy the exact same position as in the textarea
|
|
6
6
|
* - No font-size changes, no padding/margin on inline elements
|
|
@@ -9,14 +9,14 @@
|
|
|
9
9
|
export class MarkdownParser {
|
|
10
10
|
// Track link index for anchor naming
|
|
11
11
|
static linkIndex = 0;
|
|
12
|
-
|
|
12
|
+
|
|
13
13
|
/**
|
|
14
14
|
* Reset link index (call before parsing a new document)
|
|
15
15
|
*/
|
|
16
16
|
static resetLinkIndex() {
|
|
17
17
|
this.linkIndex = 0;
|
|
18
18
|
}
|
|
19
|
-
|
|
19
|
+
|
|
20
20
|
/**
|
|
21
21
|
* Escape HTML special characters
|
|
22
22
|
* @param {string} text - Raw text to escape
|
|
@@ -139,6 +139,20 @@ export class MarkdownParser {
|
|
|
139
139
|
return html;
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
+
/**
|
|
143
|
+
* Parse strikethrough text
|
|
144
|
+
* Supports both single (~) and double (~~) tildes, but rejects 3+ tildes
|
|
145
|
+
* @param {string} html - HTML with potential strikethrough markdown
|
|
146
|
+
* @returns {string} HTML with strikethrough styling
|
|
147
|
+
*/
|
|
148
|
+
static parseStrikethrough(html) {
|
|
149
|
+
// Double tilde strikethrough: ~~text~~ (but not if part of 3+ tildes)
|
|
150
|
+
html = html.replace(/(?<!~)~~(?!~)(.+?)(?<!~)~~(?!~)/g, '<del><span class="syntax-marker">~~</span>$1<span class="syntax-marker">~~</span></del>');
|
|
151
|
+
// Single tilde strikethrough: ~text~ (but not if part of 2+ tildes on either side)
|
|
152
|
+
html = html.replace(/(?<!~)~(?!~)(.+?)(?<!~)~(?!~)/g, '<del><span class="syntax-marker">~</span>$1<span class="syntax-marker">~</span></del>');
|
|
153
|
+
return html;
|
|
154
|
+
}
|
|
155
|
+
|
|
142
156
|
/**
|
|
143
157
|
* Parse inline code
|
|
144
158
|
* @param {string} html - HTML with potential code markdown
|
|
@@ -165,7 +179,7 @@ export class MarkdownParser {
|
|
|
165
179
|
// Trim whitespace and convert to lowercase for protocol check
|
|
166
180
|
const trimmed = url.trim();
|
|
167
181
|
const lower = trimmed.toLowerCase();
|
|
168
|
-
|
|
182
|
+
|
|
169
183
|
// Allow safe protocols
|
|
170
184
|
const safeProtocols = [
|
|
171
185
|
'http://',
|
|
@@ -174,22 +188,22 @@ export class MarkdownParser {
|
|
|
174
188
|
'ftp://',
|
|
175
189
|
'ftps://'
|
|
176
190
|
];
|
|
177
|
-
|
|
191
|
+
|
|
178
192
|
// Check if URL starts with a safe protocol
|
|
179
193
|
const hasSafeProtocol = safeProtocols.some(protocol => lower.startsWith(protocol));
|
|
180
|
-
|
|
194
|
+
|
|
181
195
|
// Allow relative URLs (starting with / or # or no protocol)
|
|
182
|
-
const isRelative = trimmed.startsWith('/') ||
|
|
183
|
-
trimmed.startsWith('#') ||
|
|
196
|
+
const isRelative = trimmed.startsWith('/') ||
|
|
197
|
+
trimmed.startsWith('#') ||
|
|
184
198
|
trimmed.startsWith('?') ||
|
|
185
199
|
trimmed.startsWith('.') ||
|
|
186
200
|
(!trimmed.includes(':') && !trimmed.includes('//'));
|
|
187
|
-
|
|
201
|
+
|
|
188
202
|
// If safe protocol or relative URL, return as-is
|
|
189
203
|
if (hasSafeProtocol || isRelative) {
|
|
190
204
|
return url;
|
|
191
205
|
}
|
|
192
|
-
|
|
206
|
+
|
|
193
207
|
// Block dangerous protocols (javascript:, data:, vbscript:, etc.)
|
|
194
208
|
return '#';
|
|
195
209
|
}
|
|
@@ -246,6 +260,7 @@ export class MarkdownParser {
|
|
|
246
260
|
});
|
|
247
261
|
|
|
248
262
|
// Process other inline elements on text with placeholders
|
|
263
|
+
html = this.parseStrikethrough(html);
|
|
249
264
|
html = this.parseBold(html);
|
|
250
265
|
html = this.parseItalic(html);
|
|
251
266
|
|
|
@@ -264,33 +279,33 @@ export class MarkdownParser {
|
|
|
264
279
|
*/
|
|
265
280
|
static parseLine(line) {
|
|
266
281
|
let html = this.escapeHtml(line);
|
|
267
|
-
|
|
282
|
+
|
|
268
283
|
// Preserve indentation
|
|
269
284
|
html = this.preserveIndentation(html, line);
|
|
270
|
-
|
|
285
|
+
|
|
271
286
|
// Check for block elements first
|
|
272
287
|
const horizontalRule = this.parseHorizontalRule(html);
|
|
273
288
|
if (horizontalRule) return horizontalRule;
|
|
274
|
-
|
|
289
|
+
|
|
275
290
|
const codeBlock = this.parseCodeBlock(html);
|
|
276
291
|
if (codeBlock) return codeBlock;
|
|
277
|
-
|
|
292
|
+
|
|
278
293
|
// Parse block elements
|
|
279
294
|
html = this.parseHeader(html);
|
|
280
295
|
html = this.parseBlockquote(html);
|
|
281
296
|
html = this.parseBulletList(html);
|
|
282
297
|
html = this.parseNumberedList(html);
|
|
283
|
-
|
|
298
|
+
|
|
284
299
|
// Parse inline elements
|
|
285
300
|
html = this.parseInlineElements(html);
|
|
286
|
-
|
|
301
|
+
|
|
287
302
|
// Wrap in div to maintain line structure
|
|
288
303
|
if (html.trim() === '') {
|
|
289
304
|
// Intentionally use for empty lines to maintain vertical spacing
|
|
290
305
|
// This causes a 0->1 character count difference but preserves visual alignment
|
|
291
306
|
return '<div> </div>';
|
|
292
307
|
}
|
|
293
|
-
|
|
308
|
+
|
|
294
309
|
return `<div>${html}</div>`;
|
|
295
310
|
}
|
|
296
311
|
|
|
@@ -304,17 +319,17 @@ export class MarkdownParser {
|
|
|
304
319
|
static parse(text, activeLine = -1, showActiveLineRaw = false) {
|
|
305
320
|
// Reset link counter for each parse
|
|
306
321
|
this.resetLinkIndex();
|
|
307
|
-
|
|
322
|
+
|
|
308
323
|
const lines = text.split('\n');
|
|
309
324
|
let inCodeBlock = false;
|
|
310
|
-
|
|
325
|
+
|
|
311
326
|
const parsedLines = lines.map((line, index) => {
|
|
312
327
|
// Show raw markdown on active line if requested
|
|
313
328
|
if (showActiveLineRaw && index === activeLine) {
|
|
314
329
|
const content = this.escapeHtml(line) || ' ';
|
|
315
330
|
return `<div class="raw-line">${content}</div>`;
|
|
316
331
|
}
|
|
317
|
-
|
|
332
|
+
|
|
318
333
|
// Check if this line is a code fence
|
|
319
334
|
const codeFenceRegex = /^```[^`]*$/;
|
|
320
335
|
if (codeFenceRegex.test(line)) {
|
|
@@ -322,21 +337,21 @@ export class MarkdownParser {
|
|
|
322
337
|
// Parse fence markers normally to get styled output
|
|
323
338
|
return this.parseLine(line);
|
|
324
339
|
}
|
|
325
|
-
|
|
340
|
+
|
|
326
341
|
// If we're inside a code block, don't parse as markdown
|
|
327
342
|
if (inCodeBlock) {
|
|
328
343
|
const escaped = this.escapeHtml(line);
|
|
329
344
|
const indented = this.preserveIndentation(escaped, line);
|
|
330
345
|
return `<div>${indented || ' '}</div>`;
|
|
331
346
|
}
|
|
332
|
-
|
|
347
|
+
|
|
333
348
|
// Otherwise, parse the markdown normally
|
|
334
349
|
return this.parseLine(line);
|
|
335
350
|
});
|
|
336
|
-
|
|
351
|
+
|
|
337
352
|
// Join without newlines to prevent extra spacing
|
|
338
353
|
const html = parsedLines.join('');
|
|
339
|
-
|
|
354
|
+
|
|
340
355
|
// Apply post-processing for list consolidation
|
|
341
356
|
return this.postProcessHTML(html);
|
|
342
357
|
}
|
|
@@ -352,25 +367,25 @@ export class MarkdownParser {
|
|
|
352
367
|
// In Node.js environment - do manual post-processing
|
|
353
368
|
return this.postProcessHTMLManual(html);
|
|
354
369
|
}
|
|
355
|
-
|
|
370
|
+
|
|
356
371
|
// Parse HTML string into DOM
|
|
357
372
|
const container = document.createElement('div');
|
|
358
373
|
container.innerHTML = html;
|
|
359
|
-
|
|
374
|
+
|
|
360
375
|
let currentList = null;
|
|
361
376
|
let listType = null;
|
|
362
377
|
let currentCodeBlock = null;
|
|
363
378
|
let inCodeBlock = false;
|
|
364
|
-
|
|
379
|
+
|
|
365
380
|
// Process all direct children - need to be careful with live NodeList
|
|
366
381
|
const children = Array.from(container.children);
|
|
367
|
-
|
|
382
|
+
|
|
368
383
|
for (let i = 0; i < children.length; i++) {
|
|
369
384
|
const child = children[i];
|
|
370
|
-
|
|
385
|
+
|
|
371
386
|
// Skip if child was already processed/removed
|
|
372
387
|
if (!child.parentNode) continue;
|
|
373
|
-
|
|
388
|
+
|
|
374
389
|
// Check for code fence start/end
|
|
375
390
|
const codeFence = child.querySelector('.code-fence');
|
|
376
391
|
if (codeFence) {
|
|
@@ -379,22 +394,22 @@ export class MarkdownParser {
|
|
|
379
394
|
if (!inCodeBlock) {
|
|
380
395
|
// Start of code block - keep fence visible, then add pre/code
|
|
381
396
|
inCodeBlock = true;
|
|
382
|
-
|
|
397
|
+
|
|
383
398
|
// Create the code block that will follow the fence
|
|
384
399
|
currentCodeBlock = document.createElement('pre');
|
|
385
400
|
const codeElement = document.createElement('code');
|
|
386
401
|
currentCodeBlock.appendChild(codeElement);
|
|
387
402
|
currentCodeBlock.className = 'code-block';
|
|
388
|
-
|
|
403
|
+
|
|
389
404
|
// Extract language if present
|
|
390
405
|
const lang = fenceText.slice(3).trim();
|
|
391
406
|
if (lang) {
|
|
392
407
|
codeElement.className = `language-${lang}`;
|
|
393
408
|
}
|
|
394
|
-
|
|
409
|
+
|
|
395
410
|
// Insert code block after the fence div (don't remove the fence)
|
|
396
411
|
container.insertBefore(currentCodeBlock, child.nextSibling);
|
|
397
|
-
|
|
412
|
+
|
|
398
413
|
// Store reference to the code element for adding content
|
|
399
414
|
currentCodeBlock._codeElement = codeElement;
|
|
400
415
|
continue;
|
|
@@ -406,7 +421,7 @@ export class MarkdownParser {
|
|
|
406
421
|
}
|
|
407
422
|
}
|
|
408
423
|
}
|
|
409
|
-
|
|
424
|
+
|
|
410
425
|
// Check if we're in a code block - any div that's not a code fence
|
|
411
426
|
if (inCodeBlock && currentCodeBlock && child.tagName === 'DIV' && !child.querySelector('.code-fence')) {
|
|
412
427
|
const codeElement = currentCodeBlock._codeElement || currentCodeBlock.querySelector('code');
|
|
@@ -422,36 +437,52 @@ export class MarkdownParser {
|
|
|
422
437
|
child.remove();
|
|
423
438
|
continue;
|
|
424
439
|
}
|
|
425
|
-
|
|
440
|
+
|
|
426
441
|
// Check if this div contains a list item
|
|
427
442
|
let listItem = null;
|
|
428
443
|
if (child.tagName === 'DIV') {
|
|
429
444
|
// Look for li inside the div
|
|
430
445
|
listItem = child.querySelector('li');
|
|
431
446
|
}
|
|
432
|
-
|
|
447
|
+
|
|
433
448
|
if (listItem) {
|
|
434
449
|
const isBullet = listItem.classList.contains('bullet-list');
|
|
435
450
|
const isOrdered = listItem.classList.contains('ordered-list');
|
|
436
|
-
|
|
451
|
+
|
|
437
452
|
if (!isBullet && !isOrdered) {
|
|
438
453
|
currentList = null;
|
|
439
454
|
listType = null;
|
|
440
455
|
continue;
|
|
441
456
|
}
|
|
442
|
-
|
|
457
|
+
|
|
443
458
|
const newType = isBullet ? 'ul' : 'ol';
|
|
444
|
-
|
|
459
|
+
|
|
445
460
|
// Start new list or continue current
|
|
446
461
|
if (!currentList || listType !== newType) {
|
|
447
462
|
currentList = document.createElement(newType);
|
|
448
463
|
container.insertBefore(currentList, child);
|
|
449
464
|
listType = newType;
|
|
450
465
|
}
|
|
451
|
-
|
|
466
|
+
|
|
467
|
+
// Extract and preserve indentation from the div before moving the list item
|
|
468
|
+
const indentationNodes = [];
|
|
469
|
+
for (const node of child.childNodes) {
|
|
470
|
+
if (node.nodeType === 3 && node.textContent.match(/^\u00A0+$/)) {
|
|
471
|
+
// This is a text node containing only non-breaking spaces (indentation)
|
|
472
|
+
indentationNodes.push(node.cloneNode(true));
|
|
473
|
+
} else if (node === listItem) {
|
|
474
|
+
break; // Stop when we reach the list item
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Add indentation to the list item
|
|
479
|
+
indentationNodes.forEach(node => {
|
|
480
|
+
listItem.insertBefore(node, listItem.firstChild);
|
|
481
|
+
});
|
|
482
|
+
|
|
452
483
|
// Move the list item to the current list
|
|
453
484
|
currentList.appendChild(listItem);
|
|
454
|
-
|
|
485
|
+
|
|
455
486
|
// Remove the now-empty div wrapper
|
|
456
487
|
child.remove();
|
|
457
488
|
} else {
|
|
@@ -460,7 +491,7 @@ export class MarkdownParser {
|
|
|
460
491
|
listType = null;
|
|
461
492
|
}
|
|
462
493
|
}
|
|
463
|
-
|
|
494
|
+
|
|
464
495
|
return container.innerHTML;
|
|
465
496
|
}
|
|
466
497
|
|
|
@@ -471,25 +502,53 @@ export class MarkdownParser {
|
|
|
471
502
|
*/
|
|
472
503
|
static postProcessHTMLManual(html) {
|
|
473
504
|
let processed = html;
|
|
474
|
-
|
|
505
|
+
|
|
475
506
|
// Process unordered lists
|
|
476
507
|
processed = processed.replace(/((?:<div>(?: )*<li class="bullet-list">.*?<\/li><\/div>\s*)+)/gs, (match) => {
|
|
477
|
-
const
|
|
478
|
-
if (
|
|
508
|
+
const divs = match.match(/<div>(?: )*<li class="bullet-list">.*?<\/li><\/div>/gs) || [];
|
|
509
|
+
if (divs.length > 0) {
|
|
510
|
+
const items = divs.map(div => {
|
|
511
|
+
// Extract indentation and list item
|
|
512
|
+
const indentMatch = div.match(/<div>((?: )*)<li/);
|
|
513
|
+
const listItemMatch = div.match(/<li class="bullet-list">.*?<\/li>/);
|
|
514
|
+
|
|
515
|
+
if (indentMatch && listItemMatch) {
|
|
516
|
+
const indentation = indentMatch[1];
|
|
517
|
+
const listItem = listItemMatch[0];
|
|
518
|
+
// Insert indentation at the start of the list item content
|
|
519
|
+
return listItem.replace(/<li class="bullet-list">/, `<li class="bullet-list">${indentation}`);
|
|
520
|
+
}
|
|
521
|
+
return listItemMatch ? listItemMatch[0] : '';
|
|
522
|
+
}).filter(Boolean);
|
|
523
|
+
|
|
479
524
|
return '<ul>' + items.join('') + '</ul>';
|
|
480
525
|
}
|
|
481
526
|
return match;
|
|
482
527
|
});
|
|
483
|
-
|
|
528
|
+
|
|
484
529
|
// Process ordered lists
|
|
485
530
|
processed = processed.replace(/((?:<div>(?: )*<li class="ordered-list">.*?<\/li><\/div>\s*)+)/gs, (match) => {
|
|
486
|
-
const
|
|
487
|
-
if (
|
|
531
|
+
const divs = match.match(/<div>(?: )*<li class="ordered-list">.*?<\/li><\/div>/gs) || [];
|
|
532
|
+
if (divs.length > 0) {
|
|
533
|
+
const items = divs.map(div => {
|
|
534
|
+
// Extract indentation and list item
|
|
535
|
+
const indentMatch = div.match(/<div>((?: )*)<li/);
|
|
536
|
+
const listItemMatch = div.match(/<li class="ordered-list">.*?<\/li>/);
|
|
537
|
+
|
|
538
|
+
if (indentMatch && listItemMatch) {
|
|
539
|
+
const indentation = indentMatch[1];
|
|
540
|
+
const listItem = listItemMatch[0];
|
|
541
|
+
// Insert indentation at the start of the list item content
|
|
542
|
+
return listItem.replace(/<li class="ordered-list">/, `<li class="ordered-list">${indentation}`);
|
|
543
|
+
}
|
|
544
|
+
return listItemMatch ? listItemMatch[0] : '';
|
|
545
|
+
}).filter(Boolean);
|
|
546
|
+
|
|
488
547
|
return '<ol>' + items.join('') + '</ol>';
|
|
489
548
|
}
|
|
490
549
|
return match;
|
|
491
550
|
});
|
|
492
|
-
|
|
551
|
+
|
|
493
552
|
// Process code blocks - KEEP the fence markers for alignment AND use semantic pre/code
|
|
494
553
|
const codeBlockRegex = /<div><span class="code-fence">(```[^<]*)<\/span><\/div>(.*?)<div><span class="code-fence">(```)<\/span><\/div>/gs;
|
|
495
554
|
processed = processed.replace(codeBlockRegex, (match, openFence, content, closeFence) => {
|
|
@@ -501,20 +560,20 @@ export class MarkdownParser {
|
|
|
501
560
|
.replace(/ /g, ' ');
|
|
502
561
|
return text;
|
|
503
562
|
}).join('\n');
|
|
504
|
-
|
|
563
|
+
|
|
505
564
|
// Extract language from the opening fence
|
|
506
565
|
const lang = openFence.slice(3).trim();
|
|
507
566
|
const langClass = lang ? ` class="language-${lang}"` : '';
|
|
508
|
-
|
|
567
|
+
|
|
509
568
|
// Keep fence markers visible as separate divs, with pre/code block between them
|
|
510
569
|
let result = `<div><span class="code-fence">${openFence}</span></div>`;
|
|
511
570
|
// Content is already escaped, don't double-escape
|
|
512
571
|
result += `<pre class="code-block"><code${langClass}>${codeContent}</code></pre>`;
|
|
513
572
|
result += `<div><span class="code-fence">${closeFence}</span></div>`;
|
|
514
|
-
|
|
573
|
+
|
|
515
574
|
return result;
|
|
516
575
|
});
|
|
517
|
-
|
|
576
|
+
|
|
518
577
|
return processed;
|
|
519
578
|
}
|
|
520
579
|
|
|
@@ -539,7 +598,7 @@ export class MarkdownParser {
|
|
|
539
598
|
let currentPos = 0;
|
|
540
599
|
let lineIndex = 0;
|
|
541
600
|
let lineStart = 0;
|
|
542
|
-
|
|
601
|
+
|
|
543
602
|
for (let i = 0; i < lines.length; i++) {
|
|
544
603
|
const lineLength = lines[i].length;
|
|
545
604
|
if (currentPos + lineLength >= cursorPosition) {
|
|
@@ -549,10 +608,10 @@ export class MarkdownParser {
|
|
|
549
608
|
}
|
|
550
609
|
currentPos += lineLength + 1; // +1 for newline
|
|
551
610
|
}
|
|
552
|
-
|
|
611
|
+
|
|
553
612
|
const currentLine = lines[lineIndex];
|
|
554
613
|
const lineEnd = lineStart + currentLine.length;
|
|
555
|
-
|
|
614
|
+
|
|
556
615
|
// Check for checkbox first (most specific)
|
|
557
616
|
const checkboxMatch = currentLine.match(this.LIST_PATTERNS.checkbox);
|
|
558
617
|
if (checkboxMatch) {
|
|
@@ -568,7 +627,7 @@ export class MarkdownParser {
|
|
|
568
627
|
markerEndPos: lineStart + checkboxMatch[1].length + checkboxMatch[2].length + 5 // indent + "- [ ] "
|
|
569
628
|
};
|
|
570
629
|
}
|
|
571
|
-
|
|
630
|
+
|
|
572
631
|
// Check for bullet list
|
|
573
632
|
const bulletMatch = currentLine.match(this.LIST_PATTERNS.bullet);
|
|
574
633
|
if (bulletMatch) {
|
|
@@ -583,7 +642,7 @@ export class MarkdownParser {
|
|
|
583
642
|
markerEndPos: lineStart + bulletMatch[1].length + bulletMatch[2].length + 1 // indent + marker + space
|
|
584
643
|
};
|
|
585
644
|
}
|
|
586
|
-
|
|
645
|
+
|
|
587
646
|
// Check for numbered list
|
|
588
647
|
const numberedMatch = currentLine.match(this.LIST_PATTERNS.numbered);
|
|
589
648
|
if (numberedMatch) {
|
|
@@ -598,7 +657,7 @@ export class MarkdownParser {
|
|
|
598
657
|
markerEndPos: lineStart + numberedMatch[1].length + numberedMatch[2].length + 2 // indent + number + ". "
|
|
599
658
|
};
|
|
600
659
|
}
|
|
601
|
-
|
|
660
|
+
|
|
602
661
|
// Not in a list
|
|
603
662
|
return {
|
|
604
663
|
inList: false,
|
|
@@ -639,31 +698,31 @@ export class MarkdownParser {
|
|
|
639
698
|
const lines = text.split('\n');
|
|
640
699
|
const numbersByIndent = new Map();
|
|
641
700
|
let inList = false;
|
|
642
|
-
|
|
701
|
+
|
|
643
702
|
const result = lines.map(line => {
|
|
644
703
|
const match = line.match(this.LIST_PATTERNS.numbered);
|
|
645
|
-
|
|
704
|
+
|
|
646
705
|
if (match) {
|
|
647
706
|
const indent = match[1];
|
|
648
707
|
const indentLevel = indent.length;
|
|
649
708
|
const content = match[3];
|
|
650
|
-
|
|
709
|
+
|
|
651
710
|
// If we weren't in a list or indent changed, reset lower levels
|
|
652
711
|
if (!inList) {
|
|
653
712
|
numbersByIndent.clear();
|
|
654
713
|
}
|
|
655
|
-
|
|
714
|
+
|
|
656
715
|
// Get the next number for this indent level
|
|
657
716
|
const currentNumber = (numbersByIndent.get(indentLevel) || 0) + 1;
|
|
658
717
|
numbersByIndent.set(indentLevel, currentNumber);
|
|
659
|
-
|
|
718
|
+
|
|
660
719
|
// Clear deeper indent levels
|
|
661
720
|
for (const [level] of numbersByIndent) {
|
|
662
721
|
if (level > indentLevel) {
|
|
663
722
|
numbersByIndent.delete(level);
|
|
664
723
|
}
|
|
665
724
|
}
|
|
666
|
-
|
|
725
|
+
|
|
667
726
|
inList = true;
|
|
668
727
|
return `${indent}${currentNumber}. ${content}`;
|
|
669
728
|
} else {
|
|
@@ -676,7 +735,7 @@ export class MarkdownParser {
|
|
|
676
735
|
return line;
|
|
677
736
|
}
|
|
678
737
|
});
|
|
679
|
-
|
|
738
|
+
|
|
680
739
|
return result.join('\n');
|
|
681
740
|
}
|
|
682
|
-
}
|
|
741
|
+
}
|
package/src/styles.js
CHANGED
|
@@ -325,6 +325,14 @@ export function generateStyles(options = {}) {
|
|
|
325
325
|
font-style: italic !important;
|
|
326
326
|
}
|
|
327
327
|
|
|
328
|
+
/* Strikethrough text */
|
|
329
|
+
.overtype-wrapper .overtype-preview del {
|
|
330
|
+
color: var(--del, #ee964b) !important;
|
|
331
|
+
text-decoration: line-through !important;
|
|
332
|
+
text-decoration-color: var(--del, #ee964b) !important;
|
|
333
|
+
text-decoration-thickness: 1px !important;
|
|
334
|
+
}
|
|
335
|
+
|
|
328
336
|
/* Inline code */
|
|
329
337
|
.overtype-wrapper .overtype-preview code {
|
|
330
338
|
background: var(--code-bg, rgba(244, 211, 94, 0.4)) !important;
|
|
@@ -468,10 +476,10 @@ export function generateStyles(options = {}) {
|
|
|
468
476
|
height: 8px !important;
|
|
469
477
|
background: #4caf50 !important;
|
|
470
478
|
border-radius: 50% !important;
|
|
471
|
-
animation: pulse 2s infinite !important;
|
|
479
|
+
animation: overtype-pulse 2s infinite !important;
|
|
472
480
|
}
|
|
473
481
|
|
|
474
|
-
@keyframes pulse {
|
|
482
|
+
@keyframes overtype-pulse {
|
|
475
483
|
0%, 100% { opacity: 1; transform: scale(1); }
|
|
476
484
|
50% { opacity: 0.6; transform: scale(1.2); }
|
|
477
485
|
}
|
|
@@ -479,19 +487,19 @@ export function generateStyles(options = {}) {
|
|
|
479
487
|
|
|
480
488
|
/* Toolbar Styles */
|
|
481
489
|
.overtype-toolbar {
|
|
482
|
-
display: flex;
|
|
483
|
-
align-items: center;
|
|
484
|
-
gap: 4px;
|
|
490
|
+
display: flex !important;
|
|
491
|
+
align-items: center !important;
|
|
492
|
+
gap: 4px !important;
|
|
485
493
|
padding: 8px !important; /* Override reset */
|
|
486
494
|
background: var(--toolbar-bg, var(--bg-primary, #f8f9fa)) !important; /* Override reset */
|
|
487
495
|
overflow-x: auto !important; /* Allow horizontal scrolling */
|
|
488
496
|
overflow-y: hidden !important; /* Hide vertical overflow */
|
|
489
|
-
-webkit-overflow-scrolling: touch;
|
|
490
|
-
flex-shrink: 0;
|
|
497
|
+
-webkit-overflow-scrolling: touch !important;
|
|
498
|
+
flex-shrink: 0 !important;
|
|
491
499
|
height: auto !important;
|
|
492
500
|
grid-row: 1 !important; /* Always first row in grid */
|
|
493
501
|
position: relative !important; /* Override reset */
|
|
494
|
-
z-index: 100; /* Ensure toolbar is above wrapper */
|
|
502
|
+
z-index: 100 !important; /* Ensure toolbar is above wrapper */
|
|
495
503
|
scrollbar-width: thin; /* Thin scrollbar on Firefox */
|
|
496
504
|
}
|
|
497
505
|
|