This repository has been archived by the owner on Jun 26, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 18
I/6049: Introduced the table cell properties UI #227
Merged
Changes from 26 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 1c236ce
Added some space to the table cell form. Attached the form to the edi…
oleq ab6f0cf
Code refactoring.
oleq b96df24
Used LabeledView in the cell properties form.
oleq 9453c51
Code refactoring.
oleq afb59d0
Code refactoring and docs.
oleq b9f2c49
Moved alignment icons to ckeditor5-core. Updated paths to icons.
oleq 176d8f7
Docs.
oleq df19000
Docs.
oleq 128edef
Removed the obsolete TableStyleUI plugin.
oleq a86246a
Tests: Added TableCellPropertiesView tests. Code refactoring.
oleq 268a598
Tests: Added tests for the FormRowView.
oleq e1b263f
Tests: Added TableCellPropertiesUI presence assertion to TableCellPro…
oleq 158179d
Tests: Added tests for UI utils.
oleq 19963df
Code refactoring.
oleq a19f0c9
Aligned cell properties UI to the latest UI framework API.
oleq b8461a9
Tests: Partial TableCellPropertiesUI tests.
oleq 25573f4
Merge remote-tracking branch 'origin/i/3287' into i/6049
oleq f8689d9
Fixed wrong module paths.
oleq 7be0348
Code refactoring.
oleq f165c95
Tests: Code refactoring.
oleq e4dfae1
Tests: Finished the TableCellPropertiesUI tests.
oleq 3b139f0
Docs: Fixed broken module paths.
oleq 6580667
Docs: Fixed broken links.
oleq 85d0ce6
Docs: Improved TableCellPropertiesView docs.
oleq 82dae2d
Tests: Aligned tests to the latest TableCellPropertiesView API.
oleq 2aee83a
Added contexts.json entries for the table cell properties UI.
oleq 782e3c5
Code refactoring.
oleq 6bea932
Tests: Code refactoring.
oleq ca4661d
Merge remote-tracking branch 'origin/i/3287' into i/6049
oleq cecd9d6
Merge branch 'master' into i/6049
oleq c6e9716
Merge branch 'master' into i/6049
jodator ccc7e0c
Code refactoring in the TableCellPropertiesUI class.
oleq 579f7ea
Code refactoring in the TableCellPropertiesUI class.
oleq 08667fb
Update src/tablecellproperties/ui/tablecellpropertiesview.js
oleq 1e9d558
Update src/tablecellproperties/ui/tablecellpropertiesview.js
oleq 21f11d1
Update src/tablecellproperties/ui/tablecellpropertiesview.js
oleq 10ab4ae
Merge branch 'master' into i/6049
oleq File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 ) { | ||
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 ) }`; | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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