I am currently facing a challenge with a custom paste handling feature I’ve developed for CKEditor, which is designed to work in conjunction with the PasteFromOffice and PasteFromOfficeEnhanced plugins.
The primary functionality of my custom plugin, PasteHandler, is to process HTML content during the paste operation. This involves parsing for img src tags within the pasted content, converting these images to base64 format, and sanitizing the entire content via an API call. All these operations are conducted within the handleHTMLData method of my plugin.
import { ClipboardPipeline } from '@ckeditor/ckeditor5-clipboard';
import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
import { ViewDocumentFragment, Writer } from '@ckeditor/ckeditor5-engine';
import EventInfo from '@ckeditor/ckeditor5-utils/src/eventinfo';
export default class PasteHandler extends Plugin {
static get pluginName() {
return 'PasteHandler' as const;
}
init() {
const clipboardPipeline: ClipboardPipeline = this.editor.plugins.get('ClipboardPipeline');
this.listenTo(
clipboardPipeline,
'inputTransformation',
(event: EventInfo, data: { content: ViewDocumentFragment }) => {
// Check if the pasted content is HTML
const isHtml = !!(data.content.childCount > 0 && data.content.getChild(0).is('element'));
if (isHtml) {
this.handleHTMLData(event, data);
}
},
{ priority: 'high' }
);
}
private handleHTMLData(event: EventInfo, data: { content: ViewDocumentFragment }): void {
event.stop();
// Convert the model document fragment to HTML
const content = this.editor.data.processor.toData(data.content);
// When image src is copied, we need to fetch the image, convert it to base64 and then sanitize it.
const handleAsync = async () => {
// Wait for 1 second to simulate image conversion and API call for content sanitization
await new Promise((resolve) => setTimeout(resolve, 1000));
this.editor.model.change((writer: Writer) => {
// Convert the string to a model fragment
const viewFragment = this.editor.data.processor.toView(content);
const modelFragment = this.editor.data.toModel(viewFragment);
// Insert the response at the current selection
const insertPosition = this.editor.model.document.selection.getFirstPosition();
if (insertPosition) {
writer.insert(modelFragment, insertPosition);
// Calculate the end position
const endPosition = insertPosition.getShiftedBy(modelFragment.childCount + 1);
// Set the selection to the end of the pasted content
writer.setSelection(endPosition);
}
});
};
handleAsync().catch((error) => {
console.error('Error in inputTransformation:', error);
});
}
}
However, this approach leads to issue where data pasted from Excel sheets cannot be edited post-paste.
I am looking into an approach where, instead of stopping the event, I simply put it on hold. Once my data manipulation is completed, I would then update the event with the new data and allow the event to continue.
Please note: I use event.stop(); in the handleHTMLData method to halt the event propagation and avoid data duplication. If I do not use this, there will be 2 copies of the copied excel content.