Oliverb has uploaded a new change for review.
https://gerrit.wikimedia.org/r/159311
Change subject: ContentEditable implementation for tables.
......................................................................
ContentEditable implementation for tables.
ve.ce.TableNode provides some visual sugar via overlay to display the table
selection.
Change-Id: I3d5509315e6f09138409bcaf4e39914874082353
---
M src/ce/nodes/ve.ce.TableCellNode.js
M src/ce/nodes/ve.ce.TableNode.js
M src/ce/styles/nodes/ve.ce.TableCellNode.css
M src/ce/styles/nodes/ve.ce.TableNode.css
4 files changed, 274 insertions(+), 6 deletions(-)
git pull ssh://gerrit.wikimedia.org:29418/VisualEditor/VisualEditor
refs/changes/11/159311/1
diff --git a/src/ce/nodes/ve.ce.TableCellNode.js
b/src/ce/nodes/ve.ce.TableCellNode.js
index 2fe7daa..183b212 100644
--- a/src/ce/nodes/ve.ce.TableCellNode.js
+++ b/src/ce/nodes/ve.ce.TableCellNode.js
@@ -20,9 +20,12 @@
// Events
this.model.connect( this, { update: 'onUpdate' } );
+ this.model.connect( this, { attributeChange: 'onAttributeChange' } );
// DOM changes
this.$element.addClass( 've-ce-tableCellNode' );
+ this.$element.attr('rowspan', this.model.getSpan('row'));
+ this.$element.attr('colspan', this.model.getSpan('col'));
};
/* Inheritance */
@@ -64,6 +67,19 @@
this.updateTagName();
};
+/**
+ * Handle attribute changes to keep the life HTML element updated.
+ */
+ve.ce.TableCellNode.prototype.onAttributeChange = function ( key, from, to ) {
+ if (key === 'colspan' || key === 'rowspan') {
+ if (to > 1) {
+ this.$element.attr(key, to);
+ } else {
+ this.$element.removeAttr(key);
+ }
+ }
+};
+
/* Registration */
ve.ce.nodeFactory.register( ve.ce.TableCellNode );
diff --git a/src/ce/nodes/ve.ce.TableNode.js b/src/ce/nodes/ve.ce.TableNode.js
index 52f0e4e..9ff21bf 100644
--- a/src/ce/nodes/ve.ce.TableNode.js
+++ b/src/ce/nodes/ve.ce.TableNode.js
@@ -18,14 +18,191 @@
// Parent constructor
ve.ce.BranchNode.call( this, model, config );
- // Initialization
- this.$element.addClass( 've-ce-tableNode' );
+ this.focussed = false;
+ // a ve.dm.TableMatrix.Rectange instance or null if no valid selection
+ this.selectedRectangle = null;
+
+ /* Events */
+
+ this.connect( this, {
+ 'setup': 'onTableNodeSetup',
+ 'teardown': 'onTableNodeTeardown',
+ } );
};
/* Inheritance */
OO.inheritClass( ve.ce.TableNode, ve.ce.BranchNode );
+/* Prototype */
+
+ve.ce.TableNode.prototype.onTableNodeSetup = function() {
+ // Exit if already setup or not attached
+ if ( this.isSetup || !this.root ) {
+ return;
+ }
+ var surface = this.getRoot().getSurface();
+ this.surfaceModel = surface.getModel();
+
+ // DOM changes
+
+ this.$element.addClass( 've-ce-tableNode' );
+
+ // Overlay
+
+ this.$rulerTop = $('<div>').addClass('ruler horizontal top');
+ this.$rulerBottom = $('<div>').addClass('ruler horizontal bottom');
+ this.$rulerLeft = $('<div>').addClass('ruler vertical left');
+ this.$rulerRight = $('<div>').addClass('ruler vertical right');
+ this.$bbox = $('<div>').addClass('selection-box');
+ this.$rowBracket = $('<div>').addClass('row-bracket');
+ this.$colBracket = $('<div>').addClass('column-bracket');
+
+ this.$overlay = $('<div contenteditable="false">')
+ .addClass('ve-ce-tableNodeOverlay')
+ .append([
+ this.$rulerTop, this.$rulerBottom,
+ this.$rulerLeft, this.$rulerRight,
+ this.$bbox,
+ this.$rowBracket,
+ this.$colBracket
+ ] );
+ this.$element.append(this.$overlay);
+
+ // Events
+
+ this.surfaceModel.connect( this, { 'select': 'onSurfaceModelSelect' });
+};
+
+ve.ce.TableNode.prototype.onTableNodeTeardown = function() {
+ this.surfaceModel.disconnect( this );
+};
+
+/**
+ * Reacts on selection changes and detects when the selection is fully within
+ * the table.
+ */
+ve.ce.TableNode.prototype.onSurfaceModelSelect = function( selection ) {
+ var range = this.model.getRange();
+ var isSelected, tableSelection;
+
+ // consider this table focussed when the selection is fully within the
range
+ isSelected = (selection && range.containsOffset(selection.from) &&
range.containsOffset(selection.to));
+
+ // make sure that the selection does really belong to this table not to
a nested one
+ if (isSelected) {
+ tableSelection =
ve.dm.TableNode.lookupSelection(this.surfaceModel.documentModel, selection);
+ isSelected = (tableSelection && tableSelection.node ===
this.model);
+ }
+
+ if (isSelected) {
+ if (!this.focussed) {
+ this.focussed = true;
+ this.$element.addClass('focussed');
+ }
+ this.selectedRectangle =
this.model.getRectangle(tableSelection.startCell, tableSelection.endCell);
+ this.updateOverlay();
+ } else if (!isSelected && this.focussed) {
+ this.focussed = false;
+ this.selectedRectangle = null;
+ this.$element.removeClass('focussed');
+ this.$element.find('.selected').removeClass('selected');
+ }
+};
+
+/**
+ * Recomputes the overlay positions according to the current selection.
+ *
+ * @method
+ */
+ve.ce.TableNode.prototype.updateOverlay = function() {
+ var $cells, $cell, offset, width, height,
+ top, left, bottom, right,
+ tableOffset, tableHeight, tableWidth;
+
+ this.$overlay.css({'visibility': 'hidden'});
+
+ $cells = this.getElementsForSelectedRectangle();
+ this.$element.find('.selected').removeClass('selected');
+
+ top = Number.MAX_VALUE;
+ bottom = 0;
+ left = Number.MAX_VALUE;
+ right = 0;
+
+ // compute a bounding box for the given cell elements
+ for (var i = 0; i < $cells.length; i++) {
+ $cell = $cells[i];
+ $cell.addClass('selected');
+ offset = $cell.offset();
+ width = $cell.outerWidth();
+ height = $cell.outerHeight();
+
+ top = Math.min(top, offset.top);
+ bottom = Math.max(bottom, offset.top + height);
+ left = Math.min(left, offset.left);
+ right = Math.max(right, offset.left + width);
+ }
+
+ tableOffset = this.$element.offset();
+ tableHeight = this.$element.height();
+ tableWidth = this.$element.width();
+
+ this.$rulerTop.css({
+ 'top': top - tableOffset.top,
+ 'width': tableWidth
+ } );
+ this.$rulerBottom.css({
+ 'top': bottom - tableOffset.top,
+ 'width': tableWidth
+ } );
+ this.$rulerLeft.css({
+ 'left': left - tableOffset.left,
+ 'height': tableHeight
+ } );
+ this.$rulerRight.css({
+ 'left': right - tableOffset.left,
+ 'height': tableHeight
+ } );
+ this.$bbox.css({
+ 'left': left - tableOffset.left,
+ 'top': top - tableOffset.top,
+ // Note: can we get the border strength (last subtractor) from
the element?
+ 'height': bottom - top - 2,
+ 'width': right - left - 2,
+ } );
+ this.$rowBracket.css({
+ 'top': top - tableOffset.top - 2,
+ // Note: can we get the border strength (last subtractor) from
the element?
+ 'height': bottom - top,
+ } );
+ this.$colBracket.css({
+ 'left': left - tableOffset.left - 2,
+ 'width': right - left,
+ } );
+
+ this.$overlay.css({'visibility': ''});
+};
+
+/**
+ * Retrieves DOM elements corresponding to the the cells for the current
selection.
+ */
+ve.ce.TableNode.prototype.getElementsForSelectedRectangle = function() {
+ var cellModels, cells, rect, cell, cellNode, offset, matrix, i;
+ cells = [];
+ matrix = this.model.matrix;
+ rect = this.selectedRectangle;
+ rect = matrix.getBoundingRectangle(rect);
+ cellModels = matrix.getCellsForRectangle(rect);
+ for (i = 0; i < cellModels.length; i++) {
+ cell = cellModels[i];
+ offset = cell.node.getRange().start -
this.model.getRange().start;
+ cellNode = this.getNodeFromOffset(offset);
+ cells.push(cellNode.$element);
+ }
+ return $(cells);
+};
+
/* Static Properties */
ve.ce.TableNode.static.name = 'table';
diff --git a/src/ce/styles/nodes/ve.ce.TableCellNode.css
b/src/ce/styles/nodes/ve.ce.TableCellNode.css
index cf9a340..6e820a5 100644
--- a/src/ce/styles/nodes/ve.ce.TableCellNode.css
+++ b/src/ce/styles/nodes/ve.ce.TableCellNode.css
@@ -4,11 +4,26 @@
* @copyright 2011-2014 VisualEditor Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
+.ve-ce-tableCellNode {
+ position: relative;
+ border: 1px solid #ccc;
+ padding: 5px 10px 5px 10px;
+}
+
+td.ve-ce-tableCellNode {
+ vertical-align: top;
+}
.ve-ce-tableCellNode .ve-ce-paragraphNode {
+ min-width: 10px;
margin: 0;
}
-.ve-ce-tableCellNode {
- border: 1px dotted #ccc;
+thead .ve-ce-tableCellNode {
+ background-color: #EEE;
+}
+
+.ve-ce-tableCellNode.selected {
+ border: 1px solid #008080;
+ border-style:double;
}
diff --git a/src/ce/styles/nodes/ve.ce.TableNode.css
b/src/ce/styles/nodes/ve.ce.TableNode.css
index c245cf3..3168f57 100644
--- a/src/ce/styles/nodes/ve.ce.TableNode.css
+++ b/src/ce/styles/nodes/ve.ce.TableNode.css
@@ -4,7 +4,67 @@
* @copyright 2011-2014 VisualEditor Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
-
.ve-ce-tableNode {
- border: 1px dotted #ccc;
+ position: relative;
+ margin-left:auto;
+ margin-right:auto;
+ border-spacing: 0;
+ border-collapse: collapse;
+}
+
+.ve-ce-tableNode > .ve-ce-tableNodeOverlay {
+ visibility: hidden;
+ pointer-events: none;
+}
+
+.ve-ce-tableNode.focussed > .ve-ce-tableNodeOverlay {
+ visibility: visible;
+}
+
+.ve-ce-tableNode > .ve-ce-tableNodeOverlay .ruler {
+ position: absolute;
+}
+
+.ve-ce-tableNode > .ve-ce-tableNodeOverlay .ruler.horizontal {
+ left: 0px;
+ height: 0px;
+ border-bottom: solid 1px #00ffff;
+ opacity: 0.3;
+}
+
+.ve-ce-tableNode > .ve-ce-tableNodeOverlay .ruler.vertical {
+ top: 0px;
+ width: 0px;
+ border-right: solid 1px #00ffff;
+ opacity: 0.3;
+}
+
+.ve-ce-tableNode > .ve-ce-tableNodeOverlay .selection-box {
+ position: absolute;
+ border: solid 2px #008080;
+ opacity: 0.3;
+}
+
+.ve-ce-tableNode > .ve-ce-tableNodeOverlay .row-bracket {
+ position: absolute;
+ left: -10px;
+ width: 3px;
+ border-style: solid;
+ border-color: #008080;
+ border-right: 0px;
+ opacity: 0.3;
+}
+
+.ve-ce-tableNode > .ve-ce-tableNodeOverlay .column-bracket {
+ position: absolute;
+ top: -10px;
+ height: 3px;
+ border-style: solid;
+ border-color: #008080;
+ border-bottom: 0px;
+ opacity: 0.3;
+}
+
+.ve-ce-tableNode caption {
+ caption-side: bottom;
}
--
To view, visit https://gerrit.wikimedia.org/r/159311
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I3d5509315e6f09138409bcaf4e39914874082353
Gerrit-PatchSet: 1
Gerrit-Project: VisualEditor/VisualEditor
Gerrit-Branch: master
Gerrit-Owner: Oliverb <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits