@jackuait/blok 0.6.0-beta.9 → 0.7.0-beta.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-Bn6Q_o8h.mjs → blok-ob9Fwr1L.mjs} +3414 -2975
- package/dist/chunks/i18next-B47TKgbU.mjs +1303 -0
- package/dist/chunks/{i18next-loader-DjR4d8M7.mjs → i18next-loader-Bu3vFvye.mjs} +2 -2
- package/dist/chunks/{index-oe38cp86.mjs → index-CZmRzRIX.mjs} +12 -12
- package/dist/chunks/{inline-tool-convert-SRTkyaZn.mjs → inline-tool-convert-CvFW2iie.mjs} +1579 -961
- package/dist/chunks/{messages-BogRq8lt.mjs → messages-0AbcLMLm.mjs} +6 -0
- package/dist/chunks/{messages-DJDG55Vq.mjs → messages-0E0AkrNu.mjs} +6 -0
- package/dist/{messages-DnXLrlHh.mjs → chunks/messages-4v4MuVEc.mjs} +6 -0
- package/dist/chunks/{messages-DnIhyAJk.mjs → messages-62v-CLC-.mjs} +6 -0
- package/dist/chunks/{messages-Dzwxv9v1.mjs → messages-8DeO60Oo.mjs} +6 -0
- package/dist/chunks/{messages-B1Aww8q7.mjs → messages-8IPXkrDl.mjs} +6 -0
- package/dist/{messages-uKX8WBaD.mjs → chunks/messages-96kNZDll.mjs} +6 -0
- package/dist/chunks/{messages-BL0tXcDf.mjs → messages-B1FZ8lxU.mjs} +6 -0
- package/dist/{messages-DBn76jVV.mjs → chunks/messages-B217znr-.mjs} +8 -2
- package/dist/{messages-DT4dP5uK.mjs → chunks/messages-B8WNljW3.mjs} +6 -0
- package/dist/chunks/{messages-BdeLo0N9.mjs → messages-BC8IN4Bf.mjs} +6 -0
- package/dist/{messages-CZygwLwM.mjs → chunks/messages-BI43k_BD.mjs} +6 -0
- package/dist/{messages-CzTufCHu.mjs → chunks/messages-BJ6zrz2j.mjs} +6 -0
- package/dist/{messages-BoJc_p1r.mjs → chunks/messages-BUl_Rcnj.mjs} +6 -0
- package/dist/chunks/{messages-CnwibSvh.mjs → messages-BZlmVRwn.mjs} +6 -0
- package/dist/{messages-C2htQ_3F.mjs → chunks/messages-BcpCubnC.mjs} +6 -0
- package/dist/{messages-D5C3J9qr.mjs → chunks/messages-Bm-E4iRC.mjs} +6 -0
- package/dist/chunks/{messages-BELRf6DU.mjs → messages-C4jL-90N.mjs} +6 -0
- package/dist/chunks/{messages-1fC8IMyX.mjs → messages-CDBLbUOQ.mjs} +6 -0
- package/dist/chunks/{messages-7QoX8DkW.mjs → messages-CH4hrauY.mjs} +6 -0
- package/dist/{messages-Dz9L52ol.mjs → chunks/messages-CRJ_mchV.mjs} +6 -0
- package/dist/chunks/{messages-JELdtT6E.mjs → messages-CW4c4cRk.mjs} +6 -0
- package/dist/chunks/{messages-CKI54h6O.mjs → messages-C_4otP7U.mjs} +6 -0
- package/dist/{messages-R3hUSvr3.mjs → chunks/messages-CfiyT2Wi.mjs} +6 -0
- package/dist/{messages-CJdUsQ-c.mjs → chunks/messages-CgTq3QhU.mjs} +6 -0
- package/dist/chunks/{messages-D1Hv8XGo.mjs → messages-Chb7k3Rg.mjs} +6 -0
- package/dist/{messages-Q7AO_FLv.mjs → chunks/messages-Cjjo7yHR.mjs} +6 -0
- package/dist/{messages-C99mq906.mjs → chunks/messages-Cl6ayUaq.mjs} +6 -0
- package/dist/chunks/{messages-Diu6jAaR.mjs → messages-CmR9ftc_.mjs} +6 -0
- package/dist/chunks/{messages-LPVfA-8K.mjs → messages-Cr49Nt3U.mjs} +6 -0
- package/dist/chunks/{messages-DqM1LFg5.mjs → messages-Cr94GzbX.mjs} +6 -0
- package/dist/{messages-BWF-zUpY.mjs → chunks/messages-CrCYPCk3.mjs} +6 -0
- package/dist/{messages-D-ZtY5v0.mjs → chunks/messages-Cs8zmZ3L.mjs} +6 -0
- package/dist/{messages-DprmQg6V.mjs → chunks/messages-CzK0LEhb.mjs} +6 -0
- package/dist/chunks/{messages-BSbjsyHY.mjs → messages-D00x4S8o.mjs} +6 -0
- package/dist/chunks/{messages-Xq8UmkVs.mjs → messages-D1mn7Zd5.mjs} +6 -0
- package/dist/chunks/{messages-BC86qLvI.mjs → messages-D2NOpHn9.mjs} +6 -0
- package/dist/{messages-kep5wtm4.mjs → chunks/messages-D4qqwVgQ.mjs} +6 -0
- package/dist/chunks/{messages-7W4d0DwD.mjs → messages-D5S1Dnpm.mjs} +6 -0
- package/dist/{messages-CY8_RyFE.mjs → chunks/messages-D7u2bmP2.mjs} +6 -0
- package/dist/chunks/{messages-BFG6Wlgy.mjs → messages-D85FqxgY.mjs} +6 -0
- package/dist/{messages-DLfR5bMd.mjs → chunks/messages-D9ndgBnU.mjs} +6 -0
- package/dist/{messages-CVw84KdI.mjs → chunks/messages-DDTQgImT.mjs} +6 -0
- package/dist/{messages-_ErNTNhk.mjs → chunks/messages-DH_jBeED.mjs} +6 -0
- package/dist/chunks/{messages-CMkNSDTo.mjs → messages-DRXWF0PV.mjs} +6 -0
- package/dist/chunks/{messages-BYyy6Wqf.mjs → messages-DVQvl8Qj.mjs} +6 -0
- package/dist/chunks/{messages-CznZadDf.mjs → messages-DXktiao_.mjs} +6 -0
- package/dist/chunks/{messages-DhLKYm2j.mjs → messages-DdK-nFGm.mjs} +6 -0
- package/dist/chunks/{messages-BMXCuEKO.mjs → messages-DlJbPF2T.mjs} +6 -0
- package/dist/chunks/{messages-CvGLfqmV.mjs → messages-DnVlmiNT.mjs} +6 -0
- package/dist/{messages-Z9nEU2xK.mjs → chunks/messages-DviiFSv2.mjs} +6 -0
- package/dist/chunks/{messages-BB5z9Uba.mjs → messages-DzqM3Fel.mjs} +6 -0
- package/dist/{messages-w7v1GNaE.mjs → chunks/messages-Dzzn6XoD.mjs} +6 -0
- package/dist/{messages-CqWJcCbY.mjs → chunks/messages-GSByFygY.mjs} +6 -0
- package/dist/chunks/{messages-_ncGrKHh.mjs → messages-L_kl2Qvh.mjs} +6 -0
- package/dist/chunks/{messages-BrPFGbM-.mjs → messages-Phkd7XmE.mjs} +6 -0
- package/dist/{messages-BU2nlrLK.mjs → chunks/messages-RonBBCnh.mjs} +6 -0
- package/dist/{messages-Bmu_S7GM.mjs → chunks/messages-VDriF5Qy.mjs} +6 -0
- package/dist/{messages-CLhcMlTc.mjs → chunks/messages-ZjUAIWb1.mjs} +6 -0
- package/dist/{messages-9SihnaXQ.mjs → chunks/messages-b1EdvUm0.mjs} +6 -0
- package/dist/{messages-DvFLX36Q.mjs → chunks/messages-begYOTgC.mjs} +6 -0
- package/dist/{messages-BMv4xwIr.mjs → chunks/messages-jrncnb-H.mjs} +6 -0
- package/dist/{messages-D5iv1Kox.mjs → chunks/messages-nefz1S71.mjs} +6 -0
- package/dist/{messages-CQwpzUFp.mjs → chunks/messages-ucTVgS5G.mjs} +6 -0
- package/dist/chunks/{messages-DBRw-7Zc.mjs → messages-v3GipbFl.mjs} +6 -0
- package/dist/{messages-C9eaarcK.mjs → chunks/messages-wmi-iFkH.mjs} +6 -0
- package/dist/chunks/{messages-O5tQus_0.mjs → messages-yHcs38yI.mjs} +6 -0
- package/dist/full.mjs +30 -27
- package/dist/locales.mjs +90 -84
- package/dist/{messages-BogRq8lt.mjs → messages-0AbcLMLm.mjs} +6 -0
- package/dist/{messages-DJDG55Vq.mjs → messages-0E0AkrNu.mjs} +6 -0
- package/dist/{chunks/messages-DnXLrlHh.mjs → messages-4v4MuVEc.mjs} +6 -0
- package/dist/{messages-DnIhyAJk.mjs → messages-62v-CLC-.mjs} +6 -0
- package/dist/{messages-Dzwxv9v1.mjs → messages-8DeO60Oo.mjs} +6 -0
- package/dist/{messages-B1Aww8q7.mjs → messages-8IPXkrDl.mjs} +6 -0
- package/dist/{chunks/messages-uKX8WBaD.mjs → messages-96kNZDll.mjs} +6 -0
- package/dist/{messages-BL0tXcDf.mjs → messages-B1FZ8lxU.mjs} +6 -0
- package/dist/{chunks/messages-DBn76jVV.mjs → messages-B217znr-.mjs} +8 -2
- package/dist/{chunks/messages-DT4dP5uK.mjs → messages-B8WNljW3.mjs} +6 -0
- package/dist/{messages-BdeLo0N9.mjs → messages-BC8IN4Bf.mjs} +6 -0
- package/dist/{chunks/messages-CZygwLwM.mjs → messages-BI43k_BD.mjs} +6 -0
- package/dist/{chunks/messages-CzTufCHu.mjs → messages-BJ6zrz2j.mjs} +6 -0
- package/dist/{chunks/messages-BoJc_p1r.mjs → messages-BUl_Rcnj.mjs} +6 -0
- package/dist/{messages-CnwibSvh.mjs → messages-BZlmVRwn.mjs} +6 -0
- package/dist/{chunks/messages-C2htQ_3F.mjs → messages-BcpCubnC.mjs} +6 -0
- package/dist/{chunks/messages-D5C3J9qr.mjs → messages-Bm-E4iRC.mjs} +6 -0
- package/dist/{messages-BELRf6DU.mjs → messages-C4jL-90N.mjs} +6 -0
- package/dist/{messages-1fC8IMyX.mjs → messages-CDBLbUOQ.mjs} +6 -0
- package/dist/{messages-7QoX8DkW.mjs → messages-CH4hrauY.mjs} +6 -0
- package/dist/{chunks/messages-Dz9L52ol.mjs → messages-CRJ_mchV.mjs} +6 -0
- package/dist/{messages-JELdtT6E.mjs → messages-CW4c4cRk.mjs} +6 -0
- package/dist/{messages-CKI54h6O.mjs → messages-C_4otP7U.mjs} +6 -0
- package/dist/{chunks/messages-R3hUSvr3.mjs → messages-CfiyT2Wi.mjs} +6 -0
- package/dist/{chunks/messages-CJdUsQ-c.mjs → messages-CgTq3QhU.mjs} +6 -0
- package/dist/{messages-D1Hv8XGo.mjs → messages-Chb7k3Rg.mjs} +6 -0
- package/dist/{chunks/messages-Q7AO_FLv.mjs → messages-Cjjo7yHR.mjs} +6 -0
- package/dist/{chunks/messages-C99mq906.mjs → messages-Cl6ayUaq.mjs} +6 -0
- package/dist/{messages-Diu6jAaR.mjs → messages-CmR9ftc_.mjs} +6 -0
- package/dist/{messages-LPVfA-8K.mjs → messages-Cr49Nt3U.mjs} +6 -0
- package/dist/{messages-DqM1LFg5.mjs → messages-Cr94GzbX.mjs} +6 -0
- package/dist/{chunks/messages-BWF-zUpY.mjs → messages-CrCYPCk3.mjs} +6 -0
- package/dist/{chunks/messages-D-ZtY5v0.mjs → messages-Cs8zmZ3L.mjs} +6 -0
- package/dist/{chunks/messages-DprmQg6V.mjs → messages-CzK0LEhb.mjs} +6 -0
- package/dist/{messages-BSbjsyHY.mjs → messages-D00x4S8o.mjs} +6 -0
- package/dist/{messages-Xq8UmkVs.mjs → messages-D1mn7Zd5.mjs} +6 -0
- package/dist/{messages-BC86qLvI.mjs → messages-D2NOpHn9.mjs} +6 -0
- package/dist/{chunks/messages-kep5wtm4.mjs → messages-D4qqwVgQ.mjs} +6 -0
- package/dist/{messages-7W4d0DwD.mjs → messages-D5S1Dnpm.mjs} +6 -0
- package/dist/{chunks/messages-CY8_RyFE.mjs → messages-D7u2bmP2.mjs} +6 -0
- package/dist/{messages-BFG6Wlgy.mjs → messages-D85FqxgY.mjs} +6 -0
- package/dist/{chunks/messages-DLfR5bMd.mjs → messages-D9ndgBnU.mjs} +6 -0
- package/dist/{chunks/messages-CVw84KdI.mjs → messages-DDTQgImT.mjs} +6 -0
- package/dist/{chunks/messages-_ErNTNhk.mjs → messages-DH_jBeED.mjs} +6 -0
- package/dist/{messages-CMkNSDTo.mjs → messages-DRXWF0PV.mjs} +6 -0
- package/dist/{messages-BYyy6Wqf.mjs → messages-DVQvl8Qj.mjs} +6 -0
- package/dist/{messages-CznZadDf.mjs → messages-DXktiao_.mjs} +6 -0
- package/dist/{messages-DhLKYm2j.mjs → messages-DdK-nFGm.mjs} +6 -0
- package/dist/{messages-BMXCuEKO.mjs → messages-DlJbPF2T.mjs} +6 -0
- package/dist/{messages-CvGLfqmV.mjs → messages-DnVlmiNT.mjs} +6 -0
- package/dist/{chunks/messages-Z9nEU2xK.mjs → messages-DviiFSv2.mjs} +6 -0
- package/dist/{messages-BB5z9Uba.mjs → messages-DzqM3Fel.mjs} +6 -0
- package/dist/{chunks/messages-w7v1GNaE.mjs → messages-Dzzn6XoD.mjs} +6 -0
- package/dist/{chunks/messages-CqWJcCbY.mjs → messages-GSByFygY.mjs} +6 -0
- package/dist/{messages-_ncGrKHh.mjs → messages-L_kl2Qvh.mjs} +6 -0
- package/dist/{messages-BrPFGbM-.mjs → messages-Phkd7XmE.mjs} +6 -0
- package/dist/{chunks/messages-BU2nlrLK.mjs → messages-RonBBCnh.mjs} +6 -0
- package/dist/{chunks/messages-Bmu_S7GM.mjs → messages-VDriF5Qy.mjs} +6 -0
- package/dist/{chunks/messages-CLhcMlTc.mjs → messages-ZjUAIWb1.mjs} +6 -0
- package/dist/{chunks/messages-9SihnaXQ.mjs → messages-b1EdvUm0.mjs} +6 -0
- package/dist/{chunks/messages-DvFLX36Q.mjs → messages-begYOTgC.mjs} +6 -0
- package/dist/{chunks/messages-BMv4xwIr.mjs → messages-jrncnb-H.mjs} +6 -0
- package/dist/{chunks/messages-D5iv1Kox.mjs → messages-nefz1S71.mjs} +6 -0
- package/dist/{chunks/messages-CQwpzUFp.mjs → messages-ucTVgS5G.mjs} +6 -0
- package/dist/{messages-DBRw-7Zc.mjs → messages-v3GipbFl.mjs} +6 -0
- package/dist/{chunks/messages-C9eaarcK.mjs → messages-wmi-iFkH.mjs} +6 -0
- package/dist/{messages-O5tQus_0.mjs → messages-yHcs38yI.mjs} +6 -0
- package/dist/tools.mjs +3537 -1710
- package/dist/vendor.LICENSE.txt +109 -109
- package/package.json +43 -57
- package/src/blok.ts +12 -0
- package/src/components/__module.ts +21 -0
- package/src/components/block/api.ts +17 -0
- package/src/components/block/style-manager.ts +6 -2
- package/src/components/block/tool-renderer.ts +33 -30
- package/src/components/blocks.ts +132 -15
- package/src/components/constants/data-attributes.ts +7 -0
- package/src/components/i18n/locales/am/messages.json +6 -0
- package/src/components/i18n/locales/ar/messages.json +6 -0
- package/src/components/i18n/locales/az/messages.json +6 -0
- package/src/components/i18n/locales/bg/messages.json +6 -0
- package/src/components/i18n/locales/bn/messages.json +6 -0
- package/src/components/i18n/locales/bs/messages.json +6 -0
- package/src/components/i18n/locales/cs/messages.json +6 -0
- package/src/components/i18n/locales/da/messages.json +6 -0
- package/src/components/i18n/locales/de/messages.json +6 -0
- package/src/components/i18n/locales/dv/messages.json +6 -0
- package/src/components/i18n/locales/el/messages.json +6 -0
- package/src/components/i18n/locales/en/messages.json +6 -0
- package/src/components/i18n/locales/es/messages.json +6 -0
- package/src/components/i18n/locales/et/messages.json +6 -0
- package/src/components/i18n/locales/fa/messages.json +6 -0
- package/src/components/i18n/locales/fi/messages.json +6 -0
- package/src/components/i18n/locales/fil/messages.json +6 -0
- package/src/components/i18n/locales/fr/messages.json +6 -0
- package/src/components/i18n/locales/gu/messages.json +6 -0
- package/src/components/i18n/locales/he/messages.json +6 -0
- package/src/components/i18n/locales/hi/messages.json +6 -0
- package/src/components/i18n/locales/hr/messages.json +6 -0
- package/src/components/i18n/locales/hu/messages.json +6 -0
- package/src/components/i18n/locales/hy/messages.json +6 -0
- package/src/components/i18n/locales/id/messages.json +6 -0
- package/src/components/i18n/locales/it/messages.json +6 -0
- package/src/components/i18n/locales/ja/messages.json +6 -0
- package/src/components/i18n/locales/ka/messages.json +6 -0
- package/src/components/i18n/locales/km/messages.json +6 -0
- package/src/components/i18n/locales/kn/messages.json +6 -0
- package/src/components/i18n/locales/ko/messages.json +6 -0
- package/src/components/i18n/locales/ku/messages.json +6 -0
- package/src/components/i18n/locales/lo/messages.json +6 -0
- package/src/components/i18n/locales/lt/messages.json +6 -0
- package/src/components/i18n/locales/lv/messages.json +6 -0
- package/src/components/i18n/locales/mk/messages.json +6 -0
- package/src/components/i18n/locales/ml/messages.json +6 -0
- package/src/components/i18n/locales/mn/messages.json +6 -0
- package/src/components/i18n/locales/mr/messages.json +6 -0
- package/src/components/i18n/locales/ms/messages.json +6 -0
- package/src/components/i18n/locales/my/messages.json +6 -0
- package/src/components/i18n/locales/ne/messages.json +6 -0
- package/src/components/i18n/locales/nl/messages.json +6 -0
- package/src/components/i18n/locales/no/messages.json +6 -0
- package/src/components/i18n/locales/pa/messages.json +6 -0
- package/src/components/i18n/locales/pl/messages.json +6 -0
- package/src/components/i18n/locales/ps/messages.json +6 -0
- package/src/components/i18n/locales/pt/messages.json +6 -0
- package/src/components/i18n/locales/ro/messages.json +6 -0
- package/src/components/i18n/locales/ru/messages.json +6 -0
- package/src/components/i18n/locales/sd/messages.json +6 -0
- package/src/components/i18n/locales/si/messages.json +6 -0
- package/src/components/i18n/locales/sk/messages.json +6 -0
- package/src/components/i18n/locales/sl/messages.json +6 -0
- package/src/components/i18n/locales/sq/messages.json +6 -0
- package/src/components/i18n/locales/sr/messages.json +6 -0
- package/src/components/i18n/locales/sv/messages.json +6 -0
- package/src/components/i18n/locales/sw/messages.json +6 -0
- package/src/components/i18n/locales/ta/messages.json +6 -0
- package/src/components/i18n/locales/te/messages.json +6 -0
- package/src/components/i18n/locales/th/messages.json +6 -0
- package/src/components/i18n/locales/tr/messages.json +6 -0
- package/src/components/i18n/locales/ug/messages.json +6 -0
- package/src/components/i18n/locales/uk/messages.json +6 -0
- package/src/components/i18n/locales/ur/messages.json +6 -0
- package/src/components/i18n/locales/vi/messages.json +6 -0
- package/src/components/i18n/locales/yi/messages.json +6 -0
- package/src/components/i18n/locales/zh/messages.json +6 -0
- package/src/components/icons/index.ts +61 -7
- package/src/components/inline-tools/inline-tool-link.ts +1 -1
- package/src/components/inline-tools/inline-tool-marker.ts +737 -0
- package/src/components/inline-tools/utils/formatting-range-utils.ts +6 -3
- package/src/components/inline-tools/utils/marker-dom-utils.ts +17 -0
- package/src/components/modules/api/blocks.ts +34 -9
- package/src/components/modules/blockEvents/composers/keyboardNavigation.ts +75 -29
- package/src/components/modules/blockEvents/composers/markdownShortcuts.ts +54 -2
- package/src/components/modules/blockEvents/constants.ts +12 -0
- package/src/components/modules/blockEvents/index.ts +13 -5
- package/src/components/modules/blockManager/blockManager.ts +81 -2
- package/src/components/modules/blockManager/hierarchy.ts +20 -2
- package/src/components/modules/blockManager/operations.ts +70 -35
- package/src/components/modules/blockManager/repository.ts +22 -0
- package/src/components/modules/blockManager/types.ts +3 -1
- package/src/components/modules/blockManager/yjs-sync.ts +173 -39
- package/src/components/modules/blockSelection.ts +3 -0
- package/src/components/modules/crossBlockSelection.ts +11 -3
- package/src/components/modules/drag/preview/DragPreview.ts +10 -2
- package/src/components/modules/drag/target/DropTargetDetector.ts +100 -11
- package/src/components/modules/drag/utils/drag.constants.ts +1 -1
- package/src/components/modules/normalizeInlineImages.ts +263 -0
- package/src/components/modules/paste/google-docs-preprocessor.ts +197 -0
- package/src/components/modules/paste/handlers/base.ts +43 -2
- package/src/components/modules/paste/handlers/html-handler.ts +1 -1
- package/src/components/modules/paste/handlers/index.ts +1 -0
- package/src/components/modules/paste/handlers/table-cells-handler.ts +104 -0
- package/src/components/modules/paste/index.ts +20 -3
- package/src/components/modules/readonly.ts +8 -2
- package/src/components/modules/rectangleSelection.ts +5 -2
- package/src/components/modules/renderer.ts +35 -0
- package/src/components/modules/saver.ts +52 -2
- package/src/components/modules/toolbar/blockSettings.ts +52 -44
- package/src/components/modules/toolbar/index.ts +124 -17
- package/src/components/modules/toolbar/inline/index.ts +4 -4
- package/src/components/modules/toolbar/plus-button.ts +3 -3
- package/src/components/modules/toolbar/settings-toggler.ts +3 -3
- package/src/components/modules/toolbar/styles.ts +7 -7
- package/src/components/modules/ui.ts +6 -6
- package/src/components/modules/uiControllers/controllers/blockHover.ts +16 -2
- package/src/components/modules/uiControllers/handlers/touch.ts +83 -10
- package/src/components/modules/yjs/block-observer.ts +9 -3
- package/src/components/modules/yjs/document-store.ts +10 -7
- package/src/components/modules/yjs/types.ts +8 -6
- package/src/components/modules/yjs/undo-history.ts +90 -11
- package/src/components/selection/fake-background/shadows.ts +1 -1
- package/src/components/shared/color-picker.ts +211 -0
- package/src/components/shared/color-presets.ts +25 -0
- package/src/components/ui/toolbox.ts +27 -11
- package/src/components/utils/color-mapping.ts +241 -0
- package/src/components/utils/notifier/draw.ts +9 -9
- package/src/components/utils/placeholder.ts +24 -8
- package/src/components/utils/popover/components/popover-item/popover-item-default/popover-item-default.const.ts +3 -3
- package/src/components/utils/popover/components/popover-item/popover-item-default/popover-item-default.ts +15 -12
- package/src/components/utils/popover/components/search-input/search-input.const.ts +2 -2
- package/src/components/utils/popover/popover-abstract.ts +30 -5
- package/src/components/utils/popover/popover-desktop.ts +26 -3
- package/src/components/utils/popover/popover-inline.ts +14 -1
- package/src/components/utils/popover/popover-mobile.ts +4 -4
- package/src/components/utils/popover/popover.const.ts +3 -3
- package/src/components/utils/sanitizer.ts +24 -3
- package/src/components/utils/tw.ts +17 -5
- package/src/full.ts +4 -0
- package/src/stories/Header.stories.ts +106 -0
- package/src/stories/MarkerColors.stories.ts +730 -0
- package/src/stories/Placeholder.stories.ts +7 -2
- package/src/stories/Popover.stories.ts +1 -3
- package/src/stories/Table.stories.ts +1662 -0
- package/src/stories/helpers.ts +2 -0
- package/src/styles/main.css +217 -39
- package/src/tools/header/index.ts +204 -26
- package/src/tools/index.ts +5 -1
- package/src/tools/list/caret-manager.ts +28 -10
- package/src/tools/list/constants.ts +2 -2
- package/src/tools/list/dom-builder.ts +3 -3
- package/src/tools/list/static-configs.ts +0 -1
- package/src/tools/paragraph/index.ts +9 -5
- package/src/tools/table/core/table-commands.ts +99 -0
- package/src/tools/table/core/table-controller.ts +231 -0
- package/src/tools/table/core/table-events.ts +102 -0
- package/src/tools/table/index.ts +1070 -174
- package/src/tools/table/ownership/table-event-broker.ts +74 -0
- package/src/tools/table/ownership/table-ownership-registry.ts +126 -0
- package/src/tools/table/table-add-controls.ts +85 -15
- package/src/tools/table/table-cell-blocks.ts +336 -38
- package/src/tools/table/table-cell-clipboard.ts +415 -0
- package/src/tools/table/table-cell-color-picker.ts +34 -0
- package/src/tools/table/table-cell-selection.ts +264 -15
- package/src/tools/table/table-core.ts +3 -42
- package/src/tools/table/table-heading-toggle.ts +2 -2
- package/src/tools/table/table-model.ts +623 -0
- package/src/tools/table/table-operations.ts +59 -78
- package/src/tools/table/table-resize.ts +15 -11
- package/src/tools/table/table-restrictions.ts +69 -3
- package/src/tools/table/table-row-col-action-handler.ts +22 -7
- package/src/tools/table/table-row-col-controls.ts +129 -12
- package/src/tools/table/table-row-col-drag.ts +14 -0
- package/src/tools/table/table-scroll-haze.ts +152 -0
- package/src/tools/table/types.ts +22 -1
- package/src/tools/table/view/table-cell-blocks-adapter.ts +47 -0
- package/src/tools/toggle/block-operations.ts +110 -0
- package/src/tools/toggle/constants.ts +49 -0
- package/src/tools/toggle/dom-builder.ts +125 -0
- package/src/tools/toggle/index.ts +280 -0
- package/src/tools/toggle/toggle-keyboard.ts +139 -0
- package/src/tools/toggle/toggle-lifecycle.ts +80 -0
- package/src/tools/toggle/toggle-shortcuts.ts +107 -0
- package/src/tools/toggle/types.ts +21 -0
- package/src/variants/blok-minimum.ts +13 -0
- package/types/api/block.d.ts +13 -0
- package/types/api/blocks.d.ts +16 -0
- package/types/full.d.ts +2 -0
- package/types/tools/table.d.ts +2 -0
- package/types/tools-entry.d.ts +2 -1
- package/dist/chunks/i18next-CugVlwWp.mjs +0 -1292
- package/src/tools/table/data-normalizer.ts +0 -32
|
@@ -138,6 +138,20 @@ export class DropTargetDetector {
|
|
|
138
138
|
return null;
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
+
/**
|
|
142
|
+
* If the target block is inside a table cell and the source block is NOT
|
|
143
|
+
* in the same cell, redirect the drop target to the table block itself.
|
|
144
|
+
* This prevents blocks from being dropped into table cells via drag & drop.
|
|
145
|
+
*/
|
|
146
|
+
const targetCellContainer = targetBlock.holder.closest('[data-blok-table-cell-blocks]');
|
|
147
|
+
const sourceCellContainer = sourceBlock.holder.closest('[data-blok-table-cell-blocks]');
|
|
148
|
+
const isTargetInCell = targetCellContainer !== null;
|
|
149
|
+
const isCrossCellDrop = sourceCellContainer !== targetCellContainer;
|
|
150
|
+
|
|
151
|
+
if (isTargetInCell && isCrossCellDrop) {
|
|
152
|
+
return this.redirectToTableBlock(targetCellContainer, clientY);
|
|
153
|
+
}
|
|
154
|
+
|
|
141
155
|
// Determine edge (top or bottom half of block)
|
|
142
156
|
const rect = blockHolder.getBoundingClientRect();
|
|
143
157
|
const isTopHalf = clientY < rect.top + rect.height / 2;
|
|
@@ -151,24 +165,29 @@ export class DropTargetDetector {
|
|
|
151
165
|
const canUsePreviousBlock = previousBlock && !this.sourceBlocks.includes(previousBlock);
|
|
152
166
|
|
|
153
167
|
if (isTopHalf && targetIndex > 0 && canUsePreviousBlock) {
|
|
154
|
-
const targetDepth = this.calculateTargetDepth(previousBlock, 'bottom');
|
|
168
|
+
const targetDepth = this.calculateTargetDepth(previousBlock, 'bottom', sourceBlock);
|
|
155
169
|
return { block: previousBlock, edge: 'bottom', depth: targetDepth };
|
|
156
170
|
}
|
|
157
171
|
|
|
158
172
|
// First block top half, or any block bottom half
|
|
159
173
|
const edge: 'top' | 'bottom' = isTopHalf ? 'top' : 'bottom';
|
|
160
|
-
const targetDepth = this.calculateTargetDepth(targetBlock, edge);
|
|
174
|
+
const targetDepth = this.calculateTargetDepth(targetBlock, edge, sourceBlock);
|
|
161
175
|
|
|
162
176
|
return { block: targetBlock, edge, depth: targetDepth };
|
|
163
177
|
}
|
|
164
178
|
|
|
165
179
|
/**
|
|
166
|
-
* Calculates the target depth for list item nesting
|
|
180
|
+
* Calculates the target depth for list item nesting.
|
|
181
|
+
* When a sourceBlock is provided and is a list item, the depth is calculated
|
|
182
|
+
* to match what ListDepthValidator.getTargetDepthForMove will produce post-drop,
|
|
183
|
+
* ensuring the visual indicator accurately predicts the final depth.
|
|
184
|
+
*
|
|
167
185
|
* @param targetBlock - Block being dropped onto
|
|
168
186
|
* @param targetEdge - Edge of target ('top' or 'bottom')
|
|
187
|
+
* @param sourceBlock - Optional block being dragged (for accurate list item depth prediction)
|
|
169
188
|
* @returns The target depth (0 for root level, 1+ for nested)
|
|
170
189
|
*/
|
|
171
|
-
calculateTargetDepth(targetBlock: Block, targetEdge: 'top' | 'bottom'): number {
|
|
190
|
+
calculateTargetDepth(targetBlock: Block, targetEdge: 'top' | 'bottom', sourceBlock?: Block): number {
|
|
172
191
|
const targetIndex = this.blockManager.getBlockIndex(targetBlock);
|
|
173
192
|
const dropIndex = targetEdge === 'top' ? targetIndex : targetIndex + 1;
|
|
174
193
|
|
|
@@ -184,23 +203,93 @@ export class DropTargetDetector {
|
|
|
184
203
|
return 0;
|
|
185
204
|
}
|
|
186
205
|
|
|
187
|
-
const previousDepth = this.listItemDepth.getDepth(previousBlock)
|
|
206
|
+
const previousDepth = this.listItemDepth.getDepth(previousBlock);
|
|
207
|
+
const previousIsListItem = previousDepth !== null;
|
|
208
|
+
const prevDepthValue = previousDepth ?? 0;
|
|
188
209
|
|
|
189
210
|
// Get the block that will be immediately after the drop position
|
|
190
211
|
const nextBlock = this.blockManager.getBlockByIndex(dropIndex);
|
|
191
|
-
const nextDepth = nextBlock ?
|
|
212
|
+
const nextDepth = nextBlock ? this.listItemDepth.getDepth(nextBlock) : null;
|
|
213
|
+
const nextIsListItem = nextDepth !== null;
|
|
214
|
+
const nextDepthValue = nextDepth ?? 0;
|
|
215
|
+
|
|
216
|
+
// When dragging a list item, predict the exact depth ListDepthValidator will
|
|
217
|
+
// compute post-drop — so the visual indicator matches the actual result.
|
|
218
|
+
const sourceDepth = sourceBlock ? this.listItemDepth.getDepth(sourceBlock) : null;
|
|
219
|
+
|
|
220
|
+
if (sourceDepth !== null) {
|
|
221
|
+
return this.predictListItemDepth(
|
|
222
|
+
sourceDepth, prevDepthValue, previousIsListItem, nextDepthValue, nextIsListItem
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// For non-list blocks (or when sourceBlock not provided), use neighbor-based
|
|
227
|
+
// depth for cosmetic indicator positioning only.
|
|
228
|
+
if (nextDepthValue > 0 && nextDepthValue <= prevDepthValue + 1) {
|
|
229
|
+
return nextDepthValue;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (prevDepthValue > 0) {
|
|
233
|
+
return prevDepthValue;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return 0;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Mirrors ListDepthValidator.getTargetDepthForMove to predict the exact
|
|
241
|
+
* post-drop depth for a list item. This ensures the visual indicator
|
|
242
|
+
* shows the same depth the item will actually have after being dropped.
|
|
243
|
+
*/
|
|
244
|
+
private predictListItemDepth(
|
|
245
|
+
currentDepth: number,
|
|
246
|
+
previousDepth: number,
|
|
247
|
+
previousIsListItem: boolean,
|
|
248
|
+
nextDepth: number,
|
|
249
|
+
nextIsListItem: boolean
|
|
250
|
+
): number {
|
|
251
|
+
const maxAllowedDepth = previousIsListItem ? previousDepth + 1 : 0;
|
|
252
|
+
|
|
253
|
+
// Cap current depth at max allowed
|
|
254
|
+
if (currentDepth > maxAllowedDepth) {
|
|
255
|
+
return maxAllowedDepth;
|
|
256
|
+
}
|
|
192
257
|
|
|
193
|
-
//
|
|
194
|
-
if (nextDepth >
|
|
258
|
+
// Match next depth if it's deeper than current and within bounds
|
|
259
|
+
if (nextIsListItem && nextDepth > currentDepth && nextDepth <= maxAllowedDepth) {
|
|
195
260
|
return nextDepth;
|
|
196
261
|
}
|
|
197
262
|
|
|
198
|
-
//
|
|
199
|
-
if (previousDepth >
|
|
263
|
+
// Match previous depth if deeper than current and no next list item
|
|
264
|
+
if (previousIsListItem && !nextIsListItem && previousDepth > currentDepth && previousDepth <= maxAllowedDepth) {
|
|
200
265
|
return previousDepth;
|
|
201
266
|
}
|
|
202
267
|
|
|
203
|
-
return
|
|
268
|
+
return currentDepth;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Redirect a drop target to the table block that contains the given cell container.
|
|
273
|
+
* Finds the outermost [data-blok-element] ancestor and returns it as the target.
|
|
274
|
+
*
|
|
275
|
+
* @param cellContainer - The [data-blok-table-cell-blocks] element containing the target
|
|
276
|
+
* @param clientY - Cursor Y position for edge calculation
|
|
277
|
+
* @returns Drop target pointing to the table block, or null if table block not found
|
|
278
|
+
*/
|
|
279
|
+
private redirectToTableBlock(cellContainer: Element, clientY: number): DropTarget | null {
|
|
280
|
+
const tableHolder = cellContainer.closest(createSelector(DATA_ATTR.element));
|
|
281
|
+
const tableBlock = tableHolder
|
|
282
|
+
? this.blockManager.blocks.find(b => b.holder === tableHolder)
|
|
283
|
+
: undefined;
|
|
284
|
+
|
|
285
|
+
if (!tableBlock) {
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const tableRect = tableBlock.holder.getBoundingClientRect();
|
|
290
|
+
const isTopHalf = clientY < tableRect.top + tableRect.height / 2;
|
|
291
|
+
|
|
292
|
+
return { block: tableBlock, edge: isTopHalf ? 'top' : 'bottom', depth: 0 };
|
|
204
293
|
}
|
|
205
294
|
|
|
206
295
|
}
|
|
@@ -21,7 +21,7 @@ export const DRAG_CONFIG = {
|
|
|
21
21
|
* Styles for the drag preview element
|
|
22
22
|
*/
|
|
23
23
|
export const PREVIEW_STYLES = {
|
|
24
|
-
base: 'fixed pointer-events-none z-
|
|
24
|
+
base: 'fixed pointer-events-none z-10000 opacity-80 transition-none',
|
|
25
25
|
content: 'relative mx-auto max-w-content',
|
|
26
26
|
} as const;
|
|
27
27
|
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { generateBlockId } from '../utils/id-generator';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Minimal block shape expected by the normalizer.
|
|
5
|
+
* Matches the SaverValidatedData shape from the saver pipeline.
|
|
6
|
+
*/
|
|
7
|
+
interface NormalizableBlock {
|
|
8
|
+
id?: string;
|
|
9
|
+
tool?: string;
|
|
10
|
+
data?: Record<string, unknown>;
|
|
11
|
+
isValid: boolean;
|
|
12
|
+
parentId?: string | null;
|
|
13
|
+
contentIds?: string[];
|
|
14
|
+
tunes?: Record<string, unknown>;
|
|
15
|
+
time?: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Shape of a table cell within the table's content data.
|
|
20
|
+
*/
|
|
21
|
+
interface TableCell {
|
|
22
|
+
blocks: string[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Tracking info for a paragraph whose inline images need extraction.
|
|
27
|
+
*/
|
|
28
|
+
interface ExtractionInfo {
|
|
29
|
+
parentTableId: string;
|
|
30
|
+
imgSrcs: string[];
|
|
31
|
+
cleanedText: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Regex to match <img> tags and capture their src attribute.
|
|
36
|
+
* Handles both single and double quotes around src value.
|
|
37
|
+
*/
|
|
38
|
+
const IMG_TAG_REGEX = /<img\s+[^>]*src=["']([^"']+)["'][^>]*>/g;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Normalizes inline images in table cell paragraphs by extracting `<img>` tags
|
|
42
|
+
* into standalone image blocks.
|
|
43
|
+
*
|
|
44
|
+
* For each paragraph block whose parent is a table:
|
|
45
|
+
* 1. Extracts all `<img>` tags from the paragraph's text
|
|
46
|
+
* 2. Creates a new image block for each extracted `<img>`
|
|
47
|
+
* 3. Removes the `<img>` tags from the paragraph text
|
|
48
|
+
* 4. Inserts the new image block IDs before the paragraph in the table cell's blocks array
|
|
49
|
+
* 5. Adds the new image block IDs to the table's contentIds
|
|
50
|
+
*
|
|
51
|
+
* @param blocks - array of saved block data
|
|
52
|
+
* @returns new array with inline images extracted into standalone blocks
|
|
53
|
+
*/
|
|
54
|
+
export const normalizeInlineImages = <T extends NormalizableBlock>(blocks: T[]): T[] => {
|
|
55
|
+
/**
|
|
56
|
+
* Build a lookup of block id → block for quick parent resolution.
|
|
57
|
+
*/
|
|
58
|
+
const blockById = new Map<string, T>();
|
|
59
|
+
|
|
60
|
+
for (const block of blocks) {
|
|
61
|
+
if (block.id !== undefined) {
|
|
62
|
+
blockById.set(block.id, block);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Check if there are any table blocks at all. If not, return input unchanged.
|
|
68
|
+
*/
|
|
69
|
+
const hasTable = blocks.some((b) => b.tool === 'table');
|
|
70
|
+
|
|
71
|
+
if (!hasTable) {
|
|
72
|
+
return blocks;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const extractionMap = new Map<string, ExtractionInfo>();
|
|
76
|
+
|
|
77
|
+
for (const block of blocks) {
|
|
78
|
+
if (block.tool !== 'paragraph' || block.parentId === undefined || block.parentId === null) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const parent = blockById.get(block.parentId);
|
|
83
|
+
|
|
84
|
+
if (parent === undefined || parent.tool !== 'table') {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const text = block.data?.text;
|
|
89
|
+
|
|
90
|
+
if (typeof text !== 'string') {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const imgSrcs = Array.from(text.matchAll(IMG_TAG_REGEX), (m) => m[1]);
|
|
95
|
+
|
|
96
|
+
if (imgSrcs.length === 0) {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Remove all <img> tags from text.
|
|
102
|
+
*/
|
|
103
|
+
IMG_TAG_REGEX.lastIndex = 0;
|
|
104
|
+
const cleanedText = text.replace(IMG_TAG_REGEX, '');
|
|
105
|
+
|
|
106
|
+
if (block.id !== undefined) {
|
|
107
|
+
extractionMap.set(block.id, {
|
|
108
|
+
parentTableId: block.parentId,
|
|
109
|
+
imgSrcs,
|
|
110
|
+
cleanedText,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* If no paragraphs need extraction, return input unchanged.
|
|
117
|
+
*/
|
|
118
|
+
if (extractionMap.size === 0) {
|
|
119
|
+
return blocks;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Generate image block IDs and build new image blocks.
|
|
124
|
+
* Maps paragraph id → array of new image block entries.
|
|
125
|
+
*/
|
|
126
|
+
const newImageBlocksPerParagraph = new Map<string, T[]>();
|
|
127
|
+
|
|
128
|
+
for (const [paragraphId, info] of extractionMap) {
|
|
129
|
+
const imageBlocks: T[] = [];
|
|
130
|
+
|
|
131
|
+
for (const src of info.imgSrcs) {
|
|
132
|
+
const imageBlock = {
|
|
133
|
+
id: generateBlockId(),
|
|
134
|
+
tool: 'image',
|
|
135
|
+
data: { url: src },
|
|
136
|
+
isValid: true,
|
|
137
|
+
parentId: info.parentTableId,
|
|
138
|
+
} as unknown as T;
|
|
139
|
+
|
|
140
|
+
imageBlocks.push(imageBlock);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
newImageBlocksPerParagraph.set(paragraphId, imageBlocks);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Clone table blocks and update their content/contentIds with new image block references.
|
|
148
|
+
*/
|
|
149
|
+
const updatedTableIds = new Set<string>();
|
|
150
|
+
|
|
151
|
+
for (const info of extractionMap.values()) {
|
|
152
|
+
updatedTableIds.add(info.parentTableId);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const clonedTables = new Map<string, T>();
|
|
156
|
+
|
|
157
|
+
for (const tableId of updatedTableIds) {
|
|
158
|
+
const original = blockById.get(tableId);
|
|
159
|
+
|
|
160
|
+
if (original === undefined) {
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const cloned = { ...original };
|
|
165
|
+
const originalData = original.data as { content: TableCell[][] } | undefined;
|
|
166
|
+
|
|
167
|
+
if (originalData?.content !== undefined) {
|
|
168
|
+
/**
|
|
169
|
+
* Deep clone the content array so we can mutate cell blocks arrays.
|
|
170
|
+
*/
|
|
171
|
+
const clonedContent: TableCell[][] = originalData.content.map(
|
|
172
|
+
(row) => row.map((cell) => ({ ...cell, blocks: [...cell.blocks] }))
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
cloned.data = { ...original.data, content: clonedContent };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
cloned.contentIds = original.contentIds !== undefined ? [...original.contentIds] : [];
|
|
179
|
+
clonedTables.set(tableId, cloned);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Update cloned table cell blocks arrays and contentIds.
|
|
184
|
+
*/
|
|
185
|
+
for (const [paragraphId, imageBlocks] of newImageBlocksPerParagraph) {
|
|
186
|
+
const info = extractionMap.get(paragraphId);
|
|
187
|
+
|
|
188
|
+
if (info === undefined) {
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const clonedTable = clonedTables.get(info.parentTableId);
|
|
193
|
+
|
|
194
|
+
if (clonedTable === undefined) {
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const tableData = clonedTable.data as { content: TableCell[][] } | undefined;
|
|
199
|
+
|
|
200
|
+
if (tableData?.content === undefined) {
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const imageBlockIds = imageBlocks.map((b) => b.id ?? '');
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Find the cell containing this paragraph and insert image IDs before the paragraph.
|
|
208
|
+
*/
|
|
209
|
+
tableData.content.flat().forEach((cell) => {
|
|
210
|
+
const paragraphIndex = cell.blocks.indexOf(paragraphId);
|
|
211
|
+
|
|
212
|
+
if (paragraphIndex !== -1) {
|
|
213
|
+
cell.blocks.splice(paragraphIndex, 0, ...imageBlockIds);
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Add image block IDs to the table's contentIds.
|
|
219
|
+
*/
|
|
220
|
+
if (clonedTable.contentIds !== undefined) {
|
|
221
|
+
clonedTable.contentIds.push(...imageBlockIds);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Build the result array:
|
|
227
|
+
* - Replace table blocks with cloned versions
|
|
228
|
+
* - Replace paragraph blocks with cleaned text versions
|
|
229
|
+
* - Insert image blocks before their source paragraph
|
|
230
|
+
*/
|
|
231
|
+
const result: T[] = [];
|
|
232
|
+
|
|
233
|
+
for (const block of blocks) {
|
|
234
|
+
/**
|
|
235
|
+
* If this is a table that was updated, use the cloned version.
|
|
236
|
+
*/
|
|
237
|
+
if (block.id !== undefined && clonedTables.has(block.id)) {
|
|
238
|
+
result.push(clonedTables.get(block.id) as T);
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* If this is a paragraph with images to extract, insert image blocks before it
|
|
244
|
+
* and update its text.
|
|
245
|
+
*/
|
|
246
|
+
if (block.id !== undefined && extractionMap.has(block.id)) {
|
|
247
|
+
result.push(...(newImageBlocksPerParagraph.get(block.id) ?? []));
|
|
248
|
+
|
|
249
|
+
const info = extractionMap.get(block.id);
|
|
250
|
+
const updatedParagraph = {
|
|
251
|
+
...block,
|
|
252
|
+
data: { ...block.data, text: info?.cleanedText ?? '' },
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
result.push(updatedParagraph);
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
result.push(block);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return result;
|
|
263
|
+
};
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { mapToNearestPresetColor } from '../../utils/color-mapping';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Pre-process Google Docs clipboard HTML before sanitization.
|
|
5
|
+
*
|
|
6
|
+
* Google Docs wraps content in `<b id="docs-internal-guid-...">` and
|
|
7
|
+
* encodes formatting as inline styles on `<span>` elements rather than
|
|
8
|
+
* semantic tags. The sanitizer strips `<span>` (not in the allowed
|
|
9
|
+
* config), destroying formatting. This function converts style-based
|
|
10
|
+
* spans to `<b>`/`<i>`/`<mark>` BEFORE the sanitizer runs.
|
|
11
|
+
*
|
|
12
|
+
* @param html - raw clipboard HTML string
|
|
13
|
+
* @returns preprocessed HTML string
|
|
14
|
+
*/
|
|
15
|
+
export function preprocessGoogleDocsHtml(html: string): string {
|
|
16
|
+
const wrapper = document.createElement('div');
|
|
17
|
+
|
|
18
|
+
wrapper.innerHTML = html;
|
|
19
|
+
|
|
20
|
+
unwrapGoogleDocsContent(wrapper);
|
|
21
|
+
convertGoogleDocsStyles(wrapper);
|
|
22
|
+
convertTableCellParagraphs(wrapper);
|
|
23
|
+
|
|
24
|
+
return wrapper.innerHTML;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Strip Google Docs wrapper elements to expose underlying content.
|
|
29
|
+
* Google Docs wraps clipboard HTML in `<b id="docs-internal-guid-...">`.
|
|
30
|
+
* Content may be split across multiple child `<div>` elements (e.g. one
|
|
31
|
+
* per table), so all children are moved out of the wrapper.
|
|
32
|
+
*/
|
|
33
|
+
function unwrapGoogleDocsContent(wrapper: HTMLElement): void {
|
|
34
|
+
const googleDocsWrapper = wrapper.querySelector<HTMLElement>('b[id^="docs-internal-guid-"]');
|
|
35
|
+
|
|
36
|
+
if (!googleDocsWrapper) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const fragment = document.createDocumentFragment();
|
|
41
|
+
|
|
42
|
+
while (googleDocsWrapper.firstChild) {
|
|
43
|
+
fragment.appendChild(googleDocsWrapper.firstChild);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
googleDocsWrapper.replaceWith(fragment);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Determine the background-color style declaration for a Google Docs element.
|
|
51
|
+
*
|
|
52
|
+
* When a background color is present, it is mapped to the nearest preset.
|
|
53
|
+
* When only a foreground color is present, an explicit `transparent` background
|
|
54
|
+
* is returned so the mark element doesn't inherit an unwanted background.
|
|
55
|
+
*/
|
|
56
|
+
function resolveBackgroundStyle(hasBgColor: boolean, hasColor: boolean, mappedBg: string): string {
|
|
57
|
+
if (hasBgColor) {
|
|
58
|
+
return `background-color: ${mappedBg}`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (hasColor) {
|
|
62
|
+
return 'background-color: transparent';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return '';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Check whether a CSS color value is the default black text color.
|
|
70
|
+
* Google Docs uses different formats: `rgb(0, 0, 0)`, `rgb(0,0,0)`, or `#000000`.
|
|
71
|
+
* Spans with only this color should not be converted to `<mark>`.
|
|
72
|
+
*/
|
|
73
|
+
function isDefaultBlack(color: string): boolean {
|
|
74
|
+
const normalized = color.replace(/\s/g, '');
|
|
75
|
+
|
|
76
|
+
return normalized === 'rgb(0,0,0)' || normalized === '#000000';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Convert Google Docs style-based `<span>` elements to semantic HTML tags.
|
|
81
|
+
*
|
|
82
|
+
* - `<span style="font-weight:700">` or `font-weight:bold` → `<b>`
|
|
83
|
+
* - `<span style="font-style:italic">` → `<i>`
|
|
84
|
+
* - `<span style="color:...">` → `<mark style="color: ...">`
|
|
85
|
+
* - `<span style="background-color:...">` → `<mark style="background-color: ...">`
|
|
86
|
+
*
|
|
87
|
+
* Color and bold/italic can combine: a bold red span becomes `<b><mark style="color: red;">text</mark></b>`.
|
|
88
|
+
*/
|
|
89
|
+
function convertGoogleDocsStyles(wrapper: HTMLElement): void {
|
|
90
|
+
for (const span of Array.from(wrapper.querySelectorAll('span[style]'))) {
|
|
91
|
+
const style = span.getAttribute('style') ?? '';
|
|
92
|
+
const isBold = /font-weight\s*:\s*(700|bold)/i.test(style);
|
|
93
|
+
const isItalic = /font-style\s*:\s*italic/i.test(style);
|
|
94
|
+
|
|
95
|
+
const colorMatch = /(?<![a-z-])color\s*:\s*([^;]+)/i.exec(style);
|
|
96
|
+
const bgMatch = /background-color\s*:\s*([^;]+)/i.exec(style);
|
|
97
|
+
|
|
98
|
+
const color = colorMatch?.[1]?.trim();
|
|
99
|
+
const bgColor = bgMatch?.[1]?.trim();
|
|
100
|
+
|
|
101
|
+
const hasColor = color !== undefined && !isDefaultBlack(color);
|
|
102
|
+
const hasBgColor = bgColor !== undefined && bgColor !== 'transparent';
|
|
103
|
+
|
|
104
|
+
if (!isBold && !isItalic && !hasColor && !hasBgColor) {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const mappedColor = hasColor ? mapToNearestPresetColor(color, 'text') : '';
|
|
109
|
+
const mappedBg = hasBgColor ? mapToNearestPresetColor(bgColor, 'bg') : '';
|
|
110
|
+
|
|
111
|
+
const colorStyles = [
|
|
112
|
+
hasColor ? `color: ${mappedColor}` : '',
|
|
113
|
+
resolveBackgroundStyle(hasBgColor, hasColor, mappedBg),
|
|
114
|
+
].filter(Boolean).join('; ');
|
|
115
|
+
|
|
116
|
+
const inner = colorStyles
|
|
117
|
+
? `<mark style="${colorStyles};">${span.innerHTML}</mark>`
|
|
118
|
+
: span.innerHTML;
|
|
119
|
+
|
|
120
|
+
const italic = isItalic ? `<i>${inner}</i>` : inner;
|
|
121
|
+
const wrapped = isBold ? `<b>${italic}</b>` : italic;
|
|
122
|
+
|
|
123
|
+
span.replaceWith(document.createRange().createContextualFragment(wrapped));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
convertAnchorColorStyles(wrapper);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Convert color/background-color styles on `<a>` elements to `<mark>` tags.
|
|
131
|
+
*
|
|
132
|
+
* Google Docs sometimes puts background-color directly on the `<a>` element.
|
|
133
|
+
* The sanitizer only allows `href`/`target`/`rel` on `<a>`, so inline styles
|
|
134
|
+
* are stripped — losing the background. This moves color styles into a
|
|
135
|
+
* `<mark>` wrapping the link content before sanitization runs.
|
|
136
|
+
*/
|
|
137
|
+
function convertAnchorColorStyles(wrapper: HTMLElement): void {
|
|
138
|
+
for (const anchor of Array.from(wrapper.querySelectorAll('a[style]'))) {
|
|
139
|
+
const style = anchor.getAttribute('style') ?? '';
|
|
140
|
+
|
|
141
|
+
const colorMatch = /(?<![a-z-])color\s*:\s*([^;]+)/i.exec(style);
|
|
142
|
+
const bgMatch = /background-color\s*:\s*([^;]+)/i.exec(style);
|
|
143
|
+
|
|
144
|
+
const color = colorMatch?.[1]?.trim();
|
|
145
|
+
const bgColor = bgMatch?.[1]?.trim();
|
|
146
|
+
|
|
147
|
+
const hasColor = color !== undefined && !isDefaultBlack(color) && color !== 'inherit';
|
|
148
|
+
const hasBgColor = bgColor !== undefined && bgColor !== 'transparent' && bgColor !== 'inherit';
|
|
149
|
+
|
|
150
|
+
if (!hasColor && !hasBgColor) {
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const mappedColor = hasColor ? mapToNearestPresetColor(color, 'text') : '';
|
|
155
|
+
const mappedBg = hasBgColor ? mapToNearestPresetColor(bgColor, 'bg') : '';
|
|
156
|
+
|
|
157
|
+
const colorStyles = [
|
|
158
|
+
hasColor ? `color: ${mappedColor}` : '',
|
|
159
|
+
resolveBackgroundStyle(hasBgColor, hasColor, mappedBg),
|
|
160
|
+
].filter(Boolean).join('; ');
|
|
161
|
+
|
|
162
|
+
const el = anchor as HTMLElement;
|
|
163
|
+
|
|
164
|
+
el.innerHTML = `<mark style="${colorStyles};">${el.innerHTML}</mark>`;
|
|
165
|
+
el.style.removeProperty('color');
|
|
166
|
+
el.style.removeProperty('background-color');
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Convert `<p>` boundaries to `<br>` line breaks inside table cells.
|
|
172
|
+
*
|
|
173
|
+
* Google Docs wraps each line in a cell as a separate `<p>`. The sanitizer
|
|
174
|
+
* strips `<p>` (not in the allowed config), losing line breaks. Converting
|
|
175
|
+
* to `<br>` preserves them since `<br>` IS in the config (`{ br: {} }`).
|
|
176
|
+
*
|
|
177
|
+
* Only targets `<td>` and `<th>` elements — top-level `<p>` tags are left
|
|
178
|
+
* intact so the paste pipeline can split them into separate blocks.
|
|
179
|
+
*/
|
|
180
|
+
function convertTableCellParagraphs(wrapper: HTMLElement): void {
|
|
181
|
+
for (const cell of Array.from(wrapper.querySelectorAll('td, th'))) {
|
|
182
|
+
const paragraphs = cell.querySelectorAll('p');
|
|
183
|
+
|
|
184
|
+
if (paragraphs.length === 0) {
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
for (const p of Array.from(paragraphs)) {
|
|
189
|
+
const fragment = document.createRange().createContextualFragment(p.innerHTML + '<br>');
|
|
190
|
+
|
|
191
|
+
p.replaceWith(fragment);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Remove trailing <br> from the cell
|
|
195
|
+
cell.innerHTML = cell.innerHTML.replace(/(<br\s*\/?>|\s)+$/i, '');
|
|
196
|
+
}
|
|
197
|
+
}
|