@templatical/editor 0.26.2 → 0.27.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 (285) hide show
  1. package/README.md +3 -1
  2. package/dist/{AiChatSidebar-CMBz6Wix.js → AiChatSidebar-iVJDzqjr.js} +60 -60
  3. package/dist/AiFeatureMenu-DvrK-dzj.js +57 -0
  4. package/dist/{BlockIssueBadge-Buk3_6QF.js → BlockIssueBadge-BHysq7XQ.js} +11 -11
  5. package/dist/BlockPreviewCanvas-GZbr0ph5.js +121 -0
  6. package/dist/CloudHeaderExtras-Cx8gFWu2.js +59 -0
  7. package/dist/CloudPanels-D0X449DD.js +227 -0
  8. package/dist/{CollaboratorBar-zvfvE7f-.js → CollaboratorBar-DMSWCrX3.js} +21 -21
  9. package/dist/ColorPicker-DCjFQCAg.js +501 -0
  10. package/dist/CommentsPanel-D5wQB1Kf.js +18 -0
  11. package/dist/CommentsSidebar-BoTIxpLE.js +481 -0
  12. package/dist/{cdn/chunks/CountdownBlock-vLTkTI9e.js → CountdownBlock-BFX8iU90.js} +19 -21
  13. package/dist/{cdn/chunks/CountdownToolbar-DPjcgcVR.js → CountdownToolbar-CSftc9Ma.js} +52 -53
  14. package/dist/{DesignReferenceSidebar-Bx96XFmK.js → DesignReferenceSidebar-DKsx2TiL.js} +72 -71
  15. package/dist/{IssuesPanel-Do7RLXsP.js → IssuesPanel-flKKwKpM.js} +38 -38
  16. package/dist/{LoadingTrack-BexpNuTV.js → LoadingTrack-C2T8ixEL.js} +3 -3
  17. package/dist/{MergeTagModeToggle-P-UguQHc.js → MergeTagModeToggle-PKCoKAoF.js} +43 -43
  18. package/dist/{MergeTagSuggestion-BJ43ihWR.js → MergeTagSuggestion-DGha0WRp.js} +46 -46
  19. package/dist/{cdn/chunks/NumberWithSuffix-BXrRdfq1.js → NumberWithSuffix-DdPCXfKf.js} +42 -44
  20. package/dist/{ParagraphEditor-CLvv5Ija.js → ParagraphEditor-vDU-5y6r.js} +202 -199
  21. package/dist/RestoreVersionDialog-DIn1muFD.js +86 -0
  22. package/dist/{RichTextEditorContent-fbYJXAio.js → RichTextEditorContent-I83k5DtQ.js} +44 -44
  23. package/dist/SaveBlockDialog-C_QvYAeL.js +266 -0
  24. package/dist/{SavedBlocksBrowserModal-KWlaiLlr.js → SavedBlocksBrowserModal-CefWWcdT.js} +116 -115
  25. package/dist/{cdn/chunks/SavedBlocksPanels-e4HyTZ8v.js → SavedBlocksPanels-DnQwO-7O.js} +9 -11
  26. package/dist/{SavedBlocksPickBar-C3h8oL3r.js → SavedBlocksPickBar-BsFubArH.js} +17 -17
  27. package/dist/{TemplateScoringPanel-BZUsb3FD.js → TemplateScoringPanel-Bv_bBsJi.js} +87 -87
  28. package/dist/TestEmailModal-STPWDP79.js +206 -0
  29. package/dist/{cdn/chunks/TestEmailPanel-CZAG1gQE.js → TestEmailPanel-HYVjApKZ.js} +4 -6
  30. package/dist/TitleEditor-DnB0EJ_g.js +198 -0
  31. package/dist/TplModal-chvLhXmC.js +49 -0
  32. package/dist/VersionHistoryMenu-Cq4rH56t.js +155 -0
  33. package/dist/VersionHistoryPanels-D7k_F7-9.js +46 -0
  34. package/dist/VersionPreviewBanner-DthyHe6C.js +38 -0
  35. package/dist/{blockTypeIcons-2_85L_CL.js → blockTypeIcons-B8r4jMBn.js} +6 -6
  36. package/dist/bundle-stats.json +8 -8
  37. package/dist/{ca-D1zgI9fQ.js → ca-Cqg9O8mx.js} +69 -6
  38. package/dist/cdn/chunks/{AiChatSidebar-CQ8KUmNB.js → AiChatSidebar-DTUoBH5a.js} +54 -54
  39. package/dist/cdn/chunks/AiChatSidebar-DTUoBH5a.js.map +1 -0
  40. package/dist/cdn/chunks/AiFeatureMenu-Dyh7mUMB.js +57 -0
  41. package/dist/cdn/chunks/AiFeatureMenu-Dyh7mUMB.js.map +1 -0
  42. package/dist/cdn/chunks/{BlockIssueBadge-BEEY9o2o.js → BlockIssueBadge-UzSSOM_2.js} +13 -13
  43. package/dist/cdn/chunks/{BlockIssueBadge-BEEY9o2o.js.map → BlockIssueBadge-UzSSOM_2.js.map} +1 -1
  44. package/dist/cdn/chunks/{BlockPreviewCanvas-DPtiMsVz.js → BlockPreviewCanvas-DStQFo9P.js} +42 -42
  45. package/dist/cdn/chunks/{BlockPreviewCanvas-DPtiMsVz.js.map → BlockPreviewCanvas-DStQFo9P.js.map} +1 -1
  46. package/dist/cdn/chunks/CloudHeaderExtras-BZVu7dnO.js +60 -0
  47. package/dist/cdn/chunks/CloudHeaderExtras-BZVu7dnO.js.map +1 -0
  48. package/dist/cdn/chunks/CloudPanels-CXbaBKHQ.js +227 -0
  49. package/dist/cdn/chunks/CloudPanels-CXbaBKHQ.js.map +1 -0
  50. package/dist/cdn/chunks/{CollaboratorBar-BF469_AN.js → CollaboratorBar-BH_lZlat.js} +22 -22
  51. package/dist/cdn/chunks/CollaboratorBar-BH_lZlat.js.map +1 -0
  52. package/dist/cdn/chunks/ColorPicker-CStXen0Y.js +502 -0
  53. package/dist/cdn/chunks/ColorPicker-CStXen0Y.js.map +1 -0
  54. package/dist/cdn/chunks/CommentsPanel-jzZ2X0P4.js +20 -0
  55. package/dist/cdn/chunks/CommentsPanel-jzZ2X0P4.js.map +1 -0
  56. package/dist/cdn/chunks/CommentsSidebar-Dxke-vUP.js +460 -0
  57. package/dist/cdn/chunks/CommentsSidebar-Dxke-vUP.js.map +1 -0
  58. package/dist/{CountdownBlock-DuyvGcr8.js → cdn/chunks/CountdownBlock-BYSrJWxg.js} +25 -23
  59. package/dist/cdn/chunks/{CountdownBlock-vLTkTI9e.js.map → CountdownBlock-BYSrJWxg.js.map} +1 -1
  60. package/dist/{CountdownToolbar-CBE9wRaV.js → cdn/chunks/CountdownToolbar-CZPtQIb8.js} +66 -63
  61. package/dist/cdn/chunks/{CountdownToolbar-DPjcgcVR.js.map → CountdownToolbar-CZPtQIb8.js.map} +1 -1
  62. package/dist/cdn/chunks/{DesignReferenceSidebar-B-HFXV0W.js → DesignReferenceSidebar-sZWciECR.js} +64 -64
  63. package/dist/cdn/chunks/DesignReferenceSidebar-sZWciECR.js.map +1 -0
  64. package/dist/cdn/chunks/{IssuesPanel-DKTS4QVb.js → IssuesPanel-CHtetM9i.js} +36 -36
  65. package/dist/cdn/chunks/IssuesPanel-CHtetM9i.js.map +1 -0
  66. package/dist/cdn/chunks/{LoadingTrack-C52ZGL5q.js → LoadingTrack-Dp_QZvAK.js} +3 -3
  67. package/dist/cdn/chunks/{LoadingTrack-C52ZGL5q.js.map → LoadingTrack-Dp_QZvAK.js.map} +1 -1
  68. package/dist/cdn/chunks/{MergeTagModeToggle-DQ79SqQc.js → MergeTagModeToggle-ZwW-5EeN.js} +39 -39
  69. package/dist/cdn/chunks/{MergeTagModeToggle-DQ79SqQc.js.map → MergeTagModeToggle-ZwW-5EeN.js.map} +1 -1
  70. package/dist/cdn/chunks/{MergeTagSuggestion-Dn8dExNa.js → MergeTagSuggestion-DhllqTA3.js} +47 -47
  71. package/dist/cdn/chunks/MergeTagSuggestion-DhllqTA3.js.map +1 -0
  72. package/dist/{NumberWithSuffix-D37ZWiZh.js → cdn/chunks/NumberWithSuffix-yeQLcCyH.js} +37 -35
  73. package/dist/cdn/chunks/NumberWithSuffix-yeQLcCyH.js.map +1 -0
  74. package/dist/cdn/chunks/{ParagraphEditor-Bzcma_Lm.js → ParagraphEditor-BJJ925sx.js} +235 -233
  75. package/dist/cdn/chunks/ParagraphEditor-BJJ925sx.js.map +1 -0
  76. package/dist/cdn/chunks/RestoreVersionDialog-B0Jadu6F.js +87 -0
  77. package/dist/cdn/chunks/RestoreVersionDialog-B0Jadu6F.js.map +1 -0
  78. package/dist/cdn/chunks/{RichTextEditorContent-DtL49Jpo.js → RichTextEditorContent-CfSq2wPR.js} +44 -44
  79. package/dist/cdn/chunks/RichTextEditorContent-CfSq2wPR.js.map +1 -0
  80. package/dist/cdn/chunks/SaveBlockDialog-C1DXh6P9.js +265 -0
  81. package/dist/cdn/chunks/SaveBlockDialog-C1DXh6P9.js.map +1 -0
  82. package/dist/cdn/chunks/{SavedBlocksBrowserModal-C59D4G5C.js → SavedBlocksBrowserModal-BerMxAUt.js} +114 -113
  83. package/dist/cdn/chunks/SavedBlocksBrowserModal-BerMxAUt.js.map +1 -0
  84. package/dist/{SavedBlocksPanels-CQsZvWO_.js → cdn/chunks/SavedBlocksPanels-BUE4lzVP.js} +11 -9
  85. package/dist/cdn/chunks/{SavedBlocksPanels-e4HyTZ8v.js.map → SavedBlocksPanels-BUE4lzVP.js.map} +1 -1
  86. package/dist/cdn/chunks/{SavedBlocksPickBar-DMHxPCM4.js → SavedBlocksPickBar-4HrqQfvl.js} +17 -17
  87. package/dist/cdn/chunks/SavedBlocksPickBar-4HrqQfvl.js.map +1 -0
  88. package/dist/cdn/chunks/{TemplateScoringPanel-NucRtAP0.js → TemplateScoringPanel-Xdkp2EMY.js} +81 -81
  89. package/dist/cdn/chunks/TemplateScoringPanel-Xdkp2EMY.js.map +1 -0
  90. package/dist/cdn/chunks/TestEmailModal-yfmFiavR.js +206 -0
  91. package/dist/cdn/chunks/TestEmailModal-yfmFiavR.js.map +1 -0
  92. package/dist/{TestEmailPanel-B6goIAxT.js → cdn/chunks/TestEmailPanel-DqTDh5ri.js} +6 -4
  93. package/dist/cdn/chunks/{TestEmailPanel-CZAG1gQE.js.map → TestEmailPanel-DqTDh5ri.js.map} +1 -1
  94. package/dist/cdn/chunks/{TitleEditor-D0_LY2Ex.js → TitleEditor-nUAF7fKr.js} +85 -84
  95. package/dist/cdn/chunks/TitleEditor-nUAF7fKr.js.map +1 -0
  96. package/dist/cdn/chunks/TplModal-C0IqikoV.js +50 -0
  97. package/dist/cdn/chunks/TplModal-C0IqikoV.js.map +1 -0
  98. package/dist/cdn/chunks/VersionHistoryMenu-_MAj3qZj.js +148 -0
  99. package/dist/cdn/chunks/VersionHistoryMenu-_MAj3qZj.js.map +1 -0
  100. package/dist/cdn/chunks/VersionHistoryPanels-6Hz9w20W.js +48 -0
  101. package/dist/cdn/chunks/VersionHistoryPanels-6Hz9w20W.js.map +1 -0
  102. package/dist/cdn/chunks/VersionPreviewBanner-D098_6_I.js +40 -0
  103. package/dist/cdn/chunks/VersionPreviewBanner-D098_6_I.js.map +1 -0
  104. package/dist/cdn/chunks/{blockTypeIcons-FXlMM3PL.js → blockTypeIcons-hX_W4dqo.js} +2 -2
  105. package/dist/cdn/chunks/{blockTypeIcons-FXlMM3PL.js.map → blockTypeIcons-hX_W4dqo.js.map} +1 -1
  106. package/dist/cdn/chunks/{ca-D1zgI9fQ.js → ca-Cqg9O8mx.js} +70 -7
  107. package/dist/cdn/chunks/ca-Cqg9O8mx.js.map +1 -0
  108. package/dist/{cloud-qINCmQbn.js → cdn/chunks/cloud-BA0OpRh-.js} +400 -609
  109. package/dist/cdn/chunks/cloud-BA0OpRh-.js.map +1 -0
  110. package/dist/cdn/chunks/createCloudRuntime-DmzV7OnU.js +344 -0
  111. package/dist/cdn/chunks/createCloudRuntime-DmzV7OnU.js.map +1 -0
  112. package/dist/{de-DHra3lvx.js → cdn/chunks/de-BXmeeFbX.js} +71 -6
  113. package/dist/cdn/chunks/de-BXmeeFbX.js.map +1 -0
  114. package/dist/{de-uVoSP1xF.js → cdn/chunks/de-Dbwm2cON.js} +3 -54
  115. package/dist/cdn/chunks/de-Dbwm2cON.js.map +1 -0
  116. package/dist/cdn/chunks/{dist-B2HKGHnc.js → dist-CM8TB0VD.js} +136 -136
  117. package/dist/cdn/chunks/{dist-B2HKGHnc.js.map → dist-CM8TB0VD.js.map} +1 -1
  118. package/dist/cdn/chunks/{dist-C6z8AjBC.js → dist-CYLJiSiH.js} +63 -63
  119. package/dist/cdn/chunks/{dist-C6z8AjBC.js.map → dist-CYLJiSiH.js.map} +1 -1
  120. package/dist/cdn/chunks/{draggable-DCs9qL-b.js → draggable-BfpsXFg7.js} +916 -905
  121. package/dist/cdn/chunks/{draggable-DCs9qL-b.js.map → draggable-BfpsXFg7.js.map} +1 -1
  122. package/dist/cdn/chunks/{en-DlqX2YWZ.js → en-DbBNy67B.js} +70 -7
  123. package/dist/cdn/chunks/en-DbBNy67B.js.map +1 -0
  124. package/dist/{en-CjhsJxb-.js → cdn/chunks/en-Dzw4EqNI.js} +3 -54
  125. package/dist/cdn/chunks/en-Dzw4EqNI.js.map +1 -0
  126. package/dist/cdn/chunks/{es-Cuss2snv.js → es-JUsAjxWu.js} +70 -7
  127. package/dist/cdn/chunks/es-JUsAjxWu.js.map +1 -0
  128. package/dist/cdn/chunks/{extensions-C7h52yuk.js → extensions-CMcVw2uu.js} +81 -67
  129. package/dist/cdn/chunks/extensions-CMcVw2uu.js.map +1 -0
  130. package/dist/{fr-Cm85vB2g.js → cdn/chunks/fr-J3qgXH4h.js} +71 -6
  131. package/dist/cdn/chunks/fr-J3qgXH4h.js.map +1 -0
  132. package/dist/cdn/chunks/{icons-CvYN6zrM.js → icons-C36Ha5VU.js} +11 -11
  133. package/dist/cdn/chunks/{icons-CvYN6zrM.js.map → icons-C36Ha5VU.js.map} +1 -1
  134. package/dist/{keys-fwF3qZax.js → cdn/chunks/keys-CbQuRX85.js} +6 -4
  135. package/dist/cdn/chunks/keys-CbQuRX85.js.map +1 -0
  136. package/dist/cdn/chunks/logger-ByvdoAwq.js +23 -0
  137. package/dist/cdn/chunks/logger-ByvdoAwq.js.map +1 -0
  138. package/dist/cdn/chunks/{nl-dmiIX7oT.js → nl-D_4DkqOi.js} +70 -7
  139. package/dist/cdn/chunks/nl-D_4DkqOi.js.map +1 -0
  140. package/dist/cdn/chunks/preRenderCustomBlocks-CbA3RcAy.js +23 -0
  141. package/dist/cdn/chunks/preRenderCustomBlocks-CbA3RcAy.js.map +1 -0
  142. package/dist/{pt-BR-CTzHTqJD.js → cdn/chunks/pt-BR-BxzQzWGJ.js} +3 -54
  143. package/dist/cdn/chunks/pt-BR-BxzQzWGJ.js.map +1 -0
  144. package/dist/cdn/chunks/{pt-BR-CaIG-7EM.js → pt-BR-KGURyEEQ.js} +70 -7
  145. package/dist/cdn/chunks/pt-BR-KGURyEEQ.js.map +1 -0
  146. package/dist/cdn/chunks/{src-CUKUsRzX.js → src-BOjFLNf7.js} +148 -134
  147. package/dist/cdn/chunks/src-BOjFLNf7.js.map +1 -0
  148. package/dist/cdn/chunks/{src-xkGep9eN.js → src-Dq3fgSIx.js} +1448 -1458
  149. package/dist/cdn/chunks/src-Dq3fgSIx.js.map +1 -0
  150. package/dist/cdn/chunks/styleConstants-ByDqaYj_.js +61 -0
  151. package/dist/cdn/chunks/styleConstants-ByDqaYj_.js.map +1 -0
  152. package/dist/cdn/chunks/timeouts-DHPZQ0G3.js +6 -0
  153. package/dist/cdn/chunks/timeouts-DHPZQ0G3.js.map +1 -0
  154. package/dist/cdn/chunks/{tiptap-C44S1Cmh.js → tiptap-DmpGap5g.js} +40 -40
  155. package/dist/cdn/chunks/{tiptap-C44S1Cmh.js.map → tiptap-DmpGap5g.js.map} +1 -1
  156. package/dist/cdn/chunks/{useAliveFlag-Dxwgo6P4.js → useAliveFlag-Dtm174fG.js} +2 -2
  157. package/dist/cdn/chunks/{useAliveFlag-Dxwgo6P4.js.map → useAliveFlag-Dtm174fG.js.map} +1 -1
  158. package/dist/cdn/chunks/{useCloudI18n-8wkJtuJD.js → useCloudI18n-NVa1aeY8.js} +5 -5
  159. package/dist/cdn/chunks/{useCloudI18n-8wkJtuJD.js.map → useCloudI18n-NVa1aeY8.js.map} +1 -1
  160. package/dist/cdn/chunks/{useEditorCore-CKQ9gmoo.js → useEditorCore-CTu_qZ3W.js} +1434 -1147
  161. package/dist/cdn/chunks/useEditorCore-CTu_qZ3W.js.map +1 -0
  162. package/dist/cdn/chunks/{useI18n-UMs0F71x.js → useI18n-DVWCJJFH.js} +5 -5
  163. package/dist/cdn/chunks/{useI18n-UMs0F71x.js.map → useI18n-DVWCJJFH.js.map} +1 -1
  164. package/dist/cdn/chunks/{useMergeTag-JmXOtpln.js → useMergeTag-BfZBRPQp.js} +4 -4
  165. package/dist/cdn/chunks/{useMergeTag-JmXOtpln.js.map → useMergeTag-BfZBRPQp.js.map} +1 -1
  166. package/dist/cdn/chunks/{usePopoverPosition-cf8Y-ynK.js → usePopoverPosition-BC-HA7TK.js} +3 -3
  167. package/dist/cdn/chunks/{usePopoverPosition-cf8Y-ynK.js.map → usePopoverPosition-BC-HA7TK.js.map} +1 -1
  168. package/dist/cdn/editor.css +1 -1
  169. package/dist/cdn/editor.js +5103 -284
  170. package/dist/cdn/editor.js.map +1 -1
  171. package/dist/{check-On4TSac-.js → check-DZbmkMQe.js} +1 -1
  172. package/dist/{chevron-down-69EiBGoB.js → chevron-down-nYScOLeZ.js} +1 -1
  173. package/dist/{chevron-up-Dh3MG1Dw.js → chevron-up-CpYpDLnc.js} +1 -1
  174. package/dist/{circle-alert-cilwzafX.js → circle-alert-CuuK6thq.js} +1 -1
  175. package/dist/{clock-Dt0APH92.js → clock-DxkrtqOr.js} +1 -1
  176. package/dist/cloud-BAefVBb_.js +1444 -0
  177. package/dist/createCloudRuntime-Bt8W-ajI.js +342 -0
  178. package/dist/{createLucideIcon-C8WzBYCv.js → createLucideIcon-L-wQXW_R.js} +9 -9
  179. package/dist/{cdn/chunks/de-DHra3lvx.js → de-BXmeeFbX.js} +69 -8
  180. package/dist/{cdn/chunks/de-uVoSP1xF.js → de-Dbwm2cON.js} +1 -56
  181. package/dist/dist-B5iReTtK.js +251 -0
  182. package/dist/{dist-Cl8J0IES.js → dist-Ck9roB6K.js} +42 -42
  183. package/dist/{dist-BpKxrUi2.js → dist-CqcGTIiX.js} +46 -46
  184. package/dist/{en-DlqX2YWZ.js → en-DbBNy67B.js} +69 -6
  185. package/dist/{cdn/chunks/en-CjhsJxb-.js → en-Dzw4EqNI.js} +1 -56
  186. package/dist/{es-Cuss2snv.js → es-JUsAjxWu.js} +69 -6
  187. package/dist/{extensions-DzKNPITR.js → extensions-CX87G4aL.js} +71 -57
  188. package/dist/{cdn/chunks/fr-Cm85vB2g.js → fr-J3qgXH4h.js} +69 -8
  189. package/dist/{image-up-Ceggh_R3.js → image-up-DouZRGVt.js} +1 -1
  190. package/dist/index.d.ts +499 -23
  191. package/dist/{info-DO5odBcZ.js → info-puX5V6Iq.js} +1 -1
  192. package/dist/{cdn/chunks/keys-Cmc2Yl3D.js → keys-BxixS0TN.js} +4 -6
  193. package/dist/{list-checks-BSkPKbuI.js → list-checks-jw-ZlvWY.js} +1 -1
  194. package/dist/{loader-circle-bO7o7_HD.js → loader-circle-DqTxB7Kr.js} +1 -1
  195. package/dist/logger-BBlvSn6q.js +21 -0
  196. package/dist/{message-circle-BEkAk65X.js → message-circle-BZ7YpB22.js} +1 -1
  197. package/dist/{nl-dmiIX7oT.js → nl-D_4DkqOi.js} +69 -6
  198. package/dist/{pencil-t6m0EtKL.js → pencil-DmbRhTJu.js} +1 -1
  199. package/dist/preRenderCustomBlocks-BebjKtmw.js +21 -0
  200. package/dist/{cdn/chunks/pt-BR-CTzHTqJD.js → pt-BR-BxzQzWGJ.js} +1 -56
  201. package/dist/{pt-BR-CaIG-7EM.js → pt-BR-KGURyEEQ.js} +69 -6
  202. package/dist/{refresh-cw-CkOYVzK5.js → refresh-cw-OK_O-cL0.js} +1 -1
  203. package/dist/{runtime-dom.esm-bundler-CepEmvwF.js → runtime-dom.esm-bundler-BgKXIPLV.js} +144 -144
  204. package/dist/{scan-line-DwSVj25O.js → scan-line-DyaSTsJ5.js} +1 -1
  205. package/dist/{send-CWsWN8PQ.js → send-B-wasJJC.js} +1 -1
  206. package/dist/{shield-check-qje8Kufw.js → shield-check-DUIyRrTo.js} +1 -1
  207. package/dist/{sparkles-DumaWRW-.js → sparkles-DYEMATWC.js} +1 -1
  208. package/dist/style.css +1 -1
  209. package/dist/styleConstants-ByDqaYj_.js +59 -0
  210. package/dist/templatical-editor.js +5303 -289
  211. package/dist/{text-align-start-Ir8Ah4mT.js → text-align-start-CzjLeBLe.js} +1 -1
  212. package/dist/timeouts-DHPZQ0G3.js +4 -0
  213. package/dist/{trash-2-CklAFCmO.js → trash-2-C0shEPry.js} +1 -1
  214. package/dist/{triangle-alert-BBi4n93g.js → triangle-alert-Fw0ce8oQ.js} +1 -1
  215. package/dist/{upload-sVqqppuv.js → upload-D69C2CBx.js} +3 -4
  216. package/dist/{useAliveFlag-cvyBxHp7.js → useAliveFlag-BGIAZii_.js} +1 -1
  217. package/dist/{useCloudI18n-DKVm6PCa.js → useCloudI18n-CAZTZ9_a.js} +2 -2
  218. package/dist/{useEditorCore-idmPLUeb.js → useEditorCore-B-kONNRw.js} +1604 -1320
  219. package/dist/{useI18n-DoVKdCTH.js → useI18n-BfXff_Sy.js} +2 -2
  220. package/dist/{useMergeTag-CYZk7dZD.js → useMergeTag-Dd4I7kge.js} +3 -3
  221. package/dist/usePopoverPosition-CxhCX3SQ.js +59 -0
  222. package/dist/{vue.runtime.esm-bundler-D1diBMsm.js → vue.runtime.esm-bundler-DzEsDRtY.js} +447 -436
  223. package/dist/{x-Mby7KlhS.js → x-BjTlFqRv.js} +1 -1
  224. package/package.json +7 -7
  225. package/dist/AiFeatureMenu-BY6mubnR.js +0 -63
  226. package/dist/BlockPreviewCanvas-DbNOqSI1.js +0 -121
  227. package/dist/CloudEditor-DEnMNMbm.js +0 -1188
  228. package/dist/ColorPicker-ClETsjA3.js +0 -550
  229. package/dist/CommentsSidebar-CdRCCSDD.js +0 -435
  230. package/dist/SaveBlockDialog-fVp_PwZi.js +0 -265
  231. package/dist/SnapshotHistory-DjA3Ej_i.js +0 -128
  232. package/dist/TestEmailModal-EM0RP_9L.js +0 -205
  233. package/dist/TitleEditor-JEaP9jVF.js +0 -197
  234. package/dist/TplModal-DoneXIj_.js +0 -49
  235. package/dist/cdn/chunks/AiChatSidebar-CQ8KUmNB.js.map +0 -1
  236. package/dist/cdn/chunks/AiFeatureMenu-Cwj8afsi.js +0 -63
  237. package/dist/cdn/chunks/AiFeatureMenu-Cwj8afsi.js.map +0 -1
  238. package/dist/cdn/chunks/CloudEditor-CQ830jJt.js +0 -1166
  239. package/dist/cdn/chunks/CloudEditor-CQ830jJt.js.map +0 -1
  240. package/dist/cdn/chunks/CollaboratorBar-BF469_AN.js.map +0 -1
  241. package/dist/cdn/chunks/ColorPicker-W_UyiVGc.js +0 -552
  242. package/dist/cdn/chunks/ColorPicker-W_UyiVGc.js.map +0 -1
  243. package/dist/cdn/chunks/CommentsSidebar-BLRZRSSC.js +0 -412
  244. package/dist/cdn/chunks/CommentsSidebar-BLRZRSSC.js.map +0 -1
  245. package/dist/cdn/chunks/DesignReferenceSidebar-B-HFXV0W.js.map +0 -1
  246. package/dist/cdn/chunks/IssuesPanel-DKTS4QVb.js.map +0 -1
  247. package/dist/cdn/chunks/MergeTagSuggestion-Dn8dExNa.js.map +0 -1
  248. package/dist/cdn/chunks/NumberWithSuffix-BXrRdfq1.js.map +0 -1
  249. package/dist/cdn/chunks/ParagraphEditor-Bzcma_Lm.js.map +0 -1
  250. package/dist/cdn/chunks/RichTextEditorContent-DtL49Jpo.js.map +0 -1
  251. package/dist/cdn/chunks/SaveBlockDialog-BfNVeXXV.js +0 -264
  252. package/dist/cdn/chunks/SaveBlockDialog-BfNVeXXV.js.map +0 -1
  253. package/dist/cdn/chunks/SavedBlocksBrowserModal-C59D4G5C.js.map +0 -1
  254. package/dist/cdn/chunks/SavedBlocksPickBar-DMHxPCM4.js.map +0 -1
  255. package/dist/cdn/chunks/SnapshotHistory-80tAU_VX.js +0 -121
  256. package/dist/cdn/chunks/SnapshotHistory-80tAU_VX.js.map +0 -1
  257. package/dist/cdn/chunks/TemplateScoringPanel-NucRtAP0.js.map +0 -1
  258. package/dist/cdn/chunks/TestEmailModal-DdWfAGNe.js +0 -205
  259. package/dist/cdn/chunks/TestEmailModal-DdWfAGNe.js.map +0 -1
  260. package/dist/cdn/chunks/TitleEditor-D0_LY2Ex.js.map +0 -1
  261. package/dist/cdn/chunks/TplModal-iCmoKB2k.js +0 -50
  262. package/dist/cdn/chunks/TplModal-iCmoKB2k.js.map +0 -1
  263. package/dist/cdn/chunks/ca-D1zgI9fQ.js.map +0 -1
  264. package/dist/cdn/chunks/cloud-DcC8bPwn.js +0 -1787
  265. package/dist/cdn/chunks/cloud-DcC8bPwn.js.map +0 -1
  266. package/dist/cdn/chunks/de-DHra3lvx.js.map +0 -1
  267. package/dist/cdn/chunks/de-uVoSP1xF.js.map +0 -1
  268. package/dist/cdn/chunks/en-CjhsJxb-.js.map +0 -1
  269. package/dist/cdn/chunks/en-DlqX2YWZ.js.map +0 -1
  270. package/dist/cdn/chunks/es-Cuss2snv.js.map +0 -1
  271. package/dist/cdn/chunks/extensions-C7h52yuk.js.map +0 -1
  272. package/dist/cdn/chunks/fr-Cm85vB2g.js.map +0 -1
  273. package/dist/cdn/chunks/keys-Cmc2Yl3D.js.map +0 -1
  274. package/dist/cdn/chunks/nl-dmiIX7oT.js.map +0 -1
  275. package/dist/cdn/chunks/pt-BR-CTzHTqJD.js.map +0 -1
  276. package/dist/cdn/chunks/pt-BR-CaIG-7EM.js.map +0 -1
  277. package/dist/cdn/chunks/src-CUKUsRzX.js.map +0 -1
  278. package/dist/cdn/chunks/src-xkGep9eN.js.map +0 -1
  279. package/dist/cdn/chunks/styles-CND3ey2g.js +0 -4140
  280. package/dist/cdn/chunks/styles-CND3ey2g.js.map +0 -1
  281. package/dist/cdn/chunks/timeouts-SsLMC4a3.js +0 -6
  282. package/dist/cdn/chunks/timeouts-SsLMC4a3.js.map +0 -1
  283. package/dist/cdn/chunks/useEditorCore-CKQ9gmoo.js.map +0 -1
  284. package/dist/styles-BK36D1sy.js +0 -4318
  285. package/dist/usePopoverPosition-C0dPEF4X.js +0 -308
@@ -1 +0,0 @@
1
- {"version":3,"file":"ParagraphEditor-Bzcma_Lm.js","names":[],"sources":["../../../src/components/blocks/EmojiPickerDropdown.vue","../../../src/components/blocks/EmojiPickerDropdown.vue","../../../src/components/toolbar/ToolbarIconButton.vue","../../../src/components/toolbar/ToolbarIconButton.vue","../../../src/components/toolbar/ToolbarSeparator.vue","../../../src/components/toolbar/ToolbarSelect.vue","../../../src/components/toolbar/ToolbarSelect.vue","../../../src/utils/richTextColor.ts","../../../src/components/blocks/ParagraphToolbar.vue","../../../src/components/blocks/ParagraphToolbar.vue","../../../src/components/blocks/ParagraphEditor.vue","../../../src/components/blocks/ParagraphEditor.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { useEmoji, useI18n } from \"../../composables\";\nimport { useFocusTrap } from \"../../composables/useFocusTrap\";\nimport { onClickOutside } from \"@vueuse/core\";\nimport { Smile } from \"@lucide/vue\";\nimport { computed, ref } from \"vue\";\n\nconst emit = defineEmits<{\n (e: \"insert\", emoji: string): void;\n}>();\n\nconst {\n categories: emojiCategories,\n isOpen: showEmojiPicker,\n toggle: toggleEmojiPicker,\n close: closeEmojiPicker,\n} = useEmoji();\n\nconst { t, format } = useI18n();\n\nconst pickerRef = ref<HTMLElement | null>(null);\nconst rootRef = ref<HTMLElement | null>(null);\n\nconst isOpenRef = computed(() => showEmojiPicker.value);\nuseFocusTrap(pickerRef, isOpenRef);\n\nonClickOutside(rootRef, () => {\n if (showEmojiPicker.value) {\n closeEmojiPicker();\n }\n});\n\nfunction handleInsert(emoji: string): void {\n emit(\"insert\", emoji);\n closeEmojiPicker();\n}\n</script>\n\n<template>\n <div ref=\"rootRef\" class=\"tpl:relative\">\n <button\n type=\"button\"\n class=\"tpl-text-toolbar-btn\"\n :class=\"{\n 'tpl-text-toolbar-btn--active': showEmojiPicker,\n }\"\n :aria-label=\"t.paragraphEditor.insertEmoji\"\n :title=\"t.paragraphEditor.insertEmoji\"\n :aria-expanded=\"showEmojiPicker\"\n aria-haspopup=\"dialog\"\n aria-controls=\"tpl-emoji-picker\"\n @click=\"toggleEmojiPicker\"\n >\n <Smile :size=\"16\" :stroke-width=\"2\" />\n </button>\n <div\n v-if=\"showEmojiPicker\"\n id=\"tpl-emoji-picker\"\n ref=\"pickerRef\"\n role=\"dialog\"\n aria-modal=\"false\"\n :aria-label=\"t.paragraphEditor.insertEmoji\"\n tabindex=\"-1\"\n class=\"tpl-emoji-picker tpl:absolute tpl:top-full tpl:left-0 tpl:z-10 tpl:mt-2 tpl:w-72 tpl:rounded-lg tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:p-2 tpl:shadow-lg\"\n @keydown.esc.stop.prevent=\"closeEmojiPicker\"\n >\n <div\n v-for=\"category in emojiCategories\"\n :key=\"category.key\"\n class=\"tpl:mb-2 tpl:last:mb-0\"\n >\n <div\n class=\"tpl:mb-1.5 tpl:text-[10px] tpl:font-medium tpl:tracking-wide tpl:text-[var(--tpl-text-muted)] tpl:uppercase\"\n >\n {{ t.emoji[category.key] }}\n </div>\n <div class=\"tpl:grid tpl:grid-cols-10 tpl:gap-0.5\">\n <button\n v-for=\"emoji in category.emojis\"\n :key=\"emoji\"\n type=\"button\"\n :aria-label=\"format(t.paragraphEditor.emojiItemLabel, { emoji })\"\n class=\"tpl:flex tpl:size-6 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded tpl:border-none tpl:bg-transparent tpl:text-base tpl:transition-all tpl:duration-100 tpl:hover:scale-125 tpl:hover:bg-[var(--tpl-bg-active)]\"\n @click=\"handleInsert(emoji)\"\n >\n {{ emoji }}\n </button>\n </div>\n </div>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { useEmoji, useI18n } from \"../../composables\";\nimport { useFocusTrap } from \"../../composables/useFocusTrap\";\nimport { onClickOutside } from \"@vueuse/core\";\nimport { Smile } from \"@lucide/vue\";\nimport { computed, ref } from \"vue\";\n\nconst emit = defineEmits<{\n (e: \"insert\", emoji: string): void;\n}>();\n\nconst {\n categories: emojiCategories,\n isOpen: showEmojiPicker,\n toggle: toggleEmojiPicker,\n close: closeEmojiPicker,\n} = useEmoji();\n\nconst { t, format } = useI18n();\n\nconst pickerRef = ref<HTMLElement | null>(null);\nconst rootRef = ref<HTMLElement | null>(null);\n\nconst isOpenRef = computed(() => showEmojiPicker.value);\nuseFocusTrap(pickerRef, isOpenRef);\n\nonClickOutside(rootRef, () => {\n if (showEmojiPicker.value) {\n closeEmojiPicker();\n }\n});\n\nfunction handleInsert(emoji: string): void {\n emit(\"insert\", emoji);\n closeEmojiPicker();\n}\n</script>\n\n<template>\n <div ref=\"rootRef\" class=\"tpl:relative\">\n <button\n type=\"button\"\n class=\"tpl-text-toolbar-btn\"\n :class=\"{\n 'tpl-text-toolbar-btn--active': showEmojiPicker,\n }\"\n :aria-label=\"t.paragraphEditor.insertEmoji\"\n :title=\"t.paragraphEditor.insertEmoji\"\n :aria-expanded=\"showEmojiPicker\"\n aria-haspopup=\"dialog\"\n aria-controls=\"tpl-emoji-picker\"\n @click=\"toggleEmojiPicker\"\n >\n <Smile :size=\"16\" :stroke-width=\"2\" />\n </button>\n <div\n v-if=\"showEmojiPicker\"\n id=\"tpl-emoji-picker\"\n ref=\"pickerRef\"\n role=\"dialog\"\n aria-modal=\"false\"\n :aria-label=\"t.paragraphEditor.insertEmoji\"\n tabindex=\"-1\"\n class=\"tpl-emoji-picker tpl:absolute tpl:top-full tpl:left-0 tpl:z-10 tpl:mt-2 tpl:w-72 tpl:rounded-lg tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:p-2 tpl:shadow-lg\"\n @keydown.esc.stop.prevent=\"closeEmojiPicker\"\n >\n <div\n v-for=\"category in emojiCategories\"\n :key=\"category.key\"\n class=\"tpl:mb-2 tpl:last:mb-0\"\n >\n <div\n class=\"tpl:mb-1.5 tpl:text-[10px] tpl:font-medium tpl:tracking-wide tpl:text-[var(--tpl-text-muted)] tpl:uppercase\"\n >\n {{ t.emoji[category.key] }}\n </div>\n <div class=\"tpl:grid tpl:grid-cols-10 tpl:gap-0.5\">\n <button\n v-for=\"emoji in category.emojis\"\n :key=\"emoji\"\n type=\"button\"\n :aria-label=\"format(t.paragraphEditor.emojiItemLabel, { emoji })\"\n class=\"tpl:flex tpl:size-6 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded tpl:border-none tpl:bg-transparent tpl:text-base tpl:transition-all tpl:duration-100 tpl:hover:scale-125 tpl:hover:bg-[var(--tpl-bg-active)]\"\n @click=\"handleInsert(emoji)\"\n >\n {{ emoji }}\n </button>\n </div>\n </div>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport type { Component } from \"vue\";\n\ndefineProps<{\n icon: Component;\n label: string;\n active?: boolean;\n strokeWidth?: number;\n size?: number;\n}>();\n</script>\n\n<template>\n <button\n type=\"button\"\n class=\"tpl-text-toolbar-btn\"\n :class=\"{ 'tpl-text-toolbar-btn--active': active }\"\n :aria-label=\"label\"\n :title=\"label\"\n :aria-pressed=\"active ? 'true' : 'false'\"\n >\n <component :is=\"icon\" :size=\"size ?? 16\" :stroke-width=\"strokeWidth ?? 2\" />\n </button>\n</template>\n","<script setup lang=\"ts\">\nimport type { Component } from \"vue\";\n\ndefineProps<{\n icon: Component;\n label: string;\n active?: boolean;\n strokeWidth?: number;\n size?: number;\n}>();\n</script>\n\n<template>\n <button\n type=\"button\"\n class=\"tpl-text-toolbar-btn\"\n :class=\"{ 'tpl-text-toolbar-btn--active': active }\"\n :aria-label=\"label\"\n :title=\"label\"\n :aria-pressed=\"active ? 'true' : 'false'\"\n >\n <component :is=\"icon\" :size=\"size ?? 16\" :stroke-width=\"strokeWidth ?? 2\" />\n </button>\n</template>\n","<template>\n <span\n class=\"tpl:mx-1 tpl:h-6 tpl:w-px tpl:bg-[var(--tpl-border)]\"\n aria-hidden=\"true\"\n ></span>\n</template>\n","<script setup lang=\"ts\">\ninterface OptionItem {\n value: string;\n label: string;\n}\n\ndefineProps<{\n modelValue: string;\n options: readonly (string | OptionItem)[];\n label: string;\n placeholder?: string;\n widthClass?: string;\n}>();\n\nconst emit = defineEmits<{\n (e: \"update:modelValue\", value: string): void;\n}>();\n\nfunction onChange(e: Event): void {\n emit(\"update:modelValue\", (e.target as HTMLSelectElement).value);\n}\n\nfunction optionValue(opt: string | OptionItem): string {\n return typeof opt === \"string\" ? opt : opt.value;\n}\n\nfunction optionLabel(opt: string | OptionItem): string {\n return typeof opt === \"string\" ? opt : opt.label;\n}\n</script>\n\n<template>\n <select\n :class=\"[\n 'tpl:h-8 tpl:cursor-pointer tpl:rounded tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:px-2 tpl:text-xs tpl:text-[var(--tpl-text)] tpl:outline-none',\n widthClass ?? 'tpl:w-20',\n ]\"\n :value=\"modelValue\"\n :aria-label=\"label\"\n :title=\"label\"\n @change=\"onChange\"\n >\n <option value=\"\">{{ placeholder ?? \"\" }}</option>\n <option\n v-for=\"opt in options\"\n :key=\"optionValue(opt)\"\n :value=\"optionValue(opt)\"\n >\n {{ optionLabel(opt) }}\n </option>\n </select>\n</template>\n","<script setup lang=\"ts\">\ninterface OptionItem {\n value: string;\n label: string;\n}\n\ndefineProps<{\n modelValue: string;\n options: readonly (string | OptionItem)[];\n label: string;\n placeholder?: string;\n widthClass?: string;\n}>();\n\nconst emit = defineEmits<{\n (e: \"update:modelValue\", value: string): void;\n}>();\n\nfunction onChange(e: Event): void {\n emit(\"update:modelValue\", (e.target as HTMLSelectElement).value);\n}\n\nfunction optionValue(opt: string | OptionItem): string {\n return typeof opt === \"string\" ? opt : opt.value;\n}\n\nfunction optionLabel(opt: string | OptionItem): string {\n return typeof opt === \"string\" ? opt : opt.label;\n}\n</script>\n\n<template>\n <select\n :class=\"[\n 'tpl:h-8 tpl:cursor-pointer tpl:rounded tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:px-2 tpl:text-xs tpl:text-[var(--tpl-text)] tpl:outline-none',\n widthClass ?? 'tpl:w-20',\n ]\"\n :value=\"modelValue\"\n :aria-label=\"label\"\n :title=\"label\"\n @change=\"onChange\"\n >\n <option value=\"\">{{ placeholder ?? \"\" }}</option>\n <option\n v-for=\"opt in options\"\n :key=\"optionValue(opt)\"\n :value=\"optionValue(opt)\"\n >\n {{ optionLabel(opt) }}\n </option>\n </select>\n</template>\n","import { DEFAULT_TEXT_COLOR } from \"../constants/styleConstants\";\n\n/**\n * The color a rich-text selection actually renders as, for display in the\n * text-color control (issue #373).\n *\n * Priority: an explicit inline text-color mark on the selection wins; otherwise\n * the text inherits the document-level `textColor`; if that too is unset (older\n * content predating document text color), fall back to the built-in default.\n *\n * A native `<input type=\"color\">` can't represent \"no color\", so before this it\n * always painted a hard-coded `#000000` when no inline mark existed — which both\n * looked like an explicit choice and didn't even match the real inherited color\n * (`#1a1a1a`). Resolving the effective color keeps the swatch truthful: it shows\n * the color the text is actually rendered in, whatever level it comes from.\n */\nexport function resolveEffectiveTextColor(\n explicitColor: string,\n documentTextColor: string | undefined,\n): string {\n return explicitColor || documentTextColor || DEFAULT_TEXT_COLOR;\n}\n","<script setup lang=\"ts\">\nimport ColorPicker from \"../ColorPicker.vue\";\nimport EmojiPickerDropdown from \"./EmojiPickerDropdown.vue\";\nimport ToolbarIconButton from \"../toolbar/ToolbarIconButton.vue\";\nimport ToolbarSeparator from \"../toolbar/ToolbarSeparator.vue\";\nimport ToolbarSelect from \"../toolbar/ToolbarSelect.vue\";\nimport { useI18n } from \"../../composables\";\nimport { usePopoverRoot } from \"../../composables/usePopoverRoot\";\nimport type { Editor } from \"@tiptap/core\";\nimport {\n AlignCenter,\n AlignJustify,\n AlignLeft,\n AlignRight,\n Bold,\n Italic,\n Braces,\n Link,\n List,\n ListOrdered,\n LoaderCircle,\n RemoveFormatting,\n ScanLine,\n Strikethrough,\n Subscript,\n Superscript,\n Underline,\n} from \"@lucide/vue\";\nimport { computed, inject } from \"vue\";\nimport {\n EDITOR_KEY,\n THEME_STYLES_KEY,\n UI_THEME_KEY,\n FONTS_MANAGER_KEY,\n MERGE_TAG_PICKER_KEY,\n LOGIC_TAG_PICKER_KEY,\n requireInject,\n} from \"../../keys\";\nimport {\n DEFAULT_HIGHLIGHT_COLOR,\n FONT_SIZE_OPTIONS,\n LINE_HEIGHT_OPTIONS,\n LETTER_SPACING_OPTIONS,\n} from \"../../constants/styleConstants\";\nimport { resolveEffectiveTextColor } from \"../../utils/richTextColor\";\n\nconst props = defineProps<{\n editor: Editor | null;\n toolbarPosition: { top: number; left: number };\n isLoading: boolean;\n canRequestMergeTag: boolean;\n canInsertLogicTag: boolean;\n}>();\n\nconst emit = defineEmits<{\n (e: \"open-link-dialog\"): void;\n (e: \"add-merge-tag\"): void;\n (e: \"add-logic-tag\"): void;\n}>();\n\nconst themeStyles = inject(THEME_STYLES_KEY, null);\nconst tplUiTheme = inject(UI_THEME_KEY, null);\nconst fontsManager = requireInject(FONTS_MANAGER_KEY, \"ParagraphToolbar\");\nconst popoverRoot = usePopoverRoot();\n// Picker may be null in non-editor contexts (e.g. isolated component tests).\n// When it's provided AND open, hide the floating toolbar — leaving it\n// visible behind the modal is visually noisy and the toolbar's Tailwind\n// z-index utility doesn't compile reliably, so it can sometimes paint\n// over the modal backdrop.\nconst picker = inject(MERGE_TAG_PICKER_KEY, null);\nconst logicPicker = inject(LOGIC_TAG_PICKER_KEY, null);\n// Editor may be absent in isolated tests; used only to resolve the inherited\n// document text color for the text-color swatch.\nconst editorReturn = inject(EDITOR_KEY, null);\n// Hide the floating toolbar while either built-in picker modal is open, so it\n// doesn't overlap the dialog.\nconst pickerIsOpen = computed(\n () => (picker?.isOpen.value ?? false) || (logicPicker?.isOpen.value ?? false),\n);\n\nconst { t } = useI18n();\n\nconst fontFamilies = fontsManager.fonts;\n\nfunction insertEmoji(emoji: string): void {\n props.editor?.chain().focus().insertContent(emoji).run();\n}\n\nfunction textStyleAttr(attr: string): string {\n return (props.editor?.getAttributes(\"textStyle\")[attr] as string) || \"\";\n}\n\nfunction setFontFamily(family: string): void {\n const chain = props.editor?.chain().focus();\n if (family) chain?.setFontFamily(family).run();\n else chain?.unsetFontFamily().run();\n}\n\nfunction setFontSize(size: string): void {\n const chain = props.editor?.chain().focus();\n if (size) chain?.setFontSize(size).run();\n else chain?.unsetFontSize().run();\n}\n\n// When the selection is a link, the color control operates on the link itself\n// (its `color` attribute → inline on the `<a>`) so the link's text and underline\n// stay in sync; otherwise it applies a plain inline text-color mark.\nfunction isLinkSelection(): boolean {\n return props.editor?.isActive(\"link\") ?? false;\n}\n\nfunction linkColorAttr(): string {\n return (props.editor?.getAttributes(\"link\").color as string) || \"\";\n}\n\n// Set (or clear, when empty) the color of the current selection. On a link the\n// whole link is recolored (extendMarkRange) so it doesn't split into segments.\nfunction setColor(color: string): void {\n const chain = props.editor?.chain().focus();\n if (!chain) return;\n if (isLinkSelection()) {\n chain.extendMarkRange(\"link\").updateAttributes(\"link\", {\n color: color || null,\n });\n // A per-link color takes absolute priority over any inner text-color: strip\n // the inline text color on the link range so the `<a>` color alone drives\n // the glyphs and the underline (mirrors useRichTextLinkDialog.insertLink).\n // Only when setting a color — clearing leaves the text color untouched.\n if (color) chain.unsetColor();\n chain.run();\n } else if (color) {\n // A text color applied over a selection that includes a link unifies the\n // link with it: update the link's own color so the `<a>` (which paints the\n // underline and is what the dialog/toolbar read) matches the recolored\n // glyphs. A no-op on a selection with no link. Mirror of the link branch\n // above — either way a link never ends up with a text color fighting its\n // own color.\n chain.setColor(color).updateAttributes(\"link\", { color }).run();\n } else {\n chain.unsetColor().run();\n }\n}\n\n// The color the current selection actually renders in — a link's own color (else\n// the effective document link color: link color, then text color), or for plain\n// text the inline mark else the document text color. Keeps the swatch truthful\n// rather than showing a hard-coded default (issue #373 / per-link color).\nfunction effectiveTextColor(): string {\n const settings = editorReturn?.content.value.settings;\n if (isLinkSelection()) {\n return resolveEffectiveTextColor(\n linkColorAttr(),\n settings?.linkColor || settings?.textColor,\n );\n }\n return resolveEffectiveTextColor(textStyleAttr(\"color\"), settings?.textColor);\n}\n\n// The selection's own explicit color: a link's color when on a link, else the\n// inline text-color mark. May be \"\" when the selection only inherits its color\n// — that drives the swatch's set/unset state, while `effectiveTextColor()`\n// seeds the wheel so it still opens on the real rendered color.\nfunction explicitTextColor(): string {\n return isLinkSelection() ? linkColorAttr() : textStyleAttr(\"color\");\n}\n\nfunction getCurrentLineHeight(): string {\n return (props.editor?.getAttributes(\"paragraph\").lineHeight as string) || \"\";\n}\n\nfunction setLineHeight(value: string): void {\n const chain = props.editor?.chain().focus();\n if (value) chain?.setLineHeight(value).run();\n else chain?.unsetLineHeight().run();\n}\n\nfunction setLetterSpacing(value: string): void {\n const chain = props.editor?.chain().focus();\n if (value && value !== \"normal\") chain?.setLetterSpacing(value).run();\n else chain?.unsetLetterSpacing().run();\n}\n\nfunction getCurrentHighlight(): string {\n return (props.editor?.getAttributes(\"highlight\").color as string) || \"\";\n}\n\nfunction setHighlight(color: string): void {\n const chain = props.editor?.chain().focus();\n if (color) chain?.setHighlight({ color }).run();\n else chain?.unsetHighlight().run();\n}\n</script>\n\n<template>\n <Teleport v-if=\"popoverRoot && !pickerIsOpen\" :to=\"popoverRoot\">\n <div\n :data-tpl-theme=\"tplUiTheme\"\n role=\"toolbar\"\n :aria-label=\"t.paragraphEditor.toolbar\"\n class=\"tpl tpl-text-toolbar tpl:absolute tpl:z-popover tpl:flex 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\"\n :style=\"{\n ...themeStyles,\n top: `${toolbarPosition.top}px`,\n left: `${toolbarPosition.left}px`,\n transform: 'translateY(-100%)',\n flexDirection: 'column',\n }\"\n >\n <template v-if=\"!isLoading && editor\">\n <!-- Row 1: Font family, Font size, Text color, Bold/Italic/Underline/Strikethrough -->\n <div class=\"tpl:flex tpl:items-center tpl:gap-1\">\n <ToolbarSelect\n :model-value=\"textStyleAttr('fontFamily')\"\n :options=\"fontFamilies\"\n :label=\"t.paragraphEditor.fontFamily\"\n :placeholder=\"t.paragraphEditor.defaultFont\"\n width-class=\"tpl:w-32\"\n @update:model-value=\"setFontFamily\"\n />\n <ToolbarSelect\n :model-value=\"textStyleAttr('fontSize')\"\n :options=\"FONT_SIZE_OPTIONS\"\n :label=\"t.paragraphEditor.fontSize\"\n :placeholder=\"t.paragraphEditor.defaultSize\"\n width-class=\"tpl:w-20\"\n @update:model-value=\"setFontSize\"\n />\n <ToolbarSeparator />\n <ColorPicker\n swatch-only\n size=\"sm\"\n data-testid=\"text-color-picker\"\n :model-value=\"explicitTextColor()\"\n :seed-color=\"effectiveTextColor()\"\n :aria-label=\"t.paragraphEditor.textColor\"\n @update:model-value=\"setColor\"\n />\n <ColorPicker\n swatch-only\n size=\"sm\"\n data-testid=\"highlight-color-picker\"\n :model-value=\"getCurrentHighlight()\"\n :seed-color=\"DEFAULT_HIGHLIGHT_COLOR\"\n :aria-label=\"t.paragraphEditor.highlightColor\"\n @update:model-value=\"setHighlight\"\n />\n <ToolbarSeparator />\n <ToolbarIconButton\n :icon=\"Bold\"\n :label=\"t.paragraphEditor.bold\"\n :active=\"editor.isActive('bold')\"\n :stroke-width=\"2.5\"\n @click=\"editor.chain().focus().toggleBold().run()\"\n />\n <ToolbarIconButton\n :icon=\"Italic\"\n :label=\"t.paragraphEditor.italic\"\n :active=\"editor.isActive('italic')\"\n @click=\"editor.chain().focus().toggleItalic().run()\"\n />\n <ToolbarIconButton\n :icon=\"Underline\"\n :label=\"t.paragraphEditor.underline\"\n :active=\"editor.isActive('underline')\"\n @click=\"editor.chain().focus().toggleUnderline().run()\"\n />\n <ToolbarIconButton\n :icon=\"Strikethrough\"\n :label=\"t.paragraphEditor.strikethrough\"\n :active=\"editor.isActive('strike')\"\n @click=\"editor.chain().focus().toggleStrike().run()\"\n />\n <ToolbarSeparator />\n <ToolbarIconButton\n :icon=\"Subscript\"\n :label=\"t.paragraphEditor.subscript\"\n :active=\"editor.isActive('subscript')\"\n @click=\"editor.chain().focus().toggleSubscript().run()\"\n />\n <ToolbarIconButton\n :icon=\"Superscript\"\n :label=\"t.paragraphEditor.superscript\"\n :active=\"editor.isActive('superscript')\"\n @click=\"editor.chain().focus().toggleSuperscript().run()\"\n />\n <ToolbarSeparator />\n <ToolbarIconButton\n :icon=\"Link\"\n :label=\"t.paragraphEditor.addLink\"\n :active=\"editor.isActive('link')\"\n @click=\"emit('open-link-dialog')\"\n />\n </div>\n <!-- Row 2: Lists, Alignment, LH, LS, Clear, Emoji, Merge tags -->\n <div class=\"tpl:flex tpl:items-center tpl:gap-1\">\n <ToolbarIconButton\n :icon=\"List\"\n :label=\"t.paragraphEditor.bulletList\"\n :active=\"editor.isActive('bulletList')\"\n @click=\"editor.chain().focus().toggleBulletList().run()\"\n />\n <ToolbarIconButton\n :icon=\"ListOrdered\"\n :label=\"t.paragraphEditor.numberedList\"\n :active=\"editor.isActive('orderedList')\"\n @click=\"editor.chain().focus().toggleOrderedList().run()\"\n />\n <ToolbarSeparator />\n <ToolbarIconButton\n :icon=\"AlignLeft\"\n :label=\"t.paragraphEditor.alignLeft\"\n :active=\"editor.isActive({ textAlign: 'left' })\"\n @click=\"editor.chain().focus().setTextAlign('left').run()\"\n />\n <ToolbarIconButton\n :icon=\"AlignCenter\"\n :label=\"t.paragraphEditor.alignCenter\"\n :active=\"editor.isActive({ textAlign: 'center' })\"\n @click=\"editor.chain().focus().setTextAlign('center').run()\"\n />\n <ToolbarIconButton\n :icon=\"AlignRight\"\n :label=\"t.paragraphEditor.alignRight\"\n :active=\"editor.isActive({ textAlign: 'right' })\"\n @click=\"editor.chain().focus().setTextAlign('right').run()\"\n />\n <ToolbarIconButton\n :icon=\"AlignJustify\"\n :label=\"t.paragraphEditor.alignJustify\"\n :active=\"editor.isActive({ textAlign: 'justify' })\"\n @click=\"editor.chain().focus().setTextAlign('justify').run()\"\n />\n <ToolbarSeparator />\n <ToolbarSelect\n :model-value=\"getCurrentLineHeight()\"\n :options=\"LINE_HEIGHT_OPTIONS\"\n :label=\"t.paragraphEditor.lineHeight\"\n placeholder=\"LH\"\n width-class=\"tpl:w-16\"\n @update:model-value=\"setLineHeight\"\n />\n <ToolbarSelect\n :model-value=\"textStyleAttr('letterSpacing')\"\n :options=\"LETTER_SPACING_OPTIONS\"\n :label=\"t.paragraphEditor.letterSpacing\"\n placeholder=\"LS\"\n width-class=\"tpl:w-20\"\n @update:model-value=\"setLetterSpacing\"\n />\n <ToolbarSeparator />\n <ToolbarIconButton\n :icon=\"RemoveFormatting\"\n :label=\"t.paragraphEditor.clearFormatting\"\n @click=\"editor.chain().focus().clearNodes().unsetAllMarks().run()\"\n />\n <ToolbarSeparator />\n <EmojiPickerDropdown @insert=\"insertEmoji\" />\n <template v-if=\"canRequestMergeTag\">\n <ToolbarSeparator />\n <button\n type=\"button\"\n class=\"tpl:flex tpl:h-8 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:gap-1.5 tpl:rounded tpl:border-none tpl:bg-transparent tpl:px-2.5 tpl:text-xs tpl:font-medium tpl:text-[var(--tpl-text)] tpl:transition-all tpl:duration-150 tpl:hover:bg-[var(--tpl-bg-active)]\"\n :aria-label=\"t.mergeTag.insert\"\n :title=\"t.mergeTag.insert\"\n @click=\"emit('add-merge-tag')\"\n >\n <ScanLine :size=\"16\" :stroke-width=\"2\" />\n {{ t.mergeTag.insertShort }}\n </button>\n </template>\n <template v-if=\"canInsertLogicTag\">\n <ToolbarSeparator />\n <button\n type=\"button\"\n class=\"tpl:flex tpl:h-8 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:gap-1.5 tpl:rounded tpl:border-none tpl:bg-transparent tpl:px-2.5 tpl:text-xs tpl:font-medium tpl:text-[var(--tpl-text)] tpl:transition-all tpl:duration-150 tpl:hover:bg-[var(--tpl-bg-active)]\"\n :aria-label=\"t.logicTag.insert\"\n :title=\"t.logicTag.insert\"\n data-testid=\"insert-logic-button\"\n @click=\"emit('add-logic-tag')\"\n >\n <Braces :size=\"16\" :stroke-width=\"2\" />\n {{ t.logicTag.insertShort }}\n </button>\n </template>\n </div>\n </template>\n <template v-else>\n <div\n class=\"tpl:flex tpl:items-center tpl:gap-2 tpl:px-2 tpl:text-xs tpl:text-[var(--tpl-text-dim)]\"\n >\n <LoaderCircle class=\"tpl-spinner\" :size=\"14\" :stroke-width=\"2\" />\n {{ t.errors.editorLoading }}\n </div>\n </template>\n </div>\n </Teleport>\n</template>\n","<script setup lang=\"ts\">\nimport ColorPicker from \"../ColorPicker.vue\";\nimport EmojiPickerDropdown from \"./EmojiPickerDropdown.vue\";\nimport ToolbarIconButton from \"../toolbar/ToolbarIconButton.vue\";\nimport ToolbarSeparator from \"../toolbar/ToolbarSeparator.vue\";\nimport ToolbarSelect from \"../toolbar/ToolbarSelect.vue\";\nimport { useI18n } from \"../../composables\";\nimport { usePopoverRoot } from \"../../composables/usePopoverRoot\";\nimport type { Editor } from \"@tiptap/core\";\nimport {\n AlignCenter,\n AlignJustify,\n AlignLeft,\n AlignRight,\n Bold,\n Italic,\n Braces,\n Link,\n List,\n ListOrdered,\n LoaderCircle,\n RemoveFormatting,\n ScanLine,\n Strikethrough,\n Subscript,\n Superscript,\n Underline,\n} from \"@lucide/vue\";\nimport { computed, inject } from \"vue\";\nimport {\n EDITOR_KEY,\n THEME_STYLES_KEY,\n UI_THEME_KEY,\n FONTS_MANAGER_KEY,\n MERGE_TAG_PICKER_KEY,\n LOGIC_TAG_PICKER_KEY,\n requireInject,\n} from \"../../keys\";\nimport {\n DEFAULT_HIGHLIGHT_COLOR,\n FONT_SIZE_OPTIONS,\n LINE_HEIGHT_OPTIONS,\n LETTER_SPACING_OPTIONS,\n} from \"../../constants/styleConstants\";\nimport { resolveEffectiveTextColor } from \"../../utils/richTextColor\";\n\nconst props = defineProps<{\n editor: Editor | null;\n toolbarPosition: { top: number; left: number };\n isLoading: boolean;\n canRequestMergeTag: boolean;\n canInsertLogicTag: boolean;\n}>();\n\nconst emit = defineEmits<{\n (e: \"open-link-dialog\"): void;\n (e: \"add-merge-tag\"): void;\n (e: \"add-logic-tag\"): void;\n}>();\n\nconst themeStyles = inject(THEME_STYLES_KEY, null);\nconst tplUiTheme = inject(UI_THEME_KEY, null);\nconst fontsManager = requireInject(FONTS_MANAGER_KEY, \"ParagraphToolbar\");\nconst popoverRoot = usePopoverRoot();\n// Picker may be null in non-editor contexts (e.g. isolated component tests).\n// When it's provided AND open, hide the floating toolbar — leaving it\n// visible behind the modal is visually noisy and the toolbar's Tailwind\n// z-index utility doesn't compile reliably, so it can sometimes paint\n// over the modal backdrop.\nconst picker = inject(MERGE_TAG_PICKER_KEY, null);\nconst logicPicker = inject(LOGIC_TAG_PICKER_KEY, null);\n// Editor may be absent in isolated tests; used only to resolve the inherited\n// document text color for the text-color swatch.\nconst editorReturn = inject(EDITOR_KEY, null);\n// Hide the floating toolbar while either built-in picker modal is open, so it\n// doesn't overlap the dialog.\nconst pickerIsOpen = computed(\n () => (picker?.isOpen.value ?? false) || (logicPicker?.isOpen.value ?? false),\n);\n\nconst { t } = useI18n();\n\nconst fontFamilies = fontsManager.fonts;\n\nfunction insertEmoji(emoji: string): void {\n props.editor?.chain().focus().insertContent(emoji).run();\n}\n\nfunction textStyleAttr(attr: string): string {\n return (props.editor?.getAttributes(\"textStyle\")[attr] as string) || \"\";\n}\n\nfunction setFontFamily(family: string): void {\n const chain = props.editor?.chain().focus();\n if (family) chain?.setFontFamily(family).run();\n else chain?.unsetFontFamily().run();\n}\n\nfunction setFontSize(size: string): void {\n const chain = props.editor?.chain().focus();\n if (size) chain?.setFontSize(size).run();\n else chain?.unsetFontSize().run();\n}\n\n// When the selection is a link, the color control operates on the link itself\n// (its `color` attribute → inline on the `<a>`) so the link's text and underline\n// stay in sync; otherwise it applies a plain inline text-color mark.\nfunction isLinkSelection(): boolean {\n return props.editor?.isActive(\"link\") ?? false;\n}\n\nfunction linkColorAttr(): string {\n return (props.editor?.getAttributes(\"link\").color as string) || \"\";\n}\n\n// Set (or clear, when empty) the color of the current selection. On a link the\n// whole link is recolored (extendMarkRange) so it doesn't split into segments.\nfunction setColor(color: string): void {\n const chain = props.editor?.chain().focus();\n if (!chain) return;\n if (isLinkSelection()) {\n chain.extendMarkRange(\"link\").updateAttributes(\"link\", {\n color: color || null,\n });\n // A per-link color takes absolute priority over any inner text-color: strip\n // the inline text color on the link range so the `<a>` color alone drives\n // the glyphs and the underline (mirrors useRichTextLinkDialog.insertLink).\n // Only when setting a color — clearing leaves the text color untouched.\n if (color) chain.unsetColor();\n chain.run();\n } else if (color) {\n // A text color applied over a selection that includes a link unifies the\n // link with it: update the link's own color so the `<a>` (which paints the\n // underline and is what the dialog/toolbar read) matches the recolored\n // glyphs. A no-op on a selection with no link. Mirror of the link branch\n // above — either way a link never ends up with a text color fighting its\n // own color.\n chain.setColor(color).updateAttributes(\"link\", { color }).run();\n } else {\n chain.unsetColor().run();\n }\n}\n\n// The color the current selection actually renders in — a link's own color (else\n// the effective document link color: link color, then text color), or for plain\n// text the inline mark else the document text color. Keeps the swatch truthful\n// rather than showing a hard-coded default (issue #373 / per-link color).\nfunction effectiveTextColor(): string {\n const settings = editorReturn?.content.value.settings;\n if (isLinkSelection()) {\n return resolveEffectiveTextColor(\n linkColorAttr(),\n settings?.linkColor || settings?.textColor,\n );\n }\n return resolveEffectiveTextColor(textStyleAttr(\"color\"), settings?.textColor);\n}\n\n// The selection's own explicit color: a link's color when on a link, else the\n// inline text-color mark. May be \"\" when the selection only inherits its color\n// — that drives the swatch's set/unset state, while `effectiveTextColor()`\n// seeds the wheel so it still opens on the real rendered color.\nfunction explicitTextColor(): string {\n return isLinkSelection() ? linkColorAttr() : textStyleAttr(\"color\");\n}\n\nfunction getCurrentLineHeight(): string {\n return (props.editor?.getAttributes(\"paragraph\").lineHeight as string) || \"\";\n}\n\nfunction setLineHeight(value: string): void {\n const chain = props.editor?.chain().focus();\n if (value) chain?.setLineHeight(value).run();\n else chain?.unsetLineHeight().run();\n}\n\nfunction setLetterSpacing(value: string): void {\n const chain = props.editor?.chain().focus();\n if (value && value !== \"normal\") chain?.setLetterSpacing(value).run();\n else chain?.unsetLetterSpacing().run();\n}\n\nfunction getCurrentHighlight(): string {\n return (props.editor?.getAttributes(\"highlight\").color as string) || \"\";\n}\n\nfunction setHighlight(color: string): void {\n const chain = props.editor?.chain().focus();\n if (color) chain?.setHighlight({ color }).run();\n else chain?.unsetHighlight().run();\n}\n</script>\n\n<template>\n <Teleport v-if=\"popoverRoot && !pickerIsOpen\" :to=\"popoverRoot\">\n <div\n :data-tpl-theme=\"tplUiTheme\"\n role=\"toolbar\"\n :aria-label=\"t.paragraphEditor.toolbar\"\n class=\"tpl tpl-text-toolbar tpl:absolute tpl:z-popover tpl:flex 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\"\n :style=\"{\n ...themeStyles,\n top: `${toolbarPosition.top}px`,\n left: `${toolbarPosition.left}px`,\n transform: 'translateY(-100%)',\n flexDirection: 'column',\n }\"\n >\n <template v-if=\"!isLoading && editor\">\n <!-- Row 1: Font family, Font size, Text color, Bold/Italic/Underline/Strikethrough -->\n <div class=\"tpl:flex tpl:items-center tpl:gap-1\">\n <ToolbarSelect\n :model-value=\"textStyleAttr('fontFamily')\"\n :options=\"fontFamilies\"\n :label=\"t.paragraphEditor.fontFamily\"\n :placeholder=\"t.paragraphEditor.defaultFont\"\n width-class=\"tpl:w-32\"\n @update:model-value=\"setFontFamily\"\n />\n <ToolbarSelect\n :model-value=\"textStyleAttr('fontSize')\"\n :options=\"FONT_SIZE_OPTIONS\"\n :label=\"t.paragraphEditor.fontSize\"\n :placeholder=\"t.paragraphEditor.defaultSize\"\n width-class=\"tpl:w-20\"\n @update:model-value=\"setFontSize\"\n />\n <ToolbarSeparator />\n <ColorPicker\n swatch-only\n size=\"sm\"\n data-testid=\"text-color-picker\"\n :model-value=\"explicitTextColor()\"\n :seed-color=\"effectiveTextColor()\"\n :aria-label=\"t.paragraphEditor.textColor\"\n @update:model-value=\"setColor\"\n />\n <ColorPicker\n swatch-only\n size=\"sm\"\n data-testid=\"highlight-color-picker\"\n :model-value=\"getCurrentHighlight()\"\n :seed-color=\"DEFAULT_HIGHLIGHT_COLOR\"\n :aria-label=\"t.paragraphEditor.highlightColor\"\n @update:model-value=\"setHighlight\"\n />\n <ToolbarSeparator />\n <ToolbarIconButton\n :icon=\"Bold\"\n :label=\"t.paragraphEditor.bold\"\n :active=\"editor.isActive('bold')\"\n :stroke-width=\"2.5\"\n @click=\"editor.chain().focus().toggleBold().run()\"\n />\n <ToolbarIconButton\n :icon=\"Italic\"\n :label=\"t.paragraphEditor.italic\"\n :active=\"editor.isActive('italic')\"\n @click=\"editor.chain().focus().toggleItalic().run()\"\n />\n <ToolbarIconButton\n :icon=\"Underline\"\n :label=\"t.paragraphEditor.underline\"\n :active=\"editor.isActive('underline')\"\n @click=\"editor.chain().focus().toggleUnderline().run()\"\n />\n <ToolbarIconButton\n :icon=\"Strikethrough\"\n :label=\"t.paragraphEditor.strikethrough\"\n :active=\"editor.isActive('strike')\"\n @click=\"editor.chain().focus().toggleStrike().run()\"\n />\n <ToolbarSeparator />\n <ToolbarIconButton\n :icon=\"Subscript\"\n :label=\"t.paragraphEditor.subscript\"\n :active=\"editor.isActive('subscript')\"\n @click=\"editor.chain().focus().toggleSubscript().run()\"\n />\n <ToolbarIconButton\n :icon=\"Superscript\"\n :label=\"t.paragraphEditor.superscript\"\n :active=\"editor.isActive('superscript')\"\n @click=\"editor.chain().focus().toggleSuperscript().run()\"\n />\n <ToolbarSeparator />\n <ToolbarIconButton\n :icon=\"Link\"\n :label=\"t.paragraphEditor.addLink\"\n :active=\"editor.isActive('link')\"\n @click=\"emit('open-link-dialog')\"\n />\n </div>\n <!-- Row 2: Lists, Alignment, LH, LS, Clear, Emoji, Merge tags -->\n <div class=\"tpl:flex tpl:items-center tpl:gap-1\">\n <ToolbarIconButton\n :icon=\"List\"\n :label=\"t.paragraphEditor.bulletList\"\n :active=\"editor.isActive('bulletList')\"\n @click=\"editor.chain().focus().toggleBulletList().run()\"\n />\n <ToolbarIconButton\n :icon=\"ListOrdered\"\n :label=\"t.paragraphEditor.numberedList\"\n :active=\"editor.isActive('orderedList')\"\n @click=\"editor.chain().focus().toggleOrderedList().run()\"\n />\n <ToolbarSeparator />\n <ToolbarIconButton\n :icon=\"AlignLeft\"\n :label=\"t.paragraphEditor.alignLeft\"\n :active=\"editor.isActive({ textAlign: 'left' })\"\n @click=\"editor.chain().focus().setTextAlign('left').run()\"\n />\n <ToolbarIconButton\n :icon=\"AlignCenter\"\n :label=\"t.paragraphEditor.alignCenter\"\n :active=\"editor.isActive({ textAlign: 'center' })\"\n @click=\"editor.chain().focus().setTextAlign('center').run()\"\n />\n <ToolbarIconButton\n :icon=\"AlignRight\"\n :label=\"t.paragraphEditor.alignRight\"\n :active=\"editor.isActive({ textAlign: 'right' })\"\n @click=\"editor.chain().focus().setTextAlign('right').run()\"\n />\n <ToolbarIconButton\n :icon=\"AlignJustify\"\n :label=\"t.paragraphEditor.alignJustify\"\n :active=\"editor.isActive({ textAlign: 'justify' })\"\n @click=\"editor.chain().focus().setTextAlign('justify').run()\"\n />\n <ToolbarSeparator />\n <ToolbarSelect\n :model-value=\"getCurrentLineHeight()\"\n :options=\"LINE_HEIGHT_OPTIONS\"\n :label=\"t.paragraphEditor.lineHeight\"\n placeholder=\"LH\"\n width-class=\"tpl:w-16\"\n @update:model-value=\"setLineHeight\"\n />\n <ToolbarSelect\n :model-value=\"textStyleAttr('letterSpacing')\"\n :options=\"LETTER_SPACING_OPTIONS\"\n :label=\"t.paragraphEditor.letterSpacing\"\n placeholder=\"LS\"\n width-class=\"tpl:w-20\"\n @update:model-value=\"setLetterSpacing\"\n />\n <ToolbarSeparator />\n <ToolbarIconButton\n :icon=\"RemoveFormatting\"\n :label=\"t.paragraphEditor.clearFormatting\"\n @click=\"editor.chain().focus().clearNodes().unsetAllMarks().run()\"\n />\n <ToolbarSeparator />\n <EmojiPickerDropdown @insert=\"insertEmoji\" />\n <template v-if=\"canRequestMergeTag\">\n <ToolbarSeparator />\n <button\n type=\"button\"\n class=\"tpl:flex tpl:h-8 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:gap-1.5 tpl:rounded tpl:border-none tpl:bg-transparent tpl:px-2.5 tpl:text-xs tpl:font-medium tpl:text-[var(--tpl-text)] tpl:transition-all tpl:duration-150 tpl:hover:bg-[var(--tpl-bg-active)]\"\n :aria-label=\"t.mergeTag.insert\"\n :title=\"t.mergeTag.insert\"\n @click=\"emit('add-merge-tag')\"\n >\n <ScanLine :size=\"16\" :stroke-width=\"2\" />\n {{ t.mergeTag.insertShort }}\n </button>\n </template>\n <template v-if=\"canInsertLogicTag\">\n <ToolbarSeparator />\n <button\n type=\"button\"\n class=\"tpl:flex tpl:h-8 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:gap-1.5 tpl:rounded tpl:border-none tpl:bg-transparent tpl:px-2.5 tpl:text-xs tpl:font-medium tpl:text-[var(--tpl-text)] tpl:transition-all tpl:duration-150 tpl:hover:bg-[var(--tpl-bg-active)]\"\n :aria-label=\"t.logicTag.insert\"\n :title=\"t.logicTag.insert\"\n data-testid=\"insert-logic-button\"\n @click=\"emit('add-logic-tag')\"\n >\n <Braces :size=\"16\" :stroke-width=\"2\" />\n {{ t.logicTag.insertShort }}\n </button>\n </template>\n </div>\n </template>\n <template v-else>\n <div\n class=\"tpl:flex tpl:items-center tpl:gap-2 tpl:px-2 tpl:text-xs tpl:text-[var(--tpl-text-dim)]\"\n >\n <LoaderCircle class=\"tpl-spinner\" :size=\"14\" :stroke-width=\"2\" />\n {{ t.errors.editorLoading }}\n </div>\n </template>\n </div>\n </Teleport>\n</template>\n","<script setup lang=\"ts\">\nimport { useRichTextEditor } from \"../../composables/useRichTextEditor\";\nimport { usePopoverRoot } from \"../../composables/usePopoverRoot\";\nimport type { ParagraphBlock as ParagraphBlockType } from \"@templatical/types\";\nimport ParagraphToolbar from \"./ParagraphToolbar.vue\";\nimport RichTextLinkDialog from \"./RichTextLinkDialog.vue\";\nimport RichTextEditorContent from \"./RichTextEditorContent.vue\";\nimport { withLinkColor } from \"../../utils/linkColorExtension\";\n\nconst props = defineProps<{\n block: ParagraphBlockType;\n toolbarPosition: { top: number; left: number };\n}>();\n\nconst emit = defineEmits<{\n (e: \"done\"): void;\n}>();\n\nconst popoverRoot = usePopoverRoot();\n\nconst {\n editor,\n EditorContent,\n isLoading,\n initError,\n retry,\n showLinkDialog,\n linkUrl,\n linkColor,\n linkDialogRef,\n canRequestMergeTag,\n canInsertLogicTag,\n openLinkDialog,\n insertLink,\n removeLink,\n closeLinkDialog,\n handleLinkKeydown,\n handleAddMergeTag,\n handleAddLogicTag,\n} = useRichTextEditor({\n blockId: () => props.block.id,\n blockContent: () => props.block.content,\n onDone: () => emit(\"done\"),\n editorName: \"ParagraphEditor\",\n async loadExtensions({\n mergeTags,\n syntax,\n triggerChar,\n autocompleteEnabled,\n suggestionEmptyText,\n }) {\n const [\n { Editor: TiptapEditor, EditorContent: EC },\n { default: StarterKit },\n { default: LinkExt },\n { default: UnderlineExt },\n { default: SubscriptExt },\n { default: SuperscriptExt },\n { default: TextAlign },\n { TextStyle },\n { default: Color },\n { default: FontFamily },\n { default: Highlight },\n {\n MergeTagNode,\n MergeTagSuggestion,\n LogicMergeTagNode,\n FontSize,\n LineHeight,\n LetterSpacing,\n },\n ] = await Promise.all([\n import(\"@tiptap/vue-3\"),\n import(\"@tiptap/starter-kit\"),\n import(\"@tiptap/extension-link\"),\n import(\"@tiptap/extension-underline\"),\n import(\"@tiptap/extension-subscript\"),\n import(\"@tiptap/extension-superscript\"),\n import(\"@tiptap/extension-text-align\"),\n import(\"@tiptap/extension-text-style\"),\n import(\"@tiptap/extension-color\"),\n import(\"@tiptap/extension-font-family\"),\n import(\"@tiptap/extension-highlight\"),\n import(\"../../extensions\"),\n ]);\n\n return {\n TiptapEditor,\n EC,\n extensions: [\n StarterKit.configure({\n heading: false,\n codeBlock: false,\n blockquote: false,\n horizontalRule: false,\n // StarterKit bundles @tiptap/extension-link (openOnClick: true).\n // Disable it so the LinkExt below (openOnClick: false) is the only\n // link extension — otherwise clicking a link while editing opens it.\n link: false,\n // StarterKit also bundles @tiptap/extension-underline; the editor\n // registers its own UnderlineExt below, so disable StarterKit's copy\n // to avoid a duplicate `underline` extension (and its console warning).\n underline: false,\n }),\n UnderlineExt,\n SubscriptExt,\n SuperscriptExt,\n withLinkColor(LinkExt).configure({\n openOnClick: false,\n HTMLAttributes: {\n target: \"_blank\",\n rel: \"noopener noreferrer\",\n },\n }),\n TextAlign.configure({ types: [\"paragraph\"] }),\n TextStyle,\n Color,\n FontFamily,\n Highlight.configure({ multicolor: true }),\n FontSize,\n LineHeight,\n LetterSpacing,\n MergeTagNode.configure({ mergeTags, syntax }),\n LogicMergeTagNode.configure({ syntax }),\n ...(autocompleteEnabled && triggerChar && mergeTags.length > 0\n ? [\n MergeTagSuggestion.configure({\n mergeTags,\n char: triggerChar,\n emptyText: suggestionEmptyText,\n popoverRoot,\n }),\n ]\n : []),\n ],\n };\n },\n});\n</script>\n\n<template>\n <div class=\"tpl-text-editor-wrapper tpl:relative\">\n <ParagraphToolbar\n :editor=\"editor\"\n :toolbar-position=\"toolbarPosition\"\n :is-loading=\"isLoading\"\n :can-request-merge-tag=\"canRequestMergeTag\"\n :can-insert-logic-tag=\"canInsertLogicTag\"\n @open-link-dialog=\"openLinkDialog\"\n @add-merge-tag=\"handleAddMergeTag\"\n @add-logic-tag=\"handleAddLogicTag\"\n />\n\n <RichTextEditorContent\n :editor=\"editor\"\n :editor-content=\"EditorContent\"\n :is-loading=\"isLoading\"\n :init-error=\"initError\"\n @retry=\"retry\"\n />\n\n <RichTextLinkDialog\n :visible=\"showLinkDialog\"\n :is-editing-link=\"editor?.isActive('link') ?? false\"\n v-model:dialog-ref=\"linkDialogRef\"\n v-model:link-url=\"linkUrl\"\n v-model:link-color=\"linkColor\"\n @close=\"closeLinkDialog\"\n @insert=\"insertLink\"\n @remove=\"removeLink\"\n @keydown=\"handleLinkKeydown\"\n />\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { useRichTextEditor } from \"../../composables/useRichTextEditor\";\nimport { usePopoverRoot } from \"../../composables/usePopoverRoot\";\nimport type { ParagraphBlock as ParagraphBlockType } from \"@templatical/types\";\nimport ParagraphToolbar from \"./ParagraphToolbar.vue\";\nimport RichTextLinkDialog from \"./RichTextLinkDialog.vue\";\nimport RichTextEditorContent from \"./RichTextEditorContent.vue\";\nimport { withLinkColor } from \"../../utils/linkColorExtension\";\n\nconst props = defineProps<{\n block: ParagraphBlockType;\n toolbarPosition: { top: number; left: number };\n}>();\n\nconst emit = defineEmits<{\n (e: \"done\"): void;\n}>();\n\nconst popoverRoot = usePopoverRoot();\n\nconst {\n editor,\n EditorContent,\n isLoading,\n initError,\n retry,\n showLinkDialog,\n linkUrl,\n linkColor,\n linkDialogRef,\n canRequestMergeTag,\n canInsertLogicTag,\n openLinkDialog,\n insertLink,\n removeLink,\n closeLinkDialog,\n handleLinkKeydown,\n handleAddMergeTag,\n handleAddLogicTag,\n} = useRichTextEditor({\n blockId: () => props.block.id,\n blockContent: () => props.block.content,\n onDone: () => emit(\"done\"),\n editorName: \"ParagraphEditor\",\n async loadExtensions({\n mergeTags,\n syntax,\n triggerChar,\n autocompleteEnabled,\n suggestionEmptyText,\n }) {\n const [\n { Editor: TiptapEditor, EditorContent: EC },\n { default: StarterKit },\n { default: LinkExt },\n { default: UnderlineExt },\n { default: SubscriptExt },\n { default: SuperscriptExt },\n { default: TextAlign },\n { TextStyle },\n { default: Color },\n { default: FontFamily },\n { default: Highlight },\n {\n MergeTagNode,\n MergeTagSuggestion,\n LogicMergeTagNode,\n FontSize,\n LineHeight,\n LetterSpacing,\n },\n ] = await Promise.all([\n import(\"@tiptap/vue-3\"),\n import(\"@tiptap/starter-kit\"),\n import(\"@tiptap/extension-link\"),\n import(\"@tiptap/extension-underline\"),\n import(\"@tiptap/extension-subscript\"),\n import(\"@tiptap/extension-superscript\"),\n import(\"@tiptap/extension-text-align\"),\n import(\"@tiptap/extension-text-style\"),\n import(\"@tiptap/extension-color\"),\n import(\"@tiptap/extension-font-family\"),\n import(\"@tiptap/extension-highlight\"),\n import(\"../../extensions\"),\n ]);\n\n return {\n TiptapEditor,\n EC,\n extensions: [\n StarterKit.configure({\n heading: false,\n codeBlock: false,\n blockquote: false,\n horizontalRule: false,\n // StarterKit bundles @tiptap/extension-link (openOnClick: true).\n // Disable it so the LinkExt below (openOnClick: false) is the only\n // link extension — otherwise clicking a link while editing opens it.\n link: false,\n // StarterKit also bundles @tiptap/extension-underline; the editor\n // registers its own UnderlineExt below, so disable StarterKit's copy\n // to avoid a duplicate `underline` extension (and its console warning).\n underline: false,\n }),\n UnderlineExt,\n SubscriptExt,\n SuperscriptExt,\n withLinkColor(LinkExt).configure({\n openOnClick: false,\n HTMLAttributes: {\n target: \"_blank\",\n rel: \"noopener noreferrer\",\n },\n }),\n TextAlign.configure({ types: [\"paragraph\"] }),\n TextStyle,\n Color,\n FontFamily,\n Highlight.configure({ multicolor: true }),\n FontSize,\n LineHeight,\n LetterSpacing,\n MergeTagNode.configure({ mergeTags, syntax }),\n LogicMergeTagNode.configure({ syntax }),\n ...(autocompleteEnabled && triggerChar && mergeTags.length > 0\n ? [\n MergeTagSuggestion.configure({\n mergeTags,\n char: triggerChar,\n emptyText: suggestionEmptyText,\n popoverRoot,\n }),\n ]\n : []),\n ],\n };\n },\n});\n</script>\n\n<template>\n <div class=\"tpl-text-editor-wrapper tpl:relative\">\n <ParagraphToolbar\n :editor=\"editor\"\n :toolbar-position=\"toolbarPosition\"\n :is-loading=\"isLoading\"\n :can-request-merge-tag=\"canRequestMergeTag\"\n :can-insert-logic-tag=\"canInsertLogicTag\"\n @open-link-dialog=\"openLinkDialog\"\n @add-merge-tag=\"handleAddMergeTag\"\n @add-logic-tag=\"handleAddLogicTag\"\n />\n\n <RichTextEditorContent\n :editor=\"editor\"\n :editor-content=\"EditorContent\"\n :is-loading=\"isLoading\"\n :init-error=\"initError\"\n @retry=\"retry\"\n />\n\n <RichTextLinkDialog\n :visible=\"showLinkDialog\"\n :is-editing-link=\"editor?.isActive('link') ?? false\"\n v-model:dialog-ref=\"linkDialogRef\"\n v-model:link-url=\"linkUrl\"\n v-model:link-color=\"linkColor\"\n @close=\"closeLinkDialog\"\n @insert=\"insertLink\"\n @remove=\"removeLink\"\n @keydown=\"handleLinkKeydown\"\n />\n </div>\n</template>\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"RichTextEditorContent-DtL49Jpo.js","names":[],"sources":["../../../src/components/blocks/RichTextLinkDialog.vue","../../../src/components/blocks/RichTextLinkDialog.vue","../../../src/components/blocks/RichTextEditorContent.vue","../../../src/components/blocks/RichTextEditorContent.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { useI18n } from \"../../composables/useI18n\";\nimport { usePopoverRoot } from \"../../composables/usePopoverRoot\";\nimport ColorPicker from \"../ColorPicker.vue\";\nimport { X } from \"@lucide/vue\";\nimport { computed, inject } from \"vue\";\nimport { EDITOR_KEY, THEME_STYLES_KEY, UI_THEME_KEY } from \"../../keys\";\n\ndefineProps<{\n visible: boolean;\n isEditingLink: boolean;\n}>();\n\nconst linkUrl = defineModel<string>(\"linkUrl\", { required: true });\nconst linkColor = defineModel<string>(\"linkColor\", { default: \"\" });\nconst dialogRef = defineModel<HTMLElement | null>(\"dialogRef\", {\n required: true,\n});\n\nconst emit = defineEmits<{\n (e: \"close\"): void;\n (e: \"insert\"): void;\n (e: \"remove\"): void;\n (e: \"keydown\", event: KeyboardEvent): void;\n}>();\n\nconst themeStyles = inject(THEME_STYLES_KEY, null);\nconst tplUiTheme = inject(UI_THEME_KEY, null);\nconst popoverRoot = usePopoverRoot();\nconst editorReturn = inject(EDITOR_KEY, null);\n// Seed the picker at the document link color so opening it starts from the\n// color an unset link would inherit; an explicit value paints the swatch.\nconst documentLinkColor = computed(\n () => editorReturn?.content.value.settings.linkColor || \"#2c85de\",\n);\n\nconst { t } = useI18n();\n</script>\n\n<template>\n <Teleport v-if=\"popoverRoot\" :to=\"popoverRoot\">\n <div\n v-if=\"visible\"\n :data-tpl-theme=\"tplUiTheme\"\n class=\"tpl tpl-link-dialog tpl:fixed tpl:inset-0 tpl:z-modal tpl:flex tpl:items-center tpl:justify-center\"\n :style=\"themeStyles\"\n @click.self=\"emit('close')\"\n >\n <div\n :ref=\"(el) => (dialogRef = el as HTMLElement | null)\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"tpl-link-dialog-title\"\n class=\"tpl:w-[400px] tpl:overflow-hidden tpl:rounded-lg tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:shadow-lg\"\n >\n <div\n class=\"tpl:flex tpl:items-center tpl:justify-between tpl:border-b tpl:border-[var(--tpl-border)] tpl:px-5 tpl:py-4\"\n >\n <h4\n id=\"tpl-link-dialog-title\"\n class=\"tpl:m-0 tpl:text-sm tpl:font-semibold tpl:text-[var(--tpl-text)]\"\n >\n {{\n isEditingLink ? t.linkDialog.editLink : t.linkDialog.insertLink\n }}\n </h4>\n <button\n type=\"button\"\n :aria-label=\"t.linkDialog.cancel\"\n class=\"tpl:flex tpl:size-7 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded tpl:border-none tpl:bg-transparent tpl:p-0 tpl:text-[var(--tpl-text-muted)] tpl:hover:bg-[var(--tpl-bg-hover)] tpl:hover:text-[var(--tpl-text)]\"\n @click=\"emit('close')\"\n >\n <X :size=\"16\" :stroke-width=\"2\" />\n </button>\n </div>\n <div class=\"tpl:p-5\">\n <div class=\"tpl:mb-4 tpl:last:mb-0\">\n <label\n for=\"tpl-link-dialog-url\"\n class=\"tpl:mb-1.5 tpl:block tpl:text-xs tpl:font-medium tpl:tracking-wide tpl:text-[var(--tpl-text-muted)] tpl:uppercase\"\n >{{ t.linkDialog.urlLabel }}</label\n >\n <input\n id=\"tpl-link-dialog-url\"\n v-model=\"linkUrl\"\n type=\"url\"\n class=\"tpl:w-full tpl:rounded-md tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:px-3 tpl:py-2.5 tpl:text-sm tpl:text-[var(--tpl-text)] tpl:transition-all tpl:duration-150 tpl:outline-none tpl:placeholder:text-[var(--tpl-text-dim)] tpl:focus:border-[var(--tpl-primary)] tpl:focus:shadow-[0_0_0_3px_var(--tpl-primary-light)]\"\n :placeholder=\"t.linkDialog.urlPlaceholder\"\n autofocus\n @keydown=\"emit('keydown', $event)\"\n />\n </div>\n <div class=\"tpl:mb-4 tpl:last:mb-0\">\n <label\n class=\"tpl:mb-1.5 tpl:block tpl:text-xs tpl:font-medium tpl:tracking-wide tpl:text-[var(--tpl-text-muted)] tpl:uppercase\"\n >{{ t.linkDialog.colorLabel }}</label\n >\n <ColorPicker\n :model-value=\"linkColor\"\n :seed-color=\"documentLinkColor\"\n @update:model-value=\"linkColor = $event\"\n />\n </div>\n </div>\n <div\n class=\"tpl:flex tpl:items-center tpl:justify-between tpl:border-t tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg-elevated)] tpl:px-5 tpl:py-4\"\n >\n <button\n v-if=\"isEditingLink\"\n type=\"button\"\n class=\"tpl:inline-flex tpl:cursor-pointer tpl:items-center tpl:rounded-md tpl:border tpl:border-[var(--tpl-danger)] tpl:bg-transparent tpl:px-4 tpl:py-2 tpl:text-[13px] tpl:font-medium tpl:text-[var(--tpl-danger)] tpl:transition-all tpl:duration-150 tpl:hover:bg-[var(--tpl-danger-light)]\"\n @click=\"emit('remove')\"\n >\n {{ t.linkDialog.removeLink }}\n </button>\n <div class=\"tpl:ml-auto tpl:flex tpl:gap-2\">\n <button\n type=\"button\"\n class=\"tpl:inline-flex tpl:cursor-pointer tpl:items-center tpl:rounded-md tpl:border tpl:border-[var(--tpl-border)] tpl:bg-transparent tpl:px-4 tpl:py-2 tpl:text-[13px] tpl:font-medium tpl:text-[var(--tpl-text-muted)] tpl:transition-all tpl:duration-150 tpl:hover:bg-[var(--tpl-bg-hover)] tpl:hover:text-[var(--tpl-text)]\"\n @click=\"emit('close')\"\n >\n {{ t.linkDialog.cancel }}\n </button>\n <button\n type=\"button\"\n class=\"tpl:inline-flex tpl:cursor-pointer tpl:items-center tpl:rounded-md tpl:border-none tpl:bg-[var(--tpl-primary)] tpl:px-4 tpl:py-2 tpl:text-[13px] tpl:font-medium tpl:transition-all tpl:duration-150 tpl:hover:bg-[var(--tpl-primary-hover)] tpl:text-[var(--tpl-bg)]\"\n @click=\"emit('insert')\"\n >\n {{\n isEditingLink\n ? t.linkDialog.updateLink\n : t.linkDialog.insertLink\n }}\n </button>\n </div>\n </div>\n </div>\n </div>\n </Teleport>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../composables/useI18n\";\nimport { usePopoverRoot } from \"../../composables/usePopoverRoot\";\nimport ColorPicker from \"../ColorPicker.vue\";\nimport { X } from \"@lucide/vue\";\nimport { computed, inject } from \"vue\";\nimport { EDITOR_KEY, THEME_STYLES_KEY, UI_THEME_KEY } from \"../../keys\";\n\ndefineProps<{\n visible: boolean;\n isEditingLink: boolean;\n}>();\n\nconst linkUrl = defineModel<string>(\"linkUrl\", { required: true });\nconst linkColor = defineModel<string>(\"linkColor\", { default: \"\" });\nconst dialogRef = defineModel<HTMLElement | null>(\"dialogRef\", {\n required: true,\n});\n\nconst emit = defineEmits<{\n (e: \"close\"): void;\n (e: \"insert\"): void;\n (e: \"remove\"): void;\n (e: \"keydown\", event: KeyboardEvent): void;\n}>();\n\nconst themeStyles = inject(THEME_STYLES_KEY, null);\nconst tplUiTheme = inject(UI_THEME_KEY, null);\nconst popoverRoot = usePopoverRoot();\nconst editorReturn = inject(EDITOR_KEY, null);\n// Seed the picker at the document link color so opening it starts from the\n// color an unset link would inherit; an explicit value paints the swatch.\nconst documentLinkColor = computed(\n () => editorReturn?.content.value.settings.linkColor || \"#2c85de\",\n);\n\nconst { t } = useI18n();\n</script>\n\n<template>\n <Teleport v-if=\"popoverRoot\" :to=\"popoverRoot\">\n <div\n v-if=\"visible\"\n :data-tpl-theme=\"tplUiTheme\"\n class=\"tpl tpl-link-dialog tpl:fixed tpl:inset-0 tpl:z-modal tpl:flex tpl:items-center tpl:justify-center\"\n :style=\"themeStyles\"\n @click.self=\"emit('close')\"\n >\n <div\n :ref=\"(el) => (dialogRef = el as HTMLElement | null)\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"tpl-link-dialog-title\"\n class=\"tpl:w-[400px] tpl:overflow-hidden tpl:rounded-lg tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:shadow-lg\"\n >\n <div\n class=\"tpl:flex tpl:items-center tpl:justify-between tpl:border-b tpl:border-[var(--tpl-border)] tpl:px-5 tpl:py-4\"\n >\n <h4\n id=\"tpl-link-dialog-title\"\n class=\"tpl:m-0 tpl:text-sm tpl:font-semibold tpl:text-[var(--tpl-text)]\"\n >\n {{\n isEditingLink ? t.linkDialog.editLink : t.linkDialog.insertLink\n }}\n </h4>\n <button\n type=\"button\"\n :aria-label=\"t.linkDialog.cancel\"\n class=\"tpl:flex tpl:size-7 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded tpl:border-none tpl:bg-transparent tpl:p-0 tpl:text-[var(--tpl-text-muted)] tpl:hover:bg-[var(--tpl-bg-hover)] tpl:hover:text-[var(--tpl-text)]\"\n @click=\"emit('close')\"\n >\n <X :size=\"16\" :stroke-width=\"2\" />\n </button>\n </div>\n <div class=\"tpl:p-5\">\n <div class=\"tpl:mb-4 tpl:last:mb-0\">\n <label\n for=\"tpl-link-dialog-url\"\n class=\"tpl:mb-1.5 tpl:block tpl:text-xs tpl:font-medium tpl:tracking-wide tpl:text-[var(--tpl-text-muted)] tpl:uppercase\"\n >{{ t.linkDialog.urlLabel }}</label\n >\n <input\n id=\"tpl-link-dialog-url\"\n v-model=\"linkUrl\"\n type=\"url\"\n class=\"tpl:w-full tpl:rounded-md tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:px-3 tpl:py-2.5 tpl:text-sm tpl:text-[var(--tpl-text)] tpl:transition-all tpl:duration-150 tpl:outline-none tpl:placeholder:text-[var(--tpl-text-dim)] tpl:focus:border-[var(--tpl-primary)] tpl:focus:shadow-[0_0_0_3px_var(--tpl-primary-light)]\"\n :placeholder=\"t.linkDialog.urlPlaceholder\"\n autofocus\n @keydown=\"emit('keydown', $event)\"\n />\n </div>\n <div class=\"tpl:mb-4 tpl:last:mb-0\">\n <label\n class=\"tpl:mb-1.5 tpl:block tpl:text-xs tpl:font-medium tpl:tracking-wide tpl:text-[var(--tpl-text-muted)] tpl:uppercase\"\n >{{ t.linkDialog.colorLabel }}</label\n >\n <ColorPicker\n :model-value=\"linkColor\"\n :seed-color=\"documentLinkColor\"\n @update:model-value=\"linkColor = $event\"\n />\n </div>\n </div>\n <div\n class=\"tpl:flex tpl:items-center tpl:justify-between tpl:border-t tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg-elevated)] tpl:px-5 tpl:py-4\"\n >\n <button\n v-if=\"isEditingLink\"\n type=\"button\"\n class=\"tpl:inline-flex tpl:cursor-pointer tpl:items-center tpl:rounded-md tpl:border tpl:border-[var(--tpl-danger)] tpl:bg-transparent tpl:px-4 tpl:py-2 tpl:text-[13px] tpl:font-medium tpl:text-[var(--tpl-danger)] tpl:transition-all tpl:duration-150 tpl:hover:bg-[var(--tpl-danger-light)]\"\n @click=\"emit('remove')\"\n >\n {{ t.linkDialog.removeLink }}\n </button>\n <div class=\"tpl:ml-auto tpl:flex tpl:gap-2\">\n <button\n type=\"button\"\n class=\"tpl:inline-flex tpl:cursor-pointer tpl:items-center tpl:rounded-md tpl:border tpl:border-[var(--tpl-border)] tpl:bg-transparent tpl:px-4 tpl:py-2 tpl:text-[13px] tpl:font-medium tpl:text-[var(--tpl-text-muted)] tpl:transition-all tpl:duration-150 tpl:hover:bg-[var(--tpl-bg-hover)] tpl:hover:text-[var(--tpl-text)]\"\n @click=\"emit('close')\"\n >\n {{ t.linkDialog.cancel }}\n </button>\n <button\n type=\"button\"\n class=\"tpl:inline-flex tpl:cursor-pointer tpl:items-center tpl:rounded-md tpl:border-none tpl:bg-[var(--tpl-primary)] tpl:px-4 tpl:py-2 tpl:text-[13px] tpl:font-medium tpl:transition-all tpl:duration-150 tpl:hover:bg-[var(--tpl-primary-hover)] tpl:text-[var(--tpl-bg)]\"\n @click=\"emit('insert')\"\n >\n {{\n isEditingLink\n ? t.linkDialog.updateLink\n : t.linkDialog.insertLink\n }}\n </button>\n </div>\n </div>\n </div>\n </div>\n </Teleport>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../composables/useI18n\";\nimport type { Editor } from \"@tiptap/vue-3\";\nimport type { Component } from \"vue\";\n\ndefineProps<{\n editor: Editor | null;\n editorContent: Component | null;\n isLoading: boolean;\n initError: string | null;\n}>();\n\nconst emit = defineEmits<{\n (e: \"retry\"): void;\n}>();\n\nconst { t } = useI18n();\n</script>\n\n<template>\n <div\n v-if=\"isLoading\"\n class=\"tpl-text-editable tpl:min-h-[1.5em] tpl:rounded tpl:border tpl:border-dashed tpl:border-[var(--tpl-primary)] tpl:p-2\"\n >\n <div class=\"tpl:animate-pulse tpl:text-[var(--tpl-text-dim)]\">\n {{ t.errors.editorLoading }}\n </div>\n </div>\n <div\n v-else-if=\"initError\"\n class=\"tpl-text-editable tpl:min-h-[1.5em] tpl:rounded tpl:border tpl:border-dashed tpl:p-2 tpl:text-center tpl:text-xs tpl:border-[var(--tpl-danger)] tpl:text-[var(--tpl-text-muted)]\"\n >\n {{ t.errors.editorLoadFailed }}\n <button\n class=\"tpl:ml-1 tpl:cursor-pointer tpl:border-none tpl:bg-transparent tpl:p-0 tpl:underline tpl:text-[var(--tpl-primary)]\"\n @click=\"emit('retry')\"\n >\n {{ t.errors.retry }}\n </button>\n </div>\n <component\n :is=\"editorContent\"\n v-else-if=\"editorContent && editor\"\n :editor=\"editor as Editor\"\n class=\"tpl-text-editable tpl:min-h-[1.5em] tpl:rounded tpl:border tpl:border-dashed tpl:border-[var(--tpl-primary)] tpl:p-2\"\n />\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../composables/useI18n\";\nimport type { Editor } from \"@tiptap/vue-3\";\nimport type { Component } from \"vue\";\n\ndefineProps<{\n editor: Editor | null;\n editorContent: Component | null;\n isLoading: boolean;\n initError: string | null;\n}>();\n\nconst emit = defineEmits<{\n (e: \"retry\"): void;\n}>();\n\nconst { t } = useI18n();\n</script>\n\n<template>\n <div\n v-if=\"isLoading\"\n class=\"tpl-text-editable tpl:min-h-[1.5em] tpl:rounded tpl:border tpl:border-dashed tpl:border-[var(--tpl-primary)] tpl:p-2\"\n >\n <div class=\"tpl:animate-pulse tpl:text-[var(--tpl-text-dim)]\">\n {{ t.errors.editorLoading }}\n </div>\n </div>\n <div\n v-else-if=\"initError\"\n class=\"tpl-text-editable tpl:min-h-[1.5em] tpl:rounded tpl:border tpl:border-dashed tpl:p-2 tpl:text-center tpl:text-xs tpl:border-[var(--tpl-danger)] tpl:text-[var(--tpl-text-muted)]\"\n >\n {{ t.errors.editorLoadFailed }}\n <button\n class=\"tpl:ml-1 tpl:cursor-pointer tpl:border-none tpl:bg-transparent tpl:p-0 tpl:underline tpl:text-[var(--tpl-primary)]\"\n @click=\"emit('retry')\"\n >\n {{ t.errors.retry }}\n </button>\n </div>\n <component\n :is=\"editorContent\"\n v-else-if=\"editorContent && editor\"\n :editor=\"editor as Editor\"\n class=\"tpl-text-editable tpl:min-h-[1.5em] tpl:rounded tpl:border tpl:border-dashed tpl:border-[var(--tpl-primary)] tpl:p-2\"\n />\n</template>\n"],"mappings":"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"}
@@ -1,264 +0,0 @@
1
- import { A as e, H as t, L as n, M as r, P as i, T as a, V as o, Z as s, c, ct as l, f as u, g as d, h as f, it as p, k as m, l as h, lt as g, m as _, o as v, p as y, st as b, t as x, u as S, v as C, x as w, y as T, z as E } from "./draggable-DCs9qL-b.js";
2
- import { M as D } from "./useEditorCore-CKQ9gmoo.js";
3
- import { G as O, R as k, g as A, p as ee } from "./keys-Cmc2Yl3D.js";
4
- import { t as j } from "./useI18n-UMs0F71x.js";
5
- import { It as M, Nt as te, ht as N, it as P } from "./icons-CvYN6zrM.js";
6
- import { n as F } from "./emailFrameWidth-BmFCN2dp.js";
7
- import { t as I } from "./TplModal-iCmoKB2k.js";
8
- import { t as L } from "./BlockPreviewCanvas-DPtiMsVz.js";
9
- //#region src/components/SavedBlockPreviewRow.vue?vue&type=script&setup=true&lang.ts
10
- var R = ["data-block-id"], z = ["aria-label", "title"], B = { class: "tpl:relative tpl:min-w-0 tpl:flex-1" }, V = {
11
- key: 0,
12
- "data-testid": "saved-blocks-preview-fade",
13
- "aria-hidden": "true",
14
- class: "tpl:pointer-events-none tpl:absolute tpl:inset-x-0 tpl:bottom-0 tpl:h-10",
15
- style: { background: "linear-gradient(\n to bottom,\n transparent,\n var(--tpl-bg) 90%\n )" }
16
- }, H = ["aria-label", "aria-expanded"], U = 240, W = /* @__PURE__ */ w({
17
- __name: "SavedBlockPreviewRow",
18
- props: {
19
- block: {},
20
- position: {},
21
- total: {}
22
- },
23
- emits: ["move"],
24
- setup(t, { emit: i }) {
25
- let o = t, v = i, { t: b, format: x } = j(), S = a(ee, []), w = a(A, null), E = u(() => F(w?.content.value.settings)), O = s(null), k = s(null), P = s(1), I = s(null), W = s(!1), G = null, K = u(() => I.value !== null && I.value > U), q = u(() => I.value === null ? null : W.value || !K.value ? I.value : U);
26
- function J() {
27
- let e = O.value, t = k.value;
28
- if (!e || !t) return;
29
- let n = e.clientWidth;
30
- if (n <= 0) return;
31
- let r = Math.min(1, n / E.value);
32
- P.value = r, I.value = t.offsetHeight * r;
33
- }
34
- e(() => {
35
- J(), !(typeof ResizeObserver > "u") && (G = new ResizeObserver(() => J()), O.value && G.observe(O.value), k.value && G.observe(k.value));
36
- }), m(() => {
37
- G?.disconnect(), G = null;
38
- });
39
- let Y = u(() => D(o.block, b, S)), X = u(() => x(b.savedBlocks.reorderHandle, {
40
- block: Y.value,
41
- position: o.position,
42
- total: o.total
43
- })), Z = u(() => x(W.value ? b.savedBlocks.collapsePreview : b.savedBlocks.expandPreview, { block: Y.value }));
44
- return (e, i) => (r(), d("div", {
45
- "data-testid": "saved-blocks-reorder-row",
46
- "data-block-id": t.block.id,
47
- class: "tpl:flex tpl:items-start tpl:gap-1.5 tpl:rounded-[var(--tpl-radius-md)] tpl:border tpl:p-1.5 tpl:border-[var(--tpl-border)]",
48
- style: { "background-color": "var(--tpl-bg)" }
49
- }, [y("button", {
50
- type: "button",
51
- "data-testid": "saved-blocks-reorder-handle",
52
- class: "tpl-saved-block-reorder-handle tpl:mt-0.5 tpl:shrink-0 tpl:cursor-grab tpl:rounded tpl:border-none tpl:bg-transparent tpl:p-0.5 tpl:text-[var(--tpl-text-dim)]",
53
- "aria-label": X.value,
54
- title: p(b).savedBlocks.reorderHint,
55
- onKeydown: [i[0] ||= c(h((e) => v("move", -1), ["prevent"]), ["up"]), i[1] ||= c(h((e) => v("move", 1), ["prevent"]), ["down"])]
56
- }, [T(p(N), {
57
- size: 14,
58
- "stroke-width": 1.5
59
- })], 40, z), y("div", B, [
60
- y("div", {
61
- ref_key: "frameEl",
62
- ref: O,
63
- class: "tpl:overflow-hidden",
64
- style: l({ height: q.value === null ? void 0 : `${q.value}px` })
65
- }, [y("div", {
66
- ref_key: "contentEl",
67
- ref: k,
68
- style: l({
69
- width: `${E.value}px`,
70
- transform: `scale(${P.value})`,
71
- transformOrigin: "top left"
72
- })
73
- }, [T(L, { blocks: [t.block] }, null, 8, ["blocks"])], 4)], 4),
74
- K.value && !W.value ? (r(), d("div", V)) : f("", !0),
75
- K.value ? (r(), d("button", {
76
- key: 1,
77
- type: "button",
78
- "data-testid": "saved-blocks-preview-toggle",
79
- class: "tpl:absolute tpl:bottom-1 tpl:right-1 tpl:flex tpl:cursor-pointer tpl:items-center tpl:gap-0.5 tpl:rounded tpl:border tpl:px-1.5 tpl:py-0.5 tpl:text-[10px] tpl:font-medium tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg-elevated)] tpl:text-[var(--tpl-text-muted)]",
80
- "aria-label": Z.value,
81
- "aria-expanded": W.value,
82
- onClick: i[2] ||= (e) => W.value = !W.value
83
- }, [(r(), _(n(p(W.value ? te : M)), {
84
- size: 11,
85
- "stroke-width": 2
86
- })), C(" " + g(W.value ? p(b).savedBlocks.collapse : p(b).savedBlocks.expand), 1)], 8, H)) : f("", !0)
87
- ])], 8, R));
88
- }
89
- }), G = ["aria-busy"], K = {
90
- id: "tpl-save-block-title",
91
- class: "tpl:mb-4 tpl:shrink-0 tpl:text-sm tpl:font-semibold tpl:text-[var(--tpl-text)]"
92
- }, q = { class: "tpl:mb-3 tpl:shrink-0" }, J = { class: "tpl:mb-1.5 tpl:block tpl:text-sm tpl:font-medium tpl:text-[var(--tpl-text-muted)]" }, Y = ["placeholder", "disabled"], X = { class: "tpl:mb-3 tpl:shrink-0" }, Z = {
93
- for: "tpl-save-block-category",
94
- class: "tpl:mb-1.5 tpl:block tpl:text-sm tpl:font-medium tpl:text-[var(--tpl-text-muted)]"
95
- }, ne = ["placeholder", "disabled"], re = { id: "tpl-saved-block-categories" }, ie = ["value"], ae = {
96
- "data-testid": "saved-blocks-save-summary",
97
- class: "tpl:mb-1.5 tpl:shrink-0 tpl:text-xs tpl:text-[var(--tpl-text-muted)]"
98
- }, oe = { class: "tpl:text-[var(--tpl-text-dim)]" }, se = {
99
- key: 0,
100
- class: "tpl:mb-1.5 tpl:shrink-0 tpl:text-[11px] tpl:text-[var(--tpl-text-dim)]"
101
- }, ce = {
102
- class: "tpl-sr-only",
103
- role: "status",
104
- "aria-live": "polite",
105
- "aria-atomic": "true"
106
- }, le = {
107
- key: 1,
108
- role: "alert",
109
- class: "tpl:mb-3 tpl:shrink-0 tpl:text-xs tpl:text-[var(--tpl-danger)]"
110
- }, ue = { class: "tpl:flex tpl:shrink-0 tpl:justify-end tpl:gap-2" }, de = ["disabled"], fe = ["disabled"], pe = {
111
- key: 0,
112
- class: "tpl:flex tpl:items-center tpl:gap-1.5"
113
- }, me = { key: 1 }, Q = /* @__PURE__ */ w({
114
- __name: "SaveBlockDialog",
115
- props: {
116
- visible: { type: Boolean },
117
- pickedIds: {}
118
- },
119
- emits: ["close", "saved"],
120
- setup(e, { emit: n }) {
121
- let c = e, l = n, { t: m, format: h } = j(), w = O(A, "SaveBlockDialog"), M = O(k, "SaveBlockDialog"), te = a(ee, []), N = s(""), F = s(""), L = s(!1), R = s(null), z = s(""), B = s([]), V = u(() => {
122
- let e = new Map(w.content.value.blocks.map((e) => [e.id, e]));
123
- return B.value.map((t) => e.get(t)).filter((e) => e !== void 0);
124
- }), H = u({
125
- get: () => V.value,
126
- set: (e) => {
127
- B.value = e.map((e) => e.id);
128
- }
129
- });
130
- function U(e, t) {
131
- let n = V.value.map((e) => e.id), r = n.indexOf(e), i = r + t;
132
- if (r === -1 || i < 0 || i >= n.length) return;
133
- [n[r], n[i]] = [n[i], n[r]], B.value = n;
134
- let a = V.value[i];
135
- z.value = a ? h(m.savedBlocks.reorderAnnouncement, {
136
- block: Q(a),
137
- position: i + 1,
138
- total: n.length
139
- }) : "";
140
- }
141
- function Q(e) {
142
- return D(e, m, te);
143
- }
144
- let he = u(() => V.value.map(Q).join(", "));
145
- E(() => c.visible, (e) => {
146
- e && (N.value = "", F.value = "", R.value = null, z.value = "", B.value = [...c.pickedIds]);
147
- }, { immediate: !0 });
148
- let ge = u(() => N.value.trim().length > 0 && V.value.length > 0 && !L.value);
149
- async function _e() {
150
- if (!ge.value) return;
151
- let e = V.value;
152
- if (e.length !== 0) {
153
- L.value = !0, R.value = null;
154
- try {
155
- await M.create(N.value.trim(), e, F.value.trim() || void 0), l("saved"), l("close");
156
- } catch (e) {
157
- R.value = e.message;
158
- } finally {
159
- L.value = !1;
160
- }
161
- }
162
- }
163
- function $() {
164
- L.value || l("close");
165
- }
166
- function ve(e) {
167
- e.key === "Enter" && !e.shiftKey && (e.preventDefault(), _e()), e.key === "Escape" && $();
168
- }
169
- return (n, a) => (r(), _(I, {
170
- visible: e.visible,
171
- onClose: $,
172
- onKeydown: ve
173
- }, {
174
- default: o(() => [y("div", {
175
- role: "dialog",
176
- "aria-modal": "true",
177
- "aria-busy": L.value,
178
- "aria-labelledby": "tpl-save-block-title",
179
- class: "tpl-scale-in tpl:mx-4 tpl:flex tpl:w-full tpl:max-w-2xl tpl:flex-col tpl:rounded-[var(--tpl-radius-lg)] tpl:p-5",
180
- style: {
181
- "background-color": "var(--tpl-bg-elevated)",
182
- "box-shadow": "var(--tpl-shadow-xl)",
183
- "max-height": "90vh"
184
- }
185
- }, [
186
- y("h3", K, g(p(m).savedBlocks.saveAsBlock), 1),
187
- y("div", q, [y("label", J, g(p(m).savedBlocks.name), 1), t(y("input", {
188
- "onUpdate:modelValue": a[0] ||= (e) => N.value = e,
189
- type: "text",
190
- "data-testid": "saved-blocks-name-input",
191
- placeholder: p(m).savedBlocks.namePlaceholder,
192
- 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)]",
193
- disabled: L.value
194
- }, null, 8, Y), [[v, N.value]])]),
195
- y("div", X, [
196
- y("label", Z, g(p(m).savedBlocks.category), 1),
197
- t(y("input", {
198
- id: "tpl-save-block-category",
199
- "onUpdate:modelValue": a[1] ||= (e) => F.value = e,
200
- type: "text",
201
- "data-testid": "saved-blocks-category-input",
202
- list: "tpl-saved-block-categories",
203
- placeholder: p(m).savedBlocks.categoryPlaceholder,
204
- 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)]",
205
- disabled: L.value
206
- }, null, 8, ne), [[v, F.value]]),
207
- y("datalist", re, [(r(!0), d(S, null, i(p(M).categories.value, (e) => (r(), d("option", {
208
- key: e,
209
- value: e
210
- }, null, 8, ie))), 128))])
211
- ]),
212
- y("p", ae, [C(g(p(h)(p(m).savedBlocks.savingCount, { count: V.value.length })) + " ", 1), y("span", oe, g(he.value), 1)]),
213
- V.value.length > 1 ? (r(), d("p", se, g(p(m).savedBlocks.reorderHint), 1)) : f("", !0),
214
- T(p(x), {
215
- modelValue: H.value,
216
- "onUpdate:modelValue": a[2] ||= (e) => H.value = e,
217
- "data-testid": "saved-blocks-reorder-list",
218
- animation: 150,
219
- "ghost-class": "tpl-ghost",
220
- "drag-class": "tpl-dragging",
221
- handle: ".tpl-saved-block-reorder-handle",
222
- "force-fallback": !0,
223
- class: "tpl:mb-4 tpl:flex tpl:min-h-0 tpl:flex-1 tpl:flex-col tpl:gap-1.5 tpl:overflow-y-auto"
224
- }, {
225
- default: o(() => [(r(!0), d(S, null, i(H.value, (e, t) => (r(), _(W, {
226
- key: e.id,
227
- block: e,
228
- position: t + 1,
229
- total: H.value.length,
230
- onMove: (t) => U(e.id, t)
231
- }, null, 8, [
232
- "block",
233
- "position",
234
- "total",
235
- "onMove"
236
- ]))), 128))]),
237
- _: 1
238
- }, 8, ["modelValue"]),
239
- y("div", ce, g(z.value), 1),
240
- R.value ? (r(), d("p", le, g(R.value), 1)) : f("", !0),
241
- y("div", ue, [y("button", {
242
- type: "button",
243
- class: b(["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": L.value }]),
244
- disabled: L.value,
245
- onClick: $
246
- }, g(p(m).savedBlocks.cancel), 11, de), y("button", {
247
- type: "button",
248
- 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)]",
249
- disabled: !ge.value,
250
- onClick: _e
251
- }, [L.value ? (r(), d("span", pe, [T(p(P), {
252
- class: "tpl:animate-spin",
253
- size: 12,
254
- "stroke-width": 2
255
- }), C(" " + g(p(m).savedBlocks.saving), 1)])) : (r(), d("span", me, g(p(m).savedBlocks.save), 1))], 8, fe)])
256
- ], 8, G)]),
257
- _: 1
258
- }, 8, ["visible"]));
259
- }
260
- });
261
- //#endregion
262
- export { Q as default };
263
-
264
- //# sourceMappingURL=SaveBlockDialog-BfNVeXXV.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"SaveBlockDialog-BfNVeXXV.js","names":[],"sources":["../../../src/components/SavedBlockPreviewRow.vue","../../../src/components/SavedBlockPreviewRow.vue","../../../src/components/SaveBlockDialog.vue","../../../src/components/SaveBlockDialog.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * One reorderable row in the save dialog's preview list: a grip handle beside a\n * scaled-down render of the real block.\n *\n * The preview is the same `BlockPreviewCanvas` the browser modal uses, so\n * a row shows what the block actually looks like rather than a description of\n * it. That canvas is fixed at the 600px email width every block component\n * renders against, so the row shrinks it with `transform: scale()` instead of\n * re-flowing it at dialog width — a re-flow would show a layout the recipient\n * will never see.\n *\n * `transform` doesn't participate in layout, so the frame would keep reserving\n * the full unscaled height; a `ResizeObserver` mirrors the scaled height back\n * onto it. The handle is a sibling of the scaled subtree, so it stays full size\n * and keeps its real hit area.\n *\n * Row chrome uses the raw theme tokens, not the `--tpl-chrome-*` aliases: the\n * chrome is an ancestor and a sibling of the rendered email content, never a\n * descendant, so the `.tpl-block-content` token override can't reach it.\n */\nimport BlockPreviewCanvas from \"./BlockPreviewCanvas.vue\";\nimport { useI18n } from \"../composables\";\nimport { CUSTOM_BLOCK_DEFINITIONS_KEY, EDITOR_KEY } from \"../keys\";\nimport { getEmailFrameWidth } from \"../utils/emailFrameWidth\";\nimport { getBlockLabel } from \"../utils/blockTypeLabels\";\nimport { ChevronDown, ChevronUp, GripVertical } from \"@lucide/vue\";\nimport type { Block } from \"@templatical/types\";\nimport { computed, inject, onBeforeUnmount, onMounted, ref } from \"vue\";\n\nconst props = defineProps<{\n block: Block;\n /** 1-based position in the list — spoken by the handle's label. */\n position: number;\n total: number;\n}>();\n\nconst emit = defineEmits<{\n (e: \"move\", delta: -1 | 1): void;\n}>();\n\nconst { t, format } = useI18n();\n// Optional: absent when the consumer registers no custom blocks.\nconst customBlockDefinitions = inject(CUSTOM_BLOCK_DEFINITIONS_KEY, []);\n\nconst editor = inject(EDITOR_KEY, null);\n\n/**\n * The width the nested `BlockPreviewCanvas` actually renders at.\n *\n * MUST come from the same helper the canvas uses, because the scale factor below\n * divides by it: a mismatch scales every row by the wrong amount, so a template\n * with a custom body width would overflow its frame or under-fill it. Rows are\n * always the desktop width — the save dialog has no viewport control.\n */\nconst previewWidth = computed(() =>\n getEmailFrameWidth(editor?.content.value.settings),\n);\n/**\n * Collapsed height for a tall block. A hero card or a multi-row section would\n * otherwise be taller than the whole list, leaving one row on screen — and\n * seeing the rows together is what makes them orderable. Anything taller\n * collapses to this, fades out at the cut, and can be expanded in place.\n */\nconst COLLAPSED_HEIGHT = 240;\n\nconst frameEl = ref<HTMLElement | null>(null);\nconst contentEl = ref<HTMLElement | null>(null);\nconst scale = ref(1);\n/** Scaled height of the whole block; `null` until first measured. */\nconst fullHeight = ref<number | null>(null);\nconst expanded = ref(false);\n\nlet observer: ResizeObserver | null = null;\n\n/** Only a block taller than the collapsed cap needs fading or expanding. */\nconst isClipped = computed(\n () => fullHeight.value !== null && fullHeight.value > COLLAPSED_HEIGHT,\n);\n\nconst frameHeight = computed(() => {\n if (fullHeight.value === null) return null;\n if (expanded.value || !isClipped.value) return fullHeight.value;\n return COLLAPSED_HEIGHT;\n});\n\nfunction measure(): void {\n const frame = frameEl.value;\n const content = contentEl.value;\n if (!frame || !content) return;\n const width = frame.clientWidth;\n if (width <= 0) return;\n // Never scale up: a dialog wider than the email would blow the preview past\n // its real proportions.\n const next = Math.min(1, width / previewWidth.value);\n scale.value = next;\n fullHeight.value = content.offsetHeight * next;\n}\n\nonMounted(() => {\n measure();\n // Absent in non-DOM test environments. Without it the frame simply falls back\n // to the untransformed height and clips — degraded, never broken.\n if (typeof ResizeObserver === \"undefined\") return;\n observer = new ResizeObserver(() => measure());\n if (frameEl.value) observer.observe(frameEl.value);\n if (contentEl.value) observer.observe(contentEl.value);\n});\n\nonBeforeUnmount(() => {\n observer?.disconnect();\n observer = null;\n});\n\nconst blockLabel = computed(() =>\n getBlockLabel(props.block, t, customBlockDefinitions),\n);\n\nconst handleLabel = computed(() =>\n format(t.savedBlocks.reorderHandle, {\n block: blockLabel.value,\n position: props.position,\n total: props.total,\n }),\n);\n\nconst toggleLabel = computed(() =>\n format(\n expanded.value\n ? t.savedBlocks.collapsePreview\n : t.savedBlocks.expandPreview,\n { block: blockLabel.value },\n ),\n);\n</script>\n\n<template>\n <div\n data-testid=\"saved-blocks-reorder-row\"\n :data-block-id=\"block.id\"\n class=\"tpl:flex tpl:items-start tpl:gap-1.5 tpl:rounded-[var(--tpl-radius-md)] tpl:border tpl:p-1.5 tpl:border-[var(--tpl-border)]\"\n style=\"background-color: var(--tpl-bg)\"\n >\n <!-- Sortable's `handle` selector points here, so a pointer-drag can only\n start from the grip — never from the preview itself. -->\n <button\n type=\"button\"\n data-testid=\"saved-blocks-reorder-handle\"\n class=\"tpl-saved-block-reorder-handle tpl:mt-0.5 tpl:shrink-0 tpl:cursor-grab tpl:rounded tpl:border-none tpl:bg-transparent tpl:p-0.5 tpl:text-[var(--tpl-text-dim)]\"\n :aria-label=\"handleLabel\"\n :title=\"t.savedBlocks.reorderHint\"\n @keydown.up.prevent=\"emit('move', -1)\"\n @keydown.down.prevent=\"emit('move', 1)\"\n >\n <GripVertical :size=\"14\" :stroke-width=\"1.5\" />\n </button>\n\n <div class=\"tpl:relative tpl:min-w-0 tpl:flex-1\">\n <div\n ref=\"frameEl\"\n class=\"tpl:overflow-hidden\"\n :style=\"{\n height: frameHeight !== null ? `${frameHeight}px` : undefined,\n }\"\n >\n <div\n ref=\"contentEl\"\n :style=\"{\n width: `${previewWidth}px`,\n transform: `scale(${scale})`,\n transformOrigin: 'top left',\n }\"\n >\n <BlockPreviewCanvas :blocks=\"[block]\" />\n </div>\n </div>\n\n <!-- Fade over the cut, so a clipped block reads as truncated rather than\n as a block that simply ends there. Pointer-transparent: it sits over\n the preview, and swallowing clicks would break the row. -->\n <div\n v-if=\"isClipped && !expanded\"\n data-testid=\"saved-blocks-preview-fade\"\n aria-hidden=\"true\"\n class=\"tpl:pointer-events-none tpl:absolute tpl:inset-x-0 tpl:bottom-0 tpl:h-10\"\n style=\"\n background: linear-gradient(\n to bottom,\n transparent,\n var(--tpl-bg) 90%\n );\n \"\n />\n\n <button\n v-if=\"isClipped\"\n type=\"button\"\n data-testid=\"saved-blocks-preview-toggle\"\n class=\"tpl:absolute tpl:bottom-1 tpl:right-1 tpl:flex tpl:cursor-pointer tpl:items-center tpl:gap-0.5 tpl:rounded tpl:border tpl:px-1.5 tpl:py-0.5 tpl:text-[10px] tpl:font-medium tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg-elevated)] tpl:text-[var(--tpl-text-muted)]\"\n :aria-label=\"toggleLabel\"\n :aria-expanded=\"expanded\"\n @click=\"expanded = !expanded\"\n >\n <component\n :is=\"expanded ? ChevronUp : ChevronDown\"\n :size=\"11\"\n :stroke-width=\"2\"\n />\n {{ expanded ? t.savedBlocks.collapse : t.savedBlocks.expand }}\n </button>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * One reorderable row in the save dialog's preview list: a grip handle beside a\n * scaled-down render of the real block.\n *\n * The preview is the same `BlockPreviewCanvas` the browser modal uses, so\n * a row shows what the block actually looks like rather than a description of\n * it. That canvas is fixed at the 600px email width every block component\n * renders against, so the row shrinks it with `transform: scale()` instead of\n * re-flowing it at dialog width — a re-flow would show a layout the recipient\n * will never see.\n *\n * `transform` doesn't participate in layout, so the frame would keep reserving\n * the full unscaled height; a `ResizeObserver` mirrors the scaled height back\n * onto it. The handle is a sibling of the scaled subtree, so it stays full size\n * and keeps its real hit area.\n *\n * Row chrome uses the raw theme tokens, not the `--tpl-chrome-*` aliases: the\n * chrome is an ancestor and a sibling of the rendered email content, never a\n * descendant, so the `.tpl-block-content` token override can't reach it.\n */\nimport BlockPreviewCanvas from \"./BlockPreviewCanvas.vue\";\nimport { useI18n } from \"../composables\";\nimport { CUSTOM_BLOCK_DEFINITIONS_KEY, EDITOR_KEY } from \"../keys\";\nimport { getEmailFrameWidth } from \"../utils/emailFrameWidth\";\nimport { getBlockLabel } from \"../utils/blockTypeLabels\";\nimport { ChevronDown, ChevronUp, GripVertical } from \"@lucide/vue\";\nimport type { Block } from \"@templatical/types\";\nimport { computed, inject, onBeforeUnmount, onMounted, ref } from \"vue\";\n\nconst props = defineProps<{\n block: Block;\n /** 1-based position in the list — spoken by the handle's label. */\n position: number;\n total: number;\n}>();\n\nconst emit = defineEmits<{\n (e: \"move\", delta: -1 | 1): void;\n}>();\n\nconst { t, format } = useI18n();\n// Optional: absent when the consumer registers no custom blocks.\nconst customBlockDefinitions = inject(CUSTOM_BLOCK_DEFINITIONS_KEY, []);\n\nconst editor = inject(EDITOR_KEY, null);\n\n/**\n * The width the nested `BlockPreviewCanvas` actually renders at.\n *\n * MUST come from the same helper the canvas uses, because the scale factor below\n * divides by it: a mismatch scales every row by the wrong amount, so a template\n * with a custom body width would overflow its frame or under-fill it. Rows are\n * always the desktop width — the save dialog has no viewport control.\n */\nconst previewWidth = computed(() =>\n getEmailFrameWidth(editor?.content.value.settings),\n);\n/**\n * Collapsed height for a tall block. A hero card or a multi-row section would\n * otherwise be taller than the whole list, leaving one row on screen — and\n * seeing the rows together is what makes them orderable. Anything taller\n * collapses to this, fades out at the cut, and can be expanded in place.\n */\nconst COLLAPSED_HEIGHT = 240;\n\nconst frameEl = ref<HTMLElement | null>(null);\nconst contentEl = ref<HTMLElement | null>(null);\nconst scale = ref(1);\n/** Scaled height of the whole block; `null` until first measured. */\nconst fullHeight = ref<number | null>(null);\nconst expanded = ref(false);\n\nlet observer: ResizeObserver | null = null;\n\n/** Only a block taller than the collapsed cap needs fading or expanding. */\nconst isClipped = computed(\n () => fullHeight.value !== null && fullHeight.value > COLLAPSED_HEIGHT,\n);\n\nconst frameHeight = computed(() => {\n if (fullHeight.value === null) return null;\n if (expanded.value || !isClipped.value) return fullHeight.value;\n return COLLAPSED_HEIGHT;\n});\n\nfunction measure(): void {\n const frame = frameEl.value;\n const content = contentEl.value;\n if (!frame || !content) return;\n const width = frame.clientWidth;\n if (width <= 0) return;\n // Never scale up: a dialog wider than the email would blow the preview past\n // its real proportions.\n const next = Math.min(1, width / previewWidth.value);\n scale.value = next;\n fullHeight.value = content.offsetHeight * next;\n}\n\nonMounted(() => {\n measure();\n // Absent in non-DOM test environments. Without it the frame simply falls back\n // to the untransformed height and clips — degraded, never broken.\n if (typeof ResizeObserver === \"undefined\") return;\n observer = new ResizeObserver(() => measure());\n if (frameEl.value) observer.observe(frameEl.value);\n if (contentEl.value) observer.observe(contentEl.value);\n});\n\nonBeforeUnmount(() => {\n observer?.disconnect();\n observer = null;\n});\n\nconst blockLabel = computed(() =>\n getBlockLabel(props.block, t, customBlockDefinitions),\n);\n\nconst handleLabel = computed(() =>\n format(t.savedBlocks.reorderHandle, {\n block: blockLabel.value,\n position: props.position,\n total: props.total,\n }),\n);\n\nconst toggleLabel = computed(() =>\n format(\n expanded.value\n ? t.savedBlocks.collapsePreview\n : t.savedBlocks.expandPreview,\n { block: blockLabel.value },\n ),\n);\n</script>\n\n<template>\n <div\n data-testid=\"saved-blocks-reorder-row\"\n :data-block-id=\"block.id\"\n class=\"tpl:flex tpl:items-start tpl:gap-1.5 tpl:rounded-[var(--tpl-radius-md)] tpl:border tpl:p-1.5 tpl:border-[var(--tpl-border)]\"\n style=\"background-color: var(--tpl-bg)\"\n >\n <!-- Sortable's `handle` selector points here, so a pointer-drag can only\n start from the grip — never from the preview itself. -->\n <button\n type=\"button\"\n data-testid=\"saved-blocks-reorder-handle\"\n class=\"tpl-saved-block-reorder-handle tpl:mt-0.5 tpl:shrink-0 tpl:cursor-grab tpl:rounded tpl:border-none tpl:bg-transparent tpl:p-0.5 tpl:text-[var(--tpl-text-dim)]\"\n :aria-label=\"handleLabel\"\n :title=\"t.savedBlocks.reorderHint\"\n @keydown.up.prevent=\"emit('move', -1)\"\n @keydown.down.prevent=\"emit('move', 1)\"\n >\n <GripVertical :size=\"14\" :stroke-width=\"1.5\" />\n </button>\n\n <div class=\"tpl:relative tpl:min-w-0 tpl:flex-1\">\n <div\n ref=\"frameEl\"\n class=\"tpl:overflow-hidden\"\n :style=\"{\n height: frameHeight !== null ? `${frameHeight}px` : undefined,\n }\"\n >\n <div\n ref=\"contentEl\"\n :style=\"{\n width: `${previewWidth}px`,\n transform: `scale(${scale})`,\n transformOrigin: 'top left',\n }\"\n >\n <BlockPreviewCanvas :blocks=\"[block]\" />\n </div>\n </div>\n\n <!-- Fade over the cut, so a clipped block reads as truncated rather than\n as a block that simply ends there. Pointer-transparent: it sits over\n the preview, and swallowing clicks would break the row. -->\n <div\n v-if=\"isClipped && !expanded\"\n data-testid=\"saved-blocks-preview-fade\"\n aria-hidden=\"true\"\n class=\"tpl:pointer-events-none tpl:absolute tpl:inset-x-0 tpl:bottom-0 tpl:h-10\"\n style=\"\n background: linear-gradient(\n to bottom,\n transparent,\n var(--tpl-bg) 90%\n );\n \"\n />\n\n <button\n v-if=\"isClipped\"\n type=\"button\"\n data-testid=\"saved-blocks-preview-toggle\"\n class=\"tpl:absolute tpl:bottom-1 tpl:right-1 tpl:flex tpl:cursor-pointer tpl:items-center tpl:gap-0.5 tpl:rounded tpl:border tpl:px-1.5 tpl:py-0.5 tpl:text-[10px] tpl:font-medium tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg-elevated)] tpl:text-[var(--tpl-text-muted)]\"\n :aria-label=\"toggleLabel\"\n :aria-expanded=\"expanded\"\n @click=\"expanded = !expanded\"\n >\n <component\n :is=\"expanded ? ChevronUp : ChevronDown\"\n :size=\"11\"\n :stroke-width=\"2\"\n />\n {{ expanded ? t.savedBlocks.collapse : t.savedBlocks.expand }}\n </button>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport SavedBlockPreviewRow from \"./SavedBlockPreviewRow.vue\";\nimport TplModal from \"./TplModal.vue\";\nimport { useI18n } from \"../composables\";\nimport {\n CUSTOM_BLOCK_DEFINITIONS_KEY,\n EDITOR_KEY,\n SAVED_BLOCKS_KEY,\n requireInject,\n} from \"../keys\";\nimport { getBlockLabel } from \"../utils/blockTypeLabels\";\nimport { LoaderCircle } from \"@lucide/vue\";\nimport { VueDraggable } from \"vue-draggable-plus\";\nimport type { Block } from \"@templatical/types\";\nimport { computed, inject, ref, watch } from \"vue\";\n\nconst props = defineProps<{\n visible: boolean;\n /**\n * Blocks chosen during the canvas pick session, in the order they were\n * picked — `pickedIds` is built from a `Set`, which iterates in insertion\n * order. That order seeds the reorderable list below. Every id must be a\n * top-level block, which is what the session guarantees.\n */\n pickedIds: readonly string[];\n}>();\n\nconst emit = defineEmits<{\n (e: \"close\"): void;\n (e: \"saved\"): void;\n}>();\n\nconst { t, format } = useI18n();\nconst editor = requireInject(EDITOR_KEY, \"SaveBlockDialog\");\nconst savedBlocks = requireInject(SAVED_BLOCKS_KEY, \"SaveBlockDialog\");\n// Optional: a consumer with no custom blocks never provides definitions, and\n// the label helper degrades to the type slug rather than throwing.\nconst customBlockDefinitions = inject(CUSTOM_BLOCK_DEFINITIONS_KEY, []);\n\nconst name = ref(\"\");\nconst category = ref(\"\");\nconst isSaving = ref(false);\nconst error = ref<string | null>(null);\nconst announcement = ref(\"\");\n\n/**\n * The order the blocks will be saved in: seeded from the pick order, then owned\n * by this dialog once the user drags a row.\n *\n * Ids rather than blocks, deliberately. Resolving late means a block edited\n * while the dialog is open (a collaborator in Cloud) is persisted as it stands\n * at save time, not as it looked when the dialog opened, and an id that stops\n * resolving drops out on its own instead of leaving a stale snapshot behind.\n */\nconst orderedIds = ref<string[]>([]);\n\n/**\n * The picked blocks in the list's current order. Ids that no longer resolve (a\n * block deleted mid-session) simply drop out.\n */\nconst pickedBlocks = computed(() => {\n const byId = new Map(editor.content.value.blocks.map((b) => [b.id, b]));\n return orderedIds.value\n .map((id) => byId.get(id))\n .filter((b): b is Block => b !== undefined);\n});\n\n/**\n * Sortable's model. The setter reads ids straight back off the emitted entries\n * rather than storing them, so a Sortable `el` expando can never reach the\n * payload `create()` sends to the provider — and because the getter is already\n * filtered, writing through it prunes unresolvable ids too.\n */\nconst orderedBlocks = computed<Block[]>({\n get: () => pickedBlocks.value,\n set: (blocks) => {\n orderedIds.value = blocks.map((b) => b.id);\n },\n});\n\n/** Keyboard reorder from a focused grip: swap with the neighbouring row. */\nfunction moveBlock(blockId: string, delta: -1 | 1): void {\n const order = pickedBlocks.value.map((b) => b.id);\n const from = order.indexOf(blockId);\n const to = from + delta;\n if (from === -1 || to < 0 || to >= order.length) return;\n [order[from], order[to]] = [order[to], order[from]];\n orderedIds.value = order;\n\n const moved = pickedBlocks.value[to];\n announcement.value = moved\n ? format(t.savedBlocks.reorderAnnouncement, {\n block: label(moved),\n position: to + 1,\n total: order.length,\n })\n : \"\";\n}\n\n/** Custom blocks read as the consumer's name for them, not the type `custom`. */\nfunction label(block: Block): string {\n return getBlockLabel(block, t, customBlockDefinitions);\n}\n\n/** e.g. \"Title, Paragraph, Button\" — what the user is about to save. */\nconst summary = computed(() => pickedBlocks.value.map(label).join(\", \"));\n\n// `immediate` matters: the dialog is mounted lazily behind a `v-if` on the\n// same state that drives `visible`, so it can mount with `visible` already\n// true and would otherwise never reset the name field or seed the order.\nwatch(\n () => props.visible,\n (visible) => {\n if (visible) {\n name.value = \"\";\n category.value = \"\";\n error.value = null;\n announcement.value = \"\";\n orderedIds.value = [...props.pickedIds];\n }\n },\n { immediate: true },\n);\n\nconst canSave = computed(\n () =>\n name.value.trim().length > 0 &&\n pickedBlocks.value.length > 0 &&\n !isSaving.value,\n);\n\nasync function handleSave(): Promise<void> {\n if (!canSave.value) return;\n\n const blocks = pickedBlocks.value;\n // Defence in depth: never persist an empty saved block (it would list as\n // \"0 block(s)\" and insert nothing). Guards a pick set whose ids no longer\n // resolve to anything on the canvas.\n if (blocks.length === 0) return;\n\n isSaving.value = true;\n error.value = null;\n\n try {\n // Empty stays undefined rather than \"\": an entry is either categorised or\n // it isn't, and a blank string would show up as a nameless filter option.\n await savedBlocks.create(\n name.value.trim(),\n blocks,\n category.value.trim() || undefined,\n );\n emit(\"saved\");\n emit(\"close\");\n } catch (err) {\n error.value = (err as Error).message;\n } finally {\n isSaving.value = false;\n }\n}\n\nfunction handleClose(): void {\n if (!isSaving.value) {\n emit(\"close\");\n }\n}\n\nfunction handleKeydown(event: KeyboardEvent): void {\n if (event.key === \"Enter\" && !event.shiftKey) {\n event.preventDefault();\n handleSave();\n }\n if (event.key === \"Escape\") {\n handleClose();\n }\n}\n</script>\n\n<template>\n <TplModal :visible=\"visible\" @close=\"handleClose\" @keydown=\"handleKeydown\">\n <div\n role=\"dialog\"\n aria-modal=\"true\"\n :aria-busy=\"isSaving\"\n aria-labelledby=\"tpl-save-block-title\"\n class=\"tpl-scale-in tpl:mx-4 tpl:flex 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 max-height: 90vh;\n \"\n >\n <h3\n id=\"tpl-save-block-title\"\n class=\"tpl:mb-4 tpl:shrink-0 tpl:text-sm tpl:font-semibold tpl:text-[var(--tpl-text)]\"\n >\n {{ t.savedBlocks.saveAsBlock }}\n </h3>\n\n <!-- Saved block name -->\n <div class=\"tpl:mb-3 tpl:shrink-0\">\n <label\n class=\"tpl:mb-1.5 tpl:block tpl:text-sm tpl:font-medium tpl:text-[var(--tpl-text-muted)]\"\n >\n {{ t.savedBlocks.name }}\n </label>\n <input\n v-model=\"name\"\n type=\"text\"\n data-testid=\"saved-blocks-name-input\"\n :placeholder=\"t.savedBlocks.namePlaceholder\"\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 :disabled=\"isSaving\"\n />\n </div>\n\n <!-- Category: free text, with the already-used categories offered as\n suggestions. A datalist rather than a <select> because the set is\n derived from existing entries — there is no registry to pick from,\n and the first entry in a category has to be able to name it. -->\n <div class=\"tpl:mb-3 tpl:shrink-0\">\n <label\n for=\"tpl-save-block-category\"\n class=\"tpl:mb-1.5 tpl:block tpl:text-sm tpl:font-medium tpl:text-[var(--tpl-text-muted)]\"\n >\n {{ t.savedBlocks.category }}\n </label>\n <input\n id=\"tpl-save-block-category\"\n v-model=\"category\"\n type=\"text\"\n data-testid=\"saved-blocks-category-input\"\n list=\"tpl-saved-block-categories\"\n :placeholder=\"t.savedBlocks.categoryPlaceholder\"\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 :disabled=\"isSaving\"\n />\n <datalist id=\"tpl-saved-block-categories\">\n <option\n v-for=\"option in savedBlocks.categories.value\"\n :key=\"option\"\n :value=\"option\"\n />\n </datalist>\n </div>\n\n <!-- Summary of what the pick session chose. Which blocks were picked is\n settled on the canvas — the dialog re-presents them only so the user\n can see and order what they're about to save. -->\n <p\n data-testid=\"saved-blocks-save-summary\"\n class=\"tpl:mb-1.5 tpl:shrink-0 tpl:text-xs tpl:text-[var(--tpl-text-muted)]\"\n >\n {{ format(t.savedBlocks.savingCount, { count: pickedBlocks.length }) }}\n <span class=\"tpl:text-[var(--tpl-text-dim)]\">{{ summary }}</span>\n </p>\n\n <!-- Reorderable preview. Seeded in pick order and saved in whatever order\n the list ends up in. Its own Sortable instance with no `group`, so it\n can never exchange items with the canvas or a section column. -->\n <p\n v-if=\"pickedBlocks.length > 1\"\n class=\"tpl:mb-1.5 tpl:shrink-0 tpl:text-[11px] tpl:text-[var(--tpl-text-dim)]\"\n >\n {{ t.savedBlocks.reorderHint }}\n </p>\n <!-- The only growing region: takes whatever the dialog's 90vh cap leaves\n after the fixed chrome, rather than a hand-tuned vh guess that a row\n expanding to full height would blow past. `min-h-0` is what lets a\n flex child actually shrink below its content and scroll. -->\n <VueDraggable\n v-model=\"orderedBlocks\"\n data-testid=\"saved-blocks-reorder-list\"\n :animation=\"150\"\n ghost-class=\"tpl-ghost\"\n drag-class=\"tpl-dragging\"\n handle=\".tpl-saved-block-reorder-handle\"\n :force-fallback=\"true\"\n class=\"tpl:mb-4 tpl:flex tpl:min-h-0 tpl:flex-1 tpl:flex-col tpl:gap-1.5 tpl:overflow-y-auto\"\n >\n <SavedBlockPreviewRow\n v-for=\"(block, index) in orderedBlocks\"\n :key=\"block.id\"\n :block=\"block\"\n :position=\"index + 1\"\n :total=\"orderedBlocks.length\"\n @move=\"(delta) => moveBlock(block.id, delta)\"\n />\n </VueDraggable>\n\n <!-- Keyboard reorder announcement region (visually hidden, live) -->\n <div\n class=\"tpl-sr-only\"\n role=\"status\"\n aria-live=\"polite\"\n aria-atomic=\"true\"\n >\n {{ announcement }}\n </div>\n\n <!-- Error message -->\n <p\n v-if=\"error\"\n role=\"alert\"\n class=\"tpl:mb-3 tpl:shrink-0 tpl:text-xs tpl:text-[var(--tpl-danger)]\"\n >\n {{ error }}\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 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=\"isSaving\"\n :class=\"{\n 'tpl:cursor-not-allowed tpl:opacity-50': isSaving,\n }\"\n @click=\"handleClose\"\n >\n {{ t.savedBlocks.cancel }}\n </button>\n <button\n type=\"button\"\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=\"!canSave\"\n @click=\"handleSave\"\n >\n <span v-if=\"isSaving\" 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.savedBlocks.saving }}\n </span>\n <span v-else>\n {{ t.savedBlocks.save }}\n </span>\n </button>\n </div>\n </div>\n </TplModal>\n</template>\n","<script setup lang=\"ts\">\nimport SavedBlockPreviewRow from \"./SavedBlockPreviewRow.vue\";\nimport TplModal from \"./TplModal.vue\";\nimport { useI18n } from \"../composables\";\nimport {\n CUSTOM_BLOCK_DEFINITIONS_KEY,\n EDITOR_KEY,\n SAVED_BLOCKS_KEY,\n requireInject,\n} from \"../keys\";\nimport { getBlockLabel } from \"../utils/blockTypeLabels\";\nimport { LoaderCircle } from \"@lucide/vue\";\nimport { VueDraggable } from \"vue-draggable-plus\";\nimport type { Block } from \"@templatical/types\";\nimport { computed, inject, ref, watch } from \"vue\";\n\nconst props = defineProps<{\n visible: boolean;\n /**\n * Blocks chosen during the canvas pick session, in the order they were\n * picked — `pickedIds` is built from a `Set`, which iterates in insertion\n * order. That order seeds the reorderable list below. Every id must be a\n * top-level block, which is what the session guarantees.\n */\n pickedIds: readonly string[];\n}>();\n\nconst emit = defineEmits<{\n (e: \"close\"): void;\n (e: \"saved\"): void;\n}>();\n\nconst { t, format } = useI18n();\nconst editor = requireInject(EDITOR_KEY, \"SaveBlockDialog\");\nconst savedBlocks = requireInject(SAVED_BLOCKS_KEY, \"SaveBlockDialog\");\n// Optional: a consumer with no custom blocks never provides definitions, and\n// the label helper degrades to the type slug rather than throwing.\nconst customBlockDefinitions = inject(CUSTOM_BLOCK_DEFINITIONS_KEY, []);\n\nconst name = ref(\"\");\nconst category = ref(\"\");\nconst isSaving = ref(false);\nconst error = ref<string | null>(null);\nconst announcement = ref(\"\");\n\n/**\n * The order the blocks will be saved in: seeded from the pick order, then owned\n * by this dialog once the user drags a row.\n *\n * Ids rather than blocks, deliberately. Resolving late means a block edited\n * while the dialog is open (a collaborator in Cloud) is persisted as it stands\n * at save time, not as it looked when the dialog opened, and an id that stops\n * resolving drops out on its own instead of leaving a stale snapshot behind.\n */\nconst orderedIds = ref<string[]>([]);\n\n/**\n * The picked blocks in the list's current order. Ids that no longer resolve (a\n * block deleted mid-session) simply drop out.\n */\nconst pickedBlocks = computed(() => {\n const byId = new Map(editor.content.value.blocks.map((b) => [b.id, b]));\n return orderedIds.value\n .map((id) => byId.get(id))\n .filter((b): b is Block => b !== undefined);\n});\n\n/**\n * Sortable's model. The setter reads ids straight back off the emitted entries\n * rather than storing them, so a Sortable `el` expando can never reach the\n * payload `create()` sends to the provider — and because the getter is already\n * filtered, writing through it prunes unresolvable ids too.\n */\nconst orderedBlocks = computed<Block[]>({\n get: () => pickedBlocks.value,\n set: (blocks) => {\n orderedIds.value = blocks.map((b) => b.id);\n },\n});\n\n/** Keyboard reorder from a focused grip: swap with the neighbouring row. */\nfunction moveBlock(blockId: string, delta: -1 | 1): void {\n const order = pickedBlocks.value.map((b) => b.id);\n const from = order.indexOf(blockId);\n const to = from + delta;\n if (from === -1 || to < 0 || to >= order.length) return;\n [order[from], order[to]] = [order[to], order[from]];\n orderedIds.value = order;\n\n const moved = pickedBlocks.value[to];\n announcement.value = moved\n ? format(t.savedBlocks.reorderAnnouncement, {\n block: label(moved),\n position: to + 1,\n total: order.length,\n })\n : \"\";\n}\n\n/** Custom blocks read as the consumer's name for them, not the type `custom`. */\nfunction label(block: Block): string {\n return getBlockLabel(block, t, customBlockDefinitions);\n}\n\n/** e.g. \"Title, Paragraph, Button\" — what the user is about to save. */\nconst summary = computed(() => pickedBlocks.value.map(label).join(\", \"));\n\n// `immediate` matters: the dialog is mounted lazily behind a `v-if` on the\n// same state that drives `visible`, so it can mount with `visible` already\n// true and would otherwise never reset the name field or seed the order.\nwatch(\n () => props.visible,\n (visible) => {\n if (visible) {\n name.value = \"\";\n category.value = \"\";\n error.value = null;\n announcement.value = \"\";\n orderedIds.value = [...props.pickedIds];\n }\n },\n { immediate: true },\n);\n\nconst canSave = computed(\n () =>\n name.value.trim().length > 0 &&\n pickedBlocks.value.length > 0 &&\n !isSaving.value,\n);\n\nasync function handleSave(): Promise<void> {\n if (!canSave.value) return;\n\n const blocks = pickedBlocks.value;\n // Defence in depth: never persist an empty saved block (it would list as\n // \"0 block(s)\" and insert nothing). Guards a pick set whose ids no longer\n // resolve to anything on the canvas.\n if (blocks.length === 0) return;\n\n isSaving.value = true;\n error.value = null;\n\n try {\n // Empty stays undefined rather than \"\": an entry is either categorised or\n // it isn't, and a blank string would show up as a nameless filter option.\n await savedBlocks.create(\n name.value.trim(),\n blocks,\n category.value.trim() || undefined,\n );\n emit(\"saved\");\n emit(\"close\");\n } catch (err) {\n error.value = (err as Error).message;\n } finally {\n isSaving.value = false;\n }\n}\n\nfunction handleClose(): void {\n if (!isSaving.value) {\n emit(\"close\");\n }\n}\n\nfunction handleKeydown(event: KeyboardEvent): void {\n if (event.key === \"Enter\" && !event.shiftKey) {\n event.preventDefault();\n handleSave();\n }\n if (event.key === \"Escape\") {\n handleClose();\n }\n}\n</script>\n\n<template>\n <TplModal :visible=\"visible\" @close=\"handleClose\" @keydown=\"handleKeydown\">\n <div\n role=\"dialog\"\n aria-modal=\"true\"\n :aria-busy=\"isSaving\"\n aria-labelledby=\"tpl-save-block-title\"\n class=\"tpl-scale-in tpl:mx-4 tpl:flex 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 max-height: 90vh;\n \"\n >\n <h3\n id=\"tpl-save-block-title\"\n class=\"tpl:mb-4 tpl:shrink-0 tpl:text-sm tpl:font-semibold tpl:text-[var(--tpl-text)]\"\n >\n {{ t.savedBlocks.saveAsBlock }}\n </h3>\n\n <!-- Saved block name -->\n <div class=\"tpl:mb-3 tpl:shrink-0\">\n <label\n class=\"tpl:mb-1.5 tpl:block tpl:text-sm tpl:font-medium tpl:text-[var(--tpl-text-muted)]\"\n >\n {{ t.savedBlocks.name }}\n </label>\n <input\n v-model=\"name\"\n type=\"text\"\n data-testid=\"saved-blocks-name-input\"\n :placeholder=\"t.savedBlocks.namePlaceholder\"\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 :disabled=\"isSaving\"\n />\n </div>\n\n <!-- Category: free text, with the already-used categories offered as\n suggestions. A datalist rather than a <select> because the set is\n derived from existing entries — there is no registry to pick from,\n and the first entry in a category has to be able to name it. -->\n <div class=\"tpl:mb-3 tpl:shrink-0\">\n <label\n for=\"tpl-save-block-category\"\n class=\"tpl:mb-1.5 tpl:block tpl:text-sm tpl:font-medium tpl:text-[var(--tpl-text-muted)]\"\n >\n {{ t.savedBlocks.category }}\n </label>\n <input\n id=\"tpl-save-block-category\"\n v-model=\"category\"\n type=\"text\"\n data-testid=\"saved-blocks-category-input\"\n list=\"tpl-saved-block-categories\"\n :placeholder=\"t.savedBlocks.categoryPlaceholder\"\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 :disabled=\"isSaving\"\n />\n <datalist id=\"tpl-saved-block-categories\">\n <option\n v-for=\"option in savedBlocks.categories.value\"\n :key=\"option\"\n :value=\"option\"\n />\n </datalist>\n </div>\n\n <!-- Summary of what the pick session chose. Which blocks were picked is\n settled on the canvas — the dialog re-presents them only so the user\n can see and order what they're about to save. -->\n <p\n data-testid=\"saved-blocks-save-summary\"\n class=\"tpl:mb-1.5 tpl:shrink-0 tpl:text-xs tpl:text-[var(--tpl-text-muted)]\"\n >\n {{ format(t.savedBlocks.savingCount, { count: pickedBlocks.length }) }}\n <span class=\"tpl:text-[var(--tpl-text-dim)]\">{{ summary }}</span>\n </p>\n\n <!-- Reorderable preview. Seeded in pick order and saved in whatever order\n the list ends up in. Its own Sortable instance with no `group`, so it\n can never exchange items with the canvas or a section column. -->\n <p\n v-if=\"pickedBlocks.length > 1\"\n class=\"tpl:mb-1.5 tpl:shrink-0 tpl:text-[11px] tpl:text-[var(--tpl-text-dim)]\"\n >\n {{ t.savedBlocks.reorderHint }}\n </p>\n <!-- The only growing region: takes whatever the dialog's 90vh cap leaves\n after the fixed chrome, rather than a hand-tuned vh guess that a row\n expanding to full height would blow past. `min-h-0` is what lets a\n flex child actually shrink below its content and scroll. -->\n <VueDraggable\n v-model=\"orderedBlocks\"\n data-testid=\"saved-blocks-reorder-list\"\n :animation=\"150\"\n ghost-class=\"tpl-ghost\"\n drag-class=\"tpl-dragging\"\n handle=\".tpl-saved-block-reorder-handle\"\n :force-fallback=\"true\"\n class=\"tpl:mb-4 tpl:flex tpl:min-h-0 tpl:flex-1 tpl:flex-col tpl:gap-1.5 tpl:overflow-y-auto\"\n >\n <SavedBlockPreviewRow\n v-for=\"(block, index) in orderedBlocks\"\n :key=\"block.id\"\n :block=\"block\"\n :position=\"index + 1\"\n :total=\"orderedBlocks.length\"\n @move=\"(delta) => moveBlock(block.id, delta)\"\n />\n </VueDraggable>\n\n <!-- Keyboard reorder announcement region (visually hidden, live) -->\n <div\n class=\"tpl-sr-only\"\n role=\"status\"\n aria-live=\"polite\"\n aria-atomic=\"true\"\n >\n {{ announcement }}\n </div>\n\n <!-- Error message -->\n <p\n v-if=\"error\"\n role=\"alert\"\n class=\"tpl:mb-3 tpl:shrink-0 tpl:text-xs tpl:text-[var(--tpl-danger)]\"\n >\n {{ error }}\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 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=\"isSaving\"\n :class=\"{\n 'tpl:cursor-not-allowed tpl:opacity-50': isSaving,\n }\"\n @click=\"handleClose\"\n >\n {{ t.savedBlocks.cancel }}\n </button>\n <button\n type=\"button\"\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=\"!canSave\"\n @click=\"handleSave\"\n >\n <span v-if=\"isSaving\" 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.savedBlocks.saving }}\n </span>\n <span v-else>\n {{ t.savedBlocks.save }}\n </span>\n </button>\n </div>\n </div>\n </TplModal>\n</template>\n"],"mappings":"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"}