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 #10 from ckeditor/t/9
Browse files Browse the repository at this point in the history
Feature: Initial table support. Closes #4. Closes #7. Closes #9.
  • Loading branch information
Reinmar authored May 29, 2018
2 parents 62e489f + f6bbbab commit bfe335b
Show file tree
Hide file tree
Showing 38 changed files with 8,462 additions and 0 deletions.
11 changes: 11 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,19 @@
"ckeditor5-feature"
],
"dependencies": {
"@ckeditor/ckeditor5-core": "^10.0.0",
"@ckeditor/ckeditor5-engine": "^10.0.0",
"@ckeditor/ckeditor5-ui": "^10.0.0",
"@ckeditor/ckeditor5-widget": "^10.0.0"
},
"devDependencies": {
"@ckeditor/ckeditor5-editor-classic": "^10.0.0",
"@ckeditor/ckeditor5-paragraph": "^10.0.0",
"@ckeditor/ckeditor5-utils": "^10.0.0",
"eslint": "^4.15.0",
"eslint-config-ckeditor5": "^1.0.7",
"husky": "^0.14.3",
"lint-staged": "^7.0.0"
},
"engines": {
"node": ">=6.0.0",
Expand Down
67 changes: 67 additions & 0 deletions src/commands/insertcolumncommand.js
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 } );
}
}
67 changes: 67 additions & 0 deletions src/commands/insertrowcommand.js
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 } );
}
}
60 changes: 60 additions & 0 deletions src/commands/inserttablecommand.js
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;
}
170 changes: 170 additions & 0 deletions src/commands/mergecellcommand.js
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;
}
Loading

0 comments on commit bfe335b

Please sign in to comment.