项目简介
整个项目的代码在 https://gitee.com/zhangchao19890805/csdnBlog.git 中的blog132文件夹中。
写这个代码插件的原因:觉着UEditor默认自带的代码插件太丑了,所以决定自定义一个新的代码插件。
如何运行代码?
如果想要运行代码,需要Tomcat 8.5,把 zhangchao_ueditor1_4-utf8-jsp 文件夹放到 webapps 文件夹中。启动 Tomcat ,并打开浏览器,浏览器中的访问路径是:http://127.0.0.1/zhangchao_ueditor1_4-utf8-jsp
运行效果如图所示:
有时候你需要更改项目名称,此时你需要修改两处地方:
- index.html 中,开头部分 :
<script type="text/javascript" charset="utf-8">
window.UEDITOR_HOME_URL = "/zhangchao_ueditor1_4-utf8-jsp/";
</script>
window.UEDITOR_HOME_URL 修改成你需要的项目路径
- jsp/config.json 中修改 imageUrlPrefix 变量,以便图片可以正常上传后显示。代码如下:
"imageUrlPrefix": "/zhangchao_ueditor1_4-utf8-jsp", /* 图片访问路径前缀 */
自定义修改的部分
我集成了 highlight.js 插件。在 themes/iframe.css 中引入了样式文件。代码如下:
/*可以在这里添加你自己的css*/
@import "/zhangchao_ueditor1_4-utf8-jsp/zhangchao/default.min.css";
这个样式文件中既有 highlight.js 的部分,也有我自己添加的样式。
所有代码插件相关的代码都放到了 zhangchao 文件夹里面。按钮栏上最后一个按钮就是插入代码的按钮。这个按钮图片用的是 UEditor 自带的大图片中的一个小图标。
要想使用这个插件,index.html 中有例子:
<div>
<h1>完整demo</h1>
<script id="editor" type="text/plain" style="width:1024px;height:500px;"></script>
</div>
<!-- 中间的代码省略 -->
<script type="text/javascript">
ZhangchaoUEditor("editor")
// 后面的代码省略
</script>
其中 ZhangchaoUEditor 函数还有第二个可选参数 config 。这个最终会给UE.getEditor 函数作为第二个参数传入。用于更改UEditor 配置。
思路是用 UE.registerUI 注册新按钮。新按钮弹出的窗口中包含 iframe。iframe 中的网页是 code.html
。用户不能在编辑器中直接修改代码块。如果想修改代码,要么双击代码块,要么选中代码块后点击插入代码按钮。
下面展示一下关键代码:
ZhangchaoUEditor.js 添加代码插件,并对编辑器已经添加进来的代码块加上事件响应
function ZhangchaoUEditor(scriptId, config){
var stringUtils = {
replaceAll: function (str, oldSubstr, newSbustr) {
var arr = str.split(oldSubstr);
if (!arr || arr.length == 0) {
return str;
}
var r = arr[0];
var i = 1;
for (; i < arr.length; i++) {
r = r + newSbustr + arr[i];
}
return r;
}
}
function createDialog(editor, uiName){
var dialog = new UE.ui.Dialog({
//指定弹出层中页面的路径
iframeUrl:'/zhangchao_ueditor1_4-utf8-jsp/zhangchao/code.html',
//需要指定当前的编辑器实例
editor:editor,
//指定dialog的名字
name:uiName,
//dialog的标题
title:"插入代码",
//指定dialog的外围样式
cssRules:"width:600px;height:300px;",
//如果给出了buttons就代表dialog有确定和取消
buttons:[
{
className:'edui-okbutton',
label:'确定',
onclick:function () {
dialog.close(true);
}
},
{
className:'edui-cancelbutton',
label:'取消',
onclick:function () {
dialog.close(false);
}
}
]
});
return dialog;
}
//实例化编辑器
//建议使用工厂方法getEditor创建和引用编辑器实例,如果在某个闭包下引用该编辑器,直接调用UE.getEditor('editor')就能拿到相关的实例
UE.registerUI('ZhangchaoUEditor',function(editor,uiName){
//创建dialog,因为registerUI第一个参数,只有ZhangchaoUEditor,所以uiName必然是ZhangchaoUEditor
var dialog = createDialog(editor, "ZhangchaoUEditor");
//参考addCustomizeButton.js
var btn = new UE.ui.Button({
name:'dialogbutton' + uiName,
title:'插入代码',
//需要添加的额外样式,指定icon图标,这里默认使用一个重复的icon
cssRules :'background-position: -440px -40px;',
onclick:function () {
//渲染dialog
dialog.render();
dialog.open();
}
});
return btn;
}/*index 指定添加到工具栏上的那个位置,默认时追加到最后,editorId 指定这个UI是那个编辑器实例上的,默认是页面上所有的编辑器都会添加这个按钮*/);
var thisConfig = config || {};
var instance = UE.getEditor(scriptId, thisConfig);
// begin 绑定UEditor 的ready 事件
instance.addListener('ready', function() {
var iframeTag = instance.iframe;
var win = iframeTag.contentWindow;
var doc = win.document;
// 所有代码片段去掉选中样式
function clearAllCodeBorder(){
var codeTagArr = doc.querySelectorAll("code.hljs");
var i = 0;
var length = codeTagArr.length;
for(i = 0; i < length; i++){
var node = codeTagArr[i]
if (node.className && node.className.indexOf("hljs-zhangchao-codeborder") > -1) {
var cn = node.className;
cn = stringUtils.replaceAll(cn, "hljs-zhangchao-codeborder", "");
node.className = cn;
}
};
}
// begin 在getContent方法执行之前会触发该事件
instance.addListener("beforeGetContent", function(){
clearAllCodeBorder();
});
// end 在getContent方法执行之前会触发该事件
// 文档内容 iframe 的点击事件。
win.onclick = function(e){
clearAllCodeBorder();
//把被用户点击的code标签选中
var node = e.target;
while(node){
if (node && node.tagName && node.tagName.toLowerCase() == "code" &&
node.className.indexOf("hljs") > -1) {
if (node.className.indexOf("hljs-zhangchao-codeborder") < 0) {
var cn = node.className;
// cn = stringUtils.trim(cn)
cn = cn.trim();
cn += " hljs-zhangchao-codeborder";
node.className = cn;
}
node = null;
} else {
node = node.parentNode;
}
}
}
// 文档内容 iframe 的双击事件
win.ondblclick = function(e){
clearAllCodeBorder();
//把被用户点击的code标签选中
var node = e.target;
while(node){
if (node && node.tagName && node.tagName.toLowerCase() == "code" &&
node.className.indexOf("hljs") > -1) {
if (node.className.indexOf("hljs-zhangchao-codeborder") < 0) {
var cn = node.className;
// cn = stringUtils.trim(cn)
cn = cn.trim();
cn += " hljs-zhangchao-codeborder";
node.className = cn;
}
//创建dialog
var dialog = createDialog(instance, "ZhangchaoUEditor");
dialog.render();
dialog.open();
node = null;
} else {
node = node.parentNode;
}
}
}
// 文档内容 iframe 的失去焦点事件。
win.onblur = function(){
clearAllCodeBorder();
}
// 禁止拖拽
doc.ondragstart = function(e){
e.preventDefault()
return false;
}
});
// end 绑定UEditor 的ready 事件
}
default.min.css只展示自己添加的代码
/* begin 代码块被点击后显示边框的样式 */
.hljs-zhangchao-codeborder {
border: 10px solid #eee;
border-radius: 8px;
box-shadow: 0px 0px 10px 0px #ff0000;
box-sizing: border-box;
}
/* end 代码块被点击后显示边框的样式*/
/* begin 去掉文字反选的背景颜色 */
.hljs::-moz-selection {
/*针对Firefox*/
background: rgba(0, 0, 0, 0);
border: 5px solid red;
}
.hljs::selection {
background: rgba(0, 0, 0, 0);
}
.hljs em::-moz-selection {
background: rgba(0, 0, 0, 0);
}
.hljs em::selection {
background: rgba(0, 0, 0, 0);
}
.hljs i::-moz-selection {
background: rgba(0, 0, 0, 0);
}
.hljs i::selection {
background: rgba(0, 0, 0, 0);
}
.hljs br::-moz-selection {
background: rgba(0, 0, 0, 0);
}
.hljs br::selection {
background: rgba(0, 0, 0, 0);
}
/* end 去掉文字反选的背景颜色 */
code.html 插入代码弹窗的内容:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title></title>
<link rel="stylesheet" href="default.min.css" type="text/css" />
<style>
.zhangchao-code__textarea{
width: 580px;
height: 280px;
background: #000000;
color: #ffffff;
display: block;
margin: 10px auto 0px;
resize: none;
font-family: Consolas,Inconsolata,Courier,monospace,PingFang SC,Microsoft YaHei,sans-serif;
border-radius: 4px;
padding: 10px;
box-sizing: border-box;
}
</style>
<script src="highlight.min.js"></script>
</head>
<body>
<!-- <div class="content"> -->
<textarea spellcheck="false" class="zhangchao-code__textarea" id="zhangchao-code__textarea"></textarea>
<!-- </div> -->
<pre style="display:none;"><code id="zhangchao-code__code"></code></pre>
<!--页面中一定要引入internal.js为了能直接使用当前打开dialog的实例变量-->
<!--internal.js默认是放到dialogs目录下的-->
<script type="text/javascript" src="../dialogs/internal.js"></script>
<script>
// 为了防止和其他模块的函数重名,特别加入了此对象做命名空间用。
// NS 表示命名空间 Name Space
ZhangchaoNS = {}
/**
* 检测制定的节点是不是用来展示代码的code标签。
*/
ZhangchaoNS.isCode = function (node) {
if (node && node.tagName.toLowerCase() == "code") {
var className = node.className;
if (className.indexOf("hljs") > -1) {
return true;
}
}
return false;
}
/**
* 检测已有的代码片段有没有被选中
*/
ZhangchaoNS.isSelected = function () {
var node = editor.selection.getStart();
if (ZhangchaoNS.isCode(node)) {
return true;
}
var parent = domUtils.findParentByTagName(node, ["CODE"]);
if (ZhangchaoNS.isCode(parent)) {
return true;
}
return false;
}
/**
* 如果已经选中代码片段,就返回对应的code标签
*/
ZhangchaoNS.getCodeTag = function () {
var node = editor.selection.getStart();
if (ZhangchaoNS.isCode(node)) {
return node;
}
var parent = domUtils.findParentByTagName(node, ["CODE"]);
if (ZhangchaoNS.isCode(parent)) {
return parent;
}
return null;
}
/**
*
* 处理HTML字符串转义
* Performs the following substring replacements
* (to facilitate output to XML/HTML pages):
* <p>
* & -> &
* < -> <
* > -> >
* " -> "
* ' -> '
*/
ZhangchaoNS.escapeHtml = function (str) {
// 把原始字符串拆分成单个字符,存储到strArr中。
var strArr = new Array();
var i = 0;
for (i = 0; i < str.length; i++) {
strArr.push(str.charAt(i));
}
// 转义字符。
for (i = 0; i < strArr.length; i++) {
var s = strArr[i];
if (s == "&") {
strArr[i] = "&";
} else if (s == "<") {
strArr[i] = "<";
} else if (s == ">") {
strArr[i] = ">";
} else if (s == "\"") {
strArr[i] = """;
} else if (s == "'") {
strArr[i] = "'";
}
}
// 返回新的字符串
var newStr = "";
for (i = 0; i < strArr.length; i++) {
newStr += strArr[i];
}
return newStr;
}
/**
*
* 把HTML转义字符还原成原来的字符
*
* ' -> '
* " -> "
* > -> >
* < -> <
* & -> &
*
*/
ZhangchaoNS.reverseEscape = function (str) {
var newStr = str;
newStr = ZhangchaoNS.replaceAll(newStr, "'", "'");
newStr = ZhangchaoNS.replaceAll(newStr, """, "\"");
newStr = ZhangchaoNS.replaceAll(newStr, ">", ">");
newStr = ZhangchaoNS.replaceAll(newStr, "<", "<");
newStr = ZhangchaoNS.replaceAll(newStr, "&", "&");
return newStr;
}
/**
* 代码刷新样式
*/
ZhangchaoNS.brush = function () {
var textareaTag = document.getElementById("zhangchao-code__textarea");
var code = document.getElementById("zhangchao-code__code");
var htmlStr = textareaTag.value;
htmlStr = ZhangchaoNS.escapeHtml(htmlStr);
// 空格换成 tab换成4个
htmlStr = ZhangchaoNS.replaceAll(htmlStr, " ", " ");
htmlStr = ZhangchaoNS.replaceAll(htmlStr, "\t", " ");
code.innerHTML = htmlStr;
hljs.highlightBlock(code);
}
/**
* 把回车换行符CRLF 转换成 <br/>标签
**/
ZhangchaoNS.CRLF2Br = function (str) {
// 把原始字符串拆分成单个字符,存储到strArr中。
var strArr = new Array();
var i = 0;
for (i = 0; i < str.length; i++) {
strArr.push(str.charAt(i));
}
// \r清除掉, \n 换成<br/>
for (i = 0; i < strArr.length; i++) {
var s = strArr[i];
if (s == "\r") {
strArr[i] = "";
} else if (s == "\n") {
strArr[i] = "<br/>";
}
}
// 返回新的字符串
var newStr = "";
for (i = 0; i < strArr.length; i++) {
newStr += strArr[i];
}
return newStr;
}
/**
* 字符串全部替换
*/
ZhangchaoNS.replaceAll = function (str, oldSubstr, newSbustr) {
var arr = str.split(oldSubstr);
if (!arr || arr.length == 0) {
return str;
}
var r = arr[0];
var i = 1;
for (; i < arr.length; i++) {
r = r + newSbustr + arr[i];
}
return r;
}
/**
* 清理掉HTML标签。并且欢迎转义代码。
* 顺序和代码高亮反着来。
*
*/
ZhangchaoNS.clearHtml = function (oldHtml) {
// <br> -> \n
var str = oldHtml;
str = ZhangchaoNS.replaceAll(str, "<br>", "\n");
str = ZhangchaoNS.replaceAll(str, "<br/>", "\n");
str = ZhangchaoNS.replaceAll(str, "<br />", "\n");
// 清理掉HTML标签
var reTag = /<(?:.|\s)*?>/g;
str = str.replace(reTag, "");
// -> 英文空格
str = ZhangchaoNS.replaceAll(str, " ", " ");
// 还原转义符号
str = ZhangchaoNS.reverseEscape(str);
return str;
}
// 如果光标在代码段中。替换原来的代码段。
if (ZhangchaoNS.isSelected()) {
var codeTag = ZhangchaoNS.getCodeTag();
var oldHtml = codeTag.innerHTML;
oldHtml = ZhangchaoNS.clearHtml(oldHtml);
document.getElementById("zhangchao-code__textarea").value = oldHtml;
}
dialog.onok = function () {
// 当前窗口中的CODE标签
var currentCodeTag = document.getElementById("zhangchao-code__code");
// 格式刷子。让代码高亮。
ZhangchaoNS.brush();
var htmlStr = ZhangchaoNS.CRLF2Br(currentCodeTag.innerHTML);
// span标签换成 i 标签
htmlStr = ZhangchaoNS.replaceAll(htmlStr, "<span class=", "<i style=\"font-style:normal;\" class=")
htmlStr = ZhangchaoNS.replaceAll(htmlStr, "</span>", "</i>");
if (ZhangchaoNS.isSelected()) {
var codeTag = ZhangchaoNS.getCodeTag();
codeTag.innerHTML = htmlStr;
} else {
htmlStr = "<p></p><code class=\"" + currentCodeTag.className + "\" contenteditable=\"false\" contentEditable=\"false\">" +
htmlStr + "</code><p></p>";
editor.execCommand('inserthtml', htmlStr);
}
// begin 让代码块不可编辑
var iframeTag = editor.iframe;
var win = iframeTag.contentWindow;
var doc = win.document;
var codeTagArr = doc.querySelectorAll("code.hljs");
var i = 0;
var length = codeTagArr.length;
for(i = 0; i < length; i++){
var codeTag = codeTagArr[i];
codeTag.contentEditable = false;
}
// end 让代码块不可编辑
};
dialog.oncancel = function () {
// editor.execCommand('inserthtml', '<span>html code cancel</span>');
};
</script>
</body>
</html>