Simply copy and paste the following command line in your terminal to create your first Strapi project.
npx create-strapi-app
my-project
This tutorials is outdated since it is for Strapi v3. We redirect you to the part of our documentation concerning the creation of a new WYSIWYG field in the admin panel
Another new WYSIWYG editor that allows you to replace the default one on Strapi!
Well this time, I would not need to explain in detail how to replace it by yourself because it turns out that Fagbokforlaget, which is a Norwegian publishing company that publishes nonfiction works and teaching aids for instruction at various levels, already made an npm package you can directly add to your Strapi project in order to have a wonderful Toast UI editor in your admin.
This editor is great as it implements real-time previsualization of what you are writing. This package is listed on our awesome Strapi repository but I find it important to write a blog post because many of you want to replace the default editor but do not know the existence of this Github repository.
However, I will explain the code of this package. This is exactly like we did for manually installing Quill editor.
It's very simple to get started. Head into an existing Strapi project and add the package via npm or yarn:
npm i --save strapi-plugin-wysiwyg-toastui
# or
yarn add strapi-plugin-wysiwyg-toastui
Perfect, all you have to do is build and launch your Strapi application:
1
2
3
4
5
yarn build
yarn develop
# or
npm run build
npm run develop
You can now edit your Rich Text content using the Toast UI editor!
Let's take a closer look at how they made it...
You can observe the package code by looking at the following path:
. /node_modules/strapi-plugin-wysiwyg-toastui/admin/src/components
We can see that 3 components have been created:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import React, {useEffect, useState} from 'react';
import {useStrapi, prefixFileUrlWithBackendUrl} from 'strapi-helper-plugin';
import PropTypes from 'prop-types';
const MediaLib = ({isOpen, onChange, onToggle}) => {
const {
strapi: {
componentApi: {getComponent},
},
} = useStrapi ();
const [data, setData] = useState (null);
const [isDisplayed, setIsDisplayed] = useState (false);
useEffect (() => {
if (isOpen) {
setIsDisplayed (true);
}
}, [isOpen]);
const Component = getComponent ('media-library'). Component;
const handleInputChange = data => {
if (data) {
const {url} = data;
setData ({... data, url: prefixFileUrlWithBackendUrl (url)});
}
};
const handleClosed = () => {
if (data) {
onChange (data);
}
setData (null);
setIsDisplayed (false);
};
if (Component && isDisplayed) {
return (
<Component
allowedTypes = {['images', 'videos', 'files']}
isOpen = {isOpen}
multiple = {false}
noNavigation
onClosed = {handleClosed}
onInputMediaChange = {handleInputChange}
onToggle = {onToggle}
/>
);
}
return null;
};
MediaLib.defaultProps = {
isOpen: false,
onChange: () => {},
onToggle: () => {},
};
MediaLib.propTypes = {
isOpen: PropTypes.bool,
onChange: PropTypes.func,
onToggle: PropTypes.func,
};
export default MediaLib;
This component allows you to use the Media Library and will be called in the editor component in order to directly use images from the Media Library. Nothing much! Let's see the second component.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
/**
*
* Wysiwyg
*
*/
import React from 'react';
import PropTypes from 'prop-types';
import { isEmpty, isFunction } from 'lodash';
import cn from 'classnames';
import { Description, ErrorMessage, Label } from '@buffetjs/styles';
import { Error } from '@buffetjs/core';
import Editor from '../TOASTUI';
import Wrapper from './Wrapper';
class Wysiwyg extends React.Component {
render() {
const {
autoFocus,
className,
deactivateErrorHighlight,
disabled,
error: inputError,
inputClassName,
inputDescription,
inputStyle,
label,
name,
onBlur: handleBlur,
onChange,
placeholder,
resetProps,
style,
tabIndex,
validations,
value,
...rest
} = this.props;
return (
<Error
inputError={inputError}
name={name}
type="text"
validations={validations}
>
{({ canCheck, onBlur, error, dispatch }) => {
const hasError = error && error !== null;
return (
<Wrapper
className={`${cn(!isEmpty(className) && className)} ${
hasError ? 'bordered' : ''
}`}
style={style}
>
<Label htmlFor={name}>{label}</Label>
<Editor
{...rest}
autoFocus={autoFocus}
className={inputClassName}
disabled={disabled}
deactivateErrorHighlight={deactivateErrorHighlight}
error={hasError}
name={name}
onBlur={isFunction(handleBlur) ? handleBlur : onBlur}
onChange={e => {
if (!canCheck) {
dispatch({
type: 'SET_CHECK',
});
}
dispatch({
type: 'SET_ERROR',
error: null,
});
onChange(e);
}}
placeholder={placeholder}
resetProps={resetProps}
style={inputStyle}
tabIndex={tabIndex}
value={value}
/>
{!hasError && inputDescription && (
<Description>{inputDescription}</Description>
)}
{hasError && <ErrorMessage>{error}</ErrorMessage>}
</Wrapper>
);
}}
</Error>
);
}
}
Wysiwyg.defaultProps = {
autoFocus: false,
className: '',
deactivateErrorHighlight: false,
didCheckErrors: false,
disabled: false,
error: null,
inputClassName: '',
inputDescription: '',
inputStyle: {},
label: '',
onBlur: false,
placeholder: '',
resetProps: false,
style: {},
tabIndex: '0',
validations: {},
value: null,
};
Wysiwyg.propTypes = {
autoFocus: PropTypes.bool,
className: PropTypes.string,
deactivateErrorHighlight: PropTypes.bool,
didCheckErrors: PropTypes.bool,
disabled: PropTypes.bool,
error: PropTypes.string,
inputClassName: PropTypes.string,
inputDescription: PropTypes.oneOfType([
PropTypes.string,
PropTypes.func,
PropTypes.shape({
id: PropTypes.string,
params: PropTypes.object,
}),
]),
inputStyle: PropTypes.object,
label: PropTypes.oneOfType([
PropTypes.string,
PropTypes.func,
PropTypes.shape({
id: PropTypes.string,
params: PropTypes.object,
}),
]),
name: PropTypes.string.isRequired,
onBlur: PropTypes.oneOfType([PropTypes.bool, PropTypes.func]),
onChange: PropTypes.func.isRequired,
placeholder: PropTypes.string,
resetProps: PropTypes.bool,
style: PropTypes.object,
tabIndex: PropTypes.string,
validations: PropTypes.object,
value: PropTypes.string,
};
export default Wysiwyg;
This component will wrap the Toast UI editor with a label and the errors. You can see that the index.js file is not alone. There is also a Wrapper.js file containing some style using styled-components.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import styled from 'styled-components';
const Wrapper = styled.div`
padding-bottom: 2.8rem;
font-size: 1.3rem;
font-family: 'Lato';
label {
display: block;
margin-bottom: 1rem;
}
&.bordered {
.editorWrapper {
border-color: red;
}
}
> div + p {
width: 100%;
padding-top: 12px;
font-size: 1.2rem;
line-height: normal;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-bottom: -9px;
}
`;
export default Wrapper;
Now, the final component which is the one for the Toast UI editor itself!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
import React from 'react';
import PropTypes from 'prop-types';
import '@toast-ui/editor/dist/toastui-editor.css';
import 'codemirror/lib/codemirror.css';
import { Editor } from '@toast-ui/react-editor';
import { Button } from '@buffetjs/core';
import MediaLib from '../MediaLib';
class TOIEditor extends React.Component {
editorRef = React.createRef();
constructor(props) {
super(props);
this.height = "400px";
this.initialEditType = "markdown";
this.previewStyle = "vertical";
this.state = { isOpen : false };
this.handleToggle = this.handleToggle.bind(this);
}
componentDidMount() {
const editor = this.editorRef.current.getInstance();
const toolbar = editor.getUI().getToolbar();
editor.eventManager.addEventType('insertMediaButton');
editor.eventManager.listen('insertMediaButton', () => {
this.handleToggle();
} );
toolbar.insertItem(0, {
type: 'button',
options: {
className: 'first tui-image',
event: 'insertMediaButton',
tooltip: 'Insert Media',
text: '@',
}
});
}
componentDidUpdate() {
// Bug fix, where switch button become submit type - editor bug
const elements = document.getElementsByClassName('te-switch-button');
if ( elements.length ) {
elements[0].setAttribute('type','button');
elements[1].setAttribute('type','button');
}
}
handleChange = data => {
let value = this.props.value;
let editor_instance = this.editorRef.current.getInstance();
if (data.mime.includes('image')) {
editor_instance.exec('AddImage', { 'altText': data.caption, 'imageUrl': data.url } );
}
else {
editor_instance.exec('AddLink', { 'linkText': data.name, 'url': data.url } );
}
};
handleToggle = () => this.setState({ isOpen : !this.state.isOpen });
render() {
return (
<>
<Editor
previewStyle={this.previewStyle}
height={this.height}
initialEditType={this.initialEditType}
initialValue={this.props.value}
ref={this.editorRef}
usageStatistics={false}
onChange={(event) => {
this.props.onChange({
target: {
value: this.editorRef.current.getInstance().getMarkdown(),
name: this.props.name,
type: 'textarea',
},
});
}}
toolbarItems={[
'heading',
'bold',
'italic',
'strike',
'divider',
'hr',
'quote',
'divider',
'ul',
'ol',
'task',
'indent',
'outdent',
'divider',
'table',
'link',
'divider',
'code',
'codeblock',
'divider',
]}
/>
<MediaLib onToggle={this.handleToggle} isOpen={this.state.isOpen} onChange={this.handleChange}/>
</>
);
}
}
export default TOIEditor;
As you understood, this component is the implementation of the new WYSIWYG which is simply using the Media Library.
That's it for this article which, I hope, will have introduced you to a very useful and especially great package.
Please note: Since we initially published this blog post, we released new versions of Strapi and tutorials may be outdated. Sorry for the inconvenience if it's the case. Please help us by reporting it here.
Get started with Strapi by creating a project, using a starter or trying our instant live demo. Also, consult our forum if you have any questions. We will be there to help you.
See you soon!
Maxime started to code in 2015 and quickly joined the Growth team of Strapi. He particularly likes to create useful content for the awesome Strapi community. Send him a meme on Twitter to make his day: @MaxCastres