CKEditor integration

The project uses CKEditor 5 Premium for Drafting documents in MEG Docs and other places where a rich text editor is required. The integration is implemented in editor app.

Use of the integration required CKEDITOR_PREMIUM_TOKEN.

See also

Custom Fonts

Important

Before adding any fonts, make sure of the legality of hosting said fonts on our website.

Custom fonts can be added by editing the options inside of fontFamily in the following file:

meg_forms/editor/config.py

Important

Make sure the fonts are alphabetically ordered in fontFamily.

You will need to place the font static files in our repository and have it added as a @font-face like so:

@font-face {
  font-family: 'Noto Sans';
  src: url('NotoSans/NotoSans-Regular.ttf') format('truetype');
  font-weight: 400;
  font-style: normal;
}

This is in meg_forms/megdocs/static/css/fonts.css and the fonts are placed into meg_forms/megdocs/static/fonts/. You can place the font files at a different location, but you will have to point the @font-face src url properly.

Form widget

Replace the text field widget with CKeditorPremiumWidget in the form to use the CKEditor as the input.

class editor.widgets.TrackChangesConfig
force_track_changes: bool

Whether the editor should be forced into Track Changes mode (the user will only be able to make suggestions)

allow_accept_changes: bool

Whether the user can also accept/discard suggestions

class editor.widgets.CKeditorPremiumWidget(*args, config=None, **kwargs)

Django form input widget using CKEditor Premium.

Requires CKEDITOR_PREMIUM_TOKEN to work. More details in Task #29901

instance: Model | None = None

object instance being edited

force_track_changes = False

Whether to force Track Changes mode. This should be set before the widget is rendered

allow_accept_changes = False

in forced track changes mode, whether user can also accept/reject changes

get_revision_integration_config() dict[str, Union[str, int, bool, list, float, dict, NoneType]]

Config json for Revision History integration plugin

get_ckeditor_config() dict

CKEditor config object to be passed to CKEditor’s create()

The resulting dictionary will be converted to json and merged with some hardcoded js in ckeditor.js.

Config for SectionLink plugin.

class editor.fields.RichTextFormField(config_name='default', *args, **kwargs)

A form field wrapper for CKeditorPremiumWidget. Falls back to SummernoteWidget if ckeditor key is not set in CKEDITOR_PREMIUM_TOKEN. In the case of eGuides, falls back to ckeditor4.

Supports three config_name options:
  • ‘default’ - A barebones ckeditor implementation.

  • ‘megdocs’ - A full ckeditor implementation with premium features. Configuration for creating Documents.

  • ‘eguides’ - A middleground ckeditor implementation with some premium features. Configuration for creating text pages to be delivered in the eGuides app.

Form mix-in

Use CKEditorFormMixin mixin to use some of the advanced features of CKEditor such as Revision History.

class editor.forms.CKEditorFormMixin(*args, **kwargs)

Handles data from CKEditor

property ckeditor_field: Field | None

The field using CKEditor premium widget or none if none is found in this form (for example license key was not added to the project)

property media

Return all media required to render the widgets on this form.

Upgrading CKEditor

CKEditor 5 and Premium Features are now self-hosted. The bundles live under:

  • meg_forms/editor/static/vendor/ckeditor5/

  • meg_forms/editor/static/vendor/ckeditor5-premium-features/

To upgrade, replace the contents of these directories with the new version from the CKEditor customer portal. No code changes are required unless CKEditor introduces breaking import path changes.

See also

Releases lists available versions of CKEditor.
Changelog contains migration instructions for migrating between versions.
Self-hosted ZIP downloads customer portal download page.

CKEditor plugins

Bundled plugins

Some plugins are pre-bundled with CKEditor. These can be imported and added to the editor following the documentation

Users

MegUsers implemented in meg-users.js.

Provides a list of users to CKEditor. Fetches data from CKEditorUserAPI and provides list of all users visible to current user.

This plugin is required for other integration plugins to display the correct user name beside each item. It also provides the id of the current user so that changes can be correctly attributed to the right user.

Important

The user IDs in CKEditor correspond to the primary keys in Auditor model, not Django User model

Revision History

RevisionHistoryIntegration in revision-integration.js

Revision history integration interfaces with DocumentRevision model via CKEditorRevisionAPI. It provides data about past revisions to CKEditor by querying CKEditorRevisionAPI and details about changes in each revision.

Any new changes introduced by user are stored in a text field and submitted to the view where the CKEditorFormMixin passes it on to update_revision_history().

@startuml

package Backend {
    class Auditor {
        CKEditorUserAPI
        + id
        + name
        + me
    }
    class DocumentRevision {
        CKEditorRevisionAPI
        + id
        + creatorId
        + authorsIds
        + fromVersion
        + toVersion
        + createdAt
        - diffData
    }
    class CKEditorFormMixin {
        + **revision_history** field
    }
}

package "FrontEnd (JS)" {
    class CKEditor {
        * **RevisionHistory** plugin
        * **Users** plugin
    }
    class MegUsers {
        - fetchUsers()
    }
    class RevisionHistoryIntegration {
        - fetchRevisions()
        - setupRevisionHistory()
        - updateRevisions()
    }
}

' HTTP requests
MegUsers <|-- Auditor: pull list of users
RevisionHistoryIntegration <|-- DocumentRevision: pull list of revisions
RevisionHistoryIntegration <|-- DocumentRevision: pull revision detail
RevisionHistoryIntegration --|> CKEditorFormMixin: Submit content \nand revisions
' Function calls
CKEditor => RevisionHistoryIntegration: Create / update revisions
RevisionHistoryIntegration => CKEditor: insert revision list
RevisionHistoryIntegration => CKEditor: get revision detail
MegUsers => CKEditor: insert users
MegUsers => CKEditor: mark current user
CKEditorFormMixin ==> DocumentRevision: Save revisions

@enduml

Overview of the revision history integration

See also

CKEditor documentation: Revision History Integration

config options

enable: bool

Whether to enable revision history.

url: str

url to get the revision history list from from for the current document (CKEditorRevisionAPI). Also {url}{id} will be queried to get a single version details.

revisionHistoryFieldId: str

id name of the form field where CKEditor’s revision history changes will be stored when submitting

example config json
{
  "enable": true,
  "url": "/editor/api/revisions/megdocs/documentdraft/5/"
}

Track changes (Suggest changes)

TrackChangesIntegration in track-changes-integration.js implemented in Task #29902.

Enables track changes mode. Changes suggested with using this feature are stored in DocumentSuggestion model.

See also

CKEditor documentation: Track Changes.
Suggesting changes in editor documentation.

Comments

CommentsIntegration in comments-integration.js implemented in Task #30696.

Enables commenting on the document and it suggestions.

A DocumentComment is linked to DocumentCommentThread, or DocumentSuggestion if it’s a suggestion comment.

@startuml
title Comments model diagram

package Editor {
    class Comment {
        + comment_id
        + content
        + author
        + created_at
        + thread
        + suggestion
    }
    class Thread {
        + thread_id
        + unlinked_at
        + resolved_at
        + resolved_by
        + context
    }
    class Suggestion {
        + suggestion_id
        + type
        + data
        + author
        + created_at
        + has_comments
        + state: open / accepted / rejected
    }
}
package MegForms {
    class Auditor {
        + id
        + name
        + me
    }
}

Comment ...|> Thread: threadId\n(regular comment)
Comment ...|> Suggestion: threadId\n(suggestion comment)
Comment --|> Auditor: author
Suggestion --|> Auditor: author
Comment ...> Auditor: mentioned_users
Thread ...> Auditor: resolvedBy

@enduml

Overview of the Comments integration models. Each od the models in the Editor package also links to the relevant document draft object.

See also

Mentions

Support for mentioning users in CKEditor comments was developed in #30715. MentionsIntegration is implemented in mentions-integration.js. It relies on the MegUsers plugin to provide a list of users.

This feature can be disabled by setting CKEDITOR_ENABLE_COMMENT_MENTIONS variable. It is currently integrated only with Comments plugin.

Data format

Mentions are inserted in the comment as a <span> tag. This format should not be changed as the back-end relies on it being consistent to send mention notification.

Example how mentioned user is represented in the comment code.
<span class="mention" data-mention="@admin-full" data-user-id="6">@admin-full</span>

Autosave

A document draft with save automatically at the interval defined by CKEDITOR_AUTOSAVE_DELAY (typically few seconds), if user has made changes to the document.

All subsequent auto-saved changes will be visible as a single change under the Revision History. To create a new Revision, press Save, or use “Save current revision” option. Similarly, individual auto saves are not tracked to reversion to reduce number of versions created.

There will be a SAVING… indicator in the editor while the document is being saved, and ✔️ SAVED if all changes are saved.

Autosave feature can be disabled globally by setting CKEDITOR_AUTOSAVE_DELAY to 0.

Mutex

The Mutex plugin was implemented in Task #30777 (mutex.js). It ensures that only one person is editing the document at a time by making API calls to the Mutex API to acquire and maintain the lock on the document. While document is locked, other users will have a read-only view of the document.

Developing plugins

Follow the documentation when developing a plugin, and store any plugins in /meg_forms/editor/static/ckeditor_premium/plugins

Slim editor

A slim version of ckeditor was implemented for Task #31387. This enables the use of many light editors on one page (used for comment sections). The js file added (ckeditor.js`) searches for divs with the .ckeditor-instance class name and replaces them with editors.