Преглед изворни кода

Merge branch 'master' into i/7956-reconversion-trigger-by

Maciej Gołaszewski пре 5 година
родитељ
комит
7eefb611a5

+ 1 - 1
.github/workflows/merge-stable.yml

@@ -22,7 +22,7 @@ jobs:
       - uses: rtCamp/action-slack-notify@v2.0.2
         id: error_message_slack
         name: Slack notification
-        if: (steps.merge_action.outputs.status != 201) && (steps.merge_action.outputs.status != 204)
+        if: ((steps.merge_action.outputs.status != 201) && (steps.merge_action.outputs.status != 204)) || failure()
         env:
           SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
           SLACK_CHANNEL: "cke5-ci"

+ 2 - 2
docs/framework/guides/contributing/code-style.md

@@ -334,8 +334,8 @@ CKEditor 5 development environment uses [ESLint](https://eslint.org) and [stylel
 A couple of useful links:
 
 * [Disabling ESLint with inline comments](https://eslint.org/docs/2.13.1/user-guide/configuring#disabling-rules-with-inline-comments).
-* [CKEditor 5 ESLint preset](https://github.com/ckeditor/ckeditor5-dev/blob/master/packages/eslint-config-ckeditor5/.eslintrc.js) (npm: [`eslint-config-ckeditor5`](http://npmjs.com/package/eslint-config-ckeditor5)).
-* [CKEditor 5 stylelint preset](https://github.com/ckeditor/ckeditor5-dev/blob/master/packages/stylelint-config-ckeditor5/.stylelintrc) (npm: [`stylelint-config-ckeditor5`](https://www.npmjs.com/package/stylelint-config-ckeditor5)).
+* [CKEditor 5 ESLint preset](https://github.com/ckeditor/eslint-config-ckeditor5/blob/master/.eslintrc.js) (npm: [`eslint-config-ckeditor5`](http://npmjs.com/package/eslint-config-ckeditor5)).
+* [CKEditor 5 stylelint preset](https://github.com/ckeditor/stylelint-config-ckeditor5/blob/master/.stylelintrc) (npm: [`stylelint-config-ckeditor5`](https://www.npmjs.com/package/stylelint-config-ckeditor5)).
 
 <info-box>
 	Avoid using automatic code formatters on existing code. It is fine to automatically format code that you are editing, but you should not be changing the formatting of the code that is already written to not pollute your PRs. You should also not rely solely on automatic corrections.

+ 1 - 1
package.json

@@ -131,7 +131,7 @@
   },
   "scripts": {
     "lint": "eslint --quiet '**/*.js'",
-    "stylelint": "stylelint --quiet --allow-empty-input 'packages/**/*.css' 'external/*/packages/**/*.css' 'docs/**/*.css'",
+    "stylelint": "stylelint --quiet --allow-empty-input 'packages/**/*.css' 'docs/**/*.css'",
     "test": "node --max_old_space_size=8192 node_modules/@ckeditor/ckeditor5-dev-tests/bin/test.js",
     "manual": "node --max_old_space_size=8192 node_modules/@ckeditor/ckeditor5-dev-tests/bin/test-manual.js",
     "bootstrap": "yarn install",

+ 43 - 8
packages/ckeditor5-link/src/linkui.js

@@ -662,16 +662,27 @@ export default class LinkUI extends Plugin {
 		const model = this.editor.model;
 
 		model.change( writer => {
+			const range = model.document.selection.getFirstRange();
+
 			if ( model.markers.has( VISUAL_SELECTION_MARKER_NAME ) ) {
-				writer.updateMarker( VISUAL_SELECTION_MARKER_NAME, {
-					range: model.document.selection.getFirstRange()
-				} );
+				writer.updateMarker( VISUAL_SELECTION_MARKER_NAME, { range } );
 			} else {
-				writer.addMarker( VISUAL_SELECTION_MARKER_NAME, {
-					usingOperation: false,
-					affectsData: false,
-					range: model.document.selection.getFirstRange()
-				} );
+				if ( range.start.isAtEnd ) {
+					const focus = model.document.selection.focus;
+					const nextValidRange = getNextValidRange( range, focus, writer );
+
+					writer.addMarker( VISUAL_SELECTION_MARKER_NAME, {
+						usingOperation: false,
+						affectsData: false,
+						range: nextValidRange
+					} );
+				} else {
+					writer.addMarker( VISUAL_SELECTION_MARKER_NAME, {
+						usingOperation: false,
+						affectsData: false,
+						range
+					} );
+				}
 			}
 		} );
 	}
@@ -700,3 +711,27 @@ export default class LinkUI extends Plugin {
 function findLinkElementAncestor( position ) {
 	return position.getAncestors().find( ancestor => isLinkElement( ancestor ) );
 }
+
+// Returns next valid range for the fake visual selection marker.
+//
+// @private
+// @param {module:engine/model/range~Range} range Current range.
+// @param {module:engine/model/position~Position} focus Selection focus.
+// @param {module:engine/model/writer~Writer} writer Writer.
+// @returns {module:engine/model/range~Range} New valid range for the fake visual selection marker.
+function getNextValidRange( range, focus, writer ) {
+	const nextStartPath = [ range.start.path[ 0 ] + 1, 0 ];
+	const nextStartPosition = writer.createPositionFromPath( range.start.root, nextStartPath, 'toNext' );
+	const nextRange = writer.createRange( nextStartPosition, range.end );
+
+	// Block creating a potential next valid range over the current range end.
+	if ( nextRange.start.path[ 0 ] > range.end.path[ 0 ] ) {
+		return writer.createRange( focus );
+	}
+
+	if ( nextStartPosition.isAtStart && nextStartPosition.isAtEnd ) {
+		return getNextValidRange( nextRange, focus, writer );
+	}
+
+	return nextRange;
+}

+ 212 - 29
packages/ckeditor5-link/tests/linkui.js

@@ -477,46 +477,229 @@ describe( 'LinkUI', () => {
 			} );
 		} );
 
-		it( 'should display a fake visual selection when a text fragment is selected', () => {
-			setModelData( editor.model, '<paragraph>f[o]o</paragraph>' );
+		describe( 'fake visual selection', () => {
+			describe( 'non-collapsed', () => {
+				it( 'should be displayed when a text fragment is selected', () => {
+					setModelData( editor.model, '<paragraph>f[o]o</paragraph>' );
 
-			linkUIFeature._showUI();
+					linkUIFeature._showUI();
 
-			expect( editor.model.markers.has( 'link-ui' ) ).to.be.true;
+					expect( editor.model.markers.has( 'link-ui' ) ).to.be.true;
 
-			const paragraph = editor.model.document.getRoot().getChild( 0 );
-			const expectedRange = editor.model.createRange(
-				editor.model.createPositionAt( paragraph, 1 ),
-				editor.model.createPositionAt( paragraph, 2 )
-			);
-			const markerRange = editor.model.markers.get( 'link-ui' ).getRange();
+					const paragraph = editor.model.document.getRoot().getChild( 0 );
+					const expectedRange = editor.model.createRange(
+						editor.model.createPositionAt( paragraph, 1 ),
+						editor.model.createPositionAt( paragraph, 2 )
+					);
+					const markerRange = editor.model.markers.get( 'link-ui' ).getRange();
 
-			expect( markerRange.isEqual( expectedRange ) ).to.be.true;
+					expect( markerRange.isEqual( expectedRange ) ).to.be.true;
 
-			expect( getViewData( editor.editing.view ) ).to.equal( '<p>f{<span class="ck-fake-link-selection">o</span>}o</p>' );
-			expect( editor.getData() ).to.equal( '<p>foo</p>' );
-		} );
+					expect( getViewData( editor.editing.view ) ).to.equal( '<p>f{<span class="ck-fake-link-selection">o</span>}o</p>' );
+					expect( editor.getData() ).to.equal( '<p>foo</p>' );
+				} );
 
-		it( 'should display a fake visual selection on a collapsed selection', () => {
-			setModelData( editor.model, '<paragraph>f[]o</paragraph>' );
+				it( 'should display a fake visual selection on the next non-empty text node when selection starts at the end ' +
+					'of the empty block in the multiline selection', () => {
+					setModelData( editor.model, '<paragraph>[</paragraph><paragraph>foo]</paragraph>' );
 
-			linkUIFeature._showUI();
+					linkUIFeature._showUI();
 
-			expect( editor.model.markers.has( 'link-ui' ) ).to.be.true;
+					expect( editor.model.markers.has( 'link-ui' ) ).to.be.true;
+
+					const secondParagraph = editor.model.document.getRoot().getChild( 1 );
+					const expectedRange = editor.model.createRange(
+						editor.model.createPositionAt( secondParagraph, 0 ),
+						editor.model.createPositionAt( secondParagraph, 3 )
+					);
+
+					const markerRange = editor.model.markers.get( 'link-ui' ).getRange();
+
+					expect( markerRange.isEqual( expectedRange ) ).to.be.true;
+
+					expect( getViewData( editor.editing.view ) ).to.equal(
+						'<p>[</p>' +
+						'<p><span class="ck-fake-link-selection">foo</span>]</p>'
+					);
+					expect( editor.getData() ).to.equal( '<p>&nbsp;</p><p>foo</p>' );
+				} );
+
+				it( 'should display a fake visual selection on the next non-empty text node when selection starts at the end ' +
+					'of the first block in the multiline selection', () => {
+					setModelData( editor.model, '<paragraph>foo[</paragraph><paragraph>bar]</paragraph>' );
+
+					linkUIFeature._showUI();
+
+					expect( editor.model.markers.has( 'link-ui' ) ).to.be.true;
 
-			const paragraph = editor.model.document.getRoot().getChild( 0 );
-			const expectedRange = editor.model.createRange(
-				editor.model.createPositionAt( paragraph, 1 ),
-				editor.model.createPositionAt( paragraph, 1 )
-			);
-			const markerRange = editor.model.markers.get( 'link-ui' ).getRange();
+					const secondParagraph = editor.model.document.getRoot().getChild( 1 );
+					const expectedRange = editor.model.createRange(
+						editor.model.createPositionAt( secondParagraph, 0 ),
+						editor.model.createPositionAt( secondParagraph, 3 )
+					);
+
+					const markerRange = editor.model.markers.get( 'link-ui' ).getRange();
+
+					expect( markerRange.isEqual( expectedRange ) ).to.be.true;
+
+					expect( getViewData( editor.editing.view ) ).to.equal(
+						'<p>foo{</p>' +
+						'<p><span class="ck-fake-link-selection">bar</span>]</p>'
+					);
+					expect( editor.getData() ).to.equal( '<p>foo</p><p>bar</p>' );
+				} );
+
+				it( 'should be displayed on first text node in non-empty element when selection contains few empty elements', () => {
+					setModelData( editor.model, '<paragraph>foo[</paragraph>' +
+						'<paragraph></paragraph>' +
+						'<paragraph></paragraph>' +
+						'<paragraph>bar</paragraph>' +
+						'<paragraph></paragraph>' +
+						'<paragraph></paragraph>' +
+						'<paragraph>]baz</paragraph>' );
+
+					linkUIFeature._showUI();
+
+					expect( editor.model.markers.has( 'link-ui' ) ).to.be.true;
 
-			expect( markerRange.isEqual( expectedRange ) ).to.be.true;
+					const firstNonEmptyElementInTheSelection = editor.model.document.getRoot().getChild( 3 );
+					const rangeEnd = editor.model.document.selection.getFirstRange().end;
+					const expectedRange = editor.model.createRange(
+						editor.model.createPositionAt( firstNonEmptyElementInTheSelection, 0 ),
+						editor.model.createPositionAt( rangeEnd, 0 )
+					);
+
+					const markerRange = editor.model.markers.get( 'link-ui' ).getRange();
+
+					expect( markerRange.isEqual( expectedRange ) ).to.be.true;
+
+					const expectedViewData = '<p>foo{</p>' +
+						'<p></p>' +
+						'<p></p>' +
+						'<p><span class="ck-fake-link-selection">bar</span></p>' +
+						'<p></p>' +
+						'<p></p>' +
+						'<p>}baz</p>';
+
+					expect( getViewData( editor.editing.view ) ).to.equal( expectedViewData );
+					expect( editor.getData() ).to.equal(
+						'<p>foo</p>' +
+						'<p>&nbsp;</p><p>&nbsp;</p>' +
+						'<p>bar</p>' +
+						'<p>&nbsp;</p><p>&nbsp;</p>' +
+						'<p>baz</p>'
+					);
+				} );
+			} );
 
-			expect( getViewData( editor.editing.view ) ).to.equal(
-				'<p>f{}<span class="ck-fake-link-selection ck-fake-link-selection_collapsed"></span>o</p>'
-			);
-			expect( editor.getData() ).to.equal( '<p>fo</p>' );
+			describe( 'collapsed', () => {
+				it( 'should be displayed on a collapsed selection', () => {
+					setModelData( editor.model, '<paragraph>f[]o</paragraph>' );
+
+					linkUIFeature._showUI();
+
+					expect( editor.model.markers.has( 'link-ui' ) ).to.be.true;
+
+					const paragraph = editor.model.document.getRoot().getChild( 0 );
+					const expectedRange = editor.model.createRange(
+						editor.model.createPositionAt( paragraph, 1 ),
+						editor.model.createPositionAt( paragraph, 1 )
+					);
+					const markerRange = editor.model.markers.get( 'link-ui' ).getRange();
+
+					expect( markerRange.isEqual( expectedRange ) ).to.be.true;
+
+					expect( getViewData( editor.editing.view ) ).to.equal(
+						'<p>f{}<span class="ck-fake-link-selection ck-fake-link-selection_collapsed"></span>o</p>'
+					);
+					expect( editor.getData() ).to.equal( '<p>fo</p>' );
+				} );
+
+				it( 'should be displayed on selection focus when selection contains only one empty element ' +
+					'(selection focus is at the beginning of the first non-empty element)', () => {
+					setModelData( editor.model, '<paragraph>foo[</paragraph>' +
+						'<paragraph></paragraph>' +
+						'<paragraph>]bar</paragraph>' );
+
+					linkUIFeature._showUI();
+
+					expect( editor.model.markers.has( 'link-ui' ) ).to.be.true;
+
+					const focus = editor.model.document.selection.focus;
+					const expectedRange = editor.model.createRange(
+						editor.model.createPositionAt( focus, 0 )
+					);
+
+					const markerRange = editor.model.markers.get( 'link-ui' ).getRange();
+
+					expect( markerRange.isEqual( expectedRange ) ).to.be.true;
+
+					const expectedViewData = '<p>foo{</p>' +
+						'<p></p>' +
+						'<p>]<span class="ck-fake-link-selection ck-fake-link-selection_collapsed"></span>bar</p>';
+
+					expect( getViewData( editor.editing.view ) ).to.equal( expectedViewData );
+					expect( editor.getData() ).to.equal( '<p>foo</p><p>&nbsp;</p><p>bar</p>' );
+				} );
+
+				it( 'should be displayed on selection focus when selection contains few empty elements ' +
+					'(selection focus is at the beginning of the first non-empty element)', () => {
+					setModelData( editor.model, '<paragraph>foo[</paragraph>' +
+						'<paragraph></paragraph>' +
+						'<paragraph></paragraph>' +
+						'<paragraph>]bar</paragraph>' );
+
+					linkUIFeature._showUI();
+
+					expect( editor.model.markers.has( 'link-ui' ) ).to.be.true;
+
+					const focus = editor.model.document.selection.focus;
+					const expectedRange = editor.model.createRange(
+						editor.model.createPositionAt( focus, 0 )
+					);
+
+					const markerRange = editor.model.markers.get( 'link-ui' ).getRange();
+
+					expect( markerRange.isEqual( expectedRange ) ).to.be.true;
+
+					const expectedViewData = '<p>foo{</p>' +
+						'<p></p>' +
+						'<p></p>' +
+						'<p>]<span class="ck-fake-link-selection ck-fake-link-selection_collapsed"></span>bar</p>';
+
+					expect( getViewData( editor.editing.view ) ).to.equal( expectedViewData );
+					expect( editor.getData() ).to.equal( '<p>foo</p><p>&nbsp;</p><p>&nbsp;</p><p>bar</p>' );
+				} );
+
+				it( 'should be displayed on selection focus when selection contains few empty elements ' +
+					'(selection focus is inside an empty element)', () => {
+					setModelData( editor.model, '<paragraph>foo[</paragraph>' +
+						'<paragraph></paragraph>' +
+						'<paragraph>]</paragraph>' +
+						'<paragraph>bar</paragraph>' );
+
+					linkUIFeature._showUI();
+
+					expect( editor.model.markers.has( 'link-ui' ) ).to.be.true;
+
+					const focus = editor.model.document.selection.focus;
+					const expectedRange = editor.model.createRange(
+						editor.model.createPositionAt( focus, 0 )
+					);
+
+					const markerRange = editor.model.markers.get( 'link-ui' ).getRange();
+
+					expect( markerRange.isEqual( expectedRange ) ).to.be.true;
+
+					const expectedViewData = '<p>foo{</p>' +
+						'<p></p>' +
+						'<p>]<span class="ck-fake-link-selection ck-fake-link-selection_collapsed"></span></p>' +
+						'<p>bar</p>';
+
+					expect( getViewData( editor.editing.view ) ).to.equal( expectedViewData );
+					expect( editor.getData() ).to.equal( '<p>foo</p><p>&nbsp;</p><p>&nbsp;</p><p>bar</p>' );
+				} );
+			} );
 		} );
 
 		function getMarkersRange( editor ) {

+ 1 - 1
packages/ckeditor5-upload/docs/features/simple-upload-adapter.md

@@ -67,7 +67,7 @@ ClassicEditor
 
 			// Headers sent along with the XMLHttpRequest to the upload server.
 			headers: {
-				'X-CSRF-TOKEN': 'CSFR-Token',
+				'X-CSRF-TOKEN': 'CSRF-Token',
 				Authorization: 'Bearer <JSON Web Token>'
 			}
 		}