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
package/dist/overtype.js
CHANGED
|
@@ -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
|
|
@@ -781,6 +781,16 @@ var OverType = (() => {
|
|
|
781
781
|
const modKey = isMac ? event.metaKey : event.ctrlKey;
|
|
782
782
|
if (!modKey)
|
|
783
783
|
return false;
|
|
784
|
+
if (event.key === "]") {
|
|
785
|
+
event.preventDefault();
|
|
786
|
+
this.editor.indentSelection();
|
|
787
|
+
return true;
|
|
788
|
+
}
|
|
789
|
+
if (event.key === "[") {
|
|
790
|
+
event.preventDefault();
|
|
791
|
+
this.editor.outdentSelection();
|
|
792
|
+
return true;
|
|
793
|
+
}
|
|
784
794
|
let actionId = null;
|
|
785
795
|
switch (event.key.toLowerCase()) {
|
|
786
796
|
case "b":
|
|
@@ -2893,6 +2903,10 @@ ${blockSuffix}` : suffix;
|
|
|
2893
2903
|
this.editor = editor;
|
|
2894
2904
|
this.container = null;
|
|
2895
2905
|
this.buttons = {};
|
|
2906
|
+
this.currentItemIndex = 0;
|
|
2907
|
+
this.handleDocumentClick = null;
|
|
2908
|
+
this.activeDropdown = null;
|
|
2909
|
+
this.activeDropdownButton = null;
|
|
2896
2910
|
this.toolbarButtons = options.toolbarButtons || [];
|
|
2897
2911
|
}
|
|
2898
2912
|
/**
|
|
@@ -2901,8 +2915,10 @@ ${blockSuffix}` : suffix;
|
|
|
2901
2915
|
create() {
|
|
2902
2916
|
this.container = document.createElement("div");
|
|
2903
2917
|
this.container.className = "overtype-toolbar";
|
|
2918
|
+
this.container.id = this.getInstanceElementId("toolbar");
|
|
2904
2919
|
this.container.setAttribute("role", "toolbar");
|
|
2905
2920
|
this.container.setAttribute("aria-label", "Formatting toolbar");
|
|
2921
|
+
this.container.setAttribute("aria-controls", this.editor.textarea.id);
|
|
2906
2922
|
this.toolbarButtons.forEach((buttonConfig) => {
|
|
2907
2923
|
if (buttonConfig.name === "separator") {
|
|
2908
2924
|
const separator = this.createSeparator();
|
|
@@ -2913,8 +2929,116 @@ ${blockSuffix}` : suffix;
|
|
|
2913
2929
|
this.container.appendChild(button);
|
|
2914
2930
|
}
|
|
2915
2931
|
});
|
|
2932
|
+
this.setupRovingTabIndex();
|
|
2933
|
+
this.updateButtonStates();
|
|
2916
2934
|
this.editor.container.insertBefore(this.container, this.editor.wrapper);
|
|
2917
2935
|
}
|
|
2936
|
+
/**
|
|
2937
|
+
* Build a stable id from the owning OverType instance
|
|
2938
|
+
*/
|
|
2939
|
+
getInstanceElementId(name) {
|
|
2940
|
+
return `overtype-${this.editor.instanceId}-${name}`;
|
|
2941
|
+
}
|
|
2942
|
+
/**
|
|
2943
|
+
* Configure toolbar focus management per the ARIA toolbar pattern
|
|
2944
|
+
*/
|
|
2945
|
+
setupRovingTabIndex() {
|
|
2946
|
+
const toolbarItems = this.getToolbarItems();
|
|
2947
|
+
if (toolbarItems.length === 0) {
|
|
2948
|
+
return;
|
|
2949
|
+
}
|
|
2950
|
+
this.currentItemIndex = this.getValidItemIndex(this.currentItemIndex);
|
|
2951
|
+
this.updateTabIndexes();
|
|
2952
|
+
this.container.addEventListener("keydown", (e) => {
|
|
2953
|
+
this.onToolbarKeydown(e);
|
|
2954
|
+
});
|
|
2955
|
+
this.container.addEventListener("focusin", (e) => {
|
|
2956
|
+
this.onToolbarFocusin(e);
|
|
2957
|
+
});
|
|
2958
|
+
}
|
|
2959
|
+
/**
|
|
2960
|
+
* Get toolbar buttons in DOM order for keyboard navigation
|
|
2961
|
+
*/
|
|
2962
|
+
getToolbarItems() {
|
|
2963
|
+
if (!this.container) {
|
|
2964
|
+
return [];
|
|
2965
|
+
}
|
|
2966
|
+
return Array.from(this.container.querySelectorAll(".overtype-toolbar-button"));
|
|
2967
|
+
}
|
|
2968
|
+
/**
|
|
2969
|
+
* Handle keyboard navigation within the toolbar
|
|
2970
|
+
*/
|
|
2971
|
+
onToolbarKeydown(e) {
|
|
2972
|
+
const toolbarItems = this.getToolbarItems();
|
|
2973
|
+
if (!toolbarItems.includes(e.target)) {
|
|
2974
|
+
return;
|
|
2975
|
+
}
|
|
2976
|
+
switch (e.key) {
|
|
2977
|
+
case "ArrowRight":
|
|
2978
|
+
e.preventDefault();
|
|
2979
|
+
this.focusItem(this.currentItemIndex + 1);
|
|
2980
|
+
break;
|
|
2981
|
+
case "ArrowLeft":
|
|
2982
|
+
e.preventDefault();
|
|
2983
|
+
this.focusItem(this.currentItemIndex - 1);
|
|
2984
|
+
break;
|
|
2985
|
+
case "Home":
|
|
2986
|
+
e.preventDefault();
|
|
2987
|
+
this.focusItem(0);
|
|
2988
|
+
break;
|
|
2989
|
+
case "End":
|
|
2990
|
+
e.preventDefault();
|
|
2991
|
+
this.focusItem(toolbarItems.length - 1);
|
|
2992
|
+
break;
|
|
2993
|
+
}
|
|
2994
|
+
}
|
|
2995
|
+
/**
|
|
2996
|
+
* Remember the focused toolbar item as the toolbar tab stop
|
|
2997
|
+
*/
|
|
2998
|
+
onToolbarFocusin(e) {
|
|
2999
|
+
const focusedItemIndex = this.getToolbarItems().indexOf(e.target);
|
|
3000
|
+
if (focusedItemIndex === -1) {
|
|
3001
|
+
return;
|
|
3002
|
+
}
|
|
3003
|
+
this.currentItemIndex = focusedItemIndex;
|
|
3004
|
+
this.updateTabIndexes();
|
|
3005
|
+
}
|
|
3006
|
+
/**
|
|
3007
|
+
* Move focus to a toolbar item and make it the only tab stop
|
|
3008
|
+
*/
|
|
3009
|
+
focusItem(index) {
|
|
3010
|
+
const toolbarItems = this.getToolbarItems();
|
|
3011
|
+
if (toolbarItems.length === 0) {
|
|
3012
|
+
return;
|
|
3013
|
+
}
|
|
3014
|
+
this.currentItemIndex = this.getValidItemIndex(index, toolbarItems);
|
|
3015
|
+
this.updateTabIndexes();
|
|
3016
|
+
toolbarItems[this.currentItemIndex].focus();
|
|
3017
|
+
}
|
|
3018
|
+
/**
|
|
3019
|
+
* Normalize toolbar item indexes with wrapping
|
|
3020
|
+
*/
|
|
3021
|
+
getValidItemIndex(index, toolbarItems = this.getToolbarItems()) {
|
|
3022
|
+
const itemCount = toolbarItems.length;
|
|
3023
|
+
if (itemCount === 0) {
|
|
3024
|
+
return 0;
|
|
3025
|
+
}
|
|
3026
|
+
if (index < 0) {
|
|
3027
|
+
return itemCount - 1;
|
|
3028
|
+
}
|
|
3029
|
+
if (index >= itemCount) {
|
|
3030
|
+
return 0;
|
|
3031
|
+
}
|
|
3032
|
+
return index;
|
|
3033
|
+
}
|
|
3034
|
+
/**
|
|
3035
|
+
* Keep exactly one toolbar item in the page tab sequence
|
|
3036
|
+
*/
|
|
3037
|
+
updateTabIndexes() {
|
|
3038
|
+
this.getToolbarItems().forEach((item, index) => {
|
|
3039
|
+
item.tabIndex = index === this.currentItemIndex ? 0 : -1;
|
|
3040
|
+
});
|
|
3041
|
+
}
|
|
2918
3042
|
/**
|
|
2919
3043
|
* Create a toolbar separator
|
|
2920
3044
|
*/
|
|
@@ -2938,10 +3062,22 @@ ${blockSuffix}` : suffix;
|
|
|
2938
3062
|
if (buttonConfig.name === "viewMode") {
|
|
2939
3063
|
button.classList.add("has-dropdown");
|
|
2940
3064
|
button.dataset.dropdown = "true";
|
|
2941
|
-
button.
|
|
3065
|
+
button.setAttribute("aria-haspopup", "menu");
|
|
3066
|
+
button.setAttribute("aria-expanded", "false");
|
|
3067
|
+
button._clickHandler = (e) => {
|
|
2942
3068
|
e.preventDefault();
|
|
2943
3069
|
this.toggleViewModeDropdown(button);
|
|
2944
|
-
}
|
|
3070
|
+
};
|
|
3071
|
+
button._keydownHandler = (e) => {
|
|
3072
|
+
if (!["ArrowDown", "ArrowUp", "Enter", " "].includes(e.key)) {
|
|
3073
|
+
return;
|
|
3074
|
+
}
|
|
3075
|
+
e.preventDefault();
|
|
3076
|
+
const placement = e.key === "ArrowUp" ? "last" : "current";
|
|
3077
|
+
this.openViewModeDropdown(button, placement);
|
|
3078
|
+
};
|
|
3079
|
+
button.addEventListener("click", button._clickHandler);
|
|
3080
|
+
button.addEventListener("keydown", button._keydownHandler);
|
|
2945
3081
|
return button;
|
|
2946
3082
|
}
|
|
2947
3083
|
button._clickHandler = (e) => {
|
|
@@ -2996,12 +3132,17 @@ ${blockSuffix}` : suffix;
|
|
|
2996
3132
|
* Not exposed to users - viewMode button behavior is fixed
|
|
2997
3133
|
*/
|
|
2998
3134
|
toggleViewModeDropdown(button) {
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
existingDropdown.remove();
|
|
3002
|
-
button.classList.remove("dropdown-active");
|
|
3135
|
+
if (this.activeDropdown) {
|
|
3136
|
+
this.closeViewModeDropdown(button);
|
|
3003
3137
|
return;
|
|
3004
3138
|
}
|
|
3139
|
+
this.openViewModeDropdown(button);
|
|
3140
|
+
}
|
|
3141
|
+
/**
|
|
3142
|
+
* Open the view mode dropdown
|
|
3143
|
+
*/
|
|
3144
|
+
openViewModeDropdown(button, focusPlacement = null) {
|
|
3145
|
+
this.closeViewModeDropdown(button);
|
|
3005
3146
|
button.classList.add("dropdown-active");
|
|
3006
3147
|
const dropdown = this.createViewModeDropdown(button);
|
|
3007
3148
|
const rect = button.getBoundingClientRect();
|
|
@@ -3009,16 +3150,42 @@ ${blockSuffix}` : suffix;
|
|
|
3009
3150
|
dropdown.style.top = `${rect.bottom + 5}px`;
|
|
3010
3151
|
dropdown.style.left = `${rect.left}px`;
|
|
3011
3152
|
document.body.appendChild(dropdown);
|
|
3153
|
+
this.activeDropdown = dropdown;
|
|
3154
|
+
this.activeDropdownButton = button;
|
|
3155
|
+
button.setAttribute("aria-controls", dropdown.id);
|
|
3156
|
+
button.setAttribute("aria-expanded", "true");
|
|
3012
3157
|
this.handleDocumentClick = (e) => {
|
|
3013
3158
|
if (!dropdown.contains(e.target) && !button.contains(e.target)) {
|
|
3014
|
-
|
|
3015
|
-
button.classList.remove("dropdown-active");
|
|
3016
|
-
document.removeEventListener("click", this.handleDocumentClick);
|
|
3159
|
+
this.closeViewModeDropdown(button);
|
|
3017
3160
|
}
|
|
3018
3161
|
};
|
|
3019
3162
|
setTimeout(() => {
|
|
3020
3163
|
document.addEventListener("click", this.handleDocumentClick);
|
|
3021
3164
|
}, 0);
|
|
3165
|
+
if (focusPlacement) {
|
|
3166
|
+
this.focusViewModeMenuItem(dropdown, focusPlacement);
|
|
3167
|
+
}
|
|
3168
|
+
}
|
|
3169
|
+
/**
|
|
3170
|
+
* Close the view mode dropdown
|
|
3171
|
+
*/
|
|
3172
|
+
closeViewModeDropdown(button = this.activeDropdownButton, returnFocus = false) {
|
|
3173
|
+
if (this.activeDropdown) {
|
|
3174
|
+
this.activeDropdown.remove();
|
|
3175
|
+
this.activeDropdown = null;
|
|
3176
|
+
}
|
|
3177
|
+
if (button) {
|
|
3178
|
+
button.classList.remove("dropdown-active");
|
|
3179
|
+
button.setAttribute("aria-expanded", "false");
|
|
3180
|
+
}
|
|
3181
|
+
if (this.handleDocumentClick) {
|
|
3182
|
+
document.removeEventListener("click", this.handleDocumentClick);
|
|
3183
|
+
this.handleDocumentClick = null;
|
|
3184
|
+
}
|
|
3185
|
+
this.activeDropdownButton = null;
|
|
3186
|
+
if (returnFocus && button) {
|
|
3187
|
+
button.focus();
|
|
3188
|
+
}
|
|
3022
3189
|
}
|
|
3023
3190
|
/**
|
|
3024
3191
|
* Create view mode dropdown menu (internal implementation)
|
|
@@ -3026,6 +3193,12 @@ ${blockSuffix}` : suffix;
|
|
|
3026
3193
|
createViewModeDropdown(button) {
|
|
3027
3194
|
const dropdown = document.createElement("div");
|
|
3028
3195
|
dropdown.className = "overtype-dropdown-menu";
|
|
3196
|
+
dropdown.id = this.getInstanceElementId("toolbar-view-mode-menu");
|
|
3197
|
+
dropdown.setAttribute("role", "menu");
|
|
3198
|
+
dropdown.setAttribute("aria-label", "View mode");
|
|
3199
|
+
dropdown.addEventListener("keydown", (e) => {
|
|
3200
|
+
this.onViewModeMenuKeydown(e, button);
|
|
3201
|
+
});
|
|
3029
3202
|
const items = [
|
|
3030
3203
|
{ id: "normal", label: "Normal Edit", icon: "\u2713" },
|
|
3031
3204
|
{ id: "plain", label: "Plain Textarea", icon: "\u2713" },
|
|
@@ -3036,12 +3209,15 @@ ${blockSuffix}` : suffix;
|
|
|
3036
3209
|
const menuItem = document.createElement("button");
|
|
3037
3210
|
menuItem.className = "overtype-dropdown-item";
|
|
3038
3211
|
menuItem.type = "button";
|
|
3212
|
+
menuItem.tabIndex = -1;
|
|
3213
|
+
menuItem.setAttribute("role", "menuitemradio");
|
|
3214
|
+
menuItem.setAttribute("aria-checked", String(item.id === currentMode));
|
|
3039
3215
|
menuItem.textContent = item.label;
|
|
3040
3216
|
if (item.id === currentMode) {
|
|
3041
3217
|
menuItem.classList.add("active");
|
|
3042
|
-
menuItem.setAttribute("aria-current", "true");
|
|
3043
3218
|
const checkmark = document.createElement("span");
|
|
3044
3219
|
checkmark.className = "overtype-dropdown-icon";
|
|
3220
|
+
checkmark.setAttribute("aria-hidden", "true");
|
|
3045
3221
|
checkmark.textContent = item.icon;
|
|
3046
3222
|
menuItem.prepend(checkmark);
|
|
3047
3223
|
}
|
|
@@ -3059,14 +3235,77 @@ ${blockSuffix}` : suffix;
|
|
|
3059
3235
|
this.editor.showNormalEditMode();
|
|
3060
3236
|
break;
|
|
3061
3237
|
}
|
|
3062
|
-
|
|
3063
|
-
button.classList.remove("dropdown-active");
|
|
3064
|
-
document.removeEventListener("click", this.handleDocumentClick);
|
|
3238
|
+
this.closeViewModeDropdown(button, true);
|
|
3065
3239
|
});
|
|
3066
3240
|
dropdown.appendChild(menuItem);
|
|
3067
3241
|
});
|
|
3068
3242
|
return dropdown;
|
|
3069
3243
|
}
|
|
3244
|
+
/**
|
|
3245
|
+
* Handle keyboard navigation inside the view mode menu
|
|
3246
|
+
*/
|
|
3247
|
+
onViewModeMenuKeydown(e, button) {
|
|
3248
|
+
const menuItems = this.getViewModeMenuItems();
|
|
3249
|
+
const currentIndex = menuItems.indexOf(e.target);
|
|
3250
|
+
if (currentIndex === -1) {
|
|
3251
|
+
return;
|
|
3252
|
+
}
|
|
3253
|
+
switch (e.key) {
|
|
3254
|
+
case "ArrowDown":
|
|
3255
|
+
e.preventDefault();
|
|
3256
|
+
this.focusViewModeMenuItem(this.activeDropdown, currentIndex + 1);
|
|
3257
|
+
break;
|
|
3258
|
+
case "ArrowUp":
|
|
3259
|
+
e.preventDefault();
|
|
3260
|
+
this.focusViewModeMenuItem(this.activeDropdown, currentIndex - 1);
|
|
3261
|
+
break;
|
|
3262
|
+
case "Home":
|
|
3263
|
+
e.preventDefault();
|
|
3264
|
+
this.focusViewModeMenuItem(this.activeDropdown, "first");
|
|
3265
|
+
break;
|
|
3266
|
+
case "End":
|
|
3267
|
+
e.preventDefault();
|
|
3268
|
+
this.focusViewModeMenuItem(this.activeDropdown, "last");
|
|
3269
|
+
break;
|
|
3270
|
+
case "Escape":
|
|
3271
|
+
e.preventDefault();
|
|
3272
|
+
this.closeViewModeDropdown(button, true);
|
|
3273
|
+
break;
|
|
3274
|
+
}
|
|
3275
|
+
}
|
|
3276
|
+
/**
|
|
3277
|
+
* Focus a view mode menu item by index or placement
|
|
3278
|
+
*/
|
|
3279
|
+
focusViewModeMenuItem(dropdown, placement) {
|
|
3280
|
+
const menuItems = this.getViewModeMenuItems(dropdown);
|
|
3281
|
+
if (menuItems.length === 0) {
|
|
3282
|
+
return;
|
|
3283
|
+
}
|
|
3284
|
+
let index = placement;
|
|
3285
|
+
if (placement === "first") {
|
|
3286
|
+
index = 0;
|
|
3287
|
+
} else if (placement === "last") {
|
|
3288
|
+
index = menuItems.length - 1;
|
|
3289
|
+
} else if (placement === "current") {
|
|
3290
|
+
index = menuItems.findIndex((item) => item.getAttribute("aria-checked") === "true");
|
|
3291
|
+
}
|
|
3292
|
+
if (index < 0) {
|
|
3293
|
+
index = menuItems.length - 1;
|
|
3294
|
+
}
|
|
3295
|
+
if (index >= menuItems.length) {
|
|
3296
|
+
index = 0;
|
|
3297
|
+
}
|
|
3298
|
+
menuItems[index].focus();
|
|
3299
|
+
}
|
|
3300
|
+
/**
|
|
3301
|
+
* Get the current view mode menu items
|
|
3302
|
+
*/
|
|
3303
|
+
getViewModeMenuItems(dropdown = this.activeDropdown) {
|
|
3304
|
+
if (!dropdown) {
|
|
3305
|
+
return [];
|
|
3306
|
+
}
|
|
3307
|
+
return Array.from(dropdown.querySelectorAll('[role="menuitemradio"]'));
|
|
3308
|
+
}
|
|
3070
3309
|
/**
|
|
3071
3310
|
* Update active states of toolbar buttons
|
|
3072
3311
|
*/
|
|
@@ -3080,39 +3319,14 @@ ${blockSuffix}` : suffix;
|
|
|
3080
3319
|
Object.entries(this.buttons).forEach(([name, button]) => {
|
|
3081
3320
|
if (name === "viewMode")
|
|
3082
3321
|
return;
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
isActive = activeFormats.includes("bold");
|
|
3087
|
-
break;
|
|
3088
|
-
case "italic":
|
|
3089
|
-
isActive = activeFormats.includes("italic");
|
|
3090
|
-
break;
|
|
3091
|
-
case "code":
|
|
3092
|
-
isActive = false;
|
|
3093
|
-
break;
|
|
3094
|
-
case "bulletList":
|
|
3095
|
-
isActive = activeFormats.includes("bullet-list");
|
|
3096
|
-
break;
|
|
3097
|
-
case "orderedList":
|
|
3098
|
-
isActive = activeFormats.includes("numbered-list");
|
|
3099
|
-
break;
|
|
3100
|
-
case "taskList":
|
|
3101
|
-
isActive = activeFormats.includes("task-list");
|
|
3102
|
-
break;
|
|
3103
|
-
case "quote":
|
|
3104
|
-
isActive = activeFormats.includes("quote");
|
|
3105
|
-
break;
|
|
3106
|
-
case "h1":
|
|
3107
|
-
isActive = activeFormats.includes("header");
|
|
3108
|
-
break;
|
|
3109
|
-
case "h2":
|
|
3110
|
-
isActive = activeFormats.includes("header-2");
|
|
3111
|
-
break;
|
|
3112
|
-
case "h3":
|
|
3113
|
-
isActive = activeFormats.includes("header-3");
|
|
3114
|
-
break;
|
|
3322
|
+
const buttonConfig = this.toolbarButtons.find((buttonConfig2) => buttonConfig2.name === name);
|
|
3323
|
+
if (!(buttonConfig == null ? void 0 : buttonConfig.isActive)) {
|
|
3324
|
+
return;
|
|
3115
3325
|
}
|
|
3326
|
+
const isActive = Boolean(buttonConfig.isActive({
|
|
3327
|
+
editor: this.editor,
|
|
3328
|
+
activeFormats
|
|
3329
|
+
}));
|
|
3116
3330
|
button.classList.toggle("active", isActive);
|
|
3117
3331
|
button.setAttribute("aria-pressed", isActive.toString());
|
|
3118
3332
|
});
|
|
@@ -3134,14 +3348,21 @@ ${blockSuffix}` : suffix;
|
|
|
3134
3348
|
*/
|
|
3135
3349
|
destroy() {
|
|
3136
3350
|
if (this.container) {
|
|
3137
|
-
if (this.
|
|
3351
|
+
if (this.activeDropdown) {
|
|
3352
|
+
this.closeViewModeDropdown();
|
|
3353
|
+
} else if (this.handleDocumentClick) {
|
|
3138
3354
|
document.removeEventListener("click", this.handleDocumentClick);
|
|
3355
|
+
this.handleDocumentClick = null;
|
|
3139
3356
|
}
|
|
3140
3357
|
Object.values(this.buttons).forEach((button) => {
|
|
3141
3358
|
if (button._clickHandler) {
|
|
3142
3359
|
button.removeEventListener("click", button._clickHandler);
|
|
3143
3360
|
delete button._clickHandler;
|
|
3144
3361
|
}
|
|
3362
|
+
if (button._keydownHandler) {
|
|
3363
|
+
button.removeEventListener("keydown", button._keydownHandler);
|
|
3364
|
+
delete button._keydownHandler;
|
|
3365
|
+
}
|
|
3145
3366
|
});
|
|
3146
3367
|
this.container.remove();
|
|
3147
3368
|
this.container = null;
|
|
@@ -4408,7 +4629,10 @@ ${blockSuffix}` : suffix;
|
|
|
4408
4629
|
e.preventDefault();
|
|
4409
4630
|
e.stopPropagation();
|
|
4410
4631
|
if (this.currentLink) {
|
|
4411
|
-
|
|
4632
|
+
const safeUrl = MarkdownParser.sanitizeUrl(this.currentLink.url);
|
|
4633
|
+
if (safeUrl !== "#") {
|
|
4634
|
+
window.open(safeUrl, "_blank");
|
|
4635
|
+
}
|
|
4412
4636
|
this.hide();
|
|
4413
4637
|
}
|
|
4414
4638
|
});
|
|
@@ -4436,7 +4660,7 @@ ${blockSuffix}` : suffix;
|
|
|
4436
4660
|
if (position >= start && position <= end) {
|
|
4437
4661
|
return {
|
|
4438
4662
|
text: match[1],
|
|
4439
|
-
url: match[2],
|
|
4663
|
+
url: this.transformUrl(match[2]),
|
|
4440
4664
|
index: linkIndex,
|
|
4441
4665
|
start,
|
|
4442
4666
|
end
|
|
@@ -4446,6 +4670,18 @@ ${blockSuffix}` : suffix;
|
|
|
4446
4670
|
}
|
|
4447
4671
|
return null;
|
|
4448
4672
|
}
|
|
4673
|
+
transformUrl(url) {
|
|
4674
|
+
const transform = this.editor.options.transformLinkUrl;
|
|
4675
|
+
if (typeof transform !== "function")
|
|
4676
|
+
return url;
|
|
4677
|
+
try {
|
|
4678
|
+
const result = transform(url);
|
|
4679
|
+
return typeof result === "string" ? result : url;
|
|
4680
|
+
} catch (e) {
|
|
4681
|
+
console.warn("transformLinkUrl threw:", e);
|
|
4682
|
+
return url;
|
|
4683
|
+
}
|
|
4684
|
+
}
|
|
4449
4685
|
async show(linkInfo) {
|
|
4450
4686
|
this.currentLink = linkInfo;
|
|
4451
4687
|
this.cancelHide();
|
|
@@ -4597,6 +4833,7 @@ ${blockSuffix}` : suffix;
|
|
|
4597
4833
|
actionId: "toggleBold",
|
|
4598
4834
|
icon: boldIcon,
|
|
4599
4835
|
title: "Bold (Ctrl+B)",
|
|
4836
|
+
isActive: ({ activeFormats }) => activeFormats.includes("bold"),
|
|
4600
4837
|
action: ({ editor }) => {
|
|
4601
4838
|
toggleBold(editor.textarea);
|
|
4602
4839
|
editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
|
|
@@ -4607,6 +4844,7 @@ ${blockSuffix}` : suffix;
|
|
|
4607
4844
|
actionId: "toggleItalic",
|
|
4608
4845
|
icon: italicIcon,
|
|
4609
4846
|
title: "Italic (Ctrl+I)",
|
|
4847
|
+
isActive: ({ activeFormats }) => activeFormats.includes("italic"),
|
|
4610
4848
|
action: ({ editor }) => {
|
|
4611
4849
|
toggleItalic(editor.textarea);
|
|
4612
4850
|
editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
|
|
@@ -4617,6 +4855,7 @@ ${blockSuffix}` : suffix;
|
|
|
4617
4855
|
actionId: "toggleCode",
|
|
4618
4856
|
icon: codeIcon,
|
|
4619
4857
|
title: "Inline Code",
|
|
4858
|
+
isActive: () => false,
|
|
4620
4859
|
action: ({ editor }) => {
|
|
4621
4860
|
toggleCode(editor.textarea);
|
|
4622
4861
|
editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
|
|
@@ -4641,6 +4880,7 @@ ${blockSuffix}` : suffix;
|
|
|
4641
4880
|
actionId: "toggleH1",
|
|
4642
4881
|
icon: h1Icon,
|
|
4643
4882
|
title: "Heading 1",
|
|
4883
|
+
isActive: ({ activeFormats }) => activeFormats.includes("header"),
|
|
4644
4884
|
action: ({ editor }) => {
|
|
4645
4885
|
toggleH1(editor.textarea);
|
|
4646
4886
|
editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
|
|
@@ -4651,6 +4891,7 @@ ${blockSuffix}` : suffix;
|
|
|
4651
4891
|
actionId: "toggleH2",
|
|
4652
4892
|
icon: h2Icon,
|
|
4653
4893
|
title: "Heading 2",
|
|
4894
|
+
isActive: ({ activeFormats }) => activeFormats.includes("header-2"),
|
|
4654
4895
|
action: ({ editor }) => {
|
|
4655
4896
|
toggleH2(editor.textarea);
|
|
4656
4897
|
editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
|
|
@@ -4661,6 +4902,7 @@ ${blockSuffix}` : suffix;
|
|
|
4661
4902
|
actionId: "toggleH3",
|
|
4662
4903
|
icon: h3Icon,
|
|
4663
4904
|
title: "Heading 3",
|
|
4905
|
+
isActive: ({ activeFormats }) => activeFormats.includes("header-3"),
|
|
4664
4906
|
action: ({ editor }) => {
|
|
4665
4907
|
toggleH3(editor.textarea);
|
|
4666
4908
|
editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
|
|
@@ -4671,6 +4913,7 @@ ${blockSuffix}` : suffix;
|
|
|
4671
4913
|
actionId: "toggleBulletList",
|
|
4672
4914
|
icon: bulletListIcon,
|
|
4673
4915
|
title: "Bullet List",
|
|
4916
|
+
isActive: ({ activeFormats }) => activeFormats.includes("bullet-list"),
|
|
4674
4917
|
action: ({ editor }) => {
|
|
4675
4918
|
toggleBulletList(editor.textarea);
|
|
4676
4919
|
editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
|
|
@@ -4681,6 +4924,7 @@ ${blockSuffix}` : suffix;
|
|
|
4681
4924
|
actionId: "toggleNumberedList",
|
|
4682
4925
|
icon: orderedListIcon,
|
|
4683
4926
|
title: "Numbered List",
|
|
4927
|
+
isActive: ({ activeFormats }) => activeFormats.includes("numbered-list"),
|
|
4684
4928
|
action: ({ editor }) => {
|
|
4685
4929
|
toggleNumberedList(editor.textarea);
|
|
4686
4930
|
editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
|
|
@@ -4691,6 +4935,7 @@ ${blockSuffix}` : suffix;
|
|
|
4691
4935
|
actionId: "toggleTaskList",
|
|
4692
4936
|
icon: taskListIcon,
|
|
4693
4937
|
title: "Task List",
|
|
4938
|
+
isActive: ({ activeFormats }) => activeFormats.includes("task-list"),
|
|
4694
4939
|
action: ({ editor }) => {
|
|
4695
4940
|
if (toggleTaskList) {
|
|
4696
4941
|
toggleTaskList(editor.textarea);
|
|
@@ -4703,6 +4948,7 @@ ${blockSuffix}` : suffix;
|
|
|
4703
4948
|
actionId: "toggleQuote",
|
|
4704
4949
|
icon: quoteIcon,
|
|
4705
4950
|
title: "Quote",
|
|
4951
|
+
isActive: ({ activeFormats }) => activeFormats.includes("quote"),
|
|
4706
4952
|
action: ({ editor }) => {
|
|
4707
4953
|
toggleQuote(editor.textarea);
|
|
4708
4954
|
editor.textarea.dispatchEvent(new Event("input", { bubbles: true }));
|
|
@@ -4764,6 +5010,17 @@ ${blockSuffix}` : suffix;
|
|
|
4764
5010
|
];
|
|
4765
5011
|
|
|
4766
5012
|
// src/overtype.js
|
|
5013
|
+
var _isSafariCache;
|
|
5014
|
+
function isSafariBrowser() {
|
|
5015
|
+
if (_isSafariCache !== void 0)
|
|
5016
|
+
return _isSafariCache;
|
|
5017
|
+
_isSafariCache = false;
|
|
5018
|
+
if (typeof navigator !== "undefined") {
|
|
5019
|
+
const ua = navigator.userAgent || "";
|
|
5020
|
+
_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);
|
|
5021
|
+
}
|
|
5022
|
+
return _isSafariCache;
|
|
5023
|
+
}
|
|
4767
5024
|
function buildActionsMap(buttons) {
|
|
4768
5025
|
const map = {};
|
|
4769
5026
|
(buttons || []).forEach((btn) => {
|
|
@@ -4838,6 +5095,8 @@ ${blockSuffix}` : suffix;
|
|
|
4838
5095
|
this.options = this._mergeOptions(options);
|
|
4839
5096
|
this.instanceId = ++_OverType.instanceCount;
|
|
4840
5097
|
this.initialized = false;
|
|
5098
|
+
this._isSafari = isSafariBrowser();
|
|
5099
|
+
this._safariReflowRaf = null;
|
|
4841
5100
|
_OverType.injectStyles();
|
|
4842
5101
|
_OverType.initGlobalListeners();
|
|
4843
5102
|
const container = element.querySelector(".overtype-container");
|
|
@@ -4912,8 +5171,10 @@ ${blockSuffix}` : suffix;
|
|
|
4912
5171
|
// Enable smart list continuation
|
|
4913
5172
|
codeHighlighter: null,
|
|
4914
5173
|
// Per-instance code highlighter
|
|
4915
|
-
spellcheck: false
|
|
5174
|
+
spellcheck: false,
|
|
4916
5175
|
// Browser spellcheck (disabled by default)
|
|
5176
|
+
transformLinkUrl: null
|
|
5177
|
+
// Transform URLs shown/opened in the link tooltip
|
|
4917
5178
|
};
|
|
4918
5179
|
const { theme, colors, ...cleanOptions } = options;
|
|
4919
5180
|
return {
|
|
@@ -4966,6 +5227,8 @@ ${blockSuffix}` : suffix;
|
|
|
4966
5227
|
this.wrapper._instance = this;
|
|
4967
5228
|
this._applyInstanceCSSVars();
|
|
4968
5229
|
this._configureTextarea();
|
|
5230
|
+
this._ensureTextareaId();
|
|
5231
|
+
this._syncPreviewInteractivity();
|
|
4969
5232
|
this._applyOptions();
|
|
4970
5233
|
}
|
|
4971
5234
|
/**
|
|
@@ -5029,6 +5292,7 @@ ${blockSuffix}` : suffix;
|
|
|
5029
5292
|
}
|
|
5030
5293
|
});
|
|
5031
5294
|
}
|
|
5295
|
+
this._ensureTextareaId();
|
|
5032
5296
|
this.preview = document.createElement("div");
|
|
5033
5297
|
this.preview.className = "overtype-preview";
|
|
5034
5298
|
this.preview.setAttribute("aria-hidden", "true");
|
|
@@ -5052,6 +5316,7 @@ ${blockSuffix}` : suffix;
|
|
|
5052
5316
|
} else {
|
|
5053
5317
|
this.container.classList.remove("overtype-auto-resize");
|
|
5054
5318
|
}
|
|
5319
|
+
this._syncPreviewInteractivity();
|
|
5055
5320
|
}
|
|
5056
5321
|
/**
|
|
5057
5322
|
* Configure textarea attributes
|
|
@@ -5066,6 +5331,31 @@ ${blockSuffix}` : suffix;
|
|
|
5066
5331
|
this.textarea.setAttribute("data-gramm_editor", "false");
|
|
5067
5332
|
this.textarea.setAttribute("data-enable-grammarly", "false");
|
|
5068
5333
|
}
|
|
5334
|
+
/**
|
|
5335
|
+
* Ensure the textarea can be referenced by aria-controls
|
|
5336
|
+
* @private
|
|
5337
|
+
*/
|
|
5338
|
+
_ensureTextareaId() {
|
|
5339
|
+
if (!this.textarea.id) {
|
|
5340
|
+
this.textarea.id = `overtype-${this.instanceId}-input`;
|
|
5341
|
+
}
|
|
5342
|
+
}
|
|
5343
|
+
/**
|
|
5344
|
+
* Keep rendered preview content out of keyboard navigation until Preview mode.
|
|
5345
|
+
* @private
|
|
5346
|
+
*/
|
|
5347
|
+
_syncPreviewInteractivity() {
|
|
5348
|
+
if (!this.preview || !this.container)
|
|
5349
|
+
return;
|
|
5350
|
+
const isPreviewMode = this.container.dataset.mode === "preview";
|
|
5351
|
+
this.preview.inert = !isPreviewMode;
|
|
5352
|
+
this.preview.toggleAttribute("inert", !isPreviewMode);
|
|
5353
|
+
if (isPreviewMode) {
|
|
5354
|
+
this.preview.removeAttribute("aria-hidden");
|
|
5355
|
+
return;
|
|
5356
|
+
}
|
|
5357
|
+
this.preview.setAttribute("aria-hidden", "true");
|
|
5358
|
+
}
|
|
5069
5359
|
/**
|
|
5070
5360
|
* Create and setup toolbar
|
|
5071
5361
|
* @private
|
|
@@ -5195,6 +5485,7 @@ ${blockSuffix}` : suffix;
|
|
|
5195
5485
|
return;
|
|
5196
5486
|
}
|
|
5197
5487
|
this._fileUploadCounter = 0;
|
|
5488
|
+
this._uploadedFiles = /* @__PURE__ */ new Map();
|
|
5198
5489
|
this._boundHandleFilePaste = this._handleFilePaste.bind(this);
|
|
5199
5490
|
this._boundHandleFileDrop = this._handleFileDrop.bind(this);
|
|
5200
5491
|
this._boundHandleDragOver = this._handleDragOver.bind(this);
|
|
@@ -5203,6 +5494,53 @@ ${blockSuffix}` : suffix;
|
|
|
5203
5494
|
this.textarea.addEventListener("dragover", this._boundHandleDragOver);
|
|
5204
5495
|
this.fileUploadInitialized = true;
|
|
5205
5496
|
}
|
|
5497
|
+
/**
|
|
5498
|
+
* Extract URLs from markdown link syntax: [text](url) or .
|
|
5499
|
+
* @private
|
|
5500
|
+
*/
|
|
5501
|
+
_extractMarkdownUrls(text) {
|
|
5502
|
+
const urls = [];
|
|
5503
|
+
const re = /!?\[[^\]]*\]\(([^)\s]+)/g;
|
|
5504
|
+
let m;
|
|
5505
|
+
while ((m = re.exec(text)) !== null)
|
|
5506
|
+
urls.push(m[1]);
|
|
5507
|
+
return urls;
|
|
5508
|
+
}
|
|
5509
|
+
/**
|
|
5510
|
+
* Track URLs that were just inserted, pairing each with the source File.
|
|
5511
|
+
* If multiple URLs appear in one inserted block, all get associated with
|
|
5512
|
+
* the same file (rare; happens if onInsertFile returns several links).
|
|
5513
|
+
* @private
|
|
5514
|
+
*/
|
|
5515
|
+
_trackInsertedUrls(insertedText, file) {
|
|
5516
|
+
if (!this._uploadedFiles || !file || !insertedText)
|
|
5517
|
+
return;
|
|
5518
|
+
for (const url of this._extractMarkdownUrls(insertedText)) {
|
|
5519
|
+
this._uploadedFiles.set(url, { filename: file.name, file });
|
|
5520
|
+
}
|
|
5521
|
+
}
|
|
5522
|
+
/**
|
|
5523
|
+
* Diff the tracked-URL set against the current value and fire
|
|
5524
|
+
* fileUpload.onRemoveFile for any URL no longer present.
|
|
5525
|
+
* @private
|
|
5526
|
+
*/
|
|
5527
|
+
_checkForRemovedUploads() {
|
|
5528
|
+
var _a;
|
|
5529
|
+
if (!this._uploadedFiles || this._uploadedFiles.size === 0)
|
|
5530
|
+
return;
|
|
5531
|
+
const cb = (_a = this.options.fileUpload) == null ? void 0 : _a.onRemoveFile;
|
|
5532
|
+
const value = this.textarea.value;
|
|
5533
|
+
const removed = [];
|
|
5534
|
+
for (const [url, info] of this._uploadedFiles) {
|
|
5535
|
+
if (!value.includes(url))
|
|
5536
|
+
removed.push({ url, info });
|
|
5537
|
+
}
|
|
5538
|
+
for (const { url, info } of removed) {
|
|
5539
|
+
this._uploadedFiles.delete(url);
|
|
5540
|
+
if (cb)
|
|
5541
|
+
cb({ url, filename: info.filename, file: info.file });
|
|
5542
|
+
}
|
|
5543
|
+
}
|
|
5206
5544
|
_handleFilePaste(e) {
|
|
5207
5545
|
var _a, _b;
|
|
5208
5546
|
if (!((_b = (_a = e == null ? void 0 : e.clipboardData) == null ? void 0 : _a.files) == null ? void 0 : _b.length))
|
|
@@ -5235,6 +5573,7 @@ ${blockSuffix}` : suffix;
|
|
|
5235
5573
|
}
|
|
5236
5574
|
this.options.fileUpload.onInsertFile(file).then((text) => {
|
|
5237
5575
|
this.textarea.value = this.textarea.value.replace(placeholder, text);
|
|
5576
|
+
this._trackInsertedUrls(text, file);
|
|
5238
5577
|
this.textarea.dispatchEvent(new Event("input", { bubbles: true }));
|
|
5239
5578
|
}, (error) => {
|
|
5240
5579
|
console.error("OverType: File upload failed", error);
|
|
@@ -5247,6 +5586,7 @@ ${blockSuffix}` : suffix;
|
|
|
5247
5586
|
const texts = Array.isArray(result) ? result : [result];
|
|
5248
5587
|
texts.forEach((text, index) => {
|
|
5249
5588
|
this.textarea.value = this.textarea.value.replace(files[index].placeholder, text);
|
|
5589
|
+
this._trackInsertedUrls(text, files[index].file);
|
|
5250
5590
|
});
|
|
5251
5591
|
this.textarea.dispatchEvent(new Event("input", { bubbles: true }));
|
|
5252
5592
|
}, (error) => {
|
|
@@ -5268,6 +5608,7 @@ ${blockSuffix}` : suffix;
|
|
|
5268
5608
|
this._boundHandleFilePaste = null;
|
|
5269
5609
|
this._boundHandleFileDrop = null;
|
|
5270
5610
|
this._boundHandleDragOver = null;
|
|
5611
|
+
this._uploadedFiles = null;
|
|
5271
5612
|
this.fileUploadInitialized = false;
|
|
5272
5613
|
}
|
|
5273
5614
|
insertAtCursor(text) {
|
|
@@ -5312,9 +5653,12 @@ ${blockSuffix}` : suffix;
|
|
|
5312
5653
|
* @private
|
|
5313
5654
|
*/
|
|
5314
5655
|
_notifyChange() {
|
|
5315
|
-
if (!this.
|
|
5656
|
+
if (!this.initialized)
|
|
5316
5657
|
return;
|
|
5317
|
-
this.
|
|
5658
|
+
this._checkForRemovedUploads();
|
|
5659
|
+
if (this.options.onChange) {
|
|
5660
|
+
this.options.onChange(this.textarea.value, this);
|
|
5661
|
+
}
|
|
5318
5662
|
}
|
|
5319
5663
|
/**
|
|
5320
5664
|
* Apply background styling to code blocks
|
|
@@ -5350,6 +5694,29 @@ ${blockSuffix}` : suffix;
|
|
|
5350
5694
|
handleInput(event) {
|
|
5351
5695
|
this.updatePreview();
|
|
5352
5696
|
this._notifyChange();
|
|
5697
|
+
this._scheduleSafariReflow();
|
|
5698
|
+
}
|
|
5699
|
+
/**
|
|
5700
|
+
* Force Safari to re-shape stale textarea text after an edit.
|
|
5701
|
+
* Safari can leave a textarea's glyph layout cached after incremental edits,
|
|
5702
|
+
* desyncing the caret/wrap from the styled preview overlay. Toggling
|
|
5703
|
+
* letter-spacing (with !important to beat the stylesheet rule) and reading
|
|
5704
|
+
* offsetHeight forces a synchronous re-shape. Safari-only, coalesced to one
|
|
5705
|
+
* run per animation frame.
|
|
5706
|
+
* @private
|
|
5707
|
+
*/
|
|
5708
|
+
_scheduleSafariReflow() {
|
|
5709
|
+
if (!this._isSafari || this._safariReflowRaf)
|
|
5710
|
+
return;
|
|
5711
|
+
this._safariReflowRaf = requestAnimationFrame(() => {
|
|
5712
|
+
this._safariReflowRaf = null;
|
|
5713
|
+
const ta = this.textarea;
|
|
5714
|
+
if (!ta)
|
|
5715
|
+
return;
|
|
5716
|
+
ta.style.setProperty("letter-spacing", "-0.001px", "important");
|
|
5717
|
+
void ta.offsetHeight;
|
|
5718
|
+
ta.style.removeProperty("letter-spacing");
|
|
5719
|
+
});
|
|
5353
5720
|
}
|
|
5354
5721
|
/**
|
|
5355
5722
|
* Handle focus events
|
|
@@ -5377,49 +5744,11 @@ ${blockSuffix}` : suffix;
|
|
|
5377
5744
|
if (event.key === "Tab") {
|
|
5378
5745
|
const start = this.textarea.selectionStart;
|
|
5379
5746
|
const end = this.textarea.selectionEnd;
|
|
5380
|
-
|
|
5381
|
-
|
|
5747
|
+
if (start !== end && this._canEditTextarea()) {
|
|
5748
|
+
event.preventDefault();
|
|
5749
|
+
event.shiftKey ? this.outdentSelection() : this.indentSelection();
|
|
5382
5750
|
return;
|
|
5383
5751
|
}
|
|
5384
|
-
event.preventDefault();
|
|
5385
|
-
if (start !== end && event.shiftKey) {
|
|
5386
|
-
const before = value.substring(0, start);
|
|
5387
|
-
const selection = value.substring(start, end);
|
|
5388
|
-
const after = value.substring(end);
|
|
5389
|
-
const lines = selection.split("\n");
|
|
5390
|
-
const outdented = lines.map((line) => line.replace(/^ /, "")).join("\n");
|
|
5391
|
-
if (document.execCommand) {
|
|
5392
|
-
this.textarea.setSelectionRange(start, end);
|
|
5393
|
-
document.execCommand("insertText", false, outdented);
|
|
5394
|
-
} else {
|
|
5395
|
-
this.textarea.value = before + outdented + after;
|
|
5396
|
-
this.textarea.selectionStart = start;
|
|
5397
|
-
this.textarea.selectionEnd = start + outdented.length;
|
|
5398
|
-
}
|
|
5399
|
-
} else if (start !== end) {
|
|
5400
|
-
const before = value.substring(0, start);
|
|
5401
|
-
const selection = value.substring(start, end);
|
|
5402
|
-
const after = value.substring(end);
|
|
5403
|
-
const lines = selection.split("\n");
|
|
5404
|
-
const indented = lines.map((line) => " " + line).join("\n");
|
|
5405
|
-
if (document.execCommand) {
|
|
5406
|
-
this.textarea.setSelectionRange(start, end);
|
|
5407
|
-
document.execCommand("insertText", false, indented);
|
|
5408
|
-
} else {
|
|
5409
|
-
this.textarea.value = before + indented + after;
|
|
5410
|
-
this.textarea.selectionStart = start;
|
|
5411
|
-
this.textarea.selectionEnd = start + indented.length;
|
|
5412
|
-
}
|
|
5413
|
-
} else {
|
|
5414
|
-
if (document.execCommand) {
|
|
5415
|
-
document.execCommand("insertText", false, " ");
|
|
5416
|
-
} else {
|
|
5417
|
-
this.textarea.value = value.substring(0, start) + " " + value.substring(end);
|
|
5418
|
-
this.textarea.selectionStart = this.textarea.selectionEnd = start + 2;
|
|
5419
|
-
}
|
|
5420
|
-
}
|
|
5421
|
-
this.textarea.dispatchEvent(new Event("input", { bubbles: true }));
|
|
5422
|
-
return;
|
|
5423
5752
|
}
|
|
5424
5753
|
if (event.key === "Enter" && !event.shiftKey && !event.metaKey && !event.ctrlKey && this.options.smartLists) {
|
|
5425
5754
|
if (this.handleSmartListContinuation()) {
|
|
@@ -5556,6 +5885,7 @@ ${blockSuffix}` : suffix;
|
|
|
5556
5885
|
}
|
|
5557
5886
|
if (didChange) {
|
|
5558
5887
|
this._notifyChange();
|
|
5888
|
+
this._scheduleSafariReflow();
|
|
5559
5889
|
}
|
|
5560
5890
|
}
|
|
5561
5891
|
/**
|
|
@@ -5616,6 +5946,63 @@ ${blockSuffix}` : suffix;
|
|
|
5616
5946
|
getPreviewHTML() {
|
|
5617
5947
|
return this.preview.innerHTML;
|
|
5618
5948
|
}
|
|
5949
|
+
/**
|
|
5950
|
+
* Indent the current line or selected lines by two spaces.
|
|
5951
|
+
*/
|
|
5952
|
+
indentSelection() {
|
|
5953
|
+
this._replaceSelectedLines((line) => ` ${line}`);
|
|
5954
|
+
}
|
|
5955
|
+
/**
|
|
5956
|
+
* Outdent the current line or selected lines by up to two spaces or one tab.
|
|
5957
|
+
*/
|
|
5958
|
+
outdentSelection() {
|
|
5959
|
+
this._replaceSelectedLines((line) => line.replace(/^( {1,2}|\t)/, ""));
|
|
5960
|
+
}
|
|
5961
|
+
/**
|
|
5962
|
+
* Replace full lines touched by the current selection.
|
|
5963
|
+
* @private
|
|
5964
|
+
*/
|
|
5965
|
+
_replaceSelectedLines(transformLine) {
|
|
5966
|
+
if (!this._canEditTextarea())
|
|
5967
|
+
return false;
|
|
5968
|
+
const textarea = this.textarea;
|
|
5969
|
+
const { selectionStart, selectionEnd, value } = textarea;
|
|
5970
|
+
const lineStart = value.lastIndexOf("\n", selectionStart - 1) + 1;
|
|
5971
|
+
const effectiveEnd = this._effectiveSelectionEnd(value, selectionStart, selectionEnd);
|
|
5972
|
+
const lineEndOffset = value.indexOf("\n", effectiveEnd);
|
|
5973
|
+
const lineEnd = lineEndOffset === -1 ? value.length : lineEndOffset;
|
|
5974
|
+
const selectedLines = value.slice(lineStart, lineEnd);
|
|
5975
|
+
const replacement = selectedLines.split("\n").map(transformLine).join("\n");
|
|
5976
|
+
if (replacement === selectedLines)
|
|
5977
|
+
return false;
|
|
5978
|
+
textarea.setSelectionRange(lineStart, lineEnd);
|
|
5979
|
+
let inserted = false;
|
|
5980
|
+
try {
|
|
5981
|
+
inserted = document.execCommand("insertText", false, replacement);
|
|
5982
|
+
} catch (_) {
|
|
5983
|
+
}
|
|
5984
|
+
if (!inserted) {
|
|
5985
|
+
textarea.setRangeText(replacement, lineStart, lineEnd, "preserve");
|
|
5986
|
+
}
|
|
5987
|
+
textarea.setSelectionRange(lineStart, lineStart + replacement.length);
|
|
5988
|
+
textarea.dispatchEvent(new Event("input", { bubbles: true }));
|
|
5989
|
+
return true;
|
|
5990
|
+
}
|
|
5991
|
+
/**
|
|
5992
|
+
* @private
|
|
5993
|
+
*/
|
|
5994
|
+
_effectiveSelectionEnd(value, selectionStart, selectionEnd) {
|
|
5995
|
+
if (selectionEnd > selectionStart && value[selectionEnd - 1] === "\n") {
|
|
5996
|
+
return selectionEnd - 1;
|
|
5997
|
+
}
|
|
5998
|
+
return selectionEnd;
|
|
5999
|
+
}
|
|
6000
|
+
/**
|
|
6001
|
+
* @private
|
|
6002
|
+
*/
|
|
6003
|
+
_canEditTextarea() {
|
|
6004
|
+
return this.textarea && !this.textarea.disabled && !this.textarea.readOnly;
|
|
6005
|
+
}
|
|
5619
6006
|
/**
|
|
5620
6007
|
* Get clean HTML without any OverType-specific markup
|
|
5621
6008
|
* Useful for exporting to other formats or storage
|
|
@@ -5839,6 +6226,7 @@ ${blockSuffix}` : suffix;
|
|
|
5839
6226
|
*/
|
|
5840
6227
|
showNormalEditMode() {
|
|
5841
6228
|
this.container.dataset.mode = "normal";
|
|
6229
|
+
this._syncPreviewInteractivity();
|
|
5842
6230
|
this.updatePreview();
|
|
5843
6231
|
this._updateAutoHeight();
|
|
5844
6232
|
requestAnimationFrame(() => {
|
|
@@ -5853,6 +6241,7 @@ ${blockSuffix}` : suffix;
|
|
|
5853
6241
|
*/
|
|
5854
6242
|
showPlainTextarea() {
|
|
5855
6243
|
this.container.dataset.mode = "plain";
|
|
6244
|
+
this._syncPreviewInteractivity();
|
|
5856
6245
|
this._updateAutoHeight();
|
|
5857
6246
|
if (this.toolbar) {
|
|
5858
6247
|
const toggleBtn = this.container.querySelector('[data-action="toggle-plain"]');
|
|
@@ -5869,6 +6258,7 @@ ${blockSuffix}` : suffix;
|
|
|
5869
6258
|
*/
|
|
5870
6259
|
showPreviewMode() {
|
|
5871
6260
|
this.container.dataset.mode = "preview";
|
|
6261
|
+
this._syncPreviewInteractivity();
|
|
5872
6262
|
this.updatePreview();
|
|
5873
6263
|
this._updateAutoHeight();
|
|
5874
6264
|
return this;
|
|
@@ -5887,6 +6277,10 @@ ${blockSuffix}` : suffix;
|
|
|
5887
6277
|
if (this.shortcuts) {
|
|
5888
6278
|
this.shortcuts.destroy();
|
|
5889
6279
|
}
|
|
6280
|
+
if (this._safariReflowRaf) {
|
|
6281
|
+
cancelAnimationFrame(this._safariReflowRaf);
|
|
6282
|
+
this._safariReflowRaf = null;
|
|
6283
|
+
}
|
|
5890
6284
|
if (this.wrapper) {
|
|
5891
6285
|
const content = this.getValue();
|
|
5892
6286
|
this.wrapper.remove();
|
|
@@ -5918,11 +6312,16 @@ ${blockSuffix}` : suffix;
|
|
|
5918
6312
|
return elements.map((el) => {
|
|
5919
6313
|
const options = { ...defaults };
|
|
5920
6314
|
for (const attr of el.attributes) {
|
|
5921
|
-
if (attr.name.startsWith("data-ot-"))
|
|
5922
|
-
|
|
5923
|
-
|
|
5924
|
-
|
|
6315
|
+
if (!attr.name.startsWith("data-ot-"))
|
|
6316
|
+
continue;
|
|
6317
|
+
const kebab = attr.name.slice(8);
|
|
6318
|
+
if (kebab.startsWith("textarea-") && kebab !== "textarea-props") {
|
|
6319
|
+
const propKey = kebab.slice(9).replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
6320
|
+
options.textareaProps = { ...options.textareaProps || {}, [propKey]: _OverType._parseDataValue(attr.value) };
|
|
6321
|
+
continue;
|
|
5925
6322
|
}
|
|
6323
|
+
const key = kebab.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
6324
|
+
options[key] = _OverType._parseDataValue(attr.value);
|
|
5926
6325
|
}
|
|
5927
6326
|
return new _OverType(el, options)[0];
|
|
5928
6327
|
});
|
|
@@ -5967,6 +6366,14 @@ ${blockSuffix}` : suffix;
|
|
|
5967
6366
|
return null;
|
|
5968
6367
|
if (value !== "" && !isNaN(Number(value)))
|
|
5969
6368
|
return Number(value);
|
|
6369
|
+
const trimmed = value.trim();
|
|
6370
|
+
if (trimmed[0] === "{" || trimmed[0] === "[") {
|
|
6371
|
+
try {
|
|
6372
|
+
return JSON.parse(trimmed);
|
|
6373
|
+
} catch (e) {
|
|
6374
|
+
return value;
|
|
6375
|
+
}
|
|
6376
|
+
}
|
|
5970
6377
|
return value;
|
|
5971
6378
|
}
|
|
5972
6379
|
/**
|
|
@@ -6145,8 +6552,13 @@ ${blockSuffix}` : suffix;
|
|
|
6145
6552
|
* Initialize global event listeners
|
|
6146
6553
|
*/
|
|
6147
6554
|
static initGlobalListeners() {
|
|
6148
|
-
|
|
6555
|
+
const globalScope = typeof window !== "undefined" ? window : globalThis;
|
|
6556
|
+
const globalListenersKey = "__overtypeGlobalListenersInitialized";
|
|
6557
|
+
if (_OverType.globalListenersInitialized || globalScope[globalListenersKey]) {
|
|
6558
|
+
_OverType.globalListenersInitialized = true;
|
|
6149
6559
|
return;
|
|
6560
|
+
}
|
|
6561
|
+
globalScope[globalListenersKey] = true;
|
|
6150
6562
|
document.addEventListener("input", (e) => {
|
|
6151
6563
|
if (e.target && e.target.classList && e.target.classList.contains("overtype-input")) {
|
|
6152
6564
|
const wrapper = e.target.closest(".overtype-wrapper");
|