实现Markdown编辑器编辑合并功能

pull/77/head
Minho 2017-06-12 17:58:45 +08:00
parent 92ab952f8b
commit d8dd092f3e
29 changed files with 269 additions and 51 deletions

View File

@ -110,3 +110,11 @@ func (c *BaseController) ExecuteViewPathTemplate(tplName string,data interface{}
func (c *BaseController) BaseUrl() string {
return c.Ctx.Input.Scheme() + "://" + c.Ctx.Request.Host
}
//显示错误信息页面.
func (c *BaseController) ShowErrorPage(errCode int,errMsg string) {
c.TplName = "errors/error.tpl"
c.Data["ErrorMessage"] = errMsg
c.Data["ErrorCode"] = errCode
c.StopRun()
}

View File

@ -1081,6 +1081,60 @@ func (c *DocumentController) RestoreHistory() {
func (c *DocumentController) Compare() {
c.Prepare()
c.TplName = "document/compare.tpl"
history_id ,_ := strconv.Atoi(c.Ctx.Input.Param(":id"))
identify := c.Ctx.Input.Param(":key")
book_id := 0
editor := "markdown"
//如果是超级管理员则忽略权限判断
if c.Member.IsAdministrator() {
book, err := models.NewBook().FindByFieldFirst("identify", identify)
if err != nil {
beego.Error("DocumentController.Compare => ", err)
c.Abort("403")
return
}
book_id = book.BookId
c.Data["Model"] = book
editor = book.Editor
} else {
bookResult, err := models.NewBookResult().FindByIdentify(identify, c.Member.MemberId)
if err != nil || bookResult.RoleId == conf.BookObserver {
beego.Error("FindByIdentify => ", err)
c.Abort("403")
return
}
book_id = bookResult.BookId
c.Data["Model"] = bookResult
editor = bookResult.Editor
}
if history_id <= 0 {
c.ShowErrorPage(60002,"参数错误")
}
history,err := models.NewDocumentHistory().Find(history_id)
if err != nil {
beego.Error("DocumentController.Compare => ",err)
c.ShowErrorPage(60003,err.Error())
}
doc,err := models.NewDocument().Find(history.DocumentId)
if doc.BookId != book_id {
c.ShowErrorPage(60002,"参数错误")
}
c.Data["HistoryId"] = history_id
c.Data["DocumentId"] = doc.DocumentId
if editor == "markdown" {
c.Data["HistoryContent"] = history.Markdown
c.Data["Content"] = doc.Markdown
}else{
c.Data["HistoryContent"] = template.HTML(history.Content)
c.Data["Content"] = template.HTML(doc.Content)
}
}
//递归生成文档序列数组.

View File

@ -50,8 +50,11 @@ func (m *DocumentHistory) TableNameWithPrefix() string {
func NewDocumentHistory() *DocumentHistory {
return &DocumentHistory{}
}
func (m *DocumentHistory) Find() {
func (m *DocumentHistory) Find(id int) (*DocumentHistory,error) {
o := orm.NewOrm()
err := o.QueryTable(m.TableNameWithPrefix()).Filter("history_id",id).One(m)
return m,err
}
//清空指定文档的历史.
func (m *DocumentHistory) Clear(doc_id int) error {

View File

@ -0,0 +1,128 @@
/* cyrillic-ext */
@font-face {
font-family: 'Noto Sans';
font-style: normal;
font-weight: 400;
src: local('Noto Sans'), local('NotoSans'), url(notosans/v6/C7bP6N8yXZ-PGLzbFLtQKRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
}
/* cyrillic */
@font-face {
font-family: 'Noto Sans';
font-style: normal;
font-weight: 400;
src: local('Noto Sans'), local('NotoSans'), url(notosans/v6/iLJc6PpCnnbQjYc1Jq4v0xJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* devanagari */
@font-face {
font-family: 'Noto Sans';
font-style: normal;
font-weight: 400;
src: local('Noto Sans'), local('NotoSans'), url(notosans/v6/5pCv5Yz4eMu9gmvX8nNhfRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
unicode-range: U+02BC, U+0900-097F, U+1CD0-1CF6, U+1CF8-1CF9, U+200B-200D, U+20A8, U+20B9, U+25CC, U+A830-A839, U+A8E0-A8FB;
}
/* greek-ext */
@font-face {
font-family: 'Noto Sans';
font-style: normal;
font-weight: 400;
src: local('Noto Sans'), local('NotoSans'), url(notosans/v6/gEkd0pn-sMtQ_P4HUpi6WBJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Noto Sans';
font-style: normal;
font-weight: 400;
src: local('Noto Sans'), local('NotoSans'), url(notosans/v6/iPF-u8L1qkTPHaKjvXERnxJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Noto Sans';
font-style: normal;
font-weight: 400;
src: local('Noto Sans'), local('NotoSans'), url(notosans/v6/mTzVK0-EJOCaJiOPeaz-hxJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Noto Sans';
font-style: normal;
font-weight: 400;
src: local('Noto Sans'), local('NotoSans'), url(notosans/v6/erE3KsIWUumgD1j_Ca-V-xJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Noto Sans';
font-style: normal;
font-weight: 400;
src: local('Noto Sans'), local('NotoSans'), url(notosans/v6/LeFlHvsZjXu2c3ZRgBq9nFtXRa8TVwTICgirnJhmVJw.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
}
/* cyrillic-ext */
@font-face {
font-family: 'Noto Sans';
font-style: normal;
font-weight: 700;
src: local('Noto Sans Bold'), local('NotoSans-Bold'), url(notosans/v6/PIbvSEyHEdL91QLOQRnZ16-j2U0lmluP9RWlSytm3ho.woff2) format('woff2');
unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
}
/* cyrillic */
@font-face {
font-family: 'Noto Sans';
font-style: normal;
font-weight: 700;
src: local('Noto Sans Bold'), local('NotoSans-Bold'), url(notosans/v6/PIbvSEyHEdL91QLOQRnZ15X5f-9o1vgP2EXwfjgl7AY.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* devanagari */
@font-face {
font-family: 'Noto Sans';
font-style: normal;
font-weight: 700;
src: local('Noto Sans Bold'), local('NotoSans-Bold'), url(notosans/v6/PIbvSEyHEdL91QLOQRnZ10Tj6bCwSDA5u__Fbjwz3f0.woff2) format('woff2');
unicode-range: U+02BC, U+0900-097F, U+1CD0-1CF6, U+1CF8-1CF9, U+200B-200D, U+20A8, U+20B9, U+25CC, U+A830-A839, U+A8E0-A8FB;
}
/* greek-ext */
@font-face {
font-family: 'Noto Sans';
font-style: normal;
font-weight: 700;
src: local('Noto Sans Bold'), local('NotoSans-Bold'), url(notosans/v6/PIbvSEyHEdL91QLOQRnZ1xWV49_lSm1NYrwo-zkhivY.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Noto Sans';
font-style: normal;
font-weight: 700;
src: local('Noto Sans Bold'), local('NotoSans-Bold'), url(notosans/v6/PIbvSEyHEdL91QLOQRnZ16aRobkAwv3vxw3jMhVENGA.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Noto Sans';
font-style: normal;
font-weight: 700;
src: local('Noto Sans Bold'), local('NotoSans-Bold'), url(notosans/v6/PIbvSEyHEdL91QLOQRnZ1_8zf_FOSsgRmwsS7Aa9k2w.woff2) format('woff2');
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Noto Sans';
font-style: normal;
font-weight: 700;
src: local('Noto Sans Bold'), local('NotoSans-Bold'), url(notosans/v6/PIbvSEyHEdL91QLOQRnZ1z0LW-43aMEzIO6XUTLjad8.woff2) format('woff2');
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Noto Sans';
font-style: normal;
font-weight: 700;
src: local('Noto Sans Bold'), local('NotoSans-Bold'), url(notosans/v6/PIbvSEyHEdL91QLOQRnZ1-gdm0LZdjqr5-oayXSOefg.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
}

View File

@ -232,12 +232,14 @@ function formatBytes($size) {
function uploadImage($id,$callback) {
/** 粘贴上传图片 **/
document.getElementById($id).addEventListener('paste', function(e) {
e.preventDefault();
var clipboard = e.clipboardData;
for (var i = 0, len = clipboard.items.length; i < len; i++) {
if (clipboard.items[i].kind === 'file' || clipboard.items[i].type.indexOf('image') > -1) {
var imageFile = clipboard.items[i].getAsFile();
console.log(imageFile)
var fileName = Date.parse(new Date());
switch (imageFile.type){
@ -277,6 +279,7 @@ function uploadImage($id,$callback) {
}
});
e.preventDefault();
}
}
});

View File

@ -97,17 +97,20 @@ $(document).ready(function() {
height: 'auto',
cmsettings: {
lineNumbers: true,
readOnly: isSample
readOnly: false
}
});
if (parameters.get('lhs', null)) {
var url = parameters.get('lhs');
crossdomainGET(ed, 'lhs', url);
}
if (parameters.get('rhs', null)) {
var url = parameters.get('rhs');
crossdomainGET(ed, 'rhs', url);
}
ed.mergely("lhs", $("#historyContent").html());
ed.mergely("rhs", $("#documentContent").html());
// if (parameters.get('lhs', null)) {
// var url = parameters.get('lhs');
// crossdomainGET(ed, 'lhs', url);
// }
// if (parameters.get('rhs', null)) {
// var url = parameters.get('rhs');
// crossdomainGET(ed, 'rhs', url);
// }
// set query string options
var urloptions = {};
@ -161,7 +164,7 @@ $(document).ready(function() {
});
// Load
if (key.length == 8) {
if (key.length === 8) {
$.when(
$.ajax({
type: 'GET', async: true, dataType: 'text',
@ -301,29 +304,22 @@ $(document).ready(function() {
if (id == 'file-new') {
window.location = '/editor';
}
else if (id == 'file-save') {
// download directly from browser
var text = ed.mergely('diff');
if (navigator.userAgent.toLowerCase().indexOf('msie') === -1) {
if (key == '') key = ''.random(8);
var link = jQuery('<a />', {
href: 'data:application/stream;base64,' + window.btoa(unescape(encodeURIComponent(text))),
target: '_blank',
text: 'clickme',
id: key
});
link.attr('download', key + '.diff');
jQuery('body').append(link);
var a = $('a#' + key);
a[0].click();
a.remove();
else if (id === 'file-save') {
var rhs = ed.mergely('get', 'rhs');
if(window.top.hasOwnProperty("editor")){
if(window.top.editor.hasOwnProperty("$txt")){
window.top.editor.$txt.html(rhs);
}else{
window.top.editor.clear();
window.top.editor.insertValue(rhs);
}
else {
var blob = new Blob([text]);
window.navigator.msSaveOrOpenBlob(blob, key + '.diff');
window.top.layer.closeAll();
}
}
else if (id == 'file-share') {
}else if (id == 'file-share') {
handleShare(ed);
}
else if (id == 'file-import') {

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 491 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 193 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 275 KiB

View File

@ -4,12 +4,8 @@
<head>
<meta charset="utf-8" />
<title>文档比较 - Powered by MinDoc</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<meta name="description" content="Merge and Diff your documents with diff online and share" />
<meta name="keywords" content="diff,merge,compare,jsdiff,comparison,difference,file,text,unix,patch,algorithm,saas,longest common subsequence,diff online" />
<meta name="author" content="Jamie Peabody" />
<link rel="shortcut icon" href="/favicon.ico" />
<link href='http://fonts.googleapis.com/css?family=Noto+Sans:400,700' rel='stylesheet' type='text/css' />
<link href="/static/fonts/notosans.css" rel='stylesheet' type='text/css' />
<script type="text/javascript" src="/static/jquery/1.12.4/jquery.min.js"></script>
<link type='text/css' rel='stylesheet' href='/static/mergely/editor/lib/wicked-ui.css' />
<script type="text/javascript" src="/static/mergely/editor/lib/wicked-ui.js"></script>
@ -20,25 +16,43 @@
<link type="text/css" rel="stylesheet" href="/static/mergely/editor/lib/farbtastic/farbtastic.css" />
<script type="text/javascript" src="/static/mergely/lib/codemirror.min.js"></script>
<script type="text/javascript" src="/static/mergely/lib/mergely.min.js"></script>
<script type="text/javascript" src="/static/mergely/editor/editor.min.js"></script>
<script type="text/javascript" src="/static/mergely/editor/editor.js"></script>
<link type="text/css" rel="stylesheet" href="/static/mergely/lib/codemirror.css" />
<link type="text/css" rel="stylesheet" href="/static/mergely/lib/mergely.css" />
<link type='text/css' rel='stylesheet' href='/static/mergely/editor/editor.css' />
<script type="text/javascript" src="/static/mergely/lib/searchcursor.js"></script>
<script type="text/javascript">
var key = '';
var isSample = key == 'usaindep';
// var isSample = key === 'usaindep';
</script>
</head>
<body style="visibility:hidden">
<!-- toolbar -->
<ul id="toolbar">
<li id="tb-file-save" data-icon="icon-save" title="保存">保存合并</li>
<li class="separator"></li>
<li id="tb-view-change-prev" data-icon="icon-arrow-up" title="上一处差异">上一处差异</li>
<li id="tb-view-change-next" data-icon="icon-arrow-down" title="下一处差异">下一处差异</li>
<li class="separator"></li>
<li id="tb-edit-right-merge-left" data-icon="icon-arrow-left-v" title="合并到左侧">合并到左侧</li>
<li id="tb-edit-left-merge-right" data-icon="icon-arrow-right-v" title="合并到右侧">合并到右侧</li>
<li id="tb-view-swap" data-icon="icon-swap" title="左右切换">左右切换</li>
</ul>
<!-- find -->
<div class="find">
<input type="text" placeholder="请输入关键字"/>
<button class="find-prev"><span class="icon icon-arrow-up"></span></button>
<button class="find-next"><span class="icon icon-arrow-down"></span></button>
<button class="find-close"><span class="icon icon-x-mark"></span></button>
</div>
<!-- editor -->
<div style="position: absolute;top: 0px;bottom: 10px;left: 5px;right: 5px;overflow-y: hidden;padding-bottom: 2px;">
<div style="position: absolute;top: 33px;bottom: 10px;left: 5px;right: 5px;overflow-y: hidden;padding-bottom: 2px;">
<div id="mergely"></div>
</div>
<template id="historyContent">{{.HistoryContent}}</template>
<template id="documentContent">{{.Content}}</template>
<script type="text/javascript" src="/static/layer/layer.js"></script>
</body>
</html>

View File

@ -52,6 +52,11 @@
<button class="btn btn-success btn-sm restore-btn" data-id="{{$item.HistoryId}}" data-loading-text="恢复中...">
恢复
</button>
{{if eq $.Model.Editor "markdown"}}
<button class="btn btn-success btn-sm compare-btn" data-id="{{$item.HistoryId}}">
合并
</button>
{{end}}
</td>
</tr>
{{else}}
@ -128,6 +133,18 @@
})
}
});
$(".compare-btn").on("click",function () {
var historyId = $(this).attr("data-id");
window.compareIndex = window.top.layer.open({
type: 2,
title: '文档比较【左侧为历史文档,右侧为当前文档,请将文档合并到右侧】',
shade: 0.8,
area: ['380px', '90%'],
content: "{{urlfor "DocumentController.Compare" ":key" .Model.Identify ":id" ""}}" + historyId
});
window.top.layer.full(window.compareIndex);
});
});
</script>
</body>