Image insertion at cursor in editor using slatejs - reactjs

I am trying to implement a Rich Text editor using reactjs and slate.js. So far, i was able to get most features working, however unable to add images to the document at the cursor is not working (Image insertion at beginning of the document is working, however).
when iam trying to insert it any other point iam getting the error.
at the renderpart i.e., executing the onchange method of the editor.
const target = getEventRange(e, this.state.EditorComp.state.value)
change = this.state.EditorComp.state.value.change().call(this.insertImage, filelocation,target)
this.state.EditorComp.onChange(change);
slate-react.es.js:1229 Uncaught Error: Unable to find a DOM node for "51". This is often because of forgetting to add `props.attributes` to a custom component.
at findDOMNode$1 (slate-react.es.js:1229)
at findDOMPoint (slate-react.es.js:1247)
at findDOMRange (slate-react.es.js:1290)
My code is based on the link https://github.com/ianstormtaylor/slate/blob/master/examples/images/index.js
Please help.

Do you have a schema defined for your editor? I had this same error until I added a schema for images that set isVoid to true. You need at least the following in your schema:
const schema = {
blocks: {
image: {
isVoid: true
}
}
};

I would imagine you would want to insert the image inline if you want it at the cursor:
change.insertInline({
type: 'img',
isVoid: true,
data: { location: location }
})
change.moveToStartOfNextText().focus()
Then in your renderNode method:
const { attributes, node, isSelected } = props
if (node.type === 'img') {
node.data.get('location')
return <img ... />
}

Related

Export image node to markdown with Lexical

I create a new node: 'ImageNode', similar to this: https://lexical.dev/docs/concepts/nodes#extending-decoratornode
This is working, but I need to export markdown content, for this, I'm using $convertToMarkdownString.
My problem is images inserted in the editor, aren't being exported as markdown. My console log show just basic transforms.
How can I export the image node to markdown?
I need to create a new transform to markdown?
Thanks!
(Copy-pasting from discussions) ImagePlugin (just like the toolbar) is part of the playground only, so the transformer is not exposed to NPM. We expect to make ImagePlugin as part an individual #lexical/image package in the future but only once we it's generic enough to cater most use cases and guarantee no major breaking cases in the immediate future.
For now, you may want to copy-paste this bit from the playground:
export const IMAGE: TextMatchTransformer = {
export: (node, exportChildren, exportFormat) => {
if (!$isImageNode(node)) {
return null;
}
return `![${node.getAltText()}](${node.getSrc()})`;
},
importRegExp: /!(?:\[([^[]*)\])(?:\(([^(]+)\))/,
regExp: /!(?:\[([^[]*)\])(?:\(([^(]+)\))$/,
replace: (textNode, match) => {
const [, altText, src] = match;
const imageNode = $createImageNode(src, altText, 800);
textNode.replace(imageNode);
},
trigger: ')',
type: 'text-match',
};

How to prevent react-quill to insert a new line before list when re-loading content?

Using react-quill, I write a list within text, store the content into external storage, reload the content into quill: a new <br> is inserted before the list, and it happens on each reload.
Any idea what is happening, and how to prevent it?
I prepared a minimal sandbox to show the issue: https://codesandbox.io/s/reverent-cookies-m5h3x
The steps to reproduce:
write a line, followed by a bullet list
click save to store the content to external storage
click clear to remove all content from the editor
click load to put the content from external storage to the editor
BOOM! A new <br> is inserted on each save-clear-load cycle
Found this answer by mhdhamouday in Quill GitHub Issues. This works for me.
var quill = new Quill('.quill', {
theme: 'snow',
modules: {
toolbar : [...]
clipboard: {
matchVisual: false
}
}
});
https://github.com/quilljs/quill/issues/2905#issuecomment-683128521 solution works very well. If you are using react component, this can be an alternative.
<ReactQuill
theme="snow"
value={block.note}
onChange={(value) => onEditorStateChange(value, block)}
modules={{
clipboard: {
matchVisual: false
}
}}
/>

How to set value of React Material UI Slider via Cypress?

I'm using slider from here: https://material-ui.com/components/slider/
Trying to set it's value like:
cy.get("[data-cy=slider-population]")
.as("range")
.invoke("val", val)
.trigger("change");
However it's not moving. Anyone manage to get this working?
To make it a bit simpler for future viewers, #kevin's answer points you to all the right places to extract a working test, however here's everything without their demo code:
<Slider data-cy="slider-population" />
First, you need to add this command:
Cypress.Commands.add("reactComponent", {
prevSubject: "element"
}, ($el) => {
if ($el.length !== 1) {
throw new Error(`cy.component() requires element of length 1 but got ${$el.length}`);
}
// Query for key starting with __reactInternalInstance$ for React v16.x
const key = Object.keys($el.get(0)).find((key) => key.startsWith("__reactFiber$"));
const domFiber = $el.prop(key);
Cypress.log({
name: "component",
consoleProps() {
return {
component: domFiber,
};
},
});
return domFiber.return;
});
Then in your test if you want to set the minimum to 0, and the max to 15:
cy.get('[data-cy="slider-population"]')
.reactComponent()
.its("memoizedProps")
.invoke("onChange", null, [0, 15]);
There is an example in the Cypress Real World App, a payment application to demonstrate real-world usage of Cypress testing methods, patterns, and workflows which demonstrates this for it's purposes.
cy.setTransactionAmountRange is a command built to interact with the Material UI Slider.
It uses another Cypress custom command, cy.reactComponent to gain access to the component internals and allow methods like onChange to be invoked directly on the component instance.
Here's a spartan riff on the solution provided by Shannon Hochkins.
Usage:
cy.get("[data-cy=slider-population]")
.setSlider([0,15])
Command:
Cypress.Commands.add('setSlider', { prevSubject: 'element' },
(subject, value) => {
const key = Object.keys(subject.get(0))
.find((key) =>
key.startsWith("__reactFiber$"))
const fiberNode = subject.prop(key)
fiberNode.return.memoizedProps.onChange(null, value)
return subject
})
Typescript:
declare namespace Cypress {
interface Chainable {
setSlider(value: number | number[]): Chainable<void>
}
}

How does document.getSelection work under reactjs environment?

I was working on a electron-react project for epub file. Right now, I am planning to make the app capable of selecting text field and highlight it.
To achieve it, I was trying to use web's Window.getSelection api. However, there are some really weird things come up like in this.
In short, I am unable to capture the Selection object. It seems like even if I log the Selection object, this object could somehow jump to something else. Also, I cannot even serialize the Selection object with JSON.stringfy. This is super surprising, and this is my first time seeing something like this (I will get empty object to stringfy the Selection object).
So how to use Window.getSelection properly under react-electron environment? Or this api will not work well for text content which is generated by react's dangerouslySetInnerHTML?
Looks like the window.getSelection api needs to toString the selected object.
const getSelectionHandler = () => {
const selection = window.getSelection();
console.log("Got selection", selection.toString());
};
Super simple textarea and button to get selection react demo
this solution might help someone, catch last selection
const selection = useRef('');
const handleSelection = () => {
const text = document.getSelection().toString();
if (text) {
selection.current = text;
}
}
useEffect(() => {
document.addEventListener('selectionchange', handleSelection);
return () => {
document.removeEventListener('selectionchange', handleSelection);
};
});

How to call and use embedded assets from Contentful Rich Text fields using Gatsby?

I am using Gatsby as the starter for my react app. To handle content I am using Contentful.
In my Contentful Content Model, I've created the following content types:
Short Text
Short Text
Media
Rich Text
Rich Text
Using Gatsby's gatsby-source-contenful and #contentful/gatsby-transformer-contentful-richtext plugins, I've successfully rendered my all of these content types, except in my Rich Text types I'm not able to render the Embedded Assets within it.
I've tried installing #contentful/rich-text-types via npm and using the constants MARK and INLINES based on the example in Gatsby's documentation found here
const { MARKS, INLINES } = require('#contentful/rich-text-types')
{
resolve: `#contentful/gatsby-transformer-contentful-richtext`,
options: {
renderOptions: {
/*
* Defines custom html string for each node type like heading, embedded entries etc..
*/
renderNode: {
// Example
[INLINES.ASSET_HYPERLINK]: node => {
return `<img class='custom-asset' src="${
node.data.target.fields.file['en-US'].url
}"/>`
},
[INLINES.EMBEDDED_ENTRY]: node => {
return `<div class='custom-entry' />${
node.data.target.fields.name['en-US']
}</div>`
},
},
/*
* Defines custom html string for each mark type like bold, italic etc..
*/
renderMark: {
// Example
[MARKS.BOLD]: text => `<strong>${text}<strong>`,
},
},
},
},
Ideally, I'd like for Gatbsy to automatically render image assets within the Rich Text types as <img src="[ASSET URL]" alt="[ASSET DESCRIPTION]">
Try this:
const { BLOCKS } = require('#contentful/rich-text-types')
...
renderNode: {
[BLOCKS.EMBEDDED_ASSET]: node => {
// console.log(node)
let { description, title, file } = node.data.target.fields
// console.log(file["en-US"].url)
return <img src={file["en-US"].url} />
},
},
This seems to work for me, although the image seems to be full-size, and load rather slowly. Needs additional work, but this does seem to work (at least in development)
EDIT:
It seems like the fields property on my node.data.target stopped appearing when I send my graphql query... and this stopped working... super bizarre
EDIT 2:
if you delete the .cache folder (project-root/.cache), the above issue gets fixed. https://github.com/gatsbyjs/gatsby/issues/10592

Resources