@ckeditor/ckeditor5-ckfinder
Version:
CKFinder integration for CKEditor 5.
325 lines (319 loc) • 12.6 kB
JavaScript
/**
* @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
import { Plugin, Command } from '@ckeditor/ckeditor5-core/dist/index.js';
import { ButtonView, MenuBarMenuListItemButtonView, Notification } from '@ckeditor/ckeditor5-ui/dist/index.js';
import { IconBrowseFiles, IconImageAssetManager } from '@ckeditor/ckeditor5-icons/dist/index.js';
import { CKEditorError } from '@ckeditor/ckeditor5-utils/dist/index.js';
/**
* Introduces UI components for `CKFinder` plugin.
*
* The plugin introduces two UI components to the {@link module:ui/componentfactory~ComponentFactory UI component factory}:
*
* * the `'ckfinder'` toolbar button,
* * the `'menuBar:ckfinder'` menu bar component, which is by default added to the `'Insert'` menu.
*
* It also integrates with the `insertImage` toolbar component and `menuBar:insertImage` menu component.
*/ class CKFinderUI extends Plugin {
/**
* @inheritDoc
*/ static get pluginName() {
return 'CKFinderUI';
}
/**
* @inheritDoc
*/ static get isOfficialPlugin() {
return true;
}
/**
* @inheritDoc
*/ init() {
const editor = this.editor;
editor.ui.componentFactory.add('ckfinder', ()=>this._createFileToolbarButton());
editor.ui.componentFactory.add('menuBar:ckfinder', ()=>this._createFileMenuBarButton());
if (editor.plugins.has('ImageInsertUI')) {
editor.plugins.get('ImageInsertUI').registerIntegration({
name: 'assetManager',
observable: ()=>editor.commands.get('ckfinder'),
buttonViewCreator: ()=>this._createImageToolbarButton(),
formViewCreator: ()=>this._createImageDropdownButton(),
menuBarButtonViewCreator: (isOnly)=>this._createImageMenuBarButton(isOnly ? 'insertOnly' : 'insertNested')
});
}
}
/**
* Creates the base for various kinds of the button component provided by this feature.
*/ _createButton(ButtonClass) {
const editor = this.editor;
const locale = editor.locale;
const view = new ButtonClass(locale);
const command = editor.commands.get('ckfinder');
view.bind('isEnabled').to(command);
view.on('execute', ()=>{
editor.execute('ckfinder');
editor.editing.view.focus();
});
return view;
}
/**
* Creates a simple toolbar button for files management, with an icon and a tooltip.
*/ _createFileToolbarButton() {
const t = this.editor.locale.t;
const button = this._createButton(ButtonView);
button.icon = IconBrowseFiles;
button.label = t('Insert image or file');
button.tooltip = true;
return button;
}
/**
* Creates a simple toolbar button for images management, with an icon and a tooltip.
*/ _createImageToolbarButton() {
const t = this.editor.locale.t;
const imageInsertUI = this.editor.plugins.get('ImageInsertUI');
const button = this._createButton(ButtonView);
button.icon = IconImageAssetManager;
button.bind('label').to(imageInsertUI, 'isImageSelected', (isImageSelected)=>isImageSelected ? t('Replace image with file manager') : t('Insert image with file manager'));
button.tooltip = true;
return button;
}
/**
* Creates a button for images management for the dropdown view, with an icon, text and no tooltip.
*/ _createImageDropdownButton() {
const t = this.editor.locale.t;
const imageInsertUI = this.editor.plugins.get('ImageInsertUI');
const button = this._createButton(ButtonView);
button.icon = IconImageAssetManager;
button.withText = true;
button.bind('label').to(imageInsertUI, 'isImageSelected', (isImageSelected)=>isImageSelected ? t('Replace with file manager') : t('Insert with file manager'));
button.on('execute', ()=>{
imageInsertUI.dropdownView.isOpen = false;
});
return button;
}
/**
* Creates a button for files management for the menu bar.
*/ _createFileMenuBarButton() {
const t = this.editor.locale.t;
const button = this._createButton(MenuBarMenuListItemButtonView);
button.icon = IconBrowseFiles;
button.withText = true;
button.label = t('File');
return button;
}
/**
* Creates a button for images management for the menu bar.
*/ _createImageMenuBarButton(type) {
// Use t() stored in a variable with a different name to reuse existing translations from another package.
const translateVariableKey = this.editor.locale.t;
const t = this.editor.locale.t;
const button = this._createButton(MenuBarMenuListItemButtonView);
button.icon = IconImageAssetManager;
button.withText = true;
switch(type){
case 'insertOnly':
button.label = translateVariableKey('Image');
break;
case 'insertNested':
button.label = t('With file manager');
break;
}
return button;
}
}
/**
* The CKFinder command. It is used by the {@link module:ckfinder/ckfinderediting~CKFinderEditing CKFinder editing feature}
* to open the CKFinder file manager to insert an image or a link to a file into the editor content.
*
* ```ts
* editor.execute( 'ckfinder' );
* ```
*
* **Note:** This command uses other features to perform tasks:
* - To insert images the {@link module:image/image/insertimagecommand~InsertImageCommand 'insertImage'} command
* from the {@link module:image/image~Image Image feature}.
* - To insert links to files the {@link module:link/linkcommand~LinkCommand 'link'} command
* from the {@link module:link/link~Link Link feature}.
*/ class CKFinderCommand extends Command {
/**
* @inheritDoc
*/ constructor(editor){
super(editor);
// The CKFinder command does not affect data by itself.
this.affectsData = false;
// Remove default document listener to lower its priority.
this.stopListening(this.editor.model.document, 'change');
// Lower this command listener priority to be sure that refresh() will be called after link & image refresh.
this.listenTo(this.editor.model.document, 'change', ()=>this.refresh(), {
priority: 'low'
});
}
/**
* @inheritDoc
*/ refresh() {
const imageCommand = this.editor.commands.get('insertImage');
const linkCommand = this.editor.commands.get('link');
// The CKFinder command is enabled when one of image or link command is enabled.
this.isEnabled = imageCommand.isEnabled || linkCommand.isEnabled;
}
/**
* @inheritDoc
*/ execute() {
const editor = this.editor;
const openerMethod = this.editor.config.get('ckfinder.openerMethod') || 'modal';
if (openerMethod != 'popup' && openerMethod != 'modal') {
/**
* The `ckfinder.openerMethod` must be one of: "popup" or "modal".
*
* @error ckfinder-unknown-openermethod
*/ throw new CKEditorError('ckfinder-unknown-openermethod', editor);
}
const options = this.editor.config.get('ckfinder.options') || {};
options.chooseFiles = true;
// Cache the user-defined onInit method
const originalOnInit = options.onInit;
// Pass the lang code to the CKFinder if not defined by user.
if (!options.language) {
options.language = editor.locale.uiLanguage;
}
// The onInit method allows to extend CKFinder's behavior. It is used to attach event listeners to file choosing related events.
options.onInit = (finder)=>{
// Call original options.onInit if it was defined by user.
if (originalOnInit) {
originalOnInit(finder);
}
finder.on('files:choose', (evt)=>{
const files = evt.data.files.toArray();
// Insert links
const links = files.filter((file)=>!file.isImage());
const images = files.filter((file)=>file.isImage());
for (const linkFile of links){
editor.execute('link', linkFile.getUrl());
}
const imagesUrls = [];
for (const image of images){
const url = image.getUrl();
imagesUrls.push(url ? url : finder.request('file:getProxyUrl', {
file: image
}));
}
if (imagesUrls.length) {
insertImages(editor, imagesUrls);
}
});
finder.on('file:choose:resizedImage', (evt)=>{
const resizedUrl = evt.data.resizedUrl;
if (!resizedUrl) {
const notification = editor.plugins.get('Notification');
const t = editor.locale.t;
notification.showWarning(t('Could not obtain resized image URL.'), {
title: t('Selecting resized image failed'),
namespace: 'ckfinder'
});
return;
}
insertImages(editor, [
resizedUrl
]);
});
};
window.CKFinder[openerMethod](options);
}
}
function insertImages(editor, urls) {
const imageCommand = editor.commands.get('insertImage');
// Check if inserting an image is actually possible - it might be possible to only insert a link.
if (!imageCommand.isEnabled) {
const notification = editor.plugins.get('Notification');
const t = editor.locale.t;
notification.showWarning(t('Could not insert image at the current position.'), {
title: t('Inserting image failed'),
namespace: 'ckfinder'
});
return;
}
editor.execute('insertImage', {
source: urls
});
}
/**
* The CKFinder editing feature. It introduces the {@link module:ckfinder/ckfindercommand~CKFinderCommand CKFinder command}.
*/ class CKFinderEditing extends Plugin {
/**
* @inheritDoc
*/ static get pluginName() {
return 'CKFinderEditing';
}
/**
* @inheritDoc
*/ static get isOfficialPlugin() {
return true;
}
/**
* @inheritDoc
*/ static get requires() {
return [
Notification,
'LinkEditing'
];
}
/**
* @inheritDoc
*/ init() {
const editor = this.editor;
if (!editor.plugins.has('ImageBlockEditing') && !editor.plugins.has('ImageInlineEditing')) {
/**
* CKFinder requires at least one plugin providing support for images loaded in the editor. Please
* make sure either:
*
* * {@link module:image/image~Image} (which loads both types of images),
* * or {@link module:image/imageblock~ImageBlock},
* * or {@link module:image/imageinline~ImageInline}.
*
* is loaded in your editor configuration.
*
* @error ckfinder-missing-image-plugin
*/ throw new CKEditorError('ckfinder-missing-image-plugin', editor);
}
editor.commands.add('ckfinder', new CKFinderCommand(editor));
}
}
/**
* The CKFinder feature, a bridge between the CKEditor 5 WYSIWYG editor and the
* [CKFinder](https://ckeditor.com/ckfinder) file manager and uploader.
*
* This is a "glue" plugin which enables:
*
* * {@link module:ckfinder/ckfinderediting~CKFinderEditing},
* * {@link module:ckfinder/ckfinderui~CKFinderUI},
* * {@link module:adapter-ckfinder/uploadadapter~CKFinderUploadAdapter}.
*
* See the {@glink features/file-management/ckfinder "CKFinder integration" guide} to learn how to configure
* and use this feature.
*
* Check out the {@glink features/images/image-upload/image-upload comprehensive "Image upload" guide} to learn about
* other ways to upload images into CKEditor 5.
*/ class CKFinder extends Plugin {
/**
* @inheritDoc
*/ static get pluginName() {
return 'CKFinder';
}
/**
* @inheritDoc
*/ static get isOfficialPlugin() {
return true;
}
/**
* @inheritDoc
*/ static get requires() {
return [
'Link',
'CKFinderUploadAdapter',
CKFinderEditing,
CKFinderUI
];
}
}
export { CKFinder, CKFinderEditing, CKFinderUI };
//# sourceMappingURL=index.js.map