Преглед на файлове

The feature refactoring.

Kamil Piechaczek преди 5 години
родител
ревизия
1d93042693

+ 2 - 1
packages/ckeditor5-html-embed/lang/contexts.json

@@ -1,4 +1,5 @@
 {
   "Insert HTML": "Toolbar button tooltip for the HTML embed feature.",
-  "HTML snippet": "The HTML snippet."
+  "HTML snippet": "The HTML snippet.",
+  "Paste the raw code here.": "A placeholder that will be displayed in the raw HTML textarea field."
 }

+ 23 - 0
packages/ckeditor5-html-embed/src/htmlembed.js

@@ -35,3 +35,26 @@ export default class HTMLEmbed extends Plugin {
 		return 'HTMLEmbed';
 	}
 }
+
+/**
+ * The configuration of the html embed feature.
+ *
+ *		ClassicEditor
+ *			.create( editorElement, {
+ * 				htmlEmbed: ... // Html embed feature options.
+ *			} )
+ *			.then( ... )
+ *			.catch( ... );
+ *
+ * See {@link module:core/editor/editorconfig~EditorConfig all editor options}.
+ *
+ * @interface MediaEmbedConfig
+ */
+
+/**
+ * @member {Boolean} [module:html-embed/htmlembed~MediaEmbedConfig#previewsInData=false]
+ */
+
+/**
+ * @member {Function} [module:html-embed/htmlembed~MediaEmbedConfig#sanitizeHtml]
+ */

+ 0 - 73
packages/ckeditor5-html-embed/src/htmlembedcommand.js

@@ -1,73 +0,0 @@
-/**
- * @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 html-embed/htmlembedcommand
- */
-
-import Command from '@ckeditor/ckeditor5-core/src/command';
-import { findOptimalInsertionPosition } from '@ckeditor/ckeditor5-widget/src/utils';
-import { getSelectedRawHtmlModelWidget, insertRawHtml } from './utils';
-
-/**
- * The HTML embed command.
- *
- * The command is registered by {@link module:html-embed/htmlembedediting~HTMLEmbedEditing} as `'htmlEmbed'`.
- *
- * To insert a HTML code at the current selection, execute the command:
- *
- *		editor.execute( 'htmlEmbed', { html: 'HTML to insert.' } );
- *
- * @extends module:core/command~Command
- */
-export default class HTMLEmbedCommand extends Command {
-	/**
-	 * @inheritDoc
-	 */
-	refresh() {
-		const model = this.editor.model;
-		const selection = model.document.selection;
-		const schema = model.schema;
-		const insertPosition = findOptimalInsertionPosition( selection, model );
-		const selectedRawHtml = getSelectedRawHtmlModelWidget( selection );
-
-		let parent = insertPosition.parent;
-
-		// The model.insertContent() will remove empty parent (unless it is a $root or a limit).
-		if ( parent.isEmpty && !model.schema.isLimit( parent ) ) {
-			parent = parent.parent;
-		}
-
-		this.value = selectedRawHtml ? selectedRawHtml.getAttribute( 'value' ) : null;
-		this.isEnabled = schema.checkChild( parent, 'rawHtml' );
-	}
-
-	/**
-	 * Executes the command, which either:
-	 *
-	 * * updates the URL of the selected media,
-	 * * inserts the new media into the editor and puts the selection around it.
-	 *
-	 * @fires execute
-	 * @param {Object} [options={}] The command options.
-	 * @param {String} [options.rawHtml] A HTML string that will be inserted into the editor.
-	 * @param {module:engine/model/element~Element|null} [options.element] If present, the `value` attribute will be updated
-	 * with the specified `options.rawHtml` value. Otherwise, a new element will be inserted into the editor.
-	 */
-	execute( options = {} ) {
-		const model = this.editor.model;
-
-		const rawHtml = options.rawHtml;
-		const element = options.element;
-
-		if ( element ) {
-			model.change( writer => {
-				writer.setAttribute( 'value', rawHtml, element );
-			} );
-		} else {
-			insertRawHtml( model, rawHtml );
-		}
-	}
-}

Файловите разлики са ограничени, защото са твърде много
+ 151 - 90
packages/ckeditor5-html-embed/src/htmlembedediting.js


+ 98 - 0
packages/ckeditor5-html-embed/src/htmlembedinsertcommand.js

@@ -0,0 +1,98 @@
+/**
+ * @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 html-embed/htmlembedinsertcommand
+ */
+
+import Command from '@ckeditor/ckeditor5-core/src/command';
+import { findOptimalInsertionPosition } from '@ckeditor/ckeditor5-widget/src/utils';
+
+/**
+ * The insert raw html element command.
+ *
+ * The command is registered by {@link module:html-embed/htmlembedediting~HTMLEmbedEditing} as `'htmlEmbedInsert'`.
+ *
+ * To insert a page break at the current selection, execute the command:
+ *
+ *		editor.execute( 'htmlEmbedInsert' );
+ *
+ * @extends module:core/command~Command
+ */
+export default class HTMLEmbedInsertCommand extends Command {
+	/**
+	 * @inheritDoc
+	 */
+	refresh() {
+		this.isEnabled = isHtmlEmbedAllowed( this.editor.model );
+	}
+
+	/**
+	 * Executes the command, which creates and inserts a new html element.
+	 *
+	 * @fires execute
+	 */
+	execute() {
+		const model = this.editor.model;
+
+		model.change( writer => {
+			const rawHtmlElement = writer.createElement( 'rawHtml' );
+
+			model.insertContent( rawHtmlElement );
+		} );
+	}
+}
+
+// Checks if the `htmlEmbed` element can be inserted at the current model selection.
+//
+// @param {module:engine/model/model~Model} model
+// @returns {Boolean}
+function isHtmlEmbedAllowed( model ) {
+	const schema = model.schema;
+	const selection = model.document.selection;
+
+	return isHtmlEmbedAllowedInParent( selection, schema, model ) &&
+		!checkSelectionOnObject( selection, schema );
+}
+
+// Checks if a html embed is allowed by the schema in the optimal insertion parent.
+//
+// @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection} selection
+// @param {module:engine/model/schema~Schema} schema
+// @param {module:engine/model/model~Model} model Model instance.
+// @returns {Boolean}
+function isHtmlEmbedAllowedInParent( selection, schema, model ) {
+	const parent = getInsertPageBreakParent( selection, model );
+
+	return schema.checkChild( parent, 'rawHtml' );
+}
+
+// Checks if the selection is on object.
+//
+// @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection} selection
+// @param {module:engine/model/schema~Schema} schema
+// @returns {Boolean}
+function checkSelectionOnObject( selection, schema ) {
+	const selectedElement = selection.getSelectedElement();
+
+	return selectedElement && schema.isObject( selectedElement );
+}
+
+// Returns a node that will be used to insert a page break with `model.insertContent` to check if a html embed element can be placed there.
+//
+// @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection} selection
+// @param {module:engine/model/model~Model} model Model instance.
+// @returns {module:engine/model/element~Element}
+function getInsertPageBreakParent( selection, model ) {
+	const insertAt = findOptimalInsertionPosition( selection, model );
+
+	const parent = insertAt.parent;
+
+	if ( parent.isEmpty && !parent.is( 'element', '$root' ) ) {
+		return parent.parent;
+	}
+
+	return parent;
+}

+ 11 - 2
packages/ckeditor5-html-embed/src/htmlembedui.js

@@ -10,6 +10,7 @@
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
 import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
 import htmlEmbedIcon from '../theme/icons/htmlembed.svg';
+import { getSelectedRawHtmlViewWidget } from './utils';
 
 /**
  * The HTML embed UI plugin.
@@ -23,7 +24,7 @@ export default class HTMLEmbedUI extends Plugin {
 
 		// Add the `htmlEmbed` button to feature components.
 		editor.ui.componentFactory.add( 'htmlEmbed', locale => {
-			const command = editor.commands.get( 'htmlEmbed' );
+			const command = editor.commands.get( 'htmlEmbedInsert' );
 			const view = new ButtonView( locale );
 
 			view.set( {
@@ -36,8 +37,16 @@ export default class HTMLEmbedUI extends Plugin {
 
 			// Execute the command.
 			this.listenTo( view, 'execute', () => {
-				editor.execute( 'htmlEmbed' );
+				editor.execute( 'htmlEmbedInsert' );
 				editor.editing.view.focus();
+
+				const rawHtmlWidget = getSelectedRawHtmlViewWidget( editor.editing.view.document.selection );
+
+				// After inserting a new element, switch to "Edit source" mode.
+				rawHtmlWidget.getChild( 0 ).getCustomProperty( 'DOMElement' ).click();
+
+				// And focus the edit source element (`textarea`).
+				rawHtmlWidget.getChild( 1 ).getCustomProperty( 'DOMElement' ).focus();
 			} );
 
 			return view;

+ 54 - 0
packages/ckeditor5-html-embed/src/htmlembedupdatecommand.js

@@ -0,0 +1,54 @@
+/**
+ * @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 html-embed/htmlembedupdatecommand
+ */
+
+import { getSelectedRawHtmlModelWidget } from './utils';
+import Command from '@ckeditor/ckeditor5-core/src/command';
+
+/**
+ * The update raw html value command.
+ *
+ * The command is registered by {@link module:html-embed/htmlembedediting~HTMLEmbedEditing} as `'htmlEmbedUpdate'`.
+ *
+ * To insert a page break at the current selection, execute the command:
+ *
+ *		editor.execute( 'htmlEmbedUpdate', 'HTML.' );
+ *
+ * @extends module:core/command~Command
+ */
+export default class HTMLEmbedUpdateCommand extends Command {
+	/**
+	 * @inheritDoc
+	 */
+	refresh() {
+		const model = this.editor.model;
+		const selection = model.document.selection;
+		const rawHtmlElement = getSelectedRawHtmlModelWidget( selection );
+
+		this.isEnabled = !!rawHtmlElement;
+		this.value = rawHtmlElement ? rawHtmlElement.getAttribute( 'value' ) : '';
+	}
+
+	/**
+	 * Executes the command, which updates the `value` attribute of the embedded HTML element:
+	 *
+	 * @fires execute
+	 * @param {String} value HTML as a string.
+	 */
+	execute( value ) {
+		const model = this.editor.model;
+		const selection = model.document.selection;
+		const selectedMedia = getSelectedRawHtmlModelWidget( selection );
+
+		if ( selectedMedia ) {
+			model.change( writer => {
+				writer.setAttribute( 'value', value, selectedMedia );
+			} );
+		}
+	}
+}

+ 6 - 0
packages/ckeditor5-html-embed/tests/manual/htmlembed.html

@@ -1,3 +1,9 @@
+<p>
+    <b>Mode of HTML previews</b>:
+    <input type="radio" id="mode-enabled" name="mode" value="enabled" checked><label for="mode-enabled">Enabled</label>
+    <input type="radio" id="mode-disabled" name="mode" value="disabled"><label for="mode-disabled">Disabled</label>
+</p>
+
 <div id="editor">
 <!--    <div class="raw-html-embed">-->
 <!--        <video width="320" height="240" controls>-->

+ 103 - 42
packages/ckeditor5-html-embed/tests/manual/htmlembed.js

@@ -3,55 +3,116 @@
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  */
 
-/* globals console, window, document */
+/* globals window, document */
 
 import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
 import ArticlePluginSet from '@ckeditor/ckeditor5-core/tests/_utils/articlepluginset';
-import ImageUpload from '@ckeditor/ckeditor5-image/src/imageupload';
-import EasyImage from '@ckeditor/ckeditor5-easy-image/src/easyimage';
-import { CS_CONFIG } from '@ckeditor/ckeditor5-cloud-services/tests/_utils/cloud-services-config';
+import sanitizeHtml from 'sanitize-html';
+import { clone } from 'lodash-es';
 import HTMLEmbed from '../../src/htmlembed';
 
-ClassicEditor
-	.create( document.querySelector( '#editor' ), {
-		cloudServices: CS_CONFIG,
-		plugins: [ ArticlePluginSet, ImageUpload, EasyImage, HTMLEmbed ],
+const restrictedModeButton = document.getElementById( 'mode-enabled' );
+const standardModeButton = document.getElementById( 'mode-disabled' );
+
+restrictedModeButton.addEventListener( 'change', handleModeChange );
+standardModeButton.addEventListener( 'change', handleModeChange );
+
+startMode( document.querySelector( 'input[name="mode"]:checked' ).value );
+
+async function handleModeChange( evt ) {
+	await startMode( evt.target.value );
+}
+
+async function startMode( selectedMode ) {
+	if ( selectedMode === 'enabled' ) {
+		await startEnabledPreviewsMode();
+	} else {
+		await startDisabledPreviewsMode();
+	}
+}
+
+async function startEnabledPreviewsMode() {
+	await reloadEditor( {
+		htmlEmbed: {
+			previewsInData: true,
+			sanitizeHtml( rawHtml ) {
+				const config = getSanitizeHtmlConfig( sanitizeHtml.defaults );
+				const cleanHtml = sanitizeHtml( rawHtml, config );
+
+				return {
+					html: cleanHtml,
+					hasModified: rawHtml !== cleanHtml
+				};
+			}
+		}
+	} );
+}
+
+async function startDisabledPreviewsMode() {
+	await reloadEditor();
+}
+
+async function reloadEditor( config = {} ) {
+	if ( window.editor ) {
+		await window.editor.destroy();
+	}
+
+	config = Object.assign( config, {
+		plugins: [ ArticlePluginSet, HTMLEmbed ],
 		toolbar: [
-			'heading',
-			'|',
-			'bold', 'italic', 'numberedList', 'bulletedList',
-			'|',
-			'link', 'blockquote', 'imageUpload', 'insertTable', 'mediaEmbed',
-			'|',
-			'undo', 'redo',
-			'|',
-			'htmlEmbed'
+			'heading', '|', 'bold', 'italic', 'link', '|',
+			'bulletedList', 'numberedList', 'blockQuote', 'insertTable', '|',
+			'undo', 'redo', '|', 'htmlEmbed'
 		],
 		image: {
-			styles: [
-				'full',
-				'alignLeft',
-				'alignRight'
-			],
-			toolbar: [
-				'imageStyle:alignLeft',
-				'imageStyle:full',
-				'imageStyle:alignRight',
-				'|',
-				'imageTextAlternative'
-			]
-		},
-		table: {
-			contentToolbar: [
-				'tableColumn',
-				'tableRow',
-				'mergeTableCells'
-			]
+			toolbar: [ 'imageStyle:full', 'imageStyle:side', '|', 'imageTextAlternative' ]
 		}
-	} )
-	.then( editor => {
-		window.editor = editor;
-	} )
-	.catch( err => {
-		console.error( err.stack );
 	} );
+
+	window.editor = await ClassicEditor.create( document.querySelector( '#editor' ), config );
+}
+
+function getSanitizeHtmlConfig( defaultConfig ) {
+	const config = clone( defaultConfig );
+
+	config.allowedTags.push(
+		// Allows embedding iframes.
+		'iframe',
+
+		// Allows embedding media.
+		'audio',
+		'video',
+		'picture',
+		'source',
+		'img'
+	);
+
+	config.selfClosing.push( 'source' );
+
+	// Remove duplicates.
+	config.allowedTags = [ ...new Set( config.allowedTags ) ];
+
+	config.allowedSchemesAppliedToAttributes.push(
+		// Responsive images.
+		'srcset'
+	);
+
+	for ( const htmlTag of config.allowedTags ) {
+		if ( !Array.isArray( config.allowedAttributes[ htmlTag ] ) ) {
+			config.allowedAttributes[ htmlTag ] = [];
+		}
+
+		// Allow inlining styles for all elements.
+		config.allowedAttributes[ htmlTag ].push( 'style' );
+	}
+
+	// Should we allow the `controls` attribute?
+	config.allowedAttributes.video.push( 'width', 'height', 'controls' );
+	config.allowedAttributes.audio.push( 'controls' );
+
+	config.allowedAttributes.iframe.push( 'src' );
+	config.allowedAttributes.img.push( 'srcset', 'sizes', 'src' );
+	config.allowedAttributes.source.push( 'src', 'srcset', 'media', 'sizes', 'type' );
+
+	return config;
+}

+ 49 - 3
packages/ckeditor5-html-embed/theme/htmlembed.css

@@ -3,7 +3,53 @@
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  */
 
-.raw-html__edit-preview {
-	width: 100px;
-	height: 100px;
+/* Widget container. */
+.ck-editor__editable .raw-html {
+	position: relative;
+	margin-top: 1em;
+	margin-bottom: 1em;
+}
+
+/* Switch mode button. */
+.ck-editor__editable .raw-html .raw-html__switch-mode {
+	position: absolute;
+	top: 0;
+	right: 0;
+	width: 5em;
+	height: 5em;
+	cursor: pointer;
+}
+
+/* Edit source element. */
+.ck-editor__editable .raw-html textarea {
+	width: 30em;
+	height: 5em;
+	resize: none;
+}
+
+/* Edit source mode is enabled. */
+.ck-editor__editable .raw-html.raw-html--edit-source textarea {
+	display: block;
+}
+
+.ck-editor__editable .raw-html .raw-html__preview {
+	min-height: 5em;
+	min-width: 30em;
+}
+
+/* Edit source mode is enabled. */
+.ck-editor__editable .raw-html.raw-html--edit-source .raw-html__preview {
+	display: none;
+}
+
+/* Preview mode is enabled. */
+.ck-editor__editable .raw-html:not(.raw-html--edit-source) textarea {
+	display: none;
+}
+
+.ck-editor__editable .raw-html__preview-placeholder {
+	height: 5em;
+	background-color: hsl(0, 0%, 50%);
+	text-align: center;
+	line-height: 5em;
 }

Файловите разлики са ограничени, защото са твърде много
+ 6 - 0
packages/ckeditor5-html-embed/theme/icons/htmlembedmode.svg