|
|
@@ -15,23 +15,30 @@ import Template from '@ckeditor/ckeditor5-ui/src/template';
|
|
|
|
|
|
import {
|
|
|
isTypeAroundWidget,
|
|
|
- getWidgetTypeAroundDirections,
|
|
|
+ getWidgetTypeAroundPositions,
|
|
|
getClosestTypeAroundDomButton,
|
|
|
- getTypeAroundButtonDirection,
|
|
|
- getClosestWidgetViewElement,
|
|
|
- directionToWidgetCssClass
|
|
|
+ getTypeAroundButtonPosition,
|
|
|
+ getClosestWidgetViewElement
|
|
|
} from './utils';
|
|
|
|
|
|
import returnIcon from '../../theme/icons/return-arrow.svg';
|
|
|
import '../../theme/widgettypearound.css';
|
|
|
|
|
|
-const POSSIBLE_INSERTION_DIRECTIONS = [ 'before', 'after' ];
|
|
|
+const POSSIBLE_INSERTION_POSITIONS = [ 'before', 'after' ];
|
|
|
let CACHED_RETURN_ARROW_ICON;
|
|
|
|
|
|
/**
|
|
|
- * TODO
|
|
|
+ * A plugin which allows users to type around widgets where normally it is impossible to place the caret due
|
|
|
+ * to limitations of web browsers. These "tight spots" occur, for instance, before (or after) a widget being
|
|
|
+ * the first (or last) child of its parent or between two block widgets.
|
|
|
+ *
|
|
|
+ * This plugin extends the {@link module:widget/widget~Widget `Widget`} plugin and inject a user interface
|
|
|
+ * with two buttons into each widget instance in the editor. Each of the buttons can be clicked by the
|
|
|
+ * user if the widget is next to the "tight spot". Once clicked, a paragraph is created with the selection anchored
|
|
|
+ * in it so that users can type (or insert content, paste, etc.) straight away.
|
|
|
*
|
|
|
* @extends module:core/plugin~Plugin
|
|
|
+ * @private
|
|
|
*/
|
|
|
export default class WidgetTypeAround extends Plugin {
|
|
|
/**
|
|
|
@@ -49,7 +56,7 @@ export default class WidgetTypeAround extends Plugin {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * TODO
|
|
|
+ * @inheritDoc
|
|
|
*/
|
|
|
init() {
|
|
|
this._enableTypeAroundUIInjection();
|
|
|
@@ -57,12 +64,21 @@ export default class WidgetTypeAround extends Plugin {
|
|
|
this._enableInsertingParagraphsOnButtonClick();
|
|
|
}
|
|
|
|
|
|
- typeAround( widgetViewElement, direction ) {
|
|
|
+ /**
|
|
|
+ * Inserts a new paragraph next to a widget element with the selection anchored in it.
|
|
|
+ *
|
|
|
+ * **Note**: This method is heavily user-oriented and will both focus the editing view and scroll
|
|
|
+ * the viewport to the selection in the inserted paragraph.
|
|
|
+ *
|
|
|
+ * @param {module:engine/view/element~Element} widgetViewElement The view widget element next to which a paragraph is inserted.
|
|
|
+ * @param {String} position The position where the paragraph is inserted. Either `'before'` or `'after'` the widget.
|
|
|
+ */
|
|
|
+ typeAround( widgetViewElement, position ) {
|
|
|
const editor = this.editor;
|
|
|
const editingView = editor.editing.view;
|
|
|
let viewPosition;
|
|
|
|
|
|
- if ( direction === 'before' ) {
|
|
|
+ if ( position === 'before' ) {
|
|
|
viewPosition = editingView.createPositionBefore( widgetViewElement );
|
|
|
} else {
|
|
|
viewPosition = editingView.createPositionAfter( widgetViewElement );
|
|
|
@@ -82,13 +98,19 @@ export default class WidgetTypeAround extends Plugin {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * TODO
|
|
|
+ * Creates a listener in the editing conversion pipeline that injects the type around
|
|
|
+ * UI into every single widget instance created in the editor.
|
|
|
+ *
|
|
|
+ * The UI is delivered as a {@link module:engine/view/uielement~UIElement}
|
|
|
+ * wrapper which renders DOM buttons that users can use to insert paragraphs.
|
|
|
+ *
|
|
|
+ * @private
|
|
|
*/
|
|
|
_enableTypeAroundUIInjection() {
|
|
|
const editor = this.editor;
|
|
|
const schema = editor.model.schema;
|
|
|
const t = editor.locale.t;
|
|
|
- const labels = {
|
|
|
+ const buttonTitles = {
|
|
|
before: t( 'Insert paragraph before widget' ),
|
|
|
after: t( 'Insert paragraph after widget' )
|
|
|
};
|
|
|
@@ -98,19 +120,27 @@ export default class WidgetTypeAround extends Plugin {
|
|
|
|
|
|
// Filter out non-widgets and inline widgets.
|
|
|
if ( isTypeAroundWidget( viewElement, data.item, schema ) ) {
|
|
|
- injectUIIntoWidget( editor.editing.view, labels, viewElement );
|
|
|
+ injectUIIntoWidget( editor.editing.view, buttonTitles, viewElement );
|
|
|
}
|
|
|
}, { priority: 'low' } );
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * TODO
|
|
|
+ * Registers an editing view post-fixer which checks all block widgets in the content
|
|
|
+ * and adds CSS classes to these which should have the typing around (UI) enabled
|
|
|
+ * and visible for the users.
|
|
|
+ *
|
|
|
+ * @private
|
|
|
*/
|
|
|
_enableDetectionOfTypeAroundWidgets() {
|
|
|
const editor = this.editor;
|
|
|
const schema = editor.model.schema;
|
|
|
const editingView = editor.editing.view;
|
|
|
|
|
|
+ function positionToWidgetCssClass( position ) {
|
|
|
+ return `ck-widget_can-type-around_${ position }`;
|
|
|
+ }
|
|
|
+
|
|
|
editingView.document.registerPostFixer( writer => {
|
|
|
// Find all view elements in the editing root.
|
|
|
[ ...editingView.createRangeIn( editingView.document.getRoot() ) ]
|
|
|
@@ -120,22 +150,26 @@ export default class WidgetTypeAround extends Plugin {
|
|
|
|
|
|
return isTypeAroundWidget( widgetViewElement, modelElement, schema );
|
|
|
} )
|
|
|
- // ...and update widgets' classes depending on possible directions for paragraph insertion.
|
|
|
+ // ...and update widgets' classes depending on possible positions for paragraph insertion.
|
|
|
.forEach( ( { item: widgetViewElement } ) => {
|
|
|
- const directions = getWidgetTypeAroundDirections( widgetViewElement );
|
|
|
+ const positions = getWidgetTypeAroundPositions( widgetViewElement );
|
|
|
|
|
|
// Remove all classes. In theory we could remove only these that will not be added a few lines later,
|
|
|
// but since there are only two... KISS.
|
|
|
- writer.removeClass( POSSIBLE_INSERTION_DIRECTIONS.map( directionToWidgetCssClass ), widgetViewElement );
|
|
|
+ writer.removeClass( POSSIBLE_INSERTION_POSITIONS.map( positionToWidgetCssClass ), widgetViewElement );
|
|
|
|
|
|
- // Set CSS classes related to possible directions. They are used so the UI knows which buttons to display.
|
|
|
- writer.addClass( directions.map( directionToWidgetCssClass ), widgetViewElement );
|
|
|
+ // Set CSS classes related to possible positions. They are used so the UI knows which buttons to display.
|
|
|
+ writer.addClass( positions.map( positionToWidgetCssClass ), widgetViewElement );
|
|
|
} );
|
|
|
} );
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * TODO
|
|
|
+ * Registers a `mousedown` listener for the view document which intercepts events
|
|
|
+ * coming from the type around UI, which happens when a user clicks one of the buttons
|
|
|
+ * that insert a paragraph next to a widget.
|
|
|
+ *
|
|
|
+ * @private
|
|
|
*/
|
|
|
_enableInsertingParagraphsOnButtonClick() {
|
|
|
const editor = this.editor;
|
|
|
@@ -148,10 +182,10 @@ export default class WidgetTypeAround extends Plugin {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- const buttonDirection = getTypeAroundButtonDirection( button );
|
|
|
+ const buttonPosition = getTypeAroundButtonPosition( button );
|
|
|
const widgetViewElement = getClosestWidgetViewElement( button, editingView.domConverter );
|
|
|
|
|
|
- this.typeAround( widgetViewElement, buttonDirection );
|
|
|
+ this.typeAround( widgetViewElement, buttonPosition );
|
|
|
|
|
|
domEventData.preventDefault();
|
|
|
evt.stop();
|
|
|
@@ -159,14 +193,19 @@ export default class WidgetTypeAround extends Plugin {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-function injectUIIntoWidget( editingView, labels, widgetViewElement ) {
|
|
|
+// Injects the type around UI into a view widget instance.
|
|
|
+//
|
|
|
+// @param {module:engine/view/element~Element}
|
|
|
+// @param {module:engine/view/view} editingView
|
|
|
+// @param {Object.<String,String>} buttonTitles
|
|
|
+function injectUIIntoWidget( editingView, buttonTitles, widgetViewElement ) {
|
|
|
editingView.change( writer => {
|
|
|
const typeAroundWrapper = writer.createUIElement( 'div', {
|
|
|
class: 'ck ck-reset_all ck-widget__type-around'
|
|
|
}, function( domDocument ) {
|
|
|
const wrapperDomElement = this.toDomElement( domDocument );
|
|
|
|
|
|
- injectButtons( wrapperDomElement, labels );
|
|
|
+ injectButtons( wrapperDomElement, buttonTitles );
|
|
|
|
|
|
return wrapperDomElement;
|
|
|
} );
|
|
|
@@ -179,23 +218,26 @@ function injectUIIntoWidget( editingView, labels, widgetViewElement ) {
|
|
|
// FYI: Not using the IconView class because each instance would need to be destroyed to avoid memory leaks
|
|
|
// and it's pretty hard to figure out when a view (widget) is gone for good so it's cheaper to use raw
|
|
|
// <svg> here.
|
|
|
-function injectButtons( wrapperDomElement, labels ) {
|
|
|
+//
|
|
|
+// @param {HTMLElement} wrapperDomElement
|
|
|
+// @param {Object.<String,String>} buttonTitles
|
|
|
+function injectButtons( wrapperDomElement, buttonTitles ) {
|
|
|
// Do the SVG parsing once and then clone the result <svg> DOM element for each new
|
|
|
// button. There could be dozens of them during editor's lifetime.
|
|
|
if ( !CACHED_RETURN_ARROW_ICON ) {
|
|
|
CACHED_RETURN_ARROW_ICON = new DOMParser().parseFromString( returnIcon, 'image/svg+xml' ).firstChild;
|
|
|
}
|
|
|
|
|
|
- for ( const direction of POSSIBLE_INSERTION_DIRECTIONS ) {
|
|
|
+ for ( const position of POSSIBLE_INSERTION_POSITIONS ) {
|
|
|
const buttonTemplate = new Template( {
|
|
|
tag: 'div',
|
|
|
attributes: {
|
|
|
class: [
|
|
|
'ck',
|
|
|
'ck-widget__type-around__button',
|
|
|
- `ck-widget__type-around__button_${ direction }`
|
|
|
+ `ck-widget__type-around__button_${ position }`
|
|
|
],
|
|
|
- title: labels[ direction ]
|
|
|
+ title: buttonTitles[ position ]
|
|
|
},
|
|
|
children: [
|
|
|
wrapperDomElement.ownerDocument.importNode( CACHED_RETURN_ARROW_ICON, true )
|