@jackuait/blok 0.6.0-beta.13 → 0.6.0-beta.14
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/dist/blok.mjs +2 -2
- package/dist/chunks/{blok-DzeTRJ_i.mjs → blok-Cl30YOLl.mjs} +1398 -1132
- package/dist/chunks/{i18next-loader-CgW4H90H.mjs → i18next-loader-DbTDlMux.mjs} +1 -1
- package/dist/chunks/{index-BWKk7PIS.mjs → index-DJmaYswj.mjs} +1 -1
- package/dist/chunks/{inline-tool-convert-DrSwadw_.mjs → inline-tool-convert-MjSaP8r7.mjs} +161 -145
- package/dist/{messages-B1Aww8q7.mjs → chunks/messages-1mYyrppy.mjs} +1 -0
- package/dist/chunks/{messages-DnIhyAJk.mjs → messages-4gHfbcDp.mjs} +1 -0
- package/dist/{messages-CQwpzUFp.mjs → chunks/messages-6xYv-U8A.mjs} +1 -0
- package/dist/chunks/{messages-CMkNSDTo.mjs → messages-7jqF79FO.mjs} +1 -0
- package/dist/chunks/{messages-BSbjsyHY.mjs → messages-B3EUuA7o.mjs} +1 -0
- package/dist/{messages-LPVfA-8K.mjs → chunks/messages-B6srtRn7.mjs} +1 -0
- package/dist/chunks/{messages-DJDG55Vq.mjs → messages-B85OIs0E.mjs} +1 -0
- package/dist/{messages-_ErNTNhk.mjs → chunks/messages-BCYBBG_u.mjs} +1 -0
- package/dist/{messages-CZygwLwM.mjs → chunks/messages-BDLNPiaW.mjs} +1 -0
- package/dist/{messages-C2htQ_3F.mjs → chunks/messages-BKpNd1Zb.mjs} +1 -0
- package/dist/chunks/{messages-CnwibSvh.mjs → messages-BLFUid_o.mjs} +1 -0
- package/dist/{messages-CVw84KdI.mjs → chunks/messages-BNPTVKnc.mjs} +1 -0
- package/dist/{messages-BMv4xwIr.mjs → chunks/messages-BQPXSkri.mjs} +1 -0
- package/dist/{messages-Dz9L52ol.mjs → chunks/messages-BQu6VNDq.mjs} +1 -0
- package/dist/{messages-DprmQg6V.mjs → chunks/messages-BRodFKUo.mjs} +1 -0
- package/dist/{messages-CznZadDf.mjs → chunks/messages-BWIikA8H.mjs} +1 -0
- package/dist/{messages-CqWJcCbY.mjs → chunks/messages-BXQvMilx.mjs} +1 -0
- package/dist/chunks/{messages-Z9nEU2xK.mjs → messages-BZ92luiw.mjs} +1 -0
- package/dist/chunks/{messages-BC86qLvI.mjs → messages-Bg0Phlcj.mjs} +1 -0
- package/dist/chunks/{messages-DnXLrlHh.mjs → messages-Bh6RBIu5.mjs} +1 -0
- package/dist/chunks/{messages-DhLKYm2j.mjs → messages-BnFHdIpg.mjs} +1 -0
- package/dist/{messages-BU2nlrLK.mjs → chunks/messages-Bq82EVsw.mjs} +1 -0
- package/dist/chunks/{messages-Diu6jAaR.mjs → messages-BxbcTiHh.mjs} +1 -0
- package/dist/{messages-BoJc_p1r.mjs → chunks/messages-ByO6bSV2.mjs} +1 -0
- package/dist/chunks/{messages-BrPFGbM-.mjs → messages-ByUNPiQ4.mjs} +1 -0
- package/dist/chunks/{messages-BB5z9Uba.mjs → messages-Byw0EHyf.mjs} +1 -0
- package/dist/chunks/{messages-D1Hv8XGo.mjs → messages-C-aP90zq.mjs} +1 -0
- package/dist/chunks/{messages-DBRw-7Zc.mjs → messages-C2f44jci.mjs} +1 -0
- package/dist/chunks/{messages-CJdUsQ-c.mjs → messages-CKYoAmm9.mjs} +1 -0
- package/dist/chunks/{messages-CvGLfqmV.mjs → messages-CSQJrJ_N.mjs} +1 -0
- package/dist/chunks/{messages-1fC8IMyX.mjs → messages-CX_o4Cgc.mjs} +1 -0
- package/dist/chunks/{messages-BdeLo0N9.mjs → messages-C_tfXllT.mjs} +1 -0
- package/dist/chunks/{messages-DBn76jVV.mjs → messages-Cj6TZ_By.mjs} +1 -0
- package/dist/{messages-CLhcMlTc.mjs → chunks/messages-CjdYkfzj.mjs} +1 -0
- package/dist/{messages-7W4d0DwD.mjs → chunks/messages-CmvWFWR6.mjs} +1 -0
- package/dist/{messages-CzTufCHu.mjs → chunks/messages-CnV-kxh6.mjs} +1 -0
- package/dist/chunks/{messages-BMXCuEKO.mjs → messages-CpuDfpdi.mjs} +1 -0
- package/dist/chunks/{messages-DT4dP5uK.mjs → messages-Cspe1oXk.mjs} +1 -0
- package/dist/{messages-D-ZtY5v0.mjs → chunks/messages-D2_d-Jt4.mjs} +1 -0
- package/dist/{messages-BELRf6DU.mjs → chunks/messages-DADSbW8l.mjs} +1 -0
- package/dist/chunks/{messages-O5tQus_0.mjs → messages-DHSeTQ4S.mjs} +1 -0
- package/dist/{messages-BFG6Wlgy.mjs → chunks/messages-DKeO2RsW.mjs} +1 -0
- package/dist/chunks/{messages-9SihnaXQ.mjs → messages-DLdlX_dY.mjs} +1 -0
- package/dist/chunks/{messages-BL0tXcDf.mjs → messages-DVxlJ4DU.mjs} +1 -0
- package/dist/chunks/{messages-CY8_RyFE.mjs → messages-DWSxENRk.mjs} +1 -0
- package/dist/{messages-Q7AO_FLv.mjs → chunks/messages-D_bVNZ12.mjs} +1 -0
- package/dist/chunks/{messages-BYyy6Wqf.mjs → messages-DdzjVwmG.mjs} +1 -0
- package/dist/chunks/{messages-DLfR5bMd.mjs → messages-DhyBKTi0.mjs} +1 -0
- package/dist/chunks/{messages-DvFLX36Q.mjs → messages-DlC9jDXa.mjs} +1 -0
- package/dist/chunks/{messages-D5C3J9qr.mjs → messages-DmBGk8ed.mjs} +1 -0
- package/dist/chunks/{messages-R3hUSvr3.mjs → messages-Dm_VqpU6.mjs} +1 -0
- package/dist/chunks/{messages-w7v1GNaE.mjs → messages-Dn9hI_ER.mjs} +1 -0
- package/dist/{messages-uKX8WBaD.mjs → chunks/messages-Dssp4AXT.mjs} +1 -0
- package/dist/chunks/{messages-Xq8UmkVs.mjs → messages-DzCc0JgS.mjs} +1 -0
- package/dist/chunks/{messages-JELdtT6E.mjs → messages-N6THNxo0.mjs} +1 -0
- package/dist/chunks/{messages-DqM1LFg5.mjs → messages-OctOxF2l.mjs} +1 -0
- package/dist/chunks/{messages-_ncGrKHh.mjs → messages-QT3JA95K.mjs} +1 -0
- package/dist/{messages-C9eaarcK.mjs → chunks/messages-QqWK83BF.mjs} +1 -0
- package/dist/chunks/{messages-BogRq8lt.mjs → messages-RsGaDzbb.mjs} +1 -0
- package/dist/chunks/{messages-Dzwxv9v1.mjs → messages-c7YlI6Wm.mjs} +1 -0
- package/dist/{messages-D5iv1Kox.mjs → chunks/messages-lZ8aF-4r.mjs} +1 -0
- package/dist/chunks/{messages-7QoX8DkW.mjs → messages-uGDrchjb.mjs} +1 -0
- package/dist/chunks/{messages-CKI54h6O.mjs → messages-xc2yNvog.mjs} +1 -0
- package/dist/{messages-Bmu_S7GM.mjs → chunks/messages-y5Q-ymIx.mjs} +1 -0
- package/dist/{messages-BWF-zUpY.mjs → chunks/messages-yJnyedAd.mjs} +1 -0
- package/dist/{messages-kep5wtm4.mjs → chunks/messages-yQ_4upHh.mjs} +1 -0
- package/dist/chunks/{messages-C99mq906.mjs → messages-ysbWD56Q.mjs} +1 -0
- package/dist/full.mjs +2 -2
- package/dist/locales.mjs +68 -67
- package/dist/{chunks/messages-B1Aww8q7.mjs → messages-1mYyrppy.mjs} +1 -0
- package/dist/{messages-DnIhyAJk.mjs → messages-4gHfbcDp.mjs} +1 -0
- package/dist/{chunks/messages-CQwpzUFp.mjs → messages-6xYv-U8A.mjs} +1 -0
- package/dist/{messages-CMkNSDTo.mjs → messages-7jqF79FO.mjs} +1 -0
- package/dist/{messages-BSbjsyHY.mjs → messages-B3EUuA7o.mjs} +1 -0
- package/dist/{chunks/messages-LPVfA-8K.mjs → messages-B6srtRn7.mjs} +1 -0
- package/dist/{messages-DJDG55Vq.mjs → messages-B85OIs0E.mjs} +1 -0
- package/dist/{chunks/messages-_ErNTNhk.mjs → messages-BCYBBG_u.mjs} +1 -0
- package/dist/{chunks/messages-CZygwLwM.mjs → messages-BDLNPiaW.mjs} +1 -0
- package/dist/{chunks/messages-C2htQ_3F.mjs → messages-BKpNd1Zb.mjs} +1 -0
- package/dist/{messages-CnwibSvh.mjs → messages-BLFUid_o.mjs} +1 -0
- package/dist/{chunks/messages-CVw84KdI.mjs → messages-BNPTVKnc.mjs} +1 -0
- package/dist/{chunks/messages-BMv4xwIr.mjs → messages-BQPXSkri.mjs} +1 -0
- package/dist/{chunks/messages-Dz9L52ol.mjs → messages-BQu6VNDq.mjs} +1 -0
- package/dist/{chunks/messages-DprmQg6V.mjs → messages-BRodFKUo.mjs} +1 -0
- package/dist/{chunks/messages-CznZadDf.mjs → messages-BWIikA8H.mjs} +1 -0
- package/dist/{chunks/messages-CqWJcCbY.mjs → messages-BXQvMilx.mjs} +1 -0
- package/dist/{messages-Z9nEU2xK.mjs → messages-BZ92luiw.mjs} +1 -0
- package/dist/{messages-BC86qLvI.mjs → messages-Bg0Phlcj.mjs} +1 -0
- package/dist/{messages-DnXLrlHh.mjs → messages-Bh6RBIu5.mjs} +1 -0
- package/dist/{messages-DhLKYm2j.mjs → messages-BnFHdIpg.mjs} +1 -0
- package/dist/{chunks/messages-BU2nlrLK.mjs → messages-Bq82EVsw.mjs} +1 -0
- package/dist/{messages-Diu6jAaR.mjs → messages-BxbcTiHh.mjs} +1 -0
- package/dist/{chunks/messages-BoJc_p1r.mjs → messages-ByO6bSV2.mjs} +1 -0
- package/dist/{messages-BrPFGbM-.mjs → messages-ByUNPiQ4.mjs} +1 -0
- package/dist/{messages-BB5z9Uba.mjs → messages-Byw0EHyf.mjs} +1 -0
- package/dist/{messages-D1Hv8XGo.mjs → messages-C-aP90zq.mjs} +1 -0
- package/dist/{messages-DBRw-7Zc.mjs → messages-C2f44jci.mjs} +1 -0
- package/dist/{messages-CJdUsQ-c.mjs → messages-CKYoAmm9.mjs} +1 -0
- package/dist/{messages-CvGLfqmV.mjs → messages-CSQJrJ_N.mjs} +1 -0
- package/dist/{messages-1fC8IMyX.mjs → messages-CX_o4Cgc.mjs} +1 -0
- package/dist/{messages-BdeLo0N9.mjs → messages-C_tfXllT.mjs} +1 -0
- package/dist/{messages-DBn76jVV.mjs → messages-Cj6TZ_By.mjs} +1 -0
- package/dist/{chunks/messages-CLhcMlTc.mjs → messages-CjdYkfzj.mjs} +1 -0
- package/dist/{chunks/messages-7W4d0DwD.mjs → messages-CmvWFWR6.mjs} +1 -0
- package/dist/{chunks/messages-CzTufCHu.mjs → messages-CnV-kxh6.mjs} +1 -0
- package/dist/{messages-BMXCuEKO.mjs → messages-CpuDfpdi.mjs} +1 -0
- package/dist/{messages-DT4dP5uK.mjs → messages-Cspe1oXk.mjs} +1 -0
- package/dist/{chunks/messages-D-ZtY5v0.mjs → messages-D2_d-Jt4.mjs} +1 -0
- package/dist/{chunks/messages-BELRf6DU.mjs → messages-DADSbW8l.mjs} +1 -0
- package/dist/{messages-O5tQus_0.mjs → messages-DHSeTQ4S.mjs} +1 -0
- package/dist/{chunks/messages-BFG6Wlgy.mjs → messages-DKeO2RsW.mjs} +1 -0
- package/dist/{messages-9SihnaXQ.mjs → messages-DLdlX_dY.mjs} +1 -0
- package/dist/{messages-BL0tXcDf.mjs → messages-DVxlJ4DU.mjs} +1 -0
- package/dist/{messages-CY8_RyFE.mjs → messages-DWSxENRk.mjs} +1 -0
- package/dist/{chunks/messages-Q7AO_FLv.mjs → messages-D_bVNZ12.mjs} +1 -0
- package/dist/{messages-BYyy6Wqf.mjs → messages-DdzjVwmG.mjs} +1 -0
- package/dist/{messages-DLfR5bMd.mjs → messages-DhyBKTi0.mjs} +1 -0
- package/dist/{messages-DvFLX36Q.mjs → messages-DlC9jDXa.mjs} +1 -0
- package/dist/{messages-D5C3J9qr.mjs → messages-DmBGk8ed.mjs} +1 -0
- package/dist/{messages-R3hUSvr3.mjs → messages-Dm_VqpU6.mjs} +1 -0
- package/dist/{messages-w7v1GNaE.mjs → messages-Dn9hI_ER.mjs} +1 -0
- package/dist/{chunks/messages-uKX8WBaD.mjs → messages-Dssp4AXT.mjs} +1 -0
- package/dist/{messages-Xq8UmkVs.mjs → messages-DzCc0JgS.mjs} +1 -0
- package/dist/{messages-JELdtT6E.mjs → messages-N6THNxo0.mjs} +1 -0
- package/dist/{messages-DqM1LFg5.mjs → messages-OctOxF2l.mjs} +1 -0
- package/dist/{messages-_ncGrKHh.mjs → messages-QT3JA95K.mjs} +1 -0
- package/dist/{chunks/messages-C9eaarcK.mjs → messages-QqWK83BF.mjs} +1 -0
- package/dist/{messages-BogRq8lt.mjs → messages-RsGaDzbb.mjs} +1 -0
- package/dist/{messages-Dzwxv9v1.mjs → messages-c7YlI6Wm.mjs} +1 -0
- package/dist/{chunks/messages-D5iv1Kox.mjs → messages-lZ8aF-4r.mjs} +1 -0
- package/dist/{messages-7QoX8DkW.mjs → messages-uGDrchjb.mjs} +1 -0
- package/dist/{messages-CKI54h6O.mjs → messages-xc2yNvog.mjs} +1 -0
- package/dist/{chunks/messages-Bmu_S7GM.mjs → messages-y5Q-ymIx.mjs} +1 -0
- package/dist/{chunks/messages-BWF-zUpY.mjs → messages-yJnyedAd.mjs} +1 -0
- package/dist/{chunks/messages-kep5wtm4.mjs → messages-yQ_4upHh.mjs} +1 -0
- package/dist/{messages-C99mq906.mjs → messages-ysbWD56Q.mjs} +1 -0
- package/dist/tools.mjs +887 -701
- package/package.json +2 -1
- package/src/components/block/style-manager.ts +5 -1
- package/src/components/blocks.ts +109 -9
- package/src/components/i18n/locales/am/messages.json +1 -0
- package/src/components/i18n/locales/ar/messages.json +1 -0
- package/src/components/i18n/locales/az/messages.json +1 -0
- package/src/components/i18n/locales/bg/messages.json +1 -0
- package/src/components/i18n/locales/bn/messages.json +1 -0
- package/src/components/i18n/locales/bs/messages.json +1 -0
- package/src/components/i18n/locales/cs/messages.json +1 -0
- package/src/components/i18n/locales/da/messages.json +1 -0
- package/src/components/i18n/locales/de/messages.json +1 -0
- package/src/components/i18n/locales/dv/messages.json +1 -0
- package/src/components/i18n/locales/el/messages.json +1 -0
- package/src/components/i18n/locales/en/messages.json +1 -0
- package/src/components/i18n/locales/es/messages.json +1 -0
- package/src/components/i18n/locales/et/messages.json +1 -0
- package/src/components/i18n/locales/fa/messages.json +1 -0
- package/src/components/i18n/locales/fi/messages.json +1 -0
- package/src/components/i18n/locales/fil/messages.json +1 -0
- package/src/components/i18n/locales/fr/messages.json +1 -0
- package/src/components/i18n/locales/gu/messages.json +1 -0
- package/src/components/i18n/locales/he/messages.json +1 -0
- package/src/components/i18n/locales/hi/messages.json +1 -0
- package/src/components/i18n/locales/hr/messages.json +1 -0
- package/src/components/i18n/locales/hu/messages.json +1 -0
- package/src/components/i18n/locales/hy/messages.json +1 -0
- package/src/components/i18n/locales/id/messages.json +1 -0
- package/src/components/i18n/locales/it/messages.json +1 -0
- package/src/components/i18n/locales/ja/messages.json +1 -0
- package/src/components/i18n/locales/ka/messages.json +1 -0
- package/src/components/i18n/locales/km/messages.json +1 -0
- package/src/components/i18n/locales/kn/messages.json +1 -0
- package/src/components/i18n/locales/ko/messages.json +1 -0
- package/src/components/i18n/locales/ku/messages.json +1 -0
- package/src/components/i18n/locales/lo/messages.json +1 -0
- package/src/components/i18n/locales/lt/messages.json +1 -0
- package/src/components/i18n/locales/lv/messages.json +1 -0
- package/src/components/i18n/locales/mk/messages.json +1 -0
- package/src/components/i18n/locales/ml/messages.json +1 -0
- package/src/components/i18n/locales/mn/messages.json +1 -0
- package/src/components/i18n/locales/mr/messages.json +1 -0
- package/src/components/i18n/locales/ms/messages.json +1 -0
- package/src/components/i18n/locales/my/messages.json +1 -0
- package/src/components/i18n/locales/ne/messages.json +1 -0
- package/src/components/i18n/locales/nl/messages.json +1 -0
- package/src/components/i18n/locales/no/messages.json +1 -0
- package/src/components/i18n/locales/pa/messages.json +1 -0
- package/src/components/i18n/locales/pl/messages.json +1 -0
- package/src/components/i18n/locales/ps/messages.json +1 -0
- package/src/components/i18n/locales/pt/messages.json +1 -0
- package/src/components/i18n/locales/ro/messages.json +1 -0
- package/src/components/i18n/locales/ru/messages.json +1 -0
- package/src/components/i18n/locales/sd/messages.json +1 -0
- package/src/components/i18n/locales/si/messages.json +1 -0
- package/src/components/i18n/locales/sk/messages.json +1 -0
- package/src/components/i18n/locales/sl/messages.json +1 -0
- package/src/components/i18n/locales/sq/messages.json +1 -0
- package/src/components/i18n/locales/sr/messages.json +1 -0
- package/src/components/i18n/locales/sv/messages.json +1 -0
- package/src/components/i18n/locales/sw/messages.json +1 -0
- package/src/components/i18n/locales/ta/messages.json +1 -0
- package/src/components/i18n/locales/te/messages.json +1 -0
- package/src/components/i18n/locales/th/messages.json +1 -0
- package/src/components/i18n/locales/tr/messages.json +1 -0
- package/src/components/i18n/locales/ug/messages.json +1 -0
- package/src/components/i18n/locales/uk/messages.json +1 -0
- package/src/components/i18n/locales/ur/messages.json +1 -0
- package/src/components/i18n/locales/vi/messages.json +1 -0
- package/src/components/i18n/locales/yi/messages.json +1 -0
- package/src/components/i18n/locales/zh/messages.json +1 -0
- package/src/components/icons/index.ts +8 -0
- package/src/components/modules/blockEvents/composers/keyboardNavigation.ts +51 -10
- package/src/components/modules/blockEvents/index.ts +2 -5
- package/src/components/modules/blockManager/blockManager.ts +10 -0
- package/src/components/modules/blockManager/operations.ts +11 -0
- package/src/components/modules/blockManager/repository.ts +22 -0
- package/src/components/modules/blockManager/yjs-sync.ts +148 -31
- package/src/components/modules/crossBlockSelection.ts +11 -3
- package/src/components/modules/drag/preview/DragPreview.ts +8 -0
- package/src/components/modules/drag/target/DropTargetDetector.ts +100 -11
- package/src/components/modules/paste/handlers/base.ts +3 -4
- package/src/components/modules/paste/index.ts +1 -1
- package/src/components/modules/rectangleSelection.ts +5 -2
- package/src/components/modules/toolbar/blockSettings.ts +52 -44
- package/src/components/modules/toolbar/index.ts +27 -7
- package/src/components/modules/toolbar/inline/index.ts +1 -1
- package/src/components/modules/uiControllers/controllers/blockHover.ts +16 -2
- package/src/components/modules/uiControllers/handlers/touch.ts +83 -10
- package/src/components/modules/yjs/block-observer.ts +9 -3
- package/src/components/modules/yjs/document-store.ts +4 -2
- package/src/components/modules/yjs/types.ts +8 -6
- package/src/components/utils/popover/components/popover-item/popover-item-default/popover-item-default.ts +6 -4
- package/src/components/utils/popover/popover-desktop.ts +12 -0
- package/src/stories/Popover.stories.ts +0 -2
- package/src/styles/main.css +9 -1
- package/src/tools/list/caret-manager.ts +28 -10
- package/src/tools/table/index.ts +180 -37
- package/src/tools/table/table-add-controls.ts +51 -14
- package/src/tools/table/table-cell-blocks.ts +41 -2
- package/src/tools/table/table-cell-selection.ts +27 -1
- package/src/tools/table/table-operations.ts +12 -15
- package/src/tools/table/table-row-col-controls.ts +69 -6
- package/src/tools/table/table-scroll-haze.ts +152 -0
|
@@ -112,21 +112,43 @@ export class BlockYjsSync {
|
|
|
112
112
|
* @param fn - Function to execute
|
|
113
113
|
* @param options - Options for controlling the atomic operation behavior
|
|
114
114
|
*/
|
|
115
|
-
|
|
115
|
+
/**
|
|
116
|
+
* Begin an atomic operation by incrementing sync count and suppressing stop capturing.
|
|
117
|
+
*
|
|
118
|
+
* @returns cleanup function to call when operation completes
|
|
119
|
+
*/
|
|
120
|
+
private beginAtomicOperation(): () => void {
|
|
116
121
|
this.yjsSyncCount++;
|
|
117
122
|
const operations = this.dependencies.operations;
|
|
118
|
-
const shouldExtend = options?.extendThroughRAF === true;
|
|
119
123
|
|
|
120
124
|
if (operations) {
|
|
121
125
|
operations.suppressStopCapturing = true;
|
|
122
126
|
}
|
|
123
127
|
|
|
124
|
-
|
|
128
|
+
return (): void => {
|
|
125
129
|
this.yjsSyncCount--;
|
|
126
130
|
if (operations && this.yjsSyncCount === 0) {
|
|
127
131
|
operations.suppressStopCapturing = false;
|
|
128
132
|
}
|
|
129
133
|
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* End an atomic operation, optionally deferring cleanup through RAF.
|
|
138
|
+
*
|
|
139
|
+
* @param cleanup - function to call to decrement sync count
|
|
140
|
+
* @param extendThroughRAF - if true, defer cleanup until after next animation frame
|
|
141
|
+
*/
|
|
142
|
+
private endAtomicOperation(cleanup: () => void, extendThroughRAF: boolean): void {
|
|
143
|
+
if (extendThroughRAF) {
|
|
144
|
+
requestAnimationFrame(cleanup);
|
|
145
|
+
} else {
|
|
146
|
+
cleanup();
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
public withAtomicOperation<T>(fn: () => T, options?: { extendThroughRAF?: boolean }): T {
|
|
151
|
+
const cleanup = this.beginAtomicOperation();
|
|
130
152
|
|
|
131
153
|
try {
|
|
132
154
|
const result = fn();
|
|
@@ -134,16 +156,34 @@ export class BlockYjsSync {
|
|
|
134
156
|
// If extendThroughRAF is true, delay decrementing yjsSyncCount until after requestAnimationFrame callbacks
|
|
135
157
|
// This ensures that DOM updates scheduled by rendered() hooks don't trigger
|
|
136
158
|
// block data sync to Yjs, which would create new undo entries and clear the redo stack
|
|
137
|
-
|
|
138
|
-
requestAnimationFrame(decrementSyncCount);
|
|
139
|
-
} else {
|
|
140
|
-
decrementSyncCount();
|
|
141
|
-
}
|
|
159
|
+
this.endAtomicOperation(cleanup, options?.extendThroughRAF === true);
|
|
142
160
|
|
|
143
161
|
return result;
|
|
144
162
|
} catch (error) {
|
|
145
|
-
|
|
146
|
-
|
|
163
|
+
cleanup();
|
|
164
|
+
throw error;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Async version of withAtomicOperation for operations that return promises.
|
|
170
|
+
* Keeps yjsSyncCount elevated until the async work completes, then optionally
|
|
171
|
+
* extends through RAF to cover deferred DOM callbacks.
|
|
172
|
+
*
|
|
173
|
+
* @param fn - Async function to execute
|
|
174
|
+
* @param options - Options for controlling the atomic operation behavior
|
|
175
|
+
*/
|
|
176
|
+
public async withAtomicOperationAsync(
|
|
177
|
+
fn: () => Promise<void>,
|
|
178
|
+
options?: { extendThroughRAF?: boolean }
|
|
179
|
+
): Promise<void> {
|
|
180
|
+
const cleanup = this.beginAtomicOperation();
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
await fn();
|
|
184
|
+
this.endAtomicOperation(cleanup, options?.extendThroughRAF === true);
|
|
185
|
+
} catch (error) {
|
|
186
|
+
cleanup();
|
|
147
187
|
throw error;
|
|
148
188
|
}
|
|
149
189
|
}
|
|
@@ -165,25 +205,28 @@ export class BlockYjsSync {
|
|
|
165
205
|
* @param event - the block change event from YjsManager
|
|
166
206
|
*/
|
|
167
207
|
private syncBlockFromYjs(event: BlockChangeEvent): void {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
if (changeType === 'update') {
|
|
171
|
-
this.handleYjsUpdate(blockId);
|
|
208
|
+
if (event.type === 'update') {
|
|
209
|
+
this.handleYjsUpdate(event.blockId);
|
|
172
210
|
return;
|
|
173
211
|
}
|
|
174
212
|
|
|
175
|
-
if (
|
|
213
|
+
if (event.type === 'move') {
|
|
176
214
|
this.handleYjsMove();
|
|
177
215
|
return;
|
|
178
216
|
}
|
|
179
217
|
|
|
180
|
-
if (
|
|
181
|
-
this.handleYjsAdd(blockId);
|
|
218
|
+
if (event.type === 'add') {
|
|
219
|
+
this.handleYjsAdd(event.blockId);
|
|
182
220
|
return;
|
|
183
221
|
}
|
|
184
222
|
|
|
185
|
-
if (
|
|
186
|
-
this.
|
|
223
|
+
if (event.type === 'batch-add') {
|
|
224
|
+
this.handleYjsBatchAdd(event.blockIds);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (event.type === 'remove') {
|
|
229
|
+
this.handleYjsRemove(event.blockId);
|
|
187
230
|
}
|
|
188
231
|
}
|
|
189
232
|
|
|
@@ -221,17 +264,19 @@ export class BlockYjsSync {
|
|
|
221
264
|
bindEventsImmediately: true,
|
|
222
265
|
});
|
|
223
266
|
|
|
224
|
-
//
|
|
225
|
-
|
|
226
|
-
|
|
267
|
+
// Use atomic operation with RAF extension to prevent DOM mutation observers
|
|
268
|
+
// from syncing back to Yjs after block replacement
|
|
269
|
+
this.withAtomicOperation(() => {
|
|
227
270
|
this.handlers.replaceBlock(blockIndex, newBlock);
|
|
228
|
-
}
|
|
229
|
-
this.yjsSyncCount--;
|
|
230
|
-
}
|
|
271
|
+
}, { extendThroughRAF: true });
|
|
231
272
|
} else {
|
|
232
|
-
// Update data in-place; if tool can't handle it, recreate the block
|
|
233
|
-
|
|
234
|
-
|
|
273
|
+
// Update data in-place; if tool can't handle it, recreate the block.
|
|
274
|
+
// Use async atomic operation with RAF extension to keep isSyncingFromYjs
|
|
275
|
+
// true through the entire setData lifecycle + one RAF frame, preventing
|
|
276
|
+
// DOM mutation observers from writing back to Yjs and clearing the redo stack.
|
|
277
|
+
void this.withAtomicOperationAsync(async () => {
|
|
278
|
+
const success = await block.setData(data);
|
|
279
|
+
|
|
235
280
|
if (!success) {
|
|
236
281
|
const blockIndex = this.handlers.getBlockIndex(block);
|
|
237
282
|
const newBlock = this.factory.composeBlock({
|
|
@@ -244,9 +289,7 @@ export class BlockYjsSync {
|
|
|
244
289
|
|
|
245
290
|
this.handlers.replaceBlock(blockIndex, newBlock);
|
|
246
291
|
}
|
|
247
|
-
}
|
|
248
|
-
this.yjsSyncCount--;
|
|
249
|
-
});
|
|
292
|
+
}, { extendThroughRAF: true });
|
|
250
293
|
}
|
|
251
294
|
}
|
|
252
295
|
|
|
@@ -303,6 +346,80 @@ export class BlockYjsSync {
|
|
|
303
346
|
}, { extendThroughRAF: true });
|
|
304
347
|
}
|
|
305
348
|
|
|
349
|
+
/**
|
|
350
|
+
* Handle batch block add from Yjs (undo/redo).
|
|
351
|
+
*
|
|
352
|
+
* When multiple blocks are restored at once (e.g. a table + its cell
|
|
353
|
+
* paragraphs), we use a two-pass approach:
|
|
354
|
+
* 1. Create ALL blocks and insert them into the blocks array (no DOM).
|
|
355
|
+
* 2. Activate each block (DOM insert + RENDERED lifecycle hook).
|
|
356
|
+
*
|
|
357
|
+
* This ensures that when a parent tool's `rendered()` hook fires (pass 2),
|
|
358
|
+
* child blocks already exist in BlockManager, so helpers like
|
|
359
|
+
* `mountBlocksInCell()` can find them by ID.
|
|
360
|
+
*/
|
|
361
|
+
private handleYjsBatchAdd(blockIds: string[]): void {
|
|
362
|
+
const yjsBlocks = this.dependencies.YjsManager.toJSON();
|
|
363
|
+
|
|
364
|
+
// Collect blocks to create — skip any that already exist
|
|
365
|
+
const toCreate: Array<{ blockId: string; toolName: string; data: Record<string, unknown>; parentId: string | undefined; targetIndex: number }> = [];
|
|
366
|
+
|
|
367
|
+
for (const blockId of blockIds) {
|
|
368
|
+
if (this.repository.getBlockById(blockId) !== undefined) {
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const yblock = this.dependencies.YjsManager.getBlockById(blockId);
|
|
373
|
+
|
|
374
|
+
if (yblock === undefined) {
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const toolName = yblock.get('type') as string;
|
|
379
|
+
const data = this.dependencies.YjsManager.yMapToObject(yblock.get('data') as YMap<unknown>);
|
|
380
|
+
const parentId = yblock.get('parentId') as string | undefined;
|
|
381
|
+
const targetIndex = yjsBlocks.findIndex((b) => b.id === blockId);
|
|
382
|
+
|
|
383
|
+
if (targetIndex === -1) {
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
toCreate.push({ blockId, toolName, data, parentId: parentId ?? undefined, targetIndex });
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (toCreate.length === 0) {
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
this.withAtomicOperation(() => {
|
|
395
|
+
// Pass 1 — create blocks and add to array (no DOM, no RENDERED)
|
|
396
|
+
const created: Array<{ block: Block; targetIndex: number; parentId: string | undefined }> = [];
|
|
397
|
+
|
|
398
|
+
for (const entry of toCreate) {
|
|
399
|
+
const block = this.factory.composeBlock({
|
|
400
|
+
id: entry.blockId,
|
|
401
|
+
tool: entry.toolName,
|
|
402
|
+
data: entry.data,
|
|
403
|
+
parentId: entry.parentId,
|
|
404
|
+
bindEventsImmediately: true,
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
this.blocksStore.addToArray(entry.targetIndex, block);
|
|
408
|
+
created.push({ block, targetIndex: entry.targetIndex, parentId: entry.parentId });
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Pass 2 — activate blocks (DOM insert + RENDERED), then emit events
|
|
412
|
+
for (const { block, targetIndex, parentId } of created) {
|
|
413
|
+
this.blocksStore.activateBlock(block);
|
|
414
|
+
this.handlers.onBlockAdded(block, targetIndex);
|
|
415
|
+
|
|
416
|
+
if (parentId !== undefined) {
|
|
417
|
+
this.handlers.updateIndentation(block);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}, { extendThroughRAF: true });
|
|
421
|
+
}
|
|
422
|
+
|
|
306
423
|
/**
|
|
307
424
|
* Handle block remove from Yjs (undo/redo - removing a previously added block)
|
|
308
425
|
*/
|
|
@@ -269,13 +269,21 @@ export class CrossBlockSelection extends Module {
|
|
|
269
269
|
return;
|
|
270
270
|
}
|
|
271
271
|
|
|
272
|
-
const
|
|
273
|
-
const
|
|
272
|
+
const rawRelatedBlock = BlockManager.getBlockByChildNode(mouseEvent.relatedTarget as Node) || this.lastSelectedBlock;
|
|
273
|
+
const rawTargetBlock = BlockManager.getBlockByChildNode(mouseEvent.target as Node);
|
|
274
274
|
|
|
275
|
-
if (!
|
|
275
|
+
if (!rawRelatedBlock || !rawTargetBlock) {
|
|
276
276
|
return;
|
|
277
277
|
}
|
|
278
278
|
|
|
279
|
+
/**
|
|
280
|
+
* Resolve child blocks (e.g. paragraphs inside table cells) to their root parent.
|
|
281
|
+
* Without this, dragging across a table would select individual cell blocks
|
|
282
|
+
* from the flat blocks array instead of treating the table as a single unit.
|
|
283
|
+
*/
|
|
284
|
+
const relatedBlock = BlockManager.resolveToRootBlock(rawRelatedBlock);
|
|
285
|
+
const targetBlock = BlockManager.resolveToRootBlock(rawTargetBlock);
|
|
286
|
+
|
|
279
287
|
if (targetBlock === relatedBlock) {
|
|
280
288
|
return;
|
|
281
289
|
}
|
|
@@ -23,6 +23,14 @@ export class DragPreview {
|
|
|
23
23
|
// Reset styles on clone
|
|
24
24
|
clone.className = twMerge(PREVIEW_STYLES.content, isStretched ? 'max-w-none' : '');
|
|
25
25
|
|
|
26
|
+
// Set explicit width on clone so percentage-based children (e.g. table cells)
|
|
27
|
+
// have a sizing reference inside the position:fixed preview container
|
|
28
|
+
const computedWidth = contentElement.getBoundingClientRect().width;
|
|
29
|
+
|
|
30
|
+
if (computedWidth > 0) {
|
|
31
|
+
clone.style.width = `${computedWidth}px`;
|
|
32
|
+
}
|
|
33
|
+
|
|
26
34
|
// Reset margin on the tool's rendered element (first child) to prevent offset
|
|
27
35
|
const toolElement = clone.firstElementChild as HTMLElement | null;
|
|
28
36
|
|
|
@@ -138,6 +138,20 @@ export class DropTargetDetector {
|
|
|
138
138
|
return null;
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
+
/**
|
|
142
|
+
* If the target block is inside a table cell and the source block is NOT
|
|
143
|
+
* in the same cell, redirect the drop target to the table block itself.
|
|
144
|
+
* This prevents blocks from being dropped into table cells via drag & drop.
|
|
145
|
+
*/
|
|
146
|
+
const targetCellContainer = targetBlock.holder.closest('[data-blok-table-cell-blocks]');
|
|
147
|
+
const sourceCellContainer = sourceBlock.holder.closest('[data-blok-table-cell-blocks]');
|
|
148
|
+
const isTargetInCell = targetCellContainer !== null;
|
|
149
|
+
const isCrossCellDrop = sourceCellContainer !== targetCellContainer;
|
|
150
|
+
|
|
151
|
+
if (isTargetInCell && isCrossCellDrop) {
|
|
152
|
+
return this.redirectToTableBlock(targetCellContainer, clientY);
|
|
153
|
+
}
|
|
154
|
+
|
|
141
155
|
// Determine edge (top or bottom half of block)
|
|
142
156
|
const rect = blockHolder.getBoundingClientRect();
|
|
143
157
|
const isTopHalf = clientY < rect.top + rect.height / 2;
|
|
@@ -151,24 +165,29 @@ export class DropTargetDetector {
|
|
|
151
165
|
const canUsePreviousBlock = previousBlock && !this.sourceBlocks.includes(previousBlock);
|
|
152
166
|
|
|
153
167
|
if (isTopHalf && targetIndex > 0 && canUsePreviousBlock) {
|
|
154
|
-
const targetDepth = this.calculateTargetDepth(previousBlock, 'bottom');
|
|
168
|
+
const targetDepth = this.calculateTargetDepth(previousBlock, 'bottom', sourceBlock);
|
|
155
169
|
return { block: previousBlock, edge: 'bottom', depth: targetDepth };
|
|
156
170
|
}
|
|
157
171
|
|
|
158
172
|
// First block top half, or any block bottom half
|
|
159
173
|
const edge: 'top' | 'bottom' = isTopHalf ? 'top' : 'bottom';
|
|
160
|
-
const targetDepth = this.calculateTargetDepth(targetBlock, edge);
|
|
174
|
+
const targetDepth = this.calculateTargetDepth(targetBlock, edge, sourceBlock);
|
|
161
175
|
|
|
162
176
|
return { block: targetBlock, edge, depth: targetDepth };
|
|
163
177
|
}
|
|
164
178
|
|
|
165
179
|
/**
|
|
166
|
-
* Calculates the target depth for list item nesting
|
|
180
|
+
* Calculates the target depth for list item nesting.
|
|
181
|
+
* When a sourceBlock is provided and is a list item, the depth is calculated
|
|
182
|
+
* to match what ListDepthValidator.getTargetDepthForMove will produce post-drop,
|
|
183
|
+
* ensuring the visual indicator accurately predicts the final depth.
|
|
184
|
+
*
|
|
167
185
|
* @param targetBlock - Block being dropped onto
|
|
168
186
|
* @param targetEdge - Edge of target ('top' or 'bottom')
|
|
187
|
+
* @param sourceBlock - Optional block being dragged (for accurate list item depth prediction)
|
|
169
188
|
* @returns The target depth (0 for root level, 1+ for nested)
|
|
170
189
|
*/
|
|
171
|
-
calculateTargetDepth(targetBlock: Block, targetEdge: 'top' | 'bottom'): number {
|
|
190
|
+
calculateTargetDepth(targetBlock: Block, targetEdge: 'top' | 'bottom', sourceBlock?: Block): number {
|
|
172
191
|
const targetIndex = this.blockManager.getBlockIndex(targetBlock);
|
|
173
192
|
const dropIndex = targetEdge === 'top' ? targetIndex : targetIndex + 1;
|
|
174
193
|
|
|
@@ -184,23 +203,93 @@ export class DropTargetDetector {
|
|
|
184
203
|
return 0;
|
|
185
204
|
}
|
|
186
205
|
|
|
187
|
-
const previousDepth = this.listItemDepth.getDepth(previousBlock)
|
|
206
|
+
const previousDepth = this.listItemDepth.getDepth(previousBlock);
|
|
207
|
+
const previousIsListItem = previousDepth !== null;
|
|
208
|
+
const prevDepthValue = previousDepth ?? 0;
|
|
188
209
|
|
|
189
210
|
// Get the block that will be immediately after the drop position
|
|
190
211
|
const nextBlock = this.blockManager.getBlockByIndex(dropIndex);
|
|
191
|
-
const nextDepth = nextBlock ?
|
|
212
|
+
const nextDepth = nextBlock ? this.listItemDepth.getDepth(nextBlock) : null;
|
|
213
|
+
const nextIsListItem = nextDepth !== null;
|
|
214
|
+
const nextDepthValue = nextDepth ?? 0;
|
|
215
|
+
|
|
216
|
+
// When dragging a list item, predict the exact depth ListDepthValidator will
|
|
217
|
+
// compute post-drop — so the visual indicator matches the actual result.
|
|
218
|
+
const sourceDepth = sourceBlock ? this.listItemDepth.getDepth(sourceBlock) : null;
|
|
219
|
+
|
|
220
|
+
if (sourceDepth !== null) {
|
|
221
|
+
return this.predictListItemDepth(
|
|
222
|
+
sourceDepth, prevDepthValue, previousIsListItem, nextDepthValue, nextIsListItem
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// For non-list blocks (or when sourceBlock not provided), use neighbor-based
|
|
227
|
+
// depth for cosmetic indicator positioning only.
|
|
228
|
+
if (nextDepthValue > 0 && nextDepthValue <= prevDepthValue + 1) {
|
|
229
|
+
return nextDepthValue;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (prevDepthValue > 0) {
|
|
233
|
+
return prevDepthValue;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return 0;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Mirrors ListDepthValidator.getTargetDepthForMove to predict the exact
|
|
241
|
+
* post-drop depth for a list item. This ensures the visual indicator
|
|
242
|
+
* shows the same depth the item will actually have after being dropped.
|
|
243
|
+
*/
|
|
244
|
+
private predictListItemDepth(
|
|
245
|
+
currentDepth: number,
|
|
246
|
+
previousDepth: number,
|
|
247
|
+
previousIsListItem: boolean,
|
|
248
|
+
nextDepth: number,
|
|
249
|
+
nextIsListItem: boolean
|
|
250
|
+
): number {
|
|
251
|
+
const maxAllowedDepth = previousIsListItem ? previousDepth + 1 : 0;
|
|
252
|
+
|
|
253
|
+
// Cap current depth at max allowed
|
|
254
|
+
if (currentDepth > maxAllowedDepth) {
|
|
255
|
+
return maxAllowedDepth;
|
|
256
|
+
}
|
|
192
257
|
|
|
193
|
-
//
|
|
194
|
-
if (nextDepth >
|
|
258
|
+
// Match next depth if it's deeper than current and within bounds
|
|
259
|
+
if (nextIsListItem && nextDepth > currentDepth && nextDepth <= maxAllowedDepth) {
|
|
195
260
|
return nextDepth;
|
|
196
261
|
}
|
|
197
262
|
|
|
198
|
-
//
|
|
199
|
-
if (previousDepth >
|
|
263
|
+
// Match previous depth if deeper than current and no next list item
|
|
264
|
+
if (previousIsListItem && !nextIsListItem && previousDepth > currentDepth && previousDepth <= maxAllowedDepth) {
|
|
200
265
|
return previousDepth;
|
|
201
266
|
}
|
|
202
267
|
|
|
203
|
-
return
|
|
268
|
+
return currentDepth;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Redirect a drop target to the table block that contains the given cell container.
|
|
273
|
+
* Finds the outermost [data-blok-element] ancestor and returns it as the target.
|
|
274
|
+
*
|
|
275
|
+
* @param cellContainer - The [data-blok-table-cell-blocks] element containing the target
|
|
276
|
+
* @param clientY - Cursor Y position for edge calculation
|
|
277
|
+
* @returns Drop target pointing to the table block, or null if table block not found
|
|
278
|
+
*/
|
|
279
|
+
private redirectToTableBlock(cellContainer: Element, clientY: number): DropTarget | null {
|
|
280
|
+
const tableHolder = cellContainer.closest(createSelector(DATA_ATTR.element));
|
|
281
|
+
const tableBlock = tableHolder
|
|
282
|
+
? this.blockManager.blocks.find(b => b.holder === tableHolder)
|
|
283
|
+
: undefined;
|
|
284
|
+
|
|
285
|
+
if (!tableBlock) {
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const tableRect = tableBlock.holder.getBoundingClientRect();
|
|
290
|
+
const isTopHalf = clientY < tableRect.top + tableRect.height / 2;
|
|
291
|
+
|
|
292
|
+
return { block: tableBlock, edge: isTopHalf ? 'top' : 'bottom', depth: 0 };
|
|
204
293
|
}
|
|
205
294
|
|
|
206
295
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { PasteEvent, PasteEventDetail } from '../../../../../types';
|
|
2
2
|
import type { BlokModules } from '../../../../types-internal/blok-modules';
|
|
3
|
+
import { getRestrictedTools } from '../../../../tools/table/table-restrictions';
|
|
3
4
|
import type { SanitizerConfigBuilder } from '../sanitizer-config';
|
|
4
5
|
import type { ToolRegistry } from '../tool-registry';
|
|
5
6
|
import type { HandlerContext, PasteData } from '../types';
|
|
@@ -65,9 +66,6 @@ export abstract class BasePasteHandler implements PasteHandler {
|
|
|
65
66
|
return isCurrentBlockDefault && currentBlock.isEmpty;
|
|
66
67
|
}
|
|
67
68
|
|
|
68
|
-
/** Tools that cannot be nested inside table cells */
|
|
69
|
-
private static readonly TOOLS_RESTRICTED_IN_TABLE_CELLS = new Set(['table', 'header']);
|
|
70
|
-
|
|
71
69
|
/**
|
|
72
70
|
* If we're inside a table cell and any pasted item uses a tool that can't
|
|
73
71
|
* be nested in table cells (e.g. table, header), redirect the insertion
|
|
@@ -78,7 +76,8 @@ export abstract class BasePasteHandler implements PasteHandler {
|
|
|
78
76
|
private redirectToTableParentIfNeeded(data: PasteData[], BlockManager: BlokModules['BlockManager']): void {
|
|
79
77
|
const currentBlock = BlockManager.currentBlock;
|
|
80
78
|
const isInsideTableCell = currentBlock?.holder?.closest('[data-blok-table-cell-blocks]');
|
|
81
|
-
const
|
|
79
|
+
const restricted = new Set(getRestrictedTools());
|
|
80
|
+
const hasRestrictedTools = data.some(item => restricted.has(item.tool));
|
|
82
81
|
|
|
83
82
|
if (!isInsideTableCell || !hasRestrictedTools || currentBlock === undefined) {
|
|
84
83
|
return;
|
|
@@ -611,9 +611,12 @@ export class RectangleSelection extends Module {
|
|
|
611
611
|
};
|
|
612
612
|
}
|
|
613
613
|
const blockInCurrentPos = this.Blok.BlockManager.getBlockByChildNode(elementUnderMouse);
|
|
614
|
+
const rootBlock = blockInCurrentPos !== undefined
|
|
615
|
+
? this.Blok.BlockManager.resolveToRootBlock(blockInCurrentPos)
|
|
616
|
+
: undefined;
|
|
614
617
|
|
|
615
|
-
const index =
|
|
616
|
-
? this.Blok.BlockManager.blocks.findIndex((block) => block.holder ===
|
|
618
|
+
const index = rootBlock !== undefined
|
|
619
|
+
? this.Blok.BlockManager.blocks.findIndex((block) => block.holder === rootBlock.holder)
|
|
617
620
|
: undefined;
|
|
618
621
|
|
|
619
622
|
return {
|
|
@@ -160,61 +160,69 @@ export class BlockSettings extends Module<BlockSettingsNodes> {
|
|
|
160
160
|
* Set isOpening flag BEFORE async operations to prevent toolbar from moving
|
|
161
161
|
* while menu items are being created. This fixes a bug where hovering over a different
|
|
162
162
|
* block during async getTunesItems() causes the toolbar to reposition incorrectly.
|
|
163
|
+
*
|
|
164
|
+
* Wrapped in try/catch to guarantee isOpening is always reset — if any step
|
|
165
|
+
* (getTunes, getTunesItems, PopoverClass constructor) throws, without cleanup
|
|
166
|
+
* the flag stays true and the toolbar permanently stops appearing on hover.
|
|
163
167
|
*/
|
|
164
168
|
this.isOpening = true;
|
|
165
169
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
170
|
+
try {
|
|
171
|
+
/**
|
|
172
|
+
* If block settings contains any inputs, focus will be set there,
|
|
173
|
+
* so we need to save current selection to restore it after block settings is closed
|
|
174
|
+
*/
|
|
175
|
+
this.selection.save();
|
|
171
176
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
177
|
+
/**
|
|
178
|
+
* Highlight content of a Block we are working with
|
|
179
|
+
* For multiple blocks, they should already be selected
|
|
180
|
+
*/
|
|
181
|
+
if (!hasMultipleBlocksSelected) {
|
|
182
|
+
this.Blok.BlockSelection.selectBlock(block);
|
|
183
|
+
this.Blok.BlockSelection.clearCache();
|
|
184
|
+
}
|
|
180
185
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
186
|
+
/** Get tool's settings data - only relevant for single block selection */
|
|
187
|
+
const { toolTunes, commonTunes } = block.getTunes();
|
|
188
|
+
|
|
189
|
+
const PopoverClass = isMobileScreen() ? PopoverMobile : PopoverDesktop;
|
|
190
|
+
const popoverParams: PopoverParams & { flipper?: Flipper } = {
|
|
191
|
+
searchable: false,
|
|
192
|
+
trigger: trigger || this.nodes.wrapper,
|
|
193
|
+
items: await this.getTunesItems(block, commonTunes, toolTunes),
|
|
194
|
+
scopeElement: this.Blok.API.methods.ui.nodes.redactor,
|
|
195
|
+
messages: {
|
|
196
|
+
nothingFound: this.Blok.I18n.t('popover.nothingFound'),
|
|
197
|
+
search: this.Blok.I18n.t('popover.search'),
|
|
198
|
+
},
|
|
199
|
+
};
|
|
195
200
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
201
|
+
if (PopoverClass === PopoverDesktop) {
|
|
202
|
+
popoverParams.flipper = this.flipperInstance;
|
|
203
|
+
}
|
|
199
204
|
|
|
200
|
-
|
|
201
|
-
|
|
205
|
+
this.popover = new PopoverClass(popoverParams);
|
|
206
|
+
this.popover.getElement().setAttribute('data-blok-testid', 'block-tunes-popover');
|
|
202
207
|
|
|
203
|
-
|
|
208
|
+
this.popover.on(PopoverEvent.Closed, this.onPopoverClose);
|
|
204
209
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
210
|
+
/**
|
|
211
|
+
* Set opened flag AFTER popover is created to prevent race conditions
|
|
212
|
+
* where close() is called during the async getTunesItems() call
|
|
213
|
+
* when opened=true but popover is still null
|
|
214
|
+
*/
|
|
215
|
+
this.opened = true;
|
|
216
|
+
this.isOpening = false;
|
|
212
217
|
|
|
213
|
-
|
|
214
|
-
|
|
218
|
+
/** Tell to subscribers that block settings is opened */
|
|
219
|
+
this.eventsDispatcher.emit(this.events.opened);
|
|
215
220
|
|
|
216
|
-
|
|
217
|
-
|
|
221
|
+
this.popover.show();
|
|
222
|
+
this.attachFlipperKeydownListener(block);
|
|
223
|
+
} catch {
|
|
224
|
+
this.isOpening = false;
|
|
225
|
+
}
|
|
218
226
|
}
|
|
219
227
|
|
|
220
228
|
/**
|
|
@@ -336,11 +336,15 @@ export class Toolbar extends Module<ToolbarNodes> {
|
|
|
336
336
|
}
|
|
337
337
|
|
|
338
338
|
/**
|
|
339
|
-
* Track whether the
|
|
340
|
-
*
|
|
341
|
-
*
|
|
339
|
+
* Track whether the block itself lives inside a table cell.
|
|
340
|
+
* The blockHover controller already resolves cell paragraphs to the parent
|
|
341
|
+
* table block, so when hovering a table, `unresolvedBlock` IS the table
|
|
342
|
+
* block (whose holder is NOT inside a cell). We only check the block's
|
|
343
|
+
* holder — not the raw mouse target — to avoid hiding the plus button
|
|
344
|
+
* for the table block itself.
|
|
342
345
|
*/
|
|
343
|
-
this.hoveredBlockIsFromTableCell =
|
|
346
|
+
this.hoveredBlockIsFromTableCell =
|
|
347
|
+
unresolvedBlock.holder.closest('[data-blok-table-cell-blocks]') !== null;
|
|
344
348
|
|
|
345
349
|
const targetBlock = this.resolveTableCellBlock(unresolvedBlock);
|
|
346
350
|
|
|
@@ -588,6 +592,16 @@ export class Toolbar extends Module<ToolbarNodes> {
|
|
|
588
592
|
this.settingsTogglerHandler.skipNextToggle();
|
|
589
593
|
}
|
|
590
594
|
|
|
595
|
+
/**
|
|
596
|
+
* Hides the block actions (plus button and settings toggler) without
|
|
597
|
+
* closing the entire toolbar or setting explicitlyClosed.
|
|
598
|
+
* Used when the toolbar should remain positioned but its action buttons
|
|
599
|
+
* should temporarily step aside (e.g., during typing or inline toolbar use).
|
|
600
|
+
*/
|
|
601
|
+
public hideBlockActions(): void {
|
|
602
|
+
this.blockActions.hide();
|
|
603
|
+
}
|
|
604
|
+
|
|
591
605
|
/**
|
|
592
606
|
* Resets the explicitlyClosed flag to allow the toolbar to reopen on hover.
|
|
593
607
|
* Called when drag is cancelled to re-enable hover-based toolbar opening.
|
|
@@ -876,11 +890,17 @@ export class Toolbar extends Module<ToolbarNodes> {
|
|
|
876
890
|
}
|
|
877
891
|
|
|
878
892
|
/**
|
|
879
|
-
* Do not move toolbar if it was explicitly closed
|
|
880
|
-
*
|
|
893
|
+
* Do not move toolbar if it was explicitly closed and the user is still
|
|
894
|
+
* hovering the same block. When the user hovers a DIFFERENT block
|
|
895
|
+
* (or hoveredBlock is null after close()), reset the flag and allow
|
|
896
|
+
* the toolbar to reopen — this is an intentional user action.
|
|
881
897
|
*/
|
|
882
898
|
if (this.explicitlyClosed) {
|
|
883
|
-
|
|
899
|
+
if (this.hoveredBlock !== null && this.hoveredBlock === hoveredBlock) {
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
this.explicitlyClosed = false;
|
|
884
904
|
}
|
|
885
905
|
|
|
886
906
|
/**
|