From 71be3e219ffb488d555293a4d34c88782b67c4fa Mon Sep 17 00:00:00 2001 From: Kamil Piechaczek Date: Tue, 5 Nov 2019 14:32:04 +0100 Subject: [PATCH 01/43] Plugin bootstrap. --- src/insertspecialcharactercommand.js | 17 ++++++++++++ src/specialcharacters.js | 32 ++++++++++++++++++++++ src/specialcharactersui.js | 40 ++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+) create mode 100644 src/insertspecialcharactercommand.js create mode 100644 src/specialcharacters.js create mode 100644 src/specialcharactersui.js diff --git a/src/insertspecialcharactercommand.js b/src/insertspecialcharactercommand.js new file mode 100644 index 0000000..1ad46dd --- /dev/null +++ b/src/insertspecialcharactercommand.js @@ -0,0 +1,17 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/** + * @module special-characters/insertspecialcharactercommand + */ + +import Command from '@ckeditor/ckeditor5-core/src/command'; + +/** + * @extends module:core/command~Command + */ +export default class InsertSpecialCharacterCommand extends Command { + +} diff --git a/src/specialcharacters.js b/src/specialcharacters.js new file mode 100644 index 0000000..5ee239c --- /dev/null +++ b/src/specialcharacters.js @@ -0,0 +1,32 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/** + * @module special-characters/specialcharacters + */ + +import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; +import SpecialCharactersUI from './specialcharactersui'; + +/** + * The special characters feature. + * + * @extends module:core/plugin~Plugin + */ +export default class SpecialCharacters extends Plugin { + /** + * @inheritDoc + */ + static get requires() { + return [ SpecialCharactersUI ]; + } + + /** + * @inheritDoc + */ + static get pluginName() { + return 'SpecialCharacters'; + } +} diff --git a/src/specialcharactersui.js b/src/specialcharactersui.js new file mode 100644 index 0000000..8dbd700 --- /dev/null +++ b/src/specialcharactersui.js @@ -0,0 +1,40 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/** + * @module special-characters/specialcharactersui + */ + +import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; +import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview'; +import specialCharactersIcon from '../theme/icons/specialcharacters.svg'; + +/** + * The special characters UI plugin. + * + * @extends module:core/plugin~Plugin + */ +export default class SpecialCharactersUI extends Plugin { + init() { + const editor = this.editor; + const t = editor.t; + + // Add the `specialCharacters` button to feature components. + editor.ui.componentFactory.add( 'specialCharacters', locale => { + const command = editor.commands.get( 'specialCharacters' ); + const view = new ButtonView( locale ); + + view.set( { + label: t( 'Special characters' ), + icon: specialCharactersIcon, + tooltip: true + } ); + + view.bind( 'isEnabled' ).to( command, 'isEnabled' ); + + return view; + } ); + } +} From 73b39e0221f44d0186e708cf0116abadd7e26838 Mon Sep 17 00:00:00 2001 From: Kamil Piechaczek Date: Tue, 5 Nov 2019 16:22:47 +0100 Subject: [PATCH 02/43] Working InsertSpecialCharacterCommand. --- src/insertspecialcharactercommand.js | 42 ++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/insertspecialcharactercommand.js b/src/insertspecialcharactercommand.js index 1ad46dd..f84bc75 100644 --- a/src/insertspecialcharactercommand.js +++ b/src/insertspecialcharactercommand.js @@ -13,5 +13,47 @@ import Command from '@ckeditor/ckeditor5-core/src/command'; * @extends module:core/command~Command */ export default class InsertSpecialCharacterCommand extends Command { + /** + * Creates an instance of the command. + * + * @param {module:core/editor/editor~Editor} editor + */ + constructor( editor ) { + super( editor ); + /** + * @readonly + * @private + * @member {module:typing/inputcommand~InputCommand} #_inputCommand + */ + this._inputCommand = editor.commands.get( 'input' ); + } + + /** + * @inheritDoc + */ + refresh() { + this.isEnabled = this._inputCommand.isEnabled; + } + + /** + * @param {Object} options + * @param {String} options.item A title of the special character that should be added to the editor. + */ + execute( options ) { + const editor = this.editor; + const item = options.item; + + if ( !item ) { + return; + } + + const character = editor.plugins.get( 'SpecialCharacters' ).getCharacter( item ); + + if ( !character ) { + return; + } + + this._inputCommand.execute( { text: character } ); + } } From fc98169596d6179bf8a6069edddd71581daf0e35 Mon Sep 17 00:00:00 2001 From: Kamil Piechaczek Date: Tue, 5 Nov 2019 16:24:04 +0100 Subject: [PATCH 03/43] First version of the SpecialCharacters class. --- src/specialcharacters.js | 92 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 91 insertions(+), 1 deletion(-) diff --git a/src/specialcharacters.js b/src/specialcharacters.js index 5ee239c..aea9721 100644 --- a/src/specialcharacters.js +++ b/src/specialcharacters.js @@ -8,6 +8,8 @@ */ import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; +import Typing from '@ckeditor/ckeditor5-typing/src/typing'; +import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror'; import SpecialCharactersUI from './specialcharactersui'; /** @@ -16,11 +18,31 @@ import SpecialCharactersUI from './specialcharactersui'; * @extends module:core/plugin~Plugin */ export default class SpecialCharacters extends Plugin { + constructor( editor ) { + super( editor ); + + /** + * Registered characters. A pair of a character name and its symbol. + * + * @private + * @member {Map.} + */ + this._characters = new Map(); + + /** + * Registered groups. Each group contains a collection with symbol names. + * + * @private + * @member {Map.>} + */ + this._groups = new Map(); + } + /** * @inheritDoc */ static get requires() { - return [ SpecialCharactersUI ]; + return [ SpecialCharactersUI, Typing ]; } /** @@ -29,4 +51,72 @@ export default class SpecialCharacters extends Plugin { static get pluginName() { return 'SpecialCharacters'; } + + /** + * Adds a collection of special characters to specified group. + * + * @param {String} groupName + * @param {Array.<~SpecialCharacterDefinition>} items + */ + addItems( groupName, items ) { + const group = this._getGroup( groupName ); + + for ( const item of items ) { + if ( this._characters.has( item.title ) ) { + /** + * @error specialcharacters-duplicated-character-name + */ + throw new CKEditorError( 'specialcharacters-duplicated-character-name', null ); + } + + group.add( item.title ); + this._characters.set( item.title, item.character ); + } + } + + /** + * @returns {Iterable.} + */ + getGroups() { + return this._groups.keys(); + } + + /** + * @param {String} groupName + * @returns {Set|undefined} + */ + getCharacterForGroup( groupName ) { + return this._groups.get( groupName ); + } + + /** + * Returns a symbol of the special character for specified name. If the special character couldn't be found, `undefined` is returned. + * + * @param {String} title A title of the special character. + * @returns {String|undefined} + */ + getCharacter( title ) { + return this._characters.get( title ); + } + + /** + * Returns a group of special characters. If the group with the specified name does not exist, it will be created. + * + * @param {String} groupName A name of group to create. + */ + _getGroup( groupName ) { + if ( !this._groups.has( groupName ) ) { + this._groups.set( groupName, new Set() ); + } + + return this._groups.get( groupName ); + } } + +/** + * @typedef {Object} module:special-characters/specialcharacters~SpecialCharacterDefinition + * + * @property {String} title A unique title of the character. + * + * @property {String} character A symbol that should be inserted to the editor. + */ From e3398bd6615a3edcee5a3b269a4aff68e3c0d382 Mon Sep 17 00:00:00 2001 From: Kamil Piechaczek Date: Tue, 5 Nov 2019 16:24:30 +0100 Subject: [PATCH 04/43] WIP UI. --- src/specialcharactersui.js | 55 +++++++++++++++++++++++++++++++++----- 1 file changed, 48 insertions(+), 7 deletions(-) diff --git a/src/specialcharactersui.js b/src/specialcharactersui.js index 8dbd700..cf1f972 100644 --- a/src/specialcharactersui.js +++ b/src/specialcharactersui.js @@ -8,8 +8,10 @@ */ import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; -import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview'; +import { createDropdown } from '@ckeditor/ckeditor5-ui/src/dropdown/utils'; import specialCharactersIcon from '../theme/icons/specialcharacters.svg'; +import InsertSpecialCharacterCommand from './insertspecialcharactercommand'; +import SelectView from '@ckeditor/ckeditor5-ui/src/selectview/selectview'; /** * The special characters UI plugin. @@ -20,21 +22,60 @@ export default class SpecialCharactersUI extends Plugin { init() { const editor = this.editor; const t = editor.t; + const specialCharacterPlugin = editor.plugins.get( 'SpecialCharacters' ); - // Add the `specialCharacters` button to feature components. + const command = new InsertSpecialCharacterCommand( editor ); + editor.commands.add( 'specialCharacters', command ); + + // Add the `specialCharacters` dropdown button to feature components. editor.ui.componentFactory.add( 'specialCharacters', locale => { - const command = editor.commands.get( 'specialCharacters' ); - const view = new ButtonView( locale ); + // Prepare all special characters groups for displaying in the select view. + const specialCharactersGroups = [ ...specialCharacterPlugin.getGroups() ] + .map( groupName => ( { label: groupName, value: groupName } ) ); + + const dropdownView = createDropdown( locale ); + const selectView = new SelectView( locale, specialCharactersGroups ); - view.set( { + dropdownView.buttonView.set( { label: t( 'Special characters' ), icon: specialCharactersIcon, tooltip: true } ); - view.bind( 'isEnabled' ).to( command, 'isEnabled' ); + dropdownView.bind( 'isEnabled' ).to( command ); + dropdownView.panelView.children.add( selectView ); - return view; + // When a special character was clicked, insert it to the editor. + dropdownView.on( 'execute', ( evt, data ) => { + console.log( 'Kliknąłem.', data ); + + // command.execute( { item: data } ); + } ); + + dropdownView.on( 'change:isOpen', ( evt, name, isVisible ) => { + if ( !isVisible ) { + return; + } + + // Draw special characters tiles when the dropdown is opened. + printCharacters( selectView ); + } ); + + // Draw special characters when a user changed a category. + selectView.on( 'input', () => { + printCharacters( selectView ); + } ); + + return dropdownView; } ); + + function printCharacters( selectView ) { + const groupName = selectView.element.value; + const characters = specialCharacterPlugin.getCharacterForGroup( groupName ); + + console.log( { groupName, characters } ); + console.log( 'Rysuj!' ); + } } } + From 9e0db96440f4f97d12dddb21f56271227506bc30 Mon Sep 17 00:00:00 2001 From: Kamil Piechaczek Date: Tue, 5 Nov 2019 16:24:51 +0100 Subject: [PATCH 05/43] An icon. --- theme/icons/specialcharacters.svg | 1 + 1 file changed, 1 insertion(+) create mode 100644 theme/icons/specialcharacters.svg diff --git a/theme/icons/specialcharacters.svg b/theme/icons/specialcharacters.svg new file mode 100644 index 0000000..502552d --- /dev/null +++ b/theme/icons/specialcharacters.svg @@ -0,0 +1 @@ + \ No newline at end of file From 3be0c9128832510697f844444f002fcd5f21f3d1 Mon Sep 17 00:00:00 2001 From: Kamil Piechaczek Date: Tue, 12 Nov 2019 09:27:36 +0100 Subject: [PATCH 06/43] Improved docs and added basic tests. --- src/insertspecialcharactercommand.js | 8 +- src/specialcharacters.js | 25 ++++-- src/specialcharactersui.js | 4 +- tests/insertspecialcharactercommand.js | 109 +++++++++++++++++++++++++ tests/manual/specialcharacters.html | 4 + tests/manual/specialcharacters.js | 59 +++++++++++++ tests/manual/specialcharacters.md | 4 + tests/specialcharacters.js | 107 ++++++++++++++++++++++++ 8 files changed, 306 insertions(+), 14 deletions(-) create mode 100644 tests/insertspecialcharactercommand.js create mode 100644 tests/manual/specialcharacters.html create mode 100644 tests/manual/specialcharacters.js create mode 100644 tests/manual/specialcharacters.md create mode 100644 tests/specialcharacters.js diff --git a/src/insertspecialcharactercommand.js b/src/insertspecialcharactercommand.js index f84bc75..9bf1cc2 100644 --- a/src/insertspecialcharactercommand.js +++ b/src/insertspecialcharactercommand.js @@ -27,13 +27,9 @@ export default class InsertSpecialCharacterCommand extends Command { * @member {module:typing/inputcommand~InputCommand} #_inputCommand */ this._inputCommand = editor.commands.get( 'input' ); - } - /** - * @inheritDoc - */ - refresh() { - this.isEnabled = this._inputCommand.isEnabled; + // Use the state of `Input` command to determine whether the special characters could be inserted. + this.bind( 'isEnabled' ).to( this._inputCommand, 'isEnabled' ); } /** diff --git a/src/specialcharacters.js b/src/specialcharacters.js index aea9721..fa7205c 100644 --- a/src/specialcharacters.js +++ b/src/specialcharacters.js @@ -25,7 +25,7 @@ export default class SpecialCharacters extends Plugin { * Registered characters. A pair of a character name and its symbol. * * @private - * @member {Map.} + * @member {Map.} #_characters */ this._characters = new Map(); @@ -33,7 +33,7 @@ export default class SpecialCharacters extends Plugin { * Registered groups. Each group contains a collection with symbol names. * * @private - * @member {Map.>} + * @member {Map.>} #_groups */ this._groups = new Map(); } @@ -42,7 +42,7 @@ export default class SpecialCharacters extends Plugin { * @inheritDoc */ static get requires() { - return [ SpecialCharactersUI, Typing ]; + return [ Typing, SpecialCharactersUI ]; } /** @@ -53,7 +53,7 @@ export default class SpecialCharacters extends Plugin { } /** - * Adds a collection of special characters to specified group. + * Adds a collection of special characters to specified group. A title of a special character must be unique. * * @param {String} groupName * @param {Array.<~SpecialCharacterDefinition>} items @@ -64,9 +64,14 @@ export default class SpecialCharacters extends Plugin { for ( const item of items ) { if ( this._characters.has( item.title ) ) { /** + * The provided title for a special character is already. Titles for special characters must be unique. + * * @error specialcharacters-duplicated-character-name + * @param {~SpecialCharacterDefinition} item The invalid special character definition. */ - throw new CKEditorError( 'specialcharacters-duplicated-character-name', null ); + throw new CKEditorError( + 'specialcharacters-duplicated-character-name: Duplicated special character title.', null, { item } + ); } group.add( item.title ); @@ -75,6 +80,8 @@ export default class SpecialCharacters extends Plugin { } /** + * Returns iterator of special characters groups. + * * @returns {Iterable.} */ getGroups() { @@ -82,10 +89,12 @@ export default class SpecialCharacters extends Plugin { } /** + * Returns a collection of symbol names (titles). + * * @param {String} groupName * @returns {Set|undefined} */ - getCharacterForGroup( groupName ) { + getCharactersForGroup( groupName ) { return this._groups.get( groupName ); } @@ -105,6 +114,7 @@ export default class SpecialCharacters extends Plugin { * @param {String} groupName A name of group to create. */ _getGroup( groupName ) { + /* istanbul ignore else */ if ( !this._groups.has( groupName ) ) { this._groups.set( groupName, new Set() ); } @@ -113,6 +123,9 @@ export default class SpecialCharacters extends Plugin { } } +// TODO: Make an interface for "SpecialCharacters" class. +// It should provide methods: `addItems()`, `getGroups()`, `getCharactersForGroup()`, `getCharacter()`. + /** * @typedef {Object} module:special-characters/specialcharacters~SpecialCharacterDefinition * diff --git a/src/specialcharactersui.js b/src/specialcharactersui.js index cf1f972..c7c6e42 100644 --- a/src/specialcharactersui.js +++ b/src/specialcharactersui.js @@ -71,10 +71,10 @@ export default class SpecialCharactersUI extends Plugin { function printCharacters( selectView ) { const groupName = selectView.element.value; - const characters = specialCharacterPlugin.getCharacterForGroup( groupName ); + const characters = specialCharacterPlugin.getCharactersForGroup( groupName ); console.log( { groupName, characters } ); - console.log( 'Rysuj!' ); + console.log( 'Draw!' ); } } } diff --git a/tests/insertspecialcharactercommand.js b/tests/insertspecialcharactercommand.js new file mode 100644 index 0000000..3adb4ff --- /dev/null +++ b/tests/insertspecialcharactercommand.js @@ -0,0 +1,109 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/* globals document */ + +import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor'; +import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph'; +import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils'; +import { setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model'; +import SpecialCharacters from '../src/specialcharacters'; + +describe( 'InsertSpecialCharacterCommand', () => { + let editor, model, editorElement, command; + + testUtils.createSinonSandbox(); + + beforeEach( () => { + editorElement = document.createElement( 'div' ); + document.body.appendChild( editorElement ); + + return ClassicTestEditor + .create( editorElement, { + plugins: [ Paragraph, SpecialCharacters ] + } ) + .then( newEditor => { + editor = newEditor; + model = editor.model; + command = editor.commands.get( 'specialCharacters' ); + + editor.plugins.get( 'SpecialCharacters' ).addItems( 'Arrows', [ + { title: 'arrow left', character: '←' }, + { title: 'arrow right', character: '→' } + ] ); + } ); + } ); + + afterEach( () => { + return editor.destroy() + .then( () => { + editorElement.remove(); + } ); + } ); + + describe( 'isEnabled', () => { + it( 'should be bound to InputCommand#isEnables', () => { + const inputCommand = editor.commands.get( 'input' ); + + inputCommand.isEnabled = true; + expect( command.isEnabled ).to.equal( true ); + + inputCommand.isEnabled = false; + expect( command.isEnabled ).to.equal( false ); + } ); + } ); + + describe( 'execute()', () => { + it( 'should create a single batch', () => { + setModelData( model, 'foo[]' ); + + const spy = sinon.spy(); + + model.document.on( 'change', spy ); + + command.execute( { item: 'arrow left' } ); + + sinon.assert.calledOnce( spy ); + } ); + + it( 'executes InputCommand#execute()', () => { + const inputCommand = editor.commands.get( 'input' ); + + setModelData( model, 'foo[]' ); + + const spy = sinon.stub( inputCommand, 'execute' ); + + command.execute( { item: 'arrow left' } ); + + sinon.assert.calledWithExactly( spy, { text: '←' } ); + + spy.restore(); + } ); + + it( 'does nothing if specified object is invalid', () => { + setModelData( model, 'foo[]' ); + + const spy = sinon.spy(); + + model.document.on( 'change', spy ); + + command.execute( { foo: 'arrow left' } ); + + sinon.assert.notCalled( spy ); + } ); + + it( 'does nothing if specified item name does not exist', () => { + setModelData( model, 'foo[]' ); + + const spy = sinon.spy(); + + model.document.on( 'change', spy ); + + command.execute( { item: 'arrow up' } ); + + sinon.assert.notCalled( spy ); + } ); + } ); +} ); diff --git a/tests/manual/specialcharacters.html b/tests/manual/specialcharacters.html new file mode 100644 index 0000000..4805f37 --- /dev/null +++ b/tests/manual/specialcharacters.html @@ -0,0 +1,4 @@ +

Editor

+
+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris viverra ipsum a sapien accumsan, in fringilla ligula congue. Suspendisse eget urna ac nulla dignissim sollicitudin vel non sem. Curabitur consequat nisi vel orci mollis tincidunt. Nam eget sapien non ligula aliquet commodo vel sed lectus. Sed arcu orci, vehicula vitae augue lobortis, posuere tristique nisl. Sed eleifend venenatis magna in elementum. Cras sit amet arcu mi. Suspendisse vel purus a ex maximus pharetra quis in massa. Mauris pellentesque leo sed mi faucibus molestie. Cras felis justo, volutpat sed erat at, lacinia fermentum nunc. Pellentesque est leo, dignissim at odio sit amet, vulputate placerat turpis. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Nulla eget pharetra enim. Donec fermentum ligula est, quis ultrices arcu tristique eu. Vivamus a dui sem.

+
diff --git a/tests/manual/specialcharacters.js b/tests/manual/specialcharacters.js new file mode 100644 index 0000000..c943f7f --- /dev/null +++ b/tests/manual/specialcharacters.js @@ -0,0 +1,59 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/* globals console, 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 SpecialCharacters from '../../src/specialcharacters'; +import SpecialCharactersMathematical from '../../src/specialcharactersmathematical'; +import SpecialCharactersArrows from '../../src/specialcharactersarrows'; + +ClassicEditor + .create( document.querySelector( '#editor' ), { + cloudServices: CS_CONFIG, + plugins: [ ArticlePluginSet, ImageUpload, EasyImage, SpecialCharacters, SpecialCharactersMathematical, SpecialCharactersArrows ], + toolbar: [ + 'heading', + '|', + 'bold', 'italic', 'numberedList', 'bulletedList', + '|', + 'link', 'blockquote', 'imageUpload', 'insertTable', 'mediaEmbed', + '|', + 'undo', 'redo', + '|', + 'specialCharacters' + ], + image: { + styles: [ + 'full', + 'alignLeft', + 'alignRight' + ], + toolbar: [ + 'imageStyle:alignLeft', + 'imageStyle:full', + 'imageStyle:alignRight', + '|', + 'imageTextAlternative' + ] + }, + table: { + contentToolbar: [ + 'tableColumn', + 'tableRow', + 'mergeTableCells' + ] + }, + } ) + .then( editor => { + window.editor = editor; + } ) + .catch( err => { + console.error( err.stack ); + } ); diff --git a/tests/manual/specialcharacters.md b/tests/manual/specialcharacters.md new file mode 100644 index 0000000..f4a3884 --- /dev/null +++ b/tests/manual/specialcharacters.md @@ -0,0 +1,4 @@ +## Testing + +1. Use the special characters icon in order to add a special character to the editor. +1. Use the select in order to change category of displayed special characters. diff --git a/tests/specialcharacters.js b/tests/specialcharacters.js new file mode 100644 index 0000000..99dd454 --- /dev/null +++ b/tests/specialcharacters.js @@ -0,0 +1,107 @@ +/** + * @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 SpecialCharacters from '../src/specialcharacters'; +import SpecialCharactersUI from '../src/specialcharactersui'; +import Typing from '@ckeditor/ckeditor5-typing/src/typing'; +import { expectToThrowCKEditorError } from '@ckeditor/ckeditor5-utils/tests/_utils/utils'; + +describe( 'SpecialCharacters', () => { + let plugin; + + beforeEach( () => { + plugin = new SpecialCharacters( {} ); + } ); + + it( 'should require Typing and SpecialCharactersUI', () => { + expect( SpecialCharacters.requires ).to.deep.equal( [ Typing, SpecialCharactersUI ] ); + } ); + + it( 'should be named', () => { + expect( SpecialCharacters.pluginName ).to.equal( 'SpecialCharacters' ); + } ); + + describe( 'addItems()', () => { + it( 'adds special characters to the available symbols', () => { + plugin.addItems( 'Arrows', [ + { title: 'arrow left', character: '←' }, + { title: 'arrow right', character: '→' } + ] ); + + expect( plugin._groups.size ).to.equal( 1 ); + expect( plugin._groups.has( 'Arrows' ) ).to.equal( true ); + + expect( plugin._characters.size ).to.equal( 2 ); + expect( plugin._characters.has( 'arrow left' ) ).to.equal( true ); + expect( plugin._characters.has( 'arrow right' ) ).to.equal( true ); + } ); + + it( 'throw an error when a title is not a unique value', () => { + expectToThrowCKEditorError( () => { + plugin.addItems( 'Arrows', [ + { title: 'arrow left', character: '←' }, + { title: 'arrow left', character: '←' } + ] ); + }, /^specialcharacters-duplicated-character-name/ ) + } ); + } ); + + describe( 'getGroups()', () => { + it( 'returns iterator of defined groups', () => { + plugin.addItems( 'Arrows', [ + { title: 'arrow left', character: '←' } + ] ); + + plugin.addItems( 'Mathematical', [ + { title: 'precedes', character: '≺' }, + { title: 'succeeds', character: '≻' } + ] ); + + const groups = [ ...plugin.getGroups() ]; + expect( groups ).to.deep.equal( [ 'Arrows', 'Mathematical' ] ); + } ); + } ); + + describe( 'getCharactersForGroup()', () => { + it( 'returns a collection of defined special characters names', () => { + plugin.addItems( 'Mathematical', [ + { title: 'precedes', character: '≺' }, + { title: 'succeeds', character: '≻' } + ] ); + + const characters = plugin.getCharactersForGroup( 'Mathematical' ); + + expect( characters.size ).to.equal( 2 ); + expect( characters.has( 'precedes' ) ).to.equal( true ); + expect( characters.has( 'succeeds' ) ).to.equal( true ); + } ); + + it( 'returns undefined for non-existing group', () => { + plugin.addItems( 'Mathematical', [ + { title: 'precedes', character: '≺' }, + { title: 'succeeds', character: '≻' } + ] ); + + const characters = plugin.getCharactersForGroup( 'Foo' ); + + expect( characters ).to.be.undefined; + } ); + } ); + + describe( 'getCharacter()', () => { + it( 'returns a collection of defined special characters names', () => { + plugin.addItems( 'Mathematical', [ + { title: 'precedes', character: '≺' }, + { title: 'succeeds', character: '≻' } + ] ); + + expect( plugin.getCharacter( 'succeeds' ) ).to.equal( '≻' ); + } ); + + it( 'returns undefined for non-existing character', () => { + expect( plugin.getCharacter( 'succeeds' ) ).to.be.undefined; + } ); + } ); +} ); From f2866176ac9acb5509eea4b693e04fd4838e6077 Mon Sep 17 00:00:00 2001 From: Kamil Piechaczek Date: Tue, 12 Nov 2019 09:28:22 +0100 Subject: [PATCH 07/43] Renamed console.log. --- src/specialcharactersui.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/specialcharactersui.js b/src/specialcharactersui.js index c7c6e42..6b80d86 100644 --- a/src/specialcharactersui.js +++ b/src/specialcharactersui.js @@ -47,7 +47,7 @@ export default class SpecialCharactersUI extends Plugin { // When a special character was clicked, insert it to the editor. dropdownView.on( 'execute', ( evt, data ) => { - console.log( 'Kliknąłem.', data ); + console.log( 'Clicked.', data ); // command.execute( { item: data } ); } ); From 9664d03468e2c73ecd512aa9e4b3f06b69dc7bf8 Mon Sep 17 00:00:00 2001 From: Kamil Piechaczek Date: Tue, 12 Nov 2019 09:29:11 +0100 Subject: [PATCH 08/43] Added a testing special characters. --- src/specialcharactersarrows.js | 36 ++++++++++++++++++++++++++++ src/specialcharactersmathematical.js | 36 ++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 src/specialcharactersarrows.js create mode 100644 src/specialcharactersmathematical.js diff --git a/src/specialcharactersarrows.js b/src/specialcharactersarrows.js new file mode 100644 index 0000000..e378634 --- /dev/null +++ b/src/specialcharactersarrows.js @@ -0,0 +1,36 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/** + * @module special-characters/specialcharacters + */ + +import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; + +export default class SpecialCharactersArrows extends Plugin { + init() { + this.editor.plugins.get( 'SpecialCharacters' ).addItems( 'Arrows', [ + { + title: 'arrow left', + character: '←' + }, + { + title: 'arrow right', + character: '→' + } + ] ); + + this.editor.plugins.get( 'SpecialCharacters' ).addItems( 'Arrows', [ + { + title: 'arrow up', + character: '↑' + }, + { + title: 'arrow down', + character: '↓' + } + ] ); + } +} diff --git a/src/specialcharactersmathematical.js b/src/specialcharactersmathematical.js new file mode 100644 index 0000000..6eee40b --- /dev/null +++ b/src/specialcharactersmathematical.js @@ -0,0 +1,36 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/** + * @module special-characters/specialcharacters + */ + +import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; + +export default class SpecialCharactersMathematical extends Plugin { + init() { + this.editor.plugins.get( 'SpecialCharacters' ).addItems( 'Mathematical', [ + { + title: 'precedes', + character: '≺' + }, + { + title: 'succeeds', + character: '≻' + } + ] ); + + this.editor.plugins.get( 'SpecialCharacters' ).addItems( 'Mathematical', [ + { + title: 'precedes or equal to', + character: '≼' + }, + { + title: 'succeeds or equal to', + character: '≽' + } + ] ); + } +} From 1d377713030820533890ce661394ac56c0d6ad41 Mon Sep 17 00:00:00 2001 From: Kamil Piechaczek Date: Tue, 19 Nov 2019 15:01:23 +0100 Subject: [PATCH 09/43] Added some special characters. --- src/specialcharactersarrows.js | 37 ++++++++++--------- src/specialcharactersmathematical.js | 53 ++++++++++++++++++---------- 2 files changed, 52 insertions(+), 38 deletions(-) diff --git a/src/specialcharactersarrows.js b/src/specialcharactersarrows.js index e378634..329e6e1 100644 --- a/src/specialcharactersarrows.js +++ b/src/specialcharactersarrows.js @@ -12,25 +12,24 @@ import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; export default class SpecialCharactersArrows extends Plugin { init() { this.editor.plugins.get( 'SpecialCharacters' ).addItems( 'Arrows', [ - { - title: 'arrow left', - character: '←' - }, - { - title: 'arrow right', - character: '→' - } - ] ); - - this.editor.plugins.get( 'SpecialCharacters' ).addItems( 'Arrows', [ - { - title: 'arrow up', - character: '↑' - }, - { - title: 'arrow down', - character: '↓' - } + { title: 'leftwards double arrow', character: '⇐' }, + { title: 'rightwards double arrow', character: '⇒' }, + { title: 'upwards double arrow', character: '⇑' }, + { title: 'downwards double arrow', character: '⇓' }, + { title: 'leftwards dashed arrow', character: '⇠' }, + { title: 'rightwards dashed arrow', character: '⇢' }, + { title: 'upwards dashed arrow', character: '⇡' }, + { title: 'downwards dashed arrow', character: '⇣' }, + { title: 'leftwards arrow to bar', character: '⇤' }, + { title: 'rightwards arrow to bar', character: '⇥' }, + { title: 'upwards arrow to bar', character: '⤒' }, + { title: 'downwards arrow to bar', character: '⤓' }, + { title: 'up down arrow with base', character: '↨' }, + { title: 'back with leftwards arrow above', character: '🔙' }, + { title: 'end with leftwards arrow above', character: '🔚' }, + { title: 'on with exclamation mark with left right arrow above', character: '🔛' }, + { title: 'soon with rightwards arrow above', character: '🔜' }, + { title: 'top with upwards arrow above', character: '🔝' } ] ); } } diff --git a/src/specialcharactersmathematical.js b/src/specialcharactersmathematical.js index 6eee40b..7072362 100644 --- a/src/specialcharactersmathematical.js +++ b/src/specialcharactersmathematical.js @@ -12,25 +12,40 @@ import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; export default class SpecialCharactersMathematical extends Plugin { init() { this.editor.plugins.get( 'SpecialCharacters' ).addItems( 'Mathematical', [ - { - title: 'precedes', - character: '≺' - }, - { - title: 'succeeds', - character: '≻' - } - ] ); - - this.editor.plugins.get( 'SpecialCharacters' ).addItems( 'Mathematical', [ - { - title: 'precedes or equal to', - character: '≼' - }, - { - title: 'succeeds or equal to', - character: '≽' - } + { title: 'greek small letter alpha', character: 'α' }, + { title: 'greek small letter beta', character: 'β' }, + { title: 'greek small letter delta', character: 'δ' }, + { title: 'greek small letter epsilon', character: 'ε' }, + { title: 'greek small letter theta', character: 'θ' }, + { title: 'greek small letter lamda', character: 'λ' }, + { title: 'greek small letter mu', character: 'μ' }, + { title: 'greek small letter pi', character: 'π' }, + { title: 'greek small letter phi', character: 'φ' }, + { title: 'greek small letter psi', character: 'ψ' }, + { title: 'greek capital letter omega', character: 'Ω' }, + { title: 'precedes', character: '≺' }, + { title: 'succeeds', character: '≻' }, + { title: 'precedes or equal to', character: '≼' }, + { title: 'succeeds or equal to', character: '≽' }, + { title: 'double precedes', character: '⪻' }, + { title: 'double succeeds', character: '⪼' }, + { title: 'less-than', character: '<' }, + { title: 'greater-than', character: '>' }, + { title: 'less-than or equal to', character: '≤' }, + { title: 'greater-than or equal to', character: '≥' }, + { title: 'equals colon', character: '≕' }, + { title: 'double colon equal', character: '⩴' }, + { title: 'identical to', character: '≡' }, + { title: 'not identical to', character: '≢' }, + { title: 'almost equal to', character: '≈' }, + { title: 'not almost equal to', character: '≉' }, + { title: 'almost equal or equal to', character: '≊' }, + { title: 'triple tilde', character: '≋' }, + { title: 'true', character: '⊨' }, + { title: 'not true', character: '⊭' }, + { title: 'for all', character: '∀' }, + { title: 'complement', character: '∁' }, + { title: 'there exists', character: '∃' } ] ); } } From 8412308a7c701f47e79d5c87f600e3044f1c25f2 Mon Sep 17 00:00:00 2001 From: Kamil Piechaczek Date: Tue, 19 Nov 2019 15:02:02 +0100 Subject: [PATCH 10/43] Tiles are displaed correctly. --- src/specialcharactersui.js | 64 +++++++---- src/ui/specialcharactersselectview.js | 83 +++++++++++++ src/ui/specialcharacterstableview.js | 160 ++++++++++++++++++++++++++ 3 files changed, 285 insertions(+), 22 deletions(-) create mode 100644 src/ui/specialcharactersselectview.js create mode 100644 src/ui/specialcharacterstableview.js diff --git a/src/specialcharactersui.js b/src/specialcharactersui.js index 6b80d86..00501f4 100644 --- a/src/specialcharactersui.js +++ b/src/specialcharactersui.js @@ -11,7 +11,8 @@ import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; import { createDropdown } from '@ckeditor/ckeditor5-ui/src/dropdown/utils'; import specialCharactersIcon from '../theme/icons/specialcharacters.svg'; import InsertSpecialCharacterCommand from './insertspecialcharactercommand'; -import SelectView from '@ckeditor/ckeditor5-ui/src/selectview/selectview'; +import SpecialCharactersTableView from './ui/specialcharacterstableview'; +import SpecialCharactersSelectView from './ui/specialcharactersselectview'; /** * The special characters UI plugin. @@ -23,58 +24,77 @@ export default class SpecialCharactersUI extends Plugin { const editor = this.editor; const t = editor.t; const specialCharacterPlugin = editor.plugins.get( 'SpecialCharacters' ); + const label = t( 'Special characters' ); const command = new InsertSpecialCharacterCommand( editor ); editor.commands.add( 'specialCharacters', command ); // Add the `specialCharacters` dropdown button to feature components. editor.ui.componentFactory.add( 'specialCharacters', locale => { - // Prepare all special characters groups for displaying in the select view. - const specialCharactersGroups = [ ...specialCharacterPlugin.getGroups() ] - .map( groupName => ( { label: groupName, value: groupName } ) ); - + // Prepare the dropdown element. const dropdownView = createDropdown( locale ); - const selectView = new SelectView( locale, specialCharactersGroups ); dropdownView.buttonView.set( { - label: t( 'Special characters' ), + label, icon: specialCharactersIcon, tooltip: true } ); dropdownView.bind( 'isEnabled' ).to( command ); - dropdownView.panelView.children.add( selectView ); - // When a special character was clicked, insert it to the editor. - dropdownView.on( 'execute', ( evt, data ) => { - console.log( 'Clicked.', data ); + const specialCharactersSelectView = new SpecialCharactersSelectView( locale, { + labelText: label, + selectOptions: getSelectViewOptions() + } ); + + const symbolTableView = new SpecialCharactersTableView( locale, { + columns: 10 // TODO: Read from config. + } ); + + symbolTableView.delegate( 'execute' ).to( dropdownView, 'execute' ); - // command.execute( { item: data } ); + // Insert a special character when a tile was clicked. + dropdownView.on( 'execute', ( evt, data ) => { + command.execute( { item: data.title } ); } ); + // Draw special characters tiles when the dropdown is open. dropdownView.on( 'change:isOpen', ( evt, name, isVisible ) => { if ( !isVisible ) { return; } - // Draw special characters tiles when the dropdown is opened. - printCharacters( selectView ); + printCharacters( specialCharactersSelectView, symbolTableView.symbolGridView ); } ); - // Draw special characters when a user changed a category. - selectView.on( 'input', () => { - printCharacters( selectView ); + // Draw special characters tiles for specified category (when a user has changed it). + specialCharactersSelectView.on( 'input', () => { + printCharacters( specialCharactersSelectView, symbolTableView.symbolGridView ); } ); + dropdownView.panelView.children.add( specialCharactersSelectView ); + dropdownView.panelView.children.add( symbolTableView ); + return dropdownView; } ); - function printCharacters( selectView ) { - const groupName = selectView.element.value; - const characters = specialCharacterPlugin.getCharactersForGroup( groupName ); + function printCharacters( selectView, gridView ) { + // TODO: Keyboard navigation. + gridView.items.clear(); + + const groupName = selectView.value; + const characterTitles = specialCharacterPlugin.getCharactersForGroup( groupName ); + + for ( const title of characterTitles ) { + const character = specialCharacterPlugin.getCharacter( title ); - console.log( { groupName, characters } ); - console.log( 'Draw!' ); + gridView.items.add( gridView.createSymbolTile( character, title ) ); + } + } + + function getSelectViewOptions() { + return [ ...specialCharacterPlugin.getGroups() ] + .map( groupName => ( { label: groupName, value: groupName } ) ); } } } diff --git a/src/ui/specialcharactersselectview.js b/src/ui/specialcharactersselectview.js new file mode 100644 index 0000000..b97c64a --- /dev/null +++ b/src/ui/specialcharactersselectview.js @@ -0,0 +1,83 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/** + * @module special-characters/ui/specialcharactersselectview + */ + +import View from '@ckeditor/ckeditor5-ui/src/view'; +import LabelView from '@ckeditor/ckeditor5-ui/src/label/labelview'; +import SelectView from '@ckeditor/ckeditor5-ui/src/selectview/selectview'; +import uid from '@ckeditor/ckeditor5-utils/src/uid'; + +/** + * @extends module:ui/view~View + */ +export default class SpecialCharactersSelectView extends View { + /** + * Creates a view to be inserted as a child of {@link module:ui/dropdown/dropdownview~DropdownView}. + * + * @param {module:utils/locale~Locale} [locale] The localization services instance. + * @param {Object} options + * @param {String} options.labelText A label for the select element. + * @param {Array.} options.selectOptions Options to chose in the select view. + */ + constructor( locale, options ) { + super( locale ); + + const inputUid = `ck-select-${ uid() }`; + + /** + * A label for the select view. + * + * @member {module:ui/label/labelview~LabelView} + */ + this.label = new LabelView( this.locale ); + + /** + * Select view for changing a category of special characters. + * + * @member {module:ui/selectview/selectview~SelectView} + */ + this.selectView = new SelectView( this.locale, options.selectOptions ); + this.selectView.delegate( 'input' ).to( this ); + + this.label.for = inputUid; + this.label.text = options.labelText; + this.label.extendTemplate( { + attributes: { + class: [ + 'ck', + 'ck-symbol-grid__label' + ] + } + } ); + + this.selectView.id = inputUid; + + this.setTemplate( { + tag: 'div', + attributes: { + class: [ + 'ck', + 'ck-grid-table' + ] + }, + children: [ + this.label, + this.selectView + ] + } ); + } + + /** + * Returns a value from the select view. + * + * @returns {String} + */ + get value() { + return this.selectView.element.value; + } +} diff --git a/src/ui/specialcharacterstableview.js b/src/ui/specialcharacterstableview.js new file mode 100644 index 0000000..95af87e --- /dev/null +++ b/src/ui/specialcharacterstableview.js @@ -0,0 +1,160 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/** + * @module special-characters/ui/specialcharacterstableview + */ + +import View from '@ckeditor/ckeditor5-ui/src/view'; +import FocusTracker from '@ckeditor/ckeditor5-utils/src/focustracker'; +import FocusCycler from '@ckeditor/ckeditor5-ui/src/focuscycler'; +import KeystrokeHandler from '@ckeditor/ckeditor5-utils/src/keystrokehandler'; +import SymbolGridView from '@ckeditor/ckeditor5-ui/src/symbolgrid/symbolgridview'; + +// TODO: Keyboard navigation does not work. + +/** + * @extends module:ui/view~View + */ +export default class SpecialCharactersTableView extends View { + /** + * Creates a view to be inserted as a child of {@link module:ui/dropdown/dropdownview~DropdownView}. + * + * @param {module:utils/locale~Locale} [locale] The localization services instance. + * @param {Object} config The configuration object. + * @param {Array.} config.symbolDefinitions An array with + * definitions of special characters to be displayed in the table. + * @param {Number} config.columns The number of columns in the color grid. + */ + constructor( locale, { symbolDefinitions = [], columns } ) { + super( locale ); + + /** + * A collection of the children of the table. + * + * @readonly + * @member {module:ui/viewcollection~ViewCollection} + */ + this.items = this.createCollection(); + + /** + * Tracks information about the DOM focus in the list. + * + * @readonly + * @member {module:utils/focustracker~FocusTracker} + */ + this.focusTracker = new FocusTracker(); + + /** + * An instance of the {@link module:utils/keystrokehandler~KeystrokeHandler}. + * + * @readonly + * @member {module:utils/keystrokehandler~KeystrokeHandler} + */ + this.keystrokes = new KeystrokeHandler(); + + /** + * The number of columns in the special characters grid. + * + * @type {Number} + */ + this.columns = columns; + + /** + * An array with definitions of special characters to be displayed in the table. + * + * @member {Array.} + */ + this.symbolDefinitions = symbolDefinitions; + + /** + * Preserves the reference to {@link module:ui/symbolgrid/symbolgrid~SymbolGridView} used to create + * the default (static) symbol set. + * + * @readonly + * @member {module:ui/symbolgrid/symbolgrid~SymbolGridView} + */ + this.symbolGridView = this._createSymbolGridView(); + + /** + * Helps cycling over focusable {@link #items} in the list. + * + * @readonly + * @protected + * @member {module:ui/focuscycler~FocusCycler} + */ + this._focusCycler = new FocusCycler( { + focusables: this.items, + focusTracker: this.focusTracker, + keystrokeHandler: this.keystrokes, + actions: { + // Navigate list items backwards using the Arrow Up key. + focusPrevious: 'arrowup', + + // Navigate list items forwards using the Arrow Down key. + focusNext: 'arrowdown', + } + } ); + + this.setTemplate( { + tag: 'div', + attributes: { + class: [ + 'ck', + 'ck-grid-table' + ] + }, + children: this.items + } ); + + this.items.add( this.symbolGridView ); + } + + /** + * @inheritDoc + */ + render() { + super.render(); + + // Items added before rendering should be known to the #focusTracker. + for ( const item of this.items ) { + this.focusTracker.add( item.element ); + } + + // Start listening for the keystrokes coming from #element. + this.keystrokes.listenTo( this.element ); + } + + /** + * Focuses the first focusable element in {@link #items}. + */ + focus() { + this._focusCycler.focusFirst(); + } + + /** + * Focuses the last focusable element in {@link #items}. + */ + focusLast() { + this._focusCycler.focusLast(); + } + + /** + * Creates a static symbol table grid based on the editor configuration. + * + * @private + * @returns {module:ui/symbolgrid/symbolgrid~SymbolGridView} + */ + _createSymbolGridView() { + const symbolGrid = new SymbolGridView( this.locale, { + symbolDefinitions: this.symbolDefinitions, + columns: this.columns + } ); + + symbolGrid.delegate( 'execute' ).to( this ); + + return symbolGrid; + } +} From a87432b54e56fc12895c0174b3b510e60c21d160 Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Thu, 21 Nov 2019 08:37:18 +0100 Subject: [PATCH 11/43] Internal: Moved SymbolGridView into special characters. Also reused common GridView class. --- src/ui/specialcharacterstableview.js | 2 +- src/ui/symbolgridview.js | 74 ++++++++++++++++++++++++++++ src/ui/symboltileview.js | 35 +++++++++++++ 3 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 src/ui/symbolgridview.js create mode 100644 src/ui/symboltileview.js diff --git a/src/ui/specialcharacterstableview.js b/src/ui/specialcharacterstableview.js index 95af87e..d4a0238 100644 --- a/src/ui/specialcharacterstableview.js +++ b/src/ui/specialcharacterstableview.js @@ -11,7 +11,7 @@ import View from '@ckeditor/ckeditor5-ui/src/view'; import FocusTracker from '@ckeditor/ckeditor5-utils/src/focustracker'; import FocusCycler from '@ckeditor/ckeditor5-ui/src/focuscycler'; import KeystrokeHandler from '@ckeditor/ckeditor5-utils/src/keystrokehandler'; -import SymbolGridView from '@ckeditor/ckeditor5-ui/src/symbolgrid/symbolgridview'; +import SymbolGridView from './symbolgridview'; // TODO: Keyboard navigation does not work. diff --git a/src/ui/symbolgridview.js b/src/ui/symbolgridview.js new file mode 100644 index 0000000..054a8e7 --- /dev/null +++ b/src/ui/symbolgridview.js @@ -0,0 +1,74 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/** + * @module special-characters/ui/symbolgridview + */ + +import SymbolTileView from './symboltileview'; +import GridView from '@ckeditor/ckeditor5-ui/src/gridview'; + +/** + * A grid of {@link module:special-characters/ui/symboltileview~symbolTileView symbol tiles}. + * + * @extends module:ui/grid~Grid + */ +export default class SymbolGridView extends GridView { + /** + * Creates an instance of a symbol grid containing {@link module:special-characters/ui/symboltileview~symbolTileView tiles}. + * + * @param {module:utils/locale~Locale} [locale] The localization services instance. + * @param {Object} options Component configuration + * @param {Array.} [options.symbolDefinitions] Array with definitions + * required to create the {@link module:special-characters/ui/symboltileview~symbolTileView tiles}. + * @param {Number} options.columns A number of columns to display the tiles. + */ + constructor( locale, options ) { + super( locale, options ); + + const symbolDefinitions = options && options.symbolDefinitions || []; + const viewStyleAttribute = {}; + + if ( options && options.columns ) { + viewStyleAttribute.gridTemplateColumns = `repeat( ${ options.columns }, 1fr)`; + } + + symbolDefinitions.forEach( item => { + const symbolTile = this.createSymbolTile( item.character, item.title ); + + this.items.add( symbolTile ); + } ); + + this.extendTemplate( { + attributes: { + class: [ 'ck-symbol-grid' ] + } + } ); + } + + /** + * Creates a new tile for the grid. + * + * @param {String} character A character that will be displayed on the button. + * @param {String} title A label that descrbied the character. + * @returns {module:special-characters/ui/symboltileview~SymbolTileView} + */ + createSymbolTile( character, title ) { + const symbolTile = new SymbolTileView(); + + symbolTile.set( { + symbol: character, + label: character, + tooltip: title, + withText: true + } ); + + symbolTile.on( 'execute', () => { + this.fire( 'execute', { title } ); + } ); + + return symbolTile; + } +} diff --git a/src/ui/symboltileview.js b/src/ui/symboltileview.js new file mode 100644 index 0000000..6de8a86 --- /dev/null +++ b/src/ui/symboltileview.js @@ -0,0 +1,35 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/** + * @module special-characters/ui/symboltileview + */ + +import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview'; + +/** + * @extends module:ui/button/buttonview~ButtonView + */ +export default class SymbolTileView extends ButtonView { + constructor( locale ) { + super( locale ); + + /** + * String representing a symbol shown as tile's value. + * + * @type {String} + */ + this.set( 'symbol' ); + + this.extendTemplate( { + attributes: { + class: [ + 'ck', + 'ck-symbol-grid__tile', + ] + } + } ); + } +} From 69db1ef5b8c526cb8e54955f8700634becfc4c17 Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Thu, 21 Nov 2019 12:09:25 +0100 Subject: [PATCH 12/43] Internal: CSS adjustments. --- src/ui/symbolgridview.js | 1 + theme/components/symbolgrid/symbolgrid.css | 8 ++++++++ 2 files changed, 9 insertions(+) create mode 100644 theme/components/symbolgrid/symbolgrid.css diff --git a/src/ui/symbolgridview.js b/src/ui/symbolgridview.js index 054a8e7..862592a 100644 --- a/src/ui/symbolgridview.js +++ b/src/ui/symbolgridview.js @@ -9,6 +9,7 @@ import SymbolTileView from './symboltileview'; import GridView from '@ckeditor/ckeditor5-ui/src/gridview'; +import '../../theme/components/symbolgrid/symbolgrid.css'; /** * A grid of {@link module:special-characters/ui/symboltileview~symbolTileView symbol tiles}. diff --git a/theme/components/symbolgrid/symbolgrid.css b/theme/components/symbolgrid/symbolgrid.css new file mode 100644 index 0000000..14c91e5 --- /dev/null +++ b/theme/components/symbolgrid/symbolgrid.css @@ -0,0 +1,8 @@ +/* + * Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + + .ck.ck-symbol-grid { + display: grid; +} \ No newline at end of file From 989ec679623c4d5347ec34fbe6c777513ae92b85 Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Fri, 22 Nov 2019 10:00:33 +0100 Subject: [PATCH 13/43] Docs: Fixed API references. --- src/ui/specialcharactersselectview.js | 6 +++--- src/ui/symbolgridview.js | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ui/specialcharactersselectview.js b/src/ui/specialcharactersselectview.js index b97c64a..cc4d0e1 100644 --- a/src/ui/specialcharactersselectview.js +++ b/src/ui/specialcharactersselectview.js @@ -9,7 +9,7 @@ import View from '@ckeditor/ckeditor5-ui/src/view'; import LabelView from '@ckeditor/ckeditor5-ui/src/label/labelview'; -import SelectView from '@ckeditor/ckeditor5-ui/src/selectview/selectview'; +import SelectView from '@ckeditor/ckeditor5-ui/src/select/selectview'; import uid from '@ckeditor/ckeditor5-utils/src/uid'; /** @@ -22,7 +22,7 @@ export default class SpecialCharactersSelectView extends View { * @param {module:utils/locale~Locale} [locale] The localization services instance. * @param {Object} options * @param {String} options.labelText A label for the select element. - * @param {Array.} options.selectOptions Options to chose in the select view. + * @param {Array.} options.selectOptions Options to chose in the select view. */ constructor( locale, options ) { super( locale ); @@ -39,7 +39,7 @@ export default class SpecialCharactersSelectView extends View { /** * Select view for changing a category of special characters. * - * @member {module:ui/selectview/selectview~SelectView} + * @member {module:ui/select/selectview~SelectView} */ this.selectView = new SelectView( this.locale, options.selectOptions ); this.selectView.delegate( 'input' ).to( this ); diff --git a/src/ui/symbolgridview.js b/src/ui/symbolgridview.js index 862592a..ca12cf0 100644 --- a/src/ui/symbolgridview.js +++ b/src/ui/symbolgridview.js @@ -8,13 +8,13 @@ */ import SymbolTileView from './symboltileview'; -import GridView from '@ckeditor/ckeditor5-ui/src/gridview'; +import GridView from '@ckeditor/ckeditor5-ui/src/grid/gridview'; import '../../theme/components/symbolgrid/symbolgrid.css'; /** * A grid of {@link module:special-characters/ui/symboltileview~symbolTileView symbol tiles}. * - * @extends module:ui/grid~Grid + * @extends module:ui/grid~GridView */ export default class SymbolGridView extends GridView { /** @@ -53,7 +53,7 @@ export default class SymbolGridView extends GridView { * Creates a new tile for the grid. * * @param {String} character A character that will be displayed on the button. - * @param {String} title A label that descrbied the character. + * @param {String} title A label that described the character. * @returns {module:special-characters/ui/symboltileview~SymbolTileView} */ createSymbolTile( character, title ) { From 1797d24095d2615495a40430fba4fffca9a89478 Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Mon, 25 Nov 2019 08:44:09 +0100 Subject: [PATCH 14/43] Internal: Inlined the base class into SymbolGridView type, so that it can inherit directly form view. On internal meeting we decided that due to tight schedule on the release we'll delay extracting a common grid class to a future release, to give ourself proper time to do that. --- src/ui/symbolgridview.js | 110 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 104 insertions(+), 6 deletions(-) diff --git a/src/ui/symbolgridview.js b/src/ui/symbolgridview.js index ca12cf0..d8903d8 100644 --- a/src/ui/symbolgridview.js +++ b/src/ui/symbolgridview.js @@ -7,16 +7,19 @@ * @module special-characters/ui/symbolgridview */ +import View from '@ckeditor/ckeditor5-ui/src/view'; import SymbolTileView from './symboltileview'; -import GridView from '@ckeditor/ckeditor5-ui/src/grid/gridview'; +import FocusTracker from '@ckeditor/ckeditor5-utils/src/focustracker'; +import FocusCycler from '@ckeditor/ckeditor5-ui/src/focuscycler'; +import KeystrokeHandler from '@ckeditor/ckeditor5-utils/src/keystrokehandler'; import '../../theme/components/symbolgrid/symbolgrid.css'; /** * A grid of {@link module:special-characters/ui/symboltileview~symbolTileView symbol tiles}. * - * @extends module:ui/grid~GridView + * @extends module:ui/view~View */ -export default class SymbolGridView extends GridView { +export default class SymbolGridView extends View { /** * Creates an instance of a symbol grid containing {@link module:special-characters/ui/symboltileview~symbolTileView tiles}. * @@ -27,7 +30,7 @@ export default class SymbolGridView extends GridView { * @param {Number} options.columns A number of columns to display the tiles. */ constructor( locale, options ) { - super( locale, options ); + super( locale ); const symbolDefinitions = options && options.symbolDefinitions || []; const viewStyleAttribute = {}; @@ -36,19 +39,114 @@ export default class SymbolGridView extends GridView { viewStyleAttribute.gridTemplateColumns = `repeat( ${ options.columns }, 1fr)`; } + /** + * Collection of the child tile views. + * + * @readonly + * @member {module:ui/viewcollection~ViewCollection} + */ + this.items = this.createCollection(); + + /** + * Tracks information about DOM focus in the grid. + * + * @readonly + * @member {module:utils/focustracker~FocusTracker} + */ + this.focusTracker = new FocusTracker(); + + /** + * Instance of the {@link module:utils/keystrokehandler~KeystrokeHandler}. + * + * @readonly + * @member {module:utils/keystrokehandler~KeystrokeHandler} + */ + this.keystrokes = new KeystrokeHandler(); + + /** + * Helps cycling over focusable {@link #items} in the grid. + * + * @readonly + * @protected + * @member {module:ui/focuscycler~FocusCycler} + */ + this._focusCycler = new FocusCycler( { + focusables: this.items, + focusTracker: this.focusTracker, + keystrokeHandler: this.keystrokes, + actions: { + // Navigate grid items backwards using the arrowup key. + focusPrevious: 'arrowleft', + + // Navigate grid items forwards using the arrowdown key. + focusNext: 'arrowright', + } + } ); + + this.items.on( 'add', ( evt, symbolTile ) => { + symbolTile.isOn = symbolTile.symbol === this.selectedsymbol; + } ); + symbolDefinitions.forEach( item => { const symbolTile = this.createSymbolTile( item.character, item.title ); this.items.add( symbolTile ); } ); - this.extendTemplate( { + this.setTemplate( { + tag: 'div', + children: this.items, attributes: { - class: [ 'ck-symbol-grid' ] + class: [ + 'ck', + 'ck-symbol-grid' + ], + style: viewStyleAttribute } } ); } + /** + * Focuses the first focusable in {@link #items}. + */ + focus() { + if ( this.items.length ) { + this.items.first.focus(); + } + } + + /** + * Focuses the last focusable in {@link #items}. + */ + focusLast() { + if ( this.items.length ) { + this.items.last.focus(); + } + } + + /** + * @inheritDoc + */ + render() { + super.render(); + + // Items added before rendering should be known to the #focusTracker. + for ( const item of this.items ) { + this.focusTracker.add( item.element ); + } + + this.items.on( 'add', ( evt, item ) => { + this.focusTracker.add( item.element ); + } ); + + this.items.on( 'remove', ( evt, item ) => { + this.focusTracker.remove( item.element ); + } ); + + // Start listening for the keystrokes coming from #element. + this.keystrokes.listenTo( this.element ); + } + /** * Creates a new tile for the grid. * From 3a47b575351529a8ab90008c3bff0becdbe30401 Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Mon, 25 Nov 2019 09:34:05 +0100 Subject: [PATCH 15/43] Docs: API docs corrections. --- src/specialcharacters.js | 5 +++-- src/ui/specialcharactersselectview.js | 2 +- src/ui/specialcharacterstableview.js | 6 +++--- src/ui/symbolgridview.js | 8 ++++---- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/specialcharacters.js b/src/specialcharacters.js index fa7205c..b2d4759 100644 --- a/src/specialcharacters.js +++ b/src/specialcharacters.js @@ -56,7 +56,7 @@ export default class SpecialCharacters extends Plugin { * Adds a collection of special characters to specified group. A title of a special character must be unique. * * @param {String} groupName - * @param {Array.<~SpecialCharacterDefinition>} items + * @param {Array.} items */ addItems( groupName, items ) { const group = this._getGroup( groupName ); @@ -67,7 +67,8 @@ export default class SpecialCharacters extends Plugin { * The provided title for a special character is already. Titles for special characters must be unique. * * @error specialcharacters-duplicated-character-name - * @param {~SpecialCharacterDefinition} item The invalid special character definition. + * @param {module:special-characters/specialcharacters~SpecialCharacterDefinition} item The invalid + * special character definition. */ throw new CKEditorError( 'specialcharacters-duplicated-character-name: Duplicated special character title.', null, { item } diff --git a/src/ui/specialcharactersselectview.js b/src/ui/specialcharactersselectview.js index cc4d0e1..4308fd9 100644 --- a/src/ui/specialcharactersselectview.js +++ b/src/ui/specialcharactersselectview.js @@ -22,7 +22,7 @@ export default class SpecialCharactersSelectView extends View { * @param {module:utils/locale~Locale} [locale] The localization services instance. * @param {Object} options * @param {String} options.labelText A label for the select element. - * @param {Array.} options.selectOptions Options to chose in the select view. + * @param {Array.} options.selectOptions Options to chose in the select view. */ constructor( locale, options ) { super( locale ); diff --git a/src/ui/specialcharacterstableview.js b/src/ui/specialcharacterstableview.js index d4a0238..14afa8a 100644 --- a/src/ui/specialcharacterstableview.js +++ b/src/ui/specialcharacterstableview.js @@ -70,11 +70,11 @@ export default class SpecialCharactersTableView extends View { this.symbolDefinitions = symbolDefinitions; /** - * Preserves the reference to {@link module:ui/symbolgrid/symbolgrid~SymbolGridView} used to create + * Preserves the reference to {@link module:special-characters/ui/symbolgridview~SymbolGridView} used to create * the default (static) symbol set. * * @readonly - * @member {module:ui/symbolgrid/symbolgrid~SymbolGridView} + * @member {module:special-characters/ui/symbolgridview~SymbolGridView} */ this.symbolGridView = this._createSymbolGridView(); @@ -145,7 +145,7 @@ export default class SpecialCharactersTableView extends View { * Creates a static symbol table grid based on the editor configuration. * * @private - * @returns {module:ui/symbolgrid/symbolgrid~SymbolGridView} + * @returns {module:special-characters/ui/symbolgridview~SymbolGridView} */ _createSymbolGridView() { const symbolGrid = new SymbolGridView( this.locale, { diff --git a/src/ui/symbolgridview.js b/src/ui/symbolgridview.js index d8903d8..8ae1dfe 100644 --- a/src/ui/symbolgridview.js +++ b/src/ui/symbolgridview.js @@ -15,18 +15,18 @@ import KeystrokeHandler from '@ckeditor/ckeditor5-utils/src/keystrokehandler'; import '../../theme/components/symbolgrid/symbolgrid.css'; /** - * A grid of {@link module:special-characters/ui/symboltileview~symbolTileView symbol tiles}. + * A grid of {@link module:special-characters/ui/symboltileview~SymbolTileView symbol tiles}. * * @extends module:ui/view~View */ export default class SymbolGridView extends View { /** - * Creates an instance of a symbol grid containing {@link module:special-characters/ui/symboltileview~symbolTileView tiles}. + * Creates an instance of a symbol grid containing {@link module:special-characters/ui/symboltileview~SymbolTileView tiles}. * * @param {module:utils/locale~Locale} [locale] The localization services instance. * @param {Object} options Component configuration - * @param {Array.} [options.symbolDefinitions] Array with definitions - * required to create the {@link module:special-characters/ui/symboltileview~symbolTileView tiles}. + * @param {Array.} [options.symbolDefinitions] Array with definitions + * required to create the {@link module:special-characters/ui/symboltileview~SymbolTileView tiles}. * @param {Number} options.columns A number of columns to display the tiles. */ constructor( locale, options ) { From ef196e543477916d7cdc16405b9d81aa50cfdaee Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Mon, 25 Nov 2019 10:16:57 +0100 Subject: [PATCH 16/43] Internal: Make shorter API docs line. --- src/ui/symbolgridview.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ui/symbolgridview.js b/src/ui/symbolgridview.js index 8ae1dfe..7f7ac41 100644 --- a/src/ui/symbolgridview.js +++ b/src/ui/symbolgridview.js @@ -25,8 +25,8 @@ export default class SymbolGridView extends View { * * @param {module:utils/locale~Locale} [locale] The localization services instance. * @param {Object} options Component configuration - * @param {Array.} [options.symbolDefinitions] Array with definitions - * required to create the {@link module:special-characters/ui/symboltileview~SymbolTileView tiles}. + * @param {Array.} [options.symbolDefinitions] Array with + * definitions required to create the {@link module:special-characters/ui/symboltileview~SymbolTileView tiles}. * @param {Number} options.columns A number of columns to display the tiles. */ constructor( locale, options ) { From 78c013e04114f5dafe79702c037d58433c5961a1 Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Mon, 25 Nov 2019 10:49:19 +0100 Subject: [PATCH 17/43] Internal: Extracted SpecialCharactersEditing plugin. --- src/specialcharacters.js | 4 +-- src/specialcharactersediting.js | 45 +++++++++++++++++++++++++++++++ src/specialcharactersui.js | 5 +--- tests/specialcharacters.js | 6 ++--- tests/specialcharactersediting.js | 35 ++++++++++++++++++++++++ 5 files changed, 86 insertions(+), 9 deletions(-) create mode 100644 src/specialcharactersediting.js create mode 100644 tests/specialcharactersediting.js diff --git a/src/specialcharacters.js b/src/specialcharacters.js index b2d4759..857ec68 100644 --- a/src/specialcharacters.js +++ b/src/specialcharacters.js @@ -8,9 +8,9 @@ */ import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; -import Typing from '@ckeditor/ckeditor5-typing/src/typing'; import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror'; import SpecialCharactersUI from './specialcharactersui'; +import SpecialCharactersEditing from './specialcharactersediting'; /** * The special characters feature. @@ -42,7 +42,7 @@ export default class SpecialCharacters extends Plugin { * @inheritDoc */ static get requires() { - return [ Typing, SpecialCharactersUI ]; + return [ SpecialCharactersEditing, SpecialCharactersUI ]; } /** diff --git a/src/specialcharactersediting.js b/src/specialcharactersediting.js new file mode 100644 index 0000000..bc13c81 --- /dev/null +++ b/src/specialcharactersediting.js @@ -0,0 +1,45 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/** + * @module special-characters/specialcharactersediting + */ + +import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; +import Typing from '@ckeditor/ckeditor5-typing/src/typing'; +import InsertSpecialCharacterCommand from './insertspecialcharactercommand'; + +/** + * Special characters editing plugin. + * + * It registers the {@link module:special-characters/insertspecialcharactercommand~InsertSpecialCharacterCommand Special Character} command. + * + * @extends module:core/plugin~Plugin + */ +export default class SpecialCharactersEditing extends Plugin { + /** + * @inheritDoc + */ + static get pluginName() { + return 'SpecialCharactersEditing'; + } + + /** + * @inheritDoc + */ + static get requires() { + return [ Typing ]; + } + + /** + * @inheritDoc + */ + init() { + const editor = this.editor; + + const command = new InsertSpecialCharacterCommand( editor ); + editor.commands.add( 'specialCharacters', command ); + } +} diff --git a/src/specialcharactersui.js b/src/specialcharactersui.js index 00501f4..6b83181 100644 --- a/src/specialcharactersui.js +++ b/src/specialcharactersui.js @@ -10,7 +10,6 @@ import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; import { createDropdown } from '@ckeditor/ckeditor5-ui/src/dropdown/utils'; import specialCharactersIcon from '../theme/icons/specialcharacters.svg'; -import InsertSpecialCharacterCommand from './insertspecialcharactercommand'; import SpecialCharactersTableView from './ui/specialcharacterstableview'; import SpecialCharactersSelectView from './ui/specialcharactersselectview'; @@ -25,9 +24,7 @@ export default class SpecialCharactersUI extends Plugin { const t = editor.t; const specialCharacterPlugin = editor.plugins.get( 'SpecialCharacters' ); const label = t( 'Special characters' ); - - const command = new InsertSpecialCharacterCommand( editor ); - editor.commands.add( 'specialCharacters', command ); + const command = editor.commands.get( 'specialCharacters' ); // Add the `specialCharacters` dropdown button to feature components. editor.ui.componentFactory.add( 'specialCharacters', locale => { diff --git a/tests/specialcharacters.js b/tests/specialcharacters.js index 99dd454..205df87 100644 --- a/tests/specialcharacters.js +++ b/tests/specialcharacters.js @@ -5,7 +5,7 @@ import SpecialCharacters from '../src/specialcharacters'; import SpecialCharactersUI from '../src/specialcharactersui'; -import Typing from '@ckeditor/ckeditor5-typing/src/typing'; +import SpecialCharactersEditing from '../src/specialcharactersediting'; import { expectToThrowCKEditorError } from '@ckeditor/ckeditor5-utils/tests/_utils/utils'; describe( 'SpecialCharacters', () => { @@ -15,8 +15,8 @@ describe( 'SpecialCharacters', () => { plugin = new SpecialCharacters( {} ); } ); - it( 'should require Typing and SpecialCharactersUI', () => { - expect( SpecialCharacters.requires ).to.deep.equal( [ Typing, SpecialCharactersUI ] ); + it( 'should require proper plugins', () => { + expect( SpecialCharacters.requires ).to.deep.equal( [ SpecialCharactersEditing, SpecialCharactersUI ] ); } ); it( 'should be named', () => { diff --git a/tests/specialcharactersediting.js b/tests/specialcharactersediting.js new file mode 100644 index 0000000..625db65 --- /dev/null +++ b/tests/specialcharactersediting.js @@ -0,0 +1,35 @@ +/** + * @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 SpecialCharactersEditing from '../src/specialcharactersediting'; +import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph'; +import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor'; +import InsertSpecialCharacterCommand from '../src/insertspecialcharactercommand'; + +describe( 'SpecialCharactersEditing', () => { + let editor; + + beforeEach( () => { + return VirtualTestEditor + .create( { + plugins: [ SpecialCharactersEditing, Paragraph ] + } ) + .then( newEditor => { + editor = newEditor; + } ); + } ); + + afterEach( () => { + return editor.destroy(); + } ); + + it( 'should have proper pluginName', () => { + expect( SpecialCharactersEditing.pluginName ).to.equal( 'SpecialCharactersEditing' ); + } ); + + it( 'adds a command', () => { + expect( editor.commands.get( 'specialCharacters' ) ).to.be.instanceOf( InsertSpecialCharacterCommand ); + } ); +} ); From b70bc7728898ae5170be2ae418997ec0fae6330e Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Mon, 25 Nov 2019 11:50:17 +0100 Subject: [PATCH 18/43] Internal: Removed unused columns option. Instead it will simply be assigned in the CSS for now. --- src/specialcharactersui.js | 4 +--- src/ui/specialcharacterstableview.js | 18 +++--------------- src/ui/symbolgridview.js | 9 +-------- theme/components/symbolgrid/symbolgrid.css | 5 +++-- 4 files changed, 8 insertions(+), 28 deletions(-) diff --git a/src/specialcharactersui.js b/src/specialcharactersui.js index 6b83181..60bb7d6 100644 --- a/src/specialcharactersui.js +++ b/src/specialcharactersui.js @@ -44,9 +44,7 @@ export default class SpecialCharactersUI extends Plugin { selectOptions: getSelectViewOptions() } ); - const symbolTableView = new SpecialCharactersTableView( locale, { - columns: 10 // TODO: Read from config. - } ); + const symbolTableView = new SpecialCharactersTableView( locale ); symbolTableView.delegate( 'execute' ).to( dropdownView, 'execute' ); diff --git a/src/ui/specialcharacterstableview.js b/src/ui/specialcharacterstableview.js index 14afa8a..7d1a147 100644 --- a/src/ui/specialcharacterstableview.js +++ b/src/ui/specialcharacterstableview.js @@ -23,12 +23,8 @@ export default class SpecialCharactersTableView extends View { * Creates a view to be inserted as a child of {@link module:ui/dropdown/dropdownview~DropdownView}. * * @param {module:utils/locale~Locale} [locale] The localization services instance. - * @param {Object} config The configuration object. - * @param {Array.} config.symbolDefinitions An array with - * definitions of special characters to be displayed in the table. - * @param {Number} config.columns The number of columns in the color grid. */ - constructor( locale, { symbolDefinitions = [], columns } ) { + constructor( locale ) { super( locale ); /** @@ -55,19 +51,12 @@ export default class SpecialCharactersTableView extends View { */ this.keystrokes = new KeystrokeHandler(); - /** - * The number of columns in the special characters grid. - * - * @type {Number} - */ - this.columns = columns; - /** * An array with definitions of special characters to be displayed in the table. * * @member {Array.} */ - this.symbolDefinitions = symbolDefinitions; + this.symbolDefinitions = []; /** * Preserves the reference to {@link module:special-characters/ui/symbolgridview~SymbolGridView} used to create @@ -149,8 +138,7 @@ export default class SpecialCharactersTableView extends View { */ _createSymbolGridView() { const symbolGrid = new SymbolGridView( this.locale, { - symbolDefinitions: this.symbolDefinitions, - columns: this.columns + symbolDefinitions: this.symbolDefinitions } ); symbolGrid.delegate( 'execute' ).to( this ); diff --git a/src/ui/symbolgridview.js b/src/ui/symbolgridview.js index 7f7ac41..b55f2fb 100644 --- a/src/ui/symbolgridview.js +++ b/src/ui/symbolgridview.js @@ -27,17 +27,11 @@ export default class SymbolGridView extends View { * @param {Object} options Component configuration * @param {Array.} [options.symbolDefinitions] Array with * definitions required to create the {@link module:special-characters/ui/symboltileview~SymbolTileView tiles}. - * @param {Number} options.columns A number of columns to display the tiles. */ constructor( locale, options ) { super( locale ); const symbolDefinitions = options && options.symbolDefinitions || []; - const viewStyleAttribute = {}; - - if ( options && options.columns ) { - viewStyleAttribute.gridTemplateColumns = `repeat( ${ options.columns }, 1fr)`; - } /** * Collection of the child tile views. @@ -100,8 +94,7 @@ export default class SymbolGridView extends View { class: [ 'ck', 'ck-symbol-grid' - ], - style: viewStyleAttribute + ] } } ); } diff --git a/theme/components/symbolgrid/symbolgrid.css b/theme/components/symbolgrid/symbolgrid.css index 14c91e5..fdaf4ca 100644 --- a/theme/components/symbolgrid/symbolgrid.css +++ b/theme/components/symbolgrid/symbolgrid.css @@ -3,6 +3,7 @@ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license */ - .ck.ck-symbol-grid { +.ck.ck-symbol-grid { display: grid; -} \ No newline at end of file + grid-template-columns: repeat(10, 1fr); +} From 950e0817289baa4978e88e8b51e2cfbf0e20a763 Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Mon, 25 Nov 2019 12:03:45 +0100 Subject: [PATCH 19/43] Internal: Renamed the insert special character command. --- src/specialcharactersediting.js | 2 +- src/specialcharactersui.js | 2 +- tests/insertspecialcharactercommand.js | 2 +- tests/specialcharactersediting.js | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/specialcharactersediting.js b/src/specialcharactersediting.js index bc13c81..942a3bb 100644 --- a/src/specialcharactersediting.js +++ b/src/specialcharactersediting.js @@ -40,6 +40,6 @@ export default class SpecialCharactersEditing extends Plugin { const editor = this.editor; const command = new InsertSpecialCharacterCommand( editor ); - editor.commands.add( 'specialCharacters', command ); + editor.commands.add( 'insertSpecialCharacter', command ); } } diff --git a/src/specialcharactersui.js b/src/specialcharactersui.js index 60bb7d6..a16aa69 100644 --- a/src/specialcharactersui.js +++ b/src/specialcharactersui.js @@ -24,7 +24,7 @@ export default class SpecialCharactersUI extends Plugin { const t = editor.t; const specialCharacterPlugin = editor.plugins.get( 'SpecialCharacters' ); const label = t( 'Special characters' ); - const command = editor.commands.get( 'specialCharacters' ); + const command = editor.commands.get( 'insertSpecialCharacter' ); // Add the `specialCharacters` dropdown button to feature components. editor.ui.componentFactory.add( 'specialCharacters', locale => { diff --git a/tests/insertspecialcharactercommand.js b/tests/insertspecialcharactercommand.js index 3adb4ff..0be356e 100644 --- a/tests/insertspecialcharactercommand.js +++ b/tests/insertspecialcharactercommand.js @@ -27,7 +27,7 @@ describe( 'InsertSpecialCharacterCommand', () => { .then( newEditor => { editor = newEditor; model = editor.model; - command = editor.commands.get( 'specialCharacters' ); + command = editor.commands.get( 'insertSpecialCharacter' ); editor.plugins.get( 'SpecialCharacters' ).addItems( 'Arrows', [ { title: 'arrow left', character: '←' }, diff --git a/tests/specialcharactersediting.js b/tests/specialcharactersediting.js index 625db65..986da6b 100644 --- a/tests/specialcharactersediting.js +++ b/tests/specialcharactersediting.js @@ -30,6 +30,6 @@ describe( 'SpecialCharactersEditing', () => { } ); it( 'adds a command', () => { - expect( editor.commands.get( 'specialCharacters' ) ).to.be.instanceOf( InsertSpecialCharacterCommand ); + expect( editor.commands.get( 'insertSpecialCharacter' ) ).to.be.instanceOf( InsertSpecialCharacterCommand ); } ); } ); From f9e3ba7a5520ab4bccb39a73dd513b83b13ce2d9 Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Mon, 25 Nov 2019 15:43:50 +0100 Subject: [PATCH 20/43] Cleaned-up the UI of the feature. --- src/specialcharacters.js | 2 + src/specialcharactersui.js | 95 +++++----- src/ui/charactergridview.js | 89 +++++++++ src/ui/specialcharactersnavigationview.js | 141 ++++++++++++++ src/ui/specialcharactersselectview.js | 83 --------- src/ui/specialcharacterstableview.js | 160 ---------------- src/ui/symbolgridview.js | 173 ------------------ src/ui/symboltileview.js | 35 ---- .../symbolgrid.css => charactergrid.css} | 4 +- theme/specialcharacters.css | 12 ++ 10 files changed, 289 insertions(+), 505 deletions(-) create mode 100644 src/ui/charactergridview.js create mode 100644 src/ui/specialcharactersnavigationview.js delete mode 100644 src/ui/specialcharactersselectview.js delete mode 100644 src/ui/specialcharacterstableview.js delete mode 100644 src/ui/symbolgridview.js delete mode 100644 src/ui/symboltileview.js rename theme/{components/symbolgrid/symbolgrid.css => charactergrid.css} (87%) create mode 100644 theme/specialcharacters.css diff --git a/src/specialcharacters.js b/src/specialcharacters.js index b2d4759..87176db 100644 --- a/src/specialcharacters.js +++ b/src/specialcharacters.js @@ -12,6 +12,8 @@ import Typing from '@ckeditor/ckeditor5-typing/src/typing'; import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror'; import SpecialCharactersUI from './specialcharactersui'; +import '../theme/specialcharacters.css'; + /** * The special characters feature. * diff --git a/src/specialcharactersui.js b/src/specialcharactersui.js index 00501f4..df52f21 100644 --- a/src/specialcharactersui.js +++ b/src/specialcharactersui.js @@ -11,8 +11,8 @@ import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; import { createDropdown } from '@ckeditor/ckeditor5-ui/src/dropdown/utils'; import specialCharactersIcon from '../theme/icons/specialcharacters.svg'; import InsertSpecialCharacterCommand from './insertspecialcharactercommand'; -import SpecialCharactersTableView from './ui/specialcharacterstableview'; -import SpecialCharactersSelectView from './ui/specialcharactersselectview'; +import CharacterGridView from './ui/charactergridview'; +import SpecialCharactersNavigationView from './ui/specialcharactersnavigationview'; /** * The special characters UI plugin. @@ -23,78 +23,69 @@ export default class SpecialCharactersUI extends Plugin { init() { const editor = this.editor; const t = editor.t; - const specialCharacterPlugin = editor.plugins.get( 'SpecialCharacters' ); - const label = t( 'Special characters' ); - + const specialCharsPlugin = editor.plugins.get( 'SpecialCharacters' ); const command = new InsertSpecialCharacterCommand( editor ); + editor.commands.add( 'specialCharacters', command ); // Add the `specialCharacters` dropdown button to feature components. editor.ui.componentFactory.add( 'specialCharacters', locale => { - // Prepare the dropdown element. const dropdownView = createDropdown( locale ); - - dropdownView.buttonView.set( { - label, - icon: specialCharactersIcon, - tooltip: true + const navigationView = new SpecialCharactersNavigationView( locale, specialCharsPlugin.getGroups() ); + const symbolGridView = new CharacterGridView( this.locale, { + columns: 10 } ); - dropdownView.bind( 'isEnabled' ).to( command ); + symbolGridView.delegate( 'execute' ).to( dropdownView ); + + // Set the initial content of the special characters grid. + this._updateGrid( specialCharsPlugin, navigationView.currentGroupName, symbolGridView ); - const specialCharactersSelectView = new SpecialCharactersSelectView( locale, { - labelText: label, - selectOptions: getSelectViewOptions() + // Update the grid of special characters when a user changed the character group. + navigationView.on( 'execute', () => { + this._updateGrid( specialCharsPlugin, navigationView.currentGroupName, symbolGridView ); } ); - const symbolTableView = new SpecialCharactersTableView( locale, { - columns: 10 // TODO: Read from config. + dropdownView.buttonView.set( { + label: t( 'Special characters' ), + icon: specialCharactersIcon, + tooltip: true } ); - symbolTableView.delegate( 'execute' ).to( dropdownView, 'execute' ); + dropdownView.bind( 'isEnabled' ).to( command ); // Insert a special character when a tile was clicked. dropdownView.on( 'execute', ( evt, data ) => { - command.execute( { item: data.title } ); + editor.execute( 'specialCharacters', { item: data.name } ); + editor.editing.view.focus(); } ); - // Draw special characters tiles when the dropdown is open. - dropdownView.on( 'change:isOpen', ( evt, name, isVisible ) => { - if ( !isVisible ) { - return; - } - - printCharacters( specialCharactersSelectView, symbolTableView.symbolGridView ); - } ); - - // Draw special characters tiles for specified category (when a user has changed it). - specialCharactersSelectView.on( 'input', () => { - printCharacters( specialCharactersSelectView, symbolTableView.symbolGridView ); - } ); - - dropdownView.panelView.children.add( specialCharactersSelectView ); - dropdownView.panelView.children.add( symbolTableView ); + dropdownView.panelView.children.add( navigationView ); + dropdownView.panelView.children.add( symbolGridView ); return dropdownView; } ); + } - function printCharacters( selectView, gridView ) { - // TODO: Keyboard navigation. - gridView.items.clear(); - - const groupName = selectView.value; - const characterTitles = specialCharacterPlugin.getCharactersForGroup( groupName ); - - for ( const title of characterTitles ) { - const character = specialCharacterPlugin.getCharacter( title ); - - gridView.items.add( gridView.createSymbolTile( character, title ) ); - } - } - - function getSelectViewOptions() { - return [ ...specialCharacterPlugin.getGroups() ] - .map( groupName => ( { label: groupName, value: groupName } ) ); + /** + * Updates the symbol grid depending on the currently selected character group. + * + * @private + * @param {module:special-characters/specialcharacters~SpecialCharacters} specialCharsPlugin + * @param {module:special-characters/ui/specialcharactersnavigationview~SpecialCharactersNavigationView#currentGroupName} + * currentGroupName + * @param {module:special-characters/ui/charactergridview~CharacterGridView} gridView + */ + _updateGrid( specialCharsPlugin, currentGroupName, gridView ) { + // Updating the grid starts with removing all tiles belonging to the old group. + gridView.tiles.clear(); + + const characterTitles = specialCharsPlugin.getCharactersForGroup( currentGroupName ); + + for ( const title of characterTitles ) { + const character = specialCharsPlugin.getCharacter( title ); + + gridView.tiles.add( gridView.createTile( character, title ) ); } } } diff --git a/src/ui/charactergridview.js b/src/ui/charactergridview.js new file mode 100644 index 0000000..a49db30 --- /dev/null +++ b/src/ui/charactergridview.js @@ -0,0 +1,89 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/** + * @module special-characters/ui/charactergridview + */ + +import View from '@ckeditor/ckeditor5-ui/src/view'; +import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview'; + +import '../../theme/charactergrid.css'; + +/** + * A grid of character tiles. Allows browsing special characters and selecting the character to + * be inserted into the content. + * + * @extends module:ui/view~View + */ +export default class CharacterGridView extends View { + /** + * Creates an instance of a character grid containing tiles representing special characters. + * + * @param {module:utils/locale~Locale} [locale] The localization services instance. + * @param {Object} options Component configuration + * @param {Number} options.columns A number of columns in the grid. + */ + constructor( locale, options ) { + super( locale ); + + const viewStyleAttribute = {}; + + if ( options && options.columns ) { + viewStyleAttribute.gridTemplateColumns = `repeat( ${ options.columns }, 1fr)`; + } + + /** + * Collection of the child tile views. Each tile represents some particular character. + * + * @readonly + * @member {module:ui/viewcollection~ViewCollection} + */ + this.tiles = this.createCollection(); + + this.setTemplate( { + tag: 'div', + children: this.tiles, + attributes: { + class: [ + 'ck', + 'ck-character-grid' + ], + style: viewStyleAttribute + } + } ); + + /** + * Fired when any {@link #tiles grid tiles} is clicked. + * + * @event execute + * @param {Object} data Additional information about the event. + * @param {String} data.name Name of the tile that caused the event (e.g. "greek small letter epsilon"). + */ + } + + /** + * Creates a new tile for the grid. + * + * @param {String} character A human-readable character displayed as label (e.g. "ε"). + * @param {String} name A name of the character (e.g. "greek small letter epsilon"). + * @returns {module:ui/button/button~ButtonView} + */ + createTile( character, name ) { + const tile = new ButtonView( this.locale ); + + tile.set( { + label: character, + withText: true, + class: 'ck-character-grid__tile' + } ); + + tile.on( 'execute', () => { + this.fire( 'execute', { name } ); + } ); + + return tile; + } +} diff --git a/src/ui/specialcharactersnavigationview.js b/src/ui/specialcharactersnavigationview.js new file mode 100644 index 0000000..5cd87a2 --- /dev/null +++ b/src/ui/specialcharactersnavigationview.js @@ -0,0 +1,141 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/** + * @module special-characters/ui/specialcharactersnavigationview + */ + +import View from '@ckeditor/ckeditor5-ui/src/view'; +import Collection from '@ckeditor/ckeditor5-utils/src/collection'; +import Model from '@ckeditor/ckeditor5-ui/src/model'; +import LabelView from '@ckeditor/ckeditor5-ui/src/label/labelview'; +import { + createDropdown, + addListToDropdown +} from '@ckeditor/ckeditor5-ui/src/dropdown/utils'; + +/** + * A class representing the navigation part of the special characters UI. It is responsible + * for describing the feature and selecting a particular character group by the user. + * + * @extends module:ui/view~View + */ +export default class SpecialCharactersNavigationView extends View { + /** + * Creates an instance of the {@link module:special-characters/ui/specialcharactersnavigationview~SpecialCharactersNavigationView} + * class. + * + * @param {module:utils/locale~Locale} locale The localization services instance. + * @param {Iterable.} groupNames Names of the character groups. + */ + constructor( locale, groupNames ) { + super( locale ); + + const t = locale.t; + + /** + * Label of the navigation view describing its purpose. + * + * @member {module:ui/label/labelview~LabelView} + */ + this.labelView = new LabelView( locale ); + this.labelView.text = t( 'Special characters' ); + + /** + * A dropdown that allows selecting a group of special characters to be displayed. + * + * @member {module:ui/dropdown/dropdownview~DropdownView} + */ + this.groupDropdownView = this._createGroupSelector( groupNames ); + + this.setTemplate( { + tag: 'div', + attributes: { + class: [ + 'ck', + 'ck-special-characters-navigation' + ] + }, + children: [ + this.labelView, + this.groupDropdownView + ] + } ); + } + + /** + * Returns a name of the character groups currently selected in the {@link #groupDropdownView}. + * + * @returns {String} + */ + get currentGroupName() { + return this.groupDropdownView.value; + } + + /** + * Returns a dropdown that allows selecting character groups. + * + * @private + * @param {Iterable.} groupNames Names of the character groups. + * @returns {module:ui/dropdown/dropdownview~DropdownView} + */ + _createGroupSelector( groupNames ) { + const locale = this.locale; + const t = locale.t; + const dropdown = createDropdown( locale ); + const groupDefinitions = this._getCharacterGroupListItemDefinitions( dropdown, groupNames ); + + dropdown.set( 'value', groupDefinitions.first.model.label ); + + dropdown.buttonView.bind( 'label' ).to( dropdown, 'value' ); + + dropdown.buttonView.set( { + isOn: false, + withText: true, + tooltip: t( 'Character category' ) + } ); + + dropdown.on( 'execute', evt => { + dropdown.value = evt.source.label; + } ); + + dropdown.delegate( 'execute' ).to( this ); + + addListToDropdown( dropdown, groupDefinitions ); + + return dropdown; + } + + /** + * Returns list item definitions to be used in the character group dropdown + * representing specific character groups. + * + * @private + * @param {module:ui/dropdown/dropdownview~DropdownView} dropdown + * @param {Iterable.} groupNames Names of the character groups. + * @returns {Iterable.} + */ + _getCharacterGroupListItemDefinitions( dropdown, groupNames ) { + const groupDefs = new Collection(); + + for ( const name of groupNames ) { + const definition = { + type: 'button', + model: new Model( { + label: name, + withText: true + } ) + }; + + definition.model.bind( 'isOn' ).to( dropdown, 'value', value => { + return value === definition.model.label; + } ); + + groupDefs.add( definition ); + } + + return groupDefs; + } +} diff --git a/src/ui/specialcharactersselectview.js b/src/ui/specialcharactersselectview.js deleted file mode 100644 index 4308fd9..0000000 --- a/src/ui/specialcharactersselectview.js +++ /dev/null @@ -1,83 +0,0 @@ -/** - * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license - */ - -/** - * @module special-characters/ui/specialcharactersselectview - */ - -import View from '@ckeditor/ckeditor5-ui/src/view'; -import LabelView from '@ckeditor/ckeditor5-ui/src/label/labelview'; -import SelectView from '@ckeditor/ckeditor5-ui/src/select/selectview'; -import uid from '@ckeditor/ckeditor5-utils/src/uid'; - -/** - * @extends module:ui/view~View - */ -export default class SpecialCharactersSelectView extends View { - /** - * Creates a view to be inserted as a child of {@link module:ui/dropdown/dropdownview~DropdownView}. - * - * @param {module:utils/locale~Locale} [locale] The localization services instance. - * @param {Object} options - * @param {String} options.labelText A label for the select element. - * @param {Array.} options.selectOptions Options to chose in the select view. - */ - constructor( locale, options ) { - super( locale ); - - const inputUid = `ck-select-${ uid() }`; - - /** - * A label for the select view. - * - * @member {module:ui/label/labelview~LabelView} - */ - this.label = new LabelView( this.locale ); - - /** - * Select view for changing a category of special characters. - * - * @member {module:ui/select/selectview~SelectView} - */ - this.selectView = new SelectView( this.locale, options.selectOptions ); - this.selectView.delegate( 'input' ).to( this ); - - this.label.for = inputUid; - this.label.text = options.labelText; - this.label.extendTemplate( { - attributes: { - class: [ - 'ck', - 'ck-symbol-grid__label' - ] - } - } ); - - this.selectView.id = inputUid; - - this.setTemplate( { - tag: 'div', - attributes: { - class: [ - 'ck', - 'ck-grid-table' - ] - }, - children: [ - this.label, - this.selectView - ] - } ); - } - - /** - * Returns a value from the select view. - * - * @returns {String} - */ - get value() { - return this.selectView.element.value; - } -} diff --git a/src/ui/specialcharacterstableview.js b/src/ui/specialcharacterstableview.js deleted file mode 100644 index 14afa8a..0000000 --- a/src/ui/specialcharacterstableview.js +++ /dev/null @@ -1,160 +0,0 @@ -/** - * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license - */ - -/** - * @module special-characters/ui/specialcharacterstableview - */ - -import View from '@ckeditor/ckeditor5-ui/src/view'; -import FocusTracker from '@ckeditor/ckeditor5-utils/src/focustracker'; -import FocusCycler from '@ckeditor/ckeditor5-ui/src/focuscycler'; -import KeystrokeHandler from '@ckeditor/ckeditor5-utils/src/keystrokehandler'; -import SymbolGridView from './symbolgridview'; - -// TODO: Keyboard navigation does not work. - -/** - * @extends module:ui/view~View - */ -export default class SpecialCharactersTableView extends View { - /** - * Creates a view to be inserted as a child of {@link module:ui/dropdown/dropdownview~DropdownView}. - * - * @param {module:utils/locale~Locale} [locale] The localization services instance. - * @param {Object} config The configuration object. - * @param {Array.} config.symbolDefinitions An array with - * definitions of special characters to be displayed in the table. - * @param {Number} config.columns The number of columns in the color grid. - */ - constructor( locale, { symbolDefinitions = [], columns } ) { - super( locale ); - - /** - * A collection of the children of the table. - * - * @readonly - * @member {module:ui/viewcollection~ViewCollection} - */ - this.items = this.createCollection(); - - /** - * Tracks information about the DOM focus in the list. - * - * @readonly - * @member {module:utils/focustracker~FocusTracker} - */ - this.focusTracker = new FocusTracker(); - - /** - * An instance of the {@link module:utils/keystrokehandler~KeystrokeHandler}. - * - * @readonly - * @member {module:utils/keystrokehandler~KeystrokeHandler} - */ - this.keystrokes = new KeystrokeHandler(); - - /** - * The number of columns in the special characters grid. - * - * @type {Number} - */ - this.columns = columns; - - /** - * An array with definitions of special characters to be displayed in the table. - * - * @member {Array.} - */ - this.symbolDefinitions = symbolDefinitions; - - /** - * Preserves the reference to {@link module:special-characters/ui/symbolgridview~SymbolGridView} used to create - * the default (static) symbol set. - * - * @readonly - * @member {module:special-characters/ui/symbolgridview~SymbolGridView} - */ - this.symbolGridView = this._createSymbolGridView(); - - /** - * Helps cycling over focusable {@link #items} in the list. - * - * @readonly - * @protected - * @member {module:ui/focuscycler~FocusCycler} - */ - this._focusCycler = new FocusCycler( { - focusables: this.items, - focusTracker: this.focusTracker, - keystrokeHandler: this.keystrokes, - actions: { - // Navigate list items backwards using the Arrow Up key. - focusPrevious: 'arrowup', - - // Navigate list items forwards using the Arrow Down key. - focusNext: 'arrowdown', - } - } ); - - this.setTemplate( { - tag: 'div', - attributes: { - class: [ - 'ck', - 'ck-grid-table' - ] - }, - children: this.items - } ); - - this.items.add( this.symbolGridView ); - } - - /** - * @inheritDoc - */ - render() { - super.render(); - - // Items added before rendering should be known to the #focusTracker. - for ( const item of this.items ) { - this.focusTracker.add( item.element ); - } - - // Start listening for the keystrokes coming from #element. - this.keystrokes.listenTo( this.element ); - } - - /** - * Focuses the first focusable element in {@link #items}. - */ - focus() { - this._focusCycler.focusFirst(); - } - - /** - * Focuses the last focusable element in {@link #items}. - */ - focusLast() { - this._focusCycler.focusLast(); - } - - /** - * Creates a static symbol table grid based on the editor configuration. - * - * @private - * @returns {module:special-characters/ui/symbolgridview~SymbolGridView} - */ - _createSymbolGridView() { - const symbolGrid = new SymbolGridView( this.locale, { - symbolDefinitions: this.symbolDefinitions, - columns: this.columns - } ); - - symbolGrid.delegate( 'execute' ).to( this ); - - return symbolGrid; - } -} diff --git a/src/ui/symbolgridview.js b/src/ui/symbolgridview.js deleted file mode 100644 index 7f7ac41..0000000 --- a/src/ui/symbolgridview.js +++ /dev/null @@ -1,173 +0,0 @@ -/** - * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license - */ - -/** - * @module special-characters/ui/symbolgridview - */ - -import View from '@ckeditor/ckeditor5-ui/src/view'; -import SymbolTileView from './symboltileview'; -import FocusTracker from '@ckeditor/ckeditor5-utils/src/focustracker'; -import FocusCycler from '@ckeditor/ckeditor5-ui/src/focuscycler'; -import KeystrokeHandler from '@ckeditor/ckeditor5-utils/src/keystrokehandler'; -import '../../theme/components/symbolgrid/symbolgrid.css'; - -/** - * A grid of {@link module:special-characters/ui/symboltileview~SymbolTileView symbol tiles}. - * - * @extends module:ui/view~View - */ -export default class SymbolGridView extends View { - /** - * Creates an instance of a symbol grid containing {@link module:special-characters/ui/symboltileview~SymbolTileView tiles}. - * - * @param {module:utils/locale~Locale} [locale] The localization services instance. - * @param {Object} options Component configuration - * @param {Array.} [options.symbolDefinitions] Array with - * definitions required to create the {@link module:special-characters/ui/symboltileview~SymbolTileView tiles}. - * @param {Number} options.columns A number of columns to display the tiles. - */ - constructor( locale, options ) { - super( locale ); - - const symbolDefinitions = options && options.symbolDefinitions || []; - const viewStyleAttribute = {}; - - if ( options && options.columns ) { - viewStyleAttribute.gridTemplateColumns = `repeat( ${ options.columns }, 1fr)`; - } - - /** - * Collection of the child tile views. - * - * @readonly - * @member {module:ui/viewcollection~ViewCollection} - */ - this.items = this.createCollection(); - - /** - * Tracks information about DOM focus in the grid. - * - * @readonly - * @member {module:utils/focustracker~FocusTracker} - */ - this.focusTracker = new FocusTracker(); - - /** - * Instance of the {@link module:utils/keystrokehandler~KeystrokeHandler}. - * - * @readonly - * @member {module:utils/keystrokehandler~KeystrokeHandler} - */ - this.keystrokes = new KeystrokeHandler(); - - /** - * Helps cycling over focusable {@link #items} in the grid. - * - * @readonly - * @protected - * @member {module:ui/focuscycler~FocusCycler} - */ - this._focusCycler = new FocusCycler( { - focusables: this.items, - focusTracker: this.focusTracker, - keystrokeHandler: this.keystrokes, - actions: { - // Navigate grid items backwards using the arrowup key. - focusPrevious: 'arrowleft', - - // Navigate grid items forwards using the arrowdown key. - focusNext: 'arrowright', - } - } ); - - this.items.on( 'add', ( evt, symbolTile ) => { - symbolTile.isOn = symbolTile.symbol === this.selectedsymbol; - } ); - - symbolDefinitions.forEach( item => { - const symbolTile = this.createSymbolTile( item.character, item.title ); - - this.items.add( symbolTile ); - } ); - - this.setTemplate( { - tag: 'div', - children: this.items, - attributes: { - class: [ - 'ck', - 'ck-symbol-grid' - ], - style: viewStyleAttribute - } - } ); - } - - /** - * Focuses the first focusable in {@link #items}. - */ - focus() { - if ( this.items.length ) { - this.items.first.focus(); - } - } - - /** - * Focuses the last focusable in {@link #items}. - */ - focusLast() { - if ( this.items.length ) { - this.items.last.focus(); - } - } - - /** - * @inheritDoc - */ - render() { - super.render(); - - // Items added before rendering should be known to the #focusTracker. - for ( const item of this.items ) { - this.focusTracker.add( item.element ); - } - - this.items.on( 'add', ( evt, item ) => { - this.focusTracker.add( item.element ); - } ); - - this.items.on( 'remove', ( evt, item ) => { - this.focusTracker.remove( item.element ); - } ); - - // Start listening for the keystrokes coming from #element. - this.keystrokes.listenTo( this.element ); - } - - /** - * Creates a new tile for the grid. - * - * @param {String} character A character that will be displayed on the button. - * @param {String} title A label that described the character. - * @returns {module:special-characters/ui/symboltileview~SymbolTileView} - */ - createSymbolTile( character, title ) { - const symbolTile = new SymbolTileView(); - - symbolTile.set( { - symbol: character, - label: character, - tooltip: title, - withText: true - } ); - - symbolTile.on( 'execute', () => { - this.fire( 'execute', { title } ); - } ); - - return symbolTile; - } -} diff --git a/src/ui/symboltileview.js b/src/ui/symboltileview.js deleted file mode 100644 index 6de8a86..0000000 --- a/src/ui/symboltileview.js +++ /dev/null @@ -1,35 +0,0 @@ -/** - * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license - */ - -/** - * @module special-characters/ui/symboltileview - */ - -import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview'; - -/** - * @extends module:ui/button/buttonview~ButtonView - */ -export default class SymbolTileView extends ButtonView { - constructor( locale ) { - super( locale ); - - /** - * String representing a symbol shown as tile's value. - * - * @type {String} - */ - this.set( 'symbol' ); - - this.extendTemplate( { - attributes: { - class: [ - 'ck', - 'ck-symbol-grid__tile', - ] - } - } ); - } -} diff --git a/theme/components/symbolgrid/symbolgrid.css b/theme/charactergrid.css similarity index 87% rename from theme/components/symbolgrid/symbolgrid.css rename to theme/charactergrid.css index 14c91e5..2305d6f 100644 --- a/theme/components/symbolgrid/symbolgrid.css +++ b/theme/charactergrid.css @@ -3,6 +3,6 @@ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license */ - .ck.ck-symbol-grid { +.ck.ck-character-grid { display: grid; -} \ No newline at end of file +} diff --git a/theme/specialcharacters.css b/theme/specialcharacters.css new file mode 100644 index 0000000..aacc5d5 --- /dev/null +++ b/theme/specialcharacters.css @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +.ck.ck-special-characters-navigation { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + align-items: center; + justify-content: space-between; +} From e14c0d39f034fc51904040e5b821e1a8bb45019f Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Mon, 25 Nov 2019 16:02:23 +0100 Subject: [PATCH 21/43] Tests: Added tests for the CharacterGridView class. --- src/ui/charactergridview.js | 4 +-- tests/ui/charactergridview.js | 67 +++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 2 deletions(-) create mode 100644 tests/ui/charactergridview.js diff --git a/src/ui/charactergridview.js b/src/ui/charactergridview.js index a49db30..3334ce3 100644 --- a/src/ui/charactergridview.js +++ b/src/ui/charactergridview.js @@ -22,7 +22,7 @@ export default class CharacterGridView extends View { /** * Creates an instance of a character grid containing tiles representing special characters. * - * @param {module:utils/locale~Locale} [locale] The localization services instance. + * @param {module:utils/locale~Locale} locale The localization services instance. * @param {Object} options Component configuration * @param {Number} options.columns A number of columns in the grid. */ @@ -32,7 +32,7 @@ export default class CharacterGridView extends View { const viewStyleAttribute = {}; if ( options && options.columns ) { - viewStyleAttribute.gridTemplateColumns = `repeat( ${ options.columns }, 1fr)`; + viewStyleAttribute.gridTemplateColumns = `repeat( ${ options.columns }, 1fr )`; } /** diff --git a/tests/ui/charactergridview.js b/tests/ui/charactergridview.js new file mode 100644 index 0000000..9084859 --- /dev/null +++ b/tests/ui/charactergridview.js @@ -0,0 +1,67 @@ +/** + * @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 CharacterGridView from '../../src/ui/charactergridview'; +import ViewCollection from '@ckeditor/ckeditor5-ui/src/viewcollection'; +import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview'; +import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils'; + +describe.only( 'CharacterGridView', () => { + let view; + + testUtils.createSinonSandbox(); + + beforeEach( () => { + view = new CharacterGridView(); + view.render(); + } ); + + afterEach( () => { + view.destroy(); + } ); + + describe( 'constructor()', () => { + it( 'creates view#tiles collection', () => { + expect( view.tiles ).to.be.instanceOf( ViewCollection ); + } ); + + it( 'creates #element from template', () => { + expect( view.element.classList.contains( 'ck' ) ).to.be.true; + expect( view.element.classList.contains( 'ck-character-grid' ) ).to.be.true; + expect( view.element.style.gridTemplateColumns ).to.equal( '' ); + } ); + + it( 'supports columns configuration', () => { + const view = new CharacterGridView( {}, { columns: 3 } ); + view.render(); + + expect( view.element.style.gridTemplateColumns ).to.be.oneOf( [ '1fr 1fr 1fr', 'repeat(3, 1fr)' ] ); + + view.destroy(); + } ); + } ); + + describe( 'createTile()', () => { + it( 'creates a new tile button', () => { + const tile = view.createTile( 'ε', 'foo bar baz' ); + + expect( tile ).to.be.instanceOf( ButtonView ); + expect( tile.label ).to.equal( 'ε' ); + expect( tile.withText ).to.be.true; + expect( tile.class ).to.equal( 'ck-character-grid__tile' ); + } ); + + it( 'delegates #execute from the tile to the grid', () => { + const tile = view.createTile( 'ε', 'foo bar baz' ); + const spy = sinon.spy(); + + view.on( 'execute', spy ); + tile.fire( 'execute' ); + + sinon.assert.calledOnce( spy ); + sinon.assert.calledWithExactly( spy, sinon.match.any, { name: 'foo bar baz' } ); + } ); + } ); +} ); From 923798d8764e1294aaac572a0b78d3f4bd5ab97a Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Mon, 25 Nov 2019 16:29:00 +0100 Subject: [PATCH 22/43] Tests: Added tests for the SpecialCharactersNavigationView class. --- src/ui/specialcharactersnavigationview.js | 6 +- tests/ui/charactergridview.js | 2 +- tests/ui/specialcharactersnavigationview.js | 115 ++++++++++++++++++++ 3 files changed, 119 insertions(+), 4 deletions(-) create mode 100644 tests/ui/specialcharactersnavigationview.js diff --git a/src/ui/specialcharactersnavigationview.js b/src/ui/specialcharactersnavigationview.js index 5cd87a2..2bf7cdd 100644 --- a/src/ui/specialcharactersnavigationview.js +++ b/src/ui/specialcharactersnavigationview.js @@ -48,7 +48,7 @@ export default class SpecialCharactersNavigationView extends View { * * @member {module:ui/dropdown/dropdownview~DropdownView} */ - this.groupDropdownView = this._createGroupSelector( groupNames ); + this.groupDropdownView = this._createGroupDropdown( groupNames ); this.setTemplate( { tag: 'div', @@ -81,7 +81,7 @@ export default class SpecialCharactersNavigationView extends View { * @param {Iterable.} groupNames Names of the character groups. * @returns {module:ui/dropdown/dropdownview~DropdownView} */ - _createGroupSelector( groupNames ) { + _createGroupDropdown( groupNames ) { const locale = this.locale; const t = locale.t; const dropdown = createDropdown( locale ); @@ -94,7 +94,7 @@ export default class SpecialCharactersNavigationView extends View { dropdown.buttonView.set( { isOn: false, withText: true, - tooltip: t( 'Character category' ) + tooltip: t( 'Character categories' ) } ); dropdown.on( 'execute', evt => { diff --git a/tests/ui/charactergridview.js b/tests/ui/charactergridview.js index 9084859..06a6e47 100644 --- a/tests/ui/charactergridview.js +++ b/tests/ui/charactergridview.js @@ -8,7 +8,7 @@ import ViewCollection from '@ckeditor/ckeditor5-ui/src/viewcollection'; import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview'; import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils'; -describe.only( 'CharacterGridView', () => { +describe( 'CharacterGridView', () => { let view; testUtils.createSinonSandbox(); diff --git a/tests/ui/specialcharactersnavigationview.js b/tests/ui/specialcharactersnavigationview.js new file mode 100644 index 0000000..09de898 --- /dev/null +++ b/tests/ui/specialcharactersnavigationview.js @@ -0,0 +1,115 @@ +/** + * @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 SpecialCharactersNavigationView from '../../src/ui/specialcharactersnavigationview'; +import LabelView from '@ckeditor/ckeditor5-ui/src/label/labelview'; +import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils'; + +describe( 'SpecialCharactersNavigationView', () => { + let view, locale; + + testUtils.createSinonSandbox(); + + beforeEach( () => { + locale = { + t: val => val + }; + + view = new SpecialCharactersNavigationView( locale, [ 'groupA', 'groupB' ] ); + view.render(); + } ); + + afterEach( () => { + view.destroy(); + } ); + + describe( 'constructor()', () => { + it( 'creates #labelView', () => { + expect( view.labelView ).to.be.instanceOf( LabelView ); + expect( view.labelView.text ).to.equal( 'Special characters' ); + } ); + + it( 'creates #groupDropdownView', () => { + } ); + + it( 'creates #element from template', () => { + expect( view.element.classList.contains( 'ck' ) ).to.be.true; + expect( view.element.classList.contains( 'ck-special-characters-navigation' ) ).to.be.true; + + expect( view.element.firstChild ).to.equal( view.labelView.element ); + expect( view.element.lastChild ).to.equal( view.groupDropdownView.element ); + } ); + } ); + + describe( 'currentGroupName()', () => { + it( 'returns the #value of #groupDropdownView', () => { + expect( view.currentGroupName ).to.equal( 'groupA' ); + + view.groupDropdownView.listView.items.last.children.first.fire( 'execute' ); + expect( view.currentGroupName ).to.equal( 'groupB' ); + } ); + } ); + + describe( '#groupDropdownView', () => { + let groupDropdownView; + + beforeEach( () => { + groupDropdownView = view.groupDropdownView; + } ); + + it( 'has a default #value', () => { + expect( view.currentGroupName ).to.equal( 'groupA' ); + } ); + + it( 'binds its buttonView#label to #value', () => { + expect( groupDropdownView.buttonView.label ).to.equal( 'groupA' ); + + groupDropdownView.listView.items.last.children.first.fire( 'execute' ); + expect( groupDropdownView.buttonView.label ).to.equal( 'groupB' ); + } ); + + it( 'configures the #buttonView', () => { + expect( groupDropdownView.buttonView.isOn ).to.be.false; + expect( groupDropdownView.buttonView.withText ).to.be.true; + expect( groupDropdownView.buttonView.tooltip ).to.equal( 'Character categories' ); + } ); + + it( 'delegates #execute to the naviation view', () => { + const spy = sinon.spy(); + + view.on( 'execute', spy ); + + groupDropdownView.fire( 'execute' ); + sinon.assert.calledOnce( spy ); + } ); + + describe( 'character group list items', () => { + it( 'have basic properties', () => { + expect( groupDropdownView.listView.items + .map( item => { + const { label, withText } = item.children.first; + + return { label, withText }; + } ) ) + .to.deep.equal( [ + { label: 'groupA', withText: true }, + { label: 'groupB', withText: true }, + ] ); + } ); + + it( 'bind #isOn to the #value of the dropdown', () => { + const firstButton = groupDropdownView.listView.items.first.children.last; + const lastButton = groupDropdownView.listView.items.last.children.last; + + expect( firstButton.isOn ).to.be.true; + expect( lastButton.isOn ).to.be.false; + + groupDropdownView.value = 'groupB'; + expect( firstButton.isOn ).to.be.false; + expect( lastButton.isOn ).to.be.true; + } ); + } ); + } ); +} ); From 159f8c3d013ddb5b9f32f32f256b2bf3a3d8b100 Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Mon, 25 Nov 2019 16:58:39 +0100 Subject: [PATCH 23/43] Tests: Added tests for the SpecialCharactersUI class. --- src/specialcharactersui.js | 17 +++-- tests/specialcharactersui.js | 128 +++++++++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+), 5 deletions(-) create mode 100644 tests/specialcharactersui.js diff --git a/src/specialcharactersui.js b/src/specialcharactersui.js index df52f21..8564ed5 100644 --- a/src/specialcharactersui.js +++ b/src/specialcharactersui.js @@ -20,6 +20,13 @@ import SpecialCharactersNavigationView from './ui/specialcharactersnavigationvie * @extends module:core/plugin~Plugin */ export default class SpecialCharactersUI extends Plugin { + /** + * @inheritDoc + */ + static get pluginName() { + return 'SpecialCharactersUI'; + } + init() { const editor = this.editor; const t = editor.t; @@ -32,18 +39,18 @@ export default class SpecialCharactersUI extends Plugin { editor.ui.componentFactory.add( 'specialCharacters', locale => { const dropdownView = createDropdown( locale ); const navigationView = new SpecialCharactersNavigationView( locale, specialCharsPlugin.getGroups() ); - const symbolGridView = new CharacterGridView( this.locale, { + const gridView = new CharacterGridView( this.locale, { columns: 10 } ); - symbolGridView.delegate( 'execute' ).to( dropdownView ); + gridView.delegate( 'execute' ).to( dropdownView ); // Set the initial content of the special characters grid. - this._updateGrid( specialCharsPlugin, navigationView.currentGroupName, symbolGridView ); + this._updateGrid( specialCharsPlugin, navigationView.currentGroupName, gridView ); // Update the grid of special characters when a user changed the character group. navigationView.on( 'execute', () => { - this._updateGrid( specialCharsPlugin, navigationView.currentGroupName, symbolGridView ); + this._updateGrid( specialCharsPlugin, navigationView.currentGroupName, gridView ); } ); dropdownView.buttonView.set( { @@ -61,7 +68,7 @@ export default class SpecialCharactersUI extends Plugin { } ); dropdownView.panelView.children.add( navigationView ); - dropdownView.panelView.children.add( symbolGridView ); + dropdownView.panelView.children.add( gridView ); return dropdownView; } ); diff --git a/tests/specialcharactersui.js b/tests/specialcharactersui.js new file mode 100644 index 0000000..a00c0c4 --- /dev/null +++ b/tests/specialcharactersui.js @@ -0,0 +1,128 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/* global document */ + +import SpecialCharacters from '../src/specialcharacters'; +import SpecialCharactersMathematical from '../src/specialcharactersmathematical'; +import SpecialCharactersArrows from '../src/specialcharactersarrows'; +import SpecialCharactersUI from '../src/specialcharactersui'; +import SpecialCharactersNavigationView from '../src/ui/specialcharactersnavigationview'; +import CharacterGridView from '../src/ui/charactergridview'; +import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor'; +import specialCharactersIcon from '../theme/icons/specialcharacters.svg'; +import EventInfo from '@ckeditor/ckeditor5-utils/src/eventinfo'; + +describe( 'SpecialCharactersUI', () => { + let editor, command, element; + + beforeEach( () => { + element = document.createElement( 'div' ); + document.body.appendChild( element ); + + return ClassicTestEditor + .create( element, { + plugins: [ + SpecialCharacters, + SpecialCharactersMathematical, + SpecialCharactersArrows, + SpecialCharactersUI + ] + } ) + .then( newEditor => { + editor = newEditor; + command = editor.commands.get( 'specialCharacters' ); + } ); + } ); + + afterEach( () => { + element.remove(); + + return editor.destroy(); + } ); + + it( 'should be named', () => { + expect( SpecialCharactersUI.pluginName ).to.equal( 'SpecialCharactersUI' ); + } ); + + describe( '"specialCharacters" dropdown', () => { + let dropdown; + + beforeEach( () => { + dropdown = editor.ui.componentFactory.create( 'specialCharacters' ); + } ); + + afterEach( () => { + dropdown.destroy(); + } ); + + it( 'has a navigation view', () => { + expect( dropdown.panelView.children.first ).to.be.instanceOf( SpecialCharactersNavigationView ); + } ); + + it( 'has a grid view', () => { + expect( dropdown.panelView.children.last ).to.be.instanceOf( CharacterGridView ); + } ); + + describe( '#buttonView', () => { + it( 'should get basic properties', () => { + expect( dropdown.buttonView.label ).to.equal( 'Special characters' ); + expect( dropdown.buttonView.icon ).to.equal( specialCharactersIcon ); + expect( dropdown.buttonView.tooltip ).to.be.true; + } ); + + it( 'should bind #isEnabled to the command', () => { + expect( dropdown.isEnabled ).to.be.true; + + command.isEnabled = false; + expect( dropdown.isEnabled ).to.be.false; + } ); + } ); + + it( 'executes a command and focuses the editing view', () => { + const grid = dropdown.panelView.children.last; + const executeSpy = sinon.stub( editor, 'execute' ); + const focusSpy = sinon.stub( editor.editing.view, 'focus' ); + + grid.tiles.get( 2 ).fire( 'execute' ); + + sinon.assert.calledOnce( executeSpy ); + sinon.assert.calledOnce( focusSpy ); + sinon.assert.calledWithExactly( executeSpy.firstCall, 'specialCharacters', { + item: 'greek small letter delta' + } ); + } ); + + describe( 'grid view', () => { + let grid; + + beforeEach( () => { + grid = dropdown.panelView.children.last; + } ); + + it( 'delegates #execute to the dropdown', () => { + const spy = sinon.spy(); + + dropdown.on( 'execute', spy ); + grid.fire( 'execute', { name: 'foo' } ); + + sinon.assert.calledOnce( spy ); + } ); + + it( 'has default contents', () => { + expect( grid.tiles ).to.have.length.greaterThan( 10 ); + } ); + + it( 'is updated when navigation view fires #execute', () => { + const navigation = dropdown.panelView.children.first; + + expect( grid.tiles.get( 0 ).label ).to.equal( 'α' ); + navigation.groupDropdownView.fire( new EventInfo( { label: 'Arrows' }, 'execute' ) ); + + expect( grid.tiles.get( 0 ).label ).to.equal( '⇐' ); + } ); + } ); + } ); +} ); From af12cb2e3943d33bed0cb9b55a80b7e19c13084f Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Mon, 25 Nov 2019 17:04:07 +0100 Subject: [PATCH 24/43] Docs: Minor fixes. --- src/specialcharactersui.js | 5 +++-- src/ui/charactergridview.js | 6 +++--- src/ui/specialcharactersnavigationview.js | 4 ++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/specialcharactersui.js b/src/specialcharactersui.js index 8564ed5..7effae8 100644 --- a/src/specialcharactersui.js +++ b/src/specialcharactersui.js @@ -17,6 +17,8 @@ import SpecialCharactersNavigationView from './ui/specialcharactersnavigationvie /** * The special characters UI plugin. * + * Introduces the `'specialCharacters'` dropdown. + * * @extends module:core/plugin~Plugin */ export default class SpecialCharactersUI extends Plugin { @@ -79,8 +81,7 @@ export default class SpecialCharactersUI extends Plugin { * * @private * @param {module:special-characters/specialcharacters~SpecialCharacters} specialCharsPlugin - * @param {module:special-characters/ui/specialcharactersnavigationview~SpecialCharactersNavigationView#currentGroupName} - * currentGroupName + * @param {String} currentGroupName * @param {module:special-characters/ui/charactergridview~CharacterGridView} gridView */ _updateGrid( specialCharsPlugin, currentGroupName, gridView ) { diff --git a/src/ui/charactergridview.js b/src/ui/charactergridview.js index 3334ce3..6d7b828 100644 --- a/src/ui/charactergridview.js +++ b/src/ui/charactergridview.js @@ -24,7 +24,7 @@ export default class CharacterGridView extends View { * * @param {module:utils/locale~Locale} locale The localization services instance. * @param {Object} options Component configuration - * @param {Number} options.columns A number of columns in the grid. + * @param {Number} [options.columns] A number of columns in the grid. */ constructor( locale, options ) { super( locale ); @@ -56,7 +56,7 @@ export default class CharacterGridView extends View { } ); /** - * Fired when any {@link #tiles grid tiles} is clicked. + * Fired when any of {@link #tiles grid tiles} is clicked. * * @event execute * @param {Object} data Additional information about the event. @@ -69,7 +69,7 @@ export default class CharacterGridView extends View { * * @param {String} character A human-readable character displayed as label (e.g. "ε"). * @param {String} name A name of the character (e.g. "greek small letter epsilon"). - * @returns {module:ui/button/button~ButtonView} + * @returns {module:ui/button/buttonview~ButtonView} */ createTile( character, name ) { const tile = new ButtonView( this.locale ); diff --git a/src/ui/specialcharactersnavigationview.js b/src/ui/specialcharactersnavigationview.js index 2bf7cdd..fd431c5 100644 --- a/src/ui/specialcharactersnavigationview.js +++ b/src/ui/specialcharactersnavigationview.js @@ -18,7 +18,7 @@ import { /** * A class representing the navigation part of the special characters UI. It is responsible - * for describing the feature and selecting a particular character group by the user. + * for describing the feature and allowing the user to select a particular character group. * * @extends module:ui/view~View */ @@ -66,7 +66,7 @@ export default class SpecialCharactersNavigationView extends View { } /** - * Returns a name of the character groups currently selected in the {@link #groupDropdownView}. + * Returns a name of the character group currently selected in the {@link #groupDropdownView}. * * @returns {String} */ From 8853c646ae42f2803632a5a30b3e2e04faa67938 Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Mon, 25 Nov 2019 17:18:01 +0100 Subject: [PATCH 25/43] Improved the character group dropdown panel positioning depending on the direction of the language. --- src/ui/specialcharactersnavigationview.js | 1 + tests/ui/specialcharactersnavigationview.js | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/ui/specialcharactersnavigationview.js b/src/ui/specialcharactersnavigationview.js index fd431c5..c830b28 100644 --- a/src/ui/specialcharactersnavigationview.js +++ b/src/ui/specialcharactersnavigationview.js @@ -49,6 +49,7 @@ export default class SpecialCharactersNavigationView extends View { * @member {module:ui/dropdown/dropdownview~DropdownView} */ this.groupDropdownView = this._createGroupDropdown( groupNames ); + this.groupDropdownView.panelPosition = locale.uiLanguageDirection === 'rtl' ? 'se' : 'sw'; this.setTemplate( { tag: 'div', diff --git a/tests/ui/specialcharactersnavigationview.js b/tests/ui/specialcharactersnavigationview.js index 09de898..76ad90a 100644 --- a/tests/ui/specialcharactersnavigationview.js +++ b/tests/ui/specialcharactersnavigationview.js @@ -63,6 +63,24 @@ describe( 'SpecialCharactersNavigationView', () => { expect( view.currentGroupName ).to.equal( 'groupA' ); } ); + it( 'has a right #panelPosition (LTR)', () => { + expect( groupDropdownView.panelPosition ).to.equal( 'sw' ); + } ); + + it( 'has a right #panelPosition (RTL)', () => { + const locale = { + uiLanguageDirection: 'rtl', + t: val => val + }; + + view = new SpecialCharactersNavigationView( locale, [ 'groupA', 'groupB' ] ); + view.render(); + + expect( view.groupDropdownView.panelPosition ).to.equal( 'se' ); + + view.destroy(); + } ); + it( 'binds its buttonView#label to #value', () => { expect( groupDropdownView.buttonView.label ).to.equal( 'groupA' ); From a6f492a4d120c34c29da6411514c350aac3806e9 Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Tue, 26 Nov 2019 10:12:23 +0100 Subject: [PATCH 26/43] Internal: There should be no rigorous check on label duplication. --- src/specialcharacters.js | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/specialcharacters.js b/src/specialcharacters.js index f3849d5..da2b414 100644 --- a/src/specialcharacters.js +++ b/src/specialcharacters.js @@ -8,7 +8,6 @@ */ import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; -import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror'; import SpecialCharactersUI from './specialcharactersui'; import SpecialCharactersEditing from './specialcharactersediting'; @@ -64,19 +63,6 @@ export default class SpecialCharacters extends Plugin { const group = this._getGroup( groupName ); for ( const item of items ) { - if ( this._characters.has( item.title ) ) { - /** - * The provided title for a special character is already. Titles for special characters must be unique. - * - * @error specialcharacters-duplicated-character-name - * @param {module:special-characters/specialcharacters~SpecialCharacterDefinition} item The invalid - * special character definition. - */ - throw new CKEditorError( - 'specialcharacters-duplicated-character-name: Duplicated special character title.', null, { item } - ); - } - group.add( item.title ); this._characters.set( item.title, item.character ); } From 45493ea7172e16c99faebc620e71ce50d44a41cc Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Tue, 26 Nov 2019 10:13:56 +0100 Subject: [PATCH 27/43] Internal: Added more plugins with new symbols. --- src/specialcharacterscurrency.js | 177 +++++++++ src/specialcharacterslatin.js | 529 +++++++++++++++++++++++++++ src/specialcharactersmathematical.js | 226 ++++++++++-- tests/manual/specialcharacters.js | 5 +- 4 files changed, 902 insertions(+), 35 deletions(-) create mode 100644 src/specialcharacterscurrency.js create mode 100644 src/specialcharacterslatin.js diff --git a/src/specialcharacterscurrency.js b/src/specialcharacterscurrency.js new file mode 100644 index 0000000..d60d60d --- /dev/null +++ b/src/specialcharacterscurrency.js @@ -0,0 +1,177 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/** + * @module special-characters/specialcharacters + */ + +import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; + +export default class SpecialCharactersCurrency extends Plugin { + init() { + this.editor.plugins.get( 'SpecialCharacters' ).addItems( 'Currency', [ + { + character: '$', + title: 'Dollar sign' + }, + { + character: '€', + title: 'Euro sign' + }, + { + character: '¥', + title: 'Yen sign' + }, + { + character: '£', + title: 'Pound sign' + }, + { + character: '¢', + title: 'Cent sign' + }, + { + character: '₠', + title: 'Euro-currency sign' + }, + { + character: '₡', + title: 'Colon sign' + }, + { + character: '₢', + title: 'Cruzeiro sign' + }, + { + character: '₣', + title: 'French franc sign' + }, + { + character: '₤', + title: 'Lira sign' + }, + { + character: '¤', + title: 'Currency sign' + }, + { + character: '₿', + title: 'Bitcoin sign' + }, + { + character: '₥', + title: 'Mill sign' + }, + { + character: '₦', + title: 'Naira sign' + }, + { + character: '₧', + title: 'Peseta sign' + }, + { + character: '₨', + title: 'Rupee sign' + }, + { + character: '₩', + title: 'Won sign' + }, + { + character: '₪', + title: 'New sheqel sign' + }, + { + character: '₫', + title: 'Dong sign' + }, + { + character: '₭', + title: 'Kip sign' + }, + { + character: '₮', + title: 'Tugrik sign' + }, + { + character: '₯', + title: 'Drachma sign' + }, + { + character: '₰', + title: 'German penny sign' + }, + { + character: '₱', + title: 'Peso sign' + }, + { + character: '₲', + title: 'Guarani sign' + }, + { + character: '₳', + title: 'Austral sign' + }, + { + character: '₴', + title: 'Hryvnia sign' + }, + { + character: '₵', + title: 'Cedi sign' + }, + { + character: '₶', + title: 'Livre tournois sign' + }, + { + character: '₷', + title: 'Spesmilo sign' + }, + { + character: '₸', + title: 'Tenge sign' + }, + { + character: '₹', + title: 'Indian rupee sign' + }, + { + character: '₺', + title: 'Turkish lira sign' + }, + { + character: '₻', + title: 'Nordic mark sign' + }, + { + character: '₼', + title: 'Manat sign' + }, + { + character: '₽', + title: 'Ruble sign' + }/* , + { + character: '円', + title: '' + }, + { + character: '元', + title: '' + }, + { + character: '圓', + title: '' + }, + { + character: '圆', + title: '' + } */ + ] ); + } +} diff --git a/src/specialcharacterslatin.js b/src/specialcharacterslatin.js new file mode 100644 index 0000000..a6032bb --- /dev/null +++ b/src/specialcharacterslatin.js @@ -0,0 +1,529 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/** + * @module special-characters/specialcharacters + */ + +import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; + +export default class SpecialCharactersLatin extends Plugin { + init() { + this.editor.plugins.get( 'SpecialCharacters' ).addItems( 'Latin', [ + { + character: 'Ā', + title: 'Latin capital letter a with macron' + }, + { + character: 'ā', + title: 'Latin small letter a with macron' + }, + { + character: 'Ă', + title: 'Latin capital letter a with breve' + }, + { + character: 'ă', + title: 'Latin small letter a with breve' + }, + { + character: 'Ą', + title: 'Latin capital letter a with ogonek' + }, + { + character: 'ą', + title: 'Latin small letter a with ogonek' + }, + { + character: 'Ć', + title: 'Latin capital letter c with acute' + }, + { + character: 'ć', + title: 'Latin small letter c with acute' + }, + { + character: 'Ĉ', + title: 'Latin capital letter c with circumflex' + }, + { + character: 'ĉ', + title: 'Latin small letter c with circumflex' + }, + { + character: 'Ċ', + title: 'Latin capital letter c with dot above' + }, + { + character: 'ċ', + title: 'Latin small letter c with dot above' + }, + { + character: 'Č', + title: 'Latin capital letter c with caron' + }, + { + character: 'č', + title: 'Latin small letter c with caron' + }, + { + character: 'Ď', + title: 'Latin capital letter d with caron' + }, + { + character: 'ď', + title: 'Latin small letter d with caron' + }, + { + character: 'Đ', + title: 'Latin capital letter d with stroke' + }, + { + character: 'đ', + title: 'Latin small letter d with stroke' + }, + { + character: 'Ē', + title: 'Latin capital letter e with macron' + }, + { + character: 'ē', + title: 'Latin small letter e with macron' + }, + { + character: 'Ĕ', + title: 'Latin capital letter e with breve' + }, + { + character: 'ĕ', + title: 'Latin small letter e with breve' + }, + { + character: 'Ė', + title: 'Latin capital letter e with dot above' + }, + { + character: 'ė', + title: 'Latin small letter e with dot above' + }, + { + character: 'Ę', + title: 'Latin capital letter e with ogonek' + }, + { + character: 'ę', + title: 'Latin small letter e with ogonek' + }, + { + character: 'Ě', + title: 'Latin capital letter e with caron' + }, + { + character: 'ě', + title: 'Latin small letter e with caron' + }, + { + character: 'Ĝ', + title: 'Latin capital letter g with circumflex' + }, + { + character: 'ĝ', + title: 'Latin small letter g with circumflex' + }, + { + character: 'Ğ', + title: 'Latin capital letter g with breve' + }, + { + character: 'ğ', + title: 'Latin small letter g with breve' + }, + { + character: 'Ġ', + title: 'Latin capital letter g with dot above' + }, + { + character: 'ġ', + title: 'Latin small letter g with dot above' + }, + { + character: 'Ģ', + title: 'Latin capital letter g with cedilla' + }, + { + character: 'ģ', + title: 'Latin small letter g with cedilla' + }, + { + character: 'Ĥ', + title: 'Latin capital letter h with circumflex' + }, + { + character: 'ĥ', + title: 'Latin small letter h with circumflex' + }, + { + character: 'Ħ', + title: 'Latin capital letter h with stroke' + }, + { + character: 'ħ', + title: 'Latin small letter h with stroke' + }, + { + character: 'Ĩ', + title: 'Latin capital letter i with tilde' + }, + { + character: 'ĩ', + title: 'Latin small letter i with tilde' + }, + { + character: 'Ī', + title: 'Latin capital letter i with macron' + }, + { + character: 'ī', + title: 'Latin small letter i with macron' + }, + { + character: 'Ĭ', + title: 'Latin capital letter i with breve' + }, + { + character: 'ĭ', + title: 'Latin small letter i with breve' + }, + { + character: 'Į', + title: 'Latin capital letter i with ogonek' + }, + { + character: 'į', + title: 'Latin small letter i with ogonek' + }, + { + character: 'İ', + title: 'Latin capital letter i with dot above' + }, + { + character: 'ı', + title: 'Latin small letter dotless i' + }, + { + character: 'IJ', + title: 'Latin capital ligature ij' + }, + { + character: 'ij', + title: 'Latin small ligature ij' + }, + { + character: 'Ĵ', + title: 'Latin capital letter j with circumflex' + }, + { + character: 'ĵ', + title: 'Latin small letter j with circumflex' + }, + { + character: 'Ķ', + title: 'Latin capital letter k with cedilla' + }, + { + character: 'ķ', + title: 'Latin small letter k with cedilla' + }, + { + character: 'ĸ', + title: 'Latin small letter kra' + }, + { + character: 'Ĺ', + title: 'Latin capital letter l with acute' + }, + { + character: 'ĺ', + title: 'Latin small letter l with acute' + }, + { + character: 'Ļ', + title: 'Latin capital letter l with cedilla' + }, + { + character: 'ļ', + title: 'Latin small letter l with cedilla' + }, + { + character: 'Ľ', + title: 'Latin capital letter l with caron' + }, + { + character: 'ľ', + title: 'Latin small letter l with caron' + }, + { + character: 'Ŀ', + title: 'Latin capital letter l with middle dot' + }, + { + character: 'ŀ', + title: 'Latin small letter l with middle dot' + }, + { + character: 'Ł', + title: 'Latin capital letter l with stroke' + }, + { + character: 'ł', + title: 'Latin small letter l with stroke' + }, + { + character: 'Ń', + title: 'Latin capital letter n with acute' + }, + { + character: 'ń', + title: 'Latin small letter n with acute' + }, + { + character: 'Ņ', + title: 'Latin capital letter n with cedilla' + }, + { + character: 'ņ', + title: 'Latin small letter n with cedilla' + }, + { + character: 'Ň', + title: 'Latin capital letter n with caron' + }, + { + character: 'ň', + title: 'Latin small letter n with caron' + }, + { + character: 'ʼn', + title: 'Latin small letter n preceded by apostrophe' + }, + { + character: 'Ŋ', + title: 'Latin capital letter eng' + }, + { + character: 'ŋ', + title: 'Latin small letter eng' + }, + { + character: 'Ō', + title: 'Latin capital letter o with macron' + }, + { + character: 'ō', + title: 'Latin small letter o with macron' + }, + { + character: 'Ŏ', + title: 'Latin capital letter o with breve' + }, + { + character: 'ŏ', + title: 'Latin small letter o with breve' + }, + { + character: 'Ő', + title: 'Latin capital letter o with double acute' + }, + { + character: 'ő', + title: 'Latin small letter o with double acute' + }, + { + character: 'Œ', + title: 'Latin capital ligature oe' + }, + { + character: 'œ', + title: 'Latin small ligature oe' + }, + { + character: 'Ŕ', + title: 'Latin capital letter r with acute' + }, + { + character: 'ŕ', + title: 'Latin small letter r with acute' + }, + { + character: 'Ŗ', + title: 'Latin capital letter r with cedilla' + }, + { + character: 'ŗ', + title: 'Latin small letter r with cedilla' + }, + { + character: 'Ř', + title: 'Latin capital letter r with caron' + }, + { + character: 'ř', + title: 'Latin small letter r with caron' + }, + { + character: 'Ś', + title: 'Latin capital letter s with acute' + }, + { + character: 'ś', + title: 'Latin small letter s with acute' + }, + { + character: 'Ŝ', + title: 'Latin capital letter s with circumflex' + }, + { + character: 'ŝ', + title: 'Latin small letter s with circumflex' + }, + { + character: 'Ş', + title: 'Latin capital letter s with cedilla' + }, + { + character: 'ş', + title: 'Latin small letter s with cedilla' + }, + { + character: 'Š', + title: 'Latin capital letter s with caron' + }, + { + character: 'š', + title: 'Latin small letter s with caron' + }, + { + character: 'Ţ', + title: 'Latin capital letter t with cedilla' + }, + { + character: 'ţ', + title: 'Latin small letter t with cedilla' + }, + { + character: 'Ť', + title: 'Latin capital letter t with caron' + }, + { + character: 'ť', + title: 'Latin small letter t with caron' + }, + { + character: 'Ŧ', + title: 'Latin capital letter t with stroke' + }, + { + character: 'ŧ', + title: 'Latin small letter t with stroke' + }, + { + character: 'Ũ', + title: 'Latin capital letter u with tilde' + }, + { + character: 'ũ', + title: 'Latin small letter u with tilde' + }, + { + character: 'Ū', + title: 'Latin capital letter u with macron' + }, + { + character: 'ū', + title: 'Latin small letter u with macron' + }, + { + character: 'Ŭ', + title: 'Latin capital letter u with breve' + }, + { + character: 'ŭ', + title: 'Latin small letter u with breve' + }, + { + character: 'Ů', + title: 'Latin capital letter u with ring above' + }, + { + character: 'ů', + title: 'Latin small letter u with ring above' + }, + { + character: 'Ű', + title: 'Latin capital letter u with double acute' + }, + { + character: 'ű', + title: 'Latin small letter u with double acute' + }, + { + character: 'Ų', + title: 'Latin capital letter u with ogonek' + }, + { + character: 'ų', + title: 'Latin small letter u with ogonek' + }, + { + character: 'Ŵ', + title: 'Latin capital letter w with circumflex' + }, + { + character: 'ŵ', + title: 'Latin small letter w with circumflex' + }, + { + character: 'Ŷ', + title: 'Latin capital letter y with circumflex' + }, + { + character: 'ŷ', + title: 'Latin small letter y with circumflex' + }, + { + character: 'Ÿ', + title: 'Latin capital letter y with diaeresis' + }, + { + character: 'Ź', + title: 'Latin capital letter z with acute' + }, + { + character: 'ź', + title: 'Latin small letter z with acute' + }, + { + character: 'Ż', + title: 'Latin capital letter z with dot above' + }, + { + character: 'ż', + title: 'Latin small letter z with dot above' + }, + { + character: 'Ž', + title: 'Latin capital letter z with caron' + }, + { + character: 'ž', + title: 'Latin small letter z with caron' + }, + { + character: 'ſ', + title: 'Latin small letter long s' + } + ] ); + } +} diff --git a/src/specialcharactersmathematical.js b/src/specialcharactersmathematical.js index 7072362..6149e69 100644 --- a/src/specialcharactersmathematical.js +++ b/src/specialcharactersmathematical.js @@ -12,40 +12,198 @@ import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; export default class SpecialCharactersMathematical extends Plugin { init() { this.editor.plugins.get( 'SpecialCharacters' ).addItems( 'Mathematical', [ - { title: 'greek small letter alpha', character: 'α' }, - { title: 'greek small letter beta', character: 'β' }, - { title: 'greek small letter delta', character: 'δ' }, - { title: 'greek small letter epsilon', character: 'ε' }, - { title: 'greek small letter theta', character: 'θ' }, - { title: 'greek small letter lamda', character: 'λ' }, - { title: 'greek small letter mu', character: 'μ' }, - { title: 'greek small letter pi', character: 'π' }, - { title: 'greek small letter phi', character: 'φ' }, - { title: 'greek small letter psi', character: 'ψ' }, - { title: 'greek capital letter omega', character: 'Ω' }, - { title: 'precedes', character: '≺' }, - { title: 'succeeds', character: '≻' }, - { title: 'precedes or equal to', character: '≼' }, - { title: 'succeeds or equal to', character: '≽' }, - { title: 'double precedes', character: '⪻' }, - { title: 'double succeeds', character: '⪼' }, - { title: 'less-than', character: '<' }, - { title: 'greater-than', character: '>' }, - { title: 'less-than or equal to', character: '≤' }, - { title: 'greater-than or equal to', character: '≥' }, - { title: 'equals colon', character: '≕' }, - { title: 'double colon equal', character: '⩴' }, - { title: 'identical to', character: '≡' }, - { title: 'not identical to', character: '≢' }, - { title: 'almost equal to', character: '≈' }, - { title: 'not almost equal to', character: '≉' }, - { title: 'almost equal or equal to', character: '≊' }, - { title: 'triple tilde', character: '≋' }, - { title: 'true', character: '⊨' }, - { title: 'not true', character: '⊭' }, - { title: 'for all', character: '∀' }, - { title: 'complement', character: '∁' }, - { title: 'there exists', character: '∃' } + { + character: '‹', + title: 'Single left-pointing angle quotation mark' + }, + { + character: '›', + title: 'Single right-pointing angle quotation mark' + }, + { + character: '«', + title: 'Left-pointing double angle quotation mark' + }, + { + character: '»', + title: 'Right-pointing double angle quotation mark' + }, + { + character: '<', + title: 'Less-than sign' + }, + { + character: '>', + title: 'Greater-than sign' + }, + { + character: '≤', + title: 'Less-than or equal to' + }, + { + character: '≥', + title: 'Greater-than or equal to' + }, + { + character: '–', + title: 'En dash' + }, + { + character: '—', + title: 'Em dash' + }, + { + character: '¯', + title: 'Macron' + }, + { + character: '‾', + title: 'Overline' + }, + { + character: '°', + title: 'Degree sign' + }, + { + character: '−', + title: 'Minus sign' + }, + { + character: '±', + title: 'Plus-minus sign' + }, + { + character: '÷', + title: 'Division sign' + }, + { + character: '⁄', + title: 'Fraction slash' + }, + { + character: '×', + title: 'Multiplication sign' + }, + { + character: 'ƒ', + title: 'Latin small letter f with hook' + }, + { + character: '∫', + title: 'Integral' + }, + { + character: '∑', + title: 'N-ary summation' + }, + { + character: '∞', + title: 'Infinity' + }, + { + character: '√', + title: 'Square root' + }, + { + character: '∼', + title: 'Tilde operator' + }, + { + character: '≅', + title: 'Approximately equal to' + }, + { + character: '≈', + title: 'Almost equal to' + }, + { + character: '≠', + title: 'Not equal to' + }, + { + character: '≡', + title: 'Identical to' + }, + { + character: '∈', + title: 'Element of' + }, + { + character: '∉', + title: 'Not an element of' + }, + { + character: '∋', + title: 'Contains as member' + }, + { + character: '∏', + title: 'N-ary product' + }, + { + character: '∧', + title: 'Logical and' + }, + { + character: '∨', + title: 'Logical or' + }, + { + character: '¬', + title: 'Not sign' + }, + { + character: '∩', + title: 'Intersection' + }, + { + character: '∪', + title: 'Union' + }, + { + character: '∂', + title: 'Partial differential' + }, + { + character: '∀', + title: 'For all' + }, + { + character: '∃', + title: 'There exists' + }, + { + character: '∅', + title: 'Empty set' + }, + { + character: '∇', + title: 'Nabla' + }, + { + character: '∗', + title: 'Asterisk operator' + }, + { + character: '∝', + title: 'Proportional to' + }, + { + character: '∠', + title: 'Angle' + }, + { + character: '¼', + title: 'Vulgar fraction one quarter' + }, + { + character: '½', + title: 'Vulgar fraction one half' + }, + { + character: '¾', + title: 'Vulgar fraction three quarters' + } ] ); } } diff --git a/tests/manual/specialcharacters.js b/tests/manual/specialcharacters.js index c943f7f..627827c 100644 --- a/tests/manual/specialcharacters.js +++ b/tests/manual/specialcharacters.js @@ -11,13 +11,16 @@ 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 SpecialCharacters from '../../src/specialcharacters'; +import SpecialCharactersCurrency from '../../src/specialcharacterscurrency'; import SpecialCharactersMathematical from '../../src/specialcharactersmathematical'; import SpecialCharactersArrows from '../../src/specialcharactersarrows'; +import SpecialCharactersLatin from '../../src/specialcharacterslatin'; ClassicEditor .create( document.querySelector( '#editor' ), { cloudServices: CS_CONFIG, - plugins: [ ArticlePluginSet, ImageUpload, EasyImage, SpecialCharacters, SpecialCharactersMathematical, SpecialCharactersArrows ], + plugins: [ ArticlePluginSet, ImageUpload, EasyImage, SpecialCharacters, SpecialCharactersCurrency, + SpecialCharactersMathematical, SpecialCharactersArrows, SpecialCharactersLatin ], toolbar: [ 'heading', '|', From f5849bdd0ef1ed9691e78ed71720210c2bd28f93 Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Tue, 26 Nov 2019 10:32:40 +0100 Subject: [PATCH 28/43] Internal: Introduced the SpecialCharactersEssentials plugin, which groups most common special characters setup. --- src/specialcharactersessentials.js | 29 ++++++++++++++++++++++++++++ tests/manual/specialcharacters.js | 8 ++------ tests/specialcharactersessentials.js | 22 +++++++++++++++++++++ 3 files changed, 53 insertions(+), 6 deletions(-) create mode 100644 src/specialcharactersessentials.js create mode 100644 tests/specialcharactersessentials.js diff --git a/src/specialcharactersessentials.js b/src/specialcharactersessentials.js new file mode 100644 index 0000000..3d41d00 --- /dev/null +++ b/src/specialcharactersessentials.js @@ -0,0 +1,29 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/** + * @module special-characters/specialcharacters + */ + +import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; + +import SpecialCharactersCurrency from './specialcharacterscurrency'; +import SpecialCharactersMathematical from './specialcharactersmathematical'; +import SpecialCharactersArrows from './specialcharactersarrows'; +import SpecialCharactersLatin from './specialcharacterslatin'; + +export default class SpecialCharactersEssentials extends Plugin { + /** + * @inheritDoc + */ + static get requires() { + return [ + SpecialCharactersCurrency, + SpecialCharactersMathematical, + SpecialCharactersArrows, + SpecialCharactersLatin + ]; + } +} diff --git a/tests/manual/specialcharacters.js b/tests/manual/specialcharacters.js index 627827c..44d1113 100644 --- a/tests/manual/specialcharacters.js +++ b/tests/manual/specialcharacters.js @@ -11,16 +11,12 @@ 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 SpecialCharacters from '../../src/specialcharacters'; -import SpecialCharactersCurrency from '../../src/specialcharacterscurrency'; -import SpecialCharactersMathematical from '../../src/specialcharactersmathematical'; -import SpecialCharactersArrows from '../../src/specialcharactersarrows'; -import SpecialCharactersLatin from '../../src/specialcharacterslatin'; +import SpecialCharactersEssentials from '../../src/specialcharactersessentials'; ClassicEditor .create( document.querySelector( '#editor' ), { cloudServices: CS_CONFIG, - plugins: [ ArticlePluginSet, ImageUpload, EasyImage, SpecialCharacters, SpecialCharactersCurrency, - SpecialCharactersMathematical, SpecialCharactersArrows, SpecialCharactersLatin ], + plugins: [ ArticlePluginSet, ImageUpload, EasyImage, SpecialCharacters, SpecialCharactersEssentials ], toolbar: [ 'heading', '|', diff --git a/tests/specialcharactersessentials.js b/tests/specialcharactersessentials.js new file mode 100644 index 0000000..fa9cb6a --- /dev/null +++ b/tests/specialcharactersessentials.js @@ -0,0 +1,22 @@ +/** + * @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 SpecialCharactersEssentials from '../src/specialcharactersessentials'; + +import SpecialCharactersCurrency from '../src/specialcharacterscurrency'; +import SpecialCharactersMathematical from '../src/specialcharactersmathematical'; +import SpecialCharactersArrows from '../src/specialcharactersarrows'; +import SpecialCharactersLatin from '../src/specialcharacterslatin'; + +describe( 'SpecialCharactersEssentials', () => { + it( 'includes other required plugins', () => { + expect( SpecialCharactersEssentials.requires ).to.deep.equal( [ + SpecialCharactersCurrency, + SpecialCharactersMathematical, + SpecialCharactersArrows, + SpecialCharactersLatin + ] ); + } ); +} ); From 4c891ce5845870f0b986c37964cdad81f2dc034b Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Tue, 26 Nov 2019 10:34:43 +0100 Subject: [PATCH 29/43] Internal: Error should not be thrown on title duplication. It will be fairly easy to get title duplicate if you want to put same character in two tabs for convenience. --- tests/specialcharacters.js | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/tests/specialcharacters.js b/tests/specialcharacters.js index 205df87..e172f4e 100644 --- a/tests/specialcharacters.js +++ b/tests/specialcharacters.js @@ -6,7 +6,6 @@ import SpecialCharacters from '../src/specialcharacters'; import SpecialCharactersUI from '../src/specialcharactersui'; import SpecialCharactersEditing from '../src/specialcharactersediting'; -import { expectToThrowCKEditorError } from '@ckeditor/ckeditor5-utils/tests/_utils/utils'; describe( 'SpecialCharacters', () => { let plugin; @@ -37,15 +36,6 @@ describe( 'SpecialCharacters', () => { expect( plugin._characters.has( 'arrow left' ) ).to.equal( true ); expect( plugin._characters.has( 'arrow right' ) ).to.equal( true ); } ); - - it( 'throw an error when a title is not a unique value', () => { - expectToThrowCKEditorError( () => { - plugin.addItems( 'Arrows', [ - { title: 'arrow left', character: '←' }, - { title: 'arrow left', character: '←' } - ] ); - }, /^specialcharacters-duplicated-character-name/ ) - } ); } ); describe( 'getGroups()', () => { From 1bf9281d5bfdc5bb2559c3eac87db6b2e677b45b Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Tue, 26 Nov 2019 10:39:01 +0100 Subject: [PATCH 30/43] The dataset has changed. --- tests/specialcharactersui.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/specialcharactersui.js b/tests/specialcharactersui.js index a00c0c4..286fddc 100644 --- a/tests/specialcharactersui.js +++ b/tests/specialcharactersui.js @@ -91,7 +91,7 @@ describe( 'SpecialCharactersUI', () => { sinon.assert.calledOnce( executeSpy ); sinon.assert.calledOnce( focusSpy ); sinon.assert.calledWithExactly( executeSpy.firstCall, 'specialCharacters', { - item: 'greek small letter delta' + item: 'Left-pointing double angle quotation mark' } ); } ); @@ -118,7 +118,7 @@ describe( 'SpecialCharactersUI', () => { it( 'is updated when navigation view fires #execute', () => { const navigation = dropdown.panelView.children.first; - expect( grid.tiles.get( 0 ).label ).to.equal( 'α' ); + expect( grid.tiles.get( 0 ).label ).to.equal( '‹' ); navigation.groupDropdownView.fire( new EventInfo( { label: 'Arrows' }, 'execute' ) ); expect( grid.tiles.get( 0 ).label ).to.equal( '⇐' ); From 6843d2c3766d4bb6c2fc557a5227adf9401f95bc Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Tue, 26 Nov 2019 12:45:43 +0100 Subject: [PATCH 31/43] Internal: Added text special characters plugin. --- src/specialcharactersessentials.js | 2 + src/specialcharactersmathematical.js | 16 ----- src/specialcharacterstext.js | 101 +++++++++++++++++++++++++++ tests/specialcharactersessentials.js | 2 + tests/specialcharactersui.js | 4 +- 5 files changed, 107 insertions(+), 18 deletions(-) create mode 100644 src/specialcharacterstext.js diff --git a/src/specialcharactersessentials.js b/src/specialcharactersessentials.js index 3d41d00..3242804 100644 --- a/src/specialcharactersessentials.js +++ b/src/specialcharactersessentials.js @@ -13,6 +13,7 @@ import SpecialCharactersCurrency from './specialcharacterscurrency'; import SpecialCharactersMathematical from './specialcharactersmathematical'; import SpecialCharactersArrows from './specialcharactersarrows'; import SpecialCharactersLatin from './specialcharacterslatin'; +import SpecialCharactersText from './specialcharacterstext'; export default class SpecialCharactersEssentials extends Plugin { /** @@ -21,6 +22,7 @@ export default class SpecialCharactersEssentials extends Plugin { static get requires() { return [ SpecialCharactersCurrency, + SpecialCharactersText, SpecialCharactersMathematical, SpecialCharactersArrows, SpecialCharactersLatin diff --git a/src/specialcharactersmathematical.js b/src/specialcharactersmathematical.js index 6149e69..3971a86 100644 --- a/src/specialcharactersmathematical.js +++ b/src/specialcharactersmathematical.js @@ -12,22 +12,6 @@ import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; export default class SpecialCharactersMathematical extends Plugin { init() { this.editor.plugins.get( 'SpecialCharacters' ).addItems( 'Mathematical', [ - { - character: '‹', - title: 'Single left-pointing angle quotation mark' - }, - { - character: '›', - title: 'Single right-pointing angle quotation mark' - }, - { - character: '«', - title: 'Left-pointing double angle quotation mark' - }, - { - character: '»', - title: 'Right-pointing double angle quotation mark' - }, { character: '<', title: 'Less-than sign' diff --git a/src/specialcharacterstext.js b/src/specialcharacterstext.js new file mode 100644 index 0000000..86bf6ae --- /dev/null +++ b/src/specialcharacterstext.js @@ -0,0 +1,101 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/** + * @module special-characters/specialcharacters + */ + +import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; + +export default class SpecialCharactersText extends Plugin { + init() { + this.editor.plugins.get( 'SpecialCharacters' ).addItems( 'Text', [ + { + character: '‹', + title: 'Single left-pointing angle quotation mark' + }, + { + character: '›', + title: 'Single right-pointing angle quotation mark' + }, + { + character: '«', + title: 'Left-pointing double angle quotation mark' + }, + { + character: '»', + title: 'Right-pointing double angle quotation mark' + }, + { + character: '‘', + title: 'Left single quotation mark' + }, + { + character: '’', + title: 'Right single quotation mark' + }, + { + character: '“', + title: 'Left double quotation mark' + }, + { + character: '”', + title: 'Right double quotation mark' + }, + { + character: '‚', + title: 'Single low-9 quotation mark' + }, + { + character: '„', + title: 'Double low-9 quotation mark' + }, + { + character: '¡', + title: 'Inverted exclamation mark' + }, + { + character: '¿', + title: 'Inverted question mark' + }, + { + character: '‥', + title: 'Two dot leader' + }, + { + character: '…', + title: 'Horizontal ellipsis' + }, + { + character: '‡', + title: 'Double dagger' + }, + { + character: '‰', + title: 'Per mille sign' + }, + { + character: '‱', + title: 'Per ten thousand sign' + }, + { + character: '‼', + title: 'Double exclamation mark' + }, + { + character: '⁈', + title: 'Question exclamation mark' + }, + { + character: '⁉', + title: 'Exclamation question mark' + }, + { + character: '⁇', + title: 'Double question mark' + } + ] ); + } +} diff --git a/tests/specialcharactersessentials.js b/tests/specialcharactersessentials.js index fa9cb6a..354ad60 100644 --- a/tests/specialcharactersessentials.js +++ b/tests/specialcharactersessentials.js @@ -6,6 +6,7 @@ import SpecialCharactersEssentials from '../src/specialcharactersessentials'; import SpecialCharactersCurrency from '../src/specialcharacterscurrency'; +import SpecialCharactersText from '../src/specialcharacterstext'; import SpecialCharactersMathematical from '../src/specialcharactersmathematical'; import SpecialCharactersArrows from '../src/specialcharactersarrows'; import SpecialCharactersLatin from '../src/specialcharacterslatin'; @@ -14,6 +15,7 @@ describe( 'SpecialCharactersEssentials', () => { it( 'includes other required plugins', () => { expect( SpecialCharactersEssentials.requires ).to.deep.equal( [ SpecialCharactersCurrency, + SpecialCharactersText, SpecialCharactersMathematical, SpecialCharactersArrows, SpecialCharactersLatin diff --git a/tests/specialcharactersui.js b/tests/specialcharactersui.js index 286fddc..b6e9729 100644 --- a/tests/specialcharactersui.js +++ b/tests/specialcharactersui.js @@ -91,7 +91,7 @@ describe( 'SpecialCharactersUI', () => { sinon.assert.calledOnce( executeSpy ); sinon.assert.calledOnce( focusSpy ); sinon.assert.calledWithExactly( executeSpy.firstCall, 'specialCharacters', { - item: 'Left-pointing double angle quotation mark' + item: 'Less-than or equal to' } ); } ); @@ -118,7 +118,7 @@ describe( 'SpecialCharactersUI', () => { it( 'is updated when navigation view fires #execute', () => { const navigation = dropdown.panelView.children.first; - expect( grid.tiles.get( 0 ).label ).to.equal( '‹' ); + expect( grid.tiles.get( 0 ).label ).to.equal( '<' ); navigation.groupDropdownView.fire( new EventInfo( { label: 'Arrows' }, 'execute' ) ); expect( grid.tiles.get( 0 ).label ).to.equal( '⇐' ); From 498edd52314e8d850682278fb670e05a10f7ebdc Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Tue, 26 Nov 2019 13:23:40 +0100 Subject: [PATCH 32/43] Internal: Added test coverage. --- tests/specialcharactersarrows.js | 56 ++++++++++++++++++++++++++++++ tests/specialcharacterscurrency.js | 56 ++++++++++++++++++++++++++++++ tests/specialcharacterslatin.js | 56 ++++++++++++++++++++++++++++++ tests/specialcharacterstext.js | 56 ++++++++++++++++++++++++++++++ 4 files changed, 224 insertions(+) create mode 100644 tests/specialcharactersarrows.js create mode 100644 tests/specialcharacterscurrency.js create mode 100644 tests/specialcharacterslatin.js create mode 100644 tests/specialcharacterstext.js diff --git a/tests/specialcharactersarrows.js b/tests/specialcharactersarrows.js new file mode 100644 index 0000000..28b0f5f --- /dev/null +++ b/tests/specialcharactersarrows.js @@ -0,0 +1,56 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/* globals document */ + +import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor'; +import SpecialCharacters from '../src/specialcharacters'; +import SpecialCharactersArrows from '../src/specialcharactersarrows'; +import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils'; + +describe( 'SpecialCharactersArrows', () => { + testUtils.createSinonSandbox(); + + let addItemsSpy, addItemsFirstCallArgs; + + beforeEach( () => { + const editorElement = document.createElement( 'div' ); + + addItemsSpy = sinon.spy( SpecialCharacters.prototype, 'addItems' ); + + document.body.appendChild( editorElement ); + return ClassicTestEditor + .create( editorElement, { + plugins: [ SpecialCharacters, SpecialCharactersArrows ] + } ) + .then( () => { + addItemsFirstCallArgs = addItemsSpy.args[ 0 ]; + } ); + } ); + + afterEach( () => { + addItemsSpy.restore(); + } ); + + it( 'adds new items', () => { + expect( addItemsSpy.callCount ).to.equal( 1 ); + } ); + + it( 'properly names the category', () => { + expect( addItemsFirstCallArgs[ 0 ] ).to.be.equal( 'Arrows' ); + } ); + + it( 'adds proper characters', () => { + expect( addItemsFirstCallArgs[ 1 ] ).to.deep.include( { + title: 'rightwards double arrow', + character: '⇒' + } ); + + expect( addItemsFirstCallArgs[ 1 ] ).to.deep.include( { + title: 'rightwards arrow to bar', + character: '⇥' + } ); + } ); +} ); diff --git a/tests/specialcharacterscurrency.js b/tests/specialcharacterscurrency.js new file mode 100644 index 0000000..eba96c3 --- /dev/null +++ b/tests/specialcharacterscurrency.js @@ -0,0 +1,56 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/* globals document */ + +import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor'; +import SpecialCharacters from '../src/specialcharacters'; +import SpecialCharactersCurrency from '../src/specialcharacterscurrency'; +import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils'; + +describe( 'SpecialCharactersCurrency', () => { + testUtils.createSinonSandbox(); + + let addItemsSpy, addItemsFirstCallArgs; + + beforeEach( () => { + const editorElement = document.createElement( 'div' ); + + addItemsSpy = sinon.spy( SpecialCharacters.prototype, 'addItems' ); + + document.body.appendChild( editorElement ); + return ClassicTestEditor + .create( editorElement, { + plugins: [ SpecialCharacters, SpecialCharactersCurrency ] + } ) + .then( () => { + addItemsFirstCallArgs = addItemsSpy.args[ 0 ]; + } ); + } ); + + afterEach( () => { + addItemsSpy.restore(); + } ); + + it( 'adds new items', () => { + expect( addItemsSpy.callCount ).to.equal( 1 ); + } ); + + it( 'properly names the category', () => { + expect( addItemsFirstCallArgs[ 0 ] ).to.be.equal( 'Currency' ); + } ); + + it( 'adds proper characters', () => { + expect( addItemsFirstCallArgs[ 1 ] ).to.deep.include( { + character: '¢', + title: 'Cent sign' + } ); + + expect( addItemsFirstCallArgs[ 1 ] ).to.deep.include( { + character: '₿', + title: 'Bitcoin sign' + } ); + } ); +} ); diff --git a/tests/specialcharacterslatin.js b/tests/specialcharacterslatin.js new file mode 100644 index 0000000..404dc68 --- /dev/null +++ b/tests/specialcharacterslatin.js @@ -0,0 +1,56 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/* globals document */ + +import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor'; +import SpecialCharacters from '../src/specialcharacters'; +import SpecialCharactersLatin from '../src/specialcharacterslatin'; +import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils'; + +describe( 'SpecialCharactersLatin', () => { + testUtils.createSinonSandbox(); + + let addItemsSpy, addItemsFirstCallArgs; + + beforeEach( () => { + const editorElement = document.createElement( 'div' ); + + addItemsSpy = sinon.spy( SpecialCharacters.prototype, 'addItems' ); + + document.body.appendChild( editorElement ); + return ClassicTestEditor + .create( editorElement, { + plugins: [ SpecialCharacters, SpecialCharactersLatin ] + } ) + .then( () => { + addItemsFirstCallArgs = addItemsSpy.args[ 0 ]; + } ); + } ); + + afterEach( () => { + addItemsSpy.restore(); + } ); + + it( 'adds new items', () => { + expect( addItemsSpy.callCount ).to.equal( 1 ); + } ); + + it( 'properly names the category', () => { + expect( addItemsFirstCallArgs[ 0 ] ).to.be.equal( 'Latin' ); + } ); + + it( 'adds proper characters', () => { + expect( addItemsFirstCallArgs[ 1 ] ).to.deep.include( { + character: 'Ō', + title: 'Latin capital letter o with macron' + } ); + + expect( addItemsFirstCallArgs[ 1 ] ).to.deep.include( { + character: 'Ō', + title: 'Latin capital letter o with macron' + } ); + } ); +} ); diff --git a/tests/specialcharacterstext.js b/tests/specialcharacterstext.js new file mode 100644 index 0000000..a8e2be4 --- /dev/null +++ b/tests/specialcharacterstext.js @@ -0,0 +1,56 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/* globals document */ + +import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor'; +import SpecialCharacters from '../src/specialcharacters'; +import SpecialCharactersText from '../src/specialcharacterstext'; +import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils'; + +describe( 'SpecialCharactersText', () => { + testUtils.createSinonSandbox(); + + let addItemsSpy, addItemsFirstCallArgs; + + beforeEach( () => { + const editorElement = document.createElement( 'div' ); + + addItemsSpy = sinon.spy( SpecialCharacters.prototype, 'addItems' ); + + document.body.appendChild( editorElement ); + return ClassicTestEditor + .create( editorElement, { + plugins: [ SpecialCharacters, SpecialCharactersText ] + } ) + .then( () => { + addItemsFirstCallArgs = addItemsSpy.args[ 0 ]; + } ); + } ); + + afterEach( () => { + addItemsSpy.restore(); + } ); + + it( 'adds new items', () => { + expect( addItemsSpy.callCount ).to.equal( 1 ); + } ); + + it( 'properly names the category', () => { + expect( addItemsFirstCallArgs[ 0 ] ).to.be.equal( 'Text' ); + } ); + + it( 'adds proper characters', () => { + expect( addItemsFirstCallArgs[ 1 ] ).to.deep.include( { + character: '…', + title: 'Horizontal ellipsis' + } ); + + expect( addItemsFirstCallArgs[ 1 ] ).to.deep.include( { + character: '“', + title: 'Left double quotation mark' + } ); + } ); +} ); From 48278c4487ab4ab4dacc5dcc171a051e3ac3cb14 Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Tue, 26 Nov 2019 13:37:19 +0100 Subject: [PATCH 33/43] Internal: Symbol tiles are now using title attirbute to show a tooltip. --- src/ui/charactergridview.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/ui/charactergridview.js b/src/ui/charactergridview.js index c61a222..2988eee 100644 --- a/src/ui/charactergridview.js +++ b/src/ui/charactergridview.js @@ -71,6 +71,14 @@ export default class CharacterGridView extends View { class: 'ck-character-grid__tile' } ); + // Labels are vital for the users to understand what character they're looking at. + // For now we're using native title attribute for that, see #5817. + tile.extendTemplate( { + attributes: { + title: name + } + } ); + tile.on( 'execute', () => { this.fire( 'execute', { name } ); } ); From bc5f58928401b590659ce629ccd4e199f6b12ccd Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Tue, 26 Nov 2019 14:54:35 +0100 Subject: [PATCH 34/43] Internal: Updated insert special character command references in the UI plugin. --- src/specialcharactersui.js | 7 ++----- tests/specialcharactersui.js | 5 +++-- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/specialcharactersui.js b/src/specialcharactersui.js index 7effae8..94bedf4 100644 --- a/src/specialcharactersui.js +++ b/src/specialcharactersui.js @@ -10,7 +10,6 @@ import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; import { createDropdown } from '@ckeditor/ckeditor5-ui/src/dropdown/utils'; import specialCharactersIcon from '../theme/icons/specialcharacters.svg'; -import InsertSpecialCharacterCommand from './insertspecialcharactercommand'; import CharacterGridView from './ui/charactergridview'; import SpecialCharactersNavigationView from './ui/specialcharactersnavigationview'; @@ -33,9 +32,6 @@ export default class SpecialCharactersUI extends Plugin { const editor = this.editor; const t = editor.t; const specialCharsPlugin = editor.plugins.get( 'SpecialCharacters' ); - const command = new InsertSpecialCharacterCommand( editor ); - - editor.commands.add( 'specialCharacters', command ); // Add the `specialCharacters` dropdown button to feature components. editor.ui.componentFactory.add( 'specialCharacters', locale => { @@ -44,6 +40,7 @@ export default class SpecialCharactersUI extends Plugin { const gridView = new CharacterGridView( this.locale, { columns: 10 } ); + const command = editor.commands.get( 'insertSpecialCharacter' ); gridView.delegate( 'execute' ).to( dropdownView ); @@ -65,7 +62,7 @@ export default class SpecialCharactersUI extends Plugin { // Insert a special character when a tile was clicked. dropdownView.on( 'execute', ( evt, data ) => { - editor.execute( 'specialCharacters', { item: data.name } ); + editor.execute( 'insertSpecialCharacter', { item: data.name } ); editor.editing.view.focus(); } ); diff --git a/tests/specialcharactersui.js b/tests/specialcharactersui.js index b6e9729..50bfd06 100644 --- a/tests/specialcharactersui.js +++ b/tests/specialcharactersui.js @@ -33,7 +33,7 @@ describe( 'SpecialCharactersUI', () => { } ) .then( newEditor => { editor = newEditor; - command = editor.commands.get( 'specialCharacters' ); + command = editor.commands.get( 'insertSpecialCharacter' ); } ); } ); @@ -78,6 +78,7 @@ describe( 'SpecialCharactersUI', () => { command.isEnabled = false; expect( dropdown.isEnabled ).to.be.false; + command.isEnabled = true; } ); } ); @@ -90,7 +91,7 @@ describe( 'SpecialCharactersUI', () => { sinon.assert.calledOnce( executeSpy ); sinon.assert.calledOnce( focusSpy ); - sinon.assert.calledWithExactly( executeSpy.firstCall, 'specialCharacters', { + sinon.assert.calledWithExactly( executeSpy.firstCall, 'insertSpecialCharacter', { item: 'Less-than or equal to' } ); } ); From 00b926b6b2b9b4f45f27fbd111631b8afd28b4d3 Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Wed, 27 Nov 2019 08:20:02 +0100 Subject: [PATCH 35/43] Docs: Minor correction in the API docs. --- src/insertspecialcharactercommand.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/insertspecialcharactercommand.js b/src/insertspecialcharactercommand.js index 9bf1cc2..a98f8cc 100644 --- a/src/insertspecialcharactercommand.js +++ b/src/insertspecialcharactercommand.js @@ -34,7 +34,7 @@ export default class InsertSpecialCharacterCommand extends Command { /** * @param {Object} options - * @param {String} options.item A title of the special character that should be added to the editor. + * @param {String} options.item An id of the special character that should be added to the editor. */ execute( options ) { const editor = this.editor; From 5f59ae5de97e98cce6079bad3d0ef18b8a271463 Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Wed, 27 Nov 2019 08:21:54 +0100 Subject: [PATCH 36/43] Internal: Simplified command execution. --- src/insertspecialcharactercommand.js | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/insertspecialcharactercommand.js b/src/insertspecialcharactercommand.js index a98f8cc..b32bfbb 100644 --- a/src/insertspecialcharactercommand.js +++ b/src/insertspecialcharactercommand.js @@ -38,17 +38,7 @@ export default class InsertSpecialCharacterCommand extends Command { */ execute( options ) { const editor = this.editor; - const item = options.item; - - if ( !item ) { - return; - } - - const character = editor.plugins.get( 'SpecialCharacters' ).getCharacter( item ); - - if ( !character ) { - return; - } + const character = editor.plugins.get( 'SpecialCharacters' ).getCharacter( options.item ); this._inputCommand.execute( { text: character } ); } From 5ca5364b95be7f3fb8ca185f333ed3ed5d802858 Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Wed, 27 Nov 2019 08:23:52 +0100 Subject: [PATCH 37/43] Docs: Fixed return type annotation for the SpecialCharacters.getCharactersForGroup method. --- src/specialcharacters.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/specialcharacters.js b/src/specialcharacters.js index da2b414..5f83716 100644 --- a/src/specialcharacters.js +++ b/src/specialcharacters.js @@ -81,7 +81,7 @@ export default class SpecialCharacters extends Plugin { * Returns a collection of symbol names (titles). * * @param {String} groupName - * @returns {Set|undefined} + * @returns {Set.|undefined} */ getCharactersForGroup( groupName ) { return this._groups.get( groupName ); From 8d6ff6552f6b262d82f081b5a0df107d15184003 Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Wed, 27 Nov 2019 08:37:13 +0100 Subject: [PATCH 38/43] Internal: Added unit test case for subsequent adding items with the same group names. --- src/specialcharacters.js | 1 - tests/specialcharacters.js | 17 +++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/specialcharacters.js b/src/specialcharacters.js index 5f83716..c60da51 100644 --- a/src/specialcharacters.js +++ b/src/specialcharacters.js @@ -103,7 +103,6 @@ export default class SpecialCharacters extends Plugin { * @param {String} groupName A name of group to create. */ _getGroup( groupName ) { - /* istanbul ignore else */ if ( !this._groups.has( groupName ) ) { this._groups.set( groupName, new Set() ); } diff --git a/tests/specialcharacters.js b/tests/specialcharacters.js index e172f4e..570686d 100644 --- a/tests/specialcharacters.js +++ b/tests/specialcharacters.js @@ -54,6 +54,23 @@ describe( 'SpecialCharacters', () => { } ); } ); + describe( 'addItems()', () => { + it( 'works with subsequent calls to the same group', () => { + plugin.addItems( 'Mathematical', [ { + title: 'dot', + character: '.' + } ] ); + + plugin.addItems( 'Mathematical', [ { + title: ',', + character: 'comma' + } ] ); + + const groups = [ ...plugin.getGroups() ]; + expect( groups ).to.deep.equal( [ 'Mathematical' ] ); + } ); + } ); + describe( 'getCharactersForGroup()', () => { it( 'returns a collection of defined special characters names', () => { plugin.addItems( 'Mathematical', [ From 6c297175056e2c94aba9c5aff6dc2c4e68e003cb Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Wed, 27 Nov 2019 08:41:53 +0100 Subject: [PATCH 39/43] Docs: API docs adjustments. --- src/specialcharacters.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/specialcharacters.js b/src/specialcharacters.js index c60da51..9852f27 100644 --- a/src/specialcharacters.js +++ b/src/specialcharacters.js @@ -111,13 +111,9 @@ export default class SpecialCharacters extends Plugin { } } -// TODO: Make an interface for "SpecialCharacters" class. -// It should provide methods: `addItems()`, `getGroups()`, `getCharactersForGroup()`, `getCharacter()`. - /** * @typedef {Object} module:special-characters/specialcharacters~SpecialCharacterDefinition * * @property {String} title A unique title of the character. - * * @property {String} character A symbol that should be inserted to the editor. */ From dfc6206de989c545c449382de2a14828adce4b24 Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Wed, 27 Nov 2019 08:45:34 +0100 Subject: [PATCH 40/43] Docs: Marked _getGroup as private method. --- src/specialcharacters.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/specialcharacters.js b/src/specialcharacters.js index 9852f27..9a5b6f7 100644 --- a/src/specialcharacters.js +++ b/src/specialcharacters.js @@ -100,6 +100,7 @@ export default class SpecialCharacters extends Plugin { /** * Returns a group of special characters. If the group with the specified name does not exist, it will be created. * + * @private * @param {String} groupName A name of group to create. */ _getGroup( groupName ) { From 9afc6d0094bb558485837ba6e4cbe7e55366c1b8 Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Wed, 27 Nov 2019 08:53:31 +0100 Subject: [PATCH 41/43] Docs: Added explicit inheriting docs for init / constructor methods. Also added short foreword to the SpecialCharactersEssentials plugin. --- src/specialcharacters.js | 3 +++ src/specialcharactersarrows.js | 3 +++ src/specialcharacterscurrency.js | 3 +++ src/specialcharactersessentials.js | 3 +++ src/specialcharacterslatin.js | 3 +++ src/specialcharactersmathematical.js | 3 +++ src/specialcharacterstext.js | 3 +++ src/specialcharactersui.js | 3 +++ 8 files changed, 24 insertions(+) diff --git a/src/specialcharacters.js b/src/specialcharacters.js index 9a5b6f7..a52a587 100644 --- a/src/specialcharacters.js +++ b/src/specialcharacters.js @@ -19,6 +19,9 @@ import '../theme/specialcharacters.css'; * @extends module:core/plugin~Plugin */ export default class SpecialCharacters extends Plugin { + /** + * @inheritDoc + */ constructor( editor ) { super( editor ); diff --git a/src/specialcharactersarrows.js b/src/specialcharactersarrows.js index 329e6e1..068e044 100644 --- a/src/specialcharactersarrows.js +++ b/src/specialcharactersarrows.js @@ -10,6 +10,9 @@ import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; export default class SpecialCharactersArrows extends Plugin { + /** + * @inheritDoc + */ init() { this.editor.plugins.get( 'SpecialCharacters' ).addItems( 'Arrows', [ { title: 'leftwards double arrow', character: '⇐' }, diff --git a/src/specialcharacterscurrency.js b/src/specialcharacterscurrency.js index d60d60d..2c130a0 100644 --- a/src/specialcharacterscurrency.js +++ b/src/specialcharacterscurrency.js @@ -10,6 +10,9 @@ import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; export default class SpecialCharactersCurrency extends Plugin { + /** + * @inheritDoc + */ init() { this.editor.plugins.get( 'SpecialCharacters' ).addItems( 'Currency', [ { diff --git a/src/specialcharactersessentials.js b/src/specialcharactersessentials.js index 3242804..574e5fb 100644 --- a/src/specialcharactersessentials.js +++ b/src/specialcharactersessentials.js @@ -15,6 +15,9 @@ import SpecialCharactersArrows from './specialcharactersarrows'; import SpecialCharactersLatin from './specialcharacterslatin'; import SpecialCharactersText from './specialcharacterstext'; +/** + * A plugin combining basic set of characters for the special characters plugin. + */ export default class SpecialCharactersEssentials extends Plugin { /** * @inheritDoc diff --git a/src/specialcharacterslatin.js b/src/specialcharacterslatin.js index a6032bb..c4812b4 100644 --- a/src/specialcharacterslatin.js +++ b/src/specialcharacterslatin.js @@ -10,6 +10,9 @@ import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; export default class SpecialCharactersLatin extends Plugin { + /** + * @inheritDoc + */ init() { this.editor.plugins.get( 'SpecialCharacters' ).addItems( 'Latin', [ { diff --git a/src/specialcharactersmathematical.js b/src/specialcharactersmathematical.js index 3971a86..2c6fd71 100644 --- a/src/specialcharactersmathematical.js +++ b/src/specialcharactersmathematical.js @@ -10,6 +10,9 @@ import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; export default class SpecialCharactersMathematical extends Plugin { + /** + * @inheritDoc + */ init() { this.editor.plugins.get( 'SpecialCharacters' ).addItems( 'Mathematical', [ { diff --git a/src/specialcharacterstext.js b/src/specialcharacterstext.js index 86bf6ae..a19320c 100644 --- a/src/specialcharacterstext.js +++ b/src/specialcharacterstext.js @@ -10,6 +10,9 @@ import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; export default class SpecialCharactersText extends Plugin { + /** + * @inheritDoc + */ init() { this.editor.plugins.get( 'SpecialCharacters' ).addItems( 'Text', [ { diff --git a/src/specialcharactersui.js b/src/specialcharactersui.js index 94bedf4..3a7f5f7 100644 --- a/src/specialcharactersui.js +++ b/src/specialcharactersui.js @@ -28,6 +28,9 @@ export default class SpecialCharactersUI extends Plugin { return 'SpecialCharactersUI'; } + /** + * @inheritDoc + */ init() { const editor = this.editor; const t = editor.t; From 0688423436eb5d60ef4e9e58cd68b78eecafe504 Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Wed, 27 Nov 2019 08:58:26 +0100 Subject: [PATCH 42/43] Docs: Added basic API docs for the SpecialCharactersEssentials plugin. --- src/specialcharactersessentials.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/specialcharactersessentials.js b/src/specialcharactersessentials.js index 574e5fb..6db9794 100644 --- a/src/specialcharactersessentials.js +++ b/src/specialcharactersessentials.js @@ -17,6 +17,13 @@ import SpecialCharactersText from './specialcharacterstext'; /** * A plugin combining basic set of characters for the special characters plugin. + * + * ClassicEditor + * .create( { + * plugins: [ ..., SpecialCharacters, SpecialCharactersEssentials ], + * } ) + * .then( ... ) + * .catch( ... ); */ export default class SpecialCharactersEssentials extends Plugin { /** From d4208bcbfa17d43859219c1c688b5971c8d662db Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Wed, 27 Nov 2019 09:07:49 +0100 Subject: [PATCH 43/43] Internal: Special character plugins that define character now require base plugin out of the box, thus there's no need to explicitly require the base plugin. --- src/specialcharactersarrows.js | 10 ++++++++++ src/specialcharacterscurrency.js | 10 ++++++++++ src/specialcharactersessentials.js | 2 ++ src/specialcharacterslatin.js | 10 ++++++++++ src/specialcharactersmathematical.js | 10 ++++++++++ src/specialcharacterstext.js | 10 ++++++++++ tests/specialcharactersessentials.js | 2 ++ 7 files changed, 54 insertions(+) diff --git a/src/specialcharactersarrows.js b/src/specialcharactersarrows.js index 068e044..7494a26 100644 --- a/src/specialcharactersarrows.js +++ b/src/specialcharactersarrows.js @@ -8,8 +8,18 @@ */ import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; +import SpecialCharacters from './specialcharacters'; export default class SpecialCharactersArrows extends Plugin { + /** + * @inheritDoc + */ + static get requires() { + return [ + SpecialCharacters + ]; + } + /** * @inheritDoc */ diff --git a/src/specialcharacterscurrency.js b/src/specialcharacterscurrency.js index 2c130a0..5c1d05d 100644 --- a/src/specialcharacterscurrency.js +++ b/src/specialcharacterscurrency.js @@ -8,8 +8,18 @@ */ import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; +import SpecialCharacters from './specialcharacters'; export default class SpecialCharactersCurrency extends Plugin { + /** + * @inheritDoc + */ + static get requires() { + return [ + SpecialCharacters + ]; + } + /** * @inheritDoc */ diff --git a/src/specialcharactersessentials.js b/src/specialcharactersessentials.js index 6db9794..30262d7 100644 --- a/src/specialcharactersessentials.js +++ b/src/specialcharactersessentials.js @@ -9,6 +9,7 @@ import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; +import SpecialCharacters from './specialcharacters'; import SpecialCharactersCurrency from './specialcharacterscurrency'; import SpecialCharactersMathematical from './specialcharactersmathematical'; import SpecialCharactersArrows from './specialcharactersarrows'; @@ -31,6 +32,7 @@ export default class SpecialCharactersEssentials extends Plugin { */ static get requires() { return [ + SpecialCharacters, SpecialCharactersCurrency, SpecialCharactersText, SpecialCharactersMathematical, diff --git a/src/specialcharacterslatin.js b/src/specialcharacterslatin.js index c4812b4..2cccff0 100644 --- a/src/specialcharacterslatin.js +++ b/src/specialcharacterslatin.js @@ -8,8 +8,18 @@ */ import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; +import SpecialCharacters from './specialcharacters'; export default class SpecialCharactersLatin extends Plugin { + /** + * @inheritDoc + */ + static get requires() { + return [ + SpecialCharacters + ]; + } + /** * @inheritDoc */ diff --git a/src/specialcharactersmathematical.js b/src/specialcharactersmathematical.js index 2c6fd71..19aaa39 100644 --- a/src/specialcharactersmathematical.js +++ b/src/specialcharactersmathematical.js @@ -8,8 +8,18 @@ */ import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; +import SpecialCharacters from './specialcharacters'; export default class SpecialCharactersMathematical extends Plugin { + /** + * @inheritDoc + */ + static get requires() { + return [ + SpecialCharacters + ]; + } + /** * @inheritDoc */ diff --git a/src/specialcharacterstext.js b/src/specialcharacterstext.js index a19320c..aa04a26 100644 --- a/src/specialcharacterstext.js +++ b/src/specialcharacterstext.js @@ -8,8 +8,18 @@ */ import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; +import SpecialCharacters from './specialcharacters'; export default class SpecialCharactersText extends Plugin { + /** + * @inheritDoc + */ + static get requires() { + return [ + SpecialCharacters + ]; + } + /** * @inheritDoc */ diff --git a/tests/specialcharactersessentials.js b/tests/specialcharactersessentials.js index 354ad60..8185efc 100644 --- a/tests/specialcharactersessentials.js +++ b/tests/specialcharactersessentials.js @@ -5,6 +5,7 @@ import SpecialCharactersEssentials from '../src/specialcharactersessentials'; +import SpecialCharacters from '../src/specialcharacters'; import SpecialCharactersCurrency from '../src/specialcharacterscurrency'; import SpecialCharactersText from '../src/specialcharacterstext'; import SpecialCharactersMathematical from '../src/specialcharactersmathematical'; @@ -14,6 +15,7 @@ import SpecialCharactersLatin from '../src/specialcharacterslatin'; describe( 'SpecialCharactersEssentials', () => { it( 'includes other required plugins', () => { expect( SpecialCharactersEssentials.requires ).to.deep.equal( [ + SpecialCharacters, SpecialCharactersCurrency, SpecialCharactersText, SpecialCharactersMathematical,