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 19
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #10 from ckeditor/t/9
Feature: Initial table support. Closes #4. Closes #7. Closes #9.
- Loading branch information
Showing
38 changed files
with
8,462 additions
and
0 deletions.
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,67 @@ | ||
/** | ||
* @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. | ||
* For licensing, see LICENSE.md. | ||
*/ | ||
|
||
/** | ||
* @module table/commands/insertcolumncommand | ||
*/ | ||
|
||
import Command from '@ckeditor/ckeditor5-core/src/command'; | ||
import { getParentTable } from './utils'; | ||
import TableUtils from '../tableutils'; | ||
|
||
/** | ||
* The insert column command. | ||
* | ||
* @extends module:core/command~Command | ||
*/ | ||
export default class InsertColumnCommand extends Command { | ||
/** | ||
* Creates a new `InsertRowCommand` instance. | ||
* | ||
* @param {module:core/editor/editor~Editor} editor Editor on which this command will be used. | ||
* @param {Object} options | ||
* @param {String} [options.order="after"] The order of insertion relative to a column in which caret is located. | ||
* Possible values: "after" and "before". | ||
*/ | ||
constructor( editor, options = {} ) { | ||
super( editor ); | ||
|
||
/** | ||
* The order of insertion relative to a column in which caret is located. | ||
* | ||
* @readonly | ||
* @member {String} module:table/commands/insertcolumncommand~InsertColumnCommand#order | ||
*/ | ||
this.order = options.order || 'after'; | ||
} | ||
|
||
/** | ||
* @inheritDoc | ||
*/ | ||
refresh() { | ||
const selection = this.editor.model.document.selection; | ||
|
||
const tableParent = getParentTable( selection.getFirstPosition() ); | ||
|
||
this.isEnabled = !!tableParent; | ||
} | ||
|
||
/** | ||
* @inheritDoc | ||
*/ | ||
execute() { | ||
const editor = this.editor; | ||
const selection = editor.model.document.selection; | ||
const tableUtils = editor.plugins.get( TableUtils ); | ||
|
||
const table = getParentTable( selection.getFirstPosition() ); | ||
const tableCell = selection.getFirstPosition().parent; | ||
|
||
const { column } = tableUtils.getCellLocation( tableCell ); | ||
const insertAt = this.order === 'after' ? column + 1 : column; | ||
|
||
tableUtils.insertColumns( table, { columns: 1, at: insertAt } ); | ||
} | ||
} |
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,67 @@ | ||
/** | ||
* @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. | ||
* For licensing, see LICENSE.md. | ||
*/ | ||
|
||
/** | ||
* @module table/commands/insertrowcommand | ||
*/ | ||
|
||
import Command from '@ckeditor/ckeditor5-core/src/command'; | ||
import { getParentTable } from './utils'; | ||
import TableUtils from '../tableutils'; | ||
|
||
/** | ||
* The insert row command. | ||
* | ||
* @extends module:core/command~Command | ||
*/ | ||
export default class InsertRowCommand extends Command { | ||
/** | ||
* Creates a new `InsertRowCommand` instance. | ||
* | ||
* @param {module:core/editor/editor~Editor} editor Editor on which this command will be used. | ||
* @param {Object} options | ||
* @param {String} [options.order="below"] The order of insertion relative to a row in which caret is located. | ||
* Possible values: "above" and "below". | ||
*/ | ||
constructor( editor, options = {} ) { | ||
super( editor ); | ||
|
||
/** | ||
* The order of insertion relative to a row in which caret is located. | ||
* | ||
* @readonly | ||
* @member {String} module:table/commands/insertrowcommand~InsertRowCommand#order | ||
*/ | ||
this.order = options.order || 'below'; | ||
} | ||
|
||
/** | ||
* @inheritDoc | ||
*/ | ||
refresh() { | ||
const selection = this.editor.model.document.selection; | ||
|
||
const tableParent = getParentTable( selection.getFirstPosition() ); | ||
|
||
this.isEnabled = !!tableParent; | ||
} | ||
|
||
/** | ||
* @inheritDoc | ||
*/ | ||
execute() { | ||
const editor = this.editor; | ||
const selection = editor.model.document.selection; | ||
const tableUtils = editor.plugins.get( TableUtils ); | ||
|
||
const tableCell = selection.getFirstPosition().parent; | ||
const table = getParentTable( selection.getFirstPosition() ); | ||
|
||
const row = table.getChildIndex( tableCell.parent ); | ||
const insertAt = this.order === 'below' ? row + 1 : row; | ||
|
||
tableUtils.insertRows( table, { rows: 1, at: insertAt } ); | ||
} | ||
} |
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,60 @@ | ||
/** | ||
* @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. | ||
* For licensing, see LICENSE.md. | ||
*/ | ||
|
||
/** | ||
* @module table/commands/inserttablecommand | ||
*/ | ||
|
||
import Command from '@ckeditor/ckeditor5-core/src/command'; | ||
import Position from '@ckeditor/ckeditor5-engine/src/model/position'; | ||
import TableUtils from '../tableutils'; | ||
|
||
/** | ||
* The insert table command. | ||
* | ||
* @extends module:core/command~Command | ||
*/ | ||
export default class InsertTableCommand extends Command { | ||
/** | ||
* @inheritDoc | ||
*/ | ||
refresh() { | ||
const model = this.editor.model; | ||
const selection = model.document.selection; | ||
const schema = model.schema; | ||
|
||
const validParent = getInsertTableParent( selection.getFirstPosition() ); | ||
|
||
this.isEnabled = schema.checkChild( validParent, 'table' ); | ||
} | ||
|
||
/** | ||
* @inheritDoc | ||
*/ | ||
execute( options = {} ) { | ||
const model = this.editor.model; | ||
const selection = model.document.selection; | ||
const tableUtils = this.editor.plugins.get( TableUtils ); | ||
|
||
const rows = parseInt( options.rows ) || 2; | ||
const columns = parseInt( options.columns ) || 2; | ||
|
||
const firstPosition = selection.getFirstPosition(); | ||
|
||
const isRoot = firstPosition.parent === firstPosition.root; | ||
const insertPosition = isRoot ? Position.createAt( firstPosition ) : Position.createAfter( firstPosition.parent ); | ||
|
||
tableUtils.createTable( insertPosition, rows, columns ); | ||
} | ||
} | ||
|
||
// Returns valid parent to insert table | ||
// | ||
// @param {module:engine/model/position} position | ||
function getInsertTableParent( position ) { | ||
const parent = position.parent; | ||
|
||
return parent === parent.root ? parent : parent.parent; | ||
} |
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,170 @@ | ||
/** | ||
* @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. | ||
* For licensing, see LICENSE.md. | ||
*/ | ||
|
||
/** | ||
* @module table/commands/mergecellcommand | ||
*/ | ||
|
||
import Command from '@ckeditor/ckeditor5-core/src/command'; | ||
import Position from '@ckeditor/ckeditor5-engine/src/model/position'; | ||
import Range from '@ckeditor/ckeditor5-engine/src/model/range'; | ||
import TableWalker from '../tablewalker'; | ||
|
||
/** | ||
* The merge cell command. | ||
* | ||
* @extends module:core/command~Command | ||
*/ | ||
export default class MergeCellCommand extends Command { | ||
/** | ||
* Creates a new `MergeCellCommand` instance. | ||
* | ||
* @param {module:core/editor/editor~Editor} editor Editor on which this command will be used. | ||
* @param {Object} options | ||
* @param {String} options.direction Indicates which cell merge to currently selected one. | ||
* Possible values are: "left", "right", "up" and "down". | ||
*/ | ||
constructor( editor, options ) { | ||
super( editor ); | ||
|
||
/** | ||
* The direction indicates which cell will be merged to currently selected one. | ||
* | ||
* @readonly | ||
* @member {String} #direction | ||
*/ | ||
this.direction = options.direction; | ||
|
||
/** | ||
* Whether the merge is horizontal (left/right) or vertical (up/down). | ||
* | ||
* @readonly | ||
* @member {Boolean} #isHorizontal | ||
*/ | ||
this.isHorizontal = this.direction == 'right' || this.direction == 'left'; | ||
} | ||
|
||
/** | ||
* @inheritDoc | ||
*/ | ||
refresh() { | ||
const cellToMerge = this._getMergeableCell(); | ||
|
||
this.isEnabled = !!cellToMerge; | ||
// In order to check if currently selected cell can be merged with one defined by #direction some computation are done beforehand. | ||
// As such we can cache it as a command's value. | ||
this.value = cellToMerge; | ||
} | ||
|
||
/** | ||
* @inheritDoc | ||
*/ | ||
execute() { | ||
const model = this.editor.model; | ||
const doc = model.document; | ||
const tableCell = doc.selection.getFirstPosition().parent; | ||
const cellToMerge = this.value; | ||
const direction = this.direction; | ||
|
||
model.change( writer => { | ||
const isMergeNext = direction == 'right' || direction == 'down'; | ||
|
||
// The merge mechanism is always the same so sort cells to be merged. | ||
const cellToExpand = isMergeNext ? tableCell : cellToMerge; | ||
const cellToRemove = isMergeNext ? cellToMerge : tableCell; | ||
|
||
writer.move( Range.createIn( cellToRemove ), Position.createAt( cellToExpand, 'end' ) ); | ||
writer.remove( cellToRemove ); | ||
|
||
const spanAttribute = this.isHorizontal ? 'colspan' : 'rowspan'; | ||
const cellSpan = parseInt( tableCell.getAttribute( spanAttribute ) || 1 ); | ||
const cellToMergeSpan = parseInt( cellToMerge.getAttribute( spanAttribute ) || 1 ); | ||
|
||
writer.setAttribute( spanAttribute, cellSpan + cellToMergeSpan, cellToExpand ); | ||
|
||
writer.setSelection( Range.createIn( cellToExpand ) ); | ||
} ); | ||
} | ||
|
||
/** | ||
* Returns a cell that is mergeable with current cell depending on command's direction. | ||
* | ||
* @returns {module:engine/model/element|undefined} | ||
* @private | ||
*/ | ||
_getMergeableCell() { | ||
const model = this.editor.model; | ||
const doc = model.document; | ||
const element = doc.selection.getFirstPosition().parent; | ||
|
||
if ( !element.is( 'tableCell' ) ) { | ||
return; | ||
} | ||
|
||
// First get the cell on proper direction. | ||
const cellToMerge = this.isHorizontal ? getHorizontalCell( element, this.direction ) : getVerticalCell( element, this.direction ); | ||
|
||
if ( !cellToMerge ) { | ||
return; | ||
} | ||
|
||
// If found check if the span perpendicular to merge direction is equal on both cells. | ||
const spanAttribute = this.isHorizontal ? 'rowspan' : 'colspan'; | ||
const span = parseInt( element.getAttribute( spanAttribute ) || 1 ); | ||
|
||
const cellToMergeSpan = parseInt( cellToMerge.getAttribute( spanAttribute ) || 1 ); | ||
|
||
if ( cellToMergeSpan === span ) { | ||
return cellToMerge; | ||
} | ||
} | ||
} | ||
|
||
// Returns horizontally mergeable cell. | ||
// | ||
// @param {module:engine/model/element~Element} tableCell | ||
// @param {String} direction | ||
// @returns {module:engine/model/node~Node|null} | ||
function getHorizontalCell( tableCell, direction ) { | ||
return direction == 'right' ? tableCell.nextSibling : tableCell.previousSibling; | ||
} | ||
|
||
// Returns vertically mergeable cell. | ||
// | ||
// @param {module:engine/model/element~Element} tableCell | ||
// @param {String} direction | ||
// @returns {module:engine/model/node~Node|null} | ||
function getVerticalCell( tableCell, direction ) { | ||
const tableRow = tableCell.parent; | ||
const table = tableRow.parent; | ||
|
||
const rowIndex = table.getChildIndex( tableRow ); | ||
|
||
// Don't search for mergeable cell if direction points out of the table. | ||
if ( ( direction == 'down' && rowIndex === table.childCount - 1 ) || ( direction == 'up' && rowIndex === 0 ) ) { | ||
return; | ||
} | ||
|
||
const headingRows = table.getAttribute( 'headingRows' ) || 0; | ||
|
||
// Don't search for mergeable cell if direction points out of the current table section. | ||
if ( headingRows && ( ( direction == 'down' && rowIndex === headingRows - 1 ) || ( direction == 'up' && rowIndex === headingRows ) ) ) { | ||
return; | ||
} | ||
|
||
const rowspan = parseInt( tableCell.getAttribute( 'rowspan' ) || 1 ); | ||
const mergeRow = direction == 'down' ? rowIndex + rowspan : rowIndex; | ||
|
||
const tableMap = [ ...new TableWalker( table, { endRow: mergeRow } ) ]; | ||
|
||
const currentCellData = tableMap.find( value => value.cell === tableCell ); | ||
const mergeColumn = currentCellData.column; | ||
|
||
const cellToMergeData = tableMap.find( ( { row, column } ) => { | ||
return column === mergeColumn && ( direction == 'down' ? mergeRow === row : mergeRow === rowspan + row ); | ||
} ); | ||
|
||
return cellToMergeData && cellToMergeData.cell; | ||
} |
Oops, something went wrong.