suneditor
Version:
Vanilla JavaScript WYSIWYG web editor (2.x legacy version, actively maintained)
903 lines (780 loc) • 45.9 kB
JavaScript
/*
* wysiwyg web editor
*
* suneditor.js
* Copyright 2017 JiHong Lee.
* MIT license.
*/
'use strict';
(function (global, factory) {
if (typeof module === 'object' && typeof module.exports === 'object') {
module.exports = global.document ?
factory(global, true) :
function (w) {
if (!w.document) {
throw new Error('SUNEDITOR_MODULES a window with a document');
}
return factory(w);
};
} else {
factory(global);
}
}(typeof window !== 'undefined' ? window : this, function (window, noGlobal) {
const resizing = {
name: 'resizing',
/**
* @description Constructor
* Require context properties when resizing module
inputX: Element,
inputY: Element,
_container: null,
_cover: null,
_element: null,
_element_w: 1,
_element_h: 1,
_element_l: 0,
_element_t: 0,
_defaultSizeX: 'auto',
_defaultSizeY: 'auto',
_origin_w: core.options.imageWidth === 'auto' ? '' : core.options.imageWidth,
_origin_h: core.options.imageHeight === 'auto' ? '' : core.options.imageHeight,
_proportionChecked: true,
// -- select function --
_resizing: core.options.imageResizing,
_resizeDotHide: !core.options.imageHeightShow,
_rotation: core.options.imageRotation,
_onlyPercentage: core.options.imageSizeOnlyPercentage,
_ratio: false,
_ratioX: 1,
_ratioY: 1
_captionShow: true,
// -- when used caption (_captionShow: true) --
_caption: null,
_captionChecked: false,
captionCheckEl: null,
* @param {Object} core Core object
*/
add: function (core) {
const icons = core.icons;
const context = core.context;
context.resizing = {
_resizeClientX: 0,
_resizeClientY: 0,
_resize_plugin: '',
_resize_w: 0,
_resize_h: 0,
_origin_w: 0,
_origin_h: 0,
_rotateVertical: false,
_resize_direction: '',
_move_path: null,
_isChange: false,
alignIcons: {
basic: icons.align_justify,
left: icons.align_left,
right: icons.align_right,
center: icons.align_center
}
};
/** resize controller, button */
let resize_div_container = this.setController_resize(core);
context.resizing.resizeContainer = resize_div_container;
context.resizing.resizeDiv = resize_div_container.querySelector('.se-modal-resize');
context.resizing.resizeDot = resize_div_container.querySelector('.se-resize-dot');
context.resizing.resizeDisplay = resize_div_container.querySelector('.se-resize-display');
let resize_button = this.setController_button(core);
context.resizing.resizeButton = resize_button;
let resize_handles = context.resizing.resizeHandles = context.resizing.resizeDot.querySelectorAll('span');
context.resizing.resizeButtonGroup = resize_button.querySelector('._se_resizing_btn_group');
context.resizing.rotationButtons = resize_button.querySelectorAll('._se_resizing_btn_group ._se_rotation');
context.resizing.percentageButtons = resize_button.querySelectorAll('._se_resizing_btn_group ._se_percentage');
context.resizing.alignMenu = resize_button.querySelector('.se-resizing-align-list');
context.resizing.alignMenuList = context.resizing.alignMenu.querySelectorAll('button');
context.resizing.alignButton = resize_button.querySelector('._se_resizing_align_button');
context.resizing.autoSizeButton = resize_button.querySelector('._se_resizing_btn_group ._se_auto_size');
context.resizing.captionButton = resize_button.querySelector('._se_resizing_caption_button');
/** add event listeners */
resize_div_container.addEventListener('mousedown', function (e) { e.preventDefault(); });
resize_handles[0].addEventListener('mousedown', this.onMouseDown_resize_handle.bind(core));
resize_handles[1].addEventListener('mousedown', this.onMouseDown_resize_handle.bind(core));
resize_handles[2].addEventListener('mousedown', this.onMouseDown_resize_handle.bind(core));
resize_handles[3].addEventListener('mousedown', this.onMouseDown_resize_handle.bind(core));
resize_handles[4].addEventListener('mousedown', this.onMouseDown_resize_handle.bind(core));
resize_handles[5].addEventListener('mousedown', this.onMouseDown_resize_handle.bind(core));
resize_handles[6].addEventListener('mousedown', this.onMouseDown_resize_handle.bind(core));
resize_handles[7].addEventListener('mousedown', this.onMouseDown_resize_handle.bind(core));
resize_button.addEventListener('click', this.onClick_resizeButton.bind(core));
/** append html */
context.element.relative.appendChild(resize_div_container);
context.element.relative.appendChild(resize_button);
/** empty memory */
resize_div_container = null, resize_button = null, resize_handles = null;
},
/** resize controller, button (image, iframe, video) */
setController_resize: function (core) {
const resize_container = core.util.createElement('DIV');
resize_container.className = 'se-controller se-resizing-container';
resize_container.style.display = 'none';
resize_container.innerHTML = '' +
'<div class="se-modal-resize"></div>' +
'<div class="se-resize-dot">' +
'<span class="tl"></span>' +
'<span class="tr"></span>' +
'<span class="bl"></span>' +
'<span class="br"></span>' +
'<span class="lw"></span>' +
'<span class="th"></span>' +
'<span class="rw"></span>' +
'<span class="bh"></span>' +
'<div class="se-resize-display"></div>' +
'</div>';
return resize_container;
},
setController_button: function (core) {
const lang = core.lang;
const icons = core.icons;
const resize_button = core.util.createElement("DIV");
resize_button.className = "se-controller se-controller-resizing";
resize_button.innerHTML = '' +
'<div class="se-arrow se-arrow-up"></div>' +
'<div class="se-btn-group _se_resizing_btn_group">' +
'<button type="button" data-command="percent" data-value="1" class="se-tooltip _se_percentage">' +
'<span>100%</span>' +
'<span class="se-tooltip-inner"><span class="se-tooltip-text">' + lang.controller.resize100 + '</span></span>' +
'</button>' +
'<button type="button" data-command="percent" data-value="0.75" class="se-tooltip _se_percentage">' +
'<span>75%</span>' +
'<span class="se-tooltip-inner"><span class="se-tooltip-text">' + lang.controller.resize75 + '</span></span>' +
'</button>' +
'<button type="button" data-command="percent" data-value="0.5" class="se-tooltip _se_percentage">' +
'<span>50%</span>' +
'<span class="se-tooltip-inner"><span class="se-tooltip-text">' + lang.controller.resize50 + '</span></span>' +
'</button>' +
'<button type="button" data-command="auto" class="se-btn se-tooltip _se_auto_size">' +
icons.auto_size +
'<span class="se-tooltip-inner"><span class="se-tooltip-text">' + lang.controller.autoSize + '</span></span>' +
'</button>' +
'<button type="button" data-command="rotate" data-value="-90" class="se-btn se-tooltip _se_rotation">' +
icons.rotate_left +
'<span class="se-tooltip-inner"><span class="se-tooltip-text">' + lang.controller.rotateLeft + '</span></span>' +
'</button>' +
'<button type="button" data-command="rotate" data-value="90" class="se-btn se-tooltip _se_rotation">' +
icons.rotate_right +
'<span class="se-tooltip-inner"><span class="se-tooltip-text">' + lang.controller.rotateRight + '</span></span>' +
'</button>' +
'</div>' +
'<div class="se-btn-group" style="padding-top: 0;">' +
'<button type="button" data-command="mirror" data-value="h" class="se-btn se-tooltip">' +
icons.mirror_horizontal +
'<span class="se-tooltip-inner"><span class="se-tooltip-text">' + lang.controller.mirrorHorizontal + '</span></span>' +
'</button>' +
'<button type="button" data-command="mirror" data-value="v" class="se-btn se-tooltip">' +
icons.mirror_vertical +
'<span class="se-tooltip-inner"><span class="se-tooltip-text">' + lang.controller.mirrorVertical + '</span></span>' +
'</button>' +
'<button type="button" data-command="onalign" class="se-btn se-tooltip _se_resizing_align_button">' +
icons.align_justify +
'<span class="se-tooltip-inner"><span class="se-tooltip-text">' + lang.toolbar.align + '</span></span>' +
'</button>' +
'<div class="se-btn-group-sub sun-editor-common se-list-layer se-resizing-align-list">' +
'<div class="se-list-inner">' +
'<ul class="se-list-basic">' +
'<li><button type="button" class="se-btn-list se-tooltip" data-command="align" data-value="basic">' +
icons.align_justify +
'<span class="se-tooltip-inner"><span class="se-tooltip-text">' + lang.dialogBox.basic + '</span></span>' +
'</button></li>' +
'<li><button type="button" class="se-btn-list se-tooltip" data-command="align" data-value="left">' +
icons.align_left +
'<span class="se-tooltip-inner"><span class="se-tooltip-text">' + lang.dialogBox.left + '</span></span>' +
'</button></li>' +
'<li><button type="button" class="se-btn-list se-tooltip" data-command="align" data-value="center">' +
icons.align_center +
'<span class="se-tooltip-inner"><span class="se-tooltip-text">' + lang.dialogBox.center + '</span></span>' +
'</button></li>' +
'<li><button type="button" class="se-btn-list se-tooltip" data-command="align" data-value="right">' +
icons.align_right +
'<span class="se-tooltip-inner"><span class="se-tooltip-text">' + lang.dialogBox.right + '</span></span>' +
'</button></li>' +
'</ul>' +
'</div>' +
'</div>' +
'<button type="button" data-command="caption" class="se-btn se-tooltip _se_resizing_caption_button">' +
icons.caption +
'<span class="se-tooltip-inner"><span class="se-tooltip-text">' + lang.dialogBox.caption + '</span></span>' +
'</button>' +
'<button type="button" data-command="revert" class="se-btn se-tooltip">' +
icons.revert +
'<span class="se-tooltip-inner"><span class="se-tooltip-text">' + lang.dialogBox.revertButton + '</span></span>' +
'</button>' +
'<button type="button" data-command="update" class="se-btn se-tooltip">' +
icons.modify +
'<span class="se-tooltip-inner"><span class="se-tooltip-text">' + lang.controller.edit + '</span></span>' +
'</button>' +
'<button type="button" data-command="delete" class="se-btn se-tooltip">' +
icons.delete +
'<span class="se-tooltip-inner"><span class="se-tooltip-text">' + lang.controller.remove + '</span></span>' +
'</button>' +
'</div>';
return resize_button;
},
/**
* @description Gets the width size
* @param {Object} contextPlugin context object of plugin (core.context[plugin])
* @param {Element} element Target element
* @param {Element} cover Cover element (FIGURE)
* @param {Element} container Container element (DIV.se-component)
* @returns {String}
*/
_module_getSizeX: function (contextPlugin, element, cover, container) {
if (!element) element = contextPlugin._element;
if (!cover) cover = contextPlugin._cover;
if (!container) container = contextPlugin._container;
if (!element) return '';
return !/%$/.test(element.style.width) ? element.style.width : ((container && this.util.getNumber(container.style.width, 2)) || 100) + '%';
},
/**
* @description Gets the height size
* @param {Object} contextPlugin context object of plugin (core.context[plugin])
* @param {Element} element Target element
* @param {Element} cover Cover element (FIGURE)
* @param {Element} container Container element (DIV.se-component)
* @returns {String}
*/
_module_getSizeY: function (contextPlugin, element, cover, container) {
if (!element) element = contextPlugin._element;
if (!cover) cover = contextPlugin._cover;
if (!container) container = contextPlugin._container;
if (!container || !cover) return (element && element.style.height) || '';
return this.util.getNumber(cover.style.paddingBottom, 0) > 0 && !this.context.resizing._rotateVertical ? cover.style.height : (!/%$/.test(element.style.height) || !/%$/.test(element.style.width) ? element.style.height : ((container && this.util.getNumber(container.style.height, 2)) || 100) + '%');
},
/**
* @description Called at the "openModify" to put the size of the current target into the size input element.
* @param {Object} contextPlugin context object of plugin (core.context[plugin])
* @param {Object} pluginObj Plugin object
*/
_module_setModifyInputSize: function (contextPlugin, pluginObj) {
const percentageRotation = contextPlugin._onlyPercentage && this.context.resizing._rotateVertical;
contextPlugin.proportion.checked = contextPlugin._proportionChecked = contextPlugin._element.getAttribute('data-proportion') !== 'false';
let x = percentageRotation ? '' : this.plugins.resizing._module_getSizeX.call(this, contextPlugin);
if (x === contextPlugin._defaultSizeX) x = '';
if (contextPlugin._onlyPercentage) x = this.util.getNumber(x, 2);
contextPlugin.inputX.value = x;
pluginObj.setInputSize.call(this, 'x');
if (!contextPlugin._onlyPercentage) {
let y = percentageRotation ? '' : this.plugins.resizing._module_getSizeY.call(this, contextPlugin);
if (y === contextPlugin._defaultSizeY) y = '';
if (contextPlugin._onlyPercentage) y = this.util.getNumber(y, 2);
contextPlugin.inputY.value = y;
}
contextPlugin.inputX.disabled = percentageRotation ? true : false;
contextPlugin.inputY.disabled = percentageRotation ? true : false;
contextPlugin.proportion.disabled = percentageRotation ? true : false;
pluginObj.setRatio.call(this);
},
/**
* @description It is called in "setInputSize" (input tag keyupEvent),
* checks the value entered in the input tag,
* calculates the ratio, and sets the calculated value in the input tag of the opposite size.
* @param {Object} contextPlugin context object of plugin (core.context[plugin])
* @param {String} xy 'x': width, 'y': height
*/
_module_setInputSize: function (contextPlugin, xy) {
if (contextPlugin._onlyPercentage) {
if (xy === 'x' && contextPlugin.inputX.value > 100) contextPlugin.inputX.value = 100;
return;
}
if (contextPlugin.proportion.checked && contextPlugin._ratio && /\d/.test(contextPlugin.inputX.value) && /\d/.test(contextPlugin.inputY.value)) {
const xUnit = contextPlugin.inputX.value.replace(/\d+|\./g, '') || contextPlugin.sizeUnit;
const yUnit = contextPlugin.inputY.value.replace(/\d+|\./g, '') || contextPlugin.sizeUnit;
if (xUnit !== yUnit) return;
const dec = xUnit === '%' ? 2 : 0;
if (xy === 'x') {
contextPlugin.inputY.value = this.util.getNumber(contextPlugin._ratioY * this.util.getNumber(contextPlugin.inputX.value, dec), dec) + yUnit;
} else {
contextPlugin.inputX.value = this.util.getNumber(contextPlugin._ratioX * this.util.getNumber(contextPlugin.inputY.value, dec), dec) + xUnit;
}
}
},
/**
* @description It is called in "setRatio" (input and proportionCheck tags changeEvent),
* checks the value of the input tag, calculates the ratio, and resets it in the input tag.
* @param {Object} contextPlugin context object of plugin (core.context[plugin])
*/
_module_setRatio: function (contextPlugin) {
const xValue = contextPlugin.inputX.value;
const yValue = contextPlugin.inputY.value;
if (contextPlugin.proportion.checked && /\d+/.test(xValue) && /\d+/.test(yValue)) {
const xUnit = xValue.replace(/\d+|\./g, '') || contextPlugin.sizeUnit;
const yUnit = yValue.replace(/\d+|\./g, '') || contextPlugin.sizeUnit;
if (xUnit !== yUnit) {
contextPlugin._ratio = false;
} else if (!contextPlugin._ratio) {
const x = this.util.getNumber(xValue, 0);
const y = this.util.getNumber(yValue, 0);
contextPlugin._ratio = true;
contextPlugin._ratioX = x / y;
contextPlugin._ratioY = y / x;
}
} else {
contextPlugin._ratio = false;
}
},
/**
* @description Revert size of element to origin size (plugin._origin_w, plugin._origin_h)
* @param {Object} contextPlugin context object of plugin (core.context[plugin])
*/
_module_sizeRevert: function (contextPlugin) {
if (contextPlugin._onlyPercentage) {
contextPlugin.inputX.value = contextPlugin._origin_w > 100 ? 100 : contextPlugin._origin_w;
} else {
contextPlugin.inputX.value = contextPlugin._origin_w;
contextPlugin.inputY.value = contextPlugin._origin_h;
}
},
/**
* @description Save the size data (element.setAttribute("data-size"))
* Used at the "setSize" method
* @param {Object} contextPlugin context object of plugin (core.context[plugin])
*/
_module_saveCurrentSize: function (contextPlugin) {
const x = this.plugins.resizing._module_getSizeX.call(this, contextPlugin);
const y = this.plugins.resizing._module_getSizeY.call(this, contextPlugin);
// add too width, height attribute
contextPlugin._element.setAttribute('width', x.replace('px', ''));
contextPlugin._element.setAttribute('height', y.replace('px', ''));
contextPlugin._element.setAttribute('data-size', x + ',' + y);
if (!!contextPlugin._videoRatio) contextPlugin._videoRatio = y;
},
/**
* @description Call the resizing module
* @param {Element} targetElement Resizing target element
* @param {string} plugin Plugin name
* @returns {Object} Size of resizing div {w, h, t, l}
*/
call_controller_resize: function (targetElement, plugin) {
const contextResizing = this.context.resizing;
const contextPlugin = this.context[plugin];
contextResizing._resize_plugin = plugin;
const resizeContainer = contextResizing.resizeContainer;
const resizeDiv = contextResizing.resizeDiv;
const offset = this.util.getOffset(targetElement, this.context.element.wysiwygFrame);
const isVertical = contextResizing._rotateVertical = /^(90|270)$/.test(Math.abs(targetElement.getAttribute('data-rotate')).toString());
const w = isVertical ? targetElement.offsetHeight : targetElement.offsetWidth;
const h = isVertical ? targetElement.offsetWidth : targetElement.offsetHeight;
const t = offset.top;
const l = offset.left - this.context.element.wysiwygFrame.scrollLeft;
resizeContainer.style.top = t + 'px';
resizeContainer.style.left = l + 'px';
resizeContainer.style.width = w + 'px';
resizeContainer.style.height = h + 'px';
resizeDiv.style.top = '0px';
resizeDiv.style.left = '0px';
resizeDiv.style.width = w + 'px';
resizeDiv.style.height = h + 'px';
let align = targetElement.getAttribute('data-align') || 'basic';
align = align === 'none' ? 'basic' : align;
// text
const container = this.util.getParentElement(targetElement, this.util.isComponent);
const cover = this.util.getParentElement(targetElement, 'FIGURE');
const displayX = this.plugins.resizing._module_getSizeX.call(this, contextPlugin, targetElement, cover, container) || 'auto';
const displayY = contextPlugin._onlyPercentage && plugin === 'image' ? '' : ', ' + (this.plugins.resizing._module_getSizeY.call(this, contextPlugin, targetElement, cover, container) || 'auto');
this.util.changeTxt(contextResizing.resizeDisplay, this.lang.dialogBox[align] + ' (' + displayX + displayY + ')');
// resizing display
contextResizing.resizeButtonGroup.style.display = contextPlugin._resizing ? '' : 'none';
const resizeDotShow = contextPlugin._resizing && !contextPlugin._resizeDotHide && !contextPlugin._onlyPercentage ? 'flex' : 'none';
const resizeHandles = contextResizing.resizeHandles;
for (let i = 0, len = resizeHandles.length; i < len; i++) {
resizeHandles[i].style.display = resizeDotShow;
}
if (contextPlugin._resizing) {
const rotations = contextResizing.rotationButtons;
rotations[0].style.display = rotations[1].style.display = contextPlugin._rotation ? '' : 'none';
}
// align icon
if (contextPlugin._alignHide) {
contextResizing.alignButton.style.display = 'none';
} else {
contextResizing.alignButton.style.display = '';
const alignList = contextResizing.alignMenuList;
this.util.changeElement(contextResizing.alignButton.firstElementChild, contextResizing.alignIcons[align]);
for (let i = 0, len = alignList.length; i < len; i++) {
if (alignList[i].getAttribute('data-value') === align) this.util.addClass(alignList[i], 'on');
else this.util.removeClass(alignList[i], 'on');
}
}
// percentage active
const pButtons = contextResizing.percentageButtons;
const value = /%$/.test(targetElement.style.width) && /%$/.test(container.style.width) ? (this.util.getNumber(container.style.width, 0) / 100) + '' : '' ;
for (let i = 0, len = pButtons.length; i < len; i++) {
if (pButtons[i].getAttribute('data-value') === value) {
this.util.addClass(pButtons[i], 'active');
} else {
this.util.removeClass(pButtons[i], 'active');
}
}
// caption display, active
if (!contextPlugin._captionShow) {
contextResizing.captionButton.style.display = 'none';
} else {
contextResizing.captionButton.style.display = '';
if (this.util.getChildElement(targetElement.parentNode, 'figcaption')) {
this.util.addClass(contextResizing.captionButton, 'active');
contextPlugin._captionChecked = true;
} else {
this.util.removeClass(contextResizing.captionButton, 'active');
contextPlugin._captionChecked = false;
}
}
resizeContainer.style.display = 'block';
const addOffset = {left: 0, top: 50};
if (this.options.iframe) {
addOffset.left -= this.context.element.wysiwygFrame.parentElement.offsetLeft;
addOffset.top -= this.context.element.wysiwygFrame.parentElement.offsetTop;
}
this.setControllerPosition(contextResizing.resizeButton, resizeContainer, 'bottom', addOffset);
const onControlsOff = function () {
this.util.setDisabledButtons.call(this.util, false, this.resizingDisabledButtons);
this.history._resetCachingButton();
};
this.controllersOn(resizeContainer, contextResizing.resizeButton, onControlsOff.bind(this), targetElement, plugin);
this.util.setDisabledButtons(true, this.resizingDisabledButtons);
contextResizing._resize_w = w;
contextResizing._resize_h = h;
const originSize = (targetElement.getAttribute('origin-size') || '').split(',');
contextResizing._origin_w = originSize[0] || targetElement.naturalWidth;
contextResizing._origin_h = originSize[1] || targetElement.naturalHeight;
return {
w: w,
h: h,
t: t,
l: l
};
},
_closeAlignMenu: null,
/**
* @description Open align submenu of module
*/
openAlignMenu: function () {
const alignButton = this.context.resizing.alignButton;
this.util.addClass(alignButton, 'on');
this.context.resizing.alignMenu.style.top = (alignButton.offsetTop + alignButton.offsetHeight) + 'px';
this.context.resizing.alignMenu.style.left = (alignButton.offsetLeft - alignButton.offsetWidth / 2) + 'px';
this.context.resizing.alignMenu.style.display = 'block';
this.plugins.resizing._closeAlignMenu = function () {
this.util.removeClass(this.context.resizing.alignButton, 'on');
this.context.resizing.alignMenu.style.display = 'none';
this.removeDocEvent('click', this.plugins.resizing._closeAlignMenu);
this.plugins.resizing._closeAlignMenu = null;
}.bind(this);
this.addDocEvent('click', this.plugins.resizing._closeAlignMenu);
},
/**
* @description Click event of resizing toolbar
* Performs the action of the clicked toolbar button.
* @param {MouseEvent} e Event object
*/
onClick_resizeButton: function (e) {
e.stopPropagation();
const target = e.target;
const command = target.getAttribute('data-command') || target.parentNode.getAttribute('data-command');
if (!command) return;
const value = target.getAttribute('data-value') || target.parentNode.getAttribute('data-value');
const pluginName = this.context.resizing._resize_plugin;
const currentContext = this.context[pluginName];
const contextEl = currentContext._element;
const currentModule = this.plugins[pluginName];
e.preventDefault();
if (typeof this.plugins.resizing._closeAlignMenu === 'function') {
this.plugins.resizing._closeAlignMenu();
if (command === 'onalign') return;
}
switch (command) {
case 'auto':
this.plugins.resizing.resetTransform.call(this, contextEl);
currentModule.setAutoSize.call(this);
this.selectComponent(contextEl, pluginName);
break;
case 'percent':
let percentY = this.plugins.resizing._module_getSizeY.call(this, currentContext);
if (this.context.resizing._rotateVertical) {
const percentage = contextEl.getAttribute('data-percentage');
if (percentage) percentY = percentage.split(',')[1];
}
this.plugins.resizing.resetTransform.call(this, contextEl);
currentModule.setPercentSize.call(this, (value * 100), (this.util.getNumber(percentY, 0) === null || !/%$/.test(percentY)) ? '' : percentY);
this.selectComponent(contextEl, pluginName);
break;
case 'mirror':
const r = contextEl.getAttribute('data-rotate') || '0';
let x = contextEl.getAttribute('data-rotateX') || '';
let y = contextEl.getAttribute('data-rotateY') || '';
if ((value === 'h' && !this.context.resizing._rotateVertical) || (value === 'v' && this.context.resizing._rotateVertical)) {
y = y ? '' : '180';
} else {
x = x ? '' : '180';
}
contextEl.setAttribute('data-rotateX', x);
contextEl.setAttribute('data-rotateY', y);
this.plugins.resizing._setTransForm(contextEl, r, x, y);
break;
case 'rotate':
const contextResizing = this.context.resizing;
const slope = (contextEl.getAttribute('data-rotate') * 1) + (value * 1);
const deg = this._w.Math.abs(slope) >= 360 ? 0 : slope;
contextEl.setAttribute('data-rotate', deg);
contextResizing._rotateVertical = /^(90|270)$/.test(this._w.Math.abs(deg).toString());
this.plugins.resizing.setTransformSize.call(this, contextEl, null, null);
this.selectComponent(contextEl, pluginName);
break;
case 'onalign':
this.plugins.resizing.openAlignMenu.call(this);
return;
case 'align':
const alignValue = value === 'basic' ? 'none' : value;
currentModule.setAlign.call(this, alignValue, null, null, null);
this.selectComponent(contextEl, pluginName);
break;
case 'caption':
const caption = !currentContext._captionChecked;
currentModule.openModify.call(this, true);
currentContext._captionChecked = currentContext.captionCheckEl.checked = caption;
currentModule.update_image.call(this, false, false, false);
if (caption) {
const captionText = this.util.getChildElement(currentContext._caption, function (current) {
return current.nodeType === 3;
});
if (!captionText) {
currentContext._caption.focus();
} else {
this.setRange(captionText, 0, captionText, captionText.textContent.length);
}
this.controllersOff();
} else {
this.selectComponent(contextEl, pluginName);
currentModule.openModify.call(this, true);
}
break;
case 'revert':
currentModule.setOriginSize.call(this);
this.selectComponent(contextEl, pluginName);
break;
case 'update':
currentModule.openModify.call(this);
this.controllersOff();
break;
case 'delete':
currentModule.destroy.call(this);
break;
}
// history stack
this.history.push(false);
},
/**
* @description Initialize the transform style (rotation) of the element.
* @param {Element} element Target element
*/
resetTransform: function (element) {
const size = (element.getAttribute('data-size') || element.getAttribute('data-origin') || '').split(',');
this.context.resizing._rotateVertical = false;
element.style.maxWidth = '';
element.style.transform = '';
element.style.transformOrigin = '';
element.setAttribute('data-rotate', '');
element.setAttribute('data-rotateX', '');
element.setAttribute('data-rotateY', '');
this.plugins[this.context.resizing._resize_plugin].setSize.call(this, size[0] ? size[0] : 'auto', size[1] ? size[1] : '', true);
},
/**
* @description Set the transform style (rotation) of the element.
* @param {Element} element Target element
* @param {Number|null} width Element's width size
* @param {Number|null} height Element's height size
*/
setTransformSize: function (element, width, height) {
let percentage = element.getAttribute('data-percentage');
const isVertical = this.context.resizing._rotateVertical;
const deg = element.getAttribute('data-rotate') * 1;
let transOrigin = '';
if (percentage && !isVertical) {
percentage = percentage.split(',');
if (percentage[0] === 'auto' && percentage[1] === 'auto') {
this.plugins[this.context.resizing._resize_plugin].setAutoSize.call(this);
} else {
this.plugins[this.context.resizing._resize_plugin].setPercentSize.call(this, percentage[0], percentage[1]);
}
} else {
const cover = this.util.getParentElement(element, 'FIGURE');
const offsetW = width || element.offsetWidth;
const offsetH = height || element.offsetHeight;
const w = (isVertical ? offsetH : offsetW) + 'px';
const h = (isVertical ? offsetW : offsetH) + 'px';
this.plugins[this.context.resizing._resize_plugin].cancelPercentAttr.call(this);
this.plugins[this.context.resizing._resize_plugin].setSize.call(this, offsetW + 'px', offsetH + 'px', true);
cover.style.width = w;
cover.style.height = (!!this.context[this.context.resizing._resize_plugin]._caption ? '' : h);
if (isVertical) {
let transW = (offsetW / 2) + 'px ' + (offsetW / 2) + 'px 0';
let transH = (offsetH / 2) + 'px ' + (offsetH / 2) + 'px 0';
transOrigin = deg === 90 || deg === -270 ? transH : transW;
}
}
element.style.transformOrigin = transOrigin;
this.plugins.resizing._setTransForm(element, deg.toString(), element.getAttribute('data-rotateX') || '', element.getAttribute('data-rotateY') || '');
if (isVertical) element.style.maxWidth = 'none';
else element.style.maxWidth = '';
this.plugins.resizing.setCaptionPosition.call(this, element);
},
_setTransForm: function (element, r, x, y) {
let width = (element.offsetWidth - element.offsetHeight) * (/-/.test(r) ? 1 : -1);
let translate = '';
if (/[1-9]/.test(r) && (x || y)) {
translate = x ? 'Y' : 'X';
switch (r) {
case '90':
translate = x && y ? 'X' : y ? translate : '';
break;
case '270':
width *= -1;
translate = x && y ? 'Y' : x ? translate : '';
break;
case '-90':
translate = x && y ? 'Y' : x ? translate : '';
break;
case '-270':
width *= -1;
translate = x && y ? 'X' : y ? translate : '';
break;
default:
translate = '';
}
}
if (r % 180 === 0) {
element.style.maxWidth = '';
}
element.style.transform = 'rotate(' + r + 'deg)' + (x ? ' rotateX(' + x + 'deg)' : '') + (y ? ' rotateY(' + y + 'deg)' : '') + (translate ? ' translate' + translate + '(' + width + 'px)' : '');
},
/**
* @description The position of the caption is set automatically.
* @param {Element} element Target element (not caption element)
*/
setCaptionPosition: function (element) {
const figcaption = this.util.getChildElement(this.util.getParentElement(element, 'FIGURE'), 'FIGCAPTION');
if (figcaption) {
figcaption.style.marginTop = (this.context.resizing._rotateVertical ? element.offsetWidth - element.offsetHeight : 0) + 'px';
}
},
/**
* @description Mouse down event of resize handles
* @param {MouseEvent} e Event object
*/
onMouseDown_resize_handle: function (e) {
e.stopPropagation();
e.preventDefault();
const contextResizing = this.context.resizing;
const direction = contextResizing._resize_direction = e.target.classList[0];
contextResizing._resizeClientX = e.clientX;
contextResizing._resizeClientY = e.clientY;
this.context.element.resizeBackground.style.display = 'block';
contextResizing.resizeButton.style.display = 'none';
contextResizing.resizeDiv.style.float = /l/.test(direction) ? 'right' : /r/.test(direction) ? 'left' : 'none';
const closureFunc_bind = function closureFunc(e) {
if (e.type === 'keydown' && e.keyCode !== 27) return;
const change = contextResizing._isChange;
contextResizing._isChange = false;
this.removeDocEvent('mousemove', resizing_element_bind);
this.removeDocEvent('mouseup', closureFunc_bind);
this.removeDocEvent('keydown', closureFunc_bind);
if (e.type === 'keydown') {
this.controllersOff();
this.context.element.resizeBackground.style.display = 'none';
this.plugins[this.context.resizing._resize_plugin].init.call(this);
} else {
// element resize
this.plugins.resizing.cancel_controller_resize.call(this, direction);
// history stack
if (change) this.history.push(false);
}
}.bind(this);
const resizing_element_bind = this.plugins.resizing.resizing_element.bind(this, contextResizing, direction, this.context[contextResizing._resize_plugin]);
this.addDocEvent('mousemove', resizing_element_bind);
this.addDocEvent('mouseup', closureFunc_bind);
this.addDocEvent('keydown', closureFunc_bind);
},
/**
* @description Mouse move event after call "onMouseDown_resize_handle" of resize handles
* The size of the module's "div" is adjusted according to the mouse move event.
* @param {Object} contextResizing "core.context.resizing" object (binding argument)
* @param {String} direction Direction ("tl", "tr", "bl", "br", "lw", "th", "rw", "bh") (binding argument)
* @param {Object} plugin "core.context[currentPlugin]" object (binding argument)
* @param {MouseEvent} e Event object
*/
resizing_element: function (contextResizing, direction, plugin, e) {
const clientX = e.clientX;
const clientY = e.clientY;
let resultW = plugin._element_w;
let resultH = plugin._element_h;
const w = plugin._element_w + (/r/.test(direction) ? clientX - contextResizing._resizeClientX : contextResizing._resizeClientX - clientX);
const h = plugin._element_h + (/b/.test(direction) ? clientY - contextResizing._resizeClientY : contextResizing._resizeClientY - clientY);
const wh = ((plugin._element_h / plugin._element_w) * w);
if (/t/.test(direction)) contextResizing.resizeDiv.style.top = (plugin._element_h - (/h/.test(direction) ? h : wh)) + 'px';
if (/l/.test(direction)) contextResizing.resizeDiv.style.left = (plugin._element_w - w) + 'px';
if (/r|l/.test(direction)) {
contextResizing.resizeDiv.style.width = w + 'px';
resultW = w;
}
if (/^(t|b)[^h]$/.test(direction)) {
contextResizing.resizeDiv.style.height = wh + 'px';
resultH = wh;
}
else if (/^(t|b)h$/.test(direction)) {
contextResizing.resizeDiv.style.height = h + 'px';
resultH = h;
}
contextResizing._resize_w = resultW;
contextResizing._resize_h = resultH;
this.util.changeTxt(contextResizing.resizeDisplay, this._w.Math.round(resultW) + ' x ' + this._w.Math.round(resultH));
contextResizing._isChange = true;
},
/**
* @description Resize the element to the size of the "div" adjusted in the "resizing_element" method.
* Called at the mouse-up event registered in "onMouseDown_resize_handle".
* @param {String} direction Direction ("tl", "tr", "bl", "br", "lw", "th", "rw", "bh")
*/
cancel_controller_resize: function (direction) {
const isVertical = this.context.resizing._rotateVertical;
this.controllersOff();
this.context.element.resizeBackground.style.display = 'none';
let w = this._w.Math.round(isVertical ? this.context.resizing._resize_h : this.context.resizing._resize_w);
let h = this._w.Math.round(isVertical ? this.context.resizing._resize_w : this.context.resizing._resize_h);
if (!isVertical && !/%$/.test(w)) {
const padding = 16;
const limit = this.context.element.wysiwygFrame.clientWidth - (padding * 2) - 2;
if (this.util.getNumber(w, 0) > limit) {
h = this._w.Math.round((h / w) * limit);
w = limit;
}
}
const pluginName = this.context.resizing._resize_plugin;
this.plugins[pluginName].setSize.call(this, w, h, false, direction);
if (isVertical) this.plugins.resizing.setTransformSize.call(this, this.context[this.context.resizing._resize_plugin]._element, w, h);
this.selectComponent(this.context[pluginName]._element, pluginName);
}
};
if (typeof noGlobal === typeof undefined) {
if (!window.SUNEDITOR_MODULES) {
Object.defineProperty(window, 'SUNEDITOR_MODULES', {
enumerable: true,
writable: false,
configurable: false,
value: {}
});
}
Object.defineProperty(window.SUNEDITOR_MODULES, 'resizing', {
enumerable: true,
writable: false,
configurable: false,
value: resizing
});
}
return resizing;
}));