overtype 2.3.9 → 2.4.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 +171 -15
- package/dist/overtype-webcomponent.esm.js +510 -98
- package/dist/overtype-webcomponent.esm.js.map +3 -3
- package/dist/overtype-webcomponent.js +510 -98
- package/dist/overtype-webcomponent.js.map +3 -3
- package/dist/overtype-webcomponent.min.js +57 -56
- package/dist/overtype.cjs +510 -98
- package/dist/overtype.cjs.map +3 -3
- package/dist/overtype.d.ts +13 -0
- package/dist/overtype.esm.js +510 -98
- package/dist/overtype.esm.js.map +3 -3
- package/dist/overtype.js +510 -98
- package/dist/overtype.js.map +3 -3
- package/dist/overtype.min.js +53 -52
- package/package.json +3 -3
- package/src/link-tooltip.js +18 -2
- package/src/overtype.d.ts +13 -0
- package/src/overtype.js +249 -72
- package/src/shortcuts.js +12 -0
- package/src/toolbar-buttons.js +10 -0
- package/src/toolbar.js +308 -49
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* OverType v2.
|
|
2
|
+
* OverType v2.4.0
|
|
3
3
|
* A lightweight markdown editor library with perfect WYSIWYG alignment
|
|
4
4
|
* @license MIT
|
|
5
5
|
* @author David Miranda
|
|
@@ -777,6 +777,16 @@ var OverTypeEditor = (() => {
|
|
|
777
777
|
const modKey = isMac ? event.metaKey : event.ctrlKey;
|
|
778
778
|
if (!modKey)
|
|
779
779
|
return false;
|
|
780
|
+
if (event.key === "]") {
|
|
781
|
+
event.preventDefault();
|
|
782
|
+
this.editor.indentSelection();
|
|
783
|
+
return true;
|
|
784
|
+
}
|
|
785
|
+
if (event.key === "[") {
|
|
786
|
+
event.preventDefault();
|
|
787
|
+
this.editor.outdentSelection();
|
|
788
|
+
return true;
|
|
789
|
+
}
|
|
780
790
|
let actionId = null;
|
|
781
791
|
switch (event.key.toLowerCase()) {
|
|
782
792
|
case "b":
|
|
@@ -2889,6 +2899,10 @@ ${blockSuffix}` : suffix;
|
|
|
2889
2899
|
this.editor = editor;
|
|
2890
2900
|
this.container = null;
|
|
2891
2901
|
this.buttons = {};
|
|
2902
|
+
this.currentItemIndex = 0;
|
|
2903
|
+
this.handleDocumentClick = null;
|
|
2904
|
+
this.activeDropdown = null;
|
|
2905
|
+
this.activeDropdownButton = null;
|
|
2892
2906
|
this.toolbarButtons = options.toolbarButtons || [];
|
|
2893
2907
|
}
|
|
2894
2908
|
/**
|
|
@@ -2897,8 +2911,10 @@ ${blockSuffix}` : suffix;
|
|
|
2897
2911
|
create() {
|
|
2898
2912
|
this.container = document.createElement("div");
|
|
2899
2913
|
this.container.className = "overtype-toolbar";
|
|
2914
|
+
this.container.id = this.getInstanceElementId("toolbar");
|
|
2900
2915
|
this.container.setAttribute("role", "toolbar");
|
|
2901
2916
|
this.container.setAttribute("aria-label", "Formatting toolbar");
|
|
2917
|
+
this.container.setAttribute("aria-controls", this.editor.textarea.id);
|
|
2902
2918
|
this.toolbarButtons.forEach((buttonConfig) => {
|
|
2903
2919
|
if (buttonConfig.name === "separator") {
|
|
2904
2920
|
const separator = this.createSeparator();
|
|
@@ -2909,8 +2925,116 @@ ${blockSuffix}` : suffix;
|
|
|
2909
2925
|
this.container.appendChild(button);
|
|
2910
2926
|
}
|
|
2911
2927
|
});
|
|
2928
|
+
this.setupRovingTabIndex();
|
|
2929
|
+
this.updateButtonStates();
|
|
2912
2930
|
this.editor.container.insertBefore(this.container, this.editor.wrapper);
|
|
2913
2931
|
}
|
|
2932
|
+
/**
|
|
2933
|
+
* Build a stable id from the owning OverType instance
|
|
2934
|
+
*/
|
|
2935
|
+
getInstanceElementId(name) {
|
|
2936
|
+
return `overtype-${this.editor.instanceId}-${name}`;
|
|
2937
|
+
}
|
|
2938
|
+
/**
|
|
2939
|
+
* Configure toolbar focus management per the ARIA toolbar pattern
|
|
2940
|
+
*/
|
|
2941
|
+
setupRovingTabIndex() {
|
|
2942
|
+
const toolbarItems = this.getToolbarItems();
|
|
2943
|
+
if (toolbarItems.length === 0) {
|
|
2944
|
+
return;
|
|
2945
|
+
}
|
|
2946
|
+
this.currentItemIndex = this.getValidItemIndex(this.currentItemIndex);
|
|
2947
|
+
this.updateTabIndexes();
|
|
2948
|
+
this.container.addEventListener("keydown", (e) => {
|
|
2949
|
+
this.onToolbarKeydown(e);
|
|
2950
|
+
});
|
|
2951
|
+
this.container.addEventListener("focusin", (e) => {
|
|
2952
|
+
this.onToolbarFocusin(e);
|
|
2953
|
+
});
|
|
2954
|
+
}
|
|
2955
|
+
/**
|
|
2956
|
+
* Get toolbar buttons in DOM order for keyboard navigation
|
|
2957
|
+
*/
|
|
2958
|
+
getToolbarItems() {
|
|
2959
|
+
if (!this.container) {
|
|
2960
|
+
return [];
|
|
2961
|
+
}
|
|
2962
|
+
return Array.from(this.container.querySelectorAll(".overtype-toolbar-button"));
|
|
2963
|
+
}
|
|
2964
|
+
/**
|
|
2965
|
+
* Handle keyboard navigation within the toolbar
|
|
2966
|
+
*/
|
|
2967
|
+
onToolbarKeydown(e) {
|
|
2968
|
+
const toolbarItems = this.getToolbarItems();
|
|
2969
|
+
if (!toolbarItems.includes(e.target)) {
|
|
2970
|
+
return;
|
|
2971
|
+
}
|
|
2972
|
+
switch (e.key) {
|
|
2973
|
+
case "ArrowRight":
|
|
2974
|
+
e.preventDefault();
|
|
2975
|
+
this.focusItem(this.currentItemIndex + 1);
|
|
2976
|
+
break;
|
|
2977
|
+
case "ArrowLeft":
|
|
2978
|
+
e.preventDefault();
|
|
2979
|
+
this.focusItem(this.currentItemIndex - 1);
|
|
2980
|
+
break;
|
|
2981
|
+
case "Home":
|
|
2982
|
+
e.preventDefault();
|
|
2983
|
+
this.focusItem(0);
|
|
2984
|
+
break;
|
|
2985
|
+
case "End":
|
|
2986
|
+
e.preventDefault();
|
|
2987
|
+
this.focusItem(toolbarItems.length - 1);
|
|
2988
|
+
break;
|
|
2989
|
+
}
|
|
2990
|
+
}
|
|
2991
|
+
/**
|
|
2992
|
+
* Remember the focused toolbar item as the toolbar tab stop
|
|
2993
|
+
*/
|
|
2994
|
+
onToolbarFocusin(e) {
|
|
2995
|
+
const focusedItemIndex = this.getToolbarItems().indexOf(e.target);
|
|
2996
|
+
if (focusedItemIndex === -1) {
|
|
2997
|
+
return;
|
|
2998
|
+
}
|
|
2999
|
+
this.currentItemIndex = focusedItemIndex;
|
|
3000
|
+
this.updateTabIndexes();
|
|
3001
|
+
}
|
|
3002
|
+
/**
|
|
3003
|
+
* Move focus to a toolbar item and make it the only tab stop
|
|
3004
|
+
*/
|
|
3005
|
+
focusItem(index) {
|
|
3006
|
+
const toolbarItems = this.getToolbarItems();
|
|
3007
|
+
if (toolbarItems.length === 0) {
|
|
3008
|
+
return;
|
|
3009
|
+
}
|
|
3010
|
+
this.currentItemIndex = this.getValidItemIndex(index, toolbarItems);
|
|
3011
|
+
this.updateTabIndexes();
|
|
3012
|
+
toolbarItems[this.currentItemIndex].focus();
|
|
3013
|
+
}
|
|
3014
|
+
/**
|
|
3015
|
+
* Normalize toolbar item indexes with wrapping
|
|
3016
|
+
*/
|
|
3017
|
+
getValidItemIndex(index, toolbarItems = this.getToolbarItems()) {
|
|
3018
|
+
const itemCount = toolbarItems.length;
|
|
3019
|
+
if (itemCount === 0) {
|
|
3020
|
+
return 0;
|
|
3021
|
+
}
|
|
3022
|
+
if (index < 0) {
|
|
3023
|
+
return itemCount - 1;
|
|
3024
|
+
}
|
|
3025
|
+
if (index >= itemCount) {
|
|
3026
|
+
return 0;
|
|
3027
|
+
}
|
|
3028
|
+
return index;
|
|
3029
|
+
}
|
|
3030
|
+
/**
|
|
3031
|
+
* Keep exactly one toolbar item in the page tab sequence
|
|
3032
|
+
*/
|
|
3033
|
+
updateTabIndexes() {
|
|
3034
|
+
this.getToolbarItems().forEach((item, index) => {
|
|
3035
|
+
item.tabIndex = index === this.currentItemIndex ? 0 : -1;
|
|
3036
|
+
});
|
|
3037
|
+
}
|
|
2914
3038
|
/**
|
|
2915
3039
|
* Create a toolbar separator
|
|
2916
3040
|
*/
|
|
@@ -2934,10 +3058,22 @@ ${blockSuffix}` : suffix;
|
|
|
2934
3058
|
if (buttonConfig.name === "viewMode") {
|
|
2935
3059
|
button.classList.add("has-dropdown");
|
|
2936
3060
|
button.dataset.dropdown = "true";
|
|
2937
|
-
button.
|
|
3061
|
+
button.setAttribute("aria-haspopup", "menu");
|
|
3062
|
+
button.setAttribute("aria-expanded", "false");
|
|
3063
|
+
button._clickHandler = (e) => {
|
|
2938
3064
|
e.preventDefault();
|
|
2939
3065
|
this.toggleViewModeDropdown(button);
|
|
2940
|
-
}
|
|
3066
|
+
};
|
|
3067
|
+
button._keydownHandler = (e) => {
|
|
3068
|
+
if (!["ArrowDown", "ArrowUp", "Enter", " "].includes(e.key)) {
|
|
3069
|
+
return;
|
|
3070
|
+
}
|
|
3071
|
+
e.preventDefault();
|
|
3072
|
+
const placement = e.key === "ArrowUp" ? "last" : "current";
|
|
3073
|
+
this.openViewModeDropdown(button, placement);
|
|
3074
|
+
};
|
|
3075
|
+
button.addEventListener("click", button._clickHandler);
|
|
3076
|
+
button.addEventListener("keydown", button._keydownHandler);
|
|
2941
3077
|
return button;
|
|
2942
3078
|
}
|
|
2943
3079
|
button._clickHandler = (e) => {
|
|
@@ -2992,12 +3128,17 @@ ${blockSuffix}` : suffix;
|
|
|
2992
3128
|
* Not exposed to users - viewMode button behavior is fixed
|
|
2993
3129
|
*/
|
|
2994
3130
|
toggleViewModeDropdown(button) {
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
existingDropdown.remove();
|
|
2998
|
-
button.classList.remove("dropdown-active");
|
|
3131
|
+
if (this.activeDropdown) {
|
|
3132
|
+
this.closeViewModeDropdown(button);
|
|
2999
3133
|
return;
|
|
3000
3134
|
}
|
|
3135
|
+
this.openViewModeDropdown(button);
|
|
3136
|
+
}
|
|
3137
|
+
/**
|
|
3138
|
+
* Open the view mode dropdown
|
|
3139
|
+
*/
|
|
3140
|
+
openViewModeDropdown(button, focusPlacement = null) {
|
|
3141
|
+
this.closeViewModeDropdown(button);
|
|
3001
3142
|
button.classList.add("dropdown-active");
|
|
3002
3143
|
const dropdown = this.createViewModeDropdown(button);
|
|
3003
3144
|
const rect = button.getBoundingClientRect();
|
|
@@ -3005,16 +3146,42 @@ ${blockSuffix}` : suffix;
|
|
|
3005
3146
|
dropdown.style.top = `${rect.bottom + 5}px`;
|
|
3006
3147
|
dropdown.style.left = `${rect.left}px`;
|
|
3007
3148
|
document.body.appendChild(dropdown);
|
|
3149
|
+
this.activeDropdown = dropdown;
|
|
3150
|
+
this.activeDropdownButton = button;
|
|
3151
|
+
button.setAttribute("aria-controls", dropdown.id);
|
|
3152
|
+
button.setAttribute("aria-expanded", "true");
|
|
3008
3153
|
this.handleDocumentClick = (e) => {
|
|
3009
3154
|
if (!dropdown.contains(e.target) && !button.contains(e.target)) {
|
|
3010
|
-
|
|
3011
|
-
button.classList.remove("dropdown-active");
|
|
3012
|
-
document.removeEventListener("click", this.handleDocumentClick);
|
|
3155
|
+
this.closeViewModeDropdown(button);
|
|
3013
3156
|
}
|
|
3014
3157
|
};
|
|
3015
3158
|
setTimeout(() => {
|
|
3016
3159
|
document.addEventListener("click", this.handleDocumentClick);
|
|
3017
3160
|
}, 0);
|
|
3161
|
+
if (focusPlacement) {
|
|
3162
|
+
this.focusViewModeMenuItem(dropdown, focusPlacement);
|
|
3163
|
+
}
|
|
3164
|
+
}
|
|
3165
|
+
/**
|
|
3166
|
+
* Close the view mode dropdown
|
|
3167
|
+
*/
|
|
3168
|
+
closeViewModeDropdown(button = this.activeDropdownButton, returnFocus = false) {
|
|
3169
|
+
if (this.activeDropdown) {
|
|
3170
|
+
this.activeDropdown.remove();
|
|
3171
|
+
this.activeDropdown = null;
|
|
3172
|
+
}
|
|
3173
|
+
if (button) {
|
|
3174
|
+
button.classList.remove("dropdown-active");
|
|
3175
|
+
button.setAttribute("aria-expanded", "false");
|
|
3176
|
+
}
|
|
3177
|
+
if (this.handleDocumentClick) {
|
|
3178
|
+
document.removeEventListener("click", this.handleDocumentClick);
|
|
3179
|
+
this.handleDocumentClick = null;
|
|
3180
|
+
}
|
|
3181
|
+
this.activeDropdownButton = null;
|
|
3182
|
+
if (returnFocus && button) {
|
|
3183
|
+
button.focus();
|
|
3184
|
+
}
|
|
3018
3185
|
}
|
|
3019
3186
|
/**
|
|
3020
3187
|
* Create view mode dropdown menu (internal implementation)
|
|
@@ -3022,6 +3189,12 @@ ${blockSuffix}` : suffix;
|
|
|
3022
3189
|
createViewModeDropdown(button) {
|
|
3023
3190
|
const dropdown = document.createElement("div");
|
|
3024
3191
|
dropdown.className = "overtype-dropdown-menu";
|
|
3192
|
+
dropdown.id = this.getInstanceElementId("toolbar-view-mode-menu");
|
|
3193
|
+
dropdown.setAttribute("role", "menu");
|
|
3194
|
+
dropdown.setAttribute("aria-label", "View mode");
|
|
3195
|
+
dropdown.addEventListener("keydown", (e) => {
|
|
3196
|
+
this.onViewModeMenuKeydown(e, button);
|
|
3197
|
+
});
|
|
3025
3198
|
const items = [
|
|
3026
3199
|
{ id: "normal", label: "Normal Edit", icon: "\u2713" },
|
|
3027
3200
|
{ id: "plain", label: "Plain Textarea", icon: "\u2713" },
|
|
@@ -3032,12 +3205,15 @@ ${blockSuffix}` : suffix;
|
|
|
3032
3205
|
const menuItem = document.createElement("button");
|
|
3033
3206
|
menuItem.className = "overtype-dropdown-item";
|
|
3034
3207
|
menuItem.type = "button";
|
|
3208
|
+
menuItem.tabIndex = -1;
|
|
3209
|
+
menuItem.setAttribute("role", "menuitemradio");
|
|
3210
|
+
menuItem.setAttribute("aria-checked", String(item.id === currentMode));
|
|
3035
3211
|
menuItem.textContent = item.label;
|
|
3036
3212
|
if (item.id === currentMode) {
|
|
3037
3213
|
menuItem.classList.add("active");
|
|
3038
|
-
menuItem.setAttribute("aria-current", "true");
|
|
3039
3214
|
const checkmark = document.createElement("span");
|
|
3040
3215
|
checkmark.className = "overtype-dropdown-icon";
|
|
3216
|
+
checkmark.setAttribute("aria-hidden", "true");
|
|
3041
3217
|
checkmark.textContent = item.icon;
|
|
3042
3218
|
menuItem.prepend(checkmark);
|
|
3043
3219
|
}
|
|
@@ -3055,14 +3231,77 @@ ${blockSuffix}` : suffix;
|
|
|
3055
3231
|
this.editor.showNormalEditMode();
|
|
3056
3232
|
break;
|
|
3057
3233
|
}
|
|
3058
|
-
|
|
3059
|
-
button.classList.remove("dropdown-active");
|
|
3060
|
-
document.removeEventListener("click", this.handleDocumentClick);
|
|
3234
|
+
this.closeViewModeDropdown(button, true);
|
|
3061
3235
|
});
|
|
3062
3236
|
dropdown.appendChild(menuItem);
|
|
3063
3237
|
});
|
|
3064
3238
|
return dropdown;
|
|
3065
3239
|
}
|
|
3240
|
+
/**
|
|
3241
|
+
* Handle keyboard navigation inside the view mode menu
|
|
3242
|
+
*/
|
|
3243
|
+
onViewModeMenuKeydown(e, button) {
|
|
3244
|
+
const menuItems = this.getViewModeMenuItems();
|
|
3245
|
+
const currentIndex = menuItems.indexOf(e.target);
|
|
3246
|
+
if (currentIndex === -1) {
|
|
3247
|
+
return;
|
|
3248
|
+
}
|
|
3249
|
+
switch (e.key) {
|
|
3250
|
+
case "ArrowDown":
|
|
3251
|
+
e.preventDefault();
|
|
3252
|
+
this.focusViewModeMenuItem(this.activeDropdown, currentIndex + 1);
|
|
3253
|
+
break;
|
|
3254
|
+
case "ArrowUp":
|
|
3255
|
+
e.preventDefault();
|
|
3256
|
+
this.focusViewModeMenuItem(this.activeDropdown, currentIndex - 1);
|
|
3257
|
+
break;
|
|
3258
|
+
case "Home":
|
|
3259
|
+
e.preventDefault();
|
|
3260
|
+
this.focusViewModeMenuItem(this.activeDropdown, "first");
|
|
3261
|
+
break;
|
|
3262
|
+
case "End":
|
|
3263
|
+
e.preventDefault();
|
|
3264
|
+
this.focusViewModeMenuItem(this.activeDropdown, "last");
|
|
3265
|
+
break;
|
|
3266
|
+
case "Escape":
|
|
3267
|
+
e.preventDefault();
|
|
3268
|
+
this.closeViewModeDropdown(button, true);
|
|
3269
|
+
break;
|
|
3270
|
+
}
|
|
3271
|
+
}
|
|
3272
|
+
/**
|
|
3273
|
+
* Focus a view mode menu item by index or placement
|
|
3274
|
+
*/
|
|
3275
|
+
focusViewModeMenuItem(dropdown, placement) {
|
|
3276
|
+
const menuItems = this.getViewModeMenuItems(dropdown);
|
|
3277
|
+
if (menuItems.length === 0) {
|
|
3278
|
+
return;
|
|
3279
|
+
}
|
|
3280
|
+
let index = placement;
|
|
3281
|
+
if (placement === "first") {
|
|
3282
|
+
index = 0;
|
|
3283
|
+
} else if (placement === "last") {
|
|
3284
|
+
index = menuItems.length - 1;
|
|
3285
|
+
} else if (placement === "current") {
|
|
3286
|
+
index = menuItems.findIndex((item) => item.getAttribute("aria-checked") === "true");
|
|
3287
|
+
}
|
|
3288
|
+
if (index < 0) {
|
|
3289
|
+
index = menuItems.length - 1;
|
|
3290
|
+
}
|
|
3291
|
+
if (index >= menuItems.length) {
|
|
3292
|
+
index = 0;
|
|
3293
|
+
}
|
|
3294
|
+
menuItems[index].focus();
|
|
3295
|
+
}
|
|
3296
|
+
/**
|
|
3297
|
+
* Get the current view mode menu items
|
|
3298
|
+
*/
|
|
3299
|
+
getViewModeMenuItems(dropdown = this.activeDropdown) {
|
|
3300
|
+
if (!dropdown) {
|
|
3301
|
+
return [];
|
|
3302
|
+
}
|
|
3303
|
+
return Array.from(dropdown.querySelectorAll('[role="menuitemradio"]'));
|
|
3304
|
+
}
|
|
3066
3305
|
/**
|
|
3067
3306
|
* Update active states of toolbar buttons
|
|
3068
3307
|
*/
|
|
@@ -3076,39 +3315,14 @@ ${blockSuffix}` : suffix;
|
|
|
3076
3315
|
Object.entries(this.buttons).forEach(([name, button]) => {
|
|
3077
3316
|
if (name === "viewMode")
|
|
3078
3317
|
return;
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
isActive = activeFormats.includes("bold");
|
|
3083
|
-
break;
|
|
3084
|
-
case "italic":
|
|
3085
|
-
isActive = activeFormats.includes("italic");
|
|
3086
|
-
break;
|
|
3087
|
-
case "code":
|
|
3088
|
-
isActive = false;
|
|
3089
|
-
break;
|
|
3090
|
-
case "bulletList":
|
|
3091
|
-
isActive = activeFormats.includes("bullet-list");
|
|
3092
|
-
break;
|
|
3093
|
-
case "orderedList":
|
|
3094
|
-
isActive = activeFormats.includes("numbered-list");
|
|
3095
|
-
break;
|
|
3096
|
-
case "taskList":
|
|
3097
|
-
isActive = activeFormats.includes("task-list");
|
|
3098
|
-
break;
|
|
3099
|
-
case "quote":
|
|
3100
|
-
isActive = activeFormats.includes("quote");
|
|
3101
|
-
break;
|
|
3102
|
-
case "h1":
|
|
3103
|
-
isActive = activeFormats.includes("header");
|
|
3104
|
-
break;
|
|
3105
|
-
case "h2":
|
|
3106
|
-
isActive = activeFormats.includes("header-2");
|
|
3107
|
-
break;
|
|
3108
|
-
case "h3":
|
|
3109
|
-
isActive = activeFormats.includes("header-3");
|
|
3110
|
-
break;
|
|
3318
|
+
const buttonConfig = this.toolbarButtons.find((buttonConfig2) => buttonConfig2.name === name);
|
|
3319
|
+
if (!(buttonConfig == null ? void 0 : buttonConfig.isActive)) {
|
|
3320
|
+
return;
|
|
3111
3321
|
}
|
|
3322
|
+
const isActive = Boolean(buttonConfig.isActive({
|
|
3323
|
+
editor: this.editor,
|
|
3324
|
+
activeFormats
|
|
3325
|
+
}));
|
|
3112
3326
|
button.classList.toggle("active", isActive);
|
|
3113
3327
|
button.setAttribute("aria-pressed", isActive.toString());
|
|
3114
3328
|
});
|
|
@@ -3130,14 +3344,21 @@ ${blockSuffix}` : suffix;
|
|
|
3130
3344
|
*/
|
|
3131
3345
|
destroy() {
|
|
3132
3346
|
if (this.container) {
|
|
3133
|
-
if (this.
|
|
3347
|
+
if (this.activeDropdown) {
|
|
3348
|
+
this.closeViewModeDropdown();
|
|
3349
|
+
} else if (this.handleDocumentClick) {
|
|
3134
3350
|
document.removeEventListener("click", this.handleDocumentClick);
|
|
3351
|
+
this.handleDocumentClick = null;
|
|
3135
3352
|
}
|
|
3136
3353
|
Object.values(this.buttons).forEach((button) => {
|
|
3137
3354
|
if (button._clickHandler) {
|
|
3138
3355
|
button.removeEventListener("click", button._clickHandler);
|
|
3139
3356
|
delete button._clickHandler;
|
|
3140
3357
|
}
|
|
3358
|
+
if (button._keydownHandler) {
|
|
3359
|
+
button.removeEventListener("keydown", button._keydownHandler);
|
|
3360
|
+
delete button._keydownHandler;
|
|
3361
|
+
}
|
|
3141
3362
|
});
|
|
3142
3363
|
this.container.remove();
|
|
3143
3364
|
this.container = null;
|
|
@@ -4404,7 +4625,10 @@ ${blockSuffix}` : suffix;
|
|
|
4404
4625
|
e.preventDefault();
|
|
4405
4626
|
e.stopPropagation();
|
|
4406
4627
|
if (this.currentLink) {
|
|
4407
|
-
|
|
4628
|
+
const safeUrl = MarkdownParser.sanitizeUrl(this.currentLink.url);
|
|
4629
|
+
if (safeUrl !== "#") {
|
|
4630
|
+
window.open(safeUrl, "_blank");
|
|
4631
|
+
}
|
|
4408
4632
|
this.hide();
|
|
4409
4633
|
}
|
|
4410
4634
|
});
|
|
@@ -4432,7 +4656,7 @@ ${blockSuffix}` : suffix;
|
|
|
4432
4656
|
if (position >= start && position <= end) {
|
|
4433
4657
|
return {
|
|
4434
4658
|
text: match[1],
|
|
4435
|
-
url: match[2],
|
|
4659
|
+
url: this.transformUrl(match[2]),
|
|
4436
4660
|
index: linkIndex,
|
|
4437
4661
|
start,
|
|
4438
4662
|
end
|
|
@@ -4442,6 +4666,18 @@ ${blockSuffix}` : suffix;
|
|
|
4442
4666
|
}
|
|
4443
4667
|
return null;
|
|
4444
4668
|
}
|
|
4669
|
+
transformUrl(url) {
|
|
4670
|
+
const transform = this.editor.options.transformLinkUrl;
|
|
4671
|
+
if (typeof transform !== "function")
|
|
4672
|
+
return url;
|
|
4673
|
+
try {
|
|
4674
|
+
const result = transform(url);
|
|
4675
|
+
return typeof result === "string" ? result : url;
|
|
4676
|
+
} catch (e) {
|
|
4677
|
+
console.warn("transformLinkUrl threw:", e);
|
|
4678
|
+
return url;
|
|
4679
|
+
}
|
|
4680
|
+
}
|
|
4445
4681
|
async show(linkInfo) {
|
|
4446
4682
|
this.currentLink = linkInfo;
|
|
4447
4683
|
this.cancelHide();
|
|
@@ -4593,6 +4829,7 @@ ${blockSuffix}` : suffix;
|
|
|
4593
4829
|
actionId: "toggleBold",
|
|
4594
4830
|
icon: boldIcon,
|
|
4595
4831
|
title: "Bold (Ctrl+B)",
|
|
4832
|
+
isActive: ({ activeFormats }) => activeFormats.includes("bold"),
|
|
4596
4833
|
action: ({ editor }) => {
|
|
4597
4834
|
toggleBold(editor.textarea);
|
|
4598
4835
|
editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
|
|
@@ -4603,6 +4840,7 @@ ${blockSuffix}` : suffix;
|
|
|
4603
4840
|
actionId: "toggleItalic",
|
|
4604
4841
|
icon: italicIcon,
|
|
4605
4842
|
title: "Italic (Ctrl+I)",
|
|
4843
|
+
isActive: ({ activeFormats }) => activeFormats.includes("italic"),
|
|
4606
4844
|
action: ({ editor }) => {
|
|
4607
4845
|
toggleItalic(editor.textarea);
|
|
4608
4846
|
editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
|
|
@@ -4613,6 +4851,7 @@ ${blockSuffix}` : suffix;
|
|
|
4613
4851
|
actionId: "toggleCode",
|
|
4614
4852
|
icon: codeIcon,
|
|
4615
4853
|
title: "Inline Code",
|
|
4854
|
+
isActive: () => false,
|
|
4616
4855
|
action: ({ editor }) => {
|
|
4617
4856
|
toggleCode(editor.textarea);
|
|
4618
4857
|
editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
|
|
@@ -4637,6 +4876,7 @@ ${blockSuffix}` : suffix;
|
|
|
4637
4876
|
actionId: "toggleH1",
|
|
4638
4877
|
icon: h1Icon,
|
|
4639
4878
|
title: "Heading 1",
|
|
4879
|
+
isActive: ({ activeFormats }) => activeFormats.includes("header"),
|
|
4640
4880
|
action: ({ editor }) => {
|
|
4641
4881
|
toggleH1(editor.textarea);
|
|
4642
4882
|
editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
|
|
@@ -4647,6 +4887,7 @@ ${blockSuffix}` : suffix;
|
|
|
4647
4887
|
actionId: "toggleH2",
|
|
4648
4888
|
icon: h2Icon,
|
|
4649
4889
|
title: "Heading 2",
|
|
4890
|
+
isActive: ({ activeFormats }) => activeFormats.includes("header-2"),
|
|
4650
4891
|
action: ({ editor }) => {
|
|
4651
4892
|
toggleH2(editor.textarea);
|
|
4652
4893
|
editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
|
|
@@ -4657,6 +4898,7 @@ ${blockSuffix}` : suffix;
|
|
|
4657
4898
|
actionId: "toggleH3",
|
|
4658
4899
|
icon: h3Icon,
|
|
4659
4900
|
title: "Heading 3",
|
|
4901
|
+
isActive: ({ activeFormats }) => activeFormats.includes("header-3"),
|
|
4660
4902
|
action: ({ editor }) => {
|
|
4661
4903
|
toggleH3(editor.textarea);
|
|
4662
4904
|
editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
|
|
@@ -4667,6 +4909,7 @@ ${blockSuffix}` : suffix;
|
|
|
4667
4909
|
actionId: "toggleBulletList",
|
|
4668
4910
|
icon: bulletListIcon,
|
|
4669
4911
|
title: "Bullet List",
|
|
4912
|
+
isActive: ({ activeFormats }) => activeFormats.includes("bullet-list"),
|
|
4670
4913
|
action: ({ editor }) => {
|
|
4671
4914
|
toggleBulletList(editor.textarea);
|
|
4672
4915
|
editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
|
|
@@ -4677,6 +4920,7 @@ ${blockSuffix}` : suffix;
|
|
|
4677
4920
|
actionId: "toggleNumberedList",
|
|
4678
4921
|
icon: orderedListIcon,
|
|
4679
4922
|
title: "Numbered List",
|
|
4923
|
+
isActive: ({ activeFormats }) => activeFormats.includes("numbered-list"),
|
|
4680
4924
|
action: ({ editor }) => {
|
|
4681
4925
|
toggleNumberedList(editor.textarea);
|
|
4682
4926
|
editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
|
|
@@ -4687,6 +4931,7 @@ ${blockSuffix}` : suffix;
|
|
|
4687
4931
|
actionId: "toggleTaskList",
|
|
4688
4932
|
icon: taskListIcon,
|
|
4689
4933
|
title: "Task List",
|
|
4934
|
+
isActive: ({ activeFormats }) => activeFormats.includes("task-list"),
|
|
4690
4935
|
action: ({ editor }) => {
|
|
4691
4936
|
if (toggleTaskList) {
|
|
4692
4937
|
toggleTaskList(editor.textarea);
|
|
@@ -4699,6 +4944,7 @@ ${blockSuffix}` : suffix;
|
|
|
4699
4944
|
actionId: "toggleQuote",
|
|
4700
4945
|
icon: quoteIcon,
|
|
4701
4946
|
title: "Quote",
|
|
4947
|
+
isActive: ({ activeFormats }) => activeFormats.includes("quote"),
|
|
4702
4948
|
action: ({ editor }) => {
|
|
4703
4949
|
toggleQuote(editor.textarea);
|
|
4704
4950
|
editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
|
|
@@ -4760,6 +5006,17 @@ ${blockSuffix}` : suffix;
|
|
|
4760
5006
|
];
|
|
4761
5007
|
|
|
4762
5008
|
// src/overtype.js
|
|
5009
|
+
var _isSafariCache;
|
|
5010
|
+
function isSafariBrowser() {
|
|
5011
|
+
if (_isSafariCache !== void 0)
|
|
5012
|
+
return _isSafariCache;
|
|
5013
|
+
_isSafariCache = false;
|
|
5014
|
+
if (typeof navigator !== "undefined") {
|
|
5015
|
+
const ua = navigator.userAgent || "";
|
|
5016
|
+
_isSafariCache = /^((?!chrome|android|crios|fxios|edg|opr).)*safari/i.test(ua) || /iPad|iPhone|iPod/.test(ua) || navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1 && /Safari/.test(ua);
|
|
5017
|
+
}
|
|
5018
|
+
return _isSafariCache;
|
|
5019
|
+
}
|
|
4763
5020
|
function buildActionsMap(buttons) {
|
|
4764
5021
|
const map = {};
|
|
4765
5022
|
(buttons || []).forEach((btn) => {
|
|
@@ -4834,6 +5091,8 @@ ${blockSuffix}` : suffix;
|
|
|
4834
5091
|
this.options = this._mergeOptions(options);
|
|
4835
5092
|
this.instanceId = ++_OverType.instanceCount;
|
|
4836
5093
|
this.initialized = false;
|
|
5094
|
+
this._isSafari = isSafariBrowser();
|
|
5095
|
+
this._safariReflowRaf = null;
|
|
4837
5096
|
_OverType.injectStyles();
|
|
4838
5097
|
_OverType.initGlobalListeners();
|
|
4839
5098
|
const container = element.querySelector(".overtype-container");
|
|
@@ -4908,8 +5167,10 @@ ${blockSuffix}` : suffix;
|
|
|
4908
5167
|
// Enable smart list continuation
|
|
4909
5168
|
codeHighlighter: null,
|
|
4910
5169
|
// Per-instance code highlighter
|
|
4911
|
-
spellcheck: false
|
|
5170
|
+
spellcheck: false,
|
|
4912
5171
|
// Browser spellcheck (disabled by default)
|
|
5172
|
+
transformLinkUrl: null
|
|
5173
|
+
// Transform URLs shown/opened in the link tooltip
|
|
4913
5174
|
};
|
|
4914
5175
|
const { theme, colors, ...cleanOptions } = options;
|
|
4915
5176
|
return {
|
|
@@ -4962,6 +5223,8 @@ ${blockSuffix}` : suffix;
|
|
|
4962
5223
|
this.wrapper._instance = this;
|
|
4963
5224
|
this._applyInstanceCSSVars();
|
|
4964
5225
|
this._configureTextarea();
|
|
5226
|
+
this._ensureTextareaId();
|
|
5227
|
+
this._syncPreviewInteractivity();
|
|
4965
5228
|
this._applyOptions();
|
|
4966
5229
|
}
|
|
4967
5230
|
/**
|
|
@@ -5025,6 +5288,7 @@ ${blockSuffix}` : suffix;
|
|
|
5025
5288
|
}
|
|
5026
5289
|
});
|
|
5027
5290
|
}
|
|
5291
|
+
this._ensureTextareaId();
|
|
5028
5292
|
this.preview = document.createElement("div");
|
|
5029
5293
|
this.preview.className = "overtype-preview";
|
|
5030
5294
|
this.preview.setAttribute("aria-hidden", "true");
|
|
@@ -5048,6 +5312,7 @@ ${blockSuffix}` : suffix;
|
|
|
5048
5312
|
} else {
|
|
5049
5313
|
this.container.classList.remove("overtype-auto-resize");
|
|
5050
5314
|
}
|
|
5315
|
+
this._syncPreviewInteractivity();
|
|
5051
5316
|
}
|
|
5052
5317
|
/**
|
|
5053
5318
|
* Configure textarea attributes
|
|
@@ -5062,6 +5327,31 @@ ${blockSuffix}` : suffix;
|
|
|
5062
5327
|
this.textarea.setAttribute("data-gramm_editor", "false");
|
|
5063
5328
|
this.textarea.setAttribute("data-enable-grammarly", "false");
|
|
5064
5329
|
}
|
|
5330
|
+
/**
|
|
5331
|
+
* Ensure the textarea can be referenced by aria-controls
|
|
5332
|
+
* @private
|
|
5333
|
+
*/
|
|
5334
|
+
_ensureTextareaId() {
|
|
5335
|
+
if (!this.textarea.id) {
|
|
5336
|
+
this.textarea.id = `overtype-${this.instanceId}-input`;
|
|
5337
|
+
}
|
|
5338
|
+
}
|
|
5339
|
+
/**
|
|
5340
|
+
* Keep rendered preview content out of keyboard navigation until Preview mode.
|
|
5341
|
+
* @private
|
|
5342
|
+
*/
|
|
5343
|
+
_syncPreviewInteractivity() {
|
|
5344
|
+
if (!this.preview || !this.container)
|
|
5345
|
+
return;
|
|
5346
|
+
const isPreviewMode = this.container.dataset.mode === "preview";
|
|
5347
|
+
this.preview.inert = !isPreviewMode;
|
|
5348
|
+
this.preview.toggleAttribute("inert", !isPreviewMode);
|
|
5349
|
+
if (isPreviewMode) {
|
|
5350
|
+
this.preview.removeAttribute("aria-hidden");
|
|
5351
|
+
return;
|
|
5352
|
+
}
|
|
5353
|
+
this.preview.setAttribute("aria-hidden", "true");
|
|
5354
|
+
}
|
|
5065
5355
|
/**
|
|
5066
5356
|
* Create and setup toolbar
|
|
5067
5357
|
* @private
|
|
@@ -5191,6 +5481,7 @@ ${blockSuffix}` : suffix;
|
|
|
5191
5481
|
return;
|
|
5192
5482
|
}
|
|
5193
5483
|
this._fileUploadCounter = 0;
|
|
5484
|
+
this._uploadedFiles = /* @__PURE__ */ new Map();
|
|
5194
5485
|
this._boundHandleFilePaste = this._handleFilePaste.bind(this);
|
|
5195
5486
|
this._boundHandleFileDrop = this._handleFileDrop.bind(this);
|
|
5196
5487
|
this._boundHandleDragOver = this._handleDragOver.bind(this);
|
|
@@ -5199,6 +5490,53 @@ ${blockSuffix}` : suffix;
|
|
|
5199
5490
|
this.textarea.addEventListener("dragover", this._boundHandleDragOver);
|
|
5200
5491
|
this.fileUploadInitialized = true;
|
|
5201
5492
|
}
|
|
5493
|
+
/**
|
|
5494
|
+
* Extract URLs from markdown link syntax: [text](url) or .
|
|
5495
|
+
* @private
|
|
5496
|
+
*/
|
|
5497
|
+
_extractMarkdownUrls(text) {
|
|
5498
|
+
const urls = [];
|
|
5499
|
+
const re = /!?\[[^\]]*\]\(([^)\s]+)/g;
|
|
5500
|
+
let m;
|
|
5501
|
+
while ((m = re.exec(text)) !== null)
|
|
5502
|
+
urls.push(m[1]);
|
|
5503
|
+
return urls;
|
|
5504
|
+
}
|
|
5505
|
+
/**
|
|
5506
|
+
* Track URLs that were just inserted, pairing each with the source File.
|
|
5507
|
+
* If multiple URLs appear in one inserted block, all get associated with
|
|
5508
|
+
* the same file (rare; happens if onInsertFile returns several links).
|
|
5509
|
+
* @private
|
|
5510
|
+
*/
|
|
5511
|
+
_trackInsertedUrls(insertedText, file) {
|
|
5512
|
+
if (!this._uploadedFiles || !file || !insertedText)
|
|
5513
|
+
return;
|
|
5514
|
+
for (const url of this._extractMarkdownUrls(insertedText)) {
|
|
5515
|
+
this._uploadedFiles.set(url, { filename: file.name, file });
|
|
5516
|
+
}
|
|
5517
|
+
}
|
|
5518
|
+
/**
|
|
5519
|
+
* Diff the tracked-URL set against the current value and fire
|
|
5520
|
+
* fileUpload.onRemoveFile for any URL no longer present.
|
|
5521
|
+
* @private
|
|
5522
|
+
*/
|
|
5523
|
+
_checkForRemovedUploads() {
|
|
5524
|
+
var _a;
|
|
5525
|
+
if (!this._uploadedFiles || this._uploadedFiles.size === 0)
|
|
5526
|
+
return;
|
|
5527
|
+
const cb = (_a = this.options.fileUpload) == null ? void 0 : _a.onRemoveFile;
|
|
5528
|
+
const value = this.textarea.value;
|
|
5529
|
+
const removed = [];
|
|
5530
|
+
for (const [url, info] of this._uploadedFiles) {
|
|
5531
|
+
if (!value.includes(url))
|
|
5532
|
+
removed.push({ url, info });
|
|
5533
|
+
}
|
|
5534
|
+
for (const { url, info } of removed) {
|
|
5535
|
+
this._uploadedFiles.delete(url);
|
|
5536
|
+
if (cb)
|
|
5537
|
+
cb({ url, filename: info.filename, file: info.file });
|
|
5538
|
+
}
|
|
5539
|
+
}
|
|
5202
5540
|
_handleFilePaste(e) {
|
|
5203
5541
|
var _a, _b;
|
|
5204
5542
|
if (!((_b = (_a = e == null ? void 0 : e.clipboardData) == null ? void 0 : _a.files) == null ? void 0 : _b.length))
|
|
@@ -5231,6 +5569,7 @@ ${blockSuffix}` : suffix;
|
|
|
5231
5569
|
}
|
|
5232
5570
|
this.options.fileUpload.onInsertFile(file).then((text) => {
|
|
5233
5571
|
this.textarea.value = this.textarea.value.replace(placeholder, text);
|
|
5572
|
+
this._trackInsertedUrls(text, file);
|
|
5234
5573
|
this.textarea.dispatchEvent(new Event("input", { bubbles: true }));
|
|
5235
5574
|
}, (error) => {
|
|
5236
5575
|
console.error("OverType: File upload failed", error);
|
|
@@ -5243,6 +5582,7 @@ ${blockSuffix}` : suffix;
|
|
|
5243
5582
|
const texts = Array.isArray(result) ? result : [result];
|
|
5244
5583
|
texts.forEach((text, index) => {
|
|
5245
5584
|
this.textarea.value = this.textarea.value.replace(files[index].placeholder, text);
|
|
5585
|
+
this._trackInsertedUrls(text, files[index].file);
|
|
5246
5586
|
});
|
|
5247
5587
|
this.textarea.dispatchEvent(new Event("input", { bubbles: true }));
|
|
5248
5588
|
}, (error) => {
|
|
@@ -5264,6 +5604,7 @@ ${blockSuffix}` : suffix;
|
|
|
5264
5604
|
this._boundHandleFilePaste = null;
|
|
5265
5605
|
this._boundHandleFileDrop = null;
|
|
5266
5606
|
this._boundHandleDragOver = null;
|
|
5607
|
+
this._uploadedFiles = null;
|
|
5267
5608
|
this.fileUploadInitialized = false;
|
|
5268
5609
|
}
|
|
5269
5610
|
insertAtCursor(text) {
|
|
@@ -5308,9 +5649,12 @@ ${blockSuffix}` : suffix;
|
|
|
5308
5649
|
* @private
|
|
5309
5650
|
*/
|
|
5310
5651
|
_notifyChange() {
|
|
5311
|
-
if (!this.
|
|
5652
|
+
if (!this.initialized)
|
|
5312
5653
|
return;
|
|
5313
|
-
this.
|
|
5654
|
+
this._checkForRemovedUploads();
|
|
5655
|
+
if (this.options.onChange) {
|
|
5656
|
+
this.options.onChange(this.textarea.value, this);
|
|
5657
|
+
}
|
|
5314
5658
|
}
|
|
5315
5659
|
/**
|
|
5316
5660
|
* Apply background styling to code blocks
|
|
@@ -5346,6 +5690,29 @@ ${blockSuffix}` : suffix;
|
|
|
5346
5690
|
handleInput(event) {
|
|
5347
5691
|
this.updatePreview();
|
|
5348
5692
|
this._notifyChange();
|
|
5693
|
+
this._scheduleSafariReflow();
|
|
5694
|
+
}
|
|
5695
|
+
/**
|
|
5696
|
+
* Force Safari to re-shape stale textarea text after an edit.
|
|
5697
|
+
* Safari can leave a textarea's glyph layout cached after incremental edits,
|
|
5698
|
+
* desyncing the caret/wrap from the styled preview overlay. Toggling
|
|
5699
|
+
* letter-spacing (with !important to beat the stylesheet rule) and reading
|
|
5700
|
+
* offsetHeight forces a synchronous re-shape. Safari-only, coalesced to one
|
|
5701
|
+
* run per animation frame.
|
|
5702
|
+
* @private
|
|
5703
|
+
*/
|
|
5704
|
+
_scheduleSafariReflow() {
|
|
5705
|
+
if (!this._isSafari || this._safariReflowRaf)
|
|
5706
|
+
return;
|
|
5707
|
+
this._safariReflowRaf = requestAnimationFrame(() => {
|
|
5708
|
+
this._safariReflowRaf = null;
|
|
5709
|
+
const ta = this.textarea;
|
|
5710
|
+
if (!ta)
|
|
5711
|
+
return;
|
|
5712
|
+
ta.style.setProperty("letter-spacing", "-0.001px", "important");
|
|
5713
|
+
void ta.offsetHeight;
|
|
5714
|
+
ta.style.removeProperty("letter-spacing");
|
|
5715
|
+
});
|
|
5349
5716
|
}
|
|
5350
5717
|
/**
|
|
5351
5718
|
* Handle focus events
|
|
@@ -5373,49 +5740,11 @@ ${blockSuffix}` : suffix;
|
|
|
5373
5740
|
if (event.key === "Tab") {
|
|
5374
5741
|
const start = this.textarea.selectionStart;
|
|
5375
5742
|
const end = this.textarea.selectionEnd;
|
|
5376
|
-
|
|
5377
|
-
|
|
5743
|
+
if (start !== end && this._canEditTextarea()) {
|
|
5744
|
+
event.preventDefault();
|
|
5745
|
+
event.shiftKey ? this.outdentSelection() : this.indentSelection();
|
|
5378
5746
|
return;
|
|
5379
5747
|
}
|
|
5380
|
-
event.preventDefault();
|
|
5381
|
-
if (start !== end && event.shiftKey) {
|
|
5382
|
-
const before = value.substring(0, start);
|
|
5383
|
-
const selection = value.substring(start, end);
|
|
5384
|
-
const after = value.substring(end);
|
|
5385
|
-
const lines = selection.split("\n");
|
|
5386
|
-
const outdented = lines.map((line) => line.replace(/^ /, "")).join("\n");
|
|
5387
|
-
if (document.execCommand) {
|
|
5388
|
-
this.textarea.setSelectionRange(start, end);
|
|
5389
|
-
document.execCommand("insertText", false, outdented);
|
|
5390
|
-
} else {
|
|
5391
|
-
this.textarea.value = before + outdented + after;
|
|
5392
|
-
this.textarea.selectionStart = start;
|
|
5393
|
-
this.textarea.selectionEnd = start + outdented.length;
|
|
5394
|
-
}
|
|
5395
|
-
} else if (start !== end) {
|
|
5396
|
-
const before = value.substring(0, start);
|
|
5397
|
-
const selection = value.substring(start, end);
|
|
5398
|
-
const after = value.substring(end);
|
|
5399
|
-
const lines = selection.split("\n");
|
|
5400
|
-
const indented = lines.map((line) => " " + line).join("\n");
|
|
5401
|
-
if (document.execCommand) {
|
|
5402
|
-
this.textarea.setSelectionRange(start, end);
|
|
5403
|
-
document.execCommand("insertText", false, indented);
|
|
5404
|
-
} else {
|
|
5405
|
-
this.textarea.value = before + indented + after;
|
|
5406
|
-
this.textarea.selectionStart = start;
|
|
5407
|
-
this.textarea.selectionEnd = start + indented.length;
|
|
5408
|
-
}
|
|
5409
|
-
} else {
|
|
5410
|
-
if (document.execCommand) {
|
|
5411
|
-
document.execCommand("insertText", false, " ");
|
|
5412
|
-
} else {
|
|
5413
|
-
this.textarea.value = value.substring(0, start) + " " + value.substring(end);
|
|
5414
|
-
this.textarea.selectionStart = this.textarea.selectionEnd = start + 2;
|
|
5415
|
-
}
|
|
5416
|
-
}
|
|
5417
|
-
this.textarea.dispatchEvent(new Event("input", { bubbles: true }));
|
|
5418
|
-
return;
|
|
5419
5748
|
}
|
|
5420
5749
|
if (event.key === "Enter" && !event.shiftKey && !event.metaKey && !event.ctrlKey && this.options.smartLists) {
|
|
5421
5750
|
if (this.handleSmartListContinuation()) {
|
|
@@ -5552,6 +5881,7 @@ ${blockSuffix}` : suffix;
|
|
|
5552
5881
|
}
|
|
5553
5882
|
if (didChange) {
|
|
5554
5883
|
this._notifyChange();
|
|
5884
|
+
this._scheduleSafariReflow();
|
|
5555
5885
|
}
|
|
5556
5886
|
}
|
|
5557
5887
|
/**
|
|
@@ -5612,6 +5942,63 @@ ${blockSuffix}` : suffix;
|
|
|
5612
5942
|
getPreviewHTML() {
|
|
5613
5943
|
return this.preview.innerHTML;
|
|
5614
5944
|
}
|
|
5945
|
+
/**
|
|
5946
|
+
* Indent the current line or selected lines by two spaces.
|
|
5947
|
+
*/
|
|
5948
|
+
indentSelection() {
|
|
5949
|
+
this._replaceSelectedLines((line) => ` ${line}`);
|
|
5950
|
+
}
|
|
5951
|
+
/**
|
|
5952
|
+
* Outdent the current line or selected lines by up to two spaces or one tab.
|
|
5953
|
+
*/
|
|
5954
|
+
outdentSelection() {
|
|
5955
|
+
this._replaceSelectedLines((line) => line.replace(/^( {1,2}|\t)/, ""));
|
|
5956
|
+
}
|
|
5957
|
+
/**
|
|
5958
|
+
* Replace full lines touched by the current selection.
|
|
5959
|
+
* @private
|
|
5960
|
+
*/
|
|
5961
|
+
_replaceSelectedLines(transformLine) {
|
|
5962
|
+
if (!this._canEditTextarea())
|
|
5963
|
+
return false;
|
|
5964
|
+
const textarea = this.textarea;
|
|
5965
|
+
const { selectionStart, selectionEnd, value } = textarea;
|
|
5966
|
+
const lineStart = value.lastIndexOf("\n", selectionStart - 1) + 1;
|
|
5967
|
+
const effectiveEnd = this._effectiveSelectionEnd(value, selectionStart, selectionEnd);
|
|
5968
|
+
const lineEndOffset = value.indexOf("\n", effectiveEnd);
|
|
5969
|
+
const lineEnd = lineEndOffset === -1 ? value.length : lineEndOffset;
|
|
5970
|
+
const selectedLines = value.slice(lineStart, lineEnd);
|
|
5971
|
+
const replacement = selectedLines.split("\n").map(transformLine).join("\n");
|
|
5972
|
+
if (replacement === selectedLines)
|
|
5973
|
+
return false;
|
|
5974
|
+
textarea.setSelectionRange(lineStart, lineEnd);
|
|
5975
|
+
let inserted = false;
|
|
5976
|
+
try {
|
|
5977
|
+
inserted = document.execCommand("insertText", false, replacement);
|
|
5978
|
+
} catch (_) {
|
|
5979
|
+
}
|
|
5980
|
+
if (!inserted) {
|
|
5981
|
+
textarea.setRangeText(replacement, lineStart, lineEnd, "preserve");
|
|
5982
|
+
}
|
|
5983
|
+
textarea.setSelectionRange(lineStart, lineStart + replacement.length);
|
|
5984
|
+
textarea.dispatchEvent(new Event("input", { bubbles: true }));
|
|
5985
|
+
return true;
|
|
5986
|
+
}
|
|
5987
|
+
/**
|
|
5988
|
+
* @private
|
|
5989
|
+
*/
|
|
5990
|
+
_effectiveSelectionEnd(value, selectionStart, selectionEnd) {
|
|
5991
|
+
if (selectionEnd > selectionStart && value[selectionEnd - 1] === "\n") {
|
|
5992
|
+
return selectionEnd - 1;
|
|
5993
|
+
}
|
|
5994
|
+
return selectionEnd;
|
|
5995
|
+
}
|
|
5996
|
+
/**
|
|
5997
|
+
* @private
|
|
5998
|
+
*/
|
|
5999
|
+
_canEditTextarea() {
|
|
6000
|
+
return this.textarea && !this.textarea.disabled && !this.textarea.readOnly;
|
|
6001
|
+
}
|
|
5615
6002
|
/**
|
|
5616
6003
|
* Get clean HTML without any OverType-specific markup
|
|
5617
6004
|
* Useful for exporting to other formats or storage
|
|
@@ -5835,6 +6222,7 @@ ${blockSuffix}` : suffix;
|
|
|
5835
6222
|
*/
|
|
5836
6223
|
showNormalEditMode() {
|
|
5837
6224
|
this.container.dataset.mode = "normal";
|
|
6225
|
+
this._syncPreviewInteractivity();
|
|
5838
6226
|
this.updatePreview();
|
|
5839
6227
|
this._updateAutoHeight();
|
|
5840
6228
|
requestAnimationFrame(() => {
|
|
@@ -5849,6 +6237,7 @@ ${blockSuffix}` : suffix;
|
|
|
5849
6237
|
*/
|
|
5850
6238
|
showPlainTextarea() {
|
|
5851
6239
|
this.container.dataset.mode = "plain";
|
|
6240
|
+
this._syncPreviewInteractivity();
|
|
5852
6241
|
this._updateAutoHeight();
|
|
5853
6242
|
if (this.toolbar) {
|
|
5854
6243
|
const toggleBtn = this.container.querySelector('[data-action="toggle-plain"]');
|
|
@@ -5865,6 +6254,7 @@ ${blockSuffix}` : suffix;
|
|
|
5865
6254
|
*/
|
|
5866
6255
|
showPreviewMode() {
|
|
5867
6256
|
this.container.dataset.mode = "preview";
|
|
6257
|
+
this._syncPreviewInteractivity();
|
|
5868
6258
|
this.updatePreview();
|
|
5869
6259
|
this._updateAutoHeight();
|
|
5870
6260
|
return this;
|
|
@@ -5883,6 +6273,10 @@ ${blockSuffix}` : suffix;
|
|
|
5883
6273
|
if (this.shortcuts) {
|
|
5884
6274
|
this.shortcuts.destroy();
|
|
5885
6275
|
}
|
|
6276
|
+
if (this._safariReflowRaf) {
|
|
6277
|
+
cancelAnimationFrame(this._safariReflowRaf);
|
|
6278
|
+
this._safariReflowRaf = null;
|
|
6279
|
+
}
|
|
5886
6280
|
if (this.wrapper) {
|
|
5887
6281
|
const content = this.getValue();
|
|
5888
6282
|
this.wrapper.remove();
|
|
@@ -5914,11 +6308,16 @@ ${blockSuffix}` : suffix;
|
|
|
5914
6308
|
return elements.map((el) => {
|
|
5915
6309
|
const options = { ...defaults };
|
|
5916
6310
|
for (const attr of el.attributes) {
|
|
5917
|
-
if (attr.name.startsWith("data-ot-"))
|
|
5918
|
-
|
|
5919
|
-
|
|
5920
|
-
|
|
6311
|
+
if (!attr.name.startsWith("data-ot-"))
|
|
6312
|
+
continue;
|
|
6313
|
+
const kebab = attr.name.slice(8);
|
|
6314
|
+
if (kebab.startsWith("textarea-") && kebab !== "textarea-props") {
|
|
6315
|
+
const propKey = kebab.slice(9).replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
6316
|
+
options.textareaProps = { ...options.textareaProps || {}, [propKey]: _OverType._parseDataValue(attr.value) };
|
|
6317
|
+
continue;
|
|
5921
6318
|
}
|
|
6319
|
+
const key = kebab.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
6320
|
+
options[key] = _OverType._parseDataValue(attr.value);
|
|
5922
6321
|
}
|
|
5923
6322
|
return new _OverType(el, options)[0];
|
|
5924
6323
|
});
|
|
@@ -5963,6 +6362,14 @@ ${blockSuffix}` : suffix;
|
|
|
5963
6362
|
return null;
|
|
5964
6363
|
if (value !== "" && !isNaN(Number(value)))
|
|
5965
6364
|
return Number(value);
|
|
6365
|
+
const trimmed = value.trim();
|
|
6366
|
+
if (trimmed[0] === "{" || trimmed[0] === "[") {
|
|
6367
|
+
try {
|
|
6368
|
+
return JSON.parse(trimmed);
|
|
6369
|
+
} catch (e) {
|
|
6370
|
+
return value;
|
|
6371
|
+
}
|
|
6372
|
+
}
|
|
5966
6373
|
return value;
|
|
5967
6374
|
}
|
|
5968
6375
|
/**
|
|
@@ -6141,8 +6548,13 @@ ${blockSuffix}` : suffix;
|
|
|
6141
6548
|
* Initialize global event listeners
|
|
6142
6549
|
*/
|
|
6143
6550
|
static initGlobalListeners() {
|
|
6144
|
-
|
|
6551
|
+
const globalScope = typeof window !== "undefined" ? window : globalThis;
|
|
6552
|
+
const globalListenersKey = "__overtypeGlobalListenersInitialized";
|
|
6553
|
+
if (_OverType.globalListenersInitialized || globalScope[globalListenersKey]) {
|
|
6554
|
+
_OverType.globalListenersInitialized = true;
|
|
6145
6555
|
return;
|
|
6556
|
+
}
|
|
6557
|
+
globalScope[globalListenersKey] = true;
|
|
6146
6558
|
document.addEventListener("input", (e) => {
|
|
6147
6559
|
if (e.target && e.target.classList && e.target.classList.contains("overtype-input")) {
|
|
6148
6560
|
const wrapper = e.target.closest(".overtype-wrapper");
|