+ // Remove filler at end and empty spaces around the cell content.\r
+ function trimCell( cell )\r
+ {\r
+ var bogus = cell.getBogus();\r
+ bogus && bogus.remove();\r
+ cell.trim();\r
+ }\r
+\r
+ function placeCursorInCell( cell, placeAtEnd )\r
+ {\r
+ var range = new CKEDITOR.dom.range( cell.getDocument() );\r
+ if ( !range[ 'moveToElementEdit' + ( placeAtEnd ? 'End' : 'Start' ) ]( cell ) )\r
+ {\r
+ range.selectNodeContents( cell );\r
+ range.collapse( placeAtEnd ? false : true );\r
+ }\r
+ range.select( true );\r
+ }\r
+\r
+ function cellInRow( tableMap, rowIndex, cell )\r
+ {\r
+ var oRow = tableMap[ rowIndex ];\r
+ if ( typeof cell == 'undefined' )\r
+ return oRow;\r
+\r
+ for ( var c = 0 ; oRow && c < oRow.length ; c++ )\r
+ {\r
+ if ( cell.is && oRow[c] == cell.$ )\r
+ return c;\r
+ else if ( c == cell )\r
+ return new CKEDITOR.dom.element( oRow[ c ] );\r
+ }\r
+ return cell.is ? -1 : null;\r
+ }\r
+\r
+ function cellInCol( tableMap, colIndex, cell )\r
+ {\r
+ var oCol = [];\r
+ for ( var r = 0; r < tableMap.length; r++ )\r
+ {\r
+ var row = tableMap[ r ];\r
+ if ( typeof cell == 'undefined' )\r
+ oCol.push( row[ colIndex ] );\r
+ else if ( cell.is && row[ colIndex ] == cell.$ )\r
+ return r;\r
+ else if ( r == cell )\r
+ return new CKEDITOR.dom.element( row[ colIndex ] );\r
+ }\r
+\r
+ return ( typeof cell == 'undefined' )? oCol : cell.is ? -1 : null;\r
+ }\r
+\r
+ function mergeCells( selection, mergeDirection, isDetect )\r
+ {\r
+ var cells = getSelectedCells( selection );\r
+\r
+ // Invalid merge request if:\r
+ // 1. In batch mode despite that less than two selected.\r
+ // 2. In solo mode while not exactly only one selected.\r
+ // 3. Cells distributed in different table groups (e.g. from both thead and tbody).\r
+ var commonAncestor;\r
+ if ( ( mergeDirection ? cells.length != 1 : cells.length < 2 )\r
+ || ( commonAncestor = selection.getCommonAncestor() )\r
+ && commonAncestor.type == CKEDITOR.NODE_ELEMENT\r
+ && commonAncestor.is( 'table' ) )\r
+ {\r
+ return false;\r
+ }\r
+\r
+ var cell,\r
+ firstCell = cells[ 0 ],\r
+ table = firstCell.getAscendant( 'table' ),\r
+ map = CKEDITOR.tools.buildTableMap( table ),\r
+ mapHeight = map.length,\r
+ mapWidth = map[ 0 ].length,\r
+ startRow = firstCell.getParent().$.rowIndex,\r
+ startColumn = cellInRow( map, startRow, firstCell );\r
+\r
+ if ( mergeDirection )\r
+ {\r
+ var targetCell;\r
+ try\r
+ {\r
+ targetCell =\r
+ map[ mergeDirection == 'up' ?\r
+ ( startRow - 1 ):\r
+ mergeDirection == 'down' ? ( startRow + 1 ) : startRow ] [\r
+ mergeDirection == 'left' ?\r
+ ( startColumn - 1 ):\r
+ mergeDirection == 'right' ? ( startColumn + 1 ) : startColumn ];\r
+\r
+ }\r
+ catch( er )\r
+ {\r
+ return false;\r
+ }\r
+\r
+ // 1. No cell could be merged.\r
+ // 2. Same cell actually.\r
+ if ( !targetCell || firstCell.$ == targetCell )\r
+ return false;\r
+\r
+ // Sort in map order regardless of the DOM sequence.\r
+ cells[ ( mergeDirection == 'up' || mergeDirection == 'left' ) ?\r
+ 'unshift' : 'push' ]( new CKEDITOR.dom.element( targetCell ) );\r
+ }\r
+\r
+ // Start from here are merging way ignorance (merge up/right, batch merge).\r
+ var doc = firstCell.getDocument(),\r
+ lastRowIndex = startRow,\r
+ totalRowSpan = 0,\r
+ totalColSpan = 0,\r
+ // Use a documentFragment as buffer when appending cell contents.\r
+ frag = !isDetect && new CKEDITOR.dom.documentFragment( doc ),\r
+ dimension = 0;\r
+\r
+ for ( var i = 0; i < cells.length; i++ )\r
+ {\r
+ cell = cells[ i ];\r
+\r
+ var tr = cell.getParent(),\r
+ cellFirstChild = cell.getFirst(),\r
+ colSpan = cell.$.colSpan,\r
+ rowSpan = cell.$.rowSpan,\r
+ rowIndex = tr.$.rowIndex,\r
+ colIndex = cellInRow( map, rowIndex, cell );\r
+\r
+ // Accumulated the actual places taken by all selected cells.\r
+ dimension += colSpan * rowSpan;\r
+ // Accumulated the maximum virtual spans from column and row.\r
+ totalColSpan = Math.max( totalColSpan, colIndex - startColumn + colSpan ) ;\r
+ totalRowSpan = Math.max( totalRowSpan, rowIndex - startRow + rowSpan );\r
+\r
+ if ( !isDetect )\r
+ {\r
+ // Trim all cell fillers and check to remove empty cells.\r
+ if ( trimCell( cell ), cell.getChildren().count() )\r
+ {\r
+ // Merge vertically cells as two separated paragraphs.\r
+ if ( rowIndex != lastRowIndex\r
+ && cellFirstChild\r
+ && !( cellFirstChild.isBlockBoundary\r
+ && cellFirstChild.isBlockBoundary( { br : 1 } ) ) )\r
+ {\r
+ var last = frag.getLast( CKEDITOR.dom.walker.whitespaces( true ) );\r
+ if ( last && !( last.is && last.is( 'br' ) ) )\r
+ frag.append( new CKEDITOR.dom.element( 'br' ) );\r
+ }\r
+\r
+ cell.moveChildren( frag );\r
+ }\r
+ i ? cell.remove() : cell.setHtml( '' );\r
+ }\r
+ lastRowIndex = rowIndex;\r
+ }\r
+\r
+ if ( !isDetect )\r
+ {\r
+ frag.moveChildren( firstCell );\r
+\r
+ if ( !CKEDITOR.env.ie )\r
+ firstCell.appendBogus();\r
+\r
+ if ( totalColSpan >= mapWidth )\r
+ firstCell.removeAttribute( 'rowSpan' );\r
+ else\r
+ firstCell.$.rowSpan = totalRowSpan;\r
+\r
+ if ( totalRowSpan >= mapHeight )\r
+ firstCell.removeAttribute( 'colSpan' );\r
+ else\r
+ firstCell.$.colSpan = totalColSpan;\r
+\r
+ // Swip empty <tr> left at the end of table due to the merging.\r
+ var trs = new CKEDITOR.dom.nodeList( table.$.rows ),\r
+ count = trs.count();\r
+\r
+ for ( i = count - 1; i >= 0; i-- )\r
+ {\r
+ var tailTr = trs.getItem( i );\r
+ if ( !tailTr.$.cells.length )\r
+ {\r
+ tailTr.remove();\r
+ count++;\r
+ continue;\r
+ }\r
+ }\r
+\r
+ return firstCell;\r
+ }\r
+ // Be able to merge cells only if actual dimension of selected\r
+ // cells equals to the caculated rectangle.\r
+ else\r
+ return ( totalRowSpan * totalColSpan ) == dimension;\r
+ }\r
+\r
+ function verticalSplitCell ( selection, isDetect )\r
+ {\r
+ var cells = getSelectedCells( selection );\r
+ if ( cells.length > 1 )\r
+ return false;\r
+ else if ( isDetect )\r
+ return true;\r
+\r
+ var cell = cells[ 0 ],\r
+ tr = cell.getParent(),\r
+ table = tr.getAscendant( 'table' ),\r
+ map = CKEDITOR.tools.buildTableMap( table ),\r
+ rowIndex = tr.$.rowIndex,\r
+ colIndex = cellInRow( map, rowIndex, cell ),\r
+ rowSpan = cell.$.rowSpan,\r
+ newCell,\r
+ newRowSpan,\r
+ newCellRowSpan,\r
+ newRowIndex;\r
+\r
+ if ( rowSpan > 1 )\r
+ {\r
+ newRowSpan = Math.ceil( rowSpan / 2 );\r
+ newCellRowSpan = Math.floor( rowSpan / 2 );\r
+ newRowIndex = rowIndex + newRowSpan;\r
+ var newCellTr = new CKEDITOR.dom.element( table.$.rows[ newRowIndex ] ),\r
+ newCellRow = cellInRow( map, newRowIndex ),\r
+ candidateCell;\r
+\r
+ newCell = cell.clone();\r
+\r
+ // Figure out where to insert the new cell by checking the vitual row.\r
+ for ( var c = 0; c < newCellRow.length; c++ )\r
+ {\r
+ candidateCell = newCellRow[ c ];\r
+ // Catch first cell actually following the column.\r
+ if ( candidateCell.parentNode == newCellTr.$\r
+ && c > colIndex )\r
+ {\r
+ newCell.insertBefore( new CKEDITOR.dom.element( candidateCell ) );\r
+ break;\r
+ }\r
+ else\r
+ candidateCell = null;\r
+ }\r
+\r
+ // The destination row is empty, append at will.\r
+ if ( !candidateCell )\r
+ newCellTr.append( newCell, true );\r
+ }\r
+ else\r
+ {\r
+ newCellRowSpan = newRowSpan = 1;\r
+\r
+ newCellTr = tr.clone();\r
+ newCellTr.insertAfter( tr );\r
+ newCellTr.append( newCell = cell.clone() );\r
+\r
+ var cellsInSameRow = cellInRow( map, rowIndex );\r
+ for ( var i = 0; i < cellsInSameRow.length; i++ )\r
+ cellsInSameRow[ i ].rowSpan++;\r
+ }\r
+\r
+ if ( !CKEDITOR.env.ie )\r
+ newCell.appendBogus();\r
+\r
+ cell.$.rowSpan = newRowSpan;\r
+ newCell.$.rowSpan = newCellRowSpan;\r
+ if ( newRowSpan == 1 )\r
+ cell.removeAttribute( 'rowSpan' );\r
+ if ( newCellRowSpan == 1 )\r
+ newCell.removeAttribute( 'rowSpan' );\r
+\r
+ return newCell;\r
+ }\r
+\r
+ function horizontalSplitCell( selection, isDetect )\r
+ {\r
+ var cells = getSelectedCells( selection );\r
+ if ( cells.length > 1 )\r
+ return false;\r
+ else if ( isDetect )\r
+ return true;\r
+\r
+ var cell = cells[ 0 ],\r
+ tr = cell.getParent(),\r
+ table = tr.getAscendant( 'table' ),\r
+ map = CKEDITOR.tools.buildTableMap( table ),\r
+ rowIndex = tr.$.rowIndex,\r
+ colIndex = cellInRow( map, rowIndex, cell ),\r
+ colSpan = cell.$.colSpan,\r
+ newCell,\r
+ newColSpan,\r
+ newCellColSpan;\r
+\r
+ if ( colSpan > 1 )\r
+ {\r
+ newColSpan = Math.ceil( colSpan / 2 );\r
+ newCellColSpan = Math.floor( colSpan / 2 );\r
+ }\r
+ else\r
+ {\r
+ newCellColSpan = newColSpan = 1;\r
+ var cellsInSameCol = cellInCol( map, colIndex );\r
+ for ( var i = 0; i < cellsInSameCol.length; i++ )\r
+ cellsInSameCol[ i ].colSpan++;\r
+ }\r
+ newCell = cell.clone();\r
+ newCell.insertAfter( cell );\r
+ if ( !CKEDITOR.env.ie )\r
+ newCell.appendBogus();\r
+\r
+ cell.$.colSpan = newColSpan;\r
+ newCell.$.colSpan = newCellColSpan;\r
+ if ( newColSpan == 1 )\r
+ cell.removeAttribute( 'colSpan' );\r
+ if ( newCellColSpan == 1 )\r
+ newCell.removeAttribute( 'colSpan' );\r
+\r
+ return newCell;\r
+ }\r