@jackuait/blok 0.10.2 → 0.10.4
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-D-T1XZ92.mjs → blok-NcdNQ0I6.mjs} +2304 -2101
- package/dist/chunks/{constants-CaB-mlB5.mjs → constants-DtfShkXT.mjs} +414 -317
- package/dist/chunks/{i18next-loader-CDnSPae_.mjs → i18next-loader-D32EUWLr.mjs} +1 -1
- package/dist/chunks/{lightweight-i18n-DZmo8dAI.mjs → lightweight-i18n-DpkvRXEd.mjs} +19 -0
- package/dist/{messages-Ddq3Ce3E2.mjs → chunks/messages-AD17iDBx.mjs} +18 -0
- package/dist/{messages-neGD3WGq.mjs → chunks/messages-B03yUEra2.mjs} +18 -0
- package/dist/chunks/{messages-CIfUm1Oa.mjs → messages-B2dU00Z3.mjs} +18 -0
- package/dist/chunks/{messages-BKN3YVIj.mjs → messages-B8hICx3L.mjs} +18 -0
- package/dist/{messages-Dnd5YSWv.mjs → chunks/messages-BBe45sPH.mjs} +18 -0
- package/dist/{messages-C7lJg8fy2.mjs → chunks/messages-BCifzMVO2.mjs} +18 -0
- package/dist/chunks/{messages-D7dx_6k8.mjs → messages-BGmvvtg_.mjs} +18 -0
- package/dist/{messages-Q5sQeVap2.mjs → chunks/messages-BJNFCDv42.mjs} +18 -0
- package/dist/chunks/{messages-BlxwW7M6.mjs → messages-BNy4e7Xl.mjs} +18 -0
- package/dist/chunks/{messages-C15z2r5U.mjs → messages-BcHZf9o-.mjs} +18 -0
- package/dist/{messages-A96tMxeU.mjs → chunks/messages-BjyYZeBm2.mjs} +18 -0
- package/dist/{messages-BbJ7ZXY8.mjs → chunks/messages-Bop7vrhU.mjs} +18 -0
- package/dist/{messages-BiTMwiKH.mjs → chunks/messages-BouFtpfO.mjs} +18 -0
- package/dist/chunks/{messages-ElIGUi0O2.mjs → messages-Br6bE1FD2.mjs} +18 -0
- package/dist/chunks/{messages-BHMiK51R.mjs → messages-C-EBhOHE.mjs} +18 -0
- package/dist/chunks/{messages-kGmxkeFH.mjs → messages-C3X7dv3f.mjs} +18 -0
- package/dist/chunks/{messages-4Ck88DYZ2.mjs → messages-C7Pjof0d2.mjs} +18 -0
- package/dist/{messages-D0lLw9KM.mjs → chunks/messages-C7sBaZOO2.mjs} +18 -0
- package/dist/chunks/{messages-QMOmwcZb.mjs → messages-C85zv_7x.mjs} +18 -0
- package/dist/chunks/{messages-DSrdy9Nw2.mjs → messages-CCEgR9GN2.mjs} +18 -0
- package/dist/chunks/{messages-DUr9WAkD.mjs → messages-CDSyoUft.mjs} +18 -0
- package/dist/{messages-bkGniiaz.mjs → chunks/messages-CGFlOwst.mjs} +18 -0
- package/dist/chunks/{messages-DBMaLL8b2.mjs → messages-CGLTjtRv2.mjs} +18 -0
- package/dist/{messages-2ZWBTerL.mjs → chunks/messages-CGPxUESo.mjs} +18 -0
- package/dist/chunks/{messages-BfAcUavP.mjs → messages-CNaaqQVz.mjs} +18 -0
- package/dist/{messages-DBhvm8NK.mjs → chunks/messages-CPFB2_m-2.mjs} +18 -0
- package/dist/chunks/{messages-zt6zdYWh.mjs → messages-CTdSIOAc.mjs} +18 -0
- package/dist/chunks/{messages-1_6UkKLS.mjs → messages-CXVWb9js.mjs} +18 -0
- package/dist/{messages-CdEASHDp2.mjs → chunks/messages-Cfbmwdep2.mjs} +18 -0
- package/dist/chunks/{messages-DxHh0O8j2.mjs → messages-ChayV9WY2.mjs} +18 -0
- package/dist/chunks/{messages-BgM91Lxm2.mjs → messages-Ci7UXRVI2.mjs} +18 -0
- package/dist/chunks/{messages-Clku7Cf-2.mjs → messages-CpXvyGWv2.mjs} +18 -0
- package/dist/chunks/{messages-DjvaiALg2.mjs → messages-Cql2ozf_2.mjs} +18 -0
- package/dist/{messages-DODrhcop.mjs → chunks/messages-Cxy_E2IS.mjs} +18 -0
- package/dist/chunks/{messages-CZSlfnkO2.mjs → messages-D9syZVGi2.mjs} +18 -0
- package/dist/chunks/{messages-BRAoJpOu.mjs → messages-D9uWAWjW.mjs} +18 -0
- package/dist/chunks/{messages-BK8Cp2d0.mjs → messages-DRJxSTqs.mjs} +18 -0
- package/dist/{messages-C_Qn9SbQ.mjs → chunks/messages-DSbI0vJf.mjs} +18 -0
- package/dist/chunks/{messages-CD_MnBln.mjs → messages-DVvrZRyZ.mjs} +18 -0
- package/dist/{messages-BE_z-zrb.mjs → chunks/messages-DY8zPIZW.mjs} +18 -0
- package/dist/chunks/{messages-Bz0-KNEB.mjs → messages-D_kZN9rB.mjs} +18 -0
- package/dist/{messages-C1vc5584.mjs → chunks/messages-DjSuq0-y2.mjs} +18 -0
- package/dist/chunks/{messages-DPzHD51Y.mjs → messages-DkP3Jf4F.mjs} +18 -0
- package/dist/{messages-_PLyRfVw.mjs → chunks/messages-DoPdy75l.mjs} +18 -0
- package/dist/chunks/{messages-JSQjKQ8I.mjs → messages-DpydMd36.mjs} +18 -0
- package/dist/{messages-BckDk9aq2.mjs → chunks/messages-DtZ9U9g72.mjs} +18 -0
- package/dist/{messages-JNrYldAa2.mjs → chunks/messages-H6vLy8wJ.mjs} +18 -0
- package/dist/chunks/{messages-DTN1XGll.mjs → messages-HzH9_QH8.mjs} +18 -0
- package/dist/chunks/{messages-C0IFfhnp.mjs → messages-O6FOfUgF.mjs} +18 -0
- package/dist/{messages-Be_2RHZD.mjs → chunks/messages-OSP4Hj5o.mjs} +18 -0
- package/dist/chunks/{messages-DMoERagV2.mjs → messages-RiqdVwuN2.mjs} +18 -0
- package/dist/chunks/{messages-BJ-vT1SU2.mjs → messages-SP659Sal2.mjs} +18 -0
- package/dist/{messages-Che99vKP.mjs → chunks/messages-THR8q8bJ.mjs} +18 -0
- package/dist/chunks/{messages-CvANwuht2.mjs → messages-VlEyFUxF2.mjs} +18 -0
- package/dist/{messages-apA6BStA.mjs → chunks/messages-VtfKWZ2S.mjs} +18 -0
- package/dist/{messages-DpJGbx3q.mjs → chunks/messages-YbckahVx2.mjs} +18 -0
- package/dist/{messages-DYuD5-rO.mjs → chunks/messages-ZhHLC6dk.mjs} +18 -0
- package/dist/{messages-C0GSBBCo2.mjs → chunks/messages-bFEdH3lv.mjs} +18 -0
- package/dist/chunks/{messages-euM2m3wQ.mjs → messages-dpXwA3Sz.mjs} +18 -0
- package/dist/chunks/{messages-CQBo3lmL2.mjs → messages-fbL5y58u2.mjs} +18 -0
- package/dist/chunks/{messages-CxiURE2X.mjs → messages-oPV2oMxM.mjs} +18 -0
- package/dist/{messages-DM4Gjc9h.mjs → chunks/messages-oXBbHW9A.mjs} +18 -0
- package/dist/chunks/{messages-QilfinOn2.mjs → messages-vDgsEqQW2.mjs} +18 -0
- package/dist/{messages-ClGvlFcH2.mjs → chunks/messages-wYQksm10.mjs} +18 -0
- package/dist/{messages-CnuH-BZK2.mjs → chunks/messages-yGedmr61.mjs} +18 -0
- package/dist/chunks/{messages-sDdNf8O9.mjs → messages-zQOpKjl3.mjs} +18 -0
- package/dist/chunks/{messages-eFd4YYzt.mjs → messages-zWqsggJh.mjs} +18 -0
- package/dist/chunks/{tools-BFK2MvVI.mjs → tools-DMSi-3RW.mjs} +3434 -1240
- package/dist/full.mjs +10 -10
- package/dist/locales.mjs +86 -67
- package/dist/{messages-BK_LsgY4.mjs → messages-0lOPMv8u.mjs} +18 -0
- package/dist/{messages-LYJbLq_F.mjs → messages-5wuR90qS.mjs} +18 -0
- package/dist/{messages-98nQiC7t2.mjs → messages-6eX0fWGR2.mjs} +18 -0
- package/dist/{chunks/messages-DUeiPraX.mjs → messages-9L4qqCKh2.mjs} +18 -0
- package/dist/{chunks/messages-Q7-4ZJLB2.mjs → messages-B4zPxKl62.mjs} +18 -0
- package/dist/{messages-D0005ti32.mjs → messages-BCMFYqKc2.mjs} +18 -0
- package/dist/{chunks/messages-CC_noR8y.mjs → messages-BKXjO3NH.mjs} +18 -0
- package/dist/{messages-CRNogopy2.mjs → messages-BLW2GX7J2.mjs} +18 -0
- package/dist/{messages-D81w6AmW.mjs → messages-BLfK27kX.mjs} +18 -0
- package/dist/{chunks/messages-CPBN4zWc.mjs → messages-BM2kx9Td.mjs} +18 -0
- package/dist/{messages-E8NjqzWq2.mjs → messages-BORkMoil2.mjs} +18 -0
- package/dist/{messages-Dqu4aX9s.mjs → messages-BPw_x-6H.mjs} +18 -0
- package/dist/{messages-DSmxJWju2.mjs → messages-BRY51SEw2.mjs} +18 -0
- package/dist/{chunks/messages-BONyZroH.mjs → messages-BSNsrZVN.mjs} +18 -0
- package/dist/{chunks/messages-BAlZjPcl.mjs → messages-B_UKuqrH.mjs} +18 -0
- package/dist/{chunks/messages-DB_-5Xln.mjs → messages-BrYeJsSE2.mjs} +18 -0
- package/dist/{messages-Brd5R-da2.mjs → messages-BwttyHDI2.mjs} +18 -0
- package/dist/{messages-qfvXgPpu2.mjs → messages-C-8qb9sf2.mjs} +18 -0
- package/dist/{chunks/messages-BbEW9bQz.mjs → messages-C34dTwF72.mjs} +18 -0
- package/dist/{messages-BmH2cQHQ.mjs → messages-C67YUZ9-.mjs} +18 -0
- package/dist/{messages-DpwMKDV0.mjs → messages-C6yKu_PJ.mjs} +18 -0
- package/dist/{messages-Do7Xjy0n.mjs → messages-CA6J_QoC.mjs} +18 -0
- package/dist/{messages-DVL0KZE5.mjs → messages-CFUBJfnf.mjs} +18 -0
- package/dist/{chunks/messages-DVr1sqfI2.mjs → messages-CLUBh7O_.mjs} +18 -0
- package/dist/{chunks/messages-wl8YrvGG.mjs → messages-CLZoy5fQ.mjs} +18 -0
- package/dist/{messages-CisR4PNV.mjs → messages-CNGwdIEz.mjs} +18 -0
- package/dist/{messages-DopaMHC42.mjs → messages-CR4gHjd82.mjs} +18 -0
- package/dist/{messages-DK6dA0O2.mjs → messages-CVMngZNA.mjs} +18 -0
- package/dist/{messages-Xc0KUbYl.mjs → messages-Cd5CW5Tt.mjs} +18 -0
- package/dist/{chunks/messages-ChK7v1PV.mjs → messages-CrjQ2Op0.mjs} +18 -0
- package/dist/{chunks/messages-CRF7nNrO.mjs → messages-Cv1PSaNk.mjs} +18 -0
- package/dist/{messages-DOGbHYv-2.mjs → messages-CxZarWTm2.mjs} +18 -0
- package/dist/{messages-D3rwCtKn.mjs → messages-D0eT_eWA.mjs} +18 -0
- package/dist/{messages-C6ONf71u2.mjs → messages-D6RYu9JW2.mjs} +18 -0
- package/dist/{messages-DQORja0D.mjs → messages-D8U5D391.mjs} +18 -0
- package/dist/{chunks/messages-EDMC5ukV.mjs → messages-D8dO6OMN.mjs} +18 -0
- package/dist/{messages-DfFZ6Yj5.mjs → messages-DA4T9WBe.mjs} +18 -0
- package/dist/{chunks/messages-D22e9h7V2.mjs → messages-DB4UKN8D.mjs} +18 -0
- package/dist/{chunks/messages-DEBy3nuJ2.mjs → messages-DCdP2ujL.mjs} +18 -0
- package/dist/{messages-D05jqBIa2.mjs → messages-DPFuzIdF2.mjs} +18 -0
- package/dist/{chunks/messages-DrfRYiM32.mjs → messages-DQ1icG7L.mjs} +18 -0
- package/dist/{chunks/messages-a07QVz8U.mjs → messages-DT7dwzEe.mjs} +18 -0
- package/dist/{chunks/messages-CszmHAvQ.mjs → messages-DUYxMxrQ2.mjs} +18 -0
- package/dist/{chunks/messages-DtoId_bw2.mjs → messages-D_V0kHD7.mjs} +18 -0
- package/dist/{messages-BesJaI6A.mjs → messages-DfqM_XvD.mjs} +18 -0
- package/dist/{messages-CT-Kdas6.mjs → messages-Di3-WVzq.mjs} +18 -0
- package/dist/{messages-BcVB3osF.mjs → messages-Dl0bfeA-.mjs} +18 -0
- package/dist/{chunks/messages-C1S9ztpF.mjs → messages-Do3mHd9U.mjs} +18 -0
- package/dist/{chunks/messages-BeGZqQwz.mjs → messages-DqDlcEPn.mjs} +18 -0
- package/dist/{chunks/messages-CTCe595D2.mjs → messages-DwiykEgr2.mjs} +18 -0
- package/dist/{chunks/messages-8Ld7P_9j2.mjs → messages-Dx5n6MLQ2.mjs} +18 -0
- package/dist/{messages-LMaR2_bE.mjs → messages-DxEiqa-B.mjs} +18 -0
- package/dist/{chunks/messages-CxxyR4vY.mjs → messages-Dxr1BBvo.mjs} +18 -0
- package/dist/{messages-D6VIFnSW.mjs → messages-DzknMM7W.mjs} +18 -0
- package/dist/{chunks/messages-oMc7qugU2.mjs → messages-ELvF3qMl2.mjs} +18 -0
- package/dist/{messages-53w0fPZS2.mjs → messages-JVJdC0Er2.mjs} +18 -0
- package/dist/{chunks/messages-BMD37y3q2.mjs → messages-KVerxvZC.mjs} +18 -0
- package/dist/{chunks/messages-Du2BffA7.mjs → messages-OOiDDmVw.mjs} +18 -0
- package/dist/{messages-uwK7ktqk.mjs → messages-PyOr_YgV.mjs} +18 -0
- package/dist/{messages-qbKjjvgd2.mjs → messages-VrQw3tQ62.mjs} +18 -0
- package/dist/{messages-CTTmWn4Y2.mjs → messages-WsUHzXMu2.mjs} +18 -0
- package/dist/{messages-CZbcxlZt2.mjs → messages-ZHgPRUj02.mjs} +18 -0
- package/dist/{messages-DKHbt-7l2.mjs → messages-aoO_TtoE2.mjs} +18 -0
- package/dist/{chunks/messages-CW35K1pq.mjs → messages-bh8BiOee2.mjs} +18 -0
- package/dist/{messages-BrOWqNCu2.mjs → messages-gZEhkRrR2.mjs} +18 -0
- package/dist/{chunks/messages-BRoa9tGl.mjs → messages-hya8YLMj.mjs} +18 -0
- package/dist/{messages-CdduYw-q.mjs → messages-tb1FD_ge.mjs} +18 -0
- package/dist/react.mjs +2 -2
- package/dist/tools.mjs +3 -3
- package/dist/vendor.LICENSE.txt +135 -0
- package/package.json +2 -1
- package/src/blok.ts +48 -0
- package/src/components/block/index.ts +21 -2
- package/src/components/block-tunes/block-tune-copy-link.ts +82 -0
- package/src/components/i18n/locales/am/messages.json +18 -0
- package/src/components/i18n/locales/ar/messages.json +18 -0
- package/src/components/i18n/locales/az/messages.json +18 -0
- package/src/components/i18n/locales/bg/messages.json +18 -0
- package/src/components/i18n/locales/bn/messages.json +18 -0
- package/src/components/i18n/locales/bs/messages.json +18 -0
- package/src/components/i18n/locales/cs/messages.json +18 -0
- package/src/components/i18n/locales/da/messages.json +18 -0
- package/src/components/i18n/locales/de/messages.json +18 -0
- package/src/components/i18n/locales/dv/messages.json +18 -0
- package/src/components/i18n/locales/el/messages.json +18 -0
- package/src/components/i18n/locales/en/messages.json +19 -0
- package/src/components/i18n/locales/es/messages.json +18 -0
- package/src/components/i18n/locales/et/messages.json +18 -0
- package/src/components/i18n/locales/fa/messages.json +18 -0
- package/src/components/i18n/locales/fi/messages.json +18 -0
- package/src/components/i18n/locales/fil/messages.json +18 -0
- package/src/components/i18n/locales/fr/messages.json +18 -0
- package/src/components/i18n/locales/gu/messages.json +18 -0
- package/src/components/i18n/locales/he/messages.json +18 -0
- package/src/components/i18n/locales/hi/messages.json +18 -0
- package/src/components/i18n/locales/hr/messages.json +18 -0
- package/src/components/i18n/locales/hu/messages.json +18 -0
- package/src/components/i18n/locales/hy/messages.json +18 -0
- package/src/components/i18n/locales/id/messages.json +18 -0
- package/src/components/i18n/locales/it/messages.json +18 -0
- package/src/components/i18n/locales/ja/messages.json +18 -0
- package/src/components/i18n/locales/ka/messages.json +18 -0
- package/src/components/i18n/locales/km/messages.json +18 -0
- package/src/components/i18n/locales/kn/messages.json +18 -0
- package/src/components/i18n/locales/ko/messages.json +18 -0
- package/src/components/i18n/locales/ku/messages.json +18 -0
- package/src/components/i18n/locales/lo/messages.json +18 -0
- package/src/components/i18n/locales/lt/messages.json +18 -0
- package/src/components/i18n/locales/lv/messages.json +18 -0
- package/src/components/i18n/locales/mk/messages.json +18 -0
- package/src/components/i18n/locales/ml/messages.json +18 -0
- package/src/components/i18n/locales/mn/messages.json +18 -0
- package/src/components/i18n/locales/mr/messages.json +18 -0
- package/src/components/i18n/locales/ms/messages.json +18 -0
- package/src/components/i18n/locales/my/messages.json +18 -0
- package/src/components/i18n/locales/ne/messages.json +18 -0
- package/src/components/i18n/locales/nl/messages.json +18 -0
- package/src/components/i18n/locales/no/messages.json +18 -0
- package/src/components/i18n/locales/pa/messages.json +18 -0
- package/src/components/i18n/locales/pl/messages.json +18 -0
- package/src/components/i18n/locales/ps/messages.json +18 -0
- package/src/components/i18n/locales/pt/messages.json +18 -0
- package/src/components/i18n/locales/ro/messages.json +18 -0
- package/src/components/i18n/locales/ru/messages.json +18 -0
- package/src/components/i18n/locales/sd/messages.json +18 -0
- package/src/components/i18n/locales/si/messages.json +18 -0
- package/src/components/i18n/locales/sk/messages.json +18 -0
- package/src/components/i18n/locales/sl/messages.json +18 -0
- package/src/components/i18n/locales/sq/messages.json +18 -0
- package/src/components/i18n/locales/sr/messages.json +18 -0
- package/src/components/i18n/locales/sv/messages.json +18 -0
- package/src/components/i18n/locales/sw/messages.json +18 -0
- package/src/components/i18n/locales/ta/messages.json +18 -0
- package/src/components/i18n/locales/te/messages.json +18 -0
- package/src/components/i18n/locales/th/messages.json +18 -0
- package/src/components/i18n/locales/tr/messages.json +18 -0
- package/src/components/i18n/locales/ug/messages.json +18 -0
- package/src/components/i18n/locales/uk/messages.json +18 -0
- package/src/components/i18n/locales/ur/messages.json +18 -0
- package/src/components/i18n/locales/vi/messages.json +18 -0
- package/src/components/i18n/locales/yi/messages.json +18 -0
- package/src/components/i18n/locales/zh/messages.json +18 -0
- package/src/components/icons/index.ts +65 -0
- package/src/components/inline-tools/inline-tool-bold.ts +10 -0
- package/src/components/inline-tools/inline-tool-code.ts +54 -1
- package/src/components/inline-tools/inline-tool-italic.ts +54 -1
- package/src/components/inline-tools/inline-tool-strikethrough.ts +54 -1
- package/src/components/inline-tools/inline-tool-underline.ts +54 -1
- package/src/components/inline-tools/services/bold-normalization-pass.ts +29 -3
- package/src/components/inline-tools/utils/formatting-range-utils.ts +83 -0
- package/src/components/modules/api/tools.ts +19 -0
- package/src/components/modules/blockEvents/composers/keyboardNavigation.ts +5 -5
- package/src/components/modules/blockManager/blockManager.ts +22 -0
- package/src/components/modules/blockManager/event-binder.ts +12 -1
- package/src/components/modules/blockManager/factory.ts +4 -0
- package/src/components/modules/blockManager/types.ts +4 -0
- package/src/components/modules/blockManager/yjs-sync.ts +16 -2
- package/src/components/modules/paste/google-docs-preprocessor.ts +49 -3
- package/src/components/modules/paste/handlers/table-cells-handler.ts +12 -2
- package/src/components/modules/paste/index.ts +8 -4
- package/src/components/modules/paste/types.ts +2 -0
- package/src/components/modules/renderer.ts +22 -2
- package/src/components/modules/saver.ts +19 -1
- package/src/components/modules/themeManager.ts +3 -1
- package/src/components/modules/toolbar/blockSettings.ts +51 -1
- package/src/components/modules/toolbar/index.ts +95 -3
- package/src/components/modules/toolbar/inline/index.ts +0 -3
- package/src/components/modules/toolbar/plus-button.ts +37 -0
- package/src/components/modules/toolbar/settings-toggler.ts +6 -0
- package/src/components/modules/tools.ts +5 -0
- package/src/components/modules/uiControllers/controllers/keyboard.ts +85 -22
- package/src/components/modules/uiControllers/controllers/selection.ts +14 -2
- package/src/components/modules/yjs/document-store.ts +22 -0
- package/src/components/modules/yjs/index.ts +10 -0
- package/src/components/modules/yjs/serializer.ts +20 -0
- package/src/components/selection/cursor.ts +12 -2
- package/src/components/ui/toolbox.ts +31 -5
- package/src/components/utils/id-generator.ts +11 -0
- package/src/components/utils/key-icon.ts +187 -0
- package/src/components/utils/popover/components/hint/hint.ts +3 -1
- package/src/components/utils/popover/components/popover-item/popover-item-default/popover-item-default.ts +18 -5
- package/src/components/utils/popover/popover-abstract.ts +45 -0
- package/src/components/utils/popover/popover-desktop.ts +1 -0
- package/src/components/utils/popover/popover-position.ts +12 -5
- package/src/components/utils/popover/popover.const.ts +2 -0
- package/src/components/utils.ts +1 -0
- package/src/styles/main.css +1269 -0
- package/src/tools/code/index.ts +4 -0
- package/src/tools/database/database-backend-sync.ts +132 -0
- package/src/tools/database/database-board-view.ts +410 -0
- package/src/tools/database/database-card-drag.ts +306 -0
- package/src/tools/database/database-card-drawer.ts +546 -0
- package/src/tools/database/database-column-controls.ts +141 -0
- package/src/tools/database/database-column-drag.ts +262 -0
- package/src/tools/database/database-keyboard.ts +35 -0
- package/src/tools/database/database-list-row-drag.ts +245 -0
- package/src/tools/database/database-list-view.ts +333 -0
- package/src/tools/database/database-model.ts +214 -0
- package/src/tools/database/database-property-type-popover.ts +108 -0
- package/src/tools/database/database-tab-bar.ts +558 -0
- package/src/tools/database/database-view-popover.ts +129 -0
- package/src/tools/database/database-view-renderer.ts +25 -0
- package/src/tools/database/index.ts +1223 -0
- package/src/tools/database/types.ts +152 -0
- package/src/tools/database-row/index.ts +74 -0
- package/src/tools/index.ts +4 -0
- package/src/tools/table/index.ts +10 -19
- package/src/tools/table/table-cell-selection.ts +126 -7
- package/src/tools/table/table-core.ts +59 -5
- package/src/tools/table/table-model.ts +8 -0
- package/src/tools/table/table-row-col-controls.ts +40 -18
- package/types/api/tools.d.ts +18 -0
- package/types/configs/blok-config.d.ts +27 -0
- package/types/data-formats/output-data.d.ts +13 -0
- package/types/index.d.ts +17 -0
- package/types/tools/database.d.ts +152 -0
- package/types/tools-entry.d.ts +7 -4
- package/types/utils/popover/popover.d.ts +6 -0
|
@@ -118,6 +118,22 @@ export class Toolbar extends Module<ToolbarNodes> {
|
|
|
118
118
|
*/
|
|
119
119
|
private settingsTogglerHandler: SettingsTogglerHandler;
|
|
120
120
|
|
|
121
|
+
/**
|
|
122
|
+
* The block that had focus immediately before the plus button opened the toolbox.
|
|
123
|
+
* Captured via the onFocusBlockCaptured callback in PlusButtonHandler.handleClick(),
|
|
124
|
+
* before any block manipulation occurs.
|
|
125
|
+
* Used to restore focus if the user dismisses the toolbox without selecting a tool.
|
|
126
|
+
* Cleared when a tool is selected (ToolboxEvent.BlockAdded) or when focus is restored.
|
|
127
|
+
*/
|
|
128
|
+
private preToolboxBlock: Block | null = null;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* A newly-inserted empty block created by the plus button click (not a reused block).
|
|
132
|
+
* If the user dismisses the toolbox without selecting a tool, this block is removed.
|
|
133
|
+
* Cleared when a tool is selected or when the block is removed on cancel.
|
|
134
|
+
*/
|
|
135
|
+
private plusInsertedBlock: Block | null = null;
|
|
136
|
+
|
|
121
137
|
/**
|
|
122
138
|
* @class
|
|
123
139
|
* @param moduleConfiguration - Module Configuration
|
|
@@ -143,6 +159,10 @@ export class Toolbar extends Module<ToolbarNodes> {
|
|
|
143
159
|
openToolboxWithoutSlash: () => this.toolbox.openWithoutSlash(),
|
|
144
160
|
closeToolbox: () => this.toolbox.close(),
|
|
145
161
|
moveAndOpenToolbar: (block, target) => this.moveAndOpen(block, target),
|
|
162
|
+
onFocusBlockCaptured: (block, insertedBlock) => {
|
|
163
|
+
this.preToolboxBlock = block;
|
|
164
|
+
this.plusInsertedBlock = insertedBlock;
|
|
165
|
+
},
|
|
146
166
|
}
|
|
147
167
|
);
|
|
148
168
|
|
|
@@ -537,14 +557,22 @@ export class Toolbar extends Module<ToolbarNodes> {
|
|
|
537
557
|
* Uses Math.max to guarantee the actions container (positioned via right:100%)
|
|
538
558
|
* never extends beyond the left edge of the viewport, which would make the
|
|
539
559
|
* drag handle unreachable by pointer events.
|
|
560
|
+
*
|
|
561
|
+
* For nested blocks (e.g. children inside a callout), the holder is already
|
|
562
|
+
* offset from the viewport left by the parent's indentation. In that case we
|
|
563
|
+
* only need to ensure the actions don't extend beyond the viewport left edge
|
|
564
|
+
* (holderLeft px are available to the left), so the minimum margin is
|
|
565
|
+
* max(0, actionsWidth - holderLeft) rather than a flat actionsWidth clamp.
|
|
540
566
|
*/
|
|
541
567
|
if (blockContentElement && this.nodes.content) {
|
|
542
568
|
const holderRect = this.nodes.wrapper?.getBoundingClientRect();
|
|
543
569
|
const contentRect = blockContentElement.getBoundingClientRect();
|
|
544
570
|
const visualOffset = holderRect ? Math.max(0, contentRect.left - holderRect.left) : 0;
|
|
545
571
|
const actionsWidth = this.nodes.actions?.offsetWidth ?? 0;
|
|
572
|
+
const holderLeft = holderRect ? Math.max(0, holderRect.left) : 0;
|
|
573
|
+
const minMarginLeft = Math.max(0, actionsWidth - holderLeft);
|
|
546
574
|
|
|
547
|
-
this.nodes.content.style.marginLeft = `${Math.max(visualOffset,
|
|
575
|
+
this.nodes.content.style.marginLeft = `${Math.max(visualOffset, minMarginLeft)}px`;
|
|
548
576
|
this.nodes.content.style.maxWidth = `${contentRect.width}px`;
|
|
549
577
|
}
|
|
550
578
|
}
|
|
@@ -668,15 +696,19 @@ export class Toolbar extends Module<ToolbarNodes> {
|
|
|
668
696
|
/**
|
|
669
697
|
* Sync toolbar content wrapper's position and width with the block content element.
|
|
670
698
|
* Uses getBoundingClientRect so wide-mode content (max-width: none) is handled correctly.
|
|
671
|
-
* Clamp to actionsWidth so actions never extend beyond the left
|
|
699
|
+
* Clamp to max(0, actionsWidth - holderLeft) so actions never extend beyond the left
|
|
700
|
+
* viewport edge. For nested blocks already offset from the left, a smaller clamp is
|
|
701
|
+
* used so buttons are not pushed into the text content.
|
|
672
702
|
*/
|
|
673
703
|
if (blockContentElement && this.nodes.content) {
|
|
674
704
|
const holderRect = this.nodes.wrapper?.getBoundingClientRect();
|
|
675
705
|
const contentRect = blockContentElement.getBoundingClientRect();
|
|
676
706
|
const visualOffset = holderRect ? Math.max(0, contentRect.left - holderRect.left) : 0;
|
|
677
707
|
const actionsWidth = this.nodes.actions?.offsetWidth ?? 0;
|
|
708
|
+
const holderLeft = holderRect ? Math.max(0, holderRect.left) : 0;
|
|
709
|
+
const minMarginLeft = Math.max(0, actionsWidth - holderLeft);
|
|
678
710
|
|
|
679
|
-
this.nodes.content.style.marginLeft = `${Math.max(visualOffset,
|
|
711
|
+
this.nodes.content.style.marginLeft = `${Math.max(visualOffset, minMarginLeft)}px`;
|
|
680
712
|
this.nodes.content.style.maxWidth = `${contentRect.width}px`;
|
|
681
713
|
}
|
|
682
714
|
}
|
|
@@ -1089,9 +1121,63 @@ export class Toolbar extends Module<ToolbarNodes> {
|
|
|
1089
1121
|
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
1090
1122
|
this.Blok.UI.nodes.wrapper.classList.remove(this.CSS.openedToolboxHolderModifier);
|
|
1091
1123
|
this.Blok.UI.nodes.wrapper.removeAttribute(DATA_ATTR.toolboxOpened);
|
|
1124
|
+
|
|
1125
|
+
/**
|
|
1126
|
+
* If the toolbox was opened via the plus button and the user dismissed
|
|
1127
|
+
* it without selecting a tool (Escape / click outside), restore focus to
|
|
1128
|
+
* the block that was focused BEFORE the plus button was clicked and
|
|
1129
|
+
* remove the orphan empty block that was inserted.
|
|
1130
|
+
*
|
|
1131
|
+
* When a tool IS selected, ToolboxEvent.BlockAdded fires first and clears
|
|
1132
|
+
* preToolboxBlock, so this branch is skipped for that case.
|
|
1133
|
+
*/
|
|
1134
|
+
if (this.preToolboxBlock !== null) {
|
|
1135
|
+
const blockToRestore = this.preToolboxBlock;
|
|
1136
|
+
|
|
1137
|
+
this.preToolboxBlock = null;
|
|
1138
|
+
|
|
1139
|
+
// Remove the orphan block that was inserted by the plus button click,
|
|
1140
|
+
// then restore focus. removeBlock() is Promise-based but resolves
|
|
1141
|
+
// synchronously; chaining ensures setToBlock runs after removal.
|
|
1142
|
+
if (this.plusInsertedBlock !== null) {
|
|
1143
|
+
const orphan = this.plusInsertedBlock;
|
|
1144
|
+
|
|
1145
|
+
this.plusInsertedBlock = null;
|
|
1146
|
+
void this.Blok.BlockManager.removeBlock(orphan, false).then(() => {
|
|
1147
|
+
if (blockToRestore.inputs.length > 0) {
|
|
1148
|
+
this.Blok.Caret.setToBlock(blockToRestore, this.Blok.Caret.positions.END);
|
|
1149
|
+
}
|
|
1150
|
+
});
|
|
1151
|
+
} else if (blockToRestore.inputs.length > 0) {
|
|
1152
|
+
// Reused an existing block (emptyBlockToReuse path) — just restore focus
|
|
1153
|
+
this.Blok.Caret.setToBlock(blockToRestore, this.Blok.Caret.positions.END);
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
return;
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
/**
|
|
1160
|
+
* Restore focus to the current block when the toolbox closes via any
|
|
1161
|
+
* non-plus-button path (e.g. slash-search dismissed via Escape).
|
|
1162
|
+
* Without this, focus falls to document.body after non-keyboard close
|
|
1163
|
+
* paths, causing subsequent keystrokes to be lost.
|
|
1164
|
+
*/
|
|
1165
|
+
const currentBlock = this.Blok.BlockManager.currentBlock;
|
|
1166
|
+
|
|
1167
|
+
if (currentBlock && currentBlock.inputs.length > 0) {
|
|
1168
|
+
this.Blok.Caret.setToBlock(currentBlock, this.Blok.Caret.positions.END);
|
|
1169
|
+
}
|
|
1092
1170
|
});
|
|
1093
1171
|
|
|
1094
1172
|
this.toolboxInstance.on(ToolboxEvent.BlockAdded, ({ block }) => {
|
|
1173
|
+
/**
|
|
1174
|
+
* A tool was selected and a block was added — clear the cancel context so
|
|
1175
|
+
* ToolboxEvent.Closed (which fires after this) does not try to undo the
|
|
1176
|
+
* insertion and restore focus to the pre-plus block.
|
|
1177
|
+
*/
|
|
1178
|
+
this.preToolboxBlock = null;
|
|
1179
|
+
this.plusInsertedBlock = null;
|
|
1180
|
+
|
|
1095
1181
|
const { BlockManager, Caret } = this.Blok;
|
|
1096
1182
|
const newBlock = BlockManager.getBlockById(block.id);
|
|
1097
1183
|
|
|
@@ -1142,6 +1228,12 @@ export class Toolbar extends Module<ToolbarNodes> {
|
|
|
1142
1228
|
|
|
1143
1229
|
if (plusButton) {
|
|
1144
1230
|
this.readOnlyMutableListeners.on(plusButton, 'mousedown', (e) => {
|
|
1231
|
+
/**
|
|
1232
|
+
* Prevent focus from moving away from the currently-active contenteditable block.
|
|
1233
|
+
* Without this, clicking the plus button steals DOM focus, causing subsequent
|
|
1234
|
+
* keystrokes to land in the wrong block (text-jumping bug).
|
|
1235
|
+
*/
|
|
1236
|
+
(e as MouseEvent).preventDefault();
|
|
1145
1237
|
hide();
|
|
1146
1238
|
|
|
1147
1239
|
this.clickDragHandler.setup(
|
|
@@ -330,11 +330,8 @@ export class InlineToolbar extends Module<InlineToolbarNodes> {
|
|
|
330
330
|
const popoverItems = await this.buildPopoverItems();
|
|
331
331
|
|
|
332
332
|
// Create popover
|
|
333
|
-
const scopeElement = this.Blok.API?.methods?.ui?.nodes?.redactor ?? this.Blok.UI.nodes.redactor;
|
|
334
|
-
|
|
335
333
|
this.popover = new PopoverInline({
|
|
336
334
|
items: popoverItems,
|
|
337
|
-
scopeElement,
|
|
338
335
|
messages: {
|
|
339
336
|
nothingFound: this.Blok.I18n.t('popover.nothingFound'),
|
|
340
337
|
search: this.Blok.I18n.t('popover.search'),
|
|
@@ -46,6 +46,13 @@ export class PlusButtonHandler {
|
|
|
46
46
|
*/
|
|
47
47
|
private moveAndOpenToolbar: (block?: Block | null, target?: Element | null) => void;
|
|
48
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Optional callback invoked at the very start of handleClick(), before any
|
|
51
|
+
* block manipulation, with the block that currently has focus.
|
|
52
|
+
* Used by Toolbar to capture the pre-toolbox block for focus restoration on cancel.
|
|
53
|
+
*/
|
|
54
|
+
private onFocusBlockCaptured: ((block: Block | null, insertedBlock: Block | null) => void) | undefined;
|
|
55
|
+
|
|
49
56
|
/**
|
|
50
57
|
* @param getBlok - Function to get Blok modules reference
|
|
51
58
|
* @param callbacks - Object containing callback functions
|
|
@@ -58,6 +65,7 @@ export class PlusButtonHandler {
|
|
|
58
65
|
openToolboxWithoutSlash: () => void;
|
|
59
66
|
closeToolbox: () => void;
|
|
60
67
|
moveAndOpenToolbar: (block?: Block | null, target?: Element | null) => void;
|
|
68
|
+
onFocusBlockCaptured?: (block: Block | null, insertedBlock: Block | null) => void;
|
|
61
69
|
}
|
|
62
70
|
) {
|
|
63
71
|
this.getBlok = getBlok;
|
|
@@ -66,6 +74,7 @@ export class PlusButtonHandler {
|
|
|
66
74
|
this.openToolboxWithoutSlash = callbacks.openToolboxWithoutSlash;
|
|
67
75
|
this.closeToolbox = callbacks.closeToolbox;
|
|
68
76
|
this.moveAndOpenToolbar = callbacks.moveAndOpenToolbar;
|
|
77
|
+
this.onFocusBlockCaptured = callbacks.onFocusBlockCaptured;
|
|
69
78
|
}
|
|
70
79
|
|
|
71
80
|
/**
|
|
@@ -175,6 +184,23 @@ export class PlusButtonHandler {
|
|
|
175
184
|
// If hoveredBlock is not empty (e.g. a table), check if the focused block
|
|
176
185
|
// is empty and nested inside it (e.g. an empty paragraph in a table cell).
|
|
177
186
|
const currentBlock = BlockManager.currentBlock ?? null;
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Capture the block that CURRENTLY HAS DOM FOCUS before any manipulation,
|
|
190
|
+
* so that focus can be restored to it if the user cancels (Escape) without
|
|
191
|
+
* selecting a tool.
|
|
192
|
+
*
|
|
193
|
+
* We cannot rely on BlockManager.currentBlock here: the mousedown event on
|
|
194
|
+
* the plus button (which lives inside the hovered block's DOM) triggers the
|
|
195
|
+
* redactorTouchHandler in capture phase, which calls setCurrentBlockByChildNode
|
|
196
|
+
* and overwrites currentBlock to the hovered block BEFORE our preventDefault
|
|
197
|
+
* or handleClick() runs. Instead we look at the actual DOM-focused element
|
|
198
|
+
* and find which block owns it.
|
|
199
|
+
*/
|
|
200
|
+
const activeEl = document.activeElement;
|
|
201
|
+
const focusedBlockBeforeOpen = activeEl !== null && activeEl !== document.body
|
|
202
|
+
? (BlockManager.getBlockByChildNode(activeEl) ?? null)
|
|
203
|
+
: null;
|
|
178
204
|
const hoveredIsEmpty = hoveredBlock !== null && hoveredBlock.isEmpty;
|
|
179
205
|
const nestedCurrentBlockIsEmpty = !hoveredIsEmpty && currentBlock !== null
|
|
180
206
|
&& currentBlock !== hoveredBlock && currentBlock.isEmpty
|
|
@@ -215,6 +241,17 @@ export class PlusButtonHandler {
|
|
|
215
241
|
hoveredBlock?.holder.after(targetBlock.holder);
|
|
216
242
|
}
|
|
217
243
|
|
|
244
|
+
/**
|
|
245
|
+
* Notify Toolbar of the pre-open focus context.
|
|
246
|
+
* insertedBlock is non-null only when we created a brand-new empty block
|
|
247
|
+
* (not when we're reusing an existing empty block or operating in slash mode).
|
|
248
|
+
* On cancel (Escape), Toolbar will remove the inserted block and restore focus
|
|
249
|
+
* to focusedBlockBeforeOpen.
|
|
250
|
+
*/
|
|
251
|
+
const insertedBlock = (!startsWithSlash && emptyBlockToReuse === null) ? targetBlock : null;
|
|
252
|
+
|
|
253
|
+
this.onFocusBlockCaptured?.(focusedBlockBeforeOpen, insertedBlock);
|
|
254
|
+
|
|
218
255
|
// Position caret and open toolbox
|
|
219
256
|
if (startsWithSlash) {
|
|
220
257
|
// Block already has "/" - keep slash-search mode, position after the slash
|
|
@@ -179,6 +179,12 @@ export class SettingsTogglerHandler {
|
|
|
179
179
|
*/
|
|
180
180
|
public createMousedownHandler(): (e: Event) => void {
|
|
181
181
|
return (e: Event) => {
|
|
182
|
+
/**
|
|
183
|
+
* Prevent focus from moving away from the currently-active contenteditable block.
|
|
184
|
+
* Without this, clicking the settings toggler steals DOM focus, causing subsequent
|
|
185
|
+
* keystrokes to land in the wrong block (text-jumping bug).
|
|
186
|
+
*/
|
|
187
|
+
(e as MouseEvent).preventDefault();
|
|
182
188
|
hide();
|
|
183
189
|
|
|
184
190
|
this.clickDragHandler.setup(
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { SanitizerConfig, ToolConfig, ToolConstructable, ToolSettings } from '../../../types';
|
|
2
2
|
import { Stub } from '../../tools/stub';
|
|
3
3
|
import { Module } from '../__module';
|
|
4
|
+
import { CopyLinkTune } from '../block-tunes/block-tune-copy-link';
|
|
4
5
|
import { DeleteTune } from '../block-tunes/block-tune-delete';
|
|
5
6
|
import { CriticalError } from '../errors/critical';
|
|
6
7
|
import { ConvertInlineTool } from '../inline-tools/inline-tool-convert';
|
|
@@ -272,6 +273,10 @@ export class Tools extends Module {
|
|
|
272
273
|
class: toToolConstructable(DeleteTune),
|
|
273
274
|
isInternal: true,
|
|
274
275
|
},
|
|
276
|
+
copyLink: {
|
|
277
|
+
class: toToolConstructable(CopyLinkTune),
|
|
278
|
+
isInternal: true,
|
|
279
|
+
},
|
|
275
280
|
convertTo: {
|
|
276
281
|
class: toToolConstructable(ConvertInlineTool),
|
|
277
282
|
isInternal: true,
|
|
@@ -29,6 +29,48 @@ export class KeyboardController extends Controller {
|
|
|
29
29
|
*/
|
|
30
30
|
private redactorElement: HTMLElement | null = null;
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Stable handler references for deduplication via Listeners.findOne.
|
|
34
|
+
* Storing as class properties ensures the same function reference is passed
|
|
35
|
+
* to addEventListener on every enable() call, so the Listeners utility can
|
|
36
|
+
* detect and skip duplicate registrations (e.g. when toggleReadOnly calls
|
|
37
|
+
* enable() more than once via requestIdleCallback).
|
|
38
|
+
*/
|
|
39
|
+
private readonly documentKeydownHandler = (event: Event): void => {
|
|
40
|
+
if (event instanceof KeyboardEvent) {
|
|
41
|
+
this.handleKeydown(event);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
private readonly redactorBeforeinputHandler = (): void => {
|
|
46
|
+
this.Blok.YjsManager.markCaretBeforeChange();
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
private readonly redactorKeydownHandler = (event: Event): void => {
|
|
50
|
+
if (!(event instanceof KeyboardEvent)) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const target = event.target;
|
|
55
|
+
|
|
56
|
+
if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Skip events from nested editors
|
|
61
|
+
if (target instanceof Element) {
|
|
62
|
+
const closestEditor = target.closest('[data-blok-testid="blok-editor"]');
|
|
63
|
+
|
|
64
|
+
if (closestEditor !== null && closestEditor !== this.Blok.UI.nodes.wrapper) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (KEYS_REQUIRING_CARET_CAPTURE.has(event.key)) {
|
|
70
|
+
this.Blok.YjsManager.markCaretBeforeChange();
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
32
74
|
constructor(options: {
|
|
33
75
|
config: Controller['config'];
|
|
34
76
|
eventsDispatcher: Controller['eventsDispatcher'];
|
|
@@ -54,34 +96,20 @@ export class KeyboardController extends Controller {
|
|
|
54
96
|
}
|
|
55
97
|
|
|
56
98
|
// Document-level keydown handler
|
|
57
|
-
this.readOnlyMutableListeners.on(document, 'keydown',
|
|
58
|
-
if (event instanceof KeyboardEvent) {
|
|
59
|
-
this.handleKeydown(event);
|
|
60
|
-
}
|
|
61
|
-
}, true);
|
|
99
|
+
this.readOnlyMutableListeners.on(document, 'keydown', this.documentKeydownHandler, true);
|
|
62
100
|
|
|
63
101
|
/**
|
|
64
102
|
* Capture caret position before any input changes the DOM.
|
|
65
103
|
* This ensures undo/redo restores the caret to the correct position.
|
|
66
104
|
*/
|
|
67
|
-
this.readOnlyMutableListeners.on(this.redactorElement, 'beforeinput',
|
|
68
|
-
this.Blok.YjsManager.markCaretBeforeChange();
|
|
69
|
-
}, true);
|
|
105
|
+
this.readOnlyMutableListeners.on(this.redactorElement, 'beforeinput', this.redactorBeforeinputHandler, true);
|
|
70
106
|
|
|
71
107
|
/**
|
|
72
108
|
* Capture caret position on keydown for keys that tools commonly intercept.
|
|
73
109
|
* Uses capture phase to run before tool handlers.
|
|
74
110
|
* markCaretBeforeChange() is idempotent - if beforeinput also fires, the second call is ignored.
|
|
75
111
|
*/
|
|
76
|
-
this.readOnlyMutableListeners.on(this.redactorElement, 'keydown',
|
|
77
|
-
if (!(event instanceof KeyboardEvent)) {
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (KEYS_REQUIRING_CARET_CAPTURE.has(event.key)) {
|
|
82
|
-
this.Blok.YjsManager.markCaretBeforeChange();
|
|
83
|
-
}
|
|
84
|
-
}, true);
|
|
112
|
+
this.readOnlyMutableListeners.on(this.redactorElement, 'keydown', this.redactorKeydownHandler, true);
|
|
85
113
|
}
|
|
86
114
|
|
|
87
115
|
/**
|
|
@@ -89,6 +117,20 @@ export class KeyboardController extends Controller {
|
|
|
89
117
|
* @param event - keyboard event
|
|
90
118
|
*/
|
|
91
119
|
private handleKeydown(event: KeyboardEvent): void {
|
|
120
|
+
const target = event.target;
|
|
121
|
+
|
|
122
|
+
if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (target instanceof Element) {
|
|
127
|
+
const closestEditor = target.closest('[data-blok-testid="blok-editor"]');
|
|
128
|
+
|
|
129
|
+
if (closestEditor !== null && closestEditor !== this.Blok.UI.nodes.wrapper) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
92
134
|
const key = event.key ?? '';
|
|
93
135
|
|
|
94
136
|
switch (key) {
|
|
@@ -260,12 +302,18 @@ export class KeyboardController extends Controller {
|
|
|
260
302
|
|
|
261
303
|
/**
|
|
262
304
|
* Toolbox needs specific Escape handling for caret restoration,
|
|
263
|
-
* so check it before the registry
|
|
305
|
+
* so check it before the registry.
|
|
306
|
+
*
|
|
307
|
+
* stopPropagation() is required here: this handler runs in the capture phase
|
|
308
|
+
* on document, BEFORE the block-level keydown handler. If we let the event
|
|
309
|
+
* continue bubbling after closing the toolbox, the block's keydown handler
|
|
310
|
+
* (navigationMode.handleEscape) will see `toolbox.opened === false` and
|
|
311
|
+
* incorrectly enable navigation mode, which calls `activeElement.blur()`
|
|
312
|
+
* and drops focus to body.
|
|
264
313
|
*/
|
|
265
314
|
if (this.Blok.Toolbar.toolbox.opened) {
|
|
315
|
+
event.stopPropagation();
|
|
266
316
|
this.Blok.Toolbar.toolbox.close();
|
|
267
|
-
this.Blok.BlockManager.currentBlock &&
|
|
268
|
-
this.Blok.Caret.setToBlock(this.Blok.BlockManager.currentBlock, this.Blok.Caret.positions.END);
|
|
269
317
|
|
|
270
318
|
return;
|
|
271
319
|
}
|
|
@@ -313,15 +361,30 @@ export class KeyboardController extends Controller {
|
|
|
313
361
|
|
|
314
362
|
/**
|
|
315
363
|
* If focus is inside editor content and no toolbars are open,
|
|
316
|
-
* enable navigation mode for keyboard-based block navigation
|
|
364
|
+
* enable navigation mode for keyboard-based block navigation.
|
|
365
|
+
*
|
|
366
|
+
* Skip navigation mode when a drag operation is in progress:
|
|
367
|
+
* the drag's own keydown handler (DragController.onKeyDown) must receive
|
|
368
|
+
* this Escape event to announce the cancellation and clean up drag state.
|
|
369
|
+
* Enabling navigation mode here would call blur() on the active element,
|
|
370
|
+
* then the block holder's bubbling keydown handler would see navigation
|
|
371
|
+
* mode enabled and call event.stopPropagation(), preventing DragController
|
|
372
|
+
* from ever receiving the event.
|
|
317
373
|
*/
|
|
318
374
|
const target = event.target;
|
|
319
375
|
const isTargetElement = target instanceof HTMLElement;
|
|
320
376
|
const isInsideRedactor = this.redactorElement && isTargetElement && this.redactorElement.contains(target);
|
|
321
377
|
const hasCurrentBlock = this.Blok.BlockManager.currentBlock !== undefined;
|
|
322
378
|
|
|
323
|
-
if (isInsideRedactor && hasCurrentBlock) {
|
|
379
|
+
if (isInsideRedactor && hasCurrentBlock && !this.Blok.DragManager.isDragging) {
|
|
324
380
|
event.preventDefault();
|
|
381
|
+
/**
|
|
382
|
+
* Stop propagation so the block holder's bubble keydown handler (blockEvents.keydown)
|
|
383
|
+
* does not see this same Escape event. Without this, the block-level NavigationMode
|
|
384
|
+
* composer's handleKey() would receive the event AFTER navigation mode is enabled,
|
|
385
|
+
* see navigationModeEnabled=true + key='Escape', and immediately disable it.
|
|
386
|
+
*/
|
|
387
|
+
event.stopPropagation();
|
|
325
388
|
this.Blok.Toolbar.close();
|
|
326
389
|
this.Blok.BlockSelection.enableNavigationMode();
|
|
327
390
|
|
|
@@ -167,10 +167,22 @@ export class SelectionController extends Controller {
|
|
|
167
167
|
* @returns true if current block should be updated
|
|
168
168
|
*/
|
|
169
169
|
private shouldUpdateCurrentBlock(): boolean {
|
|
170
|
+
const focusedElement = Selection.anchorElement;
|
|
171
|
+
|
|
172
|
+
if (!focusedElement || !this.wrapperElement) {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
|
|
170
176
|
/**
|
|
171
|
-
*
|
|
172
|
-
*
|
|
177
|
+
* Skip updating current block when focus is inside a nested editor instance.
|
|
178
|
+
* The closest editor wrapper must match this instance's wrapper.
|
|
173
179
|
*/
|
|
180
|
+
const closestEditor = focusedElement.closest('[data-blok-testid="blok-editor"]');
|
|
181
|
+
|
|
182
|
+
if (closestEditor !== null && closestEditor !== this.wrapperElement) {
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
|
|
174
186
|
return true;
|
|
175
187
|
}
|
|
176
188
|
}
|
|
@@ -193,6 +193,28 @@ export class DocumentStore {
|
|
|
193
193
|
}, 'local');
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
+
/**
|
|
197
|
+
* Update a block's edit metadata fields directly on the Y.Map.
|
|
198
|
+
* @param id - Block id
|
|
199
|
+
* @param lastEditedAt - Timestamp in milliseconds
|
|
200
|
+
* @param lastEditedBy - User display name, or null
|
|
201
|
+
*/
|
|
202
|
+
public updateBlockMetadata(id: string, lastEditedAt: number, lastEditedBy: string | null): void {
|
|
203
|
+
const yblock = this.getBlockById(id);
|
|
204
|
+
|
|
205
|
+
if (yblock === undefined) {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
this.transact(() => {
|
|
210
|
+
yblock.set('lastEditedAt', lastEditedAt);
|
|
211
|
+
|
|
212
|
+
if (lastEditedBy !== null) {
|
|
213
|
+
yblock.set('lastEditedBy', lastEditedBy);
|
|
214
|
+
}
|
|
215
|
+
}, 'local');
|
|
216
|
+
}
|
|
217
|
+
|
|
196
218
|
/**
|
|
197
219
|
* Find block index by id.
|
|
198
220
|
* @param id - Block id to find
|
|
@@ -164,6 +164,16 @@ export class YjsManager extends Module {
|
|
|
164
164
|
this.documentStore.updateBlockTune(id, tuneName, tuneData);
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
+
/**
|
|
168
|
+
* Update a block's edit metadata.
|
|
169
|
+
* @param id - Block id
|
|
170
|
+
* @param lastEditedAt - Timestamp in milliseconds
|
|
171
|
+
* @param lastEditedBy - User display name, or null
|
|
172
|
+
*/
|
|
173
|
+
public updateBlockMetadata(id: string, lastEditedAt: number, lastEditedBy: string | null): void {
|
|
174
|
+
this.documentStore.updateBlockMetadata(id, lastEditedAt, lastEditedBy);
|
|
175
|
+
}
|
|
176
|
+
|
|
167
177
|
/**
|
|
168
178
|
* Get block Y.Map by id.
|
|
169
179
|
* @param id - Block id
|
|
@@ -74,6 +74,14 @@ export class YBlockSerializer {
|
|
|
74
74
|
yblock.set('contentIds', Y.Array.from(blockData.content));
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
if (blockData.lastEditedAt !== undefined) {
|
|
78
|
+
yblock.set('lastEditedAt', blockData.lastEditedAt);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (blockData.lastEditedBy !== undefined) {
|
|
82
|
+
yblock.set('lastEditedBy', blockData.lastEditedBy);
|
|
83
|
+
}
|
|
84
|
+
|
|
77
85
|
return yblock;
|
|
78
86
|
}
|
|
79
87
|
|
|
@@ -122,6 +130,18 @@ export class YBlockSerializer {
|
|
|
122
130
|
block.content = contentIds.toArray();
|
|
123
131
|
}
|
|
124
132
|
|
|
133
|
+
const lastEditedAt = yblock.get('lastEditedAt');
|
|
134
|
+
|
|
135
|
+
if (typeof lastEditedAt === 'number') {
|
|
136
|
+
block.lastEditedAt = lastEditedAt;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const lastEditedBy = yblock.get('lastEditedBy');
|
|
140
|
+
|
|
141
|
+
if (typeof lastEditedBy === 'string') {
|
|
142
|
+
block.lastEditedBy = lastEditedBy;
|
|
143
|
+
}
|
|
144
|
+
|
|
125
145
|
return block;
|
|
126
146
|
}
|
|
127
147
|
|
|
@@ -46,8 +46,18 @@ export class SelectionCursor {
|
|
|
46
46
|
// Focus contenteditable elements explicitly after setting the selection range.
|
|
47
47
|
// Placed after addRange() so the selection is preserved when focus transfers —
|
|
48
48
|
// calling focus() before addRange() can reset the caret during arrow navigation.
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
//
|
|
50
|
+
// When `element` is a text node or a non-focusable inline element (e.g. <b>, <span>),
|
|
51
|
+
// `isContentEditable` will be false. In that case we walk up to the nearest
|
|
52
|
+
// contenteditable ancestor so that DOM focus is transferred there. Without this,
|
|
53
|
+
// focus stays on whatever had it before (e.g. the toolbox search input), causing
|
|
54
|
+
// subsequent keystrokes to land in the wrong place.
|
|
55
|
+
const focusTarget = $.isContentEditable(element)
|
|
56
|
+
? element
|
|
57
|
+
: (element.parentElement?.closest('[contenteditable="true"]') as HTMLElement | null) ?? null;
|
|
58
|
+
|
|
59
|
+
if (focusTarget !== null && document.activeElement !== focusTarget) {
|
|
60
|
+
focusTarget.focus();
|
|
51
61
|
}
|
|
52
62
|
|
|
53
63
|
return range.getBoundingClientRect();
|
|
@@ -341,13 +341,17 @@ export class Toolbox extends EventsDispatcher<ToolboxEventMap> {
|
|
|
341
341
|
this.popover?.show();
|
|
342
342
|
|
|
343
343
|
/**
|
|
344
|
-
* When opening toolbox inside a table cell
|
|
345
|
-
* instead of at the trigger element (which is outside
|
|
344
|
+
* When opening toolbox inside a table cell or a nested block (toggle, callout),
|
|
345
|
+
* position it at the caret instead of at the trigger element (which is outside
|
|
346
|
+
* the nested container).
|
|
346
347
|
* Must be called after show() so the popover is in the DOM.
|
|
347
348
|
*/
|
|
348
|
-
const
|
|
349
|
+
const triggerRect = this.triggerElement?.getBoundingClientRect();
|
|
350
|
+
const triggerHidden = triggerRect?.height === 0;
|
|
351
|
+
const triggerOffScreen = triggerRect !== undefined && triggerRect.bottom < 0;
|
|
352
|
+
const isInsideNestedBlock = currentBlock !== undefined && currentBlock.parentId !== null;
|
|
349
353
|
|
|
350
|
-
if ((this.isInsideTableCell || triggerHidden) && this.popover instanceof PopoverDesktop) {
|
|
354
|
+
if ((this.isInsideTableCell || triggerHidden || triggerOffScreen || isInsideNestedBlock) && this.popover instanceof PopoverDesktop) {
|
|
351
355
|
const caretRect = SelectionUtils.rect;
|
|
352
356
|
|
|
353
357
|
this.popover.updatePosition(caretRect);
|
|
@@ -381,6 +385,18 @@ export class Toolbox extends EventsDispatcher<ToolboxEventMap> {
|
|
|
381
385
|
|
|
382
386
|
this.stopListeningToBlockInput();
|
|
383
387
|
this.popover?.hide();
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Only emit Closed event when the toolbox was actually open.
|
|
391
|
+
* This prevents spurious Closed events (and their side-effects such as
|
|
392
|
+
* caret restoration) when close() is called as routine cleanup (e.g.
|
|
393
|
+
* during cross-block selection, block deletion, or toolbar dismissal)
|
|
394
|
+
* even though the toolbox was never shown.
|
|
395
|
+
*/
|
|
396
|
+
if (!this.opened) {
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
|
|
384
400
|
this.opened = false;
|
|
385
401
|
this.emit(ToolboxEvent.Closed);
|
|
386
402
|
}
|
|
@@ -411,7 +427,6 @@ export class Toolbox extends EventsDispatcher<ToolboxEventMap> {
|
|
|
411
427
|
const PopoverClass = isMobileScreen() ? PopoverMobile : PopoverDesktop;
|
|
412
428
|
|
|
413
429
|
this.popover = new PopoverClass({
|
|
414
|
-
scopeElement: this.api.ui.nodes.redactor,
|
|
415
430
|
trigger: this.triggerElement || this.nodes.toolbox,
|
|
416
431
|
leftAlignElement: this.leftAlignElement,
|
|
417
432
|
messages: {
|
|
@@ -447,6 +462,17 @@ export class Toolbox extends EventsDispatcher<ToolboxEventMap> {
|
|
|
447
462
|
* Handles popover close event
|
|
448
463
|
*/
|
|
449
464
|
private onPopoverClose = (): void => {
|
|
465
|
+
/**
|
|
466
|
+
* Only handle the Closed event when the toolbox was actually open.
|
|
467
|
+
* The popover can fire Closed during routine cleanup (e.g. when Toolbar.close()
|
|
468
|
+
* is called unconditionally as part of CBS, block deletion, etc.), even though
|
|
469
|
+
* the toolbox was never shown. Emitting ToolboxEvent.Closed in those cases
|
|
470
|
+
* triggers side-effects (like caret restoration) that break cross-block selection.
|
|
471
|
+
*/
|
|
472
|
+
if (!this.opened) {
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
|
|
450
476
|
if (this.isInsideTableCell) {
|
|
451
477
|
this.toggleRestrictedToolsHidden(false);
|
|
452
478
|
this.isInsideTableCell = false;
|
|
@@ -20,6 +20,17 @@ export const generateBlockId = (): string => {
|
|
|
20
20
|
return nanoid(idLen);
|
|
21
21
|
};
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Nanoid-compatible block ID pattern: exactly 10 URL-safe characters (A-Z, a-z, 0-9, _, -)
|
|
25
|
+
*/
|
|
26
|
+
const BLOCK_ID_PATTERN = /^[A-Za-z0-9_-]{10}$/;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Returns true if the given string is a valid block ID (nanoid format).
|
|
30
|
+
* @param id - string to check
|
|
31
|
+
*/
|
|
32
|
+
export const isValidBlockId = (id: string): boolean => BLOCK_ID_PATTERN.test(id);
|
|
33
|
+
|
|
23
34
|
/**
|
|
24
35
|
* Returns random generated identifier
|
|
25
36
|
* @param prefix - identifier prefix
|