overtype 2.1.0 → 2.2.0
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 +26 -18
- package/dist/overtype-webcomponent.esm.js +3672 -2122
- package/dist/overtype-webcomponent.esm.js.map +4 -4
- package/dist/overtype-webcomponent.js +3667 -2117
- package/dist/overtype-webcomponent.js.map +4 -4
- package/dist/overtype-webcomponent.min.js +106 -93
- package/dist/overtype.cjs +3644 -2121
- package/dist/overtype.cjs.map +4 -4
- package/dist/overtype.d.ts +16 -0
- package/dist/overtype.esm.js +3644 -2121
- package/dist/overtype.esm.js.map +4 -4
- package/dist/overtype.js +3621 -2098
- package/dist/overtype.js.map +4 -4
- package/dist/overtype.min.js +107 -94
- package/package.json +4 -4
- package/src/icons.js +6 -0
- package/src/link-tooltip.js +40 -71
- package/src/overtype-webcomponent.js +32 -3
- package/src/overtype.d.ts +16 -0
- package/src/overtype.js +403 -38
- package/src/parser.js +9 -3
- package/src/shortcuts.js +11 -76
- package/src/styles.js +36 -28
- package/src/themes.js +14 -0
- package/src/toolbar-buttons.js +48 -12
- package/src/toolbar.js +39 -48
package/src/overtype.js
CHANGED
|
@@ -7,10 +7,69 @@
|
|
|
7
7
|
import { MarkdownParser } from './parser.js';
|
|
8
8
|
import { ShortcutsManager } from './shortcuts.js';
|
|
9
9
|
import { generateStyles } from './styles.js';
|
|
10
|
-
import { getTheme, mergeTheme, solar, themeToCSSVars } from './themes.js';
|
|
10
|
+
import { getTheme, mergeTheme, solar, themeToCSSVars, resolveAutoTheme } from './themes.js';
|
|
11
11
|
import { Toolbar } from './toolbar.js';
|
|
12
12
|
import { LinkTooltip } from './link-tooltip.js';
|
|
13
|
-
import { defaultToolbarButtons } from './toolbar-buttons.js';
|
|
13
|
+
import { defaultToolbarButtons, toolbarButtons as builtinToolbarButtons } from './toolbar-buttons.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Build action map from toolbar button configurations
|
|
17
|
+
* @param {Array} buttons - Array of button config objects
|
|
18
|
+
* @returns {Object} Map of actionId -> action function
|
|
19
|
+
*/
|
|
20
|
+
function buildActionsMap(buttons) {
|
|
21
|
+
const map = {};
|
|
22
|
+
(buttons || []).forEach((btn) => {
|
|
23
|
+
if (!btn || btn.name === 'separator') return;
|
|
24
|
+
const id = btn.actionId || btn.name;
|
|
25
|
+
if (btn.action) {
|
|
26
|
+
map[id] = btn.action;
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
return map;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Normalize toolbar buttons for comparison
|
|
34
|
+
* @param {Array|null} buttons
|
|
35
|
+
* @returns {Array|null}
|
|
36
|
+
*/
|
|
37
|
+
function normalizeButtons(buttons) {
|
|
38
|
+
const list = buttons || defaultToolbarButtons;
|
|
39
|
+
if (!Array.isArray(list)) return null;
|
|
40
|
+
return list.map((btn) => ({
|
|
41
|
+
name: btn?.name || null,
|
|
42
|
+
actionId: btn?.actionId || btn?.name || null,
|
|
43
|
+
icon: btn?.icon || null,
|
|
44
|
+
title: btn?.title || null
|
|
45
|
+
}));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Determine if toolbar button configuration changed
|
|
50
|
+
* @param {Array|null} prevButtons
|
|
51
|
+
* @param {Array|null} nextButtons
|
|
52
|
+
* @returns {boolean}
|
|
53
|
+
*/
|
|
54
|
+
function toolbarButtonsChanged(prevButtons, nextButtons) {
|
|
55
|
+
const prev = normalizeButtons(prevButtons);
|
|
56
|
+
const next = normalizeButtons(nextButtons);
|
|
57
|
+
|
|
58
|
+
if (prev === null || next === null) return prev !== next;
|
|
59
|
+
if (prev.length !== next.length) return true;
|
|
60
|
+
|
|
61
|
+
for (let i = 0; i < prev.length; i++) {
|
|
62
|
+
const a = prev[i];
|
|
63
|
+
const b = next[i];
|
|
64
|
+
if (a.name !== b.name ||
|
|
65
|
+
a.actionId !== b.actionId ||
|
|
66
|
+
a.icon !== b.icon ||
|
|
67
|
+
a.title !== b.title) {
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
14
73
|
|
|
15
74
|
/**
|
|
16
75
|
* OverType Editor Class
|
|
@@ -21,6 +80,11 @@ class OverType {
|
|
|
21
80
|
static stylesInjected = false;
|
|
22
81
|
static globalListenersInitialized = false;
|
|
23
82
|
static instanceCount = 0;
|
|
83
|
+
static _autoMediaQuery = null;
|
|
84
|
+
static _autoMediaListener = null;
|
|
85
|
+
static _autoInstances = new Set();
|
|
86
|
+
static _globalAutoTheme = false;
|
|
87
|
+
static _globalAutoCustomColors = null;
|
|
24
88
|
|
|
25
89
|
/**
|
|
26
90
|
* Constructor - Always returns an array of instances
|
|
@@ -97,9 +161,16 @@ class OverType {
|
|
|
97
161
|
this._buildFromScratch();
|
|
98
162
|
}
|
|
99
163
|
|
|
164
|
+
if (this.instanceTheme === 'auto') {
|
|
165
|
+
this.setTheme('auto');
|
|
166
|
+
}
|
|
167
|
+
|
|
100
168
|
// Setup shortcuts manager
|
|
101
169
|
this.shortcuts = new ShortcutsManager(this);
|
|
102
170
|
|
|
171
|
+
// Build action map from toolbar buttons (works whether or not toolbar UI is shown)
|
|
172
|
+
this._rebuildActionsMap();
|
|
173
|
+
|
|
103
174
|
// Setup link tooltip
|
|
104
175
|
this.linkTooltip = new LinkTooltip(this);
|
|
105
176
|
|
|
@@ -164,7 +235,8 @@ class OverType {
|
|
|
164
235
|
toolbarButtons: null, // Defaults to defaultToolbarButtons if toolbar: true
|
|
165
236
|
statsFormatter: null,
|
|
166
237
|
smartLists: true, // Enable smart list continuation
|
|
167
|
-
codeHighlighter: null // Per-instance code highlighter
|
|
238
|
+
codeHighlighter: null, // Per-instance code highlighter
|
|
239
|
+
spellcheck: false // Browser spellcheck (disabled by default)
|
|
168
240
|
};
|
|
169
241
|
|
|
170
242
|
// Remove theme and colors from options - these are now global
|
|
@@ -352,9 +424,16 @@ class OverType {
|
|
|
352
424
|
this.preview.className = 'overtype-preview';
|
|
353
425
|
this.preview.setAttribute('aria-hidden', 'true');
|
|
354
426
|
|
|
427
|
+
// Create placeholder shim
|
|
428
|
+
this.placeholderEl = document.createElement('div');
|
|
429
|
+
this.placeholderEl.className = 'overtype-placeholder';
|
|
430
|
+
this.placeholderEl.setAttribute('aria-hidden', 'true');
|
|
431
|
+
this.placeholderEl.textContent = this.options.placeholder;
|
|
432
|
+
|
|
355
433
|
// Assemble DOM
|
|
356
434
|
this.wrapper.appendChild(this.textarea);
|
|
357
435
|
this.wrapper.appendChild(this.preview);
|
|
436
|
+
this.wrapper.appendChild(this.placeholderEl);
|
|
358
437
|
|
|
359
438
|
// No need to prevent link clicks - pointer-events handles this
|
|
360
439
|
|
|
@@ -389,7 +468,7 @@ class OverType {
|
|
|
389
468
|
this.textarea.setAttribute('autocomplete', 'off');
|
|
390
469
|
this.textarea.setAttribute('autocorrect', 'off');
|
|
391
470
|
this.textarea.setAttribute('autocapitalize', 'off');
|
|
392
|
-
this.textarea.setAttribute('spellcheck',
|
|
471
|
+
this.textarea.setAttribute('spellcheck', String(this.options.spellcheck));
|
|
393
472
|
this.textarea.setAttribute('data-gramm', 'false');
|
|
394
473
|
this.textarea.setAttribute('data-gramm_editor', 'false');
|
|
395
474
|
this.textarea.setAttribute('data-enable-grammarly', 'false');
|
|
@@ -400,12 +479,22 @@ class OverType {
|
|
|
400
479
|
* @private
|
|
401
480
|
*/
|
|
402
481
|
_createToolbar() {
|
|
403
|
-
|
|
404
|
-
|
|
482
|
+
let toolbarButtons = this.options.toolbarButtons || defaultToolbarButtons;
|
|
483
|
+
|
|
484
|
+
if (this.options.fileUpload?.enabled && !toolbarButtons.some(b => b?.name === 'upload')) {
|
|
485
|
+
const viewModeIdx = toolbarButtons.findIndex(b => b?.name === 'viewMode');
|
|
486
|
+
if (viewModeIdx !== -1) {
|
|
487
|
+
toolbarButtons = [...toolbarButtons];
|
|
488
|
+
toolbarButtons.splice(viewModeIdx, 0, builtinToolbarButtons.separator, builtinToolbarButtons.upload);
|
|
489
|
+
} else {
|
|
490
|
+
toolbarButtons = [...toolbarButtons, builtinToolbarButtons.separator, builtinToolbarButtons.upload];
|
|
491
|
+
}
|
|
492
|
+
}
|
|
405
493
|
|
|
406
494
|
this.toolbar = new Toolbar(this, { toolbarButtons });
|
|
407
495
|
this.toolbar.create();
|
|
408
496
|
|
|
497
|
+
|
|
409
498
|
// Store listener references for cleanup
|
|
410
499
|
this._toolbarSelectionListener = () => {
|
|
411
500
|
if (this.toolbar) {
|
|
@@ -438,6 +527,26 @@ class OverType {
|
|
|
438
527
|
}
|
|
439
528
|
}
|
|
440
529
|
|
|
530
|
+
/**
|
|
531
|
+
* Rebuild the action map from current toolbar button configuration
|
|
532
|
+
* Called during init and reinit to keep shortcuts in sync with toolbar buttons
|
|
533
|
+
* @private
|
|
534
|
+
*/
|
|
535
|
+
_rebuildActionsMap() {
|
|
536
|
+
// Always start with default actions (shortcuts always work regardless of toolbar config)
|
|
537
|
+
this.actionsById = buildActionsMap(defaultToolbarButtons);
|
|
538
|
+
|
|
539
|
+
// Overlay custom toolbar actions (can add/override, but never remove core actions)
|
|
540
|
+
if (this.options.toolbarButtons) {
|
|
541
|
+
Object.assign(this.actionsById, buildActionsMap(this.options.toolbarButtons));
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// Register upload action when file upload is enabled
|
|
545
|
+
if (this.options.fileUpload?.enabled) {
|
|
546
|
+
Object.assign(this.actionsById, buildActionsMap([builtinToolbarButtons.upload]));
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
441
550
|
/**
|
|
442
551
|
* Apply options to the editor
|
|
443
552
|
* @private
|
|
@@ -452,6 +561,8 @@ class OverType {
|
|
|
452
561
|
if (this.options.autoResize) {
|
|
453
562
|
if (!this.container.classList.contains('overtype-auto-resize')) {
|
|
454
563
|
this._setupAutoResize();
|
|
564
|
+
} else {
|
|
565
|
+
this._updateAutoHeight();
|
|
455
566
|
}
|
|
456
567
|
} else {
|
|
457
568
|
// Ensure auto-resize class is removed
|
|
@@ -469,10 +580,135 @@ class OverType {
|
|
|
469
580
|
this.toolbar = null;
|
|
470
581
|
}
|
|
471
582
|
|
|
583
|
+
// Update placeholder text
|
|
584
|
+
if (this.placeholderEl) {
|
|
585
|
+
this.placeholderEl.textContent = this.options.placeholder;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// Setup or remove file upload
|
|
589
|
+
if (this.options.fileUpload && !this.fileUploadInitialized) {
|
|
590
|
+
this._initFileUpload();
|
|
591
|
+
} else if (!this.options.fileUpload && this.fileUploadInitialized) {
|
|
592
|
+
this._destroyFileUpload();
|
|
593
|
+
}
|
|
594
|
+
|
|
472
595
|
// Update preview with initial content
|
|
473
596
|
this.updatePreview();
|
|
474
597
|
}
|
|
475
598
|
|
|
599
|
+
_initFileUpload() {
|
|
600
|
+
const options = this.options.fileUpload;
|
|
601
|
+
if (!options || !options.enabled) return;
|
|
602
|
+
|
|
603
|
+
options.maxSize = options.maxSize || 10 * 1024 * 1024;
|
|
604
|
+
options.mimeTypes = options.mimeTypes || [];
|
|
605
|
+
options.batch = options.batch || false;
|
|
606
|
+
if (!options.onInsertFile || typeof options.onInsertFile !== 'function') {
|
|
607
|
+
console.warn('OverType: fileUpload.onInsertFile callback is required for file uploads.');
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
this._fileUploadCounter = 0;
|
|
612
|
+
this._boundHandleFilePaste = this._handleFilePaste.bind(this);
|
|
613
|
+
this._boundHandleFileDrop = this._handleFileDrop.bind(this);
|
|
614
|
+
this._boundHandleDragOver = this._handleDragOver.bind(this);
|
|
615
|
+
|
|
616
|
+
this.textarea.addEventListener('paste', this._boundHandleFilePaste);
|
|
617
|
+
this.textarea.addEventListener('drop', this._boundHandleFileDrop);
|
|
618
|
+
this.textarea.addEventListener('dragover', this._boundHandleDragOver);
|
|
619
|
+
|
|
620
|
+
this.fileUploadInitialized = true;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
_handleFilePaste(e) {
|
|
624
|
+
if (!e?.clipboardData?.files?.length) return;
|
|
625
|
+
e.preventDefault();
|
|
626
|
+
this._handleDataTransfer(e.clipboardData);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
_handleFileDrop(e) {
|
|
630
|
+
if (!e?.dataTransfer?.files?.length) return;
|
|
631
|
+
e.preventDefault();
|
|
632
|
+
this._handleDataTransfer(e.dataTransfer);
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
_handleDataTransfer(dataTransfer) {
|
|
636
|
+
const files = [];
|
|
637
|
+
for (const file of dataTransfer.files) {
|
|
638
|
+
if (file.size > this.options.fileUpload.maxSize) continue;
|
|
639
|
+
if (this.options.fileUpload.mimeTypes.length > 0
|
|
640
|
+
&& !this.options.fileUpload.mimeTypes.includes(file.type)) continue;
|
|
641
|
+
|
|
642
|
+
const id = ++this._fileUploadCounter;
|
|
643
|
+
const prefix = file.type.startsWith('image/') ? '!' : '';
|
|
644
|
+
const placeholder = `${prefix}[Uploading ${file.name} (#${id})...]()`;
|
|
645
|
+
this.insertAtCursor(`${placeholder}\n`);
|
|
646
|
+
|
|
647
|
+
if (this.options.fileUpload.batch) {
|
|
648
|
+
files.push({ file, placeholder });
|
|
649
|
+
continue;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
this.options.fileUpload.onInsertFile(file).then((text) => {
|
|
653
|
+
this.textarea.value = this.textarea.value.replace(placeholder, text);
|
|
654
|
+
this.textarea.dispatchEvent(new Event('input', { bubbles: true }));
|
|
655
|
+
}, (error) => {
|
|
656
|
+
console.error('OverType: File upload failed', error);
|
|
657
|
+
this.textarea.value = this.textarea.value.replace(placeholder, '[Upload failed]()');
|
|
658
|
+
this.textarea.dispatchEvent(new Event('input', { bubbles: true }));
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
if (this.options.fileUpload.batch && files.length > 0) {
|
|
663
|
+
this.options.fileUpload.onInsertFile(files.map(f => f.file)).then((result) => {
|
|
664
|
+
const texts = Array.isArray(result) ? result : [result];
|
|
665
|
+
texts.forEach((text, index) => {
|
|
666
|
+
this.textarea.value = this.textarea.value.replace(files[index].placeholder, text);
|
|
667
|
+
});
|
|
668
|
+
this.textarea.dispatchEvent(new Event('input', { bubbles: true }));
|
|
669
|
+
}, (error) => {
|
|
670
|
+
console.error('OverType: File upload failed', error);
|
|
671
|
+
files.forEach(({ placeholder }) => {
|
|
672
|
+
this.textarea.value = this.textarea.value.replace(placeholder, '[Upload failed]()');
|
|
673
|
+
});
|
|
674
|
+
this.textarea.dispatchEvent(new Event('input', { bubbles: true }));
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
_handleDragOver(e) {
|
|
680
|
+
e.preventDefault();
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
_destroyFileUpload() {
|
|
684
|
+
this.textarea.removeEventListener('paste', this._boundHandleFilePaste);
|
|
685
|
+
this.textarea.removeEventListener('drop', this._boundHandleFileDrop);
|
|
686
|
+
this.textarea.removeEventListener('dragover', this._boundHandleDragOver);
|
|
687
|
+
this._boundHandleFilePaste = null;
|
|
688
|
+
this._boundHandleFileDrop = null;
|
|
689
|
+
this._boundHandleDragOver = null;
|
|
690
|
+
this.fileUploadInitialized = false;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
insertAtCursor(text) {
|
|
694
|
+
const start = this.textarea.selectionStart;
|
|
695
|
+
const end = this.textarea.selectionEnd;
|
|
696
|
+
|
|
697
|
+
let inserted = false;
|
|
698
|
+
try {
|
|
699
|
+
inserted = document.execCommand('insertText', false, text);
|
|
700
|
+
} catch (_) {}
|
|
701
|
+
|
|
702
|
+
if (!inserted) {
|
|
703
|
+
const before = this.textarea.value.slice(0, start);
|
|
704
|
+
const after = this.textarea.value.slice(end);
|
|
705
|
+
this.textarea.value = before + text + after;
|
|
706
|
+
this.textarea.setSelectionRange(start + text.length, start + text.length);
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
this.textarea.dispatchEvent(new Event('input', { bubbles: true }));
|
|
710
|
+
}
|
|
711
|
+
|
|
476
712
|
/**
|
|
477
713
|
* Update preview with parsed markdown
|
|
478
714
|
*/
|
|
@@ -486,7 +722,12 @@ class OverType {
|
|
|
486
722
|
|
|
487
723
|
// Parse markdown
|
|
488
724
|
const html = MarkdownParser.parse(text, activeLine, this.options.showActiveLineRaw, this.options.codeHighlighter, isPreviewMode);
|
|
489
|
-
this.preview.innerHTML = html
|
|
725
|
+
this.preview.innerHTML = html;
|
|
726
|
+
|
|
727
|
+
// Show/hide placeholder shim
|
|
728
|
+
if (this.placeholderEl) {
|
|
729
|
+
this.placeholderEl.style.display = text ? 'none' : '';
|
|
730
|
+
}
|
|
490
731
|
|
|
491
732
|
// Apply code block backgrounds
|
|
492
733
|
this._applyCodeBlockBackgrounds();
|
|
@@ -807,13 +1048,50 @@ class OverType {
|
|
|
807
1048
|
setValue(value) {
|
|
808
1049
|
this.textarea.value = value;
|
|
809
1050
|
this.updatePreview();
|
|
810
|
-
|
|
1051
|
+
|
|
811
1052
|
// Update height if auto-resize is enabled
|
|
812
1053
|
if (this.options.autoResize) {
|
|
813
1054
|
this._updateAutoHeight();
|
|
814
1055
|
}
|
|
815
1056
|
}
|
|
816
1057
|
|
|
1058
|
+
/**
|
|
1059
|
+
* Execute an action by ID
|
|
1060
|
+
* Central dispatcher used by toolbar clicks, keyboard shortcuts, and programmatic calls
|
|
1061
|
+
* @param {string} actionId - The action identifier (e.g., 'toggleBold', 'insertLink')
|
|
1062
|
+
* @param {Event|null} event - Optional event that triggered the action
|
|
1063
|
+
* @returns {Promise<boolean>} Whether the action was executed successfully
|
|
1064
|
+
*/
|
|
1065
|
+
async performAction(actionId, event = null) {
|
|
1066
|
+
const textarea = this.textarea;
|
|
1067
|
+
if (!textarea) return false;
|
|
1068
|
+
|
|
1069
|
+
const action = this.actionsById?.[actionId];
|
|
1070
|
+
if (!action) {
|
|
1071
|
+
console.warn(`OverType: Unknown action "${actionId}"`);
|
|
1072
|
+
return false;
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
textarea.focus();
|
|
1076
|
+
|
|
1077
|
+
try {
|
|
1078
|
+
await action({
|
|
1079
|
+
editor: this,
|
|
1080
|
+
getValue: () => this.getValue(),
|
|
1081
|
+
setValue: (value) => this.setValue(value),
|
|
1082
|
+
event
|
|
1083
|
+
});
|
|
1084
|
+
// Note: actions are responsible for dispatching input event
|
|
1085
|
+
// This preserves behavior for direct consumers of toolbarButtons.*.action
|
|
1086
|
+
return true;
|
|
1087
|
+
} catch (error) {
|
|
1088
|
+
console.error(`OverType: Action "${actionId}" error:`, error);
|
|
1089
|
+
this.wrapper.dispatchEvent(new CustomEvent('button-error', {
|
|
1090
|
+
detail: { actionId, error }
|
|
1091
|
+
}));
|
|
1092
|
+
return false;
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
817
1095
|
|
|
818
1096
|
/**
|
|
819
1097
|
* Get the rendered HTML of the current content
|
|
@@ -882,39 +1160,89 @@ class OverType {
|
|
|
882
1160
|
* @param {Object} options - New options to apply
|
|
883
1161
|
*/
|
|
884
1162
|
reinit(options = {}) {
|
|
1163
|
+
const prevToolbarButtons = this.options?.toolbarButtons;
|
|
885
1164
|
this.options = this._mergeOptions({ ...this.options, ...options });
|
|
1165
|
+
const toolbarNeedsRebuild = this.toolbar &&
|
|
1166
|
+
this.options.toolbar &&
|
|
1167
|
+
toolbarButtonsChanged(prevToolbarButtons, this.options.toolbarButtons);
|
|
1168
|
+
|
|
1169
|
+
// Rebuild action map in case toolbarButtons changed
|
|
1170
|
+
this._rebuildActionsMap();
|
|
1171
|
+
|
|
1172
|
+
if (toolbarNeedsRebuild) {
|
|
1173
|
+
this._cleanupToolbarListeners();
|
|
1174
|
+
this.toolbar.destroy();
|
|
1175
|
+
this.toolbar = null;
|
|
1176
|
+
this._createToolbar();
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
if (this.fileUploadInitialized) {
|
|
1180
|
+
this._destroyFileUpload();
|
|
1181
|
+
}
|
|
1182
|
+
if (this.options.fileUpload) {
|
|
1183
|
+
this._initFileUpload();
|
|
1184
|
+
}
|
|
1185
|
+
|
|
886
1186
|
this._applyOptions();
|
|
887
1187
|
this.updatePreview();
|
|
888
1188
|
}
|
|
889
1189
|
|
|
1190
|
+
showToolbar() {
|
|
1191
|
+
if (this.toolbar) {
|
|
1192
|
+
this.toolbar.show();
|
|
1193
|
+
} else {
|
|
1194
|
+
this._createToolbar();
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
hideToolbar() {
|
|
1199
|
+
if (this.toolbar) {
|
|
1200
|
+
this.toolbar.hide();
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
|
|
890
1204
|
/**
|
|
891
1205
|
* Set theme for this instance
|
|
892
1206
|
* @param {string|Object} theme - Theme name or custom theme object
|
|
893
1207
|
* @returns {this} Returns this for chaining
|
|
894
1208
|
*/
|
|
895
1209
|
setTheme(theme) {
|
|
896
|
-
|
|
1210
|
+
OverType._autoInstances.delete(this);
|
|
897
1211
|
this.instanceTheme = theme;
|
|
898
1212
|
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
1213
|
+
if (theme === 'auto') {
|
|
1214
|
+
OverType._autoInstances.add(this);
|
|
1215
|
+
OverType._startAutoListener();
|
|
1216
|
+
this._applyResolvedTheme(resolveAutoTheme('auto'));
|
|
1217
|
+
} else {
|
|
1218
|
+
const themeObj = typeof theme === 'string' ? getTheme(theme) : theme;
|
|
1219
|
+
const themeName = typeof themeObj === 'string' ? themeObj : themeObj.name;
|
|
902
1220
|
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
1221
|
+
if (themeName) {
|
|
1222
|
+
this.container.setAttribute('data-theme', themeName);
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
if (themeObj && themeObj.colors) {
|
|
1226
|
+
const cssVars = themeToCSSVars(themeObj.colors);
|
|
1227
|
+
this.container.style.cssText += cssVars;
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
this.updatePreview();
|
|
906
1231
|
}
|
|
907
1232
|
|
|
908
|
-
|
|
1233
|
+
OverType._stopAutoListener();
|
|
1234
|
+
return this;
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
_applyResolvedTheme(themeName) {
|
|
1238
|
+
const themeObj = getTheme(themeName);
|
|
1239
|
+
this.container.setAttribute('data-theme', themeName);
|
|
1240
|
+
|
|
909
1241
|
if (themeObj && themeObj.colors) {
|
|
910
|
-
|
|
911
|
-
this.container.style.cssText += cssVars;
|
|
1242
|
+
this.container.style.cssText = themeToCSSVars(themeObj.colors);
|
|
912
1243
|
}
|
|
913
1244
|
|
|
914
|
-
// Update preview to reflect new theme
|
|
915
1245
|
this.updatePreview();
|
|
916
|
-
|
|
917
|
-
return this;
|
|
918
1246
|
}
|
|
919
1247
|
|
|
920
1248
|
/**
|
|
@@ -1122,6 +1450,13 @@ class OverType {
|
|
|
1122
1450
|
* Destroy the editor instance
|
|
1123
1451
|
*/
|
|
1124
1452
|
destroy() {
|
|
1453
|
+
OverType._autoInstances.delete(this);
|
|
1454
|
+
OverType._stopAutoListener();
|
|
1455
|
+
|
|
1456
|
+
if (this.fileUploadInitialized) {
|
|
1457
|
+
this._destroyFileUpload();
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1125
1460
|
// Remove instance reference
|
|
1126
1461
|
this.element.overTypeInstance = null;
|
|
1127
1462
|
OverType.instances.delete(this.element);
|
|
@@ -1178,7 +1513,7 @@ class OverType {
|
|
|
1178
1513
|
}
|
|
1179
1514
|
}
|
|
1180
1515
|
|
|
1181
|
-
return new OverType(el, options);
|
|
1516
|
+
return new OverType(el, options)[0];
|
|
1182
1517
|
});
|
|
1183
1518
|
}
|
|
1184
1519
|
|
|
@@ -1246,59 +1581,89 @@ class OverType {
|
|
|
1246
1581
|
* @param {Object} customColors - Optional color overrides
|
|
1247
1582
|
*/
|
|
1248
1583
|
static setTheme(theme, customColors = null) {
|
|
1249
|
-
|
|
1584
|
+
OverType._globalAutoTheme = false;
|
|
1585
|
+
OverType._globalAutoCustomColors = null;
|
|
1586
|
+
|
|
1587
|
+
if (theme === 'auto') {
|
|
1588
|
+
OverType._globalAutoTheme = true;
|
|
1589
|
+
OverType._globalAutoCustomColors = customColors;
|
|
1590
|
+
OverType._startAutoListener();
|
|
1591
|
+
OverType._applyGlobalTheme(resolveAutoTheme('auto'), customColors);
|
|
1592
|
+
return;
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
OverType._stopAutoListener();
|
|
1596
|
+
OverType._applyGlobalTheme(theme, customColors);
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
static _applyGlobalTheme(theme, customColors = null) {
|
|
1250
1600
|
let themeObj = typeof theme === 'string' ? getTheme(theme) : theme;
|
|
1251
1601
|
|
|
1252
|
-
// Apply custom colors if provided
|
|
1253
1602
|
if (customColors) {
|
|
1254
1603
|
themeObj = mergeTheme(themeObj, customColors);
|
|
1255
1604
|
}
|
|
1256
1605
|
|
|
1257
|
-
// Store as current theme
|
|
1258
1606
|
OverType.currentTheme = themeObj;
|
|
1259
|
-
|
|
1260
|
-
// Re-inject styles with new theme
|
|
1261
1607
|
OverType.injectStyles(true);
|
|
1262
1608
|
|
|
1263
|
-
|
|
1609
|
+
const themeName = typeof themeObj === 'string' ? themeObj : themeObj.name;
|
|
1610
|
+
|
|
1264
1611
|
document.querySelectorAll('.overtype-container').forEach(container => {
|
|
1265
|
-
const themeName = typeof themeObj === 'string' ? themeObj : themeObj.name;
|
|
1266
1612
|
if (themeName) {
|
|
1267
1613
|
container.setAttribute('data-theme', themeName);
|
|
1268
1614
|
}
|
|
1269
1615
|
});
|
|
1270
1616
|
|
|
1271
|
-
// Also handle any old-style wrappers without containers
|
|
1272
1617
|
document.querySelectorAll('.overtype-wrapper').forEach(wrapper => {
|
|
1273
1618
|
if (!wrapper.closest('.overtype-container')) {
|
|
1274
|
-
const themeName = typeof themeObj === 'string' ? themeObj : themeObj.name;
|
|
1275
1619
|
if (themeName) {
|
|
1276
1620
|
wrapper.setAttribute('data-theme', themeName);
|
|
1277
1621
|
}
|
|
1278
1622
|
}
|
|
1279
1623
|
|
|
1280
|
-
// Trigger preview update for the instance
|
|
1281
1624
|
const instance = wrapper._instance;
|
|
1282
1625
|
if (instance) {
|
|
1283
1626
|
instance.updatePreview();
|
|
1284
1627
|
}
|
|
1285
1628
|
});
|
|
1286
1629
|
|
|
1287
|
-
// Update web components (shadow DOM instances)
|
|
1288
|
-
const themeName = typeof themeObj === 'string' ? themeObj : themeObj.name;
|
|
1289
1630
|
document.querySelectorAll('overtype-editor').forEach(webComponent => {
|
|
1290
|
-
// Set the theme attribute to update the theme name
|
|
1291
1631
|
if (themeName && typeof webComponent.setAttribute === 'function') {
|
|
1292
1632
|
webComponent.setAttribute('theme', themeName);
|
|
1293
1633
|
}
|
|
1294
|
-
// Also call refreshTheme() to handle cases where the theme name stays the same
|
|
1295
|
-
// but the theme object's properties have changed
|
|
1296
1634
|
if (typeof webComponent.refreshTheme === 'function') {
|
|
1297
1635
|
webComponent.refreshTheme();
|
|
1298
1636
|
}
|
|
1299
1637
|
});
|
|
1300
1638
|
}
|
|
1301
1639
|
|
|
1640
|
+
static _startAutoListener() {
|
|
1641
|
+
if (OverType._autoMediaQuery) return;
|
|
1642
|
+
if (!window.matchMedia) return;
|
|
1643
|
+
|
|
1644
|
+
OverType._autoMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
|
1645
|
+
OverType._autoMediaListener = (e) => {
|
|
1646
|
+
const resolved = e.matches ? 'cave' : 'solar';
|
|
1647
|
+
|
|
1648
|
+
if (OverType._globalAutoTheme) {
|
|
1649
|
+
OverType._applyGlobalTheme(resolved, OverType._globalAutoCustomColors);
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
OverType._autoInstances.forEach(inst => inst._applyResolvedTheme(resolved));
|
|
1653
|
+
};
|
|
1654
|
+
|
|
1655
|
+
OverType._autoMediaQuery.addEventListener('change', OverType._autoMediaListener);
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
static _stopAutoListener() {
|
|
1659
|
+
if (OverType._autoInstances.size > 0 || OverType._globalAutoTheme) return;
|
|
1660
|
+
if (!OverType._autoMediaQuery) return;
|
|
1661
|
+
|
|
1662
|
+
OverType._autoMediaQuery.removeEventListener('change', OverType._autoMediaListener);
|
|
1663
|
+
OverType._autoMediaQuery = null;
|
|
1664
|
+
OverType._autoMediaListener = null;
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1302
1667
|
/**
|
|
1303
1668
|
* Set global code highlighter for all OverType instances
|
|
1304
1669
|
* @param {Function|null} highlighter - Function that takes (code, language) and returns highlighted HTML
|
|
@@ -1428,4 +1793,4 @@ export default OverType;
|
|
|
1428
1793
|
export { OverType };
|
|
1429
1794
|
|
|
1430
1795
|
// Export toolbar buttons for custom toolbar configurations
|
|
1431
|
-
export { toolbarButtons, defaultToolbarButtons } from './toolbar-buttons.js';
|
|
1796
|
+
export { toolbarButtons, defaultToolbarButtons } from './toolbar-buttons.js';
|
package/src/parser.js
CHANGED
|
@@ -87,6 +87,7 @@ export class MarkdownParser {
|
|
|
87
87
|
static parseHeader(html) {
|
|
88
88
|
return html.replace(/^(#{1,3})\s(.+)$/, (match, hashes, content) => {
|
|
89
89
|
const level = hashes.length;
|
|
90
|
+
content = this.parseInlineElements(content);
|
|
90
91
|
return `<h${level}><span class="syntax-marker">${hashes} </span>${content}</h${level}>`;
|
|
91
92
|
});
|
|
92
93
|
}
|
|
@@ -121,6 +122,7 @@ export class MarkdownParser {
|
|
|
121
122
|
*/
|
|
122
123
|
static parseBulletList(html) {
|
|
123
124
|
return html.replace(/^((?: )*)([-*+])\s(.+)$/, (match, indent, marker, content) => {
|
|
125
|
+
content = this.parseInlineElements(content);
|
|
124
126
|
return `${indent}<li class="bullet-list"><span class="syntax-marker">${marker} </span>${content}</li>`;
|
|
125
127
|
});
|
|
126
128
|
}
|
|
@@ -133,6 +135,7 @@ export class MarkdownParser {
|
|
|
133
135
|
*/
|
|
134
136
|
static parseTaskList(html, isPreviewMode = false) {
|
|
135
137
|
return html.replace(/^((?: )*)-\s+\[([ xX])\]\s+(.+)$/, (match, indent, checked, content) => {
|
|
138
|
+
content = this.parseInlineElements(content);
|
|
136
139
|
if (isPreviewMode) {
|
|
137
140
|
// Preview mode: render actual checkbox
|
|
138
141
|
const isChecked = checked.toLowerCase() === 'x';
|
|
@@ -151,6 +154,7 @@ export class MarkdownParser {
|
|
|
151
154
|
*/
|
|
152
155
|
static parseNumberedList(html) {
|
|
153
156
|
return html.replace(/^((?: )*)(\d+\.)\s(.+)$/, (match, indent, marker, content) => {
|
|
157
|
+
content = this.parseInlineElements(content);
|
|
154
158
|
return `${indent}<li class="ordered-list"><span class="syntax-marker">${marker} </span>${content}</li>`;
|
|
155
159
|
});
|
|
156
160
|
}
|
|
@@ -188,7 +192,7 @@ export class MarkdownParser {
|
|
|
188
192
|
*/
|
|
189
193
|
static parseItalic(html) {
|
|
190
194
|
// Single asterisk - must not be adjacent to other asterisks
|
|
191
|
-
//
|
|
195
|
+
// Must not be inside a syntax-marker span (avoid matching bullet list markers like ">* ")
|
|
192
196
|
html = html.replace(/(?<![\*>])\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, '<em><span class="syntax-marker">*</span>$1<span class="syntax-marker">*</span></em>');
|
|
193
197
|
|
|
194
198
|
// Single underscore - must be at word boundaries to avoid matching inside words
|
|
@@ -464,8 +468,10 @@ export class MarkdownParser {
|
|
|
464
468
|
html = this.parseBulletList(html);
|
|
465
469
|
html = this.parseNumberedList(html);
|
|
466
470
|
|
|
467
|
-
// Parse inline elements
|
|
468
|
-
html
|
|
471
|
+
// Parse inline elements (skip for headers and list items — already parsed inside those functions)
|
|
472
|
+
if (!html.includes('<li') && !html.includes('<h')) {
|
|
473
|
+
html = this.parseInlineElements(html);
|
|
474
|
+
}
|
|
469
475
|
|
|
470
476
|
// Wrap in div to maintain line structure
|
|
471
477
|
if (html.trim() === '') {
|