I spent a lot of time in a rich text editor and looked at the package. What? How could a rich text box take up 20K? Why should I use such a complicated thing? Why not write my own editor, which also happens to be a Markdown editor

showdown

From rich text boxes to Markdown editors, whose core logic is to convert strings into HTML, there’s a package: Haggis, which is a package of Markdown’s core logic

var showdown  = require('showdown'),
    converter = new showdown.Converter(),
    text      = '# hello, markdown! ',
    html      = converter.makeHtml(text);
Copy the code

This is a succinct version of the package on NPM, which is actually quite easy;

interface

I also designed it according to most editors

But this is separated from the textarea, so when we change the text, the number on the left does not move: So we made a scroll in the textarea events, many of the editor is another associated with sliding sliding will drive on the left side of the edit box to the right of the preview page, but the preview page will be picture it is All sorts of things, so it’s will be longer than the edit box on the left, so I used is proportional to, probably it should be no problem.

changeScroll(scrollEvent: any) {
        (this.$refs['GUl'] as any).scroll({top: scrollEvent.target.scrollTop});
        (this.$refs['GMarkdownContent'] as any).scroll({top: (this.$refs['GMarkdownContent'] as Element).scrollHeight * (scrollEvent.target.scrollTop / scrollEvent.target.scrollHeight)});
    }
Copy the code

We bind a Text parameter to the Textarea, listen for the change, generate the HTML from the original text, preview it on the right, and just put it in v-HTML. I’m using TS and vue-property-decorator

    @Watch('text')
    change() {
        this.lineLength = Math.floor((this.$refs['g-textarea'] as Element).scrollHeight / 25);
        this.html = this.converter.makeHtml(this.localText);
        this.$forceUpdate(a); }Copy the code

This change is triggered when we change the text, the lineLength is used to mark the li on the left, and it counts the Li each time; That’s pretty much the basic version of the thing

toolbar

As an editor, you must have some tools, such as “preview”, “undo” and “redo”. I used an array to store tools, which I defined as “name”, “icon”, “callback” and “disabled”

preview

I used a Preview attribute to indicate whether it was preview or not, and then defined the tool callback to change the proView, but there was a problem with this. Callback did not change the value, so I used an external function

            toolName: 'preView',
            icon: require('@i/images/split.png'),
            cb: (data: any) => {
                this.changeAttr('preView', !data.split);
                this.$forceUpdate(a); },disable: false
Copy the code

Undo and redo

Before using stack management, now optimized, I use the feeling of sliding window, so as to ensure that there must be 5 in the window, and there will be no problems, convenient operation; When we change the markdown text, we add shakiness to the queue of five, head out and tail in, and then define the cursor as tail, and change the character by changing the cursor position;

<! -- Add queue -->if (this.cursor === this.history.length - 1 || this.history.length === 0) {
                if (this.historyTimer) {
                    clearTimeout(this.historyTimer);
                }
                this.historyTimer = setTimeout(() => {
                    if(this.history.length > 5) { this.history.shift(); } this.history.push(this.localText); this.cursor = this.history.length - 1; clearTimeout(this.historyTimer); }, 1000); } // Specify the operationundo() {
            if(this.cursor > 0) { this.cursor--; this.localText = this.history[this.cursor]; }}redo() {
            if(this.cursor < 5) { this.cursor++; this.localText = this.history[this.cursor]; }}Copy the code

extension

Of course, this is too rigid for our toolbars, so the component can be extended by passing in an array of tools that are concatenated to the toolsbar and then used on the page that calls the component;

    this.toolbar = this.toolbar.concat(this.actionExtend);

    <div v-for="item of toolbar" @click="item.cb({text:localText,html:html,preView,self:item})" class=" mx-2"
           :class="[item.disable? 'disabled': 'cursor-pointer']">
        <template v-if="item.icon">
          <img :src="item.icon" width="20" height="20"> </template> <template V-else > {{item.toolName}} </template> </div> // Call page <MarkdownEditor :text="text" :action-extend="actionlist" @change="textChange">
    </MarkdownEditor>
   
    actionlist: ToolBarItem[] = [{
        toolName: 'save',
        icon: require('@i/images/md-save.png'), cb: (data: any) => { this.save(data); }}];Copy the code

This basically completes a basic markdown