ueditor rich text copy and paste word content containing pictures, and supports screenshots or copying and pasting pictures separately

Background

The company’s recent upgrade project requires rich text to support the copying and pasting of word content containing multiple pictures. There is no other way. For the sake of user experience, we can only find a solution. Through various data searches, we have sorted out an effective solution for personal testing.

Plan: 1. Listen to the paste event and obtain the information in the clipboard through e.clipboardData

2. Get the local path of the image and find the image data through the RTF content.

3. Convert the image to base64 (not uploaded to the server, will be added later if necessary)

Implementation steps (the codes are all modified in the ueditor.all.js file)

1. Define the variables and find the code for the ueditor initialization event (you can directly search for the “_initEvents” method)

var wordImg = [];
_initEvents: function () {
                var me = this,
                    doc = me.document,
                    win = me.window;
            //************************New content**************************** ****
                //Monitor word copy and paste function
                doc.addEventListener("paste", function (e) {
                    if (!(e.clipboardData & amp; & amp; e.clipboardData.items)) {
                        return;
                    }
                    
                    /***
                     * Paste the copied image, or paste the screenshot *
                     */
                    const items = (e.clipboardData || window.clipboardData).items;
                    let file = null;
                    for (let i = 0; i < items.length; i + + ) {
                        if (items[i].type.indexOf("image") !== -1) {
                            file = items[i].getAsFile();
                            break;
                        }

                    }
                    me.fileToBase64(file,me)
                    /**
                     * Paste word document
                     * */
                    const clipboardData = e.clipboardData;
                    //HTML text in the pasteboard
                    let copyStr = clipboardData.getData('text/html');
                    //RTF data in the pasteboard
                    let rtf = clipboardData.getData('text/rtf');

                    //Get the number of pictures in the pasteboard
                    let imgs = me.findAllImageElementsWithLocalSource(copyStr);

                    me.replaceImagesFileSourceWithInlineRepresentation(imgs, me.extractImageDataFromRtf(rtf))

                })
 //************************New content**************************** ****
                me._proxyDomEvent = utils.bind(me._proxyDomEvent, me);
                domUtils.on(doc, ['click', 'contextmenu', 'mousedown', 'keydown', 'keyup', 'keypress', 'mouseup', 'mouseover', 'mouseout', 'selectstart'], me. _proxyDomEvent);
                domUtils.on(win, ['focus', 'blur'], me._proxyDomEvent);
                domUtils.on(me.body, 'drop', function (e) {
                    //Prevent the default pop-up of a new page under ff to open the picture
                    if (browser.gecko & amp; & amp; e.stopPropagation) { e.stopPropagation(); }
                    me.fireEvent('contentchange')
                });
                domUtils.on(doc, ['mouseup', 'keydown'], function (evt) {
                    //Special keys do not trigger selectionchange
                    if (evt.type == 'keydown' & amp; & amp; (evt.ctrlKey || evt.metaKey || evt.shiftKey || evt.altKey)) {
                        return;
                    }
                    if (evt.button == 2) return;
                    me._selectionChange(250, evt);
                });
            },

2. Copy the method into

 //Get the number of pictures in the pasteboard
            findAllImageElementsWithLocalSource: function (htmlData) {
                let imgReg = /<img.*?(?:>|\/>)/gi; //Match the img tag in the image
                let srcReg = /src=['"]?([^'"]*)['"]?/i; // Match src in the image
                let arr = htmlData.match(imgReg); //Filter out all img
                if (!arr || (Array.isArray(arr) & amp; & amp; !arr.length)) {
                    return false;
                }

                let srcArr = [];
                for (let i = 0; i < arr.length; i + + ) {
                    let src = arr[i].match(srcReg);
                    // Get the image address
                    srcArr.push(src[1]);
                }
                return srcArr;
            },
            //Process image information--find image data from rtf content
            extractImageDataFromRtf: function (rtfData, ignoreHeadersFooters = true) {
                if (!rtfData) {
                    return [];
                }

                const regexPictureHeader = /{\pict[\s\S] + ?({\\*\blipuid\s?[\da-fA-F] + )[\s}]*/
                const regexPicture = new RegExp('(?:(' + regexPictureHeader.source + '))([\da-fA-F\s] + )\}', 'g');
                const images = rtfData.match(regexPicture);
                const result = [];

                if (images) {
                    for (const image of images) {
                        let imageType = false;

                        if (image.includes('\pngblip')) {
                            imageType = 'image/png';
                        } else if (image.includes('\jpegblip')) {
                            imageType = 'image/jpeg';
                        }

                        if (imageType) {
                            //Whether to skip headers and footers
                            if (ignoreHeadersFooters) {
                                const headerFooterRegex = /{\header[\s\S] + ?}\par|{\footer[\s\S] + ?}\par/g;
                                if (headerFooterRegex.test(image)) {
                                    continue;
                                }
                            }
                            result.push({
                                hex: image.replace(regexPictureHeader, '').replace(/[^\da-fA-F]/g, ''),
                                type: imageType
                            });
                        }
                    }
                }
                console.log(result)
                return result;
            },
            //Convert hexadecimal to base64
            _convertHexToBase64: function (hexString) {
                return btoa(hexString.match(/\w{2}/g).map(char => {
                    return String.fromCharCode(parseInt(char, 16));
                }).join(''));
            },

            //Storage image resources
            replaceImagesFileSourceWithInlineRepresentation: function (imageElements, imagesHexSources) {
                //If there are equal amounts of image elements and image HEX sources, they can be matched accordingly based on the existing order.
                if (imageElements.length === imagesHexSources.length) {
                    for (let i = 0; i < imageElements.length; i + + ) {
                        const newSrc = `data:${imagesHexSources[i].type};base64,${this._convertHexToBase64(imagesHexSources[i].hex)}`;
                        wordImg.push(newSrc);
                    }
                }
            },
             fileToBase64:function(file,_me) {
                return new Promise((resolve, reject) => {
                  //Create a new FileReader object
                  const reader = new FileReader();
                  //Read File object
                  reader.readAsDataURL(file);
                  //After loading is complete
                  reader.onload = function () {
                    // Convert the read data to a base64 encoded string
                    const base64String ='data:image/jpeg;base64,' + reader.result.split(",")[1];
                    // Parse into a Promise object and return a base64 encoded string
                    // resolve(base64String);
                    
                    _me.execCommand('inserthtml',`<img src=${base64String}>`);
                  };
                  //When loading fails
                  reader.onerror = function () {
                    reject(new Error("Failed to load file"));
                  };
                });
              },

3. Search for //todo base64 and temporarily remove it

case 'img':
//Todo base64 is temporarily removed. After uploading remote images later, remove this
if (val = node.getAttr('src')) {

}
node.setAttr('_src', node.getAttr('src'));
break;

4. Replace inputRule content

inputRule : function (root) {
utils.each(root.getNodesByTagName(‘img’), function (img, key) {
var attrs = img.attrs,
flag = parseInt(attrs.width) < 128 || parseInt(attrs.height) < 43,
opt = me.options,
src = opt.UEDITOR_HOME_URL + ‘themes/default/images/spacer.gif’;
if (attrs[‘src’] & amp; & amp; /^(?:(file:\/ + ))/.test(attrs[‘src’])) {
img.setAttr({
width:attrs.width,
height:attrs.height,
alt:attrs.alt,
src:wordImg[key],
‘style’:’background:url(‘ + ( flag ? opt.themePath + opt.theme + ‘/images/word.gif’ : opt.langPath + opt.lang + ‘/images/localimage.png’) + ‘ ) no-repeat center center;border:1px solid #ddd’
})
}
})
//Clear usage records
wordImg = [];

}

At this point, the entire UE editor can support word image and text pasting. It should be noted that this modification does not upload images to the backend server.