Editor.md 使用小结

Editor.md 使用小结

概要

  • Editor.md - 开源在线 Markdown 编辑器
  • 图片上传:
    1. csrf 问题
    2. 补充复制粘贴、拖拽上传
  • 意外离开页面的措施
    1. localStorge 保存正在编辑的内容
    2. 离开页面时提示
  • TOC目录
  • 项目实践

1、下载

百度搜索进入 github 下载即可。

Editor.md 使用小节

开发中使用 editormd.js,上线后使用 min.js

2、页面中使用

<!-- laravel框架注意添加这个 meta -->
<meta name="_token" content="{{ csrf_token() }}">
<!-- 引入editormd.css -->
<link href="{{asset('static/editormd')}}/css/editormd.css" rel="stylesheet">

<form class="layui-form" accept-charset="UTF-8">
    @csrf
    <!--保存TOC-->
    <input id="markdownToC" type="hidden" name="toc" value="">

    <!-- MD editor -->
    <div id="editor">
        <textarea
            style="display:none;"
            class="form-control"
            id="content-editormd-markdown-doc"
            name="body_original"
            place-holder="请使用Markdown语法"
        ></textarea>
    </div>
</form>

<!-- 当然 jquery 怎么能少呢 -->
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.0.0/jquery.min.js"></script>
<!-- 引入 editormd.js -->
<script src="{{asset('static/editormd')}}/editormd.js"></script>
<script>
// 这里你需要判断是编辑模式还是创建模式
var model = "{{isset($article) ? 'edit' : 'create'}}";
// 是否是发布、保存草稿、保存修改,而不是意外离开页面
var isSubmit = false;

// 检测是否支持本地存储
function enableLocalStorage(){
    // 菜鸟教程里推荐的写法
      if(localStorage){
        try {
          localStorage.setItem("test", "yes");
          localStorage.removeItem("test");
          return true;
        } catch (err) {
          console.log(err);
        }
      }
      return false;
}
var enableLS = enableLocalStorage();

// 编辑器
var editor = editormd("editor", {
        width  : "100%",
        height : 600,
        fontSize:"14px",
        placeholder:'请使用 Markdown 语法',
        lineNumbers:false, //行号
        styleActiveLine:false, //当前行高亮
        // tocContainer : "#test",//你可以将TOC结构输出到自定义容器中
        path   : "{{asset('static')}}/editormd/lib/",
        toolbarIcons : function() {
            return ["h3","h4","bold", "quote", "hr","|", "list-ul","list-ol", "|","link", "image", "table", "|", "watch", "fullscreen","preview"]
        },
        syncScrolling: true, //左右侧预览同步
        toolbarAutoFixed:true,//工具栏自动固定定位的开启与禁用
        saveHTMLToTextarea : true, // 保存 HTML 到 Textarea
        imageUpload : true, //图片上传
        imageFormats : ["jpg", "jpeg", "gif", "png"],//上传图片格式
        imageUploadURL : "{{route('img-upload')}}",//图片上传URL
        onload : function() {
          console.log(this);
          // 如果浏览器支持本地存储
          if(enableLS){
            let markdownBody = localStorage.getItem('markdownBody');
            // 注意 只有创建时才使用本地存储
            if(markdownBody && model=='create'){
              this.setMarkdown(markdownBody);//填充markdown编辑区域
            }
          }
        },
        onchange: function(){
          // 提取TOC
          $('#markdownToC').val(JSON.stringify(this.markdownToC));
         // 保存编辑区域的内容
          if(enableLS && model=='create'){
            localStorage.setItem("markdownBody", this.getMarkdown());
          }
        }
});

// 监听离开页面动作
window.onbeforeunload=function(e){
    // 如果不是点击的发布文章或保存草稿或保存修改 离开页面时提示
    if(!isSubmit){
        return "确定离开页面?系统可能不会保存更改";
    }
}
</script>

3、了解 editormd 的数据结构

上方 onload 中我们添加了 console.log(this)

onload : function() {
    console.log(this);
}

Editor.md 使用小结

你可以了解下有用的信息如:

  • classPrefix: "editormd-" 类前缀
  • markdownToC: [] TOC目录可以 this.markdownToC 获得
  • 你可以找到所有的 settings 而不用到处找文档
    Editor.md 使用小结
    Editor.md 使用小结
    等等,我只截了一部分
  • 所有的工具栏
    Editor.md 使用小结

4、处理表单

检查元素

Editor.md 使用小结

可以看到下面的 textareaeditormd 生成的保存 html 源码的表单
那么后端就可以这么接收

$article['body_original'] = $post['body_original'];
$article['body_html'] = $post['editor-html-code'];

关于 TOC 目录表单,已在上面代码中体现了,即在 onchange

<input id="markdownToC" type="hidden" name="toc" value="">
onchange: function(){
    $('#markdownToC').val(JSON.stringify(this.markdownToC));
}

5、图片上传

后端交互返回数据格式

// MD编辑器 $code=0|1 success时为1
public static function mdImgUploadRet($code, $message='', $url='')
{
    return response()->json([
    'success' => $code,
    'message'  => $message,
    'url'  => $url
    ], 200);
}

laravel 框架图片上传 csrf 问题

所说的就是编辑器工具栏的上传图片
Editor.md 使用小结

我们需要改动下 editormd 的一个图片文件的源码
/editormd/plugins/image-dialog/image-dialog.js
大概在 49 行左右,它创建了一个表单 form,我们只需要添加一个 csrfinputform 里就可以了。

Editor.md 使用小结

// 添加代码 1
var csrfToken = $('meta[name="_token"]').attr('content');
var csrfField = csrfToken ? "<input type='hidden' name='_token' value='" + csrfToken + "' />": '';

// 添加代码 2
+ csrfField +

补充复制粘贴、拖入上传

需要引入一个插件 paste-upload-img.js,依赖 jquery.js

function initPasteDragImg(Editor){
    var doc = document.getElementById(Editor.id)
    doc.addEventListener('paste', function (event) {
        var items = (event.clipboardData || window.clipboardData).items;
        var file = null;
        if (items && items.length) {
            // 搜索剪切板items
            for (var i = 0; i < items.length; i++) {
                if (items[i].type.indexOf('image') !== -1) {
                    file = items[i].getAsFile();
                    break;
                }
            }
        } else {
            console.log("当前浏览器不支持");
            return;
        }
        if (!file) {
            console.log("粘贴内容非图片");
            return;
        }
        uploadImg(file,Editor);
    });
    var dashboard = document.getElementById(Editor.id)
    dashboard.addEventListener("dragover", function (e) {
        e.preventDefault()
        e.stopPropagation()
    })
    dashboard.addEventListener("dragenter", function (e) {
        e.preventDefault()
        e.stopPropagation()
    })
    dashboard.addEventListener("drop", function (e) {
        e.preventDefault()
        e.stopPropagation()
     var files = this.files || e.dataTransfer.files;
     uploadImg(files[0],Editor);
     })
}
function uploadImg(file,Editor){
    var formData = new FormData();
    var fileName=new Date().getTime()+"."+file.name.split(".").pop();
    formData.append('editormd-image-file', file, fileName);

    // 如果使用的 laravel 框架需要加上
    var csrfToken = $('meta[name="_token"]').attr('content');
    formData.append('_token', csrfToken);

    $.ajax({
        url: Editor.settings.imageUploadURL,
        type: 'post',
        data: formData,
        processData: false,
        contentType: false,
        dataType: 'json',
        success: function (msg) {
            var success=msg['success'];
            if(success==1){
                var url=msg["url"];
                if(/\.(png|jpg|jpeg|gif|bmp|ico)$/.test(url)){
                    Editor.insertValue("![图片alt]("+msg["url"]+" ''图片title'')");
                }else{
                    Editor.insertValue("[下载附件]("+msg["url"]+")");
                }     
            }else{
                console.log(msg);
                alert("上传失败");
            }
        }
    });
}

然后在 onload 中添加

onload : function() {
    initPasteDragImg(this);
}

6、项目实践 (laravel)

Route::resource('articles', 'ArticleController');//资源路由
Route::post('articles/create/upload', 'ArticleController@upload')->name('img-upload');//图片上传

关于资源路由

class ArticleController extends Controller
{
    // 文章列表
    // GET /articles route('articles.index')(路由命名以下不再备注)
    public function index()

    // 显示创建页面
    // GET /articles/create route('articles.create')
    public function create()

    // 保存你创建的数据
    // POST /articles route('articles.store')
    public function store(Request $request)

    // 显示对应id的文章内容
    // GET articles/1 route('articles.show',['article'=>$article->id])
    public function show($id)

    // 显示编辑表单
    // GET articles/1/edit route('articles.edit',['article'=>$article->id])
    public function edit($id)

    // 保存编辑的数据
    // PUT/PATCH articles/1 route('articles.update',['article'=> $article->id])
    public function update(Request $request, $id)

    // 删除
    // DELETE articles/1 route('articles.destroy',['article'=>$article->id])
    public function destroy($id)
}

关于 blade目录结构 (仅供参考)

views
 -- articles
 ----index.blade.php
 ----create.blade.php
 ----show.blade.php
 ----edit.blade.php

关于提取文章摘要

$desc = strip_tags($post['editor-html-code']); // 去除html标签
$article['desc'] = trim(mb_substr($desc,0,40)); // 提取前40个字

关于 TOC

前端传入 JSON.stringify(this.markdownToC) 格式如下

[{"text":"标题","level":3,"slug":"-"},{"text":"文本加粗","level":3,"slug":"-"},{"text":"引用","level":3,"slug":"-"},{"text":"全屏","level":3,"slug":"-"},{"text":"如何上传图片","level":3,"slug":"-"}]

后端直接保存到数据库即可

$article['toc'] = $post['toc'];
//提取时
$toc = json_decode($article->toc,true);

显示时示例

<ul>
@foreach($toc as $item)
<li style="padding: 7px 0;white-space:nowrap;text-overflow:ellipsis;overflow:hidden;">
    <span>{{str_repeat(' ', $item['level'])}}</span>
    <span><a href="#{{$item['text']}}" >{{$item['text']}}</a></span>
</li>
@endforeach
</ul>

关于如何显示页面

editormd 保存的 html 是没有任何样式的,你可以自定义样式、或者 copy 一份 editormd.css 剔除用不到的样式,然后自定义样式。

本作品采用《CC 协议》,转载必须注明作者和本文链接
welcome come back
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!
未填写
文章
95
粉丝
24
喜欢
156
收藏
347
排名:323
访问:2.9 万
私信
所有博文
社区赞助商