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

I/6049: Introduced the table cell properties UI #227

Merged
merged 38 commits into from
Jan 28, 2020
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
39d9228
First implementation of the cell properties form.
oleq Jan 13, 2020
1c236ce
Added some space to the table cell form. Attached the form to the edi…
oleq Jan 14, 2020
ab6f0cf
Code refactoring.
oleq Jan 14, 2020
b96df24
Used LabeledView in the cell properties form.
oleq Jan 14, 2020
9453c51
Code refactoring.
oleq Jan 15, 2020
afb59d0
Code refactoring and docs.
oleq Jan 15, 2020
b9f2c49
Moved alignment icons to ckeditor5-core. Updated paths to icons.
oleq Jan 15, 2020
176d8f7
Docs.
oleq Jan 15, 2020
df19000
Docs.
oleq Jan 15, 2020
128edef
Removed the obsolete TableStyleUI plugin.
oleq Jan 15, 2020
a86246a
Tests: Added TableCellPropertiesView tests. Code refactoring.
oleq Jan 17, 2020
268a598
Tests: Added tests for the FormRowView.
oleq Jan 17, 2020
e1b263f
Tests: Added TableCellPropertiesUI presence assertion to TableCellPro…
oleq Jan 17, 2020
158179d
Tests: Added tests for UI utils.
oleq Jan 17, 2020
19963df
Code refactoring.
oleq Jan 17, 2020
a19f0c9
Aligned cell properties UI to the latest UI framework API.
oleq Jan 20, 2020
b8461a9
Tests: Partial TableCellPropertiesUI tests.
oleq Jan 21, 2020
25573f4
Merge remote-tracking branch 'origin/i/3287' into i/6049
oleq Jan 21, 2020
f8689d9
Fixed wrong module paths.
oleq Jan 21, 2020
7be0348
Code refactoring.
oleq Jan 21, 2020
f165c95
Tests: Code refactoring.
oleq Jan 21, 2020
e4dfae1
Tests: Finished the TableCellPropertiesUI tests.
oleq Jan 21, 2020
3b139f0
Docs: Fixed broken module paths.
oleq Jan 21, 2020
6580667
Docs: Fixed broken links.
oleq Jan 21, 2020
85d0ce6
Docs: Improved TableCellPropertiesView docs.
oleq Jan 21, 2020
82dae2d
Tests: Aligned tests to the latest TableCellPropertiesView API.
oleq Jan 22, 2020
2aee83a
Added contexts.json entries for the table cell properties UI.
oleq Jan 22, 2020
782e3c5
Code refactoring.
oleq Jan 22, 2020
6bea932
Tests: Code refactoring.
oleq Jan 22, 2020
ca4661d
Merge remote-tracking branch 'origin/i/3287' into i/6049
oleq Jan 22, 2020
cecd9d6
Merge branch 'master' into i/6049
oleq Jan 24, 2020
c6e9716
Merge branch 'master' into i/6049
jodator Jan 27, 2020
ccc7e0c
Code refactoring in the TableCellPropertiesUI class.
oleq Jan 28, 2020
579f7ea
Code refactoring in the TableCellPropertiesUI class.
oleq Jan 28, 2020
08667fb
Update src/tablecellproperties/ui/tablecellpropertiesview.js
oleq Jan 28, 2020
1e9d558
Update src/tablecellproperties/ui/tablecellpropertiesview.js
oleq Jan 28, 2020
21f11d1
Update src/tablecellproperties/ui/tablecellpropertiesview.js
oleq Jan 28, 2020
10ab4ae
Merge branch 'master' into i/6049
oleq Jan 28, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/tablecellproperties.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@
*/

import Plugin from '@ckeditor/ckeditor5-core/src/plugin';

import TableCellPropertiesUI from './tablecellproperties/tablecellpropertiesui';
import TableCellPropertiesEditing from './tablecellproperties/tablecellpropertiesediting';

/**
* The table cell properties feature.
*
* This is a "glue" plugin which loads the
* {@link module:table/tablecellproperties/tablecellpropertiesediting~TableCellPropertiesEditing table editing feature} and
* table UI feature.
* the {@link module:table/tablecellproperties/tablecellpropertiesui~TableCellPropertiesUI table UI feature}.
*
* @extends module:core/plugin~Plugin
*/
Expand All @@ -32,6 +32,6 @@ export default class TableCellProperties extends Plugin {
* @inheritDoc
*/
static get requires() {
return [ TableCellPropertiesEditing ];
return [ TableCellPropertiesEditing, TableCellPropertiesUI ];
}
}
291 changes: 291 additions & 0 deletions src/tablecellproperties/tablecellpropertiesui.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
/**
* @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 table/tablecellproperties/tablecellpropertiesui
*/

import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
import { getTableWidgetAncestor } from '../utils';
import clickOutsideHandler from '@ckeditor/ckeditor5-ui/src/bindings/clickoutsidehandler';
import ContextualBalloon from '@ckeditor/ckeditor5-ui/src/panel/balloon/contextualballoon';
import TableCellPropertiesView from './ui/tablecellpropertiesview';
import tableCellProperties from './../../theme/icons/table-cell-properties.svg';
import { repositionContextualBalloon, getBalloonCellPositionData } from '../ui/utils';

const DEFAULT_BORDER_STYLE = 'none';
const DEFAULT_HORIZONTAL_ALIGNMENT = 'left';
const DEFAULT_VERTICAL_ALIGNMENT = 'middle';
const CELL_PROPERTIES = [
'borderStyle', 'borderColor', 'borderWidth',
'padding', 'backgroundColor',
'horizontalAlignment', 'verticalAlignment',
];

/**
* The table cell properties UI plugin. It introduces the `'tableCellProperties'` button
* that opens a form allowing to specify visual styling of a table cell.
*
* It uses the
* {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon contextual balloon plugin}.
*
* @extends module:core/plugin~Plugin
*/
export default class TableCellPropertiesUI extends Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ ContextualBalloon ];
}

/**
* @inheritDoc
*/
static get pluginName() {
return 'TableCellPropertiesUI';
}

/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const t = editor.t;

/**
* The contextual balloon plugin instance.
*
* @private
* @member {module:ui/panel/balloon/contextualballoon~ContextualBalloon}
*/
this._balloon = editor.plugins.get( ContextualBalloon );

/**
* The cell properties form view displayed inside the balloon.
*
* @member {module:table/tablecellproperties/ui/tablecellpropertiesview~TableCellPropertiesView}
*/
this.view = this._createPropertiesView();

/**
* The batch used to undo all changes made by the form (which are live, as the user types)
* when "Cancel" was pressed. Each time the view is shown, a new batch is created.
*
* @protected
* @member {module:engine/model/batch~Batch}
*/
this._undoStepBatch = null;

editor.ui.componentFactory.add( 'tableCellProperties', locale => {
const view = new ButtonView( locale );

view.set( {
label: t( 'Cell properties' ),
icon: tableCellProperties,
tooltip: true
} );

this.listenTo( view, 'execute', () => this._showView() );

return view;
} );
}

/**
* @inheritDoc
*/
destroy() {
super.destroy();

// Destroy created UI components as they are not automatically destroyed.
// See https://github.com/ckeditor/ckeditor5/issues/1341.
this.view.destroy();
}

/**
* Creates the {@link module:table/tablecellproperties/ui/tablecellpropertiesview~TableCellPropertiesView} instance.
*
* @private
* @returns {module:table/tablecellproperties/ui/tablecellpropertiesview~TableCellPropertiesView} The cell
* properties form view instance.
*/
_createPropertiesView() {
const editor = this.editor;
const viewDocument = editor.editing.view.document;
const view = new TableCellPropertiesView( editor.locale );

// Render the view so its #element is available for the clickOutsideHandler.
view.render();

this.listenTo( view, 'submit', () => {
this._hideView();
} );

this.listenTo( view, 'cancel', () => {
editor.execute( 'undo', this._undoStepBatch );
this._hideView();
} );

// Close the balloon on Esc key press.
view.keystrokes.set( 'Esc', ( data, cancel ) => {
this._hideView();
cancel();
} );

// Reposition the balloon or hide the form if a table cell is no longer selected.
this.listenTo( editor.ui, 'update', () => {
if ( !getTableWidgetAncestor( viewDocument.selection ) ) {
this._hideView();
} else if ( this._isViewVisible ) {
repositionContextualBalloon( editor );
}
} );

// Close on click outside of balloon panel element.
clickOutsideHandler( {
emitter: view,
activator: () => this._isViewInBalloon,
contextElements: [ this._balloon.view.element ],
callback: () => this._hideView()
} );

// Create the "UI -> editor data" binding.
// This listener updates the editor data (via table commands) when any observable
// property of the view has changed. This makes the view live, which means the changes are
// visible in the editing as soon as the user types or changes fields' values.
view.on( 'change', ( evt, propertyName, newValue ) => {
// Not all observable properties of the #view must be related to the cell editing.
// For instance, they can belong to some internal logic.
if ( !CELL_PROPERTIES.includes( propertyName ) ) {
return;
}

editor.execute( propertyNameToCommandName( propertyName ), {
value: newValue,
batch: this._undoStepBatch
} );
} );

return view;
}

/**
* In this method the "editor data -> UI" binding is happening.
*
* When executed, this method obtains selected cell property values from various table commands
* and passes them to the {@link #view}.
*
* This way, the UI stays up–to–date with the editor data.
*
* @private
*/
_fillViewFormFromCommandValues() {
const editor = this.editor;
const data = {};

for ( const propertyName of CELL_PROPERTIES ) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if it wouldn't be better to separate logic related to each value of CELL_PROPERTIES as here and in other places there are loops and checks.

This will have some code duplication for sure. But maybe it will be more clear or will require fewer changes if the logic for one of the values would change. Another consideration is enabling/disabling (by configuration - not in runtime) some properties from editing.

then some code would be

handleProperty() {
    this.listenTo( view, 'change:property', ... );
    // some other listener for the form view setting
}

let value = editor.commands.get( propertyNameToCommandName( propertyName ) ).value;

if ( !value ) {
if ( propertyName === 'borderStyle' ) {
value = DEFAULT_BORDER_STYLE;
} else if ( propertyName === 'horizontalAlignment' ) {
value = DEFAULT_HORIZONTAL_ALIGNMENT;
} else if ( propertyName === 'verticalAlignment' ) {
value = DEFAULT_VERTICAL_ALIGNMENT;
} else {
value = '';
}
}

data[ propertyName ] = value;
}
oleq marked this conversation as resolved.
Show resolved Hide resolved

this.view.set( data );
}

/**
* Shows the {@link #view} in the {@link #_balloon}.
*
* **Note**: Each time a view is shown, the new {@link #_undoStepBatch} is created that contains
* all changes made to the document when the view is visible, allowing a single undo step
* for all of them.
*
* @protected
*/
_showView() {
const editor = this.editor;

this._balloon.add( {
view: this.view,
position: getBalloonCellPositionData( editor )
} );

// Create a new batch. Clicking "Cancel" will undo this batch.
this._undoStepBatch = editor.model.createBatch();

// Update the view with the model values.
this._fillViewFormFromCommandValues();

// Basic a11y.
this.view.focus();
}

/**
* Removes the {@link #view} from the {@link #_balloon}.
*
* @protected
*/
_hideView() {
if ( !this._isViewInBalloon ) {
return;
}

const editor = this.editor;

this.stopListening( editor.ui, 'update' );

// Blur any input element before removing it from DOM to prevent issues in some browsers.
// See https://github.com/ckeditor/ckeditor5/issues/1501.
this.view.saveButtonView.focus();

this._balloon.remove( this.view );

// Make sure the focus is not lost in the process by putting it directly
// into the editing view.
this.editor.editing.view.focus();
}

/**
* Returns `true` when the {@link #view} is the visible in the {@link #_balloon}.
*
* @private
* @type {Boolean}
*/
get _isViewVisible() {
return this._balloon.visibleView === this.view;
}

/**
* Returns `true` when the {@link #view} is in the {@link #_balloon}.
*
* @private
* @type {Boolean}
*/
get _isViewInBalloon() {
return this._balloon.hasView( this.view );
}
}

// Translates view's properties into command names.
//
// 'borderWidth' -> 'tableCellBorderWidth'
//
// @param {String} propertyName
function propertyNameToCommandName( propertyName ) {
oleq marked this conversation as resolved.
Show resolved Hide resolved
return `tableCell${ propertyName[ 0 ].toUpperCase() }${ propertyName.slice( 1 ) }`;
}
Loading