@jackuait/blok 0.4.1 → 0.4.2
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 +859 -92
- package/codemod/test.js +682 -77
- package/dist/blok.mjs +5 -2
- package/dist/chunks/blok-BjgH1REI.mjs +12838 -0
- package/dist/chunks/i18next-CugVlwWp.mjs +1292 -0
- package/dist/chunks/i18next-loader-DfiUa_gd.mjs +43 -0
- package/dist/{index-CBkApZKo.mjs → chunks/index-5m5JWNey.mjs} +2 -2
- package/dist/chunks/inline-tool-convert-Bx5BVd8I.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 +59 -156
- package/package.json +47 -15
- 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 +0 -5
- package/types/configs/blok-config.d.ts +21 -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 -24
- 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/list.d.ts +25 -18
- package/types/tools/tool-settings.d.ts +8 -1
- package/types/tools/tool.d.ts +6 -0
- package/types/tools-entry.d.ts +49 -0
- package/types/utils/popover/popover-item.d.ts +0 -5
- package/dist/blok-BwPfU8ro.mjs +0 -21510
- package/dist/blok.umd.js +0 -198
|
@@ -0,0 +1,1092 @@
|
|
|
1
|
+
import { Module } from '../__module';
|
|
2
|
+
import { Dom as $ } from '../dom';
|
|
3
|
+
import { getFileExtension, isEmpty, isObject, isString, isValidMimeType, log } from '../utils';
|
|
4
|
+
import type {
|
|
5
|
+
PasteEvent,
|
|
6
|
+
PasteEventDetail,
|
|
7
|
+
SanitizerConfig,
|
|
8
|
+
SanitizerRule
|
|
9
|
+
} from '../../../types';
|
|
10
|
+
import type { SavedData } from '../../../types/data-formats';
|
|
11
|
+
import { clean, composeSanitizerConfig, sanitizeBlocks } from '../utils/sanitizer';
|
|
12
|
+
import type { BlockToolAdapter } from '../tools/block';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Tag substitute object.
|
|
16
|
+
*/
|
|
17
|
+
interface TagSubstitute {
|
|
18
|
+
/**
|
|
19
|
+
* Name of related Tool
|
|
20
|
+
*
|
|
21
|
+
*/
|
|
22
|
+
tool: BlockToolAdapter;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* If a Tool specifies just a tag name, all the attributes will be sanitized.
|
|
26
|
+
* But Tool can explicitly specify sanitizer configuration for supported tags
|
|
27
|
+
*/
|
|
28
|
+
sanitizationConfig?: SanitizerRule;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const SAFE_STRUCTURAL_TAGS = new Set([
|
|
32
|
+
'table',
|
|
33
|
+
'thead',
|
|
34
|
+
'tbody',
|
|
35
|
+
'tfoot',
|
|
36
|
+
'tr',
|
|
37
|
+
'th',
|
|
38
|
+
'td',
|
|
39
|
+
'caption',
|
|
40
|
+
'colgroup',
|
|
41
|
+
'col',
|
|
42
|
+
'ul',
|
|
43
|
+
'ol',
|
|
44
|
+
'li',
|
|
45
|
+
'dl',
|
|
46
|
+
'dt',
|
|
47
|
+
'dd',
|
|
48
|
+
]);
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Pattern substitute object.
|
|
52
|
+
*/
|
|
53
|
+
interface PatternSubstitute {
|
|
54
|
+
/**
|
|
55
|
+
* Pattern`s key
|
|
56
|
+
*/
|
|
57
|
+
key: string;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Pattern regexp
|
|
61
|
+
*/
|
|
62
|
+
pattern: RegExp;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Name of related Tool
|
|
66
|
+
*/
|
|
67
|
+
tool: BlockToolAdapter;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Files` types substitutions object.
|
|
72
|
+
*/
|
|
73
|
+
interface FilesSubstitution {
|
|
74
|
+
/**
|
|
75
|
+
* Array of file extensions Tool can handle
|
|
76
|
+
* @type {string[]}
|
|
77
|
+
*/
|
|
78
|
+
extensions: string[];
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Array of MIME types Tool can handle
|
|
82
|
+
* @type {string[]}
|
|
83
|
+
*/
|
|
84
|
+
mimeTypes: string[];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Processed paste data object.
|
|
89
|
+
* @interface PasteData
|
|
90
|
+
*/
|
|
91
|
+
interface PasteData {
|
|
92
|
+
/**
|
|
93
|
+
* Name of related Tool
|
|
94
|
+
* @type {string}
|
|
95
|
+
*/
|
|
96
|
+
tool: string;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Pasted data. Processed and wrapped to HTML element
|
|
100
|
+
* @type {HTMLElement}
|
|
101
|
+
*/
|
|
102
|
+
content: HTMLElement;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Pasted data
|
|
106
|
+
*/
|
|
107
|
+
event: PasteEvent;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* True if content should be inserted as new Block
|
|
111
|
+
* @type {boolean}
|
|
112
|
+
*/
|
|
113
|
+
isBlock: boolean;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* @class Paste
|
|
118
|
+
* @classdesc Contains methods to handle paste on blok
|
|
119
|
+
* @module Paste
|
|
120
|
+
* @version 2.0.0
|
|
121
|
+
*/
|
|
122
|
+
export class Paste extends Module {
|
|
123
|
+
/** If string`s length is greater than this number we don't check paste patterns */
|
|
124
|
+
public static readonly PATTERN_PROCESSING_MAX_LENGTH = 450;
|
|
125
|
+
|
|
126
|
+
/** Custom Blok mime-type to handle in-blok copy/paste actions */
|
|
127
|
+
public readonly MIME_TYPE = 'application/x-blok';
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Tags` substitutions parameters
|
|
131
|
+
*/
|
|
132
|
+
private toolsTags: { [tag: string]: TagSubstitute } = {};
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Store tags to substitute by tool name
|
|
136
|
+
*/
|
|
137
|
+
private tagsByTool: { [tools: string]: string[] } = {};
|
|
138
|
+
|
|
139
|
+
/** Patterns` substitutions parameters */
|
|
140
|
+
private toolsPatterns: PatternSubstitute[] = [];
|
|
141
|
+
|
|
142
|
+
/** Files` substitutions parameters */
|
|
143
|
+
private toolsFiles: {
|
|
144
|
+
[tool: string]: FilesSubstitution;
|
|
145
|
+
} = {};
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* List of tools which do not need a paste handling
|
|
149
|
+
*/
|
|
150
|
+
private exceptionList: string[] = [];
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Set onPaste callback and collect tools` paste configurations
|
|
154
|
+
*/
|
|
155
|
+
public async prepare(): Promise<void> {
|
|
156
|
+
this.processTools();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Determines whether current block should be replaced by the pasted file tool.
|
|
161
|
+
* @param toolName - tool that is going to handle the file
|
|
162
|
+
*/
|
|
163
|
+
private shouldReplaceCurrentBlockForFile(toolName?: string): boolean {
|
|
164
|
+
const { BlockManager } = this.Blok;
|
|
165
|
+
const currentBlock = BlockManager.currentBlock;
|
|
166
|
+
|
|
167
|
+
if (!currentBlock) {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (toolName && currentBlock.name === toolName) {
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const isCurrentBlockDefault = Boolean(currentBlock.tool.isDefault);
|
|
176
|
+
|
|
177
|
+
return isCurrentBlockDefault && currentBlock.isEmpty;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Builds sanitize config that keeps structural tags such as tables and lists intact.
|
|
182
|
+
* @param node - root node to inspect
|
|
183
|
+
*/
|
|
184
|
+
private getStructuralTagsSanitizeConfig(node: HTMLElement): SanitizerConfig {
|
|
185
|
+
const config: SanitizerConfig = {} as SanitizerConfig;
|
|
186
|
+
const nodesToProcess: Element[] = [ node ];
|
|
187
|
+
|
|
188
|
+
while (nodesToProcess.length > 0) {
|
|
189
|
+
const current = nodesToProcess.pop();
|
|
190
|
+
|
|
191
|
+
if (!current) {
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const tagName = current.tagName.toLowerCase();
|
|
196
|
+
|
|
197
|
+
if (SAFE_STRUCTURAL_TAGS.has(tagName)) {
|
|
198
|
+
config[tagName] = config[tagName] ?? {};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
nodesToProcess.push(...Array.from(current.children));
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return config;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Set read-only state
|
|
209
|
+
* @param {boolean} readOnlyEnabled - read only flag value
|
|
210
|
+
*/
|
|
211
|
+
public toggleReadOnly(readOnlyEnabled: boolean): void {
|
|
212
|
+
if (!readOnlyEnabled) {
|
|
213
|
+
this.setCallback();
|
|
214
|
+
} else {
|
|
215
|
+
this.unsetCallback();
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Determines whether provided DataTransfer contains file-like entries
|
|
221
|
+
* @param dataTransfer - data transfer payload to inspect
|
|
222
|
+
*/
|
|
223
|
+
private containsFiles(dataTransfer: DataTransfer): boolean {
|
|
224
|
+
const types = Array.from(dataTransfer.types);
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Common case: browser exposes explicit "Files" entry
|
|
228
|
+
*/
|
|
229
|
+
if (types.includes('Files')) {
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* File uploads sometimes omit `types` and set files directly
|
|
235
|
+
*/
|
|
236
|
+
if (dataTransfer.files?.length) {
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
try {
|
|
241
|
+
const legacyList = dataTransfer.types as unknown as DOMStringList;
|
|
242
|
+
|
|
243
|
+
if (typeof legacyList?.contains === 'function' && legacyList.contains('Files')) {
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
} catch {
|
|
247
|
+
// ignore and fallthrough
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Handle pasted data transfer object
|
|
255
|
+
* @param {DataTransfer} dataTransfer - pasted data transfer object
|
|
256
|
+
*/
|
|
257
|
+
public async processDataTransfer(dataTransfer: DataTransfer): Promise<void> {
|
|
258
|
+
const { Tools } = this.Blok;
|
|
259
|
+
const includesFiles = this.containsFiles(dataTransfer);
|
|
260
|
+
|
|
261
|
+
if (includesFiles && !isEmpty(this.toolsFiles)) {
|
|
262
|
+
await this.processFiles(dataTransfer.files);
|
|
263
|
+
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const blokData = dataTransfer.getData(this.MIME_TYPE);
|
|
268
|
+
const plainData = dataTransfer.getData('text/plain');
|
|
269
|
+
const rawHtmlData = dataTransfer.getData('text/html');
|
|
270
|
+
const normalizedHtmlData = rawHtmlData;
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* If Blok json is passed, insert it
|
|
274
|
+
*/
|
|
275
|
+
if (blokData) {
|
|
276
|
+
try {
|
|
277
|
+
this.insertBlokData(JSON.parse(blokData));
|
|
278
|
+
|
|
279
|
+
return;
|
|
280
|
+
} catch (_e) { } // Do nothing and continue execution as usual if error appears
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/** Add all tags that can be substituted to sanitizer configuration */
|
|
284
|
+
const toolsTags = Object.fromEntries(
|
|
285
|
+
Object.keys(this.toolsTags).map((tag) => [
|
|
286
|
+
tag.toLowerCase(),
|
|
287
|
+
this.toolsTags[tag].sanitizationConfig ?? {},
|
|
288
|
+
])
|
|
289
|
+
) as SanitizerConfig;
|
|
290
|
+
|
|
291
|
+
const inlineSanitizeConfig = Tools.getAllInlineToolsSanitizeConfig();
|
|
292
|
+
const customConfig = composeSanitizerConfig(
|
|
293
|
+
this.config.sanitizer as SanitizerConfig,
|
|
294
|
+
toolsTags,
|
|
295
|
+
inlineSanitizeConfig,
|
|
296
|
+
{ br: {} }
|
|
297
|
+
);
|
|
298
|
+
const cleanData = clean(normalizedHtmlData, customConfig);
|
|
299
|
+
const cleanDataIsHtml = $.isHTMLString(cleanData);
|
|
300
|
+
const shouldProcessAsPlain = !cleanData.trim() || (cleanData.trim() === plainData || !cleanDataIsHtml);
|
|
301
|
+
|
|
302
|
+
/** If there is no HTML or HTML string is equal to plain one, process it as plain text */
|
|
303
|
+
if (shouldProcessAsPlain) {
|
|
304
|
+
await this.processText(plainData);
|
|
305
|
+
} else {
|
|
306
|
+
await this.processText(cleanData, true);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Process pasted text and divide them into Blocks
|
|
312
|
+
* @param {string} data - text to process. Can be HTML or plain.
|
|
313
|
+
* @param {boolean} isHTML - if passed string is HTML, this parameter should be true
|
|
314
|
+
*/
|
|
315
|
+
public async processText(data: string, isHTML = false): Promise<void> {
|
|
316
|
+
const { Caret, BlockManager } = this.Blok;
|
|
317
|
+
const dataToInsert = isHTML ? this.processHTML(data) : this.processPlain(data);
|
|
318
|
+
|
|
319
|
+
if (!dataToInsert.length) {
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (dataToInsert.length > 1) {
|
|
324
|
+
const isCurrentBlockDefault = Boolean(BlockManager.currentBlock?.tool.isDefault);
|
|
325
|
+
const needToReplaceCurrentBlock = isCurrentBlockDefault && Boolean(BlockManager.currentBlock?.isEmpty);
|
|
326
|
+
|
|
327
|
+
for (const [index, content] of dataToInsert.entries()) {
|
|
328
|
+
await this.insertBlock(content, index === 0 && needToReplaceCurrentBlock);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
BlockManager.currentBlock &&
|
|
332
|
+
Caret.setToBlock(BlockManager.currentBlock, Caret.positions.END);
|
|
333
|
+
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const [ singleItem ] = dataToInsert;
|
|
338
|
+
|
|
339
|
+
if (singleItem.isBlock) {
|
|
340
|
+
await this.processSingleBlock(singleItem);
|
|
341
|
+
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
await this.processInlinePaste(singleItem);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Wrapper handler for paste event that matches listeners.on signature
|
|
350
|
+
* @param {Event} event - paste event
|
|
351
|
+
*/
|
|
352
|
+
private handlePasteEventWrapper = (event: Event): void => {
|
|
353
|
+
void this.handlePasteEvent(event as ClipboardEvent);
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Set onPaste callback handler
|
|
358
|
+
*/
|
|
359
|
+
private setCallback(): void {
|
|
360
|
+
this.listeners.on(this.Blok.UI.nodes.holder, 'paste', this.handlePasteEventWrapper);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Unset onPaste callback handler
|
|
365
|
+
*/
|
|
366
|
+
private unsetCallback(): void {
|
|
367
|
+
this.listeners.off(this.Blok.UI.nodes.holder, 'paste', this.handlePasteEventWrapper);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Get and process tool`s paste configs
|
|
372
|
+
*/
|
|
373
|
+
private processTools(): void {
|
|
374
|
+
const tools = this.Blok.Tools.blockTools;
|
|
375
|
+
|
|
376
|
+
Array
|
|
377
|
+
.from(tools.values())
|
|
378
|
+
.forEach(this.processTool);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Process paste config for each tool
|
|
383
|
+
* @param tool - BlockTool object
|
|
384
|
+
*/
|
|
385
|
+
private processTool = (tool: BlockToolAdapter): void => {
|
|
386
|
+
try {
|
|
387
|
+
if (tool.pasteConfig === false) {
|
|
388
|
+
this.exceptionList.push(tool.name);
|
|
389
|
+
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (!tool.hasOnPasteHandler) {
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
this.getTagsConfig(tool);
|
|
398
|
+
this.getFilesConfig(tool);
|
|
399
|
+
this.getPatternsConfig(tool);
|
|
400
|
+
} catch (e) {
|
|
401
|
+
log(
|
|
402
|
+
`Paste handling for «${tool.name}» Tool hasn't been set up because of the error`,
|
|
403
|
+
'warn',
|
|
404
|
+
e
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Get tags name list from either tag name or sanitization config.
|
|
411
|
+
* @param {string | object} tagOrSanitizeConfig - tag name or sanitize config object.
|
|
412
|
+
* @returns {string[]} array of tags.
|
|
413
|
+
*/
|
|
414
|
+
private collectTagNames(tagOrSanitizeConfig: string | SanitizerConfig): string[] {
|
|
415
|
+
/**
|
|
416
|
+
* If string, then it is a tag name.
|
|
417
|
+
*/
|
|
418
|
+
if (isString(tagOrSanitizeConfig)) {
|
|
419
|
+
return [ tagOrSanitizeConfig ];
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* If object, then its keys are tags.
|
|
423
|
+
*/
|
|
424
|
+
if (isObject(tagOrSanitizeConfig)) {
|
|
425
|
+
return Object.keys(tagOrSanitizeConfig);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/** Return empty tag list */
|
|
429
|
+
return [];
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Get tags to substitute by Tool
|
|
434
|
+
* @param tool - BlockTool object
|
|
435
|
+
*/
|
|
436
|
+
private getTagsConfig(tool: BlockToolAdapter): void {
|
|
437
|
+
if (tool.pasteConfig === false) {
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const tagsOrSanitizeConfigs = tool.pasteConfig.tags || [];
|
|
442
|
+
const toolTags: string[] = [];
|
|
443
|
+
|
|
444
|
+
tagsOrSanitizeConfigs.forEach((tagOrSanitizeConfig) => {
|
|
445
|
+
const tags = this.collectTagNames(tagOrSanitizeConfig);
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Add tags to toolTags array
|
|
449
|
+
*/
|
|
450
|
+
toolTags.push(...tags);
|
|
451
|
+
tags.forEach((tag) => {
|
|
452
|
+
if (Object.prototype.hasOwnProperty.call(this.toolsTags, tag)) {
|
|
453
|
+
log(
|
|
454
|
+
`Paste handler for «${tool.name}» Tool on «${tag}» tag is skipped ` +
|
|
455
|
+
`because it is already used by «${this.toolsTags[tag].tool.name}» Tool.`,
|
|
456
|
+
'warn'
|
|
457
|
+
);
|
|
458
|
+
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Get sanitize config for tag.
|
|
463
|
+
*/
|
|
464
|
+
const sanitizationConfig = isObject(tagOrSanitizeConfig) ? tagOrSanitizeConfig[tag] : undefined;
|
|
465
|
+
|
|
466
|
+
this.toolsTags[tag.toUpperCase()] = {
|
|
467
|
+
tool,
|
|
468
|
+
sanitizationConfig,
|
|
469
|
+
};
|
|
470
|
+
});
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
this.tagsByTool[tool.name] = toolTags.map((t) => t.toUpperCase());
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Get files` types and extensions to substitute by Tool
|
|
478
|
+
* @param tool - BlockTool object
|
|
479
|
+
*/
|
|
480
|
+
private getFilesConfig(tool: BlockToolAdapter): void {
|
|
481
|
+
if (tool.pasteConfig === false) {
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
const { files = {} } = tool.pasteConfig;
|
|
486
|
+
const { extensions: rawExtensions, mimeTypes: rawMimeTypes } = files;
|
|
487
|
+
|
|
488
|
+
if (!rawExtensions && !rawMimeTypes) {
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
const normalizedExtensions = (() => {
|
|
493
|
+
if (rawExtensions == null) {
|
|
494
|
+
return [];
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
if (Array.isArray(rawExtensions)) {
|
|
498
|
+
return rawExtensions;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
log(`«extensions» property of the paste config for «${tool.name}» Tool should be an array`);
|
|
502
|
+
|
|
503
|
+
return [];
|
|
504
|
+
})();
|
|
505
|
+
|
|
506
|
+
const normalizedMimeTypes = (() => {
|
|
507
|
+
if (rawMimeTypes == null) {
|
|
508
|
+
return [];
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
if (!Array.isArray(rawMimeTypes)) {
|
|
512
|
+
log(`«mimeTypes» property of the paste config for «${tool.name}» Tool should be an array`);
|
|
513
|
+
|
|
514
|
+
return [];
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
return rawMimeTypes.filter((type) => {
|
|
518
|
+
if (!isValidMimeType(type)) {
|
|
519
|
+
log(`MIME type value «${type}» for the «${tool.name}» Tool is not a valid MIME type`, 'warn');
|
|
520
|
+
|
|
521
|
+
return false;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
return true;
|
|
525
|
+
});
|
|
526
|
+
})();
|
|
527
|
+
|
|
528
|
+
this.toolsFiles[tool.name] = {
|
|
529
|
+
extensions: normalizedExtensions,
|
|
530
|
+
mimeTypes: normalizedMimeTypes,
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* Get RegExp patterns to substitute by Tool
|
|
536
|
+
* @param tool - BlockTool object
|
|
537
|
+
*/
|
|
538
|
+
private getPatternsConfig(tool: BlockToolAdapter): void {
|
|
539
|
+
if (
|
|
540
|
+
tool.pasteConfig === false ||
|
|
541
|
+
!tool.pasteConfig.patterns ||
|
|
542
|
+
isEmpty(tool.pasteConfig.patterns)
|
|
543
|
+
) {
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
Object.entries(tool.pasteConfig.patterns).forEach(([key, pattern]: [string, RegExp]) => {
|
|
548
|
+
/** Still need to validate pattern as it provided by user */
|
|
549
|
+
if (!(pattern instanceof RegExp)) {
|
|
550
|
+
log(
|
|
551
|
+
`Pattern ${pattern} for «${tool.name}» Tool is skipped because it should be a Regexp instance.`,
|
|
552
|
+
'warn'
|
|
553
|
+
);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
this.toolsPatterns.push({
|
|
557
|
+
key,
|
|
558
|
+
pattern,
|
|
559
|
+
tool,
|
|
560
|
+
});
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* Check if browser behavior suits better
|
|
566
|
+
* @param {EventTarget} element - element where content has been pasted
|
|
567
|
+
* @returns {boolean}
|
|
568
|
+
*/
|
|
569
|
+
private isNativeBehaviour(element: EventTarget): boolean {
|
|
570
|
+
return $.isNativeInput(element);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* Check if Blok should process pasted data and pass data transfer object to handler
|
|
575
|
+
* @param {ClipboardEvent} event - clipboard event
|
|
576
|
+
*/
|
|
577
|
+
private handlePasteEvent = async (event: ClipboardEvent): Promise<void> => {
|
|
578
|
+
const { BlockManager, Toolbar } = this.Blok;
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* When someone pasting into a block, its more stable to set current block by event target, instead of relying on current block set before
|
|
582
|
+
*/
|
|
583
|
+
const currentBlock = BlockManager.setCurrentBlockByChildNode(event.target as HTMLElement);
|
|
584
|
+
|
|
585
|
+
/** If target is native input or is not Block, use browser behaviour */
|
|
586
|
+
if (
|
|
587
|
+
!currentBlock || (event.target && this.isNativeBehaviour(event.target) && event.clipboardData && !event.clipboardData.types.includes('Files'))
|
|
588
|
+
) {
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* If Tools is in list of errors, skip processing of paste event
|
|
594
|
+
*/
|
|
595
|
+
if (this.exceptionList.includes(currentBlock.name)) {
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
event.preventDefault();
|
|
600
|
+
if (event.clipboardData) {
|
|
601
|
+
await this.processDataTransfer(event.clipboardData);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
Toolbar.close();
|
|
605
|
+
};
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* Get files from data transfer object and insert related Tools
|
|
609
|
+
* @param {FileList} items - pasted items
|
|
610
|
+
*/
|
|
611
|
+
private async processFiles(items: FileList): Promise<void> {
|
|
612
|
+
const { BlockManager } = this.Blok;
|
|
613
|
+
|
|
614
|
+
const processedFiles = await Promise.all(
|
|
615
|
+
Array
|
|
616
|
+
.from(items)
|
|
617
|
+
.map((item) => this.processFile(item))
|
|
618
|
+
);
|
|
619
|
+
const dataToInsert = processedFiles.filter((data): data is { type: string; event: PasteEvent } => data != null);
|
|
620
|
+
|
|
621
|
+
if (dataToInsert.length === 0) {
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
const shouldReplaceCurrentBlock = this.shouldReplaceCurrentBlockForFile(dataToInsert[0]?.type);
|
|
626
|
+
|
|
627
|
+
for (const [index, data] of dataToInsert.entries()) {
|
|
628
|
+
await BlockManager.paste(data.type, data.event, index === 0 && shouldReplaceCurrentBlock);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* Get information about file and find Tool to handle it
|
|
634
|
+
* @param {File} file - file to process
|
|
635
|
+
*/
|
|
636
|
+
private async processFile(file: File): Promise<{ event: PasteEvent; type: string } | undefined> {
|
|
637
|
+
const extension = getFileExtension(file);
|
|
638
|
+
|
|
639
|
+
const foundConfig = Object
|
|
640
|
+
.entries(this.toolsFiles)
|
|
641
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
642
|
+
.find(([toolName, { mimeTypes, extensions } ]) => {
|
|
643
|
+
const [fileType, fileSubtype] = file.type.split('/');
|
|
644
|
+
|
|
645
|
+
const foundExt = extensions.find((ext) => ext.toLowerCase() === extension.toLowerCase());
|
|
646
|
+
const foundMimeType = mimeTypes.find((mime) => {
|
|
647
|
+
const [type, subtype] = mime.split('/');
|
|
648
|
+
|
|
649
|
+
return type === fileType && (subtype === fileSubtype || subtype === '*');
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
return foundExt !== undefined || foundMimeType !== undefined;
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
if (!foundConfig) {
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
const [ tool ] = foundConfig;
|
|
660
|
+
const pasteEvent = this.composePasteEvent('file', {
|
|
661
|
+
file,
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
return {
|
|
665
|
+
event: pasteEvent,
|
|
666
|
+
type: tool,
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/**
|
|
671
|
+
* Split HTML string to blocks and return it as array of Block data
|
|
672
|
+
* @param {string} innerHTML - html string to process
|
|
673
|
+
* @returns {PasteData[]}
|
|
674
|
+
*/
|
|
675
|
+
private processHTML(innerHTML: string): PasteData[] {
|
|
676
|
+
const { Tools } = this.Blok;
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* @todo Research, do we really need to always wrap innerHTML to a div:
|
|
680
|
+
* - <img> tag could be processed separately, but for now it becomes div-wrapped
|
|
681
|
+
* and then .getNodes() returns strange: [document-fragment, img]
|
|
682
|
+
* (description of the method says that it should should return only block tags or fragments,
|
|
683
|
+
* but there are inline-block element along with redundant empty fragment)
|
|
684
|
+
* - probably this is a reason of bugs with unexpected new block creation instead of inline pasting:
|
|
685
|
+
* - https://github.com/codex-team/editor.js/issues/1427
|
|
686
|
+
* - https://github.com/codex-team/editor.js/issues/1244
|
|
687
|
+
* - https://github.com/codex-team/editor.js/issues/740
|
|
688
|
+
*/
|
|
689
|
+
const wrapper = $.make('DIV');
|
|
690
|
+
|
|
691
|
+
wrapper.innerHTML = innerHTML;
|
|
692
|
+
|
|
693
|
+
const nodes = this.getNodes(wrapper);
|
|
694
|
+
|
|
695
|
+
return nodes
|
|
696
|
+
.map((node) => {
|
|
697
|
+
const nodeData = (() => {
|
|
698
|
+
switch (node.nodeType) {
|
|
699
|
+
case Node.DOCUMENT_FRAGMENT_NODE: {
|
|
700
|
+
const fragmentWrapper = $.make('div');
|
|
701
|
+
|
|
702
|
+
fragmentWrapper.appendChild(node);
|
|
703
|
+
|
|
704
|
+
return {
|
|
705
|
+
content: fragmentWrapper,
|
|
706
|
+
tool: Tools.defaultTool,
|
|
707
|
+
isBlock: false,
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
case Node.ELEMENT_NODE: {
|
|
712
|
+
const elementContent = node as HTMLElement;
|
|
713
|
+
const tagSubstitute = this.toolsTags[elementContent.tagName];
|
|
714
|
+
|
|
715
|
+
return {
|
|
716
|
+
content: elementContent,
|
|
717
|
+
tool: tagSubstitute?.tool ?? Tools.defaultTool,
|
|
718
|
+
isBlock: true,
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
default:
|
|
723
|
+
return null;
|
|
724
|
+
}
|
|
725
|
+
})();
|
|
726
|
+
|
|
727
|
+
if (!nodeData) {
|
|
728
|
+
return null;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
const { content, tool, isBlock } = nodeData;
|
|
732
|
+
|
|
733
|
+
const tagsOrSanitizeConfigs = tool.pasteConfig === false
|
|
734
|
+
? []
|
|
735
|
+
: (tool.pasteConfig?.tags || []);
|
|
736
|
+
|
|
737
|
+
const toolTags = tagsOrSanitizeConfigs.reduce<SanitizerConfig>((result, tagOrSanitizeConfig) => {
|
|
738
|
+
const tags = this.collectTagNames(tagOrSanitizeConfig);
|
|
739
|
+
const nextResult: SanitizerConfig = { ...result };
|
|
740
|
+
|
|
741
|
+
tags.forEach((tag) => {
|
|
742
|
+
const sanitizationConfig = isObject(tagOrSanitizeConfig)
|
|
743
|
+
? (tagOrSanitizeConfig as SanitizerConfig)[tag]
|
|
744
|
+
: null;
|
|
745
|
+
|
|
746
|
+
nextResult[tag.toLowerCase()] = sanitizationConfig ?? {};
|
|
747
|
+
});
|
|
748
|
+
|
|
749
|
+
return nextResult;
|
|
750
|
+
}, {} as SanitizerConfig);
|
|
751
|
+
|
|
752
|
+
const structuralSanitizeConfig = this.getStructuralTagsSanitizeConfig(content);
|
|
753
|
+
const customConfig = Object.assign({}, structuralSanitizeConfig, toolTags, tool.baseSanitizeConfig);
|
|
754
|
+
const sanitizedContent = (() => {
|
|
755
|
+
if (content.tagName.toLowerCase() !== 'table') {
|
|
756
|
+
content.innerHTML = clean(content.innerHTML, customConfig);
|
|
757
|
+
|
|
758
|
+
return content;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
const cleanTableHTML = clean(content.outerHTML, customConfig);
|
|
762
|
+
const tmpWrapper = $.make('div', undefined, {
|
|
763
|
+
innerHTML: cleanTableHTML,
|
|
764
|
+
});
|
|
765
|
+
const firstChild = tmpWrapper.firstChild;
|
|
766
|
+
|
|
767
|
+
if (!firstChild || !(firstChild instanceof HTMLElement)) {
|
|
768
|
+
return null;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
return firstChild;
|
|
772
|
+
})();
|
|
773
|
+
|
|
774
|
+
if (!sanitizedContent) {
|
|
775
|
+
return null;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
const event = this.composePasteEvent('tag', {
|
|
779
|
+
data: sanitizedContent,
|
|
780
|
+
});
|
|
781
|
+
|
|
782
|
+
return {
|
|
783
|
+
content: sanitizedContent,
|
|
784
|
+
isBlock,
|
|
785
|
+
tool: tool.name,
|
|
786
|
+
event,
|
|
787
|
+
};
|
|
788
|
+
})
|
|
789
|
+
.filter((data): data is PasteData => {
|
|
790
|
+
if (!data) {
|
|
791
|
+
return false;
|
|
792
|
+
}
|
|
793
|
+
const isContentEmpty = $.isEmpty(data.content);
|
|
794
|
+
const isSingleTag = $.isSingleTag(data.content);
|
|
795
|
+
|
|
796
|
+
return !isContentEmpty || isSingleTag;
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
/**
|
|
801
|
+
* Split plain text by new line symbols and return it as array of Block data
|
|
802
|
+
* @param {string} plain - string to process
|
|
803
|
+
* @returns {PasteData[]}
|
|
804
|
+
*/
|
|
805
|
+
private processPlain(plain: string): PasteData[] {
|
|
806
|
+
const { defaultBlock } = this.config as { defaultBlock: string };
|
|
807
|
+
|
|
808
|
+
if (!plain) {
|
|
809
|
+
return [];
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
const tool = defaultBlock;
|
|
813
|
+
|
|
814
|
+
return plain
|
|
815
|
+
.split(/\r?\n/)
|
|
816
|
+
.filter((text) => text.trim())
|
|
817
|
+
.map((text) => {
|
|
818
|
+
const content = $.make('div');
|
|
819
|
+
|
|
820
|
+
content.textContent = text;
|
|
821
|
+
|
|
822
|
+
const event = this.composePasteEvent('tag', {
|
|
823
|
+
data: content,
|
|
824
|
+
});
|
|
825
|
+
|
|
826
|
+
return {
|
|
827
|
+
content,
|
|
828
|
+
tool,
|
|
829
|
+
isBlock: false,
|
|
830
|
+
event,
|
|
831
|
+
};
|
|
832
|
+
});
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
/**
|
|
836
|
+
* Process paste of single Block tool content
|
|
837
|
+
* @param {PasteData} dataToInsert - data of Block to insert
|
|
838
|
+
*/
|
|
839
|
+
private async processSingleBlock(dataToInsert: PasteData): Promise<void> {
|
|
840
|
+
const { Caret, BlockManager } = this.Blok;
|
|
841
|
+
const { currentBlock } = BlockManager;
|
|
842
|
+
|
|
843
|
+
/**
|
|
844
|
+
* If pasted tool isn`t equal current Block or if pasted content contains block elements, insert it as new Block
|
|
845
|
+
*/
|
|
846
|
+
if (
|
|
847
|
+
!currentBlock ||
|
|
848
|
+
dataToInsert.tool !== currentBlock.name ||
|
|
849
|
+
!$.containsOnlyInlineElements(dataToInsert.content.innerHTML)
|
|
850
|
+
) {
|
|
851
|
+
await this.insertBlock(dataToInsert, currentBlock ? (currentBlock.tool.isDefault && currentBlock.isEmpty) : false);
|
|
852
|
+
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
Caret.insertContentAtCaretPosition(dataToInsert.content.innerHTML);
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
/**
|
|
860
|
+
* Process paste to single Block:
|
|
861
|
+
* 1. Find patterns` matches
|
|
862
|
+
* 2. Insert new block if it is not the same type as current one
|
|
863
|
+
* 3. Just insert text if there is no substitutions
|
|
864
|
+
* @param {PasteData} dataToInsert - data of Block to insert
|
|
865
|
+
*/
|
|
866
|
+
private async processInlinePaste(dataToInsert: PasteData): Promise<void> {
|
|
867
|
+
const { BlockManager, Caret } = this.Blok;
|
|
868
|
+
const { content } = dataToInsert;
|
|
869
|
+
|
|
870
|
+
const currentBlockIsDefault = BlockManager.currentBlock?.tool.isDefault ?? false;
|
|
871
|
+
const textContent = content.textContent;
|
|
872
|
+
|
|
873
|
+
const canProcessPattern = currentBlockIsDefault &&
|
|
874
|
+
textContent !== null &&
|
|
875
|
+
textContent.length < Paste.PATTERN_PROCESSING_MAX_LENGTH;
|
|
876
|
+
|
|
877
|
+
const blockData = canProcessPattern && textContent !== null
|
|
878
|
+
? await this.processPattern(textContent)
|
|
879
|
+
: undefined;
|
|
880
|
+
|
|
881
|
+
if (blockData) {
|
|
882
|
+
const needToReplaceCurrentBlock = BlockManager.currentBlock &&
|
|
883
|
+
BlockManager.currentBlock.tool.isDefault &&
|
|
884
|
+
BlockManager.currentBlock.isEmpty;
|
|
885
|
+
|
|
886
|
+
const insertedBlock = await BlockManager.paste(blockData.tool, blockData.event, needToReplaceCurrentBlock);
|
|
887
|
+
|
|
888
|
+
Caret.setToBlock(insertedBlock, Caret.positions.END);
|
|
889
|
+
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
/** If there is no pattern substitute - insert string as it is */
|
|
894
|
+
if (BlockManager.currentBlock && BlockManager.currentBlock.currentInput) {
|
|
895
|
+
const currentToolSanitizeConfig = BlockManager.currentBlock.tool.baseSanitizeConfig;
|
|
896
|
+
|
|
897
|
+
Caret.insertContentAtCaretPosition(
|
|
898
|
+
clean(content.innerHTML, currentToolSanitizeConfig)
|
|
899
|
+
);
|
|
900
|
+
} else {
|
|
901
|
+
await this.insertBlock(dataToInsert);
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
/**
|
|
906
|
+
* Get patterns` matches
|
|
907
|
+
* @param {string} text - text to process
|
|
908
|
+
* @returns {Promise<{event: PasteEvent, tool: string}>}
|
|
909
|
+
*/
|
|
910
|
+
private async processPattern(text: string): Promise<{ event: PasteEvent; tool: string } | undefined> {
|
|
911
|
+
const pattern = this.toolsPatterns.find((substitute) => {
|
|
912
|
+
const execResult = substitute.pattern.exec(text);
|
|
913
|
+
|
|
914
|
+
if (!execResult) {
|
|
915
|
+
return false;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
return text === execResult.shift();
|
|
919
|
+
});
|
|
920
|
+
|
|
921
|
+
if (!pattern) {
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
const event = this.composePasteEvent('pattern', {
|
|
926
|
+
key: pattern.key,
|
|
927
|
+
data: text,
|
|
928
|
+
});
|
|
929
|
+
|
|
930
|
+
return {
|
|
931
|
+
event,
|
|
932
|
+
tool: pattern.tool.name,
|
|
933
|
+
};
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
/**
|
|
937
|
+
* Insert pasted Block content to Blok
|
|
938
|
+
* @param {PasteData} data - data to insert
|
|
939
|
+
* @param {boolean} canReplaceCurrentBlock - if true and is current Block is empty, will replace current Block
|
|
940
|
+
* @returns {void}
|
|
941
|
+
*/
|
|
942
|
+
private async insertBlock(data: PasteData, canReplaceCurrentBlock = false): Promise<void> {
|
|
943
|
+
const { BlockManager, Caret } = this.Blok;
|
|
944
|
+
const { currentBlock } = BlockManager;
|
|
945
|
+
|
|
946
|
+
if (canReplaceCurrentBlock && currentBlock && currentBlock.isEmpty) {
|
|
947
|
+
const replacedBlock = await BlockManager.paste(data.tool, data.event, true);
|
|
948
|
+
|
|
949
|
+
Caret.setToBlock(replacedBlock, Caret.positions.END);
|
|
950
|
+
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
const block = await BlockManager.paste(data.tool, data.event);
|
|
955
|
+
|
|
956
|
+
Caret.setToBlock(block, Caret.positions.END);
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
/**
|
|
960
|
+
* Insert data passed as application/x-blok JSON
|
|
961
|
+
* @param {Array} blocks — Blocks' data to insert
|
|
962
|
+
* @returns {void}
|
|
963
|
+
*/
|
|
964
|
+
private insertBlokData(blocks: Pick<SavedData, 'id' | 'data' | 'tool'>[]): void {
|
|
965
|
+
const { BlockManager, Caret, Tools } = this.Blok;
|
|
966
|
+
const sanitizedBlocks = sanitizeBlocks(
|
|
967
|
+
blocks,
|
|
968
|
+
(name) => Tools.blockTools.get(name)?.sanitizeConfig ?? {},
|
|
969
|
+
this.config.sanitizer as SanitizerConfig
|
|
970
|
+
);
|
|
971
|
+
|
|
972
|
+
sanitizedBlocks.forEach(({ tool, data }, i) => {
|
|
973
|
+
const needToReplaceCurrentBlock = i === 0 &&
|
|
974
|
+
Boolean(BlockManager.currentBlock?.tool.isDefault) &&
|
|
975
|
+
Boolean(BlockManager.currentBlock?.isEmpty);
|
|
976
|
+
|
|
977
|
+
const block = BlockManager.insert({
|
|
978
|
+
tool,
|
|
979
|
+
data,
|
|
980
|
+
replace: needToReplaceCurrentBlock,
|
|
981
|
+
});
|
|
982
|
+
|
|
983
|
+
Caret.setToBlock(block, Caret.positions.END);
|
|
984
|
+
});
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
/**
|
|
988
|
+
* Fetch nodes from Element node
|
|
989
|
+
* @param {Node} node - current node
|
|
990
|
+
* @param {Node[]} nodes - processed nodes
|
|
991
|
+
* @param {Node} destNode - destination node
|
|
992
|
+
*/
|
|
993
|
+
private processElementNode(node: Node, nodes: Node[], destNode: Node): Node[] | void {
|
|
994
|
+
const tags = Object.keys(this.toolsTags);
|
|
995
|
+
|
|
996
|
+
const element = node as HTMLElement;
|
|
997
|
+
|
|
998
|
+
const tagSubstitute = this.toolsTags[element.tagName];
|
|
999
|
+
const tool = tagSubstitute?.tool;
|
|
1000
|
+
const toolTags = this.tagsByTool[tool?.name ?? ''] ?? [];
|
|
1001
|
+
|
|
1002
|
+
const isSubstitutable = tags.includes(element.tagName);
|
|
1003
|
+
const isBlockElement = $.blockElements.includes(element.tagName.toLowerCase());
|
|
1004
|
+
const isStructuralElement = SAFE_STRUCTURAL_TAGS.has(element.tagName.toLowerCase());
|
|
1005
|
+
const containsAnotherToolTags = Array
|
|
1006
|
+
.from(element.children)
|
|
1007
|
+
.some(
|
|
1008
|
+
({ tagName }) => tags.includes(tagName) && !toolTags.includes(tagName)
|
|
1009
|
+
);
|
|
1010
|
+
|
|
1011
|
+
const containsBlockElements = Array.from(element.children).some(
|
|
1012
|
+
({ tagName }) => $.blockElements.includes(tagName.toLowerCase())
|
|
1013
|
+
);
|
|
1014
|
+
|
|
1015
|
+
/** Append inline elements to previous fragment */
|
|
1016
|
+
if (!isBlockElement && !isSubstitutable && !containsAnotherToolTags) {
|
|
1017
|
+
destNode.appendChild(element);
|
|
1018
|
+
|
|
1019
|
+
return [...nodes, destNode];
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
if (
|
|
1023
|
+
(isSubstitutable && !containsAnotherToolTags) ||
|
|
1024
|
+
(isBlockElement && !containsBlockElements && !containsAnotherToolTags) ||
|
|
1025
|
+
(isStructuralElement && !containsAnotherToolTags)
|
|
1026
|
+
) {
|
|
1027
|
+
return [...nodes, destNode, element];
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
/**
|
|
1032
|
+
* Recursively divide HTML string to two types of nodes:
|
|
1033
|
+
* 1. Block element
|
|
1034
|
+
* 2. Document Fragments contained text and markup tags like a, b, i etc.
|
|
1035
|
+
* @param {Node} wrapper - wrapper of paster HTML content
|
|
1036
|
+
* @returns {Node[]}
|
|
1037
|
+
*/
|
|
1038
|
+
private getNodes(wrapper: Node): Node[] {
|
|
1039
|
+
const children = Array.from(wrapper.childNodes);
|
|
1040
|
+
|
|
1041
|
+
const reducer = (nodes: Node[], node: Node): Node[] => {
|
|
1042
|
+
if ($.isEmpty(node) && !$.isSingleTag(node as HTMLElement)) {
|
|
1043
|
+
return nodes;
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
const lastNode = nodes[nodes.length - 1];
|
|
1047
|
+
const isLastNodeFragment = lastNode !== undefined && $.isFragment(lastNode);
|
|
1048
|
+
const { destNode, remainingNodes } = isLastNodeFragment
|
|
1049
|
+
? {
|
|
1050
|
+
destNode: lastNode,
|
|
1051
|
+
remainingNodes: nodes.slice(0, -1),
|
|
1052
|
+
}
|
|
1053
|
+
: {
|
|
1054
|
+
destNode: new DocumentFragment(),
|
|
1055
|
+
remainingNodes: nodes,
|
|
1056
|
+
};
|
|
1057
|
+
|
|
1058
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
1059
|
+
destNode.appendChild(node);
|
|
1060
|
+
|
|
1061
|
+
return [...remainingNodes, destNode];
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
if (node.nodeType !== Node.ELEMENT_NODE) {
|
|
1065
|
+
return [...remainingNodes, destNode];
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
const elementNodeProcessingResult = this.processElementNode(node, remainingNodes, destNode);
|
|
1069
|
+
|
|
1070
|
+
if (elementNodeProcessingResult) {
|
|
1071
|
+
return elementNodeProcessingResult;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
const processedChildNodes = Array.from(node.childNodes).reduce(reducer, []);
|
|
1075
|
+
|
|
1076
|
+
return [...remainingNodes, ...processedChildNodes];
|
|
1077
|
+
};
|
|
1078
|
+
|
|
1079
|
+
return children.reduce(reducer, []);
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
/**
|
|
1083
|
+
* Compose paste event with passed type and detail
|
|
1084
|
+
* @param {string} type - event type
|
|
1085
|
+
* @param {PasteEventDetail} detail - event detail
|
|
1086
|
+
*/
|
|
1087
|
+
private composePasteEvent(type: string, detail: PasteEventDetail): PasteEvent {
|
|
1088
|
+
return new CustomEvent(type, {
|
|
1089
|
+
detail,
|
|
1090
|
+
}) as PasteEvent;
|
|
1091
|
+
}
|
|
1092
|
+
}
|