Compare commits
258 commits
Author | SHA1 | Date | |
---|---|---|---|
29d68ecb47 | |||
d18eeb5dc8 | |||
50f43bb35d | |||
f78972ee09 | |||
bd1de56ef3 | |||
8276daa5ca | |||
238c909016 | |||
23858e0025 | |||
5eafda5ec4 | |||
efa0a34f8e | |||
c48fca1be3 | |||
1028577521 | |||
844272656e | |||
7821e35302 | |||
4118dc3aea | |||
e1c70b4fb8 | |||
54c4c234a5 | |||
5125f015dc | |||
ecdd73347c | |||
1320b047a2 | |||
b355f1673c | |||
bb2047c60f | |||
cc0d6de04b | |||
9b3e9615b0 | |||
ee6433201d | |||
e9b4c30407 | |||
8138ce95b2 | |||
7ff5faa46f | |||
b619946e8f | |||
9542551d84 | |||
4bdf7a12b7 | |||
c5ddf91189 | |||
11925ca980 | |||
cd29c52e51 | |||
ea2be754e7 | |||
5515c3b534 | |||
6385ab2a39 | |||
c5854eea14 | |||
348c1c7752 | |||
8726f8bdfa | |||
494d6e4b9b | |||
531ab06f63 | |||
5e8fe06dd6 | |||
af6b64a3e6 | |||
92ccfc5317 | |||
ee188bfe5d | |||
35337747c5 | |||
d936bf08b7 | |||
e8acc59487 | |||
bdb6eed9d1 | |||
b8cfcaec26 | |||
7d0db9b622 | |||
ca2bc803a1 | |||
c9014e670d | |||
ec569f9981 | |||
77eb320203 | |||
0369ff5827 | |||
59c8d28da5 | |||
f8e9414217 | |||
42612a0fd9 | |||
be0d33ce0f | |||
922dfd8741 | |||
b39996616c | |||
0e64665b0f | |||
022320940e | |||
41dc65274d | |||
b24bebbc40 | |||
2ab9eb13bc | |||
9e64003c0a | |||
c8993332e3 | |||
39018e07e4 | |||
1f74c5bec9 | |||
0d6c8bd4af | |||
26dce94e67 | |||
e6c265d0fa | |||
b7b00fd060 | |||
aafab1d395 | |||
0491155e33 | |||
707ff72a94 | |||
f5313fda40 | |||
d7f1853ca1 | |||
75379c66a9 | |||
c18011595a | |||
b7d754c93d | |||
5257b061c4 | |||
293135a795 | |||
e9e0146961 | |||
599d332aef | |||
839daf556f | |||
7cf5266a23 | |||
86c84d8555 | |||
153fc48bd6 | |||
447f980d4a | |||
9ae96b9e17 | |||
7bd5c53ab2 | |||
58d6869285 | |||
dfb07ec4e4 | |||
34d4b028c4 | |||
d4edbe162b | |||
07b1ce2aca | |||
551e3f10b9 | |||
d6cad60af3 | |||
24baf08c89 | |||
b4e2429476 | |||
870e265af0 | |||
c97f0842f5 | |||
b9a0665672 | |||
c18134daeb | |||
913ad0c8dc | |||
320a30afcc | |||
509023e307 | |||
d56c07c3dc | |||
1a72d2153b | |||
04118b2d6e | |||
73c9bdf40b | |||
a8026e6829 | |||
0bf5007f6c | |||
6088aaead6 | |||
cdaf10a58f | |||
e048c4d46b | |||
fb86d71828 | |||
811a41366b | |||
0d0f9e8de2 | |||
9b7da504e4 | |||
a1071b7229 | |||
b1b582b150 | |||
f0f8f8fcb5 | |||
3272efc3f7 | |||
19f3790cfd | |||
f659015be6 | |||
cd06bfc9c5 | |||
581289c03e | |||
39c4114b97 | |||
f7368edee3 | |||
6c0555a322 | |||
c1d7744b8d | |||
8fec2e71c3 | |||
f2b19cc766 | |||
07ea1ef035 | |||
3cbe128af9 | |||
6a15cc55c0 | |||
8ae8823dcd | |||
96c0bcb573 | |||
8bf4dcde9f | |||
25f7ec2456 | |||
13b8e11a21 | |||
0e30d8781e | |||
06900968ac | |||
ee28354cb4 | |||
1b84f66219 | |||
6cd6bd5de3 | |||
8f156a87ea | |||
f9e280fcad | |||
18feb06a88 | |||
32dcd3fa6d | |||
771437ed04 | |||
82e8ff9f40 | |||
640b1a2d7e | |||
c1bca10d12 | |||
27aa9164a1 | |||
1188b40959 | |||
817beff551 | |||
e3db19df84 | |||
95141f0a2d | |||
40c05173a9 | |||
ff91466b14 | |||
acdd1f5b4e | |||
9c0fc48d89 | |||
4f15bbc0cb | |||
1433c1a7dc | |||
70a736bffa | |||
a3df3b8ace | |||
65b86c9203 | |||
03fb54b474 | |||
145ece4149 | |||
d2d18f8061 | |||
9190824b1d | |||
6f36707f67 | |||
cf494a7a28 | |||
9e25400bf8 | |||
ce1d394f44 | |||
041470d171 | |||
a4ffb1a7fc | |||
4e7b33c2b8 | |||
51d94e1a11 | |||
92b023d566 | |||
9d8998e9e7 | |||
231d122949 | |||
de364175eb | |||
ef0c7d76a7 | |||
6ac58c3935 | |||
472d950b0b | |||
2996affb0a | |||
04749ed098 | |||
32ac52a62f | |||
2c77edb457 | |||
3b20c4a139 | |||
d5217067a6 | |||
14acef136c | |||
d5aaa56ca0 | |||
aa5a3d05f3 | |||
9cdda110da | |||
a88dc8e30b | |||
d02c3e3679 | |||
63e438a72d | |||
2d89105670 | |||
4cfcb656a8 | |||
51a1b48abb | |||
d27427e2da | |||
79793906a4 | |||
166ded6afe | |||
1e1b06d537 | |||
d945d4c518 | |||
c4ebdee396 | |||
2f5da52d1a | |||
2a2a6f11ad | |||
0d559a5571 | |||
a67e699991 | |||
2759b25d35 | |||
3bf30f0c1e | |||
e5fe93eeb5 | |||
1772edf912 | |||
970d64efda | |||
ae52d6116c | |||
8104585854 | |||
a229cc5b82 | |||
254a94e812 | |||
cd6378c160 | |||
c4a57c16a9 | |||
8e4b34a6e1 | |||
5cbeabd1cb | |||
3f39cb735d | |||
527ff4e6d1 | |||
aa8675d61d | |||
143a539e01 | |||
21adb61bc9 | |||
51b1fdc9e0 | |||
50cbc4d63e | |||
156a687f40 | |||
4ddba9208d | |||
78478ace2b | |||
4c7cad1b41 | |||
fbd4bd9e0a | |||
c106819184 | |||
7c8778acc4 | |||
c0ff646e8e | |||
83d8fe7df5 | |||
36d5a6751f | |||
c1f73e723f | |||
59a303438c | |||
bbbd637dfd | |||
585e01b06c | |||
f440a60ead | |||
e319e04350 | |||
bfd0ea10ea | |||
113e895d20 | |||
b134fccd1d | |||
e477621c3b |
14
.babelrc
|
@ -1,14 +0,0 @@
|
|||
{
|
||||
"presets": [
|
||||
["@babel/preset-env", {
|
||||
"modules": "umd",
|
||||
"useBuiltIns": "entry",
|
||||
"corejs": 3
|
||||
}]
|
||||
],
|
||||
"plugins": [
|
||||
"babel-plugin-add-module-exports",
|
||||
"babel-plugin-class-display-name",
|
||||
"@babel/plugin-transform-runtime"
|
||||
]
|
||||
}
|
19
.eslintrc
|
@ -1,19 +1,7 @@
|
|||
{
|
||||
"extends": [
|
||||
"codex"
|
||||
"codex/ts"
|
||||
],
|
||||
"rules": {
|
||||
/**
|
||||
* Temporary suppress some errors. We need to fix them partially in next patches
|
||||
*/
|
||||
"import/no-duplicates": ["warn"],
|
||||
"@typescript-eslint/triple-slash-reference": ["off"]
|
||||
},
|
||||
"settings": {
|
||||
"jsdoc": {
|
||||
"mode": "typescript"
|
||||
}
|
||||
},
|
||||
"globals": {
|
||||
"Node": true,
|
||||
"Range": true,
|
||||
|
@ -42,6 +30,9 @@
|
|||
"DOMRect": true,
|
||||
"ClientRect": true,
|
||||
"ArrayLike": true,
|
||||
"unknown": true
|
||||
"InputEvent": true,
|
||||
"unknown": true,
|
||||
"requestAnimationFrame": true,
|
||||
"navigator": true
|
||||
}
|
||||
}
|
||||
|
|
2
.github/FUNDING.yml
vendored
|
@ -1,5 +1,5 @@
|
|||
# These are supported funding model platforms
|
||||
|
||||
github: neSpecc
|
||||
patreon: editorjs
|
||||
open_collective: editorjs
|
||||
custom: https://codex.so/donate
|
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve Editor.js
|
||||
title: "[Bug]"
|
||||
title: ""
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
|
|
8
.github/ISSUE_TEMPLATE/config.yml
vendored
|
@ -3,9 +3,9 @@ contact_links:
|
|||
- name: Team
|
||||
url: mailto:team@codex.so
|
||||
about: Direct team contact.
|
||||
- name: 💬 Discussions
|
||||
url: https://github.com/codex-team/editor.js/discussions
|
||||
about: Use discussions if you have an issue draft, an idea for improvement or for asking questions.
|
||||
- name: Editor.js Telegram chat
|
||||
url: https://t.me/codex_editor
|
||||
about: Telegram chat for Editor.js users communication.
|
||||
- name: Editor.js contributors Telegram chat
|
||||
url: https://t.me/editorjsdev
|
||||
about: Telegram chat for Editor.js contributors communication.
|
||||
about: Telegram chat for Editor.js users communication.
|
19
.github/ISSUE_TEMPLATE/feature_request.md
vendored
|
@ -1,19 +0,0 @@
|
|||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea to improve Editor.js
|
||||
title: "\U0001F4A1"
|
||||
labels: feature
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
1. Describe a problem.
|
||||
|
||||
2. Describe the solution you'd like. Mockups are welcome.
|
||||
|
||||
3. Are there any alternatives?
|
||||
|
||||
<!--
|
||||
🤫 If you like Editor.js, please consider supporting us via OpenCollective:
|
||||
https://opencollective.com/editorjs
|
||||
-->
|
|
@ -1,8 +1,8 @@
|
|||
---
|
||||
name: Discussion
|
||||
about: Any question about the Editor.js to discuss
|
||||
name: General Issue
|
||||
about: Well-designed, algorithmized feature/idea/improvement issue for Editor.js
|
||||
title: ''
|
||||
labels: discussion
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
24
.github/ISSUE_TEMPLATE/issue--discussion.md
vendored
|
@ -1,24 +0,0 @@
|
|||
---
|
||||
name: 'Issue: Discussion'
|
||||
about: Any question about the project to discuss
|
||||
title: "❓"
|
||||
labels: discussion
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Question**
|
||||
|
||||
A clear and consistent question about the project. Ex. How can I do smth? Why smth works this way? etc.
|
||||
|
||||
**Context**
|
||||
|
||||
Why and how the question has come up
|
||||
|
||||
**Related issues**
|
||||
|
||||
If there are related issues which describe a bugs or features, put them here
|
||||
|
||||
**Comments**
|
||||
|
||||
Any thoughts about the question
|
91
.github/workflows/bump-version-on-merge-next.yml
vendored
Normal file
|
@ -0,0 +1,91 @@
|
|||
name: Bump version on merge
|
||||
|
||||
# Caution:
|
||||
# the use of "pull_request_target" trigger allows to successfully
|
||||
# run workflow even when triggered from a fork. The trigger grants
|
||||
# access to repo's secrets and gives write permission to the runner.
|
||||
# This can be used to run malicious code on untrusted PR, so, please
|
||||
# DO NOT checkout any PR's ongoing commits (aka github.event.pull_request.head.sha)
|
||||
# while using this trigger.
|
||||
on:
|
||||
pull_request_target:
|
||||
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
|
||||
permissions:
|
||||
actions: write
|
||||
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 if version was changed already
|
||||
if: steps.packageOld.outputs.version != steps.packageNew.outputs.version
|
||||
run: |
|
||||
curl -L \
|
||||
-X POST \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
https://api.github.com/repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/cancel
|
||||
|
||||
bump-version:
|
||||
needs: check-for-no-version-changing
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
# Checkout to target branch
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
# Setup node environment
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
# 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 }}
|
135
.github/workflows/create-a-release-draft.yml
vendored
Normal file
|
@ -0,0 +1,135 @@
|
|||
name: Create a release draft
|
||||
|
||||
# Caution:
|
||||
# the use of "pull_request_target" trigger allows to successfully
|
||||
# run workflow even when triggered from a fork. The trigger grants
|
||||
# access to repo's secrets and gives write permission to the runner.
|
||||
# This can be used to run malicious code on untrusted PR, so, please
|
||||
# DO NOT checkout any PR's ongoing commits (aka github.event.pull_request.head.sha)
|
||||
# while using this trigger.
|
||||
on:
|
||||
pull_request_target:
|
||||
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
|
||||
permissions:
|
||||
actions: write
|
||||
steps:
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
# 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
|
||||
if: steps.packageOld.outputs.version == steps.packageNew.outputs.version
|
||||
run: |
|
||||
curl -L \
|
||||
-X POST \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
https://api.github.com/repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/cancel
|
||||
|
||||
# Create a new draft release
|
||||
release-draft:
|
||||
needs: check-version-changing
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
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: 16
|
||||
|
||||
# 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 UMD 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/editorjs.umd.js
|
||||
asset_name: editorjs.umd.js
|
||||
asset_content_type: application/javascript
|
||||
|
||||
# Build and upload target Editor.js MJS 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/editorjs.mjs
|
||||
asset_name: editorjs.mjs
|
||||
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
|
21
.github/workflows/cypress.yml
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
name: Cypress
|
||||
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
run-tests:
|
||||
strategy:
|
||||
matrix:
|
||||
browser: [firefox, chrome, edge]
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
- uses: actions/checkout@v4
|
||||
- uses: cypress-io/github-action@v6
|
||||
with:
|
||||
config: video=false
|
||||
browser: ${{ matrix.browser }}
|
||||
build: yarn build:test
|
3
.github/workflows/eslint.yml
vendored
|
@ -19,6 +19,5 @@ jobs:
|
|||
${{ runner.OS }}-build-
|
||||
${{ runner.OS }}-
|
||||
|
||||
- run: yarn install
|
||||
|
||||
- run: yarn
|
||||
- run: yarn lint
|
||||
|
|
65
.github/workflows/publish-package-to-npm.yml
vendored
Normal file
|
@ -0,0 +1,65 @@
|
|||
name: Publish package to NPM
|
||||
|
||||
on:
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# Checkout to target branch
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
# Pull submodules
|
||||
submodules: 'recursive'
|
||||
|
||||
- name: Get package info
|
||||
id: package
|
||||
uses: codex-team/action-nodejs-package-info@v1
|
||||
|
||||
# Setup node environment
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 16
|
||||
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 with a NEXT tag
|
||||
run: yarn publish --access=public --tag=next
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
- name: Add LATEST tag for the published package if this is not a prerelease version
|
||||
if: github.event.release.prerelease != true
|
||||
run: npm dist-tag add ${{ steps.package.outputs.name }}@${{ steps.package.outputs.version }} latest
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
notify:
|
||||
needs: publish
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
GITHUB_LINK: ${{ github.server_url }}/${{ github.repository }}/releases/tag/${{ github.ref_name }}
|
||||
steps:
|
||||
# Checkout to target branch
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- 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.version }}](${{ env.GITHUB_LINK }}) was published'
|
||||
parse_mode: 'markdown'
|
||||
disable_web_page_preview: true
|
6
.gitignore
vendored
|
@ -12,3 +12,9 @@ yarn-error.log
|
|||
|
||||
test/cypress/screenshots
|
||||
test/cypress/videos
|
||||
|
||||
dist/
|
||||
|
||||
coverage/
|
||||
.nyc_output/
|
||||
.vscode/launch.json
|
||||
|
|
9
.gitmodules
vendored
|
@ -16,9 +16,6 @@
|
|||
[submodule "example/tools/simple-image"]
|
||||
path = example/tools/simple-image
|
||||
url = https://github.com/editor-js/simple-image
|
||||
[submodule "src/components/tools/paragraph"]
|
||||
path = src/components/tools/paragraph
|
||||
url = https://github.com/editor-js/paragraph
|
||||
[submodule "example/tools/marker"]
|
||||
path = example/tools/marker
|
||||
url = https://github.com/editor-js/marker
|
||||
|
@ -49,3 +46,9 @@
|
|||
[submodule "example/tools/underline"]
|
||||
path = example/tools/underline
|
||||
url = https://github.com/editor-js/underline
|
||||
[submodule "example/tools/nested-list"]
|
||||
path = example/tools/nested-list
|
||||
url = https://github.com/editor-js/nested-list
|
||||
[submodule "example/tools/text-variant-tune"]
|
||||
path = example/tools/text-variant-tune
|
||||
url = https://github.com/editor-js/text-variant-tune
|
||||
|
|
25
.npmignore
|
@ -1,19 +1,6 @@
|
|||
.idea/
|
||||
build/sprite.svg
|
||||
docs/
|
||||
example/
|
||||
src/
|
||||
.babelrc
|
||||
.editorconfig
|
||||
.eslintrc
|
||||
.git
|
||||
.gitmodules
|
||||
.jshintrc
|
||||
.postcssrc
|
||||
.stylelintrc
|
||||
CODEOWNERS
|
||||
tsconfig.json
|
||||
tslint.json
|
||||
webpack.config.js
|
||||
yarn.lock
|
||||
.github
|
||||
*
|
||||
!/dist/**/*
|
||||
!/types/**/*
|
||||
!/LICENSE
|
||||
!/README.md
|
||||
!/package.json
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
plugins:
|
||||
# Consumes files by @import rule
|
||||
# https://github.com/postcss/postcss-import
|
||||
postcss-import: {}
|
||||
|
||||
# Apply custom property sets via @apply rule
|
||||
# https://github.com/pascalduez/postcss-apply
|
||||
postcss-apply: {}
|
||||
|
@ -26,16 +22,6 @@ plugins:
|
|||
# https://github.com/csstools/postcss-preset-env#preserve
|
||||
preserve: false
|
||||
|
||||
# Enable or disable specific polyfills
|
||||
# https://github.com/csstools/postcss-preset-env#features
|
||||
#
|
||||
# List of available plugins
|
||||
# https://github.com/csstools/postcss-preset-env/blob/master/src/lib/plugins-by-id.js
|
||||
features:
|
||||
# Modify colors using the color-mod() function in CSS
|
||||
# https://github.com/jonathantneal/postcss-color-mod-function
|
||||
color-mod-function: {}
|
||||
|
||||
# Nested rules unwrapper
|
||||
# https://github.com/postcss/postcss-nested
|
||||
#
|
||||
|
@ -43,7 +29,3 @@ plugins:
|
|||
# 'postcss-nesting' feature but it does not work with BEM
|
||||
# Report: https://github.com/csstools/postcss-preset-env/issues/40
|
||||
postcss-nested: {}
|
||||
|
||||
# Compression tool
|
||||
# https://github.com/cssnano/cssnano
|
||||
cssnano: {}
|
||||
|
|
41
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"cSpell.words": [
|
||||
"autofocused",
|
||||
"Behaviour",
|
||||
"cacheable",
|
||||
"childs",
|
||||
"codexteam",
|
||||
"colspan",
|
||||
"contenteditable",
|
||||
"contentless",
|
||||
"Convertable",
|
||||
"cssnano",
|
||||
"cssnext",
|
||||
"Debouncer",
|
||||
"devserver",
|
||||
"editorjs",
|
||||
"entrypoints",
|
||||
"Flippable",
|
||||
"GRAMMARLY",
|
||||
"hsablonniere",
|
||||
"intellij",
|
||||
"keydown",
|
||||
"keydowns",
|
||||
"Kilian",
|
||||
"mergeable",
|
||||
"movetostart",
|
||||
"nofollow",
|
||||
"opencollective",
|
||||
"preconfigured",
|
||||
"resetors",
|
||||
"rowspan",
|
||||
"selectall",
|
||||
"sometool",
|
||||
"stylelint",
|
||||
"textareas",
|
||||
"twitterwidget",
|
||||
"typeof",
|
||||
"Unmergeable",
|
||||
"viewports"
|
||||
]
|
||||
}
|
|
@ -1 +1,2 @@
|
|||
* @neSpecc @gohabereg @khaydarov
|
||||
* @neSpecc @gohabereg @TatianaFomina @ilyamore88
|
||||
|
||||
|
|
404
README.md
|
@ -1,253 +1,241 @@
|
|||
<a href="https://editorjs.io/"><p align="center"><img src="https://capella.pics/79ce946a-d636-41cd-aa96-d3bc5ecfde03.jpg"></p></a>
|
||||
<p align="center">
|
||||
<a href="https://editorjs.io/">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./assets/logo_night.png">
|
||||
<source media="(prefers-color-scheme: light)" srcset="./assets/logo_day.png">
|
||||
<img alt="Editor.js Logo" src="./assets/logo_day.png">
|
||||
</picture>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
[![](https://flat.badgen.net/npm/v/@editorjs/editorjs?icon=npm)](https://www.npmjs.com/package/@editorjs/editorjs)
|
||||
[![](https://flat.badgen.net/bundlephobia/min/@editorjs/editorjs?color=cyan)](https://www.npmjs.com/package/@editorjs/editorjs)
|
||||
[![](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)
|
||||
[![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)
|
||||
<p align="center">
|
||||
<a href="https://editorjs.io/">editorjs.io</a> |
|
||||
<a href="https://editorjs.io/base-concepts/">documentation</a> |
|
||||
<a href="https://github.com/codex-team/editor.js/blob/next/docs/CHANGELOG.md">changelog</a>
|
||||
|
||||
</p>
|
||||
|
||||
| [<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 |
|
||||
| --------- | --------- | --------- | --------- | --------- | --------- |
|
||||
| Edge 12+ | Firefox 18+ | Chrome 49+ | Safari 10+ | Safari 10+ | Opera 36+
|
||||
<p align="center">
|
||||
<a href="https://www.npmjs.com/package/@editorjs/editorjs">
|
||||
<img src="https://flat.badgen.net/npm/v/@editorjs/editorjs?icon=npm" alt="npm"/>
|
||||
</a>
|
||||
<a href="https://www.npmjs.com/package/@editorjs/editorjs">
|
||||
<img src="https://flat.badgen.net/bundlephobia/minzip/@editorjs/editorjs?color=green" alt="Minzipped size"/>
|
||||
</a>
|
||||
<a href="https://github.com/codex-team/editor.js#backers">
|
||||
<img src="https://opencollective.com/editorjs/backers/badge.svg" alt="Backers on Open Collective"/>
|
||||
</a>
|
||||
<a href="https://github.com/codex-team/editor.js#sponsors">
|
||||
<img src="https://opencollective.com/editorjs/sponsors/badge.svg" alt="Sponsors on Open Collective"/>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
## If you like a project 💗💗💗
|
||||
## About
|
||||
|
||||
If you like Editor.js you can support project improvements and development of new features with a donation to our collective.
|
||||
Editor.js is an open-source text editor offering a variety of features to help users create and format content efficiently. It has a modern, block-style interface that allows users to easily add and arrange different types of content, such as text, images, lists, quotes, etc. Each Block is provided via a separate plugin making Editor.js extremely flexible.
|
||||
|
||||
👉 [https://opencollective.com/editorjs](https://opencollective.com/editorjs)
|
||||
Editor.js outputs a clean JSON data instead of heavy HTML markup. Use it in Web, iOS, Android, AMP, Instant Articles, speech readers, AI chatbots — everywhere. Easy to sanitize, extend and integrate with your logic.
|
||||
|
||||
### Sponsors
|
||||
- 😍 Modern UI out of the box
|
||||
- 💎 Clean JSON output
|
||||
- ⚙️ Well-designed API
|
||||
- 🛍 Various Tools available
|
||||
- 💌 Free and open source
|
||||
|
||||
Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/editorjs#sponsor)]
|
||||
<picture>
|
||||
<img alt="Editor.js Overview" src="./assets/overview.png">
|
||||
</picture>
|
||||
|
||||
<a href="https://opencollective.com/editorjs/sponsor/0/website" target="_blank"><img src="https://opencollective.com/editorjs/sponsor/0/avatar.svg"></a>
|
||||
## Installation
|
||||
|
||||
### Backers
|
||||
It's quite simple:
|
||||
|
||||
Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/editorjs#backer)]
|
||||
1. Install Editor.js
|
||||
2. Install tools you need
|
||||
3. Initialize Editor's instance
|
||||
|
||||
<a href="https://opencollective.com/editorjs#backers" target="_blank"><img src="https://opencollective.com/editorjs/backers.svg?width=890"></a>
|
||||
Install using NPM, Yarn, or [CDN](https://www.jsdelivr.com/package/npm/@editorjs/editorjs):
|
||||
|
||||
### Contributors
|
||||
|
||||
This project exists thanks to all the people who contribute. <img src="https://opencollective.com/editorjs/contributors.svg?width=890&button=false" />
|
||||
|
||||
We really welcome new contributors. If you want to make some code with us, please take a look at the [Good First Tasks](https://github.com/codex-team/editor.js/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+task%22). You can write to us on `team@codex.so` or via special [Telegram chat](https://t.me/editorjsdev), or any other way.
|
||||
|
||||
## Documentation
|
||||
|
||||
Please visit [https://editorjs.io/](https://editorjs.io) to view all documentation articles.
|
||||
|
||||
- [Base concepts](https://editorjs.io/base-concepts)
|
||||
- [Getting started](https://editorjs.io/getting-started)
|
||||
- [Configuration](https://editorjs.io/configuration)
|
||||
- [How to create a Block Tool Plugin](https://editorjs.io/creating-a-block-tool)
|
||||
- [How to create an Inline Tool Plugin](https://editorjs.io/creating-an-inline-tool)
|
||||
- [API for Tools](https://editorjs.io/tools-api)
|
||||
|
||||
You can join a [Gitter-channel](https://gitter.im/codex-team/editor.js) or [Telegram-chat](//t.me/codex_editor) and ask a question.
|
||||
|
||||
## Changelog
|
||||
|
||||
See the whole [Changelog](/docs/CHANGELOG.md)
|
||||
|
||||
## How to use Editor.js
|
||||
|
||||
### Basics
|
||||
|
||||
Editor.js is a Block-Styled editor. Blocks are structural units, of which the Entry is composed.
|
||||
For example, `Paragraph`, `Heading`, `Image`, `Video`, `List` are Blocks. Each Block is represented by Plugin.
|
||||
We have [many](http://github.com/editor-js/) ready-to-use Plugins and a [simple API](https://editorjs.io/tools-api) for creating new ones.
|
||||
|
||||
How to use the Editor after [Installation](https://editorjs.io/getting-started).
|
||||
|
||||
- Create new Blocks by pressing Enter or clicking the Plus Button
|
||||
- Press `TAB` or click on the Plus Button to view the Toolbox
|
||||
- Press `TAB` again to leaf Toolbox and select a Block you need. Then press Enter.
|
||||
|
||||
|
||||
![](https://github.com/editor-js/list/raw/master/assets/example.gif)
|
||||
|
||||
- Select a text fragment and apply a style or insert a link from the Inline Toolbar
|
||||
|
||||
![](https://capella.pics/7ccbcfcd-1c49-4674-bea7-71021468a1bd.jpg)
|
||||
|
||||
- Use the «three-dots» button on the right to open Block Settings. From here, you can move and delete a Block
|
||||
or apply a Tool's settings, if it provided. For example, you can set a Heading level or List style.
|
||||
|
||||
![](https://capella.pics/01a55381-46cd-47c7-b92e-34765434f2ca.jpg)
|
||||
|
||||
### Shortcuts
|
||||
|
||||
A few shortcuts are preset as available.
|
||||
|
||||
Shortcut | Action | Restrictions
|
||||
-- | -- | --
|
||||
`TAB` | Show/leaf a Toolbox. | On empty block
|
||||
`SHIFT+TAB` | Leaf back a Toolbox. | While Toolbox is opened
|
||||
`ENTER` | Create a Block | While Toolbox is opened and some Tool is selected
|
||||
`CMD+B` | Bold style | On selection
|
||||
`CMD+I` | Italic style | On selection
|
||||
`CMD+K` | Insert a link | On selection
|
||||
|
||||
Each Tool can also have its own shortcuts. These are specified in the configuration of the Tool, for example:
|
||||
|
||||
```js
|
||||
var editor = new EditorJS({
|
||||
//...
|
||||
tools: {
|
||||
header: {
|
||||
class: Header,
|
||||
shortcut: 'CMD+SHIFT+H'
|
||||
},
|
||||
list: {
|
||||
class: List,
|
||||
shortcut: 'CMD+SHIFT+L'
|
||||
}
|
||||
}
|
||||
//...
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Installation Guide
|
||||
|
||||
There are few steps to run Editor.js on your site.
|
||||
|
||||
1. [Load Editor's core](#load-editors-core)
|
||||
2. [Load Tools](#load-tools)
|
||||
3. [Initialize Editor's instance](#create-editor-instance)
|
||||
|
||||
### Step 1. Load Editor's core
|
||||
|
||||
Get Editor.js itself. It is a [minified script](dist/editor.js) with Editor's core and some default must-have tools.
|
||||
|
||||
Choose the most usable method of getting Editor for you.
|
||||
|
||||
- Node package
|
||||
- Source from CDN
|
||||
- Local file from project
|
||||
|
||||
##### Option A. NPM install
|
||||
|
||||
Install the package via NPM or Yarn
|
||||
|
||||
```shell
|
||||
```bash
|
||||
npm i @editorjs/editorjs
|
||||
```
|
||||
|
||||
Include module in your application
|
||||
Choose and install tools:
|
||||
|
||||
```javascript
|
||||
import EditorJS from '@editorjs/editorjs';
|
||||
```
|
||||
- [Heading](https://github.com/editor-js/header)
|
||||
- [Quote](https://github.com/editor-js/quote)
|
||||
- [Image](https://github.com/editor-js/image)
|
||||
- [Simple Image](https://github.com/editor-js/simple-image) (without backend requirement)
|
||||
- [Nested List](https://github.com/editor-js/nested-list)
|
||||
- [Checklist](https://github.com/editor-js/checklist)
|
||||
- [Link embed](https://github.com/editor-js/link)
|
||||
- [Embeds](https://github.com/editor-js/embed) (YouTube, Twitch, Vimeo, Gfycat, Instagram, Twitter, etc)
|
||||
- [Table](https://github.com/editor-js/table)
|
||||
- [Delimiter](https://github.com/editor-js/delimiter)
|
||||
- [Warning](https://github.com/editor-js/warning)
|
||||
- [Code](https://github.com/editor-js/code)
|
||||
- [Raw HTML](https://github.com/editor-js/raw)
|
||||
- [Attaches](https://github.com/editor-js/attaches)
|
||||
- [Marker](https://github.com/editor-js/marker)
|
||||
- [Inline Code](https://github.com/editor-js/inline-code)
|
||||
|
||||
##### Option B. Use a CDN
|
||||
See the [😎 Awesome Editor.js](https://github.com/editor-js/awesome-editorjs) list for more tools.
|
||||
|
||||
You can load EditorJS directly from from [jsDelivr CDN](https://www.jsdelivr.com/package/npm/@editorjs/editorjs).
|
||||
|
||||
`https://cdn.jsdelivr.net/npm/@editorjs/editorjs@latest`
|
||||
|
||||
For example, place this in your HTML:
|
||||
|
||||
```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.
|
||||
|
||||
```html
|
||||
<script src="editor.js"></script>
|
||||
```
|
||||
|
||||
### Step 2. Load the Tools that you want to make available
|
||||
|
||||
Each Block is represented by a [Tool](docs/tools.md). Tools are simple external scripts with their own logic. For example, there is a [Header](https://github.com/editor-js/header) Tool into which you type your heading text. If you want to be able to use this, install the Header Tool the same way as the Editor (Node.js, CDN, local file).
|
||||
|
||||
**Example:** use Header from CDN
|
||||
|
||||
```html
|
||||
<script src="https://cdn.jsdelivr.net/npm/codex.editor.header@2.0.4/dist/bundle.js"></script>
|
||||
```
|
||||
|
||||
Check [Editor.js's community](https://github.com/editor-js/) to see more ready-to-use Tools.
|
||||
|
||||
### Step 3. Create Editor instance
|
||||
|
||||
Create an instance of Editor.js and pass [Configuration Object](types/configs/editor-config.d.ts) with `holderId` and tools list.
|
||||
Initialize the Editor:
|
||||
|
||||
```html
|
||||
<div id="editorjs"></div>
|
||||
```
|
||||
|
||||
You can create a simple Editor with only default Paragraph Tool by passing a string with element's Id (wrapper for Editor) as a configuration param. Or use the default `editorjs` id for wrapper.
|
||||
|
||||
```javascript
|
||||
var editor = new EditorJS(); /** Zero-configuration */
|
||||
import EditorJS from '@editorjs/editorjs'
|
||||
|
||||
// equals
|
||||
|
||||
var editor = new EditorJS('editorjs');
|
||||
const editor = new EditorJS({
|
||||
tools: {
|
||||
// ... your tools
|
||||
}
|
||||
})
|
||||
````
|
||||
|
||||
Or pass a whole settings object.
|
||||
|
||||
```javascript
|
||||
var editor = new EditorJS({
|
||||
/**
|
||||
* Create a holder for the Editor and pass its ID
|
||||
*/
|
||||
holder : 'editorjs',
|
||||
|
||||
/**
|
||||
* Available Tools list.
|
||||
* Pass Tool's class or Settings object for each Tool you want to use
|
||||
*/
|
||||
tools: {
|
||||
header: {
|
||||
class: Header,
|
||||
inlineToolbar : true
|
||||
},
|
||||
// ...
|
||||
},
|
||||
|
||||
/**
|
||||
* Previously saved data that should be rendered
|
||||
*/
|
||||
data: {}
|
||||
});
|
||||
```
|
||||
See details about [Installation](https://editorjs.io/getting-started/) and [Configuration](https://editorjs.io/configuration/) at the documentation.
|
||||
|
||||
### Saving Data
|
||||
|
||||
Call `editor.save()` and handle returned Promise with saved data.
|
||||
|
||||
```javascript
|
||||
editor.save()
|
||||
.then((savedData) => {
|
||||
console.log(savedData);
|
||||
});
|
||||
const data = await editor.save()
|
||||
```
|
||||
|
||||
### Example
|
||||
|
||||
Take a look at the [example.html](example/example.html) to view more detailed examples.
|
||||
|
||||
## Credits and references
|
||||
|
||||
- We use [HTMLJanitor](https://github.com/guardian/html-janitor) module in our Sanitizer module.
|
||||
## Roadmap
|
||||
|
||||
## About team
|
||||
<img align="right" width="342" src="./assets/roadmap.png" style="margin-left: 30px">
|
||||
|
||||
We are CodeX and we build products for developers and makers.
|
||||
- Unified Toolbox
|
||||
- [x] Block Tunes moved left
|
||||
- [x] Toolbox becomes vertical
|
||||
- [x] Ability to display several Toolbox buttons by the single Tool
|
||||
- [x] Block Tunes become vertical
|
||||
- [ ] Block Tunes support nested menus
|
||||
- [ ] Conversion Toolbar uses Unified Toolbox
|
||||
- [ ] Conversion Toolbar added to the Block Tunes
|
||||
- Collaborative editing
|
||||
- [ ] Implement Inline Tools JSON format
|
||||
- [ ] Operations Observer, Executor, Manager, Transformer
|
||||
- [ ] Implement Undo/Redo Manager
|
||||
- [ ] Implement Tools API changes
|
||||
- [ ] Implement Server and communication
|
||||
- [ ] Update basic tools to fit the new API
|
||||
- Other features
|
||||
- [ ] Blocks drag'n'drop
|
||||
- [ ] New cross-block selection
|
||||
- [ ] New cross-block caret moving
|
||||
- Ecosystem improvements
|
||||
- [x] CodeX Icons — the way to unify all tools and core icons
|
||||
- [x] New Homepage and Docs
|
||||
- [x] @editorjs/create-tool for Tools bootstrapping
|
||||
- [ ] Editor.js DevTools — stand for core and tools development
|
||||
- [ ] Editor.js Design System
|
||||
- [ ] Editor.js Preset Env
|
||||
- [ ] Editor.js ToolKit
|
||||
- [ ] New core bundle system
|
||||
- [ ] New documentation and guides
|
||||
|
||||
Follow us on Twitter: [twitter.com/codex_team](https://twitter.com/codex_team)
|
||||
<a href="https://opencollective.com/editorjs/donate" target="_blank">
|
||||
<picture>
|
||||
<source width="162px" media="(prefers-color-scheme: dark)" srcset="./assets/support_night.png">
|
||||
<source width="162px" media="(prefers-color-scheme: light)" srcset="./assets/support_day.png">
|
||||
<img width="162px" alt="Support Editor.js" src="./assets/support_day.png">
|
||||
</picture>
|
||||
</a>
|
||||
|
||||
Feel free to contact: <a href="mailto:team@codex.so?subject=Editor.js feedback">team@codex.so</a>
|
||||
<br>
|
||||
|
||||
[codex.so](https://codex.so)
|
||||
## Like Editor.js?
|
||||
|
||||
You can support project improvement and development of new features with a donation to our team.
|
||||
|
||||
[Donate via OpenCollective](https://opencollective.com/editorjs)
|
||||
\
|
||||
[Donate via Crypto](https://codex.so/donate)
|
||||
\
|
||||
[Donate via Patreon](https://www.patreon.com/editorjs)
|
||||
|
||||
### Why donate
|
||||
|
||||
Donations to open-source products have several advantages for your business:
|
||||
|
||||
- If your business relies on Editor.js, you'll probably want it to be maintained
|
||||
- It helps Editor.js to evolve and get the new features
|
||||
- We can support contributors and the community around the project. You'll receive well organized docs, guides, etc.
|
||||
- We need to pay for our infrastructure and maintain public resources (domain names, homepages, docs, etc). Supporting it guarantees you to access any resources at the time you need them.
|
||||
- You can advertise by adding your brand assets and mentions on our public resources
|
||||
|
||||
|
||||
### Sponsors
|
||||
|
||||
Support us by becoming a sponsor. Your logo will show up here with a link to your website.
|
||||
|
||||
<p>
|
||||
<a href="https://www.mister-auto.com/" target="_blank">
|
||||
<img src="https://opencollective-production.s3.us-west-1.amazonaws.com/5131a030-5672-11ec-be79-1d003d12ec5f.png" width="50" alt="Mister Auto">
|
||||
</a>
|
||||
<a href="https://www.uplucid.com/" target="_blank">
|
||||
<img src="https://logo.clearbit.com/uplucid.com" width="50" alt="UPLUCID, K.K.">
|
||||
</a>
|
||||
<a href="https://www.contentharmony.com/" target="_blank">
|
||||
<img src="https://opencollective-production.s3.us-west-1.amazonaws.com/89edb1b0-7d82-11ed-b99e-ab6e6f9cb69f.png" width="50" alt="Kane Jamison">
|
||||
</a>
|
||||
<a href="https://www.contentharmony.com/product/" target="_blank">
|
||||
<img src="https://logo.clearbit.com/contentharmony.com" width="50" alt="Content Harmony">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
[Become a Sponsor](https://opencollective.com/editorjs/contribute/sir-8679/checkout)
|
||||
|
||||
### Backers
|
||||
Thank you to all our backers
|
||||
|
||||
<a href="https://opencollective.com/editorjs#backers" target="_blank"><img src="https://opencollective.com/editorjs/backers.svg?width=890&avatarHeight=34"></a>
|
||||
|
||||
[Become a Backer](https://opencollective.com/editorjs/contribute/backer-8632/checkout)
|
||||
|
||||
### Contributors
|
||||
|
||||
This project exists thanks to all the people who contribute.
|
||||
|
||||
<p><img src="https://opencollective.com/editorjs/contributors.svg?width=890&button=false&avatarHeight=34" /></p>
|
||||
|
||||
### Need something special?
|
||||
|
||||
Hire CodeX experts to resolve technical challenges and match your product requirements.
|
||||
|
||||
- Resolve a problem that has high value for you
|
||||
- Implement a new feature required by your business
|
||||
- Help with integration or tool development
|
||||
- Provide any consultation
|
||||
|
||||
Contact us via team@codex.so and share your details
|
||||
|
||||
## Community
|
||||
|
||||
- [Official Tools](https://github.com/editor-js)
|
||||
- [Awesome Editor.js](https://github.com/editor-js/awesome-editorjs)
|
||||
- [Good First Tasks](https://github.com/codex-team/editor.js/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+task%22)
|
||||
- [Contributing](https://editorjs.io/contributing/)
|
||||
- [Telegram Chat](https://t.me/codex_editor)
|
||||
|
||||
# About CodeX
|
||||
|
||||
<img align="right" width="120" height="120" src="https://codex.so/public/app/img/codex-logo.svg" hspace="50">
|
||||
|
||||
CodeX is a team of digital specialists around the world interested in building high-quality open source products on a global market. We are [open](https://codex.so/join) for young people who want to constantly improve their skills and grow professionally with experiments in cutting-edge technologies.
|
||||
|
||||
| 🌐 | Join 👋 | Twitter | Instagram |
|
||||
| -- | -- | -- | -- |
|
||||
| [codex.so](https://codex.so) | [codex.so/join](https://codex.so/join) |[@codex_team](http://twitter.com/codex_team) | [@codex_team](http://instagram.com/codex_team/) |
|
||||
|
|
BIN
assets/logo_day.png
Normal file
After Width: | Height: | Size: 54 KiB |
BIN
assets/logo_night.png
Normal file
After Width: | Height: | Size: 55 KiB |
BIN
assets/overview.png
Normal file
After Width: | Height: | Size: 1.3 MiB |
BIN
assets/roadmap.png
Normal file
After Width: | Height: | Size: 91 KiB |
BIN
assets/support_day.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
assets/support_night.png
Normal file
After Width: | Height: | Size: 12 KiB |
35
cypress.config.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
import { defineConfig } from 'cypress';
|
||||
|
||||
export default defineConfig({
|
||||
env: {
|
||||
NODE_ENV: 'test',
|
||||
},
|
||||
fixturesFolder: 'test/cypress/fixtures',
|
||||
screenshotsFolder: 'test/cypress/screenshots',
|
||||
video: false,
|
||||
videosFolder: 'test/cypress/videos',
|
||||
e2e: {
|
||||
// We've imported your old cypress plugins here.
|
||||
// You may want to clean this up later by importing these.
|
||||
setupNodeEvents(on, config) {
|
||||
on('file:preprocessor', require('cypress-vite')(config));
|
||||
|
||||
/**
|
||||
* Plugin for cypress that adds better terminal output for easier debugging.
|
||||
* Prints cy commands, browser console logs, cy.request and cy.intercept data. Great for your pipelines.
|
||||
* https://github.com/archfz/cypress-terminal-report
|
||||
*/
|
||||
require('cypress-terminal-report/src/installLogsPrinter')(on);
|
||||
|
||||
require('./test/cypress/plugins/index.ts')(on, config);
|
||||
},
|
||||
specPattern: 'test/cypress/tests/**/*.cy.{js,jsx,ts,tsx}',
|
||||
supportFile: 'test/cypress/support/index.ts',
|
||||
},
|
||||
'retries': {
|
||||
// Configure retry attempts for `cypress run`
|
||||
'runMode': 2,
|
||||
// Configure retry attempts for `cypress open`
|
||||
'openMode': 0,
|
||||
},
|
||||
});
|
|
@ -1,9 +0,0 @@
|
|||
{
|
||||
"env": {
|
||||
},
|
||||
"fixturesFolder": "test/cypress/fixtures",
|
||||
"integrationFolder": "test/cypress/tests",
|
||||
"screenshotsFolder": "test/cypress/screenshots",
|
||||
"videosFolder": "test/cypress/videos",
|
||||
"supportFile": "test/cypress/support/index.ts"
|
||||
}
|
128
devserver.js
Normal file
|
@ -0,0 +1,128 @@
|
|||
/**
|
||||
* Server for testing example page on mobile devices.
|
||||
*
|
||||
* Usage:
|
||||
* 1. run `yarn devserver:start`
|
||||
* 2. Open `http://{ip_address}:3000/example/example-dev.html`
|
||||
* where {ip_address} is IP of your machine.
|
||||
*
|
||||
* Also, can serve static files from `/example` or `/dist` on any device in local network.
|
||||
*/
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const http = require('http');
|
||||
const { networkInterfaces } = require('os');
|
||||
|
||||
const port = 3000;
|
||||
const localhost = '127.0.0.1';
|
||||
const nonRoutableAddress = '0.0.0.0';
|
||||
const host = getHost();
|
||||
const server = http.createServer(serveStatic([
|
||||
'/example',
|
||||
'/dist',
|
||||
]));
|
||||
|
||||
server.listen(port, nonRoutableAddress, () => {
|
||||
console.log(`
|
||||
|
||||
${wrapInColor('Editor.js 💖', consoleColors.hiColor)} devserver is running ᕕ(⌐■_■)ᕗ ✨
|
||||
---------------------------------------------
|
||||
${wrapInColor('http://' + host + ':' + port + '/example/example-dev.html', consoleColors.fgGreen)}
|
||||
---------------------------------------------
|
||||
Page can be opened from any device connected to the same local network.
|
||||
`);
|
||||
|
||||
if (host === localhost) {
|
||||
console.log(wrapInColor('Looks like you are not connected to any Network so you couldn\'t debug the Editor on your mobile device at the moment.', consoleColors.fgRed));
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Serves files from specified directories
|
||||
*
|
||||
* @param {string[]} paths - directories files from which should be served
|
||||
* @returns {Function}
|
||||
*/
|
||||
function serveStatic(paths) {
|
||||
return (request, response) => {
|
||||
const resource = request.url;
|
||||
const isPathAllowed = paths.find(p => resource.startsWith(p));
|
||||
|
||||
if (!isPathAllowed) {
|
||||
response.writeHead(404);
|
||||
response.end();
|
||||
|
||||
return;
|
||||
}
|
||||
const filePath = path.join(__dirname, resource);
|
||||
|
||||
try {
|
||||
const stat = fs.statSync(filePath);
|
||||
|
||||
response.writeHead(200, {
|
||||
'Content-Length': stat.size,
|
||||
});
|
||||
const readStream = fs.createReadStream(filePath);
|
||||
|
||||
readStream.on('error', e => {
|
||||
throw e;
|
||||
});
|
||||
readStream.pipe(response);
|
||||
} catch (e) {
|
||||
response.writeHead(500);
|
||||
response.end(e.toString());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns IP address of a machine
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
function getHost() {
|
||||
const nets = networkInterfaces();
|
||||
const results = {};
|
||||
|
||||
for (const name of Object.keys(nets)) {
|
||||
for (const net of nets[name]) {
|
||||
// Skip over non-IPv4 and internal (i.e. 127.0.0.1) addresses
|
||||
if (net.family === 'IPv4' && !net.internal) {
|
||||
if (!results[name]) {
|
||||
results[name] = [];
|
||||
}
|
||||
results[name].push(net.address);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Offline case
|
||||
*/
|
||||
if (Object.keys(results).length === 0) {
|
||||
return localhost;
|
||||
}
|
||||
|
||||
return results['en0'][0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Terminal output colors
|
||||
*/
|
||||
const consoleColors = {
|
||||
fgMagenta: 35,
|
||||
fgRed: 31,
|
||||
fgGreen: 32,
|
||||
hiColor: 1,
|
||||
};
|
||||
|
||||
/**
|
||||
* Set a terminal color to the message
|
||||
*
|
||||
* @param {string} msg - text to wrap
|
||||
* @param {string} color - color
|
||||
* @returns {string}
|
||||
*/
|
||||
function wrapInColor(msg, color) {
|
||||
return '\x1b[' + color + 'm' + msg + '\x1b[0m';
|
||||
}
|
2
dist/editor.js
vendored
67
dist/editor.js.LICENSE.txt
vendored
|
@ -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)
|
||||
*/
|
413
dist/editor.licenses.txt
vendored
|
@ -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
|
@ -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 |
|
@ -1,17 +1,308 @@
|
|||
# Changelog
|
||||
|
||||
### 2.30.0
|
||||
|
||||
- `New` – Block Tunes now supports nesting items
|
||||
- `New` – Block Tunes now supports separator items
|
||||
- `New` – "Convert to" control is now also available in Block Tunes
|
||||
- `Improvement` — The ability to merge blocks of different types (if both tools provide the conversionConfig)
|
||||
- `Fix` — `onChange` will be called when removing the entire text within a descendant element of a block.
|
||||
- `Fix` - Unexpected new line on Enter press with selected block without caret
|
||||
- `Fix` - Search input autofocus loosing after Block Tunes opening
|
||||
- `Fix` - Block removing while Enter press on Block Tunes
|
||||
- `Fix` – Unwanted scroll on first typing on iOS devices
|
||||
- `Fix` - Unwanted soft line break on Enter press after period and space (". |") on iOS devices
|
||||
- `Fix` - Caret lost after block conversion on mobile devices.
|
||||
- `Fix` - Caret lost after Backspace at the start of block when previoius block is not convertable
|
||||
- `Improvement` - The API `blocks.convert()` now returns the new block API
|
||||
- `Improvement` - The API `caret.setToBlock()` now can accept either BlockAPI or block index or block id
|
||||
- `New` – *Menu Config* – New item type – HTML
|
||||
– `Refactoring` – Switched to Vite as Cypress bundler
|
||||
– `New` – *Menu Config* – Default and HTML items now support hints
|
||||
|
||||
### 2.29.1
|
||||
|
||||
- `Fix` — Toolbox wont be shown when Slash pressed with along with Shift or Alt
|
||||
- `Fix` — Toolbox will be opened when Slash pressed in non-US keyboard layout where there is no physical '/' key.
|
||||
|
||||
### 2.29.0
|
||||
|
||||
- `New` — Editor Config now has the `style.nonce` attribute that could be used to allowlist editor style tag for Content Security Policy "style-src"
|
||||
- `New` — Toolbox now will be opened by '/' in empty Block instead of Tab
|
||||
- `New` — Block Tunes now will be opened by 'CMD+/' instead of Tab in non-empty block
|
||||
- `New` — Tab now will navigate through Blocks. In last block Tab will navigate to the next input on page.
|
||||
- `Fix` — Passing an empty array via initial data or `blocks.render()` won't break the editor
|
||||
- `Fix` — Layout did not shrink when a large document cleared in Chrome
|
||||
- `Fix` — Multiple Tooltip elements creation fixed
|
||||
- `Fix` — When the focusing Block is out of the viewport, the page will be scrolled.
|
||||
- `Fix` - Compiler error "This import is never used as a value and must use 'import type'..." fixed
|
||||
- `Fix` — `blocks.render()` won't lead the `onChange` call in Safari
|
||||
- `Fix` — Editor wrapper element growing on the Inline Toolbar close
|
||||
- `Fix` — Fix errors thrown by clicks on a document when the editor is being initialized
|
||||
- `Fix` — Caret losing on Mobile Devices when adding a block via Toolbox or via Backspace at the beginning of a Block
|
||||
- `Improvement` — Now you can set focus via arrows/Tab to "contentless" (decorative) blocks like Delimiter which have no inputs.
|
||||
- `Improvement` — Inline Toolbar sometimes opened in an incorrect position. Now it will be aligned by the left side of the selected text. And won't overflow the right side of the text column.
|
||||
- `Improvement` - Now the `data-mutation-free` supports deep nesting, so you can mark some element with it to prevent the onChange call caused by child element mutating
|
||||
- `Improvement` - Now the `data-mutation-free` also allows to skip "characterData" mutations (eg. text content change)
|
||||
- `Refactoring` — `ce-block--focused` class toggling removed as unused.
|
||||
|
||||
### 2.28.2
|
||||
|
||||
- `Fix` — Get rid of redundant logs from the build
|
||||
|
||||
### 2.28.1
|
||||
|
||||
- `Fix` — Some Block were be skipped on saving after pasting them as HTML
|
||||
|
||||
### 2.28.0
|
||||
|
||||
- `New` - Block ids now displayed in DOM via a data-id attribute. Could be useful for plugins that want to access a Block's element by id.
|
||||
- `New` - The `blocks.convert(blockId, newType)` API method was added. It allows to convert existing Block to a Block of another type.
|
||||
- `New` - The `blocks.insertMany()` API method added. It allows to insert several Blocks to the specified index.
|
||||
- `Improvement` - The Delete keydown at the end of the Block will now work opposite a Backspace at the start. Next Block will be removed (if empty) or merged with the current one.
|
||||
- `Improvement` - The Delete keydown will work like a Backspace when several Blocks are selected.
|
||||
- `Improvement` - If we have two empty Blocks, and press Backspace at the start of the second one, the previous will be removed instead of the current.
|
||||
- `Improvement` - Tools shortcuts could be used to convert one Block to another.
|
||||
- `Improvement` - Tools shortcuts displayed in the Conversion Toolbar
|
||||
- `Improvement` - Initialization Loader has been removed.
|
||||
- `Improvement` - Selection style won't override your custom style for `::selection` outside the editor.
|
||||
- `Improvement` - Performance optimizations: initialization speed increased, `blocks.render()` API method optimized. Big documents will be displayed faster.
|
||||
- `Improvement` - "Editor saving" log removed
|
||||
- `Improvement` - "I'm ready" log removed
|
||||
- `Improvement` - The stub-block style is simplified.
|
||||
- `Improvement` - If some Block's tool throws an error during construction, we will show Stub block instead of skipping it during render
|
||||
- `Improvement` - Call of `blocks.clear()` now will trigger onChange with "block-removed" event for all removed blocks.
|
||||
- `Improvement` - The `blocks.clear()` now can be awaited.
|
||||
- `Improvement` - `BlockMutationType` and `BlockMutationEvent` types exported
|
||||
- `Improvement` - `blocks.update(id, data)` now can accept partial data object — it will update only passed properties, others will remain the same.
|
||||
- `Improvement` - `blocks.update(id, data)` now will trigger onChange with only `block-change` event.
|
||||
- `Improvement` - `blocks.update(id, data)` will return a promise with BlockAPI object of the changed block.
|
||||
|
||||
|
||||
### 2.27.2
|
||||
|
||||
- `Fix` - `onChange` won't be called when element with data-mutation-free changes some attribute
|
||||
|
||||
### 2.27.1
|
||||
|
||||
- `Fix` - `onChange` will be called on removing the whole text in a block
|
||||
|
||||
### 2.27.0
|
||||
|
||||
- `New` — *Toolbar API* — Added a new method for toggling the toolbox.
|
||||
- `New` — Added types for block mutation events
|
||||
- `New` — Batching added to the `onChange` callback. Now the second argument can contain an array of CustomEvents as well as a single one. Multiple changes made in a short period of time will be batched under a single `onChange` call.
|
||||
- `Improvement` — *Toolbox* — Number of `close()` method calls optimized.
|
||||
- `Improvement` — The `onChange` callback can be muted if all mutations contain nodes with the `data-mutation-free` attribute.
|
||||
- `Improvement` — Pressing "Enter" at the end of a Block won't lead to redundant `block-changed` event triggering. Only `block-added` event will be dispatched.
|
||||
- `Improvement` — The block mutation handler is now called on every block change (including background changes), instead of only when a block is focused
|
||||
- `Improvement` — Number of caret saving method calls optimized for Block Tunes opening/closing.
|
||||
- `Improvement` — Package size reduced by removing redundant files.
|
||||
- `Refactoring` — Switched from Webpack to Vite as the build system.
|
||||
- `Refactoring` — *Dependencies* — Upgraded Cypress to v12 and related libraries to the latest versions.
|
||||
- `Refactoring` — *Dependencies* — Upgraded TypeScript to v5.
|
||||
- `Refactoring` — `EventDispatcher` types improved. Now we can pass `EventsMap` via generic to specify a map of event names and their payloads that can be used in a particular EventDispatcher instance.
|
||||
- `Refactoring` — All events in common editor Event Bus now have own type declarations.
|
||||
- `Refactoring` — Removed the block mutation observer from blocks and attached a single observer to the editor's blocks wrapper element.
|
||||
- `Refactoring` — Removed the debounce from the block mutation handler and used batching instead.
|
||||
- `Refactoring` — Refactored the popover class for better performance and maintenance.
|
||||
- `Fix` — The `onChange` callback won't trigger when block tunes are opened or closed.
|
||||
- `Fix` — Resolved a compiler error caused by importing the `BlockToolData` type.
|
||||
- `Fix` — Resolved a problem where the document would scroll to the beginning after moving a block above the viewport.
|
||||
- `Fix`- Fixed several bugs caused by browser extensions — Removed the search for a block's container in the DOM on saving and kept it in memory instead, updating it when the tool changes a container element.
|
||||
- `Fix` — *ToolsAPI* — `pasteConfig` getter with `false` value could be used to disable paste handling by Editor.js core. Could be useful if your tool has its own paste handler.
|
||||
- `CI` — Ubuntu container is now used for Edge tests runner.
|
||||
- `CI` — Node 16 is used for GitHib Actions.
|
||||
|
||||
### 2.26.5
|
||||
|
||||
- `Fix` — *Types* — Remove unnecessary import that creates a dependency on the `cypress`.
|
||||
|
||||
### 2.26.4
|
||||
|
||||
- `Improvement` — *Menu Config* — Property `label` renamed to `title`.
|
||||
|
||||
### 2.26.3
|
||||
|
||||
- `Fix` — *Paste Module* — fix for a problem with specifying of `pasteConfig().tags` in upper case [#2208](https://github.com/codex-team/editor.js/issues/2208).
|
||||
|
||||
### 2.26.2
|
||||
|
||||
- `Fix` — *Menu Config* — Installed tunes are rendered above default tunes again.
|
||||
|
||||
### 2.26.1
|
||||
|
||||
- `Improvement` — *Menu Config* — Now it becomes possible to create toggle groups.
|
||||
|
||||
### 2.26.0
|
||||
|
||||
- `New` — *UI* — Block Tunes became vertical just like the Toolbox 🤩
|
||||
- `New` — *Block Tunes API* — Now `render()` method of a Block Tune can return config with just icon, label and callback instead of custom HTML. This improvement is a key to the new straightforward way of configuring tune's appearance in Block Tunes menu.
|
||||
- `New` — *Tools API* — As well as `render()` in `Tunes API`, Tool's `renderSettings()` now also supports new configuration format.
|
||||
- `New` — *UI* — Meet the new icons from [CodeX Icons](https://github.com/codex-team/icons) pack 🛍 💝
|
||||
- `New` — *BlocksAPI* — the `blocks.insert()` method now also have the optional `id` param. If passed, this id will be used instead of the generated one.
|
||||
- `Deprecated` — *Styles API* — CSS classes `.cdx-settings-button` and `.cdx-settings-button--active` are not recommended to use. Consider configuring your block settings with new JSON API instead.
|
||||
- `Fix` — Wrong element not highlighted anymore when popover opened.
|
||||
- `Fix` — When Tunes Menu open keydown events can not be handled inside plugins.
|
||||
- `Fix` — If a Tool specifies some tags to substitute on paste, all attributes of that tags will be removed before passing them to the tool. Possible XSS vulnerability fixed.
|
||||
- `Fix` — Pasting from Microsoft Word to Chrome (Mac OS) fixed. Now if there are no image-tools connected, regular text content will be pasted.
|
||||
- `Fix` — Workaround for the HTMLJanitor bug with Tables (https://github.com/guardian/html-janitor/issues/3) added
|
||||
- `Fix` — Toolbox shortcuts appearance and execution fixed [#2112](https://github.com/codex-team/editor.js/issues/2112)
|
||||
- `Fix` — Inline Tools click handling on mobile devices improved
|
||||
- `Improvement` — *Tools API* — `pasteConfig().tags` now support sanitizing configuration. It allows you to leave some explicitly specified attributes for pasted content.
|
||||
- `Improvement` — *CodeStyle* — [CodeX ESLint Config](https://github.com/codex-team/eslint-config) has bee updated. All ESLint/Spelling issues resolved
|
||||
- `Improvement` — *ToolsAPI* — The `icon` property of the `toolbox` getter became optional.
|
||||
|
||||
|
||||
### 2.25.0
|
||||
|
||||
- `New` — *Tools API* — Introducing new feature — toolbox now can have multiple entries for one tool! <br>
|
||||
Due to that API changes: tool's `toolbox` getter now can return either a single config item or an array of config items
|
||||
- `New` — *Blocks API* — `composeBlockData()` method was added.
|
||||
|
||||
### 2.24.4
|
||||
|
||||
- `Fix` — Keyboard selection by word [#2045](https://github.com/codex-team/editor.js/issues/2045)
|
||||
|
||||
### 2.24.3
|
||||
|
||||
- `Fix` — Issue with toolbox preventing text selection fixed
|
||||
|
||||
### 2.24.2
|
||||
|
||||
- `Fix` — Scrolling issue when opening toolbox on mobile fixed
|
||||
- `Fix` — Typo in toolbox empty placeholder fixed
|
||||
- `Fix` — The issue with scroll jumping on block hovering have fixed [2036](https://github.com/codex-team/editor.js/issues/2036)
|
||||
- `Improvement` — *Dev Example Page* - Add popup example page
|
||||
- `Improvement` — *UI* - The Toolbox will restore the internal scroll on every opening
|
||||
|
||||
### 2.24.1
|
||||
|
||||
— `Fix` — The I18n of Tools` titles at the Toolbox now works correctly [#2030](https://github.com/codex-team/editor.js/issues/2030)
|
||||
|
||||
### 2.24.0
|
||||
|
||||
- `New` — *UI* — The Toolbox became vertical 🥳
|
||||
- `Improvement` — *UI* — the Plus button will always be shown (previously, it appears only for empty blocks)
|
||||
- `Improvement` — *Dev Example Page* - Server added to allow opening example page on other devices in network.
|
||||
- `Fix` — `UI` — the Toolbar won't move on hover at mobile viewports. Resolves [#1972](https://github.com/codex-team/editor.js/issues/1972)
|
||||
- `Fix` — `OnChange` event invocation after block insertion. [#1997](https://github.com/codex-team/editor.js/issues/1997)
|
||||
- `Fix` — `ReadOnly` — the `readonly.isEnabled` API getter now works correctly after `readonly.toggle()` calling. Resolves [#1822](https://github.com/codex-team/editor.js/issues/1822)
|
||||
- `Fix` — `Paste` — the inline HTML tags now will be preserved on pasting. [#1686](https://github.com/codex-team/editor.js/pull/1686)
|
||||
|
||||
### 2.23.2
|
||||
|
||||
— `Fix` — Crash on initialization in the read-only mode [#1968](https://github.com/codex-team/editor.js/issues/1968)
|
||||
|
||||
### 2.23.1
|
||||
|
||||
— `Fix` — Incorrect release tag fixed
|
||||
|
||||
### 2.23.0
|
||||
|
||||
- `Improvement` — *EditorConfig* — The `onChange` callback now accepts two arguments: EditorJS API and the CustomEvent with `type` and `detail` allowing to determine what happened with a Block
|
||||
- `New` — *Block API* — The new `dispatchChange()` method allows to manually trigger the 'onChange' callback. Useful when Tool made a state mutation that is invisible for editor core.
|
||||
- `Improvement` — *UI* — Block Tunes toggler moved to the left
|
||||
- `Improvement` — *UI* — Block Actions (BT toggler + Plus Button) will appear on block hovering instead of click
|
||||
- `Improvement` — *UI* — Block Tunes toggler icon and Plus button icon updated
|
||||
- `Improvement` — *Dev Example Page* — The menu with helpful buttons added to the bottom of the screen
|
||||
- `Improvement` — *Dev Example Page* — The 'dark' theme added. Now we can code at night more comfortably.
|
||||
- `Improvement` — *Rectangle Selection* — paint optimized
|
||||
- `Fix` — *Rectangle Selection* — the first click after RS was not clear selection state. Now does.
|
||||
- `Improvement` — *Blocks API* — toolbar moving logic removed from `blocks.move()` and `blocks.swap()` methods. Instead, you should use Toolbar API (it was used by MoveUp and MoveDown tunes, they were updated).
|
||||
- `New` — *Blocks API* — The `getBlockIndex()` method added
|
||||
- `New` — *Blocks API* — the `insert()` method now has the `replace: boolean` parameter
|
||||
- `New` — *Blocks API* — the `insert()` method now returns the inserted `Block API`
|
||||
- `New` — *Listeners API* — the `on()` method now returns the listener id.
|
||||
- `New` — *Listeners API* — the new `offById()` method added
|
||||
- `New` — `API` — The new `UiApi` section was added. It allows accessing some editor UI nodes and methods.
|
||||
- `Refactoring` — Toolbox became a standalone class instead of a Module. It can be accessed only through the Toolbar module.
|
||||
- `Refactoring` — CI flow optimized.
|
||||
- `Fix` - Recognize async `onPaste` handlers in tools [#1803](https://github.com/codex-team/editor.js/issues/1803).
|
||||
- `Fix` — Fire onChange event for native inputs [#1750](https://github.com/codex-team/editor.js/issues/1750)
|
||||
|
||||
### 2.22.3
|
||||
|
||||
- `Fix` — Tool config is passed to `prepare` method [editor-js/embed#68](https://github.com/editor-js/embed/issues/68)
|
||||
|
||||
### 2.22.2
|
||||
|
||||
- `Improvement` — Inline Toolbar might be used for any contenteditable element inside Editor.js zone
|
||||
- `Improvement` *Tunes API* - Tunes now can provide sanitize configuration
|
||||
- `Fix` *Tunes API* - Tune config now passed to constructor under `config` property
|
||||
- `Fix` *Types* - Add common type for internal and external Tools configuration
|
||||
- `Fix` — Block's destroy method is called on block deletion
|
||||
- `Fix` - Fix jump to the button of editor zone on CBS
|
||||
|
||||
### 2.22.1
|
||||
|
||||
- `Fix` — I18n for internal Block Tunes [#1661](https://github.com/codex-team/editor.js/issues/1661)
|
||||
|
||||
### 2.22.0
|
||||
|
||||
- `New` - `onChange` callback now receive Block API object of affected block
|
||||
- `New` - API method `blocks.update(id, data)` added.
|
||||
|
||||
### 2.21.0
|
||||
|
||||
- `New` - Blocks now have unique ids [#873](https://github.com/codex-team/editor.js/issues/873)
|
||||
|
||||
### 2.20.2
|
||||
|
||||
- `Fix` — Append default Tunes if user tunes are provided for Block Tool [#1640](https://github.com/codex-team/editor.js/issues/1640)
|
||||
- `Fix` - Prevent the leak of codex-tooltip when Editor.js is destroyed [#1475](https://github.com/codex-team/editor.js/issues/1475).
|
||||
- `Refactoring` - Notifier module now is a util.
|
||||
|
||||
### 2.20.1
|
||||
|
||||
- `Fix` - Create a new block when clicked at the bottom [#1588](https://github.com/codex-team/editor.js/issues/1588).
|
||||
- `Fix` — Fix sanitization problem with Inline Tools [#1631](https://github.com/codex-team/editor.js/issues/1631)
|
||||
- `Fix` — Fix copy in FireFox [1625](https://github.com/codex-team/editor.js/issues/1625)
|
||||
- `Refactoring` - The Sanitizer module is util now.
|
||||
- `Refactoring` - Tooltip module is util now.
|
||||
- `Refactoring` — Refactoring based on LGTM [#1577](https://github.com/codex-team/editor.js/issues/1577).
|
||||
- `Refactoring` — Refactoring based on ESLint [#1636](https://github.com/codex-team/editor.js/issues/1636).
|
||||
|
||||
### 2.20.0
|
||||
|
||||
- `New` — [Block Tunes API](block-tunes.md) added
|
||||
|
||||
### 2.19.3
|
||||
|
||||
- `Fix` — Ignore error raised by Shortcut module
|
||||
|
||||
### 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)
|
||||
|
@ -20,7 +311,7 @@
|
|||
- `New` - Tool's `reset` static method added to the API to clean up any data added by Tool on initialization
|
||||
- `Improvements` - The `initialBlock` property of Editor config is deprecated. Use the `defaultBlock` instead. [#993](https://github.com/codex-team/editor.js/issues/993)
|
||||
- `Improvements` - BlockAPI `call()` method now returns the result of calling method, thus allowing it to expose arbitrary data as needed [#1205](https://github.com/codex-team/editor.js/pull/1205)
|
||||
- `Improvements` - Unuseful log about missed i18n section has been removed [#1269](https://github.com/codex-team/editor.js/issues/1269)
|
||||
- `Improvements` - Useless log about missed i18n section has been removed [#1269](https://github.com/codex-team/editor.js/issues/1269)
|
||||
- `Improvements` - Allowed to set `false` as `toolbox` config in order to hide Toolbox button [#1221](https://github.com/codex-team/editor.js/issues/1221)
|
||||
- `Fix` — Fix problem with types usage [#1183](https://github.com/codex-team/editor.js/issues/1183)
|
||||
- `Fix` - Fixed issue with Spam clicking the "Click to tune" button duplicates the icons on FireFox. [#1273](https://github.com/codex-team/editor.js/issues/1273)
|
||||
|
@ -31,7 +322,7 @@
|
|||
- `Fix` - Fixed issue with enter key in inputs and textareas [#920](https://github.com/codex-team/editor.js/issues/920)
|
||||
- `Fix` - blocks.getBlockByIndex() API method now returns void for indexes out of range [#1270](https://github.com/codex-team/editor.js/issues/1270)
|
||||
- `Fix` - Fixed the `Tab` key behavior when the caret is not set inside contenteditable element, but the block is selected [#1302](https://github.com/codex-team/editor.js/issues/1302).
|
||||
- `Fix` - Fixed the `onChange` callback issue. This method didn't be called for native inputs before some contentedtable element changed [#843](https://github.com/codex-team/editor.js/issues/843)
|
||||
- `Fix` - Fixed the `onChange` callback issue. This method didn't be called for native inputs before some contenteditable element changed [#843](https://github.com/codex-team/editor.js/issues/843)
|
||||
- `Fix` - Fixed the `onChange` callback issue. This method didn't be called after the callback throws an exception [#1339](https://github.com/codex-team/editor.js/issues/1339)
|
||||
- `Fix` - The internal `shortcut` getter of Tools classes will work now.
|
||||
- `Deprecated` — The Inline Tool `clear()` method is deprecated because the new instance of Inline Tools will be created on every showing of the Inline Toolbar
|
||||
|
@ -80,7 +371,7 @@
|
|||
|
||||
- `Fix` — Fix Firefox bug with incorrect height and cursor position of empty content editable elements [#947](https://github.com/codex-team/editor.js/issues/947) [#876](https://github.com/codex-team/editor.js/issues/876) [#608](https://github.com/codex-team/editor.js/issues/608) [#876](https://github.com/codex-team/editor.js/issues/876)
|
||||
- `Fix` — Set initial hidden Inline Toolbar position [#979](https://github.com/codex-team/editor.js/issues/979)
|
||||
- `Fix` — Fix issue with CodeX.Toolips TypeScript definitions [#978](https://github.com/codex-team/editor.js/issues/978)
|
||||
- `Fix` — Fix issue with CodeX.Tooltips TypeScript definitions [#978](https://github.com/codex-team/editor.js/issues/978)
|
||||
- `Fix` — Fix some issues with Inline and Tunes toolbars.
|
||||
- `Fix` - Fix `minHeight` option with zero-value issue [#724](https://github.com/codex-team/editor.js/issues/724)
|
||||
- `Improvements` — Disable Conversion Toolbar if there are no Tools to convert [#984](https://github.com/codex-team/editor.js/issues/984)
|
||||
|
@ -193,7 +484,7 @@
|
|||
|
||||
### 2.11.5
|
||||
|
||||
- `Fix` *RectangeSelection* — Redesign of the scrolling zones
|
||||
- `Fix` *RectangleSelection* — Redesign of the scrolling zones
|
||||
|
||||
### 2.11.4
|
||||
|
||||
|
@ -209,7 +500,7 @@
|
|||
|
||||
### 2.11.1
|
||||
|
||||
- `Fix` *RectangeSelection* — Selection is available only for the main mouse button
|
||||
- `Fix` *RectangleSelection* — Selection is available only for the main mouse button
|
||||
|
||||
### 2.11.0
|
||||
|
||||
|
@ -241,7 +532,7 @@
|
|||
|
||||
### 2.9.0
|
||||
|
||||
- `New` *RectangeSelection* — Ability to select Block or several Blocks with mouse
|
||||
- `New` *RectangleSelection* — Ability to select Block or several Blocks with mouse
|
||||
|
||||
### 2.8.1
|
||||
|
||||
|
@ -249,7 +540,7 @@
|
|||
|
||||
### 2.8.0
|
||||
|
||||
- `Imporvements` *API* — Added [API methods](api.md#caretapi) to manage caret position
|
||||
- `Improvements` *API* — Added [API methods](api.md#caretapi) to manage caret position
|
||||
|
||||
### 2.7.32
|
||||
|
||||
|
|
|
@ -35,6 +35,8 @@ API for certain Block methods and properties. You can access it through `editor.
|
|||
|
||||
`validate(data: BlockToolData): Promise<boolean>` — calls Tool's validate method if exists
|
||||
|
||||
`dispatchChange(): void` - Allows to say Editor that Block was changed. Used to manually trigger Editor's 'onChange' callback. Can be useful for block changes invisible for editor core.
|
||||
|
||||
## Api object description
|
||||
|
||||
Common API interface.
|
||||
|
@ -77,6 +79,8 @@ use 'move' instead)
|
|||
|
||||
`insert(type?: string, data?: BlockToolData, config?: ToolConfig, index?: number, needToFocus?: boolean)` - insert new Block with passed parameters
|
||||
|
||||
`update(id: string, data: BlockToolData)` - updates data for the block with passed id
|
||||
|
||||
#### SanitizerAPI
|
||||
|
||||
`clean(taintString, config)` - method uses HTMLJanitor to clean taint string.
|
||||
|
@ -176,7 +180,7 @@ this.api.notifier.show({
|
|||
});
|
||||
```
|
||||
|
||||
![](https://capella.pics/14fcdbe4-d6eb-41d4-b66e-e0e86ccf1a4b.jpg)
|
||||
![](assets/14fcdbe4-d6eb-41d4-b66e-e0e86ccf1a4b.jpg)
|
||||
|
||||
|
||||
Check out [`codex-notifier` package page](https://github.com/codex-team/js-notifier) on GitHub to find docs, params and examples.
|
||||
|
@ -199,8 +203,6 @@ After executing the `destroy` method, editor inctance becomes an empty object. T
|
|||
|
||||
Methods for showing Tooltip helper near your elements. Parameters are the same as in [CodeX Tooltips](http://github.com/codex-team/codex.tooltips) lib.
|
||||
|
||||
![](https://capella.pics/00e7094a-fdb9-429b-8015-9c56f19b4ef5.jpg)
|
||||
|
||||
#### Show
|
||||
|
||||
Method shows tooltip with custom content on passed element
|
||||
|
|
BIN
docs/assets/01a55381-46cd-47c7-b92e-34765434f2ca.jpg
Normal file
After Width: | Height: | Size: 328 KiB |
BIN
docs/assets/14fcdbe4-d6eb-41d4-b66e-e0e86ccf1a4b.jpg
Normal file
After Width: | Height: | Size: 8.3 KiB |
BIN
docs/assets/57267bab-f2f0-411b-a9d1-69abee6abab5.jpg
Normal file
After Width: | Height: | Size: 77 KiB |
BIN
docs/assets/6c1f708b-a30c-4ffd-a427-5b59a1a472e0.jpg
Normal file
After Width: | Height: | Size: 404 KiB |
BIN
docs/assets/796de9eb-bbe0-485c-bc8f-9a4cb76641b7.jpg
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
docs/assets/79ce946a-d636-41cd-aa96-d3bc5ecfde03.jpg
Normal file
After Width: | Height: | Size: 58 KiB |
BIN
docs/assets/7ccbcfcd-1c49-4674-bea7-71021468a1bd.jpg
Normal file
After Width: | Height: | Size: 137 KiB |
185
docs/block-tunes.md
Normal file
|
@ -0,0 +1,185 @@
|
|||
# Block Tunes
|
||||
|
||||
Similar with [Tools](tools.md) represented Blocks, you can create Block Tunes and connect it to particular Tool or for all Tools.
|
||||
|
||||
Block Tunes allows you to set any additional options to Blocks. For example, with corresponded Block Tunes you can mark Block as «spoiler», give it an anchor, set a background, and so on.
|
||||
|
||||
## Base structure
|
||||
|
||||
Tune's class should have the `isTune` property (static getter) set to `true`.
|
||||
|
||||
Block Tune must implement the `render()` method which returns an HTML Element that will be appended to the Block Settings panel.
|
||||
|
||||
- `render()` — create a button
|
||||
|
||||
Also, you can provide optional methods
|
||||
|
||||
- `wrap()` — wraps Block content with own HTML elements
|
||||
- `save()` — save Tunes state on Editor's save
|
||||
|
||||
At the constructor of Tune's class exemplar you will receive an object with following parameters:
|
||||
|
||||
| Parameter | Description |
|
||||
| --------- | ----------- |
|
||||
| api | Editor's [API](api.md) obejct |
|
||||
| config | Configuration of Block Tool Tune is connected to (might be useful in some cases) |
|
||||
| block | [Block API](api.md#block-api) methods for block Tune is connected to |
|
||||
| data | Saved Tune data |
|
||||
|
||||
---
|
||||
|
||||
### render(): HTMLElement
|
||||
|
||||
Method that returns button to append to the block settings area
|
||||
|
||||
#### Parameters
|
||||
|
||||
Method does not accept any parameters
|
||||
|
||||
#### Return value
|
||||
|
||||
type | description |
|
||||
-- | -- |
|
||||
`HTMLElement` | element that will be added to the block settings area |
|
||||
|
||||
---
|
||||
|
||||
### wrap(blockContent: HTMLElement): HTMLElement
|
||||
|
||||
Method that accepts Block's content and wrap it with your own layout.
|
||||
Might be useful if you want to modify Block appearance.
|
||||
|
||||
```javascript
|
||||
class Tune {
|
||||
wrap(blockContent) {
|
||||
const myWrapper = document.createElement('div');
|
||||
|
||||
myWrapper.append(blockContent);
|
||||
|
||||
return myWrapper;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Parameters
|
||||
|
||||
name | type | description |
|
||||
-- |-- | -- |
|
||||
blockContent | HTMLElement | Block's content (might be wrapped by other Tunes) |
|
||||
|
||||
#### Return value
|
||||
|
||||
| type | description |
|
||||
| -- | -- |
|
||||
| HTMLElement | Your element that wraps block content |
|
||||
|
||||
---
|
||||
|
||||
### save()
|
||||
|
||||
Method should return Tune's state you want to save to Editor's output
|
||||
|
||||
#### Parameters
|
||||
|
||||
No parameters
|
||||
|
||||
#### Return value
|
||||
|
||||
type | description |
|
||||
-- | -- |
|
||||
`any` | any data you want to save |
|
||||
|
||||
---
|
||||
|
||||
### static prepare()
|
||||
|
||||
If you need to prepare some data for Tune (eg. load external script, create HTML nodes in the document, etc) you can use the static `prepare()` method.
|
||||
|
||||
It accepts tunes config passed on Editor's initialization as an argument:
|
||||
|
||||
|
||||
```javascript
|
||||
class Tune {
|
||||
static prepare(config) {
|
||||
loadScript();
|
||||
insertNodes();
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Parameters
|
||||
|
||||
type | description |
|
||||
-- | -- |
|
||||
`object` | your Tune configuration |
|
||||
|
||||
|
||||
#### Return value
|
||||
|
||||
No return value
|
||||
|
||||
---
|
||||
|
||||
### static reset()
|
||||
|
||||
On Editor destroy you can use an opposite method `reset` to clean up all prepared data:
|
||||
|
||||
```javascript
|
||||
class Tune {
|
||||
static reset() {
|
||||
cleanUpScripts();
|
||||
deleteNodes();
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Parameters
|
||||
|
||||
No parameters
|
||||
|
||||
#### Return value
|
||||
|
||||
No return value
|
||||
|
||||
---
|
||||
|
||||
### static get sanitize()
|
||||
|
||||
If your Tune inserts any HTML markup into Block's content you need to provide sanitize configuration, so your HTML is not trimmed on save.
|
||||
|
||||
Please see more information at [sanitizer page](sanitizer.md).
|
||||
|
||||
|
||||
```javascript
|
||||
class Tune {
|
||||
static get sanitize() {
|
||||
return {
|
||||
sup: true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Format
|
||||
|
||||
Tunes data is saved to `tunes` property of output object:
|
||||
|
||||
```
|
||||
{
|
||||
blocks: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
data: {
|
||||
text: 'This is paragraph with Tune'
|
||||
},
|
||||
tunes: {
|
||||
'my-tune-name': {},
|
||||
favorite: true,
|
||||
anchor: 'might be string'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
|
@ -69,7 +69,7 @@ Check [Editor.js's community](https://github.com/editor-js/) to see Tools exampl
|
|||
## Create Editor instance
|
||||
|
||||
Create an instance of Editor.js and pass [Configuration Object](../src/types-internal/editor-config.ts).
|
||||
At least the `holderId` option is required.
|
||||
At least the `holder` option is required.
|
||||
|
||||
```html
|
||||
<div id="editorjs"></div>
|
||||
|
@ -92,7 +92,7 @@ var editor = new EditorJS({
|
|||
/**
|
||||
* Create a holder for the Editor and pass its ID
|
||||
*/
|
||||
holderId : 'editorjs',
|
||||
holder : 'editorjs',
|
||||
|
||||
/**
|
||||
* Available Tools list.
|
||||
|
@ -192,8 +192,9 @@ var editor = new EditorJS({
|
|||
|
||||
/**
|
||||
* onChange callback
|
||||
* Accepts CustomEvent describing what happened
|
||||
*/
|
||||
onChange: () => {console.log('Now I know that Editor\'s content changed!')}
|
||||
onChange: (editorAPI, event) => {console.log('Now I know that Editor\'s content changed!')}
|
||||
});
|
||||
```
|
||||
|
||||
|
|
100
docs/releases.md
Normal 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.
|
||||
|
||||
![](assets/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".
|
||||
|
||||
![](assets/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.
|
|
@ -129,8 +129,6 @@ Read more about Sanitizer configuration at the [Tools#sanitize](tools.md#sanitiz
|
|||
You can pass your Tool's title via `title` static getter. It can be used, for example, in the Tooltip with
|
||||
icon description that appears by hover.
|
||||
|
||||
![](https://capella.pics/00e7094a-fdb9-429b-8015-9c56f19b4ef5.jpg)
|
||||
|
||||
```ts
|
||||
export default class BoldInlineTool implements InlineTool {
|
||||
/**
|
||||
|
|
|
@ -56,9 +56,10 @@ Options that Tool can specify. All settings should be passed as static propertie
|
|||
|
||||
| Name | Type | Default Value | Description |
|
||||
| -- | -- | -- | -- |
|
||||
| `toolbox` | _Object_ | `undefined` | Pass here `icon` and `title` to display this `Tool` in the Editor's `Toolbox` <br /> `icon` - HTML string with icon for Toolbox <br /> `title` - optional title to display in Toolbox |
|
||||
| `toolbox` | _Object_ | `undefined` | Pass the `icon` and the `title` there to display this `Tool` in the Editor's `Toolbox` <br /> `icon` - HTML string with icon for the Toolbox <br /> `title` - title to be displayed at the Toolbox. <br /><br />May contain an array of `{icon, title, data}` to display the several variants of the tool, for example "Ordered list", "Unordered list". See details at [the documentation](https://editorjs.io/tools-api#toolbox) |
|
||||
| `enableLineBreaks` | _Boolean_ | `false` | With this option, Editor.js won't handle Enter keydowns. Can be helpful for Tools like `<code>` where line breaks should be handled by default behaviour. |
|
||||
| `isInline` | _Boolean_ | `false` | Describes Tool as a [Tool for the Inline Toolbar](tools-inline.md) |
|
||||
| `isTune` | _Boolean_ | `false` | Describes Tool as a [Block Tune](block-tunes.md) |
|
||||
| `sanitize` | _Object_ | `undefined` | Config for automatic sanitizing of saved data. See [Sanitize](#sanitize) section. |
|
||||
| `conversionConfig` | _Object_ | `undefined` | Config allows Tool to specify how it can be converted into/from another Tool. See [Conversion config](#conversion-config) section. |
|
||||
|
||||
|
@ -69,7 +70,7 @@ to the `tools` property of Editor Config.
|
|||
|
||||
```javascript
|
||||
var editor = new EditorJS({
|
||||
holderId : 'editorjs',
|
||||
holder : 'editorjs',
|
||||
tools: {
|
||||
text: {
|
||||
class: Text,
|
||||
|
@ -150,7 +151,7 @@ To handle pasted HTML elements object returned from `pasteConfig` getter should
|
|||
|
||||
For correct work you MUST provide `onPaste` handler at least for `defaultBlock` Tool.
|
||||
|
||||
> Example
|
||||
#### Example
|
||||
|
||||
Header Tool can handle `H1`-`H6` tags using paste handling API
|
||||
|
||||
|
@ -162,7 +163,27 @@ static get pasteConfig() {
|
|||
}
|
||||
```
|
||||
|
||||
> Same tag can be handled by one (first specified) Tool only.
|
||||
**Note. Same tag can be handled by one (first specified) Tool only.**
|
||||
|
||||
**Note. All attributes of pasted tag will be removed. To leave some attribute, you should explicitly specify them. Se below**
|
||||
|
||||
Let's suppose you want to leave the 'src' attribute when handle pasting of the `img` tags. Your config should look like this:
|
||||
|
||||
```javascript
|
||||
static get pasteConfig() {
|
||||
return {
|
||||
tags: [
|
||||
{
|
||||
img: {
|
||||
src: true
|
||||
}
|
||||
}
|
||||
],
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
[Read more](https://editorjs.io/sanitizer) about the sanitizing configuration.
|
||||
|
||||
### RegExp patterns handling
|
||||
|
||||
|
@ -389,7 +410,7 @@ static get sanitize() {
|
|||
|
||||
Editor.js has a Conversion Toolbar that allows user to convert one Block to another.
|
||||
|
||||
![](https://capella.pics/6c1f708b-a30c-4ffd-a427-5b59a1a472e0.jpg)
|
||||
![](assets/6c1f708b-a30c-4ffd-a427-5b59a1a472e0.jpg)
|
||||
|
||||
1. You can add ability to your Tool to be converted. Specify «export» property of `conversionConfig`.
|
||||
2. You can add ability to convert other Tools to your Tool. Specify «import» property of `conversionConfig`.
|
||||
|
|
|
@ -17,12 +17,12 @@ So how to use the Editor after [Installation](installation.md).
|
|||
|
||||
- Select text fragment and apply a style or insert a link from the Inline Toolbar
|
||||
|
||||
![](https://capella.pics/7ccbcfcd-1c49-4674-bea7-71021468a1bd.jpg)
|
||||
![](assets/7ccbcfcd-1c49-4674-bea7-71021468a1bd.jpg)
|
||||
|
||||
- Use «three-dots» button on the right to open Block Settings. From here, you can move and delete a Block
|
||||
or apply Tool's settings, if it provided. For example, set a Heading level or List style.
|
||||
|
||||
![](https://capella.pics/01a55381-46cd-47c7-b92e-34765434f2ca.jpg)
|
||||
![](assets/01a55381-46cd-47c7-b92e-34765434f2ca.jpg)
|
||||
|
||||
## Shortcuts
|
||||
|
||||
|
|
|
@ -1,189 +0,0 @@
|
|||
/**
|
||||
* Styles for the example page
|
||||
*/
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 1.5em;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.ce-example {
|
||||
font-size: 16.2px;
|
||||
}
|
||||
|
||||
.ce-example__header {
|
||||
border-bottom: 1px solid #E8E8EB;
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
display: flex;
|
||||
padding: 0 30px;
|
||||
margin-bottom: 30px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.ce-example__header a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.ce-example__header-logo {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.ce-example__header-menu {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
@media all and (max-width: 730px){
|
||||
.ce-example__header-menu {
|
||||
margin-left: 0;
|
||||
margin-top: 10px;
|
||||
flex-basis: 100%;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.ce-example__header-menu a {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
@media all and (max-width: 730px){
|
||||
.ce-example__header-menu a {
|
||||
margin-left: 0;
|
||||
margin-right: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.ce-example__content {
|
||||
max-width: 1100px;
|
||||
margin: 0 auto;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.ce-example__content--small {
|
||||
max-width: 500px;
|
||||
border-left: 1px solid #eee;
|
||||
border-right: 1px solid #eee;
|
||||
padding: 0 15px;
|
||||
}
|
||||
|
||||
.ce-example__output {
|
||||
background: #1B202B;
|
||||
overflow-x: auto;
|
||||
padding: 0 30px;
|
||||
}
|
||||
|
||||
.ce-example__output-content {
|
||||
max-width: 650px;
|
||||
margin: 30px auto;
|
||||
color: #ABADC3;
|
||||
font-family: 'PT Mono', Menlo, Monaco, Consolas, Courier New, monospace;
|
||||
font-size: 13.3px;
|
||||
}
|
||||
|
||||
.ce-example__output-content:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ce-example__button {
|
||||
display: block;
|
||||
margin: 50px auto;
|
||||
max-width: 180px;
|
||||
background: #4A9DF8;
|
||||
padding: 17px 30px;
|
||||
box-shadow: 0 22px 18px -4px rgba(137, 207, 255, 0.77);
|
||||
transition: all 150ms ease;
|
||||
cursor: pointer;
|
||||
border-radius: 31px;
|
||||
color: #fff;
|
||||
font-family: 'PT Mono', Menlo, Monaco, Consolas, Courier New, monospace;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.ce-example__button:hover {
|
||||
background: #3D8DE5;
|
||||
transform: translateY(2px);
|
||||
box-shadow: 0 20px 15px -4px rgba(137, 207, 255, 0.77);
|
||||
}
|
||||
|
||||
.ce-example__output-footer {
|
||||
padding: 30px 0;
|
||||
font-size: 14.2px;
|
||||
letter-spacing: 0.3px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.ce-example__output-footer a {
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.ce-example__statusbar {
|
||||
position: fixed;
|
||||
bottom: 10px;
|
||||
right: 10px;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.18);
|
||||
font-size: 12px;
|
||||
padding: 8px 15px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.ce-example__statusbar-button {
|
||||
display: inline-flex;
|
||||
margin-left: 10px;
|
||||
background: #4A9DF8;
|
||||
padding: 6px 12px;
|
||||
box-shadow: 0 7px 8px -4px rgba(137, 207, 255, 0.77);
|
||||
transition: all 150ms ease;
|
||||
cursor: pointer;
|
||||
border-radius: 31px;
|
||||
color: #fff;
|
||||
font-family: 'PT Mono', Menlo, Monaco, Consolas, Courier New, monospace;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media all and (max-width: 730px){
|
||||
.ce-example__header,
|
||||
.ce-example__content{
|
||||
padding: 0 20px;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON highlighter
|
||||
*/
|
||||
.sc_attr {
|
||||
color: rgb(148, 162, 192);
|
||||
}
|
||||
.sc_key {
|
||||
color: rgb(190, 213, 255);
|
||||
}
|
||||
.sc_toolname {
|
||||
color: rgb(15, 205, 251);
|
||||
}
|
||||
.sc_tag {
|
||||
color: rgb(4, 131, 216);
|
||||
}
|
||||
.sc_bool {
|
||||
color: rgb(247, 60, 173);
|
||||
}
|
||||
|
||||
.ce-example .ce-block:first-of-type h2.ce-header{
|
||||
font-size: 50px;
|
||||
}
|
||||
|
||||
.ce-example h2.ce-header{
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
.ce-example h3.ce-header {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.ce-example h4.ce-header {
|
||||
font-size: 18px;
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
<!--
|
||||
Use this page for debugging purposes.
|
||||
Use this page is for debugging purposes.
|
||||
|
||||
Editor Tools are loaded as git-submodules.
|
||||
You can pull modules by running `yarn pull_tools` and start experimenting.
|
||||
|
@ -10,11 +10,16 @@
|
|||
<meta charset="UTF-8">
|
||||
<title>Editor.js 🤩🧦🤨 example</title>
|
||||
<link href="https://fonts.googleapis.com/css?family=PT+Mono" rel="stylesheet">
|
||||
<link href="assets/demo.css" rel="stylesheet">
|
||||
<script src="assets/json-preview.js"></script>
|
||||
<link href="../public/assets/demo.css" rel="stylesheet">
|
||||
<script src="../public/assets/json-preview.js"></script>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
if (localStorage.getItem('theme') === 'dark') {
|
||||
document.body.classList.add("dark-mode");
|
||||
}
|
||||
</script>
|
||||
<div class="ce-example">
|
||||
<div class="ce-example__header">
|
||||
<a class="ce-example__header-logo" href="https://codex.so/editor">Editor.js 🤩🧦🤨</a>
|
||||
|
@ -26,21 +31,43 @@
|
|||
<a href="https://editorjs.io/creating-a-block-tool" target="_blank">API</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ce-example__content _ce-example__content--small">
|
||||
<div class="ce-example__content">
|
||||
<div id="editorjs"></div>
|
||||
<div id="hint" style="text-align: center;">
|
||||
No submodules found. Run <code class="inline-code">yarn pull_tools</code>
|
||||
<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 && yarn tools:update</code>
|
||||
</div>
|
||||
<div class="ce-example__button" id="saveButton">
|
||||
editor.save()
|
||||
</div>
|
||||
<div class="ce-example__statusbar">
|
||||
Readonly:
|
||||
<b id="readonly-state">
|
||||
Off
|
||||
</b>
|
||||
<div class="ce-example__statusbar-button" id="toggleReadOnlyButton">
|
||||
toggle
|
||||
<div class="ce-example__statusbar-item">
|
||||
Readonly:
|
||||
<b id="readonly-state">
|
||||
Off
|
||||
</b>
|
||||
|
||||
<div class="ce-example__statusbar-button" id="toggleReadOnlyButton">
|
||||
toggle
|
||||
</div>
|
||||
</div>
|
||||
<div class="ce-example__statusbar-item">
|
||||
<div class="ce-example__statusbar-button" id="showBlocksBoundariesButton">
|
||||
<span data-toggled-text="Hide">Show</span>
|
||||
blocks boundaries
|
||||
</div>
|
||||
</div>
|
||||
<div class="ce-example__statusbar-item">
|
||||
<div class="ce-example__statusbar-button" id="enableThinModeButton">
|
||||
<span data-toggled-text="Disable">Enable</span>
|
||||
thin mode
|
||||
</div>
|
||||
</div>
|
||||
<div class="ce-example__statusbar-item ce-example__statusbar-item--right">
|
||||
<div class="ce-example__statusbar-toggler" id="darkThemeToggler">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -62,32 +89,33 @@
|
|||
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/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 -->
|
||||
<script src="./tools/checklist/dist/bundle.js"></script><!-- Checklist -->
|
||||
<script src="./tools/quote/dist/bundle.js"></script><!-- Quote -->
|
||||
<script src="./tools/code/dist/bundle.js"></script><!-- Code -->
|
||||
<script src="./tools/embed/dist/bundle.js"></script><!-- Embed -->
|
||||
<script src="./tools/table/dist/bundle.js"></script><!-- Table -->
|
||||
<script src="./tools/link/dist/bundle.js"></script><!-- Link -->
|
||||
<script src="./tools/raw/dist/bundle.js"></script><!-- Raw -->
|
||||
<script src="./tools/warning/dist/bundle.js"></script><!-- Warning -->
|
||||
<script src="./tools/header/dist/header.umd.js" onload="document.getElementById('hint-tools').hidden = true"></script><!-- Header -->
|
||||
<script src="./tools/simple-image/dist/simple-image.umd.js"></script><!-- Image -->
|
||||
<script src="./tools/delimiter/dist/delimiter.umd.js"></script><!-- Delimiter -->
|
||||
<!-- <script src="./tools/list/dist/list.umd.js"></script> List-->
|
||||
<script src="./tools/nested-list/dist/nested-list.umd.js"></script><!-- Nested List -->
|
||||
<script src="./tools/checklist/dist/checklist.umd.js"></script><!-- Checklist -->
|
||||
<script src="./tools/quote/dist/quote.umd.js"></script><!-- Quote -->
|
||||
<script src="./tools/code/dist/code.umd.js"></script><!-- Code -->
|
||||
<script src="./tools/embed/dist/embed.umd.js"></script><!-- Embed -->
|
||||
<script src="./tools/table/dist/table.umd.js"></script><!-- Table -->
|
||||
<script src="./tools/link/dist/link.umd.js"></script><!-- Link -->
|
||||
<script src="./tools/raw/dist/raw.umd.js"></script><!-- Raw -->
|
||||
<script src="./tools/warning/dist/warning.umd.js"></script><!-- Warning -->
|
||||
|
||||
<script src="./tools/marker/dist/bundle.js"></script><!-- Marker -->
|
||||
<script src="./tools/inline-code/dist/bundle.js"></script><!-- Inline Code -->
|
||||
<script src="./tools/marker/dist/marker.umd.js"></script><!-- Marker -->
|
||||
<script src="./tools/inline-code/dist/inline-code.umd.js"></script><!-- Inline Code -->
|
||||
|
||||
<!-- Load Editor.js's Core -->
|
||||
<script src="../dist/editor.js"></script>
|
||||
<script src="../dist/editorjs.umd.js" onload="document.getElementById('hint-core').hidden = true;"></script>
|
||||
|
||||
<!-- Initialization -->
|
||||
<script>
|
||||
/**
|
||||
* To initialize the Editor, create a new instance with configuration object
|
||||
* @see docs/installation.md for mode details
|
||||
* Editor init config
|
||||
* @see https://editorjs.io/configuration
|
||||
*/
|
||||
var editor = new EditorJS({
|
||||
const editorConfig = {
|
||||
/**
|
||||
* Enable/Disable the read only mode
|
||||
*/
|
||||
|
@ -128,7 +156,7 @@
|
|||
image: SimpleImage,
|
||||
|
||||
list: {
|
||||
class: List,
|
||||
class: NestedList,
|
||||
inlineToolbar: true,
|
||||
shortcut: 'CMD+SHIFT+L'
|
||||
},
|
||||
|
@ -192,92 +220,115 @@
|
|||
data: {
|
||||
blocks: [
|
||||
{
|
||||
id: "zcKCF1S7X8",
|
||||
type: "header",
|
||||
data: {
|
||||
text: "Editor.js",
|
||||
level: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "b6ji-DvaKb",
|
||||
"type": "paragraph",
|
||||
"data": {
|
||||
"text": "Hey. Meet the new Editor. On this page you can see it in action — try to edit this text. Source code of the page contains the example of connection and configuration."
|
||||
}
|
||||
},
|
||||
{
|
||||
type: "header",
|
||||
id: "7ItVl5biRo",
|
||||
data: {
|
||||
text: "Key features",
|
||||
level: 2
|
||||
}
|
||||
},
|
||||
{
|
||||
type : 'paragraph',
|
||||
data : {
|
||||
text : 'Hey. Meet the new Editor. On this page you can see it in action — try to edit this text. Source code of the page contains the example of connection and configuration.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: "header",
|
||||
data: {
|
||||
text: "Key features",
|
||||
level: 3
|
||||
}
|
||||
},
|
||||
{
|
||||
type : 'list',
|
||||
id: "SSBSguGvP7",
|
||||
data : {
|
||||
items : [
|
||||
'It is a block-styled editor',
|
||||
'It returns clean data output in JSON',
|
||||
'Designed to be extendable and pluggable with a simple API',
|
||||
{
|
||||
content: 'It is a block-styled editor',
|
||||
items: []
|
||||
},
|
||||
{
|
||||
content: 'It returns clean data output in JSON',
|
||||
items: []
|
||||
},
|
||||
{
|
||||
content: 'Designed to be extendable and pluggable with a simple API',
|
||||
items: []
|
||||
}
|
||||
],
|
||||
style: 'unordered'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: "header",
|
||||
id: "QZFox1m_ul",
|
||||
data: {
|
||||
text: "What does it mean «block-styled editor»",
|
||||
level: 3
|
||||
level: 2
|
||||
}
|
||||
},
|
||||
{
|
||||
type : 'paragraph',
|
||||
id: "bwnFX5LoX7",
|
||||
data : {
|
||||
text : 'Workspace in classic editors is made of a single contenteditable element, used to create different HTML markups. Editor.js <mark class=\"cdx-marker\">workspace consists of separate Blocks: paragraphs, headings, images, lists, quotes, etc</mark>. Each of them is an independent contenteditable element (or more complex structure) provided by Plugin and united by Editor\'s Core.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type : 'paragraph',
|
||||
id: "mTrPOHAQTe",
|
||||
data : {
|
||||
text : `There are dozens of <a href="https://github.com/editor-js">ready-to-use Blocks</a> and the <a href="https://editorjs.io/creating-a-block-tool">simple API</a> for creation any Block you need. For example, you can implement Blocks for Tweets, Instagram posts, surveys and polls, CTA-buttons and even games.`
|
||||
}
|
||||
},
|
||||
{
|
||||
type: "header",
|
||||
id: "1sYMhUrznu",
|
||||
data: {
|
||||
text: "What does it mean clean data output",
|
||||
level: 3
|
||||
level: 2
|
||||
}
|
||||
},
|
||||
{
|
||||
type : 'paragraph',
|
||||
id: "jpd7WEXrJG",
|
||||
data : {
|
||||
text : 'Classic WYSIWYG-editors produce raw HTML-markup with both content data and content appearance. On the contrary, Editor.js outputs JSON object with data of each Block. You can see an example below'
|
||||
}
|
||||
},
|
||||
{
|
||||
type : 'paragraph',
|
||||
id: "0lOGNUKxqt",
|
||||
data : {
|
||||
text : `Given data can be used as you want: render with HTML for <code class="inline-code">Web clients</code>, render natively for <code class="inline-code">mobile apps</code>, create markup for <code class="inline-code">Facebook Instant Articles</code> or <code class="inline-code">Google AMP</code>, generate an <code class="inline-code">audio version</code> and so on.`
|
||||
}
|
||||
},
|
||||
{
|
||||
type : 'paragraph',
|
||||
id: "WvX7kBjp0I",
|
||||
data : {
|
||||
text : 'Clean data is useful to sanitize, validate and process on the backend.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type : 'delimiter',
|
||||
id: "H9LWKQ3NYd",
|
||||
data : {}
|
||||
},
|
||||
{
|
||||
type : 'paragraph',
|
||||
id: "h298akk2Ad",
|
||||
data : {
|
||||
text : 'We have been working on this project more than three years. Several large media projects help us to test and debug the Editor, to make its core more stable. At the same time we significantly improved the API. Now, it can be used to create any plugin for any task. Hope you enjoy. 😏'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'image',
|
||||
id: "9802bjaAA2",
|
||||
data: {
|
||||
url: 'assets/codex2x.png',
|
||||
caption: '',
|
||||
|
@ -291,10 +342,15 @@
|
|||
onReady: function(){
|
||||
saveButton.click();
|
||||
},
|
||||
onChange: function() {
|
||||
console.log('something changed');
|
||||
onChange: function(api, event) {
|
||||
console.log('something changed', event);
|
||||
},
|
||||
});
|
||||
}
|
||||
/**
|
||||
* To initialize the Editor, create a new instance with configuration object
|
||||
* @see docs/installation.md for mode details
|
||||
*/
|
||||
var editor = new EditorJS(editorConfig);
|
||||
|
||||
/**
|
||||
* Saving button
|
||||
|
@ -328,6 +384,39 @@
|
|||
|
||||
readOnlyIndicator.textContent = readOnlyState ? 'On' : 'Off';
|
||||
});
|
||||
|
||||
/**
|
||||
* Button for displaying blocks borders. Useful for UI development
|
||||
*/
|
||||
const showBlocksBoundariesButton = document.getElementById("showBlocksBoundariesButton");
|
||||
|
||||
showBlocksBoundariesButton.addEventListener('click', () => {
|
||||
document.body.classList.toggle("show-block-boundaries")
|
||||
})
|
||||
|
||||
/**
|
||||
* Button for enabling the 'Thin' mode
|
||||
*/
|
||||
const enableThinModeButton = document.getElementById("enableThinModeButton");
|
||||
|
||||
enableThinModeButton.addEventListener('click', () => {
|
||||
document.body.classList.toggle("thin-mode")
|
||||
|
||||
editor.destroy();
|
||||
|
||||
editor = new EditorJS(editorConfig);
|
||||
})
|
||||
|
||||
/**
|
||||
* Toggler for toggling the dark mode
|
||||
*/
|
||||
const darkThemeToggler = document.getElementById("darkThemeToggler");
|
||||
|
||||
darkThemeToggler.addEventListener('click', () => {
|
||||
document.body.classList.toggle("dark-mode");
|
||||
|
||||
localStorage.setItem('theme', document.body.classList.contains("dark-mode") ? 'dark' : 'default');
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -10,8 +10,8 @@
|
|||
<meta charset="UTF-8">
|
||||
<title>Editor.js 🤩🧦🤨 example</title>
|
||||
<link href="https://fonts.googleapis.com/css?family=PT+Mono" rel="stylesheet">
|
||||
<link href="assets/demo.css" rel="stylesheet">
|
||||
<script src="assets/json-preview.js"></script>
|
||||
<link href="../public/assets/demo.css" rel="stylesheet">
|
||||
<script src="../public/assets/json-preview.js"></script>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
</head>
|
||||
<body>
|
||||
|
@ -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/editorjs.umd.js" onload="document.getElementById('hint-core').hidden = true"></script>
|
||||
|
||||
<!-- Initialization -->
|
||||
<script>
|
||||
|
@ -190,8 +193,12 @@
|
|||
},
|
||||
"toolbar": {
|
||||
"toolbox": {
|
||||
"Add": "Добавить"
|
||||
"Add": "Добавить",
|
||||
}
|
||||
},
|
||||
"popover": {
|
||||
"Filter": "Поиск",
|
||||
"Nothing found": "Ничего не найдено"
|
||||
}
|
||||
},
|
||||
|
||||
|
|
76
example/example-multiple.html
Normal 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="../public/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/editorjs.umd.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>
|
131
example/example-popup.html
Normal file
|
@ -0,0 +1,131 @@
|
|||
|
||||
<!--
|
||||
Use this page for debugging purposes.
|
||||
|
||||
This page can be used for testing editor nested in a popup.
|
||||
-->
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Editor.js 🤩🧦🤨 example: Popup</title>
|
||||
<link href="../public/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 ce-example--popup">
|
||||
<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="stub">
|
||||
<h1>Base concepts</h1>
|
||||
<p>
|
||||
Editor.js is a block-style editor for rich media stories. It outputs clean data in JSON instead of heavy HTML markup. And more important thing is that Editor.js is designed to be API extendable and pluggable.
|
||||
</p>
|
||||
<p>
|
||||
So there are a few key features:
|
||||
</p>
|
||||
<ul>
|
||||
<li>Clean data output</li>
|
||||
<li>API pluggable</li>
|
||||
<li>Open source</li>
|
||||
</ul>
|
||||
<h2>
|
||||
What does it mean block-styled
|
||||
</h2>
|
||||
<p>
|
||||
In other editors, the workspace is provided by single contenteditable element in where you can create different HTML markup. All of us saw permanent bugs with moving text fragments or scaling images, while page parts are jumping and twitches. Or highlighting big parts of the text in the case when you just want to make few words to be a heading or bold.
|
||||
</p>
|
||||
<p>
|
||||
The Editor.js workspace consists of separate Blocks: paragraphs, headings, images, lists, quotes, etc. Each of them is an independent contenteditable element (or more complex structure) provided by Plugin and united by Editor's Core.
|
||||
</p>
|
||||
<p>
|
||||
At the same time, most useful features as arrow-navigation, copy & paste, cross block selection, and others works almost as in the familiar editors.
|
||||
</p>
|
||||
<h2>
|
||||
What is clean data
|
||||
</h2>
|
||||
<p>
|
||||
But the more interesting thing is, as mentioned above, that Editor.js returns clean data instead of HTML-markup. Take a look at the example.
|
||||
</p>
|
||||
<p>
|
||||
If our entry consists of few paragraphs and a heading, in popular Medium editor after saving we will have something like this:
|
||||
</p>
|
||||
<p>
|
||||
As you can see, there are only data we need: a list of structural Blocks with their content description.
|
||||
</p>
|
||||
<p>
|
||||
You can use this data to easily render in Web, native mobile/desktop application, pass to Audio Readers, create templates for Facebook Instant Articles, AMP, RSS, create chat-bots, and many others.
|
||||
</p>
|
||||
<p>
|
||||
Also, the clean data can be useful for backend processing: sanitizing, validation, injecting an advertising or other stuff, extracting Headings, make covers for social networks from Image Blocks, and other.
|
||||
</p>
|
||||
<h2>
|
||||
API pluggable?
|
||||
</h2>
|
||||
<p>
|
||||
A key value of the Editor is the API. All main functional units of the editor — Blocks, Inline Formatting Tools, Block Tunes — are provided by external plugins that use Editor's API.
|
||||
</p>
|
||||
<p>
|
||||
We decide to extract all these Tools to separate scripts to make Editor's Core more abstract and make API more powerful. Any challenges and tasks you are facing can be implemented by your own plugins using the API.
|
||||
</p>
|
||||
<p>
|
||||
At the same time, API is created to be easy-to-understand and simple-to-use.
|
||||
</p>
|
||||
<h2>
|
||||
Open Source, so?
|
||||
</h2>
|
||||
<p>
|
||||
Editor.js is more than just an editor. It is a big open-source community of developers and contributors. Anyone can suggest an improvement or a bug fix. Anyone can create new cool API features and plugins.
|
||||
</p>
|
||||
<p>
|
||||
We will support each developer of Editor.js plugins: the best solutions will be collected to the Awesome List and promoted to the community. Together we can create a big suite of different Blocks, Inline Tools, Block Tunes that can hit a wide specter of tasks.
|
||||
</p>
|
||||
<p>
|
||||
Thanks for your interest. Hope you enjoy Editor.js.
|
||||
</p>
|
||||
</div>
|
||||
<div class="ce-example-popup">
|
||||
<div class="ce-example-popup__overlay"></div>
|
||||
<div class="ce-example-popup__popup">
|
||||
<div id="editorjs"></div>
|
||||
</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/editorjs.umd.js" onload="document.getElementById('hint-core').hidden = true"></script>
|
||||
<script src="./tools/header/dist/bundle.js"></script><!-- Header -->
|
||||
|
||||
<!-- Initialization -->
|
||||
<script>
|
||||
var editor1 = new EditorJS({
|
||||
holder: 'editorjs',
|
||||
tools: {
|
||||
header: {
|
||||
class: Header,
|
||||
shortcut: 'CMD+SHIFT+H'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -9,8 +9,8 @@
|
|||
<meta charset="UTF-8">
|
||||
<title>Editor.js RTL example</title>
|
||||
<link href="https://fonts.googleapis.com/css?family=PT+Mono" rel="stylesheet">
|
||||
<link href="assets/demo.css" rel="stylesheet">
|
||||
<script src="assets/json-preview.js"></script>
|
||||
<link href="../public/assets/demo.css" rel="stylesheet">
|
||||
<script src="../public/assets/json-preview.js"></script>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
</head>
|
||||
<body>
|
||||
|
@ -27,8 +27,11 @@
|
|||
</div>
|
||||
<div class="ce-example__content _ce-example__content--small">
|
||||
<div id="editorjs"></div>
|
||||
<div id="hint" style="text-align: center;">
|
||||
No submodules found. Run <code class="inline-code">yarn pull_tools</code>
|
||||
<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 && yarn tools:update</code>
|
||||
</div>
|
||||
<div class="ce-example__button" id="saveButton">
|
||||
editor.save()
|
||||
|
@ -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 -->
|
||||
|
@ -58,7 +61,7 @@
|
|||
<script src="./tools/quote/dist/bundle.js"></script><!-- Quote -->
|
||||
<script src="./tools/code/dist/bundle.js"></script><!-- Code -->
|
||||
<script src="./tools/embed/dist/bundle.js"></script><!-- Embed -->
|
||||
<script src="./tools/table/dist/bundle.js"></script><!-- Table -->
|
||||
<script src="./tools/table/dist/table.js"></script><!-- Table -->
|
||||
<script src="./tools/link/dist/bundle.js"></script><!-- Link -->
|
||||
<script src="./tools/raw/dist/bundle.js"></script><!-- Raw -->
|
||||
<script src="./tools/warning/dist/bundle.js"></script><!-- Warning -->
|
||||
|
@ -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/editorjs.umd.js" onload="document.getElementById('hint-core').hidden = true"></script>
|
||||
|
||||
<!-- Initialization -->
|
||||
<script>
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
<meta charset="UTF-8">
|
||||
<title>Editor.js 🤩🧦🤨 example</title>
|
||||
<link href="https://fonts.googleapis.com/css?family=PT+Mono" rel="stylesheet">
|
||||
<link href="assets/demo.css" rel="stylesheet">
|
||||
<script src="assets/json-preview.js"></script>
|
||||
<link href="../public/assets/demo.css" rel="stylesheet">
|
||||
<script src="../public/assets/json-preview.js"></script>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
</head>
|
||||
<body>
|
||||
|
@ -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>
|
||||
|
@ -281,8 +281,8 @@
|
|||
onReady: function(){
|
||||
saveButton.click();
|
||||
},
|
||||
onChange: function() {
|
||||
console.log('something changed');
|
||||
onChange: function(api, event) {
|
||||
console.log('something changed', event);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 197d5d53e0c7d869d76afc4eabae566d391e6d0e
|
||||
Subproject commit 1c116d5e09e19951948d6166047aa2f30877aaf9
|
|
@ -1 +1 @@
|
|||
Subproject commit 83d2d9d3136d48ab67b52bc034370c9a26c82c8c
|
||||
Subproject commit f281996f82c7ac676172757e45687cae27443427
|
|
@ -1 +1 @@
|
|||
Subproject commit 1f2ec8c709a94c5f4f499bb1aaba7f4930e10484
|
||||
Subproject commit 4ca1c1c972261f47dd34f6b8754763a4a79a4866
|
|
@ -1 +1 @@
|
|||
Subproject commit 9d3d4b5216dce1a933b1e92b45b7b8c404d889b7
|
||||
Subproject commit dfdbf2423d2777f7026a7df768c6582e1a409db7
|
|
@ -1 +1 @@
|
|||
Subproject commit 2b21da39b57d0abfcd4979444fb0c3d2d435af7d
|
||||
Subproject commit 5118ce87a752515fb6b31325f234f4ccd62f42c9
|
|
@ -1 +1 @@
|
|||
Subproject commit a983c4e62135c88d6cfd926527e6fc92c304451b
|
||||
Subproject commit 25d46cd8d3930851b14ddc26ee80fb5b485e1496
|
|
@ -1 +1 @@
|
|||
Subproject commit 051b8e9e03e2f6bea30875da8253f6c0d858b231
|
||||
Subproject commit dcd4c17740c9ba636140751596aff1e9f6ef6b01
|
|
@ -1 +1 @@
|
|||
Subproject commit 6a5563630977f223ebafaa03a7df3bf85797437b
|
||||
Subproject commit aaa69d5408bad34027d6252a3892d40f9fa121be
|
|
@ -1 +1 @@
|
|||
Subproject commit 458a5fe364e33ad5b5913155d665f335ab742e0f
|
||||
Subproject commit a6dc6a692b88c9eff3d87223b239e7517b160c67
|
|
@ -1 +1 @@
|
|||
Subproject commit 6708697c1af79abbf6650f0f14e1cedc45eb5213
|
||||
Subproject commit 8d6897fca43e387bcdf4a681380be975fe8f2a07
|
1
example/tools/nested-list
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 95b37462dc93c19b83f0481f509034a40d436cf2
|
|
@ -1 +1 @@
|
|||
Subproject commit 07881fc1020fde79ab9468f740227a99935abb2a
|
||||
Subproject commit 9377ca713f552576b8b11f77cf371b67261ec00b
|
|
@ -1 +1 @@
|
|||
Subproject commit 3f40a9cfdb0086c94ee2c7295a96d69c9b266dec
|
||||
Subproject commit cae470fded570ef9a82a45734526ccf45959e204
|
|
@ -1 +1 @@
|
|||
Subproject commit 1883b28d8aac863d3907c21d5fda231c8e6f799a
|
||||
Subproject commit 963883520c7bbe5040366335c9a37bbdc7cf60fd
|
|
@ -1 +1 @@
|
|||
Subproject commit 5c1a73a8022c18ac1c15ee8d0134caae029bfbe9
|
||||
Subproject commit 2948cd7595e632f7555e2dc09e6bac050a2b87ea
|
1
example/tools/text-variant-tune
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 02538b1daea722c854cc61b6fbca01b746c21717
|
|
@ -1 +1 @@
|
|||
Subproject commit c0507d91014f9b4fdb514f0499348d7619e3e1a2
|
||||
Subproject commit e63e91aa833d774be9bf4a76013b1025a009989d
|
413
index.html
Normal file
|
@ -0,0 +1,413 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Editor.js 🤩🧦🤨 example</title>
|
||||
<link href="https://fonts.googleapis.com/css?family=PT+Mono" rel="stylesheet">
|
||||
<link href="/assets/demo.css" rel="stylesheet">
|
||||
<script src="/assets/json-preview.js"></script>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
if (localStorage.getItem('theme') === 'dark') {
|
||||
document.body.classList.add("dark-mode");
|
||||
}
|
||||
</script>
|
||||
<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">
|
||||
<div id="editorjs"></div>
|
||||
<div class="ce-example__button" id="saveButton">
|
||||
editor.save()
|
||||
</div>
|
||||
<div class="ce-example__statusbar">
|
||||
<div class="ce-example__statusbar-item">
|
||||
Readonly:
|
||||
<b id="readonly-state">
|
||||
Off
|
||||
</b>
|
||||
|
||||
<div class="ce-example__statusbar-button" id="toggleReadOnlyButton">
|
||||
toggle
|
||||
</div>
|
||||
</div>
|
||||
<div class="ce-example__statusbar-item">
|
||||
<div class="ce-example__statusbar-button" id="showBlocksBoundariesButton">
|
||||
<span data-toggled-text="Hide">Show</span>
|
||||
blocks boundaries
|
||||
</div>
|
||||
</div>
|
||||
<div class="ce-example__statusbar-item">
|
||||
<div class="ce-example__statusbar-button" id="enableThinModeButton">
|
||||
<span data-toggled-text="Disable">Enable</span>
|
||||
thin mode
|
||||
</div>
|
||||
</div>
|
||||
<div class="ce-example__statusbar-item ce-example__statusbar-item--right">
|
||||
<div class="ce-example__statusbar-toggler" id="darkThemeToggler">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ce-example__output">
|
||||
<pre class="ce-example__output-content" id="output"></pre>
|
||||
|
||||
<div class="ce-example__output-footer">
|
||||
<a href="https://codex.so" style="font-weight: bold;">Made by CodeX</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Load Tools -->
|
||||
<!--
|
||||
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 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 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/@editorjs/delimiter@latest"></script><!-- Delimiter -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/@editorjs/nested-list@latest"></script><!-- List -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/@editorjs/checklist@latest"></script><!-- Checklist -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/@editorjs/quote@latest"></script><!-- Quote -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/@editorjs/code@latest"></script><!-- Code -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/@editorjs/embed@latest"></script><!-- Embed -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/@editorjs/table@latest"></script><!-- Table -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/@editorjs/link@latest"></script><!-- Link -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/@editorjs/warning@latest"></script><!-- Warning -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/@editorjs/raw@latest"></script><!-- Raw -->
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/@editorjs/marker@latest"></script><!-- Marker -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/@editorjs/inline-code@latest"></script><!-- Inline Code -->
|
||||
|
||||
<!-- Initialization -->
|
||||
<script type="module">
|
||||
import EditorJS from './src/codex.ts';
|
||||
|
||||
window.EditorJS = EditorJS;
|
||||
|
||||
/**
|
||||
* To initialize the Editor, create a new instance with configuration object
|
||||
* @see docs/installation.md for mode details
|
||||
*/
|
||||
const editorConfig = {
|
||||
/**
|
||||
* Enable/Disable the read only mode
|
||||
*/
|
||||
readOnly: false,
|
||||
|
||||
/**
|
||||
* Wrapper of Editor
|
||||
*/
|
||||
holder: 'editorjs',
|
||||
|
||||
/**
|
||||
* Common Inline Toolbar settings
|
||||
* - if true (or not specified), the order from 'tool' property will be used
|
||||
* - if an array of tool names, this order will be used
|
||||
*/
|
||||
// inlineToolbar: ['link', 'marker', 'bold', 'italic'],
|
||||
// inlineToolbar: true,
|
||||
|
||||
/**
|
||||
* Tools list
|
||||
*/
|
||||
tools: {
|
||||
/**
|
||||
* Each Tool is a Plugin. Pass them via 'class' option with necessary settings {@link docs/tools.md}
|
||||
*/
|
||||
header: {
|
||||
class: Header,
|
||||
inlineToolbar: ['marker', 'link'],
|
||||
config: {
|
||||
placeholder: 'Header'
|
||||
},
|
||||
shortcut: 'CMD+SHIFT+H'
|
||||
},
|
||||
|
||||
/**
|
||||
* Or pass class directly without any configuration
|
||||
*/
|
||||
image: SimpleImage,
|
||||
|
||||
list: {
|
||||
class: NestedList,
|
||||
inlineToolbar: true,
|
||||
shortcut: 'CMD+SHIFT+L'
|
||||
},
|
||||
|
||||
checklist: {
|
||||
class: Checklist,
|
||||
inlineToolbar: true,
|
||||
},
|
||||
|
||||
quote: {
|
||||
class: Quote,
|
||||
inlineToolbar: true,
|
||||
config: {
|
||||
quotePlaceholder: 'Enter a quote',
|
||||
captionPlaceholder: 'Quote\'s author',
|
||||
},
|
||||
shortcut: 'CMD+SHIFT+O'
|
||||
},
|
||||
|
||||
warning: Warning,
|
||||
|
||||
marker: {
|
||||
class: Marker,
|
||||
shortcut: 'CMD+SHIFT+M'
|
||||
},
|
||||
|
||||
code: {
|
||||
class: CodeTool,
|
||||
shortcut: 'CMD+SHIFT+C'
|
||||
},
|
||||
|
||||
delimiter: Delimiter,
|
||||
|
||||
inlineCode: {
|
||||
class: InlineCode,
|
||||
shortcut: 'CMD+SHIFT+C'
|
||||
},
|
||||
|
||||
linkTool: LinkTool,
|
||||
|
||||
raw: RawTool,
|
||||
|
||||
embed: Embed,
|
||||
|
||||
table: {
|
||||
class: Table,
|
||||
inlineToolbar: true,
|
||||
shortcut: 'CMD+ALT+T'
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* This Tool will be used as default
|
||||
*/
|
||||
// defaultBlock: 'paragraph',
|
||||
|
||||
/**
|
||||
* Initial Editor data
|
||||
*/
|
||||
data: {
|
||||
blocks: [
|
||||
{
|
||||
id: "zcKCF1S7X8",
|
||||
type: "header",
|
||||
data: {
|
||||
text: "Editor.js",
|
||||
level: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "b6ji-DvaKb",
|
||||
type: "paragraph",
|
||||
data: {
|
||||
text: "Hey. Meet the new Editor. On this page you can see it in action — try to edit this text. Source code of the page contains the example of connection and configuration."
|
||||
}
|
||||
},
|
||||
{
|
||||
type: "header",
|
||||
id: "7ItVl5biRo",
|
||||
data: {
|
||||
text: "Key features",
|
||||
level: 2
|
||||
}
|
||||
},
|
||||
{
|
||||
type : 'list',
|
||||
id: "SSBSguGvP7",
|
||||
data : {
|
||||
items : [
|
||||
{
|
||||
content: 'It is a block-styled editor',
|
||||
items: []
|
||||
},
|
||||
{
|
||||
content: 'It returns clean data output in JSON',
|
||||
items: []
|
||||
},
|
||||
{
|
||||
content: 'Designed to be extendable and pluggable with a simple API',
|
||||
items: []
|
||||
}
|
||||
],
|
||||
style: 'unordered'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: "header",
|
||||
id: "QZFox1m_ul",
|
||||
data: {
|
||||
text: "What does it mean «block-styled editor»",
|
||||
level: 2
|
||||
}
|
||||
},
|
||||
{
|
||||
type : 'paragraph',
|
||||
id: "bwnFX5LoX7",
|
||||
data : {
|
||||
text : 'Workspace in classic editors is made of a single contenteditable element, used to create different HTML markups. Editor.js <mark class=\"cdx-marker\">workspace consists of separate Blocks: paragraphs, headings, images, lists, quotes, etc</mark>. Each of them is an independent contenteditable element (or more complex structure) provided by Plugin and united by Editor\'s Core.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type : 'paragraph',
|
||||
id: "mTrPOHAQTe",
|
||||
data : {
|
||||
text : `There are dozens of <a href="https://github.com/editor-js">ready-to-use Blocks</a> and the <a href="https://editorjs.io/creating-a-block-tool">simple API</a> for creation any Block you need. For example, you can implement Blocks for Tweets, Instagram posts, surveys and polls, CTA-buttons and even games.`
|
||||
}
|
||||
},
|
||||
{
|
||||
type: "header",
|
||||
id: "1sYMhUrznu",
|
||||
data: {
|
||||
text: "What does it mean clean data output",
|
||||
level: 2
|
||||
}
|
||||
},
|
||||
{
|
||||
type : 'paragraph',
|
||||
id: "jpd7WEXrJG",
|
||||
data : {
|
||||
text : 'Classic WYSIWYG-editors produce raw HTML-markup with both content data and content appearance. On the contrary, Editor.js outputs JSON object with data of each Block. You can see an example below'
|
||||
}
|
||||
},
|
||||
{
|
||||
type : 'paragraph',
|
||||
id: "0lOGNUKxqt",
|
||||
data : {
|
||||
text : `Given data can be used as you want: render with HTML for <code class="inline-code">Web clients</code>, render natively for <code class="inline-code">mobile apps</code>, create markup for <code class="inline-code">Facebook Instant Articles</code> or <code class="inline-code">Google AMP</code>, generate an <code class="inline-code">audio version</code> and so on.`
|
||||
}
|
||||
},
|
||||
{
|
||||
type : 'paragraph',
|
||||
id: "WvX7kBjp0I",
|
||||
data : {
|
||||
text : 'Clean data is useful to sanitize, validate and process on the backend.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type : 'delimiter',
|
||||
id: "H9LWKQ3NYd",
|
||||
data : {}
|
||||
},
|
||||
{
|
||||
type : 'paragraph',
|
||||
id: "h298akk2Ad",
|
||||
data : {
|
||||
text : 'We have been working on this project more than three years. Several large media projects help us to test and debug the Editor, to make its core more stable. At the same time we significantly improved the API. Now, it can be used to create any plugin for any task. Hope you enjoy. 😏'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'image',
|
||||
id: "9802bjaAA2",
|
||||
data: {
|
||||
url: '/assets/codex2x.png',
|
||||
caption: '',
|
||||
stretched: false,
|
||||
withBorder: true,
|
||||
withBackground: false,
|
||||
}
|
||||
},
|
||||
]
|
||||
},
|
||||
onReady: function(){
|
||||
saveButton.click();
|
||||
},
|
||||
onChange: function(api, event) {
|
||||
console.log('something changed', event);
|
||||
},
|
||||
}
|
||||
/**
|
||||
* To initialize the Editor, create a new instance with configuration object
|
||||
* @see docs/installation.md for mode details
|
||||
*/
|
||||
var editor = new EditorJS(editorConfig);
|
||||
|
||||
/**
|
||||
* Saving button
|
||||
*/
|
||||
const saveButton = document.getElementById('saveButton');
|
||||
|
||||
/**
|
||||
* Toggle read-only button
|
||||
*/
|
||||
const toggleReadOnlyButton = document.getElementById('toggleReadOnlyButton');
|
||||
const readOnlyIndicator = document.getElementById('readonly-state');
|
||||
|
||||
/**
|
||||
* Saving example
|
||||
*/
|
||||
saveButton.addEventListener('click', function () {
|
||||
editor.save()
|
||||
.then((savedData) => {
|
||||
cPreview.show(savedData, document.getElementById("output"));
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Saving error', error);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Toggle read-only example
|
||||
*/
|
||||
toggleReadOnlyButton.addEventListener('click', async () => {
|
||||
const readOnlyState = await editor.readOnly.toggle();
|
||||
|
||||
readOnlyIndicator.textContent = readOnlyState ? 'On' : 'Off';
|
||||
});
|
||||
|
||||
/**
|
||||
* Button for displaying blocks borders. Useful for UI development
|
||||
*/
|
||||
const showBlocksBoundariesButton = document.getElementById("showBlocksBoundariesButton");
|
||||
|
||||
showBlocksBoundariesButton.addEventListener('click', () => {
|
||||
document.body.classList.toggle("show-block-boundaries")
|
||||
})
|
||||
|
||||
/**
|
||||
* Button for enabling the 'Thin' mode
|
||||
*/
|
||||
const enableThinModeButton = document.getElementById("enableThinModeButton");
|
||||
|
||||
enableThinModeButton.addEventListener('click', () => {
|
||||
document.body.classList.toggle("thin-mode")
|
||||
|
||||
editor.destroy();
|
||||
|
||||
editor = new EditorJS(editorConfig);
|
||||
})
|
||||
|
||||
/**
|
||||
* Toggler for toggling the dark mode
|
||||
*/
|
||||
const darkThemeToggler = document.getElementById("darkThemeToggler");
|
||||
|
||||
darkThemeToggler.addEventListener('click', () => {
|
||||
document.body.classList.toggle("dark-mode");
|
||||
|
||||
localStorage.setItem('theme', document.body.classList.contains("dark-mode") ? 'dark' : 'default');
|
||||
})
|
||||
|
||||
window.editor = editor;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
94
package.json
|
@ -1,8 +1,9 @@
|
|||
{
|
||||
"name": "@editorjs/editorjs",
|
||||
"version": "2.19.1",
|
||||
"version": "2.30.0-rc.10",
|
||||
"description": "Editor.js — Native JS, based on API and Open Source",
|
||||
"main": "dist/editor.js",
|
||||
"main": "dist/editorjs.umd.js",
|
||||
"module": "dist/editorjs.mjs",
|
||||
"types": "./types/index.d.ts",
|
||||
"keywords": [
|
||||
"codex editor",
|
||||
|
@ -12,19 +13,23 @@
|
|||
"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",
|
||||
"dev": "vite",
|
||||
"build": "vite build --mode production",
|
||||
"build:test": "vite build --mode test",
|
||||
"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",
|
||||
"test:e2e": "cypress run"
|
||||
"_tools:checkout": "git submodule foreach \"git checkout master || git checkout main\"",
|
||||
"_tools:pull": "git submodule foreach git pull",
|
||||
"_tools:yarn": "git submodule foreach yarn",
|
||||
"_tools:build": "git submodule foreach yarn build",
|
||||
"_tools:make": "yarn _tools:yarn && yarn _tools:build",
|
||||
"tools:update": "yarn _tools:checkout && yarn _tools:pull && yarn _tools:make",
|
||||
"test:e2e": "yarn build:test && cypress run",
|
||||
"test:e2e:open": "yarn build:test && cypress open",
|
||||
"devserver:start": "yarn build && node ./devserver.js"
|
||||
},
|
||||
"author": "CodeX",
|
||||
"license": "Apache-2.0",
|
||||
|
@ -33,52 +38,43 @@
|
|||
"url": "git+https://github.com/codex-team/editor.js.git"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.9.0",
|
||||
"@babel/plugin-transform-runtime": "^7.9.0",
|
||||
"@babel/polyfill": "^7.8.7",
|
||||
"@babel/preset-env": "^7.9.5",
|
||||
"@babel/register": "^7.9.0",
|
||||
"@babel/runtime": "^7.9.2",
|
||||
"@babel/register": "^7.21.0",
|
||||
"@codexteam/icons": "^0.3.0",
|
||||
"@codexteam/shortcuts": "^1.1.1",
|
||||
"@types/webpack": "^4.41.12",
|
||||
"@types/webpack-env": "^1.15.2",
|
||||
"babel-loader": "^8.1.0",
|
||||
"babel-plugin-add-module-exports": "^1.0.0",
|
||||
"babel-plugin-class-display-name": "^2.1.0",
|
||||
"core-js": "3.6.5",
|
||||
"css-loader": "^3.5.3",
|
||||
"cssnano": "^4.1.10",
|
||||
"cypress": "^5.5.0",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-config-codex": "^1.3.3",
|
||||
"eslint-loader": "^4.0.2",
|
||||
"eslint-plugin-cypress": "^2.11.2",
|
||||
"extract-text-webpack-plugin": "^3.0.2",
|
||||
"@cypress/code-coverage": "^3.10.3",
|
||||
"@editorjs/code": "^2.7.0",
|
||||
"@editorjs/delimiter": "^1.2.0",
|
||||
"@editorjs/header": "^2.7.0",
|
||||
"@editorjs/paragraph": "^2.11.4",
|
||||
"@editorjs/simple-image": "^1.4.1",
|
||||
"@types/node": "^18.15.11",
|
||||
"chai-subset": "^1.6.0",
|
||||
"codex-notifier": "^1.1.2",
|
||||
"codex-tooltip": "^1.0.5",
|
||||
"core-js": "3.30.0",
|
||||
"cypress": "^13.7.1",
|
||||
"cypress-intellij-reporter": "^0.0.7",
|
||||
"cypress-plugin-tab": "^1.0.5",
|
||||
"cypress-terminal-report": "^5.3.2",
|
||||
"cypress-vite": "^1.5.0",
|
||||
"eslint": "^8.37.0",
|
||||
"eslint-config-codex": "^1.7.1",
|
||||
"eslint-plugin-chai-friendly": "^0.7.2",
|
||||
"eslint-plugin-cypress": "2.12.1",
|
||||
"html-janitor": "^2.0.4",
|
||||
"license-webpack-plugin": "^2.1.4",
|
||||
"nanoid": "^4.0.2",
|
||||
"postcss-apply": "^0.12.0",
|
||||
"postcss-import": "^12.0.1",
|
||||
"postcss-loader": "^3.0.0",
|
||||
"postcss-nested": "^4.1.2",
|
||||
"postcss-nested-ancestors": "^2.0.0",
|
||||
"postcss-preset-env": "^6.6.0",
|
||||
"raw-loader": "^4.0.1",
|
||||
"rimraf": "^3.0.2",
|
||||
"stylelint": "^13.3.3",
|
||||
"svg-sprite-generator": "^0.0.7",
|
||||
"terser-webpack-plugin": "^2.3.6",
|
||||
"ts-loader": "^7.0.1",
|
||||
"postcss-nested": "4.1.2",
|
||||
"postcss-preset-env": "^8.3.0",
|
||||
"rollup-plugin-license": "^3.0.1",
|
||||
"stylelint": "^15.4.0",
|
||||
"tslint": "^6.1.1",
|
||||
"typescript": "3.8.3",
|
||||
"webpack": "^4.43.0",
|
||||
"webpack-cli": "^3.3.11"
|
||||
"typescript": "5.0.3",
|
||||
"vite": "^4.2.1",
|
||||
"vite-plugin-css-injected-by-js": "^3.1.0"
|
||||
},
|
||||
"collective": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/editorjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"codex-notifier": "^1.1.2",
|
||||
"codex-tooltip": "^1.0.1"
|
||||
}
|
||||
}
|
||||
|
|
Before Width: | Height: | Size: 126 KiB After Width: | Height: | Size: 126 KiB |
419
public/assets/demo.css
Normal file
|
@ -0,0 +1,419 @@
|
|||
/**
|
||||
* Styles for the example page
|
||||
*/
|
||||
|
||||
:root {
|
||||
--color-bg-main: #fff;
|
||||
--color-border-light: #E8E8EB;
|
||||
--color-text-main: #000;
|
||||
}
|
||||
|
||||
.dark-mode {
|
||||
--color-border-light: rgba(255, 255, 255,.08);
|
||||
--color-bg-main: #1c1e24;
|
||||
--color-text-main: #737886;
|
||||
}
|
||||
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 1.5em;
|
||||
margin: 0;
|
||||
background: var(--color-bg-main);
|
||||
color: var(--color-text-main);
|
||||
}
|
||||
|
||||
.ce-example {
|
||||
font-size: 16.2px;
|
||||
}
|
||||
|
||||
.ce-example__header {
|
||||
border-bottom: 1px solid var(--color-border-light);
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
display: flex;
|
||||
padding: 0 30px;
|
||||
margin-bottom: 30px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.ce-example__header a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.ce-example__header-logo {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.ce-example__header-menu {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
@media all and (max-width: 730px){
|
||||
.ce-example__header-menu {
|
||||
margin-left: 0;
|
||||
margin-top: 10px;
|
||||
flex-basis: 100%;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.ce-example__header-menu a {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
@media all and (max-width: 730px){
|
||||
.ce-example__header-menu a {
|
||||
margin-left: 0;
|
||||
margin-right: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.ce-example__content {
|
||||
max-width: 1100px;
|
||||
margin: 0 auto;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.thin-mode .ce-example__content {
|
||||
max-width: 500px;
|
||||
border-left: 1px solid #eee;
|
||||
border-right: 1px solid #eee;
|
||||
padding: 0 15px;
|
||||
}
|
||||
|
||||
.ce-example__output {
|
||||
background: #1B202B;
|
||||
overflow-x: auto;
|
||||
padding: 0 30px 80px;
|
||||
}
|
||||
|
||||
.ce-example__output-content {
|
||||
max-width: 650px;
|
||||
margin: 30px auto;
|
||||
color: #ABADC3;
|
||||
font-family: 'PT Mono', Menlo, Monaco, Consolas, Courier New, monospace;
|
||||
font-size: 13.3px;
|
||||
}
|
||||
|
||||
.ce-example__output-content:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ce-example__button {
|
||||
display: block;
|
||||
margin: 50px auto;
|
||||
max-width: 180px;
|
||||
background: #4A9DF8;
|
||||
padding: 17px 30px;
|
||||
box-shadow: 0 22px 18px -4px rgba(137, 207, 255, 0.77);
|
||||
transition: all 150ms ease;
|
||||
cursor: pointer;
|
||||
border-radius: 31px;
|
||||
color: #fff;
|
||||
font-family: 'PT Mono', Menlo, Monaco, Consolas, Courier New, monospace;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.ce-example__button:hover {
|
||||
background: #3D8DE5;
|
||||
transform: translateY(2px);
|
||||
box-shadow: 0 20px 15px -4px rgba(137, 207, 255, 0.77);
|
||||
}
|
||||
|
||||
.ce-example__output-footer {
|
||||
padding: 30px 0;
|
||||
font-size: 14.2px;
|
||||
letter-spacing: 0.3px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.ce-example__output-footer a {
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.ce-example__statusbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
background: var(--color-bg-main);
|
||||
border-radius: 8px 8px 0 0;
|
||||
border-top: 1px solid var(--color-border-light);
|
||||
box-shadow: 0 2px 6px var(--color-border-light);
|
||||
font-size: 13px;
|
||||
padding: 8px 15px;
|
||||
z-index: 1;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.ce-example__statusbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.ce-example__statusbar-item:not(:last-of-type)::after {
|
||||
content: '|';
|
||||
color: #ddd;
|
||||
margin: 0 15px 0 12px;
|
||||
}
|
||||
|
||||
.ce-example__statusbar-item--right {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.ce-example__statusbar-button {
|
||||
display: inline-block;
|
||||
padding: 3px 12px;
|
||||
transition: all 150ms ease;
|
||||
cursor: pointer;
|
||||
border-radius: 31px;
|
||||
background: #eff1f4;
|
||||
text-align: center;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.ce-example__statusbar-button:hover {
|
||||
background: #e0e4eb;
|
||||
}
|
||||
|
||||
.ce-example__statusbar-button-primary {
|
||||
background: #4A9DF8;
|
||||
color: #fff;
|
||||
box-shadow: 0 7px 8px -4px rgba(137, 207, 255, 0.77);
|
||||
font-family: 'PT Mono', Menlo, Monaco, Consolas, Courier New, monospace;
|
||||
}
|
||||
|
||||
.ce-example__statusbar {
|
||||
--toggler-size: 20px;
|
||||
}
|
||||
|
||||
.ce-example__statusbar-toggler {
|
||||
position: relative;
|
||||
background: #7b8799;
|
||||
border-radius: 20px;
|
||||
padding: 2px;
|
||||
width: calc(var(--toggler-size) * 2.2);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.ce-example__statusbar-toggler::before {
|
||||
display: block;
|
||||
content: '';
|
||||
width: var(--toggler-size);
|
||||
height: var(--toggler-size);
|
||||
background: #fff;
|
||||
border-radius: 50%;
|
||||
transition: transform 100ms ease-in;
|
||||
}
|
||||
|
||||
.ce-example__statusbar-toggler::after {
|
||||
--moon-size: calc(var(--toggler-size) * 0.5);
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
height: var(--moon-size);
|
||||
width: var(--moon-size);
|
||||
box-shadow: calc(var(--moon-size) * 0.25 * -1) calc(var(--moon-size) * 0.18) 0 calc(var(--moon-size) * 0.05) white;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
@media all and (max-width: 730px){
|
||||
.ce-example__header,
|
||||
.ce-example__content{
|
||||
padding: 0 20px;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON highlighter
|
||||
*/
|
||||
.sc_attr {
|
||||
color: rgb(148, 162, 192);
|
||||
}
|
||||
.sc_key {
|
||||
color: rgb(190, 213, 255);
|
||||
}
|
||||
.sc_toolname {
|
||||
color: rgb(15, 205, 251);
|
||||
}
|
||||
.sc_tag {
|
||||
color: rgb(4, 131, 216);
|
||||
}
|
||||
.sc_bool {
|
||||
color: rgb(247, 60, 173);
|
||||
}
|
||||
|
||||
.ce-example .ce-block:first-of-type h1.ce-header{
|
||||
font-size: 50px;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Styles for the popup example page
|
||||
*/
|
||||
.ce-example--popup {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.ce-example--popup .ce-example__content {
|
||||
flex-grow: 2;
|
||||
}
|
||||
|
||||
.ce-example-popup__overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: #00000085;
|
||||
}
|
||||
|
||||
.ce-example-popup__popup {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%,-50%);
|
||||
width: 800px;
|
||||
max-width: 100%;
|
||||
max-height: 90vh;
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
overflow: auto;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@media all and (max-width: 730px){
|
||||
.ce-example-popup__popup {
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
width: calc(100% - 20px);
|
||||
height: calc(100% - 20px);
|
||||
transform: none;
|
||||
max-height: none;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.show-block-boundaries .ce-block {
|
||||
box-shadow: inset 0 0 0 1px #eff2f5;
|
||||
}
|
||||
|
||||
.show-block-boundaries .ce-block__content {
|
||||
box-shadow: 0 0 0 1px rgba(224, 231, 241, 0.61) inset;
|
||||
}
|
||||
.show-block-boundaries #showBlocksBoundariesButton span,
|
||||
.thin-mode #enableThinModeButton span {
|
||||
font-size: 0;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
.show-block-boundaries #showBlocksBoundariesButton span::before,
|
||||
.thin-mode #enableThinModeButton span::before {
|
||||
content: attr(data-toggled-text);
|
||||
display: inline;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Dark theme overrides
|
||||
*/
|
||||
.dark-mode img {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.dark-mode .cdx-simple-image__picture--with-border,
|
||||
.dark-mode .cdx-input {
|
||||
border-color: var(--color-border-light);
|
||||
}
|
||||
|
||||
.dark-mode .ce-example__button {
|
||||
box-shadow: 0 24px 18px -14px rgba(4, 154, 255, 0.24);
|
||||
}
|
||||
|
||||
.dark-mode .ce-example__output {
|
||||
background-color: #17191f;
|
||||
}
|
||||
|
||||
.dark-mode .inline-code {
|
||||
background-color: rgba(53, 56, 68, 0.62);
|
||||
color: #727683;
|
||||
}
|
||||
|
||||
.dark-mode a {
|
||||
color: #959ba8;
|
||||
}
|
||||
|
||||
.dark-mode .ce-example__statusbar-toggler,
|
||||
.dark-mode .ce-example__statusbar-button {
|
||||
background-color: #343842;
|
||||
}
|
||||
|
||||
.dark-mode .ce-example__statusbar-toggler::before {
|
||||
transform: translateX(calc(var(--toggler-size) * 2.2 - var(--toggler-size)));
|
||||
}
|
||||
|
||||
.dark-mode .ce-example__statusbar-toggler::after {
|
||||
content: '*';
|
||||
right: auto;
|
||||
left: 6px;
|
||||
top: 7px;
|
||||
color: #fff;
|
||||
box-shadow: none;
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
.dark-mode.show-block-boundaries .ce-block,
|
||||
.dark-mode.show-block-boundaries .ce-block__content {
|
||||
box-shadow: 0 0 0 1px rgba(128, 144, 159, 0.09) inset;
|
||||
}
|
||||
|
||||
.dark-mode.thin-mode .ce-example__content{
|
||||
border-color: var(--color-border-light);
|
||||
}
|
||||
|
||||
.dark-mode .ce-example__statusbar-item:not(:last-of-type)::after {
|
||||
color: var(--color-border-light);
|
||||
}
|
||||
|
||||
.dark-mode .ce-block--selected .ce-block__content,
|
||||
.dark-mode ::selection{
|
||||
background-color: rgba(57, 68, 84, 0.57);
|
||||
}
|
||||
|
||||
.dark-mode .ce-toolbox__button,
|
||||
.dark-mode .ce-toolbar__settings-btn,
|
||||
.dark-mode .ce-toolbar__plus {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.dark-mode .ce-stub {
|
||||
opacity: 0.3;
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
<svg width="14" height="14" viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<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"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 353 B |
|
@ -1,3 +0,0 @@
|
|||
<svg width="14" height="14" viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<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"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 321 B |
|
@ -1 +0,0 @@
|
|||
<svg width="12" height="14" viewBox="0 0 12 14" xmlns="http://www.w3.org/2000/svg"><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"/></svg>
|
Before Width: | Height: | Size: 794 B |
|
@ -1,3 +0,0 @@
|
|||
<svg width="237" height="237" viewBox="0 0 237 237" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<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"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 430 B |
|
@ -1,6 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" 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"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 218 B |
|
@ -1,3 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" 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"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 530 B |
|
@ -1,3 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" 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"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 199 B |
|
@ -1,3 +0,0 @@
|
|||
<svg width="14" height="14" viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg">
|
||||
<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"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 260 B |
|
@ -1,3 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="52" height="52" 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"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 643 B |
|
@ -1,3 +0,0 @@
|
|||
<svg width="13" height="13" xmlns="http://www.w3.org/2000/svg">
|
||||
<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"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 240 B |
|
@ -1,3 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" 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"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 384 B |
16
src/codex.ts
|
@ -7,21 +7,19 @@ import { EditorConfig } from '../types';
|
|||
*/
|
||||
import '@babel/register';
|
||||
|
||||
import 'components/polyfills';
|
||||
import './components/polyfills';
|
||||
import Core from './components/core';
|
||||
import * as _ from './components/utils';
|
||||
import { destroy as destroyTooltip } from './components/utils/tooltip';
|
||||
|
||||
declare const VERSION: string;
|
||||
|
||||
/**
|
||||
* Editor.js
|
||||
*
|
||||
* Short Description (눈_눈;)
|
||||
*
|
||||
* @version 2.18.0
|
||||
*
|
||||
* @license Apache-2.0
|
||||
* @author CodeX-Team <https://ifmo.su>
|
||||
* @see Editor.js <https://editorjs.io>
|
||||
* @author CodeX Team <https://codex.so>
|
||||
*/
|
||||
export default class EditorJS {
|
||||
/**
|
||||
|
@ -70,6 +68,9 @@ export default class EditorJS {
|
|||
*/
|
||||
this.isReady = editor.isReady.then(() => {
|
||||
this.exportAPI(editor);
|
||||
/**
|
||||
* @todo pass API as an argument. It will allow to use Editor's API when editor is ready
|
||||
*/
|
||||
onReady();
|
||||
});
|
||||
}
|
||||
|
@ -87,8 +88,11 @@ export default class EditorJS {
|
|||
if (_.isFunction(moduleInstance.destroy)) {
|
||||
moduleInstance.destroy();
|
||||
}
|
||||
moduleInstance.listeners.removeAll();
|
||||
});
|
||||
|
||||
destroyTooltip();
|
||||
|
||||
editor = null;
|
||||
|
||||
for (const field in this) {
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
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';
|
||||
import { EditorEventMap } from './events';
|
||||
|
||||
/**
|
||||
* The type <T> of the Module generic.
|
||||
|
@ -12,12 +15,11 @@ export type ModuleNodes = object;
|
|||
* @abstract
|
||||
* @class Module
|
||||
* @classdesc All modules inherits from this class.
|
||||
*
|
||||
* @typedef {Module} Module
|
||||
* @property {object} config - Editor user settings
|
||||
* @property {EditorModules} Editor - List of Editor modules
|
||||
*/
|
||||
export default class Module<T extends ModuleNodes = {}> {
|
||||
export default class Module<T extends ModuleNodes = Record<string, HTMLElement>> {
|
||||
/**
|
||||
* Each module can provide some UI elements that will be stored in this property
|
||||
*/
|
||||
|
@ -38,6 +40,16 @@ export default class Module<T extends ModuleNodes = {}> {
|
|||
*/
|
||||
protected config: EditorConfig;
|
||||
|
||||
/**
|
||||
* Editor event dispatcher class
|
||||
*/
|
||||
protected eventsDispatcher: EventsDispatcher<EditorEventMap>;
|
||||
|
||||
/**
|
||||
* 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 options - Module options
|
||||
* @param options.config - Module config
|
||||
* @param options.eventsDispatcher - Common event bus
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,16 +1,21 @@
|
|||
/**
|
||||
* @class DeleteTune
|
||||
* @classdesc Editor's default tune that moves up selected block
|
||||
*
|
||||
* @copyright <CodeX Team> 2018
|
||||
*/
|
||||
import { API, BlockTune } from '../../../types';
|
||||
import $ from '../dom';
|
||||
import { IconCross } from '@codexteam/icons';
|
||||
import { TunesMenuConfig } from '../../../types/tools';
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export default class DeleteTune implements BlockTune {
|
||||
/**
|
||||
* Set Tool is Tune
|
||||
*/
|
||||
public static readonly isTune = true;
|
||||
|
||||
/**
|
||||
* Property that contains Editor.js API methods
|
||||
*
|
||||
|
@ -18,32 +23,6 @@ export default class DeleteTune implements BlockTune {
|
|||
*/
|
||||
private readonly api: API;
|
||||
|
||||
/**
|
||||
* Styles
|
||||
*/
|
||||
private CSS = {
|
||||
button: 'ce-settings__button',
|
||||
buttonDelete: 'ce-settings__button--delete',
|
||||
buttonConfirm: 'ce-settings__button--confirm',
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete confirmation
|
||||
*/
|
||||
private needConfirmation: boolean;
|
||||
|
||||
/**
|
||||
* set false confirmation state
|
||||
*/
|
||||
private readonly resetConfirmation: () => void;
|
||||
|
||||
/**
|
||||
* Tune nodes
|
||||
*/
|
||||
private nodes: {button: HTMLElement} = {
|
||||
button: null,
|
||||
};
|
||||
|
||||
/**
|
||||
* DeleteTune constructor
|
||||
*
|
||||
|
@ -51,73 +30,27 @@ export default class DeleteTune implements BlockTune {
|
|||
*/
|
||||
constructor({ api }) {
|
||||
this.api = api;
|
||||
}
|
||||
|
||||
this.resetConfirmation = (): void => {
|
||||
this.setConfirmation(false);
|
||||
/**
|
||||
* Tune's appearance in block settings menu
|
||||
*/
|
||||
public render(): TunesMenuConfig {
|
||||
return {
|
||||
icon: IconCross,
|
||||
title: this.api.i18n.t('Delete'),
|
||||
name: 'delete',
|
||||
confirmation: {
|
||||
title: this.api.i18n.t('Click to delete'),
|
||||
onActivate: (): void => this.handleClick(),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create "Delete" button and add click event listener
|
||||
*
|
||||
* @returns {HTMLElement}
|
||||
*/
|
||||
public render(): HTMLElement {
|
||||
this.nodes.button = $.make('div', [this.CSS.button, this.CSS.buttonDelete], {});
|
||||
this.nodes.button.appendChild($.svg('cross', 12, 12));
|
||||
this.api.listeners.on(this.nodes.button, 'click', (event: MouseEvent) => this.handleClick(event), false);
|
||||
|
||||
/**
|
||||
* Enable tooltip module
|
||||
*/
|
||||
this.api.tooltip.onHover(this.nodes.button, this.api.i18n.t('Delete'));
|
||||
|
||||
return this.nodes.button;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete block conditions passed
|
||||
*
|
||||
* @param {MouseEvent} event - click event
|
||||
*/
|
||||
public handleClick(event: MouseEvent): void {
|
||||
/**
|
||||
* if block is not waiting the confirmation, subscribe on block-settings-closing event to reset
|
||||
* otherwise delete block
|
||||
*/
|
||||
if (!this.needConfirmation) {
|
||||
this.setConfirmation(true);
|
||||
|
||||
/**
|
||||
* Subscribe on event.
|
||||
* When toolbar block settings is closed but block deletion is not confirmed,
|
||||
* then reset confirmation state
|
||||
*/
|
||||
this.api.events.on('block-settings-closed', this.resetConfirmation);
|
||||
} else {
|
||||
/**
|
||||
* Unsubscribe from block-settings closing event
|
||||
*/
|
||||
this.api.events.off('block-settings-closed', this.resetConfirmation);
|
||||
|
||||
this.api.blocks.delete();
|
||||
this.api.toolbar.close();
|
||||
this.api.tooltip.hide();
|
||||
|
||||
/**
|
||||
* Prevent firing ui~documentClicked that can drop currentBlock pointer
|
||||
*/
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* change tune state
|
||||
*
|
||||
* @param {boolean} state - delete confirmation state
|
||||
*/
|
||||
private setConfirmation(state: boolean): void {
|
||||
this.needConfirmation = state;
|
||||
this.nodes.button.classList.add(this.CSS.buttonConfirm);
|
||||
public handleClick(): void {
|
||||
this.api.blocks.delete();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,23 @@
|
|||
/**
|
||||
* @class MoveDownTune
|
||||
* @classdesc Editor's default tune - Moves down highlighted block
|
||||
*
|
||||
* @copyright <CodeX Team> 2018
|
||||
*/
|
||||
|
||||
import $ from '../dom';
|
||||
import { API, BlockTune } from '../../../types';
|
||||
import { IconChevronDown } from '@codexteam/icons';
|
||||
import { TunesMenuConfig } from '../../../types/tools';
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export default class MoveDownTune implements BlockTune {
|
||||
/**
|
||||
* Set Tool is Tune
|
||||
*/
|
||||
public static readonly isTune = true;
|
||||
|
||||
/**
|
||||
* Property that contains Editor.js API methods
|
||||
*
|
||||
|
@ -21,12 +27,8 @@ export default class MoveDownTune implements BlockTune {
|
|||
|
||||
/**
|
||||
* Styles
|
||||
*
|
||||
* @type {{wrapper: string}}
|
||||
*/
|
||||
private CSS = {
|
||||
button: 'ce-settings__button',
|
||||
wrapper: 'ce-tune-move-down',
|
||||
animation: 'wobble',
|
||||
};
|
||||
|
||||
|
@ -40,48 +42,27 @@ export default class MoveDownTune implements BlockTune {
|
|||
}
|
||||
|
||||
/**
|
||||
* Return 'move down' button
|
||||
*
|
||||
* @returns {HTMLElement}
|
||||
* Tune's appearance in block settings menu
|
||||
*/
|
||||
public render(): HTMLElement {
|
||||
const moveDownButton = $.make('div', [this.CSS.button, this.CSS.wrapper], {});
|
||||
|
||||
moveDownButton.appendChild($.svg('arrow-down', 14, 14));
|
||||
this.api.listeners.on(
|
||||
moveDownButton,
|
||||
'click',
|
||||
(event) => this.handleClick(event as MouseEvent, moveDownButton),
|
||||
false
|
||||
);
|
||||
|
||||
/**
|
||||
* Enable tooltip module on button
|
||||
*/
|
||||
this.api.tooltip.onHover(moveDownButton, this.api.i18n.t('Move down'));
|
||||
|
||||
return moveDownButton;
|
||||
public render(): TunesMenuConfig {
|
||||
return {
|
||||
icon: IconChevronDown,
|
||||
title: this.api.i18n.t('Move down'),
|
||||
onActivate: (): void => this.handleClick(),
|
||||
name: 'move-down',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle clicks on 'move down' button
|
||||
*
|
||||
* @param {MouseEvent} event - click event
|
||||
* @param {HTMLElement} button - clicked button
|
||||
*/
|
||||
public handleClick(event: MouseEvent, button: HTMLElement): void {
|
||||
public handleClick(): void {
|
||||
const currentBlockIndex = this.api.blocks.getCurrentBlockIndex();
|
||||
const nextBlock = this.api.blocks.getBlockByIndex(currentBlockIndex + 1);
|
||||
|
||||
// If Block is last do nothing
|
||||
if (!nextBlock) {
|
||||
button.classList.add(this.CSS.animation);
|
||||
|
||||
window.setTimeout(() => {
|
||||
button.classList.remove(this.CSS.animation);
|
||||
}, 500);
|
||||
|
||||
return;
|
||||
throw new Error('Unable to move Block down since it is already the last');
|
||||
}
|
||||
|
||||
const nextBlockElement = nextBlock.holder;
|
||||
|
@ -102,7 +83,6 @@ export default class MoveDownTune implements BlockTune {
|
|||
/** Change blocks positions */
|
||||
this.api.blocks.move(currentBlockIndex + 1);
|
||||
|
||||
/** Hide the Tooltip */
|
||||
this.api.tooltip.hide();
|
||||
this.api.toolbar.toggleBlockSettings(true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,21 @@
|
|||
/**
|
||||
* @class MoveUpTune
|
||||
* @classdesc Editor's default tune that moves up selected block
|
||||
*
|
||||
* @copyright <CodeX Team> 2018
|
||||
*/
|
||||
import $ from '../dom';
|
||||
import { API, BlockTune } from '../../../types';
|
||||
import { IconChevronUp } from '@codexteam/icons';
|
||||
import { TunesMenuConfig } from '../../../types/tools';
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export default class MoveUpTune implements BlockTune {
|
||||
/**
|
||||
* Set Tool is Tune
|
||||
*/
|
||||
public static readonly isTune = true;
|
||||
|
||||
/**
|
||||
* Property that contains Editor.js API methods
|
||||
*
|
||||
|
@ -20,12 +25,8 @@ export default class MoveUpTune implements BlockTune {
|
|||
|
||||
/**
|
||||
* Styles
|
||||
*
|
||||
* @type {{wrapper: string}}
|
||||
*/
|
||||
private CSS = {
|
||||
button: 'ce-settings__button',
|
||||
wrapper: 'ce-tune-move-up',
|
||||
animation: 'wobble',
|
||||
};
|
||||
|
||||
|
@ -39,48 +40,27 @@ export default class MoveUpTune implements BlockTune {
|
|||
}
|
||||
|
||||
/**
|
||||
* Create "MoveUp" button and add click event listener
|
||||
*
|
||||
* @returns {HTMLElement}
|
||||
* Tune's appearance in block settings menu
|
||||
*/
|
||||
public render(): HTMLElement {
|
||||
const moveUpButton = $.make('div', [this.CSS.button, this.CSS.wrapper], {});
|
||||
|
||||
moveUpButton.appendChild($.svg('arrow-up', 14, 14));
|
||||
this.api.listeners.on(
|
||||
moveUpButton,
|
||||
'click',
|
||||
(event) => this.handleClick(event as MouseEvent, moveUpButton),
|
||||
false
|
||||
);
|
||||
|
||||
/**
|
||||
* Enable tooltip module on button
|
||||
*/
|
||||
this.api.tooltip.onHover(moveUpButton, this.api.i18n.t('Move up'));
|
||||
|
||||
return moveUpButton;
|
||||
public render(): TunesMenuConfig {
|
||||
return {
|
||||
icon: IconChevronUp,
|
||||
title: this.api.i18n.t('Move up'),
|
||||
onActivate: (): void => this.handleClick(),
|
||||
name: 'move-up',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Move current block up
|
||||
*
|
||||
* @param {MouseEvent} event - click event
|
||||
* @param {HTMLElement} button - clicked button
|
||||
*/
|
||||
public handleClick(event: MouseEvent, button: HTMLElement): void {
|
||||
public handleClick(): void {
|
||||
const currentBlockIndex = this.api.blocks.getCurrentBlockIndex();
|
||||
const currentBlock = this.api.blocks.getBlockByIndex(currentBlockIndex);
|
||||
const previousBlock = this.api.blocks.getBlockByIndex(currentBlockIndex - 1);
|
||||
|
||||
if (currentBlockIndex === 0 || !currentBlock || !previousBlock) {
|
||||
button.classList.add(this.CSS.animation);
|
||||
|
||||
window.setTimeout(() => {
|
||||
button.classList.remove(this.CSS.animation);
|
||||
}, 500);
|
||||
|
||||
return;
|
||||
throw new Error('Unable to move Block up since it is already the first');
|
||||
}
|
||||
|
||||
const currentBlockElement = currentBlock.holder;
|
||||
|
@ -102,7 +82,7 @@ export default class MoveUpTune implements BlockTune {
|
|||
if (previousBlockCoords.top > 0) {
|
||||
scrollUpOffset = Math.abs(currentBlockCoords.top) - Math.abs(previousBlockCoords.top);
|
||||
} else {
|
||||
scrollUpOffset = window.innerHeight - Math.abs(currentBlockCoords.top) + Math.abs(previousBlockCoords.top);
|
||||
scrollUpOffset = Math.abs(currentBlockCoords.top) + previousBlockCoords.height;
|
||||
}
|
||||
|
||||
window.scrollBy(0, -1 * scrollUpOffset);
|
||||
|
@ -110,7 +90,6 @@ export default class MoveUpTune implements BlockTune {
|
|||
/** Change blocks positions */
|
||||
this.api.blocks.move(currentBlockIndex - 1);
|
||||
|
||||
/** Hide the Tooltip */
|
||||
this.api.tooltip.hide();
|
||||
this.api.toolbar.toggleBlockSettings(true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,13 +7,20 @@ import { BlockAPI as BlockAPIInterface } from '../../../types/api';
|
|||
* Constructs new BlockAPI object
|
||||
*
|
||||
* @class
|
||||
*
|
||||
* @param {Block} block - Block to expose
|
||||
*/
|
||||
function BlockAPI(
|
||||
block: Block
|
||||
): void {
|
||||
const blockAPI: BlockAPIInterface = {
|
||||
/**
|
||||
* Block id
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
get id(): string {
|
||||
return block.id;
|
||||
},
|
||||
/**
|
||||
* Tool name
|
||||
*
|
||||
|
@ -77,12 +84,18 @@ function BlockAPI(
|
|||
return block.stretched;
|
||||
},
|
||||
|
||||
/**
|
||||
* True if Block has inputs to be focused
|
||||
*/
|
||||
get focusable(): boolean {
|
||||
return block.focusable;
|
||||
},
|
||||
|
||||
/**
|
||||
* Call Tool method with errors handler under-the-hood
|
||||
*
|
||||
* @param {string} methodName - method to call
|
||||
* @param {object} param - object with parameters
|
||||
*
|
||||
* @returns {unknown}
|
||||
*/
|
||||
call(methodName: string, param?: object): unknown {
|
||||
|
@ -102,12 +115,19 @@ function BlockAPI(
|
|||
* Validate Block data
|
||||
*
|
||||
* @param {BlockToolData} data - data to validate
|
||||
*
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
validate(data: BlockToolData): Promise<boolean> {
|
||||
return block.validate(data);
|
||||
},
|
||||
|
||||
/**
|
||||
* Allows to say Editor that Block was changed. Used to manually trigger Editor's 'onChange' callback
|
||||
* Can be useful for block changes invisible for editor core.
|
||||
*/
|
||||
dispatchChange(): void {
|
||||
block.dispatchChange();
|
||||
},
|
||||
};
|
||||
|
||||
Object.setPrototypeOf(this, blockAPI);
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
import {
|
||||
BlockAPI as BlockAPIInterface,
|
||||
BlockTool,
|
||||
BlockToolConstructable,
|
||||
BlockTool as IBlockTool,
|
||||
BlockToolData,
|
||||
BlockTune,
|
||||
BlockTuneConstructable,
|
||||
BlockTune as IBlockTune,
|
||||
SanitizerConfig,
|
||||
ToolConfig,
|
||||
ToolSettings
|
||||
ToolboxConfigEntry,
|
||||
PopoverItemParams
|
||||
} from '../../../types';
|
||||
|
||||
import { SavedData } from '../../../types/data-formats';
|
||||
|
@ -15,22 +14,28 @@ import $ from '../dom';
|
|||
import * as _ from '../utils';
|
||||
import ApiModules from '../modules/api';
|
||||
import BlockAPI from './api';
|
||||
import { ToolType } from '../modules/tools';
|
||||
|
||||
/** Import default tunes */
|
||||
import MoveUpTune from '../block-tunes/block-tune-move-up';
|
||||
import DeleteTune from '../block-tunes/block-tune-delete';
|
||||
import MoveDownTune from '../block-tunes/block-tune-move-down';
|
||||
import SelectionUtils from '../selection';
|
||||
import BlockTool from '../tools/block';
|
||||
|
||||
import BlockTune from '../tools/tune';
|
||||
import { BlockTuneData } from '../../../types/block-tunes/block-tune-data';
|
||||
import ToolsCollection from '../tools/collection';
|
||||
import EventsDispatcher from '../utils/events';
|
||||
import { TunesMenuConfigItem } from '../../../types/tools';
|
||||
import { isMutationBelongsToElement } from '../utils/mutations';
|
||||
import { EditorEventMap, FakeCursorAboutToBeToggled, FakeCursorHaveBeenSet, RedactorDomChanged } from '../events';
|
||||
import { RedactorDomChangedPayload } from '../events/RedactorDomChanged';
|
||||
import { convertBlockDataToString, isSameBlockData } from '../utils/blocks';
|
||||
import { PopoverItemType } from '../utils/popover';
|
||||
|
||||
/**
|
||||
* Interface describes Block class constructor argument
|
||||
*/
|
||||
interface BlockConstructorOptions {
|
||||
/**
|
||||
* Tool's name
|
||||
* Block's id. Should be passed for existed block, and omitted for a new one.
|
||||
*/
|
||||
name: string;
|
||||
id?: string;
|
||||
|
||||
/**
|
||||
* Initial Block data
|
||||
|
@ -38,14 +43,9 @@ interface BlockConstructorOptions {
|
|||
data: BlockToolData;
|
||||
|
||||
/**
|
||||
* Tool's class or constructor function
|
||||
* Tool object
|
||||
*/
|
||||
Tool: BlockToolConstructable;
|
||||
|
||||
/**
|
||||
* Tool settings from initial config
|
||||
*/
|
||||
settings: ToolSettings;
|
||||
tool: BlockTool;
|
||||
|
||||
/**
|
||||
* Editor's API methods
|
||||
|
@ -56,15 +56,18 @@ interface BlockConstructorOptions {
|
|||
* This flag indicates that the Block should be constructed in the read-only mode.
|
||||
*/
|
||||
readOnly: boolean;
|
||||
|
||||
/**
|
||||
* Tunes data for current Block
|
||||
*/
|
||||
tunesData: { [name: string]: BlockTuneData };
|
||||
}
|
||||
|
||||
/**
|
||||
* @class Block
|
||||
* @classdesc This class describes editor`s block, including block`s HTMLElement, data and tool
|
||||
*
|
||||
* @property {BlockTool} tool — current block tool (Paragraph, for example)
|
||||
* @property {object} CSS — block`s css classes
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
|
@ -75,67 +78,74 @@ export enum BlockToolAPI {
|
|||
* @todo remove method in 3.0.0
|
||||
* @deprecated — use 'rendered' hook instead
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
APPEND_CALLBACK = 'appendCallback',
|
||||
RENDERED = 'rendered',
|
||||
MOVED = 'moved',
|
||||
UPDATED = 'updated',
|
||||
REMOVED = 'removed',
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
ON_PASTE = 'onPaste',
|
||||
}
|
||||
|
||||
/**
|
||||
* Names of events used in Block
|
||||
*/
|
||||
interface BlockEvents {
|
||||
'didMutated': Block,
|
||||
}
|
||||
|
||||
/**
|
||||
* @classdesc Abstract Block class that contains Block information, Tool name and Tool class instance
|
||||
*
|
||||
* @property {BlockTool} tool - Tool instance
|
||||
* @property {HTMLElement} holder - Div element that wraps block content with Tool's content. Has `ce-block` CSS class
|
||||
* @property {HTMLElement} pluginsContent - HTML content that returns by Tool's render function
|
||||
*/
|
||||
export default class Block {
|
||||
export default class Block extends EventsDispatcher<BlockEvents> {
|
||||
/**
|
||||
* CSS classes for the Block
|
||||
*
|
||||
* @returns {{wrapper: string, content: string}}
|
||||
*/
|
||||
public static get CSS(): {[name: string]: string} {
|
||||
public static get CSS(): { [name: string]: string } {
|
||||
return {
|
||||
wrapper: 'ce-block',
|
||||
wrapperStretched: 'ce-block--stretched',
|
||||
content: 'ce-block__content',
|
||||
focused: 'ce-block--focused',
|
||||
selected: 'ce-block--selected',
|
||||
dropTarget: 'ce-block--drop-target',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Block unique identifier
|
||||
*/
|
||||
public id: string;
|
||||
|
||||
/**
|
||||
* Block Tool`s name
|
||||
*/
|
||||
public name: string;
|
||||
public readonly name: string;
|
||||
|
||||
/**
|
||||
* Instance of the Tool Block represents
|
||||
*/
|
||||
public tool: BlockTool;
|
||||
|
||||
/**
|
||||
* Class blueprint of the ool Block represents
|
||||
*/
|
||||
public class: BlockToolConstructable;
|
||||
public readonly tool: BlockTool;
|
||||
|
||||
/**
|
||||
* User Tool configuration
|
||||
*/
|
||||
public settings: ToolConfig;
|
||||
public readonly settings: ToolConfig;
|
||||
|
||||
/**
|
||||
* Wrapper for Block`s content
|
||||
*/
|
||||
public holder: HTMLDivElement;
|
||||
public readonly holder: HTMLDivElement;
|
||||
|
||||
/**
|
||||
* Tunes used by Tool
|
||||
*/
|
||||
public tunes: BlockTune[];
|
||||
public readonly tunes: ToolsCollection<BlockTune>;
|
||||
|
||||
/**
|
||||
* Tool's user configuration
|
||||
|
@ -149,6 +159,32 @@ export default class Block {
|
|||
*/
|
||||
private cachedInputs: HTMLElement[] = [];
|
||||
|
||||
/**
|
||||
* We'll store a reference to the tool's rendered element to access it later
|
||||
*/
|
||||
private toolRenderedElement: HTMLElement | null = null;
|
||||
|
||||
/**
|
||||
* Tool class instance
|
||||
*/
|
||||
private readonly toolInstance: IBlockTool;
|
||||
|
||||
/**
|
||||
* User provided Block Tunes instances
|
||||
*/
|
||||
private readonly tunesInstances: Map<string, IBlockTune> = new Map();
|
||||
|
||||
/**
|
||||
* Editor provided Block Tunes instances
|
||||
*/
|
||||
private readonly defaultTunesInstances: Map<string, IBlockTune> = new Map();
|
||||
|
||||
/**
|
||||
* If there is saved data for Tune which is not available at the moment,
|
||||
* we will store it here and provide back on save so data is not lost
|
||||
*/
|
||||
private unavailableTunesData: { [name: string]: BlockTuneData } = {};
|
||||
|
||||
/**
|
||||
* Editor`s API module
|
||||
*/
|
||||
|
@ -162,35 +198,14 @@ export default class Block {
|
|||
private inputIndex = 0;
|
||||
|
||||
/**
|
||||
* Mutation observer to handle DOM mutations
|
||||
*
|
||||
* @type {MutationObserver}
|
||||
* Common editor event bus
|
||||
*/
|
||||
private mutationObserver: MutationObserver;
|
||||
private readonly editorEventBus: EventsDispatcher<EditorEventMap> | null = null;
|
||||
|
||||
/**
|
||||
* Debounce Timer
|
||||
*
|
||||
* @type {number}
|
||||
* Link to editor dom change callback. Used to remove listener on remove
|
||||
*/
|
||||
private readonly modificationDebounceTimer = 450;
|
||||
|
||||
/**
|
||||
* Is fired when DOM mutation has been happened
|
||||
*/
|
||||
private didMutated = _.debounce((): void => {
|
||||
/**
|
||||
* Drop cache
|
||||
*/
|
||||
this.cachedInputs = [];
|
||||
|
||||
/**
|
||||
* Update current input
|
||||
*/
|
||||
this.updateCurrentInput();
|
||||
|
||||
this.call(BlockToolAPI.UPDATED);
|
||||
}, this.modificationDebounceTimer);
|
||||
private redactorDomChangedCallback: (payload: RedactorDomChangedPayload) => void;
|
||||
|
||||
/**
|
||||
* Current block API interface
|
||||
|
@ -198,48 +213,62 @@ export default class Block {
|
|||
private readonly blockAPI: BlockAPIInterface;
|
||||
|
||||
/**
|
||||
* @param {object} options - block constructor options
|
||||
* @param {string} options.name - Tool name that passed on initialization
|
||||
* @param {BlockToolData} options.data - Tool's initial data
|
||||
* @param {BlockToolConstructable} options.Tool — Tool's class
|
||||
* @param {ToolSettings} options.settings - default tool's config
|
||||
* @param options - block constructor options
|
||||
* @param [options.id] - block's id. Will be generated if omitted.
|
||||
* @param options.data - Tool's initial data
|
||||
* @param options.tool — block's tool
|
||||
* @param options.api - Editor API module for pass it to the Block Tunes
|
||||
* @param {boolean} options.readOnly - Read-Only flag
|
||||
* @param options.readOnly - Read-Only flag
|
||||
* @param [eventBus] - Editor common event bus. Allows to subscribe on some Editor events. Could be omitted when "virtual" Block is created. See BlocksAPI@composeBlockData.
|
||||
*/
|
||||
constructor({
|
||||
name,
|
||||
id = _.generateBlockId(),
|
||||
data,
|
||||
Tool,
|
||||
settings,
|
||||
tool,
|
||||
api,
|
||||
readOnly,
|
||||
}: BlockConstructorOptions) {
|
||||
this.name = name;
|
||||
this.class = Tool;
|
||||
this.settings = settings;
|
||||
this.config = settings.config || {};
|
||||
tunesData,
|
||||
}: BlockConstructorOptions, eventBus?: EventsDispatcher<EditorEventMap>) {
|
||||
super();
|
||||
this.name = tool.name;
|
||||
this.id = id;
|
||||
this.settings = tool.settings;
|
||||
this.config = tool.settings.config || {};
|
||||
this.api = api;
|
||||
this.editorEventBus = eventBus || null;
|
||||
this.blockAPI = new BlockAPI(this);
|
||||
|
||||
this.mutationObserver = new MutationObserver(this.didMutated);
|
||||
this.tool = tool;
|
||||
this.toolInstance = tool.create(data, this.blockAPI, readOnly);
|
||||
|
||||
this.tool = new Tool({
|
||||
data,
|
||||
config: this.config,
|
||||
api: this.api.getMethodsForTool(name, ToolType.Block),
|
||||
block: this.blockAPI,
|
||||
readOnly,
|
||||
});
|
||||
|
||||
this.holder = this.compose();
|
||||
/**
|
||||
* @type {BlockTune[]}
|
||||
*/
|
||||
this.tunes = this.makeTunes();
|
||||
this.tunes = tool.tunes;
|
||||
|
||||
this.composeTunes(tunesData);
|
||||
|
||||
this.holder = this.compose();
|
||||
|
||||
/**
|
||||
* Bind block events in RIC for optimizing of constructing process time
|
||||
*/
|
||||
window.requestIdleCallback(() => {
|
||||
/**
|
||||
* Start watching block mutations
|
||||
*/
|
||||
this.watchBlockMutations();
|
||||
|
||||
/**
|
||||
* Mutation observer doesn't track changes in "<input>" and "<textarea>"
|
||||
* so we need to track focus events to update current input and clear cache.
|
||||
*/
|
||||
this.addInputEvents();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Find and return all editable elements (contenteditables and native inputs) in the Tool HTML
|
||||
* Find and return all editable elements (contenteditable and native inputs) in the Tool HTML
|
||||
*
|
||||
* @returns {HTMLElement[]}
|
||||
*/
|
||||
|
@ -349,17 +378,24 @@ export default class Block {
|
|||
* @returns {object}
|
||||
*/
|
||||
public get sanitize(): SanitizerConfig {
|
||||
return this.tool.sanitize;
|
||||
return this.tool.sanitizeConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* is block mergeable
|
||||
* We plugin have merge function then we call it mergable
|
||||
* We plugin have merge function then we call it mergeable
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
public get mergeable(): boolean {
|
||||
return _.isFunction(this.tool.merge);
|
||||
return _.isFunction(this.toolInstance.merge);
|
||||
}
|
||||
|
||||
/**
|
||||
* If Block contains inputs, it is focusable
|
||||
*/
|
||||
public get focusable(): boolean {
|
||||
return this.inputs.length !== 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -368,14 +404,14 @@ export default class Block {
|
|||
* @returns {boolean}
|
||||
*/
|
||||
public get isEmpty(): boolean {
|
||||
const emptyText = $.isEmpty(this.pluginsContent);
|
||||
const emptyText = $.isEmpty(this.pluginsContent, '/');
|
||||
const emptyMedia = !this.hasMedia;
|
||||
|
||||
return emptyText && emptyMedia;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if block has a media content such as images, iframes and other
|
||||
* Check if block has a media content such as images, iframe and other
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
|
@ -399,22 +435,6 @@ export default class Block {
|
|||
return !!this.holder.querySelector(mediaTags.join(','));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set focused state
|
||||
*
|
||||
* @param {boolean} state - 'true' to select, 'false' to remove selection
|
||||
*/
|
||||
public set focused(state: boolean) {
|
||||
this.holder.classList.toggle(Block.CSS.focused, state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Block's focused state
|
||||
*/
|
||||
public get focused(): boolean {
|
||||
return this.holder.classList.contains(Block.CSS.focused);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set selected state
|
||||
* We don't need to mark Block as Selected when it is empty
|
||||
|
@ -422,10 +442,21 @@ export default class Block {
|
|||
* @param {boolean} state - 'true' to select, 'false' to remove selection
|
||||
*/
|
||||
public set selected(state: boolean) {
|
||||
if (state) {
|
||||
this.holder.classList.add(Block.CSS.selected);
|
||||
} else {
|
||||
this.holder.classList.remove(Block.CSS.selected);
|
||||
this.holder.classList.toggle(Block.CSS.selected, state);
|
||||
|
||||
const fakeCursorWillBeAdded = state === true && SelectionUtils.isRangeInsideContainer(this.holder);
|
||||
const fakeCursorWillBeRemoved = state === false && SelectionUtils.isFakeCursorInsideContainer(this.holder);
|
||||
|
||||
if (fakeCursorWillBeAdded || fakeCursorWillBeRemoved) {
|
||||
this.editorEventBus?.emit(FakeCursorAboutToBeToggled, { state }); // mutex
|
||||
|
||||
if (fakeCursorWillBeAdded) {
|
||||
SelectionUtils.addFakeCursor();
|
||||
} else {
|
||||
SelectionUtils.removeFakeCursor(this.holder);
|
||||
}
|
||||
|
||||
this.editorEventBus?.emit(FakeCursorHaveBeenSet, { state });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -441,7 +472,7 @@ export default class Block {
|
|||
/**
|
||||
* Set stretched state
|
||||
*
|
||||
* @param {boolean} state - 'true' to enable, 'false' to disable stretched statte
|
||||
* @param {boolean} state - 'true' to enable, 'false' to disable stretched state
|
||||
*/
|
||||
public set stretched(state: boolean) {
|
||||
this.holder.classList.toggle(Block.CSS.wrapperStretched, state);
|
||||
|
@ -471,23 +502,7 @@ export default class Block {
|
|||
* @returns {HTMLElement}
|
||||
*/
|
||||
public get pluginsContent(): HTMLElement {
|
||||
const blockContentNodes = this.holder.querySelector(`.${Block.CSS.content}`);
|
||||
|
||||
if (blockContentNodes && blockContentNodes.childNodes.length) {
|
||||
/**
|
||||
* Editors Block content can contain different Nodes from extensions
|
||||
* We use DOM isExtensionNode to ignore such Nodes and return first Block that does not match filtering list
|
||||
*/
|
||||
for (let child = blockContentNodes.childNodes.length - 1; child >= 0; child--) {
|
||||
const contentNode = blockContentNodes.childNodes[child];
|
||||
|
||||
if (!$.isExtensionNode(contentNode)) {
|
||||
return contentNode as HTMLElement;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
return this.toolRenderedElement;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -502,7 +517,7 @@ export default class Block {
|
|||
/**
|
||||
* call Tool's method with the instance context
|
||||
*/
|
||||
if (this.tool[methodName] && this.tool[methodName] instanceof Function) {
|
||||
if (_.isFunction(this.toolInstance[methodName])) {
|
||||
if (methodName === BlockToolAPI.APPEND_CALLBACK) {
|
||||
_.log(
|
||||
'`appendCallback` hook is deprecated and will be removed in the next major release. ' +
|
||||
|
@ -513,7 +528,7 @@ export default class Block {
|
|||
|
||||
try {
|
||||
// eslint-disable-next-line no-useless-call
|
||||
this.tool[methodName].call(this.tool, params);
|
||||
this.toolInstance[methodName].call(this.toolInstance, params);
|
||||
} catch (e) {
|
||||
_.log(`Error during '${methodName}' call: ${e.message}`, 'error');
|
||||
}
|
||||
|
@ -526,7 +541,7 @@ export default class Block {
|
|||
* @param {BlockToolData} data - data to merge
|
||||
*/
|
||||
public async mergeWith(data: BlockToolData): Promise<void> {
|
||||
await this.tool.merge(data);
|
||||
await this.toolInstance.merge(data);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -535,8 +550,23 @@ export default class Block {
|
|||
*
|
||||
* @returns {object}
|
||||
*/
|
||||
public async save(): Promise<void|SavedData> {
|
||||
const extractedBlock = await this.tool.save(this.pluginsContent as HTMLElement);
|
||||
public async save(): Promise<undefined | SavedData> {
|
||||
const extractedBlock = await this.toolInstance.save(this.pluginsContent as HTMLElement);
|
||||
const tunesData: { [name: string]: BlockTuneData } = this.unavailableTunesData;
|
||||
|
||||
[
|
||||
...this.tunesInstances.entries(),
|
||||
...this.defaultTunesInstances.entries(),
|
||||
]
|
||||
.forEach(([name, tune]) => {
|
||||
if (_.isFunction(tune.save)) {
|
||||
try {
|
||||
tunesData[name] = tune.save();
|
||||
} catch (e) {
|
||||
_.log(`Tune ${tune.constructor.name} save method throws an Error %o`, 'warn', e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Measuring execution time
|
||||
|
@ -550,13 +580,15 @@ export default class Block {
|
|||
measuringEnd = window.performance.now();
|
||||
|
||||
return {
|
||||
id: this.id,
|
||||
tool: this.name,
|
||||
data: finishedExtraction,
|
||||
tunes: tunesData,
|
||||
time: measuringEnd - measuringStart,
|
||||
};
|
||||
})
|
||||
.catch((error) => {
|
||||
_.log(`Saving proccess for ${this.name} tool failed due to the ${error}`, 'log', 'red');
|
||||
_.log(`Saving process for ${this.name} tool failed due to the ${error}`, 'log', 'red');
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -565,64 +597,68 @@ export default class Block {
|
|||
* Tool's validation method is optional
|
||||
*
|
||||
* @description Method returns true|false whether data passed the validation or not
|
||||
*
|
||||
* @param {BlockToolData} data - data to validate
|
||||
* @returns {Promise<boolean>} valid
|
||||
*/
|
||||
public async validate(data: BlockToolData): Promise<boolean> {
|
||||
let isValid = true;
|
||||
|
||||
if (this.tool.validate instanceof Function) {
|
||||
isValid = await this.tool.validate(data);
|
||||
if (this.toolInstance.validate instanceof Function) {
|
||||
isValid = await this.toolInstance.validate(data);
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make an array with default settings
|
||||
* Each block has default tune instance that have states
|
||||
*
|
||||
* @returns {BlockTune[]}
|
||||
* Returns data to render in Block Tunes menu.
|
||||
* Splits block tunes into 2 groups: block specific tunes and common tunes
|
||||
*/
|
||||
public makeTunes(): BlockTune[] {
|
||||
const tunesList = [
|
||||
{
|
||||
name: 'moveUp',
|
||||
Tune: MoveUpTune,
|
||||
},
|
||||
{
|
||||
name: 'delete',
|
||||
Tune: DeleteTune,
|
||||
},
|
||||
{
|
||||
name: 'moveDown',
|
||||
Tune: MoveDownTune,
|
||||
},
|
||||
];
|
||||
public getTunes(): {
|
||||
toolTunes: PopoverItemParams[];
|
||||
commonTunes: PopoverItemParams[];
|
||||
} {
|
||||
const toolTunesPopoverParams: TunesMenuConfigItem[] = [];
|
||||
const commonTunesPopoverParams: TunesMenuConfigItem[] = [];
|
||||
|
||||
// Pluck tunes list and return tune instances with passed Editor API and settings
|
||||
return tunesList.map(({ name, Tune }: {name: string; Tune: BlockTuneConstructable}) => {
|
||||
return new Tune({
|
||||
api: this.api.getMethodsForTool(name, ToolType.Tune),
|
||||
settings: this.config,
|
||||
/** Tool's tunes: may be defined as return value of optional renderSettings method */
|
||||
const tunesDefinedInTool = typeof this.toolInstance.renderSettings === 'function' ? this.toolInstance.renderSettings() : [];
|
||||
|
||||
if ($.isElement(tunesDefinedInTool)) {
|
||||
toolTunesPopoverParams.push({
|
||||
type: PopoverItemType.Html,
|
||||
element: tunesDefinedInTool,
|
||||
});
|
||||
});
|
||||
}
|
||||
} else if (Array.isArray(tunesDefinedInTool)) {
|
||||
toolTunesPopoverParams.push(...tunesDefinedInTool);
|
||||
} else {
|
||||
toolTunesPopoverParams.push(tunesDefinedInTool);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumerates initialized tunes and returns fragment that can be appended to the toolbars area
|
||||
*
|
||||
* @returns {DocumentFragment}
|
||||
*/
|
||||
public renderTunes(): DocumentFragment {
|
||||
const tunesElement = document.createDocumentFragment();
|
||||
/** Common tunes: combination of default tunes (move up, move down, delete) and third-party tunes connected via tunes api */
|
||||
const commonTunes = [
|
||||
...this.tunesInstances.values(),
|
||||
...this.defaultTunesInstances.values(),
|
||||
].map(tuneInstance => tuneInstance.render());
|
||||
|
||||
this.tunes.forEach((tune) => {
|
||||
$.append(tunesElement, tune.render());
|
||||
/** Separate custom html from Popover items params for common tunes */
|
||||
commonTunes.forEach(tuneConfig => {
|
||||
if ($.isElement(tuneConfig)) {
|
||||
commonTunesPopoverParams.push({
|
||||
type: PopoverItemType.Html,
|
||||
element: tuneConfig,
|
||||
});
|
||||
} else if (Array.isArray(tuneConfig)) {
|
||||
commonTunesPopoverParams.push(...tuneConfig);
|
||||
} else {
|
||||
commonTunesPopoverParams.push(tuneConfig);
|
||||
}
|
||||
});
|
||||
|
||||
return tunesElement;
|
||||
return {
|
||||
toolTunes: toolTunesPopoverParams,
|
||||
commonTunes: commonTunesPopoverParams,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -641,35 +677,73 @@ export default class Block {
|
|||
}
|
||||
|
||||
/**
|
||||
* Is fired when Block will be selected as current
|
||||
* Allows to say Editor that Block was changed. Used to manually trigger Editor's 'onChange' callback
|
||||
* Can be useful for block changes invisible for editor core.
|
||||
*/
|
||||
public willSelect(): void {
|
||||
/**
|
||||
* Observe DOM mutations to update Block inputs
|
||||
*/
|
||||
this.mutationObserver.observe(
|
||||
this.holder.firstElementChild,
|
||||
{
|
||||
childList: true,
|
||||
subtree: true,
|
||||
characterData: true,
|
||||
attributes: true,
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Mutation observer doesn't track changes in "<input>" and "<textarea>"
|
||||
* so we need to track focus events to update current input and clear cache.
|
||||
*/
|
||||
this.addInputEvents();
|
||||
public dispatchChange(): void {
|
||||
this.didMutated();
|
||||
}
|
||||
|
||||
/**
|
||||
* Is fired when Block will be unselected
|
||||
* Call Tool instance destroy method
|
||||
*/
|
||||
public willUnselect(): void {
|
||||
this.mutationObserver.disconnect();
|
||||
public destroy(): void {
|
||||
this.unwatchBlockMutations();
|
||||
this.removeInputEvents();
|
||||
|
||||
super.destroy();
|
||||
|
||||
if (_.isFunction(this.toolInstance.destroy)) {
|
||||
this.toolInstance.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tool could specify several entries to be displayed at the Toolbox (for example, "Heading 1", "Heading 2", "Heading 3")
|
||||
* This method returns the entry that is related to the Block (depended on the Block data)
|
||||
*/
|
||||
public async getActiveToolboxEntry(): Promise<ToolboxConfigEntry | undefined> {
|
||||
const toolboxSettings = this.tool.toolbox;
|
||||
|
||||
/**
|
||||
* If Tool specifies just the single entry, treat it like an active
|
||||
*/
|
||||
if (toolboxSettings.length === 1) {
|
||||
return Promise.resolve(this.tool.toolbox[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* If we have several entries with their own data overrides,
|
||||
* find those who matches some current data property
|
||||
*
|
||||
* Example:
|
||||
* Tools' toolbox: [
|
||||
* {title: "Heading 1", data: {level: 1} },
|
||||
* {title: "Heading 2", data: {level: 2} }
|
||||
* ]
|
||||
*
|
||||
* the Block data: {
|
||||
* text: "Heading text",
|
||||
* level: 2
|
||||
* }
|
||||
*
|
||||
* that means that for the current block, the second toolbox item (matched by "{level: 2}") is active
|
||||
*/
|
||||
const blockData = await this.data;
|
||||
const toolboxItems = toolboxSettings;
|
||||
|
||||
return toolboxItems?.find((item) => {
|
||||
return isSameBlockData(item.data, blockData);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports Block data as string using conversion config
|
||||
*/
|
||||
public async exportDataAsString(): Promise<string> {
|
||||
const blockData = await this.data;
|
||||
|
||||
return convertBlockDataToString(blockData, this.tool.conversionConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -680,44 +754,259 @@ export default class Block {
|
|||
private compose(): HTMLDivElement {
|
||||
const wrapper = $.make('div', Block.CSS.wrapper) as HTMLDivElement,
|
||||
contentNode = $.make('div', Block.CSS.content),
|
||||
pluginsContent = this.tool.render();
|
||||
pluginsContent = this.toolInstance.render();
|
||||
|
||||
contentNode.appendChild(pluginsContent);
|
||||
wrapper.appendChild(contentNode);
|
||||
if (import.meta.env.MODE === 'test') {
|
||||
wrapper.setAttribute('data-cy', 'block-wrapper');
|
||||
}
|
||||
|
||||
/**
|
||||
* Export id to the DOM three
|
||||
* Useful for standalone modules development. For example, allows to identify Block by some child node. Or scroll to a particular Block by id.
|
||||
*/
|
||||
wrapper.dataset.id = this.id;
|
||||
|
||||
/**
|
||||
* Saving a reference to plugin's content element for guaranteed accessing it later
|
||||
*/
|
||||
this.toolRenderedElement = pluginsContent;
|
||||
|
||||
contentNode.appendChild(this.toolRenderedElement);
|
||||
|
||||
/**
|
||||
* Block Tunes might wrap Block's content node to provide any UI changes
|
||||
*
|
||||
* <tune2wrapper>
|
||||
* <tune1wrapper>
|
||||
* <blockContent />
|
||||
* </tune1wrapper>
|
||||
* </tune2wrapper>
|
||||
*/
|
||||
let wrappedContentNode: HTMLElement = contentNode;
|
||||
|
||||
[...this.tunesInstances.values(), ...this.defaultTunesInstances.values()]
|
||||
.forEach((tune) => {
|
||||
if (_.isFunction(tune.wrap)) {
|
||||
try {
|
||||
wrappedContentNode = tune.wrap(wrappedContentNode);
|
||||
} catch (e) {
|
||||
_.log(`Tune ${tune.constructor.name} wrap method throws an Error %o`, 'warn', e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
wrapper.appendChild(wrappedContentNode);
|
||||
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate Block Tunes
|
||||
*
|
||||
* @param tunesData - current Block tunes data
|
||||
* @private
|
||||
*/
|
||||
private composeTunes(tunesData: { [name: string]: BlockTuneData }): void {
|
||||
Array.from(this.tunes.values()).forEach((tune) => {
|
||||
const collection = tune.isInternal ? this.defaultTunesInstances : this.tunesInstances;
|
||||
|
||||
collection.set(tune.name, tune.create(tunesData[tune.name], this.blockAPI));
|
||||
});
|
||||
|
||||
/**
|
||||
* Check if there is some data for not available tunes
|
||||
*/
|
||||
Object.entries(tunesData).forEach(([name, data]) => {
|
||||
if (!this.tunesInstances.has(name)) {
|
||||
this.unavailableTunesData[name] = data;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Is fired when text input or contentEditable is focused
|
||||
*/
|
||||
private handleFocus = (): void => {
|
||||
/**
|
||||
* Drop cache
|
||||
* Drop inputs cache to query the new ones
|
||||
*/
|
||||
this.cachedInputs = [];
|
||||
this.dropInputsCache();
|
||||
|
||||
/**
|
||||
* Update current input
|
||||
*/
|
||||
this.updateCurrentInput();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds focus event listeners to all inputs and contentEditables
|
||||
* Adds focus event listeners to all inputs and contenteditable
|
||||
*/
|
||||
private addInputEvents(): void {
|
||||
this.inputs.forEach(input => {
|
||||
input.addEventListener('focus', this.handleFocus);
|
||||
|
||||
/**
|
||||
* If input is native input add oninput listener to observe changes
|
||||
*/
|
||||
if ($.isNativeInput(input)) {
|
||||
input.addEventListener('input', this.didMutated as EventListener);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* removes focus event listeners from all inputs and contentEditables
|
||||
* removes focus event listeners from all inputs and contenteditable
|
||||
*/
|
||||
private removeInputEvents(): void {
|
||||
this.inputs.forEach(input => {
|
||||
input.removeEventListener('focus', this.handleFocus);
|
||||
|
||||
if ($.isNativeInput(input)) {
|
||||
input.removeEventListener('input', this.didMutated as EventListener);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Is fired when DOM mutation has been happened
|
||||
*
|
||||
* @param mutationsOrInputEvent - actual changes
|
||||
* - MutationRecord[] - any DOM change
|
||||
* - InputEvent — <input> change
|
||||
* - undefined — manual triggering of block.dispatchChange()
|
||||
*/
|
||||
private readonly didMutated = (mutationsOrInputEvent: MutationRecord[] | InputEvent = undefined): void => {
|
||||
/**
|
||||
* Block API have dispatchChange() method. In this case, mutations list will be undefined.
|
||||
*/
|
||||
const isManuallyDispatched = mutationsOrInputEvent === undefined;
|
||||
|
||||
/**
|
||||
* True if didMutated has been called as "input" event handler
|
||||
*/
|
||||
const isInputEventHandler = mutationsOrInputEvent instanceof InputEvent;
|
||||
|
||||
/**
|
||||
* If tool updates its own root element, we need to renew it in our memory
|
||||
*/
|
||||
if (!isManuallyDispatched && !isInputEventHandler) {
|
||||
this.detectToolRootChange(mutationsOrInputEvent);
|
||||
}
|
||||
|
||||
/**
|
||||
* We won't fire a Block mutation event if mutation contain only nodes marked with 'data-mutation-free' attributes
|
||||
*/
|
||||
let shouldFireUpdate;
|
||||
|
||||
if (isManuallyDispatched) {
|
||||
shouldFireUpdate = true;
|
||||
} else if (isInputEventHandler) {
|
||||
shouldFireUpdate = true;
|
||||
} else {
|
||||
/**
|
||||
* Update from 2023, Feb 17:
|
||||
* Changed mutationsOrInputEvent.some() to mutationsOrInputEvent.every()
|
||||
* since there could be a real mutations same-time with mutation-free changes,
|
||||
* for example when Block Tune change: block is changing along with FakeCursor (mutation-free) removing
|
||||
* — we should fire 'didMutated' event in that case
|
||||
*/
|
||||
const everyRecordIsMutationFree = mutationsOrInputEvent.length > 0 && mutationsOrInputEvent.every((record) => {
|
||||
const { addedNodes, removedNodes, target } = record;
|
||||
const changedNodes = [
|
||||
...Array.from(addedNodes),
|
||||
...Array.from(removedNodes),
|
||||
target,
|
||||
];
|
||||
|
||||
return changedNodes.some((node) => {
|
||||
if (!$.isElement(node)) {
|
||||
/**
|
||||
* "characterData" mutation record has Text node as a target, so we need to get parent element to check it for mutation-free attribute
|
||||
*/
|
||||
node = node.parentElement;
|
||||
}
|
||||
|
||||
return node && (node as HTMLElement).closest('[data-mutation-free="true"]') !== null;
|
||||
});
|
||||
});
|
||||
|
||||
shouldFireUpdate = !everyRecordIsMutationFree;
|
||||
}
|
||||
|
||||
/**
|
||||
* In case some mutation free elements are added or removed, do not trigger didMutated event
|
||||
*/
|
||||
if (!shouldFireUpdate) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.dropInputsCache();
|
||||
|
||||
/**
|
||||
* Update current input
|
||||
*/
|
||||
this.updateCurrentInput();
|
||||
|
||||
this.call(BlockToolAPI.UPDATED);
|
||||
|
||||
/**
|
||||
* Emit a Block Event with current Block instance.
|
||||
* Block Manager subscribed to these events
|
||||
*/
|
||||
this.emit('didMutated', this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Listen common editor Dom Changed event and detect mutations related to the Block
|
||||
*/
|
||||
private watchBlockMutations(): void {
|
||||
/**
|
||||
* Save callback to a property to remove it on Block destroy
|
||||
*
|
||||
* @param payload - event payload
|
||||
*/
|
||||
this.redactorDomChangedCallback = (payload) => {
|
||||
const { mutations } = payload;
|
||||
|
||||
const mutationBelongsToBlock = mutations.some(record => isMutationBelongsToElement(record, this.toolRenderedElement));
|
||||
|
||||
if (mutationBelongsToBlock) {
|
||||
this.didMutated(mutations);
|
||||
}
|
||||
};
|
||||
|
||||
this.editorEventBus?.on(RedactorDomChanged, this.redactorDomChangedCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove redactor dom change event listener
|
||||
*/
|
||||
private unwatchBlockMutations(): void {
|
||||
this.editorEventBus?.off(RedactorDomChanged, this.redactorDomChangedCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sometimes Tool can replace own main element, for example H2 -> H4 or UL -> OL
|
||||
* We need to detect such changes and update a link to tools main element with the new one
|
||||
*
|
||||
* @param mutations - records of block content mutations
|
||||
*/
|
||||
private detectToolRootChange(mutations: MutationRecord[]): void {
|
||||
mutations.forEach(record => {
|
||||
const toolRootHasBeenUpdated = Array.from(record.removedNodes).includes(this.toolRenderedElement);
|
||||
|
||||
if (toolRootHasBeenUpdated) {
|
||||
const newToolElement = record.addedNodes[record.addedNodes.length - 1];
|
||||
|
||||
this.toolRenderedElement = newToolElement as HTMLElement;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears inputs cached value
|
||||
*/
|
||||
private dropInputsCache(): void {
|
||||
this.cachedInputs = [];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,11 +6,8 @@ import { MoveEvent } from '../../types/tools';
|
|||
/**
|
||||
* @class Blocks
|
||||
* @classdesc Class to work with Block instances array
|
||||
*
|
||||
* @private
|
||||
*
|
||||
* @property {HTMLElement} workingArea — editor`s working node
|
||||
*
|
||||
*/
|
||||
export default class Blocks {
|
||||
/**
|
||||
|
@ -25,7 +22,6 @@ export default class Blocks {
|
|||
|
||||
/**
|
||||
* @class
|
||||
*
|
||||
* @param {HTMLElement} workingArea — editor`s working node
|
||||
*/
|
||||
constructor(workingArea: HTMLElement) {
|
||||
|
@ -65,7 +61,6 @@ export default class Blocks {
|
|||
*
|
||||
* @example
|
||||
* blocks[0] = new Block(...)
|
||||
*
|
||||
* @param {Blocks} instance — Blocks instance
|
||||
* @param {PropertyKey} property — block index or any Blocks class property key to set
|
||||
* @param {Block} value — value to set
|
||||
|
@ -225,6 +220,62 @@ export default class Blocks {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces block under passed index with passed block
|
||||
*
|
||||
* @param index - index of existed block
|
||||
* @param block - new block
|
||||
*/
|
||||
public replace(index: number, block: Block): void {
|
||||
if (this.blocks[index] === undefined) {
|
||||
throw Error('Incorrect index');
|
||||
}
|
||||
|
||||
const prevBlock = this.blocks[index];
|
||||
|
||||
prevBlock.holder.replaceWith(block.holder);
|
||||
|
||||
this.blocks[index] = block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts several blocks at once
|
||||
*
|
||||
* @param blocks - blocks to insert
|
||||
* @param index - index to insert blocks at
|
||||
*/
|
||||
public insertMany(blocks: Block[], index: number ): void {
|
||||
const fragment = new DocumentFragment();
|
||||
|
||||
for (const block of blocks) {
|
||||
fragment.appendChild(block.holder);
|
||||
}
|
||||
|
||||
if (this.length > 0) {
|
||||
if (index > 0) {
|
||||
const previousBlockIndex = Math.min(index - 1, this.length - 1);
|
||||
const previousBlock = this.blocks[previousBlockIndex];
|
||||
|
||||
previousBlock.holder.after(fragment);
|
||||
} else if (index === 0) {
|
||||
this.workingArea.prepend(fragment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert blocks to the array at the specified index
|
||||
*/
|
||||
this.blocks.splice(index, 0, ...blocks);
|
||||
} else {
|
||||
this.blocks.push(...blocks);
|
||||
this.workingArea.appendChild(fragment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call Rendered event for each block
|
||||
*/
|
||||
blocks.forEach((block) => block.call(BlockToolAPI.RENDERED));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove block
|
||||
*
|
||||
|
@ -257,7 +308,6 @@ export default class Blocks {
|
|||
* Insert Block after passed target
|
||||
*
|
||||
* @todo decide if this method is necessary
|
||||
*
|
||||
* @param {Block} targetBlock — target after which Block should be inserted
|
||||
* @param {Block} newBlock — Block to insert
|
||||
*/
|
||||
|
@ -273,7 +323,7 @@ export default class Blocks {
|
|||
* @param {number} index — Block index
|
||||
* @returns {Block}
|
||||
*/
|
||||
public get(index: number): Block {
|
||||
public get(index: number): Block | undefined {
|
||||
return this.blocks[index];
|
||||
}
|
||||
|
||||
|
|
5
src/components/constants.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
/**
|
||||
* Debounce timeout for selection change event
|
||||
* {@link modules/ui.ts}
|
||||
*/
|
||||
export const selectionChangeDebounceTimeout = 180;
|
|
@ -1,41 +1,15 @@
|
|||
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';
|
||||
import Modules from './modules';
|
||||
import { EditorEventMap } from './events';
|
||||
|
||||
/**
|
||||
* @typedef {Core} Core - editor core class
|
||||
*/
|
||||
|
||||
/**
|
||||
* Require Editor modules places in components/modules dir
|
||||
*/
|
||||
const contextRequire = require.context('./modules', true);
|
||||
|
||||
const modules = [];
|
||||
|
||||
contextRequire.keys().forEach((filename) => {
|
||||
/**
|
||||
* Include files if:
|
||||
* - extension is .js or .ts
|
||||
* - does not starts with _
|
||||
*/
|
||||
if (filename.match(/^\.\/[^_][\w/]*\.([tj])s$/)) {
|
||||
modules.push(contextRequire(filename));
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @class Core
|
||||
*
|
||||
* @classdesc Editor.js core class
|
||||
*
|
||||
* @property {EditorConfig} config - all settings
|
||||
* @property {EditorModules} moduleInstances - constructed editor components
|
||||
*
|
||||
* @type {Core}
|
||||
* Editor.js core class. Bootstraps modules.
|
||||
*/
|
||||
export default class Core {
|
||||
/**
|
||||
|
@ -53,15 +27,20 @@ export default class Core {
|
|||
*/
|
||||
public isReady: Promise<void>;
|
||||
|
||||
/**
|
||||
* Common Editor Event Bus
|
||||
*/
|
||||
private eventsDispatcher: EventsDispatcher<EditorEventMap> = new EventsDispatcher();
|
||||
|
||||
/**
|
||||
* @param {EditorConfig} config - user configuration
|
||||
*
|
||||
*/
|
||||
constructor(config?: EditorConfig|string) {
|
||||
/**
|
||||
* Ready promise. Resolved if Editor.js is ready to work, rejected otherwise
|
||||
*/
|
||||
let onReady, onFail;
|
||||
let onReady: (value?: void | PromiseLike<void>) => void;
|
||||
let onFail: (reason?: unknown) => void;
|
||||
|
||||
this.isReady = new Promise((resolve, reject) => {
|
||||
onReady = resolve;
|
||||
|
@ -72,32 +51,21 @@ export default class Core {
|
|||
.then(async () => {
|
||||
this.configuration = config;
|
||||
|
||||
await this.validate();
|
||||
await this.init();
|
||||
this.validate();
|
||||
this.init();
|
||||
await this.start();
|
||||
await this.render();
|
||||
|
||||
_.logLabeled('I\'m ready! (ノ◕ヮ◕)ノ*:・゚✧', 'log', '', 'color: #E24A75');
|
||||
const { BlockManager, Caret, UI, ModificationsObserver } = this.moduleInstances;
|
||||
|
||||
setTimeout(async () => {
|
||||
await this.render();
|
||||
UI.checkEmptiness();
|
||||
ModificationsObserver.enable();
|
||||
|
||||
if ((this.configuration as EditorConfig).autofocus) {
|
||||
const { BlockManager, Caret } = this.moduleInstances;
|
||||
if ((this.configuration as EditorConfig).autofocus) {
|
||||
Caret.setToBlock(BlockManager.blocks[0], Caret.positions.START);
|
||||
}
|
||||
|
||||
Caret.setToBlock(BlockManager.blocks[0], Caret.positions.START);
|
||||
BlockManager.highlightCurrentNode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove loader, show content
|
||||
*/
|
||||
this.moduleInstances.UI.removeLoader();
|
||||
|
||||
/**
|
||||
* Resolve this.isReady promise
|
||||
*/
|
||||
onReady();
|
||||
}, 500);
|
||||
onReady();
|
||||
})
|
||||
.catch((error) => {
|
||||
_.log(`Editor.js is not ready because of ${error}`, 'error');
|
||||
|
@ -116,11 +84,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 +105,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
|
||||
*/
|
||||
|
@ -165,6 +135,7 @@ export default class Core {
|
|||
*
|
||||
* @type {number}
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
|
||||
this.config.minHeight = this.config.minHeight !== undefined ? this.config.minHeight : 300;
|
||||
|
||||
/**
|
||||
|
@ -188,7 +159,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 +169,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 +178,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';
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -227,16 +193,14 @@ export default class Core {
|
|||
*
|
||||
* @returns {EditorConfig}
|
||||
*/
|
||||
public get configuration(): EditorConfig|string {
|
||||
public get configuration(): EditorConfig {
|
||||
return this.config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for required fields in Editor's config
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async validate(): Promise<void> {
|
||||
public validate(): void {
|
||||
const { holderId, holder } = this.config;
|
||||
|
||||
if (holderId && holder) {
|
||||
|
@ -251,7 +215,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');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -324,26 +288,14 @@ export default class Core {
|
|||
* Make modules instances and save it to the @property this.moduleInstances
|
||||
*/
|
||||
private constructModules(): void {
|
||||
modules.forEach((module) => {
|
||||
/**
|
||||
* If module has non-default exports, passed object contains them all and default export as 'default' property
|
||||
*/
|
||||
const Module = _.isFunction(module) ? module : module.default;
|
||||
|
||||
Object.entries(Modules).forEach(([key, module]) => {
|
||||
try {
|
||||
/**
|
||||
* We use class name provided by displayName property
|
||||
*
|
||||
* On build, Babel will transform all Classes to the Functions so, name will always be 'Function'
|
||||
* To prevent this, we use 'babel-plugin-class-display-name' plugin
|
||||
*
|
||||
* @see https://www.npmjs.com/package/babel-plugin-class-display-name
|
||||
*/
|
||||
this.moduleInstances[Module.displayName] = new Module({
|
||||
this.moduleInstances[key] = new module({
|
||||
config: this.configuration,
|
||||
eventsDispatcher: this.eventsDispatcher,
|
||||
});
|
||||
} catch (e) {
|
||||
_.log(`Module ${Module.displayName} skipped because`, 'warn', e);
|
||||
_.log('[constructModules]', `Module ${key} skipped because`, 'error', e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -45,19 +45,20 @@ export default class Dom {
|
|||
}
|
||||
|
||||
/**
|
||||
* Helper for making Elements with classname and attributes
|
||||
* Helper for making Elements with class name and attributes
|
||||
*
|
||||
* @param {string} tagName - new Element tag name
|
||||
* @param {string[]|string} [classNames] - list or name of CSS classname(s)
|
||||
* @param {string[]|string} [classNames] - list or name of CSS class name(s)
|
||||
* @param {object} [attributes] - any attributes
|
||||
*
|
||||
* @returns {HTMLElement}
|
||||
*/
|
||||
public static make(tagName: string, classNames: string|string[] = null, attributes: object = {}): HTMLElement {
|
||||
public static make(tagName: string, classNames: string | (string | undefined)[] | null = null, attributes: object = {}): HTMLElement {
|
||||
const el = document.createElement(tagName);
|
||||
|
||||
if (Array.isArray(classNames)) {
|
||||
el.classList.add(...classNames);
|
||||
const validClassnames = classNames.filter(className => className !== undefined) as string[];
|
||||
|
||||
el.classList.add(...validClassnames);
|
||||
} else if (classNames) {
|
||||
el.classList.add(classNames);
|
||||
}
|
||||
|
@ -75,33 +76,12 @@ export default class Dom {
|
|||
* Creates Text Node with the passed content
|
||||
*
|
||||
* @param {string} content - text content
|
||||
*
|
||||
* @returns {Text}
|
||||
*/
|
||||
public static text(content: string): Text {
|
||||
return document.createTextNode(content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates SVG icon linked to the sprite
|
||||
*
|
||||
* @param {string} name - name (id) of icon from sprite
|
||||
* @param {number} [width] - icon width
|
||||
* @param {number} [height] - icon height
|
||||
*
|
||||
* @returns {SVGElement}
|
||||
*/
|
||||
public static svg(name: string, width = 14, height = 14): SVGElement {
|
||||
const icon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
|
||||
icon.classList.add('icon', 'icon--' + name);
|
||||
icon.setAttribute('width', width + 'px');
|
||||
icon.setAttribute('height', height + 'px');
|
||||
icon.innerHTML = `<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#${name}"></use>`;
|
||||
|
||||
return icon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append one or several elements to the parent
|
||||
*
|
||||
|
@ -109,8 +89,8 @@ export default class Dom {
|
|||
* @param {Element|Element[]|DocumentFragment|Text|Text[]} elements - element or elements list
|
||||
*/
|
||||
public static append(
|
||||
parent: Element|DocumentFragment,
|
||||
elements: Element|Element[]|DocumentFragment|Text|Text[]
|
||||
parent: Element | DocumentFragment,
|
||||
elements: Element | Element[] | DocumentFragment | Text | Text[]
|
||||
): void {
|
||||
if (Array.isArray(elements)) {
|
||||
elements.forEach((el) => parent.appendChild(el));
|
||||
|
@ -125,7 +105,7 @@ export default class Dom {
|
|||
* @param {Element} parent - where to append
|
||||
* @param {Element|Element[]} elements - element or elements list
|
||||
*/
|
||||
public static prepend(parent: Element, elements: Element|Element[]): void {
|
||||
public static prepend(parent: Element, elements: Element | Element[]): void {
|
||||
if (Array.isArray(elements)) {
|
||||
elements = elements.reverse();
|
||||
elements.forEach((el) => parent.prepend(el));
|
||||
|
@ -165,10 +145,9 @@ export default class Dom {
|
|||
*
|
||||
* @param {Element} el - element we searching inside. Default - DOM Document
|
||||
* @param {string} selector - searching string
|
||||
*
|
||||
* @returns {Element}
|
||||
*/
|
||||
public static find(el: Element|Document = document, selector: string): Element {
|
||||
public static find(el: Element | Document = document, selector: string): Element | null {
|
||||
return el.querySelector(selector);
|
||||
}
|
||||
|
||||
|
@ -189,10 +168,9 @@ export default class Dom {
|
|||
*
|
||||
* @param {Element|Document} el - element we searching inside. Default - DOM Document
|
||||
* @param {string} selector - searching string
|
||||
*
|
||||
* @returns {NodeList}
|
||||
*/
|
||||
public static findAll(el: Element|Document = document, selector: string): NodeList {
|
||||
public static findAll(el: Element | Document = document, selector: string): NodeList {
|
||||
return el.querySelectorAll(selector);
|
||||
}
|
||||
|
||||
|
@ -202,12 +180,12 @@ export default class Dom {
|
|||
public static get allInputsSelector(): string {
|
||||
const allowedInputTypes = ['text', 'password', 'email', 'number', 'search', 'tel', 'url'];
|
||||
|
||||
return '[contenteditable], textarea, input:not([type]), ' +
|
||||
return '[contenteditable=true], textarea, input:not([type]), ' +
|
||||
allowedInputTypes.map((type) => `input[type="${type}"]`).join(', ');
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all contendeditable, textarea and editable input elements passed holder contains
|
||||
* Find all contenteditable, textarea and editable input elements passed holder contains
|
||||
*
|
||||
* @param holder - element where to find inputs
|
||||
*/
|
||||
|
@ -230,11 +208,9 @@ export default class Dom {
|
|||
* Leaf is the vertex that doesn't have any child nodes
|
||||
*
|
||||
* @description Method recursively goes throw the all Node until it finds the Leaf
|
||||
*
|
||||
* @param {Node} node - root Node. From this vertex we start Deep-first search
|
||||
* {@link https://en.wikipedia.org/wiki/Depth-first_search}
|
||||
* @param {boolean} [atLast] - find last text node
|
||||
*
|
||||
* @returns {Node} - it can be text Node or Element Node, so that caret will able to work with it
|
||||
*/
|
||||
public static getDeepestNode(node: Node, atLast = false): Node {
|
||||
|
@ -287,7 +263,6 @@ export default class Dom {
|
|||
* Check if object is DOM node
|
||||
*
|
||||
* @param {*} node - object to check
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
|
@ -318,7 +293,6 @@ export default class Dom {
|
|||
* Check if passed element is contenteditable
|
||||
*
|
||||
* @param {HTMLElement} element - html element to check
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
public static isContentEditable(element: HTMLElement): boolean {
|
||||
|
@ -329,7 +303,6 @@ export default class Dom {
|
|||
* Checks target if it is native input
|
||||
*
|
||||
* @param {*} target - HTML element or string
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
|
@ -346,7 +319,6 @@ export default class Dom {
|
|||
* Checks if we can set caret
|
||||
*
|
||||
* @param {HTMLElement} target - target to check
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
public static canSetCaret(target: HTMLElement): boolean {
|
||||
|
@ -377,12 +349,11 @@ export default class Dom {
|
|||
*
|
||||
* @description Method checks simple Node without any childs for emptiness
|
||||
* If you have Node with 2 or more children id depth, you better use {@link Dom#isEmpty} method
|
||||
*
|
||||
* @param {Node} node - node to check
|
||||
*
|
||||
* @param {string} [ignoreChars] - char or substring to treat as empty
|
||||
* @returns {boolean} true if it is empty
|
||||
*/
|
||||
public static isNodeEmpty(node: Node): boolean {
|
||||
public static isNodeEmpty(node: Node, ignoreChars?: string): boolean {
|
||||
let nodeText;
|
||||
|
||||
if (this.isSingleTag(node as HTMLElement) && !this.isLineBreakTag(node as HTMLElement)) {
|
||||
|
@ -395,6 +366,10 @@ export default class Dom {
|
|||
nodeText = node.textContent.replace('\u200B', '');
|
||||
}
|
||||
|
||||
if (ignoreChars) {
|
||||
nodeText = nodeText.replace(new RegExp(ignoreChars, 'g'), '');
|
||||
}
|
||||
|
||||
return nodeText.trim().length === 0;
|
||||
}
|
||||
|
||||
|
@ -402,7 +377,6 @@ export default class Dom {
|
|||
* checks node if it is doesn't have any child nodes
|
||||
*
|
||||
* @param {Node} node - node to check
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
public static isLeaf(node: Node): boolean {
|
||||
|
@ -418,11 +392,11 @@ export default class Dom {
|
|||
* {@link https://en.wikipedia.org/wiki/Breadth-first_search}
|
||||
*
|
||||
* @description Pushes to stack all DOM leafs and checks for emptiness
|
||||
*
|
||||
* @param {Node} node - node to check
|
||||
* @param {string} [ignoreChars] - char or substring to treat as empty
|
||||
* @returns {boolean}
|
||||
*/
|
||||
public static isEmpty(node: Node): boolean {
|
||||
public static isEmpty(node: Node, ignoreChars?: string): boolean {
|
||||
/**
|
||||
* Normalize node to merge several text nodes to one to reduce tree walker iterations
|
||||
*/
|
||||
|
@ -437,7 +411,7 @@ export default class Dom {
|
|||
continue;
|
||||
}
|
||||
|
||||
if (this.isLeaf(node) && !this.isNodeEmpty(node)) {
|
||||
if (this.isLeaf(node) && !this.isNodeEmpty(node, ignoreChars)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -453,7 +427,6 @@ export default class Dom {
|
|||
* Check if string contains html elements
|
||||
*
|
||||
* @param {string} str - string to check
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
public static isHTMLString(str: string): boolean {
|
||||
|
@ -468,7 +441,6 @@ export default class Dom {
|
|||
* Return length of node`s text content
|
||||
*
|
||||
* @param {Node} node - node with content
|
||||
*
|
||||
* @returns {number}
|
||||
*/
|
||||
public static getContentLength(node: Node): number {
|
||||
|
@ -523,6 +495,8 @@ export default class Dom {
|
|||
'ruby',
|
||||
'section',
|
||||
'table',
|
||||
'tbody',
|
||||
'thead',
|
||||
'tr',
|
||||
'tfoot',
|
||||
'ul',
|
||||
|
@ -534,7 +508,6 @@ export default class Dom {
|
|||
* Check if passed content includes only inline elements
|
||||
*
|
||||
* @param {string|HTMLElement} data - element or html string
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
public static containsOnlyInlineElements(data: string | HTMLElement): boolean {
|
||||
|
@ -559,7 +532,6 @@ export default class Dom {
|
|||
* Find and return all block elements in the passed parent (including subtree)
|
||||
*
|
||||
* @param {HTMLElement} parent - root element
|
||||
*
|
||||
* @returns {HTMLElement[]}
|
||||
*/
|
||||
public static getDeepestBlockElements(parent: HTMLElement): HTMLElement[] {
|
||||
|
@ -576,7 +548,6 @@ export default class Dom {
|
|||
* Helper for get holder from {string} or return HTMLElement
|
||||
*
|
||||
* @param {string | HTMLElement} element - holder's id or holder's HTML Element
|
||||
*
|
||||
* @returns {HTMLElement}
|
||||
*/
|
||||
public static getHolder(element: string | HTMLElement): HTMLElement {
|
||||
|
@ -587,29 +558,35 @@ export default class Dom {
|
|||
return element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method checks passed Node if it is some extension Node
|
||||
*
|
||||
* @param {Node} node - any node
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
public static isExtensionNode(node: Node): boolean {
|
||||
const extensions = [
|
||||
'GRAMMARLY-EXTENSION',
|
||||
];
|
||||
|
||||
return node && extensions.includes(node.nodeName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if element is anchor (is A tag)
|
||||
*
|
||||
* @param {Element} element - element to check
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
public static isAnchor(element: Element): element is HTMLAnchorElement {
|
||||
return element.tagName.toLowerCase() === 'a';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return element's offset related to the document
|
||||
*
|
||||
* @todo handle case when editor initialized in scrollable popup
|
||||
* @param el - element to compute offset
|
||||
*/
|
||||
public static offset(el): { top: number; left: number; right: number; bottom: number } {
|
||||
const rect = el.getBoundingClientRect();
|
||||
const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
|
||||
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
|
||||
|
||||
const top = rect.top + scrollTop;
|
||||
const left = rect.left + scrollLeft;
|
||||
|
||||
return {
|
||||
top,
|
||||
left,
|
||||
bottom: top + rect.height,
|
||||
right: left + rect.width,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,6 +60,19 @@ export default class DomIterator {
|
|||
return this.items[this.cursor];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets cursor to specified position
|
||||
*
|
||||
* @param cursorPosition - new cursor position
|
||||
*/
|
||||
public setCursor(cursorPosition: number): void {
|
||||
if (cursorPosition < this.items.length && cursorPosition >= -1) {
|
||||
this.dropCursor();
|
||||
this.cursor = cursorPosition;
|
||||
this.items[this.cursor].classList.add(this.focusedCssClass);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets items. Can be used when iterable items changed dynamically
|
||||
*
|
||||
|
@ -162,6 +175,7 @@ export default class DomIterator {
|
|||
/**
|
||||
* Focus input with micro-delay to ensure DOM is updated
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
|
||||
_.delay(() => SelectionUtils.setCursor(this.items[focusedButtonIndex]), 50)();
|
||||
}
|
||||
|
||||
|
|