Documentation

Authoring tools

Footnotes, format painter & change case

Three long-document tools that the other commercial editors put behind a paid plan. Footnotes with markers and notes that renumber themselves, a format painter that copies character formatting from one selection to another, and change case for the selection. All three ship in the standard license.

Table of contents

Inserts a contents block built from the document’s headings, with links that jump to them, and keeps it in step as you write. Rename a heading, add one, delete one, move a section — the list re-derives itself from the document, so it cannot drift.

This is not the same thing as the document outline panel. That panel is editor chrome for navigation and never appears in your saved HTML. A table of contents is part of the document you publish or print, so it survives getHTMLCode() and reads correctly with no JavaScript running.

editor.insertTableOfContents();   // one per document
editor.updateTableOfContents();   // force a rebuild (normally automatic)
editor.hasTableOfContents();      // -> true | false
editor.removeTableOfContents();
editor.getTableOfContents();
// -> [{ id: "rte-h-3k1a0", level: 1, text: "Master Agreement", page: 1 }, ...]
ConfigDefaultWhat it does
tocTitle"Contents"Heading above the list. Set to "" to omit it.
tocMinLevel / tocMaxLevel1 / 3Heading levels included, inclusive. Indentation follows the level.
tocPageNumbers"auto""auto" shows page numbers when page view is on, true always tries, false never.

Page numbers.With the pagination plugin loaded and page view on, each entry gets the page its heading falls on, set with a dotted leader like a printed contents page. The numbers come from the same page boundaries the page-view overlay draws, so they always agree with what you see. Outside page view no new number is computed — a page number would be a fiction — but numbers already in the block are kept, so loading a document that was saved in page view never silently strips them. Re-enter page view to refresh.

The block is contenteditable="false"and is regenerated from the headings, so it is not hand-editable — delete it as a unit and re-insert to move it. Headings inside generated regions (the contents block itself, the footnotes section) are skipped, so it never lists its own title.

Footnotes

Inserts a numbered reference marker at the caret and a matching note in a footnotes section at the end of the document, then keeps the two in step. Numbering follows document order: add a footnote above an existing one, delete a marker, or move a paragraph, and every marker and note renumbers and the notes list re-sorts to match. Deleting a marker deletes its note, and removing the last marker removes the whole section — no orphans are left behind in the saved HTML.

<script src="richtexteditor/rte.js"></script>
<script src="richtexteditor/plugins/all_plugins.js"></script>

<script>
  var editor = new RichTextEditor("#editor");

  editor.insertFootnote();                 // marker at the caret, caret moves into the note
  editor.insertFootnote("Ibid., p. 42");   // ...or supply the note text up front

  editor.getFootnotes();
  // -> [{ id: "fn3k1a0", number: 1, text: "Ibid., p. 42" }]

  editor.syncFootnotes();      // force a renumber (normally automatic)
  editor.getFootnotesCss();    // the stylesheet, to reuse on your public page
</script>
ConfigDefaultWhat it does
footnotesTitle"Footnotes"Heading above the notes section. Set to "" to omit it.
footnotesNumbering"decimal"List style of the notes: decimal, lower-roman, upper-roman, lower-alpha, upper-alpha.
footnotesMarkerPrefix""Wraps the marker number, e.g. "[" to render [1].
footnotesMarkerSuffix""The closing half of the above, e.g. "]".

What lands in your HTML

Footnotes are content, so unlike the page-view overlay they are meant to persist in getHTMLCode(). The number is written as literal text rather than drawn by a CSS counter, so the saved HTML is self-contained: publish it, export it, or render it on a page that never loads our stylesheet and the numbering is still there and still correct.

<p>The clause was struck out<sup class="rte-fn-ref" data-fn-id="fn3k1a0"
     data-fn-number="1" id="fnref-fn3k1a0" contenteditable="false">1</sup>.</p>

<section class="rte-footnotes">
  <h2 class="rte-fn-title">Footnotes</h2>
  <ol class="rte-fn-list" data-numbering="decimal">
    <li class="rte-fn-note" data-fn-id="fn3k1a0" data-fn-number="1" id="fn-fn3k1a0">Ibid., p. 42<a
      class="rte-fn-back" href="#fnref-fn3k1a0" contenteditable="false">&#8617;</a></li>
  </ol>
</section>

Markers are contenteditable="false" so the caret treats them as a single unit and cannot land inside the number. Clicking a marker jumps to its note; clicking the returns.

Format painter

Picks up the character formatting at the caret and paints it onto the next selection. Formatting is read from the computed style, so it does not matter whether the source was <b>, style="font-weight:700"or a CSS class. Painting also strips the character formatting already inside the target — which is what makes painting unformatted text onto bold text actually work.

editor.copyFormatting();       // capture at the caret
editor.pasteFormatting();      // apply to the current selection

editor.toggleFormatPainter();      // one-shot: capture, then paint the next selection
editor.toggleFormatPainter(true);  // sticky: keep painting until cancelled
editor.cancelFormatPainter();      // or press Escape
editor.isFormatPainterActive();    // -> true | false
editor.getCapturedFormatting();    // -> { "font-weight": "700", "color": "rgb(204, 0, 0)" }

Only properties that actually differfrom the editor’s baseline are carried, so a paint emits a short style attribute rather than a wall of inherited defaults. Repainting the same run does not nest wrappers. Which properties travel is configurable via formatPainterProperties; block-level properties such as alignment and margins are deliberately excluded, because a painter that reflowed the document is not what the tool is for.

Change case

Rewrites the actual characters in the selection — not a text-transform, which only looks right until the HTML is consumed somewhere else. Inline markup survives: change the case of the <strong>Data</strong> Act and the <strong> is still there afterwards. With nothing selected it operates on the word under the caret.

editor.changeCase("upper");      // THE DATA ACT
editor.changeCase("lower");      // the data act
editor.changeCase("title");      // The Data Act
editor.changeCase("sentence");   // The data act
editor.changeCase("toggle");     // tHE dATA aCT

editor.getChangeCaseModes();     // -> ["upper","lower","title","sentence","toggle"]

Title Case keeps small words (a, an, and, of, the, to…) lowercase unless they are first or last, and applies the same rule inside hyphenated compounds — so state-of-the-art becomes State-of-the-Art, not State-Of-The-Art. Edit the list with titleCaseSmallWords.

Autocorrect & typography

Fixes common typos as you type, capitalises the start of sentences, and turns typed approximations into the real characters: straight quotes become curly, -- becomes an em dash, ... becomes an ellipsis, (c) becomes ©, 1/2becomes ½.

editor.setAutocorrectEnabled(false);   // off at runtime
editor.isAutocorrectEnabled();

editor.addAutocorrectRule("acheive", "achieve");
editor.getAutocorrectRules();

// Run the same typography normalisation over imported or pasted text:
editor.applyTypography('He said "hello" -- it is 1/2 done...');
// -> 'He said “hello” — it is ½ done…'
ConfigDefaultWhat it does
autocorrectEnabledtrueMaster switch.
autocorrectSpellingtrueCommon-misspelling table (tehthe).
autocorrectCapitalizetrueCapitalise the first word of a sentence.
autocorrectTypographytrueSmart quotes, dashes, ellipsis, arrows, symbols, fractions.
autocorrectReplacementsnullExtra rules, merged over the defaults. Map a word to itself to disable a built-in one.

Nothing fires inside code. <code>, <pre>, <kbd>, <samp> and <var> are skipped entirely, as is any subtree marked data-rte-no-autocorrect. Curling the quotes inside a code sample corrupts it, which is the most damaging thing a feature like this can do.

Press Backspace immediately after a correction to undo itand keep exactly what you typed — the same escape hatch Word and Google Docs give you. The built-in word list is deliberately conservative: strings that are also real words (ill, lets, its) are left alone, because guessing wrong on a real word is worse than missing a correction.

Merge fields

Placeholders that stand in for data, so one document becomes a template you render against many records — mail merge, invoice runs, personalised letters, generated contracts. Each field is an atomiccontenteditable="false" chip, so it can never be half-deleted into {{customer.na}} and silently stop resolving.

var editor = new RichTextEditor("#editor", {
  mergeFields: [
    { id: "recipient.name",  label: "Recipient name", sample: "Ada Lovelace" },
    { id: "invoice.total",   label: "Invoice total",  sample: "£1,240.00" },
    { id: "date.today",      label: "Today's date",   sample: function () { return new Date().toLocaleDateString(); } }
  ]
});

editor.insertMergeField("recipient.name");
editor.listMergeFields();
// -> [{ id: "recipient.name", label: "Recipient name", known: true, count: 1 }]

editor.setMergeFieldPreview(true);    // show sample data instead of labels
editor.toggleMergeFieldPreview();
editor.isMergeFieldPreview();

// Render one record. Flat or nested data both work.
editor.renderMergeFields({ "recipient.name": "Grace Hopper", invoice: { total: "£90" } });
// -> HTML with the values substituted. The document itself is untouched.
ConfigDefaultWhat it does
mergeFieldsexample set{ id, label, sample }definitions. The shipped list is an example — replace it with your own schema.
mergeFieldPrefix / Suffix"{{" / "}}"Wrappers drawn around the label so a placeholder is visually obvious.
mergeFieldMissing"placeholder"What renderMergeFields does when the data has no value: leave the placeholder visible, or "empty" to remove it.

Preview never reaches your saved HTML.This is the one genuinely dangerous thing about the feature: if turning on preview and pressing save wrote “Ada Lovelace” into the template where {{Recipient name}}used to be, the user would destroy their template merely by looking at it. So the serializers are wrapped — getHTMLCode, getJSON, getHTMLContent and getText all restore every chip to its label for the duration of the call. Save while previewing and you still get the template back.

By the same principle, renderMergeFields() works on a detached clone: producing merged output never mutates the document being edited. A field with no matching data keeps its placeholder rather than rendering blank, because a silently empty invoice line is worse than an obviously unfilled one. Fields pasted from a template built elsewhere are reported with known: false rather than dropped, so you can tell the user what their data needs to supply.

Table sorting & row numbering

Sort a table by the column the caret is in, and add an automatic numbering column that renumbers itself after every sort.

editor.sortTableAtCaret("asc");        // or "desc"
editor.sortTableByColumn(2, "desc");
// -> { ok: true, sortedBy: 2, direction: "desc", detectedType: "number" }

editor.canSortTable();                 // -> { ok: false, reason: "...merged cells..." }

editor.addTableRowNumbers({ series: "upper-roman" });
editor.removeTableRowNumbers();
editor.hasTableRowNumbers();

The column type is detected, not assumed. A column of prices sorts numerically even when the cells read $1,240.00 or (500) (accounting notation for a negative), and a column of dates sorts chronologically rather than alphabetically. Text-sorting a numeric column is the classic wrong answer that puts 10 before 9. Empty cells sink to the bottom in bothdirections, because a blank means “no value”, not “the smallest value”. The sort is stable, so sorting by a second column preserves the order of the first.

It refuses on tables with merged cells and tells you why, rather than sorting them badly. Arowspan has no meaning once the rows move; silently shredding a table someone spent an hour on is far worse than declining to sort it. <thead> and <tfoot> never move, and a first row of all <th> is treated as a header even without a <thead>. Row numbering is positional, so after a sort row 1 is whatever is now on top.

Permanent pen

Switch on a fixed character format and everything you type from then on gets it — the marker you pick up to annotate a document in a colour that is obviously not the original text. Unlike the format painter, pen output is real content and persists in your saved HTML.

editor.setPermanentPen(true);
editor.togglePermanentPen();
editor.isPermanentPenActive();

editor.setPermanentPenStyle({ "color": "#0b6", "font-style": "italic" });
editor.getPermanentPenStyle();

A whole stroke goes into onespan that the pen keeps extending, rather than one span per keystroke. Moving the caret ends the stroke, so the next run gets its own span instead of text teleporting into the previous one. Switching the pen off steps the caret out of the run — otherwise typing would continue inside the styled span and the pen would appear to ignore being switched off.

All three are in the base license

CKEditor 5 gates footnotes behind its Essential, Professional and Custom plans, and puts table of contents, format painter, case change and merge fields behind “selected CKEditor Plans”. TinyMCE ships every one of these as a premium plugin “only available for paid TinyMCE subscriptions” — autocorrect and typography as two separate ones. Here they are all part of the $129 perpetual license. (In fairness: CKEditor’s automatic text transformation is in its open-source core, so autocorrect is parity there rather than a gap.) See the full comparison.