diff --git a/bower.json b/bower.json index 65aca1467..39fa6ef2b 100644 --- a/bower.json +++ b/bower.json @@ -29,7 +29,7 @@ "Gruntfile.js", "demo", "package.json", - "src/js", + "src", "README.md", "CHANGES.md" ] diff --git a/spec/setup.spec.js b/spec/setup.spec.js index 85593783a..b003be887 100644 --- a/spec/setup.spec.js +++ b/spec/setup.spec.js @@ -48,6 +48,12 @@ describe('Setup/Destroy TestCase', function () { spyOn(MediumEditor.prototype, 'setup').and.callThrough(); editor.setup(); expect(editor.setup).toHaveBeenCalled(); + expect(document.querySelector('[data-medium-element]')).toBeTruthy(); + expect(document.querySelector('[aria-multiline]')).toBeTruthy(); + expect(document.querySelector('[medium-editor-index]')).toBeTruthy(); + expect(document.querySelector('[role]')).toBeTruthy(); + expect(document.querySelector('[spellcheck]')).toBeTruthy(); + expect(document.querySelector('[contenteditable]')).toBeTruthy(); }); it('should know about defaults', function () { @@ -61,6 +67,9 @@ describe('Setup/Destroy TestCase', function () { expect(document.querySelector('.medium-editor-toolbar')).toBeTruthy(); editor.destroy(); expect(document.querySelector('.medium-editor-toolbar')).toBeFalsy(); + // ensure only initial attributes are here: the editor class + expect(this.el.getAttribute('class')).toBe('editor'); + expect(this.el.attributes.length).toBe(1); }); it('should remove all the added events', function () { diff --git a/spec/textarea.spec.js b/spec/textarea.spec.js index 441b3e2e2..f255de638 100644 --- a/spec/textarea.spec.js +++ b/spec/textarea.spec.js @@ -54,6 +54,25 @@ describe('Textarea TestCase', function () { expect(editor.elements[0].className).toBe('editor test-class test-class-2'); }); + it('should create unique div ids for multiple textareas', function () { + var tas = []; + for (var i = 0; i < 12; i++) { + var ta = document.createElement('textarea'); + ta.className = 'editor'; + ta.value = 'test content'; + document.body.appendChild(ta); + tas.push(ta); + } + var editor = this.newMediumEditor('.editor'); + editor.elements.forEach(function (el) { + expect(document.querySelectorAll('div#' + el.id).length).toEqual(1); + }); + editor.destroy(); + tas.forEach(function (el) { + document.body.removeChild(el); + }); + }); + it('should cleanup after destroy', function () { var editor = this.newMediumEditor('.editor'); expect(this.el.classList.contains('medium-editor-hidden')).toBe(true); diff --git a/src/js/core.js b/src/js/core.js index 41e7b8855..4f871da52 100644 --- a/src/js/core.js +++ b/src/js/core.js @@ -177,9 +177,9 @@ function MediumEditor(elements, options) { // Loop through elements and convert textarea's into divs this.elements = []; - elements.forEach(function (element) { + elements.forEach(function (element, index) { if (element.tagName.toLowerCase() === 'textarea') { - this.elements.push(createContentEditable.call(this, element)); + this.elements.push(createContentEditable.call(this, element, index)); } else { this.elements.push(element); } @@ -285,9 +285,9 @@ function MediumEditor(elements, options) { return this.options.imageDragging !== false; } - function createContentEditable(textarea) { + function createContentEditable(textarea, id) { var div = this.options.ownerDocument.createElement('div'), - id = (+new Date()), + uniqueId = 'medium-editor-' + Date.now() + '-' + id, attributesToClone = [ 'data-disable-editing', 'data-disable-toolbar', @@ -299,7 +299,7 @@ function MediumEditor(elements, options) { ]; div.className = textarea.className; - div.id = id; + div.id = uniqueId; div.innerHTML = textarea.value; div.setAttribute('medium-editor-textarea-id', id); attributesToClone.forEach(function (attr) { @@ -637,6 +637,8 @@ function MediumEditor(elements, options) { delete this.toolbar; } + 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) { @@ -646,6 +648,9 @@ function MediumEditor(elements, options) { element.removeAttribute('contentEditable'); element.removeAttribute('spellcheck'); element.removeAttribute('data-medium-element'); + element.removeAttribute('medium-editor-index'); + element.removeAttribute('role'); + element.removeAttribute('aria-multiline'); // Remove any elements created for textareas if (element.hasAttribute('medium-editor-textarea-id')) { @@ -660,8 +665,6 @@ function MediumEditor(elements, options) { } }, this); this.elements = []; - - this.events.destroy(); }, on: function (target, event, listener, useCapture) { diff --git a/src/js/events.js b/src/js/events.js index fdb5e0a41..5ffcd4004 100644 --- a/src/js/events.js +++ b/src/js/events.js @@ -95,6 +95,12 @@ var Events; this.detachAllDOMEvents(); this.detachAllCustomEvents(); this.detachExecCommand(); + + if (this.base.elements) { + this.base.elements.forEach(function (element) { + element.removeAttribute('data-medium-focused'); + }); + } }, // Listening to calls to document.execCommand diff --git a/src/js/toolbar.js b/src/js/toolbar.js index b773cab9e..71a7ebf97 100644 --- a/src/js/toolbar.js +++ b/src/js/toolbar.js @@ -261,10 +261,10 @@ var Toolbar; // Checks for existance of multiple block elements in the current selection multipleBlockElementsSelected: function () { - /*jslint regexp: true*/ - var selectionHtml = Selection.getSelectionHtml.call(this).replace(/<[\S]+><\/[\S]+>/gim, ''), - hasMultiParagraphs = selectionHtml.match(/<(p|h[1-6]|blockquote)[^>]*>/g); - /*jslint regexp: false*/ + var regexEmptyHTMLTags = /<[^\/>][^>]*><\/[^>]+>/gim, // http://stackoverflow.com/questions/3129738/remove-empty-tags-using-regex + regexBlockElements = new RegExp('<(' + Util.parentElements.join('|') + ')[^>]*>', 'g'), + selectionHTML = Selection.getSelectionHtml.call(this).replace(regexEmptyHTMLTags, ''), // Filter out empty blocks from selection + hasMultiParagraphs = selectionHTML.match(regexBlockElements); // Find how many block elements are within the html return !!hasMultiParagraphs && hasMultiParagraphs.length > 1; },