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

Commit

Permalink
Merge 2768e6b into 90ea50d
Browse files Browse the repository at this point in the history
  • Loading branch information
jodator committed Sep 13, 2018
2 parents 90ea50d + 2768e6b commit 757dcf2
Show file tree
Hide file tree
Showing 2 changed files with 132 additions and 2 deletions.
50 changes: 48 additions & 2 deletions src/utils.js
Expand Up @@ -8,7 +8,8 @@
*/

import HighlightStack from './highlightstack';
import Position from '@ckeditor/ckeditor5-engine/src/view/position';
import ViewPosition from '@ckeditor/ckeditor5-engine/src/view/position';
import ModelPosition from '@ckeditor/ckeditor5-engine/src/model/position';
import IconView from '@ckeditor/ckeditor5-ui/src/icon/iconview';
import env from '@ckeditor/ckeditor5-utils/src/env';

Expand Down Expand Up @@ -240,6 +241,51 @@ export function toWidgetEditable( editable, writer ) {
return editable;
}

/**
* Returns a model position which is optimal (in terms of UX) for inserting a widget block.
*
* For instance, if a selection is in the middle of a paragraph, the position before this paragraph
* will be returned so that it is not split. If the selection is at the end of a paragraph,
* the position after this paragraph will be returned.
*
* Note: If the selection is placed in an empty block, that block will be returned. If that position
* is then passed to {@link module:engine/model/model~Model#insertContent},
* the block will be fully replaced by the image.
*
* @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection} selection
* The selection based on which the insertion position should be calculated.
* @returns {module:engine/model/position~Position} The optimal position.
*/
export function findOptimalInsertionPosition( selection ) {
const selectedElement = selection.getSelectedElement();

if ( selectedElement ) {
return ModelPosition.createAfter( selectedElement );
}

const firstBlock = selection.getSelectedBlocks().next().value;

if ( firstBlock ) {
// If inserting into an empty block – return position in that block. It will get
// replaced with the image by insertContent(). #42.
if ( firstBlock.isEmpty ) {
return ModelPosition.createAt( firstBlock );
}

const positionAfter = ModelPosition.createAfter( firstBlock );

// If selection is at the end of the block - return position after the block.
if ( selection.focus.isTouching( positionAfter ) ) {
return positionAfter;
}

// Otherwise return position before the block.
return ModelPosition.createBefore( firstBlock );
}

return selection.focus;
}

// Default filler offset function applied to all widget elements.
//
// @returns {null}
Expand Down Expand Up @@ -268,6 +314,6 @@ function addSelectionHandler( editable, writer ) {
} );

// Append the selection handler into the widget wrapper.
writer.insert( Position.createAt( editable ), selectionHandler );
writer.insert( ViewPosition.createAt( editable ), selectionHandler );
writer.addClass( [ 'ck-widget_selectable' ], editable );
}
84 changes: 84 additions & 0 deletions tests/utils.js
Expand Up @@ -16,11 +16,14 @@ import {
getLabel,
toWidgetEditable,
setHighlightHandling,
findOptimalInsertionPosition,
WIDGET_CLASS_NAME
} from '../src/utils';
import UIElement from '@ckeditor/ckeditor5-engine/src/view/uielement';
import env from '@ckeditor/ckeditor5-utils/src/env';
import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
import Model from '@ckeditor/ckeditor5-engine/src/model/model';
import { setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';

describe( 'widget utils', () => {
let element, writer, viewDocument;
Expand Down Expand Up @@ -337,4 +340,85 @@ describe( 'widget utils', () => {
expect( addSpy.secondCall.args[ 1 ] ).to.equal( secondDescriptor );
} );
} );

describe( 'findOptimalInsertionPosition()', () => {
let model, doc;

beforeEach( () => {
model = new Model();
doc = model.document;

doc.createRoot();

model.schema.register( 'paragraph', { inheritAllFrom: '$block' } );
model.schema.register( 'image' );
model.schema.register( 'span' );

model.schema.extend( 'image', {
allowIn: '$root',
isObject: true
} );

model.schema.extend( 'span', { allowIn: 'paragraph' } );
model.schema.extend( '$text', { allowIn: 'span' } );
} );

it( 'returns position after selected element', () => {
setData( model, '<paragraph>x</paragraph>[<image></image>]<paragraph>y</paragraph>' );

const pos = findOptimalInsertionPosition( doc.selection );

expect( pos.path ).to.deep.equal( [ 2 ] );
} );

it( 'returns position inside empty block', () => {
setData( model, '<paragraph>x</paragraph><paragraph>[]</paragraph><paragraph>y</paragraph>' );

const pos = findOptimalInsertionPosition( doc.selection );

expect( pos.path ).to.deep.equal( [ 1, 0 ] );
} );

it( 'returns position before block if at the beginning of that block', () => {
setData( model, '<paragraph>x</paragraph><paragraph>[]foo</paragraph><paragraph>y</paragraph>' );

const pos = findOptimalInsertionPosition( doc.selection );

expect( pos.path ).to.deep.equal( [ 1 ] );
} );

it( 'returns position before block if in the middle of that block', () => {
setData( model, '<paragraph>x</paragraph><paragraph>f[]oo</paragraph><paragraph>y</paragraph>' );

const pos = findOptimalInsertionPosition( doc.selection );

expect( pos.path ).to.deep.equal( [ 1 ] );
} );

it( 'returns position after block if at the end of that block', () => {
setData( model, '<paragraph>x</paragraph><paragraph>foo[]</paragraph><paragraph>y</paragraph>' );

const pos = findOptimalInsertionPosition( doc.selection );

expect( pos.path ).to.deep.equal( [ 2 ] );
} );

// Checking if isTouching() was used.
it( 'returns position after block if at the end of that block (deeply nested)', () => {
setData( model, '<paragraph>x</paragraph><paragraph>foo<span>bar[]</span></paragraph><paragraph>y</paragraph>' );

const pos = findOptimalInsertionPosition( doc.selection );

expect( pos.path ).to.deep.equal( [ 2 ] );
} );

it( 'returns selection focus if not in a block', () => {
model.schema.extend( '$text', { allowIn: '$root' } );
setData( model, 'foo[]bar' );

const pos = findOptimalInsertionPosition( doc.selection );

expect( pos.path ).to.deep.equal( [ 3 ] );
} );
} );
} );

0 comments on commit 757dcf2

Please sign in to comment.