Taming Rich Text in Elixir
Render Quill Delta on the Server
Need to render user-defined rich text in your web app or emails?
Let me show you DeltaHtml
, my solution for the Quill Delta format.
It's published at delta_html, but you might want to just copy & adapt the code.
The Challenge of Rich Text
Quill is a powerful, open-source rich text editor that provides a clean, modular architecture for developers. Like many rich text editors, Quill stores content in its own format - typically a structured JSON representation called a "Delta" document that describes the content and its formatting.
When it comes time to display this content, you have several options:
- Convert to HTML in the browser: Use the editor's own conversion methods
- Use Markdown instead: A simpler format with established conversion tools
- Convert to HTML on the server: Take control of the conversion process
Why Server-Side Conversion?
While it might seem easiest to just use Quill's built-in getSemanticHTML()
method and store the resulting HTML, this approach has several drawbacks:
- You need to store both the Delta and HTML, increasing storage requirements
- You must still sanitize the HTML server-side to prevent XSS attacks
- You have less control over the output format, especially for plugins like mentions
- Emails require inline CSS which isn't well-supported by browser-side conversion
Using Markdown is another alternative, with excellent tools like Earmark for conversion. However, if your users expect a WYSIWYG editing experience, Markdown might not be the best choice.
Converting Delta to HTML
Slab's delta Elixir library might help implement collaborative editing via Operational Transformation. But it won't help you render your document on the server.
Luckily, this is not too difficult and has been implemented in other languages. Here's an Elixir solution:
iex> DeltaHtml.to_html([%{"insert" => "Hello world!\n"}])
"<p>Hello world!</p>"
iex> DeltaHtml.to_html([
%{"attributes" => %{"bold" => true}, "insert" => "Bold text"},
%{"insert" => "\n"}
])
"<p><strong>Bold text</strong></p>"
# Preserve whitespace
iex> DeltaHtml.to_html([%{"insert" => "a b\tc\\n"}], preserve_whitespace: true)
"<div style=\\"white-space: pre-wrap;\\"><p>a b\tc\\n</p></div>"
Key Features
- Sanitized HTML: No need to worry about XSS attacks
- Store Only Delta: Keep your database lean by storing just the document model
- Control Over Output Format: Customize the HTML output to match your needs
- Stay Flexy: New design? No problem, change the layout on the fly.
- Support for Quill Plugins: Works with extensions like
quill-mention
- Small and Simple: The entire implementation is a single file with no dependencies beyond Floki
Supported Formatting
The module supports a wide range of Quill formatting options. Not everything is covered, see the docs for an up to date list:
Inline Formatting
- Bold, italic, underline, strikethrough
- Text color and background color
- Font styles (serif, monospace)
- Text sizes (small, large, huge)
- Superscript/subscript
- Inline code
- Links
Block Formatting
- Paragraphs and headers
- Ordered and unordered lists (with indentation)
- Blockquotes
- Code blocks (with language support)
Plugins
quill-mention
support (outputs as#{denotation_char}#{id}
, e.g.,+name
)
How It Works
The implementation is surprisingly simple. It:
- Processes the Delta operations sequentially
- Groups operations into blocks based on line breaks
- Applies inline formatting using HTML tags
- Handles block-level formatting using appropriate tags
- Returns sanitized HTML
The core insight is that Delta operations with a newline (\n
) character at the end represent block-level elements, while operations without newlines represent inline content.
Here's a simplified flowchart of the conversion process:
Delta JSON → Parse Operations → Group by Blocks → Apply Formatting → Output HTML
I first tried to port other language implementations to Elixir, but that resulted in very messy unreadable code. I ended up implementing from scratch instead - let me know what you think!
Adapting It for Your Needs
One of the best things about DeltaHtml
is how easy it is to adapt for your specific needs. Since it's a single, well-organized file, you can simply copy it into your project and modify it as needed.
For example, if you want to add support for additional formatting options or change how certain elements are rendered, just copy the code and make your changes.
Some common customizations include:
- Adding support for custom Quill plugins
- Changing CSS styles for colors and fonts
- Supporting additional font families
- Adding special handling for mentions or other custom content
Integration with Phoenix
Integrating with Phoenix is straightforward. Here's a simple example:
# In your template
<div class="prose">
{raw DeltaHtml.to_html(@article.content)}
</div>
If you're using Tailwind CSS, you can take advantage of the excellent Typography plugin which provides a prose
class for styled content.
This will automatically apply sensible typography styles to your rendered Delta content, handling elements like headings, lists, blockquotes, and code blocks with consistent styling.
When to Use It
DeltaHtml
is ideal when:
- You're using Quill or a similar editor that uses the Delta format
- You need to display rich text in web pages or emails
- You want to maintain separation between your content model and presentation
- Security is important (always sanitize user content!)
- You prefer a lightweight solution without external dependencies
Conclusion
Rich text handling in web applications doesn't have to be complex. With DeltaHtml
, you can easily convert Quill Delta documents to clean, sanitized HTML for display in your Elixir applications.
Check out the published library at delta_html. For most use cases, you should probably copy and adapt the code to your needs.