@jackuait/blok 0.10.3 → 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-3wc3aInM.mjs → blok-NcdNQ0I6.mjs} +2364 -2178
- package/dist/chunks/{constants-Bp622jic.mjs → constants-DtfShkXT.mjs} +318 -227
- 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-BC1jRfoS.mjs → tools-DMSi-3RW.mjs} +3445 -1302
- 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/inline/index.ts +0 -3
- package/src/components/modules/tools.ts +5 -0
- package/src/components/modules/uiControllers/controllers/keyboard.ts +29 -0
- 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/ui/toolbox.ts +0 -1
- 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 +4 -2
- 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/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
|
@@ -91,14 +91,14 @@ export class KeyboardNavigation extends BlockEventComposer {
|
|
|
91
91
|
return;
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
|
|
94
|
+
event.shiftKey ? Caret.navigatePrevious(true) : Caret.navigateNext(true);
|
|
95
95
|
|
|
96
96
|
/**
|
|
97
|
-
*
|
|
97
|
+
* Always prevent default Tab behaviour to keep focus inside the editor.
|
|
98
|
+
* Without this, pressing Tab on the last block of a nested editor (e.g. a
|
|
99
|
+
* database card drawer) lets the browser move focus out of the editor entirely.
|
|
98
100
|
*/
|
|
99
|
-
|
|
100
|
-
event.preventDefault();
|
|
101
|
-
}
|
|
101
|
+
event.preventDefault();
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
/**
|
|
@@ -300,6 +300,17 @@ export class BlockManager extends Module {
|
|
|
300
300
|
eventsDispatcher: this.eventsDispatcher,
|
|
301
301
|
getBlockIndex: (block) => this.repository.getBlockIndex(block),
|
|
302
302
|
onBlockMutated: this.blockDidMutated.bind(this),
|
|
303
|
+
shouldHandleEvent: (event: Event) => {
|
|
304
|
+
const target = event.target;
|
|
305
|
+
|
|
306
|
+
if (target instanceof Element) {
|
|
307
|
+
const closestEditor = target.closest('[data-blok-testid="blok-editor"]');
|
|
308
|
+
|
|
309
|
+
return closestEditor === null || closestEditor === this.Blok.UI.nodes.wrapper;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return true;
|
|
313
|
+
},
|
|
303
314
|
});
|
|
304
315
|
|
|
305
316
|
// Initialize factory
|
|
@@ -438,6 +449,8 @@ export class BlockManager extends Module {
|
|
|
438
449
|
parentId?: string;
|
|
439
450
|
contentIds?: string[];
|
|
440
451
|
bindEventsImmediately?: boolean;
|
|
452
|
+
lastEditedAt?: number;
|
|
453
|
+
lastEditedBy?: string | null;
|
|
441
454
|
}): Block {
|
|
442
455
|
return this.factory.composeBlock(options);
|
|
443
456
|
}
|
|
@@ -1086,6 +1099,11 @@ export class BlockManager extends Module {
|
|
|
1086
1099
|
// Also skip if a pointer drag is active — the browser can mutate contenteditable DOM across
|
|
1087
1100
|
// cell boundaries during a drag, and we must not write that corrupted state to Yjs.
|
|
1088
1101
|
if (mutationType === BlockChangedMutationType && !this.yjsSync.isSyncingFromYjs && !this._isPointerDragActive) {
|
|
1102
|
+
// eslint-disable-next-line no-param-reassign
|
|
1103
|
+
block.lastEditedAt = Date.now();
|
|
1104
|
+
// eslint-disable-next-line no-param-reassign
|
|
1105
|
+
block.lastEditedBy = this.config.user?.name ?? null;
|
|
1106
|
+
|
|
1089
1107
|
void this.syncBlockDataToYjs(block);
|
|
1090
1108
|
}
|
|
1091
1109
|
|
|
@@ -1140,5 +1158,9 @@ export class BlockManager extends Module {
|
|
|
1140
1158
|
for (const [key, value] of Object.entries(savedData.data)) {
|
|
1141
1159
|
this.Blok.YjsManager.updateBlockData(block.id, key, value);
|
|
1142
1160
|
}
|
|
1161
|
+
|
|
1162
|
+
if (block.lastEditedAt !== undefined) {
|
|
1163
|
+
this.Blok.YjsManager.updateBlockMetadata(block.id, block.lastEditedAt, block.lastEditedBy);
|
|
1164
|
+
}
|
|
1143
1165
|
}
|
|
1144
1166
|
}
|
|
@@ -51,6 +51,8 @@ export interface BlockEventBinderDependencies {
|
|
|
51
51
|
getBlockIndex: (block: Block) => number;
|
|
52
52
|
/** Callback when block is mutated */
|
|
53
53
|
onBlockMutated: BlockMutationCallback;
|
|
54
|
+
/** Check if an event should be handled by this editor instance */
|
|
55
|
+
shouldHandleEvent?: (event: Event) => boolean;
|
|
54
56
|
}
|
|
55
57
|
|
|
56
58
|
/**
|
|
@@ -76,22 +78,31 @@ export class BlockEventBinder {
|
|
|
76
78
|
* @param block - Block to bind events to
|
|
77
79
|
*/
|
|
78
80
|
public bindBlockEvents(block: Block): void {
|
|
79
|
-
const { blockEvents, listeners, onBlockMutated, getBlockIndex } = this.dependencies;
|
|
81
|
+
const { blockEvents, listeners, onBlockMutated, getBlockIndex, shouldHandleEvent } = this.dependencies;
|
|
80
82
|
|
|
81
83
|
listeners.on(block.holder, 'keydown', (event: Event) => {
|
|
82
84
|
if (event instanceof KeyboardEvent) {
|
|
85
|
+
if (shouldHandleEvent && !shouldHandleEvent(event)) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
83
88
|
blockEvents.keydown(event);
|
|
84
89
|
}
|
|
85
90
|
});
|
|
86
91
|
|
|
87
92
|
listeners.on(block.holder, 'keyup', (event: Event) => {
|
|
88
93
|
if (event instanceof KeyboardEvent) {
|
|
94
|
+
if (shouldHandleEvent && !shouldHandleEvent(event)) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
89
97
|
blockEvents.keyup(event);
|
|
90
98
|
}
|
|
91
99
|
});
|
|
92
100
|
|
|
93
101
|
listeners.on(block.holder, 'input', (event: Event) => {
|
|
94
102
|
if (event instanceof InputEvent) {
|
|
103
|
+
if (shouldHandleEvent && !shouldHandleEvent(event)) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
95
106
|
blockEvents.input(event);
|
|
96
107
|
}
|
|
97
108
|
});
|
|
@@ -64,6 +64,8 @@ export class BlockFactory {
|
|
|
64
64
|
parentId,
|
|
65
65
|
contentIds,
|
|
66
66
|
bindEventsImmediately = false,
|
|
67
|
+
lastEditedAt,
|
|
68
|
+
lastEditedBy,
|
|
67
69
|
} = options;
|
|
68
70
|
|
|
69
71
|
const tool = this.dependencies.tools.get(name);
|
|
@@ -82,6 +84,8 @@ export class BlockFactory {
|
|
|
82
84
|
parentId,
|
|
83
85
|
contentIds,
|
|
84
86
|
bindMutationWatchersImmediately: bindEventsImmediately,
|
|
87
|
+
lastEditedAt,
|
|
88
|
+
lastEditedBy,
|
|
85
89
|
}, this.dependencies.eventsDispatcher);
|
|
86
90
|
|
|
87
91
|
if (this.readOnlyState) {
|
|
@@ -26,6 +26,10 @@ export interface ComposeBlockOptions {
|
|
|
26
26
|
contentIds?: string[];
|
|
27
27
|
/** Bind events immediately instead of deferring via requestIdleCallback */
|
|
28
28
|
bindEventsImmediately?: boolean;
|
|
29
|
+
/** Timestamp of the last edit (milliseconds since epoch) */
|
|
30
|
+
lastEditedAt?: number;
|
|
31
|
+
/** Display name of the user who last edited this block */
|
|
32
|
+
lastEditedBy?: string | null;
|
|
29
33
|
}
|
|
30
34
|
|
|
31
35
|
/**
|
|
@@ -247,6 +247,8 @@ export class BlockYjsSync {
|
|
|
247
247
|
const data = this.dependencies.YjsManager.yMapToObject(yblock.get('data') as YMap<unknown>);
|
|
248
248
|
const ytunes = yblock.get('tunes') as YMap<unknown> | undefined;
|
|
249
249
|
const tunes = ytunes !== undefined ? this.dependencies.YjsManager.yMapToObject(ytunes) : {};
|
|
250
|
+
const lastEditedAt = yblock.get('lastEditedAt') as number | undefined;
|
|
251
|
+
const lastEditedBy = (yblock.get('lastEditedBy') as string | undefined) ?? null;
|
|
250
252
|
|
|
251
253
|
// Check if tunes have changed - if so, we need to recreate the block
|
|
252
254
|
// because tunes are instantiated during block construction
|
|
@@ -267,6 +269,8 @@ export class BlockYjsSync {
|
|
|
267
269
|
contentIds: block.contentIds.length > 0 ? [...block.contentIds] : undefined,
|
|
268
270
|
parentId: block.parentId ?? undefined,
|
|
269
271
|
bindEventsImmediately: true,
|
|
272
|
+
lastEditedAt,
|
|
273
|
+
lastEditedBy,
|
|
270
274
|
});
|
|
271
275
|
|
|
272
276
|
// Use atomic operation with RAF extension to prevent DOM mutation observers
|
|
@@ -298,6 +302,8 @@ export class BlockYjsSync {
|
|
|
298
302
|
contentIds: block.contentIds.length > 0 ? [...block.contentIds] : undefined,
|
|
299
303
|
parentId: block.parentId ?? undefined,
|
|
300
304
|
bindEventsImmediately: true,
|
|
305
|
+
lastEditedAt,
|
|
306
|
+
lastEditedBy,
|
|
301
307
|
});
|
|
302
308
|
|
|
303
309
|
this.handlers.replaceBlock(blockIndex, newBlock);
|
|
@@ -330,6 +336,8 @@ export class BlockYjsSync {
|
|
|
330
336
|
const toolName = yblock.get('type') as string;
|
|
331
337
|
const data = this.dependencies.YjsManager.yMapToObject(yblock.get('data') as YMap<unknown>);
|
|
332
338
|
const parentId = yblock.get('parentId') as string | undefined;
|
|
339
|
+
const lastEditedAt = yblock.get('lastEditedAt') as number | undefined;
|
|
340
|
+
const lastEditedBy = (yblock.get('lastEditedBy') as string | undefined) ?? null;
|
|
333
341
|
|
|
334
342
|
// Find the index of this block in Yjs to insert at correct position
|
|
335
343
|
const yjsBlocks = this.dependencies.YjsManager.toJSON();
|
|
@@ -350,6 +358,8 @@ export class BlockYjsSync {
|
|
|
350
358
|
data,
|
|
351
359
|
parentId: parentId ?? undefined,
|
|
352
360
|
bindEventsImmediately: true,
|
|
361
|
+
lastEditedAt,
|
|
362
|
+
lastEditedBy,
|
|
353
363
|
});
|
|
354
364
|
|
|
355
365
|
this.blocksStore.insert(targetIndex, block);
|
|
@@ -438,7 +448,7 @@ export class BlockYjsSync {
|
|
|
438
448
|
const yjsBlocks = this.dependencies.YjsManager.toJSON();
|
|
439
449
|
|
|
440
450
|
// Collect blocks to create — skip any that already exist
|
|
441
|
-
const toCreate: Array<{ blockId: string; toolName: string; data: Record<string, unknown>; parentId: string | undefined; targetIndex: number }> = [];
|
|
451
|
+
const toCreate: Array<{ blockId: string; toolName: string; data: Record<string, unknown>; parentId: string | undefined; lastEditedAt: number | undefined; lastEditedBy: string | null; targetIndex: number }> = [];
|
|
442
452
|
|
|
443
453
|
for (const blockId of blockIds) {
|
|
444
454
|
if (this.repository.getBlockById(blockId) !== undefined) {
|
|
@@ -454,13 +464,15 @@ export class BlockYjsSync {
|
|
|
454
464
|
const toolName = yblock.get('type') as string;
|
|
455
465
|
const data = this.dependencies.YjsManager.yMapToObject(yblock.get('data') as YMap<unknown>);
|
|
456
466
|
const parentId = yblock.get('parentId') as string | undefined;
|
|
467
|
+
const lastEditedAt = yblock.get('lastEditedAt') as number | undefined;
|
|
468
|
+
const lastEditedBy = (yblock.get('lastEditedBy') as string | undefined) ?? null;
|
|
457
469
|
const targetIndex = yjsBlocks.findIndex((b) => b.id === blockId);
|
|
458
470
|
|
|
459
471
|
if (targetIndex === -1) {
|
|
460
472
|
continue;
|
|
461
473
|
}
|
|
462
474
|
|
|
463
|
-
toCreate.push({ blockId, toolName, data, parentId: parentId ?? undefined, targetIndex });
|
|
475
|
+
toCreate.push({ blockId, toolName, data, parentId: parentId ?? undefined, lastEditedAt, lastEditedBy, targetIndex });
|
|
464
476
|
}
|
|
465
477
|
|
|
466
478
|
if (toCreate.length === 0) {
|
|
@@ -478,6 +490,8 @@ export class BlockYjsSync {
|
|
|
478
490
|
data: entry.data,
|
|
479
491
|
parentId: entry.parentId,
|
|
480
492
|
bindEventsImmediately: true,
|
|
493
|
+
lastEditedAt: entry.lastEditedAt,
|
|
494
|
+
lastEditedBy: entry.lastEditedBy,
|
|
481
495
|
});
|
|
482
496
|
|
|
483
497
|
this.blocksStore.addToArray(entry.targetIndex, block);
|
|
@@ -85,7 +85,9 @@ function isDefaultBlack(color: string): boolean {
|
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
/**
|
|
88
|
-
* Compute the relative luminance of a CSS color value
|
|
88
|
+
* Compute the relative luminance of a CSS color value.
|
|
89
|
+
* Supports rgb(), rgba(), hsl(), hsla(), and hex (#rrggbb / #rgb) formats.
|
|
90
|
+
* Alpha components are ignored — only the base RGB channels are used.
|
|
89
91
|
* Returns a value in [0, 1], or -1 if the format is unrecognized.
|
|
90
92
|
* Uses simplified linear luminance (no gamma correction), adequate for
|
|
91
93
|
* threshold comparisons at this scale.
|
|
@@ -93,7 +95,8 @@ function isDefaultBlack(color: string): boolean {
|
|
|
93
95
|
function computeRelativeLuminance(color: string): number {
|
|
94
96
|
const normalized = color.replace(/\s/g, '').toLowerCase();
|
|
95
97
|
|
|
96
|
-
|
|
98
|
+
/* rgb() and rgba() — alpha component is optional and ignored */
|
|
99
|
+
const rgbMatch = /^rgba?\((\d+),(\d+),(\d+)(?:,[\d.]+)?\)$/.exec(normalized);
|
|
97
100
|
|
|
98
101
|
if (rgbMatch) {
|
|
99
102
|
const r = parseInt(rgbMatch[1], 10) / 255;
|
|
@@ -103,6 +106,42 @@ function computeRelativeLuminance(color: string): number {
|
|
|
103
106
|
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
104
107
|
}
|
|
105
108
|
|
|
109
|
+
/* hsl() and hsla() — alpha component is optional and ignored */
|
|
110
|
+
const hslMatch = /^hsla?\(([\d.]+),([\d.]+)%,([\d.]+)%(?:,[\d.]+)?\)$/.exec(normalized);
|
|
111
|
+
|
|
112
|
+
if (hslMatch) {
|
|
113
|
+
const h = parseFloat(hslMatch[1]) / 360;
|
|
114
|
+
const s = parseFloat(hslMatch[2]) / 100;
|
|
115
|
+
const l = parseFloat(hslMatch[3]) / 100;
|
|
116
|
+
|
|
117
|
+
if (s === 0) {
|
|
118
|
+
return 0.2126 * l + 0.7152 * l + 0.0722 * l; // achromatic: r = g = b = l
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
|
122
|
+
const p = 2 * l - q;
|
|
123
|
+
const hueToChannel = (t: number): number => {
|
|
124
|
+
const wrapped = (() => {
|
|
125
|
+
if (t < 0) return t + 1;
|
|
126
|
+
if (t > 1) return t - 1;
|
|
127
|
+
|
|
128
|
+
return t;
|
|
129
|
+
})();
|
|
130
|
+
|
|
131
|
+
if (wrapped < 1 / 6) { return p + (q - p) * 6 * wrapped; }
|
|
132
|
+
if (wrapped < 1 / 2) { return q; }
|
|
133
|
+
if (wrapped < 2 / 3) { return p + (q - p) * (2 / 3 - wrapped) * 6; }
|
|
134
|
+
|
|
135
|
+
return p;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const r = hueToChannel(h + 1 / 3);
|
|
139
|
+
const g = hueToChannel(h);
|
|
140
|
+
const b = hueToChannel(h - 1 / 3);
|
|
141
|
+
|
|
142
|
+
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
143
|
+
}
|
|
144
|
+
|
|
106
145
|
const hexMatch = /^#([0-9a-f]{6}|[0-9a-f]{3})$/.exec(normalized);
|
|
107
146
|
|
|
108
147
|
if (hexMatch) {
|
|
@@ -156,9 +195,16 @@ function isDefaultDarkBackground(bgColor: string): boolean {
|
|
|
156
195
|
*
|
|
157
196
|
* Uses relative luminance > 0.75, which is above all Blok text presets while
|
|
158
197
|
* catching typical dark mode default text colors.
|
|
198
|
+
*
|
|
199
|
+
* Returns false for unrecognized color formats (luminance === -1) so unknown
|
|
200
|
+
* formats are treated conservatively: they are not filtered out here, but any
|
|
201
|
+
* color that cannot be parsed also cannot be mapped to a preset, so the
|
|
202
|
+
* sanitizer will strip it regardless.
|
|
159
203
|
*/
|
|
160
204
|
function isDefaultLightText(color: string): boolean {
|
|
161
|
-
|
|
205
|
+
const luminance = computeRelativeLuminance(color);
|
|
206
|
+
|
|
207
|
+
return luminance >= 0 && luminance > 0.75;
|
|
162
208
|
}
|
|
163
209
|
|
|
164
210
|
/**
|
|
@@ -41,10 +41,20 @@ export class TableCellsHandler extends BasePasteHandler implements PasteHandler
|
|
|
41
41
|
return false;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
// If cursor is inside a table cell, let the grid paste listener handle it
|
|
44
|
+
// If cursor is inside a table cell, let the grid paste listener handle it.
|
|
45
|
+
// Also bail when focus was lost (e.g. due to a React re-render) but the
|
|
46
|
+
// current block's holder is still inside a table-cell-blocks container —
|
|
47
|
+
// the paste was intended for the table, not for creating a new one.
|
|
48
|
+
// Additionally, bail when the paste event's original target was inside a
|
|
49
|
+
// table cell, even if document.activeElement is now body (covers the case
|
|
50
|
+
// where the event fired on the cell-blocks container itself).
|
|
45
51
|
const activeElement = document.activeElement as HTMLElement | null;
|
|
46
52
|
|
|
47
|
-
if (
|
|
53
|
+
if (
|
|
54
|
+
activeElement?.closest('[data-blok-table-cell]') ||
|
|
55
|
+
context.currentBlock?.holder?.closest('[data-blok-table-cell-blocks]') ||
|
|
56
|
+
context.pasteTarget?.closest('[data-blok-table-cell]')
|
|
57
|
+
) {
|
|
48
58
|
return false;
|
|
49
59
|
}
|
|
50
60
|
|
|
@@ -103,13 +103,13 @@ export class Paste extends Module {
|
|
|
103
103
|
/**
|
|
104
104
|
* Handle pasted data transfer object.
|
|
105
105
|
*/
|
|
106
|
-
public async processDataTransfer(dataTransfer: DataTransfer): Promise<void> {
|
|
106
|
+
public async processDataTransfer(dataTransfer: DataTransfer, pasteTarget?: Element): Promise<void> {
|
|
107
107
|
const blokData = dataTransfer.getData(this.MIME_TYPE);
|
|
108
108
|
const plainData = dataTransfer.getData('text/plain');
|
|
109
109
|
const rawHtmlData = dataTransfer.getData('text/html');
|
|
110
110
|
|
|
111
111
|
// Route to handlers based on data type
|
|
112
|
-
const handled = await this.routeToHandlers(dataTransfer, plainData, rawHtmlData, blokData);
|
|
112
|
+
const handled = await this.routeToHandlers(dataTransfer, plainData, rawHtmlData, blokData, pasteTarget);
|
|
113
113
|
|
|
114
114
|
if (handled) {
|
|
115
115
|
return;
|
|
@@ -126,7 +126,8 @@ export class Paste extends Module {
|
|
|
126
126
|
dataTransfer: DataTransfer,
|
|
127
127
|
plainData: string,
|
|
128
128
|
rawHtmlData: string,
|
|
129
|
-
blokData: string
|
|
129
|
+
blokData: string,
|
|
130
|
+
pasteTarget?: Element
|
|
130
131
|
): Promise<boolean> {
|
|
131
132
|
const { BlockManager } = this.Blok;
|
|
132
133
|
const currentBlock = BlockManager.currentBlock;
|
|
@@ -142,6 +143,7 @@ export class Paste extends Module {
|
|
|
142
143
|
canReplaceCurrentBlock,
|
|
143
144
|
currentBlock: currentBlock ?? undefined,
|
|
144
145
|
plainData,
|
|
146
|
+
pasteTarget,
|
|
145
147
|
};
|
|
146
148
|
|
|
147
149
|
// Try handlers in priority order
|
|
@@ -313,7 +315,9 @@ export class Paste extends Module {
|
|
|
313
315
|
event.preventDefault();
|
|
314
316
|
|
|
315
317
|
if (event.clipboardData) {
|
|
316
|
-
|
|
318
|
+
const pasteTarget = event.target instanceof Element ? event.target : undefined;
|
|
319
|
+
|
|
320
|
+
await this.processDataTransfer(event.clipboardData, pasteTarget);
|
|
317
321
|
}
|
|
318
322
|
|
|
319
323
|
Toolbar.moveAndOpen();
|
|
@@ -3,7 +3,7 @@ import type { StubData } from '../../tools/stub';
|
|
|
3
3
|
import { Module } from '../__module';
|
|
4
4
|
import type { Block } from '../block';
|
|
5
5
|
import type { BlockToolAdapter } from '../tools/block';
|
|
6
|
-
import { log, logLabeled } from '../utils';
|
|
6
|
+
import { generateBlockId, log, logLabeled } from '../utils';
|
|
7
7
|
import {
|
|
8
8
|
analyzeDataFormat,
|
|
9
9
|
expandToHierarchical,
|
|
@@ -94,11 +94,27 @@ export class Renderer extends Module {
|
|
|
94
94
|
|
|
95
95
|
// Note: Yjs data layer is loaded via BlockManager.insertMany() with the correct block IDs
|
|
96
96
|
|
|
97
|
+
/**
|
|
98
|
+
* Track seen IDs to detect and resolve duplicates
|
|
99
|
+
*/
|
|
100
|
+
const seenIds = new Set<string>();
|
|
101
|
+
|
|
97
102
|
/**
|
|
98
103
|
* Create Blocks instances
|
|
99
104
|
*/
|
|
100
105
|
const blocks = processedBlocks.map((blockData: OutputBlockData) => {
|
|
101
|
-
const { tunes,
|
|
106
|
+
const { tunes, parent, content, lastEditedAt, lastEditedBy } = blockData;
|
|
107
|
+
const hasDuplicateId = blockData.id !== undefined && seenIds.has(blockData.id);
|
|
108
|
+
|
|
109
|
+
if (hasDuplicateId) {
|
|
110
|
+
logLabeled(`Duplicate block id «${blockData.id}» replaced with a generated id to ensure uniqueness`, 'warn');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const id = hasDuplicateId ? generateBlockId() : blockData.id;
|
|
114
|
+
|
|
115
|
+
if (id !== undefined) {
|
|
116
|
+
seenIds.add(id);
|
|
117
|
+
}
|
|
102
118
|
const originalTool = blockData.type;
|
|
103
119
|
|
|
104
120
|
/**
|
|
@@ -145,6 +161,8 @@ export class Renderer extends Module {
|
|
|
145
161
|
tunes,
|
|
146
162
|
parentId: parent,
|
|
147
163
|
contentIds: content,
|
|
164
|
+
lastEditedAt,
|
|
165
|
+
lastEditedBy,
|
|
148
166
|
});
|
|
149
167
|
} catch (error) {
|
|
150
168
|
log(`Block «${tool}» skipped because of plugins error`, 'error', {
|
|
@@ -164,6 +182,8 @@ export class Renderer extends Module {
|
|
|
164
182
|
tunes,
|
|
165
183
|
parentId: parent,
|
|
166
184
|
contentIds: content,
|
|
185
|
+
lastEditedAt,
|
|
186
|
+
lastEditedBy,
|
|
167
187
|
});
|
|
168
188
|
}
|
|
169
189
|
};
|
|
@@ -24,6 +24,14 @@ type SaverValidatedData = ValidatedData & {
|
|
|
24
24
|
* Array of child block ids (Notion-like flat-with-references model)
|
|
25
25
|
*/
|
|
26
26
|
contentIds?: string[];
|
|
27
|
+
/**
|
|
28
|
+
* Timestamp of the last edit to this block
|
|
29
|
+
*/
|
|
30
|
+
lastEditedAt?: number;
|
|
31
|
+
/**
|
|
32
|
+
* Identifier of the user who last edited this block
|
|
33
|
+
*/
|
|
34
|
+
lastEditedBy?: string | null;
|
|
27
35
|
};
|
|
28
36
|
|
|
29
37
|
type SanitizableBlockData = SaverValidatedData & Pick<SavedData, 'data' | 'tool'>;
|
|
@@ -163,6 +171,8 @@ export class Saver extends Module {
|
|
|
163
171
|
isValid,
|
|
164
172
|
parentId: block.parentId,
|
|
165
173
|
contentIds: block.contentIds,
|
|
174
|
+
lastEditedAt: block.lastEditedAt,
|
|
175
|
+
lastEditedBy: block.lastEditedBy,
|
|
166
176
|
};
|
|
167
177
|
}
|
|
168
178
|
|
|
@@ -174,7 +184,7 @@ export class Saver extends Module {
|
|
|
174
184
|
private makeOutput(allExtractedData: SaverValidatedData[]): OutputData {
|
|
175
185
|
const extractedBlocks: OutputData['blocks'] = [];
|
|
176
186
|
|
|
177
|
-
allExtractedData.forEach(({ id, tool, data, tunes, isValid, parentId, contentIds }) => {
|
|
187
|
+
allExtractedData.forEach(({ id, tool, data, tunes, isValid, parentId, contentIds, lastEditedAt, lastEditedBy }) => {
|
|
178
188
|
const hasParent = parentId !== undefined && parentId !== null;
|
|
179
189
|
|
|
180
190
|
if (!isValid && !hasParent) {
|
|
@@ -204,6 +214,8 @@ export class Saver extends Module {
|
|
|
204
214
|
|
|
205
215
|
const isTunesEmpty = tunes === undefined || isEmpty(tunes);
|
|
206
216
|
const hasContent = contentIds !== undefined && contentIds.length > 0;
|
|
217
|
+
const hasLastEdited = lastEditedAt !== undefined;
|
|
218
|
+
const hasLastEditedBy = lastEditedBy !== undefined && lastEditedBy !== null;
|
|
207
219
|
|
|
208
220
|
const output: OutputData['blocks'][number] = {
|
|
209
221
|
id,
|
|
@@ -218,6 +230,12 @@ export class Saver extends Module {
|
|
|
218
230
|
...hasContent && {
|
|
219
231
|
content: contentIds,
|
|
220
232
|
},
|
|
233
|
+
...hasLastEdited && {
|
|
234
|
+
lastEditedAt,
|
|
235
|
+
},
|
|
236
|
+
...hasLastEditedBy && {
|
|
237
|
+
lastEditedBy,
|
|
238
|
+
},
|
|
221
239
|
};
|
|
222
240
|
|
|
223
241
|
extractedBlocks.push(output);
|
|
@@ -108,7 +108,9 @@ export class ThemeManager extends Module {
|
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
if (this.mode === 'auto') {
|
|
111
|
-
|
|
111
|
+
if (state.activeInstances <= 1) {
|
|
112
|
+
this.removeAttribute();
|
|
113
|
+
}
|
|
112
114
|
} else {
|
|
113
115
|
document.documentElement.setAttribute(ATTR, this.mode);
|
|
114
116
|
}
|
|
@@ -191,7 +191,6 @@ export class BlockSettings extends Module<BlockSettingsNodes> {
|
|
|
191
191
|
searchable: true,
|
|
192
192
|
trigger: trigger || this.nodes.wrapper,
|
|
193
193
|
items: await this.getTunesItems(block, commonTunes, toolTunes),
|
|
194
|
-
scopeElement: this.Blok.API.methods.ui.nodes.redactor,
|
|
195
194
|
messages: {
|
|
196
195
|
nothingFound: this.Blok.I18n.t('popover.nothingFound'),
|
|
197
196
|
search: this.Blok.I18n.t('popover.search'),
|
|
@@ -431,9 +430,60 @@ export class BlockSettings extends Module<BlockSettingsNodes> {
|
|
|
431
430
|
});
|
|
432
431
|
}
|
|
433
432
|
|
|
433
|
+
if (currentBlock.lastEditedAt !== undefined) {
|
|
434
|
+
items.push({
|
|
435
|
+
type: PopoverItemType.Separator,
|
|
436
|
+
});
|
|
437
|
+
items.push({
|
|
438
|
+
type: PopoverItemType.Html,
|
|
439
|
+
element: this.createEditMetadataFooter(currentBlock),
|
|
440
|
+
name: 'edit-metadata',
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
|
|
434
444
|
return items;
|
|
435
445
|
}
|
|
436
446
|
|
|
447
|
+
/**
|
|
448
|
+
* Creates the "Last edited by..." footer element for block settings.
|
|
449
|
+
* @param block - the block whose metadata to display
|
|
450
|
+
* @returns the footer DOM element
|
|
451
|
+
*/
|
|
452
|
+
private createEditMetadataFooter(block: Block): HTMLElement {
|
|
453
|
+
const container = document.createElement('div');
|
|
454
|
+
|
|
455
|
+
container.classList.add(
|
|
456
|
+
'px-3', 'py-2', 'text-xs', 'leading-snug',
|
|
457
|
+
'text-[color:var(--popover-text-secondary,_#888)]',
|
|
458
|
+
'select-none'
|
|
459
|
+
);
|
|
460
|
+
|
|
461
|
+
const label = document.createElement('div');
|
|
462
|
+
|
|
463
|
+
label.setAttribute('data-edit-meta-label', '');
|
|
464
|
+
|
|
465
|
+
if (block.lastEditedBy !== null && block.lastEditedBy !== undefined) {
|
|
466
|
+
label.textContent = `Last edited by ${block.lastEditedBy}`;
|
|
467
|
+
} else {
|
|
468
|
+
label.textContent = 'Last edited';
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
container.appendChild(label);
|
|
472
|
+
|
|
473
|
+
if (block.lastEditedAt !== undefined) {
|
|
474
|
+
const dateEl = document.createElement('div');
|
|
475
|
+
const formatted = new Intl.DateTimeFormat(undefined, {
|
|
476
|
+
dateStyle: 'medium',
|
|
477
|
+
timeStyle: 'short',
|
|
478
|
+
}).format(new Date(block.lastEditedAt));
|
|
479
|
+
|
|
480
|
+
dateEl.textContent = formatted;
|
|
481
|
+
container.appendChild(dateEl);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
return container;
|
|
485
|
+
}
|
|
486
|
+
|
|
437
487
|
/**
|
|
438
488
|
* Handles popover close event
|
|
439
489
|
*/
|
|
@@ -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'),
|
|
@@ -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,
|
|
@@ -51,6 +51,21 @@ export class KeyboardController extends Controller {
|
|
|
51
51
|
return;
|
|
52
52
|
}
|
|
53
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
|
+
|
|
54
69
|
if (KEYS_REQUIRING_CARET_CAPTURE.has(event.key)) {
|
|
55
70
|
this.Blok.YjsManager.markCaretBeforeChange();
|
|
56
71
|
}
|
|
@@ -102,6 +117,20 @@ export class KeyboardController extends Controller {
|
|
|
102
117
|
* @param event - keyboard event
|
|
103
118
|
*/
|
|
104
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
|
+
|
|
105
134
|
const key = event.key ?? '';
|
|
106
135
|
|
|
107
136
|
switch (key) {
|
|
@@ -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
|
}
|