thomascube
2011-04-20 a9251be2f09fb5f18a85d201c67668c70980efe3
commit | author | age
a0109c 1 /**
69d05c 2  * editor_plugin_src.js
a0109c 3  *
69d05c 4  * Copyright 2009, Moxiecode Systems AB
A 5  * Released under LGPL License.
6  *
7  * License: http://tinymce.moxiecode.com/license
8  * Contributing: http://tinymce.moxiecode.com/contributing
a0109c 9  */
S 10
69d05c 11 (function(tinymce) {
d9344f 12     var each = tinymce.each;
58fb65 13
a9251b 14     // Checks if the selection/caret is at the start of the specified block element
T 15     function isAtStart(rng, par) {
16         var doc = par.ownerDocument, rng2 = doc.createRange(), elm;
17
18         rng2.setStartBefore(par);
19         rng2.setEnd(rng.endContainer, rng.endOffset);
20
21         elm = doc.createElement('body');
22         elm.appendChild(rng2.cloneContents());
23
24         // Check for text characters of other elements that should be treated as content
25         return elm.innerHTML.replace(/<(br|img|object|embed|input|textarea)[^>]*>/gi, '-').replace(/<[^>]+>/g, '').length == 0;
26     };
27
69d05c 28     /**
A 29      * Table Grid class.
30      */
31     function TableGrid(table, dom, selection) {
32         var grid, startPos, endPos, selectedCell;
58fb65 33
69d05c 34         buildGrid();
A 35         selectedCell = dom.getParent(selection.getStart(), 'th,td');
36         if (selectedCell) {
37             startPos = getPos(selectedCell);
38             endPos = findEndPos();
39             selectedCell = getCell(startPos.x, startPos.y);
40         }
58fb65 41
2011be 42         function cloneNode(node, children) {
A 43             node = node.cloneNode(children);
44             node.removeAttribute('id');
45
46             return node;
47         }
48
69d05c 49         function buildGrid() {
A 50             var startY = 0;
58fb65 51
69d05c 52             grid = [];
A 53
54             each(['thead', 'tbody', 'tfoot'], function(part) {
a9251b 55                 var rows = dom.select('> ' + part + ' tr', table);
69d05c 56
A 57                 each(rows, function(tr, y) {
58                     y += startY;
59
a9251b 60                     each(dom.select('> td, > th', tr), function(td, x) {
69d05c 61                         var x2, y2, rowspan, colspan;
A 62
63                         // Skip over existing cells produced by rowspan
64                         if (grid[y]) {
65                             while (grid[y][x])
66                                 x++;
67                         }
68
69                         // Get col/rowspan from cell
70                         rowspan = getSpanVal(td, 'rowspan');
71                         colspan = getSpanVal(td, 'colspan');
72
73                         // Fill out rowspan/colspan right and down
74                         for (y2 = y; y2 < y + rowspan; y2++) {
75                             if (!grid[y2])
76                                 grid[y2] = [];
77
78                             for (x2 = x; x2 < x + colspan; x2++) {
79                                 grid[y2][x2] = {
80                                     part : part,
81                                     real : y2 == y && x2 == x,
82                                     elm : td,
83                                     rowspan : rowspan,
84                                     colspan : colspan
85                                 };
86                             }
87                         }
88                     });
89                 });
90
91                 startY += rows.length;
92             });
93         };
94
95         function getCell(x, y) {
96             var row;
97
98             row = grid[y];
99             if (row)
100                 return row[x];
101         };
102
103         function getSpanVal(td, name) {
104             return parseInt(td.getAttribute(name) || 1);
105         };
106
a9251b 107         function setSpanVal(td, name, val) {
T 108             if (td) {
109                 val = parseInt(val);
110
111                 if (val === 1)
112                     td.removeAttribute(name, 1);
113                 else
114                     td.setAttribute(name, val, 1);
115             }
116         }
117
69d05c 118         function isCellSelected(cell) {
a9251b 119             return cell && (dom.hasClass(cell.elm, 'mceSelected') || cell == selectedCell);
69d05c 120         };
A 121
122         function getSelectedRows() {
123             var rows = [];
124
125             each(table.rows, function(row) {
126                 each(row.cells, function(cell) {
127                     if (dom.hasClass(cell, 'mceSelected') || cell == selectedCell.elm) {
128                         rows.push(row);
129                         return false;
130                     }
131                 });
132             });
133
134             return rows;
135         };
136
137         function deleteTable() {
138             var rng = dom.createRng();
139
140             rng.setStartAfter(table);
141             rng.setEndAfter(table);
142
143             selection.setRng(rng);
144
145             dom.remove(table);
146         };
147
148         function cloneCell(cell) {
149             var formatNode;
150
151             // Clone formats
152             tinymce.walk(cell, function(node) {
153                 var curNode;
154
155                 if (node.nodeType == 3) {
156                     each(dom.getParents(node.parentNode, null, cell).reverse(), function(node) {
2011be 157                         node = cloneNode(node, false);
69d05c 158
A 159                         if (!formatNode)
160                             formatNode = curNode = node;
161                         else if (curNode)
162                             curNode.appendChild(node);
163
164                         curNode = node;
165                     });
166
167                     // Add something to the inner node
168                     if (curNode)
a9251b 169                         curNode.innerHTML = tinymce.isIE ? '&nbsp;' : '<br data-mce-bogus="1" />';
69d05c 170
A 171                     return false;
172                 }
173             }, 'childNodes');
174
2011be 175             cell = cloneNode(cell, false);
a9251b 176             setSpanVal(cell, 'rowSpan', 1);
T 177             setSpanVal(cell, 'colSpan', 1);
69d05c 178
A 179             if (formatNode) {
180                 cell.appendChild(formatNode);
181             } else {
182                 if (!tinymce.isIE)
a9251b 183                     cell.innerHTML = '<br data-mce-bogus="1" />';
69d05c 184             }
A 185
186             return cell;
187         };
188
189         function cleanup() {
190             var rng = dom.createRng();
191
192             // Empty rows
193             each(dom.select('tr', table), function(tr) {
194                 if (tr.cells.length == 0)
195                     dom.remove(tr);
196             });
197
198             // Empty table
199             if (dom.select('tr', table).length == 0) {
200                 rng.setStartAfter(table);
201                 rng.setEndAfter(table);
202                 selection.setRng(rng);
203                 dom.remove(table);
204                 return;
205             }
206
207             // Empty header/body/footer
208             each(dom.select('thead,tbody,tfoot', table), function(part) {
209                 if (part.rows.length == 0)
210                     dom.remove(part);
211             });
212
213             // Restore selection to start position if it still exists
214             buildGrid();
215
216             // Restore the selection to the closest table position
217             row = grid[Math.min(grid.length - 1, startPos.y)];
218             if (row) {
219                 selection.select(row[Math.min(row.length - 1, startPos.x)].elm, true);
220                 selection.collapse(true);
221             }
222         };
223
224         function fillLeftDown(x, y, rows, cols) {
225             var tr, x2, r, c, cell;
226
227             tr = grid[y][x].elm.parentNode;
228             for (r = 1; r <= rows; r++) {
229                 tr = dom.getNext(tr, 'tr');
230
231                 if (tr) {
232                     // Loop left to find real cell
233                     for (x2 = x; x2 >= 0; x2--) {
234                         cell = grid[y + r][x2].elm;
235
236                         if (cell.parentNode == tr) {
237                             // Append clones after
238                             for (c = 1; c <= cols; c++)
239                                 dom.insertAfter(cloneCell(cell), cell);
240
241                             break;
242                         }
243                     }
244
245                     if (x2 == -1) {
246                         // Insert nodes before first cell
247                         for (c = 1; c <= cols; c++)
248                             tr.insertBefore(cloneCell(tr.cells[0]), tr.cells[0]);
249                     }
250                 }
251             }
252         };
253
254         function split() {
255             each(grid, function(row, y) {
256                 each(row, function(cell, x) {
257                     var colSpan, rowSpan, newCell, i;
258
259                     if (isCellSelected(cell)) {
260                         cell = cell.elm;
261                         colSpan = getSpanVal(cell, 'colspan');
262                         rowSpan = getSpanVal(cell, 'rowspan');
263
264                         if (colSpan > 1 || rowSpan > 1) {
a9251b 265                             setSpanVal(cell, 'rowSpan', 1);
T 266                             setSpanVal(cell, 'colSpan', 1);
69d05c 267
A 268                             // Insert cells right
269                             for (i = 0; i < colSpan - 1; i++)
270                                 dom.insertAfter(cloneCell(cell), cell);
271
272                             fillLeftDown(x, y, rowSpan - 1, colSpan);
273                         }
274                     }
275                 });
276             });
277         };
278
279         function merge(cell, cols, rows) {
a9251b 280             var startX, startY, endX, endY, x, y, startCell, endCell, cell, children, count;
69d05c 281
A 282             // Use specified cell and cols/rows
283             if (cell) {
284                 pos = getPos(cell);
285                 startX = pos.x;
286                 startY = pos.y;
287                 endX = startX + (cols - 1);
288                 endY = startY + (rows - 1);
289             } else {
290                 // Use selection
291                 startX = startPos.x;
292                 startY = startPos.y;
293                 endX = endPos.x;
294                 endY = endPos.y;
295             }
296
297             // Find start/end cells
298             startCell = getCell(startX, startY);
299             endCell = getCell(endX, endY);
300
301             // Check if the cells exists and if they are of the same part for example tbody = tbody
302             if (startCell && endCell && startCell.part == endCell.part) {
303                 // Split and rebuild grid
304                 split();
305                 buildGrid();
306
307                 // Set row/col span to start cell
308                 startCell = getCell(startX, startY).elm;
a9251b 309                 setSpanVal(startCell, 'colSpan', (endX - startX) + 1);
T 310                 setSpanVal(startCell, 'rowSpan', (endY - startY) + 1);
69d05c 311
A 312                 // Remove other cells and add it's contents to the start cell
313                 for (y = startY; y <= endY; y++) {
314                     for (x = startX; x <= endX; x++) {
a9251b 315                         if (!grid[y] || !grid[y][x])
T 316                             continue;
317
69d05c 318                         cell = grid[y][x].elm;
A 319
320                         if (cell != startCell) {
321                             // Move children to startCell
322                             children = tinymce.grep(cell.childNodes);
a9251b 323                             each(children, function(node) {
T 324                                 startCell.appendChild(node);
69d05c 325                             });
A 326
a9251b 327                             // Remove bogus nodes if there is children in the target cell
T 328                             if (children.length) {
329                                 children = tinymce.grep(startCell.childNodes);
330                                 count = 0;
331                                 each(children, function(node) {
332                                     if (node.nodeName == 'BR' && dom.getAttrib(node, 'data-mce-bogus') && count++ < children.length - 1)
333                                         startCell.removeChild(node);
334                                 });
335                             }
336                             
69d05c 337                             // Remove cell
A 338                             dom.remove(cell);
339                         }
340                     }
341                 }
342
343                 // Remove empty rows etc and restore caret location
344                 cleanup();
345             }
346         };
347
348         function insertRow(before) {
a9251b 349             var posY, cell, lastCell, x, rowElm, newRow, newCell, otherCell, rowSpan;
69d05c 350
A 351             // Find first/last row
352             each(grid, function(row, y) {
353                 each(row, function(cell, x) {
354                     if (isCellSelected(cell)) {
355                         cell = cell.elm;
356                         rowElm = cell.parentNode;
2011be 357                         newRow = cloneNode(rowElm, false);
69d05c 358                         posY = y;
A 359
360                         if (before)
361                             return false;
362                     }
363                 });
364
365                 if (before)
366                     return !posY;
367             });
368
369             for (x = 0; x < grid[0].length; x++) {
a9251b 370                 // Cell not found could be because of an invalid table structure
T 371                 if (!grid[posY][x])
372                     continue;
373
69d05c 374                 cell = grid[posY][x].elm;
A 375
376                 if (cell != lastCell) {
377                     if (!before) {
378                         rowSpan = getSpanVal(cell, 'rowspan');
379                         if (rowSpan > 1) {
a9251b 380                             setSpanVal(cell, 'rowSpan', rowSpan + 1);
69d05c 381                             continue;
A 382                         }
383                     } else {
384                         // Check if cell above can be expanded
385                         if (posY > 0 && grid[posY - 1][x]) {
386                             otherCell = grid[posY - 1][x].elm;
a9251b 387                             rowSpan = getSpanVal(otherCell, 'rowSpan');
69d05c 388                             if (rowSpan > 1) {
a9251b 389                                 setSpanVal(otherCell, 'rowSpan', rowSpan + 1);
69d05c 390                                 continue;
A 391                             }
392                         }
393                     }
394
395                     // Insert new cell into new row
a9251b 396                     newCell = cloneCell(cell);
T 397                     setSpanVal(newCell, 'colSpan', cell.colSpan);
398
69d05c 399                     newRow.appendChild(newCell);
A 400
401                     lastCell = cell;
402                 }
403             }
404
405             if (newRow.hasChildNodes()) {
406                 if (!before)
407                     dom.insertAfter(newRow, rowElm);
408                 else
409                     rowElm.parentNode.insertBefore(newRow, rowElm);
410             }
411         };
412
413         function insertCol(before) {
414             var posX, lastCell;
415
416             // Find first/last column
417             each(grid, function(row, y) {
418                 each(row, function(cell, x) {
419                     if (isCellSelected(cell)) {
420                         posX = x;
421
422                         if (before)
423                             return false;
424                     }
425                 });
426
427                 if (before)
428                     return !posX;
429             });
430
431             each(grid, function(row, y) {
a9251b 432                 var cell, rowSpan, colSpan;
69d05c 433
a9251b 434                 if (!row[posX])
T 435                     return;
436
437                 cell = row[posX].elm;
69d05c 438                 if (cell != lastCell) {
A 439                     colSpan = getSpanVal(cell, 'colspan');
440                     rowSpan = getSpanVal(cell, 'rowspan');
441
442                     if (colSpan == 1) {
443                         if (!before) {
444                             dom.insertAfter(cloneCell(cell), cell);
445                             fillLeftDown(posX, y, rowSpan - 1, colSpan);
446                         } else {
447                             cell.parentNode.insertBefore(cloneCell(cell), cell);
448                             fillLeftDown(posX, y, rowSpan - 1, colSpan);
449                         }
450                     } else
a9251b 451                         setSpanVal(cell, 'colSpan', cell.colSpan + 1);
69d05c 452
A 453                     lastCell = cell;
454                 }
455             });
456         };
457
458         function deleteCols() {
459             var cols = [];
460
461             // Get selected column indexes
462             each(grid, function(row, y) {
463                 each(row, function(cell, x) {
464                     if (isCellSelected(cell) && tinymce.inArray(cols, x) === -1) {
465                         each(grid, function(row) {
466                             var cell = row[x].elm, colSpan;
467
a9251b 468                             colSpan = getSpanVal(cell, 'colSpan');
69d05c 469
A 470                             if (colSpan > 1)
a9251b 471                                 setSpanVal(cell, 'colSpan', colSpan - 1);
69d05c 472                             else
A 473                                 dom.remove(cell);
474                         });
475
476                         cols.push(x);
477                     }
478                 });
479             });
480
481             cleanup();
482         };
483
484         function deleteRows() {
485             var rows;
486
487             function deleteRow(tr) {
488                 var nextTr, pos, lastCell;
489
490                 nextTr = dom.getNext(tr, 'tr');
491
492                 // Move down row spanned cells
493                 each(tr.cells, function(cell) {
a9251b 494                     var rowSpan = getSpanVal(cell, 'rowSpan');
69d05c 495
A 496                     if (rowSpan > 1) {
a9251b 497                         setSpanVal(cell, 'rowSpan', rowSpan - 1);
69d05c 498                         pos = getPos(cell);
A 499                         fillLeftDown(pos.x, pos.y, 1, 1);
500                     }
501                 });
502
503                 // Delete cells
504                 pos = getPos(tr.cells[0]);
505                 each(grid[pos.y], function(cell) {
506                     var rowSpan;
507
508                     cell = cell.elm;
509
510                     if (cell != lastCell) {
a9251b 511                         rowSpan = getSpanVal(cell, 'rowSpan');
69d05c 512
A 513                         if (rowSpan <= 1)
514                             dom.remove(cell);
515                         else
a9251b 516                             setSpanVal(cell, 'rowSpan', rowSpan - 1);
69d05c 517
A 518                         lastCell = cell;
519                     }
520                 });
521             };
522
523             // Get selected rows and move selection out of scope
524             rows = getSelectedRows();
525
526             // Delete all selected rows
527             each(rows.reverse(), function(tr) {
528                 deleteRow(tr);
529             });
530
531             cleanup();
532         };
533
534         function cutRows() {
535             var rows = getSelectedRows();
536
537             dom.remove(rows);
538             cleanup();
539
540             return rows;
541         };
542
543         function copyRows() {
544             var rows = getSelectedRows();
545
546             each(rows, function(row, i) {
2011be 547                 rows[i] = cloneNode(row, true);
69d05c 548             });
A 549
550             return rows;
551         };
552
553         function pasteRows(rows, before) {
554             var selectedRows = getSelectedRows(),
555                 targetRow = selectedRows[before ? 0 : selectedRows.length - 1],
556                 targetCellCount = targetRow.cells.length;
557
558             // Calc target cell count
559             each(grid, function(row) {
560                 var match;
561
562                 targetCellCount = 0;
563                 each(row, function(cell, x) {
564                     if (cell.real)
565                         targetCellCount += cell.colspan;
566
567                     if (cell.elm.parentNode == targetRow)
568                         match = 1;
569                 });
570
571                 if (match)
572                     return false;
573             });
574
575             if (!before)
576                 rows.reverse();
577
578             each(rows, function(row) {
579                 var cellCount = row.cells.length, cell;
580
581                 // Remove col/rowspans
582                 for (i = 0; i < cellCount; i++) {
583                     cell = row.cells[i];
a9251b 584                     setSpanVal(cell, 'colSpan', 1);
T 585                     setSpanVal(cell, 'rowSpan', 1);
69d05c 586                 }
A 587
588                 // Needs more cells
589                 for (i = cellCount; i < targetCellCount; i++)
590                     row.appendChild(cloneCell(row.cells[cellCount - 1]));
591
592                 // Needs less cells
593                 for (i = targetCellCount; i < cellCount; i++)
594                     dom.remove(row.cells[i]);
595
596                 // Add before/after
597                 if (before)
598                     targetRow.parentNode.insertBefore(row, targetRow);
599                 else
600                     dom.insertAfter(row, targetRow);
601             });
602         };
603
604         function getPos(target) {
605             var pos;
606
607             each(grid, function(row, y) {
608                 each(row, function(cell, x) {
609                     if (cell.elm == target) {
610                         pos = {x : x, y : y};
611                         return false;
612                     }
613                 });
614
615                 return !pos;
616             });
617
618             return pos;
619         };
620
621         function setStartCell(cell) {
622             startPos = getPos(cell);
623         };
624
625         function findEndPos() {
626             var pos, maxX, maxY;
627
628             maxX = maxY = 0;
629
630             each(grid, function(row, y) {
631                 each(row, function(cell, x) {
632                     var colSpan, rowSpan;
633
634                     if (isCellSelected(cell)) {
635                         cell = grid[y][x];
636
637                         if (x > maxX)
638                             maxX = x;
639
640                         if (y > maxY)
641                             maxY = y;
642
643                         if (cell.real) {
644                             colSpan = cell.colspan - 1;
645                             rowSpan = cell.rowspan - 1;
646
647                             if (colSpan) {
648                                 if (x + colSpan > maxX)
649                                     maxX = x + colSpan;
650                             }
651
652                             if (rowSpan) {
653                                 if (y + rowSpan > maxY)
654                                     maxY = y + rowSpan;
655                             }
656                         }
657                     }
658                 });
659             });
660
661             return {x : maxX, y : maxY};
662         };
663
664         function setEndCell(cell) {
665             var startX, startY, endX, endY, maxX, maxY, colSpan, rowSpan;
666
667             endPos = getPos(cell);
668
669             if (startPos && endPos) {
670                 // Get start/end positions
671                 startX = Math.min(startPos.x, endPos.x);
672                 startY = Math.min(startPos.y, endPos.y);
673                 endX = Math.max(startPos.x, endPos.x);
674                 endY = Math.max(startPos.y, endPos.y);
675
676                 // Expand end positon to include spans
677                 maxX = endX;
678                 maxY = endY;
679
680                 // Expand startX
681                 for (y = startY; y <= maxY; y++) {
682                     cell = grid[y][startX];
683
684                     if (!cell.real) {
685                         if (startX - (cell.colspan - 1) < startX)
686                             startX -= cell.colspan - 1;
687                     }
688                 }
689
690                 // Expand startY
691                 for (x = startX; x <= maxX; x++) {
692                     cell = grid[startY][x];
693
694                     if (!cell.real) {
695                         if (startY - (cell.rowspan - 1) < startY)
696                             startY -= cell.rowspan - 1;
697                     }
698                 }
699
700                 // Find max X, Y
701                 for (y = startY; y <= endY; y++) {
702                     for (x = startX; x <= endX; x++) {
703                         cell = grid[y][x];
704
705                         if (cell.real) {
706                             colSpan = cell.colspan - 1;
707                             rowSpan = cell.rowspan - 1;
708
709                             if (colSpan) {
710                                 if (x + colSpan > maxX)
711                                     maxX = x + colSpan;
712                             }
713
714                             if (rowSpan) {
715                                 if (y + rowSpan > maxY)
716                                     maxY = y + rowSpan;
717                             }
718                         }
719                     }
720                 }
721
722                 // Remove current selection
723                 dom.removeClass(dom.select('td.mceSelected,th.mceSelected'), 'mceSelected');
724
725                 // Add new selection
726                 for (y = startY; y <= maxY; y++) {
a9251b 727                     for (x = startX; x <= maxX; x++) {
T 728                         if (grid[y][x])
729                             dom.addClass(grid[y][x].elm, 'mceSelected');
730                     }
69d05c 731                 }
A 732             }
733         };
734
735         // Expose to public
736         tinymce.extend(this, {
737             deleteTable : deleteTable,
738             split : split,
739             merge : merge,
740             insertRow : insertRow,
741             insertCol : insertCol,
742             deleteCols : deleteCols,
743             deleteRows : deleteRows,
744             cutRows : cutRows,
745             copyRows : copyRows,
746             pasteRows : pasteRows,
747             getPos : getPos,
748             setStartCell : setStartCell,
749             setEndCell : setEndCell
750         });
58fb65 751     };
a0109c 752
d9344f 753     tinymce.create('tinymce.plugins.TablePlugin', {
S 754         init : function(ed, url) {
69d05c 755             var winMan, clipboardRows;
a0109c 756
69d05c 757             function createTableGrid(node) {
A 758                 var selection = ed.selection, tblElm = ed.dom.getParent(node || selection.getNode(), 'table');
759
760                 if (tblElm)
761                     return new TableGrid(tblElm, ed.dom, selection);
762             };
763
764             function cleanup() {
765                 // Restore selection possibilities
766                 ed.getBody().style.webkitUserSelect = '';
767                 ed.dom.removeClass(ed.dom.select('td.mceSelected,th.mceSelected'), 'mceSelected');
768             };
a0109c 769
d9344f 770             // Register buttons
S 771             each([
772                 ['table', 'table.desc', 'mceInsertTable', true],
773                 ['delete_table', 'table.del', 'mceTableDelete'],
774                 ['delete_col', 'table.delete_col_desc', 'mceTableDeleteCol'],
775                 ['delete_row', 'table.delete_row_desc', 'mceTableDeleteRow'],
776                 ['col_after', 'table.col_after_desc', 'mceTableInsertColAfter'],
777                 ['col_before', 'table.col_before_desc', 'mceTableInsertColBefore'],
778                 ['row_after', 'table.row_after_desc', 'mceTableInsertRowAfter'],
779                 ['row_before', 'table.row_before_desc', 'mceTableInsertRowBefore'],
780                 ['row_props', 'table.row_desc', 'mceTableRowProps', true],
781                 ['cell_props', 'table.cell_desc', 'mceTableCellProps', true],
782                 ['split_cells', 'table.split_cells_desc', 'mceTableSplitCells', true],
783                 ['merge_cells', 'table.merge_cells_desc', 'mceTableMergeCells', true]
784             ], function(c) {
785                 ed.addButton(c[0], {title : c[1], cmd : c[2], ui : c[3]});
786             });
29da64 787
69d05c 788             // Select whole table is a table border is clicked
A 789             if (!tinymce.isIE) {
790                 ed.onClick.add(function(ed, e) {
791                     e = e.target;
29da64 792
a9251b 793                     if (e.nodeName === 'TABLE') {
69d05c 794                         ed.selection.select(e);
a9251b 795                         ed.nodeChanged();
T 796                     }
29da64 797                 });
A 798             }
a9251b 799
T 800             ed.onPreProcess.add(function(ed, args) {
801                 var nodes, i, node, dom = ed.dom, value;
802
803                 nodes = dom.select('table', args.node);
804                 i = nodes.length;
805                 while (i--) {
806                     node = nodes[i];
807                     dom.setAttrib(node, 'data-mce-style', '');
808
809                     if ((value = dom.getAttrib(node, 'width'))) {
810                         dom.setStyle(node, 'width', value);
811                         dom.setAttrib(node, 'width', '');
812                     }
813
814                     if ((value = dom.getAttrib(node, 'height'))) {
815                         dom.setStyle(node, 'height', value);
816                         dom.setAttrib(node, 'height', '');
817                     }
818                 }
819             });
a0109c 820
69d05c 821             // Handle node change updates
A 822             ed.onNodeChange.add(function(ed, cm, n) {
823                 var p;
824
825                 n = ed.selection.getStart();
826                 p = ed.dom.getParent(n, 'td,th,caption');
827                 cm.setActive('table', n.nodeName === 'TABLE' || !!p);
828
829                 // Disable table tools if we are in caption
830                 if (p && p.nodeName === 'CAPTION')
831                     p = 0;
832
833                 cm.setDisabled('delete_table', !p);
834                 cm.setDisabled('delete_col', !p);
835                 cm.setDisabled('delete_table', !p);
836                 cm.setDisabled('delete_row', !p);
837                 cm.setDisabled('col_after', !p);
838                 cm.setDisabled('col_before', !p);
839                 cm.setDisabled('row_after', !p);
840                 cm.setDisabled('row_before', !p);
841                 cm.setDisabled('row_props', !p);
842                 cm.setDisabled('cell_props', !p);
843                 cm.setDisabled('split_cells', !p);
844                 cm.setDisabled('merge_cells', !p);
845             });
846
847             ed.onInit.add(function(ed) {
848                 var startTable, startCell, dom = ed.dom, tableGrid;
849
850                 winMan = ed.windowManager;
851
852                 // Add cell selection logic
853                 ed.onMouseDown.add(function(ed, e) {
854                     if (e.button != 2) {
855                         cleanup();
856
857                         startCell = dom.getParent(e.target, 'td,th');
858                         startTable = dom.getParent(startCell, 'table');
859                     }
860                 });
861
862                 dom.bind(ed.getDoc(), 'mouseover', function(e) {
863                     var sel, table, target = e.target;
864
865                     if (startCell && (tableGrid || target != startCell) && (target.nodeName == 'TD' || target.nodeName == 'TH')) {
866                         table = dom.getParent(target, 'table');
867                         if (table == startTable) {
868                             if (!tableGrid) {
869                                 tableGrid = createTableGrid(table);
870                                 tableGrid.setStartCell(startCell);
871
872                                 ed.getBody().style.webkitUserSelect = 'none';
873                             }
874
875                             tableGrid.setEndCell(target);
876                         }
877
878                         // Remove current selection
879                         sel = ed.selection.getSel();
880
a9251b 881                         try {
T 882                             if (sel.removeAllRanges)
883                                 sel.removeAllRanges();
884                             else
885                                 sel.empty();
886                         } catch (ex) {
887                             // IE9 might throw errors here
888                         }
69d05c 889
A 890                         e.preventDefault();
891                     }
892                 });
893
894                 ed.onMouseUp.add(function(ed, e) {
895                     var rng, sel = ed.selection, selectedCells, nativeSel = sel.getSel(), walker, node, lastNode, endNode;
896
897                     // Move selection to startCell
898                     if (startCell) {
899                         if (tableGrid)
900                             ed.getBody().style.webkitUserSelect = '';
901
902                         function setPoint(node, start) {
903                             var walker = new tinymce.dom.TreeWalker(node, node);
904
905                             do {
906                                 // Text node
907                                 if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length != 0) {
908                                     if (start)
909                                         rng.setStart(node, 0);
910                                     else
911                                         rng.setEnd(node, node.nodeValue.length);
912
913                                     return;
914                                 }
915
916                                 // BR element
917                                 if (node.nodeName == 'BR') {
918                                     if (start)
919                                         rng.setStartBefore(node);
920                                     else
921                                         rng.setEndBefore(node);
922
923                                     return;
924                                 }
925                             } while (node = (start ? walker.next() : walker.prev()));
926                         };
927
928                         // Try to expand text selection as much as we can only Gecko supports cell selection
929                         selectedCells = dom.select('td.mceSelected,th.mceSelected');
930                         if (selectedCells.length > 0) {
931                             rng = dom.createRng();
932                             node = selectedCells[0];
933                             endNode = selectedCells[selectedCells.length - 1];
934
935                             setPoint(node, 1);
936                             walker = new tinymce.dom.TreeWalker(node, dom.getParent(selectedCells[0], 'table'));
937
938                             do {
939                                 if (node.nodeName == 'TD' || node.nodeName == 'TH') {
940                                     if (!dom.hasClass(node, 'mceSelected'))
941                                         break;
942
943                                     lastNode = node;
944                                 }
945                             } while (node = walker.next());
946
947                             setPoint(lastNode);
948
949                             sel.setRng(rng);
950                         }
951
952                         ed.nodeChanged();
953                         startCell = tableGrid = startTable = null;
954                     }
955                 });
956
957                 ed.onKeyUp.add(function(ed, e) {
958                     cleanup();
959                 });
960
961                 // Add context menu
962                 if (ed && ed.plugins.contextmenu) {
963                     ed.plugins.contextmenu.onContextMenu.add(function(th, m, e) {
964                         var sm, se = ed.selection, el = se.getNode() || ed.getBody();
965
2011be 966                         if (ed.dom.getParent(e, 'td') || ed.dom.getParent(e, 'th') || ed.dom.select('td.mceSelected,th.mceSelected').length) {
69d05c 967                             m.removeAll();
A 968
969                             if (el.nodeName == 'A' && !ed.dom.getAttrib(el, 'name')) {
970                                 m.add({title : 'advanced.link_desc', icon : 'link', cmd : ed.plugins.advlink ? 'mceAdvLink' : 'mceLink', ui : true});
971                                 m.add({title : 'advanced.unlink_desc', icon : 'unlink', cmd : 'UnLink'});
972                                 m.addSeparator();
973                             }
974
975                             if (el.nodeName == 'IMG' && el.className.indexOf('mceItem') == -1) {
976                                 m.add({title : 'advanced.image_desc', icon : 'image', cmd : ed.plugins.advimage ? 'mceAdvImage' : 'mceImage', ui : true});
977                                 m.addSeparator();
978                             }
979
980                             m.add({title : 'table.desc', icon : 'table', cmd : 'mceInsertTable', value : {action : 'insert'}});
981                             m.add({title : 'table.props_desc', icon : 'table_props', cmd : 'mceInsertTable'});
982                             m.add({title : 'table.del', icon : 'delete_table', cmd : 'mceTableDelete'});
983                             m.addSeparator();
984
985                             // Cell menu
986                             sm = m.addMenu({title : 'table.cell'});
987                             sm.add({title : 'table.cell_desc', icon : 'cell_props', cmd : 'mceTableCellProps'});
988                             sm.add({title : 'table.split_cells_desc', icon : 'split_cells', cmd : 'mceTableSplitCells'});
989                             sm.add({title : 'table.merge_cells_desc', icon : 'merge_cells', cmd : 'mceTableMergeCells'});
990
991                             // Row menu
992                             sm = m.addMenu({title : 'table.row'});
993                             sm.add({title : 'table.row_desc', icon : 'row_props', cmd : 'mceTableRowProps'});
994                             sm.add({title : 'table.row_before_desc', icon : 'row_before', cmd : 'mceTableInsertRowBefore'});
995                             sm.add({title : 'table.row_after_desc', icon : 'row_after', cmd : 'mceTableInsertRowAfter'});
996                             sm.add({title : 'table.delete_row_desc', icon : 'delete_row', cmd : 'mceTableDeleteRow'});
997                             sm.addSeparator();
998                             sm.add({title : 'table.cut_row_desc', icon : 'cut', cmd : 'mceTableCutRow'});
999                             sm.add({title : 'table.copy_row_desc', icon : 'copy', cmd : 'mceTableCopyRow'});
1000                             sm.add({title : 'table.paste_row_before_desc', icon : 'paste', cmd : 'mceTablePasteRowBefore'}).setDisabled(!clipboardRows);
1001                             sm.add({title : 'table.paste_row_after_desc', icon : 'paste', cmd : 'mceTablePasteRowAfter'}).setDisabled(!clipboardRows);
1002
1003                             // Column menu
1004                             sm = m.addMenu({title : 'table.col'});
1005                             sm.add({title : 'table.col_before_desc', icon : 'col_before', cmd : 'mceTableInsertColBefore'});
1006                             sm.add({title : 'table.col_after_desc', icon : 'col_after', cmd : 'mceTableInsertColAfter'});
1007                             sm.add({title : 'table.delete_col_desc', icon : 'delete_col', cmd : 'mceTableDeleteCol'});
1008                         } else
1009                             m.add({title : 'table.desc', icon : 'table', cmd : 'mceInsertTable'});
1010                     });
1011                 }
1012
58fb65 1013                 // Fixes an issue on Gecko where it's impossible to place the caret behind a table
A 1014                 // This fix will force a paragraph element after the table but only when the forced_root_block setting is enabled
69d05c 1015                 if (!tinymce.isIE) {
58fb65 1016                     function fixTableCaretPos() {
69d05c 1017                         var last;
A 1018
1019                         // Skip empty text nodes form the end
1020                         for (last = ed.getBody().lastChild; last && last.nodeType == 3 && !last.nodeValue.length; last = last.previousSibling) ;
58fb65 1021
A 1022                         if (last && last.nodeName == 'TABLE')
1023                             ed.dom.add(ed.getBody(), 'p', null, '<br mce_bogus="1" />');
1024                     };
1025
1026                     // Fixes an bug where it's impossible to place the caret before a table in Gecko
1027                     // this fix solves it by detecting when the caret is at the beginning of such a table
1028                     // and then manually moves the caret infront of the table
1029                     if (tinymce.isGecko) {
1030                         ed.onKeyDown.add(function(ed, e) {
1031                             var rng, table, dom = ed.dom;
1032
1033                             // On gecko it's not possible to place the caret before a table
1034                             if (e.keyCode == 37 || e.keyCode == 38) {
1035                                 rng = ed.selection.getRng();
1036                                 table = dom.getParent(rng.startContainer, 'table');
1037
1038                                 if (table && ed.getBody().firstChild == table) {
1039                                     if (isAtStart(rng, table)) {
1040                                         rng = dom.createRng();
1041
1042                                         rng.setStartBefore(table);
1043                                         rng.setEndBefore(table);
1044
1045                                         ed.selection.setRng(rng);
1046
1047                                         e.preventDefault();
1048                                     }
1049                                 }
1050                             }
1051                         });
1052                     }
1053
1054                     ed.onKeyUp.add(fixTableCaretPos);
1055                     ed.onSetContent.add(fixTableCaretPos);
1056                     ed.onVisualAid.add(fixTableCaretPos);
1057
1058                     ed.onPreProcess.add(function(ed, o) {
1059                         var last = o.node.lastChild;
1060
1061                         if (last && last.childNodes.length == 1 && last.firstChild.nodeName == 'BR')
1062                             ed.dom.remove(last);
1063                     });
1064
1065                     fixTableCaretPos();
1066                 }
d9344f 1067             });
a0109c 1068
69d05c 1069             // Register action commands
A 1070             each({
1071                 mceTableSplitCells : function(grid) {
1072                     grid.split();
1073                 },
1074
1075                 mceTableMergeCells : function(grid) {
1076                     var rowSpan, colSpan, cell;
1077
1078                     cell = ed.dom.getParent(ed.selection.getNode(), 'th,td');
1079                     if (cell) {
1080                         rowSpan = cell.rowSpan;
1081                         colSpan = cell.colSpan;
18240a 1082                     }
A 1083
69d05c 1084                     if (!ed.dom.select('td.mceSelected,th.mceSelected').length) {
A 1085                         winMan.open({
1086                             url : url + '/merge_cells.htm',
1087                             width : 240 + parseInt(ed.getLang('table.merge_cells_delta_width', 0)),
1088                             height : 110 + parseInt(ed.getLang('table.merge_cells_delta_height', 0)),
1089                             inline : 1
1090                         }, {
1091                             rows : rowSpan,
1092                             cols : colSpan,
1093                             onaction : function(data) {
1094                                 grid.merge(cell, data.cols, data.rows);
1095                             },
1096                             plugin_url : url
1097                         });
1098                     } else
1099                         grid.merge();
1100                 },
1101
1102                 mceTableInsertRowBefore : function(grid) {
1103                     grid.insertRow(true);
1104                 },
1105
1106                 mceTableInsertRowAfter : function(grid) {
1107                     grid.insertRow();
1108                 },
1109
1110                 mceTableInsertColBefore : function(grid) {
1111                     grid.insertCol(true);
1112                 },
1113
1114                 mceTableInsertColAfter : function(grid) {
1115                     grid.insertCol();
1116                 },
1117
1118                 mceTableDeleteCol : function(grid) {
1119                     grid.deleteCols();
1120                 },
1121
1122                 mceTableDeleteRow : function(grid) {
1123                     grid.deleteRows();
1124                 },
1125
1126                 mceTableCutRow : function(grid) {
1127                     clipboardRows = grid.cutRows();
1128                 },
1129
1130                 mceTableCopyRow : function(grid) {
1131                     clipboardRows = grid.copyRows();
1132                 },
1133
1134                 mceTablePasteRowBefore : function(grid) {
1135                     grid.pasteRows(clipboardRows, true);
1136                 },
1137
1138                 mceTablePasteRowAfter : function(grid) {
1139                     grid.pasteRows(clipboardRows);
1140                 },
1141
1142                 mceTableDelete : function(grid) {
1143                     grid.deleteTable();
18240a 1144                 }
69d05c 1145             }, function(func, name) {
A 1146                 ed.addCommand(name, function() {
1147                     var grid = createTableGrid();
18240a 1148
69d05c 1149                     if (grid) {
A 1150                         func(grid);
1151                         ed.execCommand('mceRepaint');
1152                         cleanup();
1153                     }
d9344f 1154                 });
69d05c 1155             });
a0109c 1156
69d05c 1157             // Register dialog commands
A 1158             each({
1159                 mceInsertTable : function(val) {
1160                     winMan.open({
1161                         url : url + '/table.htm',
1162                         width : 400 + parseInt(ed.getLang('table.table_delta_width', 0)),
1163                         height : 320 + parseInt(ed.getLang('table.table_delta_height', 0)),
1164                         inline : 1
1165                     }, {
1166                         plugin_url : url,
1167                         action : val ? val.action : 0
1168                     });
1169                 },
a0109c 1170
69d05c 1171                 mceTableRowProps : function() {
A 1172                     winMan.open({
1173                         url : url + '/row.htm',
1174                         width : 400 + parseInt(ed.getLang('table.rowprops_delta_width', 0)),
1175                         height : 295 + parseInt(ed.getLang('table.rowprops_delta_height', 0)),
1176                         inline : 1
1177                     }, {
1178                         plugin_url : url
1179                     });
1180                 },
a0109c 1181
69d05c 1182                 mceTableCellProps : function() {
A 1183                     winMan.open({
1184                         url : url + '/cell.htm',
1185                         width : 400 + parseInt(ed.getLang('table.cellprops_delta_width', 0)),
1186                         height : 295 + parseInt(ed.getLang('table.cellprops_delta_height', 0)),
1187                         inline : 1
1188                     }, {
1189                         plugin_url : url
1190                     });
a0109c 1191                 }
69d05c 1192             }, function(func, name) {
A 1193                 ed.addCommand(name, function(ui, val) {
1194                     func(val);
1195                 });
1196             });
a0109c 1197         }
d9344f 1198     });
a0109c 1199
d9344f 1200     // Register plugin
S 1201     tinymce.PluginManager.add('table', tinymce.plugins.TablePlugin);
69d05c 1202 })(tinymce);