2 Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved.
3 For licensing, see LICENSE.html or http://ckeditor.com/license
8 var cellNodeRegex
= /^(?:td|th)$/;
10 function getSelectedCells( selection
)
12 // Walker will try to split text nodes, which will make the current selection
13 // invalid. So save bookmarks before doing anything.
14 var bookmarks
= selection
.createBookmarks();
16 var ranges
= selection
.getRanges();
20 function moveOutOfCellGuard( node
)
22 // Apply to the first cell only.
23 if ( retval
.length
> 0 )
26 // If we are exiting from the first </td>, then the td should definitely be
28 if ( node
.type
== CKEDITOR
.NODE_ELEMENT
&& cellNodeRegex
.test( node
.getName() )
29 && !node
.getCustomData( 'selected_cell' ) )
31 CKEDITOR
.dom
.element
.setMarker( database
, node
, 'selected_cell', true );
36 for ( var i
= 0 ; i
< ranges
.length
; i
++ )
38 var range
= ranges
[ i
];
40 if ( range
.collapsed
)
42 // Walker does not handle collapsed ranges yet - fall back to old API.
43 var startNode
= range
.getCommonAncestor();
44 var nearestCell
= startNode
.getAscendant( 'td', true ) || startNode
.getAscendant( 'th', true );
46 retval
.push( nearestCell
);
50 var walker
= new CKEDITOR
.dom
.walker( range
);
52 walker
.guard
= moveOutOfCellGuard
;
54 while ( ( node
= walker
.next() ) )
56 // If may be possible for us to have a range like this:
57 // <td>^1</td><td>^2</td>
58 // The 2nd td shouldn't be included.
60 // So we have to take care to include a td we've entered only when we've
61 // walked into its children.
63 var parent
= node
.getAscendant( 'td' ) || node
.getAscendant( 'th' );
64 if ( parent
&& !parent
.getCustomData( 'selected_cell' ) )
66 CKEDITOR
.dom
.element
.setMarker( database
, parent
, 'selected_cell', true );
67 retval
.push( parent
);
73 CKEDITOR
.dom
.element
.clearAllMarkers( database
);
75 // Restore selection position.
76 selection
.selectBookmarks( bookmarks
);
81 function getFocusElementAfterDelCells( cellsToDelete
) {
83 last
= cellsToDelete
.length
- 1,
88 while ( ( cell
= cellsToDelete
[ i
++ ] ) )
89 CKEDITOR
.dom
.element
.setMarker( database
, cell
, 'delete_cell', true );
91 // 1.first we check left or right side focusable cell row by row;
93 while ( ( cell
= cellsToDelete
[ i
++ ] ) )
95 if ( ( focusedCell
= cell
.getPrevious() ) && !focusedCell
.getCustomData( 'delete_cell' )
96 || ( focusedCell
= cell
.getNext() ) && !focusedCell
.getCustomData( 'delete_cell' ) )
98 CKEDITOR
.dom
.element
.clearAllMarkers( database
);
103 CKEDITOR
.dom
.element
.clearAllMarkers( database
);
105 // 2. then we check the toppest row (outside the selection area square) focusable cell
106 tr
= cellsToDelete
[ 0 ].getParent();
107 if ( ( tr
= tr
.getPrevious() ) )
110 // 3. last we check the lowerest row focusable cell
111 tr
= cellsToDelete
[ last
].getParent();
112 if ( ( tr
= tr
.getNext() ) )
113 return tr
.getChild( 0 );
118 function insertRow( selection
, insertBefore
)
120 var cells
= getSelectedCells( selection
),
121 firstCell
= cells
[ 0 ],
122 table
= firstCell
.getAscendant( 'table' ),
123 doc
= firstCell
.getDocument(),
124 startRow
= cells
[ 0 ].getParent(),
125 startRowIndex
= startRow
.$.rowIndex
,
126 lastCell
= cells
[ cells
.length
- 1 ],
127 endRowIndex
= lastCell
.getParent().$.rowIndex
+ lastCell
.$.rowSpan
- 1,
128 endRow
= new CKEDITOR
.dom
.element( table
.$.rows
[ endRowIndex
] ),
129 rowIndex
= insertBefore
? startRowIndex
: endRowIndex
,
130 row
= insertBefore
? startRow
: endRow
;
132 var map
= CKEDITOR
.tools
.buildTableMap( table
),
133 cloneRow
= map
[ rowIndex
],
134 nextRow
= insertBefore
? map
[ rowIndex
- 1 ] : map
[ rowIndex
+ 1 ],
135 width
= map
[0].length
;
137 var newRow
= doc
.createElement( 'tr' );
138 for ( var i
= 0; i
< width
; i
++ )
141 // Check whether there's a spanning row here, do not break it.
142 if ( cloneRow
[ i
].rowSpan
> 1 && nextRow
&& cloneRow
[ i
] == nextRow
[ i
] )
144 cell
= cloneRow
[ i
];
149 cell
= new CKEDITOR
.dom
.element( cloneRow
[ i
] ).clone();
150 cell
.removeAttribute( 'rowSpan' );
151 !CKEDITOR
.env
.ie
&& cell
.appendBogus();
152 newRow
.append( cell
);
156 i
+= cell
.colSpan
- 1;
160 newRow
.insertBefore( row
) :
161 newRow
.insertAfter( row
);
164 function deleteRows( selectionOrRow
)
166 if ( selectionOrRow
instanceof CKEDITOR
.dom
.selection
)
168 var cells
= getSelectedCells( selectionOrRow
),
169 firstCell
= cells
[ 0 ],
170 table
= firstCell
.getAscendant( 'table' ),
171 map
= CKEDITOR
.tools
.buildTableMap( table
),
172 startRow
= cells
[ 0 ].getParent(),
173 startRowIndex
= startRow
.$.rowIndex
,
174 lastCell
= cells
[ cells
.length
- 1 ],
175 endRowIndex
= lastCell
.getParent().$.rowIndex
+ lastCell
.$.rowSpan
- 1,
178 // Delete cell or reduce cell spans by checking through the table map.
179 for ( var i
= startRowIndex
; i
<= endRowIndex
; i
++ )
181 var mapRow
= map
[ i
],
182 row
= new CKEDITOR
.dom
.element( table
.$.rows
[ i
] );
184 for ( var j
= 0; j
< mapRow
.length
; j
++ )
186 var cell
= new CKEDITOR
.dom
.element( mapRow
[ j
] ),
187 cellRowIndex
= cell
.getParent().$.rowIndex
;
189 if ( cell
.$.rowSpan
== 1 )
194 // Span row of the cell, reduce spanning.
196 // Root row of the cell, root cell to next row.
197 if ( cellRowIndex
== i
)
199 var nextMapRow
= map
[ i
+ 1 ];
200 nextMapRow
[ j
- 1 ] ?
201 cell
.insertAfter( new CKEDITOR
.dom
.element( nextMapRow
[ j
- 1 ] ) )
202 : new CKEDITOR
.dom
.element( table
.$.rows
[ i
+ 1 ] ).append( cell
, 1 );
206 j
+= cell
.$.colSpan
- 1;
209 rowsToDelete
.push( row
);
212 var rows
= table
.$.rows
;
214 // Where to put the cursor after rows been deleted?
215 // 1. Into next sibling row if any;
216 // 2. Into previous sibling row if any;
217 // 3. Into table's parent element if it's the very last row.
218 var cursorPosition
= new CKEDITOR
.dom
.element( rows
[ endRowIndex
+ 1 ] || ( startRowIndex
> 0 ? rows
[ startRowIndex
- 1 ] : null ) || table
.$.parentNode
);
220 for ( i
= rowsToDelete
.length
; i
>= 0 ; i
-- )
221 deleteRows( rowsToDelete
[ i
] );
223 return cursorPosition
;
225 else if ( selectionOrRow
instanceof CKEDITOR
.dom
.element
)
227 table
= selectionOrRow
.getAscendant( 'table' );
229 if ( table
.$.rows
.length
== 1 )
232 selectionOrRow
.remove();
238 function getCellColIndex( cell
, isStart
)
240 var row
= cell
.getParent(),
241 rowCells
= row
.$.cells
;
244 for ( var i
= 0; i
< rowCells
.length
; i
++ )
246 var mapCell
= rowCells
[ i
];
247 colIndex
+= isStart
? 1 : mapCell
.colSpan
;
248 if ( mapCell
== cell
.$ )
255 function getColumnsIndices( cells
, isStart
)
257 var retval
= isStart
? Infinity
: 0;
258 for ( var i
= 0; i
< cells
.length
; i
++ )
260 var colIndex
= getCellColIndex( cells
[ i
], isStart
);
261 if ( isStart
? colIndex
< retval
: colIndex
> retval
)
267 function insertColumn( selection
, insertBefore
)
269 var cells
= getSelectedCells( selection
),
270 firstCell
= cells
[ 0 ],
271 table
= firstCell
.getAscendant( 'table' ),
272 startCol
= getColumnsIndices( cells
, 1 ),
273 lastCol
= getColumnsIndices( cells
),
274 colIndex
= insertBefore
? startCol
: lastCol
;
276 var map
= CKEDITOR
.tools
.buildTableMap( table
),
281 for ( var i
= 0; i
< height
; i
++ )
283 cloneCol
.push( map
[ i
][ colIndex
] );
284 var nextCell
= insertBefore
? map
[ i
][ colIndex
- 1 ] : map
[ i
][ colIndex
+ 1 ];
285 nextCell
&& nextCol
.push( nextCell
);
288 for ( i
= 0; i
< height
; i
++ )
291 // Check whether there's a spanning column here, do not break it.
292 if ( cloneCol
[ i
].colSpan
> 1
294 && nextCol
[ i
] == cloneCol
[ i
] )
296 cell
= cloneCol
[ i
];
301 cell
= new CKEDITOR
.dom
.element( cloneCol
[ i
] ).clone();
302 cell
.removeAttribute( 'colSpan' );
303 !CKEDITOR
.env
.ie
&& cell
.appendBogus();
304 cell
[ insertBefore
? 'insertBefore' : 'insertAfter' ].call( cell
, new CKEDITOR
.dom
.element ( cloneCol
[ i
] ) );
308 i
+= cell
.rowSpan
- 1;
312 function deleteColumns( selectionOrCell
)
314 var cells
= getSelectedCells( selectionOrCell
),
315 firstCell
= cells
[ 0 ],
316 lastCell
= cells
[ cells
.length
- 1 ],
317 table
= firstCell
.getAscendant( 'table' ),
318 map
= CKEDITOR
.tools
.buildTableMap( table
),
323 // Figure out selected cells' column indices.
324 for ( var i
= 0, rows
= map
.length
; i
< rows
; i
++ )
326 for ( var j
= 0, cols
= map
[ i
].length
; j
< cols
; j
++ )
328 if ( map
[ i
][ j
] == firstCell
.$ )
330 if ( map
[ i
][ j
] == lastCell
.$ )
335 // Delete cell or reduce cell spans by checking through the table map.
336 for ( i
= startColIndex
; i
<= endColIndex
; i
++ )
338 for ( j
= 0; j
< map
.length
; j
++ )
340 var mapRow
= map
[ j
],
341 row
= new CKEDITOR
.dom
.element( table
.$.rows
[ j
] ),
342 cell
= new CKEDITOR
.dom
.element( mapRow
[ i
] );
346 if ( cell
.$.colSpan
== 1 )
348 // Reduce the col spans.
352 j
+= cell
.$.rowSpan
- 1;
354 if ( !row
.$.cells
.length
)
355 rowsToDelete
.push( row
);
360 var firstRowCells
= table
.$.rows
[ 0 ] && table
.$.rows
[ 0 ].cells
;
362 // Where to put the cursor after columns been deleted?
363 // 1. Into next cell of the first row if any;
364 // 2. Into previous cell of the first row if any;
365 // 3. Into table's parent element;
366 var cursorPosition
= new CKEDITOR
.dom
.element( firstRowCells
[ startColIndex
] || ( startColIndex
? firstRowCells
[ startColIndex
- 1 ] : table
.$.parentNode
) );
368 // Delete table rows only if all columns are gone (do not remove empty row).
369 if ( rowsToDelete
.length
== rows
)
372 return cursorPosition
;
375 function getFocusElementAfterDelCols( cells
)
377 var cellIndexList
= [],
378 table
= cells
[ 0 ] && cells
[ 0 ].getAscendant( 'table' ),
380 targetIndex
, targetCell
;
382 // get the cellIndex list of delete cells
383 for ( i
= 0, length
= cells
.length
; i
< length
; i
++ )
384 cellIndexList
.push( cells
[i
].$.cellIndex
);
386 // get the focusable column index
387 cellIndexList
.sort();
388 for ( i
= 1, length
= cellIndexList
.length
; i
< length
; i
++ )
390 if ( cellIndexList
[ i
] - cellIndexList
[ i
- 1 ] > 1 )
392 targetIndex
= cellIndexList
[ i
- 1 ] + 1;
398 targetIndex
= cellIndexList
[ 0 ] > 0 ? ( cellIndexList
[ 0 ] - 1 )
399 : ( cellIndexList
[ cellIndexList
.length
- 1 ] + 1 );
401 // scan row by row to get the target cell
402 var rows
= table
.$.rows
;
403 for ( i
= 0, length
= rows
.length
; i
< length
; i
++ )
405 targetCell
= rows
[ i
].cells
[ targetIndex
];
410 return targetCell
? new CKEDITOR
.dom
.element( targetCell
) : table
.getPrevious();
413 function insertCell( selection
, insertBefore
)
415 var startElement
= selection
.getStartElement();
416 var cell
= startElement
.getAscendant( 'td', 1 ) || startElement
.getAscendant( 'th', 1 );
421 // Create the new cell element to be added.
422 var newCell
= cell
.clone();
423 if ( !CKEDITOR
.env
.ie
)
424 newCell
.appendBogus();
427 newCell
.insertBefore( cell
);
429 newCell
.insertAfter( cell
);
432 function deleteCells( selectionOrCell
)
434 if ( selectionOrCell
instanceof CKEDITOR
.dom
.selection
)
436 var cellsToDelete
= getSelectedCells( selectionOrCell
);
437 var table
= cellsToDelete
[ 0 ] && cellsToDelete
[ 0 ].getAscendant( 'table' );
438 var cellToFocus
= getFocusElementAfterDelCells( cellsToDelete
);
440 for ( var i
= cellsToDelete
.length
- 1 ; i
>= 0 ; i
-- )
441 deleteCells( cellsToDelete
[ i
] );
444 placeCursorInCell( cellToFocus
, true );
448 else if ( selectionOrCell
instanceof CKEDITOR
.dom
.element
)
450 var tr
= selectionOrCell
.getParent();
451 if ( tr
.getChildCount() == 1 )
454 selectionOrCell
.remove();
458 // Remove filler at end and empty spaces around the cell content.
459 function trimCell( cell
)
461 var bogus
= cell
.getBogus();
462 bogus
&& bogus
.remove();
466 function placeCursorInCell( cell
, placeAtEnd
)
468 var range
= new CKEDITOR
.dom
.range( cell
.getDocument() );
469 if ( !range
[ 'moveToElementEdit' + ( placeAtEnd
? 'End' : 'Start' ) ]( cell
) )
471 range
.selectNodeContents( cell
);
472 range
.collapse( placeAtEnd
? false : true );
474 range
.select( true );
477 function cellInRow( tableMap
, rowIndex
, cell
)
479 var oRow
= tableMap
[ rowIndex
];
480 if ( typeof cell
== 'undefined' )
483 for ( var c
= 0 ; oRow
&& c
< oRow
.length
; c
++ )
485 if ( cell
.is
&& oRow
[c
] == cell
.$ )
487 else if ( c
== cell
)
488 return new CKEDITOR
.dom
.element( oRow
[ c
] );
490 return cell
.is
? -1 : null;
493 function cellInCol( tableMap
, colIndex
, cell
)
496 for ( var r
= 0; r
< tableMap
.length
; r
++ )
498 var row
= tableMap
[ r
];
499 if ( typeof cell
== 'undefined' )
500 oCol
.push( row
[ colIndex
] );
501 else if ( cell
.is
&& row
[ colIndex
] == cell
.$ )
503 else if ( r
== cell
)
504 return new CKEDITOR
.dom
.element( row
[ colIndex
] );
507 return ( typeof cell
== 'undefined' )? oCol
: cell
.is
? -1 : null;
510 function mergeCells( selection
, mergeDirection
, isDetect
)
512 var cells
= getSelectedCells( selection
);
514 // Invalid merge request if:
515 // 1. In batch mode despite that less than two selected.
516 // 2. In solo mode while not exactly only one selected.
517 // 3. Cells distributed in different table groups (e.g. from both thead and tbody).
519 if ( ( mergeDirection
? cells
.length
!= 1 : cells
.length
< 2 )
520 || ( commonAncestor
= selection
.getCommonAncestor() )
521 && commonAncestor
.type
== CKEDITOR
.NODE_ELEMENT
522 && commonAncestor
.is( 'table' ) )
528 firstCell
= cells
[ 0 ],
529 table
= firstCell
.getAscendant( 'table' ),
530 map
= CKEDITOR
.tools
.buildTableMap( table
),
531 mapHeight
= map
.length
,
532 mapWidth
= map
[ 0 ].length
,
533 startRow
= firstCell
.getParent().$.rowIndex
,
534 startColumn
= cellInRow( map
, startRow
, firstCell
);
536 if ( mergeDirection
)
541 var rowspan
= parseInt( firstCell
.getAttribute( 'rowspan' ), 10 ) || 1;
542 var colspan
= parseInt( firstCell
.getAttribute( 'colspan' ), 10 ) || 1;
545 map
[ mergeDirection
== 'up' ?
546 ( startRow
- rowspan
):
547 mergeDirection
== 'down' ? ( startRow
+ rowspan
) : startRow
] [
548 mergeDirection
== 'left' ?
549 ( startColumn
- colspan
):
550 mergeDirection
== 'right' ? ( startColumn
+ colspan
) : startColumn
];
558 // 1. No cell could be merged.
559 // 2. Same cell actually.
560 if ( !targetCell
|| firstCell
.$ == targetCell
)
563 // Sort in map order regardless of the DOM sequence.
564 cells
[ ( mergeDirection
== 'up' || mergeDirection
== 'left' ) ?
565 'unshift' : 'push' ]( new CKEDITOR
.dom
.element( targetCell
) );
568 // Start from here are merging way ignorance (merge up/right, batch merge).
569 var doc
= firstCell
.getDocument(),
570 lastRowIndex
= startRow
,
573 // Use a documentFragment as buffer when appending cell contents.
574 frag
= !isDetect
&& new CKEDITOR
.dom
.documentFragment( doc
),
577 for ( var i
= 0; i
< cells
.length
; i
++ )
581 var tr
= cell
.getParent(),
582 cellFirstChild
= cell
.getFirst(),
583 colSpan
= cell
.$.colSpan
,
584 rowSpan
= cell
.$.rowSpan
,
585 rowIndex
= tr
.$.rowIndex
,
586 colIndex
= cellInRow( map
, rowIndex
, cell
);
588 // Accumulated the actual places taken by all selected cells.
589 dimension
+= colSpan
* rowSpan
;
590 // Accumulated the maximum virtual spans from column and row.
591 totalColSpan
= Math
.max( totalColSpan
, colIndex
- startColumn
+ colSpan
) ;
592 totalRowSpan
= Math
.max( totalRowSpan
, rowIndex
- startRow
+ rowSpan
);
596 // Trim all cell fillers and check to remove empty cells.
597 if ( trimCell( cell
), cell
.getChildren().count() )
599 // Merge vertically cells as two separated paragraphs.
600 if ( rowIndex
!= lastRowIndex
602 && !( cellFirstChild
.isBlockBoundary
603 && cellFirstChild
.isBlockBoundary( { br
: 1 } ) ) )
605 var last
= frag
.getLast( CKEDITOR
.dom
.walker
.whitespaces( true ) );
606 if ( last
&& !( last
.is
&& last
.is( 'br' ) ) )
610 cell
.moveChildren( frag
);
612 i
? cell
.remove() : cell
.setHtml( '' );
614 lastRowIndex
= rowIndex
;
619 frag
.moveChildren( firstCell
);
621 if ( !CKEDITOR
.env
.ie
)
622 firstCell
.appendBogus();
624 if ( totalColSpan
>= mapWidth
)
625 firstCell
.removeAttribute( 'rowSpan' );
627 firstCell
.$.rowSpan
= totalRowSpan
;
629 if ( totalRowSpan
>= mapHeight
)
630 firstCell
.removeAttribute( 'colSpan' );
632 firstCell
.$.colSpan
= totalColSpan
;
634 // Swip empty <tr> left at the end of table due to the merging.
635 var trs
= new CKEDITOR
.dom
.nodeList( table
.$.rows
),
638 for ( i
= count
- 1; i
>= 0; i
-- )
640 var tailTr
= trs
.getItem( i
);
641 if ( !tailTr
.$.cells
.length
)
651 // Be able to merge cells only if actual dimension of selected
652 // cells equals to the caculated rectangle.
654 return ( totalRowSpan
* totalColSpan
) == dimension
;
657 function verticalSplitCell ( selection
, isDetect
)
659 var cells
= getSelectedCells( selection
);
660 if ( cells
.length
> 1 )
665 var cell
= cells
[ 0 ],
666 tr
= cell
.getParent(),
667 table
= tr
.getAscendant( 'table' ),
668 map
= CKEDITOR
.tools
.buildTableMap( table
),
669 rowIndex
= tr
.$.rowIndex
,
670 colIndex
= cellInRow( map
, rowIndex
, cell
),
671 rowSpan
= cell
.$.rowSpan
,
679 newRowSpan
= Math
.ceil( rowSpan
/ 2 );
680 newCellRowSpan
= Math
.floor( rowSpan
/ 2 );
681 newRowIndex
= rowIndex
+ newRowSpan
;
682 var newCellTr
= new CKEDITOR
.dom
.element( table
.$.rows
[ newRowIndex
] ),
683 newCellRow
= cellInRow( map
, newRowIndex
),
686 newCell
= cell
.clone();
688 // Figure out where to insert the new cell by checking the vitual row.
689 for ( var c
= 0; c
< newCellRow
.length
; c
++ )
691 candidateCell
= newCellRow
[ c
];
692 // Catch first cell actually following the column.
693 if ( candidateCell
.parentNode
== newCellTr
.$
696 newCell
.insertBefore( new CKEDITOR
.dom
.element( candidateCell
) );
700 candidateCell
= null;
703 // The destination row is empty, append at will.
704 if ( !candidateCell
)
705 newCellTr
.append( newCell
, true );
709 newCellRowSpan
= newRowSpan
= 1;
711 newCellTr
= tr
.clone();
712 newCellTr
.insertAfter( tr
);
713 newCellTr
.append( newCell
= cell
.clone() );
715 var cellsInSameRow
= cellInRow( map
, rowIndex
);
716 for ( var i
= 0; i
< cellsInSameRow
.length
; i
++ )
717 cellsInSameRow
[ i
].rowSpan
++;
720 if ( !CKEDITOR
.env
.ie
)
721 newCell
.appendBogus();
723 cell
.$.rowSpan
= newRowSpan
;
724 newCell
.$.rowSpan
= newCellRowSpan
;
725 if ( newRowSpan
== 1 )
726 cell
.removeAttribute( 'rowSpan' );
727 if ( newCellRowSpan
== 1 )
728 newCell
.removeAttribute( 'rowSpan' );
733 function horizontalSplitCell( selection
, isDetect
)
735 var cells
= getSelectedCells( selection
);
736 if ( cells
.length
> 1 )
741 var cell
= cells
[ 0 ],
742 tr
= cell
.getParent(),
743 table
= tr
.getAscendant( 'table' ),
744 map
= CKEDITOR
.tools
.buildTableMap( table
),
745 rowIndex
= tr
.$.rowIndex
,
746 colIndex
= cellInRow( map
, rowIndex
, cell
),
747 colSpan
= cell
.$.colSpan
,
754 newColSpan
= Math
.ceil( colSpan
/ 2 );
755 newCellColSpan
= Math
.floor( colSpan
/ 2 );
759 newCellColSpan
= newColSpan
= 1;
760 var cellsInSameCol
= cellInCol( map
, colIndex
);
761 for ( var i
= 0; i
< cellsInSameCol
.length
; i
++ )
762 cellsInSameCol
[ i
].colSpan
++;
764 newCell
= cell
.clone();
765 newCell
.insertAfter( cell
);
766 if ( !CKEDITOR
.env
.ie
)
767 newCell
.appendBogus();
769 cell
.$.colSpan
= newColSpan
;
770 newCell
.$.colSpan
= newCellColSpan
;
771 if ( newColSpan
== 1 )
772 cell
.removeAttribute( 'colSpan' );
773 if ( newCellColSpan
== 1 )
774 newCell
.removeAttribute( 'colSpan' );
778 // Context menu on table caption incorrect (#3834)
779 var contextMenuTags
= { thead
: 1, tbody
: 1, tfoot
: 1, td
: 1, tr
: 1, th
: 1 };
781 CKEDITOR
.plugins
.tabletools
=
783 init : function( editor
)
785 var lang
= editor
.lang
.table
;
787 editor
.addCommand( 'cellProperties', new CKEDITOR
.dialogCommand( 'cellProperties' ) );
788 CKEDITOR
.dialog
.add( 'cellProperties', this.path
+ 'dialogs/tableCell.js' );
790 editor
.addCommand( 'tableDelete',
792 exec : function( editor
)
794 var selection
= editor
.getSelection(),
795 startElement
= selection
&& selection
.getStartElement(),
796 table
= startElement
&& startElement
.getAscendant( 'table', 1 );
801 // If the table's parent has only one child remove it as well (unless it's the body or a table cell) (#5416, #6289)
802 var parent
= table
.getParent();
803 if ( parent
.getChildCount() == 1 && !parent
.is( 'body', 'td', 'th' ) )
806 var range
= new CKEDITOR
.dom
.range( editor
.document
);
807 range
.moveToPosition( table
, CKEDITOR
.POSITION_BEFORE_START
);
813 editor
.addCommand( 'rowDelete',
815 exec : function( editor
)
817 var selection
= editor
.getSelection();
818 placeCursorInCell( deleteRows( selection
) );
822 editor
.addCommand( 'rowInsertBefore',
824 exec : function( editor
)
826 var selection
= editor
.getSelection();
827 insertRow( selection
, true );
831 editor
.addCommand( 'rowInsertAfter',
833 exec : function( editor
)
835 var selection
= editor
.getSelection();
836 insertRow( selection
);
840 editor
.addCommand( 'columnDelete',
842 exec : function( editor
)
844 var selection
= editor
.getSelection();
845 var element
= deleteColumns( selection
);
846 element
&& placeCursorInCell( element
, true );
850 editor
.addCommand( 'columnInsertBefore',
852 exec : function( editor
)
854 var selection
= editor
.getSelection();
855 insertColumn( selection
, true );
859 editor
.addCommand( 'columnInsertAfter',
861 exec : function( editor
)
863 var selection
= editor
.getSelection();
864 insertColumn( selection
);
868 editor
.addCommand( 'cellDelete',
870 exec : function( editor
)
872 var selection
= editor
.getSelection();
873 deleteCells( selection
);
877 editor
.addCommand( 'cellMerge',
879 exec : function( editor
)
881 placeCursorInCell( mergeCells( editor
.getSelection() ), true );
885 editor
.addCommand( 'cellMergeRight',
887 exec : function( editor
)
889 placeCursorInCell( mergeCells( editor
.getSelection(), 'right' ), true );
893 editor
.addCommand( 'cellMergeDown',
895 exec : function( editor
)
897 placeCursorInCell( mergeCells( editor
.getSelection(), 'down' ), true );
901 editor
.addCommand( 'cellVerticalSplit',
903 exec : function( editor
)
905 placeCursorInCell( verticalSplitCell( editor
.getSelection() ) );
909 editor
.addCommand( 'cellHorizontalSplit',
911 exec : function( editor
)
913 placeCursorInCell( horizontalSplitCell( editor
.getSelection() ) );
917 editor
.addCommand( 'cellInsertBefore',
919 exec : function( editor
)
921 var selection
= editor
.getSelection();
922 insertCell( selection
, true );
926 editor
.addCommand( 'cellInsertAfter',
928 exec : function( editor
)
930 var selection
= editor
.getSelection();
931 insertCell( selection
);
935 // If the "menu" plugin is loaded, register the menu items.
936 if ( editor
.addMenuItems
)
942 label
: lang
.cell
.menu
,
945 getItems : function()
947 var selection
= editor
.getSelection(),
948 cells
= getSelectedCells( selection
);
950 tablecell_insertBefore
: CKEDITOR
.TRISTATE_OFF
,
951 tablecell_insertAfter
: CKEDITOR
.TRISTATE_OFF
,
952 tablecell_delete
: CKEDITOR
.TRISTATE_OFF
,
953 tablecell_merge
: mergeCells( selection
, null, true ) ? CKEDITOR
.TRISTATE_OFF
: CKEDITOR
.TRISTATE_DISABLED
,
954 tablecell_merge_right
: mergeCells( selection
, 'right', true ) ? CKEDITOR
.TRISTATE_OFF
: CKEDITOR
.TRISTATE_DISABLED
,
955 tablecell_merge_down
: mergeCells( selection
, 'down', true ) ? CKEDITOR
.TRISTATE_OFF
: CKEDITOR
.TRISTATE_DISABLED
,
956 tablecell_split_vertical
: verticalSplitCell( selection
, true ) ? CKEDITOR
.TRISTATE_OFF
: CKEDITOR
.TRISTATE_DISABLED
,
957 tablecell_split_horizontal
: horizontalSplitCell( selection
, true ) ? CKEDITOR
.TRISTATE_OFF
: CKEDITOR
.TRISTATE_DISABLED
,
958 tablecell_properties
: cells
.length
> 0 ? CKEDITOR
.TRISTATE_OFF
: CKEDITOR
.TRISTATE_DISABLED
963 tablecell_insertBefore
:
965 label
: lang
.cell
.insertBefore
,
967 command
: 'cellInsertBefore',
971 tablecell_insertAfter
:
973 label
: lang
.cell
.insertAfter
,
975 command
: 'cellInsertAfter',
981 label
: lang
.cell
.deleteCell
,
983 command
: 'cellDelete',
989 label
: lang
.cell
.merge
,
991 command
: 'cellMerge',
995 tablecell_merge_right
:
997 label
: lang
.cell
.mergeRight
,
999 command
: 'cellMergeRight',
1003 tablecell_merge_down
:
1005 label
: lang
.cell
.mergeDown
,
1006 group
: 'tablecell',
1007 command
: 'cellMergeDown',
1011 tablecell_split_horizontal
:
1013 label
: lang
.cell
.splitHorizontal
,
1014 group
: 'tablecell',
1015 command
: 'cellHorizontalSplit',
1019 tablecell_split_vertical
:
1021 label
: lang
.cell
.splitVertical
,
1022 group
: 'tablecell',
1023 command
: 'cellVerticalSplit',
1027 tablecell_properties
:
1029 label
: lang
.cell
.title
,
1030 group
: 'tablecellproperties',
1031 command
: 'cellProperties',
1037 label
: lang
.row
.menu
,
1040 getItems : function()
1043 tablerow_insertBefore
: CKEDITOR
.TRISTATE_OFF
,
1044 tablerow_insertAfter
: CKEDITOR
.TRISTATE_OFF
,
1045 tablerow_delete
: CKEDITOR
.TRISTATE_OFF
1050 tablerow_insertBefore
:
1052 label
: lang
.row
.insertBefore
,
1054 command
: 'rowInsertBefore',
1058 tablerow_insertAfter
:
1060 label
: lang
.row
.insertAfter
,
1062 command
: 'rowInsertAfter',
1068 label
: lang
.row
.deleteRow
,
1070 command
: 'rowDelete',
1076 label
: lang
.column
.menu
,
1077 group
: 'tablecolumn',
1079 getItems : function()
1082 tablecolumn_insertBefore
: CKEDITOR
.TRISTATE_OFF
,
1083 tablecolumn_insertAfter
: CKEDITOR
.TRISTATE_OFF
,
1084 tablecolumn_delete
: CKEDITOR
.TRISTATE_OFF
1089 tablecolumn_insertBefore
:
1091 label
: lang
.column
.insertBefore
,
1092 group
: 'tablecolumn',
1093 command
: 'columnInsertBefore',
1097 tablecolumn_insertAfter
:
1099 label
: lang
.column
.insertAfter
,
1100 group
: 'tablecolumn',
1101 command
: 'columnInsertAfter',
1105 tablecolumn_delete
:
1107 label
: lang
.column
.deleteColumn
,
1108 group
: 'tablecolumn',
1109 command
: 'columnDelete',
1115 // If the "contextmenu" plugin is laoded, register the listeners.
1116 if ( editor
.contextMenu
)
1118 editor
.contextMenu
.addListener( function( element
, selection
)
1120 if ( !element
|| element
.isReadOnly() )
1125 if ( element
.getName() in contextMenuTags
)
1128 tablecell
: CKEDITOR
.TRISTATE_OFF
,
1129 tablerow
: CKEDITOR
.TRISTATE_OFF
,
1130 tablecolumn
: CKEDITOR
.TRISTATE_OFF
1133 element
= element
.getParent();
1141 getSelectedCells
: getSelectedCells
1144 CKEDITOR
.plugins
.add( 'tabletools', CKEDITOR
.plugins
.tabletools
);
1148 * Create a two-dimension array that reflects the actual layout of table cells,
1149 * with cell spans, with mappings to the original td elements.
1150 * @param table {CKEDITOR.dom.element}
1152 CKEDITOR
.tools
.buildTableMap = function ( table
)
1154 var aRows
= table
.$.rows
;
1156 // Row and Column counters.
1161 for ( var i
= 0 ; i
< aRows
.length
; i
++ )
1164 !aMap
[r
] && ( aMap
[r
] = [] );
1168 for ( var j
= 0 ; j
< aRows
[i
].cells
.length
; j
++ )
1170 var oCell
= aRows
[i
].cells
[j
] ;
1173 while ( aMap
[r
][c
] )
1176 var iColSpan
= isNaN( oCell
.colSpan
) ? 1 : oCell
.colSpan
;
1177 var iRowSpan
= isNaN( oCell
.rowSpan
) ? 1 : oCell
.rowSpan
;
1179 for ( var rs
= 0 ; rs
< iRowSpan
; rs
++ )
1181 if ( !aMap
[r
+ rs
] )
1184 for ( var cs
= 0 ; cs
< iColSpan
; cs
++ )
1186 aMap
[r
+ rs
][c
+ cs
] = aRows
[i
].cells
[j
] ;