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