Draft.js convertFromHtml, htmlToDraft and stateFromHTML ignores style attribute - reactjs

I want to initialize a Draft.js text editor with the initial state. So I have this string:
const sampleMarkup = '<p>Given <span style="color: #2a00ff;"><strong>Name</strong></span></p>';
And I need colorful text.
I know that convertFromHtml, htmlToDraft, and stateFromHTML like do not accept this style attribute, so I found that I can use stateFromHTML with the second parameter options.
const options = {
customInlineFn: (element, { Style }) => {
if (element.style.color) {
return Style('color-' + element.style.color);
}
}
};
const content = stateFromHTML(sampleMarkup, options);
const [editorState, setEditorState] = useState(EditorState.createWithContent(
content
));
And I try to do this, but the text is still not colorful. Also, I try to change from
return Style('color-' + element.style.color);
to
return Style('CUSTOM_COLOR_' + element.style.color);
Didn't help.
Also, maybe there is another text editor for react, that I can use to work easier with HTML?
Thanks for any help :)

Related

How do I parse the HTML from the Lexical editorState without an extra Lexical editor

I have a posts system and when someone submits a post I am saving the editorState as a JSON string in a Postgres database.
Then, when I need to show the HTML, I am using a custom hook which loads an auxiliary editor just to obtain the HTML.
Is this the proper way? Looks to me a bit overengineering 🤔
I don't want to load other editor, and render it too just to obtain the HTML.
Any idea of how to improve this or suggestion for a different approach? 😄
The render post component:
export const PostDetails = () => {
const postSlug = useParam("postSlug", "string")
const [postDetails] = useQuery(getPostPageDetails, { slug: postSlug })
const { html, AuxEditor } = useGetHtmlFromState(postDetails.content as unknown as EditorState)
return (
<>
<AuxEditor />
<Text dangerouslySetInnerHTML={{ __html: html }} />
</>
)
}
The hook to get the HTML useGetHtmlFromState (it uses the same config as the input)
export const useGetHtmlFromState = (state: EditorState) => {
const [html, setHtml] = useState("")
function MyCustomStateToHtmlPlugin() {
const [editor] = useLexicalComposerContext()
editor.update(() => {
const html = $generateHtmlFromNodes(editor, null)
setHtml(html)
})
return null
}
const AuxEditor = () => {
return (
<LexicalComposer
initialConfig={{
namespace: "MyEditor",
onError: console.error,
editorState: state,
theme: exampleTheme,
nodes: [
HeadingNode,
ListNode,
ListItemNode,
QuoteNode,
CodeNode,
CodeHighlightNode,
TableNode,
TableCellNode,
TableRowNode,
AutoLinkNode,
LinkNode,
],
}}
>
<MyCustomStateToHtmlPlugin />
</LexicalComposer>
)
}
return { html, AuxEditor }
}
I think what you have is a creative way to turn SerializedEditorState into HTML. You could experiment with #lexical/headless, but it's essentially the same approach. Alternatively, there are a couple of other ways to solve the underlying problem of displaying saved state.
(1) You can generate the HTML up front and save it to the DB alongside the lexical state. It's a bit duplicative, but it works for the "write once, read many times" use case you've described.
(2) You can use a read-only lexical editor to display the saved state instead of converting it into HTML. You can configure the editor with a different theme if you need more control over the styling of specific elements.
I hope that helps!

React toggle hyperlink of grid header

I am working with react with Web API. I need one solution where I wanted to have one Hyperlink for once of the headers of Grid. And once I click's over the hyperlink, I wanted to hide this hyperlink and show other hyperlink (toggle). For Ex-
Those Hyperlinks are not directing anywhere. These are in use to select all Radiobuttons and another Hyperlink is used for unselecting all the Radiobuttons for Grid rows.
It's easy to toggle. But it's a hyperlink, which makes it a little tricky. Does the hyperlink links to anywhere?
Anyway, you can toggle the href in onClick, in order for 'href' take effect before 'href' itself is changed, use setTimeout (10 milliseconds should be enough):
import React from 'react'
const ToggleLinks = () => {
const google = {href:"https://google.ca", text:"google"};
const yahoo = {href:"https://yahoo.ca", text:"yahoo"};
const [link, setLink] = React.useState(google);
const toggleLink = e => {
console.log('clicked')
console.log(e.target)
setTimeout(() => {
setLink(p => {
return p.href === "https://google.ca" ? yahoo : google;
})
}, 10);
}
return (
<div>
<h1>ToggleLinks</h1>
{link.text}
</div>
)
}
export default ToggleLinks

Draft.js adding hyperlink using applyEntity doesn't seem to work

I've been working on this problem for a while, and I hope it's not a bug.
I'm testing a text editor using Draft.js, and I'd simply like my users to add a hyperlink to their articles, so I create a function for that to happen by modifying the editor state's content.
const addLlink = (e) => {
e.preventDefault();
const contentState = editorState.getCurrentContent();
const contentStateWithEntity = contentState.createEntity(
'LINK', 'MUTABLE', {url: 'https://www.amazon.com'} // link for testing only
);
const entityKey = contentStateWithEntity.getLastCreatedEntityKey();
const contentStateWithLink = Modifier.applyEntity(
contentStateWithEntity,
editorState.getSelection(),
entityKey
);
// tried with and without styling
const styledContentStateWithLink = Modifier.applyInlineStyle(
contentStateWithLink,
editorState.getSelection(),
'HYPERLINK'
);
const newState = EditorState.set(editorState, {
currentContent: styledContentStateWithLink
});
setEditorState(newState);
// Also tried: setEditorState(RichUtils.toggleLink(newState, newState.getSelection(), entityKey));
}
When I trigger it, I just use an Evergreen-ui button:
<Button onMouseDown={addLlink} appearance="minimal">Link</Button>
The styling I implement using the Modifier object works, but I can't seem to get the link to actually work. It should be noted that the link plugin as a package, which works great, but that's only for detecting typed out/pasted URLs (not embedded into text).
Does anyone have an actual working example, or suggestions of what I may be doing wrong, for links that use React functional programming?
It turns out that I needed to add a Decorator in order for the entity to be detected. I placed this code above/outside of my Editor component:
const findLinkEntities = (contentBlock, callback, contentState) => {
contentBlock.findEntityRanges(
(character) => {
const entityKey = character.getEntity();
return (
entityKey !== null &&
contentState.getEntity(entityKey).getType() === 'LINK'
);
},
callback
);
}
const Link = (props) => {
const {url} = props.contentState.getEntity(props.entityKey).getData();
return (
<a href={url} >
{props.children}
</a>
);
};
const strategyDecorator = new CompositeDecorator([
{
strategy: findLinkEntities,
component: Link,
},
]);
Basically, it detects link entities and converts them into elements when you set the new content using EditorState:
const newState = EditorState.set(editorState, {
currentContent: styledContentStateWithLink,
decorator: strategyDecorator
});
According to the official example you need to add a decorator to find the entities and apply the Link component
const decorator = new CompositeDecorator([
{
strategy: findLinkEntities,
component: Link,
},
]);
And as you're using the linkify plugin you have to pass the decorator to plugins editor
import Editor from "draft-js-plugins-editor";
import createLinkifyPlugin from "draft-js-linkify-plugin";
import "draft-js-linkify-plugin/lib/plugin.css";
...
<Editor
decorators={[decorator]}
editorState={editorState}
onChange={setEditorState}
plugins={[linkifyPlugin]}
/>
...
you can check out the working example here https://codesandbox.io/s/test-selection-decorator-draft-js-link-0lblg?file=/src/ExtendedEditor.js

How to set slate value based on the plain string value(serialized)

I am building a rich text editor with slate js on React and I need to parse URL from the content(for example: www.website.me should be transformed to www.website.me).
I have already implemented the function to parse URL from plain text and then wrap the content inside the correct tag.
However, the problem is that I need to display the parsed value as soon as the user is typing on the editor(on onChange event). Here is what I have :
onChange = ({ value }) => {
const { isFocused } = value.selection
if (isFocused !== this.state.isFocused) {
this.setState({ isFocused })
}
const string = this.state.html.serialize(value)
const linkifiedString = linkifyHtml(string) || ''
if (value.document !== this.state.value.document) {
const { onChange } = this.props
onChange && onChange(linkifiedString)
}
// this won't work and will call the onChange method infinitely
this.setState({ value: this.state.html.deserialize(linkifiedString) })
}
Thanks for your help.
Finally, I have found the solution, you have to create your own slate plugin or use this one: https://github.com/enzoferey/slate-instant-replace

Simple variable placeholders in Draft.js

We're trying to implement variable placeholders for a feature in our app.
Essentially the user will create a template using a WYSIWYG editor and put variables into the text e.g:
Hello {{first_name}}
These variables will then be interpolated by our back end (e.g. swapping {{first_name}} with "Peter").
Our users are non-technical so we don't want them to have to add the placeholders manually. We want them to drag and drop the variables into their text from a list that we predefine.
Is there a simple way of doing this with DraftJS or CKEdior out of the box?
Thanks for A2A.
We had a similar feature requirement to add templates with placeholders for mailing.
Dear {{recipient.first_name}},
Mail Body Template
With best regards,
{{sender.first_name}} {{sender.last_name}}
Manager - ABC Company.
Here is how we implemented it. I hope this would be helpful.
draft-js provides a Modifier class for this very use-case.
Modifier API has insertText method to insert custom placeholder text in the content-{{first_name}} in your case.
import { EditorState, Modifier } from 'draft-js';
insertPlaceholder = placeholderText => {
const { editorState } = this.state;
const newContentState = Modifier.insertText(
editorState.getCurrentContent(), // get ContentState from EditorState
editorState.getSelection(),
placeholderText
);
this.setState({
editorState: EditorState.createWithContent(newContentState); // get EditorState with ContentState
});
}
Then add a button to trigger the insertPlaceholder method.
<button
onClick={() => this.insertPlaceholder('{{first_name}}')}
>
Add First Name
</button>
When clicked on Add First Name placeholder text will be inserted at the current cursor position.
If the editor is out of focus, the text will be inserted at the beginning.
For Reusability, you can create a custom plugin and include it in options.
Placeholder plugin example screenshot
Note:
If you have multiple placeholders, I would rather suggest using a select input with possible placeholder options - this will keep UI clean instead of having a bunch of buttons.
Any suggestions are welcome.
You can create a button as follows
<button
onMouseDown={e => this.onAddText(e, '{{first_name}}' )}
>
ADD Name
</button>
and inside onAddText function
import { insertText } from 'draft-js-modifiers';
onAddText = (e, text) => {
e.preventDefault();
const { editorState } = this.state;
const EditorStat = insertText(editorState, text);
this.setState({ editorState: EditorStat });
};
insertText is method provided by draft-js-modifiers
and then use this.state.editorState as follows:
import {
Editor,
} from 'draft-js';
<Editor
editorState={this.state.editorState}
/>

Resources