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:
- top bar
- Markdown editor on the left
- bottom left
- Right preview
- bottom right
The first thing we need to do is lay out the various positions.
Create a file called Demo.tsx
and 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.FC
is FunctionComponent
a shorthand for yes, representing a functional component. What is returned in the component is jsx
the template content in . style.xxx
It 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" className
by referencing them directly in React components .className
Then, we Demo.scss
enter 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: flex
to do split screen. For how to automatically fill the height, it is a little troublesome, but finally 100vh
solved. vh
This unit is actually a percentage of the height in the browser's field of view. Assuming that the browser screen height is 640px, 1vh
it 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. className
Replace topBar
the div tag with the following content. where Input
is antd
the component in .
<div className={style.topBar}>
<Input className={style.title} placeholder="请输入文章标题"/>
</div>
Add the Demo.scss
following 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 important
to 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-codemirror2
packaged third-party library.
Let's change it Demo.tsx
to 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 UnControlled
the CodeMirror component in the code, and add the corresponding configuration. In addition, since the third-party component is .CodeMirro
hardcoded height: 300px
, we need to manually adjust the height to the height we need, using document.querySelector
these $el.setAttribute
two methods (see the above code).
In Demo.scss
the 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 CodeMirror
have added a onContentChange
callback in , and every time the content in Markdown is updated, it will be used showdown
to generate HTML code and add it #content
to 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.scss
add 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.tsx
the footer section.
...
<label style={{marginLeft: 20}}>Markdown编辑器</label>
...
<label style={{marginLeft: 20}}>预览</label>
...
Remove in the middle to Demo.scss
make it align to the default left..footer
justify-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.tsx
function 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 scrollTo
the 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.
- Source code of this editor: https://github.com/crawlab-team/artipub/tree/master/frontend/src/pages/Demo
- ArtiPub Github: https://github.com/crawlab-team/artipub
This article is automatically published by ArtiPub , a multi-post platform