天天看點

Vue實作一個MarkDown編輯器

Vue實作一個markdown編輯器

前段時間做項目的時候,需要一個Markdown編輯器,在網上找了一些開源的實作,但是都不滿足需求

說實話,這些開源項目也很難滿足需求公司項目的需求,與其實作一個大而全的項目,倒不如實作一個

簡單的,易于在源碼上修改的項目,核心功能都有的,以供修改使用

本文的源碼位址如下

https://github.com/jiulu313/HelloMarkDown

喜歡的朋友可以幫忙star一下,歡迎交流學習

先看一下本項目的效果圖(圖檔經過壓縮)

Vue實作一個MarkDown編輯器

本文的目的就是實作一個有核心功能的,簡單,易于修改的項目

話不多說,來看思路

1 markdown内容如何轉換成 html?

網上有一個開源的庫叫 marked,位址如下:

https://github.com/markedjs/marked.git

我們可以安裝這個庫,使用很簡單,就一個函數,傳進去markdown内容,就傳回了html内容

2 markdown内容轉換成了html,如何進行文法高亮?

網上也有一個開源的庫,位址如下 :

https://highlightjs.org/

我們可以使用這兩個庫

  1. 先把markdown内容解析成html内容
  2. 把html内容進行文法高亮

下面我們來一步一步實作代碼

3 代碼實作

預設你已經建立好了vue的項目 , 建立vue項目

vue init webpack demo

這裡面不多講。

3.1 安裝兩個庫,分别執行下面兩條指令

npm install marked --save

npm install highlight.js --save

3.2 首先建立一個

HelloMarkDown

Vue

元件

布局檔案的代碼如下:

<template>
  <div class="md_root_content" v-bind:style="{width:this.width,height: this.height}">

    <!--功能按鈕區-->
    <div class="button_bar">
      <span v-on:click="addBold"><B>B</B></span>
      <span v-on:click="addUnderline"><B>U</B></span>
      <span v-on:click="addItalic"><B>I</B></span>
    </div>

    <!--主要内容區-->
    <div class="content_bar">

      <!--markdown編輯器區-->
      <div class="markdown_body">
        <textarea ref="ref_md_edit" class="md_textarea_content" v-model="markString">
        </textarea>
      </div>

      <!--解析成html區-->
      <div class="html_body">
        <p v-html="htmlString"></p>
      </div>

    </div>

  </div>
</template>           

主要分為上下兩塊,上面是功能區的布局

下面一塊,分左右兩部分,左邊是markdown,右邊是顯示html部分

對應的樣式代碼如下:

<style scoped>

  .md_root_content {
    display: flex;
    display: -webkit-flex;
    flex-direction: column;
  }

  .button_bar {
    width: 100%;
    height: 40px;
    background-color: #d4d4d4;
    display: flex;
    display: -webkit-flex;
    align-items: center;
  }

  div.button_bar span {
    width: 30px;
    line-height: 40px;
    text-align: center;
    color: orange;
    cursor: pointer;
  }

  .content_bar {
    display: flex;
    display: -webkit-flex;
    width: 100%;
    height: 100%;
  }

  .markdown_body {
    width: 50%;
    height: 100%;
    display: flex;
    display: -webkit-flex;
  }

  .html_body {
    width: 50%;
    height: 100%;
    display: flex;
    display: -webkit-flex;
    background-color: #dfe9f1;
  }

  .md_textarea_content {
    flex: 1;
    height: 100%;
    padding: 12px;
    overflow: auto;
    box-sizing: border-box;
    resize: none;
    outline: none;
    border: none;
    background-color: #f4f4f4;
    font-size: 14px;
    color: #232323;
    line-height: 24px;
  }


</style>
           

業務邏輯部分的代碼如下:

<script>
  import marked from 'marked'     //解析mardown文法的庫
  import hljs from 'highlight.js' //對代碼進行文法高亮的庫


  import testData from '../testData'  //測試資料


  export default {
    name: "HelloMarkDown",

    props: {
      width: {
        type: String,
        default: '1000px'
      },

      height: {
        type: String,
        default: '600px'
      }
    },

    data() {
      return {
        markString: '',
        htmlString: '',
      }
    },

    mounted(){
      this.markString = testData
    },

    methods: {
      //加粗
      addBold() {
        this.changeSelectedText("**","**")
      },

      //斜體
      addItalic() {
        this.changeSelectedText("***","***")
      },

      addUnderline() {
        this.changeSelectedText("<u>","</u>")
      },

      changeSelectedText(startString,endString){
        let t = this.$refs.ref_md_edit
        if (window.getSelection) {
          if (t.selectionStart != undefined && t.selectionEnd != undefined) {

            let str1 = t.value.substring(0, t.selectionStart)
            let str2 = t.value.substring(t.selectionStart, t.selectionEnd)
            let str3 = t.value.substring(t.selectionEnd)

            let result = str1 + startString + str2 + endString + str3
            t.value = result
            this.markString = t.value
          }
        }
      }
    },

    watch: {

      //監聽markString變化
      markString: function (value) {
        marked.setOptions({
          renderer: new marked.Renderer(),
          gfm: true,
          tables: true,
          breaks: true,
          pedantic: false,
          sanitize: false,
          smartLists: true,
          smartypants: false
        })

        this.htmlString = marked(value)
      },

      //監聽htmlString并對其高亮
      htmlString: function (value) {
        this.$nextTick(() => {
          const codes = document.querySelectorAll(".html_body pre code");

          // elem 是一個 object
          codes.forEach(elem => {
            elem.innerHTML = "<ul><li>" + elem.innerHTML.replace(/\n/g, "\n</li><li>") + "\n</li></ul>"
            hljs.highlightBlock(elem);
          });
        });
      }
    }

  }
</script>           

script中的代碼解釋

props: {
      width: {
        type: String,
        default: '1000px'
      },

      height: {
        type: String,
        default: '600px'
      }
    },           

width: 元件的寬度

height:元件的高度

data() {
      return {
        markString: '',
        htmlString: '',
      }
    },           

markString:儲存我們輸入的markdown内容

htmlString:儲存markdown内容轉換成的html内容,也就是通過marked函數轉換過來的

mounted(){
      this.markString = testData
    },           

顯示預設資料

//加粗
      addBold() {
        this.changeSelectedText("**","**")
      },

      //斜體
      addItalic() {
        this.changeSelectedText("***","***")
      },

      //加下劃線
      addUnderline() {
        this.changeSelectedText("<u>","</u>")
      },           

這三個函數都是調用了 changeSelectedText 函數

主要是對滑鼠選中的内容進行改變,比如加粗效果,是在選中文本的兩邊分别添加 **

是以changeSelectedText函數的作用就是在選中的文本兩邊添加不同的md的符号

比如

this.changeSelectedText("","") ,就是在選中的文本左邊和右邊都添加**

然後再把最新的内容指派給 this.$refs.ref_md_edit.value,同時也兩會給markString

這樣就可以做到選中文本加粗效果了

//監聽markString變化
      markString: function (value) {
        marked.setOptions({
          renderer: new marked.Renderer(),
          gfm: true,
          tables: true,
          breaks: true,
          pedantic: false,
          sanitize: false,
          smartLists: true,
          smartypants: false
        })

        this.htmlString = marked(value)
      },           

此時是監聽markString的變化

然後調用marked函數進行轉換成html内容,并指派給htmlString

marked.setOptions 是設定一些配置,有興趣的可以查一下這些配置的作用

//監聽htmlString并對其高亮
      htmlString: function (value) {
        this.$nextTick(() => {
          const codes = document.querySelectorAll(".html_body pre code");

          // elem 是一個 object
          codes.forEach(elem => {
            elem.innerHTML = "<ul><li>" + elem.innerHTML.replace(/\n/g, "\n</li><li>") + "\n</li></ul>"
            hljs.highlightBlock(elem);
          });
        });
      }           

原本通過 highlight.js這個庫在顯示文法高亮的時候,是沒有行号的。這裡我進行了擴充

通過 document.querySelectorAll(".html_body pre code") 找到nodeList

然後對其循環,動态添加 ul , li, 這樣就可以顯示行号了

不過這需要對 highlight的css檔案添加幾個樣式

源碼裡面我把highlight中的css檔案全部copy到項目中了,使用的是github.css

具體位置是在項目中的 assets/markdown/styles/github.css

如果想使用其它的主題,可以自己修改其它的對應的css檔案,這裡使用了github的主題,是以隻修改了github.css這一個檔案

有興趣的可以檢視一下

github.css檔案的送出記錄

具體的思路就是這些,水準有限,難免有bug,如有發現,歡迎提出

posted on 2019-05-18 21:01 九路313 閱讀(...) 評論(...) 編輯 收藏

繼續閱讀