Skip to content

Commit

Permalink
Merge pull request #15373 from ckeditor/ck/15051
Browse files Browse the repository at this point in the history
Fix (html-support): The `DocumentSelection` should not store the GHS `linkA` attribute if the `linkHref` attribute was removed by the two-step caret movement feature. Closes #15051.

Other (link, typing): The logic behind the two-step caret movement extracted to the common code in the two-step caret movement feature.

Other (typing): Unified behavior of the `insertText` command for cases using the `DocumentSelection` and `Selection` as applied attributes behaved differently in those cases.
  • Loading branch information
arkflpc authored Nov 22, 2023
2 parents ad94655 + 2e858ed commit 73c2985
Show file tree
Hide file tree
Showing 12 changed files with 1,456 additions and 582 deletions.
3 changes: 2 additions & 1 deletion packages/ckeditor5-engine/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ export { transformSets } from './model/operation/transform';
export {
default as DocumentSelection,
type DocumentSelectionChangeRangeEvent,
type DocumentSelectionChangeMarkerEvent
type DocumentSelectionChangeMarkerEvent,
type DocumentSelectionChangeAttributeEvent
} from './model/documentselection';
export { default as Range } from './model/range';
export { default as LiveRange, type LiveRangeChangeRangeEvent } from './model/liverange';
Expand Down
39 changes: 38 additions & 1 deletion packages/ckeditor5-html-support/src/datafilter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ import {
type ViewElement,
type MatchResult,
type ViewConsumable,
type MatcherObjectPattern
type MatcherObjectPattern,
type DocumentSelectionChangeAttributeEvent
} from 'ckeditor5/src/engine';

import {
Expand Down Expand Up @@ -438,6 +439,7 @@ export default class DataFilter extends Plugin {
*/
private _registerCoupledAttributesPostFixer() {
const model = this.editor.model;
const selection = model.document.selection;

model.document.registerPostFixer( writer => {
const changes = model.document.differ.getChanges();
Expand Down Expand Up @@ -471,6 +473,41 @@ export default class DataFilter extends Plugin {

return changed;
} );

this.listenTo<DocumentSelectionChangeAttributeEvent>( selection, 'change:attribute', ( evt, { attributeKeys } ) => {
const removeAttributes = new Set<string>();
const coupledAttributes = this._getCoupledAttributesMap();

for ( const attributeKey of attributeKeys ) {
// Handle only attribute removals.
if ( selection.hasAttribute( attributeKey ) ) {
continue;
}

// Find a list of coupled GHS attributes.
const coupledAttributeKeys = coupledAttributes.get( attributeKey );

if ( !coupledAttributeKeys ) {
continue;
}

for ( const coupledAttributeKey of coupledAttributeKeys ) {
if ( selection.hasAttribute( coupledAttributeKey ) ) {
removeAttributes.add( coupledAttributeKey );
}
}
}

if ( removeAttributes.size == 0 ) {
return;
}

model.change( writer => {
for ( const attributeKey of removeAttributes ) {
writer.removeSelectionAttribute( attributeKey );
}
} );
} );
}

/**
Expand Down
104 changes: 104 additions & 0 deletions packages/ckeditor5-html-support/tests/datafilter.js
Original file line number Diff line number Diff line change
Expand Up @@ -4045,6 +4045,110 @@ describe( 'DataFilter', () => {
'</p>'
);
} );

it( 'should remove GHS selection attribute for the same range as a coupled feature attribute was removed', () => {
dataFilter.loadAllowedConfig( [ {
name: /^.*$/,
styles: true,
attributes: true,
classes: true
} ] );

editor.setData( '<p><a href="foo" class="bar">foobar</a></p>' );

expect( getModelDataWithAttributes( model ) ).to.deep.equal( {
data: '<paragraph><$text htmlA="(1)" linkHref="foo">[]foobar</$text></paragraph>',
attributes: {
1: {
classes: [ 'bar' ]
}
}
} );

expect( model.document.selection.getAttribute( 'linkHref' ) ).to.deep.equal( 'foo' );
expect( model.document.selection.getAttribute( 'htmlA' ) ).to.deep.equal( { classes: [ 'bar' ] } );

expect( editor.getData() ).to.equal( '<p><a class="bar" href="foo">foobar</a></p>' );

model.change( writer => {
writer.removeSelectionAttribute( 'linkHref' );
} );

expect( getModelDataWithAttributes( model ) ).to.deep.equal( {
data: '<paragraph>[]<$text htmlA="(1)" linkHref="foo">foobar</$text></paragraph>',
attributes: {
1: {
classes: [ 'bar' ]
}
}
} );

expect( model.document.selection.getAttribute( 'linkHref' ) ).to.be.undefined;
expect( model.document.selection.getAttribute( 'htmlA' ) ).to.be.undefined;

expect( editor.getData() ).to.equal( '<p><a class="bar" href="foo">foobar</a></p>' );
} );

it( 'should not remove other GHS selection attribute when other coupled one is removed', () => {
dataFilter.loadAllowedConfig( [ {
name: /^.*$/,
styles: true,
attributes: true,
classes: true
} ] );

editor.setData( '<p><span style="color:red;text-transform:uppercase;"><strong>foobar</strong></span></p>' );

expect( getModelDataWithAttributes( model ) ).to.deep.equal( {
data: '<paragraph><$text fontColor="red" htmlSpan="(1)" htmlStrong="(2)">[]foobar</$text></paragraph>',
attributes: {
1: {
styles: {
'text-transform': 'uppercase'
}
},
2: {}
}
} );

expect( model.document.selection.getAttribute( 'fontColor' ) ).to.deep.equal( 'red' );
expect( model.document.selection.getAttribute( 'htmlSpan' ) ).to.deep.equal( { styles: { 'text-transform': 'uppercase' } } );
expect( model.document.selection.getAttribute( 'htmlStrong' ) ).to.deep.equal( {} );

expect( editor.getData() ).to.equal(
'<p><span style="color:red;"><span style="text-transform:uppercase;"><strong>foobar</strong></span></span></p>'
);

model.change( writer => {
writer.removeSelectionAttribute( 'fontColor' );
} );

expect( getModelDataWithAttributes( model ) ).to.deep.equal( {
data:
'<paragraph>' +
'<$text htmlSpan="(1)" htmlStrong="(2)">[]</$text>' +
'<$text fontColor="red" htmlSpan="(3)" htmlStrong="(4)">foobar</$text>' +
'</paragraph>',
attributes: {
1: {
styles: {
'text-transform': 'uppercase'
}
},
2: {}
}
} );

expect( model.document.selection.getAttribute( 'fontColor' ) ).to.be.undefined;
expect( model.document.selection.getAttribute( 'htmlSpan' ) ).to.deep.equal( { styles: { 'text-transform': 'uppercase' } } );
expect( model.document.selection.getAttribute( 'htmlStrong' ) ).to.deep.equal( {} );

expect( editor.getData() ).to.equal(
'<p>' +
'<span style="color:red;"><span style="text-transform:uppercase;"><strong>foobar</strong></span></span>' +
'</p>'
);
} );
} );

describe( 'loadAllowedEmptyElementsConfig', () => {
Expand Down
18 changes: 18 additions & 0 deletions packages/ckeditor5-html-support/tests/manual/ghs-link.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<head>
<meta http-equiv="Content-Security-Policy" content="script-src 'self' https://ckeditor.com 'unsafe-inline' 'unsafe-eval'">
<style>
.link {
display: inline-block;
padding: 1em;
outline: 1px solid rgba(255, 0, 0, 0.3);
}
</style>
</head>

<div id="editor">
<p>foo <a href="#bar" class="link">bar</a></p>
<p>foo <a href="#bar" class="link">bar</a> baz</p>
<p><a href="#bar" class="link">bar</a> baz</p>
<p><a href="#bar" class="link">bar</a></p>
<p><a href="#foo" class="link">foo</a><a href="#bar" class="link">bar</a></p>
</div>
44 changes: 44 additions & 0 deletions packages/ckeditor5-html-support/tests/manual/ghs-link.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. 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 ArticlePluginSet from '@ckeditor/ckeditor5-core/tests/_utils/articlepluginset';
import SourceEditing from '@ckeditor/ckeditor5-source-editing/src/sourceediting';

import GeneralHtmlSupport from '../../src/generalhtmlsupport';

ClassicEditor
.create( document.querySelector( '#editor' ), {
plugins: [
ArticlePluginSet, SourceEditing, GeneralHtmlSupport
],
toolbar: [
'sourceEditing',
'|',
'heading',
'|',
'bold', 'italic', 'link',
'|',
'undo', 'redo'
],
htmlSupport: {
allow: [
{
name: /^.*$/,
styles: true,
attributes: true,
classes: true
}
]
}
} )
.then( editor => {
window.editor = editor;
} )
.catch( err => {
console.error( err.stack );
} );
1 change: 1 addition & 0 deletions packages/ckeditor5-html-support/tests/manual/ghs-link.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
## GHS Link handling
Loading

0 comments on commit 73c2985

Please sign in to comment.