diff --git a/CHANGES.md b/CHANGES.md index 1783d426f19..390d9644d87 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -31,6 +31,7 @@ Fixed Issues: API Changes: * [#1346](https://github.com/ckeditor/ckeditor-dev/issues/1346): [Balloon Toolbar](https://ckeditor.com/cke4/addon/balloontoolbar) [context manager API](https://docs.ckeditor.com/ckeditor4/docs/#!/api/CKEDITOR.plugins.balloontoolbar.contextManager) is now available in [pluginDefinition.init](https://docs.ckeditor.com/ckeditor4/docs/#!/api/CKEDITOR.pluginDefinition-method-init) method of a [requiring](https://docs.ckeditor.com/ckeditor4/docs/#!/api/CKEDITOR.pluginDefinition-property-requires) plugin. +* [#1530](https://github.com/ckeditor/ckeditor-dev/issues/1530): Added possibility to use custom icons for [buttons](CKEDITOR.ui.button). Other Changes: diff --git a/plugins/button/plugin.js b/plugins/button/plugin.js index e5fd6b2dcfc..4601b10a79b 100644 --- a/plugins/button/plugin.js +++ b/plugins/button/plugin.js @@ -49,7 +49,9 @@ btnTpl = CKEDITOR.addTemplate( 'button', template ); CKEDITOR.plugins.add( 'button', { + // jscs:disable maximumLineLength lang: 'af,ar,az,bg,ca,cs,da,de,de-ch,el,en,en-au,en-gb,eo,es,es-mx,eu,fa,fi,fr,gl,he,hr,hu,id,it,ja,km,ko,ku,lt,nb,nl,no,oc,pl,pt,pt-br,ro,ru,sk,sl,sq,sv,tr,tt,ug,uk,vi,zh,zh-cn', // %REMOVE_LINE_CORE% + // jscs:enable maximumLineLength beforeInit: function( editor ) { editor.ui.addHandler( CKEDITOR.UI_BUTTON, CKEDITOR.ui.button.handler ); } @@ -117,6 +119,8 @@ * this button should be appended. */ render: function( editor, output ) { + var modeStates = null; + function updateState() { // "this" is a CKEDITOR.ui.button instance. var mode = editor.mode; @@ -198,7 +202,7 @@ // Indicate a mode sensitive button. if ( this.modes ) { - var modeStates = {}; + modeStates = {}; editor.on( 'beforeModeUnload', function() { if ( editor.mode && this._.state != CKEDITOR.TRISTATE_DISABLED ) @@ -224,6 +228,8 @@ } } + var iconName; + // For button that has text-direction awareness on selection path. if ( this.directional ) { editor.on( 'contentDirChanged', function( evt ) { @@ -254,12 +260,30 @@ } var name = this.name || this.command, - iconName = name; + iconPath = null; + + iconName = name; // Check if we're pointing to an icon defined by another command. (https://dev.ckeditor.com/ticket/9555) if ( this.icon && !( /\./ ).test( this.icon ) ) { iconName = this.icon; this.icon = null; + + } else { + // Register and use custom icon for button (#1530). + if ( this.icon ) { + iconPath = this.icon; + } + if ( CKEDITOR.env.hidpi && this.iconHiDpi ) { + iconPath = this.iconHiDpi; + } + } + + if ( iconPath ) { + CKEDITOR.skin.addIcon( iconPath, iconPath ); + this.icon = null; + } else { + iconPath = iconName; } var params = { @@ -277,7 +301,7 @@ keydownFn: keydownFn, focusFn: focusFn, clickFn: clickFn, - style: CKEDITOR.skin.getIconStyle( iconName, ( editor.lang.dir == 'rtl' ), this.icon, this.iconOffset ), + style: CKEDITOR.skin.getIconStyle( iconPath, ( editor.lang.dir == 'rtl' ), this.icon, this.iconOffset ), arrowHtml: this.hasArrow ? btnArrowTpl.output() : '' }; @@ -381,6 +405,32 @@ * @param {String} definition.command The command to be executed once the button is activated. * @param {String} definition.toolbar The {@link CKEDITOR.config#toolbarGroups toolbar group} into which * the button will be added. An optional index value (separated by a comma) determines the button position within the group. + * @param {String} definition.icon Path to custom icon or icon name registered by another plugin. Custom icon paths + * are supported since **4.9.0** version. + * + * To use icon registered by another plugin, icon parameter should be used like: + * + * editor.ui.addButton( 'my_button', { + * icon: 'Link' // Uses link icon from Link plugin. + * } ); + * + * If the plugin provides HiDPI version of an icon it will be used for HiDPI displays (so defining `iconHiDpi` is not needed + * in this case). + * + * To use custom icon, path to icon should be provided like: + * + * editor.ui.addButton( 'my_button', { + * icon: 'assets/icons/my_button.png' + * } ) + * + * This icon will be used for both standard and HiDPI displays unless `iconHiDpi` is explicitly defined. + * **Important**: CKEditor will resolve relative paths based on {@link CKEDITOR#basePath}. + * @param {String} definition.iconHiDpi Path to custom HiDPI icon version. Supported since **4.9.0** version. + * It will be used only in HiDPI environments. Usage is similar to `icon` parameter: + * + * editor.ui.addButton( 'my_button', { + * iconHiDpi: 'assets/icons/my_button.hidpi.png' + * } ) */ CKEDITOR.ui.prototype.addButton = function( name, definition ) { this.add( name, CKEDITOR.UI_BUTTON, definition ); diff --git a/tests/_assets/sample_icon.hidpi.png b/tests/_assets/sample_icon.hidpi.png new file mode 100644 index 00000000000..0cb42885c23 Binary files /dev/null and b/tests/_assets/sample_icon.hidpi.png differ diff --git a/tests/_assets/sample_icon.png b/tests/_assets/sample_icon.png new file mode 100644 index 00000000000..d6da65a439f Binary files /dev/null and b/tests/_assets/sample_icon.png differ diff --git a/tests/plugins/button/buttonicon.js b/tests/plugins/button/buttonicon.js new file mode 100644 index 00000000000..addd240d495 --- /dev/null +++ b/tests/plugins/button/buttonicon.js @@ -0,0 +1,264 @@ +/* bender-tags: editor */ +/* bender-ckeditor-plugins: button,toolbar */ + +( function() { + 'use strict'; + + var editorCounter = 0, + originalBasePath = null, + isDev = CKEDITOR.version === '%VERSION%', + isHidpi, + tests; + + function createIconTests( testsObj, tests ) { + CKEDITOR.tools.array.forEach( tests, function( test ) { + testsObj[ test.name ] = function() { + if ( test.ignore ) { + // On a build version icons sprite image is loaded before any test code is execute so we are not able + // to emulate `CKEDITOR.env.hidpi` to force different icons loading. Due to this behaviour some tests + // needs to be ignored in build version. + assert.ignore(); + } else { + CKEDITOR.env.hidpi = test.name.indexOf( 'hidpi' ) !== -1; + assertIcon( test.config, test.button, test.iconPath, test.iconName ); + } + }; + } ); + } + + function assertIcon( editorConfig, btnName, iconPath, iconName ) { + editorCounter++; + bender.editorBot.create( { + name: 'editor' + editorCounter, + config: editorConfig + }, function( bot ) { + var btn = bot.editor.ui.get( btnName ), + btnEl = CKEDITOR.document.getById( btn._.id ), + btnCss = CKEDITOR.tools.parseCssText( btnEl.findOne( '.cke_button_icon' ).getAttribute( 'style' ), true ); + + if ( iconPath === undefined ) { + // Standard icon should be checked. + if ( isDev ) { + var iconFileName = ( iconName || btnName ).toLowerCase(), + hidpi = CKEDITOR.env.hidpi ? 'hidpi\\/' : ''; + + iconPath = new RegExp( 'plugins\\/' + iconFileName + '\\/icons\\/' + hidpi + iconFileName + '\\.png', 'gi' ); + } else { + // In build version all standard icons are inside 'icons.png' sprite. + iconPath = CKEDITOR.env.hidpi ? /plugins\/icons_hidpi\.png/gi : /plugins\/icons\.png/gi; + } + } + + assert.isMatching( iconPath, btnCss[ 'background-image' ] ); + } ); + } + + tests = { + init: function() { + isHidpi = CKEDITOR.env.hidpi; + }, + + tearDown: function() { + // Restore global values modified by tests. + CKEDITOR.env.hidpi = isHidpi; + if ( originalBasePath ) { + CKEDITOR.basePath = originalBasePath; + originalBasePath = null; + } + } + }; + + createIconTests( tests, [ + { + name: 'test default button icon', + button: 'Link', + config: { + extraPlugins: 'link' + }, + ignore: !isDev && CKEDITOR.env.hidpi + }, { + name: 'test default button icon (hidpi)', + button: 'Find', + config: { + extraPlugins: 'find' + }, + ignore: !isDev && !CKEDITOR.env.hidpi + }, { + name: 'test overwriting default button icon', + button: 'Replace', + iconPath: /tests\/_assets\/sample_icon\.png/gi, + config: { + extraPlugins: 'find', + toolbar: [ [ 'Replace' ] ], + on: { + pluginsLoaded: function( evt ) { + var editor = evt.editor; + editor.ui.addButton( 'Replace', { + label: editor.lang.find.replace, + command: 'replace', + icon: 'tests/_assets/sample_icon.png' + } ); + } + } + } + }, { + name: 'test overwriting default button icon (hidpi)', + button: 'Replace', + iconPath: /tests\/_assets\/sample_icon\.hidpi\.png/gi, + config: { + extraPlugins: 'find', + toolbar: [ [ 'Replace' ] ], + on: { + pluginsLoaded: function( evt ) { + var editor = evt.editor; + editor.ui.addButton( 'Replace', { + label: editor.lang.find.replace, + command: 'replace', + iconHiDpi: 'tests/_assets/sample_icon.hidpi.png' + } ); + } + } + } + }, { + name: 'test button icon from different plugin', + button: 'custom_btn1', + iconName: 'about', + config: { + extraPlugins: 'about', + toolbar: [ [ 'custom_btn1' ] ], + on: { + pluginsLoaded: function( evt ) { + evt.editor.ui.addButton( 'custom_btn1', { + icon: 'about' + } ); + } + } + }, + ignore: !isDev && CKEDITOR.env.hidpi + }, { + name: 'test button icon from different plugin (hidpi)', + button: 'custom_btn2', + iconName: 'blockquote', + config: { + extraPlugins: 'blockquote', + toolbar: [ [ 'custom_btn2' ] ], + on: { + pluginsLoaded: function( evt ) { + evt.editor.ui.addButton( 'custom_btn2', { + icon: 'blockquote' + } ); + } + } + }, + ignore: !isDev && !CKEDITOR.env.hidpi + }, { + name: 'test custom button icon', + button: 'custom_btn3', + iconPath: /tests\/_assets\/sample_icon\.png/gi, + config: { + toolbar: [ [ 'custom_btn3' ] ], + on: { + pluginsLoaded: function( evt ) { + evt.editor.ui.addButton( 'custom_btn3', { + icon: 'tests/_assets/sample_icon.png' + } ); + } + } + } + }, { + name: 'test custom button icon (hidpi)', + button: 'custom_btn4', + iconPath: /tests\/_assets\/sample_icon\.hidpi\.png/gi, + config: { + toolbar: [ [ 'custom_btn4' ] ], + on: { + pluginsLoaded: function( evt ) { + evt.editor.ui.addButton( 'custom_btn4', { + iconHiDpi: 'tests/_assets/sample_icon.hidpi.png' + } ); + } + } + } + }, { + name: 'test custom button icon-only (hidpi)', + button: 'custom_btn5', + iconPath: /tests\/_assets\/sample_icon\.png/gi, + config: { + toolbar: [ [ 'custom_btn5' ] ], + on: { + pluginsLoaded: function( evt ) { + evt.editor.ui.addButton( 'custom_btn5', { + icon: 'tests/_assets/sample_icon.png' + } ); + } + } + } + }, { + name: 'test custom button icon with different basepath', + button: 'custom_btn6', + iconPath: /different\/basepath\/assets\/icons\.sample\.png/gi, + config: { + toolbar: [ [ 'custom_btn6' ] ], + on: { + pluginsLoaded: function( evt ) { + originalBasePath = CKEDITOR.basePath; + CKEDITOR.basePath = '/different/basepath/'; + evt.editor.ui.addButton( 'custom_btn6', { + icon: 'assets/icons.sample.png' + } ); + } + } + } + }, { + name: 'test custom button icon with different basepath (hidpi)', + button: 'custom_btn7', + iconPath: /different\/basepath\/assets\/hidpi\/icons\.sample\.png/gi, + config: { + toolbar: [ [ 'custom_btn7' ] ], + on: { + pluginsLoaded: function( evt ) { + originalBasePath = CKEDITOR.basePath; + CKEDITOR.basePath = '/different/basepath/'; + evt.editor.ui.addButton( 'custom_btn7', { + icon: 'assets/hidpi/icons.sample.png' + } ); + } + } + } + }, { + name: 'test custom button icon with different basepath trailing slash', + button: 'custom_btn8', + iconPath: /['|(]\/assets\/icons\.sample\.png/gi, + config: { + toolbar: [ [ 'custom_btn8' ] ], + on: { + pluginsLoaded: function( evt ) { + originalBasePath = CKEDITOR.basePath; + CKEDITOR.basePath = '/different/basepath/'; + evt.editor.ui.addButton( 'custom_btn8', { + icon: '/assets/icons.sample.png' + } ); + } + } + } + }, { + name: 'test custom button icon with different basepath trailing slash (hidpi)', + button: 'custom_btn9', + iconPath: /['|(]\/assets\/hidpi\/icons\.sample\.png/gi, + config: { + toolbar: [ [ 'custom_btn9' ] ], + on: { + pluginsLoaded: function( evt ) { + originalBasePath = CKEDITOR.basePath; + CKEDITOR.basePath = '/different/basepath/'; + evt.editor.ui.addButton( 'custom_btn9', { + icon: '/assets/hidpi/icons.sample.png' + } ); + } + } + } + } + ] ); + + bender.test( tests ); +} )(); diff --git a/tests/plugins/button/manual/buttonicon.html b/tests/plugins/button/manual/buttonicon.html new file mode 100644 index 00000000000..ba384b9b11e --- /dev/null +++ b/tests/plugins/button/manual/buttonicon.html @@ -0,0 +1,26 @@ +