浏览代码

Introduce smart view style consuming.

Maciej Gołaszewski 5 年之前
父节点
当前提交
94b9f17a50

+ 13 - 0
packages/ckeditor5-engine/src/conversion/viewconsumable.js

@@ -9,6 +9,7 @@
 
 import { isArray } from 'lodash-es';
 import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
+import StylesMap from '../view/stylesmap';
 
 /**
  * Class used for handling consumption of view {@link module:engine/view/element~Element elements},
@@ -507,6 +508,12 @@ class ViewElementConsumables {
 			}
 
 			consumables.set( name, true );
+
+			if ( type === 'styles' ) {
+				for ( const alsoName of StylesMap.getRelatedStyles( name ) ) {
+					consumables.set( alsoName, true );
+				}
+			}
 		}
 	}
 
@@ -568,6 +575,12 @@ class ViewElementConsumables {
 				this._consume( consumableName, [ ...this._consumables[ consumableName ].keys() ] );
 			} else {
 				consumables.set( name, false );
+
+				if ( type == 'styles' ) {
+					for ( const toConsume of StylesMap.getRelatedStyles( name ) ) {
+						consumables.set( toConsume, false );
+					}
+				}
 			}
 		}
 	}

+ 23 - 0
packages/ckeditor5-engine/src/view/styles/border.js

@@ -103,6 +103,29 @@ export function addBorderRules( stylesProcessor ) {
 	stylesProcessor.setReducer( 'border-bottom', getBorderPositionReducer( 'bottom' ) );
 	stylesProcessor.setReducer( 'border-left', getBorderPositionReducer( 'left' ) );
 	stylesProcessor.setReducer( 'border', borderReducer );
+
+	stylesProcessor.setStyleRelation( 'border', [
+		'border-color', 'border-style', 'border-width',
+		'border-top', 'border-right', 'border-bottom', 'border-left',
+		'border-top-color', 'border-right-color', 'border-bottom-color', 'border-left-color',
+		'border-top-style', 'border-right-style', 'border-bottom-style', 'border-left-style',
+		'border-top-width', 'border-right-width', 'border-bottom-width', 'border-left-width'
+	] );
+
+	stylesProcessor.setStyleRelation( 'border-color', [
+		'border-top-color', 'border-right-color', 'border-bottom-color', 'border-left-color'
+	] );
+	stylesProcessor.setStyleRelation( 'border-style', [
+		'border-top-style', 'border-right-style', 'border-bottom-style', 'border-left-style'
+	] );
+	stylesProcessor.setStyleRelation( 'border-width', [
+		'border-top-width', 'border-right-width', 'border-bottom-width', 'border-left-width'
+	] );
+
+	stylesProcessor.setStyleRelation( 'border-top', [ 'border-top-color', 'border-top-style', 'border-top-width' ] );
+	stylesProcessor.setStyleRelation( 'border-right', [ 'border-right-color', 'border-right-style', 'border-right-width' ] );
+	stylesProcessor.setStyleRelation( 'border-bottom', [ 'border-bottom-color', 'border-bottom-style', 'border-bottom-width' ] );
+	stylesProcessor.setStyleRelation( 'border-left', [ 'border-left-color', 'border-left-style', 'border-left-width' ] );
 }
 
 function borderNormalizer( value ) {

+ 2 - 0
packages/ckeditor5-engine/src/view/styles/margin.js

@@ -36,4 +36,6 @@ export function addMarginRules( stylesProcessor ) {
 	stylesProcessor.setNormalizer( 'margin-left', value => ( { path: 'margin.left', value } ) );
 
 	stylesProcessor.setReducer( 'margin', getTopRightBottomLeftValueReducer( 'margin' ) );
+
+	stylesProcessor.setStyleRelation( 'margin', [ 'margin-top', 'margin-right', 'margin-bottom', 'margin-left' ] );
 }

+ 2 - 0
packages/ckeditor5-engine/src/view/styles/padding.js

@@ -35,4 +35,6 @@ export function addPaddingRules( stylesProcessor ) {
 	stylesProcessor.setNormalizer( 'padding-left', value => ( { path: 'padding.left', value } ) );
 
 	stylesProcessor.setReducer( 'padding', getTopRightBottomLeftValueReducer( 'padding' ) );
+
+	stylesProcessor.setStyleRelation( 'padding', [ 'padding-top', 'padding-right', 'padding-bottom', 'padding-left' ] );
 }

+ 79 - 0
packages/ckeditor5-engine/src/view/stylesmap.js

@@ -368,6 +368,27 @@ export default class StylesMap {
 	}
 
 	/**
+	 * Returns related style names.
+	 *
+	 *		// Enable 'margin' shorthand processing:
+	 *		editor.editing.view.document.addStyleProcessorRules( addMarginRules );
+	 *
+	 *		StylesMap.getRelatedStyles( 'margin' );
+	 *		// will return: [ 'margin-top', 'margin-right', 'margin-bottom', 'margin-left' ];
+	 *
+	 *		StylesMap.getRelatedStyles( 'margin-top' );
+	 *		// will return: [ 'margin' ];
+	 *
+	 * **Note**: To define style relations use {@link #setStyleRelation}.
+	 *
+	 * @param {String} name
+	 * @returns {Array.<String>}
+	 */
+	static getRelatedStyles( name ) {
+		return this._styleProcessor.getRelatedStyles( name );
+	}
+
+	/**
 	 * Returns normalized styles entries for further processing.
 	 *
 	 * @private
@@ -425,6 +446,7 @@ export class StylesProcessor {
 		this._normalizers = new Map();
 		this._extractors = new Map();
 		this._reducers = new Map();
+		this._consumables = new Map();
 	}
 
 	/**
@@ -555,6 +577,24 @@ export class StylesProcessor {
 	}
 
 	/**
+	 * Returns related style names.
+	 *
+	 *		stylesProcessor.getRelatedStyles( 'margin' );
+	 *		// will return: [ 'margin-top', 'margin-right', 'margin-bottom', 'margin-left' ];
+	 *
+	 *		stylesProcessor.getRelatedStyles( 'margin-top' );
+	 *		// will return: [ 'margin' ];
+	 *
+	 * **Note**: To define style relations use {@link #setStyleRelation}.
+	 *
+	 * @param {String} name
+	 * @returns {Array.<String>}
+	 */
+	getRelatedStyles( name ) {
+		return this._consumables.get( name ) || [];
+	}
+
+	/**
 	 * Adds a normalizer method for a style property.
 	 *
 	 * A normalizer returns describing how the value should be normalized.
@@ -688,6 +728,45 @@ export class StylesProcessor {
 	setReducer( name, callback ) {
 		this._reducers.set( name, callback );
 	}
+
+	/**
+	 * Defines a style shorthand relation to other style notations.
+	 *
+	 *		stylesProcessor.setStyleRelation( 'margin', [
+	 *			'margin-top',
+	 *			'margin-right',
+	 *			'margin-bottom',
+	 *			'margin-left'
+	 *		] );
+	 *
+	 * This enables expanding of style names for shorthands. For instance, if defined, view consumable entries are automatically created
+	 * for long-hand margin style notation alongside 'margin' entry.
+	 *
+	 * @param {String} shorthandName
+	 * @param {Array.<String>} styleNames
+	 */
+	setStyleRelation( shorthandName, styleNames ) {
+		this._mapStyleNames( shorthandName, styleNames );
+
+		for ( const alsoName of styleNames ) {
+			this._mapStyleNames( alsoName, [ shorthandName ] );
+		}
+	}
+
+	/**
+	 * Set two-way binding of style names.
+	 *
+	 * @param {String} name
+	 * @param {Array.<String>} styleNames
+	 * @private
+	 */
+	_mapStyleNames( name, styleNames ) {
+		if ( !this._consumables.has( name ) ) {
+			this._consumables.set( name, [] );
+		}
+
+		this._consumables.get( name ).push( ...styleNames );
+	}
 }
 
 // Parses inline styles and puts property - value pairs into styles map.

+ 81 - 0
packages/ckeditor5-engine/tests/conversion/viewconsumable.js

@@ -7,6 +7,10 @@ import ViewElement from '../../src/view/element';
 import ViewText from '../../src/view/text';
 import ViewDocumentFragment from '../../src/view/documentfragment';
 import ViewConsumable from '../../src/conversion/viewconsumable';
+import StylesMap, { StylesProcessor } from '../../src/view/stylesmap';
+import { addBorderRules } from '../../src/view/styles/border';
+import { addMarginRules } from '../../src/view/styles/margin';
+import { addPaddingRules } from '../../src/view/styles/padding';
 
 describe( 'ViewConsumable', () => {
 	let viewConsumable, el;
@@ -549,4 +553,81 @@ describe( 'ViewConsumable', () => {
 			expect( newConsumable.test( child3, { name: true, styles: 'top', classes: [ 'qux', 'bar' ] } ) ).to.be.true;
 		} );
 	} );
+
+	describe( 'style shorthands handling', () => {
+		before( () => {
+			const stylesProcessor = new StylesProcessor();
+
+			StylesMap._setProcessor( stylesProcessor );
+
+			addBorderRules( stylesProcessor );
+			addMarginRules( stylesProcessor );
+			addPaddingRules( stylesProcessor );
+		} );
+
+		describe( 'add', () => {
+			it( 'should add padding shorthands', () => {
+				viewConsumable.add( el, { styles: [ 'margin' ] } );
+
+				expect( viewConsumable.test( el, { styles: 'margin-top' } ) ).to.be.true;
+				expect( viewConsumable.test( el, { styles: 'margin-bottom' } ) ).to.be.true;
+				expect( viewConsumable.test( el, { styles: 'margin-right' } ) ).to.be.true;
+				expect( viewConsumable.test( el, { styles: 'margin-left' } ) ).to.be.true;
+			} );
+
+			it( 'should add margin shorthands', () => {
+				viewConsumable.add( el, { styles: [ 'padding' ] } );
+
+				expect( viewConsumable.test( el, { styles: 'padding-top' } ) ).to.be.true;
+				expect( viewConsumable.test( el, { styles: 'padding-bottom' } ) ).to.be.true;
+				expect( viewConsumable.test( el, { styles: 'padding-right' } ) ).to.be.true;
+				expect( viewConsumable.test( el, { styles: 'padding-left' } ) ).to.be.true;
+			} );
+
+			it( 'should add table shorthands', () => {
+				viewConsumable.add( el, { styles: [ 'border' ] } );
+
+				expect( viewConsumable.test( el, { styles: 'border-style' } ) ).to.be.true;
+				expect( viewConsumable.test( el, { styles: 'border-top-style' } ) ).to.be.true;
+				expect( viewConsumable.test( el, { styles: 'border-bottom-style' } ) ).to.be.true;
+				expect( viewConsumable.test( el, { styles: 'border-right-style' } ) ).to.be.true;
+				expect( viewConsumable.test( el, { styles: 'border-left-style' } ) ).to.be.true;
+
+				expect( viewConsumable.test( el, { styles: 'border-color' } ) ).to.be.true;
+				expect( viewConsumable.test( el, { styles: 'border-top-color' } ) ).to.be.true;
+				expect( viewConsumable.test( el, { styles: 'border-bottom-color' } ) ).to.be.true;
+				expect( viewConsumable.test( el, { styles: 'border-right-color' } ) ).to.be.true;
+				expect( viewConsumable.test( el, { styles: 'border-left-color' } ) ).to.be.true;
+
+				expect( viewConsumable.test( el, { styles: 'border-width' } ) ).to.be.true;
+				expect( viewConsumable.test( el, { styles: 'border-top-width' } ) ).to.be.true;
+				expect( viewConsumable.test( el, { styles: 'border-bottom-width' } ) ).to.be.true;
+				expect( viewConsumable.test( el, { styles: 'border-right-width' } ) ).to.be.true;
+				expect( viewConsumable.test( el, { styles: 'border-left-width' } ) ).to.be.true;
+			} );
+		} );
+
+		it( 'should return false when testing style shorthand for consumed longhand', () => {
+			viewConsumable.add( el, { styles: [ 'margin' ] } );
+
+			expect( viewConsumable.test( el, { styles: 'margin' } ) ).to.be.true;
+			viewConsumable.consume( el, { styles: 'margin' } );
+			expect( viewConsumable.test( el, { styles: 'margin-top' } ) ).to.be.false;
+			expect( viewConsumable.test( el, { styles: 'margin-bottom' } ) ).to.be.false;
+			expect( viewConsumable.test( el, { styles: 'margin-right' } ) ).to.be.false;
+			expect( viewConsumable.test( el, { styles: 'margin-left' } ) ).to.be.false;
+		} );
+
+		it( 'should return false when testing style shorthand for consumed shorthand', () => {
+			viewConsumable.add( el, { styles: [ 'margin', 'margin-top', 'margin-right', 'margin-bottom', 'margin-left' ] } );
+
+			expect( viewConsumable.test( el, { styles: 'margin-top' } ) ).to.be.true;
+			viewConsumable.consume( el, { styles: 'margin-top' } );
+			expect( viewConsumable.test( el, { styles: 'margin' } ) ).to.be.false;
+			expect( viewConsumable.test( el, { styles: 'margin-top' } ) ).to.be.false;
+			expect( viewConsumable.test( el, { styles: 'margin-bottom' } ) ).to.be.true;
+			expect( viewConsumable.test( el, { styles: 'margin-right' } ) ).to.be.true;
+			expect( viewConsumable.test( el, { styles: 'margin-left' } ) ).to.be.true;
+		} );
+	} );
 } );