ExtJS HtmlEditor Add Image Paste Plugin

Overview

Users requested the ability to paste screenshots directly into ExtJS HtmlEditor without saving the image locally first. This enhancement reduces the number of steps from seven to just two, significantly improving usability.

Implementation Details

By default, users had to folow a multi-step process:

  1. Capture screenshot using QQ
  2. Save image to local storage
  3. Upload via editor interface
  4. Insert into editor

With the new plugin, this is simplified to:

  • Capture screenshot
  • Paste directly into editor using Ctrl+V or right-click menu

Interestingly, native Firefox supports this functionality, but Chrome requires a custom solution.

Client-Side Plugin Code

Approach 1: Basic Plugin


Ext.namespace('Ext.ux', 'Ext.ux.form.HtmlEditor');
Ext.ux.form.HtmlEditor.PasteImage = function(config) {
    config = config || {};
    Ext.apply(this, config);
    this.init = function(htmlEditor) {
        this.editor = htmlEditor;
        this.editor.on('afterrender', onAfterrender, this, {
            delay: 200
        });
    };

    function onAfterrender(htmlEditor) {
        var editor = htmlEditor;
        editor.iframe.contentWindow.document.addEventListener('paste', function(event) {
            var items = (event.clipboardData || event.originalEvent.clipboardData).items;
            for (var i = 0; i < items.length; i++) {
                var item = items[i];
                if (item.kind === 'file' && item.type.match(/^image\//)) {
                    var blob = item.getAsFile();
                    var reader = new FileReader();
                    reader.onload = function(e) {
                        editor.relayCmd('insertimage', e.target.result);
                    };
                    reader.readAsDataURL(blob);
                    break;
                }
            }
        });
    }
}

Approach 2: Full Plugin Extension


Ext.namespace('Ext.ux', 'Ext.ux.form.HtmlEditor');
Ext.ux.form.HtmlEditor.PasteImage = Ext.extend(Ext.util.Observable, {
    init: function(cmp) {
        this.cmp = cmp;
        this.cmp.on('afterrender', this.onAfterrender, this, {
            delay: 200
        });
    },
    onAfterrender: function() {
        var editor = this.cmp;
        editor.iframe.contentWindow.document.addEventListener('paste', function(event) {
            var items = (event.clipboardData || event.originalEvent.clipboardData).items;
            for (var i = 0; i < items.length; i++) {
                var item = items[i];
                if (item.kind === 'file' && item.type.match(/^image\//)) {
                    var blob = item.getAsFile();
                    var reader = new FileReader();
                    reader.onload = function(e) {
                        editor.relayCmd('insertimage', e.target.result);
                    };
                    reader.readAsDataURL(blob);
                    break;
                }
            }
        });
    }
});

Usage Example


Ext.onReady(function() {
    Ext.QuickTips.init();
    new Ext.FormPanel({
        renderTo: 'layout',
        defaultType: 'textfield',
        items: [{
            xtype: 'htmleditor',
            fieldLabel: 'Html Editor',
            width: 650,
            height: 350,
            plugins: new Ext.ux.form.HtmlEditor.PasteImage()
        }]
    });
});

Server-Side Handling (C#)

On the server side, base64 images need to be extracted, saved as files, and replaced with actual URLs.


public static string ReplaceHtmlImage(string html)
{
    var images = GetImgSrcList(html);
    if (images.Count == 0) return html;

    var path = "/UploadFiles/" + DateTime.Today.ToString("yyyyMM/dd");
    var dir = HttpContext.Current.Server.MapPath("~" + path);
    if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);

    foreach (string image in images)
    {
        if (image.StartsWith("data:image"))
        {
            string fileExtension = image.Substring(11, 3).Replace(";", "");
            string fileName = DateTime.Now.ToString("HHmmssfffff") + "." + fileExtension;
            string filePath = path + "/" + fileName;
            string physicalPath = HttpContext.Current.Server.MapPath("~" + filePath);

            byte[] imageBytes = Convert.FromBase64String(image.Split(new string[] { ";base64," }, StringSplitOptions.None)[1]);
            File.WriteAllBytes(physicalPath, imageBytes);

            if (new[] { "png", "gif", "jpeg" }.Contains(fileExtension))
            {
                html = html.Replace(image, filePath);
            }
        }
    }
    return html;
}

public static string HtmlImageTagRegex = @"<img></img>]*?\bsrc[\s\t\r\n]*=[\s\t\r\n]*[""']?[\s\t\r\n]*(?<src>[^\s\t\r\n""'<>]*)[^<>]*?/?[\s\t\r\n]*>";

public static List<string> GetList(string sInput, string sRegex, string sGroupName)
{
    List<string> list = new List<string>();
    Regex re = new Regex(sRegex, RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace | RegexOptions.Multiline);
    MatchCollection mcs = re.Matches(sInput);
    foreach (Match mc in mcs)
    {
        list.Add(sGroupName != "" ? mc.Groups[sGroupName].Value : mc.Value);
    }
    return list;
}

public static List<string> GetImgSrcList(string sInput)
{
    return GetList(sInput, HtmlImageTagRegex, "src");
}
</string></string></string></string></src>

Tags: extjs HTML5 clipboard-api file-upload base64-image

Posted on Fri, 22 May 2026 17:54:03 +0000 by joshbb