tobento/js-editor

Simple JavaScript WYSIWYG HTML Editor with inlined toolbar only.

1.0.1 2024-11-20 16:56 UTC

This package is auto-updated.

Last update: 2024-11-20 16:57:03 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"]

Html Plugin

The html plugin provides the following toolbars:

["sourcecode"]

Clear Plugin

The clear plugin provides the following toolbars:

["clear"]

Links Plugin

The links plugin provides the following toolbars:

["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"]

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

import events from 'core/events.js';

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

Editor Events

Example:

import events from 'core/events.js';

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

Toolbars Events

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

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

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