@jackuait/blok 0.10.0-beta.9 → 0.10.1
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-DDu252IK.mjs → blok-u_68bnlk.mjs} +1617 -1562
- package/dist/chunks/{constants-DMW9a31I.mjs → constants-VDhCUk4c.mjs} +56 -48
- package/dist/chunks/{i18next-loader-CwsYu0n6.mjs → i18next-loader-CDnSPae_.mjs} +1 -1
- package/dist/chunks/{lightweight-i18n-Cvv8CWh4.mjs → lightweight-i18n-DZmo8dAI.mjs} +1 -0
- package/dist/chunks/{messages-DG-4DPmP.mjs → messages-1_6UkKLS.mjs} +1 -0
- package/dist/{messages-CqXtJTpU.mjs → chunks/messages-4Ck88DYZ2.mjs} +1 -0
- package/dist/chunks/{messages-DGL1ySqb2.mjs → messages-8Ld7P_9j2.mjs} +1 -0
- package/dist/{messages-DLX_iBDJ.mjs → chunks/messages-BAlZjPcl.mjs} +1 -0
- package/dist/chunks/{messages-p1mbe__S.mjs → messages-BHMiK51R.mjs} +1 -0
- package/dist/chunks/{messages-Cdf0W9H02.mjs → messages-BJ-vT1SU2.mjs} +1 -0
- package/dist/{messages-Smt4GBbj.mjs → chunks/messages-BK8Cp2d0.mjs} +1 -0
- package/dist/{messages-Ci0KqX-J.mjs → chunks/messages-BKN3YVIj.mjs} +1 -0
- package/dist/chunks/{messages-BXM80fdr2.mjs → messages-BMD37y3q2.mjs} +1 -0
- package/dist/{messages-B19o-Teb.mjs → chunks/messages-BONyZroH.mjs} +1 -0
- package/dist/{messages-BwHs4cm1.mjs → chunks/messages-BRAoJpOu.mjs} +1 -0
- package/dist/{messages-DY4IqlhY.mjs → chunks/messages-BRoa9tGl.mjs} +1 -0
- package/dist/chunks/{messages-RInp1ytX.mjs → messages-BbEW9bQz.mjs} +1 -0
- package/dist/{messages-BIHc0KHY.mjs → chunks/messages-BeGZqQwz.mjs} +1 -0
- package/dist/{messages-CmB406HW.mjs → chunks/messages-BfAcUavP.mjs} +1 -0
- package/dist/chunks/{messages-Cu-Wevxs2.mjs → messages-BgM91Lxm2.mjs} +1 -0
- package/dist/{messages-BYNcD6uR.mjs → chunks/messages-BlxwW7M6.mjs} +1 -0
- package/dist/chunks/{messages-rCd0Rrw6.mjs → messages-Bz0-KNEB.mjs} +1 -0
- package/dist/{messages-7QD-X6XT2.mjs → chunks/messages-C0IFfhnp.mjs} +1 -0
- package/dist/{messages-Dl5Y2-Ia.mjs → chunks/messages-C15z2r5U.mjs} +1 -0
- package/dist/chunks/{messages-MxpWO1db.mjs → messages-C1S9ztpF.mjs} +1 -0
- package/dist/chunks/{messages-8IHf7ZP3.mjs → messages-CC_noR8y.mjs} +1 -0
- package/dist/chunks/{messages-COO5xmcA.mjs → messages-CD_MnBln.mjs} +1 -0
- package/dist/chunks/{messages-BYlSMRkd.mjs → messages-CIfUm1Oa.mjs} +1 -0
- package/dist/{messages-BbYq1pk-.mjs → chunks/messages-CPBN4zWc.mjs} +1 -0
- package/dist/{messages-DPA-mMWC2.mjs → chunks/messages-CQBo3lmL2.mjs} +1 -0
- package/dist/{messages-DnGJD4TL.mjs → chunks/messages-CRF7nNrO.mjs} +1 -0
- package/dist/{messages-D8FQWulF2.mjs → chunks/messages-CTCe595D2.mjs} +1 -0
- package/dist/{messages-BRZX964b2.mjs → chunks/messages-CW35K1pq.mjs} +1 -0
- package/dist/{messages-DnG0ef8t2.mjs → chunks/messages-CZSlfnkO2.mjs} +1 -0
- package/dist/chunks/{messages-iS34FHFB.mjs → messages-ChK7v1PV.mjs} +1 -0
- package/dist/{messages-BiUGXvYr2.mjs → chunks/messages-Clku7Cf-2.mjs} +1 -0
- package/dist/{messages-DIJlIqlQ2.mjs → chunks/messages-CszmHAvQ.mjs} +1 -0
- package/dist/chunks/{messages-DWu1r4gc2.mjs → messages-CvANwuht2.mjs} +1 -0
- package/dist/{messages-nUVjeh7K.mjs → chunks/messages-CxiURE2X.mjs} +1 -0
- package/dist/chunks/{messages-A_MkXDlG.mjs → messages-CxxyR4vY.mjs} +1 -0
- package/dist/{messages-ynAe7ewZ.mjs → chunks/messages-D22e9h7V2.mjs} +1 -0
- package/dist/{messages-DYTTu0O12.mjs → chunks/messages-D7dx_6k8.mjs} +1 -0
- package/dist/chunks/{messages-BA8Iv99Y2.mjs → messages-DBMaLL8b2.mjs} +1 -0
- package/dist/{messages-DbySKTKt2.mjs → chunks/messages-DB_-5Xln.mjs} +1 -0
- package/dist/{messages-CcF4y-E4.mjs → chunks/messages-DEBy3nuJ2.mjs} +1 -0
- package/dist/chunks/{messages-NEqrrYvE2.mjs → messages-DMoERagV2.mjs} +1 -0
- package/dist/chunks/{messages-Bxvi1ebN.mjs → messages-DPzHD51Y.mjs} +1 -0
- package/dist/chunks/{messages-jfVpL9c-2.mjs → messages-DSrdy9Nw2.mjs} +1 -0
- package/dist/chunks/{messages-Cmf6NhSC.mjs → messages-DTN1XGll.mjs} +1 -0
- package/dist/{messages-5jvKxQNu2.mjs → chunks/messages-DUeiPraX.mjs} +1 -0
- package/dist/chunks/{messages-G416eyjY.mjs → messages-DUr9WAkD.mjs} +1 -0
- package/dist/chunks/{messages-Ck81cQkn2.mjs → messages-DVr1sqfI2.mjs} +1 -0
- package/dist/{messages-BYmmMDrN2.mjs → chunks/messages-DjvaiALg2.mjs} +1 -0
- package/dist/chunks/{messages-D55HRx5O2.mjs → messages-DrfRYiM32.mjs} +1 -0
- package/dist/chunks/{messages-B2N4fUi72.mjs → messages-DtoId_bw2.mjs} +1 -0
- package/dist/{messages-Bq3F2Tp_.mjs → chunks/messages-Du2BffA7.mjs} +1 -0
- package/dist/{messages-CjbnogEC.mjs → chunks/messages-DxHh0O8j2.mjs} +1 -0
- package/dist/{messages-BECMxmfX.mjs → chunks/messages-EDMC5ukV.mjs} +1 -0
- package/dist/{messages-BTQPpoM42.mjs → chunks/messages-ElIGUi0O2.mjs} +1 -0
- package/dist/chunks/{messages-BhzzNkN-.mjs → messages-JSQjKQ8I.mjs} +1 -0
- package/dist/{messages-CWIXvnDf2.mjs → chunks/messages-Q7-4ZJLB2.mjs} +1 -0
- package/dist/chunks/{messages-DOuS1Qge.mjs → messages-QMOmwcZb.mjs} +1 -0
- package/dist/{messages-hWwSRF-2.mjs → chunks/messages-QilfinOn2.mjs} +1 -0
- package/dist/{messages-BmAn22OX.mjs → chunks/messages-a07QVz8U.mjs} +1 -0
- package/dist/chunks/{messages-BYxLFj7y.mjs → messages-eFd4YYzt.mjs} +1 -0
- package/dist/chunks/{messages-BSghd0ez.mjs → messages-euM2m3wQ.mjs} +1 -0
- package/dist/chunks/{messages-BVjoM7P0.mjs → messages-kGmxkeFH.mjs} +1 -0
- package/dist/{messages-DMr62KiO2.mjs → chunks/messages-oMc7qugU2.mjs} +1 -0
- package/dist/chunks/{messages-DzTk8bJ5.mjs → messages-sDdNf8O9.mjs} +1 -0
- package/dist/{messages-Bm0Feca1.mjs → chunks/messages-wl8YrvGG.mjs} +1 -0
- package/dist/chunks/{messages-BAsb5CgZ.mjs → messages-zt6zdYWh.mjs} +1 -0
- package/dist/chunks/{tools-XmzH2rgQ.mjs → tools-1ZFajlGN.mjs} +1619 -1307
- package/dist/full.mjs +3 -3
- package/dist/locales.mjs +68 -67
- package/dist/{messages-F2xRoY1w.mjs → messages-2ZWBTerL.mjs} +1 -0
- package/dist/{messages-Dl3Sv6Rq2.mjs → messages-53w0fPZS2.mjs} +1 -0
- package/dist/{chunks/messages-BDZA10kl2.mjs → messages-98nQiC7t2.mjs} +1 -0
- package/dist/{chunks/messages-JyvWu4rf2.mjs → messages-A96tMxeU.mjs} +1 -0
- package/dist/{messages-Ce6KVEbT.mjs → messages-BE_z-zrb.mjs} +1 -0
- package/dist/{chunks/messages-CSJ_zb3a2.mjs → messages-BK_LsgY4.mjs} +1 -0
- package/dist/{messages-CJTy6JZt.mjs → messages-BbJ7ZXY8.mjs} +1 -0
- package/dist/{chunks/messages-DMVXnAYj.mjs → messages-BcVB3osF.mjs} +1 -0
- package/dist/{chunks/messages-CSL-6xfb2.mjs → messages-BckDk9aq2.mjs} +1 -0
- package/dist/{chunks/messages-C0HvoMPb.mjs → messages-Be_2RHZD.mjs} +1 -0
- package/dist/{chunks/messages-Dr0Ekmbz.mjs → messages-BesJaI6A.mjs} +1 -0
- package/dist/{chunks/messages-D3zojZ94.mjs → messages-BiTMwiKH.mjs} +1 -0
- package/dist/{messages-B1ZUQagA.mjs → messages-BmH2cQHQ.mjs} +1 -0
- package/dist/{chunks/messages-Bfnq1xv4.mjs → messages-BrOWqNCu2.mjs} +1 -0
- package/dist/{messages-Dnp9N6RU2.mjs → messages-Brd5R-da2.mjs} +1 -0
- package/dist/{chunks/messages-DJoNVjqP.mjs → messages-C0GSBBCo2.mjs} +1 -0
- package/dist/{messages-Dw__BcTj.mjs → messages-C1vc5584.mjs} +1 -0
- package/dist/{messages-aMXpHt5X2.mjs → messages-C6ONf71u2.mjs} +1 -0
- package/dist/{chunks/messages-BeFqtIrc2.mjs → messages-C7lJg8fy2.mjs} +1 -0
- package/dist/{messages-CSUHBs4c2.mjs → messages-CRNogopy2.mjs} +1 -0
- package/dist/{messages-DLlc9QPw.mjs → messages-CT-Kdas6.mjs} +1 -0
- package/dist/{chunks/messages-DlLXpgWM2.mjs → messages-CTTmWn4Y2.mjs} +1 -0
- package/dist/{messages-D0aw5_0k2.mjs → messages-CZbcxlZt2.mjs} +1 -0
- package/dist/{chunks/messages-Bp8qin1R.mjs → messages-C_Qn9SbQ.mjs} +1 -0
- package/dist/{messages-96iaAUds2.mjs → messages-CdEASHDp2.mjs} +1 -0
- package/dist/{messages-Dy-Y_nEI.mjs → messages-CdduYw-q.mjs} +1 -0
- package/dist/{chunks/messages-Je5YvxiY.mjs → messages-Che99vKP.mjs} +1 -0
- package/dist/{messages-nlhESX9t.mjs → messages-CisR4PNV.mjs} +1 -0
- package/dist/{chunks/messages-BE6lHKwf.mjs → messages-ClGvlFcH2.mjs} +1 -0
- package/dist/{chunks/messages-FWfsxpBz.mjs → messages-CnuH-BZK2.mjs} +1 -0
- package/dist/{chunks/messages-aZcy0JQq2.mjs → messages-D0005ti32.mjs} +1 -0
- package/dist/{messages-B7ieAJBd2.mjs → messages-D05jqBIa2.mjs} +1 -0
- package/dist/{messages-DTh9a8mR.mjs → messages-D0lLw9KM.mjs} +1 -0
- package/dist/{chunks/messages-ihCjSFJI2.mjs → messages-D3rwCtKn.mjs} +1 -0
- package/dist/{chunks/messages-xuqyb6Ff2.mjs → messages-D6VIFnSW.mjs} +1 -0
- package/dist/{chunks/messages-KdawW5Na.mjs → messages-D81w6AmW.mjs} +1 -0
- package/dist/{chunks/messages-BUVhHx0q2.mjs → messages-DBhvm8NK.mjs} +1 -0
- package/dist/{chunks/messages-C7VGpihw.mjs → messages-DK6dA0O2.mjs} +1 -0
- package/dist/{messages-rk-A1Wa42.mjs → messages-DKHbt-7l2.mjs} +1 -0
- package/dist/{messages-BIoeoik5.mjs → messages-DM4Gjc9h.mjs} +1 -0
- package/dist/{chunks/messages-B9kmbUWV.mjs → messages-DODrhcop.mjs} +1 -0
- package/dist/{messages-BsycN_JI2.mjs → messages-DOGbHYv-2.mjs} +1 -0
- package/dist/{chunks/messages-BQYvBqm2.mjs → messages-DQORja0D.mjs} +1 -0
- package/dist/{chunks/messages-CVdpweyf2.mjs → messages-DSmxJWju2.mjs} +1 -0
- package/dist/{messages-CR_L_UtK.mjs → messages-DVL0KZE5.mjs} +1 -0
- package/dist/{chunks/messages-Cs81Z_Bh.mjs → messages-DYuD5-rO.mjs} +1 -0
- package/dist/{chunks/messages-CKBhDGI3.mjs → messages-Ddq3Ce3E2.mjs} +1 -0
- package/dist/{messages-xh2eOLvs.mjs → messages-DfFZ6Yj5.mjs} +1 -0
- package/dist/{chunks/messages-C6Mpiacw.mjs → messages-Dnd5YSWv.mjs} +1 -0
- package/dist/{messages-BJeGJksD.mjs → messages-Do7Xjy0n.mjs} +1 -0
- package/dist/{chunks/messages-C3aX3q0H.mjs → messages-DopaMHC42.mjs} +1 -0
- package/dist/{messages-dv19AkyJ.mjs → messages-DpJGbx3q.mjs} +1 -0
- package/dist/{messages-DBiVgUs2.mjs → messages-DpwMKDV0.mjs} +1 -0
- package/dist/{messages-j7o5rT9s.mjs → messages-Dqu4aX9s.mjs} +1 -0
- package/dist/{chunks/messages-BjadX8jR2.mjs → messages-E8NjqzWq2.mjs} +1 -0
- package/dist/{messages-aWZH50vu2.mjs → messages-JNrYldAa2.mjs} +1 -0
- package/dist/{chunks/messages-B4UMuyjT.mjs → messages-LMaR2_bE.mjs} +1 -0
- package/dist/{messages-E_ZuzGDt.mjs → messages-LYJbLq_F.mjs} +1 -0
- package/dist/{chunks/messages-D9N2MvQx2.mjs → messages-Q5sQeVap2.mjs} +1 -0
- package/dist/{chunks/messages-Bphq_Bt3.mjs → messages-Xc0KUbYl.mjs} +1 -0
- package/dist/{chunks/messages-DlonA3wa.mjs → messages-_PLyRfVw.mjs} +1 -0
- package/dist/{chunks/messages-TRUuyiFB.mjs → messages-apA6BStA.mjs} +1 -0
- package/dist/{chunks/messages-B0vPBsWq.mjs → messages-bkGniiaz.mjs} +1 -0
- package/dist/{messages-DkLU_rWm.mjs → messages-neGD3WGq.mjs} +1 -0
- package/dist/{messages-Ddnj2iTG2.mjs → messages-qbKjjvgd2.mjs} +1 -0
- package/dist/{messages-BiiongNz2.mjs → messages-qfvXgPpu2.mjs} +1 -0
- package/dist/{messages-Dvn35ksS.mjs → messages-uwK7ktqk.mjs} +1 -0
- package/dist/react.mjs +2 -2
- package/dist/tools.mjs +2 -2
- package/package.json +3 -5
- package/src/cli/commands/convert-gdocs/index.ts +26 -0
- package/src/cli/commands/convert-html/block-builder.ts +392 -0
- package/src/cli/commands/convert-html/id-generator.ts +11 -0
- package/src/cli/commands/convert-html/index.ts +23 -0
- package/src/cli/commands/convert-html/preprocessor.ts +422 -0
- package/src/cli/commands/convert-html/sanitizer.ts +93 -0
- package/src/cli/commands/convert-html/types.ts +15 -0
- package/src/cli/index.ts +56 -5
- package/src/components/block/index.ts +58 -10
- package/src/components/constants/data-attributes.ts +10 -0
- package/src/components/i18n/locales/am/messages.json +1 -0
- package/src/components/i18n/locales/ar/messages.json +1 -0
- package/src/components/i18n/locales/az/messages.json +1 -0
- package/src/components/i18n/locales/bg/messages.json +1 -0
- package/src/components/i18n/locales/bn/messages.json +1 -0
- package/src/components/i18n/locales/bs/messages.json +1 -0
- package/src/components/i18n/locales/cs/messages.json +1 -0
- package/src/components/i18n/locales/da/messages.json +1 -0
- package/src/components/i18n/locales/de/messages.json +1 -0
- package/src/components/i18n/locales/dv/messages.json +1 -0
- package/src/components/i18n/locales/el/messages.json +1 -0
- package/src/components/i18n/locales/en/messages.json +1 -0
- package/src/components/i18n/locales/es/messages.json +1 -0
- package/src/components/i18n/locales/et/messages.json +1 -0
- package/src/components/i18n/locales/fa/messages.json +1 -0
- package/src/components/i18n/locales/fi/messages.json +1 -0
- package/src/components/i18n/locales/fil/messages.json +1 -0
- package/src/components/i18n/locales/fr/messages.json +1 -0
- package/src/components/i18n/locales/gu/messages.json +1 -0
- package/src/components/i18n/locales/he/messages.json +1 -0
- package/src/components/i18n/locales/hi/messages.json +1 -0
- package/src/components/i18n/locales/hr/messages.json +1 -0
- package/src/components/i18n/locales/hu/messages.json +1 -0
- package/src/components/i18n/locales/hy/messages.json +1 -0
- package/src/components/i18n/locales/id/messages.json +1 -0
- package/src/components/i18n/locales/it/messages.json +1 -0
- package/src/components/i18n/locales/ja/messages.json +1 -0
- package/src/components/i18n/locales/ka/messages.json +1 -0
- package/src/components/i18n/locales/km/messages.json +1 -0
- package/src/components/i18n/locales/kn/messages.json +1 -0
- package/src/components/i18n/locales/ko/messages.json +1 -0
- package/src/components/i18n/locales/ku/messages.json +1 -0
- package/src/components/i18n/locales/lo/messages.json +1 -0
- package/src/components/i18n/locales/lt/messages.json +1 -0
- package/src/components/i18n/locales/lv/messages.json +1 -0
- package/src/components/i18n/locales/mk/messages.json +1 -0
- package/src/components/i18n/locales/ml/messages.json +1 -0
- package/src/components/i18n/locales/mn/messages.json +1 -0
- package/src/components/i18n/locales/mr/messages.json +1 -0
- package/src/components/i18n/locales/ms/messages.json +1 -0
- package/src/components/i18n/locales/my/messages.json +1 -0
- package/src/components/i18n/locales/ne/messages.json +1 -0
- package/src/components/i18n/locales/nl/messages.json +1 -0
- package/src/components/i18n/locales/no/messages.json +1 -0
- package/src/components/i18n/locales/pa/messages.json +1 -0
- package/src/components/i18n/locales/pl/messages.json +1 -0
- package/src/components/i18n/locales/ps/messages.json +1 -0
- package/src/components/i18n/locales/pt/messages.json +1 -0
- package/src/components/i18n/locales/ro/messages.json +1 -0
- package/src/components/i18n/locales/ru/messages.json +1 -0
- package/src/components/i18n/locales/sd/messages.json +1 -0
- package/src/components/i18n/locales/si/messages.json +1 -0
- package/src/components/i18n/locales/sk/messages.json +1 -0
- package/src/components/i18n/locales/sl/messages.json +1 -0
- package/src/components/i18n/locales/sq/messages.json +1 -0
- package/src/components/i18n/locales/sr/messages.json +1 -0
- package/src/components/i18n/locales/sv/messages.json +1 -0
- package/src/components/i18n/locales/sw/messages.json +1 -0
- package/src/components/i18n/locales/ta/messages.json +1 -0
- package/src/components/i18n/locales/te/messages.json +1 -0
- package/src/components/i18n/locales/th/messages.json +1 -0
- package/src/components/i18n/locales/tr/messages.json +1 -0
- package/src/components/i18n/locales/ug/messages.json +1 -0
- package/src/components/i18n/locales/uk/messages.json +1 -0
- package/src/components/i18n/locales/ur/messages.json +1 -0
- package/src/components/i18n/locales/vi/messages.json +1 -0
- package/src/components/i18n/locales/yi/messages.json +1 -0
- package/src/components/i18n/locales/zh/messages.json +1 -0
- package/src/components/icons/index.ts +29 -18
- package/src/components/modules/blockEvents/composers/keyboardNavigation.ts +18 -0
- package/src/components/modules/blockManager/hierarchy.ts +4 -1
- package/src/components/modules/readonly.ts +46 -0
- package/src/components/modules/rectangleSelection.ts +25 -5
- package/src/components/modules/toolbar/index.ts +96 -19
- package/src/components/modules/toolbar/positioning.ts +11 -2
- package/src/components/modules/toolbar/styles.ts +0 -2
- package/src/components/modules/uiControllers/controllers/blockHover.ts +44 -1
- package/src/components/tools/block.ts +10 -0
- package/src/components/utils/placeholder.ts +9 -2
- package/src/components/utils/popover/components/popover-item/popover-item-default/popover-item-default.ts +11 -0
- package/src/components/utils/popover/popover-abstract.ts +7 -0
- package/src/styles/main.css +16 -0
- package/src/tools/callout/constants.ts +2 -1
- package/src/tools/callout/dom-builder.ts +13 -1
- package/src/tools/callout/index.ts +21 -7
- package/src/tools/code/constants.ts +28 -8
- package/src/tools/code/dom-builder.ts +133 -64
- package/src/tools/code/index.ts +280 -91
- package/src/tools/code/language-detector.ts +118 -0
- package/src/tools/divider/index.ts +5 -0
- package/src/tools/header/index.ts +47 -1
- package/src/tools/list/dom-builder.ts +3 -1
- package/src/tools/list/index.ts +55 -3
- package/src/tools/list/list-helpers.ts +2 -2
- package/src/tools/nested-blocks.ts +25 -0
- package/src/tools/paragraph/index.ts +47 -6
- package/src/tools/quote/index.ts +43 -8
- package/src/tools/stub/index.ts +10 -0
- package/src/tools/table/index.ts +238 -6
- package/src/tools/table/table-add-controls.ts +37 -5
- package/src/tools/table/table-cell-blocks.ts +57 -18
- package/src/tools/table/table-core.ts +2 -0
- package/src/tools/table/table-corner-drag.ts +247 -0
- package/src/tools/table/table-operations.ts +41 -14
- package/src/tools/toggle/dom-builder.ts +1 -0
- package/src/tools/toggle/index.ts +25 -0
- package/src/tools/toggle/toggle-lifecycle.ts +5 -4
- package/src/types-internal/jsdom.d.ts +9 -0
- package/types/tools/adapters/block-tool-adapter.d.ts +6 -0
- package/types/tools/block-tool.d.ts +20 -0
- package/types/utils/popover/popover-item.d.ts +6 -0
- package/bin/blok.mjs +0 -10
- package/dist/cli.mjs +0 -37
- package/src/tools/code/language-picker.ts +0 -241
|
@@ -34,6 +34,13 @@ export class BlockHoverController extends Controller {
|
|
|
34
34
|
*/
|
|
35
35
|
private static readonly HOVER_COOLDOWN_MS = 50;
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Maximum horizontal distance from content edges for extended hover zone.
|
|
39
|
+
* When cursor is within this distance of the content area, nearest-block
|
|
40
|
+
* detection activates. Beyond this distance, no hover event is emitted.
|
|
41
|
+
*/
|
|
42
|
+
private static readonly HOVER_ZONE_SIZE = 100;
|
|
43
|
+
|
|
37
44
|
constructor(options: {
|
|
38
45
|
config: Controller['config'];
|
|
39
46
|
eventsDispatcher: Controller['eventsDispatcher'];
|
|
@@ -104,9 +111,10 @@ export class BlockHoverController extends Controller {
|
|
|
104
111
|
|
|
105
112
|
/**
|
|
106
113
|
* If no block element found directly, find the nearest block by Y distance
|
|
114
|
+
* but only if the cursor is within the extended hover zone (100px from content edges).
|
|
107
115
|
*/
|
|
108
116
|
if (!hoveredBlockElement) {
|
|
109
|
-
this.
|
|
117
|
+
this.emitNearestBlockHoveredInZone(event.clientX, event.clientY);
|
|
110
118
|
|
|
111
119
|
return;
|
|
112
120
|
}
|
|
@@ -169,6 +177,41 @@ export class BlockHoverController extends Controller {
|
|
|
169
177
|
});
|
|
170
178
|
}
|
|
171
179
|
|
|
180
|
+
/**
|
|
181
|
+
* Emits a BlockHovered event for the nearest block, but only if the cursor
|
|
182
|
+
* is within the extended hover zone (HOVER_ZONE_SIZE px from content edges).
|
|
183
|
+
* @param clientX - Cursor X position
|
|
184
|
+
* @param clientY - Cursor Y position
|
|
185
|
+
*/
|
|
186
|
+
private emitNearestBlockHoveredInZone(clientX: number, clientY: number): void {
|
|
187
|
+
const blocks = this.Blok.BlockManager.blocks;
|
|
188
|
+
const topLevelBlocks = blocks.filter(block =>
|
|
189
|
+
block.holder.closest('[data-blok-table-cell-blocks], [data-blok-toggle-children]') === null
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
if (topLevelBlocks.length === 0) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const contentEl = topLevelBlocks[0].holder.querySelector<HTMLElement>('[data-blok-element-content]');
|
|
197
|
+
|
|
198
|
+
if (!contentEl) {
|
|
199
|
+
this.emitNearestBlockHovered(clientY);
|
|
200
|
+
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const contentRect = contentEl.getBoundingClientRect();
|
|
205
|
+
const distLeft = Math.abs(clientX - contentRect.left);
|
|
206
|
+
const distRight = Math.abs(clientX - contentRect.right);
|
|
207
|
+
const withinZone = distLeft <= BlockHoverController.HOVER_ZONE_SIZE
|
|
208
|
+
|| distRight <= BlockHoverController.HOVER_ZONE_SIZE;
|
|
209
|
+
|
|
210
|
+
if (withinZone) {
|
|
211
|
+
this.emitNearestBlockHovered(clientY);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
172
215
|
/**
|
|
173
216
|
* Finds the nearest block by vertical distance to cursor position.
|
|
174
217
|
* Returns the block whose vertical center is closest to the cursor Y position.
|
|
@@ -69,6 +69,16 @@ export class BlockToolAdapter extends BaseToolAdapter<ToolType.Block, IBlockTool
|
|
|
69
69
|
return (this.constructable as BlockToolConstructable)[InternalBlockToolSettings.IsReadOnlySupported] === true;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Returns true if the Tool's prototype has a setReadOnly method,
|
|
74
|
+
* enabling the in-place read-only toggle path (no save/clear/render cycle).
|
|
75
|
+
*/
|
|
76
|
+
public get supportsInPlaceReadOnly(): boolean {
|
|
77
|
+
const prototype = (this.constructable as unknown as { prototype?: { setReadOnly?: unknown } })?.prototype;
|
|
78
|
+
|
|
79
|
+
return typeof prototype?.setReadOnly === 'function';
|
|
80
|
+
}
|
|
81
|
+
|
|
72
82
|
/**
|
|
73
83
|
* Returns true if Tool supports linebreaks
|
|
74
84
|
*/
|
|
@@ -132,11 +132,18 @@ export const setupPlaceholder = (
|
|
|
132
132
|
element: HTMLElement,
|
|
133
133
|
placeholder?: string,
|
|
134
134
|
attributeName: 'data-placeholder' | 'data-blok-placeholder-active' = 'data-placeholder'
|
|
135
|
-
): void => {
|
|
135
|
+
): (() => void) => {
|
|
136
136
|
// Always set the attribute, even if empty (for consistency and testing)
|
|
137
137
|
element.setAttribute(attributeName, placeholder ?? '');
|
|
138
138
|
|
|
139
|
-
|
|
139
|
+
const handler = (): void => handleEmptyElement(element);
|
|
140
|
+
|
|
141
|
+
element.addEventListener('focus', handler);
|
|
142
|
+
|
|
143
|
+
return () => {
|
|
144
|
+
element.removeEventListener('focus', handler);
|
|
145
|
+
element.removeAttribute(attributeName);
|
|
146
|
+
};
|
|
140
147
|
};
|
|
141
148
|
|
|
142
149
|
/**
|
|
@@ -281,6 +281,17 @@ export class PopoverItemDefault extends PopoverItem {
|
|
|
281
281
|
this.nodes.secondaryLabelEl = secondaryEl;
|
|
282
282
|
}
|
|
283
283
|
|
|
284
|
+
// Trailing icon (right-side indicator, e.g. checkmark)
|
|
285
|
+
if (params.trailingIcon) {
|
|
286
|
+
const trailingEl = document.createElement('div');
|
|
287
|
+
|
|
288
|
+
trailingEl.className = 'ml-auto shrink-0 flex items-center justify-center [&_svg]:w-icon [&_svg]:h-icon';
|
|
289
|
+
trailingEl.setAttribute('data-blok-testid', 'popover-item-trailing-icon');
|
|
290
|
+
trailingEl.innerHTML = params.trailingIcon;
|
|
291
|
+
|
|
292
|
+
root.appendChild(trailingEl);
|
|
293
|
+
}
|
|
294
|
+
|
|
284
295
|
// Chevron
|
|
285
296
|
const showChevron = this.hasChildren && !this.isChevronHidden;
|
|
286
297
|
|
|
@@ -102,6 +102,13 @@ export abstract class PopoverAbstract<Nodes extends PopoverNodes = PopoverNodes>
|
|
|
102
102
|
return this.nodes.popover;
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
/**
|
|
106
|
+
* Whether the popover is currently shown
|
|
107
|
+
*/
|
|
108
|
+
public get isShown(): boolean {
|
|
109
|
+
return this.nodes.popover.hasAttribute(DATA_ATTR.popoverOpened);
|
|
110
|
+
}
|
|
111
|
+
|
|
105
112
|
/**
|
|
106
113
|
* Open popover
|
|
107
114
|
*/
|
package/src/styles/main.css
CHANGED
|
@@ -644,6 +644,10 @@
|
|
|
644
644
|
--color-swatch-ring-hover: var(--blok-swatch-ring-hover);
|
|
645
645
|
--color-swatch-ring-active: var(--blok-swatch-ring-active);
|
|
646
646
|
|
|
647
|
+
/* Surface tokens (code blocks, secondary containers) */
|
|
648
|
+
--color-bg-secondary: var(--blok-bg-secondary);
|
|
649
|
+
--color-border-secondary: var(--blok-border-secondary);
|
|
650
|
+
|
|
647
651
|
}
|
|
648
652
|
|
|
649
653
|
@layer utilities {
|
|
@@ -725,6 +729,10 @@
|
|
|
725
729
|
--blok-swatch-ring-hover: rgba(0, 0, 0, 0.10);
|
|
726
730
|
--blok-swatch-ring-active: rgba(0, 0, 0, 0.30);
|
|
727
731
|
|
|
732
|
+
/* Surface tokens (code blocks, secondary containers) */
|
|
733
|
+
--blok-bg-secondary: #f7f8fa;
|
|
734
|
+
--blok-border-secondary: rgba(55, 53, 47, 0.09);
|
|
735
|
+
|
|
728
736
|
/* Marker colors — light theme */
|
|
729
737
|
--blok-color-gray-text: #787774;
|
|
730
738
|
--blok-color-gray-bg: #f1f1ef;
|
|
@@ -818,6 +826,10 @@
|
|
|
818
826
|
--blok-swatch-ring-hover: rgba(255, 255, 255, 0.15);
|
|
819
827
|
--blok-swatch-ring-active: rgba(255, 255, 255, 0.35);
|
|
820
828
|
|
|
829
|
+
/* Surface tokens (code blocks, secondary containers) */
|
|
830
|
+
--blok-bg-secondary: rgba(255, 255, 255, 0.04);
|
|
831
|
+
--blok-border-secondary: rgba(255, 255, 255, 0.08);
|
|
832
|
+
|
|
821
833
|
/* Marker colors — dark theme */
|
|
822
834
|
--blok-color-gray-text: #9b9b9b;
|
|
823
835
|
--blok-color-gray-bg: #2f2f2f;
|
|
@@ -910,6 +922,10 @@
|
|
|
910
922
|
--blok-swatch-ring-hover: rgba(255, 255, 255, 0.15);
|
|
911
923
|
--blok-swatch-ring-active: rgba(255, 255, 255, 0.35);
|
|
912
924
|
|
|
925
|
+
/* Surface tokens (code blocks, secondary containers) */
|
|
926
|
+
--blok-bg-secondary: rgba(255, 255, 255, 0.04);
|
|
927
|
+
--blok-border-secondary: rgba(255, 255, 255, 0.08);
|
|
928
|
+
|
|
913
929
|
/* Marker colors — dark theme */
|
|
914
930
|
--blok-color-gray-text: #9b9b9b;
|
|
915
931
|
--blok-color-gray-bg: #2f2f2f;
|
|
@@ -25,7 +25,8 @@ export const EMOJI_CATEGORY_FLAGS_KEY = 'tools.callout.emojiCategoryFlags';
|
|
|
25
25
|
export const DEFAULT_EMOJI = '💡';
|
|
26
26
|
|
|
27
27
|
// CSS — Tailwind classes
|
|
28
|
-
export const WRAPPER_STYLES = 'rounded-xl
|
|
28
|
+
export const WRAPPER_STYLES = 'rounded-xl pl-8 pr-4 py-[5px] my-1 flex items-start gap-2 relative';
|
|
29
29
|
// h-[38px] = py-[7px]×2 + 1.5rem×1 = 14+24; explicit height prevents platform-specific emoji font metrics from inflating the button
|
|
30
30
|
export const EMOJI_BUTTON_STYLES = 'text-[1.5rem] leading-[1] cursor-pointer bg-transparent border-0 px-0 py-[7px] h-[38px] flex-shrink-0 select-none';
|
|
31
31
|
export const CHILDREN_STYLES = 'flex-1 min-w-0';
|
|
32
|
+
export const DRAG_ZONE_STYLES = 'absolute left-0 top-0 h-full cursor-grab select-none';
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
// src/tools/callout/dom-builder.ts
|
|
2
2
|
|
|
3
|
+
import { DATA_ATTR } from '../../components/constants/data-attributes';
|
|
3
4
|
import { TOGGLE_ATTR } from '../toggle/constants';
|
|
4
5
|
import {
|
|
5
6
|
WRAPPER_STYLES,
|
|
6
7
|
EMOJI_BUTTON_STYLES,
|
|
7
8
|
CHILDREN_STYLES,
|
|
9
|
+
DRAG_ZONE_STYLES,
|
|
8
10
|
} from './constants';
|
|
9
11
|
|
|
10
12
|
export interface CalloutDOMRefs {
|
|
11
13
|
wrapper: HTMLElement;
|
|
12
14
|
emojiButton: HTMLButtonElement;
|
|
13
15
|
childContainer: HTMLElement;
|
|
16
|
+
dragZone: HTMLElement;
|
|
14
17
|
}
|
|
15
18
|
|
|
16
19
|
export interface BuildCalloutDOMOptions {
|
|
@@ -43,11 +46,20 @@ export function buildCalloutDOM(options: BuildCalloutDOMOptions): CalloutDOMRefs
|
|
|
43
46
|
const childContainer = document.createElement('div');
|
|
44
47
|
childContainer.className = CHILDREN_STYLES;
|
|
45
48
|
childContainer.setAttribute(TOGGLE_ATTR.toggleChildren, '');
|
|
49
|
+
childContainer.setAttribute(DATA_ATTR.nestedBlocks, '');
|
|
46
50
|
childContainer.setAttribute('data-blok-child-toolbar', '');
|
|
47
51
|
childContainer.setAttribute('data-blok-mutation-free', 'true');
|
|
48
52
|
|
|
49
53
|
wrapper.appendChild(emojiButton);
|
|
50
54
|
wrapper.appendChild(childContainer);
|
|
51
55
|
|
|
52
|
-
|
|
56
|
+
// Drag zone — covers left padding area (x=[0,16px]) for drag handle,
|
|
57
|
+
// sits behind emoji button so emoji clicks pass through
|
|
58
|
+
const dragZone = document.createElement('span');
|
|
59
|
+
dragZone.className = DRAG_ZONE_STYLES;
|
|
60
|
+
dragZone.style.width = '32px'; // matches pl-8 left padding
|
|
61
|
+
dragZone.setAttribute('data-callout-drag-zone', '');
|
|
62
|
+
wrapper.prepend(dragZone);
|
|
63
|
+
|
|
64
|
+
return { wrapper, emojiButton, childContainer, dragZone };
|
|
53
65
|
}
|
|
@@ -16,6 +16,7 @@ import type { CalloutData, CalloutConfig } from './types';
|
|
|
16
16
|
import { buildCalloutDOM, type CalloutDOMRefs } from './dom-builder';
|
|
17
17
|
import { saveCallout } from './block-operations';
|
|
18
18
|
import { handleCalloutFirstChildBackspace } from './callout-keyboard';
|
|
19
|
+
import { mountChildBlocks } from '../nested-blocks';
|
|
19
20
|
import { createColorPicker, type ColorPickerHandle } from '../../components/shared/color-picker';
|
|
20
21
|
import { colorVarName } from '../../components/shared/color-presets';
|
|
21
22
|
import { mapToNearestPresetName } from '../../components/utils/color-mapping';
|
|
@@ -60,11 +61,12 @@ const VARIANT_TO_BG_PRESET: Record<string, string | null> = {
|
|
|
60
61
|
|
|
61
62
|
export class CalloutTool implements BlockTool {
|
|
62
63
|
private readonly api: API;
|
|
63
|
-
private
|
|
64
|
+
private readOnly: boolean;
|
|
64
65
|
private _data: CalloutData;
|
|
65
66
|
private _dom: CalloutDOMRefs | null = null;
|
|
66
67
|
private _emojiPicker: EmojiPicker | null = null;
|
|
67
68
|
private _colorPicker: ColorPickerHandle | null = null;
|
|
69
|
+
private _dragZone: HTMLElement | null = null;
|
|
68
70
|
private blockId?: string;
|
|
69
71
|
|
|
70
72
|
constructor({ data, api, readOnly, block }: BlockToolConstructorOptions<CalloutData, CalloutConfig>) {
|
|
@@ -108,6 +110,10 @@ export class CalloutTool implements BlockTool {
|
|
|
108
110
|
}
|
|
109
111
|
|
|
110
112
|
public render(): HTMLElement {
|
|
113
|
+
if (this._dom) {
|
|
114
|
+
return this._dom.wrapper;
|
|
115
|
+
}
|
|
116
|
+
|
|
111
117
|
const dom = buildCalloutDOM({
|
|
112
118
|
emoji: this._data.emoji,
|
|
113
119
|
readOnly: this.readOnly,
|
|
@@ -115,6 +121,7 @@ export class CalloutTool implements BlockTool {
|
|
|
115
121
|
});
|
|
116
122
|
|
|
117
123
|
this._dom = dom;
|
|
124
|
+
this._dragZone = dom.dragZone;
|
|
118
125
|
this.applyColors();
|
|
119
126
|
|
|
120
127
|
if (!this.readOnly) {
|
|
@@ -144,12 +151,7 @@ export class CalloutTool implements BlockTool {
|
|
|
144
151
|
|
|
145
152
|
const children = this.api.blocks.getChildren(this.blockId);
|
|
146
153
|
|
|
147
|
-
|
|
148
|
-
for (const child of children) {
|
|
149
|
-
if (child.holder.parentElement !== this._dom.childContainer) {
|
|
150
|
-
this._dom.childContainer.appendChild(child.holder);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
154
|
+
mountChildBlocks(this._dom.childContainer, children);
|
|
153
155
|
|
|
154
156
|
// Auto-create initial paragraph child when callout has no children
|
|
155
157
|
if (children.length === 0) {
|
|
@@ -252,6 +254,18 @@ export class CalloutTool implements BlockTool {
|
|
|
252
254
|
// No-op — no subscriptions to clean up
|
|
253
255
|
}
|
|
254
256
|
|
|
257
|
+
public setReadOnly(state: boolean): void {
|
|
258
|
+
this.readOnly = state;
|
|
259
|
+
|
|
260
|
+
if (this._dom) {
|
|
261
|
+
this._dom.emojiButton.disabled = state;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
public get dragZone(): HTMLElement | null {
|
|
266
|
+
return this._dragZone;
|
|
267
|
+
}
|
|
268
|
+
|
|
255
269
|
private syncPickerActiveColors(): void {
|
|
256
270
|
if (this._colorPicker === null) {
|
|
257
271
|
return;
|
|
@@ -5,7 +5,6 @@ export const PLACEHOLDER_KEY = 'tools.code.placeholder';
|
|
|
5
5
|
export const LANGUAGE_KEY = 'tools.code.language';
|
|
6
6
|
export const COPIED_KEY = 'tools.code.copied';
|
|
7
7
|
export const COPY_CODE_KEY = 'tools.code.copyCode';
|
|
8
|
-
export const WRAP_LINES_KEY = 'tools.code.wrapLines';
|
|
9
8
|
export const SEARCH_LANGUAGE_KEY = 'tools.code.searchLanguage';
|
|
10
9
|
|
|
11
10
|
// Default values
|
|
@@ -53,10 +52,12 @@ export const LANGUAGES: LanguageEntry[] = [
|
|
|
53
52
|
];
|
|
54
53
|
|
|
55
54
|
// CSS — Tailwind classes
|
|
56
|
-
export const WRAPPER_STYLES = 'flex flex-col rounded-
|
|
57
|
-
export const HEADER_STYLES = 'flex items-center gap-1 px-3 py-1.5
|
|
58
|
-
export const LANGUAGE_BUTTON_STYLES = 'px-1.5 py-0.5 rounded cursor-pointer bg-transparent border-0 text-xs text-gray-text font-medium transition-colors can-hover:hover:bg-item-hover-bg select-none';
|
|
55
|
+
export const WRAPPER_STYLES = 'group/code flex flex-col rounded-xl border border-border-secondary bg-bg-secondary overflow-hidden my-2';
|
|
56
|
+
export const HEADER_STYLES = 'flex items-center gap-1 px-3 py-1.5 text-xs text-gray-text';
|
|
57
|
+
export const LANGUAGE_BUTTON_STYLES = 'inline-flex items-center px-1.5 py-0.5 rounded cursor-pointer bg-transparent border-0 text-xs text-gray-text font-medium transition-colors can-hover:hover:bg-item-hover-bg select-none';
|
|
58
|
+
export const HEADER_CONTROLS_STYLES = 'flex items-center gap-1 opacity-0 group-hover/code:opacity-100 transition-opacity';
|
|
59
59
|
export const HEADER_BUTTON_STYLES = 'p-1 rounded cursor-pointer bg-transparent border-0 text-gray-text transition-colors can-hover:hover:bg-item-hover-bg flex items-center justify-center';
|
|
60
|
+
export const HEADER_BUTTON_MATCHED_STYLES = 'p-1.5 rounded-lg cursor-pointer bg-transparent border-0 text-gray-text transition-colors can-hover:hover:bg-item-hover-bg flex items-center justify-center';
|
|
60
61
|
export const CODE_AREA_STYLES = 'block px-4 py-3 font-mono text-sm leading-relaxed outline-hidden whitespace-pre-wrap overflow-x-auto min-h-[1.5em]';
|
|
61
62
|
export const COPIED_FEEDBACK_STYLES = 'text-xs text-gray-text font-medium select-none';
|
|
62
63
|
|
|
@@ -73,6 +74,28 @@ export const TAB_ACTIVE_STYLES = 'bg-blue-500 text-white';
|
|
|
73
74
|
export const TAB_INACTIVE_STYLES = 'bg-transparent text-gray-text can-hover:hover:bg-item-hover-bg';
|
|
74
75
|
export const PREVIEW_AREA_STYLES = 'px-4 py-3 overflow-x-auto min-h-[1.5em] flex justify-center';
|
|
75
76
|
|
|
77
|
+
// i18n key — preview toggle
|
|
78
|
+
export const PREVIEW_TOGGLE_KEY = 'tools.code.previewToggle';
|
|
79
|
+
|
|
80
|
+
// i18n key — side-by-side view mode
|
|
81
|
+
export const SIDE_BY_SIDE_KEY = 'tools.code.sideBySide';
|
|
82
|
+
|
|
83
|
+
// View mode type
|
|
84
|
+
export type CodeViewMode = 'code' | 'preview' | 'split';
|
|
85
|
+
|
|
86
|
+
// CSS — view mode segmented control
|
|
87
|
+
export const VIEW_MODE_CONTAINER_STYLES = 'flex items-center rounded-lg border border-border-secondary p-0.5 gap-0.5';
|
|
88
|
+
export const VIEW_MODE_BUTTON_STYLES = 'p-1 rounded cursor-pointer bg-transparent border-0 text-gray-text transition-colors flex items-center justify-center';
|
|
89
|
+
export const VIEW_MODE_BUTTON_ACTIVE_STYLES = 'p-1 rounded cursor-pointer bg-item-hover-bg border-0 text-primary transition-colors flex items-center justify-center';
|
|
90
|
+
// Preview icon is 16x16 (vs 20x20 for code/split), so extra 2px padding keeps containers equal
|
|
91
|
+
export const VIEW_MODE_PREVIEW_BUTTON_STYLES = 'p-[6px] rounded cursor-pointer bg-transparent border-0 text-gray-text transition-colors flex items-center justify-center';
|
|
92
|
+
export const VIEW_MODE_PREVIEW_BUTTON_ACTIVE_STYLES = 'p-[6px] rounded cursor-pointer bg-item-hover-bg border-0 text-primary transition-colors flex items-center justify-center';
|
|
93
|
+
|
|
94
|
+
// CSS — split container
|
|
95
|
+
export const SPLIT_CONTAINER_STYLES = 'flex flex-col overflow-hidden';
|
|
96
|
+
export const SPLIT_CONTAINER_SPLIT_STYLES = 'flex flex-row overflow-hidden';
|
|
97
|
+
export const SPLIT_HALF_STYLES = 'flex-1 min-w-0 overflow-hidden';
|
|
98
|
+
|
|
76
99
|
// Shiki theme names for syntax highlighting
|
|
77
100
|
export const SHIKI_LIGHT_THEME = 'one-light';
|
|
78
101
|
export const SHIKI_DARK_THEME = 'vitesse-dark';
|
|
@@ -87,10 +110,7 @@ export const HIGHLIGHTABLE_LANGUAGES = new Set(
|
|
|
87
110
|
.filter((id) => id !== DEFAULT_LANGUAGE)
|
|
88
111
|
);
|
|
89
112
|
|
|
90
|
-
// i18n key — line numbers toggle
|
|
91
|
-
export const LINE_NUMBERS_KEY = 'tools.code.lineNumbers';
|
|
92
|
-
|
|
93
113
|
// CSS — line number gutter
|
|
94
114
|
export const CODE_BODY_STYLES = 'flex overflow-hidden';
|
|
95
|
-
export const GUTTER_STYLES = 'select-none text-right pr-3 py-3 font-mono text-sm leading-relaxed text-gray-text/40
|
|
115
|
+
export const GUTTER_STYLES = 'select-none text-right pl-4 pr-3 py-3 font-mono text-sm leading-relaxed text-gray-text/40 shrink-0';
|
|
96
116
|
export const GUTTER_LINE_STYLES = 'leading-relaxed';
|
|
@@ -2,30 +2,41 @@ import {
|
|
|
2
2
|
WRAPPER_STYLES,
|
|
3
3
|
HEADER_STYLES,
|
|
4
4
|
LANGUAGE_BUTTON_STYLES,
|
|
5
|
+
HEADER_CONTROLS_STYLES,
|
|
5
6
|
HEADER_BUTTON_STYLES,
|
|
7
|
+
HEADER_BUTTON_MATCHED_STYLES,
|
|
6
8
|
CODE_AREA_STYLES,
|
|
7
|
-
TAB_STYLES,
|
|
8
|
-
TAB_ACTIVE_STYLES,
|
|
9
|
-
TAB_INACTIVE_STYLES,
|
|
10
9
|
PREVIEW_AREA_STYLES,
|
|
11
10
|
CODE_BODY_STYLES,
|
|
12
11
|
GUTTER_STYLES,
|
|
13
12
|
GUTTER_LINE_STYLES,
|
|
13
|
+
VIEW_MODE_CONTAINER_STYLES,
|
|
14
|
+
VIEW_MODE_BUTTON_STYLES,
|
|
15
|
+
VIEW_MODE_BUTTON_ACTIVE_STYLES,
|
|
16
|
+
VIEW_MODE_PREVIEW_BUTTON_STYLES,
|
|
17
|
+
VIEW_MODE_PREVIEW_BUTTON_ACTIVE_STYLES,
|
|
18
|
+
SPLIT_CONTAINER_STYLES,
|
|
19
|
+
SPLIT_HALF_STYLES,
|
|
14
20
|
} from './constants';
|
|
15
|
-
import {
|
|
21
|
+
import type { CodeViewMode } from './constants';
|
|
22
|
+
import { IconCopy, IconCode, IconPreview, IconSplitView, IconChevronDown } from '../../components/icons';
|
|
16
23
|
|
|
17
24
|
export interface CodeDOMRefs {
|
|
18
25
|
wrapper: HTMLElement;
|
|
19
26
|
languageButton: HTMLButtonElement;
|
|
20
|
-
lineNumbersButton: HTMLButtonElement;
|
|
21
27
|
copyButton: HTMLButtonElement;
|
|
22
|
-
wrapButton: HTMLButtonElement;
|
|
23
28
|
preElement: HTMLPreElement;
|
|
24
29
|
codeElement: HTMLElement;
|
|
25
30
|
gutterElement: HTMLElement;
|
|
26
|
-
|
|
27
|
-
previewTab: HTMLButtonElement | null;
|
|
31
|
+
viewModeContainer: HTMLElement | null;
|
|
28
32
|
previewElement: HTMLDivElement | null;
|
|
33
|
+
splitContainer: HTMLElement | null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface ViewModeLabels {
|
|
37
|
+
code: string;
|
|
38
|
+
preview: string;
|
|
39
|
+
split: string;
|
|
29
40
|
}
|
|
30
41
|
|
|
31
42
|
export interface BuildCodeDOMOptions {
|
|
@@ -33,41 +44,83 @@ export interface BuildCodeDOMOptions {
|
|
|
33
44
|
languageName: string;
|
|
34
45
|
readOnly: boolean;
|
|
35
46
|
copyLabel: string;
|
|
36
|
-
wrapLabel: string;
|
|
37
|
-
lineNumbersLabel?: string;
|
|
38
47
|
previewable?: boolean;
|
|
39
|
-
|
|
40
|
-
previewTabLabel?: string;
|
|
48
|
+
viewModeLabels?: ViewModeLabels;
|
|
41
49
|
}
|
|
42
50
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
codeTab.type = 'button';
|
|
50
|
-
codeTab.className = `${TAB_STYLES} ${TAB_INACTIVE_STYLES}`;
|
|
51
|
-
codeTab.textContent = codeTabLabel ?? 'Code';
|
|
52
|
-
codeTab.setAttribute('data-blok-testid', 'code-code-tab');
|
|
53
|
-
|
|
54
|
-
const previewTab = document.createElement('button');
|
|
51
|
+
interface ViewModeElements {
|
|
52
|
+
viewModeContainer: HTMLElement;
|
|
53
|
+
previewElement: HTMLDivElement;
|
|
54
|
+
splitContainer: HTMLElement;
|
|
55
|
+
}
|
|
55
56
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
function buildViewModeElements(
|
|
58
|
+
labels: ViewModeLabels,
|
|
59
|
+
): ViewModeElements {
|
|
60
|
+
// Segmented control container
|
|
61
|
+
const viewModeContainer = document.createElement('div');
|
|
62
|
+
|
|
63
|
+
viewModeContainer.className = VIEW_MODE_CONTAINER_STYLES;
|
|
64
|
+
viewModeContainer.setAttribute('role', 'group');
|
|
65
|
+
viewModeContainer.setAttribute('data-blok-testid', 'code-view-mode');
|
|
66
|
+
|
|
67
|
+
const modes: Array<{ mode: CodeViewMode; icon: string; label: string }> = [
|
|
68
|
+
{ mode: 'code', icon: IconCode, label: labels.code },
|
|
69
|
+
{ mode: 'preview', icon: IconPreview, label: labels.preview },
|
|
70
|
+
{ mode: 'split', icon: IconSplitView, label: labels.split },
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
for (const { mode, icon, label } of modes) {
|
|
74
|
+
const button = document.createElement('button');
|
|
75
|
+
const isPreview = mode === 'preview';
|
|
76
|
+
|
|
77
|
+
button.type = 'button';
|
|
78
|
+
button.className = isPreview ? VIEW_MODE_PREVIEW_BUTTON_STYLES : VIEW_MODE_BUTTON_STYLES;
|
|
79
|
+
button.innerHTML = icon;
|
|
80
|
+
button.setAttribute('aria-label', label);
|
|
81
|
+
button.setAttribute('aria-pressed', 'false');
|
|
82
|
+
button.setAttribute('data-blok-testid', `code-mode-${mode}`);
|
|
83
|
+
button.setAttribute('data-mode', mode);
|
|
84
|
+
viewModeContainer.appendChild(button);
|
|
85
|
+
}
|
|
60
86
|
|
|
87
|
+
// Preview container
|
|
61
88
|
const previewElement = document.createElement('div');
|
|
62
89
|
|
|
63
90
|
previewElement.className = PREVIEW_AREA_STYLES;
|
|
64
91
|
previewElement.setAttribute('data-blok-testid', 'code-preview');
|
|
65
92
|
|
|
66
|
-
|
|
93
|
+
// Split container — wraps code body + preview
|
|
94
|
+
const splitContainer = document.createElement('div');
|
|
95
|
+
|
|
96
|
+
splitContainer.className = SPLIT_CONTAINER_STYLES;
|
|
97
|
+
splitContainer.setAttribute('data-blok-testid', 'code-split-container');
|
|
98
|
+
|
|
99
|
+
return { viewModeContainer, previewElement, splitContainer };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Set the active view mode button styling and aria-pressed state.
|
|
104
|
+
*/
|
|
105
|
+
export function setActiveViewMode(viewModeContainer: HTMLElement, mode: CodeViewMode): void {
|
|
106
|
+
const buttons = Array.from(viewModeContainer.querySelectorAll<HTMLButtonElement>('[data-mode]'));
|
|
107
|
+
|
|
108
|
+
for (const btn of buttons) {
|
|
109
|
+
const isActive = btn.getAttribute('data-mode') === mode;
|
|
110
|
+
const isPreview = btn.getAttribute('data-mode') === 'preview';
|
|
111
|
+
|
|
112
|
+
btn.setAttribute('aria-pressed', String(isActive));
|
|
113
|
+
|
|
114
|
+
if (isPreview) {
|
|
115
|
+
btn.className = isActive ? VIEW_MODE_PREVIEW_BUTTON_ACTIVE_STYLES : VIEW_MODE_PREVIEW_BUTTON_STYLES;
|
|
116
|
+
} else {
|
|
117
|
+
btn.className = isActive ? VIEW_MODE_BUTTON_ACTIVE_STYLES : VIEW_MODE_BUTTON_STYLES;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
67
120
|
}
|
|
68
121
|
|
|
69
122
|
export function buildCodeDOM(options: BuildCodeDOMOptions): CodeDOMRefs {
|
|
70
|
-
const { code, languageName, readOnly, copyLabel,
|
|
123
|
+
const { code, languageName, readOnly, copyLabel, previewable, viewModeLabels } = options;
|
|
71
124
|
|
|
72
125
|
// Wrapper
|
|
73
126
|
const wrapper = document.createElement('div');
|
|
@@ -77,43 +130,43 @@ export function buildCodeDOM(options: BuildCodeDOMOptions): CodeDOMRefs {
|
|
|
77
130
|
const header = document.createElement('div');
|
|
78
131
|
header.className = HEADER_STYLES;
|
|
79
132
|
|
|
80
|
-
// Language button (opens language picker)
|
|
133
|
+
// Language button (opens language picker) — includes text + chevron icon
|
|
81
134
|
const languageButton = document.createElement('button');
|
|
82
135
|
languageButton.type = 'button';
|
|
83
136
|
languageButton.className = LANGUAGE_BUTTON_STYLES;
|
|
84
|
-
languageButton.textContent = languageName;
|
|
85
137
|
languageButton.setAttribute('aria-haspopup', 'listbox');
|
|
86
138
|
languageButton.setAttribute('data-blok-testid', 'code-language-btn');
|
|
87
139
|
|
|
140
|
+
const langText = document.createElement('span');
|
|
141
|
+
langText.textContent = languageName;
|
|
142
|
+
languageButton.appendChild(langText);
|
|
143
|
+
|
|
144
|
+
const chevronSpan = document.createElement('span');
|
|
145
|
+
chevronSpan.className = 'inline-flex items-center ml-0.5 -mr-0.5';
|
|
146
|
+
chevronSpan.innerHTML = IconChevronDown;
|
|
147
|
+
languageButton.appendChild(chevronSpan);
|
|
148
|
+
|
|
88
149
|
// Spacer
|
|
89
150
|
const spacer = document.createElement('div');
|
|
90
151
|
spacer.className = 'flex-1';
|
|
91
152
|
|
|
92
|
-
//
|
|
93
|
-
const
|
|
94
|
-
?
|
|
95
|
-
:
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
// Line numbers toggle button
|
|
106
|
-
const lineNumbersButton = document.createElement('button');
|
|
107
|
-
lineNumbersButton.type = 'button';
|
|
108
|
-
lineNumbersButton.className = HEADER_BUTTON_STYLES;
|
|
109
|
-
lineNumbersButton.innerHTML = IconLineNumbers;
|
|
110
|
-
lineNumbersButton.setAttribute('aria-label', lineNumbersLabel ?? 'Line numbers');
|
|
111
|
-
lineNumbersButton.setAttribute('data-blok-testid', 'code-line-numbers-btn');
|
|
153
|
+
// View mode segmented control — always built in edit mode, hidden for non-previewable languages
|
|
154
|
+
const viewModeResult = !readOnly && viewModeLabels
|
|
155
|
+
? buildViewModeElements(viewModeLabels)
|
|
156
|
+
: null;
|
|
157
|
+
|
|
158
|
+
const viewModeContainer = viewModeResult?.viewModeContainer ?? null;
|
|
159
|
+
const previewElement = viewModeResult?.previewElement ?? null;
|
|
160
|
+
const splitContainer = viewModeResult?.splitContainer ?? null;
|
|
161
|
+
|
|
162
|
+
if (viewModeContainer) {
|
|
163
|
+
viewModeContainer.hidden = !previewable;
|
|
164
|
+
}
|
|
112
165
|
|
|
113
166
|
// Copy button
|
|
114
167
|
const copyButton = document.createElement('button');
|
|
115
168
|
copyButton.type = 'button';
|
|
116
|
-
copyButton.className = HEADER_BUTTON_STYLES;
|
|
169
|
+
copyButton.className = previewable ? HEADER_BUTTON_MATCHED_STYLES : HEADER_BUTTON_STYLES;
|
|
117
170
|
copyButton.innerHTML = IconCopy;
|
|
118
171
|
copyButton.setAttribute('aria-label', copyLabel);
|
|
119
172
|
copyButton.setAttribute('data-blok-testid', 'code-copy-btn');
|
|
@@ -146,18 +199,21 @@ export function buildCodeDOM(options: BuildCodeDOMOptions): CodeDOMRefs {
|
|
|
146
199
|
gutterElement.appendChild(lineEl);
|
|
147
200
|
});
|
|
148
201
|
|
|
149
|
-
// Assemble header
|
|
202
|
+
// Assemble header: [language] [spacer] [controls: view mode? | copy]
|
|
150
203
|
header.appendChild(languageButton);
|
|
151
204
|
header.appendChild(spacer);
|
|
152
205
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
206
|
+
// Controls container — hidden by default, visible on wrapper hover
|
|
207
|
+
const controls = document.createElement('div');
|
|
208
|
+
controls.className = HEADER_CONTROLS_STYLES;
|
|
209
|
+
|
|
210
|
+
if (viewModeContainer) {
|
|
211
|
+
controls.appendChild(viewModeContainer);
|
|
156
212
|
}
|
|
157
213
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
header.appendChild(
|
|
214
|
+
controls.appendChild(copyButton);
|
|
215
|
+
|
|
216
|
+
header.appendChild(controls);
|
|
161
217
|
|
|
162
218
|
// Pre wrapper for semantic HTML
|
|
163
219
|
const preElement = document.createElement('pre');
|
|
@@ -171,11 +227,24 @@ export function buildCodeDOM(options: BuildCodeDOMOptions): CodeDOMRefs {
|
|
|
171
227
|
|
|
172
228
|
// Assemble wrapper
|
|
173
229
|
wrapper.appendChild(header);
|
|
174
|
-
wrapper.appendChild(codeBody);
|
|
175
230
|
|
|
176
|
-
if (previewElement) {
|
|
177
|
-
|
|
231
|
+
if (splitContainer && previewElement) {
|
|
232
|
+
// Edit mode: always wrap code body + preview in split container.
|
|
233
|
+
// previewElement is hidden initially; shown when a previewable language is active.
|
|
234
|
+
const codeHalf = document.createElement('div');
|
|
235
|
+
codeHalf.className = SPLIT_HALF_STYLES;
|
|
236
|
+
codeHalf.appendChild(codeBody);
|
|
237
|
+
|
|
238
|
+
const previewHalf = document.createElement('div');
|
|
239
|
+
previewHalf.className = SPLIT_HALF_STYLES;
|
|
240
|
+
previewHalf.appendChild(previewElement);
|
|
241
|
+
|
|
242
|
+
splitContainer.appendChild(codeHalf);
|
|
243
|
+
splitContainer.appendChild(previewHalf);
|
|
244
|
+
wrapper.appendChild(splitContainer);
|
|
245
|
+
} else {
|
|
246
|
+
wrapper.appendChild(codeBody);
|
|
178
247
|
}
|
|
179
248
|
|
|
180
|
-
return { wrapper, languageButton,
|
|
249
|
+
return { wrapper, languageButton, copyButton, preElement, codeElement, gutterElement, viewModeContainer, previewElement, splitContainer };
|
|
181
250
|
}
|