view static/js/tiny_mce/plugins/table/editor_plugin_src.js @ 631:f36d1a168be7

For issue 27, disable login dialog button during POST. This seems to prevent multiple logins most of the time. You can still bang on the enter key and sometimes get more through.
author Brian Neal <bgneal@gmail.com>
date Wed, 14 Nov 2012 20:57:05 -0600
parents 6c182ceb7147
children
line wrap: on
line source
/**
 * editor_plugin_src.js
 *
 * Copyright 2009, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://tinymce.moxiecode.com/license
 * Contributing: http://tinymce.moxiecode.com/contributing
 */

(function(tinymce) {
	var each = tinymce.each;

	// Checks if the selection/caret is at the start of the specified block element
	function isAtStart(rng, par) {
		var doc = par.ownerDocument, rng2 = doc.createRange(), elm;

		rng2.setStartBefore(par);
		rng2.setEnd(rng.endContainer, rng.endOffset);

		elm = doc.createElement('body');
		elm.appendChild(rng2.cloneContents());

		// Check for text characters of other elements that should be treated as content
		return elm.innerHTML.replace(/<(br|img|object|embed|input|textarea)[^>]*>/gi, '-').replace(/<[^>]+>/g, '').length == 0;
	};

	/**
	 * Table Grid class.
	 */
	function TableGrid(table, dom, selection) {
		var grid, startPos, endPos, selectedCell;

		buildGrid();
		selectedCell = dom.getParent(selection.getStart(), 'th,td');
		if (selectedCell) {
			startPos = getPos(selectedCell);
			endPos = findEndPos();
			selectedCell = getCell(startPos.x, startPos.y);
		}

		function cloneNode(node, children) {
			node = node.cloneNode(children);
			node.removeAttribute('id');

			return node;
		}

		function buildGrid() {
			var startY = 0;

			grid = [];

			each(['thead', 'tbody', 'tfoot'], function(part) {
				var rows = dom.select('> ' + part + ' tr', table);

				each(rows, function(tr, y) {
					y += startY;

					each(dom.select('> td, > th', tr), function(td, x) {
						var x2, y2, rowspan, colspan;

						// Skip over existing cells produced by rowspan
						if (grid[y]) {
							while (grid[y][x])
								x++;
						}

						// Get col/rowspan from cell
						rowspan = getSpanVal(td, 'rowspan');
						colspan = getSpanVal(td, 'colspan');

						// Fill out rowspan/colspan right and down
						for (y2 = y; y2 < y + rowspan; y2++) {
							if (!grid[y2])
								grid[y2] = [];

							for (x2 = x; x2 < x + colspan; x2++) {
								grid[y2][x2] = {
									part : part,
									real : y2 == y && x2 == x,
									elm : td,
									rowspan : rowspan,
									colspan : colspan
								};
							}
						}
					});
				});

				startY += rows.length;
			});
		};

		function getCell(x, y) {
			var row;

			row = grid[y];
			if (row)
				return row[x];
		};

		function getSpanVal(td, name) {
			return parseInt(td.getAttribute(name) || 1);
		};

		function setSpanVal(td, name, val) {
			if (td) {
				val = parseInt(val);

				if (val === 1)
					td.removeAttribute(name, 1);
				else
					td.setAttribute(name, val, 1);
			}
		}

		function isCellSelected(cell) {
			return cell && (dom.hasClass(cell.elm, 'mceSelected') || cell == selectedCell);
		};

		function getSelectedRows() {
			var rows = [];

			each(table.rows, function(row) {
				each(row.cells, function(cell) {
					if (dom.hasClass(cell, 'mceSelected') || cell == selectedCell.elm) {
						rows.push(row);
						return false;
					}
				});
			});

			return rows;
		};

		function deleteTable() {
			var rng = dom.createRng();

			rng.setStartAfter(table);
			rng.setEndAfter(table);

			selection.setRng(rng);

			dom.remove(table);
		};

		function cloneCell(cell) {
			var formatNode;

			// Clone formats
			tinymce.walk(cell, function(node) {
				var curNode;

				if (node.nodeType == 3) {
					each(dom.getParents(node.parentNode, null, cell).reverse(), function(node) {
						node = cloneNode(node, false);

						if (!formatNode)
							formatNode = curNode = node;
						else if (curNode)
							curNode.appendChild(node);

						curNode = node;
					});

					// Add something to the inner node
					if (curNode)
						curNode.innerHTML = tinymce.isIE ? '&nbsp;' : '<br data-mce-bogus="1" />';

					return false;
				}
			}, 'childNodes');

			cell = cloneNode(cell, false);
			setSpanVal(cell, 'rowSpan', 1);
			setSpanVal(cell, 'colSpan', 1);

			if (formatNode) {
				cell.appendChild(formatNode);
			} else {
				if (!tinymce.isIE)
					cell.innerHTML = '<br data-mce-bogus="1" />';
			}

			return cell;
		};

		function cleanup() {
			var rng = dom.createRng();

			// Empty rows
			each(dom.select('tr', table), function(tr) {
				if (tr.cells.length == 0)
					dom.remove(tr);
			});

			// Empty table
			if (dom.select('tr', table).length == 0) {
				rng.setStartAfter(table);
				rng.setEndAfter(table);
				selection.setRng(rng);
				dom.remove(table);
				return;
			}

			// Empty header/body/footer
			each(dom.select('thead,tbody,tfoot', table), function(part) {
				if (part.rows.length == 0)
					dom.remove(part);
			});

			// Restore selection to start position if it still exists
			buildGrid();

			// Restore the selection to the closest table position
			row = grid[Math.min(grid.length - 1, startPos.y)];
			if (row) {
				selection.select(row[Math.min(row.length - 1, startPos.x)].elm, true);
				selection.collapse(true);
			}
		};

		function fillLeftDown(x, y, rows, cols) {
			var tr, x2, r, c, cell;

			tr = grid[y][x].elm.parentNode;
			for (r = 1; r <= rows; r++) {
				tr = dom.getNext(tr, 'tr');

				if (tr) {
					// Loop left to find real cell
					for (x2 = x; x2 >= 0; x2--) {
						cell = grid[y + r][x2].elm;

						if (cell.parentNode == tr) {
							// Append clones after
							for (c = 1; c <= cols; c++)
								dom.insertAfter(cloneCell(cell), cell);

							break;
						}
					}

					if (x2 == -1) {
						// Insert nodes before first cell
						for (c = 1; c <= cols; c++)
							tr.insertBefore(cloneCell(tr.cells[0]), tr.cells[0]);
					}
				}
			}
		};

		function split() {
			each(grid, function(row, y) {
				each(row, function(cell, x) {
					var colSpan, rowSpan, newCell, i;

					if (isCellSelected(cell)) {
						cell = cell.elm;
						colSpan = getSpanVal(cell, 'colspan');
						rowSpan = getSpanVal(cell, 'rowspan');

						if (colSpan > 1 || rowSpan > 1) {
							setSpanVal(cell, 'rowSpan', 1);
							setSpanVal(cell, 'colSpan', 1);

							// Insert cells right
							for (i = 0; i < colSpan - 1; i++)
								dom.insertAfter(cloneCell(cell), cell);

							fillLeftDown(x, y, rowSpan - 1, colSpan);
						}
					}
				});
			});
		};

		function merge(cell, cols, rows) {
			var startX, startY, endX, endY, x, y, startCell, endCell, cell, children, count;

			// Use specified cell and cols/rows
			if (cell) {
				pos = getPos(cell);
				startX = pos.x;
				startY = pos.y;
				endX = startX + (cols - 1);
				endY = startY + (rows - 1);
			} else {
				// Use selection
				startX = startPos.x;
				startY = startPos.y;
				endX = endPos.x;
				endY = endPos.y;
			}

			// Find start/end cells
			startCell = getCell(startX, startY);
			endCell = getCell(endX, endY);

			// Check if the cells exists and if they are of the same part for example tbody = tbody
			if (startCell && endCell && startCell.part == endCell.part) {
				// Split and rebuild grid
				split();
				buildGrid();

				// Set row/col span to start cell
				startCell = getCell(startX, startY).elm;
				setSpanVal(startCell, 'colSpan', (endX - startX) + 1);
				setSpanVal(startCell, 'rowSpan', (endY - startY) + 1);

				// Remove other cells and add it's contents to the start cell
				for (y = startY; y <= endY; y++) {
					for (x = startX; x <= endX; x++) {
						if (!grid[y] || !grid[y][x])
							continue;

						cell = grid[y][x].elm;

						if (cell != startCell) {
							// Move children to startCell
							children = tinymce.grep(cell.childNodes);
							each(children, function(node) {
								startCell.appendChild(node);
							});

							// Remove bogus nodes if there is children in the target cell
							if (children.length) {
								children = tinymce.grep(startCell.childNodes);
								count = 0;
								each(children, function(node) {
									if (node.nodeName == 'BR' && dom.getAttrib(node, 'data-mce-bogus') && count++ < children.length - 1)
										startCell.removeChild(node);
								});
							}
							
							// Remove cell
							dom.remove(cell);
						}
					}
				}

				// Remove empty rows etc and restore caret location
				cleanup();
			}
		};

		function insertRow(before) {
			var posY, cell, lastCell, x, rowElm, newRow, newCell, otherCell, rowSpan;

			// Find first/last row
			each(grid, function(row, y) {
				each(row, function(cell, x) {
					if (isCellSelected(cell)) {
						cell = cell.elm;
						rowElm = cell.parentNode;
						newRow = cloneNode(rowElm, false);
						posY = y;

						if (before)
							return false;
					}
				});

				if (before)
					return !posY;
			});

			for (x = 0; x < grid[0].length; x++) {
				// Cell not found could be because of an invalid table structure
				if (!grid[posY][x])
					continue;

				cell = grid[posY][x].elm;

				if (cell != lastCell) {
					if (!before) {
						rowSpan = getSpanVal(cell, 'rowspan');
						if (rowSpan > 1) {
							setSpanVal(cell, 'rowSpan', rowSpan + 1);
							continue;
						}
					} else {
						// Check if cell above can be expanded
						if (posY > 0 && grid[posY - 1][x]) {
							otherCell = grid[posY - 1][x].elm;
							rowSpan = getSpanVal(otherCell, 'rowSpan');
							if (rowSpan > 1) {
								setSpanVal(otherCell, 'rowSpan', rowSpan + 1);
								continue;
							}
						}
					}

					// Insert new cell into new row
					newCell = cloneCell(cell);
					setSpanVal(newCell, 'colSpan', cell.colSpan);

					newRow.appendChild(newCell);

					lastCell = cell;
				}
			}

			if (newRow.hasChildNodes()) {
				if (!before)
					dom.insertAfter(newRow, rowElm);
				else
					rowElm.parentNode.insertBefore(newRow, rowElm);
			}
		};

		function insertCol(before) {
			var posX, lastCell;

			// Find first/last column
			each(grid, function(row, y) {
				each(row, function(cell, x) {
					if (isCellSelected(cell)) {
						posX = x;

						if (before)
							return false;
					}
				});

				if (before)
					return !posX;
			});

			each(grid, function(row, y) {
				var cell, rowSpan, colSpan;

				if (!row[posX])
					return;

				cell = row[posX].elm;
				if (cell != lastCell) {
					colSpan = getSpanVal(cell, 'colspan');
					rowSpan = getSpanVal(cell, 'rowspan');

					if (colSpan == 1) {
						if (!before) {
							dom.insertAfter(cloneCell(cell), cell);
							fillLeftDown(posX, y, rowSpan - 1, colSpan);
						} else {
							cell.parentNode.insertBefore(cloneCell(cell), cell);
							fillLeftDown(posX, y, rowSpan - 1, colSpan);
						}
					} else
						setSpanVal(cell, 'colSpan', cell.colSpan + 1);

					lastCell = cell;
				}
			});
		};

		function deleteCols() {
			var cols = [];

			// Get selected column indexes
			each(grid, function(row, y) {
				each(row, function(cell, x) {
					if (isCellSelected(cell) && tinymce.inArray(cols, x) === -1) {
						each(grid, function(row) {
							var cell = row[x].elm, colSpan;

							colSpan = getSpanVal(cell, 'colSpan');

							if (colSpan > 1)
								setSpanVal(cell, 'colSpan', colSpan - 1);
							else
								dom.remove(cell);
						});

						cols.push(x);
					}
				});
			});

			cleanup();
		};

		function deleteRows() {
			var rows;

			function deleteRow(tr) {
				var nextTr, pos, lastCell;

				nextTr = dom.getNext(tr, 'tr');

				// Move down row spanned cells
				each(tr.cells, function(cell) {
					var rowSpan = getSpanVal(cell, 'rowSpan');

					if (rowSpan > 1) {
						setSpanVal(cell, 'rowSpan', rowSpan - 1);
						pos = getPos(cell);
						fillLeftDown(pos.x, pos.y, 1, 1);
					}
				});

				// Delete cells
				pos = getPos(tr.cells[0]);
				each(grid[pos.y], function(cell) {
					var rowSpan;

					cell = cell.elm;

					if (cell != lastCell) {
						rowSpan = getSpanVal(cell, 'rowSpan');

						if (rowSpan <= 1)
							dom.remove(cell);
						else
							setSpanVal(cell, 'rowSpan', rowSpan - 1);

						lastCell = cell;
					}
				});
			};

			// Get selected rows and move selection out of scope
			rows = getSelectedRows();

			// Delete all selected rows
			each(rows.reverse(), function(tr) {
				deleteRow(tr);
			});

			cleanup();
		};

		function cutRows() {
			var rows = getSelectedRows();

			dom.remove(rows);
			cleanup();

			return rows;
		};

		function copyRows() {
			var rows = getSelectedRows();

			each(rows, function(row, i) {
				rows[i] = cloneNode(row, true);
			});

			return rows;
		};

		function pasteRows(rows, before) {
			var selectedRows = getSelectedRows(),
				targetRow = selectedRows[before ? 0 : selectedRows.length - 1],
				targetCellCount = targetRow.cells.length;

			// Calc target cell count
			each(grid, function(row) {
				var match;

				targetCellCount = 0;
				each(row, function(cell, x) {
					if (cell.real)
						targetCellCount += cell.colspan;

					if (cell.elm.parentNode == targetRow)
						match = 1;
				});

				if (match)
					return false;
			});

			if (!before)
				rows.reverse();

			each(rows, function(row) {
				var cellCount = row.cells.length, cell;

				// Remove col/rowspans
				for (i = 0; i < cellCount; i++) {
					cell = row.cells[i];
					setSpanVal(cell, 'colSpan', 1);
					setSpanVal(cell, 'rowSpan', 1);
				}

				// Needs more cells
				for (i = cellCount; i < targetCellCount; i++)
					row.appendChild(cloneCell(row.cells[cellCount - 1]));

				// Needs less cells
				for (i = targetCellCount; i < cellCount; i++)
					dom.remove(row.cells[i]);

				// Add before/after
				if (before)
					targetRow.parentNode.insertBefore(row, targetRow);
				else
					dom.insertAfter(row, targetRow);
			});
		};

		function getPos(target) {
			var pos;

			each(grid, function(row, y) {
				each(row, function(cell, x) {
					if (cell.elm == target) {
						pos = {x : x, y : y};
						return false;
					}
				});

				return !pos;
			});

			return pos;
		};

		function setStartCell(cell) {
			startPos = getPos(cell);
		};

		function findEndPos() {
			var pos, maxX, maxY;

			maxX = maxY = 0;

			each(grid, function(row, y) {
				each(row, function(cell, x) {
					var colSpan, rowSpan;

					if (isCellSelected(cell)) {
						cell = grid[y][x];

						if (x > maxX)
							maxX = x;

						if (y > maxY)
							maxY = y;

						if (cell.real) {
							colSpan = cell.colspan - 1;
							rowSpan = cell.rowspan - 1;

							if (colSpan) {
								if (x + colSpan > maxX)
									maxX = x + colSpan;
							}

							if (rowSpan) {
								if (y + rowSpan > maxY)
									maxY = y + rowSpan;
							}
						}
					}
				});
			});

			return {x : maxX, y : maxY};
		};

		function setEndCell(cell) {
			var startX, startY, endX, endY, maxX, maxY, colSpan, rowSpan;

			endPos = getPos(cell);

			if (startPos && endPos) {
				// Get start/end positions
				startX = Math.min(startPos.x, endPos.x);
				startY = Math.min(startPos.y, endPos.y);
				endX = Math.max(startPos.x, endPos.x);
				endY = Math.max(startPos.y, endPos.y);

				// Expand end positon to include spans
				maxX = endX;
				maxY = endY;

				// Expand startX
				for (y = startY; y <= maxY; y++) {
					cell = grid[y][startX];

					if (!cell.real) {
						if (startX - (cell.colspan - 1) < startX)
							startX -= cell.colspan - 1;
					}
				}

				// Expand startY
				for (x = startX; x <= maxX; x++) {
					cell = grid[startY][x];

					if (!cell.real) {
						if (startY - (cell.rowspan - 1) < startY)
							startY -= cell.rowspan - 1;
					}
				}

				// Find max X, Y
				for (y = startY; y <= endY; y++) {
					for (x = startX; x <= endX; x++) {
						cell = grid[y][x];

						if (cell.real) {
							colSpan = cell.colspan - 1;
							rowSpan = cell.rowspan - 1;

							if (colSpan) {
								if (x + colSpan > maxX)
									maxX = x + colSpan;
							}

							if (rowSpan) {
								if (y + rowSpan > maxY)
									maxY = y + rowSpan;
							}
						}
					}
				}

				// Remove current selection
				dom.removeClass(dom.select('td.mceSelected,th.mceSelected'), 'mceSelected');

				// Add new selection
				for (y = startY; y <= maxY; y++) {
					for (x = startX; x <= maxX; x++) {
						if (grid[y][x])
							dom.addClass(grid[y][x].elm, 'mceSelected');
					}
				}
			}
		};

		// Expose to public
		tinymce.extend(this, {
			deleteTable : deleteTable,
			split : split,
			merge : merge,
			insertRow : insertRow,
			insertCol : insertCol,
			deleteCols : deleteCols,
			deleteRows : deleteRows,
			cutRows : cutRows,
			copyRows : copyRows,
			pasteRows : pasteRows,
			getPos : getPos,
			setStartCell : setStartCell,
			setEndCell : setEndCell
		});
	};

	tinymce.create('tinymce.plugins.TablePlugin', {
		init : function(ed, url) {
			var winMan, clipboardRows;

			function createTableGrid(node) {
				var selection = ed.selection, tblElm = ed.dom.getParent(node || selection.getNode(), 'table');

				if (tblElm)
					return new TableGrid(tblElm, ed.dom, selection);
			};

			function cleanup() {
				// Restore selection possibilities
				ed.getBody().style.webkitUserSelect = '';
				ed.dom.removeClass(ed.dom.select('td.mceSelected,th.mceSelected'), 'mceSelected');
			};

			// Register buttons
			each([
				['table', 'table.desc', 'mceInsertTable', true],
				['delete_table', 'table.del', 'mceTableDelete'],
				['delete_col', 'table.delete_col_desc', 'mceTableDeleteCol'],
				['delete_row', 'table.delete_row_desc', 'mceTableDeleteRow'],
				['col_after', 'table.col_after_desc', 'mceTableInsertColAfter'],
				['col_before', 'table.col_before_desc', 'mceTableInsertColBefore'],
				['row_after', 'table.row_after_desc', 'mceTableInsertRowAfter'],
				['row_before', 'table.row_before_desc', 'mceTableInsertRowBefore'],
				['row_props', 'table.row_desc', 'mceTableRowProps', true],
				['cell_props', 'table.cell_desc', 'mceTableCellProps', true],
				['split_cells', 'table.split_cells_desc', 'mceTableSplitCells', true],
				['merge_cells', 'table.merge_cells_desc', 'mceTableMergeCells', true]
			], function(c) {
				ed.addButton(c[0], {title : c[1], cmd : c[2], ui : c[3]});
			});

			// Select whole table is a table border is clicked
			if (!tinymce.isIE) {
				ed.onClick.add(function(ed, e) {
					e = e.target;

					if (e.nodeName === 'TABLE') {
						ed.selection.select(e);
						ed.nodeChanged();
					}
				});
			}

			ed.onPreProcess.add(function(ed, args) {
				var nodes, i, node, dom = ed.dom, value;

				nodes = dom.select('table', args.node);
				i = nodes.length;
				while (i--) {
					node = nodes[i];
					dom.setAttrib(node, 'data-mce-style', '');

					if ((value = dom.getAttrib(node, 'width'))) {
						dom.setStyle(node, 'width', value);
						dom.setAttrib(node, 'width', '');
					}

					if ((value = dom.getAttrib(node, 'height'))) {
						dom.setStyle(node, 'height', value);
						dom.setAttrib(node, 'height', '');
					}
				}
			});

			// Handle node change updates
			ed.onNodeChange.add(function(ed, cm, n) {
				var p;

				n = ed.selection.getStart();
				p = ed.dom.getParent(n, 'td,th,caption');
				cm.setActive('table', n.nodeName === 'TABLE' || !!p);

				// Disable table tools if we are in caption
				if (p && p.nodeName === 'CAPTION')
					p = 0;

				cm.setDisabled('delete_table', !p);
				cm.setDisabled('delete_col', !p);
				cm.setDisabled('delete_table', !p);
				cm.setDisabled('delete_row', !p);
				cm.setDisabled('col_after', !p);
				cm.setDisabled('col_before', !p);
				cm.setDisabled('row_after', !p);
				cm.setDisabled('row_before', !p);
				cm.setDisabled('row_props', !p);
				cm.setDisabled('cell_props', !p);
				cm.setDisabled('split_cells', !p);
				cm.setDisabled('merge_cells', !p);
			});

			ed.onInit.add(function(ed) {
				var startTable, startCell, dom = ed.dom, tableGrid;

				winMan = ed.windowManager;

				// Add cell selection logic
				ed.onMouseDown.add(function(ed, e) {
					if (e.button != 2) {
						cleanup();

						startCell = dom.getParent(e.target, 'td,th');
						startTable = dom.getParent(startCell, 'table');
					}
				});

				dom.bind(ed.getDoc(), 'mouseover', function(e) {
					var sel, table, target = e.target;

					if (startCell && (tableGrid || target != startCell) && (target.nodeName == 'TD' || target.nodeName == 'TH')) {
						table = dom.getParent(target, 'table');
						if (table == startTable) {
							if (!tableGrid) {
								tableGrid = createTableGrid(table);
								tableGrid.setStartCell(startCell);

								ed.getBody().style.webkitUserSelect = 'none';
							}

							tableGrid.setEndCell(target);
						}

						// Remove current selection
						sel = ed.selection.getSel();

						try {
							if (sel.removeAllRanges)
								sel.removeAllRanges();
							else
								sel.empty();
						} catch (ex) {
							// IE9 might throw errors here
						}

						e.preventDefault();
					}
				});

				ed.onMouseUp.add(function(ed, e) {
					var rng, sel = ed.selection, selectedCells, nativeSel = sel.getSel(), walker, node, lastNode, endNode;

					// Move selection to startCell
					if (startCell) {
						if (tableGrid)
							ed.getBody().style.webkitUserSelect = '';

						function setPoint(node, start) {
							var walker = new tinymce.dom.TreeWalker(node, node);

							do {
								// Text node
								if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length != 0) {
									if (start)
										rng.setStart(node, 0);
									else
										rng.setEnd(node, node.nodeValue.length);

									return;
								}

								// BR element
								if (node.nodeName == 'BR') {
									if (start)
										rng.setStartBefore(node);
									else
										rng.setEndBefore(node);

									return;
								}
							} while (node = (start ? walker.next() : walker.prev()));
						};

						// Try to expand text selection as much as we can only Gecko supports cell selection
						selectedCells = dom.select('td.mceSelected,th.mceSelected');
						if (selectedCells.length > 0) {
							rng = dom.createRng();
							node = selectedCells[0];
							endNode = selectedCells[selectedCells.length - 1];

							setPoint(node, 1);
							walker = new tinymce.dom.TreeWalker(node, dom.getParent(selectedCells[0], 'table'));

							do {
								if (node.nodeName == 'TD' || node.nodeName == 'TH') {
									if (!dom.hasClass(node, 'mceSelected'))
										break;

									lastNode = node;
								}
							} while (node = walker.next());

							setPoint(lastNode);

							sel.setRng(rng);
						}

						ed.nodeChanged();
						startCell = tableGrid = startTable = null;
					}
				});

				ed.onKeyUp.add(function(ed, e) {
					cleanup();
				});

				// Add context menu
				if (ed && ed.plugins.contextmenu) {
					ed.plugins.contextmenu.onContextMenu.add(function(th, m, e) {
						var sm, se = ed.selection, el = se.getNode() || ed.getBody();

						if (ed.dom.getParent(e, 'td') || ed.dom.getParent(e, 'th') || ed.dom.select('td.mceSelected,th.mceSelected').length) {
							m.removeAll();

							if (el.nodeName == 'A' && !ed.dom.getAttrib(el, 'name')) {
								m.add({title : 'advanced.link_desc', icon : 'link', cmd : ed.plugins.advlink ? 'mceAdvLink' : 'mceLink', ui : true});
								m.add({title : 'advanced.unlink_desc', icon : 'unlink', cmd : 'UnLink'});
								m.addSeparator();
							}

							if (el.nodeName == 'IMG' && el.className.indexOf('mceItem') == -1) {
								m.add({title : 'advanced.image_desc', icon : 'image', cmd : ed.plugins.advimage ? 'mceAdvImage' : 'mceImage', ui : true});
								m.addSeparator();
							}

							m.add({title : 'table.desc', icon : 'table', cmd : 'mceInsertTable', value : {action : 'insert'}});
							m.add({title : 'table.props_desc', icon : 'table_props', cmd : 'mceInsertTable'});
							m.add({title : 'table.del', icon : 'delete_table', cmd : 'mceTableDelete'});
							m.addSeparator();

							// Cell menu
							sm = m.addMenu({title : 'table.cell'});
							sm.add({title : 'table.cell_desc', icon : 'cell_props', cmd : 'mceTableCellProps'});
							sm.add({title : 'table.split_cells_desc', icon : 'split_cells', cmd : 'mceTableSplitCells'});
							sm.add({title : 'table.merge_cells_desc', icon : 'merge_cells', cmd : 'mceTableMergeCells'});

							// Row menu
							sm = m.addMenu({title : 'table.row'});
							sm.add({title : 'table.row_desc', icon : 'row_props', cmd : 'mceTableRowProps'});
							sm.add({title : 'table.row_before_desc', icon : 'row_before', cmd : 'mceTableInsertRowBefore'});
							sm.add({title : 'table.row_after_desc', icon : 'row_after', cmd : 'mceTableInsertRowAfter'});
							sm.add({title : 'table.delete_row_desc', icon : 'delete_row', cmd : 'mceTableDeleteRow'});
							sm.addSeparator();
							sm.add({title : 'table.cut_row_desc', icon : 'cut', cmd : 'mceTableCutRow'});
							sm.add({title : 'table.copy_row_desc', icon : 'copy', cmd : 'mceTableCopyRow'});
							sm.add({title : 'table.paste_row_before_desc', icon : 'paste', cmd : 'mceTablePasteRowBefore'}).setDisabled(!clipboardRows);
							sm.add({title : 'table.paste_row_after_desc', icon : 'paste', cmd : 'mceTablePasteRowAfter'}).setDisabled(!clipboardRows);

							// Column menu
							sm = m.addMenu({title : 'table.col'});
							sm.add({title : 'table.col_before_desc', icon : 'col_before', cmd : 'mceTableInsertColBefore'});
							sm.add({title : 'table.col_after_desc', icon : 'col_after', cmd : 'mceTableInsertColAfter'});
							sm.add({title : 'table.delete_col_desc', icon : 'delete_col', cmd : 'mceTableDeleteCol'});
						} else
							m.add({title : 'table.desc', icon : 'table', cmd : 'mceInsertTable'});
					});
				}

				// Fixes an issue on Gecko where it's impossible to place the caret behind a table
				// This fix will force a paragraph element after the table but only when the forced_root_block setting is enabled
				if (!tinymce.isIE) {
					function fixTableCaretPos() {
						var last;

						// Skip empty text nodes form the end
						for (last = ed.getBody().lastChild; last && last.nodeType == 3 && !last.nodeValue.length; last = last.previousSibling) ;

						if (last && last.nodeName == 'TABLE')
							ed.dom.add(ed.getBody(), 'p', null, '<br mce_bogus="1" />');
					};

					// Fixes an bug where it's impossible to place the caret before a table in Gecko
					// this fix solves it by detecting when the caret is at the beginning of such a table
					// and then manually moves the caret infront of the table
					if (tinymce.isGecko) {
						ed.onKeyDown.add(function(ed, e) {
							var rng, table, dom = ed.dom;

							// On gecko it's not possible to place the caret before a table
							if (e.keyCode == 37 || e.keyCode == 38) {
								rng = ed.selection.getRng();
								table = dom.getParent(rng.startContainer, 'table');

								if (table && ed.getBody().firstChild == table) {
									if (isAtStart(rng, table)) {
										rng = dom.createRng();

										rng.setStartBefore(table);
										rng.setEndBefore(table);

										ed.selection.setRng(rng);

										e.preventDefault();
									}
								}
							}
						});
					}

					ed.onKeyUp.add(fixTableCaretPos);
					ed.onSetContent.add(fixTableCaretPos);
					ed.onVisualAid.add(fixTableCaretPos);

					ed.onPreProcess.add(function(ed, o) {
						var last = o.node.lastChild;

						if (last && last.childNodes.length == 1 && last.firstChild.nodeName == 'BR')
							ed.dom.remove(last);
					});

					fixTableCaretPos();
				}
			});

			// Register action commands
			each({
				mceTableSplitCells : function(grid) {
					grid.split();
				},

				mceTableMergeCells : function(grid) {
					var rowSpan, colSpan, cell;

					cell = ed.dom.getParent(ed.selection.getNode(), 'th,td');
					if (cell) {
						rowSpan = cell.rowSpan;
						colSpan = cell.colSpan;
					}

					if (!ed.dom.select('td.mceSelected,th.mceSelected').length) {
						winMan.open({
							url : url + '/merge_cells.htm',
							width : 240 + parseInt(ed.getLang('table.merge_cells_delta_width', 0)),
							height : 110 + parseInt(ed.getLang('table.merge_cells_delta_height', 0)),
							inline : 1
						}, {
							rows : rowSpan,
							cols : colSpan,
							onaction : function(data) {
								grid.merge(cell, data.cols, data.rows);
							},
							plugin_url : url
						});
					} else
						grid.merge();
				},

				mceTableInsertRowBefore : function(grid) {
					grid.insertRow(true);
				},

				mceTableInsertRowAfter : function(grid) {
					grid.insertRow();
				},

				mceTableInsertColBefore : function(grid) {
					grid.insertCol(true);
				},

				mceTableInsertColAfter : function(grid) {
					grid.insertCol();
				},

				mceTableDeleteCol : function(grid) {
					grid.deleteCols();
				},

				mceTableDeleteRow : function(grid) {
					grid.deleteRows();
				},

				mceTableCutRow : function(grid) {
					clipboardRows = grid.cutRows();
				},

				mceTableCopyRow : function(grid) {
					clipboardRows = grid.copyRows();
				},

				mceTablePasteRowBefore : function(grid) {
					grid.pasteRows(clipboardRows, true);
				},

				mceTablePasteRowAfter : function(grid) {
					grid.pasteRows(clipboardRows);
				},

				mceTableDelete : function(grid) {
					grid.deleteTable();
				}
			}, function(func, name) {
				ed.addCommand(name, function() {
					var grid = createTableGrid();

					if (grid) {
						func(grid);
						ed.execCommand('mceRepaint');
						cleanup();
					}
				});
			});

			// Register dialog commands
			each({
				mceInsertTable : function(val) {
					winMan.open({
						url : url + '/table.htm',
						width : 400 + parseInt(ed.getLang('table.table_delta_width', 0)),
						height : 320 + parseInt(ed.getLang('table.table_delta_height', 0)),
						inline : 1
					}, {
						plugin_url : url,
						action : val ? val.action : 0
					});
				},

				mceTableRowProps : function() {
					winMan.open({
						url : url + '/row.htm',
						width : 400 + parseInt(ed.getLang('table.rowprops_delta_width', 0)),
						height : 295 + parseInt(ed.getLang('table.rowprops_delta_height', 0)),
						inline : 1
					}, {
						plugin_url : url
					});
				},

				mceTableCellProps : function() {
					winMan.open({
						url : url + '/cell.htm',
						width : 400 + parseInt(ed.getLang('table.cellprops_delta_width', 0)),
						height : 295 + parseInt(ed.getLang('table.cellprops_delta_height', 0)),
						inline : 1
					}, {
						plugin_url : url
					});
				}
			}, function(func, name) {
				ed.addCommand(name, function(ui, val) {
					func(val);
				});
			});
		}
	});

	// Register plugin
	tinymce.PluginManager.add('table', tinymce.plugins.TablePlugin);
})(tinymce);