Release 2.19.2 (#1597)

Save 2.19.2 changes
This commit is contained in:
Peter Savchenko 2021-03-15 22:05:53 +03:00 committed by GitHub
parent b78707fb78
commit 73ac3e3f5f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
58 changed files with 1008 additions and 848 deletions

View file

@ -0,0 +1,77 @@
name: Bump version on merge
on:
pull_request:
branches:
- next
types: [closed]
jobs:
# If pull request was merged then we should check for a package version update
check-for-no-version-changing:
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
steps:
# Checkout to target branch
- uses: actions/checkout@v2
with:
fetch-depth: 0
# Get package new version name
- name: Get package info
id: packageNew
uses: codex-team/action-nodejs-package-info@v1
# Checkout to the base commit before merge
- name: Checkout to the base commit before merge
run: git checkout ${{ github.event.pull_request.base.sha }}
# Get package old version name
- name: Get package info
id: packageOld
uses: codex-team/action-nodejs-package-info@v1
# Stop workflow and do not bump version if it was changed already
- name: Stop workflow and do not bump version if it was changed already
uses: actions/github-script@v3
if: steps.packageOld.outputs.version != steps.packageNew.outputs.version
with:
script: |
core.setFailed('Version was changed! ${{ steps.packageOld.outputs.version }} -> ${{ steps.packageNew.outputs.version }}')
bump-version:
needs: check-for-no-version-changing
runs-on: ubuntu-latest
steps:
# Checkout to target branch
- uses: actions/checkout@v2
# Setup node environment
- uses: actions/setup-node@v1
with:
node-version: 15
registry-url: https://registry.npmjs.org/
# Bump version to the next prerelease (patch) with rc suffix
- name: Suggest the new version
run: yarn version --prerelease --preid rc --no-git-tag-version
# Get package new version name
- name: Get package info
id: package
uses: codex-team/action-nodejs-package-info@v1
# Create pull request with changes
- name: Create Pull Request
uses: peter-evans/create-pull-request@v3
with:
commit-message: Bump version
committer: github-actions <action@github.com>
author: github-actions <action@github.com>
branch: auto-bump-version
base: ${{ steps.vars.outputs.base_branch }}
delete-branch: true
title: "Bump version up to ${{ steps.package.outputs.version }}"
body: |
Auto-generated bump version suggestion because of PR:
**${{ github.event.pull_request.title }}** #${{ github.event.pull_request.number }}

View file

@ -0,0 +1,108 @@
name: Create a release draft
on:
pull_request:
branches:
- next
types: [closed]
jobs:
# If pull request was merged then we should check for a package version update
check-version-changing:
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
steps:
# Checkout to target branch
- uses: actions/checkout@v2
with:
fetch-depth: 0
# Get package new version name
- name: Get package info
id: packageNew
uses: codex-team/action-nodejs-package-info@v1
# Checkout to the base commit before merge
- name: Checkout to the base commit before merge
run: git checkout ${{ github.event.pull_request.base.sha }}
# Get package old version name
- name: Get package info
id: packageOld
uses: codex-team/action-nodejs-package-info@v1
# Stop workflow if version was not changed
- name: Stop workflow if version was not changed
uses: actions/github-script@v3
if: steps.packageOld.outputs.version == steps.packageNew.outputs.version
with:
script: |
core.setFailed('No version changes. ${{ steps.packageOld.outputs.version }}')
# Create a new draft release
release-draft:
needs: check-version-changing
runs-on: ubuntu-latest
steps:
# Checkout to target branch
- uses: actions/checkout@v2
with:
# Pull submodules
submodules: 'recursive'
# Setup node environment
- uses: actions/setup-node@v1
with:
node-version: 15
registry-url: https://registry.npmjs.org/
# Prepare, build and publish project
- name: Install dependencies
run: yarn
# Build Editor.js
- name: Build output files
run: yarn build
# Get package version name
- name: Get package info
id: package
uses: codex-team/action-nodejs-package-info@v1
- name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: v${{ steps.package.outputs.version }}
release_name: v${{ steps.package.outputs.version }}
# Fill release description from pull request body name
body: "${{ github.event.pull_request.title }} #${{ github.event.pull_request.number }}"
# Save as a draft release
draft: true
# If version name contains "-rc" suffix than mark a "pre-release" checkbox
prerelease: ${{ contains(steps.package.outputs.version, '-rc') }}
# Build and upload target Editor.js build to release as artifact
- name: Upload Release Asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: dist/editor.js
asset_name: editor.js
asset_content_type: application/javascript
# Send a notification message
- name: Send a message
uses: codex-team/action-codexbot-notify@v1
with:
webhook: ${{ secrets.CODEX_BOT_WEBHOOK_FRONTEND }}
message: '🦥 [Draft release v${{ steps.package.outputs.version }}](${{ steps.create_release.outputs.html_url }}) for package [${{ steps.package.outputs.name }}](${{ steps.package.outputs.npmjs-link }}) has been created. Add changelog and publish it!'
parse_mode: 'markdown'
disable_web_page_preview: true

View file

@ -0,0 +1,53 @@
name: Publish package to NPM
on:
release:
types:
- published
jobs:
publish:
runs-on: ubuntu-latest
steps:
# Checkout to target branch
- uses: actions/checkout@v2
with:
# Pull submodules
submodules: 'recursive'
# Setup node environment
- uses: actions/setup-node@v1
with:
node-version: 15
registry-url: https://registry.npmjs.org/
# Prepare, build and publish project
- name: Install dependencies
run: yarn
- name: Build output files
run: yarn build
- name: Publish the package
run: yarn publish --access=public ${{ github.event.release.prerelease && '--tag=next' || '--tag=latest' }}
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
notify:
needs: publish
runs-on: ubuntu-latest
steps:
# Checkout to target branch
- uses: actions/checkout@v2
- name: Get package info
id: package
uses: codex-team/action-nodejs-package-info@v1
- name: Send a message
uses: codex-team/action-codexbot-notify@v1
with:
webhook: ${{ secrets.CODEX_BOT_NOTIFY_EDITORJS_PUBLIC_CHAT }}
message: '📦 [${{ steps.package.outputs.name }}](${{ steps.package.outputs.npmjs-link }}) ${{ steps.package.outputs.version }} was published'
parse_mode: 'markdown'
disable_web_page_preview: true

2
.gitignore vendored
View file

@ -12,3 +12,5 @@ yarn-error.log
test/cypress/screenshots
test/cypress/videos
dist/

View file

@ -1,19 +1,21 @@
.idea/
build/sprite.svg
docs/
example/
src/
.idea
.github
docs
example
src
test
.babelrc
.editorconfig
.eslintignore
.eslintrc
.git
.gitmodules
.jshintrc
.postcssrc
.postcssrc.yml
.stylelintrc
CODEOWNERS
cypress.json
tsconfig.json
tslint.json
webpack.config.js
yarn.lock
.github

View file

@ -5,7 +5,7 @@
[![](https://flat.badgen.net/bundlephobia/minzip/@editorjs/editorjs?color=green)](https://www.npmjs.com/package/@editorjs/editorjs)
[![Backers on Open Collective](https://opencollective.com/editorjs/backers/badge.svg)](#backers)
[![Sponsors on Open Collective](https://opencollective.com/editorjs/sponsors/badge.svg)](#sponsors)
[![](https://flat.badgen.net/npm/license/@editorjs/editorjs)](https://www.npmjs.com/package/@editorjs/editorjs)
[![](https://img.shields.io/npm/l/@editorjs/editorjs?style=flat-square)](https://www.npmjs.com/package/@editorjs/editorjs)
[![Join the chat at https://gitter.im/codex-team/editor.js](https://badges.gitter.im/codex-team/editor.js.svg)](https://gitter.im/codex-team/editor.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE / Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari-ios/safari-ios_48x48.png" alt="iOS Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>iOS Safari | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/opera/opera_48x48.png" alt="Opera" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Opera |
@ -129,7 +129,6 @@ Choose the most usable method of getting Editor for you.
- Node package
- Source from CDN
- Local file from project
##### Option A. NPM install
@ -157,9 +156,7 @@ For example, place this in your HTML:
<script src="https://cdn.jsdelivr.net/npm/@editorjs/editorjs@latest"></script>
```
##### Option C. Save source within your project
Copy the [editor.js](dist/editor.js) file to your project and load it.
Or download the bundle file and use it from your server.
```html
<script src="editor.js"></script>

2
dist/editor.js vendored

File diff suppressed because one or more lines are too long

View file

@ -1,67 +0,0 @@
/*!
* CodeX.Tooltips
*
* @version 1.0.1
*
* @licence MIT
* @author CodeX <https://codex.so>
*
*
*/
/*!
* Codex JavaScript Notification module
* https://github.com/codex-team/js-notifier
*/
/*!
* Editor.js
*
* @version 2.19.1
*
* @licence Apache-2.0
* @author CodeX <https://codex.so>
*
* @uses html-janitor
* @licence Apache-2.0 (https://github.com/guardian/html-janitor/blob/master/LICENSE)
*/
/*!
* Library for handling keyboard shortcuts
* @copyright CodeX (https://codex.so)
* @license MIT
* @author CodeX (https://codex.so)
* @version 1.1.1
*/
/**
* If developer uses editor's API, then he can customize sanitize restrictions.
* Or, sanitizing config can be defined globally in editors initialization. That config will be used everywhere
* At least, if there is no config overrides, that API uses Default configuration
*
* @see {@link https://www.npmjs.com/package/html-janitor}
* @license Apache-2.0
* @see {@link https://github.com/guardian/html-janitor/blob/master/LICENSE}
*
* @param {SanitizerConfig} config - sanitizer extension
*/
/**
* Editor.js
*
* Short Description (눈_눈;)
*
* @version 2.18.0
*
* @license Apache-2.0
* @author CodeX-Team <https://ifmo.su>
*/
/**
* Base Paragraph Block for the Editor.js.
* Represents simple paragraph
*
* @author CodeX (team@codex.so)
* @copyright CodeX 2018
* @license The MIT License (MIT)
*/

View file

@ -1,413 +0,0 @@
@babel/polyfill
MIT
MIT License
Copyright (c) 2014-present Sebastian McKenzie and other contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@babel/register
MIT
MIT License
Copyright (c) 2014-present Sebastian McKenzie and other contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@babel/runtime
MIT
MIT License
Copyright (c) 2014-present Sebastian McKenzie and other contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@codexteam/shortcuts
MIT
MIT License
Copyright (c) 2018 CodeX
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@editorjs/paragraph
MIT
MIT License
Copyright (c) 2018 CodeX
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
codex-notifier
MIT
MIT License
Copyright (c) 2017 CodeX
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
codex-tooltip
MIT
Copyright 2019 CodeX https://codex.so
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
core-js
MIT
Copyright (c) 2014-2019 Denis Pushkarev
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
html-janitor
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
regenerator-runtime
MIT
MIT License
Copyright (c) 2014-present, Facebook, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

47
dist/sprite.svg vendored
View file

@ -1,47 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg">
<symbol id="arrow-down" viewBox="0 0 14 14">
<path transform="matrix(1 0 0 -1 0 14)" d="M8.024 4.1v8.6a1.125 1.125 0 0 1-2.25 0V4.1L2.18 7.695A1.125 1.125 0 1 1 .59 6.104L6.103.588c.44-.439 1.151-.439 1.59 0l5.516 5.516a1.125 1.125 0 0 1-1.59 1.59L8.023 4.1z"/>
</symbol>
<symbol id="arrow-up" viewBox="0 0 14 14">
<path d="M8.024 4.1v8.6a1.125 1.125 0 0 1-2.25 0V4.1L2.18 7.695A1.125 1.125 0 1 1 .59 6.104L6.103.588c.44-.439 1.151-.439 1.59 0l5.516 5.516a1.125 1.125 0 0 1-1.59 1.59L8.023 4.1z"/>
</symbol>
<symbol id="bold" viewBox="0 0 12 14"><path d="M5.997 14H1.72c-.618 0-1.058-.138-1.323-.415C.132 13.308 0 12.867 0 12.262V1.738C0 1.121.135.676.406.406.676.136 1.114 0 1.719 0h4.536c.669 0 1.248.041 1.738.124.49.083.93.242 1.318.478a3.458 3.458 0 0 1 1.461 1.752c.134.366.2.753.2 1.16 0 1.401-.7 2.426-2.1 3.075 1.84.586 2.76 1.726 2.76 3.42 0 .782-.2 1.487-.602 2.114a3.61 3.61 0 0 1-1.623 1.39 5.772 5.772 0 0 1-1.471.377c-.554.073-1.2.11-1.939.11zm-.21-6.217h-2.95v4.087h3.046c1.916 0 2.874-.69 2.874-2.072 0-.707-.248-1.22-.745-1.537-.496-.319-1.238-.478-2.225-.478zM2.837 2.13v3.619h2.597c.707 0 1.252-.067 1.638-.2.385-.134.68-.389.883-.765.16-.267.239-.566.239-.897 0-.707-.252-1.176-.755-1.409-.503-.232-1.27-.348-2.301-.348H2.836z"/>
</symbol>
<symbol id="cross" viewBox="0 0 237 237">
<path transform="rotate(45 280.675 51.325)" d="M191 191V73c0-5.523 4.477-10 10-10h25c5.523 0 10 4.477 10 10v118h118c5.523 0 10 4.477 10 10v25c0 5.523-4.477 10-10 10H236v118c0 5.523-4.477 10-10 10h-25c-5.523 0-10-4.477-10-10V236H73c-5.523 0-10-4.477-10-10v-25c0-5.523 4.477-10 10-10h118z"/>
</symbol>
<symbol id="dots" viewBox="0 0 8 8">
<circle cx="6.5" cy="1.5" r="1.5"/>
<circle cx="6.5" cy="6.5" r="1.5"/>
<circle cx="1.5" cy="1.5" r="1.5"/>
<circle cx="1.5" cy="6.5" r="1.5"/>
</symbol>
<symbol id="italic" viewBox="0 0 4 11">
<path d="M3.289 4.17L2.164 9.713c-.078.384-.238.674-.48.87-.243.198-.52.296-.831.296-.312 0-.545-.1-.699-.302-.153-.202-.192-.49-.116-.864L1.15 4.225c.077-.38.232-.665.466-.857a1.25 1.25 0 01.818-.288c.312 0 .55.096.713.288.163.192.21.46.141.801zm-.667-2.09c-.295 0-.53-.09-.706-.273-.176-.181-.233-.439-.173-.77.055-.302.207-.55.457-.745C2.45.097 2.716 0 3 0c.273 0 .5.088.68.265.179.176.238.434.177.771-.06.327-.21.583-.45.767-.24.185-.502.277-.785.277z"/>
</symbol>
<symbol id="link" viewBox="0 0 14 10">
<path d="M6 0v2H5a3 3 0 000 6h1v2H5A5 5 0 115 0h1zm2 0h1a5 5 0 110 10H8V8h1a3 3 0 000-6H8V0zM5 4h4a1 1 0 110 2H5a1 1 0 110-2z"/>
</symbol>
<symbol id="plus" viewBox="0 0 14 14">
<path d="M8.05 5.8h4.625a1.125 1.125 0 0 1 0 2.25H8.05v4.625a1.125 1.125 0 0 1-2.25 0V8.05H1.125a1.125 1.125 0 0 1 0-2.25H5.8V1.125a1.125 1.125 0 0 1 2.25 0V5.8z"/>
</symbol>
<symbol id="sad-face" viewBox="0 0 52 52">
<path fill="#D76B6B" fill-rule="nonzero" d="M26 52C11.64 52 0 40.36 0 26S11.64 0 26 0s26 11.64 26 26-11.64 26-26 26zm0-3.25c12.564 0 22.75-10.186 22.75-22.75S38.564 3.25 26 3.25 3.25 13.436 3.25 26 13.436 48.75 26 48.75zM15.708 33.042a2.167 2.167 0 1 1 0-4.334 2.167 2.167 0 0 1 0 4.334zm23.834 0a2.167 2.167 0 1 1 0-4.334 2.167 2.167 0 0 1 0 4.334zm-15.875 5.452a1.083 1.083 0 1 1-1.834-1.155c1.331-2.114 3.49-3.179 6.334-3.179 2.844 0 5.002 1.065 6.333 3.18a1.083 1.083 0 1 1-1.833 1.154c-.913-1.45-2.366-2.167-4.5-2.167s-3.587.717-4.5 2.167z"/>
</symbol>
<symbol id="toggler-down">
<path d="M6.5 9.294a.792.792 0 01-.562-.232L2.233 5.356a.794.794 0 011.123-1.123L6.5 7.377l3.144-3.144a.794.794 0 011.123 1.123L7.062 9.062a.792.792 0 01-.562.232z"/>
</symbol>
<symbol id="unlink" viewBox="0 0 15 11">
<path d="M13.073 2.099l-1.448 1.448A3 3 0 009 2H8V0h1c1.68 0 3.166.828 4.073 2.099zM6.929 4l-.879.879L7.172 6H5a1 1 0 110-2h1.929zM6 0v2H5a3 3 0 100 6h1v2H5A5 5 0 115 0h1zm6.414 7l2.122 2.121-1.415 1.415L11 8.414l-2.121 2.122L7.464 9.12 9.586 7 7.464 4.879 8.88 3.464 11 5.586l2.121-2.122 1.415 1.415L12.414 7z"/>
</symbol></svg>

Before

Width:  |  Height:  |  Size: 3.9 KiB

View file

@ -1,17 +1,36 @@
# Changelog
### 2.19.2
- `New` - `toolbar.toggleBlockSettings()` API method added [#1442](https://github.com/codex-team/editor.js/issues/1421).
- `Improvements` - A generic type for Tool config added [#1516](https://github.com/codex-team/editor.js/issues/1516)
- `Improvements` - Remove unused `force` option in `Caret.navigateNext()` and `Caret.navigatePrevious()` [#857](https://github.com/codex-team/editor.js/issues/857#issuecomment-770363438).
- `Improvements` - Remove bundles from the repo [#1541](https://github.com/codex-team/editor.js/pull/1541).
- `Improvements` - Document will be scrolled when blocks are selected with `SHIFT+UP` or `SHIFT+DOWN` [#1447](https://github.com/codex-team/editor.js/issues/1447)
- `Improvements` - The caret will be set on editor copy/paste [#1470](https://github.com/codex-team/editor.js/pull/1470)
- `Improvements` - Added generic types to OutputBlockData [#1551](https://github.com/codex-team/editor.js/issues/1551).
- `Fix` - Fix BlockManager.setCurrentBlockByChildNode() with multiple Editor.js instances [#1503](https://github.com/codex-team/editor.js/issues/1503).
- `Fix` - Fix an unstable block cut process [#1489](https://github.com/codex-team/editor.js/issues/1489).
- `Fix` - Type definition of the Sanitizer config: the sanitize function now contains param definition [#1491](https://github.com/codex-team/editor.js/pull/1491).
- `Fix` - Fix unexpected behavior on an empty link pasting [#1348](https://github.com/codex-team/editor.js/issues/1348).
- `Fix` - Fix SanitizerConfig type definition [#1513](https://github.com/codex-team/editor.js/issues/1513)
- `Refactoring` - The Listeners module now is a util.
- `Refactoring` - The Events module now is a util.
- `Fix` - Editor Config now immutable [#1552](https://github.com/codex-team/editor.js/issues/1552).
- `Refactoring` - Shortcuts module is util now.
- `Fix` - Fix bubbling on BlockManagers' listener [#1433](https://github.com/codex-team/editor.js/issues/1433).
### 2.19.1
- `Improvements` - The [Cypress](https://www.cypress.io) was integrated as the end-to-end testing framework
- `Improvements` - Native `typeof`replaced with custom utils methods
- `Improvements` - Bind shortcuts listeners on the editor wrapper instead of document (#1391)(https://github.com/codex-team/editor.js/issues/1391)
- `Improvements` - Bind shortcuts listeners on the editor wrapper instead of document [#1391](https://github.com/codex-team/editor.js/issues/1391)
- `Fix` - The problem with destroy() method [#1380](https://github.com/codex-team/editor.js/issues/1380).
- `Fix` - add getter keyword to `block.mergeable` method [#1415](https://github.com/codex-team/editor.js/issues/1415).
- `Fix` — Fix problem with entering to Editor.js by Tab key [#1393](https://github.com/codex-team/editor.js/issues/1393)
- `Fix` - Sanitize pasted block data [#1396](https://github.com/codex-team/editor.js/issues/1396).
- `Fix` - Unnecessary block creation after arrow navigation at last non-default block[#1414](https://github.com/codex-team/editor.js/issues/1414)
### 2.19
- `New` - Read-only mode 🥳 [#837](https://github.com/codex-team/editor.js/issues/837)

100
docs/releases.md Normal file
View file

@ -0,0 +1,100 @@
# Branches, versions and releases — complete guideline
## Branches
The project has two main branches: `master` and `next`.
Branch `master` contains the latest stable version of the editor.
The latest version published to NPM available by default or by the tag `latest`.
Branch `next` used for development the next (release candidate) version of the editor.
It may contain bug fixes, improvements or features. This version is available in NPM by `next` tag.
## Versions
We use [semantic versioning](https://semver.org) as a main guide for naming updates.
`<major>.<minor>.<patch>`
You need to bump the part of version according the changes:
- `patch` — for bug fixes, docs updates, code style fixes and other changes which do not affect the result project bundle
- `minor` — for new features with no backward compatibility problems.
- `major` — for breaking changes without backward compatibility with the api of the previous version of the project.
Pre-release versions may contain additional `-rc.*` suffix.
## Release publishing
Drafts for new releases are created automatically via [create-a-release-draft.yml](.github/workflows/create-a-release-draft.yml)
workflow when pull request to `next` branch was merged with an updated version in the package.json file.
There is a [workflow](.github/workflows/publish-package-to-npm.yml) that fired on a new release publishing on GitHub.
Use target version changelog as a description.
![](https://capella.pics/57267bab-f2f0-411b-a9d1-69abee6abab5.jpg)
Then you can publish the release and wait for package publishing via action.
This package version will be published to NPM with default `latest` tag.
### Release candidate publishing
If you want to publish release candidate version, use suffix `-rc.*` for package
version in package.json file and in tag on releases page. Workflow will detect it and mark a release as "pre-release".
![](https://capella.pics/796de9eb-bbe0-485c-bc8f-9a4cb76641b7.jpg)
This package version will be published to NPM with `next` tag.
Stable version: `2.19.0`
Release candidate: `2.19.1-rc.0`, `2.19.1-rc.1`, ...
Next version: `2.19.1`
## Auto-bump version
After each PR merge to the `next` branch [bump-version-on-merge-next.yml](.github/workflows/bump-version-on-merge-next.yml)
workflow will check if a package version was updated. If there is no update then it will open a new PR with a next
prerelease version.
### How it works
The command for bumping a version will be running in a workflow.
`yarn version --prerelease --preid rc --no-git-tag-version`
Prerelease version will be bumped or a new prerelease patch will be created:
- `2.19.1` -> `2.19.2-rc.0`
- `2.19.2-rc.0` -> `2.19.2-rc.1`
### Change version
You can edit version (and PR name of course) if you need to publish not a pre-release version or any other.
If the next update is planned to raise the minor version (`2.19.1` -> `2.20.0`), then change it before version update merge.
- `2.19.1` will be bumped to `2.19.2-rc.0` be default, change `2.19.2-rc.0` to `2.20.0-rc.0`
### Ignore update
If you do not need to upgrade and publish the update with the merged pull request (docs update or any other non-important changes),
you can close the pull request generated by the workflow.
## Example pipeline
Let's imagine that package version is `2.19.0` and you want to add some bug fixes and publish an update as `2.19.1`.
1. Merge a single update or a few pulls with fixes to the default branch `next`.
2. Workflow [bump-version-on-merge-next.yml](.github/workflows/bump-version-on-merge-next.yml) will bump the version up
to `2.19.1-rc.0` in the package.json and open a new pull request.
3. After bump version PR merge, the workflow [create-a-release-draft.yml](.github/workflows/create-a-release-draft.yml)
will automatically create a draft release on GitHub.
4. Check this new draft release on the releases page. Check tag `v2.19.1-rc.0` and notice "This is pre-release" checkbox
if it should be for a release candidate versions. Then publish that release.
5. [Workflow](.github/workflows/publish-package-to-npm.yml) will automatically push the package to NPM with tag `next`.
6. When you ready to publish a release, remove suffix from version name in package.json (`2.19.1-rc.0` -> `v2.19.1`)
in pull request from workflow [bump-version-on-merge-next.yml](.github/workflows/bump-version-on-merge-next.yml).
Follow steps 3-5 with workflows and publish a new version as `latest` update.
7. Merge branch `next` to `master` and save sources for history.

View file

@ -69,6 +69,12 @@ body {
padding: 0 15px;
}
.ce-example__content--with-bg {
background: #f4f4f4;
max-width: none;
margin-top: -30px;
}
.ce-example__output {
background: #1B202B;
overflow-x: auto;
@ -187,3 +193,16 @@ body {
.ce-example h4.ce-header {
font-size: 18px;
}
.ce-example-multiple {
display: grid;
grid-template-columns: calc(50% - 15px) calc(50% - 15px);
gap: 30px;
padding: 30px;
}
.ce-example-multiple > div {
background: #fff;
border-radius: 7px;
padding: 30px;
}

View file

@ -28,7 +28,10 @@
</div>
<div class="ce-example__content _ce-example__content--small">
<div id="editorjs"></div>
<div id="hint" style="text-align: center;">
<div id="hint-core" style="text-align: center;">
No core bundle file found. Run <code class="inline-code">yarn build</code>
</div>
<div id="hint-tools" style="text-align: center;">
No submodules found. Run <code class="inline-code">yarn pull_tools</code>
</div>
<div class="ce-example__button" id="saveButton">
@ -62,7 +65,7 @@
Read more in Tool's README file. For example:
https://github.com/editor-js/header#installation
-->
<script src="./tools/header/dist/bundle.js" onload="document.getElementById('hint').hidden = true"></script><!-- Header -->
<script src="./tools/header/dist/bundle.js" onload="document.getElementById('hint-tools').hidden = true"></script><!-- Header -->
<script src="./tools/simple-image/dist/bundle.js"></script><!-- Image -->
<script src="./tools/delimiter/dist/bundle.js"></script><!-- Delimiter -->
<script src="./tools/list/dist/bundle.js"></script><!-- List -->
@ -79,7 +82,7 @@
<script src="./tools/inline-code/dist/bundle.js"></script><!-- Inline Code -->
<!-- Load Editor.js's Core -->
<script src="../dist/editor.js"></script>
<script src="../dist/editor.js" onload="document.getElementById('hint-core').hidden = true;"></script>
<!-- Initialization -->
<script>

View file

@ -28,6 +28,9 @@
</div>
<div class="ce-example__content _ce-example__content--small">
<div id="editorjs"></div>
<div id="hint-core" style="text-align: center;">
No core bundle file found. Run <code class="inline-code">yarn build</code>
</div>
<div class="ce-example__button" id="saveButton">
editor.save()
@ -58,7 +61,7 @@
<script src="https://cdn.jsdelivr.net/npm/@editorjs/inline-code@latest"></script><!-- Inline Code -->
<!-- Load Editor.js's Core -->
<script src="../dist/editor.js"></script>
<script src="../dist/editor.js" onload="document.getElementById('hint-core').hidden = true"></script>
<!-- Initialization -->
<script>

View file

@ -0,0 +1,76 @@
<!--
Use this page for debugging purposes.
This page can be used for testing multiple editor instances on the same page.
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Editor.js 🤩🧦🤨 example: Multiple instances</title>
<link href="assets/demo.css" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
</head>
<body>
<div class="ce-example">
<div class="ce-example__header">
<a class="ce-example__header-logo" href="https://codex.so/editor">Editor.js 🤩🧦🤨</a>
<div class="ce-example__header-menu">
<a href="https://github.com/editor-js" target="_blank">Plugins</a>
<a href="https://editorjs.io/usage" target="_blank">Usage</a>
<a href="https://editorjs.io/configuration" target="_blank">Configuration</a>
<a href="https://editorjs.io/creating-a-block-tool" target="_blank">API</a>
</div>
</div>
<div class="ce-example__content ce-example__content--with-bg _ce-example__content--small">
<div id="hint-core" style="text-align: center; padding-top: 20px">
No core bundle file found. Run <code class="inline-code">yarn build</code>
</div>
<div class="ce-example-multiple">
<div id="editorjs1"></div>
<div id="editorjs2"></div>
</div>
</div>
<div class="ce-example__output">
<div class="ce-example__output-footer">
<a href="https://codex.so" style="font-weight: bold;">Made by CodeX</a>
</div>
</div>
</div>
<!-- Load Editor.js's Core -->
<script src="../dist/editor.js" onload="document.getElementById('hint-core').hidden = true"></script>
<script src="./tools/header/dist/bundle.js"></script><!-- Header -->
<!-- Initialization -->
<script>
/**
* Instance #1
*/
var editor1 = new EditorJS({
holder: 'editorjs1',
tools: {
header: {
class: Header,
shortcut: 'CMD+SHIFT+H'
}
}
});
/**
* Instance #2
*/
var editor2 = new EditorJS({
holder: 'editorjs2',
tools: {
header: {
class: Header,
shortcut: 'CMD+SHIFT+H'
}
}
});
</script>
</body>
</html>

View file

@ -27,7 +27,10 @@
</div>
<div class="ce-example__content _ce-example__content--small">
<div id="editorjs"></div>
<div id="hint" style="text-align: center;">
<div id="hint-core" style="text-align: center;">
No core bundle file found. Run <code class="inline-code">yarn build</code>
</div>
<div id="hint-tools" style="text-align: center;">
No submodules found. Run <code class="inline-code">yarn pull_tools</code>
</div>
<div class="ce-example__button" id="saveButton">
@ -50,7 +53,7 @@
Read more in Tool's README file. For example:
https://github.com/editor-js/header#installation
-->
<script src="./tools/header/dist/bundle.js" onload="document.getElementById('hint').hidden = true"></script><!-- Header -->
<script src="./tools/header/dist/bundle.js" onload="document.getElementById('hint-tools').hidden = true"></script><!-- Header -->
<script src="./tools/simple-image/dist/bundle.js"></script><!-- Image -->
<script src="./tools/delimiter/dist/bundle.js"></script><!-- Delimiter -->
<script src="./tools/list/dist/bundle.js"></script><!-- List -->
@ -67,7 +70,7 @@
<script src="./tools/inline-code/dist/bundle.js"></script><!-- Inline Code -->
<!-- Load Editor.js's Core -->
<script src="../dist/editor.js"></script>
<script src="../dist/editor.js" onload="document.getElementById('hint-core').hidden = true"></script>
<!-- Initialization -->
<script>

View file

@ -48,12 +48,12 @@
<!-- Load Tools -->
<!--
You can upload Tools to your project's directory and use as in example below.
You can upload Tools to your project's directory and connect them by relative links.
Also you can load each Tool from CDN or use NPM/Yarn packages.
Read more in Tool's README file. For example:
https://github.com/editor-js/header#installation
Read more at Tools Connection doc:
https://editorjs.io/getting-started#tools-connection
-->
<script src="https://cdn.jsdelivr.net/npm/@editorjs/header@latest"></script><!-- Header -->
<script src="https://cdn.jsdelivr.net/npm/@editorjs/simple-image@latest"></script><!-- Image -->
@ -71,7 +71,7 @@
<script src="https://cdn.jsdelivr.net/npm/@editorjs/inline-code@latest"></script><!-- Inline Code -->
<!-- Load Editor.js's Core -->
<script src="../dist/editor.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@editorjs/editorjs@latest"></script>
<!-- Initialization -->
<script>

View file

@ -1,6 +1,6 @@
{
"name": "@editorjs/editorjs",
"version": "2.19.1",
"version": "2.19.2",
"description": "Editor.js — Native JS, based on API and Open Source",
"main": "dist/editor.js",
"types": "./types/index.d.ts",
@ -12,15 +12,15 @@
"editorjs"
],
"scripts": {
"build": "rimraf dist/* && yarn svg && yarn build:prod",
"build:win": "rimraf dist && yarn svg:win && yarn build:prod",
"build:dev": "webpack --mode development --progress --display-error-details --display-entrypoints --watch",
"build:prod": "webpack --mode production",
"clear": "rimraf dist && mkdirp dist",
"build": "yarn clear && yarn svg && yarn build:webpack:prod",
"build:dev": "yarn clear && yarn svg && yarn build:webpack:dev",
"build:webpack:dev": "webpack --mode development --progress --display-error-details --display-entrypoints --watch",
"build:webpack:prod": "webpack --mode production",
"lint": "eslint src/ --ext .ts && yarn lint:tests",
"lint:errors": "eslint src/ --ext .ts --quiet",
"lint:fix": "eslint src/ --ext .ts --fix",
"lint:tests": "eslint test/ --ext .ts",
"svg:win": "if not exist dist md dist && yarn svg",
"svg": "svg-sprite-generate -d src/assets/ -o dist/sprite.svg",
"pull_tools": "git submodule update --init --recursive",
"checkout_tools": "git submodule foreach git pull origin master",
@ -56,6 +56,7 @@
"extract-text-webpack-plugin": "^3.0.2",
"html-janitor": "^2.0.4",
"license-webpack-plugin": "^2.1.4",
"mkdirp": "^1.0.4",
"postcss-apply": "^0.12.0",
"postcss-import": "^12.0.1",
"postcss-loader": "^3.0.0",

View file

@ -87,6 +87,7 @@ export default class EditorJS {
if (_.isFunction(moduleInstance.destroy)) {
moduleInstance.destroy();
}
moduleInstance.listeners.removeAll();
});
editor = null;

View file

@ -1,6 +1,8 @@
import { EditorModules } from '../types-internal/editor-modules';
import { EditorConfig } from '../../types';
import { ModuleConfig } from '../types-internal/module-config';
import Listeners from './utils/listeners';
import EventsDispatcher from './utils/events';
/**
* The type <T> of the Module generic.
@ -38,6 +40,16 @@ export default class Module<T extends ModuleNodes = {}> {
*/
protected config: EditorConfig;
/**
* Editor event dispatcher class
*/
protected eventsDispatcher: EventsDispatcher;
/**
* Util for bind/unbind DOM event listeners
*/
protected listeners: Listeners = new Listeners();
/**
* This object provides methods to push into set of listeners that being dropped when read-only mode is enabled
*/
@ -56,10 +68,8 @@ export default class Module<T extends ModuleNodes = {}> {
handler: (event: Event) => void,
options: boolean | AddEventListenerOptions = false
): void => {
const { Listeners } = this.Editor;
this.mutableListenerIds.push(
Listeners.on(element, eventType, handler, options)
this.listeners.on(element, eventType, handler, options)
);
},
@ -67,10 +77,8 @@ export default class Module<T extends ModuleNodes = {}> {
* Clears all mutable listeners
*/
clearAll: (): void => {
const { Listeners } = this.Editor;
for (const id of this.mutableListenerIds) {
Listeners.offById(id);
this.listeners.offById(id);
}
this.mutableListenerIds = [];
@ -84,14 +92,17 @@ export default class Module<T extends ModuleNodes = {}> {
/**
* @class
*
* @param {EditorConfig} config - Editor's config
* @param {EventsDispatcher} eventsDispatcher - Editor's event dispatcher
*/
constructor({ config }: ModuleConfig) {
constructor({ config, eventsDispatcher }: ModuleConfig) {
if (new.target === Module) {
throw new TypeError('Constructors for abstract class Module are not allowed.');
}
this.config = config;
this.eventsDispatcher = eventsDispatcher;
}
/**

View file

@ -1,9 +1,10 @@
import $ from './dom';
import * as _ from './utils';
import { EditorConfig, OutputData, SanitizerConfig } from '../../types';
import { EditorConfig, SanitizerConfig } from '../../types';
import { EditorModules } from '../types-internal/editor-modules';
import I18n from './i18n';
import { CriticalError } from './errors/critical';
import EventsDispatcher from './utils/events';
/**
* @typedef {Core} Core - editor core class
@ -53,6 +54,11 @@ export default class Core {
*/
public isReady: Promise<void>;
/**
* Event Dispatcher util
*/
private eventsDispatcher: EventsDispatcher = new EventsDispatcher();
/**
* @param {EditorConfig} config - user configuration
*
@ -116,11 +122,20 @@ export default class Core {
*/
public set configuration(config: EditorConfig|string) {
/**
* Process zero-configuration or with only holderId
* Make config object
* Place config into the class property
*
* @type {EditorConfig}
*/
if (!_.isObject(config)) {
config = {
if (_.isObject(config)) {
this.config = {
...config,
};
} else {
/**
* Process zero-configuration or with only holderId
* Make config object
*/
this.config = {
holder: config,
};
}
@ -128,19 +143,12 @@ export default class Core {
/**
* If holderId is preset, assign him to holder property and work next only with holder
*/
_.deprecationAssert(!!config.holderId, 'config.holderId', 'config.holder');
if (config.holderId && !config.holder) {
config.holder = config.holderId;
config.holderId = null;
_.deprecationAssert(!!this.config.holderId, 'config.holderId', 'config.holder');
if (this.config.holderId && !this.config.holder) {
this.config.holder = this.config.holderId;
this.config.holderId = null;
}
/**
* Place config into the class property
*
* @type {EditorConfig}
*/
this.config = config;
/**
* If holder is empty then set a default value
*/
@ -188,7 +196,7 @@ export default class Core {
this.config.hideToolbar = this.config.hideToolbar ? this.config.hideToolbar : false;
this.config.tools = this.config.tools || {};
this.config.i18n = this.config.i18n || {};
this.config.data = this.config.data || {} as OutputData;
this.config.data = this.config.data || { blocks: [] };
// eslint-disable-next-line @typescript-eslint/no-empty-function
this.config.onReady = this.config.onReady || ((): void => {});
// eslint-disable-next-line @typescript-eslint/no-empty-function
@ -198,13 +206,8 @@ export default class Core {
/**
* Initialize default Block to pass data to the Renderer
*/
if (_.isEmpty(this.config.data)) {
this.config.data = {} as OutputData;
this.config.data.blocks = [ defaultBlockData ];
} else {
if (!this.config.data.blocks || this.config.data.blocks.length === 0) {
this.config.data.blocks = [ defaultBlockData ];
}
if (_.isEmpty(this.config.data) || !this.config.data.blocks || this.config.data.blocks.length === 0) {
this.config.data = { blocks: [ defaultBlockData ] };
}
this.config.readOnly = this.config.readOnly as boolean || false;
@ -212,14 +215,14 @@ export default class Core {
/**
* Adjust i18n
*/
if (config.i18n?.messages) {
I18n.setDictionary(config.i18n.messages);
if (this.config.i18n?.messages) {
I18n.setDictionary(this.config.i18n.messages);
}
/**
* Text direction. If not set, uses ltr
*/
this.config.i18n.direction = config.i18n?.direction || 'ltr';
this.config.i18n.direction = this.config.i18n?.direction || 'ltr';
}
/**
@ -251,7 +254,7 @@ export default class Core {
}
if (holder && _.isObject(holder) && !$.isElement(holder)) {
throw Error('holder as HTMLElement if provided must be inherit from Element class.');
throw Error('«holder» value must be an Element node');
}
}
@ -341,6 +344,7 @@ export default class Core {
*/
this.moduleInstances[Module.displayName] = new Module({
config: this.configuration,
eventsDispatcher: this.eventsDispatcher,
});
} catch (e) {
_.log(`Module ${Module.displayName} skipped because`, 'warn', e);

View file

@ -291,6 +291,8 @@ export default class LinkInlineTool implements InlineTool {
this.unlink();
event.preventDefault();
this.closeActions();
return;
}
if (!this.validateURL(value)) {

View file

@ -1,5 +1,5 @@
import { Events } from '../../../../types/api';
import Module from '../../__module';
import { Events } from '../../../../types/api';
/**
* @class EventsAPI
@ -26,7 +26,7 @@ export default class EventsAPI extends Module {
* @param {Function} callback - event handler
*/
public on(eventName, callback): void {
this.Editor.Events.on(eventName, callback);
this.eventsDispatcher.on(eventName, callback);
}
/**
@ -36,7 +36,7 @@ export default class EventsAPI extends Module {
* @param {object} data - event's data
*/
public emit(eventName, data): void {
this.Editor.Events.emit(eventName, data);
this.eventsDispatcher.emit(eventName, data);
}
/**
@ -46,6 +46,6 @@ export default class EventsAPI extends Module {
* @param {Function} callback - event handler
*/
public off(eventName, callback): void {
this.Editor.Events.off(eventName, callback);
this.eventsDispatcher.off(eventName, callback);
}
}

View file

@ -27,7 +27,7 @@ export default class ListenersAPI extends Module {
* @param {boolean} useCapture - capture event or not
*/
public on(element: HTMLElement, eventType: string, handler: () => void, useCapture?: boolean): void {
this.Editor.Listeners.on(element, eventType, handler, useCapture);
this.listeners.on(element, eventType, handler, useCapture);
}
/**
@ -39,6 +39,6 @@ export default class ListenersAPI extends Module {
* @param {boolean} useCapture - capture event or not
*/
public off(element: Element, eventType: string, handler: () => void, useCapture?: boolean): void {
this.Editor.Listeners.off(element, eventType, handler, useCapture);
this.listeners.off(element, eventType, handler, useCapture);
}
}

View file

@ -1,6 +1,6 @@
import { Toolbar } from '../../../../types/api';
import Module from '../../__module';
import * as _ from './../../utils';
/**
* @class ToolbarAPI
* Provides methods for working with the Toolbar
@ -15,6 +15,7 @@ export default class ToolbarAPI extends Module {
return {
close: (): void => this.close(),
open: (): void => this.open(),
toggleBlockSettings: (openingState?: boolean): void => this.toggleBlockSettings(openingState),
};
}
@ -31,4 +32,35 @@ export default class ToolbarAPI extends Module {
public close(): void {
this.Editor.Toolbar.close();
}
/**
* Toggles Block Setting of the current block
*
* @param {boolean} openingState opening state of Block Setting
*/
public toggleBlockSettings(openingState?: boolean): void {
if (this.Editor.BlockManager.currentBlockIndex === -1) {
_.logLabeled('Could\'t toggle the Toolbar because there is no block selected ', 'warn');
return;
}
/** Check that opening state is set or not */
const canOpenBlockSettings = openingState ?? !this.Editor.BlockSettings.opened;
/** Check if state same as current state */
if (openingState === this.Editor.BlockSettings.opened) {
return;
}
if (canOpenBlockSettings) {
if (!this.Editor.Toolbar.opened) {
this.Editor.Toolbar.open(true, false);
this.Editor.Toolbar.plusButton.hide();
}
this.Editor.BlockSettings.open();
} else {
this.Editor.BlockSettings.close();
}
}
}

View file

@ -167,7 +167,7 @@ export default class BlockEvents extends Module {
*
* @param {ClipboardEvent} event - clipboard event
*/
public handleCommandC(event: ClipboardEvent): void {
public handleCommandC(event: ClipboardEvent): Promise<void> {
const { BlockSelection } = this.Editor;
if (!BlockSelection.anyBlockSelected) {
@ -175,7 +175,7 @@ export default class BlockEvents extends Module {
}
// Copy Selected Blocks
BlockSelection.copySelectedBlocks(event);
return BlockSelection.copySelectedBlocks(event);
}
/**
@ -183,14 +183,14 @@ export default class BlockEvents extends Module {
*
* @param {ClipboardEvent} event - clipboard event
*/
public handleCommandX(event: ClipboardEvent): void {
public async handleCommandX(event: ClipboardEvent): Promise<void> {
const { BlockSelection, BlockManager, Caret } = this.Editor;
if (!BlockSelection.anyBlockSelected) {
return;
}
BlockSelection.copySelectedBlocks(event);
await BlockSelection.copySelectedBlocks(event);
const selectionPositionIndex = BlockManager.removeSelectedBlocks();

View file

@ -184,7 +184,7 @@ export default class BlockManager extends Module {
});
/** Copy event */
this.Editor.Listeners.on(
this.listeners.on(
document,
'copy',
(e: ClipboardEvent) => this.Editor.BlockEvents.handleCommandC(e)
@ -559,11 +559,10 @@ export default class BlockManager extends Module {
* 1) Find first-level Block from passed child Node
* 2) Mark it as current
*
* @param {Node} childNode - look ahead from this node.
*
* @throws Error - when passed Node is not included at the Block
* @param {Node} childNode - look ahead from this node.
* @returns {Block | undefined} can return undefined in case when the passed child note is not a part of the current editor instance
*/
public setCurrentBlockByChildNode(childNode: Node): Block {
public setCurrentBlockByChildNode(childNode: Node): Block | undefined {
/**
* If node is Text TextNode
*/
@ -573,23 +572,36 @@ export default class BlockManager extends Module {
const parentFirstLevelBlock = (childNode as HTMLElement).closest(`.${Block.CSS.wrapper}`);
if (parentFirstLevelBlock) {
/**
* Update current Block's index
*
* @type {number}
*/
this.currentBlockIndex = this._blocks.nodes.indexOf(parentFirstLevelBlock as HTMLElement);
/**
* Update current block active input
*/
this.currentBlock.updateCurrentInput();
return this.currentBlock;
} else {
throw new Error('Can not find a Block from this child Node');
if (!parentFirstLevelBlock) {
return;
}
/**
* Support multiple Editor.js instances,
* by checking whether the found block belongs to the current instance
*
* @see {@link Ui#documentTouched}
*/
const editorWrapper = parentFirstLevelBlock.closest(`.${this.Editor.UI.CSS.editorWrapper}`);
const isBlockBelongsToCurrentInstance = editorWrapper?.isEqualNode(this.Editor.UI.nodes.wrapper);
if (!isBlockBelongsToCurrentInstance) {
return;
}
/**
* Update current Block's index
*
* @type {number}
*/
this.currentBlockIndex = this._blocks.nodes.indexOf(parentFirstLevelBlock as HTMLElement);
/**
* Update current block active input
*/
this.currentBlock.updateCurrentInput();
return this.currentBlock;
}
/**
@ -707,7 +719,7 @@ export default class BlockManager extends Module {
this.readOnlyMutableListeners.on(block.holder, 'keydown', (event: KeyboardEvent) => {
BlockEvents.keydown(event);
}, true);
});
this.readOnlyMutableListeners.on(block.holder, 'keyup', (event: KeyboardEvent) => {
BlockEvents.keyup(event);

View file

@ -9,6 +9,7 @@ import Module from '../__module';
import Block from '../block';
import * as _ from '../utils';
import $ from '../dom';
import Shortcuts from '../utils/shortcuts';
import SelectionUtils from '../selection';
import { SanitizerConfig } from '../../../types/configs';
@ -145,8 +146,6 @@ export default class BlockSelection extends Module {
* to select all and copy them
*/
public prepare(): void {
const { Shortcuts } = this.Editor;
this.selection = new SelectionUtils();
/**
@ -181,6 +180,7 @@ export default class BlockSelection extends Module {
this.handleCommandA(event);
},
on: this.Editor.UI.nodes.redactor,
});
}
@ -361,10 +361,8 @@ export default class BlockSelection extends Module {
* De-registers Shortcut CMD+A
*/
public destroy(): void {
const { Shortcuts } = this.Editor;
/** Selection shortcut */
Shortcuts.remove('CMD+A');
Shortcuts.remove(this.Editor.UI.nodes.redactor, 'CMD+A');
}
/**

View file

@ -388,11 +388,9 @@ export default class Caret extends Module {
* Before moving caret, we should check if caret position is at the end of Plugins node
* Using {@link Dom#getDeepestNode} to get a last node and match with current selection
*
* @param {boolean} force - force navigation even if caret is not at the end
*
* @returns {boolean}
*/
public navigateNext(force = false): boolean {
public navigateNext(): boolean {
const { BlockManager, Tools } = this.Editor;
const { currentBlock, nextContentfulBlock } = BlockManager;
const { nextInput } = currentBlock;
@ -422,7 +420,7 @@ export default class Caret extends Module {
nextBlock = BlockManager.insertAtEnd();
}
if (force || isAtEnd) {
if (isAtEnd) {
/** If next Tool`s input exists, focus on it. Otherwise set caret to the next Block */
if (!nextInput) {
this.setToBlock(nextBlock, this.positions.START);
@ -441,11 +439,9 @@ export default class Caret extends Module {
* Before moving caret, we should check if caret position is start of the Plugins node
* Using {@link Dom#getDeepestNode} to get a last node and match with current selection
*
* @param {boolean} force - force navigation even if caret is not at the start
*
* @returns {boolean}
*/
public navigatePrevious(force = false): boolean {
public navigatePrevious(): boolean {
const { currentBlock, previousContentfulBlock } = this.Editor.BlockManager;
if (!currentBlock) {
@ -458,7 +454,7 @@ export default class Caret extends Module {
return false;
}
if (force || this.isAtStart) {
if (this.isAtStart) {
/** If previous Tool`s input exists, focus on it. Otherwise set caret to the previous Block */
if (!previousInput) {
this.setToBlock(previousContentfulBlock, this.positions.END);

View file

@ -23,9 +23,7 @@ export default class CrossBlockSelection extends Module {
* @returns {Promise}
*/
public async prepare(): Promise<void> {
const { Listeners } = this.Editor;
Listeners.on(document, 'mousedown', (event: MouseEvent) => {
this.listeners.on(document, 'mousedown', (event: MouseEvent) => {
this.enableCrossBlockSelection(event);
});
}
@ -40,13 +38,13 @@ export default class CrossBlockSelection extends Module {
return;
}
const { BlockManager, Listeners } = this.Editor;
const { BlockManager } = this.Editor;
this.firstSelectedBlock = BlockManager.getBlock(event.target as HTMLElement);
this.lastSelectedBlock = this.firstSelectedBlock;
Listeners.on(document, 'mouseover', this.onMouseOver);
Listeners.on(document, 'mouseup', this.onMouseUp);
this.listeners.on(document, 'mouseover', this.onMouseOver);
this.listeners.on(document, 'mouseup', this.onMouseUp);
}
/**
@ -98,6 +96,10 @@ export default class CrossBlockSelection extends Module {
/** close InlineToolbar when Blocks selected */
this.Editor.InlineToolbar.close();
nextBlock.holder.scrollIntoView({
block: 'nearest',
});
}
/**
@ -172,10 +174,8 @@ export default class CrossBlockSelection extends Module {
* Removes the listeners
*/
private onMouseUp = (): void => {
const { Listeners } = this.Editor;
Listeners.off(document, 'mouseover', this.onMouseOver);
Listeners.off(document, 'mouseup', this.onMouseUp);
this.listeners.off(document, 'mouseover', this.onMouseOver);
this.listeners.off(document, 'mouseup', this.onMouseUp);
}
/**

View file

@ -87,16 +87,16 @@ export default class DragNDrop extends Module {
/**
* Try to set current block by drop target.
* If drop target (error will be thrown) is not part of the Block, set last Block as current.
* If drop target is not part of the Block, set last Block as current.
*/
try {
const targetBlock = BlockManager.setCurrentBlockByChildNode(dropEvent.target as Node);
const targetBlock = BlockManager.setCurrentBlockByChildNode(dropEvent.target as Node);
if (targetBlock) {
this.Editor.Caret.setToBlock(targetBlock, Caret.positions.END);
} catch (e) {
const targetBlock = BlockManager.setCurrentBlockByChildNode(BlockManager.lastBlock.holder);
} else {
const lastBlock = BlockManager.setCurrentBlockByChildNode(BlockManager.lastBlock.holder);
this.Editor.Caret.setToBlock(targetBlock, Caret.positions.END);
this.Editor.Caret.setToBlock(lastBlock, Caret.positions.END);
}
await Paste.processDataTransfer(dropEvent.dataTransfer, true);

View file

@ -58,7 +58,7 @@ export default class ModificationsObserver extends Module {
this.observer.disconnect();
}
this.observer = null;
this.nativeInputs.forEach((input) => this.Editor.Listeners.off(input, 'input', this.mutationDebouncer));
this.nativeInputs.forEach((input) => this.listeners.off(input, 'input', this.mutationDebouncer));
this.mutationDebouncer = null;
}
@ -163,13 +163,13 @@ export default class ModificationsObserver extends Module {
private updateNativeInputs(): void {
if (this.nativeInputs) {
this.nativeInputs.forEach((input) => {
this.Editor.Listeners.off(input, 'input');
this.listeners.off(input, 'input');
});
}
this.nativeInputs = Array.from(this.Editor.UI.nodes.redactor.querySelectorAll('textarea, input, select'));
this.nativeInputs.forEach((input) => this.Editor.Listeners.on(input, 'input', this.mutationDebouncer));
this.nativeInputs.forEach((input) => this.listeners.on(input, 'input', this.mutationDebouncer));
}
/**

View file

@ -263,18 +263,14 @@ export default class Paste extends Module {
* Set onPaste callback handler
*/
private setCallback(): void {
const { Listeners } = this.Editor;
Listeners.on(this.Editor.UI.nodes.holder, 'paste', this.handlePasteEvent);
this.listeners.on(this.Editor.UI.nodes.holder, 'paste', this.handlePasteEvent);
}
/**
* Unset onPaste callback handler
*/
private unsetCallback(): void {
const { Listeners } = this.Editor;
Listeners.off(this.Editor.UI.nodes.holder, 'paste', this.handlePasteEvent);
this.listeners.off(this.Editor.UI.nodes.holder, 'paste', this.handlePasteEvent);
}
/**
@ -760,7 +756,7 @@ export default class Paste extends Module {
* @returns {void}
*/
private insertEditorJSData(blocks: Array<Pick<SavedData, 'data' | 'tool'>>): void {
const { BlockManager, Sanitizer, Tools } = this.Editor;
const { BlockManager, Caret, Sanitizer, Tools } = this.Editor;
const sanitizedBlocks = Sanitizer.sanitizeBlocks(blocks);
sanitizedBlocks.forEach(({ tool, data }, i) => {
@ -772,11 +768,13 @@ export default class Paste extends Module {
needToReplaceCurrentBlock = isCurrentBlockDefault && BlockManager.currentBlock.isEmpty;
}
BlockManager.insert({
const block = BlockManager.insert({
tool,
data,
replace: needToReplaceCurrentBlock,
});
Caret.setToBlock(block, Caret.positions.END);
});
}

View file

@ -179,26 +179,25 @@ export default class RectangleSelection extends Module {
* Sets Module necessary event handlers
*/
private enableModuleBindings(): void {
const { Listeners } = this.Editor;
const { container } = this.genHTML();
Listeners.on(container, 'mousedown', (mouseEvent: MouseEvent) => {
this.listeners.on(container, 'mousedown', (mouseEvent: MouseEvent) => {
this.processMouseDown(mouseEvent);
}, false);
Listeners.on(document.body, 'mousemove', (mouseEvent: MouseEvent) => {
this.listeners.on(document.body, 'mousemove', (mouseEvent: MouseEvent) => {
this.processMouseMove(mouseEvent);
}, false);
Listeners.on(document.body, 'mouseleave', () => {
this.listeners.on(document.body, 'mouseleave', () => {
this.processMouseLeave();
});
Listeners.on(window, 'scroll', (mouseEvent: MouseEvent) => {
this.listeners.on(window, 'scroll', (mouseEvent: MouseEvent) => {
this.processScroll(mouseEvent);
}, false);
Listeners.on(document.body, 'mouseup', () => {
this.listeners.on(document.body, 'mouseup', () => {
this.processMouseUp();
}, false);
}

View file

@ -1,74 +0,0 @@
import Shortcut from '@codexteam/shortcuts';
/**
* Contains keyboard and mouse events binded on each Block by Block Manager
*/
import Module from '../__module';
/**
* ShortcutData interface
* Each shortcut must have name and handler
* `name` is a shortcut, like 'CMD+K', 'CMD+B' etc
* `handler` is a callback
*
* @interface ShortcutData
*/
export interface ShortcutData {
/**
* Shortcut name
* Ex. CMD+I, CMD+B ....
*/
name: string;
/**
* Shortcut handler
*/
handler(event): void;
}
/**
* @class Shortcut
* @classdesc Allows to register new shortcut
*
* Internal Shortcuts Module
*/
export default class Shortcuts extends Module {
/**
* All registered shortcuts
*
* @type {Shortcut[]}
*/
private registeredShortcuts: Shortcut[] = [];
/**
* Register shortcut
*
* @param {ShortcutData} shortcut - shortcut options
*/
public add(shortcut: ShortcutData): void {
const newShortcut = new Shortcut({
name: shortcut.name,
on: this.Editor.UI.nodes.redactor,
callback: shortcut.handler,
});
this.registeredShortcuts.push(newShortcut);
}
/**
* Remove shortcut
*
* @param {string} shortcut - shortcut name
*/
public remove(shortcut: string): void {
const index = this.registeredShortcuts.findIndex((shc) => shc.name === shortcut);
if (index === -1 || !this.registeredShortcuts[index]) {
return;
}
this.registeredShortcuts[index].remove();
this.registeredShortcuts.splice(index, 1);
}
}

View file

@ -30,7 +30,7 @@ export default class BlockSettings extends Module<BlockSettingsNodes> {
*
* @returns {{opened: string, closed: string}}
*/
public get events(): {opened: string; closed: string} {
public get events(): { opened: string; closed: string } {
return {
opened: 'block-settings-opened',
closed: 'block-settings-closed',
@ -42,7 +42,7 @@ export default class BlockSettings extends Module<BlockSettingsNodes> {
*
* @returns {{wrapper, wrapperOpened, toolSettings, defaultSettings, button}}
*/
public get CSS(): {[name: string]: string} {
public get CSS(): { [name: string]: string } {
return {
// Settings Panel
wrapper: 'ce-settings',
@ -147,7 +147,7 @@ export default class BlockSettings extends Module<BlockSettingsNodes> {
this.addDefaultSettings();
/** Tell to subscribers that block settings is opened */
this.Editor.Events.emit(this.events.opened);
this.eventsDispatcher.emit(this.events.opened);
this.flipper.activate(this.blockTunesButtons);
}
@ -171,12 +171,19 @@ export default class BlockSettings extends Module<BlockSettingsNodes> {
this.selection.clearSaved();
/**
* Remove highlighted content of a Block we are working with
*/
if (!this.Editor.CrossBlockSelection.isCrossBlockSelectionStarted && this.Editor.BlockManager.currentBlock) {
this.Editor.BlockManager.currentBlock.selected = false;
}
/** Clear settings */
this.nodes.toolSettings.innerHTML = '';
this.nodes.defaultSettings.innerHTML = '';
/** Tell to subscribers that block settings is closed */
this.Editor.Events.emit(this.events.closed);
this.eventsDispatcher.emit(this.events.closed);
/** Clear cached buttons */
this.buttons = [];

View file

@ -325,7 +325,7 @@ export default class ConversionToolbar extends Module<ConversionToolbarNodes> {
$.append(this.nodes.tools, tool);
this.tools[toolName] = tool;
this.Editor.Listeners.on(tool, 'click', async () => {
this.listeners.on(tool, 'click', async () => {
await this.replaceWithBlock(toolName);
});
}

View file

@ -6,6 +6,8 @@ import { InlineTool, InlineToolConstructable, ToolConstructable, ToolSettings }
import Flipper from '../../flipper';
import I18n from '../../i18n';
import { I18nInternalNS } from '../../i18n/namespace-internal';
import Shortcuts from '../../utils/shortcuts';
import { EditorModules } from '../../../types-internal/editor-modules';
/**
* Inline Toolbar elements
@ -87,6 +89,38 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
*/
private flipper: Flipper = null;
/**
* Internal inline tools: Link, Bold, Italic
*/
private internalTools: {[name: string]: InlineToolConstructable} = {};
/**
* Editor modules setter
*
* @param {EditorModules} Editor - Editor's Modules
*/
public set state(Editor: EditorModules) {
this.Editor = Editor;
const { Tools } = Editor;
/**
* Set internal inline tools
*/
Object
.entries(Tools.internalTools)
.filter(([, toolClass]: [string, ToolConstructable | ToolSettings]) => {
if (_.isFunction(toolClass)) {
return toolClass[Tools.INTERNAL_SETTINGS.IS_INLINE];
}
return (toolClass as ToolSettings).class[Tools.INTERNAL_SETTINGS.IS_INLINE];
})
.map(([name, toolClass]: [string, InlineToolConstructable | ToolSettings]) => {
this.internalTools[name] = _.isFunction(toolClass) ? toolClass : (toolClass as ToolSettings).class;
});
}
/**
* Toggles read-only mode
*
@ -185,7 +219,13 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
}
this.nodes.wrapper.classList.remove(this.CSS.inlineToolbarShowed);
this.toolsInstances.forEach((toolInstance) => {
Array.from(this.toolsInstances.entries()).forEach(([name, toolInstance]) => {
const shortcut = this.getToolShortcut(name);
if (shortcut) {
Shortcuts.remove(this.Editor.UI.nodes.redactor, shortcut);
}
/**
* @todo replace 'clear' with 'destroy'
*/
@ -357,7 +397,7 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
this.nodes.actions = $.make('div', this.CSS.actionsWrapper);
// To prevent reset of a selection when click on the wrapper
this.Editor.Listeners.on(this.nodes.wrapper, 'mousedown', (event) => {
this.listeners.on(this.nodes.wrapper, 'mousedown', (event) => {
const isClickedOnActionsWrapper = (event.target as Element).closest(`.${this.CSS.actionsWrapper}`);
// If click is on actions wrapper,
@ -479,7 +519,7 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
this.nodes.togglerAndButtonsWrapper.appendChild(this.nodes.conversionToggler);
this.Editor.Listeners.on(this.nodes.conversionToggler, 'click', () => {
this.listeners.on(this.nodes.conversionToggler, 'click', () => {
this.Editor.ConversionToolbar.toggle((conversionToolbarOpened) => {
/**
* When ConversionToolbar is opening on activated InlineToolbar flipper
@ -594,7 +634,6 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
*/
private addTool(toolName: string, tool: InlineTool): void {
const {
Listeners,
Tools,
Tooltip,
} = this.Editor;
@ -617,45 +656,12 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
this.nodes.actions.appendChild(actions);
}
Listeners.on(button, 'click', (event) => {
this.listeners.on(button, 'click', (event) => {
this.toolClicked(tool);
event.preventDefault();
});
/**
* Enable shortcuts
* Ignore tool that doesn't have shortcut or empty string
*/
const toolSettings = Tools.getToolSettings(toolName);
let shortcut = null;
/**
* Get internal inline tools
*/
const internalTools: string[] = Object
.entries(Tools.internalTools)
.filter(([, toolClass]: [string, ToolConstructable | ToolSettings]) => {
if (_.isFunction(toolClass)) {
return toolClass[Tools.INTERNAL_SETTINGS.IS_INLINE];
}
return (toolClass as ToolSettings).class[Tools.INTERNAL_SETTINGS.IS_INLINE];
})
.map(([ name ]: [string, InlineToolConstructable | ToolSettings]) => name);
/**
* 1) For internal tools, check public getter 'shortcut'
* 2) For external tools, check tool's settings
* 3) If shortcut is not set in settings, check Tool's public property
*/
if (internalTools.includes(toolName)) {
shortcut = this.inlineTools[toolName][Tools.INTERNAL_SETTINGS.SHORTCUT];
} else if (toolSettings && toolSettings[Tools.USER_SETTINGS.SHORTCUT]) {
shortcut = toolSettings[Tools.USER_SETTINGS.SHORTCUT];
} else if (tool.shortcut) {
shortcut = tool.shortcut;
}
const shortcut = this.getToolShortcut(toolName);
if (shortcut) {
this.enableShortcuts(tool, shortcut);
@ -684,6 +690,35 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
});
}
/**
* Get shortcut name for tool
*
* @param toolName Tool name
*/
private getToolShortcut(toolName): string | void {
const { Tools } = this.Editor;
/**
* Enable shortcuts
* Ignore tool that doesn't have shortcut or empty string
*/
const toolSettings = Tools.getToolSettings(toolName);
const tool = this.toolsInstances.get(toolName);
/**
* 1) For internal tools, check public getter 'shortcut'
* 2) For external tools, check tool's settings
* 3) If shortcut is not set in settings, check Tool's public property
*/
if (Object.keys(this.internalTools).includes(toolName)) {
return this.inlineTools[toolName][Tools.INTERNAL_SETTINGS.SHORTCUT];
} else if (toolSettings && toolSettings[Tools.USER_SETTINGS.SHORTCUT]) {
return toolSettings[Tools.USER_SETTINGS.SHORTCUT];
} else if (tool.shortcut) {
return tool.shortcut;
}
}
/**
* Enable Tool shortcut with Editor Shortcuts Module
*
@ -691,7 +726,7 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
* @param {string} shortcut - shortcut according to the ShortcutData Module format
*/
private enableShortcuts(tool: InlineTool, shortcut: string): void {
this.Editor.Shortcuts.add({
Shortcuts.add({
name: shortcut,
handler: (event) => {
const { currentBlock } = this.Editor.BlockManager;
@ -719,6 +754,7 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
event.preventDefault();
this.toolClicked(tool);
},
on: this.Editor.UI.nodes.redactor,
});
}

View file

@ -6,6 +6,7 @@ import Flipper from '../../flipper';
import { BlockToolAPI } from '../../block';
import I18n from '../../i18n';
import { I18nInternalNS } from '../../i18n/namespace-internal';
import Shortcuts from '../../utils/shortcuts';
/**
* HTMLElements used for Toolbox UI
@ -225,7 +226,7 @@ export default class Toolbox extends Module<ToolboxNodes> {
/**
* Add click listener
*/
this.Editor.Listeners.on(button, 'click', (event: KeyboardEvent|MouseEvent) => {
this.listeners.on(button, 'click', (event: KeyboardEvent|MouseEvent) => {
this.toolButtonActivate(event, toolName);
});
@ -306,12 +307,13 @@ export default class Toolbox extends Module<ToolboxNodes> {
* @param {string} shortcut - shortcut according to the ShortcutData Module format
*/
private enableShortcut(tool: BlockToolConstructable, toolName: string, shortcut: string): void {
this.Editor.Shortcuts.add({
Shortcuts.add({
name: shortcut,
handler: (event: KeyboardEvent) => {
event.preventDefault();
this.insertNewBlock(tool, toolName);
},
on: this.Editor.UI.nodes.redactor,
});
}
@ -327,7 +329,7 @@ export default class Toolbox extends Module<ToolboxNodes> {
const shortcut = this.getToolShortcut(toolName, tools[toolName]);
if (shortcut) {
this.Editor.Shortcuts.remove(shortcut);
Shortcuts.remove(this.Editor.UI.nodes.redactor, shortcut);
}
}
}

View file

@ -14,6 +14,8 @@ import BoldInlineTool from '../inline-tools/inline-tool-bold';
import ItalicInlineTool from '../inline-tools/inline-tool-italic';
import LinkInlineTool from '../inline-tools/inline-tool-link';
import Stub from '../tools/stub';
import { ModuleConfig } from '../../types-internal/module-config';
import EventsDispatcher from '../utils/events';
/**
* @module Editor.js Tools Submodule
@ -198,9 +200,13 @@ export default class Tools extends Module {
* @class
*
* @param {EditorConfig} config - Editor's configuration
* @param {EventsDispatcher} eventsDispatcher - Editor's event dispatcher
*/
constructor({ config }) {
super({ config });
constructor({ config, eventsDispatcher }: ModuleConfig) {
super({
config,
eventsDispatcher,
});
this.toolsClasses = {};

View file

@ -5,7 +5,6 @@ import Module from '../__module';
* Use external module CodeX Tooltip
*/
import CodeXTooltips, { TooltipContent, TooltipOptions } from 'codex-tooltip';
import { ModuleConfig } from '../../types-internal/module-config';
/**
* Tooltip
@ -20,14 +19,6 @@ export default class Tooltip extends Module {
*/
private lib: CodeXTooltips = new CodeXTooltips();
/**
* @class
* @param {EditorConfig} - Editor's config
*/
constructor({ config }: ModuleConfig) {
super({ config });
}
/**
* Shows tooltip on element with passed HTML content
*

View file

@ -1,5 +1,5 @@
/**
* TextRange interface fot IE9-
* TextRange interface for IE9-
*/
import * as _ from './utils';
import $ from './dom';

View file

@ -1,19 +1,17 @@
import Module from '../__module';
/**
* @module eventDispatcher
* @class EventDispatcher
*
* Has two important methods:
* - {Function} on - appends subscriber to the event. If event doesn't exist - creates new one
* - {Function} emit - fires all subscribers with data
* - {Function off - unsubsribes callback
* - {Function off - unsubscribes callback
*
* @version 1.0.0
*
* @typedef {Events} Events
* @property {object} subscribers - all subscribers grouped by event name
*/
export default class Events extends Module {
export default class EventsDispatcher {
/**
* Object with events` names as key and array of callback functions as value
*

View file

@ -1,4 +1,3 @@
import Module from '../__module';
import * as _ from '../utils';
/**
@ -36,11 +35,9 @@ export interface ListenerData {
}
/**
* Editor.js Listeners module
* Editor.js Listeners helper
*
* @module Listeners
*
* Module-decorator for event listeners assignment
* Decorator for event listeners assignment
*
* @author Codex Team
* @version 2.0.0
@ -50,7 +47,7 @@ export interface ListenerData {
* @typedef {Listeners} Listeners
* @property {ListenerData[]} allListeners - listeners store
*/
export default class Listeners extends Module {
export default class Listeners {
/**
* Stores all listeners data to find/remove/process it
*
@ -114,7 +111,7 @@ export default class Listeners extends Module {
existingListeners.forEach((listener, i) => {
const index = this.allListeners.indexOf(existingListeners[i]);
if (index > 0) {
if (index > -1) {
this.allListeners.splice(index, 1);
listener.element.removeEventListener(listener.eventType, listener.handler, listener.options);

View file

@ -0,0 +1,107 @@
import Shortcut from '@codexteam/shortcuts';
/**
* Contains keyboard and mouse events binded on each Block by Block Manager
*/
/**
* ShortcutData interface
* Each shortcut must have name and handler
* `name` is a shortcut, like 'CMD+K', 'CMD+B' etc
* `handler` is a callback
*
* @interface ShortcutData
*/
export interface ShortcutData {
/**
* Shortcut name
* Ex. CMD+I, CMD+B ....
*/
name: string;
/**
* Shortcut handler
*/
handler(event): void;
/**
* Element handler should be added for
*/
on: HTMLElement;
}
/**
* @class Shortcut
* @classdesc Allows to register new shortcut
*
* Internal Shortcuts Module
*/
class Shortcuts {
/**
* All registered shortcuts
*
* @type {Map<Element, Shortcut[]>}
*/
private registeredShortcuts: Map<Element, Shortcut[]> = new Map();
/**
* Register shortcut
*
* @param shortcut - shortcut options
*/
public add(shortcut: ShortcutData): void {
const foundShortcut = this.findShortcut(shortcut.on, shortcut.name);
if (foundShortcut) {
throw Error(
`Shortcut ${shortcut.name} is already registered for ${shortcut.on}. Please remove it before add a new handler.`
);
}
const newShortcut = new Shortcut({
name: shortcut.name,
on: shortcut.on,
callback: shortcut.handler,
});
const shortcuts = this.registeredShortcuts.get(shortcut.on) || [];
this.registeredShortcuts.set(shortcut.on, [...shortcuts, newShortcut]);
}
/**
* Remove shortcut
*
* @param element - Element shortcut is set for
* @param name - shortcut name
*/
public remove(element: Element, name: string): void {
const shortcut = this.findShortcut(element, name);
if (!shortcut) {
return;
}
shortcut.remove();
const shortcuts = this.registeredShortcuts.get(element);
this.registeredShortcuts.set(element, shortcuts.filter(el => el !== shortcut));
}
/**
* Get Shortcut instance if exist
*
* @param element - Element shorcut is set for
* @param shortcut - shortcut name
*
* @returns {number} index - shortcut index if exist
*/
private findShortcut(element: Element, shortcut: string): Shortcut | void {
const shortcuts = this.registeredShortcuts.get(element) || [];
return shortcuts.find(({ name }) => name === shortcut);
}
}
export default new Shortcuts();

View file

@ -1,12 +1,9 @@
import UI from '../components/modules/ui';
import BlockEvents from '../components/modules/blockEvents';
import Listeners from '../components/modules/listeners';
import Toolbar from '../components/modules/toolbar/index';
import InlineToolbar from '../components/modules/toolbar/inline';
import Toolbox from '../components/modules/toolbar/toolbox';
import BlockSettings from '../components/modules/toolbar/blockSettings';
import Events from '../components/modules/events';
import Shortcuts from '../components/modules/shortcuts';
import Paste from '../components/modules/paste';
import Notifier from '../components/modules/notifier';
import Tooltip from '../components/modules/tooltip';
@ -44,14 +41,11 @@ export interface EditorModules {
BlockEvents: BlockEvents;
BlockSelection: BlockSelection;
RectangleSelection: RectangleSelection;
Listeners: Listeners;
Toolbar: Toolbar;
InlineToolbar: InlineToolbar;
Toolbox: Toolbox;
BlockSettings: BlockSettings;
ConversionToolbar: ConversionToolbar;
Events: Events;
Shortcuts: Shortcuts;
Paste: Paste;
DragNDrop: DragNDrop;
ModificationsObserver: ModificationsObserver;

View file

@ -3,9 +3,15 @@
* After that we can use it at the TS modules
*/
declare module 'html-janitor' {
/**
* Sanitizer config of each HTML element
* @see {@link https://github.com/guardian/html-janitor#options}
*/
type TagConfig = boolean | { [attr: string]: boolean | string };
interface Config {
tags: {
[key: string]: boolean|{[attr: string]: boolean|string}|(() => any)
[key: string]: TagConfig | ((el: Element) => TagConfig)
};
}

View file

@ -1,8 +1,10 @@
import {EditorConfig} from '../../types/index';
import { EditorConfig } from '../../types/index';
import EventsDispatcher from '../components/utils/events';
/**
* Describes object passed to Editor modules constructor
*/
export interface ModuleConfig {
config: EditorConfig;
eventsDispatcher: EventsDispatcher;
}

View file

@ -17,14 +17,12 @@ describe('Editor basic initialization', () => {
});
it('should create a visible UI', () => {
cy.window().then((window) => {
/**
* Assert if created instance is visible or not.
*/
cy.get('[data-cy=editorjs]')
.get('div.codex-editor')
.should('be.visible');
});
/**
* Assert if created instance is visible or not.
*/
cy.get('[data-cy=editorjs]')
.get('div.codex-editor')
.should('be.visible');
});
});
});

82
test/testcases.md Normal file
View file

@ -0,0 +1,82 @@
# Editor.js specs
This document will describe various test cases of the editor.js functionality. Features will be organized by modules. Cases covered by tests should be marked by the checkmark.
## Configuration
- [ ] Zero configuration
- [ ] Editor.js should be initialized on the element with the default `editorjs` id.
- [ ] Editor.js should throw an error in case when there is no element with `editorjs` id.
- [ ] Editor.js should be initialized with the Paragraph tool only.
- [ ] The Inline Toolbar of the Paragraph tool should contain all default Inline Tools - `bold`, `italic`, `link`.
- [ ] `holder` property
- [ ] Editor.js should be initialized on the element with passed via `holder` property.
- [ ] Editor.js should throw an error if passed `holder` value is not an Element node.
- [ ] `autofocus` property
- [ ] With the empty editor
- [ ] If `true` passed, the caret should be placed to the first empty block.
- [ ] If `false` passed, the caret shouldn't be placed anywhere.
- [ ] If omitted, the caret shouldn't be placed anywhere.
- [ ] With the not-empty editor
- [ ] If `true` passed, the caret should be placed to the end of the last block.
- [ ] If `false` passed, the caret shouldn't be placed anywhere.
- [ ] If omitted, the caret shouldn't be placed anywhere.
- [ ] `placeholder` property
- [ ] With the empty editor
- [ ] If `string` passed, the string should be placed as a placeholder to the first empty block only.
- [ ] If `false` passed, the first empty block should be placed without a placeholder.
- [ ] If omitted, the first empty block should be placed without a placeholder.
- [ ] `minHeight` property
- [ ] If `number` passed, the height of the editor's bottom area from the last Block should be the `number`.
- [ ] If omitted the height of editor's bottom area from the last Block should be the default `300`.
- [ ] `logLevel` property
- [ ] If `VERBOSE` passed, the editor should output all messages to the console.
- [ ] If `INFO` passed, the editor should output info and debug messages to the console.
- [ ] If `WARN` passed, the editor should output only warning messages to the console.
- [ ] If `ERROR` passed, the editor should output only error messages to the console.
- [ ] If omitted, the editor should output all messages to the console.
- [ ] `defaultBlock` property
- [ ] If `string` passed
- [ ] If passed `string` in the `tools` option, the passed tool should be used as the default tool.
- [ ] If passed `string` not in the `tools` option, the Paragraph tool should be used as the default tool.
- [ ] If omitted the Paragraph tool should be used as default tool.
- [ ] `sanitizer` property
- [ ] If `object` passed
- [ ] The Editor.js should clean the HTML tags according to mentioned configuration.
- [ ] If omitted the Editor.js should be initialized with the default `sanitizer` configuration, which allows the tags like `paragraph`, `anchor`, and `bold` for cleaning HTML.
- [ ] `tools` property
- [ ] If omitted
- [ ] Editor.js should be initialized with the Paragraph tool only.
- [ ] If `object` passed
- [ ] Editor.js should be initialized with all the passed tools.
- [ ] The keys of the object should be represented as `type` fields for corresponded blocks in output JSON
- [ ] If value is a JavaScript class
- [ ] This class should be used as a tool
- [ ] If value is an `object`
- [ ] Checking the `class` property
- [ ] If omitted, the tool should be skipped with a warning in a console.
- [ ] If existed, the value of the `class` property should be used as a tool
- [ ] Checking the `config` property
- [ ] If `object` passed Editor.js should initialize `tool` and pass this object as `config` parameter of the tool's constructor
- [ ] Checking the `shortcut` property
- [ ] If `string` passed Editor.js should append the `tool` when such keys combination executed.
- [ ] Checking the `inilineToolbar` property
- [ ] If `true`
- [ ] Editor.js should show the Inline Toolbar for this tool with [common](https://editorjs.io/configuration#inline-toolbar-order) settings.
- [ ] If `false`
- [ ] Editor.js should not show the Inline Toolbar for this tool.
- [ ] If `array`
- [ ] Editor.js should show the Inline Toolbar for this tool with a passed list of tools and their order.
- [ ] If omitted
- [ ] Editor.js should not show the Inline Toolbar for this tool.
- [ ] Checking the `toolbox` property
- [ ] If it contains `title`, this title should be used as a tool title
- [ ] If it contains `icon`, this HTML code (maybe SVG) should be used as a tool icon

View file

@ -11,4 +11,10 @@ export interface Toolbar {
* Opens Toolbar
*/
open(): void;
/**
* Toggles Block Setting of the current block
* @param {boolean} openingState opening state of Block Setting
*/
toggleBlockSettings(openingState?: boolean): void;
}

View file

@ -11,7 +11,7 @@ export interface EditorConfig {
holderId?: string | HTMLElement;
/**
* Element where Editor will be append
* Element where Editor will be appended
*/
holder?: string | HTMLElement;

View file

@ -1,3 +1,9 @@
/**
* Sanitizer config of each HTML element
* @see {@link https://github.com/guardian/html-janitor#options}
*/
type TagConfig = boolean | { [attr: string]: boolean | string };
export interface SanitizerConfig {
/**
* Tag name and params not to be stripped off
@ -31,5 +37,5 @@ export interface SanitizerConfig {
* }
* }
*/
[key: string]: boolean|{[attr: string]: boolean|string}|(() => any);
[key: string]: TagConfig | ((el: Element) => TagConfig);
}

View file

@ -2,16 +2,19 @@ import {BlockToolData} from '../tools';
/**
* Output of one Tool
*
* @template Type - the string literal describing a tool type
* @template Data - the structure describing a data object supported by the tool
*/
export interface OutputBlockData {
export interface OutputBlockData<Type extends string = string, Data extends object = any> {
/**
* Too type
* Tool type
*/
type: string;
type: Type;
/**
* Saved Block data
*/
data: BlockToolData;
data: BlockToolData<Data>;
}
export interface OutputData {

2
types/index.d.ts vendored
View file

@ -104,8 +104,6 @@ declare class EditorJS {
public blocks: Blocks;
public caret: Caret;
public events: Events;
public listeners: Listeners;
public sanitizer: Sanitizer;
public saver: Saver;
public selection: Selection;

View file

@ -18,8 +18,10 @@ export interface ToolboxConfig {
/**
* Object passed to the Tool's constructor by {@link EditorConfig#tools}
*
* @template Config - the structure describing a config object supported by the tool
*/
export interface ToolSettings {
export interface ToolSettings <Config extends object = any> {
/**
* Tool's class
@ -29,7 +31,7 @@ export interface ToolSettings {
/**
* User configuration object that will be passed to the Tool's constructor
*/
config?: ToolConfig;
config?: ToolConfig<Config>;
/**
* Is need to show Inline Toolbar.

View file

@ -3620,8 +3620,9 @@ inherits@2.0.3:
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
ini@^1.3.4, ini@^1.3.5:
version "1.3.5"
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
version "1.3.8"
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
inquirer@^7.0.0:
version "7.1.0"
@ -4564,6 +4565,11 @@ mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.4, mkdirp@~0.5.1:
dependencies:
minimist "^1.2.5"
mkdirp@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
moment@^2.27.0:
version "2.29.1"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"