Draft.js Mention Plugin is not working after rendering contentState to the editor - draftjs

I am using mentions with the draft.js (like this #yourname) and sending to the database to save and fetching it to render on the web page but things are not working as expected.
On Saving to the database ->
const contentState = editorState.getCurrentContent();
const currentStateData = convertToRaw(contentState);
const richStringifyValue = JSON.stringify(currentStateData);
// sending richStringifyValue to save in Mongo DB
On Fetch and set in editor ->
const [editorState, setEditorState] = React.useState(() => EditorState.createEmpty());
const parsedData = JSON.parse(post.contentStyled);
const fromRawData = convertFromRaw(parsedData );
EditorState.createWithContent(fromRawData);
// here is the view rendered part -
<Editor
readOnly={true}
editorState={editorState}
/>
But after setting in editor (after the data fetched from API) my mentions (#... #... #...) lost the CSS. What should we do?
On Using Edit ->
On fetch and setting again in Editor ->
I don't know why is that happening, please help to resolve this issue!

You should do the following:
const [editorState, setEditorState] = React.useState(() => {
const parsedData = JSON.parse(post.contentStyled);
const fromRawData = convertFromRaw(parsedData );
return EditorState.createWithContent(fromRawData);
});
// ...
<Editor
readOnly={true}
editorState={editorState}
/>
If you override the editorState with a new created state you are removing all the decorators which were added by the plugins.

Dominic's answer made me realize what was going on with the decorators, but his approach didn't work for me either.
What I ended up doing instead was to avoid mounting the editor altogether until I have the data inside the EditorState:
const [editorState, setEditorState] = React.useState(null);
useEffect(() => {
const parsedData = JSON.parse(post.contentStyled);
const fromRawData = convertFromRaw(parsedData );
setEditorState(() => EditorState.createWithContent(fromRawData));
}, []);
editorState && <Editor readOnly={true} editorState={editorState}/>
This way you insert your persisted data into the state before instantiating the component. And afterwards any other plugin adding decorators will work as intended.

Related

Ionic React: How to pass data to detail page?

The flow: ParentComponent > ImageItem > DetailPage
Each image contain its own id and data.
What I want to achive: Once the user click an image it will take him to the detail page of that image.
What I have so far is only the dynamic url that changes id when images are clicked.
Since IonImg doesnt contain routerLink option Im stuck.
const ImageItem: React.FC<Images> = ({source, id}) => {
const [imageLoadingState, setimageLoadingState] = useState<ImageLoadingState>(ImageLoadingState.init)
const history = useHistory();
useEffect(()=>{
setimageLoadingState(
//ask if props sources is provided and set loading state
source ? ImageLoadingState.loading : ImageLoadingState.error
)
},[source])
const navigate = ()=>{
history.push(`/detail/${id}`)
}
if(!source){
return null
}
return (
<IonCol size="4"className="ion-align-self-center">
<IonImg className='clickable-img'
src={source}
onLoad={()=>{setimageLoadingState(ImageLoadingState.complete)}}
onError={()=>{setimageLoadingState(ImageLoadingState.error)}}
onClick={()=>{navigate()}}/>
</IonCol>
);
};
export default ImageItem;

Open another document in VSCode extension from Hover

I try to open a document from a Hover in a VSCode Extension.
The Hover appears, the link is shown and also the URI, but when I click, nothing happens. There is an output in the Debug Console, that the command is unknown in the Developer Tools Console.
What I am doing wrong? Here is the code, a little bit simplified
context.subscriptions.push(
vscode.languages.registerHoverProvider({pattern: '**/*.{ttp,tts}'}, {
provideHover(document, position, token) {
const linkPosition = new vscode.Position(10, 1);
const range = new vscode.Range(position, position);
const opts: vscode.TextDocumentShowOptions = {
selection: range,
viewColumn: vscode.ViewColumn.Beside
};
const workspace = vscode.workspace.workspaceFolders?.find(e => e.uri.fsPath.endsWith("workspace"));
const uri = vscode.Uri.file(`${workspace?.uri.path}/_global.tt/ercdata/ttc.properties`);
const args = [{ uri: uri , options: opts}];
const stageCommandUri = vscode.Uri.parse(
`command:window.showTextDocument?${encodeURIComponent(JSON.stringify(args))}`
);
let link = new vscode.MarkdownString(`[Open...](${stageCommandUri})`);
link.isTrusted = true;
let hover: vscode.Hover = {
contents: [link]
};
return hover;
let x = properties.getHoverFor(document, position, path.basename(document.uri.fsPath).replace(".tts","").replace(".ttp","").toLowerCase());
return x;
}
}));
Here is how the Hover renders:
Here is the output of the dev console:
You should use a true command like vscode.open as documented in this article, or your own command.
window.showTextDocument alone is an extension API.
Lex Li pointed me into the right direction, thank you.
Wrapping the openTextDocument task into my own command and adressing this command from the Hover solves the problem:
context.subscriptions.push(vscode.commands.registerCommand('estudio.internal.open', (uri: vscode.Uri, options: vscode.TextDocumentShowOptions) => {
logger.info("Opening a document");
vscode.window.showTextDocument(uri, options);
}));
Than composing the Hover use
const stageCommandUri = vscode.Uri.parse(
`command:estudio.internal.open?${encodeURIComponent(JSON.stringify(args))}`
did it.

How to emit new asset without minimized in webpack5 custom plugin?

Let's say i have following custom plugin:
const webpack = require('webpack');
const path = require('path');
const pluginName = 'TestWebpackPlugin';
class TestWebpackPlugin {
constructor(opts) {
this.options = opts || {};
}
apply(compiler) {
const options = this.options
compiler.hooks.compilation.tap('TestWebpackPlugin', (compilation) => {
compilation.hooks.needAdditionalPass.tap('TestWebpackPlugin', () => false)
compilation.hooks.processAssets.tap(
{
name: 'TestWebpackPlugin',
stage: webpack.Compilation.PROCESS_ASSETS_STAGE_DERIVED
},
(assets) => {
// this new asset will also be minimized if mode = production
// the question is: how to make this new asset ignored by minimizer?
compilation.emitAsset(
'newfile',
new webpack.sources.RawSource('console.log("test content")')
);
}
);
});
}
}
module.exports = TestWebpackPlugin
the question is: how to make this new asset ignored by minimizer?
I found the question while searching for more information on the emitAsset method (how to write in UTF-8).
I am rewriting a Webpack 4 plugin (in an Angular context) with this new syntax, following the example given at https://webpack.js.org/contribute/writing-a-plugin/, and when I write the file at the given Compilation.PROCESS_ASSETS_STAGE_SUMMARIZE stage, I find out that:
I can give a relative path, like: ./i18n/${fileName}.json
The written files are not minimized

Extract function to standalone custom React component in react-leaflet

My primary goal is to call fitBounds whenever a FeatureGroup is rendered in react-leaflet on initial load.
This renders correctly -
<Map>
<LayersControl>
{getLayers(groups)}
</LayersControl>
</Map>
function getLayers(featureGroups: MyFeatureGroup[]){
const showOnLoad = true;
return featureGroups.map((group: MyFeatureGroup) => {
const groupRef = createRef<FeatureGroup>();
const { id, name, } = group;
return (
<LayersControl.Overlay checked={showOnLoad} key={id} name={name}>
<FeatureGroup ref={groupRef}>
<Layer {...group} />
</FeatureGroup>
</LayersControl.Overlay>
);
});
}
However, because it is using a function instead of React component, I don't have access to using React hooks.
The alternative that I tried does not work, even though it is the same code wrapped in a React component -
...same as above...
return featureGroups.map((group: MyFeatureGroup) => (
<ControlledGroup {...group} showOnLoad={showOnLoad} /> ///----- > ADDED THIS HERE
));
const ControlledGroup: React.FC<ControlledGroupProps> = (props) => {
const groupRef = createRef<FeatureGroup>();
const { map } = useLeaflet();
/// -----> map is correctly defined here - injecting to all of the layers (LayersControl, FeatureGroup) does not solve the problem
const { showOnLoad, ...group } = props;
useEffect(() => fitBounds(map, groupRef)); ///-----> Primary Goal of what I am trying to accomplish
return (
<LayersControl.Overlay
checked={showOnLoad}
key={group.id}
name={name}
>
<FeatureGroup ref={groupRef}>
<Layer map={map} {...group} />
</FeatureGroup>
</LayersControl.Overlay>
);
};
I am a bit stumped, since this is the same code. The getLayers function returns a ReactNode in both cases. However, when moving to a standalone ControlledGroup component, it throws an error on render -
addOverlay is not a function
I tried creating a custom class component for react-leaflet, but the difficulty that I ran into there is that createLeafletElement returns a Leaflet.Element, whereas I am simply looking to return a ReactNode. That is, all of these are valid react-leaflet components already.
My questions - why does one work and the other does not? What is the correct/recommended way to convert this function to a renderable stand-alone React component?
Further, if there is an alternative pattern to calling fitBounds, that would be helpful as well.
Any insight would be appreciated.
Since the Layers share an inheritance with Layers.Overlay, the solution to the render error is to keep the Layers together and move the feature group to a standalone component.
This works as expected and allows me to call useEffect on the groupRef -
function getLayers(groups: MyFeatureGroup[]){
return featureGroups.map((group: MyFeatureGroup) => {
const { id, name, } = group;
return (
///---> Keep the Overlay in the function here and extract just the FeatureGroup out
<LayersControl.Overlay checked={showOnLoad} key={id} name={name}>
<ControlledGroup {...group}></ControlledGroup>
</LayersControl.Overlay>
);
}

How to add properties to leaflet-geoman layer when using the toolbar

I need to add custom props to my created polys. To do so currently when the user select in the toolbar the polygon and create a shape, on the create event I convert it to json remove it from the map add the custom props to the json and reload the newly created layer.
this.map.on('pm:create', e => {
const id = getUID();
const leafId = e.layer._leaflet_id;
const featureGroup = L.featureGroup().addLayer(e.layer);
this.map.eachLayer(layer => {
if (layer._leaflet_id === leafId) {
this.map.removeLayer(layer);
}
});
const data = featureGroup.toGeoJSON();
data.features[0].properties = {
id,
name: `Zone ${id}`
};
this.zoneService.add({id, data: JSON.stringify(data)})
.pipe(
switchMap((res) => this.zoneService.getAll().pipe(this.addToMap(this.map)))
).subscribe();
});
This is working but I feel I am not doing something right here. Adding removing Adding, there must be a better way. Thanks for any help