xhEditor简介
xhEditor是一个基于jQuery开发的简单迷你并且高效的可视化HTML编辑器,基于网络访问并且兼容IE 6.0+, Firefox 3.0+, Opera 9.6+, Chrome 1.0+, Safari 3.22+。
xhEditor曾经是我比较喜欢的编辑器,也是率先支持拖拽上传的编辑器之一。xhEditor在当年是优秀的编辑器,功能足够强大,使用体验也相当好,拖拽上传是我最喜欢的功能,只可惜已经停止开发了。xhEditor最后的稳定版本是1.1.14,至今已超过2年未更新(2013年发布了开发版本1.2.1),作者已经停止开发和维护了,社区论坛完全不能打开。
由于xhEditor基于jQuery开发,而对于新版本的jQuery,它并不能很好的支持,只有1.4版本的jQuery是支持得最好的。
虽然已经不再更新了,但在一些需要富文本编辑器的场合,她还是可以完全胜任的。
本文以1.1.14版本为例,讲述如何在Flask项目中使用xhEditor编辑器,并实现图片上传、文件上传的后端功能。
xhEditor主要特点:
- 精简迷你:初始加载4个文件,包括:1个js(50k)+2个css(10k)+1个图片(5k),总共65k。若js和css文件进行gzip压缩传输,可以进一步缩减为24k左右。
- 使用简单:简单的调用方式,加一个class属性就能将您的textarea立马变成一个功能丰富的可视化编辑器。
- 无障碍访问:提供WAI-ARIA全面支持,全键盘精细操作,全程语音向导,提供完美无障碍访问体验,让残疾人也能够谱写精彩人生。
- 内置Ajax上传:内置强大的Ajax上传,包括HTML4和HTML5上传支持(多文件上传、真实上传进度及文件拖放上传),剪切板上传及远程抓取上传,追求完美的用户上传体验。
- Word自动清理:实现Word代码自动检测并清理,提供高效完美的Word代码过滤方案,生成代码最优化精简,但是却不丢失任何细节效果。
- UBB可视化编辑:提供完美的UBB可视化编辑解决方案,在您获得安全高效代码存储的同时,又能享受可视化编辑的便捷。
在Flask项目中使用xhEditor
首先我们需要到xhEditor官网下载1.1.14版本的xhEditor编辑器,下载之后解压到
Flask项目的static/xheditor目录。
xhEditor提供2种初始化方式:Class初始化和JavaScript初始化。Class初始化只需要给textarea设置值为xheditor的class属性,它就会自动变成xhEditor编辑器,一个页面可以同时同在多个编辑器,而且这个类属性可以添加参数。(PS:CKEditor也有这个功能)
对于这两种初始化方式,官网有提供设置很方便的设置向导,使得配置相对比较简单。
示例代码:
现在,我们就拥有一个xhEditor编辑器了。
开启上传功能
xhEditor的上传功能需要设置几个参数(以图片上传为例):
- upImgUrl : 图片文件上传接收URL,例:/upload/,可使用内置变量{editorRoot}
- upImgExt : 图片上传前限制本地文件扩展名,默认:jpg,jpeg,gif,png
这里假设上传文件接收URL为/upload/,我们的编辑器初始化代码就变成:
其他类型的文件上传设置类推。
Flask处理上传请求
xhEditor支持2种上传方式:标准HTML4上传和HTML5上传。
- HTML4上传使用标准的表单上传域,上传文件域的name为:filedata
- HTML5上传的整个POST数据流就是上传的文件完整数据,而本地文件名等信息储
存于HTTP_CONTENT_DISPOSITION这个服务器变量中
返回内容必需是标准的json字符串,结构可以是如下:
{\"err\":\"\",\"msg\":\"200906030521128703.gif\"} 或者 {\"err\":\"\",\"msg\":{\"url\":\"200906030521128703.jpg\",\"localfile\":\"test.jpg\",\"id\":\"1\"}}
注:若选择结构2,则url变量是必有。
文件上传处理示例代码:
def gen_rnd_filename(): filename_prefix = datetime.datetime.now().strftime(\'%Y%m%d%H%M%S\') return \'%s%s\' % (filename_prefix, str(random.randrange(1000, 10000))) @app.route(\'/upload/\', methods=[\'GET\', \'POST\']) def upload(): \'\'\'文件上传函数 本函数未做上传类型判断及上传大小判断。 \'\'\' result = {\"err\": \"\", \"msg\": {\"url\": \"\", \"localfile\": \"\"}} if request.method == \'POST\' and \'filedata\' in request.files: # 传统上传模式,IE浏览器使用这种模式 fileobj = request.files[\'filedata\'] fname, fext = os.path.splitext(fileobj.filename) rnd_name = \'%s%s\' % (gen_rnd_filename(), fext) fileobj.save(os.path.join(app.static_folder, \'upload\', rnd_name)) result[\"msg\"][\"localfile\"] = fileobj.filename result[\"msg\"][\"url\"] = \'!%s\' % \\ url_for(\'static\', filename=\'%s/%s\' % (\'upload\', rnd_name)) elif \'CONTENT_DISPOSITION\' in request.headers: # HTML5上传模式,FIREFOX等默认使用此模式 pattern = re.compile(r\"\"\"\\s.*?\\s?filename\\s*=\\s*[\'|\"]?([^\\s\'\"]+).*?\"\"\", re.I) _d = request.headers.get(\'CONTENT_DISPOSITION\').encode(\'utf-8\') if urllib.quote(_d).count(\'%25\') > 0: _d = urllib.unquote(_d) filenames = pattern.findall(_d) if len(filenames) == 1: result[\"msg\"][\"localfile\"] = urllib.unquote(filenames[0]) fname, fext = os.path.splitext(filenames[0]) img = request.data rnd_name = \'%s%s\' % (gen_rnd_filename(), fext) with open(os.path.join(app.static_folder, \'upload\', rnd_name), \'wb\') as fp: fp.write(img) result[\"msg\"][\"url\"] = \'!%s\' % \\ url_for(\'static\', filename=\'%s/%s\' % (\'upload\', rnd_name)) return json.dumps(result)
远程抓图
一般情况下,当复制站外的图片时,我们希望可以把图片保存到本地,远程抓图就可以完成这个事情。
启用远程抓图功能,需要设置2个参数:
- localUrlTest : 非本站域名测试正则表达式
- remoteImgSaveUrl : 远程图片抓取接收程序URL
设置这2个参数之后,我们的编辑器初始化代码变成:
复制代码 代码如下:
这里表示抓取除localhost之外其它域名的图片。
远程抓图处理示例代码:
def gen_rnd_filename(): filename_prefix = datetime.datetime.now().strftime(\'%Y%m%d%H%M%S\') return \'%s%s\' % (filename_prefix, str(random.randrange(1000, 10000))) @app.route(\'/uploadremote/\', methods=[\'POST\']) def uploadremote(): \"\"\" xheditor保存远程图片简单实现 URL用\"|\"分隔,返回的字符串也是用\"|\"分隔 返回格式是字符串,不是JSON格式 \"\"\" localdomain_re = re.compile(r\'https?:\\/\\/[^\\/]*?(localhost:?\\d*)\\/\', re.I) imageTypes = {\'gif\': \'.gif\', \'jpeg\': \'.jpg\', \'jpg\': \'.jpg\', \'png\': \'.png\'} urlout = [] result = \'\' srcUrl = request.form.get(\'urls\') if srcUrl: urls = srcUrl.split(\'|\') for url in urls: if not localdomain_re.search(url.strip()): downfile = urllib.urlopen(url) fext = imageTypes[downfile.headers.getsubtype().lower()] rnd_name = \'%s%s\' % (gen_rnd_filename(), fext) with open(os.path.join(app.static_folder, \'upload\', rnd_name), \'wb\') as fp: fp.write(downfile.read()) urlreturn = url_for(\'static\', filename=\'%s/%s\' % (\'upload\', rnd_name)) urlout.append(urlreturn) else: urlout.append(url) result = \'|\'.join(urlout) return result