UNPKG

jodit

Version:

Jodit is an awesome and useful wysiwyg editor with filebrowser

630 lines (629 loc) 21.4 kB
/*! * Jodit Editor (https://xdsoft.net/jodit/) * Released under MIT see LICENSE.txt in the project root for license information. * Copyright (c) 2013-2025 Valeriy Chupurnov. All rights reserved. https://xdsoft.net */ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var Dialog_1; import { STATUSES } from "../../core/component/index.js"; import { KEY_ESC } from "../../core/constants.js"; import { autobind, component, hook } from "../../core/decorators/index.js"; import { Dom } from "../../core/dom/dom.js"; import { eventEmitter, pluginSystem } from "../../core/global.js"; import { asArray, splitArray, toArray } from "../../core/helpers/array/index.js"; import { hasContainer, isArray, isBoolean, isFunction, isString, isVoid } from "../../core/helpers/checker/index.js"; import { $$, attr, ConfigProto, css } from "../../core/helpers/utils/index.js"; import { assert } from "../../core/helpers/utils/assert.js"; import { Icon } from "../../core/ui/index.js"; import { View } from "../../core/view/view.js"; import { ViewWithToolbar } from "../../core/view/view-with-toolbar.js"; import { Config } from "../../config.js"; Config.prototype.dialog = { namespace: '', extraButtons: [], /** * This dialog can resize by trigger */ resizable: true, /** * This dialog can move by header */ draggable: true, buttons: ['dialog.close'], removeButtons: [], toolbarButtonSize: 'middle', zIndex: 'inherit' }; Config.prototype.controls.dialog = { close: { icon: 'cancel', exec: dialog => { dialog.close(); } } }; /** * Module to generate dialog windows */ let Dialog = Dialog_1 = class Dialog extends ViewWithToolbar { /** @override */ className() { return 'Dialog'; } get destination() { const { popupRoot, shadowRoot } = this.o; if (popupRoot) { return popupRoot; } if (shadowRoot) { return shadowRoot; } return this.od.body; } setElements(root, elements) { const elements_list = []; asArray(elements).forEach((elm) => { if (isArray(elm)) { const div = this.c.div(this.getFullElName('column')); elements_list.push(div); root.appendChild(div); return this.setElements(div, elm); } let element; if (isString(elm)) { element = this.c.fromHTML(elm); } else { element = hasContainer(elm) ? elm.container : elm; } elements_list.push(element); if (element.parentNode !== root) { root.appendChild(element); } }); toArray(root.childNodes).forEach((elm) => { if (elements_list.indexOf(elm) === -1) { root.removeChild(elm); } }); } __onMouseUp() { if (this.draggable || this.resizable) { this.__removeGlobalResizeListeners(); this.draggable = false; this.resizable = false; this.unlockSelect(); if (this.e) { this.__removeGlobalResizeListeners(); /** * Fired when the dialog box is finished to resizing */ this.e.fire(this, 'endResize endMove'); } } } /** * */ __onHeaderMouseDown(e) { const target = e.target; if (!this.o.draggable || (target && target.nodeName.match(/^(INPUT|SELECT)$/))) { return; } this.draggable = true; this.startX = e.clientX; this.startY = e.clientY; this.startPoint.x = css(this.dialog, 'left'); this.startPoint.y = css(this.dialog, 'top'); this.setMaxZIndex(); if (e.cancelable) { e.preventDefault(); } this.lockSelect(); this.__addGlobalResizeListeners(); if (this.e) { /** * Fired when dialog box is started moving */ this.e.fire(this, 'startMove'); this.e.fire('closeAllPopups'); } } __onMouseMove(e) { if (this.draggable && this.o.draggable) { this.setPosition(this.startPoint.x + e.clientX - this.startX, this.startPoint.y + e.clientY - this.startY); if (this.e) { /** * Fired when dialog box is moved */ this.e.fire(this, 'move', e.clientX - this.startX, e.clientY - this.startY); } e.stopImmediatePropagation(); } if (this.resizable && this.o.resizable) { this.setSize(this.startPoint.w + e.clientX - this.startX, this.startPoint.h + e.clientY - this.startY); if (this.e) { /** * Fired when dialog box is resized */ this.e.fire(this, 'resizeDialog', e.clientX - this.startX, e.clientY - this.startY); } } } __onEsc(e) { if (!this.o.closeOnEsc) { return; } if (this.isOpened && e.key === KEY_ESC && this.getMod('static') !== true) { const me = this.getMaxZIndexDialog(); if (me) { me.close(); } else { this.close(); } e.stopImmediatePropagation(); } } __onResizerMouseDown(e) { this.resizable = true; this.startX = e.clientX; this.startY = e.clientY; this.startPoint.w = this.dialog.offsetWidth; this.startPoint.h = this.dialog.offsetHeight; this.lockSelect(); this.__addGlobalResizeListeners(); if (this.e) { /** * Fired when dialog box is started resizing */ this.e.fire(this, 'startResize'); } } __addGlobalResizeListeners() { const self = this; self.e .on(self.ow, 'pointermove touchmove', self.__onMouseMove) .on(self.ow, 'pointerup touchend', self.__onMouseUp); } __removeGlobalResizeListeners() { const self = this; self.e .off(self.ow, 'mousemove pointermove', self.__onMouseMove) .off(self.ow, 'mouseup pointerup', self.__onMouseUp); } /** * Specifies the size of the window * * @param w - The width of the window * @param h - The height of the window */ setSize(w, h) { if (w == null) { w = this.dialog.offsetWidth; } if (h == null) { h = this.dialog.offsetHeight; } css(this.dialog, { width: w, height: h }); return this; } /** * Recalculate auto sizes */ calcAutoSize() { this.setSize('auto', 'auto'); this.setSize(); return this; } /** * Specifies the position of the upper left corner of the window . If x and y are specified, * the window is centered on the center of the screen * * @param x - Position px Horizontal * @param y - Position px Vertical */ setPosition(x, y) { const w = this.ow.innerWidth, h = this.ow.innerHeight; let left = w / 2 - this.dialog.offsetWidth / 2, top = h / 2 - this.dialog.offsetHeight / 2; if (left < 0) { left = 0; } if (top < 0) { top = 0; } if (x !== undefined && y !== undefined) { this.offsetX = x; this.offsetY = y; this.moved = Math.abs(x - left) > 100 || Math.abs(y - top) > 100; } this.dialog.style.left = (x || left) + 'px'; this.dialog.style.top = (y || top) + 'px'; return this; } /** * Specifies the dialog box title . It can take a string and an array of objects * * @param content - A string or an HTML element , * or an array of strings and elements * @example * ```javascript * var dialog = new Jodi.modules.Dialog(parent); * dialog.setHeader('Hello world'); * dialog.setHeader(['Hello world', '<button>OK</button>', $('<div>some</div>')]); * dialog.open(); * ``` */ setHeader(content) { this.setElements(this.dialogbox_header, content); return this; } /** * It specifies the contents of the dialog box. It can take a string and an array of objects * * @param content - A string or an HTML element , * or an array of strings and elements * @example * ```javascript * var dialog = new Jodi.modules.Dialog(parent); * dialog.setHeader('Hello world'); * dialog.setContent('<form onsubmit="alert(1);"><input type="text" /></form>'); * dialog.open(); * ``` */ setContent(content) { this.setElements(this.dialogbox_content, content); return this; } /** * Sets the bottom of the dialog. It can take a string and an array of objects * * @param content - A string or an HTML element , * or an array of strings and elements * @example * ```javascript * var dialog = new Jodi.modules.Dialog(parent); * dialog.setHeader('Hello world'); * dialog.setContent('<form><input id="someText" type="text" /></form>'); * dialog.setFooter([ * $('<a class="jodit-button">OK</a>').click(function () { * alert($('someText').val()) * dialog.close(); * }) * ]); * dialog.open(); * ``` */ setFooter(content) { this.setElements(this.dialogbox_footer, content); this.setMod('footer', Boolean(content)); return this; } /** * Get zIndex from dialog */ getZIndex() { return parseInt(css(this.container, 'zIndex'), 10) || 0; } /** * Get dialog instance with maximum z-index displaying it on top of all the dialog boxes */ getMaxZIndexDialog() { let maxZi = 0, dlg, zIndex, res = this; $$('.jodit-dialog', this.destination).forEach((dialog) => { dlg = dialog.component; zIndex = parseInt(css(dialog, 'zIndex'), 10); if (dlg.isOpened && !isNaN(zIndex) && zIndex > maxZi) { res = dlg; maxZi = zIndex; } }); return res; } /** * Sets the maximum z-index dialog box, displaying it on top of all the dialog boxes */ setMaxZIndex() { if (this.getMod('static')) return; let maxZIndex = 20000004, zIndex = 0; $$('.jodit-dialog', this.destination).forEach(dialog => { zIndex = parseInt(css(dialog, 'zIndex'), 10); maxZIndex = Math.max(isNaN(zIndex) ? 0 : zIndex, maxZIndex); }); this.container.style.zIndex = (maxZIndex + 1).toString(); } /** * Expands the dialog on full browser window */ toggleFullSize(isFullSize) { if (isVoid(isFullSize)) { isFullSize = !this.getMod('fullsize'); } this.setMod('fullsize', isFullSize); super.toggleFullSize(isFullSize); } /** * It opens a dialog box to center it, and causes the two event. * * @param contentOrClose - specifies the contents of the dialog box. * Can be false or undefined. see [[Dialog.setContent]] * @param title - specifies the title of the dialog box, @see setHeader * @param destroyAfterClose - true - After closing the window , the destructor will be called. * @param modal - true window will be opened in modal mode */ open(contentOrClose, titleOrModal, destroyAfterClose, modal) { eventEmitter.fire('closeAllPopups hideHelpers'); /** * Called before the opening of the dialog box */ if (this.e.fire(this, 'beforeOpen') === false) { return this; } if (isBoolean(contentOrClose)) { destroyAfterClose = contentOrClose; } if (isBoolean(titleOrModal)) { modal = titleOrModal; } this.destroyAfterClose = destroyAfterClose === true; const content = isBoolean(contentOrClose) ? undefined : contentOrClose; const title = isBoolean(titleOrModal) ? undefined : titleOrModal; if (title !== undefined) { this.setHeader(title); } if (content) { this.setContent(content); } this.setMod('active', true); this.isOpened = true; this.setModal(modal); this.destination.appendChild(this.container); if (this.getMod('static') !== true) { this.setPosition(this.offsetX, this.offsetY); this.setMaxZIndex(); } else { this.container.style.removeProperty('z-index'); } if (this.o.fullsize) { this.toggleFullSize(true); } /** * Called after the opening of the dialog box */ this.e.fire('afterOpen', this); return this; } /** * Set modal mode */ setModal(modal) { this.isModal = Boolean(modal); this.setMod('modal', this.isModal); return this; } /**** * Closes the dialog box , if you want to call the method `destruct` * * @see destroy * @example * ```javascript * //You can close dialog two ways * var dialog = new Jodit.modules.Dialog(); * dialog.open('Hello world!', 'Title'); * var $close = dialog.create.fromHTML('<a href="#" style="float:left;" class="jodit-button"> * <i class="icon icon-check"></i>&nbsp;' + Jodit.prototype.i18n('Ok') + '</a>'); * $close.addEventListener('click', function () { * dialog.close(); * }); * dialog.setFooter($close); * // and second way, you can close dialog from content * dialog.open('<a onclick="var event = doc.createEvent('HTMLEvents'); event.initEvent('close_dialog', true, true); * this.dispatchEvent(event)">Close</a>', 'Title'); * ``` */ close() { if (this.isDestructed || !this.isOpened || this.getMod('static') === true) { return this; } const { e } = this; /** * Called up to close the window */ if (e.fire(this, 'beforeClose') === false || e.fire('beforeClose', this) === false) { return this; } this.setMod('active', false); this.isOpened = false; if (this.isFullSize) { this.toggleFullSize(false); } Dom.safeRemove(this.container); this.__removeGlobalResizeListeners(); /** * It called after the window is closed */ e.fire(this, 'afterClose'); e.fire(this.ow, 'joditCloseDialog'); if (this.destroyAfterClose) { this.destruct(); } return this; } constructor(options = {}) { super(options); this.destroyAfterClose = false; this.moved = false; this.resizable = false; this.draggable = false; this.startX = 0; this.startY = 0; this.startPoint = { x: 0, y: 0, w: 0, h: 0 }; this.lockSelect = () => { this.setMod('moved', true); }; this.unlockSelect = () => { this.setMod('moved', false); }; this.__onResize = () => { if (this.options && this.o.resizable && !this.moved && this.isOpened && !this.offsetX && !this.offsetY) { this.setPosition(); } }; this.isModal = false; /** * True, if dialog was opened */ this.isOpened = false; const self = this; self.options = ConfigProto(options, ConfigProto(Config.prototype.dialog, Dialog_1.defaultOptions)); Dom.safeRemove(self.container); const n = this.getFullElName.bind(this); self.container = this.c.fromHTML(`<div class="jodit jodit-dialog ${this.componentName}"> <div class="${n('overlay')}"></div> <div class="${this.getFullElName('panel')}"> <div class="${n('header')}"> <div class="${n('header-title')}"></div> <div class="${n('header-toolbar')}"></div> </div> <div class="${n('content')}"></div> <div class="${n('footer')}"></div> <div class="${n('resizer')}">${Icon.get('resize_handler')}</div> </div> </div>`); if (self.options.direction === 'rtl') { self.container.style.direction = 'rtl'; self.container.setAttribute('dir', 'rtl'); } if (this.o.zIndex) { this.container.style.zIndex = this.o.zIndex.toString(); } attr(self.container, 'role', 'dialog'); Object.defineProperty(self.container, 'component', { value: this }); self.setMod('theme', self.o.theme || 'default').setMod('resizable', Boolean(self.o.resizable)); const dialog = self.getElm('panel'); assert(dialog != null, 'Panel element does not exist'); const resizer = self.getElm('resizer'); assert(resizer != null, 'Resizer element does not exist'); const dialogbox_header = self.getElm('header-title'); assert(dialogbox_header != null, 'header-title element does not exist'); const dialogbox_content = self.getElm('content'); assert(dialogbox_content != null, 'Content element does not exist'); const dialogbox_footer = self.getElm('footer'); assert(dialogbox_footer != null, 'Footer element does not exist'); const dialogbox_toolbar = self.getElm('header-toolbar'); assert(dialogbox_toolbar != null, 'header-toolbar element does not exist'); this.dialog = dialog; this.resizer = resizer; this.dialogbox_header = dialogbox_header; this.dialogbox_content = dialogbox_content; this.dialogbox_footer = dialogbox_footer; this.dialogbox_toolbar = dialogbox_toolbar; css(self.dialog, { maxWidth: self.options.maxWidth, minHeight: self.options.minHeight, minWidth: self.options.minWidth }); const headerBox = self.getElm('header'); headerBox && self.e.on(headerBox, 'pointerdown touchstart', self.__onHeaderMouseDown); self.e.on(self.resizer, 'mousedown touchstart', self.__onResizerMouseDown); const fullSize = pluginSystem.get('fullsize'); isFunction(fullSize) && fullSize(self); this.e .on(self.container, 'close_dialog', self.close) .on(this.ow, 'keydown', this.__onEsc) .on(this.ow, 'resize', this.__onResize); if (this.o.closeOnClickOverlay) { const overlay = self.getElm('overlay'); assert(overlay != null, 'Overlay element does not exist'); this.e.on(overlay, 'click', self.close); } } /** * Build toolbar after ready */ buildToolbar() { this.o.buttons && this.toolbar .build(splitArray(this.o.buttons)) .setMod('mode', 'header') .appendTo(this.dialogbox_toolbar); } /** * It destroys all objects created for the windows and also includes all the handlers for the window object */ destruct() { if (this.isInDestruct) { return; } this.setStatus(STATUSES.beforeDestruct); if (this.isOpened) { this.close(); } if (this.events) { this.__removeGlobalResizeListeners(); this.events .off(this.container, 'close_dialog', self.close) .off(this.ow, 'keydown', this.__onEsc) .off(this.ow, 'resize', this.__onResize); } super.destruct(); } }; __decorate([ autobind ], Dialog.prototype, "__onMouseUp", null); __decorate([ autobind ], Dialog.prototype, "__onHeaderMouseDown", null); __decorate([ autobind ], Dialog.prototype, "__onMouseMove", null); __decorate([ autobind ], Dialog.prototype, "__onEsc", null); __decorate([ autobind ], Dialog.prototype, "__onResizerMouseDown", null); __decorate([ autobind ], Dialog.prototype, "close", null); __decorate([ hook('ready') ], Dialog.prototype, "buildToolbar", null); Dialog = Dialog_1 = __decorate([ component ], Dialog); export { Dialog }; Dialog.defaultOptions = { ...View.defaultOptions, closeOnClickOverlay: false, closeOnEsc: true };