diff --git a/CHANGES.md b/CHANGES.md index 7e60ecf..5734223 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,15 @@ ## 版本更新记录 -### 2019-12-01 +### v0.2.3 2019-12-08 + +- 优化Markdown内容目录样式; +- 修复文档修改时间不生效的Bug; +- 添加文档左侧目录可展开收缩功能,默认只显示第一级目录; +- 调整文集页显示逻辑,默认显示文集简介和文集目录大纲; +- 添加用户后台的文集导出功能,支持导出文集的Markdown文件压缩包; + + +### v0.2.2 2019-12-01 - 添加MrDoc全站favicon图标 - 添加后台配置关闭注册功能、启用邮箱功能、统计代码、广告代码等功能 diff --git a/README.md b/README.md index 9221f9f..722bb6c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # MrDoc - 一个简单的文档写作应用 -![Mrdoc首页](./docs/mrdoc_index.png) +![Mrdoc首页](./docs/mrdoc_2019080101.gif) ## 介绍 一个简单的MarkDown文档写作系统。 @@ -35,7 +35,61 @@ Markdown科学公式:Katex.js ## 安装教程 -详见MrDoc使用文档:http://mrdoc.zmister.com +### 1、安装依赖库 +``` +pip install -r requirements.txt +``` + +### 2、配置数据库信息 +默认情况下,MrDoc使用Django的SQLite数据库,如果你使用的是MrDoc源码附带的Sqlite数据库,则无需另外配置数据库。 +如果有配置其他数据库的需求,请在/MrDoc/MrDoc目录下打开settings.py文件,在约80行的位置,将如下代码: +``` +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + } +} +``` +按照自己数据库的信息,将其修改如下格式,下面以MySQL为例: +``` +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.mysql', # 使用的数据库后端 + 'NAME': 'mrdoc', # 数据库名 + 'USER':'root', # 数据库用户 + 'PASSWORD':'123456789', # 数据库用户密码 + 'HOST':'', # 数据库主机地址 + 'PORT':'3306', # 数据库端口 + } +} +``` +### 3、初始化数据库 +在安装完所需的第三方库并配置好数据库信息之后,我们需要对数据库进行初始化。 + +在项目路径下打开命令行界面,运行如下命令生成数据库迁移: +``` +python manage.py makemigrations +``` +接着,运行如下命令执行数据库迁移: +``` +python manage.py migrate +``` +执行完毕之后,数据库就初始化完成了。 + +### 4、创建管理员账户 +在初始化完数据库之后,需要创建一个管理员账户来管理整个MrDoc,在项目路径下打开命令行终端,运行如下命令: +``` +python manage.py createsuperuser +``` +按照提示输入用户名、电子邮箱地址和密码即可。 +### 5、测试运行 +在完成上述步骤之后,即可运行使用MrDoc。 + +在测试环境中,可以使用Django自带的服务器运行MrDoc,其命令为: +``` +python manage.py runserver +``` ## 使用说明 @@ -61,7 +115,7 @@ Markdown科学公式:Katex.js ## 版本更新 -关注州的先生微信公众号(ID:zmister2016)、博客 https://zmister.com,及时获取MrDoc版本更新信息。 +关注州的先生微信公众号(ID:zmister2016)、博客 https://zmister.com,及时获取MrDoc版本更新信息。 ## 更多截图 diff --git a/app_doc/report_utils.py b/app_doc/report_utils.py new file mode 100644 index 0000000..09542e5 --- /dev/null +++ b/app_doc/report_utils.py @@ -0,0 +1,122 @@ +# coding:utf-8 +# @文件: report_utils.py +# @创建者:州的先生 +# #日期:2019/12/7 +# 博客地址:zmister.com +# MrDoc文集文档导出相关功能代码 +from django.conf import settings +from app_doc.models import * +import subprocess +import datetime +import re +import os +import shutil + +# 导出MD文件压缩包 +class ReportMD(): + def __init__(self,project_id): + # 查询文集信息 + self.pro_id = project_id + project_data = Project.objects.get(pk=project_id) + + # 文集名称 + self.project_name = "{0}_{1}_{2}".format( + project_data.create_user, + project_data.name, + str(datetime.date.today()) + ) + + # 判断MD导出临时文件夹是否存在 + if os.path.exists(settings.MEDIA_ROOT + "/reportmd_temp") is False: + os.mkdir(settings.MEDIA_ROOT + "/reportmd_temp") + + # 判断文集名称文件夹是否存在 + self.project_path = settings.MEDIA_ROOT + "/reportmd_temp/{}".format(self.project_name) + is_fold = os.path.exists(self.project_path) + if is_fold is False: + os.mkdir(self.project_path) + + # 判断是否存在静态文件文件夹 + self.media_path = settings.MEDIA_ROOT + "/reportmd_temp/{}/media".format(self.project_name) + is_media = os.path.exists(self.media_path) + if is_media is False: + os.mkdir(self.media_path) + + def work(self): + # 读取指定文集的文档数据 + data = Doc.objects.filter(top_doc=self.pro_id, parent_doc=0).order_by("sort") + # 遍历文档 + for d in data: + md_name = d.name + md_content = d.pre_content + md_content = self.operat_md_media(md_content) + + # 新建MD文件 + with open('{}/{}.md'.format(self.project_path,md_name),'w',encoding='utf-8') as files: + files.write(md_content) + + # 查询二级文档 + data_2 = Doc.objects.filter(parent_doc=d.id).order_by("sort") + for d2 in data_2: + md_name_2 = d2.name + md_content_2 = d2.pre_content + + md_content_2 = self.operat_md_media(md_content_2) + + # 新建MD文件 + with open('{}/{}.md'.format(self.project_path, md_name_2), 'w', encoding='utf-8') as files: + files.write(md_content_2) + + # 获取第三级文档 + data_3 = Doc.objects.filter(parent_doc=d2.id).order_by("sort") + for d3 in data_3: + md_name_3 = d3.name + md_content_3 = d3.pre_content + + md_content_3 = self.operat_md_media(md_content_3) + + # 新建MD文件 + with open('{}/{}.md'.format(self.project_path, md_name_3), 'w', encoding='utf-8') as files: + files.write(md_content_3) + + # 压缩文件 + shutil.make_archive(self.project_path,'zip',self.project_path) + # 删除文件夹 + shutil.rmtree(self.project_path) + + return "{}.zip".format(self.project_path) + + # 处理MD内容中的静态文件 + def operat_md_media(self,md_content): + # 查找MD内容中的静态文件 + pattern = r"\!\[.*?\]\(.*?\)" + media_list = re.findall(pattern, md_content) + # print(media_list) + # 存在静态文件,进行遍历 + if len(media_list) > 0: + for media in media_list: + media_filename = media.split("(")[-1].split(")")[0] # 媒体文件的文件名 + # 对本地静态文件进行复制 + if media_filename.startswith("/"): + sub_folder = "/" + media_filename.split("/")[3] # 获取子文件夹的名称 + is_sub_folder = os.path.exists(self.media_path+sub_folder) + # 创建子文件夹 + if is_sub_folder is False: + os.mkdir(self.media_path+sub_folder) + # 替换MD内容的静态文件链接 + md_content = md_content.replace(media_filename, "." + media_filename) + # 复制静态文件到指定文件夹 + shutil.copy(settings.BASE_DIR + media_filename, self.media_path+sub_folder) + # 不存在本地静态文件,直接返回MD内容 + # else: + # print("没有本地静态文件") + return md_content + # 不存在静态文件,直接返回MD内容 + else: + return md_content + +if __name__ == '__main__': + app = ReportMD( + project_id=7 + ) + app.work() diff --git a/app_doc/urls.py b/app_doc/urls.py index 1456fed..03a1e74 100644 --- a/app_doc/urls.py +++ b/app_doc/urls.py @@ -10,6 +10,7 @@ urlpatterns = [ path('modify_pro/',views.modify_project,name='modify_project'), # 修改文集 path('manage_project',views.manage_project,name="manage_project"), # 管理文集 path('del_project/',views.del_project,name='del_project'), # 删除文集 + path('report_project_md/',views.report_md,name='report_md'), # 导出文集MD文件 #################文档相关 path('project///', views.doc, name='doc'), # 文档浏览页 path('create_doc/', views.create_doc, name="create_doc"), # 新建文档 diff --git a/app_doc/views.py b/app_doc/views.py index d2103c7..7cd1835 100644 --- a/app_doc/views.py +++ b/app_doc/views.py @@ -5,6 +5,9 @@ from django.core.paginator import Paginator,PageNotAnInteger,EmptyPage,InvalidPa from app_doc.models import Project,Doc,DocTemp from django.contrib.auth.models import User from django.db.models import Q +import datetime +import traceback +from app_doc.report_utils import * # 文集列表 @@ -36,7 +39,7 @@ def create_project(request): return JsonResponse({'status':False}) -# 文集浏览页 +# 文集页 def project_index(request,pro_id): # 获取文集 if request.method == 'GET': @@ -45,19 +48,16 @@ def project_index(request,pro_id): project = Project.objects.get(id=int(pro_id)) # 获取搜索词 kw = request.GET.get('kw','') - if kw == '': - doc = Doc.objects.filter(top_doc=int(pro_id)).order_by('sort') - # 获取文集第一篇文档作为默认内容 - if doc.count() > 0: - doc = doc[0] - else: - doc = None - else: # 搜索结果 - search_result = Doc.objects.filter(top_doc=int(pro_id),pre_content__icontains=kw) # 获取文集下所有一级文档 project_docs = Doc.objects.filter(top_doc=int(pro_id), parent_doc=0).order_by('sort') - return render(request,'app_doc/project.html',locals()) + if kw != '': + search_result = Doc.objects.filter(top_doc=int(pro_id),pre_content__icontains=kw) + # if search_result.count() == 0: + # search_result = {'count':0} + return render(request,'app_doc/project_doc_search.html',locals()) + return render(request, 'app_doc/project.html', locals()) except Exception as e: + print(traceback.print_exc()) return HttpResponse('请求出错') else: return HttpResponse('方法不允许') @@ -152,7 +152,7 @@ def doc(request,pro_id,doc_id): doc = Doc.objects.get(id=int(doc_id)) # 获取文集下一级文档 project_docs = Doc.objects.filter(top_doc=doc.top_doc, parent_doc=0).order_by('sort') - return render(request,'app_doc/project.html',locals()) + return render(request,'app_doc/doc.html',locals()) else: return HttpResponse('参数错误') except Exception as e: @@ -233,6 +233,7 @@ def modify_doc(request,doc_id): pre_content=pre_content, parent_doc=int(parent_doc) if parent_doc != '' else 0, sort=sort if sort != '' else 99, + modify_time = datetime.datetime.now() ) return JsonResponse({'status': True,'data':'修改成功'}) else: @@ -448,3 +449,28 @@ def get_pro_doc(request): # 404页面 def handle_404(request): return render(request,'404.html') + + +# 导出文集MD文件 +@login_required() +def report_md(request): + if request.method == 'POST': + pro_id = request.POST.get('project_id','') + user = request.user + try: + project = Project.objects.get(id=int(pro_id)) + if project.create_user == user: + project_md = ReportMD( + project_id=int(pro_id) + ) + md_file_path = project_md.work() # 生成并获取MD文件压缩包绝对路径 + md_file_filename = os.path.split(md_file_path)[-1] # 提取文件名 + md_file = "/media/reportmd_temp/"+ md_file_filename # 拼接相对链接 + return JsonResponse({'status':True,'data':md_file}) + else: + return JsonResponse({'status':False,'data':'无权限'}) + except: + return JsonResponse({'status':False,'data':'文集不存在'}) + + else: + return Http404 \ No newline at end of file diff --git a/db.sqlite3 b/db.sqlite3 deleted file mode 100644 index 7f2564d..0000000 Binary files a/db.sqlite3 and /dev/null differ diff --git a/docs/mrdoc_2019080101.gif b/docs/mrdoc_2019080101.gif new file mode 100644 index 0000000..7ca0812 Binary files /dev/null and b/docs/mrdoc_2019080101.gif differ diff --git a/static/mrdoc.css b/static/mrdoc.css index 343b374..3df595a 100644 --- a/static/mrdoc.css +++ b/static/mrdoc.css @@ -250,7 +250,7 @@ body, html { .doc-summary ul.summary li a:hover,.bq a:hover{ text-decoration: underline; } -li.active > a{ +li.active > a,li.active > div > a{ color: #008cff; } .bq a { @@ -261,6 +261,7 @@ li.active > a{ border-top: 1px solid #dcdcdc; margin-top: 10px; font-size: 12px; + text-align:center; } .doc-header { font-family: helvetica neue,Helvetica,Arial,sans-serif; @@ -353,4 +354,12 @@ li.active > a{ /* 广告样式 */ .ad-code{ margin:10px; +} + +/* 左边侧栏目录样式 */ +.toc-open{ + display:block; +} +.toc-close{ + display:none; } \ No newline at end of file diff --git a/template/app_admin/admin_base.html b/template/app_admin/admin_base.html index cb160d7..3849acb 100644 --- a/template/app_admin/admin_base.html +++ b/template/app_admin/admin_base.html @@ -31,6 +31,7 @@
+
个人中心
退出登录
@@ -54,7 +55,7 @@ 用户管理
  • - 应用设置 + 应用设置
  • {% endif %} diff --git a/template/app_admin/admin_project.html b/template/app_admin/admin_project.html index 8b23b8d..c2c0006 100644 --- a/template/app_admin/admin_project.html +++ b/template/app_admin/admin_project.html @@ -43,9 +43,9 @@ {% for pro in projects %} {{ pro.name }} - {{ pro.intro }} - {% load project_filter %} - {{ pro.id | get_doc_count }} + {{ pro.intro }} + {% load project_filter %} + {{ pro.id | get_doc_count }} {{ pro.create_time }} {{ pro.create_user }} diff --git a/template/app_admin/admin_setting.html b/template/app_admin/admin_setting.html index a02504d..52c6d9a 100644 --- a/template/app_admin/admin_setting.html +++ b/template/app_admin/admin_setting.html @@ -57,6 +57,7 @@ +
    diff --git a/template/app_doc/create_base.html b/template/app_doc/create_base.html index f8722ea..abfc43f 100644 --- a/template/app_doc/create_base.html +++ b/template/app_doc/create_base.html @@ -85,7 +85,13 @@ } } }); - + //未保存离开提示 + window.onbeforeunload =function() { +    // code... + //return null; + console.log("xx") + return 1 + } {% block custom_script %} diff --git a/template/app_doc/doc.html b/template/app_doc/doc.html new file mode 100644 index 0000000..21df6af --- /dev/null +++ b/template/app_doc/doc.html @@ -0,0 +1,345 @@ +{% load staticfiles %} + + + + + {{ doc.name }} - {{ project.name }} - MrDoc + + + + + + + + + + +
    + +
    + +
    + +
    + + +
    + {% load doc_filter %} + + +
    + + + +
    + + + +
    +
    + +
    + +
    + + {% if doc %} +

    {{ doc.name }}

    +
    +

    + 发表:{{ doc.create_time }} +      最后修改:{{ doc.modify_time }} + +

    + {% else %} +

    共有{{ search_result.count }}个搜索结果

    +
    + {% endif %} + + + {% if ad_code %} +
    + {{ ad_code | safe }} +
    + {% endif %} + +
    + + +
    + {% if doc %} +{# {{ doc.content | safe }}#} + + + {% else %} + {% for result in search_result %} +
    +

    {{ result.name }}

    +

    {{ result.pre_content|truncatechars:300 }}

    +
    + {% endfor %} + {% endif %} +
    + +
    + +
    +
    +
    + + +
    + +
    +
    + +
    +
    + + + + + + + + + + + + + +{% block custom_script %} + + + + + + + + + + + + + + {{ static | safe }} + +{% endblock %} + + \ No newline at end of file diff --git a/template/app_doc/manage_base.html b/template/app_doc/manage_base.html index 968ff59..914401b 100644 --- a/template/app_doc/manage_base.html +++ b/template/app_doc/manage_base.html @@ -34,7 +34,9 @@ {{request.user.username}}
    - + {% if request.user.is_superuser %} +
    后台管理
    + {% endif %}
    退出登录
    diff --git a/template/app_doc/manage_project.html b/template/app_doc/manage_project.html index edfc237..4701b83 100644 --- a/template/app_doc/manage_project.html +++ b/template/app_doc/manage_project.html @@ -24,13 +24,15 @@ + - + 文集名称 - 文集简介 + 文集简介 + 文档数量 创建时间 操作 @@ -38,13 +40,16 @@ {% for pro in pros %} - {{ pro.name }} - {{ pro.intro }} + {{ pro.name }} + {{ pro.intro }} + {% load project_filter %} + {{ pro.id | get_doc_count }} {{ pro.create_time }} - 查看 + 修改 删除 + 导出 {% endfor %} @@ -157,5 +162,48 @@ }, }); } + //导出MD + reportMd = function(pro_id){ + layer.open({ + type:1, + title:"导出文集", + area:"300px", + id:"reportMd", + content:'
    将此文集下所有文档导出为MD文件并打包?
    ', + btn:['确定','取消'], //添加按钮 + btnAlign:'c', //按钮居中 + yes:function (index,layero) { + var load = layer.load() + data = { + 'project_id':pro_id, + } + $.post("{% url 'report_md' %}",data,function(r){ + if(r.status){ + //导出成功 + layer.close(index) + //文件下载提示 + downloadMd(r.data) + }else{ + //导出失败,提示 + console.log(r) + layer.msg(r.data) + } + layer.close(load) + }) + }, + }) + } + //下载文件弹出框 + downloadMd = function(download_link){ + layer.open({ + type:1, + title:"下载导出文档", + area:"300px", + id:"downloadMd", + content:'

    请尽快下载,避免失效!


    点击下载文件(zip)', + //btn:['确定','取消'], //添加按钮 + //btnAlign:'c', //按钮居中 + }) + }; {% endblock %} \ No newline at end of file diff --git a/template/app_doc/project.html b/template/app_doc/project.html index 47ab616..36fd04b 100644 --- a/template/app_doc/project.html +++ b/template/app_doc/project.html @@ -3,7 +3,7 @@ - {{ doc.name }} - {{ project.name }} - MrDoc + {{ project.name }} - MrDoc @@ -42,25 +42,35 @@ {% for docs in project_docs %}
  • - {{ docs.name }} - {% if docs.id|get_next_doc %} -{# #} - - {% endif %} + {% if docs.id|get_next_doc %} +
    + {{ docs.name }} + +
    + + {% else %} + {{ docs.name }} + {% endif %}
  • {% endfor %} @@ -109,17 +119,15 @@
    - {% if doc %} -

    {{ doc.name }}

    + +

    {{ project.name }}


    -

    - {{ doc.create_time }}     - {{ doc.create_user.username }} +

    + 发表:{{ project.create_time }} +      最后修改:{{ project.modify_time }} +

    - {% else %} -

    共有{{ search_result.count }}个搜索结果

    -
    - {% endif %} + {% if ad_code %}
    @@ -129,24 +137,46 @@
    +
    - {% if doc %} -{# {{ doc.content | safe }}#} - - - {% else %} - {% for result in search_result %} -
    -

    {{ result.name }}

    -

    {{ result.pre_content|truncatechars:300 }}

    -
    +
    +

    文集简介:

    +

    {{project.intro}}

    +
    +
    +

    文集大纲:

    +
      + + {% for docs in project_docs %} +
    • + {% if docs.id|get_next_doc %} + {{ docs.name }} + + {% else %} + {{ docs.name }} + {% endif %} +
    • {% endfor %} - {% endif %} +
    +
    @@ -183,28 +213,22 @@ $.ajaxSetup({ data: {csrfmiddlewaretoken: '{{ csrf_token }}' }, }); - //解析Markdown为HTML - editormd.markdownToHTML("content", { - htmlDecode : "style,script,iframe", - emoji : true, - taskList : true, - tex : true, // 科学公式 - flowChart : true, // 流程图 - sequenceDiagram : true, // 默认不解析 - tocm : true, //目录 - //tocContainer : "#toc-container" - }); - //为当前页面的目录链接添加样式 + //为当前页面的目录链接添加蓝色样式 $("nav li a").each(function (i) { var $me = $(this); var lochref = $.trim(window.location.href); var mehref = $.trim($me.get(0).href); if (lochref.indexOf(mehref) != -1) { //console.log($me,lochref,mehref) - $me.parent().addClass("active"); + $me.closest("li").addClass("active"); + //展开当前文档的上级目录 + $me.parent("li").parent('ul.sub-menu').toggleClass("toc-close toc-open"); //展开二级目录 + $me.parent("div").parent('li').parent('ul.sub-menu').toggleClass("toc-close toc-open"); //展开还有子级的二级目录 + $me.parent("li").parent('ul').parent('li').parent('ul.sub-menu').toggleClass("toc-close toc-open"); //展开三级目录 + $me.parents("ul.sub-menu").prevAll("div").children("i").toggleClass("fa-chevron-left fa-chevron-down");//切换图标 } else { //console.log(lochref,mehref) - $me.parent().removeClass("active"); + $me.closest("li").removeClass("active"); } }); @@ -282,7 +306,7 @@ var font_size = window.localStorage.getItem('font-size') console.log(font_size) if(parseFloat(font_size) < 1.4){ - size = parseFloat(font_size) + 0.2 + size = parseFloat(font_size) + 0.1 //$('.doc-info h1').css({'font-size':size+'rem'}) $('#content').css({'font-size':size+'rem'}) window.localStorage.setItem('font-size',size) @@ -307,6 +331,18 @@ } }; + + + {{ static | safe }} diff --git a/template/app_doc/project_doc_search.html b/template/app_doc/project_doc_search.html new file mode 100644 index 0000000..823f061 --- /dev/null +++ b/template/app_doc/project_doc_search.html @@ -0,0 +1,315 @@ +{% load staticfiles %} + + + + + 搜索{{kw}}结果 - {{ project.name }} - MrDoc + + + + + + + + + + +
    + +
    +
    +
    + +
    +
    + +
    + {% load doc_filter %} + + +
    + + + +
    + + + +
    +
    + +
    + +
    + +

    搜索{{kw}}共有{{ search_result.count }}个结果 - {{project.name}}

    +
    + + + {% if ad_code %} +
    + {{ ad_code | safe }} +
    + {% endif %} + +
    + + + +
    + {% for result in search_result %} +
    +

    {{ result.name }}

    +

    {{ result.pre_content|truncatechars:300 }}

    +
    + {% endfor %} +
    + +
    + +
    +
    +
    + + +
    + +
    +
    + +
    +
    + + + + + + + + + + + + + +{% block custom_script %} + + + + + + + + + + + + + + {{ static | safe }} + +{% endblock %} + + \ No newline at end of file