| | |
| | | var tinymce = {
|
| | | majorVersion : '3',
|
| | |
|
| | | minorVersion : '5.4.1',
|
| | | minorVersion : '5.10',
|
| | |
|
| | | releaseDate : '2012-06-24',
|
| | | releaseDate : '2013-10-24',
|
| | |
|
| | | _init : function() {
|
| | | var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v;
|
| | |
|
| | | t.isIE11 = ua.indexOf('Trident/') != -1 && (ua.indexOf('rv:') != -1 || na.appName.indexOf('Netscape') != -1);
|
| | |
|
| | | t.isOpera = win.opera && opera.buildNumber;
|
| | |
|
| | | t.isWebKit = /WebKit/.test(ua);
|
| | |
|
| | | t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(na.appName);
|
| | | t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(na.appName) || t.isIE11;
|
| | |
|
| | | t.isIE6 = t.isIE && /MSIE [56]/.test(ua);
|
| | |
|
| | |
| | |
|
| | | t.isIE9 = t.isIE && /MSIE [9]/.test(ua);
|
| | |
|
| | | t.isGecko = !t.isWebKit && /Gecko/.test(ua);
|
| | | t.isGecko = !t.isWebKit && !t.isIE11 && /Gecko/.test(ua);
|
| | |
|
| | | t.isMac = ua.indexOf('Mac') != -1;
|
| | |
|
| | |
| | | if (!t)
|
| | | return o !== undef;
|
| | |
|
| | | if (t == 'array' && (o.hasOwnProperty && o instanceof Array))
|
| | | if (t == 'array' && tinymce.isArray(o))
|
| | | return true;
|
| | |
|
| | | return typeof(o) == t;
|
| | | },
|
| | |
|
| | | isArray: Array.isArray || function(obj) {
|
| | | return Object.prototype.toString.call(obj) === "[object Array]";
|
| | | },
|
| | |
|
| | | makeMap : function(items, delim, map) {
|
| | |
| | | }
|
| | |
|
| | | if (t == 'object') {
|
| | | if (o.hasOwnProperty && o instanceof Array) {
|
| | | if (o.hasOwnProperty && Object.prototype.toString.call(o) === '[object Array]') {
|
| | | for (i=0, v = '['; i<o.length; i++)
|
| | | v += (i > 0 ? ',' : '') + serialize(o[i], quote);
|
| | |
|
| | |
| | | },
|
| | |
|
| | | metaKeyPressed: function(e) {
|
| | | return tinymce.isMac ? e.metaKey : e.ctrlKey;
|
| | | // Check if ctrl or meta key is pressed also check if alt is false for Polish users
|
| | | return tinymce.isMac ? e.metaKey : e.ctrlKey && !e.altKey;
|
| | | }
|
| | | };
|
| | | })(tinymce);
|
| | |
|
| | | tinymce.util.Quirks = function(editor) {
|
| | | var VK = tinymce.VK, BACKSPACE = VK.BACKSPACE, DELETE = VK.DELETE, dom = editor.dom, selection = editor.selection, settings = editor.settings;
|
| | | var VK = tinymce.VK, BACKSPACE = VK.BACKSPACE, DELETE = VK.DELETE, dom = editor.dom, selection = editor.selection,
|
| | | settings = editor.settings, parser = editor.parser, serializer = editor.serializer, each = tinymce.each;
|
| | |
|
| | | function setEditorCommandState(cmd, state) {
|
| | | try {
|
| | |
| | | return documentMode ? documentMode : 6;
|
| | | };
|
| | |
|
| | | function isDefaultPrevented(e) {
|
| | | return e.isDefaultPrevented();
|
| | | };
|
| | |
|
| | | function cleanupStylesWhenDeleting() {
|
| | | function removeMergedFormatSpans(isDelete) {
|
| | | var rng, blockElm, node, clonedSpan;
|
| | | var rng, blockElm, wrapperElm, bookmark, container, offset, elm;
|
| | |
|
| | | function isAtStartOrEndOfElm() {
|
| | | if (container.nodeType == 3) {
|
| | | if (isDelete && offset == container.length) {
|
| | | return true;
|
| | | }
|
| | |
|
| | | if (!isDelete && offset === 0) {
|
| | | return true;
|
| | | }
|
| | | }
|
| | | }
|
| | |
|
| | | rng = selection.getRng();
|
| | | var tmpRng = [rng.startContainer, rng.startOffset, rng.endContainer, rng.endOffset];
|
| | |
|
| | | // Find root block
|
| | | blockElm = dom.getParent(rng.startContainer, dom.isBlock);
|
| | | if (!rng.collapsed) {
|
| | | isDelete = true;
|
| | | }
|
| | |
|
| | | // On delete clone the root span of the next block element
|
| | | if (isDelete)
|
| | | blockElm = dom.getNext(blockElm, dom.isBlock);
|
| | | container = rng[(isDelete ? 'start' : 'end') + 'Container'];
|
| | | offset = rng[(isDelete ? 'start' : 'end') + 'Offset'];
|
| | |
|
| | | // Locate root span element and clone it since it would otherwise get merged by the "apple-style-span" on delete/backspace
|
| | | if (blockElm) {
|
| | | node = blockElm.firstChild;
|
| | | if (container.nodeType == 3) {
|
| | | blockElm = dom.getParent(rng.startContainer, dom.isBlock);
|
| | |
|
| | | // Ignore empty text nodes
|
| | | while (node && node.nodeType == 3 && node.nodeValue.length === 0)
|
| | | node = node.nextSibling;
|
| | | // On delete clone the root span of the next block element
|
| | | if (isDelete) {
|
| | | blockElm = dom.getNext(blockElm, dom.isBlock);
|
| | | }
|
| | |
|
| | | if (node && node.nodeName === 'SPAN') {
|
| | | clonedSpan = node.cloneNode(false);
|
| | | if (blockElm && (isAtStartOrEndOfElm() || !rng.collapsed)) {
|
| | | // Wrap children of block in a EM and let WebKit stick is
|
| | | // runtime styles junk into that EM
|
| | | wrapperElm = dom.create('em', {'id': '__mceDel'});
|
| | |
|
| | | each(tinymce.grep(blockElm.childNodes), function(node) {
|
| | | wrapperElm.appendChild(node);
|
| | | });
|
| | |
|
| | | blockElm.appendChild(wrapperElm);
|
| | | }
|
| | | }
|
| | |
|
| | | // Do the backspace/delete action
|
| | | rng = dom.createRng();
|
| | | rng.setStart(tmpRng[0], tmpRng[1]);
|
| | | rng.setEnd(tmpRng[2], tmpRng[3]);
|
| | | selection.setRng(rng);
|
| | | editor.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null);
|
| | |
|
| | | // Find all odd apple-style-spans
|
| | | blockElm = dom.getParent(rng.startContainer, dom.isBlock);
|
| | | tinymce.each(dom.select('span.Apple-style-span,font.Apple-style-span', blockElm), function(span) {
|
| | | var bm = selection.getBookmark();
|
| | | // Remove temp wrapper element
|
| | | if (wrapperElm) {
|
| | | bookmark = selection.getBookmark();
|
| | |
|
| | | if (clonedSpan) {
|
| | | dom.replace(clonedSpan.cloneNode(false), span, true);
|
| | | } else {
|
| | | dom.remove(span, true);
|
| | | while (elm = dom.get('__mceDel')) {
|
| | | dom.remove(elm, true);
|
| | | }
|
| | |
|
| | | // Restore the selection
|
| | | selection.moveToBookmark(bm);
|
| | | });
|
| | | };
|
| | | selection.moveToBookmark(bookmark);
|
| | | }
|
| | | }
|
| | |
|
| | | editor.onKeyDown.add(function(editor, e) {
|
| | | var isDelete;
|
| | |
|
| | | isDelete = e.keyCode == DELETE;
|
| | | if (!e.isDefaultPrevented() && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) {
|
| | | if (!isDefaultPrevented(e) && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) {
|
| | | e.preventDefault();
|
| | | removeMergedFormatSpans(isDelete);
|
| | | }
|
| | |
| | | var allRng = dom.createRng();
|
| | | allRng.selectNode(editor.getBody());
|
| | |
|
| | | var allSelection = serializeRng(allRng);//console.log(selection, "----", allSelection);
|
| | | var allSelection = serializeRng(allRng);
|
| | | return selection === allSelection;
|
| | | }
|
| | |
|
| | |
| | | var keyCode = e.keyCode, isCollapsed;
|
| | |
|
| | | // Empty the editor if it's needed for example backspace at <p><b>|</b></p>
|
| | | if (!e.isDefaultPrevented() && (keyCode == DELETE || keyCode == BACKSPACE)) {
|
| | | if (!isDefaultPrevented(e) && (keyCode == DELETE || keyCode == BACKSPACE)) {
|
| | | isCollapsed = editor.selection.isCollapsed();
|
| | |
|
| | | // Selection is collapsed but the editor isn't empty
|
| | |
| | |
|
| | | function selectAll() {
|
| | | editor.onKeyDown.add(function(editor, e) {
|
| | | if (e.keyCode == 65 && VK.metaKeyPressed(e)) {
|
| | | if (!isDefaultPrevented(e) && e.keyCode == 65 && VK.metaKeyPressed(e)) {
|
| | | e.preventDefault();
|
| | | editor.execCommand('SelectAll');
|
| | | }
|
| | |
| | |
|
| | | function removeHrOnBackspace() {
|
| | | editor.onKeyDown.add(function(editor, e) {
|
| | | if (!e.isDefaultPrevented() && e.keyCode === BACKSPACE) {
|
| | | if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) {
|
| | | if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) {
|
| | | var node = selection.getNode();
|
| | | var previousSibling = node.previousSibling;
|
| | |
| | | // wouldn't get proper focus if the user clicked on the HTML element
|
| | | if (!Range.prototype.getClientRects) { // Detect getClientRects got introduced in FF 4
|
| | | editor.onMouseDown.add(function(editor, e) {
|
| | | if (e.target.nodeName === "HTML") {
|
| | | if (!isDefaultPrevented(e) && e.target.nodeName === "HTML") {
|
| | | var body = editor.getBody();
|
| | |
|
| | | // Blur the body it's focused but not correctly focused
|
| | |
| | | if (target !== editor.getBody()) {
|
| | | dom.setAttrib(target, "style", null);
|
| | |
|
| | | tinymce.each(template, function(attr) {
|
| | | each(template, function(attr) {
|
| | | target.setAttributeNode(attr.cloneNode(true));
|
| | | });
|
| | | }
|
| | |
| | | }
|
| | |
|
| | | function isSelectionAcrossElements() {
|
| | | return !selection.isCollapsed() && selection.getStart() != selection.getEnd();
|
| | | return !selection.isCollapsed() && dom.getParent(selection.getStart(), dom.isBlock) != dom.getParent(selection.getEnd(), dom.isBlock);
|
| | | }
|
| | |
|
| | | function blockEvent(editor, e) {
|
| | |
| | | editor.onKeyPress.add(function(editor, e) {
|
| | | var applyAttributes;
|
| | |
|
| | | if ((e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) {
|
| | | if (!isDefaultPrevented(e) && (e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) {
|
| | | applyAttributes = getAttributeApplyFunction();
|
| | | editor.getDoc().execCommand('delete', false, null);
|
| | | applyAttributes();
|
| | |
| | | dom.bind(editor.getDoc(), 'cut', function(e) {
|
| | | var applyAttributes;
|
| | |
|
| | | if (isSelectionAcrossElements()) {
|
| | | if (!isDefaultPrevented(e) && isSelectionAcrossElements()) {
|
| | | applyAttributes = getAttributeApplyFunction();
|
| | | editor.onKeyUp.addToTop(blockEvent);
|
| | |
|
| | |
| | |
|
| | | function disableBackspaceIntoATable() {
|
| | | editor.onKeyDown.add(function(editor, e) {
|
| | | if (!e.isDefaultPrevented() && e.keyCode === BACKSPACE) {
|
| | | if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) {
|
| | | if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) {
|
| | | var previousSibling = selection.getNode().previousSibling;
|
| | | if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "table") {
|
| | |
| | | dom.addClass(editor.getBody(), 'mceHideBrInPre');
|
| | |
|
| | | // Adds a \n before all BR elements in PRE to get them visual
|
| | | editor.parser.addNodeFilter('pre', function(nodes, name) {
|
| | | parser.addNodeFilter('pre', function(nodes, name) {
|
| | | var i = nodes.length, brNodes, j, brElm, sibling;
|
| | |
|
| | | while (i--) {
|
| | |
| | | });
|
| | |
|
| | | // Removes any \n before BR elements in PRE since other browsers and in contentEditable=false mode they will be visible
|
| | | editor.serializer.addNodeFilter('pre', function(nodes, name) {
|
| | | serializer.addNodeFilter('pre', function(nodes, name) {
|
| | | var i = nodes.length, brNodes, j, brElm, sibling;
|
| | |
|
| | | while (i--) {
|
| | |
| | | var isDelete, rng, container, offset, brElm, sibling, collapsed;
|
| | |
|
| | | isDelete = e.keyCode == DELETE;
|
| | | if (!e.isDefaultPrevented() && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) {
|
| | | if (!isDefaultPrevented(e) && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) {
|
| | | rng = selection.getRng();
|
| | | container = rng.startContainer;
|
| | | offset = rng.startOffset;
|
| | |
| | | // Override delete if the start container is a text node and is at the beginning of text or
|
| | | // just before/after the last character to be deleted in collapsed mode
|
| | | if (container.nodeType == 3 && container.nodeValue.length > 0 && ((offset === 0 && !collapsed) || (collapsed && offset === (isDelete ? 0 : 1)))) {
|
| | | // Edge case when deleting <p><b><img> |x</b></p>
|
| | | sibling = container.previousSibling;
|
| | | if (sibling && sibling.nodeName == "IMG") {
|
| | | return;
|
| | | }
|
| | |
|
| | | nonEmptyElements = editor.schema.getNonEmptyElements();
|
| | |
|
| | | // Prevent default logic since it's broken
|
| | |
| | | editor.onKeyDown.add(function(editor, e) {
|
| | | var rng, container, offset, root, parent;
|
| | |
|
| | | if (e.isDefaultPrevented() || e.keyCode != VK.BACKSPACE) {
|
| | | if (isDefaultPrevented(e) || e.keyCode != VK.BACKSPACE) {
|
| | | return;
|
| | | }
|
| | |
|
| | |
| | | editor.formatter.toggle('blockquote', null, parent);
|
| | |
|
| | | // Move the caret to the beginning of container
|
| | | rng = dom.createRng();
|
| | | rng.setStart(container, 0);
|
| | | rng.setEnd(container, 0);
|
| | | selection.setRng(rng);
|
| | | selection.collapse(false);
|
| | | }
|
| | | });
|
| | | };
|
| | |
| | |
|
| | | function addBrAfterLastLinks() {
|
| | | function fixLinks(editor, o) {
|
| | | tinymce.each(dom.select('a'), function(node) {
|
| | | each(dom.select('a'), function(node) {
|
| | | var parentNode = node.parentNode, root = dom.getRoot();
|
| | |
|
| | | if (parentNode.lastChild === node) {
|
| | |
| | | editor.onKeyDown.add(function(editor, e) {
|
| | | var rng;
|
| | |
|
| | | if (!e.isDefaultPrevented() && e.keyCode == BACKSPACE) {
|
| | | if (!isDefaultPrevented(e) && e.keyCode == BACKSPACE) {
|
| | | rng = editor.getDoc().selection.createRange();
|
| | | if (rng && rng.item) {
|
| | | e.preventDefault();
|
| | |
| | | // IE10+
|
| | | if (getDocumentMode() >= 10) {
|
| | | emptyBlocksCSS = '';
|
| | | tinymce.each('p div h1 h2 h3 h4 h5 h6'.split(' '), function(name, i) {
|
| | | each('p div h1 h2 h3 h4 h5 h6'.split(' '), function(name, i) {
|
| | | emptyBlocksCSS += (i > 0 ? ',' : '') + name + ':empty';
|
| | | });
|
| | |
|
| | |
| | | };
|
| | |
|
| | | function fakeImageResize() {
|
| | | var mouseDownImg, startX, startY, startW, startH;
|
| | | var selectedElmX, selectedElmY, selectedElm, selectedElmGhost, selectedHandle, startX, startY, startW, startH, ratio,
|
| | | resizeHandles, width, height, rootDocument = document, editableDoc = editor.getDoc();
|
| | |
|
| | | if (!settings.object_resizing || settings.webkit_fake_resize === false) {
|
| | | return;
|
| | | }
|
| | |
|
| | | editor.contentStyles.push('.mceResizeImages img {cursor: se-resize !important}');
|
| | | // Try disabling object resizing if WebKit implements resizing in the future
|
| | | setEditorCommandState("enableObjectResizing", false);
|
| | |
|
| | | function resizeImage(e) {
|
| | | var deltaX, deltaY, ratio, width, height;
|
| | | // Details about each resize handle how to scale etc
|
| | | resizeHandles = {
|
| | | // Name: x multiplier, y multiplier, delta size x, delta size y
|
| | | n: [.5, 0, 0, -1],
|
| | | e: [1, .5, 1, 0],
|
| | | s: [.5, 1, 0, 1],
|
| | | w: [0, .5, -1, 0],
|
| | | nw: [0, 0, -1, -1],
|
| | | ne: [1, 0, 1, -1],
|
| | | se: [1, 1, 1, 1],
|
| | | sw : [0, 1, -1, 1]
|
| | | };
|
| | |
|
| | | if (mouseDownImg) {
|
| | | deltaX = e.screenX - startX;
|
| | | deltaY = e.screenY - startY;
|
| | | ratio = Math.max((startW + deltaX) / startW, (startH + deltaY) / startH);
|
| | | function resizeElement(e) {
|
| | | var deltaX, deltaY;
|
| | |
|
| | | // Only update styles if the user draged one pixel or more
|
| | | if (Math.abs(deltaX) > 1 || Math.abs(deltaY) > 1) {
|
| | | // Constrain proportions
|
| | | width = Math.round(startW * ratio);
|
| | | height = Math.round(startH * ratio);
|
| | | // Calc new width/height
|
| | | deltaX = e.screenX - startX;
|
| | | deltaY = e.screenY - startY;
|
| | |
|
| | | // Calc new size
|
| | | width = deltaX * selectedHandle[2] + startW;
|
| | | height = deltaY * selectedHandle[3] + startH;
|
| | |
|
| | | // Never scale down lower than 5 pixels
|
| | | width = width < 5 ? 5 : width;
|
| | | height = height < 5 ? 5 : height;
|
| | |
|
| | | // Constrain proportions when modifier key is pressed or if the nw, ne, sw, se corners are moved on an image
|
| | | if (VK.modifierPressed(e) || (selectedElm.nodeName == "IMG" && selectedHandle[2] * selectedHandle[3] !== 0)) {
|
| | | width = Math.round(height / ratio);
|
| | | height = Math.round(width * ratio);
|
| | | }
|
| | |
|
| | | // Update ghost size
|
| | | dom.setStyles(selectedElmGhost, {
|
| | | width: width,
|
| | | height: height
|
| | | });
|
| | |
|
| | | // Update ghost X position if needed
|
| | | if (selectedHandle[2] < 0 && selectedElmGhost.clientWidth <= width) {
|
| | | dom.setStyle(selectedElmGhost, 'left', selectedElmX + (startW - width));
|
| | | }
|
| | |
|
| | | // Update ghost Y position if needed
|
| | | if (selectedHandle[3] < 0 && selectedElmGhost.clientHeight <= height) {
|
| | | dom.setStyle(selectedElmGhost, 'top', selectedElmY + (startH - height));
|
| | | }
|
| | | }
|
| | |
|
| | | function endResize() {
|
| | | function setSizeProp(name, value) {
|
| | | if (value) {
|
| | | // Resize by using style or attribute
|
| | | if (mouseDownImg.style.width) {
|
| | | dom.setStyle(mouseDownImg, 'width', width);
|
| | | if (selectedElm.style[name] || !editor.schema.isValid(selectedElm.nodeName.toLowerCase(), name)) {
|
| | | dom.setStyle(selectedElm, name, value);
|
| | | } else {
|
| | | dom.setAttrib(mouseDownImg, 'width', width);
|
| | | }
|
| | |
|
| | | // Resize by using style or attribute
|
| | | if (mouseDownImg.style.height) {
|
| | | dom.setStyle(mouseDownImg, 'height', height);
|
| | | } else {
|
| | | dom.setAttrib(mouseDownImg, 'height', height);
|
| | | }
|
| | |
|
| | | if (!dom.hasClass(editor.getBody(), 'mceResizeImages')) {
|
| | | dom.addClass(editor.getBody(), 'mceResizeImages');
|
| | | dom.setAttrib(selectedElm, name, value);
|
| | | }
|
| | | }
|
| | | }
|
| | | };
|
| | |
|
| | | editor.onMouseDown.add(function(editor, e) {
|
| | | var target = e.target;
|
| | | // Set width/height properties
|
| | | setSizeProp('width', width);
|
| | | setSizeProp('height', height);
|
| | |
|
| | | if (target.nodeName == "IMG") {
|
| | | mouseDownImg = target;
|
| | | startX = e.screenX;
|
| | | startY = e.screenY;
|
| | | startW = mouseDownImg.clientWidth;
|
| | | startH = mouseDownImg.clientHeight;
|
| | | dom.bind(editor.getDoc(), 'mousemove', resizeImage);
|
| | | e.preventDefault();
|
| | | }
|
| | | });
|
| | | dom.unbind(editableDoc, 'mousemove', resizeElement);
|
| | | dom.unbind(editableDoc, 'mouseup', endResize);
|
| | |
|
| | | // Unbind events on node change and restore resize cursor
|
| | | editor.onNodeChange.add(function() {
|
| | | if (mouseDownImg) {
|
| | | mouseDownImg = null;
|
| | | dom.unbind(editor.getDoc(), 'mousemove', resizeImage);
|
| | | if (rootDocument != editableDoc) {
|
| | | dom.unbind(rootDocument, 'mousemove', resizeElement);
|
| | | dom.unbind(rootDocument, 'mouseup', endResize);
|
| | | }
|
| | |
|
| | | if (selection.getNode().nodeName == "IMG") {
|
| | | dom.addClass(editor.getBody(), 'mceResizeImages');
|
| | | // Remove ghost and update resize handle positions
|
| | | dom.remove(selectedElmGhost);
|
| | | showResizeRect(selectedElm);
|
| | | }
|
| | |
|
| | | function showResizeRect(targetElm) {
|
| | | var position, targetWidth, targetHeight;
|
| | |
|
| | | hideResizeRect();
|
| | |
|
| | | // Get position and size of target
|
| | | position = dom.getPos(targetElm);
|
| | | selectedElmX = position.x;
|
| | | selectedElmY = position.y;
|
| | | targetWidth = targetElm.offsetWidth;
|
| | | targetHeight = targetElm.offsetHeight;
|
| | |
|
| | | // Reset width/height if user selects a new image/table
|
| | | if (selectedElm != targetElm) {
|
| | | selectedElm = targetElm;
|
| | | width = height = 0;
|
| | | }
|
| | |
|
| | | each(resizeHandles, function(handle, name) {
|
| | | var handleElm;
|
| | |
|
| | | // Get existing or render resize handle
|
| | | handleElm = dom.get('mceResizeHandle' + name);
|
| | | if (!handleElm) {
|
| | | handleElm = dom.add(editableDoc.documentElement, 'div', {
|
| | | id: 'mceResizeHandle' + name,
|
| | | 'class': 'mceResizeHandle',
|
| | | style: 'cursor:' + name + '-resize; margin:0; padding:0'
|
| | | });
|
| | |
|
| | | dom.bind(handleElm, 'mousedown', function(e) {
|
| | | e.preventDefault();
|
| | |
|
| | | endResize();
|
| | |
|
| | | startX = e.screenX;
|
| | | startY = e.screenY;
|
| | | startW = selectedElm.clientWidth;
|
| | | startH = selectedElm.clientHeight;
|
| | | ratio = startH / startW;
|
| | | selectedHandle = handle;
|
| | |
|
| | | selectedElmGhost = selectedElm.cloneNode(true);
|
| | | dom.addClass(selectedElmGhost, 'mceClonedResizable');
|
| | | dom.setStyles(selectedElmGhost, {
|
| | | left: selectedElmX,
|
| | | top: selectedElmY,
|
| | | margin: 0
|
| | | });
|
| | |
|
| | | editableDoc.documentElement.appendChild(selectedElmGhost);
|
| | |
|
| | | dom.bind(editableDoc, 'mousemove', resizeElement);
|
| | | dom.bind(editableDoc, 'mouseup', endResize);
|
| | |
|
| | | if (rootDocument != editableDoc) {
|
| | | dom.bind(rootDocument, 'mousemove', resizeElement);
|
| | | dom.bind(rootDocument, 'mouseup', endResize);
|
| | | }
|
| | | });
|
| | | } else {
|
| | | dom.show(handleElm);
|
| | | }
|
| | |
|
| | | // Position element
|
| | | dom.setStyles(handleElm, {
|
| | | left: (targetWidth * handle[0] + selectedElmX) - (handleElm.offsetWidth / 2),
|
| | | top: (targetHeight * handle[1] + selectedElmY) - (handleElm.offsetHeight / 2)
|
| | | });
|
| | | });
|
| | |
|
| | | // Only add resize rectangle on WebKit and only on images
|
| | | if (!tinymce.isOpera && selectedElm.nodeName == "IMG") {
|
| | | selectedElm.setAttribute('data-mce-selected', '1');
|
| | | }
|
| | | }
|
| | |
|
| | | function hideResizeRect() {
|
| | | if (selectedElm) {
|
| | | selectedElm.removeAttribute('data-mce-selected');
|
| | | }
|
| | |
|
| | | for (var name in resizeHandles) {
|
| | | dom.hide('mceResizeHandle' + name);
|
| | | }
|
| | | }
|
| | |
|
| | | // Add CSS for resize handles, cloned element and selected
|
| | | editor.contentStyles.push(
|
| | | '.mceResizeHandle {' +
|
| | | 'position: absolute;' +
|
| | | 'border: 1px solid black;' +
|
| | | 'background: #FFF;' +
|
| | | 'width: 5px;' +
|
| | | 'height: 5px;' +
|
| | | 'z-index: 10000' +
|
| | | '}' +
|
| | | '.mceResizeHandle:hover {' +
|
| | | 'background: #000' +
|
| | | '}' +
|
| | | 'img[data-mce-selected] {' +
|
| | | 'outline: 1px solid black' +
|
| | | '}' +
|
| | | 'img.mceClonedResizable, table.mceClonedResizable {' +
|
| | | 'position: absolute;' +
|
| | | 'outline: 1px dashed black;' +
|
| | | 'opacity: .5;' +
|
| | | 'z-index: 10000' +
|
| | | '}'
|
| | | );
|
| | |
|
| | | function updateResizeRect() {
|
| | | var controlElm = dom.getParent(selection.getNode(), 'table,img');
|
| | |
|
| | | // Remove data-mce-selected from all elements since they might have been copied using Ctrl+c/v
|
| | | each(dom.select('img[data-mce-selected]'), function(img) {
|
| | | img.removeAttribute('data-mce-selected');
|
| | | });
|
| | |
|
| | | if (controlElm) {
|
| | | showResizeRect(controlElm);
|
| | | } else {
|
| | | dom.removeClass(editor.getBody(), 'mceResizeImages');
|
| | | hideResizeRect();
|
| | | }
|
| | | }
|
| | |
|
| | | // Show/hide resize rect when image is selected
|
| | | editor.onNodeChange.add(updateResizeRect);
|
| | |
|
| | | // Fixes WebKit quirk where it returns IMG on getNode if caret is after last image in container
|
| | | dom.bind(editableDoc, 'selectionchange', updateResizeRect);
|
| | |
|
| | | // Remove the internal attribute when serializing the DOM
|
| | | editor.serializer.addAttributeFilter('data-mce-selected', function(nodes, name) {
|
| | | var i = nodes.length;
|
| | |
|
| | | while (i--) {
|
| | | nodes[i].attr(name, null);
|
| | | }
|
| | | });
|
| | | };
|
| | | }
|
| | |
|
| | | function keepNoScriptContents() {
|
| | | if (getDocumentMode() < 9) {
|
| | | parser.addNodeFilter('noscript', function(nodes) {
|
| | | var i = nodes.length, node, textNode;
|
| | |
|
| | | while (i--) {
|
| | | node = nodes[i];
|
| | | textNode = node.firstChild;
|
| | |
|
| | | if (textNode) {
|
| | | node.attr('data-mce-innertext', textNode.value);
|
| | | }
|
| | | }
|
| | | });
|
| | |
|
| | | serializer.addNodeFilter('noscript', function(nodes) {
|
| | | var i = nodes.length, node, textNode, value;
|
| | |
|
| | | while (i--) {
|
| | | node = nodes[i];
|
| | | textNode = nodes[i].firstChild;
|
| | |
|
| | | if (textNode) {
|
| | | textNode.value = tinymce.html.Entities.decode(textNode.value);
|
| | | } else {
|
| | | // Old IE can't retain noscript value so an attribute is used to store it
|
| | | value = node.attributes.map['data-mce-innertext'];
|
| | | if (value) {
|
| | | node.attr('data-mce-innertext', null);
|
| | | textNode = new tinymce.html.Node('#text', 3);
|
| | | textNode.value = value;
|
| | | textNode.raw = true;
|
| | | node.append(textNode);
|
| | | }
|
| | | }
|
| | | }
|
| | | });
|
| | | }
|
| | | }
|
| | |
|
| | | function bodyHeight() {
|
| | | editor.contentStyles.push('body {min-height: 100px}');
|
| | | editor.onClick.add(function(ed, e) {
|
| | | if (e.target.nodeName == 'HTML') {
|
| | | editor.execCommand('SelectAll');
|
| | | editor.selection.collapse(true);
|
| | | editor.nodeChanged();
|
| | | }
|
| | | });
|
| | | }
|
| | |
|
| | | // All browsers
|
| | | disableBackspaceIntoATable();
|
| | |
| | | }
|
| | |
|
| | | // IE
|
| | | if (tinymce.isIE) {
|
| | | if (tinymce.isIE && !tinymce.isIE11) {
|
| | | removeHrOnBackspace();
|
| | | ensureBodyHasRoleApplication();
|
| | | addNewLinesBeforeBrInPre();
|
| | | removePreSerializedStylesWhenSelectingControls();
|
| | | deleteControlItemOnBackSpace();
|
| | | renderEmptyBlocksFix();
|
| | | keepNoScriptContents();
|
| | | }
|
| | |
|
| | | // IE 11+
|
| | | if (tinymce.isIE11) {
|
| | | bodyHeight();
|
| | | }
|
| | |
|
| | | // Gecko
|
| | | if (tinymce.isGecko) {
|
| | | if (tinymce.isGecko && !tinymce.isIE11) {
|
| | | removeHrOnBackspace();
|
| | | focusBody();
|
| | | removeStylesWhenDeletingAccrossBlockElements();
|
| | | setGeckoEditingOptions();
|
| | | addBrAfterLastLinks();
|
| | | removeGhostSelection();
|
| | | }
|
| | |
|
| | | // Opera
|
| | | if (tinymce.isOpera) {
|
| | | fakeImageResize();
|
| | | }
|
| | | };
|
| | | (function(tinymce) {
|
| | |
| | |
|
| | | function compress(prefix, suffix) {
|
| | | var top, right, bottom, left;
|
| | |
|
| | | // IE 11 will produce a border-image: none when getting the style attribute from <p style="border: 1px solid red"></p>
|
| | | // So lets asume it shouldn't be there
|
| | | if (styles['border-image'] === 'none') {
|
| | | delete styles['border-image'];
|
| | | }
|
| | |
|
| | | // Get values and check it it needs compressing
|
| | | top = styles[prefix + '-top' + suffix];
|
| | |
| | | 'form[A|accept-charset|action|autocomplete|enctype|method|name|novalidate|target][C]' +
|
| | | 'fieldset[A|disabled|form|name][C|legend]' +
|
| | | 'label[A|form|for][B]' +
|
| | | 'input[A|type|accept|alt|autocomplete|checked|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|height|list|max|maxlength|min|' +
|
| | | 'input[A|type|accept|alt|autocomplete|autofocus|checked|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|height|list|max|maxlength|min|' +
|
| | | 'multiple|pattern|placeholder|readonly|required|size|src|step|width|files|value|name][]' +
|
| | | 'button[A|autofocus|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|name|value|type][B]' +
|
| | | 'select[A|autofocus|disabled|form|multiple|name|size][option|optgroup]' +
|
| | |
| | | }
|
| | |
|
| | | // Setup map objects
|
| | | whiteSpaceElementsMap = createLookupTable('whitespace_elements', 'pre script style textarea');
|
| | | whiteSpaceElementsMap = createLookupTable('whitespace_elements', 'pre script noscript style textarea');
|
| | | selfClosingElementsMap = createLookupTable('self_closing_elements', 'colgroup dd dt li option p td tfoot th thead tr');
|
| | | shortEndedElementsMap = createLookupTable('short_ended_elements', 'area base basefont br col frame hr img input isindex link meta param embed source wbr');
|
| | | boolAttrMap = createLookupTable('boolean_attributes', 'checked compact declare defer disabled ismap multiple nohref noresize noshade nowrap readonly selected autoplay loop controls');
|
| | | nonEmptyElementsMap = createLookupTable('non_empty_elements', 'td th iframe video audio object', shortEndedElementsMap);
|
| | | blockElementsMap = createLookupTable('block_elements', 'h1 h2 h3 h4 h5 h6 hr p div address pre form table tbody thead tfoot ' + |
| | | 'th tr td li ol ul caption blockquote center dl dt dd dir fieldset ' + |
| | | 'noscript menu isindex samp header footer article section hgroup aside nav figure option datalist select optgroup');
|
| | | nonEmptyElementsMap = createLookupTable('non_empty_elements', 'td th iframe video audio object script', shortEndedElementsMap);
|
| | | textBlockElementsMap = createLookupTable('text_block_elements', 'h1 h2 h3 h4 h5 h6 p div address pre form ' + |
| | | 'blockquote center dir fieldset header footer article section hgroup aside nav figure');
|
| | | blockElementsMap = createLookupTable('block_elements', 'hr table tbody thead tfoot ' + |
| | | 'th tr td li ol ul caption dl dt dd noscript menu isindex samp option datalist select optgroup', textBlockElementsMap);
|
| | |
|
| | | // Converts a wildcard expression string to a regexp for example *a will become /.*a/.
|
| | | function patternToRegExp(str) {
|
| | |
| | | customElementsMap[name] = cloneName;
|
| | |
|
| | | // If it's not marked as inline then add it to valid block elements
|
| | | if (!inline)
|
| | | if (!inline) {
|
| | | blockElementsMap[name.toUpperCase()] = {};
|
| | | blockElementsMap[name] = {};
|
| | | }
|
| | |
|
| | | // Add elements clone if needed
|
| | | if (!elements[name]) {
|
| | | elements[name] = elements[cloneName];
|
| | | }
|
| | |
|
| | | // Add custom elements at span/div positions
|
| | | each(children, function(element, child) {
|
| | |
| | | return blockElementsMap;
|
| | | };
|
| | |
|
| | | self.getTextBlockElements = function() {
|
| | | return textBlockElementsMap;
|
| | | };
|
| | |
|
| | | self.getShortEndedElements = function() {
|
| | | return shortEndedElementsMap;
|
| | | };
|
| | |
| | | self.addCustomElements = addCustomElements;
|
| | |
|
| | | self.addValidChildren = addValidChildren;
|
| | |
|
| | | self.elements = elements;
|
| | | };
|
| | | })(tinymce);
|
| | |
|
| | |
| | | '(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE
|
| | | '(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI
|
| | | '(?:\\/([^>]+)>)|' + // End element
|
| | | '(?:([A-Za-z0-9\\-\\:]+)((?:\\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\\/|\\s+)>)' + // Start element
|
| | | '(?:([A-Za-z0-9\\-\\:\\.]+)((?:\\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\\/|\\s+)>)' + // Start element
|
| | | ')', 'g');
|
| | |
|
| | | attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:\\.|[^\"])*)\")|(?:\'((?:\\.|[^\'])*)\')|([^>\s]+)))?/g;
|
| | | attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:[^\"])*)\")|(?:\'((?:[^\'])*)\')|([^>\s]+)))?/g;
|
| | | specialElements = {
|
| | | 'script' : /<\/script[^>]*>/gi,
|
| | | 'style' : /<\/style[^>]*>/gi,
|
| | |
| | | i = node.attributes.length;
|
| | | while (i--) {
|
| | | name = node.attributes[i].name;
|
| | | if (name === "name" || name.indexOf('data-') === 0)
|
| | | if (name === "name" || name.indexOf('data-mce-') === 0)
|
| | | return false;
|
| | | }
|
| | | }
|
| | |
| | |
|
| | | function fixInvalidChildren(nodes) {
|
| | | var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i,
|
| | | childClone, nonEmptyElements, nonSplitableElements, sibling, nextNode;
|
| | | childClone, nonEmptyElements, nonSplitableElements, textBlockElements, sibling, nextNode;
|
| | |
|
| | | nonSplitableElements = tinymce.makeMap('tr,td,th,tbody,thead,tfoot,table');
|
| | | nonEmptyElements = schema.getNonEmptyElements();
|
| | | textBlockElements = schema.getTextBlockElements();
|
| | |
|
| | | for (ni = 0; ni < nodes.length; ni++) {
|
| | | node = nodes[ni];
|
| | |
|
| | | // Already removed
|
| | | if (!node.parent)
|
| | | // Already removed or fixed
|
| | | if (!node.parent || node.fixed)
|
| | | continue;
|
| | |
|
| | | // If the invalid element is a text block and the text block is within a parent LI element
|
| | | // Then unwrap the first text block and convert other sibling text blocks to LI elements similar to Word/Open Office
|
| | | if (textBlockElements[node.name] && node.parent.name == 'li') {
|
| | | // Move sibling text blocks after LI element
|
| | | sibling = node.next;
|
| | | while (sibling) {
|
| | | if (textBlockElements[sibling.name]) {
|
| | | sibling.name = 'li';
|
| | | sibling.fixed = true;
|
| | | node.parent.insert(sibling, node.parent);
|
| | | } else {
|
| | | break;
|
| | | }
|
| | |
|
| | | sibling = sibling.next;
|
| | | }
|
| | |
|
| | | // Unwrap current text block
|
| | | node.unwrap(node);
|
| | | continue;
|
| | | }
|
| | |
|
| | | // Get list of all parent nodes until we find a valid parent to stick the child into
|
| | | parents = [node];
|
| | |
| | | }
|
| | |
|
| | | // Trim start white space
|
| | | textNode = node.prev;
|
| | | // Removed due to: #5424
|
| | | /*textNode = node.prev;
|
| | | if (textNode && textNode.type === 3) {
|
| | | text = textNode.value.replace(startWhiteSpaceRegExp, '');
|
| | |
|
| | |
| | | textNode.value = text;
|
| | | else
|
| | | textNode.remove();
|
| | | }
|
| | | }*/
|
| | | }
|
| | |
|
| | | // Check if we exited a whitespace preserved element
|
| | |
| | | event_utils.domLoaded = true;
|
| | | callback(event);
|
| | | }
|
| | | }
|
| | |
|
| | | // Page already loaded then fire it directly
|
| | | if (doc.readyState == "complete") {
|
| | | readyHandler();
|
| | | return;
|
| | | }
|
| | |
|
| | | // Use W3C method
|
| | |
| | | blockElementsMap = s.schema ? s.schema.getBlockElements() : {};
|
| | |
|
| | | t.isBlock = function(node) {
|
| | | // Fix for #5446
|
| | | if (!node) {
|
| | | return false;
|
| | | }
|
| | |
|
| | | // This function is called in module pattern style since it might be executed with the wrong this scope
|
| | | var type = node.nodeType;
|
| | |
|
| | |
| | | fixDoc: function(doc) {
|
| | | var settings = this.settings, name;
|
| | |
|
| | | if (isIE && settings.schema) {
|
| | | if (isIE && !tinymce.isIE11 && settings.schema) {
|
| | | // Add missing HTML 4/5 elements to IE
|
| | | ('abbr article aside audio canvas ' +
|
| | | 'details figcaption figure footer ' +
|
| | |
| | | var self = this, clone, doc;
|
| | |
|
| | | // TODO: Add feature detection here in the future
|
| | | if (!isIE || node.nodeType !== 1 || deep) {
|
| | | if (!isIE || tinymce.isIE11 || node.nodeType !== 1 || deep) {
|
| | | return node.cloneNode(deep);
|
| | | }
|
| | |
|
| | |
| | | switch (na) {
|
| | | case 'opacity':
|
| | | // IE specific opacity
|
| | | if (isIE) {
|
| | | if (isIE && ! tinymce.isIE11) {
|
| | | s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")";
|
| | |
|
| | | if (!n.currentStyle || !n.currentStyle.hasLayout)
|
| | |
| | | break;
|
| | |
|
| | | case 'float':
|
| | | isIE ? s.styleFloat = v : s.cssFloat = v;
|
| | | (isIE && ! tinymce.isIE11) ? s.styleFloat = v : s.cssFloat = v;
|
| | | break;
|
| | |
|
| | | default:
|
| | |
| | | styleElm.id = 'mceDefaultStyles';
|
| | | styleElm.type = 'text/css';
|
| | |
|
| | | head = doc.getElementsByTagName('head')[0]
|
| | | head = doc.getElementsByTagName('head')[0];
|
| | | if (head.firstChild) {
|
| | | head.insertBefore(styleElm, head.firstChild);
|
| | | } else {
|
| | |
| | | // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug
|
| | | // This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading
|
| | | // It's ugly but it seems to work fine.
|
| | | if (isIE && d.documentMode && d.recalc) {
|
| | | if (isIE && !tinymce.isIE11 && d.documentMode && d.recalc) {
|
| | | link.onload = function() {
|
| | | if (d.recalc)
|
| | | d.recalc();
|
| | |
| | |
|
| | | // Import
|
| | | case 3:
|
| | | addClasses(r.styleSheet);
|
| | | try {
|
| | | addClasses(r.styleSheet);
|
| | | } catch (ex) {
|
| | | // Ignore
|
| | | }
|
| | |
|
| | | break;
|
| | | }
|
| | | });
|
| | |
| | | };
|
| | |
|
| | | this.addRange = function(rng) {
|
| | | var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, sibling, doc = selection.dom.doc, body = doc.body;
|
| | | var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, sibling,
|
| | | doc = selection.dom.doc, body = doc.body, nativeRng, ctrlElm;
|
| | |
|
| | | function setEndPoint(start) {
|
| | | var container, offset, marker, tmpRng, nodes;
|
| | |
| | |
|
| | | if (startOffset == endOffset - 1) {
|
| | | try {
|
| | | ctrlElm = startContainer.childNodes[startOffset];
|
| | | ctrlRng = body.createControlRange();
|
| | | ctrlRng.addElement(startContainer.childNodes[startOffset]);
|
| | | ctrlRng.addElement(ctrlElm);
|
| | | ctrlRng.select();
|
| | | return;
|
| | |
|
| | | // Check if the range produced is on the correct element and is a control range
|
| | | // On IE 8 it will select the parent contentEditable container if you select an inner element see: #5398
|
| | | nativeRng = selection.getRng();
|
| | | if (nativeRng.item && ctrlElm === nativeRng.item(0)) {
|
| | | return;
|
| | | }
|
| | | } catch (ex) {
|
| | | // Ignore
|
| | | }
|
| | |
| | | if (!t.win.getSelection)
|
| | | t.tridentSel = new tinymce.dom.TridentSelection(t);
|
| | |
|
| | | if (tinymce.isIE && dom.boxModel)
|
| | | if (tinymce.isIE && ! tinymce.isIE11 && dom.boxModel)
|
| | | this._fixIESelection();
|
| | |
|
| | | // Prevent leaks
|
| | |
| | | }
|
| | |
|
| | | // Handle simple range
|
| | | if (type)
|
| | | return {rng : t.getRng()};
|
| | | if (type) {
|
| | | rng = t.getRng();
|
| | |
|
| | | if (rng.setStart) {
|
| | | rng = {
|
| | | startContainer: rng.startContainer,
|
| | | startOffset: rng.startOffset,
|
| | | endContainer: rng.endContainer,
|
| | | endOffset: rng.endOffset
|
| | | };
|
| | | }
|
| | |
|
| | | return {rng : rng};
|
| | | }
|
| | |
|
| | | rng = t.getRng();
|
| | | id = dom.uniqueId();
|
| | |
| | | },
|
| | |
|
| | | moveToBookmark : function(bookmark) {
|
| | | var t = this, dom = t.dom, marker1, marker2, rng, root, startContainer, endContainer, startOffset, endOffset;
|
| | | var t = this, dom = t.dom, marker1, marker2, rng, rng2, root, startContainer, endContainer, startOffset, endOffset;
|
| | |
|
| | | function setEndPoint(start) {
|
| | | var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;
|
| | |
| | | }
|
| | | } else if (bookmark.name) {
|
| | | t.select(dom.select(bookmark.name)[bookmark.index]);
|
| | | } else if (bookmark.rng)
|
| | | t.setRng(bookmark.rng);
|
| | | } else if (bookmark.rng) {
|
| | | rng = bookmark.rng;
|
| | |
|
| | | if (rng.startContainer) {
|
| | | rng2 = t.dom.createRng();
|
| | |
|
| | | try {
|
| | | rng2.setStart(rng.startContainer, rng.startOffset);
|
| | | rng2.setEnd(rng.endContainer, rng.endOffset);
|
| | | } catch (e) {
|
| | | // Might fail with index error
|
| | | }
|
| | |
|
| | | rng = rng2;
|
| | | }
|
| | |
|
| | | t.setRng(rng);
|
| | | }
|
| | | }
|
| | | },
|
| | |
|
| | |
| | | }
|
| | |
|
| | | // We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet
|
| | | if (tinymce.isIE && rng && rng.setStart && doc.selection.createRange().item) {
|
| | | if (tinymce.isIE && ! tinymce.isIE11 && rng && rng.setStart && doc.selection.createRange().item) {
|
| | | elm = doc.selection.createRange().item(0);
|
| | | rng = doc.createRange();
|
| | | rng.setStartBefore(elm);
|
| | |
| | | return self;
|
| | | },
|
| | |
|
| | | scrollIntoView: function(elm) {
|
| | | var y, viewPort, self = this, dom = self.dom;
|
| | |
|
| | | viewPort = dom.getViewPort(self.editor.getWin());
|
| | | y = dom.getPos(elm).y;
|
| | | if (y < viewPort.y || y + 25 > viewPort.y + viewPort.h) {
|
| | | self.editor.getWin().scrollTo(0, y < viewPort.y ? y : y - viewPort.h + 25);
|
| | | }
|
| | | },
|
| | |
|
| | | destroy : function(manual) {
|
| | | var self = this;
|
| | |
|
| | |
| | | }
|
| | | });
|
| | |
|
| | | htmlParser.addNodeFilter('noscript', function(nodes) {
|
| | | var i = nodes.length, node;
|
| | |
|
| | | while (i--) {
|
| | | node = nodes[i].firstChild;
|
| | |
|
| | | if (node) {
|
| | | node.value = tinymce.html.Entities.decode(node.value);
|
| | | }
|
| | | }
|
| | | });
|
| | |
|
| | | // Force script into CDATA sections and remove the mce- prefix also add comments around styles
|
| | | htmlParser.addNodeFilter('script,style', function(nodes, name) {
|
| | | var i = nodes.length, node, value;
|
| | |
| | |
|
| | | // Replace all BOM characters for now until we can find a better solution
|
| | | if (!args.cleanup)
|
| | | args.content = args.content.replace(/\uFEFF|\u200B/g, '');
|
| | | args.content = args.content.replace(/\uFEFF/g, '');
|
| | |
|
| | | // Post process
|
| | | if (!args.no_events)
|
| | |
| | |
|
| | | // Add onload listener for non IE browsers since IE9
|
| | | // fires onload event before the script is parsed and executed
|
| | | if (!tinymce.isIE)
|
| | | if (!tinymce.isIE || tinymce.isIE11)
|
| | | elm.onload = done;
|
| | |
|
| | | // Add onerror event will get fired on some browsers but not all of them
|
| | |
| | | switch (evt.keyCode) {
|
| | | case DOM_VK_LEFT:
|
| | | if (enableLeftRight) t.moveFocus(-1);
|
| | | Event.cancel(evt);
|
| | | break;
|
| | |
|
| | | case DOM_VK_RIGHT:
|
| | | if (enableLeftRight) t.moveFocus(1);
|
| | | Event.cancel(evt);
|
| | | break;
|
| | |
|
| | | case DOM_VK_UP:
|
| | | if (enableUpDown) t.moveFocus(-1);
|
| | | Event.cancel(evt);
|
| | | break;
|
| | |
|
| | | case DOM_VK_DOWN:
|
| | | if (enableUpDown) t.moveFocus(1);
|
| | | Event.cancel(evt);
|
| | | break;
|
| | |
|
| | | case DOM_VK_ESCAPE:
|
| | |
| | | else
|
| | | h += '<span class="mceIcon ' + s['class'] + '"></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '');
|
| | |
|
| | | h += '<span class="mceVoiceLabel mceIconOnly" style="display: none;" id="' + this.id + '_voice">' + s.title + '</span>'; |
| | | h += '<span class="mceVoiceLabel mceIconOnly" style="display: none;" id="' + this.id + '_voice">' + s.title + '</span>';
|
| | | h += '</a>';
|
| | | return h;
|
| | | },
|
| | |
| | | return s.onclick.call(s.scope, e);
|
| | | }
|
| | | });
|
| | | tinymce.dom.Event.add(t.id, 'keyup', function(e) {
|
| | | if (!t.isDisabled() && e.keyCode==tinymce.VK.SPACEBAR)
|
| | | tinymce.dom.Event.add(t.id, 'keydown', function(e) {
|
| | | if (!t.isDisabled() && e.keyCode==tinymce.VK.SPACEBAR) {
|
| | | tinymce.dom.Event.cancel(e);
|
| | | return s.onclick.call(s.scope, e);
|
| | | }
|
| | | });
|
| | | }
|
| | | });
|
| | |
| | |
|
| | | // Accessibility keyhandler
|
| | | Event.add(t.id, 'keydown', function(e) {
|
| | | var bf;
|
| | | var bf, DOM_VK_LEFT = 37, DOM_VK_RIGHT = 39, DOM_VK_UP = 38, DOM_VK_DOWN = 40, DOM_VK_RETURN = 13, DOM_VK_SPACE = 32;
|
| | |
|
| | | Event.remove(t.id, 'change', ch);
|
| | | changeListenerAdded = false;
|
| | |
| | | Event.remove(t.id, 'blur', bf);
|
| | | });
|
| | |
|
| | | //prevent default left and right keys on chrome - so that the keyboard navigation is used.
|
| | | if (tinymce.isWebKit && (e.keyCode==37 ||e.keyCode==39)) {
|
| | | return Event.prevent(e);
|
| | | }
|
| | | |
| | | if (e.keyCode == 13 || e.keyCode == 32) {
|
| | | if (e.keyCode == DOM_VK_RETURN || e.keyCode == DOM_VK_SPACE) {
|
| | | onChange(e);
|
| | | return Event.cancel(e);
|
| | | } else if (e.keyCode == DOM_VK_DOWN || e.keyCode == DOM_VK_UP) {
|
| | | // allow native implementation (navigate select element options)
|
| | | e.stopImmediatePropagation();
|
| | | }
|
| | | });
|
| | |
|
| | |
| | | if (id === undef)
|
| | | return this.editors;
|
| | |
|
| | | if (!this.editors.hasOwnProperty(id))
|
| | | return undef;
|
| | |
|
| | | return this.editors[id];
|
| | | },
|
| | |
|
| | |
| | | ed.render();
|
| | |
|
| | | // Fix IE memory leaks
|
| | | if (tinymce.isIE) {
|
| | | if (tinymce.isIE && ! tinymce.isIE11) {
|
| | | w.attachEvent('onunload', clr);
|
| | | }
|
| | |
|
| | |
| | | // Store away the selection when it's changed to it can be restored later with a editor.focus() call
|
| | | if (isIE) {
|
| | | t.onInit.add(function(ed) {
|
| | | ed.dom.bind(ed.getBody(), 'beforedeactivate keydown', function() {
|
| | | ed.lastIERng = ed.selection.getRng();
|
| | | ed.dom.bind(ed.getBody(), 'beforedeactivate keydown keyup', function() {
|
| | | ed.bookmark = ed.selection.getBookmark(1);
|
| | | });
|
| | | });
|
| | |
|
| | | t.onNodeChange.add(function(ed) {
|
| | | if (document.activeElement.id == ed.id + "_ifr") {
|
| | | ed.bookmark = ed.selection.getBookmark(1);
|
| | | }
|
| | | });
|
| | | }
|
| | | }
|
| | |
| | | each(explode(s.content_css), function(u) {
|
| | | t.contentCSS.push(t.documentBaseURI.toAbsolute(u));
|
| | | });
|
| | | }
|
| | |
|
| | | // Load specified content CSS last
|
| | | if (s.content_style) {
|
| | | t.contentStyles.push(s.content_style);
|
| | | }
|
| | |
|
| | | // Content editable mode ends here
|
| | |
| | | t.iframeHTML += '<base href="' + t.documentBaseURI.getURI() + '" />';
|
| | |
|
| | | // IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode.
|
| | | if (s.ie7_compat)
|
| | | t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />';
|
| | | else
|
| | | t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=edge" />';
|
| | | if (tinymce.isIE8) {
|
| | | if (s.ie7_compat)
|
| | | t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />';
|
| | | else
|
| | | t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=edge" />';
|
| | | }
|
| | |
|
| | | t.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
|
| | |
|
| | |
| | | var oed, self = this, selection = self.selection, contentEditable = self.settings.content_editable, ieRng, controlElm, doc = self.getDoc(), body;
|
| | |
|
| | | if (!skip_focus) {
|
| | | if (self.lastIERng) {
|
| | | selection.setRng(self.lastIERng);
|
| | | if (self.bookmark) {
|
| | | selection.moveToBookmark(self.bookmark);
|
| | | self.bookmark = null;
|
| | | }
|
| | |
|
| | | // Get selected control element
|
| | |
| | | body = self.getBody();
|
| | |
|
| | | // Check for setActive since it doesn't scroll to the element
|
| | | if (body.setActive) {
|
| | | if (body.setActive && ! tinymce.isIE11) {
|
| | | body.setActive();
|
| | | } else {
|
| | | body.focus();
|
| | |
| | |
|
| | | // We must save before we hide so Safari doesn't crash
|
| | | self.save();
|
| | |
|
| | | // defer the call to hide to prevent an IE9 crash #4921
|
| | | DOM.hide(self.getContainer());
|
| | | DOM.setStyle(self.id, 'display', self.orgDisplay);
|
| | | },
|
| | |
| | | },
|
| | |
|
| | | getContent : function(args) {
|
| | | var self = this, content;
|
| | | var self = this, content, body = self.getBody();
|
| | |
|
| | | // Setup args object
|
| | | args = args || {};
|
| | |
| | |
|
| | | // Get raw contents or by default the cleaned contents
|
| | | if (args.format == 'raw')
|
| | | content = self.getBody().innerHTML;
|
| | | content = body.innerHTML;
|
| | | else if (args.format == 'text')
|
| | | content = body.innerText || body.textContent;
|
| | | else
|
| | | content = self.serializer.serialize(self.getBody(), args);
|
| | | content = self.serializer.serialize(body, args);
|
| | |
|
| | | args.content = tinymce.trim(content);
|
| | | // Trim whitespace in beginning/end of HTML
|
| | | if (args.format != 'text') {
|
| | | args.content = tinymce.trim(content);
|
| | | } else {
|
| | | args.content = content;
|
| | | }
|
| | |
|
| | | // Do post processing
|
| | | if (!args.no_events)
|
| | |
| | | },
|
| | |
|
| | | remove : function() {
|
| | | var self = this, elm = self.getContainer();
|
| | | var self = this, elm = self.getContainer(), doc = self.getDoc();
|
| | |
|
| | | if (!self.removed) {
|
| | | self.removed = 1; // Cancels post remove event execution
|
| | | self.hide();
|
| | |
|
| | | // Fixed bug where IE has a blinking cursor left from the editor
|
| | | if (isIE && doc)
|
| | | doc.execCommand('SelectAll');
|
| | |
|
| | | // We must save before we hide so Safari doesn't crash
|
| | | self.save();
|
| | |
|
| | | DOM.setStyle(self.id, 'display', self.orgDisplay);
|
| | |
|
| | | // Don't clear the window or document if content editable
|
| | | // is enabled since other instances might still be present
|
| | |
| | |
|
| | | // Insert bookmark node and get the parent
|
| | | selection.setContent(bookmarkHtml);
|
| | | parentNode = editor.selection.getNode();
|
| | | parentNode = selection.getNode();
|
| | | rootNode = editor.getBody();
|
| | |
|
| | | // Opera will return the document node when selection is in root
|
| | |
| | | selectAll : function() {
|
| | | var root = dom.getRoot(), rng = dom.createRng();
|
| | |
|
| | | rng.setStart(root, 0);
|
| | | rng.setEnd(root, root.childNodes.length);
|
| | | // Old IE does a better job with selectall than new versions
|
| | | if (selection.getRng().setStart) {
|
| | | rng.setStart(root, 0);
|
| | | rng.setEnd(root, root.childNodes.length);
|
| | |
|
| | | editor.selection.setRng(rng);
|
| | | selection.setRng(rng);
|
| | | } else {
|
| | | execNativeCommand('SelectAll');
|
| | | }
|
| | | }
|
| | | });
|
| | |
|
| | |
| | | },
|
| | |
|
| | | 'InsertUnorderedList,InsertOrderedList' : function(command) {
|
| | | return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : 'OL');
|
| | | var list = dom.getParent(selection.getNode(), 'ul,ol');
|
| | | return list && |
| | | (command === 'insertunorderedlist' && list.tagName === 'UL'
|
| | | || command === 'insertorderedlist' && list.tagName === 'OL');
|
| | | }
|
| | | }, 'state');
|
| | |
|
| | |
| | | // Add undo level on save contents, drag end and blur/focusout
|
| | | editor.onSaveContent.add(addNonTypingUndoLevel);
|
| | | editor.dom.bind(editor.dom.getRoot(), 'dragend', addNonTypingUndoLevel);
|
| | | editor.dom.bind(editor.getDoc(), tinymce.isGecko ? 'blur' : 'focusout', function(e) {
|
| | | editor.dom.bind(editor.getBody(), 'focusout', function(e) {
|
| | | if (!editor.removed && self.typing) {
|
| | | addNonTypingUndoLevel();
|
| | | }
|
| | |
| | | node = rootNode.firstChild;
|
| | | while (node) {
|
| | | if (node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName])) {
|
| | | // Remove empty text nodes
|
| | | if (node.nodeType === 3 && node.nodeValue.length == 0) {
|
| | | tempNode = node;
|
| | | node = node.nextSibling;
|
| | | dom.remove(tempNode);
|
| | | continue;
|
| | | }
|
| | |
|
| | | if (!rootBlockNode) {
|
| | | rootBlockNode = dom.create(settings.forced_root_block);
|
| | | node.parentNode.insertBefore(rootBlockNode, node);
|
| | |
| | | TreeWalker = tinymce.dom.TreeWalker,
|
| | | rangeUtils = new tinymce.dom.RangeUtils(dom),
|
| | | isValid = ed.schema.isValidChild,
|
| | | isArray = tinymce.isArray,
|
| | | isBlock = dom.isBlock,
|
| | | forcedRootBlock = ed.settings.forced_root_block,
|
| | | nodeIndex = dom.nodeIndex,
|
| | | INVISIBLE_CHAR = tinymce.isGecko ? '\u200B' : '\uFEFF',
|
| | | INVISIBLE_CHAR = '\uFEFF',
|
| | | MCE_ATTR_RE = /^(src|href|style)$/,
|
| | | FALSE = false,
|
| | | TRUE = true,
|
| | |
| | | undef,
|
| | | getContentEditable = dom.getContentEditable;
|
| | |
|
| | | function isArray(obj) {
|
| | | return obj instanceof Array;
|
| | | };
|
| | | function isTextBlock(name) {
|
| | | if (name.nodeType) {
|
| | | name = name.nodeName;
|
| | | }
|
| | |
|
| | | return !!ed.schema.getTextBlockElements()[name.toLowerCase()];
|
| | | }
|
| | |
|
| | | function getParents(node, selector) {
|
| | | return dom.getParents(node, selector, dom.getRoot());
|
| | |
| | |
|
| | | // Is it valid to wrap this item
|
| | | if (contentEditable && !hasContentEditableState && isValid(wrapName, nodeName) && isValid(parentName, wrapName) &&
|
| | | !(!node_specific && node.nodeType === 3 && node.nodeValue.length === 1 && node.nodeValue.charCodeAt(0) === 65279) && !isCaretNode(node)) {
|
| | | !(!node_specific && node.nodeType === 3 && node.nodeValue.length === 1 && node.nodeValue.charCodeAt(0) === 65279) && !isCaretNode(node) && (!format.inline || !isBlock(node))) {
|
| | | // Start wrapping
|
| | | if (!currentWrapElm) {
|
| | | // Wrap the node
|
| | |
| | | // Merges the styles for each node
|
| | | function process(node) {
|
| | | var children, i, l, localContentEditable, lastContentEditable, hasContentEditableState;
|
| | |
|
| | | // Skip on text nodes as they have neither format to remove nor children
|
| | | if (node.nodeType === 3) {
|
| | | return;
|
| | | }
|
| | |
|
| | | // Node has a contentEditable value
|
| | | if (node.nodeType === 1 && getContentEditable(node)) {
|
| | |
| | | return FALSE;
|
| | | };
|
| | |
|
| | | function formatChanged(formats, callback) {
|
| | | function formatChanged(formats, callback, similar) {
|
| | | var currentFormats;
|
| | |
|
| | | // Setup format node change logic
|
| | |
| | | // Check for new formats
|
| | | each(formatChangeData, function(callbacks, format) {
|
| | | each(parents, function(node) {
|
| | | if (matchNode(node, format, {}, true)) {
|
| | | if (matchNode(node, format, {}, callbacks.similar)) {
|
| | | if (!currentFormats[format]) {
|
| | | // Execute callbacks
|
| | | each(callbacks, function(callback) {
|
| | |
| | | each(formats.split(','), function(format) {
|
| | | if (!formatChangeData[format]) {
|
| | | formatChangeData[format] = [];
|
| | | formatChangeData[format].similar = similar;
|
| | | }
|
| | |
|
| | | formatChangeData[format].push(callback);
|
| | |
| | | siblingName = start ? 'previousSibling' : 'nextSibling';
|
| | | root = dom.getRoot();
|
| | |
|
| | | function isBogusBr(node) {
|
| | | return node.nodeName == "BR" && node.getAttribute('data-mce-bogus') && !node.nextSibling;
|
| | | };
|
| | |
|
| | | // If it's a text node and the offset is inside the text
|
| | | if (container.nodeType == 3 && !isWhiteSpaceNode(container)) {
|
| | | if (start ? startOffset > 0 : endOffset < container.nodeValue.length) {
|
| | |
| | |
|
| | | // Walk left/right
|
| | | for (sibling = parent[siblingName]; sibling; sibling = sibling[siblingName]) {
|
| | | if (!isBookmarkNode(sibling) && !isWhiteSpaceNode(sibling)) {
|
| | | if (!isBookmarkNode(sibling) && !isWhiteSpaceNode(sibling) && !isBogusBr(sibling)) {
|
| | | return parent;
|
| | | }
|
| | | }
|
| | |
| | |
|
| | | // Expand to first wrappable block element or any block element
|
| | | if (!node)
|
| | | node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isBlock);
|
| | | node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isTextBlock);
|
| | |
|
| | | // Exclude inner lists from wrapping
|
| | | if (node && format[0].wrapper)
|
| | |
| | | return next;
|
| | | };
|
| | |
|
| | | function isTextBlock(name) {
|
| | | return /^(h[1-6]|p|div|pre|address|dl|dt|dd)$/.test(name);
|
| | | };
|
| | |
|
| | | function getContainer(rng, start) {
|
| | | var container, offset, lastIdx, walker;
|
| | |
|
| | |
| | | node.appendChild(dom.doc.createTextNode(INVISIBLE_CHAR));
|
| | | node = node.firstChild;
|
| | |
|
| | | // Insert caret container after the formated node
|
| | | dom.insertAfter(caretContainer, formatNode);
|
| | | var block = dom.getParent(formatNode, isTextBlock);
|
| | |
|
| | | if (block && dom.isEmpty(block)) {
|
| | | // Replace formatNode with caretContainer when removing format from empty block like <p><b>|</b></p>
|
| | | formatNode.parentNode.replaceChild(caretContainer, formatNode);
|
| | | } else {
|
| | | // Insert caret container after the formated node
|
| | | dom.insertAfter(caretContainer, formatNode);
|
| | | }
|
| | |
|
| | | // Move selection to text node
|
| | | selection.setCursorLocation(node, 1);
|
| | |
|
| | | // If the formatNode is empty, we can remove it safely. |
| | | if (dom.isEmpty(formatNode)) {
|
| | | dom.remove(formatNode);
|
| | | }
|
| | | }
|
| | | };
|
| | |
|
| | |
| | | var dom = editor.dom, selection = editor.selection, settings = editor.settings, undoManager = editor.undoManager, nonEmptyElementsMap = editor.schema.getNonEmptyElements();
|
| | |
|
| | | function handleEnterKey(evt) {
|
| | | var rng = selection.getRng(true), tmpRng, editableRoot, container, offset, parentBlock, documentMode,
|
| | | var rng = selection.getRng(true), tmpRng, editableRoot, container, offset, parentBlock, documentMode, shiftKey,
|
| | | newBlock, fragment, containerBlock, parentBlockName, containerBlockName, newBlockName, isAfterLastNodeInContainer;
|
| | |
|
| | | // Returns true if the block can be split into two blocks or not
|
| | |
| | | function renderBlockOnIE(block) {
|
| | | var oldRng;
|
| | |
|
| | | if (tinymce.isIE && dom.isBlock(block)) {
|
| | | if (tinymce.isIE && !tinymce.isIE11 && dom.isBlock(block)) {
|
| | | oldRng = selection.getRng();
|
| | | block.appendChild(dom.create('span', null, '\u00a0'));
|
| | | selection.select(block);
|
| | |
| | | node = firstChilds[i];
|
| | | if (!node.hasChildNodes() || (node.firstChild == node.lastChild && node.firstChild.nodeValue === '')) {
|
| | | dom.remove(node);
|
| | | } else {
|
| | | // Remove <a> </a> see #5381
|
| | | if (node.nodeName == "A" && (node.innerText || node.textContent) === ' ') {
|
| | | dom.remove(node);
|
| | | }
|
| | | }
|
| | | }
|
| | | };
|
| | |
| | | if (settings.keep_styles !== false) {
|
| | | do {
|
| | | if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) {
|
| | | // Never clone a caret containers
|
| | | if (node.id == '_mce_caret') {
|
| | | continue;
|
| | | }
|
| | |
|
| | | clonedNode = node.cloneNode(false);
|
| | | dom.setAttrib(clonedNode, 'id', ''); // Remove ID since it needs to be document unique
|
| | |
|
| | |
| | | }
|
| | |
|
| | | // BR is needed in empty blocks on non IE browsers
|
| | | if (!tinymce.isIE) {
|
| | | caretNode.innerHTML = '<br>';
|
| | | if (!tinymce.isIE || tinymce.isIE11) {
|
| | | caretNode.innerHTML = '<br data-mce-bogus="1">';
|
| | | }
|
| | |
|
| | | return block;
|
| | |
| | | undoManager.add();
|
| | | };
|
| | |
|
| | | // Walks the parent block to the right and look for BR elements
|
| | | function hasRightSideBr() {
|
| | | // Walks the parent block to the right and look for any contents
|
| | | function hasRightSideContent() {
|
| | | var walker = new TreeWalker(container, parentBlock), node;
|
| | |
|
| | | while (node = walker.current()) {
|
| | | if (node.nodeName == 'BR') {
|
| | | while (node = walker.next()) {
|
| | | if (nonEmptyElementsMap[node.nodeName.toLowerCase()] || node.length > 0) {
|
| | | return true;
|
| | | }
|
| | |
|
| | | node = walker.next();
|
| | | }
|
| | | }
|
| | | |
| | |
|
| | | // Inserts a BR element if the forced_root_block option is set to false or empty string
|
| | | function insertBr() {
|
| | | var brElm, extraBr;
|
| | | var brElm, extraBr, marker;
|
| | |
|
| | | if (container && container.nodeType == 3 && offset >= container.nodeValue.length) {
|
| | | // Insert extra BR element at the end block elements
|
| | | if (!tinymce.isIE && !hasRightSideBr()) {
|
| | | brElm = dom.create('br')
|
| | | if ((!tinymce.isIE || tinymce.isIE11) && !hasRightSideContent()) {
|
| | | brElm = dom.create('br');
|
| | | rng.insertNode(brElm);
|
| | | rng.setStartAfter(brElm);
|
| | | rng.setEndAfter(brElm);
|
| | |
| | | rng.insertNode(brElm);
|
| | |
|
| | | // Rendering modes below IE8 doesn't display BR elements in PRE unless we have a \n before it
|
| | | if (tinymce.isIE && parentBlockName == 'PRE' && (!documentMode || documentMode < 8)) {
|
| | | if ((tinymce.isIE && !tinymce.isIE11) && parentBlockName == 'PRE' && (!documentMode || documentMode < 8)) {
|
| | | brElm.parentNode.insertBefore(dom.doc.createTextNode('\r'), brElm);
|
| | | }
|
| | |
|
| | | // Insert temp marker and scroll to that
|
| | | marker = dom.create('span', {}, ' ');
|
| | | brElm.parentNode.insertBefore(marker, brElm);
|
| | | selection.scrollIntoView(marker);
|
| | | dom.remove(marker);
|
| | |
|
| | | if (!extraBr) {
|
| | | rng.setStartAfter(brElm);
|
| | |
| | | var lastChild;
|
| | |
|
| | | // IE will render the blocks correctly other browsers needs a BR
|
| | | if (!tinymce.isIE) {
|
| | | if (!tinymce.isIE || tinymce.isIE11) {
|
| | | block.normalize(); // Remove empty text nodes that got left behind by the extract
|
| | |
|
| | | // Check if the block is empty or contains a floated last child
|
| | |
| | | // Setup range items and newBlockName
|
| | | container = rng.startContainer;
|
| | | offset = rng.startOffset;
|
| | | newBlockName = settings.forced_root_block;
|
| | | newBlockName = (settings.force_p_newlines ? 'p' : '') || settings.forced_root_block;
|
| | | newBlockName = newBlockName ? newBlockName.toUpperCase() : '';
|
| | | documentMode = dom.doc.documentMode;
|
| | | shiftKey = evt.shiftKey;
|
| | |
|
| | | // Resolve node index
|
| | | if (container.nodeType == 1 && container.hasChildNodes()) {
|
| | |
| | |
|
| | | // If editable root isn't block nor the root of the editor
|
| | | if (!dom.isBlock(editableRoot) && editableRoot != dom.getRoot()) {
|
| | | if (!newBlockName || evt.shiftKey) {
|
| | | if (!newBlockName || shiftKey) {
|
| | | insertBr();
|
| | | }
|
| | |
|
| | |
| | | // Wrap the current node and it's sibling in a default block if it's needed.
|
| | | // for example this <td>text|<b>text2</b></td> will become this <td><p>text|<b>text2</p></b></td>
|
| | | // This won't happen if root blocks are disabled or the shiftKey is pressed
|
| | | if ((newBlockName && !evt.shiftKey) || (!newBlockName && evt.shiftKey)) {
|
| | | if ((newBlockName && !shiftKey) || (!newBlockName && shiftKey)) {
|
| | | container = wrapSelfAndSiblingsInDefaultBlock(container, offset);
|
| | | }
|
| | |
|
| | |
| | | parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
|
| | | containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
|
| | |
|
| | | // Handle enter inside an empty list item
|
| | | if (parentBlockName == 'LI' && dom.isEmpty(parentBlock)) {
|
| | | // Let the list plugin or browser handle nested lists for now
|
| | | if (/^(UL|OL|LI)$/.test(containerBlock.parentNode.nodeName)) {
|
| | | return false;
|
| | | // Enter inside block contained within a LI then split or insert before/after LI
|
| | | if (containerBlockName == 'LI' && !evt.ctrlKey) {
|
| | | parentBlock = containerBlock;
|
| | | parentBlockName = containerBlockName;
|
| | | }
|
| | |
|
| | | // Handle enter in LI
|
| | | if (parentBlockName == 'LI') {
|
| | | if (!newBlockName && shiftKey) {
|
| | | insertBr();
|
| | | return;
|
| | | }
|
| | |
|
| | | handleEmptyListItem();
|
| | | return;
|
| | | // Handle enter inside an empty list item
|
| | | if (dom.isEmpty(parentBlock)) {
|
| | | // Let the list plugin or browser handle nested lists for now
|
| | | if (/^(UL|OL|LI)$/.test(containerBlock.parentNode.nodeName)) {
|
| | | return false;
|
| | | }
|
| | |
|
| | | handleEmptyListItem();
|
| | | return;
|
| | | }
|
| | | }
|
| | |
|
| | | // Don't split PRE tags but insert a BR instead easier when writing code samples etc
|
| | | if (parentBlockName == 'PRE' && settings.br_in_pre !== false) {
|
| | | if (!evt.shiftKey) {
|
| | | if (!shiftKey) {
|
| | | insertBr();
|
| | | return;
|
| | | }
|
| | | } else {
|
| | | // If no root block is configured then insert a BR by default or if the shiftKey is pressed
|
| | | if ((!newBlockName && !evt.shiftKey && parentBlockName != 'LI') || (newBlockName && evt.shiftKey)) {
|
| | | if ((!newBlockName && !shiftKey && parentBlockName != 'LI') || (newBlockName && shiftKey)) {
|
| | | insertBr();
|
| | | return;
|
| | | }
|