Repository: codex-team/editor.js Branch: next Commit: 530ec56bb87e Files: 304 Total size: 1.1 MB Directory structure: gitextract_ol6jf4j0/ ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .github/ │ ├── CODE_OF_CONDUCT.md │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ ├── config.yml │ │ └── general_issue.md │ └── workflows/ │ ├── bump-version-on-merge-next.yml │ ├── create-a-release-draft.yml │ ├── cypress.yml │ ├── eslint.yml │ └── publish-package-to-npm.yml ├── .gitignore ├── .npmignore ├── .nvmrc ├── .postcssrc.yml ├── .stylelintrc ├── .vscode/ │ └── settings.json ├── CODEOWNERS ├── LICENSE ├── README.md ├── cypress.config.ts ├── docs/ │ ├── CHANGELOG.md │ ├── api.md │ ├── block-tunes.md │ ├── caret.md │ ├── installation.md │ ├── releases.md │ ├── sanitizer.md │ ├── toolbar-settings.md │ ├── tools-inline.md │ ├── tools.md │ └── usage.md ├── example/ │ ├── example-i18n.html │ ├── example-multiple.html │ ├── example-popup.html │ ├── example-rtl.html │ └── example.html ├── index.html ├── package.json ├── public/ │ └── assets/ │ ├── demo.css │ └── json-preview.js ├── src/ │ ├── codex.ts │ ├── components/ │ │ ├── __module.ts │ │ ├── block/ │ │ │ ├── api.ts │ │ │ └── index.ts │ │ ├── block-tunes/ │ │ │ ├── block-tune-delete.ts │ │ │ ├── block-tune-move-down.ts │ │ │ └── block-tune-move-up.ts │ │ ├── blocks.ts │ │ ├── constants.ts │ │ ├── core.ts │ │ ├── dom.ts │ │ ├── domIterator.ts │ │ ├── errors/ │ │ │ └── critical.ts │ │ ├── events/ │ │ │ ├── BlockChanged.ts │ │ │ ├── BlockHovered.ts │ │ │ ├── EditorMobileLayoutToggled.ts │ │ │ ├── FakeCursorAboutToBeToggled.ts │ │ │ ├── FakeCursorHaveBeenSet.ts │ │ │ ├── RedactorDomChanged.ts │ │ │ └── index.ts │ │ ├── flipper.ts │ │ ├── i18n/ │ │ │ ├── index.ts │ │ │ ├── locales/ │ │ │ │ └── en/ │ │ │ │ └── messages.json │ │ │ └── namespace-internal.ts │ │ ├── inline-tools/ │ │ │ ├── inline-tool-bold.ts │ │ │ ├── inline-tool-convert.ts │ │ │ ├── inline-tool-italic.ts │ │ │ └── inline-tool-link.ts │ │ ├── modules/ │ │ │ ├── api/ │ │ │ │ ├── blocks.ts │ │ │ │ ├── caret.ts │ │ │ │ ├── events.ts │ │ │ │ ├── i18n.ts │ │ │ │ ├── index.ts │ │ │ │ ├── inlineToolbar.ts │ │ │ │ ├── listeners.ts │ │ │ │ ├── notifier.ts │ │ │ │ ├── readonly.ts │ │ │ │ ├── sanitizer.ts │ │ │ │ ├── saver.ts │ │ │ │ ├── selection.ts │ │ │ │ ├── styles.ts │ │ │ │ ├── toolbar.ts │ │ │ │ ├── tools.ts │ │ │ │ ├── tooltip.ts │ │ │ │ └── ui.ts │ │ │ ├── blockEvents.ts │ │ │ ├── blockManager.ts │ │ │ ├── blockSelection.ts │ │ │ ├── caret.ts │ │ │ ├── crossBlockSelection.ts │ │ │ ├── dragNDrop.ts │ │ │ ├── index.ts │ │ │ ├── modificationsObserver.ts │ │ │ ├── paste.ts │ │ │ ├── readonly.ts │ │ │ ├── rectangleSelection.ts │ │ │ ├── renderer.ts │ │ │ ├── saver.ts │ │ │ ├── toolbar/ │ │ │ │ ├── blockSettings.ts │ │ │ │ ├── index.ts │ │ │ │ └── inline.ts │ │ │ ├── tools.ts │ │ │ └── ui.ts │ │ ├── polyfills.ts │ │ ├── selection.ts │ │ ├── tools/ │ │ │ ├── base.ts │ │ │ ├── block.ts │ │ │ ├── collection.ts │ │ │ ├── factory.ts │ │ │ ├── inline.ts │ │ │ └── tune.ts │ │ ├── ui/ │ │ │ └── toolbox.ts │ │ ├── utils/ │ │ │ ├── api.ts │ │ │ ├── bem.ts │ │ │ ├── blocks.ts │ │ │ ├── caret.ts │ │ │ ├── events.ts │ │ │ ├── keyboard.ts │ │ │ ├── listeners.ts │ │ │ ├── mutations.ts │ │ │ ├── notifier.ts │ │ │ ├── popover/ │ │ │ │ ├── components/ │ │ │ │ │ ├── hint/ │ │ │ │ │ │ ├── hint.const.ts │ │ │ │ │ │ ├── hint.css │ │ │ │ │ │ ├── hint.ts │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── popover-header/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── popover-header.const.ts │ │ │ │ │ │ ├── popover-header.ts │ │ │ │ │ │ └── popover-header.types.ts │ │ │ │ │ ├── popover-item/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── popover-item-default/ │ │ │ │ │ │ │ ├── popover-item-default.const.ts │ │ │ │ │ │ │ └── popover-item-default.ts │ │ │ │ │ │ ├── popover-item-html/ │ │ │ │ │ │ │ ├── popover-item-html.const.ts │ │ │ │ │ │ │ └── popover-item-html.ts │ │ │ │ │ │ ├── popover-item-separator/ │ │ │ │ │ │ │ ├── popover-item-separator.const.ts │ │ │ │ │ │ │ └── popover-item-separator.ts │ │ │ │ │ │ └── popover-item.ts │ │ │ │ │ └── search-input/ │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── search-input.const.ts │ │ │ │ │ ├── search-input.ts │ │ │ │ │ └── search-input.types.ts │ │ │ │ ├── index.ts │ │ │ │ ├── popover-abstract.ts │ │ │ │ ├── popover-desktop.ts │ │ │ │ ├── popover-inline.ts │ │ │ │ ├── popover-mobile.ts │ │ │ │ ├── popover.const.ts │ │ │ │ └── utils/ │ │ │ │ └── popover-states-history.ts │ │ │ ├── promise-queue.ts │ │ │ ├── resolve-aliases.ts │ │ │ ├── sanitizer.ts │ │ │ ├── scroll-locker.ts │ │ │ ├── shortcuts.ts │ │ │ ├── tools.ts │ │ │ └── tooltip.ts │ │ └── utils.ts │ ├── env.d.ts │ ├── styles/ │ │ ├── animations.css │ │ ├── block.css │ │ ├── export.css │ │ ├── inline-toolbar.css │ │ ├── input.css │ │ ├── main.css │ │ ├── placeholders.css │ │ ├── popover-inline.css │ │ ├── popover.css │ │ ├── rtl.css │ │ ├── stub.css │ │ ├── toolbar.css │ │ ├── toolbox.css │ │ ├── ui.css │ │ └── variables.css │ ├── tools/ │ │ └── stub/ │ │ └── index.ts │ └── types-internal/ │ ├── editor-modules.d.ts │ ├── html-janitor.d.ts │ ├── i18n-internal-namespace.d.ts │ └── module-config.d.ts ├── test/ │ ├── cypress/ │ │ ├── .eslintrc │ │ ├── fixtures/ │ │ │ ├── test.html │ │ │ ├── tools/ │ │ │ │ ├── ContentlessTool.ts │ │ │ │ ├── SimpleHeader.ts │ │ │ │ ├── ToolMock.ts │ │ │ │ └── ToolWithoutConversionExport.ts │ │ │ └── types/ │ │ │ └── PartialBlockMutationEvent.ts │ │ ├── support/ │ │ │ ├── commands.ts │ │ │ ├── e2e.ts │ │ │ ├── index.d.ts │ │ │ ├── index.ts │ │ │ └── utils/ │ │ │ ├── createEditorWithTextBlocks.ts │ │ │ ├── createParagraphMock.ts │ │ │ └── nestedEditorInstance.ts │ │ ├── tests/ │ │ │ ├── api/ │ │ │ │ ├── block.cy.ts │ │ │ │ ├── blocks.cy.ts │ │ │ │ ├── caret.cy.ts │ │ │ │ ├── toolbar.cy.ts │ │ │ │ ├── tools.cy.ts │ │ │ │ └── tunes.cy.ts │ │ │ ├── block-ids.cy.ts │ │ │ ├── copy-paste.cy.ts │ │ │ ├── i18n.cy.ts │ │ │ ├── initialization.cy.ts │ │ │ ├── inline-tools/ │ │ │ │ └── link.cy.ts │ │ │ ├── modules/ │ │ │ │ ├── BlockEvents/ │ │ │ │ │ ├── ArrowLeft.cy.ts │ │ │ │ │ ├── ArrowRight.cy.ts │ │ │ │ │ ├── Backspace.cy.ts │ │ │ │ │ ├── Delete.cy.ts │ │ │ │ │ ├── Enter.cy.ts │ │ │ │ │ ├── Slash.cy.ts │ │ │ │ │ └── Tab.cy.ts │ │ │ │ ├── InlineToolbar.cy.ts │ │ │ │ ├── Renderer.cy.ts │ │ │ │ ├── Saver.cy.ts │ │ │ │ ├── Tools.cy.ts │ │ │ │ └── Ui.cy.ts │ │ │ ├── onchange.cy.ts │ │ │ ├── readOnly.cy.ts │ │ │ ├── sanitisation.cy.ts │ │ │ ├── selection.cy.ts │ │ │ ├── tools/ │ │ │ │ ├── BlockTool.cy.ts │ │ │ │ ├── BlockTune.cy.ts │ │ │ │ ├── InlineTool.cy.ts │ │ │ │ ├── ToolsCollection.cy.ts │ │ │ │ └── ToolsFactory.cy.ts │ │ │ ├── ui/ │ │ │ │ ├── BlockTunes.cy.ts │ │ │ │ ├── DataEmpty.cy.ts │ │ │ │ ├── InlineToolbar.cy.ts │ │ │ │ ├── Placeholders.cy.ts │ │ │ │ └── toolbox.cy.ts │ │ │ ├── utils/ │ │ │ │ ├── flipper.cy.ts │ │ │ │ └── popover.cy.ts │ │ │ └── utils.cy.ts │ │ └── tsconfig.json │ └── testcases.md ├── tsconfig.build.json ├── tsconfig.json ├── tslint.json ├── types/ │ ├── api/ │ │ ├── block.d.ts │ │ ├── blocks.d.ts │ │ ├── caret.d.ts │ │ ├── events.d.ts │ │ ├── i18n.d.ts │ │ ├── index.d.ts │ │ ├── inline-toolbar.d.ts │ │ ├── listeners.d.ts │ │ ├── notifier.d.ts │ │ ├── readonly.d.ts │ │ ├── sanitizer.d.ts │ │ ├── saver.d.ts │ │ ├── selection.d.ts │ │ ├── styles.d.ts │ │ ├── toolbar.d.ts │ │ ├── tools.d.ts │ │ ├── tooltip.d.ts │ │ └── ui.d.ts │ ├── block-tunes/ │ │ ├── block-tune-data.d.ts │ │ ├── block-tune.d.ts │ │ └── index.d.ts │ ├── configs/ │ │ ├── conversion-config.ts │ │ ├── editor-config.d.ts │ │ ├── i18n-config.d.ts │ │ ├── i18n-dictionary.d.ts │ │ ├── index.d.ts │ │ ├── log-levels.d.ts │ │ ├── paste-config.d.ts │ │ └── sanitizer-config.d.ts │ ├── data-formats/ │ │ ├── block-data.d.ts │ │ ├── block-id.ts │ │ ├── index.d.ts │ │ └── output-data.d.ts │ ├── events/ │ │ └── block/ │ │ ├── Base.ts │ │ ├── BlockAdded.ts │ │ ├── BlockChanged.ts │ │ ├── BlockMoved.ts │ │ ├── BlockRemoved.ts │ │ └── index.ts │ ├── index.d.ts │ ├── tools/ │ │ ├── adapters/ │ │ │ ├── base-tool-adapter.d.ts │ │ │ ├── block-tool-adapter.d.ts │ │ │ ├── block-tune-adapter.d.ts │ │ │ ├── inline-tool-adapter.d.ts │ │ │ ├── tool-factory.d.ts │ │ │ ├── tool-type.ts │ │ │ └── tools-collection.d.ts │ │ ├── block-tool-data.d.ts │ │ ├── block-tool.d.ts │ │ ├── hook-events.d.ts │ │ ├── index.d.ts │ │ ├── inline-tool.d.ts │ │ ├── menu-config.d.ts │ │ ├── paste-events.d.ts │ │ ├── tool-config.d.ts │ │ ├── tool-settings.d.ts │ │ └── tool.d.ts │ └── utils/ │ └── popover/ │ ├── hint.d.ts │ ├── index.d.ts │ ├── popover-event.ts │ ├── popover-item-type.ts │ ├── popover-item.d.ts │ └── popover.d.ts ├── vite.config.js └── vite.config.test.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ root = false [*] charset = utf-8 indent_style = space indent_size = 2 end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true ================================================ FILE: .eslintignore ================================================ node_modules *.d.ts src/components/tools/paragraph src/polyfills.ts ================================================ FILE: .eslintrc ================================================ { "extends": [ "codex/ts" ], "globals": { "Node": true, "Range": true, "HTMLElement": true, "HTMLDivElement": true, "Element": true, "Selection": true, "SVGElement": true, "Text": true, "InsertPosition": true, "PropertyKey": true, "MouseEvent": true, "TouchEvent": true, "KeyboardEvent": true, "ClipboardEvent": true, "DragEvent": true, "Event": true, "EventTarget": true, "Document": true, "NodeList": true, "File": true, "FileList": true, "MutationRecord": true, "AddEventListenerOptions": true, "DataTransfer": true, "DOMRect": true, "ClientRect": true, "ArrayLike": true, "InputEvent": true, "unknown": true, "requestAnimationFrame": true, "navigator": true }, "rules": { "jsdoc/require-returns-type": "off", "@typescript-eslint/strict-boolean-expressions": "warn", "@typescript-eslint/consistent-type-imports": "error", "@typescript-eslint/consistent-type-exports": "error" }, "overrides": [ { "files": [ "tsconfig.json", "package.json", "tsconfig.*.json", "tslint.json" ], "rules": { "quotes": [1, "double"], "semi": [1, "never"], } } ] } ================================================ FILE: .github/CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at team@codex.so. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms patreon: editorjs open_collective: editorjs custom: https://codex.so/donate ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report to help us improve Editor.js title: "" labels: bug assignees: '' --- Describe a bug. Steps to reproduce: 1. Go to … 2. Click on … 3. … Expected behavior: Screenshots: Device, Browser, OS: Editor.js version: Plugins you use with their versions: ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false 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. ================================================ FILE: .github/ISSUE_TEMPLATE/general_issue.md ================================================ --- name: General Issue about: Well-designed, algorithmized feature/idea/improvement issue for Editor.js title: '' labels: '' assignees: '' --- The question. Why and how the question has come up. ================================================ FILE: .github/workflows/bump-version-on-merge-next.yml ================================================ 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 author: github-actions 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 }} ================================================ FILE: .github/workflows/create-a-release-draft.yml ================================================ 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 ================================================ FILE: .github/workflows/cypress.yml ================================================ name: Cypress on: [pull_request] jobs: run-tests: strategy: matrix: browser: [firefox, chrome, edge] runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v3 with: node-version: 18 - name: Setup Firefox if: matrix.browser == 'firefox' uses: browser-actions/setup-firefox@v1 with: firefox-version: '115.0esr' - uses: cypress-io/github-action@v6 with: config: video=false browser: ${{ matrix.browser }} build: yarn build:test ================================================ FILE: .github/workflows/eslint.yml ================================================ name: ESLint CodeX on: [pull_request] jobs: lint: name: ESlint runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v3 with: node-version: 18 - name: Cache dependencies uses: actions/cache@v4 with: path: ~/.npm key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }} restore-keys: | ${{ runner.os }}-node- - run: yarn - run: yarn lint ================================================ FILE: .github/workflows/publish-package-to-npm.yml ================================================ 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 ================================================ FILE: .gitignore ================================================ # --- proj files --- .DS_Store Thumbs.db /.idea/ /*.sublime-project /*.sublime-workspace node_modules/* npm-debug.log yarn-error.log test/cypress/screenshots test/cypress/videos dist/ coverage/ .nyc_output/ .vscode/launch.json ================================================ FILE: .npmignore ================================================ * !/dist/**/* !/types/**/* !/LICENSE !/README.md !/package.json ================================================ FILE: .nvmrc ================================================ v18.20.1 ================================================ FILE: .postcssrc.yml ================================================ plugins: # Apply custom property sets via @apply rule # https://github.com/pascalduez/postcss-apply postcss-apply: {} # Convert modern CSS into something most browsers can understand # https://github.com/csstools/postcss-preset-env postcss-preset-env: # Polyfill CSS features # https://github.com/csstools/postcss-preset-env#stage # # List of features with levels: https://cssdb.org/ stage: 0 # Define polyfills based on browsers you are supporting # https://github.com/csstools/postcss-preset-env#browsers browsers: - 'last 2 versions' - '> 1%' # Instruct all plugins to omit pre-polyfilled CSS # https://github.com/csstools/postcss-preset-env#preserve preserve: false # Nested rules unwrapper # https://github.com/postcss/postcss-nested # # As you know 'postcss-preset-env' plugin has an ability to process # 'postcss-nesting' feature but it does not work with BEM # Report: https://github.com/csstools/postcss-preset-env/issues/40 postcss-nested: {} ================================================ FILE: .stylelintrc ================================================ { "rules": { "at-rule-empty-line-before": [ "always", { except: [ "blockless-after-same-name-blockless", "first-nested", ], ignore: [ "after-comment" ], } ], "at-rule-name-case": "lower", "at-rule-name-space-after": "always-single-line", "at-rule-semicolon-newline-after": "always", "block-closing-brace-empty-line-before": "never", "block-closing-brace-newline-after": "always", "block-closing-brace-newline-before": "always-multi-line", "block-closing-brace-space-before": "always-single-line", "block-no-empty": true, "block-opening-brace-newline-after": "always-multi-line", "block-opening-brace-space-after": "always-single-line", "block-opening-brace-space-before": "always", "color-hex-case": "lower", "color-hex-length": "short", "color-no-invalid-hex": true, "comment-empty-line-before": [ "always", { except: [ "first-nested" ], ignore: [ "stylelint-commands" ], } ], "comment-no-empty": true, "comment-whitespace-inside": "always", "custom-property-empty-line-before": [ "always", { except: [ "after-custom-property", "first-nested", ], ignore: [ "after-comment", "inside-single-line-block", ], } ], "declaration-bang-space-after": "never", "declaration-bang-space-before": "always", "declaration-block-no-duplicate-properties": [ true, { ignore: [ "consecutive-duplicates-with-different-values" ], } ], "declaration-block-no-redundant-longhand-properties": true, "declaration-block-no-shorthand-property-overrides": true, "declaration-block-semicolon-newline-after": "always-multi-line", "declaration-block-semicolon-space-after": "always-single-line", "declaration-block-semicolon-space-before": "never", "declaration-block-single-line-max-declarations": 1, "declaration-block-trailing-semicolon": "always", "declaration-colon-newline-after": "always-multi-line", "declaration-colon-space-after": "always-single-line", "declaration-colon-space-before": "never", "declaration-empty-line-before": [ "always", { except: [ "after-declaration", "first-nested", ], ignore: [ "after-comment", "inside-single-line-block", ], } ], "font-family-no-duplicate-names": true, "function-calc-no-unspaced-operator": true, "function-comma-newline-after": "always-multi-line", "function-comma-space-after": "always-single-line", "function-comma-space-before": "never", "function-linear-gradient-no-nonstandard-direction": true, "function-max-empty-lines": 0, "function-name-case": "lower", "function-parentheses-newline-inside": "always-multi-line", "function-parentheses-space-inside": "never-single-line", "function-whitespace-after": "always", "indentation": 4, "keyframe-declaration-no-important": true, "length-zero-no-unit": true, "max-empty-lines": 1, "media-feature-colon-space-after": "always", "media-feature-colon-space-before": "never", "media-feature-name-case": "lower", "media-feature-name-no-unknown": true, "media-feature-parentheses-space-inside": "never", "media-feature-range-operator-space-after": "always", "media-feature-range-operator-space-before": "always", "media-query-list-comma-newline-after": "always-multi-line", "media-query-list-comma-space-after": "always-single-line", "media-query-list-comma-space-before": "never", "no-empty-source": true, "no-eol-whitespace": true, "no-extra-semicolons": true, "no-invalid-double-slash-comments": true, "no-missing-end-of-source-newline": true, "number-leading-zero": "always", "number-no-trailing-zeros": true, "property-case": "lower", "property-no-unknown": true, "rule-empty-line-before": [ "always-multi-line", { except: [ "first-nested" ], ignore: [ "after-comment" ], } ], "selector-attribute-brackets-space-inside": "never", "selector-attribute-operator-space-after": "never", "selector-attribute-operator-space-before": "never", "selector-combinator-space-after": "always", "selector-combinator-space-before": "always", "selector-descendant-combinator-no-non-space": true, "selector-list-comma-newline-after": "always", "selector-list-comma-space-before": "never", "selector-max-empty-lines": 0, "selector-pseudo-class-case": "lower", "selector-pseudo-class-no-unknown": true, "selector-pseudo-class-parentheses-space-inside": "never", "selector-pseudo-element-case": "lower", "selector-pseudo-element-colon-notation": "double", "selector-pseudo-element-no-unknown": true, "selector-type-case": "lower", "selector-type-no-unknown": true, "shorthand-property-no-redundant-values": true, "string-no-newline": true, "unit-case": "lower", "unit-no-unknown": true, "value-list-comma-newline-after": "always-multi-line", "value-list-comma-space-after": "always-single-line", "value-list-comma-space-before": "never", "value-list-max-empty-lines": 0, }, } ================================================ FILE: .vscode/settings.json ================================================ { "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" ] } ================================================ FILE: CODEOWNERS ================================================ * @neSpecc @gohabereg @TatianaFomina @ilyamore88 ================================================ FILE: LICENSE ================================================ 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 Copyright © 2015-present CodeX 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. ================================================ FILE: README.md ================================================

Editor.js Logo

editorjs.io | documentation | changelog

npm Minzipped size Backers on Open Collective Sponsors on Open Collective

## About 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. 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. - 😍  Modern UI out of the box - 💎  Clean JSON output - ⚙️  Well-designed API - 🛍  Various Tools available - 💌  Free and open source Editor.js Overview ## Installation It's quite simple: 1. Install Editor.js 2. Install tools you need 3. Initialize Editor's instance Install using NPM, Yarn, or [CDN](https://www.jsdelivr.com/package/npm/@editorjs/editorjs): ```bash npm i @editorjs/editorjs ``` Choose and install tools: - [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) See the [😎 Awesome Editor.js](https://github.com/editor-js/awesome-editorjs) list for more tools. Initialize the Editor: ```html
``` ```javascript import EditorJS from '@editorjs/editorjs' const editor = new EditorJS({ tools: { // ... your tools } }) ```` 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 const data = await editor.save() ``` ### Example Take a look at the [example.html](example/example.html) to view more detailed examples. ## Roadmap - Unified Toolbars - [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 - [x] Block Tunes support nested menus - [x] Block Tunes support separators - [x] Conversion Menu added to the Block Tunes - [x] Unified Toolbar supports hints - [x] Conversion Toolbar uses Unified Toolbar - [x] Inline Toolbar uses Unified Toolbar - 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 Support Editor.js
## 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.

Mister Auto UPLUCID, K.K. Kane Jamison Content Harmony

[Become a Sponsor](https://opencollective.com/editorjs/contribute/sir-8679/checkout) ### Backers Thank you to all our backers [Become a Backer](https://opencollective.com/editorjs/contribute/backer-8632/checkout) ### Contributors This project exists thanks to all the people who contribute.

### 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 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/) | ================================================ FILE: cypress.config.ts ================================================ import { defineConfig } from 'cypress'; import path from 'node:path'; import vitePreprocessor from 'cypress-vite'; 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', vitePreprocessor({ configFile: path.resolve(__dirname, './vite.config.test.js'), })); /** * 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('@cypress/code-coverage/task')(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, }, }); ================================================ FILE: docs/CHANGELOG.md ================================================ # Changelog ### 2.31.5 - `Fix` - Handle __Ctrl + click__ on links with inline styles applied (e.g., bold, italic) ### 2.31.4 - `Fix` - Prevent inline-toolbar re-renders when linked text is selected ### 2.31.3 - `Fix` - Prevent text formatting removal when applying link ### 2.31.2 - `Fix` - Prevent link removal when applying bold to linked text ### 2.31.1 - `Fix` - Prevent the warning from appearing when `readOnly` mode is initially set to `true` ### 2.31.0 - `New` - Inline tools (those with `isReadOnlySupported` specified) can now be used in read-only mode - `New` - Inline tools (those with `isReadOnlySupported` specified) shortcuts now work in read-only mode - `Improvement` - Block manager passes target tool config to the `conversionConfig.import` method on conversion - `Fix` - Fix selection of first block in read-only initialization with "autofocus=true" - `Fix` - Incorrect caret position after blocks merging in Safari - `Fix` - Several toolbox items exported by the one tool have the same shortcut displayed in toolbox - `Improvement` - The current block reference will be updated in read-only mode when blocks are clicked - `Fix` - codex-notifier and codex-tooltip moved from devDependencies to dependencies in package.json to solve type errors - `Fix` - Handle whitespace input in empty placeholder elements to prevent caret from moving unexpectedly to the end of the placeholder - `Fix` - Fix the memory leak issue in `Shortcuts` class - `Fix` - Fix when / overides selected text outside of the editor - `DX` - Tools submodules removed from the repository - `Improvement` - Shift + Down/Up will allow to select next/previous line instead of Inline Toolbar flipping - `Improvement` - The API `caret.setToBlock()` offset now works across the entire block content, not just the first or last node. - `Improvement` - The API `blocks.renderFromHTML()` became async and now can be awaited. - `Fix` - `blocks.renderFromHTML()` — Error "Can't find a Block to remove." fixed - `Fix` - The API `.clear()` index invalidation fixed ### 2.30.7 - `Fix` - Link insertion in Safari fixed ### 2.30.6 - `Fix` – Fix the display of ‘Convert To’ near blocks that do not have the ‘conversionConfig.export’ rule specified - `Fix` – The Plus button does not appear when the editor is loaded in an iframe in Chrome - `Fix` - Prevent inline toolbar from closing in nested instance of editor ### 2.30.5 – `Fix` – Fix exported types ### 2.30.4 - `Fix` - Tool's exporting types added ### 2.30.3 - `Fix` – I18n in nested popover ### 2.30.2 - `Fix` – The onChange callback won't be fired when editor is initialized in the Read-Only mode - `Fix` – Convert To supports i18n again - `Fix` – Prevent form submit on inline tool click ### 2.30.1 - `Fix` – Remove fake selection after multiple "convert to" inline tool toggles ### 2.30.0 - `New` – Block Tunes now supports nesting items - `New` – Block Tunes now supports separator items - `New` – *Menu Config* – New item type – HTML - `New` – *Menu Config* – Default and HTML items now support hints - `New` – Inline Toolbar has new look 💅 - `New` – Inline Tool's `render()` now supports [Menu Config](https://editorjs.io/menu-config/) format - `New` – *ToolsAPI* – All installed block tools now accessible via ToolsAPI `getBlockTools()` method - `New` – *SelectionAPI* – Exposed methods `save()` and `restore()` that allow to save selection to be able to temporally move focus away, methods `setFakeBackground()` and `removeFakeBackground()` that allow to immitate selection while focus moved away - `New` – *BlocksAPI* – Exposed `getBlockByElement()` method that helps find block by any child html element - `New` – "Convert to" control is now also available in Block Tunes - `New` — Editor.js now supports contenteditable placeholders out of the box. Just add `data-placeholder` or `data-placeholder-active` attribute to make it work. The first one will work like native placeholder while the second one will show placeholder only when block is current. - `Improvement` — Now Paragraph placeholder will be shown for the current paragraph, not only the first one. - `Improvement` - The API `blocks.update` now accepts `tunes` data as optional third argument and makes `data` - block data as optional. - `Improvement` — The ability to merge blocks of different types (if both tools provide the conversionConfig) - `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 - `Improvement` – *MenuConfig* – `TunesMenuConfig` type is deprecated, use the `MenuConfig` instead – `Improvement` — *Types* — `BlockToolConstructorOptions` type improved, `block` and `config` are not optional anymore - `Improvement` - The Plus button and Block Tunes toggler are now better aligned with large line-height blocks, such as Headings - `Improvement` — Creating links on Android devices: now the mobile keyboard will have an "Enter" key for accepting the inserted link. - `Improvement` — Placeholders will stay visible on inputs focus. – `Refactoring` – Switched to Vite as Cypress bundler - `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 – `Fix` — Deleting whitespaces at the start/end of the block - `Fix` — The problem caused by missed "import type" in block mutation event types resolved ### 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!
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) - `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) - `New` - RTL mode added [#670](https://github.com/codex-team/editor.js/issues/670) - `New` - Allows users to provide common `inlineToolbar` property which will be used for all tools whose `inlineToolbar` property is set to `true`. It can be overridden by the tool's own `inlineToolbar` property. Also, inline tools will be ordered according to the order of the inline tools in array provided in the `inlineToolbar` property. [#1056](https://github.com/codex-team/editor.js/issues/1056) - `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` - 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) - `Fix` - Fixed issue with `editor.blocks.delete(index)` method which throws an error when Editor.js is not focused, even after providing a valid index. [#1182](https://github.com/codex-team/editor.js/issues/1182) - `Fix` - Fixed the issue of toolbar not disappearing on entering input in Chinese, Hindi and some other languages. [#1196](https://github.com/codex-team/editor.js/issues/1196) - `Fix` - Do not stop events propagation if not needed (essential for React synthetic events) [#1051](https://github.com/codex-team/editor.js/issues/1051) [#946](https://github.com/codex-team/editor.js/issues/946) - `Fix` - Tool's `destroy` method is not invoked when `editor.destroy()` is called. [#1047](https://github.com/codex-team/editor.js/issues/1047) - `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 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 ### 2.18 - `New` *I18n API* — Ability to provide internalization for Editor.js core and tools. [#751](https://github.com/codex-team/editor.js/issues/751) - `New` — Block API that allows you to access certain Block properties and methods - `Improvements` - TSLint (deprecated) replaced with ESLint, old config changed to [CodeX ESLint Config](https://github.com/codex-team/eslint-config). - `Improvements` - Fix many code-style issues, add missed annotations. - `Improvements` - Adjusted GitHub action for ESLint. - `Improvements` - Blocks API: if `blocks.delete` method is called, but no Block is selected, show warning instead of throwing an error [#1102](https://github.com/codex-team/editor.js/issues/1102) - `Improvements` - Blocks API: allow deletion of blocks by specifying block index via `blocks.delete(index)`. - `Improvements` - UX: Navigate next Block from the last non-initial one creates new initial Block now [#1103](https://github.com/codex-team/editor.js/issues/1103) - `Improvements` - Improve performance of DOM traversing at the `isEmpty()` method [#1095](https://github.com/codex-team/editor.js/issues/1095) - `Improvements` - CODE OF CONDUCT added - `Improvements` - Disabled useCapture flag for a block keydown handling. That will allow plugins to override keydown and stop event propagation, for example, to make own Tab behavior. - `Improvements` - All modules now might have `destroy` method called on Editor.js destroy - `Improvements` - Block settings can contain text inputs, focus will be restored after settings closed [#1090](https://github.com/codex-team/editor.js/issues/1090) - `Fix` - Editor's styles won't be appended to the `` when another instance have already do that [#1079](https://github.com/codex-team/editor.js/issues/1079) - `Fix` - Fixed wrong toolbar icon centering in Firefox [#1120](https://github.com/codex-team/editor.js/pull/1120) - `Fix` - Toolbox: Tool's order in Toolbox now saved in accordance with `tools` object keys order [#1073](https://github.com/codex-team/editor.js/issues/1073) - `Fix` - Setting `autofocus` config property to `true` cause adding `.ce-block--focused` for the autofocused block [#1073](https://github.com/codex-team/editor.js/issues/1124) - `Fix` - Public getter `shortcut` now works for Inline Tools [#1132](https://github.com/codex-team/editor.js/issues/1132) - `Fix` - `CMD+A` handler removed after Editor.js destroy [#1133](https://github.com/codex-team/editor.js/issues/1133) > *Breaking changes* `blocks.getBlockByIndex` method now returns BlockAPI object. To access old value, use BlockAPI.holder property ### 2.17 - `Improvements` - Editor's [onchange callback](https://editorjs.io/configuration#editor-modifications-callback) now accepts an API as a parameter - `Fix` - Some mistakes are fixed in [installation.md](installation.md) - `Fix` - Fixed multiple paste callback triggering in a case when several editors are instantiated [#1011](https://github.com/codex-team/editor.js/issues/1011) - `Fix` - Fixed inline toolbar flipper activation on closing conversion toolbar [#995](https://github.com/codex-team/editor.js/issues/995) - `Improvements` - New window tab is opened by clicking on anchor with ctrl [#1057](https://github.com/codex-team/editor.js/issues/1057) - `Fix` - Fix block-tune buttons alignment in some CSS-resetors that forces `box-sizing: border-box` rule [#1003](https://github.com/codex-team/editor.js/issues/1003) - `Improvements` - New style of a Block Settings button. Focused block background removed. - `New` — Add in-house copy-paste support through `application/x-editor-js` mime-type - `New` Block [lifecycle hook](tools.md#block-lifecycle-hooks) `moved` - `Deprecated` — [`blocks.swap(fromIndex, toIndex)`](api.md) method is deprecated. Use `blocks.move(toIndex, fromIndex)` instead. - `Fix` — Improve plain text paste [#1012](https://github.com/codex-team/editor.js/issues/1012) - `Fix` — Fix multiline paste [#1015](https://github.com/codex-team/editor.js/issues/1015) ### 2.16.1 - `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.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) ### 2.16 - `Improvements` — Inline Toolbar design improved - `Improvements` — Conversion Toolbar now included in the Inline Toolbar [#853](https://github.com/codex-team/editor.js/issues/853) - `Improvements` — All buttons now have beautiful Tooltips provided by [CodeX Tooltips](https://github.com/codex-team/codex.tooltips) - `New` — new Tooltips API for displaying tooltips near your custom elements - `New` *API* — Block [lifecycle hooks](tools.md#block-lifecycle-hooks) - `New` *Inline Tools API* — Ability to specify Tool's title via `title` static getter. - `Fix` — On selection from end to start backspace is working as expected now [#869](https://github.com/codex-team/editor.js/issues/869) - `Fix` — Fix flipper with empty dom iterator [#926](https://github.com/codex-team/editor.js/issues/926) - `Fix` — Normalize node before walking through children at `isEmpty` method [#943](https://github.com/codex-team/editor.js/issues/943) - `Fix` — Fixed Grammarly conflict [#779](https://github.com/codex-team/editor.js/issues/779) - `Improvements` — Module Listeners now correctly removes events with options [#904](https://github.com/codex-team/editor.js/pull/904) - `Improvements` — Styles API: `.cdx-block` default vertical margins decreased from 0.7 to 0.4 ems. - `Fix` — Fixed `getRangeCount` call if range count is 0 [#938](https://github.com/codex-team/editor.js/issues/938) - `New` — Log levels now available to suppress Editor.js console messages [#962](https://github.com/codex-team/editor.js/issues/962) - `Fix` — Fixed wrong navigation on block deletion ### 2.15.1 - `Refactoring` — Constants of tools settings separated by internal and external to correspond API - `Refactoring` — Created universal Flipper class that responses for navigation by keyboard inside of any Toolbars - `Fix` — First CMD+A on block with now uses default behaviour. Fixed problem with second CMD+A after selection clearing [#827](https://github.com/codex-team/editor.js/issues/827) - `Improvements` — Style of inline selection and selected blocks improved - `Fix` - Fixed problem when property 'observer' in modificationObserver is not defined ### 2.15 - `New` — New [`blocks.insert()`](api.md) API method [#715](https://github.com/codex-team/editor.js/issues/715). - `New` *Conversion Toolbar* — Ability to convert one block to another [#704](https://github.com/codex-team/editor.js/issues/704) - `New` *Cross-block selection* — Ability to select multiple blocks by mouse and with SHIFT+ARROWS [#703](https://github.com/codex-team/editor.js/issues/703) - `Deprecated` — [`blocks.insertNewBlock()`](api.md) method is deprecated. Use `blocks.insert()` instead. - `Improvements` — Inline Toolbar now works on mobile devices [#706](https://github.com/codex-team/editor.js/issues/706) - `Improvements` — Toolbar looks better on mobile devices [#706](https://github.com/codex-team/editor.js/issues/706) - `Improvements` — Now `pasteConfig` can return `false` to disable paste handling on your Tool [#801](https://github.com/codex-team/editor.js/issues/801) - `Fix` — EditorConfig's `onChange` callback now fires when native inputs\` content has been changed [#794](https://github.com/codex-team/editor.js/issues/794) - `Fix` — Resolve bug with deleting leading new lines [#726](https://github.com/codex-team/editor.js/issues/726) - `Fix` — Fix inline link Tool to support different link types like `mailto` and `tel` [#809](https://github.com/codex-team/editor.js/issues/809) - `Fix` — Added `typeof` util method to check exact object type [#805](https://github.com/codex-team/editor.js/issues/805) - `Fix` — Remove internal `enableLineBreaks` option from external Tool settings type description [#825](https://github.com/codex-team/editor.js/pull/825) ### 2.14 - `Fix` *Config* — User config now has higher priority than internal settings [#771](https://github.com/codex-team/editor.js/issues/771) - `New` — Ability to work with Block Actions and Inline Toolbar from the keyboard by Tab. [#705](https://github.com/codex-team/editor.js/issues/705) - `Fix` — Fix error thrown by click on the empty editor after `blocks.clear()` method calling [#761](https://github.com/codex-team/editor.js/issues/761) - `Fix` — Fix placeholder property appearance. Now you can assign it via `placeholder` property of EditorConfig. [#714](https://github.com/codex-team/editor.js/issues/714) - `Fix` — Add API shorthands to TS types [#788](https://github.com/codex-team/editor.js/issues/788) ### 2.13 - `Improvements` *BlockSelection* — Block Selection allows to select single editable element via CMD+A - `New` *API* — Added [API methods](api.md) to open and close inline toolbar [#665](https://github.com/codex-team/editor.js/issues/665) - `New` *Config* - Added new property in EditorConfig `holder`, use this property for append Editor instead `holderId`. `holder` property now support reference on dom element. [#696](https://github.com/codex-team/editor.js/issues/696) - `Deprecated` *Config* - `holderId` property now is deprecated and will removed in next major release. Use `holder` instead. - `Fix` *Types* — Fixed error with `codex-notifier` package [#713](https://github.com/codex-team/editor.js/issues/713) - `Improvements` — Close inline toolbar after creating a new link. - `New` *Config* — Option `minHeight` for customizing Editor's bottom zone height added. ### 2.12.4 - `Improvements` — CodeX.Shortcuts version updated to the v1.1 [#684](https://github.com/codex-team/editor.js/issues/684) - `Fix` — Do not start multi-block selection on Toolbox and Inline Toolbar [#646](https://github.com/codex-team/editor.js/issues/646) - `Fix` — Minor fixes of caret behaviour [#663](https://github.com/codex-team/editor.js/issues/663) - `Fix` — Fix inline-link icon position in Firefox [#674](https://github.com/codex-team/editor.js/issues/674) ### 2.12.3 - `Fix` — Make Toolbox tooltip position font-size independent ### 2.12.2 - New *Inline Tools* — pass tool settings from configuration to Tool constructor ### 2.12.1 - `Fix` — Fix processing `color-mod` function in styles ### 2.12.0 - `New` *API* - new `blocks` API method `renderFromHTML` ### 2.11.11 - `New` — Add ability to pass configuration for internal Tools ### 2.11.10 - `Fix` - Fix editor view on mobile devices ### 2.11.9 - `Fix` - Fix inline toolbar buttons margin. Update dependencies list. Update tools for example page. ### 2.11.8 - `Fix` — Block tunes margins now better works with more than 3 buttons ### 2.11.7 - `Fix` *Paste* — Fix pasting into non-initial Blocks ### 2.11.6 - `Fix` *Paste* — Polyfill for Microsoft Edge ### 2.11.5 - `Fix` *RectangleSelection* — Redesign of the scrolling zones ### 2.11.4 - `Fix` - Clear focus when click is outside the Editor instance ### 2.11.3 - `Fix` — Fix CMD+A Selection on multiple Editor instances ### 2.11.2 - `Improvements` — Docs updated and common enhancements ### 2.11.1 - `Fix` *RectangleSelection* — Selection is available only for the main mouse button ### 2.11.0 - `New` — Add API methods shorthands ### 2.10.0 - `New` — Rename from CodeX Editor to Editor.js ### 2.9.5 - `New` — Toolbox now have beautiful helpers with Tool names and shortcuts ### 2.9.4 - `Improvements` — Prevent navigating back on Firefox when Block is removing by backspace ### 2.9.3 - `Fix` — Handle paste only on initial Block ### 2.9.2 - `New` — Blocks selected with Rectangle Selection can be also removed, copied or cut ### 2.9.1 - `Improvements` — Migrate from `postcss-cssnext` to `postcss-preset-env` and disable `postcss-custom-properties` which conflicts with `postcss-preset-env` ### 2.9.0 - `New` *RectangleSelection* — Ability to select Block or several Blocks with mouse ### 2.8.1 - `Fix` *Caret* — Fix "History back" call on backspace in Firefox ### 2.8.0 - `Improvements` *API* — Added [API methods](api.md#caretapi) to manage caret position ### 2.7.32 - `Improvements` *Types* — TypeScript types sre updated ### 2.7.31 - `Fix` — Caret now goes through elements without `type` attribute ### 2.7.30 - `Fix` — Fixed selection behavior when text has modifiers form Inline Toolbar ### 2.7.29 - `Fix` — cmd+x works only for custom selection now ### 2.7.28 - `New` [Tools Validation](https://github.com/codex-team/editor.js/blob/master/docs/tools.md#validate-optional) is added. ### 2.2.27 - `New` *Mobile view* — Editor now adopted for mobile devices - `New` *Narrow mode* — Editor now adopted for narrow containers ### 2.2.26 - `Improvements` *Caret* — Improvements of the caret behaviour: arrows, backspace and enter keys better handling. ### 2.2.25 - `New` *Autofocus* — Now you can set focus at Editor after page has been loaded ### 2.2.24 - `Improvements` *Paste* handling — minor paste handling improvements ### 2.2.23 - `New` *Shortcuts* — copy and cut Blocks selected by CMD+A ### 2.2—2.7 - `New` *Sanitize API* — [Sanitize Config](https://github.com/codex-team/editor.js/blob/master/docs/tools.md#automatic-sanitize) of `Block Tools` now automatically extends by tags of `Inline Tools` that is enabled by current Tool by `inlineToolbar` option. You don't need more to specify `a, b, mark, code` manually. This feature will be added to fields that supports inline markup. - `New` *Block Selection* — Ability to select Block by `CMD+A`, and the whole Editor by double `CMD+A`. After that, you can copy (`CMD+C`), remove (`Backspace`) or clear (`Enter`) selected Blocks. - `New` *[Styles API](https://github.com/codex-team/editor.js/blob/master/types/api/styles.d.ts)* — Added `button` class for stylization of any buttons provided by Tools with one unified style. - `New` *[Notifier API](https://github.com/codex-team/editor.js/blob/master/docs/api.md#notifierapi)* — methods for showing user notifications: on success, errors, warnings, etc. - `New` *Block Tool* — [Table](http://github.com/editor-js/table) constructor 💪 - `New` If one of the Tools is unavailable on Editor initialization, its Blocks will be rendered with *Dummy Block*, describing that user can not edit content of this Block. Dummy Blocks can be moved, removed and saved as normal Blocks. So saved data won't be lost if one of the Tools is failed - `New` [Public TS-types](https://github.com/codex-team/editor.js/tree/master/types) are presented. - `Changes` *Tools API* — options `irreplaceable` and `contentless` was removed. - `Changes` *Tools API* — [Paste API](https://github.com/codex-team/editor.js/blob/master/docs/tools.md#paste-handling): tags, patterns and mime-types now should be specified by Tool's `pasteConfig` static property. Custom Paste Event should be handled by `onPaste(event)` that should not be static from now. - `Changes` *Tools API* — options `displayInToolbox ` and `toolboxIcon` was removed. Use [`toolbox`](https://github.com/codex-team/editor.js/blob/master/docs/tools.md#internal-tool-settings) instead, that should return object with `icon` and `title` field, or `false` if Tool should not be placed at the Toolbox. Also, there are a way to override `toolbox {icon, title}` settings provided by Tool with you own settings at the Initial Config. - `Improvements` — All Projects code now on TypeScript - `Improvements` — NPM package size decreased from 1300kb to 422kb - `Improvements` — Bundle size decreased from 438kb to 252kb - `Improvements` — `Inline Toolbar`: when you add a Link to the selected fragment, Editor will highlight this fragment even when Caret is placed into the URL-input. - `Improvements` — Block Settings won't be shown near empty Blocks of `initialType` by default. You should click on them instead. - `Improvements` — `onChange`-callback now will be fired even with children attributes changing. - `Improvements` — HTMLJanitor package was updated due to found vulnerability - `Improvements` — Logging improved: now all Editor's logs will be preceded by beautiful label with current Editor version. - `Improvements` — Internal `isEmpty` checking was improved for Blocks with many children nodes (200 and more) - `Improvements` — Paste improvements: tags that can be substituted by Tool now will matched even on deep-level of pasted DOM three. - `Improvements` — There is no more «unavailable» sound on copying Block by `CMD+C` on macOS - `Improvements` — Dozens of bugfixes and small improvements See a whole [Changelog](/docs/) ### 2.1-beta changelog - `New` *Tools API* — support pasted content via drag-n-drop or from the Buffer. See [documentation](https://github.com/codex-team/editor.js/blob/master/docs/tools.md#paste-handling) and [example](https://github.com/editor-js/simple-image/blob/master/src/index.js#L177) at the Simple Image Tool. - `New` *Tools API* — new `sanitize` getter for Tools for automatic HTML sanitizing of returned data. See [documentation](https://github.com/codex-team/editor.js/blob/master/docs/tools.md#sanitize) and [example](https://github.com/editor-js/paragraph/blob/master/src/index.js#L121) at the Paragraph Tool - `New` Added `onChange`-callback, fired after any modifications at the Editor. See [documentation](https://github.com/codex-team/editor.js/blob/master/docs/installation.md#features). - `New` New Inline Tool example — [Marker](https://github.com/editor-js/marker) - `New` New Inline Tool example — [Code](https://github.com/editor-js/code) - `New` New [Editor.js PHP](http://github.com/codex-team/codex.editor.backend) — example of server-side implementation with HTML purifying and data validation. - `Improvements` - Improvements of Toolbar's position calculation. - `Improvements` — Improved zero-configuration initialization. - and many little improvements. ================================================ FILE: docs/api.md ================================================ # Editor.js API --- Most actual API described by [this interface](../types/api/index.d.ts). --- 📃 See official API documentation [https://editorjs.io/api](https://editorjs.io/api) --- Tools have access to the public methods provided by Editor.js API Module. Plugin and Tune Developers can use Editor\`s API as they want. ## Block API API for certain Block methods and properties. You can access it through `editor.api.block.getBlockByIndex` method or get it form `block` property of [Tool constructor](../types/tools/block-tool.d.ts) argument. `name: string` — Block's Tool name (key, specified in `tools` property of initial configuration) `config: ToolConfig` — Tool config passed on Editor initialization `holder: HTMLElement` — HTML Element that wraps Tool's HTML content `isEmpty: boolean` — `true` if Block has any editable content `selected: boolean` - `true` if Block is selected with Cross-Block Selection `set stretched(state: boolean)` — set Block's stretch state `stretched: boolean` — `true` if Block is stretched `call(methodName: string, param?: object): void` — method to call any Tool's instance methods with checks and error handlers under-the-hood. For example, [Block lifecycle hooks](./tools.md#block-lifecycle-hooks) `save(): Promise` — returns data saved from current Block's state, including Tool name and saving exec time `validate(data: BlockToolData): Promise` — 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. ```js export interface API { blocks: IBlocksAPI; caret: ICaretAPI; sanitizer: ISanitizerAPI; toolbar: IToolbarAPI; // ... } ``` #### BlocksAPI Methods that working with Blocks `render(data)` - render passed JSON data `renderFromHTML(data)` - parse and render passed HTML string (*not for production use*) `swap(fromIndex, toIndex)` - swaps two Blocks by their positions (deprecated: use 'move' instead) `move(toIndex, fromIndex)` - moves block from one index to another position. `fromIndex` will be the current block's index by default. `delete(blockIndex?: Number)` - deletes Block with passed index `getCurrentBlockIndex()` - current Block index `getBlockByIndex(index: Number)` - returns Block API object by passed index `getBlocksCount()` - returns Blocks count `stretchBlock(index: number, status: boolean)` - _Deprecated. Use Block API interface instead._ make Block stretched. `insertNewBlock()` - __Deprecated__ insert new Block after working place `insert(type?: string, data?: BlockToolData, config?: ToolConfig, index?: number, needToFocus?: boolean)` - insert new Block with passed parameters `update(id: string, data?: BlockToolData, tunes?: {[name: string]: BlockTuneData})` - updates block data and block tunes for the block with passed id #### SanitizerAPI `clean(taintString, config)` - method uses HTMLJanitor to clean taint string. Editor.js provides basic config without attributes, but you can inherit by passing your own config. If Tool enables inline-tools, we get it's sanitizing rules and merge with your passed custom rules. Usage: ```js let taintString = '

BlockWithText

' let customConfig = { b: true, p: { style: true, }, } this.api.sanitizer.clean(taintString, customConfig); ``` ### ToolbarAPI Methods that working with Toolbar `open()` - opens toolbar `close()` - closes toolbar, toolbox and blockSettings if they are opened ### InlineToolbarAPI Methods that works with inline toolbar `open()` - opens inline toolbar, (opens for the current selection) `close()` - closes inline toolbar ### ListenerAPI Methods that allows to work with DOM listener. Useful when you forgot to remove listener. Module collects all listeners and destroys automatically `on(element: HTMLElement, eventType: string, handler: Function, useCapture?: boolean)` - add event listener to HTML element `off(element: HTMLElement, eventType: string, handler: Function)` - remove event handler from HTML element ### CaretAPI Methods to manage caret position. Each method accept `position` and `offset` parameters. `Offset` should be used to shift caret by passed amount of characters. `Position` can be one of the following values: | Value | Description | --------- | ----------- | `start` | Caret will be set at the Block's beginning | `end` | Caret will be set at the Block end | `default` | More or less emulates browser behaviour, in most cases behaves as `start` Each method returns `boolean` value: true if caret is set successfully or false otherwise (e.g. when there is no Block at index); `setToFirstBlock(position?: 'end'|'start'|'default', offset?: number): boolean;` — set caret to the first Block `setToLastBlock(position?: 'end'|'start'|'default', offset?: number): boolean;` — set caret to the last Block `setToNextBlock(position?: 'end'|'start'|'default', offset?: number): boolean;` — set caret to the next Block `setToPreviousBlock(position?: 'end'|'start'|'default', offset?: number): boolean;` — set caret to the previous Block `setToBlock(index: number, position?: 'end'|'start'|'default', offset?: number): boolean;` — set caret to the Block by passed `index` `focus(atEnd?: boolean): boolean;` — set caret to the Editor. If `atEnd` is true, set it at the end. ### NotifierAPI If you need to show any messages for success or failure events you can use notifications module. Call on target Editor: ```javascript let editor = new EditorJS({ onReady: () => { editor.notifier.show({ message: 'Editor is ready!' }); }, }); ``` In Tool's class: ```javascript this.api.notifier.show({ message: 'Cannot upload image. Wrong mime-type.', style: 'error', }); ``` ![](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. ### Destroy API If there are necessity to remove Editor.js instance from the page you can use `destroy()` method. It makes following steps: 1. Clear the holder element by setting it\`s innerHTML to empty string 2. Remove all event listeners related to Editor.js 3. Delete all properties from instance object and set it\`s prototype to `null` After executing the `destroy` method, editor inctance becomes an empty object. This way you will free occupied JS Heap on your page. ### Tooltip API Methods for showing Tooltip helper near your elements. Parameters are the same as in [CodeX Tooltips](http://github.com/codex-team/codex.tooltips) lib. #### Show Method shows tooltip with custom content on passed element ```js this.api.tooltip.show(element, content, options); ``` | parameter | type | description | | -- | -- | -- | | `element` | _HTMLElement_ | Tooltip will be showed near this element | | `content` | _String_ or _Node_ | Content that will be appended to the Tooltip | | `options` | _Object_ | Some displaying options, see below | Available showing options | name | type | action | | -- | -- | -- | | placement | `top`, `bottom`, `left`, `right` | Where to place the tooltip. Default value is `bottom' | | marginTop | _Number_ | Offset above the tooltip with `top` placement | | marginBottom | _Number_ | Offset below the tooltip with `bottom` placement | | marginLeft | _Number_ | Offset at left from the tooltip with `left` placement | | marginRight | _Number_ | Offset at right from the tooltip with `right` placement | | delay | _Number_ | Delay before showing, in ms. Default is `70` | | hidingDelay | _Number_ | Delay before hiding, in ms. Default is `0` | #### Hide Method hides the Tooltip. ```js this.api.tooltip.hide(); ``` #### onHover Decorator for showing tooltip near some element by "mouseenter" and hide by "mouseleave". ```js this.api.tooltip.onHover(element, content, options); ``` ### API Shorthands Editor`s API provides some shorthands for API methods. | Alias | Method | | ------ | --------------- | | `clear` | `blocks.clear` | | `render` | `blocks.render` | | `focus` | `caret.focus` | | `save` | `saver.save` | > Example ```javascript const editor = EditorJS(); editor.focus(); editor.save(); ``` ================================================ FILE: docs/block-tunes.md ================================================ # 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' } } ] } ``` ================================================ FILE: docs/caret.md ================================================ # Editor.js Caret Module The `Caret` module contains methods working with caret. Uses [Range](https://developer.mozilla.org/en-US/docs/Web/API/Range) methods to navigate caret between blocks. Caret class implements basic Module class that holds User configuration and default Editor.js instances ## Properties ## Methods ### setToBlock ```javascript Caret.setToBlock(block, position, offset) ``` > Method gets Block instance and puts caret to the text node with offset #### params | Param | Type | Description| | -------------|------ |:-------------:| | block | Object | Block instance that BlockManager created| | position | String | Can be 'start', 'end' or 'default'. Other values will be treated as 'default'. Shows position of the caret regarding to the Block.| | offset | Number | caret offset regarding to the text node (Default: 0)| ### setToTheLastBlock ```javascript Caret.setToTheLastBlock() ``` > sets Caret at the end of last Block If last block is not empty, inserts another empty Block which is passed as initial ================================================ FILE: docs/installation.md ================================================ # 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) ## Load Editor's core Firstly you need to get Editor.js itself. It is a [minified script](../dist/editor.js) with minimal available Choose the most usable method of getting an Editor for you. - Node package - Source from CDN - Local file from a project ### Node.js Install the package via NPM or Yarn ```shell npm i @editorjs/editorjs ``` Include module at your application ```javascript import EditorJS from '@editorjs/editorjs'; ``` ### Use from CDN You can load specific version of package from [jsDelivr CDN](https://www.jsdelivr.com/package/npm/@editorjs/editorjs). `https://cdn.jsdelivr.net/npm/@editorjs/editorjs@2.10.0` Then require this script. ```html ``` ### Save sources to project Copy [editor.js](../dist/editor.js) file to your project and load it. ```html ``` ## Load Tools Each Block at the Editor.js represented by [Tools](tools.md). There are simple external scripts with their own logic. You'll probably want to use several Block Tools that should be connected. For example, check out our [Header](https://github.com/editor-js/header) Tool that represents heading blocks. You can install the Header Tool via the same ways as an Editor (Node.js, CDN, local file). Check [Editor.js's community](https://github.com/editor-js/) to see Tools examples. **Example:** use Header from CDN ```html ``` ## Create Editor instance Create an instance of Editor.js and pass [Configuration Object](../src/types-internal/editor-config.ts). At least the `holder` option is required. ```html
``` You can create a simple Editor only with a default Paragraph Tool by passing a string with element's Id (wrapper for Editor) as a configuration param or use default `editorjs`. ```javascript var editor = new EditorJS(); /** Zero-configuration */ // equals var editor = new EditorJS('editorjs'); ```` 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: {} }); ``` ## Ready callback Editor.js needs a bit of time to initialize. It is an asynchronous action so it won't block execution of your main script. If you need to know when the editor instance is ready you can use one of the following ways: ##### Pass `onReady` property to the configuration object. It must be a function: ```javascript var editor = new EditorJS({ // Other configuration properties /** * onReady callback */ onReady: () => {console.log('Editor.js is ready to work!')} }); ``` #### Use `isReady` promise. After you create a new `EditorJS` object it will contain `isReady` property. It is a Promise object that resolves when the editor will be ready to work and rejected otherwise. If there is an error during initialization `isReady` promise will be rejected with an error message. ```javascript var editor = new EditorJS(); editor.isReady .then(() => { /** Do anything you need after editor initialization */ }) .catch((reason) => { console.log(`Editor.js initialization failed because of ${reason}`) }); ``` You can use `async/await` to keep your code looking synchronous: ```javascript var editor = new EditorJS(); try { await editor.isReady; /** Do anything you need after editor initialization */ } catch (reason) { console.log(`Editor.js initialization failed because of ${reason}`) } ``` ## Saving Data Call `editor.saver.save()` and handle returned Promise with saved data. ```javascript editor.saver.save() .then((savedData) => { console.log(savedData); }); ``` ## Features Also, Editor.js provides useful methods to work with Editor's state. ```javascript var editor = new EditorJS({ // Other configuration properties /** * onReady callback */ onReady: () => {console.log('Editor.js is ready to work!')}, /** * onChange callback * Accepts CustomEvent describing what happened */ onChange: (editorAPI, event) => {console.log('Now I know that Editor\'s content changed!')} }); ``` ## Example Take a look at the [example.html](../example/example.html) to view more detailed examples. ================================================ FILE: docs/releases.md ================================================ # 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. `..` 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. ================================================ FILE: docs/sanitizer.md ================================================ # Editor.js Sanitizer Module The `Sanitizer` module represents a set of methods that clears taint strings. Uses lightweight npm package with simple API [html-janitor](https://www.npmjs.com/package/html-janitor) ## Methods ### clean ```javascript clean(taintString, customConfig) ``` > Cleans up the passed taint string #### params | Param | Type | Description| | -------------|------ |:-------------:| | taintString | String | string that needs to be cleaned| | customConfig | Object | Can be passed new config per usage (Default: uses default configuration)| ================================================ FILE: docs/toolbar-settings.md ================================================ # Editor.js Toolbar Block Settings Module Toolbar Module has space for Block settings. Settings divided into: - space for plugin's settings, that is described by «Plugin»'s Developer - space for default settings. This option is also can be implemented and expanded They difference between zones is that the first option is specified by plugin and each Block can have different options, when second option is for every Block regardless to the plugin's option. ### Let's look the examples: «Plugin»'s Developers need to expand «renderSettings» method that returns HTML. Every user action will be handled by itself. So, you can easily write callbacks that switches your content or makes better. For more information read [Tools](tools.md). --- «Tune»'s Developers need to implement core-provided interface to develop tunes that will be appeared in Toolbar default settings zone. Tunes must expand two important methods: - `render()` - returns HTML and it is appended to the default settings zone - `save()` - extracts important information to be saved No restrictions. Handle user action by yourself Create Class that implements block-tune.ts Your Tune's constructor gets argument as object and it includes: - {Object} api - object contains public methods from modules. @see [API](api.md) - {Object} settings - settings contains block default state. This object could have information about cover, anchor and so on. Example on TypeScript: ```js import IBlockTune from './block-tune'; export default class YourCustomTune implements IBlockTune { public constructor({api, settings}) { this.api = api; this.settings = settings; } render() { let someHTML = '...'; return someHTML; } save() { // Return the important data that needs to be saved return object } someMethod() { // moves current block down this.api.blocks.moveDown(); } } ``` Example on ES6 ```js export default class YourCustomTune { constructor({api, settings}) { this.api = api; this.settings = settings; } render() { let someHTML = '...'; return someHTML; } save() { // Return the important data that needs to be saved return object } someMethod() { // moves current block down this.api.blocks.moveDown(); } } ``` ================================================ FILE: docs/tools-inline.md ================================================ # Tools for the Inline Toolbar Similar with [Tools](tools.md) represented Blocks, you can create Tools for the Inline Toolbar. It will work with selected fragment of text. The simplest example is `bold` or `italic` Tools. ## Base structure First of all, Tool's class should have a `isInline` property (static getter) set as `true`. After that Inline Tool should implement next methods. - `render()` — create a button - `surround()` — works with selected range - `checkState()` — get Tool's activated state by selected range Also, you can provide optional methods - `renderActions()` — create additional element below the buttons - `clear()` — clear Tool's stuff on opening/closing of Inline Toolbar - `sanitize()` — sanitizer configuration At the constructor of Tool's class exemplar you will accept an object with the [API](api.md) as a parameter. --- ### render() Method that returns button to append at the Inline Toolbar #### Parameters Method does not accept any parameters #### Return value type | description | -- | -- | `HTMLElement` | element that will be added to the Inline Toolbar | --- ### surround(range: Range) Method that accepts selected range and wrap it somehow #### Parameters name | type | description | -- |-- | -- | range | Range | first range of current Selection | #### Return value There is no return value --- ### checkState(selection: Selection) Get Selection and detect if Tool was applied. For example, after that Tool can highlight button or show some details. #### Parameters name | type | description | -- |-- | -- | selection | Selection | current Selection | #### Return value type | description | -- | -- | `Boolean` | `true` if Tool is active, otherwise `false` | --- ### renderActions() Optional method that returns additional Element with actions. For example, input for the 'link' tool or textarea for the 'comment' tool. It will be places below the buttons list at Inline Toolbar. #### Parameters Method does not accept any parameters #### Return value type | description | -- | -- | `HTMLElement` | element that will be added to the Inline Toolbar | --- ### clear() Optional method that will be called on opening/closing of Inline Toolbar. Can contain logic for clearing Tool's stuff, such as inputs, states and other. #### Parameters Method does not accept any parameters #### Return value Method should not return a value. ### static get sanitize() We recommend to specify the Sanitizer config that corresponds with inline tags that is used by your Tool. In that case, your config will be merged with sanitizer configuration of Block Tool that is using the Inline Toolbar with your Tool. Example: If your Tool wrapps selected text with `` tag, the sanitizer config should looks like this: ```js static get sanitize() { return { b: {} // {} means clean all attributes. true — leave all attributes } } ``` Read more about Sanitizer configuration at the [Tools#sanitize](tools.md#sanitize) ### Specifying a title 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. ```ts export default class BoldInlineTool implements InlineTool { /** * Specifies Tool as Inline Toolbar Tool * * @return {boolean} */ public static isInline = true; /** * Title for hover-tooltip */ public static title: string = 'Bold'; // ... other methods } ``` ================================================ FILE: docs/tools.md ================================================ # Editor.js Tools Editor.js is a block-oriented editor. It means that entry composed with the list of `Blocks` of different types: `Texts`, `Headers`, `Images`, `Quotes` etc. `Tool` — is a class that provide custom `Block` type. All Tools represented by `Plugins`. Each Tool should have an installation guide. ## Tool class structure ### constructor() Each Tool's instance called with an params object. | Param | Type | Description | | ------ | ------------------------------------------------------ | ----------------------------------------------- | | api | [`IAPI`](../types/index.d.ts) | Editor.js's API methods | | config | [`ToolConfig`](../types/tools/tool-config.d.ts) | Special configuration params passed in «config» | | data | [`BlockToolData`](../types/tools/block-tool-data.d.ts) | Data to be rendered in this Tool | | block | [`BlockAPI`](../types/api/block.d.ts) | Block's API methods | [iapi-link]: ../src/types-internal/api.ts #### Example ```javascript constructor({data, config, api}) { this.data = data; this.api = api; this.config = config; // ... } ``` ### render() Method that returns Tool's element {HTMLElement} that will be placed into Editor. ### save() Process Tool's element created by `render()` function in DOM and return Block's data. ### validate(data: BlockToolData): boolean|Promise\ _optional_ Allows to check correctness of Tool's data. If data didn't pass the validation it won't be saved. Receives Tool's `data` as input param and returns `boolean` result of validation. ### merge() _optional_ Method that specifies how to merge two `Blocks` of the same type, for example on `Backspace` keypress. Method does accept data object in same format as the `Render` and it should provide logic how to combine new data with the currently stored value. ## Internal Tool Settings Options that Tool can specify. All settings should be passed as static properties of Tool's class. | Name | Type | Default Value | Description | | -- | -- | -- | -- | | `toolbox` | _Object_ | `undefined` | Pass the `icon` and the `title` there to display this `Tool` in the Editor's `Toolbox`
`icon` - HTML string with icon for the Toolbox
`title` - title to be displayed at the Toolbox.

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 `` 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. | ## User configuration All Tools can be configured by users. You can set up some of available settings along with Tool's class to the `tools` property of Editor Config. ```javascript var editor = new EditorJS({ holder : 'editorjs', tools: { text: { class: Text, inlineToolbar : true, // other settings.. }, header: Header }, defaultBlock : 'text', }); ``` There are few options available by Editor.js. | Name | Type | Default Value | Description | | -- | -- | -- | -- | | `inlineToolbar` | _Boolean/Array_ | `false` | Pass `true` to enable the Inline Toolbar with all Tools, or pass an array with specified Tools list | | `config` | _Object_ | `null` | User's configuration for Plugin. ## Tool prepare and reset If you need to prepare some data for Tool (eg. load external script, create HTML nodes in the document, etc) you can use static prepare method. It accepts tools config passed on Editor's initialization as an argument: ```javascript class Tool { static prepare(config) { loadScript(); insertNodes(); ... } } ``` On Editor destroy you can use an opposite method `reset` to clean up all prepared data: ```javascript class Tool { static reset() { cleanUpScripts(); deleteNodes(); ... } } ``` Both methods might be async. ## Paste handling Editor.js handles paste on Blocks and provides API for Tools to process the pasted data. When user pastes content into Editor, pasted content will be splitted into blocks. 1. If plain text will be pasted, it will be splitted by new line characters 2. If HTML string will be pasted, it will be splitted by block tags Also Editor API allows you to define your own pasting scenario. You can either: 1. Specify **HTML tags**, that can be represented by your Tool. For example, Image Tool can handle `` tags. If tags you specified will be found on content pasting, your Tool will be rendered. 2. Specify **RegExp** for pasted strings. If pattern has been matched, your Tool will be rendered. 3. Specify **MIME type** or **extensions** of files that can be handled by your Tool on pasting by drag-n-drop or from clipboard. For each scenario, you should do 2 next things: 1. Define static getter `pasteConfig` in Tool class. Specify handled patterns there. 2. Define public method `onPaste` that will handle PasteEvent to process pasted data. ### HTML tags handling To handle pasted HTML elements object returned from `pasteConfig` getter should contain following field: | Name | Type | Description | | -- | -- | -- | | `tags` | `String[]` | _Optional_. Should contain all tag names you want to be extracted from pasted data and processed by your `onPaste` method | For correct work you MUST provide `onPaste` handler at least for `defaultBlock` Tool. #### Example Header Tool can handle `H1`-`H6` tags using paste handling API ```javascript static get pasteConfig() { return { tags: ['H1', 'H2', 'H3', 'H4', 'H5', 'H6'], } } ``` **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 Your Tool can analyze text by RegExp patterns to substitute pasted string with data you want. Object returned from `pasteConfig` getter should contain following field to use patterns: | Name | Type | Description | | -- | -- | -- | | `patterns` | `Object` | _Optional_. `patterns` object contains RegExp patterns with their names as object's keys | **Note** Editor will check pattern's full match, so don't forget to handle all available chars in there. Pattern will be processed only if paste was on `defaultBlock` Tool and pasted string length is less than 450 characters. > Example You can handle YouTube links and insert embeded video instead: ```javascript static get pasteConfig() { return { patterns: { youtube: /http(?:s?):\/\/(?:www\.)?youtu(?:be\.com\/watch\?v=|\.be\/)([\w\-\_]*)(&(amp;)?[\w\?‌​=]*)?/ }, } } ``` ### Files pasting Your Tool can handle files pasted or dropped into the Editor. To handle file you should provide `files` property in your `pasteConfig` configuration object. `files` property is an object with the following fields: | Name | Type | Description | | ---- | ---- | ----------- | | `extensions` | `string[]` | _Optional_ Array of extensions your Tool can handle | | `mimeTypes` | `sring[]` | _Optional_ Array of MIME types your Tool can handle | Example ```javascript static get pasteConfig() { return { files: { mimeTypes: ['image/png'], extensions: ['json'] } } } ``` ### Pasted data handling If you registered some paste substitutions in `pasteConfig` property, you **should** provide `onPaste` callback in your Tool class. `onPaste` should be public non-static method. It accepts custom _PasteEvent_ object as argument. PasteEvent is an alias for three types of events - `tag`, `pattern` and `file`. You can get the type from _PasteEvent_ object's `type` property. Each of these events provide `detail` property with info about pasted content. | Type | Detail | | ----- | ------ | | `tag` | `data` - pasted HTML element | | `pattern` | `key` - matched pattern key you specified in `pasteConfig` object
`data` - pasted string | | `file` | `file` - pasted file | Example ```javascript onPaste (event) { switch (event.type) { case 'tag': const element = event.detail.data; this.handleHTMLPaste(element); break; case 'pattern': const text = event.detail.data; const key = event.detail.key; this.handlePatternPaste(key, text); break; case 'file': const file = event.detail.file; this.handleFilePaste(file); break; } } ``` ### Disable paste handling If you need to disable paste handling on your Tool for some reason, you can provide `false` as `pasteConfig` value. That way paste event won't be processed if fired on your Tool: ```javascript static get pasteConfig { return false; } ``` ## Sanitize
Editor.js provides [API](sanitizer.md) to clean taint strings. Use it manually at the `save()` method or or pass `sanitizer` config to do it automatically. ### Sanitizer Configuration The example of sanitizer configuration ```javascript let sanitizerConfig = { b: true, // leave p: true, // leave

} ``` Keys of config object is tags and the values is a rules. #### Rule Rule can be boolean, object or function. Object is a dictionary of rules for tag's attributes. You can set `true`, to allow tag with all attributes or `false|{}` to remove all attributes, but leave tag. Also you can pass special attributes that you want to leave. ```javascript a: { href: true } ``` If you want to use a custom handler, use should specify a function that returns a rule. ```javascript b: function(el) { return !el.textContent.includes('bad text'); } ``` or ```javascript a: function(el) { let anchorHref = el.getAttribute('href'); if (anchorHref && anchorHref.substring(0, 4) === 'http') { return { href: true, target: '_blank' } } else { return { href: true } } } ``` ### Manual sanitize Call API method `sanitizer.clean()` at the save method for each field in returned data. ```javascript save() { return { text: this.api.sanitizer.clean(taintString, sanitizerConfig) } } ``` ### Automatic sanitize If you pass the sanitizer config as static getter, Editor.js will automatically sanitize your saved data. Note that if your Tool is allowed to use the Inline Toolbar, we will get sanitizing rules for each Inline Tool and merge with your passed config. You can define rules for each field ```javascript static get sanitize() { return { text: {}, items: { b: true, // leave a: false, // remove } } } ``` Don't forget to set the rule for each embedded subitems otherwise they will not be sanitized. if you want to sanitize everything and get data without any tags, use `{}` or just ignore field in case if you want to get pure HTML ```javascript static get sanitize() { return { text: {}, items: {}, // this rules will be used for all properties of this object // or items: { // other objects here won't be sanitized subitems: { // leave and in subitems a: true, b: true, } } } } ``` ## Conversion config Editor.js has a Conversion Toolbar that allows user to convert one Block to another. ![](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`. Conversion Toolbar will be shown only near Blocks that specified an «export» rule, when user selected almost all block's content. This Toolbar will contain only Tools that specified an «import» rule. Example: ```js class Header { constructor(){ this.data = { text: '', level: 2 } } /** * Rules specified how our Tool can be converted to/from other Tool. */ static get conversionConfig() { return { export: 'text', // this property of tool data will be used as string to pass to other tool import: 'text' // to this property imported string will be passed }; } } ``` ### Your Tool -> other Tool The «export» field specifies how to represent your Tool's data as a string to pass it to other tool. It can be a `String` or a `Function`. `String` means a key of your Tool data object that should be used as string to export. `Function` is a method that accepts your Tool data and compose a string to export from it. See example below: ```js class ListTool { constructor(){ this.data = { items: [ 'Fisrt item', 'Second item', 'Third item' ], type: 'ordered' } } static get conversionConfig() { return { export: (data) => { return data.items.join('.'); // in this example, all list items will be concatenated to an export string }, // ... import rule }; } } ``` ### Other Tool -> your Tool The «import» rule specifies how to create your Tool's data object from the string created by original block. It can be a `String` or a `Function`. `String` means the key in tool data that will be filled by an exported string. For example, `import: 'text'` means that `constructor` of your block will accept a `data` object with `text` property filled with string composed by original block. `Function` allows you to specify own logic, how a string should be converted to your tool data. For example: ```js class ListTool { constructor(data){ this.data = data || { items: [], type: 'unordered' } } static get conversionConfig() { return { // ... export rule /** * In this example, List Tool creates items by splitting original text by a dot symbol. */ import: (string) => { const items = string.split('.'); return { items: items.filter( (text) => text.trim() !== ''), type: 'unordered' }; } }; } } ``` ## Block Lifecycle hooks ### `rendered()` Called after Block contents is added to the page ### `updated()` Called each time Block contents is updated ### `removed()` Called after Block contents is removed from the page but before Block instance deleted ### `moved(MoveEvent)` Called after Block was moved. `MoveEvent` contains `fromIndex` and `toIndex` respectively. ================================================ FILE: docs/usage.md ================================================ # So how to use Editor.js ## Basics Editor.js is a Block-Styled editor. Blocks is a structural units, of which the Entry is composed. For example, `Paragraph`, `Heading`, `Image`, `Video`, `List` are Blocks. Each Block is represented by a Plugin. We have [many](http://github.com/editor-js/) ready-to-use Plugins and the [simple API](tools.md) for creation new ones. So how to use the Editor after [Installation](installation.md). - Create new Blocks by Enter or with 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 text fragment and apply a style or insert a link from the Inline Toolbar ![](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. ![](assets/01a55381-46cd-47c7-b92e-34765434f2ca.jpg) ## Shortcuts We really appreciate shortcuts. So there are few presets. Action | Shortcut | 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 Also we support shortcuts on the all type of Tools. Specify a shortcut with the Tools configuration. For example: ```js var editor = new EditorJS({ //... tools: { header: { class: Header, shortcut: 'CMD+SHIFT+H' }, list: { class: List, shortcut: 'CMD+SHIFT+L' } } //... }); ``` ## Autofocus If you want to focus Editor after page has been loaded, you can enable autofocus by passing `autofocus` to the initial config ```js var editor = new EditorJS({ //... autofocus: true //... }); ``` ## Holder The `holder` property supports an id or a reference to dom element. ```js var editor = new EditorJS({ holder: document.querySelector('.editor'), }) var editor2 = new EditorJS({ holder: 'codex-editor' // like document.getElementById('codex-editor') }) ``` ## Placeholder By default Editor\`s placeholder is empty. You can pass your own placeholder via `placeholder` field: ```js var editor = new EditorJS({ //... placeholder: 'My awesome placeholder' //... }); ``` If you are using your custom `Initial Block`, `placeholder` property is passed in `config` to your Tool constructor. ## Log level You can specify log level for Editor.js console messages via `logLevel` property of configuration: ```js var editor = new EditorJS({ //... logLevel: 'WARN' //.. }) ``` Possible values: | Value | Description | | ----- | ---------------------------- | | `VERBOSE` | Show all messages | | `INFO` | Show info and debug messages | | `WARN` | Show errors and warns only | | `ERROR` | Show errors only | ================================================ FILE: example/example-i18n.html ================================================ Editor.js 🤩🧦🤨 example

No core bundle file found. Run yarn build
editor.save()
================================================ FILE: example/example-multiple.html ================================================ Editor.js 🤩🧦🤨 example: Multiple instances
No core bundle file found. Run yarn build
================================================ FILE: example/example-popup.html ================================================ Editor.js 🤩🧦🤨 example: Popup
No core bundle file found. Run yarn build

Base concepts

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.

So there are a few key features:

  • Clean data output
  • API pluggable
  • Open source

What does it mean block-styled

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.

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.

At the same time, most useful features as arrow-navigation, copy & paste, cross block selection, and others works almost as in the familiar editors.

What is clean data

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.

If our entry consists of few paragraphs and a heading, in popular Medium editor after saving we will have something like this:

As you can see, there are only data we need: a list of structural Blocks with their content description.

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.

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.

API pluggable?

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.

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.

At the same time, API is created to be easy-to-understand and simple-to-use.

Open Source, so?

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.

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.

Thanks for your interest. Hope you enjoy Editor.js.

================================================ FILE: example/example-rtl.html ================================================ Editor.js RTL example
No core bundle file found. Run yarn build
No submodules found. Run yarn pull_tools && yarn tools:update
editor.save()
================================================ FILE: example/example.html ================================================ Editor.js 🤩🧦🤨 example
editor.save()
Readonly: Off
toggle
================================================ FILE: index.html ================================================ Editor.js 🤩🧦🤨 example
editor.save()
Readonly: Off  
toggle
Show blocks boundaries
Enable thin mode
================================================ FILE: package.json ================================================ { "name": "@editorjs/editorjs", "version": "2.31.5", "description": "Editor.js — open source block-style WYSIWYG editor with JSON output", "main": "dist/editorjs.umd.js", "module": "dist/editorjs.mjs", "types": "./types/index.d.ts", "keywords": [ "codex editor", "text editor", "editor", "editor.js", "editorjs", "wysiwyg" ], "scripts": { "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", "test:e2e": "yarn build:test && cypress run", "test:e2e:open": "yarn build:test && cypress open" }, "author": "CodeX", "license": "Apache-2.0", "repository": { "type": "git", "url": "git+https://github.com/codex-team/editor.js.git" }, "devDependencies": { "@babel/register": "^7.21.0", "@codexteam/icons": "0.3.2", "@codexteam/shortcuts": "^1.1.1", "@cypress/code-coverage": "^3.10.3", "@editorjs/code": "^2.7.0", "@editorjs/delimiter": "^1.2.0", "@editorjs/header": "^2.8.8", "@editorjs/paragraph": "^2.11.6", "@editorjs/simple-image": "^1.4.1", "@types/node": "^18.15.11", "chai-subset": "^1.6.0", "core-js": "3.30.0", "cypress": "^13.13.3", "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", "nanoid": "^4.0.2", "postcss-apply": "^0.12.0", "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": "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": { "@editorjs/caret": "^1.0.1", "codex-notifier": "^1.1.2", "codex-tooltip": "^1.0.5" } } ================================================ FILE: public/assets/demo.css ================================================ /** * 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; } ================================================ FILE: public/assets/json-preview.js ================================================ /** * Module to compose output JSON preview */ const cPreview = (function (module) { /** * Shows JSON in pretty preview * @param {object} output - what to show * @param {Element} holder - where to show */ module.show = function(output, holder) { /** Make JSON pretty */ output = JSON.stringify( output, null, 4 ); /** Encode HTML entities */ output = encodeHTMLEntities( output ); /** Stylize! */ output = stylize( output ); holder.innerHTML = output; }; /** * Converts '>', '<', '&' symbols to entities */ function encodeHTMLEntities(string) { return string.replace(/&/g, '&').replace(//g, '>'); } /** * Some styling magic */ function stylize(string) { /** Stylize JSON keys */ string = string.replace( /"(\w+)"\s?:/g, '"$1" :'); /** Stylize tool names */ string = string.replace( /"(paragraph|quote|list|header|link|code|image|delimiter|raw|checklist|table|embed|warning)"/g, '"$1"'); /** Stylize HTML tags */ string = string.replace( /(<[\/a-z]+(>)?)/gi, '$1' ); /** Stylize strings */ string = string.replace( /"([^"]+)"/gi, '"$1"' ); /** Boolean/Null */ string = string.replace( /\b(true|false|null)\b/gi, '$1' ); return string; } return module; })({}); ================================================ FILE: src/codex.ts ================================================ 'use strict'; import type { EditorConfig } from '../types'; /** * Apply polyfills */ import '@babel/register'; 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 * * @license Apache-2.0 * @see Editor.js * @author CodeX Team */ export default class EditorJS { /** * Promise that resolves when core modules are ready and UI is rendered on the page */ public isReady: Promise; /** * Stores destroy method implementation. * Clear heap occupied by Editor and remove UI components from the DOM. */ public destroy: () => void; /** Editor version */ public static get version(): string { return VERSION; } /** * @param {EditorConfig|string|undefined} [configuration] - user configuration */ constructor(configuration?: EditorConfig|string) { /** * Set default onReady function */ // eslint-disable-next-line @typescript-eslint/no-empty-function let onReady = (): void => {}; /** * If `onReady` was passed in `configuration` then redefine onReady function */ if (_.isObject(configuration) && _.isFunction(configuration.onReady)) { onReady = configuration.onReady; } /** * Create a Editor.js instance */ const editor = new Core(configuration); /** * We need to export isReady promise in the constructor * as it can be used before other API methods are exported * * @type {Promise} */ 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(); }); } /** * Export external API methods * * @param {Core} editor — Editor's instance */ public exportAPI(editor: Core): void { const fieldsToExport = [ 'configuration' ]; const destroy = (): void => { Object.values(editor.moduleInstances) .forEach((moduleInstance) => { if (_.isFunction(moduleInstance.destroy)) { moduleInstance.destroy(); } moduleInstance.listeners.removeAll(); }); destroyTooltip(); editor = null; for (const field in this) { if (Object.prototype.hasOwnProperty.call(this, field)) { delete this[field]; } } Object.setPrototypeOf(this, null); }; fieldsToExport.forEach((field) => { this[field] = editor[field]; }); this.destroy = destroy; Object.setPrototypeOf(this, editor.moduleInstances.API.methods); delete this.exportAPI; const shorthands = { blocks: { clear: 'clear', render: 'render', }, caret: { focus: 'focus', }, events: { on: 'on', off: 'off', emit: 'emit', }, saver: { save: 'save', }, }; Object.entries(shorthands) .forEach(([key, methods]) => { Object.entries(methods) .forEach(([name, alias]) => { this[alias] = editor.moduleInstances.API.methods[key][name]; }); }); } } ================================================ FILE: src/components/__module.ts ================================================ import type { EditorModules } from '../types-internal/editor-modules'; import type { EditorConfig } from '../../types'; import type { ModuleConfig } from '../types-internal/module-config'; import Listeners from './utils/listeners'; import type EventsDispatcher from './utils/events'; import type { EditorEventMap } from './events'; /** * The type of the Module generic. * It describes the structure of nodes used in modules. */ 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> { /** * Each module can provide some UI elements that will be stored in this property */ // eslint-disable-next-line @typescript-eslint/no-explicit-any public nodes: T = {} as any; /** * Editor modules list * * @type {EditorModules} */ protected Editor: EditorModules; /** * Editor configuration object * * @type {EditorConfig} */ protected config: EditorConfig; /** * Editor event dispatcher class */ protected eventsDispatcher: EventsDispatcher; /** * Util for bind/unbind DOM event listeners */ protected listeners: Listeners = new Listeners(); /** * This object provides methods to push into set of listeners that being dropped when read-only mode is enabled */ protected readOnlyMutableListeners = { /** * Assigns event listener on DOM element and pushes into special array that might be removed * * @param {EventTarget} element - DOM Element * @param {string} eventType - Event name * @param {Function} handler - Event handler * @param {boolean|AddEventListenerOptions} options - Listening options */ on: ( element: EventTarget, eventType: string, handler: (event: Event) => void, options: boolean | AddEventListenerOptions = false ): void => { this.mutableListenerIds.push( this.listeners.on(element, eventType, handler, options) ); }, /** * Clears all mutable listeners */ clearAll: (): void => { for (const id of this.mutableListenerIds) { this.listeners.offById(id); } this.mutableListenerIds = []; }, }; /** * The set of listener identifiers which will be dropped in read-only mode */ private mutableListenerIds: string[] = []; /** * @class * @param options - Module options * @param options.config - Module config * @param options.eventsDispatcher - Common event bus */ 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; } /** * Editor modules setter * * @param {EditorModules} Editor - Editor's Modules */ public set state(Editor: EditorModules) { this.Editor = Editor; } /** * Remove memorized nodes */ public removeAllNodes(): void { for (const key in this.nodes) { const node = this.nodes[key]; if (node instanceof HTMLElement) { node.remove(); } } } /** * Returns true if current direction is RTL (Right-To-Left) */ protected get isRtl(): boolean { return this.config.i18n.direction === 'rtl'; } } ================================================ FILE: src/components/block/api.ts ================================================ import type Block from './index'; import type { BlockToolData, ToolConfig, ToolboxConfigEntry } from '../../../types/tools'; import type { SavedData } from '../../../types/data-formats'; import type { 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 * * @returns {string} */ get name(): string { return block.name; }, /** * Tool config passed on Editor's initialization * * @returns {ToolConfig} */ get config(): ToolConfig { return block.config; }, /** * .ce-block element, that wraps plugin contents * * @returns {HTMLElement} */ get holder(): HTMLElement { return block.holder; }, /** * True if Block content is empty * * @returns {boolean} */ get isEmpty(): boolean { return block.isEmpty; }, /** * True if Block is selected with Cross-Block selection * * @returns {boolean} */ get selected(): boolean { return block.selected; }, /** * Set Block's stretch state * * @param {boolean} state — state to set */ set stretched(state: boolean) { block.stretched = state; }, /** * True if Block is stretched * * @returns {boolean} */ get stretched(): boolean { 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 { return block.call(methodName, param); }, /** * Save Block content * * @returns {Promise} */ save(): Promise { return block.save(); }, /** * Validate Block data * * @param {BlockToolData} data - data to validate * @returns {Promise} */ validate(data: BlockToolData): Promise { 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(); }, /** * 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) */ getActiveToolboxEntry(): Promise { return block.getActiveToolboxEntry(); }, }; Object.setPrototypeOf(this, blockAPI); } export default BlockAPI; ================================================ FILE: src/components/block/index.ts ================================================ import type { BlockAPI as BlockAPIInterface, BlockTool as IBlockTool, BlockToolData, BlockTune as IBlockTune, SanitizerConfig, ToolConfig, ToolboxConfigEntry, PopoverItemParams } from '../../../types'; import type { SavedData } from '../../../types/data-formats'; import $, { toggleEmptyMark } from '../dom'; import * as _ from '../utils'; import type ApiModules from '../modules/api'; import BlockAPI from './api'; import SelectionUtils from '../selection'; import type BlockToolAdapter from '../tools/block'; import type BlockTuneAdapter from '../tools/tune'; import type { BlockTuneData } from '../../../types/block-tunes/block-tune-data'; import type ToolsCollection from '../tools/collection'; import EventsDispatcher from '../utils/events'; import type { TunesMenuConfigItem } from '../../../types/tools'; import { isMutationBelongsToElement } from '../utils/mutations'; import type { EditorEventMap } from '../events'; import { FakeCursorAboutToBeToggled, FakeCursorHaveBeenSet, RedactorDomChanged } from '../events'; import type { RedactorDomChangedPayload } from '../events/RedactorDomChanged'; import { convertBlockDataToString, isSameBlockData } from '../utils/blocks'; import { PopoverItemType } from '@/types/utils/popover/popover-item-type'; /** * Interface describes Block class constructor argument */ interface BlockConstructorOptions { /** * Block's id. Should be passed for existed block, and omitted for a new one. */ id?: string; /** * Initial Block data */ data: BlockToolData; /** * Tool object */ tool: BlockToolAdapter; /** * Editor's API methods */ api: ApiModules; /** * 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 */ /** * Available Block Tool API methods */ 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 extends EventsDispatcher { /** * CSS classes for the Block * * @returns {{wrapper: string, content: string}} */ public static get CSS(): { [name: string]: string } { return { wrapper: 'ce-block', wrapperStretched: 'ce-block--stretched', content: 'ce-block__content', selected: 'ce-block--selected', dropTarget: 'ce-block--drop-target', }; } /** * Block unique identifier */ public id: string; /** * Block Tool`s name */ public readonly name: string; /** * Instance of the Tool Block represents */ public readonly tool: BlockToolAdapter; /** * User Tool configuration */ public readonly settings: ToolConfig; /** * Wrapper for Block`s content */ public readonly holder: HTMLDivElement; /** * Tunes used by Tool */ public readonly tunes: ToolsCollection; /** * Tool's user configuration */ public readonly config: ToolConfig; /** * Cached inputs */ 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 = new Map(); /** * Editor provided Block Tunes instances */ private readonly defaultTunesInstances: Map = 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 } = {}; /** * Focused input index * * @type {number} */ private inputIndex = 0; /** * Common editor event bus */ private readonly editorEventBus: EventsDispatcher | null = null; /** * Link to editor dom change callback. Used to remove listener on remove */ private redactorDomChangedCallback: (payload: RedactorDomChangedPayload) => void; /** * Current block API interface */ private readonly blockAPI: BlockAPIInterface; /** * @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 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({ id = _.generateBlockId(), data, tool, readOnly, tunesData, }: BlockConstructorOptions, eventBus?: EventsDispatcher) { super(); this.name = tool.name; this.id = id; this.settings = tool.settings; this.config = tool.settings.config || {}; this.editorEventBus = eventBus || null; this.blockAPI = new BlockAPI(this); this.tool = tool; this.toolInstance = tool.create(data, this.blockAPI, readOnly); /** * @type {BlockTuneAdapter[]} */ 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 "" and "