I have modified the toolbar a bit so that I can upload images instead of adding urls using a popup. Now I am looking for a way to insert the image at the correct cursor position. I want to accomplish the following flow:
Click upload icon
Store cursor position (is there a dom to position function, I can’t find it?)
Select image from file browser
Get preview of image and insert preview at stored cursor position
Show simple progress below image
Update src of image
Steps 2, 5 and 6 are difficult to accomplish, because I can’t find good examples of it, or the examples are of previous versions.
I was wondering if there is a method that calculates domNode to position? I could give the img tag an id and retrieve the position by this id?
Previously you could have used markRange to mark your preview image. The marked range would stay up to date with any document changes. Decorators have replaced markRange, so you could use a decorator to effectively “tag”/mark the preview image. Then once the upload is finished, you would do a replace using the position of the decorator.
A decorator here would also be useful for you to render a progress bar.
So… you’d have an inserted <img src="data:..."> and then add a decorator to both keep track of its location AND add a progress widget. Then on upload completion you’d replace the node and cleanup the decorator (not sure if decorators auto remove if the node they are attached to is replaced/removed).
This is best done using a widget or node decoration.
You can call setNodeType. If you kept a decoration at the image and mapped it forward when new edits came in, you can get the position of the image node from that decoration (and drop the decoration when you’re done).
Decoration.widget creates a widget object, but doesn’t add it to anything. You have to create a DecorationSet and arrange for it to be provided to the editor view through the decorations prop, probably by writing a plugin.
Well at the moment when the run function of the MenuItem is triggered I need to do something? What is completely unclear to me at the moment. Do I have to execute:
to insert an image node? Or do I need to create a decoration here. At this moment the src is not yet available. Because showing a preview is async. What I want to do is insert a div with some text like Uploading or whatever.
This must be accomplished by a decoration. But how and where do I do this. I really can’t get a grasp on it. I have created a UploadFilesPlugin.
But really don’t know how to create a domNode here at the right moment and the right position. This domNode also gets some events attached. These events keep track of the progress and state of the file.
With this metadata set, I’d think to retrieve the meta info in the plugin. But nothing gets executed. Is that expected behavior and if yes, how can I dispatch an transaction such that nothig gets added or changed.
UPDATED
Had it wrong, this is triggering the Plugin.
This gives very weird behavior. When I want to insert an image. The widget is added. However when I go with the cursor near the “Uploading” text of the widget and start typing, it keeps on inserting the “Uploading” text on every insert of a character.
It is near to impossible to get this working . I’ve tried everything, from node views to decorations. I viewed some demos, like the codemirror demo and the commit demo, but it is very difficult to understand what happens and what not.
The patch did the trick of removing the multiplication of the widget text However when I now add a decoration and insert another decoration, the other is removed. So if I upload 2 files. The other decoration is removed. I fixed this by replacing:
Is this correct or should the deco be a completely new object with the old decorations copied?
Currently it also isn’t completely clear to me how and where I remove the decoration and replace it wit the actual image. When the $dom is added as a decoration, the $dom also has an event listener. This event gets triggered by the upload in the background. In the event I know which id is finished. I have to track down the decoration belonging to this $dom. How do I do this?
Also is it possible to let the decoration behave like a block node? Currently it is inserted in a paragraph, but it would be nice if it splits the paragraph or wherever the cursor is and inserts the placeholder div between the split paragraphs.
Is this correct or should the deco be a completely new object with the old decorations copied?
The add method creates a new object. Most of the ProseMirror API is functional-style.
You could put the upload ID in the decoration’s options object (third parameter to Decoration.widget), and then call deco.find() to get the current decorations, loop through them to find the one whose .options.uploadID property matches the ID you’re interested in, and remove that one.
$dom.addEventListener('changes', (ev) => {
const state = window.editor.editor.state // retrieve global reference, because no reference is available???
let decoset = this.getState(state)
let decos = decoset.find()
for (let j = 0; j < decos.length; j++) {
if (decos[j].type.options.fileId == ev.detail.fileMessage.fileId) {
decoset = decoset.remove([decos[j]])
}
}
console.log(decoset.find())
decoset.map(state.tr.mapping, state.tr.doc)
})
inside the apply(tr, deco) method of the plugin, isn’t the reference to deco outdated if more files are upload. So shouldn’t I get a fresh reference to the decorations. For example by doing
let decoset = this.getState(window.editor.editor.state) // Feels kind of hacky
wherein state is a reference to the EditorState object. But if so, how do I get the latest reference to the EditorState object. Because I can’t find a property on the Plugin which has a reference to this state and if I have finally deleted the decoration from the decorationSet, how is the new decorationSet applied to the document.
If I implement the getState like this, it returns a very old state when there were no decorations added. When I use:
const state = window.editor.editor.state
as state it has the latest state, or it has a state with decorations.
The following is still not working. When I remove the decoration from the decorationSet:
decoset = decoset.remove([decos[j]])
and map it:
decoset.map(state.tr.mapping, state.tr.doc)
The decoration is still visible. There must be done something that applies the removal of the decoration, but what is it? I thought that the mapping did it, but it isn’t.
After some async event, remove the same decoration based on id
Step 1 is working, step 2 isn’t.
What I do in step 2 is the following:
Get latest state by const state = window.editor.editor.state (window.editor.editor is ref to view)
Retrieve the decorationSet let decoset = this.getState(state)
Get it in an array let decos = decoset.find()
Loop over the array and remove matching decorations from array, if found, remove deco from decoset
As step 5 I have to apply the new decorationSet (but how can this be done?). I thought I had to do it with decoset.map(state.tr.mapping, state.tr.doc), but this just returns a decorationSet. How can I update the editor with the new deocrationSet?
In other words, how do I refresh the editor with the new DecorationSet?
Removing them from the array is not necessary or useful. Removing it from the set is what will cause it to vanish, provided the new set without that decoration is returned the next time the decorations prop is called.
Mapping a decoration set just moves it from one version of the document to another, and has nothing to do with applying it (it’s a pure function that returns a new set)
What you need to do is create and dispatch a transaction that contains some metainfo which tells your decoration plugin to drop the decoration with a given id, and return the new set as updated state.
Finally got it working. Thanks for your patience The trick is to dispatch a new transaction in the event and set some meta and catch in the Plugin on this meta and do some transformations.
The part that bothers me is to get a reference to the view in the event handler of the $dom in the plugin. Currently I am doing this via the global window object, but this isn’t the nicest solution. How can I get a reference to the view without using the global window object. The plugin is residing in a separate file. The view is instantiated somewhere else. I tried the method as mentioned above for the state. But this also doesn’t seem to work, because the view is instantiated after the state is created.
Also I’d like to know if it is intended behavior, that when I insert a decoration at the end of a document it can’t be removed with the cursor or selected. If I add a decoration in the middle of some text, I can delete the decoration and also select it.
That’s what my suggestion about passing a getState callback to the plugin was about. It’ll involve storing the state somewhere outside of the view (which is easy to do in dispatchTransaction, and a good idea in general), but if you do that, you can pass a function that looks at whichever variable or property you’re using for the state when you create the plugin.