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 #43 from ckeditor/t/ckeditor5/1151
Browse files Browse the repository at this point in the history
Feature: Integrated the text alignment feature with different editor content directions (LTR and RTL). See ckeditor/ckeditor5#1151.
  • Loading branch information
Reinmar authored Aug 12, 2019
2 parents 20683e0 + dc5f1a7 commit edc7d8b
Show file tree
Hide file tree
Showing 13 changed files with 267 additions and 32 deletions.
6 changes: 5 additions & 1 deletion docs/features/text-alignment.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ The {@link module:alignment/alignment~Alignment} feature enables support for tex

## Configuring alignment options

It is possible to configure which alignment options are available in the editor by setting the {@link module:alignment/alignment~AlignmentConfig#options `alignment.options`} configuration option. You can choose from `'left'`, `'right'`, `'center'` and `'justify'`; note that `'left'` should always be included.
It is possible to configure which alignment options are available in the editor by setting the {@link module:alignment/alignment~AlignmentConfig#options `alignment.options`} configuration option. You can choose from `'left'`, `'right'`, `'center'` and `'justify'`.

<info-box>
Note that the `'left'` option should always be included for the <abbr title="left–to–right">LTR</abbr> content. Similarly, the `'right'` option should always be included for the <abbr title="right-to-left">RTL</abbr> content. Learn more about {@link features/ui-language#setting-the-language-of-the-content configuring language of the editor content}.
</info-box>

For example, the following editor will support only two alignment options: to the left and to the right:

Expand Down
5 changes: 3 additions & 2 deletions src/alignment.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,9 @@ export default class Alignment extends Plugin {
*
* The available options are: `'left'`, `'right'`, `'center'` and `'justify'`. Other values are ignored.
*
* **Note:** It is recommended to always use `'left'` as it is the default value which the user should
* normally be able to choose.
* **Note:** It is recommended to always use `'left'` or `'right'` as these are default values which the user should
* normally be able to choose depending on the
* {@glink features/ui-language#setting-the-language-of-the-content language of the editor content}.
*
* ClassicEditor
* .create( editorElement, {
Expand Down
11 changes: 9 additions & 2 deletions src/alignmentcommand.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ export default class AlignmentCommand extends Command {
* @inheritDoc
*/
refresh() {
const editor = this.editor;
const locale = editor.locale;
const firstBlock = first( this.editor.model.document.selection.getSelectedBlocks() );

// As first check whether to enable or disable the command as the value will always be false if the command cannot be enabled.
Expand All @@ -36,7 +38,11 @@ export default class AlignmentCommand extends Command {
* @readonly
* @member {String} #value
*/
this.value = ( this.isEnabled && firstBlock.hasAttribute( 'alignment' ) ) ? firstBlock.getAttribute( 'alignment' ) : 'left';
if ( this.isEnabled && firstBlock.hasAttribute( 'alignment' ) ) {
this.value = firstBlock.getAttribute( 'alignment' );
} else {
this.value = locale.contentLanguageDirection === 'rtl' ? 'right' : 'left';
}
}

/**
Expand All @@ -50,6 +56,7 @@ export default class AlignmentCommand extends Command {
*/
execute( options = {} ) {
const editor = this.editor;
const locale = editor.locale;
const model = editor.model;
const doc = model.document;

Expand All @@ -64,7 +71,7 @@ export default class AlignmentCommand extends Command {
// - default (should not be stored in model as it will bloat model data)
// - equal to currently set
// - or no value is passed - denotes default alignment.
const removeAlignment = isDefault( value ) || currentAlignment === value || !value;
const removeAlignment = isDefault( value, locale ) || currentAlignment === value || !value;

if ( removeAlignment ) {
removeAlignmentFromSelection( blocks, writer );
Expand Down
3 changes: 2 additions & 1 deletion src/alignmentediting.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export default class AlignmentEditing extends Plugin {
*/
init() {
const editor = this.editor;
const locale = editor.locale;
const schema = editor.model.schema;

// Filter out unsupported options.
Expand All @@ -43,7 +44,7 @@ export default class AlignmentEditing extends Plugin {
schema.extend( '$block', { allowAttributes: 'alignment' } );
editor.model.schema.setAttributeProperties( 'alignment', { isFormatting: true } );

const definition = _buildDefinition( enabledOptions.filter( option => !isDefault( option ) ) );
const definition = _buildDefinition( enabledOptions.filter( option => !isDefault( option, locale ) ) );

editor.conversion.attributeToAttribute( definition );

Expand Down
4 changes: 2 additions & 2 deletions src/alignmentui.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,8 @@ export default class AlignmentUI extends Plugin {
}
} );

// The default icon is align left as we do not support RTL yet (see #3).
const defaultIcon = alignLeftIcon;
// The default icon depends on the direction of the content.
const defaultIcon = locale.contentLanguageDirection === 'rtl' ? alignRightIcon : alignLeftIcon;

// Change icon to reflect current selection's alignment.
dropdownView.buttonView.bind( 'icon' ).toMany( buttons, 'isOn', ( ...areActive ) => {
Expand Down
13 changes: 10 additions & 3 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,19 @@ export function isSupported( option ) {
}

/**
* Checks whether alignment is the default one.
* Checks whether alignment is the default one considering the direction
* of the editor content.
*
* @param {String} alignment The name of the alignment to check.
* @param {module:utils/locale~Locale} locale The {@link module:core/editor/editor~Editor#locale} instance.
* @returns {Boolean}
*/
export function isDefault( alignment ) {
export function isDefault( alignment, locale ) {
// Right now only LTR is supported so the 'left' value is always the default one.
return alignment === 'left';

if ( locale.contentLanguageDirection == 'rtl' ) {
return alignment === 'right';
} else {
return alignment === 'left';
}
}
50 changes: 48 additions & 2 deletions tests/alignmentcommand.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,33 @@ describe( 'AlignmentCommand', () => {
expect( command ).to.have.property( 'value', 'center' );
} );

it( 'is set to default alignment when selection is in block with default alignment', () => {
it( 'is set to default alignment when selection is in block with default alignment (LTR content)', () => {
setModelData( model, '<paragraph>x[]x</paragraph>' );

expect( command ).to.have.property( 'value', 'left' );
} );

it( 'is set to default alignment when selection is in block with default alignment (RTL content)', () => {
return ModelTestEditor.create( {
language: {
content: 'ar'
}
} ).then( newEditor => {
model = newEditor.model;
command = new AlignmentCommand( newEditor );
newEditor.commands.add( 'alignment', command );

model.schema.register( 'paragraph', { inheritAllFrom: '$block' } );
model.schema.register( 'div', { inheritAllFrom: '$block' } );
model.schema.extend( 'paragraph', { allowAttributes: 'alignment' } );

setModelData( model, '<paragraph>x[]x</paragraph>' );

expect( command ).to.have.property( 'value', 'right' );

return newEditor.destroy();
} );
} );
} );

describe( 'isEnabled', () => {
Expand All @@ -75,14 +97,38 @@ describe( 'AlignmentCommand', () => {
expect( getModelData( model ) ).to.equal( '<paragraph alignment="center">x[]x</paragraph>' );
} );

it( 'should remove alignment from single block element if already has one', () => {
it( 'should remove alignment from single block element if already has one (LTR content)', () => {
setModelData( model, '<paragraph alignment="center">x[]x</paragraph>' );

editor.execute( 'alignment', { value: 'left' } );

expect( getModelData( model ) ).to.equal( '<paragraph>x[]x</paragraph>' );
} );

it( 'should remove alignment from single block element if already has one (RTL content)', () => {
return ModelTestEditor.create( {
language: {
content: 'ar'
}
} ).then( newEditor => {
model = newEditor.model;
command = new AlignmentCommand( newEditor );
newEditor.commands.add( 'alignment', command );

model.schema.register( 'paragraph', { inheritAllFrom: '$block' } );
model.schema.register( 'div', { inheritAllFrom: '$block' } );
model.schema.extend( 'paragraph', { allowAttributes: 'alignment' } );

setModelData( model, '<paragraph alignment="center">x[]x</paragraph>' );

newEditor.execute( 'alignment', { value: 'right' } );

expect( getModelData( model ) ).to.equal( '<paragraph>x[]x</paragraph>' );

return newEditor.destroy();
} );
} );

it( 'adds alignment to all selected blocks', () => {
setModelData( model, '<paragraph>x[x</paragraph><paragraph>xx</paragraph><paragraph>x]x</paragraph>' );

Expand Down
118 changes: 105 additions & 13 deletions tests/alignmentediting.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,62 @@ describe( 'AlignmentEditing', () => {
} );

describe( 'left alignment', () => {
it( 'adds converters to the data pipeline', () => {
const data = '<p style="text-align:left;">x</p>';
describe( 'LTR content', () => {
it( 'adds converters to the data pipeline', () => {
const data = '<p style="text-align:left;">x</p>';

editor.setData( data );
editor.setData( data );

expect( getModelData( model ) ).to.equal( '<paragraph>[]x</paragraph>' );
expect( editor.getData() ).to.equal( '<p>x</p>' );
expect( getModelData( model ) ).to.equal( '<paragraph>[]x</paragraph>' );
expect( editor.getData() ).to.equal( '<p>x</p>' );
} );

it( 'adds a converter to the view pipeline', () => {
setModelData( model, '<paragraph alignment="left">[]x</paragraph>' );

expect( editor.getData() ).to.equal( '<p>x</p>' );
} );
} );

describe( 'RTL content', () => {
it( 'adds converters to the data pipeline', () => {
return VirtualTestEditor
.create( {
language: {
content: 'ar'
},
plugins: [ AlignmentEditing, Paragraph ]
} )
.then( newEditor => {
const model = newEditor.model;
const data = '<p style="text-align:left;">x</p>';

newEditor.setData( data );

expect( getModelData( model ) ).to.equal( '<paragraph alignment="left">[]x</paragraph>' );
expect( newEditor.getData() ).to.equal( '<p style="text-align:left;">x</p>' );

return newEditor.destroy();
} );
} );

it( 'adds a converter to the view pipeline', () => {
return VirtualTestEditor
.create( {
language: {
content: 'ar'
},
plugins: [ AlignmentEditing, Paragraph ]
} )
.then( newEditor => {
const model = newEditor.model;

setModelData( model, '<paragraph alignment="left">[]x</paragraph>' );
expect( newEditor.getData() ).to.equal( '<p style="text-align:left;">x</p>' );

return newEditor.destroy();
} );
} );
} );

it( 'adds a converter to the view pipeline for removing attribute', () => {
Expand Down Expand Up @@ -125,19 +174,62 @@ describe( 'AlignmentEditing', () => {
} );

describe( 'right alignment', () => {
it( 'adds converters to the data pipeline', () => {
const data = '<p style="text-align:right;">x</p>';
describe( 'LTR content', () => {
it( 'adds converters to the data pipeline', () => {
const data = '<p style="text-align:right;">x</p>';

editor.setData( data );
editor.setData( data );

expect( getModelData( model ) ).to.equal( '<paragraph alignment="right">[]x</paragraph>' );
expect( editor.getData() ).to.equal( data );
expect( getModelData( model ) ).to.equal( '<paragraph alignment="right">[]x</paragraph>' );
expect( editor.getData() ).to.equal( data );
} );

it( 'adds a converter to the view pipeline', () => {
setModelData( model, '<paragraph alignment="right">[]x</paragraph>' );

expect( editor.getData() ).to.equal( '<p style="text-align:right;">x</p>' );
} );
} );

it( 'adds a converter to the view pipeline', () => {
setModelData( model, '<paragraph alignment="right">[]x</paragraph>' );
describe( 'RTL content', () => {
it( 'adds converters to the data pipeline', () => {
return VirtualTestEditor
.create( {
language: {
content: 'ar'
},
plugins: [ AlignmentEditing, Paragraph ]
} )
.then( newEditor => {
const model = newEditor.model;
const data = '<p style="text-align:right;">x</p>';

newEditor.setData( data );

expect( getModelData( model ) ).to.equal( '<paragraph>[]x</paragraph>' );
expect( newEditor.getData() ).to.equal( '<p>x</p>' );

return newEditor.destroy();
} );
} );

expect( editor.getData() ).to.equal( '<p style="text-align:right;">x</p>' );
it( 'adds a converter to the view pipeline', () => {
return VirtualTestEditor
.create( {
language: {
content: 'ar'
},
plugins: [ AlignmentEditing, Paragraph ]
} )
.then( newEditor => {
const model = newEditor.model;

setModelData( model, '<paragraph alignment="right">[]x</paragraph>' );
expect( newEditor.getData() ).to.equal( '<p>x</p>' );

return newEditor.destroy();
} );
} );
} );
} );

Expand Down
24 changes: 23 additions & 1 deletion tests/alignmentui.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import AlignmentEditing from '../src/alignmentediting';
import AlignmentUI from '../src/alignmentui';

import alignLeftIcon from '../theme/icons/align-left.svg';
import alignRightIcon from '../theme/icons/align-right.svg';

import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';

Expand Down Expand Up @@ -284,10 +285,31 @@ describe( 'Alignment UI', () => {
expect( items.includes( 'Justify' ) ).to.be.true;
} );

it( 'should have default icon set', () => {
it( 'should have default icon set (LTR content)', () => {
expect( dropdown.buttonView.icon ).to.equal( alignLeftIcon );
} );

it( 'should have default icon set (RTL content)', () => {
const element = document.createElement( 'div' );
document.body.appendChild( element );

return ClassicTestEditor
.create( element, {
language: {
content: 'ar'
},
plugins: [ AlignmentEditing, AlignmentUI ],
alignment: { options: [ 'center', 'justify' ] }
} )
.then( newEditor => {
dropdown = newEditor.ui.componentFactory.create( 'alignment' );

expect( dropdown.buttonView.icon ).to.equal( alignRightIcon );

return newEditor.destroy();
} );
} );

it( 'should change icon to active alignment', () => {
command.value = 'center';

Expand Down
9 changes: 9 additions & 0 deletions tests/manual/rtl.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<div id="editor">
<p>This should be aligned to the right</p>

<p style="text-align:left">This should be aligned to the left.</p>

<p style="text-align:center">This should be centered.</p>

<p style="text-align:justify">This should be justified but also to the right. This should be justified but also to the right. This should be justified but also to the right.</p>
</div>
Loading

0 comments on commit edc7d8b

Please sign in to comment.