Browse Source

Add demo for inline widget tutorial.

Mateusz Samsel 6 years ago
parent
commit
b024ede888

+ 17 - 0
docs/_snippets/framework/tutorials/inline-widget.html

@@ -0,0 +1,17 @@
+<style>
+	.placeholder {
+		background: #ffff00;
+		padding: 4px 2px;
+		outline-offset: -2px;
+		line-height: 1em;
+		margin: 0 1px;
+	}
+
+	.placeholder::selection {
+		display: none;
+	}
+</style>
+
+<div id="snippet-inline-widget">
+	<p>Hello <span class="placeholder">{first name}</span> <span class="placeholder">{surname}</span>!</p>
+</div>

+ 213 - 0
docs/_snippets/framework/tutorials/inline-widget.js

@@ -0,0 +1,213 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+/* globals console, window, document */
+
+import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
+import Essentials from '@ckeditor/ckeditor5-essentials/src/essentials';
+import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
+import Heading from '@ckeditor/ckeditor5-heading/src/heading';
+import List from '@ckeditor/ckeditor5-list/src/list';
+import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';
+import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic';
+
+import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
+import { toWidget, viewToModelPositionOutsideModelElement } from '@ckeditor/ckeditor5-widget/src/utils';
+import Widget from '@ckeditor/ckeditor5-widget/src/widget';
+import Command from '@ckeditor/ckeditor5-core/src/command';
+
+import { addListToDropdown, createDropdown } from '@ckeditor/ckeditor5-ui/src/dropdown/utils';
+import Collection from '@ckeditor/ckeditor5-utils/src/collection';
+import Model from '@ckeditor/ckeditor5-ui/src/model';
+
+class PlaceholderCommand extends Command {
+	execute( { value } ) {
+		const editor = this.editor;
+
+		editor.model.change( writer => {
+			// Create <placeholder> elment with name attribute...
+			const placeholder = writer.createElement( 'placeholder', { name: value } );
+
+			// ... and insert it into the document.
+			editor.model.insertContent( placeholder );
+
+			// Put the selection on inserted element.
+			writer.setSelection( placeholder, 'on' );
+		} );
+	}
+
+	refresh() {
+		const model = this.editor.model;
+		const selection = model.document.selection;
+
+		const isAllowed = model.schema.checkChild( selection.focus.parent, 'placeholder' );
+
+		this.isEnabled = isAllowed;
+	}
+}
+
+class Placeholder extends Plugin {
+	static get requires() {
+		return [ PlaceholderEditing, PlaceholderUI ];
+	}
+}
+
+class PlaceholderUI extends Plugin {
+	init() {
+		const editor = this.editor;
+		const t = editor.t;
+		const placeholderNames = editor.config.get( 'placeholderConfig.types' );
+
+		// The "placeholder" dropdown must be registered among UI components of the editor
+		// to be displayed in the toolbar.
+		editor.ui.componentFactory.add( 'placeholder', locale => {
+			const dropdownView = createDropdown( locale );
+
+			// Populate the list in the dropdown with items.
+			addListToDropdown( dropdownView, getDropdownItemsDefinitions( placeholderNames ) );
+
+			dropdownView.buttonView.set( {
+				// The t() function helps localize the editor. All strings enclosed in t() can be
+				// translated and change when the language of the editor changes.
+				label: t( 'Placeholder' ),
+				tooltip: true,
+				withText: true
+			} );
+
+			// Execute the command when the dropdown items is clicked (executed).
+			this.listenTo( dropdownView, 'execute', evt => {
+				editor.execute( 'placeholder', { value: evt.source.commandParam } );
+				editor.editing.view.focus();
+			} );
+
+			return dropdownView;
+		} );
+	}
+}
+
+function getDropdownItemsDefinitions( placeholderNames ) {
+	const itemDefinitions = new Collection();
+
+	for ( const name of placeholderNames ) {
+		const definition = {
+			type: 'button',
+			model: new Model( {
+				commandParam: name,
+				label: name,
+				withText: true
+			} )
+		};
+
+		// Add the item definition to the collection.
+		itemDefinitions.add( definition );
+	}
+
+	return itemDefinitions;
+}
+
+class PlaceholderEditing extends Plugin {
+	static get requires() {
+		return [ Widget ];
+	}
+
+	init() {
+		console.log( 'PlaceholderEditing#init() got called' );
+
+		this._defineSchema();
+		this._defineConverters();
+
+		this.editor.commands.add( 'placeholder', new PlaceholderCommand( this.editor ) );
+
+		this.editor.editing.mapper.on(
+			'viewToModelPosition',
+			viewToModelPositionOutsideModelElement( this.editor.model, viewElement => viewElement.hasClass( 'placeholder' ) )
+		);
+		this.editor.config.define( 'placeholderConfig', {
+			types: [ 'date', 'first name', 'surname' ]
+		} );
+	}
+
+	_defineSchema() {
+		const schema = this.editor.model.schema;
+
+		schema.register( 'placeholder', {
+			// Allow wherever text is allowed:
+			allowWhere: '$text',
+
+			// The placeholder will acts as an inline node:
+			isInline: true,
+
+			// The inline-widget is self-contained so cannot be split by the caret and can be selected:
+			isObject: true,
+
+			// The placeholder can have many types, like date, name, surname, etc:
+			allowAttributes: [ 'name' ]
+		} );
+	}
+
+	_defineConverters() {
+		const conversion = this.editor.conversion;
+
+		conversion.for( 'upcast' ).elementToElement( {
+			view: {
+				name: 'span',
+				classes: [ 'placeholder' ]
+			},
+			model: ( viewElement, modelWriter ) => {
+				// Extract the "name" from "{name}".
+				const name = viewElement.getChild( 0 ).data.slice( 1, -1 );
+
+				return modelWriter.createElement( 'placeholder', { name } );
+			}
+		} );
+
+		conversion.for( 'editingDowncast' ).elementToElement( {
+			model: 'placeholder',
+			view: ( modelItem, viewWriter ) => {
+				const widgetElement = createPlaceholderView( modelItem, viewWriter );
+
+				// Enable widget handling on placeholder element inside editing view.
+				return toWidget( widgetElement, viewWriter );
+			}
+		} );
+
+		conversion.for( 'dataDowncast' ).elementToElement( {
+			model: 'placeholder',
+			view: createPlaceholderView
+		} );
+
+		// Helper method for both downcast converters.
+		function createPlaceholderView( modelItem, viewWriter ) {
+			const name = modelItem.getAttribute( 'name' );
+
+			const placeholderView = viewWriter.createContainerElement( 'span', {
+				class: 'placeholder'
+			} );
+
+			// Insert the placeholder name (as a text).
+			const innerText = viewWriter.createText( '{' + name + '}' );
+			viewWriter.insert( viewWriter.createPositionAt( placeholderView, 0 ), innerText );
+
+			return placeholderView;
+		}
+	}
+}
+
+ClassicEditor
+	.create( document.querySelector( '#snippet-inline-widget' ), {
+		plugins: [ Essentials, Paragraph, Heading, List, Bold, Italic, Placeholder ],
+		toolbar: [ 'heading', '|', 'bold', 'italic', 'numberedList', 'bulletedList', '|', 'placeholder' ],
+		placeholderConfig: {
+			types: [ 'date', 'color', 'first name', 'surname' ]
+		}
+	} )
+	.then( editor => {
+		console.log( 'Editor was initialized', editor );
+
+		// Expose for playing in the console.
+		window.editor = editor;
+	} )
+	.catch( error => {
+		console.error( error.stack );
+	} );

+ 4 - 0
docs/framework/guides/tutorials/implementing-an-inline-widget.md

@@ -716,3 +716,7 @@ ClassicEditor
 Now if you open the dropdown in the toolbar you'll see new list of placeholders to insert.
 Now if you open the dropdown in the toolbar you'll see new list of placeholders to insert.
 
 
 {@img assets/img/tutorial-implementing-an-inline-widget-2.png Screenshot of the placeholder widgets being inserted using the dropdown.}
 {@img assets/img/tutorial-implementing-an-inline-widget-2.png Screenshot of the placeholder widgets being inserted using the dropdown.}
+
+## Demo
+
+{@snippet framework/tutorials/inline-widget}