mirror of https://github.com/mindoc-org/mindoc.git
增加清理附件按钮,对悬空无引用的图片/附件进行清理 (#918)
* add attach_clean & change_theme * add attach_clean & change_theme * add * add readme --------- Co-authored-by: root <root@DESKTOP-L84EQPB.localdomain>pull/926/head
parent
0dbb5d7967
commit
71b8e528ff
|
@ -24,7 +24,7 @@ _testmain.go
|
|||
mindoc
|
||||
mindoc_linux_amd64
|
||||
mindoc_linux_musl_amd64
|
||||
database
|
||||
database/mindoc.db
|
||||
*.test
|
||||
*.prof
|
||||
.idea
|
||||
|
|
56
README.md
56
README.md
|
@ -268,3 +268,59 @@ docker run -p 8181:8181 --name mindoc -e DB_ADAPTER=mysql -e MYSQL_PORT_3306_TCP
|
|||
|
||||
一个不纯粹的PHPer,一个不自由的 gopher 。
|
||||
|
||||
|
||||
# 部署补充
|
||||
- 若内网部署,draw.io无法使用外网,则需要用tomcat运行war包,见(https://github.com/jgraph/drawio) 从release下载,之后修改markdown.js的TODO行对应的链接即可
|
||||
- 为了护眼,简单增加了编辑界面的主题切换,见editormd.js和markdown_edit_template.tpl
|
||||
- (需重新编译项)为了对已删除文档/文档引用图片删除文字后,对悬空无引用的图片/附件进行清理,增加了清理接口,需重新编译
|
||||
- 编译后除二进制文件外还需更新三个文件: conf/lang/en-us.ini,zh-cn.ini; attach_list.tpl
|
||||
- 若不想重新编译,也可通过database/clean.py,手动执行对无引用图片/附件的文件清理和数据库记录双向清理。
|
||||
- 若采用nginx二级部署,以yourpath/为例,需修改
|
||||
- conf/app.conf修改:`baseurl="/yourpath"`
|
||||
- static/js/kancloud.js文件中`url: "/comment/xxxxx` => `url: "/yourpath" + "/comment/xxxxx`, 共两处
|
||||
|
||||
- nginx端口代理示例:
|
||||
```
|
||||
增加
|
||||
location /yourpath/ {
|
||||
rewrite ^/yourpath/(.*) /$1 break;
|
||||
proxy_pass http://127.0.0.1:8181;
|
||||
}
|
||||
```
|
||||
注意使用的是127.0.0.1,根据自身选择替换,如果nginx是docker部署,则还需要在docker中托管运行mindoc,具体参考如下配置:
|
||||
- docker-compose代理示例(docker-nginx代理运行mindoc)
|
||||
```
|
||||
version: '3'
|
||||
services:
|
||||
mynginx:
|
||||
image: nginx:latest
|
||||
ports:
|
||||
- "8880:80"
|
||||
command:
|
||||
- bash
|
||||
- -c
|
||||
- |
|
||||
service nginx start
|
||||
cd /src/mindoc/ && ./mindoc
|
||||
volumes:
|
||||
- ..:/src
|
||||
- ./nginx:/etc/nginx/conf.d
|
||||
```
|
||||
|
||||
目录结构
|
||||
```
|
||||
onefolder
|
||||
|
|
||||
- docker
|
||||
|
|
||||
- docker-compose.yml
|
||||
- nginx
|
||||
|
|
||||
- mynginx.conf
|
||||
|
||||
- mindoc
|
||||
|
|
||||
- database/
|
||||
- conf/
|
||||
- ...
|
||||
```
|
|
@ -54,6 +54,7 @@ yes = yes
|
|||
no = no
|
||||
read = Read
|
||||
generate = Generate
|
||||
clean = Clean
|
||||
|
||||
[init]
|
||||
default_proj_name = MinDoc Demo Project
|
||||
|
|
|
@ -54,6 +54,7 @@ yes = 是
|
|||
no = 否
|
||||
read = 阅读
|
||||
generate = 生成
|
||||
clean = 清理
|
||||
|
||||
[init]
|
||||
default_proj_name = MinDoc演示项目
|
||||
|
|
|
@ -634,6 +634,41 @@ func (c *ManagerController) AttachList() {
|
|||
c.Data["Lists"] = attachList
|
||||
}
|
||||
|
||||
//附件清理.
|
||||
func (c *ManagerController) AttachClean() {
|
||||
c.Prepare()
|
||||
|
||||
attachList, _, err := models.NewAttachment().FindToPager(0, 0)
|
||||
|
||||
if err != nil {
|
||||
c.Abort("500")
|
||||
}
|
||||
|
||||
for _, item := range attachList {
|
||||
|
||||
p := filepath.Join(conf.WorkingDirectory, item.FilePath)
|
||||
|
||||
item.IsExist = filetil.FileExists(p)
|
||||
if item.IsExist {
|
||||
// 判断
|
||||
searchList, err := models.NewDocumentSearchResult().SearchAllDocument(item.HttpPath)
|
||||
if err != nil {
|
||||
c.Abort("500")
|
||||
} else if len(searchList) == 0 {
|
||||
logs.Info("delete file:", item.FilePath)
|
||||
item.FilePath = p
|
||||
if err := item.Delete(); err != nil {
|
||||
logs.Error("AttachDelete => ", err)
|
||||
c.JsonResult(6002, err.Error())
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c.JsonResult(0, "ok")
|
||||
}
|
||||
|
||||
//附件详情.
|
||||
func (c *ManagerController) AttachDetailed() {
|
||||
c.Prepare()
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
import sqlite3
|
||||
import os, glob
|
||||
|
||||
conn = sqlite3.connect("mindoc.db")
|
||||
cur = conn.cursor() #通过建立数据库游标对象,准备读写操作
|
||||
|
||||
|
||||
cmd = """
|
||||
SELECT
|
||||
att.http_path
|
||||
FROM
|
||||
md_attachment AS att
|
||||
WHERE (att.document_id != 0 OR (NOT EXISTS( SELECT 1 FROM md_documents WHERE markdown LIKE ("%" || att.http_path || "%"))))
|
||||
AND (att.document_id = 0 OR (NOT EXISTS( SELECT 1 FROM md_documents WHERE att.document_id = document_id )))
|
||||
"""
|
||||
cur.execute(cmd)
|
||||
file_list = cur.fetchall()
|
||||
for file_item in file_list:
|
||||
item_path = file_item[0]
|
||||
# 1. 删除os文件
|
||||
if os.path.exists(os.path.join("..", item_path[1:])):
|
||||
os.remove(os.path.join("..", item_path[1:]))
|
||||
|
||||
# 2. 查询os是否删除成功,成功则删除附件记录
|
||||
if not os.path.exists(os.path.join("..", item_path[1:])):
|
||||
cmd = """
|
||||
delete
|
||||
from md_attachment
|
||||
WHERE http_path = '{}'
|
||||
""".format(item_path)
|
||||
cur.execute(cmd)
|
||||
conn.commit() #保存提交,确保数据保存成功
|
||||
|
||||
conn.close() #关闭与数据库的连接
|
|
@ -102,11 +102,15 @@ func (m *Attachment) FindToPager(pageIndex, pageSize int) (attachList []*Attachm
|
|||
return nil, 0, err
|
||||
}
|
||||
totalCount = int(total)
|
||||
offset := (pageIndex - 1) * pageSize
|
||||
|
||||
var list []*Attachment
|
||||
|
||||
_, err = o.QueryTable(m.TableNameWithPrefix()).OrderBy("-attachment_id").Offset(offset).Limit(pageSize).All(&list)
|
||||
offset := (pageIndex - 1) * pageSize
|
||||
if pageSize == 0 {
|
||||
_, err = o.QueryTable(m.TableNameWithPrefix()).OrderBy("-attachment_id").Offset(offset).Limit(pageSize).All(&list)
|
||||
} else {
|
||||
_, err = o.QueryTable(m.TableNameWithPrefix()).OrderBy("-attachment_id").All(&list)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if err == orm.ErrNoRows {
|
||||
|
|
|
@ -309,3 +309,23 @@ func (m *DocumentSearchResult) SearchDocument(keyword string, bookId int) (docs
|
|||
|
||||
return
|
||||
}
|
||||
|
||||
// 所有项目搜索.
|
||||
func (m *DocumentSearchResult) SearchAllDocument(keyword string) (docs []*DocumentSearchResult, err error) {
|
||||
o := orm.NewOrm()
|
||||
|
||||
sql := "SELECT * FROM md_documents WHERE (document_name LIKE ? OR `release` LIKE ?) "
|
||||
keyword = "%" + keyword + "%"
|
||||
|
||||
_need_escape := need_escape(keyword)
|
||||
escape_sql := func(sql string) string {
|
||||
if _need_escape {
|
||||
return escape_re.ReplaceAllString(sql, escape_replace)
|
||||
}
|
||||
return sql
|
||||
}
|
||||
|
||||
_, err = o.Raw(escape_sql(sql), keyword, keyword).QueryRows(&docs)
|
||||
|
||||
return
|
||||
}
|
||||
|
|
|
@ -154,6 +154,7 @@ func init() {
|
|||
web.Router("/manager/books/open", &controllers.ManagerController{}, "post:PrivatelyOwned")
|
||||
|
||||
web.Router("/manager/attach/list", &controllers.ManagerController{}, "*:AttachList")
|
||||
web.Router("/manager/attach/clean", &controllers.ManagerController{}, "post:AttachClean")
|
||||
web.Router("/manager/attach/detailed/:id", &controllers.ManagerController{}, "*:AttachDetailed")
|
||||
web.Router("/manager/attach/delete", &controllers.ManagerController{}, "post:AttachDelete")
|
||||
web.Router("/manager/label/list", &controllers.ManagerController{}, "get:LabelList")
|
||||
|
|
|
@ -71,7 +71,7 @@
|
|||
"list-ul", "list-ol", "hr", "|",
|
||||
"link", "reference-link", "image", "code", "preformatted-text", "code-block", "table", "datetime", "emoji", "html-entities", "pagebreak", "|",
|
||||
"goto-line", "watch", "preview", "fullscreen", "clear", "search", "|",
|
||||
"help", "info"
|
||||
"help", "changetheme", "info"
|
||||
],
|
||||
simple : [
|
||||
"undo", "redo", "|",
|
||||
|
@ -79,12 +79,12 @@
|
|||
"h1", "h2", "h3", "h4", "h5", "h6", "|",
|
||||
"list-ul", "list-ol", "hr", "|",
|
||||
"watch", "preview", "fullscreen", "|",
|
||||
"help", "info"
|
||||
"help", "changetheme", "info"
|
||||
],
|
||||
mini : [
|
||||
"undo", "redo", "|",
|
||||
"watch", "preview", "|",
|
||||
"help", "info"
|
||||
"help", "changetheme", "info"
|
||||
]
|
||||
};
|
||||
|
||||
|
@ -94,8 +94,8 @@
|
|||
name : "", // Form element name
|
||||
value : "", // value for CodeMirror, if mode not gfm/markdown
|
||||
theme : "", // Editor.md self themes, before v1.5.0 is CodeMirror theme, default empty
|
||||
editorTheme : "default", // Editor area, this is CodeMirror theme at v1.5.0
|
||||
previewTheme : "", // Preview area theme, default empty
|
||||
editorTheme : "pastel-on-dark", //"default", // Editor area, this is CodeMirror theme at v1.5.0
|
||||
previewTheme : "dark", //"", // Preview area theme, default empty
|
||||
markdown : "", // Markdown source code
|
||||
appendMarkdown : "", // if in init textarea value not empty, append markdown to textarea
|
||||
width : "100%",
|
||||
|
@ -225,6 +225,7 @@
|
|||
fullscreen : "fa-arrows-alt",
|
||||
clear : "fa-eraser",
|
||||
help : "fa-question-circle",
|
||||
changetheme : "fa-info-circle",
|
||||
info : "fa-info-circle"
|
||||
},
|
||||
toolbarIconTexts : {},
|
||||
|
@ -271,6 +272,7 @@
|
|||
clear : "清空",
|
||||
search : "搜索",
|
||||
help : "使用帮助",
|
||||
changetheme : "切换编辑主题",
|
||||
info : "关于" + editormd.title
|
||||
},
|
||||
buttons : {
|
||||
|
@ -322,7 +324,10 @@
|
|||
},
|
||||
help : {
|
||||
title : "使用帮助"
|
||||
}
|
||||
},
|
||||
changetheme : {
|
||||
title : "切换编辑主题"
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -3385,6 +3390,11 @@
|
|||
this.executePlugin("helpDialog", "help-dialog/help-dialog");
|
||||
},
|
||||
|
||||
changetheme : function() {
|
||||
this.setEditorTheme((this.settings.editorTheme=="default")?"pastel-on-dark":"default");
|
||||
this.setPreviewTheme((this.settings.previewTheme=="")?"dark":"");
|
||||
},
|
||||
|
||||
info : function() {
|
||||
this.showInfoDialog();
|
||||
}
|
||||
|
|
|
@ -72,7 +72,7 @@ $(function () {
|
|||
|
||||
drawio.show = function () {
|
||||
|
||||
const drawUrl = 'https://embed.diagrams.net/?embed=1&libraries=1&proto=json&spin=1&saveAndExit=1&noSaveBtn=1&noExitBtn=0';
|
||||
const drawUrl = 'https://embed.diagrams.net/?embed=1&libraries=1&proto=json&spin=1&saveAndExit=1&noSaveBtn=1&noExitBtn=0'; // TODO: with Tomcat & https://github.com/jgraph/drawio
|
||||
this.div = document.createElement('div');
|
||||
this.div.id = 'diagram';
|
||||
this.gXml = '';
|
||||
|
|
|
@ -80,12 +80,6 @@
|
|||
<div class="editormd-group">
|
||||
<a href="javascript:;" data-toggle="tooltip" data-title="{{i18n .Lang "doc.h1"}}"><i class="fa editormd-bold first" name="h1" unselectable="on">H1</i></a>
|
||||
<a href="javascript:;" data-toggle="tooltip" data-title="{{i18n .Lang "doc.h2"}}"><i class="fa editormd-bold item" name="h2" unselectable="on">H2</i></a>
|
||||
<a href="javascript:;" data-toggle="tooltip" data-title="{{i18n .Lang "doc.h3"}}"><i class="fa editormd-bold item" name="h3" unselectable="on">H3</i></a>
|
||||
<a href="javascript:;" data-toggle="tooltip" data-title="{{i18n .Lang "doc.h4"}}"><i class="fa editormd-bold item" name="h4" unselectable="on">H4</i></a>
|
||||
<a href="javascript:;" data-toggle="tooltip" data-title="{{i18n .Lang "doc.h5"}}"><i class="fa editormd-bold item" name="h5" unselectable="on">H5</i></a>
|
||||
<a href="javascript:;" data-toggle="tooltip" data-title="{{i18n .Lang "doc.h6"}}"><i class="fa editormd-bold last" name="h6" unselectable="on">H6</i></a>
|
||||
</div>
|
||||
<div class="editormd-group">
|
||||
<a href="javascript:;" data-toggle="tooltip" data-title="{{i18n .Lang "doc.unorder_list"}}"><i class="fa fa-list-ul first" name="list-ul" unselectable="on"></i></a>
|
||||
<a href="javascript:;" data-toggle="tooltip" data-title="{{i18n .Lang "doc.order_list"}}"><i class="fa fa-list-ol item" name="list-ol" unselectable="on"></i></a>
|
||||
<a href="javascript:;" data-toggle="tooltip" data-title="{{i18n .Lang "doc.hline"}}"><i class="fa fa-minus last" name="hr" unselectable="on"></i></a>
|
||||
|
@ -111,10 +105,12 @@
|
|||
<a href="javascript:;" data-toggle="tooltip" data-title="{{i18n .Lang "doc.modify_history"}}"><i class="fa fa-history item" name="history" aria-hidden="true"></i></a>
|
||||
<a href="javascript:;" data-toggle="tooltip" data-title="{{i18n .Lang "doc.sidebar"}}"><i class="fa fa-columns item" aria-hidden="true" name="sidebar"></i></a>
|
||||
<a href="javascript:;" data-toggle="tooltip" data-title="{{i18n .Lang "doc.help"}}"><i class="fa fa-question-circle-o last" aria-hidden="true" name="help"></i></a>
|
||||
<a href="javascript:;" data-toggle="tooltip" data-title="{{i18n .Lang "doc.changetheme"}}"><i class="fa fa-paint-brush item" aria-hidden="true" name="changetheme"></i></a>
|
||||
</div>
|
||||
|
||||
<div class="editormd-group pull-right">
|
||||
<a target="_blank" href="{{urlfor "DocumentController.Read" ":key" .Model.Identify ":id" ""}}" data-toggle="tooltip" data-title="{{i18n .Lang "blog.preview"}}"><i class="fa fa-external-link" name="preview-open" aria-hidden="true"></i></a>
|
||||
<!--<a target="_blank" href="{{urlfor "DocumentController.Read" ":key" .Model.Identify ":id" ""}}" data-toggle="tooltip" data-title="{{i18n .Lang "blog.preview"}}"><i class="fa fa-external-link" name="preview-open" aria-hidden="true"></i></a>-->
|
||||
<a href="{{urlfor "DocumentController.Read" ":key" .Model.Identify ":id" ""}}" data-toggle="tooltip" data-title="{{i18n .Lang "blog.preview"}}"><i class="fa fa-external-link" name="preview-open" aria-hidden="true"></i></a>
|
||||
<a href="javascript:;" data-toggle="tooltip" data-title="{{i18n .Lang "doc.publish"}}"><i class="fa fa-cloud-upload" name="release" aria-hidden="true"></i></a>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -27,8 +27,9 @@
|
|||
{{template "manager/widgets.tpl" .}}
|
||||
<div class="page-right">
|
||||
<div class="m-box">
|
||||
<div class="box-head">
|
||||
<div class="box-head" id="attachAll">
|
||||
<strong class="box-title">{{i18n .Lang "mgr.attachment_mgr"}}</strong>
|
||||
<button type="button" data-method="clean" class="btn btn-danger btn-sm" data-loading-text="{{i18n $.Lang "message.processing"}}">{{i18n $.Lang "common.clean"}}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
|
@ -104,6 +105,29 @@
|
|||
}
|
||||
});
|
||||
});
|
||||
|
||||
$("#attachAll").on("click","button[data-method='clean']",function () {
|
||||
var $this = $(this);
|
||||
$(this).button("loading");
|
||||
$.ajax({
|
||||
url : "{{urlfor "ManagerController.AttachClean"}}",
|
||||
type : "post",
|
||||
dataType : "json",
|
||||
success : function (res) {
|
||||
if(res.errcode === 0){
|
||||
alert("done");
|
||||
}else {
|
||||
layer.msg(res.message);
|
||||
}
|
||||
},
|
||||
error : function () {
|
||||
layer.msg({{i18n .Lang "message.system_error"}});
|
||||
},
|
||||
complete : function () {
|
||||
$this.button("reset");
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
|
Loading…
Reference in New Issue