draft-js-link-detection-plugin save to html with link? - javascript

So, what I'm trying to do is use this draftjs plugin to save text already formatted as HTML so I can insert it as HTML in my div to have clickable links, I already saw these examples and guides and tried to follow them, but I can't figure out what I'm doing wrong, examples and documentation used:
oficial documentation for plugin
Code sandBox with code example
-> These two I think were the really helpful ones and my base to write my code:
example of front with working implementation
The code behind that implementation
OBS: the problem is they don't really show how they save the formatted HTML state to pass in the div
After reading the documentation I coded this (dummies for show):
Some background to understand the code below
contentPost // useState passed in the <Editor>
const [contentPost, setContentPost] = useState(EditorState.createEmpty())
The component USAGE
<TextEditor
editorState={contentPost}
setEditorState={setContentPost}
hasError={hasError}
/>
component DEFINITION
function TextEditor({
editorState,
setEditorState
}: {
editorState: any
setEditorState: any
}) {
const linkifyPlugin = createLinkifyPlugin()
const plugins = [linkifyPlugin]
return (
<div>
<Editor
plugins={plugins}
editorState={editorState}
onEditorStateChange={setEditorState}
/>
</div>
)
}
How I save the values:
const payload = {
content: contentPost.getCurrentContent().getPlainText(),
contentFormatted: contentHtml,
}
handleCreatePost({ ...payload })
How I make the variable in which I save the HTML
const contentHtml = React.useMemo(() => {
const currentContent = contentPost.getCurrentContent()
return stateToHTML(currentContent)
}, [contentPost])
Any help would be appreciated!

Related

How to bind a react hook to an element created by jquery function

I'm working with a project written in jquery and I want to integrate this code in my react project. I'm trying to send a query to my graphql server by clicking a button.
There is a JQ function that creates multiple elements like this:
canvas.append($(`<div class="d-flex flex-wrap my-2"><img class="else_picture" src="${elem.artworkUrl600}" height="150px"><div class="mx-2"><div class="elseTitle" id="${elem.trackId}">${elem.trackName}</div><h6>by:${elem.artistName}</h6><h6>keywords:</h6><p>${elem.genres}</p><div class="d-flex justify-content-start"><button value="${elem.feedUrl}" class="get_description"><a target="_blank" href="${elem.trackViewUrl}">Info link</a></i></button><div class="like_box"><i id="like" class="fa fa-thumbs-up"></div></div></div><hr>`)
);
My goal is to connect a function "onClick()" to all buttons inside these created elements. For that I'm trying to define a function that would query all elements by id and connect it to a function with a hook to my graphql server:
function connectFunctionalityToLike(){
$("#like").on("click", function(){ Like(this); });
}
function Like(x){
console.log("clicked");
const { loading, error, data } = useQuery(ME_QUERY);
if (loading) return 'Loading...';
if (error) return `Error! ${error.message}`;
console.log(data);
}
My function Like() does not really work because I'm missing in understanding how elements in react actually work all together with jquery. I cannot rewrite code in JQuery. I just want to integrate this existing code. Is there any way around connecting react hooks to created elements by id?
Is there
You should create a React component wrapper for the JQuery function.
You will have to manage the React lifecycle manually - by calling the JQuery function in a useEffect() callback and taking care to remove the DOM elements in the cleanup:
function ReactWrapper() {
const { loading, error, data } = useQuery(ME_QUERY);
const [ data, setData ] = React.useState();
React.useEffect(() => {
jqueryFn();
$('#like').on('click', function(){ setData('from click') });
return () => {
/* code that removes the DOM elements created by jqueryFn() */
};
}, []);
if (loading) return 'Loading...';
if (error) return `Error! ${error.message}`;
return <div>{data}</div>;
}
Then, in this React component, will be allowed to use hooks.
Also, if your JQuery function is so simple, you are probably much better off with rewriting it in React.
As Andy says in his comment: "You shouldn't be mixing jQuery and React." - But, we've all be in spots where we are given no choice. If you have no choice but to use jQuery along side React, the code in the following example may be helpful.
Example is slightly different (no canvas, etc), but you should be able to see what you need.
This example goes beyond your question and anticipates the next - "How can I access and use the data from that [card, div, section, etc] that was added by jQuery?"
Here is a working StackBlitz EDIT - fixed link
Please see the comments in code:
// jQuery Addition - happens outside the React App
$('body').append(
$(`<div class="d-flex flex-wrap my-2"><img class="else_picture" src="${elem.artworkUrl600}" height="10px"><div class="mx-2"><div class="elseTitle" id="${elem.trackId}">${elem.trackName}</div><h6>by:${elem.artistName}</h6><h6>keywords:</h6><p>${elem.genres}</p><div class="d-flex justify-content-start"><button value="${elem.feedUrl}" class="get_description"><a target="_blank" href="${elem.trackViewUrl}">Info link</a></i></button><div class="like_box"><i id="like" class="fa fa-thumbs-up"></div></div></div><hr>`)
);
export default function App() {
// state to hold the like data
const [likes, setLikes] = useState([]);
// create reference to the jquery element of importance
// This ref will reference a jQuery object in .current
const ref = useRef($('.elseTitle'));
// Like function
function Like(x) {
// ref.current is a jQuery object so use .text() method to retrieve textContent
console.log(ref.current.text(), 'hit Like function');
setLikes([...likes, ' :: ' + ref.current.text()]);
}
// This is where you put
useEffect(() => {
// Add event listener and react
$('#like').on('click', function () {
Like(this);
});
// Remove event listener
return () => $('#like').off('click');
});
return (
<div>
<h1>Likes!</h1>
<p>{likes}</p>
</div>
);
}

Next.js - Cant apply dynamic class names when using CSS Modules

hoping someone here can help me solve this.
Am trying to build a website through NextJs. One of my pages has some paragraphs and buttons which are styled differently based on states and events. I can get the styling to work as intended when using pure React, and also when using a Global Stylesheet with NextJs; but when I use CSS Modules I cant get it to function as intended.
(Note: I can also get it to work by using a simple ternary like
<h1 className={submitted ? styles.showresult : styles.hideresult}>Correct? {correct}</h1>;
but I have some other scenarios where I need to rely on an multiple ifs and create multiple classes, each with their own styling, so I cant make a simple ternary my final solution.
E.g. this is the file pagex.js
import React from 'react';
import ReactDOM from 'react-dom';
const Pagex = () => {
const [submitted, setSubmitted] = React.useState(false); // whether the submit button was pressed
function calculateScore() {
let correct = 0
let incorrect = 0
//......some scoring logic.....
setSubmitted(true)
}
// function to create a display class based on whether the submit button has been pressed
function displayResult(){
if (submitted === true) {
return "showtheresult"
} else {
return "hidetheresult"
}
}
return (
<section className="results">
<h1 className={displayResult()}>Correct? {correct}</h1>
<h1 className={displayResult()}>Incorrect? {incorrect}</h1>
<button className={displayResult()} onClick={handleMovClick}>An instruction</button>
</section>
</div>
);
};
export default Pagex;
the globals.css file contains
h1.hidetheresult, h3.hidetheresult {
visibility: hidden;
}
h1.showtheresult, h3.showtheresult {
visibility: visible;
}
button.hidetheresult {
border-color: pink;
}
button.showtheresult {
border-color: aqua;
}
Changes when using CSS modules
Add a CSS file in the correct folder with the correct name
(../styles/Pagex.module.css) which contains the same styling shown
above
Additional import in pagex.js import styles from '../styles/Pagex.module.css'
Change reference in the function
within pagex.js
function displayResult(){
if (submitted === true) {
return {styles.showtheresult}
} else {
return {styles.hidetheresult}
}
}
When i do this the '.' in {styles.showtheresult} and {styles.hidetheresult} gets highlighted as an error by vscode with this detail: ',' expected. ts(1005).
Saving the js with a dev server running shows a similar message after trying to compile: Syntax error: Unexpected token, expected "," and the browser shows the same message along with "Failed to compile"
Also tried just passing styles.showtheresult / styles.hidetheresult by removing the curly braces from the displayResult() function. That compiles but nothing happens on the compiled webpage, i.e the class doesnt get updated when the button is pressed and so the styling cant be applied.
Also Tried passing as ${styles.showresult} and ${styles.hideresult} (with `)in the return statement. That also compiles but the page itself gives me an "Unhandled Runtime Error ReferenceError: styles is not defined" message and I cant load the page.
Would highly appreciated if someone could help correct my syntax in the function itself or elsewhere in the code.
Because you asked nicely ;) (just kiddin')
So Next.js is an opinionated framework and uses CSS Modules to enforce component scoped styling.
Basically you define your stylesheet with a name.module.css filename and add regular CSS in it.
.hidetheresult {
visibility: hidden;
}
.showtheresult{
visibility: visible;
}
.btn-hidetheresult {
border-color: pink;
}
.btn-showtheresult {
border-color: aqua;
}
Now to use this, import it like any JS module,
import styles from './styles.module.css'
console.log(styles);
// styles => {
// hidetheresult: 'contact_hidetheresult__3LvIF',
// showtheresult: 'contact_showtheresult__N5XLE',
// 'btn-hidetheresult': 'contact_btn-hidetheresult__3CQHv',
// 'btn-showtheresult': 'contact_btn-showtheresult__1rM1E'
// }
as you can see, the styles are converted to objects and now you can use them
like styles.hidetheresult or styles['btn-hidetheresult'].
Notice the absence of element selector in the stylesheet. That's because CSS Modules rewrite class names, but they don't touch tag names. And in Next.js that is
the default behaviour. i.e it does not allow element tag selectors.
File extensions with *.module.(css | scss | sass) are css modules and they can only target elements using classnames or ids and not using tag names. Although this is possible in other frameworks like create-react-app, it is not possible in next-js.
But you can override it in the next.config.js file. (Beyond the scope of this answer)
There is an article which explains how to override it. - disclaimer: I am the author
Now coming to your use-case, you can do contitional styling like so: (assuming the styles are as per the sample given in the answer)
import React from "react";
import styles from "./styles.module.css";
const PageX = () => {
const [submitted, setSubmitted] = React.useState(false);
const getStyle = () => {
if (submitted) return styles.showtheresult;
else return styles.hidetheresult;
};
const getButtonStyle = () => {
if (submitted) return styles["btn-showtheresult"];
else return styles["btn-hidetheresult"];
};
return (
<div>
<section className="results">
<h1 className={getStyle()}>Correct?</h1>
<h1 className={getStyle()}>Incorrect?</h1>
<button className={getButtonStyle()} onClick={handleMovClick}>
An instruction
</button>
</section>
</div>
);
};
As you add more conditions, the methods do tend to get more complex. This is where the classnames
module comes handy.
import styles from "./styles.module.css";
import clsx from "classnames";
const PageX = () => {
const [submitted, setSubmitted] = React.useState(false);
const headerStyle = clsx({
[styles.showtheresult]: submitted,
[styles.hidetheresult]: !submitted,
});
const btnStyle = clsx({
[styles["btn-showtheresult"]]: submitted,
[styles["btn-hidetheresult"]]: !submitted,
});
return (
<div>
<section className="results">
<h1 className={headerStyle}>Correct?</h1>
<h1 className={headerStyle}>Incorrect?</h1>
<button className={btnStyle} onClick={handleMovClick}>
An instruction
</button>
</section>
</div>
);
};
Here's a CodeSandbox for you to play with:

Custom Element (Web Component) won't accept keyboard input when inserted by a CKEditor 5 plugin

I'm in the initial stages of developing a plugin that will allow the user to insert placeholder elements into HTML content that will be processed server-side and used to incorporate some simple logic into a generated PDF document. To this end, I'm attempting to insert a custom element that I've defined using the web components API.
class NSLoop extends HTMLElement {
constructor() {
super();
}
get source() {
return this.getAttribute('source');
}
get as() {
return this.getAttribute('as');
}
}
window.customElements.define('ns-loop', NSLoop);
The contents of loopediting.js:
import Plugin from "#ckeditor/ckeditor5-core/src/plugin";
import Widget from "#ckeditor/ckeditor5-widget/src/widget";
import {viewToModelPositionOutsideModelElement} from "#ckeditor/ckeditor5-widget/src/utils";
import LoopCommand from "./loopcommand";
export default class LoopEditing extends Plugin {
static get requires() {
return [Widget];
}
constructor(editor) {
super(editor);
}
init() {
this._defineSchema();
this._defineConverters();
this.editor.commands.add('loop', new LoopCommand(this.editor));
this.editor.editing.mapper.on('viewToModelPosition', viewToModelPositionOutsideModelElement(this.editor.model, viewElement => viewElement.is('element', 'ns-loop')));
}
_defineSchema() {
const schema = this.editor.model.schema;
schema.register('loop', {
isBlock: false,
isLimit: false,
isObject: false,
isInline: false,
isSelectable: false,
isContent: false,
allowWhere: '$block',
allowAttributes: ['for', 'as'],
});
schema.extend( '$text', {
allowIn: 'loop'
} );
schema.extend( '$block', {
allowIn: 'loop'
} );
}
_defineConverters() {
const conversion = this.editor.conversion;
conversion.for('upcast').elementToElement({
view: {
name: 'ns-loop',
},
model: (viewElement, {write: modelWriter}) => {
const source = viewElement.getAttribute('for');
const as = viewElement.getAttribute('as');
return modelWriter.createElement('loop', {source: source, as: as});
}
});
conversion.for('editingDowncast').elementToElement({
model: 'loop',
view: (modelItem, {writer: viewWriter}) => {
const widgetElement = createLoopView(modelItem, viewWriter);
return widgetElement;
}
});
function createLoopView(modelItem, viewWriter) {
const source = modelItem.getAttribute('source');
const as = modelItem.getAttribute('as');
const loopElement = viewWriter.createContainerElement('ns-loop', {'for': source, 'as': as});
return loopElement;
}
}
}
This code works, in the sense that an <ns-loop> element is successfully inserted into the editor content; however, I am not able to edit this element's content. Any keyboard input is inserted into a <p> before the <ns-loop> element, and any text selection disappears once the mouse stops moving. Additionally, it is only possible to place the cursor at the beginning of the element.
If I simply swap out 'ns-loop' as the tag name for 'div' or 'p', I am able to type within the element without issue, so I suspect that I am missing something in the schema definition to make CKEditor aware that this element is "allowed" to be typed in, however I have no idea what I may have missed -- as far as I'm aware, that's what I should be achieving with the schema.extend() calls.
I have tried innumerable variations of allowedIn, allowedWhere, inheritAllFrom, isBlock, isLimit, etc within the schema definition, with no apparent change in behaviour.
Can anyone provide any insight?
Edit: Some additional information I just noticed - when the cursor is within the <ns-loop> element, the Heading/Paragraph dropdown menu is empty. That may be relevant.
Edit 2: Aaand I found the culprit staring me in the face.
this.editor.editing.mapper.on('viewToModelPosition', viewToModelPositionOutsideModelElement(this.editor.model, viewElement => viewElement.is('element', 'ns-loop')));
I'm new to the CKE5 plugin space, and was using other plugins as a reference point, and I guess I copied that code from another plugin. Removing that code solves the problem.
As noted in the second edit, the culprit was the code,
this.editor.editing.mapper.on('viewToModelPosition', viewToModelPositionOutsideModelElement(this.editor.model, viewElement => viewElement.is('element', 'ns-loop')));
which I apparently copied from another plugin I was using for reference. Removing this code has solved the immediate problem.
I'll accept this answer and close the question once the 2-day timer is up.

How to add text on border with css for input field using React JS?

I want to create an input box like below,
using css, however am not able to find anything on how to do the text on border for input fields anywhere at all
I tried using but am unable to create the input box as shown above.
I am very much stuck here, it would be a great help if anyone could suggest anyway to create an input field like shown above.
Thank you in advance.
You can use react-native-material-textfield library and need some customization. There is a prop renderLeftAccessory which can render your left view like country code here. Try example code here
import React, { Component } from 'react';
import {
TextField,
FilledTextField,
OutlinedTextField,
} from 'react-native-material-textfield';
class Example extends Component {
fieldRef = React.createRef();
onSubmit = () => {
let { current: field } = this.fieldRef;
console.log(field.value());
};
formatText = (text) => {
return text.replace(/[^+\d]/g, '');
};
render() {
return (
<OutlinedTextField
label='Phone number'
keyboardType='phone-pad'
formatText={this.formatText}
onSubmitEditing={this.onSubmit}
ref={this.fieldRef}
/>
);
}
}

Targeting only the clicked item on a mapped component ( React quiz trivia App)

i'm trying to develop an App with React using the Open trivia Api. I have mapped a button component (using material ui) to show the different answers for each question. I'm struggling now to target only the clicked one to apply a css property: if the answer is correct should become green, else red. The problem is the fact that once i click, all button become red or green. I tried to store the index in a state and compare the real index, but it doesn't work. here is my code:
in the main APP.js
const [clickedOne, setClickedOne] = useState({
clickedIndex: null,
});
useEffect(() => {
grabData();
}, []);
const handleClick = (choice, ke) => {
setChoice(choice);
if (choice === data.correct_answer) {
setIsCorrect(true);
} else {
setIsCorrect(false);
}
setClickedOne({ clickedIndex: ke });
grabData();
};
The mapped button inside the Render:
{answers.map((answer, index) => {
return (
<ContainedButtons
choice={handleClick}
answer={answer}
correct={data.correct_answer}
isCorrect={isCorrect}
key={index}
id={index}
clicked={clickedOne}
/>
);
})}
Inside the Button component:
const backStyle = () => {
if (clicked === id) {
if (isCorrect) {
return "green";
} else if (isCorrect === false) {
return "red";
} else {
return null;
}
}
};
return (
<div className={classes.root}>
<Button
style={{ backgroundColor: backStyle() }}
value={answer}
onClick={() => choice(answer, id)}
variant="contained"
>
{decodeURIComponent(answer)}
</Button>
When i check now inside the backstyle function if the clicked===id, now nothing happens anymore. Without that if check, i would have all buttons red or green.
Thank you guys for the help!
I have looked at your codesandbox demo, there are alot of other problems apart from the one your question is about.
First of all, each time you make a request to the API to fetch next question, you are making a request to get 10 questions instead of 1. API request URL contains a query parameter named amount which determines how many questions will be fetched on each request. Change its value to 1.
"https://opentdb.com/api.php?amount=1&encode=url3986"
Secondly, there is a lot of unnecessary code and unnecessary use of useState hook. You only need 2 things to be stored in the state, data and answers
const [data, setData] = useState({});
const [answers, setAnswers] = useState([]);
Now, coming to the original problem of detecting which button is clicked and correctly updating its background color.
To achieve the desired functionality, take following steps:
create couple of CSS classes as shown below
button.bgGreen {
background-color: green !important;
}
button.bgRed {
background-color: red !important;
}
pass a handleClick function from App component to ContainedButtons component. When a button is clicked, this click handler will be invoked. Inside the handleClick function, get the text and the button that was clicked using Event.target and depending on whether user answered correctly or not, add appropriate CSS class, created in step 1, on the button that was clicked.
Instead of using index as key for ContainedButtons in map function, use something that will be unique each time. This is needed because we want React to not re-use the ContainedButtons because if React re-uses the ContainedButtons component, then CSS classes added in step 2 will not be removed from the button.
Here's a working codesanbox demo of your app with the above mentioned steps.
In this demo, i have removed the unnecessary code and also changed the key of ContainedButtons inside map function to key={answer.length * Math.random() * 100}. You can change it to anything that will ensure that this key will be unique each time.

Categories

Resources