v0.5.5 更新内容详见CHANGES.MD

This commit is contained in:
yangjian 2020-07-20 21:38:29 +08:00
parent 052b717b07
commit 2a5e2846b7
22 changed files with 5127 additions and 116 deletions

View File

@ -1,10 +1,17 @@
## 版本更新记录
### v0.5.5
### v0.5.5 2020-07-20
- 禁用编辑器页面的列表目录和下拉目录语法解析
- Chrome扩展添加鼠标选择控制选项
- 优化文档样式
- 添加文集导入功能
- 优化文集目录加载速度
- 编辑器支持音视频外链和视频网站外链
- 编辑器添加字符统计功能
- 优化文档页面分享样式
- 优化文档编辑器排版布局
- 新增图片上传的URL链接插入
### v0.5.4

View File

@ -40,7 +40,7 @@ SECRET_KEY = '5&71mt9@^58zdg*_!t(x6g14q*@84d%ptr%%s6e0l50zs0we3d'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = CONFIG.getboolean('site','debug')
VERSIONS = '0.5.4'
VERSIONS = '0.5.5'
ALLOWED_HOSTS = ['*']

147
app_doc/import_utils.py Normal file
View File

@ -0,0 +1,147 @@
# coding:utf-8
# @文件: import_utils.py
# @创建者:州的先生
# #日期2020/6/17
# 博客地址zmister.com
# 文集导入相关方法
import shutil
import os
import time
import re
from app_doc.models import Doc,Project,Image
from app_doc.util_upload_img import upload_generation_dir
from django.db import transaction
from django.conf import settings
from loguru import logger
# 导入Zip文集
class ImportZipProject():
# 读取 Zip 压缩包
def read_zip(self,zip_file_path,create_user):
# 导入流程:
# 1、解压zip压缩包文件到temp文件夹
# 2、遍历temp文件夹内的解压后的.md文件
# 3、读取.md文件的文本内容
# 4、如果里面匹配到相对路径的静态文件从指定文件夹里面读取
# 5、上传图片写入数据库修改.md文件里面的url路径
# 新建一个临时文件夹,用于存放解压的文件
self.temp_dir = zip_file_path[:-3]
os.mkdir(self.temp_dir)
# 解压 zip 文件到指定临时文件夹
shutil.unpack_archive(zip_file_path, extract_dir=self.temp_dir)
# 处理文件夹和文件名的中文乱码
for root, dirs, files in os.walk(self.temp_dir):
for dir in dirs:
try:
new_dir = dir.encode('cp437').decode('gbk')
except:
new_dir = dir.encode('utf-8').decode('utf-8')
# print(new_dir)
os.rename(os.path.join(root, dir), os.path.join(root, new_dir))
for file in files:
try:
new_file = file.encode('cp437').decode('gbk')
except:
new_file = file.encode('utf-8').decode('utf-8')
# print(root, new_file)
os.rename(os.path.join(root, file), os.path.join(root, new_file))
# 开启事务
with transaction.atomic():
save_id = transaction.savepoint()
try:
# 新建文集
project = Project.objects.create(
name=zip_file_path[:-4].split('/')[-1],
intro='',
role=1,
create_user=create_user
)
# 遍历临时文件夹中的所有文件和文件夹
for f in os.listdir(self.temp_dir):
# 获取 .md 文件
if f.endswith('.md'):
# print(f)
# 读取 .md 文件文本内容
with open(os.path.join(self.temp_dir,f),'r',encoding='utf-8') as md_file:
md_content = md_file.read()
md_content = self.operat_md_media(md_content,create_user)
# 新建文档
doc = Doc.objects.create(
name = f[:-3],
pre_content = md_content,
top_doc = project.id,
status = 0,
create_user = create_user
)
except:
logger.exception("解析导入文件异常")
# 回滚事务
transaction.savepoint_rollback(save_id)
transaction.savepoint_commit(save_id)
try:
shutil.rmtree(self.temp_dir)
os.remove(zip_file_path)
return project.id
except:
logger.exception("删除临时文件异常")
return None
# 处理MD内容中的静态文件
def operat_md_media(self,md_content,create_user):
# 查找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("./"):
# 获取文件后缀
file_suffix = media_filename.split('.')[-1]
if file_suffix.lower() not in settings.ALLOWED_IMG:
continue
# 判断本地图片路径是否存在
temp_media_file_path = os.path.join(self.temp_dir,media_filename[2:])
if os.path.exists(temp_media_file_path):
# 如果存在,上传本地图片
print(media_filename)
dir_name = upload_generation_dir() # 获取当月文件夹名称
# 复制文件到媒体文件夹
copy2_filename = dir_name + '/' + str(time.time()) + '.' + file_suffix
new_media_file_path = shutil.copy2(
temp_media_file_path,
settings.MEDIA_ROOT + copy2_filename
)
# 替换MD内容的静态文件链接
new_media_filename = new_media_file_path.split(settings.MEDIA_ROOT,1)[-1]
new_media_filename = '/media' + new_media_filename
# 图片数据写入数据库
Image.objects.create(
user=create_user,
file_path=new_media_filename,
file_name=str(time.time())+'.'+file_suffix,
remark='本地上传',
)
md_content = md_content.replace(media_filename, new_media_filename)
else:
pass
return md_content
# 不存在静态文件直接返回MD内容
else:
return md_content
if __name__ == '__main__':
imp = ImportZipProject()
imp.read_zip(r"D:\Python XlsxWriter模块中文文档_2020-06-16.zip")

118
app_doc/import_views.py Normal file
View File

@ -0,0 +1,118 @@
# coding:utf-8
# @文件: import_views.py
# @创建者:州的先生
# #日期2020/6/17
# 博客地址zmister.com
# 文集导入相关视图函数
from django.shortcuts import render,redirect
from django.http.response import JsonResponse,Http404,HttpResponseNotAllowed,HttpResponse
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.core.paginator import Paginator,PageNotAnInteger,EmptyPage,InvalidPage # 后端分页
from django.core.exceptions import PermissionDenied,ObjectDoesNotExist
from app_doc.models import Project,Doc,DocTemp
from django.contrib.auth.models import User
from django.db.models import Q
from django.db import transaction
from loguru import logger
import datetime
import traceback
import re
from app_doc.report_utils import *
from app_admin.decorators import check_headers,allow_report_file
import os.path
import json
from app_doc.import_utils import *
# 导入文集
@login_required()
@require_http_methods(['GET','POST'])
def import_project(request):
if request.method == 'GET':
return render(request,'app_doc/manage_project_import.html',locals())
elif request.method == 'POST':
file_type = request.POST.get('type',None)
# 上传Zip压缩文件
if file_type == 'zip':
import_file = request.FILES.get('import_file',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('.zip'):
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())+'.zip'
temp_file_path = os.path.join(settings.MEDIA_ROOT,'import_temp/'+temp_file_name)
with open(temp_file_path,'wb+') as zip_file:
for chunk in import_file:
zip_file.write(chunk)
if os.path.exists(temp_file_path):
import_file = ImportZipProject()
project = import_file.read_zip(temp_file_path,request.user)
if project:
docs = Doc.objects.filter(top_doc=project).values_list('id','name')
doc_list = [doc for doc in docs]
return JsonResponse({'status':True,'data':doc_list,'id':project})
else:
return JsonResponse({'status':False,'data':'上传失败'})
else:
return JsonResponse({'status':False,'data':'上传失败'})
else:
return JsonResponse({'status':False,'data':'仅支持.zip格式'})
else:
return JsonResponse({'status':False,'data':'无有效文件'})
else:
return JsonResponse({'status':False,'data':'参数错误'})
# 文集文档排序
@login_required()
@require_http_methods(['POST'])
def project_doc_sort(request):
project_id = request.POST.get('pid',None) # 文集ID
title = request.POST.get('title',None) # 文集名称
desc = request.POST.get('desc',None) # 文集简介
role = request.POST.get('role',1) # 文集权限
sort_data = request.POST.get('sort_data','[]') # 文档排序列表
doc_status = request.POST.get('status',0) # 文档状态
# print(sort_data)
try:
sort_data = json.loads(sort_data)
except Exception:
return JsonResponse({'status':False,'data':'文档参数错误'})
try:
Project.objects.get(id=project_id,create_user=request.user)
except ObjectDoesNotExist:
return JsonResponse({'status':False,'data':'没有匹配的文集'})
# 修改文集信息
Project.objects.filter(id=project_id).update(
name = title,
intro = desc,
role = role
)
# 文档排序
n = 10
# 第一级文档
for data in sort_data:
Doc.objects.filter(id=data['id']).update(sort = n,status=doc_status)
n += 10
# 存在第二级文档
if 'children' in data.keys():
n1 = 10
for c1 in data['children']:
Doc.objects.filter(id=c1['id']).update(sort = n1,parent_doc=data['id'],status=doc_status)
n1 += 10
# 存在第三级文档
if 'children' in c1.keys():
n2 = 10
for c2 in c1['children']:
Doc.objects.filter(id=c2['id']).update(sort=n2,parent_doc=c1['id'],status=doc_status)
return JsonResponse({'status':True,'data':'ok'})

View File

@ -1,5 +1,5 @@
from django.urls import path,re_path
from app_doc import views,util_upload_img
from app_doc import views,util_upload_img,import_views
urlpatterns = [
path('',views.project_list,name='pro_list'),# 文档首页
@ -19,6 +19,8 @@ urlpatterns = [
path('check_viewcode/',views.check_viewcode,name='check_viewcode'),# 文集访问码验证
path('manage_project_colla/<int:pro_id>/',views.manage_project_collaborator,name="manage_pro_colla"), # 管理文集协作
path('manage_pro_colla_self/',views.manage_pro_colla_self,name="manage_pro_colla_self"), # 我协作的文集
path('manage_project_import/',import_views.import_project,name="import_project"), # 导入文集
path('manage_project_doc_sort/',import_views.project_doc_sort,name='project_doc_sort'), # 导入文集文档排序
#################文档相关
path('project-<int:pro_id>/doc-<int:doc_id>/', views.doc, name='doc'), # 文档浏览页
path('create_doc/', views.create_doc, name="create_doc"), # 新建文档

View File

@ -1298,47 +1298,52 @@ def get_pro_doc(request):
def get_pro_doc_tree(request):
pro_id = request.POST.get('pro_id', None)
if pro_id:
# 获取一级文档
# 查询存在上级文档的文档
parent_id_list = Doc.objects.filter(top_doc=pro_id,status=1).exclude(parent_doc=0).values_list('parent_doc',flat=True)
# 获取存在上级文档的上级文档ID
# print(parent_id_list)
doc_list = []
# 获取一级文档
top_docs = Doc.objects.filter(top_doc=pro_id,parent_doc=0,status=1).values('id','name').order_by('sort')
# 遍历一级文档
for doc in top_docs:
top_item = {
'id':doc['id'],
'field':doc['name'],
'title':doc['name'],
'href':'/project-{}/doc-{}/'.format(pro_id,doc['id']),
'spread':True,
'level':1
}
# 获取二级文档
sec_docs = Doc.objects.filter(top_doc=pro_id,parent_doc=doc['id'],status=1).values('id','name').order_by('sort')
if sec_docs.exists():# 二级文档
# 如果一级文档存在下级文档,查询其二级文档
if doc['id'] in parent_id_list:
# 获取二级文档
sec_docs = Doc.objects.filter(top_doc=pro_id,parent_doc=doc['id'],status=1).values('id','name').order_by('sort')
top_item['children'] = []
for doc in sec_docs:
sec_item = {
'id': doc['id'],
'field': doc['name'],
'title': doc['name'],
'href': '/project-{}/doc-{}/'.format(pro_id, doc['id']),
'level':2
}
# 获取三级文档
thr_docs = Doc.objects.filter(top_doc=pro_id,parent_doc=doc['id'],status=1).values('id','name').order_by('sort')
if thr_docs.exists():
# 如果二级文档存在下级文档,查询第三级文档
if doc['id'] in parent_id_list:
# 获取三级文档
thr_docs = Doc.objects.filter(top_doc=pro_id,parent_doc=doc['id'],status=1).values('id','name').order_by('sort')
sec_item['children'] = []
for doc in thr_docs:
item = {
'id': doc['id'],
'field': doc['name'],
'title': doc['name'],
'href': '/project-{}/doc-{}/'.format(pro_id, doc['id']),
'level': 3
}
sec_item['children'].append(item)
top_item['children'].append(sec_item)
top_item['children'].append(sec_item)
else:
top_item['children'].append(sec_item)
doc_list.append(top_item)
# 如果一级文档没有下级文档,直接保存
else:
doc_list.append(top_item)
return JsonResponse({'status':True,'data':doc_list})

View File

@ -3478,6 +3478,94 @@
var editormdLogoReg = regexs.editormdLogo;
var pageBreakReg = regexs.pageBreak;
// marked 解析图片
markedRenderer.image = function(href,title,text) {
var attr = "";
var begin = "";
var end = "";
// console.log(href,title,text)
if(/^=(.*?)/.test(text)){
console.log(text)
switch(text){
case '=video':
if(href.match(/^.+.(mp4|m4v|ogg|ogv|webm)$/)){
return "<video src='"+ href + "' controls='controls' preload width=500></video>"
}
break;
case '=audio':
if(href.match(/^.+.(mp3|wav|flac|m4a)$/)){
return "<audio src='"+ href + "' controls='controls'></audio>"
}
break;
case '=video_iframe':
const youtubeMatch = href.match(/\/\/(?:www\.)?(?:youtu\.be\/|youtube\.com\/(?:embed\/|v\/|watch\?v=|watch\?.+&v=))([\w|-]{11})(?:(?:[\?&]t=)(\S+))?/);
const youkuMatch = href.match(/\/\/v\.youku\.com\/v_show\/id_(\w+)=*\.html/);
const qqMatch = href.match(/\/\/v\.qq\.com\/x\/cover\/.*\/([^\/]+)\.html\??.*/);
const coubMatch = href.match(/(?:www\.|\/\/)coub\.com\/view\/(\w+)/);
const facebookMatch = href.match(/(?:www\.|\/\/)facebook\.com\/([^\/]+)\/videos\/([0-9]+)/);
const dailymotionMatch = href.match(/.+dailymotion.com\/(video|hub)\/(\w+)\?/);
const bilibiliMatch = href.match(/(?:www\.|\/\/)bilibili\.com\/video\/(\w+)/);
const tedMatch = href.match(/(?:www\.|\/\/)ted\.com\/talks\/(\w+)/);
if (youtubeMatch && youtubeMatch[1].length === 11) {
return `<iframe height=400 width=500 frameborder=0 allowfullscreen src="//www.youtube.com/embed/${youtubeMatch[1] + (youtubeMatch[2] ? "?start=" + youtubeMatch[2] : "")}">`
} else if (youkuMatch && youkuMatch[1]) {
return `<iframe height=400 width=500 frameborder=0 allowfullscreen src="//player.youku.com/embed/${youkuMatch[1]}">`
} else if (qqMatch && qqMatch[1]) {
return `<iframe height=400 width=500 frameborder=0 allowfullscreen src="https://v.qq.com/txp/iframe/player.html?vid=${qqMatch[1]}">`
} else if (coubMatch && coubMatch[1]) {
return `<iframe height=400 width=500 frameborder=0 allowfullscreen src="//coub.com/embed/${coubMatch[1]}?muted=false&autostart=false&originalSize=true&startWithHD=true">`
} else if (facebookMatch && facebookMatch[0]) {
return `<iframe height=400 width=500 frameborder=0 allowfullscreen src="https://www.facebook.com/plugins/video.php?href=${encodeURIComponent(facebookMatch[0])}">`
} else if (dailymotionMatch && dailymotionMatch[2]) {
return `<iframe height=400 width=500 frameborder=0 allowfullscreen src="https://www.dailymotion.com/embed/video/${dailymotionMatch[2]}">`
} else if (bilibiliMatch && bilibiliMatch[1]) {
return `<iframe height=400 width=500 frameborder=0 allowfullscreen src="//player.bilibili.com/player.html?bvid=${bilibiliMatch[1]}">`
} else if (tedMatch && tedMatch[1]) {
return `<iframe height=400 width=500 frameborder=0 allowfullscreen src="//embed.ted.com/talks/${tedMatch[1]}">`
}
// return '<iframe height=400 width=500 src="' + href +'" frameborder=0 allowfullscreen />'
}
}
if(href && href !== ""){
var a = document.createElement('a');
a.href = href;
var attrs = a.hash.match(/size=\d+x\d+/i);
if(attrs !== null) {
a.hash = a.hash.replace(attrs[0],"");
href = a.href;
attrs = attrs[0].replace("size=","").split("x");
if(attrs[0] > 0) {
attr += " width=\"" + attrs[0] + "\""
}
if(attrs[1] > 0) {
attr += " height=\"" + attrs[1] + "\""
}
}
attrs = a.hash.match(/align=(center|left|right)/i)
if (attrs !== null) {
var hash = a.hash.replace(attrs[0],"");
if (hash.indexOf("#&") === 0) {
hash = "#" + hash.substr(2);
}
a.hash = hash;
href = a.href;
attrs = attrs[0].replace("align=","");
end = "</p>";
if(attrs === "center"){
begin = '<p align="center">';
}else if (attrs === "left") {
begin = '<p align="left">';
}else if (attrs === "right") {
begin = '<p align="right">';
}
}
}
return begin + "<img src=\""+href+"\" title=\""+title+"\" alt=\""+text+"\" "+attr+">" + end;
};
// marked emoji 解析
markedRenderer.emoji = function(text) {

View File

@ -270,9 +270,12 @@ body, html {
.doc-summary ul.summary li a:hover,.bq a:hover{
text-decoration: underline;
}
/* 文档目前当前链接 */
li.active > a,li.active > div > a{
color: #5FB878;
font-weight:700;
}
.bq a {
padding: 15px;
border-bottom: none;
@ -402,6 +405,260 @@ li.active > a,li.active > div > a{
color:#e91e63 !important;
}
/* 自定义按钮样式 */
.mrdoc-btn-default:hover{
border-color:#1E9FFF;
color:#1E9FFF;
}
/* 文字悬浮提示样式 */
/* tooltip样式 */
[tooltip] {
position: relative;
}
[tooltip]::after {
display: none;
content: attr(tooltip);
position: absolute;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
padding: 8px 15px;
max-width: 200px;
border-radius: 4px;
box-shadow: 0 10px 20px -5px rgba(0, 0, 0, 0.4);
z-index: 100;
}
[tooltip]::before {
display: none;
content: '';
position: absolute;
border: 5px solid transparent;
border-bottom-width: 0;
z-index: 100;
}
[tooltip]:hover::after {
display: block;
}
[tooltip]:hover::before {
display: block;
}
[tooltip][placement^="top"]::after, [tooltip][placement^="top"]::before {
animation: anime-top 300ms ease-out forwards;
}
[tooltip][placement^="right"]::after, [tooltip][placement^="right"]::before {
animation: anime-right 300ms ease-out forwards;
}
[tooltip][placement^="bottom"]::after, [tooltip][placement^="bottom"]::before {
animation: anime-bottom 300ms ease-out forwards;
}
[tooltip][placement^="left"]::after, [tooltip][placement^="left"]::before {
animation: anime-left 300ms ease-out forwards;
}
/* 气泡主题 */
.tooltip-theme-dark, [tooltip]::after {
color: #fff;
background-color: #313131;
}
.tooltip-theme-light, [tooltip][effect="light"]::after {
color: #313131;
background-color: #fff;
border: 1px solid #313131;
}
/* 气泡位置 */
/*----上----*/
.tooltip-placement-top, [tooltip]:not([placement])::after, [tooltip][placement=""]::after, [tooltip][placement="top"]::after {
bottom: calc(100% + 10px);
left: 50%;
transform: translate(-50%, 0);
}
.tooltip-placement-top-right, [tooltip][placement="top-right"]::after {
bottom: calc(100% + 10px);
left: 100%;
transform: translate(-100%, 0);
}
.tooltip-placement-top-left, [tooltip][placement="top-left"]::after {
bottom: calc(100% + 10px);
left: 0;
transform: translate(0, 0);
}
/*----右----*/
.tooltip-placement-right, [tooltip][placement="right"]::after {
left: calc(100% + 10px);
top: 50%;
transform: translate(0, -50%);
}
.tooltip-placement-right-top, [tooltip][placement="right-top"]::after {
left: calc(100% + 10px);
top: 0;
transform: translate(0, 0);
}
.tooltip-placement-right-bottom, [tooltip][placement="right-bottom"]::after {
left: calc(100% + 10px);
top: 100%;
transform: translate(0, -100%);
}
/*----下----*/
.tooltip-placement-bottom, [tooltip][placement="bottom"]::after {
top: calc(100% + 10px);
left: 50%;
transform: translate(-50%, 0);
}
.tooltip-placement-bottom-right, [tooltip][placement="bottom-right"]::after {
top: calc(100% + 10px);
left: 100%;
transform: translate(-100%, 0);
}
.tooltip-placement-bottom-left, [tooltip][placement="bottom-left"]::after {
top: calc(100% + 10px);
left: 0;
transform: translate(0, 0);
}
/*----左----*/
.tooltip-placement-left, [tooltip][placement="left"]::after {
right: calc(100% + 10px);
top: 50%;
transform: translate(0, -50%);
}
.tooltip-placement-left-top, [tooltip][placement="left-top"]::after {
right: calc(100% + 10px);
top: 0;
transform: translate(0, 0);
}
.tooltip-placement-left-bottom, [tooltip][placement="left-bottom"]::after {
right: calc(100% + 10px);
top: 100%;
transform: translate(0, -100%);
}
/* 三角形主题 */
.triangle-theme-dark, [tooltip]::before {
border-top-color: #313131;
}
.triangle-theme-light, [tooltip][effect="light"]::before {
border-top-color: #313131;
}
/* 三角形位置 */
/*----上----*/
.triangle-placement-top, [tooltip]:not([placement])::before, [tooltip][placement=""]::before, [tooltip][placement="top"]::before {
bottom: calc(100% + 5px);
left: 50%;
transform: translate(-50%, 0);
}
.triangle-placement-top-left, [tooltip][placement="top-left"]::before {
bottom: calc(100% + 5px);
left: 10px;
}
.triangle-placement-top-right, [tooltip][placement="top-right"]::before {
bottom: calc(100% + 5px);
right: 10px;
}
/*----右----*/
.triangle-placement-right, [tooltip][placement="right"]::before, .triangle-placement-right-top, [tooltip][placement="right-top"]::before, .triangle-placement-right-bottom, [tooltip][placement="right-bottom"]::before {
left: calc(100% + 3px);
top: 50%;
transform: translate(0, -50%) rotateZ(90deg);
}
.triangle-placement-right-top, [tooltip][placement="right-top"]::before {
top: 10px;
}
.triangle-placement-right-bottom, [tooltip][placement="right-bottom"]::before {
bottom: 10px;
top: auto;
transform: translate(0, 0) rotateZ(90deg);
}
/*----下----*/
.triangle-placement-bottom, [tooltip][placement="bottom"]::before, .triangle-placement-bottom-left, [tooltip][placement="bottom-left"]::before, .triangle-placement-bottom-right, [tooltip][placement="bottom-right"]::before {
top: calc(100% + 5px);
left: 50%;
transform: translate(-50%, 0) rotateZ(180deg);
}
.triangle-placement-bottom-left, [tooltip][placement="bottom-left"]::before {
transform: translate(0, 0) rotateZ(180deg);
left: 10px;
}
.triangle-placement-bottom-right, [tooltip][placement="bottom-right"]::before {
right: 10px;
left: auto;
}
/*----左----*/
.triangle-placement-left, [tooltip][placement="left"]::before, .triangle-placement-left-top, [tooltip][placement="left-top"]::before, .triangle-placement-left-bottom, [tooltip][placement="left-bottom"]::before {
right: calc(100% + 3px);
top: 50%;
transform: translate(0, -50%) rotateZ(270deg);
}
.triangle-placement-left-top, [tooltip][placement="left-top"]::before {
top: 10px;
}
.triangle-placement-left-bottom, [tooltip][placement="left-bottom"]::before {
bottom: 10px;
top: auto;
transform: translate(0, 0) rotateZ(270deg);
}
@keyframes anime-top {
from {
opacity: .5;
bottom: 150%;
}
}
@keyframes anime-bottom {
from {
opacity: .5;
top: 150%;
}
}
@keyframes anime-left {
from {
opacity: .5;
right: 150%;
}
}
@keyframes anime-right {
from {
opacity: .5;
left: 150%;
}
}
/* 移动端小屏幕样式 */
@media screen and (max-width:768px){
/* 首页 */

1
static/qrcodejs/qrcode.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

221
static/sortablejs/test.html Normal file
View File

@ -0,0 +1,221 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>layout 后台大布局 - Layui</title>
<link rel="stylesheet" href="../layui/css/layui.css">
<style>
.row {
display: -ms-flexbox;
display: flex;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
}
.list-group {
display: -ms-flexbox;
display: flex;
-ms-flex-direction: column;
flex-direction: column;
padding-left: 0;
margin-bottom: 0;
}
.col {
-ms-flex-preferred-size: 0;
flex-basis: 0;
-ms-flex-positive: 1;
flex-grow: 1;
max-width: 100%;
position: relative;
width: 100%;
padding-right: 15px;
padding-left: 15px;
}
.list-group-item:first-child {
border-top-left-radius: .25rem;
border-top-right-radius: .25rem;
}
.list-group-item {
position: relative;
display: block;
padding: .75rem 1.25rem;
margin-bottom: -1px;
background-color: #fff;
border: 1px solid rgba(0,0,0,.125);
}
.list-group-item:first-child {
border-top-left-radius: 0.25rem;
border-top-right-radius: 0.25rem;
}
.list-group-item:last-child {
margin-bottom: 0;
border-bottom-right-radius: 0.25rem;
border-bottom-left-radius: 0.25rem;
}
.list-group-item:hover, .list-group-item:focus {
z-index: 1;
text-decoration: none;
}
.list-group-item.disabled, .list-group-item:disabled {
color: #6c757d;
pointer-events: none;
background-color: #fff;
}
.list-group-item.active {
z-index: 2;
color: #fff;
background-color: #007bff;
border-color: #007bff;
}
</style>
</head>
<body class="layui-layout-body">
<div class="layui-layout layui-layout-admin">
<div class="layui-header">
<div class="layui-logo">layui 后台布局</div>
<!-- 头部区域可配合layui已有的水平导航 -->
<ul class="layui-nav layui-layout-left">
<li class="layui-nav-item"><a href="">控制台</a></li>
<li class="layui-nav-item"><a href="">商品管理</a></li>
<li class="layui-nav-item"><a href="">用户</a></li>
<li class="layui-nav-item">
<a href="javascript:;">其它系统</a>
<dl class="layui-nav-child">
<dd><a href="">邮件管理</a></dd>
<dd><a href="">消息管理</a></dd>
<dd><a href="">授权管理</a></dd>
</dl>
</li>
</ul>
<ul class="layui-nav layui-layout-right">
<li class="layui-nav-item">
<a href="javascript:;">
<img src="http://t.cn/RCzsdCq" class="layui-nav-img">
贤心
</a>
<dl class="layui-nav-child">
<dd><a href="">基本资料</a></dd>
<dd><a href="">安全设置</a></dd>
</dl>
</li>
<li class="layui-nav-item"><a href="">退了</a></li>
</ul>
</div>
<div class="layui-side layui-bg-black">
<div class="layui-side-scroll">
<!-- 左侧导航区域可配合layui已有的垂直导航 -->
<ul class="layui-nav layui-nav-tree" lay-filter="test">
<li class="layui-nav-item layui-nav-itemed">
<a class="" href="javascript:;">所有商品</a>
<dl class="layui-nav-child">
<dd><a href="javascript:;">列表一</a></dd>
<dd><a href="javascript:;">列表二</a></dd>
<dd><a href="javascript:;">列表三</a></dd>
<dd><a href="">超链接</a></dd>
</dl>
</li>
<li class="layui-nav-item">
<a href="javascript:;">解决方案</a>
<dl class="layui-nav-child">
<dd><a href="javascript:;">列表一</a></dd>
<dd><a href="javascript:;">列表二</a></dd>
<dd><a href="">超链接</a></dd>
</dl>
</li>
<li class="layui-nav-item"><a href="">云市场</a></li>
<li class="layui-nav-item"><a href="">发布商品</a></li>
</ul>
</div>
</div>
<div class="layui-body">
<!-- 内容主体区域 -->
<div style="padding: 15px;">
<div class="layui-row" style="margin-bottom: 10px;padding-left:15px;">
<span class="layui-breadcrumb" lay-separator=">">
<a href="{% url 'manage_project' %}">文集管理</a>
<a><cite>导入文集</cite></a>
</span>
</div>
<div class="layui-card-header" style="margin-bottom: 10px;">
<span style="font-size:18px;">导入文集到MrDoc</span>
</div>
<div class="layui-row" >
<button class="layui-btn layui-btn-normal layui-btn-sm" id="upload-zip"><i class="layui-icon layui-icon-upload"></i>导入.Zip文件</button>
</div>
<div id="nested" class="row">
<button onclick="getLevel()">aaa</button>
<ul id="nestedDemo" class="list-group col nested-sortable">
<li data-sortable-id="499" class="list-group-item" style="">万视电商数据助手脑图.<ul class="list-group nested-sortable"></ul></li>
<li data-sortable-id="498" class="list-group-item" style="">《读懂一本书》思维导图.<ul class="list-group nested-sortable"></ul></li>
<li data-sortable-id="500" class="list-group-item" style="">六类群体可得到慈善组织、红十字会优先帮扶_滚动新闻_中国政府网.<ul class="list-group nested-sortable"></ul></li>
</ul>
</div>
</div>
</div>
<div class="layui-footer">
<!-- 底部固定区域 -->
© layui.com - 底部固定区域
</div>
</div>
<script src="../jquery/3.1.1/jquery.min.js"></script>
<script src="../layui/layui.all.js"></script>
<script src="./Sortable.js"></script>
<script>
// Nested demo
var nestedSortables = [].slice.call(document.querySelectorAll('.nested-sortable'));
// Loop through each nested sortable element
for (var i = 0; i < nestedSortables.length; i++) {
new Sortable(nestedSortables[i], {
group: {
name:'nested',
pull: function(event) {
//console.log(event);
var deep = event.el.parentNode.parentNode.parentNode.parentNode.className
// console.log(deep)
// please get deep
// console.log(deep)
if(deep == 'list-group nested-sortable') return false;
return true;
},
},
animation: 150,
fallbackOnBody: true,
invertSwap:true,
swapThreshold: 0.65,
});
}
const nestedQuery = '.nested-sortable';
const identifier = 'sortableId';
const root = document.getElementById('nestedDemo');
function serialize(sortable) {
var serialized = [];
var children = [].slice.call(sortable.children);
for (var i in children) {
var nested = children[i].querySelector(nestedQuery);
serialized.push({
id: children[i].dataset[identifier],
children: nested ? serialize(nested) : []
});
}
return serialized
}
console.log(serialize(root))
function getLevel(){
console.log(serialize(root))
}
</script>
</body>
</html>

View File

@ -7,6 +7,7 @@
<meta http-equiv="Cache-Control" content="no-transform" />
<meta http-equiv="Cache-Control" content="no-siteapp" />
<meta http-equiv="Cache-Control" content="max-age=7200" />
<meta name="referrer" content="no-referrer">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta name="keywords" content="{% block keyword %}{% endblock %}mrdoc"/>
<meta name="description" content="{% block description %}{% endblock %}" />
@ -104,6 +105,13 @@
.CodeMirror-linenumber{
width: auto !important;
}
/* layui 折叠面板 边框颜色 - 用在左侧文集结构 */
.layui-badge-rim, .layui-colla-content, .layui-colla-item, .layui-collapse, .layui-elem-field, .layui-form-pane .layui-form-item[pane], .layui-form-pane .layui-form-label, .layui-input, .layui-layedit, .layui-layedit-tool, .layui-quote-nm, .layui-select, .layui-tab-bar, .layui-tab-card, .layui-tab-title, .layui-tab-title .layui-this:after, .layui-textarea{
border-color: #f8f8f8;
}
.layui-colla-content{
padding: 0px;
}
</style>
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
@ -117,6 +125,7 @@
<div class="doc-summary">
<div class="project-title"><i class="fa fa-edit"></i> MrDoc文档编辑器<br>
<span style="font-size: 14px;">你正在:{% block editor_type %}{% endblock %}</span>
<span class="layui-badge layui-bg-cyan" id="wordcount">0</span>
</div>
<hr>
@ -245,7 +254,7 @@
"undo", "redo", "|",
"h1", "bold", "del", "italic", "quote","kaiSpan",
"list-ul", "list-ol", "hr", "link", "reference-link",
"mindmap","echart","imgUpload", "attachment" ,"code", "code-block", "htmltable",
"mindmap","echart","imgUpload", "attachment" ,"multimedia","code", "code-block", "htmltable",
"emoji", "html-entities", "pagebreak",
"watch", "preview",
"help"
@ -261,7 +270,8 @@
attachment:'fa-upload',
htmltable:'fa-table',
mindmap:'fa-sitemap',
echart:'fa-bar-chart'
echart:'fa-bar-chart',
multimedia:'fa-youtube-play',
},
//设置自定义工具栏按钮的事件
toolbarHandlers : {
@ -284,7 +294,7 @@
// this == 当前editormd实例
//console.log("testIcon =>", this, cm, icon, cursor, selection);
},
// 点击工具栏图片图标的响应函数 - 上传图片和选择图片
// 上传图片和选择图片
imgUpload : function(cm,icon,cursor,selection){
layer.ready(function(){
element.init();
@ -297,7 +307,7 @@
content:$('#upload-img'),
})
},
//点击工具栏文件按钮的响应函数 - 上传和选择附件
// 上传和选择附件
attachment : function(cm,icon,cursor,selection){
layer.ready(function(){
element.init();
@ -339,7 +349,7 @@
}
})
},
//点击工具栏表格按钮的响应函数 - 添加表格
// 添加表格
htmltable:function(cm,icon,cursor,selection){
layer.ready(function(){
element.init();
@ -388,11 +398,22 @@
cm.setCursor(cursor.line+1, cursor.ch);
}
},
// 添加echart图
echart:function(cm,icon,cursor,selection){
cm.replaceSelection('```echart\n' + selection + "\n```");
if(selection === "") {
cm.setCursor(cursor.line+1, cursor.ch);
}
},
// 添加音视频
multimedia:function(){
layer.open({
type:'1',
title:'添加音视频外链',
area:['800px','600px'],
id:'insertMultiMedia',//配置ID,
content:$('#insertMultimedia'),
})
}
},
//设置语言
@ -404,6 +425,7 @@
htmltable:"添加表格",
mindmap:"添加思维导图",
echart:"添加Echarts图表",
multimedia:"添加音频、视频",
}
},
//配置项
@ -426,12 +448,17 @@
imageUploadURL : "{% url 'upload_doc_img' %}",
onchange:function(){
md_changed = true;
//console.log("字符数:",this.getMarkdown().length)
// console.log("字符数:",this.getMarkdown().length)
var wcnt = this.getMarkdown().length;
$("#wordcount").text(wcnt);
},
onload : function() {
// this.insertValue(" ")
var wcnt = this.getMarkdown().length;
$("#wordcount").text(wcnt);
}
});
//粘贴上传图片
$("#editor-md").on('paste', function (ev) {
var data = ev.clipboardData;
@ -518,12 +545,18 @@
})
}
});
//插入选择的图片到编辑器
// 插入选择的图片到编辑器
insertImg = function(e){
console.log(e.src);
editor.insertValue("\n![](" + e.src + ")");
};
//切换图片分组
// 按钮点击插入输入框图片链接
insertImgUrl = function(){
editor.insertValue("\n![](" + $("#img_url_input").val() + ")");
$("#img_url_input").val("")
layer.closeAll();
};
// 切换图片分组
switchImgGroup = function(e){
layer.load(1);
$.post("{% url 'manage_image' %}", {
@ -557,11 +590,25 @@
}
})
};
//插入选择的附件到编辑器
// 插入选择的附件到编辑器
insertAttach = function(e){
editor.insertValue("\n[【附件】"+ $(e).data('name') + "](/media/" + $(e).data('path') + ")");
layer.closeAll();
}
// 插入音视频到编辑器
insertMultimedia = function(e){
if(e === 'audio'){
editor.insertValue("\n![=audio](" + $("#audio_input").val() + ")");
}else if(e === 'video'){
editor.insertValue("\n![=video](" + $("#video_input").val() + ")");
}else if(e === 'video_iframe'){
editor.insertValue("\n![=video_iframe](" + $("#video_iframe_input").val() + ")");
}
$("#audio_input").val('')
$("#video_input").val('')
$("#video_iframe_input").val('')
layer.closeAll();
}
</script>
{% block custom_script %}
{% endblock %}
@ -577,7 +624,16 @@
</ul>
<div class="layui-tab-content">
<div class="layui-tab-item layui-show">
<button class="layui-btn layui-btn-normal layui-btn-fluid" id="upload_img"><i class="layui-icon layui-icon-upload"></i> 点击上传图片</button>
<div class="layui-row">
<button class="layui-btn layui-btn-normal layui-btn-fluid" id="upload_img"><i class="layui-icon layui-icon-upload"></i> 点击上传图片</button>
</div>
<fieldset class="layui-elem-field layui-field-title" style="text-align: center;">
<legend style="font-size: 12px;">或 插入外链图片链接</legend>
</fieldset>
<div class="layui-row" style="margin-top: 10px;">
<input type="text" class="layui-input" placeholder="输入图片URL" id="img_url_input" style="margin-bottom: 5px;"/>
<button type="button" class="layui-btn layui-btn-primary" onclick="insertImgUrl()">插入图片链接</button>
</div>
</div>
<div class="layui-tab-item">
<div class="layui-row" id="select-img-group">
@ -588,6 +644,7 @@
</div>
</div>
</div>
<!-- 选择和上传附件div -->
<div id="upload-attach" style="display:none;margin:20px;">
<span>* 仅支持.zip格式的压缩文件作为附件文件大小不超过50Mb</span>
@ -607,6 +664,7 @@
</table>
<div id="select-attach-page"></div>
</div>
<!-- 添加表格div -->
<div id="layer-table" style="display: none;margin: 10px;">
<div class="layui-tab" lay-filter="table-tab" id="insert-table-div">
@ -641,6 +699,38 @@
</div>
</div>
</div>
<!-- 添加音视频div -->
<div id="insertMultimedia" style="display: none;margin:10px;">
<div class="layui-row">
<fieldset class="layui-elem-field">
<legend>插入音频链接</legend>
<div class="layui-field-box">
<input class="layui-input" type="url" id="audio_input" placeholder="填入音频文件链接支持文件后缀格式为mp3、wav、flac、m4a" style="margin-bottom: 5px;">
<button type="button" class="layui-btn layui-btn-sm layui-btn-normal" onclick="insertMultimedia('audio')"><i class="fa fa-music"></i> 插入音频</button>
</div>
</fieldset>
</div>
<div class="layui-row">
<fieldset class="layui-elem-field">
<legend>插入视频链接</legend>
<div class="layui-field-box">
<input class="layui-input" type="url" id="video_input" placeholder="填入视频文件链接支持文件后缀格式为mp4、m4v、ogg、ogv、webm" style="margin-bottom: 5px;">
<button type="button" class="layui-btn layui-btn-sm layui-btn-normal" onclick="insertMultimedia('video')"><i class="fa fa-video-camera"></i> 插入视频</button>
</div>
</fieldset>
</div>
<div class="layui-row">
<fieldset class="layui-elem-field">
<legend>插入视频网站链接</legend>
<div class="layui-field-box">
<input class="layui-input" type="url" id="video_iframe_input" placeholder="填入视频网站视频播放页链接支持YouTube、优酷、QQ视频、Facebook、哔哩哔哩、TED网站" style="margin-bottom: 5px;">
<button type="button" class="layui-btn layui-btn-sm layui-btn-normal" onclick="insertMultimedia('video_iframe')"><i class="fa fa-youtube-play"></i> 插入视频网站视频</button>
</div>
</fieldset>
</div>
</div>
<script>
//按钮选择上传图片
var upload = layui.upload;
@ -691,6 +781,7 @@
field:'attachment_upload',
size: 51200, //最大允许上传的文件大小
})
//修改预览div中a标签链接新窗口打开
$('div.editormd-preview').on('click','a',function(e){
e.target.target = '_blank';

View File

@ -17,32 +17,18 @@
{% block left_opera %}
<div class="layui-form" style="padding: 10px;">
<div class="layui-form" style="padding: 0 10px 10px 10px;">
<div class="layui-row">
<div class="layui-col-md12" style="margin-bottom: 10px;">
<label class="doc-form-label" style="margin-right:0px;">
<button class="layui-btn layui-btn-normal layui-btn-sm" id="sel-doctemp" title="插入模板内容">
<i class="fa fa-clipboard"></i> 模板
<div class="layui-btn-container" style="margin-bottom: 0px;">
<button class="layui-btn layui-btn-primary layui-btn-sm mrdoc-btn-default" id="sel-doctemp" title="插入模板内容">
<i class="fa fa-clipboard"></i> 选择模板
</button>
</label>
<label class="doc-form-label" style="margin-right:0px;">
<input type="file" id="insert-local-file" onchange="insertLocalFile(this)" style="display:none;">
<button class="layui-btn layui-btn-normal layui-btn-sm" id="sel-local" onclick="selectLocalFile()" title="插入本地文本文件内容">
<i class="fa fa-clipboard"></i> 文本
<button class="layui-btn layui-btn-primary layui-btn-sm mrdoc-btn-default" id="sel-local" onclick="selectLocalFile()" title="插入本地文本文件内容">
<i class="fa fa-upload"></i> 导入文本
</button>
</label>
<label class="doc-form-label">
<button class="layui-btn layui-btn-normal layui-btn-sm" onclick="saveDoc()" title="保存当前内容为草稿文档">
<i class="fa fa-save"></i> 保存
</button>
</label>
<label class="doc-form-label">
<button class="layui-btn layui-btn-normal layui-btn-sm" onclick="createDoc()" id="create_doc" title="发布当前内容">
<i class="fa fa-save"></i> 发布
</button>
</label>
</div>
<div class="layui-col-md12" style="margin-bottom: 10px;">
@ -79,15 +65,36 @@
<div class="layui-row">
<div class="layui-col-md12" style="margin-bottom: 10px;">
<span>点击文档树选择上级(可选)或</span>
<button class="layui-btn layui-btn-xs layui-btn-normal" id="clearParentDoc">不设上级</button>
<button class="layui-btn layui-btn-xs layui-btn-normal" id="clearParentDoc">取消上级</button>
<input type="text" id="parent-doc" hidden>
</div>
<div class="layui-col-md12">
<input type="number" class="layui-input" placeholder="输入文档排序值默认99" id="sort">
</div>
</div>
</div>
<div class="layui-collapse" style="margin-top: 10px;margin-bottom: 10px;">
<div class="layui-colla-item">
<h2 class="layui-colla-title">文档结构树</h2>
<div class="layui-colla-content layui-show" style="max-height: 400px;overflow: hidden;overflow-y: scroll;overflow-x: scroll;">
<div id="doc-tree"></div>
</div>
</div>
</div>
<hr>
<div class="layui-row" style="margin-bottom: 10px;">
<button class="layui-btn layui-btn-primary layui-btn-fluid mrdoc-btn-default" onclick="saveDoc()" title="保存当前内容为草稿文档">
<i class="fa fa-save"></i> 保存文档为草稿
</button>
</div>
<div class="layui-row">
<button class="layui-btn layui-btn-normal layui-btn-fluid" onclick="createDoc()" id="create_doc" title="发布当前内容">
<i class="fa fa-save"></i> 发布文档
</button>
</div>
<div class="layui-col-md12" id="doc-tree"></div>
</div>
</div>
@ -169,7 +176,7 @@
var $me = $(this)
if($me.data('id') == obj.data.id){
// console.log('点击了')
layer.msg("选择了上级文档:"+obj.data.title)
layer.msg("选择了上级文档:"+obj.data.title)
$me.find('span.layui-tree-txt').first().addClass('selected-parent-doc')
}else{
$me.find('span.layui-tree-txt').first().removeClass('selected-parent-doc')
@ -300,11 +307,11 @@
//清除所选上级文档
$("#clearParentDoc").click(function(){
$('#parent-doc').val("");
$("span.layui-tree-txt").each(function(i){
var $me = $(this)
$me.removeClass('selected-parent-doc')
});
layer.msg("你取消了当前文档的上级文档")
});
//插入模板
insertTemp = function(doctemp_id){

View File

@ -52,8 +52,8 @@
{% block doc_previous_next %}
{% load doc_filter %}
<div class="layui-row" style="margin-top: 10px;padding:10px;display:flex;justify-content:space-around;border-top: 1px #e6e6e6 solid;">
<hr>
<div class="layui-row" style="margin-top: 10px;padding:10px;display:flex;justify-content:space-around;">
<!-- <hr> -->
<div>
{% if doc.id|get_doc_previous == None %}
<button class="layui-btn layui-btn-disabled layui-btn-sm layui-btn-radius"><i class="layui-icon layui-icon-prev "></i>上一篇</button>

View File

@ -7,6 +7,7 @@
<meta http-equiv="Cache-Control" content="no-transform" />
<meta http-equiv="Cache-Control" content="no-siteapp" />
<meta http-equiv="Cache-Control" content="max-age=7200" />
<meta name="referrer" content="no-referrer">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta name="keywords" content="{% block keyword %}{% endblock %}mrdoc"/>
<meta name="description" content="{% block description %}{% endblock %}" />
@ -14,7 +15,6 @@
<link href="{% static 'layui/css/layui.css' %}?version={{mrdoc_version}}" rel="stylesheet">
<link rel="stylesheet" href="{% static 'editor.md/css/editormd.css' %}?version={{mrdoc_version}}" />
<link rel="stylesheet" href="{% static 'katex/katex.min.css' %}?version={{mrdoc_version}}" />
<link rel="stylesheet" href="{% static 'share.js/css/share.min.css' %}?version={{mrdoc_version}}" />
<link href="{% static 'viewerjs/viewer.css' %}?version={{mrdoc_version}}" rel="stylesheet">
<link rel="icon" href="{% static 'favicon_16.png' %}"/>
<link href="{% static 'mrdoc.css' %}?version={{mrdoc_version}}" rel="stylesheet">
@ -79,6 +79,19 @@
.markdown-body h6{
font-size: .85em;
}
#url_qrcode img{
margin: auto;
}
#share{
background-color: #333;
}
/* layui弹出框颜色 */
.layui-tab-brief>.layui-tab-more li.layui-this:after, .layui-tab-brief>.layui-tab-title .layui-this:after{
border-bottom: 2px solid #333;
}
.layui-tab-brief>.layui-tab-title .layui-this{
color: #333;
}
</style>
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
@ -195,6 +208,11 @@
</div>
<!-- 正文结束 -->
</div>
<hr>
<!-- 分享栏 -->
<button id="share" class="layui-btn layui-btn-radius layui-btn-xs">
<i class="fa fa-share-alt"></i> 分享
</button>
<!-- 广告代码开始 -->
{% if debug %}
@ -208,10 +226,6 @@
<!-- 广告代码结束 -->
{% block doc_previous_next %}{% endblock %}
<!-- 社交分享 -->
<div class="share-div" style="margin-top: 10px;padding:10px;text-align: center;background-color: #fafafa">
分享到:<span class="social-share"></span>
</div>
</div>
</div>
@ -258,7 +272,7 @@
<!-- <script src="{% static 'editor.md/editormd.min.js' %}"></script> -->
<script src="{% static 'editor.md/editormd.js' %}?version={{mrdoc_version}}"></script>
<script src="{% static 'share.js/js/social-share.min.js' %}"></script>
<script src="{% static 'qrcodejs/qrcode.min.js' %}"></script>
<!-- 解析Markdown -->
<script>
@ -266,7 +280,6 @@
data: {csrfmiddlewaretoken: '{{ csrf_token }}' },
});
//解析Markdown为HTML
// layer.load(1);
editormd.markdownToHTML("content", {
htmlDecode : "style,script,iframe",
emoji : true, //emoji表情
@ -281,6 +294,18 @@
atLink : false,//禁用@链接
});
// 显示分享弹出框
$("#share").click(function(r){
var layer = layui.layer;
layer.open({
type: 1,
title: false,
closeBtn: 0,
area: ['350px','350px'],
shadeClose: true,
content: $('#share_div')
});
});
</script>
<!-- 生成文集目录大纲 -->
@ -290,18 +315,6 @@
$.post("{% url 'get_pro_doc_tree' %}",{'pro_id':'{{project.id}}'},function(r){
$("#loading-project-toc").hide();
if(r.status){
// var doc_tree = tree.render({
// elem:"#project-toc",
// id:'projectToc',
// // showCheckbox:true,
// onlyIconControl:true,
// isJump:true,
// data:r.data,
// text: {
// defaultNodeName: '未命名' //节点默认名称
// ,none: '文集暂无文档' //数据为空时的提示文本
// },
// });
var toc_str = ""
layui.each(r.data,function(index,item){
toc_str += "<li>"
@ -401,6 +414,8 @@
$(".toTop").click(function() {
$(document).scrollTop(0);
});
// 生成当前网页链接
$("input[name=current_url]").val(document.URL)
});
</script>
@ -465,9 +480,27 @@
}
};
</script>
<!-- 展开收起左边目录 -->
<!-- 分享选项卡模板 -->
<div id="share_div" style="display: none;">
<div class="layui-tab layui-tab-brief" lay-filter="docDemoTabBrief">
<ul class="layui-tab-title">
<li class="layui-this">微信扫一扫</li>
<li>复制链接</li>
</ul>
<div class="layui-tab-content" style="height: 100px;">
<div class="layui-tab-item layui-show" align='center'>
<p style="font-weight: 700;margin-bottom: 10px;">手机扫一扫进行分享</p>
<div id="url_qrcode"></div>
</div>
<div class="layui-tab-item" align='center'>
<input type="text" id="copy_crt_url" name="current_url" class="layui-input" /><br>
<button class="layui-btn layui-btn-radius layui-btn-xs" style="background-color: #333;" onclick="copyUrl();">复制链接</button>
</div>
</div>
</div>
</div>
<script>
// 展开收起左边目录
$(function(){
// $(".switch-toc").click(SwitchToc);
$("body").on('click','.switch-toc',SwitchToc)
@ -478,10 +511,8 @@
$(this).parent().next("ul").toggleClass("toc-close"); //切换展开收起样式
$(this).toggleClass("fa-chevron-left fa-chevron-down");//切换图标
};
</script>
<!-- 展开\收起文档树 -->
<script>
// 展开文档树
function openDocTree(){
$("nav ul.summary ul").each(function(obj){
console.log(obj,this)
@ -490,14 +521,31 @@
})
};
// 收起文档树
function closeDocTree(){
$("nav ul.summary ul").each(function(obj){
console.log(obj,this)
$(this).addClass("toc-close")
$(this).prev().children('i').toggleClass("fa-chevron-left fa-chevron-down");//切换图标
})
};
// 文档分享 - 复制链接
copyUrl = function(){
var crt_url_val = document.getElementById("copy_crt_url");
crt_url_val.select();
window.clipb
document.execCommand("Copy");
layer.msg("链接复制成功!")
}
// 生成二维码
var qrcode = new QRCode("url_qrcode", {
text: document.URL,
width: 200,
height: 200,
colorDark : "#000000",
colorLight : "#ffffff",
correctLevel : QRCode.CorrectLevel.H
});
</script>
<!-- 统计代码开始 -->

View File

@ -1,7 +1,7 @@
<div class="layui-footer" style="border-top: 1px #e6e6e6 solid;text-align:center;margin-top:10px;width:100%;">
<div style="margin-top:10px;">
© <a href="/">MrDoc 2019-2020</a>&nbsp;|&nbsp;
<span class="layui-hide-xs">基于<a href="https://www.djangoproject.com/" target="_blank">Django</a>&nbsp;|&nbsp;</span>
<!-- <span class="layui-hide-xs">基于<a href="https://www.djangoproject.com/" target="_blank">Django</a>&nbsp;|&nbsp;</span> -->
<a href="https://zmister.com" target="_blank">州的先生</a>出品 |
<a href="{% url 'sitemap' %}" target="_blank">网站地图</a>
</div>

View File

@ -7,8 +7,9 @@
<title>{% block title %}{% endblock %} - 个人中心 - 觅道文档MrDoc</title>
<link href="{% static 'layui/css/layui.css' %}?version={{mrdoc_version}}" rel="stylesheet">
<link href="{% static 'mrdoc-admin.css' %}?version={{mrdoc_version}}" rel="stylesheet">
<link href="{% static 'viewerjs/viewer.css' %}?version={{mrdoc_version}}" rel="stylesheet">
<link rel="icon" href="{% static 'favicon_16.png' %}"/>
{% block custom_link %}
{% endblock %}
<style>
.layui-btn a{
color:white;
@ -116,12 +117,13 @@
<div style="padding: 15px;">{% block content %}{% endblock %}</div>
</div>
<div class="layui-footer" style="text-align:center;">
<div class="layui-footer" style="text-align:center;font-size: 12px;">
<!-- 底部固定区域 -->
© <a href="https://gitee.com/zmister/MrDoc" target="_blank">MrDoc 2019-2020</a>&nbsp;-&nbsp;
当前版本:<a href="https://gitee.com/zmister/MrDoc/tree/{{mrdoc_version}}/" target="_blank">{{mrdoc_version}}</a>&nbsp;-&nbsp;
<a href="https://github.com/zmister2016/MrDoc" target="_blank">GitHub</a>&nbsp;-&nbsp;
<a href="https://gitee.com/zmister/MrDoc" target="_blank">码云</a>
<a href="https://gitee.com/zmister/MrDoc" target="_blank">码云</a>&nbsp;-&nbsp;
<a class="register-link" id="dashang" href="javascript:void(0);">打赏作者</a>
</div>
</div>
@ -145,8 +147,47 @@
$.ajaxSetup({
data: {csrfmiddlewaretoken: '{{ csrf_token }}' },
});
// 显示打赏图片
$("#dashang").click(function(r){
var layer = layui.layer;
layer.open({
type: 1,
title: false,
closeBtn: 0,
area: ['400px','400px'],
shadeClose: true,
content: $('#dashang_img')
});
});
</script>
{% block custom_script %}
{% endblock %}
</body>
<!-- 打赏选项卡模板 -->
<div id="dashang_img" style="display: none;">
<div class="layui-tab layui-tab-brief" lay-filter="docDemoTabBrief">
<ul class="layui-tab-title">
<li class="layui-this">微信</li>
<li>支付宝</li>
<li>QQ支付</li>
<li>PayPal</li>
</ul>
<div class="layui-tab-content" style="height: 100px;">
<div class="layui-tab-item layui-show" align='center'>
<img src="{% static 'dashang/dashang_wx.jpg' %}" style="width: 300px;height: auto;" />
</div>
<div class="layui-tab-item" align='center'>
<img src="{% static 'dashang/dashang_alipay.jpg' %}" style="width: 300px;height: auto;" />
</div>
<div class="layui-tab-item" align='center'>
<img src="{% static 'dashang/dashang_qq.png' %}" style="width: 300px;height: auto;" />
</div>
<div class="layui-tab-item" align='center'>
<a href="https://paypal.me/zmister" target="_blank">
<img src="{% static 'dashang/dashang_paypal.png' %}" style="width: 280px;height: auto;" />
</a>
</div>
</div>
</div>
</div>
</html>

View File

@ -1,6 +1,11 @@
{% extends 'app_doc/manage_base.html' %}
{% load staticfiles %}
{% block title %}图片素材管理{% endblock %}
{% block custom_link %}
<link href="{% static 'viewerjs/viewer.css' %}?version={{mrdoc_version}}" rel="stylesheet">
{% endblock %}
{% block content %}
<div class="layui-card-header" style="margin-bottom: 10px;">
<div class="layui-row">

View File

@ -17,6 +17,7 @@
<button class="layui-btn layui-btn-normal layui-btn-sm" type="submit"><i class="layui-icon layui-icon-search" ></i>搜索</button>
<button class="layui-btn layui-btn-normal layui-btn-sm" onclick="createProject()" type="button"><i class="layui-icon layui-icon-addition"></i>新建文集</button>
<a class="layui-btn layui-btn-normal layui-btn-sm" href="{% url 'manage_pro_colla_self' %}"><i class="layui-icon layui-icon-group"></i>我协作的文集</a>
<a class="layui-btn layui-btn-normal layui-btn-sm" href="{% url 'import_project' %}"><i class="layui-icon layui-icon-upload"></i>导入文集</a>
</div>
</form>
</div>

View File

@ -0,0 +1,249 @@
{% extends 'app_doc/manage_base.html' %}
{% load staticfiles %}
{% block title %}导入文集{% endblock %}
{% block content %}
<div class="layui-row" style="margin-bottom: 10px;padding-left:15px;">
<span class="layui-breadcrumb" lay-separator=">">
<a href="{% url 'manage_project' %}">文集管理</a>
<a><cite>导入文集</cite></a>
</span>
</div>
<div class="layui-card-header" style="margin-bottom: 10px;">
<span style="font-size:18px;">导入文集到MrDoc</span>
</div>
<div class="layui-row" >
<button class="layui-btn layui-btn-normal layui-btn-sm" id="upload-zip"><i class="layui-icon layui-icon-upload"></i>导入Markdown压缩包.Zip</button>
</div>
{% endblock %}
{% block custom_script %}
<script>
$.ajaxSetup({
data: {csrfmiddlewaretoken: '{{ csrf_token }}' },
});
var upload = layui.upload;
// 上传Zip文件
var uploadZip = upload.render({
elem: '#upload-zip', //绑定元素
url: "{% url 'import_project' %}", //上传接口
accept:'file',
exts:'zip',
data:{'type':'zip',csrfmiddlewaretoken: '{{ csrf_token }}'},
field:'import_file',
before:function(){
layer.load(1);
},
done: function(res){
//上传完毕回调
layer.closeAll('loading'); //关闭loading
// 遍历文集的文档
layui.each(res.data,function(index,item){
// console.log(index,item)
var doc_ele = '<li data-sortable-id="'+ item[0] +'" class="list-group-item">'+item[1]+'<ul class="list-group nested-sortable"></ul></li>'
$("#nestedDemo").append(doc_ele)
});
docSort();// 动态添加元素之后,调用一次
// 打开排序弹出框
var editor_import = layer.open({
type:1,
title:'编辑导入的文集',
area:['700px','400px'],
content:$("#import-project-sort"),
btn:['保存文档','发布文档'],
yes:function(index,layero){
// 保存文档
console.log(serialize(root))
var sort_data = {
'pid':res.id,
'title':$('input[name="project-name"]').val(),
'desc':$('input[name="project-desc"]').val(),
'role':$(':radio[name="role"]:checked').val(),
'sort_data':JSON.stringify(serialize(root)),
'status':0,
}
console.log(sort_data)
$.post('{% url "project_doc_sort" %}',sort_data,function(r){
if(r.status){
layer.msg("完成文集导入",function(){
layer.close(index)
window.location.href = "{% url 'manage_project' %}"
})
}else{
layer.msg("修改导入文集出错")
}
});
},
btn2:function(index,layero){
// 发布文档
console.log(serialize(root))
var sort_data = {
'pid':res.id,
'title':$('input[name="project-name"]').val(),
'desc':$('input[name="project-desc"]').val(),
'role':$(':radio[name="role"]:checked').val(),
'sort_data':JSON.stringify(serialize(root)),
'status':1,
}
console.log(sort_data)
$.post('{% url "project_doc_sort" %}',sort_data,function(r){
if(r.status){
layer.msg("完成文集导入",function(){
layer.close(index)
window.location.href = "{% url 'manage_project' %}"
})
}else{
layer.msg("修改导入文集出错")
}
});
},
})
},
error: function(){
//请求异常回调
layer.closeAll('loading'); //关闭loading
layer.msg("文件上传出错")
}
});
</script>
<!-- 导入的文集文档排序模板div -->
<style>
.row {
display: -ms-flexbox;
display: flex;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
}
.list-group {
display: -ms-flexbox;
display: flex;
-ms-flex-direction: column;
flex-direction: column;
padding-left: 0;
margin-bottom: 0;
}
.col {
-ms-flex-preferred-size: 0;
flex-basis: 0;
-ms-flex-positive: 1;
flex-grow: 1;
max-width: 100%;
position: relative;
width: 100%;
padding-right: 15px;
padding-left: 15px;
}
.list-group-item:first-child {
border-top-left-radius: .25rem;
border-top-right-radius: .25rem;
}
.list-group-item {
position: relative;
display: block;
padding: .75rem 1.25rem;
margin-bottom: -1px;
background-color: #fff;
border: 1px solid rgba(0,0,0,.125);
}
.list-group-item:first-child {
border-top-left-radius: 0.25rem;
border-top-right-radius: 0.25rem;
}
.list-group-item:last-child {
margin-bottom: 0;
border-bottom-right-radius: 0.25rem;
border-bottom-left-radius: 0.25rem;
}
.list-group-item:hover, .list-group-item:focus {
z-index: 1;
text-decoration: none;
}
.list-group-item.disabled, .list-group-item:disabled {
color: #6c757d;
pointer-events: none;
background-color: #fff;
}
.list-group-item.active {
z-index: 2;
color: #fff;
background-color: #007bff;
border-color: #007bff;
}
</style>
<div id="import-project-sort" style="display: none;margin: 10px;" class="layui-form">
<div class="layui-row" style="padding-left: 14px;padding-bottom: 10px;">
<input class="layui-input" placeholder="请输入文集名称" name="project-name">
</div>
<div class="layui-row" style="padding-left: 14px;padding-bottom: 10px;">
<input class="layui-input" placeholder="请输入文集简介" name="project-desc">
</div>
<div class="layui-row" style="">
<div class="layui-form-item">
<label class="layui-form-label" style="width: auto;">文集状态</label>
<div class="layui-input-block">
<input type="radio" name="role" value="1" title="私密" checked>
<input type="radio" name="role" value="0" title="公开">
</div>
</div>
</div>
<div class="layui-row" style="padding-left: 14px;padding-bottom: 10px;">文档拖拽排序</div>
<div id="nested" class="row">
<ul id="nestedDemo" class="list-group col nested-sortable"></ul>
</div>
</div>
<!-- 文档拖拽排序 -->
<script src="{% static 'sortablejs/Sortable.js' %}"></script>
<script>
// 文档动态排序
function docSort(){
// Nested demo
var nestedSortables = [].slice.call(document.querySelectorAll('.nested-sortable'));
// Loop through each nested sortable element
for (var i = 0; i < nestedSortables.length; i++) {
new Sortable(nestedSortables[i], {
group: {
name:'docsort',
pull: function(event) {
var deep = event.el.parentNode.parentNode.parentNode.parentNode.className
if(deep == 'list-group nested-sortable') return false;
return true;
},
},
animation: 150,
fallbackOnBody: true,
invertSwap:true,
swapThreshold: 0.65,
});
}
};
const nestedQuery = '.nested-sortable';
const identifier = 'sortableId';
const root = document.getElementById('nestedDemo');
function serialize(sortable) {
var serialized = [];
var children = [].slice.call(sortable.children);
for (var i in children) {
var nested = children[i].querySelector(nestedQuery);
serialized.push({
id: children[i].dataset[identifier],
children: nested ? serialize(nested) : []
});
}
return serialized
}
function getLevel(){
console.log(serialize(root))
}
form.render();
</script>
{% endblock %}

View File

@ -49,26 +49,15 @@
<div class="layui-col-md12" style="margin-bottom: 10px;">
<label class="doc-form-label" style="margin-right:0px;">
<button class="layui-btn layui-btn-normal layui-btn-sm" id="sel-doctemp" title="插入模板内容">
<i class="fa fa-clipboard"></i> 模板
<button class="layui-btn layui-btn-primary layui-btn-sm mrdoc-btn-default" id="sel-doctemp" title="插入文档模板">
<i class="fa fa-clipboard"></i> 选择模板
</button>
</label>
<label class="doc-form-label" style="margin-right:0px;">
<input type="file" id="insert-local-file" onchange="insertLocalFile(this)" style="display:none;">
<button class="layui-btn layui-btn-normal layui-btn-sm" id="sel-local" onclick="selectLocalFile()" title="插入本地文本文件内容">
<i class="fa fa-clipboard"></i> 文本
</button>
</label>
<label class="doc-form-label">
<button class="layui-btn layui-btn-normal layui-btn-sm" onclick="saveDoc()" title="保存当前内容为草稿文档">
<i class="fa fa-save"></i> 保存
</button>
</label>
<label class="doc-form-label">
<button class="layui-btn layui-btn-normal layui-btn-sm" onclick="createDoc()" id="create_doc" title="发布当前内容">
<i class="fa fa-save"></i> 发布
<button class="layui-btn layui-btn-primary layui-btn-sm mrdoc-btn-default" id="sel-local" onclick="selectLocalFile()" title="插入本地文本文件内容">
<i class="fa fa-upload"></i> 导入文本
</button>
</label>
</div>
@ -84,23 +73,46 @@
<div class="layui-row">
<div class="layui-col-md12" style="margin-bottom: 10px;">
<span>点击文档树选择上级(可选)或</span>
<button class="layui-btn layui-btn-xs layui-btn-normal" id="clearParentDoc">不设上级</button>
<button class="layui-btn layui-btn-xs layui-btn-normal" id="clearParentDoc">取消上级</button>
<input type="text" id="parent-doc" hidden>
</div>
<div class="layui-col-md12">
<input type="number" class="layui-input" placeholder="输入文档排序值默认99" id="sort" value="{{doc.sort}}">
</div>
</div>
<div class="layui-col-md12" id="doc-tree"></div>
</div>
<div class="layui-row" style="margin-top: 20px;">
<button class="layui-btn layui-btn-warm layui-btn-fluid" onclick="moveDoc()" title="复制或移动此文档到其他文集">复制/移动文档</button>
</div>
<div class="layui-row" style="margin-top: 10px;">
<button class="layui-btn layui-btn-danger layui-btn-fluid" onclick="delDoc()" title="删除此文档">删除文档</button>
<div class="layui-collapse" style="margin-top: 10px;margin-bottom: 10px;">
<div class="layui-colla-item">
<h2 class="layui-colla-title">文档结构树</h2>
<div class="layui-colla-content layui-show" style="max-height: 400px;overflow: hidden;overflow-y: scroll;overflow-x: scroll;">
<div id="doc-tree"></div>
</div>
</div>
</div>
<hr>
<div class="layui-row" style="margin-top: 10px;">
<button class="layui-btn layui-btn-primary layui-btn-fluid mrdoc-btn-default" onclick="saveDoc()" title="保存当前内容为草稿文档">
<i class="fa fa-save"></i> 保存为草稿
</button>
</div>
<div class="layui-row" style="margin-top: 10px;">
<button class="layui-btn layui-btn-normal layui-btn-fluid" onclick="createDoc()" id="create_doc" title="发布当前内容">
<i class="fa fa-save"></i> 发布文档
</button>
</div>
<div class="layui-row" style="margin-top: 10px;">
<button class="layui-btn layui-btn-warm layui-btn-fluid" onclick="moveDoc()" title="复制或移动此文档到其他文集"><i class="fa fa-copy"></i> 复制/移动文档</button>
</div>
<div class="layui-row" style="margin-top: 10px;">
<button class="layui-btn layui-btn-danger layui-btn-fluid" onclick="delDoc()" title="删除此文档"><i class="fa fa-trash"></i> 删除文档</button>
</div>
</div>
</div>
{% endblock %}
@ -126,7 +138,7 @@
//获取文档数和上级文档信息
$(function(){
layer.load(1);
var doc_parent_id = {{ doc.parent_doc }};
var doc_parent_id = '{{ doc.parent_doc }}';
$.post("{% url 'get_pro_doc_tree' %}",{'pro_id':$("#project").val()},function(r){
if(r.status){
layer.closeAll("loading");
@ -138,12 +150,13 @@
click: function(obj){
if(obj.data.level != 3){
console.log(obj.data.id,doc_parent_id)
if(obj.data.id != {{ doc.id }} ){
if(obj.data.id != '{{ doc.id }}' ){
$('#parent-doc').val(obj.data.id);// 设置上级文档
$("div.layui-tree-set").each(function(i){
var $me = $(this)
if($me.data('id') == obj.data.id){
// console.log('点击了')
layer.msg("你选择了上级文档:"+obj.data.title)
$me.find('span.layui-tree-txt').first().addClass('selected-parent-doc')
}else{
$me.find('span.layui-tree-txt').first().removeClass('selected-parent-doc')
@ -179,7 +192,7 @@
$('button.layui-btn').addClass('layui-btn-disabled');
layer.load(1);
var data = {
'doc_id':{{ doc.id }},
'doc_id':'{{ doc.id }}',
'project':$("#project").val(),
'parent_doc':$("#parent-doc").val(),
'doc_name':$("#doc-name").val(),
@ -210,7 +223,7 @@
$('button.layui-btn').addClass('layui-btn-disabled');
layer.load(1);
var data = {
'doc_id':{{ doc.id }},
'doc_id':'{{ doc.id }}',
'project':$("#project").val(),
'parent_doc':$("#parent-doc").val(),
'doc_name':$("#doc-name").val(),
@ -240,8 +253,8 @@
layer.open({
type: 1,
id:'temp-div',
content: $('#doctemp-list'),
area:['530px','300px'],
content: $('#doctemp-list'),
area:['530px','400px'],
});
});
//插入模板
@ -261,6 +274,7 @@
$("#doc-history").click(function(){
layer.open({
type: 1,
title:'查看当前文档的历史版本',
id:'history-div',
content: $('#history-list'),
area:['530px','300px'],
@ -301,7 +315,7 @@
yes:function (index,layero) {
layer.load(1);
data = {
'doc_id':{{ doc.id }},
'doc_id':'{{ doc.id }}',
};
$.post("{% url 'del_doc' %}",data,function(r){
layer.closeAll('loading')
@ -404,8 +418,8 @@
<!-- 模板div块 -->
<div class="doctemp-list" id="doctemp-list" style="display: none;width: 500px;">
<div style="margin: 10px 0 0 10px;">
<a class="layui-btn layui-btn-normal" href="{% url 'create_doctemp' %}" target="_blank">创建新模板</a>
<a class="layui-btn layui-btn-normal" href="{% url 'manage_doctemp' %}" target="_blank">管理文档模板</a>
<a class="layui-btn layui-btn-normal layui-btn-sm" href="{% url 'create_doctemp' %}" target="_blank">创建新模板</a>
<a class="layui-btn layui-btn-normal layui-btn-sm" href="{% url 'manage_doctemp' %}" target="_blank">管理文档模板</a>
</div>
<table class="layui-table" style="margin: 10px;">
<colgroup>
@ -451,7 +465,7 @@
<td>{{ history.create_time }}</td>
<td>{{ history.create_user }}</td>
<td>
<a href="{% url 'diff_doc' history.doc.id history.id %}" target="_blank">查看差异</a>
<a href="{% url 'diff_doc' history.doc.id history.id %}" class="layui-btn layui-btn-primary layui-btn-sm" target="_blank">查看版本差异</a>
</td>
<td>
<a href="javascript:void(0);" class="layui-btn layui-btn-normal layui-btn-sm" onclick="insertHistory('{{history.doc.id}}','{{history.id}}');">恢复此版本</a>

View File

@ -44,7 +44,7 @@
{% if enable_project_report %}
<div class="layui-row">
<h3>文集下载:</h3>
<p>
<p style="margin-bottom: 0px;">
<span class="layui-breadcrumb" lay-separator="|">
{% if allow_download %}
{% if allow_download.allow_epub == 1 %}