comparison static/js/tiny_mce/plugins/table/editor_plugin_src.js @ 312:88b2b9cb8c1f

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