medium-editor
Advanced tools
Comparing version 5.5.1 to 5.5.2
@@ -0,1 +1,10 @@ | ||
5.5.2 / 2015-08-02 | ||
================== | ||
* Fix issue where block elements where cleaned up incorrectly when pasting | ||
* Fix anchor form checkboxes to reflect status of selected link | ||
* Fix issue with creating links in same paragraph as another link | ||
* Fix issue with creating links after empty paragraphs | ||
* Ensure all attributes are copied from textareas to divs | ||
5.5.1 / 2015-07-23 | ||
@@ -2,0 +11,0 @@ ================== |
{ | ||
"name": "medium-editor", | ||
"version": "5.5.1", | ||
"version": "5.5.2", | ||
"author": "Davi Ferreira <hi@daviferreira.com>", | ||
@@ -5,0 +5,0 @@ "contributors": [ |
@@ -17,3 +17,5 @@ /*global describe, it, expect, spyOn, AnchorForm, | ||
'<a id="test-markup-link" href="http://test.com"><b>ipsum</b></a> ' + | ||
'<a id="test-symbol-link" href="http://[{~#custom#~}].com"></a>'); | ||
'<a id="test-symbol-link" href="http://[{~#custom#~}].com"></a> ' + | ||
'<a id="text-target-blank-link" target="_blank" href="http://test.com">ipsum</a> ' + | ||
'<a id="text-custom-class-link" class="custom-class" href="http://test.com">ipsum</a>'); | ||
}); | ||
@@ -120,3 +122,10 @@ | ||
it('should display the anchor form in the toolbar when clicked', function () { | ||
var editor = this.newMediumEditor('.editor'), | ||
var editor = this.newMediumEditor('.editor', { | ||
anchor: { | ||
targetCheckbox: true, | ||
targetCheckboxText: 'Open in new window', | ||
customClassOption: 'custom-class', | ||
customClassOptionText: 'Custom Class' | ||
} | ||
}), | ||
anchorPreview = editor.getExtensionByName('anchor-preview'), | ||
@@ -136,4 +145,66 @@ anchor = editor.getExtensionByName('anchor'), | ||
expect(anchor.isDisplayed()).toBe(true); | ||
// the checkboxes should be unchecked | ||
expect(anchor.getAnchorTargetCheckbox().checked).toBe(false); | ||
expect(anchor.getAnchorButtonCheckbox().checked).toBe(false); | ||
}); | ||
it('should display the anchor form with target checkbox checked in the toolbar when clicked', function () { | ||
var editor = this.newMediumEditor('.editor', { | ||
anchor: { | ||
targetCheckbox: true, | ||
targetCheckboxText: 'Open in new window', | ||
customClassOption: 'custom-class', | ||
customClassOptionText: 'Custom Class' | ||
} | ||
}), | ||
anchorPreview = editor.getExtensionByName('anchor-preview'), | ||
anchor = editor.getExtensionByName('anchor'), | ||
toolbar = editor.getExtensionByName('toolbar'); | ||
// show preview | ||
fireEvent(document.getElementById('text-target-blank-link'), 'mouseover'); | ||
// load into editor | ||
jasmine.clock().tick(1); | ||
fireEvent(anchorPreview.getPreviewElement(), 'click'); | ||
jasmine.clock().tick(200); | ||
expect(toolbar.isDisplayed()).toBe(true); | ||
expect(anchor.isDisplayed()).toBe(true); | ||
// the checkboxes should be unchecked | ||
expect(anchor.getAnchorTargetCheckbox().checked).toBe(true); | ||
expect(anchor.getAnchorButtonCheckbox().checked).toBe(false); | ||
}); | ||
it('should display the anchor form with custom class checkbox checked in the toolbar when clicked', function () { | ||
var editor = this.newMediumEditor('.editor', { | ||
anchor: { | ||
targetCheckbox: true, | ||
targetCheckboxText: 'Open in new window', | ||
customClassOption: 'custom-class', | ||
customClassOptionText: 'Custom Class' | ||
} | ||
}), | ||
anchorPreview = editor.getExtensionByName('anchor-preview'), | ||
anchor = editor.getExtensionByName('anchor'), | ||
toolbar = editor.getExtensionByName('toolbar'); | ||
// show preview | ||
fireEvent(document.getElementById('text-custom-class-link'), 'mouseover'); | ||
// load into editor | ||
jasmine.clock().tick(1); | ||
fireEvent(anchorPreview.getPreviewElement(), 'click'); | ||
jasmine.clock().tick(200); | ||
expect(toolbar.isDisplayed()).toBe(true); | ||
expect(anchor.isDisplayed()).toBe(true); | ||
// the checkboxes should be unchecked | ||
expect(anchor.getAnchorTargetCheckbox().checked).toBe(false); | ||
expect(anchor.getAnchorButtonCheckbox().checked).toBe(true); | ||
}); | ||
it('should NOT be displayed when the hovered link is empty', function () { | ||
@@ -140,0 +211,0 @@ var editor = this.newMediumEditor('.editor', { |
/*global MediumEditor, describe, it, expect, spyOn, | ||
afterEach, beforeEach, selectElementContents, | ||
jasmine, fireEvent, Util, setupTestHelpers, | ||
selectElementContentsAndFire, AnchorForm, | ||
Selection */ | ||
selectElementContentsAndFire */ | ||
@@ -86,3 +85,3 @@ describe('Anchor Button TestCase', function () { | ||
Selection.select(document, this.el.childNodes[0], 'Hello world, '.length, | ||
MediumEditor.selection.select(document, this.el.childNodes[0], 'Hello world, '.length, | ||
this.el.childNodes[1].childNodes[0], 'will become a link'.length); | ||
@@ -116,3 +115,3 @@ button = toolbar.getToolbarElement().querySelector('[data-action="createLink"]'); | ||
Selection.select(document, this.el.querySelector('span'), 0, | ||
MediumEditor.selection.select(document, this.el.querySelector('span'), 0, | ||
this.el.querySelector('strong'), 1); | ||
@@ -306,3 +305,3 @@ button = toolbar.getToolbarElement().querySelector('[data-action="createLink"]'); | ||
it('should display the anchor form when toolbar is visible', function () { | ||
spyOn(AnchorForm.prototype, 'showForm').and.callThrough(); | ||
spyOn(MediumEditor.extensions.anchor.prototype, 'showForm').and.callThrough(); | ||
var button, | ||
@@ -335,4 +334,26 @@ editor = this.newMediumEditor('.editor'), | ||
// https://github.com/yabwe/medium-editor/issues/751 | ||
it('should allow more than one link creation within a paragraph', function () { | ||
spyOn(MediumEditor.extensions.anchor.prototype, 'showForm').and.callThrough(); | ||
this.el.innerHTML = '<p><a href="#">beginning</a> some text middle some text end</p>'; | ||
var editor = this.newMediumEditor('.editor'), | ||
anchorExtension = editor.getExtensionByName('anchor'), | ||
toolbar = editor.getExtensionByName('toolbar'), | ||
para = this.el.firstChild; | ||
// Select the text 'middle' | ||
MediumEditor.selection.select(document, para.childNodes[1], 11, para.childNodes[1], 18); | ||
fireEvent(editor.elements[0], 'focus'); | ||
jasmine.clock().tick(1); | ||
// Click the 'anchor' button in the toolbar | ||
fireEvent(toolbar.getToolbarElement().querySelector('[data-action="createLink"]'), 'click'); | ||
expect(toolbar.getToolbarActionsElement().style.display).toBe('none'); | ||
expect(anchorExtension.isDisplayed()).toBe(true); | ||
expect(anchorExtension.showForm).toHaveBeenCalled(); | ||
expect(anchorExtension.getInput().value).toBe(''); | ||
}); | ||
}); | ||
}); |
@@ -71,2 +71,23 @@ /*global MediumEditor, describe, it, expect, spyOn, | ||
// https://github.com/yabwe/medium-editor/issues/738 | ||
it('should import an exported non-collapsed selection after an empty paragraph', function () { | ||
this.el.innerHTML = '<p>This is <a href="#">a link</a></p><p><br/></p><p>not a link</p>'; | ||
var editor = this.newMediumEditor('.editor'), | ||
lastTextNode = this.el.childNodes[2].firstChild; | ||
MediumEditor.selection.select(document, lastTextNode, 0, lastTextNode, 'not a link'.length); | ||
var exportedSelection = editor.exportSelection(); | ||
expect(exportedSelection).toEqual({ start: 14, end: 24, emptyBlocksIndex: 2 }); | ||
editor.importSelection(exportedSelection); | ||
var range = window.getSelection().getRangeAt(0); | ||
expect(range.startContainer === lastTextNode || range.startContainer === lastTextNode.parentNode) | ||
.toBe(true, 'The selection is starting at the wrong element'); | ||
expect(range.startOffset).toBe(0, 'The start of the selection is not at the beginning of the text node'); | ||
expect(range.endContainer === lastTextNode || range.endContainer === lastTextNode.parentNode) | ||
.toBe(true, 'The selection is ending at the wrong element'); | ||
expect(range.endOffset).toBe('not a link'.length, 'The end of the selection is not at the end of the text node'); | ||
}); | ||
it('should have an index in the exported selection when it is in the second contenteditable', function () { | ||
@@ -73,0 +94,0 @@ this.createElement('div', 'editor', 'lorem <i>ipsum</i> dolor'); |
@@ -18,2 +18,3 @@ /*global describe, it, beforeEach, afterEach, expect, | ||
this.el.setAttribute('spellcheck', true); | ||
this.el.setAttribute('data-imhere', 'ohyeah'); | ||
document.body.appendChild(this.el); | ||
@@ -27,3 +28,3 @@ }); | ||
it('should accept a textarea element and "convert" it to a div, preserving important attributes', function () { | ||
it('should accept a textarea element and "convert" it to a div, preserving all attributes', function () { | ||
var editor = this.newMediumEditor('.editor'), | ||
@@ -33,3 +34,4 @@ textarea = this.el; | ||
var attributesToPreserve = ['data-disable-editing', | ||
var attributes = [ | ||
'data-disable-editing', | ||
'data-disable-toolbar', | ||
@@ -40,4 +42,6 @@ 'data-placeholder', | ||
'data-disable-preview', | ||
'spellcheck']; | ||
attributesToPreserve.forEach(function (attr) { | ||
'spellcheck', | ||
'data-imhere' | ||
]; | ||
attributes.forEach(function (attr) { | ||
expect(editor.elements[0].getAttribute(attr)).toBe(textarea.getAttribute(attr)); | ||
@@ -44,0 +48,0 @@ }); |
@@ -296,11 +296,3 @@ /*global Util, Selection, Extension, | ||
uniqueId = 'medium-editor-' + Date.now() + '-' + id, | ||
attributesToClone = [ | ||
'data-disable-editing', | ||
'data-disable-toolbar', | ||
'data-placeholder', | ||
'data-disable-return', | ||
'data-disable-double-return', | ||
'data-disable-preview', | ||
'spellcheck' | ||
]; | ||
atts = textarea.attributes; | ||
@@ -310,11 +302,14 @@ div.className = textarea.className; | ||
div.innerHTML = textarea.value; | ||
div.setAttribute('medium-editor-textarea-id', id); | ||
attributesToClone.forEach(function (attr) { | ||
if (textarea.hasAttribute(attr)) { | ||
div.setAttribute(attr, textarea.getAttribute(attr)); | ||
textarea.setAttribute('medium-editor-textarea-id', id); | ||
// re-create all attributes from the textearea to the new created div | ||
for (var i = 0, n = atts.length; i < n; i++) { | ||
// do not re-create existing attributes | ||
if (!div.hasAttribute(atts[i].nodeName)) { | ||
div.setAttribute(atts[i].nodeName, atts[i].nodeValue); | ||
} | ||
}); | ||
} | ||
textarea.classList.add('medium-editor-hidden'); | ||
textarea.setAttribute('medium-editor-textarea-id', id); | ||
textarea.parentNode.insertBefore( | ||
@@ -321,0 +316,0 @@ div, |
@@ -137,3 +137,8 @@ var AnchorPreview; | ||
if (activeAnchor) { | ||
anchorExtension.showForm(activeAnchor.attributes.href.value); | ||
var opts = { | ||
url: activeAnchor.attributes.href.value, | ||
target: activeAnchor.getAttribute('target'), | ||
buttonClass: activeAnchor.getAttribute('class') | ||
}; | ||
anchorExtension.showForm(opts); | ||
activeAnchor = null; | ||
@@ -140,0 +145,0 @@ } |
@@ -63,6 +63,7 @@ var AnchorForm; | ||
var selectedParentElement = Selection.getSelectedParentElement(Selection.getSelectionRange(this.document)), | ||
firstTextNode = Util.getFirstTextNode(selectedParentElement); | ||
var range = Selection.getSelectionRange(this.document); | ||
if (Util.getClosestTag(firstTextNode, 'a')) { | ||
if (range.startContainer.nodeName.toLowerCase() === 'a' || | ||
range.endContainer.nodeName.toLowerCase() === 'a' || | ||
Util.getClosestTag(Selection.getSelectedParentElement(range), 'a')) { | ||
return this.execAction('unlink'); | ||
@@ -151,5 +152,16 @@ } | ||
showForm: function (linkValue) { | ||
var input = this.getInput(); | ||
showForm: function (opts) { | ||
var input = this.getInput(), | ||
targetCheckbox = this.getAnchorTargetCheckbox(), | ||
buttonCheckbox = this.getAnchorButtonCheckbox(); | ||
opts = opts || { url: '' }; | ||
// TODO: This is for backwards compatability | ||
// We don't need to support the 'string' argument in 6.0.0 | ||
if (typeof opts === 'string') { | ||
opts = { | ||
url: opts | ||
}; | ||
} | ||
this.base.saveSelection(); | ||
@@ -160,4 +172,17 @@ this.hideToolbarDefaultActions(); | ||
input.value = linkValue || ''; | ||
input.value = opts.url; | ||
input.focus(); | ||
// If we have a target checkbox, we want it to be checked/unchecked | ||
// based on whether the existing link has target=_blank | ||
if (targetCheckbox) { | ||
targetCheckbox.checked = opts.target === '_blank'; | ||
} | ||
// If we have a custom class checkbox, we want it to be checked/unchecked | ||
// based on whether an existing link already has the class | ||
if (buttonCheckbox) { | ||
var classList = opts.buttonClass ? opts.buttonClass.split(' ') : []; | ||
buttonCheckbox.checked = (classList.indexOf(this.customClassOption) !== -1); | ||
} | ||
}, | ||
@@ -182,4 +207,4 @@ | ||
// no notion of private functions? wanted `_getFormOpts` | ||
var targetCheckbox = this.getForm().querySelector('.medium-editor-toolbar-anchor-target'), | ||
buttonCheckbox = this.getForm().querySelector('.medium-editor-toolbar-anchor-button'), | ||
var targetCheckbox = this.getAnchorTargetCheckbox(), | ||
buttonCheckbox = this.getAnchorButtonCheckbox(), | ||
opts = { | ||
@@ -263,2 +288,10 @@ url: this.getInput().value | ||
getAnchorTargetCheckbox: function () { | ||
return this.getForm().querySelector('.medium-editor-toolbar-anchor-target'); | ||
}, | ||
getAnchorButtonCheckbox: function () { | ||
return this.getForm().querySelector('.medium-editor-toolbar-anchor-button'); | ||
}, | ||
handleTextboxKeyup: function (event) { | ||
@@ -265,0 +298,0 @@ // For ENTER -> create the anchor |
@@ -76,3 +76,3 @@ var KeyboardCommands; | ||
// command can be false so the shortcurt is just disabled | ||
// command can be false so the shortcut is just disabled | ||
if (false !== data.command) { | ||
@@ -79,0 +79,0 @@ this.execAction(data.command); |
@@ -1,2 +0,2 @@ | ||
/*global Util, Selection, Extension */ | ||
/*global Util, Extension */ | ||
var PasteHandler; | ||
@@ -141,4 +141,3 @@ | ||
cleanPaste: function (text) { | ||
var i, elList, workEl, | ||
el = Selection.getSelectionElement(this.window), | ||
var i, elList, | ||
multiline = /<p|<br|<div/.test(text), | ||
@@ -159,27 +158,2 @@ replacements = createReplacements().concat(this.cleanReplacements || []); | ||
this.pasteHTML('<p>' + elList.join('</p><p>') + '</p>'); | ||
try { | ||
this.document.execCommand('insertText', false, '\n'); | ||
} catch (ignore) { } | ||
// block element cleanup | ||
elList = el.querySelectorAll('a,p,div,br'); | ||
for (i = 0; i < elList.length; i += 1) { | ||
workEl = elList[i]; | ||
// Microsoft Word replaces some spaces with newlines. | ||
// While newlines between block elements are meaningless, newlines within | ||
// elements are sometimes actually spaces. | ||
workEl.innerHTML = workEl.innerHTML.replace(/\n/gi, ' '); | ||
switch (workEl.nodeName.toLowerCase()) { | ||
case 'p': | ||
case 'div': | ||
this.filterCommonBlocks(workEl); | ||
break; | ||
case 'br': | ||
this.filterLineBreak(workEl); | ||
break; | ||
} | ||
} | ||
}, | ||
@@ -203,3 +177,2 @@ | ||
elList = fragmentBody.querySelectorAll('*'); | ||
for (i = 0; i < elList.length; i += 1) { | ||
@@ -216,2 +189,23 @@ workEl = elList[i]; | ||
// block element cleanup | ||
elList = fragmentBody.querySelectorAll('a,p,div,br'); | ||
for (i = 0; i < elList.length; i += 1) { | ||
workEl = elList[i]; | ||
// Microsoft Word replaces some spaces with newlines. | ||
// While newlines between block elements are meaningless, newlines within | ||
// elements are sometimes actually spaces. | ||
workEl.innerHTML = workEl.innerHTML.replace(/\n/gi, ' '); | ||
switch (workEl.nodeName.toLowerCase()) { | ||
case 'p': | ||
case 'div': | ||
this.filterCommonBlocks(workEl); | ||
break; | ||
case 'br': | ||
this.filterLineBreak(workEl); | ||
break; | ||
} | ||
} | ||
Util.insertHTMLCommand(this.document, fragmentBody.innerHTML.replace(/ /g, ' ')); | ||
@@ -218,0 +212,0 @@ }, |
@@ -138,3 +138,2 @@ /*global Util */ | ||
range.setStart(Util.getFirstSelectableLeafNode(targetNode), 0); | ||
range.collapse(true); | ||
} | ||
@@ -179,3 +178,2 @@ | ||
range.setStart(currentNode.parentNode, currentNodeIndex + 1); | ||
range.collapse(true); | ||
} | ||
@@ -182,0 +180,0 @@ } |
@@ -20,3 +20,3 @@ /*global MediumEditor */ | ||
// grunt-bump looks for this: | ||
'version': '5.5.1' | ||
'version': '5.5.2' | ||
}).version); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1491453
18302