Here's a codepen example of the issue.
I have added a custom block type called section
, which wraps up selected text in red. It works fine when you click on the section
in the edit toolbar. However, when used with convertFromHTML
to render initial content,
const sampleMarkup =
'<b>Bold text</b><br/>Section Testing:<section>dsasdasad</section><br/><i>Italic text</i>';
the editor is still treating section
type as unstyle
type. Does convertFromHTML
support custom block types for initial rendering? Is there a solution for this?
Code:
const {Editor,convertFromHTML,ContentState, EditorState,DefaultDraftBlockRenderMap, RichUtils} = Draft;
const blockRenderMap = Immutable.Map({
'section': {
element: 'section'
}
});
const extendedBlockRenderMap = DefaultDraftBlockRenderMap.merge(blockRenderMap);
class RichEditorExample extends React.Component {
constructor(props) {
super(props);
const sampleMarkup =
'<b>Bold text</b><br/>Section Testing:<section>dsasdasad</section><br/><i>Italic text</i>';
const blocksFromHTML = convertFromHTML(sampleMarkup);
const state = ContentState.createFromBlockArray(
blocksFromHTML.contentBlocks,
blocksFromHTML.entityMap
);
this.state = {editorState: EditorState.createWithContent(state)};
this.focus = () => this.refs.editor.focus();
this.onChange = (editorState) => this.setState({editorState});
this.handleKeyCommand = (command) => this._handleKeyCommand(command);
this.onTab = (e) => this._onTab(e);
this.toggleBlockType = (type) => this._toggleBlockType(type);
this.toggleInlineStyle = (style) => this._toggleInlineStyle(style);
}
_handleKeyCommand(command) {
const {editorState} = this.state;
const newState = RichUtils.handleKeyCommand(editorState, command);
if (newState) {
this.onChange(newState);
return true;
}
return false;
}
_onTab(e) {
const maxDepth = 4;
this.onChange(RichUtils.onTab(e, this.state.editorState, maxDepth));
}
_toggleBlockType(blockType) {
this.onChange(
RichUtils.toggleBlockType(
this.state.editorState,
blockType
)
);
}
_toggleInlineStyle(inlineStyle) {
this.onChange(
RichUtils.toggleInlineStyle(
this.state.editorState,
inlineStyle
)
);
}
render() {
const {editorState} = this.state;
// If the user changes block type before entering any text, we can
// either style the placeholder or hide it. Let's just hide it now.
let className = 'RichEditor-editor';
var contentState = editorState.getCurrentContent();
if (!contentState.hasText()) {
if (contentState.getBlockMap().first().getType() !== 'unstyled') {
className += ' RichEditor-hidePlaceholder';
}
}
return (
<div className="RichEditor-root">
<BlockStyleControls
editorState={editorState}
onToggle={this.toggleBlockType}
/>
<InlineStyleControls
editorState={editorState}
onToggle={this.toggleInlineStyle}
/>
<div className={className} onClick={this.focus}>
<Editor
blockRenderMap={extendedBlockRenderMap}
blockStyleFn={getBlockStyle}
customStyleMap={styleMap}
editorState={editorState}
handleKeyCommand={this.handleKeyCommand}
onChange={this.onChange}
onTab={this.onTab}
placeholder="Tell a story..."
ref="editor"
spellCheck={true}
/>
</div>
</div>
);
}
}
// Custom overrides for "code" style.
const styleMap = {
CODE: {
backgroundColor: 'rgba(0, 0, 0, 0.05)',
fontFamily: '"Inconsolata", "Menlo", "Consolas", monospace',
fontSize: 16,
padding: 2,
},
};
function getBlockStyle(block) {
switch (block.getType()) {
case 'blockquote': return 'RichEditor-blockquote';
default: return null;
}
}
class StyleButton extends React.Component {
constructor() {
super();
this.onToggle = (e) => {
e.preventDefault();
this.props.onToggle(this.props.style);
};
}
render() {
let className = 'RichEditor-styleButton';
if (this.props.active) {
className += ' RichEditor-activeButton';
}
return (
<span className={className} onMouseDown={this.onToggle}>
{this.props.label}
</span>
);
}
}
const BLOCK_TYPES = [
{label: 'H1', style: 'header-one'},
{label: 'H2', style: 'header-two'},
{label: 'H3', style: 'header-three'},
{label: 'H4', style: 'header-four'},
{label: 'H5', style: 'header-five'},
{label: 'H6', style: 'header-six'},
{label: 'Blockquote', style: 'blockquote'},
{label: 'UL', style: 'unordered-list-item'},
{label: 'OL', style: 'ordered-list-item'},
{label: 'Code Block', style: 'code-block'},
{label:'section',style:'section'},
];
const BlockStyleControls = (props) => {
const {editorState} = props;
const selection = editorState.getSelection();
const blockType = editorState
.getCurrentContent()
.getBlockForKey(selection.getStartKey())
.getType();
return (
<div className="RichEditor-controls">
{BLOCK_TYPES.map((type) =>
<StyleButton
key={type.label}
active={type.style === blockType}
label={type.label}
onToggle={props.onToggle}
style={type.style}
/>
)}
</div>
);
};
var INLINE_STYLES = [
{label: 'Bold', style: 'BOLD'},
{label: 'Italic', style: 'ITALIC'},
{label: 'Underline', style: 'UNDERLINE'},
{label: 'Monospace', style: 'CODE'},
];
const InlineStyleControls = (props) => {
var currentStyle = props.editorState.getCurrentInlineStyle();
return (
<div className="RichEditor-controls">
{INLINE_STYLES.map(type =>
<StyleButton
key={type.label}
active={currentStyle.has(type.style)}
label={type.label}
onToggle={props.onToggle}
style={type.style}
/>
)}
</div>
);
};
ReactDOM.render(
<RichEditorExample />,
document.getElementById('target')
);
To be efficient, Draft. js organizes set of consecutive characters w/ same styles(or same set of styles) in a single object called inlineStyleRanges. To be clear, this is ONLY organized like this in the “convertToRaw” output (for brevity). In actual contentState it is stored the long way for each character.
The Flow type RawDraftContentState denotes the expected structure of the raw format of the contents. The raw state contains a list of content blocks, as well as a map of all relevant entity objects.
I know its a little late but I found a solution for that here. I couldn't find a way to set it at the initial render but if you couldn't set it at all, you can use this.
Example:
// convertToHtml is a custom function which returns convertFromHTML(html)
let htmlTranscript = convertToHtml(paragraphsArray)
// important part
htmlTranscript.contentBlocks[0] = htmlTranscript.contentBlocks[0].set('type', 'myType')
const contentState = ContentState.createFromBlockArray(
htmlTranscript.contentBlocks,
htmlTranscript.entityMap
)
this.setState({
editorState: EditorState.createWithContent(contentState)
})
Yes it does.
It's not well documented, but you need to pass the same blockRenderMap
that you passed to the Editor
, to the third argument of the convertFromHTML
function.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With