tobento/js-editor

Simple JavaScript WYSIWYG HTML Editor with inlined toolbar only.

1.0.0 2024-02-27 13:49 UTC

This package is auto-updated.

Last update: 2024-04-27 14:17:38 UTC


README

Simple JavaScript WYSIWYG HTML Editor with inlined toolbar only.

The editor does not style the content by using inline style attributes. Instead it uses classes only. This has multiple advantages:

  • easily customize content by its classes
  • using strong Content-Security-Policy blocking style-src
  • limits user to keep corporate design

You may visit the docs.tobento.ch/js-editor page for demo.

Table of Contents

Getting started

Browser support

Modern browser only.

Documentation

Basic Usage

1. Include JS/CSS

<link href="editor.css" rel="stylesheet" type="text/css">
<script src="editor.js" type="module"></script>

2. Register

<!-- using div -->
<div data-editor='{"id": "foo"}'></div>

<!-- using textarea -->
<textarea name="bar" data-editor></textarea>

<!-- with specific toolbar -->
<div data-editor='{"id": "baz", "toolbar": ["bold", "italic"]}'></div>

Thats all.

You may get the HTML code

Example using the editors:

<script type="module">
    import editors from 'core/editors.js';
    
    document.addEventListener('DOMContentLoaded', (e) => {
        const fooEditorCode = editors.get('foo').code();
        const barEditorCode = editors.get('bar').code();
    });
</script>

Example using events:

<script type="module">
    import events from 'core/events.js';

    events.listen('editor.blur', (e, editor) => {
        const code = editor.code();
    });
</script>

Custom Editor

You may create a custom editor:

1. Create custom-editor.js

import editors from './core/editors.js';
import translator from './core/translator.js';
import table from './plugin/table.js';
import styles from './plugin/styles.js';
import { basic, html, clear, links } from './plugin/basic.js';

// you may add translations:
translator.locale('de-CH');
// or translator.locale(document.querySelector('html').getAttribute('lang'));
translator.localeFallbacks({"de-CH": "en"});
translator.add('de-CH', {
    "Start typing something...": "Start mit schreiben...",
    "Table": "Tabelle",
    "Add row below": "Zeile danach einfügen",
    "Add row above": "Zeile davor einfügen",
    "Delete row": "Zeile löschen",
    "Add column left": "Spalte links einfügen",
    "Add column right": "Spalte rechts einfügen",
    "Delete column": "Spalte löschen",
    "Delete table": "Tabelle löschen"
});

// you may add styles:
styles.add({
    key: "style.fonts",
    title: "Font styles",
    options: {
        "Default Font": "",
        "Primary Font": "font-primary",
        "Secondary Font": "font-secondary"
    },
    optionToClass: true
});

// add plugins:
editors.plugins([basic, html, clear, links, table, styles]);

// editors:
document.addEventListener('DOMContentLoaded', (e) => {
    // init the editors:
    editors.init();
    
    // you may auto register editors with the data-editor attribute:
    editors.register();
    
    // or you may create it manually:
    const editor = editors.create(document.querySelector('#editor'), {
        // config (optional):
        id: 'foo',
        toolbar: ['p', 'bold', 'headings']
    });
});

2. Replace default editor with custom editor

<link href="editor.css" rel="stylesheet" type="text/css">
<!--<script src="editor.js" type="module"></script>-->
<script src="custom-editor.js" type="module"></script>

Available Plugins

Basic Plugins

import editors from './core/editors.js';
import { basic, html, clear, links } from 'plugin/basic.js';

editors.plugins([basic, html, clear, links]);

Basic Plugin

The basic plugin provides the following toolbars:

["p", "h1", "h2", "h3", "h4", "h5", "h6", "bold", "italic", "underline", "strike", "ol", "ul", "quote", "pre", "code", "undo", "redo"]

KEY HTML Element Description
p <p> -
h1 <h1> -
h2 <h2> -
h3 <h3> -
h4 <h4> -
h5 <h5> -
h6 <h6> -
headings - Opens a toolbar with h1-h6.
bold <b> -
italic <i> -
underline <u> -
strike <s> -
ol <ol> -
ul <ul> -
quote <blockquote> -
pre <pre> -
code <code> -
undo - Undo last step
redo - Redo last step
| - Horizontal line separator
|| - Horizontal space separator
_ - Vertical line separator
__ - Vertical space separator
back - Back to last toolbar

Html Plugin

The html plugin provides the following toolbars:

["sourcecode"]

KEY HTML Element Description
sourcecode - To switch between source code and normal view.

Clear Plugin

The clear plugin provides the following toolbars:

["clear"]

KEY HTML Element Description
clear - To clear the editable area.

Links Plugin

The links plugin provides the following toolbars:

["link"]

KEY HTML Element Description
link <a> To insert a link.

Customize link classes

import events from './../events.js';

events.listen('toolbars.item', (item) => {
    if (item.key === 'link.class') {
        item.options = {
            "none": "",
            "default": "button",
            "default fit": "button fit"
        };
    }
});

Table Plugin

import editors from './core/editors.js';
import table from 'plugin/table.js';

editors.plugins([table]);

The table plugin provides the following toolbars:

["tables"]

KEY HTML Element Description
tables <table> To add tables.

Styles Plugin

The styles plugin adds/removes classe(s) to your selected text.

import editors from './core/editors.js';
import styles from 'plugin/styles.js';

// add your styles:
styles.add({
    key: "style.fonts",
    title: "Font styles",
    options: {
        "Default Font": "",
        "Primary Font": "font-primary",
        "Secondary Font": "font-secondary"
    },
    optionToClass: true
});
styles.add({
    key: "style.text.colors",
    title: "Text Colors",
    options: {
        "Default Text Color": "",
        "Text Color Success": "text-success"
    },
    optionToClass: true
});

// add plugin:
editors.plugins([styles]);

Toolbars:

The style key will be your toolbar key:

["style.fonts", "style.text.colors"]

Create Plugin

Check out the plugin/table.js or plugin/basic.js file to see how a plugin is created.

Events

Global Events

You may use the available events for plugin creation e.g.

Editors Events

Event Callback Parameters Description
editors.init (editors) Triggered when the editors.init() function has been called.
editors.registered (editors) Triggered after the editors.register() function has been called.
import events from 'core/events.js';

events.listen('editors.init', (editors) => {
    //
});

Editor Events

Event Callback Parameters Description
editor.blur (event, editor) Triggered by the blur event of the editor element (editable area).
editor.click (event, editor) Triggered by the click event of the editor element (editable area).
editor.created (editor) Triggered when the editor has been created.
editor.focus (event, editor) Triggered when the editor is focused.
editor.mousedown (event, editor) Triggered by the mousedown event of the editor element (editable area).
editor.mouseup (event, editor) Triggered by the mouesup event of the editor element (editable area).
editor.keydown (event, editor) Triggered by the keydown event of the editor element (editable area).
editor.keypress (event, editor) Triggered by the keypress event of the editor element (editable area).
editor.keyup (event, editor) Triggered by the keyup event of the editor element (editable area).

Example:

import events from 'core/events.js';

events.listen('editor.blur', (event, editor) => {
    //
});

Toolbars Events

Event Callback Parameters Description
toolbars.item (item) Triggered when a toolbars item is added.

Example:

import events from 'core/events.js';

events.listen('toolbars.item', (item) => {
    if (item.key === 'link.class') {
        item.options = {
            "none": "",
            "default": "button",
            "default fit": "button fit"
        };
    }
});

Toolbar Events

Event Callback Parameters Description
toolbar.build (data) Triggered when a toolbar is being build.
toolbar.opened (toolbar) Triggered after toolbar is opened.
toolbar.closed (toolbar) Triggered after toolbar is closed.

Example:

import events from 'core/events.js';

events.listen('toolbar.build', (data) => {
    // customize link toolbar
    if (toolbar.id === 'link') {
        data.items = [
            'back', 'link.visit', 'link.delete', 'link.insert', '_',
            'link.input', '_',
            'link.window', 'link.windowLabel', '_',
            //'link.class'
        ];
    }
});

Editor Events

Event Callback Parameters Description
blur (event, editor) Triggered by the blur event of the editor element (editable area).
click (event, editor) Triggered by the click event of the editor element (editable area).
created (editor) Triggered when the editor has been created.
focus (event, editor) Triggered when the editor is focused.
mousedown (event, editor) Triggered by the mousedown event of the editor element (editable area).
mouseup (event, editor) Triggered by the mouesup event of the editor element (editable area).
keydown (event, editor) Triggered by the keydown event of the editor element (editable area).
keypress (event, editor) Triggered by the keypress event of the editor element (editable area).
keyup (event, editor) Triggered by the keyup event of the editor element (editable area).

Example:

import editors from './core/editors.js';

document.addEventListener('DOMContentLoaded', (e) => {
    // create
    editors.create(document.querySelector('#editor'), {
        events: {
            click: (event, editor) => {
                //
            }
        }
    });
});

API

Editors API

import editors from './core/editors.js';

init/register

// init the editors:
editors.init();

// you may auto register editors with the data-editor attribute:
editors.register();

create

Creating a new editor.

const editor = editors.create(document.querySelector('#editor'));

// with config:
const editor = editors.create(document.querySelector('#editor'), {
    id: 'foo',
    toolbar: ['p', 'bold', 'headings'],
    events: {
        click: (event, editor) => {
            //
        }
    }
});

has / get / all

if (editors.has('ID')) {
    const editor = editors.get('ID');
}

const allEditors = editors.all();

plugin / plugins

editors.plugin({
    name: 'foo',
    init: () => {
        //
    },
    events: {
        //
    }
});

editors.plugins([{name: 'foo'}, {name: 'bar'}]);

Editor API

import editors from './core/editors.js';

const editor = editors.create(document.querySelector('#editor'));

code

Get the HTML code.

const htmlCode = editor.code();

config

Get config value with dot notation.

const value = editor.config('foo.bar', 'default');

Misc

const id = editor.id;
const el = editor.el; // editable area
const selection = editor.selection;
const toolbar = editor.toolbar;
const events = editor.events;
  • selection check out the Selection API for more information.
  • toolbar check out the Toolbar API for more information.
  • events check out the Events API for more information.

Events API

import events from './core/events.js';

listen / fire

// with array params:
events.listen('click', (foo, bar) => {});
events.fire('click', ['foo', 'bar']);

// or with object params:
events.listen('click', (obj) => {});
events.fire('click', {});

register

const listeners = {
    "eventName": () => {
        
    },
};

events.register(listeners);

Selection API

import editors from './core/editors.js';

const editor = editors.create(document.querySelector('#editor'));
const selection = editor.selection;

get

Get the selected data:

const sel = selection.get();

const text = sel.text;
const element = sel.element;
const tagName = sel.tagName;

getTagnames

Get the selected tag names:

const tagnames = selection.getTagnames();

Misc

// Saves the current selection with unwrapping a HTML <span> marker.
selection.save();

// Saves the current selection without unwrapping a HTML <span> marker.
selection.save(false);

// Set saved selection by element.
selection.setSaved(element, '');

// Get the saved selection.
const sel = selection.getSaved();

// Clears the selection. Removes the marker if any.
selection.clear();

// Replaces the selection marker with the node.
selection.replace(node);

// Replaces the sel with the node.
selection.insertReplace(sel, node);

// Inserts the node after the sel.
selection.insertAfter(sel, node);

Toolbars API

import toolbars from './core/toolbars.js';

addItem

Add an item to be later used to build a toolbar.

Example Button:

toolbars.addItem({
    // required:
    key: "Foo", // a unique key
    text: "Foo",
    command: ["formatblock", "<h2>"],
    undo: ["formatblock", "<p>"], // or null
    tagname: "h2", // used for setting button active
});

Example Input:

toolbars.addItem({
    // required:
    key: "email", // a unique key
    // optional:
    element: "input", // HTML element, default "button"
    type: "email",
    id: "foo",
    for: "id",
    name: "foo",
    title: "Type your email",
    placeholder: "Type...",
    classes: ["foo", "bar"],
    // options: {"name": "value"}, // for selection element.
    attributes: {"name": "value"}, // any additonal HTML element attributes
    build_default: false, // if to build on default
    click: (event, item, toolbar, editor) => {
        // do something on click
    },
    keyup: (event, item, toolbar, editor) => {
        // do something on keyup
    }
});

Misc

const toolbar = toolbars.create({id: 'ID'});

if (toolbars.has('ID')) {
    const toolbar = toolbars.get('ID');
}

// opens the toolbar if exists:
toolbars.open('ID');

// closes all toolbars:
toolbars.close();

// closes all toolbars except those specified:
toolbars.close(["links"]);

Toolbar API

import toolbars from './core/toolbars.js';

build

Build toolbar with the specified items.

const toolbar = toolbars.create({id: 'ID'});

toolbar.build([
    'back', 'table.delete', '_',
    'table.row.below', 'table.row.above', 'table.row.delete', '_',
    'table.col.left', 'table.col.right', 'table.col.delete'
]);

open / close

const toolbar = toolbars.get({id: 'ID'});

toolbar.open();
toolbar.close();

Items

const toolbar = toolbars.get({id: 'ID'});

if (toolbar.has('key')) {
    const item = toolbar.get('key');
}

toolbar.disable('key');
toolbar.disable('key', false); // without hiding
toolbar.disableExcept(['key']);
toolbar.disableExcept(['key'], false); // without hiding
toolbar.enable('key');

toolbar.setActive(['key']);
toolbar.setActive(['a', 'p'], 'tagname'); // based on item tagname
toolbar.setActive(['key', 'key', false]); // without setting others inactive
toolbar.setActiveItem('key'); // set only active.

toolbar.positioning(); // position to selection;
toolbar.positioning(event); // position to event.pageX and event.pageY
toolbar.positioning(null, 100, 200); // position to x: 100, y: 200
toolbar.positioning(null, null, null, true); // position to last position

Translator API

import translator from './core/translator.js';
translator.locale('de-CH'); // current locale
translator.localeFallbacks({"de-CH": "en"});

// add translation for the specified locale.
translator.add('de-CH', {
    "Table": "Tabelle"
});

const translated = translator.trans('Table');

Credits