@jackuait/blok 0.4.1-beta.4 → 0.4.1-beta.6
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 +136 -17
- package/codemod/README.md +16 -0
- package/codemod/migrate-editorjs-to-blok.js +868 -92
- package/codemod/test.js +682 -77
- package/dist/blok.mjs +5 -2
- package/dist/chunks/blok-B5qs7C5l.mjs +12838 -0
- package/dist/chunks/i18next-CugVlwWp.mjs +1292 -0
- package/dist/chunks/i18next-loader-CTrK3HzG.mjs +43 -0
- package/dist/{index-XWGz4gev.mjs → chunks/index-DDpzQn-0.mjs} +2 -2
- package/dist/chunks/inline-tool-convert-RBcopmCh.mjs +1988 -0
- package/dist/chunks/messages-2434tVOK.mjs +47 -0
- package/dist/chunks/messages-3DcCwXMF.mjs +47 -0
- package/dist/chunks/messages-4kMwVAKY.mjs +47 -0
- package/dist/chunks/messages-57uL5htT.mjs +47 -0
- package/dist/chunks/messages-76-iJV9Q.mjs +47 -0
- package/dist/chunks/messages-8p86Eyf2.mjs +47 -0
- package/dist/chunks/messages-BBX0p0Pi.mjs +47 -0
- package/dist/chunks/messages-BCm2eudQ.mjs +47 -0
- package/dist/chunks/messages-BFiUomgG.mjs +47 -0
- package/dist/chunks/messages-BIPNHHAV.mjs +47 -0
- package/dist/chunks/messages-BUlwu9mo.mjs +47 -0
- package/dist/chunks/messages-BX-DPa-z.mjs +47 -0
- package/dist/chunks/messages-BextV3Qh.mjs +47 -0
- package/dist/chunks/messages-BiPSFlUG.mjs +47 -0
- package/dist/chunks/messages-BiXe9G-O.mjs +47 -0
- package/dist/chunks/messages-Bl5z_Igo.mjs +47 -0
- package/dist/chunks/messages-BnsE97ku.mjs +47 -0
- package/dist/chunks/messages-BoO8gsVD.mjs +47 -0
- package/dist/chunks/messages-BqWaOGMn.mjs +47 -0
- package/dist/chunks/messages-BqkL2_Ro.mjs +47 -0
- package/dist/chunks/messages-BvCkXKX-.mjs +47 -0
- package/dist/chunks/messages-C6tbPLoj.mjs +47 -0
- package/dist/chunks/messages-CA6T3-gQ.mjs +47 -0
- package/dist/chunks/messages-CFFPFdWP.mjs +47 -0
- package/dist/chunks/messages-CFrKE-TN.mjs +47 -0
- package/dist/chunks/messages-CHz8VlG-.mjs +47 -0
- package/dist/chunks/messages-CLixzySl.mjs +47 -0
- package/dist/chunks/messages-CV7OM_qk.mjs +47 -0
- package/dist/chunks/messages-CXHt3eCC.mjs +47 -0
- package/dist/chunks/messages-CbmsBrB0.mjs +47 -0
- package/dist/chunks/messages-Ceo1KtFx.mjs +47 -0
- package/dist/chunks/messages-Cm0LJLtB.mjs +47 -0
- package/dist/chunks/messages-CmymP_Ar.mjs +47 -0
- package/dist/chunks/messages-D0ohMB5H.mjs +47 -0
- package/dist/chunks/messages-D3GrDwXh.mjs +47 -0
- package/dist/chunks/messages-D3vTzIpL.mjs +47 -0
- package/dist/chunks/messages-D5WeksbV.mjs +47 -0
- package/dist/chunks/messages-DGaab4EP.mjs +47 -0
- package/dist/chunks/messages-DKha57ZU.mjs +47 -0
- package/dist/chunks/messages-DOaujgMW.mjs +47 -0
- package/dist/chunks/messages-DVbPLd_0.mjs +47 -0
- package/dist/chunks/messages-D_FCyfW6.mjs +47 -0
- package/dist/chunks/messages-Dd5iZN3c.mjs +47 -0
- package/dist/chunks/messages-DehM7135.mjs +47 -0
- package/dist/chunks/messages-Dg1OHftD.mjs +47 -0
- package/dist/chunks/messages-Di6Flq-b.mjs +47 -0
- package/dist/chunks/messages-Dqhhex6e.mjs +47 -0
- package/dist/chunks/messages-DueVe0F1.mjs +47 -0
- package/dist/chunks/messages-Dx3eFwI0.mjs +47 -0
- package/dist/chunks/messages-FOtiUoKl.mjs +47 -0
- package/dist/chunks/messages-FTOZNhRD.mjs +47 -0
- package/dist/chunks/messages-IQxGfQIV.mjs +47 -0
- package/dist/chunks/messages-JF2fzCkK.mjs +47 -0
- package/dist/chunks/messages-MOGl7I5v.mjs +47 -0
- package/dist/chunks/messages-QgYhPL-3.mjs +47 -0
- package/dist/chunks/messages-WYWIbQwo.mjs +47 -0
- package/dist/chunks/messages-a6A_LgDv.mjs +47 -0
- package/dist/chunks/messages-bSf31LJi.mjs +47 -0
- package/dist/chunks/messages-diGozhTn.mjs +47 -0
- package/dist/chunks/messages-er-kd-VO.mjs +47 -0
- package/dist/chunks/messages-ez3w5NBn.mjs +47 -0
- package/dist/chunks/messages-f3uXjegd.mjs +47 -0
- package/dist/chunks/messages-ohwI1UGv.mjs +47 -0
- package/dist/chunks/messages-p9BZJaFV.mjs +47 -0
- package/dist/chunks/messages-qIQ4L4rw.mjs +47 -0
- package/dist/chunks/messages-qWkXPggi.mjs +47 -0
- package/dist/chunks/messages-w5foGze_.mjs +47 -0
- package/dist/full.mjs +50 -0
- package/dist/locales.mjs +227 -0
- package/dist/messages-2434tVOK.mjs +47 -0
- package/dist/messages-3DcCwXMF.mjs +47 -0
- package/dist/messages-4kMwVAKY.mjs +47 -0
- package/dist/messages-57uL5htT.mjs +47 -0
- package/dist/messages-76-iJV9Q.mjs +47 -0
- package/dist/messages-8p86Eyf2.mjs +47 -0
- package/dist/messages-BBX0p0Pi.mjs +47 -0
- package/dist/messages-BCm2eudQ.mjs +47 -0
- package/dist/messages-BFiUomgG.mjs +47 -0
- package/dist/messages-BIPNHHAV.mjs +47 -0
- package/dist/messages-BUlwu9mo.mjs +47 -0
- package/dist/messages-BX-DPa-z.mjs +47 -0
- package/dist/messages-BextV3Qh.mjs +47 -0
- package/dist/messages-BiPSFlUG.mjs +47 -0
- package/dist/messages-BiXe9G-O.mjs +47 -0
- package/dist/messages-Bl5z_Igo.mjs +47 -0
- package/dist/messages-BnsE97ku.mjs +47 -0
- package/dist/messages-BoO8gsVD.mjs +47 -0
- package/dist/messages-BqWaOGMn.mjs +47 -0
- package/dist/messages-BqkL2_Ro.mjs +47 -0
- package/dist/messages-BvCkXKX-.mjs +47 -0
- package/dist/messages-C6tbPLoj.mjs +47 -0
- package/dist/messages-CA6T3-gQ.mjs +47 -0
- package/dist/messages-CFFPFdWP.mjs +47 -0
- package/dist/messages-CFrKE-TN.mjs +47 -0
- package/dist/messages-CHz8VlG-.mjs +47 -0
- package/dist/messages-CLixzySl.mjs +47 -0
- package/dist/messages-CV7OM_qk.mjs +47 -0
- package/dist/messages-CXHt3eCC.mjs +47 -0
- package/dist/messages-CbmsBrB0.mjs +47 -0
- package/dist/messages-Ceo1KtFx.mjs +47 -0
- package/dist/messages-Cm0LJLtB.mjs +47 -0
- package/dist/messages-CmymP_Ar.mjs +47 -0
- package/dist/messages-D0ohMB5H.mjs +47 -0
- package/dist/messages-D3GrDwXh.mjs +47 -0
- package/dist/messages-D3vTzIpL.mjs +47 -0
- package/dist/messages-D5WeksbV.mjs +47 -0
- package/dist/messages-DGaab4EP.mjs +47 -0
- package/dist/messages-DKha57ZU.mjs +47 -0
- package/dist/messages-DOaujgMW.mjs +47 -0
- package/dist/messages-DVbPLd_0.mjs +47 -0
- package/dist/messages-D_FCyfW6.mjs +47 -0
- package/dist/messages-Dd5iZN3c.mjs +47 -0
- package/dist/messages-DehM7135.mjs +47 -0
- package/dist/messages-Dg1OHftD.mjs +47 -0
- package/dist/messages-Di6Flq-b.mjs +47 -0
- package/dist/messages-Dqhhex6e.mjs +47 -0
- package/dist/messages-DueVe0F1.mjs +47 -0
- package/dist/messages-Dx3eFwI0.mjs +47 -0
- package/dist/messages-FOtiUoKl.mjs +47 -0
- package/dist/messages-FTOZNhRD.mjs +47 -0
- package/dist/messages-IQxGfQIV.mjs +47 -0
- package/dist/messages-JF2fzCkK.mjs +47 -0
- package/dist/messages-MOGl7I5v.mjs +47 -0
- package/dist/messages-QgYhPL-3.mjs +47 -0
- package/dist/messages-WYWIbQwo.mjs +47 -0
- package/dist/messages-a6A_LgDv.mjs +47 -0
- package/dist/messages-bSf31LJi.mjs +47 -0
- package/dist/messages-diGozhTn.mjs +47 -0
- package/dist/messages-er-kd-VO.mjs +47 -0
- package/dist/messages-ez3w5NBn.mjs +47 -0
- package/dist/messages-f3uXjegd.mjs +47 -0
- package/dist/messages-ohwI1UGv.mjs +47 -0
- package/dist/messages-p9BZJaFV.mjs +47 -0
- package/dist/messages-qIQ4L4rw.mjs +47 -0
- package/dist/messages-qWkXPggi.mjs +47 -0
- package/dist/messages-w5foGze_.mjs +47 -0
- package/dist/tools.mjs +3073 -0
- package/dist/vendor.LICENSE.txt +26 -225
- package/package.json +49 -23
- 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 +1427 -0
- package/src/components/block-tunes/block-tune-delete.ts +51 -0
- package/src/components/blocks.ts +338 -0
- package/src/components/constants/data-attributes.ts +342 -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 +481 -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 +44 -0
- package/src/components/i18n/locales/ar/messages.json +44 -0
- package/src/components/i18n/locales/az/messages.json +44 -0
- package/src/components/i18n/locales/bg/messages.json +44 -0
- package/src/components/i18n/locales/bn/messages.json +44 -0
- package/src/components/i18n/locales/bs/messages.json +44 -0
- package/src/components/i18n/locales/cs/messages.json +44 -0
- package/src/components/i18n/locales/da/messages.json +44 -0
- package/src/components/i18n/locales/de/messages.json +44 -0
- package/src/components/i18n/locales/dv/messages.json +44 -0
- package/src/components/i18n/locales/el/messages.json +44 -0
- package/src/components/i18n/locales/en/messages.json +44 -0
- package/src/components/i18n/locales/es/messages.json +44 -0
- package/src/components/i18n/locales/et/messages.json +44 -0
- package/src/components/i18n/locales/fa/messages.json +44 -0
- package/src/components/i18n/locales/fi/messages.json +44 -0
- package/src/components/i18n/locales/fil/messages.json +44 -0
- package/src/components/i18n/locales/fr/messages.json +44 -0
- package/src/components/i18n/locales/gu/messages.json +44 -0
- package/src/components/i18n/locales/he/messages.json +44 -0
- package/src/components/i18n/locales/hi/messages.json +44 -0
- package/src/components/i18n/locales/hr/messages.json +44 -0
- package/src/components/i18n/locales/hu/messages.json +44 -0
- package/src/components/i18n/locales/hy/messages.json +44 -0
- package/src/components/i18n/locales/id/messages.json +44 -0
- package/src/components/i18n/locales/index.ts +225 -0
- package/src/components/i18n/locales/it/messages.json +44 -0
- package/src/components/i18n/locales/ja/messages.json +44 -0
- package/src/components/i18n/locales/ka/messages.json +44 -0
- package/src/components/i18n/locales/km/messages.json +44 -0
- package/src/components/i18n/locales/kn/messages.json +44 -0
- package/src/components/i18n/locales/ko/messages.json +44 -0
- package/src/components/i18n/locales/ku/messages.json +44 -0
- package/src/components/i18n/locales/lo/messages.json +44 -0
- package/src/components/i18n/locales/lt/messages.json +44 -0
- package/src/components/i18n/locales/lv/messages.json +44 -0
- package/src/components/i18n/locales/mk/messages.json +44 -0
- package/src/components/i18n/locales/ml/messages.json +44 -0
- package/src/components/i18n/locales/mn/messages.json +44 -0
- package/src/components/i18n/locales/mr/messages.json +44 -0
- package/src/components/i18n/locales/ms/messages.json +44 -0
- package/src/components/i18n/locales/my/messages.json +44 -0
- package/src/components/i18n/locales/ne/messages.json +44 -0
- package/src/components/i18n/locales/nl/messages.json +44 -0
- package/src/components/i18n/locales/no/messages.json +44 -0
- package/src/components/i18n/locales/pa/messages.json +44 -0
- package/src/components/i18n/locales/pl/messages.json +44 -0
- package/src/components/i18n/locales/ps/messages.json +44 -0
- package/src/components/i18n/locales/pt/messages.json +44 -0
- package/src/components/i18n/locales/ro/messages.json +44 -0
- package/src/components/i18n/locales/ru/messages.json +44 -0
- package/src/components/i18n/locales/sd/messages.json +44 -0
- package/src/components/i18n/locales/si/messages.json +44 -0
- package/src/components/i18n/locales/sk/messages.json +44 -0
- package/src/components/i18n/locales/sl/messages.json +44 -0
- package/src/components/i18n/locales/sq/messages.json +44 -0
- package/src/components/i18n/locales/sr/messages.json +44 -0
- package/src/components/i18n/locales/sv/messages.json +44 -0
- package/src/components/i18n/locales/sw/messages.json +44 -0
- package/src/components/i18n/locales/ta/messages.json +44 -0
- package/src/components/i18n/locales/te/messages.json +44 -0
- package/src/components/i18n/locales/th/messages.json +44 -0
- package/src/components/i18n/locales/tr/messages.json +44 -0
- package/src/components/i18n/locales/ug/messages.json +44 -0
- package/src/components/i18n/locales/uk/messages.json +44 -0
- package/src/components/i18n/locales/ur/messages.json +44 -0
- package/src/components/i18n/locales/vi/messages.json +44 -0
- package/src/components/i18n/locales/yi/messages.json +44 -0
- package/src/components/i18n/locales/zh/messages.json +44 -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 +363 -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 +33 -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 +1375 -0
- package/src/components/modules/blockManager.ts +1348 -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 +1141 -0
- package/src/components/modules/history.ts +1098 -0
- package/src/components/modules/i18n.ts +325 -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 +668 -0
- package/src/components/modules/renderer.ts +155 -0
- package/src/components/modules/saver.ts +283 -0
- package/src/components/modules/toolbar/blockSettings.ts +776 -0
- package/src/components/modules/toolbar/index.ts +1311 -0
- package/src/components/modules/toolbar/inline.ts +956 -0
- package/src/components/modules/tools.ts +589 -0
- package/src/components/modules/ui.ts +1179 -0
- package/src/components/polyfills.ts +113 -0
- package/src/components/selection.ts +1189 -0
- package/src/components/tools/base.ts +274 -0
- package/src/components/tools/block.ts +291 -0
- package/src/components/tools/collection.ts +67 -0
- package/src/components/tools/factory.ts +85 -0
- package/src/components/tools/inline.ts +71 -0
- package/src/components/tools/tune.ts +33 -0
- package/src/components/ui/toolbox.ts +497 -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 +666 -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 +187 -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 +181 -0
- package/src/components/utils/popover/components/search-input/search-input.types.ts +30 -0
- package/src/components/utils/popover/index.ts +13 -0
- package/src/components/utils/popover/popover-abstract.ts +448 -0
- package/src/components/utils/popover/popover-desktop.ts +643 -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 +105 -0
- package/src/components/utils/tooltip.ts +642 -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 +570 -0
- package/src/tools/index.ts +38 -0
- package/src/tools/list/index.ts +1803 -0
- package/src/tools/paragraph/index.ts +411 -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 +1 -1
- package/types/api/i18n.d.ts +5 -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 +169 -0
- package/types/data-formats/output-data.d.ts +15 -0
- package/types/full.d.ts +80 -0
- package/types/index.d.ts +9 -12
- 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 +16 -2
- package/types/tools/tool.d.ts +6 -0
- package/types/tools-entry.d.ts +49 -0
- package/types/utils/popover/popover-item.d.ts +6 -5
- package/dist/blok-B870U2fw.mjs +0 -25803
- package/dist/blok.umd.js +0 -181
|
@@ -0,0 +1,853 @@
|
|
|
1
|
+
import { SelectionUtils as Selection } from '../selection';
|
|
2
|
+
import { Module } from '../__module';
|
|
3
|
+
import type { Block } from '../block';
|
|
4
|
+
import { getCaretXPosition, isCaretAtEndOfInput, isCaretAtFirstLine, isCaretAtLastLine, isCaretAtStartOfInput, setCaretAtXPosition } from '../utils/caret';
|
|
5
|
+
import { Dom as $ } from '../dom';
|
|
6
|
+
|
|
7
|
+
const ASCII_MAX_CODE_POINT = 0x7f;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Determines whether the provided text is comprised only of punctuation and whitespace characters.
|
|
11
|
+
* @param text - text to check
|
|
12
|
+
*/
|
|
13
|
+
const isPunctuationOnly = (text: string): boolean => {
|
|
14
|
+
for (const character of text) {
|
|
15
|
+
if (character.trim().length === 0) {
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (character >= '0' && character <= '9') {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (character.toLowerCase() !== character.toUpperCase()) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const codePoint = character.codePointAt(0);
|
|
28
|
+
|
|
29
|
+
if (typeof codePoint === 'number' && codePoint > ASCII_MAX_CODE_POINT) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return true;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const collectTextNodes = (node: Node): Text[] => {
|
|
38
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
39
|
+
return [ node as Text ];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!node.hasChildNodes?.()) {
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return Array.from(node.childNodes).flatMap((child) => collectTextNodes(child));
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Finds last text node suitable for placing caret near the end of the element.
|
|
51
|
+
*
|
|
52
|
+
* Prefers nodes that contain more than just punctuation so caret remains inside formatting nodes
|
|
53
|
+
* whenever possible.
|
|
54
|
+
* @param root - element to search within
|
|
55
|
+
*/
|
|
56
|
+
const findLastMeaningfulTextNode = (root: HTMLElement): Text | null => {
|
|
57
|
+
const textNodes = collectTextNodes(root);
|
|
58
|
+
|
|
59
|
+
if (textNodes.length === 0) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const lastTextNode = textNodes[textNodes.length - 1];
|
|
64
|
+
const lastMeaningfulNode = [ ...textNodes ]
|
|
65
|
+
.reverse()
|
|
66
|
+
.find((node) => !isPunctuationOnly(node.textContent ?? '')) ?? null;
|
|
67
|
+
|
|
68
|
+
if (
|
|
69
|
+
lastMeaningfulNode &&
|
|
70
|
+
lastMeaningfulNode !== lastTextNode &&
|
|
71
|
+
isPunctuationOnly(lastTextNode.textContent ?? '') &&
|
|
72
|
+
lastMeaningfulNode.parentNode !== root
|
|
73
|
+
) {
|
|
74
|
+
return lastMeaningfulNode;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return lastTextNode;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Caret
|
|
82
|
+
* Contains methods for working Caret
|
|
83
|
+
* @todo get rid of this module and separate it for utility functions
|
|
84
|
+
*/
|
|
85
|
+
export class Caret extends Module {
|
|
86
|
+
/**
|
|
87
|
+
* Allowed caret positions in input
|
|
88
|
+
* @static
|
|
89
|
+
* @returns {{START: string, END: string, DEFAULT: string}}
|
|
90
|
+
*/
|
|
91
|
+
public get positions(): { START: string; END: string; DEFAULT: string } {
|
|
92
|
+
return {
|
|
93
|
+
START: 'start',
|
|
94
|
+
END: 'end',
|
|
95
|
+
DEFAULT: 'default',
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Data attributes used by Caret Module
|
|
101
|
+
*/
|
|
102
|
+
private static get DATA_ATTR(): { shadowCaret: string } {
|
|
103
|
+
return {
|
|
104
|
+
shadowCaret: 'data-blok-shadow-caret',
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Method gets Block instance and puts caret to the text node with offset
|
|
110
|
+
* There two ways that method applies caret position:
|
|
111
|
+
* - first found text node: sets at the beginning, but you can pass an offset
|
|
112
|
+
* - last found text node: sets at the end of the node. Also, you can customize the behaviour
|
|
113
|
+
* @param {Block} block - Block class
|
|
114
|
+
* @param {string} position - position where to set caret.
|
|
115
|
+
* If default - leave default behaviour and apply offset if it's passed
|
|
116
|
+
* @param {number} offset - caret offset regarding to the block content
|
|
117
|
+
*/
|
|
118
|
+
public setToBlock(block: Block, position: string = this.positions.DEFAULT, offset = 0): void {
|
|
119
|
+
const { BlockManager, BlockSelection } = this.Blok;
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Clear previous selection since we possible will select the new Block
|
|
123
|
+
*/
|
|
124
|
+
BlockSelection.clearSelection();
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* If Block is not focusable, just select (highlight) it
|
|
128
|
+
*/
|
|
129
|
+
if (!block.focusable) {
|
|
130
|
+
/**
|
|
131
|
+
* Hide current cursor
|
|
132
|
+
*/
|
|
133
|
+
window.getSelection()?.removeAllRanges();
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Highlight Block
|
|
137
|
+
*/
|
|
138
|
+
BlockSelection.selectBlock(block);
|
|
139
|
+
BlockManager.currentBlock = block;
|
|
140
|
+
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const getElement = (): HTMLElement | undefined => {
|
|
145
|
+
if (position === this.positions.START) {
|
|
146
|
+
return block.firstInput;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (position === this.positions.END) {
|
|
150
|
+
return block.lastInput;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return block.currentInput;
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const element = getElement();
|
|
157
|
+
|
|
158
|
+
if (!element) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const getNodeAndOffset = (el: HTMLElement): { node: Node | null; offset: number } => {
|
|
163
|
+
if (position === this.positions.START) {
|
|
164
|
+
return {
|
|
165
|
+
node: $.getDeepestNode(el, false),
|
|
166
|
+
offset: 0,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (position === this.positions.END) {
|
|
171
|
+
return this.resolveEndPositionNode(el);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const { node, offset: nodeOffset } = $.getNodeByOffset(el, offset);
|
|
175
|
+
|
|
176
|
+
if (node) {
|
|
177
|
+
return {
|
|
178
|
+
node,
|
|
179
|
+
offset: nodeOffset,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// case for empty block's input
|
|
184
|
+
return {
|
|
185
|
+
node: $.getDeepestNode(el, false),
|
|
186
|
+
offset: 0,
|
|
187
|
+
};
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const { node: nodeToSet, offset: offsetToSet } = getNodeAndOffset(element);
|
|
191
|
+
|
|
192
|
+
if (!nodeToSet) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
this.set(nodeToSet as HTMLElement, offsetToSet);
|
|
197
|
+
|
|
198
|
+
BlockManager.setCurrentBlockByChildNode(block.holder);
|
|
199
|
+
|
|
200
|
+
BlockManager.currentBlock!.currentInput = element;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Calculates the node and offset when caret should be placed near element's end.
|
|
205
|
+
* @param {HTMLElement} el - element to inspect
|
|
206
|
+
*/
|
|
207
|
+
private resolveEndPositionNode(el: HTMLElement): { node: Node | null; offset: number } {
|
|
208
|
+
const nodeToSet = $.getDeepestNode(el, true);
|
|
209
|
+
|
|
210
|
+
if (nodeToSet instanceof HTMLElement && $.isNativeInput(nodeToSet)) {
|
|
211
|
+
return {
|
|
212
|
+
node: nodeToSet,
|
|
213
|
+
offset: $.getContentLength(nodeToSet),
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const meaningfulTextNode = findLastMeaningfulTextNode(el);
|
|
218
|
+
|
|
219
|
+
if (meaningfulTextNode) {
|
|
220
|
+
return {
|
|
221
|
+
node: meaningfulTextNode,
|
|
222
|
+
offset: $.getContentLength(meaningfulTextNode),
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (nodeToSet) {
|
|
227
|
+
return {
|
|
228
|
+
node: nodeToSet,
|
|
229
|
+
offset: $.getContentLength(nodeToSet),
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
node: null,
|
|
235
|
+
offset: 0,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Set caret to the current input of current Block.
|
|
241
|
+
* @param {HTMLElement} input - input where caret should be set
|
|
242
|
+
* @param {string} position - position of the caret.
|
|
243
|
+
* If default - leave default behaviour and apply offset if it's passed
|
|
244
|
+
* @param {number} offset - caret offset regarding to the text node
|
|
245
|
+
*/
|
|
246
|
+
public setToInput(input: HTMLElement, position: string = this.positions.DEFAULT, offset = 0): void {
|
|
247
|
+
const { currentBlock } = this.Blok.BlockManager;
|
|
248
|
+
|
|
249
|
+
this.setCaretToInputPosition(input, position, offset);
|
|
250
|
+
|
|
251
|
+
if (currentBlock) {
|
|
252
|
+
currentBlock.currentInput = input;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Sets caret to a block at a specific X position (horizontal coordinate).
|
|
258
|
+
* Used for Notion-style vertical navigation to preserve horizontal caret position.
|
|
259
|
+
* @param block - Block to set caret in
|
|
260
|
+
* @param targetX - Target X coordinate, or null to use start/end position
|
|
261
|
+
* @param atFirstLine - If true, place caret on first line; if false, place on last line
|
|
262
|
+
*/
|
|
263
|
+
public setToBlockAtXPosition(block: Block, targetX: number | null, atFirstLine: boolean): void {
|
|
264
|
+
const { BlockManager, BlockSelection } = this.Blok;
|
|
265
|
+
|
|
266
|
+
BlockSelection.clearSelection();
|
|
267
|
+
|
|
268
|
+
if (!block.focusable) {
|
|
269
|
+
window.getSelection()?.removeAllRanges();
|
|
270
|
+
BlockSelection.selectBlock(block);
|
|
271
|
+
BlockManager.currentBlock = block;
|
|
272
|
+
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const element = atFirstLine ? block.firstInput : block.lastInput;
|
|
277
|
+
|
|
278
|
+
if (!element) {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (targetX !== null) {
|
|
283
|
+
setCaretAtXPosition(element, targetX, atFirstLine);
|
|
284
|
+
} else {
|
|
285
|
+
const position = atFirstLine ? this.positions.START : this.positions.END;
|
|
286
|
+
|
|
287
|
+
this.setCaretToInputPosition(element, position, 0);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
BlockManager.setCurrentBlockByChildNode(block.holder);
|
|
291
|
+
BlockManager.currentBlock!.currentInput = element;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Sets caret to an input at a specific X position (horizontal coordinate).
|
|
296
|
+
* Used for Notion-style vertical navigation to preserve horizontal caret position.
|
|
297
|
+
* @param input - Input element to set caret in
|
|
298
|
+
* @param targetX - Target X coordinate, or null to use start/end position
|
|
299
|
+
* @param atFirstLine - If true, place caret on first line; if false, place on last line
|
|
300
|
+
*/
|
|
301
|
+
public setToInputAtXPosition(input: HTMLElement, targetX: number | null, atFirstLine: boolean): void {
|
|
302
|
+
const { currentBlock } = this.Blok.BlockManager;
|
|
303
|
+
|
|
304
|
+
if (targetX !== null) {
|
|
305
|
+
setCaretAtXPosition(input, targetX, atFirstLine);
|
|
306
|
+
} else {
|
|
307
|
+
const position = atFirstLine ? this.positions.START : this.positions.END;
|
|
308
|
+
|
|
309
|
+
this.setCaretToInputPosition(input, position, 0);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (currentBlock) {
|
|
313
|
+
currentBlock.currentInput = input;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Internal method to handle caret positioning within an input
|
|
319
|
+
* @param input - the input element
|
|
320
|
+
* @param position - position type (START, END, DEFAULT)
|
|
321
|
+
* @param offset - character offset for DEFAULT position
|
|
322
|
+
*/
|
|
323
|
+
private setCaretToInputPosition(input: HTMLElement, position: string, offset: number): void {
|
|
324
|
+
if (position === this.positions.START) {
|
|
325
|
+
this.setCaretToStart(input);
|
|
326
|
+
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (position === this.positions.END) {
|
|
331
|
+
this.setCaretToEnd(input);
|
|
332
|
+
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// DEFAULT position: use getNodeByOffset to find the correct node for the given offset
|
|
337
|
+
// This properly handles multi-node content and clamps out-of-bounds offsets
|
|
338
|
+
this.setCaretToOffset(input, offset);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Sets caret to the start of an input
|
|
343
|
+
* @param input - the input element
|
|
344
|
+
*/
|
|
345
|
+
private setCaretToStart(input: HTMLElement): void {
|
|
346
|
+
const nodeToSet = $.getDeepestNode(input, false);
|
|
347
|
+
|
|
348
|
+
if (!nodeToSet) {
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
this.set(nodeToSet as HTMLElement, 0);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Sets caret to the end of an input
|
|
357
|
+
* @param input - the input element
|
|
358
|
+
*/
|
|
359
|
+
private setCaretToEnd(input: HTMLElement): void {
|
|
360
|
+
const nodeToSet = $.getDeepestNode(input, true);
|
|
361
|
+
|
|
362
|
+
if (!nodeToSet) {
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
this.set(nodeToSet as HTMLElement, $.getContentLength(nodeToSet));
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Sets caret at a specific offset within an input
|
|
371
|
+
* Falls back to end position if offset resolution fails
|
|
372
|
+
* @param input - the input element
|
|
373
|
+
* @param offset - the character offset
|
|
374
|
+
*/
|
|
375
|
+
private setCaretToOffset(input: HTMLElement, offset: number): void {
|
|
376
|
+
const { node, offset: nodeOffset } = $.getNodeByOffset(input, offset);
|
|
377
|
+
|
|
378
|
+
if (node) {
|
|
379
|
+
this.set(node as HTMLElement, nodeOffset);
|
|
380
|
+
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Fallback to end of input for empty inputs or when offset resolution fails
|
|
385
|
+
this.setCaretToEnd(input);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Creates Document Range and sets caret to the element with offset
|
|
390
|
+
* @param {HTMLElement} element - target node.
|
|
391
|
+
* @param {number} offset - offset
|
|
392
|
+
*/
|
|
393
|
+
public set(element: HTMLElement, offset = 0): void {
|
|
394
|
+
const scrollOffset = 30;
|
|
395
|
+
const { top, bottom } = Selection.setCursor(element, offset);
|
|
396
|
+
const { innerHeight } = window;
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* If new cursor position is not visible, scroll to it
|
|
400
|
+
*/
|
|
401
|
+
if (top < 0) {
|
|
402
|
+
window.scrollBy(0, top - scrollOffset);
|
|
403
|
+
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (bottom > innerHeight) {
|
|
408
|
+
window.scrollBy(0, bottom - innerHeight + scrollOffset);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Set Caret to the last Block
|
|
414
|
+
* If last block is not empty, append another empty block
|
|
415
|
+
*/
|
|
416
|
+
public setToTheLastBlock(): void {
|
|
417
|
+
const lastBlock = this.Blok.BlockManager.lastBlock;
|
|
418
|
+
|
|
419
|
+
if (!lastBlock) {
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* If last block is empty and it is an defaultBlock, set to that.
|
|
425
|
+
* Otherwise, append new empty block and set to that
|
|
426
|
+
*/
|
|
427
|
+
if (lastBlock.tool.isDefault && lastBlock.isEmpty) {
|
|
428
|
+
this.setToBlock(lastBlock);
|
|
429
|
+
} else {
|
|
430
|
+
const newBlock = this.Blok.BlockManager.insertAtEnd();
|
|
431
|
+
|
|
432
|
+
this.setToBlock(newBlock);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Extract content fragment of current Block from Caret position to the end of the Block
|
|
438
|
+
*/
|
|
439
|
+
public extractFragmentFromCaretPosition(): void | DocumentFragment {
|
|
440
|
+
const selection = Selection.get();
|
|
441
|
+
|
|
442
|
+
if (!selection || !selection.rangeCount) {
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const selectRange = selection.getRangeAt(0);
|
|
447
|
+
const currentBlock = this.Blok.BlockManager.currentBlock;
|
|
448
|
+
|
|
449
|
+
if (!currentBlock) {
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
const currentBlockInput = currentBlock.currentInput;
|
|
454
|
+
|
|
455
|
+
selectRange.deleteContents();
|
|
456
|
+
|
|
457
|
+
if (!currentBlockInput) {
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
if ($.isNativeInput(currentBlockInput)) {
|
|
462
|
+
/**
|
|
463
|
+
* If input is native text input we need to use it's value
|
|
464
|
+
* Text before the caret stays in the input,
|
|
465
|
+
* while text after the caret is returned as a fragment to be inserted after the block.
|
|
466
|
+
*/
|
|
467
|
+
const input = currentBlockInput as HTMLInputElement | HTMLTextAreaElement;
|
|
468
|
+
const newFragment = document.createDocumentFragment();
|
|
469
|
+
const selectionStart = input.selectionStart ?? 0;
|
|
470
|
+
|
|
471
|
+
const inputRemainingText = input.value.substring(0, selectionStart);
|
|
472
|
+
const fragmentText = input.value.substring(selectionStart);
|
|
473
|
+
|
|
474
|
+
newFragment.textContent = fragmentText;
|
|
475
|
+
input.value = inputRemainingText;
|
|
476
|
+
|
|
477
|
+
return newFragment;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
const range = selectRange.cloneRange();
|
|
481
|
+
|
|
482
|
+
range.selectNodeContents(currentBlockInput);
|
|
483
|
+
range.setStart(selectRange.endContainer, selectRange.endOffset);
|
|
484
|
+
|
|
485
|
+
return range.extractContents();
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Set's caret to the next Block or Tool`s input
|
|
490
|
+
* Before moving caret, we should check if caret position is at the end of Plugins node
|
|
491
|
+
* Using {@link Dom#getDeepestNode} to get a last node and match with current selection
|
|
492
|
+
* @param {boolean} force - pass true to skip check for caret position
|
|
493
|
+
*/
|
|
494
|
+
public navigateNext(force = false): boolean {
|
|
495
|
+
const { BlockManager } = this.Blok;
|
|
496
|
+
const { currentBlock, nextBlock } = BlockManager;
|
|
497
|
+
|
|
498
|
+
if (currentBlock === undefined) {
|
|
499
|
+
return false;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
const { nextInput, currentInput } = currentBlock;
|
|
503
|
+
const isAtEnd = currentInput !== undefined ? isCaretAtEndOfInput(currentInput) : undefined;
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* We should jump to the next block if:
|
|
507
|
+
* - 'force' is true (Tab-navigation)
|
|
508
|
+
* - caret is at the end of the current block
|
|
509
|
+
* - block does not contain any inputs (e.g. to allow go next when Delimiter is focused)
|
|
510
|
+
*/
|
|
511
|
+
const navigationAllowed = force || isAtEnd || !currentBlock.focusable;
|
|
512
|
+
|
|
513
|
+
/** If next Tool`s input exists, focus on it. Otherwise set caret to the next Block */
|
|
514
|
+
if (nextInput && navigationAllowed) {
|
|
515
|
+
this.setToInput(nextInput, this.positions.START);
|
|
516
|
+
|
|
517
|
+
return true;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
const getBlockToNavigate = (): Block | null => {
|
|
521
|
+
if (nextBlock !== null) {
|
|
522
|
+
return nextBlock;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* This code allows to exit from the last non-initial tool:
|
|
527
|
+
* https://github.com/codex-team/editor.js/issues/1103
|
|
528
|
+
*/
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* 1. If there is a last block and it is default, do nothing
|
|
532
|
+
* 2. If there is a last block and it is non-default --> and caret not at the end <--, do nothing
|
|
533
|
+
* (https://github.com/codex-team/editor.js/issues/1414)
|
|
534
|
+
*/
|
|
535
|
+
if (currentBlock.tool.isDefault || !navigationAllowed) {
|
|
536
|
+
return null;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* If there is no nextBlock, but currentBlock is not default,
|
|
541
|
+
* insert new default block at the end and navigate to it
|
|
542
|
+
*/
|
|
543
|
+
return BlockManager.insertAtEnd() as Block;
|
|
544
|
+
};
|
|
545
|
+
|
|
546
|
+
const blockToNavigate = getBlockToNavigate();
|
|
547
|
+
|
|
548
|
+
if (blockToNavigate !== null && navigationAllowed) {
|
|
549
|
+
this.setToBlock(blockToNavigate, this.positions.START);
|
|
550
|
+
|
|
551
|
+
return true;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
return false;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Set's caret to the previous Tool`s input or Block
|
|
559
|
+
* Before moving caret, we should check if caret position is start of the Plugins node
|
|
560
|
+
* Using {@link Dom#getDeepestNode} to get a last node and match with current selection
|
|
561
|
+
* @param {boolean} force - pass true to skip check for caret position
|
|
562
|
+
*/
|
|
563
|
+
public navigatePrevious(force = false): boolean {
|
|
564
|
+
const { currentBlock, previousBlock } = this.Blok.BlockManager;
|
|
565
|
+
|
|
566
|
+
if (!currentBlock) {
|
|
567
|
+
return false;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
const { previousInput, currentInput } = currentBlock;
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* We should jump to the previous block if:
|
|
574
|
+
* - 'force' is true (Tab-navigation)
|
|
575
|
+
* - caret is at the start of the current block
|
|
576
|
+
* - block does not contain any inputs (e.g. to allow go back when Delimiter is focused)
|
|
577
|
+
*/
|
|
578
|
+
const caretAtStart = currentInput !== undefined ? isCaretAtStartOfInput(currentInput) : undefined;
|
|
579
|
+
const navigationAllowed = force || caretAtStart || !currentBlock.focusable;
|
|
580
|
+
|
|
581
|
+
/** If previous Tool`s input exists, focus on it. Otherwise set caret to the previous Block */
|
|
582
|
+
if (previousInput && navigationAllowed) {
|
|
583
|
+
this.setToInput(previousInput, this.positions.END);
|
|
584
|
+
|
|
585
|
+
return true;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
if (previousBlock !== null && navigationAllowed) {
|
|
589
|
+
this.setToBlock(previousBlock as Block, this.positions.END);
|
|
590
|
+
|
|
591
|
+
return true;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
return false;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
/**
|
|
598
|
+
* Helper method to navigate to a target block.
|
|
599
|
+
* If target is focusable, sets caret to it; otherwise selects it.
|
|
600
|
+
* @param targetBlock - Block to navigate to (or null)
|
|
601
|
+
* @param atFirstLine - If true, place caret at first line; if false, at last line
|
|
602
|
+
* @returns {boolean} - true if navigation occurred
|
|
603
|
+
*/
|
|
604
|
+
private navigateToBlock(targetBlock: Block | null, atFirstLine: boolean): boolean {
|
|
605
|
+
if (targetBlock === null) {
|
|
606
|
+
return false;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
const { BlockManager, BlockSelection } = this.Blok;
|
|
610
|
+
|
|
611
|
+
BlockSelection.clearSelection();
|
|
612
|
+
|
|
613
|
+
if (targetBlock.focusable) {
|
|
614
|
+
this.setToBlockAtXPosition(targetBlock, null, atFirstLine);
|
|
615
|
+
} else {
|
|
616
|
+
BlockSelection.selectBlock(targetBlock);
|
|
617
|
+
BlockManager.currentBlock = targetBlock;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
return true;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/**
|
|
624
|
+
* Navigates to the next block using vertical (Arrow Down) navigation.
|
|
625
|
+
* Implements Notion-style behavior: line-by-line within block, then jump to next block.
|
|
626
|
+
* Preserves horizontal caret position when moving between blocks.
|
|
627
|
+
* @returns {boolean} - true if navigation to next block occurred
|
|
628
|
+
*/
|
|
629
|
+
public navigateVerticalNext(): boolean {
|
|
630
|
+
const { BlockManager } = this.Blok;
|
|
631
|
+
const { currentBlock, nextBlock } = BlockManager;
|
|
632
|
+
|
|
633
|
+
if (currentBlock === undefined) {
|
|
634
|
+
return false;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* For non-focusable blocks (images, embeds, contentless), navigate to next block
|
|
639
|
+
*/
|
|
640
|
+
if (!currentBlock.focusable) {
|
|
641
|
+
return this.navigateToBlock(nextBlock, true);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* For empty blocks, jump immediately to the next block
|
|
646
|
+
*/
|
|
647
|
+
if (currentBlock.isEmpty) {
|
|
648
|
+
return this.navigateToBlock(nextBlock, true);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
const { currentInput } = currentBlock;
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* Check if caret is at the last line - if not, let browser handle line navigation
|
|
655
|
+
*/
|
|
656
|
+
const isAtLastLine = currentInput !== undefined ? isCaretAtLastLine(currentInput) : true;
|
|
657
|
+
|
|
658
|
+
if (!isAtLastLine) {
|
|
659
|
+
return false;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
/**
|
|
663
|
+
* Save the current caret X position before navigation
|
|
664
|
+
*/
|
|
665
|
+
const caretX = getCaretXPosition();
|
|
666
|
+
|
|
667
|
+
/**
|
|
668
|
+
* Navigate to next input within the block first
|
|
669
|
+
*/
|
|
670
|
+
const { nextInput } = currentBlock;
|
|
671
|
+
|
|
672
|
+
if (nextInput) {
|
|
673
|
+
this.setToInputAtXPosition(nextInput, caretX, true);
|
|
674
|
+
|
|
675
|
+
return true;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* Navigate to next block, preserving horizontal position
|
|
680
|
+
*/
|
|
681
|
+
if (nextBlock !== null) {
|
|
682
|
+
this.setToBlockAtXPosition(nextBlock, caretX, true);
|
|
683
|
+
|
|
684
|
+
return true;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
/**
|
|
688
|
+
* At the last block - check if we should create a new block
|
|
689
|
+
*/
|
|
690
|
+
const isAtEnd = currentInput !== undefined ? isCaretAtEndOfInput(currentInput) : true;
|
|
691
|
+
|
|
692
|
+
if (!currentBlock.tool.isDefault && isAtEnd) {
|
|
693
|
+
const newBlock = BlockManager.insertAtEnd() as Block;
|
|
694
|
+
|
|
695
|
+
this.setToBlock(newBlock, this.positions.START);
|
|
696
|
+
|
|
697
|
+
return true;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
return false;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/**
|
|
704
|
+
* Navigates to the previous block using vertical (Arrow Up) navigation.
|
|
705
|
+
* Implements Notion-style behavior: line-by-line within block, then jump to previous block.
|
|
706
|
+
* Preserves horizontal caret position when moving between blocks.
|
|
707
|
+
* @returns {boolean} - true if navigation to previous block occurred
|
|
708
|
+
*/
|
|
709
|
+
public navigateVerticalPrevious(): boolean {
|
|
710
|
+
const { BlockManager } = this.Blok;
|
|
711
|
+
const { currentBlock, previousBlock } = BlockManager;
|
|
712
|
+
|
|
713
|
+
if (currentBlock === undefined) {
|
|
714
|
+
return false;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
/**
|
|
718
|
+
* For non-focusable blocks (images, embeds, contentless), navigate to previous block
|
|
719
|
+
*/
|
|
720
|
+
if (!currentBlock.focusable) {
|
|
721
|
+
return this.navigateToBlock(previousBlock, false);
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
/**
|
|
725
|
+
* For empty blocks, jump immediately to the previous block
|
|
726
|
+
*/
|
|
727
|
+
if (currentBlock.isEmpty) {
|
|
728
|
+
return this.navigateToBlock(previousBlock, false);
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
const { currentInput } = currentBlock;
|
|
732
|
+
|
|
733
|
+
/**
|
|
734
|
+
* Check if caret is at the first line - if not, let browser handle line navigation
|
|
735
|
+
*/
|
|
736
|
+
const isAtFirstLine = currentInput !== undefined ? isCaretAtFirstLine(currentInput) : true;
|
|
737
|
+
|
|
738
|
+
if (!isAtFirstLine) {
|
|
739
|
+
return false;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
/**
|
|
743
|
+
* Save the current caret X position before navigation
|
|
744
|
+
*/
|
|
745
|
+
const caretX = getCaretXPosition();
|
|
746
|
+
|
|
747
|
+
/**
|
|
748
|
+
* Navigate to previous input within the block first
|
|
749
|
+
*/
|
|
750
|
+
const { previousInput } = currentBlock;
|
|
751
|
+
|
|
752
|
+
if (previousInput) {
|
|
753
|
+
this.setToInputAtXPosition(previousInput, caretX, false);
|
|
754
|
+
|
|
755
|
+
return true;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
/**
|
|
759
|
+
* Navigate to previous block, preserving horizontal position
|
|
760
|
+
*/
|
|
761
|
+
if (previousBlock !== null) {
|
|
762
|
+
this.setToBlockAtXPosition(previousBlock as Block, caretX, false);
|
|
763
|
+
|
|
764
|
+
return true;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
return false;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
/**
|
|
771
|
+
* Inserts shadow element after passed element where caret can be placed
|
|
772
|
+
* @param {Element} element - element after which shadow caret should be inserted
|
|
773
|
+
*/
|
|
774
|
+
public createShadow(element: Element): void {
|
|
775
|
+
const shadowCaret = document.createElement('span');
|
|
776
|
+
|
|
777
|
+
shadowCaret.setAttribute(Caret.DATA_ATTR.shadowCaret, '');
|
|
778
|
+
shadowCaret.setAttribute('data-blok-testid', 'shadow-caret');
|
|
779
|
+
element.insertAdjacentElement('beforeend', shadowCaret);
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
/**
|
|
783
|
+
* Restores caret position
|
|
784
|
+
* @param {HTMLElement} element - element where caret should be restored
|
|
785
|
+
*/
|
|
786
|
+
public restoreCaret(element: HTMLElement): void {
|
|
787
|
+
const shadowCaret = element.querySelector('[data-blok-testid="shadow-caret"]');
|
|
788
|
+
|
|
789
|
+
if (!shadowCaret) {
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
/**
|
|
794
|
+
* After we set the caret to the required place
|
|
795
|
+
* we need to clear shadow caret
|
|
796
|
+
*
|
|
797
|
+
* - make new range
|
|
798
|
+
* - select shadowed span
|
|
799
|
+
* - use extractContent to remove it from DOM
|
|
800
|
+
*/
|
|
801
|
+
const sel = new Selection();
|
|
802
|
+
|
|
803
|
+
sel.expandToTag(shadowCaret as HTMLElement);
|
|
804
|
+
|
|
805
|
+
const newRange = document.createRange();
|
|
806
|
+
|
|
807
|
+
newRange.selectNode(shadowCaret);
|
|
808
|
+
newRange.extractContents();
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
/**
|
|
812
|
+
* Inserts passed content at caret position
|
|
813
|
+
* @param {string} content - content to insert
|
|
814
|
+
*/
|
|
815
|
+
public insertContentAtCaretPosition(content: string): void {
|
|
816
|
+
const fragment = document.createDocumentFragment();
|
|
817
|
+
const wrapper = document.createElement('div');
|
|
818
|
+
const selection = Selection.get();
|
|
819
|
+
const range = Selection.range;
|
|
820
|
+
|
|
821
|
+
if (!selection || !range) {
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
wrapper.innerHTML = content;
|
|
826
|
+
|
|
827
|
+
Array.from(wrapper.childNodes).forEach((child: Node) => fragment.appendChild(child));
|
|
828
|
+
|
|
829
|
+
/**
|
|
830
|
+
* If there is no child node, append empty one
|
|
831
|
+
*/
|
|
832
|
+
if (fragment.childNodes.length === 0) {
|
|
833
|
+
fragment.appendChild(new Text());
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
const lastChild = fragment.lastChild as ChildNode;
|
|
837
|
+
|
|
838
|
+
range.deleteContents();
|
|
839
|
+
range.insertNode(fragment);
|
|
840
|
+
|
|
841
|
+
/** Cross-browser caret insertion */
|
|
842
|
+
const newRange = document.createRange();
|
|
843
|
+
|
|
844
|
+
const nodeToSetCaret = lastChild.nodeType === Node.TEXT_NODE ? lastChild : lastChild.firstChild;
|
|
845
|
+
|
|
846
|
+
if (nodeToSetCaret !== null && nodeToSetCaret.textContent !== null) {
|
|
847
|
+
newRange.setStart(nodeToSetCaret, nodeToSetCaret.textContent.length);
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
selection.removeAllRanges();
|
|
851
|
+
selection.addRange(newRange);
|
|
852
|
+
}
|
|
853
|
+
}
|