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.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-editor
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.
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:
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
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 behaviorStikcy 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.
Transformations per language
You can also customize the transformations per-language. For example, it might be certain countries quotes' transformation do not conform with the default transformations offered by NextGen Editor. In this case you might want to disable it only for this specific language.
Custom transformations can also be specified per language and will always take precedence over the default, global, definition.
Advanced Transformations
NextGen Editor also supports custom transformations with regex and match replacement. This is considered an advanced feature and it is not represented in the Admin UI settings.
To create a custom advanced transformation, you can edit your configuration user/config/plugins/nextgen-editor.yaml
and add a custom_advanced
entry under the options.nextgenEditor.transformations
. Languages are also supported, see the examples below for both regular and language specific custom advanced transformations.
Global Advanced Custom Transformation
The following example will replace "quoted strings"
to « quoted strings »
.
options:
nextgenEditor:
transformations:
custom_advanced:
'(^|\s)(")([^"]*)(")$': [null, '« ', null, ' »']
Language Specific Advanced Custom Transformation
This example works exactly like the one above, but is specific to fr
languages only.
options:
nextgenEditor:
lang:
transformations:
custom_advanced:
fr:
'(^|\s)(")([^"]*)(")$': [null, '« ', null, ' »']
Additional Code Block Languages
It is possible to create additional code block languages. These will render fenced code with the specified language (ie, ```scss
and on frontend as <code class="language-scss">
).
It is the theme/plugin responsibility to provide highlight colors and style for the languages, NextGen Editor is only able to properly set classes based on the selected language and to provide backend visual aid regarding it.
You can add as many additional languages as needed by clicking on the "+ Add Item" button:
Language
- The language name. This should be lowercase, no spaces, single worded. Usually matches the filename extension, and it will become part of the classname when rendering.Label
- The label that identifies the language. You can use any character, but it is advised to keep it simple. (ie,Sass (Scss)
).
Extra Media Embed Providers
The Media Embed feature brings support for inserting embeddable media such as YouTube or Vimeo videos and tweets into your rich text content.
NextGen Editor allows configuring custom Providers to expand on the predefined embeddable media. You can use the “Insert media” button in the toolbar to embed media, or you can also paste the media URL directly into the editor content, and it will be automatically embedded.
You can create as many Extra Media Embed as you would like, each one requires a few fields to be filled:
Name
- The name of the Media Embed, this should be unique and it is recommended to be a simple no-space, camelCased nameURL Regex
- The regexp matching the URL when inserting media or pasting.HTML
- The HTML to be inserted for embedding the media. You can use${match[X]}
where X is the matching index from the URL Regex
Example
Here is an example of an embeddable media based on Unsplash Random API.
Consider the URL https://source.unsplash.com/random?nature,water
. We want to be able to paste this URL or add it via "Insert media" and automatically convert it to an embedded image. As shown in the screenshot above, these are the parameters you would use:
Name | URL Regex | HTML |
---|---|---|
unsplashRandom | /^source\.unsplash\.com\/random?.*/ |
<img src="https://${match[0]}" /> |
Displaying embedded media on your website
The media embed content saved by NextGen Editor is not going to be including previews or preformatted HTML, instead it is going to produce a semantic output that needs to be transformed on frontend by your theme.
NextGen Editor recommends using a client-side solution for transforming the embedded media on frontend via either Iframely or Embedly. More details can be found on the very detailed guide on CKEditor 5 Documentation
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.
Line Breaks
- Convert\n
in paragraphs into<br>
HTML tagsLinkify Text Links
- Automatically turn text links into HTML linksTypographer
- Enable some language-neutral replacement + quotes beautificationCodeblock Language Prefix
- what to prefix codeblocks withFancy Quotes
- Fancy quotes to be converted to regular quotesHighlight 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.
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
.
Editing Source Code (Advanced)
It is possible to enable the sourceEditing
plugin (by adding it into the toolbar options) to have a greater and more advanced control over the content of your page. This might be especially useful if you wish to write HTML code directly into the editor while having the spellcheck enabled.
The representation of the Source Code is not necessary the final value that will be stored after saving. There is a lot of conversions to Markdown and Shortcodes and cleanup that happen when saving and that cannot be captured by the Source Editing.
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 nameschema (Object)
- shortcode schematype (String)
- shortcode type ('block'
or'inline'
)title (String)
- shortcode titleparent (String)
- parent shortcode nameattributes (Object)
- shortcode attributestype (JS Type)
- attribute value typetitle (String)
- attribute titlewidget (String|Object)
- attribute editing widget type or schematype (String)
- widget typevisible (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 valuevalue (Any)
- attribute default valuepreserve (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 contentblock (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