Aleksander Machniak
2012-07-10 2b21b97ef0b13c958ed53c7adf10f02f6c4c434f
program/js/tiny_mce/plugins/table/editor_plugin_src.js
@@ -25,6 +25,10 @@
      return elm.innerHTML.replace(/<(br|img|object|embed|input|textarea)[^>]*>/gi, '-').replace(/<[^>]+>/g, '').length == 0;
   };
   function getSpanVal(td, name) {
      return parseInt(td.getAttribute(name) || 1);
   }
   /**
    * Table Grid class.
    */
@@ -98,10 +102,6 @@
         row = grid[y];
         if (row)
            return row[x];
      };
      function getSpanVal(td, name) {
         return parseInt(td.getAttribute(name) || 1);
      };
      function setSpanVal(td, name, val) {
@@ -287,6 +287,21 @@
            endX = startX + (cols - 1);
            endY = startY + (rows - 1);
         } else {
            startPos = endPos = null;
            // Calculate start/end pos by checking for selected cells in grid works better with context menu
            each(grid, function(row, y) {
               each(row, function(cell, x) {
                  if (isCellSelected(cell)) {
                     if (!startPos) {
                        startPos = {x: x, y: y};
                     }
                     endPos = {x: x, y: y};
                  }
               });
            });
            // Use selection
            startX = startPos.x;
            startY = startPos.y;
@@ -599,6 +614,9 @@
            else
               dom.insertAfter(row, targetRow);
         });
         // Remove current selection
         dom.removeClass(dom.select('td.mceSelected,th.mceSelected'), 'mceSelected');
      };
      function getPos(target) {
@@ -752,7 +770,7 @@
   tinymce.create('tinymce.plugins.TablePlugin', {
      init : function(ed, url) {
         var winMan, clipboardRows;
         var winMan, clipboardRows, hasCellSelection = true; // Might be selected cells on reload
         function createTableGrid(node) {
            var selection = ed.selection, tblElm = ed.dom.getParent(node || selection.getNode(), 'table');
@@ -764,7 +782,11 @@
         function cleanup() {
            // Restore selection possibilities
            ed.getBody().style.webkitUserSelect = '';
            ed.dom.removeClass(ed.dom.select('td.mceSelected,th.mceSelected'), 'mceSelected');
            if (hasCellSelection) {
               ed.dom.removeClass(ed.dom.select('td.mceSelected,th.mceSelected'), 'mceSelected');
               hasCellSelection = false;
            }
         };
         // Register buttons
@@ -873,6 +895,7 @@
                     }
                     tableGrid.setEndCell(target);
                     hasCellSelection = true;
                  }
                  // Remove current selection
@@ -923,7 +946,7 @@
                           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');
@@ -931,6 +954,8 @@
                     rng = dom.createRng();
                     node = selectedCells[0];
                     endNode = selectedCells[selectedCells.length - 1];
                     rng.setStartBefore(node);
                     rng.setEndAfter(node);
                     setPoint(node, 1);
                     walker = new tinymce.dom.TreeWalker(node, dom.getParent(selectedCells[0], 'table'));
@@ -957,6 +982,60 @@
            ed.onKeyUp.add(function(ed, e) {
               cleanup();
            });
            ed.onKeyDown.add(function (ed, e) {
               fixTableCellSelection(ed);
            });
            ed.onMouseDown.add(function (ed, e) {
               if (e.button != 2) {
                  fixTableCellSelection(ed);
               }
            });
            function tableCellSelected(ed, rng, n, currentCell) {
               // The decision of when a table cell is selected is somewhat involved.  The fact that this code is
               // required is actually a pointer to the root cause of this bug. A cell is selected when the start
               // and end offsets are 0, the start container is a text, and the selection node is either a TR (most cases)
               // or the parent of the table (in the case of the selection containing the last cell of a table).
               var TEXT_NODE = 3, table = ed.dom.getParent(rng.startContainer, 'TABLE'),
               tableParent, allOfCellSelected, tableCellSelection;
               if (table)
               tableParent = table.parentNode;
               allOfCellSelected =rng.startContainer.nodeType == TEXT_NODE &&
                  rng.startOffset == 0 &&
                  rng.endOffset == 0 &&
                  currentCell &&
                  (n.nodeName=="TR" || n==tableParent);
               tableCellSelection = (n.nodeName=="TD"||n.nodeName=="TH")&& !currentCell;
               return  allOfCellSelected || tableCellSelection;
               // return false;
            }
            // this nasty hack is here to work around some WebKit selection bugs.
            function fixTableCellSelection(ed) {
               if (!tinymce.isWebKit)
                  return;
               var rng = ed.selection.getRng();
               var n = ed.selection.getNode();
               var currentCell = ed.dom.getParent(rng.startContainer, 'TD,TH');
               if (!tableCellSelected(ed, rng, n, currentCell))
                  return;
                  if (!currentCell) {
                     currentCell=n;
                  }
               // Get the very last node inside the table cell
               var end = currentCell.lastChild;
               while (end.lastChild)
                  end = end.lastChild;
               // Select the entire table cell. Nothing outside of the table cell should be selected.
               rng.setEnd(end, end.nodeValue.length);
               ed.selection.setRng(rng);
            }
            ed.plugins.table.fixTableCellSelection=fixTableCellSelection;
            // Add context menu
            if (ed && ed.plugins.contextmenu) {
@@ -1010,60 +1089,228 @@
               });
            }
            // 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;
            // Fix to allow navigating up and down in a table in WebKit browsers.
            if (tinymce.isWebKit) {
               function moveSelection(ed, e) {
                  var VK = tinymce.VK;
                  var key = e.keyCode;
                  // Skip empty text nodes form the end
                  for (last = ed.getBody().lastChild; last && last.nodeType == 3 && !last.nodeValue.length; last = last.previousSibling) ;
                  function handle(upBool, sourceNode, event) {
                     var siblingDirection = upBool ? 'previousSibling' : 'nextSibling';
                     var currentRow = ed.dom.getParent(sourceNode, 'tr');
                     var siblingRow = currentRow[siblingDirection];
                  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();
                     if (siblingRow) {
                        moveCursorToRow(ed, sourceNode, siblingRow, upBool);
                        tinymce.dom.Event.cancel(event);
                        return true;
                     } else {
                        var tableNode = ed.dom.getParent(currentRow, 'table');
                        var middleNode = currentRow.parentNode;
                        var parentNodeName = middleNode.nodeName.toLowerCase();
                        if (parentNodeName === 'tbody' || parentNodeName === (upBool ? 'tfoot' : 'thead')) {
                           var targetParent = getTargetParent(upBool, tableNode, middleNode, 'tbody');
                           if (targetParent !== null) {
                              return moveToRowInTarget(upBool, targetParent, sourceNode, event);
                           }
                        }
                        return escapeTable(upBool, currentRow, siblingDirection, tableNode, event);
                     }
                  });
                  }
                  function getTargetParent(upBool, topNode, secondNode, nodeName) {
                     var tbodies = ed.dom.select('>' + nodeName, topNode);
                     var position = tbodies.indexOf(secondNode);
                     if (upBool && position === 0 || !upBool && position === tbodies.length - 1) {
                        return getFirstHeadOrFoot(upBool, topNode);
                     } else if (position === -1) {
                        var topOrBottom = secondNode.tagName.toLowerCase() === 'thead' ? 0 : tbodies.length - 1;
                        return tbodies[topOrBottom];
                     } else {
                        return tbodies[position + (upBool ? -1 : 1)];
                     }
                  }
                  function getFirstHeadOrFoot(upBool, parent) {
                     var tagName = upBool ? 'thead' : 'tfoot';
                     var headOrFoot = ed.dom.select('>' + tagName, parent);
                     return headOrFoot.length !== 0 ? headOrFoot[0] : null;
                  }
                  function moveToRowInTarget(upBool, targetParent, sourceNode, event) {
                     var targetRow = getChildForDirection(targetParent, upBool);
                     targetRow && moveCursorToRow(ed, sourceNode, targetRow, upBool);
                     tinymce.dom.Event.cancel(event);
                     return true;
                  }
                  function escapeTable(upBool, currentRow, siblingDirection, table, event) {
                     var tableSibling = table[siblingDirection];
                     if (tableSibling) {
                        moveCursorToStartOfElement(tableSibling);
                        return true;
                     } else {
                        var parentCell = ed.dom.getParent(table, 'td,th');
                        if (parentCell) {
                           return handle(upBool, parentCell, event);
                        } else {
                           var backUpSibling = getChildForDirection(currentRow, !upBool);
                           moveCursorToStartOfElement(backUpSibling);
                           return tinymce.dom.Event.cancel(event);
                        }
                     }
                  }
                  function getChildForDirection(parent, up) {
                     var child =  parent && parent[up ? 'lastChild' : 'firstChild'];
                     // BR is not a valid table child to return in this case we return the table cell
                     return child && child.nodeName === 'BR' ? ed.dom.getParent(child, 'td,th') : child;
                  }
                  function moveCursorToStartOfElement(n) {
                     ed.selection.setCursorLocation(n, 0);
                  }
                  function isVerticalMovement() {
                     return key == VK.UP || key == VK.DOWN;
                  }
                  function isInTable(ed) {
                     var node = ed.selection.getNode();
                     var currentRow = ed.dom.getParent(node, 'tr');
                     return currentRow !== null;
                  }
                  function columnIndex(column) {
                     var colIndex = 0;
                     var c = column;
                     while (c.previousSibling) {
                        c = c.previousSibling;
                        colIndex = colIndex + getSpanVal(c, "colspan");
                     }
                     return colIndex;
                  }
                  function findColumn(rowElement, columnIndex) {
                     var c = 0;
                     var r = 0;
                     each(rowElement.children, function(cell, i) {
                        c = c + getSpanVal(cell, "colspan");
                        r = i;
                        if (c > columnIndex)
                           return false;
                     });
                     return r;
                  }
                  function moveCursorToRow(ed, node, row, upBool) {
                     var srcColumnIndex = columnIndex(ed.dom.getParent(node, 'td,th'));
                     var tgtColumnIndex = findColumn(row, srcColumnIndex);
                     var tgtNode = row.childNodes[tgtColumnIndex];
                     var rowCellTarget = getChildForDirection(tgtNode, upBool);
                     moveCursorToStartOfElement(rowCellTarget || tgtNode);
                  }
                  function shouldFixCaret(preBrowserNode) {
                     var newNode = ed.selection.getNode();
                     var newParent = ed.dom.getParent(newNode, 'td,th');
                     var oldParent = ed.dom.getParent(preBrowserNode, 'td,th');
                     return newParent && newParent !== oldParent && checkSameParentTable(newParent, oldParent)
                  }
                  function checkSameParentTable(nodeOne, NodeTwo) {
                     return ed.dom.getParent(nodeOne, 'TABLE') === ed.dom.getParent(NodeTwo, 'TABLE');
                  }
                  if (isVerticalMovement() && isInTable(ed)) {
                     var preBrowserNode = ed.selection.getNode();
                     setTimeout(function() {
                        if (shouldFixCaret(preBrowserNode)) {
                           handle(!e.shiftKey && key === VK.UP, preBrowserNode, e);
                        }
                     }, 0);
                  }
               }
               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();
               ed.onKeyDown.add(moveSelection);
            }
            // 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
            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') {
                  if (ed.settings.forced_root_block)
                     ed.dom.add(ed.getBody(), ed.settings.forced_root_block, null, tinymce.isIE ? '&nbsp;' : '<br data-mce-bogus="1" />');
                  else
                     ed.dom.add(ed.getBody(), 'br', {'data-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.nodeName == "BR" || (last.childNodes.length == 1 && (last.firstChild.nodeName == 'BR' || last.firstChild.nodeValue == '\u00a0'))) && last.previousSibling && last.previousSibling.nodeName == "TABLE") {
                  ed.dom.remove(last);
               }
            });
            /**
             * Fixes bug in Gecko where shift-enter in table cell does not place caret on new line
             */
            if (tinymce.isGecko) {
               ed.onKeyDown.add(function(ed, e) {
                  if (e.keyCode === tinymce.VK.ENTER && e.shiftKey) {
                     var node = ed.selection.getRng().startContainer;
                     var tableCell = dom.getParent(node, 'td,th');
                     if (tableCell) {
                        var zeroSizedNbsp = ed.getDoc().createTextNode("\uFEFF");
                        dom.insertAfter(zeroSizedNbsp, node);
                     }
                  }
               });
            }
            fixTableCaretPos();
            ed.startContent = ed.getContent({format : 'raw'});
         });
         // Register action commands
@@ -1199,4 +1446,4 @@
   // Register plugin
   tinymce.PluginManager.add('table', tinymce.plugins.TablePlugin);
})(tinymce);
})(tinymce);