editor.js/docs/tools.md
Murod Khaydarov a6194c19fc [Feature/issue-704]: Conversion toolbar (#787)
* [issue-748]: update Readme (#773)

* Do not start multi-block selection on UI elements (#662)

* Do not start multi-block selection on UI elements

* Do not prevent mousedown event on inline toolbar actions

* Remove log

* Add comment

* Add link to issue

closes #646

* Fix loss of pointer (#666)

* Fix loss of pointer when click is outside of the editor but selection is inside

* Remove log

* Update shortcuts module (#685)

* Fixed possible grammatical typo (#681)

Thanks

* Update shortcuts module

* update changelog

* update

* Remove margin top for inline-link icon (#690)

* Remove margin top for inline-link icon

resolves #674

* Update CHANGELOG.md

* Remove unused style

* Pull fresh tools

* Remove changelog contents from readme (#700)

* #665 API to open and close inline-toolbar (#711)

* API to open and close inline-toolbar

* Fixed documentation

* renamed inline -> inline-toolbar

* removed dist

* reset editor.js

* added editor.js bundle

* Fixed build error

* Null checks on toolbar/inline@open

* updated bundle

* Improve some comments

* Updatd api.md CHANGELOG.md

* Change feature to new instead of improvement

* Allow holderId work with ref on dom element (#710)

* done

* update types

* attempt to simplify code

* remove useless helper

* revert holderId logic and add holder property

* Apply suggestions from code review

Co-Authored-By: dimensi <eddimensi@gmail.com>

* update holder type on string | HTMLElement

* fix typo

* add deprecated notice and fix typos

* fix wrong compare

* fix comments

* swap console.log on _.log

* update types for editor config

* update examples

* update docs

* update build

* Activating Open Collective (#736)

Hi, I'm making updates for Open Collective. Either you or a supporter signed this repo up for Open Collective. This pull request adds backers and sponsors from your Open Collective https://opencollective.com/editorjs❤️

It adds two badges at the top to show the latest number of backers and sponsors. It also adds placeholders so that the avatar/logo of new backers/sponsors can automatically be shown without having to update your README.md. [more info](https://github.com/opencollective/opencollective/wiki/Github-banner). See how it looks on this [repo](https://github.com/apex/apex#backers).

You can also add a postinstall script to let people know after npm|yarn install that you are welcoming donations (optional). [More info](https://github.com/OpenCollective/opencollective-cli)
You can also add a "Donate" button to your website and automatically show your backers and sponsors there with our widgets. Have a look here: https://opencollective.com/widgets

P.S: As with any pull request, feel free to comment or suggest changes. The only thing "required" are the placeholders on the README because we believe it's important to acknowledge the people in your community that are contributing (financially or with code!).

Thank you for your great contribution to the open source community. You are awesome! 🙌
And welcome to the open collective community! 😊

Come chat with us in the #opensource channel on https://slack.opencollective.com - great place to ask questions and share best practices with other open source sustainers!

* Do not install editor.js as dev-dependency (#731)

Resolves #730

* Move codex-notifier to dependencies for typescript declarations (#728)

* Close inline toolbar after creating new link by pressing ENTER (#722)

* Method to clear current selection and close inline toolbar

* clearSelection with optional collapsed range

* refactored selection.ts

* removed experimental function

* Update src/components/selection.ts

Co-Authored-By: tanmayv <12tanmayvijay@gmail.com>

* update version, add changelog

* Link Logo Image to homepage (#738)

* Update README.md (#744)

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

Co-Authored-By: neSpecc <specc.dev@gmail.com>

* Config minHeight option that allows to customize bottom zone (#745)

* issue-739: allow Block's editable element selection (#747)

* issue-739: allow Block's input selection

* little improvements

* update Changelog and cache inputs

* small fix

* delete map file

* fix inputs.count condition

* Fix typo in example paragraph (#749)

* Fix typo

* Update example-dev.html

* minor release

* update link to the minified script

* bump version

* Prefer user settings during tools merge (#774)

* Prefer user settings during tools merge

* issue-705: Leaf BlockSettings and InlineTools via keyboard (#723)

* Do not start multi-block selection on UI elements (#662)

* Do not start multi-block selection on UI elements

* Do not prevent mousedown event on inline toolbar actions

* Remove log

* Add comment

* Add link to issue

closes #646

* Fix loss of pointer (#666)

* Fix loss of pointer when click is outside of the editor but selection is inside

* Remove log

* Update shortcuts module (#685)

* Fixed possible grammatical typo (#681)

Thanks

* Update shortcuts module

* update changelog

* update

* Remove margin top for inline-link icon (#690)

* Remove margin top for inline-link icon

resolves #674

* Update CHANGELOG.md

* Remove unused style

* Pull fresh tools

* Remove changelog contents from readme (#700)

* #665 API to open and close inline-toolbar (#711)

* API to open and close inline-toolbar

* Fixed documentation

* renamed inline -> inline-toolbar

* removed dist

* reset editor.js

* added editor.js bundle

* Fixed build error

* Null checks on toolbar/inline@open

* updated bundle

* Improve some comments

* Updatd api.md CHANGELOG.md

* Change feature to new instead of improvement

* leaf buttons: initial

* leaf inline toolbar buttons

* Allow holderId work with ref on dom element (#710)

* done

* update types

* attempt to simplify code

* remove useless helper

* revert holderId logic and add holder property

* Apply suggestions from code review

Co-Authored-By: dimensi <eddimensi@gmail.com>

* update holder type on string | HTMLElement

* fix typo

* add deprecated notice and fix typos

* fix wrong compare

* fix comments

* swap console.log on _.log

* update types for editor config

* update examples

* update docs

* update build

* leaf inline tools and drop index after click

* leaf toolbox and clear active button after activation

* debugging blockSettings

* Activating Open Collective (#736)

Hi, I'm making updates for Open Collective. Either you or a supporter signed this repo up for Open Collective. This pull request adds backers and sponsors from your Open Collective https://opencollective.com/editorjs❤️

It adds two badges at the top to show the latest number of backers and sponsors. It also adds placeholders so that the avatar/logo of new backers/sponsors can automatically be shown without having to update your README.md. [more info](https://github.com/opencollective/opencollective/wiki/Github-banner). See how it looks on this [repo](https://github.com/apex/apex#backers).

You can also add a postinstall script to let people know after npm|yarn install that you are welcoming donations (optional). [More info](https://github.com/OpenCollective/opencollective-cli)
You can also add a "Donate" button to your website and automatically show your backers and sponsors there with our widgets. Have a look here: https://opencollective.com/widgets

P.S: As with any pull request, feel free to comment or suggest changes. The only thing "required" are the placeholders on the README because we believe it's important to acknowledge the people in your community that are contributing (financially or with code!).

Thank you for your great contribution to the open source community. You are awesome! 🙌
And welcome to the open collective community! 😊

Come chat with us in the #opensource channel on https://slack.opencollective.com - great place to ask questions and share best practices with other open source sustainers!

* Do not install editor.js as dev-dependency (#731)

Resolves #730

* Move codex-notifier to dependencies for typescript declarations (#728)

* Close inline toolbar after creating new link by pressing ENTER (#722)

* Method to clear current selection and close inline toolbar

* clearSelection with optional collapsed range

* refactored selection.ts

* removed experimental function

* Update src/components/selection.ts

Co-Authored-By: tanmayv <12tanmayvijay@gmail.com>

* update version, add changelog

* Link Logo Image to homepage (#738)

* Update README.md (#744)

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

Co-Authored-By: neSpecc <specc.dev@gmail.com>

* Config minHeight option that allows to customize bottom zone (#745)

* issue-739: allow Block's editable element selection (#747)

* issue-739: allow Block's input selection

* little improvements

* update Changelog and cache inputs

* small fix

* delete map file

* fix inputs.count condition

* Fix typo in example paragraph (#749)

* Fix typo

* Update example-dev.html

* minor release

* done

* requested changes

* production build

* update package.json

* some improvements

* ready for testing

* update

* ready

* requested changes

* updates

* use setToBlock instead of focus

* active -> focused

* update

* refactor types

* fix inline tools flipping

* inhancements

* rm check for focus at the handleShowingEvent

* fix IT closing after second enter

* add animation to settings buttons

* Click animation

* Add changelog

* do not patch version

* conversion toolbar

* positioning and flipping initial

* conversion toolbar is ready

* save changes

* update

* bump version

* save changes

* ready

* open conversion toolbar via keyup

* save changes

* ready to be reviewed

* restore some useless changes

* several improvements

* Update src/components/modules/toolbar/conversion.ts

Co-Authored-By: Peter Savchenko <specc.dev@gmail.com>

* Update src/components/modules/toolbar/conversion.ts

Co-Authored-By: Peter Savchenko <specc.dev@gmail.com>

* Update src/components/modules/toolbar/conversion.ts

Co-Authored-By: Peter Savchenko <specc.dev@gmail.com>

* Update src/components/modules/toolbar/conversion.ts

Co-Authored-By: Peter Savchenko <specc.dev@gmail.com>

* Update src/components/modules/toolbar/conversion.ts

Co-Authored-By: Peter Savchenko <specc.dev@gmail.com>

* Update src/components/modules/toolbar/conversion.ts

Co-Authored-By: Peter Savchenko <specc.dev@gmail.com>

* ready

* Update src/components/modules/toolbar/conversion.ts

Co-Authored-By: Peter Savchenko <specc.dev@gmail.com>

* requested changes

* update

* update comment

* define types

* simplify keydowns

* Update blockEvents.ts

* Don't use handleShowingEvent to close Inline/Conversion Toolbars

* battle with events

* bundle

* bundle

* rm console.trace

* bundle

* small improvements

* improve types description

* add docs, improve types

* improve anchor links

* fix move

* fix toolbar movement

* conversion toolbar fix movement

* improve margins

* bundle

* add changelog

* rm commented code

* tools goes to master
2019-06-29 23:41:18 +03:00

13 KiB
Raw Blame History

Editor.js Tools

Editor.js is a block-oriented editor. It means that entry composed with the list of Blocks of different types: Texts, Headers, Images, Quotes etc.

Tool — is a class that provide custom Block type. All Tools represented by Plugins.

Each Tool should have an installation guide.

Tool class structure

constructor()

Each Tool's instance called with an params object.

Param Type Description
api IAPI Editor.js's API methods
config object Special configuration params passed in «config»
data object Data to be rendered in this Tool

Example

constructor({data, config, api}) {
  this.data = data;
  this.api = api;
  this.config = config;
  // ...
}

render()

Method that returns Tool's element {HTMLElement} that will be placed into Editor.

save()

Process Tool's element created by render() function in DOM and return Block's data.

validate(data: BlockToolData): boolean|Promise<boolean> optional

Allows to check correctness of Tool's data. If data didn't pass the validation it won't be saved. Receives Tool's data as input param and returns boolean result of validation.

merge() optional

Method that specifies how to merge two Blocks of the same type, for example on Backspace keypress. Method does accept data object in same format as the Render and it should provide logic how to combine new data with the currently stored value.

Internal Tool Settings

Options that Tool can specify. All settings should be passed as static properties of Tool's class.

Name Type Default Value Description
toolbox Object undefined Pass here icon and title to display this Tool in the Editor's Toolbox
icon - HTML string with icon for Toolbox
title - optional title to display in Toolbox
enableLineBreaks Boolean false With this option, Editor.js won't handle Enter keydowns. Can be helpful for Tools like <code> where line breaks should be handled by default behaviour.
isInline Boolean false Describes Tool as a Tool for the Inline Toolbar
sanitize Object undefined Config for automatic sanitizing of saved data. See Sanitize section.
conversionConfig Object undefined Config allows Tool to specify how it can be converted into/from another Tool. See Conversion config section.

User configuration

All Tools can be configured by users. You can set up some of available settings along with Tool's class to the tools property of Editor Config.

var editor = new EditorJS({
  holderId : 'editorjs',
  tools: {
    text: {
      class: Text,
      inlineToolbar : true,
      // other settings..
    },
    header: Header
  },
  initialBlock : 'text',
});

There are few options available by Editor.js.

Name Type Default Value Description
inlineToolbar Boolean/Array false Pass true to enable the Inline Toolbar with all Tools, or pass an array with specified Tools list
config Object null User's configuration for Plugin.

Paste handling

Editor.js handles paste on Blocks and provides API for Tools to process the pasted data.

When user pastes content into Editor, pasted content will be splitted into blocks.

  1. If plain text will be pasted, it will be splitted by new line characters
  2. If HTML string will be pasted, it will be splitted by block tags

Also Editor API allows you to define your own pasting scenario. You can either:

  1. Specify HTML tags, that can be represented by your Tool. For example, Image Tool can handle <img> tags. If tags you specified will be found on content pasting, your Tool will be rendered.
  2. Specify RegExp for pasted strings. If pattern has been matched, your Tool will be rendered.
  3. Specify MIME type or extensions of files that can be handled by your Tool on pasting by drag-n-drop or from clipboard.

For each scenario, you should do 2 next things:

  1. Define static getter pasteConfig in Tool class. Specify handled patterns there.
  2. Define public method onPaste that will handle PasteEvent to process pasted data.

HTML tags handling

To handle pasted HTML elements object returned from pasteConfig getter should contain following field:

Name Type Description
tags String[] Optional. Should contain all tag names you want to be extracted from pasted data and processed by your onPaste method

For correct work you MUST provide onPaste handler at least for initialBlock Tool.

Example

Header Tool can handle H1-H6 tags using paste handling API

static get pasteConfig() {
  return {
    tags: ['H1', 'H2', 'H3', 'H4', 'H5', 'H6'],
  }
}

Same tag can be handled by one (first specified) Tool only.

RegExp patterns handling

Your Tool can analyze text by RegExp patterns to substitute pasted string with data you want. Object returned from pasteConfig getter should contain following field to use patterns:

Name Type Description
patterns Object Optional. patterns object contains RegExp patterns with their names as object's keys

Note Editor will check pattern's full match, so don't forget to handle all available chars in there.

Pattern will be processed only if paste was on initialBlock Tool and pasted string length is less than 450 characters.

Example

You can handle YouTube links and insert embeded video instead:

static get pasteConfig() {
  return {
    patterns: {
      youtube: /http(?:s?):\/\/(?:www\.)?youtu(?:be\.com\/watch\?v=|\.be\/)([\w\-\_]*)(&(amp;)?[\w\?=]*)?/
    },
  }
}

Files pasting

Your Tool can handle files pasted or dropped into the Editor.

To handle file you should provide files property in your pasteConfig configuration object.

files property is an object with the following fields:

Name Type Description
extensions string[] Optional Array of extensions your Tool can handle
mimeTypes sring[] Optional Array of MIME types your Tool can handle

Example

static get pasteConfig() {
  return {
    files: {
      mimeTypes: ['image/png'],
      extensions: ['json']
    }
  }
}

Pasted data handling

If you registered some paste substitutions in pasteConfig property, you should provide onPaste callback in your Tool class. onPaste should be public non-static method. It accepts custom PasteEvent object as argument.

PasteEvent is an alias for three types of events - tag, pattern and file. You can get the type from PasteEvent object's type property. Each of these events provide detail property with info about pasted content.

Type Detail
tag data - pasted HTML element
pattern key - matched pattern key you specified in pasteConfig object
data - pasted string
file file - pasted file

Example

onPaste (event) {
  switch (event.type) {
    case 'tag':
      const element = event.detail.data;

      this.handleHTMLPaste(element);
      break;

    case 'pattern':
      const text = event.detail.data;
      const key = event.detail.key;

      this.handlePatternPaste(key, text);
      break;

    case 'file':
      const file = event.detail.file;

      this.handleFilePaste(file);
      break;
  }
}

Sanitize

Editor.js provides API to clean taint strings. Use it manually at the save() method or or pass sanitizer config to do it automatically.

Sanitizer Configuration

The example of sanitizer configuration

let sanitizerConfig = {
  b: true, // leave <b>
  p: true, // leave <p>
}

Keys of config object is tags and the values is a rules.

Rule

Rule can be boolean, object or function. Object is a dictionary of rules for tag's attributes.

You can set true, to allow tag with all attributes or false|{} to remove all attributes, but leave tag.

Also you can pass special attributes that you want to leave.

a: {
  href: true
}

If you want to use a custom handler, use should specify a function that returns a rule.

b: function(el) {
  return !el.textContent.includes('bad text');
}

or

a: function(el) {
  let anchorHref = el.getAttribute('href');
  if (anchorHref && anchorHref.substring(0, 4) === 'http') {
    return {
      href: true,
      target: '_blank'
    }
  } else {
    return {
      href: true
    }
  }
}

Manual sanitize

Call API method sanitizer.clean() at the save method for each field in returned data.

save() {
  return {
    text: this.api.sanitizer.clean(taintString, sanitizerConfig)
  }
}

Automatic sanitize

If you pass the sanitizer config as static getter, Editor.js will automatically sanitize your saved data.

Note that if your Tool is allowed to use the Inline Toolbar, we will get sanitizing rules for each Inline Tool and merge with your passed config.

You can define rules for each field

static get sanitize() {
  return {
    text: {},
    items: {
      b: true, // leave <b>
      a: false, // remove <a>
    }
  }
}

Don't forget to set the rule for each embedded subitems otherwise they will not be sanitized.

if you want to sanitize everything and get data without any tags, use {} or just ignore field in case if you want to get pure HTML

static get sanitize() {
  return {
    text: {},
    items: {}, // this rules will be used for all properties of this object
    // or
    items: {
      // other objects here won't be sanitized
      subitems: {
        // leave <a> and <b> in subitems
        a: true,
        b: true,
      }
    }
  }
}

Conversion config

Editor.js has a Conversion Toolbar that allows user to convert one Block to another.

  1. You can add ability to your Tool to be converted. Specify «export» property of conversionConfig.
  2. You can add ability to convert other Tools to your Tool. Specify «import» property of conversionConfig.

Conversion Toolbar will be shown only near Blocks that specified an «export» rule, when user selected almost all block's content. This Toolbar will contain only Tools that specified an «import» rule.

Example:

class Header {
  constructor(){
    this.data = {
       text: '',
       level: 2
    }
  }

  /**
   * Rules specified how our Tool can be converted to/from other Tool.
   */
  static get conversionConfig() {
    return {
      export: 'text', // this property of tool data will be used as string to pass to other tool
      import: 'text' // to this property imported string will be passed
    };
  }
}

Your Tool -> other Tool

The «export» field specifies how to represent your Tool's data as a string to pass it to other tool.

It can be a String or a Function.

String means a key of your Tool data object that should be used as string to export.

Function is a method that accepts your Tool data and compose a string to export from it. See example below:

class ListTool {
  constructor(){
    this.data = {
      items: [
        'Fisrt item',
        'Second item',
        'Third item'
      ],
      type: 'ordered'
    }
  }
  
  static get conversionConfig() {
    return {
      export: (data) => {
        return data.items.join('.'); // in this example, all list items will be concatenated to an export string
      },
      // ... import rule
    };
  }
}

Other Tool -> your Tool

The «import» rule specifies how to create your Tool's data object from the string created by original block.

It can be a String or a Function.

String means the key in tool data that will be filled by an exported string. For example, import: 'text' means that constructor of your block will accept a data object with text property filled with string composed by original block.

Function allows you to specify own logic, how a string should be converted to your tool data. For example:

class ListTool {
  constructor(data){
    this.data = data || {
      items: [],
      type: 'unordered'
    }
  }
  
  static get conversionConfig() {
    return {
      // ... export rule 
      
      /**
       * In this example, List Tool creates items by splitting original text by a dot symbol. 
       */
      import: (string) => {
        const items = string.split('.');

        return {
          items: items.filter( (text) => text.trim() !== ''),
          type: 'unordered'
        };
      }
    };
  }
}