diff --git a/VisualEditor.php b/VisualEditor.php
index 3be6ccf5ce..0eb7be1ed6 100644
--- a/VisualEditor.php
+++ b/VisualEditor.php
@@ -271,6 +271,7 @@ $wgResourceModules += array(
've/dm/nodes/ve.dm.ListNode.js',
've/dm/nodes/ve.dm.ParagraphNode.js',
've/dm/nodes/ve.dm.PreformattedNode.js',
+ 've/dm/nodes/ve.dm.TableCaptionNode.js',
've/dm/nodes/ve.dm.TableCellNode.js',
've/dm/nodes/ve.dm.TableNode.js',
've/dm/nodes/ve.dm.TableRowNode.js',
@@ -324,6 +325,7 @@ $wgResourceModules += array(
've/ce/nodes/ve.ce.ListNode.js',
've/ce/nodes/ve.ce.ParagraphNode.js',
've/ce/nodes/ve.ce.PreformattedNode.js',
+ 've/ce/nodes/ve.ce.TableCaptionNode.js',
've/ce/nodes/ve.ce.TableCellNode.js',
've/ce/nodes/ve.ce.TableNode.js',
've/ce/nodes/ve.ce.TableRowNode.js',
@@ -516,7 +518,6 @@ $wgResourceModules += array(
),
),
);
-
// Parsoid Wrapper API
$wgAutoloadClasses['ApiVisualEditor'] = $dir . 'ApiVisualEditor.php';
$wgAPIModules['visualeditor'] = 'ApiVisualEditor';
diff --git a/demos/ve/index.php b/demos/ve/index.php
index ca7deaea26..af9385e92f 100644
--- a/demos/ve/index.php
+++ b/demos/ve/index.php
@@ -155,6 +155,7 @@ $html = file_get_contents( $page );
+
@@ -208,6 +209,7 @@ $html = file_get_contents( $page );
+
diff --git a/modules/ve/ce/nodes/ve.ce.TableCaptionNode.js b/modules/ve/ce/nodes/ve.ce.TableCaptionNode.js
new file mode 100644
index 0000000000..67741ed1fc
--- /dev/null
+++ b/modules/ve/ce/nodes/ve.ce.TableCaptionNode.js
@@ -0,0 +1,31 @@
+/*!
+ * VisualEditor ContentEditable TableCaptionNode class.
+ *
+ * @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
+ * @license The MIT License (MIT); see LICENSE.txt
+ */
+
+/**
+ * ContentEditable table caption node.
+ *
+ * @class
+ * @extends ve.ce.BranchNode
+ * @constructor
+ * @param {ve.dm.TableCaptionNode} model Model to observe
+ */
+ve.ce.TableCaptionNode = function VeCeTableCaptionNode( model ) {
+ // Parent constructor
+ ve.ce.BranchNode.call( this, model, $( '
' ) );
+};
+
+/* Inheritance */
+
+ve.inheritClass( ve.ce.TableCaptionNode, ve.ce.BranchNode );
+
+/* Static Properties */
+
+ve.ce.TableCaptionNode.static.name = 'tableCaption';
+
+/* Registration */
+
+ve.ce.nodeFactory.register( ve.ce.TableCaptionNode );
diff --git a/modules/ve/dm/nodes/ve.dm.TableCaptionNode.js b/modules/ve/dm/nodes/ve.dm.TableCaptionNode.js
new file mode 100644
index 0000000000..0ec55a2ee3
--- /dev/null
+++ b/modules/ve/dm/nodes/ve.dm.TableCaptionNode.js
@@ -0,0 +1,44 @@
+/*!
+ * VisualEditor DataModel TableCaptionNode class.
+ *
+ * @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
+ * @license The MIT License (MIT); see LICENSE.txt
+ */
+
+/**
+ * DataModel table caption node.
+ *
+ * @class
+ * @extends ve.dm.BranchNode
+ * @constructor
+ * @param {ve.dm.BranchNode[]} [children] Child nodes to attach
+ * @param {Object} [element] Reference to element in linear model
+ */
+ve.dm.TableCaptionNode = function VeDmTableCaptionNode( children, element ) {
+ // Parent constructor
+ ve.dm.BranchNode.call( this, children, element );
+};
+
+/* Inheritance */
+
+ve.inheritClass( ve.dm.TableCaptionNode, ve.dm.BranchNode );
+
+/* Static Properties */
+
+ve.dm.TableCaptionNode.static.name = 'tableCaption';
+
+ve.dm.TableCaptionNode.static.parentNodeTypes = [ 'table' ];
+
+ve.dm.TableCaptionNode.static.matchTagNames = [ 'caption' ];
+
+ve.dm.TableCaptionNode.static.toDataElement = function () {
+ return { 'type': 'tableCaption' };
+};
+
+ve.dm.TableCaptionNode.static.toDomElements = function ( dataElement, doc ) {
+ return [ doc.createElement( 'caption' ) ];
+};
+
+/* Registration */
+
+ve.dm.modelRegistry.register( ve.dm.TableCaptionNode );
diff --git a/modules/ve/test/dm/ve.dm.example.js b/modules/ve/test/dm/ve.dm.example.js
index 2caeddfb2e..01086f50aa 100644
--- a/modules/ve/test/dm/ve.dm.example.js
+++ b/modules/ve/test/dm/ve.dm.example.js
@@ -570,6 +570,63 @@ ve.dm.example.withMetaMetaData = [
]
];
+ve.dm.example.complexTableHtml = '';
+
+ve.dm.example.complexTable = [
+ { 'type': 'table' },
+ { 'type': 'tableCaption' },
+ { 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } },
+ 'F',
+ 'o',
+ 'o',
+ { 'type': '/paragraph' },
+ { 'type': '/tableCaption' },
+ { 'type': 'tableSection', 'attributes': { 'style': 'header' } },
+ { 'type': 'tableRow' },
+ { 'type': 'tableCell', 'attributes': { 'style': 'header' } },
+ { 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } },
+ 'B',
+ 'a',
+ 'r',
+ { 'type': '/paragraph' },
+ { 'type': '/tableCell' },
+ { 'type': '/tableRow' },
+ { 'type': '/tableSection' },
+ { 'type': 'tableSection', 'attributes': { 'style': 'footer' } },
+ { 'type': 'tableRow' },
+ { 'type': 'tableCell', 'attributes': { 'style': 'data' } },
+ { 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } },
+ 'B',
+ 'a',
+ 'z',
+ { 'type': '/paragraph' },
+ { 'type': '/tableCell' },
+ { 'type': '/tableRow' },
+ { 'type': '/tableSection' },
+ { 'type': 'tableSection', 'attributes': { 'style': 'body' } },
+ { 'type': 'tableRow' },
+ { 'type': 'tableCell', 'attributes': { 'style': 'data' } },
+ { 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } },
+ 'Q',
+ 'u',
+ 'u',
+ 'x',
+ { 'type': '/paragraph' },
+ { 'type': '/tableCell' },
+ { 'type': 'tableCell', 'attributes': { 'style': 'data' } },
+ { 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } },
+ 'W',
+ 'h',
+ 'e',
+ 'e',
+ { 'type': '/paragraph' },
+ { 'type': '/tableCell' },
+ { 'type': '/tableRow' },
+ { 'type': '/tableSection' },
+ { 'type': '/table' }
+];
+
/**
* Sample content data index.
*
@@ -2370,26 +2427,6 @@ ve.dm.example.domToDataCases = {
{ 'type': '/paragraph' }
]
},
- 'context-sensitive nodes are alienated correctly': {
- 'html': '',
- 'data': [
- { 'type': 'table' },
- { 'type': 'alienBlock', 'attributes': { 'html': 'Foo' } },
- { 'type': '/alienBlock' },
- { 'type': 'tableSection', 'attributes': { 'style': 'body' } },
- { 'type': 'tableRow' },
- { 'type': 'tableCell', 'attributes': { 'style': 'data' } },
- { 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } },
- 'B',
- 'a',
- 'r',
- { 'type': '/paragraph' },
- { 'type': '/tableCell' },
- { 'type': '/tableRow' },
- { 'type': '/tableSection' },
- { 'type': '/table' }
- ]
- },
'whitespace before meta node in wrapping mode': {
'html': '',
'data': [
@@ -2429,6 +2466,10 @@ ve.dm.example.domToDataCases = {
{ 'type': '/tableSection' },
{ 'type': '/table' }
]
+ },
+ 'table with caption, head, foot and body': {
+ 'html': ve.dm.example.complexTableHtml,
+ 'data': ve.dm.example.complexTable
}
};
diff --git a/modules/ve/test/index.php b/modules/ve/test/index.php
index 49bc925310..138fc64c65 100644
--- a/modules/ve/test/index.php
+++ b/modules/ve/test/index.php
@@ -98,6 +98,7 @@
+
@@ -151,6 +152,7 @@
+