Handle with Care is a software collective that builds and maintains open source rich text editing libraries, including React ProseMirror [1]. We all came from The New York Times’ content management system team, and we spend a lot of time thinking about rich text and collaborative editing.
Now we’re working on something new: Pitter Patter will be a fully featured collaborative rich text editing toolkit, with all of the bells and whistles you need for your own text editor.
The space we’re entering is not devoid of solutions — Lexical, Slate, ProseMirror, and Tiptap are all viable options for building modern, browser-based rich text editors. But we feel pretty confident that we’re going to be able to bring some value, nonetheless.
First of all, Lexical, Slate, and ProseMirror (especially ProseMirror, in our opinion!) are all excellent rich text libraries, but they are also quite low level. You can build nearly anything atop them, but you will have to do quite a lot of the building yourself. Sometimes that’s exactly what you’re looking for — in that case, Pitter Patter can still provide you some value, because we’re going to be releasing individual libraries (like a CodeBlock node view, advanced markdown serialization, and suggest changes) that interop with the existing ProseMirror ecosystem.
But if you want something that’s more batteries-included, you’re mostly left with Tiptap. Tiptap has been dominant in the space for a while, but we think we can do better!
- We’re building on top of React ProseMirror, a truly React-native ProseMirror view, that doesn’t have to make any of the compromises that Tiptap’s React integration currently makes [2]
- We have a deep understanding of ProseMirror’s internals (and we’re not afraid to use it!)
- Pitter Patter will be completely open source
- We’re building on top of prosemirror-collab-commit, the best (only?) rich text collaboration protocol that is both correct and fast [3]
Anyway, we’re posting here for two reasons:
1. Maybe there are some more collaborative rich text editing nerds here that will be exciting (or not!) to hear about this. Sign up for our newsletter if you want updates!
2. Maybe there are some companies that are looking for alternative solutions to what’s out there. Consider sponsoring us on GitHub [4], or reaching out if you want to be more involved!
Howdy! React ProseMirror maintainer here. Our collective has been helping out a client with migrating their existing text editor to use React ProseMirror from @tiptap/react. They had a very complex system for deferring updates to their miniature editor preview, which involved queuing ProseMirror transactions and applying them to a second Tiptap Editor during idle time.
While migrating to React ProseMirror, initially I tried out just passing the primary editor's EditorState directly to the preview editor's <ProseMirror /> component, but the top level node view components turned out to be just slow enough to render that rendering them twice on every keypress introduced a noticeable lag. So I added a useDeferredValue to render the preview editor in a Transition! Here's a post about how that works and the tradeoffs involved. I added some interactive demos to illustrate how the Transition changes the render flow.
I found myself recently needing to implement the HTTP range request protocol in order to support video elements in Safari. It took some effort, so I figured I would document what I learned in case it’s useful for anyone else!
Howdy folks. This is a somewhat long (… sorry!) deep dive into several years’ worth of work that started when I was a staff engineer at The New York Times and has followed me into open source development in the years since I left to do my own thing! In it, I break down the issues we’ve faced while attempting to integrate React and ProseMirror — there are loads of code snippets and live demos in there. I hope that at least one other person finds this interesting!
Readium is fantastic, and Storyteller uses it basically whenever possible. But Readium is exclusively for reading EPUB contents, and doesn’t have any support for modifying or creating them, which is the primary purpose of this library!
Thanks, yeah I agree. The vast majority of the code in this library lives in a single class definition. Is it possible to move the implementations into separate files? Totally. Would that make the codebase more legible? I think at the moment, I would argue no, it would actually hurt legibility. If the class needs to grow dramatically, then maybe we’ll need a different approach, but I think this is actually the right thing to do for now!
This is like... comically hard to express succinctly haha. We've been having several conversations about how to explain it quickly to new folks. I like the phrase that the Readium folks use, "guided narration", but I don't know how useful that is for folks that aren't already familiar with what Storyteller does
I think that the thing we need to account for (which, number of words per chapter would capture this, I think) is different publications of the same book, which would need different overlays if they have different chapter filepaths, etc.
Yeah I was toying around with that, too… but folks often mess around with metadata in tools like Calibre and Audiobookshelf in ways that wouldn’t have an impact on Storyteller’s sync, but would change their hash. On the other hand, I don’t know how various publishers handle EPUB dc:identifiers and that may not be robust enough, either. We could try doing something like hashing only the contents of spine items (including their file names, since that’s how media overlays refer to content)
Is the idea that you have some devices that you want to download just the text to, but have it sync with your other devices? I think we could support that natively, honestly! Storyteller already has the input files, and it uses a text-based position system that doesn’t require the audio to exist. If you’re already doing work on this, maybe we could add it to Storyteller?
That’s a really interesting idea! The more I think about it, the more I like it.
A challenge I foresee is that the media overlays are only reusable if you have the exact same input EPUB file, and have processed it with Storyteller to mark up the sentence boundaries. EPUBs have unique identifiers, though, so maybe this would be fine! We’d need to add a new processing flow to Storyteller, but it should be doable.
Feel free to hit me up in the Storyteller chat if you want to discuss more! Thanks for sharing this idea!
You can’t do much better than that; that’s the size of the audiobook! For what it’s worth, I also used Storyteller on Wind and Truth, and got it down to 1.2GB by using the OPUS codec with a 32 kb/s bitrate.
Yup, great point. Especially if you've used some tool to convert from another file, like a PDF, into an EPUB, you can easily end up with the entire book in a single XHTML file, which, again, can be pretty heavy for a browser to parse and format! I also have no idea whether Calibre et al actually use native web views, or have their own renderers, which are almost certainly less performant than native web views!
It was, actually, very enjoyable! When we pulled React ProseMirror[1] out of the NYT text editor, it was a pretty laborious process that we had to careful plan and execute for months, and we still ended up with an internal fork for a while.
By contrast, this was mostly just moving a file around and then writing documentation and cleaning up the public API. I rather enjoy thinking about and modeling library APIs in general, so I actually had a lot of fun with it!
Thanks! Absolutely. You can invite users to your Storyteller server and give them whatever permissions are appropriate (e.g. you can choose whether they can only download books, or can also manage uploading and syncing books and/or managing users). It has SMTP support for emailing invites, or it can just generate invite links for you to share yourself.
Of course! I'm hoping to have a web reader with Media Overlay support built in to Storyteller available in the next few months, along with some much needed library management tooling, so maybe that will be useful for you! I'll try to make it snappy :)
I would guess there are multiple potential pitfalls here. Firstly, not all ebook formats are created equal -- Storyteller only operates on EPUB files, because EPUB is an open source format and it supports Media Overlays (read-aloud) natively. I can only really speak to that format, but there are others (MOBI, PDF, etc).
An EPUB is just a ZIP archive of XML and XHTML files (plus other assets, like images). Partly, I suspect, because of the dearth of actively maintained open source projects in the space, and partly because of the nature of tech in the book publishing industry, EPUB generation software used by authors and publishers often messes up this spec, which means that EPUB readers sometimes need to have fairly complex fallback logic for trying to figure out how to render a book. Also, because EPUBs are ZIP archives, some readers may either unzip the entire book into memory or "explode" it into an unzipped directory on disk, both of which may result in some slowness, especially if the book has lots of large resources. The newest Brandon Sanderson novel, for example, is ~300MB _zipped_.
Additionally, and perhaps more importantly, EPUBs (and I believe MOBIs as well) represent content as XHTML and CSS, which means that readers very often need to use a browser or webview to actually render the book. Precisely how they deliver this content into the webview can have a huge impact on performance; most browser don't love to be told to format entire novels worth of content into text columns, for example.