I made and wrote this article in 2019 for myself, but never had the nerve to publish it. This is a small story about creating a drag & drop editor in Vue.js
If there's more interest I can try going more in-depth in the code and releasing a code sandbox example.
This is done using tiptap. Every single tag is a little uneditable tiptap editor. The tags inside are a custom Vue Tiptap node. So when you drag them into the real editable editors, they will recognize the node. We then render these nodes however we want.
We have this list with tags. The goal is to drag them into a full-fledged rich-text editor.
First, I made it possible to drag the tag and release it in a box. This was not a rich-text editor yet, because those have their complex layout.
When you click the tag, it creates a copy and you drag the copy. When you release the tag, it destroys the copy and appends the tag to the dropped element.
A colleague continued on this task. He focused on dragging the tag inside a rich-text editor. He did this by dividing the text into chunks and calculating which chunk you were hovering.
Because the rich-text editor can't render Vue Components, he had to repeat the styling with a custom class.
This was working quite well, except that he received errors for exceeding the stack trace. He was storing the dragged element inside the global store.
Now when you had the tag rendered and you wanted to delete it, it would treat it like regular text. So he made a function to detect if you pressed delete against a tag, and to delete the whole tag if that was the case.
Because the rich-text editor did not allow dropping tags inside of it, he made a separate component that gets switched when it detects a tag is being dragged.
This separate component also had the logic to render a dropping indicator.
Due to the lack of available time of my colleague, I get to finish the project.
I first spent some time trying to decipher his code, which was quite plenty by now.
If I wanted to work on this, I had to fix the things I didn't like about it. So I started with removing any elements stored in the global store.
So I completely replaced the tag-dropping logic. Now I stored an array of objects in the store, telling me if it contained a text or tag node.
Then I just looped over the array, rendering the text/tag and blocks in between. These blocks could detect mouseovers and would render the droppable placeholder.
Now I was still swapping between editors. The longer I kept thinking about future use-cases, such as detecting when you delete against a tag, making it not selectable, making the tag still draggable when it's rendered inside of the rich-text editor, the less I believed in completing this.
I just couldn't resist looking into other options. So I found tiptap, a renderless and extendable rich-text editor for Vue.js.
Going through the documentation, I saw something that looked exactly like the thing we needed: Custom Vue components as nodes. This allowed me to make a custom
<tag> component that would get used inside of tiptap, which would render my real Tag component in vue.
I decided to just give it a try. I might end up with something very clever.
This dependency is quite new, so you can't just google what you want to find. So after a while of struggling to create a custom node, I managed to get it finished.
Now I could at least re-use my Vue component.
Then I started thinking. I was still switching between states. One editor to drop tags in, and one to edit your text.
Apparently, tiptap allows you to make any Node draggable.
If I could drag these tags between editors, I could just change the list with tags into mini non-readable editors, that render 1 tag, and drag those into my text fields.
And it seemed it could do that.
4 weeks and a lot of code later, I ended up deleting most of it and replacing everything with tiptap editors.