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
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

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