浏览代码

Define StyleProxy methods.

Maciej Gołaszewski 6 年之前
父节点
当前提交
c8b757fc9d

+ 4 - 0
packages/ckeditor5-engine/src/view/element.js

@@ -14,6 +14,7 @@ import objectToMap from '@ckeditor/ckeditor5-utils/src/objecttomap';
 import isIterable from '@ckeditor/ckeditor5-utils/src/isiterable';
 import Matcher from './matcher';
 import { isPlainObject } from 'lodash-es';
+import { getStyleProxy } from './stylenormalizer';
 
 /**
  * View element.
@@ -113,6 +114,8 @@ export default class Element extends Node {
 		if ( this._attrs.has( 'style' ) ) {
 			// Remove style attribute and handle it by styles map.
 			parseInlineStyles( this._styles, this._attrs.get( 'style' ) );
+			this._stylesProxy = getStyleProxy( this._attrs.get( 'style' ) );
+
 			this._attrs.delete( 'style' );
 		}
 
@@ -615,6 +618,7 @@ export default class Element extends Node {
 			parseClasses( this._classes, value );
 		} else if ( key == 'style' ) {
 			parseInlineStyles( this._styles, value );
+			this._stylesProxy = getStyleProxy( value );
 		} else {
 			this._attrs.set( key, value );
 		}

+ 142 - 36
packages/ckeditor5-engine/src/view/stylenormalizer.js

@@ -4,50 +4,134 @@
  */
 import { parseInlineStyles } from './element';
 
-function parseBorder( entry, value, styleObject ) {
-	if ( entry === 'border' ) {
-		const parsedBorder = parseBorderAttribute( value );
+import { get, has, isObject, unset } from 'lodash-es';
 
-		const border = {
-			top: parsedBorder,
-			right: parsedBorder,
-			bottom: parsedBorder,
-			left: parsedBorder
-		};
+export function getStyleProxy( styleString ) {
+	return new StyleProxy( styleString );
+}
 
-		addStyle( styleObject, 'border', border );
-	} else {
-		const borderPositionRegExp = /border-(top|right|bottom|left)$/;
+// <-- FROM VIEW/MODEL
+// proxy.setStyle( 'border: 1px solid blue;' )
+//
+// <-> MODIFY
+// proxy.insertRule( 'border-top', '1px solid blue' ); // obj?
+// proxy.removeRule( 'border-top' );
+// proxy.clear();
+//
+// --> TO MODEL
+// proxy.getModel(); // full
+// proxy.getModel( 'border' );
+// proxy.getModel( 'border-top' );
+//
+// --> TO VIEW
+// proxy.getInlineStyle();
+// proxy.getInlineRule( 'border' );
+// proxy.getInlineRule( 'border-top' );
+export class StyleProxy {
+	constructor( styleString = '' ) {
+		this._styles = {};
+
+		this.setStyle( styleString );
+	}
+
+	setStyle( styleString = '' ) {
+		this._styles = parseStyle( styleString );
+	}
+
+	insertRule( nameOrObject, value ) {
+		parseRule( nameOrObject, value, this._styles );
+	}
 
-		if ( borderPositionRegExp.test( entry ) ) {
-			const border = {};
-			const which = borderPositionRegExp.exec( entry )[ 1 ];
+	removeRule( name ) {
+		unset( this._styles, name.replace( '-', '.' ) );
+	}
 
-			border[ which ] = parseBorderAttribute( value );
+	getModel( name ) {
+		if ( !name ) {
+			return this._styles;
+		} // TODO: clone
 
-			addStyle( styleObject, 'border', border );
+		if ( has( this._styles, name.replace( '-', '.' ) ) ) {
+			return get( this._styles, name.replace( '-', '.' ) );
 		} else {
-			addStyle( styleObject, entry, value );
+			return this._styles[ name ];
+		}
+	}
+
+	getInlineStyle() {
+		const parsed = [];
+
+		for ( const key of Object.keys( this._styles ) ) {
+			const model = this.getModel( key );
+
+			parsed.push( toInlineStyle( key, model ) );
 		}
+
+		return parsed.join( ';' ) + ( parsed.length ? ';' : '' );
+	}
+
+	getInlineRule( name ) {
+		const model = this.getModel( name );
+
+		if ( !model ) {
+			// Try return directly
+			return this._styles[ name ];
+		}
+
+		if ( isObject( model ) ) {
+			return toInlineStyle( name, model, true );
+		}
+		// String value
+		else {
+			return model;
+		}
+	}
+
+	clear() {
+		this._styles = {};
 	}
 }
 
-export function parseStyle( string ) {
+const borderPositionRegExp = /border-(top|right|bottom|left)$/;
+
+export function parseStyle( string, styleObject = {} ) {
 	const map = new Map();
 
 	parseInlineStyles( map, string );
 
-	const styleObject = {};
-
 	for ( const key of map.keys() ) {
 		const value = map.get( key );
 
-		parseBorder( key, value, styleObject );
+		parseRule( key, value, styleObject );
 	}
 
 	return styleObject;
 }
 
+function parseRule( key, value, styleObject ) {
+	if ( key === 'border' ) {
+		const parsedBorder = parseBorderAttribute( value );
+
+		const border = {
+			top: parsedBorder,
+			right: parsedBorder,
+			bottom: parsedBorder,
+			left: parsedBorder
+		};
+
+		addStyle( styleObject, 'border', border );
+	} else if ( borderPositionRegExp.test( key ) ) {
+		const border = {};
+		const which = borderPositionRegExp.exec( key )[ 1 ];
+
+		border[ which ] = parseBorderAttribute( value );
+
+		addStyle( styleObject, 'border', border );
+	} else {
+		addStyle( styleObject, key, value );
+	}
+}
+
 function parseBorderAttribute( string ) {
 	const result = {};
 
@@ -88,27 +172,49 @@ function addStyle( styleObject, name, value ) {
 	}
 }
 
-export function toInlineStyle( styleName, styleObject ) {
+function toInlineStyle( styleName, styleObjectOrString, strict = false ) {
 	if ( styleName === 'border' ) {
-		const top = toInlineBorder( styleObject.top );
-		const right = toInlineBorder( styleObject.right );
-		const bottom = toInlineBorder( styleObject.bottom );
-		const left = toInlineBorder( styleObject.left );
+		const top = toInlineBorder( styleObjectOrString.top );
+		const right = toInlineBorder( styleObjectOrString.right );
+		const bottom = toInlineBorder( styleObjectOrString.bottom );
+		const left = toInlineBorder( styleObjectOrString.left );
 
 		if ( top === right && right === bottom && bottom === left ) {
-			return 'border:' + top;
-		} else {
-			return [
-				'border-top:' + top,
-				'border-right:' + right,
-				'border-bottom:' + bottom,
-				'border-left:' + left
-			].join( ';' );
+			return ( strict ? '' : 'border:' ) + top;
+		} else if ( !strict ) {
+			const ret = [];
+
+			// TODO not so nice:
+			if ( top ) {
+				ret.push( 'border-top:' + top );
+			}
+
+			if ( right ) {
+				ret.push( 'border-right:' + right );
+			}
+
+			if ( bottom ) {
+				ret.push( 'border-bottom:' + bottom );
+			}
+
+			if ( left ) {
+				ret.push( 'border-left:' + left );
+			}
+
+			return ret.join( ';' );
 		}
+
+		return;
 	}
+
+	if ( borderPositionRegExp.test( styleName ) ) {
+		return toInlineBorder( styleObjectOrString );
+	}
+
+	return ( strict ? '' : styleName + ':' ) + styleObjectOrString;
 }
 
-function toInlineBorder( object ) {
+function toInlineBorder( object = {} ) {
 	const style = [];
 
 	if ( object.width ) {

+ 72 - 81
packages/ckeditor5-engine/tests/view/stylenormalizer.js

@@ -2,85 +2,19 @@
  * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  */
-import { parseStyle, toInlineStyle } from '../../src/view/stylenormalizer';
+import { StyleProxy } from '../../src/view/stylenormalizer';
 
-function isColor( string ) {
-	return /^([#0-9A-Fa-f]{3,8}|[a-zA-Z]+)$/.test( string ) && !isLineStyle( string );
-}
+describe( 'Style proxy', () => {
+	let styleProxy;
 
-function isLineStyle( string ) {
-	return /^(none|hidden|dotted|dashed|solid|double|groove|ridge|inset|outset)$/.test( string );
-}
-
-function isLength( string ) {
-	return /^[+-]?[0-9]?[.]?[0-9]+([a-z]+|%)$/.test( string );
-}
-
-function addStyle( styleObject, name, value ) {
-	if ( typeof value === 'object' ) {
-		styleObject[ name ] = Object.assign( {}, styleObject[ name ] || {}, value );
-	} else {
-		styleObject[ name ] = value;
-	}
-}
-
-describe( 'Style normalizer', () => {
-	it( 'should parse', () => {
-		expect( parseStyle( 'border:1px solid blue;' ) ).to.deep.equal( {
-			border: {
-				bottom: {
-					color: 'blue',
-					style: 'solid',
-					width: '1px'
-				},
-				left: {
-					color: 'blue',
-					style: 'solid',
-					width: '1px'
-				},
-				right: {
-					color: 'blue',
-					style: 'solid',
-					width: '1px'
-				},
-				top: {
-					color: 'blue',
-					style: 'solid',
-					width: '1px'
-				}
-			}
-		} );
+	beforeEach( () => {
+		styleProxy = new StyleProxy();
 	} );
 
 	it( 'should parse', () => {
-		expect( parseStyle( 'border:1px solid blue;border-left:#665511 dashed 2.7em;border-top:7px dotted #ccc;' ) ).to.deep.equal( {
-			border: {
-				bottom: {
-					color: 'blue',
-					style: 'solid',
-					width: '1px'
-				},
-				left: {
-					color: '#665511',
-					style: 'dashed',
-					width: '2.7em'
-				},
-				right: {
-					color: 'blue',
-					style: 'solid',
-					width: '1px'
-				},
-				top: {
-					color: '#ccc',
-					style: 'dotted',
-					width: '7px'
-				}
-			}
-		} );
-	} );
+		styleProxy.setStyle( 'border:1px solid blue;' );
 
-	it( 'should output', () => {
-		const border = {
+		expect( styleProxy.getModel( 'border' ) ).to.deep.equal( {
 			bottom: {
 				color: 'blue',
 				style: 'solid',
@@ -101,13 +35,13 @@ describe( 'Style normalizer', () => {
 				style: 'solid',
 				width: '1px'
 			}
-		};
-
-		expect( toInlineStyle( 'border', border ) ).to.equal( 'border:1px solid blue' );
+		} );
 	} );
 
-	it( 'should output', () => {
-		const border = {
+	it( 'should parse', () => {
+		styleProxy.setStyle( 'border:1px solid blue;border-left:#665511 dashed 2.7em;border-top:7px dotted #ccc;' );
+
+		expect( styleProxy.getModel( 'border' ) ).to.deep.equal( {
 			bottom: {
 				color: 'blue',
 				style: 'solid',
@@ -128,10 +62,67 @@ describe( 'Style normalizer', () => {
 				style: 'dotted',
 				width: '7px'
 			}
-		};
+		} );
+	} );
 
-		expect( toInlineStyle( 'border', border ) ).to.equal(
-			'border-top:7px dotted #ccc;border-right:1px solid blue;border-bottom:1px solid blue;border-left:2.7em dashed #665511'
+	it( 'should output', () => {
+		styleProxy.setStyle( 'border:1px solid blue;' );
+
+		expect( styleProxy.getInlineStyle() ).to.equal( 'border:1px solid blue;' );
+		expect( styleProxy.getInlineRule( 'border' ) ).to.equal( '1px solid blue' );
+		expect( styleProxy.getInlineRule( 'border-top' ) ).to.equal( '1px solid blue' );
+		expect( styleProxy.getInlineRule( 'border-right' ) ).to.equal( '1px solid blue' );
+		expect( styleProxy.getInlineRule( 'border-bottom' ) ).to.equal( '1px solid blue' );
+		expect( styleProxy.getInlineRule( 'border-left' ) ).to.equal( '1px solid blue' );
+	} );
+
+	it( 'should output', () => {
+		styleProxy.setStyle( 'border:1px solid blue;border-left:#665511 dashed 2.7em;border-top:7px dotted #ccc;' );
+
+		expect( styleProxy.getInlineStyle() ).to.equal(
+			'border-top:7px dotted #ccc;border-right:1px solid blue;border-bottom:1px solid blue;border-left:2.7em dashed #665511;'
+		);
+		expect( styleProxy.getInlineRule( 'border' ) ).to.be.undefined;
+		expect( styleProxy.getInlineRule( 'border-top' ) ).to.equal( '7px dotted #ccc' );
+		expect( styleProxy.getInlineRule( 'border-right' ) ).to.equal( '1px solid blue' );
+		expect( styleProxy.getInlineRule( 'border-bottom' ) ).to.equal( '1px solid blue' );
+		expect( styleProxy.getInlineRule( 'border-left' ) ).to.equal( '2.7em dashed #665511' );
+	} );
+
+	it( 'should add', () => {
+		styleProxy.setStyle( 'border:1px solid blue;' );
+		styleProxy.insertRule( 'border-left', '#665511 dashed 2.7em' );
+		styleProxy.insertRule( 'border-top', '7px dotted #ccc' );
+
+		expect( styleProxy.getInlineStyle() ).to.equal(
+			'border-top:7px dotted #ccc;border-right:1px solid blue;border-bottom:1px solid blue;border-left:2.7em dashed #665511;'
 		);
+		expect( styleProxy.getInlineRule( 'border' ) ).to.be.undefined;
+		expect( styleProxy.getInlineRule( 'border-top' ) ).to.equal( '7px dotted #ccc' );
+		expect( styleProxy.getInlineRule( 'border-right' ) ).to.equal( '1px solid blue' );
+		expect( styleProxy.getInlineRule( 'border-bottom' ) ).to.equal( '1px solid blue' );
+		expect( styleProxy.getInlineRule( 'border-left' ) ).to.equal( '2.7em dashed #665511' );
+	} );
+
+	it( 'should output', () => {
+		styleProxy.setStyle( 'border:1px solid blue' );
+		styleProxy.removeRule( 'border-top' );
+
+		expect( styleProxy.getInlineStyle() ).to.equal(
+			'border-right:1px solid blue;border-bottom:1px solid blue;border-left:1px solid blue;'
+		);
+		expect( styleProxy.getInlineRule( 'border' ) ).to.be.undefined;
+		expect( styleProxy.getInlineRule( 'border-top' ) ).to.be.undefined;
+		expect( styleProxy.getInlineRule( 'border-right' ) ).to.equal( '1px solid blue' );
+		expect( styleProxy.getInlineRule( 'border-bottom' ) ).to.equal( '1px solid blue' );
+		expect( styleProxy.getInlineRule( 'border-left' ) ).to.equal( '1px solid blue' );
+	} );
+
+	it( 'pass-through', () => {
+		styleProxy.setStyle( 'foo-bar:baz 1px abc;margin: 2px 3em;' );
+
+		expect( styleProxy.getInlineStyle() ).to.equal( 'foo-bar:baz 1px abc;margin:2px 3em;' );
+		expect( styleProxy.getInlineRule( 'foo-bar' ) ).to.equal( 'baz 1px abc' );
+		expect( styleProxy.getInlineRule( 'margin' ) ).to.equal( '2px 3em' );
 	} );
 } );