UNPKG

ckeditor5-image-upload-base64

Version:

The development environment of CKEditor 5 – the best browser-based rich text editor.

253 lines (213 loc) 9.35 kB
/** * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license */ /** * @module engine/view/placeholder */ import '../../theme/placeholder.css'; // Each document stores information about its placeholder elements and check functions. const documentPlaceholders = new WeakMap(); /** * A helper that enables a placeholder on the provided view element (also updates its visibility). * The placeholder is a CSS pseudo–element (with a text content) attached to the element. * * To change the placeholder text, simply call this method again with new options. * * To disable the placeholder, use {@link module:engine/view/placeholder~disablePlaceholder `disablePlaceholder()`} helper. * * @param {Object} [options] Configuration options of the placeholder. * @param {module:engine/view/view~View} options.view Editing view instance. * @param {module:engine/view/element~Element} options.element Element that will gain a placeholder. * See `options.isDirectHost` to learn more. * @param {String} options.text Placeholder text. * @param {Boolean} [options.isDirectHost=true] If set `false`, the placeholder will not be enabled directly * in the passed `element` but in one of its children (selected automatically, i.e. a first empty child element). * Useful when attaching placeholders to elements that can host other elements (not just text), for instance, * editable root elements. */ export function enablePlaceholder( options ) { const { view, element, text, isDirectHost = true } = options; const doc = view.document; // Use a single a single post fixer per—document to update all placeholders. if ( !documentPlaceholders.has( doc ) ) { documentPlaceholders.set( doc, new Map() ); // If a post-fixer callback makes a change, it should return `true` so other post–fixers // can re–evaluate the document again. doc.registerPostFixer( writer => updateDocumentPlaceholders( doc, writer ) ); } // Store information about the element placeholder under its document. documentPlaceholders.get( doc ).set( element, { text, isDirectHost } ); // Update the placeholders right away. view.change( writer => updateDocumentPlaceholders( doc, writer ) ); } /** * Disables the placeholder functionality from a given element. * * See {@link module:engine/view/placeholder~enablePlaceholder `enablePlaceholder()`} to learn more. * * @param {module:engine/view/view~View} view * @param {module:engine/view/element~Element} element */ export function disablePlaceholder( view, element ) { const doc = element.document; view.change( writer => { if ( !documentPlaceholders.has( doc ) ) { return; } const placeholders = documentPlaceholders.get( doc ); const config = placeholders.get( element ); writer.removeAttribute( 'data-placeholder', config.hostElement ); hidePlaceholder( writer, config.hostElement ); placeholders.delete( element ); } ); } /** * Shows a placeholder in the provided element by changing related attributes and CSS classes. * * **Note**: This helper will not update the placeholder visibility nor manage the * it in any way in the future. What it does is a one–time state change of an element. Use * {@link module:engine/view/placeholder~enablePlaceholder `enablePlaceholder()`} and * {@link module:engine/view/placeholder~disablePlaceholder `disablePlaceholder()`} for full * placeholder functionality. * * **Note**: This helper will blindly show the placeholder directly in the root editable element if * one is passed, which could result in a visual clash if the editable element has some children * (for instance, an empty paragraph). Use {@link module:engine/view/placeholder~enablePlaceholder `enablePlaceholder()`} * in that case or make sure the correct element is passed to the helper. * * @param {module:engine/view/downcastwriter~DowncastWriter} writer * @param {module:engine/view/element~Element} element * @returns {Boolean} `true`, if any changes were made to the `element`. */ export function showPlaceholder( writer, element ) { if ( !element.hasClass( 'ck-placeholder' ) ) { writer.addClass( 'ck-placeholder', element ); return true; } return false; } /** * Hides a placeholder in the element by changing related attributes and CSS classes. * * **Note**: This helper will not update the placeholder visibility nor manage the * it in any way in the future. What it does is a one–time state change of an element. Use * {@link module:engine/view/placeholder~enablePlaceholder `enablePlaceholder()`} and * {@link module:engine/view/placeholder~disablePlaceholder `disablePlaceholder()`} for full * placeholder functionality. * * @param {module:engine/view/downcastwriter~DowncastWriter} writer * @param {module:engine/view/element~Element} element * @returns {Boolean} `true`, if any changes were made to the `element`. */ export function hidePlaceholder( writer, element ) { if ( element.hasClass( 'ck-placeholder' ) ) { writer.removeClass( 'ck-placeholder', element ); return true; } return false; } /** * Checks if a placeholder should be displayed in the element. * * **Note**: This helper will blindly check the possibility of showing a placeholder directly in the * root editable element if one is passed, which may not be the expected result. If an element can * host other elements (not just text), most likely one of its children should be checked instead * because it will be the final host for the placeholder. Use * {@link module:engine/view/placeholder~enablePlaceholder `enablePlaceholder()`} in that case or make * sure the correct element is passed to the helper. * * @param {module:engine/view/element~Element} element * @returns {Boolean} */ export function needsPlaceholder( element ) { if ( !element.isAttached() ) { return false; } // The element is empty only as long as it contains nothing but uiElements. const isEmptyish = !Array.from( element.getChildren() ) .some( element => !element.is( 'uiElement' ) ); const doc = element.document; // If the element is empty and the document is blurred. if ( !doc.isFocused && isEmptyish ) { return true; } const viewSelection = doc.selection; const selectionAnchor = viewSelection.anchor; // If document is focused and the element is empty but the selection is not anchored inside it. if ( isEmptyish && selectionAnchor && selectionAnchor.parent !== element ) { return true; } return false; } // Updates all placeholders associated with a document in a post–fixer callback. // // @private // @param { module:engine/view/document~Document} doc // @param {module:engine/view/downcastwriter~DowncastWriter} writer // @returns {Boolean} True if any changes were made to the view document. function updateDocumentPlaceholders( doc, writer ) { const placeholders = documentPlaceholders.get( doc ); let wasViewModified = false; for ( const [ element, config ] of placeholders ) { if ( updatePlaceholder( writer, element, config ) ) { wasViewModified = true; } } return wasViewModified; } // Updates a single placeholder in a post–fixer callback. // // @private // @param {module:engine/view/downcastwriter~DowncastWriter} writer // @param {module:engine/view/element~Element} element // @param {Object} config Configuration of the placeholder // @param {String} config.text // @param {Boolean} config.isDirectHost // @returns {Boolean} True if any changes were made to the view document. function updatePlaceholder( writer, element, config ) { const { text, isDirectHost } = config; const hostElement = isDirectHost ? element : getChildPlaceholderHostSubstitute( element ); let wasViewModified = false; // When not a direct host, it could happen that there is no child element // capable of displaying a placeholder. if ( !hostElement ) { return false; } // Cache the host element. It will be necessary for disablePlaceholder() to know // which element should have class and attribute removed because, depending on // the config.isDirectHost value, it could be the element or one of its descendants. config.hostElement = hostElement; // This may be necessary when updating the placeholder text to something else. if ( hostElement.getAttribute( 'data-placeholder' ) !== text ) { writer.setAttribute( 'data-placeholder', text, hostElement ); wasViewModified = true; } if ( needsPlaceholder( hostElement ) ) { if ( showPlaceholder( writer, hostElement ) ) { wasViewModified = true; } } else if ( hidePlaceholder( writer, hostElement ) ) { wasViewModified = true; } return wasViewModified; } // Gets a child element capable of displaying a placeholder if a parent element can host more // than just text (for instance, when it is a root editable element). The child element // can then be used in other placeholder helpers as a substitute of its parent. // // @private // @param {module:engine/view/element~Element} parent // @returns {module:engine/view/element~Element|null} function getChildPlaceholderHostSubstitute( parent ) { if ( parent.childCount === 1 ) { const firstChild = parent.getChild( 0 ); if ( firstChild.is( 'element' ) && !firstChild.is( 'uiElement' ) ) { return firstChild; } } return null; }