Index: /CKEditor/branches/versions/3.1.x/CHANGES.html =================================================================== --- /CKEditor/branches/versions/3.1.x/CHANGES.html (revision 4608) +++ /CKEditor/branches/versions/3.1.x/CHANGES.html (revision 4609) @@ -47,4 +47,5 @@
Index: /CKEditor/branches/versions/3.1.x/_source/core/dom/range.js =================================================================== --- /CKEditor/branches/versions/3.1.x/_source/core/dom/range.js (revision 4608) +++ /CKEditor/branches/versions/3.1.x/_source/core/dom/range.js (revision 4609) @@ -1579,14 +1579,14 @@ return walker.checkForward(); }, - /** - * Moves the range boundaries to the first editing point inside an + * Moves the range boundaries to the first/end editing point inside an * element. For example, in an element tree like * "<p><b><i></i></b> Text</p>", the start editing point is * "<p><b><i>^</i></b> Text</p>" (inside <i>). * @param {CKEDITOR.dom.element} targetElement The element into which - * look for the editing spot. + * look for the editing spot, it should be guaranteed to contains at least one editable position. + @param {Boolean} isMoveToEnd Whether move to the end editable position. */ - moveToElementEditStart : function( targetElement ) + moveToElementEditablePosition: function( targetElement, isMoveToEnd ) { var editableElement; @@ -1599,10 +1599,15 @@ break ; // If we already found an editable element, stop the loop. - targetElement = targetElement.getFirst(); + targetElement = targetElement[ isMoveToEnd? 'getLast' : 'getFirst' ].call( targetElement ); } if ( editableElement ) { - this.moveToPosition(editableElement, CKEDITOR.POSITION_AFTER_START); + // Make sure carot anchor before filler when moving to editable end. + var filler = editableElement.isBlockBoundary() && editableElement.getBogus(); + this.moveToPosition( isMoveToEnd && filler ? filler : editableElement, + isMoveToEnd && filler ? CKEDITOR.POSITION_BEFORE_START : + isMoveToEnd ? CKEDITOR.POSITION_BEFORE_END : + CKEDITOR.POSITION_AFTER_START ); return true; } @@ -1612,20 +1617,17 @@ /** - * Get the single node enclosed within the range if there's one. + *@see {CKEDITOR.dom.range.moveToElementEditablePosition} */ - getEnclosedNode : function() - { - var walkerRange = this.clone(), - walker = new CKEDITOR.dom.walker( walkerRange ), - isNotBookmarks = CKEDITOR.dom.walker.bookmark( true ), - isNotWhitespaces = CKEDITOR.dom.walker.whitespaces( true ), - evaluator = function( node ) - { - return isNotWhitespaces( node ) && isNotBookmarks( node ); - }; - walkerRange.evaluator = evaluator; - var node = walker.next(); - walker.reset(); - return node && node.equals( walker.previous() ) ? node : null; + moveToElementEditStart : function( target ) + { + return this.moveToElementEditablePosition( target ); + }, + + /** + *@see {CKEDITOR.dom.range.moveToElementEditablePosition} + */ + moveToElementEditEnd : function( target ) + { + return this.moveToElementEditablePosition( target, true ); }, Index: /CKEditor/branches/versions/3.1.x/_source/core/dom/walker.js =================================================================== --- /CKEditor/branches/versions/3.1.x/_source/core/dom/walker.js (revision 4608) +++ /CKEditor/branches/versions/3.1.x/_source/core/dom/walker.js (revision 4609) @@ -429,3 +429,23 @@ }; + var tailNbspRegex = /^[\t\r\n ]*(?: |\xa0)$/, + isNotWhitespaces = CKEDITOR.dom.walker.whitespaces( true ), + isNotBookmark = CKEDITOR.dom.walker.bookmark( false, true ), + fillerEvaluator = function( element ) + { + return isNotBookmark( element ) && isNotWhitespaces( element ); + }; + + // Check if there's a filler node at the end of an element, and return it. + CKEDITOR.dom.element.prototype.getBogus = function () + { + var tail = this.getLast( fillerEvaluator ); + if ( tail && ( !CKEDITOR.env.ie ? tail.is && tail.is( 'br' ) + : tail.getText && tailNbspRegex.test( tail.getText() ) ) ) + { + return tail; + } + return false; + }; + })(); Index: /CKEditor/branches/versions/3.1.x/_source/plugins/contextmenu/plugin.js =================================================================== --- /CKEditor/branches/versions/3.1.x/_source/plugins/contextmenu/plugin.js (revision 4608) +++ /CKEditor/branches/versions/3.1.x/_source/plugins/contextmenu/plugin.js (revision 4609) @@ -83,9 +83,4 @@ var selection = this.editor.getSelection(), element = selection && selection.getStartElement(); - - // Lock the selection in IE, so it can be restored when closing the - // menu. - if ( CKEDITOR.env.ie ) - selection.lock(); menu.onHide = CKEDITOR.tools.bind( function() @@ -178,4 +173,15 @@ } } ); + } + + // Certain forms of IE selection changes on 'contextmenu' event, + // lock the selection before that.(#4041) + if ( CKEDITOR.env.ie ) + { + element.on( 'mousedown', function( event ) + { + if ( event.data.$.button == 2 ) + this.editor.getSelection().lock(); + }, this ); } Index: /CKEditor/branches/versions/3.1.x/_source/plugins/selection/plugin.js =================================================================== --- /CKEditor/branches/versions/3.1.x/_source/plugins/selection/plugin.js (revision 4608) +++ /CKEditor/branches/versions/3.1.x/_source/plugins/selection/plugin.js (revision 4609) @@ -950,5 +950,13 @@ this.selectRanges( ranges ); return this; - } + }, + + getCommonAncestor : function() + { + var ranges = this.getRanges(), + startNode = ranges[ 0 ].startContainer, + endNode = ranges[ ranges.length - 1 ].endContainer; + return startNode.getCommonAncestor( endNode ); + } }; })(); Index: /CKEditor/branches/versions/3.1.x/_source/plugins/tabletools/plugin.js =================================================================== --- /CKEditor/branches/versions/3.1.x/_source/plugins/tabletools/plugin.js (revision 4608) +++ /CKEditor/branches/versions/3.1.x/_source/plugins/tabletools/plugin.js (revision 4609) @@ -87,156 +87,4 @@ } - function createTableMap( $refCell ) - { - var refCell = new CKEDITOR.dom.element( $refCell ); - var $table = ( refCell.getName() == 'table' ? $refCell : refCell.getAscendant( 'table' ) ).$; - var $rows = $table.rows; - - // Row and column counters. - var r = -1; - var map = []; - for ( var i = 0 ; i < $rows.length ; i++ ) - { - r++; - if ( !map[ r ] ) - map[ r ] = []; - - var c = -1; - - for ( var j = 0 ; j < $rows[ i ].cells.length ; j++ ) - { - var $cell = $rows[ i ].cells[ j ]; - - c++; - while ( map[ r ][ c ] ) - c++; - - var colSpan = isNaN( $cell.colSpan ) ? 1 : $cell.colSpan; - var rowSpan = isNaN( $cell.rowSpan ) ? 1 : $cell.rowSpan; - - for ( var rs = 0 ; rs < rowSpan ; rs++ ) - { - if ( !map[ r + rs ] ) - map[ r + rs ] = []; - - for ( var cs = 0 ; cs < colSpan ; cs++ ) - map [ r + rs ][ c + cs ] = $rows[ i ].cells[ j ]; - } - - c += colSpan - 1; - } - } - - return map; - } - - function installTableMap( tableMap, $table ) - { - /* - * IE BUG: rowSpan is always 1 in IE if the cell isn't attached to a row. So - * store is separately in another attribute. (#1917) - */ - var rowSpanAttr = CKEDITOR.env.ie ? '_cke_rowspan' : 'rowSpan'; - - /* - * Disconnect all the cells in tableMap from their parents, set all colSpan - * and rowSpan attributes to 1. - */ - for ( var i = 0 ; i < tableMap.length ; i++ ) - { - for ( var j = 0 ; j < tableMap[ i ].length ; j++ ) - { - var $cell = tableMap[ i ][ j ]; - if ( $cell.parentNode ) - $cell.parentNode.removeChild( $cell ); - $cell.colSpan = $cell[ rowSpanAttr ] = 1; - } - } - - // Scan by rows and set colSpan. - var maxCol = 0; - for ( i = 0 ; i < tableMap.length ; i++ ) - { - for ( j = 0 ; j < tableMap[ i ].length ; j++ ) - { - $cell = tableMap[ i ][ j ]; - if ( !$cell ) - continue; - if ( j > maxCol ) - maxCol = j; - if ( $cell[ '_cke_colScanned' ] ) - continue; - if ( tableMap[ i ][ j - 1 ] == $cell ) - $cell.colSpan++; - if ( tableMap[ i ][ j + 1 ] != $cell ) - $cell[ '_cke_colScanned' ] = 1; - } - } - - // Scan by columns and set rowSpan. - for ( i = 0 ; i <= maxCol ; i++ ) - { - for ( j = 0 ; j < tableMap.length ; j++ ) - { - if ( !tableMap[ j ] ) - continue; - $cell = tableMap[ j ][ i ]; - if ( !$cell || $cell[ '_cke_rowScanned' ] ) - continue; - if ( tableMap[ j - 1 ] && tableMap[ j - 1 ][ i ] == $cell ) - $cell[ rowSpanAttr ]++; - if ( !tableMap[ j + 1 ] || tableMap[ j + 1 ][ i ] != $cell ) - $cell[ '_cke_rowScanned' ] = 1; - } - } - - // Clear all temporary flags. - for ( i = 0 ; i < tableMap.length ; i++ ) - { - for ( j = 0 ; j < tableMap[ i ].length ; j++ ) - { - $cell = tableMap[ i ][ j ]; - removeRawAttribute( $cell, '_cke_colScanned' ); - removeRawAttribute( $cell, '_cke_rowScanned' ); - } - } - - // Insert physical rows and columns to table. - for ( i = 0 ; i < tableMap.length ; i++ ) - { - var $row = $table.ownerDocument.createElement( 'tr' ); - for ( j = 0 ; j < tableMap[ i ].length ; ) - { - $cell = tableMap[ i ][ j ]; - if ( tableMap[ i - 1 ] && tableMap[ i - 1 ][ j ] == $cell ) - { - j += $cell.colSpan; - continue; - } - $row.appendChild( $cell ); - if ( rowSpanAttr != 'rowSpan' ) - { - $cell.rowSpan = $cell[ rowSpanAttr ]; - $cell.removeAttribute( rowSpanAttr ); - } - j += $cell.colSpan; - if ( $cell.colSpan == 1 ) - $cell.removeAttribute( 'colSpan' ); - if ( $cell.rowSpan == 1 ) - $cell.removeAttribute( 'rowSpan' ); - } - - if ( CKEDITOR.env.ie ) - $table.rows[ i ].replaceNode( $row ); - else - { - var dest = new CKEDITOR.dom.element( $table.rows[ i ] ); - var src = new CKEDITOR.dom.element( $row ); - dest.setHtml( '' ); - src.moveChildren( dest ); - } - } - } - function clearRow( $tr ) { @@ -417,4 +265,359 @@ } + // Remove filler at end and empty spaces around the cell content. + function trimCell( cell ) + { + var bogus = cell.getBogus(); + bogus && bogus.remove(); + cell.trim(); + } + + function placeCursorInCell( cell, placeAtEnd ) + { + var range = new CKEDITOR.dom.range( cell.getDocument() ); + if ( !range[ 'moveToElementEdit' + ( placeAtEnd ? 'End' : 'Start' ) ]( cell ) ) + { + range.selectNodeContents( cell ); + range.collapse( placeAtEnd ? false : true ); + } + range.select( true ); + } + + function buildTableMap( table ) + { + + var aRows = table.$.rows ; + + // Row and Column counters. + var r = -1 ; + + var aMap = []; + + for ( var i = 0 ; i < aRows.length ; i++ ) + { + r++ ; + !aMap[r] && ( aMap[r] = [] ); + + var c = -1 ; + + for ( var j = 0 ; j < aRows[i].cells.length ; j++ ) + { + var oCell = aRows[i].cells[j] ; + + c++ ; + while ( aMap[r][c] ) + c++ ; + + var iColSpan = isNaN( oCell.colSpan ) ? 1 : oCell.colSpan ; + var iRowSpan = isNaN( oCell.rowSpan ) ? 1 : oCell.rowSpan ; + + for ( var rs = 0 ; rs < iRowSpan ; rs++ ) + { + if ( !aMap[r + rs] ) + aMap[r + rs] = new Array() ; + + for ( var cs = 0 ; cs < iColSpan ; cs++ ) + { + aMap[r + rs][c + cs] = aRows[i].cells[j] ; + } + } + + c += iColSpan - 1 ; + } + } + return aMap ; + } + + function cellInRow( tableMap, rowIndex, cell ) + { + var oRow = tableMap[ rowIndex ]; + if( typeof cell == 'undefined' ) + return oRow; + + for ( var c = 0 ; oRow && c < oRow.length ; c++ ) + { + if ( cell.is && oRow[c] == cell.$ ) + return c; + else if( c == cell ) + return new CKEDITOR.dom.element( oRow[ c ] ); + } + return cell.is ? -1 : null; + } + + function cellInCol( tableMap, colIndex, cell ) + { + var oCol = []; + for ( var r = 0; r < tableMap.length; r++ ) + { + var row = tableMap[ r ]; + if( typeof cell == 'undefined' ) + oCol.push( row[ colIndex ] ); + else if( cell.is && row[ colIndex ] == cell.$ ) + return r; + else if( r == cell ) + return new CKEDITOR.dom.element( row[ colIndex ] ); + } + + return ( typeof cell == 'undefined' )? oCol : cell.is ? -1 : null; + } + + function mergeCells( selection, mergeDirection, isDetect ) + { + var cells = getSelectedCells( selection ); + + // Invalid merge request if: + // 1. In batch mode despite that less than two selected. + // 2. In solo mode while not exactly only one selected. + // 3. Cells distributed in different table groups (e.g. from both thead and tbody). + if( ( mergeDirection ? cells.length != 1 : cells.length < 2 ) + || selection.getCommonAncestor().is( 'table' ) ) + return false; + + var cell, + firstCell = cells[ 0 ], + table = firstCell.getAscendant( 'table' ), + map = buildTableMap( table ), + mapHeight = map.length, + mapWidth = map[ 0 ].length, + startRow = firstCell.getParent().$.rowIndex, + startColumn = cellInRow( map, startRow, firstCell ); + + if( mergeDirection ) + { + var targetCell; + try + { + targetCell = + map[ mergeDirection == 'up' ? + ( startRow - 1 ): + mergeDirection == 'down' ? ( startRow + 1 ) : startRow ] [ + mergeDirection == 'left' ? + ( startColumn - 1 ): + mergeDirection == 'right' ? ( startColumn + 1 ) : startColumn ]; + + } + catch( er ) + { + return false; + } + + // 1. No cell could be merged. + // 2. Same cell actually. + if( !targetCell || firstCell.$ == targetCell ) + return false; + + // Sort in map order regardless of the DOM sequence. + cells[ ( mergeDirection == 'up' || mergeDirection == 'left' ) ? + 'unshift' : 'push' ]( new CKEDITOR.dom.element( targetCell ) ); + } + + // Start from here are merging way ignorance (merge up/right, batch merge). + var doc = firstCell.getDocument(), + lastRowIndex = startRow, + totalRowSpan = 0, + totalColSpan = 0, + // Use a documentFragment as buffer when appending cell contents. + frag = !isDetect && new CKEDITOR.dom.documentFragment( doc ), + dimension = 0; + + for ( var i = 0; i < cells.length; i++ ) + { + cell = cells[ i ]; + + var tr = cell.getParent(), + cellFirstChild = cell.getFirst(), + colSpan = cell.$.colSpan, + rowSpan = cell.$.rowSpan, + rowIndex = tr.$.rowIndex, + colIndex = cellInRow( map, rowIndex, cell ); + + // Accumulated the actual places taken by all selected cells. + dimension += colSpan * rowSpan; + // Accumulated the maximum virtual spans from column and row. + totalColSpan = Math.max( totalColSpan, colIndex - startColumn + colSpan ) ; + totalRowSpan = Math.max( totalRowSpan, rowIndex - startRow + rowSpan ); + + if ( !isDetect ) + { + // Trim all cell fillers and check to remove empty cells. + if( trimCell( cell ), cell.getChildren().count() ) + { + // Merge vertically cells as two separated paragraphs. + if( rowIndex != lastRowIndex + && cellFirstChild + && !( cellFirstChild.isBlockBoundary + && cellFirstChild.isBlockBoundary( { br : 1 } ) ) ) + { + var last = frag.getLast( CKEDITOR.dom.walker.whitespaces( true ) ); + if( last && !( last.is && last.is( 'br' ) ) ) + frag.append( new CKEDITOR.dom.element( 'br' ) ); + } + + cell.moveChildren( frag ); + } + i ? cell.remove() : cell.setHtml( '' ); + } + lastRowIndex = rowIndex; + } + + if ( !isDetect ) + { + frag.moveChildren( firstCell ); + + if( !CKEDITOR.env.ie ) + firstCell.appendBogus(); + + if( totalColSpan >= mapWidth ) + firstCell.removeAttribute( 'rowSpan' ); + else + firstCell.$.rowSpan = totalRowSpan; + + if( totalRowSpan >= mapHeight ) + firstCell.removeAttribute( 'colSpan' ); + else + firstCell.$.colSpan = totalColSpan; + + // Swip empty