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

Reply via email to