Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changelog/20260424115528_ck_18846.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
type: Fix
scope:
- ckeditor5-paste-from-office
closes:
- https://github.com/ckeditor/ckeditor5/issues/18846
---

Pasting content from Word no longer inserts unwanted visible bookmarks into the editor.
28 changes: 25 additions & 3 deletions packages/ckeditor5-paste-from-office/src/filters/bookmark.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@
* @module paste-from-office/filters/bookmark
*/

import {
type ViewUpcastWriter,
type ViewDocumentFragment
import type {
ViewUpcastWriter,
ViewDocumentFragment,
ViewElement
} from '@ckeditor/ckeditor5-engine';

/**
Expand Down Expand Up @@ -40,5 +41,26 @@ export function transformBookmarks(
const children = element.getChildren();

writer.insertChild( index, children, element.parent! );

if ( isHiddenBookmarkAnchor( element ) ) {
writer.remove( element );
}
}
}

/**
* Checks whether the given element is a hidden or auto-generated bookmark anchor.
*
* Editors like MS Word and Google Docs use the `name` attribute (rather than `id`)
* for bookmarks. Furthermore, they reserve `_`-prefixed bookmark names for
* auto-generated anchors (e.g., Table of Contents or internal hyperlinks) and
* do not allow users to manually create custom bookmarks starting with an underscore.
*
* @param element The element to check.
* @returns True if the element is a hidden bookmark anchor, false otherwise.
*/
function isHiddenBookmarkAnchor( element: ViewElement ) {
Comment thread
Mati365 marked this conversation as resolved.
const name = element.getAttribute( 'name' );
Comment thread
Mati365 marked this conversation as resolved.

return !!name && name.startsWith( '_' );
Comment thread
Mati365 marked this conversation as resolved.
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<p style="-webkit-text-size-adjust:auto;-webkit-text-stroke-width:0px;caret-color:rgb(0, 0, 0);color:rgb(0, 0, 0);font-family:Calibri, sans-serif;font-size:medium;font-style:normal;font-variant-caps:normal;font-weight:normal;letter-spacing:normal;margin:0cm;orphans:auto;text-align:start;text-decoration:none;text-indent:0px;text-transform:none;white-space:normal;widows:auto;word-spacing:0px"><span lang="FR" style="font-family:&quot;Times New Roman&quot;, serif;font-size:11pt">An example list with an error:</span></p>
<ul style="list-style-type:square">
<li><p><a name="_heading=h.tyjcwt"></a><span lang="FR" style="font-family:Wingdings"></span><span lang="FR">List item 1.</span></p></li>
<li><p><span lang="FR" style="font-family:Wingdings"></span><span lang="FR">List item 1.</span></p></li>
<li><p><span lang="FR" style="font-family:Wingdings"></span><span lang="FR">List item 2.</span></p></li>
<li><p><span lang="FR" style="font-family:Wingdings"></span><span lang="FR">List item 3.</span></p></li>
</ul>
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<p><span lang="FR" style="font-family:&quot;Times New Roman&quot;,serif;font-size:11.0pt">An example list with an error:</span></p>
<ul style="list-style-type:square">
<li><p><a name="_heading=h.tyjcwt"></a><span lang="FR" style="font-family:Wingdings"></span><span lang="FR">List item 1.</span></p></li>
<li><p><span lang="FR" style="font-family:Wingdings"></span><span lang="FR">List item 1.</span></p></li>
<li><p><span lang="FR" style="font-family:Wingdings"></span><span lang="FR">List item 2.</span></p></li>
<li><p><span lang="FR" style="font-family:Wingdings"></span><span lang="FR">List item 3.</span></p></li>
</ul>
40 changes: 40 additions & 0 deletions packages/ckeditor5-paste-from-office/tests/filters/bookmark.js
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,46 @@ describe( 'PasteFromOffice - filters - bookmark', () => {
);
} );

it( 'should extract text and remove the <a> element completely if its name starts with an underscore (hidden bookmark)', () => {
performTest(
'<a name="_GoBack">' +
'<span>text</span>' +
'</a>',

'<span>text</span>'
);
} );

it( 'should completely remove an empty <a> element if its name starts with an underscore', () => {
performTest(
'<p>paragraph</p>' +
'<a name="_Toc12345"></a>',

'<p>paragraph</p>'
);
} );

it( 'should remove the <a> element but keep content when both id and name are present and name starts with an underscore', () => {
performTest(
'<a id="some-id" name="_hiddenBookmark">' +
'<span>text</span>' +
'</a>',

'<span>text</span>'
);
} );

it( 'should NOT remove the <a> element if only its id starts with an underscore (since Word/GDocs use name)', () => {
performTest(
'<a id="_GoBack">' +
'<span>text</span>' +
'</a>',

'<a id="_GoBack"></a>' +
'<span>text</span>'
);
} );

function performTest( inputData, expectedData ) {
const documentFragment = htmlDataProcessor.toView( inputData );

Expand Down