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 #24 from ckeditor/t/23
Browse files Browse the repository at this point in the history
Feature: The toolbar should support a vertical offset from the top of the web page. Closes #23.
  • Loading branch information
Reinmar committed Jul 24, 2017
2 parents 68a454c + b434d93 commit 01e29d5
Show file tree
Hide file tree
Showing 7 changed files with 338 additions and 98 deletions.
18 changes: 17 additions & 1 deletion src/inlineeditorui.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import ComponentFactory from '@ckeditor/ckeditor5-ui/src/componentfactory';
import FocusTracker from '@ckeditor/ckeditor5-utils/src/focustracker';
import enableToolbarKeyboardFocus from '@ckeditor/ckeditor5-ui/src/toolbar/enabletoolbarkeyboardfocus';
import normalizeToolbarConfig from '@ckeditor/ckeditor5-ui/src/toolbar/normalizetoolbarconfig';

/**
* The inline editor UI class.
Expand Down Expand Up @@ -44,9 +45,21 @@ export default class InlineEditorUI {
*/
this.focusTracker = new FocusTracker();

/**
* A normalized `config.toolbar` object.
*
* @type {Object}
* @private
*/
this._toolbarConfig = normalizeToolbarConfig( editor.config.get( 'toolbar' ) );

// Set–up the view#panel.
view.panel.bind( 'isVisible' ).to( this.focusTracker, 'isFocused' );

if ( this._toolbarConfig && this._toolbarConfig.viewportTopOffset ) {
view.viewportTopOffset = this._toolbarConfig.viewportTopOffset;
}

// https://github.com/ckeditor/ckeditor5-editor-inline/issues/4
view.listenTo( editor.editing.view, 'render', () => {
// Don't pin if the panel is not already visible. It prevents the panel
Expand Down Expand Up @@ -78,7 +91,10 @@ export default class InlineEditorUI {
const editor = this.editor;

this.view.init();
this.view.toolbar.fillFromConfig( editor.config.get( 'toolbar' ), this.componentFactory );

if ( this._toolbarConfig ) {
this.view.toolbar.fillFromConfig( this._toolbarConfig.items, this.componentFactory );
}

enableToolbarKeyboardFocus( {
origin: editor.editing.view,
Expand Down
189 changes: 103 additions & 86 deletions src/inlineeditoruiview.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,28 +13,6 @@ import BalloonPanelView from '@ckeditor/ckeditor5-ui/src/panel/balloon/balloonpa
import ToolbarView from '@ckeditor/ckeditor5-ui/src/toolbar/toolbarview';
import Template from '@ckeditor/ckeditor5-ui/src/template';

// A set of positioning functions used by the
// {@link module:editor-inline/inlineeditoruiview~InlineEditableUIView#panel}.
//
// @private
// @type {module:utils/dom/position~Options#positions}
const panelPositions = [
( editableRect, panelRect ) => {
return {
top: getPanelPositionTop( editableRect, panelRect ),
left: editableRect.left,
name: 'toolbar_west'
};
},
( editableRect, panelRect ) => {
return {
top: getPanelPositionTop( editableRect, panelRect ),
left: editableRect.left + editableRect.width - panelRect.width,
name: 'toolbar_east'
};
}
];

/**
* Inline editor UI view. Uses inline editable and floating toolbar.
*
Expand All @@ -57,6 +35,21 @@ export default class InlineEditorUIView extends EditorUIView {
*/
this.toolbar = new ToolbarView( locale );

/**
* The offset from the top edge of the web browser's viewport which makes the
* UI become sticky. The default value is `0`, which means the UI becomes
* sticky when it's upper edge touches the top of the page viewport.
*
* This attribute is useful when the web page has UI elements positioned to the top
* either using `position: fixed` or `position: sticky`, which would cover the
* UI or vice–versa (depending on the `z-index` hierarchy).
*
* @readonly
* @observable
* @member {Number} #viewportTopOffset
*/
this.set( 'viewportTopOffset', 0 );

Template.extend( this.toolbar.template, {
attributes: {
class: [
Expand All @@ -78,6 +71,53 @@ export default class InlineEditorUIView extends EditorUIView {

this.panel.withArrow = false;

/**
* A set of positioning functions used by the {@link #panel} to float around
* {@link #editableElement}.
*
* The positioning functions are as follows:
*
* * West:
*
* [ Panel ]
* +------------------+
* | #editableElement |
* +------------------+
*
* +------------------+
* | #editableElement |
* |[ Panel ] |
* | |
* +------------------+
*
* +------------------+
* | #editableElement |
* +------------------+
* [ Panel ]
*
* * East:
*
* [ Panel ]
* +------------------+
* | #editableElement |
* +------------------+
*
* +------------------+
* | #editableElement |
* | [ Panel ]|
* | |
* +------------------+
*
* +------------------+
* | #editableElement |
* +------------------+
* [ Panel ]
*
* @readonly
* @type {module:utils/dom/position~Options#positions}
*/
this.panelPositions = this._getPanelPositions();

Template.extend( this.panel.template, {
attributes: {
class: 'ck-toolbar-container'
Expand Down Expand Up @@ -113,73 +153,50 @@ export default class InlineEditorUIView extends EditorUIView {
}

/**
* A set of positioning functions used by the {@link #panel} to float around
* {@link #editableElement}.
*
* The positioning functions are as follows:
*
* * West:
*
* [ Panel ]
* +------------------+
* | #editableElement |
* +------------------+
*
* +------------------+
* | #editableElement |
* |[ Panel ] |
* | |
* +------------------+
*
* +------------------+
* | #editableElement |
* +------------------+
* [ Panel ]
*
* * East:
* Determines panel top position of the {@link #panel} in {@link #panelPositions}.
*
* [ Panel ]
* +------------------+
* | #editableElement |
* +------------------+
*
* +------------------+
* | #editableElement |
* | [ Panel ]|
* | |
* +------------------+
*
* +------------------+
* | #editableElement |
* +------------------+
* [ Panel ]
*
* @readonly
* @type {module:utils/dom/position~Options#positions}
* @private
* @param {module:utils/dom/rect~Rect} editableRect Rect of the
* {@link module:editor-inline/inlineeditoruiview~InlineEditableUIView#editableElement}.
* @param {module:utils/dom/rect~Rect} panelRect Rect of the
* {@link module:editor-inline/inlineeditoruiview~InlineEditableUIView#panel}.
*/
get panelPositions() {
return panelPositions;
_getPanelPositionTop( editableRect, panelRect ) {
let top;

if ( editableRect.top > panelRect.height + this.viewportTopOffset ) {
top = editableRect.top - panelRect.height;
} else if ( editableRect.bottom > panelRect.height + this.viewportTopOffset + 50 ) {
top = this.viewportTopOffset;
} else {
top = editableRect.bottom;
}

return top;
}
}

// Determines panel top position for
// {@link module:editor-inline/inlineeditoruiview~InlineEditableUIView#panelPositions}
//
// @private
// @param {module:utils/dom/rect~Rect} editableRect Rect of the
// {@link module:editor-inline/inlineeditoruiview~InlineEditableUIView#editableElement}.
// @param {module:utils/dom/rect~Rect} panelRect Rect of the
// {@link module:editor-inline/inlineeditoruiview~InlineEditableUIView#panel}.
function getPanelPositionTop( editableRect, panelRect ) {
let top;

if ( editableRect.top > panelRect.height ) {
top = editableRect.top - panelRect.height;
} else if ( editableRect.bottom > panelRect.height + 50 ) {
top = 0;
} else {
top = editableRect.bottom;
/**
* Returns the positions for {@link #panelPositions}.
*
* @private
* @returns module:utils/dom/position~Options#positions
*/
_getPanelPositions() {
return [
( editableRect, panelRect ) => {
return {
top: this._getPanelPositionTop( editableRect, panelRect ),
left: editableRect.left,
name: 'toolbar_west'
};
},
( editableRect, panelRect ) => {
return {
top: this._getPanelPositionTop( editableRect, panelRect ),
left: editableRect.left + editableRect.width - panelRect.width,
name: 'toolbar_east'
};
}
];
}

return top;
}
88 changes: 77 additions & 11 deletions tests/inlineeditorui.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,24 @@ describe( 'InlineEditorUI', () => {
editorElement = document.createElement( 'div' );
document.body.appendChild( editorElement );

editor = new ClassicTestEditor( editorElement, {
return ClassicTestEditor.create( editorElement, {
toolbar: [ 'foo', 'bar' ]
} );
} )
.then( newEditor => {
editor = newEditor;

view = new InlineEditorUIView( editor.locale );
ui = new InlineEditorUI( editor, view );
editable = editor.editing.view.getRoot();
view = new InlineEditorUIView( editor.locale );
ui = new InlineEditorUI( editor, view );
editable = editor.editing.view.getRoot();

ui.componentFactory.add( 'foo', viewCreator( 'foo' ) );
ui.componentFactory.add( 'bar', viewCreator( 'bar' ) );
ui.componentFactory.add( 'foo', viewCreator( 'foo' ) );
ui.componentFactory.add( 'bar', viewCreator( 'bar' ) );
} );
} );

afterEach( () => {
editorElement.remove();
editor.destroy();
} );

describe( 'constructor()', () => {
Expand Down Expand Up @@ -65,6 +73,35 @@ describe( 'InlineEditorUI', () => {
expect( view.panel.isVisible ).to.be.true;
} );

it( 'doesn\'t set the view#viewportTopOffset, if not specified in the config', () => {
expect( view.viewportTopOffset ).to.equal( 0 );
} );

it( 'sets view#viewportTopOffset, if specified', () => {
editorElement = document.createElement( 'div' );
document.body.appendChild( editorElement );

return ClassicTestEditor.create( editorElement, {
toolbar: {
items: [ 'foo', 'bar' ],
viewportTopOffset: 100
}
} )
.then( editor => {
view = new InlineEditorUIView( editor.locale );
ui = new InlineEditorUI( editor, view );
editable = editor.editing.view.getRoot();

ui.componentFactory.add( 'foo', viewCreator( 'foo' ) );
ui.componentFactory.add( 'bar', viewCreator( 'bar' ) );

expect( view.viewportTopOffset ).to.equal( 100 );

editorElement.remove();
return editor.destroy();
} );
} );

// https://github.com/ckeditor/ckeditor5-editor-inline/issues/4
it( 'pin() is called on editor.editable.view#render', () => {
const spy = sinon.spy( view.panel, 'pin' );
Expand Down Expand Up @@ -133,11 +170,40 @@ describe( 'InlineEditorUI', () => {
sinon.assert.calledOnce( spy );
} );

it( 'fills view.toolbar#items with editor config', () => {
const spy = testUtils.sinon.spy( view.toolbar, 'fillFromConfig' );
describe( 'view.toolbar#items', () => {
it( 'are filled with the config.toolbar (specified as an Array)', () => {
const spy = testUtils.sinon.spy( view.toolbar, 'fillFromConfig' );

ui.init();
sinon.assert.calledWithExactly( spy, editor.config.get( 'toolbar' ), ui.componentFactory );
ui.init();
sinon.assert.calledWithExactly( spy, editor.config.get( 'toolbar' ), ui.componentFactory );
} );

it( 'are filled with the config.toolbar (specified as an Object)', () => {
editorElement = document.createElement( 'div' );
document.body.appendChild( editorElement );

return ClassicTestEditor.create( editorElement, {
toolbar: {
items: [ 'foo', 'bar' ],
viewportTopOffset: 100
}
} )
.then( editor => {
view = new InlineEditorUIView( editor.locale );
ui = new InlineEditorUI( editor, view );

ui.componentFactory.add( 'foo', viewCreator( 'foo' ) );
ui.componentFactory.add( 'bar', viewCreator( 'bar' ) );

const spy = testUtils.sinon.spy( view.toolbar, 'fillFromConfig' );

ui.init();
sinon.assert.calledWithExactly( spy,
editor.config.get( 'toolbar.items' ),
ui.componentFactory
);
} );
} );
} );

it( 'initializes keyboard navigation between view#toolbar and view#editable', () => {
Expand Down
Loading

0 comments on commit 01e29d5

Please sign in to comment.