diff --git a/spec/extension.spec.js b/spec/extension.spec.js index 0ef322567..de05357ae 100644 --- a/spec/extension.spec.js +++ b/spec/extension.spec.js @@ -285,6 +285,26 @@ describe('Extensions TestCase', function () { expect(tempExtension.getEditorOption('spellcheck')).toBe(editor.options.spellcheck); }); + it('should be able to manipulate the html via setContent() and getContent()', function () { + var TempExtension = MediumEditor.Extension.extend({ + setContent: function (html) { + return html.replace(/replace/g, '*******'); + }, + getContent: function (html) { + return html.replace(/\*\*\*\*\*\*\*/g, 'replace'); + } + }), + editable = this.createElement('div', 'editable', '

replace

'), + editor = this.newMediumEditor('.editable', { + extensions: { + 'temp-extension': new TempExtension() + } + }); + + expect(editable.innerHTML).toBe('

*******

'); + expect(editor.getContent()).toBe('

replace

'); + }); + it('should be able to prevent blur on the editor when user iteracts with extension elements', function () { var sampleElOne = this.createElement('button', null, 'Test Button'), sampleElTwo = this.createElement('textarea', null, 'Test Div'), diff --git a/spec/toolbar.spec.js b/spec/toolbar.spec.js index 50fcc7a2e..a405e5f1f 100644 --- a/spec/toolbar.spec.js +++ b/spec/toolbar.spec.js @@ -528,12 +528,15 @@ describe('MediumEditor.extensions.toolbar TestCase', function () { expect(document.getElementsByClassName('medium-editor-toolbar-actions').length).toBe(0); }); + /* + // Only disable using options, it is possible to add elements later who don't have the disable-toolbar attr and will not work it('should not create the toolbar if all elements has data attr of disable-toolbar', function () { this.el.setAttribute('data-disable-toolbar', 'true'); var editor = this.newMediumEditor('.editor'); expect(document.getElementsByClassName('medium-editor-toolbar-actions').length).toBe(0); expect(editor.getExtensionByName('toolbar')).toBeUndefined(); }); + */ it('should not show the toolbar when one element has a data attr of disable-toolbar set and text is selected', function () { var element = this.createElement('div', 'editor', 'lorem ipsum'), diff --git a/spec/util.spec.js b/spec/util.spec.js index de688b2bc..568103798 100644 --- a/spec/util.spec.js +++ b/spec/util.spec.js @@ -389,9 +389,10 @@ describe('MediumEditor.util', function () { it('should execute indent command when called with blockquote when isIE is true', function () { var origIsIE = MediumEditor.util.isIE, - el = this.createElement('div', '', '

Some Text

'); + el = this.createElement('div', 'editable', '

Some Text

'); + this.newMediumEditor('.editable'); MediumEditor.util.isIE = true; - el.setAttribute('contenteditable', true); + //el.setAttribute('contenteditable', true); selectElementContents(el.querySelector('b')); spyOn(document, 'execCommand'); @@ -570,27 +571,25 @@ describe('MediumEditor.util', function () { }); it('should return the parent editable if element is a text node child of the editor', function () { - var el = this.createElement('div', 'editable', '

text

'), - emptyTextNode = el.firstChild; + var el = this.createElement('div', 'editable', '

text

'); this.newMediumEditor('.editable'); - var container = MediumEditor.util.getClosestBlockContainer(emptyTextNode); + var container = MediumEditor.util.getClosestBlockContainer(el.firstChild); expect(container).toBe(el); }); }); describe('getTopBlockContainer', function () { it('should return the highest level block container', function () { - var el = this.createElement('div', '', '

paragraph

'), - span = el.querySelector('span'), - container = MediumEditor.util.getTopBlockContainer(span); + var el = this.createElement('div', 'editable', '

paragraph

'); + this.newMediumEditor('.editable'); + var container = MediumEditor.util.getTopBlockContainer(el.querySelector('span')); expect(container).toBe(el.querySelector('blockquote')); }); it('should return the parent editable if element is a text node child of the editor', function () { - var el = this.createElement('div', 'editable', '

text

'), - emptyTextNode = el.firstChild; + var el = this.createElement('div', 'editable', '

text

'); this.newMediumEditor('.editable'); - var container = MediumEditor.util.getTopBlockContainer(emptyTextNode); + var container = MediumEditor.util.getTopBlockContainer(el.firstChild); expect(container).toBe(el); }); }); @@ -613,10 +612,10 @@ describe('MediumEditor.util', function () { }); it('should not find a previous sibling if the element is at the beginning of an editor element', function () { - var el = this.createElement('div', 'editable', '

first second third

'), - first = el.querySelector('p').firstChild; + var el = this.createElement('div', 'editable', '

first second third

'); this.newMediumEditor('.editable'); - var prevSibling = MediumEditor.util.findPreviousSibling(first); + var first = el.querySelector('p').firstChild, + prevSibling = MediumEditor.util.findPreviousSibling(first); expect(prevSibling).toBeFalsy(); }); }); diff --git a/src/js/core.js b/src/js/core.js index e71455b95..b4f13391e 100644 --- a/src/js/core.js +++ b/src/js/core.js @@ -195,12 +195,19 @@ this.options.ownerDocument.execCommand('formatBlock', false, 'p'); } } + + if (MediumEditor.util.isKey(event, MediumEditor.util.keyCode.ENTER) && node.nodeName.toLowerCase() === 'div') { + this.options.ownerDocument.execCommand('formatBlock', false, 'p'); + } } function handleEditableInput(event, editable) { var textarea = editable.parentNode.querySelector('textarea[medium-editor-textarea-id="' + editable.getAttribute('medium-editor-textarea-id') + '"]'); if (textarea) { - textarea.value = editable.innerHTML.trim(); + var index = this.elements.indexOf(editable); + if (index !== -1) { + textarea.value = this.getContent(index); + } } } @@ -315,14 +322,6 @@ } function isToolbarEnabled() { - // If any of the elements don't have the toolbar disabled - // We need a toolbar - if (this.elements.every(function (element) { - return !!element.getAttribute('data-disable-toolbar'); - })) { - return false; - } - return this.options.toolbar !== false; } @@ -694,17 +693,13 @@ this.events = new MediumEditor.Events(this); this.elements = []; - this.addElements(this.origElements); - - if (this.elements.length === 0) { - return; - } - - this.isActive = true; - // Call initialization helpers initExtensions.call(this); attachHandlers.call(this); + + this.addElements(this.origElements); + + this.isActive = true; }, destroy: function () { @@ -722,11 +717,9 @@ this.events.destroy(); - this.elements.forEach(function (element) { - // Reset elements content, fix for issue where after editor destroyed the red underlines on spelling errors are left - if (this.options.spellcheck) { - element.innerHTML = element.innerHTML; - } + this.elements.forEach(function (element, i) { + // Call the getContent to set innerHTML, extensions can do some cleanup + element.innerHTML = this.getContent(i); // cleanup extra added attributes element.removeAttribute('contentEditable'); @@ -798,7 +791,7 @@ for (i = 0; i < len; i += 1) { elementid = (this.elements[i].id !== '') ? this.elements[i].id : 'element-' + i; content[elementid] = { - value: this.elements[i].innerHTML.trim() + value: this.getContent(i) }; } return content; @@ -1190,7 +1183,15 @@ if (this.elements[index]) { var target = this.elements[index]; + + this.extensions.forEach(function (extension) { + if (typeof extension.setContent === 'function') { + html = extension.setContent(html); + } + }, this); + target.innerHTML = html; + this.checkContentChanged(target); } }, @@ -1199,8 +1200,17 @@ index = index || 0; if (this.elements[index]) { - return this.elements[index].innerHTML.trim(); + var html = this.elements[index].innerHTML.trim(); + + this.extensions.forEach(function (extension) { + if (typeof extension.getContent === 'function') { + html = extension.getContent(html); + } + }, this); + + return html; } + return null; }, @@ -1243,6 +1253,8 @@ // Add new elements to our internal elements array this.elements.push(element); + this.setContent(element.innerHTML, this.elements.length - 1); + // Trigger event so extensions can know when an element has been added this.trigger('addElement', { target: element, currentTarget: element }, element); }, this); diff --git a/src/js/util.js b/src/js/util.js index f3007be27..3644642da 100644 --- a/src/js/util.js +++ b/src/js/util.js @@ -112,7 +112,7 @@ // all other known block elements 'address', 'article', 'aside', 'audio', 'canvas', 'dd', 'dl', 'dt', 'fieldset', 'figcaption', 'figure', 'footer', 'form', 'header', 'hgroup', 'main', 'nav', - 'noscript', 'output', 'section', 'video', + 'noscript', 'output', 'section', 'video', 'div', 'table', 'thead', 'tbody', 'tfoot', 'tr', 'th', 'td' ], @@ -545,6 +545,21 @@ tagName = '<' + tagName + '>'; } + if (blockContainer) { + var blockTagName = blockContainer.nodeName.toLowerCase(); + + if (tagName !== blockTagName) { + // Changing list items to something else breaks, remove list item first + if (blockTagName === 'ul') { + doc.execCommand('insertunorderedlist', false); + } + + if (blockTagName === 'ol') { + doc.execCommand('insertorderedlist', false); + } + } + } + // When FF, IE and Edge, we have to handle blockquote node seperately as 'formatblock' does not work. // https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand#Commands if (blockContainer && blockContainer.nodeName.toLowerCase() === 'blockquote') { @@ -926,7 +941,7 @@ }, isBlockContainer: function (element) { - return element && element.nodeType !== 3 && Util.blockContainerElementNames.indexOf(element.nodeName.toLowerCase()) !== -1; + return element && element.nodeType !== 3 && !Util.isMediumEditorElement(element) && Util.blockContainerElementNames.indexOf(element.nodeName.toLowerCase()) !== -1; }, /* Finds the closest ancestor which is a block container element