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