暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

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

alitrack 2022-01-02
1148

这里先祝大家新年快乐!谢谢大家一直以来的支持。

Flask系列索引,


编辑器功能特点,

  • 浏览器端的 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-shadowrgba(0000.050 1px 7px;
            border-bottom1px 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/"
                , dataJSON.stringify({
                    'fname': fname,
                    'content': vditor.getValue(),
                })
                , dataType'json'
                , contentType'application/json'
                , successfunction (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)\n所见即所得模式对不熟悉 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/"
                    , dataJSON.stringify({
                        'fname': fname,
                        'content': vditor.getValue(),
                    })
                    , dataType'json'
                    , contentType'application/json'
                    , successfunction (data{
                        console.log("success")
                    }
                });
            }

        
    </script>

  • vditor 初始化脚本, 包括图片上传的配置(upload 部分)。

        <div id="vditor"></div>
        <script>
            var vditor = new Vditor('vditor', {
                "height"720,
                "cache": {
                    "enable"false
                },
                "value""## 所见即所得(WYSIWYG)\n所见即所得模式对不熟悉 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 md5
from pathlib import Path
from flask import Flask,request,jsonify,render_template
import os
app = Flask(__name__)
app.config.from_object('config')
from flask_wtf.csrf import CSRFProtect


csrf = 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

后台部分,目前实现两个功能,

  1. 图片的保存(前端支持黏贴,拖拽和点击上传)
  2. markdown 的保存(具体实现逻辑没有写)

后端的注意点在于图片保存后返回的 json 格式是固定的,这个需要注意。


只需要简单的代码,一个所见即所得的 Markdown 编辑器就实现了。

欢迎关注公众号

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

也欢迎投稿!


文章转载自alitrack,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论