draftjs how to initiate an editor with content - reactjs

Stumbled on this cool text editor, draft.js by Facebook.
I tried to follow the example in Github, but I want to create an editor with content instead of an empty editor.
var EditorState = Draft.EditorState;
var RichEditor = React.createClass({
getInitialState(){
return {editorState: EditorState.createWithContent("Hello")}
//the example use this code to createEmpty editor
// return ({editorState: EditorState.createEmpty()})
}
});
When I run it, it throws an error with the following message "Uncaught TypeError: contentState.getBlockMap is not a function".

The first argument to EditorState.createWithContent is a ContentState, not a string. You need to import ContentState
var EditorState = Draft.EditorState;
var ContentState = Draft.ContentState;
Use ContentState.createFromText And pass the result to EditorState.createWithContent.
return {
editorState: EditorState.createWithContent(ContentState.createFromText('Hello'))
};

I've created a set of packages for DraftJS to help with importing and exporting content (HTML/Markdown). I use these in my project react-rte. The one you're probably looking for is: draft-js-import-html on npm.
npm install draft-js-import-html
An example of how you might use it:
var stateFromHTML = require('draft-js-import-html').stateFromHTML;
var EditorState = Draft.EditorState;
var RichEditor = React.createClass({
getInitialState() {
let contentState = stateFromHTML('<p>Hello</p>');
return {
editorState: EditorState.createWithContent(contentState)
};
}
});
The modules I've published are:
draft-js-import-html
draft-js-export-html
draft-js-import-markdown
draft-js-export-markdown

There has been some API changes, for clarity these examples uses latest API which is v0.10.0.
There are many ways but basically you got three options depending on whether you want to use plain text, styled text or html markup for content resource.
What plain text is obvious but for styled text, you need to use either serialized javasript object or html markup.
Lets start with plain text example:
import {Editor, EditorState} from 'draft-js';
class MyEditor extends Component{
constructor(props) {
super(props);
const plainText = 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit.';
const content = ContentState.createFromText(plainText);
this.state = { editorState: EditorState.createWithContent(content)};
this.onChange = (editorState) => {
this.setState({editorState});
}
}
render(){
return(
<Editor
editorState={this.state.editorState}
onChange={this.onChange}
/>
)
}
}
For importing styled content Draft.js provides convertFromRaw and convertFromHTML utility functions.
convertFromRaw function takes raw javascript object as parameter. Here, we are using a JSON stringified javascript object as content source:
class MyEditor extends Component{
constructor(props) {
super(props);
const rawJsText = `{
"entityMap": {},
"blocks": [
{
"key": "e4brl",
"text": "Lorem ipsum dolor sit amet, consectetuer adipiscing elit.",
"type": "unstyled",
"depth": 0,
"inlineStyleRanges": [
{
"offset": 0,
"length": 11,
"style": "BOLD"
},
{
"offset": 28,
"length": 29,
"style": "BOLD"
},
{
"offset": 12,
"length": 15,
"style": "ITALIC"
},
{
"offset": 28,
"length": 28,
"style": "ITALIC"
}
],
"entityRanges": [],
"data": {}
},
{
"key": "3bflg",
"text": "Aenean commodo ligula eget dolor.",
"type": "unstyled",
"depth": 0,
"inlineStyleRanges": [],
"entityRanges": [],
"data": {}
}
]
}`;
const content = convertFromRaw(JSON.parse(rawJsText));
this.state = { editorState: EditorState.createWithContent(content)};
this.onChange = (editorState) => {
this.setState({editorState});
}
}
render(){
return(
<Editor
editorState={this.state.editorState}
onChange={this.onChange}
/>
)
}
}
Draft.js provides convertToRaw function so that you can convert your editor's state to raw javascript object for long term storage.
And lastly, here how you do it with html markup:
class MyEditor extends Component{
constructor(props) {
super(props);
const html = `<p>Lorem ipsum <b>dolor</b> sit amet, <i>consectetuer adipiscing elit.</i></p>
<p>Aenean commodo ligula eget dolor. <b><i>Aenean massa.</i></b></p>`;
const blocksFromHTML = convertFromHTML(html);
const content = ContentState.createFromBlockArray(
blocksFromHTML.contentBlocks,
blocksFromHTML.entityMap
);
this.state = { editorState: EditorState.createWithContent(content)};
this.onChange = (editorState) => {
this.setState({editorState});
}
}
render(){
return(
<Editor
editorState={this.state.editorState}
onChange={this.onChange}
/>
)
}
}

You can use convertFromHTML to import html with createWithContent
import { convertFromHTML, ContentState } from 'draft-js'
const html = '<div><p>hello</p></div>'
const blocksFromHTML = convertFromHTML(html)
const content = ContentState.createFromBlockArray(blocksFromHTML)
this.state = {
editorState: EditorState.createWithContent(content)
}
As shown in Draft's convertFromHtml example. Note that the 0.9.1 version cannot import images, whereas 0.10.0 can.
In 0.10.0 createFromBlockArray changes to:
const content = ContentState.createFromBlockArray(
blocksFromHTML.contentBlocks,
blocksFromHTML.entityMap
)

When you need to initiate an editor with plain text.
Use EditorState.createWithContent and ContentState.createFromText methods. Working example - https://jsfiddle.net/levsha/3m5780jc/
constructor(props) {
super(props);
const initialContent = 'Some text';
const editorState = EditorState.createWithContent(ContentState.createFromText(initialContent));
this.state = {
editorState
};
}
When you need to initiate an editor with content from the html markup string.
Use convertFromHTML and ContentState.createFromBlockArray. Working example - https://jsfiddle.net/levsha/8aj4hjwh/
constructor(props) {
super(props);
const sampleMarkup = `
<div>
<h2>Title</h2>
<i>some text</i>
</div>
`;
const blocksFromHTML = convertFromHTML(sampleMarkup);
const state = ContentState.createFromBlockArray(
blocksFromHTML.contentBlocks,
blocksFromHTML.entityMap
);
this.state = {
editorState: EditorState.createWithContent(state),
};
}
When you have an array of string and you want to initiate an editor with some of the default draft.js block types.
You can create array of ContentBlockss with constructor new ContentBlock(...), and pass it to ContentState.createFromBlockArray method. Working example with unordered-list-item - https://jsfiddle.net/levsha/uy04se6r/
constructor(props) {
super(props);
const input = ['foo', 'bar', 'baz'];
const contentBlocksArray = input.map(word => {
return new ContentBlock({
key: genKey(),
type: 'unordered-list-item',
characterList: new List(Repeat(CharacterMetadata.create(), word.length)),
text: word
});
});
this.state = {
editorState: EditorState.createWithContent(ContentState.createFromBlockArray(contentBlocksArray))
};
}
When you need to initiate an editor with content from ContentState raw JS structure.
If you previously saved you content state to raw JS structure with convertToRaw (read this answer for details). You can initiate an editor with convertFromRaw method. Working example - https://jsfiddle.net/levsha/tutc419a/
constructor(props) {
super(props);
this.state = {
editorState: EditorState.createWithContent(convertFromRaw(JSON.parse(editorStateAsJSON)))
};
}

In simple, you can set raw HTML content to editor from the initial stage or using setState at any time as below.
editorState: EditorState.createWithContent(ContentState.createFromBlockArray(convertFromHTML('<b>Program</b>')))
Import the necessary components.
import { EditorState, ContentState, convertFromHTML } from 'draft-js'

I found this to be a clean way of doing things with rich functionality. You can add more plugins in the future, export your content as .md etc without changing the structure of your component much.
import Draft from 'draft-js';
import DraftPasteProcessor from 'draft-js/lib/DraftPasteProcessor';
const { EditorState, ContentState } = Draft;
import Editor from 'draft-js-plugins-editor';
import createRichButtonsPlugin from 'draft-js-richbuttons-plugin';
const richButtonsPlugin = createRichButtonsPlugin();
class DescriptionForm extends React.Component {
state = {
editorState: this._getPlaceholder(),
}
_getPlaceholder() {
const placeholder = 'Write something here';
const contentHTML = DraftPasteProcessor.processHTML(placeholder);
const state = ContentState.createFromBlockArray(contentHTML);
return Draft.EditorState.createWithContent(state);
}
_onChange(editorState) {
this.setState({
editorState,
});
}
render () {
let { editorState } = this.state;
return (
<div>
<Editor
editorState={editorState}
onChange={this._onChange.bind(this)}
spellCheck={false}
plugins={[richButtonsPlugin, videoPlugin]}
/>
</div>
);
}
}

You can set the initial value to your edtitorState just by add the folwwling code if you want to set it with HTML format
this.state = {
editorState: EditorState.createWithContent(
ContentState.createFromBlockArray(
convertFromHTML('<p>My initial content.</p>')
)
),
}

for Nextjs
import React, { Component } from 'react';
import { EditorState, convertToRaw, ContentState, convertFromHTML } from 'draft-
js';
import draftToHtml from 'draftjs-to-html';
import dynamic from 'next/dynamic';
import "react-draft-wysiwyg/dist/react-draft-wysiwyg.css";
const Editor = dynamic(
() => import('react-draft-wysiwyg').then(mod => mod.Editor),
{ ssr: false }
)
let htmlToDraft = null;
if (typeof window === 'object') {
htmlToDraft = require('html-to-draftjs').default;
}
export default class EditorConvertToHTML extends Component {
constructor(props) {
super(props);
this.state = {
editorState: EditorState.createEmpty(),
contentState: ""
}
}
componentDidMount() {
const html = '<p>Hey this <strong>editor</strong> rocks d😀</p>';
const contentBlock = htmlToDraft(html);
console.log(contentBlock)
if (contentBlock) {
const contentState =
ContentState.createFromBlockArray(contentBlock.contentBlocks);
const editorState = EditorState.createWithContent(contentState);
console.log(editorState)
this.setState(
{
editorState: EditorState.createWithContent(
ContentState.createFromBlockArray(
convertFromHTML(html)
)
)
}
)
}
}
onEditorStateChange = (editorState) => {
this.setState({
editorState,
});
};
render() {
const { editorState } = this.state;
console.log(this.state.editorState.getCurrentContent())
return (
<div>
<Editor
editorState={editorState}
wrapperClassName="demo-wrapper"
editorClassName="demo-editor"
onEditorStateChange={this.onEditorStateChange}
/>
<textarea
disabled
value=
{draftToHtml(convertToRaw(editorState.getCurrentContent()))}
/>
</div>
);
}
}

you can use insertText()
insertContent(){
const contentState=editorState.getCurrentState();
const selection=editorState.getSelection();
const newContentState=Modifer.insertText(contentState,selection,'hello');
const nextEditorState=EditorState.push(editorState,newContentState,'insert-character')
setEditorState(nextEditorState)
}

Related

How to change the initial content of DraftJS Editor as the state change?

I initiated an Editor with some content using Draft.js. The initial content defined in the state as given in the example below. How can I replace the entire content with new content when I change the state. From the following example I tried the state initData is given as What is your name? when the user clicks on the input button the state changes to Quel est votre nom? and the transelated content should replace the editor.
import React, { Component } from 'react';
import { EditorState, ContentState, convertFromHTML} from 'draft-js';
export default class DraftEditor extends Component {
constructor(props) {
super(props);
this.state = {
editorState: EditorState.createEmpty(),
initData: "What is your name?" //Initial data and will be updated on button click
};
this.changeContent = this.changeContent.bind(this);
const blocksFromHtml = this.state.initData;
const html = blocksFromHtml;
const blocksFromHTML = convertFromHTML(html)
const content = ContentState.createFromBlockArray(
blocksFromHTML.contentBlocks,
blocksFromHTML.entityMap
);
this.state = { editorState: EditorState.createWithContent(content)};
this.onChange = (editorState) => {
this.setState({editorState});
}
}
onEditorStateChange = (editorState) => {
this.setState({
editorState
});
};
changeContent() {
this.setState({ initData: "Quel est votre nom?" }); //This is the data that will be replaced the initial data
}
render(){
return (
<React.Fragment>
<input type='button' value="change" onClick={this.changeContent} />
<Editor editorState={editorState} />
</React.Fragment>
);
}
}
Use ContentState to initialize the editor and to change the initial content as well.
To initialize, instead of EditorState.createEmpty() plus all the other lines of code in your constructor, use:
this.state = {
editorState: EditorState.createWithContent(
ContentState.createFromText('What is your name?')
),
};
To change this data in your click handler, do something like the following:
changeContent() {
this.setState({
editorState: EditorState.createWithContent(
ContentState.createFromText('Quel est votre nom?')
)
});
}

Dynamically loading Markdown file in React

I use markdown-to-jsx to render markdown in my React component.
My problem is that I want to dynamically load the markdown file, instead of specifying it with import. The scenario is that this happens on an article details page, i.e. I get the articleId from the route params and then based on that id, I want to load the corresponding markdown file, e.g. article-123.md.
Here's what I have so far. How can I load the md file dynamically?
import React, { Component } from 'react'
import Markdown from 'markdown-to-jsx';
import articleMd from './article-123.md'
class Article extends Component {
constructor(props) {
super(props)
this.state = { md: '' }
}
componentWillMount() {
fetch(articleMd)
.then((res) => res.text())
.then((md) => {
this.setState({ md })
})
}
render() {
return (
<div className="article">
<Markdown children={this.state.md}/>
</div>
)
}
}
export default Article
This works fine as is, but if I remove import articleMd from './article-123.md' at the top and instead pass the file path directly to fetch it output what looks like index.html, not the expected md file.
Can't you use dynamic import?
class Article extends React.Component {
constructor(props) {
super(props)
this.state = { md: '' }
}
async componentDidMount() {
const articleId = this.props.params.articleId; // or however you get your articleId
const file = await import(`./article-${articleId}.md`);
const response = await fetch(file.default);
const text = await response.text();
this.setState({
md: text
})
}
render() {
return (
<div className="article">
<Markdown children={this.state.md} />
</div>
)
}
}
I know this is an old thread but I just solved this issue with the following code
using markdown-to-jsx
import React, { Component } from 'react'
import Markdown from 'markdown-to-jsx'
class Markdown_parser extends Component {
constructor(props) {
super(props)
this.state = { md: "" }
}
componentWillMount() {
const { path } = this.props;
import(`${path}`).then((module)=>
fetch(module.default)
.then((res) => res.text())
.then((md) => {
this.setState({ md })
})
)
}
render() {
let { md } = this.state
return (
<div className="post">
<Markdown children={md} />
</div>
)
}
}
export default Markdown_parser
I then call the class sa follows
<Markdown_parser path = "path-to-your-fle" />

how to displayed local content react-draft-wysiwyg

how do i display edited content with all styles?
const content = {
entityMap: {},
blocks: [
{
key: "637gr",
text: "Initialized from content state.",
type: "unstyled",
depth: 0,
inlineStyleRanges: [],
entityRanges: [],
data: {},
},
],
}
export default class EditorConvertToJSON extends Component {
constructor(props) {
super(props)
const contentState = convertFromRaw(content)
this.state = {
contentState,
}
}
onContentStateChange = (contentState) => {
this.setState({
contentState,
})
}
render() {
const { contentState } = this.state
console.log("==============")
console.log("contentState", contentState)
return (
<div>
<Editor
wrapperClassName="demo-wrapper"
editorClassName="demo-editor"
onContentStateChange={this.onContentStateChange}
// editorState={this.state.contentState}
/>
<Editor editorState={contentState} readOnly />
</div>
)
}
}
I get an error TypeError: editorState.getImmutable is not a function
where am I wrong?
may need to display this data in divs and other html tags?
I'm completely confused
I hope this helps you:
Do
npm i draftjs-to-html
const content = {
entityMap: {},
blocks: [
{
key: "637gr",
text: "Initialized from content state.",
type: "unstyled",
depth: 0,
inlineStyleRanges: [],
entityRanges: [],
data: {},
},
],
}
export default class EditorExample extends Component {
constructor(props) {
super(props)
this.state = {
contentState : null
}
}
onContentStateChange = contentState => {
console.log('as HTML:', draftToHtml(contentState));
this.setState({ contentState});
}
render() {
const { contentState } = this.state
return (
<Editor
initialContentState={content}
editorContent={contentState}
onContentStateChange={this.onContentStateChange}
wrapperClassName="demo-wrapper"
editorClassName="demo-editor"
/>
)
}
}
Here is the full example from official documentation of react-draft-wyswiyg you can find example if you scroll down to the bottom of documentation web page :). Here you need to use convertToRaw method of draft-js. With the help of draftjs-to-html, code will be similar to draftToHtml(convertToRaw(editorState.getCurrentContent()))
import React, { Component } from 'react';
import { EditorState, convertToRaw, ContentState } from 'draft-js';
import { Editor } from 'react-draft-wysiwyg';
import draftToHtml from 'draftjs-to-html';
import htmlToDraft from 'html-to-draftjs';
class EditorConvertToHTML extends Component {
constructor(props) {
super(props);
const html = '<p>Hey this <strong>editor</strong> rocks 😀</p>';
const contentBlock = htmlToDraft(html);
if (contentBlock) {
const contentState = ContentState.createFromBlockArray(contentBlock.contentBlocks);
const editorState = EditorState.createWithContent(contentState);
this.state = {
editorState,
};
}
}
onEditorStateChange: Function = (editorState) => {
this.setState({
editorState,
});
};
render() {
const { editorState } = this.state;
return (
<div>
<Editor
editorState={editorState}
wrapperClassName="demo-wrapper"
editorClassName="demo-editor"
onEditorStateChange={this.onEditorStateChange}
/>
<textarea
disabled
value={draftToHtml(convertToRaw(editorState.getCurrentContent()))}
/>
</div>
);
}
}

How to modify styles within the draftjs editor?

I'm tring to use draftjs and want to crate a custom block component.
First question is, I can't perfectly complete an example which is create in document (link here).
When I click the button named 'bold', the editor loses focus and my text doesn't get bolder.
Here is my code:
import React, { Component } from 'react';
import { Editor, EditorState, RichUtils } from 'draft-js';
import { Paper, Button } from '#material-ui/core';
class ProductComponent extends Component {
render() {
const { src } = this.props;
return (
<div>{src}</div>
)
}
}
export default class MyEditor extends React.Component {
constructor(props) {
super(props);
this.state = {
editorState: EditorState.createEmpty()
}
}
onChange = (editorState) => this.setState({ editorState });
onBoldClick = () => {
this.onChange(RichUtils.toggleInlineStyle(this.state.editorState, 'BOLD'));
}
myBlockStyleFn = (contentBlock) => {
const type = contentBlock.getType();
if (type === 'product') {
return {
component: ProductComponent,
editable: false,
props: {
src: 'https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=3307626454,3432420457&fm=27&gp=0.jpg',
},
}
}
}
render() {
const {editorState} = this.state;
return (
<Paper elevation={0}>
<Button onClick={this.onBoldClick}>bold</Button>
<Editor
blockStyleFn={this.myBlockStyleFn}
editorState={editorState}
onChange={this.onChange}
/>
</Paper>
);
}
}

How to stop DraftJS cursor jumping to beginning of text?

Code involved using DraftJS and Meteor Js application
Task - Make a live preview where text from DraftJS will get saved to DB and from DB it get displayed on another component.
But problem is once data comes from DB and I try to edit DraftJS cursor moved to the beginning.
Code is
import {Editor, EditorState, ContentState} from 'draft-js';
import React, { Component } from 'react';
import { TestDB } from '../api/yaml-component.js';
import { createContainer } from 'meteor/react-meteor-data';
import PropTypes from 'prop-types';
class EditorComponent extends Component {
constructor(props) {
super(props);
this.state = {
editorState : EditorState.createEmpty(),
};
}
componentWillReceiveProps(nextProps) {
console.log('Receiving Props');
if (!nextProps) return;
console.log(nextProps);
let j = nextProps.testDB[0];
let c = ContentState.createFromText(j.text);
this.setState({
editorState: EditorState.createWithContent(c),
})
}
insertToDB(finalComponentStructure) {
if (!finalComponentStructure) return;
finalComponentStructure.author = 'Sandeep3005';
Meteor.call('testDB.insert', finalComponentStructure);
}
_handleChange(editorState) {
console.log('Inside handle change');
let contentState = editorState.getCurrentContent();
this.insertToDB({text: contentState.getPlainText()});
this.setState({editorState});
}
render() {
return (
<div>
<Editor
placeholder="Insert YAML Here"
editorState={this.state.editorState}
onChange={this._handleChange.bind(this)}
/>
</div>
);
}
}
EditorComponent.propTypes = {
staff: PropTypes.array.isRequired,
};
export default createContainer(() => {
return {
staff: Staff.find({}).fetch(),
};
}, EditorComponent);
Any helpful comment in right direction will be useful
When you call EditorState.createWithContent(c) Draft will return a new EditorState for you, but it has no idea about your current SelectionState. Instead, it will just create a new empty selection in the first block of your new ContentState.
To overcome this, you will have to set the SelectionState yourself, using the SelectionState from your current state, e.g:
const stateWithContent = EditorState.createWithContent(c)
const currentSelection = this.state.editorState.getSelection()
const stateWithContentAndSelection = EditorState.forceSelection(stateWithContent, currentSelection)
this.setState({
editorState: stateWithContentAndSelection
})
There is a propery to move focus to end:
const newState = EditorState.createEmpty()
this.setState({
editorState:
EditorState.moveFocusToEnd(newState)
})
This works for me.
All you need to do is to pass your given EditorState inside built in static EditorState.moveSelectionToEnd() method:
const editorState = EditorState.createEmpty();
const editorStateWithFocusOnTheEnd = EditorState.moveSelectionToEnd(editorState)

Resources