Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added email support for autolink plugin #1825

Merged
merged 5 commits into from
Mar 25, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@

## CKEditor 4.10

New Features:

* [#1761](https://github.com/ckeditor/ckeditor-dev/issues/1761): [Autolink](https://ckeditor.com/cke4/addon/autolink) plugin supports email links.

Fixed Issues:

* [#1458](https://github.com/ckeditor/ckeditor-dev/issues/1458): [Edge] Fixed: After blurring editor it takes 2 clicks to focus a widget.
Expand Down
31 changes: 29 additions & 2 deletions plugins/autolink/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

// Regex by Imme Emosol.
var validUrlRegex = /^(https?|ftp):\/\/(-\.)?([^\s\/?\.#]+\.?)+(\/[^\s]*)?[^\s\.,]$/ig,
// Regex by (https://www.w3.org/TR/html5/forms.html#valid-e-mail-address).
validEmailRegex = /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/g,
doubleQuoteRegex = /"/g;

CKEDITOR.plugins.add( 'autolink', {
Expand All @@ -26,8 +28,14 @@
return;
}

// https://dev.ckeditor.com/ticket/13419
data = data.replace( validUrlRegex , '<a href="' + data.replace( doubleQuoteRegex, '%22' ) + '">$&</a>' );
// Create valid email links (#1761).
if ( data.match( validEmailRegex ) ) {
data = data.replace( validEmailRegex, '<a href="mailto:' + data.replace( doubleQuoteRegex, '%22' ) + '">$&</a>' );
data = tryToEncodeLink( data );
} else {
// https://dev.ckeditor.com/ticket/13419
data = data.replace( validUrlRegex , '<a href="' + data.replace( doubleQuoteRegex, '%22' ) + '">$&</a>' );
}

// If link was discovered, change the type to 'html'. This is important e.g. when pasting plain text in Chrome
// where real type is correctly recognized.
Expand All @@ -37,6 +45,25 @@

evt.data.dataValue = data;
} );

function tryToEncodeLink( data ) {
// If enabled use link plugin to encode email link.
if ( editor.plugins.link ) {
var link = CKEDITOR.dom.element.createFromHtml( data ),
linkData = CKEDITOR.plugins.link.parseLinkAttributes( editor, link ),
attributes = CKEDITOR.plugins.link.getLinkAttributes( editor, linkData );

if ( !CKEDITOR.tools.isEmpty( attributes.set ) ) {
link.setAttributes( attributes.set );
}

if ( attributes.removed.length ) {
link.removeAttributes( attributes.removed );
}
return link.getOuterHtml();
}
return data;
}
}
} );
} )();
200 changes: 156 additions & 44 deletions tests/plugins/autolink/autolink.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,34 @@
/* bender-include: ../clipboard/_helpers/pasting.js */
/* global assertPasteEvent */

bender.editor = {
config: {
allowedContent: true,
pasteFilter: null
bender.editors = {
classic: {
config: {
allowedContent: true,
pasteFilter: null
}
},
encodedDefault: {
config: {
allowedContent: true,
pasteFilter: null,
emailProtection: 'encode',
extraPlugins: 'link'
}
},
encodedCustom: {
config: {
allowedContent: true,
pasteFilter: null,
emailProtection: 'mt(NAME,DOMAIN,SUBJECT,BODY)',
extraPlugins: 'link'
}
}
};

bender.test( {
'test normal link': function() {
var pastedText = 'https://placekitten.com/g/180/300',
expected = '<a href="' + pastedText + '">' + pastedText + '</a>';

assertPasteEvent( this.editor, { dataValue: pastedText }, { dataValue: expected, type: 'html' } );
},

'test fake link': function() {
var pastedText = 'https//placekitten.com/g/190/300';

assertPasteEvent( this.editor, { dataValue: pastedText }, { dataValue: pastedText, type: 'html' } );
},

'test link with HTML tags': function() {
'test URL link with HTML tags': function() {
var pastedTexts = [
'https://<br>placekitten.com/g/200/301',
'https://<br/>placekitten.com/g/200/302',
Expand All @@ -37,43 +43,76 @@ bender.test( {
var pastedText;

while ( ( pastedText = pastedTexts.pop() ) ) {
this.editor.once( 'paste', function( evt ) {
this.editors.classic.once( 'paste', function( evt ) {
evt.cancel();

assert.areSame( -1, evt.data.dataValue.search( /<a / ), 'text was not auto linked: ' + pastedText );
}, null, null, 900 );

this.editors.classic.execCommand( 'paste', pastedText );
}
},

'test mail link with HTML tags': function() {
var pastedTexts = [
'<br>[email protected]',
'<br/>[email protected]',
'<br />[email protected]',
'<b>mail</b>@example.com'
];

var pastedText;

while ( ( pastedText = pastedTexts.pop() ) ) {
this.editors.classic.once( 'paste', function( evt ) {
evt.cancel();

assert.areSame( -1, evt.data.dataValue.search( /<a / ), 'text was not auto linked: ' + pastedText );
}, null, null, 900 );

this.editor.execCommand( 'paste', pastedText );
this.editors.classic.execCommand( 'paste', pastedText );
}
},

// https://dev.ckeditor.com/ticket/13419
'test link with quotation marks': function() {
'test URL link with quotation marks': function() {
var pastedText = 'https://foo.bar/?bam="bom"',
expected = '<a href="https://foo.bar/?bam=%22bom%22">https://foo.bar/?bam="bom"</a>';

assertPasteEvent( this.editor, { dataValue: pastedText }, { dataValue: expected, type: 'html' } );
assertPasteEvent( this.editors.classic, { dataValue: pastedText }, { dataValue: expected, type: 'html' } );
},

'test link with text after': function() {
'test URL link with text after': function() {
var pastedText = 'https://placekitten.com/g/210/300 nope';

assertPasteEvent( this.editor, { dataValue: pastedText }, { dataValue: pastedText, type: 'html' } );
assertPasteEvent( this.editors.classic, { dataValue: pastedText }, { dataValue: pastedText, type: 'html' } );
},

'test link with text before': function() {
'test mail link with text after': function() {
var pastedText = '[email protected] nope';

assertPasteEvent( this.editors.classic, { dataValue: pastedText }, { dataValue: pastedText, type: 'html' } );
},

'test URL link with text before': function() {
var pastedText = 'nope https://placekitten.com/g/220/300';

assertPasteEvent( this.editor, { dataValue: pastedText }, { dataValue: pastedText, type: 'html' } );
assertPasteEvent( this.editors.classic, { dataValue: pastedText }, { dataValue: pastedText, type: 'html' } );
},

'test mail link with text before': function() {
var pastedText = 'nope [email protected]';

assertPasteEvent( this.editors.classic, { dataValue: pastedText }, { dataValue: pastedText, type: 'html' } );
},

'test link with text attached before': function() {
'test URL link with text attached before': function() {
var pastedText = 'nopehttps://placekitten.com/g/230/300';

assertPasteEvent( this.editor, { dataValue: pastedText }, { dataValue: pastedText, type: 'html' } );
assertPasteEvent( this.editors.classic, { dataValue: pastedText }, { dataValue: pastedText, type: 'html' } );
},

'test various valid links': function() {
'test various valid URL links': function() {
var pastedTexts = [
'https://placekitten.com/g/180/300',
'http://giphy.com/gifs/space-nasa-test-GDiDCTh9AjbiM',
Expand All @@ -85,18 +124,40 @@ bender.test( {
var pastedText;

while ( ( pastedText = pastedTexts.pop() ) ) {
this.editor.once( 'paste', function( evt ) {
this.editors.classic.once( 'paste', function( evt ) {
evt.cancel();

assert.areSame( '<a href="' + pastedText + '">' + pastedText + '</a>', evt.data.dataValue );
}, null, null, 900 );

this.editor.execCommand( 'paste', pastedText );
this.editors.classic.execCommand( 'paste', pastedText );
}
},

'test various valid email links': function() {
var pastedTexts = [
'[email protected]',
'mail@mail',
".!#$%&'*+-/=?^_`{|}~@1234567890",
'[email protected]'
];

var pastedText;

while ( ( pastedText = pastedTexts.pop() ) ) {
this.editors.classic.once( 'paste', function( evt ) {
evt.cancel();

assert.areSame( '<a href="mailto:' + pastedText + '">' + pastedText + '</a>', evt.data.dataValue );
}, null, null, 900 );

this.editors.classic.execCommand( 'paste', pastedText );
}
},

'test various invalid links': function() {
'test various invalid URL links': function() {
var pastedTexts = [
'https//placekitten.com/g/190/300',
'https://placekitten.com/g/181/300.',
'http://giphy.com?search',
'https://www.google.pl,,,,',
Expand All @@ -106,64 +167,115 @@ bender.test( {
var pastedText;

while ( ( pastedText = pastedTexts.pop() ) ) {
this.editor.once( 'paste', function( evt ) {
this.editors.classic.once( 'paste', function( evt ) {
evt.cancel();

assert.areSame( pastedText, evt.data.dataValue );
}, null, null, 900 );

this.editors.classic.execCommand( 'paste', pastedText );
}
},

'test various invalid email links': function() {
var pastedTexts = [
'mail@',
'@mail',
'hello,[email protected]',
'example@hello,world',
'"@emai.com',
'"example"@email.com'
];

var pastedText;

while ( ( pastedText = pastedTexts.pop() ) ) {
this.editors.classic.once( 'paste', function( evt ) {
evt.cancel();

assert.areSame( pastedText, evt.data.dataValue );
}, null, null, 900 );

this.editor.execCommand( 'paste', pastedText );
this.editors.classic.execCommand( 'paste', pastedText );
}
},

'test pasting multiple links': function() {
'test pasting multiple URL links': function() {
var pastedText = 'http://en.wikipedia.org/wiki/Weasel http://en.wikipedia.org/wiki/Weasel';

assertPasteEvent( this.editor, { dataValue: pastedText }, { dataValue: pastedText, type: 'html' } );
assertPasteEvent( this.editors.classic, { dataValue: pastedText }, { dataValue: pastedText, type: 'html' } );
},

'test pasting multiple email links': function() {
var pastedText = '[email protected] [email protected]';

assertPasteEvent( this.editors.classic, { dataValue: pastedText }, { dataValue: pastedText, type: 'html' } );
},

'test pasting whole paragraph': function() {
var pastedText =
'A multi-channel operating <a href="http://en.wikipedia.org/wiki/Strategy">strategy</a>' +
'drives the enabler, while the enablers strategically embrace the game-changing, ' +
'<a id="organic" href="http://en.wikipedia.org/wiki/Organic">organic</a> and cross-enterprise cultures.';
'<a id="organic" href="http://en.wikipedia.org/wiki/Organic">organic</a> and cross-enterprise ' +
'<a href="mailto: [email protected]">cultures</a>.';

assertPasteEvent( this.editor, { dataValue: pastedText }, { dataValue: pastedText, type: 'html' } );
assertPasteEvent( this.editors.classic, { dataValue: pastedText }, { dataValue: pastedText, type: 'html' } );
},

'test content that is a link': function() {
var pastedText = '<a href="http://en.wikipedia.org/wiki/Weasel">Weasel</a>';

assertPasteEvent( this.editor, { dataValue: pastedText }, { dataValue: pastedText, type: 'html' } );
assertPasteEvent( this.editors.classic, { dataValue: pastedText }, { dataValue: pastedText, type: 'html' } );
},

'test type is changed once a link is created': function() {
var pastedText = 'https://placekitten.com/g/180/300',
expected = '<a href="' + pastedText + '">' + pastedText + '</a>';

assertPasteEvent( this.editor, { dataValue: pastedText, type: 'text' }, { dataValue: expected, type: 'html' } );
assertPasteEvent( this.editors.classic, { dataValue: pastedText, type: 'text' }, { dataValue: expected, type: 'html' } );
},

'test type is not changed if link was not found': function() {
var pastedText = 'foo bar';

assertPasteEvent( this.editor, { dataValue: pastedText, type: 'text' }, { dataValue: pastedText, type: 'text' } );
assertPasteEvent( this.editors.classic, { dataValue: pastedText, type: 'text' }, { dataValue: pastedText, type: 'text' } );
},

'test internal paste is not autolinked': function() {
var editor = this.editor,
var editor = this.editors.classic,
pastedText = 'https://foo.bar/g/185/310';

this.editor.once( 'paste', function( evt ) {
this.editors.classic.once( 'paste', function( evt ) {
evt.data.dataTransfer.sourceEditor = editor;
}, null, null, 1 );

this.editor.once( 'paste', function( evt ) {
this.editors.classic.once( 'paste', function( evt ) {
evt.cancel();

assert.areSame( pastedText, evt.data.dataValue );
}, null, null, 900 );

this.editor.execCommand( 'paste', pastedText );
this.editors.classic.execCommand( 'paste', pastedText );
},

'test created protected mail link (function)': function() {
var pastedText = 'a@a',
expected = '<a href="javascript:void(location.href=\'mailto:\'+String.fromCharCode(97,64,97))"' +
' data-cke-saved-href="javascript:void(location.href=\'mailto:\'+String.fromCharCode(97,64,97))">a@a</a>';

assertPasteEvent( this.editors.encodedDefault, { dataValue: pastedText, type: 'text' }, function( data ) {
assert.areEqual( 'html', data.type );
assert.areEqual( expected, bender.tools.compatHtml( data.dataValue ) );
} );
},

'test created protected mail link (string)': function() {
var pastedText = 'a@a',
expected = '<a href="javascript:mt(\'a\',\'a\',\'\',\'\')" data-cke-saved-href="javascript:mt(\'a\',\'a\',\'\',\'\')">a@a</a>';

assertPasteEvent( this.editors.encodedCustom, { dataValue: pastedText, type: 'text' }, function( data ) {
assert.areEqual( 'html', data.type );
assert.areEqual( expected, bender.tools.compatHtml( data.dataValue ) );
} );
}
} );
Loading