8
0
Quellcode durchsuchen

Merge pull request #7370 from ckeditor/i/4858

Feature (link): Introduced the `config.link.defaultProtocol` option for automatically adding it to the links when it's not provided by the user in the link form. Closes #4858.
Maciej vor 5 Jahren
Ursprung
Commit
76c762e5a6

Datei-Diff unterdrückt, da er zu groß ist
+ 24 - 0
packages/ckeditor5-link/docs/features/link.md


+ 1 - 0
packages/ckeditor5-link/package.json

@@ -16,6 +16,7 @@
     "lodash-es": "^4.17.15"
   },
   "devDependencies": {
+    "@ckeditor/ckeditor5-basic-styles": "^19.0.1",
     "@ckeditor/ckeditor5-block-quote": "^19.0.1",
     "@ckeditor/ckeditor5-clipboard": "^19.0.1",
     "@ckeditor/ckeditor5-editor-classic": "^19.0.1",

+ 22 - 0
packages/ckeditor5-link/src/link.js

@@ -58,6 +58,28 @@ export default class Link extends Plugin {
  */
 
 /**
+ * When set, the editor will add the given protocol to the link when the user creates a link without one.
+ * For example, when the user is creating a link and types `ckeditor.com` in the link form input — during link submission —
+ * the editor will automatically add the `http://` protocol, so the link will be as follows: `http://ckeditor.com`.
+ *
+ * The feature also comes with an email auto-detection. When you submit `hello@example.com`
+ * the plugin will automatically change it to `mailto:hello@example.com`.
+ *
+ * 		ClassicEditor
+ *			.create( editorElement, {
+ * 				link: {
+ * 					defaultProtocol: 'http://'
+ * 				}
+ *			} )
+ *			.then( ... )
+ *			.catch( ... );
+ *
+ * **NOTE:** In case no configuration is provided, the editor won't auto-fix the links.
+ *
+ * @member {String} module:link/link~LinkConfig#defaultProtocol
+ */
+
+/**
  * When set to `true`, the `target="blank"` and `rel="noopener noreferrer"` attributes are automatically added to all external links
  * in the editor. "External links" are all links in the editor content starting with `http`, `https`, or `//`.
  *

+ 15 - 2
packages/ckeditor5-link/src/linkui.js

@@ -21,6 +21,8 @@ import LinkActionsView from './ui/linkactionsview';
 import linkIcon from '../theme/icons/link.svg';
 
 const linkKeystroke = 'Ctrl+K';
+const protocolRegExp = /^((\w+:(\/{2,})?)|(\W))/i;
+const emailRegExp = /[\w-]+@[\w-]+\.+[\w-]+/i;
 
 /**
  * The link UI plugin. It introduces the `'link'` and `'unlink'` buttons and support for the <kbd>Ctrl+K</kbd> keystroke.
@@ -143,8 +145,9 @@ export default class LinkUI extends Plugin {
 	_createFormView() {
 		const editor = this.editor;
 		const linkCommand = editor.commands.get( 'link' );
+		const defaultProtocol = editor.config.get( 'link.defaultProtocol' );
 
-		const formView = new LinkFormView( editor.locale, linkCommand );
+		const formView = new LinkFormView( editor.locale, linkCommand, defaultProtocol );
 
 		formView.urlInputView.fieldView.bind( 'value' ).to( linkCommand, 'value' );
 
@@ -154,7 +157,17 @@ export default class LinkUI extends Plugin {
 
 		// Execute link command after clicking the "Save" button.
 		this.listenTo( formView, 'submit', () => {
-			editor.execute( 'link', formView.urlInputView.fieldView.element.value, formView.getDecoratorSwitchesState() );
+			const { value } = formView.urlInputView.fieldView.element;
+
+			// The regex checks for the protocol syntax ('xxxx://' or 'xxxx:')
+			// or non-word charecters at the begining 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() );
 			this._closeFormView();
 		} );
 

+ 6 - 5
packages/ckeditor5-link/src/ui/linkformview.js

@@ -40,8 +40,9 @@ export default class LinkFormView extends View {
 	 *
 	 * @param {module:utils/locale~Locale} [locale] The localization services instance.
 	 * @param {module:link/linkcommand~LinkCommand} linkCommand Reference to {@link module:link/linkcommand~LinkCommand}.
+	 * @param {String} [protocol] A value of a protocol to be displayed in the input's placeholder.
 	 */
-	constructor( locale, linkCommand ) {
+	constructor( locale, linkCommand, protocol ) {
 		super( locale );
 
 		const t = locale.t;
@@ -67,7 +68,7 @@ export default class LinkFormView extends View {
 		 *
 		 * @member {module:ui/labeledfield/labeledfieldview~LabeledFieldView}
 		 */
-		this.urlInputView = this._createUrlInput();
+		this.urlInputView = this._createUrlInput( protocol );
 
 		/**
 		 * The Save button view.
@@ -207,15 +208,15 @@ export default class LinkFormView extends View {
 	 * Creates a labeled input view.
 	 *
 	 * @private
+	 * @param {String} [protocol=http://] A value of a protocol to be displayed in the input's placeholder.
 	 * @returns {module:ui/labeledfield/labeledfieldview~LabeledFieldView} Labeled field view instance.
 	 */
-	_createUrlInput() {
+	_createUrlInput( protocol = 'https://' ) {
 		const t = this.locale.t;
-
 		const labeledInput = new LabeledFieldView( this.locale, createLabeledInputText );
 
 		labeledInput.label = t( 'Link URL' );
-		labeledInput.fieldView.placeholder = 'https://example.com';
+		labeledInput.fieldView.placeholder = protocol + 'example.com';
 
 		return labeledInput;
 	}

+ 145 - 1
packages/ckeditor5-link/tests/linkui.js

@@ -8,7 +8,7 @@
 import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
 import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
 import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard';
-import { setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
+import { setData as setModelData, getData as getModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
 import { getData as getViewData } from '@ckeditor/ckeditor5-engine/src/dev-utils/view';
 
 import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
@@ -891,6 +891,27 @@ describe( 'LinkUI', () => {
 	describe( 'link form view', () => {
 		let focusEditableSpy;
 
+		const createEditorWithDefaultProtocol = defaultProtocol => {
+			return ClassicTestEditor
+				.create( editorElement, {
+					plugins: [ LinkEditing, LinkUI, Paragraph, BlockQuote ],
+					link: { defaultProtocol }
+				} )
+				.then( editor => {
+					const linkUIFeature = editor.plugins.get( LinkUI );
+					const formView = linkUIFeature.formView;
+
+					formView.render();
+
+					editor.model.schema.extend( '$text', {
+						allowIn: '$root',
+						allowAttributes: 'linkHref'
+					} );
+
+					return { editor, formView };
+				} );
+		};
+
 		beforeEach( () => {
 			focusEditableSpy = testUtils.sinon.spy( editor.editing.view, 'focus' );
 		} );
@@ -905,6 +926,129 @@ describe( 'LinkUI', () => {
 			expect( editor.ui.focusTracker.isFocused ).to.be.true;
 		} );
 
+		describe( 'link protocol', () => {
+			it( 'should use a default link protocol from the `config.link.defaultProtocol` when provided', () => {
+				return ClassicTestEditor
+					.create( editorElement, {
+						link: {
+							defaultProtocol: 'https://'
+						}
+					} )
+					.then( editor => {
+						const defaultProtocol = editor.config.get( 'link.defaultProtocol' );
+
+						expect( defaultProtocol ).to.equal( 'https://' );
+
+						editor.destroy();
+					} );
+			} );
+
+			it( 'should not add a protocol without the configuration', () => {
+				formView.urlInputView.fieldView.value = 'ckeditor.com';
+				formView.fire( 'submit' );
+
+				expect( formView.urlInputView.fieldView.value ).to.equal( 'ckeditor.com' );
+			} );
+
+			it( 'should not add a protocol to the local links even when `config.link.defaultProtocol` configured', () => {
+				return createEditorWithDefaultProtocol( 'http://' ).then( ( { editor, formView } ) => {
+					formView.urlInputView.fieldView.value = '#test';
+					formView.fire( 'submit' );
+
+					expect( formView.urlInputView.fieldView.value ).to.equal( '#test' );
+
+					editor.destroy();
+				} );
+			} );
+
+			it( 'should not add a protocol to the relative links even when `config.link.defaultProtocol` configured', () => {
+				return createEditorWithDefaultProtocol( 'http://' ).then( ( { editor, formView } ) => {
+					formView.urlInputView.fieldView.value = '/test.html';
+					formView.fire( 'submit' );
+
+					expect( formView.urlInputView.fieldView.value ).to.equal( '/test.html' );
+
+					editor.destroy();
+				} );
+			} );
+
+			it( 'should not add a protocol when given provided within the value even when `config.link.defaultProtocol` configured', () => {
+				return createEditorWithDefaultProtocol( 'http://' ).then( ( { editor, formView } ) => {
+					formView.urlInputView.fieldView.value = 'http://example.com';
+					formView.fire( 'submit' );
+
+					expect( formView.urlInputView.fieldView.value ).to.equal( 'http://example.com' );
+
+					editor.destroy();
+				} );
+			} );
+
+			it( 'should use the "http://" protocol when it\'s configured', () => {
+				return createEditorWithDefaultProtocol( 'http://' ).then( ( { editor, formView } ) => {
+					formView.urlInputView.fieldView.value = 'ckeditor.com';
+					formView.fire( 'submit' );
+
+					expect( formView.urlInputView.fieldView.value ).to.equal( 'http://ckeditor.com' );
+
+					editor.destroy();
+				} );
+			} );
+
+			it( 'should use the "http://" protocol when it\'s configured and form input value contains "www."', () => {
+				return createEditorWithDefaultProtocol( 'http://' ).then( ( { editor, formView } ) => {
+					formView.urlInputView.fieldView.value = 'www.ckeditor.com';
+					formView.fire( 'submit' );
+
+					expect( formView.urlInputView.fieldView.value ).to.equal( 'http://www.ckeditor.com' );
+
+					editor.destroy();
+				} );
+			} );
+
+			it( 'should propagate the protocol to the link\'s `linkHref` attribute in model', () => {
+				return createEditorWithDefaultProtocol( 'http://' ).then( ( { editor, formView } ) => {
+					setModelData( editor.model, '[ckeditor.com]' );
+
+					formView.urlInputView.fieldView.value = 'ckeditor.com';
+					formView.fire( 'submit' );
+
+					expect( getModelData( editor.model ) ).to.equal(
+						'[<$text linkHref="http://ckeditor.com">ckeditor.com</$text>]'
+					);
+
+					editor.destroy();
+				} );
+			} );
+
+			it( 'should detect an email on submitting the form and add "mailto:" protocol automatically to the provided value', () => {
+				return createEditorWithDefaultProtocol( 'http://' ).then( ( { editor, formView } ) => {
+					setModelData( editor.model, '[email@example.com]' );
+
+					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(
+						'[<$text linkHref="mailto:email@example.com">email@example.com</$text>]'
+					);
+
+					editor.destroy();
+				} );
+			} );
+
+			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 } ) => {
+					formView.urlInputView.fieldView.value = 'mailto:test@example.com';
+					formView.fire( 'submit' );
+
+					expect( formView.urlInputView.fieldView.value ).to.equal( 'mailto:test@example.com' );
+
+					editor.destroy();
+				} );
+			} );
+		} );
+
 		describe( 'binding', () => {
 			beforeEach( () => {
 				setModelData( editor.model, '<paragraph>f[o]o</paragraph>' );

+ 59 - 0
packages/ckeditor5-link/tests/manual/protocol.html

@@ -0,0 +1,59 @@
+<style>
+	code {
+		background: hsl(0, 0%, 90%);
+	}
+
+	sup {
+		position: relative;
+	}
+
+	.ck-editor__editable sup:after {
+		content: '';
+		display: inline-block;
+		position: absolute;
+		width: 14px;
+		height: 14px;
+		left: 1px;
+		top: 1px;
+		background: hsla(207, 87%, 55%, 0.5);
+		border-radius: 50px;
+		animation: pulse 2s linear infinite;
+	}
+
+	@keyframes pulse {
+		0% {
+			transform: scale(1);
+		}
+
+		50% {
+			transform: scale(2);
+		}
+
+		100% {
+			transform: scale(1);
+		}
+	}
+</style>
+
+
+<h2><code>Feature is disabled</code></h2>
+<div id="editor0">
+	<p>This is <a href="http://ckeditor.com">CKEditor5</a> from <a href="http://cksource.com">CKSource</a>. If you need more information please contact us at support@example.com.</p>
+</div>
+
+<h2><code>http://</code></h2>
+<div id="editor1">
+	<p>This is <a href="http://ckeditor.com">CKEditor5</a> from <a href="http://cksource.com">CKSource</a>. If you need more information please contact us at support@example.com <sup class="indicator">[1]</sup>.</p>
+</div>
+
+<p><sup>[1]</sup><strong>When feature enabled:</strong> copy the email address and create a link with it (<code>mailto:</code> protocol will be added automatically).</p>
+
+<h2><code>https://</code></h2>
+<div id="editor2">
+	<p>This is <a href="http://ckeditor.com">CKEditor5</a> from <a href="http://cksource.com">CKSource</a>. If you need more information please contact us at support@example.com.</p>
+</div>
+
+<h2><code>mailto:</code></h2>
+<div id="editor3">
+	<p>This is <a href="http://ckeditor.com">CKEditor5</a> from <a href="http://cksource.com">CKSource</a>. If you need more information please contact us at support@example.com.</p>
+</div>

+ 37 - 0
packages/ckeditor5-link/tests/manual/protocol.js

@@ -0,0 +1,37 @@
+/**
+ * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+/* globals console:false, window, document */
+
+import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
+import Enter from '@ckeditor/ckeditor5-enter/src/enter';
+import Typing from '@ckeditor/ckeditor5-typing/src/typing';
+import Link from '../../src/link';
+import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
+import Undo from '@ckeditor/ckeditor5-undo/src/undo';
+import Superscript from '@ckeditor/ckeditor5-basic-styles/src/superscript';
+
+createEditorWithDefaultProtocol( '#editor0' );
+createEditorWithDefaultProtocol( '#editor1', 'http://' );
+createEditorWithDefaultProtocol( '#editor2', 'https://' );
+createEditorWithDefaultProtocol( '#editor3', 'mailto:' );
+
+function createEditorWithDefaultProtocol( editor, defaultProtocol ) {
+	return ClassicEditor
+		.create( document.querySelector( editor ), {
+			plugins: [ Link, Typing, Paragraph, Undo, Enter, Superscript ],
+			toolbar: [ 'link', 'undo', 'redo' ],
+			link: {
+				addTargetToExternalLinks: true,
+				...defaultProtocol && { defaultProtocol }
+			}
+		} )
+		.then( editor => {
+			window.editor = editor;
+		} )
+		.catch( err => {
+			console.error( err.stack );
+		} );
+}

+ 6 - 0
packages/ckeditor5-link/tests/manual/protocol.md

@@ -0,0 +1,6 @@
+## Link protocol
+
+This test checks whether:
+- `config.link.defaultProtocol` applies.
+- when input value starts with a protocol-like syntax (like `http://` etc.) or any non-word (like `#` or `/`) then `defaultProtocol` won't be applied.
+- the plugin dynamically change link protocol to `mailto:` when email address was detected.