Strapi blog logo
  • Product

      Why Strapi?Content ArchitectureRoadmapIntegrationsTry live demo
      OverviewContent Types BuilderCustomizable APIMedia LibraryRoles & Permissions
      Discover Strapi Enterprise EditionDiscover our partners
  • Pricing

  • Solutions

      Static WebsitesMobile ApplicationsCorporate websitesEditorial SitesEcommerce
      Delivery HeroL'EquipeSociete GeneralePixelDustBanco International
      Discover all our user stories
  • Community

      CommunityWrite for the communityWall of LoveStrapi Conf 2021
      SlackGitHubYoutubeCommunity Forum
      Meet the Strapi Community StarsDiscover the Strapi Showcase
  • Resources

      BlogStartersNewsroomSupport
      Strapi AcademyTutorialsVideos GuidesWebinars
      The Guide to Headless CMS Strapi Community Forum
  • Docs

      Getting StartedInstallationConfigurationsDeploymentUpdate versionContent API
      Getting StartedContent ManagerContent-Types BuilderUsers, Roles & PermissionsPlugins
      Developer DocumentationStrapi User Guide

Looking for our logo ?

Logo Brand download
Download Logo Pack
See more Strapi assets
Strapi blog logo
  • Product

    Product

    • Why Strapi?
    • Content Architecture
    • Roadmap
    • Integrations
    • Try live demo

    Features

    • Overview
    • Content Types Builder
    • Customizable API
    • Media Library
    • Roles & Permissions
    • Discover Strapi Enterprise Edition
    • Discover our partners
    Features cover

    Unlock the full potential of content management

    See all features
    Strapi Enterprise Edition

    Discover the advanced features included in Strapi Enterprise Edition.

    Get Started
  • Pricing
  • Solutions

    Solutions

    • Static Websites
    • Mobile Applications
    • Corporate websites
    • Editorial Sites
    • Ecommerce

    Stories

    • Delivery Hero
    • L'Equipe
    • Societe Generale
    • PixelDust
    • Banco International
    • Discover all our user stories
    Delivery Hero team

    Delivery Hero manages their partner portal with Strapi.

    Read their story
    turn 10 studios website

    How 1minus1 delivered a creative website for Turn10 Studios 25% faster with Strapi

    Discover their story
  • Community

    Community

    • Community
    • Write for the community
    • Wall of Love
    • Strapi Conf 2021

    Resources

    • Slack
    • GitHub
    • Youtube
    • Community Forum
    • Meet the Strapi Community Stars
    • Discover the Strapi Showcase
    Strapi Conf

    The first Strapi Global User Conference.

    Register now
    Write for the community

    Contribute on educational content for the community

    Discover the program
  • Resources

    Resources

    • Blog
    • Starters
    • Newsroom
    • Support

    Learning

    • Strapi Academy
    • Tutorials
    • Videos Guides
    • Webinars
    • The Guide to Headless CMS
    • Strapi Community Forum
    Introducing Strapi Academy

    Everything you need to know to master Strapi.

    Go to the academy
    Strapi Repository on GitHub

    Get started with the Strapi repository

    Go to repository
  • Docs

    Developers

    • Getting Started
    • Installation
    • Configurations
    • Deployment
    • Update version
    • Content API

    User Guide

    • Getting Started
    • Content Manager
    • Content-Types Builder
    • Users, Roles & Permissions
    • Plugins
    • Developer Documentation
    • Strapi User Guide
    Install Strapi

    Install Strapi locally or wherever you need.

    Get Started
    Migration Guides Strapi

    Using a previous version of Strapi? Migrate to the latest.

    Read Guides
Get Started
Back to articles

Change the default WYSIWYG to Toast UI editor

strapi-toast-ui
  • Share on facebook
  • Share on linkedin
  • Share on twitter
  • Share by email

Maxime Castres

January 18, 2021

Our community is looking for talented writers who are passionate about our ecosystem (jamstack, open-source, javascript) and willing to share their knowledge/experiences through our Write for the community program.

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:

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:

MediaLib component

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.

WYSIWYG component

/**
 *
 * 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.

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!

ToastUI component

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.

strapi-toast-ui

To learn more about other WYSIWYG

  • Replace the default WYSIWYG by CKEditor.
  • Replace the default WYSIWYG by Quill Editor.

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, join our academy to become a Strapi expert, and consult our forum if you have any questions. We will be there to help you.

See you soon!
  • Share on facebook
  • Share on linkedin
  • Share on twitter
  • Share by email

You might also be interested in...

How to change the WYSIWYG in Strapi
  • Strapi
  • Product

How to change the WYSIWYG in Strapi

This tutorial explains how to easily do an Admin Customization in Strapi. This example shows how to replace the markdown rich text editor by a WYSIWYG editor.

Cyril Lopez

Cyril Lopez

October 23, 2019

Reasons and Best Practices to Create Custom Roles
  • Strapi
  • Product

Reasons and Best Practices to Create Custom Roles

Strapi recently introduced the Role-Based Access Control feature, and with the Silver and Gold Enterprise Edition, you can create unlimited custom roles. In this article, you will learn what are custom roles and what are the best practices to create them.

Yves

Yves Do

November 3, 2020

How to change the default WYSIWYG to Quill Editor
  • Guides & Tutorials

How to change the default WYSIWYG to Quill Editor

Change the default WYSIWYG to Quill Editor in less than 5 minutes.

Maxime Castres

November 24, 2020

Unleash content.

Starters
Get Started

Strapi is the leading open-source Headless CMS. Strapi gives developers the freedom to use their favorite tools and frameworks while allowing editors to easily manage their content and distribute it anywhere.

Product

  • Why Strapi?
  • Content Architecture
  • Features
  • Enterprise Edition
  • Partner Program
  • Roadmap
  • Support
  • Integrations
  • Try live demo
  • Changelog

Resources

  • How to get started
  • Meet the community
  • Tutorials
  • API documentation
  • GitHub repository
  • Starters
  • Strapi vs Wordpress
  • The Guide to headless CMS

Integrations

  • Gatsby CMS
  • React CMS
  • Vue.js CMS
  • Nuxt.js CMS
  • Next.js CMS
  • Angular CMS
  • Gridsome CMS
  • Jekyll CMS
  • 11ty CMS
  • Svelte CMS
  • Sapper CMS
  • Ruby CMS
  • Python CMS

Company

  • About us
  • Blog
  • Careers
  • Contact
  • Newsroom
  • © 2021, Strapi
  • LicenseTermsPrivacy