实现富文本编辑器

pull/219/head
Minho 2018-01-24 19:41:59 +08:00
parent 00d6d0ee8f
commit dab6f31d01
5 changed files with 337 additions and 117 deletions

View File

@ -33,13 +33,14 @@ body{
width: 280px;
position: fixed;
border-top: 1px solid #DDDDDD;
bottom: 0px;
bottom: 0;
top: 40px;
background-color: #FAFAFA;
left: 0;
right: 0;
padding-bottom: 15px;
overflow-y:auto;
z-index: 999;
}
.manual-category .manual-nav {
font-size: 14px;

273
static/js/quill.js 100644
View File

@ -0,0 +1,273 @@
$(function () {
window.addDocumentModalFormHtml = $(this).find("form").html();
window.menu_save = $("#markdown-save");
window.uploader = null;
window.editor = new Quill('#docEditor', {
theme: 'snow',
modules : {
toolbar :"#editormd-tools"
}
});
window.editor.on("editor-change",function () {
resetEditorChanged(true);
});
window.menu_save.on("click",function () {
if($(this).hasClass('change')){
saveDocument();
}
});
/**
*
* @param $is_change
*/
function resetEditorChanged($is_change) {
if ($is_change && !window.isLoad) {
$("#markdown-save").removeClass('disabled').addClass('change');
} else {
$("#markdown-save").removeClass('change').addClass('disabled');
}
window.isLoad = false;
}
/***
*
* @param $node
*/
function loadDocument($node) {
var index = layer.load(1, {
shade: [0.1,'#fff'] //0.1透明度的白色背景
});
$.get(window.editURL + $node.node.id ).done(function (res) {
layer.close(index);
if(res.errcode === 0){
window.isLoad = true;
window.editor.setContents([{ insert: res.data.content }]);
// 将原始内容备份
window.source = res.data.content;
var node = { "id" : res.data.doc_id,'parent' : res.data.parent_id === 0 ? '#' : res.data.parent_id ,"text" : res.data.doc_name,"identify" : res.data.identify,"version" : res.data.version};
pushDocumentCategory(node);
window.selectNode = node;
pushVueLists(res.data.attach);
}else{
layer.msg("文档加载失败");
}
}).fail(function () {
layer.close(index);
layer.msg("文档加载失败");
});
}
/**
*
* @param $is_cover
*/
function saveDocument($is_cover,callback) {
var index = null;
var node = window.selectNode;
var html = window.editor.getContents();
var content = "";
if($.trim(html) !== ""){
content = toMarkdown(html, { gfm: true });
}
var version = "";
if(!node){
layer.msg("获取当前文档信息失败");
return;
}
var doc_id = parseInt(node.id);
for(var i in window.documentCategory){
var item = window.documentCategory[i];
if(item.id === doc_id){
version = item.version;
break;
}
}
$.ajax({
beforeSend : function () {
index = layer.load(1, {shade: [0.1,'#fff'] });
},
url : window.editURL,
data : {"identify" : window.book.identify,"doc_id" : doc_id,"markdown" : content,"html" : html,"cover" : $is_cover ? "yes":"no","version": version},
type :"post",
dataType :"json",
success : function (res) {
layer.close(index);
if(res.errcode === 0){
for(var i in window.documentCategory){
var item = window.documentCategory[i];
if(item.id === doc_id){
window.documentCategory[i].version = res.data.version;
break;
}
}
// 更新内容备份
window.source = res.data.content;
// 触发编辑器 onchange 回调函数
window.editor.onchange();
if(typeof callback === "function"){
callback();
}
}else if(res.errcode === 6005){
var confirmIndex = layer.confirm('', {
btn: ['',''] //按钮
}, function(){
layer.close(confirmIndex);
saveDocument(true,callback);
});
}else{
layer.msg(res.message);
}
}
});
}
/**
*
*/
$("#addDocumentForm").ajaxForm({
beforeSubmit : function () {
var doc_name = $.trim($("#documentName").val());
if (doc_name === ""){
return showError("目录名称不能为空","#add-error-message")
}
window.addDocumentFormIndex = layer.load(1, { shade: [0.1,'#fff'] });
return true;
},
success : function (res) {
if(res.errcode === 0){
var data = { "id" : res.data.doc_id,'parent' : res.data.parent_id === 0 ? '#' : res.data.parent_id ,"text" : res.data.doc_name,"identify" : res.data.identify,"version" : res.data.version};
var node = window.treeCatalog.get_node(data.id);
if(node){
window.treeCatalog.rename_node({"id":data.id},data.text);
}else {
window.treeCatalog.create_node(data.parent, data);
window.treeCatalog.deselect_all();
window.treeCatalog.select_node(data);
}
pushDocumentCategory(data);
$("#markdown-save").removeClass('change').addClass('disabled');
$("#addDocumentModal").modal('hide');
}else{
showError(res.message,"#add-error-message")
}
layer.close(window.addDocumentFormIndex);
}
});
/**
*
*/
$("#sidebar").jstree({
'plugins': ["wholerow", "types", 'dnd', 'contextmenu'],
"types": {
"default": {
"icon": false // 删除默认图标
}
},
'core': {
'check_callback': true,
"multiple": false,
'animation': 0,
"data": window.documentCategory
},
"contextmenu": {
show_at_node: false,
select_node: false,
"items": {
"添加文档": {
"separator_before": false,
"separator_after": true,
"_disabled": false,
"label": "添加文档",
"icon": "fa fa-plus",
"action": function (data) {
var inst = $.jstree.reference(data.reference),
node = inst.get_node(data.reference);
openCreateCatalogDialog(node);
}
},
"编辑": {
"separator_before": false,
"separator_after": true,
"_disabled": false,
"label": "编辑",
"icon": "fa fa-edit",
"action": function (data) {
var inst = $.jstree.reference(data.reference);
var node = inst.get_node(data.reference);
openEditCatalogDialog(node);
}
},
"删除": {
"separator_before": false,
"separator_after": true,
"_disabled": false,
"label": "删除",
"icon": "fa fa-trash-o",
"action": function (data) {
var inst = $.jstree.reference(data.reference);
var node = inst.get_node(data.reference);
openDeleteDocumentDialog(node);
}
}
}
}
}).on('loaded.jstree', function () {
window.treeCatalog = $(this).jstree();
}).on('select_node.jstree', function (node, selected, event) {
if(window.menu_save.hasClass('selected')) {
if(confirm("编辑内容未保存,需要保存吗?")){
saveDocument(false,function () {
loadDocument(selected);
});
return true;
}
}
loadDocument(selected);
}).on("move_node.jstree", jstree_save);
window.saveDocument = saveDocument;
window.releaseBook = function () {
if(Object.prototype.toString.call(window.documentCategory) === '[object Array]' && window.documentCategory.length > 0){
if(window.menu_save.hasClass('selected')) {
if(confirm("编辑内容未保存,需要保存吗?")) {
saveDocument();
}
}
$.ajax({
url : window.releaseURL,
data :{"identify" : window.book.identify },
type : "post",
dataType : "json",
success : function (res) {
if(res.errcode === 0){
layer.msg("发布任务已推送到任务队列,稍后将在后台执行。");
}else{
layer.msg(res.message);
}
}
});
}else{
layer.msg("没有需要发布的文档")
}
};
});

View File

@ -12,4 +12,9 @@
icons.header[6] = '<svg viewBox="0 0 18 18">\n' +
' <path class="ql-fill" d="M14.51758,9.64453a1.85627,1.85627,0,0,0-1.24316.38477H13.252a1.73532,1.73532,0,0,1,1.72754-1.4082,2.66491,2.66491,0,0,1,.5498.06641c.35254.05469.57227.01074.70508-.40723l.16406-.5166a.53393.53393,0,0,0-.373-.75977,4.83723,4.83723,0,0,0-1.17773-.14258c-2.43164,0-3.7627,2.17773-3.7627,4.43359,0,2.47559,1.60645,3.69629,3.19043,3.69629A2.70585,2.70585,0,0,0,16.96,12.19727,2.43861,2.43861,0,0,0,14.51758,9.64453Zm-.23047,3.58691c-.67187,0-1.22168-.81445-1.22168-1.45215,0-.47363.30762-.583.72559-.583.96875,0,1.27734.59375,1.27734,1.12207A.82182.82182,0,0,1,14.28711,13.23145ZM10,4V14a1,1,0,0,1-2,0V10H3v4a1,1,0,0,1-2,0V4A1,1,0,0,1,3,4V8H8V4a1,1,0,0,1,2,0Z"/>\n' +
'</svg>';
icons.font = '<svg viewbox="0 0 18 18">\n' +
' <polyline class="ql-stroke" points="3.5 14 7 4 10.5 14"></polyline>\n' +
' <line class="ql-stroke" x1="9.45" x2="4.55" y1="11" y2="11"></line>\n' +
' <path class="ql-fill" d="M13.636,5.013a4.016,4.016,0,0,0-1.863.472,0.42,0.42,0,0,0-.179.629l0.112,0.214a0.418,0.418,0,0,0,.625.191,2.557,2.557,0,0,1,1.183-.326A0.933,0.933,0,0,1,14.573,7.2V7.338H14.339c-1.272,0-3.325.281-3.325,1.954A1.75,1.75,0,0,0,12.9,11.011a2.072,2.072,0,0,0,1.785-1.078h0.022a1.132,1.132,0,0,0-.022.247V10.4a0.412,0.412,0,0,0,.457.472h0.379A0.416,0.416,0,0,0,15.99,10.4V7.293A2.121,2.121,0,0,0,13.636,5.013Zm0.948,3.4a1.452,1.452,0,0,1-1.305,1.505,0.775,0.775,0,0,1-.859-0.753c0-.854,1.216-0.966,1.93-0.966h0.234V8.416Z"></path>\n' +
'</svg>';
})();

View File

@ -9654,6 +9654,9 @@ var Tooltip = function () {
key: 'position',
value: function position(reference) {
var left = reference.left + reference.width / 2 - this.root.offsetWidth / 2;
if(left <= 0){
left = 0;
}
// root.scrollTop should be 0 if scrollContainer !== root
var top = reference.bottom + this.quill.root.scrollTop;
this.root.style.left = left + 'px';

View File

@ -41,7 +41,15 @@
<script src="/static/respond.js/1.4.2/respond.min.js"></script>
<![endif]-->
<style type="text/css">
#docEditor {overflow:auto;border: 1px solid #ddd; height: 100%;outline:none;padding: 5px;}
.modal{z-index: 999999999;}
#docEditor {
overflow:auto;
border: 1px solid #ddd;
border-left: none;
height: 100%;
outline:none;
padding: 5px;
}
.btn-info{background-color: #ffffff !important;}
.btn-info>i{background-color: #cacbcd !important; color: #393939 !important; box-shadow: inset 0 0 0 1px transparent,inset 0 0 0 0 rgba(34,36,38,.15);}
.editor-wrapper>pre{padding: 0;}
@ -66,12 +74,12 @@
margin-right: 10px;
}
.editor-group .editor-item{
.editor-group .editor-item,.editor-group .editor-item-select>.ql-picker-label{
float: left;
display: inline-block;
width: 34px !important;
min-width: 34px;
height: 30px !important;
padding: 5px !important;
padding: 5px;
line-height: 30px;
text-align: center;
color: #4b4b4b;
@ -82,6 +90,17 @@
border-radius: 0;
font-size: 12px
}
.ql-snow .ql-picker.ql-expanded .ql-picker-options{
margin-top: 5px;
}
.editor-group .editor-item-select>.ql-picker-label{
border-right: 1px solid #ccc !important;
}
.editor-group .editor-item-single-select>.ql-picker-label{
border-radius: 4px;
padding: 0;
}
.editor-group .editor-item-last{
border-right: 1px solid #ccc !important;
border-radius: 0 4px 4px 0;
@ -163,7 +182,7 @@
<body>
<div class="m-manual manual-editor">
<div class="manual-head btn-toolbar" id="editormd-tools" data-role="editor-toolbar" data-target="#editor">
<div class="manual-head btn-toolbar" id="editormd-tools" style="min-width: 1600px;" data-role="editor-toolbar" data-target="#editor">
<div class="editor-group">
<a href="{{urlfor "BookController.Index"}}" data-toggle="tooltip" data-title="返回"><i class="fa fa-chevron-left" aria-hidden="true"></i></a>
</div>
@ -174,10 +193,17 @@
<a href="javascript:;" data-toggle="tooltip" data-title="撤销 (Ctrl-Z)" class="ql-undo"><i class="fa fa-undo first" name="undo" unselectable="on"></i></a>
<a href="javascript:;" data-toggle="tooltip" data-title="重做 (Ctrl-Y)" class="ql-redo"><i class="fa fa-repeat last" name="redo" unselectable="on"></i></a>
</div>
<div class="editor-group">
<select data-toggle="tooltip" data-title="字体" title="字体" class="ql-font editor-item-select editor-item-single-select"></select>
</div>
<div class="editor-group">
<select data-toggle="tooltip" data-title="字号" title="字号" class="ql-size editor-item-select editor-item-single-select"></select>
</div>
<div class="editor-group">
<button data-toggle="tooltip" data-title="粗体" class="ql-bold editor-item editor-item-first"></button>
<button data-toggle="tooltip" data-title="斜体" class="ql-italic editor-item"></button>
<button data-toggle="tooltip" data-title="删除线" class="ql-underline editor-item editor-item-last"></button>
<button data-toggle="tooltip" data-title="删除线" class="ql-strike editor-item"></button>
<button data-toggle="tooltip" data-title="下划线" class="ql-underline editor-item editor-item-last"></button>
</div>
<div class="editor-group">
<button data-toggle="tooltip" data-title="标题一" class="ql-header editor-item editor-item-first" value="1"></button>
@ -191,17 +217,20 @@
<button data-toggle="tooltip" data-title="无序列表" class="ql-list editor-item editor-item-first" value="ordered"></button>
<button data-toggle="tooltip" data-title="有序列表" class="ql-list editor-item" value="bullet"></button>
<button data-toggle="tooltip" data-title="右缩进" class="ql-indent editor-item" value="-1"></button>
<button data-toggle="tooltip" data-title="左缩进" class="ql-indent editor-item editor-item-last" value="+1"></button>
<button data-toggle="tooltip" data-title="左缩进" class="ql-indent editor-item" value="+1"></button>
<button data-toggle="tooltip" data-title="下标" class="ql-script editor-item" value="sub"></button>
<button data-toggle="tooltip" data-title="上标" class="ql-script editor-item editor-item-last" value="super"></button>
</div>
<div class="editor-group ql-formats">
<button data-toggle="tooltip" data-title="链接" class="ql-link editor-item editor-item-first"></button>
<a href="javascript:;" data-toggle="tooltip" data-title="引用链接"><i class="fa fa-anchor item" name="reference-link" unselectable="on"></i></a>
<button data-toggle="tooltip" data-title="清空格式" class="ql-clean editor-item"></button>
<button data-toggle="tooltip" data-title="添加图片" class="ql-image editor-item"></button>
<button data-toggle="tooltip" data-title="添加视频" class="ql-video editor-item"></button>
<button data-toggle="tooltip" data-title="代码块" class="ql-code-block editor-item"></button>
<button data-toggle="tooltip" data-title="添加表格" class="ql-table editor-item"></button>
<button data-toggle="tooltip" data-title="引用" class="ql-blockquote editor-item"><i class="fa fa-quote-right item" name="quote" unselectable="on"></i></button>
<button data-toggle="tooltip" data-title="公式" class="ql-formula editor-item"><i class="fa fa-tasks item" name="tasks" aria-hidden="true"></i></button>
<select data-toggle="" data-title="" class="ql-color ql-picker ql-color-picker editor-item"></select>
<select data-toggle="tooltip" data-title="字体颜色" class="ql-color ql-picker ql-color-picker editor-item-select" ></select>
<select data-toggle="tooltip" data-title="背景颜色" class="ql-background editor-item-select"></select>
<a href="javascript:;" data-toggle="tooltip" data-title="附件"><i class="fa fa-paperclip item" aria-hidden="true" name="attachment"></i></a>
<a href="javascript:;" data-toggle="tooltip" data-title="模板"><i class="fa fa-tachometer last" name="template"></i></a>
@ -225,8 +254,8 @@
<div class="clearfix"></div>
</div>
<div class="manual-body">
<div class="manual-category" id="manualCategory">
<div class="manual-body" style="min-width: 1600px;right: inherit">
<div class="manual-category" id="manualCategory" style=" border-right: 1px solid #DDDDDD;width: 281px;position: absolute;">
<div class="manual-nav">
<div class="nav-item active"><i class="fa fa-bars" aria-hidden="true"></i> 文档</div>
<div class="nav-plus pull-right" id="btnAddDocument" data-toggle="tooltip" data-title="创建文档" data-direction="right"><i class="fa fa-plus" aria-hidden="true"></i></div>
@ -234,9 +263,9 @@
</div>
<div class="manual-tree" id="sidebar"> </div>
</div>
<div class="manual-editor-container" id="manualEditorContainer">
<div class="manual-editor-container" id="manualEditorContainer" style="min-width: 1319px;">
<div class="manual-editormd">
<div id="docEditor" class="manual-editormd-active">
<div id="docEditor" class="manual-editormd-active ql-editor ql-blank">
MinDoc 是一款针对IT团队开发的简单好用的文档管理系统。
@ -256,57 +285,6 @@
</div>
</div>
<!--创建代码块的模态窗-->
<div class="modal fade" id="createCodeToolbarModal" tabindex="-1" role="dialog" aria-labelledby="mySmallModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title">创建链接</h4>
</div>
<div class="modal-body">
<textarea></textarea>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary" id="btnCreateCodeToolbar">确定</button>
</div>
</div>
</div>
</div>
<!--创建链接的模态窗-->
<div class="modal fade" id="createLinkToolbarModal" tabindex="-1" role="dialog" aria-labelledby="mySmallModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title">创建链接</h4>
</div>
<div class="modal-body">
<form class="form-horizontal">
<div class="form-group">
<label for="linkUrl" class="control-label col-sm-2">链接地址</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="linkUrl" value="http://" data-url="">
</div>
</div>
<div class="form-group">
<label for="linkTitle" class="control-label col-sm-2">链接标题</label>
<div class="col-sm-10">
<input type="text" class="form-control" value="" id="linkTitle" data-title="">
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary" id="btnCreateLinkToolbar">确定</button>
</div>
</div>
</div>
</div>
<!-- 添加文档 -->
<div class="modal fade" id="addDocumentModal" tabindex="-1" role="dialog" aria-labelledby="addDocumentModalLabel">
<div class="modal-dialog" role="document">
@ -355,7 +333,11 @@
</div>
<div class="modal-body">
<div class="attach-drop-panel">
<div class="upload-container" id="filePicker"><i class="fa fa-upload" aria-hidden="true"></i></div>
<div class="upload-container" id="filePicker">
<div class="webuploader-pick">
<i class="fa fa-upload" aria-hidden="true"></i>
</div>
</div>
</div>
<div class="attach-list" id="attachList">
<template v-for="item in lists">
@ -473,20 +455,14 @@
{{/*<script src="/static/bootstrap/plugins/bootstrap-wysiwyg/bootstrap-wysiwyg.js" type="text/javascript"></script>*/}}
{{/*<script src="/static/bootstrap/plugins/bootstrap-wysiwyg/external/google-code-prettify/prettify.js"></script>*/}}
<script src="/static/katex/katex.min.js" type="text/javascript"></script>
<script src="/static/quill/quill.min.js" type="text/javascript"></script>
<script src="/static/quill/quill.js" type="text/javascript"></script>
<script src="/static/quill/quill.icons.js"></script>
<script src="{{cdnjs "/static/layer/layer.js"}}" type="text/javascript" ></script>
<script src="{{cdnjs "/static/js/jquery.form.js"}}" type="text/javascript"></script>
{{/*<script src="/static/js/editor.js" type="text/javascript"></script>*/}}
<script src="/static/js/editor.js" type="text/javascript"></script>
<script src="/static/js/quill.js" type="text/javascript"></script>
<script type="text/javascript">
var quill = new Quill('#docEditor', {
theme: 'snow',
modules : {
toolbar :"#editormd-tools"
}
});
$(function () {
var $editorEle = $("#editormd-tools");
@ -498,46 +474,6 @@
quill.history.redo();
});
//弹出创建链接的对话框
$("#createLinkToolbar").on("click",function () {
$("#createLinkToolbarModal").modal("show");
});
/**
* 当点击创建链接按钮后
*/
$("#btnCreateLinkToolbar").on("click",function () {
var $then = $("#createLinkToolbarModal");
var link = $then.find("input[data-url]").val();
var title = $then.find("input[data-title]").val();
if(link === ""){
alert("链接地址不能为空");
return false;
}else if(title === ""){
alert("链接标题不能为空");
return false;
}
$then.modal("hide");
window.wysiwyg.insertLink(link,title);
});
/**
* 创建代码块弹窗
*/
$("#createCodeToolbar").on("click",function () {
$("#createCodeToolbarModal").modal("show");
});
/**
* 插入代码块
*/
$("#btnCreateCodeToolbar").on("click",function () {
var $then = $("#createCodeToolbarModal");
var code = $then.find("textarea").val();
console.log(code)
var codeHtml = '<div contenteditable="false" class="editor-wrapper"><code class="editor-code">' + code + '</code></div><p></p>';
$then.modal("hide");
window.wysiwyg.insertHtml(codeHtml);
});
$(".editor-code").on("dblclick",function () {
var code = $(this).html();
$("#createCodeToolbarModal").find("textarea").val(code);
@ -552,9 +488,11 @@
$("#attachInfo").on("click",function () {
$("#uploadAttachModal").modal("show");
});
window.uploader = null;
/**
* 文件上传
*/
$("#uploadAttachModal").on("shown.bs.modal",function () {
if(window.uploader === null){
try {