ReactJs: How to replace html and string template with a component? - reactjs

I want to manage the content of the page from a content editor where I am getting page content from the API.
Check this screenshot.
I used two different react modules for this react-html-parser and react-string-replace but it is still not working.
Here is my code.
let pageData = '';
pageData = ReactHtmlParser(page.content);
// replacing contact us form with a contact us form component
pageData = reactStringReplace(pageData, '{CONTACT_US_FORM}', (match, i) => (
<ContactUsForm />
));
return <div>{pageData}</div>;
react-html-parser -> It is used to parse HTML tags which are in string format into tree of elements.
react-string-replace -> It is used to replace a string into react a component.
Note: If I use react-html-parser or react-string-replace individually then it works fine but it does not work together.
Any suggestion?

Depends on the expected structure of page.content. If it contains HTML you are right in using react-html-parser, which has a replace option.
import parse from 'html-react-parser';
const macro = '{CONTACT_US_FORM}';
const replaceContactUsForm = (domhandlerNode) => {
if (domhandlerNode.type === 'text' && domhandlerNode.data.includes(macro))
return <ContactUsForm />;
};
// ...
const elements = parse(page.content, { replace: replaceContactUsForm });
return <div>{elements}</div>;
Additionally, If the string {CONTACT_US_FORM} is embedded in text you could use react-string-replace to keep the rest of the text intact:
const replaceContactUsForm = (domhandlerNode) => {
if (domhandlerNode.type === 'text' && domhandlerNode.data.includes(macro))
return <>{reactStringReplace(domhandlerNode.data, macro, () => (<ContactUsForm />))}</>;
};
If page.content does not contain HTML you do not need react-html-parser. But judging from your screenshot some markup is probably contained.

Related

react native Tappable links (hyperlinks / mailto) inside text View

How to implement a component so that when I pass text to it.
All the hyperlinks (https://somelink.com) in the text should become tappable.
All the emails(example#example.com) in the text should open the default mail app on the device
Assuming you want a ready-to-go implementation, you could use react-string-replace following the Multiple replacements on a single string section of the docs:
const text = 'Hey #ian_sinn, check out this link https://github.com/iansinnott/ Hope to see you at #reactconf';
let replacedText;
// Match URLs
replacedText = reactStringReplace(text, /(https?:\/\/\S+)/g, (match, i) => (
<a key={match + i} href={match}>{match}</a>
));
// Match #-mentions
replacedText = reactStringReplace(replacedText, /#(\w+)/g, (match, i) => (
<a key={match + i} href={`https://twitter.com/${match}`}>#{match}</a>
));
Adjust the regexes used and components rendered to match your use case.

How to load HTML template instead of json using react-email-editor?

I am using react-email-editor to edit email templates. Now I want to save template data in html format and also want to load html data in editor.
As react-email-editor is loading data in json format(I have used onLoad function and passes json data in it), but now the question is how can I load editor data using html.
Is there any method to convert html to json format using this package?
Or if there is any other method to do this please suggest.
Details:
https://github.com/unlayer/react-email-editor/blob/master/demo/src/example/index.js
Even though its been above 1 year, someone might find it useful later:
const emailEditorRef = useRef(null);
const saveDesign = () => {
emailEditorRef.current.editor.saveDesign((design) => {
console.log('saveDesign', design);
alert('Design JSON has been logged in your developer console.');
});
};
const exportHtml = () => {
emailEditorRef.current.editor.exportHtml((data) => {
const { design, html } = data;
console.log('exportHtml', html);
alert('Output HTML has been logged in your developer console.');
});
};
<button onClick={saveDesign}>Save Design</button>
<button onClick={exportHtml}>Export HTML</button>

Wordpress Gutenberg PluginDocumentSettingPanel not working with controls?

I want to add a custom meta field to the gutenberg document panel and used this doc. For the custom meta field I used this tutorial.
The problem I have occurs when trying to put them together.
Here is my code so far:
const { __ } = wp.i18n;
const { registerBlockType } = wp.blocks;
const { InspectorControls } = wp.editor;
const { registerPlugin } = wp.plugins
const { PluginDocumentSettingPanel } = wp.editPost
const { PanelBody, PanelRow, TextControl } = wp.components
const PluginDocumentSettingPanelDemo = () => (
<PluginDocumentSettingPanel
name="custom-panel"
title="Custom Panel"
className="custom-panel"
>
<TextControl
value={wp.data.select('core/editor').getEditedPostAttribute('meta')['_myprefix_text_metafield']}
label={ "Text Meta" }
onChange={(value) => wp.data.dispatch('core/editor').editPost({meta: {_myprefix_text_metafield: value}})}
/>
</PluginDocumentSettingPanel>
)
registerPlugin('plugin-document-setting-panel-demo', {
render: PluginDocumentSettingPanelDemo
})
Edit: Thanks to Ivan I solved this side issue :)
My Sidebar looks okay at first:
But when I try to change the inputs value it isn't updated (but the storage in wp.data is). I can't delete it, too. It stays at it's initial value. When I remove the part where I set the initial value it works like it should be but since I need the initial value this isn't an option for me ;)
Here an example log from the console when I add an "x" to the end of the input (as mentioned above the text in the input itself doesn't change)
Does anyone know how to make the input field working properly?
First of all, make sure you have https://wordpress.org/plugins/gutenberg/ plugin installed, because PluginDocumentSettingPanel is not fully implemented in core WP yet. It should be for 5.3 version, as per these tweets.
Second, you don't need the interval function for the wp.plugins. The reason it is undefined at first is that WordPress doesn't know that you need the wp-plugins loaded first. From the WordPress documentation
If you wanted to use the PlainText component from the editor module, first you would specify wp-editor as a dependency when you enqueue your script
The same applies for all other modules (read scripts, like 'wp-plugins').
You have to add the 'wp-plugins' script as a dependency, when registering your js plugin script:
<?php
/*
Plugin Name: Sidebar plugin
*/
function sidebar_plugin_register() {
wp_register_script(
'plugin-sidebar-js',
plugins_url( 'plugin-sidebar.js', __FILE__ ),
array( 'wp-plugins', 'wp-edit-post', 'wp-element' ) // <== the dependencies array is important!
);
}
add_action( 'init', 'sidebar_plugin_register' );
function sidebar_plugin_script_enqueue() {
wp_enqueue_script( 'plugin-sidebar-js' );
}
add_action( 'enqueue_block_editor_assets', 'sidebar_plugin_script_enqueue' );
The PHP above is taken from the official WP documentation.
I would also suggest reading thoroughly this awesome tutorial from Css Tricks. It goes in depth about setting up an ESNext environment with only the #wordpress/scripts package. It goes over the dependencies, adding meta fields and much more :) I hope this helps!
--------------- Initial answer ends here ---------------
Edit: After testing the code from the author, I found out a couple of issues. First of all, there was a missing closing tag for the TextControl. Second, I added Higher order components from the wp-data package, which I then used to "enhance" the TextControl, so that it doesn't manipulate or read data directly, but abstract that logic into it's higher order components. The code looks like so:
const { __ } = wp.i18n;
const { registerPlugin } = wp.plugins;
const { PluginDocumentSettingPanel } = wp.editPost;
const { TextControl } = wp.components;
const { withSelect, withDispatch, dispatch, select } = wp.data;
// All the necessary code is pulled from the wp global variable,
// so you don't have to install anything
// import { withSelect, withDispatch, dispatch, select } from "#wordpress/data";
// !!! You should install all the packages locally,
// so your editor could access the files so you could
// look up the functions and classes directly.
// It will not add to the final bundle if you
// run it through wp-scripts. If not, you can
// still use the wp global variable, like you have done so far.
let TextController = props => (
<TextControl
value={props.text_metafield}
label={__("Text Meta", "textdomain")}
onChange={(value) => props.onMetaFieldChange(value)}
/>
);
TextController = withSelect(
(select) => {
return {
text_metafield: select('core/editor').getEditedPostAttribute('meta')['_myprefix_text_metafield']
}
}
)(TextController);
TextController = withDispatch(
(dispatch) => {
return {
onMetaFieldChange: (value) => {
dispatch('core/editor').editPost({ meta: { _myprefix_text_metafield: value } })
}
}
}
)(TextController);
const PluginDocumentSettingPanelDemo = () => {
// Check if a value has been set
// This is for editing a post, because you don't want to override it everytime
if (!select('core/editor').getEditedPostAttribute('meta')['_myprefix_text_metafield']) {
// Set initial value
dispatch('core/editor').editPost({ meta: { _myprefix_text_metafield: 'Your custom value' } });
}
return (
<PluginDocumentSettingPanel
name="custom-panel"
title="Custom Panel"
className="custom-panel"
>
<TextController />
</PluginDocumentSettingPanel>
)
};
registerPlugin('plugin-document-setting-panel-demo', {
render: PluginDocumentSettingPanelDemo
})
Since the meta field is registered with an underscore as a first symbol in the name, WordPress will not allow you to save it, because it treats it as a private value, so you need to add extra code, when registering the field:
function myprefix_register_meta()
{
register_post_meta('post', '_myprefix_text_metafield', array(
'show_in_rest' => true,
'type' => 'string',
'single' => true,
'sanitize_callback' => 'sanitize_text_field',
'auth_callback' => function () {
return current_user_can('edit_posts');
}
));
}
add_action('init', 'myprefix_register_meta');
Again, all of this is explained in the Css tricks tutorial.
I had the same problem - values were not being updated in the field and in the database - and, after some research, I have found that the reason for this is that you should add 'custom-fields' to the 'supports' array in your call to register_post_type(), like this:
register_post_type(
'my_post_type_slug',
array(
...
'supports' => array( 'title', 'editor', 'custom-fields' ),
...
)
);
You can test that this works by calling wp.data.select( 'core/editor' ).getCurrentPost().meta in your JavaScript console, when the block editor is loaded. If your post type does not add support for 'custom-fields', this call will return undefined; if it does, it will return an empty array (or rather, an array with the already existing meta from the database). This behavior is mentioned in the Gutenberg docs, in a note on registering your post meta:
To make sure the field has been loaded, query the block editor internal data structures, also known as stores. Open your browser’s console, and execute this piece of code:
wp.data.select( 'core/editor' ).getCurrentPost().meta;
Before adding the register_post_meta function to the plugin, this code returns a void array, because WordPress hasn’t been told to load any meta field yet. After registering the field, the same code will return an object containing the registered meta field you registered.
I worked on a similar implementation recently, and worked through a bunch of examples as well. Between the above-mentioned articles, and this great series by one of the Automattic devs, I got a working version of the above example using the newer useSelect and useDispatch custom hooks. It's really quite similar, but utilizes custom hooks from React 16.8 for a slightly more concise dev experience:
(Also, using #wordpress/scripts, so the imports are from the npm packages instead of the wp object directly, but either would work.)
import { __ } from '#wordpress/i18n';
import { useSelect, useDispatch } from '#wordpress/data';
import { PluginDocumentSettingPanel } from '#wordpress/edit-post';
import { TextControl } from '#wordpress/components';
const TextController = (props) => {
const meta = useSelect(
(select) =>
select('core/editor').getEditedPostAttribute('meta')['_myprefix_text_metafield']
);
const { editPost } = useDispatch('core/editor');
return (
<TextControl
label={__("Text Meta", "textdomain")}
value={meta}
onChange={(value) => editPost({ meta: { _myprefix_text_metafield: value } })}
/>
);
};
const PluginDocumentSettingPanelDemo = () => {
return (
<PluginDocumentSettingPanel
name="custom-panel"
title="Custom Panel"
className="custom-panel"
>
<TextController />
</PluginDocumentSettingPanel>
);
};
export default PluginDocumentSettingPanelDemo;
Hopefully that helps someone else searching.

how to pass jsx as a string inside the return call

Is it possible to do something like:
const data={
star: "<h1>STAR</h1>",
moon: "<h3>moon</h3>"
}
const App = () => {
return(
<div>{data.start}</div>
);
}
what i get is the actual string of <h1>STAR</h1> not just STAR
I don't think you can. You can return an html string and possibly get it to display, but JSX isn't a string, it gets compiled into javscript code that creates those elements. it works when your app is built, I don't think you can use dynamic strings with it at run-time. You could do something like this:
const getData = (which) => {
if (which === 'star') {
return (<h1>STAR</h1>);
}
if (which === 'moon') {
return (<h3>moon</h3>);
}
return null; // nothing will display
}
const App = () => {
return (
<div>{getData('star')}</div>
);
};
Strings can be converted to JSX with third-party libraries such as h2x or react-render-html. It may be unsafe to do this with user input because of possible vulnerabilities and security problems that may exist libraries that parse DOM.
It's impossible to use components this way because component names aren't associated with functions that implement them during conversion.

Convert Quill Delta to HTML

How do I convert Deltas to pure HTML? I'm using Quill as a rich text editor, but I'm not sure how I would display the existing Deltas in a HTML context. Creating multiple Quill instances wouldn't be reasonable, but I couldn't come up with anything better yet.
I did my research, and I didn't find any way to do this.
Not very elegant, but this is how I had to do it.
function quillGetHTML(inputDelta) {
var tempCont = document.createElement("div");
(new Quill(tempCont)).setContents(inputDelta);
return tempCont.getElementsByClassName("ql-editor")[0].innerHTML;
}
Obviously this needs quill.js.
I guess you want the HTML inside it. Its fairly simple.
quill.root.innerHTML
If I've understood you correctly, there's a quill thread of discussion here, with the key information you're after.
I've quoted what should be of most value to you below:
Quill has always used Deltas as a more consistent and easier to use (no parsing)
data structure. There's no reason for Quill to reimplement DOM APIs in
addition to this. quill.root.innerHTML or document.querySelector(".ql-editor").innerHTML works just fine (quill.container.firstChild.innerHTML is a bit more brittle as it depends on child ordering) and the previous getHTML implementation did little more than this.
Simple, solution is here:
https://www.scalablepath.com/blog/using-quill-js-build-wysiwyg-editor-website/
The main code is:
console.log(quill.root.innerHTML);
This is a very common confusion when it comes to Quilljs. The thing is you should NOT retrieve your html just to display it. You should render and display your Quill container just the same way you do when it is an editor. This is one of the major advantages to Quilljs and the ONLY thing you need to do is:
$conf.readOnly = true;
This will remove the toolbar and make the content not editable.
I have accomplished it in the backend using php.
My input is json encoded delta and my output is the html string.
here is the code , if it is of any help to you.This function is still to handle lists though and some other formats but you can always extend those in operate function.
function formatAnswer($answer){
$formattedAnswer = '';
$answer = json_decode($answer,true);
foreach($answer['ops'] as $key=>$element){
if(empty($element['insert']['image'])){
$result = $element['insert'];
if(!empty($element['attributes'])){
foreach($element['attributes'] as $key=>$attribute){
$result = operate($result,$key,$attribute);
}
}
}else{
$image = $element['insert']['image'];
// if you are getting the image as url
if(strpos($image,'http://') !== false || strpos($image,'https://') !== false){
$result = "<img src='".$image."' />";
}else{
//if the image is uploaded
//saving the image somewhere and replacing it with its url
$imageUrl = getImageUrl($image);
$result = "<img src='".$imageUrl."' />";
}
}
$formattedAnswer = $formattedAnswer.$result;
}
return nl2br($formattedAnswer);
}
function operate($text,$ops,$attribute){
$operatedText = null;
switch($ops){
case 'bold':
$operatedText = '<strong>'.$text.'</strong>';
break;
case 'italic':
$operatedText = '<i>'.$text.'</i>';
break;
case 'strike':
$operatedText = '<s>'.$text.'</s>';
break;
case 'underline':
$operatedText = '<u>'.$text.'</u>';
break;
case 'link':
$operatedText = ''.$text.'';
break;
default:
$operatedText = $text;
}
return $operatedText;
}
Here's a full function using quill.root.innerHTML, as the others didn't quite cover the complete usage of it:
function quillGetHTML(inputDelta) {
var tempQuill=new Quill(document.createElement("div"));
tempQuill.setContents(inputDelta);
return tempQuill.root.innerHTML;
}
This is just a slight different variation of km6 's answer.
For Quill version 1.3.6, just use:
quill.root.innerHTML;
Try it online: https://jsfiddle.net/Imabot/86dtuhap/
Detailed explaination on my blog
This link if you have to post the Quill HTML content in a form
quill.root.innerHTML on the quill object works perfectly.
$scope.setTerm = function (form) {
var contents = JSON.stringify(quill.root.innerHTML)
$("#note").val(contents)
$scope.main.submitFrm(form)
}
I put together a node package to convert html or plain text to and from a Quill Delta.
My team used it to update our data model to include both Quill's Delta and HTML. This allows us to render on the client without an instance of Quill.
See node-quill-converter.
It features the following functions:
- convertTextToDelta
- convertHtmlToDelta
- convertDeltaToHtml
Behind the scenes it uses an instance of JSDOM. This may make it best suited for migration scripts as performance has not been tested in a typical app request lifecycle.
Try
console.log ( $('.ql-editor').html() );
Here is how I did it, for you Express folks. It seems to have worked very well in conjunction with express-sanitizer.
app.js
import expressSanitizer from 'express-sanitizer'
app.use(expressSanitizer())
app.post('/route', async (req, res) => {
const title = req.body.article.title
const content = req.sanitize(req.body.article.content)
// Do stuff with content
})
new.ejs
<head>
<link href="https://cdn.quilljs.com/1.3.2/quill.snow.css" rel="stylesheet">
</head>
...
<form action="/route" method="POST">
<input type="text" name="article[title]" placeholder="Enter Title">
<div id="editor"></div>
<input type="submit" onclick="return quillContents()" />
</form>
...
<script src="https://cdn.quilljs.com/1.3.2/quill.js"></script>
<script>
const quill = new Quill('#editor', {
theme: 'snow'
})
const quillContents = () => {
const form = document.forms[0]
const editor = document.createElement('input')
editor.type = 'hidden'
editor.name = 'article[content]'
editor.value = document.querySelector('.ql-editor').innerHTML
form.appendChild(editor)
return form.submit()
}
</script>
express-sanitizer (https://www.npmjs.com/package/express-sanitizer)
document.forms (https://developer.mozilla.org/en-US/docs/Web/API/Document/forms)
My view only has one form, so I used document.forms[0], but if you have multiple or may extend your view in the future to have multiple forms, check out the MDN reference.
What we are doing here is creating a hidden form input that we assign the contents of the Quill Div, and then we bootleg the form submit and pass it through our function to finish it off.
Now, to test it, make a post with <script>alert()</script> in it, and you won't have to worry about injection exploits.
That's all there is to it.
Here is a proper way to do it.
var QuillDeltaToHtmlConverter = require('quill-delta-to-html').QuillDeltaToHtmlConverter;
// TypeScript / ES6:
// import { QuillDeltaToHtmlConverter } from 'quill-delta-to-html';
var deltaOps = [
{insert: "Hello\n"},
{insert: "This is colorful", attributes: {color: '#f00'}}
];
var cfg = {};
var converter = new QuillDeltaToHtmlConverter(deltaOps, cfg);
var html = converter.convert();
Refer https://github.com/nozer/quill-delta-to-html
For a jQuery-style solution that allows getting and setting the Quill value I am doing the following:
Quill.prototype.val = function(newVal) {
if (newVal) {
this.container.querySelector('.ql-editor').innerHTML = newVal;
} else {
return this.container.querySelector('.ql-editor').innerHTML;
}
};
let editor = new Quill( ... );
//set the value
editor.val('<h3>My new editor value</h3>');
//get the value
let theValue = editor.val();
quill-render looks like it's what you want. From the docs:
var render = require('quill-render');
render([
{
"attributes": {
"bold": true
},
"insert": "Hi mom"
}
]);
// => '<b>Hi mom</b>'
If you want to render quill using nodejs, there is a package quite simple based on jsdom, usefull to render backside (only one file & last update 18 days from now) render quill delta to html string on server
Just use this clean library to convert from delta from/to text/html
node-quill-converter
example:
const { convertDeltaToHtml } = require('node-quill-converter');
let html = convertDeltaToHtml(delta);
console.log(html) ; // '<p>hello, <strong>world</strong></p>'

Resources