@jackuait/blok 0.4.1-beta.5 → 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-Cl_5rkKS.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 +59 -156
- package/package.json +48 -16
- 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 -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 +0 -5
- package/dist/blok-DvN73wsH.mjs +0 -19922
- package/dist/blok.umd.js +0 -166
package/dist/tools.mjs
ADDED
|
@@ -0,0 +1,3073 @@
|
|
|
1
|
+
var nt = Object.defineProperty, rt = Object.defineProperties;
|
|
2
|
+
var st = Object.getOwnPropertyDescriptors;
|
|
3
|
+
var K = Object.getOwnPropertySymbols;
|
|
4
|
+
var ot = Object.prototype.hasOwnProperty, it = Object.prototype.propertyIsEnumerable;
|
|
5
|
+
var U = (f, t, e) => t in f ? nt(f, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : f[t] = e, w = (f, t) => {
|
|
6
|
+
for (var e in t || (t = {}))
|
|
7
|
+
ot.call(t, e) && U(f, e, t[e]);
|
|
8
|
+
if (K)
|
|
9
|
+
for (var e of K(t))
|
|
10
|
+
it.call(t, e) && U(f, e, t[e]);
|
|
11
|
+
return f;
|
|
12
|
+
}, P = (f, t) => rt(f, st(t));
|
|
13
|
+
import { t as L, D as g, a9 as et, aa as at, ab as lt, ac as ct, ad as dt, ae as ut, af as ht, ag as ft, ah as pt, ai as G, aj as j, ak as $, f as A, al as gt, am as mt, S as H, P as Et, an as Tt, l as Ct, J as At } from "./chunks/inline-tool-convert-RBcopmCh.mjs";
|
|
14
|
+
import { a0 as Ot } from "./chunks/inline-tool-convert-RBcopmCh.mjs";
|
|
15
|
+
const W = [
|
|
16
|
+
"empty:before:pointer-events-none",
|
|
17
|
+
"empty:before:text-gray-text",
|
|
18
|
+
"empty:before:cursor-text",
|
|
19
|
+
"empty:before:content-[attr(data-placeholder)]",
|
|
20
|
+
"[&[data-blok-empty=true]]:before:pointer-events-none",
|
|
21
|
+
"[&[data-blok-empty=true]]:before:text-gray-text",
|
|
22
|
+
"[&[data-blok-empty=true]]:before:cursor-text",
|
|
23
|
+
"[&[data-blok-empty=true]]:before:content-[attr(data-placeholder)]"
|
|
24
|
+
], yt = [
|
|
25
|
+
"empty:focus:before:pointer-events-none",
|
|
26
|
+
"empty:focus:before:text-gray-text",
|
|
27
|
+
"empty:focus:before:cursor-text",
|
|
28
|
+
"empty:focus:before:content-[attr(data-blok-placeholder-active)]",
|
|
29
|
+
"[&[data-empty=true]:focus]:before:pointer-events-none",
|
|
30
|
+
"[&[data-empty=true]:focus]:before:text-gray-text",
|
|
31
|
+
"[&[data-empty=true]:focus]:before:cursor-text",
|
|
32
|
+
"[&[data-empty=true]:focus]:before:content-[attr(data-blok-placeholder-active)]"
|
|
33
|
+
], bt = (f) => {
|
|
34
|
+
const t = f.innerHTML.trim();
|
|
35
|
+
return t === "" || t === "<br>";
|
|
36
|
+
}, St = (f) => {
|
|
37
|
+
f.innerHTML === "<br>" && (f.innerHTML = "");
|
|
38
|
+
const t = window.getSelection();
|
|
39
|
+
if (!t) return;
|
|
40
|
+
const e = document.createRange();
|
|
41
|
+
e.selectNodeContents(f), e.collapse(!0), t.removeAllRanges(), t.addRange(e);
|
|
42
|
+
}, Nt = (f) => {
|
|
43
|
+
bt(f) && St(f);
|
|
44
|
+
}, z = (f, t, e = "data-placeholder") => {
|
|
45
|
+
f.setAttribute(e, t != null ? t : "");
|
|
46
|
+
const n = () => Nt(f);
|
|
47
|
+
f.addEventListener("focus", n), f.addEventListener("input", n);
|
|
48
|
+
}, Bt = (f) => {
|
|
49
|
+
const t = document.createElement("div");
|
|
50
|
+
t.innerHTML = f.trim();
|
|
51
|
+
const e = document.createDocumentFragment();
|
|
52
|
+
return e.append(...Array.from(t.childNodes)), e;
|
|
53
|
+
}, M = class M {
|
|
54
|
+
/**
|
|
55
|
+
* Default placeholder for Paragraph Tool
|
|
56
|
+
*
|
|
57
|
+
* @returns empty string
|
|
58
|
+
*/
|
|
59
|
+
static get DEFAULT_PLACEHOLDER() {
|
|
60
|
+
return "";
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Render plugin's main Element and fill it with saved data
|
|
64
|
+
*
|
|
65
|
+
* @param options - constructor options
|
|
66
|
+
* @param options.data - previously saved data
|
|
67
|
+
* @param options.config - user config for Tool
|
|
68
|
+
* @param options.api - editor.js api
|
|
69
|
+
* @param options.readOnly - read only mode flag
|
|
70
|
+
*/
|
|
71
|
+
constructor({ data: t, config: e, api: n, readOnly: r }) {
|
|
72
|
+
var s, o, a;
|
|
73
|
+
this.api = n, this.readOnly = r, this.readOnly || (this.onKeyUp = this.onKeyUp.bind(this)), this._placeholder = (s = e == null ? void 0 : e.placeholder) != null ? s : M.DEFAULT_PLACEHOLDER, this._data = t != null ? t : { text: "" }, this._element = null, this._preserveBlank = (o = e == null ? void 0 : e.preserveBlank) != null ? o : !1, this._styles = (a = e == null ? void 0 : e.styles) != null ? a : {};
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Check if text content is empty and set empty string to inner html.
|
|
77
|
+
* We need this because some browsers (e.g. Safari) insert <br> into empty contenteditable elements
|
|
78
|
+
*
|
|
79
|
+
* @param e - key up event
|
|
80
|
+
*/
|
|
81
|
+
onKeyUp(t) {
|
|
82
|
+
if (t.code !== "Backspace" && t.code !== "Delete" || !this._element)
|
|
83
|
+
return;
|
|
84
|
+
const { textContent: e } = this._element;
|
|
85
|
+
e === "" && (this._element.innerHTML = "");
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Build inline styles from style configuration
|
|
89
|
+
*
|
|
90
|
+
* @returns Partial CSSStyleDeclaration with custom styles
|
|
91
|
+
*/
|
|
92
|
+
buildInlineStyles() {
|
|
93
|
+
const t = {};
|
|
94
|
+
return this._styles.size && (t.fontSize = this._styles.size), this._styles.lineHeight && (t.lineHeight = this._styles.lineHeight), this._styles.marginTop && (t.marginTop = this._styles.marginTop), this._styles.marginBottom && (t.marginBottom = this._styles.marginBottom), t;
|
|
95
|
+
}
|
|
96
|
+
drawView() {
|
|
97
|
+
const t = document.createElement("DIV");
|
|
98
|
+
t.className = L(
|
|
99
|
+
this.api.styles.block,
|
|
100
|
+
M.WRAPPER_CLASSES,
|
|
101
|
+
yt
|
|
102
|
+
), t.setAttribute(g.tool, "paragraph"), t.contentEditable = "false";
|
|
103
|
+
const e = this.buildInlineStyles();
|
|
104
|
+
return Object.keys(e).length > 0 && Object.assign(t.style, e), this._data.text && (t.innerHTML = this._data.text), this.readOnly ? t.setAttribute("data-blok-placeholder-active", this.api.i18n.t(this._placeholder)) : (t.contentEditable = "true", t.addEventListener("keyup", this.onKeyUp), z(t, this.api.i18n.t(this._placeholder), "data-blok-placeholder-active")), t;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Return Tool's view
|
|
108
|
+
*
|
|
109
|
+
* @returns HTMLDivElement
|
|
110
|
+
*/
|
|
111
|
+
render() {
|
|
112
|
+
return this._element = this.drawView(), this._element;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Method that specified how to merge two Text blocks.
|
|
116
|
+
* Called by Editor by backspace at the beginning of the Block
|
|
117
|
+
*
|
|
118
|
+
* @param data - saved data to merge with current block
|
|
119
|
+
*/
|
|
120
|
+
merge(t) {
|
|
121
|
+
if (!this._element)
|
|
122
|
+
return;
|
|
123
|
+
this._data.text += t.text;
|
|
124
|
+
const e = Bt(t.text);
|
|
125
|
+
this._element.appendChild(e), this._element.normalize();
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Validate Paragraph block data:
|
|
129
|
+
* - check for emptiness
|
|
130
|
+
*
|
|
131
|
+
* @param savedData - data received after saving
|
|
132
|
+
* @returns false if saved data is not correct, otherwise true
|
|
133
|
+
*/
|
|
134
|
+
validate(t) {
|
|
135
|
+
return !(t.text.trim() === "" && !this._preserveBlank);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Extract Tool's data from the view
|
|
139
|
+
*
|
|
140
|
+
* @param toolsContent - Paragraph tools rendered view
|
|
141
|
+
* @returns saved data
|
|
142
|
+
*/
|
|
143
|
+
save(t) {
|
|
144
|
+
return {
|
|
145
|
+
text: et(t.innerHTML)
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* On paste callback fired from Editor.
|
|
150
|
+
*
|
|
151
|
+
* @param event - event with pasted data
|
|
152
|
+
*/
|
|
153
|
+
onPaste(t) {
|
|
154
|
+
const e = t.detail;
|
|
155
|
+
if (!("data" in e))
|
|
156
|
+
return;
|
|
157
|
+
const r = {
|
|
158
|
+
text: e.data.innerHTML
|
|
159
|
+
};
|
|
160
|
+
this._data = r, window.requestAnimationFrame(() => {
|
|
161
|
+
this._element && (this._element.innerHTML = this._data.text || "");
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Enable Conversion Toolbar. Paragraph can be converted to/from other tools
|
|
166
|
+
*
|
|
167
|
+
* @returns ConversionConfig
|
|
168
|
+
*/
|
|
169
|
+
static get conversionConfig() {
|
|
170
|
+
return {
|
|
171
|
+
export: "text",
|
|
172
|
+
// to convert Paragraph to other block, use 'text' property of saved data
|
|
173
|
+
import: "text"
|
|
174
|
+
// to convert other block's exported string to Paragraph, fill 'text' property of tool data
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Sanitizer rules
|
|
179
|
+
*
|
|
180
|
+
* @returns SanitizerConfig
|
|
181
|
+
*/
|
|
182
|
+
static get sanitize() {
|
|
183
|
+
return {
|
|
184
|
+
text: {
|
|
185
|
+
br: !0
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Returns true to notify the core that read-only mode is supported
|
|
191
|
+
*
|
|
192
|
+
* @returns true
|
|
193
|
+
*/
|
|
194
|
+
static get isReadOnlySupported() {
|
|
195
|
+
return !0;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Used by Editor paste handling API.
|
|
199
|
+
* Provides configuration to handle P tags.
|
|
200
|
+
*
|
|
201
|
+
* @returns PasteConfig
|
|
202
|
+
*/
|
|
203
|
+
static get pasteConfig() {
|
|
204
|
+
return {
|
|
205
|
+
tags: ["P"]
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Icon and title for displaying at the Toolbox
|
|
210
|
+
*
|
|
211
|
+
* @returns ToolboxConfig
|
|
212
|
+
*/
|
|
213
|
+
static get toolbox() {
|
|
214
|
+
return {
|
|
215
|
+
icon: at,
|
|
216
|
+
title: "Text",
|
|
217
|
+
titleKey: "text"
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
M.WRAPPER_CLASSES = [
|
|
222
|
+
"leading-[1.6em]",
|
|
223
|
+
"outline-none",
|
|
224
|
+
"mt-[2px]",
|
|
225
|
+
"mb-px",
|
|
226
|
+
"[&>p:first-of-type]:mt-0",
|
|
227
|
+
"[&>p:last-of-type]:mb-0"
|
|
228
|
+
];
|
|
229
|
+
let X = M;
|
|
230
|
+
const I = class I {
|
|
231
|
+
/**
|
|
232
|
+
* Render plugin's main Element and fill it with saved data
|
|
233
|
+
*
|
|
234
|
+
* @param options - constructor options
|
|
235
|
+
* @param options.data - previously saved data
|
|
236
|
+
* @param options.config - user config for Tool
|
|
237
|
+
* @param options.api - Editor API
|
|
238
|
+
* @param options.readOnly - read only mode flag
|
|
239
|
+
*/
|
|
240
|
+
constructor({ data: t, config: e, api: n, readOnly: r }) {
|
|
241
|
+
this.api = n, this.readOnly = r, this._settings = e || {}, this._data = this.normalizeData(t), this._element = this.getTag();
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Styles
|
|
245
|
+
* @deprecated Use data-blok-tool attribute instead (DATA_ATTR.tool)
|
|
246
|
+
*/
|
|
247
|
+
get _CSS() {
|
|
248
|
+
return {
|
|
249
|
+
block: this.api.styles.block,
|
|
250
|
+
wrapper: ""
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Check if data is valid HeaderData
|
|
255
|
+
*
|
|
256
|
+
* @param data - data to check
|
|
257
|
+
* @returns true if data is HeaderData
|
|
258
|
+
*/
|
|
259
|
+
isHeaderData(t) {
|
|
260
|
+
return typeof t == "object" && t !== null && "text" in t;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Normalize input data
|
|
264
|
+
*
|
|
265
|
+
* @param data - saved data to process
|
|
266
|
+
* @returns normalized HeaderData
|
|
267
|
+
*/
|
|
268
|
+
normalizeData(t) {
|
|
269
|
+
if (!this.isHeaderData(t))
|
|
270
|
+
return { text: "", level: this.defaultLevel.number };
|
|
271
|
+
const e = parseInt(String(t.level)), n = t.level !== void 0 && !isNaN(e);
|
|
272
|
+
return {
|
|
273
|
+
text: t.text || "",
|
|
274
|
+
level: n ? e : this.defaultLevel.number
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Return Tool's view
|
|
279
|
+
*
|
|
280
|
+
* @returns HTMLHeadingElement
|
|
281
|
+
*/
|
|
282
|
+
render() {
|
|
283
|
+
return this._element;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Returns header block tunes config
|
|
287
|
+
*
|
|
288
|
+
* @returns MenuConfig array
|
|
289
|
+
*/
|
|
290
|
+
renderSettings() {
|
|
291
|
+
return this.levels.map((t) => {
|
|
292
|
+
const e = this.api.i18n.t(t.nameKey), n = e !== t.nameKey ? e : t.name;
|
|
293
|
+
return {
|
|
294
|
+
icon: t.icon,
|
|
295
|
+
title: n,
|
|
296
|
+
onActivate: () => this.setLevel(t.number),
|
|
297
|
+
closeOnActivate: !0,
|
|
298
|
+
isActive: this.currentLevel.number === t.number,
|
|
299
|
+
dataset: {
|
|
300
|
+
"blok-header-level": String(t.number)
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Callback for Block's settings buttons
|
|
307
|
+
*
|
|
308
|
+
* @param level - level to set
|
|
309
|
+
*/
|
|
310
|
+
setLevel(t) {
|
|
311
|
+
this.data = {
|
|
312
|
+
level: t,
|
|
313
|
+
text: this.data.text
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Method that specified how to merge two Text blocks.
|
|
318
|
+
* Called by Editor by backspace at the beginning of the Block
|
|
319
|
+
*
|
|
320
|
+
* @param data - saved data to merge with current block
|
|
321
|
+
*/
|
|
322
|
+
merge(t) {
|
|
323
|
+
this._element.insertAdjacentHTML("beforeend", t.text);
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Validate Text block data:
|
|
327
|
+
* - check for emptiness
|
|
328
|
+
*
|
|
329
|
+
* @param blockData - data received after saving
|
|
330
|
+
* @returns false if saved data is not correct, otherwise true
|
|
331
|
+
*/
|
|
332
|
+
validate(t) {
|
|
333
|
+
return t.text.trim() !== "";
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Extract Tool's data from the view
|
|
337
|
+
*
|
|
338
|
+
* @param toolsContent - Text tools rendered view
|
|
339
|
+
* @returns saved data
|
|
340
|
+
*/
|
|
341
|
+
save(t) {
|
|
342
|
+
return {
|
|
343
|
+
text: t.innerHTML,
|
|
344
|
+
level: this.currentLevel.number
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Allow Header to be converted to/from other blocks
|
|
349
|
+
*/
|
|
350
|
+
static get conversionConfig() {
|
|
351
|
+
return {
|
|
352
|
+
export: "text",
|
|
353
|
+
// use 'text' property for other blocks
|
|
354
|
+
import: "text"
|
|
355
|
+
// fill 'text' property from other block's export string
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Sanitizer Rules
|
|
360
|
+
*/
|
|
361
|
+
static get sanitize() {
|
|
362
|
+
return {
|
|
363
|
+
level: !1,
|
|
364
|
+
text: {}
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Returns true to notify core that read-only is supported
|
|
369
|
+
*
|
|
370
|
+
* @returns true
|
|
371
|
+
*/
|
|
372
|
+
static get isReadOnlySupported() {
|
|
373
|
+
return !0;
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Get current Tool's data
|
|
377
|
+
*
|
|
378
|
+
* @returns Current data
|
|
379
|
+
*/
|
|
380
|
+
get data() {
|
|
381
|
+
return this._data.text = this._element.innerHTML, this._data.level = this.currentLevel.number, this._data;
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Store data in plugin:
|
|
385
|
+
* - at the this._data property
|
|
386
|
+
* - at the HTML
|
|
387
|
+
*
|
|
388
|
+
* @param data - data to set
|
|
389
|
+
*/
|
|
390
|
+
set data(t) {
|
|
391
|
+
if (this._data = this.normalizeData(t), t.level !== void 0 && this._element.parentNode) {
|
|
392
|
+
const e = this.getTag();
|
|
393
|
+
e.innerHTML = this._element.innerHTML, this._element.parentNode.replaceChild(e, this._element), this._element = e;
|
|
394
|
+
}
|
|
395
|
+
t.text !== void 0 && (this._element.innerHTML = this._data.text || "");
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Get tag for target level
|
|
399
|
+
* By default returns second-leveled header
|
|
400
|
+
*
|
|
401
|
+
* @returns HTMLHeadingElement
|
|
402
|
+
*/
|
|
403
|
+
getTag() {
|
|
404
|
+
const t = document.createElement(this.currentLevel.tag);
|
|
405
|
+
t.innerHTML = this._data.text || "", t.className = L(I.BASE_STYLES, this.currentLevel.styles, W);
|
|
406
|
+
const { inlineStyles: e } = this.currentLevel;
|
|
407
|
+
return e && Object.assign(t.style, e), t.setAttribute(g.tool, "header"), t.contentEditable = this.readOnly ? "false" : "true", this.readOnly ? t.setAttribute("data-placeholder", this.api.i18n.t(this._settings.placeholder || "")) : z(t, this.api.i18n.t(this._settings.placeholder || "")), t;
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Get current level
|
|
411
|
+
*
|
|
412
|
+
* @returns Level object
|
|
413
|
+
*/
|
|
414
|
+
get currentLevel() {
|
|
415
|
+
const t = this.levels.find((e) => e.number === this._data.level);
|
|
416
|
+
return t != null ? t : this.defaultLevel;
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Return default level
|
|
420
|
+
*
|
|
421
|
+
* @returns Level object
|
|
422
|
+
*/
|
|
423
|
+
get defaultLevel() {
|
|
424
|
+
if (!this._settings.defaultLevel)
|
|
425
|
+
return this.levels[1];
|
|
426
|
+
const t = this.levels.find((e) => e.number === this._settings.defaultLevel);
|
|
427
|
+
return t || (console.warn("(ง'̀-'́)ง Heading Tool: the default level specified was not found in available levels"), this.levels[1]);
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Available header levels
|
|
431
|
+
*
|
|
432
|
+
* @returns Level array
|
|
433
|
+
*/
|
|
434
|
+
get levels() {
|
|
435
|
+
const t = this._settings.levelOverrides || {}, e = I.DEFAULT_LEVELS.map((n) => {
|
|
436
|
+
var o;
|
|
437
|
+
const r = t[n.number] || {}, s = {};
|
|
438
|
+
return r.size && (s.fontSize = r.size), r.marginTop && (s.marginTop = r.marginTop), r.marginBottom && (s.marginBottom = r.marginBottom), {
|
|
439
|
+
number: n.number,
|
|
440
|
+
tag: ((o = r.tag) == null ? void 0 : o.toUpperCase()) || n.tag,
|
|
441
|
+
nameKey: n.nameKey,
|
|
442
|
+
name: r.name || n.name,
|
|
443
|
+
icon: n.icon,
|
|
444
|
+
styles: n.styles,
|
|
445
|
+
inlineStyles: s
|
|
446
|
+
};
|
|
447
|
+
});
|
|
448
|
+
return this._settings.levels ? e.filter((n) => this._settings.levels.includes(n.number)) : e;
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Handle H1-H6 tags on paste to substitute it with header Tool
|
|
452
|
+
*
|
|
453
|
+
* @param event - event with pasted content
|
|
454
|
+
*/
|
|
455
|
+
onPaste(t) {
|
|
456
|
+
var a;
|
|
457
|
+
const e = t.detail;
|
|
458
|
+
if (!("data" in e))
|
|
459
|
+
return;
|
|
460
|
+
const n = e.data, s = (a = {
|
|
461
|
+
H1: 1,
|
|
462
|
+
H2: 2,
|
|
463
|
+
H3: 3,
|
|
464
|
+
H4: 4,
|
|
465
|
+
H5: 5,
|
|
466
|
+
H6: 6
|
|
467
|
+
}[n.tagName]) != null ? a : this.defaultLevel.number, o = this._settings.levels ? this._settings.levels.reduce((l, d) => Math.abs(d - s) < Math.abs(l - s) ? d : l) : s;
|
|
468
|
+
this.data = {
|
|
469
|
+
level: o,
|
|
470
|
+
text: n.innerHTML
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Used by Editor paste handling API.
|
|
475
|
+
* Provides configuration to handle H1-H6 tags.
|
|
476
|
+
*
|
|
477
|
+
* @returns PasteConfig
|
|
478
|
+
*/
|
|
479
|
+
static get pasteConfig() {
|
|
480
|
+
return {
|
|
481
|
+
tags: ["H1", "H2", "H3", "H4", "H5", "H6"]
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Get Tool toolbox settings
|
|
486
|
+
* icon - Tool icon's SVG
|
|
487
|
+
* title - title to show in toolbox
|
|
488
|
+
*
|
|
489
|
+
* @returns ToolboxConfig
|
|
490
|
+
*/
|
|
491
|
+
static get toolbox() {
|
|
492
|
+
return {
|
|
493
|
+
icon: pt,
|
|
494
|
+
title: "Heading",
|
|
495
|
+
titleKey: "heading"
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
};
|
|
499
|
+
I.BASE_STYLES = "py-[3px] px-[2px] m-0 !leading-[1.3] outline-none [&_p]:!p-0 [&_p]:!m-0 [&_div]:!p-0 [&_div]:!m-0", I.DEFAULT_LEVELS = [
|
|
500
|
+
{ number: 1, tag: "H1", nameKey: "tools.header.heading1", name: "Heading 1", icon: lt, styles: "text-4xl font-bold mt-8 mb-1" },
|
|
501
|
+
{ number: 2, tag: "H2", nameKey: "tools.header.heading2", name: "Heading 2", icon: ct, styles: "text-3xl font-semibold mt-6 mb-px" },
|
|
502
|
+
{ number: 3, tag: "H3", nameKey: "tools.header.heading3", name: "Heading 3", icon: dt, styles: "text-2xl font-semibold mt-4 mb-px" },
|
|
503
|
+
{ number: 4, tag: "H4", nameKey: "tools.header.heading4", name: "Heading 4", icon: ut, styles: "text-xl font-semibold mt-3 mb-px" },
|
|
504
|
+
{ number: 5, tag: "H5", nameKey: "tools.header.heading5", name: "Heading 5", icon: ht, styles: "text-base font-semibold mt-3 mb-px" },
|
|
505
|
+
{ number: 6, tag: "H6", nameKey: "tools.header.heading6", name: "Heading 6", icon: ft, styles: "text-sm font-semibold mt-3 mb-px" }
|
|
506
|
+
];
|
|
507
|
+
let Y = I;
|
|
508
|
+
const u = class u {
|
|
509
|
+
constructor({ data: t, config: e, api: n, readOnly: r, block: s }) {
|
|
510
|
+
this._element = null, this.handleBlockChanged = (o) => {
|
|
511
|
+
var l;
|
|
512
|
+
const a = o;
|
|
513
|
+
((l = a == null ? void 0 : a.event) == null ? void 0 : l.type) === "block-removed" && (u.pendingMarkerUpdate || (u.pendingMarkerUpdate = !0, requestAnimationFrame(() => {
|
|
514
|
+
u.pendingMarkerUpdate = !1, this.updateAllOrderedListMarkers();
|
|
515
|
+
})));
|
|
516
|
+
}, this.api = n, this.readOnly = r, this._settings = e || {}, this._data = this.normalizeData(t), s && (this.blockId = s.id), this._data.style === "ordered" && this.api.events.on("block changed", this.handleBlockChanged);
|
|
517
|
+
}
|
|
518
|
+
normalizeData(t) {
|
|
519
|
+
var n;
|
|
520
|
+
const e = this._settings.defaultStyle || "unordered";
|
|
521
|
+
return !t || typeof t != "object" ? {
|
|
522
|
+
text: "",
|
|
523
|
+
style: e,
|
|
524
|
+
checked: !1,
|
|
525
|
+
depth: 0
|
|
526
|
+
} : w({
|
|
527
|
+
text: t.text || "",
|
|
528
|
+
style: t.style || e,
|
|
529
|
+
checked: !!t.checked,
|
|
530
|
+
depth: (n = t.depth) != null ? n : 0
|
|
531
|
+
}, t.start !== void 0 && t.start !== 1 ? { start: t.start } : {});
|
|
532
|
+
}
|
|
533
|
+
get currentStyleConfig() {
|
|
534
|
+
return u.STYLE_CONFIGS.find((t) => t.style === this._data.style) || u.STYLE_CONFIGS[0];
|
|
535
|
+
}
|
|
536
|
+
get availableStyles() {
|
|
537
|
+
const t = this._settings.styles;
|
|
538
|
+
return !t || t.length === 0 ? u.STYLE_CONFIGS : u.STYLE_CONFIGS.filter((e) => t.includes(e.style));
|
|
539
|
+
}
|
|
540
|
+
get itemColor() {
|
|
541
|
+
return this._settings.itemColor;
|
|
542
|
+
}
|
|
543
|
+
get itemSize() {
|
|
544
|
+
return this._settings.itemSize;
|
|
545
|
+
}
|
|
546
|
+
get placeholder() {
|
|
547
|
+
return this.api.i18n.t(u.DEFAULT_PLACEHOLDER);
|
|
548
|
+
}
|
|
549
|
+
applyItemStyles(t) {
|
|
550
|
+
const e = t.style;
|
|
551
|
+
this.itemColor && (e.color = this.itemColor), this.itemSize && (e.fontSize = this.itemSize);
|
|
552
|
+
}
|
|
553
|
+
setupItemPlaceholder(t) {
|
|
554
|
+
this.readOnly || z(t, this.placeholder);
|
|
555
|
+
}
|
|
556
|
+
render() {
|
|
557
|
+
return this._element = this.createItemElement(), this._element;
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Called after block content is added to the page.
|
|
561
|
+
* Updates the marker with the correct index now that we know our position,
|
|
562
|
+
* and also updates all sibling list items since their indices may have changed.
|
|
563
|
+
*/
|
|
564
|
+
rendered() {
|
|
565
|
+
this.updateMarkersAfterPositionChange();
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* Called after block was moved.
|
|
569
|
+
* Validates and adjusts depth to follow list formation rules,
|
|
570
|
+
* then updates the marker to reflect the new position.
|
|
571
|
+
*/
|
|
572
|
+
moved(t) {
|
|
573
|
+
this.validateAndAdjustDepthAfterMove(t.detail.toIndex), this.updateMarkersAfterPositionChange();
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* Updates this block's marker and all sibling ordered list markers.
|
|
577
|
+
* Called after this block's position may have changed (rendered, moved).
|
|
578
|
+
*/
|
|
579
|
+
updateMarkersAfterPositionChange() {
|
|
580
|
+
this._data.style !== "ordered" || !this._element || (this.updateMarker(), this.updateSiblingListMarkers());
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Validates and adjusts the depth of this list item after a drag-and-drop move.
|
|
584
|
+
* Ensures the depth follows list formation rules:
|
|
585
|
+
* 1. First item (index 0) must be at depth 0
|
|
586
|
+
* 2. Item depth cannot exceed previousItem.depth + 1
|
|
587
|
+
* 3. When dropped between nested items, adopt the sibling's depth
|
|
588
|
+
*
|
|
589
|
+
* @param newIndex - The new index where the block was moved to
|
|
590
|
+
*/
|
|
591
|
+
validateAndAdjustDepthAfterMove(t) {
|
|
592
|
+
const e = this.getDepth(), n = this.calculateMaxAllowedDepth(t), r = this.calculateTargetDepthForPosition(t, n);
|
|
593
|
+
e !== r && this.adjustDepthTo(r);
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Calculates the target depth for a list item dropped at the given index.
|
|
597
|
+
* When dropping into a nested context, the item should match the sibling's depth.
|
|
598
|
+
*
|
|
599
|
+
* @param blockIndex - The index where the block was dropped
|
|
600
|
+
* @param maxAllowedDepth - The maximum allowed depth at this position
|
|
601
|
+
* @returns The target depth for the dropped item
|
|
602
|
+
*/
|
|
603
|
+
calculateTargetDepthForPosition(t, e) {
|
|
604
|
+
const n = this.getDepth();
|
|
605
|
+
if (n > e)
|
|
606
|
+
return e;
|
|
607
|
+
const r = this.api.blocks.getBlockByIndex(t + 1), s = r && r.name === u.TOOL_NAME, o = s ? this.getBlockDepth(r) : 0;
|
|
608
|
+
if (s && o > n && o <= e)
|
|
609
|
+
return o;
|
|
610
|
+
const l = t > 0 ? this.api.blocks.getBlockByIndex(t - 1) : null, d = l && l.name === u.TOOL_NAME, c = d ? this.getBlockDepth(l) : 0;
|
|
611
|
+
return d && !s && c > n && c <= e ? c : n;
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Calculates the maximum allowed depth for a list item at the given index.
|
|
615
|
+
*
|
|
616
|
+
* Rules:
|
|
617
|
+
* 1. First item (index 0) must be at depth 0
|
|
618
|
+
* 2. For other items: maxDepth = previousListItem.depth + 1
|
|
619
|
+
* 3. If previous block is not a list item, maxDepth = 0
|
|
620
|
+
*
|
|
621
|
+
* @param blockIndex - The index of the block
|
|
622
|
+
* @returns The maximum allowed depth (0 or more)
|
|
623
|
+
*/
|
|
624
|
+
calculateMaxAllowedDepth(t) {
|
|
625
|
+
if (t === 0)
|
|
626
|
+
return 0;
|
|
627
|
+
const e = this.api.blocks.getBlockByIndex(t - 1);
|
|
628
|
+
return !e || e.name !== u.TOOL_NAME ? 0 : this.getBlockDepth(e) + 1;
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* Adjusts the depth of this list item to the specified value.
|
|
632
|
+
* Updates internal data and the DOM element's indentation.
|
|
633
|
+
*
|
|
634
|
+
* @param newDepth - The new depth value
|
|
635
|
+
*/
|
|
636
|
+
adjustDepthTo(t) {
|
|
637
|
+
var n;
|
|
638
|
+
this._data.depth = t, this._element && this._element.setAttribute("data-list-depth", String(t));
|
|
639
|
+
const e = (n = this._element) == null ? void 0 : n.querySelector('[role="listitem"]');
|
|
640
|
+
e instanceof HTMLElement && (e.style.marginLeft = t > 0 ? `${t * u.INDENT_PER_LEVEL}px` : "");
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Called when this block is about to be removed.
|
|
644
|
+
* Updates sibling ordered list markers to renumber correctly after removal.
|
|
645
|
+
*/
|
|
646
|
+
removed() {
|
|
647
|
+
this._data.style === "ordered" && (this.api.events.off("block changed", this.handleBlockChanged), requestAnimationFrame(() => {
|
|
648
|
+
this.updateAllOrderedListMarkers();
|
|
649
|
+
}));
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Update markers on all ordered list items in the editor.
|
|
653
|
+
* Called when a list item is removed to ensure correct renumbering.
|
|
654
|
+
*/
|
|
655
|
+
updateAllOrderedListMarkers() {
|
|
656
|
+
const t = this.api.blocks.getBlocksCount();
|
|
657
|
+
Array.from({ length: t }, (e, n) => n).forEach((e) => {
|
|
658
|
+
const n = this.api.blocks.getBlockByIndex(e);
|
|
659
|
+
if (!n || n.name !== u.TOOL_NAME)
|
|
660
|
+
return;
|
|
661
|
+
const r = n.holder;
|
|
662
|
+
r != null && r.querySelector('[data-list-style="ordered"]') && this.updateBlockMarker(n);
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* Update marker if this is an ordered list item.
|
|
667
|
+
*/
|
|
668
|
+
updateMarkerIfOrdered() {
|
|
669
|
+
this._data.style !== "ordered" || !this._element || this.updateMarker();
|
|
670
|
+
}
|
|
671
|
+
/**
|
|
672
|
+
* Update the marker element with the correct index.
|
|
673
|
+
* Called after the block is rendered and positioned.
|
|
674
|
+
*/
|
|
675
|
+
updateMarker() {
|
|
676
|
+
var s;
|
|
677
|
+
const t = (s = this._element) == null ? void 0 : s.querySelector("[data-list-marker]");
|
|
678
|
+
if (!t)
|
|
679
|
+
return;
|
|
680
|
+
const e = this.getDepth(), n = this.getSiblingIndex(), r = this.getOrderedMarkerText(n, e);
|
|
681
|
+
t.textContent = r;
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* Update markers on all sibling ordered list items.
|
|
685
|
+
* Called when this block is moved to ensure all list numbers are correct.
|
|
686
|
+
* Respects style boundaries - only updates items with the same style.
|
|
687
|
+
*/
|
|
688
|
+
updateSiblingListMarkers() {
|
|
689
|
+
var o;
|
|
690
|
+
const t = this.blockId ? (o = this.api.blocks.getBlockIndex(this.blockId)) != null ? o : this.api.blocks.getCurrentBlockIndex() : this.api.blocks.getCurrentBlockIndex(), e = this.getDepth(), n = this._data.style, r = this.api.blocks.getBlocksCount(), s = this.findListGroupStartIndex(t, e, n);
|
|
691
|
+
this.updateMarkersInRange(s, r, t, e, n);
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* Find the starting index of a list group by walking backwards.
|
|
695
|
+
* Stops at style boundaries at the same depth (when encountering a different list style).
|
|
696
|
+
* Items at deeper depths are skipped regardless of their style.
|
|
697
|
+
*/
|
|
698
|
+
findListGroupStartIndex(t, e, n) {
|
|
699
|
+
const r = (s, o) => {
|
|
700
|
+
if (s < 0)
|
|
701
|
+
return o;
|
|
702
|
+
const a = this.api.blocks.getBlockByIndex(s);
|
|
703
|
+
if (!a || a.name !== u.TOOL_NAME)
|
|
704
|
+
return o;
|
|
705
|
+
const l = this.getBlockDepth(a);
|
|
706
|
+
if (l < e)
|
|
707
|
+
return o;
|
|
708
|
+
if (l > e)
|
|
709
|
+
return r(s - 1, o);
|
|
710
|
+
const d = this.getBlockStyle(a);
|
|
711
|
+
return n !== void 0 && d !== n ? o : r(s - 1, s);
|
|
712
|
+
};
|
|
713
|
+
return r(t - 1, t);
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* Update markers for all list items in a range at the given depth.
|
|
717
|
+
* Stops at style boundaries at the same depth (when encountering a different list style).
|
|
718
|
+
* Items at deeper depths are skipped regardless of their style.
|
|
719
|
+
*/
|
|
720
|
+
updateMarkersInRange(t, e, n, r, s) {
|
|
721
|
+
const o = (a) => {
|
|
722
|
+
if (a >= e)
|
|
723
|
+
return;
|
|
724
|
+
if (a === n) {
|
|
725
|
+
o(a + 1);
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
const l = this.api.blocks.getBlockByIndex(a);
|
|
729
|
+
if (!l || l.name !== u.TOOL_NAME)
|
|
730
|
+
return;
|
|
731
|
+
const d = this.getBlockDepth(l);
|
|
732
|
+
if (d < r)
|
|
733
|
+
return;
|
|
734
|
+
if (d > r) {
|
|
735
|
+
o(a + 1);
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
const c = this.getBlockStyle(l);
|
|
739
|
+
s !== void 0 && c !== s || (this.updateBlockMarker(l), o(a + 1));
|
|
740
|
+
};
|
|
741
|
+
o(t);
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* Get the depth of a block by reading from its DOM
|
|
745
|
+
*/
|
|
746
|
+
getBlockDepth(t) {
|
|
747
|
+
if (!t)
|
|
748
|
+
return 0;
|
|
749
|
+
const e = t.holder, n = e == null ? void 0 : e.querySelector('[role="listitem"]'), r = n == null ? void 0 : n.getAttribute("style"), s = r == null ? void 0 : r.match(/margin-left:\s*(\d+)px/);
|
|
750
|
+
return s ? Math.round(parseInt(s[1], 10) / u.INDENT_PER_LEVEL) : 0;
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* Get the style of a block by reading from its DOM
|
|
754
|
+
*/
|
|
755
|
+
getBlockStyle(t) {
|
|
756
|
+
if (!t)
|
|
757
|
+
return null;
|
|
758
|
+
const e = t.holder, n = e == null ? void 0 : e.querySelector("[data-list-style]");
|
|
759
|
+
return (n == null ? void 0 : n.getAttribute("data-list-style")) || null;
|
|
760
|
+
}
|
|
761
|
+
/**
|
|
762
|
+
* Update the marker of a specific block by finding its marker element and recalculating
|
|
763
|
+
*/
|
|
764
|
+
updateBlockMarker(t) {
|
|
765
|
+
if (!t)
|
|
766
|
+
return;
|
|
767
|
+
const e = t.holder, n = e == null ? void 0 : e.querySelector('[data-list-style="ordered"]');
|
|
768
|
+
if (!n)
|
|
769
|
+
return;
|
|
770
|
+
const r = n.querySelector("[data-list-marker]");
|
|
771
|
+
if (!r)
|
|
772
|
+
return;
|
|
773
|
+
const s = this.api.blocks.getBlockIndex(t.id);
|
|
774
|
+
if (s == null)
|
|
775
|
+
return;
|
|
776
|
+
const o = this.getBlockDepth(t), a = this.getBlockStyle(t) || "ordered", l = this.countPrecedingSiblingsAtDepth(s, o, a), c = this.getListStartValueForBlock(s, o, l, a) + l, h = this.formatOrderedMarker(c, o);
|
|
777
|
+
r.textContent = h;
|
|
778
|
+
}
|
|
779
|
+
/**
|
|
780
|
+
* Format an ordered list marker based on the number and depth
|
|
781
|
+
*/
|
|
782
|
+
formatOrderedMarker(t, e) {
|
|
783
|
+
const n = e % 3;
|
|
784
|
+
return n === 1 ? `${this.numberToLowerAlpha(t)}.` : n === 2 ? `${this.numberToLowerRoman(t)}.` : `${t}.`;
|
|
785
|
+
}
|
|
786
|
+
/**
|
|
787
|
+
* Count preceding list items at the same depth and style for a given block index
|
|
788
|
+
*/
|
|
789
|
+
countPrecedingSiblingsAtDepth(t, e, n) {
|
|
790
|
+
return t <= 0 ? 0 : this.countPrecedingListItemsAtDepthFromIndex(t - 1, e, n);
|
|
791
|
+
}
|
|
792
|
+
/**
|
|
793
|
+
* Recursively count preceding list items at the given depth and style starting from index.
|
|
794
|
+
* Stops at style boundaries at the same depth (when encountering a different list style).
|
|
795
|
+
* Items at deeper depths are skipped regardless of their style.
|
|
796
|
+
*/
|
|
797
|
+
countPrecedingListItemsAtDepthFromIndex(t, e, n) {
|
|
798
|
+
if (t < 0)
|
|
799
|
+
return 0;
|
|
800
|
+
const r = this.api.blocks.getBlockByIndex(t);
|
|
801
|
+
if (!r || r.name !== u.TOOL_NAME)
|
|
802
|
+
return 0;
|
|
803
|
+
const s = this.getBlockDepth(r);
|
|
804
|
+
if (s < e)
|
|
805
|
+
return 0;
|
|
806
|
+
if (s > e)
|
|
807
|
+
return this.countPrecedingListItemsAtDepthFromIndex(t - 1, e, n);
|
|
808
|
+
const o = this.getBlockStyle(r);
|
|
809
|
+
return n !== void 0 && o !== n ? 0 : 1 + this.countPrecedingListItemsAtDepthFromIndex(t - 1, e, n);
|
|
810
|
+
}
|
|
811
|
+
/**
|
|
812
|
+
* Get the list start value for a block at a given index and depth
|
|
813
|
+
*/
|
|
814
|
+
getListStartValueForBlock(t, e, n, r) {
|
|
815
|
+
if (n === 0)
|
|
816
|
+
return this.getBlockStartValue(t);
|
|
817
|
+
const s = this.findFirstListItemIndexFromBlock(t - 1, e, n, r);
|
|
818
|
+
return s === null ? 1 : this.getBlockStartValue(s);
|
|
819
|
+
}
|
|
820
|
+
/**
|
|
821
|
+
* Get the start value from a block's data-list-start attribute
|
|
822
|
+
*/
|
|
823
|
+
getBlockStartValue(t) {
|
|
824
|
+
const e = this.api.blocks.getBlockByIndex(t);
|
|
825
|
+
if (!e)
|
|
826
|
+
return 1;
|
|
827
|
+
const n = e.holder, r = n == null ? void 0 : n.querySelector("[data-list-style]"), s = r == null ? void 0 : r.getAttribute("data-list-start");
|
|
828
|
+
return s ? parseInt(s, 10) : 1;
|
|
829
|
+
}
|
|
830
|
+
/**
|
|
831
|
+
* Find the first list item in a consecutive group.
|
|
832
|
+
* Stops at style boundaries at the same depth (when encountering a different list style).
|
|
833
|
+
* Items at deeper depths are skipped regardless of their style.
|
|
834
|
+
*/
|
|
835
|
+
findFirstListItemIndexFromBlock(t, e, n, r) {
|
|
836
|
+
if (t < 0 || n <= 0)
|
|
837
|
+
return t + 1;
|
|
838
|
+
const s = this.api.blocks.getBlockByIndex(t);
|
|
839
|
+
if (!s || s.name !== u.TOOL_NAME)
|
|
840
|
+
return t + 1;
|
|
841
|
+
const o = this.getBlockDepth(s);
|
|
842
|
+
if (o < e)
|
|
843
|
+
return t + 1;
|
|
844
|
+
if (o > e)
|
|
845
|
+
return this.findFirstListItemIndexFromBlock(t - 1, e, n, r);
|
|
846
|
+
const a = this.getBlockStyle(s);
|
|
847
|
+
return r !== void 0 && a !== r ? t + 1 : this.findFirstListItemIndexFromBlock(t - 1, e, n - 1, r);
|
|
848
|
+
}
|
|
849
|
+
createItemElement() {
|
|
850
|
+
const { style: t } = this._data, e = document.createElement("div");
|
|
851
|
+
e.className = u.BASE_STYLES, e.setAttribute(g.tool, u.TOOL_NAME), e.setAttribute("data-list-style", t), e.setAttribute("data-list-depth", String(this.getDepth())), this._data.start !== void 0 && this._data.start !== 1 && e.setAttribute("data-list-start", String(this._data.start));
|
|
852
|
+
const n = t === "checklist" ? this.createChecklistContent() : this.createStandardContent();
|
|
853
|
+
return e.appendChild(n), this.readOnly || e.addEventListener("keydown", this.handleKeyDown.bind(this)), e;
|
|
854
|
+
}
|
|
855
|
+
createStandardContent() {
|
|
856
|
+
const t = document.createElement("div");
|
|
857
|
+
t.setAttribute("role", "listitem"), t.className = L(u.ITEM_STYLES, "flex", ...W), this.applyItemStyles(t);
|
|
858
|
+
const e = this.getDepth();
|
|
859
|
+
e > 0 && (t.style.marginLeft = `${e * u.INDENT_PER_LEVEL}px`);
|
|
860
|
+
const n = this.createListMarker();
|
|
861
|
+
n.setAttribute("data-list-marker", "true"), t.appendChild(n);
|
|
862
|
+
const r = document.createElement("div");
|
|
863
|
+
return r.className = L("flex-1 min-w-0 outline-none", ...W), r.contentEditable = this.readOnly ? "false" : "true", r.innerHTML = this._data.text, this.setupItemPlaceholder(r), t.appendChild(r), t;
|
|
864
|
+
}
|
|
865
|
+
createChecklistContent() {
|
|
866
|
+
const t = document.createElement("div");
|
|
867
|
+
t.setAttribute("role", "listitem"), t.className = u.CHECKLIST_ITEM_STYLES, this.applyItemStyles(t);
|
|
868
|
+
const e = this.getDepth();
|
|
869
|
+
e > 0 && (t.style.marginLeft = `${e * u.INDENT_PER_LEVEL}px`);
|
|
870
|
+
const n = document.createElement("input");
|
|
871
|
+
n.type = "checkbox", n.className = u.CHECKBOX_STYLES, n.checked = !!this._data.checked, n.disabled = this.readOnly;
|
|
872
|
+
const r = document.createElement("div");
|
|
873
|
+
return r.className = L(
|
|
874
|
+
"flex-1 outline-none leading-[1.6em]",
|
|
875
|
+
this._data.checked ? "line-through opacity-60" : "",
|
|
876
|
+
...W
|
|
877
|
+
), r.contentEditable = this.readOnly ? "false" : "true", r.innerHTML = this._data.text, this.setupItemPlaceholder(r), this.readOnly || n.addEventListener("change", () => {
|
|
878
|
+
this._data.checked = n.checked, r.classList.toggle("line-through", n.checked), r.classList.toggle("opacity-60", n.checked);
|
|
879
|
+
}), t.appendChild(n), t.appendChild(r), t;
|
|
880
|
+
}
|
|
881
|
+
/**
|
|
882
|
+
* Create the marker element (bullet or number) for a list item
|
|
883
|
+
*/
|
|
884
|
+
createListMarker() {
|
|
885
|
+
const t = document.createElement("span");
|
|
886
|
+
t.className = "flex-shrink-0 select-none", t.setAttribute("aria-hidden", "true"), t.contentEditable = "false";
|
|
887
|
+
const e = this.getDepth();
|
|
888
|
+
if (this._data.style === "ordered") {
|
|
889
|
+
const n = this.getSiblingIndex(), r = this.getOrderedMarkerText(n, e);
|
|
890
|
+
t.textContent = r, t.className = L(t.className, "text-right"), t.style.paddingRight = "11px", t.style.minWidth = "fit-content";
|
|
891
|
+
} else {
|
|
892
|
+
const n = this.getBulletCharacter(e);
|
|
893
|
+
t.textContent = n, t.className = L(t.className, "w-6 text-center flex justify-center"), t.style.paddingLeft = "1px", t.style.paddingRight = "13px", t.style.fontSize = "24px", t.style.fontFamily = "Arial";
|
|
894
|
+
}
|
|
895
|
+
return t;
|
|
896
|
+
}
|
|
897
|
+
/**
|
|
898
|
+
* Calculate the index of this ListItem among consecutive siblings with the same style.
|
|
899
|
+
* This is used to determine the correct number for ordered lists.
|
|
900
|
+
*/
|
|
901
|
+
getSiblingIndex() {
|
|
902
|
+
var n;
|
|
903
|
+
const t = this.blockId ? (n = this.api.blocks.getBlockIndex(this.blockId)) != null ? n : this.api.blocks.getCurrentBlockIndex() : this.api.blocks.getCurrentBlockIndex();
|
|
904
|
+
if (t <= 0)
|
|
905
|
+
return 0;
|
|
906
|
+
const e = this.getDepth();
|
|
907
|
+
return this.countPrecedingListItemsAtDepth(t - 1, e);
|
|
908
|
+
}
|
|
909
|
+
/**
|
|
910
|
+
* Recursively count consecutive preceding list blocks at the same depth and style.
|
|
911
|
+
* Stops when encountering a block that's not a list, a list at a shallower depth (parent),
|
|
912
|
+
* or a list with a different style at the same depth (treating style changes as list boundaries).
|
|
913
|
+
* Items at deeper depths are skipped regardless of their style.
|
|
914
|
+
*/
|
|
915
|
+
countPrecedingListItemsAtDepth(t, e) {
|
|
916
|
+
var c;
|
|
917
|
+
if (t < 0)
|
|
918
|
+
return 0;
|
|
919
|
+
const n = this.api.blocks.getBlockByIndex(t);
|
|
920
|
+
if (!n || n.name !== u.TOOL_NAME)
|
|
921
|
+
return 0;
|
|
922
|
+
const r = n.holder, s = r == null ? void 0 : r.querySelector("[data-list-style]"), o = (c = s == null ? void 0 : s.querySelector('[role="listitem"]')) == null ? void 0 : c.getAttribute("style"), a = o == null ? void 0 : o.match(/margin-left:\s*(\d+)px/), l = a ? Math.round(parseInt(a[1], 10) / u.INDENT_PER_LEVEL) : 0;
|
|
923
|
+
return l < e ? 0 : l > e ? this.countPrecedingListItemsAtDepth(t - 1, e) : (s == null ? void 0 : s.getAttribute("data-list-style")) !== this._data.style ? 0 : 1 + this.countPrecedingListItemsAtDepth(t - 1, e);
|
|
924
|
+
}
|
|
925
|
+
/**
|
|
926
|
+
* Get the depth of this item in the hierarchy (0 = root level)
|
|
927
|
+
*/
|
|
928
|
+
getDepth() {
|
|
929
|
+
var t;
|
|
930
|
+
return (t = this._data.depth) != null ? t : 0;
|
|
931
|
+
}
|
|
932
|
+
/**
|
|
933
|
+
* Get the appropriate bullet character based on nesting depth
|
|
934
|
+
*/
|
|
935
|
+
getBulletCharacter(t) {
|
|
936
|
+
const e = ["•", "◦", "▪"];
|
|
937
|
+
return e[t % e.length];
|
|
938
|
+
}
|
|
939
|
+
/**
|
|
940
|
+
* Get the ordered list marker text based on depth and index
|
|
941
|
+
*/
|
|
942
|
+
getOrderedMarkerText(t, e) {
|
|
943
|
+
const r = this.getListStartValue(t, e) + t;
|
|
944
|
+
switch (e % 3) {
|
|
945
|
+
case 0:
|
|
946
|
+
return `${r}.`;
|
|
947
|
+
case 1:
|
|
948
|
+
return `${this.numberToLowerAlpha(r)}.`;
|
|
949
|
+
case 2:
|
|
950
|
+
return `${this.numberToLowerRoman(r)}.`;
|
|
951
|
+
default:
|
|
952
|
+
return `${r}.`;
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
/**
|
|
956
|
+
* Get the starting number for this list group.
|
|
957
|
+
* Looks up the first item in the consecutive list group to find its start value.
|
|
958
|
+
*/
|
|
959
|
+
getListStartValue(t, e) {
|
|
960
|
+
var d, c;
|
|
961
|
+
if (t === 0)
|
|
962
|
+
return (d = this._data.start) != null ? d : 1;
|
|
963
|
+
const n = this.blockId ? (c = this.api.blocks.getBlockIndex(this.blockId)) != null ? c : this.api.blocks.getCurrentBlockIndex() : this.api.blocks.getCurrentBlockIndex(), r = this.findFirstListItemIndex(n - 1, e, t);
|
|
964
|
+
if (r === null)
|
|
965
|
+
return 1;
|
|
966
|
+
const s = this.api.blocks.getBlockByIndex(r);
|
|
967
|
+
if (!s)
|
|
968
|
+
return 1;
|
|
969
|
+
const o = s.holder, a = o == null ? void 0 : o.querySelector("[data-list-style]"), l = a == null ? void 0 : a.getAttribute("data-list-start");
|
|
970
|
+
return l ? parseInt(l, 10) : 1;
|
|
971
|
+
}
|
|
972
|
+
/**
|
|
973
|
+
* Find the index of the first list item in this consecutive group.
|
|
974
|
+
* Walks backwards through the blocks counting items at the same depth and style.
|
|
975
|
+
* Stops at style boundaries at the same depth (when encountering a different list style).
|
|
976
|
+
* Items at deeper depths are skipped regardless of their style.
|
|
977
|
+
*/
|
|
978
|
+
findFirstListItemIndex(t, e, n) {
|
|
979
|
+
var h;
|
|
980
|
+
if (t < 0 || n <= 0)
|
|
981
|
+
return t + 1;
|
|
982
|
+
const r = this.api.blocks.getBlockByIndex(t);
|
|
983
|
+
if (!r || r.name !== u.TOOL_NAME)
|
|
984
|
+
return t + 1;
|
|
985
|
+
const s = r.holder, o = s == null ? void 0 : s.querySelector("[data-list-style]"), a = (h = o == null ? void 0 : o.querySelector('[role="listitem"]')) == null ? void 0 : h.getAttribute("style"), l = a == null ? void 0 : a.match(/margin-left:\s*(\d+)px/), d = l ? Math.round(parseInt(l[1], 10) / u.INDENT_PER_LEVEL) : 0;
|
|
986
|
+
return d < e ? t + 1 : d > e ? this.findFirstListItemIndex(t - 1, e, n) : (o == null ? void 0 : o.getAttribute("data-list-style")) !== this._data.style ? t + 1 : this.findFirstListItemIndex(t - 1, e, n - 1);
|
|
987
|
+
}
|
|
988
|
+
numberToLowerAlpha(t) {
|
|
989
|
+
const e = (n) => {
|
|
990
|
+
if (n <= 0) return "";
|
|
991
|
+
const r = n - 1;
|
|
992
|
+
return e(Math.floor(r / 26)) + String.fromCharCode(97 + r % 26);
|
|
993
|
+
};
|
|
994
|
+
return e(t);
|
|
995
|
+
}
|
|
996
|
+
numberToLowerRoman(t) {
|
|
997
|
+
const e = [
|
|
998
|
+
[1e3, "m"],
|
|
999
|
+
[900, "cm"],
|
|
1000
|
+
[500, "d"],
|
|
1001
|
+
[400, "cd"],
|
|
1002
|
+
[100, "c"],
|
|
1003
|
+
[90, "xc"],
|
|
1004
|
+
[50, "l"],
|
|
1005
|
+
[40, "xl"],
|
|
1006
|
+
[10, "x"],
|
|
1007
|
+
[9, "ix"],
|
|
1008
|
+
[5, "v"],
|
|
1009
|
+
[4, "iv"],
|
|
1010
|
+
[1, "i"]
|
|
1011
|
+
], n = (r, s) => {
|
|
1012
|
+
if (r <= 0 || s >= e.length) return "";
|
|
1013
|
+
const [o, a] = e[s];
|
|
1014
|
+
return r >= o ? a + n(r - o, s) : n(r, s + 1);
|
|
1015
|
+
};
|
|
1016
|
+
return n(t, 0);
|
|
1017
|
+
}
|
|
1018
|
+
handleKeyDown(t) {
|
|
1019
|
+
if (t.key === "Enter" && !t.shiftKey) {
|
|
1020
|
+
t.preventDefault(), this.handleEnter();
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
1023
|
+
if (t.key === "Backspace") {
|
|
1024
|
+
this.handleBackspace(t);
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
if (t.key === "Tab" && t.shiftKey) {
|
|
1028
|
+
t.preventDefault(), this.handleOutdent();
|
|
1029
|
+
return;
|
|
1030
|
+
}
|
|
1031
|
+
t.key === "Tab" && (t.preventDefault(), this.handleIndent());
|
|
1032
|
+
}
|
|
1033
|
+
async handleEnter() {
|
|
1034
|
+
const t = window.getSelection();
|
|
1035
|
+
if (!t || !this._element) return;
|
|
1036
|
+
const e = this.getContentElement();
|
|
1037
|
+
if (!e) return;
|
|
1038
|
+
const n = e.innerHTML.trim();
|
|
1039
|
+
if (n === "" || n === "<br>") {
|
|
1040
|
+
await this.exitListOrOutdent();
|
|
1041
|
+
return;
|
|
1042
|
+
}
|
|
1043
|
+
const r = t.getRangeAt(0), { beforeContent: s, afterContent: o } = this.splitContentAtCursor(e, r);
|
|
1044
|
+
e.innerHTML = s, this._data.text = s;
|
|
1045
|
+
const a = this.api.blocks.getCurrentBlockIndex(), l = this.api.blocks.insert(u.TOOL_NAME, {
|
|
1046
|
+
text: o,
|
|
1047
|
+
style: this._data.style,
|
|
1048
|
+
checked: !1,
|
|
1049
|
+
depth: this._data.depth
|
|
1050
|
+
}, void 0, a + 1, !0);
|
|
1051
|
+
this.setCaretToBlockContent(l, "start");
|
|
1052
|
+
}
|
|
1053
|
+
async exitListOrOutdent() {
|
|
1054
|
+
const t = this.api.blocks.getCurrentBlockIndex();
|
|
1055
|
+
if (this.getDepth() > 0) {
|
|
1056
|
+
await this.handleOutdent();
|
|
1057
|
+
return;
|
|
1058
|
+
}
|
|
1059
|
+
await this.api.blocks.delete(t);
|
|
1060
|
+
const n = this.api.blocks.insert("paragraph", { text: "" }, void 0, t, !0);
|
|
1061
|
+
this.setCaretToBlockContent(n, "start");
|
|
1062
|
+
}
|
|
1063
|
+
async handleBackspace(t) {
|
|
1064
|
+
const e = window.getSelection();
|
|
1065
|
+
if (!e || !this._element) return;
|
|
1066
|
+
const n = e.getRangeAt(0), r = this.getContentElement();
|
|
1067
|
+
if (!r) return;
|
|
1068
|
+
this.syncContentFromDOM();
|
|
1069
|
+
const s = this.api.blocks.getCurrentBlockIndex(), o = this._data.text, a = this.getDepth();
|
|
1070
|
+
if (this.isEntireContentSelected(r, n) && !e.isCollapsed) {
|
|
1071
|
+
t.preventDefault(), r.innerHTML = "", this._data.text = "";
|
|
1072
|
+
const c = document.createRange();
|
|
1073
|
+
c.setStart(r, 0), c.collapse(!0), e.removeAllRanges(), e.addRange(c);
|
|
1074
|
+
return;
|
|
1075
|
+
}
|
|
1076
|
+
if (!this.isAtStart(r, n)) return;
|
|
1077
|
+
t.preventDefault(), await this.api.blocks.delete(s);
|
|
1078
|
+
const d = this.api.blocks.insert(
|
|
1079
|
+
"paragraph",
|
|
1080
|
+
{ text: o },
|
|
1081
|
+
void 0,
|
|
1082
|
+
s,
|
|
1083
|
+
!0
|
|
1084
|
+
);
|
|
1085
|
+
a > 0 && requestAnimationFrame(() => {
|
|
1086
|
+
const c = d.holder;
|
|
1087
|
+
c && (c.style.marginLeft = `${a * u.INDENT_PER_LEVEL}px`, c.setAttribute("data-blok-depth", String(a)));
|
|
1088
|
+
}), this.setCaretToBlockContent(d, "start");
|
|
1089
|
+
}
|
|
1090
|
+
/**
|
|
1091
|
+
* Collect all text nodes from an element
|
|
1092
|
+
* @param node - Node to collect text nodes from
|
|
1093
|
+
* @returns Array of text nodes
|
|
1094
|
+
*/
|
|
1095
|
+
collectTextNodes(t) {
|
|
1096
|
+
var e;
|
|
1097
|
+
return t.nodeType === Node.TEXT_NODE ? [t] : (e = t.hasChildNodes) != null && e.call(t) ? Array.from(t.childNodes).flatMap((n) => this.collectTextNodes(n)) : [];
|
|
1098
|
+
}
|
|
1099
|
+
/**
|
|
1100
|
+
* Find the text node and offset for a given character position
|
|
1101
|
+
* @param textNodes - Array of text nodes to search through
|
|
1102
|
+
* @param targetPosition - Character position to find
|
|
1103
|
+
* @returns Object with node and offset, or null if not found
|
|
1104
|
+
*/
|
|
1105
|
+
findCaretPosition(t, e) {
|
|
1106
|
+
const n = t.reduce(
|
|
1107
|
+
(r, s) => {
|
|
1108
|
+
var a, l;
|
|
1109
|
+
if (r.found) return r;
|
|
1110
|
+
const o = (l = (a = s.textContent) == null ? void 0 : a.length) != null ? l : 0;
|
|
1111
|
+
return r.charCount + o >= e ? {
|
|
1112
|
+
found: !0,
|
|
1113
|
+
charCount: r.charCount,
|
|
1114
|
+
node: s,
|
|
1115
|
+
offset: e - r.charCount
|
|
1116
|
+
} : P(w({}, r), {
|
|
1117
|
+
charCount: r.charCount + o
|
|
1118
|
+
});
|
|
1119
|
+
},
|
|
1120
|
+
{ found: !1, charCount: 0, node: null, offset: 0 }
|
|
1121
|
+
);
|
|
1122
|
+
return n.node ? { node: n.node, offset: n.offset } : null;
|
|
1123
|
+
}
|
|
1124
|
+
/**
|
|
1125
|
+
* Sync the current DOM content to the data model
|
|
1126
|
+
*/
|
|
1127
|
+
syncContentFromDOM() {
|
|
1128
|
+
var n;
|
|
1129
|
+
const t = this.getContentElement();
|
|
1130
|
+
if (t && (this._data.text = t.innerHTML), this._data.style !== "checklist")
|
|
1131
|
+
return;
|
|
1132
|
+
const e = (n = this._element) == null ? void 0 : n.querySelector('input[type="checkbox"]');
|
|
1133
|
+
e && (this._data.checked = e.checked);
|
|
1134
|
+
}
|
|
1135
|
+
/**
|
|
1136
|
+
* Get the depth of the parent list item by walking backwards through preceding items.
|
|
1137
|
+
* A parent is the first preceding list item with a depth less than the current item.
|
|
1138
|
+
* @param blockIndex - The index of the current block
|
|
1139
|
+
* @returns The parent's depth, or -1 if no parent exists (at root level)
|
|
1140
|
+
*/
|
|
1141
|
+
getParentDepth(t) {
|
|
1142
|
+
const e = this.getDepth(), n = (r) => {
|
|
1143
|
+
if (r < 0)
|
|
1144
|
+
return -1;
|
|
1145
|
+
const s = this.api.blocks.getBlockByIndex(r);
|
|
1146
|
+
if (!s || s.name !== u.TOOL_NAME)
|
|
1147
|
+
return -1;
|
|
1148
|
+
const o = this.getBlockDepth(s);
|
|
1149
|
+
return o < e ? o : n(r - 1);
|
|
1150
|
+
};
|
|
1151
|
+
return n(t - 1);
|
|
1152
|
+
}
|
|
1153
|
+
async handleIndent() {
|
|
1154
|
+
const t = this.api.blocks.getCurrentBlockIndex();
|
|
1155
|
+
if (t === 0) return;
|
|
1156
|
+
const e = this.api.blocks.getBlockByIndex(t - 1);
|
|
1157
|
+
if (!e || e.name !== u.TOOL_NAME) return;
|
|
1158
|
+
const n = this.getDepth(), r = this.getBlockDepth(e);
|
|
1159
|
+
if (n > r) return;
|
|
1160
|
+
this.syncContentFromDOM();
|
|
1161
|
+
const s = n + 1;
|
|
1162
|
+
this._data.depth = s;
|
|
1163
|
+
const o = await this.api.blocks.update(this.blockId || "", P(w({}, this._data), {
|
|
1164
|
+
depth: s
|
|
1165
|
+
}));
|
|
1166
|
+
this.setCaretToBlockContent(o);
|
|
1167
|
+
}
|
|
1168
|
+
async handleOutdent() {
|
|
1169
|
+
const t = this.getDepth();
|
|
1170
|
+
if (t === 0) return;
|
|
1171
|
+
this.syncContentFromDOM();
|
|
1172
|
+
const e = t - 1;
|
|
1173
|
+
this._data.depth = e;
|
|
1174
|
+
const n = await this.api.blocks.update(this.blockId || "", P(w({}, this._data), {
|
|
1175
|
+
depth: e
|
|
1176
|
+
}));
|
|
1177
|
+
this.setCaretToBlockContent(n);
|
|
1178
|
+
}
|
|
1179
|
+
getContentElement() {
|
|
1180
|
+
return this._element ? this._data.style === "checklist" ? this._element.querySelector("[contenteditable]") : this._element.querySelector("div.flex-1") : null;
|
|
1181
|
+
}
|
|
1182
|
+
/**
|
|
1183
|
+
* Sets caret to the content element of a block after ensuring DOM is ready.
|
|
1184
|
+
* Uses requestAnimationFrame to wait for the browser to process DOM updates.
|
|
1185
|
+
* @param block - BlockAPI to set caret to
|
|
1186
|
+
* @param position - 'start' or 'end' position (defaults to 'end')
|
|
1187
|
+
*/
|
|
1188
|
+
setCaretToBlockContent(t, e = "end") {
|
|
1189
|
+
requestAnimationFrame(() => {
|
|
1190
|
+
const n = t.holder;
|
|
1191
|
+
if (!n) return;
|
|
1192
|
+
const r = n.querySelector('[contenteditable="true"]');
|
|
1193
|
+
if (!r) {
|
|
1194
|
+
this.api.caret.setToBlock(t, e);
|
|
1195
|
+
return;
|
|
1196
|
+
}
|
|
1197
|
+
r.focus();
|
|
1198
|
+
const s = window.getSelection();
|
|
1199
|
+
if (!s) return;
|
|
1200
|
+
const o = document.createRange();
|
|
1201
|
+
e === "start" ? (o.setStart(r, 0), o.collapse(!0)) : (o.selectNodeContents(r), o.collapse(!1)), s.removeAllRanges(), s.addRange(o);
|
|
1202
|
+
});
|
|
1203
|
+
}
|
|
1204
|
+
isAtStart(t, e) {
|
|
1205
|
+
const n = document.createRange();
|
|
1206
|
+
return n.selectNodeContents(t), n.setEnd(e.startContainer, e.startOffset), n.toString().length === 0;
|
|
1207
|
+
}
|
|
1208
|
+
/**
|
|
1209
|
+
* Check if the entire content of an element is selected
|
|
1210
|
+
* @param element - The content element to check
|
|
1211
|
+
* @param range - The current selection range
|
|
1212
|
+
* @returns true if the entire content is selected
|
|
1213
|
+
*/
|
|
1214
|
+
isEntireContentSelected(t, e) {
|
|
1215
|
+
const n = document.createRange();
|
|
1216
|
+
n.selectNodeContents(t), n.setEnd(e.startContainer, e.startOffset);
|
|
1217
|
+
const r = n.toString().length === 0, s = document.createRange();
|
|
1218
|
+
s.selectNodeContents(t), s.setStart(e.endContainer, e.endOffset);
|
|
1219
|
+
const o = s.toString().length === 0;
|
|
1220
|
+
return r && o;
|
|
1221
|
+
}
|
|
1222
|
+
splitContentAtCursor(t, e) {
|
|
1223
|
+
const n = document.createRange();
|
|
1224
|
+
n.setStart(t, 0), n.setEnd(e.startContainer, e.startOffset);
|
|
1225
|
+
const r = document.createRange();
|
|
1226
|
+
return r.setStart(e.endContainer, e.endOffset), r.setEndAfter(t.lastChild || t), {
|
|
1227
|
+
beforeContent: this.getFragmentHTML(n.cloneContents()),
|
|
1228
|
+
afterContent: this.getFragmentHTML(r.cloneContents())
|
|
1229
|
+
};
|
|
1230
|
+
}
|
|
1231
|
+
getFragmentHTML(t) {
|
|
1232
|
+
const e = document.createElement("div");
|
|
1233
|
+
return e.appendChild(t), e.innerHTML;
|
|
1234
|
+
}
|
|
1235
|
+
renderSettings() {
|
|
1236
|
+
return this.availableStyles.map((t) => ({
|
|
1237
|
+
icon: t.icon,
|
|
1238
|
+
label: this.api.i18n.t(`toolNames.${t.name}`),
|
|
1239
|
+
onActivate: () => this.setStyle(t.style),
|
|
1240
|
+
closeOnActivate: !0,
|
|
1241
|
+
isActive: this._data.style === t.style
|
|
1242
|
+
}));
|
|
1243
|
+
}
|
|
1244
|
+
setStyle(t) {
|
|
1245
|
+
const e = this._data.style;
|
|
1246
|
+
this._data.style = t, this.rerender(), e !== t && requestAnimationFrame(() => {
|
|
1247
|
+
this.updateAllOrderedListMarkers();
|
|
1248
|
+
});
|
|
1249
|
+
}
|
|
1250
|
+
rerender() {
|
|
1251
|
+
if (!this._element) return;
|
|
1252
|
+
const t = this._element.parentNode;
|
|
1253
|
+
if (!t) return;
|
|
1254
|
+
const e = this.createItemElement();
|
|
1255
|
+
t.replaceChild(e, this._element), this._element = e;
|
|
1256
|
+
}
|
|
1257
|
+
validate(t) {
|
|
1258
|
+
return typeof t.text == "string";
|
|
1259
|
+
}
|
|
1260
|
+
save() {
|
|
1261
|
+
if (!this._element) return this._data;
|
|
1262
|
+
const t = this.getContentElement(), n = {
|
|
1263
|
+
text: t ? et(t.innerHTML) : this._data.text,
|
|
1264
|
+
style: this._data.style,
|
|
1265
|
+
checked: this._data.checked
|
|
1266
|
+
};
|
|
1267
|
+
return this._data.start !== void 0 && this._data.start !== 1 && (n.start = this._data.start), this._data.depth !== void 0 && this._data.depth > 0 && (n.depth = this._data.depth), n;
|
|
1268
|
+
}
|
|
1269
|
+
merge(t) {
|
|
1270
|
+
if (!this._element)
|
|
1271
|
+
return;
|
|
1272
|
+
this._data.text += t.text;
|
|
1273
|
+
const e = this.getContentElement();
|
|
1274
|
+
if (e && t.text) {
|
|
1275
|
+
const n = this.parseHtml(t.text);
|
|
1276
|
+
e.appendChild(n), e.normalize();
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
/**
|
|
1280
|
+
* Parse HTML string into a DocumentFragment
|
|
1281
|
+
* @param html - HTML string to parse
|
|
1282
|
+
* @returns DocumentFragment with parsed nodes
|
|
1283
|
+
*/
|
|
1284
|
+
parseHtml(t) {
|
|
1285
|
+
const e = document.createElement("div");
|
|
1286
|
+
e.innerHTML = t.trim();
|
|
1287
|
+
const n = document.createDocumentFragment();
|
|
1288
|
+
return n.append(...Array.from(e.childNodes)), n;
|
|
1289
|
+
}
|
|
1290
|
+
static get conversionConfig() {
|
|
1291
|
+
return {
|
|
1292
|
+
export: (t) => t.text,
|
|
1293
|
+
import: (t) => ({
|
|
1294
|
+
text: t,
|
|
1295
|
+
style: "unordered",
|
|
1296
|
+
checked: !1
|
|
1297
|
+
})
|
|
1298
|
+
};
|
|
1299
|
+
}
|
|
1300
|
+
static get sanitize() {
|
|
1301
|
+
return {
|
|
1302
|
+
text: {
|
|
1303
|
+
br: !0,
|
|
1304
|
+
a: !0,
|
|
1305
|
+
b: !0,
|
|
1306
|
+
i: !0,
|
|
1307
|
+
mark: !0
|
|
1308
|
+
}
|
|
1309
|
+
};
|
|
1310
|
+
}
|
|
1311
|
+
static get pasteConfig() {
|
|
1312
|
+
return { tags: ["LI"] };
|
|
1313
|
+
}
|
|
1314
|
+
onPaste(t) {
|
|
1315
|
+
const e = t.detail;
|
|
1316
|
+
if (!("data" in e)) return;
|
|
1317
|
+
const n = e.data, r = n.innerHTML || n.textContent || "", s = n.querySelector('input[type="checkbox"]'), o = (s == null ? void 0 : s.checked) || !1;
|
|
1318
|
+
this._data = {
|
|
1319
|
+
text: r,
|
|
1320
|
+
style: this.detectStyleFromPastedContent(n),
|
|
1321
|
+
checked: o
|
|
1322
|
+
}, this.rerender();
|
|
1323
|
+
}
|
|
1324
|
+
/**
|
|
1325
|
+
* Detect list style from pasted content based on parent element
|
|
1326
|
+
*/
|
|
1327
|
+
detectStyleFromPastedContent(t) {
|
|
1328
|
+
const e = t.parentElement;
|
|
1329
|
+
return e ? e.tagName === "OL" ? "ordered" : e.tagName !== "UL" ? this._data.style : t.querySelector('input[type="checkbox"]') ? "checklist" : "unordered" : this._data.style;
|
|
1330
|
+
}
|
|
1331
|
+
static get isReadOnlySupported() {
|
|
1332
|
+
return !0;
|
|
1333
|
+
}
|
|
1334
|
+
/**
|
|
1335
|
+
* Returns the horizontal offset of the content at the hovered element.
|
|
1336
|
+
* Used by the toolbar to position itself closer to nested list items.
|
|
1337
|
+
*
|
|
1338
|
+
* @param hoveredElement - The element that is currently being hovered
|
|
1339
|
+
* @returns Object with left offset in pixels based on the list item's depth
|
|
1340
|
+
*/
|
|
1341
|
+
getContentOffset(t) {
|
|
1342
|
+
var r;
|
|
1343
|
+
const e = (r = t.closest('[role="listitem"]')) != null ? r : t.querySelector('[role="listitem"]'), n = this.getMarginLeftFromElement(e);
|
|
1344
|
+
return n !== void 0 ? n : this.getOffsetFromDepthAttribute(t);
|
|
1345
|
+
}
|
|
1346
|
+
/**
|
|
1347
|
+
* Extracts the margin-left value from an element's inline style
|
|
1348
|
+
* @param element - The element to extract margin-left from
|
|
1349
|
+
* @returns Object with left offset if valid margin-left found, undefined otherwise
|
|
1350
|
+
*/
|
|
1351
|
+
getMarginLeftFromElement(t) {
|
|
1352
|
+
if (!t)
|
|
1353
|
+
return;
|
|
1354
|
+
const n = (t.getAttribute("style") || "").match(/margin-left:\s*(\d+)px/);
|
|
1355
|
+
if (!n)
|
|
1356
|
+
return;
|
|
1357
|
+
const r = parseInt(n[1], 10);
|
|
1358
|
+
return r > 0 ? { left: r } : void 0;
|
|
1359
|
+
}
|
|
1360
|
+
/**
|
|
1361
|
+
* Gets the offset from the data-list-depth attribute
|
|
1362
|
+
* @param hoveredElement - The element to start searching from
|
|
1363
|
+
* @returns Object with left offset based on depth, undefined if depth is 0 or not found
|
|
1364
|
+
*/
|
|
1365
|
+
getOffsetFromDepthAttribute(t) {
|
|
1366
|
+
const e = t.closest("[data-list-depth]");
|
|
1367
|
+
if (!e)
|
|
1368
|
+
return;
|
|
1369
|
+
const n = e.getAttribute("data-list-depth");
|
|
1370
|
+
if (n === null)
|
|
1371
|
+
return;
|
|
1372
|
+
const r = parseInt(n, 10);
|
|
1373
|
+
return r > 0 ? { left: r * u.INDENT_PER_LEVEL } : void 0;
|
|
1374
|
+
}
|
|
1375
|
+
static get toolbox() {
|
|
1376
|
+
return [
|
|
1377
|
+
{
|
|
1378
|
+
icon: G,
|
|
1379
|
+
title: "Bulleted list",
|
|
1380
|
+
titleKey: "bulletedList",
|
|
1381
|
+
data: { style: "unordered" },
|
|
1382
|
+
name: "bulleted-list"
|
|
1383
|
+
},
|
|
1384
|
+
{
|
|
1385
|
+
icon: j,
|
|
1386
|
+
title: "Numbered list",
|
|
1387
|
+
titleKey: "numberedList",
|
|
1388
|
+
data: { style: "ordered" },
|
|
1389
|
+
name: "numbered-list"
|
|
1390
|
+
},
|
|
1391
|
+
{
|
|
1392
|
+
icon: $,
|
|
1393
|
+
title: "To-do list",
|
|
1394
|
+
titleKey: "todoList",
|
|
1395
|
+
data: { style: "checklist" },
|
|
1396
|
+
name: "check-list"
|
|
1397
|
+
}
|
|
1398
|
+
];
|
|
1399
|
+
}
|
|
1400
|
+
};
|
|
1401
|
+
u.BASE_STYLES = "outline-none", u.ITEM_STYLES = "outline-none py-0.5 pl-0.5 leading-[1.6em]", u.CHECKLIST_ITEM_STYLES = "flex items-start py-0.5 pl-0.5", u.CHECKBOX_STYLES = "mt-1 w-4 mr-2 h-4 cursor-pointer accent-current", u.STYLE_CONFIGS = [
|
|
1402
|
+
{ style: "unordered", name: "bulletedList", icon: G },
|
|
1403
|
+
{ style: "ordered", name: "numberedList", icon: j },
|
|
1404
|
+
{ style: "checklist", name: "todoList", icon: $ }
|
|
1405
|
+
], u.pendingMarkerUpdate = !1, u.DEFAULT_PLACEHOLDER = "List", u.INDENT_PER_LEVEL = 24, u.TOOL_NAME = "list";
|
|
1406
|
+
let J = u;
|
|
1407
|
+
const i = class i {
|
|
1408
|
+
/**
|
|
1409
|
+
* Sanitizer Rule
|
|
1410
|
+
* Leave <strong> tags
|
|
1411
|
+
* @returns {object}
|
|
1412
|
+
*/
|
|
1413
|
+
static get sanitize() {
|
|
1414
|
+
return {
|
|
1415
|
+
strong: {},
|
|
1416
|
+
b: {}
|
|
1417
|
+
};
|
|
1418
|
+
}
|
|
1419
|
+
/**
|
|
1420
|
+
* Normalize any remaining legacy <b> tags within the blok wrapper
|
|
1421
|
+
*/
|
|
1422
|
+
static normalizeAllBoldTags() {
|
|
1423
|
+
if (typeof document == "undefined")
|
|
1424
|
+
return;
|
|
1425
|
+
const t = `${A(g.interface)} b, ${A(g.editor)} b`;
|
|
1426
|
+
document.querySelectorAll(t).forEach((e) => {
|
|
1427
|
+
i.ensureStrongElement(e);
|
|
1428
|
+
});
|
|
1429
|
+
}
|
|
1430
|
+
/**
|
|
1431
|
+
* Normalize bold tags within a mutated node if it belongs to the blok
|
|
1432
|
+
* @param node - The node affected by mutation
|
|
1433
|
+
*/
|
|
1434
|
+
static normalizeBoldInNode(t) {
|
|
1435
|
+
var r;
|
|
1436
|
+
const e = t.nodeType === Node.ELEMENT_NODE ? t : t.parentElement;
|
|
1437
|
+
!e || typeof e.closest != "function" || !e.closest(`${A(g.interface)}, ${A(g.editor)}`) || (e.tagName === "B" && i.ensureStrongElement(e), (r = e.querySelectorAll) == null || r.call(e, "b").forEach((s) => {
|
|
1438
|
+
i.ensureStrongElement(s);
|
|
1439
|
+
}));
|
|
1440
|
+
}
|
|
1441
|
+
/**
|
|
1442
|
+
*
|
|
1443
|
+
*/
|
|
1444
|
+
constructor() {
|
|
1445
|
+
typeof document != "undefined" && (i.instances.add(this), i.initializeGlobalListeners());
|
|
1446
|
+
}
|
|
1447
|
+
/**
|
|
1448
|
+
* Ensure global event listeners are registered once per document
|
|
1449
|
+
*/
|
|
1450
|
+
static initializeGlobalListeners() {
|
|
1451
|
+
return typeof document == "undefined" ? !1 : (i.shortcutListenerRegistered || (document.addEventListener("keydown", i.handleShortcut, !0), i.shortcutListenerRegistered = !0), i.selectionListenerRegistered || (document.addEventListener("selectionchange", i.handleGlobalSelectionChange, !0), i.selectionListenerRegistered = !0), i.inputListenerRegistered || (document.addEventListener("input", i.handleGlobalInput, !0), i.inputListenerRegistered = !0), i.beforeInputListenerRegistered || (document.addEventListener("beforeinput", i.handleBeforeInput, !0), i.beforeInputListenerRegistered = !0), i.ensureMutationObserver(), !0);
|
|
1452
|
+
}
|
|
1453
|
+
/**
|
|
1454
|
+
* Ensure that text typed after exiting a collapsed bold selection stays outside of the bold element
|
|
1455
|
+
*/
|
|
1456
|
+
static maintainCollapsedExitState() {
|
|
1457
|
+
var t, e, n, r, s;
|
|
1458
|
+
if (typeof document != "undefined")
|
|
1459
|
+
for (const o of Array.from(i.collapsedExitRecords)) {
|
|
1460
|
+
const a = i.resolveBoundary(o);
|
|
1461
|
+
if (!a) {
|
|
1462
|
+
i.collapsedExitRecords.delete(o);
|
|
1463
|
+
continue;
|
|
1464
|
+
}
|
|
1465
|
+
o.boundary = a.boundary, o.boldElement = a.boldElement;
|
|
1466
|
+
const l = a.boundary, d = a.boldElement, c = o.allowedLength, h = (t = d.textContent) != null ? t : "";
|
|
1467
|
+
if (h.length > c) {
|
|
1468
|
+
const b = h.slice(0, c), S = h.slice(c);
|
|
1469
|
+
d.textContent = b, l.textContent = ((e = l.textContent) != null ? e : "") + S;
|
|
1470
|
+
}
|
|
1471
|
+
const p = (n = l.textContent) != null ? n : "";
|
|
1472
|
+
p.length > 1 && p.startsWith("") && (l.textContent = p.slice(1));
|
|
1473
|
+
const T = window.getSelection();
|
|
1474
|
+
i.ensureCaretAtBoundary(T, l), i.scheduleBoundaryCaretAdjustment(l);
|
|
1475
|
+
const y = (r = l.textContent) != null ? r : "", m = y.replace(/\u200B/g, ""), C = m.match(/^\s+/), E = /\S/.test(m), N = y.startsWith("");
|
|
1476
|
+
C && (o.hasLeadingSpace = !0, o.leadingWhitespace = C[0]), E && (o.hasTypedContent = !0);
|
|
1477
|
+
const B = /^\s/.test(m), x = o.hasTypedContent && !N && ((s = d.textContent) != null ? s : "").length <= c, v = o.hasLeadingSpace && o.hasTypedContent && !B;
|
|
1478
|
+
if (x && v) {
|
|
1479
|
+
const b = y.replace(/^[\u200B\s]+/, ""), S = o.leadingWhitespace || " ";
|
|
1480
|
+
l.textContent = `${S}${b}`, i.ensureCaretAtBoundary(T, l);
|
|
1481
|
+
}
|
|
1482
|
+
x && i.collapsedExitRecords.delete(o);
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
/**
|
|
1486
|
+
* Ensure the caret remains at the end of the boundary text node when exiting bold
|
|
1487
|
+
* @param selection - Current document selection
|
|
1488
|
+
* @param boundary - Text node following the bold element
|
|
1489
|
+
*/
|
|
1490
|
+
static ensureCaretAtBoundary(t, e) {
|
|
1491
|
+
!t || !t.isCollapsed || i.setCaretToBoundaryEnd(t, e);
|
|
1492
|
+
}
|
|
1493
|
+
/**
|
|
1494
|
+
* Ensure the caret remains at the end of the boundary text node after the current microtask queue is flushed
|
|
1495
|
+
* @param boundary - Boundary text node that should keep the caret at its end
|
|
1496
|
+
*/
|
|
1497
|
+
static scheduleBoundaryCaretAdjustment(t) {
|
|
1498
|
+
i.pendingBoundaryCaretAdjustments.has(t) || (i.pendingBoundaryCaretAdjustments.add(t), setTimeout(() => {
|
|
1499
|
+
var s, o, a;
|
|
1500
|
+
i.pendingBoundaryCaretAdjustments.delete(t);
|
|
1501
|
+
const e = (s = t.ownerDocument) != null ? s : typeof document != "undefined" ? document : null;
|
|
1502
|
+
if (!e)
|
|
1503
|
+
return;
|
|
1504
|
+
const n = e.getSelection();
|
|
1505
|
+
if (!n || !n.isCollapsed || n.anchorNode !== t)
|
|
1506
|
+
return;
|
|
1507
|
+
const r = (a = (o = t.textContent) == null ? void 0 : o.length) != null ? a : 0;
|
|
1508
|
+
n.anchorOffset !== r && i.setCaret(n, t, r);
|
|
1509
|
+
}, 0));
|
|
1510
|
+
}
|
|
1511
|
+
/**
|
|
1512
|
+
* Ensure there is a text node immediately following the provided bold element.
|
|
1513
|
+
* Creates one when necessary.
|
|
1514
|
+
* @param boldElement - Bold element that precedes the boundary
|
|
1515
|
+
* @returns The text node following the bold element or null if it cannot be created
|
|
1516
|
+
*/
|
|
1517
|
+
static ensureTextNodeAfter(t) {
|
|
1518
|
+
var o;
|
|
1519
|
+
const e = t.nextSibling;
|
|
1520
|
+
if ((e == null ? void 0 : e.nodeType) === Node.TEXT_NODE)
|
|
1521
|
+
return e;
|
|
1522
|
+
const n = t.parentNode;
|
|
1523
|
+
if (!n)
|
|
1524
|
+
return null;
|
|
1525
|
+
const r = (o = t.ownerDocument) != null ? o : typeof document != "undefined" ? document : null;
|
|
1526
|
+
if (!r)
|
|
1527
|
+
return null;
|
|
1528
|
+
const s = r.createTextNode("");
|
|
1529
|
+
return n.insertBefore(s, e), s;
|
|
1530
|
+
}
|
|
1531
|
+
/**
|
|
1532
|
+
* Resolve the boundary text node tracked for a collapsed exit record.
|
|
1533
|
+
* @param record - Collapsed exit tracking record
|
|
1534
|
+
* @returns The aligned boundary text node or null when it cannot be determined
|
|
1535
|
+
*/
|
|
1536
|
+
static resolveBoundary(t) {
|
|
1537
|
+
if (!t.boldElement.isConnected)
|
|
1538
|
+
return null;
|
|
1539
|
+
const e = i.ensureStrongElement(t.boldElement), n = t.boundary, s = n.isConnected && n.previousSibling === e ? n : i.ensureTextNodeAfter(e);
|
|
1540
|
+
return s ? {
|
|
1541
|
+
boundary: s,
|
|
1542
|
+
boldElement: e
|
|
1543
|
+
} : null;
|
|
1544
|
+
}
|
|
1545
|
+
/**
|
|
1546
|
+
* Move caret to the end of the provided boundary text node
|
|
1547
|
+
* @param selection - Current selection to update
|
|
1548
|
+
* @param boundary - Boundary text node that hosts the caret
|
|
1549
|
+
*/
|
|
1550
|
+
static setCaretToBoundaryEnd(t, e) {
|
|
1551
|
+
var s, o;
|
|
1552
|
+
const n = document.createRange(), r = (o = (s = e.textContent) == null ? void 0 : s.length) != null ? o : 0;
|
|
1553
|
+
n.setStart(e, r), n.collapse(!0), t.removeAllRanges(), t.addRange(n);
|
|
1554
|
+
}
|
|
1555
|
+
/**
|
|
1556
|
+
* Recursively check if a node or any of its parents is a bold tag (<strong>)
|
|
1557
|
+
* @param node - The node to check
|
|
1558
|
+
*/
|
|
1559
|
+
static hasBoldParent(t) {
|
|
1560
|
+
return t ? t.nodeType === Node.ELEMENT_NODE && i.isBoldTag(t) ? !0 : i.hasBoldParent(t.parentNode) : !1;
|
|
1561
|
+
}
|
|
1562
|
+
/**
|
|
1563
|
+
* Recursively find a bold element (<strong>) in the parent chain
|
|
1564
|
+
* @param node - The node to start searching from
|
|
1565
|
+
*/
|
|
1566
|
+
static findBoldElement(t) {
|
|
1567
|
+
return t ? t.nodeType === Node.ELEMENT_NODE && i.isBoldTag(t) ? i.ensureStrongElement(t) : i.findBoldElement(t.parentNode) : null;
|
|
1568
|
+
}
|
|
1569
|
+
/**
|
|
1570
|
+
* Check if an element is a bold tag (<strong> for conversion)
|
|
1571
|
+
* @param node - The element to check
|
|
1572
|
+
*/
|
|
1573
|
+
static isBoldTag(t) {
|
|
1574
|
+
const e = t.tagName;
|
|
1575
|
+
return e === "B" || e === "STRONG";
|
|
1576
|
+
}
|
|
1577
|
+
/**
|
|
1578
|
+
* Ensure an element is a <strong> tag, converting from <b> if needed
|
|
1579
|
+
* @param element - The element to ensure is a strong tag
|
|
1580
|
+
*/
|
|
1581
|
+
static ensureStrongElement(t) {
|
|
1582
|
+
if (t.tagName === "STRONG")
|
|
1583
|
+
return t;
|
|
1584
|
+
const e = document.createElement("strong");
|
|
1585
|
+
for (t.hasAttributes() && Array.from(t.attributes).forEach((n) => {
|
|
1586
|
+
e.setAttribute(n.name, n.value);
|
|
1587
|
+
}); t.firstChild; )
|
|
1588
|
+
e.appendChild(t.firstChild);
|
|
1589
|
+
return t.replaceWith(e), e;
|
|
1590
|
+
}
|
|
1591
|
+
/**
|
|
1592
|
+
* Merge two strong elements by moving children from right to left
|
|
1593
|
+
* @param left - The left strong element to merge into
|
|
1594
|
+
* @param right - The right strong element to merge from
|
|
1595
|
+
*/
|
|
1596
|
+
static mergeStrongNodes(t, e) {
|
|
1597
|
+
const n = i.ensureStrongElement(t), r = i.ensureStrongElement(e);
|
|
1598
|
+
for (; r.firstChild; )
|
|
1599
|
+
n.appendChild(r.firstChild);
|
|
1600
|
+
return r.remove(), n;
|
|
1601
|
+
}
|
|
1602
|
+
/**
|
|
1603
|
+
* Create button for Inline Toolbar
|
|
1604
|
+
*/
|
|
1605
|
+
render() {
|
|
1606
|
+
return {
|
|
1607
|
+
icon: gt,
|
|
1608
|
+
name: "bold",
|
|
1609
|
+
onActivate: () => {
|
|
1610
|
+
this.toggleBold();
|
|
1611
|
+
},
|
|
1612
|
+
isActive: () => {
|
|
1613
|
+
const t = window.getSelection();
|
|
1614
|
+
return t ? this.isSelectionVisuallyBold(t) : !1;
|
|
1615
|
+
}
|
|
1616
|
+
};
|
|
1617
|
+
}
|
|
1618
|
+
/**
|
|
1619
|
+
* Apply or remove bold formatting using modern Selection API
|
|
1620
|
+
*/
|
|
1621
|
+
toggleBold() {
|
|
1622
|
+
const t = window.getSelection();
|
|
1623
|
+
if (!t || t.rangeCount === 0)
|
|
1624
|
+
return;
|
|
1625
|
+
const e = t.getRangeAt(0);
|
|
1626
|
+
if (e.collapsed) {
|
|
1627
|
+
this.toggleCollapsedSelection();
|
|
1628
|
+
return;
|
|
1629
|
+
}
|
|
1630
|
+
this.isRangeBold(e, { ignoreWhitespace: !0 }) ? this.unwrapBoldTags(e) : this.wrapWithBold(e);
|
|
1631
|
+
}
|
|
1632
|
+
/**
|
|
1633
|
+
* Check if current selection is within a bold tag (<strong>)
|
|
1634
|
+
* @param selection - The Selection object to check
|
|
1635
|
+
*/
|
|
1636
|
+
isSelectionVisuallyBold(t) {
|
|
1637
|
+
if (!t || t.rangeCount === 0)
|
|
1638
|
+
return !1;
|
|
1639
|
+
const e = t.getRangeAt(0);
|
|
1640
|
+
return this.isRangeBold(e, { ignoreWhitespace: !0 });
|
|
1641
|
+
}
|
|
1642
|
+
/**
|
|
1643
|
+
* Wrap selection with <strong> tag
|
|
1644
|
+
* @param range - The Range object containing the selection to wrap
|
|
1645
|
+
*/
|
|
1646
|
+
wrapWithBold(t) {
|
|
1647
|
+
const e = this.getRangeHtmlWithoutBold(t), n = this.replaceRangeWithHtml(t, `<strong>${e}</strong>`), r = window.getSelection();
|
|
1648
|
+
r && n && (r.removeAllRanges(), r.addRange(n)), i.normalizeAllBoldTags();
|
|
1649
|
+
const s = this.findBoldElementFromRangeOrSelection(n, r);
|
|
1650
|
+
if (!s) {
|
|
1651
|
+
this.notifySelectionChange();
|
|
1652
|
+
return;
|
|
1653
|
+
}
|
|
1654
|
+
const o = this.mergeAdjacentBold(s);
|
|
1655
|
+
this.normalizeWhitespaceAround(o), this.selectElementContents(o), i.normalizeBoldTagsWithinBlok(window.getSelection()), i.replaceNbspInBlock(window.getSelection()), this.notifySelectionChange();
|
|
1656
|
+
}
|
|
1657
|
+
/**
|
|
1658
|
+
* Remove bold tags (<strong>) while preserving content
|
|
1659
|
+
* @param range - The Range object containing the selection to unwrap
|
|
1660
|
+
*/
|
|
1661
|
+
unwrapBoldTags(t) {
|
|
1662
|
+
const e = this.collectBoldAncestors(t), n = window.getSelection();
|
|
1663
|
+
if (!n)
|
|
1664
|
+
return;
|
|
1665
|
+
const r = document.createElement("span"), s = t.extractContents();
|
|
1666
|
+
r.setAttribute("data-blok-bold-marker", `unwrap-${i.markerSequence++}`), r.appendChild(s), this.removeNestedBold(r), t.insertNode(r);
|
|
1667
|
+
const o = document.createRange();
|
|
1668
|
+
for (o.selectNodeContents(r), n.removeAllRanges(), n.addRange(o); ; ) {
|
|
1669
|
+
const c = i.findBoldElement(r);
|
|
1670
|
+
if (!c)
|
|
1671
|
+
break;
|
|
1672
|
+
this.moveMarkerOutOfBold(r, c);
|
|
1673
|
+
}
|
|
1674
|
+
const a = r.firstChild, l = r.lastChild;
|
|
1675
|
+
this.unwrapElement(r);
|
|
1676
|
+
const d = a && l ? (() => {
|
|
1677
|
+
const c = document.createRange();
|
|
1678
|
+
return c.setStartBefore(a), c.setEndAfter(l), n.removeAllRanges(), n.addRange(c), c;
|
|
1679
|
+
})() : void 0;
|
|
1680
|
+
d || n.removeAllRanges(), this.replaceNbspWithinRange(d), i.normalizeBoldTagsWithinBlok(n), i.replaceNbspInBlock(n), i.removeEmptyBoldElements(n), e.forEach((c) => {
|
|
1681
|
+
i.isElementEmpty(c) && c.remove();
|
|
1682
|
+
}), this.notifySelectionChange();
|
|
1683
|
+
}
|
|
1684
|
+
/**
|
|
1685
|
+
* Replace the current range contents with provided HTML snippet
|
|
1686
|
+
* @param range - Range to replace
|
|
1687
|
+
* @param html - HTML string to insert
|
|
1688
|
+
* @returns range spanning inserted content
|
|
1689
|
+
*/
|
|
1690
|
+
replaceRangeWithHtml(t, e) {
|
|
1691
|
+
var a, l;
|
|
1692
|
+
const n = i.createFragmentFromHtml(e), r = (a = n.firstChild) != null ? a : null, s = (l = n.lastChild) != null ? l : null;
|
|
1693
|
+
if (t.deleteContents(), !r || !s)
|
|
1694
|
+
return;
|
|
1695
|
+
t.insertNode(n);
|
|
1696
|
+
const o = document.createRange();
|
|
1697
|
+
return o.setStartBefore(r), o.setEndAfter(s), o;
|
|
1698
|
+
}
|
|
1699
|
+
/**
|
|
1700
|
+
* Move a temporary marker element outside of a bold ancestor while preserving content order
|
|
1701
|
+
* @param marker - Marker element wrapping the selection contents
|
|
1702
|
+
* @param boldElement - Bold ancestor containing the marker
|
|
1703
|
+
*/
|
|
1704
|
+
moveMarkerOutOfBold(t, e) {
|
|
1705
|
+
const n = e.parentNode;
|
|
1706
|
+
if (!n)
|
|
1707
|
+
return;
|
|
1708
|
+
if (Array.from(e.childNodes).forEach((l) => {
|
|
1709
|
+
var d;
|
|
1710
|
+
l.nodeType === Node.TEXT_NODE && ((d = l.textContent) != null ? d : "").length === 0 && l.remove();
|
|
1711
|
+
}), e.childNodes.length === 1 && e.firstChild === t) {
|
|
1712
|
+
e.replaceWith(t);
|
|
1713
|
+
return;
|
|
1714
|
+
}
|
|
1715
|
+
if (e.firstChild === t) {
|
|
1716
|
+
n.insertBefore(t, e);
|
|
1717
|
+
return;
|
|
1718
|
+
}
|
|
1719
|
+
if (e.lastChild === t) {
|
|
1720
|
+
n.insertBefore(t, e.nextSibling);
|
|
1721
|
+
return;
|
|
1722
|
+
}
|
|
1723
|
+
const a = e.cloneNode(!1);
|
|
1724
|
+
for (; t.nextSibling; )
|
|
1725
|
+
a.appendChild(t.nextSibling);
|
|
1726
|
+
n.insertBefore(a, e.nextSibling), n.insertBefore(t, a);
|
|
1727
|
+
}
|
|
1728
|
+
/**
|
|
1729
|
+
* Select all contents of an element
|
|
1730
|
+
* @param element - The element whose contents should be selected
|
|
1731
|
+
*/
|
|
1732
|
+
selectElementContents(t) {
|
|
1733
|
+
const e = window.getSelection();
|
|
1734
|
+
if (!e)
|
|
1735
|
+
return;
|
|
1736
|
+
const n = document.createRange();
|
|
1737
|
+
n.selectNodeContents(t), e.removeAllRanges(), e.addRange(n);
|
|
1738
|
+
}
|
|
1739
|
+
/**
|
|
1740
|
+
* Check if a range contains bold text
|
|
1741
|
+
* @param range - The range to check
|
|
1742
|
+
* @param options - Options for checking bold status
|
|
1743
|
+
* @param options.ignoreWhitespace - Whether to ignore whitespace-only text nodes
|
|
1744
|
+
*/
|
|
1745
|
+
isRangeBold(t, e) {
|
|
1746
|
+
var s;
|
|
1747
|
+
if (t.collapsed)
|
|
1748
|
+
return !!i.findBoldElement(t.startContainer);
|
|
1749
|
+
const n = document.createTreeWalker(
|
|
1750
|
+
t.commonAncestorContainer,
|
|
1751
|
+
NodeFilter.SHOW_TEXT,
|
|
1752
|
+
{
|
|
1753
|
+
acceptNode: (o) => {
|
|
1754
|
+
try {
|
|
1755
|
+
return t.intersectsNode(o) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
|
|
1756
|
+
} catch (a) {
|
|
1757
|
+
const l = document.createRange();
|
|
1758
|
+
l.selectNodeContents(o);
|
|
1759
|
+
const d = t.compareBoundaryPoints(Range.END_TO_START, l) > 0, c = t.compareBoundaryPoints(Range.START_TO_END, l) < 0;
|
|
1760
|
+
return d && c ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
), r = [];
|
|
1765
|
+
for (; n.nextNode(); ) {
|
|
1766
|
+
const o = n.currentNode, a = (s = o.textContent) != null ? s : "";
|
|
1767
|
+
e.ignoreWhitespace && a.trim().length === 0 || a.length !== 0 && r.push(o);
|
|
1768
|
+
}
|
|
1769
|
+
return r.length === 0 ? !!i.findBoldElement(t.startContainer) : r.every((o) => i.hasBoldParent(o));
|
|
1770
|
+
}
|
|
1771
|
+
/**
|
|
1772
|
+
* Remove nested bold tags from a root node
|
|
1773
|
+
* @param root - The root node to process
|
|
1774
|
+
*/
|
|
1775
|
+
removeNestedBold(t) {
|
|
1776
|
+
var n;
|
|
1777
|
+
const e = (n = t.querySelectorAll) == null ? void 0 : n.call(t, "b,strong");
|
|
1778
|
+
e && e.forEach((r) => {
|
|
1779
|
+
this.unwrapElement(r);
|
|
1780
|
+
});
|
|
1781
|
+
}
|
|
1782
|
+
/**
|
|
1783
|
+
* Unwrap an element by moving its children to the parent
|
|
1784
|
+
* @param element - The element to unwrap
|
|
1785
|
+
*/
|
|
1786
|
+
unwrapElement(t) {
|
|
1787
|
+
const e = t.parentNode;
|
|
1788
|
+
if (!e) {
|
|
1789
|
+
t.remove();
|
|
1790
|
+
return;
|
|
1791
|
+
}
|
|
1792
|
+
for (; t.firstChild; )
|
|
1793
|
+
e.insertBefore(t.firstChild, t);
|
|
1794
|
+
e.removeChild(t);
|
|
1795
|
+
}
|
|
1796
|
+
/**
|
|
1797
|
+
* Find bold element from an inserted range or fall back to selection
|
|
1798
|
+
* @param insertedRange - Range spanning inserted content
|
|
1799
|
+
* @param selection - Current selection as fallback
|
|
1800
|
+
*/
|
|
1801
|
+
findBoldElementFromRangeOrSelection(t, e) {
|
|
1802
|
+
if (!t)
|
|
1803
|
+
return e ? i.findBoldElement(e.focusNode) : null;
|
|
1804
|
+
const n = i.findBoldElement(t.startContainer);
|
|
1805
|
+
if (n)
|
|
1806
|
+
return n;
|
|
1807
|
+
const r = i.findBoldElement(t.commonAncestorContainer);
|
|
1808
|
+
return r || (t.startContainer.nodeType === Node.ELEMENT_NODE && i.isBoldTag(t.startContainer) ? t.startContainer : null);
|
|
1809
|
+
}
|
|
1810
|
+
/**
|
|
1811
|
+
* Merge adjacent bold elements into a single element
|
|
1812
|
+
* @param element - The bold element to merge with adjacent elements
|
|
1813
|
+
*/
|
|
1814
|
+
mergeAdjacentBold(t) {
|
|
1815
|
+
const e = i.ensureStrongElement(t), n = e.previousSibling, r = n && n.nodeType === Node.ELEMENT_NODE && i.isBoldTag(n) ? i.mergeStrongNodes(n, e) : e, s = r.nextSibling;
|
|
1816
|
+
return s && s.nodeType === Node.ELEMENT_NODE && i.isBoldTag(s) ? i.mergeStrongNodes(r, s) : r;
|
|
1817
|
+
}
|
|
1818
|
+
/**
|
|
1819
|
+
* Toggle bold formatting for a collapsed selection (caret position)
|
|
1820
|
+
* Exits bold if caret is inside a bold element, otherwise starts a new bold element
|
|
1821
|
+
*/
|
|
1822
|
+
toggleCollapsedSelection() {
|
|
1823
|
+
const t = window.getSelection();
|
|
1824
|
+
if (!t || t.rangeCount === 0)
|
|
1825
|
+
return;
|
|
1826
|
+
const e = t.getRangeAt(0), n = i.findBoldElement(e.startContainer), r = (() => {
|
|
1827
|
+
if (n && n.getAttribute(i.DATA_ATTR_COLLAPSED_ACTIVE) !== "true")
|
|
1828
|
+
return i.exitCollapsedBold(t, n);
|
|
1829
|
+
const s = n != null ? n : i.getBoundaryBold(e);
|
|
1830
|
+
return s ? i.exitCollapsedBold(t, s) : this.startCollapsedBold(e);
|
|
1831
|
+
})();
|
|
1832
|
+
document.dispatchEvent(new Event("selectionchange")), r && (t.removeAllRanges(), t.addRange(r)), i.normalizeBoldTagsWithinBlok(t), i.replaceNbspInBlock(t), i.removeEmptyBoldElements(t), this.notifySelectionChange();
|
|
1833
|
+
}
|
|
1834
|
+
/**
|
|
1835
|
+
* Insert a bold wrapper at the caret so newly typed text becomes bold
|
|
1836
|
+
* @param range - Current collapsed range
|
|
1837
|
+
*/
|
|
1838
|
+
startCollapsedBold(t) {
|
|
1839
|
+
if (!t.collapsed)
|
|
1840
|
+
return;
|
|
1841
|
+
const e = document.createElement("strong"), n = document.createTextNode("");
|
|
1842
|
+
e.appendChild(n), e.setAttribute(i.DATA_ATTR_COLLAPSED_ACTIVE, "true");
|
|
1843
|
+
const r = t.startContainer, s = t.startOffset;
|
|
1844
|
+
if (!(r.nodeType === Node.TEXT_NODE ? this.insertCollapsedBoldIntoText(r, e, s) : r.nodeType === Node.ELEMENT_NODE ? (this.insertCollapsedBoldIntoElement(r, e, s), !0) : !1))
|
|
1845
|
+
return;
|
|
1846
|
+
const a = window.getSelection(), l = document.createRange();
|
|
1847
|
+
l.setStart(n, 0), l.collapse(!0);
|
|
1848
|
+
const d = this.mergeAdjacentBold(e);
|
|
1849
|
+
return i.normalizeBoldTagsWithinBlok(a), i.replaceNbspInBlock(a), i.removeEmptyBoldElements(a), a && (a.removeAllRanges(), a.addRange(l)), this.notifySelectionChange(), d.firstChild instanceof Text ? (() => {
|
|
1850
|
+
var h, p;
|
|
1851
|
+
const c = document.createRange();
|
|
1852
|
+
return c.setStart(d.firstChild, (p = (h = d.firstChild.textContent) == null ? void 0 : h.length) != null ? p : 0), c.collapse(!0), c;
|
|
1853
|
+
})() : l;
|
|
1854
|
+
}
|
|
1855
|
+
/**
|
|
1856
|
+
* Insert a collapsed bold wrapper when the caret resides inside a text node
|
|
1857
|
+
* @param text - Text node containing the caret
|
|
1858
|
+
* @param strong - Strong element to insert
|
|
1859
|
+
* @param offset - Caret offset within the text node
|
|
1860
|
+
* @returns true when insertion succeeded
|
|
1861
|
+
*/
|
|
1862
|
+
insertCollapsedBoldIntoText(t, e, n) {
|
|
1863
|
+
var c;
|
|
1864
|
+
const r = t, s = r.parentNode;
|
|
1865
|
+
if (!s)
|
|
1866
|
+
return !1;
|
|
1867
|
+
const o = (c = r.textContent) != null ? c : "", a = o.slice(0, n), l = o.slice(n);
|
|
1868
|
+
r.textContent = a;
|
|
1869
|
+
const d = l.length ? document.createTextNode(l) : null;
|
|
1870
|
+
return d && s.insertBefore(d, r.nextSibling), s.insertBefore(e, d != null ? d : r.nextSibling), e.setAttribute(i.DATA_ATTR_PREV_LENGTH, a.length.toString()), !0;
|
|
1871
|
+
}
|
|
1872
|
+
/**
|
|
1873
|
+
* Insert a collapsed bold wrapper directly into an element container
|
|
1874
|
+
* @param element - Container element
|
|
1875
|
+
* @param strong - Strong element to insert
|
|
1876
|
+
* @param offset - Index at which to insert the strong element
|
|
1877
|
+
*/
|
|
1878
|
+
insertCollapsedBoldIntoElement(t, e, n) {
|
|
1879
|
+
var s;
|
|
1880
|
+
const r = (s = t.childNodes[n]) != null ? s : null;
|
|
1881
|
+
t.insertBefore(e, r), e.setAttribute(i.DATA_ATTR_PREV_LENGTH, "0");
|
|
1882
|
+
}
|
|
1883
|
+
/**
|
|
1884
|
+
* Check if an element is empty (has no text content)
|
|
1885
|
+
* @param element - The element to check
|
|
1886
|
+
*/
|
|
1887
|
+
static isElementEmpty(t) {
|
|
1888
|
+
var e;
|
|
1889
|
+
return ((e = t.textContent) != null ? e : "").length === 0;
|
|
1890
|
+
}
|
|
1891
|
+
/**
|
|
1892
|
+
*
|
|
1893
|
+
*/
|
|
1894
|
+
notifySelectionChange() {
|
|
1895
|
+
i.enforceCollapsedBoldLengths(window.getSelection()), document.dispatchEvent(new Event("selectionchange")), this.updateToolbarButtonState();
|
|
1896
|
+
}
|
|
1897
|
+
/**
|
|
1898
|
+
* Ensure inline toolbar button reflects the actual bold state after programmatic toggles
|
|
1899
|
+
*/
|
|
1900
|
+
updateToolbarButtonState() {
|
|
1901
|
+
const t = window.getSelection();
|
|
1902
|
+
if (!t)
|
|
1903
|
+
return;
|
|
1904
|
+
const e = t.anchorNode, n = (e == null ? void 0 : e.nodeType) === Node.ELEMENT_NODE ? e : e == null ? void 0 : e.parentElement, r = n == null ? void 0 : n.closest(A(g.editor));
|
|
1905
|
+
if (!r)
|
|
1906
|
+
return;
|
|
1907
|
+
const s = r.querySelector("[data-blok-testid=inline-toolbar]");
|
|
1908
|
+
if (!(s instanceof HTMLElement))
|
|
1909
|
+
return;
|
|
1910
|
+
const o = s.querySelector('[data-blok-item-name="bold"]');
|
|
1911
|
+
if (!(o instanceof HTMLElement))
|
|
1912
|
+
return;
|
|
1913
|
+
this.isSelectionVisuallyBold(t) ? o.setAttribute("data-blok-popover-item-active", "true") : o.removeAttribute("data-blok-popover-item-active");
|
|
1914
|
+
}
|
|
1915
|
+
/**
|
|
1916
|
+
* Normalize whitespace around a bold element
|
|
1917
|
+
* @param element - The bold element to normalize whitespace around
|
|
1918
|
+
*/
|
|
1919
|
+
normalizeWhitespaceAround(t) {
|
|
1920
|
+
i.replaceNbspWithSpace(t.previousSibling), i.replaceNbspWithSpace(t.nextSibling);
|
|
1921
|
+
}
|
|
1922
|
+
/**
|
|
1923
|
+
* Replace non-breaking spaces with regular spaces in a text node
|
|
1924
|
+
* @param node - The text node to process
|
|
1925
|
+
*/
|
|
1926
|
+
static replaceNbspWithSpace(t) {
|
|
1927
|
+
var r;
|
|
1928
|
+
if (!t || t.nodeType !== Node.TEXT_NODE)
|
|
1929
|
+
return;
|
|
1930
|
+
const e = t, n = (r = e.textContent) != null ? r : "";
|
|
1931
|
+
n.includes(" ") && (e.textContent = n.replace(/\u00A0/g, " "));
|
|
1932
|
+
}
|
|
1933
|
+
/**
|
|
1934
|
+
* Restore a selection range from marker elements
|
|
1935
|
+
* @param markerId - The ID of the markers used to mark the selection
|
|
1936
|
+
*/
|
|
1937
|
+
restoreSelectionFromMarkers(t) {
|
|
1938
|
+
const e = document.querySelector(`[data-blok-bold-marker="${t}-start"]`), n = document.querySelector(`[data-blok-bold-marker="${t}-end"]`);
|
|
1939
|
+
if (!e || !n) {
|
|
1940
|
+
e == null || e.remove(), n == null || n.remove();
|
|
1941
|
+
return;
|
|
1942
|
+
}
|
|
1943
|
+
const r = window.getSelection();
|
|
1944
|
+
if (!r) {
|
|
1945
|
+
e.remove(), n.remove();
|
|
1946
|
+
return;
|
|
1947
|
+
}
|
|
1948
|
+
const s = document.createRange();
|
|
1949
|
+
return s.setStartAfter(e), s.setEndBefore(n), r.removeAllRanges(), r.addRange(s), e.remove(), n.remove(), s;
|
|
1950
|
+
}
|
|
1951
|
+
/**
|
|
1952
|
+
* Replace non-breaking spaces with regular spaces within a range
|
|
1953
|
+
* @param range - The range to process
|
|
1954
|
+
*/
|
|
1955
|
+
replaceNbspWithinRange(t) {
|
|
1956
|
+
if (!t)
|
|
1957
|
+
return;
|
|
1958
|
+
const e = document.createTreeWalker(
|
|
1959
|
+
t.commonAncestorContainer,
|
|
1960
|
+
NodeFilter.SHOW_TEXT,
|
|
1961
|
+
{
|
|
1962
|
+
acceptNode: (n) => {
|
|
1963
|
+
try {
|
|
1964
|
+
return t.intersectsNode(n) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
|
|
1965
|
+
} catch (r) {
|
|
1966
|
+
const s = document.createRange();
|
|
1967
|
+
s.selectNodeContents(n);
|
|
1968
|
+
const o = t.compareBoundaryPoints(Range.END_TO_START, s) > 0, a = t.compareBoundaryPoints(Range.START_TO_END, s) < 0;
|
|
1969
|
+
return o && a ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
|
|
1970
|
+
}
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
);
|
|
1974
|
+
for (; e.nextNode(); )
|
|
1975
|
+
i.replaceNbspWithSpace(e.currentNode);
|
|
1976
|
+
}
|
|
1977
|
+
/**
|
|
1978
|
+
* Normalize all bold tags within the blok to <strong> tags
|
|
1979
|
+
* Converts any legacy <b> tags to <strong> tags
|
|
1980
|
+
* @param selection - The current selection to determine the blok context
|
|
1981
|
+
*/
|
|
1982
|
+
static normalizeBoldTagsWithinBlok(t) {
|
|
1983
|
+
var s;
|
|
1984
|
+
const e = (s = t == null ? void 0 : t.anchorNode) != null ? s : t == null ? void 0 : t.focusNode;
|
|
1985
|
+
if (!e)
|
|
1986
|
+
return;
|
|
1987
|
+
const n = e.nodeType === Node.ELEMENT_NODE ? e : e.parentElement, r = n == null ? void 0 : n.closest(A(g.editor));
|
|
1988
|
+
r && r.querySelectorAll("b").forEach((o) => {
|
|
1989
|
+
i.ensureStrongElement(o);
|
|
1990
|
+
});
|
|
1991
|
+
}
|
|
1992
|
+
/**
|
|
1993
|
+
* Replace non-breaking spaces with regular spaces in the block containing the selection
|
|
1994
|
+
* @param selection - The current selection to determine the block context
|
|
1995
|
+
*/
|
|
1996
|
+
static replaceNbspInBlock(t) {
|
|
1997
|
+
var o;
|
|
1998
|
+
const e = (o = t == null ? void 0 : t.anchorNode) != null ? o : t == null ? void 0 : t.focusNode;
|
|
1999
|
+
if (!e)
|
|
2000
|
+
return;
|
|
2001
|
+
const n = e.nodeType === Node.ELEMENT_NODE ? e : e.parentElement, r = n == null ? void 0 : n.closest('[data-blok-component="paragraph"]');
|
|
2002
|
+
if (!r)
|
|
2003
|
+
return;
|
|
2004
|
+
const s = document.createTreeWalker(r, NodeFilter.SHOW_TEXT);
|
|
2005
|
+
for (; s.nextNode(); )
|
|
2006
|
+
i.replaceNbspWithSpace(s.currentNode);
|
|
2007
|
+
r.querySelectorAll("b").forEach((a) => {
|
|
2008
|
+
i.ensureStrongElement(a);
|
|
2009
|
+
});
|
|
2010
|
+
}
|
|
2011
|
+
/**
|
|
2012
|
+
* Remove empty bold elements within the current block
|
|
2013
|
+
* @param selection - The current selection to determine the block context
|
|
2014
|
+
*/
|
|
2015
|
+
static removeEmptyBoldElements(t) {
|
|
2016
|
+
var o, a;
|
|
2017
|
+
const e = (o = t == null ? void 0 : t.anchorNode) != null ? o : t == null ? void 0 : t.focusNode;
|
|
2018
|
+
if (!e)
|
|
2019
|
+
return;
|
|
2020
|
+
const n = e.nodeType === Node.ELEMENT_NODE ? e : e.parentElement, r = n == null ? void 0 : n.closest('[data-blok-component="paragraph"]');
|
|
2021
|
+
if (!r)
|
|
2022
|
+
return;
|
|
2023
|
+
const s = (a = t == null ? void 0 : t.focusNode) != null ? a : null;
|
|
2024
|
+
r.querySelectorAll("strong").forEach((l) => {
|
|
2025
|
+
var h;
|
|
2026
|
+
const d = l.getAttribute(i.DATA_ATTR_COLLAPSED_ACTIVE) === "true", c = l.hasAttribute(i.DATA_ATTR_COLLAPSED_LENGTH);
|
|
2027
|
+
d || c || ((h = l.textContent) != null ? h : "").length === 0 && !i.isNodeWithin(s, l) && l.remove();
|
|
2028
|
+
});
|
|
2029
|
+
}
|
|
2030
|
+
/**
|
|
2031
|
+
* Ensure collapsed bold placeholders absorb newly typed text
|
|
2032
|
+
* @param selection - The current selection to determine the blok context
|
|
2033
|
+
*/
|
|
2034
|
+
static synchronizeCollapsedBold(t) {
|
|
2035
|
+
var o, a;
|
|
2036
|
+
const e = (o = t == null ? void 0 : t.anchorNode) != null ? o : t == null ? void 0 : t.focusNode, n = e && e.nodeType === Node.ELEMENT_NODE ? e : e == null ? void 0 : e.parentElement, r = (a = n == null ? void 0 : n.closest(A(g.editor))) != null ? a : n == null ? void 0 : n.ownerDocument;
|
|
2037
|
+
if (!r)
|
|
2038
|
+
return;
|
|
2039
|
+
const s = `strong[${i.DATA_ATTR_COLLAPSED_ACTIVE}="true"]`;
|
|
2040
|
+
r.querySelectorAll(s).forEach((l) => {
|
|
2041
|
+
var k, R, F, V, q;
|
|
2042
|
+
const d = l.getAttribute(i.DATA_ATTR_PREV_LENGTH), c = l.previousSibling;
|
|
2043
|
+
if (!d || !c || c.nodeType !== Node.TEXT_NODE)
|
|
2044
|
+
return;
|
|
2045
|
+
const h = Number(d);
|
|
2046
|
+
if (!Number.isFinite(h))
|
|
2047
|
+
return;
|
|
2048
|
+
const p = c, T = (k = p.textContent) != null ? k : "";
|
|
2049
|
+
if (T.length <= h)
|
|
2050
|
+
return;
|
|
2051
|
+
const y = T.slice(0, h), m = T.slice(h);
|
|
2052
|
+
p.textContent = y;
|
|
2053
|
+
const C = m.match(/^[\u00A0\s]+/);
|
|
2054
|
+
if (C && !l.hasAttribute(i.DATA_ATTR_LEADING_WHITESPACE) && l.setAttribute(i.DATA_ATTR_LEADING_WHITESPACE, C[0]), m.length === 0)
|
|
2055
|
+
return;
|
|
2056
|
+
const E = (R = l.textContent) != null ? R : "", N = E + m, B = (F = l.getAttribute(i.DATA_ATTR_LEADING_WHITESPACE)) != null ? F : "", v = B.length > 0 && E.length === 0 && !N.startsWith(B) ? B + N : N, b = document.createTextNode(v);
|
|
2057
|
+
for (; l.firstChild; )
|
|
2058
|
+
l.removeChild(l.firstChild);
|
|
2059
|
+
if (l.appendChild(b), !(t != null && t.isCollapsed) || !i.isNodeWithin(t.focusNode, p))
|
|
2060
|
+
return;
|
|
2061
|
+
const S = document.createRange(), _ = (q = (V = b.textContent) == null ? void 0 : V.length) != null ? q : 0;
|
|
2062
|
+
S.setStart(b, _), S.collapse(!0), t.removeAllRanges(), t.addRange(S);
|
|
2063
|
+
});
|
|
2064
|
+
}
|
|
2065
|
+
/**
|
|
2066
|
+
* Ensure caret is positioned after boundary bold elements when toggling collapsed selections
|
|
2067
|
+
* @param selection - Current selection
|
|
2068
|
+
*/
|
|
2069
|
+
static moveCaretAfterBoundaryBold(t) {
|
|
2070
|
+
if (!t.rangeCount)
|
|
2071
|
+
return;
|
|
2072
|
+
const e = t.getRangeAt(0);
|
|
2073
|
+
if (!e.collapsed)
|
|
2074
|
+
return;
|
|
2075
|
+
const n = i.findBoldElement(e.startContainer);
|
|
2076
|
+
(n == null ? void 0 : n.getAttribute(i.DATA_ATTR_COLLAPSED_ACTIVE)) !== "true" && (i.moveCaretFromElementContainer(t, e) || i.moveCaretFromTextContainer(t, e));
|
|
2077
|
+
}
|
|
2078
|
+
/**
|
|
2079
|
+
* Locate a bold element adjacent to a collapsed range
|
|
2080
|
+
* @param range - Range to inspect
|
|
2081
|
+
*/
|
|
2082
|
+
static getAdjacentBold(t) {
|
|
2083
|
+
const e = t.startContainer;
|
|
2084
|
+
return e.nodeType === Node.TEXT_NODE ? i.getBoldAdjacentToText(t, e) : e.nodeType === Node.ELEMENT_NODE ? i.getBoldAdjacentToElement(t, e) : null;
|
|
2085
|
+
}
|
|
2086
|
+
/**
|
|
2087
|
+
* Get bold element adjacent to a text node container
|
|
2088
|
+
* @param range - Current collapsed range
|
|
2089
|
+
* @param textNode - Text node hosting the caret
|
|
2090
|
+
*/
|
|
2091
|
+
static getBoldAdjacentToText(t, e) {
|
|
2092
|
+
var o, a;
|
|
2093
|
+
const n = (a = (o = e.textContent) == null ? void 0 : o.length) != null ? a : 0, r = e.previousSibling;
|
|
2094
|
+
if (t.startOffset === 0 && i.isBoldElement(r))
|
|
2095
|
+
return r;
|
|
2096
|
+
if (t.startOffset !== n)
|
|
2097
|
+
return null;
|
|
2098
|
+
const s = e.nextSibling;
|
|
2099
|
+
return i.isBoldElement(s) ? s : null;
|
|
2100
|
+
}
|
|
2101
|
+
/**
|
|
2102
|
+
* Get bold element adjacent to an element container
|
|
2103
|
+
* @param range - Current collapsed range
|
|
2104
|
+
* @param element - Element containing the caret
|
|
2105
|
+
*/
|
|
2106
|
+
static getBoldAdjacentToElement(t, e) {
|
|
2107
|
+
var s, o;
|
|
2108
|
+
const n = t.startOffset > 0 && (s = e.childNodes[t.startOffset - 1]) != null ? s : null;
|
|
2109
|
+
if (i.isBoldElement(n))
|
|
2110
|
+
return n;
|
|
2111
|
+
const r = (o = e.childNodes[t.startOffset]) != null ? o : null;
|
|
2112
|
+
return i.isBoldElement(r) ? r : null;
|
|
2113
|
+
}
|
|
2114
|
+
/**
|
|
2115
|
+
* Exit collapsed bold state when caret no longer resides within bold content
|
|
2116
|
+
* @param selection - Current selection
|
|
2117
|
+
* @param range - Collapsed range after toggling bold
|
|
2118
|
+
*/
|
|
2119
|
+
static exitCollapsedIfNeeded(t, e) {
|
|
2120
|
+
var o;
|
|
2121
|
+
if (!!i.findBoldElement(e.startContainer))
|
|
2122
|
+
return;
|
|
2123
|
+
const r = (o = i.getBoundaryBold(e)) != null ? o : i.getAdjacentBold(e);
|
|
2124
|
+
if (!r)
|
|
2125
|
+
return;
|
|
2126
|
+
const s = i.exitCollapsedBold(t, r);
|
|
2127
|
+
s && (t.removeAllRanges(), t.addRange(s));
|
|
2128
|
+
}
|
|
2129
|
+
/**
|
|
2130
|
+
* Adjust caret when selection container is an element adjacent to bold content
|
|
2131
|
+
* @param selection - Current selection
|
|
2132
|
+
* @param range - Collapsed range to inspect
|
|
2133
|
+
* @returns true when caret position was updated
|
|
2134
|
+
*/
|
|
2135
|
+
static moveCaretFromElementContainer(t, e) {
|
|
2136
|
+
if (e.startContainer.nodeType !== Node.ELEMENT_NODE)
|
|
2137
|
+
return !1;
|
|
2138
|
+
const n = e.startContainer;
|
|
2139
|
+
return i.moveCaretAfterPreviousBold(t, n, e.startOffset) ? !0 : i.moveCaretBeforeNextBold(t, n, e.startOffset);
|
|
2140
|
+
}
|
|
2141
|
+
/**
|
|
2142
|
+
* Move caret after the bold node that precedes the caret when possible
|
|
2143
|
+
* @param selection - Current selection
|
|
2144
|
+
* @param element - Container element
|
|
2145
|
+
* @param offset - Caret offset within the container
|
|
2146
|
+
*/
|
|
2147
|
+
static moveCaretAfterPreviousBold(t, e, n) {
|
|
2148
|
+
var a, l, d;
|
|
2149
|
+
const r = n > 0 && (a = e.childNodes[n - 1]) != null ? a : null;
|
|
2150
|
+
if (!i.isBoldElement(r))
|
|
2151
|
+
return !1;
|
|
2152
|
+
const s = i.ensureFollowingTextNode(r, r.nextSibling);
|
|
2153
|
+
if (!s)
|
|
2154
|
+
return !1;
|
|
2155
|
+
const o = (d = (l = s.textContent) == null ? void 0 : l.length) != null ? d : 0;
|
|
2156
|
+
return i.setCaret(t, s, o), !0;
|
|
2157
|
+
}
|
|
2158
|
+
/**
|
|
2159
|
+
* Move caret before the bold node that follows the caret, ensuring there's a text node to receive input
|
|
2160
|
+
* @param selection - Current selection
|
|
2161
|
+
* @param element - Container element
|
|
2162
|
+
* @param offset - Caret offset within the container
|
|
2163
|
+
*/
|
|
2164
|
+
static moveCaretBeforeNextBold(t, e, n) {
|
|
2165
|
+
var o;
|
|
2166
|
+
const r = (o = e.childNodes[n]) != null ? o : null;
|
|
2167
|
+
if (!i.isBoldElement(r))
|
|
2168
|
+
return !1;
|
|
2169
|
+
const s = i.ensureFollowingTextNode(r, r.nextSibling);
|
|
2170
|
+
return s ? (i.setCaret(t, s, 0), !0) : (i.setCaretAfterNode(t, r), !0);
|
|
2171
|
+
}
|
|
2172
|
+
/**
|
|
2173
|
+
* Adjust caret when selection container is a text node adjacent to bold content
|
|
2174
|
+
* @param selection - Current selection
|
|
2175
|
+
* @param range - Collapsed range to inspect
|
|
2176
|
+
*/
|
|
2177
|
+
static moveCaretFromTextContainer(t, e) {
|
|
2178
|
+
var d, c, h;
|
|
2179
|
+
if (e.startContainer.nodeType !== Node.TEXT_NODE)
|
|
2180
|
+
return;
|
|
2181
|
+
const n = e.startContainer, r = n.previousSibling, s = (d = n.textContent) != null ? d : "", o = /^\s/.test(s);
|
|
2182
|
+
if (e.startOffset === 0 && i.isBoldElement(r) && (s.length === 0 || o)) {
|
|
2183
|
+
i.setCaret(t, n, s.length);
|
|
2184
|
+
return;
|
|
2185
|
+
}
|
|
2186
|
+
const a = i.findBoldElement(n);
|
|
2187
|
+
if (!a || e.startOffset !== ((h = (c = n.textContent) == null ? void 0 : c.length) != null ? h : 0))
|
|
2188
|
+
return;
|
|
2189
|
+
const l = i.ensureFollowingTextNode(a, a.nextSibling);
|
|
2190
|
+
if (l) {
|
|
2191
|
+
i.setCaret(t, l, 0);
|
|
2192
|
+
return;
|
|
2193
|
+
}
|
|
2194
|
+
i.setCaretAfterNode(t, a);
|
|
2195
|
+
}
|
|
2196
|
+
/**
|
|
2197
|
+
* Ensure caret is positioned at the end of a collapsed boundary text node before the browser processes a printable keydown
|
|
2198
|
+
* @param event - Keydown event fired before browser input handling
|
|
2199
|
+
*/
|
|
2200
|
+
static guardCollapsedBoundaryKeydown(t) {
|
|
2201
|
+
var l;
|
|
2202
|
+
if (t.defaultPrevented || t.metaKey || t.ctrlKey || t.altKey || t.key.length !== 1)
|
|
2203
|
+
return;
|
|
2204
|
+
const n = window.getSelection();
|
|
2205
|
+
if (!n || !n.isCollapsed || n.rangeCount === 0)
|
|
2206
|
+
return;
|
|
2207
|
+
const r = n.getRangeAt(0);
|
|
2208
|
+
if (r.startContainer.nodeType !== Node.TEXT_NODE)
|
|
2209
|
+
return;
|
|
2210
|
+
const s = r.startContainer, o = (l = s.textContent) != null ? l : "";
|
|
2211
|
+
if (o.length === 0 || r.startOffset !== 0)
|
|
2212
|
+
return;
|
|
2213
|
+
const a = s.previousSibling;
|
|
2214
|
+
i.isBoldElement(a) && /^\s/.test(o) && i.setCaret(n, s, o.length);
|
|
2215
|
+
}
|
|
2216
|
+
/**
|
|
2217
|
+
* Determine whether a node is a bold element (<strong>/<b>)
|
|
2218
|
+
* @param node - Node to inspect
|
|
2219
|
+
*/
|
|
2220
|
+
static isBoldElement(t) {
|
|
2221
|
+
return !!(t && t.nodeType === Node.ELEMENT_NODE && i.isBoldTag(t));
|
|
2222
|
+
}
|
|
2223
|
+
/**
|
|
2224
|
+
* Place caret at the provided offset within a text node
|
|
2225
|
+
* @param selection - Current selection
|
|
2226
|
+
* @param node - Target text node
|
|
2227
|
+
* @param offset - Offset within the text node
|
|
2228
|
+
*/
|
|
2229
|
+
static setCaret(t, e, n) {
|
|
2230
|
+
const r = document.createRange();
|
|
2231
|
+
r.setStart(e, n), r.collapse(!0), t.removeAllRanges(), t.addRange(r);
|
|
2232
|
+
}
|
|
2233
|
+
/**
|
|
2234
|
+
* Position caret immediately after the provided node
|
|
2235
|
+
* @param selection - Current selection
|
|
2236
|
+
* @param node - Reference node
|
|
2237
|
+
*/
|
|
2238
|
+
static setCaretAfterNode(t, e) {
|
|
2239
|
+
if (!e)
|
|
2240
|
+
return;
|
|
2241
|
+
const n = document.createRange();
|
|
2242
|
+
n.setStartAfter(e), n.collapse(!0), t.removeAllRanges(), t.addRange(n);
|
|
2243
|
+
}
|
|
2244
|
+
/**
|
|
2245
|
+
* Ensure there is a text node immediately following a bold element to accept new input
|
|
2246
|
+
* @param boldElement - Bold element after which text should be inserted
|
|
2247
|
+
* @param referenceNode - Node that currently follows the bold element
|
|
2248
|
+
*/
|
|
2249
|
+
static ensureFollowingTextNode(t, e) {
|
|
2250
|
+
const n = t.parentNode;
|
|
2251
|
+
if (!n)
|
|
2252
|
+
return null;
|
|
2253
|
+
if (e && e.nodeType === Node.TEXT_NODE)
|
|
2254
|
+
return e;
|
|
2255
|
+
const r = document.createTextNode("");
|
|
2256
|
+
return n.insertBefore(r, e), r;
|
|
2257
|
+
}
|
|
2258
|
+
/**
|
|
2259
|
+
* Enforce length limits on collapsed bold elements
|
|
2260
|
+
* @param selection - The current selection to determine the blok context
|
|
2261
|
+
*/
|
|
2262
|
+
static enforceCollapsedBoldLengths(t) {
|
|
2263
|
+
var o;
|
|
2264
|
+
const e = (o = t == null ? void 0 : t.anchorNode) != null ? o : t == null ? void 0 : t.focusNode;
|
|
2265
|
+
if (!e)
|
|
2266
|
+
return;
|
|
2267
|
+
const n = e.nodeType === Node.ELEMENT_NODE ? e : e.parentElement, r = n == null ? void 0 : n.closest(A(g.editor));
|
|
2268
|
+
if (!r)
|
|
2269
|
+
return;
|
|
2270
|
+
r.querySelectorAll(`strong[${i.DATA_ATTR_COLLAPSED_LENGTH}]`).forEach((a) => {
|
|
2271
|
+
var x, v, b, S, _;
|
|
2272
|
+
const l = a, d = l.getAttribute(i.DATA_ATTR_COLLAPSED_LENGTH);
|
|
2273
|
+
if (!d)
|
|
2274
|
+
return;
|
|
2275
|
+
const c = Number(d), h = (x = l.textContent) != null ? x : "";
|
|
2276
|
+
if (!Number.isFinite(c))
|
|
2277
|
+
return;
|
|
2278
|
+
const p = h.length > c, T = p ? i.splitCollapsedBoldText(l, c, h) : null, y = l.getAttribute(i.DATA_ATTR_PREV_LENGTH), m = y ? Number(y) : NaN, C = l.previousSibling, E = (C == null ? void 0 : C.nodeType) === Node.TEXT_NODE ? C : null, N = (v = E == null ? void 0 : E.textContent) != null ? v : "", B = !!(y && Number.isFinite(m) && E && N.length > m);
|
|
2279
|
+
if (B && E) {
|
|
2280
|
+
const k = N.slice(0, m), R = N.slice(m);
|
|
2281
|
+
E.textContent = k;
|
|
2282
|
+
const F = document.createTextNode(R);
|
|
2283
|
+
(b = l.parentNode) == null || b.insertBefore(F, l.nextSibling);
|
|
2284
|
+
}
|
|
2285
|
+
if (B && l.removeAttribute(i.DATA_ATTR_PREV_LENGTH), t != null && t.isCollapsed && T && i.isNodeWithin(t.focusNode, l)) {
|
|
2286
|
+
const k = document.createRange(), R = (_ = (S = T.textContent) == null ? void 0 : S.length) != null ? _ : 0;
|
|
2287
|
+
k.setStart(T, R), k.collapse(!0), t.removeAllRanges(), t.addRange(k);
|
|
2288
|
+
}
|
|
2289
|
+
p && l.removeAttribute(i.DATA_ATTR_COLLAPSED_LENGTH);
|
|
2290
|
+
});
|
|
2291
|
+
}
|
|
2292
|
+
/**
|
|
2293
|
+
* Split text content exceeding the allowed collapsed bold length and move the excess outside
|
|
2294
|
+
* @param boldEl - Bold element hosting the collapsed selection
|
|
2295
|
+
* @param allowedLength - Maximum allowed length for the collapsed bold
|
|
2296
|
+
* @param currentText - Current text content inside the bold element
|
|
2297
|
+
*/
|
|
2298
|
+
static splitCollapsedBoldText(t, e, n) {
|
|
2299
|
+
const r = t, s = r.parentNode;
|
|
2300
|
+
if (!s)
|
|
2301
|
+
return null;
|
|
2302
|
+
const o = n.slice(0, e), a = n.slice(e);
|
|
2303
|
+
r.textContent = o;
|
|
2304
|
+
const l = document.createTextNode(a);
|
|
2305
|
+
return s.insertBefore(l, r.nextSibling), l;
|
|
2306
|
+
}
|
|
2307
|
+
/**
|
|
2308
|
+
* Check if a node is within the provided container
|
|
2309
|
+
* @param target - Node to test
|
|
2310
|
+
* @param container - Potential ancestor container
|
|
2311
|
+
*/
|
|
2312
|
+
static isNodeWithin(t, e) {
|
|
2313
|
+
return t ? t === e || e.contains(t) : !1;
|
|
2314
|
+
}
|
|
2315
|
+
/**
|
|
2316
|
+
*
|
|
2317
|
+
*/
|
|
2318
|
+
static handleGlobalSelectionChange() {
|
|
2319
|
+
i.refreshSelectionState("selectionchange");
|
|
2320
|
+
}
|
|
2321
|
+
/**
|
|
2322
|
+
*
|
|
2323
|
+
*/
|
|
2324
|
+
static handleGlobalInput() {
|
|
2325
|
+
i.refreshSelectionState("input");
|
|
2326
|
+
}
|
|
2327
|
+
/**
|
|
2328
|
+
* Normalize selection state after blok input or selection updates
|
|
2329
|
+
* @param source - The event source triggering the refresh
|
|
2330
|
+
*/
|
|
2331
|
+
static refreshSelectionState(t) {
|
|
2332
|
+
const e = window.getSelection();
|
|
2333
|
+
i.enforceCollapsedBoldLengths(e), i.maintainCollapsedExitState(), i.synchronizeCollapsedBold(e), i.normalizeBoldTagsWithinBlok(e), i.removeEmptyBoldElements(e), t === "input" && e && i.moveCaretAfterBoundaryBold(e), i.normalizeAllBoldTags();
|
|
2334
|
+
}
|
|
2335
|
+
/**
|
|
2336
|
+
* Ensure mutation observer is registered to convert legacy <b> tags
|
|
2337
|
+
*/
|
|
2338
|
+
static ensureMutationObserver() {
|
|
2339
|
+
if (typeof MutationObserver == "undefined" || i.mutationObserver)
|
|
2340
|
+
return;
|
|
2341
|
+
const t = new MutationObserver((e) => {
|
|
2342
|
+
if (!i.isProcessingMutation) {
|
|
2343
|
+
i.isProcessingMutation = !0;
|
|
2344
|
+
try {
|
|
2345
|
+
e.forEach((n) => {
|
|
2346
|
+
n.addedNodes.forEach((r) => {
|
|
2347
|
+
i.normalizeBoldInNode(r);
|
|
2348
|
+
}), n.type === "characterData" && n.target && i.normalizeBoldInNode(n.target);
|
|
2349
|
+
});
|
|
2350
|
+
} finally {
|
|
2351
|
+
i.isProcessingMutation = !1;
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2354
|
+
});
|
|
2355
|
+
t.observe(document.body, {
|
|
2356
|
+
subtree: !0,
|
|
2357
|
+
childList: !0,
|
|
2358
|
+
characterData: !0
|
|
2359
|
+
}), i.mutationObserver = t;
|
|
2360
|
+
}
|
|
2361
|
+
/**
|
|
2362
|
+
* Prevent the browser's native bold command to avoid <b> wrappers
|
|
2363
|
+
* @param event - BeforeInput event fired by the browser
|
|
2364
|
+
*/
|
|
2365
|
+
static handleBeforeInput(t) {
|
|
2366
|
+
if (t.inputType !== "formatBold")
|
|
2367
|
+
return;
|
|
2368
|
+
const e = window.getSelection(), n = !!(e && i.isSelectionInsideBlok(e)), r = i.isEventTargetInsideBlok(t.target);
|
|
2369
|
+
!n && !r || (t.preventDefault(), t.stopPropagation(), t.stopImmediatePropagation(), i.normalizeAllBoldTags());
|
|
2370
|
+
}
|
|
2371
|
+
/**
|
|
2372
|
+
* Attempt to toggle bold via the browser's native command
|
|
2373
|
+
* @param selection - Current selection
|
|
2374
|
+
*/
|
|
2375
|
+
/**
|
|
2376
|
+
* Exit a collapsed bold selection by moving the caret outside the bold element
|
|
2377
|
+
* @param selection - The current selection
|
|
2378
|
+
* @param boldElement - The bold element to exit from
|
|
2379
|
+
*/
|
|
2380
|
+
static exitCollapsedBold(t, e) {
|
|
2381
|
+
const n = i.ensureStrongElement(e), r = n.parentNode;
|
|
2382
|
+
if (r)
|
|
2383
|
+
return i.isElementEmpty(n) ? i.removeEmptyBoldElement(t, n, r) : i.exitCollapsedBoldWithContent(t, n, r);
|
|
2384
|
+
}
|
|
2385
|
+
/**
|
|
2386
|
+
* Remove an empty bold element and place the caret before its position
|
|
2387
|
+
* @param selection - Current selection
|
|
2388
|
+
* @param boldElement - Bold element to remove
|
|
2389
|
+
* @param parent - Parent node that hosts the bold element
|
|
2390
|
+
*/
|
|
2391
|
+
static removeEmptyBoldElement(t, e, n) {
|
|
2392
|
+
const r = document.createRange();
|
|
2393
|
+
return r.setStartBefore(e), r.collapse(!0), n.removeChild(e), t.removeAllRanges(), t.addRange(r), r;
|
|
2394
|
+
}
|
|
2395
|
+
/**
|
|
2396
|
+
* Exit a collapsed bold state when the bold element still contains text
|
|
2397
|
+
* @param selection - Current selection
|
|
2398
|
+
* @param boldElement - Bold element currently wrapping the caret
|
|
2399
|
+
* @param parent - Parent node that hosts the bold element
|
|
2400
|
+
*/
|
|
2401
|
+
static exitCollapsedBoldWithContent(t, e, n) {
|
|
2402
|
+
var p, T, y, m, C, E;
|
|
2403
|
+
e.setAttribute(i.DATA_ATTR_COLLAPSED_LENGTH, ((T = (p = e.textContent) == null ? void 0 : p.length) != null ? T : 0).toString()), e.removeAttribute(i.DATA_ATTR_PREV_LENGTH), e.removeAttribute(i.DATA_ATTR_COLLAPSED_ACTIVE), e.removeAttribute(i.DATA_ATTR_LEADING_WHITESPACE);
|
|
2404
|
+
const r = e.nextSibling, s = !r || r.nodeType !== Node.TEXT_NODE, o = s ? document.createTextNode("") : null;
|
|
2405
|
+
o && n.insertBefore(o, r);
|
|
2406
|
+
const a = o != null ? o : r;
|
|
2407
|
+
!s && ((y = a.textContent) != null ? y : "").length === 0 && (a.textContent = "");
|
|
2408
|
+
const l = document.createRange(), c = ((m = a.textContent) != null ? m : "").startsWith("") ? 1 : 0;
|
|
2409
|
+
l.setStart(a, c), l.collapse(!0), t.removeAllRanges(), t.addRange(l);
|
|
2410
|
+
const h = i.ensureStrongElement(e);
|
|
2411
|
+
return i.collapsedExitRecords.add({
|
|
2412
|
+
boundary: a,
|
|
2413
|
+
boldElement: h,
|
|
2414
|
+
allowedLength: (E = (C = h.textContent) == null ? void 0 : C.length) != null ? E : 0,
|
|
2415
|
+
hasLeadingSpace: !1,
|
|
2416
|
+
hasTypedContent: !1,
|
|
2417
|
+
leadingWhitespace: ""
|
|
2418
|
+
}), l;
|
|
2419
|
+
}
|
|
2420
|
+
/**
|
|
2421
|
+
* Get a bold element at the boundary of a collapsed range
|
|
2422
|
+
* @param range - The collapsed range to check
|
|
2423
|
+
*/
|
|
2424
|
+
static getBoundaryBold(t) {
|
|
2425
|
+
const e = t.startContainer;
|
|
2426
|
+
return e.nodeType === Node.TEXT_NODE ? i.getBoundaryBoldForText(t, e) : e.nodeType === Node.ELEMENT_NODE ? i.getBoundaryBoldForElement(t, e) : null;
|
|
2427
|
+
}
|
|
2428
|
+
/**
|
|
2429
|
+
* Get boundary bold when caret resides inside a text node
|
|
2430
|
+
* @param range - Collapsed range
|
|
2431
|
+
* @param textNode - Text container
|
|
2432
|
+
*/
|
|
2433
|
+
static getBoundaryBoldForText(t, e) {
|
|
2434
|
+
var s, o;
|
|
2435
|
+
const n = (o = (s = e.textContent) == null ? void 0 : s.length) != null ? o : 0;
|
|
2436
|
+
if (t.startOffset === n)
|
|
2437
|
+
return i.findBoldElement(e);
|
|
2438
|
+
if (t.startOffset !== 0)
|
|
2439
|
+
return null;
|
|
2440
|
+
const r = e.previousSibling;
|
|
2441
|
+
return i.isBoldElement(r) ? r : null;
|
|
2442
|
+
}
|
|
2443
|
+
/**
|
|
2444
|
+
* Get boundary bold when caret container is an element
|
|
2445
|
+
* @param range - Collapsed range
|
|
2446
|
+
* @param element - Element container
|
|
2447
|
+
*/
|
|
2448
|
+
static getBoundaryBoldForElement(t, e) {
|
|
2449
|
+
if (t.startOffset <= 0)
|
|
2450
|
+
return null;
|
|
2451
|
+
const n = e.childNodes[t.startOffset - 1];
|
|
2452
|
+
return i.isBoldElement(n) ? n : null;
|
|
2453
|
+
}
|
|
2454
|
+
/**
|
|
2455
|
+
* Handle keyboard shortcut for bold when selection is collapsed
|
|
2456
|
+
* @param event - The keyboard event
|
|
2457
|
+
*/
|
|
2458
|
+
static handleShortcut(t) {
|
|
2459
|
+
var r;
|
|
2460
|
+
if (i.guardCollapsedBoundaryKeydown(t), !i.isBoldShortcut(t))
|
|
2461
|
+
return;
|
|
2462
|
+
const e = window.getSelection();
|
|
2463
|
+
if (!e || !e.rangeCount || !i.isSelectionInsideBlok(e))
|
|
2464
|
+
return;
|
|
2465
|
+
const n = (r = i.instances.values().next().value) != null ? r : new i();
|
|
2466
|
+
n && (t.preventDefault(), t.stopPropagation(), t.stopImmediatePropagation(), n.toggleBold());
|
|
2467
|
+
}
|
|
2468
|
+
/**
|
|
2469
|
+
* Check if a keyboard event is the bold shortcut (Cmd/Ctrl+B)
|
|
2470
|
+
* @param event - The keyboard event to check
|
|
2471
|
+
*/
|
|
2472
|
+
static isBoldShortcut(t) {
|
|
2473
|
+
return !((typeof navigator != "undefined" ? navigator.userAgent.toLowerCase() : "").includes("mac") ? t.metaKey : t.ctrlKey) || t.altKey ? !1 : t.key.toLowerCase() === "b";
|
|
2474
|
+
}
|
|
2475
|
+
/**
|
|
2476
|
+
* Check if a selection is inside the blok
|
|
2477
|
+
* @param selection - The selection to check
|
|
2478
|
+
*/
|
|
2479
|
+
static isSelectionInsideBlok(t) {
|
|
2480
|
+
const e = t.anchorNode;
|
|
2481
|
+
if (!e)
|
|
2482
|
+
return !1;
|
|
2483
|
+
const n = e.nodeType === Node.ELEMENT_NODE ? e : e.parentElement;
|
|
2484
|
+
return !!(n != null && n.closest(A(g.editor)));
|
|
2485
|
+
}
|
|
2486
|
+
/**
|
|
2487
|
+
* Check if an event target resides inside the blok wrapper
|
|
2488
|
+
* @param target - Event target to inspect
|
|
2489
|
+
*/
|
|
2490
|
+
static isEventTargetInsideBlok(t) {
|
|
2491
|
+
var n;
|
|
2492
|
+
if (!t || typeof Node == "undefined")
|
|
2493
|
+
return !1;
|
|
2494
|
+
if (t instanceof Element)
|
|
2495
|
+
return !!t.closest(A(g.editor));
|
|
2496
|
+
if (t instanceof Text)
|
|
2497
|
+
return !!((n = t.parentElement) != null && n.closest(A(g.editor)));
|
|
2498
|
+
if (typeof ShadowRoot != "undefined" && t instanceof ShadowRoot)
|
|
2499
|
+
return i.isEventTargetInsideBlok(t.host);
|
|
2500
|
+
if (!(t instanceof Node))
|
|
2501
|
+
return !1;
|
|
2502
|
+
const e = t.parentNode;
|
|
2503
|
+
return e ? e instanceof Element ? !!e.closest(A(g.editor)) : i.isEventTargetInsideBlok(e) : !1;
|
|
2504
|
+
}
|
|
2505
|
+
/**
|
|
2506
|
+
* Get HTML content of a range with bold tags removed
|
|
2507
|
+
* @param range - The range to extract HTML from
|
|
2508
|
+
*/
|
|
2509
|
+
getRangeHtmlWithoutBold(t) {
|
|
2510
|
+
const e = t.cloneContents();
|
|
2511
|
+
this.removeNestedBold(e);
|
|
2512
|
+
const n = document.createElement("div");
|
|
2513
|
+
return n.appendChild(e), n.innerHTML;
|
|
2514
|
+
}
|
|
2515
|
+
/**
|
|
2516
|
+
* Convert an HTML snippet to a document fragment
|
|
2517
|
+
* @param html - HTML string to convert
|
|
2518
|
+
*/
|
|
2519
|
+
static createFragmentFromHtml(t) {
|
|
2520
|
+
const e = document.createElement("template");
|
|
2521
|
+
return e.innerHTML = t, e.content;
|
|
2522
|
+
}
|
|
2523
|
+
/**
|
|
2524
|
+
* Collect all bold ancestor elements within a range
|
|
2525
|
+
* @param range - The range to search for bold ancestors
|
|
2526
|
+
*/
|
|
2527
|
+
collectBoldAncestors(t) {
|
|
2528
|
+
const e = /* @__PURE__ */ new Set(), n = document.createTreeWalker(
|
|
2529
|
+
t.commonAncestorContainer,
|
|
2530
|
+
NodeFilter.SHOW_TEXT,
|
|
2531
|
+
{
|
|
2532
|
+
acceptNode: (r) => {
|
|
2533
|
+
try {
|
|
2534
|
+
return t.intersectsNode(r) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
|
|
2535
|
+
} catch (s) {
|
|
2536
|
+
const o = document.createRange();
|
|
2537
|
+
o.selectNodeContents(r);
|
|
2538
|
+
const a = t.compareBoundaryPoints(Range.END_TO_START, o) > 0, l = t.compareBoundaryPoints(Range.START_TO_END, o) < 0;
|
|
2539
|
+
return a && l ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
|
|
2540
|
+
}
|
|
2541
|
+
}
|
|
2542
|
+
}
|
|
2543
|
+
);
|
|
2544
|
+
for (; n.nextNode(); ) {
|
|
2545
|
+
const r = i.findBoldElement(n.currentNode);
|
|
2546
|
+
r && e.add(r);
|
|
2547
|
+
}
|
|
2548
|
+
return Array.from(e);
|
|
2549
|
+
}
|
|
2550
|
+
};
|
|
2551
|
+
i.isInline = !0, i.title = "Bold", i.titleKey = "bold", i.shortcutListenerRegistered = !1, i.selectionListenerRegistered = !1, i.inputListenerRegistered = !1, i.beforeInputListenerRegistered = !1, i.globalListenersInitialized = i.initializeGlobalListeners(), i.collapsedExitRecords = /* @__PURE__ */ new Set(), i.markerSequence = 0, i.isProcessingMutation = !1, i.DATA_ATTR_COLLAPSED_LENGTH = "data-blok-bold-collapsed-length", i.DATA_ATTR_COLLAPSED_ACTIVE = "data-blok-bold-collapsed-active", i.DATA_ATTR_PREV_LENGTH = "data-blok-bold-prev-length", i.DATA_ATTR_LEADING_WHITESPACE = "data-blok-bold-leading-ws", i.instances = /* @__PURE__ */ new Set(), i.pendingBoundaryCaretAdjustments = /* @__PURE__ */ new WeakSet(), i.shortcut = "CMD+B";
|
|
2552
|
+
let Z = i;
|
|
2553
|
+
const O = class O {
|
|
2554
|
+
/**
|
|
2555
|
+
* Sanitizer Rule
|
|
2556
|
+
* Leave <i> and <em> tags
|
|
2557
|
+
* @returns {object}
|
|
2558
|
+
*/
|
|
2559
|
+
static get sanitize() {
|
|
2560
|
+
return {
|
|
2561
|
+
i: {},
|
|
2562
|
+
em: {}
|
|
2563
|
+
};
|
|
2564
|
+
}
|
|
2565
|
+
/**
|
|
2566
|
+
* Create button for Inline Toolbar
|
|
2567
|
+
*/
|
|
2568
|
+
render() {
|
|
2569
|
+
return {
|
|
2570
|
+
icon: mt,
|
|
2571
|
+
name: "italic",
|
|
2572
|
+
onActivate: () => {
|
|
2573
|
+
this.toggleItalic();
|
|
2574
|
+
},
|
|
2575
|
+
isActive: () => {
|
|
2576
|
+
const t = window.getSelection();
|
|
2577
|
+
return t ? this.isSelectionVisuallyItalic(t) : !1;
|
|
2578
|
+
}
|
|
2579
|
+
};
|
|
2580
|
+
}
|
|
2581
|
+
/**
|
|
2582
|
+
* Apply or remove italic formatting using modern Selection API
|
|
2583
|
+
*/
|
|
2584
|
+
toggleItalic() {
|
|
2585
|
+
const t = window.getSelection();
|
|
2586
|
+
if (!t || t.rangeCount === 0)
|
|
2587
|
+
return;
|
|
2588
|
+
const e = t.getRangeAt(0);
|
|
2589
|
+
if (e.collapsed) {
|
|
2590
|
+
this.toggleCollapsedItalic(e, t);
|
|
2591
|
+
return;
|
|
2592
|
+
}
|
|
2593
|
+
this.isRangeItalic(e, { ignoreWhitespace: !0 }) ? this.unwrapItalicTags(e) : this.wrapWithItalic(e);
|
|
2594
|
+
}
|
|
2595
|
+
/**
|
|
2596
|
+
* Handle toggle for collapsed selection (caret)
|
|
2597
|
+
* @param range - Current range
|
|
2598
|
+
* @param selection - Current selection
|
|
2599
|
+
*/
|
|
2600
|
+
toggleCollapsedItalic(t, e) {
|
|
2601
|
+
if (this.isRangeItalic(t, { ignoreWhitespace: !0 })) {
|
|
2602
|
+
const r = document.createTextNode("");
|
|
2603
|
+
t.insertNode(r), t.selectNode(r), this.unwrapItalicTags(t);
|
|
2604
|
+
const s = document.createRange();
|
|
2605
|
+
s.setStart(r, 1), s.setEnd(r, 1), e.removeAllRanges(), e.addRange(s);
|
|
2606
|
+
} else {
|
|
2607
|
+
const r = document.createElement("i"), s = document.createTextNode("");
|
|
2608
|
+
r.appendChild(s), t.insertNode(r);
|
|
2609
|
+
const o = document.createRange();
|
|
2610
|
+
o.setStart(s, 1), o.setEnd(s, 1), e.removeAllRanges(), e.addRange(o);
|
|
2611
|
+
}
|
|
2612
|
+
}
|
|
2613
|
+
/**
|
|
2614
|
+
* Check if current selection is within an italic tag
|
|
2615
|
+
* @param selection - The Selection object to check
|
|
2616
|
+
*/
|
|
2617
|
+
isSelectionVisuallyItalic(t) {
|
|
2618
|
+
if (!t || t.rangeCount === 0)
|
|
2619
|
+
return !1;
|
|
2620
|
+
const e = t.getRangeAt(0);
|
|
2621
|
+
return this.isRangeItalic(e, { ignoreWhitespace: !0 });
|
|
2622
|
+
}
|
|
2623
|
+
/**
|
|
2624
|
+
* Check if a range contains italic text
|
|
2625
|
+
* @param range - The range to check
|
|
2626
|
+
* @param options - Options for checking italic status
|
|
2627
|
+
*/
|
|
2628
|
+
isRangeItalic(t, e) {
|
|
2629
|
+
var s;
|
|
2630
|
+
if (t.collapsed)
|
|
2631
|
+
return !!this.findItalicElement(t.startContainer);
|
|
2632
|
+
const n = document.createTreeWalker(
|
|
2633
|
+
t.commonAncestorContainer,
|
|
2634
|
+
NodeFilter.SHOW_TEXT,
|
|
2635
|
+
{
|
|
2636
|
+
acceptNode: (o) => {
|
|
2637
|
+
try {
|
|
2638
|
+
return t.intersectsNode(o) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
|
|
2639
|
+
} catch (a) {
|
|
2640
|
+
const l = document.createRange();
|
|
2641
|
+
l.selectNodeContents(o);
|
|
2642
|
+
const d = t.compareBoundaryPoints(Range.END_TO_START, l) > 0, c = t.compareBoundaryPoints(Range.START_TO_END, l) < 0;
|
|
2643
|
+
return d && c ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
|
|
2644
|
+
}
|
|
2645
|
+
}
|
|
2646
|
+
}
|
|
2647
|
+
), r = [];
|
|
2648
|
+
for (; n.nextNode(); ) {
|
|
2649
|
+
const o = n.currentNode, a = (s = o.textContent) != null ? s : "";
|
|
2650
|
+
e.ignoreWhitespace && a.trim().length === 0 || a.length !== 0 && r.push(o);
|
|
2651
|
+
}
|
|
2652
|
+
return r.length === 0 ? !!this.findItalicElement(t.startContainer) : r.every((o) => this.hasItalicParent(o));
|
|
2653
|
+
}
|
|
2654
|
+
/**
|
|
2655
|
+
* Wrap selection with <i> tag
|
|
2656
|
+
* @param range - The Range object containing the selection to wrap
|
|
2657
|
+
*/
|
|
2658
|
+
wrapWithItalic(t) {
|
|
2659
|
+
const e = this.getRangeHtmlWithoutItalic(t), n = this.replaceRangeWithHtml(t, `<i>${e}</i>`), r = window.getSelection();
|
|
2660
|
+
r && n && (r.removeAllRanges(), r.addRange(n));
|
|
2661
|
+
}
|
|
2662
|
+
/**
|
|
2663
|
+
* Remove italic tags (<i>/<em>) while preserving content
|
|
2664
|
+
* @param range - The Range object containing the selection to unwrap
|
|
2665
|
+
*/
|
|
2666
|
+
unwrapItalicTags(t) {
|
|
2667
|
+
const e = this.collectItalicAncestors(t), n = window.getSelection();
|
|
2668
|
+
if (!n)
|
|
2669
|
+
return;
|
|
2670
|
+
const r = document.createElement("span"), s = t.extractContents();
|
|
2671
|
+
r.appendChild(s), this.removeNestedItalic(r), t.insertNode(r);
|
|
2672
|
+
const o = document.createRange();
|
|
2673
|
+
for (o.selectNodeContents(r), n.removeAllRanges(), n.addRange(o); ; ) {
|
|
2674
|
+
const c = this.findItalicElement(r);
|
|
2675
|
+
if (!c)
|
|
2676
|
+
break;
|
|
2677
|
+
this.moveMarkerOutOfItalic(r, c);
|
|
2678
|
+
}
|
|
2679
|
+
const a = r.firstChild, l = r.lastChild;
|
|
2680
|
+
this.unwrapElement(r), (a && l ? (() => {
|
|
2681
|
+
const c = document.createRange();
|
|
2682
|
+
return c.setStartBefore(a), c.setEndAfter(l), n.removeAllRanges(), n.addRange(c), c;
|
|
2683
|
+
})() : void 0) || n.removeAllRanges(), e.forEach((c) => {
|
|
2684
|
+
var h;
|
|
2685
|
+
((h = c.textContent) != null ? h : "").length === 0 && c.remove();
|
|
2686
|
+
});
|
|
2687
|
+
}
|
|
2688
|
+
/**
|
|
2689
|
+
* Check if a node or any of its parents is an italic tag
|
|
2690
|
+
* @param node - The node to check
|
|
2691
|
+
*/
|
|
2692
|
+
hasItalicParent(t) {
|
|
2693
|
+
return t ? t.nodeType === Node.ELEMENT_NODE && this.isItalicTag(t) ? !0 : this.hasItalicParent(t.parentNode) : !1;
|
|
2694
|
+
}
|
|
2695
|
+
/**
|
|
2696
|
+
* Find an italic element in the parent chain
|
|
2697
|
+
* @param node - The node to start searching from
|
|
2698
|
+
*/
|
|
2699
|
+
findItalicElement(t) {
|
|
2700
|
+
return t ? t.nodeType === Node.ELEMENT_NODE && this.isItalicTag(t) ? t : this.findItalicElement(t.parentNode) : null;
|
|
2701
|
+
}
|
|
2702
|
+
/**
|
|
2703
|
+
* Check if an element is an italic tag (<i> or <em>)
|
|
2704
|
+
* @param node - The element to check
|
|
2705
|
+
*/
|
|
2706
|
+
isItalicTag(t) {
|
|
2707
|
+
const e = t.tagName;
|
|
2708
|
+
return e === "I" || e === "EM";
|
|
2709
|
+
}
|
|
2710
|
+
/**
|
|
2711
|
+
* Collect all italic ancestor elements within a range
|
|
2712
|
+
* @param range - The range to search for italic ancestors
|
|
2713
|
+
*/
|
|
2714
|
+
collectItalicAncestors(t) {
|
|
2715
|
+
const e = /* @__PURE__ */ new Set(), n = document.createTreeWalker(
|
|
2716
|
+
t.commonAncestorContainer,
|
|
2717
|
+
NodeFilter.SHOW_TEXT,
|
|
2718
|
+
{
|
|
2719
|
+
acceptNode: (r) => {
|
|
2720
|
+
try {
|
|
2721
|
+
return t.intersectsNode(r) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
|
|
2722
|
+
} catch (s) {
|
|
2723
|
+
const o = document.createRange();
|
|
2724
|
+
o.selectNodeContents(r);
|
|
2725
|
+
const a = t.compareBoundaryPoints(Range.END_TO_START, o) > 0, l = t.compareBoundaryPoints(Range.START_TO_END, o) < 0;
|
|
2726
|
+
return a && l ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
|
|
2727
|
+
}
|
|
2728
|
+
}
|
|
2729
|
+
}
|
|
2730
|
+
);
|
|
2731
|
+
for (; n.nextNode(); ) {
|
|
2732
|
+
const r = this.findItalicElement(n.currentNode);
|
|
2733
|
+
r && e.add(r);
|
|
2734
|
+
}
|
|
2735
|
+
return Array.from(e);
|
|
2736
|
+
}
|
|
2737
|
+
/**
|
|
2738
|
+
* Get HTML content of a range with italic tags removed
|
|
2739
|
+
* @param range - The range to extract HTML from
|
|
2740
|
+
*/
|
|
2741
|
+
getRangeHtmlWithoutItalic(t) {
|
|
2742
|
+
const e = t.cloneContents();
|
|
2743
|
+
this.removeNestedItalic(e);
|
|
2744
|
+
const n = document.createElement("div");
|
|
2745
|
+
return n.appendChild(e), n.innerHTML;
|
|
2746
|
+
}
|
|
2747
|
+
/**
|
|
2748
|
+
* Remove nested italic tags from a root node
|
|
2749
|
+
* @param root - The root node to process
|
|
2750
|
+
*/
|
|
2751
|
+
removeNestedItalic(t) {
|
|
2752
|
+
var n;
|
|
2753
|
+
const e = (n = t.querySelectorAll) == null ? void 0 : n.call(t, "i,em");
|
|
2754
|
+
e && e.forEach((r) => {
|
|
2755
|
+
this.unwrapElement(r);
|
|
2756
|
+
});
|
|
2757
|
+
}
|
|
2758
|
+
/**
|
|
2759
|
+
* Unwrap an element by moving its children to the parent
|
|
2760
|
+
* @param element - The element to unwrap
|
|
2761
|
+
*/
|
|
2762
|
+
unwrapElement(t) {
|
|
2763
|
+
const e = t.parentNode;
|
|
2764
|
+
if (!e) {
|
|
2765
|
+
t.remove();
|
|
2766
|
+
return;
|
|
2767
|
+
}
|
|
2768
|
+
for (; t.firstChild; )
|
|
2769
|
+
e.insertBefore(t.firstChild, t);
|
|
2770
|
+
e.removeChild(t);
|
|
2771
|
+
}
|
|
2772
|
+
/**
|
|
2773
|
+
* Replace the current range contents with provided HTML snippet
|
|
2774
|
+
* @param range - Range to replace
|
|
2775
|
+
* @param html - HTML string to insert
|
|
2776
|
+
*/
|
|
2777
|
+
replaceRangeWithHtml(t, e) {
|
|
2778
|
+
var a, l;
|
|
2779
|
+
const n = this.createFragmentFromHtml(e), r = (a = n.firstChild) != null ? a : null, s = (l = n.lastChild) != null ? l : null;
|
|
2780
|
+
if (t.deleteContents(), !r || !s)
|
|
2781
|
+
return;
|
|
2782
|
+
t.insertNode(n);
|
|
2783
|
+
const o = document.createRange();
|
|
2784
|
+
return o.setStartBefore(r), o.setEndAfter(s), o;
|
|
2785
|
+
}
|
|
2786
|
+
/**
|
|
2787
|
+
* Convert an HTML snippet to a document fragment
|
|
2788
|
+
* @param html - HTML string to convert
|
|
2789
|
+
*/
|
|
2790
|
+
createFragmentFromHtml(t) {
|
|
2791
|
+
const e = document.createElement("template");
|
|
2792
|
+
return e.innerHTML = t, e.content;
|
|
2793
|
+
}
|
|
2794
|
+
/**
|
|
2795
|
+
* Move a temporary marker element outside of an italic ancestor while preserving content order
|
|
2796
|
+
* @param marker - Marker element wrapping the selection contents
|
|
2797
|
+
* @param italicElement - Italic ancestor containing the marker
|
|
2798
|
+
*/
|
|
2799
|
+
moveMarkerOutOfItalic(t, e) {
|
|
2800
|
+
const n = e.parentNode;
|
|
2801
|
+
if (!n)
|
|
2802
|
+
return;
|
|
2803
|
+
if (Array.from(e.childNodes).forEach((l) => {
|
|
2804
|
+
var d;
|
|
2805
|
+
l.nodeType === Node.TEXT_NODE && ((d = l.textContent) != null ? d : "").length === 0 && l.remove();
|
|
2806
|
+
}), e.childNodes.length === 1 && e.firstChild === t) {
|
|
2807
|
+
e.replaceWith(t);
|
|
2808
|
+
return;
|
|
2809
|
+
}
|
|
2810
|
+
if (e.firstChild === t) {
|
|
2811
|
+
n.insertBefore(t, e);
|
|
2812
|
+
return;
|
|
2813
|
+
}
|
|
2814
|
+
if (e.lastChild === t) {
|
|
2815
|
+
n.insertBefore(t, e.nextSibling);
|
|
2816
|
+
return;
|
|
2817
|
+
}
|
|
2818
|
+
const a = e.cloneNode(!1);
|
|
2819
|
+
for (; t.nextSibling; )
|
|
2820
|
+
a.appendChild(t.nextSibling);
|
|
2821
|
+
n.insertBefore(a, e.nextSibling), n.insertBefore(t, a);
|
|
2822
|
+
}
|
|
2823
|
+
};
|
|
2824
|
+
O.isInline = !0, O.title = "Italic", O.titleKey = "italic", O.shortcut = "CMD+I";
|
|
2825
|
+
let Q = O;
|
|
2826
|
+
const D = class D {
|
|
2827
|
+
/**
|
|
2828
|
+
* @param api - Blok API
|
|
2829
|
+
*/
|
|
2830
|
+
constructor({ api: t }) {
|
|
2831
|
+
this.INPUT_BASE_CLASSES = "hidden w-full m-0 px-2 py-1 text-sm leading-[22px] font-medium bg-item-hover-bg border border-[rgba(226,226,229,0.2)] rounded-md outline-none box-border appearance-none font-[inherit] placeholder:text-gray-text mobile:text-[15px] mobile:font-medium", this.DATA_ATTRIBUTES = {
|
|
2832
|
+
buttonActive: "data-blok-link-tool-active",
|
|
2833
|
+
buttonUnlink: "data-blok-link-tool-unlink",
|
|
2834
|
+
inputOpened: "data-blok-link-tool-input-opened"
|
|
2835
|
+
}, this.nodes = {
|
|
2836
|
+
input: null,
|
|
2837
|
+
button: null
|
|
2838
|
+
}, this.inputOpened = !1, this.unlinkAvailable = !1, this.handleButtonClick = (e) => {
|
|
2839
|
+
!this.inputOpened || !this.unlinkAvailable || (e.preventDefault(), e.stopPropagation(), e.stopImmediatePropagation(), this.restoreSelection(), this.unlink(), this.inlineToolbar.close());
|
|
2840
|
+
}, this.toolbar = t.toolbar, this.inlineToolbar = t.inlineToolbar, this.notifier = t.notifier, this.i18n = t.i18n, this.selection = new H(), this.nodes.input = this.createInput();
|
|
2841
|
+
}
|
|
2842
|
+
/**
|
|
2843
|
+
* Sanitizer Rule
|
|
2844
|
+
* Leave <a> tags
|
|
2845
|
+
* @returns {object}
|
|
2846
|
+
*/
|
|
2847
|
+
static get sanitize() {
|
|
2848
|
+
return {
|
|
2849
|
+
a: {
|
|
2850
|
+
href: !0,
|
|
2851
|
+
target: "_blank",
|
|
2852
|
+
rel: "nofollow"
|
|
2853
|
+
}
|
|
2854
|
+
};
|
|
2855
|
+
}
|
|
2856
|
+
/**
|
|
2857
|
+
* Create button for Inline Toolbar
|
|
2858
|
+
*/
|
|
2859
|
+
render() {
|
|
2860
|
+
return {
|
|
2861
|
+
icon: Tt,
|
|
2862
|
+
name: "link",
|
|
2863
|
+
isActive: () => !!this.selection.findParentTag("A"),
|
|
2864
|
+
children: {
|
|
2865
|
+
hideChevron: !0,
|
|
2866
|
+
items: [
|
|
2867
|
+
{
|
|
2868
|
+
type: Et.Html,
|
|
2869
|
+
element: this.nodes.input
|
|
2870
|
+
}
|
|
2871
|
+
],
|
|
2872
|
+
onOpen: () => {
|
|
2873
|
+
this.openActions(!0);
|
|
2874
|
+
},
|
|
2875
|
+
onClose: () => {
|
|
2876
|
+
this.closeActions();
|
|
2877
|
+
}
|
|
2878
|
+
}
|
|
2879
|
+
};
|
|
2880
|
+
}
|
|
2881
|
+
/**
|
|
2882
|
+
* Input for the link
|
|
2883
|
+
*/
|
|
2884
|
+
createInput() {
|
|
2885
|
+
const t = document.createElement("input");
|
|
2886
|
+
return t.placeholder = this.i18n.t("tools.link.addLink"), t.enterKeyHint = "done", t.className = this.INPUT_BASE_CLASSES, t.setAttribute("data-blok-testid", "inline-tool-input"), this.setBooleanStateAttribute(t, this.DATA_ATTRIBUTES.inputOpened, !1), t.addEventListener("keydown", (e) => {
|
|
2887
|
+
e.key === "Enter" && this.enterPressed(e);
|
|
2888
|
+
}), t;
|
|
2889
|
+
}
|
|
2890
|
+
/**
|
|
2891
|
+
* @param {boolean} needFocus - on link creation we need to focus input. On editing - nope.
|
|
2892
|
+
*/
|
|
2893
|
+
openActions(t = !1) {
|
|
2894
|
+
if (!this.nodes.input)
|
|
2895
|
+
return;
|
|
2896
|
+
const e = this.selection.findParentTag("A"), n = !!e;
|
|
2897
|
+
if (this.updateButtonStateAttributes(n), this.unlinkAvailable = n, e) {
|
|
2898
|
+
const r = e.getAttribute("href");
|
|
2899
|
+
this.nodes.input.value = r !== null ? r : "";
|
|
2900
|
+
} else
|
|
2901
|
+
this.nodes.input.value = "";
|
|
2902
|
+
this.nodes.input.className = L(this.INPUT_BASE_CLASSES, "block"), this.setBooleanStateAttribute(this.nodes.input, this.DATA_ATTRIBUTES.inputOpened, !0), this.selection.setFakeBackground(), this.selection.save(), t && this.focusInputWithRetry(), this.inputOpened = !0;
|
|
2903
|
+
}
|
|
2904
|
+
/**
|
|
2905
|
+
* Ensures the link input receives focus even if other listeners steal it
|
|
2906
|
+
*/
|
|
2907
|
+
focusInputWithRetry() {
|
|
2908
|
+
this.nodes.input && (this.nodes.input.focus(), !(typeof window == "undefined" || typeof document == "undefined") && window.setTimeout(() => {
|
|
2909
|
+
var t;
|
|
2910
|
+
document.activeElement !== this.nodes.input && ((t = this.nodes.input) == null || t.focus());
|
|
2911
|
+
}, 0));
|
|
2912
|
+
}
|
|
2913
|
+
/**
|
|
2914
|
+
* Resolve the current inline toolbar button element
|
|
2915
|
+
*/
|
|
2916
|
+
getButtonElement() {
|
|
2917
|
+
const t = document.querySelector(
|
|
2918
|
+
`${A(g.interface, At)} [data-blok-item-name="link"]`
|
|
2919
|
+
);
|
|
2920
|
+
return t && t !== this.nodes.button && (t.addEventListener("click", this.handleButtonClick, !0), this.nodes.button = t), t;
|
|
2921
|
+
}
|
|
2922
|
+
/**
|
|
2923
|
+
* Update button state attributes for e2e hooks
|
|
2924
|
+
* @param hasAnchor - Optional override for anchor presence
|
|
2925
|
+
*/
|
|
2926
|
+
updateButtonStateAttributes(t) {
|
|
2927
|
+
const e = this.getButtonElement();
|
|
2928
|
+
if (!e)
|
|
2929
|
+
return;
|
|
2930
|
+
const n = typeof t == "boolean" ? t : !!this.selection.findParentTag("A");
|
|
2931
|
+
this.setBooleanStateAttribute(e, this.DATA_ATTRIBUTES.buttonActive, n), this.setBooleanStateAttribute(e, this.DATA_ATTRIBUTES.buttonUnlink, n);
|
|
2932
|
+
}
|
|
2933
|
+
/**
|
|
2934
|
+
* Close input
|
|
2935
|
+
* @param {boolean} clearSavedSelection — we don't need to clear saved selection
|
|
2936
|
+
* on toggle-clicks on the icon of opened Toolbar
|
|
2937
|
+
*/
|
|
2938
|
+
closeActions(t = !0) {
|
|
2939
|
+
(this.selection.isFakeBackgroundEnabled || t && this.selection.savedSelectionRange) && this.restoreSelection(), this.nodes.input && (this.nodes.input.className = this.INPUT_BASE_CLASSES, this.setBooleanStateAttribute(this.nodes.input, this.DATA_ATTRIBUTES.inputOpened, !1), this.nodes.input.value = "", this.updateButtonStateAttributes(!1), this.unlinkAvailable = !1, t && this.selection.clearSaved(), this.inputOpened = !1);
|
|
2940
|
+
}
|
|
2941
|
+
/**
|
|
2942
|
+
* Restore selection after closing actions
|
|
2943
|
+
*/
|
|
2944
|
+
restoreSelection() {
|
|
2945
|
+
const t = new H(), e = H.isAtBlok;
|
|
2946
|
+
if (e && t.save(), this.selection.removeFakeBackground(), this.selection.restore(), !e && this.selection.savedSelectionRange) {
|
|
2947
|
+
const s = this.selection.savedSelectionRange.commonAncestorContainer, o = s.nodeType === Node.ELEMENT_NODE ? s : s.parentElement;
|
|
2948
|
+
o == null || o.focus();
|
|
2949
|
+
}
|
|
2950
|
+
if (!e)
|
|
2951
|
+
return;
|
|
2952
|
+
t.restore();
|
|
2953
|
+
const n = t.savedSelectionRange;
|
|
2954
|
+
if (n) {
|
|
2955
|
+
const r = n.commonAncestorContainer, s = r.nodeType === Node.ELEMENT_NODE ? r : r.parentElement;
|
|
2956
|
+
s == null || s.focus();
|
|
2957
|
+
}
|
|
2958
|
+
}
|
|
2959
|
+
/**
|
|
2960
|
+
* Enter pressed on input
|
|
2961
|
+
* @param {KeyboardEvent} event - enter keydown event
|
|
2962
|
+
*/
|
|
2963
|
+
enterPressed(t) {
|
|
2964
|
+
if (!this.nodes.input)
|
|
2965
|
+
return;
|
|
2966
|
+
const e = this.nodes.input.value || "";
|
|
2967
|
+
if (!e.trim()) {
|
|
2968
|
+
this.selection.restore(), this.unlink(), t.preventDefault(), this.closeActions(), this.inlineToolbar.close();
|
|
2969
|
+
return;
|
|
2970
|
+
}
|
|
2971
|
+
if (!this.validateURL(e)) {
|
|
2972
|
+
this.notifier.show({
|
|
2973
|
+
message: this.i18n.t("tools.link.invalidLink"),
|
|
2974
|
+
style: "error"
|
|
2975
|
+
}), Ct("Incorrect Link pasted", "warn", e);
|
|
2976
|
+
return;
|
|
2977
|
+
}
|
|
2978
|
+
const n = this.prepareLink(e);
|
|
2979
|
+
this.selection.removeFakeBackground(), this.selection.restore(), this.insertLink(n), t.preventDefault(), t.stopPropagation(), t.stopImmediatePropagation(), this.selection.collapseToEnd(), this.inlineToolbar.close();
|
|
2980
|
+
}
|
|
2981
|
+
/**
|
|
2982
|
+
* Detects if passed string is URL
|
|
2983
|
+
* @param {string} str - string to validate
|
|
2984
|
+
* @returns {boolean}
|
|
2985
|
+
*/
|
|
2986
|
+
validateURL(t) {
|
|
2987
|
+
return !/\s/.test(t);
|
|
2988
|
+
}
|
|
2989
|
+
/**
|
|
2990
|
+
* Process link before injection
|
|
2991
|
+
* - sanitize
|
|
2992
|
+
* - add protocol for links like 'google.com'
|
|
2993
|
+
* @param {string} link - raw user input
|
|
2994
|
+
*/
|
|
2995
|
+
prepareLink(t) {
|
|
2996
|
+
return this.addProtocol(t.trim());
|
|
2997
|
+
}
|
|
2998
|
+
/**
|
|
2999
|
+
* Add 'http' protocol to the links like 'vc.ru', 'google.com'
|
|
3000
|
+
* @param {string} link - string to process
|
|
3001
|
+
*/
|
|
3002
|
+
addProtocol(t) {
|
|
3003
|
+
if (/^(\w+):(\/\/)?/.test(t))
|
|
3004
|
+
return t;
|
|
3005
|
+
const e = /^\/[^/\s]/.test(t), n = t.substring(0, 1) === "#", r = /^\/\/[^/\s]/.test(t);
|
|
3006
|
+
return !e && !n && !r ? "http://" + t : t;
|
|
3007
|
+
}
|
|
3008
|
+
/**
|
|
3009
|
+
* Inserts <a> tag with "href"
|
|
3010
|
+
* @param {string} link - "href" value
|
|
3011
|
+
*/
|
|
3012
|
+
insertLink(t) {
|
|
3013
|
+
const e = this.selection.findParentTag("A");
|
|
3014
|
+
if (e) {
|
|
3015
|
+
this.selection.expandToTag(e), e.href = t, e.target = "_blank", e.rel = "nofollow";
|
|
3016
|
+
return;
|
|
3017
|
+
}
|
|
3018
|
+
const n = H.range;
|
|
3019
|
+
if (!n)
|
|
3020
|
+
return;
|
|
3021
|
+
const r = document.createElement("a");
|
|
3022
|
+
r.href = t, r.target = "_blank", r.rel = "nofollow", r.appendChild(n.extractContents()), n.insertNode(r), this.selection.expandToTag(r);
|
|
3023
|
+
}
|
|
3024
|
+
/**
|
|
3025
|
+
* Removes <a> tag
|
|
3026
|
+
*/
|
|
3027
|
+
unlink() {
|
|
3028
|
+
const t = this.selection.findParentTag("A");
|
|
3029
|
+
t && (this.unwrap(t), this.updateButtonStateAttributes(!1), this.unlinkAvailable = !1);
|
|
3030
|
+
}
|
|
3031
|
+
/**
|
|
3032
|
+
* Unwrap passed node
|
|
3033
|
+
* @param term - node to unwrap
|
|
3034
|
+
*/
|
|
3035
|
+
unwrap(t) {
|
|
3036
|
+
var n;
|
|
3037
|
+
const e = document.createDocumentFragment();
|
|
3038
|
+
for (; t.firstChild; )
|
|
3039
|
+
e.appendChild(t.firstChild);
|
|
3040
|
+
(n = t.parentNode) == null || n.replaceChild(e, t);
|
|
3041
|
+
}
|
|
3042
|
+
/**
|
|
3043
|
+
* Persist state as data attributes for testing hooks
|
|
3044
|
+
* @param element - The HTML element to set the attribute on, or null
|
|
3045
|
+
* @param attributeName - The name of the attribute to set
|
|
3046
|
+
* @param state - The boolean state value to persist
|
|
3047
|
+
*/
|
|
3048
|
+
setBooleanStateAttribute(t, e, n) {
|
|
3049
|
+
t && t.setAttribute(e, n ? "true" : "false");
|
|
3050
|
+
}
|
|
3051
|
+
};
|
|
3052
|
+
D.isInline = !0, D.title = "Link", D.titleKey = "link", D.shortcut = "CMD+K";
|
|
3053
|
+
let tt = D;
|
|
3054
|
+
const xt = {
|
|
3055
|
+
paragraph: { inlineToolbar: !0, config: { preserveBlank: !0 } },
|
|
3056
|
+
header: { inlineToolbar: !0 },
|
|
3057
|
+
list: { inlineToolbar: !0 }
|
|
3058
|
+
}, vt = {
|
|
3059
|
+
bold: {},
|
|
3060
|
+
italic: {},
|
|
3061
|
+
link: {}
|
|
3062
|
+
};
|
|
3063
|
+
export {
|
|
3064
|
+
Z as Bold,
|
|
3065
|
+
Ot as Convert,
|
|
3066
|
+
Y as Header,
|
|
3067
|
+
Q as Italic,
|
|
3068
|
+
tt as Link,
|
|
3069
|
+
J as List,
|
|
3070
|
+
X as Paragraph,
|
|
3071
|
+
xt as defaultBlockTools,
|
|
3072
|
+
vt as defaultInlineTools
|
|
3073
|
+
};
|