Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: prevent duplicate anchors in text with styles #4733

Merged
merged 10 commits into from
Jul 23, 2021
3 changes: 3 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ CKEditor 4 Changelog
## CKEditor 4.16.2

Fixed Issues:
* [#4733](https://github.com/ckeditor/ckeditor4/pull/4733): Fixed: [Link](https://ckeditor.com/cke4/addon/link) prevent duplicate anchors in text with styles.
* [#4728](https://github.com/ckeditor/ckeditor4/issues/4728): Fixed: Multiple anchors in one line and multi-line with text style.
* [#3863](https://github.com/ckeditor/ckeditor4/issues/3863): Fixed: Multiple anchors in single word with text style.
* [#3819](https://github.com/ckeditor/ckeditor4/issues/3819): [Chrome] Fixed: After removing one of the two consecutive spaces, the ` ` character appears in the editor instead of a space.
* [#4666](https://github.com/ckeditor/ckeditor4/pull/4666): [IE] Introduce CSS.escape polyfill. Thanks to [limingli0707](https://github.com/limingli0707)!
* [#681](https://github.com/ckeditor/ckeditor4/issues/681): Fixed: Table elements (td, tr, th, ..) with an id that starts with dot (.) causes javascript runtime err.
Expand Down
27 changes: 27 additions & 0 deletions plugins/link/dialogs/anchor.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,37 @@ CKEDITOR.dialog.add( 'anchor', function( editor ) {
element = element.getParent();
}

// If anchor exists and has any styles find the closest parent <a> tag. (#3863)
if ( element && !element.is( 'a' ) ) {
element = element.getAscendant( 'a' ) || element;
}

if ( element && element.type === CKEDITOR.NODE_ELEMENT &&
( element.data( 'cke-real-element-type' ) === 'anchor' || element.is( 'a' ) ) ) {
return element;
}
}

function removeAnchorsWithinRange( range ) {
var newRange = range.clone();
newRange.enlarge( CKEDITOR.ENLARGE_ELEMENT );
sculpt0r marked this conversation as resolved.
Show resolved Hide resolved

var walker = new CKEDITOR.dom.walker( newRange ),
element = newRange.collapsed ? newRange.startContainer : walker.next(),
bookmark = range.createBookmark();

while ( element ) {
if ( element.type === CKEDITOR.NODE_ELEMENT && element.getAttribute( 'data-cke-saved-name' ) ) {
element.remove( true );
// Reset the walker and start from beginning, to check if element has more nested anchors.
// Without it, next element is null, so there might be space to more nested elements.
walker.reset();
}
element = walker.next();
}
range.moveToBookmark( bookmark );
}

return {
title: editor.lang.link.anchor.title,
minWidth: 300,
Expand Down Expand Up @@ -76,6 +101,8 @@ CKEDITOR.dialog.add( 'anchor', function( editor ) {
if ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 )
attributes[ 'class' ] = 'cke_anchor';

// (#4728)
removeAnchorsWithinRange( range );
// Apply style.
var style = new CKEDITOR.style( { element: 'a', attributes: attributes } );
style.type = CKEDITOR.STYLE_INLINE;
Expand Down
130 changes: 129 additions & 1 deletion tests/plugins/link/anchor.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* bender-tags: editor */
/* bender-ckeditor-plugins: link,toolbar */
/* bender-ckeditor-plugins: link,toolbar,basicstyles */

( function() {
'use strict';
Expand Down Expand Up @@ -79,6 +79,134 @@
assert.areSame( anchor, dialog.getModel( editor ) );
} );
} );
},

// (#3863)
'test prevent duplicated anchors after editing a word with custom style': function() {
var editor = this.editor,
bot = this.editorBot,
template = '[<p><a id="test" name="test"><strong>text</strong></a></p>]',
expected = '<p><a id="duplicate-test" name="duplicate-test"><strong>text</strong></a></p>';

bot.setHtmlWithSelection( template );
bot.dialog( 'anchor', function( dialog ) {
dialog.setValueOf( 'info', 'txtName', 'duplicate-test' );
dialog.getButton( 'ok' ).click();

sculpt0r marked this conversation as resolved.
Show resolved Hide resolved
assert.beautified.html( expected, editor.getData(), 'Prevent duplicated anchors failed after editing a word with custom style' );
} );
},

// (#4728)
'test prevent duplicated anchors': function() {
var editor = this.editor,
bot = this.editorBot,
template = '[<p>Hello <a id="hello" name="hello" data-cke-saved-name="hello"><strong>world!</strong></a></p>]',
expected = '<p><a id="helloWorld" name="helloWorld">Hello <strong>world!</strong></a></p>';

bot.setHtmlWithSelection( template );
bot.dialog( 'anchor', function( dialog ) {
dialog.setValueOf( 'info', 'txtName', 'helloWorld' );
dialog.getButton( 'ok' ).click();

assert.beautified.html( expected, editor.getData(), 'Prevent duplicated anchors failed' );
} );
},

// (#4728)
'test prevent duplicated anchors after editing multiple words with styles': function() {
var editor = this.editor,
bot = this.editorBot,
template = '[<p>Simple <a id="multiTest" name="multiTest" data-cke-saved-name="multiTest"><strong>text</strong></a></p>]',
expected = '<p><a id="multiTestResult" name="multiTestResult">Simple <strong>text</strong></a></p>';

bot.setHtmlWithSelection( template );
bot.dialog( 'anchor', function( dialog ) {
dialog.setValueOf( 'info', 'txtName', 'multiTestResult' );
dialog.getButton( 'ok' ).click();

assert.beautified.html( expected, editor.getData(), 'Prevent duplicated anchors failed after editing multiple words with styles' );
} );
},

// (#4728)
'test prevent duplicated anchors in the selection: strong > em': function() {
var editor = this.editor,
bot = this.editorBot,
template = '<p>[<strong>Simple<em>Test</em></strong>]</p>',
expected = '<p><a id="nested" name="nested"><strong>Simple<em>Test</em></strong></a></p>';

bot.setHtmlWithSelection( template );
bot.dialog( 'anchor', function( dialog ) {
dialog.setValueOf( 'info', 'txtName', 'nested' );
dialog.getButton( 'ok' ).click();

assert.beautified.html( expected, editor.getData(), 'Prevent duplicated anchors failed in the selection: strong > em' );
} );
},

// (#4728)
'test prevent duplicated anchors in the selection: strong > em > span': function() {
var editor = this.editor,
bot = this.editorBot,
template = '<p>[<strong><em><span>test</span></em></strong>]</p>',
expected = '<p><a id="emphasize" name="emphasize"><strong><em><span>test</span></em></strong></a></p>';

bot.setHtmlWithSelection( template );
bot.dialog( 'anchor', function( dialog ) {
dialog.setValueOf( 'info', 'txtName', 'emphasize' );
dialog.getButton( 'ok' ).click();

assert.beautified.html( expected, editor.getData(), 'Prevent duplicated anchors failed in the selection: strong > em > span' );
} );
},

// (#4728)
'test prevent duplicated anchors in the selection of multiline with styled words': function() {
var editor = this.editor,
bot = this.editorBot,
template = '[<p><em><strong>Simple</strong></em></p><p>test</p>]',
expected = '<p><a id="multiLine" name="multiLine"><em><strong>Simple</strong></em></a></p><p><a id="multiLine" name="multiLine">test</a></p>';

bot.setHtmlWithSelection( template );
bot.dialog( 'anchor', function( dialog ) {
dialog.setValueOf( 'info', 'txtName', 'multiLine' );
dialog.getButton( 'ok' ).click();

assert.beautified.html( expected, editor.getData(), 'Prevent duplicated anchors failed in the selection of multiline with styled words' );
} );
},

// (#4728)
'test prevent duplicated anchors in the unordered list with styled word': function() {
var editor = this.editor,
bot = this.editorBot,
template = '[<ul><li><s>test</s></li></ul>]',
expected = '<ul><li><a id="unorderedList" name="unorderedList"><s>test</s></a></li></ul>';

bot.setHtmlWithSelection( template );
bot.dialog( 'anchor', function( dialog ) {
dialog.setValueOf( 'info', 'txtName', 'unorderedList' );
dialog.getButton( 'ok' ).click();

assert.beautified.html( expected, editor.getData(), 'Prevent duplicated anchors failed in the unordered list with styled word' );
} );
},

// (#4728)
'test prevent duplicated anchors in the ordered list with styled word': function() {
var editor = this.editor,
bot = this.editorBot,
template = '[<ol><li><s>test</s></li></ol>]',
expected = '<ol><li><a id="orderedList" name="orderedList"><s>test</s></a></li></ol>';

bot.setHtmlWithSelection( template );
bot.dialog( 'anchor', function( dialog ) {
dialog.setValueOf( 'info', 'txtName', 'orderedList' );
dialog.getButton( 'ok' ).click();

assert.beautified.html( expected, editor.getData(), 'Prevent duplicated anchors failed in the ordered list with styled word' );
} );
}
} );
}() );