Compare commits

...

134 commits

Author SHA1 Message Date
Peter Savchenko 29d68ecb47
fix(block-events): caret losing after backspace after nested list (#2723)
* feat: Fix caret loss after Backspace at the start of block when previous block is not convertible

* fix create shadow caret

* fix: remove unnecessary blank line in blockEvents.ts

* fix: pass event object to slashPressed method in blockEvents.ts

* fix eslint
2024-05-23 20:06:33 +03:00
Tatiana Fomina d18eeb5dc8
feat(popover): Add hint support (#2711)
* Add custom item

* Remove customcontent parameter from popover

* Tests

* Cleanup

* Cleanup

* Lint

* Cleanup

* Rename custom to html, add enum with item types

* Fix tests

* Support hint

* Rename hint content to hint

* Align hint left

* Move types and exports

* Update changelog

* Cleanup

* Add todos

* Change the way hint is disabled for mobile

* Get rid of buildItems override

* Update comment
2024-05-16 15:26:25 +03:00
Tatiana Fomina 50f43bb35d
Change cypress preprocessor (#2712) 2024-05-04 21:23:36 +03:00
Tatiana Fomina f78972ee09
feat(popover): custom content becomes a popover item (#2707)
* Add custom item

* Remove customcontent parameter from popover

* Tests

* Cleanup

* Cleanup

* Lint

* Cleanup

* Rename custom to html, add enum with item types

* Fix tests

* Add order test

* Update jsdoc

* Update changelog

* Fix issue with html item not hiding on search

* Fix flipper issue

* Update changelog
2024-05-04 15:35:36 +00:00
github-actions[bot] bd1de56ef3
Bump version (#2705)
Co-authored-by: github-actions <action@github.com>
2024-05-01 21:00:22 +03:00
Peter Savchenko 8276daa5ca
fix changelog (#2704) 2024-05-01 20:59:33 +03:00
github-actions[bot] 238c909016
Bump version (#2701)
Co-authored-by: github-actions <action@github.com>
2024-04-29 22:28:45 +03:00
Peter Savchenko 23858e0025
fix(conversion): restore caret after conversion though the Inline Toolbar and API (#2699)
* fix caret loosing after caret

* Refactor convert method to return Promise in Blocks API

* changelog upd

* Fix missing semicolon in blocks.cy.ts and BlockTunes.cy.ts

* add test for inline toolbar conversion

* Fix missing semicolon in InlineToolbar.cy.ts

* add test for toolbox shortcut

* api caret.setToBlock now can accept block api or index or id

* eslint fix

* Refactor test descriptions in caret.cy.ts

* rm tsconfig change

* lint

* lint

* Update CHANGELOG.md
2024-04-29 22:24:31 +03:00
github-actions[bot] 5eafda5ec4
Bump version (#2698)
Co-authored-by: github-actions <action@github.com>
2024-04-27 21:22:12 +03:00
Peter Savchenko efa0a34f8e
fix caret loosing after caret (#2697) 2024-04-27 21:19:12 +03:00
Peter Savchenko c48fca1be3
fix ios shift (#2696) 2024-04-27 21:09:16 +03:00
Peter Savchenko 1028577521
fix(scroll): acidental scroll to top on iOS devices (#2695)
* fix scroll on ios typing

* Update tsconfig.json

* Update CHANGELOG.md

* Update CHANGELOG.md

* Update package.json

* Fix popover hide method to use isHidden flag
2024-04-27 21:04:26 +03:00
github-actions[bot] 844272656e
Bump version (#2694)
Co-authored-by: github-actions <action@github.com>
2024-04-27 16:59:52 +03:00
Tatiana Fomina 7821e35302
feat(block tunes): Conversion Menu in Block Tunes (#2692)
* Support delimiter

* Rename types, move types to popover-item folder

* Fix ts errors

* Add tests

* Review fixes

* Review fixes 2

* Fix delimiter while search

* Fix flipper issue

* Fix block tunes types

* Fix types

* tmp

* Fixes

* Make search input emit event

* Fix types

* Rename delimiter to separator

* Update chengelog

* Add convert to to block tunes

* i18n

* Lint

* Fix tests

* Fix tests 2

* Tests

* Add caching

* Rename

* Fix for miltiple toolbox entries

* Update changelog

* Update changelog

* Fix popover test

* Fix flipper tests

* Fix popover tests

* Remove type: 'default'

* Create isSameBlockData util

* Add testcase
2024-04-27 16:57:52 +03:00
github-actions[bot] 4118dc3aea
Bump version (#2693)
Co-authored-by: github-actions <action@github.com>
2024-04-23 22:52:31 +03:00
Tatiana Fomina e1c70b4fb8
feat(popover): separator (#2690)
* Support delimiter

* Rename types, move types to popover-item folder

* Fix ts errors

* Add tests

* Review fixes

* Review fixes 2

* Fix delimiter while search

* Fix flipper issue

* Fix block tunes types

* Fix types

* Fixes

* Make search input emit event

* Fix types

* Rename delimiter to separator

* Update chengelog
2024-04-22 22:38:20 +03:00
github-actions[bot] 54c4c234a5
Bump version (#2659)
Co-authored-by: github-actions <action@github.com>
2024-04-13 23:07:20 +03:00
Tatiana Fomina 5125f015dc
feat: nested popover (#2649)
* Move popover types to separate file

* tmp

* open top

* Fix bug with keyboard navigation

* Fix bug with scroll

* Fix mobile

* Add popover header class

* Display nested items on mobile

* Refactor history

* Fix positioning on desktop

* Fix tests

* Fix child popover indent left

* Fix ts errors in popover files

* Move files

* Rename cn to bem

* Clarify comments and rename method

* Refactor popover css classes

* Rename cls to css

* Split popover desktop and mobile classes

* Add ability to open popover to the left if not enough space to open to the right

* Add nested popover test

* Add popover test for mobile screens

* Fix tests

* Add union type for both popovers

* Add global window resize event

* Multiple fixes

* Move nodes initialization to constructor

* Rename handleShowingNestedItems to showNestedItems

* Replace WindowResize with EditorMobileLayoutToggled

* New doze of fixes

* Review fixes

* Fixes

* Fixes

* Make each nested popover decide itself if it should open top

* Update changelog

* Update changelog

* Update changelog
2024-04-13 17:34:26 +00:00
Peter Savchenko ecdd73347c
fix(dx): dev example page fixed (#2682)
* fix dev example

* embed goes to master
2024-04-11 17:00:48 +03:00
Peter Savchenko 1320b047a2
feat(merge): blocks of different types can be merged (#2671)
* feature: possibilities to merge blocks of different types

* fix: remove scope change

* feat: use convert config instead of defined property

* chore:: use built-in function for type check

* fix: remove console.log

* chore: remove styling added by mistakes

* test: add testing for different blocks types merging

* fix: remove unused import

* fix: remove type argument

* fix: use existing functions for data export

* chore: update changelog

* fix: re put await

* fix: remove unnecessary check

* fix: typo in test name

* fix: re-add condition for merge

* test: add caret position test

* fix caret issues, add sanitize

* make if-else statement more clear

* upgrade cypress

* Update cypress.yml

* upd cypress to 13

* make sanitize test simpler

* patch rc version

---------

Co-authored-by: GuillaumeOnepilot <guillaume@onepilot.co>
Co-authored-by: Guillaume Leon <97881811+GuillaumeOnepilot@users.noreply.github.com>
2024-04-01 12:29:47 +03:00
Alex Yang b355f1673c
fix: strict css type (#2573)
Co-authored-by: Peter Savchenko <specc.dev@gmail.com>
Co-authored-by: Ilya Maroz <37909603+ilyamore88@users.noreply.github.com>
2024-03-13 17:57:52 +03:00
dependabot[bot] bb2047c60f
build(deps): bump word-wrap from 1.2.3 to 1.2.5 (#2433)
Bumps [word-wrap](https://github.com/jonschlinkert/word-wrap) from 1.2.3 to 1.2.5.
- [Release notes](https://github.com/jonschlinkert/word-wrap/releases)
- [Commits](https://github.com/jonschlinkert/word-wrap/compare/1.2.3...1.2.5)

---
updated-dependencies:
- dependency-name: word-wrap
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Peter Savchenko <specc.dev@gmail.com>
2024-03-13 17:53:00 +03:00
dependabot[bot] cc0d6de04b
build(deps): bump semver from 5.7.1 to 5.7.2 (#2411)
Bumps [semver](https://github.com/npm/node-semver) from 5.7.1 to 5.7.2.
- [Release notes](https://github.com/npm/node-semver/releases)
- [Changelog](https://github.com/npm/node-semver/blob/v5.7.2/CHANGELOG.md)
- [Commits](https://github.com/npm/node-semver/compare/v5.7.1...v5.7.2)

---
updated-dependencies:
- dependency-name: semver
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Peter Savchenko <specc.dev@gmail.com>
2024-03-13 17:47:18 +03:00
dependabot[bot] 9b3e9615b0
build(deps-dev): bump vite from 4.2.1 to 4.5.2 (#2651)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 4.2.1 to 4.5.2.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v4.5.2/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v4.5.2/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Peter Savchenko <specc.dev@gmail.com>
2024-03-13 17:36:26 +03:00
Peter Savchenko ee6433201d
fix(block-tunes): enter keydown problems (#2650)
* debug enter press

* fix sync set caret

* fix enter keydown problems + tests addedd

* Update search-input.ts

* add changelog

* add useful log to cypress custom comand

* Update commands.ts
2024-03-13 17:30:16 +03:00
github-actions[bot] e9b4c30407
Bump version up to 2.30.0-rc.0 (#2640)
* Bump version

* Update package.json

---------

Co-authored-by: github-actions <action@github.com>
Co-authored-by: Peter Savchenko <specc.dev@gmail.com>
2024-02-28 20:17:42 +03:00
VikhorKonstantin 8138ce95b2
fix issue #2523 (#2639)
* fix isMutationBelongsToElement function: make it return true if the whole text node is deleted inside of some descendant of the passed element

* isMutationBelongsToElement function shouldn't return true if some of the ancestors of the passed element were added or deleted, only if the element itself

* add test case verifying that 'onChange' is fired when the whole text inside some nested  descendant of the block is removed

* replace introduced dependency with ToolMock

* add comment explaining isMutationBelongsToElement behaviour in case of adding/removing the passed element itself

* fix formatting

* added some more explanation

* added record to the changelog

---------

Co-authored-by: Peter Savchenko <specc.dev@gmail.com>
2024-02-28 20:08:08 +03:00
Yaroslav Gusev 7ff5faa46f
Change workflows trigger (#2595) 2024-02-28 15:33:50 +03:00
Peter Savchenko b619946e8f
fix(slash): do not handle / + shift/alt, support for ascii keyboard (#2599)
* fix(slash): do not handle / + shift/alt, support for ascii keyboard

* support keyboards without physical '/'
2024-01-28 13:45:01 +03:00
Peter Savchenko 9542551d84
chore(release): increment package version to 2.29 (#2593) 2024-01-24 18:01:49 +03:00
Betty Steger 4bdf7a12b7
chore(on-change): check data-mutation-free of parent nodes (#2548)
* add data-mutation-free=deep

* just use closest and reduce waiting time in test

* Update src/components/block/index.ts

Co-authored-by: Peter Savchenko <specc.dev@gmail.com>

* Update test/cypress/tests/onchange.cy.ts

Co-authored-by: Peter Savchenko <specc.dev@gmail.com>

* add data-mutation-free=deep

* just use closest and reduce waiting time in test

* Update src/components/block/index.ts

Co-authored-by: Peter Savchenko <specc.dev@gmail.com>

* Update test/cypress/tests/onchange.cy.ts

Co-authored-by: Peter Savchenko <specc.dev@gmail.com>

* add line in Changelog

* Update docs/CHANGELOG.md

Co-authored-by: Peter Savchenko <specc.dev@gmail.com>

* add support for characterData mutations

* Update onchange.cy.ts

---------

Co-authored-by: Peter Savchenko <specc.dev@gmail.com>
2024-01-10 22:27:19 +03:00
github-actions[bot] c5ddf91189
Bump version (#2557)
Co-authored-by: github-actions <action@github.com>
2023-12-22 23:49:00 +03:00
Peter Savchenko 11925ca980
fix(caret): caret loosing on mobile devices (#2570)
* fix(caret): caret loosing on mobile devices

* rm unused import
2023-12-22 23:31:46 +03:00
Peter Savchenko cd29c52e51
feat(ui): native-like tab behaviour, slash for toolbox (#2569)
* slash to open toolbox, tab for navigation

* tab, focus improvements

- remove "focused" block state
- tab navigation respects inputs
- allow to focus contentless blocks

* fix tests

* tests for Slash

* tab tests

* test for tabbing out of editor

* tests fixed

* review fixes
2023-12-22 23:15:35 +03:00
Gusev Yaroslav ea2be754e7
Make a request to github api instead of using composite action (#2558) 2023-12-11 11:24:26 +03:00
Gusev Yaroslav 5515c3b534
feat: Skip workflow and do not bump version if it was changed already (#2555)
* Skip workflow and do not bump version if it was changed already

* Change comment

* Change action name
2023-12-09 19:57:03 +07:00
github-actions[bot] 6385ab2a39
Bump version (#2554)
Co-authored-by: github-actions <action@github.com>
2023-12-09 02:18:53 +03:00
Peter Savchenko c5854eea14
fix(inline-toolbar): appearance logic improved (#2550)
* fix(inline-toolbar): appearing logic improved

* tests added

* fix tests

* debounce added

* fix test build in github action

* increase closeTo delta for ff
2023-12-09 02:05:27 +03:00
Gusev Yaroslav 348c1c7752
Give a bump-version action a pull-request:write permission (#2553) 2023-12-09 01:34:11 +03:00
Gusev Yaroslav 8726f8bdfa
Give contents:write permission to jobs (#2549) 2023-12-06 12:54:54 +03:00
ferntheplant 494d6e4b9b
fix: use type imports for block events (#2491)
* fix: use type imports for block events

* add changelog entry

---------

Co-authored-by: Peter Savchenko <specc.dev@gmail.com>
2023-12-05 19:26:10 +03:00
Gusev Yaroslav 531ab06f63
Set workflow status to skipped if same version published (#2539)
* Made a step that cancels a workflow, settings it's status to 'skipped'

* Fixed accidentally removed files

* Fix: returned deleted submodules
2023-11-16 20:34:02 +03:00
Peter Savchenko 5e8fe06dd6
fix: errors caused by events fired while editor is not initialized yet (#2532)
* fix(init): errors cause by events fired while editor is not initialized yet

* Update package.json
2023-11-08 22:35:52 +03:00
Gusev Yaroslav af6b64a3e6
Added a github link to codex bot release notification (#2527)
* Added a github link to codex bot release notification

* Url now depends on type of release

* Removed unnecessary condition

* fixed indentation
2023-11-06 19:00:19 +03:00
Al-Amin Firdows 92ccfc5317
docs: replace holderId config by holder (#2525)
* docs: replace holderId by holder

* docs: replace holderId by holder
2023-11-02 19:46:09 +03:00
Peter Savchenko ee188bfe5d
feat(config): new style.nonce option for CSP (#2519) 2023-10-23 01:08:26 +03:00
Peter Savchenko 35337747c5
fix(inline-toolbar): editor element grow on inline toolbar close (#2516) 2023-10-19 00:44:06 +03:00
github-actions[bot] d936bf08b7
Bump version (#2515)
Co-authored-by: github-actions <action@github.com>
2023-10-17 19:15:43 +03:00
Peter Savchenko e8acc59487
fix(onchange): blocks.render would not call onChange in safari (#2514) 2023-10-17 18:26:23 +03:00
Peter Savchenko bdb6eed9d1
Merge branch '2.28' into next 2023-10-17 15:43:50 +03:00
Peter Savchenko b8cfcaec26
fix(build): 2.28.1 package rebuilt 2023-10-17 15:19:57 +03:00
Peter Savchenko 7d0db9b622
chore(patch): pull 2.28.1 patch to the next (#2507)
* fix(paste): delay onPaste call to be sure that block is ready (#2506)

* chore(package.json): increment patch version

* Update CHANGELOG.md
2023-10-11 20:28:16 +03:00
Peter Savchenko ca2bc803a1
fix(paste): delay onPaste call to be sure that block is ready (#2506) 2023-10-11 19:43:18 +03:00
github-actions[bot] c9014e670d
Bump version (#2485)
Co-authored-by: github-actions <action@github.com>
2023-09-20 11:19:42 +03:00
Peter Savchenko ec569f9981
fix(toolbar): layout shrink after blocks removing (#2484) 2023-09-20 11:07:25 +03:00
github-actions[bot] 77eb320203
Bump version up to 2.28.1-rc.0 (#2468)
* Bump version

* use 2.29.0-rc.0

---------

Co-authored-by: github-actions <action@github.com>
Co-authored-by: Peter Savchenko <specc.dev@gmail.com>
2023-09-05 11:33:27 +03:00
Peter Savchenko 0369ff5827
fix(renderer): handle empty array as data.blocks (#2454) 2023-09-05 11:24:47 +03:00
Peter Savchenko 59c8d28da5
chore(release): bump version to 2.28.0 (#2455) 2023-08-25 00:23:57 +03:00
github-actions[bot] f8e9414217
Bump version (#2450)
Co-authored-by: github-actions <action@github.com>
2023-08-21 12:29:45 +03:00
Peter Savchenko 42612a0fd9
chore(blocks.render): mutex for onchange added to the blocks.render() (#2449) 2023-08-21 12:28:31 +03:00
github-actions[bot] be0d33ce0f
Bump version (#2447)
Co-authored-by: github-actions <action@github.com>
2023-08-20 17:29:21 +03:00
Peter Savchenko 922dfd8741
chore(api): blocks.update(id, data) method improved (#2443)
* add custom Chai assertion "be.calledWithBatchedEvents" for testing onchange

* chore(api): blocks.update(id, data) method improved

- `blocks.update(id, data)` now can accept partial data object — it will update only passed properties, others will remain the same.
- `blocks.update(id, data)` now will trigger onChange with only `block-change` event.
- `blocks.update(id, data)` will return a promise with BlockAPI object of changed block.

* fix tests

* Update blocks.cy.ts
2023-08-19 07:53:42 +03:00
Peter Savchenko b39996616c
chore(perf): initialisation and rendering performance optimisations (#2430)
* renderer batching

* initialization and rendering performance optimized

* insertMany api method added

* Update index.html

* rm old method

* upd changelog

* upd paragraph

* paste tests fixed

* api blocks tests fixed

* backspace event tests fixed

* async issues in tests fixed

* eslint

* stub block added, tests added

* eslint

* eslint

* add test for insertMany()

* Update package.json
2023-08-08 22:17:09 +03:00
github-actions[bot] 0e64665b0f
Bump version (#2424)
Co-authored-by: github-actions <action@github.com>
2023-07-20 23:34:45 +03:00
Peter Savchenko 022320940e
feat(shortcuts): convert block by tools shortcut (#2419)
* feat(conversion): allow to convert block using shortcut

* display shortcuts in conversion toolbar

* tests for the blocks.convert

* tests for the toolbox shortcuts

* Update CHANGELOG.md

* Update toolbox.cy.ts

* rm unused imports

* firefox test fixed

* test errors via to.throw
2023-07-20 23:27:18 +03:00
github-actions[bot] 41dc65274d
chore(package.json): bump version up to 2.28.0-rc.0 (#2355)
* Bump version

* 2.28.0-rc.0

---------

Co-authored-by: github-actions <action@github.com>
Co-authored-by: Peter Savchenko <specc.dev@gmail.com>
2023-07-07 00:16:55 +03:00
Peter Savchenko b24bebbc40
feat(blocks-dom): block ids exported as data-id attribute (#2404) 2023-07-06 23:59:28 +03:00
Peter Savchenko 2ab9eb13bc
feat(block-events): delete keydown handling added (#2402)
* feat(block-events): delete keydown logic added

* chore(ui-events): delete keydown will remove selected blocks

* backspace tests added

* delete keydown tests

* tests for ui added

* fix ci

* separate block events tests for several files

* Delete BlockEvents.cy.ts

* rm unused change

* add code-review fixes + corner cases handling

* Update blockEvents.ts

* allow merging only same type blocks
2023-07-06 23:45:12 +03:00
Peter Savchenko 9e64003c0a
fix(onchange): support data-mutation-free on attributes chane (#2393) 2023-06-22 20:42:14 +03:00
Peter Savchenko c8993332e3
fix(onchange): callback will be fired on removing of a whole text (#2392)
* fix(onchange): callback will be fired when the whole text is removed in a block

* whops
2023-06-21 20:32:25 +03:00
Taly 39018e07e4
Update create-a-release-draft.yml (#2358)
* Update create-a-release-draft.yml

* Update create-a-release-draft.yml
2023-05-15 23:05:58 +03:00
Peter Savchenko 1f74c5bec9
chore(release): change log updated, version updated (#2356)
* changelog updated, version updated
2023-05-15 22:43:45 +03:00
Taly 0d6c8bd4af
Use node 16 for actions (#2357)
* use node 16

* use node 16
2023-05-15 22:27:35 +03:00
Peter Savchenko 26dce94e67
fix ci (#2354) 2023-05-12 21:12:17 +03:00
github-actions[bot] e6c265d0fa
Bump version (#2313)
Co-authored-by: github-actions <action@github.com>
2023-05-12 21:02:47 +03:00
Peter Savchenko b7b00fd060
chore(onChange): bugfix, batching, improvements of ModificationsObserver (#2349)
* block onchange stash

* improve block filtration

* update tool root

* chore(block): remove willSelect und willUnselect

* onchange events batching

* get rid of CustomEvent extension, create custom event map instead

* improve types of EventsDispatcher

* fix tests

* custom sinon + chai matchers

* improve tests, add mutex for fake cursor

* add test for fake-cursor mutex

* test for batch filtering

* fix caret setting by enter press at the end of the block

* test for detectToolRootChange

* remove resolved todos

* changelog added

* fix tests

* Update CHANGELOG.md

* rename FakeCursorAboutToBeSet -> FakeCursorAboutToBeToggled

* update didMutated statements

* move inputs cache clearing to a separate method

* rm Record inheritance from Event maps

* add type alisases

* rename isElementContainsFakeCursor ->  isFakeCursorInsideContainer

* improve code style
2023-05-12 20:50:48 +03:00
Taly aafab1d395
Use Vite builder (#2300)
* vite builder initial

* save

* add displayName

* add paragraph from npm

* fix postcss apply

* remove some packages, fix tests

* Update cypress.yml

* remove logs

* remove unused packages

* update path to image

* update

* Update index.html

* Update cypress.yml

* Update cypress.yml

* Update cypress.yml

* remove displayName field

* update names

* Update index.ts

* Update index.ts

* update packages

* remove packages

* postcss preserve: true

* Update index.html

* Update editor-modules.d.ts

* use public dir for static

* Update vite.config.js

* update modules type

* Update CHANGELOG.md
2023-04-13 22:25:29 +03:00
Guillaume Leon 0491155e33
feat(toolbar-api): method for toggling toolbox (#2332)
* Expose toolbox control method

* Add test for toolbox

* rename method to toggleToolbox

* add missing test case

* Add changelog

* remove eslint rule

* Update changelog

Co-authored-by: Peter Savchenko <specc.dev@gmail.com>

* fix linter problems

---------

Co-authored-by: Peter Savchenko <specc.dev@gmail.com>
2023-04-13 20:02:46 +03:00
Taly 707ff72a94
Update FUNDING.yml (#2330) 2023-04-04 17:21:22 +03:00
Ilya Maroz f5313fda40
ci(tests): improve workflow for running tests (#2328)
* ci: use ubuntu container for edge runner

* chore: upgrade CHANGELOG.md

* chore: fix linter in cypress config
2023-04-02 22:16:45 +01:00
Ilya Maroz d7f1853ca1
deps(Cypress): upgrade library and related packages to latest versions, migrate config, fix type error (#2327)
* deps: upgrade cypress and related libraries

* chore: automate migrate cypress config, rename spec files

* fix: custom commands types

* chore: upgrade CHANGELOG.md

* ci: upgrade cypress action to support new config file format

* ci: remove container from firefox job, upgrade checkout action
2023-04-02 19:20:59 +01:00
Ilya Maroz 75379c66a9
deps(TypeScript) - upgrade to v5, upgrade ts-loader, fix types error, fix pasteConfig getter wrapper (#2322)
* deps: upgrade typescript to v5, upgrade ts-loader to support newest TS

* Fix (??) type of `pasteConfig`

TypeScript 4.9 found something is wrong with this code, but it's unclear (to me) which line is wrong. This PR is a guess, do with it what you will.

In paste.ts there's a check to see if `pasteConfig === false`:
https://github.com/codex-team/editor.js/blob/next/src/components/modules/paste.ts#L287

However, this getter never returns false because if the LHS of the `||` is `false`, `{ }` is returned instead.

It seems like this meant to be `??` instead so that if `this.constructable[InternalBlockToolSettings.PasteConfig]` was `undefined` (missing), then `{}` would be returned instead. But maybe you meant `false` here - I don't know.

* feat: create alias for PasteConfig, fix lint

* fix: problems with types

* test: add case for disabling preventing default behavior of paste event handler, add cases for pasteConfig getter in BlockTool wrapper

* chore: upgrade CHANGELOG.md

* fix: interface naming convention

* chore: apply CHANGELOG.md suggestion

* refactor: create custom Editor instance inside test case

* fix: remove editor instance destroy after PR feedback

---------

Co-authored-by: Ryan Cavanaugh <RyanCavanaugh@users.noreply.github.com>
2023-04-02 16:52:42 +01:00
Peter Savchenko c18011595a
Update table (#2315) 2023-03-23 20:35:06 +03:00
Taly b7d754c93d
chore(docs): use local images in docs files (#2239)
* update image

* use local images

---------

Co-authored-by: Ilya Maroz <moroz.ilya.olegovich@gmail.com>
Co-authored-by: Peter Savchenko <specc.dev@gmail.com>
2023-03-21 18:59:57 +00:00
Peter Savchenko 5257b061c4
fix(block): remember plugin's content element instead of dom search (#2311)
* fix(block): remember plugin's content element instead of dom search

* improve doc

* test case added
2023-03-20 23:28:17 +03:00
dependabot[bot] 293135a795
Bump path-parse from 1.0.6 to 1.0.7 (#1746)
Bumps [path-parse](https://github.com/jbgutierrez/path-parse) from 1.0.6 to 1.0.7.
- [Release notes](https://github.com/jbgutierrez/path-parse/releases)
- [Commits](https://github.com/jbgutierrez/path-parse/commits/v1.0.7)

---
updated-dependencies:
- dependency-name: path-parse
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-18 20:27:49 +00:00
github-actions[bot] e9e0146961
Bump version (#2308)
Co-authored-by: github-actions <action@github.com>
Co-authored-by: Ilya Maroz <37909603+ilyamore88@users.noreply.github.com>
2023-03-16 15:33:14 +00:00
dependabot[bot] 599d332aef
build(deps): bump decode-uri-component from 0.2.0 to 0.2.2 (#2199)
Bumps [decode-uri-component](https://github.com/SamVerschueren/decode-uri-component) from 0.2.0 to 0.2.2.
- [Release notes](https://github.com/SamVerschueren/decode-uri-component/releases)
- [Commits](https://github.com/SamVerschueren/decode-uri-component/compare/v0.2.0...v0.2.2)

---
updated-dependencies:
- dependency-name: decode-uri-component
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Peter Savchenko <specc.dev@gmail.com>
Co-authored-by: Ilya Maroz <37909603+ilyamore88@users.noreply.github.com>
2023-03-16 12:41:56 +00:00
Ilya Maroz 839daf556f
fix(npm): ignore redundant files in sub-directories (#2307)
fix: ignore redundant files in sub-directories
2023-03-16 12:08:15 +00:00
github-actions[bot] 7cf5266a23
Bump version (#2304)
Co-authored-by: github-actions <action@github.com>
Co-authored-by: Ilya Maroz <37909603+ilyamore88@users.noreply.github.com>
2023-03-15 12:41:03 +00:00
Anwar Hussain 86c84d8555
hotfix: add popular grammar checker extension node names (#2096)
* hotfix: add popular grammar checker extension node names

* fix: prettier messed the existing code style

* update the changelog

* Update docs/CHANGELOG.md

Co-authored-by: Peter Savchenko <specc.dev@gmail.com>

* Update CHANGELOG.md

---------

Co-authored-by: Peter Savchenko <specc.dev@gmail.com>
2023-03-15 12:27:25 +00:00
dependabot[bot] 153fc48bd6
build(deps): bump json5 from 1.0.1 to 1.0.2 (#2233)
Bumps [json5](https://github.com/json5/json5) from 1.0.1 to 1.0.2.
- [Release notes](https://github.com/json5/json5/releases)
- [Changelog](https://github.com/json5/json5/blob/main/CHANGELOG.md)
- [Commits](https://github.com/json5/json5/compare/v1.0.1...v1.0.2)

---
updated-dependencies:
- dependency-name: json5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Peter Savchenko <specc.dev@gmail.com>
Co-authored-by: Ilya Maroz <37909603+ilyamore88@users.noreply.github.com>
2023-03-15 10:16:18 +00:00
Ilya Maroz 447f980d4a
build(publish): optimise npm package size (#2301)
* build: optimize npm package size

* fix: excluded regexes

* chore: update CHANGELOG.md

* fix: changelog information

Co-authored-by: Peter Savchenko <specc.dev@gmail.com>

---------

Co-authored-by: Peter Savchenko <specc.dev@gmail.com>
2023-03-14 11:09:42 +00:00
Kirk Douglas Jr 9ae96b9e17
fix(move-up-tune): scroll up offset for blocks above viewport (#2298)
* fix: scroll up offset for blocks above viewport

* Update CHANGELOG.md

* Update CHANGELOG.md

---------

Co-authored-by: Peter Savchenko <specc.dev@gmail.com>
2023-03-14 00:09:04 +03:00
Alireza Kamali 7bd5c53ab2
fix(types): compiler error from importing type BlockToolData (#2264)
Co-authored-by: Peter Savchenko <specc.dev@gmail.com>
2023-02-21 00:18:50 +02:00
github-actions[bot] 58d6869285
Bump version (#2197)
Co-authored-by: github-actions <action@github.com>
2023-02-20 22:00:25 +02:00
Peter Savchenko dfb07ec4e4
fix(tests): fire mutation event on tune activation (#2281)
* fix(onchange): fire mutation event if there are other changes along with mutation-free nodes

* update header dependency

* use node 16 for windows

* fix onChange firing by manual dispatchChange() call

* eslint

* use node 16 for ci tests

* Update CHANGELOG.md
2023-02-20 00:59:10 +02:00
Peter Savchenko 34d4b028c4
chore: update submodules 2023-02-17 02:28:11 +02:00
Tatiana Fomina d4edbe162b
Update a readme (#2267)
* Update a readme

* Update changelog link

* Update browsers table

* Update logo

* Fix image path

* Remove credits and references

* Upd

* Add features section

* added the about section

* update the installation guide

* Update community section

* Upd

* Upd

* Upd

* Fix linebreaks

* Fix

* Upd roadmap image indentation

* Add link to landing

* Upd

* Upd

* Fix

---------

Co-authored-by: Peter Savchenko <specc.dev@gmail.com>
2023-02-15 22:51:43 +03:00
Tatiana Fomina 07b1ce2aca
Chore/popover refactoring (#2249)
* Add new popover class

* Add flipper

* confirmation

* confirmation

* Add confirmation support

* Add search

* Add toggle group support and update popover tests

* Add custom content support

* Fix scroll issue

* Add mobile version

* Integration

* Fix animation

* Cleanup

* Fix popover position for narrow mode

* Fix tests

* Update version and changelog

* Rename css classes

* Move files

* Stop using PopoverItem from outside of popover context

* Fix jsdoc

* Move error animation to popover item

* Update css variables

* Update docs/CHANGELOG.md

Co-authored-by: Ilya Maroz <37909603+ilyamore88@users.noreply.github.com>

* Update src/components/block-tunes/block-tune-move-down.ts

Co-authored-by: Peter Savchenko <specc.dev@gmail.com>

* Update src/components/block-tunes/block-tune-move-up.ts

Co-authored-by: Peter Savchenko <specc.dev@gmail.com>

* Fixes

* Fix imports

* Fix toolbox close event

* Move search-input file

* Fix comment

* Rename method

* Cleanup

* Remove onFlip callback from popover item

* Rename

* Fix removing event listener

* Move popover animations to popover.css file

* Cleanup styles

* Fix jsdoc

* Fix confirmation chains

* Close toolbox oly when it's open

* Change activation error animation

* Update version and changelog

* Fix overlay

* Update icon border-radius on mobile

* Disable item text select

* Update changelog

* Update yarn.lock

* Add rc postfix to version

---------

Co-authored-by: Ilya Maroz <37909603+ilyamore88@users.noreply.github.com>
Co-authored-by: Peter Savchenko <specc.dev@gmail.com>
2023-02-10 21:29:58 +03:00
Tatiana Fomina 551e3f10b9
Update README.md (#2258) 2023-02-01 00:09:08 +02:00
Denys Vuika d6cad60af3
Remove incorrect import for typings (#2252)
* Remove incorrect import for typings

Removes incorrect `'cypress/types/bluebird'` import; fixes #2251

* Update changelog and version

* Update CHANGELOG.md

---------

Co-authored-by: Tanya Fomina <fomina.tatianaaa@yandex.ru>
2023-01-31 20:32:43 +03:00
Nick 24baf08c89
fix(issue templates) Made changes in issue templates (#2257)
* fix(issue templates) Made changes in issue templates

* Deleted issue--discussion
* Deleted feature_request
* Deleted link to the contributors chat in tg
* Changed bug_report title
* Renamed discussion to general_issue

* fix(issue templates) Made changes in issue templates

* Changed description for General Issue template
* Added link to the disscussions tab

* Revert "fix(issue templates) Made changes in issue templates"

This reverts commit 377d8a7da7.

* fix(issue templates) Made changes in issue templates

* Added link to discussions
* Changed "general issue" template description
2023-01-30 22:16:53 +03:00
Nick b4e2429476
fix(issue templates) Made changes in issue templates (#2254)
* Deleted issue--discussion
* Deleted feature_request
* Deleted link to the contributors chat in tg
* Changed bug_report title
* Renamed discussion to general_issue
2023-01-26 00:37:20 +03:00
Tatiana Fomina 870e265af0
fix(tunes): Make label an alias for title in tunes menu item (#2198)
* Make label an alias for title in tunes item

* Cleanup

* Update version and changelog

* Update changelog

* Move resolveAlias to utils

* Add fallback for popover item title

* Lint

* Lint

* Add fallback icon and title to popover

* Update version

* Lint

* Fix changelog

* Fallback to empty string

This reverts commit ae9d643557.

* Fix changelog again

* Cleanup

* Add deprecated
2022-12-14 23:46:36 +03:00
Umang G. Patel c97f0842f5
fix(paste): handle pasteConfig with tag names specified in upper case (#2217)
* toLower case added

* regression test case added

* change log update

* version updated

* Apply suggestions from code review

Co-authored-by: Peter Savchenko <specc.dev@gmail.com>

* Update docs/CHANGELOG.md

Co-authored-by: Peter Savchenko <specc.dev@gmail.com>

Co-authored-by: Peter Savchenko <specc.dev@gmail.com>
2022-12-14 14:25:02 +05:30
Tatiana Fomina b9a0665672
fix(tunes): make installed tunes render above default tunes (#2204)
* Fix tunes order

* Update version and changelog

* Fix tests
2022-12-06 16:32:57 +03:00
Peter Savchenko c18134daeb
update all submodules (#2196) 2022-12-02 23:36:14 +04:00
Tatiana Fomina 913ad0c8dc
feat: Add toggle group support (#2195)
* Add toggle group support

* Update version and changelog

* Fix

* Simplify

* Update test/cypress/tests/utils/popover.spec.ts

Co-authored-by: Peter Savchenko <specc.dev@gmail.com>

Co-authored-by: Peter Savchenko <specc.dev@gmail.com>
2022-12-01 20:58:58 +03:00
github-actions[bot] 320a30afcc
bump: 2.26.0 is ready (#2189)
* Bump version

* Update package.json

Co-authored-by: github-actions <action@github.com>
Co-authored-by: Peter Savchenko <specc.dev@gmail.com>
2022-11-29 22:03:33 +04:00
Peter Savchenko 509023e307
chore(submodule): paragraph submodule updated with the new icons (#2191)
* do not show tooltip on mobile

* paragraph submodule updated with the new icon
2022-11-29 15:01:02 +04:00
Peter Savchenko d56c07c3dc
chore(icons): icons size increased for mobile devices (#2190)
* popover icons size increased on mobile devices

* update icon size on mobile

* some mobile styles improvements

* inline tools click on mobile devices improved
2022-11-29 12:02:23 +04:00
Peter Savchenko 1a72d2153b
fix(toolbox): shortcuts appearance fixed, icons became optional (#2188)
* fix(toolbox): shortcuts appearing fixed, icons became optional

* rm only from test

* Update tools.spec.ts

* rm useless tests

* Update CHANGELOG.md
2022-11-29 01:57:48 +04:00
github-actions[bot] 04118b2d6e
bump: 2.26.0-rc.7
Co-authored-by: github-actions <action@github.com>
2022-11-28 20:28:41 +04:00
Peter Savchenko 73c9bdf40b
fix(block-events): split block with removing of selected text fragment (#2186)
* fix(block-events): split block with removing of selected text fragment

* Update BlockEvents.spec.ts
2022-11-28 20:23:30 +04:00
github-actions[bot] a8026e6829
Bump version (#2183)
Co-authored-by: github-actions <action@github.com>
2022-11-26 11:37:53 +04:00
Peter Savchenko 0bf5007f6c
fix ci (#2182) 2022-11-26 11:32:04 +04:00
github-actions[bot] 6088aaead6
bump: 2.26.0-rc.5
Co-authored-by: github-actions <action@github.com>
2022-11-26 10:48:28 +04:00
dependabot[bot] cdaf10a58f
build(deps): bump moment from 2.29.2 to 2.29.4 (#2104)
Bumps [moment](https://github.com/moment/moment) from 2.29.2 to 2.29.4.
- [Release notes](https://github.com/moment/moment/releases)
- [Changelog](https://github.com/moment/moment/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/moment/moment/compare/2.29.2...2.29.4)

---
updated-dependencies:
- dependency-name: moment
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Peter Savchenko <specc.dev@gmail.com>
2022-11-26 10:42:23 +04:00
Peter Savchenko e048c4d46b
fix release drafter 2022-11-26 03:13:48 +04:00
github-actions[bot] fb86d71828
bump: 2.26.0-rc.4
Co-authored-by: github-actions <action@github.com>
Co-authored-by: Peter Savchenko <specc.dev@gmail.com>
2022-11-26 03:09:00 +04:00
Valerii Varankin 811a41366b
Fix: check for Files substitutions before handling files paste (#1928)
* FIX: Paste text from MS Word instead of image

* changelog added

* Update CHANGELOG.md

* rm submodule change

Co-authored-by: Peter Savchenko <specc.dev@gmail.com>
2022-11-26 01:24:17 +04:00
Peter Savchenko 0d0f9e8de2
chore(submodules): submodules updated, changelog fixed (#2179)
* submodules updated

* changelog updated
2022-11-26 00:53:08 +04:00
azibodusi osain 9b7da504e4
feat(blocks-api): the insert() method now has the optional id param
* refactor: added id to the insert method  to allow user pass and existing id to the method

When  working with multiple editor at the same and need to link  all  blocks to each editor and keeping the same ids in all.

* moved the position of the block_id params to the end to aaviod breaking cha

* doc: updated  the  documentation on insert method params

* refactor :  formatted  the code to  add/remove  space

* refactor: moved the position of the `id` and its description to the respective position

* refactor: rollback to previous commit

* added back the removed default value

* fix error, remove garbage

* test added, changelog added

Co-authored-by: Umang G. Patel <23169768+robonetphy@users.noreply.github.com>
Co-authored-by: Peter Savchenko <specc.dev@gmail.com>
2022-11-25 23:59:38 +04:00
github-actions[bot] a1071b7229
Bump version (#2178)
Co-authored-by: github-actions <action@github.com>
2022-11-25 22:29:01 +04:00
Peter Savchenko b1b582b150
feat(icons): codex icons package is used instead of svg sprite (#2173)
* chore(icons): migrating to the coded icon pack

* conversion toolbar

* inline toolbar, part 1

* inline-link tool has the new icons

* added a test for creating a link by Enter keydown in link input

* rm last icons, svg sprite, loaders

* rollback .ce-settings styles

* Update CHANGELOG.md

* Update settings.json
2022-11-25 22:26:23 +04:00
github-actions[bot] f0f8f8fcb5
Bump version (#2177)
Co-authored-by: github-actions <action@github.com>
2022-11-25 22:01:02 +04:00
Peter Savchenko 3272efc3f7
chore(linting): eslint updated, code linted (#2174)
* update eslint + autofix

* a bunch of eslint fixes

* some spelling & eslint fixes

* fix some eslint errors and spells

* Update __module.ts

* a bunch of eslint fixes in tests

* Update cypress.yml

* Update cypress.yml

* fix cypress docker image name

* fixes for tests

* more tests fixed

* rm rule ignore

* rm another ignored rule

* Update .eslintrc
2022-11-25 21:56:50 +04:00
github-actions[bot] 19f3790cfd
Bump version (#2169)
Co-authored-by: github-actions <action@github.com>
2022-11-22 00:30:24 +04:00
Umang G. Patel f659015be6
fix(tools-api): pasteConfig.tags now supports a sanitize config (#2100)
* event handlers function added

* santization config added

* integrate with paste event

* lint removed

* remove old changes

* object based sanitization configuration support

* paste config updated

* logic updated

* extract tag name from paste-config

* tool tags added

* multi tag sanitization added

* the comments added

* lint removed

* Update types/configs/paste-config.d.ts

Co-authored-by: Peter Savchenko <specc.dev@gmail.com>

* update the changes

* lint removed\

* return empty array by get tags

* submoduble reset

* Update src/components/modules/paste.ts

Co-authored-by: Jorge <46056498+jorgectf@users.noreply.github.com>

* changelog added

* tool comments added

* chore: docs, code comments updated

* fix: xss in processDataTransfer

* base tests added

* test added

* rm 'only' from test suite

* rm log

* reorder test

Co-authored-by: Peter Savchenko <specc.dev@gmail.com>
Co-authored-by: Jorge <46056498+jorgectf@users.noreply.github.com>
2022-11-22 00:28:53 +04:00
github-actions[bot] cd06bfc9c5
Bump version up to 2.26.0-rc.0 (#2105)
* Bump version

* Update package.json

* Update changelog

Co-authored-by: github-actions <action@github.com>
Co-authored-by: Tanya Fomina <fomina.tatianaaa@yandex.ru>
2022-11-03 22:39:51 +03:00
Tatiana Fomina 581289c03e
Block tunes as a popover (#2091)
* Default tunes to popover

* Add the rest of default tunes

* Add popover

* Cleanup

* Rename custom content

* Cleanup

* Add ability to open block settings upwards

* Fix tests

* Cleanup default tunes

* Rename and cleanup

* Add ability to display rendered custom tunes

* cleanup

* Rename

* Add flag to close tunes popover

* Cleanup

* i18n

* Cleanup

* Fix build and tests

* Fix for iframe

* Add comments

* Display active item, move closeOnActivate to popover

* Add confirmation support to popover

* Handle boolean value in confirmation param

* Clarify flippable logic in popover

* Comments

* Pass editor element as a param of popover constructor

* Fix readability

* Tests

* Fix flipper for confirmation element

* Update confirmation config structure

* Rename onClick to onActivate

* Fix tests and build

* Make confirmation props optional

* Simplify processing tunes

* Renamings

* Fix text block tunes

* Docs

* Update event type

* Move enabling confirmation state to separate method

* move popover types

* Unhardcode color

* Support toggling

* Add support of disabled items

* Fix tab in empty block leading to selecting second item in popover

* Remove margins for styles api settings button class

* Fix arrow navigation between blocks after opening block tunes

* Cleaup in default tunes code

* Fix chaining confirmations

* Colors

* Types

* Change the way flippable elements of popover custom area are set

* Remove borders around popover icons

* Fix untabbable inline toolbar

* Fix locked scroll after closing tunes popover on mobile

* Cleanup

* Set max popover width

* Make popover icon's border outside

* Fix tab issue

* Fix focus/hover issue

* Reformat

* Cleanup

* Fix opening block tunes via keyboard

* Add disableSpecialHoverAndFocusBehavior

* Add deprecated comment

* Cleanup

* Fix popover active state

* Fix checklist deletion with confirmation

* Fix checklist deletion 2

* Fix popover focus

* Fix popover items being impossible to flip after searching

* Fix popover item highlighting issue

* Update flipper.spec.ts

* Fixes after review

* Add Tunes Api tests

* Fix multiple popover entries configured by one tune

* Add tool's renderSettings() tests

* Add popover confirmation state test

* Fix popover width on mobile

* Add popover tests

* Add changelog and update version

* Update changelog

* Fix block tunes being unable to open after tune activation

Co-authored-by: Peter Savchenko <specc.dev@gmail.com>
2022-11-03 20:52:33 +03:00
dependabot[bot] 39c4114b97
build(deps): bump terser from 4.6.12 to 4.8.1 (#2103)
Bumps [terser](https://github.com/terser/terser) from 4.6.12 to 4.8.1.
- [Release notes](https://github.com/terser/terser/releases)
- [Changelog](https://github.com/terser/terser/blob/master/CHANGELOG.md)
- [Commits](https://github.com/terser/terser/commits)

---
updated-dependencies:
- dependency-name: terser
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-20 09:32:54 +03:00
Peter Savchenko f7368edee3
Roadmap updated 2022-06-22 19:48:15 +03:00
279 changed files with 17933 additions and 11696 deletions

View file

@ -1,19 +0,0 @@
{
"presets": [
["@babel/preset-env", {
"modules": "umd",
"useBuiltIns": "entry",
"corejs": 3
}]
],
"plugins": [
"babel-plugin-add-module-exports",
"babel-plugin-class-display-name",
"@babel/plugin-transform-runtime"
],
"env": {
"test": {
"plugins": [ "istanbul" ]
}
}
}

View file

@ -1,27 +1,7 @@
{
"extends": [
"codex"
"codex/ts"
],
"rules": {
/**
* Temporary suppress some errors. We need to fix them partially in next patches
*/
"import/no-duplicates": ["warn"],
"@typescript-eslint/triple-slash-reference": ["off"],
"jsdoc/no-undefined-types": ["warn", {"definedTypes": [
"ConstructorOptions",
"API",
"BlockToolConstructable",
"EditorConfig",
"Tool",
"ToolSettings"
]}]
},
"settings": {
"jsdoc": {
"mode": "typescript"
}
},
"globals": {
"Node": true,
"Range": true,
@ -51,6 +31,8 @@
"ClientRect": true,
"ArrayLike": true,
"InputEvent": true,
"unknown": true
"unknown": true,
"requestAnimationFrame": true,
"navigator": true
}
}

2
.github/FUNDING.yml vendored
View file

@ -1,5 +1,5 @@
# These are supported funding model platforms
github: neSpecc
patreon: editorjs
open_collective: editorjs
custom: https://codex.so/donate

View file

@ -1,7 +1,7 @@
---
name: Bug report
about: Create a report to help us improve Editor.js
title: "[Bug]"
title: ""
labels: bug
assignees: ''

View file

@ -3,9 +3,9 @@ contact_links:
- name: Team
url: mailto:team@codex.so
about: Direct team contact.
- name: 💬 Discussions
url: https://github.com/codex-team/editor.js/discussions
about: Use discussions if you have an issue draft, an idea for improvement or for asking questions.
- name: Editor.js Telegram chat
url: https://t.me/codex_editor
about: Telegram chat for Editor.js users communication.
- name: Editor.js contributors Telegram chat
url: https://t.me/editorjsdev
about: Telegram chat for Editor.js contributors communication.
about: Telegram chat for Editor.js users communication.

View file

@ -1,19 +0,0 @@
---
name: Feature request
about: Suggest an idea to improve Editor.js
title: "\U0001F4A1"
labels: feature
assignees: ''
---
1. Describe a problem.
2. Describe the solution you'd like. Mockups are welcome.
3. Are there any alternatives?
<!--
🤫 If you like Editor.js, please consider supporting us via OpenCollective:
https://opencollective.com/editorjs
-->

View file

@ -1,8 +1,8 @@
---
name: Discussion
about: Any question about the Editor.js to discuss
name: General Issue
about: Well-designed, algorithmized feature/idea/improvement issue for Editor.js
title: ''
labels: discussion
labels: ''
assignees: ''
---

View file

@ -1,24 +0,0 @@
---
name: 'Issue: Discussion'
about: Any question about the project to discuss
title: "❓"
labels: discussion
assignees: ''
---
**Question**
A clear and consistent question about the project. Ex. How can I do smth? Why smth works this way? etc.
**Context**
Why and how the question has come up
**Related issues**
If there are related issues which describe a bugs or features, put them here
**Comments**
Any thoughts about the question

View file

@ -1,7 +1,14 @@
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:
pull_request_target:
branches:
- next
types: [closed]
@ -11,6 +18,8 @@ jobs:
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
@ -32,16 +41,22 @@ jobs:
uses: codex-team/action-nodejs-package-info@v1
# Stop workflow and do not bump version if it was changed already
- name: Stop workflow and do not bump version if it was changed already
uses: actions/github-script@v3
- name: Stop workflow if version was changed already
if: steps.packageOld.outputs.version != steps.packageNew.outputs.version
with:
script: |
core.setFailed('Version was changed! ${{ 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
@ -49,8 +64,7 @@ jobs:
# Setup node environment
- uses: actions/setup-node@v1
with:
node-version: 15
registry-url: https://registry.npmjs.org/
node-version: 16
# Bump version to the next prerelease (patch) with rc suffix
- name: Suggest the new version

View file

@ -1,7 +1,14 @@
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:
pull_request_target:
branches:
- next
types: [closed]
@ -11,7 +18,12 @@ jobs:
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:
@ -33,16 +45,21 @@ jobs:
# Stop workflow if version was not changed
- name: Stop workflow if version was not changed
uses: actions/github-script@v3
if: steps.packageOld.outputs.version == steps.packageNew.outputs.version
with:
script: |
core.setFailed('No version changes. ${{ steps.packageOld.outputs.version }}')
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
@ -53,8 +70,7 @@ jobs:
# Setup node environment
- uses: actions/setup-node@v1
with:
node-version: 15
registry-url: https://registry.npmjs.org/
node-version: 16
# Prepare, build and publish project
- name: Install dependencies
@ -87,16 +103,27 @@ jobs:
# If version name contains "-rc" suffix than mark a "pre-release" checkbox
prerelease: ${{ contains(steps.package.outputs.version, '-rc') }}
# Build and upload target Editor.js build to release as artifact
# 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/editor.js
asset_name: editor.js
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
@ -105,4 +132,4 @@ jobs:
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
disable_web_page_preview: true

View file

@ -1,36 +1,21 @@
name: Tests
name: Cypress
on: [pull_request]
jobs:
firefox:
runs-on: ubuntu-latest
container:
image: cypress/browsers:node14.16.0-chrome89-ff86
options: --user 1001
steps:
- uses: actions/checkout@v2
- run: yarn ci:pull_paragraph
- uses: cypress-io/github-action@v2
with:
config: video=false
browser: firefox
build: yarn build
chrome:
run-tests:
strategy:
matrix:
browser: [firefox, chrome, edge]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: yarn ci:pull_paragraph
- uses: cypress-io/github-action@v2
- uses: actions/setup-node@v3
with:
node-version: 18
- uses: actions/checkout@v4
- uses: cypress-io/github-action@v6
with:
config: video=false
browser: chrome
build: yarn build
edge:
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- run: yarn ci:pull_paragraph
- uses: cypress-io/github-action@v2
with:
config: video=false
browser: edge
build: yarn build
browser: ${{ matrix.browser }}
build: yarn build:test

View file

@ -19,6 +19,5 @@ jobs:
${{ runner.OS }}-build-
${{ runner.OS }}-
- run: yarn install
- run: yarn
- run: yarn lint

View file

@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
steps:
# Checkout to target branch
- uses: actions/checkout@v2
- uses: actions/checkout@v4
with:
# Pull submodules
submodules: 'recursive'
@ -22,7 +22,7 @@ jobs:
# Setup node environment
- uses: actions/setup-node@v1
with:
node-version: 15
node-version: 16
registry-url: https://registry.npmjs.org/
# Prepare, build and publish project
@ -46,9 +46,11 @@ jobs:
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@v2
- uses: actions/checkout@v4
- name: Get package info
id: package
@ -58,6 +60,6 @@ jobs:
uses: codex-team/action-codexbot-notify@v1
with:
webhook: ${{ secrets.CODEX_BOT_NOTIFY_EDITORJS_PUBLIC_CHAT }}
message: '📦 [${{ steps.package.outputs.name }}](${{ steps.package.outputs.npmjs-link }}) ${{ steps.package.outputs.version }} was published'
message: '📦 [${{ steps.package.outputs.name }} ${{ steps.package.outputs.version }}](${{ env.GITHUB_LINK }}) was published'
parse_mode: 'markdown'
disable_web_page_preview: true
disable_web_page_preview: true

1
.gitignore vendored
View file

@ -17,3 +17,4 @@ dist/
coverage/
.nyc_output/
.vscode/launch.json

3
.gitmodules vendored
View file

@ -16,9 +16,6 @@
[submodule "example/tools/simple-image"]
path = example/tools/simple-image
url = https://github.com/editor-js/simple-image
[submodule "src/tools/paragraph"]
path = src/tools/paragraph
url = https://github.com/editor-js/paragraph
[submodule "example/tools/marker"]
path = example/tools/marker
url = https://github.com/editor-js/marker

View file

@ -1,22 +1,6 @@
.idea
.github
docs
example
src
test
.babelrc
.editorconfig
.eslintignore
.eslintrc
.git
.gitmodules
.jshintrc
.postcssrc.yml
.stylelintrc
CODEOWNERS
cypress.json
tsconfig.json
tslint.json
webpack.config.js
yarn.lock
devserver.js
*
!/dist/**/*
!/types/**/*
!/LICENSE
!/README.md
!/package.json

View file

@ -1,8 +1,4 @@
plugins:
# Consumes files by @import rule
# https://github.com/postcss/postcss-import
postcss-import: {}
# Apply custom property sets via @apply rule
# https://github.com/pascalduez/postcss-apply
postcss-apply: {}
@ -26,16 +22,6 @@ plugins:
# https://github.com/csstools/postcss-preset-env#preserve
preserve: false
# Enable or disable specific polyfills
# https://github.com/csstools/postcss-preset-env#features
#
# List of available plugins
# https://github.com/csstools/postcss-preset-env/blob/master/src/lib/plugins-by-id.js
features:
# Modify colors using the color-mod() function in CSS
# https://github.com/jonathantneal/postcss-color-mod-function
color-mod-function: {}
# Nested rules unwrapper
# https://github.com/postcss/postcss-nested
#
@ -43,7 +29,3 @@ plugins:
# 'postcss-nesting' feature but it does not work with BEM
# Report: https://github.com/csstools/postcss-preset-env/issues/40
postcss-nested: {}
# Compression tool
# https://github.com/cssnano/cssnano
cssnano: {}

41
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,41 @@
{
"cSpell.words": [
"autofocused",
"Behaviour",
"cacheable",
"childs",
"codexteam",
"colspan",
"contenteditable",
"contentless",
"Convertable",
"cssnano",
"cssnext",
"Debouncer",
"devserver",
"editorjs",
"entrypoints",
"Flippable",
"GRAMMARLY",
"hsablonniere",
"intellij",
"keydown",
"keydowns",
"Kilian",
"mergeable",
"movetostart",
"nofollow",
"opencollective",
"preconfigured",
"resetors",
"rowspan",
"selectall",
"sometool",
"stylelint",
"textareas",
"twitterwidget",
"typeof",
"Unmergeable",
"viewports"
]
}

423
README.md
View file

@ -1,275 +1,234 @@
<a href="https://editorjs.io/"><p align="center"><img src="https://capella.pics/79ce946a-d636-41cd-aa96-d3bc5ecfde03.jpg"></p></a>
<p align="center">
<a href="https://editorjs.io/">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="./assets/logo_night.png">
<source media="(prefers-color-scheme: light)" srcset="./assets/logo_day.png">
<img alt="Editor.js Logo" src="./assets/logo_day.png">
</picture>
</a>
</p>
[![](https://flat.badgen.net/npm/v/@editorjs/editorjs?icon=npm)](https://www.npmjs.com/package/@editorjs/editorjs)
[![](https://flat.badgen.net/bundlephobia/min/@editorjs/editorjs?color=cyan)](https://www.npmjs.com/package/@editorjs/editorjs)
[![](https://flat.badgen.net/bundlephobia/minzip/@editorjs/editorjs?color=green)](https://www.npmjs.com/package/@editorjs/editorjs)
[![Backers on Open Collective](https://opencollective.com/editorjs/backers/badge.svg)](#backers)
[![Sponsors on Open Collective](https://opencollective.com/editorjs/sponsors/badge.svg)](#sponsors)
[![](https://img.shields.io/npm/l/@editorjs/editorjs?style=flat-square)](https://www.npmjs.com/package/@editorjs/editorjs)
[![Join the chat at https://gitter.im/codex-team/editor.js](https://badges.gitter.im/codex-team/editor.js.svg)](https://gitter.im/codex-team/editor.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
<p align="center">
<a href="https://editorjs.io/">editorjs.io</a> |
<a href="https://editorjs.io/base-concepts/">documentation</a> |
<a href="https://github.com/codex-team/editor.js/blob/next/docs/CHANGELOG.md">changelog</a>
</p>
| | | | | | |
| --------- | --------- | --------- | --------- | --------- | --------- |
| <img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="16px" height="16px" /> Edge 12+ | <img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="16px" height="16px" /> Firefox 18+ | <img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="16px" height="16px" /> Chrome 49+ | <img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="16px" height="16px" /> Safari 10+ | <img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari-ios/safari-ios_48x48.png" alt="iOS Safari" width="16px" height="16px" /> iOS Safari 10+ | <img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/opera/opera_48x48.png" alt="Opera" width="16px" height="16px" /> Opera 36+
<p align="center">
<a href="https://www.npmjs.com/package/@editorjs/editorjs">
<img src="https://flat.badgen.net/npm/v/@editorjs/editorjs?icon=npm" alt="npm"/>
</a>
<a href="https://www.npmjs.com/package/@editorjs/editorjs">
<img src="https://flat.badgen.net/bundlephobia/minzip/@editorjs/editorjs?color=green" alt="Minzipped size"/>
</a>
<a href="https://github.com/codex-team/editor.js#backers">
<img src="https://opencollective.com/editorjs/backers/badge.svg" alt="Backers on Open Collective"/>
</a>
<a href="https://github.com/codex-team/editor.js#sponsors">
<img src="https://opencollective.com/editorjs/sponsors/badge.svg" alt="Sponsors on Open Collective"/>
</a>
</p>
## 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.
## Roadmap
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.
<img align="right" width="342" src="https://user-images.githubusercontent.com/3684889/152149321-3f9c585d-7d87-4dad-ab14-def0d526e71c.png" hspace="50">
- 😍  Modern UI out of the box
- 💎  Clean JSON output
- ⚙  Well-designed API
- 🛍  Various Tools available
- 💌  Free and open source
- Collaborative editing
- [ ] Implement Inline Tools JSON format `In progress` [#1801](https://github.com/codex-team/editor.js/pull/1801)
- [ ] Implement Operations creation and transformations
- [ ] Implement Tools API changes
- [ ] Implement Server and communication
- [ ] Update basic tools to fit the new API
<picture>
<img alt="Editor.js Overview" src="./assets/overview.png">
</picture>
- Unified Toolbox
- [x] Block Tunes moved left [#1815](https://github.com/codex-team/editor.js/pull/1815)
- [x] Toolbox become vertical [#2014](https://github.com/codex-team/editor.js/pull/2014)
- [ ] Ability to display several Toolbox buttons by the single Tool `In progress`
- [ ] Conversion Toolbar uses Unified Toolbox
- [ ] Block Tunes become vertical
- [ ] Conversion Toolbar added to the Block Tunes
- Ecosystem improvements
## Installation
<a href="https://opencollective.com/editorjs/donate" target="_blank">
<img width="432" alt="image" src="https://user-images.githubusercontent.com/3684889/166146666-e6f5fcc3-bccf-43b0-b48c-e8f1b25b2896.png">
</a>
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):
## If you like a project 💗💗💗
If you like Editor.js you can support project improvements and development of new features with a donation to our collective.
👉 [https://opencollective.com/editorjs](https://opencollective.com/editorjs)
### Sponsors
Support us by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/editorjs/contribute/sir-8679/checkout)]
<a href="https://humm.earth/" target="_blank"><img src="https://images.opencollective.com/hummearth/2a8406a/logo/256.png" width="64"></a>
<a href="https://tesen.com/" target="_blank"><img src="https://images.opencollective.com/tesen-media-inc/b90cf6a/logo/256.png" width="64"></a>
<a href="https://slid.cc/" target="_blank"><img src="https://images.opencollective.com/slid_team/ff564d7/logo/256.png" width="64"></a>
### Backers
Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/editorjs/contribute/backer-8632/checkout)]
<a href="https://opencollective.com/editorjs#backers" target="_blank"><img src="https://opencollective.com/editorjs/backers.svg?width=890"></a>
### Contributors
This project exists thanks to all the people who contribute. <img src="https://opencollective.com/editorjs/contributors.svg?width=890&button=false" />
We really welcome new contributors. If you want to make some code with us, please take a look at the [Good First Tasks](https://github.com/codex-team/editor.js/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+task%22). You can write to us on `team@codex.so` or via special [Telegram chat](https://t.me/editorjsdev), or any other way.
## Documentation
Please visit [https://editorjs.io/](https://editorjs.io) to view all documentation articles.
- [Base concepts](https://editorjs.io/base-concepts)
- [Getting started](https://editorjs.io/getting-started)
- [Configuration](https://editorjs.io/configuration)
- [How to create a Block Tool Plugin](https://editorjs.io/creating-a-block-tool)
- [How to create an Inline Tool Plugin](https://editorjs.io/creating-an-inline-tool)
- [API for Tools](https://editorjs.io/tools-api)
You can join a [Gitter-channel](https://gitter.im/codex-team/editor.js) or [Telegram-chat](//t.me/codex_editor) and ask a question.
## Changelog
See the whole [Changelog](/docs/CHANGELOG.md)
## How to use Editor.js
### Basics
Editor.js is a Block-Styled editor. Blocks are structural units, of which the Entry is composed.
For example, `Paragraph`, `Heading`, `Image`, `Video`, `List` are Blocks. Each Block is represented by Plugin.
We have [many](http://github.com/editor-js/) ready-to-use Plugins and a [simple API](https://editorjs.io/tools-api) for creating new ones.
How to use the Editor after [Installation](https://editorjs.io/getting-started).
- Create new Blocks by pressing Enter or clicking the Plus Button
- Press `TAB` or click on the Plus Button to view the Toolbox
- Press `TAB` again to leaf Toolbox and select a Block you need. Then press Enter.
![](https://github.com/editor-js/list/raw/master/assets/example.gif)
- Select a text fragment and apply a style or insert a link from the Inline Toolbar
![](https://capella.pics/7ccbcfcd-1c49-4674-bea7-71021468a1bd.jpg)
- Use the «three-dots» button on the right to open Block Settings. From here, you can move and delete a Block
or apply a Tool's settings, if it provided. For example, you can set a Heading level or List style.
![](https://capella.pics/01a55381-46cd-47c7-b92e-34765434f2ca.jpg)
### Shortcuts
A few shortcuts are preset as available.
Shortcut | Action | Restrictions
-- | -- | --
`TAB` | Show/leaf a Toolbox. | On empty block
`SHIFT+TAB` | Leaf back a Toolbox. | While Toolbox is opened
`ENTER` | Create a Block | While Toolbox is opened and some Tool is selected
`CMD+B` | Bold style | On selection
`CMD+I` | Italic style | On selection
`CMD+K` | Insert a link | On selection
Each Tool can also have its own shortcuts. These are specified in the configuration of the Tool, for example:
```js
var editor = new EditorJS({
//...
tools: {
header: {
class: Header,
shortcut: 'CMD+SHIFT+H'
},
list: {
class: List,
shortcut: 'CMD+SHIFT+L'
}
}
//...
});
```
## Installation Guide
There are few steps to run Editor.js on your site.
1. [Load Editor's core](#load-editors-core)
2. [Load Tools](#load-tools)
3. [Initialize Editor's instance](#create-editor-instance)
### Step 1. Load Editor's core
Get Editor.js itself. It is a [minified script](dist/editor.js) with Editor's core and some default must-have tools.
Choose the most usable method of getting Editor for you.
- Node package
- Source from CDN
##### Option A. NPM install
Install the package via NPM or Yarn
```shell
```bash
npm i @editorjs/editorjs
```
Include module in your application
Choose and install tools:
```javascript
import EditorJS from '@editorjs/editorjs';
```
- [Heading](https://github.com/editor-js/header)
- [Quote](https://github.com/editor-js/quote)
- [Image](https://github.com/editor-js/image)
- [Simple Image](https://github.com/editor-js/simple-image) (without backend requirement)
- [Nested List](https://github.com/editor-js/nested-list)
- [Checklist](https://github.com/editor-js/checklist)
- [Link embed](https://github.com/editor-js/link)
- [Embeds](https://github.com/editor-js/embed) (YouTube, Twitch, Vimeo, Gfycat, Instagram, Twitter, etc)
- [Table](https://github.com/editor-js/table)
- [Delimiter](https://github.com/editor-js/delimiter)
- [Warning](https://github.com/editor-js/warning)
- [Code](https://github.com/editor-js/code)
- [Raw HTML](https://github.com/editor-js/raw)
- [Attaches](https://github.com/editor-js/attaches)
- [Marker](https://github.com/editor-js/marker)
- [Inline Code](https://github.com/editor-js/inline-code)
##### Option B. Use a CDN
See the [😎 Awesome Editor.js](https://github.com/editor-js/awesome-editorjs) list for more tools.
You can load EditorJS directly from from [jsDelivr CDN](https://www.jsdelivr.com/package/npm/@editorjs/editorjs).
`https://cdn.jsdelivr.net/npm/@editorjs/editorjs@latest`
For example, place this in your HTML:
```html
<script src="https://cdn.jsdelivr.net/npm/@editorjs/editorjs@latest"></script>
```
Or download the bundle file and use it from your server.
```html
<script src="editor.js"></script>
```
### Step 2. Load the Tools that you want to make available
Each Block is represented by a [Tool](docs/tools.md). Tools are simple external scripts with their own logic. For example, there is a [Header](https://github.com/editor-js/header) Tool into which you type your heading text. If you want to be able to use this, install the Header Tool the same way as the Editor (Node.js, CDN, local file).
**Example:** use Header from CDN
```html
<script src="https://cdn.jsdelivr.net/npm/codex.editor.header@2.0.4/dist/bundle.js"></script>
```
Check [Editor.js's community](https://github.com/editor-js/) to see more ready-to-use Tools.
### Step 3. Create Editor instance
Create an instance of Editor.js and pass [Configuration Object](types/configs/editor-config.d.ts) with `holderId` and tools list.
Initialize the Editor:
```html
<div id="editorjs"></div>
```
You can create a simple Editor with only default Paragraph Tool by passing a string with element's Id (wrapper for Editor) as a configuration param. Or use the default `editorjs` id for wrapper.
```javascript
var editor = new EditorJS(); /** Zero-configuration */
import EditorJS from '@editorjs/editorjs'
// equals
var editor = new EditorJS('editorjs');
const editor = new EditorJS({
tools: {
// ... your tools
}
})
````
Or pass a whole settings object.
```javascript
var editor = new EditorJS({
/**
* Create a holder for the Editor and pass its ID
*/
holder : 'editorjs',
/**
* Available Tools list.
* Pass Tool's class or Settings object for each Tool you want to use
*/
tools: {
header: {
class: Header,
inlineToolbar : true
},
// ...
},
/**
* Previously saved data that should be rendered
*/
data: {}
});
```
See details about [Installation](https://editorjs.io/getting-started/) and [Configuration](https://editorjs.io/configuration/) at the documentation.
### Saving Data
Call `editor.save()` and handle returned Promise with saved data.
```javascript
editor.save()
.then((savedData) => {
console.log(savedData);
});
const data = await editor.save()
```
### Example
Take a look at the [example.html](example/example.html) to view more detailed examples.
## Credits and references
- We use [HTMLJanitor](https://github.com/guardian/html-janitor) module in our Sanitizer module.
## Roadmap
<img align="right" width="342" src="./assets/roadmap.png" style="margin-left: 30px">
- Unified Toolbox
- [x] Block Tunes moved left
- [x] Toolbox becomes vertical
- [x] Ability to display several Toolbox buttons by the single Tool
- [x] Block Tunes become vertical
- [ ] Block Tunes support nested menus
- [ ] Conversion Toolbar uses Unified Toolbox
- [ ] Conversion Toolbar added to the Block Tunes
- Collaborative editing
- [ ] Implement Inline Tools JSON format
- [ ] Operations Observer, Executor, Manager, Transformer
- [ ] Implement Undo/Redo Manager
- [ ] Implement Tools API changes
- [ ] Implement Server and communication
- [ ] Update basic tools to fit the new API
- Other features
- [ ] Blocks drag'n'drop
- [ ] New cross-block selection
- [ ] New cross-block caret moving
- Ecosystem improvements
- [x] CodeX Icons — the way to unify all tools and core icons
- [x] New Homepage and Docs
- [x] @editorjs/create-tool for Tools bootstrapping
- [ ] Editor.js DevTools — stand for core and tools development
- [ ] Editor.js Design System
- [ ] Editor.js Preset Env
- [ ] Editor.js ToolKit
- [ ] New core bundle system
- [ ] New documentation and guides
<a href="https://opencollective.com/editorjs/donate" target="_blank">
<picture>
<source width="162px" media="(prefers-color-scheme: dark)" srcset="./assets/support_night.png">
<source width="162px" media="(prefers-color-scheme: light)" srcset="./assets/support_day.png">
<img width="162px" alt="Support Editor.js" src="./assets/support_day.png">
</picture>
</a>
<br>
## Like Editor.js?
You can support project improvement and development of new features with a donation to our team.
[Donate via OpenCollective](https://opencollective.com/editorjs)
\
[Donate via Crypto](https://codex.so/donate)
\
[Donate via Patreon](https://www.patreon.com/editorjs)
### Why donate
Donations to open-source products have several advantages for your business:
- If your business relies on Editor.js, you'll probably want it to be maintained
- It helps Editor.js to evolve and get the new features
- We can support contributors and the community around the project. You'll receive well organized docs, guides, etc.
- We need to pay for our infrastructure and maintain public resources (domain names, homepages, docs, etc). Supporting it guarantees you to access any resources at the time you need them.
- You can advertise by adding your brand assets and mentions on our public resources
### Sponsors
Support us by becoming a sponsor. Your logo will show up here with a link to your website.
<p>
<a href="https://www.mister-auto.com/" target="_blank">
<img src="https://opencollective-production.s3.us-west-1.amazonaws.com/5131a030-5672-11ec-be79-1d003d12ec5f.png" width="50" alt="Mister Auto">
</a>
<a href="https://www.uplucid.com/" target="_blank">
<img src="https://logo.clearbit.com/uplucid.com" width="50" alt="UPLUCID, K.K.">
</a>
<a href="https://www.contentharmony.com/" target="_blank">
<img src="https://opencollective-production.s3.us-west-1.amazonaws.com/89edb1b0-7d82-11ed-b99e-ab6e6f9cb69f.png" width="50" alt="Kane Jamison">
</a>
<a href="https://www.contentharmony.com/product/" target="_blank">
<img src="https://logo.clearbit.com/contentharmony.com" width="50" alt="Content Harmony">
</a>
</p>
[Become a Sponsor](https://opencollective.com/editorjs/contribute/sir-8679/checkout)
### Backers
Thank you to all our backers
<a href="https://opencollective.com/editorjs#backers" target="_blank"><img src="https://opencollective.com/editorjs/backers.svg?width=890&avatarHeight=34"></a>
[Become a Backer](https://opencollective.com/editorjs/contribute/backer-8632/checkout)
### Contributors
This project exists thanks to all the people who contribute.
<p><img src="https://opencollective.com/editorjs/contributors.svg?width=890&button=false&avatarHeight=34" /></p>
### Need something special?
Hire CodeX experts to resolve technical challenges and match your product requirements.
- Resolve a problem that has high value for you
- Implement a new feature required by your business
- Help with integration or tool development
- Provide any consultation
Contact us via team@codex.so and share your details
## Community
- [Official Tools](https://github.com/editor-js)
- [Awesome Editor.js](https://github.com/editor-js/awesome-editorjs)
- [Good First Tasks](https://github.com/codex-team/editor.js/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+task%22)
- [Contributing](https://editorjs.io/contributing/)
- [Telegram Chat](https://t.me/codex_editor)
# About CodeX

BIN
assets/logo_day.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

BIN
assets/logo_night.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

BIN
assets/overview.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

BIN
assets/roadmap.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

BIN
assets/support_day.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
assets/support_night.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

35
cypress.config.ts Normal file
View file

@ -0,0 +1,35 @@
import { defineConfig } from 'cypress';
export default defineConfig({
env: {
NODE_ENV: 'test',
},
fixturesFolder: 'test/cypress/fixtures',
screenshotsFolder: 'test/cypress/screenshots',
video: false,
videosFolder: 'test/cypress/videos',
e2e: {
// We've imported your old cypress plugins here.
// You may want to clean this up later by importing these.
setupNodeEvents(on, config) {
on('file:preprocessor', require('cypress-vite')(config));
/**
* Plugin for cypress that adds better terminal output for easier debugging.
* Prints cy commands, browser console logs, cy.request and cy.intercept data. Great for your pipelines.
* https://github.com/archfz/cypress-terminal-report
*/
require('cypress-terminal-report/src/installLogsPrinter')(on);
require('./test/cypress/plugins/index.ts')(on, config);
},
specPattern: 'test/cypress/tests/**/*.cy.{js,jsx,ts,tsx}',
supportFile: 'test/cypress/support/index.ts',
},
'retries': {
// Configure retry attempts for `cypress run`
'runMode': 2,
// Configure retry attempts for `cypress open`
'openMode': 0,
},
});

View file

@ -1,11 +0,0 @@
{
"env": {
"NODE_ENV": "test"
},
"fixturesFolder": "test/cypress/fixtures",
"integrationFolder": "test/cypress/tests",
"screenshotsFolder": "test/cypress/screenshots",
"videosFolder": "test/cypress/videos",
"supportFile": "test/cypress/support/index.ts",
"pluginsFile": "test/cypress/plugins/index.ts"
}

View file

@ -1,5 +1,159 @@
# Changelog
### 2.30.0
- `New` Block Tunes now supports nesting items
- `New` Block Tunes now supports separator items
- `New` "Convert to" control is now also available in Block Tunes
- `Improvement` — The ability to merge blocks of different types (if both tools provide the conversionConfig)
- `Fix``onChange` will be called when removing the entire text within a descendant element of a block.
- `Fix` - Unexpected new line on Enter press with selected block without caret
- `Fix` - Search input autofocus loosing after Block Tunes opening
- `Fix` - Block removing while Enter press on Block Tunes
- `Fix` Unwanted scroll on first typing on iOS devices
- `Fix` - Unwanted soft line break on Enter press after period and space (". |") on iOS devices
- `Fix` - Caret lost after block conversion on mobile devices.
- `Fix` - Caret lost after Backspace at the start of block when previoius block is not convertable
- `Improvement` - The API `blocks.convert()` now returns the new block API
- `Improvement` - The API `caret.setToBlock()` now can accept either BlockAPI or block index or block id
- `New` *Menu Config* New item type HTML
`Refactoring` Switched to Vite as Cypress bundler
`New` *Menu Config* Default and HTML items now support hints
### 2.29.1
- `Fix` — Toolbox wont be shown when Slash pressed with along with Shift or Alt
- `Fix` — Toolbox will be opened when Slash pressed in non-US keyboard layout where there is no physical '/' key.
### 2.29.0
- `New` — Editor Config now has the `style.nonce` attribute that could be used to allowlist editor style tag for Content Security Policy "style-src"
- `New` — Toolbox now will be opened by '/' in empty Block instead of Tab
- `New` — Block Tunes now will be opened by 'CMD+/' instead of Tab in non-empty block
- `New` — Tab now will navigate through Blocks. In last block Tab will navigate to the next input on page.
- `Fix` — Passing an empty array via initial data or `blocks.render()` won't break the editor
- `Fix` — Layout did not shrink when a large document cleared in Chrome
- `Fix` — Multiple Tooltip elements creation fixed
- `Fix` — When the focusing Block is out of the viewport, the page will be scrolled.
- `Fix` - Compiler error "This import is never used as a value and must use 'import type'..." fixed
- `Fix``blocks.render()` won't lead the `onChange` call in Safari
- `Fix` — Editor wrapper element growing on the Inline Toolbar close
- `Fix` — Fix errors thrown by clicks on a document when the editor is being initialized
- `Fix` — Caret losing on Mobile Devices when adding a block via Toolbox or via Backspace at the beginning of a Block
- `Improvement` — Now you can set focus via arrows/Tab to "contentless" (decorative) blocks like Delimiter which have no inputs.
- `Improvement` — Inline Toolbar sometimes opened in an incorrect position. Now it will be aligned by the left side of the selected text. And won't overflow the right side of the text column.
- `Improvement` - Now the `data-mutation-free` supports deep nesting, so you can mark some element with it to prevent the onChange call caused by child element mutating
- `Improvement` - Now the `data-mutation-free` also allows to skip "characterData" mutations (eg. text content change)
- `Refactoring``ce-block--focused` class toggling removed as unused.
### 2.28.2
- `Fix` — Get rid of redundant logs from the build
### 2.28.1
- `Fix` — Some Block were be skipped on saving after pasting them as HTML
### 2.28.0
- `New` - Block ids now displayed in DOM via a data-id attribute. Could be useful for plugins that want to access a Block's element by id.
- `New` - The `blocks.convert(blockId, newType)` API method was added. It allows to convert existing Block to a Block of another type.
- `New` - The `blocks.insertMany()` API method added. It allows to insert several Blocks to the specified index.
- `Improvement` - The Delete keydown at the end of the Block will now work opposite a Backspace at the start. Next Block will be removed (if empty) or merged with the current one.
- `Improvement` - The Delete keydown will work like a Backspace when several Blocks are selected.
- `Improvement` - If we have two empty Blocks, and press Backspace at the start of the second one, the previous will be removed instead of the current.
- `Improvement` - Tools shortcuts could be used to convert one Block to another.
- `Improvement` - Tools shortcuts displayed in the Conversion Toolbar
- `Improvement` - Initialization Loader has been removed.
- `Improvement` - Selection style won't override your custom style for `::selection` outside the editor.
- `Improvement` - Performance optimizations: initialization speed increased, `blocks.render()` API method optimized. Big documents will be displayed faster.
- `Improvement` - "Editor saving" log removed
- `Improvement` - "I'm ready" log removed
- `Improvement` - The stub-block style is simplified.
- `Improvement` - If some Block's tool throws an error during construction, we will show Stub block instead of skipping it during render
- `Improvement` - Call of `blocks.clear()` now will trigger onChange with "block-removed" event for all removed blocks.
- `Improvement` - The `blocks.clear()` now can be awaited.
- `Improvement` - `BlockMutationType` and `BlockMutationEvent` types exported
- `Improvement` - `blocks.update(id, data)` now can accept partial data object — it will update only passed properties, others will remain the same.
- `Improvement` - `blocks.update(id, data)` now will trigger onChange with only `block-change` event.
- `Improvement` - `blocks.update(id, data)` will return a promise with BlockAPI object of the changed block.
### 2.27.2
- `Fix` - `onChange` won't be called when element with data-mutation-free changes some attribute
### 2.27.1
- `Fix` - `onChange` will be called on removing the whole text in a block
### 2.27.0
- `New`*Toolbar API* — Added a new method for toggling the toolbox.
- `New` — Added types for block mutation events
- `New` — Batching added to the `onChange` callback. Now the second argument can contain an array of CustomEvents as well as a single one. Multiple changes made in a short period of time will be batched under a single `onChange` call.
- `Improvement`*Toolbox* — Number of `close()` method calls optimized.
- `Improvement` — The `onChange` callback can be muted if all mutations contain nodes with the `data-mutation-free` attribute.
- `Improvement` — Pressing "Enter" at the end of a Block won't lead to redundant `block-changed` event triggering. Only `block-added` event will be dispatched.
- `Improvement` — The block mutation handler is now called on every block change (including background changes), instead of only when a block is focused
- `Improvement` — Number of caret saving method calls optimized for Block Tunes opening/closing.
- `Improvement` — Package size reduced by removing redundant files.
- `Refactoring` — Switched from Webpack to Vite as the build system.
- `Refactoring`*Dependencies* — Upgraded Cypress to v12 and related libraries to the latest versions.
- `Refactoring`*Dependencies* — Upgraded TypeScript to v5.
- `Refactoring``EventDispatcher` types improved. Now we can pass `EventsMap` via generic to specify a map of event names and their payloads that can be used in a particular EventDispatcher instance.
- `Refactoring` — All events in common editor Event Bus now have own type declarations.
- `Refactoring` — Removed the block mutation observer from blocks and attached a single observer to the editor's blocks wrapper element.
- `Refactoring` — Removed the debounce from the block mutation handler and used batching instead.
- `Refactoring` — Refactored the popover class for better performance and maintenance.
- `Fix` — The `onChange` callback won't trigger when block tunes are opened or closed.
- `Fix` — Resolved a compiler error caused by importing the `BlockToolData` type.
- `Fix` — Resolved a problem where the document would scroll to the beginning after moving a block above the viewport.
- `Fix`- Fixed several bugs caused by browser extensions — Removed the search for a block's container in the DOM on saving and kept it in memory instead, updating it when the tool changes a container element.
- `Fix`*ToolsAPI*`pasteConfig` getter with `false` value could be used to disable paste handling by Editor.js core. Could be useful if your tool has its own paste handler.
- `CI` — Ubuntu container is now used for Edge tests runner.
- `CI` — Node 16 is used for GitHib Actions.
### 2.26.5
- `Fix`*Types* — Remove unnecessary import that creates a dependency on the `cypress`.
### 2.26.4
- `Improvement`*Menu Config* — Property `label` renamed to `title`.
### 2.26.3
- `Fix`*Paste Module* — fix for a problem with specifying of `pasteConfig().tags` in upper case [#2208](https://github.com/codex-team/editor.js/issues/2208).
### 2.26.2
- `Fix`*Menu Config* — Installed tunes are rendered above default tunes again.
### 2.26.1
- `Improvement`*Menu Config* — Now it becomes possible to create toggle groups.
### 2.26.0
- `New`*UI* — Block Tunes became vertical just like the Toolbox 🤩
- `New`*Block Tunes API* — Now `render()` method of a Block Tune can return config with just icon, label and callback instead of custom HTML. This improvement is a key to the new straightforward way of configuring tune's appearance in Block Tunes menu.
- `New`*Tools API* — As well as `render()` in `Tunes API`, Tool's `renderSettings()` now also supports new configuration format.
- `New`*UI* — Meet the new icons from [CodeX Icons](https://github.com/codex-team/icons) pack 🛍 💝
- `New`*BlocksAPI* — the `blocks.insert()` method now also have the optional `id` param. If passed, this id will be used instead of the generated one.
- `Deprecated`*Styles API* — CSS classes `.cdx-settings-button` and `.cdx-settings-button--active` are not recommended to use. Consider configuring your block settings with new JSON API instead.
- `Fix` — Wrong element not highlighted anymore when popover opened.
- `Fix` — When Tunes Menu open keydown events can not be handled inside plugins.
- `Fix` — If a Tool specifies some tags to substitute on paste, all attributes of that tags will be removed before passing them to the tool. Possible XSS vulnerability fixed.
- `Fix` — Pasting from Microsoft Word to Chrome (Mac OS) fixed. Now if there are no image-tools connected, regular text content will be pasted.
- `Fix` — Workaround for the HTMLJanitor bug with Tables (https://github.com/guardian/html-janitor/issues/3) added
- `Fix` — Toolbox shortcuts appearance and execution fixed [#2112](https://github.com/codex-team/editor.js/issues/2112)
- `Fix` — Inline Tools click handling on mobile devices improved
- `Improvement`*Tools API*`pasteConfig().tags` now support sanitizing configuration. It allows you to leave some explicitly specified attributes for pasted content.
- `Improvement`*CodeStyle* — [CodeX ESLint Config](https://github.com/codex-team/eslint-config) has bee updated. All ESLint/Spelling issues resolved
- `Improvement`*ToolsAPI* — The `icon` property of the `toolbox` getter became optional.
### 2.25.0
- `New`*Tools API* — Introducing new feature — toolbox now can have multiple entries for one tool! <br>
@ -8,7 +162,7 @@ Due to that API changes: tool's `toolbox` getter now can return either a single
### 2.24.4
- `Fix` — Keyboard selection by word [2045](https://github.com/codex-team/editor.js/issues/2045)
- `Fix` — Keyboard selection by word [#2045](https://github.com/codex-team/editor.js/issues/2045)
### 2.24.3
@ -102,7 +256,7 @@ Due to that API changes: tool's `toolbox` getter now can return either a single
### 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 sanitisation problem with Inline Tools [#1631](https://github.com/codex-team/editor.js/issues/1631)
- `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.
@ -157,7 +311,7 @@ Due to that API changes: tool's `toolbox` getter now can return either a single
- `New` - Tool's `reset` static method added to the API to clean up any data added by Tool on initialization
- `Improvements` - The `initialBlock` property of Editor config is deprecated. Use the `defaultBlock` instead. [#993](https://github.com/codex-team/editor.js/issues/993)
- `Improvements` - BlockAPI `call()` method now returns the result of calling method, thus allowing it to expose arbitrary data as needed [#1205](https://github.com/codex-team/editor.js/pull/1205)
- `Improvements` - Unuseful log about missed i18n section has been removed [#1269](https://github.com/codex-team/editor.js/issues/1269)
- `Improvements` - Useless log about missed i18n section has been removed [#1269](https://github.com/codex-team/editor.js/issues/1269)
- `Improvements` - Allowed to set `false` as `toolbox` config in order to hide Toolbox button [#1221](https://github.com/codex-team/editor.js/issues/1221)
- `Fix` — Fix problem with types usage [#1183](https://github.com/codex-team/editor.js/issues/1183)
- `Fix` - Fixed issue with Spam clicking the "Click to tune" button duplicates the icons on FireFox. [#1273](https://github.com/codex-team/editor.js/issues/1273)
@ -168,7 +322,7 @@ Due to that API changes: tool's `toolbox` getter now can return either a single
- `Fix` - Fixed issue with enter key in inputs and textareas [#920](https://github.com/codex-team/editor.js/issues/920)
- `Fix` - blocks.getBlockByIndex() API method now returns void for indexes out of range [#1270](https://github.com/codex-team/editor.js/issues/1270)
- `Fix` - Fixed the `Tab` key behavior when the caret is not set inside contenteditable element, but the block is selected [#1302](https://github.com/codex-team/editor.js/issues/1302).
- `Fix` - Fixed the `onChange` callback issue. This method didn't be called for native inputs before some contentedtable element changed [#843](https://github.com/codex-team/editor.js/issues/843)
- `Fix` - Fixed the `onChange` callback issue. This method didn't be called for native inputs before some contenteditable element changed [#843](https://github.com/codex-team/editor.js/issues/843)
- `Fix` - Fixed the `onChange` callback issue. This method didn't be called after the callback throws an exception [#1339](https://github.com/codex-team/editor.js/issues/1339)
- `Fix` - The internal `shortcut` getter of Tools classes will work now.
- `Deprecated` — The Inline Tool `clear()` method is deprecated because the new instance of Inline Tools will be created on every showing of the Inline Toolbar
@ -217,7 +371,7 @@ Due to that API changes: tool's `toolbox` getter now can return either a single
- `Fix` — Fix Firefox bug with incorrect height and cursor position of empty content editable elements [#947](https://github.com/codex-team/editor.js/issues/947) [#876](https://github.com/codex-team/editor.js/issues/876) [#608](https://github.com/codex-team/editor.js/issues/608) [#876](https://github.com/codex-team/editor.js/issues/876)
- `Fix` — Set initial hidden Inline Toolbar position [#979](https://github.com/codex-team/editor.js/issues/979)
- `Fix` — Fix issue with CodeX.Toolips TypeScript definitions [#978](https://github.com/codex-team/editor.js/issues/978)
- `Fix` — Fix issue with CodeX.Tooltips TypeScript definitions [#978](https://github.com/codex-team/editor.js/issues/978)
- `Fix` — Fix some issues with Inline and Tunes toolbars.
- `Fix` - Fix `minHeight` option with zero-value issue [#724](https://github.com/codex-team/editor.js/issues/724)
- `Improvements` — Disable Conversion Toolbar if there are no Tools to convert [#984](https://github.com/codex-team/editor.js/issues/984)
@ -330,7 +484,7 @@ Due to that API changes: tool's `toolbox` getter now can return either a single
### 2.11.5
- `Fix` *RectangeSelection* — Redesign of the scrolling zones
- `Fix` *RectangleSelection* — Redesign of the scrolling zones
### 2.11.4
@ -346,7 +500,7 @@ Due to that API changes: tool's `toolbox` getter now can return either a single
### 2.11.1
- `Fix` *RectangeSelection* — Selection is available only for the main mouse button
- `Fix` *RectangleSelection* — Selection is available only for the main mouse button
### 2.11.0
@ -378,7 +532,7 @@ Due to that API changes: tool's `toolbox` getter now can return either a single
### 2.9.0
- `New` *RectangeSelection* — Ability to select Block or several Blocks with mouse
- `New` *RectangleSelection* — Ability to select Block or several Blocks with mouse
### 2.8.1
@ -386,7 +540,7 @@ Due to that API changes: tool's `toolbox` getter now can return either a single
### 2.8.0
- `Imporvements` *API* — Added [API methods](api.md#caretapi) to manage caret position
- `Improvements` *API* — Added [API methods](api.md#caretapi) to manage caret position
### 2.7.32

View file

@ -180,7 +180,7 @@ this.api.notifier.show({
});
```
![](https://capella.pics/14fcdbe4-d6eb-41d4-b66e-e0e86ccf1a4b.jpg)
![](assets/14fcdbe4-d6eb-41d4-b66e-e0e86ccf1a4b.jpg)
Check out [`codex-notifier` package page](https://github.com/codex-team/js-notifier) on GitHub to find docs, params and examples.
@ -203,8 +203,6 @@ After executing the `destroy` method, editor inctance becomes an empty object. T
Methods for showing Tooltip helper near your elements. Parameters are the same as in [CodeX Tooltips](http://github.com/codex-team/codex.tooltips) lib.
![](https://capella.pics/00e7094a-fdb9-429b-8015-9c56f19b4ef5.jpg)
#### Show
Method shows tooltip with custom content on passed element

Binary file not shown.

After

Width:  |  Height:  |  Size: 328 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

View file

@ -69,7 +69,7 @@ Check [Editor.js's community](https://github.com/editor-js/) to see Tools exampl
## Create Editor instance
Create an instance of Editor.js and pass [Configuration Object](../src/types-internal/editor-config.ts).
At least the `holderId` option is required.
At least the `holder` option is required.
```html
<div id="editorjs"></div>
@ -92,7 +92,7 @@ var editor = new EditorJS({
/**
* Create a holder for the Editor and pass its ID
*/
holderId : 'editorjs',
holder : 'editorjs',
/**
* Available Tools list.

View file

@ -33,7 +33,7 @@ There is a [workflow](.github/workflows/publish-package-to-npm.yml) that fired o
Use target version changelog as a description.
![](https://capella.pics/57267bab-f2f0-411b-a9d1-69abee6abab5.jpg)
![](assets/57267bab-f2f0-411b-a9d1-69abee6abab5.jpg)
Then you can publish the release and wait for package publishing via action.
@ -44,7 +44,7 @@ This package version will be published to NPM with default `latest` tag.
If you want to publish release candidate version, use suffix `-rc.*` for package
version in package.json file and in tag on releases page. Workflow will detect it and mark a release as "pre-release".
![](https://capella.pics/796de9eb-bbe0-485c-bc8f-9a4cb76641b7.jpg)
![](assets/796de9eb-bbe0-485c-bc8f-9a4cb76641b7.jpg)
This package version will be published to NPM with `next` tag.

View file

@ -129,8 +129,6 @@ Read more about Sanitizer configuration at the [Tools#sanitize](tools.md#sanitiz
You can pass your Tool's title via `title` static getter. It can be used, for example, in the Tooltip with
icon description that appears by hover.
![](https://capella.pics/00e7094a-fdb9-429b-8015-9c56f19b4ef5.jpg)
```ts
export default class BoldInlineTool implements InlineTool {
/**

View file

@ -70,7 +70,7 @@ to the `tools` property of Editor Config.
```javascript
var editor = new EditorJS({
holderId : 'editorjs',
holder : 'editorjs',
tools: {
text: {
class: Text,
@ -151,7 +151,7 @@ To handle pasted HTML elements object returned from `pasteConfig` getter should
For correct work you MUST provide `onPaste` handler at least for `defaultBlock` Tool.
> Example
#### Example
Header Tool can handle `H1`-`H6` tags using paste handling API
@ -163,7 +163,27 @@ static get pasteConfig() {
}
```
> Same tag can be handled by one (first specified) Tool only.
**Note. Same tag can be handled by one (first specified) Tool only.**
**Note. All attributes of pasted tag will be removed. To leave some attribute, you should explicitly specify them. Se below**
Let's suppose you want to leave the 'src' attribute when handle pasting of the `img` tags. Your config should look like this:
```javascript
static get pasteConfig() {
return {
tags: [
{
img: {
src: true
}
}
],
}
}
```
[Read more](https://editorjs.io/sanitizer) about the sanitizing configuration.
### RegExp patterns handling
@ -390,7 +410,7 @@ static get sanitize() {
Editor.js has a Conversion Toolbar that allows user to convert one Block to another.
![](https://capella.pics/6c1f708b-a30c-4ffd-a427-5b59a1a472e0.jpg)
![](assets/6c1f708b-a30c-4ffd-a427-5b59a1a472e0.jpg)
1. You can add ability to your Tool to be converted. Specify «export» property of `conversionConfig`.
2. You can add ability to convert other Tools to your Tool. Specify «import» property of `conversionConfig`.

View file

@ -17,12 +17,12 @@ So how to use the Editor after [Installation](installation.md).
- Select text fragment and apply a style or insert a link from the Inline Toolbar
![](https://capella.pics/7ccbcfcd-1c49-4674-bea7-71021468a1bd.jpg)
![](assets/7ccbcfcd-1c49-4674-bea7-71021468a1bd.jpg)
- Use «three-dots» button on the right to open Block Settings. From here, you can move and delete a Block
or apply Tool's settings, if it provided. For example, set a Heading level or List style.
![](https://capella.pics/01a55381-46cd-47c7-b92e-34765434f2ca.jpg)
![](assets/01a55381-46cd-47c7-b92e-34765434f2ca.jpg)
## Shortcuts

View file

@ -10,8 +10,8 @@
<meta charset="UTF-8">
<title>Editor.js 🤩🧦🤨 example</title>
<link href="https://fonts.googleapis.com/css?family=PT+Mono" rel="stylesheet">
<link href="assets/demo.css" rel="stylesheet">
<script src="assets/json-preview.js"></script>
<link href="../public/assets/demo.css" rel="stylesheet">
<script src="../public/assets/json-preview.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
</head>
<body>
@ -89,25 +89,25 @@
Read more in Tool's README file. For example:
https://github.com/editor-js/header#installation
-->
<script src="./tools/header/dist/bundle.js" onload="document.getElementById('hint-tools').hidden = true"></script><!-- Header -->
<script src="./tools/simple-image/dist/bundle.js"></script><!-- Image -->
<script src="./tools/delimiter/dist/bundle.js"></script><!-- Delimiter -->
<!-- <script src="./tools/list/dist/bundle.js"></script> List-->
<script src="./tools/nested-list/dist/nested-list.js"></script><!-- Nested List -->
<script src="./tools/checklist/dist/bundle.js"></script><!-- Checklist -->
<script src="./tools/quote/dist/bundle.js"></script><!-- Quote -->
<script src="./tools/code/dist/bundle.js"></script><!-- Code -->
<script src="./tools/embed/dist/bundle.js"></script><!-- Embed -->
<script src="./tools/table/dist/table.js"></script><!-- Table -->
<script src="./tools/link/dist/bundle.js"></script><!-- Link -->
<script src="./tools/raw/dist/bundle.js"></script><!-- Raw -->
<script src="./tools/warning/dist/bundle.js"></script><!-- Warning -->
<script src="./tools/header/dist/header.umd.js" onload="document.getElementById('hint-tools').hidden = true"></script><!-- Header -->
<script src="./tools/simple-image/dist/simple-image.umd.js"></script><!-- Image -->
<script src="./tools/delimiter/dist/delimiter.umd.js"></script><!-- Delimiter -->
<!-- <script src="./tools/list/dist/list.umd.js"></script> List-->
<script src="./tools/nested-list/dist/nested-list.umd.js"></script><!-- Nested List -->
<script src="./tools/checklist/dist/checklist.umd.js"></script><!-- Checklist -->
<script src="./tools/quote/dist/quote.umd.js"></script><!-- Quote -->
<script src="./tools/code/dist/code.umd.js"></script><!-- Code -->
<script src="./tools/embed/dist/embed.umd.js"></script><!-- Embed -->
<script src="./tools/table/dist/table.umd.js"></script><!-- Table -->
<script src="./tools/link/dist/link.umd.js"></script><!-- Link -->
<script src="./tools/raw/dist/raw.umd.js"></script><!-- Raw -->
<script src="./tools/warning/dist/warning.umd.js"></script><!-- Warning -->
<script src="./tools/marker/dist/bundle.js"></script><!-- Marker -->
<script src="./tools/inline-code/dist/bundle.js"></script><!-- Inline Code -->
<script src="./tools/marker/dist/marker.umd.js"></script><!-- Marker -->
<script src="./tools/inline-code/dist/inline-code.umd.js"></script><!-- Inline Code -->
<!-- Load Editor.js's Core -->
<script src="../dist/editor.js" onload="document.getElementById('hint-core').hidden = true;"></script>
<script src="../dist/editorjs.umd.js" onload="document.getElementById('hint-core').hidden = true;"></script>
<!-- Initialization -->
<script>

View file

@ -10,8 +10,8 @@
<meta charset="UTF-8">
<title>Editor.js 🤩🧦🤨 example</title>
<link href="https://fonts.googleapis.com/css?family=PT+Mono" rel="stylesheet">
<link href="assets/demo.css" rel="stylesheet">
<script src="assets/json-preview.js"></script>
<link href="../public/assets/demo.css" rel="stylesheet">
<script src="../public/assets/json-preview.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
</head>
<body>
@ -61,7 +61,7 @@
<script src="https://cdn.jsdelivr.net/npm/@editorjs/inline-code@latest"></script><!-- Inline Code -->
<!-- Load Editor.js's Core -->
<script src="../dist/editor.js" onload="document.getElementById('hint-core').hidden = true"></script>
<script src="../dist/editorjs.umd.js" onload="document.getElementById('hint-core').hidden = true"></script>
<!-- Initialization -->
<script>
@ -194,9 +194,11 @@
"toolbar": {
"toolbox": {
"Add": "Добавить",
"Filter": "Поиск",
"Nothing found": "Ничего не найдено"
}
},
"popover": {
"Filter": "Поиск",
"Nothing found": "Ничего не найдено"
}
},

View file

@ -9,7 +9,7 @@
<head>
<meta charset="UTF-8">
<title>Editor.js 🤩🧦🤨 example: Multiple instances</title>
<link href="assets/demo.css" rel="stylesheet">
<link href="../public/assets/demo.css" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
</head>
<body>
@ -41,7 +41,7 @@
</div>
<!-- Load Editor.js's Core -->
<script src="../dist/editor.js" onload="document.getElementById('hint-core').hidden = true"></script>
<script src="../dist/editorjs.umd.js" onload="document.getElementById('hint-core').hidden = true"></script>
<script src="./tools/header/dist/bundle.js"></script><!-- Header -->
<!-- Initialization -->

View file

@ -9,7 +9,7 @@
<head>
<meta charset="UTF-8">
<title>Editor.js 🤩🧦🤨 example: Popup</title>
<link href="assets/demo.css" rel="stylesheet">
<link href="../public/assets/demo.css" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
</head>
<body>
@ -111,7 +111,7 @@
</div>
<!-- Load Editor.js's Core -->
<script src="../dist/editor.js" onload="document.getElementById('hint-core').hidden = true"></script>
<script src="../dist/editorjs.umd.js" onload="document.getElementById('hint-core').hidden = true"></script>
<script src="./tools/header/dist/bundle.js"></script><!-- Header -->
<!-- Initialization -->

View file

@ -9,8 +9,8 @@
<meta charset="UTF-8">
<title>Editor.js RTL example</title>
<link href="https://fonts.googleapis.com/css?family=PT+Mono" rel="stylesheet">
<link href="assets/demo.css" rel="stylesheet">
<script src="assets/json-preview.js"></script>
<link href="../public/assets/demo.css" rel="stylesheet">
<script src="../public/assets/json-preview.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
</head>
<body>
@ -70,7 +70,7 @@
<script src="./tools/inline-code/dist/bundle.js"></script><!-- Inline Code -->
<!-- Load Editor.js's Core -->
<script src="../dist/editor.js" onload="document.getElementById('hint-core').hidden = true"></script>
<script src="../dist/editorjs.umd.js" onload="document.getElementById('hint-core').hidden = true"></script>
<!-- Initialization -->
<script>

View file

@ -4,8 +4,8 @@
<meta charset="UTF-8">
<title>Editor.js 🤩🧦🤨 example</title>
<link href="https://fonts.googleapis.com/css?family=PT+Mono" rel="stylesheet">
<link href="assets/demo.css" rel="stylesheet">
<script src="assets/json-preview.js"></script>
<link href="../public/assets/demo.css" rel="stylesheet">
<script src="../public/assets/json-preview.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
</head>
<body>

@ -1 +1 @@
Subproject commit a0aefce6bdc9819fa71649a3d042b15a28a8f344
Subproject commit 1c116d5e09e19951948d6166047aa2f30877aaf9

@ -1 +1 @@
Subproject commit b4f252d112a524e53e1f9c960746a61569eaae28
Subproject commit f281996f82c7ac676172757e45687cae27443427

@ -1 +1 @@
Subproject commit 48bf650eadbb894606f3428d2fb30add022ad2b7
Subproject commit 4ca1c1c972261f47dd34f6b8754763a4a79a4866

@ -1 +1 @@
Subproject commit 35742f01ae5875d442b145121d3c9b71b23aea56
Subproject commit dfdbf2423d2777f7026a7df768c6582e1a409db7

@ -1 +1 @@
Subproject commit 585bca271f7696cd17533fa5877d1f72b3a03d2e
Subproject commit 5118ce87a752515fb6b31325f234f4ccd62f42c9

@ -1 +1 @@
Subproject commit 7edabc571ed4b5975b73073a9eaed857f0f10592
Subproject commit 25d46cd8d3930851b14ddc26ee80fb5b485e1496

@ -1 +1 @@
Subproject commit 3722d145b02880fdd5b5792bdd6a9475284559bc
Subproject commit dcd4c17740c9ba636140751596aff1e9f6ef6b01

@ -1 +1 @@
Subproject commit 0fc365ef256decb8f765fb72b060d5bef9254aa3
Subproject commit aaa69d5408bad34027d6252a3892d40f9fa121be

@ -1 +1 @@
Subproject commit 58b5dc7072ad92a048ebe9488d0ca8e7bfa069d2
Subproject commit a6dc6a692b88c9eff3d87223b239e7517b160c67

@ -1 +1 @@
Subproject commit 40ef7e3adc32dea1762ffe4fa75f90c6ad8af7cc
Subproject commit 8d6897fca43e387bcdf4a681380be975fe8f2a07

@ -1 +1 @@
Subproject commit 9add95389afca0711c05260a92283fae8eb209eb
Subproject commit 95b37462dc93c19b83f0481f509034a40d436cf2

@ -1 +1 @@
Subproject commit d781cd0d5c7529930440c254f0146ce5db63f9f8
Subproject commit 9377ca713f552576b8b11f77cf371b67261ec00b

@ -1 +1 @@
Subproject commit 4f8010dd640d847778220d9b1947fa8235b17f87
Subproject commit cae470fded570ef9a82a45734526ccf45959e204

@ -1 +1 @@
Subproject commit 0b392a90e4001efb048980955b4a72e6b132f6fc
Subproject commit 963883520c7bbe5040366335c9a37bbdc7cf60fd

@ -1 +1 @@
Subproject commit ad0d9012d149e3ca4b41a5ce096b31767cc8c1fd
Subproject commit 2948cd7595e632f7555e2dc09e6bac050a2b87ea

@ -1 +1 @@
Subproject commit d7fa1d81728cb225ccccafefdcd18c81e05918ba
Subproject commit e63e91aa833d774be9bf4a76013b1025a009989d

413
index.html Normal file
View file

@ -0,0 +1,413 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Editor.js 🤩🧦🤨 example</title>
<link href="https://fonts.googleapis.com/css?family=PT+Mono" rel="stylesheet">
<link href="/assets/demo.css" rel="stylesheet">
<script src="/assets/json-preview.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
</head>
<body>
<script>
if (localStorage.getItem('theme') === 'dark') {
document.body.classList.add("dark-mode");
}
</script>
<div class="ce-example">
<div class="ce-example__header">
<a class="ce-example__header-logo" href="https://codex.so/editor">Editor.js 🤩🧦🤨</a>
<div class="ce-example__header-menu">
<a href="https://github.com/editor-js" target="_blank">Plugins</a>
<a href="https://editorjs.io/usage" target="_blank">Usage</a>
<a href="https://editorjs.io/configuration" target="_blank">Configuration</a>
<a href="https://editorjs.io/creating-a-block-tool" target="_blank">API</a>
</div>
</div>
<div class="ce-example__content">
<div id="editorjs"></div>
<div class="ce-example__button" id="saveButton">
editor.save()
</div>
<div class="ce-example__statusbar">
<div class="ce-example__statusbar-item">
Readonly:
<b id="readonly-state">
Off
</b>
&nbsp;
<div class="ce-example__statusbar-button" id="toggleReadOnlyButton">
toggle
</div>
</div>
<div class="ce-example__statusbar-item">
<div class="ce-example__statusbar-button" id="showBlocksBoundariesButton">
<span data-toggled-text="Hide">Show</span>
blocks boundaries
</div>
</div>
<div class="ce-example__statusbar-item">
<div class="ce-example__statusbar-button" id="enableThinModeButton">
<span data-toggled-text="Disable">Enable</span>
thin mode
</div>
</div>
<div class="ce-example__statusbar-item ce-example__statusbar-item--right">
<div class="ce-example__statusbar-toggler" id="darkThemeToggler">
</div>
</div>
</div>
</div>
<div class="ce-example__output">
<pre class="ce-example__output-content" id="output"></pre>
<div class="ce-example__output-footer">
<a href="https://codex.so" style="font-weight: bold;">Made by CodeX</a>
</div>
</div>
</div>
<!-- Load Tools -->
<!--
You can upload Tools to your project's directory and connect them by relative links.
Also you can load each Tool from CDN or use NPM/Yarn packages.
Read more at Tools Connection doc:
https://editorjs.io/getting-started#tools-connection
-->
<script src="https://cdn.jsdelivr.net/npm/@editorjs/header@latest"></script><!-- Header -->
<script src="https://cdn.jsdelivr.net/npm/@editorjs/simple-image@latest"></script><!-- Image -->
<script src="https://cdn.jsdelivr.net/npm/@editorjs/delimiter@latest"></script><!-- Delimiter -->
<script src="https://cdn.jsdelivr.net/npm/@editorjs/nested-list@latest"></script><!-- List -->
<script src="https://cdn.jsdelivr.net/npm/@editorjs/checklist@latest"></script><!-- Checklist -->
<script src="https://cdn.jsdelivr.net/npm/@editorjs/quote@latest"></script><!-- Quote -->
<script src="https://cdn.jsdelivr.net/npm/@editorjs/code@latest"></script><!-- Code -->
<script src="https://cdn.jsdelivr.net/npm/@editorjs/embed@latest"></script><!-- Embed -->
<script src="https://cdn.jsdelivr.net/npm/@editorjs/table@latest"></script><!-- Table -->
<script src="https://cdn.jsdelivr.net/npm/@editorjs/link@latest"></script><!-- Link -->
<script src="https://cdn.jsdelivr.net/npm/@editorjs/warning@latest"></script><!-- Warning -->
<script src="https://cdn.jsdelivr.net/npm/@editorjs/raw@latest"></script><!-- Raw -->
<script src="https://cdn.jsdelivr.net/npm/@editorjs/marker@latest"></script><!-- Marker -->
<script src="https://cdn.jsdelivr.net/npm/@editorjs/inline-code@latest"></script><!-- Inline Code -->
<!-- Initialization -->
<script type="module">
import EditorJS from './src/codex.ts';
window.EditorJS = EditorJS;
/**
* To initialize the Editor, create a new instance with configuration object
* @see docs/installation.md for mode details
*/
const editorConfig = {
/**
* Enable/Disable the read only mode
*/
readOnly: false,
/**
* Wrapper of Editor
*/
holder: 'editorjs',
/**
* Common Inline Toolbar settings
* - if true (or not specified), the order from 'tool' property will be used
* - if an array of tool names, this order will be used
*/
// inlineToolbar: ['link', 'marker', 'bold', 'italic'],
// inlineToolbar: true,
/**
* Tools list
*/
tools: {
/**
* Each Tool is a Plugin. Pass them via 'class' option with necessary settings {@link docs/tools.md}
*/
header: {
class: Header,
inlineToolbar: ['marker', 'link'],
config: {
placeholder: 'Header'
},
shortcut: 'CMD+SHIFT+H'
},
/**
* Or pass class directly without any configuration
*/
image: SimpleImage,
list: {
class: NestedList,
inlineToolbar: true,
shortcut: 'CMD+SHIFT+L'
},
checklist: {
class: Checklist,
inlineToolbar: true,
},
quote: {
class: Quote,
inlineToolbar: true,
config: {
quotePlaceholder: 'Enter a quote',
captionPlaceholder: 'Quote\'s author',
},
shortcut: 'CMD+SHIFT+O'
},
warning: Warning,
marker: {
class: Marker,
shortcut: 'CMD+SHIFT+M'
},
code: {
class: CodeTool,
shortcut: 'CMD+SHIFT+C'
},
delimiter: Delimiter,
inlineCode: {
class: InlineCode,
shortcut: 'CMD+SHIFT+C'
},
linkTool: LinkTool,
raw: RawTool,
embed: Embed,
table: {
class: Table,
inlineToolbar: true,
shortcut: 'CMD+ALT+T'
},
},
/**
* This Tool will be used as default
*/
// defaultBlock: 'paragraph',
/**
* Initial Editor data
*/
data: {
blocks: [
{
id: "zcKCF1S7X8",
type: "header",
data: {
text: "Editor.js",
level: 1
}
},
{
id: "b6ji-DvaKb",
type: "paragraph",
data: {
text: "Hey. Meet the new Editor. On this page you can see it in action — try to edit this text. Source code of the page contains the example of connection and configuration."
}
},
{
type: "header",
id: "7ItVl5biRo",
data: {
text: "Key features",
level: 2
}
},
{
type : 'list',
id: "SSBSguGvP7",
data : {
items : [
{
content: 'It is a block-styled editor',
items: []
},
{
content: 'It returns clean data output in JSON',
items: []
},
{
content: 'Designed to be extendable and pluggable with a simple API',
items: []
}
],
style: 'unordered'
}
},
{
type: "header",
id: "QZFox1m_ul",
data: {
text: "What does it mean «block-styled editor»",
level: 2
}
},
{
type : 'paragraph',
id: "bwnFX5LoX7",
data : {
text : 'Workspace in classic editors is made of a single contenteditable element, used to create different HTML markups. Editor.js <mark class=\"cdx-marker\">workspace consists of separate Blocks: paragraphs, headings, images, lists, quotes, etc</mark>. Each of them is an independent contenteditable element (or more complex structure) provided by Plugin and united by Editor\'s Core.'
}
},
{
type : 'paragraph',
id: "mTrPOHAQTe",
data : {
text : `There are dozens of <a href="https://github.com/editor-js">ready-to-use Blocks</a> and the <a href="https://editorjs.io/creating-a-block-tool">simple API</a> for creation any Block you need. For example, you can implement Blocks for Tweets, Instagram posts, surveys and polls, CTA-buttons and even games.`
}
},
{
type: "header",
id: "1sYMhUrznu",
data: {
text: "What does it mean clean data output",
level: 2
}
},
{
type : 'paragraph',
id: "jpd7WEXrJG",
data : {
text : 'Classic WYSIWYG-editors produce raw HTML-markup with both content data and content appearance. On the contrary, Editor.js outputs JSON object with data of each Block. You can see an example below'
}
},
{
type : 'paragraph',
id: "0lOGNUKxqt",
data : {
text : `Given data can be used as you want: render with HTML for <code class="inline-code">Web clients</code>, render natively for <code class="inline-code">mobile apps</code>, create markup for <code class="inline-code">Facebook Instant Articles</code> or <code class="inline-code">Google AMP</code>, generate an <code class="inline-code">audio version</code> and so on.`
}
},
{
type : 'paragraph',
id: "WvX7kBjp0I",
data : {
text : 'Clean data is useful to sanitize, validate and process on the backend.'
}
},
{
type : 'delimiter',
id: "H9LWKQ3NYd",
data : {}
},
{
type : 'paragraph',
id: "h298akk2Ad",
data : {
text : 'We have been working on this project more than three years. Several large media projects help us to test and debug the Editor, to make its core more stable. At the same time we significantly improved the API. Now, it can be used to create any plugin for any task. Hope you enjoy. 😏'
}
},
{
type: 'image',
id: "9802bjaAA2",
data: {
url: '/assets/codex2x.png',
caption: '',
stretched: false,
withBorder: true,
withBackground: false,
}
},
]
},
onReady: function(){
saveButton.click();
},
onChange: function(api, event) {
console.log('something changed', event);
},
}
/**
* To initialize the Editor, create a new instance with configuration object
* @see docs/installation.md for mode details
*/
var editor = new EditorJS(editorConfig);
/**
* Saving button
*/
const saveButton = document.getElementById('saveButton');
/**
* Toggle read-only button
*/
const toggleReadOnlyButton = document.getElementById('toggleReadOnlyButton');
const readOnlyIndicator = document.getElementById('readonly-state');
/**
* Saving example
*/
saveButton.addEventListener('click', function () {
editor.save()
.then((savedData) => {
cPreview.show(savedData, document.getElementById("output"));
})
.catch((error) => {
console.error('Saving error', error);
});
});
/**
* Toggle read-only example
*/
toggleReadOnlyButton.addEventListener('click', async () => {
const readOnlyState = await editor.readOnly.toggle();
readOnlyIndicator.textContent = readOnlyState ? 'On' : 'Off';
});
/**
* Button for displaying blocks borders. Useful for UI development
*/
const showBlocksBoundariesButton = document.getElementById("showBlocksBoundariesButton");
showBlocksBoundariesButton.addEventListener('click', () => {
document.body.classList.toggle("show-block-boundaries")
})
/**
* Button for enabling the 'Thin' mode
*/
const enableThinModeButton = document.getElementById("enableThinModeButton");
enableThinModeButton.addEventListener('click', () => {
document.body.classList.toggle("thin-mode")
editor.destroy();
editor = new EditorJS(editorConfig);
})
/**
* Toggler for toggling the dark mode
*/
const darkThemeToggler = document.getElementById("darkThemeToggler");
darkThemeToggler.addEventListener('click', () => {
document.body.classList.toggle("dark-mode");
localStorage.setItem('theme', document.body.classList.contains("dark-mode") ? 'dark' : 'default');
})
window.editor = editor;
</script>
</body>
</html>

View file

@ -1,8 +1,9 @@
{
"name": "@editorjs/editorjs",
"version": "2.25.0",
"version": "2.30.0-rc.10",
"description": "Editor.js — Native JS, based on API and Open Source",
"main": "dist/editor.js",
"main": "dist/editorjs.umd.js",
"module": "dist/editorjs.mjs",
"types": "./types/index.d.ts",
"keywords": [
"codex editor",
@ -12,17 +13,13 @@
"editorjs"
],
"scripts": {
"clear": "rimraf dist && mkdirp dist",
"build": "yarn clear && yarn svg && yarn build:webpack:prod",
"build:dev": "yarn clear && yarn svg && yarn build:webpack:dev",
"build:webpack:dev": "webpack --mode development --progress --display-error-details --display-entrypoints --watch",
"build:webpack:prod": "webpack --mode production",
"dev": "vite",
"build": "vite build --mode production",
"build:test": "vite build --mode test",
"lint": "eslint src/ --ext .ts && yarn lint:tests",
"lint:errors": "eslint src/ --ext .ts --quiet",
"lint:fix": "eslint src/ --ext .ts --fix",
"lint:tests": "eslint test/ --ext .ts",
"svg": "svg-sprite-generate -d src/assets/ -o dist/sprite.svg",
"ci:pull_paragraph": "git submodule update --init ./src/tools/paragraph",
"pull_tools": "git submodule update --init --recursive",
"_tools:checkout": "git submodule foreach \"git checkout master || git checkout main\"",
"_tools:pull": "git submodule foreach git pull",
@ -30,8 +27,8 @@
"_tools:build": "git submodule foreach yarn build",
"_tools:make": "yarn _tools:yarn && yarn _tools:build",
"tools:update": "yarn _tools:checkout && yarn _tools:pull && yarn _tools:make",
"test:e2e": "yarn build && cypress run",
"test:e2e:open": "yarn build && cypress open",
"test:e2e": "yarn build:test && cypress run",
"test:e2e:open": "yarn build:test && cypress open",
"devserver:start": "yarn build && node ./devserver.js"
},
"author": "CodeX",
@ -41,65 +38,43 @@
"url": "git+https://github.com/codex-team/editor.js.git"
},
"devDependencies": {
"@babel/core": "^7.9.0",
"@babel/plugin-transform-runtime": "^7.9.0",
"@babel/polyfill": "^7.8.7",
"@babel/preset-env": "^7.9.5",
"@babel/preset-typescript": "^7.13.0",
"@babel/register": "^7.9.0",
"@babel/runtime": "^7.9.2",
"@babel/register": "^7.21.0",
"@codexteam/icons": "^0.3.0",
"@codexteam/shortcuts": "^1.1.1",
"@cypress/code-coverage": "^3.9.2",
"@cypress/webpack-preprocessor": "^5.6.0",
"@cypress/code-coverage": "^3.10.3",
"@editorjs/code": "^2.7.0",
"@editorjs/delimiter": "^1.2.0",
"@editorjs/header": "^2.6.1",
"@editorjs/header": "^2.7.0",
"@editorjs/paragraph": "^2.11.4",
"@editorjs/simple-image": "^1.4.1",
"@types/node": "^14.14.35",
"@types/webpack": "^4.41.12",
"@types/webpack-env": "^1.15.2",
"babel-loader": "^8.1.0",
"babel-plugin-add-module-exports": "^1.0.0",
"babel-plugin-class-display-name": "^2.1.0",
"babel-plugin-istanbul": "^6.0.0",
"core-js": "3.6.5",
"css-loader": "^3.5.3",
"cssnano": "^4.1.10",
"cypress": "^6.8.0",
"cypress-intellij-reporter": "^0.0.6",
"eslint": "^6.8.0",
"eslint-config-codex": "^1.3.3",
"eslint-loader": "^4.0.2",
"eslint-plugin-chai-friendly": "^0.6.0",
"eslint-plugin-cypress": "^2.11.2",
"extract-text-webpack-plugin": "^3.0.2",
"@types/node": "^18.15.11",
"chai-subset": "^1.6.0",
"codex-notifier": "^1.1.2",
"codex-tooltip": "^1.0.5",
"core-js": "3.30.0",
"cypress": "^13.7.1",
"cypress-intellij-reporter": "^0.0.7",
"cypress-plugin-tab": "^1.0.5",
"cypress-terminal-report": "^5.3.2",
"cypress-vite": "^1.5.0",
"eslint": "^8.37.0",
"eslint-config-codex": "^1.7.1",
"eslint-plugin-chai-friendly": "^0.7.2",
"eslint-plugin-cypress": "2.12.1",
"html-janitor": "^2.0.4",
"license-webpack-plugin": "^2.1.4",
"mkdirp": "^1.0.4",
"nanoid": "^4.0.2",
"postcss-apply": "^0.12.0",
"postcss-import": "^12.0.1",
"postcss-loader": "^3.0.0",
"postcss-nested": "^4.1.2",
"postcss-nested-ancestors": "^2.0.0",
"postcss-preset-env": "^6.6.0",
"raw-loader": "^4.0.1",
"rimraf": "^3.0.2",
"stylelint": "^13.3.3",
"svg-sprite-generator": "^0.0.7",
"terser-webpack-plugin": "^2.3.6",
"ts-loader": "^7.0.1",
"postcss-nested": "4.1.2",
"postcss-preset-env": "^8.3.0",
"rollup-plugin-license": "^3.0.1",
"stylelint": "^15.4.0",
"tslint": "^6.1.1",
"typescript": "3.8.3",
"webpack": "^4.43.0",
"webpack-cli": "^3.3.11"
"typescript": "5.0.3",
"vite": "^4.2.1",
"vite-plugin-css-injected-by-js": "^3.1.0"
},
"collective": {
"type": "opencollective",
"url": "https://opencollective.com/editorjs"
},
"dependencies": {
"codex-notifier": "^1.1.2",
"codex-tooltip": "^1.0.5",
"nanoid": "^3.1.22"
}
}

View file

Before

Width:  |  Height:  |  Size: 126 KiB

After

Width:  |  Height:  |  Size: 126 KiB

View file

@ -1,3 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<path transform="matrix(1 0 0 -1 0 14)" d="M8.024 4.1v8.6a1.125 1.125 0 0 1-2.25 0V4.1L2.18 7.695A1.125 1.125 0 1 1 .59 6.104L6.103.588c.44-.439 1.151-.439 1.59 0l5.516 5.516a1.125 1.125 0 0 1-1.59 1.59L8.023 4.1z"/>
</svg>

Before

Width:  |  Height:  |  Size: 353 B

View file

@ -1,3 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<path d="M8.024 4.1v8.6a1.125 1.125 0 0 1-2.25 0V4.1L2.18 7.695A1.125 1.125 0 1 1 .59 6.104L6.103.588c.44-.439 1.151-.439 1.59 0l5.516 5.516a1.125 1.125 0 0 1-1.59 1.59L8.023 4.1z"/>
</svg>

Before

Width:  |  Height:  |  Size: 321 B

View file

@ -1 +0,0 @@
<svg width="12" height="14" viewBox="0 0 12 14" xmlns="http://www.w3.org/2000/svg"><path d="M5.997 14H1.72c-.618 0-1.058-.138-1.323-.415C.132 13.308 0 12.867 0 12.262V1.738C0 1.121.135.676.406.406.676.136 1.114 0 1.719 0h4.536c.669 0 1.248.041 1.738.124.49.083.93.242 1.318.478a3.458 3.458 0 0 1 1.461 1.752c.134.366.2.753.2 1.16 0 1.401-.7 2.426-2.1 3.075 1.84.586 2.76 1.726 2.76 3.42 0 .782-.2 1.487-.602 2.114a3.61 3.61 0 0 1-1.623 1.39 5.772 5.772 0 0 1-1.471.377c-.554.073-1.2.11-1.939.11zm-.21-6.217h-2.95v4.087h3.046c1.916 0 2.874-.69 2.874-2.072 0-.707-.248-1.22-.745-1.537-.496-.319-1.238-.478-2.225-.478zM2.837 2.13v3.619h2.597c.707 0 1.252-.067 1.638-.2.385-.134.68-.389.883-.765.16-.267.239-.566.239-.897 0-.707-.252-1.176-.755-1.409-.503-.232-1.27-.348-2.301-.348H2.836z"/></svg>

Before

Width:  |  Height:  |  Size: 794 B

View file

@ -1,3 +0,0 @@
<svg width="237" height="237" viewBox="0 0 237 237" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<path transform="rotate(45 280.675 51.325)" d="M191 191V73c0-5.523 4.477-10 10-10h25c5.523 0 10 4.477 10 10v118h118c5.523 0 10 4.477 10 10v25c0 5.523-4.477 10-10 10H236v118c0 5.523-4.477 10-10 10h-25c-5.523 0-10-4.477-10-10V236H73c-5.523 0-10-4.477-10-10v-25c0-5.523 4.477-10 10-10h118z"/>
</svg>

Before

Width:  |  Height:  |  Size: 430 B

View file

@ -1,10 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<g transform="translate(4 1.5)" fill-rule="evenodd">
<circle cx="1.3" cy="1.3" r="1.3"/>
<circle cx="6.5" cy="1.3" r="1.3"/>
<circle cx="6.5" cy="6.5" r="1.3"/>
<circle cx="1.3" cy="6.5" r="1.3"/>
<circle cx="6.5" cy="11.7" r="1.3"/>
<circle cx="1.3" cy="11.7" r="1.3"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 372 B

View file

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 4 11">
<path d="M3.289 4.17L2.164 9.713c-.078.384-.238.674-.48.87-.243.198-.52.296-.831.296-.312 0-.545-.1-.699-.302-.153-.202-.192-.49-.116-.864L1.15 4.225c.077-.38.232-.665.466-.857a1.25 1.25 0 01.818-.288c.312 0 .55.096.713.288.163.192.21.46.141.801zm-.667-2.09c-.295 0-.53-.09-.706-.273-.176-.181-.233-.439-.173-.77.055-.302.207-.55.457-.745C2.45.097 2.716 0 3 0c.273 0 .5.088.68.265.179.176.238.434.177.771-.06.327-.21.583-.45.767-.24.185-.502.277-.785.277z"/>
</svg>

Before

Width:  |  Height:  |  Size: 530 B

View file

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 10">
<path d="M6 0v2H5a3 3 0 000 6h1v2H5A5 5 0 115 0h1zm2 0h1a5 5 0 110 10H8V8h1a3 3 0 000-6H8V0zM5 4h4a1 1 0 110 2H5a1 1 0 110-2z"/>
</svg>

Before

Width:  |  Height:  |  Size: 199 B

View file

@ -1,6 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<g transform="translate(1 1.5)" fill-rule="evenodd">
<rect x="6" width="2" height="13" rx="1"/>
<rect x=".5" y="5.5" width="13" height="2" rx="1"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 233 B

View file

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="52" height="52" viewBox="0 0 52 52">
<path fill="#D76B6B" fill-rule="nonzero" d="M26 52C11.64 52 0 40.36 0 26S11.64 0 26 0s26 11.64 26 26-11.64 26-26 26zm0-3.25c12.564 0 22.75-10.186 22.75-22.75S38.564 3.25 26 3.25 3.25 13.436 3.25 26 13.436 48.75 26 48.75zM15.708 33.042a2.167 2.167 0 1 1 0-4.334 2.167 2.167 0 0 1 0 4.334zm23.834 0a2.167 2.167 0 1 1 0-4.334 2.167 2.167 0 0 1 0 4.334zm-15.875 5.452a1.083 1.083 0 1 1-1.834-1.155c1.331-2.114 3.49-3.179 6.334-3.179 2.844 0 5.002 1.065 6.333 3.18a1.083 1.083 0 1 1-1.833 1.154c-.913-1.45-2.366-2.167-4.5-2.167s-3.587.717-4.5 2.167z"/>
</svg>

Before

Width:  |  Height:  |  Size: 643 B

View file

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<path d="M12.711 2.18a7.448 7.448 0 0 1 .79 9.603l2.143 2.144a1.214 1.214 0 1 1-1.717 1.717L11.783 13.5a7.446 7.446 0 1 1 .928-11.32ZM11.39 3.61a5.5 5.5 0 1 0-7.778 7.78 5.5 5.5 0 0 0 7.778-7.78Z" fill-rule="evenodd"/>
</svg>

Before

Width:  |  Height:  |  Size: 289 B

View file

@ -1,3 +0,0 @@
<svg width="13" height="13" xmlns="http://www.w3.org/2000/svg">
<path d="M6.5 9.294a.792.792 0 01-.562-.232L2.233 5.356a.794.794 0 011.123-1.123L6.5 7.377l3.144-3.144a.794.794 0 011.123 1.123L7.062 9.062a.792.792 0 01-.562.232z"/>
</svg>

Before

Width:  |  Height:  |  Size: 240 B

View file

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15 11">
<path d="M13.073 2.099l-1.448 1.448A3 3 0 009 2H8V0h1c1.68 0 3.166.828 4.073 2.099zM6.929 4l-.879.879L7.172 6H5a1 1 0 110-2h1.929zM6 0v2H5a3 3 0 100 6h1v2H5A5 5 0 115 0h1zm6.414 7l2.122 2.121-1.415 1.415L11 8.414l-2.121 2.122L7.464 9.12 9.586 7 7.464 4.879 8.88 3.464 11 5.586l2.121-2.122 1.415 1.415L12.414 7z"/>
</svg>

Before

Width:  |  Height:  |  Size: 384 B

View file

@ -7,21 +7,19 @@ import { EditorConfig } from '../types';
*/
import '@babel/register';
import 'components/polyfills';
import './components/polyfills';
import Core from './components/core';
import * as _ from './components/utils';
import { destroy as destroyTooltip } from './components/utils/tooltip';
declare const VERSION: string;
/**
* Editor.js
*
* Short Description (_눈;)
*
* @version 2.18.0
*
* @license Apache-2.0
* @author CodeX-Team <https://ifmo.su>
* @see Editor.js <https://editorjs.io>
* @author CodeX Team <https://codex.so>
*/
export default class EditorJS {
/**
@ -70,6 +68,9 @@ export default class EditorJS {
*/
this.isReady = editor.isReady.then(() => {
this.exportAPI(editor);
/**
* @todo pass API as an argument. It will allow to use Editor's API when editor is ready
*/
onReady();
});
}
@ -90,6 +91,8 @@ export default class EditorJS {
moduleInstance.listeners.removeAll();
});
destroyTooltip();
editor = null;
for (const field in this) {

View file

@ -3,6 +3,7 @@ import { EditorConfig } from '../../types';
import { ModuleConfig } from '../types-internal/module-config';
import Listeners from './utils/listeners';
import EventsDispatcher from './utils/events';
import { EditorEventMap } from './events';
/**
* The type <T> of the Module generic.
@ -14,12 +15,11 @@ export type ModuleNodes = object;
* @abstract
* @class Module
* @classdesc All modules inherits from this class.
*
* @typedef {Module} Module
* @property {object} config - Editor user settings
* @property {EditorModules} Editor - List of Editor modules
*/
export default class Module<T extends ModuleNodes = {}> {
export default class Module<T extends ModuleNodes = Record<string, HTMLElement>> {
/**
* Each module can provide some UI elements that will be stored in this property
*/
@ -43,7 +43,7 @@ export default class Module<T extends ModuleNodes = {}> {
/**
* Editor event dispatcher class
*/
protected eventsDispatcher: EventsDispatcher;
protected eventsDispatcher: EventsDispatcher<EditorEventMap>;
/**
* Util for bind/unbind DOM event listeners
@ -92,8 +92,9 @@ export default class Module<T extends ModuleNodes = {}> {
/**
* @class
*
* @param {ModuleConfig} - Module config
* @param options - Module options
* @param options.config - Module config
* @param options.eventsDispatcher - Common event bus
*/
constructor({ config, eventsDispatcher }: ModuleConfig) {
if (new.target === Module) {

View file

@ -1,11 +1,11 @@
/**
* @class DeleteTune
* @classdesc Editor's default tune that moves up selected block
*
* @copyright <CodeX Team> 2018
*/
import { API, BlockTune } from '../../../types';
import $ from '../dom';
import { IconCross } from '@codexteam/icons';
import { TunesMenuConfig } from '../../../types/tools';
/**
*
@ -23,32 +23,6 @@ export default class DeleteTune implements BlockTune {
*/
private readonly api: API;
/**
* Styles
*/
private CSS = {
button: 'ce-settings__button',
buttonDelete: 'ce-settings__button--delete',
buttonConfirm: 'ce-settings__button--confirm',
};
/**
* Delete confirmation
*/
private needConfirmation: boolean;
/**
* set false confirmation state
*/
private readonly resetConfirmation: () => void;
/**
* Tune nodes
*/
private nodes: {button: HTMLElement} = {
button: null,
};
/**
* DeleteTune constructor
*
@ -56,75 +30,27 @@ export default class DeleteTune implements BlockTune {
*/
constructor({ api }) {
this.api = api;
}
this.resetConfirmation = (): void => {
this.setConfirmation(false);
/**
* Tune's appearance in block settings menu
*/
public render(): TunesMenuConfig {
return {
icon: IconCross,
title: this.api.i18n.t('Delete'),
name: 'delete',
confirmation: {
title: this.api.i18n.t('Click to delete'),
onActivate: (): void => this.handleClick(),
},
};
}
/**
* Create "Delete" button and add click event listener
*
* @returns {HTMLElement}
*/
public render(): HTMLElement {
this.nodes.button = $.make('div', [this.CSS.button, this.CSS.buttonDelete], {});
this.nodes.button.appendChild($.svg('cross', 12, 12));
this.api.listeners.on(this.nodes.button, 'click', (event: MouseEvent) => this.handleClick(event), false);
/**
* Enable tooltip module
*/
this.api.tooltip.onHover(this.nodes.button, this.api.i18n.t('Delete'), {
hidingDelay: 300,
});
return this.nodes.button;
}
/**
* Delete block conditions passed
*
* @param {MouseEvent} event - click event
*/
public handleClick(event: MouseEvent): void {
/**
* if block is not waiting the confirmation, subscribe on block-settings-closing event to reset
* otherwise delete block
*/
if (!this.needConfirmation) {
this.setConfirmation(true);
/**
* Subscribe on event.
* When toolbar block settings is closed but block deletion is not confirmed,
* then reset confirmation state
*/
this.api.events.on('block-settings-closed', this.resetConfirmation);
} else {
/**
* Unsubscribe from block-settings closing event
*/
this.api.events.off('block-settings-closed', this.resetConfirmation);
this.api.blocks.delete();
this.api.toolbar.close();
this.api.tooltip.hide();
/**
* Prevent firing ui~documentClicked that can drop currentBlock pointer
*/
event.stopPropagation();
}
}
/**
* change tune state
*
* @param {boolean} state - delete confirmation state
*/
private setConfirmation(state: boolean): void {
this.needConfirmation = state;
this.nodes.button.classList.add(this.CSS.buttonConfirm);
public handleClick(): void {
this.api.blocks.delete();
}
}

View file

@ -1,12 +1,13 @@
/**
* @class MoveDownTune
* @classdesc Editor's default tune - Moves down highlighted block
*
* @copyright <CodeX Team> 2018
*/
import $ from '../dom';
import { API, BlockTune } from '../../../types';
import { IconChevronDown } from '@codexteam/icons';
import { TunesMenuConfig } from '../../../types/tools';
/**
*
@ -26,12 +27,8 @@ export default class MoveDownTune implements BlockTune {
/**
* Styles
*
* @type {{wrapper: string}}
*/
private CSS = {
button: 'ce-settings__button',
wrapper: 'ce-tune-move-down',
animation: 'wobble',
};
@ -45,50 +42,27 @@ export default class MoveDownTune implements BlockTune {
}
/**
* Return 'move down' button
*
* @returns {HTMLElement}
* Tune's appearance in block settings menu
*/
public render(): HTMLElement {
const moveDownButton = $.make('div', [this.CSS.button, this.CSS.wrapper], {});
moveDownButton.appendChild($.svg('arrow-down', 14, 14));
this.api.listeners.on(
moveDownButton,
'click',
(event) => this.handleClick(event as MouseEvent, moveDownButton),
false
);
/**
* Enable tooltip module on button
*/
this.api.tooltip.onHover(moveDownButton, this.api.i18n.t('Move down'), {
hidingDelay: 300,
});
return moveDownButton;
public render(): TunesMenuConfig {
return {
icon: IconChevronDown,
title: this.api.i18n.t('Move down'),
onActivate: (): void => this.handleClick(),
name: 'move-down',
};
}
/**
* Handle clicks on 'move down' button
*
* @param {MouseEvent} event - click event
* @param {HTMLElement} button - clicked button
*/
public handleClick(event: MouseEvent, button: HTMLElement): void {
public handleClick(): void {
const currentBlockIndex = this.api.blocks.getCurrentBlockIndex();
const nextBlock = this.api.blocks.getBlockByIndex(currentBlockIndex + 1);
// If Block is last do nothing
if (!nextBlock) {
button.classList.add(this.CSS.animation);
window.setTimeout(() => {
button.classList.remove(this.CSS.animation);
}, 500);
return;
throw new Error('Unable to move Block down since it is already the last');
}
const nextBlockElement = nextBlock.holder;
@ -110,8 +84,5 @@ export default class MoveDownTune implements BlockTune {
this.api.blocks.move(currentBlockIndex + 1);
this.api.toolbar.toggleBlockSettings(true);
/** Hide the Tooltip */
this.api.tooltip.hide();
}
}

View file

@ -1,11 +1,11 @@
/**
* @class MoveUpTune
* @classdesc Editor's default tune that moves up selected block
*
* @copyright <CodeX Team> 2018
*/
import $ from '../dom';
import { API, BlockTune } from '../../../types';
import { IconChevronUp } from '@codexteam/icons';
import { TunesMenuConfig } from '../../../types/tools';
/**
*
@ -25,12 +25,8 @@ export default class MoveUpTune implements BlockTune {
/**
* Styles
*
* @type {{wrapper: string}}
*/
private CSS = {
button: 'ce-settings__button',
wrapper: 'ce-tune-move-up',
animation: 'wobble',
};
@ -44,50 +40,27 @@ export default class MoveUpTune implements BlockTune {
}
/**
* Create "MoveUp" button and add click event listener
*
* @returns {HTMLElement}
* Tune's appearance in block settings menu
*/
public render(): HTMLElement {
const moveUpButton = $.make('div', [this.CSS.button, this.CSS.wrapper], {});
moveUpButton.appendChild($.svg('arrow-up', 14, 14));
this.api.listeners.on(
moveUpButton,
'click',
(event) => this.handleClick(event as MouseEvent, moveUpButton),
false
);
/**
* Enable tooltip module on button
*/
this.api.tooltip.onHover(moveUpButton, this.api.i18n.t('Move up'), {
hidingDelay: 300,
});
return moveUpButton;
public render(): TunesMenuConfig {
return {
icon: IconChevronUp,
title: this.api.i18n.t('Move up'),
onActivate: (): void => this.handleClick(),
name: 'move-up',
};
}
/**
* Move current block up
*
* @param {MouseEvent} event - click event
* @param {HTMLElement} button - clicked button
*/
public handleClick(event: MouseEvent, button: HTMLElement): void {
public handleClick(): void {
const currentBlockIndex = this.api.blocks.getCurrentBlockIndex();
const currentBlock = this.api.blocks.getBlockByIndex(currentBlockIndex);
const previousBlock = this.api.blocks.getBlockByIndex(currentBlockIndex - 1);
if (currentBlockIndex === 0 || !currentBlock || !previousBlock) {
button.classList.add(this.CSS.animation);
window.setTimeout(() => {
button.classList.remove(this.CSS.animation);
}, 500);
return;
throw new Error('Unable to move Block up since it is already the first');
}
const currentBlockElement = currentBlock.holder;
@ -109,7 +82,7 @@ export default class MoveUpTune implements BlockTune {
if (previousBlockCoords.top > 0) {
scrollUpOffset = Math.abs(currentBlockCoords.top) - Math.abs(previousBlockCoords.top);
} else {
scrollUpOffset = window.innerHeight - Math.abs(currentBlockCoords.top) + Math.abs(previousBlockCoords.top);
scrollUpOffset = Math.abs(currentBlockCoords.top) + previousBlockCoords.height;
}
window.scrollBy(0, -1 * scrollUpOffset);
@ -118,8 +91,5 @@ export default class MoveUpTune implements BlockTune {
this.api.blocks.move(currentBlockIndex - 1);
this.api.toolbar.toggleBlockSettings(true);
/** Hide the Tooltip */
this.api.tooltip.hide();
}
}

View file

@ -7,7 +7,6 @@ import { BlockAPI as BlockAPIInterface } from '../../../types/api';
* Constructs new BlockAPI object
*
* @class
*
* @param {Block} block - Block to expose
*/
function BlockAPI(
@ -85,12 +84,18 @@ function BlockAPI(
return block.stretched;
},
/**
* True if Block has inputs to be focused
*/
get focusable(): boolean {
return block.focusable;
},
/**
* Call Tool method with errors handler under-the-hood
*
* @param {string} methodName - method to call
* @param {object} param - object with parameters
*
* @returns {unknown}
*/
call(methodName: string, param?: object): unknown {
@ -110,7 +115,6 @@ function BlockAPI(
* Validate Block data
*
* @param {BlockToolData} data - data to validate
*
* @returns {Promise<boolean>}
*/
validate(data: BlockToolData): Promise<boolean> {

View file

@ -5,7 +5,8 @@ import {
BlockTune as IBlockTune,
SanitizerConfig,
ToolConfig,
ToolboxConfigEntry
ToolboxConfigEntry,
PopoverItemParams
} from '../../../types';
import { SavedData } from '../../../types/data-formats';
@ -20,6 +21,12 @@ import BlockTune from '../tools/tune';
import { BlockTuneData } from '../../../types/block-tunes/block-tune-data';
import ToolsCollection from '../tools/collection';
import EventsDispatcher from '../utils/events';
import { TunesMenuConfigItem } from '../../../types/tools';
import { isMutationBelongsToElement } from '../utils/mutations';
import { EditorEventMap, FakeCursorAboutToBeToggled, FakeCursorHaveBeenSet, RedactorDomChanged } from '../events';
import { RedactorDomChangedPayload } from '../events/RedactorDomChanged';
import { convertBlockDataToString, isSameBlockData } from '../utils/blocks';
import { PopoverItemType } from '../utils/popover';
/**
* Interface describes Block class constructor argument
@ -59,10 +66,8 @@ interface BlockConstructorOptions {
/**
* @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
*
*/
/**
@ -73,22 +78,25 @@ 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 supported by Block class
* Names of events used in Block
*/
type BlockEvents = 'didMutated';
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
@ -104,7 +112,6 @@ export default class Block extends EventsDispatcher<BlockEvents> {
wrapper: 'ce-block',
wrapperStretched: 'ce-block--stretched',
content: 'ce-block__content',
focused: 'ce-block--focused',
selected: 'ce-block--selected',
dropTarget: 'ce-block--drop-target',
};
@ -152,6 +159,11 @@ export default class Block extends EventsDispatcher<BlockEvents> {
*/
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
*/
@ -186,53 +198,14 @@ export default class Block extends EventsDispatcher<BlockEvents> {
private inputIndex = 0;
/**
* Mutation observer to handle DOM mutations
*
* @type {MutationObserver}
* Common editor event bus
*/
private mutationObserver: MutationObserver;
private readonly editorEventBus: EventsDispatcher<EditorEventMap> | null = null;
/**
* Debounce Timer
*
* @type {number}
* Link to editor dom change callback. Used to remove listener on remove
*/
private readonly modificationDebounceTimer = 450;
/**
* Is fired when DOM mutation has been happened
*/
private didMutated = _.debounce((mutationsOrInputEvent: MutationRecord[] | InputEvent = []): void => {
const shouldFireUpdate = mutationsOrInputEvent instanceof InputEvent ||
!mutationsOrInputEvent.some(({
addedNodes = [],
removedNodes,
}) => {
return [...Array.from(addedNodes), ...Array.from(removedNodes)]
.some(node => $.isElement(node) && (node as HTMLElement).dataset.mutationFree === 'true');
});
/**
* In case some mutation free elements are added or removed, do not trigger didMutated event
*/
if (!shouldFireUpdate) {
return;
}
/**
* Drop cache
*/
this.cachedInputs = [];
/**
* Update current input
*/
this.updateCurrentInput();
this.call(BlockToolAPI.UPDATED);
this.emit('didMutated', this);
}, this.modificationDebounceTimer);
private redactorDomChangedCallback: (payload: RedactorDomChangedPayload) => void;
/**
* Current block API interface
@ -240,12 +213,13 @@ export default class Block extends EventsDispatcher<BlockEvents> {
private readonly blockAPI: BlockAPIInterface;
/**
* @param {object} options - block constructor options
* @param {string} [options.id] - block's id. Will be generated if omitted.
* @param {BlockToolData} options.data - Tool's initial data
* @param {BlockToolConstructable} options.tool block's tool
* @param options - block constructor options
* @param [options.id] - block's id. Will be generated if omitted.
* @param options.data - Tool's initial data
* @param options.tool block's tool
* @param options.api - Editor API module for pass it to the Block Tunes
* @param {boolean} options.readOnly - Read-Only flag
* @param options.readOnly - Read-Only flag
* @param [eventBus] - Editor common event bus. Allows to subscribe on some Editor events. Could be omitted when "virtual" Block is created. See BlocksAPI@composeBlockData.
*/
constructor({
id = _.generateBlockId(),
@ -254,18 +228,16 @@ export default class Block extends EventsDispatcher<BlockEvents> {
api,
readOnly,
tunesData,
}: BlockConstructorOptions) {
}: BlockConstructorOptions, eventBus?: EventsDispatcher<EditorEventMap>) {
super();
this.name = tool.name;
this.id = id;
this.settings = tool.settings;
this.config = tool.settings.config || {};
this.api = api;
this.editorEventBus = eventBus || null;
this.blockAPI = new BlockAPI(this);
this.mutationObserver = new MutationObserver(this.didMutated);
this.tool = tool;
this.toolInstance = tool.create(data, this.blockAPI, readOnly);
@ -277,10 +249,26 @@ export default class Block extends EventsDispatcher<BlockEvents> {
this.composeTunes(tunesData);
this.holder = this.compose();
/**
* Bind block events in RIC for optimizing of constructing process time
*/
window.requestIdleCallback(() => {
/**
* Start watching block mutations
*/
this.watchBlockMutations();
/**
* Mutation observer doesn't track changes in "<input>" and "<textarea>"
* so we need to track focus events to update current input and clear cache.
*/
this.addInputEvents();
});
}
/**
* Find and return all editable elements (contenteditables and native inputs) in the Tool HTML
* Find and return all editable elements (contenteditable and native inputs) in the Tool HTML
*
* @returns {HTMLElement[]}
*/
@ -395,7 +383,7 @@ export default class Block extends EventsDispatcher<BlockEvents> {
/**
* is block mergeable
* We plugin have merge function then we call it mergable
* We plugin have merge function then we call it mergeable
*
* @returns {boolean}
*/
@ -403,20 +391,27 @@ export default class Block extends EventsDispatcher<BlockEvents> {
return _.isFunction(this.toolInstance.merge);
}
/**
* If Block contains inputs, it is focusable
*/
public get focusable(): boolean {
return this.inputs.length !== 0;
}
/**
* Check block for emptiness
*
* @returns {boolean}
*/
public get isEmpty(): boolean {
const emptyText = $.isEmpty(this.pluginsContent);
const emptyText = $.isEmpty(this.pluginsContent, '/');
const emptyMedia = !this.hasMedia;
return emptyText && emptyMedia;
}
/**
* Check if block has a media content such as images, iframes and other
* Check if block has a media content such as images, iframe and other
*
* @returns {boolean}
*/
@ -440,22 +435,6 @@ export default class Block extends EventsDispatcher<BlockEvents> {
return !!this.holder.querySelector(mediaTags.join(','));
}
/**
* Set focused state
*
* @param {boolean} state - 'true' to select, 'false' to remove selection
*/
public set focused(state: boolean) {
this.holder.classList.toggle(Block.CSS.focused, state);
}
/**
* Get Block's focused state
*/
public get focused(): boolean {
return this.holder.classList.contains(Block.CSS.focused);
}
/**
* Set selected state
* We don't need to mark Block as Selected when it is empty
@ -463,14 +442,21 @@ export default class Block extends EventsDispatcher<BlockEvents> {
* @param {boolean} state - 'true' to select, 'false' to remove selection
*/
public set selected(state: boolean) {
if (state) {
this.holder.classList.add(Block.CSS.selected);
this.holder.classList.toggle(Block.CSS.selected, state);
SelectionUtils.addFakeCursor(this.holder);
} else {
this.holder.classList.remove(Block.CSS.selected);
const fakeCursorWillBeAdded = state === true && SelectionUtils.isRangeInsideContainer(this.holder);
const fakeCursorWillBeRemoved = state === false && SelectionUtils.isFakeCursorInsideContainer(this.holder);
SelectionUtils.removeFakeCursor(this.holder);
if (fakeCursorWillBeAdded || fakeCursorWillBeRemoved) {
this.editorEventBus?.emit(FakeCursorAboutToBeToggled, { state }); // mutex
if (fakeCursorWillBeAdded) {
SelectionUtils.addFakeCursor();
} else {
SelectionUtils.removeFakeCursor(this.holder);
}
this.editorEventBus?.emit(FakeCursorHaveBeenSet, { state });
}
}
@ -486,7 +472,7 @@ export default class Block extends EventsDispatcher<BlockEvents> {
/**
* Set stretched state
*
* @param {boolean} state - 'true' to enable, 'false' to disable stretched statte
* @param {boolean} state - 'true' to enable, 'false' to disable stretched state
*/
public set stretched(state: boolean) {
this.holder.classList.toggle(Block.CSS.wrapperStretched, state);
@ -516,23 +502,7 @@ export default class Block extends EventsDispatcher<BlockEvents> {
* @returns {HTMLElement}
*/
public get pluginsContent(): HTMLElement {
const blockContentNodes = this.holder.querySelector(`.${Block.CSS.content}`);
if (blockContentNodes && blockContentNodes.childNodes.length) {
/**
* Editors Block content can contain different Nodes from extensions
* We use DOM isExtensionNode to ignore such Nodes and return first Block that does not match filtering list
*/
for (let child = blockContentNodes.childNodes.length - 1; child >= 0; child--) {
const contentNode = blockContentNodes.childNodes[child];
if (!$.isExtensionNode(contentNode)) {
return contentNode as HTMLElement;
}
}
}
return null;
return this.toolRenderedElement;
}
/**
@ -580,7 +550,7 @@ export default class Block extends EventsDispatcher<BlockEvents> {
*
* @returns {object}
*/
public async save(): Promise<void | SavedData> {
public async save(): Promise<undefined | SavedData> {
const extractedBlock = await this.toolInstance.save(this.pluginsContent as HTMLElement);
const tunesData: { [name: string]: BlockTuneData } = this.unavailableTunesData;
@ -618,7 +588,7 @@ export default class Block extends EventsDispatcher<BlockEvents> {
};
})
.catch((error) => {
_.log(`Saving proccess for ${this.name} tool failed due to the ${error}`, 'log', 'red');
_.log(`Saving process for ${this.name} tool failed due to the ${error}`, 'log', 'red');
});
}
@ -627,7 +597,6 @@ export default class Block extends EventsDispatcher<BlockEvents> {
* Tool's validation method is optional
*
* @description Method returns true|false whether data passed the validation or not
*
* @param {BlockToolData} data - data to validate
* @returns {Promise<boolean>} valid
*/
@ -642,22 +611,54 @@ export default class Block extends EventsDispatcher<BlockEvents> {
}
/**
* Enumerates initialized tunes and returns fragment that can be appended to the toolbars area
*
* @returns {DocumentFragment[]}
* Returns data to render in Block Tunes menu.
* Splits block tunes into 2 groups: block specific tunes and common tunes
*/
public renderTunes(): [DocumentFragment, DocumentFragment] {
const tunesElement = document.createDocumentFragment();
const defaultTunesElement = document.createDocumentFragment();
public getTunes(): {
toolTunes: PopoverItemParams[];
commonTunes: PopoverItemParams[];
} {
const toolTunesPopoverParams: TunesMenuConfigItem[] = [];
const commonTunesPopoverParams: TunesMenuConfigItem[] = [];
this.tunesInstances.forEach((tune) => {
$.append(tunesElement, tune.render());
});
this.defaultTunesInstances.forEach((tune) => {
$.append(defaultTunesElement, tune.render());
/** Tool's tunes: may be defined as return value of optional renderSettings method */
const tunesDefinedInTool = typeof this.toolInstance.renderSettings === 'function' ? this.toolInstance.renderSettings() : [];
if ($.isElement(tunesDefinedInTool)) {
toolTunesPopoverParams.push({
type: PopoverItemType.Html,
element: tunesDefinedInTool,
});
} else if (Array.isArray(tunesDefinedInTool)) {
toolTunesPopoverParams.push(...tunesDefinedInTool);
} else {
toolTunesPopoverParams.push(tunesDefinedInTool);
}
/** Common tunes: combination of default tunes (move up, move down, delete) and third-party tunes connected via tunes api */
const commonTunes = [
...this.tunesInstances.values(),
...this.defaultTunesInstances.values(),
].map(tuneInstance => tuneInstance.render());
/** Separate custom html from Popover items params for common tunes */
commonTunes.forEach(tuneConfig => {
if ($.isElement(tuneConfig)) {
commonTunesPopoverParams.push({
type: PopoverItemType.Html,
element: tuneConfig,
});
} else if (Array.isArray(tuneConfig)) {
commonTunesPopoverParams.push(...tuneConfig);
} else {
commonTunesPopoverParams.push(tuneConfig);
}
});
return [tunesElement, defaultTunesElement];
return {
toolTunes: toolTunesPopoverParams,
commonTunes: commonTunesPopoverParams,
};
}
/**
@ -675,38 +676,6 @@ export default class Block extends EventsDispatcher<BlockEvents> {
: SelectionUtils.anchorNode;
}
/**
* Is fired when Block will be selected as current
*/
public willSelect(): void {
/**
* Observe DOM mutations to update Block inputs
*/
this.mutationObserver.observe(
this.holder.firstElementChild,
{
childList: true,
subtree: true,
characterData: true,
attributes: true,
}
);
/**
* Mutation observer doesn't track changes in "<input>" and "<textarea>"
* so we need to track focus events to update current input and clear cache.
*/
this.addInputEvents();
}
/**
* Is fired when Block will be unselected
*/
public willUnselect(): void {
this.mutationObserver.disconnect();
this.removeInputEvents();
}
/**
* 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.
@ -719,6 +688,9 @@ export default class Block extends EventsDispatcher<BlockEvents> {
* Call Tool instance destroy method
*/
public destroy(): void {
this.unwatchBlockMutations();
this.removeInputEvents();
super.destroy();
if (_.isFunction(this.toolInstance.destroy)) {
@ -726,15 +698,6 @@ export default class Block extends EventsDispatcher<BlockEvents> {
}
}
/**
* Call Tool instance renderSettings method
*/
public renderSettings(): HTMLElement | undefined {
if (_.isFunction(this.toolInstance.renderSettings)) {
return this.toolInstance.renderSettings();
}
}
/**
* 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)
@ -769,14 +732,20 @@ export default class Block extends EventsDispatcher<BlockEvents> {
const blockData = await this.data;
const toolboxItems = toolboxSettings;
return toolboxItems.find((item) => {
return Object.entries(item.data)
.some(([propName, propValue]) => {
return blockData[propName] && _.equals(blockData[propName], propValue);
});
return toolboxItems?.find((item) => {
return isSameBlockData(item.data, blockData);
});
}
/**
* Exports Block data as string using conversion config
*/
public async exportDataAsString(): Promise<string> {
const blockData = await this.data;
return convertBlockDataToString(blockData, this.tool.conversionConfig);
}
/**
* Make default Block wrappers and put Tool`s content there
*
@ -787,7 +756,22 @@ export default class Block extends EventsDispatcher<BlockEvents> {
contentNode = $.make('div', Block.CSS.content),
pluginsContent = this.toolInstance.render();
contentNode.appendChild(pluginsContent);
if (import.meta.env.MODE === 'test') {
wrapper.setAttribute('data-cy', 'block-wrapper');
}
/**
* Export id to the DOM three
* Useful for standalone modules development. For example, allows to identify Block by some child node. Or scroll to a particular Block by id.
*/
wrapper.dataset.id = this.id;
/**
* Saving a reference to plugin's content element for guaranteed accessing it later
*/
this.toolRenderedElement = pluginsContent;
contentNode.appendChild(this.toolRenderedElement);
/**
* Block Tunes might wrap Block's content node to provide any UI changes
@ -844,18 +828,18 @@ export default class Block extends EventsDispatcher<BlockEvents> {
*/
private handleFocus = (): void => {
/**
* Drop cache
* Drop inputs cache to query the new ones
*/
this.cachedInputs = [];
this.dropInputsCache();
/**
* Update current input
*/
this.updateCurrentInput();
}
};
/**
* Adds focus event listeners to all inputs and contentEditables
* Adds focus event listeners to all inputs and contenteditable
*/
private addInputEvents(): void {
this.inputs.forEach(input => {
@ -865,21 +849,164 @@ export default class Block extends EventsDispatcher<BlockEvents> {
* If input is native input add oninput listener to observe changes
*/
if ($.isNativeInput(input)) {
input.addEventListener('input', this.didMutated);
input.addEventListener('input', this.didMutated as EventListener);
}
});
}
/**
* removes focus event listeners from all inputs and contentEditables
* removes focus event listeners from all inputs and contenteditable
*/
private removeInputEvents(): void {
this.inputs.forEach(input => {
input.removeEventListener('focus', this.handleFocus);
if ($.isNativeInput(input)) {
input.removeEventListener('input', this.didMutated);
input.removeEventListener('input', this.didMutated as EventListener);
}
});
}
/**
* Is fired when DOM mutation has been happened
*
* @param mutationsOrInputEvent - actual changes
* - MutationRecord[] - any DOM change
* - InputEvent <input> change
* - undefined manual triggering of block.dispatchChange()
*/
private readonly didMutated = (mutationsOrInputEvent: MutationRecord[] | InputEvent = undefined): void => {
/**
* Block API have dispatchChange() method. In this case, mutations list will be undefined.
*/
const isManuallyDispatched = mutationsOrInputEvent === undefined;
/**
* True if didMutated has been called as "input" event handler
*/
const isInputEventHandler = mutationsOrInputEvent instanceof InputEvent;
/**
* If tool updates its own root element, we need to renew it in our memory
*/
if (!isManuallyDispatched && !isInputEventHandler) {
this.detectToolRootChange(mutationsOrInputEvent);
}
/**
* We won't fire a Block mutation event if mutation contain only nodes marked with 'data-mutation-free' attributes
*/
let shouldFireUpdate;
if (isManuallyDispatched) {
shouldFireUpdate = true;
} else if (isInputEventHandler) {
shouldFireUpdate = true;
} else {
/**
* Update from 2023, Feb 17:
* Changed mutationsOrInputEvent.some() to mutationsOrInputEvent.every()
* since there could be a real mutations same-time with mutation-free changes,
* for example when Block Tune change: block is changing along with FakeCursor (mutation-free) removing
* we should fire 'didMutated' event in that case
*/
const everyRecordIsMutationFree = mutationsOrInputEvent.length > 0 && mutationsOrInputEvent.every((record) => {
const { addedNodes, removedNodes, target } = record;
const changedNodes = [
...Array.from(addedNodes),
...Array.from(removedNodes),
target,
];
return changedNodes.some((node) => {
if (!$.isElement(node)) {
/**
* "characterData" mutation record has Text node as a target, so we need to get parent element to check it for mutation-free attribute
*/
node = node.parentElement;
}
return node && (node as HTMLElement).closest('[data-mutation-free="true"]') !== null;
});
});
shouldFireUpdate = !everyRecordIsMutationFree;
}
/**
* In case some mutation free elements are added or removed, do not trigger didMutated event
*/
if (!shouldFireUpdate) {
return;
}
this.dropInputsCache();
/**
* Update current input
*/
this.updateCurrentInput();
this.call(BlockToolAPI.UPDATED);
/**
* Emit a Block Event with current Block instance.
* Block Manager subscribed to these events
*/
this.emit('didMutated', this);
};
/**
* Listen common editor Dom Changed event and detect mutations related to the Block
*/
private watchBlockMutations(): void {
/**
* Save callback to a property to remove it on Block destroy
*
* @param payload - event payload
*/
this.redactorDomChangedCallback = (payload) => {
const { mutations } = payload;
const mutationBelongsToBlock = mutations.some(record => isMutationBelongsToElement(record, this.toolRenderedElement));
if (mutationBelongsToBlock) {
this.didMutated(mutations);
}
};
this.editorEventBus?.on(RedactorDomChanged, this.redactorDomChangedCallback);
}
/**
* Remove redactor dom change event listener
*/
private unwatchBlockMutations(): void {
this.editorEventBus?.off(RedactorDomChanged, this.redactorDomChangedCallback);
}
/**
* Sometimes Tool can replace own main element, for example H2 -> H4 or UL -> OL
* We need to detect such changes and update a link to tools main element with the new one
*
* @param mutations - records of block content mutations
*/
private detectToolRootChange(mutations: MutationRecord[]): void {
mutations.forEach(record => {
const toolRootHasBeenUpdated = Array.from(record.removedNodes).includes(this.toolRenderedElement);
if (toolRootHasBeenUpdated) {
const newToolElement = record.addedNodes[record.addedNodes.length - 1];
this.toolRenderedElement = newToolElement as HTMLElement;
}
});
}
/**
* Clears inputs cached value
*/
private dropInputsCache(): void {
this.cachedInputs = [];
}
}

View file

@ -6,11 +6,8 @@ import { MoveEvent } from '../../types/tools';
/**
* @class Blocks
* @classdesc Class to work with Block instances array
*
* @private
*
* @property {HTMLElement} workingArea editor`s working node
*
*/
export default class Blocks {
/**
@ -25,7 +22,6 @@ export default class Blocks {
/**
* @class
*
* @param {HTMLElement} workingArea editor`s working node
*/
constructor(workingArea: HTMLElement) {
@ -65,7 +61,6 @@ export default class Blocks {
*
* @example
* blocks[0] = new Block(...)
*
* @param {Blocks} instance Blocks instance
* @param {PropertyKey} property block index or any Blocks class property key to set
* @param {Block} value value to set
@ -225,6 +220,62 @@ export default class Blocks {
}
}
/**
* Replaces block under passed index with passed block
*
* @param index - index of existed block
* @param block - new block
*/
public replace(index: number, block: Block): void {
if (this.blocks[index] === undefined) {
throw Error('Incorrect index');
}
const prevBlock = this.blocks[index];
prevBlock.holder.replaceWith(block.holder);
this.blocks[index] = block;
}
/**
* Inserts several blocks at once
*
* @param blocks - blocks to insert
* @param index - index to insert blocks at
*/
public insertMany(blocks: Block[], index: number ): void {
const fragment = new DocumentFragment();
for (const block of blocks) {
fragment.appendChild(block.holder);
}
if (this.length > 0) {
if (index > 0) {
const previousBlockIndex = Math.min(index - 1, this.length - 1);
const previousBlock = this.blocks[previousBlockIndex];
previousBlock.holder.after(fragment);
} else if (index === 0) {
this.workingArea.prepend(fragment);
}
/**
* Insert blocks to the array at the specified index
*/
this.blocks.splice(index, 0, ...blocks);
} else {
this.blocks.push(...blocks);
this.workingArea.appendChild(fragment);
}
/**
* Call Rendered event for each block
*/
blocks.forEach((block) => block.call(BlockToolAPI.RENDERED));
}
/**
* Remove block
*
@ -257,7 +308,6 @@ export default class Blocks {
* Insert Block after passed target
*
* @todo decide if this method is necessary
*
* @param {Block} targetBlock target after which Block should be inserted
* @param {Block} newBlock Block to insert
*/
@ -273,7 +323,7 @@ export default class Blocks {
* @param {number} index Block index
* @returns {Block}
*/
public get(index: number): Block {
public get(index: number): Block | undefined {
return this.blocks[index];
}

View file

@ -0,0 +1,5 @@
/**
* Debounce timeout for selection change event
* {@link modules/ui.ts}
*/
export const selectionChangeDebounceTimeout = 180;

View file

@ -5,38 +5,11 @@ import { EditorModules } from '../types-internal/editor-modules';
import I18n from './i18n';
import { CriticalError } from './errors/critical';
import EventsDispatcher from './utils/events';
import Modules from './modules';
import { EditorEventMap } from './events';
/**
* @typedef {Core} Core - editor core class
*/
/**
* Require Editor modules places in components/modules dir
*/
const contextRequire = require.context('./modules', true);
const modules = [];
contextRequire.keys().forEach((filename) => {
/**
* Include files if:
* - extension is .js or .ts
* - does not starts with _
*/
if (filename.match(/^\.\/[^_][\w/]*\.([tj])s$/)) {
modules.push(contextRequire(filename));
}
});
/**
* @class Core
*
* @classdesc Editor.js core class
*
* @property {EditorConfig} config - all settings
* @property {EditorModules} moduleInstances - constructed editor components
*
* @type {Core}
* Editor.js core class. Bootstraps modules.
*/
export default class Core {
/**
@ -55,19 +28,19 @@ export default class Core {
public isReady: Promise<void>;
/**
* Event Dispatcher util
* Common Editor Event Bus
*/
private eventsDispatcher: EventsDispatcher = new EventsDispatcher();
private eventsDispatcher: EventsDispatcher<EditorEventMap> = new EventsDispatcher();
/**
* @param {EditorConfig} config - user configuration
*
*/
constructor(config?: EditorConfig|string) {
/**
* Ready promise. Resolved if Editor.js is ready to work, rejected otherwise
*/
let onReady, onFail;
let onReady: (value?: void | PromiseLike<void>) => void;
let onFail: (reason?: unknown) => void;
this.isReady = new Promise((resolve, reject) => {
onReady = resolve;
@ -78,32 +51,21 @@ export default class Core {
.then(async () => {
this.configuration = config;
await this.validate();
await this.init();
this.validate();
this.init();
await this.start();
await this.render();
_.logLabeled('I\'m ready! (ノ◕ヮ◕)ノ*:・゚✧', 'log', '', 'color: #E24A75');
const { BlockManager, Caret, UI, ModificationsObserver } = this.moduleInstances;
setTimeout(async () => {
await this.render();
UI.checkEmptiness();
ModificationsObserver.enable();
if ((this.configuration as EditorConfig).autofocus) {
const { BlockManager, Caret } = this.moduleInstances;
if ((this.configuration as EditorConfig).autofocus) {
Caret.setToBlock(BlockManager.blocks[0], Caret.positions.START);
}
Caret.setToBlock(BlockManager.blocks[0], Caret.positions.START);
BlockManager.highlightCurrentNode();
}
/**
* Remove loader, show content
*/
this.moduleInstances.UI.removeLoader();
/**
* Resolve this.isReady promise
*/
onReady();
}, 500);
onReady();
})
.catch((error) => {
_.log(`Editor.js is not ready because of ${error}`, 'error');
@ -173,6 +135,7 @@ export default class Core {
*
* @type {number}
*/
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
this.config.minHeight = this.config.minHeight !== undefined ? this.config.minHeight : 300;
/**
@ -230,16 +193,14 @@ export default class Core {
*
* @returns {EditorConfig}
*/
public get configuration(): EditorConfig|string {
public get configuration(): EditorConfig {
return this.config;
}
/**
* Checks for required fields in Editor's config
*
* @returns {Promise<void>}
*/
public async validate(): Promise<void> {
public validate(): void {
const { holderId, holder } = this.config;
if (holderId && holder) {
@ -327,27 +288,14 @@ export default class Core {
* Make modules instances and save it to the @property this.moduleInstances
*/
private constructModules(): void {
modules.forEach((module) => {
/**
* If module has non-default exports, passed object contains them all and default export as 'default' property
*/
const Module = _.isFunction(module) ? module : module.default;
Object.entries(Modules).forEach(([key, module]) => {
try {
/**
* We use class name provided by displayName property
*
* On build, Babel will transform all Classes to the Functions so, name will always be 'Function'
* To prevent this, we use 'babel-plugin-class-display-name' plugin
*
* @see https://www.npmjs.com/package/babel-plugin-class-display-name
*/
this.moduleInstances[Module.displayName] = new Module({
this.moduleInstances[key] = new module({
config: this.configuration,
eventsDispatcher: this.eventsDispatcher,
});
} catch (e) {
_.log(`Module ${Module.displayName} skipped because`, 'error', e);
_.log('[constructModules]', `Module ${key} skipped because`, 'error', e);
}
});
}

View file

@ -45,19 +45,20 @@ export default class Dom {
}
/**
* Helper for making Elements with classname and attributes
* Helper for making Elements with class name and attributes
*
* @param {string} tagName - new Element tag name
* @param {string[]|string} [classNames] - list or name of CSS classname(s)
* @param {string[]|string} [classNames] - list or name of CSS class name(s)
* @param {object} [attributes] - any attributes
*
* @returns {HTMLElement}
*/
public static make(tagName: string, classNames: string|string[] = null, attributes: object = {}): HTMLElement {
public static make(tagName: string, classNames: string | (string | undefined)[] | null = null, attributes: object = {}): HTMLElement {
const el = document.createElement(tagName);
if (Array.isArray(classNames)) {
el.classList.add(...classNames);
const validClassnames = classNames.filter(className => className !== undefined) as string[];
el.classList.add(...validClassnames);
} else if (classNames) {
el.classList.add(classNames);
}
@ -75,33 +76,12 @@ export default class Dom {
* Creates Text Node with the passed content
*
* @param {string} content - text content
*
* @returns {Text}
*/
public static text(content: string): Text {
return document.createTextNode(content);
}
/**
* Creates SVG icon linked to the sprite
*
* @param {string} name - name (id) of icon from sprite
* @param {number} [width] - icon width
* @param {number} [height] - icon height
*
* @returns {SVGElement}
*/
public static svg(name: string, width = 14, height = 14): SVGElement {
const icon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
icon.classList.add('icon', 'icon--' + name);
icon.setAttribute('width', width + 'px');
icon.setAttribute('height', height + 'px');
icon.innerHTML = `<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#${name}"></use>`;
return icon;
}
/**
* Append one or several elements to the parent
*
@ -109,8 +89,8 @@ export default class Dom {
* @param {Element|Element[]|DocumentFragment|Text|Text[]} elements - element or elements list
*/
public static append(
parent: Element|DocumentFragment,
elements: Element|Element[]|DocumentFragment|Text|Text[]
parent: Element | DocumentFragment,
elements: Element | Element[] | DocumentFragment | Text | Text[]
): void {
if (Array.isArray(elements)) {
elements.forEach((el) => parent.appendChild(el));
@ -125,7 +105,7 @@ export default class Dom {
* @param {Element} parent - where to append
* @param {Element|Element[]} elements - element or elements list
*/
public static prepend(parent: Element, elements: Element|Element[]): void {
public static prepend(parent: Element, elements: Element | Element[]): void {
if (Array.isArray(elements)) {
elements = elements.reverse();
elements.forEach((el) => parent.prepend(el));
@ -165,10 +145,9 @@ export default class Dom {
*
* @param {Element} el - element we searching inside. Default - DOM Document
* @param {string} selector - searching string
*
* @returns {Element}
*/
public static find(el: Element|Document = document, selector: string): Element {
public static find(el: Element | Document = document, selector: string): Element | null {
return el.querySelector(selector);
}
@ -189,10 +168,9 @@ export default class Dom {
*
* @param {Element|Document} el - element we searching inside. Default - DOM Document
* @param {string} selector - searching string
*
* @returns {NodeList}
*/
public static findAll(el: Element|Document = document, selector: string): NodeList {
public static findAll(el: Element | Document = document, selector: string): NodeList {
return el.querySelectorAll(selector);
}
@ -207,7 +185,7 @@ export default class Dom {
}
/**
* Find all contendeditable, textarea and editable input elements passed holder contains
* Find all contenteditable, textarea and editable input elements passed holder contains
*
* @param holder - element where to find inputs
*/
@ -230,11 +208,9 @@ export default class Dom {
* Leaf is the vertex that doesn't have any child nodes
*
* @description Method recursively goes throw the all Node until it finds the Leaf
*
* @param {Node} node - root Node. From this vertex we start Deep-first search
* {@link https://en.wikipedia.org/wiki/Depth-first_search}
* @param {boolean} [atLast] - find last text node
*
* @returns {Node} - it can be text Node or Element Node, so that caret will able to work with it
*/
public static getDeepestNode(node: Node, atLast = false): Node {
@ -287,7 +263,6 @@ export default class Dom {
* Check if object is DOM node
*
* @param {*} node - object to check
*
* @returns {boolean}
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -318,7 +293,6 @@ export default class Dom {
* Check if passed element is contenteditable
*
* @param {HTMLElement} element - html element to check
*
* @returns {boolean}
*/
public static isContentEditable(element: HTMLElement): boolean {
@ -329,7 +303,6 @@ export default class Dom {
* Checks target if it is native input
*
* @param {*} target - HTML element or string
*
* @returns {boolean}
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -346,7 +319,6 @@ export default class Dom {
* Checks if we can set caret
*
* @param {HTMLElement} target - target to check
*
* @returns {boolean}
*/
public static canSetCaret(target: HTMLElement): boolean {
@ -377,12 +349,11 @@ export default class Dom {
*
* @description Method checks simple Node without any childs for emptiness
* If you have Node with 2 or more children id depth, you better use {@link Dom#isEmpty} method
*
* @param {Node} node - node to check
*
* @param {string} [ignoreChars] - char or substring to treat as empty
* @returns {boolean} true if it is empty
*/
public static isNodeEmpty(node: Node): boolean {
public static isNodeEmpty(node: Node, ignoreChars?: string): boolean {
let nodeText;
if (this.isSingleTag(node as HTMLElement) && !this.isLineBreakTag(node as HTMLElement)) {
@ -395,6 +366,10 @@ export default class Dom {
nodeText = node.textContent.replace('\u200B', '');
}
if (ignoreChars) {
nodeText = nodeText.replace(new RegExp(ignoreChars, 'g'), '');
}
return nodeText.trim().length === 0;
}
@ -402,7 +377,6 @@ export default class Dom {
* checks node if it is doesn't have any child nodes
*
* @param {Node} node - node to check
*
* @returns {boolean}
*/
public static isLeaf(node: Node): boolean {
@ -418,11 +392,11 @@ export default class Dom {
* {@link https://en.wikipedia.org/wiki/Breadth-first_search}
*
* @description Pushes to stack all DOM leafs and checks for emptiness
*
* @param {Node} node - node to check
* @param {string} [ignoreChars] - char or substring to treat as empty
* @returns {boolean}
*/
public static isEmpty(node: Node): boolean {
public static isEmpty(node: Node, ignoreChars?: string): boolean {
/**
* Normalize node to merge several text nodes to one to reduce tree walker iterations
*/
@ -437,7 +411,7 @@ export default class Dom {
continue;
}
if (this.isLeaf(node) && !this.isNodeEmpty(node)) {
if (this.isLeaf(node) && !this.isNodeEmpty(node, ignoreChars)) {
return false;
}
@ -453,7 +427,6 @@ export default class Dom {
* Check if string contains html elements
*
* @param {string} str - string to check
*
* @returns {boolean}
*/
public static isHTMLString(str: string): boolean {
@ -468,7 +441,6 @@ export default class Dom {
* Return length of node`s text content
*
* @param {Node} node - node with content
*
* @returns {number}
*/
public static getContentLength(node: Node): number {
@ -523,6 +495,8 @@ export default class Dom {
'ruby',
'section',
'table',
'tbody',
'thead',
'tr',
'tfoot',
'ul',
@ -534,7 +508,6 @@ export default class Dom {
* Check if passed content includes only inline elements
*
* @param {string|HTMLElement} data - element or html string
*
* @returns {boolean}
*/
public static containsOnlyInlineElements(data: string | HTMLElement): boolean {
@ -559,7 +532,6 @@ export default class Dom {
* Find and return all block elements in the passed parent (including subtree)
*
* @param {HTMLElement} parent - root element
*
* @returns {HTMLElement[]}
*/
public static getDeepestBlockElements(parent: HTMLElement): HTMLElement[] {
@ -576,7 +548,6 @@ export default class Dom {
* Helper for get holder from {string} or return HTMLElement
*
* @param {string | HTMLElement} element - holder's id or holder's HTML Element
*
* @returns {HTMLElement}
*/
public static getHolder(element: string | HTMLElement): HTMLElement {
@ -587,26 +558,10 @@ export default class Dom {
return element;
}
/**
* Method checks passed Node if it is some extension Node
*
* @param {Node} node - any node
*
* @returns {boolean}
*/
public static isExtensionNode(node: Node): boolean {
const extensions = [
'GRAMMARLY-EXTENSION',
];
return node && extensions.includes(node.nodeName);
}
/**
* Returns true if element is anchor (is A tag)
*
* @param {Element} element - element to check
*
* @returns {boolean}
*/
public static isAnchor(element: Element): element is HTMLAnchorElement {
@ -619,7 +574,7 @@ export default class Dom {
* @todo handle case when editor initialized in scrollable popup
* @param el - element to compute offset
*/
public static offset(el): {top: number; left: number; right: number; bottom: number} {
public static offset(el): { top: number; left: number; right: number; bottom: number } {
const rect = el.getBoundingClientRect();
const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;

View file

@ -60,6 +60,19 @@ export default class DomIterator {
return this.items[this.cursor];
}
/**
* Sets cursor to specified position
*
* @param cursorPosition - new cursor position
*/
public setCursor(cursorPosition: number): void {
if (cursorPosition < this.items.length && cursorPosition >= -1) {
this.dropCursor();
this.cursor = cursorPosition;
this.items[this.cursor].classList.add(this.focusedCssClass);
}
}
/**
* Sets items. Can be used when iterable items changed dynamically
*
@ -162,6 +175,7 @@ export default class DomIterator {
/**
* Focus input with micro-delay to ensure DOM is updated
*/
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
_.delay(() => SelectionUtils.setCursor(this.items[focusedButtonIndex]), 50)();
}

View file

@ -0,0 +1,16 @@
import { BlockMutationEvent } from '../../../types/events/block';
/**
* Fired when some block state has changed
*/
export const BlockChanged = 'block changed';
/**
* Payload that will be passed with the event
*/
export interface BlockChangedPayload {
/**
* CustomEvent describing a block change
*/
event: BlockMutationEvent;
}

View file

@ -0,0 +1,16 @@
import type Block from '../block';
/**
* Fired when some block is hovered by user
*/
export const BlockHovered = 'block hovered';
/**
* Payload that will be passed with the event
*/
export interface BlockHoveredPayload {
/**
* Hovered block
*/
block: Block;
}

View file

@ -0,0 +1,15 @@
/**
* Fired when editor mobile layout toggled
*/
export const EditorMobileLayoutToggled = 'editor mobile layout toggled';
/**
* Payload that will be passed with the event
*/
export interface EditorMobileLayoutToggledPayload {
/**
* True, if mobile layout enabled
*/
isEnabled: boolean;
}

View file

@ -0,0 +1,17 @@
/**
* Fired before we're adding/removing a fake cursor.
*
* Allows to disable mutation observer to skip this block change
*/
export const FakeCursorAboutToBeToggled = 'fake cursor is about to be toggled';
/**
* Payload that will be passed with the event
*/
export interface FakeCursorAboutToBeToggledPayload {
/**
* true - when added a cursor
* false - when removed
*/
state: boolean;
}

View file

@ -0,0 +1,17 @@
/**
* Fired after we've added/removed a fake cursor.
*
* Allows to enable mutation observer which was disabled before setting
*/
export const FakeCursorHaveBeenSet = 'fake cursor have been set';
/**
* Payload that will be passed with the event
*/
export interface FakeCursorHaveBeenSetPayload {
/**
* true - when added a cursor
* false - when removed
*/
state: boolean;
}

View file

@ -0,0 +1,14 @@
/**
* Fired when blocks wrapper (.codex-editor-redactor) dom changed
*/
export const RedactorDomChanged = 'redactor dom changed';
/**
* Payload that will be passed with the event
*/
export interface RedactorDomChangedPayload {
/**
* Mutations happened with blocks wrapper
*/
mutations: MutationRecord[];
}

View file

@ -0,0 +1,29 @@
import { RedactorDomChanged, RedactorDomChangedPayload } from './RedactorDomChanged';
import { BlockChanged, BlockChangedPayload } from './BlockChanged';
import { BlockHovered, BlockHoveredPayload } from './BlockHovered';
import { FakeCursorAboutToBeToggled, FakeCursorAboutToBeToggledPayload } from './FakeCursorAboutToBeToggled';
import { FakeCursorHaveBeenSet, FakeCursorHaveBeenSetPayload } from './FakeCursorHaveBeenSet';
import { EditorMobileLayoutToggled, EditorMobileLayoutToggledPayload } from './EditorMobileLayoutToggled';
/**
* Events fired by Editor Event Dispatcher
*/
export {
RedactorDomChanged,
BlockChanged,
FakeCursorAboutToBeToggled,
FakeCursorHaveBeenSet,
EditorMobileLayoutToggled
};
/**
* Event name -> Event payload
*/
export interface EditorEventMap {
[BlockHovered]: BlockHoveredPayload;
[RedactorDomChanged]: RedactorDomChangedPayload;
[BlockChanged]: BlockChangedPayload;
[FakeCursorAboutToBeToggled]: FakeCursorAboutToBeToggledPayload;
[FakeCursorHaveBeenSet]: FakeCursorHaveBeenSetPayload;
[EditorMobileLayoutToggled]: EditorMobileLayoutToggledPayload
}

View file

@ -41,16 +41,19 @@ export interface FlipperOptions {
*/
export default class Flipper {
/**
* Instance of flipper iterator
*
* @type {DomIterator|null}
* True if flipper is currently activated
*/
private readonly iterator: DomIterator = null;
public get isActivated(): boolean {
return this.activated;
}
/**
* Instance of flipper iterator
*/
private readonly iterator: DomIterator | null = null;
/**
* Flag that defines activation status
*
* @type {boolean}
*/
private activated = false;
@ -65,7 +68,12 @@ export default class Flipper {
private readonly activateCallback: (item: HTMLElement) => void;
/**
* @param {FlipperOptions} options - different constructing settings
* Contains list of callbacks to be executed on each flip
*/
private flipCallbacks: Array<() => void> = [];
/**
* @param options - different constructing settings
*/
constructor(options: FlipperOptions) {
this.iterator = new DomIterator(options.items, options.focusedItemClass);
@ -93,21 +101,29 @@ export default class Flipper {
/**
* Active tab/arrows handling by flipper
*
* @param {HTMLElement[]} items - Some modules (like, InlineToolbar, BlockSettings) might refresh buttons dynamically
* @param items - Some modules (like, InlineToolbar, BlockSettings) might refresh buttons dynamically
* @param cursorPosition - index of the item that should be focused once flipper is activated
*/
public activate(items?: HTMLElement[]): void {
public activate(items?: HTMLElement[], cursorPosition?: number): void {
this.activated = true;
if (items) {
this.iterator.setItems(items);
}
if (cursorPosition !== undefined) {
this.iterator.setCursor(cursorPosition);
}
/**
* Listening all keydowns on document and react on TAB/Enter press
* TAB will leaf iterator items
* ENTER will click the focused item
*
* Note: the event should be handled in capturing mode on following reasons:
* - prevents plugins inner keydown handlers from being called while keyboard navigation
* - otherwise this handler will be called at the moment it is attached which causes false flipper firing (see https://techread.me/js-addeventlistener-fires-for-past-events/)
*/
document.addEventListener('keydown', this.onKeyDown);
document.addEventListener('keydown', this.onKeyDown, true);
}
/**
@ -151,6 +167,24 @@ export default class Flipper {
return !!this.iterator.currentItem;
}
/**
* Registeres function that should be executed on each navigation action
*
* @param cb - function to execute
*/
public onFlip(cb: () => void): void {
this.flipCallbacks.push(cb);
}
/**
* Unregisteres function that is executed on each navigation action
*
* @param cb - function to stop executing
*/
public removeOnFlip(cb: () => void): void {
this.flipCallbacks = this.flipCallbacks.filter(fn => fn !== cb);
}
/**
* Drops flipper's iterator cursor
*
@ -240,15 +274,17 @@ export default class Flipper {
}
if (this.iterator.currentItem) {
/**
* Stop Enter propagation only if flipper is ready to select focused item
*/
event.stopPropagation();
event.preventDefault();
this.iterator.currentItem.click();
}
if (_.isFunction(this.activateCallback)) {
this.activateCallback(this.iterator.currentItem);
}
event.preventDefault();
event.stopPropagation();
}
/**
@ -258,5 +294,7 @@ export default class Flipper {
if (this.iterator.currentItem) {
this.iterator.currentItem.scrollIntoViewIfNeeded();
}
this.flipCallbacks.forEach(cb => cb());
}
}

Some files were not shown because too many files have changed in this diff Show more