The original [” # CKEditor series (3) the paste operation is how to complete the “] (www.daozhao.com/10215.html)…

In CKEditor, we learned how to implement the event system in CKEditor.

  • The user can specify the priority when registering the callback function. The smaller the value, the higher the priority. The default value is 10
  • According to the user’s input, the system will assemble the system specification callback function for subsequent execution
  • You can cancel the event and block the event while executing the callback function, preventing other callback functions that listen for the event from executing.

When a plug-in wants to respond to paste events, it generally has two options.

Listen directly for the ‘paste’ event

By default, the clipboard plug-in listens for the most Paste events. We can see code like this appearing several times

// plugins/clipboard/plugin.js
editor.on( 'paste'.function( evt ) {})Copy the code

We can see several callbacks with priority 1

Handle the scene of pasting pictures

Assign base64 information for PNG, JPG, and GIF images to evt.data.datavalue.

editor.on( 'paste'.function( evt ) {
    var dataObj = evt.data,
        data = dataObj.dataValue,
        dataTransfer = dataObj.dataTransfer;

    // If data empty check for image content inside data transfer. https://dev.ckeditor.com/ticket/16705
    // Allow both dragging and dropping and pasting images as base64 (#4681).
    if ( !data && isFileData( evt, dataTransfer ) ) {
        var file = dataTransfer.getFile( 0 );
        if( CKEDITOR.tools.indexOf( supportedImageTypes, file.type ) ! = -1 ) {
            var fileReader = new FileReader();

            // Convert image file to img tag with base64 image.
            fileReader.addEventListener( 'load'.function() {
                evt.data.dataValue = '<img src="' + fileReader.result + '" / >';
                editor.fire( 'paste', evt.data );
            }, false );

            // Proceed with normal flow if reading file was aborted.
            fileReader.addEventListener( 'abort'.function() {
                / / (# 4681)
                setCustomIEEventAttribute( evt );
                editor.fire( 'paste', evt.data );
            }, false );

            // Proceed with normal flow if reading file failed.
            fileReader.addEventListener( 'error'.function() {
                / / (# 4681)
                setCustomIEEventAttribute( evt );
                editor.fire( 'paste', evt.data );
            }, false); fileReader.readAsDataURL( file ); latestId = dataObj.dataTransfer.id; evt.stop(); }}},null.null.1 );
Copy the code

Because base64 information needs to be processed by fileReader: Paste event editor.fire(‘paste’, evt.data) in the image load callback. , the corresponding abort and error are also raised to prevent other callbacks from being executed if the image fails.

Will the callback be executed again in the next paste callback execution? No, because evt.data.dataValue is empty when the callback is first executed and evt.datavalue has already been assigned by the previous callback, fileReader processing will not be repeated.

Data preparation
editor.on( 'paste'.function( evt ) {
    // Init `dataTransfer` if `paste` event was fired without it, so it will be always available.
    if ( !evt.data.dataTransfer ) {
        evt.data.dataTransfer = new CKEDITOR.plugins.clipboard.dataTransfer();
    }

    // If dataValue is already set (manually or by paste bin), so do not override it.
    if ( evt.data.dataValue ) {
        return;
    }

    var dataTransfer = evt.data.dataTransfer,
        // IE support only text data and throws exception if we try to get html data.
        // This html data object may also be empty if we drag content of the textarea.
        value = dataTransfer.getData( 'text/html' );

    if ( value ) {
        evt.data.dataValue = value;
        evt.data.type = 'html';
    } else {
        // Try to get text data otherwise.
        value = dataTransfer.getData( 'text/plain' );

        if ( value ) {
            evt.data.dataValue = editor.editable().transformPlainTextToHtml( value );
            evt.data.type = 'text'; }}},null.null.1 );
Copy the code

DataTransfer, dataValue (if set by other plug-ins), and Type are added to evt.data in preparation, so this callback should be executed first, with priority set to 1.

Look at the second callback function

Resolve compatibility
editor.on( 'paste'.function( evt ) {
    var data = evt.data.dataValue,
        blockElements = CKEDITOR.dtd.$block;

    // Filter webkit garbage.
    if ( data.indexOf( 'Apple-' ) > -1 ) {
        // Replace special webkit's with simple space, because webkit
        // produces them even for normal spaces.
        data = data.replace( /<span class="Apple-converted-space"> <\/span>/gi.' ' );

        // Strip <span> around white-spaces when not in forced 'html' content type.
        // This spans are created only when pasting plain text into Webkit,
        // but for safety reasons remove them always.
        if( evt.data.type ! ='html' ) {
            data = data.replace( /<span class="Apple-tab-span"[^>]*>([^<]*)<\/span>/gi.function( all, spaces ) {
                // Replace tabs with 4 spaces like Fx does.
                return spaces.replace( /\t/g.' '); }); }// This br is produced only when copying & pasting HTML content.
        if ( data.indexOf( '<br class="Apple-interchange-newline">' ) > -1 ) {
            evt.data.startsWithEOL = 1;
            evt.data.preSniffing = 'html'; // Mark as not text.
            data = data.replace( /<br class="Apple-interchange-newline">/.' ' );
        }

        // Remove all other classes.
        data = data.replace( /(<[^>]+) class="Apple-[^"]*"/gi.'$1' );
    }

    // Strip editable that was copied from inside. (https://dev.ckeditor.com/ticket/9534)
    if ( data.match( /^<[^<]+cke_(editable|contents)/i)) {var tmp,
            editable_wrapper,
            wrapper = new CKEDITOR.dom.element( 'div' );

        wrapper.setHtml( data );
        // Verify for sure and check for nested editor UI parts. (https://dev.ckeditor.com/ticket/9675)
        while ( wrapper.getChildCount() == 1 &&
                ( tmp = wrapper.getFirst() ) &&
                tmp.type == CKEDITOR.NODE_ELEMENT &&    // Make sure first-child is element.
                ( tmp.hasClass( 'cke_editable' ) || tmp.hasClass( 'cke_contents' ) ) ) {
            wrapper = editable_wrapper = tmp;
        }

        // If editable wrapper was found strip it and bogus <br> (added on FF).
        if ( editable_wrapper )
            data = editable_wrapper.getHtml().replace( /<br>$/i.' ' );
    }

    if ( CKEDITOR.env.ie ) {
        // 

->

(br.cke-pasted-remove will be removed later)

data = data.replace( / ^ (? : |\r\n)? <(\w+)/g.function( match, elementName ) { if ( elementName.toLowerCase() in blockElements ) { evt.data.preSniffing = 'html'; // Mark as not a text. return '<' + elementName; } returnmatch; }); }else if ( CKEDITOR.env.webkit ) { // </p><div><br></div> -> </p><br> // We don't mark br, because this situation can happen for htmlified text too. data = data.replace( /<\/(\w+)><div><br><\/div>$/.function( match, elementName ) { if ( elementName in blockElements ) { evt.data.endsWithEOL = 1; return '< /' + elementName + '>'; } returnmatch; }); }else if ( CKEDITOR.env.gecko ) { // Firefox adds bogus <br> when user pasted text followed by space(s). data = data.replace( /(\s)<br>$/.'$1' ); } evt.data.dataValue = data; }, null.null.3 ); Copy the code

It’s easy to see from the code above that it’s all about compatibility for different browsers, but we don’t have to worry about the details

Filter data for different paste sources
editor.on( 'paste'.function( evt ) {
    var dataObj = evt.data,
        type = editor._.nextPasteType || dataObj.type,
        data = dataObj.dataValue,
        trueType,
        // Default is 'html'.
        defaultType = editor.config.clipboard_defaultContentType || 'html',
        transferType = dataObj.dataTransfer.getTransferType( editor ),
        isExternalPaste = transferType == CKEDITOR.DATA_TRANSFER_EXTERNAL,
        isActiveForcePAPT = editor.config.forcePasteAsPlainText === true;

    // If forced type is 'html' we don't need to know true data type.
    if ( type == 'html' || dataObj.preSniffing == 'html' ) {
        trueType = 'html';
    } else {
        trueType = recogniseContentType( data );
    }

    delete editor._.nextPasteType;

    // Unify text markup.
    if ( trueType == 'htmlifiedtext' ) {
        data = htmlifiedTextHtmlification( editor.config, data );
    }

    // Strip presentational markup & unify text markup.
    // Forced plain text (dialog or forcePAPT).
    // Note: we do not check dontFilter option in this case, because forcePAPT was implemented
    // before pasteFilter and pasteFilter is automatically used on Webkit&Blink since 4.5, so
    // forcePAPT should have priority as it had before 4.5
    if ( type == 'text' && trueType == 'html' ) {
        data = filterContent( editor, data, filtersFactory.get( 'plain-text')); }// External paste and pasteFilter exists and filtering isn't disabled.
    // Or force filtering even for internal and cross-editor paste, when forcePAPT is active (#620).
    else if( isExternalPaste && editor.pasteFilter && ! dataObj.dontFilter || isActiveForcePAPT ) { data = filterContent( editor, data, editor.pasteFilter ); }if ( dataObj.startsWithEOL ) {
        data = '<br data-cke-eol="1">' + data;
    }
    if ( dataObj.endsWithEOL ) {
        data += '<br data-cke-eol="1">';
    }

    if ( type == 'auto' ) {
        type = ( trueType == 'html' || defaultType == 'html')?'html' : 'text';
    }

    dataObj.type = type;
    dataObj.dataValue = data;
    delete dataObj.preSniffing;
    delete dataObj.startsWithEOL;
    delete dataObj.endsWithEOL;
}, null.null.6 );
Copy the code

This is mainly based on different types, truetypes to carry out some filtering operations on the data

Insert and paste data

The pasted data must go into the editor, which depends on it.

editor.on( 'paste'.function( evt ) {
    var data = evt.data;
    if ( data.dataValue ) {
        editor.insertHtml( data.dataValue, data.type, data.range );

        // Defer 'afterPaste' so all other listeners for 'paste' will be fired first.
        // Fire afterPaste only if paste inserted some HTML.
        setTimeout( function() {
            editor.fire( 'afterPaste' );
        }, 0); }},null.null.1000 );
Copy the code

Once the paste event system callback and the user-added callback are complete, this callback will be executed last (if evt.stop() or evt.cancel() was not executed). Insert the value of evt.data.datavalue into the editor.

We can see the/plugins/clipboard/plugin. The js file, there is a paste increases on the toolbar button, plus pasteCommand operation

{

    exec: function( editor, data ) {
    data = typeofdata ! = ='undefined'&& data ! = =null ? data : {};

    var cmd = this,
        notification = typeofdata.notification ! = ='undefined' ? data.notification : true,
        forcedType = data.type,
        keystroke = CKEDITOR.tools.keystrokeToString( editor.lang.common.keyboard,
            editor.getCommandKeystroke( this ) ),
        msg = typeof notification === 'string' ? notification : editor.lang.clipboard.pasteNotification
            .replace( / / % 1.'<kbd aria-label="' + keystroke.aria + '" >' + keystroke.display + '</kbd>' ),
        pastedContent = typeof data === 'string' ? data : data.dataValue;

    function callback( data, withBeforePaste ) {
        withBeforePaste = typeofwithBeforePaste ! = ='undefined' ? withBeforePaste : true;

        if ( data ) {
            data.method = 'paste';

            if ( !data.dataTransfer ) {
                data.dataTransfer = clipboard.initPasteDataTransfer();
            }

            firePasteEvents( editor, data, withBeforePaste );
        } else if( notification && ! editor._.forcePasteDialog ) { editor.showNotification( msg,'info', editor.config.clipboard_notificationDuration );
        }

        // Reset dialog mode (#595).
        editor._.forcePasteDialog = false;

        editor.fire( 'afterCommandExec', {
            name: 'paste'.command: cmd,
            returnValue:!!!!! data } ); }// Force type for the next paste. Do not force if `config.forcePasteAsPlainText` set to true or 'allow-word' (#1013).
    if( forcedType && editor.config.forcePasteAsPlainText ! = =true&& editor.config.forcePasteAsPlainText ! = ='allow-word' ) {
        editor._.nextPasteType = forcedType;
    } else {
        delete editor._.nextPasteType;
    }

    if ( typeof pastedContent === 'string' ) {
        callback( {
            dataValue: pastedContent
        } );
    } else{ editor.getClipboardData( callback ); }}Copy the code

The above callback executes firePasteEvents and then fires the Paste event. If pastedContent is not a string, editor.getClipboardData is executed first, which has the paste callback with the best priority seen so far

editor.on( 'paste', onPaste, null.null.0 );

function onPaste( evt ) {
    evt.removeListener();
    evt.cancel();
    callback( evt.data );
}
Copy the code

The onPaste method removes the current callback, cancels subsequent paste callbacks, and executes callback, which triggers a new paste callback.

Register paste callbacks through the pasteTools plug-in

{ register: function(definition) { if (typeof definition.priority ! == 'number') { definition.priority = 10; } this.handlers.push(definition); }, addPasteListener: function( editor ) { editor.on( 'paste', function( evt ) { var handlers = getMatchingHandlers( this.handlers, evt ), filters, isLoaded; if ( handlers.length === 0 ) { return; } filters = getFilters( handlers ); isLoaded = loadFilters( filters, function() { return editor.fire( 'paste', evt.data ); }); if ( ! isLoaded ) { return evt.cancel(); } handlePaste( handlers, evt ); }, this, null, 3 ); }}... function getMatchingHandlers( handlers, evt ) { return CKEDITOR.tools.array.filter( handlers, function( handler ) { return handler.canHandle( evt ); } ).sort( function( handler1, handler2 ) { if ( handler1.priority === handler2.priority ) { return 0; } return handler1.priority - handler2.priority; }); } function handlePaste( handlers, evt ) { var handler = handlers.shift(); if ( ! handler ) { return; } handler.handle( evt, function() { handlePaste( handlers, evt ); }); }Copy the code

This puts the callbacks registered with it in its handlers, not in the handlers that listen directly to paste events, but only the component itself listens for Paste events, priority 3. This means that the set of callbacks registered with Pastetools. register are executed in order of priority 3. Of course, this set of callbacks are also executed in order of priority and are filtered according to the value returned by their canHandle method. The callback logic is performed through its Handle.

A search of the source code reveals that most of CKEditor’s official paste intervention plug-ins are registered via pastetools. register.

conclusion

By studying the pasteTools plugin, we can get a little more insight into the way we want to separate system level events from user level events. We assume that editor.on(‘paste’) is system level and only system level plug-ins are allowed to do this, but user level plug-ins are not. User-level plug-ins can only be registered through the register exposed by system-level pasteTools, and we can use the canHandle method of user-level plug-ins to make the plug-in process only the parts it wants to process. A similar separation method can also better reduce the impact of user-level plug-ins on the overall system