2 * editor_plugin_src.js
\r
4 * Copyright 2009, Moxiecode Systems AB
\r
5 * Released under LGPL License.
\r
7 * License: http://tinymce.moxiecode.com/license
\r
8 * Contributing: http://tinymce.moxiecode.com/contributing
\r
11 (function(tinymce) {
\r
12 var each = tinymce.each;
\r
14 // Checks if the selection/caret is at the start of the specified block element
\r
15 function isAtStart(rng, par) {
\r
16 var doc = par.ownerDocument, rng2 = doc.createRange(), elm;
\r
18 rng2.setStartBefore(par);
\r
19 rng2.setEnd(rng.endContainer, rng.endOffset);
\r
21 elm = doc.createElement('body');
\r
22 elm.appendChild(rng2.cloneContents());
\r
24 // Check for text characters of other elements that should be treated as content
\r
25 return elm.innerHTML.replace(/<(br|img|object|embed|input|textarea)[^>]*>/gi, '-').replace(/<[^>]+>/g, '').length == 0;
\r
31 function TableGrid(table, dom, selection) {
\r
32 var grid, startPos, endPos, selectedCell;
\r
35 selectedCell = dom.getParent(selection.getStart(), 'th,td');
\r
37 startPos = getPos(selectedCell);
\r
38 endPos = findEndPos();
\r
39 selectedCell = getCell(startPos.x, startPos.y);
\r
42 function cloneNode(node, children) {
\r
43 node = node.cloneNode(children);
\r
44 node.removeAttribute('id');
\r
49 function buildGrid() {
\r
54 each(['thead', 'tbody', 'tfoot'], function(part) {
\r
55 var rows = dom.select('> ' + part + ' tr', table);
\r
57 each(rows, function(tr, y) {
\r
60 each(dom.select('> td, > th', tr), function(td, x) {
\r
61 var x2, y2, rowspan, colspan;
\r
63 // Skip over existing cells produced by rowspan
\r
69 // Get col/rowspan from cell
\r
70 rowspan = getSpanVal(td, 'rowspan');
\r
71 colspan = getSpanVal(td, 'colspan');
\r
73 // Fill out rowspan/colspan right and down
\r
74 for (y2 = y; y2 < y + rowspan; y2++) {
\r
78 for (x2 = x; x2 < x + colspan; x2++) {
\r
81 real : y2 == y && x2 == x,
\r
91 startY += rows.length;
\r
95 function getCell(x, y) {
\r
103 function getSpanVal(td, name) {
\r
104 return parseInt(td.getAttribute(name) || 1);
\r
107 function isCellSelected(cell) {
\r
108 return dom.hasClass(cell.elm, 'mceSelected') || cell == selectedCell;
\r
111 function getSelectedRows() {
\r
114 each(table.rows, function(row) {
\r
115 each(row.cells, function(cell) {
\r
116 if (dom.hasClass(cell, 'mceSelected') || cell == selectedCell.elm) {
\r
126 function deleteTable() {
\r
127 var rng = dom.createRng();
\r
129 rng.setStartAfter(table);
\r
130 rng.setEndAfter(table);
\r
132 selection.setRng(rng);
\r
137 function cloneCell(cell) {
\r
141 tinymce.walk(cell, function(node) {
\r
144 if (node.nodeType == 3) {
\r
145 each(dom.getParents(node.parentNode, null, cell).reverse(), function(node) {
\r
146 node = cloneNode(node, false);
\r
149 formatNode = curNode = node;
\r
151 curNode.appendChild(node);
\r
156 // Add something to the inner node
\r
158 curNode.innerHTML = tinymce.isIE ? ' ' : '<br _mce_bogus="1" />';
\r
164 cell = cloneNode(cell, false);
\r
165 cell.rowSpan = cell.colSpan = 1;
\r
168 cell.appendChild(formatNode);
\r
171 cell.innerHTML = '<br _mce_bogus="1" />';
\r
177 function cleanup() {
\r
178 var rng = dom.createRng();
\r
181 each(dom.select('tr', table), function(tr) {
\r
182 if (tr.cells.length == 0)
\r
187 if (dom.select('tr', table).length == 0) {
\r
188 rng.setStartAfter(table);
\r
189 rng.setEndAfter(table);
\r
190 selection.setRng(rng);
\r
195 // Empty header/body/footer
\r
196 each(dom.select('thead,tbody,tfoot', table), function(part) {
\r
197 if (part.rows.length == 0)
\r
201 // Restore selection to start position if it still exists
\r
204 // Restore the selection to the closest table position
\r
205 row = grid[Math.min(grid.length - 1, startPos.y)];
\r
207 selection.select(row[Math.min(row.length - 1, startPos.x)].elm, true);
\r
208 selection.collapse(true);
\r
212 function fillLeftDown(x, y, rows, cols) {
\r
213 var tr, x2, r, c, cell;
\r
215 tr = grid[y][x].elm.parentNode;
\r
216 for (r = 1; r <= rows; r++) {
\r
217 tr = dom.getNext(tr, 'tr');
\r
220 // Loop left to find real cell
\r
221 for (x2 = x; x2 >= 0; x2--) {
\r
222 cell = grid[y + r][x2].elm;
\r
224 if (cell.parentNode == tr) {
\r
225 // Append clones after
\r
226 for (c = 1; c <= cols; c++)
\r
227 dom.insertAfter(cloneCell(cell), cell);
\r
234 // Insert nodes before first cell
\r
235 for (c = 1; c <= cols; c++)
\r
236 tr.insertBefore(cloneCell(tr.cells[0]), tr.cells[0]);
\r
243 each(grid, function(row, y) {
\r
244 each(row, function(cell, x) {
\r
245 var colSpan, rowSpan, newCell, i;
\r
247 if (isCellSelected(cell)) {
\r
249 colSpan = getSpanVal(cell, 'colspan');
\r
250 rowSpan = getSpanVal(cell, 'rowspan');
\r
252 if (colSpan > 1 || rowSpan > 1) {
\r
253 cell.colSpan = cell.rowSpan = 1;
\r
255 // Insert cells right
\r
256 for (i = 0; i < colSpan - 1; i++)
\r
257 dom.insertAfter(cloneCell(cell), cell);
\r
259 fillLeftDown(x, y, rowSpan - 1, colSpan);
\r
266 function merge(cell, cols, rows) {
\r
267 var startX, startY, endX, endY, x, y, startCell, endCell, cell, children;
\r
269 // Use specified cell and cols/rows
\r
271 pos = getPos(cell);
\r
274 endX = startX + (cols - 1);
\r
275 endY = startY + (rows - 1);
\r
278 startX = startPos.x;
\r
279 startY = startPos.y;
\r
284 // Find start/end cells
\r
285 startCell = getCell(startX, startY);
\r
286 endCell = getCell(endX, endY);
\r
288 // Check if the cells exists and if they are of the same part for example tbody = tbody
\r
289 if (startCell && endCell && startCell.part == endCell.part) {
\r
290 // Split and rebuild grid
\r
294 // Set row/col span to start cell
\r
295 startCell = getCell(startX, startY).elm;
\r
296 startCell.colSpan = (endX - startX) + 1;
\r
297 startCell.rowSpan = (endY - startY) + 1;
\r
299 // Remove other cells and add it's contents to the start cell
\r
300 for (y = startY; y <= endY; y++) {
\r
301 for (x = startX; x <= endX; x++) {
\r
302 cell = grid[y][x].elm;
\r
304 if (cell != startCell) {
\r
305 // Move children to startCell
\r
306 children = tinymce.grep(cell.childNodes);
\r
307 each(children, function(node, i) {
\r
308 // Jump over last BR element
\r
309 if (node.nodeName != 'BR' || i != children.length - 1)
\r
310 startCell.appendChild(node);
\r
319 // Remove empty rows etc and restore caret location
\r
324 function insertRow(before) {
\r
325 var posY, cell, lastCell, x, rowElm, newRow, newCell, otherCell;
\r
327 // Find first/last row
\r
328 each(grid, function(row, y) {
\r
329 each(row, function(cell, x) {
\r
330 if (isCellSelected(cell)) {
\r
332 rowElm = cell.parentNode;
\r
333 newRow = cloneNode(rowElm, false);
\r
345 for (x = 0; x < grid[0].length; x++) {
\r
346 cell = grid[posY][x].elm;
\r
348 if (cell != lastCell) {
\r
350 rowSpan = getSpanVal(cell, 'rowspan');
\r
352 cell.rowSpan = rowSpan + 1;
\r
356 // Check if cell above can be expanded
\r
357 if (posY > 0 && grid[posY - 1][x]) {
\r
358 otherCell = grid[posY - 1][x].elm;
\r
359 rowSpan = getSpanVal(otherCell, 'rowspan');
\r
361 otherCell.rowSpan = rowSpan + 1;
\r
367 // Insert new cell into new row
\r
368 newCell = cloneCell(cell)
\r
369 newCell.colSpan = cell.colSpan;
\r
370 newRow.appendChild(newCell);
\r
376 if (newRow.hasChildNodes()) {
\r
378 dom.insertAfter(newRow, rowElm);
\r
380 rowElm.parentNode.insertBefore(newRow, rowElm);
\r
384 function insertCol(before) {
\r
385 var posX, lastCell;
\r
387 // Find first/last column
\r
388 each(grid, function(row, y) {
\r
389 each(row, function(cell, x) {
\r
390 if (isCellSelected(cell)) {
\r
402 each(grid, function(row, y) {
\r
403 var cell = row[posX].elm, rowSpan, colSpan;
\r
405 if (cell != lastCell) {
\r
406 colSpan = getSpanVal(cell, 'colspan');
\r
407 rowSpan = getSpanVal(cell, 'rowspan');
\r
409 if (colSpan == 1) {
\r
411 dom.insertAfter(cloneCell(cell), cell);
\r
412 fillLeftDown(posX, y, rowSpan - 1, colSpan);
\r
414 cell.parentNode.insertBefore(cloneCell(cell), cell);
\r
415 fillLeftDown(posX, y, rowSpan - 1, colSpan);
\r
425 function deleteCols() {
\r
428 // Get selected column indexes
\r
429 each(grid, function(row, y) {
\r
430 each(row, function(cell, x) {
\r
431 if (isCellSelected(cell) && tinymce.inArray(cols, x) === -1) {
\r
432 each(grid, function(row) {
\r
433 var cell = row[x].elm, colSpan;
\r
435 colSpan = getSpanVal(cell, 'colspan');
\r
438 cell.colSpan = colSpan - 1;
\r
451 function deleteRows() {
\r
454 function deleteRow(tr) {
\r
455 var nextTr, pos, lastCell;
\r
457 nextTr = dom.getNext(tr, 'tr');
\r
459 // Move down row spanned cells
\r
460 each(tr.cells, function(cell) {
\r
461 var rowSpan = getSpanVal(cell, 'rowspan');
\r
464 cell.rowSpan = rowSpan - 1;
\r
465 pos = getPos(cell);
\r
466 fillLeftDown(pos.x, pos.y, 1, 1);
\r
471 pos = getPos(tr.cells[0]);
\r
472 each(grid[pos.y], function(cell) {
\r
477 if (cell != lastCell) {
\r
478 rowSpan = getSpanVal(cell, 'rowspan');
\r
483 cell.rowSpan = rowSpan - 1;
\r
490 // Get selected rows and move selection out of scope
\r
491 rows = getSelectedRows();
\r
493 // Delete all selected rows
\r
494 each(rows.reverse(), function(tr) {
\r
501 function cutRows() {
\r
502 var rows = getSelectedRows();
\r
510 function copyRows() {
\r
511 var rows = getSelectedRows();
\r
513 each(rows, function(row, i) {
\r
514 rows[i] = cloneNode(row, true);
\r
520 function pasteRows(rows, before) {
\r
521 var selectedRows = getSelectedRows(),
\r
522 targetRow = selectedRows[before ? 0 : selectedRows.length - 1],
\r
523 targetCellCount = targetRow.cells.length;
\r
525 // Calc target cell count
\r
526 each(grid, function(row) {
\r
529 targetCellCount = 0;
\r
530 each(row, function(cell, x) {
\r
532 targetCellCount += cell.colspan;
\r
534 if (cell.elm.parentNode == targetRow)
\r
545 each(rows, function(row) {
\r
546 var cellCount = row.cells.length, cell;
\r
548 // Remove col/rowspans
\r
549 for (i = 0; i < cellCount; i++) {
\r
550 cell = row.cells[i];
\r
551 cell.colSpan = cell.rowSpan = 1;
\r
554 // Needs more cells
\r
555 for (i = cellCount; i < targetCellCount; i++)
\r
556 row.appendChild(cloneCell(row.cells[cellCount - 1]));
\r
558 // Needs less cells
\r
559 for (i = targetCellCount; i < cellCount; i++)
\r
560 dom.remove(row.cells[i]);
\r
562 // Add before/after
\r
564 targetRow.parentNode.insertBefore(row, targetRow);
\r
566 dom.insertAfter(row, targetRow);
\r
570 function getPos(target) {
\r
573 each(grid, function(row, y) {
\r
574 each(row, function(cell, x) {
\r
575 if (cell.elm == target) {
\r
576 pos = {x : x, y : y};
\r
587 function setStartCell(cell) {
\r
588 startPos = getPos(cell);
\r
591 function findEndPos() {
\r
592 var pos, maxX, maxY;
\r
596 each(grid, function(row, y) {
\r
597 each(row, function(cell, x) {
\r
598 var colSpan, rowSpan;
\r
600 if (isCellSelected(cell)) {
\r
610 colSpan = cell.colspan - 1;
\r
611 rowSpan = cell.rowspan - 1;
\r
614 if (x + colSpan > maxX)
\r
615 maxX = x + colSpan;
\r
619 if (y + rowSpan > maxY)
\r
620 maxY = y + rowSpan;
\r
627 return {x : maxX, y : maxY};
\r
630 function setEndCell(cell) {
\r
631 var startX, startY, endX, endY, maxX, maxY, colSpan, rowSpan;
\r
633 endPos = getPos(cell);
\r
635 if (startPos && endPos) {
\r
636 // Get start/end positions
\r
637 startX = Math.min(startPos.x, endPos.x);
\r
638 startY = Math.min(startPos.y, endPos.y);
\r
639 endX = Math.max(startPos.x, endPos.x);
\r
640 endY = Math.max(startPos.y, endPos.y);
\r
642 // Expand end positon to include spans
\r
647 for (y = startY; y <= maxY; y++) {
\r
648 cell = grid[y][startX];
\r
651 if (startX - (cell.colspan - 1) < startX)
\r
652 startX -= cell.colspan - 1;
\r
657 for (x = startX; x <= maxX; x++) {
\r
658 cell = grid[startY][x];
\r
661 if (startY - (cell.rowspan - 1) < startY)
\r
662 startY -= cell.rowspan - 1;
\r
667 for (y = startY; y <= endY; y++) {
\r
668 for (x = startX; x <= endX; x++) {
\r
672 colSpan = cell.colspan - 1;
\r
673 rowSpan = cell.rowspan - 1;
\r
676 if (x + colSpan > maxX)
\r
677 maxX = x + colSpan;
\r
681 if (y + rowSpan > maxY)
\r
682 maxY = y + rowSpan;
\r
688 // Remove current selection
\r
689 dom.removeClass(dom.select('td.mceSelected,th.mceSelected'), 'mceSelected');
\r
691 // Add new selection
\r
692 for (y = startY; y <= maxY; y++) {
\r
693 for (x = startX; x <= maxX; x++)
\r
694 dom.addClass(grid[y][x].elm, 'mceSelected');
\r
699 // Expose to public
\r
700 tinymce.extend(this, {
\r
701 deleteTable : deleteTable,
\r
704 insertRow : insertRow,
\r
705 insertCol : insertCol,
\r
706 deleteCols : deleteCols,
\r
707 deleteRows : deleteRows,
\r
709 copyRows : copyRows,
\r
710 pasteRows : pasteRows,
\r
712 setStartCell : setStartCell,
\r
713 setEndCell : setEndCell
\r
717 tinymce.create('tinymce.plugins.TablePlugin', {
\r
718 init : function(ed, url) {
\r
719 var winMan, clipboardRows;
\r
721 function createTableGrid(node) {
\r
722 var selection = ed.selection, tblElm = ed.dom.getParent(node || selection.getNode(), 'table');
\r
725 return new TableGrid(tblElm, ed.dom, selection);
\r
728 function cleanup() {
\r
729 // Restore selection possibilities
\r
730 ed.getBody().style.webkitUserSelect = '';
\r
731 ed.dom.removeClass(ed.dom.select('td.mceSelected,th.mceSelected'), 'mceSelected');
\r
734 // Register buttons
\r
736 ['table', 'table.desc', 'mceInsertTable', true],
\r
737 ['delete_table', 'table.del', 'mceTableDelete'],
\r
738 ['delete_col', 'table.delete_col_desc', 'mceTableDeleteCol'],
\r
739 ['delete_row', 'table.delete_row_desc', 'mceTableDeleteRow'],
\r
740 ['col_after', 'table.col_after_desc', 'mceTableInsertColAfter'],
\r
741 ['col_before', 'table.col_before_desc', 'mceTableInsertColBefore'],
\r
742 ['row_after', 'table.row_after_desc', 'mceTableInsertRowAfter'],
\r
743 ['row_before', 'table.row_before_desc', 'mceTableInsertRowBefore'],
\r
744 ['row_props', 'table.row_desc', 'mceTableRowProps', true],
\r
745 ['cell_props', 'table.cell_desc', 'mceTableCellProps', true],
\r
746 ['split_cells', 'table.split_cells_desc', 'mceTableSplitCells', true],
\r
747 ['merge_cells', 'table.merge_cells_desc', 'mceTableMergeCells', true]
\r
749 ed.addButton(c[0], {title : c[1], cmd : c[2], ui : c[3]});
\r
752 // Select whole table is a table border is clicked
\r
753 if (!tinymce.isIE) {
\r
754 ed.onClick.add(function(ed, e) {
\r
757 if (e.nodeName === 'TABLE')
\r
758 ed.selection.select(e);
\r
762 // Handle node change updates
\r
763 ed.onNodeChange.add(function(ed, cm, n) {
\r
766 n = ed.selection.getStart();
\r
767 p = ed.dom.getParent(n, 'td,th,caption');
\r
768 cm.setActive('table', n.nodeName === 'TABLE' || !!p);
\r
770 // Disable table tools if we are in caption
\r
771 if (p && p.nodeName === 'CAPTION')
\r
774 cm.setDisabled('delete_table', !p);
\r
775 cm.setDisabled('delete_col', !p);
\r
776 cm.setDisabled('delete_table', !p);
\r
777 cm.setDisabled('delete_row', !p);
\r
778 cm.setDisabled('col_after', !p);
\r
779 cm.setDisabled('col_before', !p);
\r
780 cm.setDisabled('row_after', !p);
\r
781 cm.setDisabled('row_before', !p);
\r
782 cm.setDisabled('row_props', !p);
\r
783 cm.setDisabled('cell_props', !p);
\r
784 cm.setDisabled('split_cells', !p);
\r
785 cm.setDisabled('merge_cells', !p);
\r
788 ed.onInit.add(function(ed) {
\r
789 var startTable, startCell, dom = ed.dom, tableGrid;
\r
791 winMan = ed.windowManager;
\r
793 // Add cell selection logic
\r
794 ed.onMouseDown.add(function(ed, e) {
\r
795 if (e.button != 2) {
\r
798 startCell = dom.getParent(e.target, 'td,th');
\r
799 startTable = dom.getParent(startCell, 'table');
\r
803 dom.bind(ed.getDoc(), 'mouseover', function(e) {
\r
804 var sel, table, target = e.target;
\r
806 if (startCell && (tableGrid || target != startCell) && (target.nodeName == 'TD' || target.nodeName == 'TH')) {
\r
807 table = dom.getParent(target, 'table');
\r
808 if (table == startTable) {
\r
810 tableGrid = createTableGrid(table);
\r
811 tableGrid.setStartCell(startCell);
\r
813 ed.getBody().style.webkitUserSelect = 'none';
\r
816 tableGrid.setEndCell(target);
\r
819 // Remove current selection
\r
820 sel = ed.selection.getSel();
\r
822 if (sel.removeAllRanges)
\r
823 sel.removeAllRanges();
\r
827 e.preventDefault();
\r
831 ed.onMouseUp.add(function(ed, e) {
\r
832 var rng, sel = ed.selection, selectedCells, nativeSel = sel.getSel(), walker, node, lastNode, endNode;
\r
834 // Move selection to startCell
\r
837 ed.getBody().style.webkitUserSelect = '';
\r
839 function setPoint(node, start) {
\r
840 var walker = new tinymce.dom.TreeWalker(node, node);
\r
844 if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length != 0) {
\r
846 rng.setStart(node, 0);
\r
848 rng.setEnd(node, node.nodeValue.length);
\r
854 if (node.nodeName == 'BR') {
\r
856 rng.setStartBefore(node);
\r
858 rng.setEndBefore(node);
\r
862 } while (node = (start ? walker.next() : walker.prev()));
\r
865 // Try to expand text selection as much as we can only Gecko supports cell selection
\r
866 selectedCells = dom.select('td.mceSelected,th.mceSelected');
\r
867 if (selectedCells.length > 0) {
\r
868 rng = dom.createRng();
\r
869 node = selectedCells[0];
\r
870 endNode = selectedCells[selectedCells.length - 1];
\r
873 walker = new tinymce.dom.TreeWalker(node, dom.getParent(selectedCells[0], 'table'));
\r
876 if (node.nodeName == 'TD' || node.nodeName == 'TH') {
\r
877 if (!dom.hasClass(node, 'mceSelected'))
\r
882 } while (node = walker.next());
\r
884 setPoint(lastNode);
\r
890 startCell = tableGrid = startTable = null;
\r
894 ed.onKeyUp.add(function(ed, e) {
\r
898 // Add context menu
\r
899 if (ed && ed.plugins.contextmenu) {
\r
900 ed.plugins.contextmenu.onContextMenu.add(function(th, m, e) {
\r
901 var sm, se = ed.selection, el = se.getNode() || ed.getBody();
\r
903 if (ed.dom.getParent(e, 'td') || ed.dom.getParent(e, 'th') || ed.dom.select('td.mceSelected,th.mceSelected').length) {
\r
906 if (el.nodeName == 'A' && !ed.dom.getAttrib(el, 'name')) {
\r
907 m.add({title : 'advanced.link_desc', icon : 'link', cmd : ed.plugins.advlink ? 'mceAdvLink' : 'mceLink', ui : true});
\r
908 m.add({title : 'advanced.unlink_desc', icon : 'unlink', cmd : 'UnLink'});
\r
912 if (el.nodeName == 'IMG' && el.className.indexOf('mceItem') == -1) {
\r
913 m.add({title : 'advanced.image_desc', icon : 'image', cmd : ed.plugins.advimage ? 'mceAdvImage' : 'mceImage', ui : true});
\r
917 m.add({title : 'table.desc', icon : 'table', cmd : 'mceInsertTable', value : {action : 'insert'}});
\r
918 m.add({title : 'table.props_desc', icon : 'table_props', cmd : 'mceInsertTable'});
\r
919 m.add({title : 'table.del', icon : 'delete_table', cmd : 'mceTableDelete'});
\r
923 sm = m.addMenu({title : 'table.cell'});
\r
924 sm.add({title : 'table.cell_desc', icon : 'cell_props', cmd : 'mceTableCellProps'});
\r
925 sm.add({title : 'table.split_cells_desc', icon : 'split_cells', cmd : 'mceTableSplitCells'});
\r
926 sm.add({title : 'table.merge_cells_desc', icon : 'merge_cells', cmd : 'mceTableMergeCells'});
\r
929 sm = m.addMenu({title : 'table.row'});
\r
930 sm.add({title : 'table.row_desc', icon : 'row_props', cmd : 'mceTableRowProps'});
\r
931 sm.add({title : 'table.row_before_desc', icon : 'row_before', cmd : 'mceTableInsertRowBefore'});
\r
932 sm.add({title : 'table.row_after_desc', icon : 'row_after', cmd : 'mceTableInsertRowAfter'});
\r
933 sm.add({title : 'table.delete_row_desc', icon : 'delete_row', cmd : 'mceTableDeleteRow'});
\r
935 sm.add({title : 'table.cut_row_desc', icon : 'cut', cmd : 'mceTableCutRow'});
\r
936 sm.add({title : 'table.copy_row_desc', icon : 'copy', cmd : 'mceTableCopyRow'});
\r
937 sm.add({title : 'table.paste_row_before_desc', icon : 'paste', cmd : 'mceTablePasteRowBefore'}).setDisabled(!clipboardRows);
\r
938 sm.add({title : 'table.paste_row_after_desc', icon : 'paste', cmd : 'mceTablePasteRowAfter'}).setDisabled(!clipboardRows);
\r
941 sm = m.addMenu({title : 'table.col'});
\r
942 sm.add({title : 'table.col_before_desc', icon : 'col_before', cmd : 'mceTableInsertColBefore'});
\r
943 sm.add({title : 'table.col_after_desc', icon : 'col_after', cmd : 'mceTableInsertColAfter'});
\r
944 sm.add({title : 'table.delete_col_desc', icon : 'delete_col', cmd : 'mceTableDeleteCol'});
\r
946 m.add({title : 'table.desc', icon : 'table', cmd : 'mceInsertTable'});
\r
950 // Fixes an issue on Gecko where it's impossible to place the caret behind a table
\r
951 // This fix will force a paragraph element after the table but only when the forced_root_block setting is enabled
\r
952 if (!tinymce.isIE) {
\r
953 function fixTableCaretPos() {
\r
956 // Skip empty text nodes form the end
\r
957 for (last = ed.getBody().lastChild; last && last.nodeType == 3 && !last.nodeValue.length; last = last.previousSibling) ;
\r
959 if (last && last.nodeName == 'TABLE')
\r
960 ed.dom.add(ed.getBody(), 'p', null, '<br mce_bogus="1" />');
\r
963 // Fixes an bug where it's impossible to place the caret before a table in Gecko
\r
964 // this fix solves it by detecting when the caret is at the beginning of such a table
\r
965 // and then manually moves the caret infront of the table
\r
966 if (tinymce.isGecko) {
\r
967 ed.onKeyDown.add(function(ed, e) {
\r
968 var rng, table, dom = ed.dom;
\r
970 // On gecko it's not possible to place the caret before a table
\r
971 if (e.keyCode == 37 || e.keyCode == 38) {
\r
972 rng = ed.selection.getRng();
\r
973 table = dom.getParent(rng.startContainer, 'table');
\r
975 if (table && ed.getBody().firstChild == table) {
\r
976 if (isAtStart(rng, table)) {
\r
977 rng = dom.createRng();
\r
979 rng.setStartBefore(table);
\r
980 rng.setEndBefore(table);
\r
982 ed.selection.setRng(rng);
\r
984 e.preventDefault();
\r
991 ed.onKeyUp.add(fixTableCaretPos);
\r
992 ed.onSetContent.add(fixTableCaretPos);
\r
993 ed.onVisualAid.add(fixTableCaretPos);
\r
995 ed.onPreProcess.add(function(ed, o) {
\r
996 var last = o.node.lastChild;
\r
998 if (last && last.childNodes.length == 1 && last.firstChild.nodeName == 'BR')
\r
999 ed.dom.remove(last);
\r
1002 fixTableCaretPos();
\r
1006 // Register action commands
\r
1008 mceTableSplitCells : function(grid) {
\r
1012 mceTableMergeCells : function(grid) {
\r
1013 var rowSpan, colSpan, cell;
\r
1015 cell = ed.dom.getParent(ed.selection.getNode(), 'th,td');
\r
1017 rowSpan = cell.rowSpan;
\r
1018 colSpan = cell.colSpan;
\r
1021 if (!ed.dom.select('td.mceSelected,th.mceSelected').length) {
\r
1023 url : url + '/merge_cells.htm',
\r
1024 width : 240 + parseInt(ed.getLang('table.merge_cells_delta_width', 0)),
\r
1025 height : 110 + parseInt(ed.getLang('table.merge_cells_delta_height', 0)),
\r
1030 onaction : function(data) {
\r
1031 grid.merge(cell, data.cols, data.rows);
\r
1039 mceTableInsertRowBefore : function(grid) {
\r
1040 grid.insertRow(true);
\r
1043 mceTableInsertRowAfter : function(grid) {
\r
1047 mceTableInsertColBefore : function(grid) {
\r
1048 grid.insertCol(true);
\r
1051 mceTableInsertColAfter : function(grid) {
\r
1055 mceTableDeleteCol : function(grid) {
\r
1056 grid.deleteCols();
\r
1059 mceTableDeleteRow : function(grid) {
\r
1060 grid.deleteRows();
\r
1063 mceTableCutRow : function(grid) {
\r
1064 clipboardRows = grid.cutRows();
\r
1067 mceTableCopyRow : function(grid) {
\r
1068 clipboardRows = grid.copyRows();
\r
1071 mceTablePasteRowBefore : function(grid) {
\r
1072 grid.pasteRows(clipboardRows, true);
\r
1075 mceTablePasteRowAfter : function(grid) {
\r
1076 grid.pasteRows(clipboardRows);
\r
1079 mceTableDelete : function(grid) {
\r
1080 grid.deleteTable();
\r
1082 }, function(func, name) {
\r
1083 ed.addCommand(name, function() {
\r
1084 var grid = createTableGrid();
\r
1088 ed.execCommand('mceRepaint');
\r
1094 // Register dialog commands
\r
1096 mceInsertTable : function(val) {
\r
1098 url : url + '/table.htm',
\r
1099 width : 400 + parseInt(ed.getLang('table.table_delta_width', 0)),
\r
1100 height : 320 + parseInt(ed.getLang('table.table_delta_height', 0)),
\r
1104 action : val ? val.action : 0
\r
1108 mceTableRowProps : function() {
\r
1110 url : url + '/row.htm',
\r
1111 width : 400 + parseInt(ed.getLang('table.rowprops_delta_width', 0)),
\r
1112 height : 295 + parseInt(ed.getLang('table.rowprops_delta_height', 0)),
\r
1119 mceTableCellProps : function() {
\r
1121 url : url + '/cell.htm',
\r
1122 width : 400 + parseInt(ed.getLang('table.cellprops_delta_width', 0)),
\r
1123 height : 295 + parseInt(ed.getLang('table.cellprops_delta_height', 0)),
\r
1129 }, function(func, name) {
\r
1130 ed.addCommand(name, function(ui, val) {
\r
1137 // Register plugin
\r
1138 tinymce.PluginManager.add('table', tinymce.plugins.TablePlugin);
\r