inital commit

This commit is contained in:
2026-01-01 15:25:19 +05:30
commit f0ae49465a
36361 changed files with 4894111 additions and 0 deletions

View File

@@ -0,0 +1,31 @@
/*!
* 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
*/
/**
* [[include:modules/context-menu/README.md]]
* @packageDocumentation
* @module modules/context-menu
*/
import type { IContextMenu, IContextMenuAction } from "../../types/index";
import { Popup } from "../../core/ui/popup/popup";
/**
* Module to generate context menu
*/
export declare class ContextMenu extends Popup implements IContextMenu {
/** @override */
className(): string;
/**
* Generate and show context menu
*
* @param x - Global coordinate by X
* @param y - Global coordinate by Y
* @param actions - Array with plain objects `{icon: 'bin', title: 'Delete', exec: function () {}}`
* @example
* ```javascript
* parent.show(e.clientX, e.clientY, [{icon: 'bin', title: 'Delete', exec: function () { alert(1) }}]);
* ```
*/
show(x: number, y: number, actions: Array<false | IContextMenuAction>): void;
}

View File

@@ -0,0 +1,67 @@
/*!
* 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;
};
import { component } from "../../core/decorators/component/component.js";
import { isArray } from "../../core/helpers/checker/is-array.js";
import { Button } from "../../core/ui/button/button/button.js";
import { Popup } from "../../core/ui/popup/popup.js";
/**
* Module to generate context menu
*/
let ContextMenu = class ContextMenu extends Popup {
/** @override */
className() {
return 'ContextMenu';
}
/**
* Generate and show context menu
*
* @param x - Global coordinate by X
* @param y - Global coordinate by Y
* @param actions - Array with plain objects `{icon: 'bin', title: 'Delete', exec: function () {}}`
* @example
* ```javascript
* parent.show(e.clientX, e.clientY, [{icon: 'bin', title: 'Delete', exec: function () { alert(1) }}]);
* ```
*/
show(x, y, actions) {
const self = this;
self.clear();
if (!isArray(actions)) {
return;
}
actions.forEach(item => {
if (!item) {
return;
}
const action = Button(this.jodit, item.icon || 'empty', item.title);
this.jodit && action.setParentView(this.jodit);
action.setMod('context', 'menu');
action.onAction((e) => {
var _a;
(_a = item.exec) === null || _a === void 0 ? void 0 : _a.call(self, e);
self.clear();
self.close();
return false;
});
this.append(action);
});
this.open(() => ({ left: x, top: y, width: 0, height: 0 }), true);
}
};
ContextMenu = __decorate([
component
], ContextMenu);
export { ContextMenu };

24
node_modules/jodit/esm/modules/dialog/alert.d.ts generated vendored Normal file
View File

@@ -0,0 +1,24 @@
/*!
* 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
*/
/**
* @module modules/dialog
*/
import type { IDialog } from "../../types/index";
/**
* Show `alert` dialog. Work without Jodit object
* @example
* ```javascript
* Jodit.Alert("File was uploaded");
* Jodit.Alert("File was uploaded", "Message");
* Jodit.Alert("File was uploaded", function() {
* $('form').hide();
* });
* Jodit.Alert("File wasn't uploaded", "Error", function() {
* $('form').hide();
* });
* ```
*/
export declare function Alert(this: IDialog | unknown, msg: string | HTMLElement, title?: string | (() => void | false), callback?: string | ((dialog: IDialog) => void | false), className?: string): IDialog;

45
node_modules/jodit/esm/modules/dialog/alert.js generated vendored Normal file
View File

@@ -0,0 +1,45 @@
/*!
* 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
*/
import { Dom } from "../../core/dom/dom.js";
import { asArray } from "../../core/helpers/array/as-array.js";
import { isFunction } from "../../core/helpers/checker/is-function.js";
import { Button } from "../../core/ui/button/button/button.js";
import { Dialog } from "./dialog.js";
/**
* Show `alert` dialog. Work without Jodit object
* @example
* ```javascript
* Jodit.Alert("File was uploaded");
* Jodit.Alert("File was uploaded", "Message");
* Jodit.Alert("File was uploaded", function() {
* $('form').hide();
* });
* Jodit.Alert("File wasn't uploaded", "Error", function() {
* $('form').hide();
* });
* ```
*/
export function Alert(msg, title, callback, className = 'jodit-dialog_alert') {
if (isFunction(title)) {
callback = title;
title = undefined;
}
const dialog = this instanceof Dialog
? this
: new Dialog({ closeOnClickOverlay: true }), container = dialog.c.div(className), okButton = Button(dialog, 'ok', 'Ok');
asArray(msg).forEach(oneMessage => {
container.appendChild(Dom.isNode(oneMessage) ? oneMessage : dialog.c.fromHTML(oneMessage));
});
okButton.onAction(() => {
if (!callback || !isFunction(callback) || callback(dialog) !== false) {
dialog.close();
}
});
dialog.setFooter([okButton]);
dialog.open(container, title || '&nbsp;', true, true);
okButton.focus();
return dialog;
}

24
node_modules/jodit/esm/modules/dialog/confirm.d.ts generated vendored Normal file
View File

@@ -0,0 +1,24 @@
/*!
* 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
*/
/**
* @module modules/dialog
*/
import type { IDialog } from "../../types/index";
/**
* Show `confirm` dialog. Work without Jodit object
*
* @param title - Title or callback
* @param callback - callback. The first argument is the value entered
* @example
* ```javascript
* Jodit.Confirm("Are you sure?", "Confirm Dialog", function (yes) {
* if (yes) {
* // do something
* }
* });
* ```
*/
export declare function Confirm(this: IDialog | unknown, msg: string, title: string | ((yes: boolean) => void) | undefined, callback?: (yes: boolean) => void | false): IDialog;

50
node_modules/jodit/esm/modules/dialog/confirm.js generated vendored Normal file
View File

@@ -0,0 +1,50 @@
/*!
* 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
*/
import { isFunction } from "../../core/helpers/checker/is-function.js";
import { Button } from "../../core/ui/button/button/button.js";
import { Dialog } from "./dialog.js";
/**
* Show `confirm` dialog. Work without Jodit object
*
* @param title - Title or callback
* @param callback - callback. The first argument is the value entered
* @example
* ```javascript
* Jodit.Confirm("Are you sure?", "Confirm Dialog", function (yes) {
* if (yes) {
* // do something
* }
* });
* ```
*/
export function Confirm(msg, title, callback) {
const dialog = this instanceof Dialog
? this
: new Dialog({ closeOnClickOverlay: true }), $div = dialog.c.fromHTML('<form class="jodit-dialog_prompt"></form>'), $label = dialog.c.element('label');
if (isFunction(title)) {
callback = title;
title = undefined;
}
$label.appendChild(dialog.c.fromHTML(msg));
$div.appendChild($label);
const action = (yes) => () => {
if (!callback || callback(yes) !== false) {
dialog.close();
}
};
const $cancel = Button(dialog, 'cancel', 'Cancel');
const $ok = Button(dialog, 'ok', 'Yes');
$cancel.onAction(action(false));
$ok.onAction(action(true));
dialog.e.on($div, 'submit', () => {
action(true)();
return false;
});
dialog.setFooter([$ok, $cancel]);
dialog.open($div, title || '&nbsp;', true, true);
$ok.focus();
return dialog;
}

181
node_modules/jodit/esm/modules/dialog/dialog.d.ts generated vendored Normal file
View File

@@ -0,0 +1,181 @@
/*!
* 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
*/
/**
* @module modules/dialog
*/
import type { Content, IDialog, IDialogOptions, IToolbarCollection } from "../../types/index";
import { ViewWithToolbar } from "../../core/view/view-with-toolbar";
declare module 'jodit/config' {
interface Config {
dialog: IDialogOptions;
}
}
/**
* Module to generate dialog windows
*/
export declare class Dialog extends ViewWithToolbar implements IDialog {
/** @override */
className(): string;
private readonly resizer;
toolbar: IToolbarCollection;
private offsetX?;
private offsetY?;
private get destination();
private destroyAfterClose;
private moved;
private resizable;
private draggable;
private startX;
private startY;
private startPoint;
private lockSelect;
private unlockSelect;
private setElements;
private __onMouseUp;
/**
*
*/
private __onHeaderMouseDown;
private __onMouseMove;
private __onEsc;
private __onResize;
private __onResizerMouseDown;
private __addGlobalResizeListeners;
private __removeGlobalResizeListeners;
OPTIONS: IDialogOptions;
readonly dialog: HTMLElement;
workplace: HTMLDivElement;
private readonly dialogbox_header;
private readonly dialogbox_content;
private readonly dialogbox_footer;
private readonly dialogbox_toolbar;
/**
* Specifies the size of the window
*
* @param w - The width of the window
* @param h - The height of the window
*/
setSize(w?: number | string, h?: number | string): this;
/**
* Recalculate auto sizes
*/
calcAutoSize(): 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?: number, y?: number): 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: Content): 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: Content): 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: Content): this;
/**
* Get zIndex from dialog
*/
getZIndex(): number;
/**
* Get dialog instance with maximum z-index displaying it on top of all the dialog boxes
*/
getMaxZIndexDialog(): IDialog;
/**
* Sets the maximum z-index dialog box, displaying it on top of all the dialog boxes
*/
setMaxZIndex(): void;
/**
* Expands the dialog on full browser window
*/
toggleFullSize(isFullSize?: boolean): void;
open(destroyAfterClose: boolean): this;
open(destroyAfterClose: boolean, modal: boolean): this;
open(content?: Content, title?: Content, destroyAfterClose?: boolean, modal?: boolean): this;
private isModal;
/**
* Set modal mode
*/
setModal(modal: undefined | boolean): this;
/**
* True, if dialog was opened
*/
isOpened: boolean;
/****
* 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(): this;
constructor(options?: Partial<IDialogOptions>);
/**
* Build toolbar after ready
*/
protected buildToolbar(): void;
/**
* It destroys all objects created for the windows and also includes all the handlers for the window object
*/
destruct(): void;
static defaultOptions: IDialogOptions;
}

629
node_modules/jodit/esm/modules/dialog/dialog.js generated vendored Normal file
View File

@@ -0,0 +1,629 @@
/*!
* 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();
}
};
Dialog.defaultOptions = {
...View.defaultOptions,
closeOnClickOverlay: false,
closeOnEsc: true
};
__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 };

14
node_modules/jodit/esm/modules/dialog/index.d.ts generated vendored Normal file
View File

@@ -0,0 +1,14 @@
/*!
* 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
*/
/**
* [[include:modules/dialog/README.md]]
* @packageDocumentation
* @module modules/dialog
*/
export { Alert } from "./alert";
export { Confirm } from "./confirm";
export { Dialog } from "./dialog";
export { Prompt } from "./prompt";

14
node_modules/jodit/esm/modules/dialog/index.js generated vendored Normal file
View File

@@ -0,0 +1,14 @@
/*!
* 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
*/
/**
* [[include:modules/dialog/README.md]]
* @packageDocumentation
* @module modules/dialog
*/
export { Alert } from "./alert.js";
export { Confirm } from "./confirm.js";
export { Dialog } from "./dialog.js";
export { Prompt } from "./prompt.js";

28
node_modules/jodit/esm/modules/dialog/prompt.d.ts generated vendored Normal file
View File

@@ -0,0 +1,28 @@
/*!
* 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
*/
/**
* @module modules/dialog
*/
import type { IDialog } from "../../types/index";
/**
* Show `Prompt` dialog. Work without Jodit object
*
* @param msg - Dialog content
* @param title - Title or callback
* @param callback - callback. The first argument is the value entered
* @param placeholder - Placeholder for input
* @example
* ```javascript
* Jodit.Prompt("Enter your name", "Prompt Dialog", function (name) {
* if (name.length < 3) {
* Jodit.Alert("The name must be at least 3 letters");
* return false;
* }
* // do something
* });
* ```
*/
export declare function Prompt(this: IDialog | unknown, msg: string, title: string | (() => false | void) | undefined, callback: (value: string) => false | void, placeholder?: string, defaultValue?: string): IDialog;

68
node_modules/jodit/esm/modules/dialog/prompt.js generated vendored Normal file
View File

@@ -0,0 +1,68 @@
/*!
* 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
*/
import { isFunction } from "../../core/helpers/checker/is-function.js";
import { attr } from "../../core/helpers/utils/attr.js";
import { Button } from "../../core/ui/button/button/button.js";
import { Dialog } from "./dialog.js";
/**
* Show `Prompt` dialog. Work without Jodit object
*
* @param msg - Dialog content
* @param title - Title or callback
* @param callback - callback. The first argument is the value entered
* @param placeholder - Placeholder for input
* @example
* ```javascript
* Jodit.Prompt("Enter your name", "Prompt Dialog", function (name) {
* if (name.length < 3) {
* Jodit.Alert("The name must be at least 3 letters");
* return false;
* }
* // do something
* });
* ```
*/
export function Prompt(msg, title, callback, placeholder, defaultValue) {
const dialog = this instanceof Dialog
? this
: new Dialog({ closeOnClickOverlay: true }), cancelButton = Button(dialog, 'cancel', 'Cancel'), okButton = Button(dialog, 'ok', 'Ok'), form = dialog.c.element('form', {
class: 'jodit-dialog_prompt'
}), inputElement = dialog.c.element('input', {
autofocus: true,
class: 'jodit-input'
}), labelElement = dialog.c.element('label');
if (isFunction(title)) {
callback = title;
title = undefined;
}
if (placeholder) {
attr(inputElement, 'placeholder', placeholder);
}
labelElement.appendChild(dialog.c.text(msg));
form.appendChild(labelElement);
form.appendChild(inputElement);
cancelButton.onAction(dialog.close);
const onclick = () => {
if (!callback ||
!isFunction(callback) ||
callback(inputElement.value) !== false) {
dialog.close();
}
};
okButton.onAction(onclick);
dialog.e.on(form, 'submit', () => {
onclick();
return false;
});
dialog.setFooter([okButton, cancelButton]);
dialog.open(form, title || '&nbsp;', true, true);
inputElement.focus();
if (defaultValue !== undefined && defaultValue.length) {
inputElement.value = defaultValue;
inputElement.select();
}
return dialog;
}

View File

@@ -0,0 +1,11 @@
/*!
* 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
*/
/**
* @module modules/file-browser
*/
import type { IFileBrowser } from "../../../types/index";
declare const _default: (self: IFileBrowser) => ((e: DragEvent) => boolean | void);
export default _default;

View File

@@ -0,0 +1,161 @@
/*!
* 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
*/
import { Dom } from "../../../core/dom/index.js";
import { attr, error } from "../../../core/helpers/utils/index.js";
import { Icon } from "../../../core/ui/icon.js";
import { elementsMap } from "./elements-map.js";
import { makeContextMenu } from "../factories.js";
import { deleteFile } from "../fetch/delete-file.js";
import { loadTree } from "../fetch/load-tree.js";
import { elementToItem, getItem } from "../listeners/native-listeners.js";
import { openImageEditor } from "../../image-editor/image-editor.js";
const CLASS_PREVIEW = 'jodit-file-browser-preview', preview_tpl_next = (next = 'next', right = 'right') => `<div class="${CLASS_PREVIEW}__navigation ${CLASS_PREVIEW}__navigation_arrow_${next}">` +
'' +
Icon.get('angle-' + right) +
'</a>';
export default (self) => {
if (!self.o.contextMenu) {
return () => { };
}
const contextmenu = makeContextMenu(self);
return (e) => {
const a = getItem(e.target, self.container);
if (!a) {
return;
}
let item = a;
const opt = self.options, ga = (key) => attr(item, key) || '';
self.async.setTimeout(() => {
const selectedItem = elementToItem(a, elementsMap(self));
if (!selectedItem) {
return;
}
self.state.activeElements = [selectedItem];
contextmenu.show(e.clientX, e.clientY, [
ga('data-is-file') !== '1' &&
opt.editImage &&
(self.dataProvider.canI('ImageResize') ||
self.dataProvider.canI('ImageCrop'))
? {
icon: 'pencil',
title: 'Edit',
exec: () => openImageEditor.call(self, ga('href'), ga('data-name'), ga('data-path'), ga('data-source'))
}
: false,
self.dataProvider.canI('FileRename')
? {
icon: 'italic',
title: 'Rename',
exec: () => {
self.e.fire('fileRename.filebrowser', ga('data-name'), ga('data-path'), ga('data-source'));
}
}
: false,
self.dataProvider.canI('FileRemove')
? {
icon: 'bin',
title: 'Delete',
exec: async () => {
try {
await deleteFile(self, ga('data-name'), ga('data-source'));
}
catch (e) {
return self.status(e);
}
self.state.activeElements = [];
return loadTree(self).catch(self.status);
}
}
: false,
opt.preview
? {
icon: 'eye',
title: 'Preview',
exec: () => {
const preview = self.dlg({
buttons: ['fullsize', 'dialog.close']
}), temp_content = self.c.div(CLASS_PREVIEW, '<div class="jodit-icon_loader"></div>'), preview_box = self.c.div(CLASS_PREVIEW + '__box'), next = self.c.fromHTML(preview_tpl_next()), prev = self.c.fromHTML(preview_tpl_next('prev', 'left')), addLoadHandler = (src) => {
const image = self.c.element('img');
image.setAttribute('src', src);
const onload = () => {
var _a;
if (self.isInDestruct) {
return;
}
self.e.off(image, 'load');
Dom.detach(temp_content);
if (opt.showPreviewNavigation) {
if (Dom.prevWithClass(item, self.files.getFullElName('item'))) {
temp_content.appendChild(prev);
}
if (Dom.nextWithClass(item, self.files.getFullElName('item'))) {
temp_content.appendChild(next);
}
}
temp_content.appendChild(preview_box);
preview_box.appendChild(image);
preview.setPosition();
(_a = self === null || self === void 0 ? void 0 : self.events) === null || _a === void 0 ? void 0 : _a.fire('previewOpenedAndLoaded');
};
self.e.on(image, 'load', onload);
if (image.complete) {
onload();
}
};
self.e.on([next, prev], 'click', function () {
if (this === next) {
item = Dom.nextWithClass(item, self.files.getFullElName('item'));
}
else {
item = Dom.prevWithClass(item, self.files.getFullElName('item'));
}
if (!item) {
throw error('Need element');
}
Dom.detach(temp_content);
Dom.detach(preview_box);
temp_content.innerHTML =
'<div class="jodit-icon_loader"></div>';
addLoadHandler(ga('href'));
});
self.e.on('beforeDestruct', () => {
preview.destruct();
});
preview.container.classList.add(CLASS_PREVIEW + '__dialog');
preview.setContent(temp_content);
preview.setPosition();
preview.open();
addLoadHandler(ga('href'));
self.events
.on('beforeDestruct', () => {
preview.destruct();
})
.fire('previewOpened');
}
}
: false,
{
icon: 'upload',
title: 'Download',
exec: () => {
const url = ga('href');
if (url) {
self.ow.open(url);
}
}
}
]);
}, self.defaultTimeout);
self.e
.on('beforeClose', () => {
contextmenu.close();
})
.on('beforeDestruct', () => contextmenu.destruct());
e.stopPropagation();
e.preventDefault();
return false;
};
};

View File

@@ -0,0 +1,19 @@
/*!
* 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
*/
/**
* @module modules/file-browser
*/
import type { IDictionary, IFileBrowserItem, IViewBased } from "../../../types/index";
type ElementsMap = IDictionary<{
elm: HTMLElement;
item: IFileBrowserItem;
}>;
/**
* Returns a map of the file's key correspondence to its DOM element in the file browser
* @private
*/
export declare const elementsMap: (view: IViewBased) => ElementsMap;
export {};

View File

@@ -0,0 +1,18 @@
/*!
* 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
*/
const map = new WeakMap();
/**
* Returns a map of the file's key correspondence to its DOM element in the file browser
* @private
*/
export const elementsMap = (view) => {
let result = map.get(view);
if (!result) {
result = {};
map.set(view, result);
}
return result;
};

View File

@@ -0,0 +1,23 @@
/*!
* 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
*/
/**
* @module modules/file-browser
*/
import type { IFileBrowserItemElement, IFileBrowserItemWrapper, ISource } from "../../../types/index";
export declare class FileBrowserItem implements IFileBrowserItemWrapper {
readonly data: IFileBrowserItemElement;
source: ISource;
sourceName: string;
type: IFileBrowserItemWrapper['type'];
private constructor();
static create(data: IFileBrowserItemElement): FileBrowserItem & IFileBrowserItemElement;
get path(): string;
get imageURL(): string;
get fileURL(): string;
get time(): string;
get uniqueHashKey(): string;
toJSON(): object;
}

View File

@@ -0,0 +1,65 @@
/*!
* 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
*/
import { normalizePath, normalizeUrl } from "../../../core/helpers/index.js";
export class FileBrowserItem {
constructor(data) {
this.data = data;
// TODO Check with Object.assign
Object.keys(data).forEach(key => {
this[key] = data[key];
});
}
static create(data) {
if (data instanceof FileBrowserItem) {
return data;
}
return new FileBrowserItem(data);
}
get path() {
return normalizePath(this.data.source.path ? this.data.source.path + '/' : '/');
}
get imageURL() {
const timestamp = this.time || new Date().getTime().toString(), { thumbIsAbsolute, source, thumb, file } = this.data, path = thumb || file;
return thumbIsAbsolute && path
? path
: normalizeUrl(source.baseurl, source.path, path || '') +
'?_tmst=' +
encodeURIComponent(timestamp);
}
get fileURL() {
let { name } = this.data;
const { file, fileIsAbsolute, source } = this.data;
if (file !== undefined) {
name = file;
}
return fileIsAbsolute && name
? name
: normalizeUrl(source.baseurl, source.path, name || '');
}
get time() {
const { changed } = this.data;
return ((changed &&
(typeof changed === 'number'
? new Date(changed).toLocaleString()
: changed)) ||
'');
}
get uniqueHashKey() {
const data = this.data;
let key = [
data.sourceName,
data.name,
data.file,
this.time,
data.thumb
].join('_');
key = key.toLowerCase().replace(/[^0-9a-z\-.]/g, '-');
return key;
}
toJSON() {
return this.data;
}
}

View File

@@ -0,0 +1,15 @@
/*!
* 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
*/
/**
* @module modules/file-browser
*/
import type { IFileBrowserOptions } from "../../types/index";
import "../../core/request/config";
declare module 'jodit/config' {
interface Config {
filebrowser: IFileBrowserOptions;
}
}

332
node_modules/jodit/esm/modules/file-browser/config.js generated vendored Normal file
View File

@@ -0,0 +1,332 @@
/*!
* 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
*/
import "../../core/request/config.js";
import { isArray, isString } from "../../core/helpers/checker/index.js";
import { humanSizeToBytes } from "../../core/helpers/utils/human-size-to-bytes.js";
import { UIFileInput } from "../../core/ui/form/inputs/file/file.js";
import { Config } from "../../config.js";
Config.prototype.filebrowser = {
namespace: '',
cache: true,
extraButtons: [],
filter(item, search) {
search = search.toLowerCase();
if (isString(item)) {
return item.toLowerCase().indexOf(search) !== -1;
}
if (isString(item.name)) {
return item.name.toLowerCase().indexOf(search) !== -1;
}
if (isString(item.file)) {
return item.file.toLowerCase().indexOf(search) !== -1;
}
return true;
},
sortBy: 'changed-desc',
sort(a, b, sortBy) {
const [sortAttr, arrow] = sortBy.toLowerCase().split('-'), asc = arrow === 'asc';
const compareStr = (f, s) => {
if (f < s) {
return asc ? -1 : 1;
}
if (f > s) {
return asc ? 1 : -1;
}
return 0;
};
if (isString(a)) {
return compareStr(a.toLowerCase(), b.toLowerCase());
}
if (a[sortAttr] === undefined || sortAttr === 'name') {
if (isString(a.name)) {
return compareStr(a.name.toLowerCase(), b.name.toLowerCase());
}
if (isString(a.file)) {
return compareStr(a.file.toLowerCase(), b.file.toLowerCase());
}
return 0;
}
switch (sortAttr) {
case 'changed': {
const f = new Date(a.changed).getTime(), s = new Date(b.changed).getTime();
return asc ? f - s : s - f;
}
case 'size': {
const f = humanSizeToBytes(a.size), s = humanSizeToBytes(b.size);
return asc ? f - s : s - f;
}
}
return 0;
},
editImage: true,
preview: true,
showPreviewNavigation: true,
showSelectButtonInPreview: true,
contextMenu: true,
howLongShowMsg: 3000,
createNewFolder: true,
deleteFolder: true,
renameFolder: true,
moveFolder: true,
moveFile: true,
permissionsPresets: {
allowFileDownload: undefined,
allowFileMove: undefined,
allowFileRemove: undefined,
allowFileRename: undefined,
allowFileUpload: undefined,
allowFileUploadRemote: undefined,
allowFiles: undefined,
allowFolderCreate: undefined,
allowFolderMove: undefined,
allowFolderRemove: undefined,
allowFolderRename: undefined,
allowFolderTree: undefined,
allowFolders: undefined,
allowGeneratePdf: undefined,
allowImageCrop: undefined,
allowImageResize: undefined
},
showFoldersPanel: true,
storeLastOpenedFolder: true,
width: 859,
height: 400,
buttons: [
'filebrowser.upload',
'filebrowser.remove',
'filebrowser.update',
'filebrowser.select',
'filebrowser.edit',
'|',
'filebrowser.tiles',
'filebrowser.list',
'|',
'filebrowser.filter',
'|',
'filebrowser.sort'
],
removeButtons: [],
fullsize: false,
showTooltip: true,
view: null,
isSuccess(resp) {
return resp.success;
},
getMessage(resp) {
return resp.data.messages !== undefined && isArray(resp.data.messages)
? resp.data.messages.join(' ')
: '';
},
showFileName: true,
showFileSize: true,
showFileChangeTime: true,
saveStateInStorage: {
storeLastOpenedFolder: true,
storeView: true,
storeSortBy: true
},
pixelOffsetLoadNewChunk: 200,
getThumbTemplate(item, source, source_name) {
const opt = this.options, IC = this.files.getFullElName('item'), showName = opt.showFileName, showSize = opt.showFileSize && item.size, showTime = opt.showFileChangeTime && item.time;
let name = '';
if (item.file !== undefined) {
name = item.file;
}
const info = `<div class="${IC}-info">${showName ? `<span class="${IC}-info-filename">${name}</span>` : ''}${showSize
? `<span class="${IC}-info-filesize">${item.size}</span>`
: ''}${showTime
? `<span class="${IC}-info-filechanged">${showTime}</span>`
: ''}</div>`;
return `<a
data-jodit-file-browser-item="true"
data-is-file="${item.isImage ? 0 : 1}"
draggable="true"
class="${IC}"
href="${item.fileURL}"
data-source="${source_name}"
data-path="${item.path}"
data-name="${name}"
title="${name}"
data-url="${item.fileURL}">
<img
data-is-file="${item.isImage ? 0 : 1}"
data-src="${item.fileURL}"
src="${item.imageURL}"
alt="${name}"
loading="lazy"
/>
${showName || showSize || showTime ? info : ''}
</a>`;
},
ajax: {
...Config.prototype.defaultAjaxOptions,
url: '',
data: {},
cache: true,
contentType: 'application/x-www-form-urlencoded; charset=UTF-8',
method: 'POST',
processData: true,
headers: {},
prepareData(data) {
return data;
},
process(resp) {
return resp;
}
},
create: {
data: { action: 'folderCreate' }
},
getLocalFileByUrl: {
data: { action: 'getLocalFileByUrl' }
},
resize: {
data: { action: 'imageResize' }
},
crop: {
data: { action: 'imageCrop' }
},
fileMove: {
data: { action: 'fileMove' }
},
folderMove: {
data: { action: 'folderMove' }
},
fileRename: {
data: { action: 'fileRename' }
},
folderRename: {
data: { action: 'folderRename' }
},
fileRemove: {
data: { action: 'fileRemove' }
},
folderRemove: {
data: { action: 'folderRemove' }
},
items: {
data: { action: 'files' }
},
folder: {
data: { action: 'folders' }
},
permissions: {
data: { action: 'permissions' }
}
};
Config.prototype.controls.filebrowser = {
upload: {
icon: 'plus',
tooltip: 'Upload file',
isInput: true,
isDisabled: (browser) => !browser.dataProvider.canI('FileUpload'),
getContent: (filebrowser, btnInt) => {
const btn = new UIFileInput(filebrowser, {
tooltip: btnInt.control.tooltip,
onlyImages: filebrowser.state.onlyImages
});
filebrowser.e.fire('bindUploader.filebrowser', btn.container);
return btn.container;
}
},
remove: {
icon: 'bin',
tooltip: 'Remove file',
isDisabled: (browser) => {
return (!browser.state.activeElements.length ||
!browser.dataProvider.canI('FileRemove'));
},
exec: (editor) => {
editor.e.fire('fileRemove.filebrowser');
}
},
update: {
tooltip: 'Update file list',
exec: (editor) => {
editor.e.fire('update.filebrowser');
}
},
select: {
tooltip: 'Select file',
icon: 'check',
isDisabled: (browser) => !browser.state.activeElements.length,
exec: (editor) => {
editor.e.fire('select.filebrowser');
}
},
edit: {
tooltip: 'Edit image',
icon: 'pencil',
isDisabled: (browser) => {
const selected = browser.state.activeElements;
return (selected.length !== 1 ||
!selected[0].isImage ||
!(browser.dataProvider.canI('ImageCrop') ||
browser.dataProvider.canI('ImageResize')));
},
exec: editor => {
editor.e.fire('edit.filebrowser');
}
},
tiles: {
tooltip: 'Tiles view',
icon: 'th',
isActive: (filebrowser) => filebrowser.state.view === 'tiles',
exec: (filebrowser) => {
filebrowser.e.fire('view.filebrowser', 'tiles');
}
},
list: {
tooltip: 'List view',
icon: 'th-list',
isActive: (filebrowser) => filebrowser.state.view === 'list',
exec: (filebrowser) => {
filebrowser.e.fire('view.filebrowser', 'list');
}
},
filter: {
isInput: true,
getContent: (filebrowser, b) => {
const oldInput = b.container.querySelector('.jodit-input');
if (oldInput) {
return oldInput;
}
const input = filebrowser.c.element('input', {
class: 'jodit-input',
placeholder: filebrowser.i18n('Filter')
});
input.value = filebrowser.state.filterWord;
filebrowser.e.on(input, 'keydown mousedown', filebrowser.async.debounce(() => {
filebrowser.e.fire('filter.filebrowser', input.value);
}, filebrowser.defaultTimeout));
return input;
}
},
sort: {
isInput: true,
getContent: (fb) => {
const select = fb.c.fromHTML('<select class="jodit-input jodit-select">' +
`<option value="changed-asc">${fb.i18n('Sort by changed')} (⬆)</option>` +
`<option value="changed-desc">${fb.i18n('Sort by changed')} (⬇)</option>` +
`<option value="name-asc">${fb.i18n('Sort by name')} (⬆)</option>` +
`<option value="name-desc">${fb.i18n('Sort by name')} (⬇)</option>` +
`<option value="size-asc">${fb.i18n('Sort by size')} (⬆)</option>` +
`<option value="size-desc">${fb.i18n('Sort by size')} (⬇)</option>` +
'</select>');
select.value = fb.state.sortBy;
fb.e
.on('sort.filebrowser', (value) => {
if (select.value !== value) {
select.value = value;
}
})
.on(select, 'change', () => {
fb.e.fire('sort.filebrowser', select.value);
});
return select;
}
}
};

View File

@@ -0,0 +1,112 @@
/*!
* 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
*/
/**
* @module modules/file-browser
*/
import type { IFileBrowserAnswer, IFileBrowserDataProvider, IFileBrowserDataProviderItemsMods, IFileBrowserItem, IFileBrowserOptions, ImageBox, IPermissions, ISourcesFiles, IViewBased, Nullable } from "../../types/index";
export declare const DEFAULT_SOURCE_NAME = "default";
export default class DataProvider implements IFileBrowserDataProvider {
readonly parent: IViewBased;
readonly options: IFileBrowserOptions;
private __currentPermissions;
constructor(parent: IViewBased, options: IFileBrowserOptions);
/**
* Alias for options
*/
get o(): this['options'];
private __ajaxInstances;
protected get<T extends IFileBrowserAnswer = IFileBrowserAnswer>(name: keyof IFileBrowserOptions): Promise<T>;
private progressHandler;
onProgress(callback: (percentage: number) => void): void;
/**
* Load permissions for path and source
*/
permissions(path: string, source: string): Promise<Nullable<IPermissions>>;
canI(action: string): boolean;
private __items;
/**
* Load items list by path and source
*/
items(path: string, source: string, mods?: IFileBrowserDataProviderItemsMods): Promise<IFileBrowserItem[]>;
/**
* Load items list by path and source
*/
itemsEx(path: string, source: string, mods?: IFileBrowserDataProviderItemsMods): ReturnType<IFileBrowserDataProvider['itemsEx']>;
private generateItemsList;
tree(path: string, source: string): Promise<ISourcesFiles>;
/**
* Get path by url. You can use this method in another modules
*/
getPathByUrl(url: string): Promise<any>;
/**
* Create a directory on the server
*
* @param name - Name the new folder
* @param path - Relative directory in which you want create a folder
* @param source - Server source key
*/
createFolder(name: string, path: string, source: string): Promise<boolean>;
/**
* Move a file / directory on the server
*
* @param filepath - The relative path to the file / folder source
* @param path - Relative to the directory where you want to move the file / folder
*/
move(filepath: string, path: string, source: string, isFile: boolean): Promise<boolean>;
/**
* Deleting item
*
* @param path - Relative path
* @param file - The filename
* @param source - Source
*/
private remove;
/**
* Deleting a file
*
* @param path - Relative path
* @param file - The filename
* @param source - Source
*/
fileRemove(path: string, file: string, source: string): Promise<string>;
/**
* Deleting a folder
*
* @param path - Relative path
* @param file - The filename
* @param source - Source
*/
folderRemove(path: string, file: string, source: string): Promise<string>;
/**
* Rename action
*
* @param path - Relative path
* @param name - Old name
* @param newname - New name
* @param source - Source
*/
private rename;
/**
* Rename folder
*/
folderRename(path: string, name: string, newname: string, source: string): Promise<string>;
/**
* Rename file
*/
fileRename(path: string, name: string, newname: string, source: string): Promise<string>;
private changeImage;
/**
* Send command to server to crop image
*/
crop(path: string, source: string, name: string, newname: string | void, box: ImageBox | void): Promise<boolean>;
/**
* Send command to server to resize image
*/
resize(path: string, source: string, name: string, newname: string | void, box: ImageBox | void): Promise<boolean>;
getMessage(resp: IFileBrowserAnswer): string;
isSuccess(resp: IFileBrowserAnswer): boolean;
destruct(): any;
}

View File

@@ -0,0 +1,388 @@
/*!
* 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
*/
import { IS_PROD } from "../../core/constants.js";
import { abort, ConfigProto, error, isFunction, normalizeRelativePath, set } from "../../core/helpers/index.js";
import { Ajax } from "../../core/request/index.js";
import { FileBrowserItem } from "./builders/item.js";
export const DEFAULT_SOURCE_NAME = 'default';
const possibleRules = new Set([
'allowFiles',
'allowFileMove',
'allowFileUpload',
'allowFileUploadRemote',
'allowFileRemove',
'allowFileRename',
'allowFolders',
'allowFolderMove',
'allowFolderCreate',
'allowFolderRemove',
'allowFolderRename',
'allowImageResize',
'allowImageCrop'
]);
export default class DataProvider {
constructor(parent, options) {
this.parent = parent;
this.options = options;
this.__currentPermissions = null;
this.__ajaxInstances = new Map();
this.progressHandler = (ignore) => { };
}
/**
* Alias for options
*/
get o() {
return this.options;
}
get(name) {
const instances = this.__ajaxInstances;
if (instances.has(name)) {
const ajax = instances.get(name);
ajax === null || ajax === void 0 ? void 0 : ajax.abort();
instances.delete(name);
}
const opts = ConfigProto(this.options[name] !== undefined
? this.options[name]
: {}, ConfigProto({
onProgress: this.progressHandler
}, this.o.ajax));
if (opts.prepareData) {
opts.data = opts.prepareData.call(this, opts.data);
}
const ajax = new Ajax(opts);
instances.set(name, ajax);
const promise = ajax.send();
promise
.finally(() => {
ajax.destruct();
instances.delete(name);
this.progressHandler(100);
})
.catch(() => null);
return promise
.then(resp => resp.json())
.then(resp => {
if (resp && !this.isSuccess(resp)) {
throw new Error(this.getMessage(resp));
}
return resp;
});
}
onProgress(callback) {
this.progressHandler = callback;
}
/**
* Load permissions for path and source
*/
async permissions(path, source) {
if (!this.o.permissions) {
return null;
}
this.o.permissions.data.path = path;
this.o.permissions.data.source = source;
if (this.o.permissions.url) {
return this.get('permissions').then(resp => {
if (this.parent.isInDestruct) {
throw abort();
}
let process = this.o.permissions.process;
if (!process) {
process = this.o.ajax.process;
}
if (process) {
const respData = process.call(self, resp);
if (respData.data.permissions) {
this.parent.events.fire(this, 'changePermissions', this.__currentPermissions, respData.data.permissions);
this.__currentPermissions = respData.data.permissions;
}
}
return this.__currentPermissions;
});
}
return null;
}
canI(action) {
const rule = 'allow' + action;
if (!IS_PROD) {
if (!possibleRules.has(rule)) {
throw error('Wrong action ' + action);
}
}
const presetValue = this.o.permissionsPresets[rule];
if (presetValue !== undefined) {
return presetValue;
}
return (this.__currentPermissions == null ||
this.__currentPermissions[rule] === undefined ||
this.__currentPermissions[rule]);
}
__items(path, source, mods, onResult) {
const opt = this.options;
if (!opt.items) {
return Promise.reject(Error('Set Items api options'));
}
opt.items.data.path = path;
opt.items.data.source = source;
opt.items.data.mods = mods;
return this.get('items').then(resp => {
let process = this.o.items.process;
if (!process) {
process = this.o.ajax.process;
}
if (process) {
resp = process.call(self, resp);
}
return onResult(resp);
});
}
/**
* Load items list by path and source
*/
items(path, source, mods = {}) {
return this.__items(path, source, mods, resp => this.generateItemsList(resp.data.sources, mods));
}
/**
* Load items list by path and source
*/
itemsEx(path, source, mods = {}) {
const calcTotal = (sources) => sources.reduce((acc, source) => acc + source.files.length, 0);
return this.__items(path, source, mods, resp => ({
items: this.generateItemsList(resp.data.sources, mods),
loadedTotal: calcTotal(resp.data.sources)
}));
}
generateItemsList(sources, mods = {}) {
const elements = [];
const canBeFile = (item) => item.type === 'folder' ||
!mods.onlyImages ||
item.isImage === undefined ||
item.isImage;
const inFilter = (item) => {
var _a;
return !((_a = mods.filterWord) === null || _a === void 0 ? void 0 : _a.length) ||
this.o.filter === undefined ||
this.o.filter(item, mods.filterWord);
};
sources.forEach(source => {
if (source.files && source.files.length) {
const { sort } = this.o;
if (isFunction(sort) && mods.sortBy) {
source.files.sort((a, b) => sort(a, b, mods.sortBy));
}
source.files.forEach((item) => {
if (inFilter(item) && canBeFile(item)) {
elements.push(FileBrowserItem.create({
...item,
sourceName: source.name,
source
}));
}
});
}
});
return elements;
}
async tree(path, source) {
path = normalizeRelativePath(path);
if (!this.o.folder) {
return Promise.reject(Error('Set Folder Api options'));
}
await this.permissions(path, source);
this.o.folder.data.path = path;
this.o.folder.data.source = source;
return this.get('folder').then(resp => {
let process = this.o.folder.process;
if (!process) {
process = this.o.ajax.process;
}
if (process) {
resp = process.call(self, resp);
}
return resp.data.sources;
});
}
/**
* Get path by url. You can use this method in another modules
*/
getPathByUrl(url) {
set('options.getLocalFileByUrl.data.url', url, this);
return this.get('getLocalFileByUrl').then(resp => {
if (this.isSuccess(resp)) {
return resp.data;
}
throw error(this.getMessage(resp));
});
}
/**
* Create a directory on the server
*
* @param name - Name the new folder
* @param path - Relative directory in which you want create a folder
* @param source - Server source key
*/
createFolder(name, path, source) {
const { create } = this.o;
if (!create) {
throw error('Set Create api options');
}
create.data.source = source;
create.data.path = path;
create.data.name = name;
return this.get('create').then(resp => {
if (this.isSuccess(resp)) {
return true;
}
throw error(this.getMessage(resp));
});
}
/**
* Move a file / directory on the server
*
* @param filepath - The relative path to the file / folder source
* @param path - Relative to the directory where you want to move the file / folder
*/
move(filepath, path, source, isFile) {
const mode = isFile
? 'fileMove'
: 'folderMove';
const option = this.options[mode];
if (!option) {
throw error('Set Move api options');
}
option.data.from = filepath;
option.data.path = path;
option.data.source = source;
return this.get(mode).then(resp => {
if (this.isSuccess(resp)) {
return true;
}
throw error(this.getMessage(resp));
});
}
/**
* Deleting item
*
* @param path - Relative path
* @param file - The filename
* @param source - Source
*/
remove(action, path, file, source) {
const fr = this.o[action];
if (!fr) {
throw error(`Set "${action}" api options`);
}
fr.data.path = path;
fr.data.name = file;
fr.data.source = source;
return this.get(action).then(resp => {
if (fr.process) {
resp = fr.process.call(this, resp);
}
return this.getMessage(resp);
});
}
/**
* Deleting a file
*
* @param path - Relative path
* @param file - The filename
* @param source - Source
*/
fileRemove(path, file, source) {
return this.remove('fileRemove', path, file, source);
}
/**
* Deleting a folder
*
* @param path - Relative path
* @param file - The filename
* @param source - Source
*/
folderRemove(path, file, source) {
return this.remove('folderRemove', path, file, source);
}
/**
* Rename action
*
* @param path - Relative path
* @param name - Old name
* @param newname - New name
* @param source - Source
*/
rename(action, path, name, newname, source) {
const fr = this.o[action];
if (!fr) {
throw error(`Set "${action}" api options`);
}
fr.data.path = path;
fr.data.name = name;
fr.data.newname = newname;
fr.data.source = source;
return this.get(action).then(resp => {
if (fr.process) {
resp = fr.process.call(self, resp);
}
return this.getMessage(resp);
});
}
/**
* Rename folder
*/
folderRename(path, name, newname, source) {
return this.rename('folderRename', path, name, newname, source);
}
/**
* Rename file
*/
fileRename(path, name, newname, source) {
return this.rename('fileRename', path, name, newname, source);
}
changeImage(type, path, source, name, newname, box) {
if (!this.o[type]) {
this.o[type] = {
data: {}
};
}
const query = this.o[type];
if (query.data === undefined) {
query.data = {
action: type
};
}
query.data.newname = newname || name;
if (box) {
query.data.box = box;
}
query.data.path = path;
query.data.name = name;
query.data.source = source;
return this.get(type).then(() => {
return true;
});
}
/**
* Send command to server to crop image
*/
crop(path, source, name, newname, box) {
return this.changeImage('crop', path, source, name, newname, box);
}
/**
* Send command to server to resize image
*/
resize(path, source, name, newname, box) {
return this.changeImage('resize', path, source, name, newname, box);
}
getMessage(resp) {
return this.options.getMessage(resp);
}
isSuccess(resp) {
return this.options.isSuccess(resp);
}
destruct() {
this.__ajaxInstances.forEach(a => a.destruct());
this.__ajaxInstances.clear();
}
}

View File

@@ -0,0 +1,11 @@
/*!
* 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
*/
/**
* @module modules/file-browser
*/
import type { IContextMenu, IFileBrowserDataProvider, IFileBrowserOptions, IUIElement, IViewBased } from "../../types/index";
export declare function makeDataProvider(parent: IViewBased, options: IFileBrowserOptions): IFileBrowserDataProvider;
export declare function makeContextMenu(parent: IViewBased): IContextMenu & IUIElement;

View File

@@ -0,0 +1,13 @@
/*!
* 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
*/
import { ContextMenu } from "../context-menu/context-menu.js";
import DataProvider from "./data-provider.js";
export function makeDataProvider(parent, options) {
return new DataProvider(parent, options);
}
export function makeContextMenu(parent) {
return new ContextMenu(parent);
}

View File

@@ -0,0 +1,14 @@
/*!
* 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
*/
/**
* @module modules/file-browser
*/
import type { IFileBrowser } from "../../../types/index";
/**
* Removes a file from the server
* @private
*/
export declare function deleteFile(fb: IFileBrowser, name: string, source: string): Promise<void>;

View File

@@ -0,0 +1,17 @@
/*!
* 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
*/
/**
* Removes a file from the server
* @private
*/
export function deleteFile(fb, name, source) {
return fb.dataProvider
.fileRemove(fb.state.currentPath, name, source)
.then(message => {
fb.status(message || fb.i18n('File "%s" was deleted', name), true);
})
.catch(fb.status);
}

View File

@@ -0,0 +1,14 @@
/*!
* 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
*/
/**
* @module modules/file-browser
*/
import type { IFileBrowser } from "../../../types/index";
/**
* Loads a list of files and adds them to the state
* @private
*/
export declare function loadItems(fb: IFileBrowser): Promise<any>;

View File

@@ -0,0 +1,27 @@
/*!
* 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
*/
/**
* Loads a list of files and adds them to the state
* @private
*/
export function loadItems(fb) {
fb.files.setMod('active', true);
fb.files.setMod('loading', true);
return fb.dataProvider
.items(fb.state.currentPath, fb.state.currentSource, {
sortBy: fb.state.sortBy,
onlyImages: fb.state.onlyImages,
filterWord: fb.state.filterWord
})
.then(resp => {
if (resp) {
fb.state.elements = resp;
fb.state.activeElements = [];
}
})
.catch(fb.status)
.finally(() => fb.files.setMod('loading', false));
}

View File

@@ -0,0 +1,14 @@
/*!
* 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
*/
/**
* @module modules/file-browser
*/
import type { IFileBrowser } from "../../../types/index";
/**
* Loads a list of directories
* @private
*/
export declare function loadTree(fb: IFileBrowser): Promise<any>;

View File

@@ -0,0 +1,29 @@
/*!
* 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
*/
import { Dom } from "../../../core/dom/index.js";
import { loadItems } from "./load-items.js";
/**
* Loads a list of directories
* @private
*/
export async function loadTree(fb) {
fb.tree.setMod('active', true);
Dom.detach(fb.tree.container);
const items = loadItems(fb);
if (fb.o.showFoldersPanel) {
fb.tree.setMod('loading', true);
const tree = fb.dataProvider
.tree(fb.state.currentPath, fb.state.currentSource)
.then(resp => {
fb.state.sources = resp;
})
.catch(fb.status)
.finally(() => fb.tree.setMod('loading', false));
return Promise.all([tree, items]);
}
fb.tree.setMod('active', false);
return items;
}

View File

@@ -0,0 +1,74 @@
/*!
* 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
*/
/**
* @module modules/file-browser
*/
import type { CanUndef, IFileBrowser, IFileBrowserCallBackData, IFileBrowserDataProvider, IFileBrowserOptions, IFileBrowserState, IStorage, IUploader } from "../../types/index";
import { Dlgs } from "../../core/traits/dlgs";
import { ViewWithToolbar } from "../../core/view/view-with-toolbar";
import "./config";
import { FileBrowserFiles, FileBrowserTree } from "./ui/index";
export interface FileBrowser extends Dlgs {
}
export declare class FileBrowser extends ViewWithToolbar implements IFileBrowser, Dlgs {
/** @override */
className(): string;
private browser;
private status_line;
tree: FileBrowserTree;
files: FileBrowserFiles;
state: IFileBrowserState & import("../../types/index").IObservable;
get dataProvider(): IFileBrowserDataProvider;
private onSelect;
private errorHandler;
OPTIONS: IFileBrowserOptions;
private get _dialog();
/**
* Container for set/get value
*/
get storage(): IStorage;
uploader: IUploader;
get isOpened(): boolean;
/**
* It displays a message in the status bar of filebrowser
*
* @param message - The message that will be displayed
* @param success - true It will be shown a message light . If no option is specified ,
* ßan error will be shown the red
* @example
* ```javascript
* parent.filebrowser.status('There was an error uploading file', false);
* ```
*/
status(message: string | Error, success?: boolean): void;
/**
* Close dialog
*/
close: () => void;
/**
* It opens a web browser window
*
* @param callback - The function that will be called after the file selection in the browser
* @param onlyImages - Show only images
* @example
* ```javascript
* var fb = new Jodit.modules.FileBrowser(parent);
* fb.open(function (data) {
* var i;
* for (i = 0;i < data.files.length; i += 1) {
* parent.s.insertImage(data.baseurl + data.files[i]);
* }
* });
* ```
*/
open(callback?: CanUndef<(_: IFileBrowserCallBackData) => void>, onlyImages?: boolean): Promise<void>;
private __prevButtons;
private __getButtons;
private initUploader;
constructor(options?: IFileBrowserOptions);
destruct(): void;
private __updateToolbarButtons;
}

View File

@@ -0,0 +1,363 @@
/*!
* 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;
};
import { STATUSES } from "../../core/component/index.js";
import * as consts from "../../core/constants.js";
import { IS_PROD } from "../../core/constants.js";
import { autobind, cache, cached, derive } from "../../core/decorators/index.js";
import { watch } from "../../core/decorators/watch/watch.js";
import { observable } from "../../core/event-emitter/index.js";
import { ConfigProto, error, isAbortError, isFunction, isString, trim } from "../../core/helpers/index.js";
import { Storage } from "../../core/storage/index.js";
import { Dlgs } from "../../core/traits/dlgs.js";
import { ViewWithToolbar } from "../../core/view/view-with-toolbar.js";
import { Config } from "../../config.js";
import "./config.js";
import { loadItems } from "./fetch/load-items.js";
import { loadTree } from "./fetch/load-tree.js";
import { nativeListeners } from "./listeners/native-listeners.js";
import { selfListeners } from "./listeners/self-listeners.js";
import { stateListeners } from "./listeners/state-listeners.js";
import { DEFAULT_SOURCE_NAME } from "./data-provider.js";
import { makeDataProvider } from "./factories.js";
import { FileBrowserFiles, FileBrowserTree } from "./ui/index.js";
let FileBrowser = class FileBrowser extends ViewWithToolbar {
/** @override */
className() {
return 'FileBrowser';
}
get dataProvider() {
return makeDataProvider(this, this.options);
}
onSelect(callback) {
return () => {
if (this.state.activeElements.length) {
const files = [];
const isImages = [];
this.state.activeElements.forEach(elm => {
const url = elm.fileURL;
if (url) {
files.push(url);
isImages.push(elm.isImage || false);
}
});
this.close();
const data = {
baseurl: '',
files,
isImages
};
if (isFunction(callback)) {
callback(data);
}
this.close();
}
return false;
};
}
get _dialog() {
var _a;
const dialog = this.dlg({
minWidth: Math.min(700, screen.width),
minHeight: 300,
buttons: (_a = this.o.headerButtons) !== null && _a !== void 0 ? _a : ['fullsize', 'dialog.close']
});
['beforeClose', 'afterClose', 'beforeOpen'].forEach(proxyEvent => dialog.events.on(dialog, proxyEvent, () => this.e.fire(proxyEvent)));
dialog.setSize(this.o.width, this.o.height);
return dialog;
}
/**
* Container for set/get value
*/
get storage() {
return Storage.makeStorage(Boolean(this.o.saveStateInStorage), this.componentName);
}
get isOpened() {
return this._dialog.isOpened && this.browser.style.display !== 'none';
}
/**
* It displays a message in the status bar of filebrowser
*
* @param message - The message that will be displayed
* @param success - true It will be shown a message light . If no option is specified ,
* ßan error will be shown the red
* @example
* ```javascript
* parent.filebrowser.status('There was an error uploading file', false);
* ```
*/
status(message, success) {
if (!message || isAbortError(message)) {
return;
}
if (!isString(message)) {
message = message.message;
}
if (!isString(message) || !trim(message).length) {
return;
}
this.message.message(message, success ? 'success' : 'error', this.o.howLongShowMsg);
}
/**
* It opens a web browser window
*
* @param callback - The function that will be called after the file selection in the browser
* @param onlyImages - Show only images
* @example
* ```javascript
* var fb = new Jodit.modules.FileBrowser(parent);
* fb.open(function (data) {
* var i;
* for (i = 0;i < data.files.length; i += 1) {
* parent.s.insertImage(data.baseurl + data.files[i]);
* }
* });
* ```
*/
open(callback = this.o
.defaultCallback, onlyImages = false) {
this.state.onlyImages = onlyImages;
return this.async
.promise((resolve, reject) => {
var _a;
if (!this.o.items || !this.o.items.url) {
throw error('Need set options.filebrowser.ajax.url');
}
let localTimeout = 0;
this.e
.off(this.files.container, 'dblclick')
.on(this.files.container, 'dblclick', this.onSelect(callback))
.on(this.files.container, 'touchstart', () => {
const now = new Date().getTime();
if (now - localTimeout <
consts.EMULATE_DBLCLICK_TIMEOUT) {
this.onSelect(callback)();
}
localTimeout = now;
})
.off('select.filebrowser')
.on('select.filebrowser', this.onSelect(callback));
const header = this.c.div();
(_a = this.toolbar) === null || _a === void 0 ? void 0 : _a.appendTo(header);
this.__updateToolbarButtons();
this._dialog.open(this.browser, header);
this.e.fire('sort.filebrowser', this.state.sortBy);
loadTree(this)
.then(resolve, reject)
.finally(() => {
var _a;
if (this.isInDestruct) {
return;
}
(_a = this === null || this === void 0 ? void 0 : this.e) === null || _a === void 0 ? void 0 : _a.fire('fileBrowserReady.filebrowser');
});
})
.catch((e) => {
if (!isAbortError(e) && !IS_PROD) {
throw e;
}
});
}
__getButtons() {
var _a;
const options = ((_a = this.o.buttons) !== null && _a !== void 0 ? _a : []);
return options.filter((btn) => {
if (!isString(btn)) {
return true;
}
switch (btn) {
case 'filebrowser.upload':
return this.dataProvider.canI('FileUpload');
case 'filebrowser.edit':
return (this.dataProvider.canI('ImageResize') ||
this.dataProvider.canI('ImageCrop'));
case 'filebrowser.remove':
return this.dataProvider.canI('FileRemove');
}
return true;
});
}
initUploader(editor) {
var _a;
const self = this, options = (_a = editor === null || editor === void 0 ? void 0 : editor.options) === null || _a === void 0 ? void 0 : _a.uploader, uploaderOptions = ConfigProto(options || {}, Config.defaultOptions.uploader);
const uploadHandler = () => loadItems(this);
self.uploader = self.getInstance('Uploader', uploaderOptions);
self.uploader
.setPath(self.state.currentPath)
.setSource(self.state.currentSource)
.bind(self.browser, uploadHandler, self.errorHandler);
this.state.on(['change.currentPath', 'change.currentSource'], () => {
this.uploader
.setPath(this.state.currentPath)
.setSource(this.state.currentSource);
});
self.e.on('bindUploader.filebrowser', (button) => {
self.uploader.bind(button, uploadHandler, self.errorHandler);
});
}
constructor(options) {
super(options);
this.browser = this.c.div(this.componentName);
this.status_line = this.c.div(this.getFullElName('status'));
this.tree = new FileBrowserTree(this);
this.files = new FileBrowserFiles(this);
this.state = observable({
currentPath: '',
currentSource: DEFAULT_SOURCE_NAME,
currentBaseUrl: '',
activeElements: [],
elements: [],
sources: [],
view: 'tiles',
sortBy: 'changed-desc',
filterWord: '',
onlyImages: false
});
this.errorHandler = (resp) => {
if (isAbortError(resp)) {
return;
}
if (resp instanceof Error) {
this.status(this.i18n(resp.message));
}
else {
this.status(this.dataProvider.getMessage(resp));
}
};
/**
* Close dialog
*/
this.close = () => {
this._dialog.close();
};
this.__prevButtons = [];
this.attachEvents(options);
const self = this;
self.options = ConfigProto(options || {}, Config.defaultOptions.filebrowser);
self.browser.component = this;
self.container = self.browser;
if (self.o.showFoldersPanel) {
self.browser.appendChild(self.tree.container);
}
self.browser.appendChild(self.files.container);
self.browser.appendChild(self.status_line);
selfListeners.call(self);
nativeListeners.call(self);
stateListeners.call(self);
const keys = [
'getLocalFileByUrl',
'crop',
'resize',
'create',
'fileMove',
'folderMove',
'fileRename',
'folderRename',
'fileRemove',
'folderRemove',
'folder',
'items',
'permissions'
];
keys.forEach(key => {
if (this.options[key] != null) {
this.options[key] = ConfigProto(this.options[key], this.o.ajax);
}
});
const { storeView, storeSortBy, storeLastOpenedFolder } = this.o
.saveStateInStorage || {
storeLastOpenedFolder: false,
storeView: false,
storeSortBy: false
};
const view = storeView && this.storage.get('view');
if (view && this.o.view == null) {
self.state.view = view === 'list' ? 'list' : 'tiles';
}
else {
self.state.view = self.o.view === 'list' ? 'list' : 'tiles';
}
self.files.setMod('view', self.state.view);
const sortBy = storeSortBy && self.storage.get('sortBy');
if (sortBy) {
const parts = sortBy.split('-');
self.state.sortBy = ['changed', 'name', 'size'].includes(parts[0])
? sortBy
: 'changed-desc';
}
else {
self.state.sortBy = self.o.sortBy || 'changed-desc';
}
if (storeLastOpenedFolder) {
const currentPath = self.storage.get('currentPath'), currentSource = self.storage.get('currentSource');
self.state.currentPath = currentPath !== null && currentPath !== void 0 ? currentPath : '';
self.state.currentSource = currentSource !== null && currentSource !== void 0 ? currentSource : '';
}
self.initUploader(self);
self.setStatus(STATUSES.ready);
}
destruct() {
var _a;
if (this.isInDestruct) {
return;
}
(_a = cached(this, '_dialog')) === null || _a === void 0 ? void 0 : _a.destruct();
super.destruct();
this.events && this.e.off('.filebrowser');
this.uploader && this.uploader.destruct();
}
__updateToolbarButtons() {
var _a;
const buttons = this.__getButtons();
if (isEqualButtonList(this.__prevButtons, buttons)) {
return;
}
this.__prevButtons = buttons;
(_a = this.toolbar) === null || _a === void 0 ? void 0 : _a.build(buttons);
}
};
__decorate([
cache
], FileBrowser.prototype, "dataProvider", null);
__decorate([
cache
], FileBrowser.prototype, "_dialog", null);
__decorate([
cache
], FileBrowser.prototype, "storage", null);
__decorate([
autobind
], FileBrowser.prototype, "status", null);
__decorate([
autobind
], FileBrowser.prototype, "open", null);
__decorate([
watch('dataProvider:changePermissions')
], FileBrowser.prototype, "__updateToolbarButtons", null);
FileBrowser = __decorate([
derive(Dlgs)
], FileBrowser);
export { FileBrowser };
function isEqualButtonList(prevButtons, buttons) {
if (prevButtons.length !== buttons.length) {
return false;
}
for (let i = 0; i < prevButtons.length; i++) {
if (prevButtons[i] !== buttons[i]) {
return false;
}
}
return true;
}

11
node_modules/jodit/esm/modules/file-browser/index.d.ts generated vendored Normal file
View File

@@ -0,0 +1,11 @@
/*!
* 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
*/
/**
* [[include:modules/file-browser/README.md]]
* @packageDocumentation
* @module modules/file-browser
*/
export * from "./file-browser";

11
node_modules/jodit/esm/modules/file-browser/index.js generated vendored Normal file
View File

@@ -0,0 +1,11 @@
/*!
* 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
*/
/**
* [[include:modules/file-browser/README.md]]
* @packageDocumentation
* @module modules/file-browser
*/
export * from "./file-browser.js";

View File

@@ -0,0 +1,24 @@
/*!
* 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
*/
/**
* @module modules/file-browser
*/
import type { HTMLTagNames, IDictionary, IFileBrowser, IFileBrowserItem, Nullable } from "../../../types/index";
/**
* @private
*/
export declare const getItem: (node: Nullable<EventTarget>, root: HTMLElement, tag?: HTMLTagNames) => Nullable<HTMLElement>;
/**
* @private
*/
export declare const elementToItem: (elm: HTMLElement, elementsMap: IDictionary<{
elm: HTMLElement;
item: IFileBrowserItem;
}>) => IFileBrowserItem | void;
/**
* @private
*/
export declare function nativeListeners(this: IFileBrowser): void;

View File

@@ -0,0 +1,102 @@
/*!
* 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
*/
import { Dom } from "../../../core/dom/index.js";
import { attr, ctrlKey } from "../../../core/helpers/index.js";
import contextMenu from "../builders/context-menu.js";
import { elementsMap } from "../builders/elements-map.js";
import { loadTree } from "../fetch/load-tree.js";
/**
* @private
*/
export const getItem = (node, root, tag = 'a') => Dom.closest(node, elm => Dom.isTag(elm, tag), root);
/**
* @private
*/
export const elementToItem = (elm, elementsMap) => {
const { key } = elm.dataset, { item } = elementsMap[key || ''];
return item;
};
/**
* @private
*/
export function nativeListeners() {
let dragElement = false;
const elmMap = elementsMap(this);
const self = this;
self.e
.on(self.tree.container, 'dragstart', (e) => {
const a = getItem(e.target, self.container);
if (!a) {
return;
}
if (self.o.moveFolder) {
dragElement = a;
}
})
.on(self.tree.container, 'drop', (e) => {
if ((self.o.moveFile || self.o.moveFolder) && dragElement) {
let path = attr(dragElement, '-path') || '';
// move folder
if (!self.o.moveFolder &&
dragElement.classList.contains(this.tree.getFullElName('item'))) {
return false;
}
// move file
if (dragElement.classList.contains(this.files.getFullElName('item'))) {
path += attr(dragElement, '-name');
if (!self.o.moveFile) {
return false;
}
}
const a = getItem(e.target, self.container);
if (!a) {
return;
}
self.dataProvider
.move(path, attr(a, '-path') || '', attr(a, '-source') || '', dragElement.classList.contains(this.files.getFullElName('item')))
.then(() => loadTree(this))
.catch(self.status);
dragElement = false;
}
})
.on(self.files.container, 'contextmenu', contextMenu(self))
.on(self.files.container, 'click', (e) => {
if (!ctrlKey(e)) {
this.state.activeElements = [];
}
})
.on(self.files.container, 'click', (e) => {
const a = getItem(e.target, self.container);
if (!a) {
return;
}
const item = elementToItem(a, elmMap);
if (!item) {
return;
}
if (!ctrlKey(e)) {
self.state.activeElements = [item];
}
else {
self.state.activeElements = [
...self.state.activeElements,
item
];
}
e.stopPropagation();
return false;
})
.on(self.files.container, 'dragstart', (e) => {
if (self.o.moveFile) {
const a = getItem(e.target, self.container);
if (!a) {
return;
}
dragElement = a;
}
})
.on(self.container, 'drop', (e) => e.preventDefault());
}

View File

@@ -0,0 +1,13 @@
/*!
* 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
*/
/**
* @module modules/file-browser
*/
import type { IFileBrowser } from "../../../types/index";
/**
* @private
*/
export declare function selfListeners(this: IFileBrowser): void;

View File

@@ -0,0 +1,128 @@
/*!
* 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
*/
import { normalizePath } from "../../../core/helpers/index.js";
import { isValidName } from "../../../core/helpers/checker/index.js";
import { DEFAULT_SOURCE_NAME } from "../data-provider.js";
import { deleteFile } from "../fetch/delete-file.js";
import { loadItems } from "../fetch/load-items.js";
import { loadTree } from "../fetch/load-tree.js";
import { openImageEditor } from "../../image-editor/image-editor.js";
/**
* @private
*/
export function selfListeners() {
const state = this.state, dp = this.dataProvider, self = this;
self.e
.on('view.filebrowser', (view) => {
if (view !== state.view) {
state.view = view;
}
})
.on('sort.filebrowser', (value) => {
if (value !== state.sortBy) {
state.sortBy = value;
loadItems(self);
}
})
.on('filter.filebrowser', (value) => {
if (value !== state.filterWord) {
state.filterWord = value;
loadItems(self);
}
})
.on('openFolder.filebrowser', (data) => {
let path;
if (data.name === '..') {
path = data.path
.split('/')
.filter((p) => p.length)
.slice(0, -1)
.join('/');
}
else {
path = normalizePath(data.path, data.name);
}
self.state.currentPath = path;
self.state.currentSource =
data.name === '.' ? DEFAULT_SOURCE_NAME : data.source;
})
.on('removeFolder.filebrowser', (data) => {
self.confirm('Are you sure?', 'Delete', (yes) => {
if (yes) {
dp.folderRemove(data.path, data.name, data.source)
.then(message => {
self.status(message, true);
return loadTree(self);
})
.catch(self.status);
}
});
})
.on('renameFolder.filebrowser', (data) => {
self.prompt('Enter new name', 'Rename', (newName) => {
if (!isValidName(newName)) {
self.status(self.i18n('Enter new name'));
return false;
}
dp.folderRename(data.path, data.name, newName, data.source)
.then(message => {
self.state.activeElements = [];
self.status(message, true);
return loadTree(self);
})
.catch(self.status);
return;
}, 'type name', data.name);
})
.on('addFolder.filebrowser', (data) => {
self.prompt('Enter Directory name', 'Create directory', (name) => {
dp.createFolder(name, data.path, data.source)
.then(() => loadTree(self))
.catch(self.status);
}, 'type name');
})
.on('fileRemove.filebrowser', () => {
if (self.state.activeElements.length) {
self.confirm('Are you sure?', '', (yes) => {
if (yes) {
const promises = [];
self.state.activeElements.forEach(item => {
promises.push(deleteFile(self, item.file || item.name || '', item.sourceName));
});
self.state.activeElements = [];
Promise.all(promises).then(() => loadTree(self).catch(self.status), self.status);
}
});
}
})
.on('edit.filebrowser', () => {
if (self.state.activeElements.length === 1) {
const [file] = this.state.activeElements;
openImageEditor.call(self, file.fileURL, file.file || '', file.path, file.sourceName);
}
})
.on('fileRename.filebrowser', (name, path, source) => {
if (self.state.activeElements.length === 1) {
self.prompt('Enter new name', 'Rename', (newName) => {
if (!isValidName(newName)) {
self.status(self.i18n('Enter new name'));
return false;
}
dp.fileRename(path, name, newName, source)
.then(message => {
self.state.activeElements = [];
self.status(message, true);
loadItems(self);
})
.catch(self.status);
return;
}, 'type name', name);
}
})
.on('update.filebrowser', () => {
loadTree(this).then(this.status, this.status);
});
}

View File

@@ -0,0 +1,14 @@
/*!
* 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
*/
/**
* @module modules/file-browser
*/
import type { IFileBrowser } from "../../../types/index";
/**
* Convert state to view
* @private
*/
export declare function stateListeners(this: IFileBrowser): void;

View File

@@ -0,0 +1,146 @@
/*!
* 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
*/
import { Dom } from "../../../core/dom/index.js";
import { normalizePath } from "../../../core/helpers/normalize/index.js";
import { Button } from "../../../core/ui/button/button/button.js";
import { elementsMap } from "../builders/elements-map.js";
import { loadTree } from "../fetch/load-tree.js";
const DEFAULT_SOURCE_NAME = 'default';
/**
* Convert state to view
* @private
*/
export function stateListeners() {
const elmMap = elementsMap(this);
const { state, files, create, options } = this, getDomElement = (item) => {
const key = item.uniqueHashKey;
if (elmMap[key]) {
return elmMap[key].elm;
}
const elm = create.fromHTML(options.getThumbTemplate.call(this, item, item.source, item.sourceName.toString()));
elm.dataset.key = key;
elmMap[key] = {
item,
elm
};
return elmMap[key].elm;
};
state
.on(['change.currentPath', 'change.currentSource'], this.async.debounce(() => {
if (this.o.saveStateInStorage &&
this.o.saveStateInStorage.storeLastOpenedFolder) {
this.storage
.set('currentPath', this.state.currentPath)
.set('currentSource', this.state.currentSource);
}
loadTree(this).catch(this.status);
}, this.defaultTimeout))
.on('beforeChange.activeElements', () => {
state.activeElements.forEach(item => {
const key = item.uniqueHashKey, { elm } = elmMap[key];
elm &&
elm.classList.remove(files.getFullElName('item', 'active', true));
});
})
.on('change.activeElements', () => {
this.e.fire('changeSelection');
state.activeElements.forEach(item => {
const key = item.uniqueHashKey, { elm } = elmMap[key];
elm &&
elm.classList.add(files.getFullElName('item', 'active', true));
});
})
.on('change.view', () => {
files.setMod('view', state.view);
if (this.o.saveStateInStorage &&
this.o.saveStateInStorage.storeView) {
this.storage.set('view', state.view);
}
})
.on('change.sortBy', () => {
if (this.o.saveStateInStorage &&
this.o.saveStateInStorage.storeSortBy) {
this.storage.set('sortBy', state.sortBy);
}
})
.on('change.elements', this.async.debounce(() => {
Dom.detach(files.container);
if (state.elements.length) {
state.elements.forEach(item => {
this.files.container.appendChild(getDomElement(item));
});
}
else {
files.container.appendChild(create.div(this.componentName + '_no-files_true', this.i18n('There are no files')));
}
}, this.defaultTimeout))
.on('change.sources', this.async.debounce(() => {
Dom.detach(this.tree.container);
state.sources.forEach(source => {
const sourceName = source.name;
if (sourceName && sourceName !== DEFAULT_SOURCE_NAME) {
this.tree.container.appendChild(create.div(this.tree.getFullElName('source-title'), sourceName));
}
source.folders.forEach((name) => {
const folderElm = create.a(this.tree.getFullElName('item'), {
draggable: 'draggable',
href: '#',
'data-path': normalizePath(source.path, name + '/'),
'data-name': name,
'data-source': sourceName,
'data-source-path': source.path
}, create.span(this.tree.getFullElName('item-title'), name));
const action = (actionName) => (e) => {
this.e.fire(`${actionName}.filebrowser`, {
name,
path: normalizePath(source.path + '/'),
source: sourceName
});
e.stopPropagation();
e.preventDefault();
};
this.e.on(folderElm, 'click', action('openFolder'));
this.tree.container.appendChild(folderElm);
if (name === '..' || name === '.') {
return;
}
if (options.renameFolder &&
this.dataProvider.canI('FolderRename')) {
const btn = Button(this, {
icon: { name: 'pencil' },
name: 'rename',
tooltip: 'Rename',
size: 'tiny'
});
btn.onAction(action('renameFolder'));
folderElm.appendChild(btn.container);
}
if (options.deleteFolder &&
this.dataProvider.canI('FolderRemove')) {
const btn = Button(this, {
icon: { name: 'cancel' },
name: 'remove',
tooltip: 'Delete',
size: 'tiny'
});
btn.onAction(action('removeFolder'));
folderElm.appendChild(btn.container);
}
});
if (options.createNewFolder &&
this.dataProvider.canI('FolderCreate')) {
const button = Button(this, 'plus', 'Add folder', 'secondary');
button.onAction(() => {
this.e.fire('addFolder', {
path: normalizePath(source.path + '/'),
source: sourceName
});
});
this.tree.append(button);
}
});
}, this.defaultTimeout));
}

View File

@@ -0,0 +1,9 @@
/*!
* 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
*/
import { UIGroup } from "../../../../core/ui/index";
export declare class FileBrowserFiles extends UIGroup {
className(): string;
}

View File

@@ -0,0 +1,11 @@
/*!
* 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
*/
import { UIGroup } from "../../../../core/ui/index.js";
export class FileBrowserFiles extends UIGroup {
className() {
return 'FileBrowserFiles';
}
}

View File

@@ -0,0 +1,10 @@
/*!
* 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
*/
/**
* @module modules/file-browser
*/
export * from "./files/files";
export * from "./tree/tree";

View File

@@ -0,0 +1,10 @@
/*!
* 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
*/
/**
* @module modules/file-browser
*/
export * from "./files/files.js";
export * from "./tree/tree.js";

View File

@@ -0,0 +1,9 @@
/*!
* 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
*/
import { UIGroup } from "../../../../core/ui/index";
export declare class FileBrowserTree extends UIGroup {
className(): string;
}

View File

@@ -0,0 +1,11 @@
/*!
* 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
*/
import { UIGroup } from "../../../../core/ui/index.js";
export class FileBrowserTree extends UIGroup {
className() {
return 'FileBrowserTree';
}
}

19
node_modules/jodit/esm/modules/history/command.d.ts generated vendored Normal file
View File

@@ -0,0 +1,19 @@
/*!
* 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
*/
/**
* @module modules/history
*/
import type { SnapshotType } from "../../types/index";
import type { History } from "./history";
export declare class Command {
readonly oldValue: SnapshotType;
readonly newValue: SnapshotType;
private readonly history;
readonly tick: number;
undo(): void;
redo(): void;
constructor(oldValue: SnapshotType, newValue: SnapshotType, history: History, tick: number);
}

19
node_modules/jodit/esm/modules/history/command.js generated vendored Normal file
View File

@@ -0,0 +1,19 @@
/*!
* 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
*/
export class Command {
undo() {
this.history.snapshot.restore(this.oldValue);
}
redo() {
this.history.snapshot.restore(this.newValue);
}
constructor(oldValue, newValue, history, tick) {
this.oldValue = oldValue;
this.newValue = newValue;
this.history = history;
this.tick = tick;
}
}

74
node_modules/jodit/esm/modules/history/history.d.ts generated vendored Normal file
View File

@@ -0,0 +1,74 @@
/*!
* 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
*/
/**
* [[include:modules/history/README.md]]
* @packageDocumentation
* @module modules/history
*/
import type { IDestructible, IHistory, IJodit, ISnapshot, SnapshotType } from "../../types/index";
import { ViewComponent } from "../../core/component/index";
import { Snapshot } from "./snapshot";
import { Stack } from "./stack";
declare module 'jodit/config' {
interface Config {
history: {
enable: boolean;
/**
* Limit of history length
*/
maxHistoryLength: number;
/**
* Delay on every change
*/
timeout: number;
};
}
}
/**
* The module monitors the status of the editor and creates / deletes the required number of Undo / Redo shots .
*/
export declare class History extends ViewComponent<IJodit> implements IHistory {
/** @override */
className(): string;
/**
* Return state of the WYSIWYG editor to step back
*/
redo(): void;
canRedo(): boolean;
/**
* Return the state of the WYSIWYG editor to step forward
*/
undo(): void;
canUndo(): boolean;
clear(): void;
get length(): number;
private __startValue;
protected get startValue(): SnapshotType;
protected set startValue(value: SnapshotType);
private readonly __stack;
snapshot: ISnapshot & IDestructible;
constructor(editor: IJodit, stack?: Stack, snapshot?: Snapshot);
private updateTick;
/**
* Update change counter
* @internal
*/
protected __upTick(): void;
/**
* Push new command in stack on some changes
*/
private onChange;
/**
* @internal
*/
protected __processChanges(): void;
/**
* Update history stack
*/
private updateStack;
private fireChangeStack;
destruct(): void;
}

168
node_modules/jodit/esm/modules/history/history.js generated vendored Normal file
View File

@@ -0,0 +1,168 @@
/*!
* 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;
};
import { ViewComponent } from "../../core/component/index.js";
import { debounce } from "../../core/decorators/index.js";
import { Config } from "../../config.js";
import { Command } from "./command.js";
import { Snapshot } from "./snapshot.js";
import { Stack } from "./stack.js";
Config.prototype.history = {
enable: true,
maxHistoryLength: Infinity,
timeout: 1000
};
/**
* The module monitors the status of the editor and creates / deletes the required number of Undo / Redo shots .
*/
export class History extends ViewComponent {
/** @override */
className() {
return 'History';
}
/**
* Return state of the WYSIWYG editor to step back
*/
redo() {
if (this.__stack.redo()) {
this.startValue = this.snapshot.make();
this.fireChangeStack();
}
}
canRedo() {
return this.__stack.canRedo();
}
/**
* Return the state of the WYSIWYG editor to step forward
*/
undo() {
if (this.__stack.undo()) {
this.startValue = this.snapshot.make();
this.fireChangeStack();
}
}
canUndo() {
return this.__stack.canUndo();
}
clear() {
this.startValue = this.snapshot.make();
this.__stack.clear();
this.fireChangeStack();
}
get length() {
return this.__stack.length;
}
get startValue() {
return this.__startValue;
}
set startValue(value) {
this.__startValue = value;
}
constructor(editor, stack = new Stack(editor.o.history.maxHistoryLength), snapshot = new Snapshot(editor)) {
super(editor);
this.updateTick = 0;
this.__stack = stack;
this.snapshot = snapshot;
if (editor.o.history.enable) {
editor.e.on('afterAddPlace.history', () => {
if (this.isInDestruct) {
return;
}
this.startValue = this.snapshot.make();
editor.events
// save selection
.on('internalChange internalUpdate', () => {
this.startValue = this.snapshot.make();
})
.on(editor.editor, [
'changeSelection',
'selectionstart',
'selectionchange',
'mousedown',
'mouseup',
'keydown',
'keyup'
]
.map(f => f + '.history')
.join(' '), () => {
if (this.startValue.html ===
this.j.getNativeEditorValue()) {
this.startValue = this.snapshot.make();
}
})
.on(this, 'change.history', this.onChange);
});
}
}
/**
* Update change counter
* @internal
*/
__upTick() {
this.updateTick += 1;
}
/**
* Push new command in stack on some changes
*/
onChange() {
this.__processChanges();
}
/**
* @internal
*/
__processChanges() {
if (this.snapshot.isBlocked || !this.j.o.history.enable) {
return;
}
this.updateStack();
}
/**
* Update history stack
*/
updateStack(replace = false) {
const newValue = this.snapshot.make();
if (!Snapshot.equal(newValue, this.startValue)) {
const newCommand = new Command(this.startValue, newValue, this, this.updateTick);
if (replace) {
const command = this.__stack.current();
if (command && this.updateTick === command.tick) {
this.__stack.replace(newCommand);
}
}
else {
this.__stack.push(newCommand);
}
this.startValue = newValue;
this.fireChangeStack();
}
}
fireChangeStack() {
var _a;
this.j && !this.j.isInDestruct && ((_a = this.j.events) === null || _a === void 0 ? void 0 : _a.fire('changeStack'));
}
destruct() {
if (this.isInDestruct) {
return;
}
if (this.j.events) {
this.j.e.off('.history');
}
this.snapshot.destruct();
super.destruct();
}
}
__decorate([
debounce()
], History.prototype, "onChange", null);

68
node_modules/jodit/esm/modules/history/snapshot.d.ts generated vendored Normal file
View File

@@ -0,0 +1,68 @@
/*!
* 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
*/
/**
* @module modules/history
*/
import type { IJodit, ISnapshot, SnapshotType } from "../../types/index";
import { ViewComponent } from "../../core/component/index";
/**
* Module for creating snapshot of editor which includes html content and the current selection
*/
export declare class Snapshot extends ViewComponent<IJodit> implements ISnapshot {
/** @override */
className(): string;
/**
* Compare two snapshotes, if and htmls and selections match, then return true
*
* @param first - the first snapshote
* @param second - second shot
*/
static equal(first: SnapshotType, second: SnapshotType): boolean;
/**
* Calc count element before some node in parentNode. All text nodes are joined
*/
private static countNodesBeforeInParent;
/**
* Calc normal offset in joined text nodes
*/
private static strokeOffset;
/**
* Calc whole hierarchy path before some element in editor's tree
*/
private calcHierarchyLadder;
private getElementByLadder;
private __isBlocked;
get isBlocked(): boolean;
private __block;
private __levelOfTransaction;
transaction(changes: () => void): void;
/**
* Creates object a snapshot of editor: html and the current selection. Current selection calculate by
* offset by start document
* \{html: string, range: \{startContainer: int, startOffset: int, endContainer: int, endOffset: int\}\} or
* \{html: string\} without selection
*/
make(): SnapshotType;
/**
* Restores the state of the editor of the snapshot. Rebounding is not only html but selected text
*
* @param snapshot - snapshot of editor resulting from the `[[Snapshot.make]]` method
* @see make
*/
restore(snapshot: SnapshotType): void;
private storeScrollState;
private restoreScrollState;
/**
* Restore selection from snapshot
*
* @param snapshot - snapshot of editor resulting from the [[Snapshot.make]] method
* @see make
*/
restoreOnlySelection(snapshot: SnapshotType): void;
destruct(): void;
private static isIgnoredNode;
private __getCleanedEditorValue;
}

213
node_modules/jodit/esm/modules/history/snapshot.js generated vendored Normal file
View File

@@ -0,0 +1,213 @@
/*!
* 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
*/
import { ViewComponent } from "../../core/component/index.js";
import { IS_PROD } from "../../core/constants.js";
import { Dom } from "../../core/dom/dom.js";
/**
* Module for creating snapshot of editor which includes html content and the current selection
*/
export class Snapshot extends ViewComponent {
constructor() {
super(...arguments);
this.__isBlocked = false;
this.__levelOfTransaction = 0;
}
/** @override */
className() {
return 'Snapshot';
}
/**
* Compare two snapshotes, if and htmls and selections match, then return true
*
* @param first - the first snapshote
* @param second - second shot
*/
static equal(first, second) {
return (first.html === second.html &&
JSON.stringify(first.range) === JSON.stringify(second.range));
}
/**
* Calc count element before some node in parentNode. All text nodes are joined
*/
static countNodesBeforeInParent(elm) {
if (!elm.parentNode) {
return 0;
}
const elms = elm.parentNode.childNodes;
let count = 0, previous = null;
for (let j = 0; j < elms.length; j += 1) {
if (previous &&
!this.isIgnoredNode(elms[j]) &&
!(Dom.isText(previous) && Dom.isText(elms[j]))) {
count += 1;
}
if (elms[j] === elm) {
return count;
}
previous = elms[j];
}
return 0;
}
/**
* Calc normal offset in joined text nodes
*/
static strokeOffset(elm, offset) {
while (Dom.isText(elm)) {
elm = elm.previousSibling;
if (Dom.isText(elm) && elm.nodeValue) {
offset += elm.nodeValue.length;
}
}
return offset;
}
/**
* Calc whole hierarchy path before some element in editor's tree
*/
calcHierarchyLadder(elm) {
const counts = [];
if (!elm || !elm.parentNode || !Dom.isOrContains(this.j.editor, elm)) {
return [];
}
while (elm && elm !== this.j.editor) {
if (elm && !Snapshot.isIgnoredNode(elm)) {
counts.push(Snapshot.countNodesBeforeInParent(elm));
}
elm = elm.parentNode;
}
return counts.reverse();
}
getElementByLadder(ladder) {
let n = this.j.editor, i;
for (i = 0; n && i < ladder.length; i += 1) {
n = n.childNodes[ladder[i]];
}
return n;
}
get isBlocked() {
return this.__isBlocked;
}
__block(enable) {
this.__isBlocked = enable;
}
transaction(changes) {
this.__block(true);
this.__levelOfTransaction += 1;
try {
changes();
}
catch (e) {
if (!IS_PROD) {
throw e;
}
}
finally {
this.__levelOfTransaction -= 1;
if (this.__levelOfTransaction === 0) {
this.__block(false);
}
}
}
/**
* Creates object a snapshot of editor: html and the current selection. Current selection calculate by
* offset by start document
* \{html: string, range: \{startContainer: int, startOffset: int, endContainer: int, endOffset: int\}\} or
* \{html: string\} without selection
*/
make() {
const snapshot = {
html: '',
range: {
startContainer: [],
startOffset: 0,
endContainer: [],
endOffset: 0
}
};
snapshot.html = this.__getCleanedEditorValue(this.j.editor);
const sel = this.j.s.sel;
if (sel && sel.rangeCount) {
const range = sel.getRangeAt(0);
const startContainer = this.calcHierarchyLadder(range.startContainer);
const endContainer = this.calcHierarchyLadder(range.endContainer);
let startOffset = Snapshot.strokeOffset(range.startContainer, range.startOffset), endOffset = Snapshot.strokeOffset(range.endContainer, range.endOffset);
if (!startContainer.length &&
range.startContainer !== this.j.editor) {
startOffset = 0;
}
if (!endContainer.length && range.endContainer !== this.j.editor) {
endOffset = 0;
}
snapshot.range = {
startContainer,
startOffset,
endContainer,
endOffset
};
}
return snapshot;
}
/**
* Restores the state of the editor of the snapshot. Rebounding is not only html but selected text
*
* @param snapshot - snapshot of editor resulting from the `[[Snapshot.make]]` method
* @see make
*/
restore(snapshot) {
this.transaction(() => {
const scroll = this.storeScrollState();
const html = this.__getCleanedEditorValue(this.j.editor);
if (html !== snapshot.html) {
this.j.value = snapshot.html;
}
this.restoreOnlySelection(snapshot);
this.restoreScrollState(scroll);
});
}
storeScrollState() {
return [this.j.ow.scrollY, this.j.editor.scrollTop];
}
restoreScrollState(scrolls) {
const { j } = this, { ow } = j;
ow.scrollTo(ow.scrollX, scrolls[0]);
j.editor.scrollTop = scrolls[1];
}
/**
* Restore selection from snapshot
*
* @param snapshot - snapshot of editor resulting from the [[Snapshot.make]] method
* @see make
*/
restoreOnlySelection(snapshot) {
try {
if (snapshot.range) {
const range = this.j.ed.createRange();
range.setStart(this.getElementByLadder(snapshot.range.startContainer), snapshot.range.startOffset);
range.setEnd(this.getElementByLadder(snapshot.range.endContainer), snapshot.range.endOffset);
this.j.s.selectRange(range);
}
}
catch (__ignore) {
this.j.editor.lastChild &&
this.j.s.setCursorAfter(this.j.editor.lastChild);
if (!IS_PROD) {
// tslint:disable-next-line:no-console
console.warn('Broken snapshot', __ignore);
}
}
}
destruct() {
this.__block(false);
super.destruct();
}
static isIgnoredNode(node) {
return (Dom.isText(node) && !node.nodeValue) || Dom.isTemporary(node);
}
__getCleanedEditorValue(node) {
const clone = node.cloneNode(true);
Dom.temporaryList(clone).forEach(Dom.unwrap);
return clone.innerHTML;
}
}

26
node_modules/jodit/esm/modules/history/stack.d.ts generated vendored Normal file
View File

@@ -0,0 +1,26 @@
/*!
* 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
*/
/**
* @module modules/history
*/
import type { CanUndef, IStack } from "../../types/index";
import type { Command } from "./command";
export declare class Stack implements IStack {
private readonly size;
private readonly commands;
private stackPosition;
constructor(size: number);
get length(): number;
private clearRedo;
clear(): void;
push(command: Command): void;
replace(command: Command): void;
current(): CanUndef<Command>;
undo(): boolean;
redo(): boolean;
canUndo(): boolean;
canRedo(): boolean;
}

63
node_modules/jodit/esm/modules/history/stack.js generated vendored Normal file
View File

@@ -0,0 +1,63 @@
/*!
* 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
*/
export class Stack {
constructor(size) {
this.size = size;
this.commands = [];
this.stackPosition = -1;
}
get length() {
return this.commands.length;
}
clearRedo() {
this.commands.length = this.stackPosition + 1;
}
clear() {
this.commands.length = 0;
this.stackPosition = -1;
}
push(command) {
this.clearRedo();
this.commands.push(command);
this.stackPosition += 1;
if (this.commands.length > this.size) {
this.commands.shift();
this.stackPosition -= 1;
}
}
replace(command) {
this.commands[this.stackPosition] = command;
}
current() {
return this.commands[this.stackPosition];
}
undo() {
if (this.canUndo()) {
if (this.commands[this.stackPosition]) {
this.commands[this.stackPosition].undo();
}
this.stackPosition -= 1;
return true;
}
return false;
}
redo() {
if (this.canRedo()) {
this.stackPosition += 1;
if (this.commands[this.stackPosition]) {
this.commands[this.stackPosition].redo();
}
return true;
}
return false;
}
canUndo() {
return this.stackPosition >= 0;
}
canRedo() {
return this.stackPosition < this.commands.length - 1;
}
}

View File

@@ -0,0 +1,14 @@
/*!
* 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
*/
/**
* @module modules/image-editor
*/
import type { ImageEditorOptions } from "../../types/index";
declare module 'jodit/config' {
interface Config {
imageeditor: ImageEditorOptions;
}
}

25
node_modules/jodit/esm/modules/image-editor/config.js generated vendored Normal file
View File

@@ -0,0 +1,25 @@
/*!
* 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
*/
import { Icon } from "../../core/ui/icon.js";
import { Config } from "../../config.js";
import cropIcon from "./icons/crop.svg.js";
import resizeIcon from "./icons/resize.svg.js";
Config.prototype.imageeditor = {
min_width: 20,
min_height: 20,
closeAfterSave: false,
width: '85%',
height: '85%',
crop: true,
resize: true,
resizeUseRatio: true,
resizeMinWidth: 20,
resizeMinHeight: 20,
cropUseRatio: true,
cropDefaultWidth: '70%',
cropDefaultHeight: '70%'
};
Icon.set('crop', cropIcon).set('resize', resizeIcon);

View File

@@ -0,0 +1 @@
export default "<svg xmlns='http://www.w3.org/2000/svg' viewBox=\"0 0 1792 1792\"> <path d=\"M621 1280h595v-595zm-45-45l595-595h-595v595zm1152 77v192q0 14-9 23t-23 9h-224v224q0 14-9 23t-23 9h-192q-14 0-23-9t-9-23v-224h-864q-14 0-23-9t-9-23v-864h-224q-14 0-23-9t-9-23v-192q0-14 9-23t23-9h224v-224q0-14 9-23t23-9h192q14 0 23 9t9 23v224h851l246-247q10-9 23-9t23 9q9 10 9 23t-9 23l-247 246v851h224q14 0 23 9t9 23z\"/> </svg> ";

View File

@@ -0,0 +1 @@
export default "<svg xmlns='http://www.w3.org/2000/svg' viewBox=\"0 0 24 24\"> <g transform=\"translate(-251.000000, -443.000000)\"> <g transform=\"translate(215.000000, 119.000000)\"/> <path d=\"M252,448 L256,448 L256,444 L252,444 L252,448 Z M257,448 L269,448 L269,446 L257,446 L257,448 Z M257,464 L269,464 L269,462 L257,462 L257,464 Z M270,444 L270,448 L274,448 L274,444 L270,444 Z M252,462 L252,466 L256,466 L256,462 L252,462 Z M270,462 L270,466 L274,466 L274,462 L270,462 Z M254,461 L256,461 L256,449 L254,449 L254,461 Z M270,461 L272,461 L272,449 L270,449 L270,461 Z\"/> </g> </svg> ";

View File

@@ -0,0 +1,119 @@
/*!
* 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
*/
/**
* [[include:modules/image-editor/README.md]]
* @packageDocumentation
* @module modules/image-editor
*/
import type { IDialog, IDlgs, IFileBrowserDataProvider, ImageEditorActionBox, ImageEditorOptions, IViewWithToolbar } from "../../types/index";
import { ViewComponent } from "../../core/component/index";
import "./config";
interface onSave {
(
/**
* new filename
*/
newname: string | void, box: ImageEditorActionBox,
/**
* called after success operation
*/
success: () => void,
/**
* called after failed operation
*/
failed: (error: Error) => void): void;
}
/**
* The module allows you to edit the image: resize or cut any part of it
*
*/
export declare class ImageEditor extends ViewComponent<IViewWithToolbar & IDlgs> {
/** @override */
className(): string;
options: ImageEditorOptions;
get o(): this['options'];
private onSave;
/**
* Hide image editor
*/
hide(): void;
/**
* Open image editor
* @example
* ```javascript
* const jodit = Jodit.make('.editor', {
* imageeditor: {
* crop: false,
* closeAfterSave: true,
* width: 500
* }
* });
* jodit.imageeditor.open('https://xdsoft.net/jodit/images/test.png', function (name, data, success, failed) {
* var img = jodit.node.c('img');
* img.setAttribute('src', 'https://xdsoft.net/jodit/images/test.png');
* if (box.action !== 'resize') {
* return failed('Sorry it is work only in resize mode. For croping use FileBrowser');
* }
* img.style.width = data.w;
* img.style.height = data.h;
* jodit.s.insertNode(img);
* success();
* });
* ```
*/
open(url: string, save: onSave): Promise<IDialog>;
private resizeUseRatio;
private cropUseRatio;
private readonly _dialog;
private image;
private cropImage;
private clicked;
private target;
private start_x;
private start_y;
private top_x;
private top_y;
private width;
private height;
private activeTab;
private naturalWidth;
private naturalHeight;
private ratio;
private new_h;
private new_w;
private diff_x;
private diff_y;
private readonly buttons;
private readonly editor;
private readonly resize_box;
private readonly crop_box;
private sizes;
private readonly resizeHandler;
private readonly cropHandler;
private readonly cropBox;
private readonly resizeBox;
private static calcValueByPercent;
private calcCropBox;
private showCrop;
private updateCropBox;
private updateResizeBox;
private setHandlers;
private onTitleModeClick;
private onChangeSizeInput;
private onResizeHandleMouseDown;
private onGlobalMouseUp;
private onGlobalMouseMove;
constructor(editor: IViewWithToolbar & IDlgs);
/** @override */
destruct(): any;
}
/**
* Open Image Editor
*/
export declare function openImageEditor(this: IViewWithToolbar & {
dataProvider: IFileBrowserDataProvider;
}, href: string, name: string, path: string, source: string, onSuccess?: () => void, onFailed?: (error: Error) => void): Promise<IDialog>;
export {};

View File

@@ -0,0 +1,577 @@
/*!
* 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 ImageEditor_1;
import { ViewComponent } from "../../core/component/index.js";
import { autobind, component, debounce, throttle } from "../../core/decorators/index.js";
import { Dom } from "../../core/dom/index.js";
import { $$, attr, call, css, refs, toArray, trim } from "../../core/helpers/index.js";
import { Button } from "../../core/ui/button/index.js";
import { Config } from "../../config.js";
import "./config.js";
import { form } from "./templates/form.js";
const jie = 'jodit-image-editor';
const TABS = {
resize: 'resize',
crop: 'crop'
};
/**
* The module allows you to edit the image: resize or cut any part of it
*
*/
let ImageEditor = ImageEditor_1 = class ImageEditor extends ViewComponent {
/** @override */
className() {
return 'ImageEditor';
}
get o() {
return this.options;
}
/**
* Hide image editor
*/
hide() {
this._dialog.close();
}
/**
* Open image editor
* @example
* ```javascript
* const jodit = Jodit.make('.editor', {
* imageeditor: {
* crop: false,
* closeAfterSave: true,
* width: 500
* }
* });
* jodit.imageeditor.open('https://xdsoft.net/jodit/images/test.png', function (name, data, success, failed) {
* var img = jodit.node.c('img');
* img.setAttribute('src', 'https://xdsoft.net/jodit/images/test.png');
* if (box.action !== 'resize') {
* return failed('Sorry it is work only in resize mode. For croping use FileBrowser');
* }
* img.style.width = data.w;
* img.style.height = data.h;
* jodit.s.insertNode(img);
* success();
* });
* ```
*/
open(url, save) {
return this.j.async.promise((resolve) => {
const timestamp = new Date().getTime();
this.image = this.j.c.element('img');
$$('img,.jodit-icon_loader', this.resize_box).forEach(Dom.safeRemove);
$$('img,.jodit-icon_loader', this.crop_box).forEach(Dom.safeRemove);
css(this.cropHandler, 'background', 'transparent');
this.onSave = save;
this.resize_box.appendChild(this.j.c.element('i', { class: 'jodit-icon_loader' }));
this.crop_box.appendChild(this.j.c.element('i', { class: 'jodit-icon_loader' }));
if (/\?/.test(url)) {
url += '&_tst=' + timestamp;
}
else {
url += '?_tst=' + timestamp;
}
this.image.setAttribute('src', url);
this._dialog.open();
const { widthInput, heightInput } = refs(this.editor);
const onload = () => {
if (this.isDestructed) {
return;
}
this.image.removeEventListener('load', onload);
this.naturalWidth = this.image.naturalWidth;
this.naturalHeight = this.image.naturalHeight;
widthInput.value = this.naturalWidth.toString();
heightInput.value = this.naturalHeight.toString();
this.ratio = this.naturalWidth / this.naturalHeight;
this.resize_box.appendChild(this.image);
this.cropImage = this.image.cloneNode(true);
this.crop_box.appendChild(this.cropImage);
Dom.safeRemove.apply(null, $$('.jodit-icon_loader', this.editor));
if (this.activeTab === TABS.crop) {
this.showCrop();
}
this.j.e.fire(this.resizeHandler, 'updatesize');
this.j.e.fire(this.cropHandler, 'updatesize');
this._dialog.setPosition();
this.j.e.fire('afterImageEditor');
resolve(this._dialog);
};
this.image.addEventListener('load', onload);
if (this.image.complete) {
onload();
}
});
}
onTitleModeClick(e) {
const self = this, title = e.target;
const slide = title === null || title === void 0 ? void 0 : title.parentElement;
if (!slide) {
return;
}
$$(`.${jie}__slider,.${jie}__area`, self.editor).forEach(elm => elm.classList.remove(`${jie}_active`));
slide.classList.add(`${jie}_active`);
this.activeTab = attr(slide, '-area') || TABS.resize;
const tab = self.editor.querySelector(`.${jie}__area.${jie}__area_` + self.activeTab);
if (tab) {
tab.classList.add(`${jie}_active`);
}
if (self.activeTab === TABS.crop) {
self.showCrop();
}
}
onChangeSizeInput(e) {
const self = this, input = e.target, { widthInput, heightInput } = refs(this.editor), isWidth = attr(input, 'data-ref') === 'widthInput', x = parseInt(input.value, 10), minX = isWidth ? self.o.min_width : self.o.min_height, minY = !isWidth ? self.o.min_width : self.o.min_height;
let y;
if (x > minX) {
css(self.image, isWidth ? 'width' : 'height', x);
if (self.resizeUseRatio) {
y = isWidth
? Math.round(x / self.ratio)
: Math.round(x * self.ratio);
if (y > minY) {
css(self.image, !isWidth ? 'width' : 'height', y);
if (isWidth) {
heightInput.value = y.toString();
}
else {
widthInput.value = y.toString();
}
}
}
}
this.j.e.fire(self.resizeHandler, 'updatesize');
}
onResizeHandleMouseDown(e) {
const self = this;
self.target = e.target;
e.preventDefault();
e.stopImmediatePropagation();
self.clicked = true;
self.start_x = e.clientX;
self.start_y = e.clientY;
if (self.activeTab === TABS.crop) {
self.top_x = css(self.cropHandler, 'left');
self.top_y = css(self.cropHandler, 'top');
self.width = self.cropHandler.offsetWidth;
self.height = self.cropHandler.offsetHeight;
}
else {
self.width = self.image.offsetWidth;
self.height = self.image.offsetHeight;
}
self.j.e
.on(this.j.ow, 'mousemove', this.onGlobalMouseMove)
.one(this.j.ow, 'mouseup', this.onGlobalMouseUp);
}
onGlobalMouseUp(e) {
if (this.clicked) {
this.clicked = false;
e.stopImmediatePropagation();
this.j.e.off(this.j.ow, 'mousemove', this.onGlobalMouseMove);
}
}
onGlobalMouseMove(e) {
const self = this;
if (!self.clicked) {
return;
}
const { widthInput, heightInput } = refs(this.editor);
self.diff_x = e.clientX - self.start_x;
self.diff_y = e.clientY - self.start_y;
if ((self.activeTab === TABS.resize && self.resizeUseRatio) ||
(self.activeTab === TABS.crop && self.cropUseRatio)) {
if (self.diff_x) {
self.new_w = self.width + self.diff_x;
self.new_h = Math.round(self.new_w / self.ratio);
}
else {
self.new_h = self.height + self.diff_y;
self.new_w = Math.round(self.new_h * self.ratio);
}
}
else {
self.new_w = self.width + self.diff_x;
self.new_h = self.height + self.diff_y;
}
if (self.activeTab === TABS.resize) {
if (self.new_w > self.o.resizeMinWidth) {
css(self.image, 'width', self.new_w + 'px');
widthInput.value = self.new_w.toString();
}
if (self.new_h > self.o.resizeMinHeight) {
css(self.image, 'height', self.new_h + 'px');
heightInput.value = self.new_h.toString();
}
this.j.e.fire(self.resizeHandler, 'updatesize');
}
else {
if (self.target !== self.cropHandler) {
if (self.top_x + self.new_w > self.cropImage.offsetWidth) {
self.new_w = self.cropImage.offsetWidth - self.top_x;
}
if (self.top_y + self.new_h > self.cropImage.offsetHeight) {
self.new_h = self.cropImage.offsetHeight - self.top_y;
}
css(self.cropHandler, {
width: self.new_w,
height: self.new_h
});
}
else {
if (self.top_x + self.diff_x + self.cropHandler.offsetWidth >
self.cropImage.offsetWidth) {
self.diff_x =
self.cropImage.offsetWidth -
self.top_x -
self.cropHandler.offsetWidth;
}
css(self.cropHandler, 'left', self.top_x + self.diff_x);
if (self.top_y + self.diff_y + self.cropHandler.offsetHeight >
self.cropImage.offsetHeight) {
self.diff_y =
self.cropImage.offsetHeight -
self.top_y -
self.cropHandler.offsetHeight;
}
css(self.cropHandler, 'top', self.top_y + self.diff_y);
}
this.j.e.fire(self.cropHandler, 'updatesize');
}
}
constructor(editor) {
super(editor);
this.resizeUseRatio = true;
this.cropUseRatio = true;
this.clicked = false;
this.start_x = 0;
this.start_y = 0;
this.top_x = 0;
this.top_y = 0;
this.width = 0;
this.height = 0;
this.activeTab = TABS.resize;
this.naturalWidth = 0;
this.naturalHeight = 0;
this.ratio = 0;
this.new_h = 0;
this.new_w = 0;
this.diff_x = 0;
this.diff_y = 0;
this.cropBox = {
x: 0,
y: 0,
w: 0,
h: 0
};
this.resizeBox = {
w: 0,
h: 0
};
this.calcCropBox = () => {
const node = this.crop_box.parentNode, w = node.offsetWidth * 0.8, h = node.offsetHeight * 0.8;
let wn = w, hn = h;
const { naturalWidth: nw, naturalHeight: nh } = this;
if (w > nw && h > nh) {
wn = nw;
hn = nh;
}
else if (this.ratio > w / h) {
wn = w;
hn = nh * (w / nw);
}
else {
wn = nw * (h / nh);
hn = h;
}
css(this.crop_box, {
width: wn,
height: hn
});
};
this.showCrop = () => {
if (!this.cropImage) {
return;
}
this.calcCropBox();
const w = this.cropImage.offsetWidth ||
this.image.offsetWidth ||
this.image.naturalWidth;
this.new_w = ImageEditor_1.calcValueByPercent(w, this.o.cropDefaultWidth);
const h = this.cropImage.offsetHeight ||
this.image.offsetHeight ||
this.image.naturalHeight;
if (this.cropUseRatio) {
this.new_h = this.new_w / this.ratio;
}
else {
this.new_h = ImageEditor_1.calcValueByPercent(h, this.o.cropDefaultHeight);
}
css(this.cropHandler, {
backgroundImage: 'url(' + attr(this.cropImage, 'src') + ')',
width: this.new_w,
height: this.new_h,
left: w / 2 - this.new_w / 2,
top: h / 2 - this.new_h / 2
});
this.j.e.fire(this.cropHandler, 'updatesize');
};
this.updateCropBox = () => {
if (!this.cropImage) {
return;
}
const ratioX = this.cropImage.offsetWidth / this.naturalWidth, ratioY = this.cropImage.offsetHeight / this.naturalHeight;
this.cropBox.x = css(this.cropHandler, 'left') / ratioX;
this.cropBox.y = css(this.cropHandler, 'top') / ratioY;
this.cropBox.w = this.cropHandler.offsetWidth / ratioX;
this.cropBox.h = this.cropHandler.offsetHeight / ratioY;
this.sizes.textContent =
this.cropBox.w.toFixed(0) + 'x' + this.cropBox.h.toFixed(0);
};
this.updateResizeBox = () => {
this.resizeBox.w = this.image.offsetWidth || this.naturalWidth;
this.resizeBox.h = this.image.offsetHeight || this.naturalHeight;
};
this.setHandlers = () => {
const self = this;
const { widthInput, heightInput } = refs(this.editor);
self.j.e
.on([
self.editor.querySelector('.jodit_bottomright'),
self.cropHandler
], `mousedown.${jie}`, this.onResizeHandleMouseDown)
.on(this.j.ow, `resize.${jie}`, () => {
this.j.e.fire(self.resizeHandler, 'updatesize');
self.showCrop();
this.j.e.fire(self.cropHandler, 'updatesize');
});
self.j.e
.on(toArray(this.editor.querySelectorAll(`.${jie}__slider-title`)), 'click', this.onTitleModeClick)
.on([widthInput, heightInput], 'input', this.onChangeSizeInput);
const { keepAspectRatioResize, keepAspectRatioCrop } = refs(this.editor);
if (keepAspectRatioResize) {
keepAspectRatioResize.addEventListener('change', () => {
this.resizeUseRatio = keepAspectRatioResize.checked;
});
}
if (keepAspectRatioCrop) {
keepAspectRatioCrop.addEventListener('change', () => {
this.cropUseRatio = keepAspectRatioCrop.checked;
});
}
self.j.e
.on(self.resizeHandler, 'updatesize', () => {
css(self.resizeHandler, {
top: 0,
left: 0,
width: self.image.offsetWidth || self.naturalWidth,
height: self.image.offsetHeight || self.naturalHeight
});
this.updateResizeBox();
})
.on(self.cropHandler, 'updatesize', () => {
if (!self.cropImage) {
return;
}
let new_x = css(self.cropHandler, 'left'), new_y = css(self.cropHandler, 'top'), new_width = self.cropHandler.offsetWidth, new_height = self.cropHandler.offsetHeight;
if (new_x < 0) {
new_x = 0;
}
if (new_y < 0) {
new_y = 0;
}
if (new_x + new_width > self.cropImage.offsetWidth) {
new_width = self.cropImage.offsetWidth - new_x;
if (self.cropUseRatio) {
new_height = new_width / self.ratio;
}
}
if (new_y + new_height > self.cropImage.offsetHeight) {
new_height = self.cropImage.offsetHeight - new_y;
if (self.cropUseRatio) {
new_width = new_height * self.ratio;
}
}
css(self.cropHandler, {
width: new_width,
height: new_height,
left: new_x,
top: new_y,
backgroundPosition: -new_x - 1 + 'px ' + (-new_y - 1) + 'px',
backgroundSize: self.cropImage.offsetWidth +
'px ' +
self.cropImage.offsetHeight +
'px'
});
self.updateCropBox();
});
Object.values(self.buttons).forEach(button => {
button.onAction(() => {
const data = {
action: self.activeTab,
box: self.activeTab === TABS.resize
? self.resizeBox
: self.cropBox
};
switch (button) {
case self.buttons.saveas:
self.j.prompt('Enter new name', 'Save in new file', (name) => {
if (!trim(name)) {
self.j.alert('The name should not be empty');
return false;
}
self.onSave(name, data, self.hide, (e) => {
self.j.alert(e.message);
});
});
break;
case self.buttons.save:
self.onSave(undefined, data, self.hide, (e) => {
self.j.alert(e.message);
});
break;
case self.buttons.reset:
if (self.activeTab === TABS.resize) {
css(self.image, {
width: null,
height: null
});
widthInput.value = self.naturalWidth.toString();
heightInput.value = self.naturalHeight.toString();
self.j.e.fire(self.resizeHandler, 'updatesize');
}
else {
self.showCrop();
}
break;
}
});
});
};
this.options =
editor && editor.o && editor.o.imageeditor
? editor.o.imageeditor
: Config.defaultOptions.imageeditor;
const o = this.options;
this.resizeUseRatio = o.resizeUseRatio;
this.cropUseRatio = o.cropUseRatio;
this.buttons = {
reset: Button(this.j, 'update', 'Reset'),
save: Button(this.j, 'save', 'Save'),
saveas: Button(this.j, 'save', 'Save as ...')
};
this.activeTab = o.resize ? TABS.resize : TABS.crop;
this.editor = form(this.j, this.options);
const { resizeBox, cropBox } = refs(this.editor);
this.resize_box = resizeBox;
this.crop_box = cropBox;
this.sizes = this.editor.querySelector(`.${jie}__area.${jie}__area_crop .jodit-image-editor__sizes`);
this.resizeHandler = this.editor.querySelector(`.${jie}__resizer`);
this.cropHandler = this.editor.querySelector(`.${jie}__croper`);
this._dialog = this.j.dlg({
buttons: ['fullsize', 'dialog.close']
});
this._dialog.setContent(this.editor);
this._dialog.setSize(this.o.width, this.o.height);
this._dialog.setHeader([
this.buttons.reset,
this.buttons.save,
this.buttons.saveas
]);
this.setHandlers();
}
/** @override */
destruct() {
if (this.isDestructed) {
return;
}
if (this._dialog && !this._dialog.isInDestruct) {
this._dialog.destruct();
}
Dom.safeRemove(this.editor);
if (this.j.e) {
this.j.e
.off(this.j.ow, 'mousemove', this.onGlobalMouseMove)
.off(this.j.ow, 'mouseup', this.onGlobalMouseUp)
.off(this.ow, `.${jie}`)
.off(`.${jie}`);
}
super.destruct();
}
};
ImageEditor.calcValueByPercent = (value, percent) => {
const percentStr = percent.toString();
const valueNbr = parseFloat(value.toString());
let match;
match = /^[-+]?[0-9]+(px)?$/.exec(percentStr);
if (match) {
return parseInt(percentStr, 10);
}
match = /^([-+]?[0-9.]+)%$/.exec(percentStr);
if (match) {
return Math.round(valueNbr * (parseFloat(match[1]) / 100));
}
return valueNbr || 0;
};
__decorate([
autobind
], ImageEditor.prototype, "hide", null);
__decorate([
autobind
], ImageEditor.prototype, "open", null);
__decorate([
autobind
], ImageEditor.prototype, "onTitleModeClick", null);
__decorate([
debounce(),
autobind
], ImageEditor.prototype, "onChangeSizeInput", null);
__decorate([
autobind
], ImageEditor.prototype, "onResizeHandleMouseDown", null);
__decorate([
autobind
], ImageEditor.prototype, "onGlobalMouseUp", null);
__decorate([
throttle(10)
], ImageEditor.prototype, "onGlobalMouseMove", null);
ImageEditor = ImageEditor_1 = __decorate([
component
], ImageEditor);
export { ImageEditor };
/**
* Open Image Editor
*/
export function openImageEditor(href, name, path, source, onSuccess, onFailed) {
return this.getInstance('ImageEditor', this.o).open(href, (newname, box, success, failed) => call(box.action === 'resize'
? this.dataProvider.resize
: this.dataProvider.crop, path, source, name, newname, box.box)
.then(ok => {
if (ok) {
success();
if (onSuccess) {
onSuccess();
}
}
})
.catch(error => {
failed(error);
if (onFailed) {
onFailed(error);
}
}));
}

View File

@@ -0,0 +1,10 @@
/*!
* 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
*/
/**
* @module modules/image-editor
*/
import type { ImageEditorOptions, IViewBased } from "../../../types/index";
export declare const form: (editor: IViewBased, o: ImageEditorOptions) => HTMLElement;

View File

@@ -0,0 +1,81 @@
/*!
* 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
*/
import { Icon } from "../../../core/ui/index.js";
const jie = 'jodit-image-editor';
const gi = Icon.get.bind(Icon);
const act = (el, className = 'jodti-image-editor_active') => el ? className : '';
export const form = (editor, o) => {
const i = editor.i18n.bind(editor);
const switcher = (label, ref, active = true) => `<div class="jodit-form__group">
<label class="jodit-switcher-wrapper">
<span class='jodit-switcher'>
<input ${act(active, 'checked')} data-ref="${ref}" type="checkbox"/>
<span class="jodit-switcher__slider"></span>
</span>
<span>${i(label)}</span>
</label>
</div>`;
return editor.create.fromHTML(`<form class="${jie} jodit-properties">
<div class="jodit-grid jodit-grid_xs-column">
<div class="jodit_col-lg-3-4 jodit_col-sm-5-5">
${o.resize
? `<div class="${jie}__area ${jie}__area_resize ${jie}_active">
<div data-ref="resizeBox" class="${jie}__box"></div>
<div class="${jie}__resizer">
<i class="jodit_bottomright"></i>
</div>
</div>`
: ''}
${o.crop
? `<div class="${jie}__area ${jie}__area_crop ${act(!o.resize)}">
<div data-ref="cropBox" class="${jie}__box">
<div class="${jie}__croper">
<i class="jodit_bottomright"></i>
<i class="${jie}__sizes"></i>
</div>
</div>
</div>`
: ''}
</div>
<div class="jodit_col-lg-1-4 jodit_col-sm-5-5">
${o.resize
? `<div data-area="resize" class="${jie}__slider ${jie}_active">
<div class="${jie}__slider-title">
${gi('resize')}
${i('Resize')}
</div>
<div class="${jie}__slider-content">
<div class="jodit-form__group">
<label>
${i('Width')}
</label>
<input type="number" data-ref="widthInput" class="jodit-input"/>
</div>
<div class="jodit-form__group">
<label>
${i('Height')}
</label>
<input type="number" data-ref="heightInput" class="jodit-input"/>
</div>
${switcher('Keep Aspect Ratio', 'keepAspectRatioResize')}
</div>
</div>`
: ''}
${o.crop
? `<div data-area="crop" class="${jie}__slider ${act(!o.resize)}'">
<div class="${jie}__slider-title">
${gi('crop')}
${i('Crop')}
</div>
<div class="${jie}__slider-content">
${switcher('Keep Aspect Ratio', 'keepAspectRatioCrop')}
</div>
</div>`
: ''}
</div>
</div>
</form>`);
};

38
node_modules/jodit/esm/modules/index.d.ts generated vendored Normal file
View File

@@ -0,0 +1,38 @@
/*!
* 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
*/
/**
* [[include:modules/README.md]]
* @packageDocumentation
* @module modules
*/
import { Component, STATUSES, ViewComponent } from "../core/component/index";
import * as Helpers from "../core/helpers/index";
export { Component, STATUSES, ViewComponent };
export { ContextMenu } from "./context-menu/context-menu";
export * from "./dialog/index";
export * from "./file-browser/index";
export { Async } from "../core/async/index";
export { Create } from "../core/create/index";
export { Dom, LazyWalker } from "../core/dom/index";
export * from "../core/event-emitter/index";
export { Plugin } from "../core/plugin/index";
export * from "../core/request/index";
export * from "../core/ui/index";
export { View } from "../core/view/view";
export { ViewWithToolbar } from "../core/view/view-with-toolbar";
export { Helpers };
export { History } from "./history/history";
export { Snapshot } from "./history/snapshot";
export { ImageEditor } from "./image-editor/image-editor";
export { UIMessages } from "./messages/messages";
export { StatusBar } from "./status-bar/status-bar";
export { Table } from "./table/table";
export * from "./toolbar/button/index";
export { ToolbarCollection } from "./toolbar/collection/collection";
export { ToolbarEditorCollection } from "./toolbar/collection/editor-collection";
export { Uploader } from "./uploader/uploader";
export { PluginSystem } from "../core/plugin/plugin-system";
export { CommitStyle, Selection } from "../core/selection/index";

38
node_modules/jodit/esm/modules/index.js generated vendored Normal file
View File

@@ -0,0 +1,38 @@
/*!
* 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
*/
/**
* [[include:modules/README.md]]
* @packageDocumentation
* @module modules
*/
import { Component, STATUSES, ViewComponent } from "../core/component/index.js";
import * as Helpers from "../core/helpers/index.js";
export { Component, STATUSES, ViewComponent };
export { ContextMenu } from "./context-menu/context-menu.js";
export * from "./dialog/index.js";
export * from "./file-browser/index.js";
export { Async } from "../core/async/index.js";
export { Create } from "../core/create/index.js";
export { Dom, LazyWalker } from "../core/dom/index.js";
export * from "../core/event-emitter/index.js";
export { Plugin } from "../core/plugin/index.js";
export * from "../core/request/index.js";
export * from "../core/ui/index.js";
export { View } from "../core/view/view.js";
export { ViewWithToolbar } from "../core/view/view-with-toolbar.js";
export { Helpers };
export { History } from "./history/history.js";
export { Snapshot } from "./history/snapshot.js";
export { ImageEditor } from "./image-editor/image-editor.js";
export { UIMessages } from "./messages/messages.js";
export { StatusBar } from "./status-bar/status-bar.js";
export { Table } from "./table/table.js";
export * from "./toolbar/button/index.js";
export { ToolbarCollection } from "./toolbar/collection/collection.js";
export { ToolbarEditorCollection } from "./toolbar/collection/editor-collection.js";
export { Uploader } from "./uploader/uploader.js";
export { PluginSystem } from "../core/plugin/plugin-system.js";
export { CommitStyle, Selection } from "../core/selection/index.js";

17
node_modules/jodit/esm/modules/messages/message.d.ts generated vendored Normal file
View File

@@ -0,0 +1,17 @@
/*!
* 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
*/
/**
* @module modules/messages
*/
import type { IViewBased, MessageVariant } from "../../types/index";
import { UIElement } from "../../core/ui/index";
export declare class UIMessage extends UIElement {
className(): string;
constructor(jodit: IViewBased, options: {
text: string;
variant: MessageVariant;
});
}

32
node_modules/jodit/esm/modules/messages/message.js generated vendored Normal file
View File

@@ -0,0 +1,32 @@
/*!
* 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;
};
import { component } from "../../core/decorators/component/component.js";
import { UIElement } from "../../core/ui/index.js";
let UIMessage = class UIMessage extends UIElement {
className() {
return 'UIMessage';
}
constructor(jodit, options) {
super(jodit);
this.setMod('active', true);
this.setMod('variant', options.variant);
this.container.textContent = options.text;
}
};
UIMessage = __decorate([
component
], UIMessage);
export { UIMessage };

63
node_modules/jodit/esm/modules/messages/messages.d.ts generated vendored Normal file
View File

@@ -0,0 +1,63 @@
/*!
* 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
*/
/**
* [[include:modules/messages/README.md]]
* @packageDocumentation
* @module modules/messages
*/
import type { IMessages, IViewBased, MessageVariant } from "../../types/index";
import { UIGroup } from "../../core/ui/group/group";
/**
* Plugin display pop-up messages in the lower right corner of the editor
*/
export declare class UIMessages extends UIGroup implements IMessages {
private readonly __box;
readonly options: {
defaultTimeout: number;
defaultOffset: number;
};
className(): string;
constructor(jodit: IViewBased, __box: HTMLElement, options?: {
defaultTimeout: number;
defaultOffset: number;
});
/**
* Show popup info message in the lower right corner of the container
* ```js
* const jodit = Jodit.make('#editor');
* jodit.info('Hello world', 3000);
* ```
*/
info(text: string, timeout?: number): void;
/**
* Show popup success message in the lower right corner of the container
* ```js
* const jodit = Jodit.make('#editor');
* jodit.success('Hello world', 3000);
* ```
*/
success(text: string, timeout?: number): void;
/**
* Show popup error message in the lower right corner of the container
* ```js
* const jodit = Jodit.make('#editor');
* jodit.error('Hello world', 3000);
* ```
*/
error(text: string, timeout?: number): void;
/**
* Show popup message in the lower right corner of the container
* ```js
* const jodit = Jodit.make('#editor');
* jodit.message('Hello world', 'info', 3000);
* ```
*/
message(text: string, variant?: MessageVariant, timeout?: number): void;
private __message;
private __getRemoveCallback;
private __messages;
private __calcOffsets;
}

126
node_modules/jodit/esm/modules/messages/messages.js generated vendored Normal file
View File

@@ -0,0 +1,126 @@
/*!
* 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;
};
import { component } from "../../core/decorators/component/component.js";
import { css } from "../../core/helpers/utils/css.js";
import { UIGroup } from "../../core/ui/group/group.js";
import { UIMessage } from "./message.js";
/**
* Plugin display pop-up messages in the lower right corner of the editor
*/
let UIMessages = class UIMessages extends UIGroup {
className() {
return 'UIMessages';
}
constructor(jodit, __box, options = {
defaultTimeout: 3000,
defaultOffset: 5
}) {
super(jodit);
this.__box = __box;
this.options = options;
this.__messages = new Set();
}
/**
* Show popup info message in the lower right corner of the container
* ```js
* const jodit = Jodit.make('#editor');
* jodit.info('Hello world', 3000);
* ```
*/
info(text, timeout) {
this.__message(text, 'info', timeout);
}
/**
* Show popup success message in the lower right corner of the container
* ```js
* const jodit = Jodit.make('#editor');
* jodit.success('Hello world', 3000);
* ```
*/
success(text, timeout) {
this.__message(text, 'success', timeout);
}
/**
* Show popup error message in the lower right corner of the container
* ```js
* const jodit = Jodit.make('#editor');
* jodit.error('Hello world', 3000);
* ```
*/
error(text, timeout) {
this.__message(text, 'error', timeout);
}
/**
* Show popup message in the lower right corner of the container
* ```js
* const jodit = Jodit.make('#editor');
* jodit.message('Hello world', 'info', 3000);
* ```
*/
message(text, variant, timeout) {
this.__message(text, variant, timeout);
}
__message(text, variant = 'info', timeout) {
const key = text + ':' + variant;
if (this.__messages.has(key)) {
this.async.updateTimeout(key, timeout || this.options.defaultTimeout);
return;
}
if (!this.__box) {
throw new Error('Container is not defined: ' + key);
}
this.__box.appendChild(this.container);
const msg = new UIMessage(this.j, { text, variant });
this.append(msg);
this.__calcOffsets();
this.__messages.add(key);
const remove = this.__getRemoveCallback(msg, key);
this.j.e.on(msg.container, 'pointerdown', remove);
this.async.setTimeout(remove, {
label: key,
timeout: timeout || this.options.defaultTimeout
});
}
__getRemoveCallback(msg, key) {
const remove = (e) => {
e && e.preventDefault();
if (msg.isInDestruct) {
return;
}
this.async.clearTimeout(key);
this.j.e.off(msg.container, 'pointerdown', remove);
this.__messages.delete(key);
msg.setMod('active', false);
this.async.setTimeout(() => {
this.remove(msg);
msg.destruct();
this.__calcOffsets();
}, 300);
};
return remove;
}
__calcOffsets() {
let height = 5;
this.elements.forEach(elm => {
css(elm.container, 'bottom', height + 'px');
height += elm.container.offsetHeight + this.options.defaultOffset;
});
}
};
UIMessages = __decorate([
component
], UIMessages);
export { UIMessages };

View File

@@ -0,0 +1,45 @@
/*!
* 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
*/
/**
* [[include:modules/status-bar/README.md]]
* @packageDocumentation
* @module modules/status-bar
*/
import type { IDictionary, IJodit, IStatusBar, ModType } from "../../types/index";
import { ViewComponent } from "../../core/component/index";
import { Elms } from "../../core/traits/elms";
import { Mods } from "../../core/traits/mods";
export interface StatusBar extends Mods, Elms {
}
export declare class StatusBar extends ViewComponent<IJodit> implements IStatusBar {
readonly target: HTMLElement;
className(): string;
readonly container: HTMLDivElement;
/**
* Hide statusbar
*/
hide(): void;
/**
* Show statusbar
*/
show(): void;
/**
* Status bar is shown
*/
get isShown(): boolean;
readonly mods: IDictionary<ModType>;
/**
* Height of statusbar
*/
getHeight(): number;
private findEmpty;
/**
* Add element in statusbar
*/
append(child: HTMLElement, inTheRight?: boolean): void;
constructor(jodit: IJodit, target: HTMLElement);
destruct(): void;
}

View File

@@ -0,0 +1,97 @@
/*!
* 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;
};
import { STATUSES, ViewComponent } from "../../core/component/index.js";
import { component, derive } from "../../core/decorators/index.js";
import { Dom } from "../../core/dom/dom.js";
import { Elms } from "../../core/traits/elms.js";
import { Mods } from "../../core/traits/mods.js";
let StatusBar = class StatusBar extends ViewComponent {
className() {
return 'StatusBar';
}
/**
* Hide statusbar
*/
hide() {
this.container.classList.add('jodit_hidden');
}
/**
* Show statusbar
*/
show() {
this.container.classList.remove('jodit_hidden');
}
/**
* Status bar is shown
*/
get isShown() {
return !this.container.classList.contains('jodit_hidden');
}
/**
* Height of statusbar
*/
getHeight() {
var _a, _b;
return (_b = (_a = this.container) === null || _a === void 0 ? void 0 : _a.offsetHeight) !== null && _b !== void 0 ? _b : 0;
}
findEmpty(inTheRight = false) {
const items = this.getElms(inTheRight ? 'item-right' : 'item');
for (let i = 0; i < items.length; i += 1) {
if (!items[i].innerHTML.trim().length) {
return items[i];
}
}
return;
}
/**
* Add element in statusbar
*/
append(child, inTheRight = false) {
var _a;
const wrapper = this.findEmpty(inTheRight) ||
this.j.c.div(this.getFullElName('item'));
if (inTheRight) {
wrapper.classList.add(this.getFullElName('item-right'));
}
wrapper.appendChild(child);
(_a = this.container) === null || _a === void 0 ? void 0 : _a.appendChild(wrapper);
if (this.j.o.statusbar) {
this.show();
}
this.j.e.fire('resize');
}
constructor(jodit, target) {
super(jodit);
this.target = target;
this.mods = {};
this.container = jodit.c.div('jodit-status-bar');
target.appendChild(this.container);
this.hide();
}
destruct() {
if (this.isInDestruct) {
return;
}
this.setStatus(STATUSES.beforeDestruct);
Dom.safeRemove(this.container);
super.destruct();
}
};
StatusBar = __decorate([
component,
derive(Mods, Elms)
], StatusBar);
export { StatusBar };

108
node_modules/jodit/esm/modules/table/table.d.ts generated vendored Normal file
View File

@@ -0,0 +1,108 @@
/*!
* 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
*/
/**
* [[include:modules/table/README.md]]
* @packageDocumentation
* @module modules/table
*/
import type { IJodit } from "../../types/index";
import { ViewComponent } from "../../core/component/index";
export declare class Table extends ViewComponent<IJodit> {
/** @override */
className(): string;
private selected;
private static __selectedByTable;
private __recalculateStyles;
addSelection(td: HTMLTableCellElement): void;
removeSelection(td: HTMLTableCellElement): void;
/**
* Returns array of selected cells
*/
getAllSelectedCells(): HTMLTableCellElement[];
private static __getSelectedCellsByTable;
/** @override **/
destruct(): any;
private static __getRowsCount;
/**
* Returns rows count in the table
*/
getRowsCount(table: HTMLTableElement): number;
private static __getColumnsCount;
/**
* Returns columns count in the table
*/
getColumnsCount(table: HTMLTableElement): number;
private static __formalMatrix;
/**
* Generate formal table martix columns*rows
* @param table - Working table
* @param callback - if return false cycle break
*/
formalMatrix(table: HTMLTableElement, callback?: (cell: HTMLTableCellElement, row: number, col: number, colSpan: number, rowSpan: number) => false | void): HTMLTableCellElement[][];
private static __formalCoordinate;
/**
* Get cell coordinate in formal table (without colspan and rowspan)
*/
formalCoordinate(table: HTMLTableElement, cell: HTMLTableCellElement, max?: boolean): number[];
private static __appendRow;
/**
* Inserts a new line after row what contains the selected cell
*
* @param table - Working table
* @param line - Insert a new line after/before this
* line contains the selected cell
* @param after - Insert a new line after line contains the selected cell
*/
appendRow(table: HTMLTableElement, line: false | HTMLTableRowElement, after: boolean): void;
private static __removeRow;
/**
* Remove row
*/
removeRow(table: HTMLTableElement, rowIndex: number): void;
/**
* Insert column before / after all the columns containing the selected cells
*/
appendColumn(table: HTMLTableElement, selectedCell: HTMLTableCellElement, insertAfter?: boolean): void;
private static __removeColumn;
/**
* Remove column by index
*/
removeColumn(table: HTMLTableElement, j: number): void;
private static __getSelectedBound;
/**
* Define bound for selected cells
*/
getSelectedBound(table: HTMLTableElement, selectedCells: HTMLTableCellElement[]): number[][];
private static __normalizeTable;
private static __removeExtraColspans;
private static __removeExtraRowspans;
/**
* Try recalculate all coluns and rows after change
*/
normalizeTable(table: HTMLTableElement): void;
private static __mergeSelected;
/**
* It combines all the selected cells into one. The contents of the cells will also be combined
*/
mergeSelected(table: HTMLTableElement): void;
private static __splitHorizontal;
/**
* Divides all selected by `jodit_focused_cell` class table cell in 2 parts vertical. Those division into 2 columns
*/
splitHorizontal(table: HTMLTableElement): void;
private static __splitVertical;
/**
* It splits all the selected cells into 2 parts horizontally. Those. are added new row
*/
splitVertical(table: HTMLTableElement): void;
private static __setColumnWidthByDelta;
/**
* Set column width used delta value
*/
setColumnWidthByDelta(table: HTMLTableElement, column: number, delta: number, noUnmark: boolean, marked: HTMLTableCellElement[]): void;
private static __mark;
private static __unmark;
}

685
node_modules/jodit/esm/modules/table/table.js generated vendored Normal file
View File

@@ -0,0 +1,685 @@
/*!
* 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;
};
import { ViewComponent } from "../../core/component/index.js";
import * as consts from "../../core/constants.js";
import { debounce } from "../../core/decorators/index.js";
import { Dom } from "../../core/dom/index.js";
import { getContainer } from "../../core/global.js";
import { $$, attr, cssPath, isNumber, toArray, trim } from "../../core/helpers/index.js";
const markedValue = new WeakMap();
export class Table extends ViewComponent {
constructor() {
super(...arguments);
this.selected = new Set();
}
/** @override */
className() {
return 'Table';
}
__recalculateStyles() {
const style = getContainer(this.j, Table, 'style', true);
const selectors = [];
this.selected.forEach(td => {
const selector = cssPath(td);
selector && selectors.push(selector);
});
style.innerHTML = selectors.length
? selectors.join(',') +
`{${this.jodit.options.table.selectionCellStyle}}`
: '';
}
addSelection(td) {
this.selected.add(td);
this.__recalculateStyles();
const table = Dom.closest(td, 'table', this.j.editor);
if (table) {
const cells = Table.__selectedByTable.get(table) || new Set();
cells.add(td);
Table.__selectedByTable.set(table, cells);
}
}
removeSelection(td) {
this.selected.delete(td);
this.__recalculateStyles();
const table = Dom.closest(td, 'table', this.j.editor);
if (table) {
const cells = Table.__selectedByTable.get(table);
if (cells) {
cells.delete(td);
if (!cells.size) {
Table.__selectedByTable.delete(table);
}
}
}
}
/**
* Returns array of selected cells
*/
getAllSelectedCells() {
return toArray(this.selected);
}
static __getSelectedCellsByTable(table) {
const cells = Table.__selectedByTable.get(table);
return cells ? toArray(cells) : [];
}
/** @override **/
destruct() {
this.selected.clear();
return super.destruct();
}
static __getRowsCount(table) {
return table.rows.length;
}
/**
* Returns rows count in the table
*/
getRowsCount(table) {
return Table.__getRowsCount(table);
}
static __getColumnsCount(table) {
const matrix = Table.__formalMatrix(table);
return matrix.reduce((max_count, cells) => Math.max(max_count, cells.length), 0);
}
/**
* Returns columns count in the table
*/
getColumnsCount(table) {
return Table.__getColumnsCount(table);
}
static __formalMatrix(table, callback) {
const matrix = [[]];
const rows = toArray(table.rows);
const setCell = (cell, i) => {
if (matrix[i] === undefined) {
matrix[i] = [];
}
const colSpan = cell.colSpan, rowSpan = cell.rowSpan;
let column, row, currentColumn = 0;
while (matrix[i][currentColumn]) {
currentColumn += 1;
}
for (row = 0; row < rowSpan; row += 1) {
for (column = 0; column < colSpan; column += 1) {
if (matrix[i + row] === undefined) {
matrix[i + row] = [];
}
if (callback &&
callback(cell, i + row, currentColumn + column, colSpan, rowSpan) === false) {
return false;
}
matrix[i + row][currentColumn + column] = cell;
}
}
};
for (let i = 0; i < rows.length; i += 1) {
const cells = toArray(rows[i].cells);
for (let j = 0; j < cells.length; j += 1) {
if (setCell(cells[j], i) === false) {
return matrix;
}
}
}
return matrix;
}
/**
* Generate formal table martix columns*rows
* @param table - Working table
* @param callback - if return false cycle break
*/
formalMatrix(table, callback) {
return Table.__formalMatrix(table, callback);
}
static __formalCoordinate(table, cell, max = false) {
let i = 0, j = 0, width = 1, height = 1;
Table.__formalMatrix(table, (td, ii, jj, colSpan, rowSpan) => {
if (cell === td) {
i = ii;
j = jj;
width = colSpan || 1;
height = rowSpan || 1;
if (max) {
j += (colSpan || 1) - 1;
i += (rowSpan || 1) - 1;
}
return false;
}
});
return [i, j, width, height];
}
/**
* Get cell coordinate in formal table (without colspan and rowspan)
*/
formalCoordinate(table, cell, max = false) {
return Table.__formalCoordinate(table, cell, max);
}
static __appendRow(table, line, after, create) {
var _a;
let row;
if (!line) {
const columnsCount = Table.__getColumnsCount(table);
row = create.element('tr');
for (let j = 0; j < columnsCount; j += 1) {
row.appendChild(create.element('td'));
}
}
else {
row = line.cloneNode(true);
$$('td,th', line).forEach(cell => {
const rowspan = attr(cell, 'rowspan');
if (rowspan && parseInt(rowspan, 10) > 1) {
const newRowSpan = parseInt(rowspan, 10) - 1;
attr(cell, 'rowspan', newRowSpan > 1 ? newRowSpan : null);
}
});
$$('td,th', row).forEach(cell => {
cell.innerHTML = '';
});
}
if (after && line && line.nextSibling) {
line.parentNode &&
line.parentNode.insertBefore(row, line.nextSibling);
}
else if (!after && line) {
line.parentNode && line.parentNode.insertBefore(row, line);
}
else {
(((_a = table.getElementsByTagName('tbody')) === null || _a === void 0 ? void 0 : _a[0]) || table).appendChild(row);
}
}
/**
* Inserts a new line after row what contains the selected cell
*
* @param table - Working table
* @param line - Insert a new line after/before this
* line contains the selected cell
* @param after - Insert a new line after line contains the selected cell
*/
appendRow(table, line, after) {
return Table.__appendRow(table, line, after, this.j.createInside);
}
static __removeRow(table, rowIndex) {
const box = Table.__formalMatrix(table);
let dec;
const row = table.rows[rowIndex];
box[rowIndex].forEach((cell, j) => {
dec = false;
if (rowIndex - 1 >= 0 && box[rowIndex - 1][j] === cell) {
dec = true;
}
else if (box[rowIndex + 1] && box[rowIndex + 1][j] === cell) {
if (cell.parentNode === row && cell.parentNode.nextSibling) {
dec = true;
let nextCell = j + 1;
while (box[rowIndex + 1][nextCell] === cell) {
nextCell += 1;
}
const nextRow = Dom.next(cell.parentNode, elm => Dom.isTag(elm, 'tr'), table);
if (nextRow) {
if (box[rowIndex + 1][nextCell]) {
nextRow.insertBefore(cell, box[rowIndex + 1][nextCell]);
}
else {
nextRow.appendChild(cell);
}
}
}
}
else {
Dom.safeRemove(cell);
}
if (dec &&
(cell.parentNode === row || cell !== box[rowIndex][j - 1])) {
const rowSpan = cell.rowSpan;
attr(cell, 'rowspan', rowSpan - 1 > 1 ? rowSpan - 1 : null);
}
});
Dom.safeRemove(row);
}
/**
* Remove row
*/
removeRow(table, rowIndex) {
return Table.__removeRow(table, rowIndex);
}
/**
* Insert column before / after all the columns containing the selected cells
*/
appendColumn(table, selectedCell, insertAfter = true) {
const box = Table.__formalMatrix(table);
if (!insertAfter && Dom.isCell(selectedCell.previousElementSibling)) {
return this.appendColumn(table, selectedCell.previousElementSibling, true);
}
const columnIndex = insertAfter
? selectedCell.cellIndex + ((selectedCell.colSpan || 1) - 1)
: selectedCell.cellIndex;
const newColumnIndex = insertAfter ? columnIndex + 1 : columnIndex;
for (let i = 0; i < box.length;) {
const cells = box[i];
if (cells[columnIndex] !== cells[newColumnIndex] ||
columnIndex === newColumnIndex) {
const cell = this.j.createInside.element('td');
if (insertAfter) {
Dom.after(cells[columnIndex], cell);
}
else {
Dom.before(cells[columnIndex], cell);
}
if (cells[columnIndex].rowSpan > 1) {
cell.rowSpan = cells[columnIndex].rowSpan;
}
}
else {
cells[columnIndex].colSpan += 1;
}
i += cells[columnIndex].rowSpan || 1;
}
}
static __removeColumn(table, j) {
const box = Table.__formalMatrix(table);
let dec;
box.forEach((cells, i) => {
const td = cells[j];
dec = false;
if (j - 1 >= 0 && box[i][j - 1] === td) {
dec = true;
}
else if (j + 1 < cells.length && box[i][j + 1] === td) {
dec = true;
}
else {
Dom.safeRemove(td);
}
if (dec && (i - 1 < 0 || td !== box[i - 1][j])) {
const colSpan = td.colSpan;
attr(td, 'colspan', colSpan - 1 > 1 ? (colSpan - 1).toString() : null);
}
});
}
/**
* Remove column by index
*/
removeColumn(table, j) {
return Table.__removeColumn(table, j);
}
static __getSelectedBound(table, selectedCells) {
const bound = [
[Infinity, Infinity],
[0, 0]
];
const box = Table.__formalMatrix(table);
let i, j, k;
for (i = 0; i < box.length; i += 1) {
for (j = 0; box[i] && j < box[i].length; j += 1) {
if (selectedCells.includes(box[i][j])) {
bound[0][0] = Math.min(i, bound[0][0]);
bound[0][1] = Math.min(j, bound[0][1]);
bound[1][0] = Math.max(i, bound[1][0]);
bound[1][1] = Math.max(j, bound[1][1]);
}
}
}
for (i = bound[0][0]; i <= bound[1][0]; i += 1) {
for (k = 1, j = bound[0][1]; j <= bound[1][1]; j += 1) {
while (box[i] && box[i][j - k] && box[i][j] === box[i][j - k]) {
bound[0][1] = Math.min(j - k, bound[0][1]);
bound[1][1] = Math.max(j - k, bound[1][1]);
k += 1;
}
k = 1;
while (box[i] && box[i][j + k] && box[i][j] === box[i][j + k]) {
bound[0][1] = Math.min(j + k, bound[0][1]);
bound[1][1] = Math.max(j + k, bound[1][1]);
k += 1;
}
k = 1;
while (box[i - k] && box[i][j] === box[i - k][j]) {
bound[0][0] = Math.min(i - k, bound[0][0]);
bound[1][0] = Math.max(i - k, bound[1][0]);
k += 1;
}
k = 1;
while (box[i + k] && box[i][j] === box[i + k][j]) {
bound[0][0] = Math.min(i + k, bound[0][0]);
bound[1][0] = Math.max(i + k, bound[1][0]);
k += 1;
}
}
}
return bound;
}
/**
* Define bound for selected cells
*/
getSelectedBound(table, selectedCells) {
return Table.__getSelectedBound(table, selectedCells);
}
static __normalizeTable(table) {
const __marked = [], box = Table.__formalMatrix(table);
Table.__removeExtraColspans(box, __marked);
Table.__removeExtraRowspans(box, __marked);
// remove rowspans and colspans equal 1 and empty class
for (let i = 0; i < box.length; i += 1) {
for (let j = 0; j < box[i].length; j += 1) {
if (box[i][j] === undefined) {
continue; // broken table
}
if (box[i][j].hasAttribute('rowspan') &&
box[i][j].rowSpan === 1) {
attr(box[i][j], 'rowspan', null);
}
if (box[i][j].hasAttribute('colspan') &&
box[i][j].colSpan === 1) {
attr(box[i][j], 'colspan', null);
}
if (box[i][j].hasAttribute('class') &&
!attr(box[i][j], 'class')) {
attr(box[i][j], 'class', null);
}
}
}
Table.__unmark(__marked);
}
static __removeExtraColspans(box, __marked) {
for (let j = 0; j < box[0].length; j += 1) {
let min = 1000000;
let not = false;
for (let i = 0; i < box.length; i += 1) {
if (box[i][j] === undefined) {
continue; // broken table
}
if (box[i][j].colSpan < 2) {
not = true;
break;
}
min = Math.min(min, box[i][j].colSpan);
}
if (!not) {
for (let i = 0; i < box.length; i += 1) {
if (box[i][j] === undefined) {
continue; // broken table
}
Table.__mark(box[i][j], 'colspan', box[i][j].colSpan - min + 1, __marked);
}
}
}
}
static __removeExtraRowspans(box, marked) {
let i = 0;
let j = 0;
for (i = 0; i < box.length; i += 1) {
let min = 1000000;
let not = false;
for (j = 0; j < box[i].length; j += 1) {
if (box[i][j] === undefined) {
continue; // broken table
}
if (box[i][j].rowSpan < 2) {
not = true;
break;
}
min = Math.min(min, box[i][j].rowSpan);
}
if (!not) {
for (j = 0; j < box[i].length; j += 1) {
if (box[i][j] === undefined) {
continue; // broken table
}
Table.__mark(box[i][j], 'rowspan', box[i][j].rowSpan - min + 1, marked);
}
}
}
}
/**
* Try recalculate all coluns and rows after change
*/
normalizeTable(table) {
return Table.__normalizeTable(table);
}
static __mergeSelected(table, jodit) {
const html = [], bound = Table.__getSelectedBound(table, Table.__getSelectedCellsByTable(table));
let w = 0, first = null, first_j = 0, td, cols = 0, rows = 0;
const alreadyMerged = new Set(), __marked = [];
if (bound && (bound[0][0] - bound[1][0] || bound[0][1] - bound[1][1])) {
Table.__formalMatrix(table, (cell, i, j, cs, rs) => {
if (i >= bound[0][0] && i <= bound[1][0]) {
if (j >= bound[0][1] && j <= bound[1][1]) {
td = cell;
if (alreadyMerged.has(td)) {
return;
}
alreadyMerged.add(td);
if (i === bound[0][0] && td.style.width) {
w += td.offsetWidth;
}
if (trim(cell.innerHTML.replace(/<br(\/)?>/g, '')) !== '') {
html.push(cell.innerHTML);
}
if (cs > 1) {
cols += cs - 1;
}
if (rs > 1) {
rows += rs - 1;
}
if (!first) {
first = cell;
first_j = j;
}
else {
Table.__mark(td, 'remove', 1, __marked);
instance(jodit).removeSelection(td);
}
}
}
});
cols = bound[1][1] - bound[0][1] + 1;
rows = bound[1][0] - bound[0][0] + 1;
if (first) {
if (cols > 1) {
Table.__mark(first, 'colspan', cols, __marked);
}
if (rows > 1) {
Table.__mark(first, 'rowspan', rows, __marked);
}
if (w) {
Table.__mark(first, 'width', ((w / table.offsetWidth) * 100).toFixed(consts.ACCURACY) + '%', __marked);
if (first_j) {
Table.__setColumnWidthByDelta(table, first_j, 0, true, __marked);
}
}
first.innerHTML = html.join('<br/>');
instance(jodit).addSelection(first);
alreadyMerged.delete(first);
Table.__unmark(__marked);
Table.__normalizeTable(table);
toArray(table.rows).forEach(tr => {
if (!tr.cells.length) {
Dom.safeRemove(tr);
}
});
}
}
}
/**
* It combines all the selected cells into one. The contents of the cells will also be combined
*/
mergeSelected(table) {
return Table.__mergeSelected(table, this.j);
}
static __splitHorizontal(table, jodit) {
let coord, td, tr, parent, after;
const __marked = [];
Table.__getSelectedCellsByTable(table).forEach((cell) => {
td = jodit.createInside.element('td');
td.appendChild(jodit.createInside.element('br'));
tr = jodit.createInside.element('tr');
coord = Table.__formalCoordinate(table, cell);
if (cell.rowSpan < 2) {
Table.__formalMatrix(table, (tdElm, i, j) => {
if (coord[0] === i &&
coord[1] !== j &&
tdElm !== cell) {
Table.__mark(tdElm, 'rowspan', tdElm.rowSpan + 1, __marked);
}
});
Dom.after(Dom.closest(cell, 'tr', table), tr);
tr.appendChild(td);
}
else {
Table.__mark(cell, 'rowspan', cell.rowSpan - 1, __marked);
Table.__formalMatrix(table, (tdElm, i, j) => {
if (i > coord[0] &&
i < coord[0] + cell.rowSpan &&
coord[1] > j &&
tdElm.parentNode
.rowIndex === i) {
after = tdElm;
}
if (coord[0] < i && tdElm === cell) {
parent = table.rows[i];
}
});
if (after) {
Dom.after(after, td);
}
else {
parent.insertBefore(td, parent.firstChild);
}
}
if (cell.colSpan > 1) {
Table.__mark(td, 'colspan', cell.colSpan, __marked);
}
Table.__unmark(__marked);
instance(jodit).removeSelection(cell);
});
this.__normalizeTable(table);
}
/**
* Divides all selected by `jodit_focused_cell` class table cell in 2 parts vertical. Those division into 2 columns
*/
splitHorizontal(table) {
return Table.__splitHorizontal(table, this.j);
}
static __splitVertical(table, jodit) {
let coord, td, percentage;
const __marked = [];
Table.__getSelectedCellsByTable(table).forEach(cell => {
coord = Table.__formalCoordinate(table, cell);
if (cell.colSpan < 2) {
Table.__formalMatrix(table, (tdElm, i, j) => {
if (coord[1] === j && coord[0] !== i && tdElm !== cell) {
Table.__mark(tdElm, 'colspan', tdElm.colSpan + 1, __marked);
}
});
}
else {
Table.__mark(cell, 'colspan', cell.colSpan - 1, __marked);
}
td = jodit.createInside.element('td');
td.appendChild(jodit.createInside.element('br'));
if (cell.rowSpan > 1) {
Table.__mark(td, 'rowspan', cell.rowSpan, __marked);
}
const oldWidth = cell.offsetWidth; // get old width
Dom.after(cell, td);
percentage = oldWidth / table.offsetWidth / 2;
Table.__mark(cell, 'width', (percentage * 100).toFixed(consts.ACCURACY) + '%', __marked);
Table.__mark(td, 'width', (percentage * 100).toFixed(consts.ACCURACY) + '%', __marked);
Table.__unmark(__marked);
instance(jodit).removeSelection(cell);
});
Table.__normalizeTable(table);
}
/**
* It splits all the selected cells into 2 parts horizontally. Those. are added new row
*/
splitVertical(table) {
return Table.__splitVertical(table, this.j);
}
static __setColumnWidthByDelta(table, column, delta, noUnmark, marked) {
const box = Table.__formalMatrix(table);
let clearWidthIndex = 0;
for (let i = 0; i < box.length; i += 1) {
const cell = box[i][column];
if (cell.colSpan > 1 && box.length > 1) {
continue;
}
const w = cell.offsetWidth;
const percent = ((w + delta) / table.offsetWidth) * 100;
Table.__mark(cell, 'width', percent.toFixed(consts.ACCURACY) + '%', marked);
clearWidthIndex = i;
break;
}
for (let i = clearWidthIndex + 1; i < box.length; i += 1) {
const cell = box[i][column];
Table.__mark(cell, 'width', null, marked);
}
if (!noUnmark) {
Table.__unmark(marked);
}
}
/**
* Set column width used delta value
*/
setColumnWidthByDelta(table, column, delta, noUnmark, marked) {
return Table.__setColumnWidthByDelta(table, column, delta, noUnmark, marked);
}
static __mark(cell, key, value, marked) {
var _a;
marked.push(cell);
const dict = (_a = markedValue.get(cell)) !== null && _a !== void 0 ? _a : {};
dict[key] = value === undefined ? 1 : value;
markedValue.set(cell, dict);
}
static __unmark(marked) {
marked.forEach(cell => {
const dict = markedValue.get(cell);
if (dict) {
Object.keys(dict).forEach((key) => {
const value = dict[key];
switch (key) {
case 'remove':
Dom.safeRemove(cell);
break;
case 'rowspan':
attr(cell, 'rowspan', isNumber(value) && value > 1 ? value : null);
break;
case 'colspan':
attr(cell, 'colspan', isNumber(value) && value > 1 ? value : null);
break;
case 'width':
if (value == null) {
cell.style.removeProperty('width');
if (!attr(cell, 'style')) {
attr(cell, 'style', null);
}
}
else {
cell.style.width = value.toString();
}
break;
}
delete dict[key];
});
markedValue.delete(cell);
}
});
}
}
Table.__selectedByTable = new WeakMap();
__decorate([
debounce()
], Table.prototype, "__recalculateStyles", null);
const instance = (j) => j.getInstance('Table', j.o);

View File

@@ -0,0 +1,84 @@
/*!
* 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
*/
/**
* [[include:modules/toolbar/button/README.md]]
* @packageDocumentation
* @module modules/toolbar/button
*/
import type { IControlTypeStrong, IToolbarButton, IToolbarCollection, IViewBased, Nullable } from "../../../types/index";
import { UIButton } from "../../../core/ui/button/index";
export declare class ToolbarButton<T extends IViewBased = IViewBased> extends UIButton implements IToolbarButton {
readonly control: IControlTypeStrong;
readonly target: Nullable<HTMLElement>;
/** @override */
className(): string;
readonly state: {
theme: string;
currentValue: string;
hasTrigger: boolean;
size: "tiny" | "xsmall" | "small" | "middle" | "large";
name: string;
value: string | number | boolean;
variant: import("../../../types/index").ButtonVariant;
type: "button" | "submit";
role: "button" | "tab";
disabled: boolean;
activated: boolean;
icon: import("../../../types/index").IUIIconState;
text: string;
tooltip: string;
tabIndex: import("../../../types/index").CanUndef<number>;
};
protected trigger: HTMLElement;
/**
* Get parent toolbar
*/
protected get toolbar(): Nullable<IToolbarCollection>;
/** @override **/
update(): void;
/**
* Calculates whether the button is active
*/
private __calculateActivatedStatus;
/**
* Calculates whether an element is blocked for the user
*/
private __calculateDisabledStatus;
/** @override */
protected onChangeActivated(): void;
/** @override */
protected onChangeText(): void;
/** @override */
protected onChangeTabIndex(): void;
protected createContainer(): HTMLElement;
/** @override */
focus(): void;
protected onChangeHasTrigger(): void;
/** @override */
protected onChangeDisabled(): void;
constructor(jodit: T, control: IControlTypeStrong, target?: Nullable<HTMLElement>);
/**
* Init constant data from control
*/
protected __initFromControl(): void;
/**
* Click on trigger button
*/
protected onTriggerClick(e: MouseEvent): void;
private openedPopup;
/**
* Create an open popup list
*/
private __openControlList;
protected onOutsideClick(e: MouseEvent): void;
private openPopup;
private __closePopup;
/**
* Click handler
*/
protected onClick(originalEvent: MouseEvent): void;
destruct(): any;
}

415
node_modules/jodit/esm/modules/toolbar/button/button.js generated vendored Normal file
View File

@@ -0,0 +1,415 @@
/*!
* 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;
};
import { STATUSES } from "../../../core/component/statuses.js";
import { autobind, cacheHTML, component, watch } from "../../../core/decorators/index.js";
import { Dom } from "../../../core/dom/index.js";
import { assert, attr, call, camelCase, isArray, isFunction, isJoditObject, isPlainObject, isString, keys, position } from "../../../core/helpers/index.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 { ToolbarCollection } from "../collection/collection.js";
import { makeCollection } from "../factory.js";
let ToolbarButton = class ToolbarButton extends UIButton {
/** @override */
className() {
return 'ToolbarButton';
}
/**
* Get parent toolbar
*/
get toolbar() {
return this.closest(ToolbarCollection);
}
/** @override **/
update() {
var _a, _b;
const { control, state } = this, tc = this.closest(ToolbarCollection);
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));
}
/** @override */
onChangeActivated() {
attr(this.button, 'aria-pressed', this.state.activated);
super.onChangeActivated();
}
/** @override */
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));
}
/** @override */
onChangeTabIndex() {
attr(this.button, 'tabindex', this.state.tabIndex);
}
createContainer() {
const cn = this.componentName;
const container = this.j.c.span(cn);
const button = super.createContainer();
attr(container, 'role', 'listitem');
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="trigger" 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() {
var _a;
const { control: ctr, state } = this;
this.updateSize();
state.name = ctr.name;
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;
}
else {
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;
}
}
if (ctr.tooltip) {
state.tooltip = this.j.i18n(isFunction(ctr.tooltip)
? ctr.tooltip(this.j, ctr, this)
: ctr.tooltip);
}
state.hasTrigger = Boolean(ctr.list || (ctr.popup && ctr.exec));
}
/**
* Click on trigger button
*/
onTriggerClick(e) {
var _a, _b, _c;
if (this.openedPopup) {
this.__closePopup();
return;
}
const { control: ctr } = this;
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, menu = this.openPopup(), 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;
}
}
/**
* 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 };

View File

@@ -0,0 +1,21 @@
/*!
* 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
*/
/**
* @module modules/toolbar/button
*/
import type { IControlTypeContent, IToolbarButton, IViewBased, Nullable } from "../../../types/index";
import { UIButton } from "../../../core/ui/button/index";
export declare class ToolbarContent<T extends IViewBased = IViewBased> extends UIButton implements IToolbarButton {
readonly control: IControlTypeContent;
readonly target: Nullable<HTMLElement>;
/** @override */
className(): string;
/** @override */
update(): void;
/** @override */
protected createContainer(): HTMLElement;
constructor(jodit: T, control: IControlTypeContent, target?: Nullable<HTMLElement>);
}

View File

@@ -0,0 +1,49 @@
/*!
* 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;
};
import { component } from "../../../core/decorators/index.js";
import { Dom } from "../../../core/dom/index.js";
import { attr, isString } from "../../../core/helpers/index.js";
import { UIButton } from "../../../core/ui/button/index.js";
let ToolbarContent = class ToolbarContent extends UIButton {
/** @override */
className() {
return 'ToolbarContent';
}
/** @override */
update() {
const content = this.control.getContent(this.j, this);
if (isString(content) || content.parentNode !== this.container) {
Dom.detach(this.container);
this.container.appendChild(isString(content) ? this.j.create.fromHTML(content) : content);
}
super.update();
}
/** @override */
createContainer() {
return this.j.c.span(this.componentName);
}
constructor(jodit, control, target = null) {
super(jodit);
this.control = control;
this.target = target;
this.container.classList.add(`${this.componentName}_${this.clearName(control.name)}`);
attr(this.container, 'role', 'content');
}
};
ToolbarContent = __decorate([
component
], ToolbarContent);
export { ToolbarContent };

View File

@@ -0,0 +1,11 @@
/*!
* 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
*/
/**
* @module modules/toolbar/button
*/
export * from "./button";
export * from "./content";
export * from "./select/select";

11
node_modules/jodit/esm/modules/toolbar/button/index.js generated vendored Normal file
View File

@@ -0,0 +1,11 @@
/*!
* 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
*/
/**
* @module modules/toolbar/button
*/
export * from "./button.js";
export * from "./content.js";
export * from "./select/select.js";

View File

@@ -0,0 +1,14 @@
/*!
* 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
*/
/**
* @module modules/toolbar/button
*/
import type { IViewBased } from "../../../../types/index";
import { ToolbarButton } from "../button";
export declare class ToolbarSelect<T extends IViewBased = IViewBased> extends ToolbarButton<T> {
className(): string;
update(): void;
}

View File

@@ -0,0 +1,47 @@
/*!
* 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;
};
import { component } from "../../../../core/decorators/index.js";
import { isPlainObject } from "../../../../core/helpers/checker/is-plain-object.js";
import { isString } from "../../../../core/helpers/checker/is-string.js";
import { ToolbarButton } from "../button.js";
let ToolbarSelect = class ToolbarSelect extends ToolbarButton {
className() {
return 'ToolbarSelect';
}
update() {
var _a, _b, _c;
super.update();
this.state.icon.name = '';
const { list, data } = this.control;
if (list) {
let key = this.state.value ||
(data && isString(data.currentValue)
? data.currentValue
: undefined);
if (!key) {
const keys = Object.keys(list);
key = keys[0];
}
const text = (isPlainObject(list) ? list[key.toString()] || key : key).toString();
this.state.text =
(_c = (_b = (_a = this.control).textTemplate) === null || _b === void 0 ? void 0 : _b.call(_a, this.jodit, text)) !== null && _c !== void 0 ? _c : text;
}
}
};
ToolbarSelect = __decorate([
component
], ToolbarSelect);
export { ToolbarSelect };

View File

@@ -0,0 +1,50 @@
/*!
* 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
*/
/**
* [[include:modules/toolbar/collection/README.md]]
* @packageDocumentation
* @module modules/toolbar/collection
*/
import type { ButtonsGroups, IBound, IControlTypeStrong, IToolbarButton, IToolbarCollection, IUIButton, IViewBased, IViewWithToolbar, Nullable } from "../../../types/index";
import { UIList } from "../../../core/ui/index";
export declare class ToolbarCollection<T extends IViewWithToolbar = IViewWithToolbar> extends UIList<T> implements IToolbarCollection {
/** @override */
className(): string;
private readonly __listenEvents;
/**
* First button in a list
*/
get firstButton(): Nullable<IToolbarButton>;
protected makeButton(control: IControlTypeStrong, target?: Nullable<HTMLElement>): IUIButton;
protected makeSelect(control: IControlTypeStrong, target?: Nullable<HTMLElement>): IUIButton;
/**
* Button should be active
*/
shouldBeActive(button: IToolbarButton): boolean | undefined;
/**
* The Button should be disabled
*/
shouldBeDisabled(button: IToolbarButton): boolean | undefined;
/**
* Returns current target for button
*/
getTarget(button: IToolbarButton): Node | null;
private __immediateUpdate;
update(): void;
/**
* Set direction
*/
setDirection(direction: 'rtl' | 'ltr'): void;
constructor(jodit: IViewBased);
protected __initEvents(): void;
hide(): void;
show(): void;
showInline(bound?: IBound): void;
/** @override **/
build(items: ButtonsGroups, target?: Nullable<HTMLElement>): this;
/** @override **/
destruct(): void;
}

View File

@@ -0,0 +1,124 @@
/*!
* 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;
};
import { autobind, component, debounce, hook } from "../../../core/decorators/index.js";
import { error } from "../../../core/helpers/utils/error/error.js";
import { UIList } from "../../../core/ui/index.js";
import { makeButton, makeSelect } from "../factory.js";
let ToolbarCollection = class ToolbarCollection extends UIList {
/** @override */
className() {
return 'ToolbarCollection';
}
/**
* First button in a list
*/
get firstButton() {
const [button] = this.buttons;
return button || null;
}
makeButton(control, target = null) {
return makeButton(this.j, control, target);
}
makeSelect(control, target = null) {
return makeSelect(this.j, control, target);
}
/**
* Button should be active
*/
shouldBeActive(button) {
return undefined;
}
/**
* The Button should be disabled
*/
shouldBeDisabled(button) {
return undefined;
}
/**
* Returns current target for button
*/
getTarget(button) {
return button.target || null;
}
__immediateUpdate() {
if (this.isDestructed || this.j.isLocked) {
return;
}
super.update();
this.j.e.fire('afterUpdateToolbar', this);
}
update() {
this.__immediateUpdate();
}
/**
* Set direction
*/
setDirection(direction) {
this.container.style.direction = direction;
this.container.setAttribute('dir', direction);
}
constructor(jodit) {
super(jodit);
this.__listenEvents = 'updatePlugins updateToolbar changeStack mousedown mouseup keydown change afterInit readonly afterResize ' +
'selectionchange changeSelection focus afterSetMode touchstart focus blur';
}
__initEvents() {
this.j.e
.on(this.__listenEvents, this.update)
.on('afterSetMode focus', this.__immediateUpdate);
}
hide() {
this.container.remove();
}
show() {
this.appendTo(this.j.toolbarContainer);
}
showInline(bound) {
throw error('The method is not implemented for this class.');
}
/** @override **/
build(items, target = null) {
const itemsWithGroupps = this.j.e.fire('beforeToolbarBuild', items);
if (itemsWithGroupps) {
items = itemsWithGroupps;
}
super.build(items, target);
return this;
}
/** @override **/
destruct() {
if (this.isDestructed) {
return;
}
this.j.e
.off(this.__listenEvents, this.update)
.off('afterSetMode focus', this.__immediateUpdate);
super.destruct();
}
};
__decorate([
autobind
], ToolbarCollection.prototype, "__immediateUpdate", null);
__decorate([
debounce(ctx => ctx.j.defaultTimeout, true)
], ToolbarCollection.prototype, "update", null);
__decorate([
hook('ready')
], ToolbarCollection.prototype, "__initEvents", null);
ToolbarCollection = __decorate([
component
], ToolbarCollection);
export { ToolbarCollection };

View File

@@ -0,0 +1,38 @@
/*!
* 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
*/
/**
* @module modules/toolbar/collection
*/
import type { IBound, IJodit, IToolbarButton } from "../../../types/index";
import { ToolbarCollection } from "./collection";
export declare class ToolbarEditorCollection extends ToolbarCollection<IJodit> {
/** @override */
className(): string;
/** @override */
shouldBeDisabled(button: IToolbarButton): boolean;
/** @override */
shouldBeActive(button: IToolbarButton): boolean;
private checkActiveStatus;
/** @override */
getTarget(button: IToolbarButton): Node | null;
/** @override */
constructor(jodit: IJodit);
/**
* Adds an invisible element to the container that can handle the
* situation when the editor is inside the <label>
*
* @see https://github.com/jodit/jodit-react/issues/138
*/
private prependInvisibleInput;
/**
* Show the inline toolbar inside WYSIWYG editor.
* @param bound - you can set the place for displaying the toolbar,
* or the place will be in the place of the cursor
*/
showInline(bound?: IBound): void;
hide(): void;
show(): void;
}

View File

@@ -0,0 +1,136 @@
/*!
* 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;
};
import * as consts from "../../../core/constants.js";
import { component } from "../../../core/decorators/index.js";
import { Dom } from "../../../core/dom/index.js";
import { css, isFunction } from "../../../core/helpers/index.js";
import { ToolbarCollection } from "./collection.js";
let ToolbarEditorCollection = class ToolbarEditorCollection extends ToolbarCollection {
/** @override */
className() {
return 'ToolbarEditorCollection';
}
/** @override */
shouldBeDisabled(button) {
const disabled = super.shouldBeDisabled(button);
if (disabled !== undefined) {
return disabled;
}
const mode = button.control.mode === undefined
? consts.MODE_WYSIWYG
: button.control.mode;
return !(mode === consts.MODE_SPLIT || mode === this.j.getRealMode());
}
/** @override */
shouldBeActive(button) {
const active = super.shouldBeActive(button);
if (active !== undefined) {
return active;
}
const element = this.j.selection ? this.j.s.current() : null;
if (!element) {
return false;
}
let elm;
if (button.control.tags) {
const tags = button.control.tags;
elm = element;
if (Dom.up(elm, (node) => {
if (node &&
tags.indexOf(node.nodeName.toLowerCase()) !== -1) {
return true;
}
}, this.j.editor)) {
return true;
}
}
// activate by supposed css
if (button.control.css) {
const css = button.control.css;
elm = element;
if (Dom.up(elm, (node) => {
if (node && !Dom.isText(node) && !Dom.isComment(node)) {
return this.checkActiveStatus(css, node);
}
}, this.j.editor)) {
return true;
}
}
return false;
}
/** @override */
getTarget(button) {
return button.target || this.j.s.current() || null;
}
/** @override */
constructor(jodit) {
super(jodit);
this.checkActiveStatus = (cssObject, node) => {
let matches = 0, total = 0;
Object.keys(cssObject).forEach((cssProperty) => {
const cssValue = cssObject[cssProperty];
if (isFunction(cssValue)) {
if (cssValue(this.j, css(node, cssProperty).toString())) {
matches += 1;
}
}
else {
if (cssValue.indexOf(css(node, cssProperty).toString()) !== -1) {
matches += 1;
}
}
total += 1;
});
return total === matches;
};
this.prependInvisibleInput(this.container);
}
/**
* Adds an invisible element to the container that can handle the
* situation when the editor is inside the <label>
*
* @see https://github.com/jodit/jodit-react/issues/138
*/
prependInvisibleInput(container) {
const input = this.j.create.element('input', {
tabIndex: -1,
disabled: true, // Because <label> can trigger click
style: 'width: 0; height:0; position: absolute; visibility: hidden;'
});
Dom.appendChildFirst(container, input);
}
/**
* Show the inline toolbar inside WYSIWYG editor.
* @param bound - you can set the place for displaying the toolbar,
* or the place will be in the place of the cursor
*/
showInline(bound) {
this.jodit.e.fire('showInlineToolbar', bound);
}
hide() {
this.jodit.e.fire('hidePopup');
super.hide();
this.jodit.e.fire('toggleToolbar');
}
show() {
super.show();
this.jodit.e.fire('toggleToolbar');
}
};
ToolbarEditorCollection = __decorate([
component
], ToolbarEditorCollection);
export { ToolbarEditorCollection };

20
node_modules/jodit/esm/modules/toolbar/factory.d.ts generated vendored Normal file
View File

@@ -0,0 +1,20 @@
/*!
* 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
*/
/**
* [[include:modules/toolbar/README.md]]
* @packageDocumentation
* @module modules/toolbar
*/
import type { IControlTypeStrong, IToolbarButton, IToolbarCollection, IUIElement, IViewBased, Nullable } from "../../types/index";
/**
* Collection factory
*/
export declare function makeCollection(jodit: IViewBased, parentElement?: IUIElement): IToolbarCollection;
/**
* Button factory
*/
export declare function makeButton(jodit: IViewBased, control: IControlTypeStrong, target?: Nullable<HTMLElement>): IToolbarButton;
export declare function makeSelect(view: IViewBased, control: IControlTypeStrong, target?: Nullable<HTMLElement>): IToolbarButton;

43
node_modules/jodit/esm/modules/toolbar/factory.js generated vendored Normal file
View File

@@ -0,0 +1,43 @@
/*!
* 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
*/
import { isFunction, isJoditObject } from "../../core/helpers/index.js";
import { ToolbarButton } from "./button/button.js";
import { ToolbarContent } from "./button/content.js";
import { ToolbarSelect } from "./button/select/select.js";
import { ToolbarCollection } from "./collection/collection.js";
import { ToolbarEditorCollection } from "./collection/editor-collection.js";
/**
* Collection factory
*/
export function makeCollection(jodit, parentElement) {
const collection = isJoditObject(jodit)
? new ToolbarEditorCollection(jodit)
: new ToolbarCollection(jodit);
if (jodit.o.textIcons) {
collection.container.classList.add('jodit_text_icons');
}
if (parentElement) {
collection.parentElement = parentElement;
}
if (jodit.o.toolbarButtonSize) {
collection.buttonSize = jodit.o.toolbarButtonSize;
}
return collection;
}
/**
* Button factory
*/
export function makeButton(jodit, control, target = null) {
if (isFunction(control.getContent)) {
return new ToolbarContent(jodit, control, target);
}
const button = new ToolbarButton(jodit, control, target);
button.state.tabIndex = jodit.o.allowTabNavigation ? 0 : -1;
return button;
}
export function makeSelect(view, control, target = null) {
return new ToolbarSelect(view, control, target);
}

18
node_modules/jodit/esm/modules/uploader/config.d.ts generated vendored Normal file
View File

@@ -0,0 +1,18 @@
/*!
* 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
*/
/**
* @module modules/uploader
*/
import type { IUploader, IUploaderOptions } from "../../types/index";
declare module 'jodit/config' {
interface Config {
/**
* Enable drag and drop file editor
*/
enableDragAndDropFileToEditor: boolean;
uploader: IUploaderOptions<IUploader>;
}
}

86
node_modules/jodit/esm/modules/uploader/config.js generated vendored Normal file
View File

@@ -0,0 +1,86 @@
/*!
* 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
*/
import { isArray } from "../../core/helpers/checker/is-array.js";
import { isJoditObject } from "../../core/helpers/checker/is-jodit-object.js";
import { Config } from "../../config.js";
/**
* Module for processing download documents and images by Drag and Drop
* Drag and Drop files
*/
Config.prototype.enableDragAndDropFileToEditor = true;
Config.prototype.uploader = {
url: '',
insertImageAsBase64URI: false,
imagesExtensions: ['jpg', 'png', 'jpeg', 'gif'],
headers: null,
data: null,
filesVariableName(i) {
return `files[${i}]`;
},
withCredentials: false,
pathVariableName: 'path',
format: 'json',
method: 'POST',
prepareData(formData) {
return formData;
},
isSuccess(resp) {
return resp.success;
},
getMessage(resp) {
return resp.data.messages !== undefined && isArray(resp.data.messages)
? resp.data.messages.join(' ')
: '';
},
/**
* @see [[IUploader.processFileName]]
*/
processFileName(key, file, name) {
return [key, file, name];
},
process(resp) {
return resp.data;
},
error(e) {
this.j.message.error(e.message, 4000);
},
getDisplayName(baseurl, filename) {
return baseurl + filename;
},
defaultHandlerSuccess(resp) {
const j = this.j || this;
if (!isJoditObject(j)) {
return;
}
if (resp.files && resp.files.length) {
resp.files.forEach((filename, index) => {
const [tagName, attr] = resp.isImages && resp.isImages[index]
? ['img', 'src']
: ['a', 'href'];
const elm = j.createInside.element(tagName);
elm.setAttribute(attr, resp.baseurl + filename);
if (tagName === 'a') {
elm.textContent = j.o.uploader.getDisplayName.call(this, resp.baseurl, filename);
}
if (tagName === 'img') {
j.s.insertImage(elm, null, j.o.imageDefaultWidth);
}
else {
j.s.insertNode(elm);
}
});
}
},
defaultHandlerError(e) {
this.j.message.error(e.message);
},
contentType(requestData) {
return this.ow.FormData !== undefined &&
typeof requestData !== 'string'
? false
: 'application/x-www-form-urlencoded; charset=UTF-8';
}
};

View File

@@ -0,0 +1,10 @@
/*!
* 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
*/
/**
* @module modules/uploader
*/
import type { BuildDataResult, IDictionary, IUploader } from "../../../types/index";
export declare function buildData(uploader: IUploader, data: FormData | IDictionary<string> | string): BuildDataResult;

View File

@@ -0,0 +1,27 @@
/*!
* 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
*/
import { isFunction, isString } from "../../../core/helpers/index.js";
export function buildData(uploader, data) {
if (isFunction(uploader.o.buildData)) {
return uploader.o.buildData.call(uploader, data);
}
const FD = uploader.ow.FormData;
if (FD !== undefined) {
if (data instanceof FD) {
return data;
}
if (isString(data)) {
return data;
}
const newData = new FD();
const dict = data;
Object.keys(dict).forEach(key => {
newData.append(key, dict[key]);
});
return newData;
}
return data;
}

View File

@@ -0,0 +1,12 @@
/*!
* 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
*/
/**
* @module modules/uploader
*/
/**
* Convert dataURI to Blob
*/
export declare function dataURItoBlob(dataURI: string): Blob;

View File

@@ -0,0 +1,24 @@
/*!
* 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
*/
/**
* @module modules/uploader
*/
/**
* Convert dataURI to Blob
*/
export function dataURItoBlob(dataURI) {
// convert base64 to raw binary data held in a string
// doesn't handle URLEncoded DataURIs - see SO answer #6850276 for code that does this
const byteString = atob(dataURI.split(',')[1]),
// separate out the mime component
mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0],
// write the bytes of the string to an ArrayBuffer
ab = new ArrayBuffer(byteString.length), ia = new Uint8Array(ab);
for (let i = 0; i < byteString.length; i += 1) {
ia[i] = byteString.charCodeAt(i);
}
return new Blob([ia.buffer], { type: mimeString });
}

View File

@@ -0,0 +1,16 @@
/*!
* 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
*/
/**
* @module modules/uploader
*/
import type { Nullable } from "../../../types/index";
export * from "./build-data";
export * from "./data-uri-to-blob";
export * from "./process-old-browser-drag";
export * from "./send";
export * from "./send-files";
export declare function hasFiles(data: Nullable<DataTransfer>): data is DataTransfer;
export declare function hasItems(data: Nullable<DataTransfer>): data is DataTransfer;

View File

@@ -0,0 +1,16 @@
/*!
* 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
*/
export * from "./build-data.js";
export * from "./data-uri-to-blob.js";
export * from "./process-old-browser-drag.js";
export * from "./send.js";
export * from "./send-files.js";
export function hasFiles(data) {
return Boolean(data && data.files && data.files.length > 0);
}
export function hasItems(data) {
return Boolean(data && data.items && data.items.length > 0);
}

View File

@@ -0,0 +1,10 @@
/*!
* 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
*/
/**
* @module modules/uploader
*/
import type { HandlerError, HandlerSuccess, IUploader } from "../../../types/index";
export declare function processOldBrowserDrag(self: IUploader, cData: DataTransfer | null, handlerSuccess?: HandlerSuccess, handlerError?: HandlerError, onFinally?: () => void): void;

View File

@@ -0,0 +1,33 @@
/*!
* 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
*/
import { TEXT_PLAIN } from "../../../core/constants.js";
import { Dom } from "../../../core/dom/index.js";
import { getContainer } from "../../../core/global.js";
import { attr, isJoditObject } from "../../../core/helpers/index.js";
import { dataURItoBlob } from "./data-uri-to-blob.js";
import { sendFiles } from "./send-files.js";
export function processOldBrowserDrag(self, cData, handlerSuccess, handlerError, onFinally) {
if (cData && (!cData.types.length || cData.types[0] !== TEXT_PLAIN)) {
const div = self.j.c.div('', {
tabindex: -1,
style: 'left: -9999px; top: 0; width: 0; height: 100%;line-height: 140%; ' +
'overflow: hidden; position: fixed; z-index: 2147483647; word-break: break-all;',
contenteditable: true
});
getContainer(self.j, self.constructor).appendChild(div);
const selection = isJoditObject(self.j) ? self.j.s.save() : null, restore = () => selection && isJoditObject(self.j) && self.j.s.restore();
div.focus();
self.j.async.setTimeout(() => {
const child = div.firstChild;
Dom.safeRemove(div);
if (child && child.hasAttribute('src')) {
const src = attr(child, 'src') || '';
restore();
sendFiles(self, [dataURItoBlob(src)], handlerSuccess, handlerError).finally(onFinally);
}
}, self.j.defaultTimeout);
}
}

View File

@@ -0,0 +1,13 @@
/*!
* 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
*/
/**
* @module modules/uploader
*/
import type { HandlerError, HandlerSuccess, IUploader } from "../../../types/index";
/**
* Send files to server
*/
export declare function sendFiles(uploader: IUploader, files: FileList | File[] | null, handlerSuccess?: HandlerSuccess, handlerError?: HandlerError, process?: (form: FormData) => void): Promise<any>;

View File

@@ -0,0 +1,112 @@
/*!
* 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
*/
import { error, isFunction, isPlainObject, toArray } from "../../../core/helpers/index.js";
import { send } from "./send.js";
/**
* Send files to server
*/
export function sendFiles(uploader, files, handlerSuccess, handlerError, process) {
if (!files) {
return Promise.reject(error('Need files'));
}
const { o } = uploader;
let fileList = toArray(files);
if (!fileList.length) {
return Promise.reject(error('Need files'));
}
const promises = [];
if (o.insertImageAsBase64URI) {
readImagesWithReader(fileList, o.imagesExtensions, promises, uploader, handlerSuccess, o.defaultHandlerSuccess);
}
fileList = fileList.filter(a => a);
if (fileList.length) {
const form = new FormData();
form.append(o.pathVariableName, uploader.path);
form.append('source', uploader.source);
let file;
for (let i = 0; i < fileList.length; i += 1) {
file = fileList[i];
if (file) {
const hasRealExtension = /\.\w+$/.test(file.name);
const mime = file.type.match(/\/([a-z0-9]+)/i);
const extension = mime && mime[1] ? mime[1].toLowerCase() : '';
let newName = fileList[i].name ||
Math.random().toString().replace('.', '');
if (!hasRealExtension && extension) {
let extForReg = extension;
if (['jpeg', 'jpg'].includes(extForReg)) {
extForReg = 'jpeg|jpg';
}
const reEnd = new RegExp('.(' + extForReg + ')$', 'i');
if (!reEnd.test(newName)) {
newName += '.' + extension;
}
}
const [key, iFile, name] = o.processFileName.call(uploader, o.filesVariableName(i), fileList[i], newName);
form.append(key, iFile, name);
}
}
if (process) {
process(form);
}
if (o.data && isPlainObject(o.data)) {
Object.keys(o.data).forEach((key) => {
form.append(key, o.data[key]);
});
}
o.prepareData.call(uploader, form);
promises.push(send(uploader, form)
.then(resp => {
if (o.isSuccess.call(uploader, resp)) {
const handler = isFunction(handlerSuccess)
? handlerSuccess
: o.defaultHandlerSuccess;
handler.call(uploader, o.process.call(uploader, resp));
return resp;
}
const handler = isFunction(handlerError)
? handlerError
: o.defaultHandlerError;
handler.call(uploader, error(o.getMessage.call(uploader, resp)));
return resp;
})
.then(() => {
uploader.j.events && uploader.j.e.fire('filesWereUploaded');
}));
}
return Promise.all(promises);
}
function readImagesWithReader(fileList, imagesExtensions, promises, uploader, handlerSuccess, defaultHandlerSuccess) {
let file, i;
for (i = 0; i < fileList.length; i += 1) {
file = fileList[i];
if (file && file.type) {
const mime = file.type.match(/\/([a-z0-9]+)/i);
const extension = mime[1] ? mime[1].toLowerCase() : '';
if (!imagesExtensions.includes(extension)) {
continue;
}
const reader = new FileReader();
promises.push(uploader.j.async.promise((resolve, reject) => {
reader.onerror = reject;
reader.onloadend = () => {
const resp = {
baseurl: '',
files: [reader.result],
isImages: [true]
};
const handler = isFunction(handlerSuccess)
? handlerSuccess
: defaultHandlerSuccess;
handler.call(uploader, resp);
resolve(resp);
};
reader.readAsDataURL(file);
}));
fileList[i] = null;
}
}
}

View File

@@ -0,0 +1,12 @@
/*!
* 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
*/
/**
* @module modules/uploader
*/
import type { IDictionary, IUploader, IUploaderAnswer } from "../../../types/index";
import { Ajax } from "../../../core/request/index";
export declare const ajaxInstances: WeakMap<IUploader, Set<Ajax>>;
export declare function send(uploader: IUploader, data: FormData | IDictionary<string>): Promise<IUploaderAnswer>;

View File

@@ -0,0 +1,82 @@
/*!
* 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
*/
import { isFunction, isPromise } from "../../../core/helpers/index.js";
import { Ajax } from "../../../core/request/index.js";
import { buildData } from "./build-data.js";
export const ajaxInstances = new WeakMap();
export function send(uploader, data) {
const requestData = buildData(uploader, data);
const showProgress = (progress) => {
uploader.j.progressbar.show().progress(progress);
if (progress >= 100) {
uploader.j.progressbar.hide();
}
};
let sendData = (request, showProgress) => {
const ajax = new Ajax({
xhr: () => {
const xhr = new XMLHttpRequest();
if (uploader.j.ow.FormData !== undefined &&
xhr.upload) {
showProgress(10);
xhr.upload.addEventListener('progress', evt => {
if (evt.lengthComputable) {
let percentComplete = evt.loaded / evt.total;
percentComplete *= 100;
showProgress(percentComplete);
}
}, false);
}
else {
showProgress(100);
}
return xhr;
},
method: uploader.o.method || 'POST',
data: request,
url: isFunction(uploader.o.url)
? uploader.o.url(request)
: uploader.o.url,
headers: uploader.o.headers,
queryBuild: uploader.o.queryBuild,
contentType: uploader.o.contentType.call(uploader, request),
withCredentials: uploader.o.withCredentials || false
});
let instances = ajaxInstances.get(uploader);
if (!instances) {
instances = new Set();
ajaxInstances.set(uploader, instances);
}
instances.add(ajax);
uploader.j.e.one('beforeDestruct', ajax.destruct);
return ajax
.send()
.then(resp => resp.json())
.catch(error => {
return {
success: false,
data: {
messages: [error]
}
};
})
.finally(() => {
ajax.destruct();
instances === null || instances === void 0 ? void 0 : instances.delete(ajax);
});
};
if (isFunction(uploader.o.customUploadFunction)) {
sendData = uploader.o.customUploadFunction;
}
if (isPromise(requestData)) {
return requestData
.then(data => sendData(data, showProgress))
.catch(error => {
uploader.o.error.call(uploader, error);
});
}
return sendData(requestData, showProgress);
}

Some files were not shown because too many files have changed in this diff Show More