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:
- Capture screenshot using QQ
- Save image to local storage
- Upload via editor interface
- 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>