Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

The Weka editor is implemented using the the ProseMirror editor toolkit. ProseMirror provides an immutable data model, tools for configuring the document schema, and handles rendering the actual editor control for our node-based data structure. Reading through the the ProseMirror guide is  is highly recommended to understand how Weka works if you are looking at extending it.

...

This same model is used for programmatic changes to the editor content as well, e.g. to add new node when a toolbar button is clicked, we dispatch a transaction that describes the change we want to make. These actions are encapsulated as as commands, which are just functions that take the editor state (used to construct the transaction), and an optional dispatch function to execute the transaction.

...

There are four core variants to choose from:

  • full: Full editor functionality. Used for long-form text such as course pages, Totara Engage resource content, etc.

  • standard: The default variant. Excludes extensions that are only relevant for long-form content, such as layout.

  • basic: Basic text editing. Only includes b/i/u, links, lists, and emoji. This is the variant used for comments.

  • simple: This is a very simple editor containing only b/i/u.

You can also provide the name of additional extensions to load at the site of use:

...

Extensions can provide the following:

  • nodes() - map of node names to an object containing a ProseMirror node definition under the schema key, and optionally a Vue component under the component key

  • marks() - same as nodes, except for ProseMirror mark definitions

  • plugins() - array of ProseMirror plugins to add

  • toolbarItems() - array of items to add to the Weka toolbar

  • keymap(bind) - called to set up keyboard shortcuts

  • inputRules() - array

    of 

    of ProseMirror input rules, e.g. used to automatically create a list when you type an asterisk

Each ProseMirror node schema has a few key properties:

  • group - Used in content expressions

     that

     that control where nodes can be placed. In Weka, there are two main groups: block, which can appear as direct children of the document, but can also appear as list item content for example, and inline, which can appear as the children of paragraphs and headings.

  • parseDOM - Defines rules used to parse HTML and convert to this node during a paste. Read more in

    the 

    the ProseMirror documentation.

  • toDOM - Called to convert the node to HTML. This is used for copying to the clipboard, and is used to render the node if no component is provided.

  • inline - Causes ProseMirror to treat this as an inline node. This should be set to true if group  is inline.

  • attrs - Used to define the possible attributes for this node. This is an object of the form attrs: { someAttr: {}, anotherAttr: { default: null } }. In this example, someAttr is required as it does not have a default, whereas anotherAttr is optional.

  • See

    the 

    the ProseMirror documentation

     for

     for the full list of node schema properties.

You can also provide a component to render the node. Note that the component property does not go inside the schema object, it goes one level up - see the sample below. The component should extend editor_weka/components/nodes/BaseNode. The node attr values will be available as this.attrs, and componentContext as this.context.

A good way to understand each of these is to look at the existing extensions in client/component/editor_weka/src/js/extensions - ruler and list are good ones to start with.

...

client/component/weka_myplugin/src/js/extension.js
Code Block
languagejs
import BaseExtension from 'editor_weka/extensions/Base';
import Marquee from 'weka_myplugin/components/nodes/Marquee';

class MypluginExtension extends BaseExtension {
  nodes() {
    return {
      // "myplugin" here comes from the name of the jsoneditor plugin described in a section below
      'myplugin/marquee': {
        // ProseMirror node spec
        // https://prosemirror.net/docs/guide/#schema
        // https://prosemirror.net/docs/ref/#model.NodeSpec
        schema: {
          group: 'block',
          content: 'inline*',
          parseDOM: [{ tag: 'marquee' }],
          toDOM() {
            return ['marquee', 0];
          },
        },
        // Component to use for rendering in the editor.
        // If this is not specified, toDOM() will be used for rendering.
        component: Marquee,
        // You can also optionally pass "context" to the component.
        // componentContext: {
        //   replaceWithFoo: this.replaceWithFoo.bind(this),
        // }
      },
    };
  }
}

export default opt => new MypluginExtension(opt);

...

This contains a classes/extension.php that defines the JS path for the Weka front-end extension.

...

server/lib/editor/weka/extensions/myplugin/classes/extension.php
Code Block
languagephp
namespace weka_myplugin;
use editor_weka\extension\extension as base_extension;
class extension extends base_extension {
    public function get_js_path(): string {
        return "weka_myplugin/extension";
    }
}

...

In order to add your custom extension to the basic variant as well, you can override the get_included_in_variants() method to tell Weka to load it:

...

Avoid adding extensions to the simple variant, as that variant is typically used in specialised use cases.

To prevent your extension from being added to standard and full extensions, you can implement the specific_custom_extension interface to signal to Weka that this extension is use-case specific and needs to be enabled manually – either through get_included_in_variants or extra-extensions.

jsoneditor_myplugin backend plugin

...