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 #51 from ckeditor/t/9
Browse files Browse the repository at this point in the history
Fix: Enhanced how selection labels for widgets are defined. Closes #9.
  • Loading branch information
Reinmar authored Mar 3, 2017
2 parents 925a538 + 5e752d2 commit 5c1897d
Show file tree
Hide file tree
Showing 9 changed files with 133 additions and 71 deletions.
30 changes: 0 additions & 30 deletions src/image/converters.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import ModelElement from '@ckeditor/ckeditor5-engine/src/model/element';
import ModelPosition from '@ckeditor/ckeditor5-engine/src/model/position';
import modelWriter from '@ckeditor/ckeditor5-engine/src/model/writer';
import { isImageWidget } from './utils';

/**
* Returns function that converts image view representation:
Expand Down Expand Up @@ -71,35 +70,6 @@ export function viewToModelImage() {
};
}

/**
* Returns model to view selection converter. This converter is applied after default selection conversion is made.
* It creates fake view selection when {@link module:engine/view/selection~Selection#getSelectedElement} returns instance
* of image widget.
*
* @param {Function} t {@link module:utils/locale~Locale#t Locale#t function} used to translate default fake selection's label.
* @returns {Function}
*/
export function modelToViewSelection( t ) {
return ( evt, data, consumable, conversionApi ) => {
const viewSelection = conversionApi.viewSelection;
const selectedElement = viewSelection.getSelectedElement();

if ( !selectedElement || !isImageWidget( selectedElement ) ) {
return;
}

let fakeSelectionLabel = t( 'image widget' );
const imgElement = selectedElement.getChild( 0 );
const altText = imgElement.getAttribute( 'alt' );

if ( altText ) {
fakeSelectionLabel = `${ altText } ${ fakeSelectionLabel }`;
}

viewSelection.setFake( true, { label: fakeSelectionLabel } );
};
}

/**
* Creates image attribute converter for provided model conversion dispatchers.
*
Expand Down
8 changes: 3 additions & 5 deletions src/image/imageengine.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
import buildModelConverter from '@ckeditor/ckeditor5-engine/src/conversion/buildmodelconverter';
import WidgetEngine from '../widget/widgetengine';
import { viewToModelImage, modelToViewSelection, createImageAttributeConverter } from './converters';
import { viewToModelImage, createImageAttributeConverter } from './converters';
import { toImageWidget } from './utils';
import ViewContainerElement from '@ckeditor/ckeditor5-engine/src/view/containerelement';
import ViewEmptyElement from '@ckeditor/ckeditor5-engine/src/view/emptyelement';
Expand Down Expand Up @@ -39,6 +39,7 @@ export default class ImageEngine extends Plugin {
const schema = doc.schema;
const data = editor.data;
const editing = editor.editing;
const t = editor.t;

// Configure schema.
schema.registerItem( 'image' );
Expand All @@ -54,16 +55,13 @@ export default class ImageEngine extends Plugin {
// Build converter from model to view for editing pipeline.
buildModelConverter().for( editing.modelToView )
.fromElement( 'image' )
.toElement( () => toImageWidget( createImageViewElement() ) );
.toElement( () => toImageWidget( createImageViewElement(), t( 'image widget' ) ) );

createImageAttributeConverter( [ editing.modelToView, data.modelToView ], 'src' );
createImageAttributeConverter( [ editing.modelToView, data.modelToView ], 'alt' );

// Converter for figure element from view to model.
data.viewToModel.on( 'element:figure', viewToModelImage() );

// Creates fake selection label if selection is placed around image widget.
editing.modelToView.on( 'selection', modelToViewSelection( editor.t ), { priority: 'lowest' } );
}
}

Expand Down
14 changes: 11 additions & 3 deletions src/image/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,23 @@ const imageSymbol = Symbol( 'isImage' );
/**
* Converts given {@link module:engine/view/element~Element} to image widget:
* * adds {@link module:engine/view/element~Element#setCustomProperty custom property} allowing to recognize image widget element,
* * calls {@link module:image/widget/utils~widgetize widgetize}.
* * calls {@link module:image/widget/utils~widgetize widgetize} function with proper element's label creator.
*
* @param {module:engine/view/element~Element} viewElement
* @param {String} label Element's label. It will be concatenated with image's `alt` attribute if one is present.
* @returns {module:engine/view/element~Element}
*/
export function toImageWidget( viewElement ) {
export function toImageWidget( viewElement, label ) {
viewElement.setCustomProperty( imageSymbol, true );

return widgetize( viewElement );
return widgetize( viewElement, { label: labelCreator } );

function labelCreator() {
const imgElement = viewElement.getChild( 0 );
const altText = imgElement.getAttribute( 'alt' );

return altText ? `${ altText } ${ label }` : label;
}
}

/**
Expand Down
39 changes: 38 additions & 1 deletion src/widget/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
*/

const widgetSymbol = Symbol( 'isWidget' );
const labelSymbol = Symbol( 'label' );

/**
* CSS class added to each widget element.
Expand Down Expand Up @@ -41,17 +42,53 @@ export function isWidget( element ) {
* * adds custom property allowing to recognize widget elements by using {@link ~isWidget}.
*
* @param {module:engine/view/element~Element} element
* @param {Object} [options]
* @param {String|Function} [options.label] Element's label provided to {@link ~setLabel} function. It can be passed as
* a plain string or a function returning a string.
* @returns {module:engine/view/element~Element} Returns same element.
*/
export function widgetize( element ) {
export function widgetize( element, options ) {
options = options || {};
element.setAttribute( 'contenteditable', false );
element.getFillerOffset = getFillerOffset;
element.addClass( WIDGET_CLASS_NAME );
element.setCustomProperty( widgetSymbol, true );

if ( options.label ) {
setLabel( element, options.label );
}

return element;
}

/**
* Sets label for given element.
* It can be passed as a plain string or a function returning a string. Function will be called each time label is retrieved by
* {@link ~getLabel}.
*
* @param {module:engine/view/element~Element} element
* @param {String|Function} labelOrCreator
*/
export function setLabel( element, labelOrCreator ) {
element.setCustomProperty( labelSymbol, labelOrCreator );
}

/**
* Returns label for provided element.
*
* @param {module:engine/view/element~Element} element
* @return {String}
*/
export function getLabel( element ) {
const labelCreator = element.getCustomProperty( labelSymbol );

if ( !labelCreator ) {
return '';
}

return typeof labelCreator == 'function' ? labelCreator() : labelCreator;
}

// Default filler offset function applied to all widget elements.
//
// @returns {null}
Expand Down
4 changes: 2 additions & 2 deletions src/widget/widgetengine.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
*/

import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
import { WIDGET_SELECTED_CLASS_NAME, isWidget } from './utils';
import { WIDGET_SELECTED_CLASS_NAME, isWidget, getLabel } from './utils';

/**
* The widget engine plugin.
Expand Down Expand Up @@ -42,7 +42,7 @@ export default class WidgetEngine extends Plugin {
return;
}

viewSelection.setFake( true );
viewSelection.setFake( true, { label: getLabel( selectedElement ) } );
selectedElement.addClass( WIDGET_SELECTED_CLASS_NAME );
previouslySelected = selectedElement;
}, { priority: 'low' } );
Expand Down
25 changes: 1 addition & 24 deletions tests/image/converters.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* For licensing, see LICENSE.md.
*/

import { viewToModelImage, modelToViewSelection, createImageAttributeConverter } from '../../src/image/converters';
import { viewToModelImage, createImageAttributeConverter } from '../../src/image/converters';
import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
import { createImageViewElement } from '../../src/image/imageengine';
import { toImageWidget } from '../../src/image/utils';
Expand Down Expand Up @@ -32,7 +32,6 @@ describe( 'Image converters', () => {
.fromElement( 'image' )
.toElement( () => toImageWidget( createImageViewElement() ) );

editor.editing.modelToView.on( 'selection', modelToViewSelection( editor.t ), { priority: 'lowest' } );
buildModelConverter().for( editor.editing.modelToView )
.fromElement( 'image' )
.toElement( () => toImageWidget( createImageViewElement() ) );
Expand Down Expand Up @@ -85,28 +84,6 @@ describe( 'Image converters', () => {
} );
} );

describe( 'modelToViewSelection', () => {
it( 'should convert selection over image to fake selection', () => {
setModelData( document, '[<image src=""></image>]' );

expect( viewDocument.selection.isFake ).to.be.true;
expect( viewDocument.selection.fakeSelectionLabel ).to.equal( 'image widget' );
} );

it( 'should convert fake selection label with alt', () => {
setModelData( document, '[<image src="" alt="foo bar"></image>]' );

expect( viewDocument.selection.isFake ).to.be.true;
expect( viewDocument.selection.fakeSelectionLabel ).to.equal( 'foo bar image widget' );
} );

it( 'should not convert fake selection if image is not selected', () => {
setModelData( document, '[]<image src="" alt="foo bar"></image>' );

expect( viewDocument.selection.isFake ).to.be.false;
} );
} );

describe( 'modelToViewAttributeConverter', () => {
it( 'should convert adding attribute to image', () => {
setModelData( document, '<image src=""></image>' );
Expand Down
25 changes: 21 additions & 4 deletions tests/image/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,37 @@
import ViewElement from '@ckeditor/ckeditor5-engine/src/view/element';
import ModelElement from '@ckeditor/ckeditor5-engine/src/model/element';
import { toImageWidget, isImageWidget, isImage } from '../../src/image/utils';
import { isWidget } from '../../src/widget/utils';
import { isWidget, getLabel } from '../../src/widget/utils';

describe( 'image widget utils', () => {
let element;
let element, image;

beforeEach( () => {
element = new ViewElement( 'div' );
toImageWidget( element );
image = new ViewElement( 'img' );
element = new ViewElement( 'figure', null, image );
toImageWidget( element, 'image widget' );
} );

describe( 'toImageWidget()', () => {
it( 'should be widgetized', () => {
expect( isWidget( element ) ).to.be.true;
} );

it( 'should set element\'s label', () => {
expect( getLabel( element ) ).to.equal( 'image widget' );
} );

it( 'should set element\'s label combined with alt attribute', () => {
image.setAttribute( 'alt', 'foo bar baz' );
expect( getLabel( element ) ).to.equal( 'foo bar baz image widget' );
} );

it( 'provided label creator should always return same label', () => {
image.setAttribute( 'alt', 'foo bar baz' );

expect( getLabel( element ) ).to.equal( 'foo bar baz image widget' );
expect( getLabel( element ) ).to.equal( 'foo bar baz image widget' );
} );
} );

describe( 'isImageWidget()', () => {
Expand Down
41 changes: 40 additions & 1 deletion tests/widget/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

import ViewElement from '@ckeditor/ckeditor5-engine/src/view/element';
import { widgetize, isWidget, WIDGET_CLASS_NAME } from '../../src/widget/utils';
import { widgetize, isWidget, WIDGET_CLASS_NAME, setLabel, getLabel } from '../../src/widget/utils';

describe( 'widget utils', () => {
let element;
Expand All @@ -27,6 +27,20 @@ describe( 'widget utils', () => {
it( 'should add proper CSS class', () => {
expect( element.hasClass( WIDGET_CLASS_NAME ) ).to.be.true;
} );

it( 'should add element\'s label if one is provided', () => {
element = new ViewElement( 'div' );
widgetize( element, { label: 'foo bar baz label' } );

expect( getLabel( element ) ).to.equal( 'foo bar baz label' );
} );

it( 'should add element\'s label if one is provided as function', () => {
element = new ViewElement( 'div' );
widgetize( element, { label: () => 'foo bar baz label' } );

expect( getLabel( element ) ).to.equal( 'foo bar baz label' );
} );
} );

describe( 'isWidget()', () => {
Expand All @@ -38,4 +52,29 @@ describe( 'widget utils', () => {
expect( isWidget( new ViewElement( 'p' ) ) ).to.be.false;
} );
} );

describe( 'label utils', () => {
it( 'should allow to set label for element', () => {
const element = new ViewElement( 'p' );
setLabel( element, 'foo bar baz' );

expect( getLabel( element ) ).to.equal( 'foo bar baz' );
} );

it( 'should return empty string for elements without label', () => {
const element = new ViewElement( 'div' );

expect( getLabel( element ) ).to.equal( '' );
} );

it( 'should allow to use a function as label creator', () => {
const element = new ViewElement( 'p' );
let caption = 'foo';
setLabel( element, () => caption );

expect( getLabel( element ) ).to.equal( 'foo' );
caption = 'bar';
expect( getLabel( element ) ).to.equal( 'bar' );
} );
} );
} );
18 changes: 17 additions & 1 deletion tests/widget/widgetengine.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,11 @@ describe( 'WidgetEngine', () => {

buildModelConverter().for( editor.editing.modelToView )
.fromElement( 'widget' )
.toElement( () => widgetize( new ViewContainer( 'div' ) ) );
.toElement( () => {
const element = widgetize( new ViewContainer( 'div' ), { label: 'element label' } );

return element;
} );

buildModelConverter().for( editor.editing.modelToView )
.fromElement( 'editable' )
Expand All @@ -52,6 +56,18 @@ describe( 'WidgetEngine', () => {
expect( viewDocument.selection.isFake ).to.be.true;
} );

it( 'should use element\'s label to set fake selection if one is provided', () => {
setModelData( document, '[<widget>foo bar</widget>]' );

expect( viewDocument.selection.fakeSelectionLabel ).to.equal( 'element label' );
} );

it( 'fake selection should be empty if widget is not selected', () => {
setModelData( document, '<widget>foo bar</widget>' );

expect( viewDocument.selection.fakeSelectionLabel ).to.equal( '' );
} );

it( 'should toggle selected class', () => {
setModelData( document, '[<widget>foo</widget>]' );

Expand Down

0 comments on commit 5c1897d

Please sign in to comment.