Implement a Nuggets Style article editor

foreword

I am a heavy user of Nuggets. Not only do I often mine articles with high gold content on Nuggets, but also occasionally create technical articles on Nuggets. I believe readers are also very satisfied with Nuggets, especially its article editor, which not only supports Markdown editing, but also supports code highlighting, split-screen preview, automatic saving, and more. This article will use React+CodeMirror+Showdown to implement a single-page application similar to the Nuggets editor.

Motion picture effect

Don't say so much, let's start with the animation effect.

layout

Below is the layout of the Nuggets article editor.

As you can see, the editor is mainly composed of 5 parts:

  1. top bar
  2. Markdown editor on the left
  3. bottom left
  4. Right preview
  5. bottom right

The first thing we need to do is lay out the various positions.

Create a file called Demo.tsxand enter the following contents. (Let's build a React+Typescript application no matter how, just look at the logic here)

import React from 'react';

// 引入样式
import style from './Demo.scss';

const Demo: React.FC = () => {
  return (
    <div className={style.articleEdit}>
      <div className={style.topBar}>
        顶部栏
      </div>

      <div className={style.main}>
        <div className={style.editor}>
          <div className={style.markdown}>
            左侧Markdown编辑器
          </div>
          <div className={style.footer}>
            左侧底部
          </div>
        </div>

        <div id="preview" className={style.preview}>
          <div
            id="content"
            className={style.content}
          >
            右侧预览
          </div>
          <div className={style.footer}>
            右侧底部
          </div>
        </div>
      </div>
    </div>
  );
};

export default Demo;

Here React.FCis FunctionComponenta shorthand for yes, representing a functional component. What is returned in the component is jsxthe template content in . style.xxxIt is a way of referencing styles unique to React, that is, the styles are encapsulated in , and the styles (including pseudo-classes) covered by them can be "inherited" classNameby referencing them directly in React components .className

Then, we Demo.scssenter the following style content in the style file.

.articleEdit {
  height: 100vh;
  color: red;
  font-size: 24px;
}

.topBar {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 50px;
  border-bottom: 1px solid #eee;
}

.main {
  display: flex;
}

.editor {
  flex: 1 1 50%;
}

.markdown {
  display: flex;
  align-items: center;
  justify-content: center;
  height: calc(100vh - 100px);
  border-right: 1px solid #eee;
  border-bottom: 1px solid #eee;
}

.preview {
  flex: 1 1 50%;
}

.content {
  display: flex;
  align-items: center;
  justify-content: center;
  height: calc(100vh - 100px);
  border-bottom: 1px solid #eee;
}

.footer {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 50px;
  border-right: 1px solid #eee;
}

In the style, I used a flexible layout display: flexto do split screen. For how to automatically fill the height, it is a little troublesome, but finally 100vhsolved. vhThis unit is actually a percentage of the height in the browser's field of view. Assuming that the browser screen height is 640px, 1vhit represents 6.4px. Therefore, the top height 50px, bottom height 50px, and middle height are set height: calc(100% - 100px)so that the middle part fills the height of the screen.

The effect is as follows.

top title input box

We need to add a title input box at the top. classNameReplace topBarthe div tag with the following content. where Inputis antdthe component in .

<div className={style.topBar}>
    <Input className={style.title} placeholder="请输入文章标题"/>
</div>

Add the Demo.scssfollowing to the .

.title {
  margin-left: 10px !important;
  font-size: 24px !important;
  border: none !important;
}

.title:focus {
  box-shadow: none !important;
}

Here is the default style importantto override .antd

The effect is as follows.

Markdown editor on the left

We use the popular CodeMirror for Markdown editor support. In React, we refer to the react-codemirror2packaged third-party library.

Let's change it Demo.tsxto the following.

import React from 'react';
import {Input} from "antd";
import {UnControlled as CodeMirror} from 'react-codemirror2'

// 引入样式
import style from './Demo.scss';

// 引入CodeMirror样式
import 'codemirror/mode/markdown/markdown';

const Demo: React.FC = () => {
  // 调整CodeMirror高度
  setTimeout(() => {
    const $el = document.querySelector('.CodeMirror');
    if ($el) {
      $el.setAttribute('style', 'min-height:calc(100vh - 100px);box-shadow:none');
    }
  }, 100);

  return (
    <div className={style.articleEdit}>
      <div className={style.topBar}>
        <Input className={style.title} placeholder="请输入文章标题"/>
      </div>

      <div className={style.main}>
        <div className={style.editor}>
          <div className={style.markdown}>
            <CodeMirror
              className={style.codeMirror}
              options={{
                mode: 'markdown',
                theme: 'eclipse',
                lineNumbers: true,
                smartIndent: true,
                lineWrapping: true,
              }}
            />
          </div>
          <div className={style.footer}>
            左侧底部
          </div>
        </div>

        <div id="preview" className={style.preview}>
          <div
            id="content"
            className={style.content}
          >
            右侧预览
          </div>
          <div className={style.footer}>
            右侧底部
          </div>
        </div>
      </div>
    </div>
  );
};

export default Demo;

Here, we refer to the Markdown style in CodeMirror, and then refer to UnControlledthe CodeMirror component in the code, and add the corresponding configuration. In addition, since the third-party component is .CodeMirrohardcoded height: 300px, we need to manually adjust the height to the height we need, using document.querySelectorthese $el.setAttributetwo methods (see the above code).

In Demo.scssthe CSS style introduced in CodeMirror, the content is as follows.

@import '../../../node_modules/codemirror/lib/codemirror.css';
@import '../../../node_modules/codemirror/theme/eclipse.css';

...

.codeMirror {
  width: 100%;
}

Right preview

This time we will use showdown as the preview module.

This time, let's remake it first Demo.tsx. Add some import logic and monitoring functions.

import showdown from 'showdown';

showdown.setOption('tables', true);
showdown.setOption('tasklists', true);
showdown.setFlavor('github');

...

const Demo: React.FC = () => {
  ...

  // markdown to html转换器
  const converter = new showdown.Converter();

  // 内容变化回调
  const onContentChange = (editor: Editor, data: EditorChange, value: string) => {
    const $el = document.getElementById('content');
    if (!$el) return;
    $el.innerHTML = converter.makeHtml(value);
  };

  return (
    ...
        <CodeMirror
          className={style.codeMirror}
          options={{
            mode: 'markdown',
            theme: 'eclipse',
            lineNumbers: true,
            smartIndent: true,
            lineWrapping: true,
          }}
          onChange={onContentChange}
        />
    ...
        <div
        id="content"
        className={style.content}
        >
            <article
              id="content"
              className={style.content}
            />
        </div>
    ...
  )
};

Among them, we CodeMirrorhave added a onContentChangecallback in , and every time the content in Markdown is updated, it will be used showdownto generate HTML code and add it #contentto innerHTML. In this way, you can preview the edited content in real time.

In addition, we also need to customize the CSS content of the preview module, we Demo.scssadd the following content in it.

...

article {
  height: 100%;
  padding: 20px;
  overflow-y: auto;
  line-height: 1.7;
}

h1 {
  font-weight: bolder;
  font-size: 32px;
}

h2 {
  font-weight: bold;
  font-size: 24px;
}

h3 {
  font-weight: bold;
  font-size: 20px;
}

h4 {
  font-weight: bold;
  font-size: 16px;
}

h5 {
  font-weight: bold;
  font-size: 14px;
}

h6 {
  font-weight: bold;
  font-size: 12px;
}

ul {
  list-style: inherit;
}

ol {
  list-style: inherit;
}

pre {
  overflow-x: auto;
  color: #333;
  font-family: Monaco, Consolas, Courier New, monospace;
  background: #f8f8f8;
}

img {
  max-width: 100%;
  margin: 10px 0;
}

table {
  max-width: 100%;
  overflow: auto;
  font-size: 14px;
  border: 1px solid #f6f6f6;
  border-collapse: collapse;
  border-spacing: 0;

  thead {
    color: #000;
    text-align: left;
    background: #f6f6f6;
  }
}

td,
th {
  min-width: 80px;
  padding: 10px;
}

tbody tr:nth-of-type(odd) {
  background: #fcfcfc;
}

tbody tr:nth-of-type(even) {
  background: #f6f6f6;
}

The effect is as follows.

In this way, when we edit Markdown on the left, the preview on the right will be rendered in real time along with it.

bottom

The bottom is relatively simple, just fill it with content.

Fill in the following content in Demo.tsxthe footer section.

...
<label style={{marginLeft: 20}}>Markdown编辑器</label>
...
<label style={{marginLeft: 20}}>预览</label>
...

Remove in the middle to Demo.scssmake it align to the default left..footerjustify-content: center

The effect is as follows.

Markdown and preview sliding linkage

Editing is done, but we want to synchronize the Markdown editor with the preview on the right.

Add a Demo.tsxfunction to it and hang it on the CodeMirror component.

...
  // 监听左右侧上下滑动
  const onEditorScroll = (editor: Editor, scrollInfo: ScrollInfo) => {
    const $el = document.querySelector('#content') as HTMLDivElement;
    if (!$el) return;
    $el.scrollTo(0, Math.round(scrollInfo.top / scrollInfo.height * ($el.scrollHeight + $el.clientHeight)));
  };

...
    <CodeMirror
      className={style.codeMirror}
      options={{
        mode: 'markdown',
        theme: 'eclipse',
        lineNumbers: true,
        smartIndent: true,
        lineWrapping: true,
      }}
      onChange={onContentChange}
      onScroll={onEditorScroll}
    />
...

Here, we take advantage of scrollTothe method. This method accepts x and y parameters. Since we are scrolling vertically, only the y parameter is used.

Summarize

In this way, we have implemented a simple Nuggets-style article editor. Of course, the Nuggets editor also has many functions (such as automatic saving, expansion and contraction, word count, etc.), but only some of the main functions are implemented here.

The article editor implemented in this article is part of my new open source project ArtiPub (meaning Article Publisher). The project aims to solve the problem of difficult article publishing management, and hopes to achieve multi-platform article publishing, and is currently under continuous development. If you are interested, you can pay attention, add me on WeChat tikazyq1 or scan the QR code below to indicate ArtiPub to join the exchange group.


This article is automatically published by ArtiPub , a multi-post platform
{{o.name}}
{{m.name}}

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=324122359&siteId=291194637