浏览代码

Merge pull request #10 from ckeditor/i/6003

Fix: Fix non-flat exception markers. Closes ckeditor/ckeditor5#6003.
Piotrek Koszuliński 6 年之前
父节点
当前提交
c0cc532f06

+ 29 - 0
packages/ckeditor5-restricted-editing/src/restrictededitingmodeediting.js

@@ -168,6 +168,7 @@ export default class RestrictedEditingModeEditing extends Plugin {
 
 		doc.registerPostFixer( extendMarkerOnTypingPostFixer( editor ) );
 		doc.registerPostFixer( resurrectCollapsedMarkerPostFixer( editor ) );
+		model.markers.on( 'update:restrictedEditingException', ensureNewMarkerIsFlat( editor ) );
 
 		setupExceptionHighlighting( editor );
 	}
@@ -377,3 +378,31 @@ function isRangeInsideSingleMarker( editor, range ) {
 
 	return markerAtStart && markerAtEnd && markerAtEnd === markerAtStart;
 }
+
+// Checks if new marker range is flat. Non-flat ranges might appear during upcast conversion in nested structures, ie tables.
+//
+// Note: This marker fixer only consider case which is possible to create using StandardEditing mode plugin.
+// Markers created by developer in the data might break in many other ways.
+//
+// See #6003.
+function ensureNewMarkerIsFlat( editor ) {
+	const model = editor.model;
+
+	return ( evt, marker, oldRange, newRange ) => {
+		if ( !oldRange && !newRange.isFlat ) {
+			model.change( writer => {
+				const start = newRange.start;
+				const end = newRange.end;
+
+				const startIsHigherInTree = start.path.length > end.path.length;
+
+				const fixedStart = startIsHigherInTree ? newRange.start : writer.createPositionAt( end.parent, 0 );
+				const fixedEnd = startIsHigherInTree ? writer.createPositionAt( start.parent, 'end' ) : newRange.end;
+
+				writer.updateMarker( marker, {
+					range: writer.createRange( fixedStart, fixedEnd )
+				} );
+			} );
+		}
+	};
+}

+ 9 - 0
packages/ckeditor5-restricted-editing/tests/manual/restrictedediting.html

@@ -9,6 +9,15 @@
 	<p>Paragraph <span class="restricted-editing-exception">it is editable</span></p>
 	<p>Exception on part of a word: <a href="ckeditor.com">coompi</a><span class="restricted-editing-exception"><a href="ckeditor.com">ter</a> (fix spelling in Firefox).</span></p>
 	<p><strong>Bold</strong> <i>Italic</i> <a href="foo">Link</a></p>
+	<figure class="table">
+		<table>
+			<tbody>
+				<tr>
+					<td><span class="restricted-editing-exception">foo bar</span></td>
+				</tr>
+			</tbody>
+		</table>
+	</figure>
 	<ul>
 		<li>UL List item 1</li>
 		<li>UL List item 2</li>

+ 100 - 3
packages/ckeditor5-restricted-editing/tests/restrictededitingmodeediting.js

@@ -17,6 +17,7 @@ import Clipboard from '@ckeditor/ckeditor5-clipboard/src/clipboard';
 
 import RestrictedEditingModeEditing from './../src/restrictededitingmodeediting';
 import RestrictedEditingModeNavigationCommand from '../src/restrictededitingmodenavigationcommand';
+import TableEditing from '@ckeditor/ckeditor5-table/src/tableediting';
 
 describe( 'RestrictedEditingModeEditing', () => {
 	let editor, model;
@@ -60,12 +61,12 @@ describe( 'RestrictedEditingModeEditing', () => {
 
 	describe( 'conversion', () => {
 		beforeEach( async () => {
-			editor = await VirtualTestEditor.create( { plugins: [ Paragraph, RestrictedEditingModeEditing ] } );
+			editor = await VirtualTestEditor.create( { plugins: [ Paragraph, TableEditing, RestrictedEditingModeEditing ] } );
 			model = editor.model;
 		} );
 
-		afterEach( () => {
-			return editor.destroy();
+		afterEach( async () => {
+			await editor.destroy();
 		} );
 
 		describe( 'upcast', () => {
@@ -96,6 +97,21 @@ describe( 'RestrictedEditingModeEditing', () => {
 				expect( secondMarker.getEnd().path ).to.deep.equal( [ 1, 11 ] );
 			} );
 
+			it( 'should convert <span class="restricted-editing-exception"> inside table to marker', () => {
+				editor.setData(
+					'<figure class="table">' +
+						'<table><tbody><tr><td><span class="restricted-editing-exception">bar</span></td></tr></tbody></table>' +
+					'</figure>'
+				);
+
+				expect( model.markers.has( 'restrictedEditingException:1' ) ).to.be.true;
+
+				const marker = model.markers.get( 'restrictedEditingException:1' );
+
+				expect( marker.getStart().path ).to.deep.equal( [ 0, 0, 0, 0, 0 ] );
+				expect( marker.getEnd().path ).to.deep.equal( [ 0, 0, 0, 0, 3 ] );
+			} );
+
 			it( 'should not convert other <span> elements', () => {
 				editor.setData( '<p>foo <span class="foo bar">bar</span> baz</p>' );
 
@@ -164,6 +180,87 @@ describe( 'RestrictedEditingModeEditing', () => {
 					'</p>'
 				);
 			} );
+
+			it( 'converted <span> should be the outermost attribute element', () => {
+				editor.conversion.for( 'downcast' ).attributeToElement( { model: 'bold', view: 'b' } );
+				setModelData( model,
+					'<table><tableRow><tableCell>' +
+					'<paragraph><$text bold="true">foo bar baz</$text></paragraph>' +
+					'</tableCell></tableRow></table>'
+				);
+
+				const paragraph = model.document.getRoot().getChild( 0 ).getChild( 0 ).getChild( 0 ).getChild( 0 );
+
+				model.change( writer => {
+					writer.addMarker( 'restrictedEditingException:1', {
+						range: writer.createRange( writer.createPositionAt( paragraph, 0 ), writer.createPositionAt( paragraph, 'end' ) ),
+						usingOperation: true,
+						affectsData: true
+					} );
+				} );
+
+				assertEqualMarkup( editor.getData(),
+					'<figure class="table"><table><tbody><tr><td>' +
+					'<span class="restricted-editing-exception"><b>foo bar baz</b></span>' +
+					'</td></tr></tbody></table></figure>'
+				);
+				assertEqualMarkup( getViewData( editor.editing.view, { withoutSelection: true } ),
+					'<figure class="ck-widget ck-widget_with-selection-handle table" contenteditable="false">' +
+					'<div class="ck ck-widget__selection-handle"></div>' +
+					'<table><tbody><tr><td class="ck-editor__editable ck-editor__nested-editable" contenteditable="true">' +
+					'<span><span class="restricted-editing-exception"><b>foo bar baz</b></span></span>' +
+					'</td></tr></tbody></table>' +
+					'</figure>'
+				);
+			} );
+		} );
+
+		describe( 'flattening exception markers', () => {
+			it( 'should fix non-flat marker range (start is higher in tree)', () => {
+				setModelData( model, '<table><tableRow><tableCell><paragraph>foo bar baz</paragraph></tableCell></tableRow></table>' );
+				const tableCell = model.document.getRoot().getNodeByPath( [ 0, 0, 0 ] );
+				const paragraph = model.document.getRoot().getNodeByPath( [ 0, 0, 0, 0 ] );
+
+				model.change( writer => {
+					writer.addMarker( `restrictedEditingException:${ 1 }`, {
+						range: writer.createRange(
+							writer.createPositionAt( paragraph, 0 ),
+							writer.createPositionAt( tableCell, 'end' )
+						),
+						usingOperation: true,
+						affectsData: true
+					} );
+				} );
+
+				const marker = model.markers.get( 'restrictedEditingException:1' );
+
+				expect( marker.getStart().parent ).to.equal( marker.getEnd().parent );
+				expect( marker.getStart().path ).to.deep.equal( [ 0, 0, 0, 0, 0 ] );
+				expect( marker.getEnd().path ).to.deep.equal( [ 0, 0, 0, 0, 11 ] );
+			} );
+
+			it( 'should fix non-flat marker range (end is higher in tree)', () => {
+				setModelData( model, '<table><tableRow><tableCell><paragraph>foo bar baz</paragraph></tableCell></tableRow></table>' );
+				const tableCell = model.document.getRoot().getNodeByPath( [ 0, 0, 0 ] );
+				const paragraph = model.document.getRoot().getNodeByPath( [ 0, 0, 0, 0 ] );
+
+				model.change( writer => {
+					writer.addMarker( `restrictedEditingException:${ 1 }`, {
+						range: writer.createRange(
+							writer.createPositionAt( tableCell, 0 ),
+							writer.createPositionAt( paragraph, 'end' )
+						),
+						usingOperation: true,
+						affectsData: true
+					} );
+				} );
+
+				const marker = model.markers.get( 'restrictedEditingException:1' );
+
+				expect( marker.getStart().parent ).to.equal( marker.getEnd().parent );
+				expect( marker.getStart().path ).to.deep.equal( [ 0, 0, 0, 0, 0 ] );
+				expect( marker.getEnd().path ).to.deep.equal( [ 0, 0, 0, 0, 11 ] );
+			} );
 		} );
 	} );