Преглед изворни кода

Merge branch 'master' into t/144-new

Piotrek Koszuliński пре 8 година
родитељ
комит
e2085dbf2c

+ 7 - 0
packages/ckeditor5-utils/CHANGELOG.md

@@ -1,6 +1,13 @@
 Changelog
 =========
 
+## [1.0.0-alpha.2](https://github.com/ckeditor/ckeditor5-utils/compare/v1.0.0-alpha.1...v1.0.0-alpha.2) (2017-11-14)
+
+### Bug fixes
+
+* Removed a period at the end of an error message because some browsers included the period in links to errors. Closes [#193](https://github.com/ckeditor/ckeditor5-utils/issues/193). ([fdebc2f](https://github.com/ckeditor/ckeditor5-utils/commit/fdebc2f))
+
+
 ## [1.0.0-alpha.1](https://github.com/ckeditor/ckeditor5-utils/compare/v0.10.0...v1.0.0-alpha.1) (2017-10-03)
 
 ### Features

+ 4 - 4
packages/ckeditor5-utils/package.json

@@ -1,17 +1,17 @@
 {
   "name": "@ckeditor/ckeditor5-utils",
-  "version": "1.0.0-alpha.1",
+  "version": "1.0.0-alpha.2",
   "description": "Miscellaneous utils used by CKEditor 5.",
   "keywords": [
     "ckeditor5",
     "ckeditor5-lib"
   ],
   "devDependencies": {
-    "@ckeditor/ckeditor5-core": "^1.0.0-alpha.1",
-    "@ckeditor/ckeditor5-engine": "^1.0.0-alpha.1",
+    "@ckeditor/ckeditor5-core": "^1.0.0-alpha.2",
+    "@ckeditor/ckeditor5-engine": "^1.0.0-alpha.2",
     "del": "^2.2.0",
     "eslint": "^4.8.0",
-    "eslint-config-ckeditor5": "^1.0.6",
+    "eslint-config-ckeditor5": "^1.0.7",
     "husky": "^0.14.3",
     "lint-staged": "^4.2.3",
     "lodash-cli": "^4"

+ 3 - 2
packages/ckeditor5-utils/src/dom/emittermixin.js

@@ -11,6 +11,7 @@ import { default as EmitterMixin, _getEmitterListenedTo, _setEmitterId } from '.
 import uid from '../uid';
 import extend from '../lib/lodash/extend';
 import isDomNode from './isdomnode';
+import isWindow from './iswindow';
 
 /**
  * Mixin that injects the DOM events API into its host. It provides the API
@@ -53,7 +54,7 @@ const DomEmitterMixin = extend( {}, EmitterMixin, {
 	listenTo( emitter, ...rest ) {
 		// Check if emitter is an instance of DOM Node. If so, replace the argument with
 		// corresponding ProxyEmitter (or create one if not existing).
-		if ( isDomNode( emitter ) ) {
+		if ( isDomNode( emitter ) || isWindow( emitter ) ) {
 			const proxy = this._getProxyEmitter( emitter ) || new ProxyEmitter( emitter );
 
 			proxy.attach( ...rest );
@@ -83,7 +84,7 @@ const DomEmitterMixin = extend( {}, EmitterMixin, {
 	 */
 	stopListening( emitter, event, callback ) {
 		// Check if emitter is an instance of DOM Node. If so, replace the argument with corresponding ProxyEmitter.
-		if ( isDomNode( emitter ) ) {
+		if ( isDomNode( emitter ) || isWindow( emitter ) ) {
 			const proxy = this._getProxyEmitter( emitter );
 
 			// Element has no listeners.

+ 9 - 3
packages/ckeditor5-utils/src/dom/isdomnode.js

@@ -7,8 +7,6 @@
  * @module utils/dom/isdomnode
  */
 
-import isNative from '../lib/lodash/isNative';
-
 /**
  * Checks if the object is a native DOM Node.
  *
@@ -16,5 +14,13 @@ import isNative from '../lib/lodash/isNative';
  * @returns {Boolean}
  */
 export default function isDomNode( obj ) {
-	return !!( obj && isNative( obj.addEventListener ) );
+	if ( obj ) {
+		if ( obj.defaultView ) {
+			return obj instanceof obj.defaultView.Document;
+		} else if ( obj.ownerDocument && obj.ownerDocument.defaultView ) {
+			return obj instanceof obj.ownerDocument.defaultView.Node;
+		}
+	}
+
+	return false;
 }

+ 26 - 7
packages/ckeditor5-utils/src/keyboard.js

@@ -12,6 +12,18 @@
 import CKEditorError from './ckeditorerror';
 import env from './env';
 
+const macGlyphsToModifiers = {
+	'⌘': 'ctrl',
+	'⇧': 'shift',
+	'⌥': 'alt'
+};
+
+const modifiersToMacGlyphs = {
+	'ctrl': '⌘',
+	'shift': '⇧',
+	'alt': '⌥'
+};
+
 /**
  * Object with `keyName => keyCode` pairs for a set of known keys.
  *
@@ -96,15 +108,22 @@ export function parseKeystroke( keystroke ) {
  * @returns {String} Keystroke text specific for the environment.
  */
 export function getEnvKeystrokeText( keystroke ) {
-	const split = splitKeystrokeText( keystroke );
-
-	if ( env.mac ) {
-		if ( split[ 0 ].toLowerCase() == 'ctrl' ) {
-			return '⌘' + ( split[ 1 ] || '' );
-		}
+	if ( !env.mac ) {
+		return keystroke;
 	}
 
-	return keystroke;
+	return splitKeystrokeText( keystroke )
+		// Replace modifiers (e.g. "ctrl") with Mac glyphs (e.g. "⌘") first.
+		.map( key => modifiersToMacGlyphs[ key.toLowerCase() ] || key )
+
+		// Decide whether to put "+" between keys in the keystroke or not.
+		.reduce( ( value, key ) => {
+			if ( value.slice( -1 ) in macGlyphsToModifiers ) {
+				return value + key;
+			} else {
+				return value + '+' + key;
+			}
+		} );
 }
 
 function generateKnownKeyCodes() {

+ 6 - 6
packages/ckeditor5-utils/src/locale.js

@@ -16,20 +16,20 @@ export default class Locale {
 	/**
 	 * Creates a new instance of the Locale class.
 	 *
-	 * @param {String} [lang='en'] The language code in [ISO 639-1](https://en.wikipedia.org/wiki/ISO_639-1) format.
+	 * @param {String} [language='en'] The language code in [ISO 639-1](https://en.wikipedia.org/wiki/ISO_639-1) format.
 	 */
-	constructor( lang ) {
+	constructor( language ) {
 		/**
 		 * The language code in [ISO 639-1](https://en.wikipedia.org/wiki/ISO_639-1) format.
 		 *
 		 * @readonly
 		 * @member {String}
 		 */
-		this.lang = lang || 'en';
+		this.language = language || 'en';
 
 		/**
-		 * Translates the given string to the {@link #lang}. This method is also availble in {@link module:core/editor/editor~Editor#t} and
-		 * {@link module:ui/view~View#t}.
+		 * Translates the given string to the {@link #language}. This method is also available in {@link module:core/editor/editor~Editor#t}
+		 * and {@link module:ui/view~View#t}.
 		 *
 		 * The strings may contain placeholders (`%<index>`) for values which are passed as the second argument.
 		 * `<index>` is the index in the `values` array.
@@ -55,7 +55,7 @@ export default class Locale {
 	 * @private
 	 */
 	_t( str, values ) {
-		let translatedString = translate( this.lang, str );
+		let translatedString = translate( this.language, str );
 
 		if ( values ) {
 			translatedString = translatedString.replace( /%(\d+)/g, ( match, index ) => {

+ 6 - 0
packages/ckeditor5-utils/src/observablemixin.js

@@ -216,6 +216,12 @@ const ObservableMixin = {
 
 			unbindAttrs.forEach( attrName => {
 				const binding = boundAttributes.get( attrName );
+
+				// Nothing to do if the binding is not defined
+				if ( !binding ) {
+					return;
+				}
+
 				let toObservable, toAttr, toAttrs, toAttrBindings;
 
 				binding.to.forEach( to => {

+ 31 - 5
packages/ckeditor5-utils/src/translation-service.js

@@ -3,6 +3,8 @@
  * For licensing, see LICENSE.md.
  */
 
+/* globals window */
+
 /**
  * @module utils/translation-service
  */
@@ -18,6 +20,12 @@ let dictionaries = {};
  *			'Cancel [context: reject]': 'Anuluj'
  *		} );
  *
+ * That function is accessible globally via `window.CKEDITOR_TRANSLATIONS.add()`. So it's possible to add translation from
+ * the other script, just after that one.
+ *
+ * 		<script src="./path/to/ckeditor.js"></script>
+ * 		<script src="./path/to/translations/en.js"></script>
+ *
  * @param {String} lang Target language.
  * @param {Object.<String, String>} translations Translations which will be added to the dictionary.
  */
@@ -34,24 +42,33 @@ export function add( lang, translations ) {
  * When no translation is defined in the dictionary or the dictionary doesn't exist this function returns
  * the original string without the `'[context: ]'` (happens in development and single-language modes).
  *
- * In a single-language mode (when values passed to `t()` were replaced with target languange strings) the dictionary
+ * In a single-language mode (when values passed to `t()` were replaced with target language strings) the dictionary
  * is left empty, so this function will return the original strings always.
  *
  *		translate( 'pl', 'Cancel [context: reject]' );
  *
  * @param {String} lang Target language.
- * @param {String} translationKey String which is going to be translated.
+ * @param {String} translationKey String that will be translated.
  * @returns {String} Translated sentence.
  */
 export function translate( lang, translationKey ) {
-	if ( !hasTranslation( lang, translationKey ) ) {
+	const numberOfLanguages = getNumberOfLanguages();
+
+	if ( numberOfLanguages === 1 ) {
+		// Override the language to the only supported one.
+		// This can't be done in the `Locale` class, because the translations comes after the `Locale` class initialization.
+		lang = Object.keys( dictionaries )[ 0 ];
+	}
+
+	if ( numberOfLanguages === 0 || !hasTranslation( lang, translationKey ) ) {
 		return translationKey.replace( / \[context: [^\]]+\]$/, '' );
 	}
 
-	return dictionaries[ lang ][ translationKey ];
+	// In case of missing translations we still need to cut off the `[context: ]` parts.
+	return dictionaries[ lang ][ translationKey ].replace( / \[context: [^\]]+\]$/, '' );
 }
 
-// Checks whether the dictionary exists and translaiton in that dictionary exists.
+// Checks whether the dictionary exists and translation in that dictionary exists.
 function hasTranslation( lang, translationKey ) {
 	return (
 		( lang in dictionaries ) &&
@@ -67,3 +84,12 @@ function hasTranslation( lang, translationKey ) {
 export function _clear() {
 	dictionaries = {};
 }
+
+function getNumberOfLanguages() {
+	return Object.keys( dictionaries ).length;
+}
+
+// Export globally add function to enable adding later translations.
+// See https://github.com/ckeditor/ckeditor5/issues/624
+window.CKEDITOR_TRANSLATIONS = window.CKEDITOR_TRANSLATIONS || {};
+window.CKEDITOR_TRANSLATIONS.add = add;

+ 8 - 8
packages/ckeditor5-utils/tests/_utils-tests/normalizehtml.js

@@ -8,29 +8,29 @@ import normalizeHtml from '../../tests/_utils/normalizehtml';
 describe( 'utils', () => {
 	describe( 'normalizeHtml', () => {
 		it( 'should sort attributes', () => {
-			const actual = '<a style="border:1px;" class="" href="file://"></a>';
-			const expected = '<a class="" href="file://" style="border:1px;"></a>';
+			const actual = '<a style="border:1px" class="" href="file://"></a>';
+			const expected = '<a class="" href="file://" style="border:1px"></a>';
 
 			expect( normalizeHtml( actual ) ).to.equal( expected );
 		} );
 
 		it( 'should normalize styles', () => {
 			const actual = '<a style="border:1px"></a>';
-			const expected = '<a style="border:1px;"></a>';
+			const expected = '<a style="border:1px"></a>';
 
 			expect( normalizeHtml( actual ) ).to.equal( expected );
 		} );
 
 		it( 'should lowercase attributes', () => {
-			const actual = '<A CLASS="" HREF="file://" STYLE="border:1px;"></A>';
-			const expected = '<a class="" href="file://" style="border:1px;"></a>';
+			const actual = '<A CLASS="" HREF="file://" STYLE="border:1px"></A>';
+			const expected = '<a class="" href="file://" style="border:1px"></a>';
 
 			expect( normalizeHtml( actual ) ).to.equal( expected );
 		} );
 
 		it( 'should trim whitespace', () => {
 			const actual = '<a class="  " href="file://"      style="border:  1px"></a>';
-			const expected = '<a class="" href="file://" style="border:1px;"></a>';
+			const expected = '<a class="" href="file://" style="border:1px"></a>';
 
 			expect( normalizeHtml( actual ) ).to.equal( expected );
 		} );
@@ -49,9 +49,9 @@ describe( 'utils', () => {
 			expect( normalizeHtml( actual ) ).to.equal( expected );
 		} );
 
-		it( 'should not sort attribute value', () => {
+		it( 'should sort attribute value', () => {
 			const actual = '<a class="b c a"></a>';
-			const expected = actual;
+			const expected = '<a class="a b c"></a>';
 
 			expect( normalizeHtml( actual ) ).to.equal( expected );
 		} );

+ 24 - 4
packages/ckeditor5-utils/tests/dom/isdomnode.js

@@ -9,14 +9,34 @@ import isDomNode from '../../src/dom/isdomnode';
 
 describe( 'isDomNode()', () => {
 	it( 'detects native DOM nodes', () => {
+		expect( isDomNode( document ) ).to.be.true;
+		expect( isDomNode( document.createElement( 'div' ) ) ).to.be.true;
+		expect( isDomNode( document.createTextNode( 'Foo' ) ) ).to.be.true;
+
 		expect( isDomNode( {} ) ).to.be.false;
 		expect( isDomNode( null ) ).to.be.false;
 		expect( isDomNode( undefined ) ).to.be.false;
 		expect( isDomNode( new Date() ) ).to.be.false;
 		expect( isDomNode( 42 ) ).to.be.false;
-		expect( isDomNode( window ) ).to.be.true;
-		expect( isDomNode( document ) ).to.be.true;
-		expect( isDomNode( document.createElement( 'div' ) ) ).to.be.true;
-		expect( isDomNode( document.createTextNode( 'Foo' ) ) ).to.be.true;
+		expect( isDomNode( window ) ).to.be.false;
+	} );
+
+	it( 'works for nodes in an iframe', done => {
+		const iframe = document.createElement( 'iframe' );
+
+		iframe.addEventListener( 'load', () => {
+			const iframeDocument = iframe.contentWindow.document;
+
+			expect( isDomNode( iframeDocument ) ).to.be.true;
+			expect( isDomNode( iframeDocument.createElement( 'div' ) ) ).to.be.true;
+			expect( isDomNode( iframeDocument.createTextNode( 'Foo' ) ) ).to.be.true;
+
+			expect( isDomNode( iframe.contentWindow ) ).to.be.false;
+
+			iframe.remove();
+			done();
+		} );
+
+		document.body.appendChild( iframe );
 	} );
 } );

+ 5 - 2
packages/ckeditor5-utils/tests/dom/rect.js

@@ -435,7 +435,7 @@ describe( 'Rect', () => {
 			ancestorA = document.createElement( 'div' );
 			ancestorB = document.createElement( 'div' );
 
-			ancestorA.append( element );
+			ancestorA.appendChild( element );
 			document.body.appendChild( ancestorA );
 		} );
 
@@ -575,7 +575,7 @@ describe( 'Rect', () => {
 		} );
 
 		it( 'should return the visible rect (HTMLElement), partially cropped, deep ancestor overflow', () => {
-			ancestorB.append( ancestorA );
+			ancestorB.appendChild( ancestorA );
 			document.body.appendChild( ancestorB );
 
 			testUtils.sinon.stub( element, 'getBoundingClientRect' ).returns( {
@@ -909,6 +909,9 @@ describe( 'Rect', () => {
 					height: 230
 				} );
 
+				// Safari fails because of "afterEach()" hook tries to restore values from removed element.
+				// We need to restore these values manually.
+				testUtils.sinon.restore();
 				iframe.remove();
 				done();
 			} );

+ 3 - 0
packages/ckeditor5-utils/tests/dom/scroll.js

@@ -248,6 +248,9 @@ describe( 'scrollViewportToShowTarget()', () => {
 		} );
 
 		afterEach( () => {
+			// Safari fails because of "afterEach()" hook tries to restore values from removed element.
+			// We need to restore these values manually.
+			testUtils.sinon.restore();
 			iframeAncestor.remove();
 		} );
 

+ 22 - 1
packages/ckeditor5-utils/tests/keyboard.js

@@ -120,9 +120,28 @@ describe( 'Keyboard', () => {
 				expect( getEnvKeystrokeText( 'ctrl+A' ) ).to.equal( '⌘A' );
 			} );
 
+			it( 'replaces SHIFT with ⇧', () => {
+				expect( getEnvKeystrokeText( 'SHIFT' ) ).to.equal( '⇧' );
+				expect( getEnvKeystrokeText( 'SHIFT+A' ) ).to.equal( '⇧A' );
+				expect( getEnvKeystrokeText( 'shift+A' ) ).to.equal( '⇧A' );
+			} );
+
+			it( 'replaces ALT with ⌥', () => {
+				expect( getEnvKeystrokeText( 'ALT' ) ).to.equal( '⌥' );
+				expect( getEnvKeystrokeText( 'ALT+A' ) ).to.equal( '⌥A' );
+				expect( getEnvKeystrokeText( 'alt+A' ) ).to.equal( '⌥A' );
+			} );
+
+			it( 'work for multiple modifiers', () => {
+				expect( getEnvKeystrokeText( 'CTRL+SHIFT+X' ) ).to.equal( '⌘⇧X' );
+				expect( getEnvKeystrokeText( 'ALT+SHIFT+X' ) ).to.equal( '⌥⇧X' );
+			} );
+
 			it( 'does not touch other keys', () => {
-				expect( getEnvKeystrokeText( 'SHIFT+A' ) ).to.equal( 'SHIFT+A' );
+				expect( getEnvKeystrokeText( 'ESC+A' ) ).to.equal( 'ESC+A' );
+				expect( getEnvKeystrokeText( 'TAB' ) ).to.equal( 'TAB' );
 				expect( getEnvKeystrokeText( 'A' ) ).to.equal( 'A' );
+				expect( getEnvKeystrokeText( 'A+CTRL+B' ) ).to.equal( 'A+⌘B' );
 			} );
 		} );
 
@@ -135,6 +154,8 @@ describe( 'Keyboard', () => {
 				expect( getEnvKeystrokeText( 'CTRL+A' ) ).to.equal( 'CTRL+A' );
 				expect( getEnvKeystrokeText( 'ctrl+A' ) ).to.equal( 'ctrl+A' );
 				expect( getEnvKeystrokeText( 'SHIFT+A' ) ).to.equal( 'SHIFT+A' );
+				expect( getEnvKeystrokeText( 'alt+A' ) ).to.equal( 'alt+A' );
+				expect( getEnvKeystrokeText( 'CTRL+SHIFT+A' ) ).to.equal( 'CTRL+SHIFT+A' );
 				expect( getEnvKeystrokeText( 'A' ) ).to.equal( 'A' );
 			} );
 		} );

+ 4 - 4
packages/ckeditor5-utils/tests/locale.js

@@ -13,16 +13,16 @@ describe( 'Locale', () => {
 	} );
 
 	describe( 'constructor', () => {
-		it( 'sets the lang', () => {
+		it( 'sets the language', () => {
 			const locale = new Locale( 'pl' );
 
-			expect( locale ).to.have.property( 'lang', 'pl' );
+			expect( locale ).to.have.property( 'language', 'pl' );
 		} );
 
-		it( 'defaults lang to en', () => {
+		it( 'defaults language to en', () => {
 			const locale = new Locale();
 
-			expect( locale ).to.have.property( 'lang', 'en' );
+			expect( locale ).to.have.property( 'language', 'en' );
 		} );
 	} );
 

+ 8 - 0
packages/ckeditor5-utils/tests/observablemixin.js

@@ -739,6 +739,14 @@ describe( 'Observable', () => {
 			observable.unbind();
 		} );
 
+		it( 'should not fail when unbinding attribute that is not bound', () => {
+			const observable = new Observable();
+
+			observable.bind( 'foo' ).to( car, 'color' );
+
+			expect( () => observable.unbind( 'bar' ) ).to.not.throw();
+		} );
+
 		it( 'should throw when non-string attribute is passed', () => {
 			expect( () => {
 				car.unbind( new Date() );

+ 19 - 1
packages/ckeditor5-utils/tests/translation-service.js

@@ -3,10 +3,12 @@
  * For licensing, see LICENSE.md.
  */
 
+/* globals window */
+
 import { translate, add, _clear } from '../src/translation-service';
 
 describe( 'translation-service', () => {
-	beforeEach( () => {
+	afterEach( () => {
 		_clear();
 	} );
 
@@ -45,6 +47,17 @@ describe( 'translation-service', () => {
 		expect( translation ).to.be.equal( 'Bold' );
 	} );
 
+	it( 'should use provided language if only one is provided', () => {
+		add( 'pl', {
+			'OK': 'OK',
+			'Cancel [context: reject]': 'Anuluj'
+		} );
+
+		const translation = translate( 'de', 'Cancel [context: reject]' );
+
+		expect( translation ).to.be.equal( 'Anuluj' );
+	} );
+
 	it( 'should be able to merge translations', () => {
 		add( 'pl', {
 			'OK': 'OK',
@@ -62,4 +75,9 @@ describe( 'translation-service', () => {
 		expect( translationPL ).to.be.equal( 'Anuluj' );
 		expect( translationEN ).to.be.equal( 'Cancel' );
 	} );
+
+	it( 'should expose `add` function globally', () => {
+		expect( window.CKEDITOR_TRANSLATIONS ).to.be.an( 'object' );
+		expect( window.CKEDITOR_TRANSLATIONS.add ).to.be.a( 'function' );
+	} );
 } );