Editor.md 使用小結

php_yt發表於2021-04-10

概要

  • Editor.md - 開源線上 Markdown 編輯器
  • 圖片上傳:
    1. csrf 問題
    2. 補充複製貼上、拖拽上傳
  • 意外離開頁面的措施
    1. localStorge 儲存正在編輯的內容
    2. 離開頁面時提示
  • TOC目錄
  • 專案實踐

下載

百度搜尋進入 github 下載即可。

Editor.md 使用小節

開發中使用 editormd.js,上線後使用 min.js

頁面中使用

Editor.md 使用小結

<!-- 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>

瞭解 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 使用小結

處理表單

檢查元素

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));
}

圖片上傳

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);
}

專案實踐 (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>
本作品採用《CC 協議》,轉載必須註明作者和本文連結
focus

相關文章