文章目录
我打算把我的域名用于图床了,网站后面可能访问就不太行了
【系统已经升级啦,快看这里 】
所谓天下代码一大抄,抄来抄去有提高,用来描述编程再合适不过了,今天我也抄了一波。我通过开源+借鉴的方式,自己搞了一个在线的markdown编辑器,没错这篇文章就是在上面写的。
话不多说,先上图,下面就是我抄的成果:



我之前一直都是使用vscode敲各种代码的,我非常喜欢这个工具,主要是颜值把住了我,其次通过插件可以支持非常多的语言,通用性非常高,上一个被我这么宠幸的IDE还是eclipse。
我写文章使用的是markdown,之前也用过富文本编辑器,相比于markdown,富文本编辑器更多样,这是优势也是劣势。主要的缺点是写出来的文章比较花哨(对我来说,有很多读者都喜欢这种),而且非常容易造成自己写的文章格式风格不统一。
我一直用vscode编写markdown,Markdown All in One这个插件非常的神器,基本上能用到的功能都有涉及。
问题在于代码的同步,最初都是用Gitee,因为GitHub老是打不开。我这人有一个毛病,不喜欢同步代码,这就导致家里和公司的代码出现了不匹配,很烦。
当然,代码同步只是一个方面,最主要的是,如果在公司打开一个黑乎乎的vscode很引人注意(我的岗位不需要敲代码),这就有了划水的嫌疑。
另外呢,我买的还有两台服务器,域名也收藏了好多,正好用上。其实用vscode连接远端服务器也蛮好的,但是问题还是在工位上打开vscode不合适~~
(把vscode改成light主题??哈哈)
话说我还买了好几个中文域名,太费钱了
为了解决我遇到的困扰,我收集了一下我的主要矛盾:
我目前了解到的、喜欢的开源在线编辑工具主要有两个:
Editor.md是一个网页版的markdown编辑器,界面风格非常简洁,Demo也非常丰富,也是本文的选择。遗憾的是代码库停止更新了。
CKEditor是一个富文本编辑器,就能力上来说,更强,但是是一个富文本编辑器,虽然支持markdown,对我来说有那么一奈奈的功能过剩。
这两款编辑工具都非常优秀,我非常喜欢,只恨自己不是开发者~~
原计划只做一个页面,其他功能以弹窗的方式实现,但是Editor和bootstrap等前端框架有冲突,自己前端水平有限,做不出好看的界面,就能简则简。
页面包括三个:
Editor.md实现;所谓,人生苦短,我用Python,顺理成章的就选择了Flask作为后端框架。
简单介绍一下Flask,Python服务器开发的流行框架,非常的轻量,同时插件很丰富,文档也齐全,有兴趣的童鞋可以访问官网,或则访问我之前写的文章《我用Python写网站》,文章写的比较粗,但是基本的注意事项都提到了。
sqlite是常用的单机数据库解决方案,完全能够满足我当前的需求,就不折腾MySQL了。也非常推荐简单玩玩的童鞋使用,MySQL如果不是老鸟,太难了~💔
我之前的文章使用的是MySQL,详细介绍了如何连接数据库,使用起来都差不多。
连接数据库的工具是flask-sqlalchemy,SQLAlchemy是一个ORM(Object Relational Mapper)框架,简单来讲,就是可以在不写sql的情况下完成各种数据库操作。
因为贫穷,只能使用免费的图床平台,这里我用的是sm.ms。
市面上有很多图床可以选择,一般都有免费空间赠送,sm.ms有5GB的免费空间,支持API上传,不过访问速度一般,可能因为我是白嫖的。
关键是不需要注册就能使用,直接上传图片就可以获得链接。

下面是抄袭教程:
数据库使用flask-sqlalchemy连接,详细操作在《我用Python写网站》中都有讲解。
下面的代码涉及了flask-sqlalchemy的使用方法、flask-cli命令行的使用。
可以简单的使用flask db-init、flask db-rebuild等命令操作数据库。
话不多说,上代码:
# db.py
from email.policy import default
from flask_sqlalchemy import SQLAlchemy
import sqlite3
import click
from flask.cli import with_appcontext
from datetime import datetime
from werkzeug.security import generate_password_hash, check_password_hash
from flask_login import UserMixin
db = SQLAlchemy()
def addUser(u):
if isinstance(u, User):
db.session.add(u)
db.session.commit()
def updatePost(p):
if isinstance(p, Post):
db.session.add(p)
db.session.commit()
def init_app(app):
db.init_app(app)
app.cli.add_command(init_db_command)
app.cli.add_command(reb_db_command)
app.cli.add_command(del_db_command)
def init_data():
admin = User(username='admin', password='996996', email='666@163.com')
db.session.add(admin)
db.session.flush()
db.session.commit()
post = Post(title='第一篇文章', html='# 第一篇文章', markdown='# 第一篇文章')
post.author_id = admin.id
db.session.add(post)
db.session.commit()
anonym = User(username='anonym', password='996996', email='666@666.com')
db.session.add(anonym)
db.session.commit()
def init_db():
db.create_all()
init_data()
def del_db():
db.drop_all()
@click.command('db-rebuild')
@with_appcontext
def reb_db_command():
del_db()
init_db()
click.echo('Rebuild the database.')
@click.command('db-clean')
@with_appcontext
def del_db_command():
del_db()
click.echo('Cleared the database.')
@click.command('db-init')
@with_appcontext
def init_db_command():
init_db()
click.echo('Initialized the database.')
class ShareField(object):
created = db.Column(
db.DateTime, nullable=False, default=datetime.utcnow)
updated = db.Column(db.DateTime, onupdate=datetime.utcnow)
status = db.Column(db.Integer, default=0)
class User(db.Model, ShareField, UserMixin):
__tablename__ = 't_users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), nullable=False)
_password = db.Column(db.String(128), nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
posts = db.relationship('Post', backref='author', lazy=True)
def __init__(self, username, password, email):
self.username = username
self.password = password
self.email = email
# getter
@property
def password(self):
return self._password
# setter
@password.setter
def password(self, raw_password):
self._password = generate_password_hash(raw_password) # 加密
# check
def check_password(self, raw_password):
result = check_password_hash(self.password, raw_password)
return result
class Post(db.Model, ShareField):
__tablename__ = 't_posts'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(64), nullable=False, default='')
html = db.Column(db.String(30000), nullable=False, default='')
markdown = db.Column(db.String(30000), nullable=False, default='')
author_id = db.Column(db.Integer, db.ForeignKey(
't_users.id'), nullable=False)
首先,去csdn上搜个登录注册页面源代码!

我选择的是《好看实用的HTML登录界面》
稍微改改里面的form代码,以下仅供参考:
<div class="container__form container--signup">
<form action="{{url_for('auth.register')}}" method="post" class="form" id="form1">
<h2 class="form__title">Sign Up</h2>
<input type="text" name="username" placeholder="UserName" class="input" />
<input type="email" name="email" placeholder="Email" class="input" />
<input type="password" name="password" placeholder="Password" class="input" />
<input type="submit" class="btn" value="Sign Up"></input>
</form>
</div>
<!-- Sign In -->
<div class="container__form container--signin">
<form action="{{url_for('auth.login')}}" method="post" class="form" id="form2">
<h2 class="form__title">Sign In</h2>
<input type="email" name="email" placeholder="Email" class="input" />
<input type="password" name="password" placeholder="Password" class="input" />
<a href="#" class="link">Forgot your password?</a>
<input type="submit" class="btn" value="Sign In"></input>
</form>
</div>
后端使用flask-login插件完成登录,如果不会用这个插件的,可以访问我之前的文章《我用Python写网站》。
@bp.route('/register', methods=['POST', 'GET'])
def register():
if request.method == 'POST':
email = request.form.get('email', '')
username = request.form.get('username', '')
password = request.form.get('password', '')
if email == '' or username == '' or password == '':
flash('注册信息不完整')
return {'msg': '注册信息不完整'}, 201
user = User.query.filter_by(email=email).first()
if user:
flash('邮箱已注册')
return {'msg': '邮箱已注册'}, 201
user = User(email=email,username=username,password=password)
addUser(user) #插入数据库
return redirect(url_for('auth.login'))
return render_template('sigh.html')
@bp.route('/login', methods=['POST', 'GET'])
def login():
if request.method == 'POST':
email = request.form.get('email', '')
password = request.form.get('password', '')
print(email, password)
if email == '' or password == '':
flash('登录信息不完整')
return {'msg': '登录信息不完整'}, 201
user = User.query.filter_by(email=email).first()
if not user:
flash('用户不存在')
return {'msg': '用户不存在'}, 404
if not user.check_password(password):
flash('密码错误')
return {'msg': '密码错误'}, 201
login_user(user)
return redirect(url_for('post.all'))
return render_template('sign.html')
以上代码写的非常粗糙,基本上没有异常的处理,而且登录失败没有页面跳转😫,可以稍微改下。
主要是Editor.md的引入,同样的,《我用Python写网站》里面也都有,我这里直接上代码:
index.html
{% extends 'base.html' %}
{% block style %}
{{super()}}
<link rel="stylesheet" href="{{ url_for('static',filename='css/editormd.css')}}" />
{% endblock %}
{% block content %}
<div class="main_content">
<div class="center_content">
<div class="btn-group">
<button id="show-btn">Show editor</button>
<button id="hide-btn">Hide editor</button>
<button id="get-md-btn">Get Markdown</button>
<button id="get-html-btn">Get HTML</button>
<button id="show-toolbar-btn">Show toolbar</button>
<button id="close-toolbar-btn">Hide toolbar</button>
{% if current_user.is_authenticated %}
<a class="link-btn" href="{{url_for('auth.logout')}}">Quit</a>
<a class="link-btn" href="{{url_for('post.all')}}">Post List</a>
{% else %}
<a class="link-btn" href="{{url_for('auth.register')}}">Sign Up</a>
<a class="link-btn" href="{{url_for('auth.login')}}">Sign In</a>
{%endif%}
</div>
<input id="title" name="title" type="text" value="{{target.title}}" style="width: 100%;" placeholder="请输入文章标题">
</div>
<div id="test-editormd">
<textarea style="display: none;">{% if target %}{{ target.markdown}}{% else %}{% endif %}</textarea>
</div>
</div>
{% endblock %}
{% block script %}
{{super()}}
<script src="{{ url_for('static',filename='js/editormd.js')}}"></script>
<script type="text/javascript">
function debounce(func, wait, immediate) {
let timeout
return function (...args) {
clearTimeout(timeout)
timeout = setTimeout(() => {
timeout = null
if (!immediate) func.apply(this, args)
}, wait)
if (immediate && !timeout) func.apply(this, [...args])
}
};
function update() {
title = $('#title').val();
html = testEditor.getHTML();
mark = testEditor.getMarkdown();
data = {
title: title,
html: html,
markdown: mark
}
$.ajax({
url: '{{url_for("post.edit",id=target.id)}}',
data: JSON.stringify(data),
method: 'post',
dataType: 'json',
contentType: 'application/json',
success: function (data) {
console.log(data.msg);
}
});
}
$('#title').on('input', debounce(update, 3000, false));
var testEditor;
$(function () {
// $.get('test.md', function (md) {
testEditor = editormd("test-editormd", {
width: "90%",
height: 740,
path: '{{url_for("static",filename="editor.md/lib/")}}',
// theme: "dark",
// previewTheme: "dark",
// editorTheme: "pastel-on-dark",
// markdown: "{% if target %}{{ target.markdown.replace('\n','\\n')}}{% else %}{% endif %}",
codeFold: true,
//syncScrolling : false,
saveHTMLToTextarea: true, // 保存 HTML 到 Textarea
searchReplace: true,
//watch : false, // 关闭实时预览
htmlDecode: "style,script,iframe|on*", // 开启 HTML 标签解析,为了安全性,默认不开启
//toolbar : false, //关闭工具栏
//previewCodeHighlight : false, // 关闭预览 HTML 的代码块高亮,默认开启
emoji: true,
taskList: true,
tocm: true, // Using [TOCM]
tex: true, // 开启科学公式TeX语言支持,默认关闭
flowChart: true, // 开启流程图支持,默认关闭
sequenceDiagram: true, // 开启时序/序列图支持,默认关闭,
//dialogLockScreen : false, // 设置弹出层对话框不锁屏,全局通用,默认为true
//dialogShowMask : false, // 设置弹出层对话框显示透明遮罩层,全局通用,默认为true
//dialogDraggable : false, // 设置弹出层对话框不可拖动,全局通用,默认为true
//dialogMaskOpacity : 0.4, // 设置透明遮罩层的透明度,全局通用,默认值为0.1
//dialogMaskBgColor : "#000", // 设置透明遮罩层的背景颜色,全局通用,默认为#fff
imageUpload: true,
imageFormats: ["jpg", "jpeg", "gif", "png", "bmp", "webp"],
imageUploadURL: "{{url_for('post.upload')}}",
onload: function () {
console.log('onload', this);
//this.fullscreen();
//this.unwatch();
//this.watch().fullscreen();
//this.setMarkdown("#PHP");
//this.width("100%");
//this.height(480);
//this.resize("100%", 640);
},
onchange: debounce(update, 3000, false),
});
// });
$("#goto-line-btn").bind("click", function () {
testEditor.gotoLine(90);
});
$("#show-btn").bind('click', function () {
testEditor.show();
});
$("#hide-btn").bind('click', function () {
testEditor.hide();
});
$("#get-md-btn").bind('click', function () {
alert(testEditor.getMarkdown());
});
$("#get-html-btn").bind('click', function () {
alert(testEditor.getHTML());
});
$("#watch-btn").bind('click', function () {
testEditor.watch();
});
$("#unwatch-btn").bind('click', function () {
testEditor.unwatch();
});
$("#preview-btn").bind('click', function () {
testEditor.previewing();
});
$("#fullscreen-btn").bind('click', function () {
testEditor.fullscreen();
});
$("#show-toolbar-btn").bind('click', function () {
testEditor.showToolbar();
});
$("#close-toolbar-btn").bind('click', function () {
testEditor.hideToolbar();
});
$("#toc-menu-btn").click(function () {
testEditor.config({
tocDropdown: true,
tocTitle: "目录 Table of Contents",
});
});
$("#toc-default-btn").click(function () {
testEditor.config("tocDropdown", false);
});
});
</script>
{% endblock %}
简单解释一下,下载Editor.md压缩包,解压后放在static文件夹下面,重命名为editor.md,页面中的css和js文件都可以直接抄editor.md/expample/full.html的引用方式,然后换成jinja的格式就可以了。
需要注意的是:
自动保存
自动保存功能使用onchange实现,Editor.md留的有接口,我在这里使用了一个防抖动的技术,说白了就是在文章修改后的第一时间不上传,而是等停止改动后3秒再上传,这样可以有效的降低服务器压力。
图片上传
图片上传使用imageUploadURL指定上传路径。我这里没有把图片保存在自己的服务器,而是转手把图片上传到了sm.ms,下面会有详细的实现代码。
@bp.route('/edit/<int:id>', methods=['POST', 'GET'])
@login_required
def edit(id=0):
target = Post.query.filter_by(id=id).first()
if not target:
return {'msg': '服务器没有查询到当前文章的信息!'}, 404
if request.method == 'POST':
data = request.json
target.title = data['title']
target.html = data['html']
target.markdown = data['markdown']
print(target.html, target.markdown)
updatePost(target)
return {'msg': 'success'}, 200
return render_template('index.html', target=target)
@bp.route('/all')
@login_required
def all():
post_list = current_user.posts
return render_template('posts.html', post_list=post_list)
@bp.route('/upload', methods=['POST'])
@login_required
def upload():
img = request.files.get('editormd-image-file')
if not img:
return {'success': 0, 'message': 'null'}
headers = {'Authorization': '这里需要写自己的授权码'}
files = {'smfile': img}
url = 'https://sm.ms/api/v2/upload'
res = requests.post(url, files=files, headers=headers).text
import json
js = json.loads(res)
if js.get('success') == True:
url = js.get('data').get('url')
else:
url = js.get('images')
msg = js.get('message')
return {'success': 1, 'message': msg, 'url': url}
edit方法用于更新文章,upload方法用于图片上传。
可以看到代码写的非常脆弱,大佬不要嘲讽我~~
图片上传里面有一个需要注意的地方,就是headers变量中的Authorization值。
这个值需要自己注册sm.ms才能获得,获得方法如下图:

列表页面也是我自己写的唯一一个页面,主要没得抄,当然也是非常简单的。
功能就是展示所有的文章,也没有使用分页功能。
后端代码就是上面代码的all()函数,这里不再重复,简单贴一下前端代码:
posts.html
{% extends 'base.html' %}
{% block style %}
{{super()}}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.8.1/font/bootstrap-icons.css">
{% endblock %}
{% block content %}
<div class="main_content">
<div class="center_content">
<div>
<span style="font-size: 18px;font-weight: bold ;">文章列表</span>
{% if current_user.is_authenticated %}
<a class="link-btn" href="{{url_for('auth.logout')}}">Quit</a>
{% else %}
<a class="link-btn" href="{{url_for('auth.register')}}">Sign Up</a>
<a class="link-btn" href="{{url_for('auth.login')}}">Sign In</a>
{%endif%}
</div>
<br>
<ul class="post_list">
<li><a href="{{url_for('post.add')}}" style="color: rgb(45, 141, 128);"><i class="bi bi-plus-circle"></i>
Create a new
post</a></li>
{% if post_list %}
{% for post in post_list | reverse %}
<li>
<a href="{{url_for('post.edit',id=post.id)}}"> {{ post.created.strftime('%Y-%m-%d %H:%M:%S')}}《{{
post.title}}》 </a>
</li>
{% endfor %}
{% endif %}
</ul>
</div>
</div>
{% endblock %}
这里没啥好解释的,还是那句话,如果有兴趣可以看我之前的文章《我用Python写网站》。
欢迎大家留言讨论,这点我还算熟悉~~~
还有图标呢~~~
😍
我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden
无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD
导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵
华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o
C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.
1.postman介绍Postman一款非常流行的API调试工具。其实,开发人员用的更多。因为测试人员做接口测试会有更多选择,例如Jmeter、soapUI等。不过,对于开发过程中去调试接口,Postman确实足够的简单方便,而且功能强大。2.下载安装官网地址:https://www.postman.com/下载完成后双击安装吧,安装过程极其简单,无需任何操作3.使用教程这里以百度为例,工具使用简单,填写URL地址即可发送请求,在下方查看响应结果和响应状态码常用方法都有支持请求方法:getpostputdeleteGet、Post、Put与Delete的作用get:请求方法一般是用于数据查询,
MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO
需求:要创建虚拟机,就需要给他提供一个虚拟的磁盘,我们就在/opt目录下创建一个10G大小的raw格式的虚拟磁盘CentOS-7-x86_64.raw命令格式:qemu-imgcreate-f磁盘格式磁盘名称磁盘大小qemu-imgcreate-f磁盘格式-o?1.创建磁盘qemu-imgcreate-fraw/opt/CentOS-7-x86_64.raw10G执行效果#ls/opt/CentOS-7-x86_64.raw2.安装虚拟机使用virt-install命令,基于我们提供的系统镜像和虚拟磁盘来创建一个虚拟机,另外在创建虚拟机之前,提前打开vnc客户端,在创建虚拟机的时候,通过vnc
遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg
通常,数组被实现为内存块,集合被实现为HashMap,有序集合被实现为跳跃列表。在Ruby中也是如此吗?我正在尝试从性能和内存占用方面评估Ruby中不同容器的使用情况 最佳答案 数组是Ruby核心库的一部分。每个Ruby实现都有自己的数组实现。Ruby语言规范只规定了Ruby数组的行为,并没有规定任何特定的实现策略。它甚至没有指定任何会强制或至少建议特定实现策略的性能约束。然而,大多数Rubyist对数组的性能特征有一些期望,这会迫使不符合它们的实现变得默默无闻,因为实际上没有人会使用它:插入、前置或追加以及删除元素的最坏情况步骤复