Extensions (v5.0.0)

What is an Extension?

Extensions are custom actions or commands that can be passed in via the extensions option to medium-editor. They can replace existing buttons if they share the same name, or can add additional custom functionality into the editor. Extensions can be implemented in any way, and just provide a way to hook into medium-editor. New extensions can be created by extending the exposed MediumEditor.Extension object via MediumEditor.Extension.extend().

Examples of functionality that are implemented via built-in extensions:
Examples of custom built external extensions:

What is a Button?

Buttons are a specific type of Extension which have a contract with the MediumEditor toolbar. Buttons have specific lifecycle methods that MediumEditor and the toolbar use to interact with these specific types of Extensions. These contract create easy hooks, allowing custom buttons to:
Display an element in the toolbar (ie a clickable button/link) Execute an action on the editor text when clicked (ie bold, underline, blockquote, etc.)
* Update the appearance of the element based on the user selection (ie the bold button looks ‘active’ if the selected text is already bold, ‘inactive’ if the text is not bold)

All of the built-in MediumEditor buttons are just Button Extensions with different configuration:
Examples of custom built external buttons:

What is a Form Extension?

Form Extensions are a specific type of Button Extension which collect input from the user via the toolbar. Form Extensions extend from Button, and thus inherit all of the lifecycle methods of a Button. In addition, Form Extensions have some additional methods exposed to interact with MediumEditor and provide some common functionality.

Built-in Form Extensions

Extensions

Example: DisableContextMenuExtension

Define the Extension

As a simple example, let’s create an extension that disables the context menu from appearing when the user right-clicks on the editor.

Defining this extension is as simple as calling MediumEditor.Extension.extend() and passing in the methods/properties we want to override.

var DisableContextMenuExtension = MediumEditor.Extension.extend({
  name: 'disable-context-menu'
});

We now have an extension named 'disable-context-menu' which we can pass into MediumEditor like this:

new MediumEditor('.editable', {
  extensions: {
    'disable-context-menu': new DisableContextMenuExtension()
  }
});

Attaching To Click

To make the extension actually do something, we’ll want to attach to the click event on any elements of the editor. We can set this up by implementing the init() method, which is called on every Extension during setup of MediumEditor:

var DisableContextMenuExtension = MediumEditor.Extension.extend({
  name: 'disable-context-menu',

  init: function () {
    this.base.subscribe('editableClick', this.handleClick.bind(this));
  },

  handleClick: function (event, editable) { }
});

Here, we’re leveraging some of the helpers that are available to all Extensions.

NOTE

Adding Functionality

So, the last piece we need is to handle the click event and, if it’s a right-click, prevent the default action:

var DisableContextMenuExtension = MediumEditor.Extension.extend({
  name: 'disable-context-menu',

  init: function () {
    this.subscribe('editableClick', this.handleClick.bind(this));
  },

  handleClick: function (event, editable) { 
    if ((event.which && event.which === 3) ||
        (event.button && event.button === 2)) {
      event.preventDefault();
    }
  }
});

Leveraging Custom Event Listeners

Let’s say we wanted to allow users to be able to set an attribute on any of the elements which would disable our extension (thus allowing right-click). Since this could happen at any point, we can’t just not attach the event handler, we’ll need to inspect the editor element on each click. One way to accomplish this would be to leverage one of the features of custom events, where the 2nd argument is always the editable that triggered the event. So, let’s use the 2nd argument of the custom event listener to detect if a data-allow-right-click attribute exists on the element.

var DisableContextMenuExtension = MediumEditor.Extension.extend({
  name: 'disable-context-menu',

  init: function () {
    this.subscribe('editableClick', this.handleClick.bind(this));
  },

  handleClick: function (event, editable) {
    if (editable.getAttribute('data-allow-right-click')) {
      return;
    }

    if ((event.which && event.which === 3) ||
        (event.button && event.button === 2)) {
      event.preventDefault();
    }
  }
});

NOTE

For events like click, we could always use currentTarget and not need to use the reference to the editable element. However, there may be times when we want to trigger one of these events manually, and this allows us to specify exactly which editable element we want to trigger the event for. It’s also a handy standardization for events which are more complicated, like the custom focus and blur events.

Extension Interface

name (string)

The name to identify the extension by. This is used for calls to MediumEditor.getExtensionByName(name) to retrieve the extension. If not defined, this will be set to whatever identifier was used when passing the extension into MediumEditor via the extensions option.


init()

Called by MediumEditor during initialization. The .base property will already have been set to current instance of MediumEditor when this is called. All helper methods will exist as well


subscribe(name, listener)


subscribe(name, listener)


subscribe(name, listener)

Extension Helpers

base (MediumEditor)

A reference to the instance of MediumEditor that this extension is part of.


subscribe(name, listener)

Buttons

Example: Highlight Button

Button Extension API

Form Buttons

Example: Anchor

From Extension API