Sorry if this has been asked before, I haven't been able to find anything on it, and would be more than fine submitting a PR with an example if I can figure it out.
Is it possible to write a plugin that would perform some asynchronous behaviour and update the state when completed? For instance, rather than just auto detecting a link, it would make a request to bit.ly to grab a short link and replace it with that.
Obviously, it would have to be non blocking so I'm not sure how you would do this.
Thanks!
This would also be useful in the case of rich embeds, and while doing things like mentions, etc.
Hey @gpfunk, great question!
Each of the handlers you can define in your plugin receive both the state (the current State object), as well as the editor (a reference to the <Editor> component). For synchronous plugins, the easiest thing to do is to do return a newly transformed state.
For asynchronous plugins though, you obviously can't do that. But, you can call the editor.getState() method to retrieve the current state at any later point. And then you can call the editor.onChange(state: State) method with a newly transformed state at any later point to trigger an update.
So off the top of my head, you could do something like:
async function onKeyDown(e, state, editor) {
const value = await someAsyncOperation()
const currentState = editor.getState()
const newState = currentState
.transform()
.insertText(value)
.apply()
editor.onChange(newState)
}
I haven't added any async plugins myself yet鈥攁lthough I have plans too鈥攕o let me know if you run into any issues!
@ianstormtaylor One issue I've run into while creating an async plugin is maintaining the default fall-through behavior.
For example, I have a plugin that watches for a user to paste a link to a tweet and then asynchronously round trips to get the oembed code from Twitter's API to render it.
The problem I'm having right now is that I can't fall through for pastes that aren't tweets. For example:
function myTweetPlugin() {
return {
async onPaste(event, change, editor){
const transfer = getEventTransfer(event);
const { text } = transfer;
if (!testIsTweet(text)) return; // This doesn't fall through and blocks subsequent paste behavior...
// Behavior for tweets
const data = await fetchTweetOembed(text);
const newState = change.insertBlock({
type: 'tweet',
data,
});
editor.onChange(newState);
}
}
}
I'd definitely appreciate any ideas to help keep the async behavior composable with other plugins.
... and I've answered my own question ... Thanks, everyone, I'll be here all week!
function myTweetPlugin() {
return {
async handleTweet(url, change, editor){
const data = await fetchTweetOembed(text);
const newState = change.insertBlock({
type: 'tweet',
data,
});
editor.onChange(newState);
},
onPaste(event, change, editor){
const transfer = getEventTransfer(event);
const { text } = transfer;
if (!testIsTweet(text)) return; // Falls through correctly!
// Behavior for tweets in its own async func
this.handleTweet(text, change, editor);
}
}
}
Upshot: don't make your core plugin methods async if you want them to be composable with other plugins using the same methods.
Most helpful comment
Hey @gpfunk, great question!
Each of the handlers you can define in your plugin receive both the
state(the currentStateobject), as well as theeditor(a reference to the<Editor>component). For synchronous plugins, the easiest thing to do is to do return a newly transformed state.For asynchronous plugins though, you obviously can't do that. But, you can call the
editor.getState()method to retrieve the current state at any later point. And then you can call theeditor.onChange(state: State)method with a newly transformed state at any later point to trigger an update.So off the top of my head, you could do something like:
I haven't added any async plugins myself yet鈥攁lthough I have plans too鈥攕o let me know if you run into any issues!