Most Rich Text Editors like Prosemirror, TipTap give JSON output these days. If we want to store the JSON in Ecto database how do we do that?
I have a content field defined as content, :map, default: %{} in the ecto schema. Now, in the form I have <div id="summary" phx-hook="Prosemirror" />. The Prosemirror hook will create the instance of editor. I registered an event listener for the editor - and - onChange - I get the content using editor.getJSON. How do I store this in the field content?
I tried keeping a hidden field hidden_input(f, :content) and assigned the value of getJSON to value of the above field. It is not working. It says HTML.Safe() is not implemented for Map.
How to solve this problem?
I wouldn’t think to store it in an <input>. Sounds like it is part of a larger form, or is it the only field you’re editing? The latter will be easier to handle.
You can send it from the Hook to the liveview/livecomponent using pushEvent or pushEventTo.
There are title and summary fields. So, another text field for title is there. I have to put the value of the JSON from the rich text editor into the summary - and - I thought changeset will work on the insertion. JS-based rich text editor disappers as soon as page loaded - #2 by gdub01 is what prompted me.
I am really confused - this handle_event is not getting called. The first hook is in the app.js file and I am getting an error saying handle_event/3 is undefined or private.
Struggling for a few hours but not finding what I am missing.
hey @cvkmohan
my team currently trying to implement prosemirror/remirror into frontend react app and for the same I have to handle that data and then store it in phoenix backend
so I was checking for some resources I came across your post
how you handled prosemirror data stream in your project?
did you use Phoenix Channels?
any information would be a great help.
Hello @ambareesha7 !
Yes, I am in pursuit of implementing Rich Text Editor. I am assuming you are also developing a primarily LiveView application with the need for Rich Text Editor for Text Content.
I wanted to document the entire process with resources as a blog post so that it can help others - your question hastened the process. I will put things up here itself.
Lexical · An extensible text editor framework that does things differently
These are two other libraries that I have explored but, discarded them early. Both of them have a strong coupling with React - Lexical is very recent. I was looking more in terms of plain Javascript integration - avoiding JS Framework if possible is one of my objectives.
Out of these, I discarded Milkdown first. Mainly because, as much as I could understand the documentation, Milkdown allows only one instance on a page. I would be needing multiple instances with various levels of Rich Text Editing. One instance might need a full blown Rich Text Editing, and another will need simple Github flavoured Markdown.
I explored and integrated both TipTap and ProseMirror into my code using LiveView Hooks. The implementation has been fairly straight forward. As you might have observed even I was into dual mind on whether to store the resultant data as a map or text. Initially, I went with Text format - and - returned the getHTML() of the instance in the updated() event using the hook. My point of view was, by storing the HTML, I am safer even if I have to change the editor in future. Both Prosemirror and TipTap provide HTML export of the content via an event - and - integrating it has been straight forward.
As my understanding grew, I took a few more decisions.
a. Though Tiptap provides a lot of extensions, and provides a reasonable compatibility to Prosemirror extensions, I felt it is better to stick to Prosemirror. That allowed much greater flexibility in terms of extensibility. ( Just personal experience. No Value Judgement.)
b. Instead of storing as RichText, defining a schema and storing the content as a map, provided a lot more control over validation of the Rich Content that I needed in my application. So, I moved from Text data type to Map Data Type for the content.
c. In the initial stages, the implementation is via hidden field. One hidden field for the content - One div with a hook to instantiate the RichText Instance. It worked reasonably well. Prosemirror example: replacing form textareas (github.com) This gist has been useful in that implementation. Very useful.
d. However, the above implementation does not help in fine grained validation of the content - specially for the sort of validation my application needed. That is when I came across this library - Omerlo-Technologies/ex_prosemirror: ExProsemirror is a toolkit that integrates the ProseMirror rich-text editor into elixir/phoenix. (github.com) - This module integrates prosemirror into phoenix with a very different approach. Though, in its early stages of implementation, the idea this module took up really caught my attention.
At present, I am extending the idea of exprosemirror and writing my own library for the custom validations and other stuff my application needs.
I did explore integrating Remirror into the application - and - it worked - thanks mainly due to this article Stephen Bussey - React in LiveView: How and Why?
Well, this is the brief run through of my experiments. Hope you will find this useful. Feel free to revert back with any questions.
Edit 1:
Our own Livebook.dev also has a very interesting take on Rich Text Editing. It gets one level deeper and uses remark - markdown processor powered by plugins to transform markdown. I initially wanted to copy this approach - mainly - because you get even collaborative editing also just by copy-pasting the code from the greats like @chrismccord and @josevalim. However, this approach is more suited if we are looking for more free form rich text. My application needs more control over the rich text - and - specially I would be needing mentionsembeds etc in my application.
Also kind of related – there was a topic recently discussing integrating Trix editor with Liveview along with some code snippets that you might helpful if you are unable to get Tiptap working correctly.