@templatical/editor 0.43.2 → 0.44.0
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/README.md +1 -1
- package/dist/{AiChatSidebar-DTtMEXNR.js → AiChatSidebar-BUR5Jshu.js} +8 -8
- package/dist/{AiFeatureMenu-DFj-_S9b.js → AiFeatureMenu-CgfLFHJw.js} +4 -4
- package/dist/{BlockIssueBadge-CyLmqjLl.js → BlockIssueBadge-CWZEwMXX.js} +2 -2
- package/dist/{BlockPreviewCanvas-C6F66f3k.js → BlockPreviewCanvas-2vVu55yN.js} +3 -3
- package/dist/{CloudHeaderExtras-Z4K6OMn0.js → CloudHeaderExtras-BZ62q1aS.js} +2 -2
- package/dist/{CloudPanels-DOJ2Vske.js → CloudPanels-sv4aCKKE.js} +4 -4
- package/dist/{CollaboratorBar-BCkl3dKY.js → CollaboratorBar-CSEFL7uC.js} +2 -2
- package/dist/{CommentsPanel-DPMi6-rb.js → CommentsPanel-CYMHfFcn.js} +1 -1
- package/dist/{CommentsSidebar-C1mQcyh3.js → CommentsSidebar-uvQyCMUe.js} +10 -10
- package/dist/{CountdownToolbar-DKmzpk__.js → CountdownToolbar-C052mx2P.js} +3 -3
- package/dist/{DesignReferenceSidebar-CGRzOOb8.js → DesignReferenceSidebar-ZKJ7y7-G.js} +6 -6
- package/dist/{IssuesPanel-CsdxjjRm.js → IssuesPanel-DAlNiJX_.js} +5 -5
- package/dist/{LogicTagInsertButton-BX4jOUQy.js → LogicTagInsertButton-DMUfVsPr.js} +6 -6
- package/dist/{MediaEditModal-Bm8uOKBx.js → MediaEditModal-Cmd9tQXZ.js} +1 -1
- package/dist/{MediaPanels-BNWtEntu.js → MediaPanels-DoBhG5gg.js} +1 -1
- package/dist/{MergeTagInput-Mj8CFfX1.js → MergeTagInput-DoQWMSW4.js} +1 -1
- package/dist/{MergeTagModeToggle-CinojvpV.js → MergeTagModeToggle-DOHixe4G.js} +4 -4
- package/dist/{ParagraphEditor-DdOteRjD.js → ParagraphEditor-C_p6yKLK.js} +23 -23
- package/dist/{RestoreVersionDialog-B-i0wPjh.js → RestoreVersionDialog-CKHeCPG2.js} +3 -3
- package/dist/{RichTextEditorContent-CDtKOWsh.js → RichTextEditorContent-BedBVHXS.js} +4 -4
- package/dist/{SaveBlockDialog-DTcGPDMT.js → SaveBlockDialog-q3_fjb9t.js} +8 -8
- package/dist/{SavedBlocksBrowserModal-BA5yXQi0.js → SavedBlocksBrowserModal-C4J-_cW8.js} +9 -9
- package/dist/{SavedBlocksPanels-mfSALJqB.js → SavedBlocksPanels-DBoqC6df.js} +1 -1
- package/dist/{SavedBlocksPickBar-BOjduGI4.js → SavedBlocksPickBar-Pq2bjpB_.js} +2 -2
- package/dist/{TemplateScoringPanel-Byz-ajjf.js → TemplateScoringPanel-DXsyp0_A.js} +11 -11
- package/dist/{TemplateSettings-Bf5rfsU8.js → TemplateSettings-Bh10NFIN.js} +6 -6
- package/dist/{TestEmailModal-DJo2yFLh.js → TestEmailModal-BYCKpH7g.js} +7 -7
- package/dist/{TestEmailPanel-CXIFWfDm.js → TestEmailPanel-lUXAFsT5.js} +1 -1
- package/dist/{TitleEditor-BDtokkQF.js → TitleEditor-CWPxXUp2.js} +9 -9
- package/dist/{ToggleSwitch-BCYZvP1e.js → ToggleSwitch-D7GLaT8s.js} +1 -1
- package/dist/{Toolbar-XOtJ_N58.js → Toolbar-CWxqqBHE.js} +29 -29
- package/dist/{TplModal-C3mzMXof.js → TplModal-D6lqrKls.js} +1 -1
- package/dist/{VersionHistoryMenu-DRoFz15y.js → VersionHistoryMenu-fLrx-Lvi.js} +5 -5
- package/dist/{VersionHistoryPanels-CeF0JnXI.js → VersionHistoryPanels-C3XhpQO_.js} +1 -1
- package/dist/{VersionPreviewBanner-DQvIEKUU.js → VersionPreviewBanner-CWuxq_bb.js} +1 -1
- package/dist/{WrapperBlock-DZwp4G7P.js → WrapperBlock-ox8TMS_p.js} +1 -1
- package/dist/{blockTypeIcons-CAPoy6X7.js → blockTypeIcons-DEvNVvBj.js} +2 -2
- package/dist/bundle-stats.json +6 -6
- package/dist/{ca--zGWn9AU.js → ca-DSwai-th.js} +1 -4
- package/dist/cdn/chunks/{AiChatSidebar-DW1uRkKE.js → AiChatSidebar-Cvp9beXR.js} +3 -3
- package/dist/cdn/chunks/{AiChatSidebar-DW1uRkKE.js.map → AiChatSidebar-Cvp9beXR.js.map} +1 -1
- package/dist/cdn/chunks/{AiFeatureMenu-CHsgD78N.js → AiFeatureMenu-vOJtwdmT.js} +3 -3
- package/dist/cdn/chunks/{AiFeatureMenu-CHsgD78N.js.map → AiFeatureMenu-vOJtwdmT.js.map} +1 -1
- package/dist/cdn/chunks/{BlockIssueBadge-CIqu8JnG.js → BlockIssueBadge-CKIrYROg.js} +2 -2
- package/dist/cdn/chunks/{BlockIssueBadge-CIqu8JnG.js.map → BlockIssueBadge-CKIrYROg.js.map} +1 -1
- package/dist/cdn/chunks/{BlockPreviewCanvas-Dtea66SI.js → BlockPreviewCanvas-CB27Joyn.js} +4 -4
- package/dist/cdn/chunks/{BlockPreviewCanvas-Dtea66SI.js.map → BlockPreviewCanvas-CB27Joyn.js.map} +1 -1
- package/dist/cdn/chunks/{CloudHeaderExtras-Kjj78__m.js → CloudHeaderExtras-9tKrhPm6.js} +3 -3
- package/dist/cdn/chunks/{CloudHeaderExtras-Kjj78__m.js.map → CloudHeaderExtras-9tKrhPm6.js.map} +1 -1
- package/dist/cdn/chunks/{CloudPanels-DARXXD0q.js → CloudPanels-DTQ7zbEN.js} +4 -4
- package/dist/cdn/chunks/{CloudPanels-DARXXD0q.js.map → CloudPanels-DTQ7zbEN.js.map} +1 -1
- package/dist/cdn/chunks/{CollaboratorBar-C7O9mQJ_.js → CollaboratorBar-BKTkVMbz.js} +3 -3
- package/dist/cdn/chunks/{CollaboratorBar-C7O9mQJ_.js.map → CollaboratorBar-BKTkVMbz.js.map} +1 -1
- package/dist/cdn/chunks/{CommentsPanel-KH5_JS7_.js → CommentsPanel-BjHRj_Yw.js} +2 -2
- package/dist/cdn/chunks/{CommentsPanel-KH5_JS7_.js.map → CommentsPanel-BjHRj_Yw.js.map} +1 -1
- package/dist/cdn/chunks/{CommentsSidebar-ucrNvgAQ.js → CommentsSidebar-CpCfqSq4.js} +2 -2
- package/dist/cdn/chunks/{CommentsSidebar-ucrNvgAQ.js.map → CommentsSidebar-CpCfqSq4.js.map} +1 -1
- package/dist/cdn/chunks/{CountdownToolbar-DVInzkj9.js → CountdownToolbar-C9QpHQcv.js} +4 -4
- package/dist/cdn/chunks/{CountdownToolbar-DVInzkj9.js.map → CountdownToolbar-C9QpHQcv.js.map} +1 -1
- package/dist/cdn/chunks/{DesignReferenceSidebar-pleljkEJ.js → DesignReferenceSidebar-BO0xz1wC.js} +3 -3
- package/dist/cdn/chunks/{DesignReferenceSidebar-pleljkEJ.js.map → DesignReferenceSidebar-BO0xz1wC.js.map} +1 -1
- package/dist/cdn/chunks/{IssuesPanel-CPcQIUni.js → IssuesPanel--62qud8w.js} +2 -2
- package/dist/cdn/chunks/{IssuesPanel-CPcQIUni.js.map → IssuesPanel--62qud8w.js.map} +1 -1
- package/dist/cdn/chunks/{LogicTagInsertButton-DSrW9v0x.js → LogicTagInsertButton-gc8ICURV.js} +5 -5
- package/dist/cdn/chunks/{LogicTagInsertButton-DSrW9v0x.js.map → LogicTagInsertButton-gc8ICURV.js.map} +1 -1
- package/dist/cdn/chunks/{MediaEditModal-BHp7euCZ.js → MediaEditModal-BuDXKwx4.js} +2 -2
- package/dist/cdn/chunks/{MediaEditModal-BHp7euCZ.js.map → MediaEditModal-BuDXKwx4.js.map} +1 -1
- package/dist/cdn/chunks/{MediaPanels-CGIRlO48.js → MediaPanels-xFpaxFRH.js} +2 -2
- package/dist/cdn/chunks/{MediaPanels-CGIRlO48.js.map → MediaPanels-xFpaxFRH.js.map} +1 -1
- package/dist/cdn/chunks/{MergeTagInput-BSFofWuc.js → MergeTagInput-DsPKWbWr.js} +2 -2
- package/dist/cdn/chunks/{MergeTagInput-BSFofWuc.js.map → MergeTagInput-DsPKWbWr.js.map} +1 -1
- package/dist/cdn/chunks/{MergeTagModeToggle-D7KlTFZV.js → MergeTagModeToggle-DU0WqgVH.js} +3 -3
- package/dist/cdn/chunks/{MergeTagModeToggle-D7KlTFZV.js.map → MergeTagModeToggle-DU0WqgVH.js.map} +1 -1
- package/dist/cdn/chunks/{ParagraphEditor-CokXagL1.js → ParagraphEditor-BSDGOsja.js} +23 -23
- package/dist/cdn/chunks/{ParagraphEditor-CokXagL1.js.map → ParagraphEditor-BSDGOsja.js.map} +1 -1
- package/dist/cdn/chunks/{RestoreVersionDialog-C-XEncAM.js → RestoreVersionDialog-DLe0Co57.js} +3 -3
- package/dist/cdn/chunks/{RestoreVersionDialog-C-XEncAM.js.map → RestoreVersionDialog-DLe0Co57.js.map} +1 -1
- package/dist/cdn/chunks/{RichTextEditorContent-DS_2UsJX.js → RichTextEditorContent-Dkcts8Vh.js} +4 -4
- package/dist/cdn/chunks/{RichTextEditorContent-DS_2UsJX.js.map → RichTextEditorContent-Dkcts8Vh.js.map} +1 -1
- package/dist/cdn/chunks/{SaveBlockDialog-D8OAKF_k.js → SaveBlockDialog-DQzaED8U.js} +7 -7
- package/dist/cdn/chunks/{SaveBlockDialog-D8OAKF_k.js.map → SaveBlockDialog-DQzaED8U.js.map} +1 -1
- package/dist/cdn/chunks/{SavedBlocksBrowserModal-B6GN_llD.js → SavedBlocksBrowserModal-D3JF-Gx-.js} +6 -6
- package/dist/cdn/chunks/{SavedBlocksBrowserModal-B6GN_llD.js.map → SavedBlocksBrowserModal-D3JF-Gx-.js.map} +1 -1
- package/dist/cdn/chunks/{SavedBlocksPanels-hRCREVxS.js → SavedBlocksPanels-Cxbgby24.js} +2 -2
- package/dist/cdn/chunks/{SavedBlocksPanels-hRCREVxS.js.map → SavedBlocksPanels-Cxbgby24.js.map} +1 -1
- package/dist/cdn/chunks/{SavedBlocksPickBar-C5YrK0iB.js → SavedBlocksPickBar-DEL0r3Bv.js} +3 -3
- package/dist/cdn/chunks/{SavedBlocksPickBar-C5YrK0iB.js.map → SavedBlocksPickBar-DEL0r3Bv.js.map} +1 -1
- package/dist/cdn/chunks/{TemplateScoringPanel-DXyQjcdU.js → TemplateScoringPanel-BBRHCNcR.js} +2 -2
- package/dist/cdn/chunks/{TemplateScoringPanel-DXyQjcdU.js.map → TemplateScoringPanel-BBRHCNcR.js.map} +1 -1
- package/dist/cdn/chunks/{TemplateSettings-da14bXlk.js → TemplateSettings-vsFOU0oA.js} +5 -5
- package/dist/cdn/chunks/{TemplateSettings-da14bXlk.js.map → TemplateSettings-vsFOU0oA.js.map} +1 -1
- package/dist/cdn/chunks/{TestEmailModal-Dl_cAyj-.js → TestEmailModal-B7Dr2eFa.js} +7 -7
- package/dist/cdn/chunks/{TestEmailModal-Dl_cAyj-.js.map → TestEmailModal-B7Dr2eFa.js.map} +1 -1
- package/dist/cdn/chunks/{TestEmailPanel-CVZGOKaQ.js → TestEmailPanel-C3PTYDbT.js} +2 -2
- package/dist/cdn/chunks/{TestEmailPanel-CVZGOKaQ.js.map → TestEmailPanel-C3PTYDbT.js.map} +1 -1
- package/dist/cdn/chunks/{TitleEditor-DO8OdG_w.js → TitleEditor-gHTla_4I.js} +8 -8
- package/dist/cdn/chunks/{TitleEditor-DO8OdG_w.js.map → TitleEditor-gHTla_4I.js.map} +1 -1
- package/dist/cdn/chunks/{ToggleSwitch-D5Wi6Env.js → ToggleSwitch-DoUNcLPP.js} +2 -2
- package/dist/cdn/chunks/{ToggleSwitch-D5Wi6Env.js.map → ToggleSwitch-DoUNcLPP.js.map} +1 -1
- package/dist/cdn/chunks/{Toolbar-CXDE8xM-.js → Toolbar-CnfNUkOY.js} +17 -17
- package/dist/cdn/chunks/{Toolbar-CXDE8xM-.js.map → Toolbar-CnfNUkOY.js.map} +1 -1
- package/dist/cdn/chunks/{TplModal-CFi-Psf7.js → TplModal-BVUBOEQL.js} +2 -2
- package/dist/cdn/chunks/{TplModal-CFi-Psf7.js.map → TplModal-BVUBOEQL.js.map} +1 -1
- package/dist/cdn/chunks/{VersionHistoryMenu-1bfCr14E.js → VersionHistoryMenu-Cp3EHO_P.js} +2 -2
- package/dist/cdn/chunks/{VersionHistoryMenu-1bfCr14E.js.map → VersionHistoryMenu-Cp3EHO_P.js.map} +1 -1
- package/dist/cdn/chunks/{VersionHistoryPanels-DbgbyAdW.js → VersionHistoryPanels-Dcdy7ySA.js} +2 -2
- package/dist/cdn/chunks/{VersionHistoryPanels-DbgbyAdW.js.map → VersionHistoryPanels-Dcdy7ySA.js.map} +1 -1
- package/dist/cdn/chunks/{VersionPreviewBanner-BaOTRIrm.js → VersionPreviewBanner-BgkroXnG.js} +2 -2
- package/dist/cdn/chunks/{VersionPreviewBanner-BaOTRIrm.js.map → VersionPreviewBanner-BgkroXnG.js.map} +1 -1
- package/dist/cdn/chunks/{WrapperBlock-BYNfYvl3.js → WrapperBlock-23nAzweL.js} +2 -2
- package/dist/cdn/chunks/{WrapperBlock-BYNfYvl3.js.map → WrapperBlock-23nAzweL.js.map} +1 -1
- package/dist/cdn/chunks/{blockTypeIcons-B_IAjnTD.js → blockTypeIcons-BMZi7sdX.js} +2 -2
- package/dist/cdn/chunks/{blockTypeIcons-B_IAjnTD.js.map → blockTypeIcons-BMZi7sdX.js.map} +1 -1
- package/dist/cdn/chunks/{ca--zGWn9AU.js → ca-DSwai-th.js} +2 -5
- package/dist/cdn/chunks/ca-DSwai-th.js.map +1 -0
- package/dist/cdn/chunks/{cloud-De-eh1u6.js → cloud-DLQU5l0Z.js} +2 -2
- package/dist/cdn/chunks/{cloud-De-eh1u6.js.map → cloud-DLQU5l0Z.js.map} +1 -1
- package/dist/cdn/chunks/{createCloudRuntime-DsaUaWvr.js → createCloudRuntime-Mnx8kV2W.js} +3 -3
- package/dist/cdn/chunks/{createCloudRuntime-DsaUaWvr.js.map → createCloudRuntime-Mnx8kV2W.js.map} +1 -1
- package/dist/cdn/chunks/{de-BRNLKcDl.js → de-DOhKuphf.js} +2 -5
- package/dist/cdn/chunks/de-DOhKuphf.js.map +1 -0
- package/dist/cdn/chunks/dist-BRZhN8qJ.js.map +1 -1
- package/dist/cdn/chunks/dist-CXB2_kBa.js.map +1 -1
- package/dist/cdn/chunks/draggable-IB4x6w2O.js.map +1 -1
- package/dist/cdn/chunks/{editor-modal-BDOfw3l0.js → editor-modal-B7NNAFI5.js} +4 -4
- package/dist/cdn/chunks/{editor-modal-BDOfw3l0.js.map → editor-modal-B7NNAFI5.js.map} +1 -1
- package/dist/cdn/chunks/{en-JJjqHMjy.js → en-BE28ZcUF.js} +2 -5
- package/dist/cdn/chunks/en-BE28ZcUF.js.map +1 -0
- package/dist/cdn/chunks/en-C3n0qWte.js +2 -0
- package/dist/cdn/chunks/{es-BIh8BxV9.js → es-oQI-dfx2.js} +2 -5
- package/dist/cdn/chunks/es-oQI-dfx2.js.map +1 -0
- package/dist/cdn/chunks/{extensions-1hJBHnGv.js → extensions-ClmkNA8R.js} +5 -5
- package/dist/cdn/chunks/{extensions-1hJBHnGv.js.map → extensions-ClmkNA8R.js.map} +1 -1
- package/dist/cdn/chunks/{fr-C5VUZogP.js → fr-rzLdCMSG.js} +2 -5
- package/dist/cdn/chunks/fr-rzLdCMSG.js.map +1 -0
- package/dist/cdn/chunks/htmlparser-ClUZ6waR.js.map +1 -1
- package/dist/cdn/chunks/{icons-BrAyaJ0T.js → icons-BjvraR-Y.js} +35 -35
- package/dist/cdn/chunks/{icons-BrAyaJ0T.js.map → icons-BjvraR-Y.js.map} +1 -1
- package/dist/{ja-x5JYxaCW.js → cdn/chunks/ja-dyFc6Ghz.js} +3 -4
- package/dist/cdn/chunks/ja-dyFc6Ghz.js.map +1 -0
- package/dist/cdn/chunks/{liquid.browser-BypTT2oc.js → liquid.browser-DXPjcePu.js} +758 -719
- package/dist/cdn/chunks/liquid.browser-DXPjcePu.js.map +1 -0
- package/dist/cdn/chunks/{nl-UXxFyXV6.js → nl-BcQhRqKR.js} +2 -5
- package/dist/cdn/chunks/nl-BcQhRqKR.js.map +1 -0
- package/dist/cdn/chunks/{preRenderCustomBlocks-h4ZFBKbD.js → preRenderCustomBlocks-Bg3rIgGJ.js} +2 -2
- package/dist/cdn/chunks/{preRenderCustomBlocks-h4ZFBKbD.js.map → preRenderCustomBlocks-Bg3rIgGJ.js.map} +1 -1
- package/dist/cdn/chunks/{pt-BR-DHm3SP6W.js → pt-BR-UombhTMO.js} +2 -5
- package/dist/cdn/chunks/pt-BR-UombhTMO.js.map +1 -0
- package/dist/cdn/chunks/pusher-BbIHJKf3.js.map +1 -1
- package/dist/cdn/chunks/{socialIcons-ZOCwCCyn.js → socialIcons-CnUJBZTO.js} +2 -2
- package/dist/cdn/chunks/socialIcons-CnUJBZTO.js.map +1 -0
- package/dist/cdn/chunks/{src-fZSLjhjy.js → src-B5-VKcRQ.js} +2 -2
- package/dist/cdn/chunks/{src-fZSLjhjy.js.map → src-B5-VKcRQ.js.map} +1 -1
- package/dist/cdn/chunks/{src-D5ceT_Y4.js → src-BmKDV_BK.js} +4 -4
- package/dist/cdn/chunks/{src-D5ceT_Y4.js.map → src-BmKDV_BK.js.map} +1 -1
- package/dist/cdn/chunks/{src-BR3I3gOW.js → src-QIGNPFkl.js} +6 -8
- package/dist/cdn/chunks/src-QIGNPFkl.js.map +1 -0
- package/dist/cdn/chunks/{tiptap-D1u8JIUW.js → tiptap-02G9FGkl.js} +3726 -3552
- package/dist/cdn/chunks/tiptap-02G9FGkl.js.map +1 -0
- package/dist/cdn/chunks/{useEditorCore-BTKqlRIg.js → useEditorCore-DjD7fJpq.js} +19 -19
- package/dist/cdn/chunks/{useEditorCore-BTKqlRIg.js.map → useEditorCore-DjD7fJpq.js.map} +1 -1
- package/dist/cdn/chunks/{useLogicTag-J_i_hvZg.js → useLogicTag-D6lWY0bD.js} +2 -2
- package/dist/cdn/chunks/{useLogicTag-J_i_hvZg.js.map → useLogicTag-D6lWY0bD.js.map} +1 -1
- package/dist/cdn/chunks/{useMergeTag-C5MyHYxJ.js → useMergeTag-2iW-BKWW.js} +2 -2
- package/dist/cdn/chunks/{useMergeTag-C5MyHYxJ.js.map → useMergeTag-2iW-BKWW.js.map} +1 -1
- package/dist/cdn/editor.css +1 -1
- package/dist/cdn/editor.js +467 -484
- package/dist/cdn/editor.js.map +1 -1
- package/dist/{check-CA6fvSIp.js → check-DNyj-E0y.js} +1 -1
- package/dist/{chevron-down-DYGJgpqJ.js → chevron-down-BYnhkMsA.js} +1 -1
- package/dist/{chevron-right-Dm5_X4j6.js → chevron-right-D9HGBqJo.js} +1 -1
- package/dist/{chevron-up-Ds9ormyq.js → chevron-up-BnIR1-ma.js} +1 -1
- package/dist/{circle-alert-BYXE0olE.js → circle-alert-B6xl1B_M.js} +1 -1
- package/dist/{clock-Ch1-h0sb.js → clock-Cs5Wd3dW.js} +1 -1
- package/dist/{cloud-AO_X8T9g.js → cloud-BNEL7bxL.js} +1 -1
- package/dist/{copy-DLNyaPza.js → copy-6uKc-WBk.js} +1 -1
- package/dist/{createCloudRuntime-D641nhri.js → createCloudRuntime-CaZapQZ6.js} +2 -2
- package/dist/{createLucideIcon-BrPnbRQT.js → createLucideIcon-BVcoovvP.js} +5 -5
- package/dist/{de-BRNLKcDl.js → de-DOhKuphf.js} +1 -4
- package/dist/{dist-CxmLZQso.js → dist-8By0p8ah.js} +5 -5
- package/dist/{dist-W_ERo6c_.js → dist-BYg1EL5g.js} +5 -5
- package/dist/{dist-C2Ge44fD.js → dist-BgSLW3Hn.js} +1704 -1303
- package/dist/{dist-B0dkvy7z.js → dist-CWc7nIkA.js} +5 -5
- package/dist/{dist-CIxmOOSG.js → dist-Ceew_u40.js} +448 -699
- package/dist/{dist-BwoEME8p.js → dist-CmwBvyW0.js} +172 -171
- package/dist/{dist-CJ7RZARo.js → dist-Czzst9ng.js} +302 -279
- package/dist/dist-D2m55Lvs.js +5 -0
- package/dist/{dist-p-LkiWIQ.js → dist-D4HP93ZF.js} +13 -13
- package/dist/{dist-BPWuzECC.js → dist-DdSWMtEP.js} +6 -6
- package/dist/{dist-DRmafAUz.js → dist-DmQckB4p.js} +29 -29
- package/dist/dist-Dw2_11LA.js +5 -0
- package/dist/{dist-iLUVPysa.js → dist-HjILVdTA.js} +5 -7
- package/dist/{editor-modal-Cl5GFWTQ.js → editor-modal-Co-rZGaf.js} +16 -16
- package/dist/en-B-xoKkWi.js +2 -0
- package/dist/{en-JJjqHMjy.js → en-BE28ZcUF.js} +1 -4
- package/dist/{es-BIh8BxV9.js → es-oQI-dfx2.js} +1 -4
- package/dist/{extensions-AKHJ0lax.js → extensions-DXSI8Dyj.js} +63 -63
- package/dist/{eye-_7xKFxQh.js → eye-Nbtw_q05.js} +1 -1
- package/dist/{fr-C5VUZogP.js → fr-rzLdCMSG.js} +1 -4
- package/dist/{image-up-Ck-xPOGS.js → image-up-C1jC7NzQ.js} +1 -1
- package/dist/index.d.ts +2434 -374
- package/dist/{info-CBBNLK_i.js → info-ClH66pKN.js} +1 -1
- package/dist/{cdn/chunks/ja-x5JYxaCW.js → ja-dyFc6Ghz.js} +1 -6
- package/dist/{link-BA_V9Nns.js → link-y47GNW0o.js} +1 -1
- package/dist/{liquid.browser-wPG0n4Cs.js → liquid.browser-Cs4gOjeX.js} +757 -718
- package/dist/{list-BTujzSSo.js → list-DiLMlMaE.js} +1 -1
- package/dist/{loader-circle-B5OQFRUF.js → loader-circle-BRdCPW5h.js} +1 -1
- package/dist/{message-circle-DWXr27lZ.js → message-circle-CNgIpVJv.js} +1 -1
- package/dist/{nl-UXxFyXV6.js → nl-BcQhRqKR.js} +1 -4
- package/dist/{package-DXW9QTU3.js → package-B9OWk-Kp.js} +1 -1
- package/dist/{pencil-QmONZtjF.js → pencil-BDnjo24u.js} +1 -1
- package/dist/{plus-XxHKE_GT.js → plus-BMgjN7lI.js} +1 -1
- package/dist/{preRenderCustomBlocks-CTstI3Q-.js → preRenderCustomBlocks-C_PIxBp7.js} +1 -1
- package/dist/{pt-BR-DHm3SP6W.js → pt-BR-UombhTMO.js} +1 -4
- package/dist/{refresh-cw-DhXU8dCo.js → refresh-cw-CR5lenxl.js} +1 -1
- package/dist/{search-CpFaMrnL.js → search-CxRUiOSF.js} +1 -1
- package/dist/{send-CFK3e6tQ.js → send-Bu2RysGL.js} +1 -1
- package/dist/{shield-check-ndwpOTik.js → shield-check-Cg8f4LB0.js} +1 -1
- package/dist/{smartphone-DndMh9W6.js → smartphone-CHf-8lHp.js} +1 -1
- package/dist/{socialIcons-D43BLoTu.js → socialIcons-D1gkHc_i.js} +2 -2
- package/dist/{sparkles-c1M5lwAQ.js → sparkles-_DQGv4ub.js} +1 -1
- package/dist/style.css +1 -1
- package/dist/templatical-editor.js +578 -595
- package/dist/{text-align-start-DOtBufPq.js → text-align-start-PHo-Ep5i.js} +13 -13
- package/dist/{trash-DmaPdiUk.js → trash-BxIzHAri.js} +1 -1
- package/dist/{triangle-alert-DqvxsSNL.js → triangle-alert-fcb2KoA-.js} +1 -1
- package/dist/{upload-CO_hogFv.js → upload-BxBtMPMv.js} +1 -1
- package/dist/{useEditorCore-0K8joh4X.js → useEditorCore-iuKC8yr5.js} +23 -23
- package/dist/{useLogicTag-BaH8gey0.js → useLogicTag-wqfdwYYR.js} +1 -1
- package/dist/{useMergeTag-Cyad1yBE.js → useMergeTag-D3XDE_58.js} +1 -1
- package/dist/{useScrollToBlock-BU5A9eS9.js → useScrollToBlock-DA_aIJXS.js} +1 -1
- package/dist/{x-BCop_VsL.js → x-DrZO5tt0.js} +1 -1
- package/package.json +30 -26
- package/dist/cdn/chunks/ca--zGWn9AU.js.map +0 -1
- package/dist/cdn/chunks/de-BRNLKcDl.js.map +0 -1
- package/dist/cdn/chunks/en-DxpXHoxJ.js +0 -2
- package/dist/cdn/chunks/en-JJjqHMjy.js.map +0 -1
- package/dist/cdn/chunks/es-BIh8BxV9.js.map +0 -1
- package/dist/cdn/chunks/fr-C5VUZogP.js.map +0 -1
- package/dist/cdn/chunks/ja-x5JYxaCW.js.map +0 -1
- package/dist/cdn/chunks/liquid.browser-BypTT2oc.js.map +0 -1
- package/dist/cdn/chunks/nl-UXxFyXV6.js.map +0 -1
- package/dist/cdn/chunks/pt-BR-DHm3SP6W.js.map +0 -1
- package/dist/cdn/chunks/socialIcons-ZOCwCCyn.js.map +0 -1
- package/dist/cdn/chunks/src-BR3I3gOW.js.map +0 -1
- package/dist/cdn/chunks/tiptap-D1u8JIUW.js.map +0 -1
- package/dist/dist-BUHovM4k.js +0 -5
- package/dist/dist-DdOAotfz.js +0 -5
- package/dist/en-CdCFaV2V.js +0 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,279 +1,704 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
import type { McpConfig } from '@templatical/types';
|
|
32
|
-
import { MediaAsset } from '@templatical/types';
|
|
33
|
-
import { MediaOptions } from '@templatical/types';
|
|
34
|
-
import { MediaProvider as MediaProvider_2 } from '@templatical/types';
|
|
35
|
-
import { MediaRequestContext } from '@templatical/types';
|
|
36
|
-
import type { MediaResult } from '@templatical/types';
|
|
37
|
-
import type { MergeTag } from '@templatical/types';
|
|
38
|
-
import { MergeTagsConfig } from '@templatical/types';
|
|
39
|
-
import { Ref } from 'vue';
|
|
40
|
-
import { RenderPayload } from '@templatical/types';
|
|
41
|
-
import { RenderProvider } from '@templatical/types';
|
|
42
|
-
import type { ResolvePreview } from '@templatical/types';
|
|
43
|
-
import { SavedBlock } from '@templatical/types';
|
|
44
|
-
import { SavedBlocksListParams } from '@templatical/types';
|
|
45
|
-
import { SavedBlocksOptions } from '@templatical/types';
|
|
46
|
-
import { SavedBlocksProvider } from '@templatical/types';
|
|
47
|
-
import { Template } from '@templatical/types';
|
|
48
|
-
import { TemplateContent } from '@templatical/types';
|
|
49
|
-
import { TemplateDefaults } from '@templatical/types';
|
|
50
|
-
import { TemplatePatch } from '@templatical/types';
|
|
51
|
-
import { TemplateSaveTrigger } from '@templatical/types';
|
|
52
|
-
import { TemplateSettingsConfig } from '@templatical/types';
|
|
53
|
-
import { TemplatesOptions } from '@templatical/types';
|
|
54
|
-
import { TemplatesProvider } from '@templatical/types';
|
|
55
|
-
import { TestEmailOptions } from '@templatical/types';
|
|
56
|
-
import { TestEmailPayload } from '@templatical/types';
|
|
57
|
-
import { TestEmailProvider } from '@templatical/types';
|
|
58
|
-
import { ThemeOverrides } from '@templatical/types';
|
|
59
|
-
import { UiTheme } from '@templatical/types';
|
|
60
|
-
import { validateLayout } from '@templatical/types';
|
|
61
|
-
import { VersionHistoryOptions } from '@templatical/types';
|
|
62
|
-
import type { VersionHistoryProvider } from '@templatical/types';
|
|
63
|
-
import { ViewportSize } from '@templatical/types';
|
|
64
|
-
|
|
65
|
-
export { applyLayout }
|
|
66
|
-
|
|
67
|
-
export { BlockDefaults }
|
|
68
|
-
|
|
69
|
-
export { ColorsConfig }
|
|
70
|
-
|
|
71
|
-
export { CommentEventMeta }
|
|
72
|
-
|
|
73
|
-
export { CommentsOptions }
|
|
74
|
-
|
|
75
|
-
export { createDefaultTemplateContent }
|
|
76
|
-
|
|
77
|
-
export { createLocalStorageMediaProvider }
|
|
78
|
-
|
|
79
|
-
export { createLocalStorageSavedBlocksProvider }
|
|
80
|
-
|
|
81
|
-
export { createParagraphBlock }
|
|
82
|
-
|
|
83
|
-
export { createSlotBlock }
|
|
84
|
-
|
|
85
|
-
export { createWrapperBlock }
|
|
86
|
-
|
|
87
|
-
export { CustomBlockDefinition }
|
|
88
|
-
|
|
89
|
-
export { CustomFont }
|
|
90
|
-
|
|
91
|
-
export { DisplayConditionsConfig }
|
|
92
|
-
|
|
93
|
-
export declare interface EditorCapabilities {
|
|
94
|
-
plan?: {
|
|
95
|
-
hasFeature(feature: string): boolean;
|
|
96
|
-
};
|
|
97
|
-
ai?: {
|
|
98
|
-
isFeatureEnabled(feature: string): boolean;
|
|
1
|
+
/** Options consumed only by the accessibility linter. */
|
|
2
|
+
declare interface AccessibilityLintOptions {
|
|
3
|
+
rules?: RuleOverrides;
|
|
4
|
+
thresholds?: Partial<LintThresholds>;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
declare interface AiConfig {
|
|
8
|
+
chat?: boolean;
|
|
9
|
+
scoring?: boolean;
|
|
10
|
+
designToTemplate?: boolean;
|
|
11
|
+
rewrite?: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Overlay `layout` around `content`. Clones both; remints layout ids.
|
|
16
|
+
* Layout wins only `settings.backgroundColor`.
|
|
17
|
+
*/
|
|
18
|
+
export declare function applyLayout(layout: TemplateContent, content: TemplateContent): TemplateContent;
|
|
19
|
+
|
|
20
|
+
declare interface BaseBlock {
|
|
21
|
+
id: string;
|
|
22
|
+
type: string;
|
|
23
|
+
styles: BlockStyles;
|
|
24
|
+
visibility?: BlockVisibility;
|
|
25
|
+
displayCondition?: {
|
|
26
|
+
label: string;
|
|
27
|
+
before: string;
|
|
28
|
+
after: string;
|
|
29
|
+
group?: string;
|
|
30
|
+
description?: string;
|
|
99
31
|
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
declare type Block = SectionBlock | TitleBlock | ParagraphBlock | ImageBlock | ButtonBlock | DividerBlock | VideoBlock | SocialIconsBlock | SpacerBlock | HtmlBlock | MenuBlock | TableBlock | CountdownBlock | CustomBlock | SlotBlock | WrapperBlock;
|
|
35
|
+
|
|
36
|
+
export declare interface BlockDefaults {
|
|
37
|
+
title?: BlockDefaultsFor<TitleBlock>;
|
|
38
|
+
paragraph?: BlockDefaultsFor<ParagraphBlock>;
|
|
39
|
+
image?: BlockDefaultsFor<ImageBlock>;
|
|
40
|
+
button?: BlockDefaultsFor<ButtonBlock>;
|
|
41
|
+
divider?: BlockDefaultsFor<DividerBlock>;
|
|
42
|
+
section?: BlockDefaultsFor<SectionBlock>;
|
|
43
|
+
video?: BlockDefaultsFor<VideoBlock>;
|
|
44
|
+
social?: BlockDefaultsFor<SocialIconsBlock>;
|
|
45
|
+
spacer?: BlockDefaultsFor<SpacerBlock>;
|
|
46
|
+
html?: BlockDefaultsFor<HtmlBlock>;
|
|
47
|
+
menu?: BlockDefaultsFor<MenuBlock>;
|
|
48
|
+
table?: BlockDefaultsFor<TableBlock>;
|
|
49
|
+
countdown?: BlockDefaultsFor<CountdownBlock>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
declare type BlockDefaultsFor<T> = Partial<Omit<T, "id" | "type">>;
|
|
53
|
+
|
|
54
|
+
declare interface BlockStyles {
|
|
55
|
+
padding: SpacingValue;
|
|
56
|
+
backgroundColor?: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
declare interface BlockVisibility {
|
|
60
|
+
desktop: boolean;
|
|
61
|
+
mobile: boolean;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
declare type BorderCorner = "topLeft" | "topRight" | "bottomRight" | "bottomLeft";
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* A corner radius in px: one number for all four corners, or a radius per
|
|
68
|
+
* corner.
|
|
69
|
+
*/
|
|
70
|
+
declare type BorderRadiusValue = number | CornerRadius;
|
|
71
|
+
|
|
72
|
+
/** One side of a border. A width of `0` leaves that side undrawn. */
|
|
73
|
+
declare interface BorderSideValue {
|
|
74
|
+
/** Width in px. `0` means no border on this side. */
|
|
75
|
+
width: number;
|
|
76
|
+
style: BorderStyle;
|
|
77
|
+
color: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
declare type BorderStyle = "solid" | "dashed" | "dotted";
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* A border described per side, like `SpacingValue`. Only elements MJML can
|
|
84
|
+
* border natively carry one: sections, images and buttons.
|
|
85
|
+
*/
|
|
86
|
+
declare interface BorderValue {
|
|
87
|
+
top: BorderSideValue;
|
|
88
|
+
right: BorderSideValue;
|
|
89
|
+
bottom: BorderSideValue;
|
|
90
|
+
left: BorderSideValue;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
declare interface ButtonBlock extends BaseBlock {
|
|
94
|
+
type: "button";
|
|
95
|
+
text: string;
|
|
96
|
+
url: string;
|
|
97
|
+
openInNewTab?: boolean;
|
|
98
|
+
backgroundColor: string;
|
|
99
|
+
textColor: string;
|
|
100
|
+
/** Corner radius in px — one number, or a radius per corner. */
|
|
101
|
+
borderRadius: BorderRadiusValue;
|
|
100
102
|
/**
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
* Always `true` for a store that accepts any anchor; Cloud is what supplies a
|
|
114
|
-
* real answer.
|
|
115
|
-
*/
|
|
116
|
-
isBlockSaved(blockId: string): boolean;
|
|
117
|
-
/**
|
|
118
|
-
* Whether the feature is usable right now. Reactive for the same reason as
|
|
119
|
-
* `savedBlocks.isAvailable`, plus one of its own: with no `user` there is
|
|
120
|
-
* nobody to attribute a comment to, so the feature reports itself unavailable
|
|
121
|
-
* rather than writing an anonymous one.
|
|
122
|
-
*/
|
|
123
|
-
isAvailable: ComputedRef<boolean>;
|
|
124
|
-
/** Open threads — the count badge on the header trigger. */
|
|
125
|
-
unresolvedCount: ComputedRef<number>;
|
|
126
|
-
/**
|
|
127
|
-
* Which mutations the provider supplied — `false` instead of a function
|
|
128
|
-
* withholds one. Shared UI hides the corresponding affordance: with all four
|
|
129
|
-
* false the review is read-only (threads readable, jump-to-block working, no
|
|
130
|
-
* way to add, edit, delete or resolve).
|
|
131
|
-
*/
|
|
132
|
-
canCreate: ComputedRef<boolean>;
|
|
133
|
-
canUpdate: ComputedRef<boolean>;
|
|
134
|
-
canDelete: ComputedRef<boolean>;
|
|
135
|
-
canResolve: ComputedRef<boolean>;
|
|
136
|
-
};
|
|
103
|
+
* Border around the button itself, per side. Omitted = no border; a side
|
|
104
|
+
* with width 0 is not drawn. For an outline ("ghost") button, set
|
|
105
|
+
* `backgroundColor` to the keyword `"transparent"` (the background control
|
|
106
|
+
* stores it) and set `textColor` too — a new button is `#333333` with white
|
|
107
|
+
* text. An empty fill exports as `transparent`, because MJML paints
|
|
108
|
+
* `#414141` when the attribute is omitted.
|
|
109
|
+
*/
|
|
110
|
+
border?: BorderValue;
|
|
111
|
+
fontSize: number;
|
|
112
|
+
buttonPadding: SpacingValue;
|
|
113
|
+
fontFamily?: string;
|
|
114
|
+
width?: number | "full";
|
|
137
115
|
/**
|
|
138
|
-
*
|
|
139
|
-
* `
|
|
140
|
-
*/
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
* UI must gate on this, or it will render controls that do nothing.
|
|
167
|
-
*/
|
|
168
|
-
isAvailable: ComputedRef<boolean>;
|
|
169
|
-
/**
|
|
170
|
-
* Which mutations the provider supplied — a provider may pass `false`
|
|
171
|
-
* instead of a function to withhold one. Shared UI hides the corresponding
|
|
172
|
-
* affordance: with `canCreate` false there is no bookmark action and so no
|
|
173
|
-
* pick session, leaving a browse-and-insert-only library.
|
|
174
|
-
*
|
|
175
|
-
* Separate from {@link isAvailable}, which answers whether the feature
|
|
176
|
-
* exists at all.
|
|
177
|
-
*/
|
|
178
|
-
canCreate: ComputedRef<boolean>;
|
|
179
|
-
canUpdate: ComputedRef<boolean>;
|
|
180
|
-
canDelete: ComputedRef<boolean>;
|
|
181
|
-
};
|
|
116
|
+
* Placement of the button within its column. No visible effect when `width`
|
|
117
|
+
* is `"full"`, since the button then spans the column.
|
|
118
|
+
*/
|
|
119
|
+
align: "left" | "center" | "right";
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
declare interface CollaborationConfig {
|
|
123
|
+
enabled: boolean;
|
|
124
|
+
onCollaboratorJoined?: (collaborator: Collaborator) => void;
|
|
125
|
+
onCollaboratorLeft?: (collaborator: Collaborator) => void;
|
|
126
|
+
onBlockLocked?: (event: {
|
|
127
|
+
blockId: string;
|
|
128
|
+
collaborator: Collaborator;
|
|
129
|
+
}) => void;
|
|
130
|
+
onBlockUnlocked?: (event: {
|
|
131
|
+
blockId: string;
|
|
132
|
+
collaborator: Collaborator;
|
|
133
|
+
}) => void;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
declare interface Collaborator {
|
|
137
|
+
id: string;
|
|
138
|
+
name: string;
|
|
139
|
+
color: string;
|
|
140
|
+
selectedBlockId: string | null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export declare interface ColorsConfig {
|
|
182
144
|
/**
|
|
183
|
-
*
|
|
184
|
-
* `
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
*/
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Persist now. Fire-and-forget: the outcome lands in {@link status}. A no-op
|
|
192
|
-
* while a save is running, without a loaded template, or when the provider
|
|
193
|
-
* withheld `save`.
|
|
194
|
-
*/
|
|
195
|
-
save(): void;
|
|
196
|
-
/**
|
|
197
|
-
* Commit an inline rename and persist it. Empty names and no-change commits
|
|
198
|
-
* are ignored.
|
|
199
|
-
*/
|
|
200
|
-
rename(name: string): void;
|
|
201
|
-
/** The loaded template's name, `undefined` when unnamed or not loaded. */
|
|
202
|
-
name: ComputedRef<string | undefined>;
|
|
203
|
-
/**
|
|
204
|
-
* When the stored template was last written, for the header's relative
|
|
205
|
-
* label. `updatedAt` when the store supplies it, otherwise `createdAt`;
|
|
206
|
-
* `null` when it supplies neither, which renders no line at all. `kind` is
|
|
207
|
-
* what picks the "Updated"/"Created" wording.
|
|
208
|
-
*/
|
|
209
|
-
timestamp: ComputedRef<{
|
|
210
|
-
iso: string;
|
|
211
|
-
kind: "updatedAt" | "createdAt";
|
|
212
|
-
} | null>;
|
|
213
|
-
/** Whether `create()` or `load()` has resolved — nothing to save until then. */
|
|
214
|
-
hasTemplate: ComputedRef<boolean>;
|
|
215
|
-
isSaving: ComputedRef<boolean>;
|
|
216
|
-
/** Drives the three-state status indicator. */
|
|
217
|
-
status: Ref<"idle" | "saved" | "error">;
|
|
218
|
-
errorMessage: Ref<string>;
|
|
219
|
-
/**
|
|
220
|
-
* Which mutations the provider supplied — `false` instead of a function
|
|
221
|
-
* withholds one. With `canSave` false the save button *and* the status
|
|
222
|
-
* indicator disappear, and the name becomes read-only: there is nowhere for
|
|
223
|
-
* a change to go.
|
|
224
|
-
*/
|
|
225
|
-
canCreate: ComputedRef<boolean>;
|
|
226
|
-
canSave: ComputedRef<boolean>;
|
|
227
|
-
/**
|
|
228
|
-
* Whether the feature is usable right now. Reactive for the same reason as
|
|
229
|
-
* `savedBlocks.isAvailable`.
|
|
230
|
-
*/
|
|
231
|
-
isAvailable: ComputedRef<boolean>;
|
|
232
|
-
};
|
|
145
|
+
* Preset colors rendered as a clickable grid inside every color picker
|
|
146
|
+
* popover. Each entry is a hex string (`'#0b5cff'`); clicking one applies it,
|
|
147
|
+
* and the preset matching the current value is marked selected (matched
|
|
148
|
+
* case-insensitively). The grid supplements the wheel and hex input unless
|
|
149
|
+
* `allowCustom` is `false`.
|
|
150
|
+
*/
|
|
151
|
+
presets?: string[];
|
|
233
152
|
/**
|
|
234
|
-
*
|
|
235
|
-
*
|
|
236
|
-
*
|
|
237
|
-
*
|
|
238
|
-
*
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
153
|
+
* Allow free-form color entry — the wheel and hex input — alongside the
|
|
154
|
+
* presets. Defaults to `true`.
|
|
155
|
+
*
|
|
156
|
+
* Set to `false` together with `presets` to lock authors to the preset
|
|
157
|
+
* palette: the popover then offers only the preset grid, with no wheel or hex
|
|
158
|
+
* field (a white-label / brand-kit constraint). Ignored with a warning when
|
|
159
|
+
* no `presets` are configured — hiding the only inputs would leave the picker
|
|
160
|
+
* with no way to choose a color.
|
|
161
|
+
*/
|
|
162
|
+
allowCustom?: boolean;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
declare type ColumnLayout = "1" | "2" | "3" | "2-1" | "1-2";
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* One comment — a thread root, or a reply to one.
|
|
169
|
+
*
|
|
170
|
+
* Threads are **one level deep**: a root carries {@link replies}, and a reply
|
|
171
|
+
* never does. That is what the editor renders, and flattening a deeper tree is
|
|
172
|
+
* your store's problem rather than the editor's.
|
|
173
|
+
*/
|
|
174
|
+
declare interface Comment_2 {
|
|
175
|
+
/** Store-assigned. The editor never generates one. */
|
|
176
|
+
id: string;
|
|
177
|
+
body: string;
|
|
178
|
+
author: CommentAuthor;
|
|
179
|
+
/** ISO 8601. */
|
|
180
|
+
createdAt: string;
|
|
260
181
|
/**
|
|
261
|
-
*
|
|
262
|
-
*
|
|
263
|
-
*
|
|
264
|
-
*/
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
182
|
+
* ISO 8601, present only when the body has been edited since. Drives the
|
|
183
|
+
* "(edited)" marker, so a store that stamps it on creation makes every comment
|
|
184
|
+
* look edited.
|
|
185
|
+
*/
|
|
186
|
+
updatedAt?: string;
|
|
187
|
+
/**
|
|
188
|
+
* The block this comment is anchored to, or `null` for a comment about the
|
|
189
|
+
* template as a whole.
|
|
190
|
+
*/
|
|
191
|
+
blockId?: string | null;
|
|
192
|
+
/** The thread root this is a reply to, or `null` for a root. */
|
|
193
|
+
parentId?: string | null;
|
|
194
|
+
/** ISO 8601 when resolved; `null` or absent while the thread is open. */
|
|
195
|
+
resolvedAt?: string | null;
|
|
196
|
+
/** Who resolved it. Absent when unresolved, or when your store doesn't track it. */
|
|
197
|
+
resolvedBy?: CommentAuthor | null;
|
|
198
|
+
/** Replies to this root, oldest first. Absent or empty on a reply. */
|
|
199
|
+
replies?: Comment_2[];
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Who wrote a comment, or resolved one.
|
|
204
|
+
*
|
|
205
|
+
* The same shape as {@link EditorUser}, and deliberately so — a comment's author
|
|
206
|
+
* is whoever was editing. It is a separate name only because a store returns
|
|
207
|
+
* authors it did not get from this session.
|
|
208
|
+
*/
|
|
209
|
+
declare interface CommentAuthor {
|
|
210
|
+
id: string;
|
|
211
|
+
name: string;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* One remote change, as reported by {@link CommentsProvider.subscribe}.
|
|
216
|
+
*
|
|
217
|
+
* A discriminated union rather than a `{ type, comment }` pair, because a delete
|
|
218
|
+
* has no comment to carry — only the id, plus the parent so the editor knows
|
|
219
|
+
* which list to remove it from without looking it up first.
|
|
220
|
+
*/
|
|
221
|
+
declare type CommentChange = {
|
|
222
|
+
type: "created";
|
|
223
|
+
comment: Comment_2;
|
|
224
|
+
} | {
|
|
225
|
+
type: "updated";
|
|
226
|
+
comment: Comment_2;
|
|
227
|
+
} | {
|
|
228
|
+
type: "deleted";
|
|
229
|
+
commentId: string;
|
|
230
|
+
parentId?: string | null;
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Where a comment change came from.
|
|
235
|
+
*
|
|
236
|
+
* `local` — the mutation ran through your own `create` / `update` / `delete` /
|
|
237
|
+
* `setResolved`, called by this editor. `remote` — it arrived through
|
|
238
|
+
* {@link CommentsProvider.subscribe} and this editor never called a mutation
|
|
239
|
+
* method: someone else, in another browser.
|
|
240
|
+
*
|
|
241
|
+
* A "new comments" badge counts `remote` only; counting `local` too makes a
|
|
242
|
+
* user's own comment increment their own unread count.
|
|
243
|
+
*/
|
|
244
|
+
export declare interface CommentEventMeta {
|
|
245
|
+
origin: "local" | "remote";
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** What {@link CommentsProvider.create} is asked to store. */
|
|
249
|
+
declare interface CommentInput {
|
|
250
|
+
body: string;
|
|
251
|
+
/** Omitted for a template-level comment. */
|
|
252
|
+
blockId?: string;
|
|
253
|
+
/** Omitted for a thread root. */
|
|
254
|
+
parentId?: string;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Partial patch for {@link CommentsProvider.update}. A patch rather than a bare
|
|
259
|
+
* body, matching `TemplatePatch` and `SavedBlockPatch` — retrofitting one later
|
|
260
|
+
* would break every implementation, and only the shape is being paid for now.
|
|
261
|
+
*/
|
|
262
|
+
declare type CommentPatch = Partial<{
|
|
263
|
+
body: string;
|
|
264
|
+
}>;
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Reserved filter object for {@link CommentsProvider.list}.
|
|
268
|
+
*
|
|
269
|
+
* Empty today. The editor always calls `list` bare and narrows in memory
|
|
270
|
+
* (unresolved / all / this block), so the provider decides *what is visible*
|
|
271
|
+
* and the editor decides how it is filtered within that.
|
|
272
|
+
*
|
|
273
|
+
* **Not a pagination hook, and comments deliberately has none.** `useComments`
|
|
274
|
+
* derives `unresolvedCount` (the header badge) and `commentCountByBlock` (the
|
|
275
|
+
* per-block canvas indicators) over the *whole* loaded list. A partial page
|
|
276
|
+
* would make both under-report silently — wrong rather than slow. Correct
|
|
277
|
+
* paging would mean moving counts and filtering server-side, which is a
|
|
278
|
+
* redesign, not a `loadMore()`. A long-lived template caps its own growth by
|
|
279
|
+
* having `list()` stop returning resolved threads past some age; the panel
|
|
280
|
+
* hides those by default anyway. Contrast {@link VersionHistoryListParams},
|
|
281
|
+
* which does page: its list is a flat menu with nothing aggregating over it.
|
|
282
|
+
*/
|
|
283
|
+
declare interface CommentsListParams {}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Outward notifications about the review conversation. Every member is optional.
|
|
287
|
+
*
|
|
288
|
+
* Separate from the storage methods so `initCloud()` can accept this half alone:
|
|
289
|
+
* a comment is keyed to a template id Cloud issued and its author is signed by
|
|
290
|
+
* the auth token, so Cloud owns the conversation. {@link CommentsProvider}
|
|
291
|
+
* extends this, so one object satisfies both entry points.
|
|
292
|
+
*
|
|
293
|
+
* Each fires once per change the editor applied, after the list reflects it. A
|
|
294
|
+
* handler that throws is reported through `onError` and never fails the write.
|
|
295
|
+
*/
|
|
296
|
+
export declare interface CommentsOptions {
|
|
297
|
+
onCreated?: (comment: Comment_2, meta: CommentEventMeta) => void;
|
|
298
|
+
onUpdated?: (comment: Comment_2, meta: CommentEventMeta) => void;
|
|
299
|
+
onDeleted?: (comment: Comment_2, meta: CommentEventMeta) => void;
|
|
300
|
+
/**
|
|
301
|
+
* Which of `onResolved` / `onUnresolved` fires is decided by the **stored**
|
|
302
|
+
* result's `resolvedAt`, never by the state that was requested — a store may
|
|
303
|
+
* refuse to reopen, and the handler should hear what happened.
|
|
304
|
+
*/
|
|
305
|
+
onResolved?: (comment: Comment_2, meta: CommentEventMeta) => void;
|
|
306
|
+
onUnresolved?: (comment: Comment_2, meta: CommentEventMeta) => void;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Storage contract for **comments** — the review conversation on a template.
|
|
311
|
+
*
|
|
312
|
+
* Pass an implementation as `comments` to `init()`. With no provider the panel,
|
|
313
|
+
* its trigger and the per-block indicators do not render and none of that UI is
|
|
314
|
+
* downloaded.
|
|
315
|
+
*
|
|
316
|
+
* Comments also need to know **who is commenting**, which is not part of this
|
|
317
|
+
* contract: it is the top-level `user` config key, because collaboration presence
|
|
318
|
+
* will want the same answer and a provider-local copy would be the first thing to
|
|
319
|
+
* drift. With no `user` the feature reports itself unavailable — an
|
|
320
|
+
* unattributable comment is worse than no comment feature.
|
|
321
|
+
*
|
|
322
|
+
* **Each mutation can be turned off by passing `false` instead of a function**,
|
|
323
|
+
* mirroring `SavedBlocksProvider`, `TemplatesProvider` and
|
|
324
|
+
* `VersionHistoryProvider`. They are required rather than optional precisely so
|
|
325
|
+
* that disabling is a decision you state, never something you get by forgetting a
|
|
326
|
+
* method. All four `false` yields a read-only review: existing threads are
|
|
327
|
+
* browsable and jump-to-block still works, with no way to add, edit, delete or
|
|
328
|
+
* resolve.
|
|
329
|
+
*
|
|
330
|
+
* ```ts
|
|
331
|
+
* const provider: CommentsProvider = {
|
|
332
|
+
* list: (templateId) =>
|
|
333
|
+
* fetch(`/api/templates/${templateId}/comments`).then((r) => r.json()),
|
|
334
|
+
* create: (templateId, input) =>
|
|
335
|
+
* fetch(`/api/templates/${templateId}/comments`, {
|
|
336
|
+
* method: "POST",
|
|
337
|
+
* headers: { "Content-Type": "application/json" },
|
|
338
|
+
* body: JSON.stringify(input),
|
|
339
|
+
* }).then((r) => r.json()),
|
|
340
|
+
* update: (templateId, commentId, patch) =>
|
|
341
|
+
* fetch(`/api/templates/${templateId}/comments/${commentId}`, {
|
|
342
|
+
* method: "PATCH",
|
|
343
|
+
* headers: { "Content-Type": "application/json" },
|
|
344
|
+
* body: JSON.stringify(patch),
|
|
345
|
+
* }).then((r) => r.json()),
|
|
346
|
+
* delete: async (templateId, commentId) => {
|
|
347
|
+
* await fetch(`/api/templates/${templateId}/comments/${commentId}`, {
|
|
348
|
+
* method: "DELETE",
|
|
349
|
+
* });
|
|
350
|
+
* },
|
|
351
|
+
* setResolved: (templateId, commentId, resolved) =>
|
|
352
|
+
* fetch(`/api/templates/${templateId}/comments/${commentId}/resolve`, {
|
|
353
|
+
* method: "POST",
|
|
354
|
+
* headers: { "Content-Type": "application/json" },
|
|
355
|
+
* body: JSON.stringify({ resolved }),
|
|
356
|
+
* }).then((r) => r.json()),
|
|
357
|
+
* };
|
|
358
|
+
* ```
|
|
359
|
+
*/
|
|
360
|
+
declare interface CommentsProvider extends CommentsOptions {
|
|
361
|
+
/**
|
|
362
|
+
* The thread roots to show, each with its `replies`. The editor renders this
|
|
363
|
+
* order verbatim and never re-sorts — ordering is your store's call.
|
|
364
|
+
*
|
|
365
|
+
* The one method that cannot be disabled: without it there is nothing to show.
|
|
366
|
+
*/
|
|
367
|
+
list(templateId: string, params?: CommentsListParams): Promise<Comment_2[]>;
|
|
368
|
+
/**
|
|
369
|
+
* Store a new comment or reply and return it with its store-assigned `id`, or
|
|
370
|
+
* `false` to make the review read-only.
|
|
371
|
+
*/
|
|
372
|
+
create: false | ((templateId: string, input: CommentInput) => Promise<Comment_2>);
|
|
373
|
+
/**
|
|
374
|
+
* Apply a partial update and return the stored result, or `false` to disable
|
|
375
|
+
* editing — the pencil action then does not render on any comment.
|
|
376
|
+
*/
|
|
377
|
+
update: false | ((templateId: string, commentId: string, patch: CommentPatch) => Promise<Comment_2>);
|
|
378
|
+
/**
|
|
379
|
+
* Remove a comment (and, for a root, its replies), or `false` to disable
|
|
380
|
+
* deletion. Resolves to nothing: there is no post-delete state to report.
|
|
381
|
+
*/
|
|
382
|
+
delete: false | ((templateId: string, commentId: string) => Promise<void>);
|
|
383
|
+
/**
|
|
384
|
+
* Mark a thread resolved or reopened and return the stored result, or `false`
|
|
385
|
+
* to disable it.
|
|
386
|
+
*
|
|
387
|
+
* Takes the **target state** rather than toggling, so the call is idempotent
|
|
388
|
+
* and a store that receives two clicks in flight cannot end up inverted.
|
|
389
|
+
*/
|
|
390
|
+
setResolved: false | ((templateId: string, commentId: string, resolved: boolean) => Promise<Comment_2>);
|
|
391
|
+
/**
|
|
392
|
+
* **Optional.** Push remote changes into the open panel, so a colleague's
|
|
393
|
+
* comment appears without a reload.
|
|
394
|
+
*
|
|
395
|
+
* Realtime is separable from comments rather than a prerequisite for them:
|
|
396
|
+
* without this the feature works exactly as it does with it, you just don't see
|
|
397
|
+
* someone else's comment until the list is next read. Returns an unsubscribe
|
|
398
|
+
* function; the editor calls it when the template changes and on teardown.
|
|
399
|
+
*
|
|
400
|
+
* ```ts
|
|
401
|
+
* subscribe: (templateId, onChange) => {
|
|
402
|
+
* const source = new EventSource(`/api/templates/${templateId}/comments/stream`);
|
|
403
|
+
* source.onmessage = (e) => onChange(JSON.parse(e.data) as CommentChange);
|
|
404
|
+
* return () => source.close();
|
|
405
|
+
* }
|
|
406
|
+
* ```
|
|
407
|
+
*
|
|
408
|
+
* Your own writes may echo back through here. Neither the list nor
|
|
409
|
+
* {@link CommentsOptions} needs de-duplication on your side: a `created` or
|
|
410
|
+
* `updated` for a comment already in the list replaces the existing entry by
|
|
411
|
+
* id, and the matching handler fires only when that replacement actually
|
|
412
|
+
* changes the stored comment — a same-content echo replaces silently, with
|
|
413
|
+
* nothing to notify. The comparison is structural equality over the stored
|
|
414
|
+
* shape (`sameComment` decides both), so an echo whose transport frame
|
|
415
|
+
* serializes identical content in a different key order is not recognized
|
|
416
|
+
* as the same comment.
|
|
417
|
+
*/
|
|
418
|
+
subscribe?: (templateId: string, onChange: (change: CommentChange) => void) => () => void;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Writing direction of the delivered email — the canvas `dir` and the
|
|
423
|
+
* rendered `<mjml dir>`. Independent of the editor chrome language
|
|
424
|
+
* (`init({ locale })`).
|
|
425
|
+
*/
|
|
426
|
+
declare type ContentDirection = "ltr" | "rtl";
|
|
427
|
+
|
|
428
|
+
/** A radius per corner, in px. */
|
|
429
|
+
declare type CornerRadius = Record<BorderCorner, number>;
|
|
430
|
+
|
|
431
|
+
declare interface CountdownBlock extends BaseBlock {
|
|
432
|
+
type: "countdown";
|
|
433
|
+
targetDate: string;
|
|
434
|
+
timezone: string;
|
|
435
|
+
showDays: boolean;
|
|
436
|
+
showHours: boolean;
|
|
437
|
+
showMinutes: boolean;
|
|
438
|
+
showSeconds: boolean;
|
|
439
|
+
separator: ":" | "-" | " ";
|
|
440
|
+
digitFontSize: number;
|
|
441
|
+
digitColor: string;
|
|
442
|
+
labelColor: string;
|
|
443
|
+
labelFontSize: number;
|
|
444
|
+
backgroundColor: string;
|
|
445
|
+
fontFamily?: string;
|
|
446
|
+
labelDays: string;
|
|
447
|
+
labelHours: string;
|
|
448
|
+
labelMinutes: string;
|
|
449
|
+
labelSeconds: string;
|
|
450
|
+
expiredMessage: string;
|
|
451
|
+
expiredImageUrl: string;
|
|
452
|
+
hideOnExpiry: boolean;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
export declare function createDefaultTemplateContent(defaultFontFamily?: string, templateDefaults?: TemplateDefaults): TemplateContent;
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Browser-local {@link MediaProvider} backed by `localStorage`.
|
|
459
|
+
*
|
|
460
|
+
* Opt-in: pass it explicitly as `init({ media: createLocalStorageMediaProvider() })`.
|
|
461
|
+
* The editor never falls back to it, so consumers without a provider keep
|
|
462
|
+
* image fields URL-only.
|
|
463
|
+
*
|
|
464
|
+
* `create` stores the file as a data URL. That is a demo-sized persistence
|
|
465
|
+
* model — `localStorage` quotas are typically ~5 MB, and a few large
|
|
466
|
+
* images will fill it. Back a real gallery with your own API.
|
|
467
|
+
*
|
|
468
|
+
* Folders, replace, import, usage, frequently-used and quota are `false`:
|
|
469
|
+
* this adapter is a flat list of assets.
|
|
470
|
+
*/
|
|
471
|
+
export declare function createLocalStorageMediaProvider(options?: LocalStorageMediaProviderOptions): MediaProvider_2;
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Browser-local {@link SavedBlocksProvider} backed by `localStorage`.
|
|
475
|
+
*
|
|
476
|
+
* Opt-in: pass it explicitly as `init({ savedBlocks: createLocalStorageSavedBlocksProvider() })`.
|
|
477
|
+
* The editor never falls back to it, so consumers without a provider keep the
|
|
478
|
+
* feature — and its UI — entirely off.
|
|
479
|
+
*
|
|
480
|
+
* Intended for demos, prototypes, and single-device use. Entries live in one
|
|
481
|
+
* browser profile only: they don't sync across devices or users, and clearing
|
|
482
|
+
* site data removes them. Back saved blocks with your own API for anything
|
|
483
|
+
* that needs to outlive a browser profile.
|
|
484
|
+
*/
|
|
485
|
+
export declare function createLocalStorageSavedBlocksProvider(options?: LocalStorageSavedBlocksOptions): SavedBlocksProvider;
|
|
486
|
+
|
|
487
|
+
export declare function createParagraphBlock(partial?: Partial<ParagraphBlock>): ParagraphBlock;
|
|
488
|
+
|
|
489
|
+
/** Layout-only. Use this to build a layout; createBlock("slot") throws. */
|
|
490
|
+
export declare function createSlotBlock(): SlotBlock;
|
|
491
|
+
|
|
492
|
+
/** Layout-only. Use this to build a layout; createBlock("wrapper") throws. */
|
|
493
|
+
export declare function createWrapperBlock(partial?: Partial<WrapperBlock>): WrapperBlock;
|
|
494
|
+
|
|
495
|
+
declare interface CustomBlock extends BaseBlock {
|
|
496
|
+
type: "custom";
|
|
497
|
+
customType: string;
|
|
498
|
+
fieldValues: Record<string, unknown>;
|
|
499
|
+
renderedHtml?: string;
|
|
500
|
+
dataSourceFetched?: boolean;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
declare interface CustomBlockBooleanField extends CustomBlockFieldBase {
|
|
504
|
+
type: "boolean";
|
|
505
|
+
default?: boolean;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* A color-picker field. Beyond the shared field props it accepts the same flat
|
|
510
|
+
* `presets` / `allowCustom` pair as the editor-wide `colors` config (their
|
|
511
|
+
* shapes are derived from `ColorsConfig`, so the two can't drift), scoping this
|
|
512
|
+
* one field to a color *role* — e.g. a button-color field offering only the
|
|
513
|
+
* brand's accent/ink pair while other color fields inherit the global palette.
|
|
514
|
+
*
|
|
515
|
+
* **Narrowing only — of the author's freedom, not the palette.** A field can
|
|
516
|
+
* restrict what authors may do (lock this field, or give it its own `presets`),
|
|
517
|
+
* but never unlock what the editor locked. Its `presets` *replace* the
|
|
518
|
+
* editor-wide grid rather than intersecting it, so a locked field may offer
|
|
519
|
+
* colors outside `colors.presets`:
|
|
520
|
+
*
|
|
521
|
+
* - `presets` — entries are validated exactly like editor-level presets
|
|
522
|
+
* (`#rgb` / `#rrggbb` hex; invalid ones are skipped with a console warning
|
|
523
|
+
* naming the block and field). A non-empty valid list replaces the editor's
|
|
524
|
+
* palette for this field; leaving it unset, passing `[]`, or passing only
|
|
525
|
+
* invalid entries all inherit the editor's palette.
|
|
526
|
+
* - `allowCustom` — `false` locks this field to its palette even while the rest
|
|
527
|
+
* of the editor allows free-form entry. `true` cannot unlock a field when the
|
|
528
|
+
* editor-wide `colors.allowCustom` is `false`; it is ignored with a warning.
|
|
529
|
+
*
|
|
530
|
+
* @see https://docs.templatical.com/guide/custom-blocks
|
|
531
|
+
*/
|
|
532
|
+
declare interface CustomBlockColorField extends CustomBlockFieldBase, Pick<ColorsConfig, "presets" | "allowCustom"> {
|
|
533
|
+
type: "color";
|
|
534
|
+
default?: string;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
export declare interface CustomBlockDefinition {
|
|
538
|
+
type: string;
|
|
539
|
+
name: string;
|
|
540
|
+
icon?: string;
|
|
541
|
+
description?: string;
|
|
542
|
+
fields: CustomBlockField[];
|
|
543
|
+
template: string;
|
|
544
|
+
dataSource?: DataSourceConfig;
|
|
545
|
+
/**
|
|
546
|
+
* Default block styles applied when a new instance of this custom block is
|
|
547
|
+
* created. Deep-merged over the built-in defaults — only specify the fields
|
|
548
|
+
* you want to override. Controls both the editor canvas wrapper and the
|
|
549
|
+
* rendered MJML/email output.
|
|
550
|
+
*
|
|
551
|
+
* @example
|
|
552
|
+
* defaultStyles: {
|
|
553
|
+
* padding: { top: 0, right: 0, bottom: 0, left: 0 },
|
|
554
|
+
* }
|
|
555
|
+
*/
|
|
556
|
+
defaultStyles?: Partial<BlockStyles>;
|
|
557
|
+
/**
|
|
558
|
+
* Optional CSS rules attached to this custom block definition. Emitted once
|
|
559
|
+
* (deduped across instances) into `<mj-head><mj-style>…</mj-style></mj-head>`
|
|
560
|
+
* in the rendered MJML, and adopted into the editor canvas (shadow root or
|
|
561
|
+
* light-DOM mount) so authored responsive/hover/font behavior previews
|
|
562
|
+
* inside the editor.
|
|
563
|
+
*
|
|
564
|
+
* Use this for media queries, hover states, or any CSS that should apply
|
|
565
|
+
* once per definition rather than per block instance. Class names are not
|
|
566
|
+
* scoped by the SDK — namespace them yourself (e.g. `.tplc-<type>-<el>`) to
|
|
567
|
+
* avoid collisions with other definitions or built-in editor styles.
|
|
568
|
+
*
|
|
569
|
+
* @example
|
|
570
|
+
* stylesheet: `
|
|
571
|
+
* @media (max-width: 480px) {
|
|
572
|
+
* .tplc-image-text-cell { display: block !important; width: 100% !important; }
|
|
573
|
+
* }
|
|
574
|
+
* `
|
|
575
|
+
*/
|
|
576
|
+
stylesheet?: string;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
declare type CustomBlockField = CustomBlockTextField | CustomBlockTextareaField | CustomBlockImageField | CustomBlockColorField | CustomBlockNumberField | CustomBlockSelectField | CustomBlockBooleanField | CustomBlockRepeatableField;
|
|
580
|
+
|
|
581
|
+
declare interface CustomBlockFieldBase {
|
|
582
|
+
key: string;
|
|
583
|
+
label: string;
|
|
584
|
+
required?: boolean;
|
|
585
|
+
placeholder?: string;
|
|
586
|
+
readOnly?: boolean;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
declare interface CustomBlockImageField extends CustomBlockFieldBase {
|
|
590
|
+
type: "image";
|
|
591
|
+
default?: string;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
declare interface CustomBlockNumberField extends CustomBlockFieldBase {
|
|
595
|
+
type: "number";
|
|
596
|
+
default?: number;
|
|
597
|
+
min?: number;
|
|
598
|
+
max?: number;
|
|
599
|
+
step?: number;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
declare interface CustomBlockRepeatableField extends CustomBlockFieldBase {
|
|
603
|
+
type: "repeatable";
|
|
604
|
+
fields: Exclude<CustomBlockField, CustomBlockRepeatableField>[];
|
|
605
|
+
default?: Record<string, unknown>[];
|
|
606
|
+
minItems?: number;
|
|
607
|
+
maxItems?: number;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
declare interface CustomBlockSelectField extends CustomBlockFieldBase {
|
|
611
|
+
type: "select";
|
|
612
|
+
options: SelectOption[];
|
|
613
|
+
default?: string;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
declare interface CustomBlockTextareaField extends CustomBlockFieldBase {
|
|
617
|
+
type: "textarea";
|
|
618
|
+
default?: string;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
declare interface CustomBlockTextField extends CustomBlockFieldBase {
|
|
622
|
+
type: "text";
|
|
623
|
+
default?: string;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
export declare interface CustomFont {
|
|
627
|
+
name: string;
|
|
628
|
+
url: string;
|
|
629
|
+
fallback?: string;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
declare interface DataSourceConfig {
|
|
633
|
+
label: string;
|
|
634
|
+
onFetch: (context: DataSourceFetchContext) => Promise<Record<string, unknown> | null>;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
declare interface DataSourceFetchContext {
|
|
638
|
+
fieldValues: Record<string, unknown>;
|
|
639
|
+
blockId: string;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
declare interface DisplayCondition {
|
|
643
|
+
label: string;
|
|
644
|
+
before: string;
|
|
645
|
+
after: string;
|
|
646
|
+
group?: string;
|
|
647
|
+
description?: string;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
export declare interface DisplayConditionsConfig {
|
|
651
|
+
conditions: DisplayCondition[];
|
|
652
|
+
allowCustom?: boolean;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
declare interface DividerBlock extends BaseBlock {
|
|
656
|
+
type: "divider";
|
|
657
|
+
lineStyle: "solid" | "dashed" | "dotted";
|
|
658
|
+
color: string;
|
|
659
|
+
thickness: number;
|
|
660
|
+
/**
|
|
661
|
+
* `"full"` spans the column and a number is pixels. A percentage is a share
|
|
662
|
+
* of the column: MJML renders it natively, and it shrinks with the column on
|
|
663
|
+
* a phone where a pixel width would overflow.
|
|
664
|
+
*/
|
|
665
|
+
width: number | "full" | DividerPercentWidth;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
/**
|
|
669
|
+
* A share of the column, from `"0%"` to `"100%"`.
|
|
670
|
+
* @pattern ^(?:100(?:\.0+)?|\d{1,2}(?:\.\d+)?)%$
|
|
671
|
+
*/
|
|
672
|
+
declare type DividerPercentWidth = `${number}%`;
|
|
673
|
+
|
|
674
|
+
/**
|
|
675
|
+
* Who is using the editor right now — `init({ user })`.
|
|
676
|
+
*
|
|
677
|
+
* A **top-level config key, not part of any provider.** Comments are the first
|
|
678
|
+
* feature to need it (the panel decides which comments may be edited or deleted
|
|
679
|
+
* by comparing `id` against each author, and a new comment is attributed to
|
|
680
|
+
* `name`), but collaboration presence will want exactly the same answer. Putting
|
|
681
|
+
* it on the comments provider would guarantee a second, drifting copy the moment
|
|
682
|
+
* the second feature landed.
|
|
683
|
+
*
|
|
684
|
+
* Features that need an identity **report themselves unavailable without one**
|
|
685
|
+
* rather than falling back to an anonymous author. An unattributable comment is
|
|
686
|
+
* worse than no comment feature — the same reasoning that makes an explicitly
|
|
687
|
+
* empty `TestEmailProvider.allowedRecipients` disable the feature instead of
|
|
688
|
+
* degrading to free text.
|
|
689
|
+
*
|
|
690
|
+
* Not a security boundary: this identifies the user to the editor's UI, in the
|
|
691
|
+
* user's own browser. Whatever your provider writes must be attributed
|
|
692
|
+
* server-side, from the session your backend already trusts.
|
|
693
|
+
*/
|
|
694
|
+
declare interface EditorUser {
|
|
695
|
+
/**
|
|
696
|
+
* Stable identifier, compared against `Comment.author.id`. Whatever your
|
|
697
|
+
* backend calls a user — a primary key, a UUID, an email.
|
|
698
|
+
*/
|
|
699
|
+
id: string;
|
|
700
|
+
/** Display name, shown on the comments this session writes. */
|
|
701
|
+
name: string;
|
|
277
702
|
}
|
|
278
703
|
|
|
279
704
|
export declare interface FontOption {
|
|
@@ -282,7 +707,29 @@ export declare interface FontOption {
|
|
|
282
707
|
isCustom?: boolean;
|
|
283
708
|
}
|
|
284
709
|
|
|
285
|
-
export
|
|
710
|
+
export declare interface FontsConfig {
|
|
711
|
+
defaultFallback?: string;
|
|
712
|
+
defaultFont?: string;
|
|
713
|
+
customFonts?: CustomFont[];
|
|
714
|
+
/**
|
|
715
|
+
* Restrict which of the seven built-in fonts the font picker offers.
|
|
716
|
+
*
|
|
717
|
+
* - `true` / omitted — every built-in is offered (the default).
|
|
718
|
+
* - `false` — no built-ins; the picker lists only `customFonts`. Use this to
|
|
719
|
+
* keep authors on an embedded brand kit's typefaces.
|
|
720
|
+
* - `string[]` — an allowlist of built-in names to keep, matched
|
|
721
|
+
* case-insensitively (`['Georgia', 'arial']`). A name that isn't a built-in
|
|
722
|
+
* is logged with a warning and skipped — consistent with how
|
|
723
|
+
* `paletteBlocks` treats an unknown entry — so a typo narrows the list
|
|
724
|
+
* rather than silently offering all seven.
|
|
725
|
+
*
|
|
726
|
+
* Independent of `customFonts`: excluding the built-ins never removes a custom
|
|
727
|
+
* font, and a custom font can still be the `defaultFont` when every built-in
|
|
728
|
+
* is excluded. Filtering the picker never affects rendering — content already
|
|
729
|
+
* using an excluded built-in still resolves to its proper fallback stack.
|
|
730
|
+
*/
|
|
731
|
+
builtIns?: boolean | string[];
|
|
732
|
+
}
|
|
286
733
|
|
|
287
734
|
/**
|
|
288
735
|
* Get the base language code from a locale string.
|
|
@@ -296,6 +743,13 @@ export declare function getSupportedCloudLocales(): string[];
|
|
|
296
743
|
/** List of OSS-supported locales. */
|
|
297
744
|
export declare function getSupportedLocales(): string[];
|
|
298
745
|
|
|
746
|
+
declare type HeadingLevel = 1 | 2 | 3 | 4;
|
|
747
|
+
|
|
748
|
+
declare interface HtmlBlock extends BaseBlock {
|
|
749
|
+
type: "html";
|
|
750
|
+
content: string;
|
|
751
|
+
}
|
|
752
|
+
|
|
299
753
|
/**
|
|
300
754
|
* Consumer-facing shape of the `htmlBlockPreview` editor config option.
|
|
301
755
|
*
|
|
@@ -309,129 +763,1442 @@ export declare type HtmlBlockPreviewConfig = boolean | {
|
|
|
309
763
|
enabled: boolean;
|
|
310
764
|
};
|
|
311
765
|
|
|
766
|
+
declare interface ImageBlock extends BaseBlock {
|
|
767
|
+
type: "image";
|
|
768
|
+
src: string;
|
|
769
|
+
alt: string;
|
|
770
|
+
width: number | "full";
|
|
771
|
+
/**
|
|
772
|
+
* Height in pixels. Absent means the height is derived from the width, so the
|
|
773
|
+
* image keeps its aspect ratio — setting both stretches it, since email
|
|
774
|
+
* clients don't support `object-fit`.
|
|
775
|
+
*/
|
|
776
|
+
height?: number;
|
|
777
|
+
align: "left" | "center" | "right";
|
|
778
|
+
/**
|
|
779
|
+
* Corner radius in px. Omitted/0 = square corners. A radius of at least half
|
|
780
|
+
* the rendered size rounds a square image to a circle, which is how avatar
|
|
781
|
+
* and portrait layouts are built. A radius per corner is also accepted.
|
|
782
|
+
*/
|
|
783
|
+
borderRadius?: BorderRadiusValue;
|
|
784
|
+
/**
|
|
785
|
+
* Border around the image itself, per side. Omitted = no border; a side with
|
|
786
|
+
* width 0 is not drawn.
|
|
787
|
+
*/
|
|
788
|
+
border?: BorderValue;
|
|
789
|
+
linkUrl?: string;
|
|
790
|
+
linkOpenInNewTab?: boolean;
|
|
791
|
+
placeholderUrl?: string;
|
|
792
|
+
decorative?: boolean;
|
|
793
|
+
}
|
|
794
|
+
|
|
312
795
|
export declare function init(config: TemplaticalEditorConfig): Promise<TemplaticalEditor>;
|
|
313
796
|
|
|
314
797
|
/**
|
|
315
|
-
* Mount the editor against Templatical Cloud.
|
|
316
|
-
*
|
|
317
|
-
* **This is `init()` with Cloud's adapters filled in**, and deliberately nothing
|
|
318
|
-
* more. Templates, rendering and version history are providers, so Cloud's entry
|
|
319
|
-
* point is adapter wiring rather than a second editor:
|
|
798
|
+
* Mount the editor against Templatical Cloud.
|
|
799
|
+
*
|
|
800
|
+
* **This is `init()` with Cloud's adapters filled in**, and deliberately nothing
|
|
801
|
+
* more. Templates, rendering and version history are providers, so Cloud's entry
|
|
802
|
+
* point is adapter wiring rather than a second editor:
|
|
803
|
+
*
|
|
804
|
+
* 1. Build the auth manager and complete the handshake.
|
|
805
|
+
* 2. Health-check the API, and fetch the plan config.
|
|
806
|
+
* 3. Build Cloud's adapters over that auth manager.
|
|
807
|
+
* 4. Delegate to `init()`.
|
|
808
|
+
*
|
|
809
|
+
* A failure in steps 1–2 **rejects**, rather than mounting an editor that shows
|
|
810
|
+
* an error overlay. That is the one place the wrapper is genuinely not `init()`:
|
|
811
|
+
* `init()` cannot fail after it mounts, and OSS should not grow the ability. A
|
|
812
|
+
* session that dies *later* — an auth refresh that cannot renew the token — does
|
|
813
|
+
* still surface as an overlay, because by then there is an editor to cover.
|
|
814
|
+
*
|
|
815
|
+
* `templates`, `comments` and `versionHistory` are all keyed to a template
|
|
816
|
+
* id Cloud issued, which also anchors collaboration, AI rewrite, scoring and
|
|
817
|
+
* the server-side export — a store Cloud never issued ids for would degrade
|
|
818
|
+
* all of them silently. Cloud therefore keeps each key's storage, but each
|
|
819
|
+
* key is still accepted, for its configuration and events:
|
|
820
|
+
* `templates.load`/`create`/`save`,
|
|
821
|
+
* `comments.list`/`create`/`update`/`delete`/`setResolved`, and
|
|
822
|
+
* `versionHistory.list`/`get`/`create`/`restore` are ignored with a warning
|
|
823
|
+
* naming them, while `templates`' `autoSave`, `unsavedChangesGuard`,
|
|
824
|
+
* `nameField`, `onSaved`, `onCreated`, `onLoaded`, `comments`' `onCreated`,
|
|
825
|
+
* `onUpdated`, `onDeleted`, `onResolved`, `onUnresolved`, and
|
|
826
|
+
* `versionHistory`'s `onCreated`, `onRestored` are all honoured.
|
|
827
|
+
*
|
|
828
|
+
* `render` is not a key here at all, unlike `init()` — Cloud renders
|
|
829
|
+
* server-side for test email, sends and exports, so a supplied renderer
|
|
830
|
+
* would change only what you preview and export, never what Cloud delivers.
|
|
831
|
+
* `resolvePreview` is the same key with the same type on both entry points,
|
|
832
|
+
* so upgrading an OSS integration is a deletion. `savedBlocks`, `testEmail`
|
|
833
|
+
* and `media` are the same key on both entry points too, but Cloud widens
|
|
834
|
+
* each type to also accept an events-only shape — `boolean |
|
|
835
|
+
* SavedBlocksOptions | SavedBlocksProvider`, `Pick<TestEmailOptions,
|
|
836
|
+
* "onSent" | "defaultRecipient"> | TestEmailProvider`, and
|
|
837
|
+
* `false | MediaOptions | MediaProvider` — so upgrading is still a
|
|
838
|
+
* deletion: drop the key to adopt Cloud's store or sender, or leave it
|
|
839
|
+
* exactly as it is to keep your own.
|
|
840
|
+
*
|
|
841
|
+
* `user` is not a key either: Cloud signs comment writes against the auth token's
|
|
842
|
+
* `user` claim, so it fills `init({ user })` from there rather than letting a
|
|
843
|
+
* browser name someone else.
|
|
844
|
+
*/
|
|
845
|
+
export declare function initCloud(config: TemplaticalCloudEditorConfig): Promise<TemplaticalCloudEditor>;
|
|
846
|
+
|
|
847
|
+
/** Check if a locale has cloud translations (matched by exact locale, then base). */
|
|
848
|
+
export declare function isCloudLocaleSupported(locale: string): boolean;
|
|
849
|
+
|
|
850
|
+
/** Check if a locale has OSS translations (matched by exact locale, then base). */
|
|
851
|
+
export declare function isLocaleSupported(locale: string): boolean;
|
|
852
|
+
|
|
853
|
+
export declare function isSlot(block: Block): block is SlotBlock;
|
|
854
|
+
|
|
855
|
+
export declare function isWrapper(block: Block): block is WrapperBlock;
|
|
856
|
+
|
|
857
|
+
/** True iff the one slot is a direct child of a wrapper (the card). */
|
|
858
|
+
export declare function layoutWrapsSlot(layout: TemplateContent): boolean;
|
|
859
|
+
|
|
860
|
+
/** Options consumed only by the links linter. */
|
|
861
|
+
declare interface LinksLintOptions {
|
|
862
|
+
rules?: RuleOverrides;
|
|
863
|
+
/**
|
|
864
|
+
* Host patterns that should flag as "staging / non-production".
|
|
865
|
+
* Each entry is a glob-style pattern matched against the URL host.
|
|
866
|
+
* `*` matches any run of characters (including `.`), so `*.staging.*`
|
|
867
|
+
* matches `app.staging.example.com`.
|
|
868
|
+
*
|
|
869
|
+
* Default: ['localhost', '127.0.0.1', '0.0.0.0', '*.local',
|
|
870
|
+
* '*.staging.*', '*.dev.*']
|
|
871
|
+
*/
|
|
872
|
+
nonProductionHosts?: string[];
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
declare interface LintOptions {
|
|
876
|
+
/**
|
|
877
|
+
* Fully disable linting. When true, the editor skips lazy-loading the
|
|
878
|
+
* package, hides the sidebar tab, and suppresses inline badges.
|
|
879
|
+
*/
|
|
880
|
+
disabled?: boolean;
|
|
881
|
+
/** Locale for vague-text dictionaries and message text. Falls back to `en`. */
|
|
882
|
+
locale?: string;
|
|
883
|
+
/**
|
|
884
|
+
* Accessibility linter config. Set to `false` to disable the whole
|
|
885
|
+
* `lintAccessibility` linter without enumerating its rules.
|
|
886
|
+
*/
|
|
887
|
+
accessibility?: false | AccessibilityLintOptions;
|
|
888
|
+
/**
|
|
889
|
+
* Structure linter config. Set to `false` to disable the whole
|
|
890
|
+
* `lintStructure` linter without enumerating its rules.
|
|
891
|
+
*/
|
|
892
|
+
structure?: false | StructureLintOptions;
|
|
893
|
+
/**
|
|
894
|
+
* Links linter config. Set to `false` to disable the whole `lintLinks`
|
|
895
|
+
* linter without enumerating its rules.
|
|
896
|
+
*/
|
|
897
|
+
links?: false | LinksLintOptions;
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
declare interface LintThresholds {
|
|
901
|
+
altMaxLength: number;
|
|
902
|
+
minFontSize: number;
|
|
903
|
+
allCapsMinLength: number;
|
|
904
|
+
minTouchTargetPx: number;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
export declare interface LocalStorageMediaProviderOptions {
|
|
908
|
+
/**
|
|
909
|
+
* `localStorage` key holding the serialized array.
|
|
910
|
+
*
|
|
911
|
+
* @default "templatical:media"
|
|
912
|
+
*/
|
|
913
|
+
key?: string;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
export declare interface LocalStorageSavedBlocksOptions {
|
|
917
|
+
/**
|
|
918
|
+
* `localStorage` key holding the serialized array.
|
|
919
|
+
*
|
|
920
|
+
* @default "templatical:saved-blocks"
|
|
921
|
+
*/
|
|
922
|
+
key?: string;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
/**
|
|
926
|
+
* A paired logic construct — an opening tag (`before`) and its matching closer
|
|
927
|
+
* (`after`), e.g. `{% if vip %}` … `{% endif %}`. Selecting one inserts both
|
|
928
|
+
* pills at once, wrapping the selection (or with the caret placed between
|
|
929
|
+
* them). Shares its `before`/`after` shape with `DisplayCondition`.
|
|
930
|
+
*/
|
|
931
|
+
export declare interface LogicPair {
|
|
932
|
+
label: string;
|
|
933
|
+
/** Opening logic tag, including delimiters (e.g. `{% if vip %}`). */
|
|
934
|
+
before: string;
|
|
935
|
+
/** Closing logic tag, including delimiters (e.g. `{% endif %}`). */
|
|
936
|
+
after: string;
|
|
937
|
+
/** Optional grouping label used to section the logic picker. */
|
|
938
|
+
group?: string;
|
|
939
|
+
/** Optional helper text shown beneath the pair in the picker. */
|
|
940
|
+
description?: string;
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
/**
|
|
944
|
+
* A standalone logic tag — a control-flow token (e.g. `{% else %}`) inserted
|
|
945
|
+
* at the cursor. Distinct from data merge tags; rendered as a keyword badge.
|
|
946
|
+
*/
|
|
947
|
+
export declare interface LogicTag {
|
|
948
|
+
label: string;
|
|
949
|
+
value: string;
|
|
950
|
+
/** Optional grouping label used to section the logic picker. */
|
|
951
|
+
group?: string;
|
|
952
|
+
/** Optional helper text shown beneath the tag in the picker. */
|
|
953
|
+
description?: string;
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
/**
|
|
957
|
+
* Standalone logic-tag configuration, independent of `mergeTags`. `tags` are
|
|
958
|
+
* single control-flow tokens inserted at the cursor; `pairs` are open/close
|
|
959
|
+
* constructs inserted around the selection. Both appear in the built-in logic
|
|
960
|
+
* picker, grouped together by `group`. Typed/pasted logic highlighting is
|
|
961
|
+
* always on regardless of this.
|
|
962
|
+
*/
|
|
963
|
+
export declare interface LogicTagsConfig {
|
|
964
|
+
tags?: LogicTag[];
|
|
965
|
+
pairs?: LogicPair[];
|
|
966
|
+
/**
|
|
967
|
+
* Consumer-owned picker. When set, the "Insert logic" affordance calls this
|
|
968
|
+
* instead of the built-in logic picker — return the chosen tag/pair (or
|
|
969
|
+
* `null` to cancel). Mirrors `MergeTagsConfig.onRequest`; lets consumers use
|
|
970
|
+
* their own UI and custom syntax. Precedence: `onRequest` → built-in picker.
|
|
971
|
+
*/
|
|
972
|
+
onRequest?: () => Promise<LogicTag | LogicPair | null>;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
declare interface McpConfig {
|
|
976
|
+
enabled: boolean;
|
|
977
|
+
onOperation?: (payload: TemplateOperationPayload) => void;
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
/**
|
|
981
|
+
* One file in a media library — the row `list` returns and `create` persists.
|
|
982
|
+
*
|
|
983
|
+
* Distinct from `MediaResult` in `./config` (`{ url, alt? }`): that is what
|
|
984
|
+
* an image field stores. A picked asset is reduced to a result at confirm;
|
|
985
|
+
* the library itself talks in assets.
|
|
986
|
+
*
|
|
987
|
+
* The store owns `id` — it is returned by {@link MediaProvider.create} and
|
|
988
|
+
* never generated by the editor, so identity stays a property of your storage.
|
|
989
|
+
*
|
|
990
|
+
* There is no conversion set (`small` / `medium` / `large`) on this type. The
|
|
991
|
+
* grid uses {@link thumbnailUrl} falling back to {@link url}; confirm always
|
|
992
|
+
* inserts `url`. If a derivative belongs in the email, put that URL on `url`.
|
|
993
|
+
*/
|
|
994
|
+
export declare interface MediaAsset {
|
|
995
|
+
/** Store-assigned. The editor never generates one. */
|
|
996
|
+
id: string;
|
|
997
|
+
/** The URL that lands on the block when the asset is confirmed. */
|
|
998
|
+
url: string;
|
|
999
|
+
alt?: string;
|
|
1000
|
+
filename?: string;
|
|
1001
|
+
mimeType?: string;
|
|
1002
|
+
width?: number;
|
|
1003
|
+
height?: number;
|
|
1004
|
+
/** Size in bytes. */
|
|
1005
|
+
size?: number;
|
|
1006
|
+
/**
|
|
1007
|
+
* Grid thumbnail. Falls back to {@link url} when omitted, so a store that
|
|
1008
|
+
* only has one URL still renders.
|
|
1009
|
+
*/
|
|
1010
|
+
thumbnailUrl?: string;
|
|
1011
|
+
/** Folder this asset sits in, or `null` / omitted for the root. */
|
|
1012
|
+
folderId?: string | null;
|
|
1013
|
+
/**
|
|
1014
|
+
* Store-assigned timestamps, ISO 8601, used for display only. They do not
|
|
1015
|
+
* affect ordering — the editor renders whatever order `list()` returns.
|
|
1016
|
+
*/
|
|
1017
|
+
createdAt?: string;
|
|
1018
|
+
updatedAt?: string;
|
|
1019
|
+
/**
|
|
1020
|
+
* Per-entry permission carve-outs. **Absent means allowed** — the provider's
|
|
1021
|
+
* `update` / `delete` already say whether the capability exists at all, so
|
|
1022
|
+
* these exist only to forbid it on *particular* entries.
|
|
1023
|
+
*
|
|
1024
|
+
* They compose one-way: `canUpdate: true` cannot re-enable an `update: false`.
|
|
1025
|
+
*/
|
|
1026
|
+
canUpdate?: boolean;
|
|
1027
|
+
canDelete?: boolean;
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
/**
|
|
1031
|
+
* Partial patch for {@link MediaProvider.update}. Only the keys present are
|
|
1032
|
+
* being changed.
|
|
1033
|
+
*/
|
|
1034
|
+
declare interface MediaAssetPatch {
|
|
1035
|
+
alt?: string;
|
|
1036
|
+
filename?: string;
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
/**
|
|
1040
|
+
* Which kind of file a listing or picker is constrained to.
|
|
1041
|
+
*
|
|
1042
|
+
* Image fields and the video thumbnail pass `["images"]`. Custom blocks may
|
|
1043
|
+
* pass other categories. The modal forces `list({ category })` to that set.
|
|
1044
|
+
*/
|
|
1045
|
+
declare type MediaCategory = "images" | "documents" | "videos" | "audio";
|
|
1046
|
+
|
|
1047
|
+
/** Payload for {@link MediaProvider.create}. */
|
|
1048
|
+
declare interface MediaCreateInput {
|
|
1049
|
+
file: File;
|
|
1050
|
+
folderId?: string | null;
|
|
1051
|
+
alt?: string;
|
|
1052
|
+
filename?: string;
|
|
1053
|
+
templateId?: string;
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
/**
|
|
1057
|
+
* One folder in the library. {@link MediaFoldersProvider.list} returns these
|
|
1058
|
+
* **flat**; the UI trees them via {@link parentId}.
|
|
1059
|
+
*/
|
|
1060
|
+
declare interface MediaFolder {
|
|
1061
|
+
id: string;
|
|
1062
|
+
name: string;
|
|
1063
|
+
/** `null` / omitted = root. */
|
|
1064
|
+
parentId?: string | null;
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
/** Payload for {@link MediaFoldersProvider.create}. */
|
|
1068
|
+
declare interface MediaFolderInput {
|
|
1069
|
+
name: string;
|
|
1070
|
+
parentId?: string | null;
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
/**
|
|
1074
|
+
* Nested folder + move contract, so an OSS gallery writes `folders: false`
|
|
1075
|
+
* once rather than stubbing five methods.
|
|
1076
|
+
*
|
|
1077
|
+
* `list` returns a **flat** array; the UI trees it via {@link MediaFolder.parentId}.
|
|
1078
|
+
* A store whose HTTP API returns a tree flattens it here.
|
|
1079
|
+
*/
|
|
1080
|
+
declare interface MediaFoldersProvider {
|
|
1081
|
+
/** Flat listing. The one method that cannot be `false` when folders exist. */
|
|
1082
|
+
list(): Promise<MediaFolder[]>;
|
|
1083
|
+
create: false | ((input: MediaFolderInput) => Promise<MediaFolder>);
|
|
1084
|
+
update: false | ((id: string, patch: {
|
|
1085
|
+
name: string;
|
|
1086
|
+
}) => Promise<MediaFolder>);
|
|
1087
|
+
delete: false | ((id: string) => Promise<void>);
|
|
1088
|
+
/**
|
|
1089
|
+
* Move assets into a folder (`folderId: null` = root) and return the stored
|
|
1090
|
+
* results. Bulk — the grid is multi-select.
|
|
1091
|
+
*/
|
|
1092
|
+
move: false | ((ids: string[], folderId: string | null) => Promise<MediaAsset[]>);
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
/**
|
|
1096
|
+
* One page of {@link MediaProvider.list}.
|
|
1097
|
+
*
|
|
1098
|
+
* An envelope rather than a bare array so pagination never needs a breaking
|
|
1099
|
+
* change: a cursor has somewhere to live from day one. A provider that returns
|
|
1100
|
+
* everything at once omits {@link nextCursor}.
|
|
1101
|
+
*/
|
|
1102
|
+
declare interface MediaListPage {
|
|
1103
|
+
items: MediaAsset[];
|
|
1104
|
+
nextCursor?: string;
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
/**
|
|
1108
|
+
* Parameters for {@link MediaProvider.list}.
|
|
1109
|
+
*
|
|
1110
|
+
* Unlike saved-blocks (which the editor calls bare and filters in memory),
|
|
1111
|
+
* these are **real**: galleries outgrow one response, so search, folder,
|
|
1112
|
+
* category and cursor are sent on every listing. A provider that ignores them
|
|
1113
|
+
* cannot page or search.
|
|
1114
|
+
*
|
|
1115
|
+
* `templateId` is opportunistic — present when a template is loaded, omitted
|
|
1116
|
+
* on a blank canvas. Cloud's store ignores it; a CMS that scopes a gallery
|
|
1117
|
+
* per template reads it here.
|
|
1118
|
+
*/
|
|
1119
|
+
declare interface MediaListParams {
|
|
1120
|
+
search?: string;
|
|
1121
|
+
cursor?: string;
|
|
1122
|
+
folderId?: string | null;
|
|
1123
|
+
category?: MediaCategory;
|
|
1124
|
+
/** When a template is loaded; Cloud ignores. */
|
|
1125
|
+
templateId?: string;
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
/**
|
|
1129
|
+
* Configuration and outward notifications for the media library. Every member
|
|
1130
|
+
* is optional.
|
|
1131
|
+
*
|
|
1132
|
+
* Separate from the storage methods so `initCloud()` can accept this half
|
|
1133
|
+
* alone: these fire the same way whichever store holds the assets, Cloud's
|
|
1134
|
+
* own or yours. {@link MediaProvider} extends this, so one object satisfies
|
|
1135
|
+
* both entry points.
|
|
1136
|
+
*
|
|
1137
|
+
* `maxFileSize` / `mimeTypes` are a **client pre-check**, not a security
|
|
1138
|
+
* boundary — the backend must enforce too.
|
|
1139
|
+
*/
|
|
1140
|
+
export declare interface MediaOptions {
|
|
1141
|
+
/** Bytes; client pre-check only. */
|
|
1142
|
+
maxFileSize?: number;
|
|
1143
|
+
mimeTypes?: Partial<Record<MediaCategory, string[]>>;
|
|
1144
|
+
/** An asset was created and prepended to the listing. */
|
|
1145
|
+
onCreated?: (asset: MediaAsset) => void;
|
|
1146
|
+
/** An asset was updated and its listing entry replaced. */
|
|
1147
|
+
onUpdated?: (asset: MediaAsset) => void;
|
|
1148
|
+
/**
|
|
1149
|
+
* An asset was deleted and filtered out of the listing. `delete` resolves
|
|
1150
|
+
* to nothing, so this carries the entry the editor captured before removing
|
|
1151
|
+
* it — not an id.
|
|
1152
|
+
*
|
|
1153
|
+
* Fires only when the entry was present in the locally loaded list at the
|
|
1154
|
+
* time of the delete. Deleting an id that was never loaded still deletes
|
|
1155
|
+
* successfully; there is just nothing to pass, so this does not fire.
|
|
1156
|
+
*/
|
|
1157
|
+
onDeleted?: (asset: MediaAsset) => void;
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
/**
|
|
1161
|
+
* Storage contract for a media library. Implement it to back the editor's
|
|
1162
|
+
* media picker with your own persistence — the editor owns the modal, the
|
|
1163
|
+
* grid, crop (client-side; persisting a crop is `create` or `replace` of the
|
|
1164
|
+
* resulting `File`) and insertion; you own the transport.
|
|
1165
|
+
*
|
|
1166
|
+
* Pass an implementation as `media` to `init()`. When omitted, the feature
|
|
1167
|
+
* stays off entirely and image fields are URL-only. `onRequestMedia` remains
|
|
1168
|
+
* the UI override (a host widget); it is not this store.
|
|
1169
|
+
*
|
|
1170
|
+
* Every method may reject; the editor surfaces the failure through `onError`
|
|
1171
|
+
* and leaves its in-memory list untouched. Provider messages are
|
|
1172
|
+
* user-presentable.
|
|
1173
|
+
*
|
|
1174
|
+
* **Each method other than `list` can be turned off by passing `false`
|
|
1175
|
+
* instead of a function.** The editor then hides the affordance rather than
|
|
1176
|
+
* letting the user try and fail. They are required rather than optional
|
|
1177
|
+
* precisely so that disabling is a decision you state, never something you
|
|
1178
|
+
* get by forgetting a method. Setting every mutation to `false` yields a
|
|
1179
|
+
* **read-only library**: users still browse, search and pick, because picking
|
|
1180
|
+
* only copies a URL onto the canvas.
|
|
1181
|
+
*
|
|
1182
|
+
* `list` cannot be `false`: without it there is nothing to show.
|
|
1183
|
+
*
|
|
1184
|
+
* ```ts
|
|
1185
|
+
* const media: MediaProvider = {
|
|
1186
|
+
* list: ({ search, cursor, templateId }) =>
|
|
1187
|
+
* myCms.page({ search, cursor, templateId }),
|
|
1188
|
+
* create: ({ file }) => myCms.upload(file),
|
|
1189
|
+
* update: false,
|
|
1190
|
+
* delete: false,
|
|
1191
|
+
* folders: false,
|
|
1192
|
+
* replace: false,
|
|
1193
|
+
* importFromUrl: false,
|
|
1194
|
+
* checkUsage: false,
|
|
1195
|
+
* frequentlyUsed: false,
|
|
1196
|
+
* storage: false,
|
|
1197
|
+
* };
|
|
1198
|
+
* ```
|
|
1199
|
+
*/
|
|
1200
|
+
declare interface MediaProvider_2 extends MediaOptions {
|
|
1201
|
+
/**
|
|
1202
|
+
* Fetch one page of assets. Search, folder, category and cursor are real
|
|
1203
|
+
* params — this is not an in-memory filter over a full dump.
|
|
1204
|
+
*
|
|
1205
|
+
* The one method that cannot be disabled: without it the feature has nothing
|
|
1206
|
+
* to show.
|
|
1207
|
+
*/
|
|
1208
|
+
list(params?: MediaListParams): Promise<MediaListPage>;
|
|
1209
|
+
/**
|
|
1210
|
+
* Persist a new asset from a `File` and return it with its store-assigned
|
|
1211
|
+
* `id`, or `false` to disable upload — the drop zone and upload control then
|
|
1212
|
+
* do not render. There is no progress callback; resolve with the asset.
|
|
1213
|
+
*/
|
|
1214
|
+
create: false | ((input: MediaCreateInput) => Promise<MediaAsset>);
|
|
1215
|
+
/**
|
|
1216
|
+
* Apply a partial update and return the stored result, or `false` to disable
|
|
1217
|
+
* editing entirely. Per-entry exceptions go through
|
|
1218
|
+
* {@link MediaAsset.canUpdate}.
|
|
1219
|
+
*/
|
|
1220
|
+
update: false | ((id: string, patch: MediaAssetPatch) => Promise<MediaAsset>);
|
|
1221
|
+
/**
|
|
1222
|
+
* Remove assets by id, resolving once the store has applied the delete, or
|
|
1223
|
+
* `false` to disable deletion entirely. **Bulk** — the grid is multi-select.
|
|
1224
|
+
* Per-entry exceptions go through {@link MediaAsset.canDelete}.
|
|
1225
|
+
*/
|
|
1226
|
+
delete: false | ((ids: string[]) => Promise<void>);
|
|
1227
|
+
/**
|
|
1228
|
+
* Folder tree + move, or `false` to hide folders entirely. Nested so a
|
|
1229
|
+
* gallery without folders writes `folders: false` once.
|
|
1230
|
+
*/
|
|
1231
|
+
folders: false | MediaFoldersProvider;
|
|
1232
|
+
/**
|
|
1233
|
+
* Replace an asset's file in place and return the stored result, or `false`
|
|
1234
|
+
* to disable replace.
|
|
1235
|
+
*/
|
|
1236
|
+
replace: false | ((id: string, file: File) => Promise<MediaAsset>);
|
|
1237
|
+
/**
|
|
1238
|
+
* Import a remote URL into the library and return the stored asset, or
|
|
1239
|
+
* `false` to hide import. `folderId` / `templateId` are the same opportunistic
|
|
1240
|
+
* scoping as {@link MediaCreateInput}.
|
|
1241
|
+
*/
|
|
1242
|
+
importFromUrl: false | ((url: string, folderId?: string | null, templateId?: string) => Promise<MediaAsset>);
|
|
1243
|
+
/**
|
|
1244
|
+
* Look up how many templates reference each asset, or `false` to skip the
|
|
1245
|
+
* usage warning on delete. **Bulk** — keyed by the same id array `delete`
|
|
1246
|
+
* takes.
|
|
1247
|
+
*/
|
|
1248
|
+
checkUsage: false | ((ids: string[]) => Promise<Record<string, MediaUsageInfo>>);
|
|
1249
|
+
/**
|
|
1250
|
+
* Assets the current user reaches for often, or `false` to hide that tab.
|
|
1251
|
+
*/
|
|
1252
|
+
frequentlyUsed: false | (() => Promise<MediaAsset[]>);
|
|
1253
|
+
/**
|
|
1254
|
+
* Current quota, or `false` to hide the storage ring. May resolve `null`
|
|
1255
|
+
* when the store does not (yet) know.
|
|
1256
|
+
*/
|
|
1257
|
+
storage: false | (() => Promise<MediaStorageInfo | null>);
|
|
1258
|
+
}
|
|
1259
|
+
export { MediaProvider_2 as MediaProvider }
|
|
1260
|
+
|
|
1261
|
+
/**
|
|
1262
|
+
* Why the media picker opened. Passed to `onRequestMedia` and used by the
|
|
1263
|
+
* modal to constrain the listing.
|
|
1264
|
+
*
|
|
1265
|
+
* `files` is the drop path: image files dragged onto an image block. Absent
|
|
1266
|
+
* on click → Browse.
|
|
1267
|
+
*/
|
|
1268
|
+
export declare interface MediaRequestContext {
|
|
1269
|
+
accept?: MediaCategory[];
|
|
1270
|
+
/** Drop path; absent on Browse. */
|
|
1271
|
+
files?: File[];
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
declare interface MediaResult {
|
|
1275
|
+
url: string;
|
|
1276
|
+
alt?: string;
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
/**
|
|
1280
|
+
* Quota snapshot for the storage ring. `storage()` may resolve `null` when
|
|
1281
|
+
* the store does not track quota (or, for Cloud, until plan config has loaded).
|
|
1282
|
+
*/
|
|
1283
|
+
declare interface MediaStorageInfo {
|
|
1284
|
+
usedBytes: number;
|
|
1285
|
+
limitBytes: number;
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
/**
|
|
1289
|
+
* How many templates reference an asset, for the bulk-delete warning.
|
|
1290
|
+
*
|
|
1291
|
+
* Returned from {@link MediaProvider.checkUsage} keyed by asset id.
|
|
1292
|
+
*/
|
|
1293
|
+
declare interface MediaUsageInfo {
|
|
1294
|
+
templateCount: number;
|
|
1295
|
+
templateNames: string[];
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
declare interface MenuBlock extends BaseBlock {
|
|
1299
|
+
type: "menu";
|
|
1300
|
+
items: MenuItemData[];
|
|
1301
|
+
fontSize: number;
|
|
1302
|
+
fontFamily?: string;
|
|
1303
|
+
/** Base text/link color. Unset = inherit the document-level `textColor`. */
|
|
1304
|
+
color?: string;
|
|
1305
|
+
linkColor?: string;
|
|
1306
|
+
textAlign: "left" | "center" | "right";
|
|
1307
|
+
separator: string;
|
|
1308
|
+
separatorColor: string;
|
|
1309
|
+
spacing: number;
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
declare interface MenuItemData {
|
|
1313
|
+
id: string;
|
|
1314
|
+
text: string;
|
|
1315
|
+
url: string;
|
|
1316
|
+
openInNewTab: boolean;
|
|
1317
|
+
bold: boolean;
|
|
1318
|
+
underline: boolean;
|
|
1319
|
+
color?: string;
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
declare interface MergeTag {
|
|
1323
|
+
label: string;
|
|
1324
|
+
value: string;
|
|
1325
|
+
/**
|
|
1326
|
+
* Optional grouping label used by the built-in merge tag picker to
|
|
1327
|
+
* section the list. When no tag in the configured array carries
|
|
1328
|
+
* `group`, the picker renders a plain flat list with no headers.
|
|
1329
|
+
* Ignored by the renderer and by typing-autocomplete.
|
|
1330
|
+
*/
|
|
1331
|
+
group?: string;
|
|
1332
|
+
/**
|
|
1333
|
+
* Optional helper text shown beneath the tag in the built-in merge
|
|
1334
|
+
* tag picker. Not rendered anywhere else (toolbar, autocomplete,
|
|
1335
|
+
* MJML output) and not stored on the inserted document node.
|
|
1336
|
+
*/
|
|
1337
|
+
description?: string;
|
|
1338
|
+
/**
|
|
1339
|
+
* Example value shown in place of this tag on preview surfaces — e.g.
|
|
1340
|
+
* `"Ada"` for `{{first_name}}` — so a preview reads like a delivered
|
|
1341
|
+
* email instead of a list of field names.
|
|
1342
|
+
*
|
|
1343
|
+
* Display-only, and only in the previews' Sample mode: never written to
|
|
1344
|
+
* the template, never sent, never present in MJML output. Setting it is
|
|
1345
|
+
* the whole opt-in; there is no accompanying flag. A tag without a
|
|
1346
|
+
* sample keeps showing its `label`.
|
|
1347
|
+
*/
|
|
1348
|
+
sample?: string;
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
/**
|
|
1352
|
+
* Why the merge-tag chooser opened, handed to {@link MergeTagsConfig.onRequest}.
|
|
1353
|
+
*
|
|
1354
|
+
* `"insert"` is a new tag at the caret. `"edit"` is the user activating a tag
|
|
1355
|
+
* that is already in the content, asking to swap it for another one.
|
|
1356
|
+
*/
|
|
1357
|
+
declare interface MergeTagRequestContext {
|
|
1358
|
+
reason: "insert" | "edit";
|
|
1359
|
+
/**
|
|
1360
|
+
* The tag being replaced. Present only when `reason` is `"edit"` AND the
|
|
1361
|
+
* token in the content resolves against `tags` — a token that matches no
|
|
1362
|
+
* configured tag still opens the chooser, with nothing to preselect.
|
|
1363
|
+
*/
|
|
1364
|
+
current?: MergeTag;
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
export declare interface MergeTagsConfig {
|
|
1368
|
+
syntax?: SyntaxPresetName | SyntaxPreset;
|
|
1369
|
+
tags?: MergeTag[];
|
|
1370
|
+
/**
|
|
1371
|
+
* Consumer-owned chooser. Called both to insert a tag at the caret and to
|
|
1372
|
+
* replace one already in the content — `context.reason` says which. Return
|
|
1373
|
+
* the chosen tag, or `null` to cancel.
|
|
1374
|
+
*
|
|
1375
|
+
* The parameter is optional, so an existing zero-argument implementation
|
|
1376
|
+
* keeps working unchanged.
|
|
1377
|
+
*/
|
|
1378
|
+
onRequest?: (context?: MergeTagRequestContext) => Promise<MergeTag | null>;
|
|
1379
|
+
/**
|
|
1380
|
+
* Whether a tag's tooltip reveals the raw token behind its label. Defaults
|
|
1381
|
+
* to `true`, which suits a readable syntax like `{{first_name}}` — the
|
|
1382
|
+
* tooltip tells an author which field they are looking at.
|
|
1383
|
+
*
|
|
1384
|
+
* Set `false` when `value` is an internal identifier (an opaque id resolved
|
|
1385
|
+
* by your backend) that an author should never see. Display-only: the token
|
|
1386
|
+
* is unchanged in stored content and in the rendered output.
|
|
1387
|
+
*/
|
|
1388
|
+
showRawValue?: boolean;
|
|
1389
|
+
/**
|
|
1390
|
+
* Enables typing-based autocomplete in rich text blocks and in every
|
|
1391
|
+
* merge-tag-enabled input/textarea field (toolbars, template settings,
|
|
1392
|
+
* custom-block text fields). When the user types the syntax opener
|
|
1393
|
+
* (e.g. `{{`), a popup lists matching `tags`. Both surfaces share the
|
|
1394
|
+
* same popup, so behavior is identical.
|
|
1395
|
+
*
|
|
1396
|
+
* Defaults to `true`. Effective only when `tags` is non-empty AND
|
|
1397
|
+
* `syntax` matches a built-in preset (custom regex syntaxes cannot be
|
|
1398
|
+
* mapped to a trigger string and silently disable autocomplete).
|
|
1399
|
+
*/
|
|
1400
|
+
autocomplete?: boolean;
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
/** Function type for media browser requests, used by both OSS and Cloud editors. */
|
|
1404
|
+
export declare type OnRequestMedia = (context?: MediaRequestContext) => Promise<MediaResult | null>;
|
|
1405
|
+
|
|
1406
|
+
declare interface ParagraphBlock extends BaseBlock {
|
|
1407
|
+
type: "paragraph";
|
|
1408
|
+
content: string;
|
|
1409
|
+
/**
|
|
1410
|
+
* Gap in px between this block's paragraphs — the space below every `<p>`
|
|
1411
|
+
* except the last. Absent means `RICH_TEXT_SPACING.paragraphGap`.
|
|
1412
|
+
*
|
|
1413
|
+
* Only affects a block holding more than one paragraph; a single `<p>` has no
|
|
1414
|
+
* internal gap, and the space around the block is `styles.padding`.
|
|
1415
|
+
*
|
|
1416
|
+
* `0` is a valid choice (paragraphs butted together) and is distinct from the
|
|
1417
|
+
* field being absent, so readers must test for `undefined` rather than
|
|
1418
|
+
* falsiness.
|
|
1419
|
+
*/
|
|
1420
|
+
paragraphSpacing?: number;
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
/**
|
|
1424
|
+
* Context handed to `resolvePreview`.
|
|
1425
|
+
*
|
|
1426
|
+
* `recipient` is present only on surfaces that have one — the test-email
|
|
1427
|
+
* dialog — and absent in the editor's own preview mode. Treat its absence as
|
|
1428
|
+
* "no particular recipient", not as an error: a resolver should still return
|
|
1429
|
+
* something renderable, typically its own default or sample data.
|
|
1430
|
+
*/
|
|
1431
|
+
declare interface PreviewResolveContext {
|
|
1432
|
+
/**
|
|
1433
|
+
* The template as it currently stands. Already a copy — mutating it has no
|
|
1434
|
+
* effect on the editor, and returning it unchanged is a valid no-op.
|
|
1435
|
+
*/
|
|
1436
|
+
content: TemplateContent;
|
|
1437
|
+
/** The address the preview is being shown for, when the surface has one. */
|
|
1438
|
+
recipient?: string;
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
/**
|
|
1442
|
+
* Everything a {@link RenderProvider} needs to render a template, handed over as
|
|
1443
|
+
* one object so the editor's obligations are stated rather than implied.
|
|
1444
|
+
*
|
|
1445
|
+
* The editor guarantees the payload is **render-complete**: whatever a backend
|
|
1446
|
+
* cannot know by itself has already been resolved into it.
|
|
1447
|
+
*/
|
|
1448
|
+
export declare interface RenderPayload {
|
|
1449
|
+
/**
|
|
1450
|
+
* The template to render, with every custom block's `renderedHtml` already
|
|
1451
|
+
* filled in.
|
|
1452
|
+
*
|
|
1453
|
+
* This is the part a server genuinely cannot do: a custom block's HTML comes
|
|
1454
|
+
* from the consumer's liquid template plus the block's field values, and the
|
|
1455
|
+
* template registration lives in the browser. A renderer that receives a
|
|
1456
|
+
* custom block with neither a resolver nor `renderedHtml` omits it silently,
|
|
1457
|
+
* so pre-rendering is a contract obligation, not an optimisation.
|
|
1458
|
+
*
|
|
1459
|
+
* A defensive copy — mutating it does not touch what the user is editing.
|
|
1460
|
+
*/
|
|
1461
|
+
content: TemplateContent;
|
|
1462
|
+
/**
|
|
1463
|
+
* The fonts the editor is actually rendering with: the custom faces it
|
|
1464
|
+
* resolved, plus the fallback stack to use for anything unmatched.
|
|
1465
|
+
*
|
|
1466
|
+
* Optional on the type so a headless caller can omit it; the editor always
|
|
1467
|
+
* sends it, because a font list assembled from `init({ fonts })` is not
|
|
1468
|
+
* something a backend can reconstruct from `content` alone.
|
|
1469
|
+
*/
|
|
1470
|
+
fonts?: {
|
|
1471
|
+
customFonts: CustomFont[];
|
|
1472
|
+
defaultFallback: string;
|
|
1473
|
+
};
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
/**
|
|
1477
|
+
* Rendering backend — how a template becomes MJML, and how MJML becomes the HTML
|
|
1478
|
+
* that gets sent.
|
|
1479
|
+
*
|
|
1480
|
+
* Deliberately separate from `TemplatesProvider`: saving and rendering run
|
|
1481
|
+
* at different frequencies (autosave would compile MJML on every debounce tick),
|
|
1482
|
+
* fail in different ways, and are wanted by different callers. Rendering is never
|
|
1483
|
+
* a field on a save result.
|
|
1484
|
+
*
|
|
1485
|
+
* Pass an implementation as `render` to `init()` or `initCloud()`. **Every method
|
|
1486
|
+
* is independently optional**, and each one the editor resolves on its own:
|
|
1487
|
+
*
|
|
1488
|
+
* | Call | Order |
|
|
1489
|
+
* |---|---|
|
|
1490
|
+
* | `editor.toMjml()` | {@link toMjml} → the local `@templatical/renderer` → throw |
|
|
1491
|
+
* | `editor.toHtml()` | {@link toHtml} → `toMjml()`'s result + {@link compileMjml} → throw |
|
|
1492
|
+
*
|
|
1493
|
+
* **There is no local HTML path, ever.** The SDK does not bundle an MJML
|
|
1494
|
+
* compiler, so `toHtml()` without either `toHtml` or `compileMjml` rejects with
|
|
1495
|
+
* an explanatory error rather than guessing.
|
|
1496
|
+
*
|
|
1497
|
+
* ```ts
|
|
1498
|
+
* // The cheapest useful implementation: one dumb mjml2html endpoint, and the
|
|
1499
|
+
* // editor gains `toHtml()` while still rendering MJML locally.
|
|
1500
|
+
* const editor = await init({
|
|
1501
|
+
* container,
|
|
1502
|
+
* render: {
|
|
1503
|
+
* compileMjml: (mjml) =>
|
|
1504
|
+
* fetch("/api/mjml", { method: "POST", body: mjml }).then((r) => r.text()),
|
|
1505
|
+
* },
|
|
1506
|
+
* });
|
|
1507
|
+
*
|
|
1508
|
+
* const html = await editor.toHtml();
|
|
1509
|
+
* ```
|
|
1510
|
+
*/
|
|
1511
|
+
export declare interface RenderProvider {
|
|
1512
|
+
/**
|
|
1513
|
+
* Render the template to MJML source. Wins over the bundled renderer — a
|
|
1514
|
+
* backend can render block types or refinements the browser cannot (a
|
|
1515
|
+
* server-generated countdown GIF, a composited video thumbnail), so its output
|
|
1516
|
+
* is treated as authoritative.
|
|
1517
|
+
*/
|
|
1518
|
+
toMjml?(payload: RenderPayload): Promise<string>;
|
|
1519
|
+
/**
|
|
1520
|
+
* Render the template straight to sending-ready HTML, skipping the MJML round
|
|
1521
|
+
* trip. Implement this when your backend already owns the whole pipeline.
|
|
1522
|
+
*/
|
|
1523
|
+
toHtml?(payload: RenderPayload): Promise<string>;
|
|
1524
|
+
/**
|
|
1525
|
+
* Compile MJML to HTML. A dumb `mjml2html` endpoint — it needs no knowledge of
|
|
1526
|
+
* the block model, which is exactly the point: MJML compilation is a commodity
|
|
1527
|
+
* (an off-the-shelf service, a container, a CLI shell-out), whereas rendering
|
|
1528
|
+
* Templatical JSON is not.
|
|
1529
|
+
*
|
|
1530
|
+
* This is the cheap tier, and the reason the interface has three methods
|
|
1531
|
+
* instead of two: without it, every non-Node backend that wants HTML would have
|
|
1532
|
+
* to stand up a Node sidecar first.
|
|
1533
|
+
*/
|
|
1534
|
+
compileMjml?(mjml: string): Promise<string>;
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
/**
|
|
1538
|
+
* Display-only resolver for image `src` values (`config.resolveImageUrl`,
|
|
1539
|
+
* #415). Maps a canonical src (e.g. a plain file name like `logo.png`) to a
|
|
1540
|
+
* URL the canvas can actually display (e.g. an ephemeral `blob:` URL).
|
|
1541
|
+
* Returning `null` (or the input value) means "use the src as-is". The
|
|
1542
|
+
* resolved value never enters the content model or the MJML export.
|
|
1543
|
+
*/
|
|
1544
|
+
export declare type ResolveImageUrl = (src: string) => string | null | Promise<string | null>;
|
|
1545
|
+
|
|
1546
|
+
/**
|
|
1547
|
+
* Resolves a template for display on preview surfaces — typically substituting
|
|
1548
|
+
* merge tags and **evaluating logic tags** against real data.
|
|
1549
|
+
*
|
|
1550
|
+
* Logic tags are the reason this hook exists. `MergeTag.sample` already covers
|
|
1551
|
+
* value tags client-side, but branching (`{% if %}` … `{% endif %}`) cannot be
|
|
1552
|
+
* evaluated in a browser for every supported syntax — mailchimp and ampscript
|
|
1553
|
+
* logic are server-side dialects — so only the consumer's own backend can do it.
|
|
1554
|
+
*
|
|
1555
|
+
* Display-only. The returned content reaches preview surfaces and nothing else:
|
|
1556
|
+
* it is never written to the editor's state, never returned from
|
|
1557
|
+
* `getContent()`, never sent, and never exported. Rejecting is safe — the
|
|
1558
|
+
* preview falls back to the unresolved template and says so.
|
|
1559
|
+
*/
|
|
1560
|
+
declare type ResolvePreview = (context: PreviewResolveContext) => Promise<TemplateContent>;
|
|
1561
|
+
|
|
1562
|
+
/**
|
|
1563
|
+
* Per-rule severity override. Set a rule to `'off'` to disable it.
|
|
1564
|
+
* Keys are the full prefixed rule IDs (`a11y.*`, `structure.*`, `link.*`)
|
|
1565
|
+
* so a value copied from `LintIssue.ruleId` pastes straight in.
|
|
1566
|
+
*/
|
|
1567
|
+
declare type RuleOverrides = Record<string, Severity>;
|
|
1568
|
+
|
|
1569
|
+
/**
|
|
1570
|
+
* A reusable, user-authored group of blocks — saved from the canvas and
|
|
1571
|
+
* re-insertable into any template.
|
|
1572
|
+
*
|
|
1573
|
+
* Distinct from a *custom block* (`CustomBlockDefinition`), which is a
|
|
1574
|
+
* developer-defined block **type** with its own template and field schema.
|
|
1575
|
+
* A saved block is an instance-level snapshot of ordinary blocks.
|
|
1576
|
+
*/
|
|
1577
|
+
export declare interface SavedBlock {
|
|
1578
|
+
/**
|
|
1579
|
+
* Store-assigned identifier. Returned by {@link SavedBlocksProvider.create}
|
|
1580
|
+
* — the editor never generates it, so the store stays the authority on
|
|
1581
|
+
* identity (database primary key, storage slug, etc.).
|
|
1582
|
+
*/
|
|
1583
|
+
id: string;
|
|
1584
|
+
name: string;
|
|
1585
|
+
/**
|
|
1586
|
+
* Top-level blocks captured in this saved block. A `section` carries its own
|
|
1587
|
+
* `children`, so a whole section-with-columns round-trips as one entry.
|
|
1588
|
+
*
|
|
1589
|
+
* Blocks are re-identified on insert (via `cloneBlock`), so the IDs stored
|
|
1590
|
+
* here never collide with the blocks already on a canvas.
|
|
1591
|
+
*/
|
|
1592
|
+
content: Block[];
|
|
1593
|
+
/**
|
|
1594
|
+
* Optional free-text grouping, surfaced in the browser as a filter.
|
|
1595
|
+
*
|
|
1596
|
+
* Flat and free-text by design — there is no category registry and no
|
|
1597
|
+
* nesting. The editor derives the set of available categories from the
|
|
1598
|
+
* entries it has loaded, so a category exists exactly as long as something
|
|
1599
|
+
* carries it; an entry without one is simply uncategorised.
|
|
1600
|
+
*/
|
|
1601
|
+
category?: string;
|
|
1602
|
+
/**
|
|
1603
|
+
* Per-entry permission carve-outs. **Absent means allowed** — the provider's
|
|
1604
|
+
* `update` / `delete` already say whether the capability exists at all, so
|
|
1605
|
+
* these exist only to forbid it on *particular* entries (a shared block a
|
|
1606
|
+
* viewer may insert but not edit, someone else's block, a locked entry).
|
|
1607
|
+
*
|
|
1608
|
+
* Return them from your API alongside the row, where the answer is already
|
|
1609
|
+
* known — the editor never second-guesses them and never computes its own.
|
|
1610
|
+
* When one is `false` the corresponding control is not rendered for that
|
|
1611
|
+
* entry.
|
|
1612
|
+
*/
|
|
1613
|
+
canUpdate?: boolean;
|
|
1614
|
+
canDelete?: boolean;
|
|
1615
|
+
/**
|
|
1616
|
+
* Store-assigned timestamps, used for display only: the browser shows a
|
|
1617
|
+
* relative "5m ago" label per entry (preferring `updatedAt`, falling back to
|
|
1618
|
+
* `createdAt`) with the absolute date on hover.
|
|
1619
|
+
*
|
|
1620
|
+
* They do **not** affect ordering — the editor renders whatever order
|
|
1621
|
+
* `list()` returns and never re-sorts. Both are optional; omit them and the
|
|
1622
|
+
* label is simply not shown.
|
|
1623
|
+
*/
|
|
1624
|
+
createdAt?: string;
|
|
1625
|
+
updatedAt?: string;
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
/** Payload for {@link SavedBlocksProvider.create}. */
|
|
1629
|
+
declare interface SavedBlockInput {
|
|
1630
|
+
name: string;
|
|
1631
|
+
content: Block[];
|
|
1632
|
+
category?: string;
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
/**
|
|
1636
|
+
* Partial patch for {@link SavedBlocksProvider.update}. Only the keys present
|
|
1637
|
+
* are being changed — `category: ""` clears the category, whereas omitting the
|
|
1638
|
+
* key leaves it alone.
|
|
1639
|
+
*/
|
|
1640
|
+
declare type SavedBlockPatch = Partial<{
|
|
1641
|
+
name: string;
|
|
1642
|
+
content: Block[];
|
|
1643
|
+
category: string;
|
|
1644
|
+
}>;
|
|
1645
|
+
|
|
1646
|
+
/**
|
|
1647
|
+
* Parameters for {@link SavedBlocksProvider.list}. An object (rather than
|
|
1648
|
+
* positional arguments) so future filters can be added without breaking
|
|
1649
|
+
* existing provider implementations.
|
|
1650
|
+
*
|
|
1651
|
+
* **These are only sent by headless callers.** The editor's own browser calls
|
|
1652
|
+
* `list()` with no parameters and filters the loaded entries in memory — that
|
|
1653
|
+
* way a provider stays a dumb store and still gets a working search box and
|
|
1654
|
+
* category filter. They arrive only when you drive `useSavedBlocks`
|
|
1655
|
+
* yourself (see the guide's "Headless use"), so implement them if you want
|
|
1656
|
+
* server-side filtering for your own UI and ignore them otherwise.
|
|
1657
|
+
*/
|
|
1658
|
+
export declare interface SavedBlocksListParams {
|
|
1659
|
+
/** Free-text filter over the saved block's `name`. */
|
|
1660
|
+
search?: string;
|
|
1661
|
+
/** Exact-match filter over {@link SavedBlock.category}. */
|
|
1662
|
+
category?: string;
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
/**
|
|
1666
|
+
* Outward notifications for saved-block mutations. Every member is optional.
|
|
320
1667
|
*
|
|
321
|
-
*
|
|
322
|
-
*
|
|
323
|
-
*
|
|
324
|
-
*
|
|
1668
|
+
* Separate from the storage methods so `initCloud()` can accept this half
|
|
1669
|
+
* alone: these fire the same way whichever store holds the entries, Cloud's
|
|
1670
|
+
* own or yours. {@link SavedBlocksProvider} extends this, so one object
|
|
1671
|
+
* satisfies both entry points.
|
|
1672
|
+
*/
|
|
1673
|
+
export declare interface SavedBlocksOptions {
|
|
1674
|
+
/** A saved block was created and prepended to the list. */
|
|
1675
|
+
onCreated?: (block: SavedBlock) => void;
|
|
1676
|
+
/** A saved block was updated and its list entry replaced. */
|
|
1677
|
+
onUpdated?: (block: SavedBlock) => void;
|
|
1678
|
+
/**
|
|
1679
|
+
* A saved block was deleted and filtered out of the list. `delete` resolves
|
|
1680
|
+
* to nothing, so this carries the entry the editor captured before removing
|
|
1681
|
+
* it — not an id.
|
|
1682
|
+
*
|
|
1683
|
+
* Fires only when the entry was present in the locally loaded list at the
|
|
1684
|
+
* time of the delete, since that capture is where the entry comes from.
|
|
1685
|
+
* Deleting an id that was never loaded — e.g. a headless caller acting on
|
|
1686
|
+
* an id from elsewhere — still deletes successfully; there is just nothing
|
|
1687
|
+
* to pass, so this does not fire.
|
|
1688
|
+
*/
|
|
1689
|
+
onDeleted?: (block: SavedBlock) => void;
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
/**
|
|
1693
|
+
* Storage contract for saved blocks. Implement it to back the editor's saved
|
|
1694
|
+
* blocks UI with your own persistence — the editor owns the save dialog, the
|
|
1695
|
+
* browser, and insertion; you own the transport.
|
|
325
1696
|
*
|
|
326
|
-
*
|
|
327
|
-
*
|
|
328
|
-
* `init()` cannot fail after it mounts, and OSS should not grow the ability. A
|
|
329
|
-
* session that dies *later* — an auth refresh that cannot renew the token — does
|
|
330
|
-
* still surface as an overlay, because by then there is an editor to cover.
|
|
1697
|
+
* Pass an implementation as `savedBlocks` to `init()`. When omitted, the
|
|
1698
|
+
* feature stays off entirely and none of its UI renders.
|
|
331
1699
|
*
|
|
332
|
-
*
|
|
333
|
-
*
|
|
334
|
-
* the server-side export — a store Cloud never issued ids for would degrade
|
|
335
|
-
* all of them silently. Cloud therefore keeps each key's storage, but each
|
|
336
|
-
* key is still accepted, for its configuration and events:
|
|
337
|
-
* `templates.load`/`create`/`save`,
|
|
338
|
-
* `comments.list`/`create`/`update`/`delete`/`setResolved`, and
|
|
339
|
-
* `versionHistory.list`/`get`/`create`/`restore` are ignored with a warning
|
|
340
|
-
* naming them, while `templates`' `autoSave`, `unsavedChangesGuard`,
|
|
341
|
-
* `nameField`, `onSaved`, `onCreated`, `onLoaded`, `comments`' `onCreated`,
|
|
342
|
-
* `onUpdated`, `onDeleted`, `onResolved`, `onUnresolved`, and
|
|
343
|
-
* `versionHistory`'s `onCreated`, `onRestored` are all honoured.
|
|
1700
|
+
* Every method may reject; the editor surfaces the failure through the
|
|
1701
|
+
* editor's `onError` callback and leaves its in-memory list untouched.
|
|
344
1702
|
*
|
|
345
|
-
*
|
|
346
|
-
*
|
|
347
|
-
*
|
|
348
|
-
*
|
|
349
|
-
*
|
|
350
|
-
*
|
|
351
|
-
* each type to also accept an events-only shape — `boolean |
|
|
352
|
-
* SavedBlocksOptions | SavedBlocksProvider`, `Pick<TestEmailOptions,
|
|
353
|
-
* "onSent" | "defaultRecipient"> | TestEmailProvider`, and
|
|
354
|
-
* `false | MediaOptions | MediaProvider` — so upgrading is still a
|
|
355
|
-
* deletion: drop the key to adopt Cloud's store or sender, or leave it
|
|
356
|
-
* exactly as it is to keep your own.
|
|
1703
|
+
* **Each mutation can be turned off by passing `false` instead of a function.**
|
|
1704
|
+
* The editor then hides the affordance rather than letting the user try and
|
|
1705
|
+
* fail. They are required rather than optional precisely so that disabling is a
|
|
1706
|
+
* decision you state, never something you get by forgetting a method. Setting
|
|
1707
|
+
* all three yields a **read-only library**: users still browse, preview and
|
|
1708
|
+
* insert, because insertion only touches the canvas and never your store.
|
|
357
1709
|
*
|
|
358
|
-
*
|
|
359
|
-
*
|
|
360
|
-
*
|
|
1710
|
+
* ```ts
|
|
1711
|
+
* const provider: SavedBlocksProvider = {
|
|
1712
|
+
* list: ({ search } = {}) =>
|
|
1713
|
+
* fetch(`/api/saved-blocks?search=${search ?? ""}`).then((r) => r.json()),
|
|
1714
|
+
* create: (input) =>
|
|
1715
|
+
* fetch("/api/saved-blocks", {
|
|
1716
|
+
* method: "POST",
|
|
1717
|
+
* headers: { "Content-Type": "application/json" },
|
|
1718
|
+
* body: JSON.stringify(input),
|
|
1719
|
+
* }).then((r) => r.json()),
|
|
1720
|
+
* update: (id, patch) =>
|
|
1721
|
+
* fetch(`/api/saved-blocks/${id}`, {
|
|
1722
|
+
* method: "PUT",
|
|
1723
|
+
* headers: { "Content-Type": "application/json" },
|
|
1724
|
+
* body: JSON.stringify(patch),
|
|
1725
|
+
* }).then((r) => r.json()),
|
|
1726
|
+
* delete: (id) =>
|
|
1727
|
+
* fetch(`/api/saved-blocks/${id}`, { method: "DELETE" }).then(() => undefined),
|
|
1728
|
+
* };
|
|
1729
|
+
* ```
|
|
361
1730
|
*/
|
|
362
|
-
export declare
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
1731
|
+
export declare interface SavedBlocksProvider extends SavedBlocksOptions {
|
|
1732
|
+
/**
|
|
1733
|
+
* Fetch saved blocks. The editor calls this with no arguments and expects
|
|
1734
|
+
* everything the current user may see — scoping the result per user, tenant
|
|
1735
|
+
* or permission is yours to do here. {@link SavedBlocksListParams} is only
|
|
1736
|
+
* populated by headless callers; honouring it is optional.
|
|
1737
|
+
*
|
|
1738
|
+
* The one method that cannot be disabled: without it the feature has nothing
|
|
1739
|
+
* to show.
|
|
1740
|
+
*/
|
|
1741
|
+
list(params?: SavedBlocksListParams): Promise<SavedBlock[]>;
|
|
1742
|
+
/**
|
|
1743
|
+
* Persist a new saved block and return it with its store-assigned `id`, or
|
|
1744
|
+
* `false` to disable saving entirely — the block chrome's bookmark action
|
|
1745
|
+
* disappears and no pick session can be started.
|
|
1746
|
+
*/
|
|
1747
|
+
create: false | ((input: SavedBlockInput) => Promise<SavedBlock>);
|
|
1748
|
+
/**
|
|
1749
|
+
* Apply a partial update and return the stored result, or `false` to disable
|
|
1750
|
+
* editing entirely. Renaming is `update(id, { name })` and recategorising is
|
|
1751
|
+
* `update(id, { category })` — there are no separate methods for either.
|
|
1752
|
+
*
|
|
1753
|
+
* To allow editing in general but forbid it on particular entries, keep the
|
|
1754
|
+
* function and set {@link SavedBlock.canUpdate} to `false` on those.
|
|
1755
|
+
*/
|
|
1756
|
+
update: false | ((id: string, patch: SavedBlockPatch) => Promise<SavedBlock>);
|
|
1757
|
+
/**
|
|
1758
|
+
* Remove a saved block, resolving once the store has applied the delete, or
|
|
1759
|
+
* `false` to disable deletion entirely. Per-entry exceptions go through
|
|
1760
|
+
* {@link SavedBlock.canDelete}.
|
|
1761
|
+
*/
|
|
1762
|
+
delete: false | ((id: string) => Promise<void>);
|
|
1763
|
+
}
|
|
366
1764
|
|
|
367
|
-
|
|
368
|
-
|
|
1765
|
+
declare interface SectionBlock extends BaseBlock {
|
|
1766
|
+
type: "section";
|
|
1767
|
+
columns: ColumnLayout;
|
|
1768
|
+
children: Block[][];
|
|
1769
|
+
/**
|
|
1770
|
+
* Whether columns stack vertically on mobile. Absent or `true` keeps MJML's
|
|
1771
|
+
* default responsive behavior (columns stack below 480px). `false` renders
|
|
1772
|
+
* the columns inside an `<mj-group>` so they stay side-by-side on mobile,
|
|
1773
|
+
* proportionally shrunk to fit.
|
|
1774
|
+
*/
|
|
1775
|
+
stackOnMobile?: boolean;
|
|
1776
|
+
/**
|
|
1777
|
+
* Corner radius in px — one number, or a radius per corner. Omitted/0 =
|
|
1778
|
+
* square corners.
|
|
1779
|
+
*/
|
|
1780
|
+
borderRadius?: BorderRadiusValue;
|
|
1781
|
+
/**
|
|
1782
|
+
* Border around the section box, per side. Omitted = no border; a side with
|
|
1783
|
+
* width 0 is not drawn.
|
|
1784
|
+
*/
|
|
1785
|
+
border?: BorderValue;
|
|
1786
|
+
/** Optional outer frame (rendered as an `mj-wrapper` around the section). */
|
|
1787
|
+
wrapper?: SectionWrapper;
|
|
1788
|
+
}
|
|
369
1789
|
|
|
370
|
-
|
|
1790
|
+
/**
|
|
1791
|
+
* Optional outer frame for a section. When present, the section is rendered
|
|
1792
|
+
* inside an `mj-wrapper` — a full-width band (its own background + padding)
|
|
1793
|
+
* that frames the section, e.g. a white card sitting on a colored band.
|
|
1794
|
+
*/
|
|
1795
|
+
declare interface SectionWrapper {
|
|
1796
|
+
backgroundColor?: string;
|
|
1797
|
+
padding?: SpacingValue;
|
|
1798
|
+
/**
|
|
1799
|
+
* Corner radius in px for the outer frame — one number, or a radius per
|
|
1800
|
+
* corner. Omitted/0 = square corners.
|
|
1801
|
+
*/
|
|
1802
|
+
borderRadius?: BorderRadiusValue;
|
|
1803
|
+
}
|
|
371
1804
|
|
|
372
|
-
|
|
1805
|
+
declare interface SelectOption {
|
|
1806
|
+
label: string;
|
|
1807
|
+
value: string;
|
|
1808
|
+
}
|
|
373
1809
|
|
|
374
|
-
|
|
1810
|
+
declare type Severity = "error" | "warning" | "info" | "off";
|
|
375
1811
|
|
|
376
|
-
|
|
1812
|
+
/**
|
|
1813
|
+
* Layout-only hole where authored content is spliced in. Extends BaseBlock so
|
|
1814
|
+
* Block walkers stay typed (`styles` unused; spliced out before render).
|
|
1815
|
+
*/
|
|
1816
|
+
declare interface SlotBlock extends BaseBlock {
|
|
1817
|
+
type: "slot";
|
|
1818
|
+
}
|
|
377
1819
|
|
|
378
|
-
|
|
1820
|
+
declare interface SocialIcon {
|
|
1821
|
+
id: string;
|
|
1822
|
+
platform: SocialPlatform;
|
|
1823
|
+
url: string;
|
|
1824
|
+
}
|
|
379
1825
|
|
|
380
|
-
|
|
1826
|
+
declare interface SocialIconsBlock extends BaseBlock {
|
|
1827
|
+
type: "social";
|
|
1828
|
+
icons: SocialIcon[];
|
|
1829
|
+
iconStyle: SocialIconStyle;
|
|
1830
|
+
iconSize: SocialIconSize;
|
|
1831
|
+
spacing: number;
|
|
1832
|
+
align: "left" | "center" | "right";
|
|
1833
|
+
}
|
|
381
1834
|
|
|
382
|
-
|
|
1835
|
+
declare type SocialIconSize = "small" | "medium" | "large";
|
|
383
1836
|
|
|
384
|
-
|
|
1837
|
+
declare type SocialIconStyle = "solid" | "outlined" | "rounded" | "square" | "circle";
|
|
385
1838
|
|
|
386
|
-
|
|
1839
|
+
declare type SocialPlatform = "facebook" | "twitter" | "instagram" | "linkedin" | "youtube" | "tiktok" | "pinterest" | "email" | "whatsapp" | "telegram" | "discord" | "snapchat" | "reddit" | "github" | "dribbble" | "behance" | "website";
|
|
387
1840
|
|
|
388
|
-
|
|
1841
|
+
declare interface SpacerBlock extends BaseBlock {
|
|
1842
|
+
type: "spacer";
|
|
1843
|
+
height: number;
|
|
1844
|
+
}
|
|
389
1845
|
|
|
390
|
-
|
|
1846
|
+
declare interface SpacingValue {
|
|
1847
|
+
top: number;
|
|
1848
|
+
right: number;
|
|
1849
|
+
bottom: number;
|
|
1850
|
+
left: number;
|
|
1851
|
+
}
|
|
391
1852
|
|
|
392
|
-
|
|
1853
|
+
/** Options consumed only by the structure linter. */
|
|
1854
|
+
declare interface StructureLintOptions {
|
|
1855
|
+
rules?: RuleOverrides;
|
|
1856
|
+
}
|
|
393
1857
|
|
|
394
|
-
|
|
1858
|
+
declare interface SyntaxPreset {
|
|
1859
|
+
value: RegExp;
|
|
1860
|
+
logic: RegExp;
|
|
1861
|
+
}
|
|
395
1862
|
|
|
396
|
-
|
|
397
|
-
|
|
1863
|
+
declare type SyntaxPresetName = "liquid" | "handlebars" | "mailchimp" | "ampscript";
|
|
1864
|
+
|
|
1865
|
+
declare interface TableBlock extends BaseBlock {
|
|
1866
|
+
type: "table";
|
|
1867
|
+
rows: TableRowData[];
|
|
1868
|
+
hasHeaderRow: boolean;
|
|
1869
|
+
headerBackgroundColor?: string;
|
|
1870
|
+
borderColor: string;
|
|
1871
|
+
borderWidth: number;
|
|
1872
|
+
cellPadding: number;
|
|
1873
|
+
fontSize: number;
|
|
1874
|
+
fontFamily?: string;
|
|
1875
|
+
/** Text color. Unset = inherit the document-level `textColor`. */
|
|
1876
|
+
color?: string;
|
|
1877
|
+
textAlign: "left" | "center" | "right";
|
|
1878
|
+
}
|
|
398
1879
|
|
|
399
|
-
|
|
1880
|
+
declare interface TableCellData {
|
|
1881
|
+
id: string;
|
|
1882
|
+
content: string;
|
|
1883
|
+
}
|
|
400
1884
|
|
|
401
|
-
|
|
1885
|
+
declare interface TableRowData {
|
|
1886
|
+
id: string;
|
|
1887
|
+
cells: TableCellData[];
|
|
1888
|
+
}
|
|
402
1889
|
|
|
403
1890
|
/**
|
|
404
|
-
*
|
|
405
|
-
*
|
|
406
|
-
*
|
|
407
|
-
*
|
|
408
|
-
*
|
|
1891
|
+
* A stored template: its identity, an optional human-readable name, and the
|
|
1892
|
+
* content the editor edits.
|
|
1893
|
+
*
|
|
1894
|
+
* The store owns `id` — it is returned by {@link TemplatesProvider.create} and
|
|
1895
|
+
* never generated by the editor, so identity stays a property of your storage
|
|
1896
|
+
* (database primary key, slug, document id).
|
|
409
1897
|
*/
|
|
410
|
-
export declare
|
|
411
|
-
|
|
412
|
-
|
|
1898
|
+
export declare interface Template {
|
|
1899
|
+
id: string;
|
|
1900
|
+
/**
|
|
1901
|
+
* Optional display name, shown in the editor header and editable inline.
|
|
1902
|
+
*
|
|
1903
|
+
* Optional because a store may not have a name column, in which case the
|
|
1904
|
+
* header renders nothing to edit. A rename is persisted as an ordinary
|
|
1905
|
+
* `save(id, { name })` patch — there is no separate rename method.
|
|
1906
|
+
*/
|
|
1907
|
+
name?: string;
|
|
1908
|
+
/**
|
|
1909
|
+
* When your store created the template, ISO 8601.
|
|
1910
|
+
*
|
|
1911
|
+
* A fallback for {@link Template.updatedAt}: the header prefers `updatedAt`
|
|
1912
|
+
* and reads this only in its absence, so a store that records creation but not
|
|
1913
|
+
* modification still shows something.
|
|
1914
|
+
*/
|
|
1915
|
+
createdAt?: string;
|
|
1916
|
+
/**
|
|
1917
|
+
* When your store last wrote the template, ISO 8601.
|
|
1918
|
+
*
|
|
1919
|
+
* Display-only — the header renders it as a relative label ("Updated 5m ago")
|
|
1920
|
+
* with the absolute date on hover. Both timestamps are absent from
|
|
1921
|
+
* {@link TemplatePatch}, so the editor never writes them: they are yours to
|
|
1922
|
+
* set, and the value the editor shows is whatever `load` or `save` returned.
|
|
1923
|
+
* Absent, or a value that does not parse, renders nothing.
|
|
1924
|
+
*/
|
|
1925
|
+
updatedAt?: string;
|
|
1926
|
+
content: TemplateContent;
|
|
1927
|
+
}
|
|
413
1928
|
|
|
414
|
-
export
|
|
1929
|
+
export declare interface TemplateContent {
|
|
1930
|
+
blocks: Block[];
|
|
1931
|
+
settings: TemplateSettings;
|
|
1932
|
+
}
|
|
415
1933
|
|
|
416
|
-
export
|
|
1934
|
+
export declare type TemplateDefaults = Partial<TemplateSettings>;
|
|
417
1935
|
|
|
418
|
-
|
|
1936
|
+
declare type TemplateOperation = "addBlock" | "updateBlock" | "deleteBlock" | "moveBlock" | "updateSettings" | "setContent" | "updateBlockStyle";
|
|
419
1937
|
|
|
420
|
-
|
|
1938
|
+
declare interface TemplateOperationPayload {
|
|
1939
|
+
operation: TemplateOperation;
|
|
1940
|
+
data: Record<string, unknown>;
|
|
1941
|
+
timestamp: number;
|
|
1942
|
+
}
|
|
421
1943
|
|
|
422
|
-
|
|
1944
|
+
/**
|
|
1945
|
+
* Partial patch for {@link TemplatesProvider.save}. Only the keys present are
|
|
1946
|
+
* being changed, so a rename can travel without content and vice versa.
|
|
1947
|
+
*
|
|
1948
|
+
* A patch (rather than bare content) because retrofitting `save(id, content)`
|
|
1949
|
+
* into `save(id, patch)` later would break every implementation — the same
|
|
1950
|
+
* reasoning, and the same shape, as `SavedBlockPatch`.
|
|
1951
|
+
*/
|
|
1952
|
+
export declare type TemplatePatch = Partial<{
|
|
1953
|
+
name: string;
|
|
1954
|
+
content: TemplateContent;
|
|
1955
|
+
}>;
|
|
423
1956
|
|
|
424
|
-
|
|
1957
|
+
/**
|
|
1958
|
+
* Why a save happened, as reported to {@link TemplatesOptions.onSaved}.
|
|
1959
|
+
*
|
|
1960
|
+
* `manual` covers both the header's Save button and Cmd+S: one intent, and
|
|
1961
|
+
* splitting them would invite handling one and silently missing the other.
|
|
1962
|
+
*
|
|
1963
|
+
* The other three are separate because treating them as a Save press is wrong.
|
|
1964
|
+
* A `rename` writes through `save()` but the user only edited the name, and a
|
|
1965
|
+
* `restore` write is the confirmation step of restoring a version — navigating
|
|
1966
|
+
* away on either strands the user mid-task.
|
|
1967
|
+
*/
|
|
1968
|
+
export declare type TemplateSaveTrigger = "manual" | "autosave" | "rename" | "restore" | "api";
|
|
425
1969
|
|
|
426
|
-
|
|
1970
|
+
declare interface TemplateSettings {
|
|
1971
|
+
width: number;
|
|
1972
|
+
backgroundColor: string;
|
|
1973
|
+
/**
|
|
1974
|
+
* Document-level default text color: the `<mj-text>` default in the rendered
|
|
1975
|
+
* MJML, inherited by every text block (Title, Paragraph, Menu, Table) that
|
|
1976
|
+
* doesn't set its own `color`. Required, defaulting to `#1a1a1a` (see
|
|
1977
|
+
* `DEFAULT_TEMPLATE_DEFAULTS`); customize the default per-consumer via
|
|
1978
|
+
* `templateDefaults`. A block's explicit `color` or an inline text-color
|
|
1979
|
+
* mark overrides it.
|
|
1980
|
+
*/
|
|
1981
|
+
textColor: string;
|
|
1982
|
+
/**
|
|
1983
|
+
* Document-level link color: emitted as the global `a { color }` rule in the
|
|
1984
|
+
* rendered MJML, so it cascades to every link — rich-text and menu alike.
|
|
1985
|
+
* Optional: when unset, links inherit the surrounding text color
|
|
1986
|
+
* (`color: inherit`), preserving the pre-#352 default. A per-block or
|
|
1987
|
+
* per-item color (a Menu item's `color`, `MenuBlock.linkColor`) still
|
|
1988
|
+
* overrides it.
|
|
1989
|
+
*/
|
|
1990
|
+
linkColor?: string;
|
|
1991
|
+
/**
|
|
1992
|
+
* Whether links are underlined document-wide: drives the global
|
|
1993
|
+
* `a { text-decoration }` rule (`underline` when true, `none` when false).
|
|
1994
|
+
* Required, defaulting to `true` (see `DEFAULT_TEMPLATE_DEFAULTS`) — the
|
|
1995
|
+
* common, more accessible email default. Applies to body (rich-text) links;
|
|
1996
|
+
* buttons and menu items carry their own inline `text-decoration` and are
|
|
1997
|
+
* unaffected. Set `false` to render links without an underline.
|
|
1998
|
+
*/
|
|
1999
|
+
linkUnderline: boolean;
|
|
2000
|
+
fontFamily: string;
|
|
2001
|
+
preheaderText?: string;
|
|
2002
|
+
/**
|
|
2003
|
+
* BCP-47 language code for the rendered email's `<html lang>`. Drives
|
|
2004
|
+
* screen-reader pronunciation. Default `'en'` via `DEFAULT_TEMPLATE_DEFAULTS`.
|
|
2005
|
+
*/
|
|
2006
|
+
locale: string;
|
|
2007
|
+
/**
|
|
2008
|
+
* Writing direction of the delivered email: the canvas `dir` and the
|
|
2009
|
+
* rendered `<mjml dir>`. Independent of the editor chrome
|
|
2010
|
+
* (`init({ locale })`). Optional: when unset, `resolveContentDirection`
|
|
2011
|
+
* falls through to the content language (`ar` / `he` / `fa` / … → `"rtl"`,
|
|
2012
|
+
* otherwise `"ltr"`). An explicit `"ltr"` on an Arabic template is a
|
|
2013
|
+
* stated decision, not a missing value.
|
|
2014
|
+
*/
|
|
2015
|
+
direction?: ContentDirection;
|
|
2016
|
+
}
|
|
427
2017
|
|
|
428
|
-
export
|
|
2018
|
+
export declare interface TemplateSettingsConfig {
|
|
2019
|
+
/**
|
|
2020
|
+
* Which template settings the Settings panel exposes.
|
|
2021
|
+
*
|
|
2022
|
+
* - `true` / omitted — every setting is editable (the default).
|
|
2023
|
+
* - `false` — none; the Settings tab itself stops rendering.
|
|
2024
|
+
* - `Array<keyof TemplateSettings>` — an allowlist of settings to keep
|
|
2025
|
+
* (`['width', 'backgroundColor', 'fontFamily']`). A card renders when at
|
|
2026
|
+
* least one of its settings survives, so hiding `locale` removes the
|
|
2027
|
+
* Language card and hiding `preheaderText` removes the Preheader card. An
|
|
2028
|
+
* empty array means none, the same as `false`.
|
|
2029
|
+
*
|
|
2030
|
+
* The list narrows, it never reorders — settings sit in fixed cards, so
|
|
2031
|
+
* unlike `paletteBlocks` there is no order to express. An entry that isn't a
|
|
2032
|
+
* `TemplateSettings` member is a compile error for TypeScript callers, and is
|
|
2033
|
+
* logged with a warning and skipped at runtime, so a typo narrows the panel
|
|
2034
|
+
* rather than silently restoring every setting.
|
|
2035
|
+
*
|
|
2036
|
+
* Presentation only. Hiding a setting never changes its value: whatever the
|
|
2037
|
+
* loaded content carries keeps rendering and keeps round-tripping through
|
|
2038
|
+
* `getContent()`. Set the values you hide from the content you hand the
|
|
2039
|
+
* editor — `init({ content })`, or your own `templates.load`.
|
|
2040
|
+
*/
|
|
2041
|
+
fields?: boolean | Array<keyof TemplateSettings>;
|
|
2042
|
+
}
|
|
429
2043
|
|
|
430
|
-
|
|
2044
|
+
/**
|
|
2045
|
+
* Configuration and outward notifications for the template lifecycle. Every
|
|
2046
|
+
* member is optional.
|
|
2047
|
+
*
|
|
2048
|
+
* Separate from the storage methods so `initCloud()` can accept this half alone:
|
|
2049
|
+
* Cloud owns template storage, because the id it issues is the join key for
|
|
2050
|
+
* collaboration, version history, comments, AI rewrite, scoring and the
|
|
2051
|
+
* server-side export. {@link TemplatesProvider} extends this, so one object
|
|
2052
|
+
* satisfies both entry points and moving to Cloud edits nothing.
|
|
2053
|
+
*/
|
|
2054
|
+
export declare interface TemplatesOptions {
|
|
2055
|
+
/**
|
|
2056
|
+
* Save automatically, debounced, after the user stops editing.
|
|
2057
|
+
*
|
|
2058
|
+
* Lives here because it is meaningless without somewhere to save to. The
|
|
2059
|
+
* cadence is `changeDebounce` at the config root, since one timer drives both
|
|
2060
|
+
* this and the `onChange` notification, and `onChange` works with no provider.
|
|
2061
|
+
*
|
|
2062
|
+
* Defaults to off for `init()` and on for `initCloud()` — a Cloud session
|
|
2063
|
+
* always has a store.
|
|
2064
|
+
*/
|
|
2065
|
+
autoSave?: boolean;
|
|
2066
|
+
/**
|
|
2067
|
+
* Warn before the tab closes with unsaved changes. Defaults to on.
|
|
2068
|
+
*
|
|
2069
|
+
* Cannot cover client-side route changes; `onDirtyChange` at the config root
|
|
2070
|
+
* is what guards a router, and it fires with or without a provider.
|
|
2071
|
+
*/
|
|
2072
|
+
unsavedChangesGuard?: boolean;
|
|
2073
|
+
/**
|
|
2074
|
+
* Render the inline template-name field in the header. Defaults to on.
|
|
2075
|
+
*
|
|
2076
|
+
* `false` hides the field and nothing else: `create({ name })`, `setName()`
|
|
2077
|
+
* and the `name` in each save patch keep working, so your own chrome can still
|
|
2078
|
+
* manage names.
|
|
2079
|
+
*/
|
|
2080
|
+
nameField?: boolean;
|
|
2081
|
+
/**
|
|
2082
|
+
* A save completed, with `isDirty` cleared and `isSaving` false — so a
|
|
2083
|
+
* handler may navigate without tripping the consumer's own unsaved-changes
|
|
2084
|
+
* guard. Never fires for a failed save; that reaches `onError`.
|
|
2085
|
+
*
|
|
2086
|
+
* `template` is the provider's response to `save()` as returned, not
|
|
2087
|
+
* necessarily what got adopted: a rename that lands mid-flight keeps the
|
|
2088
|
+
* local name, and under a concurrent `load()` it may describe a template
|
|
2089
|
+
* that is no longer open.
|
|
2090
|
+
*/
|
|
2091
|
+
onSaved?: (template: Template, meta: {
|
|
2092
|
+
trigger: TemplateSaveTrigger;
|
|
2093
|
+
}) => void;
|
|
2094
|
+
/** A template was created and attached to the editor. */
|
|
2095
|
+
onCreated?: (template: Template) => void;
|
|
2096
|
+
/** A template was fetched and its content put on the canvas. */
|
|
2097
|
+
onLoaded?: (template: Template) => void;
|
|
2098
|
+
}
|
|
431
2099
|
|
|
432
|
-
|
|
2100
|
+
/**
|
|
2101
|
+
* Storage contract for the template itself — the save/load lifecycle around
|
|
2102
|
+
* whatever the editor is editing.
|
|
2103
|
+
*
|
|
2104
|
+
* Deliberately **not** CRUD: there is no `list` and no `delete`, and the editor
|
|
2105
|
+
* has no template browser. Choosing *which* template to open belongs to your
|
|
2106
|
+
* own application, which then hands the id to `editor.load(id)`.
|
|
2107
|
+
*
|
|
2108
|
+
* Pass an implementation as `templates` to `init()`. When omitted, the header's
|
|
2109
|
+
* name field, save button and save-status indicator do not render, and
|
|
2110
|
+
* `create()` / `load()` / `save()` reject with an explanatory error.
|
|
2111
|
+
*
|
|
2112
|
+
* Every method may reject; the editor reports the failure through `onError`,
|
|
2113
|
+
* surfaces it in the header, and leaves its state untouched — nothing is marked
|
|
2114
|
+
* saved that wasn't.
|
|
2115
|
+
*
|
|
2116
|
+
* **Each mutation can be turned off by passing `false` instead of a function**,
|
|
2117
|
+
* mirroring `SavedBlocksProvider`. They are required rather than optional
|
|
2118
|
+
* precisely so that disabling is a decision you state, never something you get
|
|
2119
|
+
* by forgetting a method. `save: false` yields a genuine read-only mode (a
|
|
2120
|
+
* template still loads and can be edited locally, there is simply nothing to
|
|
2121
|
+
* persist); `create: false` yields a no-new-templates mode.
|
|
2122
|
+
*
|
|
2123
|
+
* ```ts
|
|
2124
|
+
* const provider: TemplatesProvider = {
|
|
2125
|
+
* load: (id) => fetch(`/api/templates/${id}`).then((r) => r.json()),
|
|
2126
|
+
* create: (input) =>
|
|
2127
|
+
* fetch("/api/templates", {
|
|
2128
|
+
* method: "POST",
|
|
2129
|
+
* headers: { "Content-Type": "application/json" },
|
|
2130
|
+
* body: JSON.stringify(input),
|
|
2131
|
+
* }).then((r) => r.json()),
|
|
2132
|
+
* save: (id, patch) =>
|
|
2133
|
+
* fetch(`/api/templates/${id}`, {
|
|
2134
|
+
* method: "PATCH",
|
|
2135
|
+
* headers: { "Content-Type": "application/json" },
|
|
2136
|
+
* body: JSON.stringify(patch),
|
|
2137
|
+
* }).then((r) => r.json()),
|
|
2138
|
+
* };
|
|
2139
|
+
* ```
|
|
2140
|
+
*/
|
|
2141
|
+
export declare interface TemplatesProvider extends TemplatesOptions {
|
|
2142
|
+
/**
|
|
2143
|
+
* Fetch a template by id. The one method that cannot be disabled: without it
|
|
2144
|
+
* there is nothing to open.
|
|
2145
|
+
*/
|
|
2146
|
+
load(id: string): Promise<Template>;
|
|
2147
|
+
/**
|
|
2148
|
+
* Persist a new template and return it with its store-assigned `id`, or
|
|
2149
|
+
* `false` to disable creation entirely.
|
|
2150
|
+
*/
|
|
2151
|
+
create: false | ((input: {
|
|
2152
|
+
name?: string;
|
|
2153
|
+
content: TemplateContent;
|
|
2154
|
+
}) => Promise<Template>);
|
|
2155
|
+
/**
|
|
2156
|
+
* Apply a partial update to an existing template and return the stored
|
|
2157
|
+
* result, or `false` to disable saving entirely — the editor then hides both
|
|
2158
|
+
* the save button and the save-status indicator.
|
|
2159
|
+
*/
|
|
2160
|
+
save: false | ((id: string, patch: TemplatePatch) => Promise<Template>);
|
|
2161
|
+
}
|
|
433
2162
|
|
|
434
|
-
|
|
2163
|
+
/**
|
|
2164
|
+
* One recorded version of a template.
|
|
2165
|
+
*
|
|
2166
|
+
* Called a *version* rather than a snapshot deliberately: "snapshot" is an
|
|
2167
|
+
* implementation word, and it collides with the editor's undo/redo history,
|
|
2168
|
+
* which is a different thing entirely (in-session, unsaved, per-keystroke).
|
|
2169
|
+
*/
|
|
2170
|
+
declare interface TemplateVersion {
|
|
2171
|
+
/** Store-assigned. The editor never generates one. */
|
|
2172
|
+
id: string;
|
|
2173
|
+
/** ISO 8601. Drives the relative-time label in the history dropdown. */
|
|
2174
|
+
createdAt: string;
|
|
2175
|
+
/**
|
|
2176
|
+
* Recorded by a save rather than by a person. Drives the "auto" badge, and is
|
|
2177
|
+
* where a future filter would key off.
|
|
2178
|
+
*/
|
|
2179
|
+
isAutomatic?: boolean;
|
|
2180
|
+
/** Short human label, when your store lets someone name a version. */
|
|
2181
|
+
label?: string;
|
|
2182
|
+
author?: {
|
|
2183
|
+
id?: string;
|
|
2184
|
+
name?: string;
|
|
2185
|
+
};
|
|
2186
|
+
/**
|
|
2187
|
+
* Optional eager content — a **cache hint**, never an alternative to
|
|
2188
|
+
* {@link VersionHistoryProvider.get}.
|
|
2189
|
+
*
|
|
2190
|
+
* When present the editor previews this version instantly and never calls
|
|
2191
|
+
* `get` for it. Evaluated **per entry**, so a provider may hydrate the recent
|
|
2192
|
+
* versions and omit the older ones.
|
|
2193
|
+
*
|
|
2194
|
+
* This exists because scrubbing through history is synchronous: once the
|
|
2195
|
+
* preview is open, stepping to another version swaps the canvas in the same
|
|
2196
|
+
* tick. A provider that eager-loads keeps that; one that omits `content` pays
|
|
2197
|
+
* a single round-trip the first time each version is visited, and the editor
|
|
2198
|
+
* caches it thereafter.
|
|
2199
|
+
*/
|
|
2200
|
+
content?: TemplateContent;
|
|
2201
|
+
}
|
|
435
2202
|
|
|
436
2203
|
/**
|
|
437
2204
|
* `initCloud()` returns the **same** editor `init()` does.
|
|
@@ -796,6 +2563,12 @@ declare interface TemplaticalEditorBase {
|
|
|
796
2563
|
* enables it for the next block opened, not one already being edited.
|
|
797
2564
|
*/
|
|
798
2565
|
setMergeTags(tags: MergeTag[]): void;
|
|
2566
|
+
/**
|
|
2567
|
+
* Tear this editor down. It unmounts only this instance: once a later
|
|
2568
|
+
* `init()` on the same container has replaced it, calling `unmount()` here
|
|
2569
|
+
* does nothing, so a stale handle never removes the editor that replaced it.
|
|
2570
|
+
* Calling it again after the teardown is also a no-op.
|
|
2571
|
+
*/
|
|
799
2572
|
unmount(): void;
|
|
800
2573
|
/**
|
|
801
2574
|
* Render the current template to MJML.
|
|
@@ -1353,41 +3126,328 @@ export declare interface TemplaticalEditorConfig {
|
|
|
1353
3126
|
lint?: LintOptions;
|
|
1354
3127
|
}
|
|
1355
3128
|
|
|
1356
|
-
|
|
3129
|
+
/**
|
|
3130
|
+
* Configuration and outward notifications for test-email sending. Every
|
|
3131
|
+
* member is optional.
|
|
3132
|
+
*
|
|
3133
|
+
* Separate from `send` so `initCloud()` can accept this half alone: these are
|
|
3134
|
+
* meaningful whichever backend does the sending, Cloud's own or yours.
|
|
3135
|
+
* {@link TestEmailProvider} extends this, adding only `send`.
|
|
3136
|
+
*/
|
|
3137
|
+
export declare interface TestEmailOptions {
|
|
3138
|
+
/**
|
|
3139
|
+
* Also render the template to MJML and pass it as
|
|
3140
|
+
* {@link TestEmailPayload.mjml}, saving you a `renderToMjml()` call.
|
|
3141
|
+
*
|
|
3142
|
+
* **Requires `@templatical/renderer`**, an optional peer dependency. If it
|
|
3143
|
+
* isn't installed the send still happens with JSON only and one warning is
|
|
3144
|
+
* logged — opting in never breaks sending. A *rendering* failure is different:
|
|
3145
|
+
* that fails the send, because it means the template itself is broken and
|
|
3146
|
+
* silently sending without the MJML would hide it.
|
|
3147
|
+
*/
|
|
3148
|
+
includeMjml?: boolean;
|
|
3149
|
+
/**
|
|
3150
|
+
* Restrict who may be sent to:
|
|
3151
|
+
*
|
|
3152
|
+
* - **omitted** — the dialog accepts free text, validated for shape only;
|
|
3153
|
+
* - **one entry** — a read-only field, pre-filled with it;
|
|
3154
|
+
* - **several** — a picker of exactly those addresses;
|
|
3155
|
+
* - **empty array** — nobody may be sent to, so the feature reports itself
|
|
3156
|
+
* unavailable and no trigger renders. `[]` is read as a decision, not as
|
|
3157
|
+
* "unset".
|
|
3158
|
+
*
|
|
3159
|
+
* A picker constraint, **not a security boundary**: this array lives in the
|
|
3160
|
+
* user's browser and is trivially edited there. Validate the recipient on your
|
|
3161
|
+
* server regardless of what the dialog offered.
|
|
3162
|
+
*/
|
|
3163
|
+
allowedRecipients?: string[];
|
|
3164
|
+
/**
|
|
3165
|
+
* Pre-fills the recipient field. Ignored when it isn't in
|
|
3166
|
+
* {@link allowedRecipients}.
|
|
3167
|
+
*/
|
|
3168
|
+
defaultRecipient?: string;
|
|
3169
|
+
/**
|
|
3170
|
+
* A test send completed. Fires once `send` resolves, with the same payload
|
|
3171
|
+
* it was given — never for a rejected send, since that surfaces through the
|
|
3172
|
+
* dialog's own inline error instead.
|
|
3173
|
+
*/
|
|
3174
|
+
onSent?: (payload: TestEmailPayload) => void;
|
|
3175
|
+
}
|
|
3176
|
+
|
|
3177
|
+
/**
|
|
3178
|
+
* What a {@link TestEmailProvider} receives when the user asks to send a test.
|
|
3179
|
+
*/
|
|
3180
|
+
export declare interface TestEmailPayload {
|
|
3181
|
+
recipient: string;
|
|
3182
|
+
/**
|
|
3183
|
+
* The editor's current content, exactly as `getContent()` would return it.
|
|
3184
|
+
* Always present.
|
|
3185
|
+
*/
|
|
3186
|
+
content: TemplateContent;
|
|
3187
|
+
/**
|
|
3188
|
+
* The template rendered to MJML.
|
|
3189
|
+
*
|
|
3190
|
+
* Present only when {@link TestEmailOptions.includeMjml} is set **and**
|
|
3191
|
+
* `@templatical/renderer` resolved. Always guard for absence: opting in
|
|
3192
|
+
* without the renderer installed still sends, just without this field (the
|
|
3193
|
+
* editor logs one warning naming the package).
|
|
3194
|
+
*
|
|
3195
|
+
* You still have to compile MJML to HTML — the editor never does, and
|
|
3196
|
+
* deliberately doesn't bundle a compiler.
|
|
3197
|
+
*/
|
|
3198
|
+
mjml?: string;
|
|
3199
|
+
/**
|
|
3200
|
+
* Echo of {@link TestEmailOptions.allowedRecipients}, present only when one
|
|
3201
|
+
* was configured.
|
|
3202
|
+
*
|
|
3203
|
+
* **Untrusted.** It is read out of the browser and carries no signature, so it
|
|
3204
|
+
* is not authoritative about anything. Two things it is genuinely useful for:
|
|
3205
|
+
* keeping one `send` implementation portable between your own backend and
|
|
3206
|
+
* Templatical Cloud, and comparing it against `recipient` server-side — a
|
|
3207
|
+
* mismatch means the client was tampered with or is buggy, which is worth
|
|
3208
|
+
* logging. Never treat it *as* the allowlist; that list belongs on your
|
|
3209
|
+
* server.
|
|
3210
|
+
*/
|
|
3211
|
+
allowedRecipients?: string[];
|
|
3212
|
+
}
|
|
1357
3213
|
|
|
1358
|
-
|
|
3214
|
+
/**
|
|
3215
|
+
* Sending contract for test emails. Implement it to let users send a test of the
|
|
3216
|
+
* template they're editing through your own infrastructure — the editor owns the
|
|
3217
|
+
* trigger, the dialog, recipient validation and the sending/success/error
|
|
3218
|
+
* states; you own delivery.
|
|
3219
|
+
*
|
|
3220
|
+
* Pass an implementation as `testEmail` to `init()`. When omitted the feature
|
|
3221
|
+
* stays off entirely and no trigger renders.
|
|
3222
|
+
*
|
|
3223
|
+
* ```ts
|
|
3224
|
+
* const provider: TestEmailProvider = {
|
|
3225
|
+
* send: ({ recipient, content }) =>
|
|
3226
|
+
* fetch("/api/test-email", {
|
|
3227
|
+
* method: "POST",
|
|
3228
|
+
* headers: { "Content-Type": "application/json" },
|
|
3229
|
+
* body: JSON.stringify({ recipient, content }),
|
|
3230
|
+
* }).then((r) => {
|
|
3231
|
+
* if (!r.ok) throw new Error("Could not send the test email");
|
|
3232
|
+
* }),
|
|
3233
|
+
* };
|
|
3234
|
+
* ```
|
|
3235
|
+
*/
|
|
3236
|
+
export declare interface TestEmailProvider extends TestEmailOptions {
|
|
3237
|
+
/**
|
|
3238
|
+
* Deliver a test of the current template.
|
|
3239
|
+
*
|
|
3240
|
+
* Resolve on success — the dialog confirms, then closes. Reject with a
|
|
3241
|
+
* **user-presentable** message on failure: the dialog renders `error.message`
|
|
3242
|
+
* inline and stays open so the user can retry.
|
|
3243
|
+
*/
|
|
3244
|
+
send(payload: TestEmailPayload): Promise<void>;
|
|
3245
|
+
}
|
|
1359
3246
|
|
|
1360
|
-
export
|
|
3247
|
+
export declare interface ThemeOverrides {
|
|
3248
|
+
bg?: string;
|
|
3249
|
+
bgElevated?: string;
|
|
3250
|
+
bgHover?: string;
|
|
3251
|
+
bgActive?: string;
|
|
3252
|
+
border?: string;
|
|
3253
|
+
borderLight?: string;
|
|
3254
|
+
text?: string;
|
|
3255
|
+
textMuted?: string;
|
|
3256
|
+
textDim?: string;
|
|
3257
|
+
primary?: string;
|
|
3258
|
+
primaryHover?: string;
|
|
3259
|
+
primaryLight?: string;
|
|
3260
|
+
secondary?: string;
|
|
3261
|
+
secondaryHover?: string;
|
|
3262
|
+
secondaryLight?: string;
|
|
3263
|
+
success?: string;
|
|
3264
|
+
successLight?: string;
|
|
3265
|
+
warning?: string;
|
|
3266
|
+
warningLight?: string;
|
|
3267
|
+
danger?: string;
|
|
3268
|
+
dangerLight?: string;
|
|
3269
|
+
canvasBg?: string;
|
|
3270
|
+
dark?: Omit<ThemeOverrides, "dark">;
|
|
3271
|
+
}
|
|
1361
3272
|
|
|
1362
|
-
|
|
3273
|
+
declare interface TitleBlock extends BaseBlock {
|
|
3274
|
+
type: "title";
|
|
3275
|
+
content: string;
|
|
3276
|
+
level: HeadingLevel;
|
|
3277
|
+
/** Text color. Unset = inherit the document-level `textColor`. */
|
|
3278
|
+
color?: string;
|
|
3279
|
+
textAlign: "left" | "center" | "right";
|
|
3280
|
+
fontFamily?: string;
|
|
3281
|
+
}
|
|
1363
3282
|
|
|
1364
|
-
export
|
|
3283
|
+
export declare type UiTheme = "light" | "dark" | "auto";
|
|
1365
3284
|
|
|
1366
3285
|
/**
|
|
1367
3286
|
* Unmount the most-recently-created OSS editor. Single-instance legacy
|
|
1368
3287
|
* API — callers managing multiple editors should use `instance.unmount()`
|
|
1369
|
-
* from each returned object, which
|
|
3288
|
+
* from each returned object, which tears down only that instance.
|
|
1370
3289
|
*/
|
|
1371
3290
|
export declare function unmount(): void;
|
|
1372
3291
|
|
|
1373
|
-
|
|
3292
|
+
/** Throw if `layout` does not contain exactly one legal slot. */
|
|
3293
|
+
export declare function validateLayout(layout: TemplateContent): void;
|
|
3294
|
+
|
|
3295
|
+
/**
|
|
3296
|
+
* Page request for {@link VersionHistoryProvider.list}.
|
|
3297
|
+
*
|
|
3298
|
+
* The editor always calls `list` bare — it loads one page and renders it. Both
|
|
3299
|
+
* fields exist for headless callers and for providers that page their own
|
|
3300
|
+
* storage; a provider free to return everything at once may ignore them.
|
|
3301
|
+
*/
|
|
3302
|
+
declare interface VersionHistoryListParams {
|
|
3303
|
+
/** Maximum entries to return. A provider may return fewer, never more. */
|
|
3304
|
+
limit?: number;
|
|
3305
|
+
/**
|
|
3306
|
+
* Opaque cursor taken from a previous result's
|
|
3307
|
+
* {@link VersionHistoryListResult.nextCursor}.
|
|
3308
|
+
*/
|
|
3309
|
+
cursor?: string;
|
|
3310
|
+
}
|
|
3311
|
+
|
|
3312
|
+
/**
|
|
3313
|
+
* What {@link VersionHistoryProvider.list} resolves to.
|
|
3314
|
+
*
|
|
3315
|
+
* An envelope rather than a bare array **so that adding pagination never breaks
|
|
3316
|
+
* an implementation**: a cursor has somewhere to live from day one. Reserving
|
|
3317
|
+
* only the params object would have solved the request side and left the
|
|
3318
|
+
* response side needing a breaking change.
|
|
3319
|
+
*/
|
|
3320
|
+
declare interface VersionHistoryListResult {
|
|
3321
|
+
/**
|
|
3322
|
+
* The versions to offer, newest first. The editor renders this order verbatim
|
|
3323
|
+
* and never re-sorts — ordering is your store's call.
|
|
3324
|
+
*/
|
|
3325
|
+
versions: TemplateVersion[];
|
|
3326
|
+
/**
|
|
3327
|
+
* Cursor for the next page, or absent when this is the last one. Opaque to
|
|
3328
|
+
* the editor: pass it back as {@link VersionHistoryListParams.cursor}.
|
|
3329
|
+
* A provider that returns its whole history at once omits it.
|
|
3330
|
+
*/
|
|
3331
|
+
nextCursor?: string;
|
|
3332
|
+
}
|
|
3333
|
+
|
|
3334
|
+
/**
|
|
3335
|
+
* Outward notifications about a template's version history. Every member is
|
|
3336
|
+
* optional.
|
|
3337
|
+
*
|
|
3338
|
+
* Separate from the storage methods so `initCloud()` can accept this half
|
|
3339
|
+
* alone: a version is keyed to a template id Cloud issued, so Cloud owns
|
|
3340
|
+
* version-history storage. {@link VersionHistoryProvider} extends this, so one
|
|
3341
|
+
* object satisfies both entry points.
|
|
3342
|
+
*/
|
|
3343
|
+
export declare interface VersionHistoryOptions {
|
|
3344
|
+
/**
|
|
3345
|
+
* A version was recorded through {@link VersionHistoryProvider.create}. Not
|
|
3346
|
+
* called for a version your `save` implementation records automatically —
|
|
3347
|
+
* that happens inside your own backend, outside anything the editor can see.
|
|
3348
|
+
*/
|
|
3349
|
+
onCreated?: (version: TemplateVersion) => void;
|
|
3350
|
+
/**
|
|
3351
|
+
* A past version was made current. Takes the **resulting `Template`** that
|
|
3352
|
+
* {@link VersionHistoryProvider.restore} resolves to, not the
|
|
3353
|
+
* {@link TemplateVersion} that was restored from.
|
|
3354
|
+
*/
|
|
3355
|
+
onRestored?: (template: Template) => void;
|
|
3356
|
+
}
|
|
1374
3357
|
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
3358
|
+
/**
|
|
3359
|
+
* Storage contract for a template's version history.
|
|
3360
|
+
*
|
|
3361
|
+
* Pass an implementation as `versionHistory` to `init()` or `initCloud()`. With
|
|
3362
|
+
* no provider the history control does not render and none of its UI is
|
|
3363
|
+
* downloaded.
|
|
3364
|
+
*
|
|
3365
|
+
* **The editor never creates versions on its own.** Whoever implements
|
|
3366
|
+
* `TemplatesProvider.save` decides whether a save also records a version, which
|
|
3367
|
+
* keeps throttling, retention and dedupe policy on the side that pays for the
|
|
3368
|
+
* storage. {@link create} is for versions a *person* asks for.
|
|
3369
|
+
*
|
|
3370
|
+
* ```ts
|
|
3371
|
+
* const provider: VersionHistoryProvider = {
|
|
3372
|
+
* list: (templateId) =>
|
|
3373
|
+
* fetch(`/api/templates/${templateId}/versions`).then((r) => r.json()),
|
|
3374
|
+
* get: (templateId, versionId) =>
|
|
3375
|
+
* fetch(`/api/templates/${templateId}/versions/${versionId}`)
|
|
3376
|
+
* .then((r) => r.json())
|
|
3377
|
+
* .then((v) => v.content),
|
|
3378
|
+
* create: false,
|
|
3379
|
+
* // No atomic restore endpoint? Compose it: read the old content, save it.
|
|
3380
|
+
* restore: async (templateId, versionId) => {
|
|
3381
|
+
* const content = await provider.get(templateId, versionId);
|
|
3382
|
+
* return templates.save(templateId, { content });
|
|
3383
|
+
* },
|
|
3384
|
+
* };
|
|
3385
|
+
* ```
|
|
3386
|
+
*/
|
|
3387
|
+
declare interface VersionHistoryProvider extends VersionHistoryOptions {
|
|
3388
|
+
/** One page of versions. See {@link VersionHistoryListResult}. */
|
|
3389
|
+
list(templateId: string, params?: VersionHistoryListParams): Promise<VersionHistoryListResult>;
|
|
3390
|
+
/**
|
|
3391
|
+
* Fetch one version's content. **The operation, and always required** — the
|
|
3392
|
+
* editor must always be able to obtain a version's content.
|
|
3393
|
+
* {@link TemplateVersion.content} is the optimisation, and optimisations don't
|
|
3394
|
+
* get to be mandatory.
|
|
3395
|
+
*/
|
|
3396
|
+
get(templateId: string, versionId: string): Promise<TemplateContent>;
|
|
3397
|
+
/**
|
|
3398
|
+
* Record the current content as a version on demand, or `false` to disable
|
|
3399
|
+
* it — the editor then offers no way to create one by hand, and the history
|
|
3400
|
+
* is whatever `save` recorded.
|
|
3401
|
+
*
|
|
3402
|
+
* `false`-able and required rather than optional, mirroring
|
|
3403
|
+
* `SavedBlocksProvider` and `TemplatesProvider`: disabling is a decision you
|
|
3404
|
+
* state, never something you get by forgetting a method.
|
|
3405
|
+
*/
|
|
3406
|
+
create: false | ((templateId: string, content: TemplateContent, meta?: {
|
|
3407
|
+
label?: string;
|
|
3408
|
+
}) => Promise<TemplateVersion>);
|
|
3409
|
+
/**
|
|
3410
|
+
* Make a past version current and return the resulting template, or `false`
|
|
3411
|
+
* to make history read-only — versions can then be browsed and previewed, but
|
|
3412
|
+
* the Restore action does not render.
|
|
3413
|
+
*
|
|
3414
|
+
* **History is append-only:** a restore adds an entry rather than rewriting
|
|
3415
|
+
* one, so undo stays coherent and two backends can't disagree about what
|
|
3416
|
+
* history looks like afterwards. A store with no atomic endpoint composes it
|
|
3417
|
+
* in one line — `get` the old content, then `save` it — accepting two
|
|
3418
|
+
* round-trips and a narrower failure window.
|
|
3419
|
+
*/
|
|
3420
|
+
restore: false | ((templateId: string, versionId: string) => Promise<Template>);
|
|
1385
3421
|
}
|
|
1386
3422
|
|
|
1387
|
-
|
|
3423
|
+
declare interface VideoBlock extends BaseBlock {
|
|
3424
|
+
type: "video";
|
|
3425
|
+
url: string;
|
|
3426
|
+
openInNewTab?: boolean;
|
|
3427
|
+
thumbnailUrl: string;
|
|
3428
|
+
alt: string;
|
|
3429
|
+
width: number | "full";
|
|
3430
|
+
/**
|
|
3431
|
+
* Height in pixels for the thumbnail. Absent means the height is derived from
|
|
3432
|
+
* the width, so the thumbnail keeps its aspect ratio — setting both stretches
|
|
3433
|
+
* it, since email clients don't support `object-fit`.
|
|
3434
|
+
*/
|
|
3435
|
+
height?: number;
|
|
3436
|
+
align: "left" | "center" | "right";
|
|
3437
|
+
placeholderUrl?: string;
|
|
3438
|
+
}
|
|
1388
3439
|
|
|
1389
|
-
export
|
|
3440
|
+
export declare type ViewportSize = "desktop" | "mobile";
|
|
1390
3441
|
|
|
1391
|
-
|
|
3442
|
+
/**
|
|
3443
|
+
* Layout-only band rendered as `mj-wrapper`. The block is the band:
|
|
3444
|
+
* `styles.backgroundColor` / `styles.padding` / `borderRadius` map to the
|
|
3445
|
+
* wrapper. Legal in layout only this ship.
|
|
3446
|
+
*/
|
|
3447
|
+
declare interface WrapperBlock extends BaseBlock {
|
|
3448
|
+
type: "wrapper";
|
|
3449
|
+
children: Block[];
|
|
3450
|
+
borderRadius?: number;
|
|
3451
|
+
}
|
|
1392
3452
|
|
|
1393
3453
|
export { }
|