Kaynağa Gözat

Added highlighting linked text when selection is inside.

Szymon Kupś 7 yıl önce
ebeveyn
işleme
d808e42bdc

+ 47 - 1
packages/ckeditor5-link/src/linkediting.js

@@ -8,12 +8,14 @@
  */
 
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
-import { downcastAttributeToElement } from '@ckeditor/ckeditor5-engine/src/conversion/downcast-converters';
+import { downcastAttributeToElement, downcastMarkerToHighlight } from '@ckeditor/ckeditor5-engine/src/conversion/downcast-converters';
 import { upcastElementToAttribute } from '@ckeditor/ckeditor5-engine/src/conversion/upcast-converters';
 import LinkCommand from './linkcommand';
 import UnlinkCommand from './unlinkcommand';
 import { createLinkElement } from './utils';
 import bindTwoStepCaretToAttribute from '@ckeditor/ckeditor5-engine/src/utils/bindtwostepcarettoattribute';
+import findLinkRange from './findlinkrange';
+import '../theme/link.css';
 
 /**
  * The link engine feature.
@@ -55,5 +57,49 @@ export default class LinkEditing extends Plugin {
 
 		// Enable two-step caret movement for `linkHref` attribute.
 		bindTwoStepCaretToAttribute( editor.editing.view, editor.model, this, 'linkHref' );
+
+		// Setup highlight over selected link.
+		this._setupLinkHighlight();
+	}
+
+	/**
+	 * Setups a highlight over selected link element.
+	 *
+	 * @private
+	 */
+	_setupLinkHighlight() {
+		const editor = this.editor;
+		const model = this.editor.model;
+		const doc = model.document;
+
+		// Convert linkBoundaries marker to view highlight.
+		editor.conversion.for( 'editingDowncast' )
+			.add( downcastMarkerToHighlight( {
+				model: 'linkBoundaries',
+				view: {
+					class: 'ck-link_selected',
+					priority: 1
+				}
+			} ) );
+
+		doc.on( 'change', () => {
+			const selection = doc.selection;
+
+			// Create marker over link when selection is inside or remove marker otherwise.
+			if ( selection.hasAttribute( 'linkHref' ) ) {
+				const modelRange = findLinkRange( selection.getFirstPosition(), selection.getAttribute( 'linkHref' ) );
+				const marker = model.markers.get( 'linkBoundaries' );
+
+				if ( !marker || !marker.getRange().isEqual( modelRange ) ) {
+					model.change( writer => {
+						writer.setMarker( 'linkBoundaries', modelRange );
+					} );
+				}
+			} else if ( model.markers.has( 'linkBoundaries' ) ) {
+				model.change( writer => {
+					writer.removeMarker( 'linkBoundaries' );
+				} );
+			}
+		} );
 	}
 }

+ 66 - 2
packages/ckeditor5-link/tests/linkediting.js

@@ -9,6 +9,7 @@ import UnlinkCommand from '../src/unlinkcommand';
 
 import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
 import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
+import Enter from '@ckeditor/ckeditor5-enter/src/enter';
 import { getData as getModelData, setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
 import { getData as getViewData } from '@ckeditor/ckeditor5-engine/src/dev-utils/view';
 import { isLinkElement } from '../src/utils';
@@ -19,16 +20,17 @@ import { downcastAttributeToElement } from '@ckeditor/ckeditor5-engine/src/conve
 /* global document */
 
 describe( 'LinkEditing', () => {
-	let editor, model;
+	let editor, model, view;
 
 	beforeEach( () => {
 		return VirtualTestEditor
 			.create( {
-				plugins: [ Paragraph, LinkEditing ]
+				plugins: [ Paragraph, LinkEditing, Enter ]
 			} )
 			.then( newEditor => {
 				editor = newEditor;
 				model = editor.model;
+				view = editor.editing.view;
 			} );
 	} );
 
@@ -143,4 +145,66 @@ describe( 'LinkEditing', () => {
 			expect( editor.getData() ).to.equal( '<p><a href="url">a<f>b</f>c</a></p>' );
 		} );
 	} );
+
+	describe( 'highlight link boundaries', () => {
+		it( 'should create marker in model when selection is inside a link', () => {
+			expect( model.markers.has( 'linkBoundaries' ) ).to.be.false;
+
+			setModelData( model,
+				'<paragraph>foo <$text linkHref="url">b{}ar</$text> baz</paragraph>'
+			);
+
+			expect( model.markers.has( 'linkBoundaries' ) ).to.be.true;
+			const marker = model.markers.get( 'linkBoundaries' );
+			expect( marker.getStart().path ).to.deep.equal( [ 0, 4 ] );
+			expect( marker.getEnd().path ).to.deep.equal( [ 0, 7 ] );
+		} );
+
+		it( 'should convert link boundaries marker to proper view', () => {
+			setModelData( model,
+				'<paragraph>foo <$text linkHref="url">b{}ar</$text> baz</paragraph>'
+			);
+
+			expect( getViewData( view ) ).to.equal(
+				'<p>foo <span class="ck-link_selected"><a href="url">b{}ar</a></span> baz</p>'
+			);
+		} );
+
+		it( 'should work whenever selection has linkHref attribute', () => {
+			setModelData( model,
+				'<paragraph>foo {}<$text linkHref="url">bar</$text> baz</paragraph>'
+			);
+
+			expect( model.document.selection.hasAttribute( 'linkHref' ) ).to.be.false;
+
+			model.change( writer => {
+				writer.setSelectionAttribute( 'linkHref', 'url' );
+			} );
+
+			expect( model.document.selection.hasAttribute( 'linkHref' ) ).to.be.true;
+			expect( model.markers.has( 'linkBoundaries' ) ).to.be.true;
+			const marker = model.markers.get( 'linkBoundaries' );
+			expect( marker.getStart().path ).to.deep.equal( [ 0, 4 ] );
+			expect( marker.getEnd().path ).to.deep.equal( [ 0, 7 ] );
+		} );
+
+		it( 'should render highlight correctly after splitting the link', () => {
+			setModelData( model,
+				'<paragraph>foo <$text linkHref="url">li{}nk</$text> baz</paragraph>'
+			);
+
+			editor.execute( 'enter' );
+
+			expect( getModelData( model ) ).to.equal(
+				'<paragraph>foo <$text linkHref="url">li</$text></paragraph>' +
+				'<paragraph><$text linkHref="url">[]nk</$text> baz</paragraph>'
+			);
+
+			expect( model.document.selection.hasAttribute( 'linkHref' ) ).to.be.true;
+			expect( model.markers.has( 'linkBoundaries' ) ).to.be.true;
+			const marker = model.markers.get( 'linkBoundaries' );
+			expect( marker.getStart().path ).to.deep.equal( [ 1, 0 ] );
+			expect( marker.getEnd().path ).to.deep.equal( [ 1, 2 ] );
+		} );
+	} );
 } );

+ 11 - 3
packages/ckeditor5-link/tests/linkui.js

@@ -9,6 +9,7 @@ import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictest
 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 { getData as getViewData } from '@ckeditor/ckeditor5-engine/src/dev-utils/view';
 
 import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
 import LinkEditing from '../src/linkediting';
@@ -248,8 +249,13 @@ describe( 'LinkUI', () => {
 				linkUIFeature._showUI();
 				const spy = testUtils.sinon.stub( balloon, 'updatePosition' ).returns( {} );
 
+				expect( getViewData( view ) ).to.equal(
+					'<p><span class="ck-link_selected"><a href="url">f{}oo</a></span></p>'
+				);
+
 				const root = viewDocument.getRoot();
-				const text = root.getChild( 0 ).getChild( 0 ).getChild( 0 );
+				const linkElement = root.getChild( 0 ).getChild( 0 ).getChild( 0 );
+				const text = linkElement.getChild( 0 );
 
 				// Move selection to foo[].
 				view.change( writer => {
@@ -258,7 +264,7 @@ describe( 'LinkUI', () => {
 
 				sinon.assert.calledOnce( spy );
 				sinon.assert.calledWithExactly( spy, {
-					target: view.domConverter.mapViewToDom( root.getChild( 0 ).getChild( 0 ) )
+					target: view.domConverter.mapViewToDom( linkElement )
 				} );
 			} );
 
@@ -313,8 +319,10 @@ describe( 'LinkUI', () => {
 				const spyUpdate = testUtils.sinon.stub( balloon, 'updatePosition' ).returns( {} );
 				const spyHide = testUtils.sinon.spy( linkUIFeature, '_hideUI' );
 
+				expect( getViewData( view ) ).to.equal( '<p><span class="ck-link_selected"><a href="url">f{}oo</a></span></p>' );
+
 				const root = viewDocument.getRoot();
-				const text = root.getChild( 0 ).getChild( 0 ).getChild( 0 );
+				const text = root.getChild( 0 ).getChild( 0 ).getChild( 0 ).getChild( 0 );
 
 				// Move selection to f[o]o.
 				view.change( writer => {

+ 3 - 0
packages/ckeditor5-link/tests/manual/linkhighlight.html

@@ -0,0 +1,3 @@
+<div id="editor">
+	<p>foo <a href="http://ckeditor.com">linked text</a> baz</p>
+</div>

+ 25 - 0
packages/ckeditor5-link/tests/manual/linkhighlight.js

@@ -0,0 +1,25 @@
+/**
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* 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';
+
+ClassicEditor
+	.create( document.querySelector( '#editor' ), {
+		plugins: [ Link, Typing, Paragraph, Undo, Enter ],
+		toolbar: [ 'link', 'undo', 'redo' ]
+	} )
+	.then( editor => {
+		window.editor = editor;
+	} )
+	.catch( err => {
+		console.error( err.stack );
+	} );

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

@@ -0,0 +1,6 @@
+## Link highlight
+
+1. Put selection inside "linked text" - whole linked text should be highlighted.
+1. Move selection inside linked text, it should stay highlighted.
+1. Check if it works correctly with two-step caret movement when entering and leaving linked text.
+1. Use enter to split linked text, check if links are highlighted separately.

+ 10 - 0
packages/ckeditor5-link/theme/link.css

@@ -0,0 +1,10 @@
+/*
+ * Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/*
+ * Note: This file should contain the wireframe styles only. But since there are no such styles,
+ * it acts as a message to the builder telling that it should look for the corresponding styles
+ * **in the theme** when compiling the editor.
+ */