Upgrade TinyMCE to v3.4.5
[citadel.git] / webcit / tiny_mce / plugins / table / editor_plugin_src.js
1 /**
2  * editor_plugin_src.js
3  *
4  * Copyright 2009, Moxiecode Systems AB
5  * Released under LGPL License.
6  *
7  * License: http://tinymce.moxiecode.com/license
8  * Contributing: http://tinymce.moxiecode.com/contributing
9  */
10
11 (function(tinymce) {
12         var each = tinymce.each;
13
14         // Checks if the selection/caret is at the start of the specified block element
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
28         function getSpanVal(td, name) {
29                 return parseInt(td.getAttribute(name) || 1);
30         }
31
32         /**
33          * Table Grid class.
34          */
35         function TableGrid(table, dom, selection) {
36                 var grid, startPos, endPos, selectedCell;
37
38                 buildGrid();
39                 selectedCell = dom.getParent(selection.getStart(), 'th,td');
40                 if (selectedCell) {
41                         startPos = getPos(selectedCell);
42                         endPos = findEndPos();
43                         selectedCell = getCell(startPos.x, startPos.y);
44                 }
45
46                 function cloneNode(node, children) {
47                         node = node.cloneNode(children);
48                         node.removeAttribute('id');
49
50                         return node;
51                 }
52
53                 function buildGrid() {
54                         var startY = 0;
55
56                         grid = [];
57
58                         each(['thead', 'tbody', 'tfoot'], function(part) {
59                                 var rows = dom.select('> ' + part + ' tr', table);
60
61                                 each(rows, function(tr, y) {
62                                         y += startY;
63
64                                         each(dom.select('> td, > th', tr), function(td, x) {
65                                                 var x2, y2, rowspan, colspan;
66
67                                                 // Skip over existing cells produced by rowspan
68                                                 if (grid[y]) {
69                                                         while (grid[y][x])
70                                                                 x++;
71                                                 }
72
73                                                 // Get col/rowspan from cell
74                                                 rowspan = getSpanVal(td, 'rowspan');
75                                                 colspan = getSpanVal(td, 'colspan');
76
77                                                 // Fill out rowspan/colspan right and down
78                                                 for (y2 = y; y2 < y + rowspan; y2++) {
79                                                         if (!grid[y2])
80                                                                 grid[y2] = [];
81
82                                                         for (x2 = x; x2 < x + colspan; x2++) {
83                                                                 grid[y2][x2] = {
84                                                                         part : part,
85                                                                         real : y2 == y && x2 == x,
86                                                                         elm : td,
87                                                                         rowspan : rowspan,
88                                                                         colspan : colspan
89                                                                 };
90                                                         }
91                                                 }
92                                         });
93                                 });
94
95                                 startY += rows.length;
96                         });
97                 };
98
99                 function getCell(x, y) {
100                         var row;
101
102                         row = grid[y];
103                         if (row)
104                                 return row[x];
105                 };
106
107                 function setSpanVal(td, name, val) {
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
118                 function isCellSelected(cell) {
119                         return cell && (dom.hasClass(cell.elm, 'mceSelected') || cell == selectedCell);
120                 };
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) {
157                                                 node = cloneNode(node, false);
158
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)
169                                                 curNode.innerHTML = tinymce.isIE ? '&nbsp;' : '<br data-mce-bogus="1" />';
170
171                                         return false;
172                                 }
173                         }, 'childNodes');
174
175                         cell = cloneNode(cell, false);
176                         setSpanVal(cell, 'rowSpan', 1);
177                         setSpanVal(cell, 'colSpan', 1);
178
179                         if (formatNode) {
180                                 cell.appendChild(formatNode);
181                         } else {
182                                 if (!tinymce.isIE)
183                                         cell.innerHTML = '<br data-mce-bogus="1" />';
184                         }
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) {
265                                                         setSpanVal(cell, 'rowSpan', 1);
266                                                         setSpanVal(cell, 'colSpan', 1);
267
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) {
280                         var startX, startY, endX, endY, x, y, startCell, endCell, cell, children, count;
281
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;
309                                 setSpanVal(startCell, 'colSpan', (endX - startX) + 1);
310                                 setSpanVal(startCell, 'rowSpan', (endY - startY) + 1);
311
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++) {
315                                                 if (!grid[y] || !grid[y][x])
316                                                         continue;
317
318                                                 cell = grid[y][x].elm;
319
320                                                 if (cell != startCell) {
321                                                         // Move children to startCell
322                                                         children = tinymce.grep(cell.childNodes);
323                                                         each(children, function(node) {
324                                                                 startCell.appendChild(node);
325                                                         });
326
327                                                         // Remove bogus nodes if there is children in the target cell
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                                                         
337                                                         // Remove cell
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) {
349                         var posY, cell, lastCell, x, rowElm, newRow, newCell, otherCell, rowSpan;
350
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;
357                                                 newRow = cloneNode(rowElm, false);
358                                                 posY = y;
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++) {
370                                 // Cell not found could be because of an invalid table structure
371                                 if (!grid[posY][x])
372                                         continue;
373
374                                 cell = grid[posY][x].elm;
375
376                                 if (cell != lastCell) {
377                                         if (!before) {
378                                                 rowSpan = getSpanVal(cell, 'rowspan');
379                                                 if (rowSpan > 1) {
380                                                         setSpanVal(cell, 'rowSpan', rowSpan + 1);
381                                                         continue;
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;
387                                                         rowSpan = getSpanVal(otherCell, 'rowSpan');
388                                                         if (rowSpan > 1) {
389                                                                 setSpanVal(otherCell, 'rowSpan', rowSpan + 1);
390                                                                 continue;
391                                                         }
392                                                 }
393                                         }
394
395                                         // Insert new cell into new row
396                                         newCell = cloneCell(cell);
397                                         setSpanVal(newCell, 'colSpan', cell.colSpan);
398
399                                         newRow.appendChild(newCell);
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) {
432                                 var cell, rowSpan, colSpan;
433
434                                 if (!row[posX])
435                                         return;
436
437                                 cell = row[posX].elm;
438                                 if (cell != lastCell) {
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
451                                                 setSpanVal(cell, 'colSpan', cell.colSpan + 1);
452
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
468                                                         colSpan = getSpanVal(cell, 'colSpan');
469
470                                                         if (colSpan > 1)
471                                                                 setSpanVal(cell, 'colSpan', colSpan - 1);
472                                                         else
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) {
494                                         var rowSpan = getSpanVal(cell, 'rowSpan');
495
496                                         if (rowSpan > 1) {
497                                                 setSpanVal(cell, 'rowSpan', rowSpan - 1);
498                                                 pos = getPos(cell);
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) {
511                                                 rowSpan = getSpanVal(cell, 'rowSpan');
512
513                                                 if (rowSpan <= 1)
514                                                         dom.remove(cell);
515                                                 else
516                                                         setSpanVal(cell, 'rowSpan', rowSpan - 1);
517
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) {
547                                 rows[i] = cloneNode(row, true);
548                         });
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];
584                                         setSpanVal(cell, 'colSpan', 1);
585                                         setSpanVal(cell, 'rowSpan', 1);
586                                 }
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++) {
727                                         for (x = startX; x <= maxX; x++) {
728                                                 if (grid[y][x])
729                                                         dom.addClass(grid[y][x].elm, 'mceSelected');
730                                         }
731                                 }
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                 });
751         };
752
753         tinymce.create('tinymce.plugins.TablePlugin', {
754                 init : function(ed, url) {
755                         var winMan, clipboardRows, hasCellSelection = true; // Might be selected cells on reload
756
757                         function createTableGrid(node) {
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
768                                 if (hasCellSelection) {
769                                         ed.dom.removeClass(ed.dom.select('td.mceSelected,th.mceSelected'), 'mceSelected');
770                                         hasCellSelection = false;
771                                 }
772                         };
773
774                         // Register buttons
775                         each([
776                                 ['table', 'table.desc', 'mceInsertTable', true],
777                                 ['delete_table', 'table.del', 'mceTableDelete'],
778                                 ['delete_col', 'table.delete_col_desc', 'mceTableDeleteCol'],
779                                 ['delete_row', 'table.delete_row_desc', 'mceTableDeleteRow'],
780                                 ['col_after', 'table.col_after_desc', 'mceTableInsertColAfter'],
781                                 ['col_before', 'table.col_before_desc', 'mceTableInsertColBefore'],
782                                 ['row_after', 'table.row_after_desc', 'mceTableInsertRowAfter'],
783                                 ['row_before', 'table.row_before_desc', 'mceTableInsertRowBefore'],
784                                 ['row_props', 'table.row_desc', 'mceTableRowProps', true],
785                                 ['cell_props', 'table.cell_desc', 'mceTableCellProps', true],
786                                 ['split_cells', 'table.split_cells_desc', 'mceTableSplitCells', true],
787                                 ['merge_cells', 'table.merge_cells_desc', 'mceTableMergeCells', true]
788                         ], function(c) {
789                                 ed.addButton(c[0], {title : c[1], cmd : c[2], ui : c[3]});
790                         });
791
792                         // Select whole table is a table border is clicked
793                         if (!tinymce.isIE) {
794                                 ed.onClick.add(function(ed, e) {
795                                         e = e.target;
796
797                                         if (e.nodeName === 'TABLE') {
798                                                 ed.selection.select(e);
799                                                 ed.nodeChanged();
800                                         }
801                                 });
802                         }
803
804                         ed.onPreProcess.add(function(ed, args) {
805                                 var nodes, i, node, dom = ed.dom, value;
806
807                                 nodes = dom.select('table', args.node);
808                                 i = nodes.length;
809                                 while (i--) {
810                                         node = nodes[i];
811                                         dom.setAttrib(node, 'data-mce-style', '');
812
813                                         if ((value = dom.getAttrib(node, 'width'))) {
814                                                 dom.setStyle(node, 'width', value);
815                                                 dom.setAttrib(node, 'width', '');
816                                         }
817
818                                         if ((value = dom.getAttrib(node, 'height'))) {
819                                                 dom.setStyle(node, 'height', value);
820                                                 dom.setAttrib(node, 'height', '');
821                                         }
822                                 }
823                         });
824
825                         // Handle node change updates
826                         ed.onNodeChange.add(function(ed, cm, n) {
827                                 var p;
828
829                                 n = ed.selection.getStart();
830                                 p = ed.dom.getParent(n, 'td,th,caption');
831                                 cm.setActive('table', n.nodeName === 'TABLE' || !!p);
832
833                                 // Disable table tools if we are in caption
834                                 if (p && p.nodeName === 'CAPTION')
835                                         p = 0;
836
837                                 cm.setDisabled('delete_table', !p);
838                                 cm.setDisabled('delete_col', !p);
839                                 cm.setDisabled('delete_table', !p);
840                                 cm.setDisabled('delete_row', !p);
841                                 cm.setDisabled('col_after', !p);
842                                 cm.setDisabled('col_before', !p);
843                                 cm.setDisabled('row_after', !p);
844                                 cm.setDisabled('row_before', !p);
845                                 cm.setDisabled('row_props', !p);
846                                 cm.setDisabled('cell_props', !p);
847                                 cm.setDisabled('split_cells', !p);
848                                 cm.setDisabled('merge_cells', !p);
849                         });
850
851                         ed.onInit.add(function(ed) {
852                                 var startTable, startCell, dom = ed.dom, tableGrid;
853
854                                 winMan = ed.windowManager;
855
856                                 // Add cell selection logic
857                                 ed.onMouseDown.add(function(ed, e) {
858                                         if (e.button != 2) {
859                                                 cleanup();
860
861                                                 startCell = dom.getParent(e.target, 'td,th');
862                                                 startTable = dom.getParent(startCell, 'table');
863                                         }
864                                 });
865
866                                 dom.bind(ed.getDoc(), 'mouseover', function(e) {
867                                         var sel, table, target = e.target;
868
869                                         if (startCell && (tableGrid || target != startCell) && (target.nodeName == 'TD' || target.nodeName == 'TH')) {
870                                                 table = dom.getParent(target, 'table');
871                                                 if (table == startTable) {
872                                                         if (!tableGrid) {
873                                                                 tableGrid = createTableGrid(table);
874                                                                 tableGrid.setStartCell(startCell);
875
876                                                                 ed.getBody().style.webkitUserSelect = 'none';
877                                                         }
878
879                                                         tableGrid.setEndCell(target);
880                                                         hasCellSelection = true;
881                                                 }
882
883                                                 // Remove current selection
884                                                 sel = ed.selection.getSel();
885
886                                                 try {
887                                                         if (sel.removeAllRanges)
888                                                                 sel.removeAllRanges();
889                                                         else
890                                                                 sel.empty();
891                                                 } catch (ex) {
892                                                         // IE9 might throw errors here
893                                                 }
894
895                                                 e.preventDefault();
896                                         }
897                                 });
898
899                                 ed.onMouseUp.add(function(ed, e) {
900                                         var rng, sel = ed.selection, selectedCells, nativeSel = sel.getSel(), walker, node, lastNode, endNode;
901
902                                         // Move selection to startCell
903                                         if (startCell) {
904                                                 if (tableGrid)
905                                                         ed.getBody().style.webkitUserSelect = '';
906
907                                                 function setPoint(node, start) {
908                                                         var walker = new tinymce.dom.TreeWalker(node, node);
909
910                                                         do {
911                                                                 // Text node
912                                                                 if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length != 0) {
913                                                                         if (start)
914                                                                                 rng.setStart(node, 0);
915                                                                         else
916                                                                                 rng.setEnd(node, node.nodeValue.length);
917
918                                                                         return;
919                                                                 }
920
921                                                                 // BR element
922                                                                 if (node.nodeName == 'BR') {
923                                                                         if (start)
924                                                                                 rng.setStartBefore(node);
925                                                                         else
926                                                                                 rng.setEndBefore(node);
927
928                                                                         return;
929                                                                 }
930                                                         } while (node = (start ? walker.next() : walker.prev()));
931                                                 }
932
933                                                 // Try to expand text selection as much as we can only Gecko supports cell selection
934                                                 selectedCells = dom.select('td.mceSelected,th.mceSelected');
935                                                 if (selectedCells.length > 0) {
936                                                         rng = dom.createRng();
937                                                         node = selectedCells[0];
938                                                         endNode = selectedCells[selectedCells.length - 1];
939                                                         rng.setStartBefore(node);
940                                                         rng.setEndAfter(node);
941
942                                                         setPoint(node, 1);
943                                                         walker = new tinymce.dom.TreeWalker(node, dom.getParent(selectedCells[0], 'table'));
944
945                                                         do {
946                                                                 if (node.nodeName == 'TD' || node.nodeName == 'TH') {
947                                                                         if (!dom.hasClass(node, 'mceSelected'))
948                                                                                 break;
949
950                                                                         lastNode = node;
951                                                                 }
952                                                         } while (node = walker.next());
953
954                                                         setPoint(lastNode);
955
956                                                         sel.setRng(rng);
957                                                 }
958
959                                                 ed.nodeChanged();
960                                                 startCell = tableGrid = startTable = null;
961                                         }
962                                 });
963
964                                 ed.onKeyUp.add(function(ed, e) {
965                                         cleanup();
966                                 });
967
968                                 ed.onKeyDown.add(function (ed, e) {
969                                         fixTableCellSelection(ed);
970                                 });
971
972                                 ed.onMouseDown.add(function (ed, e) {
973                                         if (e.button != 2) {
974                                                 fixTableCellSelection(ed);
975                                         }
976                                 });
977                                 function tableCellSelected(ed, rng, n, currentCell) {
978                                         // The decision of when a table cell is selected is somewhat involved.  The fact that this code is
979                                         // required is actually a pointer to the root cause of this bug. A cell is selected when the start 
980                                         // and end offsets are 0, the start container is a text, and the selection node is either a TR (most cases)
981                                         // or the parent of the table (in the case of the selection containing the last cell of a table).
982                                         var TEXT_NODE = 3, table = ed.dom.getParent(rng.startContainer, 'TABLE'), 
983                                         tableParent, allOfCellSelected, tableCellSelection;
984                                         if (table) 
985                                         tableParent = table.parentNode;
986                                         allOfCellSelected =rng.startContainer.nodeType == TEXT_NODE && 
987                                                 rng.startOffset == 0 && 
988                                                 rng.endOffset == 0 && 
989                                                 currentCell && 
990                                                 (n.nodeName=="TR" || n==tableParent);
991                                         tableCellSelection = (n.nodeName=="TD"||n.nodeName=="TH")&& !currentCell;       
992                                         return  allOfCellSelected || tableCellSelection;
993                                         // return false;
994                                 }
995                                 
996                                 // this nasty hack is here to work around some WebKit selection bugs.
997                                 function fixTableCellSelection(ed) {
998                                         if (!tinymce.isWebKit)
999                                                 return;
1000
1001                                         var rng = ed.selection.getRng();
1002                                         var n = ed.selection.getNode();
1003                                         var currentCell = ed.dom.getParent(rng.startContainer, 'TD');
1004                                 
1005                                         if (!tableCellSelected(ed, rng, n, currentCell))
1006                                                 return;
1007                                                 if (!currentCell) {
1008                                                         currentCell=n;
1009                                                 }
1010                                         
1011                                         // Get the very last node inside the table cell
1012                                         var end = currentCell.lastChild;
1013                                         while (end.lastChild)
1014                                                 end = end.lastChild;
1015                     
1016                                         // Select the entire table cell. Nothing outside of the table cell should be selected.
1017                                         rng.setEnd(end, end.nodeValue.length);
1018                                         ed.selection.setRng(rng);
1019                                 }
1020                                 ed.plugins.table.fixTableCellSelection=fixTableCellSelection;
1021
1022                                 // Add context menu
1023                                 if (ed && ed.plugins.contextmenu) {
1024                                         ed.plugins.contextmenu.onContextMenu.add(function(th, m, e) {
1025                                                 var sm, se = ed.selection, el = se.getNode() || ed.getBody();
1026
1027                                                 if (ed.dom.getParent(e, 'td') || ed.dom.getParent(e, 'th') || ed.dom.select('td.mceSelected,th.mceSelected').length) {
1028                                                         m.removeAll();
1029
1030                                                         if (el.nodeName == 'A' && !ed.dom.getAttrib(el, 'name')) {
1031                                                                 m.add({title : 'advanced.link_desc', icon : 'link', cmd : ed.plugins.advlink ? 'mceAdvLink' : 'mceLink', ui : true});
1032                                                                 m.add({title : 'advanced.unlink_desc', icon : 'unlink', cmd : 'UnLink'});
1033                                                                 m.addSeparator();
1034                                                         }
1035
1036                                                         if (el.nodeName == 'IMG' && el.className.indexOf('mceItem') == -1) {
1037                                                                 m.add({title : 'advanced.image_desc', icon : 'image', cmd : ed.plugins.advimage ? 'mceAdvImage' : 'mceImage', ui : true});
1038                                                                 m.addSeparator();
1039                                                         }
1040
1041                                                         m.add({title : 'table.desc', icon : 'table', cmd : 'mceInsertTable', value : {action : 'insert'}});
1042                                                         m.add({title : 'table.props_desc', icon : 'table_props', cmd : 'mceInsertTable'});
1043                                                         m.add({title : 'table.del', icon : 'delete_table', cmd : 'mceTableDelete'});
1044                                                         m.addSeparator();
1045
1046                                                         // Cell menu
1047                                                         sm = m.addMenu({title : 'table.cell'});
1048                                                         sm.add({title : 'table.cell_desc', icon : 'cell_props', cmd : 'mceTableCellProps'});
1049                                                         sm.add({title : 'table.split_cells_desc', icon : 'split_cells', cmd : 'mceTableSplitCells'});
1050                                                         sm.add({title : 'table.merge_cells_desc', icon : 'merge_cells', cmd : 'mceTableMergeCells'});
1051
1052                                                         // Row menu
1053                                                         sm = m.addMenu({title : 'table.row'});
1054                                                         sm.add({title : 'table.row_desc', icon : 'row_props', cmd : 'mceTableRowProps'});
1055                                                         sm.add({title : 'table.row_before_desc', icon : 'row_before', cmd : 'mceTableInsertRowBefore'});
1056                                                         sm.add({title : 'table.row_after_desc', icon : 'row_after', cmd : 'mceTableInsertRowAfter'});
1057                                                         sm.add({title : 'table.delete_row_desc', icon : 'delete_row', cmd : 'mceTableDeleteRow'});
1058                                                         sm.addSeparator();
1059                                                         sm.add({title : 'table.cut_row_desc', icon : 'cut', cmd : 'mceTableCutRow'});
1060                                                         sm.add({title : 'table.copy_row_desc', icon : 'copy', cmd : 'mceTableCopyRow'});
1061                                                         sm.add({title : 'table.paste_row_before_desc', icon : 'paste', cmd : 'mceTablePasteRowBefore'}).setDisabled(!clipboardRows);
1062                                                         sm.add({title : 'table.paste_row_after_desc', icon : 'paste', cmd : 'mceTablePasteRowAfter'}).setDisabled(!clipboardRows);
1063
1064                                                         // Column menu
1065                                                         sm = m.addMenu({title : 'table.col'});
1066                                                         sm.add({title : 'table.col_before_desc', icon : 'col_before', cmd : 'mceTableInsertColBefore'});
1067                                                         sm.add({title : 'table.col_after_desc', icon : 'col_after', cmd : 'mceTableInsertColAfter'});
1068                                                         sm.add({title : 'table.delete_col_desc', icon : 'delete_col', cmd : 'mceTableDeleteCol'});
1069                                                 } else
1070                                                         m.add({title : 'table.desc', icon : 'table', cmd : 'mceInsertTable'});
1071                                         });
1072                                 }
1073
1074                                 // Fix to allow navigating up and down in a table in WebKit browsers.
1075                                 if (tinymce.isWebKit) {
1076                                         function moveSelection(ed, e) {
1077
1078                                                 function moveCursorToStartOfElement(n) {
1079                                                         ed.selection.setCursorLocation(n, 0);
1080                                                 }
1081
1082                                                 function getSibling(event, element) {
1083                                                         return event.keyCode == UP_ARROW ? element.previousSibling : element.nextSibling;
1084                                                 }
1085
1086                                                 function getNextRow(e, row) {
1087                                                         var sibling = getSibling(e, row);
1088                                                         return sibling !== null && sibling.tagName === 'TR' ? sibling : null;
1089                                                 }
1090
1091                                                 function getTable(ed, currentRow) {
1092                                                         return ed.dom.getParent(currentRow, 'table');
1093                                                 }
1094
1095                                                 function getTableSibling(currentRow) {
1096                                                         var table = getTable(ed, currentRow);
1097                                                         return getSibling(e, table);
1098                                                 }
1099
1100                                                 function isVerticalMovement(event) {
1101                                                         return event.keyCode == UP_ARROW || event.keyCode == DOWN_ARROW;
1102                                                 }
1103
1104                                                 function isInTable(ed) {
1105                                                         var node = ed.selection.getNode();
1106                                                         var currentRow = ed.dom.getParent(node, 'tr');
1107                                                         return currentRow !== null;
1108                                                 }
1109
1110                                                 function columnIndex(column) {
1111                                                         var colIndex = 0;
1112                                                         var c = column;
1113                                                         while (c.previousSibling) {
1114                                                                 c = c.previousSibling;
1115                                                                 colIndex = colIndex + getSpanVal(c, "colspan");
1116                                                         }
1117                                                         return colIndex;
1118                                                 }
1119
1120                                                 function findColumn(rowElement, columnIndex) {
1121                                                         var c = 0;
1122                                                         var r = 0;
1123                                                         each(rowElement.children, function(cell, i) {
1124                                                                 c = c + getSpanVal(cell, "colspan");
1125                                                                 r = i;
1126                                                                 if (c > columnIndex)
1127                                                                         return false;
1128                                                         });
1129                                                         return r;
1130                                                 }
1131
1132                                                 function moveCursorToRow(ed, node, row) {
1133                                                         var srcColumnIndex = columnIndex(ed.dom.getParent(node, 'td,th'));
1134                                                         var tgtColumnIndex = findColumn(row, srcColumnIndex)
1135                                                         var tgtNode = row.childNodes[tgtColumnIndex];
1136                                                         moveCursorToStartOfElement(tgtNode);
1137                                                 }
1138
1139                                                 function escapeTable(currentRow, e) {
1140                                                         var tableSiblingElement = getTableSibling(currentRow);
1141                                                         if (tableSiblingElement !== null) {
1142                                                                 moveCursorToStartOfElement(tableSiblingElement);
1143                                                                 return tinymce.dom.Event.cancel(e);
1144                                                         } else {
1145                                                                 var element = e.keyCode == UP_ARROW ? currentRow.firstChild : currentRow.lastChild;
1146                                                                 // rely on default behaviour to escape table after we are in the last cell of the last row
1147                                                                 moveCursorToStartOfElement(element);
1148                                                                 return true;
1149                                                         }
1150                                                 }
1151
1152                                                 var UP_ARROW = 38;
1153                                                 var DOWN_ARROW = 40;
1154
1155                                                 if (isVerticalMovement(e) && isInTable(ed)) {
1156                                                         var node = ed.selection.getNode();
1157                                                         var currentRow = ed.dom.getParent(node, 'tr');
1158                                                         var nextRow = getNextRow(e, currentRow);
1159
1160                                                         // If we're at the first or last row in the table, we should move the caret outside of the table
1161                                                         if (nextRow == null) {
1162                                                                 return escapeTable(currentRow, e);
1163                                                         } else {
1164                                                                 moveCursorToRow(ed, node, nextRow);
1165                                                                 tinymce.dom.Event.cancel(e);
1166                                                                 return true;
1167                                                         }
1168                                                 }
1169                                         }
1170
1171                                         ed.onKeyDown.add(moveSelection);
1172                                 }
1173                                                                 
1174                                 // Fixes an issue on Gecko where it's impossible to place the caret behind a table
1175                                 // This fix will force a paragraph element after the table but only when the forced_root_block setting is enabled
1176                                 if (!tinymce.isIE) {
1177                                         function fixTableCaretPos() {
1178                                                 var last;
1179
1180                                                 // Skip empty text nodes form the end
1181                                                 for (last = ed.getBody().lastChild; last && last.nodeType == 3 && !last.nodeValue.length; last = last.previousSibling) ;
1182
1183                                                 if (last && last.nodeName == 'TABLE')
1184                                                         ed.dom.add(ed.getBody(), 'p', null, '<br mce_bogus="1" />');
1185                                         };
1186
1187                                         // Fixes an bug where it's impossible to place the caret before a table in Gecko
1188                                         // this fix solves it by detecting when the caret is at the beginning of such a table
1189                                         // and then manually moves the caret infront of the table
1190                                         if (tinymce.isGecko) {
1191                                                 ed.onKeyDown.add(function(ed, e) {
1192                                                         var rng, table, dom = ed.dom;
1193
1194                                                         // On gecko it's not possible to place the caret before a table
1195                                                         if (e.keyCode == 37 || e.keyCode == 38) {
1196                                                                 rng = ed.selection.getRng();
1197                                                                 table = dom.getParent(rng.startContainer, 'table');
1198
1199                                                                 if (table && ed.getBody().firstChild == table) {
1200                                                                         if (isAtStart(rng, table)) {
1201                                                                                 rng = dom.createRng();
1202
1203                                                                                 rng.setStartBefore(table);
1204                                                                                 rng.setEndBefore(table);
1205
1206                                                                                 ed.selection.setRng(rng);
1207
1208                                                                                 e.preventDefault();
1209                                                                         }
1210                                                                 }
1211                                                         }
1212                                                 });
1213                                         }
1214
1215                                         ed.onKeyUp.add(fixTableCaretPos);
1216                                         ed.onSetContent.add(fixTableCaretPos);
1217                                         ed.onVisualAid.add(fixTableCaretPos);
1218
1219                                         ed.onPreProcess.add(function(ed, o) {
1220                                                 var last = o.node.lastChild;
1221
1222                                                 if (last && last.childNodes.length == 1 && last.firstChild.nodeName == 'BR')
1223                                                         ed.dom.remove(last);
1224                                         });
1225
1226                                         fixTableCaretPos();
1227                                         ed.startContent = ed.getContent({format : 'raw'});
1228                                 }
1229                         });
1230
1231                         // Register action commands
1232                         each({
1233                                 mceTableSplitCells : function(grid) {
1234                                         grid.split();
1235                                 },
1236
1237                                 mceTableMergeCells : function(grid) {
1238                                         var rowSpan, colSpan, cell;
1239
1240                                         cell = ed.dom.getParent(ed.selection.getNode(), 'th,td');
1241                                         if (cell) {
1242                                                 rowSpan = cell.rowSpan;
1243                                                 colSpan = cell.colSpan;
1244                                         }
1245
1246                                         if (!ed.dom.select('td.mceSelected,th.mceSelected').length) {
1247                                                 winMan.open({
1248                                                         url : url + '/merge_cells.htm',
1249                                                         width : 240 + parseInt(ed.getLang('table.merge_cells_delta_width', 0)),
1250                                                         height : 110 + parseInt(ed.getLang('table.merge_cells_delta_height', 0)),
1251                                                         inline : 1
1252                                                 }, {
1253                                                         rows : rowSpan,
1254                                                         cols : colSpan,
1255                                                         onaction : function(data) {
1256                                                                 grid.merge(cell, data.cols, data.rows);
1257                                                         },
1258                                                         plugin_url : url
1259                                                 });
1260                                         } else
1261                                                 grid.merge();
1262                                 },
1263
1264                                 mceTableInsertRowBefore : function(grid) {
1265                                         grid.insertRow(true);
1266                                 },
1267
1268                                 mceTableInsertRowAfter : function(grid) {
1269                                         grid.insertRow();
1270                                 },
1271
1272                                 mceTableInsertColBefore : function(grid) {
1273                                         grid.insertCol(true);
1274                                 },
1275
1276                                 mceTableInsertColAfter : function(grid) {
1277                                         grid.insertCol();
1278                                 },
1279
1280                                 mceTableDeleteCol : function(grid) {
1281                                         grid.deleteCols();
1282                                 },
1283
1284                                 mceTableDeleteRow : function(grid) {
1285                                         grid.deleteRows();
1286                                 },
1287
1288                                 mceTableCutRow : function(grid) {
1289                                         clipboardRows = grid.cutRows();
1290                                 },
1291
1292                                 mceTableCopyRow : function(grid) {
1293                                         clipboardRows = grid.copyRows();
1294                                 },
1295
1296                                 mceTablePasteRowBefore : function(grid) {
1297                                         grid.pasteRows(clipboardRows, true);
1298                                 },
1299
1300                                 mceTablePasteRowAfter : function(grid) {
1301                                         grid.pasteRows(clipboardRows);
1302                                 },
1303
1304                                 mceTableDelete : function(grid) {
1305                                         grid.deleteTable();
1306                                 }
1307                         }, function(func, name) {
1308                                 ed.addCommand(name, function() {
1309                                         var grid = createTableGrid();
1310
1311                                         if (grid) {
1312                                                 func(grid);
1313                                                 ed.execCommand('mceRepaint');
1314                                                 cleanup();
1315                                         }
1316                                 });
1317                         });
1318
1319                         // Register dialog commands
1320                         each({
1321                                 mceInsertTable : function(val) {
1322                                         winMan.open({
1323                                                 url : url + '/table.htm',
1324                                                 width : 400 + parseInt(ed.getLang('table.table_delta_width', 0)),
1325                                                 height : 320 + parseInt(ed.getLang('table.table_delta_height', 0)),
1326                                                 inline : 1
1327                                         }, {
1328                                                 plugin_url : url,
1329                                                 action : val ? val.action : 0
1330                                         });
1331                                 },
1332
1333                                 mceTableRowProps : function() {
1334                                         winMan.open({
1335                                                 url : url + '/row.htm',
1336                                                 width : 400 + parseInt(ed.getLang('table.rowprops_delta_width', 0)),
1337                                                 height : 295 + parseInt(ed.getLang('table.rowprops_delta_height', 0)),
1338                                                 inline : 1
1339                                         }, {
1340                                                 plugin_url : url
1341                                         });
1342                                 },
1343
1344                                 mceTableCellProps : function() {
1345                                         winMan.open({
1346                                                 url : url + '/cell.htm',
1347                                                 width : 400 + parseInt(ed.getLang('table.cellprops_delta_width', 0)),
1348                                                 height : 295 + parseInt(ed.getLang('table.cellprops_delta_height', 0)),
1349                                                 inline : 1
1350                                         }, {
1351                                                 plugin_url : url
1352                                         });
1353                                 }
1354                         }, function(func, name) {
1355                                 ed.addCommand(name, function(ui, val) {
1356                                         func(val);
1357                                 });
1358                         });
1359                 }
1360         });
1361
1362         // Register plugin
1363         tinymce.PluginManager.add('table', tinymce.plugins.TablePlugin);
1364 })(tinymce);