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

The ClipboardPipeline and PasteFromOffice should allow for common HTML string normalisation before conversion to view #17733

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
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
21 changes: 19 additions & 2 deletions packages/ckeditor5-clipboard/src/clipboardobserver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import {
type ViewRange
} from '@ckeditor/ckeditor5-engine';

import plainTextToHtml from './utils/plaintexttohtml.js';

/**
* Clipboard events observer.
*
Expand Down Expand Up @@ -63,10 +65,20 @@ export default class ClipboardObserver extends DomEventObserver<
data.preventDefault();

const targetRanges = data.dropRange ? [ data.dropRange ] : null;
const dataTransfer = data.dataTransfer;
let content = '';

if ( dataTransfer.getData( 'text/html' ) ) {
content = dataTransfer.getData( 'text/html' );
} else if ( dataTransfer.getData( 'text/plain' ) ) {
content = plainTextToHtml( dataTransfer.getData( 'text/plain' ) );
}

const eventInfo = new EventInfo( viewDocument, type );

viewDocument.fire( eventInfo, {
dataTransfer: data.dataTransfer,
dataTransfer,
content,
method: evt.name,
targetRanges,
target: data.target,
Expand Down Expand Up @@ -175,7 +187,12 @@ export interface ClipboardInputEventData {
/**
* The content of clipboard input.
*/
content?: ViewDocumentFragment;
content: string | ViewDocumentFragment;

/**
* TODO
*/
extraContent?: unknown;
}

/**
Expand Down
31 changes: 13 additions & 18 deletions packages/ckeditor5-clipboard/src/clipboardpipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import ClipboardObserver, {
type ViewDocumentClipboardInputEvent
} from './clipboardobserver.js';

import plainTextToHtml from './utils/plaintexttohtml.js';
import normalizeClipboardHtml from './utils/normalizeclipboarddata.js';
import viewToPlainText from './utils/viewtoplaintext.js';
import ClipboardMarkersUtils from './clipboardmarkersutils.js';
Expand Down Expand Up @@ -217,29 +216,20 @@ export default class ClipboardPipeline extends Plugin {
}, { priority: 'highest' } );

this.listenTo<ViewDocumentClipboardInputEvent>( viewDocument, 'clipboardInput', ( evt, data ) => {
const dataTransfer = data.dataTransfer;
let content: ViewDocumentFragment;

// Some feature could already inject content in the higher priority event handler (i.e., codeBlock).
if ( data.content ) {
content = data.content;
} else {
let contentData = '';

if ( dataTransfer.getData( 'text/html' ) ) {
contentData = normalizeClipboardHtml( dataTransfer.getData( 'text/html' ) );
} else if ( dataTransfer.getData( 'text/plain' ) ) {
contentData = plainTextToHtml( dataTransfer.getData( 'text/plain' ) );
}

content = this.editor.data.htmlProcessor.toView( contentData );
if ( typeof data.content == 'string' ) {
data.content = normalizeClipboardHtml( data.content );
}
}, { priority: 'low' } );

this.listenTo<ViewDocumentClipboardInputEvent>( viewDocument, 'clipboardInput', ( evt, data ) => {
// Some feature could already inject content in the higher priority event handler (i.e., codeBlock, paste from office).
const content = typeof data.content == 'string' ? this.editor.data.htmlProcessor.toView( data.content ) : data.content;
const eventInfo = new EventInfo( this, 'inputTransformation' );

this.fire<ClipboardInputTransformationEvent>( eventInfo, {
content,
dataTransfer,
extraContent: data.extraContent,
dataTransfer: data.dataTransfer,
targetRanges: data.targetRanges,
method: data.method as 'paste' | 'drop'
} );
Expand Down Expand Up @@ -375,6 +365,11 @@ export interface ClipboardInputTransformationData {
*/
content: ViewDocumentFragment;

/**
* TODO
*/
extraContent?: unknown;

/**
* The data transfer instance.
*/
Expand Down
4 changes: 3 additions & 1 deletion packages/ckeditor5-clipboard/tests/clipboardpipeline.js
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,7 @@ describe( 'ClipboardPipeline feature', () => {

viewDocument.fire( 'clipboardInput', {
dataTransfer: dataTransferMock,
content: dataTransferMock.getData( 'text/html' ),
method: 'paste'
} );

Expand All @@ -331,7 +332,8 @@ describe( 'ClipboardPipeline feature', () => {
editor.disableReadOnlyMode( 'unit-test' );

viewDocument.fire( 'clipboardInput', {
dataTransfer: dataTransferMock
dataTransfer: dataTransferMock,
content: dataTransferMock.getData( 'text/html' )
} );

sinon.assert.calledOnce( spy );
Expand Down
2 changes: 2 additions & 0 deletions packages/ckeditor5-clipboard/tests/dragdrop.js
Original file line number Diff line number Diff line change
Expand Up @@ -2531,6 +2531,7 @@ describe( 'Drag and Drop', () => {
...prepareEventData( modelPositionOrRange ),
method: 'dragging',
dataTransfer: dataTransferMock,
content: dataTransferMock.getData( 'text/html' ),
stopPropagation: () => {},
preventDefault: () => {}
} );
Expand All @@ -2541,6 +2542,7 @@ describe( 'Drag and Drop', () => {
...prepareEventData( modelPosition ),
method: 'drop',
dataTransfer: dataTransferMock,
content: dataTransferMock.getData( 'text/html' ),
stopPropagation: () => {},
preventDefault: () => {}
} );
Expand Down
5 changes: 5 additions & 0 deletions packages/ckeditor5-clipboard/tests/pasteplaintext.js
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ describe( 'PastePlainText', () => {

viewDocument.fire( 'clipboardInput', {
dataTransfer: dataTransferMock,
content: dataTransferMock.getData( 'text/html' ),
stopPropagation() {},
preventDefault() {}
} );
Expand Down Expand Up @@ -222,6 +223,7 @@ describe( 'PastePlainText', () => {

viewDocument.fire( 'clipboardInput', {
dataTransfer: dataTransferMock,
content: dataTransferMock.getData( 'text/html' ),
stopPropagation() {},
preventDefault() {}
} );
Expand All @@ -237,6 +239,7 @@ describe( 'PastePlainText', () => {
'text/html': 'foo',
'text/plain': 'foo'
} ),
content: 'foo',
stopPropagation() {},
preventDefault() {}
} );
Expand All @@ -252,6 +255,7 @@ describe( 'PastePlainText', () => {
'text/html': 'foo',
'text/plain': 'foo'
} ),
content: 'foo',
stopPropagation() {},
preventDefault() {}
} );
Expand All @@ -275,6 +279,7 @@ describe( 'PastePlainText', () => {
'text/html': '<obj></obj>',
'text/plain': 'foo'
} ),
content: '<obj></obj>',
stopPropagation() {},
preventDefault() {}
} );
Expand Down
4 changes: 2 additions & 2 deletions packages/ckeditor5-code-block/src/codeblockediting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
type Element,
type SelectionChangeRangeEvent
} from 'ckeditor5/src/engine.js';
import { ClipboardPipeline, type ClipboardContentInsertionEvent } from 'ckeditor5/src/clipboard.js';
import { ClipboardPipeline, type ClipboardContentInsertionEvent, type ViewDocumentClipboardInputEvent } from 'ckeditor5/src/clipboard.js';

import CodeBlockCommand from './codeblockcommand.js';
import IndentCodeBlockCommand from './indentcodeblockcommand.js';
Expand Down Expand Up @@ -178,7 +178,7 @@ export default class CodeBlockEditing extends Plugin {
// Intercept the clipboard input (paste) when the selection is anchored in the code block and force the clipboard
// data to be pasted as a single plain text. Otherwise, the code lines will split the code block and
// "spill out" as separate paragraphs.
this.listenTo( editor.editing.view.document, 'clipboardInput', ( evt, data ) => {
this.listenTo<ViewDocumentClipboardInputEvent>( editor.editing.view.document, 'clipboardInput', ( evt, data ) => {
let insertionRange = model.createRange( model.document.selection.anchor! );

// Use target ranges in case this is a drop.
Expand Down
3 changes: 2 additions & 1 deletion packages/ckeditor5-engine/src/view/view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import type { StylesProcessor } from './stylesmap.js';
import type Element from './element.js';
import type { default as Node, ViewNodeChangeEvent } from './node.js';
import type Item from './item.js';
import type DocumentFragment from './documentfragment.js';

import KeyObserver from './observer/keyobserver.js';
import FakeSelectionObserver from './observer/fakeselectionobserver.js';
Expand Down Expand Up @@ -679,7 +680,7 @@ export default class View extends /* #__PURE__ */ ObservableMixin() {
*
* @param element Element which is a parent for the range.
*/
public createRangeIn( element: Element ): Range {
public createRangeIn( element: Element | DocumentFragment ): Range {
return Range._createIn( element );
}

Expand Down
4 changes: 2 additions & 2 deletions packages/ckeditor5-image/src/autoimage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
*/

import { Plugin, type Editor } from 'ckeditor5/src/core.js';
import { Clipboard, type ClipboardPipeline } from 'ckeditor5/src/clipboard.js';
import { Clipboard, type ClipboardInputTransformationEvent, type ClipboardPipeline } from 'ckeditor5/src/clipboard.js';
import { LivePosition, LiveRange } from 'ckeditor5/src/engine.js';
import { Undo } from 'ckeditor5/src/undo.js';
import { Delete } from 'ckeditor5/src/typing.js';
Expand Down Expand Up @@ -82,7 +82,7 @@ export default class AutoImage extends Plugin {
// We need to listen on `Clipboard#inputTransformation` because we need to save positions of selection.
// After pasting, the content between those positions will be checked for a URL that could be transformed
// into an image.
this.listenTo( clipboardPipeline, 'inputTransformation', () => {
this.listenTo<ClipboardInputTransformationEvent>( clipboardPipeline, 'inputTransformation', () => {
const firstRange = modelDocument.selection.getFirstRange()!;

const leftLivePosition = LivePosition.fromPosition( firstRange.start );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@ import {
} from 'ckeditor5/src/engine.js';

import { Notification } from 'ckeditor5/src/ui.js';
import { ClipboardPipeline, type ViewDocumentClipboardInputEvent } from 'ckeditor5/src/clipboard.js';
import {
ClipboardPipeline,
type ClipboardInputTransformationEvent,
type ViewDocumentClipboardInputEvent
} from 'ckeditor5/src/clipboard.js';
import { FileRepository, type UploadResponse, type FileLoader } from 'ckeditor5/src/upload.js';
import { env } from 'ckeditor5/src/utils.js';

Expand Down Expand Up @@ -199,7 +203,7 @@ export default class ImageUploadEditing extends Plugin {
// For every image file, a new file loader is created and a placeholder image is
// inserted into the content. Then, those images are uploaded once they appear in the model
// (see Document#change listener below).
this.listenTo( clipboardPipeline, 'inputTransformation', ( evt, data ) => {
this.listenTo<ClipboardInputTransformationEvent>( clipboardPipeline, 'inputTransformation', ( evt, data ) => {
const fetchableImages = Array.from( editor.editing.view.createRangeIn( data.content ) )
.map( value => value.item as ViewElement )
.filter( viewElement =>
Expand Down
4 changes: 2 additions & 2 deletions packages/ckeditor5-link/src/autolink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
*/

import { Plugin } from 'ckeditor5/src/core.js';
import type { ClipboardInputTransformationData } from 'ckeditor5/src/clipboard.js';
import type { ClipboardInputTransformationData, ClipboardInputTransformationEvent } from 'ckeditor5/src/clipboard.js';
import type { DocumentSelectionChangeEvent, Element, Model, Position, Range, Writer } from 'ckeditor5/src/engine.js';
import { Delete, TextWatcher, getLastTextLine, findAttributeRange, type TextWatcherMatchedDataEvent } from 'ckeditor5/src/typing.js';
import type { EnterCommand, ShiftEnterCommand } from 'ckeditor5/src/enter.js';
Expand Down Expand Up @@ -160,7 +160,7 @@ export default class AutoLink extends Plugin {
const clipboardPipeline = editor.plugins.get( 'ClipboardPipeline' );
const linkCommand = editor.commands.get( 'link' )!;

clipboardPipeline.on( 'inputTransformation', ( evt, data: ClipboardInputTransformationData ) => {
clipboardPipeline.on<ClipboardInputTransformationEvent>( 'inputTransformation', ( evt, data: ClipboardInputTransformationData ) => {
if ( !this.isEnabled || !linkCommand.isEnabled || selection.isCollapsed || data.method !== 'paste' ) {
// Abort if we are disabled or the selection is collapsed.
return;
Expand Down
4 changes: 2 additions & 2 deletions packages/ckeditor5-media-embed/src/automediaembed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

import { type Editor, Plugin } from 'ckeditor5/src/core.js';
import { LiveRange, LivePosition } from 'ckeditor5/src/engine.js';
import { Clipboard, type ClipboardPipeline } from 'ckeditor5/src/clipboard.js';
import { Clipboard, type ClipboardInputTransformationEvent, type ClipboardPipeline } from 'ckeditor5/src/clipboard.js';
import { Delete } from 'ckeditor5/src/typing.js';
import { Undo, type UndoCommand } from 'ckeditor5/src/undo.js';
import { global } from 'ckeditor5/src/utils.js';
Expand Down Expand Up @@ -79,7 +79,7 @@ export default class AutoMediaEmbed extends Plugin {
// After pasting, the content between those positions will be checked for a URL that could be transformed
// into media.
const clipboardPipeline: ClipboardPipeline = editor.plugins.get( 'ClipboardPipeline' );
this.listenTo( clipboardPipeline, 'inputTransformation', () => {
this.listenTo<ClipboardInputTransformationEvent>( clipboardPipeline, 'inputTransformation', () => {
const firstRange = modelDocument.selection.getFirstRange()!;

const leftLivePosition = LivePosition.fromPosition( firstRange.start );
Expand Down
2 changes: 1 addition & 1 deletion packages/ckeditor5-paste-from-office/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
*/

export { default as PasteFromOffice } from './pastefromoffice.js';
export type { Normalizer, NormalizerData } from './normalizer.js';
export type { Normalizer } from './normalizer.js';
export { default as MSWordNormalizer } from './normalizers/mswordnormalizer.js';
export { parseHtml } from './filters/parse.js';

Expand Down
8 changes: 1 addition & 7 deletions packages/ckeditor5-paste-from-office/src/normalizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
*/

import type { ClipboardInputTransformationData } from 'ckeditor5/src/clipboard.js';
import type { ParseHtmlResult } from './filters/parse.js';

/**
* Interface defining a content transformation pasted from an external editor.
Expand All @@ -27,10 +26,5 @@ export interface Normalizer {
/**
* Executes the normalization of a given data.
*/
execute( data: NormalizerData ): void;
}

export interface NormalizerData extends ClipboardInputTransformationData {
_isTransformedWithPasteFromOffice?: boolean;
_parsedData: ParseHtmlResult;
execute( data: ClipboardInputTransformationData ): void;
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import { UpcastWriter, type ViewDocument } from 'ckeditor5/src/engine.js';
import removeBoldWrapper from '../filters/removeboldwrapper.js';
import transformBlockBrsToParagraphs from '../filters/br.js';
import { unwrapParagraphInListItem } from '../filters/list.js';
import type { Normalizer, NormalizerData } from '../normalizer.js';
import type { Normalizer } from '../normalizer.js';
import type { ClipboardInputTransformationData } from 'ckeditor5/src/clipboard.js';

const googleDocsMatch = /id=("|')docs-internal-guid-[-0-9a-f]+("|')/i;

Expand Down Expand Up @@ -41,14 +42,11 @@ export default class GoogleDocsNormalizer implements Normalizer {
/**
* @inheritDoc
*/
public execute( data: NormalizerData ): void {
public execute( data: ClipboardInputTransformationData ): void {
const writer = new UpcastWriter( this.document );
const { body: documentFragment } = data._parsedData;

removeBoldWrapper( documentFragment, writer );
unwrapParagraphInListItem( documentFragment, writer );
transformBlockBrsToParagraphs( documentFragment, writer );

data.content = documentFragment;
removeBoldWrapper( data.content, writer );
unwrapParagraphInListItem( data.content, writer );
transformBlockBrsToParagraphs( data.content, writer );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import removeXmlns from '../filters/removexmlns.js';
import removeGoogleSheetsTag from '../filters/removegooglesheetstag.js';
import removeInvalidTableWidth from '../filters/removeinvalidtablewidth.js';
import removeStyleBlock from '../filters/removestyleblock.js';
import type { Normalizer, NormalizerData } from '../normalizer.js';
import type { Normalizer } from '../normalizer.js';
import type { ClipboardInputTransformationData } from 'ckeditor5/src/clipboard.js';

const googleSheetsMatch = /<google-sheets-html-origin/i;

Expand Down Expand Up @@ -42,15 +43,12 @@ export default class GoogleSheetsNormalizer implements Normalizer {
/**
* @inheritDoc
*/
public execute( data: NormalizerData ): void {
public execute( data: ClipboardInputTransformationData ): void {
const writer = new UpcastWriter( this.document );
const { body: documentFragment } = data._parsedData;

removeGoogleSheetsTag( documentFragment, writer );
removeXmlns( documentFragment, writer );
removeInvalidTableWidth( documentFragment, writer );
removeStyleBlock( documentFragment, writer );

data.content = documentFragment;
removeGoogleSheetsTag( data.content, writer );
removeXmlns( data.content, writer );
removeInvalidTableWidth( data.content, writer );
removeStyleBlock( data.content, writer );
}
}
Loading
Loading