├── .nvmrc
├── .eslintignore
├── .vscode
└── settings.json
├── .storybook
├── addons.js
├── config.js
├── webpack.config.js
└── tsconfig.json
├── src
├── index.tsx
├── __stories__
│ ├── index.stories.tsx
│ ├── config.ts
│ └── data.ts
├── inline-tools.tsx
└── tool.tsx
├── index.d.ts
├── @types
└── vendor.d.ts
├── .prettierrc
├── .github
├── workflows
│ └── size-limit.yml
├── CODEOWNERS
├── PULL_REQUEST_TEMPLATE.md
├── ISSUE_TEMPLATE
│ ├── question.md
│ ├── feature_request.md
│ └── bug_report.md
└── style.yml
├── .gitignore
├── .all-contributorsrc
├── LICENCE
├── .eslintrc.js
├── webpack.config.js
├── tsconfig.json
├── CHANGELOG.md
├── README.md
└── package.json
/.nvmrc:
--------------------------------------------------------------------------------
1 | v12.14.1
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | dist
2 | es
3 | esm
4 | lib
5 | tmp
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "typescript.tsdk": "node_modules/typescript/lib"
3 | }
--------------------------------------------------------------------------------
/.storybook/addons.js:
--------------------------------------------------------------------------------
1 | import '@storybook/addon-actions/register'
2 | import 'storybook-readme/register'
3 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | export { default } from './tool'
2 | export {
3 | ItalicInlineTool,
4 | StrongInlineTool,
5 | UnderlineInlineTool,
6 | } from './inline-tools'
7 |
--------------------------------------------------------------------------------
/index.d.ts:
--------------------------------------------------------------------------------
1 | // required for VS Code
2 | // inspired by https://github.com/storybookjs/storybook/issues/2883#issuecomment-409839786
3 | declare module '*.md' {
4 | const value: string
5 | export default value
6 | }
7 |
--------------------------------------------------------------------------------
/@types/vendor.d.ts:
--------------------------------------------------------------------------------
1 | declare module '@editorjs/*'
2 |
3 | // required for storybook
4 | // inspired by https://github.com/storybookjs/storybook/issues/2883#issuecomment-409839786
5 | declare module '*.md' {
6 | const value: string
7 | export default value
8 | }
9 |
--------------------------------------------------------------------------------
/.storybook/config.js:
--------------------------------------------------------------------------------
1 | import { addDecorator, configure } from '@storybook/react'
2 | import { addReadme } from 'storybook-readme'
3 |
4 | addDecorator(addReadme)
5 |
6 | // automatically import all files ending in *.stories.tsx
7 | configure(require.context('../src', true, /\.stories\.tsx?$/), module)
8 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "arrowParens": "avoid",
3 | "printWidth": 80,
4 | "semi": false,
5 | "singleQuote": true,
6 | "trailingComma": "all",
7 | "overrides": [
8 | {
9 | "files": "package*.json",
10 | "options": {
11 | "printWidth": 1000
12 | }
13 | }
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/.github/workflows/size-limit.yml:
--------------------------------------------------------------------------------
1 | name: 'size'
2 | on:
3 | pull_request:
4 | branches:
5 | - master
6 | jobs:
7 | size:
8 | runs-on: ubuntu-latest
9 | env:
10 | CI_JOB_NUMBER: 1
11 | steps:
12 | - uses: actions/checkout@v1
13 | - uses: andresz1/size-limit-action@v1.4.0
14 | with:
15 | github_token: ${{ secrets.GITHUB_TOKEN }}
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # OS, IDE, ...
2 | .DS_Store
3 |
4 | # Development
5 | .awcache
6 | node_modules
7 | tmp
8 | *.log
9 |
10 | # build
11 | .awcache
12 | build
13 | dist
14 | es
15 | esm
16 | lib
17 | *.tgz
18 | storybook-static
19 | *.d.ts.map
20 |
21 | # Optional npm cache directory
22 | .npm
23 |
24 | # Optional eslint cache
25 | .eslintcache
26 |
27 | # Yarn Integrity file
28 | .yarn-integrity
29 |
--------------------------------------------------------------------------------
/.storybook/webpack.config.js:
--------------------------------------------------------------------------------
1 | // https://storybook.js.org/docs/configurations/typescript-config
2 | // alternative: https://github.com/storybookjs/presets/tree/master/packages/preset-typescript
3 | module.exports = ({ config }) => {
4 | config.module.rules.push({
5 | test: /\.(ts|tsx)$/,
6 | use: [
7 | {
8 | loader: require.resolve('ts-loader'),
9 | },
10 | {
11 | loader: require.resolve('react-docgen-typescript-loader'),
12 | },
13 | ],
14 | })
15 | config.resolve.extensions.push('.ts', '.tsx')
16 | return config
17 | }
18 |
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | # Code ownership for pull request reviews
2 |
3 | # Docs
4 | # https://github.com/blog/2392-introducing-code-owners
5 | # https://help.github.com/articles/about-codeowners/
6 |
7 | # These owners will be the default owners for everything in the repo.
8 | * @natterstefan
9 |
10 | # Order is important. The last matching pattern has the most precedence.
11 | # So if a pull request only touches javascript files, only these owners
12 | # will be requested to review.
13 |
14 | # For example:
15 | # *.js @octocat @github/js
16 |
17 | # You can also use email addresses if you prefer.
18 | # docs/* docs@example.com
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
5 |
6 | # Issue
7 |
8 | ## What I did
9 |
10 |
14 |
15 | ## What it adds, solves and/or improves
16 |
17 |
20 |
21 | ## How to test
22 |
23 |
31 |
--------------------------------------------------------------------------------
/.all-contributorsrc:
--------------------------------------------------------------------------------
1 | {
2 | "projectName": "editorjs-inline-tool",
3 | "projectOwner": "natterstefan",
4 | "repoType": "github",
5 | "repoHost": "https://github.com",
6 | "files": [
7 | "README.md"
8 | ],
9 | "imageSize": 100,
10 | "commit": false,
11 | "commitConvention": "eslint",
12 | "contributors": [
13 | {
14 | "login": "natterstefan",
15 | "name": "Stefan Natter",
16 | "avatar_url": "https://avatars2.githubusercontent.com/u/1043668?v=4",
17 | "profile": "http://twitter.com/natterstefan",
18 | "contributions": [
19 | "code",
20 | "doc",
21 | "example"
22 | ]
23 | }
24 | ],
25 | "contributorsPerLine": 7
26 | }
27 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/question.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Question 🤔
3 | about: Usage question or discussion.
4 | ---
5 |
6 |
13 |
14 | # Question
15 |
16 | ## Relevant information
17 |
18 | Provide as much useful information as you can.
19 |
20 | ### Your Environment
21 |
22 | - Browser: **\_**
23 | - Browser version: **\_**
24 | - OS: **\_**
25 | - Node: **x.y.z**
26 | - Package Version: **x.y.z**
27 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature Request 💡
3 | about: Suggest a new idea.
4 | ---
5 |
6 |
13 |
14 | # Feature Request
15 |
16 | Brief explanation of the feature you have in mind.
17 |
18 | ## Basic example
19 |
20 | If you want you can include a basic code example. Omit this section if it's
21 | not applicable.
22 |
23 | ## Motivation
24 |
25 | Why are you suggesting this? What is the use case for it and what is the
26 | expected outcome?
27 |
--------------------------------------------------------------------------------
/.storybook/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "build/lib",
4 | "module": "commonjs",
5 | "target": "es5",
6 | "lib": ["es5", "es6", "es7", "es2017", "dom"],
7 | "sourceMap": true,
8 | "allowJs": true,
9 | "jsx": "react",
10 | "moduleResolution": "node",
11 | "rootDirs": ["src"],
12 | "baseUrl": "src",
13 | "forceConsistentCasingInFileNames": true,
14 | "noImplicitReturns": true,
15 | "noImplicitThis": true,
16 | "noImplicitAny": true,
17 | "strictNullChecks": true,
18 | "suppressImplicitAnyIndexErrors": true,
19 | "noUnusedLocals": true,
20 | "allowSyntheticDefaultImports": true,
21 | "experimentalDecorators": true,
22 | "emitDecoratorMetadata": true
23 | },
24 | "include": ["src/**/*"],
25 | "exclude": ["node_modules", "build", "scripts"]
26 | }
27 |
--------------------------------------------------------------------------------
/src/__stories__/index.stories.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable class-methods-use-this */
2 | /* eslint-disable react/prop-types */
3 | import React from 'react'
4 | import { storiesOf } from '@storybook/react'
5 | import { action } from '@storybook/addon-actions'
6 | import EditorJs from '@natterstefan/react-editor-js'
7 |
8 | import Readme from '../../README.md'
9 |
10 | import data from './data'
11 | import { TOOLS } from './config'
12 |
13 | storiesOf('EditorJS GenericInlineTool', module)
14 | .add('readme', () =>
, {
15 | readme: {
16 | content: Readme,
17 | },
18 | })
19 | .add('default', () => {
20 | let instance: EditorJS.default = null
21 |
22 | const onChange = () => {
23 | action('EditorJs onChange')(instance)
24 | }
25 |
26 | return (
27 | {
32 | instance = editorInstance
33 | action('EditorJs editorInstance')(editorInstance)
34 | }}
35 | />
36 | )
37 | })
38 |
--------------------------------------------------------------------------------
/LICENCE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Stefan Natter
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug Report 🐞
3 | about: Something isn't working as expected? Here is the right place to report.
4 | ---
5 |
6 |
13 |
14 | # Bug Report
15 |
16 | ## Relevant information
17 |
18 |
19 |
20 | ### Your Environment
21 |
22 | - Browser: **\_**
23 | - Browser version: **\_**
24 | - OS: **\_**
25 | - Node: **x.y.z**
26 | - Package Version: **x.y.z**
27 |
28 | #### Steps to reproduce
29 |
30 | 1. Step 1
31 | 2. Step 2
32 | 3. Step 3
33 |
34 | #### Observed Results
35 |
36 | - What happened? This could be a description, log output, etc.
37 |
38 | #### Expected Results
39 |
40 | - What did you expect to happen?
41 |
42 | #### Relevant Code (optional)
43 |
44 | ```js
45 | // TODO(you): code here to reproduce the problem
46 | ```
47 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: [
3 | 'eslint-config-ns',
4 | 'plugin:@typescript-eslint/recommended',
5 | 'prettier/@typescript-eslint',
6 | ],
7 | parser: '@typescript-eslint/parser',
8 | rules: {
9 | 'class-methods-use-this': 0,
10 | 'import/extensions': 0,
11 | 'sort-keys': 0,
12 | '@typescript-eslint/interface-name-prefix': [
13 | 2,
14 | {
15 | prefixWithI: 'always',
16 | allowUnderscorePrefix: true,
17 | },
18 | ],
19 | '@typescript-eslint/no-use-before-define': 0,
20 | },
21 | overrides: [
22 | {
23 | files: [
24 | '.storybook',
25 | '**/__stories__/**/*.ts',
26 | '**/__stories__/**/*.tsx',
27 | ],
28 | rules: {
29 | 'import/no-extraneous-dependencies': 0,
30 | '@typescript-eslint/explicit-function-return-type': 0,
31 | '@typescript-eslint/no-explicit-any': 0,
32 | },
33 | },
34 | {
35 | files: ['@types/**/*.ts'],
36 | rules: {
37 | '@typescript-eslint/no-explicit-any': 0,
38 | },
39 | },
40 | ],
41 | settings: {
42 | 'import/resolver': {
43 | node: {
44 | extensions: ['.js', '.jsx', '.ts', '.tsx'],
45 | },
46 | },
47 | },
48 | }
49 |
--------------------------------------------------------------------------------
/src/inline-tools.tsx:
--------------------------------------------------------------------------------
1 | import createGenericInlineTool from './tool'
2 |
3 | export const ItalicInlineTool = createGenericInlineTool({
4 | sanitize: {
5 | em: {},
6 | },
7 | shortcut: 'CMD+I',
8 | tagName: 'EM',
9 | toolboxIcon:
10 | // icon editor-js uses
11 | '',
12 | })
13 |
14 | export const StrongInlineTool = createGenericInlineTool({
15 | sanitize: {
16 | strong: {},
17 | },
18 | shortcut: 'CMD+B',
19 | tagName: 'STRONG',
20 | toolboxIcon:
21 | '',
22 | })
23 |
24 | export const UnderlineInlineTool = createGenericInlineTool({
25 | sanitize: {
26 | u: {},
27 | },
28 | tagName: 'U',
29 | // icon taken from https://github.com/mui-org/material-ui/blob/4fba0dafd30f608937efa32883d151ba01fc9681/packages/material-ui-icons/src/FormatUnderlined.js
30 | toolboxIcon:
31 | '',
32 | })
33 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-var-requires */
2 | const { resolve } = require('path')
3 |
4 | const TerserPlugin = require('terser-webpack-plugin')
5 |
6 | module.exports = {
7 | entry: resolve(__dirname, 'src/index.tsx'),
8 | output: {
9 | filename: 'editorjs-inline-tool.js',
10 | library: 'EditorjsInlineTool',
11 | libraryTarget: 'umd',
12 | path: resolve(__dirname, './dist'),
13 | },
14 | devtool: 'source-map',
15 | mode: 'production',
16 | resolve: {
17 | extensions: ['.ts', '.tsx', '.js', '.jsx'],
18 | },
19 | module: {
20 | rules: [
21 | {
22 | test: /\.(ts)x?$/,
23 | exclude: /node_modules/,
24 | loader: 'ts-loader',
25 | options: {
26 | transpileOnly: true,
27 | },
28 | },
29 | ],
30 | },
31 | optimization: {
32 | minimize: true,
33 | minimizer: [
34 | new TerserPlugin({
35 | terserOptions: {
36 | cache: true,
37 | output: {
38 | comments: false,
39 | },
40 | },
41 | // https://github.com/webpack-contrib/terser-webpack-plugin#extractcomments
42 | extractComments: true,
43 | // https://github.com/webpack-contrib/terser-webpack-plugin#sourcemap
44 | sourceMap: true,
45 | }),
46 | ],
47 | },
48 | }
49 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | /* inspired by https://github.com/marmelab/react-admin/blob/HEAD@%7B2019-10-20T17:02:44Z%7D/tsconfig.json */
3 | "compilerOptions": {
4 | /* Basic Options */
5 | "target": "es2015" /* Specify ECMAScript target version: 'es3' (default), 'es5', 'es2015', 'es2016', 'es2017','es2018' or 'esnext'. */,
6 | "lib": ["DOM"],
7 | "module": "es2015",
8 | "declaration": true /* Generates corresponding '.d.ts' file. (set to true in npm script) */,
9 | "declarationMap": true /* Generates a sourcemap for each corresponding '.d.ts' file. (set to true in npm script) */,
10 | "sourceMap": true /* Generates corresponding '.map' file. */,
11 | "removeComments": false /* Do emit comments to output. */,
12 | "jsx": "react" /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */,
13 | /* Strict Type-Checking Options */
14 | "noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */,
15 | /* Module Resolution Options */
16 | "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */,
17 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
18 | },
19 | "include": ["src/**/*", "@types"],
20 | "exclude": ["node_modules", "src/**/__tests__", "src/**/__stories__/*"]
21 | }
22 |
--------------------------------------------------------------------------------
/.github/style.yml:
--------------------------------------------------------------------------------
1 | # Number of days of inactivity before an issue becomes stale
2 | daysUntilStale: 60
3 | # Number of days of inactivity before a stale issue is closed
4 | daysUntilClose: 7
5 | # Issues with these labels will never be considered stale
6 | exemptLabels:
7 | - pinned
8 | - security
9 | - bug
10 | - help wanted
11 |
12 | exemptMilestones: true
13 |
14 | # Label to use when marking an issue as stale
15 | staleLabel: stale
16 |
17 | only: issues
18 |
19 | # Comment to post when removing the stale label.
20 | unmarkComment: >
21 | Okay, it looks like this issue or feature request might still be important.
22 | We'll re-open it for now. Thank you for letting us know!
23 |
24 | # Comment to post when marking an issue as stale. Set to `false` to disable
25 | markComment: >
26 | Is this still relevant? We haven't heard from anyone in a bit. If so,
27 | please comment with any updates or additional detail.
28 |
29 | This issue has been automatically marked as stale because it has not had
30 | recent activity. It will be closed if no further activity occurs. Don't
31 | take it personally, we just need to keep a handle on things. Thank you
32 | for your contributions!
33 |
34 | # Comment to post when closing a stale issue. Set to `false` to disable
35 | closeComment: >
36 | This issue has been automatically closed because it has not had
37 | recent activity. If you believe this is still an issue, please confirm that
38 | this issue is still happening in the most recent version and reply to this
39 | thread to re-open it.
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # editorjs-inline-tool
2 |
3 | All notable changes to this project will be documented here. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
4 |
5 |
6 | ## [0.4.0](https://github.com/natterstefan/editorjs-inline-tool/compare/v0.3.0...v0.4.0) (2020-09-01)
7 |
8 |
9 | ### Features
10 |
11 | * tsc generate proper declaration (typing updated) ([#4](https://github.com/natterstefan/editorjs-inline-tool/issues/4)) ([1e79f7d](https://github.com/natterstefan/editorjs-inline-tool/commit/1e79f7d041739e58356b440c19af1ea23f91a6cc))
12 |
13 | ## [0.3.0](https://github.com/natterstefan/editorjs-inline-tool/compare/v0.2.0...v0.3.0) (2020-03-22)
14 |
15 |
16 | ### Features
17 |
18 | * new required version for peerDependency @editorjs/editorjs ([dce2565](https://github.com/natterstefan/editorjs-inline-tool/commit/dce2565579b267355f3848a14ca0335f1fe0a01d))
19 | * underline inline tool added ([f3776b1](https://github.com/natterstefan/editorjs-inline-tool/commit/f3776b11377d5dc2e98163c8e546ba4774641a1b))
20 |
21 | ## [0.2.0](https://github.com/natterstefan/editorjs-inline-tool/compare/v0.1.1...v0.2.0) (2019-11-29)
22 |
23 |
24 | ### Features
25 |
26 | * add two pre-defined tools: ItalicInlineTool and StrongInlineTool ([aa23db2](https://github.com/natterstefan/editorjs-inline-tool/commit/aa23db22ce2b25abebd6ae6e2c4f906a1d021b08))
27 |
28 | ### [0.1.1](https://github.com/natterstefan/editorjs-inline-tool/compare/v0.1.0...v0.1.1) (2019-11-26)
29 |
30 | Release for [npmjs.com](https://www.npmjs.com/package/editorjs-inline-tool).
31 |
32 | ### 0.1.0 (2019-11-26)
33 |
34 | ### Features
35 |
36 | - initial setup of editorjs-inline-tool plugin ([281d8e7](https://github.com/natterstefan/editorjs-inline-tool/commit/281d8e7aeedd72627b5be1370d31c0e891443423))
37 |
--------------------------------------------------------------------------------
/src/__stories__/config.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * find more available plugins here:
3 | * - https://www.npmjs.com/search?q=%40editorjs
4 | * - or https://github.com/editor-js
5 | *
6 | * or create your own: https://editorjs.io/the-first-plugin
7 | */
8 | import CheckList from '@editorjs/checklist'
9 | import Code from '@editorjs/code'
10 | import Delimiter from '@editorjs/delimiter'
11 | import Embed from '@editorjs/embed'
12 | import Image from '@editorjs/image'
13 | import InlineCode from '@editorjs/inline-code'
14 | import LinkTool from '@editorjs/link'
15 | import List from '@editorjs/list'
16 | import Marker from '@editorjs/marker'
17 | import Quote from '@editorjs/quote'
18 | import Raw from '@editorjs/raw'
19 | import SimpleImage from '@editorjs/simple-image'
20 | import Table from '@editorjs/table'
21 | import Warning from '@editorjs/warning'
22 |
23 | import { UnderlineInlineTool } from '../inline-tools'
24 |
25 | import createGenericInlineTool, { ItalicInlineTool } from '..'
26 |
27 | export const TOOLS = {
28 | embed: Embed,
29 | table: {
30 | class: Table,
31 | // in some cases it is also required to explicitly define `inlineToolbar` _again_
32 | inlineToolbar: ['bold', 'italic', 'link'],
33 | },
34 | list: {
35 | class: List,
36 | inlineToolbar: ['bold', 'italic', 'link'],
37 | },
38 | warning: Warning,
39 | code: Code,
40 | linkTool: LinkTool,
41 | image: Image,
42 | raw: Raw,
43 | quote: Quote,
44 | marker: Marker,
45 | checklist: CheckList,
46 | delimiter: Delimiter,
47 | inlineCode: InlineCode,
48 | simpleImage: SimpleImage,
49 | // overwrite default tools of editorjs by using the same name
50 | bold: {
51 | // use createGenericInlineTool
52 | class: createGenericInlineTool({
53 | sanitize: {
54 | strong: {},
55 | },
56 | shortcut: 'CMD+B',
57 | tagName: 'STRONG',
58 | toolboxIcon:
59 | '',
60 | }),
61 | },
62 | // or use a pre-defined tool
63 | italic: ItalicInlineTool,
64 | underline: UnderlineInlineTool,
65 | }
66 |
--------------------------------------------------------------------------------
/src/__stories__/data.ts:
--------------------------------------------------------------------------------
1 | import { OutputData } from '@editorjs/editorjs'
2 |
3 | interface IDataObj extends OutputData {
4 | blocks: Array<{
5 | type: string
6 | data: {
7 | [key: string]: any
8 | }
9 | }>
10 | }
11 |
12 | const data: IDataObj = {
13 | time: new Date().getTime(),
14 | blocks: [
15 | {
16 | type: 'header',
17 | data: {
18 | text: 'Editor.js',
19 | level: 2,
20 | },
21 | },
22 | {
23 | type: 'paragraph',
24 | data: {
25 | text:
26 | 'Hey. Meet the new Editor. On this page you can see it in action — try to edit this text.',
27 | },
28 | },
29 | {
30 | type: 'header',
31 | data: {
32 | text: 'Key features',
33 | level: 3,
34 | },
35 | },
36 | {
37 | type: 'list',
38 | data: {
39 | style: 'unordered',
40 | items: [
41 | 'It is a block-styled editor',
42 | 'It returns clean data output in JSON',
43 | 'Designed to be extendable and pluggable with a simple API',
44 | ],
45 | },
46 | },
47 | {
48 | type: 'header',
49 | data: {
50 | text: 'What does it mean «block-styled editor»',
51 | level: 3,
52 | },
53 | },
54 | {
55 | type: 'paragraph',
56 | data: {
57 | text:
58 | 'Workspace in classic editors is made of a single contenteditable element, used to create different HTML markups. Editor.js workspace consists of separate Blocks: paragraphs, headings, images, lists, quotes, etc. Each of them is an independent contenteditable element (or more complex structure) provided by Plugin and united by Editor\'s Core.',
59 | },
60 | },
61 | {
62 | type: 'paragraph',
63 | data: {
64 | text:
65 | 'There are dozens of ready-to-use Blocks and the simple API for creation any Block you need. For example, you can implement Blocks for Tweets, Instagram posts, surveys and polls, CTA-buttons and even games.',
66 | },
67 | },
68 | {
69 | type: 'header',
70 | data: {
71 | text: 'What does it mean clean data output',
72 | level: 3,
73 | },
74 | },
75 | {
76 | type: 'paragraph',
77 | data: {
78 | text:
79 | 'Classic WYSIWYG-editors produce raw HTML-markup with both content data and content appearance. On the contrary, Editor.js outputs JSON object with data of each Block. You can see an example below',
80 | },
81 | },
82 | {
83 | type: 'paragraph',
84 | data: {
85 | text:
86 | 'Given data can be used as you want: render with HTML for Web clients, render natively for mobile apps, create markup for Facebook Instant Articles or Google AMP, generate an audio version and so on.',
87 | },
88 | },
89 | {
90 | type: 'paragraph',
91 | data: {
92 | text:
93 | 'Clean data is useful to sanitize, validate and process on the backend.',
94 | },
95 | },
96 | {
97 | type: 'delimiter',
98 | data: {},
99 | },
100 | {
101 | type: 'paragraph',
102 | data: {
103 | text:
104 | "We have been working on this project more than three years. Several large media projects help us to test and debug the Editor, to make it's core more stable. At the same time we significantly improved the API. Now, it can be used to create any plugin for any task. Hope you enjoy. 😏",
105 | },
106 | },
107 | {
108 | type: 'image',
109 | data: {
110 | file: {
111 | url: 'https://capella.pics/6d8f1a84-9544-4afa-b806-5167d45baf7c.jpg',
112 | },
113 | caption: '',
114 | withBorder: true,
115 | stretched: false,
116 | withBackground: false,
117 | },
118 | },
119 | ],
120 | version: '2.15.0',
121 | }
122 |
123 | export default data
124 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # editorjs-inline-tool
2 |
3 | [](https://badge.fury.io/js/editorjs-inline-tool)
4 | [](http://commitizen.github.io/cz-cli/)
5 |
6 | Create an inline tool for ([https://editorjs.io/][1]) with [text formatting tags](https://www.w3schools.com/html/html_formatting.asp)
7 | (eg. `bold`, `strong`, `em`, `u`, ...).
8 |
9 | ## Getting started
10 |
11 | ```sh
12 | npm i editorjs-inline-tool --save
13 |
14 | # or
15 | yarn add editorjs-inline-tool
16 | ```
17 |
18 | ## PeerDependencies
19 |
20 | You have to install the required peerDependencies, which are listed by the
21 | following command:
22 |
23 | ```sh
24 | npm info "editorjs-inline-tool" peerDependencies
25 | ```
26 |
27 | If using npm 5+, use this shortcut:
28 |
29 | ```sh
30 | npx install-peerdeps --dev editorjs-inline-tool
31 |
32 | # or
33 | yarn add editorjs-inline-tool -D --peer
34 | ```
35 |
36 | ## Usage
37 |
38 | This is an example where `GenericInlineTool` is used in a React app (using
39 | [@natterstefan/react-editor-js](https://www.npmjs.com/package/@natterstefan/react-editor-js)).
40 | But this should work for any other framework as well.
41 |
42 | ```jsx
43 | // index.js
44 | import EditorJs from '@natterstefan/react-editor-js'
45 | import Header from '@editorjs/header'
46 | import Paragraph from '@editorjs/paragraph'
47 |
48 | import createGenericInlineTool, {
49 | ItalicInlineTool,
50 | UnderlineInlineTool,
51 | } from 'editorjs-inline-tool'
52 |
53 | const TOOLS = {
54 | header: Header,
55 | paragraph: {
56 | class: Paragraph,
57 | inlineToolbar: true,
58 | },
59 | // add custom tags or overwrite default tools of editorjs by using the same
60 | // name (eg. `bold` or `italic`)
61 | bold: {
62 | class: createGenericInlineTool({
63 | sanitize: {
64 | strong: {},
65 | },
66 | shortcut: 'CMD+B',
67 | tagName: 'STRONG',
68 | toolboxIcon:
69 | '',
70 | }),
71 | },
72 | // or use a pre-defined tool instead
73 | italic: ItalicInlineTool,
74 | underline: UnderlineInlineTool,
75 | }
76 |
77 | const App = () => {
78 | return
79 | }
80 | ```
81 |
82 | ### Configuration
83 |
84 | `createGenericInlineTool` returns an `InlineTool` for `EditorJS`. The following
85 | options are available:
86 |
87 | | Name | Required | Type | Default | Description |
88 | | :---------- | :------: | :------: | :---------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
89 | | sanitize | `false` | `object` | `undefined` | Object that defines rules for [automatic sanitizing](https://editorjs.io/tools-api#sanitize). |
90 | | shortcut | `false` | `string` | `undefined` | [Shortcut](https://github.com/codex-team/codex.shortcuts) to apply [Tool's render and inserting behaviour](https://editorjs.io/tools-api#shortcut) |
91 | | tagName | `true` | `string` | `undefined` | text [formatting tag](https://www.w3schools.com/html/html_formatting.asp) (eg. `bold`) |
92 | | toolboxIcon | `true` | `string` | `undefined` | Icon for the tools [inline toolbar](https://editorjs.io/inline-tools-api-1#render) |
93 |
94 | Additionally, there are pre-defined inline tools available: `ItalicInlineTool`,
95 | `StrongInlineTool` and `UnderlineInlineTool` (they can be found
96 | [here](src/inline-tools.tsx)).
97 |
98 | ## Licence
99 |
100 | [MIT](LICENCE)
101 |
102 | This project is not affiliated, associated, authorized, endorsed by or in any
103 | way officially connected to EditorJS ([editorjs.io](https://editorjs.io/)).
104 |
105 | ## Contributors ✨
106 |
107 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
108 |
109 |
110 |
111 |
112 |
117 |
118 |
119 |
120 |
121 |
122 |
123 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
124 |
125 | [1]: https://editorjs.io/
126 |
--------------------------------------------------------------------------------
/src/tool.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/explicit-function-return-type */
2 | import * as EditorJS from '@editorjs/editorjs'
3 |
4 | type Props = {
5 | /**
6 | * Object that defines rules for automatic sanitizing.
7 | * @see https://editorjs.io/tools-api#sanitize
8 | * @default undefined
9 | */
10 | sanitize?: {}
11 | /**
12 | * [Shortcut](https://github.com/codex-team/codex.shortcuts) to apply
13 | * [Tool's render and inserting behaviour](https://editorjs.io/tools-api#shortcut)
14 | * @default undefined
15 | */
16 | shortcut?: string
17 | /**
18 | * text [formatting tag](https://www.w3schools.com/html/html_formatting.asp)
19 | * (eg. `bold`)
20 | * @default undefined
21 | */
22 | tagName: string
23 | /**
24 | * Icon for the tools [inline toolbar](https://editorjs.io/inline-tools-api-1#render)
25 | * @default undefined
26 | */
27 | toolboxIcon: string
28 | }
29 |
30 | /**
31 | * GenericInlineTool returns an EditorJS.InlineTool capable of wrapping a
32 | * selected text with any given `tagName`.
33 | *
34 | * inspired by
35 | * @see https://github.com/editor-js/marker/blob/c306bcb33c88eaa3c172eaf387fbcd06ae6b297f/src/index.js
36 | */
37 | const createGenericInlineTool = ({
38 | sanitize,
39 | shortcut,
40 | tagName,
41 | toolboxIcon,
42 | }: Props) => {
43 | return class GenericInlineTool implements EditorJS.InlineTool {
44 | api: EditorJS.API = null
45 |
46 | button: HTMLButtonElement = null
47 |
48 | tag: string = null
49 |
50 | iconClasses: {
51 | active: string
52 | base: string
53 | } = null
54 |
55 | /**
56 | * Specifies Tool as Inline Toolbar Tool
57 | *
58 | * @return {boolean}
59 | */
60 | public static isInline = true
61 |
62 | /**
63 | * @param {{api: object}} - Editor.js API
64 | */
65 | constructor({ api }: { api: EditorJS.API }) {
66 | this.api = api
67 |
68 | /**
69 | * Toolbar Button
70 | *
71 | * @type {HTMLElement|null}
72 | */
73 | this.button = null
74 |
75 | /**
76 | * Tag that should is rendered in the editor
77 | *
78 | * @type {string}
79 | */
80 | this.tag = tagName
81 |
82 | /**
83 | * CSS classes
84 | */
85 | this.iconClasses = {
86 | base: this.api.styles.inlineToolButton,
87 | active: this.api.styles.inlineToolButtonActive,
88 | }
89 | }
90 |
91 | /**
92 | * Create button element for Toolbar
93 | *
94 | * @return {HTMLButtonElement}
95 | */
96 | render(): HTMLButtonElement {
97 | this.button = document.createElement('button')
98 | this.button.type = 'button'
99 | this.button.classList.add(this.iconClasses.base)
100 | this.button.innerHTML = this.toolboxIcon
101 |
102 | return this.button
103 | }
104 |
105 | /**
106 | * Wrap/Unwrap selected fragment
107 | *
108 | * @param {Range} range - selected fragment
109 | */
110 | surround(range: Range) {
111 | if (!range) {
112 | return
113 | }
114 |
115 | const termWrapper = this.api.selection.findParentTag(this.tag)
116 |
117 | /**
118 | * If start or end of selection is in the highlighted block
119 | */
120 | if (termWrapper) {
121 | this.unwrap(termWrapper)
122 | } else {
123 | this.wrap(range)
124 | }
125 | }
126 |
127 | /**
128 | * Wrap selection with term-tag
129 | *
130 | * @param {Range} range - selected fragment
131 | */
132 | wrap(range: Range) {
133 | /**
134 | * Create a wrapper for given tagName
135 | */
136 | const strongElement = document.createElement(this.tag)
137 |
138 | /**
139 | * SurroundContent throws an error if the Range splits a non-Text node with only one of its boundary points
140 | * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Range/surroundContents}
141 | *
142 | * // range.surroundContents(span);
143 | */
144 | strongElement.appendChild(range.extractContents())
145 | range.insertNode(strongElement)
146 |
147 | /**
148 | * Expand (add) selection to highlighted block
149 | */
150 | this.api.selection.expandToTag(strongElement)
151 | }
152 |
153 | /**
154 | * Unwrap term-tag
155 | *
156 | * @param {HTMLElement} termWrapper - term wrapper tag
157 | */
158 | unwrap(termWrapper: HTMLElement) {
159 | /**
160 | * Expand selection to all term-tag
161 | */
162 | this.api.selection.expandToTag(termWrapper)
163 |
164 | const sel = window.getSelection()
165 | const range = sel.getRangeAt(0)
166 |
167 | const unwrappedContent = range.extractContents()
168 |
169 | /**
170 | * Remove empty term-tag
171 | */
172 | termWrapper.parentNode.removeChild(termWrapper)
173 |
174 | /**
175 | * Insert extracted content
176 | */
177 | range.insertNode(unwrappedContent)
178 |
179 | /**
180 | * Restore selection
181 | */
182 | sel.removeAllRanges()
183 | sel.addRange(range)
184 | }
185 |
186 | /**
187 | * Check and change Term's state for current selection
188 | */
189 | checkState() {
190 | const termTag = this.api.selection.findParentTag(this.tag)
191 |
192 | this.button.classList.toggle(this.iconClasses.active, !!termTag)
193 |
194 | return !!termTag
195 | }
196 |
197 | /**
198 | * Get Tool icon's SVG
199 | * @return {string}
200 | */
201 | get toolboxIcon() {
202 | return toolboxIcon
203 | }
204 |
205 | /**
206 | * Sanitizer rule
207 | * @return {Object.} {Object.}
208 | */
209 | static get sanitize() {
210 | return sanitize
211 | }
212 |
213 | /**
214 | * Set a shortcut
215 | */
216 | public get shortcut() {
217 | return shortcut
218 | }
219 | }
220 | }
221 |
222 | export default createGenericInlineTool
223 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "editorjs-inline-tool",
3 | "version": "0.4.0",
4 | "description": "Create an inline tool for editorjs with text formatting tags easily.",
5 | "repository": {
6 | "type": "git",
7 | "url": "https://github.com/natterstefan/editorjs-inline-tool.git"
8 | },
9 | "author": "Stefan Natter (https://twitter.com/natterstefan)",
10 | "license": "MIT",
11 | "bugs": {
12 | "url": "https://github.com/natterstefan/editorjs-inline-tool/issues"
13 | },
14 | "homepage": "https://github.com/natterstefan/editorjs-inline-tool#readme",
15 | "publishConfig": {
16 | "access": "public"
17 | },
18 | "main": "lib/index.js",
19 | "module": "esm/index.js",
20 | "types": "lib/index.d.ts",
21 | "files": [
22 | "dist",
23 | "es",
24 | "esm",
25 | "lib"
26 | ],
27 | "keywords": [
28 | "editor",
29 | "editor.js",
30 | "editorjs",
31 | "@editorjs",
32 | "editorjs-tool",
33 | "editor-js-tool",
34 | "editorjs-inlinetool",
35 | "editor-js-inline-tool",
36 | "wysiwyg"
37 | ],
38 | "scripts": {
39 | "build": "npm run build-cjs && npm run build-es && npm run build-esm && npm run build-umd",
40 | "build-cjs": "tsc --outDir lib --module commonjs --target es5",
41 | "build-es": "tsc --outDir es --module es2015 --target es2015",
42 | "build-esm": "tsc --outDir esm --module es2015 --target es5",
43 | "build-umd": "webpack --mode=production",
44 | "build-storybook": "build-storybook",
45 | "contributors-add": "all-contributors add",
46 | "contributors-generate": "all-contributors generate",
47 | "lint": "tsc --noEmit && eslint '**/*.{ts,tsx}' --quiet --cache",
48 | "prebuild": "rimraf dist && rimraf es && rimraf esm && rimraf lib",
49 | "prepublishOnly": "npm run build",
50 | "postbuild": "npm run size",
51 | "release-minor": "HUSKY_SKIP_HOOKS=1 standard-version --release-as minor",
52 | "release-patch": "HUSKY_SKIP_HOOKS=1 standard-version --release-as patch",
53 | "start": "start-storybook -p 6006 --ci",
54 | "size": "size-limit",
55 | "test": "echo \"Error: no test specified\" && exit 1"
56 | },
57 | "peerDependencies": {
58 | "@editorjs/editorjs": "^2.16.0"
59 | },
60 | "devDependencies": {
61 | "@babel/core": "^7.9.0",
62 | "@editorjs/checklist": "^1.1.0",
63 | "@editorjs/code": "^2.4.1",
64 | "@editorjs/delimiter": "^1.1.0",
65 | "@editorjs/editorjs": "^2.16.2",
66 | "@editorjs/embed": "^2.2.1",
67 | "@editorjs/header": "^2.4.0",
68 | "@editorjs/image": "^2.3.3",
69 | "@editorjs/inline-code": "^1.3.1",
70 | "@editorjs/link": "^2.1.3",
71 | "@editorjs/list": "^1.4.0",
72 | "@editorjs/marker": "^1.2.2",
73 | "@editorjs/paragraph": "^2.6.1",
74 | "@editorjs/quote": "^2.3.0",
75 | "@editorjs/raw": "^2.1.1",
76 | "@editorjs/simple-image": "^1.3.3",
77 | "@editorjs/table": "^1.2.2",
78 | "@editorjs/warning": "^1.1.1",
79 | "@natterstefan/react-editor-js": "^0.3.1",
80 | "@size-limit/preset-small-lib": "^2.2.1",
81 | "@storybook/addon-actions": "^5.2.6",
82 | "@storybook/addons": "^5.2.6",
83 | "@storybook/react": "^5.2.6",
84 | "@types/enzyme": "^3.10.5",
85 | "@types/enzyme-adapter-react-16": "^1.0.6",
86 | "@types/react": "^16.9.13",
87 | "@types/react-dom": "^16.9.4",
88 | "@typescript-eslint/eslint-plugin": "^2.24.0",
89 | "@typescript-eslint/parser": "^2.24.0",
90 | "all-contributors-cli": "^6.14.0",
91 | "babel-eslint": "^10.1.0",
92 | "babel-loader": "^8.1.0",
93 | "commitizen": "^4.0.3",
94 | "cz-conventional-changelog": "^3.1.0",
95 | "enzyme": "^3.11.0",
96 | "enzyme-adapter-react-16": "^1.15.2",
97 | "eslint": "^6.8.0",
98 | "eslint-config-airbnb": "^18.1.0",
99 | "eslint-config-ns": "^1.1.0",
100 | "eslint-config-prettier": "^6.10.1",
101 | "eslint-import-resolver-alias": "^1.1.2",
102 | "eslint-plugin-import": "^2.20.1",
103 | "eslint-plugin-jest": "^23.8.2",
104 | "eslint-plugin-jsx-a11y": "^6.2.3",
105 | "eslint-plugin-prettier": "^3.1.2",
106 | "eslint-plugin-react": "^7.19.0",
107 | "eslint-plugin-react-hooks": "^2.5.1",
108 | "husky": "^4.2.3",
109 | "lint-staged": "^10.0.8",
110 | "prettier": "^2.0.1",
111 | "react": "^16.12.0",
112 | "react-docgen-typescript-loader": "^3.7.1",
113 | "react-dom": "^16.12.0",
114 | "rimraf": "^3.0.2",
115 | "source-map-loader": "^0.2.4",
116 | "standard-version": "^8.0.1",
117 | "start-server-and-test": "^1.10.11",
118 | "storybook-readme": "^5.0.8",
119 | "terser-webpack-plugin": "^2.3.5",
120 | "ts-loader": "^8.0.3",
121 | "typescript": "^3.9.7",
122 | "webpack": "^4.42.0",
123 | "webpack-cli": "^3.3.11"
124 | },
125 | "husky": {
126 | "hooks": {
127 | "pre-commit": "lint-staged",
128 | "prepare-commit-msg": "exec < /dev/tty && git cz --hook"
129 | }
130 | },
131 | "lint-staged": {
132 | "*.js": [
133 | "npm run lint",
134 | "prettier --write",
135 | "git update-index --again"
136 | ]
137 | },
138 | "size-limit": [
139 | {
140 | "limit": "6 KB",
141 | "path": "dist/index.js",
142 | "config": "./webpack.config.js"
143 | },
144 | {
145 | "limit": "6 KB",
146 | "path": "lib/**/*.js"
147 | },
148 | {
149 | "limit": "6 KB",
150 | "path": "es/**/*.js"
151 | },
152 | {
153 | "limit": "6 KB",
154 | "path": "esm/**/*.js"
155 | }
156 | ],
157 | "config": {
158 | "commitizen": {
159 | "path": "./node_modules/cz-conventional-changelog"
160 | }
161 | },
162 | "standard-version": {
163 | "changelogHeader": "# editorjs-inline-tool\n\nAll notable changes to this project will be documented here. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).\n\n",
164 | "types": [
165 | {
166 | "type": "feat",
167 | "section": "Features"
168 | },
169 | {
170 | "type": "fix",
171 | "section": "Fixes"
172 | },
173 | {
174 | "type": "chore",
175 | "hidden": true
176 | },
177 | {
178 | "type": "docs",
179 | "hidden": true
180 | },
181 | {
182 | "type": "style",
183 | "hidden": true
184 | },
185 | {
186 | "type": "refactor",
187 | "hidden": true
188 | },
189 | {
190 | "type": "perf",
191 | "hidden": true
192 | },
193 | {
194 | "type": "test",
195 | "hidden": true
196 | }
197 | ]
198 | }
199 | }
200 |
--------------------------------------------------------------------------------