本flask博客首先支持在线编辑markdown编辑,作为技术人通常使用markdown写文档还是比较顺手的,因为格式统一,不需要花很多的时间在排版上,我选用了国内editor.md作为本博客的编辑器,下面就对editor.md的集成做下简单介绍
文章编辑界面
{% extends 'admin/common/base.html' %}
{% block css %}
{{ super() }}
<link rel="stylesheet" href="{{url_for('.static',filename='editor_md/editormd.min.css')}}">
<link rel="stylesheet" href="{{url_for('.static',filename='css/write.css')}}">
{% endblock %}
{% block js %}
{{ super() }}
<script src="{{url_for('.static',filename='editor_md/editormd.min.js')}}"></script>
<script>
var imagehosting_for = "thumbnail-img";
//图床
function imagehosting(flag) {
imagehosting_for = flag;
layer.open({
type: 2,
title: '图片管理',
shade: 0.8,
maxmin: true,
shadeClose: true,
area: ['80%', '90%'],
content: '{{url_for("admin.image_hosting")}}' //这里content是一个URL,如果你不想让iframe出现滚动条,你还可以content: ['http://sentsin.com', 'no']
});
}
function imagehosting_callback(obj) {
//缩略图
if(imagehosting_for === 'thumbnail-img') {
$('#thumbnail-img').attr('src', obj.img_url);
$('#thumbnail').val(obj.img_url);
}
//编辑器 editor.md
if(imagehosting_for === 'editor-img') {
console.log(myEditor)
myEditor.settings.imagehosting_callback(obj)
}
}
var myEditor;
$(function () {
myEditor = editormd("write-editor", {
// width: '100%',
height: '450px',
syncScrolling: "single",
path: "{{url_for('.static',filename='editor_md/lib/')}}",
emoji: true,//emoji表情,默认关闭
watch: true, // 关闭实时预览
taskList: true,
tocm: true, // Using [TOCM]
tex: true,// 开启科学公式TeX语言支持,默认关闭
//flowChart: true,//开启流程图支持,默认关闭
//sequenceDiagram: true,//开启时序/序列图支持,默认关闭,
//启动本地图片上传功能
imageUpload: true,
// imageFormats: ["jpg", "jpeg", "gif", "png", "bmp", "webp"],
// imageUploadURL: "{{url_for('.upload')}}",
toolbarIcons: function () {
// Or return editormd.toolbarModes[name]; // full, simple, mini
// Using "||" set icons align right.
// ["undo", "redo", "|", "bold", "del", "italic", "quote", "|", "h1", "h2", "h3", "h4",
// "h5", "h6", "|", "list-ul", "list-ol", "hr", "|", "link", "reference-link", "image",
// "code", "code-block", "table", "datetime", "html-entities", "goto-line", "search", "||",
// "fullscreen", "watch", "preview", "info", "|", "publish"]
return ["bold", "del", "italic", "quote", "|", "h2", "h3", "h4",
"|", "list-ul", "list-ol", "hr", "|", "link", "reference-link", "image",
"code", "code-block", "table", "|", "watch", "fullscreen"]
},
imagehosting: function () {
imagehosting('editor-img');
},
imagehosting_callback:function(obj) {
}
});
function save(state) {
$.ajax({
url: '{{url_for("admin.write")}}',
type: "post",
data: $("form").serialize(),
dataType: 'json',
success: function (res) {
if (res.code == 1) {
if (res.id) {
$('#id').val(res.id);
}
toastr.success(res.msg);
} else {
toastr.error(res.msg);
}
},
fail: function (res) {
toastr.error('网络错误');
}
})
}
//注册保存按钮点击事件
$('#form').on('submit', function (ev) {
ev.preventDefault();
// var content = $('#hidden-body').val();
// var summary = content.replace(/#*.*#/g, '').replace(/[^a-z0-9\u4e00-\u9fa5]/, '').substring(0, 200) // 除去标题部分,截取200个字用来显示
// $('#summary').val(summary);
save(1);
});
setInterval(save,1000 * 15,1);
//注册草稿按钮
$('#draft').click(function (e) {
$('#state').val(0);
$('#form').submit();
});
//注册发布按钮点击
$('#publish').click(function (e) {
$('#state').val(1);
$('#form').submit();
});
//上传图片
$('#thumbnail-upload-btn').click(function (e) {
imagehosting('thumbnail-img');
});
//固定路径编辑
$('#baseURL').html(window.location.protocol + "//" + window.location.host + '/');
})
</script>
{% endblock %}
{% block content %}
<div class="content-wrapper" style="min-height: 600px;">
<section class="content-header">
<h3>撰写</h3>
</section>
<section class="content">
<form id="form" method="POST">
{{ form.hidden_tag() }}
<div class="row">
<div class="col-lg-9 col-md-9">
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text" id="inputGroup-sizing-default">标题</span>
</div>
{{form.title(class='form-control')}}
</div>
<div class="mb-2 ml-2">
网址:<span
id="baseURL">http://www.h3blog.com/</span>article/{{form.name(class='border-top-0 border-left-0 border-right-0 input-small w-50')}}
</div>
<div class="write-article">
<div id="write-editor">
{{form.content(id='hidden-body',class='hidden')}}
</div>
</div>
<div class="mt-2">
<div class="form-group">
<label for="summary">文章简介</label>
{{form.summary(id='summary',class='form-control')}}
</div>
</div>
</div>
<div class="col-lg-3 col-md-3">
<div id="saveBtns" class="card">
<div class="card-body">
{{form.save(id='save',class="btn btn-primary btn-sm")}}
<button id='draft' type="button" class="btn btn-secondary btn-sm">草稿</button>
<button id='publish' type="button" class="btn btn-success btn-sm">发布</button>
</div>
</div>
<!--save btn end-->
<div id="category" class="card mt-3">
<h6 class="card-title p-2 border-bottom">分类</h6>
<div class="card-body">
{{form.category_id(id='category_id',class='form-control')}}
</div>
</div>
<!--category end-->
<div id="tags" class="card mt-3">
<h6 class="card-title p-2 border-bottom">标签</h6>
<div class="card-body">
{{form.tags(class='form-control')}}
</div>
</div>
<!--tags end-->
<div id="thumbnail-div" class="card mt-3">
<h6 class="card-title p-2 border-bottom">缩略图</h6>
<div class="card-body">
<img id="thumbnail-img"
src="{{form.thumbnail.data if form.thumbnail.data else url_for('static',filename='img/thumbnail.jpg')}}"
class="card-img-top" alt="缩略图">
<div class="mt-2">
<button id="thumbnail-upload-btn" type="button"
class="btn btn-outline-secondary btn-sm">选择图片</button>
</div>
</div>
</div>
<!--thumbnail end-->
<div id="publish-time" class="card mt-3">
<h6 class="card-title p-2 border-bottom">发布时间</h6>
<div class="card-body">
{{form.timestamp(type='datetime',class='form-control')}}
</div>
</div><!--publish time end-->
</div>
</div>
</form>
</section>
</div>
{% endblock %}
后台代码业务处理
后台保存代码
@admin.route('/article/write', methods=['GET','POST'])
@login_required
@admin_required
def write():
form = ArticleForm()
if form.validate_on_submit():
# --------以下功能是增加文章的分类
cty = Category.query.get(int(form.category_id.data))
a = None
if form.id.data:
a = Article.query.get(int(form.id.data))
if a :
a.title = form.title.data.strip()
a.content = form.content.data
a.content_html = a.content_to_html()
a.summary = form.summary.data
a.thumbnail = form.thumbnail.data
a.category = cty
a.name = form.name.data.strip()
a.state = form.state.data
a.timestamp = form.timestamp.data
if not a.name and len(a.name) == 0 :
a.name = a.id
db.session.commit()
else:
# --------以下功能是将文章信息插入数据库
a = Article(title=form.title.data.strip(), content=form.content.data,
thumbnail = form.thumbnail.data,name = form.name.data.strip(),
state = form.state.data,summary = form.summary.data,
category=cty, author=current_user._get_current_object())
a.content_html = a.content_to_html()
db.session.add(a)
db.session.commit()
if not a.name and len(a.name) == 0 :
a.name = a.id
db.session.commit()
# --------以下功能是将文章标识插入数据库
a.tags = []
for tg in form.tags.data.split(','):
if tg.strip() == '':
continue
t = Tag.query.filter_by(name=tg.strip()).first()
if not t:
t = Tag(name=tg.strip())
db.session.add(t)
if t not in a.tags :
a.tags.append(t)
if isAjax() :
msg = '发布成功' if int(form.state.data) == 1 else '保存成功'
return jsonify({'code':1,'msg':msg,'id':a.id})
return render_template('admin/write.html', form=form)
Article类的实现
class Article(db.Model):
__tablename__ = 'article'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(120), index=True)
name = db.Column(db.String(64),index=True,unique=True)
content = db.Column(db.Text)
content_html = db.Column(db.Text)
summary = db.Column(db.String(300))
thumbnail = db.Column(db.String(200))
state = db.Column(db.Integer,default=0)
vc = db.Column(db.Integer,default=0)
timestamp = db.Column(db.DateTime, index=True, default=datetime.now)
author_id = db.Column(db.Integer, db.ForeignKey('user.id'))
category_id = db.Column(db.Integer, db.ForeignKey('category.id'))
tags = db.relationship('Tag',secondary=article_tag,backref=db.backref('articles',lazy='dynamic'),lazy='dynamic')
def content_to_html(self):
return markdown.markdown(self.content, extensions=[
'markdown.extensions.extra',
'markdown.extensions.codehilite',
])
@property
def author(self):
"""返回作者对象"""
return User.query.get(self.author_id)
@property
def category(self):
"""返回文章分类对象"""
return Category.query.get(self.category_id)
@property
def category_name(self):
"""返回文章分类名称,主要是为了使用 flask-wtf 的 obj 返回对象的功能"""
return Category.query.get(self.category_id).name
@property
def previous(self):
"""用于分页显示的上一页"""
a = self.query.filter(Article.state==1,Article.id < self.id). \
order_by(Article.timestamp.desc()).first()
return a
@property
def next(self):
"""用于分页显示的下一页"""
a = self.query.filter(Article.state==1,Article.id > self.id). \
order_by(Article.timestamp.asc()).first()
return a
@property
def tag_names(self):
"""返回文章的标签的字符串,用英文‘, ’分隔,主要用于修改文章功能"""
tags = []
for tag in self.tags:
tags.append(tag.name)
return ', '.join(tags)
@property
def thread_key(self): # 用于评论插件
return hashlib.new(name='md5', string=str(self.id)).hexdigest()
def __repr__(self):
return '<Title %r>' % self.title