diff --git a/CHANGES.md b/CHANGES.md index 7851ac5..1686a51 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,6 +7,10 @@ - [升级]同步Vditor组件版本至3.7.1; - [新增]富文本编辑器iceEditor; - [优化]Vditor编辑模式下移动端输入体验; +- [新增]文集图标配置功能; +- [新增]Word(.docx格式)文档导入功能; +- [优化]文档编辑器界面样式和交互; + ### v0.6.2 2020-12 diff --git a/app_doc/import_utils.py b/app_doc/import_utils.py index a00ab79..29481ad 100644 --- a/app_doc/import_utils.py +++ b/app_doc/import_utils.py @@ -14,6 +14,8 @@ from app_doc.util_upload_img import upload_generation_dir from django.db import transaction from django.conf import settings from loguru import logger +import mammoth +from markdownify import markdownify # 导入Zip文集 class ImportZipProject(): @@ -142,6 +144,63 @@ class ImportZipProject(): else: return md_content + +# 导入Word文档(.docx) +class ImportDocxDoc(): + def __init__(self,docx_file_path,editor_mode,create_user): + self.docx_file_path = docx_file_path # docx文件绝对路径 + self.tmp_img_dir = self.docx_file_path.split('.') + self.create_user = create_user + self.editor_mode = int(editor_mode) + + # 转存docx文件中的图片 + def convert_img(self,image): + with image.open() as image_bytes: + file_suffix = image.content_type.split("/")[1] + file_time_name = str(time.time()) + dir_name = upload_generation_dir() # 获取当月文件夹名称 + # 图片在媒体文件夹内的路径,形如 /202012/12542542.jpg + copy2_filename = dir_name + '/' + file_time_name + '.' + file_suffix + # 文件的绝对路径 形如/home/MrDoc/media/202012/12542542.jpg + new_media_file_path = settings.MEDIA_ROOT + copy2_filename + # 图片文件的相对url路径 + new_media_filename = '/media' + copy2_filename + + # 图片数据写入数据库 + Image.objects.create( + user=self.create_user, + file_path=new_media_filename, + file_name=file_time_name + '.' + file_suffix, + remark='本地上传', + ) + with open(new_media_file_path, 'wb') as f: + f.write(image_bytes.read()) + return {"src": new_media_filename} + + # 转换docx文件内容为HTML和Markdown + def convert_docx(self): + # 读取Word文件 + with open(self.docx_file_path, "rb") as docx_file: + # 转化Word文档为HTML + result = mammoth.convert_to_html(docx_file, convert_image=mammoth.images.img_element(self.convert_img)) + # 获取HTML内容 + html = result.value + if self.editor_mode in [1,2]: + # 转化HTML为Markdown + md = markdownify(html, heading_style="ATX") + return md + else: + return html + + def run(self): + try: + result = self.convert_docx() + os.remove(self.docx_file_path) + return {'status':True,'data':result} + except: + os.remove(self.docx_file_path) + return {'status':False,'data':'读取异常'} + if __name__ == '__main__': imp = ImportZipProject() imp.read_zip(r"D:\Python XlsxWriter模块中文文档_2020-06-16.zip") \ No newline at end of file diff --git a/app_doc/urls.py b/app_doc/urls.py index 72db248..848dc9d 100644 --- a/app_doc/urls.py +++ b/app_doc/urls.py @@ -35,6 +35,7 @@ urlpatterns = [ path('doc_recycle/', views.doc_recycle,name='doc_recycle'), # 文档回收站 path('fast_pub_doc/',views.fast_publish_doc,name='fast_pub_doc'), # 一键发布文档 path('download_doc_md//',views.download_doc_md,name='download_doc_md'), # 下载文档Markdown文件 + path('import/doc_docx/',views_import.import_doc_docx,name="import_doc_docx"), # 导入docx文档 #################文档分享相关 path('share_doc/', views.share_doc, name='share_doc'), # 私密文档分享 path('share_doc_check/', views.share_doc_check, name='share_doc_check'), # 私密文档验证 diff --git a/app_doc/views.py b/app_doc/views.py index 3fbae60..9173bee 100644 --- a/app_doc/views.py +++ b/app_doc/views.py @@ -315,9 +315,14 @@ def project_index(request,pro_id): new_docs = Doc.objects.filter(top_doc=pro_id,status=1).order_by('-modify_time')[:5] # markdown文本生成摘要(不带markdown标记) remove_markdown_tag(new_docs) + # 获取文集的文档目录 toc_list,toc_cnt = get_pro_toc(pro_id) # toc_list,toc_cnt = ([],1000) + + # 获取文集的协作成员 + colla_user_list = ProjectCollaborator.objects.filter(project=project) + # 获取文集的协作用户信息 if request.user.is_authenticated: # 对登陆用户查询其协作文档信息 colla_user = ProjectCollaborator.objects.filter(project=project,user=request.user).count() @@ -922,11 +927,11 @@ def create_doc(request): status = request.POST.get('status',1) # 文档状态 open_children = request.POST.get('open_children', False) # 展示下级目录 show_children = request.POST.get('show_children', False) # 展示下级目录 - if open_children == 'true': + if open_children == 'on': open_children = True else: open_children = False - if show_children == 'true': + if show_children == 'on': show_children = True else: show_children = False @@ -1028,11 +1033,11 @@ def modify_doc(request,doc_id): status = request.POST.get('status',1) # 文档状态 open_children = request.POST.get('open_children',False) # 展示下级目录 show_children = request.POST.get('show_children', False) # 展示下级目录 - if open_children == 'true': + if open_children == 'on': open_children = True else: open_children = False - if show_children == 'true': + if show_children == 'on': show_children = True else: show_children = False diff --git a/app_doc/views_import.py b/app_doc/views_import.py index 61cbf90..371169b 100644 --- a/app_doc/views_import.py +++ b/app_doc/views_import.py @@ -10,6 +10,7 @@ from django.http.response import JsonResponse,Http404,HttpResponseNotAllowed,Htt from django.http import HttpResponseForbidden from django.contrib.auth.decorators import login_required # 登录需求装饰器 from django.views.decorators.http import require_http_methods,require_GET,require_POST # 视图请求方法装饰器 +from django.views.decorators.csrf import csrf_exempt from django.core.paginator import Paginator,PageNotAnInteger,EmptyPage,InvalidPage # 后端分页 from django.core.exceptions import PermissionDenied,ObjectDoesNotExist from app_doc.models import Project,Doc,DocTemp @@ -116,3 +117,45 @@ def project_doc_sort(request): Doc.objects.filter(id=c2['id']).update(sort=n2,parent_doc=c1['id'],status=doc_status) return JsonResponse({'status':True,'data':'ok'}) + + +# 导入docx文档 +@login_required() +@csrf_exempt +@require_POST +def import_doc_docx(request): + file_type = request.POST.get('type', None) + editor_mode = request.POST.get('editor_mode',1) + # 上传Zip压缩文件 + if file_type == 'docx': + import_file = request.FILES.get('import_doc_docx', None) + if import_file: + file_name = import_file.name + # 限制文件大小在50mb以内 + if import_file.size > 52428800: + return JsonResponse({'status': False, 'data': '文件大小超出限制'}) + # 限制文件格式为.zip + if file_name.endswith('.docx'): + if os.path.exists(os.path.join(settings.MEDIA_ROOT, 'import_temp')) is False: + os.mkdir(os.path.join(settings.MEDIA_ROOT, 'import_temp')) + + temp_file_name = str(time.time()) + '.docx' + temp_file_path = os.path.join(settings.MEDIA_ROOT, 'import_temp/' + temp_file_name) + with open(temp_file_path, 'wb+') as docx_file: + for chunk in import_file: + docx_file.write(chunk) + if os.path.exists(temp_file_path): + import_file = ImportDocxDoc( + docx_file_path=temp_file_path, + editor_mode=editor_mode, + create_user=request.user + ).run() + return JsonResponse(import_file) + else: + return JsonResponse({'status': False, 'data': '上传失败'}) + else: + return JsonResponse({'status': False, 'data': '仅支持.docx格式'}) + else: + return JsonResponse({'status': False, 'data': '无有效文件'}) + else: + return JsonResponse({'status': False, 'data': '参数错误'}) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 3571811..0323a64 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,4 +9,6 @@ requests==2.24.0 whoosh==2.7.4 django-haystack==3.0 Markdown==3.3.3 -jieba==0.42.1 \ No newline at end of file +jieba==0.42.1 +mammoth==1.4.13 +markdownify==0.6.0 \ No newline at end of file diff --git a/static/mrdoc/mrdoc.css b/static/mrdoc/mrdoc.css index 84d9722..562bed9 100644 --- a/static/mrdoc/mrdoc.css +++ b/static/mrdoc/mrdoc.css @@ -520,6 +520,9 @@ input#doc-name,input#doctemp-name{ display: none; } +.doc-summary .layui-nav{ + padding: 0; +} .mrdoc-import-doc-list{ margin-bottom: 10px; } @@ -529,7 +532,7 @@ input#doc-name,input#doctemp-name{ border-radius:2px; box-shadow:0 2px 4px rgba(0,0,0,.12); box-sizing:border-box; - margin-right: 5px; + margin-right: 2px; padding-right: 5px; } .mrdoc-import-doc-item a{ @@ -557,6 +560,21 @@ input#doc-name,input#doctemp-name{ background-color: #f2f2f2 !important; color: #333 !important; } +.mrdoc-import-doc-child .layui-form-checkbox[lay-skin=primary]{ + margin-left: 10px; + padding-left: 20px; + +} +.mrdoc-import-doc-child .layui-form-checkbox[lay-skin=primary] i{ + width: 14px; + height: 14px; + line-height: 14px; +} +.layui-form-checked[lay-skin=primary] i { + border-color: #2176ff!important; + background-color: #2176ff; + color: #fff; +} .layui-nav-bar{ height: 0px !important; } diff --git a/static/mrdoc/mrdoc.editor.js b/static/mrdoc/mrdoc.editor.js index ae568f4..e66273a 100644 --- a/static/mrdoc/mrdoc.editor.js +++ b/static/mrdoc/mrdoc.editor.js @@ -530,6 +530,51 @@ upload_attach.render({ field:'attachment_upload', }) +// 按钮上传docx文档 +var upload_docx_doc = layui.upload; +upload_docx_doc.render({ + elem:"#import-doc-docx", + url:"/import/doc_docx/", + data:{'type':'docx','editor_mode':editor_mode}, + before: function(obj){ //obj参数包含的信息,跟 choose回调完全一致,可参见上文。 + layer.load(1); //上传loading + }, + accept: 'file', //允许上传的文件类型 + exts:'docx', + field:'import_doc_docx', + done: function(res, index, upload){ //上传后的回调 + //上传成功,刷新页面 + if(res.status){ + if(editor_mode == 3){ + editor.addValue(res.data) + }else if(editor_mode == 1){ + editor.insertValue(res.data); + }else if(editor_mode == 2){ + editor.setValue(res.data); + } + layer.closeAll(); + layer.msg("导入成功"); + }else{ + layer.closeAll('loading'); + layer.msg(res.data) + } + }, + error:function(){ + layer.closeAll('loading'); //关闭loading + layer.msg("系统异常,请稍后再试!") + }, +}); + +$("#doc-tag-set").click(function(){ + layer.open({ + type:1, + title:"文档标签设置", + content:$("#doc-tag-div"), + area:['300px'], + btn:['确定'] + }) +}); + // 粘贴表格文本框侦听paste粘贴事件 // 列宽的函数 function columnWidth(rows, columnIndex) { diff --git a/template/app_doc/editor/create_doc.html b/template/app_doc/editor/create_doc.html index f769f57..09c501a 100644 --- a/template/app_doc/editor/create_doc.html +++ b/template/app_doc/editor/create_doc.html @@ -18,33 +18,51 @@ {% block left_opera %}
-
-
- - - - -
-
- -
- -
-
+
+ +
- @@ -96,71 +114,18 @@

- -
-
-

标签

-
-
- -
-
-
-
- - -
-
-

下级目录

-
-
- {%if doc.open_children %} - - - {% else %} - - - {% endif %} -
-
-
-
- - -
-
-

文档内显示下级文档

-
-
- {%if doc.show_children %} - - - {% else %} - - - {% endif %} -
-
-
-
- -

发布

-
-
-
- -
-
- -
+
+
+ +
@@ -321,8 +286,8 @@ 'sort':$("#sort").val(), 'editor_mode':'{{editor_mode}}', 'status':status, - 'open_children':$('input:radio[name="open-children"]:checked').val(), - 'show_children':$('input:radio[name="show-children"]:checked').val(), + 'open_children':$('input:checkbox[name="open-children"]:checked').val(), + 'show_children':$('input:checkbox[name="show-children"]:checked').val(), } // console.log(data) if(data.doc_name == ""){ diff --git a/template/app_doc/editor/modify_doc.html b/template/app_doc/editor/modify_doc.html index 6be1454..60b3f9c 100644 --- a/template/app_doc/editor/modify_doc.html +++ b/template/app_doc/editor/modify_doc.html @@ -40,42 +40,54 @@ {% endif %}
-
- - 查看 - - - -
-
- -
- -
-
- -
- - -
+
+ + 文本文件
+
+ + 文档缓存
+
+ + Word文档(.docx)
+ + +
  • + 编辑器 +
    +
    EditorMD{% if editor_mode == 1 %}{% endif %}
    +
    Vditor{% if editor_mode == 2 %}{% endif %}
    +
    iceEditor{% if editor_mode == 3 %}{% endif %}
    +
    +
  • +
  • + 其他配置 +
    +
    + + 文档标签
    +
    +
    +
    +
  • + +
    @@ -106,54 +118,6 @@

    - -
    -
    -

    标签

    -
    -
    - -
    -
    -
    -
    - - -
    -
    -

    下级目录

    -
    -
    - {%if doc.open_children %} - - - {% else %} - - - {% endif %} -
    -
    -
    -
    - - -
    -
    -

    文档内显示下级文档

    -
    -
    - {%if doc.show_children %} - - - {% else %} - - - {% endif %} -
    -
    -
    -
    -
    @@ -162,13 +126,13 @@
    @@ -318,8 +282,8 @@ 'sort':$("#sort").val(), 'editor_mode':'{{editor_mode}}', 'status':status, - 'open_children':$('input:radio[name="open-children"]:checked').val(), - 'show_children':$('input:radio[name="show-children"]:checked').val(), + 'open_children':$('input:checkbox[name="open-children"]:checked').val(), + 'show_children':$('input:checkbox[name="show-children"]:checked').val(), } $.post("{% url 'modify_doc' doc_id=doc.id %}",data,function(r){ layer.closeAll("loading"); diff --git a/template/app_doc/editor/tpl_editor_div_doc.html b/template/app_doc/editor/tpl_editor_div_doc.html index 43985e9..339824c 100644 --- a/template/app_doc/editor/tpl_editor_div_doc.html +++ b/template/app_doc/editor/tpl_editor_div_doc.html @@ -129,4 +129,11 @@
    警告:MrDoc仅支持3级目录显示,若移动的下级文档层级在目标文集中超过3层将不会显示在文集目录中
    +
    + + + \ No newline at end of file diff --git a/template/app_doc/manage/manage_project_collaborator.html b/template/app_doc/manage/manage_project_collaborator.html index b1017c9..dc182ad 100644 --- a/template/app_doc/manage/manage_project_collaborator.html +++ b/template/app_doc/manage/manage_project_collaborator.html @@ -203,7 +203,7 @@ layer.open({ type:1, title:'修改'+ user + '的协作权限', - area:'300px;', + area:'400px;', id:'modifyProColla',//配置ID content:$('#modify-pro-colla-layer'), btn:['确定','取消'], //添加按钮 @@ -273,10 +273,10 @@ {% endfor %}
    -
    - - -
    + + + +
    @@ -284,10 +284,10 @@ diff --git a/template/app_doc/project.html b/template/app_doc/project.html index b9bbdcc..0729ffa 100644 --- a/template/app_doc/project.html +++ b/template/app_doc/project.html @@ -22,10 +22,10 @@ {% block content_head %}

    {{ project.name }}


    -

    + {% endblock %} {% block page_content %} @@ -132,6 +132,14 @@ {% endblock %} {% block doc_bottom_block %} + + + {% if project.create_user.first_name != '' %} {{project.create_user.first_name}} {% else %} {{project.create_user.username}}{% endif %} + {% for colla in colla_user_list %} + ,{% if colla.user.first_name != '' %} {{colla.user.first_name}} {% else %} {{colla.user.username}}{% endif %} + {% endfor %} + +