medium-editor
Advanced tools
Comparing version 4.11.0 to 4.11.1
{ | ||
"name": "medium-editor", | ||
"version": "4.11.0", | ||
"version": "4.11.1", | ||
"homepage": "http://daviferreira.github.io/medium-editor/", | ||
@@ -5,0 +5,0 @@ "authors": [ |
@@ -0,1 +1,7 @@ | ||
4.11.1 / 2015-05-26 | ||
================== | ||
* Fix issue with auto-linked text after manually unlinking | ||
* Fix some incorrect TLDs for auto-link | ||
4.11.0 / 2015-05-26 | ||
@@ -2,0 +8,0 @@ ================== |
{ | ||
"name": "medium-editor", | ||
"version": "4.11.0", | ||
"version": "4.11.1", | ||
"author": "Davi Ferreira <hi@daviferreira.com>", | ||
@@ -5,0 +5,0 @@ "contributors": [ |
@@ -39,3 +39,6 @@ /*global describe, it, expect, beforeEach, afterEach, | ||
'http://www.example.com/baz?foo=bar#buzz', | ||
'http://www.example.com/#buzz' | ||
'http://www.example.com/#buzz', | ||
'http://about.museum', | ||
'http://getty.art.museum/visit/center/art.html', | ||
'http://en.wikipedia.org/wiki/List_of_diplomatic_missions_of_China' | ||
], | ||
@@ -46,3 +49,6 @@ notLinks = [ | ||
'app.can', | ||
'sadasda.sdfasf.sdfas' | ||
'sadasda.sdfasf.sdfas', | ||
'www.example.combasic', | ||
// Our algorithm assumes that '.' is punctuation, not part of the URL. | ||
'en.wikipedia.org/wiki/Embassy_of_China_in_Washington,_D.C.' | ||
]; | ||
@@ -101,3 +107,3 @@ | ||
var anchors = this.el.getElementsByTagName('a'); | ||
expect(anchors.length).toBe(0); | ||
expect(anchors.length).toBe(0, '# of anchors'); | ||
}; | ||
@@ -165,6 +171,6 @@ } | ||
expect(links.length).toBe(1); | ||
expect(links[0].innerHTML).toBe('<span data-auto-link="true">' + | ||
'<span class="a"><b>http://www.</b>exa</span>mple.com</span>'); | ||
expect(links[0].getAttribute('href')).toBe('http://www.example.com'); | ||
expect(links[0].firstChild.getAttribute('data-auto-link')).toBe('true'); | ||
expect(links[0].firstChild.nodeName.toLowerCase()).toBe('span'); | ||
expect(links[0].firstChild.innerHTML).toBe('<span class="a"><b>http://www.</b>exa</span>mple.com'); | ||
expect(this.el.firstChild.lastChild.nodeValue).toBe(' '); | ||
@@ -187,4 +193,5 @@ }); | ||
expect(links.length).toBe(1); | ||
expect(links[0].innerHTML).toBe('<span data-auto-link="true">' + | ||
'<b>http://www.</b>exampl<b>e</b>.com</span>'); | ||
expect(links[0].firstChild.getAttribute('data-auto-link')).toBe('true'); | ||
expect(links[0].firstChild.nodeName.toLowerCase()).toBe('span'); | ||
expect(links[0].firstChild.innerHTML).toBe('<b>http://www.</b>exampl<b>e</b>.com'); | ||
expect(links[0].getAttribute('href')).toBe('http://www.example.com'); | ||
@@ -217,2 +224,4 @@ expect(links[0].firstChild.getAttribute('data-auto-link')).toBe('true'); | ||
expect(links[0].firstChild.getAttribute('data-auto-link')).toBe('true'); | ||
expect(links[0].firstChild.getAttribute('data-href')).toBe('http://www.google.com/wow'); | ||
links[0].firstChild.removeAttribute('data-href'); // to make the next innerHTML check work consistently | ||
@@ -265,3 +274,4 @@ var expectedOutput = '' + | ||
it('should not auto-link text inside a span with data-auto-link=true', function () { | ||
this.el.innerHTML = 'Click this <span data-auto-link="true">http://www.example.com</span> link'; | ||
this.el.innerHTML = 'Click this <span data-href="http://www.example.com" data-auto-link="true">' + | ||
'http://www.example.com</span> link'; | ||
@@ -272,6 +282,8 @@ selectElementContentsAndFire(this.el.firstChild); | ||
expect(this.el.getElementsByTagName('a').length).toBe(0, 'should not create a link'); | ||
expect(this.el.getElementsByTagName('span').length).toBe(1, 'span should remain in place'); | ||
}); | ||
it('should not auto-link text containing a span with data-auto-link=true', function () { | ||
this.el.innerHTML = 'Click this <span data-auto-link="true">http://www.example.com</span>foo/bar/baz link'; | ||
this.el.innerHTML = 'Click this <span data-href="http://www.example.com" data-auto-link="true">' + | ||
'www.example.com</span>foo/bar/baz link'; | ||
@@ -282,4 +294,24 @@ selectElementContentsAndFire(this.el.firstChild); | ||
expect(this.el.getElementsByTagName('a').length).toBe(0, 'should not create a link'); | ||
expect(this.el.getElementsByTagName('span').length).toBe(1, 'span should remain in place'); | ||
}); | ||
it('should remove a span with data-auto-link=true when the text no longer matches the original link', function () { | ||
this.el.innerHTML = 'Click this <span data-auto-link="true" data-href="http://www.example.com">' + | ||
'foo</span> link'; | ||
triggerAutolinking(this.el); | ||
expect(this.el.getElementsByTagName('span').length).toBe(0, 'span should have been removed'); | ||
}); | ||
it('should create a link with data-auto-link=true when the text no longer matches the original link' + | ||
' and it has been unlinked', function () { | ||
this.el.innerHTML = 'Click this <span data-auto-link="true" data-href="http://www.example.com">' + | ||
'www.example.co.uk</span> link'; | ||
triggerAutolinking(this.el); | ||
expect(this.el.getElementsByTagName('a').length).toBe(1, 'link should have been added'); | ||
expect(this.el.getElementsByTagName('a')[0].getAttribute('href')).toBe('http://www.example.co.uk', | ||
'link should have been added'); | ||
}); | ||
it('should stop attempting to auto-link on keypress if an error is encountered', function () { | ||
@@ -286,0 +318,0 @@ var spy = spyOn(MediumEditor.extensions.autoLink.prototype, 'performLinking').and.throwError('DOM ERROR'); |
/*global Extension, Util */ | ||
var AutoLink, | ||
KNOWN_TLDS_FRAGMENT, | ||
LINK_REGEXP_TEXT; | ||
KNOWN_TLDS_FRAGMENT = 'com|net|org|edu|gov|mil|aero|asia|biz|cat|coop|info|int|jobs|mobi|museum|name|post|pro|tel|travel|' + | ||
'xxx|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|' + | ||
'bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cs|cu|cv|cx|cy|cz|dd|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|eu|fi|fj|' + | ||
'fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|' + | ||
'is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|' + | ||
'mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|' + | ||
'pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|ja|sk|sl|sm|sn|so|sr|ss|st|su|sv|sx|sy|sz|tc|td|tf|tg|th|' + | ||
'tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw'; | ||
LINK_REGEXP_TEXT = | ||
'(' + | ||
// Version of Gruber URL Regexp optimized for JS: http://stackoverflow.com/a/17733640 | ||
'((?:(https?://|ftps?://|nntp://)|www\\d{0,3}[.]|[a-z0-9.\\-]+[.][a-z]{2,4}\\\/)\\S+(?:[^\\s`!\\[\\]{};:\'\".,?\u00AB\u00BB\u201C\u201D\u2018\u2019]))' + | ||
'((?:(https?://|ftps?://|nntp://)|www\\d{0,3}[.]|[a-z0-9.\\-]+[.](' + KNOWN_TLDS_FRAGMENT + ')\\\/)\\S+(?:[^\\s`!\\[\\]{};:\'\".,?\u00AB\u00BB\u201C\u201D\u2018\u2019]))' + | ||
// Addition to above Regexp to support bare domains/one level subdomains with common non-i18n TLDs and without www prefix: | ||
')|(([a-z0-9\\-]+\\.)?[a-z0-9\\-]+\\.(com|net|org|edu|gov|mil|aero|asia|biz|cat|coop|info|int|jobs|mobi|museum|name|post|pro|tel|travel|xxx|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cs|cu|cv|cx|cy|cz|dd|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj| Ja|sk|sl|sm|sn|so|sr|ss|st|su|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw))'; | ||
')|(([a-z0-9\\-]+\\.)?[a-z0-9\\-]+\\.(' + KNOWN_TLDS_FRAGMENT + '))'; | ||
@@ -16,2 +25,8 @@ (function () { | ||
var KNOWN_TLDS_REGEXP = new RegExp('^(' + KNOWN_TLDS_FRAGMENT + ')$', 'i'); | ||
function nodeIsNotInsideAnchorTag(node) { | ||
return !Util.getClosestTag(node, 'a'); | ||
} | ||
AutoLink = Extension.extend({ | ||
@@ -66,3 +81,3 @@ | ||
var paragraphs = contenteditable.querySelectorAll('p'), | ||
linkCreated = false; | ||
documentModified = false; | ||
if (paragraphs.length === 0) { | ||
@@ -72,5 +87,6 @@ paragraphs = [contenteditable]; | ||
for (var i = 0; i < paragraphs.length; i++) { | ||
linkCreated = this.performLinkingWithinElement(paragraphs[i]) || linkCreated; | ||
documentModified = this.removeObsoleteAutoLinkSpans(paragraphs[i]) || documentModified; | ||
documentModified = this.performLinkingWithinElement(paragraphs[i]) || documentModified; | ||
} | ||
return linkCreated; | ||
return documentModified; | ||
}, | ||
@@ -100,2 +116,24 @@ | ||
removeObsoleteAutoLinkSpans: function (element) { | ||
var spans = element.querySelectorAll('span[data-auto-link="true"]'), | ||
documentModified = false; | ||
for (var i = 0; i < spans.length; i++) { | ||
var textContent = spans[i].textContent; | ||
if (textContent.indexOf('://') === -1) { | ||
textContent = Util.ensureUrlHasProtocol(textContent); | ||
} | ||
if (spans[i].getAttribute('data-href') !== textContent && nodeIsNotInsideAnchorTag(spans[i])) { | ||
documentModified = true; | ||
// Some editing has happened to the span, so just remove it entirely. The user can put it back | ||
// around just the href content if they need to prevent it from linking | ||
while (spans[i].childNodes.length > 0) { | ||
spans[i].parentNode.insertBefore(spans[i].firstChild, spans[i]); | ||
} | ||
spans[i].parentNode.removeChild(spans[i]); | ||
} | ||
} | ||
return documentModified; | ||
}, | ||
performLinkingWithinElement: function (element) { | ||
@@ -121,6 +159,11 @@ var matches = this.findLinkableText(element), | ||
while ((match = linkRegExp.exec(textContent)) !== null) { | ||
var matchEnd = match.index + match[0].length; | ||
var matchOk = true, | ||
matchEnd = match.index + match[0].length; | ||
// If the regexp detected something as a link that has text immediately preceding/following it, bail out. | ||
if ((match.index === 0 || whitespaceChars.indexOf(textContent[match.index - 1]) !== -1) && | ||
(matchEnd === textContent.length || whitespaceChars.indexOf(textContent[matchEnd]) !== -1)) { | ||
matchOk = (match.index === 0 || whitespaceChars.indexOf(textContent[match.index - 1]) !== -1) && | ||
(matchEnd === textContent.length || whitespaceChars.indexOf(textContent[matchEnd]) !== -1); | ||
// If the regexp detected a bare domain that doesn't use one of our expected TLDs, bail out. | ||
matchOk = matchOk && (match[0].indexOf('/') !== -1 || | ||
KNOWN_TLDS_REGEXP.test(match[0].split('.').pop().split('?').shift())); | ||
if (matchOk) { | ||
matches.push({ | ||
@@ -188,6 +231,8 @@ href: match[0], | ||
var anchor = this.document.createElement('a'), | ||
span = this.document.createElement('span'); | ||
span = this.document.createElement('span'), | ||
hrefWithProtocol = Util.ensureUrlHasProtocol(href); | ||
Util.moveTextRangeIntoElement(textNodes[0], textNodes[textNodes.length - 1], span); | ||
span.setAttribute('data-auto-link', 'true'); | ||
anchor.setAttribute('href', Util.ensureUrlHasProtocol(href)); | ||
span.setAttribute('data-href', hrefWithProtocol); | ||
anchor.setAttribute('href', hrefWithProtocol); | ||
span.parentNode.insertBefore(anchor, span); | ||
@@ -194,0 +239,0 @@ anchor.appendChild(span); |
@@ -14,3 +14,3 @@ /*global MediumEditor */ | ||
// grunt-bump looks for this: | ||
'version': '4.11.0' | ||
'version': '4.11.1' | ||
}).version.split('.')); |
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
1322233
17069