Skip to main content

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.

A QuillJS editor with a header drop-down, bold, italic, underline, link, list and clean-format buttons, showing Hello QuillJS using deltas with QuillJS in bold

A QuillJS editor with the snow theme and a custom toolbar, after the Delta example below.

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.

Bundled Quill version

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.

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);
}
Configuration is read once

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​

PropertyDefaultWhat it does
HtmlnullThe 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.
Toolbarempty (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.
ReadOnlyfalseMakes the content read-only.
AutoFocusfalseFocuses the editor once it's loaded.
OptionsThe 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:

FormatReadWrite
HTMLHtmlHtml
Plain textTextText (drops all formatting)
DeltaGetDeltaAsync()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.

One round trip per change

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.

Treat the HTML as untrusted

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.

MemberWhat 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.
OperationsThe 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.
// 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);
tip

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.

MethodWhat 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.

// 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);

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;
note

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​

EventWhen
TextChangedThe content changed on the client. Text and Html are already updated.
SelectionChangedThe 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.
LinkClickThe user clicked a link. e.Url is the resolved, absolute URL.
DeltaChangedDeclared, with DeltaChangedEventArgs, but see the warning below.
DeltaChanged doesn't fire

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​