QuillJS
QuillJS puts the Quill rich text editor into a Wisej.NET page. Like the other editors, it gives you the content as HTML and plain text. It also exposes Quill's Delta format, a JSON description of the document as a list of insert, retain and delete operations with their formatting. With Deltas you can build, store, diff and patch documents precisely, instead of manipulating HTML strings.
Everything runs from the extension assembly: the Quill library and its snow theme are embedded, so there's no CDN and no licence key. Quill itself is open source under the BSD licence.
Installation
Add the Wisej-4-QuillJS package to your Wisej.NET project:
dotnet add package Wisej-4-QuillJS
The package targets .NET Framework 4.8 and .NET 8 or later, including the -windows, Android, iOS and Mac Catalyst targets. After a rebuild, QuillJSEditor appears in the Visual Studio toolbox.
The extension embeds Quill 2.0.0-dev.2, a pre-release of Quill 2. Quill's Delta format and core API were stable by then, but check the Quill documentation for version 2 when you use options or modules beyond the ones this page shows.
Getting started
Drop a QuillJSEditor on a page, then give it a toolbar and some content. By default the editor has no toolbar: Toolbar starts as an empty configuration.
- C#
- VB.NET
using Wisej.Web.Ext.QuillJS;
public Page1()
{
InitializeComponent();
this.quillJSEditor1.Placeholder = "Write your message...";
this.quillJSEditor1.Toolbar = new object[]
{
new object[] { new { header = new object[] { 1, 2, 3, false } } },
new object[] { QuillFormat.Formats.Bold, QuillFormat.Formats.Italic, QuillFormat.Formats.Underline, QuillFormat.Formats.Link },
new object[] { new { list = QuillFormat.Lists.Ordered }, new { list = QuillFormat.Lists.Bullet } },
new object[] { QuillFormat.Formats.Clean }
};
}
private async void Page1_Load(object sender, EventArgs e)
{
var delta = new QuillDelta();
delta.Insert("Hello ");
delta.Insert("QuillJS", new Dictionary<string, object> { [QuillFormat.Formats.Bold] = true });
delta.Insert(" using deltas!\n");
await this.quillJSEditor1.SetDeltaAsync(delta);
}
Imports Wisej.Web.Ext.QuillJS
Public Sub New()
InitializeComponent()
Me.QuillJSEditor1.Placeholder = "Write your message..."
Me.QuillJSEditor1.Toolbar = New Object() {
New Object() {New With {.header = New Object() {1, 2, 3, False}}},
New Object() {QuillFormat.Formats.Bold, QuillFormat.Formats.Italic, QuillFormat.Formats.Underline, QuillFormat.Formats.Link},
New Object() {New With {.list = QuillFormat.Lists.Ordered}, New With {.list = QuillFormat.Lists.Bullet}},
New Object() {QuillFormat.Formats.Clean}
}
End Sub
Private Async Sub Page1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim delta As New QuillDelta()
delta.Insert("Hello ")
delta.Insert("QuillJS", New Dictionary(Of String, Object) From {{QuillFormat.Formats.Bold, True}})
delta.Insert(" using deltas!" & vbLf)
Await Me.QuillJSEditor1.SetDeltaAsync(delta)
End Sub
Toolbar, Theme, Placeholder, ReadOnly and Options are passed to Quill when the editor is created on the client. Set them in the constructor or the designer, before the editor is shown. Changing them afterwards doesn't reconfigure an editor that's already on screen.
Properties
| Property | Default | What it does |
|---|---|---|
Html | null | The content as HTML. Takes precedence over Text when both are set. |
Text | "" | The content as plain text. It always ends with a newline. Setting it replaces the content and removes all formatting. |
Toolbar | empty (no toolbar) | Quill's modules.toolbar configuration: an array of button groups. |
Theme | "snow" | snow (docked toolbar) or bubble (floating toolbar). Only the snow style sheet is included. See Themes. |
Placeholder | "" | Text shown while the editor is empty. |
ReadOnly | false | Makes the content read-only. |
AutoFocus | false | Focuses the editor once it's loaded. |
Options | The Quill configuration object, for modules that have no property of their own. |
Reading and writing the content
There are three ways in and out of the editor:
| Format | Read | Write |
|---|---|---|
| HTML | Html | Html |
| Plain text | Text | Text (drops all formatting) |
| Delta | GetDeltaAsync() | SetDeltaAsync(delta) replaces everything. UpdateContent(delta) applies a change. |
Html and Text are refreshed on the server every time the content changes on the client, so they're always current when an event handler runs. Quill normalizes HTML to the formats it supports, so Html can read back differently from what you assigned.
Every edit, including every keystroke, sends the new text and HTML to the server. That's convenient for live previews and validation, but it adds up with long documents or slow connections. Keep TextChanged handlers light.
Sanitize Html on the server before you display it elsewhere or send it by e-mail. A Delta is safer to store and to render again, because it only contains the formats Quill knows.
Working with Deltas
A QuillDelta is a list of QuillOperations. A document is made only of inserts. A change can also retain (skip) and delete characters.
| Member | What it does |
|---|---|
Insert(text) / Insert(text, attributes) | Adds text, optionally formatted, e.g. { "bold": true }. |
Retain(length) / Retain(length, attributes) | Skips characters, optionally re-formatting them. |
Operations | The raw list. Add a QuillOperation { Delete = n } to delete characters. |
Compose(other) | Returns the result of applying other to this delta. |
Slice(start, end) | Returns part of the delta. |
GetText() / Length() | The plain text of the inserts, and the total length. |
- C#
- VB.NET
// Save the document as a delta.
private async void buttonSave_Click(object sender, EventArgs e)
{
var delta = await this.quillJSEditor1.GetDeltaAsync();
SaveDocument(JSON.Stringify(delta));
}
// Add italic text after the first 20 characters, keeping everything else.
var change = new QuillDelta();
change.Retain(20);
change.Insert("Best regards", new Dictionary<string, object> { { QuillFormat.Formats.Italic, true } });
this.quillJSEditor1.UpdateContent(change);
' Save the document as a delta.
Private Async Sub ButtonSave_Click(sender As Object, e As EventArgs) Handles ButtonSave.Click
Dim delta = Await Me.QuillJSEditor1.GetDeltaAsync()
SaveDocument(JSON.Stringify(delta))
End Sub
' Add italic text after the first 20 characters, keeping everything else.
Dim change As New QuillDelta()
change.Retain(20)
change.Insert("Best regards", New Dictionary(Of String, Object) From {{QuillFormat.Formats.Italic, True}})
Me.QuillJSEditor1.UpdateContent(change)
The newline character \n ends a line in a Delta, and line formats such as headers, lists and alignment are stored on it. To make a heading, insert the text and then "\n" with { "header": 1 }.
Formatting from the server
These methods apply formats without the user touching the toolbar. Positions are zero-based character indexes into Text.
| Method | What it does |
|---|---|
Format(format, value) | Formats the current selection. With an empty selection, applies to what the user types next. |
FormatText(index, length, format, value) | Formats a range with an inline format (bold, color, link...). |
FormatLine(index, length, format, value) | Formats every line touching a range with a line format (header, align, list...). Inline formats have no effect. |
RemoveFormat() / RemoveFormat(index, length) | Removes inline and line formats from the selection or a range. |
GetSelectionAsync() | Returns the selection as a QuillSelection (Index, Length), or null when the editor isn't focused. |
SetSelection(index, length) | Selects a range, or places the caret when length is 0. Also focuses the editor. |
Focus() | Focuses the control and the editable area. |
Pass false as the value to remove a format. QuillFormat holds the names and values as constants, so you don't have to type strings: Formats (Bold, Header, Color, Link, ...), Alignments, Lists, Scripts, Sizes, Fonts and Directions.
- C#
- VB.NET
// Make the selected text bold.
private async void buttonBold_Click(object sender, EventArgs e)
{
var selection = await this.quillJSEditor1.GetSelectionAsync();
if (selection != null && selection.Length > 0)
this.quillJSEditor1.FormatText(selection.Index, selection.Length, QuillFormat.Formats.Bold, true);
}
// Turn the first line into a centered heading.
this.quillJSEditor1.FormatLine(0, 1, QuillFormat.Formats.Header, 1);
this.quillJSEditor1.FormatLine(0, 1, QuillFormat.Formats.Align, QuillFormat.Alignments.Center);
// Find and select a word.
int index = this.quillJSEditor1.Text.IndexOf("invoice");
if (index > -1)
this.quillJSEditor1.SetSelection(index, "invoice".Length);
' Make the selected text bold.
Private Async Sub ButtonBold_Click(sender As Object, e As EventArgs) Handles ButtonBold.Click
Dim selection = Await Me.QuillJSEditor1.GetSelectionAsync()
If selection IsNot Nothing AndAlso selection.Length > 0 Then
Me.QuillJSEditor1.FormatText(selection.Index, selection.Length, QuillFormat.Formats.Bold, True)
End If
End Sub
' Turn the first line into a centered heading.
Me.QuillJSEditor1.FormatLine(0, 1, QuillFormat.Formats.Header, 1)
Me.QuillJSEditor1.FormatLine(0, 1, QuillFormat.Formats.Align, QuillFormat.Alignments.Center)
' Find and select a word.
Dim index = Me.QuillJSEditor1.Text.IndexOf("invoice")
If index > -1 Then Me.QuillJSEditor1.SetSelection(index, "invoice".Length)
Toolbar, themes and modules
Toolbar takes Quill's toolbar module configuration. Each inner array is a group of buttons. A format given as an object with an array value becomes a drop-down (header above), and one with a single value becomes a button that sets that value (list: ordered). Member names are sent exactly as you write them, without case conversion.
The bubble theme shows a floating toolbar next to the selection instead of a docked one. Its style sheet isn't embedded, so add it to Packages first:
this.quillJSEditor1.Packages.Add(new Package
{
Name = "QuillBubbleTheme",
Source = "https://cdn.jsdelivr.net/npm/quill@2/dist/quill.bubble.css"
});
this.quillJSEditor1.Theme = "bubble";
Other Quill modules can be configured through Options. For example, the history module:
this.quillJSEditor1.Options.modules.history = new DynamicObject();
this.quillJSEditor1.Options.modules.history.delay = 2000;
this.quillJSEditor1.Options.modules.history.maxStack = 500;
CustomFormat describes a custom Quill format (tag, class, inline or block), but the editor doesn't use it by itself. You still have to register the format on the client with Quill's own API.
Events
| Event | When |
|---|---|
TextChanged | The content changed on the client. Text and Html are already updated. |
SelectionChanged | The selection or caret moved. e.Index and e.Length describe it. A length of 0 is the caret. It isn't fired when the editor loses the focus. |
LinkClick | The user clicked a link. e.Url is the resolved, absolute URL. |
DeltaChanged | Declared, with DeltaChangedEventArgs, but see the warning below. |
In the current release, the client script never raises DeltaChanged. Handle TextChanged, then call GetDeltaAsync() if you need the content as a Delta.
API reference
QuillJSEditorQuillDeltaandQuillOperationQuillFormatandCustomFormatQuillSelection- The whole
Wisej.Web.Ext.QuillJSnamespace