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,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';
}
}