UNPKG

@edsdk/flmngr-ckeditor5

Version:

File manager plugin for CKEditor 5

263 lines (222 loc) 7.2 kB
import { Command, findAttributeRange, first } from 'ckeditor5'; import {showWarning} from "./utils"; export default class FlmngrCommand extends Command { static flmngr; imageExtensions = ['jpeg', 'jpg', 'png', 'bmp', 'svg', 'webp', 'gif']; constructor( editor ) { super( editor ); // 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' } ); } refresh() { const imageCommand = this.editor.commands.get( 'insertImage' ); const linkCommand = this.editor.commands.get( 'link' ); this.isEnabled = !imageCommand || // if there is no image command, the button IS always enabled: we will show a message (imageCommand.isEnabled || (!!linkCommand && linkCommand.isEnabled)); } isImage(filepath) { let i = filepath.lastIndexOf("."); if (i > -1 && i < filepath.length-1) { let ext = filepath.substr(i + 1).toLowerCase(); return ext === 'jpeg' || ext === 'jpg' || ext === 'png' || ext === 'gif' || ext === "bmp" || ext === "svg" || ext === "webp"; } return false; } // Call a dialog to select local file and upload them ("Upload" action) executeUpload() { this.execute2(true, null); } getSelectedElements() { const selection = this.editor.model.document.selection; const el = selection.getSelectedElement() || first( selection.getSelectedBlocks() ); let currentUrl = null; let elA = null; const position = selection.getFirstPosition(); if ( selection.hasAttribute( 'linkHref' ) ) { elA = findAttributeRange(position, 'linkHref', selection.getAttribute('linkHref'), this.editor.model).getItems().next().value.textNode; currentUrl = elA.getAttribute("linkHref"); } let elImg = null; if (!!el && (el.name === 'imageBlock' || el.name === 'imageInline')) { elImg = el; currentUrl = elImg.getAttribute("src"); elA = null; } return { el: el, elImg: elImg, elA: elA, currentUrl: currentUrl } } // Call Flmngr ("Browse" action) execute() { this.execute2(false, (urls) => { if (urls !== null) { let selectedElements = this.getSelectedElements(); this.createOrChange( selectedElements.el, selectedElements.elImg, selectedElements.elA, urls ); } }); } execute2( doUpload, // false = browse callback // will insert files instead if callback === null ) { let selectedElements = this.getSelectedElements(); const imageCommand = this.editor.commands.get( 'insertImage' ); if (!imageCommand) { let msg = "Please enable CKEditor 5 `Image` plugin in order to use Flmngr file manager"; if (!!window.Drupal) msg += ":\n\nDrupal users must set `Image Upload` -> `Enable image uploads` checkbox on the page of CKEditor 5 text format"; alert(msg); callback(null); return; } if (!FlmngrCommand.flmngr) { console.log("File manager is not loaded yet"); callback(null); return; } if (doUpload) { FlmngrCommand.flmngr.selectFiles({ acceptExtensions: !!selectedElements.elImg ? this.imageExtensions : null, isMultiple: false, onFinish: (files) => { FlmngrCommand.flmngr.upload({ filesOrLinks: files, onFinish: (urls, paths) => { callback(urls); }, onFail: (error) => { showWarning(this.editor, 'Unable to upload files', true, error, false); callback(null); }, onCancel: () => { callback(null); } }); } }) } else { FlmngrCommand.flmngr.pickFiles({ acceptExtensions: !!selectedElements.elImg ? this.imageExtensions : null, isMultiple: false, list: selectedElements.currentUrl ? [selectedElements.currentUrl] : null, onFinish: (files) => { let urls = files.map(f => f.url); callback(urls) }, onCancel: () => { callback(null); } }); } } isPlainTextSelection(selection) { const ranges = selection.getRanges(); for (const range of ranges) { for (const item of range.getItems()) { if (item.is('textProxy')) continue; return false; } } return true; } createOrChange(el, elImg, elA, urls) { // If non-image file is selected, and selection is a plain text, just convert it to a link if (!this.isImage(urls[0])) { const selection = this.editor.model.document.selection; if (!selection.isCollapsed && this.isPlainTextSelection(selection)) { editor.model.change(writer => { this.editor.commands.get('link').execute(urls[0]); }); return; } } if (!!elImg) { this.changeImgSrc(elImg, FlmngrCommand.flmngr.getNoCacheUrl(urls[0])); } else if (!!elA) { this.changeAHref(elA, urls[0]); } else { // Create new IMG and A elements let urlsImages = []; let urlsFiles = []; for (let url of urls) { if (this.isImage(url)) urlsImages.push(FlmngrCommand.flmngr.getNoCacheUrl(url)); else urlsFiles.push(url); } for (const url of urlsFiles) this.createNewA(url); for (const url of urlsImages) this.createNewImg(url); } } createNewImg(url) { this.editor.model.change( writer => { const imageCommand = this.editor.commands.get( 'insertImage' ); // Check if inserting an image is actually possible - it might be possible to only insert a link. if ( !imageCommand.isEnabled ) { showWarning(this.editor, 'Inserting image failed', true, 'Could not insert image at the current position.', true); return; } this.editor.execute( 'insertImage', { source: [url] } ); } ); }; createNewA(url) { this.editor.model.change( writer => { const insertPosition = this.editor.model.document.selection.getFirstPosition(); const i = url.lastIndexOf("/"); const filename = url.substr(i + 1); const title = "Download " + filename; writer.insertText( title, { linkHref: url }, insertPosition ); } ); }; changeImgSrc(el, url) { this.editor.model.change( writer => { writer.setAttribute("src", url, el); writer.removeAttribute("srcset", el); writer.removeAttribute("sizes", el); /* In Drupal when using existing content with existing image, CKEditor 5 sets also a custom HTML attribute "src", so to change an image URL we need not only to set an attribute "src" of a model of CKEditor 5, but also and "src" attribute of HTML. Probably this is a bug or a misconfiguration of CKEditor 5 in Drupal 9/10. */ let attr = el.getAttribute("htmlAttributes"); if (attr) { delete attr.attributes.src; delete attr.attributes.srcset; delete attr.attributes.sizes; writer.setAttribute("htmlAttributes", attr, el); } }); }; changeAHref(el, url) { this.editor.model.change( writer => { writer.setAttribute( 'linkHref', url, el ); // TODO: probably change text }); }; isImage(filepath) { let i = filepath.lastIndexOf("."); if (i > -1 && i < filepath.length-1) { let ext = filepath.substr(i + 1).toLowerCase(); if (this.imageExtensions.indexOf(ext) > -1) return true; } return false; } }