Skip to content
This repository has been archived by the owner on Jun 26, 2020. It is now read-only.

Commit

Permalink
Merge pull request #9 from ckeditor/i/5817
Browse files Browse the repository at this point in the history
Feature: Added the special character info bar that makes the feature more user–friendly. Closes ckeditor/ckeditor5#5817.
  • Loading branch information
oleq authored Jan 2, 2020
2 parents 0af9a0a + 0d6b56d commit 5d36463
Show file tree
Hide file tree
Showing 7 changed files with 286 additions and 15 deletions.
18 changes: 15 additions & 3 deletions src/specialcharacters.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { createDropdown } from '@ckeditor/ckeditor5-ui/src/dropdown/utils';
import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
import SpecialCharactersNavigationView from './ui/specialcharactersnavigationview';
import CharacterGridView from './ui/charactergridview';
import CharacterInfoView from './ui/characterinfoview';

import specialCharactersIcon from '../theme/icons/specialcharacters.svg';
import '../theme/specialcharacters.css';
Expand Down Expand Up @@ -82,12 +83,22 @@ export default class SpecialCharacters extends Plugin {
specialCharsGroups.push( ALL_SPECIAL_CHARACTERS_GROUP );

const navigationView = new SpecialCharactersNavigationView( locale, specialCharsGroups );
const gridView = new CharacterGridView( this.locale, {
columns: 10
} );
const gridView = new CharacterGridView( locale );
const infoView = new CharacterInfoView( locale );

gridView.delegate( 'execute' ).to( dropdownView );

gridView.on( 'tileHover', ( evt, data ) => {
infoView.set( data );
} );

dropdownView.on( 'change:isOpen', () => {
infoView.set( {
character: null,
name: null
} );
} );

// Set the initial content of the special characters grid.
this._updateGrid( navigationView.currentGroupName, gridView );

Expand All @@ -112,6 +123,7 @@ export default class SpecialCharacters extends Plugin {

dropdownView.panelView.children.add( navigationView );
dropdownView.panelView.children.add( gridView );
dropdownView.panelView.children.add( infoView );

return dropdownView;
} );
Expand Down
17 changes: 17 additions & 0 deletions src/ui/charactergridview.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,16 @@ export default class CharacterGridView extends View {
* @param {String} data.name A name of the tile that caused the event (e.g. "greek small letter epsilon").
* @param {String} data.character A human-readable character displayed as label (e.g. "ε").
*/

/**
* Fired when a mouse or other pointing device caused the cursor to move onto any {@link #tiles grid tile}
* (similar to the native `mouseover` DOM event).
*
* @event tileHover
* @param {Object} data Additional information about the event.
* @param {String} data.name A name of the tile that caused the event (e.g. "greek small letter epsilon").
* @param {String} data.character A human-readable character displayed as label (e.g. "ε").
*/
}

/**
Expand All @@ -88,9 +98,16 @@ export default class CharacterGridView extends View {
tile.extendTemplate( {
attributes: {
title: name
},
on: {
mouseover: tile.bindTemplate.to( 'mouseover' )
}
} );

tile.on( 'mouseover', () => {
this.fire( 'tileHover', { name, character } );
} );

tile.on( 'execute', () => {
this.fire( 'execute', { name, character } );
} );
Expand Down
111 changes: 111 additions & 0 deletions src/ui/characterinfoview.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/**
* @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/characterinfoview
*/

import View from '@ckeditor/ckeditor5-ui/src/view';

import '../../theme/characterinfo.css';

/**
* The view displaying detailed information about a special character glyph, e.g. upon
* hovering it with a mouse.
*
* @extends module:ui/view~View
*/
export default class CharacterInfoView extends View {
constructor( locale ) {
super( locale );

const bind = this.bindTemplate;

/**
* The character which info is displayed by the view. For instance,
* "∑" or "¿".
*
* @observable
* @member {String|null} #character
*/
this.set( 'character', null );

/**
* The name of the {@link #character}. For instance,
* "N-ary summation" or "Inverted question mark".
*
* @observable
* @member {String|null} #name
*/
this.set( 'name', null );

/**
* The "Unicode string" of the {@link #character}. For instance,
* "U+0061".
*
* @observable
* @readonly
* @member {String} #code
*/
this.bind( 'code' ).to( this, 'character', characterToUnicodeString );

this.setTemplate( {
tag: 'div',
children: [
{
tag: 'span',
attributes: {
class: [
'ck-character-info__name'
]
},
children: [
{
// Note: ZWSP to prevent vertical collapsing.
text: bind.to( 'name', name => name ? name : '\u200B' )
}
]
},
{
tag: 'span',
attributes: {
class: [
'ck-character-info__code'
]
},
children: [
{
text: bind.to( 'code' )
}
]
}
],
attributes: {
class: [
'ck',
'ck-character-info'
]
}
} );
}
}

// Converts a character into a "Unicode string", for instance:
//
// "$" -> "U+0024"
//
// Returns empty string when character is `null`.
//
// @param {String} character
// @returns {String}
function characterToUnicodeString( character ) {
if ( character === null ) {
return '';
}

const hexCode = character.codePointAt( 0 ).toString( 16 );

return 'U+' + ( '0000' + hexCode ).slice( -4 );
}
61 changes: 49 additions & 12 deletions tests/specialcharacters.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import SpecialCharactersMathematical from '../src/specialcharactersmathematical'
import SpecialCharactersArrows from '../src/specialcharactersarrows';
import SpecialCharactersNavigationView from '../src/ui/specialcharactersnavigationview';
import CharacterGridView from '../src/ui/charactergridview';
import CharacterInfoView from '../src/ui/characterinfoview';
import specialCharactersIcon from '../theme/icons/specialcharacters.svg';
import { expectToThrowCKEditorError } from '@ckeditor/ckeditor5-utils/tests/_utils/utils';

Expand Down Expand Up @@ -82,7 +83,11 @@ describe( 'SpecialCharacters', () => {
} );

it( 'has a grid view', () => {
expect( dropdown.panelView.children.last ).to.be.instanceOf( CharacterGridView );
expect( dropdown.panelView.children.get( 1 ) ).to.be.instanceOf( CharacterGridView );
} );

it( 'has a character info view', () => {
expect( dropdown.panelView.children.last ).to.be.instanceOf( CharacterInfoView );
} );

describe( '#buttonView', () => {
Expand All @@ -102,7 +107,7 @@ describe( 'SpecialCharacters', () => {
} );

it( 'executes a command and focuses the editing view', () => {
const grid = dropdown.panelView.children.last;
const grid = dropdown.panelView.children.get( 1 );
const executeSpy = sinon.stub( editor, 'execute' );
const focusSpy = sinon.stub( editor.editing.view, 'focus' );

Expand All @@ -119,7 +124,7 @@ describe( 'SpecialCharacters', () => {
let grid;

beforeEach( () => {
grid = dropdown.panelView.children.last;
grid = dropdown.panelView.children.get( 1 );
} );

it( 'delegates #execute to the dropdown', () => {
Expand All @@ -144,6 +149,34 @@ describe( 'SpecialCharacters', () => {
expect( grid.tiles.get( 0 ).label ).to.equal( '⇐' );
} );
} );

describe( 'character info view', () => {
let grid, characterInfo;

beforeEach( () => {
grid = dropdown.panelView.children.get( 1 );
characterInfo = dropdown.panelView.children.last;
} );

it( 'is empty when the dropdown was shown', () => {
dropdown.fire( 'change:isOpen' );

expect( characterInfo.character ).to.equal( null );
expect( characterInfo.name ).to.equal( null );
expect( characterInfo.code ).to.equal( '' );
} );

it( 'is updated when the tile fires #mouseover', () => {
const tile = grid.tiles.get( 0 );

tile.fire( 'mouseover' );

expect( tile.label ).to.equal( '<' );
expect( characterInfo.character ).to.equal( '<' );
expect( characterInfo.name ).to.equal( 'Less-than sign' );
expect( characterInfo.code ).to.equal( 'U+003c' );
} );
} );
} );
} );

Expand All @@ -163,15 +196,19 @@ describe( 'SpecialCharacters', () => {
} );

it( 'works with subsequent calls to the same group', () => {
plugin.addItems( 'Mathematical', [ {
title: 'dot',
character: '.'
} ] );

plugin.addItems( 'Mathematical', [ {
title: ',',
character: 'comma'
} ] );
plugin.addItems( 'Mathematical', [
{
title: 'dot',
character: '.'
}
] );

plugin.addItems( 'Mathematical', [
{
title: ',',
character: 'comma'
}
] );

const groups = [ ...plugin.getGroups() ];
expect( groups ).to.deep.equal( [ 'Mathematical' ] );
Expand Down
11 changes: 11 additions & 0 deletions tests/ui/charactergridview.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,5 +63,16 @@ describe( 'CharacterGridView', () => {
sinon.assert.calledOnce( spy );
sinon.assert.calledWithExactly( spy, sinon.match.any, { name: 'foo bar baz', character: 'ε' } );
} );

it( 'delegates #tileHover from the tile to the grid on hover the tile', () => {
const tile = view.createTile( 'ε', 'foo bar baz' );
const spy = sinon.spy();

view.on( 'tileHover', spy );
tile.fire( 'mouseover' );

sinon.assert.calledOnce( spy );
sinon.assert.calledWithExactly( spy, sinon.match.any, { name: 'foo bar baz', character: 'ε' } );
} );
} );
} );
74 changes: 74 additions & 0 deletions tests/ui/characterinfoview.js
Original file line number Diff line number Diff line change
@@ -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
*/

import CharacterInfoView from '../../src/ui/characterinfoview';
import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';

describe( 'CharacterInfoView', () => {
let view;

testUtils.createSinonSandbox();

beforeEach( () => {
view = new CharacterInfoView();
view.render();
} );

afterEach( () => {
view.destroy();
} );

describe( 'constructor()', () => {
describe( '#character', () => {
it( 'is defined', () => {
expect( view.character ).to.equal( null );
} );
} );

describe( '#name', () => {
it( 'is defined', () => {
expect( view.name ).to.equal( null );
} );
} );

describe( '#code', () => {
it( 'is defined', () => {
expect( view.code ).to.equal( '' );
} );

it( 'is bound to #character', () => {
view.set( 'character', 'A' );

expect( view.code ).to.equal( 'U+0041' );
} );
} );

describe( '#element', () => {
it( 'is being created from template', () => {
expect( view.element.classList.contains( 'ck' ) ).to.be.true;
expect( view.element.classList.contains( 'ck-character-info' ) ).to.be.true;

expect( view.element.firstElementChild.classList.contains( 'ck-character-info__name' ) ).to.be.true;
expect( view.element.lastElementChild.classList.contains( 'ck-character-info__code' ) ).to.be.true;
} );

it( 'is being updated when #code and #name have changed', () => {
const infoEl = view.element.firstElementChild;
const codeEl = view.element.lastElementChild;

expect( infoEl.innerText ).to.equal( '\u200B' );
expect( codeEl.innerText ).to.equal( '' );

view.set( {
character: 'A',
name: 'SYMBOL: A'
} );

expect( infoEl.innerText ).to.equal( 'SYMBOL: A' );
expect( codeEl.innerText ).to.equal( 'U+0041' );
} );
} );
} );
} );
9 changes: 9 additions & 0 deletions theme/characterinfo.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* 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-character-info {
display: flex;
justify-content: space-between;
}

0 comments on commit 5d36463

Please sign in to comment.