├── .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 | [![npm version](https://badge.fury.io/js/editorjs-inline-tool.svg)](https://badge.fury.io/js/editorjs-inline-tool) 4 | [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](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 | 113 | 114 | 115 | 116 |
Stefan Natter
Stefan Natter

💻 📖 💡
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 | --------------------------------------------------------------------------------