jodit
Version:
Jodit is an awesome and useful wysiwyg editor with filebrowser
451 lines (450 loc) • 17.1 kB
JavaScript
/*!
* Jodit Editor (https://xdsoft.net/jodit/)
* Released under MIT see LICENSE.txt in the project root for license information.
* Copyright (c) 2013-2026 Valerii 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;
};
import { STATUSES } from "../../../core/component/statuses.js";
import { autobind } from "../../../core/decorators/autobind/autobind.js";
import { cacheHTML } from "../../../core/decorators/cache/cache.js";
import { component, getComponentClass } from "../../../core/decorators/component/component.js";
import { watch } from "../../../core/decorators/watch/watch.js";
import { Dom } from "../../../core/dom/dom.js";
import { isArray } from "../../../core/helpers/checker/is-array.js";
import { isFunction } from "../../../core/helpers/checker/is-function.js";
import { isJoditObject } from "../../../core/helpers/checker/is-jodit-object.js";
import { isPlainObject } from "../../../core/helpers/checker/is-plain-object.js";
import { isString } from "../../../core/helpers/checker/is-string.js";
import { position } from "../../../core/helpers/size/index.js";
import { camelCase } from "../../../core/helpers/string/camel-case.js";
import { assert } from "../../../core/helpers/utils/assert.js";
import { attr } from "../../../core/helpers/utils/attr.js";
import { call, keys } from "../../../core/helpers/utils/utils.js";
import { UIButton, UIButtonState } from "../../../core/ui/button/index.js";
import { findControlType } from "../../../core/ui/helpers/get-control-type.js";
import { Icon } from "../../../core/ui/icon.js";
import { Popup } from "../../../core/ui/popup/popup.js";
import { makeCollection } from "../factory.js";
let ToolbarButton = class ToolbarButton extends UIButton {
className() {
return 'ToolbarButton';
}
getRole() {
return 'listitem';
}
updateAriaLabel() {
super.updateAriaLabel();
if (this.trigger) {
const i8nTooltip = this.state.tooltip
? this.jodit.i18n(this.state.tooltip)
: null;
attr(this.trigger, 'aria-label', i8nTooltip);
}
}
/**
* Get parent toolbar
*/
get toolbar() {
const ToolbarCollection = getComponentClass('ToolbarCollection');
return this.closest(ToolbarCollection);
}
update() {
var _a, _b;
const { control, state } = this;
const tc = this.toolbar;
if (!tc) {
return;
}
const value = (_a = control.value) === null || _a === void 0 ? void 0 : _a.call(control, tc.jodit, this);
if (value !== undefined) {
state.value = value;
}
state.disabled = this.__calculateDisabledStatus(tc);
state.activated = this.__calculateActivatedStatus(tc);
(_b = control.update) === null || _b === void 0 ? void 0 : _b.call(control, tc.jodit, this);
}
/**
* Calculates whether the button is active
*/
__calculateActivatedStatus(tc) {
var _a, _b;
if (isJoditObject(this.j) && !this.j.editorIsActive) {
return false;
}
if ((_b = (_a = this.control).isActive) === null || _b === void 0 ? void 0 : _b.call(_a, this.j, this)) {
return true;
}
return Boolean(tc && tc.shouldBeActive(this));
}
/**
* Calculates whether an element is blocked for the user
*/
__calculateDisabledStatus(tc) {
var _a, _b;
if (this.j.o.disabled) {
return true;
}
if (this.j.o.readonly &&
(!this.j.o.activeButtonsInReadOnly ||
!this.j.o.activeButtonsInReadOnly.includes(this.control.name))) {
return true;
}
if ((_b = (_a = this.control).isDisabled) === null || _b === void 0 ? void 0 : _b.call(_a, this.j, this)) {
return true;
}
return Boolean(tc && tc.shouldBeDisabled(this));
}
onChangeActivated() {
attr(this.button, 'aria-pressed', this.state.activated);
super.onChangeActivated();
}
onChangeText() {
if (isFunction(this.control.template)) {
this.text.innerHTML = this.control.template(this.j, this.control.name, this.j.i18n(this.state.text));
}
else {
super.onChangeText();
}
this.setMod('text-icons', Boolean(this.text.innerText.trim().length));
}
onChangeTabIndex() {
attr(this.button, 'tabindex', this.state.tabIndex);
}
createContainer() {
const cn = this.componentName;
const container = this.j.c.span(cn);
const button = super.createContainer();
button.classList.remove(cn);
button.classList.add(cn + '__button');
Object.defineProperty(button, 'component', {
value: this
});
container.appendChild(button);
const trigger = this.j.c.fromHTML(`<span role="button" aria-haspopup="true" aria-expanded="false" class="${cn}__trigger">${Icon.get('chevron')}</span>`);
// For caching
button.appendChild(trigger);
return container;
}
/** @override */
focus() {
var _a;
(_a = this.container.querySelector('button')) === null || _a === void 0 ? void 0 : _a.focus();
}
onChangeHasTrigger() {
if (this.state.hasTrigger) {
this.container.appendChild(this.trigger);
}
else {
Dom.safeRemove(this.trigger);
}
this.setMod('with-trigger', this.state.hasTrigger || null);
}
/** @override */
onChangeDisabled() {
const disabled = this.state.disabled ? 'disabled' : null;
attr(this.trigger, 'disabled', disabled);
attr(this.button, 'disabled', disabled);
attr(this.container, 'disabled', disabled);
}
constructor(jodit, control, target = null) {
super(jodit);
this.control = control;
this.target = target;
this.state = {
...UIButtonState(),
theme: 'toolbar',
currentValue: '',
hasTrigger: false
};
this.openedPopup = null;
const button = this.getElm('button');
assert(button, 'Element button should exists');
this.button = button;
Object.defineProperty(button, 'component', {
value: this,
configurable: true
});
const trigger = this.getElm('trigger');
assert(trigger, 'Element trigger should exists');
this.trigger = trigger;
trigger.remove();
// Prevent lost focus
jodit.e.on([this.button, this.trigger], 'mousedown', (e) => e.preventDefault());
this.onAction(this.onClick);
this.hookStatus(STATUSES.ready, () => {
this.__initFromControl();
this.update();
});
if (control.mods) {
Object.keys(control.mods).forEach(mod => {
control.mods && this.setMod(mod, control.mods[mod]);
});
}
}
/**
* Init constant data from control
*/
__initFromControl() {
const { control: ctr, state } = this;
this.updateSize();
state.name = ctr.name;
this.__initIconFromControl();
if (ctr.tooltip) {
state.tooltip = isFunction(ctr.tooltip)
? ctr.tooltip(this.j, ctr, this)
: ctr.tooltip;
}
state.hasTrigger = Boolean(ctr.list || (ctr.popup && ctr.exec));
}
__initIconFromControl() {
var _a;
const { control: ctr, state } = this;
const { textIcons } = this.j.o;
if (textIcons === true ||
(isFunction(textIcons) && textIcons(ctr.name)) ||
ctr.template) {
state.icon = UIButtonState().icon;
state.text = ctr.text || ctr.name;
return;
}
if (!isString(ctr.icon) && ctr.icon != null) {
state.icon = {
name: ctr.icon.name || ctr.name,
iconURL: ctr.icon.iconURL || '',
fill: ctr.icon.fill || '',
scale: ctr.icon.scale
};
return;
}
if (ctr.iconURL) {
state.icon.iconURL = ctr.iconURL;
}
else {
const name = ctr.icon || ctr.name;
state.icon.name =
Icon.exists(name) || ((_a = this.j.o.extraIcons) === null || _a === void 0 ? void 0 : _a[name]) ? name : '';
}
if (!ctr.iconURL && !state.icon.name) {
state.text = ctr.text || ctr.name;
}
}
/**
* Click on trigger button
*/
onTriggerClick(e) {
var _a, _b, _c;
if (this.openedPopup) {
this.__closePopup();
return;
}
const { control: ctr } = this;
attr(this.trigger, 'aria-expanded', true);
e.buffer = {
actionTrigger: this
};
if (ctr.list) {
return this.__openControlList(ctr);
}
if (isFunction(ctr.popup)) {
const popup = this.openPopup();
popup.parentElement = this;
try {
if (this.j.e.fire(camelCase(`before-${ctr.name}-open-popup`), this.target, ctr, popup) !== false) {
const target = (_c = (_b = (_a = this.toolbar) === null || _a === void 0 ? void 0 : _a.getTarget(this)) !== null && _b !== void 0 ? _b : this.target) !== null && _c !== void 0 ? _c : null;
const elm = ctr.popup(this.j, target, this.__closePopup, this);
if (elm) {
popup
.setContent(isString(elm) ? this.j.c.fromHTML(elm) : elm)
.open(() => position(this.container), false, this.j.o.allowTabNavigation
? this.container
: undefined);
}
else {
this.__closePopup();
}
}
}
catch (e) {
this.__closePopup();
throw e;
}
/**
* Fired after the popup was opened for some control button
*/
/**
* Close all opened popups
*/
this.j.e.fire(camelCase(`after-${ctr.name}-open-popup`), popup.container);
}
}
/**
* Create an open popup list
*/
__openControlList(control) {
var _a;
const controls = (_a = this.jodit.options.controls) !== null && _a !== void 0 ? _a : {}, getControl = (key) => findControlType(key, controls);
const list = control.list;
const menu = this.openPopup();
const toolbar = makeCollection(this.j);
menu.parentElement = this;
toolbar.parentElement = menu;
toolbar.mode = 'vertical';
const isListItem = (key) => isPlainObject(key) && 'title' in key && 'value' in key;
const getButton = (key, value) => {
if (isString(value) && getControl(value)) {
return {
name: value.toString(),
...getControl(value)
};
}
if (isString(key) && getControl(key)) {
return {
name: key.toString(),
...getControl(key),
...(typeof value === 'object' ? value : {})
};
}
if (isListItem(key)) {
value = key.value;
key = key.title;
}
const { childTemplate } = control;
const childControl = {
name: key.toString(),
template: childTemplate &&
((j, k, v) => childTemplate(j, k, v, this)),
exec: control.childExec
? (view, current, options) => {
var _a;
return (_a = control.childExec) === null || _a === void 0 ? void 0 : _a.call(control, view, current, {
...options,
parentControl: control
});
}
: control.exec,
data: control.data,
command: control.command,
isActive: control.isChildActive,
value: control.value,
isDisabled: control.isChildDisabled,
mode: control.mode,
args: [...(control.args ? control.args : []), key, value]
};
if (isString(value)) {
childControl.text = value;
}
return childControl;
};
toolbar.build(isArray(list)
? list.map(getButton)
: keys(list, false).map(key => getButton(key, list[key])), this.target);
menu.setContent(toolbar).open(() => position(this.container), false, this.j.o.allowTabNavigation ? this.container : undefined);
this.state.activated = true;
}
onOutsideClick(e) {
if (!this.openedPopup) {
return;
}
if (!e ||
!Dom.isNode(e.target) ||
(!Dom.isOrContains(this.container, e.target) &&
!this.openedPopup.isOwnClick(e))) {
this.__closePopup();
}
}
openPopup() {
this.__closePopup();
this.openedPopup = new Popup(this.j, false);
this.j.e
.on(this.ow, 'mousedown touchstart', this.onOutsideClick)
.on('escape closeAllPopups', this.onOutsideClick);
return this.openedPopup;
}
__closePopup() {
if (this.openedPopup) {
this.j.e
.off(this.ow, 'mousedown touchstart', this.onOutsideClick)
.off('escape closeAllPopups', this.onOutsideClick);
this.state.activated = false;
this.openedPopup.close();
this.openedPopup.destruct();
this.openedPopup = null;
if (this.trigger) {
attr(this.trigger, 'aria-expanded', false);
}
}
}
/**
* Click handler
*/
onClick(originalEvent) {
var _a, _b, _c, _d, _e, _f, _g;
const { control: ctr } = this;
if (isFunction(ctr.exec)) {
const target = (_c = (_b = (_a = this.toolbar) === null || _a === void 0 ? void 0 : _a.getTarget(this)) !== null && _b !== void 0 ? _b : this.target) !== null && _c !== void 0 ? _c : null;
const result = ctr.exec(this.j, target, {
control: ctr,
originalEvent,
button: this
});
// For memorise exec
if (result !== false && result !== true) {
(_e = (_d = this.j) === null || _d === void 0 ? void 0 : _d.e) === null || _e === void 0 ? void 0 : _e.fire('synchro');
if (this.parentElement) {
this.parentElement.update();
}
/**
* Fired after calling `button.exec` function
*/
(_g = (_f = this.j) === null || _f === void 0 ? void 0 : _f.e) === null || _g === void 0 ? void 0 : _g.fire('closeAllPopups afterExec');
}
if (result !== false) {
return;
}
}
if (ctr.list) {
return this.__openControlList(ctr);
}
if (isFunction(ctr.popup)) {
return this.onTriggerClick(originalEvent);
}
if (ctr.command || ctr.name) {
call(isJoditObject(this.j)
? this.j.execCommand.bind(this.j)
: this.j.od.execCommand.bind(this.j.od), ctr.command || ctr.name, false, ctr.args && ctr.args[0]);
this.j.e.fire('closeAllPopups');
}
}
destruct() {
this.__closePopup();
return super.destruct();
}
};
__decorate([
cacheHTML
], ToolbarButton.prototype, "createContainer", null);
__decorate([
watch('state.hasTrigger', { immediately: false })
], ToolbarButton.prototype, "onChangeHasTrigger", null);
__decorate([
watch('trigger:click')
], ToolbarButton.prototype, "onTriggerClick", null);
__decorate([
autobind
], ToolbarButton.prototype, "onOutsideClick", null);
__decorate([
autobind
], ToolbarButton.prototype, "__closePopup", null);
ToolbarButton = __decorate([
component
], ToolbarButton);
export { ToolbarButton };