Flask系列:开发自己的所见即所得Markdown 编辑器



这里先祝大家新年快乐!谢谢大家一直以来的支持。
Flask系列索引,
开发自己的所见即所得Markdown 编辑器
不写一行Javascript实现省市区三级联动
几行代码轻松实现图片点击、黏贴、拖拽上传和预览
将机器学习模型部署为REST API
几款基于Flask框架的企业级开源软件
通过GitHub快速部署Flask App 到 Heroku Cloud
在 Heroku 上部署 Python Flask App
用Flask来渲染Markdown
Python开发的Web应用方便分发吗?
编辑器功能特点,
浏览器端的 Markdown 编辑器
支持三种编辑模式:所见即所得(wysiwyg)、即时渲染(ir,类似 Typora)、分屏预览(sv)
支持大纲、数学公式、脑图、图表、流程图、甘特图、时序图、五线谱、多媒体、语音阅读、标题锚点、代码高亮及复制、graphviz、PlantUML 渲染
内置安全过滤、导出、图片懒加载、任务列表、多平台预览、多主题切换、复制到微信公众号/知乎功能
实现 CommonMark 和 GFM 规范,可对 Markdown 进行格式化和语法树查看,并支持 10+ 项配置
工具栏包含 36+ 项操作,除支持扩展外还可对每一项中的快捷键、提示、提示位置、图标、点击事件、类名、子工具栏进行自定义
表情/at/话题等自动补全扩展
可使用拖拽、剪切板粘贴上传,显示实时上传进度,支持 CORS 跨域上传
实时保存内容,防止意外丢失
录音支持,用户可直接发布语音
粘贴 HTML 自动转换为 Markdown,如粘贴中包含外链图片可通过指定接口上传到服务器
支持主窗口大小拖拽、字符计数
多主题支持,内置黑白绿三套主题
多语言支持,内置中、英、韩文本地化
支持主流浏览器,对移动端友好
好吧,如果你看了我的终于找到一款比 Typora 还好用的免费 Markdown 编辑器可能会想,这不是思源的功能吗?
没有错,今天要用的就是思源所使用的编辑器核心组件,Vditor, 来开发我们自己的Markdown编辑器。
index.html
<html><head>    <link rel="stylesheet" href="//cdn.jsdelivr.net/npm/vditor/dist/index.css" />    <script src="//cdn.jsdelivr.net/npm/vditor/dist/index.min.js"></script>    <script src="//cdn.jsdelivr.net/npm/vditor/dist/js/lute/lute.min.js"></script>    <script src="//cdn.jsdelivr.net/npm/vditor/dist/js/highlight.js/highlight.pack.js"></script>    <script src="//cdn.jsdelivr.net/npm/vditor/dist/js/mermaid/mermaid.min.js"></script>    <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"        integrity="sha512-894YE6QWD5I59HgZOGReFYm4dnWc1Qt5NtvYSaNcOP+u1T9qYdvdihz0PPSiiqn/+/3e7Jo4EaG7TubfWGUrMQ=="        crossorigin="anonymous" referrerpolicy="no-referrer"></script>    <script>        window.Lute = window.Lute || {}        window.hljs = window.hljs || {}    </script>    <style>        .header {            background-color: #fff;            box-shadow: rgba(0, 0, 0, 0.05) 0 1px 7px;            border-bottom: 1px solid #e1e4e8;        }    </style>    <link rel="stylesheet" type="text/css" href="//fonts.googleapis.com/css?family=Open+Sans" /></head><body>    <h1>Markdown Editor</h1>    <script>        function save(event) {            event.preventDefault();            var fname = document.getElementById("fname").value;            alert(fname)            $.ajax({                type: 'POST', url: "/vditor/save/"                , data: JSON.stringify({                    'fname': fname,                    'content': vditor.getValue(),                })                , dataType: 'json'                , contentType: 'application/json'                , success: function (data) {                    console.log("success")                }            });        }    </script>    file name:<input type="text" id="fname" value="untitled.md"><button onclick="save(event)">Save</button>    <div id="vditor"></div>    <script>        var vditor = new Vditor('vditor', {            "height": 720,            "cache": {                "enable": false            },            "value": "## 所见即所得(WYSIWYG)所见即所得模式对不熟悉 Markdown 的用户较为友好,熟悉 Markdown 的话也可以无缝使用。 ",            "mode": "wysiwyg",            upload: {                url: '/vditor/uploads/',                linkToImgUrl: '/static/uploads/',                accept: '.jpg,.png,.gif,.jpeg',                filename(name) {                    return name.replace(/\?|\\|\/|:|\||<|>|\*|\[|\]|\s+/g, '-')                },            },        })    </script></body></html>
HTML 模版部分主要分几部分,
vditor 所需的 JavaScript 库和 css 库
    <link rel="stylesheet" href="//cdn.jsdelivr.net/npm/vditor/dist/index.css" />    <script src="//cdn.jsdelivr.net/npm/vditor/dist/index.min.js"></script>    <script src="//cdn.jsdelivr.net/npm/vditor/dist/js/lute/lute.min.js"></script>    <script src="//cdn.jsdelivr.net/npm/vditor/dist/js/highlight.js/highlight.pack.js"></script>    <script src="//cdn.jsdelivr.net/npm/vditor/dist/js/mermaid/mermaid.min.js"></script>
实现 ajax 保存所需的 jquery 库 及 ajax 保存 markdown 内容的 JavaScript 代码。
    <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"        integrity="sha512-894YE6QWD5I59HgZOGReFYm4dnWc1Qt5NtvYSaNcOP+u1T9qYdvdihz0PPSiiqn/+/3e7Jo4EaG7TubfWGUrMQ=="        crossorigin="anonymous" referrerpolicy="no-referrer"></script>    <script>        function save(event) {            event.preventDefault();            var fname = document.getElementById("fname").value;            alert(fname)            $.ajax({                type: 'POST', url: "/vditor/save/"                , data: JSON.stringify({                    'fname': fname,                    'content': vditor.getValue(),                })                , dataType: 'json'                , contentType: 'application/json'                , success: function (data) {                    console.log("success")                }            });        }    </script>
vditor 初始化脚本, 包括图片上传的配置(upload 部分)。
    <div id="vditor"></div>    <script>        var vditor = new Vditor('vditor', {            "height": 720,            "cache": {                "enable": false            },            "value": "## 所见即所得(WYSIWYG)所见即所得模式对不熟悉 Markdown 的用户较为友好,熟悉 Markdown 的话也可以无缝使用。 ",            "mode": "wysiwyg",            upload: {                url: '/vditor/uploads/',                linkToImgUrl: '/static/uploads/',                accept: '.jpg,.png,.gif,.jpeg',                filename(name) {                    return name.replace(/\?|\\|\/|:|\||<|>|\*|\[|\]|\s+/g, '-')                },            },        })    </script>
模式设置为所见即所得(wysiwyg),编辑器支持模式切换,如下图,

image.png
app.py
from  hashlib import md5from pathlib import Pathfrom flask import Flask,request,jsonify,render_templateimport osapp = Flask(__name__)app.config.from_object('config')from flask_wtf.csrf import CSRFProtectcsrf = CSRFProtect(app)@app.route("/")def index():    return render_template("index.html")@csrf.exempt@app.route('/vditor/uploads/',methods=['POST'])def vditor_uploads():    """    支持黏贴、拖拽和点击图片上传    """    images_upload = request.files.get('file[]', None)    img = images_upload.stream.read()    digest=md5(img).hexdigest()    suffix = Path(images_upload.filename).suffix    images_name = f'{digest}{suffix}'    image_full_name = os.path.join(app.config['IMG_UPLOAD_FOLDER'], images_name)    if not Path(image_full_name).exists():        with open(image_full_name,"wb") as f :            f.write(img)    image_full_path = os.path.join(app.config['IMG_UPLOAD_URL'], images_name)    # 返回的json有指定的结构    return jsonify(                            {                    "msg": "Success!",                    "code": 0,                    "data": {                    "errFiles": [],                    "succMap": {                        images_upload.filename: image_full_path,                        }                    }                }        ),200@csrf.exempt@app.route('/vditor/save/',methods=['POST'])def vditor_save():    """"    markdown 保存    json格式    """    data = request.json    print(data['fname'])    print(data['content'])    # save it    return jsonify({"msg":0}),200
后台部分,目前实现两个功能,
图片的保存(前端支持黏贴,拖拽和点击上传)
markdown 的保存(具体实现逻辑没有写)
后端的注意点在于图片保存后返回的 json 格式是固定的,这个需要注意。
只需要简单的代码,一个所见即所得的 Markdown 编辑器就实现了。
欢迎关注公众号

有兴趣加群讨论数据挖掘和分析的朋友可以加我微信(witwall),暗号:入群

也欢迎投稿!
到顶部