diff --git a/lib/wysihtml/rails/version.rb b/lib/wysihtml/rails/version.rb index 8b0b755..a7450a1 100644 --- a/lib/wysihtml/rails/version.rb +++ b/lib/wysihtml/rails/version.rb @@ -1,5 +1,5 @@ module Wysihtml module Rails - VERSION = "0.5.0.beta5" + VERSION = "0.5.0.beta6" end end diff --git a/vendor/assets/javascripts/wysihtml-toolbar.js b/vendor/assets/javascripts/wysihtml-toolbar.js index c4d01c8..c6a5508 100644 --- a/vendor/assets/javascripts/wysihtml-toolbar.js +++ b/vendor/assets/javascripts/wysihtml-toolbar.js @@ -1,5 +1,5 @@ /** - * @license wysihtml5x v0.5.0-beta5 + * @license wysihtml5x v0.5.0-beta6 * https://github.com/Edicy/wysihtml5 * * Author: Christopher Blum (https://github.com/tiff) @@ -10,7 +10,7 @@ * */ var wysihtml5 = { - version: "0.5.0-beta5", + version: "0.5.0-beta6", // namespaces commands: {}, @@ -6692,7 +6692,7 @@ wysihtml5.dom.parse = function(elementOrHtml_current, config_current) { } function _checkAttribute(attributeName, attributeValue, methodName, nodeName) { - var method = attributeCheckMethods[methodName], + var method = wysihtml5.lang.object(methodName).isFunction() ? methodName : attributeCheckMethods[methodName], newAttributeValue; if (method) { @@ -9304,6 +9304,7 @@ wysihtml5.quirks.ensureProperClearing = (function() { } }; + caretPlaceholder.className = '_wysihtml5-temp-caret-fix'; caretPlaceholder.style.position = 'absolute'; caretPlaceholder.style.display = 'block'; caretPlaceholder.style.minWidth = '1px'; @@ -9339,7 +9340,9 @@ wysihtml5.quirks.ensureProperClearing = (function() { fixWebkitSelection = function() { // Webkit fails to add selection if there are no textnodes in that region // (like an uneditable container at the end of content). - if (!sel) { + var parent = node.parentNode, + lastSibling = parent ? parent.childNodes[parent.childNodes.length - 1] : null; + if (!sel || (lastSibling === node && this.win.getComputedStyle(node).display === "block")) { if (notVisual) { // If setAfter is used as internal between actions, self-removing caretPlaceholder has simpler implementation // and remove itself in call stack end instead on user interaction @@ -9640,45 +9643,69 @@ wysihtml5.quirks.ensureProperClearing = (function() { } }, - caretIsBeforeUneditable: function() { - var selection = this.getSelection(), - node = selection.anchorNode, - offset = selection.anchorOffset, - childNodes = [], - range, contentNodes, lastNode; - - if (node) { - if (offset === 0) { - var prevNode = this.getPreviousNode(node, true), - prevLeaf = prevNode ? wysihtml5.dom.domNode(prevNode).lastLeafNode((this.unselectableClass) ? {leafClasses: [this.unselectableClass]} : false) : null; - if (prevLeaf) { - var uneditables = this.getOwnUneditables(); - for (var i = 0, maxi = uneditables.length; i < maxi; i++) { - if (prevLeaf === uneditables[i]) { - return uneditables[i]; - } - } + // Returns object describing node/text before selection + // If includePrevLeaves is true returns also previous last leaf child if selection is in the beginning of current node + getBeforeSelection: function(includePrevLeaves) { + var sel = this.getSelection(), + startNode = (sel.isBackwards()) ? sel.focusNode : sel.anchorNode, + startOffset = (sel.isBackwards()) ? sel.focusOffset : sel.anchorOffset, + rng = this.createRange(), endNode, inTmpCaret; + + // Escape temproray helper nodes if selection in them + inTmpCaret = wysihtml5.dom.getParentElement(startNode, { query: '._wysihtml5-temp-caret-fix' }, 1); + if (inTmpCaret) { + startNode = inTmpCaret.parentNode; + startOffset = Array.prototype.indexOf.call(startNode.childNodes, inTmpCaret); + } + + if (startNode) { + if (startOffset > 0) { + if (startNode.nodeType === 3) { + rng.setStart(startNode, 0); + rng.setEnd(startNode, startOffset); + return { + type: "text", + range: rng, + offset : startOffset, + node: startNode + }; + } else { + rng.setStartBefore(startNode.childNodes[0]); + endNode = startNode.childNodes[startOffset - 1]; + rng.setEndAfter(endNode); + return { + type: "element", + range: rng, + offset : startOffset, + node: endNode + }; } } else { - range = selection.getRangeAt(0); - range.setStart(range.startContainer, range.startOffset - 1); - // TODO: make getting children on range a separate funtion - if (range) { - contentNodes = range.getNodes([1,3]); - for (var n = 0, max = contentNodes.length; n < max; n++) { - if (contentNodes[n].parentNode && contentNodes[n].parentNode === node) { - childNodes.push(contentNodes[n]); - } + rng.setStartAndEnd(startNode, 0); + + if (includePrevLeaves) { + var prevNode = this.getPreviousNode(startNode, true), + prevLeaf = prevNode ? wysihtml5.dom.domNode(prevNode).lastLeafNode() : null; + + if (prevLeaf) { + return { + type: "leafnode", + range: rng, + offset : startOffset, + node: prevLeaf + }; } } - lastNode = childNodes.length > 0 ? childNodes[childNodes.length -1] : null; - if (lastNode && lastNode.nodeType === 1 && wysihtml5.dom.hasClass(lastNode, this.unselectableClass)) { - return lastNode; - } + return { + type: "none", + range: rng, + offset : startOffset, + node: startNode + }; } } - return false; + return null; }, // TODO: Figure out a method from following 2 that would work universally @@ -9758,7 +9785,7 @@ wysihtml5.quirks.ensureProperClearing = (function() { } this.setSelection(newRange); for (var i = caretPlaceholder.length; i--;) { - caretPlaceholder[i].parentNode.removeChild(caretPlaceholder[i]); + caretPlaceholder[i].parentNode.removeChild(caretPlaceholder[i]); } } else { @@ -12414,7 +12441,7 @@ wysihtml5.Commands = Base.extend( for (row = 0; row < value.rows; row ++) { html += ''; for (col = 0; col < value.cols; col ++) { - html += " "; + html += ""; } html += ''; } @@ -13670,59 +13697,98 @@ wysihtml5.views.View = Base.extend( } }; + // Override for giving user ability to delete last line break in table cell + var fixLastBrDeletionInTable = function(composer, force) { + if (composer.selection.caretIsLastInSelection()) { + var sel = composer.selection.getSelection(), + aNode = sel.anchorNode; + if (aNode && aNode.nodeType === 1 && (wysihtml5.dom.getParentElement(aNode, {query: 'td, th'}, false, composer.element) || force)) { + var nextNode = aNode.childNodes[sel.anchorOffset]; + if (nextNode && nextNode.nodeType === 1 & nextNode.nodeName === "BR") { + nextNode.parentNode.removeChild(nextNode); + return true; + } + } + } + return false; + }; + + // If found an uneditable before caret then notify it before deletion + var handleUneditableDeletion = function(composer) { + var before = composer.selection.getBeforeSelection(true); + if (before && (before.type === "element" || before.type === "leafnode") && before.node.nodeType === 1 && before.node.classList.contains(composer.config.uneditableContainerClassname)) { + if (fixLastBrDeletionInTable(composer, true)) { + return true; + } + try { + var ev = new CustomEvent("wysihtml5:uneditable:delete"); + before.node.dispatchEvent(ev); + } catch (err) {} + before.node.parentNode.removeChild(before.node); + return true; + } + return false; + }; + + // Deletion with caret in the beginning of headings needs special attention + // Heading does not concate text to previous block node correctly (browsers do unexpected miracles here especially webkit) + var fixDeleteInTheBeginnigOfHeading = function(composer) { + var selection = composer.selection; + + if (selection.caretIsFirstInSelection() && + selection.getPreviousNode() && + selection.getPreviousNode().nodeName && + (/^H\d$/gi).test(selection.getPreviousNode().nodeName) + ) { + var prevNode = selection.getPreviousNode(); + if ((/^\s*$/).test(prevNode.textContent || prevNode.innerText)) { + // If heading is empty remove the heading node + prevNode.parentNode.removeChild(prevNode); + return true; + } else { + if (prevNode.lastChild) { + var selNode = prevNode.lastChild, + curNode = wysihtml5.dom.getParentElement(selection.getSelectedNode(), { query: "h1, h2, h3, h4, h5, h6, p, pre, div, blockquote" }, false, composer.element); + if (prevNode) { + if (curNode) { + while (curNode.firstChild) { + prevNode.appendChild(curNode.firstChild); + } + selection.setAfter(selNode); + return true; + } else if (selection.getSelectedNode().nodeType === 3) { + prevNode.appendChild(selection.getSelectedNode()); + selection.setAfter(selNode); + return true; + } + } + } + } + } + return false; + }; + var handleDeleteKeyPress = function(event, composer) { var selection = composer.selection, element = composer.element; if (selection.isCollapsed()) { if (selection.caretIsInTheBeginnig('li')) { + // delete in the beginnig of LI will outdent not delete event.preventDefault(); composer.commands.exec('outdentList'); - } else if (selection.caretIsInTheBeginnig()) { - event.preventDefault(); } else { - if (selection.caretIsFirstInSelection() && - selection.getPreviousNode() && - selection.getPreviousNode().nodeName && - (/^H\d$/gi).test(selection.getPreviousNode().nodeName) - ) { - var prevNode = selection.getPreviousNode(); - if ((/^\s*$/).test(prevNode.textContent || prevNode.innerText)) { - // heading is empty - event.preventDefault(); - prevNode.parentNode.removeChild(prevNode); - } else { - if (prevNode.lastChild) { - var selNode = prevNode.lastChild, - curNode = wysihtml5.dom.getParentElement(selection.getSelectedNode(), { query: "h1, h2, h3, h4, h5, h6, p, pre, div, blockquote" }, false, composer.element); - if (prevNode) { - if (curNode) { - event.preventDefault(); - while (curNode.firstChild) { - prevNode.appendChild(curNode.firstChild); - } - selection.setAfter(selNode); - } else if (selection.getSelectedNode().nodeType === 3) { - event.preventDefault(); - prevNode.appendChild(selection.getSelectedNode()); - selection.setAfter(selNode); - } - } - } - } + if (fixDeleteInTheBeginnigOfHeading(composer)) { + event.preventDefault(); + return; } - - var beforeUneditable = selection.caretIsBeforeUneditable(); - // Do a special delete if caret would delete uneditable - if (beforeUneditable) { + if (fixLastBrDeletionInTable(composer)) { event.preventDefault(); - // If customevents present notify element of being deleted - // TODO: Investigate if browser support can be extended - try { - var ev = new CustomEvent("wysihtml5:uneditable:delete"); - beforeUneditable.dispatchEvent(ev); - } catch (err) {} - beforeUneditable.parentNode.removeChild(beforeUneditable); + return; + } + if (handleUneditableDeletion(composer)) { + event.preventDefault(); + return; } } } else { @@ -14089,6 +14155,61 @@ wysihtml5.views.View = Base.extend( } }); })(wysihtml5); +;(function(wysihtml5) { + + wysihtml5.views.SourceView = Base.extend( + /** @scope wysihtml5.views.SourceView.prototype */ { + + constructor: function(editor, composer) { + this.editor = editor; + this.composer = composer; + + this._observe(); + }, + + switchToTextarea: function(shouldParseHtml) { + var composerStyles = this.composer.win.getComputedStyle(this.composer.element), + width = parseFloat(composerStyles.width), + height = Math.max(parseFloat(composerStyles.height), 100); + + if (!this.textarea) { + this.textarea = this.composer.doc.createElement('textarea'); + this.textarea.className = "wysihtml5-source-view"; + } + this.textarea.style.width = width + 'px'; + this.textarea.style.height = height + 'px'; + this.textarea.value = this.editor.getValue(shouldParseHtml, true); + this.composer.element.parentNode.insertBefore(this.textarea, this.composer.element); + this.editor.currentView = "source"; + this.composer.element.style.display = 'none'; + }, + + switchToComposer: function(shouldParseHtml) { + var textareaValue = this.textarea.value; + if (textareaValue) { + this.composer.setValue(textareaValue, shouldParseHtml); + } else { + this.composer.clear(); + this.editor.fire("set_placeholder"); + } + this.textarea.parentNode.removeChild(this.textarea); + this.editor.currentView = this.composer; + this.composer.element.style.display = ''; + }, + + _observe: function() { + this.editor.on("change_view", function(view) { + if (view === "composer") { + this.switchToComposer(true); + } else if (view === "textarea") { + this.switchToTextarea(true); + } + }.bind(this)); + } + + }); + +})(wysihtml5); ;wysihtml5.views.Textarea = wysihtml5.views.View.extend( /** @scope wysihtml5.views.Textarea.prototype */ { name: "textarea", @@ -14281,7 +14402,9 @@ wysihtml5.views.View = Base.extend( handleBeforeLoad: function() { if (!this.config.noTextarea) { - this.synchronizer = new wysihtml5.views.Synchronizer(this, this.textarea, this.composer); + this.synchronizer = new wysihtml5.views.Synchronizer(this, this.textarea, this.composer); + } else { + this.sourceView = new wysihtml5.views.SourceView(this, this.composer); } if (this.config.toolbar) { this.toolbar = new wysihtml5.toolbar.Toolbar(this, this.config.toolbar, this.config.showToolbarAfterInit); @@ -14847,12 +14970,10 @@ wysihtml5.views.View = Base.extend( execAction: function(action) { var editor = this.editor; if (action === "change_view") { - if (editor.textarea) { - if (editor.currentView === editor.textarea) { - editor.fire("change_view", "composer"); - } else { - editor.fire("change_view", "textarea"); - } + if (editor.currentView === editor.textarea || editor.currentView === "source") { + editor.fire("change_view", "composer"); + } else { + editor.fire("change_view", "textarea"); } } if (action == "showSource") { @@ -14917,17 +15038,15 @@ wysihtml5.views.View = Base.extend( editor.on("change_view", function(currentView) { // Set timeout needed in order to let the blur event fire first - if (editor.textarea) { - setTimeout(function() { - that.commandsDisabled = (currentView !== "composer"); - that._updateLinkStates(); - if (that.commandsDisabled) { - dom.addClass(container, CLASS_NAME_COMMANDS_DISABLED); - } else { - dom.removeClass(container, CLASS_NAME_COMMANDS_DISABLED); - } - }, 0); - } + setTimeout(function() { + that.commandsDisabled = (currentView !== "composer"); + that._updateLinkStates(); + if (that.commandsDisabled) { + dom.addClass(container, CLASS_NAME_COMMANDS_DISABLED); + } else { + dom.removeClass(container, CLASS_NAME_COMMANDS_DISABLED); + } + }, 0); }); }, @@ -15008,7 +15127,7 @@ wysihtml5.views.View = Base.extend( action = actionMapping[i]; if (action.name === "change_view") { - action.state = this.editor.currentView === this.editor.textarea; + action.state = this.editor.currentView === this.editor.textarea || this.editor.currentView === "source"; if (action.state) { dom.addClass(action.link, CLASS_NAME_ACTION_ACTIVE); } else { diff --git a/vendor/assets/javascripts/wysihtml.js b/vendor/assets/javascripts/wysihtml.js index cc24f0e..b870c23 100644 --- a/vendor/assets/javascripts/wysihtml.js +++ b/vendor/assets/javascripts/wysihtml.js @@ -1,5 +1,5 @@ /** - * @license wysihtml5x v0.5.0-beta5 + * @license wysihtml5x v0.5.0-beta6 * https://github.com/Edicy/wysihtml5 * * Author: Christopher Blum (https://github.com/tiff) @@ -10,7 +10,7 @@ * */ var wysihtml5 = { - version: "0.5.0-beta5", + version: "0.5.0-beta6", // namespaces commands: {}, @@ -6692,7 +6692,7 @@ wysihtml5.dom.parse = function(elementOrHtml_current, config_current) { } function _checkAttribute(attributeName, attributeValue, methodName, nodeName) { - var method = attributeCheckMethods[methodName], + var method = wysihtml5.lang.object(methodName).isFunction() ? methodName : attributeCheckMethods[methodName], newAttributeValue; if (method) { @@ -9304,6 +9304,7 @@ wysihtml5.quirks.ensureProperClearing = (function() { } }; + caretPlaceholder.className = '_wysihtml5-temp-caret-fix'; caretPlaceholder.style.position = 'absolute'; caretPlaceholder.style.display = 'block'; caretPlaceholder.style.minWidth = '1px'; @@ -9339,7 +9340,9 @@ wysihtml5.quirks.ensureProperClearing = (function() { fixWebkitSelection = function() { // Webkit fails to add selection if there are no textnodes in that region // (like an uneditable container at the end of content). - if (!sel) { + var parent = node.parentNode, + lastSibling = parent ? parent.childNodes[parent.childNodes.length - 1] : null; + if (!sel || (lastSibling === node && this.win.getComputedStyle(node).display === "block")) { if (notVisual) { // If setAfter is used as internal between actions, self-removing caretPlaceholder has simpler implementation // and remove itself in call stack end instead on user interaction @@ -9640,45 +9643,69 @@ wysihtml5.quirks.ensureProperClearing = (function() { } }, - caretIsBeforeUneditable: function() { - var selection = this.getSelection(), - node = selection.anchorNode, - offset = selection.anchorOffset, - childNodes = [], - range, contentNodes, lastNode; - - if (node) { - if (offset === 0) { - var prevNode = this.getPreviousNode(node, true), - prevLeaf = prevNode ? wysihtml5.dom.domNode(prevNode).lastLeafNode((this.unselectableClass) ? {leafClasses: [this.unselectableClass]} : false) : null; - if (prevLeaf) { - var uneditables = this.getOwnUneditables(); - for (var i = 0, maxi = uneditables.length; i < maxi; i++) { - if (prevLeaf === uneditables[i]) { - return uneditables[i]; - } - } + // Returns object describing node/text before selection + // If includePrevLeaves is true returns also previous last leaf child if selection is in the beginning of current node + getBeforeSelection: function(includePrevLeaves) { + var sel = this.getSelection(), + startNode = (sel.isBackwards()) ? sel.focusNode : sel.anchorNode, + startOffset = (sel.isBackwards()) ? sel.focusOffset : sel.anchorOffset, + rng = this.createRange(), endNode, inTmpCaret; + + // Escape temproray helper nodes if selection in them + inTmpCaret = wysihtml5.dom.getParentElement(startNode, { query: '._wysihtml5-temp-caret-fix' }, 1); + if (inTmpCaret) { + startNode = inTmpCaret.parentNode; + startOffset = Array.prototype.indexOf.call(startNode.childNodes, inTmpCaret); + } + + if (startNode) { + if (startOffset > 0) { + if (startNode.nodeType === 3) { + rng.setStart(startNode, 0); + rng.setEnd(startNode, startOffset); + return { + type: "text", + range: rng, + offset : startOffset, + node: startNode + }; + } else { + rng.setStartBefore(startNode.childNodes[0]); + endNode = startNode.childNodes[startOffset - 1]; + rng.setEndAfter(endNode); + return { + type: "element", + range: rng, + offset : startOffset, + node: endNode + }; } } else { - range = selection.getRangeAt(0); - range.setStart(range.startContainer, range.startOffset - 1); - // TODO: make getting children on range a separate funtion - if (range) { - contentNodes = range.getNodes([1,3]); - for (var n = 0, max = contentNodes.length; n < max; n++) { - if (contentNodes[n].parentNode && contentNodes[n].parentNode === node) { - childNodes.push(contentNodes[n]); - } + rng.setStartAndEnd(startNode, 0); + + if (includePrevLeaves) { + var prevNode = this.getPreviousNode(startNode, true), + prevLeaf = prevNode ? wysihtml5.dom.domNode(prevNode).lastLeafNode() : null; + + if (prevLeaf) { + return { + type: "leafnode", + range: rng, + offset : startOffset, + node: prevLeaf + }; } } - lastNode = childNodes.length > 0 ? childNodes[childNodes.length -1] : null; - if (lastNode && lastNode.nodeType === 1 && wysihtml5.dom.hasClass(lastNode, this.unselectableClass)) { - return lastNode; - } + return { + type: "none", + range: rng, + offset : startOffset, + node: startNode + }; } } - return false; + return null; }, // TODO: Figure out a method from following 2 that would work universally @@ -9758,7 +9785,7 @@ wysihtml5.quirks.ensureProperClearing = (function() { } this.setSelection(newRange); for (var i = caretPlaceholder.length; i--;) { - caretPlaceholder[i].parentNode.removeChild(caretPlaceholder[i]); + caretPlaceholder[i].parentNode.removeChild(caretPlaceholder[i]); } } else { @@ -12414,7 +12441,7 @@ wysihtml5.Commands = Base.extend( for (row = 0; row < value.rows; row ++) { html += ''; for (col = 0; col < value.cols; col ++) { - html += " "; + html += ""; } html += ''; } @@ -13670,59 +13697,98 @@ wysihtml5.views.View = Base.extend( } }; + // Override for giving user ability to delete last line break in table cell + var fixLastBrDeletionInTable = function(composer, force) { + if (composer.selection.caretIsLastInSelection()) { + var sel = composer.selection.getSelection(), + aNode = sel.anchorNode; + if (aNode && aNode.nodeType === 1 && (wysihtml5.dom.getParentElement(aNode, {query: 'td, th'}, false, composer.element) || force)) { + var nextNode = aNode.childNodes[sel.anchorOffset]; + if (nextNode && nextNode.nodeType === 1 & nextNode.nodeName === "BR") { + nextNode.parentNode.removeChild(nextNode); + return true; + } + } + } + return false; + }; + + // If found an uneditable before caret then notify it before deletion + var handleUneditableDeletion = function(composer) { + var before = composer.selection.getBeforeSelection(true); + if (before && (before.type === "element" || before.type === "leafnode") && before.node.nodeType === 1 && before.node.classList.contains(composer.config.uneditableContainerClassname)) { + if (fixLastBrDeletionInTable(composer, true)) { + return true; + } + try { + var ev = new CustomEvent("wysihtml5:uneditable:delete"); + before.node.dispatchEvent(ev); + } catch (err) {} + before.node.parentNode.removeChild(before.node); + return true; + } + return false; + }; + + // Deletion with caret in the beginning of headings needs special attention + // Heading does not concate text to previous block node correctly (browsers do unexpected miracles here especially webkit) + var fixDeleteInTheBeginnigOfHeading = function(composer) { + var selection = composer.selection; + + if (selection.caretIsFirstInSelection() && + selection.getPreviousNode() && + selection.getPreviousNode().nodeName && + (/^H\d$/gi).test(selection.getPreviousNode().nodeName) + ) { + var prevNode = selection.getPreviousNode(); + if ((/^\s*$/).test(prevNode.textContent || prevNode.innerText)) { + // If heading is empty remove the heading node + prevNode.parentNode.removeChild(prevNode); + return true; + } else { + if (prevNode.lastChild) { + var selNode = prevNode.lastChild, + curNode = wysihtml5.dom.getParentElement(selection.getSelectedNode(), { query: "h1, h2, h3, h4, h5, h6, p, pre, div, blockquote" }, false, composer.element); + if (prevNode) { + if (curNode) { + while (curNode.firstChild) { + prevNode.appendChild(curNode.firstChild); + } + selection.setAfter(selNode); + return true; + } else if (selection.getSelectedNode().nodeType === 3) { + prevNode.appendChild(selection.getSelectedNode()); + selection.setAfter(selNode); + return true; + } + } + } + } + } + return false; + }; + var handleDeleteKeyPress = function(event, composer) { var selection = composer.selection, element = composer.element; if (selection.isCollapsed()) { if (selection.caretIsInTheBeginnig('li')) { + // delete in the beginnig of LI will outdent not delete event.preventDefault(); composer.commands.exec('outdentList'); - } else if (selection.caretIsInTheBeginnig()) { - event.preventDefault(); } else { - if (selection.caretIsFirstInSelection() && - selection.getPreviousNode() && - selection.getPreviousNode().nodeName && - (/^H\d$/gi).test(selection.getPreviousNode().nodeName) - ) { - var prevNode = selection.getPreviousNode(); - if ((/^\s*$/).test(prevNode.textContent || prevNode.innerText)) { - // heading is empty - event.preventDefault(); - prevNode.parentNode.removeChild(prevNode); - } else { - if (prevNode.lastChild) { - var selNode = prevNode.lastChild, - curNode = wysihtml5.dom.getParentElement(selection.getSelectedNode(), { query: "h1, h2, h3, h4, h5, h6, p, pre, div, blockquote" }, false, composer.element); - if (prevNode) { - if (curNode) { - event.preventDefault(); - while (curNode.firstChild) { - prevNode.appendChild(curNode.firstChild); - } - selection.setAfter(selNode); - } else if (selection.getSelectedNode().nodeType === 3) { - event.preventDefault(); - prevNode.appendChild(selection.getSelectedNode()); - selection.setAfter(selNode); - } - } - } - } + if (fixDeleteInTheBeginnigOfHeading(composer)) { + event.preventDefault(); + return; } - - var beforeUneditable = selection.caretIsBeforeUneditable(); - // Do a special delete if caret would delete uneditable - if (beforeUneditable) { + if (fixLastBrDeletionInTable(composer)) { event.preventDefault(); - // If customevents present notify element of being deleted - // TODO: Investigate if browser support can be extended - try { - var ev = new CustomEvent("wysihtml5:uneditable:delete"); - beforeUneditable.dispatchEvent(ev); - } catch (err) {} - beforeUneditable.parentNode.removeChild(beforeUneditable); + return; + } + if (handleUneditableDeletion(composer)) { + event.preventDefault(); + return; } } } else { @@ -14089,6 +14155,61 @@ wysihtml5.views.View = Base.extend( } }); })(wysihtml5); +;(function(wysihtml5) { + + wysihtml5.views.SourceView = Base.extend( + /** @scope wysihtml5.views.SourceView.prototype */ { + + constructor: function(editor, composer) { + this.editor = editor; + this.composer = composer; + + this._observe(); + }, + + switchToTextarea: function(shouldParseHtml) { + var composerStyles = this.composer.win.getComputedStyle(this.composer.element), + width = parseFloat(composerStyles.width), + height = Math.max(parseFloat(composerStyles.height), 100); + + if (!this.textarea) { + this.textarea = this.composer.doc.createElement('textarea'); + this.textarea.className = "wysihtml5-source-view"; + } + this.textarea.style.width = width + 'px'; + this.textarea.style.height = height + 'px'; + this.textarea.value = this.editor.getValue(shouldParseHtml, true); + this.composer.element.parentNode.insertBefore(this.textarea, this.composer.element); + this.editor.currentView = "source"; + this.composer.element.style.display = 'none'; + }, + + switchToComposer: function(shouldParseHtml) { + var textareaValue = this.textarea.value; + if (textareaValue) { + this.composer.setValue(textareaValue, shouldParseHtml); + } else { + this.composer.clear(); + this.editor.fire("set_placeholder"); + } + this.textarea.parentNode.removeChild(this.textarea); + this.editor.currentView = this.composer; + this.composer.element.style.display = ''; + }, + + _observe: function() { + this.editor.on("change_view", function(view) { + if (view === "composer") { + this.switchToComposer(true); + } else if (view === "textarea") { + this.switchToTextarea(true); + } + }.bind(this)); + } + + }); + +})(wysihtml5); ;wysihtml5.views.Textarea = wysihtml5.views.View.extend( /** @scope wysihtml5.views.Textarea.prototype */ { name: "textarea", @@ -14281,7 +14402,9 @@ wysihtml5.views.View = Base.extend( handleBeforeLoad: function() { if (!this.config.noTextarea) { - this.synchronizer = new wysihtml5.views.Synchronizer(this, this.textarea, this.composer); + this.synchronizer = new wysihtml5.views.Synchronizer(this, this.textarea, this.composer); + } else { + this.sourceView = new wysihtml5.views.SourceView(this, this.composer); } if (this.config.toolbar) { this.toolbar = new wysihtml5.toolbar.Toolbar(this, this.config.toolbar, this.config.showToolbarAfterInit);