NextGen Editor Documentation

How to use and integrate your plugins with the NextGen Editor

Found an issue or a problem that you can't find an answer for? Create an issue in our Premium Issue Tracker.

Before you start

Before you can install a Premium Plugin, you are required to have the License Manager plugin installed. It is a free plugin available in GPM and can be installed like any other through the Plugin section of the admin plugin, or via command line.

$ bin/gpm install license-manager

Installation

First ensure you are running the latest version of Grav 1.6 or Grav 1.7 (-f forces a refresh of the GPM index).

$ bin/gpm selfupgrade -f

The NextGen Editor doesn't have any dependencies, so installation is a simple process of running the command:

$ bin/gpm install nextgen-edtior

You can also install the plugin via the Plugins section in the admin plugin.

Configuration

Before configuring this plugin, you should copy the user/plugins/ck-editor5/ck-editor5.yaml to user/config/plugins/ck-editor5.yaml and only edit that copy.

Here is the default configuration and an explanation of available options:

enabled: true

Note that if you use the admin plugin, a file with your configuration, and named ck-editor5.yaml will be saved in the user/config/plugins/ folder once the configuration is saved in the admin.

Usage

After installation, the default behavior sets the NextGen Editor as the Default for All. You can change this behavior in the plugin configuration.

Default for all

If you are running Grav 1.7, there is a new option that lets you configure which editor to use per-user. Simply click on your user profile (or the user in the Users list if you are using FlexUsers), and choose the editor you prefer to use:

Profile editor selection

Keyboard Shortcuts

Platform Specific Shortcuts

Action PC Mac
Save Ctrl + S + S
Copy Ctrl + C + C
Paste Ctrl + V + V
Copy + Cut Ctrl + X + X
Undo Ctrl + Z + Z
Redo Ctrl + Y + V
Ctrl + Shift + Z + Shift + Z
Bold Ctrl + B + B
Italic Ctrl + I + I
Link Ctrl + K + K

General Shortcuts

Action Keyboard
Insert a hard break (e.g. a new paragraph) Enter
Insert a soft break (e.g. carriage return) Shift + Enter
Nest the current list item (when in a list) Tab

Widget/Shortcode Shortcuts

Action Keyboard
Insert a new paragraph directly after a widget Enter
Insert a new paragraph directly before a widget Shift + Enter
Display the caret to allow typing directly before a widget /
Display the caret to allow typing directly after a widget /

Table Cell Shortcuts

Action Keyboard
Move the selection to the next cell Tab
Move the selection to the previous cell Shift + Tab
Insert a new table row (when in the last cell of a table) Tab
Navigate through the table / / /

User Interface and Navigation Shortcuts

Action Keyboard
Close contextual balloons and UI components like dropdowns Esc
Move focus to the visible contextual balloon Tab
Move focus between fields in contextual balloons Tab
Move focus to the toolbar Alt + F10
Navigate through the toolbar / / /
Execute the currently focused button Enter

Editor Options

Editor Options

Here you can specify:

  • Fixed Height - The ability to show only a fixed height editor and a scrollbar, rather than the full height editor which is the default behavior
  • Stikcy Toolbar - Particularly useful for the default full-height editor, the toolbar will stick to the page as you scroll down large content.
  • Toolbar - Gives you the ability to remove specific toolbar items. The default list displays all the currently available toolbar items.
  • Show HTML Snippets Preview - By default, HTML Snippets will be displayed as HTML code. When this setting is enabled, NextGen Editor will render the HTML within the editor. Both views will have a pencil icon that allows for editing the HTML directly.

    Even with previews enable, JavaScript won't be executed.

Automatic Text Transformations

It is possible to enable and disable sets of text transformations, conveniently grouped by typhography, quotations, symbols, mathematical. You can also create custom text transformations to meet your need. Whenever the input text matches a text transformation, this will get automatically converted to the appropriate output.

Automatic Text Transformations

Markdown → Editor Options

These options control the loading behavior when NextGen Editor loads markdown from the .md file and converts it into a WYSIWYM editor environment.

Markdown to Editor

  • Line Breaks - Convert \n in paragraphs into <br> HTML tags
  • Linkify Text Links - Automatically turn text links into HTML links
  • Typographer - Enable some language-neutral replacement + quotes beautification
  • Codeblock Language Prefix - what to prefix codeblocks with
  • Fancy Quotes - Fancy quotes to be converted to regular quotes
  • Highlight Function - Advanced use only, change this only if you know what you are doing

Editor → Markdown Options

These options control the saving behavior when NextGen Editor saves the markdown to the .md file from the WYSIWYM editor environment. These control the markdown format behavior.

Editor to Markdown

HTML Snippets

At some point during writing content, you will find yourself needing to save raw HTML in your content but do not want said HTML to get converted to Markdown, nor having the HTML get modified by any internal operations.

NextGen Editor makes this possible thanks to the HTML Snippets, a custom shortcut available in the toolbar that allows to write any HTML that, upon saving, gets wrapped by a div with class name raw-html-embed.

HTML Snippets Editor

HTML Snippets Saved

Extending NextGen Editor

The NextGen Editor has powerful functionality that allows it to be extended via plugins. Good examples of this capability can be found in both Shortcode Core and Page Inject plugins. These both already have support for NextGen editor custom blocks.

There a single plugin events that you can utilize to register your plugin's custom functionality: registerNextGenEditorPlugin. In Shortcode Core plugin, two methods are called from this one event. One to register some global functionality, and another to register specific shortcodes:

   public function registerNextGenEditorPlugin($event) {
        $config = $this->config->get('plugins.shortcode-core.nextgen-editor');
        $plugins = $event['plugins'];

        if ($config['env'] !== 'development') {
            $plugins['css'][] = 'plugin://shortcode-core/nextgen-editor/dist/css/app.css';
            $plugins['js'][]  = 'plugin://shortcode-core/nextgen-editor/dist/js/app.js';
        } else {
            $plugins['js'][]  = 'http://' . $config['dev_host'] . ':' . $config['dev_port'] . '/js/app.js';
        }

        $event['plugins']  = $plugins;
        return $event;
    }

In this example, some core CSS and JS functionality is added to the plugins element on the $event object that is passed to the event. This particular example even has the ability to load a dynamic app.js when in development mode.

The Shortcode Core plugin is providing everything you need to handle custom shortcode logic, so you don't need to repeat this functionality if all you developing a shortcode plugin. The Shortcode UI plugin is a custom shortcode plugin that relies on Shortcode Core for it's core functionality and also relies on it for it's core support for the NextGen Editor.

The Shortcode UI plugin listens for the registerNextGenEditorPlugin event and calls the registerNextGenEditorPluginShortcodes method.:

public static function getSubscribedEvents()
{
    return [
        ...
        'registerNextGenEditorPlugin' => ['registerNextGenEditorPluginShortcodes', 0],
    ];
}

Then this method simply adds the JS that controls the shortcode handling, and optionally any CSS that may be required:

public function registerNextGenEditorPluginShortcodes($event) {
    $plugins = $event['plugins'];

    $plugins['js'][] = 'plugin://shortcode-ui/nextgen-editor/shortcodes/shortcode-ui.js';
    $plugins['js'][] = 'plugin://shortcode-ui/nextgen-editor/shortcodes/ui-accordion/ui-accordion.js';
    $plugins['js'][] = 'plugin://shortcode-ui/nextgen-editor/shortcodes/ui-tabs/ui-tabs.js';
    $plugins['js'][] = 'plugin://shortcode-ui/nextgen-editor/shortcodes/ui-browser/ui-browser.js';
    $plugins['js'][] = 'plugin://shortcode-ui/nextgen-editor/shortcodes/ui-polaroid/ui-polaroid.js';
    $plugins['js'][] = 'plugin://shortcode-ui/nextgen-editor/shortcodes/ui-animated-text/ui-animated-text.js';
    $plugins['js'][] = 'plugin://shortcode-ui/nextgen-editor/shortcodes/ui-image-compare/ui-image-compare.js';

    $event['plugins']  = $plugins;
    return $event;
}

The first JavaScript file shortcode-ui.js contains the logic to create the plugin reference and adds a button group that will used by all the subsequent shortcodes so they can appear in a dropdown:

window.nextgenEditor.addHook('hookInit', () => {
  window.nextgenEditor.addShortcodePlugin('shortcode-ui', {
    title: 'Shortcode UI',
  });

  window.nextgenEditor.addButtonGroup('shortcode-ui', {
    icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M2.5 19h19a2.5 2.5 0 002.5-2.5v-14A2.5 2.5 0 0021.5 0h-19A2.5 2.5 0 000 2.5v14A2.5 2.5 0 002.5 19zM18.25 7.5a.75.75 0 011.28-.53l2 2a.749.749 0 010 1.06l-2 2a.746.746 0 01-.53.22.738.738 0 01-.287-.057.75.75 0 01-.463-.693zM2.47 8.97l2-2a.75.75 0 011.28.53v4a.75.75 0 01-.463.693.738.738 0 01-.287.057.746.746 0 01-.53-.22l-2-2a.749.749 0 010-1.06z"/><circle cx="7.5" cy="22.5" r="1.5"/><circle cx="12" cy="22.5" r="1.5"/><circle cx="16.5" cy="22.5" r="1.5"/></svg>',
    label: 'Shortcode UI',
  });
});

The individual shortcode JavaScript files contain the logic that is pertinent to that shortcode. A relatively simple example is the UI Browser shortcode (ui-browser/ui-browser.js):

window.nextgenEditor.addShortcode('ui-browser', {
  type: 'block',
  plugin: 'shortcode-ui',
  title: 'UI Browser',
  button: {
    group: 'shortcode-ui',
    label: 'UI Browser',
    icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><rect x="4" y="8.25" style="width:16px;height:3px;" rx=".5" ry=".5"/><path d="M13.25 12.75a.75.75 0 000 1.5h6a.75.75 0 000-1.5zM19.25 16.25h-6a.75.75 0 000 1.5h6a.75.75 0 000-1.5z"/><rect x="4" y="12.75" style="width:7px;height:6.5px" rx="1" ry="1"/><path d="M24 4.75a3 3 0 00-3-3H3a3 3 0 00-3 3v14.5a3 3 0 003 3h18a3 3 0 003-3zm-14.346-1a.966.966 0 011.692 0 .969.969 0 01.154.5.969.969 0 01-.154.5.966.966 0 01-1.692 0 .969.969 0 01-.154-.5.969.969 0 01.154-.5zm-3.5 0a.966.966 0 011.692 0 .969.969 0 01.154.5.969.969 0 01-.154.5.966.966 0 01-1.692 0A.969.969 0 016 4.25a.969.969 0 01.154-.5zm-3.562.092A1 1 0 013.5 3.25a.985.985 0 01.846.5.969.969 0 01.154.5.969.969 0 01-.154.5.966.966 0 01-1.692 0 .969.969 0 01-.154-.5.979.979 0 01.092-.408zM22 19.25a1 1 0 01-1 1H3a1 1 0 01-1-1V7a.25.25 0 01.25-.25h19.5A.25.25 0 0122 7z"/></svg>',
  },
  attributes: {
    address: {
      type: String,
      title: 'Address',
      widget: 'input-text',
      default: '',
    },
    class: {
      type: String,
      title: 'Class',
      widget: 'input-text',
      default: '',
    },
  },
  titlebar({ attributes }) {
    return `address: <strong>${attributes.address}</strong>`;
  },
  content() {
    return `<div>{{content_editable}}</div>`;
  },
});

Configuration Details:

  • name (String) - shortcode name
  • schema (Object) - shortcode schema
    • type (String) - shortcode type ('block' or 'inline')
    • title (String) - shortcode title
    • parent (String) - parent shortcode name
    • attributes (Object) - shortcode attributes
    • type (JS Type) - attribute value type
    • title (String) - attribute title
    • widget (String|Object) - attribute editing widget type or schema
      • type (String) - widget type
      • visible (Function) - whether the widget is displayed in the settings popup
      • (arg) attributes - shortcode attribute values
      • (arg) parentAttributes - parent shortcode attribute values (if shortcode has parent in schema)
    • default (Any|Object) - attribute default value
      • value (Any) - attribute default value
      • preserve (Boolean) - whether to cut attribute if value is equal to default value on save
    • titlebar (Function) - creating shortcode titlebar content using ckeditor model writer (only for block type)
    • (arg) writer - ckeditor model writer instance
    • (arg) container - ckeditor model element of parent container (place where to insert titlebar content)
    • (arg) attributes - shortcode attribute values
    • (arg) parentAttributes - parent shortcode attribute values (if shortcode has parent in schema)
    • content (Function) - creating wrapper for shorcode inner content using ckeditor model writer
    • (arg) writer - ckeditor model writer instance
    • (arg) container - ckeditor model element of parent container (place where to insert wrapper content)
    • (arg) attributes - shortcode attribute values
    • (arg) parentAttributes - parent shortcode attribute values (if shortcode has parent in schema)
    • (return) - ckeditor model element of parent container for content (place where to put shortcode inner content)
    • preserve (Object) - list of html tags used in wrapper for shortcode inner content
    • block (Array) - for block type
      • (String|Object)
    • inline (Array) - for inline type
      • (String|Object)

Example #1 (block type)

window.nextgenEditor.addShortcode('fieldset', {
  type: 'block',
  title: 'Fieldset',
  attributes: {
    title: {
      type: String,
      title: 'Title',
      widget: 'input-text',
      default: '',
    },
  },
  titlebar(writer, container, attributes) {
    const strong = writer.createElement('strong');
    writer.append(writer.createText(attributes.title), strong);
    writer.append(writer.createText('title: '), container);
    writer.append(strong, container);
  },
  upcast(writer, container, attributes) {
    const fieldset = writer.createElement('fieldset');
    writer.append(fieldset, container);

    const legend = writer.createElement('legend');
    writer.append(writer.createText(attributes.title), legend);
    writer.append(legend, fieldset);

    const content = writer.createElement('div');
    writer.append(content, fieldset);

    return content;
  },
  preserve: {
    block: [
      'fieldset',
      { name: 'legend', readonly: true },
    ],
  },
});

Example #2 (inline type)

window.nextgenEditor.addShortcode('color', {
  type: 'inline',
  title: 'Color',
  attributes: {
    value: {
      type: String,
      title: 'Value',
      widget: 'input-text',
      default: '',
    },
  },
  upcast(writer, container, attributes) {
    const font = writer.createElement('font', { color: attributes.value });
    writer.append(font, container);

    return font;
  },
  preserve: {
    inline: ['font'],
  },
});

Credits

This plugin is built on-top of the fantastic CKEditor5 plugin