π Basic Usage
It has been developing iteratively, so update the latest version please. Publish logs: releases
π€ Npm Install
-
-
shell
shell
npm install md-editor-rt
yarn add md-editor-rt
When using server-side rendering, make sure to set editorId
to a constant value.
Starting from version 5.0, there is no such limitation.
π€ CDN
Use production version in html directly:
html
<!DOCTYPE html>
<html lang="en">
<head>
<link
href="https://unpkg.com/md-editor-rt@5.0.2/lib/style.css"
rel="stylesheet"
/>
</head>
<body>
<div id="root"></div>
<script src="https://unpkg.com/react@18.2.0/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@18.2.0/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/md-editor-rt@5.0.2/lib/umd/index.js"></script>
<script>
ReactDOM.createRoot(document.getElementById('root')).render(
React.createElement(MdEditorRT.MdEditor, {
modelValue: 'Hello Editor!!',
})
);
</script>
</body>
</html>
π€ Jsx Template
jsx
import { useState } from 'react';
import { MdEditor } from 'md-editor-rt';
import 'md-editor-rt/lib/style.css';
export default () => {
const [text, setText] = useState('hello md-editor-rt!');
return <MdEditor modelValue={text} onChange={setText} />;
};
π Preview Only
jsx
import { useState } from 'react';
import { MdPreview, MdCatalog } from 'md-editor-rt';
import 'md-editor-rt/lib/preview.css';
export default () => {
const [id] = useState('preview-only');
const [scrollElement] = useState(document.documentElement);
const [text] = useState('hello md-editor-rtοΌ');
return (
<>
<MdPreview id={id} modelValue={text} />
<MdCatalog editorId={id} scrollElement={scrollElement} />
</>
);
};
When using server-side rendering, scrollElement
should be of string type, eg: html
, body
, #id
, .class
.
π Used in Web Component
Complete example reference the sample project provided in the source code.
Here are the precautions:
- The image zoom-in view feature is ineffective; implementation needs to be done manually!!!
- Do not use CDN to reference dependency libraries by default; refer to [Import All Library]!!!
π₯ Api usage
Usages of some APIs.
π₯Ά Customize Shortcut Key
-
Source code for built-in shortcut key configuration: commands.ts. They have been added as extensions to
codemirror
. -
The basic principle of replacing or deleting shortcut keys is to find the corresponding extension, and handle it.
-
In fact, The Second input parameter
extensions
ofcodeMirrorExtensions
is an array, The first item in the array is the shortcut key extension. The third input parameter is the default shortcut key configuration.
π Modify Shortcut Key
Change Ctrl-b
to Ctrl-m
js
import { config } from 'md-editor-rt';
import { keymap } from '@codemirror/view';
config({
// [keymap, minimalSetup, markdown, EditorView.lineWrapping, EditorView.updateListener, EditorView.domEventHandlers, oneDark??oneLight]
codeMirrorExtensions(theme, extensions, mdEditorCommands) {
const newExtensions = [...extensions];
// 1. Remove the default shortcut key extension first
newExtensions.shift();
// 2. Reference the source code for shortcut key configuration
// Find the location of the configuration item for CtrlB in mdEditorCommands
const CtrlB = mdEditorCommands[0];
// 3. Document for configuring shortcut keys of codemirror
// https://codemirror.net/docs/ref/#commands
const CtrlM = {
// We need the run method in CtrlB here
...CtrlB,
key: 'Ctrl-m',
mac: 'Cmd-m',
};
// 4. Add the modified shortcut key to the array
const newMdEditorCommands = [
CtrlM,
...mdEditorCommands.filter((i) => i.key !== 'Ctrl-b'),
];
newExtensions.push(keymap.of(newMdEditorCommands));
return newExtensions;
},
});
βοΈ Delete Shortcut Key
Disable all shortcut keys
js
import { config } from 'md-editor-rt';
config({
// [keymap, minimalSetup, markdown, EditorView.lineWrapping, EditorView.updateListener, EditorView.domEventHandlers, oneDark??oneLight]
codeMirrorExtensions(theme, extensions) {
const newExtensions = [...extensions];
// 1. Remove default shortcut key extensions
newExtensions.shift();
// 2. Return extension list
return newExtensions;
},
});
π Add Shortcut Key
If you want to insert content into the edit box, you need to use the insert
method bound on the instance of editor, reference: Insert content into the edit box.
If you are not using config
in the component where the editor is located, you are unable to obtain instance of editor at this time. You may need to use EventBus
.
Add shortcut key Ctrl+m
, to insert a marking module into the editing box(==mark==
)
index.ts
js
import { config } from 'md-editor-rt';
import { keymap, KeyBinding } from '@codemirror/view';
// If you used EventBus
import bus from '@/utils/event-bus';
config({
// [keymap, minimalSetup, markdown, EditorView.lineWrapping, EditorView.updateListener, EditorView.domEventHandlers, oneDark??oneLight]
codeMirrorExtensions(theme, extensions, mdEditorCommands) {
const newExtensions = [...extensions];
// 1. Remove the default shortcut key extension first
newExtensions.shift();
// 2. Create a new shortcut key configuration, reference: https://codemirror.net/docs/ref/#commands
const CtrlM: KeyBinding = {
key: 'Ctrl-m',
mac: 'Cmd-m',
run: () => {
bus.emit('insertMarkBlock');
return true;
},
};
// 4. Add a new shortcut key to the array
const newMdEditorCommands = [...mdEditorCommands, CtrlM];
newExtensions.push(keymap.of(newMdEditorCommands));
return newExtensions;
},
});
Next, listening 'insertMarkBlock' in the component where the editor is located
App.tsx
tsx
import { useState, useRef, useEffect } from 'react';
import { MdEditor, ExposeParam } from 'md-editor-rt';
// If you used EventBus
import bus from '@/utils/event-bus';
const App = () => {
const [text] = useState('## md-editor-rt\n\n');
const mdEditorRef = useRef<ExposeParam>();
useEffect(() => {
bus.on('insertMarkBlock', () => {
mdEditorRef.current?.insert((selectedText) => {
return {
targetValue: `==${selectedText}==`,
select: true,
deviationStart: 2,
deviationEnd: -2,
};
});
});
}, []);
return <MdEditor modelValue={text} ref={mdEditorRef} />;
};
Attach: Simple version of EventBus
ts
/* eslint-disable @typescript-eslint/ban-types */
class EventBus {
private events: Map<string, Function[]>;
constructor() {
this.events = new Map();
}
on(eventName: string, fn: Function) {
if (!eventName) {
console.error('Get a wrong eventName');
return false;
}
if (!(fn instanceof Function)) {
console.error('Get a wrong callback');
return false;
}
const fns = this.events.get(eventName) || [];
fns.push(fn);
this.events.set(eventName, fns);
}
emit(eventName: string, ...args: any[]) {
this.events.get(eventName)?.forEach((fn) => {
fn(args);
});
}
}
export default new EventBus();
π¦ Change Theme
Themes are divided into editor theme(theme
), article preview theme(previewTheme
) and code theme(codeTheme
).
π§ Editor Theme
Support light
and dark
default.
jsx
import { useState } from 'react';
import { MdEditor } from 'md-editor-rt';
import 'md-editor-rt/lib/style.css';
export default () => {
const [text, setText] = useState('hello md-editor-rt!');
const [theme] = useState('dark');
return <MdEditor modelValue={text} onChange={setText} theme={theme} />;
};
π‘ Preview Theme
There are 6 kinds of themes: default
, github
, vuepress
, mk-cute
, smart-blue
and cyanosis
. It is useful When you want to show your article directly. Modify previewTheme
.
jsx
import { useState } from 'react';
import { MdEditor } from 'md-editor-rt';
import 'md-editor-rt/lib/style.css';
export default () => {
const [text, setText] = useState('hello md-editor-rt!');
const [previewTheme] = useState('github');
return (
<MdEditor
modelValue={text}
onChange={setText}
previewTheme={previewTheme}
/>
);
};
-
Custom
- Write
css
under thexxx-theme
claa.xxx
is the name of your theme, for more examples, refer to markdown-theme.
xxx.css
css.xxx-theme code { color: red; }
- Import
jsimport 'xxx.css';
- Set
previewTheme
jsx<MdEditor previewTheme="xxx" />
- Write
π Code Theme
There are 8 kinds of themes: atom
, a11y
, github
, gradient
, kimbie
, paraiso
,qtcreator
and stackoverflow
, they are all from highlight.js.
-
Usage
jsximport { useState } from 'react'; import { MdEditor } from 'md-editor-rt'; import 'md-editor-rt/lib/style.css'; export default () => { const [text, setText] = useState('hello md-editor-rt!'); const [codeTheme] = useState('atom'); return ( <MdEditor modelValue={text} onChange={setText} codeTheme={codeTheme} /> ); };
-
Custom
- Find or Write your favorite theme, then config them:
jsimport { config } from 'md-editor-rt'; config({ editorExtensions: { highlight: { css: { xxxxx: { light: 'https://unpkg.com/highlight.js@11.2.0/styles/xxxxx-light.css', dark: 'https://unpkg.com/highlight.js@11.2.0/styles/xxxxx-dark.css', }, yyyyy: { light: 'https://unpkg.com/highlight.js@11.2.0/styles/xxxxx-light.css', dark: 'https://unpkg.com/highlight.js@11.2.0/styles/xxxxx-dark.css', }, }, }, }, });
If some keys in object
css
are same as Editor's, Editor's whill be replaced.- Set
codeTheme
jsx<MdEditor codeTheme="xxxxx" />
π Config Extensions
Extensions highlight, prettier, cropper, screenfull are import from cdn
. When your project is running offline, replace urls of these extensions. Some Extensions support be injected in development environment.
Example for screenfull
:
β°οΈ Inject Directly
jsx
import { useState } from 'react';
import { MdEditor, config } from 'md-editor-rt';
import 'md-editor-rt/lib/style.css';
import screenfull from 'screenfull';
config({
editorExtensions: {
screenfull: {
instance: screenfull,
},
},
});
export default () => {
const [text, setText] = useState('hello md-editor-rt!');
return <MdEditor modelValue={text} onChange={setText} />;
};
π‘ Intranet Link
Get files from unpkg.com.
jsx
import { useState } from 'react';
import { MdEditor, config } from 'md-editor-rt';
import 'md-editor-rt/lib/style.css';
config({
editorExtensions: {
screenfull: {
js: 'https://localhost:8090/screenfull@5.2.0/index.js',
},
},
});
export default () => {
const [text, setText] = useState('hello md-editor-rt!');
return <MdEditor modelValue={text} onChange={setText} />;
};
π· Upload Pictures
By default, you can select multiple pictures. You can paste and upload screenshots and copy web page pictures.
Tips: When pasting pictures, if they are GIF graphs, it does not work! Please upload it by file system.
jsx
import { useState } from 'react';
import { MdEditor } from 'md-editor-rt';
import 'md-editor-rt/lib/style.css';
const onUploadImg = async (files, callback) => {
const res = await Promise.all(
files.map((file) => {
return new Promise((rev, rej) => {
const form = new FormData();
form.append('file', file);
axios
.post('/api/img/upload', form, {
headers: {
'Content-Type': 'multipart/form-data',
},
})
.then((res) => rev(res))
.catch((error) => rej(error));
});
})
);
callback(res.map((item) => item.data.url));
};
export default () => {
const [text, setText] = useState('hello md-editor-rt!');
return (
<MdEditor modelValue={text} onChange={setText} onUploadImg={onUploadImg} />
);
};
π³οΈβπ Extension Language
js
import { useState } from 'react';
import { MdEditor, config } from 'md-editor-rt';
import 'md-editor-rt/lib/style.css';
config({
editorConfig: {
languageUserDefined: {
'my-lang': {
toolbarTips: {
bold: 'bold',
underline: 'underline',
italic: 'italic',
strikeThrough: 'strikeThrough',
title: 'title',
sub: 'subscript',
sup: 'superscript',
quote: 'quote',
unorderedList: 'unordered list',
orderedList: 'ordered list',
task: 'task list',
codeRow: 'inline code',
code: 'block-level code',
link: 'link',
image: 'image',
table: 'table',
mermaid: 'mermaid',
katex: 'formula',
revoke: 'revoke',
next: 'undo revoke',
save: 'save',
prettier: 'prettier',
pageFullscreen: 'fullscreen in page',
fullscreen: 'fullscreen',
preview: 'preview',
htmlPreview: 'html preview',
catalog: 'catalog',
github: 'source code',
},
titleItem: {
h1: 'Lv1 Heading',
h2: 'Lv2 Heading',
h3: 'Lv3 Heading',
h4: 'Lv4 Heading',
h5: 'Lv5 Heading',
h6: 'Lv6 Heading',
},
imgTitleItem: {
link: 'Add Img Link',
upload: 'Upload Img',
clip2upload: 'Clip Upload',
},
linkModalTips: {
linkTitle: 'Add Link',
imageTitle: 'Add Image',
descLabel: 'Desc:',
descLabelPlaceHolder: 'Enter a description...',
urlLabel: 'Link:',
urlLabelPlaceHolder: 'Enter a link...',
buttonOK: 'OK',
},
clipModalTips: {
title: 'Crop Image',
buttonUpload: 'Upload',
},
copyCode: {
text: 'Copy',
successTips: 'Copied!',
failTips: 'Copy failed!',
},
mermaid: {
flow: 'flow',
sequence: 'sequence',
gantt: 'gantt',
class: 'class',
state: 'state',
pie: 'pie',
relationship: 'relationship',
journey: 'journey',
},
katex: {
inline: 'inline',
block: 'block',
},
footer: {
markdownTotal: 'Word Count',
scrollAuto: 'Scroll Auto',
},
},
},
},
});
export default () => {
const [text, setText] = useState('hello md-editor-rt!');
const [language] = useState('my-lang');
return <MdEditor modelValue={text} onChange={setText} language={language} />;
};
You can install the existing language also: md-editor-extension. Refer to extension library for the usage and the way to contribute~
π Get Catalogue
-
Get
jsximport { useState } from 'react'; import { MdEditor } from 'md-editor-rt'; import 'md-editor-rt/lib/style.css'; export default () => { const [text, setText] = useState('hello md-editor-rt!'); const [catalogList, setList] = useState([]); return ( <MdEditor modelValue={text} onChange={setText} onGetCatalog={setList} /> ); };
-
Display
Use
MdCatalog
jsximport { useState } from 'react'; import { MdPreview, MdCatalog } from 'md-editor-rt'; import 'md-editor-rt/lib/preview.css'; const editorId = 'my-editor'; export default () => { const [state] = useState({ text: '# heading', scrollElement: document.documentElement, }); return ( <> <MdPreview modelValue={state.text} id={editorId} /> <MdCatalog editorId={editorId} scrollElement={state.scrollElement} /> </> ); };
πͺ Define Toolbar
after v1.2.0, You can sort the toolbar as you like, split tools by
'-'
, the left and right toolbars are divided by'='
!
jsx
import { useState } from 'react';
import { MdEditor } from 'md-editor-rt';
import 'md-editor-rt/lib/style.css';
export default () => {
const [text, setText] = useState('hello md-editor-rt!');
const [toolbars] = useState([
'italic',
'underline',
'-',
'bold',
'=',
'github',
]);
return <MdEditor modelValue={text} onChange={setText} toolbars={toolbars} />;
};
πͺ Customize Toolbar
There are examples of mark
and emoji
.
To get complete code, refer to docs.
Get more emojis, go to https://getemoji.com/.
π§ββοΈ Change Styles
less
.css-vars(@isDark) {
--md-color: if(@isDark, #999, #222);
--md-hover-color: if(@isDark, #bbb, #000);
--md-bk-color: if(@isDark, #000, #fff);
--md-bk-color-outstand: if(@isDark, #333, #f2f2f2);
--md-bk-hover-color: if(@isDark, #1b1a1a, #f5f7fa);
--md-border-color: if(@isDark, #2d2d2d, #e6e6e6);
--md-border-hover-color: if(@isDark, #636262, #b9b9b9);
--md-border-active-color: if(@isDark, #777, #999);
--md-modal-mask: #00000073;
--md-scrollbar-bg-color: if(@isDark, #0f0f0f, #e2e2e2);
--md-scrollbar-thumb-color: if(@isDark, #2d2d2d, #0000004d);
--md-scrollbar-thumb-hover-color: if(@isDark, #3a3a3a, #00000059);
--md-scrollbar-thumb-active-color: if(@isDark, #3a3a3a, #00000061);
}
.md-editor {
.css-vars(false);
}
.md-editor-dark {
.css-vars(true);
}
Change background color in dark mode:
css
.md-editor-dark {
--md-bk-color: #333 !important;
}
ππ»ββοΈ Import All Library
- Install Dependencies
shell
yarn add screenfull katex cropperjs mermaid highlight.js prettier
- Configure
We recommend configuring it at the project entry point, such as in main.js
for projects created with Vite. Avoid calling config
within components!
main.js
js
import { config } from 'md-editor-rt';
import 'md-editor-rt/lib/style.css';
import screenfull from 'screenfull';
import katex from 'katex';
import 'katex/dist/katex.min.css';
import Cropper from 'cropperjs';
import 'cropperjs/dist/cropper.css';
import mermaid from 'mermaid';
import highlight from 'highlight.js';
import 'highlight.js/styles/atom-one-dark.css';
// <3.0
import prettier from 'prettier';
import parserMarkdown from 'prettier/parser-markdown';
// >=3.0
import * as prettier from 'prettier';
import parserMarkdown from 'prettier/plugins/markdown';
config({
editorExtensions: {
prettier: {
prettierInstance: prettier,
parserMarkdownInstance: parserMarkdown,
},
highlight: {
instance: highlight,
},
screenfull: {
instance: screenfull,
},
katex: {
instance: katex,
},
cropper: {
instance: Cropper,
},
mermaid: {
instance: mermaid,
},
},
});
jsx
import { MdEditor } from 'md-editor-rt';
import 'md-editor-rt/lib/style.css';
export default () => {
return <MdEditor modelValue="" />;
};
Tips: While import highlight styles by yourself, editor will not be able to change code styles.
π Compile-time Prevention of XSS
Version 5.0 exports the built-in XSS plugin, which is no longer added by default. The exported XSS plugin includes additional tags and attributes on top of the default whitelist:
json
{
"img": ["class"],
// Task List
"input": ["class", "disabled", "type", "checked"],
// Embedded video codes such as YouTube, WeTV, and Bilibili
"iframe": [
"class",
"width",
"height",
"src",
"title",
"border",
"frameborder",
"framespacing",
"allow",
"allowfullscreen"
]
}
π Add XSS extension
js
import { config, XSSPlugin } from 'md-editor-rt';
config({
markdownItPlugins(plugins) {
return [
...plugins,
{
type: 'xss',
plugin: XSSPlugin,
options: {},
},
];
},
});
π Modify XSS configuration
Add a configuration that allows for events where image loading fails
js
import { config, XSSPlugin } from 'md-editor-rt';
// import { getDefaultWhiteList } from 'xss';
config({
markdownItPlugins(plugins) {
return [
...plugins,
{
type: 'xss',
plugin: XSSPlugin,
options: {
// Option 1: Extend All by Yourself
// xss() {
// return {
// whiteList: Object.assign({}, getDefaultWhiteList(), {
// // If you need to use task list, please keep this configuration
// img: ['class'],
// input: ['class', 'disabled', 'type', 'checked'],
// // If you need to use embedded video code, please keep this configuration
// iframe: [
// 'class',
// 'width',
// 'height',
// 'src',
// 'title',
// 'border',
// 'frameborder',
// 'framespacing',
// 'allow',
// 'allowfullscreen'
// ],
// img: ['onerror']
// })
// };
// }
// Option 2: Add on Top of the Default Whitelist. ^4.15.6
extendedWhiteList: {
img: ['onerror'],
},
},
},
];
},
});
More configuration references: js-xss
π Prevent XSS after compilation
Using sanitize
to sanitize html
. eg: sanitize-html
shell
yarn add sanitize-html
jsx
import { MdEditor } from 'md-editor-rt';
import 'md-editor-rt/lib/style.css';
import sanitizeHtml from 'sanitize-html';
const sanitize = (html) => sanitizeHtml(html);
export default () => {
return <MdEditor sanitize={sanitize} />;
};
π Folding Document Content
js
import { config } from 'md-editor-rt';
import { foldGutter } from '@codemirror/language';
import { lineNumbers } from '@codemirror/view';
config({
codeMirrorExtensions(_theme, extensions) {
return [...extensions, lineNumbers(), foldGutter()];
},
});
ππ»ββοΈ Open Links In New Window
- Install additional extensions
shell
yarn add markdown-it-link-attributes
- Add extensions to the compiler
js
import { config } from 'md-editor-rt';
import LinkAttr from 'markdown-it-link-attributes';
// import Anchor from 'markdown-it-anchor';
config({
markdownItPlugins(plugins) {
return [
...plugins,
{
type: 'linkAttr',
plugin: LinkAttr,
options: {
matcher(href: string) {
// If markdown-it-anchor is used.
// Anchor links at the heading should be ignored.
return !href.startsWith('#');
},
attrs: {
target: '_blank',
},
},
},
// {
// type: 'anchor',
// plugin: Anchor,
// options: {
// permalink: Anchor.permalink.headerLink(),
// slugify(s: string) {
// return s;
// }
// }
// }
];
},
});
βοΈ Toggleable status task list
js
import { config } from 'md-editor-rt';
config({
markdownItPlugins(plugins, { editorId }) {
return plugins.map((item) => {
if (item.type === 'taskList') {
return {
...item,
options: {
...item.options,
enabled: true,
// If you just want to enable this feature for a certain editor
// enabled: editorId === 'myId'
},
};
}
return item;
});
},
});
jsx
<MdEditor id="myId" modelValue={text} onChange={setText} />
π³ co-working
Install yjs
shell
npm i yjs y-codemirror.next y-websocket
Add the yjs
extension in main.js:
js
import { config } from 'md-editor-rt';
import 'md-editor-rt/lib/style.css';
import * as Y from 'yjs';
import * as random from 'lib0/random';
import { yCollab } from 'y-codemirror.next';
import { WebsocketProvider } from 'y-websocket';
const usercolors = [
{ color: '#30bced', light: '#30bced33' },
{ color: '#6eeb83', light: '#6eeb8333' },
{ color: '#ffbc42', light: '#ffbc4233' },
{ color: '#ecd444', light: '#ecd44433' },
{ color: '#ee6352', light: '#ee635233' },
{ color: '#9ac2c9', light: '#9ac2c933' },
{ color: '#8acb88', light: '#8acb8833' },
{ color: '#1be7ff', light: '#1be7ff33' },
];
// select a random color for this user
const userColor = usercolors[random.uint32() % usercolors.length];
const ydoc = new Y.Doc();
const provider = new WebsocketProvider(
// Start a websocket server quickly: https://github.com/yjs/y-websocket?tab=readme-ov-file#start-a-y-websocket-server
'ws://127.0.0.1:1234',
'md-editor-v3-room',
ydoc
);
const ytext = ydoc.getText('module-name');
const undoManager = new Y.UndoManager(ytext);
provider.awareness.setLocalStateField('user', {
name: 'Anonymous ' + Math.floor(Math.random() * 100),
color: userColor.color,
colorLight: userColor.light,
});
config({
codeMirrorExtensions(_theme, extensions) {
return [...extensions, yCollab(ytext, provider.awareness, { undoManager })];
},
});
If you want to use it in only one editor, try distinguishing using editorId
(^4.20.0
):
js
config({
codeMirrorExtensions(_theme, extensions, _keyBindings, { editorId }) {
return editorId === 'myId'
? [...extensions, yCollab(ytext, provider.awareness, { undoManager })]
: extensions;
},
});