Skip to content

Commit

Permalink
Merge pull request #811 from yabwe/autolink-in-list
Browse files Browse the repository at this point in the history
Fix #790 - Don't auto-link across multiple list-items
  • Loading branch information
j0k3r committed Sep 13, 2015
2 parents c128534 + cd8c234 commit aa5b45b
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 24 deletions.
5 changes: 4 additions & 1 deletion demo/auto-link.html
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ <h1>Medium Editor</h1>
<script>
var editor = new MediumEditor('.editable', {
buttonLabels: 'fontawesome',
autoLink: true
autoLink: true,
toolbar: {
buttons: ['bold', 'italic', 'unorderedlist', 'orderedlist', 'anchor']
}
}),
cssLink = document.getElementById('medium-editor-theme');

Expand Down
26 changes: 26 additions & 0 deletions spec/auto-link.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,32 @@ describe('Autolink', function () {
expect(links[0].getAttribute('href')).toBe('http://www.example.com');
expect(links[0].firstChild.getAttribute('data-auto-link')).toBe('true');
});

// https://github.com/yabwe/medium-editor/issues/790
it('should not create a link when text in consecutive list items could be a valid url when combined', function () {
this.el.innerHTML = '<ul><li>text ending in a period.</li><li>name - text starting with a TLD</li></ul>';

selectElementContentsAndFire(this.el);
triggerAutolinking(this.el);
var links = this.el.getElementsByTagName('a');
expect(links.length).toBe(0, 'A link was created without a valid url being in the text');
expect(this.el.innerHTML).toBe('<ul><li>text ending in a period.</li><li>name - text starting with a TLD</li></ul>',
'Content does not contain a valid url, but auto-link caused the content to change');
});

// https://github.com/yabwe/medium-editor/issues/790
it('should not create a link which spans multiple list items', function () {
this.el.innerHTML = '<ol><li>abc</li><li>www.example.com</li></ol>';

selectElementContentsAndFire(this.el);
triggerAutolinking(this.el);
var links = this.el.getElementsByTagName('a'),
lastLi = this.el.querySelector('ol').lastChild;
expect(links.length).toBe(1, 'There should have been exactly 1 link created');
expect(links[0].getAttribute('href')).toBe('http://www.example.com');
expect(lastLi.firstChild).toBe(links[0]);
expect(lastLi.textContent).toBe('www.example.com');
});
});
});

Expand Down
61 changes: 38 additions & 23 deletions src/js/extensions/auto-link.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,43 @@
var WHITESPACE_CHARS,
KNOWN_TLDS_FRAGMENT,
LINK_REGEXP_TEXT;

WHITESPACE_CHARS = [' ', '\t', '\n', '\r', '\u00A0', '\u2000', '\u2001', '\u2002', '\u2003',
'\u2028', '\u2029'];
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.\\-]+[.](' + 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\\-]+\\.(' + KNOWN_TLDS_FRAGMENT + '))';

(function () {
'use strict';

var KNOWN_TLDS_REGEXP = new RegExp('^(' + KNOWN_TLDS_FRAGMENT + ')$', 'i');
var WHITESPACE_CHARS,
KNOWN_TLDS_FRAGMENT,
LINK_REGEXP_TEXT,
IGNORED_BLOCK_ELEMENTS,
KNOWN_TLDS_REGEXP,
AUTO_LINK_BLOCK_ELEMENTS;

WHITESPACE_CHARS = [' ', '\t', '\n', '\r', '\u00A0', '\u2000', '\u2001', '\u2002', '\u2003',
'\u2028', '\u2029'];
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.\\-]+[.](' + 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\\-]+\\.(' + KNOWN_TLDS_FRAGMENT + '))';

// Block elements to ignore when querying all block elements for whether they contain auto-linkable text
// These are elements whose text content should be ignored, but instead the text of their child nodes
// should be evaluated for auto-link text
// (ie don't check the text of an <ol>, check the <li>'s inside of it instead)
IGNORED_BLOCK_ELEMENTS = ['ol', 'ul', 'dl', 'table', 'tbody', 'tfoot', 'tr'];

KNOWN_TLDS_REGEXP = new RegExp('^(' + KNOWN_TLDS_FRAGMENT + ')$', 'i');

// List of block elements to search for when trying to find auto-linkable text
AUTO_LINK_BLOCK_ELEMENTS = MediumEditor.util.blockContainerElementNames.filter(function (name) {
return (IGNORED_BLOCK_ELEMENTS.indexOf(name) === -1);
});

function nodeIsNotInsideAnchorTag(node) {
return !MediumEditor.util.getClosestTag(node, 'a');
Expand Down Expand Up @@ -82,7 +97,7 @@ LINK_REGEXP_TEXT =
// "link." and the next paragraph beginning with "my" is interpreted into "link.my" and the code tries to create
// a link across blockElements - which doesn't work and is terrible.
// (Medium deletes the spaces/returns between P tags so the textContent ends up without paragraph spacing)
var blockElements = contenteditable.querySelectorAll(MediumEditor.util.blockContainerElementNames.join(',')),
var blockElements = contenteditable.querySelectorAll(AUTO_LINK_BLOCK_ELEMENTS.join(',')),
documentModified = false;
if (blockElements.length === 0) {
blockElements = [contenteditable];
Expand Down

0 comments on commit aa5b45b

Please sign in to comment.