@jackuait/blok 0.4.1-beta.0 → 0.4.1-beta.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +138 -17
- package/codemod/README.md +45 -7
- package/codemod/migrate-editorjs-to-blok.js +960 -92
- package/codemod/test.js +780 -77
- package/dist/blok.mjs +5 -2
- package/dist/chunks/blok-oNSQ3HA6.mjs +13217 -0
- package/dist/chunks/i18next-CugVlwWp.mjs +1292 -0
- package/dist/chunks/i18next-loader-BdNRw4n4.mjs +43 -0
- package/dist/{index-OwEtDFlk.mjs → chunks/index-DHgXmfki.mjs} +2 -2
- package/dist/chunks/inline-tool-convert-CRqgjRim.mjs +1989 -0
- package/dist/chunks/messages-0tDXLuyH.mjs +48 -0
- package/dist/chunks/messages-2_xedlYw.mjs +48 -0
- package/dist/chunks/messages-AHESHJm_.mjs +48 -0
- package/dist/chunks/messages-B5hdXZwA.mjs +48 -0
- package/dist/chunks/messages-B5jGUnOy.mjs +48 -0
- package/dist/chunks/messages-B5puUm7R.mjs +48 -0
- package/dist/chunks/messages-B66ZSDCJ.mjs +48 -0
- package/dist/chunks/messages-B9Oba7sq.mjs +48 -0
- package/dist/chunks/messages-BA0rcTCY.mjs +48 -0
- package/dist/chunks/messages-BBJgd5jG.mjs +48 -0
- package/dist/chunks/messages-BPqWKx5Z.mjs +48 -0
- package/dist/chunks/messages-Bdv-IkfG.mjs +48 -0
- package/dist/chunks/messages-BeUhMpsr.mjs +48 -0
- package/dist/chunks/messages-Bf6Y3_GI.mjs +48 -0
- package/dist/chunks/messages-BiExzWJv.mjs +48 -0
- package/dist/chunks/messages-BlpqL8vG.mjs +48 -0
- package/dist/chunks/messages-BmKCChWZ.mjs +48 -0
- package/dist/chunks/messages-Bn253WWC.mjs +48 -0
- package/dist/chunks/messages-BrJHUxQL.mjs +48 -0
- package/dist/chunks/messages-C5b7hr_E.mjs +48 -0
- package/dist/chunks/messages-C7I_AVH2.mjs +48 -0
- package/dist/chunks/messages-CJoBtXU6.mjs +48 -0
- package/dist/chunks/messages-CQj2JU2j.mjs +48 -0
- package/dist/chunks/messages-CUZ1x1QD.mjs +48 -0
- package/dist/chunks/messages-CUy1vn-b.mjs +48 -0
- package/dist/chunks/messages-CVeWVKsV.mjs +48 -0
- package/dist/chunks/messages-CXHd9SUK.mjs +48 -0
- package/dist/chunks/messages-CbMyJSzS.mjs +48 -0
- package/dist/chunks/messages-CbhuIWRJ.mjs +48 -0
- package/dist/chunks/messages-CeCjVKMW.mjs +48 -0
- package/dist/chunks/messages-Cj-t1bdy.mjs +48 -0
- package/dist/chunks/messages-CkFT2gle.mjs +48 -0
- package/dist/chunks/messages-Cm9aLHeX.mjs +48 -0
- package/dist/chunks/messages-CnvW8Slp.mjs +48 -0
- package/dist/chunks/messages-Cr-RJ7YB.mjs +48 -0
- package/dist/chunks/messages-CrsJ1TEJ.mjs +48 -0
- package/dist/chunks/messages-Cu08aLS3.mjs +48 -0
- package/dist/chunks/messages-CvaqJFN-.mjs +48 -0
- package/dist/chunks/messages-CyDU5lz9.mjs +48 -0
- package/dist/chunks/messages-CySyfkMU.mjs +48 -0
- package/dist/chunks/messages-Cyi2AMmz.mjs +48 -0
- package/dist/chunks/messages-D00OjS2n.mjs +48 -0
- package/dist/chunks/messages-DDLgIPDF.mjs +48 -0
- package/dist/chunks/messages-DMQIHGRj.mjs +48 -0
- package/dist/chunks/messages-DOlC_Tty.mjs +48 -0
- package/dist/chunks/messages-DV6shA9b.mjs +48 -0
- package/dist/chunks/messages-DY94ykcE.mjs +48 -0
- package/dist/chunks/messages-DbVquYKN.mjs +48 -0
- package/dist/chunks/messages-DcKOuncK.mjs +48 -0
- package/dist/chunks/messages-Dg92dXZ5.mjs +48 -0
- package/dist/chunks/messages-DnbbyJT3.mjs +48 -0
- package/dist/chunks/messages-DteYq0rv.mjs +48 -0
- package/dist/chunks/messages-GC2PhgV3.mjs +48 -0
- package/dist/chunks/messages-JGsXAReJ.mjs +48 -0
- package/dist/chunks/messages-JZUhXTuV.mjs +48 -0
- package/dist/chunks/messages-LvFKBBPa.mjs +48 -0
- package/dist/chunks/messages-NP1myMGI.mjs +48 -0
- package/dist/chunks/messages-Q4kc_ZtL.mjs +48 -0
- package/dist/chunks/messages-RvMHb2Ht.mjs +48 -0
- package/dist/chunks/messages-ftMcCEuO.mjs +48 -0
- package/dist/chunks/messages-o24dK6CU.mjs +48 -0
- package/dist/chunks/messages-pA5TvcAj.mjs +48 -0
- package/dist/chunks/messages-rRSHQDCX.mjs +48 -0
- package/dist/chunks/messages-srxrv8Yh.mjs +48 -0
- package/dist/chunks/messages-wdqp4610.mjs +48 -0
- package/dist/chunks/messages-zS1AXZ0y.mjs +48 -0
- package/dist/chunks/messages-zSzDzXej.mjs +48 -0
- package/dist/full.mjs +50 -0
- package/dist/locales.mjs +228 -0
- package/dist/messages-0tDXLuyH.mjs +48 -0
- package/dist/messages-2_xedlYw.mjs +48 -0
- package/dist/messages-AHESHJm_.mjs +48 -0
- package/dist/messages-B5hdXZwA.mjs +48 -0
- package/dist/messages-B5jGUnOy.mjs +48 -0
- package/dist/messages-B5puUm7R.mjs +48 -0
- package/dist/messages-B66ZSDCJ.mjs +48 -0
- package/dist/messages-B9Oba7sq.mjs +48 -0
- package/dist/messages-BA0rcTCY.mjs +48 -0
- package/dist/messages-BBJgd5jG.mjs +48 -0
- package/dist/messages-BPqWKx5Z.mjs +48 -0
- package/dist/messages-Bdv-IkfG.mjs +48 -0
- package/dist/messages-BeUhMpsr.mjs +48 -0
- package/dist/messages-Bf6Y3_GI.mjs +48 -0
- package/dist/messages-BiExzWJv.mjs +48 -0
- package/dist/messages-BlpqL8vG.mjs +48 -0
- package/dist/messages-BmKCChWZ.mjs +48 -0
- package/dist/messages-Bn253WWC.mjs +48 -0
- package/dist/messages-BrJHUxQL.mjs +48 -0
- package/dist/messages-C5b7hr_E.mjs +48 -0
- package/dist/messages-C7I_AVH2.mjs +48 -0
- package/dist/messages-CJoBtXU6.mjs +48 -0
- package/dist/messages-CQj2JU2j.mjs +48 -0
- package/dist/messages-CUZ1x1QD.mjs +48 -0
- package/dist/messages-CUy1vn-b.mjs +48 -0
- package/dist/messages-CVeWVKsV.mjs +48 -0
- package/dist/messages-CXHd9SUK.mjs +48 -0
- package/dist/messages-CbMyJSzS.mjs +48 -0
- package/dist/messages-CbhuIWRJ.mjs +48 -0
- package/dist/messages-CeCjVKMW.mjs +48 -0
- package/dist/messages-Cj-t1bdy.mjs +48 -0
- package/dist/messages-CkFT2gle.mjs +48 -0
- package/dist/messages-Cm9aLHeX.mjs +48 -0
- package/dist/messages-CnvW8Slp.mjs +48 -0
- package/dist/messages-Cr-RJ7YB.mjs +48 -0
- package/dist/messages-CrsJ1TEJ.mjs +48 -0
- package/dist/messages-Cu08aLS3.mjs +48 -0
- package/dist/messages-CvaqJFN-.mjs +48 -0
- package/dist/messages-CyDU5lz9.mjs +48 -0
- package/dist/messages-CySyfkMU.mjs +48 -0
- package/dist/messages-Cyi2AMmz.mjs +48 -0
- package/dist/messages-D00OjS2n.mjs +48 -0
- package/dist/messages-DDLgIPDF.mjs +48 -0
- package/dist/messages-DMQIHGRj.mjs +48 -0
- package/dist/messages-DOlC_Tty.mjs +48 -0
- package/dist/messages-DV6shA9b.mjs +48 -0
- package/dist/messages-DY94ykcE.mjs +48 -0
- package/dist/messages-DbVquYKN.mjs +48 -0
- package/dist/messages-DcKOuncK.mjs +48 -0
- package/dist/messages-Dg92dXZ5.mjs +48 -0
- package/dist/messages-DnbbyJT3.mjs +48 -0
- package/dist/messages-DteYq0rv.mjs +48 -0
- package/dist/messages-GC2PhgV3.mjs +48 -0
- package/dist/messages-JGsXAReJ.mjs +48 -0
- package/dist/messages-JZUhXTuV.mjs +48 -0
- package/dist/messages-LvFKBBPa.mjs +48 -0
- package/dist/messages-NP1myMGI.mjs +48 -0
- package/dist/messages-Q4kc_ZtL.mjs +48 -0
- package/dist/messages-RvMHb2Ht.mjs +48 -0
- package/dist/messages-ftMcCEuO.mjs +48 -0
- package/dist/messages-o24dK6CU.mjs +48 -0
- package/dist/messages-pA5TvcAj.mjs +48 -0
- package/dist/messages-rRSHQDCX.mjs +48 -0
- package/dist/messages-srxrv8Yh.mjs +48 -0
- package/dist/messages-wdqp4610.mjs +48 -0
- package/dist/messages-zS1AXZ0y.mjs +48 -0
- package/dist/messages-zSzDzXej.mjs +48 -0
- package/dist/tools.mjs +3117 -0
- package/dist/vendor.LICENSE.txt +26 -225
- package/package.json +63 -24
- package/src/blok.ts +267 -0
- package/src/components/__module.ts +139 -0
- package/src/components/block/api.ts +155 -0
- package/src/components/block/index.ts +1428 -0
- package/src/components/block-tunes/block-tune-delete.ts +51 -0
- package/src/components/blocks.ts +352 -0
- package/src/components/constants/data-attributes.ts +344 -0
- package/src/components/constants.ts +76 -0
- package/src/components/core.ts +392 -0
- package/src/components/dom.ts +773 -0
- package/src/components/domIterator.ts +189 -0
- package/src/components/errors/critical.ts +5 -0
- package/src/components/events/BlockChanged.ts +16 -0
- package/src/components/events/BlockHovered.ts +21 -0
- package/src/components/events/BlockSettingsClosed.ts +12 -0
- package/src/components/events/BlockSettingsOpened.ts +12 -0
- package/src/components/events/BlokMobileLayoutToggled.ts +15 -0
- package/src/components/events/FakeCursorAboutToBeToggled.ts +17 -0
- package/src/components/events/FakeCursorHaveBeenSet.ts +17 -0
- package/src/components/events/HistoryStateChanged.ts +19 -0
- package/src/components/events/RedactorDomChanged.ts +14 -0
- package/src/components/events/index.ts +46 -0
- package/src/components/flipper.ts +497 -0
- package/src/components/i18n/i18next-loader.ts +84 -0
- package/src/components/i18n/lightweight-i18n.ts +86 -0
- package/src/components/i18n/locales/TRANSLATION_GUIDELINES.md +113 -0
- package/src/components/i18n/locales/am/messages.json +45 -0
- package/src/components/i18n/locales/ar/messages.json +45 -0
- package/src/components/i18n/locales/az/messages.json +45 -0
- package/src/components/i18n/locales/bg/messages.json +45 -0
- package/src/components/i18n/locales/bn/messages.json +45 -0
- package/src/components/i18n/locales/bs/messages.json +45 -0
- package/src/components/i18n/locales/cs/messages.json +45 -0
- package/src/components/i18n/locales/da/messages.json +45 -0
- package/src/components/i18n/locales/de/messages.json +45 -0
- package/src/components/i18n/locales/dv/messages.json +45 -0
- package/src/components/i18n/locales/el/messages.json +45 -0
- package/src/components/i18n/locales/en/messages.json +45 -0
- package/src/components/i18n/locales/es/messages.json +45 -0
- package/src/components/i18n/locales/et/messages.json +45 -0
- package/src/components/i18n/locales/fa/messages.json +45 -0
- package/src/components/i18n/locales/fi/messages.json +45 -0
- package/src/components/i18n/locales/fil/messages.json +45 -0
- package/src/components/i18n/locales/fr/messages.json +45 -0
- package/src/components/i18n/locales/gu/messages.json +45 -0
- package/src/components/i18n/locales/he/messages.json +45 -0
- package/src/components/i18n/locales/hi/messages.json +45 -0
- package/src/components/i18n/locales/hr/messages.json +45 -0
- package/src/components/i18n/locales/hu/messages.json +45 -0
- package/src/components/i18n/locales/hy/messages.json +45 -0
- package/src/components/i18n/locales/id/messages.json +45 -0
- package/src/components/i18n/locales/index.ts +231 -0
- package/src/components/i18n/locales/it/messages.json +45 -0
- package/src/components/i18n/locales/ja/messages.json +45 -0
- package/src/components/i18n/locales/ka/messages.json +45 -0
- package/src/components/i18n/locales/km/messages.json +45 -0
- package/src/components/i18n/locales/kn/messages.json +45 -0
- package/src/components/i18n/locales/ko/messages.json +45 -0
- package/src/components/i18n/locales/ku/messages.json +45 -0
- package/src/components/i18n/locales/lo/messages.json +45 -0
- package/src/components/i18n/locales/lt/messages.json +45 -0
- package/src/components/i18n/locales/lv/messages.json +45 -0
- package/src/components/i18n/locales/mk/messages.json +45 -0
- package/src/components/i18n/locales/ml/messages.json +45 -0
- package/src/components/i18n/locales/mn/messages.json +45 -0
- package/src/components/i18n/locales/mr/messages.json +45 -0
- package/src/components/i18n/locales/ms/messages.json +45 -0
- package/src/components/i18n/locales/my/messages.json +45 -0
- package/src/components/i18n/locales/ne/messages.json +45 -0
- package/src/components/i18n/locales/nl/messages.json +45 -0
- package/src/components/i18n/locales/no/messages.json +45 -0
- package/src/components/i18n/locales/pa/messages.json +45 -0
- package/src/components/i18n/locales/pl/messages.json +45 -0
- package/src/components/i18n/locales/ps/messages.json +45 -0
- package/src/components/i18n/locales/pt/messages.json +45 -0
- package/src/components/i18n/locales/ro/messages.json +45 -0
- package/src/components/i18n/locales/ru/messages.json +45 -0
- package/src/components/i18n/locales/sd/messages.json +45 -0
- package/src/components/i18n/locales/si/messages.json +45 -0
- package/src/components/i18n/locales/sk/messages.json +45 -0
- package/src/components/i18n/locales/sl/messages.json +45 -0
- package/src/components/i18n/locales/sq/messages.json +45 -0
- package/src/components/i18n/locales/sr/messages.json +45 -0
- package/src/components/i18n/locales/sv/messages.json +45 -0
- package/src/components/i18n/locales/sw/messages.json +45 -0
- package/src/components/i18n/locales/ta/messages.json +45 -0
- package/src/components/i18n/locales/te/messages.json +45 -0
- package/src/components/i18n/locales/th/messages.json +45 -0
- package/src/components/i18n/locales/tr/messages.json +45 -0
- package/src/components/i18n/locales/ug/messages.json +45 -0
- package/src/components/i18n/locales/uk/messages.json +45 -0
- package/src/components/i18n/locales/ur/messages.json +45 -0
- package/src/components/i18n/locales/vi/messages.json +45 -0
- package/src/components/i18n/locales/yi/messages.json +45 -0
- package/src/components/i18n/locales/zh/messages.json +45 -0
- package/src/components/icons/index.ts +242 -0
- package/src/components/inline-tools/inline-tool-bold.ts +2213 -0
- package/src/components/inline-tools/inline-tool-convert.ts +141 -0
- package/src/components/inline-tools/inline-tool-italic.ts +500 -0
- package/src/components/inline-tools/inline-tool-link.ts +539 -0
- package/src/components/modules/api/blocks.ts +377 -0
- package/src/components/modules/api/caret.ts +125 -0
- package/src/components/modules/api/events.ts +51 -0
- package/src/components/modules/api/history.ts +73 -0
- package/src/components/modules/api/i18n.ts +35 -0
- package/src/components/modules/api/index.ts +39 -0
- package/src/components/modules/api/inlineToolbar.ts +33 -0
- package/src/components/modules/api/listeners.ts +56 -0
- package/src/components/modules/api/notifier.ts +46 -0
- package/src/components/modules/api/readonly.ts +39 -0
- package/src/components/modules/api/sanitizer.ts +30 -0
- package/src/components/modules/api/saver.ts +52 -0
- package/src/components/modules/api/selection.ts +48 -0
- package/src/components/modules/api/styles.ts +72 -0
- package/src/components/modules/api/toolbar.ts +79 -0
- package/src/components/modules/api/tools.ts +16 -0
- package/src/components/modules/api/tooltip.ts +67 -0
- package/src/components/modules/api/ui.ts +36 -0
- package/src/components/modules/blockEvents.ts +1591 -0
- package/src/components/modules/blockManager.ts +1356 -0
- package/src/components/modules/blockSelection.ts +708 -0
- package/src/components/modules/caret.ts +853 -0
- package/src/components/modules/crossBlockSelection.ts +329 -0
- package/src/components/modules/dragManager.ts +1204 -0
- package/src/components/modules/history.ts +1098 -0
- package/src/components/modules/i18n.ts +332 -0
- package/src/components/modules/index.ts +139 -0
- package/src/components/modules/modificationsObserver.ts +147 -0
- package/src/components/modules/paste.ts +1092 -0
- package/src/components/modules/readonly.ts +136 -0
- package/src/components/modules/rectangleSelection.ts +711 -0
- package/src/components/modules/renderer.ts +155 -0
- package/src/components/modules/saver.ts +283 -0
- package/src/components/modules/toolbar/blockSettings.ts +781 -0
- package/src/components/modules/toolbar/index.ts +1315 -0
- package/src/components/modules/toolbar/inline.ts +956 -0
- package/src/components/modules/tools.ts +625 -0
- package/src/components/modules/ui.ts +1283 -0
- package/src/components/polyfills.ts +113 -0
- package/src/components/selection.ts +1179 -0
- package/src/components/tools/base.ts +301 -0
- package/src/components/tools/block.ts +339 -0
- package/src/components/tools/collection.ts +67 -0
- package/src/components/tools/factory.ts +138 -0
- package/src/components/tools/inline.ts +71 -0
- package/src/components/tools/tune.ts +33 -0
- package/src/components/ui/toolbox.ts +601 -0
- package/src/components/utils/announcer.ts +205 -0
- package/src/components/utils/api.ts +20 -0
- package/src/components/utils/bem.ts +26 -0
- package/src/components/utils/blocks.ts +284 -0
- package/src/components/utils/caret.ts +1067 -0
- package/src/components/utils/data-model-transform.ts +382 -0
- package/src/components/utils/events.ts +117 -0
- package/src/components/utils/keyboard.ts +60 -0
- package/src/components/utils/listeners.ts +296 -0
- package/src/components/utils/mutations.ts +39 -0
- package/src/components/utils/notifier/draw.ts +190 -0
- package/src/components/utils/notifier/index.ts +66 -0
- package/src/components/utils/notifier/types.ts +1 -0
- package/src/components/utils/notifier.ts +77 -0
- package/src/components/utils/placeholder.ts +140 -0
- package/src/components/utils/popover/components/hint/hint.const.ts +10 -0
- package/src/components/utils/popover/components/hint/hint.ts +46 -0
- package/src/components/utils/popover/components/hint/index.ts +6 -0
- package/src/components/utils/popover/components/popover-header/index.ts +2 -0
- package/src/components/utils/popover/components/popover-header/popover-header.const.ts +8 -0
- package/src/components/utils/popover/components/popover-header/popover-header.ts +80 -0
- package/src/components/utils/popover/components/popover-header/popover-header.types.ts +14 -0
- package/src/components/utils/popover/components/popover-item/index.ts +13 -0
- package/src/components/utils/popover/components/popover-item/popover-item-default/popover-item-default.const.ts +50 -0
- package/src/components/utils/popover/components/popover-item/popover-item-default/popover-item-default.ts +680 -0
- package/src/components/utils/popover/components/popover-item/popover-item-html/popover-item-html.const.ts +14 -0
- package/src/components/utils/popover/components/popover-item/popover-item-html/popover-item-html.ts +136 -0
- package/src/components/utils/popover/components/popover-item/popover-item-separator/popover-item-separator.const.ts +20 -0
- package/src/components/utils/popover/components/popover-item/popover-item-separator/popover-item-separator.ts +117 -0
- package/src/components/utils/popover/components/popover-item/popover-item.ts +186 -0
- package/src/components/utils/popover/components/search-input/index.ts +2 -0
- package/src/components/utils/popover/components/search-input/search-input.const.ts +8 -0
- package/src/components/utils/popover/components/search-input/search-input.ts +178 -0
- package/src/components/utils/popover/components/search-input/search-input.types.ts +59 -0
- package/src/components/utils/popover/index.ts +13 -0
- package/src/components/utils/popover/popover-abstract.ts +457 -0
- package/src/components/utils/popover/popover-desktop.ts +676 -0
- package/src/components/utils/popover/popover-inline.ts +338 -0
- package/src/components/utils/popover/popover-mobile.ts +201 -0
- package/src/components/utils/popover/popover.const.ts +81 -0
- package/src/components/utils/popover/utils/popover-states-history.ts +72 -0
- package/src/components/utils/promise-queue.ts +43 -0
- package/src/components/utils/sanitizer.ts +537 -0
- package/src/components/utils/scroll-locker.ts +87 -0
- package/src/components/utils/shortcut.ts +231 -0
- package/src/components/utils/shortcuts.ts +113 -0
- package/src/components/utils/tools.ts +110 -0
- package/src/components/utils/tooltip.ts +591 -0
- package/src/components/utils/tw.ts +241 -0
- package/src/components/utils.ts +1081 -0
- package/src/env.d.ts +13 -0
- package/src/full.ts +69 -0
- package/src/locales.ts +51 -0
- package/src/stories/Block.stories.ts +498 -0
- package/src/stories/EditorModes.stories.ts +505 -0
- package/src/stories/Header.stories.ts +137 -0
- package/src/stories/InlineToolbar.stories.ts +498 -0
- package/src/stories/List.stories.ts +259 -0
- package/src/stories/Notifier.stories.ts +340 -0
- package/src/stories/Paragraph.stories.ts +112 -0
- package/src/stories/Placeholder.stories.ts +319 -0
- package/src/stories/Popover.stories.ts +844 -0
- package/src/stories/Selection.stories.ts +250 -0
- package/src/stories/StubBlock.stories.ts +156 -0
- package/src/stories/Toolbar.stories.ts +223 -0
- package/src/stories/Toolbox.stories.ts +166 -0
- package/src/stories/Tooltip.stories.ts +198 -0
- package/src/stories/helpers.ts +463 -0
- package/src/styles/main.css +123 -0
- package/src/tools/header/index.ts +646 -0
- package/src/tools/index.ts +45 -0
- package/src/tools/list/index.ts +1819 -0
- package/src/tools/paragraph/index.ts +412 -0
- package/src/tools/stub/index.ts +107 -0
- package/src/types-internal/blok-modules.d.ts +87 -0
- package/src/types-internal/html-janitor.d.ts +28 -0
- package/src/types-internal/module-config.d.ts +11 -0
- package/src/variants/all-locales.ts +155 -0
- package/src/variants/blok-maximum.ts +20 -0
- package/src/variants/blok-minimum.ts +243 -0
- package/types/api/blocks.d.ts +9 -1
- package/types/api/history.d.ts +7 -0
- package/types/api/i18n.d.ts +22 -3
- package/types/api/selection.d.ts +6 -0
- package/types/api/styles.d.ts +23 -10
- package/types/configs/blok-config.d.ts +29 -0
- package/types/configs/i18n-config.d.ts +52 -2
- package/types/configs/i18n-dictionary.d.ts +16 -90
- package/types/data-attributes.d.ts +170 -0
- package/types/data-formats/output-data.d.ts +15 -0
- package/types/full.d.ts +80 -0
- package/types/index.d.ts +30 -13
- package/types/locales.d.ts +59 -0
- package/types/tools/adapters/inline-tool-adapter.d.ts +10 -0
- package/types/tools/block-tool.d.ts +9 -0
- package/types/tools/header.d.ts +18 -0
- package/types/tools/index.d.ts +1 -0
- package/types/tools/list.d.ts +91 -0
- package/types/tools/paragraph.d.ts +71 -0
- package/types/tools/tool-settings.d.ts +92 -6
- package/types/tools/tool.d.ts +6 -0
- package/types/tools-entry.d.ts +49 -0
- package/types/utils/popover/popover-item.d.ts +18 -5
- package/types/utils/popover/popover.d.ts +7 -0
- package/dist/blok-D_baBvTG.mjs +0 -25795
- package/dist/blok.umd.js +0 -181
|
@@ -0,0 +1,1315 @@
|
|
|
1
|
+
import { Module } from '../../__module';
|
|
2
|
+
import { Dom as $ } from '../../dom';
|
|
3
|
+
import { getUserOS, isMobileScreen, log } from '../../utils';
|
|
4
|
+
import { hide, onHover } from '../../utils/tooltip';
|
|
5
|
+
import type { ModuleConfig } from '../../../types-internal/module-config';
|
|
6
|
+
import { Block } from '../../block';
|
|
7
|
+
import { Toolbox, ToolboxEvent } from '../../ui/toolbox';
|
|
8
|
+
import { IconMenu, IconPlus } from '../../icons';
|
|
9
|
+
import { BlockHovered } from '../../events/BlockHovered';
|
|
10
|
+
import { BlockSettingsClosed } from '../../events/BlockSettingsClosed';
|
|
11
|
+
import { BlockSettingsOpened } from '../../events/BlockSettingsOpened';
|
|
12
|
+
import type { BlockChangedPayload } from '../../events/BlockChanged';
|
|
13
|
+
import { BlockChanged } from '../../events/BlockChanged';
|
|
14
|
+
import { twJoin } from '../../utils/tw';
|
|
15
|
+
import { DATA_ATTR } from '../../constants';
|
|
16
|
+
import { SelectionUtils } from '../../selection';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @todo Tab on non-empty block should open Block Settings of the hoveredBlock (not where caret is set)
|
|
20
|
+
* - make Block Settings a standalone module
|
|
21
|
+
* @todo - Keyboard-only mode bug:
|
|
22
|
+
* press Tab, flip to the Checkbox. press Enter (block will be added), Press Tab
|
|
23
|
+
* (Block Tunes will be opened with Move up focused), press Enter, press Tab ———— both Block Tunes and Toolbox will be opened
|
|
24
|
+
* @todo TEST CASE - show toggler after opening and closing the Inline Toolbar
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* HTML Elements used for Toolbar UI
|
|
29
|
+
*/
|
|
30
|
+
interface ToolbarNodes {
|
|
31
|
+
wrapper: HTMLElement | undefined;
|
|
32
|
+
content: HTMLElement | undefined;
|
|
33
|
+
actions: HTMLElement | undefined;
|
|
34
|
+
|
|
35
|
+
plusButton: HTMLElement | undefined;
|
|
36
|
+
settingsToggler: HTMLElement | undefined;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Threshold in pixels to distinguish between a click and a drag.
|
|
41
|
+
* Should be higher than DragManager's dragThreshold (5px) so that
|
|
42
|
+
* clicks with slight mouse movement still open the menu.
|
|
43
|
+
*/
|
|
44
|
+
const DRAG_THRESHOLD = 10;
|
|
45
|
+
/**
|
|
46
|
+
*
|
|
47
|
+
*«Toolbar» is the node that moves up/down over current block
|
|
48
|
+
*
|
|
49
|
+
*______________________________________ Toolbar ____________________________________________
|
|
50
|
+
*| |
|
|
51
|
+
*| ..................... Content ......................................................... |
|
|
52
|
+
*| . ........ Block Actions ........... |
|
|
53
|
+
*| . . [Open Settings] . |
|
|
54
|
+
*| . [Plus Button] [Toolbox: {Tool1}, {Tool2}] . . |
|
|
55
|
+
*| . . [Settings Panel] . |
|
|
56
|
+
*| . .................................. |
|
|
57
|
+
*| ....................................................................................... |
|
|
58
|
+
*| |
|
|
59
|
+
*|___________________________________________________________________________________________|
|
|
60
|
+
*
|
|
61
|
+
*
|
|
62
|
+
*Toolbox — its an Element contains tools buttons. Can be shown by Plus Button.
|
|
63
|
+
*
|
|
64
|
+
*_______________ Toolbox _______________
|
|
65
|
+
*| |
|
|
66
|
+
*| [Header] [Image] [List] [Quote] ... |
|
|
67
|
+
*|_______________________________________|
|
|
68
|
+
*
|
|
69
|
+
*
|
|
70
|
+
*Settings Panel — is an Element with block settings:
|
|
71
|
+
*
|
|
72
|
+
*____ Settings Panel ____
|
|
73
|
+
*| ...................... |
|
|
74
|
+
*| . Tool Settings . |
|
|
75
|
+
*| ...................... |
|
|
76
|
+
*| . Default Settings . |
|
|
77
|
+
*| ...................... |
|
|
78
|
+
*|________________________|
|
|
79
|
+
* @class
|
|
80
|
+
* @classdesc Toolbar module
|
|
81
|
+
*/
|
|
82
|
+
/**
|
|
83
|
+
* @property {object} nodes - Toolbar nodes
|
|
84
|
+
* @property {Element} nodes.wrapper - Toolbar main element
|
|
85
|
+
* @property {Element} nodes.content - Zone with Plus button and toolbox.
|
|
86
|
+
* @property {Element} nodes.actions - Zone with Block Settings and Remove Button
|
|
87
|
+
* @property {Element} nodes.blockActionsButtons - Zone with Block Buttons: [Settings]
|
|
88
|
+
* @property {Element} nodes.plusButton - Button that opens or closes Toolbox
|
|
89
|
+
* @property {Element} nodes.toolbox - Container for tools
|
|
90
|
+
* @property {Element} nodes.settingsToggler - open/close Settings Panel button
|
|
91
|
+
* @property {Element} nodes.settings - Settings Panel
|
|
92
|
+
* @property {Element} nodes.pluginSettings - Plugin Settings section of Settings Panel
|
|
93
|
+
* @property {Element} nodes.defaultSettings - Default Settings section of Settings Panel
|
|
94
|
+
*/
|
|
95
|
+
export class Toolbar extends Module<ToolbarNodes> {
|
|
96
|
+
/**
|
|
97
|
+
* Block near which we display the Toolbox
|
|
98
|
+
*/
|
|
99
|
+
private hoveredBlock: Block | null = null;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* The actual element being hovered (could be a nested element like a list item)
|
|
103
|
+
*/
|
|
104
|
+
private hoveredTarget: Element | null = null;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Toolbox class instance
|
|
108
|
+
* It will be created in requestIdleCallback so it can be null in some period of time
|
|
109
|
+
*/
|
|
110
|
+
private toolboxInstance: Toolbox | null = null;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Mouse position when mousedown occurred on settings toggler
|
|
114
|
+
* Used to distinguish between click and drag
|
|
115
|
+
*/
|
|
116
|
+
private settingsTogglerMouseDownPosition: { x: number; y: number } | null = null;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Mouse position when mousedown occurred on plus button
|
|
120
|
+
* Used to distinguish between click and drag
|
|
121
|
+
*/
|
|
122
|
+
private plusButtonMouseDownPosition: { x: number; y: number } | null = null;
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Last calculated toolbar Y position
|
|
126
|
+
* Used to avoid unnecessary repositioning when the position hasn't changed
|
|
127
|
+
*/
|
|
128
|
+
private lastToolbarY: number | null = null;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Flag to ignore the next mouseup on settings toggler after a block drop
|
|
132
|
+
* Prevents the settings menu from opening when the cursor is over the toggler after drop
|
|
133
|
+
*/
|
|
134
|
+
private ignoreNextSettingsMouseUp = false;
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* @class
|
|
138
|
+
* @param moduleConfiguration - Module Configuration
|
|
139
|
+
* @param moduleConfiguration.config - Blok's config
|
|
140
|
+
* @param moduleConfiguration.eventsDispatcher - Blok's event dispatcher
|
|
141
|
+
*/
|
|
142
|
+
constructor({ config, eventsDispatcher }: ModuleConfig) {
|
|
143
|
+
super({
|
|
144
|
+
config,
|
|
145
|
+
eventsDispatcher,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* CSS styles
|
|
151
|
+
* @returns {object}
|
|
152
|
+
* @deprecated Use data attributes via constants instead
|
|
153
|
+
*/
|
|
154
|
+
public get CSS(): { [name: string]: string } {
|
|
155
|
+
return {
|
|
156
|
+
toolbar: twJoin(
|
|
157
|
+
'absolute left-0 right-0 top-0 transition-opacity duration-100 ease-linear will-change-[opacity,top]'
|
|
158
|
+
),
|
|
159
|
+
toolbarOpened: 'block',
|
|
160
|
+
toolbarClosed: 'hidden',
|
|
161
|
+
content: twJoin(
|
|
162
|
+
'relative mx-auto max-w-content'
|
|
163
|
+
),
|
|
164
|
+
actions: twJoin(
|
|
165
|
+
'absolute flex opacity-0 pr-[5px]',
|
|
166
|
+
'right-full',
|
|
167
|
+
// Mobile styles
|
|
168
|
+
'mobile:right-auto',
|
|
169
|
+
// RTL styles
|
|
170
|
+
'group-data-[blok-rtl=true]:right-auto group-data-[blok-rtl=true]:left-[calc(-1*theme(width.toolbox-btn))]',
|
|
171
|
+
'mobile:group-data-[blok-rtl=true]:ml-0 mobile:group-data-[blok-rtl=true]:mr-auto mobile:group-data-[blok-rtl=true]:pr-0 mobile:group-data-[blok-rtl=true]:pl-[10px]'
|
|
172
|
+
),
|
|
173
|
+
actionsOpened: 'opacity-100',
|
|
174
|
+
|
|
175
|
+
plusButton: twJoin(
|
|
176
|
+
// Base toolbox-button styles
|
|
177
|
+
'text-dark cursor-pointer w-toolbox-btn h-toolbox-btn rounded-[7px] inline-flex justify-center items-center select-none',
|
|
178
|
+
'shrink-0',
|
|
179
|
+
// SVG sizing
|
|
180
|
+
'[&_svg]:h-6 [&_svg]:w-6',
|
|
181
|
+
// Hover (can-hover)
|
|
182
|
+
'can-hover:hover:bg-bg-light',
|
|
183
|
+
// Keep hover background when toolbox is open
|
|
184
|
+
'group-data-[blok-toolbox-opened=true]:bg-bg-light',
|
|
185
|
+
// Mobile styles (static positioning with overlay-pane appearance)
|
|
186
|
+
'mobile:bg-white mobile:border mobile:border-[#e8e8eb] mobile:shadow-overlay-pane mobile:rounded-[6px] mobile:z-[2]',
|
|
187
|
+
'mobile:w-toolbox-btn-mobile mobile:h-toolbox-btn-mobile',
|
|
188
|
+
// RTL styles
|
|
189
|
+
'group-data-[blok-rtl=true]:right-[calc(-1*theme(width.toolbox-btn))] group-data-[blok-rtl=true]:left-auto',
|
|
190
|
+
// Narrow mode (not-mobile)
|
|
191
|
+
'not-mobile:group-data-[blok-narrow=true]:left-[5px]',
|
|
192
|
+
// Narrow mode RTL (not-mobile)
|
|
193
|
+
'not-mobile:group-data-[blok-narrow=true]:group-data-[blok-rtl=true]:left-0 not-mobile:group-data-[blok-narrow=true]:group-data-[blok-rtl=true]:right-[5px]'
|
|
194
|
+
),
|
|
195
|
+
plusButtonShortcutKey: 'text-white',
|
|
196
|
+
/**
|
|
197
|
+
* Data attribute selector used by SortableJS for drag handle
|
|
198
|
+
*/
|
|
199
|
+
settingsToggler: twJoin(
|
|
200
|
+
// Base toolbox-button styles
|
|
201
|
+
'text-dark cursor-pointer w-toolbox-btn h-toolbox-btn rounded-[7px] inline-flex justify-center items-center select-none',
|
|
202
|
+
'cursor-pointer select-none',
|
|
203
|
+
// SVG sizing
|
|
204
|
+
'[&_svg]:h-6 [&_svg]:w-6',
|
|
205
|
+
// Active state
|
|
206
|
+
'active:cursor-grabbing',
|
|
207
|
+
// Hover (can-hover)
|
|
208
|
+
'can-hover:hover:bg-bg-light can-hover:hover:cursor-grab',
|
|
209
|
+
// When toolbox is opened, use pointer cursor on hover
|
|
210
|
+
'group-data-[blok-toolbox-opened=true]:can-hover:hover:cursor-pointer',
|
|
211
|
+
// When block settings is opened, show hover background and pointer cursor
|
|
212
|
+
'group-data-[blok-block-settings-opened=true]:bg-bg-light',
|
|
213
|
+
'group-data-[blok-block-settings-opened=true]:can-hover:hover:cursor-pointer',
|
|
214
|
+
// Mobile styles (static positioning with overlay-pane appearance)
|
|
215
|
+
'mobile:bg-white mobile:border mobile:border-[#e8e8eb] mobile:shadow-overlay-pane mobile:rounded-[6px] mobile:z-[2]',
|
|
216
|
+
'mobile:w-toolbox-btn-mobile mobile:h-toolbox-btn-mobile',
|
|
217
|
+
// Not-mobile styles
|
|
218
|
+
'not-mobile:w-6'
|
|
219
|
+
),
|
|
220
|
+
settingsTogglerHidden: 'hidden',
|
|
221
|
+
settingsTogglerOpened: '',
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Returns the Toolbar opening state
|
|
227
|
+
* @returns {boolean}
|
|
228
|
+
*/
|
|
229
|
+
public get opened(): boolean {
|
|
230
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
231
|
+
return this.nodes.wrapper?.classList.contains(this.CSS.toolbarOpened) ?? false;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Check if the element is contained in the Toolbar or its components (Toolbox, BlockSettings)
|
|
236
|
+
* @param element - element to check
|
|
237
|
+
*/
|
|
238
|
+
public contains(element: HTMLElement): boolean {
|
|
239
|
+
if (this.nodes.wrapper?.contains(element)) {
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (this.toolboxInstance?.contains(element)) {
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (this.Blok.BlockSettings.contains(element)) {
|
|
248
|
+
return true;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Public interface for accessing the Toolbox
|
|
256
|
+
*/
|
|
257
|
+
public get toolbox(): {
|
|
258
|
+
opened: boolean | undefined; // undefined is for the case when Toolbox is not initialized yet
|
|
259
|
+
close: () => void;
|
|
260
|
+
open: () => void;
|
|
261
|
+
toggle: () => void;
|
|
262
|
+
hasFocus: () => boolean | undefined;
|
|
263
|
+
} {
|
|
264
|
+
return {
|
|
265
|
+
opened: this.toolboxInstance?.opened,
|
|
266
|
+
close: () => {
|
|
267
|
+
this.toolboxInstance?.close();
|
|
268
|
+
},
|
|
269
|
+
open: () => {
|
|
270
|
+
/**
|
|
271
|
+
* If Toolbox is not initialized yet, do nothing
|
|
272
|
+
*/
|
|
273
|
+
if (this.toolboxInstance === null) {
|
|
274
|
+
log('toolbox.open() called before initialization is finished', 'warn');
|
|
275
|
+
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Set current block to cover the case when the Toolbar showed near hovered Block but caret is set to another Block.
|
|
281
|
+
*/
|
|
282
|
+
if (this.hoveredBlock) {
|
|
283
|
+
this.Blok.BlockManager.currentBlock = this.hoveredBlock;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
this.toolboxInstance.open();
|
|
287
|
+
},
|
|
288
|
+
toggle: () => {
|
|
289
|
+
/**
|
|
290
|
+
* If Toolbox is not initialized yet, do nothing
|
|
291
|
+
*/
|
|
292
|
+
if (this.toolboxInstance === null) {
|
|
293
|
+
log('toolbox.toggle() called before initialization is finished', 'warn');
|
|
294
|
+
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
this.toolboxInstance.toggle();
|
|
299
|
+
},
|
|
300
|
+
hasFocus: () => this.toolboxInstance?.hasFocus(),
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Block actions appearance manipulations
|
|
306
|
+
*/
|
|
307
|
+
private get blockActions(): { hide: () => void; show: () => void } {
|
|
308
|
+
return {
|
|
309
|
+
hide: (): void => {
|
|
310
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
311
|
+
this.nodes.actions?.classList.remove(this.CSS.actionsOpened);
|
|
312
|
+
this.nodes.actions?.removeAttribute('data-blok-opened');
|
|
313
|
+
},
|
|
314
|
+
show: (): void => {
|
|
315
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
316
|
+
this.nodes.actions?.classList.add(this.CSS.actionsOpened);
|
|
317
|
+
this.nodes.actions?.setAttribute('data-blok-opened', 'true');
|
|
318
|
+
},
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Methods for working with Block Tunes toggler
|
|
324
|
+
*/
|
|
325
|
+
private get blockTunesToggler(): { hide: () => void; show: () => void } {
|
|
326
|
+
return {
|
|
327
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
328
|
+
hide: (): void => this.nodes.settingsToggler?.classList.add(this.CSS.settingsTogglerHidden),
|
|
329
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
330
|
+
show: (): void => this.nodes.settingsToggler?.classList.remove(this.CSS.settingsTogglerHidden),
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Toggles read-only mode
|
|
337
|
+
* @param {boolean} readOnlyEnabled - read-only mode
|
|
338
|
+
*/
|
|
339
|
+
public toggleReadOnly(readOnlyEnabled: boolean): void {
|
|
340
|
+
if (!readOnlyEnabled) {
|
|
341
|
+
window.requestIdleCallback(async () => {
|
|
342
|
+
await this.drawUI();
|
|
343
|
+
this.enableModuleBindings();
|
|
344
|
+
}, { timeout: 2000 });
|
|
345
|
+
} else {
|
|
346
|
+
this.destroy();
|
|
347
|
+
this.Blok.BlockSettings.destroy();
|
|
348
|
+
this.disableModuleBindings();
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Move Toolbar to the passed (or current) Block
|
|
354
|
+
* @param block - block to move Toolbar near it
|
|
355
|
+
* @param target - optional target element that was hovered (for content offset calculation)
|
|
356
|
+
*/
|
|
357
|
+
public moveAndOpen(block?: Block | null, target?: Element | null): void {
|
|
358
|
+
/**
|
|
359
|
+
* Some UI elements creates inside requestIdleCallback, so the can be not ready yet
|
|
360
|
+
*/
|
|
361
|
+
if (this.toolboxInstance === null) {
|
|
362
|
+
log('Can\'t open Toolbar since Blok initialization is not finished yet', 'warn');
|
|
363
|
+
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Close Toolbox when we move toolbar
|
|
369
|
+
*/
|
|
370
|
+
if (this.toolboxInstance.opened) {
|
|
371
|
+
this.toolboxInstance.close();
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if (this.Blok.BlockSettings.opened) {
|
|
375
|
+
this.Blok.BlockSettings.close();
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* If no one Block selected as a Current
|
|
380
|
+
*/
|
|
381
|
+
const targetBlock = block ?? this.Blok.BlockManager.currentBlock;
|
|
382
|
+
|
|
383
|
+
if (!targetBlock) {
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/** Clean up draggable on previous block if any */
|
|
388
|
+
if (this.hoveredBlock && this.hoveredBlock !== targetBlock) {
|
|
389
|
+
this.hoveredBlock.cleanupDraggable();
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
this.hoveredBlock = targetBlock;
|
|
393
|
+
this.hoveredTarget = target ?? null;
|
|
394
|
+
this.lastToolbarY = null; // Reset cached position when moving to a new block
|
|
395
|
+
|
|
396
|
+
const { wrapper, plusButton, settingsToggler } = this.nodes;
|
|
397
|
+
|
|
398
|
+
if (!wrapper || !plusButton) {
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const targetBlockHolder = targetBlock.holder;
|
|
403
|
+
const { isMobile } = this.Blok.UI;
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
const toolbarY = this.calculateToolbarY(targetBlock, plusButton, isMobile);
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Move Toolbar to the Top coordinate of Block
|
|
410
|
+
*/
|
|
411
|
+
const newToolbarY = Math.floor(toolbarY);
|
|
412
|
+
|
|
413
|
+
this.lastToolbarY = newToolbarY;
|
|
414
|
+
wrapper.style.top = `${newToolbarY}px`;
|
|
415
|
+
targetBlockHolder.appendChild(wrapper);
|
|
416
|
+
|
|
417
|
+
/** Set up draggable on the target block using the settings toggler as drag handle */
|
|
418
|
+
if (settingsToggler && !this.Blok.ReadOnly.isEnabled) {
|
|
419
|
+
targetBlock.setupDraggable(settingsToggler, this.Blok.DragManager);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Apply content offset for nested elements (e.g., nested list items)
|
|
424
|
+
*/
|
|
425
|
+
this.applyContentOffset(targetBlock);
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Do not show Block Tunes Toggler near single and empty block
|
|
429
|
+
*/
|
|
430
|
+
const tunes = targetBlock.getTunes();
|
|
431
|
+
const hasAnyTunes = tunes.toolTunes.length > 0 || tunes.commonTunes.length > 0;
|
|
432
|
+
|
|
433
|
+
if (this.Blok.BlockManager.blocks.length === 1 && targetBlock.isEmpty && !hasAnyTunes) {
|
|
434
|
+
this.blockTunesToggler.hide();
|
|
435
|
+
} else {
|
|
436
|
+
this.blockTunesToggler.show();
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
this.open();
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Move Toolbar to the specified block (or first selected block) and open it for multi-block selection.
|
|
444
|
+
* Keeps the add button visible so users can still insert blocks while multiple are selected.
|
|
445
|
+
* @param block - optional block to position the toolbar at (defaults to first selected block)
|
|
446
|
+
*/
|
|
447
|
+
public moveAndOpenForMultipleBlocks(block?: Block): void {
|
|
448
|
+
const selectedBlocks = this.Blok.BlockSelection.selectedBlocks;
|
|
449
|
+
|
|
450
|
+
if (selectedBlocks.length < 2) {
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Some UI elements creates inside requestIdleCallback, so they can be not ready yet
|
|
456
|
+
*/
|
|
457
|
+
if (this.toolboxInstance === null) {
|
|
458
|
+
log('Can\'t open Toolbar since Blok initialization is not finished yet', 'warn');
|
|
459
|
+
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Close Toolbox when we move toolbar
|
|
465
|
+
*/
|
|
466
|
+
if (this.toolboxInstance.opened) {
|
|
467
|
+
this.toolboxInstance.close();
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (this.Blok.BlockSettings.opened) {
|
|
471
|
+
this.Blok.BlockSettings.close();
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Use the provided block or fall back to the first selected block as the anchor for the toolbar
|
|
476
|
+
*/
|
|
477
|
+
const targetBlock = block ?? selectedBlocks[0];
|
|
478
|
+
|
|
479
|
+
/** Clean up draggable on previous block if any */
|
|
480
|
+
if (this.hoveredBlock && this.hoveredBlock !== targetBlock) {
|
|
481
|
+
this.hoveredBlock.cleanupDraggable();
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
this.hoveredBlock = targetBlock;
|
|
485
|
+
this.hoveredTarget = null; // No target for multi-block selection
|
|
486
|
+
this.lastToolbarY = null; // Reset cached position when moving to a new block
|
|
487
|
+
|
|
488
|
+
const { wrapper, plusButton } = this.nodes;
|
|
489
|
+
|
|
490
|
+
if (!wrapper || !plusButton) {
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
const targetBlockHolder = targetBlock.holder;
|
|
495
|
+
|
|
496
|
+
const newToolbarY = Math.floor(this.calculateToolbarY(targetBlock, plusButton, false));
|
|
497
|
+
|
|
498
|
+
this.lastToolbarY = newToolbarY;
|
|
499
|
+
wrapper.style.top = `${newToolbarY}px`;
|
|
500
|
+
targetBlockHolder.appendChild(wrapper);
|
|
501
|
+
|
|
502
|
+
/** Set up draggable on the target block using the settings toggler as drag handle */
|
|
503
|
+
const { settingsToggler } = this.nodes;
|
|
504
|
+
|
|
505
|
+
if (settingsToggler && !this.Blok.ReadOnly.isEnabled) {
|
|
506
|
+
targetBlock.setupDraggable(settingsToggler, this.Blok.DragManager);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Reset content offset for multi-block selection
|
|
511
|
+
*/
|
|
512
|
+
this.applyContentOffset(targetBlock);
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Always show the settings toggler for multi-block selection
|
|
516
|
+
*/
|
|
517
|
+
this.blockTunesToggler.show();
|
|
518
|
+
|
|
519
|
+
this.open();
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Close the Toolbar
|
|
524
|
+
*/
|
|
525
|
+
public close(): void {
|
|
526
|
+
if (this.Blok.ReadOnly.isEnabled) {
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
531
|
+
this.nodes.wrapper?.classList.remove(this.CSS.toolbarOpened);
|
|
532
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
533
|
+
this.nodes.wrapper?.classList.add(this.CSS.toolbarClosed);
|
|
534
|
+
this.nodes.wrapper?.removeAttribute('data-blok-opened');
|
|
535
|
+
|
|
536
|
+
/** Close components */
|
|
537
|
+
this.blockActions.hide();
|
|
538
|
+
this.toolboxInstance?.close();
|
|
539
|
+
this.Blok.BlockSettings.close();
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Restore plus button visibility in case it was hidden by other interactions
|
|
543
|
+
*/
|
|
544
|
+
if (this.nodes.plusButton) {
|
|
545
|
+
this.nodes.plusButton.style.display = '';
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* Reset the content offset transform
|
|
550
|
+
*/
|
|
551
|
+
if (this.nodes.actions) {
|
|
552
|
+
this.nodes.actions.style.transform = '';
|
|
553
|
+
}
|
|
554
|
+
this.hoveredTarget = null;
|
|
555
|
+
|
|
556
|
+
this.reset();
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* Prevents the settings menu from opening on the next mouseup event
|
|
561
|
+
* Used after block drop to avoid accidental menu opening
|
|
562
|
+
*/
|
|
563
|
+
public skipNextSettingsToggle(): void {
|
|
564
|
+
this.ignoreNextSettingsMouseUp = true;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Reset the Toolbar position to prevent DOM height growth, for example after blocks deletion
|
|
569
|
+
*/
|
|
570
|
+
private reset(): void {
|
|
571
|
+
this.lastToolbarY = null; // Reset cached position when toolbar is reset
|
|
572
|
+
|
|
573
|
+
if (this.nodes.wrapper) {
|
|
574
|
+
this.nodes.wrapper.style.top = 'unset';
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* Move Toolbar back to the Blok wrapper to save it from deletion
|
|
578
|
+
*/
|
|
579
|
+
this.Blok.UI.nodes.wrapper.appendChild(this.nodes.wrapper);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* Open Toolbar with Plus Button and Actions
|
|
585
|
+
* @param {boolean} withBlockActions - by default, Toolbar opens with Block Actions.
|
|
586
|
+
* This flag allows to open Toolbar without Actions.
|
|
587
|
+
*/
|
|
588
|
+
private open(withBlockActions = true): void {
|
|
589
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
590
|
+
this.nodes.wrapper?.classList.remove(this.CSS.toolbarClosed);
|
|
591
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
592
|
+
this.nodes.wrapper?.classList.add(this.CSS.toolbarOpened);
|
|
593
|
+
this.nodes.wrapper?.setAttribute('data-blok-opened', 'true');
|
|
594
|
+
|
|
595
|
+
if (withBlockActions) {
|
|
596
|
+
this.blockActions.show();
|
|
597
|
+
} else {
|
|
598
|
+
this.blockActions.hide();
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Draws Toolbar elements
|
|
604
|
+
*/
|
|
605
|
+
private async make(): Promise<void> {
|
|
606
|
+
const wrapper = $.make('div', [
|
|
607
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated -- CSS getter now returns Tailwind classes
|
|
608
|
+
this.CSS.toolbar,
|
|
609
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated -- CSS getter now returns Tailwind classes
|
|
610
|
+
this.CSS.toolbarClosed,
|
|
611
|
+
'group-data-[blok-dragging=true]:pointer-events-none',
|
|
612
|
+
]);
|
|
613
|
+
|
|
614
|
+
this.nodes.wrapper = wrapper;
|
|
615
|
+
wrapper.setAttribute(DATA_ATTR.toolbar, '');
|
|
616
|
+
wrapper.setAttribute('data-blok-testid', 'toolbar');
|
|
617
|
+
|
|
618
|
+
/**
|
|
619
|
+
* Make Content Zone and Actions Zone
|
|
620
|
+
*/
|
|
621
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated -- CSS getter now returns Tailwind classes
|
|
622
|
+
const content = $.make('div', this.CSS.content);
|
|
623
|
+
const actions = $.make('div', [
|
|
624
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated -- CSS getter now returns Tailwind classes
|
|
625
|
+
this.CSS.actions,
|
|
626
|
+
// Narrow mode positioning on non-mobile screens
|
|
627
|
+
'not-mobile:group-data-[blok-narrow=true]:right-[calc(-1*theme(spacing.narrow-mode-right-padding)-5px)]',
|
|
628
|
+
// RTL narrow mode: use left positioning instead
|
|
629
|
+
'not-mobile:group-data-[blok-narrow=true]:group-data-[blok-rtl=true]:right-auto',
|
|
630
|
+
'not-mobile:group-data-[blok-narrow=true]:group-data-[blok-rtl=true]:left-[calc(-1*theme(spacing.narrow-mode-right-padding)-5px)]',
|
|
631
|
+
// RTL narrow mode additional left offset
|
|
632
|
+
'not-mobile:group-data-[blok-narrow=true]:group-data-[blok-rtl=true]:left-[-5px]',
|
|
633
|
+
]);
|
|
634
|
+
|
|
635
|
+
this.nodes.content = content;
|
|
636
|
+
|
|
637
|
+
this.nodes.actions = actions;
|
|
638
|
+
actions.setAttribute('data-blok-testid', 'toolbar-actions');
|
|
639
|
+
|
|
640
|
+
/**
|
|
641
|
+
* Actions will be included to the toolbar content so we can align in to the right of the content
|
|
642
|
+
*/
|
|
643
|
+
$.append(wrapper, content);
|
|
644
|
+
$.append(content, actions);
|
|
645
|
+
|
|
646
|
+
/**
|
|
647
|
+
* Fill Content Zone:
|
|
648
|
+
* - Plus Button
|
|
649
|
+
* - Toolbox
|
|
650
|
+
*/
|
|
651
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
652
|
+
const plusButton = $.make('div', this.CSS.plusButton, {
|
|
653
|
+
innerHTML: IconPlus,
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
plusButton.setAttribute('data-blok-testid', 'plus-button');
|
|
657
|
+
|
|
658
|
+
this.nodes.plusButton = plusButton;
|
|
659
|
+
$.append(actions, plusButton);
|
|
660
|
+
|
|
661
|
+
/**
|
|
662
|
+
* Plus button mousedown handler
|
|
663
|
+
* Stores the initial mouse position and sets up a document-level mouseup listener.
|
|
664
|
+
* Using document-level mouseup ensures we catch the event even if the mouse
|
|
665
|
+
* moves slightly off the button element during the click.
|
|
666
|
+
*/
|
|
667
|
+
this.readOnlyMutableListeners.on(plusButton, 'mousedown', (e) => {
|
|
668
|
+
hide();
|
|
669
|
+
|
|
670
|
+
const mouseEvent = e as MouseEvent;
|
|
671
|
+
|
|
672
|
+
/**
|
|
673
|
+
* Store the mouse position when mousedown occurs
|
|
674
|
+
* This will be used to determine if the user dragged or clicked
|
|
675
|
+
*/
|
|
676
|
+
this.plusButtonMouseDownPosition = {
|
|
677
|
+
x: mouseEvent.clientX,
|
|
678
|
+
y: mouseEvent.clientY,
|
|
679
|
+
};
|
|
680
|
+
|
|
681
|
+
/**
|
|
682
|
+
* Add document-level mouseup listener to catch the event even if mouse
|
|
683
|
+
* moves slightly off the button. This is removed after firing once.
|
|
684
|
+
*/
|
|
685
|
+
const onMouseUp = (mouseUpEvent: MouseEvent): void => {
|
|
686
|
+
document.removeEventListener('mouseup', onMouseUp, true);
|
|
687
|
+
|
|
688
|
+
const mouseDownPos = this.plusButtonMouseDownPosition;
|
|
689
|
+
|
|
690
|
+
this.plusButtonMouseDownPosition = null;
|
|
691
|
+
|
|
692
|
+
if (mouseDownPos === null) {
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
const wasDragged = (
|
|
697
|
+
Math.abs(mouseUpEvent.clientX - mouseDownPos.x) > DRAG_THRESHOLD ||
|
|
698
|
+
Math.abs(mouseUpEvent.clientY - mouseDownPos.y) > DRAG_THRESHOLD
|
|
699
|
+
);
|
|
700
|
+
|
|
701
|
+
if (wasDragged) {
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
/**
|
|
706
|
+
* Check for modifier key to determine insert direction:
|
|
707
|
+
* - Option/Alt on Mac, Ctrl on Windows → insert above
|
|
708
|
+
* - No modifier → insert below (default)
|
|
709
|
+
*/
|
|
710
|
+
const userOS = getUserOS();
|
|
711
|
+
const insertAbove = userOS.win ? mouseUpEvent.ctrlKey : mouseUpEvent.altKey;
|
|
712
|
+
|
|
713
|
+
this.plusButtonClicked(insertAbove);
|
|
714
|
+
};
|
|
715
|
+
|
|
716
|
+
document.addEventListener('mouseup', onMouseUp, true);
|
|
717
|
+
}, true);
|
|
718
|
+
|
|
719
|
+
/**
|
|
720
|
+
* Add events to show/hide tooltip for plus button
|
|
721
|
+
*/
|
|
722
|
+
const userOS = getUserOS();
|
|
723
|
+
const modifierClickText = userOS.win
|
|
724
|
+
? this.Blok.I18n.t('toolbox.ctrlAddAbove')
|
|
725
|
+
: this.Blok.I18n.t('toolbox.optionAddAbove');
|
|
726
|
+
|
|
727
|
+
const tooltipContent = this.createTooltipContent([
|
|
728
|
+
this.Blok.I18n.t('toolbox.addBelow'),
|
|
729
|
+
modifierClickText,
|
|
730
|
+
]);
|
|
731
|
+
|
|
732
|
+
onHover(plusButton, tooltipContent, {
|
|
733
|
+
delay: 500,
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
/**
|
|
737
|
+
* Fill Actions Zone:
|
|
738
|
+
* - Settings Toggler
|
|
739
|
+
* - Remove Block Button
|
|
740
|
+
* - Settings Panel
|
|
741
|
+
*/
|
|
742
|
+
const settingsToggler = $.make('span', [
|
|
743
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
744
|
+
this.CSS.settingsToggler,
|
|
745
|
+
'group-data-[blok-dragging=true]:cursor-grabbing',
|
|
746
|
+
], {
|
|
747
|
+
innerHTML: IconMenu,
|
|
748
|
+
});
|
|
749
|
+
|
|
750
|
+
settingsToggler.setAttribute(DATA_ATTR.settingsToggler, '');
|
|
751
|
+
settingsToggler.setAttribute(DATA_ATTR.dragHandle, '');
|
|
752
|
+
settingsToggler.setAttribute('data-blok-testid', 'settings-toggler');
|
|
753
|
+
|
|
754
|
+
// Accessibility: make the drag handle accessible to screen readers
|
|
755
|
+
// Using tabindex="-1" keeps it accessible but removes from tab order
|
|
756
|
+
// Users can move blocks with keyboard shortcuts (Cmd/Ctrl+Shift+Arrow)
|
|
757
|
+
settingsToggler.setAttribute('role', 'button');
|
|
758
|
+
settingsToggler.setAttribute('tabindex', '-1');
|
|
759
|
+
settingsToggler.setAttribute(
|
|
760
|
+
'aria-label',
|
|
761
|
+
this.Blok.I18n.t('a11y.dragHandle')
|
|
762
|
+
);
|
|
763
|
+
settingsToggler.setAttribute(
|
|
764
|
+
'aria-roledescription',
|
|
765
|
+
this.Blok.I18n.t('a11y.dragHandleRole')
|
|
766
|
+
);
|
|
767
|
+
|
|
768
|
+
this.nodes.settingsToggler = settingsToggler;
|
|
769
|
+
|
|
770
|
+
$.append(actions, settingsToggler);
|
|
771
|
+
|
|
772
|
+
const blockTunesTooltip = this.createTooltipContent([
|
|
773
|
+
this.Blok.I18n.t('blockSettings.dragToMove'),
|
|
774
|
+
this.Blok.I18n.t('blockSettings.clickToOpenMenu'),
|
|
775
|
+
]);
|
|
776
|
+
|
|
777
|
+
onHover(settingsToggler, blockTunesTooltip, {
|
|
778
|
+
delay: 500,
|
|
779
|
+
});
|
|
780
|
+
|
|
781
|
+
/**
|
|
782
|
+
* Appending Toolbar components to itself
|
|
783
|
+
*/
|
|
784
|
+
$.append(actions, this.makeToolbox());
|
|
785
|
+
|
|
786
|
+
const blockSettingsElement = this.Blok.BlockSettings.getElement();
|
|
787
|
+
|
|
788
|
+
if (!blockSettingsElement) {
|
|
789
|
+
throw new Error('Block Settings element was not created');
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
$.append(actions, blockSettingsElement);
|
|
793
|
+
|
|
794
|
+
/**
|
|
795
|
+
* Append toolbar to the Blok
|
|
796
|
+
*/
|
|
797
|
+
$.append(this.Blok.UI.nodes.wrapper, wrapper);
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
/**
|
|
801
|
+
* Creates the Toolbox instance and return it's rendered element
|
|
802
|
+
*/
|
|
803
|
+
private makeToolbox(): Element {
|
|
804
|
+
/**
|
|
805
|
+
* Make the Toolbox
|
|
806
|
+
*/
|
|
807
|
+
this.toolboxInstance = new Toolbox({
|
|
808
|
+
api: this.Blok.API.methods,
|
|
809
|
+
tools: this.Blok.Tools.blockTools,
|
|
810
|
+
i18nLabels: {
|
|
811
|
+
filter: this.Blok.I18n.t('popover.search'),
|
|
812
|
+
nothingFound: this.Blok.I18n.t('popover.nothingFound'),
|
|
813
|
+
},
|
|
814
|
+
i18n: this.Blok.I18n,
|
|
815
|
+
triggerElement: this.nodes.plusButton,
|
|
816
|
+
});
|
|
817
|
+
|
|
818
|
+
this.toolboxInstance.on(ToolboxEvent.Opened, () => {
|
|
819
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
820
|
+
this.Blok.UI.nodes.wrapper.classList.add(this.CSS.openedToolboxHolderModifier);
|
|
821
|
+
this.Blok.UI.nodes.wrapper.setAttribute(DATA_ATTR.toolboxOpened, 'true');
|
|
822
|
+
});
|
|
823
|
+
|
|
824
|
+
this.toolboxInstance.on(ToolboxEvent.Closed, () => {
|
|
825
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
826
|
+
this.Blok.UI.nodes.wrapper.classList.remove(this.CSS.openedToolboxHolderModifier);
|
|
827
|
+
this.Blok.UI.nodes.wrapper.removeAttribute(DATA_ATTR.toolboxOpened);
|
|
828
|
+
});
|
|
829
|
+
|
|
830
|
+
this.toolboxInstance.on(ToolboxEvent.BlockAdded, ({ block }) => {
|
|
831
|
+
const { BlockManager, Caret } = this.Blok;
|
|
832
|
+
const newBlock = BlockManager.getBlockById(block.id);
|
|
833
|
+
|
|
834
|
+
if (!newBlock) {
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
if (newBlock.inputs.length !== 0) {
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
/**
|
|
843
|
+
* If the new block doesn't contain inputs, insert the new paragraph below
|
|
844
|
+
*/
|
|
845
|
+
if (newBlock === BlockManager.lastBlock) {
|
|
846
|
+
BlockManager.insertAtEnd();
|
|
847
|
+
Caret.setToBlock(BlockManager.lastBlock);
|
|
848
|
+
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
const nextBlock = BlockManager.nextBlock;
|
|
853
|
+
|
|
854
|
+
if (nextBlock) {
|
|
855
|
+
Caret.setToBlock(nextBlock);
|
|
856
|
+
}
|
|
857
|
+
});
|
|
858
|
+
|
|
859
|
+
const element = this.toolboxInstance.getElement();
|
|
860
|
+
|
|
861
|
+
if (element === null) {
|
|
862
|
+
throw new Error('Toolbox element was not created');
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
return element;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
|
|
869
|
+
/**
|
|
870
|
+
* Handler for Plus Button.
|
|
871
|
+
* Inserts "/" into target block and opens toolbox, or toggles toolbox closed if already open.
|
|
872
|
+
* @param insertAbove - if true, insert above the current block instead of below
|
|
873
|
+
*/
|
|
874
|
+
private plusButtonClicked(insertAbove = false): void {
|
|
875
|
+
const { BlockManager, BlockSettings, BlockSelection, Caret } = this.Blok;
|
|
876
|
+
|
|
877
|
+
// Close other menus and clear selections
|
|
878
|
+
if (BlockSettings.opened) {
|
|
879
|
+
BlockSettings.close();
|
|
880
|
+
}
|
|
881
|
+
if (BlockSelection.anyBlockSelected) {
|
|
882
|
+
BlockSelection.clearSelection();
|
|
883
|
+
}
|
|
884
|
+
SelectionUtils.get()?.removeAllRanges();
|
|
885
|
+
|
|
886
|
+
// Toggle closed if already open
|
|
887
|
+
if (this.toolbox.opened) {
|
|
888
|
+
this.toolbox.close();
|
|
889
|
+
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
// Determine target block: reuse empty/slash paragraph, or create new one
|
|
894
|
+
const hoveredBlock = this.hoveredBlock;
|
|
895
|
+
const isParagraph = hoveredBlock?.name === 'paragraph';
|
|
896
|
+
const startsWithSlash = isParagraph && hoveredBlock.pluginsContent.textContent?.startsWith('/');
|
|
897
|
+
const isEmptyParagraph = isParagraph && hoveredBlock.isEmpty;
|
|
898
|
+
|
|
899
|
+
// Calculate insert index based on direction
|
|
900
|
+
const hoveredBlockIndex = hoveredBlock !== null
|
|
901
|
+
? BlockManager.getBlockIndex(hoveredBlock)
|
|
902
|
+
: BlockManager.currentBlockIndex;
|
|
903
|
+
const insertIndex = insertAbove ? hoveredBlockIndex : hoveredBlockIndex + 1;
|
|
904
|
+
|
|
905
|
+
const targetBlock = isEmptyParagraph || startsWithSlash
|
|
906
|
+
? hoveredBlock
|
|
907
|
+
: BlockManager.insertDefaultBlockAtIndex(insertIndex, true);
|
|
908
|
+
|
|
909
|
+
// Insert "/" or position caret after existing one
|
|
910
|
+
if (startsWithSlash) {
|
|
911
|
+
Caret.setToBlock(targetBlock, Caret.positions.DEFAULT, 1);
|
|
912
|
+
} else {
|
|
913
|
+
Caret.setToBlock(targetBlock, Caret.positions.START);
|
|
914
|
+
Caret.insertContentAtCaretPosition('/');
|
|
915
|
+
}
|
|
916
|
+
this.moveAndOpen(targetBlock);
|
|
917
|
+
this.toolbox.open();
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
/**
|
|
921
|
+
* Enable bindings
|
|
922
|
+
*/
|
|
923
|
+
private enableModuleBindings(): void {
|
|
924
|
+
/**
|
|
925
|
+
* Settings toggler
|
|
926
|
+
*
|
|
927
|
+
* mousedown is used because on click selection is lost in Safari and FF
|
|
928
|
+
*/
|
|
929
|
+
const settingsToggler = this.nodes.settingsToggler;
|
|
930
|
+
|
|
931
|
+
if (settingsToggler) {
|
|
932
|
+
/**
|
|
933
|
+
* Settings toggler mousedown handler
|
|
934
|
+
* Stores the initial mouse position and sets up a document-level mouseup listener.
|
|
935
|
+
* Using document-level mouseup ensures we catch the event even if the mouse
|
|
936
|
+
* moves slightly off the toggler element during the click.
|
|
937
|
+
*/
|
|
938
|
+
this.readOnlyMutableListeners.on(settingsToggler, 'mousedown', (e) => {
|
|
939
|
+
hide();
|
|
940
|
+
|
|
941
|
+
const mouseEvent = e as MouseEvent;
|
|
942
|
+
|
|
943
|
+
/**
|
|
944
|
+
* Store the mouse position when mousedown occurs
|
|
945
|
+
* This will be used to determine if the user dragged or clicked
|
|
946
|
+
*/
|
|
947
|
+
this.settingsTogglerMouseDownPosition = {
|
|
948
|
+
x: mouseEvent.clientX,
|
|
949
|
+
y: mouseEvent.clientY,
|
|
950
|
+
};
|
|
951
|
+
|
|
952
|
+
/**
|
|
953
|
+
* Add document-level mouseup listener to catch the event even if mouse
|
|
954
|
+
* moves slightly off the toggler. This is removed after firing once.
|
|
955
|
+
*/
|
|
956
|
+
const onMouseUp = (mouseUpEvent: MouseEvent): void => {
|
|
957
|
+
document.removeEventListener('mouseup', onMouseUp, true);
|
|
958
|
+
|
|
959
|
+
/**
|
|
960
|
+
* Ignore mouseup after a block drop to prevent settings menu from opening
|
|
961
|
+
*/
|
|
962
|
+
if (this.ignoreNextSettingsMouseUp) {
|
|
963
|
+
this.ignoreNextSettingsMouseUp = false;
|
|
964
|
+
this.settingsTogglerMouseDownPosition = null;
|
|
965
|
+
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
const mouseDownPos = this.settingsTogglerMouseDownPosition;
|
|
970
|
+
|
|
971
|
+
this.settingsTogglerMouseDownPosition = null;
|
|
972
|
+
|
|
973
|
+
if (mouseDownPos === null) {
|
|
974
|
+
return;
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
const wasDragged = (
|
|
978
|
+
Math.abs(mouseUpEvent.clientX - mouseDownPos.x) > DRAG_THRESHOLD ||
|
|
979
|
+
Math.abs(mouseUpEvent.clientY - mouseDownPos.y) > DRAG_THRESHOLD
|
|
980
|
+
);
|
|
981
|
+
|
|
982
|
+
if (wasDragged) {
|
|
983
|
+
return;
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
this.settingsTogglerClicked();
|
|
987
|
+
|
|
988
|
+
if (this.toolboxInstance?.opened) {
|
|
989
|
+
this.toolboxInstance.close();
|
|
990
|
+
}
|
|
991
|
+
};
|
|
992
|
+
|
|
993
|
+
document.addEventListener('mouseup', onMouseUp, true);
|
|
994
|
+
}, true);
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
/**
|
|
998
|
+
* Subscribe to the 'block-hovered' event if current view is not mobile
|
|
999
|
+
* @see https://github.com/codex-team/editor.js/issues/1972
|
|
1000
|
+
*/
|
|
1001
|
+
if (!isMobileScreen()) {
|
|
1002
|
+
/**
|
|
1003
|
+
* Subscribe to the 'block-hovered' event
|
|
1004
|
+
*/
|
|
1005
|
+
this.eventsDispatcher.on(BlockHovered, (data) => {
|
|
1006
|
+
/**
|
|
1007
|
+
* Do not move toolbar during drag or rectangle selection operations
|
|
1008
|
+
*/
|
|
1009
|
+
if (this.Blok.DragManager.isDragging || this.Blok.RectangleSelection.isRectActivated()) {
|
|
1010
|
+
return;
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
const hoveredBlock = (data as { block?: Block; target?: Element }).block;
|
|
1014
|
+
const hoveredTarget = (data as { block?: Block; target?: Element }).target;
|
|
1015
|
+
|
|
1016
|
+
if (!(hoveredBlock instanceof Block)) {
|
|
1017
|
+
return;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
/**
|
|
1021
|
+
* Do not move toolbar if Block Settings or Toolbox opened
|
|
1022
|
+
*/
|
|
1023
|
+
if (this.Blok.BlockSettings.opened || this.toolboxInstance?.opened) {
|
|
1024
|
+
return;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
/**
|
|
1028
|
+
* Check if multiple blocks are selected
|
|
1029
|
+
*/
|
|
1030
|
+
const selectedBlocks = this.Blok.BlockSelection.selectedBlocks;
|
|
1031
|
+
const isMultiBlockSelection = selectedBlocks.length > 1;
|
|
1032
|
+
const isHoveredBlockSelected = isMultiBlockSelection && selectedBlocks.some(block => block === hoveredBlock);
|
|
1033
|
+
|
|
1034
|
+
/**
|
|
1035
|
+
* For multi-block selection, only move toolbar if the hovered block is one of the selected blocks
|
|
1036
|
+
*/
|
|
1037
|
+
if (isMultiBlockSelection && isHoveredBlockSelected) {
|
|
1038
|
+
this.moveAndOpenForMultipleBlocks(hoveredBlock);
|
|
1039
|
+
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
/**
|
|
1044
|
+
* For multi-block selection where hovered block is not selected, do nothing
|
|
1045
|
+
*/
|
|
1046
|
+
if (isMultiBlockSelection) {
|
|
1047
|
+
return;
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
this.moveAndOpen(hoveredBlock, hoveredTarget);
|
|
1051
|
+
});
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
/**
|
|
1055
|
+
* Subscribe to the Block Settings events to toggle 'opened' state of the Settings Toggler
|
|
1056
|
+
*/
|
|
1057
|
+
this.eventsDispatcher.on(BlockSettingsOpened, this.onBlockSettingsOpen);
|
|
1058
|
+
this.eventsDispatcher.on(BlockSettingsClosed, this.onBlockSettingsClose);
|
|
1059
|
+
|
|
1060
|
+
/**
|
|
1061
|
+
* Subscribe to block changes to reposition toolbar when block content changes
|
|
1062
|
+
*/
|
|
1063
|
+
this.eventsDispatcher.on(BlockChanged, this.onBlockChanged);
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
/**
|
|
1067
|
+
* Disable bindings
|
|
1068
|
+
*/
|
|
1069
|
+
private disableModuleBindings(): void {
|
|
1070
|
+
this.readOnlyMutableListeners.clearAll();
|
|
1071
|
+
this.eventsDispatcher.off(BlockSettingsOpened, this.onBlockSettingsOpen);
|
|
1072
|
+
this.eventsDispatcher.off(BlockSettingsClosed, this.onBlockSettingsClose);
|
|
1073
|
+
this.eventsDispatcher.off(BlockChanged, this.onBlockChanged);
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
/**
|
|
1077
|
+
* Handler for BlockSettingsOpened event
|
|
1078
|
+
*/
|
|
1079
|
+
private onBlockSettingsOpen = (): void => {
|
|
1080
|
+
this.Blok.UI.nodes.wrapper.setAttribute(DATA_ATTR.blockSettingsOpened, 'true');
|
|
1081
|
+
};
|
|
1082
|
+
|
|
1083
|
+
/**
|
|
1084
|
+
* Handler for BlockSettingsClosed event
|
|
1085
|
+
*/
|
|
1086
|
+
private onBlockSettingsClose = (): void => {
|
|
1087
|
+
this.Blok.UI.nodes.wrapper.removeAttribute(DATA_ATTR.blockSettingsOpened);
|
|
1088
|
+
};
|
|
1089
|
+
|
|
1090
|
+
/**
|
|
1091
|
+
* Handler for BlockChanged event - repositions toolbar when block content changes
|
|
1092
|
+
*/
|
|
1093
|
+
private onBlockChanged = (payload: BlockChangedPayload): void => {
|
|
1094
|
+
/**
|
|
1095
|
+
* Only reposition if toolbar is opened and we have a hovered block
|
|
1096
|
+
*/
|
|
1097
|
+
if (!this.opened || !this.hoveredBlock) {
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
/**
|
|
1102
|
+
* Don't reposition if Block Settings or Toolbox is opened
|
|
1103
|
+
*/
|
|
1104
|
+
if (this.Blok.BlockSettings.opened || this.toolboxInstance?.opened) {
|
|
1105
|
+
return;
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
/**
|
|
1109
|
+
* Only reposition if the changed block is the hovered block.
|
|
1110
|
+
* This prevents unnecessary repositioning when other blocks change,
|
|
1111
|
+
* and avoids toolbar jumping when interacting with checklist items.
|
|
1112
|
+
*/
|
|
1113
|
+
const changedBlockId = payload.event.detail.target.id;
|
|
1114
|
+
|
|
1115
|
+
if (changedBlockId !== this.hoveredBlock.id) {
|
|
1116
|
+
return;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
this.repositionToolbar();
|
|
1120
|
+
};
|
|
1121
|
+
|
|
1122
|
+
/**
|
|
1123
|
+
* Calculates the Y position for the toolbar, centered on the first line of the block
|
|
1124
|
+
* @param targetBlock - the block to position the toolbar relative to
|
|
1125
|
+
* @param plusButton - the plus button element (used to get toolbar height)
|
|
1126
|
+
* @param isMobile - whether the current view is mobile
|
|
1127
|
+
* @returns the Y position in pixels
|
|
1128
|
+
*/
|
|
1129
|
+
private calculateToolbarY(targetBlock: Block, plusButton: HTMLElement, isMobile: boolean): number {
|
|
1130
|
+
const targetBlockHolder = targetBlock.holder;
|
|
1131
|
+
const holderRect = targetBlockHolder.getBoundingClientRect();
|
|
1132
|
+
|
|
1133
|
+
/**
|
|
1134
|
+
* Use the hovered target element (e.g., a nested list item) if available,
|
|
1135
|
+
* otherwise fall back to the block's pluginsContent
|
|
1136
|
+
*/
|
|
1137
|
+
const listItemElement = this.hoveredTarget?.closest('[role="listitem"]');
|
|
1138
|
+
/**
|
|
1139
|
+
* For list items, find the actual text content element ([contenteditable]) and use its position
|
|
1140
|
+
* to properly center the toolbar on the text, not on the marker which may have different font-size
|
|
1141
|
+
*/
|
|
1142
|
+
const textElement = listItemElement?.querySelector('[contenteditable]');
|
|
1143
|
+
const contentElement = textElement ?? listItemElement ?? targetBlock.pluginsContent;
|
|
1144
|
+
const contentRect = contentElement.getBoundingClientRect();
|
|
1145
|
+
const contentOffset = contentRect.top - holderRect.top;
|
|
1146
|
+
|
|
1147
|
+
const contentStyle = window.getComputedStyle(contentElement);
|
|
1148
|
+
const contentPaddingTop = parseInt(contentStyle.paddingTop, 10) || 0;
|
|
1149
|
+
const lineHeight = parseFloat(contentStyle.lineHeight) || 24;
|
|
1150
|
+
const toolbarHeight = parseInt(window.getComputedStyle(plusButton).height, 10);
|
|
1151
|
+
|
|
1152
|
+
if (isMobile) {
|
|
1153
|
+
return contentOffset - toolbarHeight;
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
const firstLineTop = contentOffset + contentPaddingTop;
|
|
1157
|
+
const firstLineCenterY = firstLineTop + (lineHeight / 2);
|
|
1158
|
+
|
|
1159
|
+
return firstLineCenterY - (toolbarHeight / 2);
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
/**
|
|
1163
|
+
* Repositions the toolbar to stay centered on the first line of the current block
|
|
1164
|
+
* without closing/opening toolbox or block settings
|
|
1165
|
+
*/
|
|
1166
|
+
private repositionToolbar(): void {
|
|
1167
|
+
const { wrapper, plusButton } = this.nodes;
|
|
1168
|
+
|
|
1169
|
+
if (!wrapper || !plusButton || !this.hoveredBlock) {
|
|
1170
|
+
return;
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
const newToolbarY = Math.floor(this.calculateToolbarY(this.hoveredBlock, plusButton, this.Blok.UI.isMobile));
|
|
1174
|
+
|
|
1175
|
+
/**
|
|
1176
|
+
* Only update the toolbar position if it has actually changed significantly.
|
|
1177
|
+
* This prevents unnecessary repositioning when block changes don't affect
|
|
1178
|
+
* the toolbar's position (e.g., toggling checkbox styles in a checklist).
|
|
1179
|
+
*
|
|
1180
|
+
* We use a tolerance of 2px to account for:
|
|
1181
|
+
* - Floating-point precision issues in getBoundingClientRect()
|
|
1182
|
+
* - Minor layout changes that don't warrant toolbar repositioning
|
|
1183
|
+
* - Browser rendering differences during DOM mutations
|
|
1184
|
+
*/
|
|
1185
|
+
const POSITION_TOLERANCE = 2;
|
|
1186
|
+
const positionChanged = this.lastToolbarY === null ||
|
|
1187
|
+
Math.abs(newToolbarY - this.lastToolbarY) > POSITION_TOLERANCE;
|
|
1188
|
+
|
|
1189
|
+
if (positionChanged) {
|
|
1190
|
+
this.lastToolbarY = newToolbarY;
|
|
1191
|
+
wrapper.style.top = `${newToolbarY}px`;
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
/**
|
|
1196
|
+
* Applies the content offset transform to the actions element based on the hovered target.
|
|
1197
|
+
* This positions the toolbar closer to nested content like list items.
|
|
1198
|
+
* @param targetBlock - the block to get the content offset from
|
|
1199
|
+
*/
|
|
1200
|
+
private applyContentOffset(targetBlock: Block): void {
|
|
1201
|
+
const { actions } = this.nodes;
|
|
1202
|
+
|
|
1203
|
+
if (!actions) {
|
|
1204
|
+
return;
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
if (!this.hoveredTarget) {
|
|
1208
|
+
actions.style.transform = '';
|
|
1209
|
+
|
|
1210
|
+
return;
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
const contentOffset = targetBlock.getContentOffset(this.hoveredTarget);
|
|
1214
|
+
const hasValidOffset = contentOffset && contentOffset.left > 0;
|
|
1215
|
+
|
|
1216
|
+
actions.style.transform = hasValidOffset ? `translateX(${contentOffset.left}px)` : '';
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
/**
|
|
1220
|
+
* Clicks on the Block Settings toggler
|
|
1221
|
+
*/
|
|
1222
|
+
private settingsTogglerClicked(): void {
|
|
1223
|
+
/**
|
|
1224
|
+
* Cancel any pending drag tracking since we're opening the settings menu
|
|
1225
|
+
* This prevents the drag from starting when the user moves their mouse to the menu
|
|
1226
|
+
*/
|
|
1227
|
+
this.Blok.DragManager.cancelTracking();
|
|
1228
|
+
|
|
1229
|
+
/**
|
|
1230
|
+
* Prefer the hovered block (desktop), fall back to the current block (mobile) so tapping the toggler still works
|
|
1231
|
+
*/
|
|
1232
|
+
const targetBlock = this.hoveredBlock ?? this.Blok.BlockManager.currentBlock;
|
|
1233
|
+
|
|
1234
|
+
if (!targetBlock) {
|
|
1235
|
+
return;
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
this.hoveredBlock = targetBlock;
|
|
1239
|
+
this.Blok.BlockManager.currentBlock = targetBlock;
|
|
1240
|
+
|
|
1241
|
+
if (this.Blok.BlockSettings.opened) {
|
|
1242
|
+
this.Blok.BlockSettings.close();
|
|
1243
|
+
} else {
|
|
1244
|
+
void this.Blok.BlockSettings.open(targetBlock, this.nodes.settingsToggler);
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
/**
|
|
1249
|
+
* Draws Toolbar UI
|
|
1250
|
+
*
|
|
1251
|
+
* Toolbar contains BlockSettings and Toolbox.
|
|
1252
|
+
* That's why at first we draw its components and then Toolbar itself
|
|
1253
|
+
*
|
|
1254
|
+
* Steps:
|
|
1255
|
+
* - Make Toolbar dependent components like BlockSettings, Toolbox and so on
|
|
1256
|
+
* - Make itself and append dependent nodes to itself
|
|
1257
|
+
*
|
|
1258
|
+
*/
|
|
1259
|
+
private async drawUI(): Promise<void> {
|
|
1260
|
+
/**
|
|
1261
|
+
* Make BlockSettings Panel
|
|
1262
|
+
*/
|
|
1263
|
+
this.Blok.BlockSettings.make();
|
|
1264
|
+
|
|
1265
|
+
/**
|
|
1266
|
+
* Make Toolbar
|
|
1267
|
+
*/
|
|
1268
|
+
await this.make();
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
/**
|
|
1272
|
+
* Creates a tooltip content element with multiple lines and consistent styling
|
|
1273
|
+
* @param lines - array of text strings, each will be displayed on its own line
|
|
1274
|
+
* @returns the tooltip container element
|
|
1275
|
+
*/
|
|
1276
|
+
private createTooltipContent(lines: string[]): HTMLElement {
|
|
1277
|
+
const container = $.make('div');
|
|
1278
|
+
|
|
1279
|
+
container.style.display = 'flex';
|
|
1280
|
+
container.style.flexDirection = 'column';
|
|
1281
|
+
container.style.gap = '4px';
|
|
1282
|
+
|
|
1283
|
+
lines.forEach((text) => {
|
|
1284
|
+
const line = $.make('div');
|
|
1285
|
+
const spaceIndex = text.indexOf(' ');
|
|
1286
|
+
|
|
1287
|
+
if (spaceIndex > 0) {
|
|
1288
|
+
const firstWord = text.substring(0, spaceIndex);
|
|
1289
|
+
const rest = text.substring(spaceIndex);
|
|
1290
|
+
const styledWord = $.make('span', null, { textContent: firstWord });
|
|
1291
|
+
|
|
1292
|
+
styledWord.style.color = 'white';
|
|
1293
|
+
line.appendChild(styledWord);
|
|
1294
|
+
line.appendChild(document.createTextNode(rest));
|
|
1295
|
+
} else {
|
|
1296
|
+
line.appendChild(document.createTextNode(text));
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
container.appendChild(line);
|
|
1300
|
+
});
|
|
1301
|
+
|
|
1302
|
+
return container;
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
/**
|
|
1306
|
+
* Removes all created and saved HTMLElements
|
|
1307
|
+
* It is used in Read-Only mode
|
|
1308
|
+
*/
|
|
1309
|
+
private destroy(): void {
|
|
1310
|
+
this.removeAllNodes();
|
|
1311
|
+
if (this.toolboxInstance) {
|
|
1312
|
+
this.toolboxInstance.destroy();
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
}
|