Skip to content

Commit

Permalink
Merge pull request #8292 from ckeditor/i/8079
Browse files Browse the repository at this point in the history
Fix (link): Auto link feature will now use `link.defaultProtocol` if set. Closes #8079.
  • Loading branch information
jodator committed Oct 22, 2020
2 parents f6bb239 + 66f2409 commit 9a9f9c3
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 25 deletions.
16 changes: 5 additions & 11 deletions packages/ckeditor5-link/src/autolink.js
Expand Up @@ -10,6 +10,7 @@
import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
import TextWatcher from '@ckeditor/ckeditor5-typing/src/textwatcher';
import getLastTextLine from '@ckeditor/ckeditor5-typing/src/utils/getlasttextline';
import { addLinkProtocolIfApplicable } from './utils';

const MIN_LINK_LENGTH_WITH_SPACE_AT_END = 4; // Ie: "t.co " (length 5).

Expand Down Expand Up @@ -49,9 +50,6 @@ const URL_REG_EXP = new RegExp(

const URL_GROUP_IN_MATCH = 2;

// Simplified email test - should be run over previously found URL.
const EMAIL_REG_EXP = /^[\S]+@((?![-_])(?:[-\w\u00a1-\uffff]{0,63}[^-_]\.))+(?:[a-z\u00a1-\uffff]{2,})$/i;

/**
* The autolink plugin.
*
Expand Down Expand Up @@ -213,7 +211,7 @@ export default class AutoLink extends Plugin {
* @param {module:engine/model/range~Range} range The text range to apply the link attribute to.
* @private
*/
_applyAutoLink( url, range ) {
_applyAutoLink( link, range ) {
const model = this.editor.model;

if ( !this.isEnabled || !isLinkAllowedOnRange( range, model ) ) {
Expand All @@ -222,9 +220,9 @@ export default class AutoLink extends Plugin {

// Enqueue change to make undo step.
model.enqueueChange( writer => {
const linkHrefValue = isEmail( url ) ? `mailto:${ url }` : url;

writer.setAttribute( 'linkHref', linkHrefValue, range );
const defaultProtocol = this.editor.config.get( 'link.defaultProtocol' );
const parsedUrl = addLinkProtocolIfApplicable( link, defaultProtocol );
writer.setAttribute( 'linkHref', parsedUrl, range );
} );
}
}
Expand All @@ -240,10 +238,6 @@ function getUrlAtTextEnd( text ) {
return match ? match[ URL_GROUP_IN_MATCH ] : null;
}

function isEmail( linkHref ) {
return EMAIL_REG_EXP.exec( linkHref );
}

function isLinkAllowedOnRange( range, model ) {
return model.schema.checkAttributeInSelection( model.createSelection( range ), 'linkHref' );
}
16 changes: 3 additions & 13 deletions packages/ckeditor5-link/src/linkui.js
Expand Up @@ -9,7 +9,7 @@

import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
import ClickObserver from '@ckeditor/ckeditor5-engine/src/view/observer/clickobserver';
import { isLinkElement, LINK_KEYSTROKE } from './utils';
import { addLinkProtocolIfApplicable, isLinkElement, LINK_KEYSTROKE } from './utils';

import ContextualBalloon from '@ckeditor/ckeditor5-ui/src/panel/balloon/contextualballoon';

Expand All @@ -21,8 +21,6 @@ import LinkActionsView from './ui/linkactionsview';

import linkIcon from '../theme/icons/link.svg';

const protocolRegExp = /^((\w+:(\/{2,})?)|(\W))/i;
const emailRegExp = /[\w-]+@[\w-]+\.+[\w-]+/i;
const VISUAL_SELECTION_MARKER_NAME = 'link-ui';

/**
Expand Down Expand Up @@ -176,16 +174,8 @@ export default class LinkUI extends Plugin {
// Execute link command after clicking the "Save" button.
this.listenTo( formView, 'submit', () => {
const { value } = formView.urlInputView.fieldView.element;

// The regex checks for the protocol syntax ('xxxx://' or 'xxxx:')
// or non-word characters at the beginning of the link ('/', '#' etc.).
const isProtocolNeeded = !!defaultProtocol && !protocolRegExp.test( value );
const isEmail = emailRegExp.test( value );

const protocol = isEmail ? 'mailto:' : defaultProtocol;
const parsedValue = value && isProtocolNeeded ? protocol + value : value;

editor.execute( 'link', parsedValue, formView.getDecoratorSwitchesState() );
const parsedUrl = addLinkProtocolIfApplicable( value, defaultProtocol );
editor.execute( 'link', parsedUrl, formView.getDecoratorSwitchesState() );
this._closeFormView();
} );

Expand Down
36 changes: 36 additions & 0 deletions packages/ckeditor5-link/src/utils.js
Expand Up @@ -12,6 +12,13 @@ import { upperFirst } from 'lodash-es';
const ATTRIBUTE_WHITESPACES = /[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205f\u3000]/g; // eslint-disable-line no-control-regex
const SAFE_URL = /^(?:(?:https?|ftps?|mailto):|[^a-z]|[a-z+.-]+(?:[^a-z+.:-]|$))/i;

// Simplified email test - should be run over previously found URL.
const EMAIL_REG_EXP = /^[\S]+@((?![-_])(?:[-\w\u00a1-\uffff]{0,63}[^-_]\.))+(?:[a-z\u00a1-\uffff]{2,})$/i;

// The regex checks for the protocol syntax ('xxxx://' or 'xxxx:')
// or non-word characters at the beginning of the link ('/', '#' etc.).
const PROTOCOL_REG_EXP = /^((\w+:(\/{2,})?)|(\W))/i;

/**
* A keystroke used by the {@link module:link/linkui~LinkUI link UI feature}.
*/
Expand Down Expand Up @@ -135,3 +142,32 @@ export function isImageAllowed( element, schema ) {

return element.is( 'element', 'image' ) && schema.checkAttribute( 'image', 'linkHref' );
}

/**
* Returns `true` if the specified `value` is an email.
*
* @params {String} value
* @returns {Boolean}
*/
export function isEmail( value ) {
return EMAIL_REG_EXP.test( value );
}

/**
* Adds protocol prefix to the specified `link` when:
*
* * it doesn't contain it already, and there is a {@link module:link/link~LinkConfig#defaultProtocol `defaultProtocol` }
* config value provided
* * or the link is an email address
*
*
* @params {String} link
* @params {String} defaultProtocol
* @returns {Boolean}
*/
export function addLinkProtocolIfApplicable( link, defaultProtocol ) {
const protocol = isEmail( link ) ? 'mailto:' : defaultProtocol;
const isProtocolNeeded = !!protocol && !PROTOCOL_REG_EXP.test( link );

return link && isProtocolNeeded ? protocol + link : link;
}
9 changes: 9 additions & 0 deletions packages/ckeditor5-link/tests/autolink.js
Expand Up @@ -215,6 +215,15 @@ describe( 'AutoLink', () => {
);
} );

it( 'adds default protocol to link of detected www addresses', () => {
editor.config.set( 'link.defaultProtocol', 'http://' );
simulateTyping( 'www.cksource.com ' );

expect( getData( model ) ).to.equal(
'<paragraph><$text linkHref="http://www.cksource.com">www.cksource.com</$text> []</paragraph>'
);
} );

// Some examples came from https://mathiasbynens.be/demo/url-regex.
describe( 'supported URL', () => {
const supportedURLs = [
Expand Down
13 changes: 13 additions & 0 deletions packages/ckeditor5-link/tests/linkui.js
Expand Up @@ -1321,6 +1321,19 @@ describe( 'LinkUI', () => {
} );
} );

it( 'should detect an email on submitting the form and add "mailto:" protocol automatically to the provided value ' +
'even when defaultProtocol is undefined', () => {
setModelData( editor.model, '<paragraph>[email@example.com]</paragraph>' );

formView.urlInputView.fieldView.value = 'email@example.com';
formView.fire( 'submit' );

expect( formView.urlInputView.fieldView.value ).to.equal( 'mailto:email@example.com' );
expect( getModelData( editor.model ) ).to.equal(
'<paragraph>[<$text linkHref="mailto:email@example.com">email@example.com</$text>]</paragraph>'
);
} );

it( 'should not add an email protocol when given provided within the value' +
'even when `config.link.defaultProtocol` configured', () => {
return createEditorWithDefaultProtocol( 'mailto:' ).then( ( { editor, formView } ) => {
Expand Down
36 changes: 35 additions & 1 deletion packages/ckeditor5-link/tests/utils.js
Expand Up @@ -10,7 +10,9 @@ import ContainerElement from '@ckeditor/ckeditor5-engine/src/view/containereleme
import Text from '@ckeditor/ckeditor5-engine/src/view/text';
import Schema from '@ckeditor/ckeditor5-engine/src/model/schema';
import ModelElement from '@ckeditor/ckeditor5-engine/src/model/element';
import { createLinkElement, isLinkElement, ensureSafeUrl, normalizeDecorators, isImageAllowed } from '../src/utils';
import {
createLinkElement, isLinkElement, ensureSafeUrl, normalizeDecorators, isImageAllowed, isEmail, addLinkProtocolIfApplicable
} from '../src/utils';

describe( 'utils', () => {
describe( 'isLinkElement()', () => {
Expand Down Expand Up @@ -246,4 +248,36 @@ describe( 'utils', () => {
expect( isImageAllowed( element, schema ) ).to.equal( true );
} );
} );

describe( 'isEmail()', () => {
it( 'should return true for email string', () => {
expect( isEmail( 'newsletter@cksource.com' ) ).to.be.true;
} );

it( 'should return false for not email string', () => {
expect( isEmail( 'test' ) ).to.be.false;
expect( isEmail( 'test.test' ) ).to.be.false;
expect( isEmail( 'test@test' ) ).to.be.false;
} );
} );

describe( 'addLinkProtocolIfApplicable()', () => {
it( 'should return link with email protocol for email string', () => {
expect( addLinkProtocolIfApplicable( 'foo@bar.com' ) ).to.equal( 'mailto:foo@bar.com' );
expect( addLinkProtocolIfApplicable( 'foo@bar.com', 'http://' ) ).to.equal( 'mailto:foo@bar.com' );
} );

it( 'should return link with http protocol for url string if defaultProtocol is provided', () => {
expect( addLinkProtocolIfApplicable( 'www.ckeditor.com', 'http://' ) ).to.equal( 'http://www.ckeditor.com' );
} );

it( 'should return unmodified link if not applicable', () => {
expect( addLinkProtocolIfApplicable( 'test' ) ).to.equal( 'test' );
expect( addLinkProtocolIfApplicable( 'www.ckeditor.com' ) ).to.equal( 'www.ckeditor.com' );
expect( addLinkProtocolIfApplicable( 'http://www.ckeditor.com' ) ).to.equal( 'http://www.ckeditor.com' );
expect( addLinkProtocolIfApplicable( 'http://www.ckeditor.com', 'http://' ) ).to.equal( 'http://www.ckeditor.com' );
expect( addLinkProtocolIfApplicable( 'mailto:foo@bar.com' ) ).to.equal( 'mailto:foo@bar.com' );
expect( addLinkProtocolIfApplicable( 'mailto:foo@bar.com', 'http://' ) ).to.equal( 'mailto:foo@bar.com' );
} );
} );
} );

0 comments on commit 9a9f9c3

Please sign in to comment.