JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
vanilla ckeditor-3.1
[ckeditor.git] / _source / plugins / tabletools / plugin.js
1 /*\r
2 Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.\r
3 For licensing, see LICENSE.html or http://ckeditor.com/license\r
4 */\r
5 \r
6 (function()\r
7 {\r
8         function removeRawAttribute( $node, attr )\r
9         {\r
10                 if ( CKEDITOR.env.ie )\r
11                         $node.removeAttribute( attr );\r
12                 else\r
13                         delete $node[ attr ];\r
14         }\r
15 \r
16         var cellNodeRegex = /^(?:td|th)$/;\r
17 \r
18         function getSelectedCells( selection )\r
19         {\r
20                 // Walker will try to split text nodes, which will make the current selection\r
21                 // invalid. So save bookmarks before doing anything.\r
22                 var bookmarks = selection.createBookmarks();\r
23 \r
24                 var ranges = selection.getRanges();\r
25                 var retval = [];\r
26                 var database = {};\r
27 \r
28                 function moveOutOfCellGuard( node )\r
29                 {\r
30                         // Apply to the first cell only.\r
31                         if ( retval.length > 0 )\r
32                                 return;\r
33 \r
34                         // If we are exiting from the first </td>, then the td should definitely be\r
35                         // included.\r
36                         if ( node.type == CKEDITOR.NODE_ELEMENT && cellNodeRegex.test( node.getName() )\r
37                                         && !node.getCustomData( 'selected_cell' ) )\r
38                         {\r
39                                 CKEDITOR.dom.element.setMarker( database, node, 'selected_cell', true );\r
40                                 retval.push( node );\r
41                         }\r
42                 }\r
43 \r
44                 for ( var i = 0 ; i < ranges.length ; i++ )\r
45                 {\r
46                         var range = ranges[ i ];\r
47 \r
48                         if ( range.collapsed )\r
49                         {\r
50                                 // Walker does not handle collapsed ranges yet - fall back to old API.\r
51                                 var startNode = range.getCommonAncestor();\r
52                                 var nearestCell = startNode.getAscendant( 'td', true ) || startNode.getAscendant( 'th', true );\r
53                                 if ( nearestCell )\r
54                                         retval.push( nearestCell );\r
55                         }\r
56                         else\r
57                         {\r
58                                 var walker = new CKEDITOR.dom.walker( range );\r
59                                 var node;\r
60                                 walker.guard = moveOutOfCellGuard;\r
61 \r
62                                 while ( ( node = walker.next() ) )\r
63                                 {\r
64                                         // If may be possible for us to have a range like this:\r
65                                         // <td>^1</td><td>^2</td>\r
66                                         // The 2nd td shouldn't be included.\r
67                                         //\r
68                                         // So we have to take care to include a td we've entered only when we've\r
69                                         // walked into its children.\r
70 \r
71                                         var parent = node.getParent();\r
72                                         if ( parent && cellNodeRegex.test( parent.getName() ) && !parent.getCustomData( 'selected_cell' ) )\r
73                                         {\r
74                                                 CKEDITOR.dom.element.setMarker( database, parent, 'selected_cell', true );\r
75                                                 retval.push( parent );\r
76                                         }\r
77                                 }\r
78                         }\r
79                 }\r
80 \r
81                 CKEDITOR.dom.element.clearAllMarkers( database );\r
82 \r
83                 // Restore selection position.\r
84                 selection.selectBookmarks( bookmarks );\r
85 \r
86                 return retval;\r
87         }\r
88 \r
89         function clearRow( $tr )\r
90         {\r
91                 // Get the array of row's cells.\r
92                 var $cells = $tr.cells;\r
93 \r
94                 // Empty all cells.\r
95                 for ( var i = 0 ; i < $cells.length ; i++ )\r
96                 {\r
97                         $cells[ i ].innerHTML = '';\r
98 \r
99                         if ( !CKEDITOR.env.ie )\r
100                                 ( new CKEDITOR.dom.element( $cells[ i ] ) ).appendBogus();\r
101                 }\r
102         }\r
103 \r
104         function insertRow( selection, insertBefore )\r
105         {\r
106                 // Get the row where the selection is placed in.\r
107                 var row = selection.getStartElement().getAscendant( 'tr' );\r
108                 if ( !row )\r
109                         return;\r
110 \r
111                 // Create a clone of the row.\r
112                 var newRow = row.clone( true );\r
113 \r
114                 // Insert the new row before of it.\r
115                 newRow.insertBefore( row );\r
116 \r
117                 // Clean one of the rows to produce the illusion of inserting an empty row\r
118                 // before or after.\r
119                 clearRow( insertBefore ? newRow.$ : row.$ );\r
120         }\r
121 \r
122         function deleteRows( selectionOrRow )\r
123         {\r
124                 if ( selectionOrRow instanceof CKEDITOR.dom.selection )\r
125                 {\r
126                         var cells = getSelectedCells( selectionOrRow );\r
127                         var rowsToDelete = [];\r
128 \r
129                         // Queue up the rows - it's possible and likely that we have duplicates.\r
130                         for ( var i = 0 ; i < cells.length ; i++ )\r
131                         {\r
132                                 var row = cells[ i ].getParent();\r
133                                 rowsToDelete[ row.$.rowIndex ] = row;\r
134                         }\r
135 \r
136                         for ( i = rowsToDelete.length ; i >= 0 ; i-- )\r
137                         {\r
138                                 if ( rowsToDelete[ i ] )\r
139                                         deleteRows( rowsToDelete[ i ] );\r
140                         }\r
141                 }\r
142                 else if ( selectionOrRow instanceof CKEDITOR.dom.element )\r
143                 {\r
144                         var table = selectionOrRow.getAscendant( 'table' );\r
145 \r
146                         if ( table.$.rows.length == 1 )\r
147                                 table.remove();\r
148                         else\r
149                                 selectionOrRow.remove();\r
150                 }\r
151         }\r
152 \r
153         function insertColumn( selection, insertBefore )\r
154         {\r
155                 // Get the cell where the selection is placed in.\r
156                 var startElement = selection.getStartElement();\r
157                 var cell = startElement.getAscendant( 'td', true ) || startElement.getAscendant( 'th', true );\r
158 \r
159                 if ( !cell )\r
160                         return;\r
161 \r
162                 // Get the cell's table.\r
163                 var table = cell.getAscendant( 'table' );\r
164                 var cellIndex = cell.$.cellIndex;\r
165 \r
166                 // Loop through all rows available in the table.\r
167                 for ( var i = 0 ; i < table.$.rows.length ; i++ )\r
168                 {\r
169                         var $row = table.$.rows[ i ];\r
170 \r
171                         // If the row doesn't have enough cells, ignore it.\r
172                         if ( $row.cells.length < ( cellIndex + 1 ) )\r
173                                 continue;\r
174 \r
175                         cell = new CKEDITOR.dom.element( $row.cells[ cellIndex ].cloneNode( false ) );\r
176 \r
177                         if ( !CKEDITOR.env.ie )\r
178                                 cell.appendBogus();\r
179 \r
180                         // Get back the currently selected cell.\r
181                         var baseCell = new CKEDITOR.dom.element( $row.cells[ cellIndex ] );\r
182                         if ( insertBefore )\r
183                                 cell.insertBefore( baseCell );\r
184                         else\r
185                                 cell.insertAfter( baseCell );\r
186                 }\r
187         }\r
188 \r
189         function deleteColumns( selectionOrCell )\r
190         {\r
191                 if ( selectionOrCell instanceof CKEDITOR.dom.selection )\r
192                 {\r
193                         var colsToDelete = getSelectedCells( selectionOrCell );\r
194                         for ( var i = colsToDelete.length ; i >= 0 ; i-- )\r
195                         {\r
196                                 if ( colsToDelete[ i ] )\r
197                                         deleteColumns( colsToDelete[ i ] );\r
198                         }\r
199                 }\r
200                 else if ( selectionOrCell instanceof CKEDITOR.dom.element )\r
201                 {\r
202                         // Get the cell's table.\r
203                         var table = selectionOrCell.getAscendant( 'table' );\r
204 \r
205                         // Get the cell index.\r
206                         var cellIndex = selectionOrCell.$.cellIndex;\r
207 \r
208                         /*\r
209                          * Loop through all rows from down to up, coz it's possible that some rows\r
210                          * will be deleted.\r
211                          */\r
212                         for ( i = table.$.rows.length - 1 ; i >= 0 ; i-- )\r
213                         {\r
214                                 // Get the row.\r
215                                 var row = new CKEDITOR.dom.element( table.$.rows[ i ] );\r
216 \r
217                                 // If the cell to be removed is the first one and the row has just one cell.\r
218                                 if ( !cellIndex && row.$.cells.length == 1 )\r
219                                 {\r
220                                         deleteRows( row );\r
221                                         continue;\r
222                                 }\r
223 \r
224                                 // Else, just delete the cell.\r
225                                 if ( row.$.cells[ cellIndex ] )\r
226                                         row.$.removeChild( row.$.cells[ cellIndex ] );\r
227                         }\r
228                 }\r
229         }\r
230 \r
231         function insertCell( selection, insertBefore )\r
232         {\r
233                 var startElement = selection.getStartElement();\r
234                 var cell = startElement.getAscendant( 'td', true ) || startElement.getAscendant( 'th', true );\r
235 \r
236                 if ( !cell )\r
237                         return;\r
238 \r
239                 // Create the new cell element to be added.\r
240                 var newCell = cell.clone();\r
241                 if ( !CKEDITOR.env.ie )\r
242                         newCell.appendBogus();\r
243 \r
244                 if ( insertBefore )\r
245                         newCell.insertBefore( cell );\r
246                 else\r
247                         newCell.insertAfter( cell );\r
248         }\r
249 \r
250         function deleteCells( selectionOrCell )\r
251         {\r
252                 if ( selectionOrCell instanceof CKEDITOR.dom.selection )\r
253                 {\r
254                         var cellsToDelete = getSelectedCells( selectionOrCell );\r
255                         for ( var i = cellsToDelete.length - 1 ; i >= 0 ; i-- )\r
256                                 deleteCells( cellsToDelete[ i ] );\r
257                 }\r
258                 else if ( selectionOrCell instanceof CKEDITOR.dom.element )\r
259                 {\r
260                         if ( selectionOrCell.getParent().getChildCount() == 1 )\r
261                                 selectionOrCell.getParent().remove();\r
262                         else\r
263                                 selectionOrCell.remove();\r
264                 }\r
265         }\r
266 \r
267         // Remove filler at end and empty spaces around the cell content.\r
268         function trimCell( cell )\r
269         {\r
270                 var bogus = cell.getBogus();\r
271                 bogus && bogus.remove();\r
272                 cell.trim();\r
273         }\r
274 \r
275         function placeCursorInCell( cell, placeAtEnd )\r
276         {\r
277                 var range = new CKEDITOR.dom.range( cell.getDocument() );\r
278                 if ( !range[ 'moveToElementEdit' + ( placeAtEnd ? 'End' : 'Start' ) ]( cell ) )\r
279                 {\r
280                         range.selectNodeContents( cell );\r
281                         range.collapse( placeAtEnd ? false : true );\r
282                 }\r
283                 range.select( true );\r
284         }\r
285 \r
286         function buildTableMap( table )\r
287         {\r
288 \r
289                 var aRows = table.$.rows ;\r
290 \r
291                 // Row and Column counters.\r
292                 var r = -1 ;\r
293 \r
294                 var aMap = [];\r
295 \r
296                 for ( var i = 0 ; i < aRows.length ; i++ )\r
297                 {\r
298                         r++ ;\r
299                         !aMap[r] && ( aMap[r] = [] );\r
300 \r
301                         var c = -1 ;\r
302 \r
303                         for ( var j = 0 ; j < aRows[i].cells.length ; j++ )\r
304                         {\r
305                                 var oCell = aRows[i].cells[j] ;\r
306 \r
307                                 c++ ;\r
308                                 while ( aMap[r][c] )\r
309                                         c++ ;\r
310 \r
311                                 var iColSpan = isNaN( oCell.colSpan ) ? 1 : oCell.colSpan ;\r
312                                 var iRowSpan = isNaN( oCell.rowSpan ) ? 1 : oCell.rowSpan ;\r
313 \r
314                                 for ( var rs = 0 ; rs < iRowSpan ; rs++ )\r
315                                 {\r
316                                         if ( !aMap[r + rs] )\r
317                                                 aMap[r + rs] = new Array() ;\r
318 \r
319                                         for ( var cs = 0 ; cs < iColSpan ; cs++ )\r
320                                         {\r
321                                                 aMap[r + rs][c + cs] = aRows[i].cells[j] ;\r
322                                         }\r
323                                 }\r
324 \r
325                                 c += iColSpan - 1 ;\r
326                         }\r
327                 }\r
328                 return aMap ;\r
329         }\r
330 \r
331         function cellInRow( tableMap, rowIndex, cell )\r
332         {\r
333                 var oRow = tableMap[ rowIndex ];\r
334                 if( typeof cell == 'undefined' )\r
335                         return oRow;\r
336 \r
337                 for ( var c = 0 ; oRow && c < oRow.length ; c++ )\r
338                 {\r
339                         if ( cell.is && oRow[c] == cell.$ )\r
340                                 return c;\r
341                         else if( c == cell )\r
342                                 return new CKEDITOR.dom.element( oRow[ c ] );\r
343                 }\r
344                 return cell.is ? -1 : null;\r
345         }\r
346 \r
347         function cellInCol( tableMap, colIndex, cell )\r
348         {\r
349                 var oCol = [];\r
350                 for ( var r = 0; r < tableMap.length; r++ )\r
351                 {\r
352                         var row = tableMap[ r ];\r
353                         if( typeof cell == 'undefined' )\r
354                                 oCol.push( row[ colIndex ] );\r
355                         else if( cell.is && row[ colIndex ] == cell.$ )\r
356                                 return r;\r
357                         else if( r == cell )\r
358                                 return new CKEDITOR.dom.element( row[ colIndex ] );\r
359                 }\r
360 \r
361                 return ( typeof cell == 'undefined' )? oCol : cell.is ? -1 :  null;\r
362         }\r
363 \r
364         function mergeCells( selection, mergeDirection, isDetect )\r
365         {\r
366                 var cells = getSelectedCells( selection );\r
367 \r
368                 // Invalid merge request if:\r
369                 // 1. In batch mode despite that less than two selected.\r
370                 // 2. In solo mode while not exactly only one selected.\r
371                 // 3. Cells distributed in different table groups (e.g. from both thead and tbody).\r
372                 var commonAncestor;\r
373                 if ( ( mergeDirection ? cells.length != 1 : cells.length < 2 )\r
374                                 || ( commonAncestor = selection.getCommonAncestor() )\r
375                                 && commonAncestor.type == CKEDITOR.NODE_ELEMENT\r
376                                 && commonAncestor.is( 'table' ) )\r
377                 {\r
378                         return false;\r
379                 }\r
380 \r
381                 var     cell,\r
382                         firstCell = cells[ 0 ],\r
383                         table = firstCell.getAscendant( 'table' ),\r
384                         map = buildTableMap( table ),\r
385                         mapHeight = map.length,\r
386                         mapWidth = map[ 0 ].length,\r
387                         startRow = firstCell.getParent().$.rowIndex,\r
388                         startColumn = cellInRow( map, startRow, firstCell );\r
389 \r
390                 if( mergeDirection )\r
391                 {\r
392                         var targetCell;\r
393                         try\r
394                         {\r
395                                 targetCell =\r
396                                         map[ mergeDirection == 'up' ?\r
397                                                         ( startRow - 1 ):\r
398                                                         mergeDirection == 'down' ? ( startRow + 1 ) : startRow  ] [\r
399                                                  mergeDirection == 'left' ?\r
400                                                         ( startColumn - 1 ):\r
401                                                  mergeDirection == 'right' ?  ( startColumn + 1 ) : startColumn ];\r
402 \r
403                         }\r
404                         catch( er )\r
405                         {\r
406                                 return false;\r
407                         }\r
408 \r
409                         // 1. No cell could be merged.\r
410                         // 2. Same cell actually.\r
411                         if( !targetCell || firstCell.$ == targetCell  )\r
412                                 return false;\r
413 \r
414                         // Sort in map order regardless of the DOM sequence.\r
415                         cells[ ( mergeDirection == 'up' || mergeDirection == 'left' ) ?\r
416                                  'unshift' : 'push' ]( new CKEDITOR.dom.element( targetCell ) );\r
417                 }\r
418 \r
419                 // Start from here are merging way ignorance (merge up/right, batch merge).\r
420                 var     doc = firstCell.getDocument(),\r
421                         lastRowIndex = startRow,\r
422                         totalRowSpan = 0,\r
423                         totalColSpan = 0,\r
424                         // Use a documentFragment as buffer when appending cell contents.\r
425                         frag = !isDetect && new CKEDITOR.dom.documentFragment( doc ),\r
426                         dimension = 0;\r
427 \r
428                 for ( var i = 0; i < cells.length; i++ )\r
429                 {\r
430                         cell = cells[ i ];\r
431 \r
432                         var tr = cell.getParent(),\r
433                                 cellFirstChild = cell.getFirst(),\r
434                                 colSpan = cell.$.colSpan,\r
435                                 rowSpan = cell.$.rowSpan,\r
436                                 rowIndex = tr.$.rowIndex,\r
437                                 colIndex = cellInRow( map, rowIndex, cell );\r
438 \r
439                         // Accumulated the actual places taken by all selected cells.\r
440                         dimension += colSpan * rowSpan;\r
441                         // Accumulated the maximum virtual spans from column and row.\r
442                         totalColSpan = Math.max( totalColSpan, colIndex - startColumn + colSpan ) ;\r
443                         totalRowSpan = Math.max( totalRowSpan, rowIndex - startRow + rowSpan );\r
444 \r
445                         if ( !isDetect )\r
446                         {\r
447                                 // Trim all cell fillers and check to remove empty cells.\r
448                                 if( trimCell( cell ), cell.getChildren().count() )\r
449                                 {\r
450                                         // Merge vertically cells as two separated paragraphs.\r
451                                         if( rowIndex != lastRowIndex\r
452                                                 && cellFirstChild\r
453                                                 && !( cellFirstChild.isBlockBoundary\r
454                                                           && cellFirstChild.isBlockBoundary( { br : 1 } ) ) )\r
455                                         {\r
456                                                 var last = frag.getLast( CKEDITOR.dom.walker.whitespaces( true ) );\r
457                                                 if( last && !( last.is && last.is( 'br' ) ) )\r
458                                                         frag.append( new CKEDITOR.dom.element( 'br' ) );\r
459                                         }\r
460 \r
461                                         cell.moveChildren( frag );\r
462                                 }\r
463                                 i ? cell.remove() : cell.setHtml( '' );\r
464                         }\r
465                         lastRowIndex = rowIndex;\r
466                 }\r
467 \r
468                 if ( !isDetect )\r
469                 {\r
470                         frag.moveChildren( firstCell );\r
471 \r
472                         if( !CKEDITOR.env.ie )\r
473                                 firstCell.appendBogus();\r
474 \r
475                         if( totalColSpan >= mapWidth )\r
476                                 firstCell.removeAttribute( 'rowSpan' );\r
477                         else\r
478                                 firstCell.$.rowSpan = totalRowSpan;\r
479 \r
480                         if( totalRowSpan >= mapHeight )\r
481                                 firstCell.removeAttribute( 'colSpan' );\r
482                         else\r
483                                 firstCell.$.colSpan = totalColSpan;\r
484 \r
485                         // Swip empty <tr> left at the end of table due to the merging.\r
486                         var trs = new CKEDITOR.dom.nodeList( table.$.rows ),\r
487                                 count = trs.count();\r
488 \r
489                         for ( i = count - 1; i >= 0; i-- )\r
490                         {\r
491                                 var tailTr = trs.getItem( i );\r
492                                 if( !tailTr.$.cells.length )\r
493                                 {\r
494                                         tailTr.remove();\r
495                                         count++;\r
496                                         continue;\r
497                                 }\r
498                         }\r
499 \r
500                         return firstCell;\r
501                 }\r
502                 // Be able to merge cells only if actual dimension of selected\r
503                 // cells equals to the caculated rectangle.\r
504                 else\r
505                         return ( totalRowSpan * totalColSpan ) == dimension;\r
506         }\r
507 \r
508         function verticalSplitCell ( selection, isDetect )\r
509         {\r
510                 var cells = getSelectedCells( selection );\r
511                 if( cells.length > 1 )\r
512                         return false;\r
513                 else if( isDetect )\r
514                         return true;\r
515 \r
516                 var cell = cells[ 0 ],\r
517                         tr = cell.getParent(),\r
518                         table = tr.getAscendant( 'table' ),\r
519                         map = buildTableMap( table ),\r
520                         rowIndex = tr.$.rowIndex,\r
521                         colIndex = cellInRow( map, rowIndex, cell ),\r
522                         rowSpan = cell.$.rowSpan,\r
523                         newCell,\r
524                         newRowSpan,\r
525                         newCellRowSpan,\r
526                         newRowIndex;\r
527 \r
528                 if( rowSpan > 1 )\r
529                 {\r
530                         newRowSpan = Math.ceil( rowSpan / 2 );\r
531                         newCellRowSpan = Math.floor( rowSpan / 2 );\r
532                         newRowIndex = rowIndex + newRowSpan;\r
533                         var newCellTr = new CKEDITOR.dom.element( table.$.rows[ newRowIndex ] ),\r
534                                 newCellRow = cellInRow( map, newRowIndex ),\r
535                                 candidateCell;\r
536 \r
537                         newCell = cell.clone();\r
538 \r
539                         // Figure out where to insert the new cell by checking the vitual row.\r
540                         for ( var c = 0; c < newCellRow.length; c++ )\r
541                         {\r
542                                 candidateCell = newCellRow[ c ];\r
543                                 // Catch first cell actually following the column.\r
544                                 if( candidateCell.parentNode == newCellTr.$\r
545                                         && c > colIndex )\r
546                                 {\r
547                                         newCell.insertBefore( new CKEDITOR.dom.element( candidateCell ) );\r
548                                         break;\r
549                                 }\r
550                                 else\r
551                                         candidateCell = null;\r
552                         }\r
553 \r
554                         // The destination row is empty, append at will.\r
555                         if( !candidateCell )\r
556                                 newCellTr.append( newCell, true );\r
557                 }\r
558                 else\r
559                 {\r
560                         newCellRowSpan = newRowSpan = 1;\r
561 \r
562                         newCellTr = tr.clone();\r
563                         newCellTr.insertAfter( tr );\r
564                         newCellTr.append( newCell = cell.clone() );\r
565 \r
566                         var cellsInSameRow = cellInRow( map, rowIndex );\r
567                         for ( var i = 0; i < cellsInSameRow.length; i++ )\r
568                                 cellsInSameRow[ i ].rowSpan++;\r
569                 }\r
570 \r
571                 if( !CKEDITOR.env.ie )\r
572                         newCell.appendBogus();\r
573 \r
574                 cell.$.rowSpan = newRowSpan;\r
575                 newCell.$.rowSpan = newCellRowSpan;\r
576                 if( newRowSpan == 1 )\r
577                         cell.removeAttribute( 'rowSpan' );\r
578                 if( newCellRowSpan == 1 )\r
579                         newCell.removeAttribute( 'rowSpan' );\r
580 \r
581                 return newCell;\r
582         }\r
583 \r
584         function horizontalSplitCell( selection, isDetect )\r
585         {\r
586                 var cells = getSelectedCells( selection );\r
587                 if( cells.length > 1 )\r
588                         return false;\r
589                 else if( isDetect )\r
590                         return true;\r
591 \r
592                 var cell = cells[ 0 ],\r
593                         tr = cell.getParent(),\r
594                         table = tr.getAscendant( 'table' ),\r
595                         map = buildTableMap( table ),\r
596                         rowIndex = tr.$.rowIndex,\r
597                         colIndex = cellInRow( map, rowIndex, cell ),\r
598                         colSpan = cell.$.colSpan,\r
599                         newCell,\r
600                         newColSpan,\r
601                         newCellColSpan;\r
602 \r
603                 if( colSpan > 1 )\r
604                 {\r
605                         newColSpan = Math.ceil( colSpan / 2 );\r
606                         newCellColSpan = Math.floor( colSpan / 2 );\r
607                 }\r
608                 else\r
609                 {\r
610                         newCellColSpan = newColSpan = 1;\r
611                         var cellsInSameCol = cellInCol( map, colIndex );\r
612                         for ( var i = 0; i < cellsInSameCol.length; i++ )\r
613                                 cellsInSameCol[ i ].colSpan++;\r
614                 }\r
615                 newCell = cell.clone();\r
616                 newCell.insertAfter( cell );\r
617                 if( !CKEDITOR.env.ie )\r
618                         newCell.appendBogus();\r
619 \r
620                 cell.$.colSpan = newColSpan;\r
621                 newCell.$.colSpan = newCellColSpan;\r
622                 if( newColSpan == 1 )\r
623                         cell.removeAttribute( 'colSpan' );\r
624                 if( newCellColSpan == 1 )\r
625                         newCell.removeAttribute( 'colSpan' );\r
626 \r
627                 return newCell;\r
628         }\r
629         // Context menu on table caption incorrect (#3834)\r
630         var contextMenuTags = { thead : 1, tbody : 1, tfoot : 1, td : 1, tr : 1, th : 1 };\r
631 \r
632         CKEDITOR.plugins.tabletools =\r
633         {\r
634                 init : function( editor )\r
635                 {\r
636                         var lang = editor.lang.table;\r
637 \r
638                         editor.addCommand( 'cellProperties', new CKEDITOR.dialogCommand( 'cellProperties' ) );\r
639                         CKEDITOR.dialog.add( 'cellProperties', this.path + 'dialogs/tableCell.js' );\r
640 \r
641                         editor.addCommand( 'tableDelete',\r
642                                 {\r
643                                         exec : function( editor )\r
644                                         {\r
645                                                 var selection = editor.getSelection();\r
646                                                 var startElement = selection && selection.getStartElement();\r
647                                                 var table = startElement && startElement.getAscendant( 'table', true );\r
648 \r
649                                                 if ( !table )\r
650                                                         return;\r
651 \r
652                                                 // Maintain the selection point at where the table was deleted.\r
653                                                 selection.selectElement( table );\r
654                                                 var range = selection.getRanges()[0];\r
655                                                 range.collapse();\r
656                                                 selection.selectRanges( [ range ] );\r
657 \r
658                                                 // If the table's parent has only one child, remove it as well.\r
659                                                 if ( table.getParent().getChildCount() == 1 )\r
660                                                         table.getParent().remove();\r
661                                                 else\r
662                                                         table.remove();\r
663                                         }\r
664                                 } );\r
665 \r
666                         editor.addCommand( 'rowDelete',\r
667                                 {\r
668                                         exec : function( editor )\r
669                                         {\r
670                                                 var selection = editor.getSelection();\r
671                                                 deleteRows( selection );\r
672                                         }\r
673                                 } );\r
674 \r
675                         editor.addCommand( 'rowInsertBefore',\r
676                                 {\r
677                                         exec : function( editor )\r
678                                         {\r
679                                                 var selection = editor.getSelection();\r
680                                                 insertRow( selection, true );\r
681                                         }\r
682                                 } );\r
683 \r
684                         editor.addCommand( 'rowInsertAfter',\r
685                                 {\r
686                                         exec : function( editor )\r
687                                         {\r
688                                                 var selection = editor.getSelection();\r
689                                                 insertRow( selection );\r
690                                         }\r
691                                 } );\r
692 \r
693                         editor.addCommand( 'columnDelete',\r
694                                 {\r
695                                         exec : function( editor )\r
696                                         {\r
697                                                 var selection = editor.getSelection();\r
698                                                 deleteColumns( selection );\r
699                                         }\r
700                                 } );\r
701 \r
702                         editor.addCommand( 'columnInsertBefore',\r
703                                 {\r
704                                         exec : function( editor )\r
705                                         {\r
706                                                 var selection = editor.getSelection();\r
707                                                 insertColumn( selection, true );\r
708                                         }\r
709                                 } );\r
710 \r
711                         editor.addCommand( 'columnInsertAfter',\r
712                                 {\r
713                                         exec : function( editor )\r
714                                         {\r
715                                                 var selection = editor.getSelection();\r
716                                                 insertColumn( selection );\r
717                                         }\r
718                                 } );\r
719 \r
720                         editor.addCommand( 'cellDelete',\r
721                                 {\r
722                                         exec : function( editor )\r
723                                         {\r
724                                                 var selection = editor.getSelection();\r
725                                                 deleteCells( selection );\r
726                                         }\r
727                                 } );\r
728 \r
729                         editor.addCommand( 'cellMerge',\r
730                                 {\r
731                                         exec : function( editor )\r
732                                         {\r
733                                                 placeCursorInCell( mergeCells( editor.getSelection() ), true );\r
734                                         }\r
735                                 } );\r
736 \r
737                         editor.addCommand( 'cellMergeRight',\r
738                                 {\r
739                                         exec : function( editor )\r
740                                         {\r
741                                                 placeCursorInCell( mergeCells( editor.getSelection(), 'right' ), true );\r
742                                         }\r
743                                 } );\r
744 \r
745                         editor.addCommand( 'cellMergeDown',\r
746                                 {\r
747                                         exec : function( editor )\r
748                                         {\r
749                                                 placeCursorInCell( mergeCells( editor.getSelection(), 'down' ), true );\r
750                                         }\r
751                                 } );\r
752 \r
753                         editor.addCommand( 'cellVerticalSplit',\r
754                                 {\r
755                                         exec : function( editor )\r
756                                         {\r
757                                                 placeCursorInCell( verticalSplitCell( editor.getSelection() ) );\r
758                                         }\r
759                                 } );\r
760 \r
761                         editor.addCommand( 'cellHorizontalSplit',\r
762                                 {\r
763                                         exec : function( editor )\r
764                                         {\r
765                                                 placeCursorInCell( horizontalSplitCell( editor.getSelection() ) );\r
766                                         }\r
767                                 } );\r
768 \r
769                         editor.addCommand( 'cellInsertBefore',\r
770                                 {\r
771                                         exec : function( editor )\r
772                                         {\r
773                                                 var selection = editor.getSelection();\r
774                                                 insertCell( selection, true );\r
775                                         }\r
776                                 } );\r
777 \r
778                         editor.addCommand( 'cellInsertAfter',\r
779                                 {\r
780                                         exec : function( editor )\r
781                                         {\r
782                                                 var selection = editor.getSelection();\r
783                                                 insertCell( selection );\r
784                                         }\r
785                                 } );\r
786 \r
787                         // If the "menu" plugin is loaded, register the menu items.\r
788                         if ( editor.addMenuItems )\r
789                         {\r
790                                 editor.addMenuItems(\r
791                                         {\r
792                                                 tablecell :\r
793                                                 {\r
794                                                         label : lang.cell.menu,\r
795                                                         group : 'tablecell',\r
796                                                         order : 1,\r
797                                                         getItems : function()\r
798                                                         {\r
799                                                                 var selection = editor.getSelection(),\r
800                                                                         cells = getSelectedCells( selection );\r
801                                                                 return {\r
802                                                                         tablecell_insertBefore : CKEDITOR.TRISTATE_OFF,\r
803                                                                         tablecell_insertAfter : CKEDITOR.TRISTATE_OFF,\r
804                                                                         tablecell_delete : CKEDITOR.TRISTATE_OFF,\r
805                                                                         tablecell_merge : mergeCells( selection, null, true ) ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED,\r
806                                                                         tablecell_merge_right : mergeCells( selection, 'right', true ) ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED,\r
807                                                                         tablecell_merge_down : mergeCells( selection, 'down', true ) ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED,\r
808                                                                         tablecell_split_vertical : verticalSplitCell( selection, true ) ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED,\r
809                                                                         tablecell_split_horizontal : horizontalSplitCell( selection, true ) ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED,\r
810                                                                         tablecell_properties : cells.length > 0 ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED\r
811                                                                 };\r
812                                                         }\r
813                                                 },\r
814 \r
815                                                 tablecell_insertBefore :\r
816                                                 {\r
817                                                         label : lang.cell.insertBefore,\r
818                                                         group : 'tablecell',\r
819                                                         command : 'cellInsertBefore',\r
820                                                         order : 5\r
821                                                 },\r
822 \r
823                                                 tablecell_insertAfter :\r
824                                                 {\r
825                                                         label : lang.cell.insertAfter,\r
826                                                         group : 'tablecell',\r
827                                                         command : 'cellInsertAfter',\r
828                                                         order : 10\r
829                                                 },\r
830 \r
831                                                 tablecell_delete :\r
832                                                 {\r
833                                                         label : lang.cell.deleteCell,\r
834                                                         group : 'tablecell',\r
835                                                         command : 'cellDelete',\r
836                                                         order : 15\r
837                                                 },\r
838 \r
839                                                 tablecell_merge :\r
840                                                 {\r
841                                                         label : lang.cell.merge,\r
842                                                         group : 'tablecell',\r
843                                                         command : 'cellMerge',\r
844                                                         order : 16\r
845                                                 },\r
846 \r
847                                                 tablecell_merge_right :\r
848                                                 {\r
849                                                         label : lang.cell.mergeRight,\r
850                                                         group : 'tablecell',\r
851                                                         command : 'cellMergeRight',\r
852                                                         order : 17\r
853                                                 },\r
854 \r
855                                                 tablecell_merge_down :\r
856                                                 {\r
857                                                         label : lang.cell.mergeDown,\r
858                                                         group : 'tablecell',\r
859                                                         command : 'cellMergeDown',\r
860                                                         order : 18\r
861                                                 },\r
862 \r
863                                                 tablecell_split_horizontal :\r
864                                                 {\r
865                                                         label : lang.cell.splitHorizontal,\r
866                                                         group : 'tablecell',\r
867                                                         command : 'cellHorizontalSplit',\r
868                                                         order : 19\r
869                                                 },\r
870 \r
871                                                 tablecell_split_vertical :\r
872                                                 {\r
873                                                         label : lang.cell.splitVertical,\r
874                                                         group : 'tablecell',\r
875                                                         command : 'cellVerticalSplit',\r
876                                                         order : 20\r
877                                                 },\r
878 \r
879                                                 tablecell_properties :\r
880                                                 {\r
881                                                         label : lang.cell.title,\r
882                                                         group : 'tablecellproperties',\r
883                                                         command : 'cellProperties',\r
884                                                         order : 21\r
885                                                 },\r
886 \r
887                                                 tablerow :\r
888                                                 {\r
889                                                         label : lang.row.menu,\r
890                                                         group : 'tablerow',\r
891                                                         order : 1,\r
892                                                         getItems : function()\r
893                                                         {\r
894                                                                 return {\r
895                                                                         tablerow_insertBefore : CKEDITOR.TRISTATE_OFF,\r
896                                                                         tablerow_insertAfter : CKEDITOR.TRISTATE_OFF,\r
897                                                                         tablerow_delete : CKEDITOR.TRISTATE_OFF\r
898                                                                 };\r
899                                                         }\r
900                                                 },\r
901 \r
902                                                 tablerow_insertBefore :\r
903                                                 {\r
904                                                         label : lang.row.insertBefore,\r
905                                                         group : 'tablerow',\r
906                                                         command : 'rowInsertBefore',\r
907                                                         order : 5\r
908                                                 },\r
909 \r
910                                                 tablerow_insertAfter :\r
911                                                 {\r
912                                                         label : lang.row.insertAfter,\r
913                                                         group : 'tablerow',\r
914                                                         command : 'rowInsertAfter',\r
915                                                         order : 10\r
916                                                 },\r
917 \r
918                                                 tablerow_delete :\r
919                                                 {\r
920                                                         label : lang.row.deleteRow,\r
921                                                         group : 'tablerow',\r
922                                                         command : 'rowDelete',\r
923                                                         order : 15\r
924                                                 },\r
925 \r
926                                                 tablecolumn :\r
927                                                 {\r
928                                                         label : lang.column.menu,\r
929                                                         group : 'tablecolumn',\r
930                                                         order : 1,\r
931                                                         getItems : function()\r
932                                                         {\r
933                                                                 return {\r
934                                                                         tablecolumn_insertBefore : CKEDITOR.TRISTATE_OFF,\r
935                                                                         tablecolumn_insertAfter : CKEDITOR.TRISTATE_OFF,\r
936                                                                         tablecolumn_delete : CKEDITOR.TRISTATE_OFF\r
937                                                                 };\r
938                                                         }\r
939                                                 },\r
940 \r
941                                                 tablecolumn_insertBefore :\r
942                                                 {\r
943                                                         label : lang.column.insertBefore,\r
944                                                         group : 'tablecolumn',\r
945                                                         command : 'columnInsertBefore',\r
946                                                         order : 5\r
947                                                 },\r
948 \r
949                                                 tablecolumn_insertAfter :\r
950                                                 {\r
951                                                         label : lang.column.insertAfter,\r
952                                                         group : 'tablecolumn',\r
953                                                         command : 'columnInsertAfter',\r
954                                                         order : 10\r
955                                                 },\r
956 \r
957                                                 tablecolumn_delete :\r
958                                                 {\r
959                                                         label : lang.column.deleteColumn,\r
960                                                         group : 'tablecolumn',\r
961                                                         command : 'columnDelete',\r
962                                                         order : 15\r
963                                                 }\r
964                                         });\r
965                         }\r
966 \r
967                         // If the "contextmenu" plugin is laoded, register the listeners.\r
968                         if ( editor.contextMenu )\r
969                         {\r
970                                 editor.contextMenu.addListener( function( element, selection )\r
971                                         {\r
972                                                 if ( !element )\r
973                                                         return null;\r
974 \r
975                                                 while ( element )\r
976                                                 {\r
977                                                         if ( element.getName() in contextMenuTags )\r
978                                                         {\r
979                                                                 return {\r
980                                                                         tablecell : CKEDITOR.TRISTATE_OFF,\r
981                                                                         tablerow : CKEDITOR.TRISTATE_OFF,\r
982                                                                         tablecolumn : CKEDITOR.TRISTATE_OFF\r
983                                                                 };\r
984                                                         }\r
985                                                         element = element.getParent();\r
986                                                 }\r
987 \r
988                                                 return null;\r
989                                         } );\r
990                         }\r
991                 },\r
992 \r
993                 getSelectedCells : getSelectedCells\r
994 \r
995         };\r
996         CKEDITOR.plugins.add( 'tabletools', CKEDITOR.plugins.tabletools );\r
997 })();\r