多图上传
图片在线管理页面不显示
其中的在线管理页面图片无法显示,控制台报错500,后台报错内容如下:
Python NameError: name 'unicode' is not defined `
出错的位置位于DjangoUeditor/views.py
,之所以报错是因为Python2 的unicode 函数在 Python3 中被命名为 str。在 Python3 中使用我们需要使用str 来代替 Python2中的 unicode如此页面可以正常显示图片了
在线管理给图片增加删除按钮
修改/DjangoUeditor/static/ueditor/dialogs/image/image.js
... item.appendChild(icon); /*增加删除功能开始*/ span= document.createElement('span'); span.setAttribute("url",list[i].url); span.innerText="X"; domUtils.addClass(span, 'delbtn'); domUtils.on(span, 'click', (function(span){ return function(){ try{ window.event.cancelBubble = true; //停止冒泡 window.event.returnValue = false; //阻止事件的默认行为 window.event.preventDefault(); //取消事件的默认行为 window.event.stopPropagation(); //阻止事件的传播 } finally { if(!confirm("确定要删除吗?")) return; $.post(editor.getOpt("serverUrl") + "&action=delete_site_file", { "path": span.getAttribute("url") }, function(result) { if (result == "ok") domUtils.remove(span.parentNode,false); else alert(result); }); } } })(span)); item.appendChild(span); /*增加删除功能结束*/ this.list.insertBefore(item, this.clearFloat);
在/DjangoUeditor/static/ueditor/dialogs/image/image.css增加以下内容:
/* 在线管理删除按钮样式 */ #online li span.delbtn { position: absolute; top: 0; right: 0; border: 0; z-index: 3; color: #ffffff; display: inline; font-size: 12px; line-height: 10.5px; padding:3px 5px; text-align: center; background-color: #d9534f; }
增加图片删除后台逻辑
由于DjangoUeditor并没有提供文件删除的action,需要自己添加,可在/DjangoUeditor/views.py下修改部分内容:... @csrf_exempt def get_ueditor_controller(request): """获取ueditor的后端URL地址 """ action = request.GET.get("action", "") reponseAction = { "config": get_ueditor_settings, "uploadimage": UploadFile, "uploadscrawl": UploadFile, "uploadvideo": UploadFile, "uploadfile": UploadFile, "catchimage": catcher_remote_image, "listimage": list_files, "listfile": list_files, "delete_site_file":delete_site_file,#添加action } return reponseAction[action](request) ....
#ueditor新增在线管理文件删除功能 @csrf_exempt def delete_site_file(request): path=request.POST.get("path") file=USettings.BASE_DIR+path if os.path.isfile(file): os.remove(file) return HttpResponse("ok",content_type="text/html")
视频上传
视频大小可读性
上面的图是上传5G的视频显示的视频大小,显然可读性太差。可以修改/ueditor/dialogs/video/video.js
里面的updateStatus
函数的
WebUploader.formatSize(fileSize)
改为如下:
WebUploader.formatSize(fileSize,['M'])
添加视频封面
ueditor并没有实现视频上传封面的功能,但是经过博主测试,研究出下面操作是可以实现视频上传添加封面,但是视频上传封面并不是在视频上传的时候指定的,而是在视频上传完成后再实现的,具体实现操作如下:
首先修改ueditor.all.js里面的上传视频插件内容为如下:
UE.plugins['video'] = function (){
var me =this;
/**
* 创建插入视频字符窜
* @param url 视频地址
* @param width 视频宽度
* @param height 视频高度
* @param align 视频对齐
* @param classname 标签class值
* @param type 类型(video、image、audio、embed)
* @param poster 视频封面
*/
function creatInsertStr(url,width,height,id,align,classname,type,poster){
var str;
switch (type){
case 'image':
str = '<img ' + (id ? 'id="' + id+'"' : '') + ' width="'+ width +'" height="' + height + '" _url="'+utils.html(url)+'" class="' + classname.replace(/\bvideo-js\b/, '') + '"' +
(poster ? ' src="'+poster+'" ':' src="'+ me.options.UEDITOR_HOME_URL+'themes/default/images/videologo.gif"')+
(poster ? ' _src="'+poster+'" ':' _src="'+ me.options.UEDITOR_HOME_URL+'themes/default/images/videologo.gif"')+
(align ? ' style="float:' + align + ';"': '')+'/>'
break;
case 'video':
str = '<video' + (id ? ' id="' + id + '"' : '') + ' class="' + classname + ' video-js" ' + (align ? ' style="float:' + align + '"': ' ') +
(poster ? ' poster="'+poster+'" ':' poster="'+ me.options.UEDITOR_HOME_URL+'themes/default/images/videologo.gif"')+
(poster ? ' _src="'+poster+'" ':' _src="'+ me.options.UEDITOR_HOME_URL+'themes/default/images/videologo.gif"')+
' controls preload="none" width="' + width + '" height="' + height + '" src="' + utils.html(url)+'" _url="'+utils.html(url)+ '">' +
'</video>';
break;
}
return str;
}
/**
*
* @param root 根节点
* @param img2video 为true表示image转video,为false表示video转image
*/
function switchImgAndVideo(root,img2video){
console.log(img2video);
utils.each(root.getNodesByTagName(img2video ? 'img' : 'video'),function(node){
var className = node.getAttr('class');
console.log("align:"+node.getStyle('float'));
if(className && className.indexOf('fake-video') != -1){
console.log(node);
console.log(node.getAttr("_src"));
var html = creatInsertStr( node.getAttr('_url'),node.getAttr('width'),
node.getAttr('height'),null,node.getStyle('float') || '',className,
img2video ? 'video':"image",node.getAttr("_src"));
console.log(html);
node.parentNode.replaceChild(UE.uNode.createElement(html),node);
}
if(className && className.indexOf('upload-video') != -1){
var html = creatInsertStr( node.getAttr('_url'),node.getAttr('width'),
node.getAttr('height'),null,node.getStyle('float') || '',className,
img2video ? 'video':"image",node.getAttr("_src"));
console.log(html);
node.parentNode.replaceChild(UE.uNode.createElement(html),node);
}
})
}
me.addOutputRule(function(root){
switchImgAndVideo(root,true)
});
me.addInputRule(function(root){
switchImgAndVideo(root)
});
me.commands["insertvideo"] = {
execCommand: function (cmd, videoObjs, type){
videoObjs = utils.isArray(videoObjs)?videoObjs:[videoObjs];
var html = [],id = 'tmpVedio',cl;//
for(var i=0,vi,len = videoObjs.length;i<len;i++){
vi = videoObjs[i];
cl = (type == 'upload' ? 'upload-video video-js vjs-default-skin':'fake-video');
console.log("align:"+vi.align);
console.log("poster:"+vi.poster);
var temp=creatInsertStr( vi.url,vi.width || 420, vi.height || 280,
id + i, vi.align, cl, 'image',vi.poster);
html.push(temp);
}
me.execCommand("inserthtml",html.join(""));
var rng = this.selection.getRange();
for(var i= 0,len=videoObjs.length;i<len;i++){
var img = this.document.getElementById('tmpVedio'+i);
domUtils.removeAttributes(img,'id');
rng.selectNode(img).select();
me.execCommand('imagefloat',videoObjs[i].align)
}
},
queryCommandState : function(){
var video = me.selection.getRange().getClosedNode(),
flag = video && (video.className == "fake-video" || video.className.indexOf("upload-video")!=-1);
return flag ? 1 : 0;
}
};
};
修改video.js里面的内容为如下
function initVideo(){
createAlignButton( ["videoFloat", "upload_alignment"] );
addUrlChangeListener($G("videoUrl"), $G("videoPoster"));
addOkListener();
//编辑视频时初始化相关信息
(function(){
var node = editor.selection.getRange().getClosedNode(),url;
if(node && node.className){
var hasInsertClass = node.className.indexOf("fake-video")!=-1,
hasUploadClass = node.className.indexOf("upload-video")!=-1,
poster=node.getAttribute("_src");
if(hasInsertClass || hasUploadClass) {
$G("videoUrl").value = url = node.getAttribute("_url");
$G("videoWidth").value = node.width;
$G("videoHeight").value = node.height;
$G("videoPoster").value =poster ;
var align = domUtils.getComputedStyle(node,"float"),
parentAlign = domUtils.getComputedStyle(node.parentNode,"text-align");
updateAlignButton(parentAlign==="center"?"center":align);
}
if(hasUploadClass) {
isModifyUploadVideo = true;
}
}
createPreviewVideo(url,poster);
})();
}
function createPreviewVideo(url,poster){
if ( !url )return;
var conUrl = convert_url(url);
$G("preview").innerHTML = '<div class="previewMsg"><span>'+lang.urlError+'</span></div>'+
'<video class="previewVideo" controls ' +
' src="' + conUrl + '"' +
' width="' + 420 + '"' +
' height="' + 280 +'"'+
' poster="'+poster+
'">' +
'</video>';
}
选中内容区刚上传的视频如下:
音频上传
由于ueditor并没有提供音频上传功能,所以这里需要自己模仿视频上传来实现音频上传的功能。
修改ueditor.config.js文件,增加插入音频功能入口
//这个地方的toolbars其实是默认的full类型也就是全部的编辑器操作项 toolbars=[[...,'insertvideo','insertaudio', 'music',...]] //当鼠标放在工具栏上时显示的tooltip提示,留空支持自动多语言配置,否则以配置值为准 labelMap: { 'insertaudio' : '音频', }
修改ueditor.all.js文件,增加插入音频页面和命令入口
//对话框路径,如果在ueditor.config.js配置了iframeUrlMap会被其覆盖 var iframeUrlMap = { ..., 'insertvideo':'~/dialogs/video/video.html', 'insertaudio':'~/dialogs/audio/audio.html', .... } //对话框按钮 var dialogBtns = { noOk:['searchreplace', 'help', 'spechars', 'webapp','preview'], ok:[...,'insertvideo','insertaudio',...] };
修改/DjangoUeditor/static/ueditor/lang/zh-cn/zh-cn.js文件,模仿insertvideo增加insertaudio的相关配置
'insertaudio':{ 'static':{ 'lang_tab_insertV':"插入音频", 'lang_tab_searchV':"搜索音频", 'lang_tab_uploadV':"上传音频", 'lang_audio_url':"音频网址", 'lang_audio_size':"音频尺寸", 'lang_audioW':"宽度", 'lang_audioH':"高度", 'lang_alignment':"对齐方式", 'audioSearchTxt':{'value':"请输入搜索关键字!"}, 'audioType':{'options':["全部", "热门", "娱乐", "搞笑", "体育", "科技", "综艺"]}, 'audioSearchBtn':{'value':"百度一下"}, 'audioSearchReset':{'value':"清空结果"}, 'lang_input_fileStatus':' 当前未上传文件', 'startUpload':{'style':"background:url(upload.png) no-repeat;"}, 'lang_upload_size':"音频尺寸", 'lang_upload_width':"宽度", 'lang_upload_height':"高度", 'lang_upload_alignment':"对齐方式", 'lang_format_advice':"建议使用mp3格式." }, 'numError':"请输入正确的数值,如123,400", 'floatLeft':"左浮动", 'floatRight':"右浮动", '"default"':"默认", 'block':"独占一行", 'urlError':"输入的音频地址有误,请检查后再试!", 'loading':" 音频加载中,请等待……", 'clickToSelect':"点击选中", 'goToSource':'访问源音频', 'noAduio':" 抱歉,找不到对应的音频,请重试!", 'browseFiles':'浏览文件', 'uploadSuccess':'上传成功!', 'delSuccessFile':'从成功队列中移除', 'delFailSaveFile':'移除保存失败文件', 'statusPrompt':' 个文件已上传! ', 'flashVersionError':'当前Flash版本过低,请更新FlashPlayer后重试!', 'flashLoadingError':'Flash加载失败!请检查路径或网络状态', 'fileUploadReady':'等待上传……', 'delUploadQueue':'从上传队列中移除', 'limitPrompt1':'单次不能选择超过', 'limitPrompt2':'个文件!请重新选择!', 'delFailFile':'移除失败文件', 'fileSizeLimit':'文件大小超出限制!', 'emptyFile':'空文件无法上传!', 'fileTypeError':'文件类型不允许!', 'unknownError':'未知错误!', 'fileUploading':'上传中,请等待……', 'cancelUpload':'取消上传', 'netError':'网络错误', 'failUpload':'上传失败!', 'serverIOError':'服务器IO错误!', 'noAuthority':'无权限!', 'fileNumLimit':'上传个数限制', 'failCheck':'验证失败,本次上传被跳过!', 'fileCanceling':'取消中,请等待……', 'stopUploading':'上传已停止……', 'uploadSelectFile':'点击选择文件', 'uploadAddFile':'继续添加', 'uploadStart':'开始上传', 'uploadPause':'暂停上传', 'uploadContinue':'继续上传', 'uploadRetry':'重试上传', 'uploadDelete':'删除', 'uploadTurnLeft':'向左旋转', 'uploadTurnRight':'向右旋转', 'uploadPreview':'预览中', 'updateStatusReady': '选中_个文件,共_KB。', 'updateStatusConfirm': '成功上传_个,_个失败', 'updateStatusFinish': '共_个(_KB),_个成功上传', 'updateStatusError': ',_张上传失败。', 'errorNotSupport': 'WebUploader 不支持您的浏览器!如果你使用的是IE浏览器,请尝试升级 flash 播放器。', 'errorLoadConfig': '后端配置项没有正常加载,上传插件不能正常使用!', 'errorExceedSize':'文件大小超出', 'errorFileType':'文件格式不允许', 'errorInterrupt':'文件传输中断', 'errorUploadRetry':'上传失败,请重试', 'errorHttp':'http请求错误', 'errorServerUpload':'服务器返回出错' },
修改/DjangoUeditor/settings.py文件,模仿insertvideo配置来增加insertaudio配置
# 上传音频配置 "audioActionName": "uploadaudio", # 执行上传视频的action名称 "audioPathFormat": "",#文件存储路径格式 "audioFieldName": "upfile", # 提交的视频表单名称 "audioMaxSize": 102400000, # 上传大小限制,单位B,默认100MB "audioUrlPrefix": "",#上传url前缀 "audioAllowFiles": [".mp3"], # 上传视频格式显示
修改/DjangoUeditor/static/ueditor/themes/default/css/ueditor.css,增加以下内容,我这里图标用的是ueditor自带的图标也就是音乐搜索的图标
/*音频上传*/ .edui-default .edui-for-insertaudio .edui-dialog-content { width: 590px; height: 390px; } .edui-default .edui-for-insertaudio .edui-icon { background-position: -18px -40px; }
修改/DjangoUeditor/static/ueditor/dialogs/audio/audio.css
其实就是复制一份video.css将video字眼替换成audio即可,下面只给出涉及到audio的相关css
#audioUrl { width: 490px; height: 21px; line-height: 21px; margin: 8px 5px; background: #FFF; border: 1px solid #d7d7d7; } #audioSearchTxt{margin-left:15px;background: #FFF;width:200px;height:21px;line-height:21px;border: 1px solid #d7d7d7;} #audioType{ width: 65px; height: 23px; line-height: 22px; border: 1px solid #d7d7d7; } #audioSearchBtn,#audioSearchReset{ /*width: 80px;*/ height: 25px; line-height: 25px; background: #eee; border: 1px solid #d7d7d7; cursor: pointer; padding: 0 5px; } #preview .previewAudio {position:absolute;top:0;margin:0;padding:0;height:280px;width:100%;} #audioInfo {width: 120px;float: left;margin-left: 10px;_margin-left:7px;} #audioFloat div{cursor:pointer;opacity: 0.5;filter: alpha(opacity = 50);margin:9px;_margin:5px;width:38px;height:36px;float:left;} #audioFloat .focus{opacity: 1;filter: alpha(opacity = 100)} #uploadAudioInfo{margin-top:10px;float:right;padding-right:8px;}
增加/DjangoUeditor/static/ueditor/dialogs/audio/audio.html(按照video.html来改就行)
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <title></title> <meta http-equiv="Content-Type" content="text/html;charset=utf-8"/> <script type="text/javascript" src="../internal.js"></script> <link rel="stylesheet" type="text/css" href="audio.css" /> </head> <body> <div class="wrapper"> <div id="audioTab"> <div id="tabHeads" class="tabhead"> <span tabSrc="audio" class="focus" data-content-id="audio"><var id="lang_tab_insertV"></var></span> <span tabSrc="upload" data-content-id="upload"><var id="lang_tab_uploadV"></var></span> </div> <div id="tabBodys" class="tabbody"> <div id="audio" class="panel focus"> <table><tr><td><label for="audioUrl" class="url"><var id="lang_audio_url"></var></label></td><td><input id="audioUrl" type="text"></td></tr></table> <div id="preview"></div> <div id="audioInfo"> <fieldset> <legend><var id="lang_audio_size"></var></legend> <table> <tr><td><label for="audioWidth"><var id="lang_audioW"></var></label></td><td><input class="txt" id="audioWidth" type="text"/></td></tr> <tr><td><label for="audioHeight"><var id="lang_audioH"></var></label></td><td><input class="txt" id="audioHeight" type="text"/></td></tr> </table> </fieldset> <fieldset> <legend><var id="lang_alignment"></var></legend> <div id="audioFloat"></div> </fieldset> </div> </div> <div id="upload" class="panel"> <div id="upload_left"> <div id="queueList" class="queueList"> <div class="statusBar element-invisible"> <div class="progress"> <span class="text">0%</span> <span class="percentage"></span> </div> <div class="info"></div> <div class="btns"> <div id="filePickerBtn"></div> <div class="uploadBtn"><var id="lang_start_upload"></var></div> </div> </div> <div id="dndArea" class="placeholder"> <div class="filePickerContainer"> <div id="filePickerReady"></div> </div> </div> <ul class="filelist element-invisible"> <li id="filePickerBlock" class="filePickerBlock"></li> </ul> </div> </div> <div id="uploadAudioInfo"> <fieldset> <legend><var id="lang_upload_size"></var></legend> <table> <tr><td><label><var id="lang_upload_width"></var></label></td><td><input class="txt" id="upload_width" type="text"/></td></tr> <tr><td><label><var id="lang_upload_height"></var></label></td><td><input class="txt" id="upload_height" type="text"/></td></tr> </table> </fieldset> <fieldset> <legend><var id="lang_upload_alignment"></var></legend> <div id="upload_alignment"></div> </fieldset> </div> </div> </div> </div> </div> <!-- jquery --> <script type="text/javascript" src="../../third-party/jquery-1.10.2.min.js"></script> <!-- webuploader --> <script type="text/javascript" src="../../third-party/webuploader/webuploader.min.js"></script> <link rel="stylesheet" type="text/css" href="../../third-party/webuploader/webuploader.css"> <!-- audio --> <script type="text/javascript" src="audio.js"></script> </body> </html>
- 增加/DjangoUeditor/static/ueditor/dialogs/audio/audio.js,也是按照video.js来改就行
由于内容过长不方便贴出代码,具体代码可移步前往 我的博客下载完整源代码
在/DjangoUeditor/static/ueditor/ueditor.all.js增加音频上传插件
//plugins/audio.js UE.plugins['insertaudio'] = function (){ var me =this; /** * * * @param url 视频地址 * @param width 视频宽度 * @param height 视频高度 * @param align 视频对齐 * @param id id * @param align 音频对齐 * @param classname class * @param type 类型(image还是video) */ function creatInsertStr(url,width,height,id,align,classname,type){ var str=""; switch(type){ case 'image': str='<img '+(id ? 'id="' + id+'"' : '') + (align ? 'style="float:' + align + '"' : '') + ' width="'+ width +'" height="' + height + '" _url="'+url+'" class="'+classname+'"' + ' src="'+me.options.langPath+me.options.lang+'/images/music.png" />' break; case 'audio': str='<audio'+ ' class="'+classname+'" ' + (align ? 'style="float:' + align + '"' : '') + ' controls preload="none" width="' + width + '" height="' + height +'" _url="'+url+'" src="' + url + '"></audio>'; break return str; } return str; } /** * * @param root * @param img2audio 为true表示image转audio,为false表示audio转image */ function switchImgAndVideo(root,img2audio){ utils.each(root.getNodesByTagName(img2audio ? 'img' : 'audio'),function(node){ var className = node.getAttr('class'); if(className && className.indexOf('fake-audio') != -1){ var html = creatInsertStr(node.getAttr("_url"),node.getAttr('width'), node.getAttr('height'),null, node.getStyle("float"),className,img2audio?'audio':'image'); node.parentNode.replaceChild(UE.uNode.createElement(html),node); } if(className && className.indexOf('upload-audio') != -1){ var html = creatInsertStr(node.getAttr("_url"), node.getAttr('width'), node.getAttr('height'),null, node.getStyle("float"),className,img2audio?'audio':'image'); node.parentNode.replaceChild(UE.uNode.createElement(html),node); } }) } me.addOutputRule(function(root){ switchImgAndVideo(root,true) }); me.addInputRule(function(root){ switchImgAndVideo(root) }); me.commands["insertaudio"] = { /** * 插入视频 * @command insertaudio * @method execCommand * @param { String } cmd 命令字符串 * @param { Array } videoArr 需要插入的视频的数组, 其中的每一个元素都是一个键值对对象, 描述了一个视频的所有属性 */ execCommand:function (cmd, audioObjs,type) { var me = this, audioObjs = utils.isArray(audioObjs)?audioObjs:[audioObjs]; var html = [],id = 'tmpAudio', cl; for(var i=0,vi,len = audioObjs.length;i<len;i++){ vi = audioObjs[i]; cl = (type == 'upload' ? 'upload-audio audio-js vjs-default-skin':'fake-audio'); html.push(creatInsertStr( vi.url, vi.width || 400, vi.height || 95, id + i, vi.align, cl, 'image')); } me.execCommand("inserthtml",html.join(""),true); var rng = this.selection.getRange(); for(var i= 0,len=audioObjs.length;i<len;i++){ var img = this.document.getElementById(id+i); domUtils.removeAttributes(img,'id'); rng.selectNode(img).select(); me.execCommand('imagefloat',audioObjs[i].align) } }, /** * 查询当前光标所在处是否是一个视频 * @command insertvideo * @method queryCommandState * @param { String } cmd 需要查询的命令字符串 * @return { int } 如果当前光标所在处的元素是一个视频对象, 则返回1,否则返回0 */ queryCommandState:function () { var me = this, img = me.selection.getRange().getClosedNode(), flag = img && (img.className == "faked-audio" || img.className.indexOf("upload-audio")!=-1); return flag ? 1 : 0; } }; };
代码块
ueditor通过syntaxhighlighter高亮生成的代码html文件是这样的一个结构底部出现水平滚动条
<table cellspacing="0" cellpadding="0" border="0">
<tbody>
<tr>
<td class="gutter">
<div class="line number1 index0 alt2">1</div>
<div class="line number2 index1 alt1">2</div>
</td>
<td class="code">
<div class="container">
<div class="line number1 index0 alt2">
<code class="html spaces"> </code>
<code class="html plain"><</code>
<code class="html keyword">p</code>
<code class="html color1">class</code>
<code class="html plain">=</code>
<code class="html string">"rollBlock"</code>
<code class="html plain">>这是一段长度很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长的文字</</code>
<code class="html keyword">p</code>
<code class="html plain">></code>
</div>
</div>
</td>
</tr>
</tbody>
</table>
```
可以看出代码并没有自动出现横向滚动条,导致代码语句超过了该内容所在区域,一开始我通过审查元素给对应class加上"overflow: auto
,发现并不行,后来就百度了下看看能不能找到解决的办法,后来在该ueditor解决syntaxhighlighter美化代码没有出现横向滚动条)找到了解决办法,我们可以通过修改shCore.js里面源码,位置在该js文件大概2266行,也就是该js生成上面代码的地方,给上面的代码再强行在table前加上,<div style="overflow-x: auto!important;"><table></table></div>
原代码内容如下:
html =
'<div id="' + getHighlighterId(this.id) + '" class="' + classes.join(' ') + '">'
+ (this.getParam('toolbar') ? sh.toolbar.getHtml(this) : '')
+ '<table border="0" cellpadding="0" cellspacing="0">'
+ this.getTitleHtml(this.getParam('title'))
+ '<tbody>'
+ '<tr>'
+ (gutter ? '<td class="gutter">' + this.getLineNumbersHtml(code) + '</td>' : '')
+ '<td class="code">'
+ '<div class="container">'
+ html
+ '</div>'
+ '</td>'
+ '</tr>'
+ '</tbody>'
+ '</table>'
+ '</div>'
;
修改后的代码如下:
html =
'<div id="' + getHighlighterId(this.id) + '" class="' + classes.join(' ') + '">'
+ (this.getParam('toolbar') ? sh.toolbar.getHtml(this) : '')
+'<div style="overflow-x: auto!important;">'
+ '<table border="0" cellpadding="0" cellspacing="0">'
+ this.getTitleHtml(this.getParam('title'))
+ '<tbody>'
+ '<tr>'
+ (gutter ? '<td class="gutter">' + this.getLineNumbersHtml(code) + '</td>' : '')
+ '<td class="code">'
+ '<div class="container">'
+ html
+ '</div>'
+ '</td>'
+ '</tr>'
+ '</tbody>'
+ '</table>'
+ '</div>'
+ '</div>'
;