@templatical/editor 0.25.2 → 0.26.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.
Files changed (179) hide show
  1. package/dist/{AiChatSidebar-JLsbPnsH.js → AiChatSidebar-c-3yyjN1.js} +8 -8
  2. package/dist/{AiFeatureMenu-BvA-lViN.js → AiFeatureMenu-B-COyG7k.js} +4 -4
  3. package/dist/{BlockIssueBadge-Cf0-GC3o.js → BlockIssueBadge-Buk3_6QF.js} +2 -2
  4. package/dist/{BlockPreviewCanvas-CnJIM5TO.js → BlockPreviewCanvas-CJPHGI93.js} +1 -1
  5. package/dist/{CloudEditor-Caq7z4-7.js → CloudEditor-BdSQ8hfv.js} +19 -19
  6. package/dist/{CollaboratorBar-BFz4LgA0.js → CollaboratorBar-mGoi6yye.js} +2 -2
  7. package/dist/{ColorPicker-B5xXWWX5.js → ColorPicker-ClETsjA3.js} +1 -1
  8. package/dist/{CommentsSidebar-CGihWwn9.js → CommentsSidebar-CdRCCSDD.js} +10 -10
  9. package/dist/{CountdownToolbar-DpNCZAUI.js → CountdownToolbar-CBE9wRaV.js} +2 -2
  10. package/dist/{DesignReferenceSidebar-CiIHHiil.js → DesignReferenceSidebar-CgYkySre.js} +6 -6
  11. package/dist/{IssuesPanel-CUkmAC_S.js → IssuesPanel-Do7RLXsP.js} +5 -5
  12. package/dist/{MergeTagModeToggle-C6nS3qri.js → MergeTagModeToggle-hAJgIKhw.js} +3 -3
  13. package/dist/{MergeTagSuggestion-98fAcuM-.js → MergeTagSuggestion-BJ43ihWR.js} +9 -9
  14. package/dist/{NumberWithSuffix-DB-1GNvx.js → NumberWithSuffix-D37ZWiZh.js} +1 -1
  15. package/dist/{ParagraphEditor-NgW-T9YV.js → ParagraphEditor-DBG63S70.js} +19 -19
  16. package/dist/{RichTextEditorContent-CN7OVR6u.js → RichTextEditorContent-fbYJXAio.js} +3 -3
  17. package/dist/{SaveBlockDialog-lgcmhAsO.js → SaveBlockDialog-Dq9UVihi.js} +6 -6
  18. package/dist/{SavedBlocksBrowserModal-3u247V2z.js → SavedBlocksBrowserModal-CtFG4zeV.js} +7 -7
  19. package/dist/{SavedBlocksPanels-Dh6dABJt.js → SavedBlocksPanels-DNVdso_W.js} +1 -1
  20. package/dist/{SavedBlocksPickBar-Bo82D3Qy.js → SavedBlocksPickBar-Cv4dIpP-.js} +2 -2
  21. package/dist/{SnapshotHistory-Dsb1i40w.js → SnapshotHistory-DsOtMHOV.js} +11 -11
  22. package/dist/{TemplateScoringPanel-Bzd_YSag.js → TemplateScoringPanel-BZUsb3FD.js} +10 -10
  23. package/dist/{TestEmailModal-DvqDTOcO.js → TestEmailModal-CIwqtGfr.js} +7 -7
  24. package/dist/{TestEmailPanel-BXoVnAqu.js → TestEmailPanel-l6LDmBGM.js} +1 -1
  25. package/dist/{TitleEditor-DWSi998C.js → TitleEditor-imoBUynh.js} +8 -8
  26. package/dist/{TplModal-DKn0h2qe.js → TplModal-DDDHcoir.js} +1 -1
  27. package/dist/{blockTypeIcons-DKlza6aR.js → blockTypeIcons-CfLN4A6t.js} +2 -2
  28. package/dist/bundle-stats.json +6 -6
  29. package/dist/cdn/chunks/{AiChatSidebar-BY1Iy-e8.js → AiChatSidebar-BHqNxNcX.js} +35 -35
  30. package/dist/cdn/chunks/{AiChatSidebar-BY1Iy-e8.js.map → AiChatSidebar-BHqNxNcX.js.map} +1 -1
  31. package/dist/cdn/chunks/{AiFeatureMenu-BS4Rek6j.js → AiFeatureMenu-D2AIDwEG.js} +11 -11
  32. package/dist/cdn/chunks/{AiFeatureMenu-BS4Rek6j.js.map → AiFeatureMenu-D2AIDwEG.js.map} +1 -1
  33. package/dist/cdn/chunks/{BlockIssueBadge-BcIQUe_Q.js → BlockIssueBadge-BEEY9o2o.js} +5 -5
  34. package/dist/cdn/chunks/{BlockIssueBadge-BcIQUe_Q.js.map → BlockIssueBadge-BEEY9o2o.js.map} +1 -1
  35. package/dist/cdn/chunks/{BlockPreviewCanvas-oND1iXLL.js → BlockPreviewCanvas-DH5ULGkw.js} +22 -22
  36. package/dist/cdn/chunks/{BlockPreviewCanvas-oND1iXLL.js.map → BlockPreviewCanvas-DH5ULGkw.js.map} +1 -1
  37. package/dist/cdn/chunks/{CloudEditor-BaqiFbf0.js → CloudEditor-C0nB1JfD.js} +65 -65
  38. package/dist/cdn/chunks/{CloudEditor-BaqiFbf0.js.map → CloudEditor-C0nB1JfD.js.map} +1 -1
  39. package/dist/cdn/chunks/{CollaboratorBar-DF02nTwF.js → CollaboratorBar-f6Ni7KNH.js} +12 -12
  40. package/dist/cdn/chunks/{CollaboratorBar-DF02nTwF.js.map → CollaboratorBar-f6Ni7KNH.js.map} +1 -1
  41. package/dist/cdn/chunks/{ColorPicker-BXH44v2H.js → ColorPicker-W_UyiVGc.js} +131 -131
  42. package/dist/cdn/chunks/{ColorPicker-BXH44v2H.js.map → ColorPicker-W_UyiVGc.js.map} +1 -1
  43. package/dist/cdn/chunks/{CommentsSidebar-B9bWvvbl.js → CommentsSidebar-BLRZRSSC.js} +112 -112
  44. package/dist/cdn/chunks/{CommentsSidebar-B9bWvvbl.js.map → CommentsSidebar-BLRZRSSC.js.map} +1 -1
  45. package/dist/cdn/chunks/{CountdownBlock-BTpRd0Hn.js → CountdownBlock-vLTkTI9e.js} +11 -11
  46. package/dist/cdn/chunks/{CountdownBlock-BTpRd0Hn.js.map → CountdownBlock-vLTkTI9e.js.map} +1 -1
  47. package/dist/cdn/chunks/{CountdownToolbar-CW10rbWt.js → CountdownToolbar-DPjcgcVR.js} +41 -41
  48. package/dist/cdn/chunks/{CountdownToolbar-CW10rbWt.js.map → CountdownToolbar-DPjcgcVR.js.map} +1 -1
  49. package/dist/cdn/chunks/{DesignReferenceSidebar-BXjH47QC.js → DesignReferenceSidebar-DBoEBKxQ.js} +28 -28
  50. package/dist/cdn/chunks/{DesignReferenceSidebar-BXjH47QC.js.map → DesignReferenceSidebar-DBoEBKxQ.js.map} +1 -1
  51. package/dist/cdn/chunks/{IssuesPanel-DStHCHfr.js → IssuesPanel-DKTS4QVb.js} +32 -32
  52. package/dist/cdn/chunks/{IssuesPanel-DStHCHfr.js.map → IssuesPanel-DKTS4QVb.js.map} +1 -1
  53. package/dist/cdn/chunks/{LoadingTrack-DQowK3MV.js → LoadingTrack-C52ZGL5q.js} +2 -2
  54. package/dist/cdn/chunks/{LoadingTrack-DQowK3MV.js.map → LoadingTrack-C52ZGL5q.js.map} +1 -1
  55. package/dist/cdn/chunks/{MergeTagModeToggle-BCmQg4Qe.js → MergeTagModeToggle-D8iKvz7v.js} +20 -20
  56. package/dist/cdn/chunks/{MergeTagModeToggle-BCmQg4Qe.js.map → MergeTagModeToggle-D8iKvz7v.js.map} +1 -1
  57. package/dist/cdn/chunks/{MergeTagSuggestion-BgExdloL.js → MergeTagSuggestion-Dn8dExNa.js} +18 -18
  58. package/dist/cdn/chunks/{MergeTagSuggestion-BgExdloL.js.map → MergeTagSuggestion-Dn8dExNa.js.map} +1 -1
  59. package/dist/cdn/chunks/{NumberWithSuffix-BANVbpT0.js → NumberWithSuffix-BXrRdfq1.js} +22 -22
  60. package/dist/cdn/chunks/{NumberWithSuffix-BANVbpT0.js.map → NumberWithSuffix-BXrRdfq1.js.map} +1 -1
  61. package/dist/cdn/chunks/{ParagraphEditor-DqilB4_f.js → ParagraphEditor-Cuc83Blj.js} +43 -43
  62. package/dist/cdn/chunks/{ParagraphEditor-DqilB4_f.js.map → ParagraphEditor-Cuc83Blj.js.map} +1 -1
  63. package/dist/cdn/chunks/{RichTextEditorContent-BeJx4LVH.js → RichTextEditorContent-DtL49Jpo.js} +20 -20
  64. package/dist/cdn/chunks/{RichTextEditorContent-BeJx4LVH.js.map → RichTextEditorContent-DtL49Jpo.js.map} +1 -1
  65. package/dist/cdn/chunks/{SaveBlockDialog-Dm31xRZy.js → SaveBlockDialog-B8KHhVkI.js} +43 -43
  66. package/dist/cdn/chunks/{SaveBlockDialog-Dm31xRZy.js.map → SaveBlockDialog-B8KHhVkI.js.map} +1 -1
  67. package/dist/cdn/chunks/{SavedBlocksBrowserModal-Tp37b4y7.js → SavedBlocksBrowserModal-B-sjsIJv.js} +62 -62
  68. package/dist/cdn/chunks/{SavedBlocksBrowserModal-Tp37b4y7.js.map → SavedBlocksBrowserModal-B-sjsIJv.js.map} +1 -1
  69. package/dist/cdn/chunks/{SavedBlocksPanels-ElerTTa_.js → SavedBlocksPanels-Bb-eDMAh.js} +3 -3
  70. package/dist/cdn/chunks/{SavedBlocksPanels-ElerTTa_.js.map → SavedBlocksPanels-Bb-eDMAh.js.map} +1 -1
  71. package/dist/cdn/chunks/{SavedBlocksPickBar-QHJ-Mjx0.js → SavedBlocksPickBar-GudFfXa4.js} +12 -12
  72. package/dist/cdn/chunks/{SavedBlocksPickBar-QHJ-Mjx0.js.map → SavedBlocksPickBar-GudFfXa4.js.map} +1 -1
  73. package/dist/cdn/chunks/{SnapshotHistory-jogWGLB-.js → SnapshotHistory-Srpzt2na.js} +26 -26
  74. package/dist/cdn/chunks/{SnapshotHistory-jogWGLB-.js.map → SnapshotHistory-Srpzt2na.js.map} +1 -1
  75. package/dist/cdn/chunks/{TemplateScoringPanel-B2XIOTl-.js → TemplateScoringPanel-NucRtAP0.js} +34 -34
  76. package/dist/cdn/chunks/{TemplateScoringPanel-B2XIOTl-.js.map → TemplateScoringPanel-NucRtAP0.js.map} +1 -1
  77. package/dist/cdn/chunks/{TestEmailModal-BcPjQygV.js → TestEmailModal-DLeoJqaJ.js} +31 -31
  78. package/dist/cdn/chunks/{TestEmailModal-BcPjQygV.js.map → TestEmailModal-DLeoJqaJ.js.map} +1 -1
  79. package/dist/cdn/chunks/{TestEmailPanel-1zxRa00s.js → TestEmailPanel-Dr7Dkq2P.js} +3 -3
  80. package/dist/cdn/chunks/{TestEmailPanel-1zxRa00s.js.map → TestEmailPanel-Dr7Dkq2P.js.map} +1 -1
  81. package/dist/cdn/chunks/{TitleEditor-DTrmublj.js → TitleEditor-DzYRr18C.js} +35 -35
  82. package/dist/cdn/chunks/{TitleEditor-DTrmublj.js.map → TitleEditor-DzYRr18C.js.map} +1 -1
  83. package/dist/cdn/chunks/{TplModal-By50gtwa.js → TplModal-byw7-3wg.js} +17 -17
  84. package/dist/cdn/chunks/{TplModal-By50gtwa.js.map → TplModal-byw7-3wg.js.map} +1 -1
  85. package/dist/cdn/chunks/{blockTypeIcons-BhFhHuFt.js → blockTypeIcons-FXlMM3PL.js} +2 -2
  86. package/dist/cdn/chunks/{blockTypeIcons-BhFhHuFt.js.map → blockTypeIcons-FXlMM3PL.js.map} +1 -1
  87. package/dist/cdn/chunks/{cloud-B0f7Ub8f.js → cloud-CuItCnN_.js} +3 -3
  88. package/dist/cdn/chunks/{cloud-B0f7Ub8f.js.map → cloud-CuItCnN_.js.map} +1 -1
  89. package/dist/cdn/chunks/{dist-H51neg3K.js → dist-B2HKGHnc.js} +3 -3
  90. package/dist/cdn/chunks/{dist-H51neg3K.js.map → dist-B2HKGHnc.js.map} +1 -1
  91. package/dist/cdn/chunks/{dist-B4VIlp1g.js → dist-C6z8AjBC.js} +2 -2
  92. package/dist/cdn/chunks/{dist-B4VIlp1g.js.map → dist-C6z8AjBC.js.map} +1 -1
  93. package/dist/cdn/chunks/{draggable-Z4XXmbPC.js → draggable-DCs9qL-b.js} +2 -2
  94. package/dist/cdn/chunks/{draggable-Z4XXmbPC.js.map → draggable-DCs9qL-b.js.map} +1 -1
  95. package/dist/cdn/chunks/{extensions-Dq0D79tq.js → extensions-B7laxHjs.js} +22 -22
  96. package/dist/cdn/chunks/{extensions-Dq0D79tq.js.map → extensions-B7laxHjs.js.map} +1 -1
  97. package/dist/cdn/chunks/{icons-C_kRN0_C.js → icons-CvYN6zrM.js} +8 -8
  98. package/dist/cdn/chunks/{icons-C_kRN0_C.js.map → icons-CvYN6zrM.js.map} +1 -1
  99. package/dist/cdn/chunks/{keys-DsoX52o_.js → keys-Cmc2Yl3D.js} +2 -2
  100. package/dist/cdn/chunks/{keys-DsoX52o_.js.map → keys-Cmc2Yl3D.js.map} +1 -1
  101. package/dist/cdn/chunks/{liquid.browser-DrGys_ek.js → liquid.browser-UlAOKBjI.js} +99 -91
  102. package/dist/cdn/chunks/liquid.browser-UlAOKBjI.js.map +1 -0
  103. package/dist/cdn/chunks/{src-Aea1Vl1H.js → src-BYKYmEOD.js} +7 -6
  104. package/dist/cdn/chunks/src-BYKYmEOD.js.map +1 -0
  105. package/dist/cdn/chunks/{src-_GNJgPcS.js → src-Bi-Qhtfs.js} +2 -2
  106. package/dist/cdn/chunks/{src-_GNJgPcS.js.map → src-Bi-Qhtfs.js.map} +1 -1
  107. package/dist/cdn/chunks/{src-CRvTVX4F.js → src-C0N74uvF.js} +201 -201
  108. package/dist/cdn/chunks/{src-CRvTVX4F.js.map → src-C0N74uvF.js.map} +1 -1
  109. package/dist/cdn/chunks/{src-CZjSXPYq.js → src-EKTTBQ0y.js} +3 -2
  110. package/dist/cdn/chunks/src-EKTTBQ0y.js.map +1 -0
  111. package/dist/cdn/chunks/{styles-znpPfrFd.js → styles-C7s-VtJi.js} +1112 -1094
  112. package/dist/cdn/chunks/styles-C7s-VtJi.js.map +1 -0
  113. package/dist/cdn/chunks/{tiptap-C5vzWlHv.js → tiptap-C44S1Cmh.js} +3480 -2806
  114. package/dist/cdn/chunks/tiptap-C44S1Cmh.js.map +1 -0
  115. package/dist/cdn/chunks/{useAliveFlag-4IDYQRNu.js → useAliveFlag-Dxwgo6P4.js} +2 -2
  116. package/dist/cdn/chunks/{useAliveFlag-4IDYQRNu.js.map → useAliveFlag-Dxwgo6P4.js.map} +1 -1
  117. package/dist/cdn/chunks/{useCloudI18n-D4JpHTPk.js → useCloudI18n-8wkJtuJD.js} +3 -3
  118. package/dist/cdn/chunks/{useCloudI18n-D4JpHTPk.js.map → useCloudI18n-8wkJtuJD.js.map} +1 -1
  119. package/dist/cdn/chunks/{useEditorCore-BMXBmdd8.js → useEditorCore-C_nUXiph.js} +268 -268
  120. package/dist/cdn/chunks/useEditorCore-C_nUXiph.js.map +1 -0
  121. package/dist/cdn/chunks/{useI18n-BvcEYzrI.js → useI18n-UMs0F71x.js} +3 -3
  122. package/dist/cdn/chunks/{useI18n-BvcEYzrI.js.map → useI18n-UMs0F71x.js.map} +1 -1
  123. package/dist/cdn/chunks/{useMergeTag-BSdxMUzg.js → useMergeTag-CPa_9VOq.js} +4 -4
  124. package/dist/cdn/chunks/{useMergeTag-BSdxMUzg.js.map → useMergeTag-CPa_9VOq.js.map} +1 -1
  125. package/dist/cdn/chunks/{usePopoverPosition-ig-71caO.js → usePopoverPosition-cf8Y-ynK.js} +3 -3
  126. package/dist/cdn/chunks/{usePopoverPosition-ig-71caO.js.map → usePopoverPosition-cf8Y-ynK.js.map} +1 -1
  127. package/dist/cdn/editor.js +47 -47
  128. package/dist/cdn/editor.js.map +1 -1
  129. package/dist/{check-COV27dGb.js → check-On4TSac-.js} +1 -1
  130. package/dist/{chevron-down-vsU88vcr.js → chevron-down-69EiBGoB.js} +1 -1
  131. package/dist/{chevron-up-RrZ0Rjn9.js → chevron-up-Dh3MG1Dw.js} +1 -1
  132. package/dist/{circle-alert-B6GlHmmi.js → circle-alert-cilwzafX.js} +1 -1
  133. package/dist/{clock-egOt8zjQ.js → clock-Dt0APH92.js} +1 -1
  134. package/dist/{cloud-D-2StbvU.js → cloud-C7-FhzMc.js} +1 -1
  135. package/dist/{createLucideIcon-D6cqT5Pm.js → createLucideIcon-C8WzBYCv.js} +2 -2
  136. package/dist/dist-6YJAWMuf.js +5 -0
  137. package/dist/dist-8vlm9kfO.js +5 -0
  138. package/dist/{dist-DHwnYYx0.js → dist-BImfm-46.js} +2 -2
  139. package/dist/{dist-DzzTB4eE.js → dist-C08jiemd.js} +7 -7
  140. package/dist/{dist-CqV4XslQ.js → dist-C27SeFL5.js} +15 -15
  141. package/dist/{dist-MwT7T6Xp.js → dist-CYx-E82k.js} +2 -2
  142. package/dist/{dist-CHxPjcxA.js → dist-ChYLhoJ4.js} +2143 -1583
  143. package/dist/{dist-UkG5ZJ1P.js → dist-Cl8J0IES.js} +258 -172
  144. package/dist/{dist-CQ0fVBQ3.js → dist-D5zCZFdU.js} +2 -1
  145. package/dist/{dist-D4GoAr2g.js → dist-DMJRSouD.js} +464 -436
  146. package/dist/{dist-GUZZpDK3.js → dist-DfpeSdmE.js} +2 -2
  147. package/dist/{dist-Du95Y9M3.js → dist-M7m1lYRe.js} +47 -47
  148. package/dist/{dist-vpuiQ7_Q.js → dist-g6cbzhZW.js} +2 -2
  149. package/dist/{extensions-y6KjLr9t.js → extensions-CxwhoG8v.js} +16 -16
  150. package/dist/{image-up-CycES-MQ.js → image-up-Ceggh_R3.js} +1 -1
  151. package/dist/{info-BUdvm046.js → info-DO5odBcZ.js} +1 -1
  152. package/dist/{liquid.browser-Di5zMIEx.js → liquid.browser-B-rGrnrQ.js} +98 -90
  153. package/dist/{list-checks-BAeuuATD.js → list-checks-BSkPKbuI.js} +1 -1
  154. package/dist/{loader-circle-L5yuAx_j.js → loader-circle-bO7o7_HD.js} +1 -1
  155. package/dist/{message-circle-DRdaapVc.js → message-circle-BEkAk65X.js} +1 -1
  156. package/dist/{pencil-BfErVPf_.js → pencil-t6m0EtKL.js} +1 -1
  157. package/dist/{refresh-cw-D0PYatER.js → refresh-cw-CkOYVzK5.js} +1 -1
  158. package/dist/{scan-line-Byi3fJcr.js → scan-line-DwSVj25O.js} +1 -1
  159. package/dist/{send-DuyNexMY.js → send-CWsWN8PQ.js} +1 -1
  160. package/dist/{shield-check-BkkN_1qw.js → shield-check-qje8Kufw.js} +1 -1
  161. package/dist/{sparkles-DAw2sBXk.js → sparkles-DumaWRW-.js} +1 -1
  162. package/dist/{styles-BzwheHH_.js → styles-C_slTGwp.js} +446 -428
  163. package/dist/templatical-editor.js +9 -9
  164. package/dist/{text-align-start-Ba3xJKn6.js → text-align-start-Ir8Ah4mT.js} +1 -1
  165. package/dist/{trash-2-CapEBXBI.js → trash-2-CklAFCmO.js} +1 -1
  166. package/dist/{triangle-alert-52pdw8jI.js → triangle-alert-BBi4n93g.js} +1 -1
  167. package/dist/{upload-oAgigC31.js → upload-sVqqppuv.js} +1 -1
  168. package/dist/{useEditorCore-BhjEMSeE.js → useEditorCore-r4gWzCBA.js} +889 -889
  169. package/dist/{useMergeTag-DTSOQnyr.js → useMergeTag-C-_WxrgL.js} +1 -1
  170. package/dist/{x-DvBgbDuf.js → x-Mby7KlhS.js} +1 -1
  171. package/package.json +22 -22
  172. package/dist/cdn/chunks/liquid.browser-DrGys_ek.js.map +0 -1
  173. package/dist/cdn/chunks/src-Aea1Vl1H.js.map +0 -1
  174. package/dist/cdn/chunks/src-CZjSXPYq.js.map +0 -1
  175. package/dist/cdn/chunks/styles-znpPfrFd.js.map +0 -1
  176. package/dist/cdn/chunks/tiptap-C5vzWlHv.js.map +0 -1
  177. package/dist/cdn/chunks/useEditorCore-BMXBmdd8.js.map +0 -1
  178. package/dist/dist-B56SufAv.js +0 -5
  179. package/dist/dist-BQZkRg05.js +0 -5
@@ -1,12 +1,12 @@
1
- import { H as e, M as t, P as n, T as r, V as i, Z as a, a as ee, ct as o, f as s, g as c, h as l, it as u, m as d, o as te, ot as ne, p as f, u as p, v as m, x as h, y as g, z as re } from "./draggable-Z4XXmbPC.js";
2
- import { u as ie } from "./src-CZjSXPYq.js";
3
- import { P as ae } from "./useEditorCore-BMXBmdd8.js";
4
- import { E as oe, L as se, g as ce, k as le } from "./keys-DsoX52o_.js";
5
- import { t as ue } from "./useI18n-BvcEYzrI.js";
6
- import { Lt as de, it as fe } from "./icons-C_kRN0_C.js";
7
- import { n as pe, t as me } from "./MergeTagModeToggle-BCmQg4Qe.js";
8
- import { t as he } from "./TplModal-By50gtwa.js";
9
- import { t as ge } from "./BlockPreviewCanvas-oND1iXLL.js";
1
+ import { H as e, M as t, P as n, T as r, V as i, Z as a, a as ee, f as o, g as s, h as c, it as l, lt as u, m as d, o as te, p as f, st as ne, u as p, v as m, x as h, y as g, z as re } from "./draggable-DCs9qL-b.js";
2
+ import { u as ie } from "./src-EKTTBQ0y.js";
3
+ import { P as ae } from "./useEditorCore-C_nUXiph.js";
4
+ import { E as oe, L as se, g as ce, k as le } from "./keys-Cmc2Yl3D.js";
5
+ import { t as ue } from "./useI18n-UMs0F71x.js";
6
+ import { Lt as de, it as fe } from "./icons-CvYN6zrM.js";
7
+ import { n as pe, t as me } from "./MergeTagModeToggle-D8iKvz7v.js";
8
+ import { t as he } from "./TplModal-byw7-3wg.js";
9
+ import { t as ge } from "./BlockPreviewCanvas-DH5ULGkw.js";
10
10
  //#region src/utils/validateEmailShape.ts
11
11
  var _ = /[,;<>()[\]\\"'`|/?#@!$%^&*+=~{}]/;
12
12
  function v(e) {
@@ -81,16 +81,16 @@ var y = ["aria-busy"], b = {
81
81
  },
82
82
  isActive: () => R.visible,
83
83
  getRecipient: () => V.value || void 0
84
- }), W = r(le, null), G = a(ie(r(oe, []))), K = s({
84
+ }), W = r(le, null), G = a(ie(r(oe, []))), K = o({
85
85
  get: () => U.isConfigured ? !1 : W?.value ?? G.value,
86
86
  set: (e) => {
87
87
  W ? W.value = e : G.value = e;
88
88
  }
89
- }), Te = s(() => U.content.value.blocks ?? []), q = s(() => R.allowedRecipients !== void 0 && R.allowedRecipients.length > 0), Ee = s(() => R.allowedRecipients?.length === 1);
89
+ }), Te = o(() => U.content.value.blocks ?? []), q = o(() => R.allowedRecipients !== void 0 && R.allowedRecipients.length > 0), Ee = o(() => R.allowedRecipients?.length === 1);
90
90
  re(() => R.visible, (e) => {
91
91
  e && (V.value = R.defaultRecipient ?? R.allowedRecipients?.[0] ?? "");
92
92
  }, { immediate: !0 });
93
- let J = s(() => q.value ? V.value.length > 0 : v(V.value)), Y = s(() => J.value && !R.isSending && !R.justSent), X = s(() => !q.value && V.value.trim().length > 0 && !J.value), Z = s(() => R.error ? R.error.kind === "provider" ? R.error.message : B.testEmail.recipientNotAllowed : null);
93
+ let J = o(() => q.value ? V.value.length > 0 : v(V.value)), Y = o(() => J.value && !R.isSending && !R.justSent), X = o(() => !q.value && V.value.trim().length > 0 && !J.value), Z = o(() => R.error ? R.error.kind === "provider" ? R.error.message : B.testEmail.recipientNotAllowed : null);
94
94
  function Q() {
95
95
  Y.value && z("send", V.value.trim());
96
96
  }
@@ -116,10 +116,10 @@ var y = ["aria-busy"], b = {
116
116
  "box-shadow": "var(--tpl-shadow-xl)"
117
117
  }
118
118
  }, [
119
- f("h3", b, o(u(B).testEmail.title), 1),
119
+ f("h3", b, u(l(B).testEmail.title), 1),
120
120
  f("div", x, [
121
- f("label", S, o(u(B).testEmail.recipientLabel), 1),
122
- Ee.value ? (t(), c("input", {
121
+ f("label", S, u(l(B).testEmail.recipientLabel), 1),
122
+ Ee.value ? (t(), s("input", {
123
123
  key: 0,
124
124
  id: "tpl-test-email-recipient",
125
125
  type: "text",
@@ -127,73 +127,73 @@ var y = ["aria-busy"], b = {
127
127
  disabled: "",
128
128
  "data-testid": "test-email-recipient",
129
129
  class: "tpl:h-9 tpl:w-full tpl:rounded-md tpl:border tpl:px-3 tpl:py-1 tpl:text-sm tpl:opacity-70 tpl:shadow-xs tpl:outline-none tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:text-[var(--tpl-text)]"
130
- }, null, 8, C)) : q.value ? e((t(), c("select", {
130
+ }, null, 8, C)) : q.value ? e((t(), s("select", {
131
131
  key: 1,
132
132
  id: "tpl-test-email-recipient",
133
133
  "onUpdate:modelValue": a[0] ||= (e) => V.value = e,
134
134
  disabled: h.isSending || h.justSent,
135
135
  "data-testid": "test-email-recipient",
136
136
  class: "tpl:h-9 tpl:w-full tpl:rounded-md tpl:border tpl:px-3 tpl:py-1 tpl:text-sm tpl:shadow-xs tpl:outline-none tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:text-[var(--tpl-text)]"
137
- }, [(t(!0), c(p, null, n(h.allowedRecipients, (e) => (t(), c("option", {
137
+ }, [(t(!0), s(p, null, n(h.allowedRecipients, (e) => (t(), s("option", {
138
138
  key: e,
139
139
  value: e
140
- }, o(e), 9, T))), 128))], 8, w)), [[ee, V.value]]) : e((t(), c("input", {
140
+ }, u(e), 9, T))), 128))], 8, w)), [[ee, V.value]]) : e((t(), s("input", {
141
141
  key: 2,
142
142
  id: "tpl-test-email-recipient",
143
143
  "onUpdate:modelValue": a[1] ||= (e) => V.value = e,
144
144
  type: "email",
145
145
  autocomplete: "email",
146
146
  spellcheck: "false",
147
- placeholder: u(B).testEmail.recipientPlaceholder,
147
+ placeholder: l(B).testEmail.recipientPlaceholder,
148
148
  disabled: h.isSending || h.justSent,
149
149
  "data-testid": "test-email-recipient",
150
150
  class: "tpl:h-9 tpl:w-full tpl:rounded-md tpl:border tpl:px-3 tpl:py-1 tpl:text-sm tpl:shadow-xs tpl:outline-none tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:text-[var(--tpl-text)]"
151
151
  }, null, 8, E)), [[te, V.value]]),
152
- X.value ? (t(), c("p", D, o(u(B).testEmail.invalidAddress), 1)) : l("", !0)
152
+ X.value ? (t(), s("p", D, u(l(B).testEmail.invalidAddress), 1)) : c("", !0)
153
153
  ]),
154
- f("div", O, [f("div", k, [f("span", A, o(u(B).testEmail.preview), 1), f("div", j, [u(U).supersedesSamples.value ? l("", !0) : (t(), d(me, {
154
+ f("div", O, [f("div", k, [f("span", A, u(l(B).testEmail.preview), 1), f("div", j, [l(U).supersedesSamples.value ? c("", !0) : (t(), d(me, {
155
155
  key: 0,
156
156
  "sample-mode": K.value,
157
157
  onChange: a[2] ||= (e) => K.value = e
158
158
  }, null, 8, ["sample-mode"])), g(pe, {
159
159
  viewport: H.value,
160
160
  onChange: a[3] ||= (e) => H.value = e
161
- }, null, 8, ["viewport"])])]), f("p", M, o(u(U).supersedesSamples.value ? u(B).previewResolution.hint : K.value ? u(B).testEmail.previewHintSample : u(B).testEmail.previewHint), 1)]),
162
- f("div", N, [u(U).isInitialResolve.value ? (t(), c("div", P, [f("span", F, o(u(B).previewResolution.resolving), 1), (t(), c(p, null, n(3, (e) => f("div", {
161
+ }, null, 8, ["viewport"])])]), f("p", M, u(l(U).supersedesSamples.value ? l(B).previewResolution.hint : K.value ? l(B).testEmail.previewHintSample : l(B).testEmail.previewHint), 1)]),
162
+ f("div", N, [l(U).isInitialResolve.value ? (t(), s("div", P, [f("span", F, u(l(B).previewResolution.resolving), 1), (t(), s(p, null, n(3, (e) => f("div", {
163
163
  key: e,
164
164
  "aria-hidden": "true",
165
165
  class: "tpl-pulse tpl:h-16 tpl:rounded-md tpl:bg-[var(--tpl-bg-hover)]"
166
- })), 64))])) : u(U).hasFailed.value ? (t(), c("p", I, o(u(B).previewResolution.failed), 1)) : l("", !0), u(U).isInitialResolve.value ? l("", !0) : (t(), d(ge, {
166
+ })), 64))])) : l(U).hasFailed.value ? (t(), s("p", I, u(l(B).previewResolution.failed), 1)) : c("", !0), l(U).isInitialResolve.value ? c("", !0) : (t(), d(ge, {
167
167
  key: 2,
168
168
  blocks: Te.value,
169
169
  viewport: H.value,
170
- "apply-condition-filter": !u(U).isConfigured
170
+ "apply-condition-filter": !l(U).isConfigured
171
171
  }, null, 8, [
172
172
  "blocks",
173
173
  "viewport",
174
174
  "apply-condition-filter"
175
175
  ]))]),
176
- h.justSent ? (t(), c("p", L, [g(u(de), {
176
+ h.justSent ? (t(), s("p", L, [g(l(de), {
177
177
  size: 13,
178
178
  "stroke-width": 2.5
179
- }), m(" " + o(u(B).testEmail.success), 1)])) : Z.value ? (t(), c("p", _e, o(Z.value), 1)) : l("", !0),
179
+ }), m(" " + u(l(B).testEmail.success), 1)])) : Z.value ? (t(), s("p", _e, u(Z.value), 1)) : c("", !0),
180
180
  f("div", ve, [f("button", {
181
181
  type: "button",
182
182
  "data-testid": "test-email-cancel",
183
183
  class: ne(["tpl:cursor-pointer tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-sm tpl:font-medium tpl:shadow-xs tpl:transition-all tpl:duration-150 tpl:border-[var(--tpl-border)] tpl:text-[var(--tpl-text)] tpl:bg-[var(--tpl-bg)]", { "tpl:cursor-not-allowed tpl:opacity-50": h.isSending }]),
184
184
  disabled: h.isSending,
185
185
  onClick: $
186
- }, o(u(B).testEmail.cancel), 11, ye), f("button", {
186
+ }, u(l(B).testEmail.cancel), 11, ye), f("button", {
187
187
  type: "button",
188
188
  "data-testid": "test-email-send",
189
189
  class: "tpl:cursor-pointer tpl:rounded-md tpl:px-3 tpl:py-1.5 tpl:text-sm tpl:font-medium tpl:shadow-xs tpl:transition-all tpl:duration-150 tpl:hover:opacity-90 tpl:disabled:cursor-not-allowed tpl:disabled:opacity-50 tpl:bg-[var(--tpl-primary)] tpl:text-[var(--tpl-bg)]",
190
190
  disabled: !Y.value,
191
191
  onClick: Q
192
- }, [h.isSending ? (t(), c("span", xe, [g(u(fe), {
192
+ }, [h.isSending ? (t(), s("span", xe, [g(l(fe), {
193
193
  class: "tpl:animate-spin",
194
194
  size: 12,
195
195
  "stroke-width": 2
196
- }), m(" " + o(u(B).testEmail.sending), 1)])) : (t(), c("span", Se, o(u(B).testEmail.send), 1))], 8, be)])
196
+ }), m(" " + u(l(B).testEmail.sending), 1)])) : (t(), s("span", Se, u(l(B).testEmail.send), 1))], 8, be)])
197
197
  ], 8, y)]),
198
198
  _: 1
199
199
  }, 8, ["visible"]));
@@ -202,4 +202,4 @@ var y = ["aria-busy"], b = {
202
202
  //#endregion
203
203
  export { R as default };
204
204
 
205
- //# sourceMappingURL=TestEmailModal-BcPjQygV.js.map
205
+ //# sourceMappingURL=TestEmailModal-DLeoJqaJ.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"TestEmailModal-BcPjQygV.js","names":[],"sources":["../../../src/utils/validateEmailShape.ts","../../../src/components/TestEmailModal.vue","../../../src/components/TestEmailModal.vue"],"sourcesContent":["/**\n * Characters that cannot appear in a domain but routinely arrive attached to one.\n *\n * A **denylist**, not an allowlist, so unicode domains keep working — an\n * allowlist of `[a-z0-9.-]` would be the stricter, more obviously \"correct\"\n * choice and would reject them. What this actually targets is the punctuation\n * that comes from pasting out of a list or a mail client: `user@example.com,`\n * and `<user@example.com>`.\n */\nconst IMPOSSIBLE_DOMAIN_CHARS = /[,;<>()[\\]\\\\\"'`|/?#@!$%^&*+=~{}]/;\n\n/**\n * Does this string look like an email address?\n *\n * Deliberately loose: non-empty, no whitespace, exactly one `@`, a non-empty\n * local part, and a domain containing a dot with something either side and no\n * impossible characters. That is enough to catch the mistakes a typo actually\n * produces — a missing `@`, a trailing comma, a half-typed domain — before the\n * user pays for a round-trip.\n *\n * **Do not \"improve\" this into an RFC-ish regex.** Those reliably reject valid\n * addresses (plus-addressing, long new gTLDs, unicode domains) and the resulting\n * bug reports cost far more than the malformed addresses they catch. Only the\n * receiving mail server can really tell, and the sending backend has to validate\n * anyway — this is a fast-feedback affordance, not a gate. `validateEmailShape`\n * has its own test file pinning the addresses that must keep passing.\n */\nexport function looksLikeEmail(value: string): boolean {\n const trimmed = value.trim();\n if (trimmed.length === 0) return false;\n if (/\\s/.test(trimmed)) return false;\n\n const parts = trimmed.split(\"@\");\n if (parts.length !== 2) return false;\n\n const [local, domain] = parts;\n if (local.length === 0) return false;\n if (IMPOSSIBLE_DOMAIN_CHARS.test(domain)) return false;\n\n // A dot with at least one character either side, so `a@b` and `a@b.` fail but\n // `a@b.c` and `a@sub.example.museum` pass.\n const dot = domain.indexOf(\".\");\n return dot > 0 && dot < domain.length - 1;\n}\n","<script setup lang=\"ts\">\n/**\n * Send-a-test dialog, shared by the OSS and Cloud editors.\n *\n * The recipient control has three shapes, driven entirely by whether the\n * provider configured an allowlist:\n *\n * - **no allowlist** — free text, validated for shape only. This branch exists\n * for BYO senders; Cloud never takes it, because its list comes from a signed\n * JWT.\n * - **exactly one** — a read-only field. Preserved verbatim from the Cloud-only\n * version; there is nothing to choose, so offering an editable field would\n * imply otherwise.\n * - **several** — a picker of exactly those addresses.\n *\n * An empty allowlist never reaches here: the feature reports itself unavailable,\n * so no trigger renders. (The Cloud-only version fell through to `v-else` and\n * rendered a select with no options and a permanently disabled Send.)\n */\nimport TplModal from \"./TplModal.vue\";\n// Statically imported: this modal is already lazy-loaded as a whole, so the\n// preview rides its chunk rather than costing a second round-trip and flashing\n// an empty frame. Still shared with the saved-blocks surfaces, which lazy-load\n// it themselves.\nimport BlockPreviewCanvas from \"./BlockPreviewCanvas.vue\";\n// The editor header's own control, reused verbatim so the two viewport switches\n// are the same component rather than two things that resemble each other.\nimport ViewportToggle from \"./ViewportToggle.vue\";\nimport MergeTagModeToggle from \"./MergeTagModeToggle.vue\";\nimport { useI18n } from \"../composables/useI18n\";\nimport { looksLikeEmail } from \"../utils/validateEmailShape\";\nimport { Check, LoaderCircle } from \"@lucide/vue\";\nimport { computed, inject, ref, watch } from \"vue\";\nimport { hasMergeTagSamples, type ViewportSize } from \"@templatical/types\";\nimport {\n EDITOR_KEY,\n MERGE_TAGS_KEY,\n MERGE_TAG_SAMPLE_MODE_KEY,\n RESOLVE_PREVIEW_KEY,\n} from \"../keys\";\nimport { usePreviewResolution } from \"../composables/usePreviewResolution\";\nimport type { TestEmailError } from \"../composables/useTestEmailFeature\";\n\nconst props = defineProps<{\n visible: boolean;\n /** Omitted / undefined means unrestricted, so the field accepts free text. */\n allowedRecipients?: string[];\n defaultRecipient?: string;\n isSending: boolean;\n justSent: boolean;\n error: TestEmailError | null;\n}>();\n\nconst emit = defineEmits<{\n (e: \"send\", recipient: string): void;\n (e: \"close\"): void;\n}>();\n\nconst { t } = useI18n();\nconst editor = inject(EDITOR_KEY, null);\n\nconst recipient = ref(\"\");\n\nconst previewViewport = ref<ViewportSize>(\"desktop\");\n\n/**\n * Its own resolution instance rather than the editor's, because this dialog has\n * a recipient and the editor's preview does not — resolving for \"whoever is\n * selected here\" is a different question from resolving for the canvas.\n * `isActive` is the dialog's own visibility.\n */\nconst resolvePreviewHook = inject(RESOLVE_PREVIEW_KEY, undefined);\nconst previewResolution = usePreviewResolution({\n resolvePreview: resolvePreviewHook ?? undefined,\n getContent: () =>\n editor?.content.value ?? { blocks: [], settings: {} as never },\n isActive: () => props.visible,\n getRecipient: () => recipient.value || undefined,\n});\n\n/**\n * Shared with the editor's own preview, so switching here and switching there\n * are the same choice — it is a property of how the user wants to read a\n * preview, not of one dialog. Falls back to a local ref for headless mounts.\n */\nconst sharedSampleMode = inject(MERGE_TAG_SAMPLE_MODE_KEY, null);\n// Seeded from availability, matching how `useEditorCore` seeds the shared ref.\n// A blanket `true` would make the dialog claim tags \"show example values\" for a\n// consumer who configured none — false, and with no toggle rendered to correct\n// it, since the toggle hides itself when nothing has a sample.\nconst localSampleMode = ref(hasMergeTagSamples(inject(MERGE_TAGS_KEY, [])));\nconst sampleMode = computed({\n get: () =>\n // Off entirely while a resolver is configured — same rule as the canvas.\n previewResolution.isConfigured\n ? false\n : (sharedSampleMode?.value ?? localSampleMode.value),\n set: (on: boolean) => {\n if (sharedSampleMode) sharedSampleMode.value = on;\n else localSampleMode.value = on;\n },\n});\n\nconst previewBlocks = computed(\n () => previewResolution.content.value.blocks ?? [],\n);\n\nconst hasAllowlist = computed(\n () =>\n props.allowedRecipients !== undefined && props.allowedRecipients.length > 0,\n);\nconst isSingleRecipient = computed(() => props.allowedRecipients?.length === 1);\n\n// Mounted behind a `v-if` on the same state that drives `visible`, so it can\n// mount with `visible` already true — without `immediate` the field would never\n// be seeded.\nwatch(\n () => props.visible,\n (visible) => {\n if (!visible) return;\n recipient.value =\n props.defaultRecipient ?? props.allowedRecipients?.[0] ?? \"\";\n },\n { immediate: true },\n);\n\n/** Shape-check free text only; a picker can't produce a malformed value. */\nconst isRecipientValid = computed(() =>\n hasAllowlist.value\n ? recipient.value.length > 0\n : looksLikeEmail(recipient.value),\n);\n\nconst canSend = computed(\n () => isRecipientValid.value && !props.isSending && !props.justSent,\n);\n\n/** Free text that's been typed into but isn't yet a plausible address. */\nconst showShapeHint = computed(\n () =>\n !hasAllowlist.value &&\n recipient.value.trim().length > 0 &&\n !isRecipientValid.value,\n);\n\nconst errorText = computed(() => {\n if (!props.error) return null;\n return props.error.kind === \"provider\"\n ? props.error.message\n : t.testEmail.recipientNotAllowed;\n});\n\nfunction handleSend(): void {\n if (!canSend.value) return;\n emit(\"send\", recipient.value.trim());\n}\n\nfunction handleClose(): void {\n // Sending is the one state that blocks dismissal; the success state closes\n // immediately rather than making the user wait out the timer.\n if (props.isSending) return;\n emit(\"close\");\n}\n\nfunction handleKeydown(event: KeyboardEvent): void {\n if (event.key === \"Enter\" && !event.shiftKey) {\n event.preventDefault();\n handleSend();\n }\n if (event.key === \"Escape\") {\n handleClose();\n }\n}\n</script>\n\n<template>\n <TplModal :visible=\"visible\" @close=\"handleClose\" @keydown=\"handleKeydown\">\n <!-- Grows to `max-w-2xl` only while the preview is open, so a 600px email\n renders at roughly true size — the same reason SaveBlockDialog picked\n that width. Collapsed, it stays the compact form it was. `max-h-[90vh]`\n with a `min-h-0 overflow-y-auto` preview region is the pattern the\n saved-blocks dialogs use: `min-h-0` is what lets a flex child shrink\n below its content and actually scroll. -->\n <div\n role=\"dialog\"\n aria-modal=\"true\"\n :aria-busy=\"isSending\"\n aria-labelledby=\"tpl-test-email-title\"\n class=\"tpl-scale-in tpl:mx-4 tpl:flex tpl:max-h-[90vh] tpl:w-full tpl:max-w-2xl tpl:flex-col tpl:rounded-[var(--tpl-radius-lg)] tpl:p-5\"\n style=\"\n background-color: var(--tpl-bg-elevated);\n box-shadow: var(--tpl-shadow-xl);\n \"\n >\n <h3\n id=\"tpl-test-email-title\"\n class=\"tpl:mb-4 tpl:shrink-0 tpl:text-sm tpl:font-semibold tpl:text-[var(--tpl-text)]\"\n >\n {{ t.testEmail.title }}\n </h3>\n\n <!-- Recipient -->\n <div class=\"tpl:mb-3 tpl:shrink-0\">\n <label\n for=\"tpl-test-email-recipient\"\n class=\"tpl:mb-1.5 tpl:block tpl:text-sm tpl:font-medium tpl:text-[var(--tpl-text-muted)]\"\n >\n {{ t.testEmail.recipientLabel }}\n </label>\n\n <!-- Exactly one allowed address: nothing to choose. -->\n <input\n v-if=\"isSingleRecipient\"\n id=\"tpl-test-email-recipient\"\n type=\"text\"\n :value=\"recipient\"\n disabled\n data-testid=\"test-email-recipient\"\n class=\"tpl:h-9 tpl:w-full tpl:rounded-md tpl:border tpl:px-3 tpl:py-1 tpl:text-sm tpl:opacity-70 tpl:shadow-xs tpl:outline-none tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:text-[var(--tpl-text)]\"\n />\n\n <!-- Several allowed addresses: pick one. -->\n <select\n v-else-if=\"hasAllowlist\"\n id=\"tpl-test-email-recipient\"\n v-model=\"recipient\"\n :disabled=\"isSending || justSent\"\n data-testid=\"test-email-recipient\"\n class=\"tpl:h-9 tpl:w-full tpl:rounded-md tpl:border tpl:px-3 tpl:py-1 tpl:text-sm tpl:shadow-xs tpl:outline-none tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:text-[var(--tpl-text)]\"\n >\n <option\n v-for=\"email in allowedRecipients\"\n :key=\"email\"\n :value=\"email\"\n >\n {{ email }}\n </option>\n </select>\n\n <!-- No allowlist: free text. -->\n <input\n v-else\n id=\"tpl-test-email-recipient\"\n v-model=\"recipient\"\n type=\"email\"\n autocomplete=\"email\"\n spellcheck=\"false\"\n :placeholder=\"t.testEmail.recipientPlaceholder\"\n :disabled=\"isSending || justSent\"\n data-testid=\"test-email-recipient\"\n class=\"tpl:h-9 tpl:w-full tpl:rounded-md tpl:border tpl:px-3 tpl:py-1 tpl:text-sm tpl:shadow-xs tpl:outline-none tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:text-[var(--tpl-text)]\"\n />\n\n <p\n v-if=\"showShapeHint\"\n class=\"tpl:mt-1.5 tpl:text-xs tpl:text-[var(--tpl-text-dim)]\"\n >\n {{ t.testEmail.invalidAddress }}\n </p>\n </div>\n\n <!-- Preview header: a label plus the viewport switch. Always shown — the\n preview is part of the dialog rather than something to opt into. -->\n <div class=\"tpl:mb-2 tpl:shrink-0\">\n <!-- Two segmented controls: `flex-wrap` so they drop to their own row\n rather than crushing the label on a narrow viewport. -->\n <div\n class=\"tpl:flex tpl:flex-wrap tpl:items-center tpl:justify-between tpl:gap-2\"\n >\n <span\n class=\"tpl:text-sm tpl:font-medium tpl:text-[var(--tpl-text-muted)]\"\n >\n {{ t.testEmail.preview }}\n </span>\n\n <div class=\"tpl:flex tpl:items-center tpl:gap-2\">\n <MergeTagModeToggle\n v-if=\"!previewResolution.supersedesSamples.value\"\n :sample-mode=\"sampleMode\"\n @change=\"sampleMode = $event\"\n />\n\n <!-- Without this the preview would silently show only the desktop\n variant of any responsive block. -->\n <ViewportToggle\n :viewport=\"previewViewport\"\n @change=\"previewViewport = $event\"\n />\n </div>\n </div>\n\n <!-- The hint has to follow the mode: \"shown unresolved\" is simply false\n once every tag renders a realistic value. -->\n <p\n data-testid=\"test-email-preview-hint\"\n class=\"tpl:mt-1 tpl:text-xs tpl:text-[var(--tpl-text-dim)]\"\n >\n {{\n previewResolution.supersedesSamples.value\n ? t.previewResolution.hint\n : sampleMode\n ? t.testEmail.previewHintSample\n : t.testEmail.previewHint\n }}\n </p>\n </div>\n\n <!-- The one region allowed to grow: `flex-1 min-h-0` so it absorbs the\n remaining height and scrolls, leaving the form and actions fixed. -->\n <div\n id=\"tpl-test-email-preview\"\n data-testid=\"test-email-preview\"\n class=\"tpl:mb-3 tpl:min-h-0 tpl:flex-1 tpl:overflow-y-auto tpl:rounded-[var(--tpl-radius-sm)] tpl:p-3 tpl:bg-[var(--tpl-canvas-bg)]\"\n >\n <!-- Skeleton while a *first* resolve is outstanding, so the dialog never\n flashes the unresolved template before the resolved one. Sized like\n the frame it replaces to avoid a layout jump. A re-resolve (recipient\n change) keeps the previous result instead — `isInitialResolve`. -->\n <div\n v-if=\"previewResolution.isInitialResolve.value\"\n role=\"status\"\n aria-busy=\"true\"\n data-testid=\"preview-resolution-loading\"\n class=\"tpl:flex tpl:flex-col tpl:gap-3\"\n >\n <span class=\"tpl:sr-only\">{{ t.previewResolution.resolving }}</span>\n <div\n v-for=\"n in 3\"\n :key=\"n\"\n aria-hidden=\"true\"\n class=\"tpl-pulse tpl:h-16 tpl:rounded-md tpl:bg-[var(--tpl-bg-hover)]\"\n ></div>\n </div>\n\n <!-- Resolution failed: the unresolved template renders below, so the\n dialog degrades rather than showing an empty frame. -->\n <p\n v-else-if=\"previewResolution.hasFailed.value\"\n role=\"status\"\n data-testid=\"preview-resolution-failed\"\n class=\"tpl:mb-2 tpl:rounded-md tpl:px-3 tpl:py-2 tpl:text-xs tpl:text-[var(--tpl-text-muted)] tpl:bg-[var(--tpl-bg-hover)]\"\n >\n {{ t.previewResolution.failed }}\n </p>\n\n <BlockPreviewCanvas\n v-if=\"!previewResolution.isInitialResolve.value\"\n :blocks=\"previewBlocks\"\n :viewport=\"previewViewport\"\n :apply-condition-filter=\"!previewResolution.isConfigured\"\n />\n </div>\n\n <!-- Success. Announced politely; the dialog closes itself shortly after. -->\n <p\n v-if=\"justSent\"\n role=\"status\"\n data-testid=\"test-email-success\"\n class=\"tpl:mb-3 tpl:flex tpl:shrink-0 tpl:items-center tpl:gap-1.5 tpl:text-xs tpl:text-[var(--tpl-success,var(--tpl-primary))]\"\n >\n <Check :size=\"13\" :stroke-width=\"2.5\" />\n {{ t.testEmail.success }}\n </p>\n\n <!-- Failure. Stays open so the user can retry. -->\n <p\n v-else-if=\"errorText\"\n role=\"alert\"\n data-testid=\"test-email-error\"\n class=\"tpl:mb-3 tpl:shrink-0 tpl:text-xs tpl:text-[var(--tpl-danger)]\"\n >\n {{ errorText }}\n </p>\n\n <!-- Actions -->\n <div class=\"tpl:flex tpl:shrink-0 tpl:justify-end tpl:gap-2\">\n <button\n type=\"button\"\n data-testid=\"test-email-cancel\"\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-sm tpl:font-medium tpl:shadow-xs tpl:transition-all tpl:duration-150 tpl:border-[var(--tpl-border)] tpl:text-[var(--tpl-text)] tpl:bg-[var(--tpl-bg)]\"\n :disabled=\"isSending\"\n :class=\"{ 'tpl:cursor-not-allowed tpl:opacity-50': isSending }\"\n @click=\"handleClose\"\n >\n {{ t.testEmail.cancel }}\n </button>\n <button\n type=\"button\"\n data-testid=\"test-email-send\"\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:px-3 tpl:py-1.5 tpl:text-sm tpl:font-medium tpl:shadow-xs tpl:transition-all tpl:duration-150 tpl:hover:opacity-90 tpl:disabled:cursor-not-allowed tpl:disabled:opacity-50 tpl:bg-[var(--tpl-primary)] tpl:text-[var(--tpl-bg)]\"\n :disabled=\"!canSend\"\n @click=\"handleSend\"\n >\n <span v-if=\"isSending\" class=\"tpl:flex tpl:items-center tpl:gap-1.5\">\n <LoaderCircle\n class=\"tpl:animate-spin\"\n :size=\"12\"\n :stroke-width=\"2\"\n />\n {{ t.testEmail.sending }}\n </span>\n <span v-else>\n {{ t.testEmail.send }}\n </span>\n </button>\n </div>\n </div>\n </TplModal>\n</template>\n","<script setup lang=\"ts\">\n/**\n * Send-a-test dialog, shared by the OSS and Cloud editors.\n *\n * The recipient control has three shapes, driven entirely by whether the\n * provider configured an allowlist:\n *\n * - **no allowlist** — free text, validated for shape only. This branch exists\n * for BYO senders; Cloud never takes it, because its list comes from a signed\n * JWT.\n * - **exactly one** — a read-only field. Preserved verbatim from the Cloud-only\n * version; there is nothing to choose, so offering an editable field would\n * imply otherwise.\n * - **several** — a picker of exactly those addresses.\n *\n * An empty allowlist never reaches here: the feature reports itself unavailable,\n * so no trigger renders. (The Cloud-only version fell through to `v-else` and\n * rendered a select with no options and a permanently disabled Send.)\n */\nimport TplModal from \"./TplModal.vue\";\n// Statically imported: this modal is already lazy-loaded as a whole, so the\n// preview rides its chunk rather than costing a second round-trip and flashing\n// an empty frame. Still shared with the saved-blocks surfaces, which lazy-load\n// it themselves.\nimport BlockPreviewCanvas from \"./BlockPreviewCanvas.vue\";\n// The editor header's own control, reused verbatim so the two viewport switches\n// are the same component rather than two things that resemble each other.\nimport ViewportToggle from \"./ViewportToggle.vue\";\nimport MergeTagModeToggle from \"./MergeTagModeToggle.vue\";\nimport { useI18n } from \"../composables/useI18n\";\nimport { looksLikeEmail } from \"../utils/validateEmailShape\";\nimport { Check, LoaderCircle } from \"@lucide/vue\";\nimport { computed, inject, ref, watch } from \"vue\";\nimport { hasMergeTagSamples, type ViewportSize } from \"@templatical/types\";\nimport {\n EDITOR_KEY,\n MERGE_TAGS_KEY,\n MERGE_TAG_SAMPLE_MODE_KEY,\n RESOLVE_PREVIEW_KEY,\n} from \"../keys\";\nimport { usePreviewResolution } from \"../composables/usePreviewResolution\";\nimport type { TestEmailError } from \"../composables/useTestEmailFeature\";\n\nconst props = defineProps<{\n visible: boolean;\n /** Omitted / undefined means unrestricted, so the field accepts free text. */\n allowedRecipients?: string[];\n defaultRecipient?: string;\n isSending: boolean;\n justSent: boolean;\n error: TestEmailError | null;\n}>();\n\nconst emit = defineEmits<{\n (e: \"send\", recipient: string): void;\n (e: \"close\"): void;\n}>();\n\nconst { t } = useI18n();\nconst editor = inject(EDITOR_KEY, null);\n\nconst recipient = ref(\"\");\n\nconst previewViewport = ref<ViewportSize>(\"desktop\");\n\n/**\n * Its own resolution instance rather than the editor's, because this dialog has\n * a recipient and the editor's preview does not — resolving for \"whoever is\n * selected here\" is a different question from resolving for the canvas.\n * `isActive` is the dialog's own visibility.\n */\nconst resolvePreviewHook = inject(RESOLVE_PREVIEW_KEY, undefined);\nconst previewResolution = usePreviewResolution({\n resolvePreview: resolvePreviewHook ?? undefined,\n getContent: () =>\n editor?.content.value ?? { blocks: [], settings: {} as never },\n isActive: () => props.visible,\n getRecipient: () => recipient.value || undefined,\n});\n\n/**\n * Shared with the editor's own preview, so switching here and switching there\n * are the same choice — it is a property of how the user wants to read a\n * preview, not of one dialog. Falls back to a local ref for headless mounts.\n */\nconst sharedSampleMode = inject(MERGE_TAG_SAMPLE_MODE_KEY, null);\n// Seeded from availability, matching how `useEditorCore` seeds the shared ref.\n// A blanket `true` would make the dialog claim tags \"show example values\" for a\n// consumer who configured none — false, and with no toggle rendered to correct\n// it, since the toggle hides itself when nothing has a sample.\nconst localSampleMode = ref(hasMergeTagSamples(inject(MERGE_TAGS_KEY, [])));\nconst sampleMode = computed({\n get: () =>\n // Off entirely while a resolver is configured — same rule as the canvas.\n previewResolution.isConfigured\n ? false\n : (sharedSampleMode?.value ?? localSampleMode.value),\n set: (on: boolean) => {\n if (sharedSampleMode) sharedSampleMode.value = on;\n else localSampleMode.value = on;\n },\n});\n\nconst previewBlocks = computed(\n () => previewResolution.content.value.blocks ?? [],\n);\n\nconst hasAllowlist = computed(\n () =>\n props.allowedRecipients !== undefined && props.allowedRecipients.length > 0,\n);\nconst isSingleRecipient = computed(() => props.allowedRecipients?.length === 1);\n\n// Mounted behind a `v-if` on the same state that drives `visible`, so it can\n// mount with `visible` already true — without `immediate` the field would never\n// be seeded.\nwatch(\n () => props.visible,\n (visible) => {\n if (!visible) return;\n recipient.value =\n props.defaultRecipient ?? props.allowedRecipients?.[0] ?? \"\";\n },\n { immediate: true },\n);\n\n/** Shape-check free text only; a picker can't produce a malformed value. */\nconst isRecipientValid = computed(() =>\n hasAllowlist.value\n ? recipient.value.length > 0\n : looksLikeEmail(recipient.value),\n);\n\nconst canSend = computed(\n () => isRecipientValid.value && !props.isSending && !props.justSent,\n);\n\n/** Free text that's been typed into but isn't yet a plausible address. */\nconst showShapeHint = computed(\n () =>\n !hasAllowlist.value &&\n recipient.value.trim().length > 0 &&\n !isRecipientValid.value,\n);\n\nconst errorText = computed(() => {\n if (!props.error) return null;\n return props.error.kind === \"provider\"\n ? props.error.message\n : t.testEmail.recipientNotAllowed;\n});\n\nfunction handleSend(): void {\n if (!canSend.value) return;\n emit(\"send\", recipient.value.trim());\n}\n\nfunction handleClose(): void {\n // Sending is the one state that blocks dismissal; the success state closes\n // immediately rather than making the user wait out the timer.\n if (props.isSending) return;\n emit(\"close\");\n}\n\nfunction handleKeydown(event: KeyboardEvent): void {\n if (event.key === \"Enter\" && !event.shiftKey) {\n event.preventDefault();\n handleSend();\n }\n if (event.key === \"Escape\") {\n handleClose();\n }\n}\n</script>\n\n<template>\n <TplModal :visible=\"visible\" @close=\"handleClose\" @keydown=\"handleKeydown\">\n <!-- Grows to `max-w-2xl` only while the preview is open, so a 600px email\n renders at roughly true size — the same reason SaveBlockDialog picked\n that width. Collapsed, it stays the compact form it was. `max-h-[90vh]`\n with a `min-h-0 overflow-y-auto` preview region is the pattern the\n saved-blocks dialogs use: `min-h-0` is what lets a flex child shrink\n below its content and actually scroll. -->\n <div\n role=\"dialog\"\n aria-modal=\"true\"\n :aria-busy=\"isSending\"\n aria-labelledby=\"tpl-test-email-title\"\n class=\"tpl-scale-in tpl:mx-4 tpl:flex tpl:max-h-[90vh] tpl:w-full tpl:max-w-2xl tpl:flex-col tpl:rounded-[var(--tpl-radius-lg)] tpl:p-5\"\n style=\"\n background-color: var(--tpl-bg-elevated);\n box-shadow: var(--tpl-shadow-xl);\n \"\n >\n <h3\n id=\"tpl-test-email-title\"\n class=\"tpl:mb-4 tpl:shrink-0 tpl:text-sm tpl:font-semibold tpl:text-[var(--tpl-text)]\"\n >\n {{ t.testEmail.title }}\n </h3>\n\n <!-- Recipient -->\n <div class=\"tpl:mb-3 tpl:shrink-0\">\n <label\n for=\"tpl-test-email-recipient\"\n class=\"tpl:mb-1.5 tpl:block tpl:text-sm tpl:font-medium tpl:text-[var(--tpl-text-muted)]\"\n >\n {{ t.testEmail.recipientLabel }}\n </label>\n\n <!-- Exactly one allowed address: nothing to choose. -->\n <input\n v-if=\"isSingleRecipient\"\n id=\"tpl-test-email-recipient\"\n type=\"text\"\n :value=\"recipient\"\n disabled\n data-testid=\"test-email-recipient\"\n class=\"tpl:h-9 tpl:w-full tpl:rounded-md tpl:border tpl:px-3 tpl:py-1 tpl:text-sm tpl:opacity-70 tpl:shadow-xs tpl:outline-none tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:text-[var(--tpl-text)]\"\n />\n\n <!-- Several allowed addresses: pick one. -->\n <select\n v-else-if=\"hasAllowlist\"\n id=\"tpl-test-email-recipient\"\n v-model=\"recipient\"\n :disabled=\"isSending || justSent\"\n data-testid=\"test-email-recipient\"\n class=\"tpl:h-9 tpl:w-full tpl:rounded-md tpl:border tpl:px-3 tpl:py-1 tpl:text-sm tpl:shadow-xs tpl:outline-none tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:text-[var(--tpl-text)]\"\n >\n <option\n v-for=\"email in allowedRecipients\"\n :key=\"email\"\n :value=\"email\"\n >\n {{ email }}\n </option>\n </select>\n\n <!-- No allowlist: free text. -->\n <input\n v-else\n id=\"tpl-test-email-recipient\"\n v-model=\"recipient\"\n type=\"email\"\n autocomplete=\"email\"\n spellcheck=\"false\"\n :placeholder=\"t.testEmail.recipientPlaceholder\"\n :disabled=\"isSending || justSent\"\n data-testid=\"test-email-recipient\"\n class=\"tpl:h-9 tpl:w-full tpl:rounded-md tpl:border tpl:px-3 tpl:py-1 tpl:text-sm tpl:shadow-xs tpl:outline-none tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:text-[var(--tpl-text)]\"\n />\n\n <p\n v-if=\"showShapeHint\"\n class=\"tpl:mt-1.5 tpl:text-xs tpl:text-[var(--tpl-text-dim)]\"\n >\n {{ t.testEmail.invalidAddress }}\n </p>\n </div>\n\n <!-- Preview header: a label plus the viewport switch. Always shown — the\n preview is part of the dialog rather than something to opt into. -->\n <div class=\"tpl:mb-2 tpl:shrink-0\">\n <!-- Two segmented controls: `flex-wrap` so they drop to their own row\n rather than crushing the label on a narrow viewport. -->\n <div\n class=\"tpl:flex tpl:flex-wrap tpl:items-center tpl:justify-between tpl:gap-2\"\n >\n <span\n class=\"tpl:text-sm tpl:font-medium tpl:text-[var(--tpl-text-muted)]\"\n >\n {{ t.testEmail.preview }}\n </span>\n\n <div class=\"tpl:flex tpl:items-center tpl:gap-2\">\n <MergeTagModeToggle\n v-if=\"!previewResolution.supersedesSamples.value\"\n :sample-mode=\"sampleMode\"\n @change=\"sampleMode = $event\"\n />\n\n <!-- Without this the preview would silently show only the desktop\n variant of any responsive block. -->\n <ViewportToggle\n :viewport=\"previewViewport\"\n @change=\"previewViewport = $event\"\n />\n </div>\n </div>\n\n <!-- The hint has to follow the mode: \"shown unresolved\" is simply false\n once every tag renders a realistic value. -->\n <p\n data-testid=\"test-email-preview-hint\"\n class=\"tpl:mt-1 tpl:text-xs tpl:text-[var(--tpl-text-dim)]\"\n >\n {{\n previewResolution.supersedesSamples.value\n ? t.previewResolution.hint\n : sampleMode\n ? t.testEmail.previewHintSample\n : t.testEmail.previewHint\n }}\n </p>\n </div>\n\n <!-- The one region allowed to grow: `flex-1 min-h-0` so it absorbs the\n remaining height and scrolls, leaving the form and actions fixed. -->\n <div\n id=\"tpl-test-email-preview\"\n data-testid=\"test-email-preview\"\n class=\"tpl:mb-3 tpl:min-h-0 tpl:flex-1 tpl:overflow-y-auto tpl:rounded-[var(--tpl-radius-sm)] tpl:p-3 tpl:bg-[var(--tpl-canvas-bg)]\"\n >\n <!-- Skeleton while a *first* resolve is outstanding, so the dialog never\n flashes the unresolved template before the resolved one. Sized like\n the frame it replaces to avoid a layout jump. A re-resolve (recipient\n change) keeps the previous result instead — `isInitialResolve`. -->\n <div\n v-if=\"previewResolution.isInitialResolve.value\"\n role=\"status\"\n aria-busy=\"true\"\n data-testid=\"preview-resolution-loading\"\n class=\"tpl:flex tpl:flex-col tpl:gap-3\"\n >\n <span class=\"tpl:sr-only\">{{ t.previewResolution.resolving }}</span>\n <div\n v-for=\"n in 3\"\n :key=\"n\"\n aria-hidden=\"true\"\n class=\"tpl-pulse tpl:h-16 tpl:rounded-md tpl:bg-[var(--tpl-bg-hover)]\"\n ></div>\n </div>\n\n <!-- Resolution failed: the unresolved template renders below, so the\n dialog degrades rather than showing an empty frame. -->\n <p\n v-else-if=\"previewResolution.hasFailed.value\"\n role=\"status\"\n data-testid=\"preview-resolution-failed\"\n class=\"tpl:mb-2 tpl:rounded-md tpl:px-3 tpl:py-2 tpl:text-xs tpl:text-[var(--tpl-text-muted)] tpl:bg-[var(--tpl-bg-hover)]\"\n >\n {{ t.previewResolution.failed }}\n </p>\n\n <BlockPreviewCanvas\n v-if=\"!previewResolution.isInitialResolve.value\"\n :blocks=\"previewBlocks\"\n :viewport=\"previewViewport\"\n :apply-condition-filter=\"!previewResolution.isConfigured\"\n />\n </div>\n\n <!-- Success. Announced politely; the dialog closes itself shortly after. -->\n <p\n v-if=\"justSent\"\n role=\"status\"\n data-testid=\"test-email-success\"\n class=\"tpl:mb-3 tpl:flex tpl:shrink-0 tpl:items-center tpl:gap-1.5 tpl:text-xs tpl:text-[var(--tpl-success,var(--tpl-primary))]\"\n >\n <Check :size=\"13\" :stroke-width=\"2.5\" />\n {{ t.testEmail.success }}\n </p>\n\n <!-- Failure. Stays open so the user can retry. -->\n <p\n v-else-if=\"errorText\"\n role=\"alert\"\n data-testid=\"test-email-error\"\n class=\"tpl:mb-3 tpl:shrink-0 tpl:text-xs tpl:text-[var(--tpl-danger)]\"\n >\n {{ errorText }}\n </p>\n\n <!-- Actions -->\n <div class=\"tpl:flex tpl:shrink-0 tpl:justify-end tpl:gap-2\">\n <button\n type=\"button\"\n data-testid=\"test-email-cancel\"\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-sm tpl:font-medium tpl:shadow-xs tpl:transition-all tpl:duration-150 tpl:border-[var(--tpl-border)] tpl:text-[var(--tpl-text)] tpl:bg-[var(--tpl-bg)]\"\n :disabled=\"isSending\"\n :class=\"{ 'tpl:cursor-not-allowed tpl:opacity-50': isSending }\"\n @click=\"handleClose\"\n >\n {{ t.testEmail.cancel }}\n </button>\n <button\n type=\"button\"\n data-testid=\"test-email-send\"\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:px-3 tpl:py-1.5 tpl:text-sm tpl:font-medium tpl:shadow-xs tpl:transition-all tpl:duration-150 tpl:hover:opacity-90 tpl:disabled:cursor-not-allowed tpl:disabled:opacity-50 tpl:bg-[var(--tpl-primary)] tpl:text-[var(--tpl-bg)]\"\n :disabled=\"!canSend\"\n @click=\"handleSend\"\n >\n <span v-if=\"isSending\" class=\"tpl:flex tpl:items-center tpl:gap-1.5\">\n <LoaderCircle\n class=\"tpl:animate-spin\"\n :size=\"12\"\n :stroke-width=\"2\"\n />\n {{ t.testEmail.sending }}\n </span>\n <span v-else>\n {{ t.testEmail.send }}\n </span>\n </button>\n </div>\n </div>\n </TplModal>\n</template>\n"],"mappings":"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"}
1
+ {"version":3,"file":"TestEmailModal-DLeoJqaJ.js","names":[],"sources":["../../../src/utils/validateEmailShape.ts","../../../src/components/TestEmailModal.vue","../../../src/components/TestEmailModal.vue"],"sourcesContent":["/**\n * Characters that cannot appear in a domain but routinely arrive attached to one.\n *\n * A **denylist**, not an allowlist, so unicode domains keep working — an\n * allowlist of `[a-z0-9.-]` would be the stricter, more obviously \"correct\"\n * choice and would reject them. What this actually targets is the punctuation\n * that comes from pasting out of a list or a mail client: `user@example.com,`\n * and `<user@example.com>`.\n */\nconst IMPOSSIBLE_DOMAIN_CHARS = /[,;<>()[\\]\\\\\"'`|/?#@!$%^&*+=~{}]/;\n\n/**\n * Does this string look like an email address?\n *\n * Deliberately loose: non-empty, no whitespace, exactly one `@`, a non-empty\n * local part, and a domain containing a dot with something either side and no\n * impossible characters. That is enough to catch the mistakes a typo actually\n * produces — a missing `@`, a trailing comma, a half-typed domain — before the\n * user pays for a round-trip.\n *\n * **Do not \"improve\" this into an RFC-ish regex.** Those reliably reject valid\n * addresses (plus-addressing, long new gTLDs, unicode domains) and the resulting\n * bug reports cost far more than the malformed addresses they catch. Only the\n * receiving mail server can really tell, and the sending backend has to validate\n * anyway — this is a fast-feedback affordance, not a gate. `validateEmailShape`\n * has its own test file pinning the addresses that must keep passing.\n */\nexport function looksLikeEmail(value: string): boolean {\n const trimmed = value.trim();\n if (trimmed.length === 0) return false;\n if (/\\s/.test(trimmed)) return false;\n\n const parts = trimmed.split(\"@\");\n if (parts.length !== 2) return false;\n\n const [local, domain] = parts;\n if (local.length === 0) return false;\n if (IMPOSSIBLE_DOMAIN_CHARS.test(domain)) return false;\n\n // A dot with at least one character either side, so `a@b` and `a@b.` fail but\n // `a@b.c` and `a@sub.example.museum` pass.\n const dot = domain.indexOf(\".\");\n return dot > 0 && dot < domain.length - 1;\n}\n","<script setup lang=\"ts\">\n/**\n * Send-a-test dialog, shared by the OSS and Cloud editors.\n *\n * The recipient control has three shapes, driven entirely by whether the\n * provider configured an allowlist:\n *\n * - **no allowlist** — free text, validated for shape only. This branch exists\n * for BYO senders; Cloud never takes it, because its list comes from a signed\n * JWT.\n * - **exactly one** — a read-only field. Preserved verbatim from the Cloud-only\n * version; there is nothing to choose, so offering an editable field would\n * imply otherwise.\n * - **several** — a picker of exactly those addresses.\n *\n * An empty allowlist never reaches here: the feature reports itself unavailable,\n * so no trigger renders. (The Cloud-only version fell through to `v-else` and\n * rendered a select with no options and a permanently disabled Send.)\n */\nimport TplModal from \"./TplModal.vue\";\n// Statically imported: this modal is already lazy-loaded as a whole, so the\n// preview rides its chunk rather than costing a second round-trip and flashing\n// an empty frame. Still shared with the saved-blocks surfaces, which lazy-load\n// it themselves.\nimport BlockPreviewCanvas from \"./BlockPreviewCanvas.vue\";\n// The editor header's own control, reused verbatim so the two viewport switches\n// are the same component rather than two things that resemble each other.\nimport ViewportToggle from \"./ViewportToggle.vue\";\nimport MergeTagModeToggle from \"./MergeTagModeToggle.vue\";\nimport { useI18n } from \"../composables/useI18n\";\nimport { looksLikeEmail } from \"../utils/validateEmailShape\";\nimport { Check, LoaderCircle } from \"@lucide/vue\";\nimport { computed, inject, ref, watch } from \"vue\";\nimport { hasMergeTagSamples, type ViewportSize } from \"@templatical/types\";\nimport {\n EDITOR_KEY,\n MERGE_TAGS_KEY,\n MERGE_TAG_SAMPLE_MODE_KEY,\n RESOLVE_PREVIEW_KEY,\n} from \"../keys\";\nimport { usePreviewResolution } from \"../composables/usePreviewResolution\";\nimport type { TestEmailError } from \"../composables/useTestEmailFeature\";\n\nconst props = defineProps<{\n visible: boolean;\n /** Omitted / undefined means unrestricted, so the field accepts free text. */\n allowedRecipients?: string[];\n defaultRecipient?: string;\n isSending: boolean;\n justSent: boolean;\n error: TestEmailError | null;\n}>();\n\nconst emit = defineEmits<{\n (e: \"send\", recipient: string): void;\n (e: \"close\"): void;\n}>();\n\nconst { t } = useI18n();\nconst editor = inject(EDITOR_KEY, null);\n\nconst recipient = ref(\"\");\n\nconst previewViewport = ref<ViewportSize>(\"desktop\");\n\n/**\n * Its own resolution instance rather than the editor's, because this dialog has\n * a recipient and the editor's preview does not — resolving for \"whoever is\n * selected here\" is a different question from resolving for the canvas.\n * `isActive` is the dialog's own visibility.\n */\nconst resolvePreviewHook = inject(RESOLVE_PREVIEW_KEY, undefined);\nconst previewResolution = usePreviewResolution({\n resolvePreview: resolvePreviewHook ?? undefined,\n getContent: () =>\n editor?.content.value ?? { blocks: [], settings: {} as never },\n isActive: () => props.visible,\n getRecipient: () => recipient.value || undefined,\n});\n\n/**\n * Shared with the editor's own preview, so switching here and switching there\n * are the same choice — it is a property of how the user wants to read a\n * preview, not of one dialog. Falls back to a local ref for headless mounts.\n */\nconst sharedSampleMode = inject(MERGE_TAG_SAMPLE_MODE_KEY, null);\n// Seeded from availability, matching how `useEditorCore` seeds the shared ref.\n// A blanket `true` would make the dialog claim tags \"show example values\" for a\n// consumer who configured none — false, and with no toggle rendered to correct\n// it, since the toggle hides itself when nothing has a sample.\nconst localSampleMode = ref(hasMergeTagSamples(inject(MERGE_TAGS_KEY, [])));\nconst sampleMode = computed({\n get: () =>\n // Off entirely while a resolver is configured — same rule as the canvas.\n previewResolution.isConfigured\n ? false\n : (sharedSampleMode?.value ?? localSampleMode.value),\n set: (on: boolean) => {\n if (sharedSampleMode) sharedSampleMode.value = on;\n else localSampleMode.value = on;\n },\n});\n\nconst previewBlocks = computed(\n () => previewResolution.content.value.blocks ?? [],\n);\n\nconst hasAllowlist = computed(\n () =>\n props.allowedRecipients !== undefined && props.allowedRecipients.length > 0,\n);\nconst isSingleRecipient = computed(() => props.allowedRecipients?.length === 1);\n\n// Mounted behind a `v-if` on the same state that drives `visible`, so it can\n// mount with `visible` already true — without `immediate` the field would never\n// be seeded.\nwatch(\n () => props.visible,\n (visible) => {\n if (!visible) return;\n recipient.value =\n props.defaultRecipient ?? props.allowedRecipients?.[0] ?? \"\";\n },\n { immediate: true },\n);\n\n/** Shape-check free text only; a picker can't produce a malformed value. */\nconst isRecipientValid = computed(() =>\n hasAllowlist.value\n ? recipient.value.length > 0\n : looksLikeEmail(recipient.value),\n);\n\nconst canSend = computed(\n () => isRecipientValid.value && !props.isSending && !props.justSent,\n);\n\n/** Free text that's been typed into but isn't yet a plausible address. */\nconst showShapeHint = computed(\n () =>\n !hasAllowlist.value &&\n recipient.value.trim().length > 0 &&\n !isRecipientValid.value,\n);\n\nconst errorText = computed(() => {\n if (!props.error) return null;\n return props.error.kind === \"provider\"\n ? props.error.message\n : t.testEmail.recipientNotAllowed;\n});\n\nfunction handleSend(): void {\n if (!canSend.value) return;\n emit(\"send\", recipient.value.trim());\n}\n\nfunction handleClose(): void {\n // Sending is the one state that blocks dismissal; the success state closes\n // immediately rather than making the user wait out the timer.\n if (props.isSending) return;\n emit(\"close\");\n}\n\nfunction handleKeydown(event: KeyboardEvent): void {\n if (event.key === \"Enter\" && !event.shiftKey) {\n event.preventDefault();\n handleSend();\n }\n if (event.key === \"Escape\") {\n handleClose();\n }\n}\n</script>\n\n<template>\n <TplModal :visible=\"visible\" @close=\"handleClose\" @keydown=\"handleKeydown\">\n <!-- Grows to `max-w-2xl` only while the preview is open, so a 600px email\n renders at roughly true size — the same reason SaveBlockDialog picked\n that width. Collapsed, it stays the compact form it was. `max-h-[90vh]`\n with a `min-h-0 overflow-y-auto` preview region is the pattern the\n saved-blocks dialogs use: `min-h-0` is what lets a flex child shrink\n below its content and actually scroll. -->\n <div\n role=\"dialog\"\n aria-modal=\"true\"\n :aria-busy=\"isSending\"\n aria-labelledby=\"tpl-test-email-title\"\n class=\"tpl-scale-in tpl:mx-4 tpl:flex tpl:max-h-[90vh] tpl:w-full tpl:max-w-2xl tpl:flex-col tpl:rounded-[var(--tpl-radius-lg)] tpl:p-5\"\n style=\"\n background-color: var(--tpl-bg-elevated);\n box-shadow: var(--tpl-shadow-xl);\n \"\n >\n <h3\n id=\"tpl-test-email-title\"\n class=\"tpl:mb-4 tpl:shrink-0 tpl:text-sm tpl:font-semibold tpl:text-[var(--tpl-text)]\"\n >\n {{ t.testEmail.title }}\n </h3>\n\n <!-- Recipient -->\n <div class=\"tpl:mb-3 tpl:shrink-0\">\n <label\n for=\"tpl-test-email-recipient\"\n class=\"tpl:mb-1.5 tpl:block tpl:text-sm tpl:font-medium tpl:text-[var(--tpl-text-muted)]\"\n >\n {{ t.testEmail.recipientLabel }}\n </label>\n\n <!-- Exactly one allowed address: nothing to choose. -->\n <input\n v-if=\"isSingleRecipient\"\n id=\"tpl-test-email-recipient\"\n type=\"text\"\n :value=\"recipient\"\n disabled\n data-testid=\"test-email-recipient\"\n class=\"tpl:h-9 tpl:w-full tpl:rounded-md tpl:border tpl:px-3 tpl:py-1 tpl:text-sm tpl:opacity-70 tpl:shadow-xs tpl:outline-none tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:text-[var(--tpl-text)]\"\n />\n\n <!-- Several allowed addresses: pick one. -->\n <select\n v-else-if=\"hasAllowlist\"\n id=\"tpl-test-email-recipient\"\n v-model=\"recipient\"\n :disabled=\"isSending || justSent\"\n data-testid=\"test-email-recipient\"\n class=\"tpl:h-9 tpl:w-full tpl:rounded-md tpl:border tpl:px-3 tpl:py-1 tpl:text-sm tpl:shadow-xs tpl:outline-none tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:text-[var(--tpl-text)]\"\n >\n <option\n v-for=\"email in allowedRecipients\"\n :key=\"email\"\n :value=\"email\"\n >\n {{ email }}\n </option>\n </select>\n\n <!-- No allowlist: free text. -->\n <input\n v-else\n id=\"tpl-test-email-recipient\"\n v-model=\"recipient\"\n type=\"email\"\n autocomplete=\"email\"\n spellcheck=\"false\"\n :placeholder=\"t.testEmail.recipientPlaceholder\"\n :disabled=\"isSending || justSent\"\n data-testid=\"test-email-recipient\"\n class=\"tpl:h-9 tpl:w-full tpl:rounded-md tpl:border tpl:px-3 tpl:py-1 tpl:text-sm tpl:shadow-xs tpl:outline-none tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:text-[var(--tpl-text)]\"\n />\n\n <p\n v-if=\"showShapeHint\"\n class=\"tpl:mt-1.5 tpl:text-xs tpl:text-[var(--tpl-text-dim)]\"\n >\n {{ t.testEmail.invalidAddress }}\n </p>\n </div>\n\n <!-- Preview header: a label plus the viewport switch. Always shown — the\n preview is part of the dialog rather than something to opt into. -->\n <div class=\"tpl:mb-2 tpl:shrink-0\">\n <!-- Two segmented controls: `flex-wrap` so they drop to their own row\n rather than crushing the label on a narrow viewport. -->\n <div\n class=\"tpl:flex tpl:flex-wrap tpl:items-center tpl:justify-between tpl:gap-2\"\n >\n <span\n class=\"tpl:text-sm tpl:font-medium tpl:text-[var(--tpl-text-muted)]\"\n >\n {{ t.testEmail.preview }}\n </span>\n\n <div class=\"tpl:flex tpl:items-center tpl:gap-2\">\n <MergeTagModeToggle\n v-if=\"!previewResolution.supersedesSamples.value\"\n :sample-mode=\"sampleMode\"\n @change=\"sampleMode = $event\"\n />\n\n <!-- Without this the preview would silently show only the desktop\n variant of any responsive block. -->\n <ViewportToggle\n :viewport=\"previewViewport\"\n @change=\"previewViewport = $event\"\n />\n </div>\n </div>\n\n <!-- The hint has to follow the mode: \"shown unresolved\" is simply false\n once every tag renders a realistic value. -->\n <p\n data-testid=\"test-email-preview-hint\"\n class=\"tpl:mt-1 tpl:text-xs tpl:text-[var(--tpl-text-dim)]\"\n >\n {{\n previewResolution.supersedesSamples.value\n ? t.previewResolution.hint\n : sampleMode\n ? t.testEmail.previewHintSample\n : t.testEmail.previewHint\n }}\n </p>\n </div>\n\n <!-- The one region allowed to grow: `flex-1 min-h-0` so it absorbs the\n remaining height and scrolls, leaving the form and actions fixed. -->\n <div\n id=\"tpl-test-email-preview\"\n data-testid=\"test-email-preview\"\n class=\"tpl:mb-3 tpl:min-h-0 tpl:flex-1 tpl:overflow-y-auto tpl:rounded-[var(--tpl-radius-sm)] tpl:p-3 tpl:bg-[var(--tpl-canvas-bg)]\"\n >\n <!-- Skeleton while a *first* resolve is outstanding, so the dialog never\n flashes the unresolved template before the resolved one. Sized like\n the frame it replaces to avoid a layout jump. A re-resolve (recipient\n change) keeps the previous result instead — `isInitialResolve`. -->\n <div\n v-if=\"previewResolution.isInitialResolve.value\"\n role=\"status\"\n aria-busy=\"true\"\n data-testid=\"preview-resolution-loading\"\n class=\"tpl:flex tpl:flex-col tpl:gap-3\"\n >\n <span class=\"tpl:sr-only\">{{ t.previewResolution.resolving }}</span>\n <div\n v-for=\"n in 3\"\n :key=\"n\"\n aria-hidden=\"true\"\n class=\"tpl-pulse tpl:h-16 tpl:rounded-md tpl:bg-[var(--tpl-bg-hover)]\"\n ></div>\n </div>\n\n <!-- Resolution failed: the unresolved template renders below, so the\n dialog degrades rather than showing an empty frame. -->\n <p\n v-else-if=\"previewResolution.hasFailed.value\"\n role=\"status\"\n data-testid=\"preview-resolution-failed\"\n class=\"tpl:mb-2 tpl:rounded-md tpl:px-3 tpl:py-2 tpl:text-xs tpl:text-[var(--tpl-text-muted)] tpl:bg-[var(--tpl-bg-hover)]\"\n >\n {{ t.previewResolution.failed }}\n </p>\n\n <BlockPreviewCanvas\n v-if=\"!previewResolution.isInitialResolve.value\"\n :blocks=\"previewBlocks\"\n :viewport=\"previewViewport\"\n :apply-condition-filter=\"!previewResolution.isConfigured\"\n />\n </div>\n\n <!-- Success. Announced politely; the dialog closes itself shortly after. -->\n <p\n v-if=\"justSent\"\n role=\"status\"\n data-testid=\"test-email-success\"\n class=\"tpl:mb-3 tpl:flex tpl:shrink-0 tpl:items-center tpl:gap-1.5 tpl:text-xs tpl:text-[var(--tpl-success,var(--tpl-primary))]\"\n >\n <Check :size=\"13\" :stroke-width=\"2.5\" />\n {{ t.testEmail.success }}\n </p>\n\n <!-- Failure. Stays open so the user can retry. -->\n <p\n v-else-if=\"errorText\"\n role=\"alert\"\n data-testid=\"test-email-error\"\n class=\"tpl:mb-3 tpl:shrink-0 tpl:text-xs tpl:text-[var(--tpl-danger)]\"\n >\n {{ errorText }}\n </p>\n\n <!-- Actions -->\n <div class=\"tpl:flex tpl:shrink-0 tpl:justify-end tpl:gap-2\">\n <button\n type=\"button\"\n data-testid=\"test-email-cancel\"\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-sm tpl:font-medium tpl:shadow-xs tpl:transition-all tpl:duration-150 tpl:border-[var(--tpl-border)] tpl:text-[var(--tpl-text)] tpl:bg-[var(--tpl-bg)]\"\n :disabled=\"isSending\"\n :class=\"{ 'tpl:cursor-not-allowed tpl:opacity-50': isSending }\"\n @click=\"handleClose\"\n >\n {{ t.testEmail.cancel }}\n </button>\n <button\n type=\"button\"\n data-testid=\"test-email-send\"\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:px-3 tpl:py-1.5 tpl:text-sm tpl:font-medium tpl:shadow-xs tpl:transition-all tpl:duration-150 tpl:hover:opacity-90 tpl:disabled:cursor-not-allowed tpl:disabled:opacity-50 tpl:bg-[var(--tpl-primary)] tpl:text-[var(--tpl-bg)]\"\n :disabled=\"!canSend\"\n @click=\"handleSend\"\n >\n <span v-if=\"isSending\" class=\"tpl:flex tpl:items-center tpl:gap-1.5\">\n <LoaderCircle\n class=\"tpl:animate-spin\"\n :size=\"12\"\n :stroke-width=\"2\"\n />\n {{ t.testEmail.sending }}\n </span>\n <span v-else>\n {{ t.testEmail.send }}\n </span>\n </button>\n </div>\n </div>\n </TplModal>\n</template>\n","<script setup lang=\"ts\">\n/**\n * Send-a-test dialog, shared by the OSS and Cloud editors.\n *\n * The recipient control has three shapes, driven entirely by whether the\n * provider configured an allowlist:\n *\n * - **no allowlist** — free text, validated for shape only. This branch exists\n * for BYO senders; Cloud never takes it, because its list comes from a signed\n * JWT.\n * - **exactly one** — a read-only field. Preserved verbatim from the Cloud-only\n * version; there is nothing to choose, so offering an editable field would\n * imply otherwise.\n * - **several** — a picker of exactly those addresses.\n *\n * An empty allowlist never reaches here: the feature reports itself unavailable,\n * so no trigger renders. (The Cloud-only version fell through to `v-else` and\n * rendered a select with no options and a permanently disabled Send.)\n */\nimport TplModal from \"./TplModal.vue\";\n// Statically imported: this modal is already lazy-loaded as a whole, so the\n// preview rides its chunk rather than costing a second round-trip and flashing\n// an empty frame. Still shared with the saved-blocks surfaces, which lazy-load\n// it themselves.\nimport BlockPreviewCanvas from \"./BlockPreviewCanvas.vue\";\n// The editor header's own control, reused verbatim so the two viewport switches\n// are the same component rather than two things that resemble each other.\nimport ViewportToggle from \"./ViewportToggle.vue\";\nimport MergeTagModeToggle from \"./MergeTagModeToggle.vue\";\nimport { useI18n } from \"../composables/useI18n\";\nimport { looksLikeEmail } from \"../utils/validateEmailShape\";\nimport { Check, LoaderCircle } from \"@lucide/vue\";\nimport { computed, inject, ref, watch } from \"vue\";\nimport { hasMergeTagSamples, type ViewportSize } from \"@templatical/types\";\nimport {\n EDITOR_KEY,\n MERGE_TAGS_KEY,\n MERGE_TAG_SAMPLE_MODE_KEY,\n RESOLVE_PREVIEW_KEY,\n} from \"../keys\";\nimport { usePreviewResolution } from \"../composables/usePreviewResolution\";\nimport type { TestEmailError } from \"../composables/useTestEmailFeature\";\n\nconst props = defineProps<{\n visible: boolean;\n /** Omitted / undefined means unrestricted, so the field accepts free text. */\n allowedRecipients?: string[];\n defaultRecipient?: string;\n isSending: boolean;\n justSent: boolean;\n error: TestEmailError | null;\n}>();\n\nconst emit = defineEmits<{\n (e: \"send\", recipient: string): void;\n (e: \"close\"): void;\n}>();\n\nconst { t } = useI18n();\nconst editor = inject(EDITOR_KEY, null);\n\nconst recipient = ref(\"\");\n\nconst previewViewport = ref<ViewportSize>(\"desktop\");\n\n/**\n * Its own resolution instance rather than the editor's, because this dialog has\n * a recipient and the editor's preview does not — resolving for \"whoever is\n * selected here\" is a different question from resolving for the canvas.\n * `isActive` is the dialog's own visibility.\n */\nconst resolvePreviewHook = inject(RESOLVE_PREVIEW_KEY, undefined);\nconst previewResolution = usePreviewResolution({\n resolvePreview: resolvePreviewHook ?? undefined,\n getContent: () =>\n editor?.content.value ?? { blocks: [], settings: {} as never },\n isActive: () => props.visible,\n getRecipient: () => recipient.value || undefined,\n});\n\n/**\n * Shared with the editor's own preview, so switching here and switching there\n * are the same choice — it is a property of how the user wants to read a\n * preview, not of one dialog. Falls back to a local ref for headless mounts.\n */\nconst sharedSampleMode = inject(MERGE_TAG_SAMPLE_MODE_KEY, null);\n// Seeded from availability, matching how `useEditorCore` seeds the shared ref.\n// A blanket `true` would make the dialog claim tags \"show example values\" for a\n// consumer who configured none — false, and with no toggle rendered to correct\n// it, since the toggle hides itself when nothing has a sample.\nconst localSampleMode = ref(hasMergeTagSamples(inject(MERGE_TAGS_KEY, [])));\nconst sampleMode = computed({\n get: () =>\n // Off entirely while a resolver is configured — same rule as the canvas.\n previewResolution.isConfigured\n ? false\n : (sharedSampleMode?.value ?? localSampleMode.value),\n set: (on: boolean) => {\n if (sharedSampleMode) sharedSampleMode.value = on;\n else localSampleMode.value = on;\n },\n});\n\nconst previewBlocks = computed(\n () => previewResolution.content.value.blocks ?? [],\n);\n\nconst hasAllowlist = computed(\n () =>\n props.allowedRecipients !== undefined && props.allowedRecipients.length > 0,\n);\nconst isSingleRecipient = computed(() => props.allowedRecipients?.length === 1);\n\n// Mounted behind a `v-if` on the same state that drives `visible`, so it can\n// mount with `visible` already true — without `immediate` the field would never\n// be seeded.\nwatch(\n () => props.visible,\n (visible) => {\n if (!visible) return;\n recipient.value =\n props.defaultRecipient ?? props.allowedRecipients?.[0] ?? \"\";\n },\n { immediate: true },\n);\n\n/** Shape-check free text only; a picker can't produce a malformed value. */\nconst isRecipientValid = computed(() =>\n hasAllowlist.value\n ? recipient.value.length > 0\n : looksLikeEmail(recipient.value),\n);\n\nconst canSend = computed(\n () => isRecipientValid.value && !props.isSending && !props.justSent,\n);\n\n/** Free text that's been typed into but isn't yet a plausible address. */\nconst showShapeHint = computed(\n () =>\n !hasAllowlist.value &&\n recipient.value.trim().length > 0 &&\n !isRecipientValid.value,\n);\n\nconst errorText = computed(() => {\n if (!props.error) return null;\n return props.error.kind === \"provider\"\n ? props.error.message\n : t.testEmail.recipientNotAllowed;\n});\n\nfunction handleSend(): void {\n if (!canSend.value) return;\n emit(\"send\", recipient.value.trim());\n}\n\nfunction handleClose(): void {\n // Sending is the one state that blocks dismissal; the success state closes\n // immediately rather than making the user wait out the timer.\n if (props.isSending) return;\n emit(\"close\");\n}\n\nfunction handleKeydown(event: KeyboardEvent): void {\n if (event.key === \"Enter\" && !event.shiftKey) {\n event.preventDefault();\n handleSend();\n }\n if (event.key === \"Escape\") {\n handleClose();\n }\n}\n</script>\n\n<template>\n <TplModal :visible=\"visible\" @close=\"handleClose\" @keydown=\"handleKeydown\">\n <!-- Grows to `max-w-2xl` only while the preview is open, so a 600px email\n renders at roughly true size — the same reason SaveBlockDialog picked\n that width. Collapsed, it stays the compact form it was. `max-h-[90vh]`\n with a `min-h-0 overflow-y-auto` preview region is the pattern the\n saved-blocks dialogs use: `min-h-0` is what lets a flex child shrink\n below its content and actually scroll. -->\n <div\n role=\"dialog\"\n aria-modal=\"true\"\n :aria-busy=\"isSending\"\n aria-labelledby=\"tpl-test-email-title\"\n class=\"tpl-scale-in tpl:mx-4 tpl:flex tpl:max-h-[90vh] tpl:w-full tpl:max-w-2xl tpl:flex-col tpl:rounded-[var(--tpl-radius-lg)] tpl:p-5\"\n style=\"\n background-color: var(--tpl-bg-elevated);\n box-shadow: var(--tpl-shadow-xl);\n \"\n >\n <h3\n id=\"tpl-test-email-title\"\n class=\"tpl:mb-4 tpl:shrink-0 tpl:text-sm tpl:font-semibold tpl:text-[var(--tpl-text)]\"\n >\n {{ t.testEmail.title }}\n </h3>\n\n <!-- Recipient -->\n <div class=\"tpl:mb-3 tpl:shrink-0\">\n <label\n for=\"tpl-test-email-recipient\"\n class=\"tpl:mb-1.5 tpl:block tpl:text-sm tpl:font-medium tpl:text-[var(--tpl-text-muted)]\"\n >\n {{ t.testEmail.recipientLabel }}\n </label>\n\n <!-- Exactly one allowed address: nothing to choose. -->\n <input\n v-if=\"isSingleRecipient\"\n id=\"tpl-test-email-recipient\"\n type=\"text\"\n :value=\"recipient\"\n disabled\n data-testid=\"test-email-recipient\"\n class=\"tpl:h-9 tpl:w-full tpl:rounded-md tpl:border tpl:px-3 tpl:py-1 tpl:text-sm tpl:opacity-70 tpl:shadow-xs tpl:outline-none tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:text-[var(--tpl-text)]\"\n />\n\n <!-- Several allowed addresses: pick one. -->\n <select\n v-else-if=\"hasAllowlist\"\n id=\"tpl-test-email-recipient\"\n v-model=\"recipient\"\n :disabled=\"isSending || justSent\"\n data-testid=\"test-email-recipient\"\n class=\"tpl:h-9 tpl:w-full tpl:rounded-md tpl:border tpl:px-3 tpl:py-1 tpl:text-sm tpl:shadow-xs tpl:outline-none tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:text-[var(--tpl-text)]\"\n >\n <option\n v-for=\"email in allowedRecipients\"\n :key=\"email\"\n :value=\"email\"\n >\n {{ email }}\n </option>\n </select>\n\n <!-- No allowlist: free text. -->\n <input\n v-else\n id=\"tpl-test-email-recipient\"\n v-model=\"recipient\"\n type=\"email\"\n autocomplete=\"email\"\n spellcheck=\"false\"\n :placeholder=\"t.testEmail.recipientPlaceholder\"\n :disabled=\"isSending || justSent\"\n data-testid=\"test-email-recipient\"\n class=\"tpl:h-9 tpl:w-full tpl:rounded-md tpl:border tpl:px-3 tpl:py-1 tpl:text-sm tpl:shadow-xs tpl:outline-none tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:text-[var(--tpl-text)]\"\n />\n\n <p\n v-if=\"showShapeHint\"\n class=\"tpl:mt-1.5 tpl:text-xs tpl:text-[var(--tpl-text-dim)]\"\n >\n {{ t.testEmail.invalidAddress }}\n </p>\n </div>\n\n <!-- Preview header: a label plus the viewport switch. Always shown — the\n preview is part of the dialog rather than something to opt into. -->\n <div class=\"tpl:mb-2 tpl:shrink-0\">\n <!-- Two segmented controls: `flex-wrap` so they drop to their own row\n rather than crushing the label on a narrow viewport. -->\n <div\n class=\"tpl:flex tpl:flex-wrap tpl:items-center tpl:justify-between tpl:gap-2\"\n >\n <span\n class=\"tpl:text-sm tpl:font-medium tpl:text-[var(--tpl-text-muted)]\"\n >\n {{ t.testEmail.preview }}\n </span>\n\n <div class=\"tpl:flex tpl:items-center tpl:gap-2\">\n <MergeTagModeToggle\n v-if=\"!previewResolution.supersedesSamples.value\"\n :sample-mode=\"sampleMode\"\n @change=\"sampleMode = $event\"\n />\n\n <!-- Without this the preview would silently show only the desktop\n variant of any responsive block. -->\n <ViewportToggle\n :viewport=\"previewViewport\"\n @change=\"previewViewport = $event\"\n />\n </div>\n </div>\n\n <!-- The hint has to follow the mode: \"shown unresolved\" is simply false\n once every tag renders a realistic value. -->\n <p\n data-testid=\"test-email-preview-hint\"\n class=\"tpl:mt-1 tpl:text-xs tpl:text-[var(--tpl-text-dim)]\"\n >\n {{\n previewResolution.supersedesSamples.value\n ? t.previewResolution.hint\n : sampleMode\n ? t.testEmail.previewHintSample\n : t.testEmail.previewHint\n }}\n </p>\n </div>\n\n <!-- The one region allowed to grow: `flex-1 min-h-0` so it absorbs the\n remaining height and scrolls, leaving the form and actions fixed. -->\n <div\n id=\"tpl-test-email-preview\"\n data-testid=\"test-email-preview\"\n class=\"tpl:mb-3 tpl:min-h-0 tpl:flex-1 tpl:overflow-y-auto tpl:rounded-[var(--tpl-radius-sm)] tpl:p-3 tpl:bg-[var(--tpl-canvas-bg)]\"\n >\n <!-- Skeleton while a *first* resolve is outstanding, so the dialog never\n flashes the unresolved template before the resolved one. Sized like\n the frame it replaces to avoid a layout jump. A re-resolve (recipient\n change) keeps the previous result instead — `isInitialResolve`. -->\n <div\n v-if=\"previewResolution.isInitialResolve.value\"\n role=\"status\"\n aria-busy=\"true\"\n data-testid=\"preview-resolution-loading\"\n class=\"tpl:flex tpl:flex-col tpl:gap-3\"\n >\n <span class=\"tpl:sr-only\">{{ t.previewResolution.resolving }}</span>\n <div\n v-for=\"n in 3\"\n :key=\"n\"\n aria-hidden=\"true\"\n class=\"tpl-pulse tpl:h-16 tpl:rounded-md tpl:bg-[var(--tpl-bg-hover)]\"\n ></div>\n </div>\n\n <!-- Resolution failed: the unresolved template renders below, so the\n dialog degrades rather than showing an empty frame. -->\n <p\n v-else-if=\"previewResolution.hasFailed.value\"\n role=\"status\"\n data-testid=\"preview-resolution-failed\"\n class=\"tpl:mb-2 tpl:rounded-md tpl:px-3 tpl:py-2 tpl:text-xs tpl:text-[var(--tpl-text-muted)] tpl:bg-[var(--tpl-bg-hover)]\"\n >\n {{ t.previewResolution.failed }}\n </p>\n\n <BlockPreviewCanvas\n v-if=\"!previewResolution.isInitialResolve.value\"\n :blocks=\"previewBlocks\"\n :viewport=\"previewViewport\"\n :apply-condition-filter=\"!previewResolution.isConfigured\"\n />\n </div>\n\n <!-- Success. Announced politely; the dialog closes itself shortly after. -->\n <p\n v-if=\"justSent\"\n role=\"status\"\n data-testid=\"test-email-success\"\n class=\"tpl:mb-3 tpl:flex tpl:shrink-0 tpl:items-center tpl:gap-1.5 tpl:text-xs tpl:text-[var(--tpl-success,var(--tpl-primary))]\"\n >\n <Check :size=\"13\" :stroke-width=\"2.5\" />\n {{ t.testEmail.success }}\n </p>\n\n <!-- Failure. Stays open so the user can retry. -->\n <p\n v-else-if=\"errorText\"\n role=\"alert\"\n data-testid=\"test-email-error\"\n class=\"tpl:mb-3 tpl:shrink-0 tpl:text-xs tpl:text-[var(--tpl-danger)]\"\n >\n {{ errorText }}\n </p>\n\n <!-- Actions -->\n <div class=\"tpl:flex tpl:shrink-0 tpl:justify-end tpl:gap-2\">\n <button\n type=\"button\"\n data-testid=\"test-email-cancel\"\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-sm tpl:font-medium tpl:shadow-xs tpl:transition-all tpl:duration-150 tpl:border-[var(--tpl-border)] tpl:text-[var(--tpl-text)] tpl:bg-[var(--tpl-bg)]\"\n :disabled=\"isSending\"\n :class=\"{ 'tpl:cursor-not-allowed tpl:opacity-50': isSending }\"\n @click=\"handleClose\"\n >\n {{ t.testEmail.cancel }}\n </button>\n <button\n type=\"button\"\n data-testid=\"test-email-send\"\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:px-3 tpl:py-1.5 tpl:text-sm tpl:font-medium tpl:shadow-xs tpl:transition-all tpl:duration-150 tpl:hover:opacity-90 tpl:disabled:cursor-not-allowed tpl:disabled:opacity-50 tpl:bg-[var(--tpl-primary)] tpl:text-[var(--tpl-bg)]\"\n :disabled=\"!canSend\"\n @click=\"handleSend\"\n >\n <span v-if=\"isSending\" class=\"tpl:flex tpl:items-center tpl:gap-1.5\">\n <LoaderCircle\n class=\"tpl:animate-spin\"\n :size=\"12\"\n :stroke-width=\"2\"\n />\n {{ t.testEmail.sending }}\n </span>\n <span v-else>\n {{ t.testEmail.send }}\n </span>\n </button>\n </div>\n </div>\n </TplModal>\n</template>\n"],"mappings":"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"}
@@ -1,11 +1,11 @@
1
- import { M as e, b as t, h as n, it as r, m as i, x as a } from "./draggable-Z4XXmbPC.js";
1
+ import { M as e, b as t, h as n, it as r, m as i, x as a } from "./draggable-DCs9qL-b.js";
2
2
  //#endregion
3
3
  //#region src/components/TestEmailPanel.vue
4
4
  var o = /* @__PURE__ */ a({
5
5
  __name: "TestEmailPanel",
6
6
  props: { feature: {} },
7
7
  setup(a) {
8
- let o = a, s = t(() => import("./TestEmailModal-BcPjQygV.js"));
8
+ let o = a, s = t(() => import("./TestEmailModal-DLeoJqaJ.js"));
9
9
  function c(e) {
10
10
  o.feature.send(e);
11
11
  }
@@ -32,4 +32,4 @@ var o = /* @__PURE__ */ a({
32
32
  //#endregion
33
33
  export { o as default };
34
34
 
35
- //# sourceMappingURL=TestEmailPanel-1zxRa00s.js.map
35
+ //# sourceMappingURL=TestEmailPanel-Dr7Dkq2P.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"TestEmailPanel-1zxRa00s.js","names":[],"sources":["../../../src/components/TestEmailPanel.vue","../../../src/components/TestEmailPanel.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * Mount point for the test-email dialog, shared by the OSS and Cloud editors.\n *\n * The dialog is `defineAsyncComponent` behind a `v-if` on its open state, so its\n * chunk is fetched only when the user actually opens it — `defineAsyncComponent`\n * triggers its `import()` on first render, not at definition time. The editors in\n * turn lazy-load *this* wrapper and render it only when a `TestEmailProvider` is\n * configured, so a consumer without one downloads none of it.\n *\n * Never static-import `TestEmailModal` into `Editor.vue` / `CloudEditor.vue`:\n * that collapses it into the main entry for every consumer, including those who\n * never configure a sender. Guarded by `tests/cdn-chunk-granularity.test.ts`.\n */\nimport { defineAsyncComponent } from \"vue\";\nimport type { UseTestEmailFeatureReturn } from \"../composables/useTestEmailFeature\";\n\nconst props = defineProps<{\n feature: UseTestEmailFeatureReturn;\n}>();\n\nconst TestEmailModal = defineAsyncComponent(\n () => import(\"./TestEmailModal.vue\"),\n);\n\nfunction handleSend(recipient: string): void {\n void props.feature.send(recipient);\n}\n</script>\n\n<template>\n <TestEmailModal\n v-if=\"feature.isModalOpen.value\"\n :visible=\"feature.isModalOpen.value\"\n :allowed-recipients=\"feature.allowedRecipients.value\"\n :default-recipient=\"feature.defaultRecipient.value\"\n :is-sending=\"feature.isSending.value\"\n :just-sent=\"feature.justSent.value\"\n :error=\"feature.error.value\"\n @send=\"handleSend\"\n @close=\"feature.close()\"\n />\n</template>\n","<script setup lang=\"ts\">\n/**\n * Mount point for the test-email dialog, shared by the OSS and Cloud editors.\n *\n * The dialog is `defineAsyncComponent` behind a `v-if` on its open state, so its\n * chunk is fetched only when the user actually opens it — `defineAsyncComponent`\n * triggers its `import()` on first render, not at definition time. The editors in\n * turn lazy-load *this* wrapper and render it only when a `TestEmailProvider` is\n * configured, so a consumer without one downloads none of it.\n *\n * Never static-import `TestEmailModal` into `Editor.vue` / `CloudEditor.vue`:\n * that collapses it into the main entry for every consumer, including those who\n * never configure a sender. Guarded by `tests/cdn-chunk-granularity.test.ts`.\n */\nimport { defineAsyncComponent } from \"vue\";\nimport type { UseTestEmailFeatureReturn } from \"../composables/useTestEmailFeature\";\n\nconst props = defineProps<{\n feature: UseTestEmailFeatureReturn;\n}>();\n\nconst TestEmailModal = defineAsyncComponent(\n () => import(\"./TestEmailModal.vue\"),\n);\n\nfunction handleSend(recipient: string): void {\n void props.feature.send(recipient);\n}\n</script>\n\n<template>\n <TestEmailModal\n v-if=\"feature.isModalOpen.value\"\n :visible=\"feature.isModalOpen.value\"\n :allowed-recipients=\"feature.allowedRecipients.value\"\n :default-recipient=\"feature.defaultRecipient.value\"\n :is-sending=\"feature.isSending.value\"\n :just-sent=\"feature.justSent.value\"\n :error=\"feature.error.value\"\n @send=\"handleSend\"\n @close=\"feature.close()\"\n />\n</template>\n"],"mappings":";;;;;;;EAiBA,IAAM,IAAQ,GAIR,IAAiB,QACf,OAAO,+BACf;EAEA,SAAS,EAAW,GAAyB;GAC3C,EAAW,QAAQ,KAAK,CAAS;EACnC;mBAKU,EAAA,QAAQ,YAAY,SAD5B,EAAA,GAAA,EAUE,EAAA,CAAA,GAAA;;GARC,SAAS,EAAA,QAAQ,YAAY;GAC7B,sBAAoB,EAAA,QAAQ,kBAAkB;GAC9C,qBAAmB,EAAA,QAAQ,iBAAiB;GAC5C,cAAY,EAAA,QAAQ,UAAU;GAC9B,aAAW,EAAA,QAAQ,SAAS;GAC5B,OAAO,EAAA,QAAQ,MAAM;GACrB,QAAM;GACN,SAAK,AAAA,EAAA,QAAA,MAAE,EAAA,QAAQ,MAAK"}
1
+ {"version":3,"file":"TestEmailPanel-Dr7Dkq2P.js","names":[],"sources":["../../../src/components/TestEmailPanel.vue","../../../src/components/TestEmailPanel.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * Mount point for the test-email dialog, shared by the OSS and Cloud editors.\n *\n * The dialog is `defineAsyncComponent` behind a `v-if` on its open state, so its\n * chunk is fetched only when the user actually opens it — `defineAsyncComponent`\n * triggers its `import()` on first render, not at definition time. The editors in\n * turn lazy-load *this* wrapper and render it only when a `TestEmailProvider` is\n * configured, so a consumer without one downloads none of it.\n *\n * Never static-import `TestEmailModal` into `Editor.vue` / `CloudEditor.vue`:\n * that collapses it into the main entry for every consumer, including those who\n * never configure a sender. Guarded by `tests/cdn-chunk-granularity.test.ts`.\n */\nimport { defineAsyncComponent } from \"vue\";\nimport type { UseTestEmailFeatureReturn } from \"../composables/useTestEmailFeature\";\n\nconst props = defineProps<{\n feature: UseTestEmailFeatureReturn;\n}>();\n\nconst TestEmailModal = defineAsyncComponent(\n () => import(\"./TestEmailModal.vue\"),\n);\n\nfunction handleSend(recipient: string): void {\n void props.feature.send(recipient);\n}\n</script>\n\n<template>\n <TestEmailModal\n v-if=\"feature.isModalOpen.value\"\n :visible=\"feature.isModalOpen.value\"\n :allowed-recipients=\"feature.allowedRecipients.value\"\n :default-recipient=\"feature.defaultRecipient.value\"\n :is-sending=\"feature.isSending.value\"\n :just-sent=\"feature.justSent.value\"\n :error=\"feature.error.value\"\n @send=\"handleSend\"\n @close=\"feature.close()\"\n />\n</template>\n","<script setup lang=\"ts\">\n/**\n * Mount point for the test-email dialog, shared by the OSS and Cloud editors.\n *\n * The dialog is `defineAsyncComponent` behind a `v-if` on its open state, so its\n * chunk is fetched only when the user actually opens it — `defineAsyncComponent`\n * triggers its `import()` on first render, not at definition time. The editors in\n * turn lazy-load *this* wrapper and render it only when a `TestEmailProvider` is\n * configured, so a consumer without one downloads none of it.\n *\n * Never static-import `TestEmailModal` into `Editor.vue` / `CloudEditor.vue`:\n * that collapses it into the main entry for every consumer, including those who\n * never configure a sender. Guarded by `tests/cdn-chunk-granularity.test.ts`.\n */\nimport { defineAsyncComponent } from \"vue\";\nimport type { UseTestEmailFeatureReturn } from \"../composables/useTestEmailFeature\";\n\nconst props = defineProps<{\n feature: UseTestEmailFeatureReturn;\n}>();\n\nconst TestEmailModal = defineAsyncComponent(\n () => import(\"./TestEmailModal.vue\"),\n);\n\nfunction handleSend(recipient: string): void {\n void props.feature.send(recipient);\n}\n</script>\n\n<template>\n <TestEmailModal\n v-if=\"feature.isModalOpen.value\"\n :visible=\"feature.isModalOpen.value\"\n :allowed-recipients=\"feature.allowedRecipients.value\"\n :default-recipient=\"feature.defaultRecipient.value\"\n :is-sending=\"feature.isSending.value\"\n :just-sent=\"feature.justSent.value\"\n :error=\"feature.error.value\"\n @send=\"handleSend\"\n @close=\"feature.close()\"\n />\n</template>\n"],"mappings":";;;;;;;EAiBA,IAAM,IAAQ,GAIR,IAAiB,QACf,OAAO,+BACf;EAEA,SAAS,EAAW,GAAyB;GAC3C,EAAW,QAAQ,KAAK,CAAS;EACnC;mBAKU,EAAA,QAAQ,YAAY,SAD5B,EAAA,GAAA,EAUE,EAAA,CAAA,GAAA;;GARC,SAAS,EAAA,QAAQ,YAAY;GAC7B,sBAAoB,EAAA,QAAQ,kBAAkB;GAC9C,qBAAmB,EAAA,QAAQ,iBAAiB;GAC5C,cAAY,EAAA,QAAQ,UAAU;GAC9B,aAAW,EAAA,QAAQ,SAAS;GAC5B,OAAO,EAAA,QAAQ,MAAM;GACrB,QAAM;GACN,SAAK,AAAA,EAAA,QAAA,MAAE,EAAA,QAAQ,MAAK"}
@@ -1,10 +1,10 @@
1
- import { K as e, M as t, T as n, ct as r, d as i, f as a, g as o, h as s, it as c, m as l, ot as u, p as d, st as ee, u as f, v as p, x as m, y as h } from "./draggable-Z4XXmbPC.js";
2
- import { S as te, x as ne } from "./useEditorCore-BMXBmdd8.js";
3
- import { O as re, T as g, U as _, V as v } from "./keys-DsoX52o_.js";
4
- import { t as y } from "./useI18n-BvcEYzrI.js";
5
- import { n as b } from "./usePopoverPosition-ig-71caO.js";
6
- import { P as ie, Rt as ae, Vt as oe, ct as se, it as ce, ut as x } from "./icons-C_kRN0_C.js";
7
- import { n as S, t as C } from "./RichTextEditorContent-BeJx4LVH.js";
1
+ import { K as e, M as t, T as n, ct as r, d as i, f as a, g as o, h as s, it as c, lt as l, m as u, p as d, st as f, u as p, v as m, x as h, y as g } from "./draggable-DCs9qL-b.js";
2
+ import { S as ee, x as te } from "./useEditorCore-C_nUXiph.js";
3
+ import { O as ne, T as _, U as v, V as y } from "./keys-Cmc2Yl3D.js";
4
+ import { t as b } from "./useI18n-UMs0F71x.js";
5
+ import { n as x } from "./usePopoverPosition-cf8Y-ynK.js";
6
+ import { P as re, Rt as ie, Vt as ae, ct as oe, it as se, ut as ce } from "./icons-CvYN6zrM.js";
7
+ import { n as S, t as C } from "./RichTextEditorContent-DtL49Jpo.js";
8
8
  //#region src/components/blocks/TitleEditor.vue?vue&type=script&setup=true&lang.ts
9
9
  var w = { class: "tpl-text-editor-wrapper tpl:relative" }, T = ["data-tpl-theme", "aria-label"], E = ["aria-label", "title"], D = ["aria-label", "title"], O = ["aria-label", "title"], k = {
10
10
  key: 0,
@@ -15,25 +15,25 @@ var w = { class: "tpl-text-editor-wrapper tpl:relative" }, T = ["data-tpl-theme"
15
15
  }, M = ["aria-label", "title"], N = {
16
16
  key: 1,
17
17
  class: "tpl:flex tpl:items-center tpl:gap-2 tpl:px-2 tpl:text-xs tpl:text-[var(--tpl-text-dim)]"
18
- }, P = /* @__PURE__ */ m({
18
+ }, P = /* @__PURE__ */ h({
19
19
  __name: "TitleEditor",
20
20
  props: {
21
21
  block: {},
22
22
  toolbarPosition: {}
23
23
  },
24
24
  emits: ["done"],
25
- setup(m, { emit: P }) {
26
- let F = m, I = P, L = n(v, null), R = n(_, null), z = b(), B = n(re, null), le = n(g, null), V = a(() => (B?.isOpen.value ?? !1) || (le?.isOpen.value ?? !1)), { t: H } = y(), { editor: U, EditorContent: W, isLoading: G, initError: ue, retry: de, showLinkDialog: fe, linkUrl: K, linkColor: q, linkDialogRef: J, canRequestMergeTag: Y, canInsertLogicTag: X, openLinkDialog: Z, insertLink: pe, removeLink: me, closeLinkDialog: he, handleLinkKeydown: ge, handleAddMergeTag: Q, handleAddLogicTag: $ } = ne({
25
+ setup(h, { emit: P }) {
26
+ let F = h, I = P, L = n(y, null), R = n(v, null), z = x(), B = n(ne, null), V = n(_, null), le = a(() => (B?.isOpen.value ?? !1) || (V?.isOpen.value ?? !1)), { t: H } = b(), { editor: U, EditorContent: W, isLoading: G, initError: ue, retry: de, showLinkDialog: fe, linkUrl: K, linkColor: q, linkDialogRef: J, canRequestMergeTag: Y, canInsertLogicTag: X, openLinkDialog: Z, insertLink: pe, removeLink: me, closeLinkDialog: he, handleLinkKeydown: ge, handleAddMergeTag: Q, handleAddLogicTag: $ } = te({
27
27
  blockId: () => F.block.id,
28
28
  blockContent: () => F.block.content,
29
29
  onDone: () => I("done"),
30
30
  editorName: "TitleEditor",
31
31
  async loadExtensions({ mergeTags: e, syntax: t, triggerChar: n, autocompleteEnabled: r, suggestionEmptyText: i }) {
32
32
  let [{ Editor: a, EditorContent: o }, { default: s }, { default: c }, { MergeTagNode: l, MergeTagSuggestion: u, LogicMergeTagNode: d }] = await Promise.all([
33
- import("./tiptap-C5vzWlHv.js").then((e) => e.r),
34
- import("./tiptap-C5vzWlHv.js").then((e) => e.i),
35
- import("./tiptap-C5vzWlHv.js").then((e) => e.l),
36
- import("./extensions-Dq0D79tq.js")
33
+ import("./tiptap-C44S1Cmh.js").then((e) => e.r),
34
+ import("./tiptap-C44S1Cmh.js").then((e) => e.i),
35
+ import("./tiptap-C44S1Cmh.js").then((e) => e.l),
36
+ import("./extensions-B7laxHjs.js")
37
37
  ]);
38
38
  return {
39
39
  TiptapEditor: a,
@@ -50,7 +50,7 @@ var w = { class: "tpl-text-editor-wrapper tpl:relative" }, T = ["data-tpl-theme"
50
50
  strike: !1,
51
51
  link: !1
52
52
  }),
53
- te(c).configure({
53
+ ee(c).configure({
54
54
  openOnClick: !1,
55
55
  HTMLAttributes: {
56
56
  target: "_blank",
@@ -73,7 +73,7 @@ var w = { class: "tpl-text-editor-wrapper tpl:relative" }, T = ["data-tpl-theme"
73
73
  }
74
74
  });
75
75
  return (n, a) => (t(), o("div", w, [
76
- c(z) && !V.value ? (t(), l(i, {
76
+ c(z) && !le.value ? (t(), u(i, {
77
77
  key: 0,
78
78
  to: c(z)
79
79
  }, [d("div", {
@@ -81,30 +81,30 @@ var w = { class: "tpl-text-editor-wrapper tpl:relative" }, T = ["data-tpl-theme"
81
81
  role: "toolbar",
82
82
  "aria-label": c(H).titleEditor.toolbar,
83
83
  class: "tpl tpl-text-toolbar tpl:absolute tpl:z-popover tpl:flex tpl:items-center tpl:gap-1 tpl:rounded-lg tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:px-3 tpl:py-2 tpl:shadow-lg",
84
- style: ee({
84
+ style: r({
85
85
  ...c(L),
86
- top: `${m.toolbarPosition.top}px`,
87
- left: `${m.toolbarPosition.left}px`,
86
+ top: `${h.toolbarPosition.top}px`,
87
+ left: `${h.toolbarPosition.left}px`,
88
88
  transform: "translateY(-100%)"
89
89
  })
90
- }, [!c(G) && c(U) ? (t(), o(f, { key: 0 }, [
90
+ }, [!c(G) && c(U) ? (t(), o(p, { key: 0 }, [
91
91
  d("button", {
92
92
  type: "button",
93
- class: u(["tpl-text-toolbar-btn", { "tpl-text-toolbar-btn--active": c(U)?.isActive("bold") }]),
93
+ class: f(["tpl-text-toolbar-btn", { "tpl-text-toolbar-btn--active": c(U)?.isActive("bold") }]),
94
94
  "aria-label": c(H).titleEditor.bold,
95
95
  title: c(H).titleEditor.bold,
96
96
  onClick: a[0] ||= (e) => c(U)?.chain().focus().toggleBold().run()
97
- }, [h(c(oe), {
97
+ }, [g(c(ae), {
98
98
  size: 16,
99
99
  "stroke-width": 2.5
100
100
  })], 10, E),
101
101
  d("button", {
102
102
  type: "button",
103
- class: u(["tpl-text-toolbar-btn", { "tpl-text-toolbar-btn--active": c(U)?.isActive("italic") }]),
103
+ class: f(["tpl-text-toolbar-btn", { "tpl-text-toolbar-btn--active": c(U)?.isActive("italic") }]),
104
104
  "aria-label": c(H).titleEditor.italic,
105
105
  title: c(H).titleEditor.italic,
106
106
  onClick: a[1] ||= (e) => c(U)?.chain().focus().toggleItalic().run()
107
- }, [h(c(x), {
107
+ }, [g(c(ce), {
108
108
  size: 16,
109
109
  "stroke-width": 2
110
110
  })], 10, D),
@@ -114,11 +114,11 @@ var w = { class: "tpl-text-editor-wrapper tpl:relative" }, T = ["data-tpl-theme"
114
114
  }, null, -1),
115
115
  d("button", {
116
116
  type: "button",
117
- class: u(["tpl-text-toolbar-btn", { "tpl-text-toolbar-btn--active": c(U)?.isActive("link") }]),
117
+ class: f(["tpl-text-toolbar-btn", { "tpl-text-toolbar-btn--active": c(U)?.isActive("link") }]),
118
118
  "aria-label": c(H).titleEditor.addLink,
119
119
  title: c(H).titleEditor.addLink,
120
120
  onClick: a[2] ||= (...e) => c(Z) && c(Z)(...e)
121
- }, [h(c(se), {
121
+ }, [g(c(oe), {
122
122
  size: 16,
123
123
  "stroke-width": 2
124
124
  })], 10, O),
@@ -130,10 +130,10 @@ var w = { class: "tpl-text-editor-wrapper tpl:relative" }, T = ["data-tpl-theme"
130
130
  "aria-label": c(H).mergeTag.insert,
131
131
  title: c(H).mergeTag.insert,
132
132
  onClick: a[3] ||= (...e) => c(Q) && c(Q)(...e)
133
- }, [h(c(ie), {
133
+ }, [g(c(re), {
134
134
  size: 16,
135
135
  "stroke-width": 2
136
- }), p(" " + r(c(H).mergeTag.insertShort), 1)], 8, A)) : s("", !0),
136
+ }), m(" " + l(c(H).mergeTag.insertShort), 1)], 8, A)) : s("", !0),
137
137
  c(X) ? (t(), o("span", j)) : s("", !0),
138
138
  c(X) ? (t(), o("button", {
139
139
  key: 3,
@@ -143,16 +143,16 @@ var w = { class: "tpl-text-editor-wrapper tpl:relative" }, T = ["data-tpl-theme"
143
143
  title: c(H).logicTag.insert,
144
144
  "data-testid": "insert-logic-button",
145
145
  onClick: a[4] ||= (...e) => c($) && c($)(...e)
146
- }, [h(c(ae), {
146
+ }, [g(c(ie), {
147
147
  size: 16,
148
148
  "stroke-width": 2
149
- }), p(" " + r(c(H).logicTag.insertShort), 1)], 8, M)) : s("", !0)
150
- ], 64)) : (t(), o("div", N, [h(c(ce), {
149
+ }), m(" " + l(c(H).logicTag.insertShort), 1)], 8, M)) : s("", !0)
150
+ ], 64)) : (t(), o("div", N, [g(c(se), {
151
151
  class: "tpl-spinner",
152
152
  size: 14,
153
153
  "stroke-width": 2
154
- }), p(" " + r(c(H).errors.editorLoading), 1)]))], 12, T)], 8, ["to"])) : s("", !0),
155
- h(C, {
154
+ }), m(" " + l(c(H).errors.editorLoading), 1)]))], 12, T)], 8, ["to"])) : s("", !0),
155
+ g(C, {
156
156
  editor: c(U),
157
157
  "editor-content": c(W),
158
158
  "is-loading": c(G),
@@ -165,7 +165,7 @@ var w = { class: "tpl-text-editor-wrapper tpl:relative" }, T = ["data-tpl-theme"
165
165
  "init-error",
166
166
  "onRetry"
167
167
  ]),
168
- h(S, {
168
+ g(S, {
169
169
  visible: c(fe),
170
170
  "is-editing-link": c(U)?.isActive("link") ?? !1,
171
171
  "dialog-ref": c(J),
@@ -195,4 +195,4 @@ var w = { class: "tpl-text-editor-wrapper tpl:relative" }, T = ["data-tpl-theme"
195
195
  //#endregion
196
196
  export { P as default };
197
197
 
198
- //# sourceMappingURL=TitleEditor-DTrmublj.js.map
198
+ //# sourceMappingURL=TitleEditor-DzYRr18C.js.map