jodit
Version:
Jodit is awesome and usefully wysiwyg editor with filebrowser
530 lines (467 loc) • 13.1 kB
text/typescript
/*!
* Jodit Editor (https://xdsoft.net/jodit/)
* Licensed under GNU General Public License version 2 or later or a commercial license or MIT;
* For GPL see LICENSE-GPL.txt in the project root for license information.
* For MIT see LICENSE-MIT.txt in the project root for license information.
* For commercial licenses see https://xdsoft.net/jodit/commercial/
* Copyright (c) 2013-2019 Valeriy Chupurnov. All rights reserved. https://xdsoft.net
*/
import { Config } from '../Config';
import * as consts from '../constants';
import { IS_IE } from '../constants';
import { IBound } from '../types/types';
import { Dom } from '../modules/Dom';
import { $$ } from '../modules/helpers/selector';
import { debounce, setTimeout } from '../modules/helpers/async';
import { offset, innerWidth } from '../modules/helpers/size';
import { css } from '../modules/helpers';
import { IJodit } from '../types';
/**
* The module creates a supporting frame for resizing of the elements img and table
* @module Resizer
* @params {Object} parent Jodit main object
*/
/**
* @property{boolean} useIframeResizer=true Use true frame for editing iframe size
*/
declare module '../Config' {
interface Config {
useIframeResizer: boolean;
useTableResizer: boolean;
useImageResizer: boolean;
resizer: {
showSize: boolean;
hideSizeTimeout: number;
min_width: number;
min_height: number;
};
}
}
Config.prototype.useIframeResizer = true;
/**
* @property{boolean} useTableResizer=true Use true frame for editing table size
*/
Config.prototype.useTableResizer = true;
/**
* @property{boolean} useImageResizer=true Use true image editing frame size
*/
Config.prototype.useImageResizer = true;
/**
* @property {object} resizer
* @property {int} resizer.min_width=10 The minimum width for the editable element
* @property {int} resizer.min_height=10 The minimum height for the item being edited
* @property {boolean} resizer.showSize=true Show size
*/
Config.prototype.resizer = {
showSize: true,
hideSizeTimeout: 1000,
min_width: 10,
min_height: 10
};
/**
* Resize table and img
* @param {Jodit} editor
*/
export function resizer(editor: IJodit) {
const LOCK_KEY = 'resizer';
let
handle: HTMLElement,
currentElement: null | HTMLElement,
resizeElementClicked: boolean = false,
isResizing: boolean = false,
start_x: number,
start_y: number,
width: number,
height: number,
ratio: number,
new_h: number,
new_w: number,
diff_x: number,
diff_y: number,
resizerIsVisible: boolean = false,
timeoutSizeViewer: number = 0;
const
resizerElm: HTMLElement = editor.create.fromHTML(
'<div data-editor_id="' +
editor.id +
'" style="display:none" class="jodit_resizer">' +
'<i class="jodit_resizer-topleft"></i>' +
'<i class="jodit_resizer-topright"></i>' +
'<i class="jodit_resizer-bottomright"></i>' +
'<i class="jodit_resizer-bottomleft"></i>' +
'<span>100x100</span>' +
'</div>'
),
sizeViewer: HTMLSpanElement = resizerElm.getElementsByTagName(
'span'
)[0],
hideResizer = () => {
isResizing = false;
resizerIsVisible = false;
currentElement = null;
resizerElm.style.display = 'none';
},
hideSizeViewer = () => {
sizeViewer.style.opacity = '0';
},
showSizeViewer = (w: number, h: number) => {
if (!editor.options.resizer.showSize) {
return;
}
if (w < sizeViewer.offsetWidth || h < sizeViewer.offsetHeight) {
hideSizeViewer();
return;
}
sizeViewer.style.opacity = '1';
sizeViewer.innerHTML = `${w} x ${h}`;
clearTimeout(timeoutSizeViewer);
timeoutSizeViewer = setTimeout(
hideSizeViewer,
editor.options.resizer.hideSizeTimeout
);
},
updateSize = () => {
if (resizerIsVisible && currentElement && resizerElm) {
const
workplacePosition: IBound = offset(
(resizerElm.parentNode ||
editor.ownerDocument
.documentElement) as HTMLElement,
editor,
editor.ownerDocument,
true
),
pos: IBound = offset(
currentElement,
editor,
editor.editorDocument
),
left: number = parseInt(resizerElm.style.left || '0', 10),
top: number = parseInt(resizerElm.style.top || '0', 10),
w: number = resizerElm.offsetWidth,
h: number = resizerElm.offsetHeight;
// 1 - because need move border higher and toWYSIWYG the left than the picture
// 2 - in box-sizing: border-box mode width is real width indifferent by border-width.
const newTop: number = pos.top - 1 - workplacePosition.top,
newLeft: number = pos.left - 1 - workplacePosition.left;
if (
top !== newTop ||
left !== newLeft ||
w !== currentElement.offsetWidth ||
h !== currentElement.offsetHeight
) {
resizerElm.style.top = newTop + 'px';
resizerElm.style.left = newLeft + 'px';
resizerElm.style.width = currentElement.offsetWidth + 'px';
resizerElm.style.height =
currentElement.offsetHeight + 'px';
if (editor.events) {
editor.events.fire(currentElement, 'changesize');
// check for first init. Ex. inlinePopup hides when it was fired
if (!isNaN(left)) {
editor.events.fire('resize');
}
}
}
}
},
showResizer = () => {
if (editor.options.readonly) {
return;
}
if (!resizerElm.parentNode) {
editor.workplace.appendChild(resizerElm);
}
resizerIsVisible = true;
resizerElm.style.display = 'block';
if (editor.isFullSize()) {
resizerElm.style.zIndex = css(
editor.container,
'zIndex'
).toString();
}
updateSize();
},
/**
* Bind an edit element toWYSIWYG element
* @param {HTMLElement} element The element that you want toWYSIWYG add a function toWYSIWYG resize
*/
bind = (element: HTMLElement) => {
let wrapper: HTMLElement;
if (element.tagName === 'IFRAME') {
const iframe = element;
if (
element.parentNode &&
(element.parentNode as HTMLElement).getAttribute(
'data-jodit_iframe_wrapper'
)
) {
element = element.parentNode as HTMLElement;
} else {
wrapper = editor.create.inside.fromHTML(
'<jodit ' +
'data-jodit-temp="1" ' +
'contenteditable="false" ' +
'draggable="true" ' +
'data-jodit_iframe_wrapper="1"' +
'></jodit>'
);
wrapper.style.display =
element.style.display === 'inline-block'
? 'inline-block'
: 'block';
wrapper.style.width = element.offsetWidth + 'px';
wrapper.style.height = element.offsetHeight + 'px';
if (element.parentNode) {
element.parentNode.insertBefore(wrapper, element);
}
wrapper.appendChild(element);
element = wrapper;
}
editor.events
.off(element, 'mousedown.select touchstart.select')
.on(element, 'mousedown.select touchstart.select', () => {
editor.selection.select(element);
});
editor.events
.off(element, 'changesize')
.on(element, 'changesize', () => {
iframe.setAttribute(
'width',
element.offsetWidth + 'px'
);
iframe.setAttribute(
'height',
element.offsetHeight + 'px'
);
});
}
let timer: number;
editor.events
.on(element, 'dragstart', hideResizer)
.on(element, 'mousedown', (event: MouseEvent) => {
// for IE don't show native resizer
if (IS_IE && element.nodeName === 'IMG') {
event.preventDefault();
}
})
.on(element, 'mousedown touchstart', () => {
if (!resizeElementClicked) {
resizeElementClicked = true;
currentElement = element;
showResizer();
if (
currentElement.tagName === 'IMG' &&
!(currentElement as HTMLImageElement).complete
) {
currentElement.addEventListener(
'load',
function ElementOnLoad() {
updateSize();
if (currentElement) {
currentElement.removeEventListener(
'load',
ElementOnLoad
);
}
}
);
}
clearTimeout(timer);
}
timer = setTimeout(() => {
resizeElementClicked = false;
}, 400);
});
};
// resizeElement = {};
$$('i', resizerElm).forEach((resizeHandle: HTMLElement) => {
editor.events.on(
resizeHandle,
'mousedown touchstart',
(e: MouseEvent): false | void => {
if (!currentElement || !currentElement.parentNode) {
hideResizer();
return false;
}
// resizeElementClicked = false;
handle = resizeHandle;
e.preventDefault();
e.stopImmediatePropagation();
width = currentElement.offsetWidth;
height = currentElement.offsetHeight;
ratio = width / height;
// clicked = true;
isResizing = true;
// resized = false;
start_x = e.clientX;
start_y = e.clientY;
editor.events.fire('hidePopup');
editor.lock(LOCK_KEY);
}
);
});
editor.events
.on('readonly', (isReadOnly: boolean) => {
if (isReadOnly) {
hideResizer();
}
})
.on('beforeDestruct', () => {
Dom.safeRemove(resizerElm);
})
.on('afterInit', () => {
editor.events
.on(editor.editor, 'keydown', (e: KeyboardEvent) => {
if (
resizerIsVisible &&
e.which === consts.KEY_DELETE &&
currentElement &&
currentElement.tagName.toLowerCase() !== 'table'
) {
if (currentElement.tagName !== 'JODIT') {
editor.selection.select(currentElement);
} else {
Dom.safeRemove(currentElement);
hideResizer();
e.preventDefault();
}
}
})
.on(
editor.ownerWindow,
'mousemove touchmove',
(e: MouseEvent) => {
if (isResizing) {
diff_x = e.clientX - start_x;
diff_y = e.clientY - start_y;
if (!currentElement) {
return;
}
const className: string = handle.className;
if ('IMG' === currentElement.tagName) {
if (diff_x) {
new_w =
width +
(className.match(/left/) ? -1 : 1) *
diff_x;
new_h = Math.round(new_w / ratio);
} else {
new_h =
height +
(className.match(/top/) ? -1 : 1) *
diff_y;
new_w = Math.round(new_h * ratio);
}
if (
new_w >
innerWidth(
editor.editor,
editor.ownerWindow
)
) {
new_w = innerWidth(
editor.editor,
editor.ownerWindow
);
new_h = Math.round(new_w / ratio);
}
} else {
new_w =
width +
(className.match(/left/) ? -1 : 1) * diff_x;
new_h =
height +
(className.match(/top/) ? -1 : 1) * diff_y;
}
if (new_w > editor.options.resizer.min_width) {
if (
new_w <
(resizerElm.parentNode as HTMLElement)
.offsetWidth
) {
currentElement.style.width = new_w + 'px';
} else {
currentElement.style.width = '100%';
}
}
if (new_h > editor.options.resizer.min_height) {
currentElement.style.height = new_h + 'px';
}
updateSize();
showSizeViewer(
currentElement.offsetWidth,
currentElement.offsetHeight
);
e.stopImmediatePropagation();
}
}
)
.on(editor.ownerWindow, 'resize', () => {
if (resizerIsVisible) {
updateSize();
}
})
.on(
editor.ownerWindow,
'mouseup keydown touchend',
(e: MouseEvent) => {
if (resizerIsVisible && !resizeElementClicked) {
if (isResizing) {
editor.unlock();
isResizing = false;
editor.setEditorValue();
e.stopImmediatePropagation();
} else {
hideResizer();
}
}
}
)
.on([editor.ownerWindow, editor.editor], 'scroll', () => {
if (resizerIsVisible && !isResizing) {
hideResizer();
}
});
})
.on('afterGetValueFromEditor', (data: { value: string }) => {
const rgx = /<jodit[^>]+data-jodit_iframe_wrapper[^>]+>(.*?<iframe[^>]+>[\s\n\r]*<\/iframe>.*?)<\/jodit>/gi;
if (rgx.test(data.value)) {
data.value = data.value.replace(rgx, '$1');
}
})
.on('hideResizer', hideResizer)
.on(
'change afterInit afterSetMode',
debounce(() => {
if (resizerIsVisible) {
if (!currentElement || !currentElement.parentNode) {
hideResizer();
} else {
updateSize();
}
}
if (!editor.isDestructed) {
$$('img, table, iframe', editor.editor).forEach(
(elm: HTMLElement) => {
if (editor.getMode() === consts.MODE_SOURCE) {
return;
}
if (
!(elm as any).__jodit_resizer_binded &&
((elm.tagName === 'IFRAME' &&
editor.options.useIframeResizer) ||
(elm.tagName === 'IMG' &&
editor.options.useImageResizer) ||
(elm.tagName === 'TABLE' &&
editor.options.useTableResizer))
) {
(elm as any).__jodit_resizer_binded = true;
bind(elm);
}
}
);
}
}, editor.defaultTimeout)
);
}