@templatical/editor 0.39.2 → 0.39.3

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 (137) hide show
  1. package/dist/{AiChatSidebar-pOxCZWz9.js → AiChatSidebar-5qqrS6uy.js} +69 -63
  2. package/dist/{AiFeatureMenu-Bljk6Qjv.js → AiFeatureMenu-BBO_Ojlp.js} +1 -1
  3. package/dist/{BlockPreviewCanvas-DXGjUECG.js → BlockPreviewCanvas-BMC16_QU.js} +1 -1
  4. package/dist/{CloudHeaderExtras-DaocwNmy.js → CloudHeaderExtras-Cg6uF-yQ.js} +1 -1
  5. package/dist/{CloudPanels-CyZAnSJY.js → CloudPanels-D1Gg4qrG.js} +80 -66
  6. package/dist/{CollaboratorBar-C7Zca3g1.js → CollaboratorBar-Cmn0LccS.js} +3 -3
  7. package/dist/{CommentsPanel-CNOb7YCm.js → CommentsPanel-twNhYUqZ.js} +1 -1
  8. package/dist/{CommentsSidebar-VmwjU9va.js → CommentsSidebar-DDO0Qx8U.js} +122 -112
  9. package/dist/{CountdownToolbar-C2kfS6Oy.js → CountdownToolbar-nvHxu8Gu.js} +1 -1
  10. package/dist/DesignReferenceSidebar-C8ohNhOd.js +280 -0
  11. package/dist/{IssuesPanel-DL2Xr14_.js → IssuesPanel-Ck0EGoV5.js} +4 -4
  12. package/dist/{LoadingTrack-C2jKCU2d.js → LoadingTrack-C1Epulqq.js} +1 -1
  13. package/dist/{ParagraphEditor-DkkPSexG.js → ParagraphEditor-D6FRxr8Y.js} +2 -2
  14. package/dist/{RestoreVersionDialog-BgueZnZr.js → RestoreVersionDialog--b0qfNaK.js} +1 -1
  15. package/dist/{SaveBlockDialog-BuTuVlLr.js → SaveBlockDialog-C0BdEGeL.js} +6 -6
  16. package/dist/{SavedBlocksBrowserModal-Srhix8BB.js → SavedBlocksBrowserModal-BzDgXtr4.js} +8 -8
  17. package/dist/{SavedBlocksPanels-B2iScCS-.js → SavedBlocksPanels-DN-bDZzy.js} +1 -1
  18. package/dist/SavedBlocksPickBar-pEi9Et8Q.js +53 -0
  19. package/dist/{TemplateScoringPanel-C2Rdlyn6.js → TemplateScoringPanel-B1wcfE0w.js} +62 -56
  20. package/dist/{TestEmailModal-DJ5q_u4N.js → TestEmailModal-maM8SLBV.js} +6 -6
  21. package/dist/{TestEmailPanel-CP24UGst.js → TestEmailPanel-8iAZYpCK.js} +1 -1
  22. package/dist/{TitleEditor-QBBOXtxx.js → TitleEditor-L26QXwna.js} +1 -1
  23. package/dist/{Toolbar-COD2TW20.js → Toolbar-B2kDLFhn.js} +314 -314
  24. package/dist/{TplModal-C8xGjC1l.js → TplModal-DVMS_nAn.js} +1 -1
  25. package/dist/{VersionHistoryPanels-BrK6na4A.js → VersionHistoryPanels-D8f8KvKX.js} +1 -1
  26. package/dist/bundle-stats.json +6 -6
  27. package/dist/{ca-YlPDhElv.js → ca-Bqeygp1_.js} +1 -0
  28. package/dist/cdn/chunks/{AiChatSidebar-CjSEoK4x.js → AiChatSidebar-ctfIU4i0.js} +67 -61
  29. package/dist/cdn/chunks/AiChatSidebar-ctfIU4i0.js.map +1 -0
  30. package/dist/cdn/chunks/{AiFeatureMenu-l0K5TOSn.js → AiFeatureMenu-ByygdH_9.js} +2 -2
  31. package/dist/cdn/chunks/{AiFeatureMenu-l0K5TOSn.js.map → AiFeatureMenu-ByygdH_9.js.map} +1 -1
  32. package/dist/cdn/chunks/{BlockPreviewCanvas-lQrAdgG3.js → BlockPreviewCanvas-DeS7Sa--.js} +2 -2
  33. package/dist/cdn/chunks/{BlockPreviewCanvas-lQrAdgG3.js.map → BlockPreviewCanvas-DeS7Sa--.js.map} +1 -1
  34. package/dist/cdn/chunks/{CloudHeaderExtras-CXNe7Q-4.js → CloudHeaderExtras-BxsMpvVr.js} +2 -2
  35. package/dist/cdn/chunks/{CloudHeaderExtras-CXNe7Q-4.js.map → CloudHeaderExtras-BxsMpvVr.js.map} +1 -1
  36. package/dist/cdn/chunks/{CloudPanels-bHhh9AUA.js → CloudPanels-DmxmqAS_.js} +78 -64
  37. package/dist/cdn/chunks/CloudPanels-DmxmqAS_.js.map +1 -0
  38. package/dist/cdn/chunks/{CollaboratorBar-DgqcaGGI.js → CollaboratorBar-B8ttqAqX.js} +4 -4
  39. package/dist/cdn/chunks/CollaboratorBar-B8ttqAqX.js.map +1 -0
  40. package/dist/cdn/chunks/{CommentsPanel-Cy4TlTHC.js → CommentsPanel-C8ibhaiK.js} +2 -2
  41. package/dist/cdn/chunks/{CommentsPanel-Cy4TlTHC.js.map → CommentsPanel-C8ibhaiK.js.map} +1 -1
  42. package/dist/cdn/chunks/{CommentsSidebar-C7oAP3Mx.js → CommentsSidebar-CybWfZ3V.js} +118 -108
  43. package/dist/cdn/chunks/CommentsSidebar-CybWfZ3V.js.map +1 -0
  44. package/dist/cdn/chunks/{CountdownToolbar-CN9h4nlC.js → CountdownToolbar-DRESXiVD.js} +2 -2
  45. package/dist/cdn/chunks/{CountdownToolbar-CN9h4nlC.js.map → CountdownToolbar-DRESXiVD.js.map} +1 -1
  46. package/dist/cdn/chunks/DesignReferenceSidebar-CtaoY-hl.js +230 -0
  47. package/dist/cdn/chunks/DesignReferenceSidebar-CtaoY-hl.js.map +1 -0
  48. package/dist/cdn/chunks/{IssuesPanel-DdFk-Blf.js → IssuesPanel-C6RlIxoi.js} +5 -5
  49. package/dist/cdn/chunks/IssuesPanel-C6RlIxoi.js.map +1 -0
  50. package/dist/cdn/chunks/{LoadingTrack-Ba5xh5HD.js → LoadingTrack-_7DH_5aw.js} +2 -2
  51. package/dist/cdn/chunks/{LoadingTrack-Ba5xh5HD.js.map → LoadingTrack-_7DH_5aw.js.map} +1 -1
  52. package/dist/cdn/chunks/{ParagraphEditor-DO4RzP8l.js → ParagraphEditor-R2ph_UOo.js} +3 -3
  53. package/dist/cdn/chunks/ParagraphEditor-R2ph_UOo.js.map +1 -0
  54. package/dist/cdn/chunks/{RestoreVersionDialog-CzZ_sFnz.js → RestoreVersionDialog-C-dwbb7T.js} +2 -2
  55. package/dist/cdn/chunks/{RestoreVersionDialog-CzZ_sFnz.js.map → RestoreVersionDialog-C-dwbb7T.js.map} +1 -1
  56. package/dist/cdn/chunks/{SaveBlockDialog-LgxYrXyx.js → SaveBlockDialog-C_BPjVv6.js} +7 -7
  57. package/dist/cdn/chunks/SaveBlockDialog-C_BPjVv6.js.map +1 -0
  58. package/dist/cdn/chunks/{SavedBlocksBrowserModal-CdeSsTpb.js → SavedBlocksBrowserModal-CrrGEfFb.js} +9 -9
  59. package/dist/cdn/chunks/SavedBlocksBrowserModal-CrrGEfFb.js.map +1 -0
  60. package/dist/cdn/chunks/{SavedBlocksPanels-B5hakNJj.js → SavedBlocksPanels-DfTaMPAr.js} +2 -2
  61. package/dist/cdn/chunks/{SavedBlocksPanels-B5hakNJj.js.map → SavedBlocksPanels-DfTaMPAr.js.map} +1 -1
  62. package/dist/cdn/chunks/SavedBlocksPickBar-DziwHYSu.js +55 -0
  63. package/dist/cdn/chunks/SavedBlocksPickBar-DziwHYSu.js.map +1 -0
  64. package/dist/cdn/chunks/{TemplateScoringPanel--JxR6CsQ.js → TemplateScoringPanel-CR6RG6dZ.js} +95 -89
  65. package/dist/cdn/chunks/TemplateScoringPanel-CR6RG6dZ.js.map +1 -0
  66. package/dist/cdn/chunks/{TestEmailModal-BAuDxJtD.js → TestEmailModal-DId9Og-N.js} +7 -7
  67. package/dist/cdn/chunks/TestEmailModal-DId9Og-N.js.map +1 -0
  68. package/dist/cdn/chunks/{TestEmailPanel-GUGZisLp.js → TestEmailPanel-B8LwXmeX.js} +2 -2
  69. package/dist/cdn/chunks/{TestEmailPanel-GUGZisLp.js.map → TestEmailPanel-B8LwXmeX.js.map} +1 -1
  70. package/dist/cdn/chunks/{TitleEditor-uKu33Nwr.js → TitleEditor-C7KMzX1e.js} +2 -2
  71. package/dist/cdn/chunks/{TitleEditor-uKu33Nwr.js.map → TitleEditor-C7KMzX1e.js.map} +1 -1
  72. package/dist/cdn/chunks/{Toolbar-dxDYwN5E.js → Toolbar-DqFhWxgW.js} +310 -310
  73. package/dist/cdn/chunks/Toolbar-DqFhWxgW.js.map +1 -0
  74. package/dist/cdn/chunks/{TplModal-DuwS2ifK.js → TplModal-BKqDFLq3.js} +2 -2
  75. package/dist/cdn/chunks/{TplModal-DuwS2ifK.js.map → TplModal-BKqDFLq3.js.map} +1 -1
  76. package/dist/cdn/chunks/{VersionHistoryPanels-GeR6MStB.js → VersionHistoryPanels-DcCFFDK6.js} +2 -2
  77. package/dist/cdn/chunks/{VersionHistoryPanels-GeR6MStB.js.map → VersionHistoryPanels-DcCFFDK6.js.map} +1 -1
  78. package/dist/cdn/chunks/{ca-YlPDhElv.js → ca-Bqeygp1_.js} +2 -1
  79. package/dist/cdn/chunks/{ca-YlPDhElv.js.map → ca-Bqeygp1_.js.map} +1 -1
  80. package/dist/cdn/chunks/{de-CONjnzrF.js → de-CY6SlqBx.js} +2 -1
  81. package/dist/cdn/chunks/{de-CONjnzrF.js.map → de-CY6SlqBx.js.map} +1 -1
  82. package/dist/{de-BytZwgn6.js → cdn/chunks/de-mMY5yQJu.js} +5 -0
  83. package/dist/cdn/chunks/de-mMY5yQJu.js.map +1 -0
  84. package/dist/cdn/chunks/{en-BAMooiOF.js → en-8V-2kDcp.js} +2 -1
  85. package/dist/cdn/chunks/{en-BAMooiOF.js.map → en-8V-2kDcp.js.map} +1 -1
  86. package/dist/{en-DyOSV0lY.js → cdn/chunks/en-ChVeGQBY.js} +5 -0
  87. package/dist/cdn/chunks/en-ChVeGQBY.js.map +1 -0
  88. package/dist/cdn/chunks/{es-I47lHs2m.js → es-Cmn1rT3l.js} +2 -1
  89. package/dist/cdn/chunks/{es-I47lHs2m.js.map → es-Cmn1rT3l.js.map} +1 -1
  90. package/dist/cdn/chunks/{fr-B8MIKjT4.js → fr-2yDa4zXM.js} +2 -1
  91. package/dist/cdn/chunks/{fr-B8MIKjT4.js.map → fr-2yDa4zXM.js.map} +1 -1
  92. package/dist/cdn/chunks/{nl-CBLxcb-0.js → nl-Bzk2zRI8.js} +2 -1
  93. package/dist/cdn/chunks/{nl-CBLxcb-0.js.map → nl-Bzk2zRI8.js.map} +1 -1
  94. package/dist/cdn/chunks/{pt-BR-C1WLSGDh.js → pt-BR-CoCwRUi0.js} +4 -1
  95. package/dist/cdn/chunks/pt-BR-CoCwRUi0.js.map +1 -0
  96. package/dist/cdn/chunks/{pt-BR-CMSRfX7t.js → pt-BR-DqtSCTUi.js} +2 -1
  97. package/dist/cdn/chunks/{pt-BR-CMSRfX7t.js.map → pt-BR-DqtSCTUi.js.map} +1 -1
  98. package/dist/cdn/chunks/{src-O5Sp_qJ7.js → src-kSeXM7B2.js} +2 -2
  99. package/dist/cdn/chunks/{src-O5Sp_qJ7.js.map → src-kSeXM7B2.js.map} +1 -1
  100. package/dist/cdn/chunks/{useEditorCore-BHmIp1hW.js → useEditorCore-CXYI14ux.js} +14 -14
  101. package/dist/cdn/chunks/{useEditorCore-BHmIp1hW.js.map → useEditorCore-CXYI14ux.js.map} +1 -1
  102. package/dist/cdn/editor.css +1 -1
  103. package/dist/cdn/editor.js +14 -14
  104. package/dist/cdn/editor.js.map +1 -1
  105. package/dist/{de-CONjnzrF.js → de-CY6SlqBx.js} +1 -0
  106. package/dist/{cdn/chunks/de-BytZwgn6.js → de-mMY5yQJu.js} +3 -2
  107. package/dist/{en-BAMooiOF.js → en-8V-2kDcp.js} +1 -0
  108. package/dist/{cdn/chunks/en-DyOSV0lY.js → en-ChVeGQBY.js} +3 -2
  109. package/dist/{es-I47lHs2m.js → es-Cmn1rT3l.js} +1 -0
  110. package/dist/{fr-B8MIKjT4.js → fr-2yDa4zXM.js} +1 -0
  111. package/dist/{nl-CBLxcb-0.js → nl-Bzk2zRI8.js} +1 -0
  112. package/dist/{pt-BR-C1WLSGDh.js → pt-BR-CoCwRUi0.js} +3 -0
  113. package/dist/{pt-BR-CMSRfX7t.js → pt-BR-DqtSCTUi.js} +1 -0
  114. package/dist/style.css +1 -1
  115. package/dist/templatical-editor.js +13 -13
  116. package/dist/{useEditorCore-C5APBjjV.js → useEditorCore-BUVbQgwb.js} +13 -13
  117. package/package.json +7 -7
  118. package/dist/DesignReferenceSidebar-QC4gEUWp.js +0 -273
  119. package/dist/SavedBlocksPickBar-cI7bDQQi.js +0 -52
  120. package/dist/cdn/chunks/AiChatSidebar-CjSEoK4x.js.map +0 -1
  121. package/dist/cdn/chunks/CloudPanels-bHhh9AUA.js.map +0 -1
  122. package/dist/cdn/chunks/CollaboratorBar-DgqcaGGI.js.map +0 -1
  123. package/dist/cdn/chunks/CommentsSidebar-C7oAP3Mx.js.map +0 -1
  124. package/dist/cdn/chunks/DesignReferenceSidebar-Bjo0M-AY.js +0 -223
  125. package/dist/cdn/chunks/DesignReferenceSidebar-Bjo0M-AY.js.map +0 -1
  126. package/dist/cdn/chunks/IssuesPanel-DdFk-Blf.js.map +0 -1
  127. package/dist/cdn/chunks/ParagraphEditor-DO4RzP8l.js.map +0 -1
  128. package/dist/cdn/chunks/SaveBlockDialog-LgxYrXyx.js.map +0 -1
  129. package/dist/cdn/chunks/SavedBlocksBrowserModal-CdeSsTpb.js.map +0 -1
  130. package/dist/cdn/chunks/SavedBlocksPickBar-Cl6PLwwV.js +0 -54
  131. package/dist/cdn/chunks/SavedBlocksPickBar-Cl6PLwwV.js.map +0 -1
  132. package/dist/cdn/chunks/TemplateScoringPanel--JxR6CsQ.js.map +0 -1
  133. package/dist/cdn/chunks/TestEmailModal-BAuDxJtD.js.map +0 -1
  134. package/dist/cdn/chunks/Toolbar-dxDYwN5E.js.map +0 -1
  135. package/dist/cdn/chunks/de-BytZwgn6.js.map +0 -1
  136. package/dist/cdn/chunks/en-DyOSV0lY.js.map +0 -1
  137. package/dist/cdn/chunks/pt-BR-C1WLSGDh.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"BlockPreviewCanvas-lQrAdgG3.js","names":[],"sources":["../../../src/components/blocks/PreviewSectionBlock.vue","../../../src/components/blocks/PreviewSectionBlock.vue","../../../src/components/BlockPreviewCanvas.vue","../../../src/components/BlockPreviewCanvas.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport ButtonBlock from \"./ButtonBlock.vue\";\nimport CustomBlock from \"./CustomBlock.vue\";\nimport DividerBlock from \"./DividerBlock.vue\";\nimport HtmlBlock from \"./HtmlBlock.vue\";\nimport ImageBlock from \"./ImageBlock.vue\";\nimport MenuBlock from \"./MenuBlock.vue\";\nimport SocialIconsBlock from \"./SocialIconsBlock.vue\";\nimport SpacerBlock from \"./SpacerBlock.vue\";\nimport TableBlock from \"./TableBlock.vue\";\nimport TitleBlock from \"./TitleBlock.vue\";\nimport ParagraphBlock from \"./ParagraphBlock.vue\";\nimport VideoBlock from \"./VideoBlock.vue\";\nimport {\n resolveBlockComponent,\n getBlockWrapperStyle,\n} from \"../../utils/blockComponentResolver\";\nimport type {\n Block,\n SectionBlock as SectionBlockType,\n} from \"@templatical/types\";\nimport { computed, inject, type Component } from \"vue\";\nimport { BLOCK_REGISTRY_KEY } from \"../../keys\";\n\nconst previewBlockComponentMap: Record<string, Component> = {\n title: TitleBlock,\n paragraph: ParagraphBlock,\n image: ImageBlock,\n video: VideoBlock,\n button: ButtonBlock,\n divider: DividerBlock,\n social: SocialIconsBlock,\n menu: MenuBlock,\n table: TableBlock,\n spacer: SpacerBlock,\n html: HtmlBlock,\n custom: CustomBlock,\n};\n\nconst props = defineProps<{\n block: SectionBlockType;\n viewport: \"desktop\";\n}>();\n\ndefineOptions({ name: \"PreviewSectionBlock\" });\n\nconst blockRegistry = inject(BLOCK_REGISTRY_KEY, null);\n\nconst columnWidths = computed(() => {\n switch (props.block.columns) {\n case \"2\":\n return [\"50%\", \"50%\"];\n case \"3\":\n return [\"33.33%\", \"33.33%\", \"33.33%\"];\n case \"1-2\":\n return [\"33.33%\", \"66.67%\"];\n case \"2-1\":\n return [\"66.67%\", \"33.33%\"];\n default:\n return [\"100%\"];\n }\n});\n\nconst columns = computed(() => {\n const count = columnWidths.value.length;\n const children = [...props.block.children];\n while (children.length < count) {\n children.push([]);\n }\n return children.slice(0, count);\n});\n\nfunction getColumnBlocks(colIndex: number): Block[] {\n return columns.value[colIndex] || [];\n}\n\nfunction getBlockComponent(block: Block): Component | null {\n return resolveBlockComponent(block, blockRegistry, previewBlockComponentMap);\n}\n</script>\n\n<template>\n <div class=\"tpl:w-full\">\n <div class=\"tpl:flex tpl:gap-0\">\n <div\n v-for=\"(_, colIndex) in columns\"\n :key=\"colIndex\"\n :style=\"{ width: columnWidths[colIndex] }\"\n >\n <div\n v-for=\"childBlock in getColumnBlocks(colIndex)\"\n :key=\"childBlock.id\"\n :style=\"getBlockWrapperStyle(childBlock)\"\n >\n <!-- Recursive self-reference for nested sections -->\n <PreviewSectionBlock\n v-if=\"childBlock.type === 'section'\"\n :block=\"childBlock\"\n viewport=\"desktop\"\n />\n <component\n :is=\"getBlockComponent(childBlock)\"\n v-else\n :block=\"childBlock\"\n viewport=\"desktop\"\n />\n </div>\n </div>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport ButtonBlock from \"./ButtonBlock.vue\";\nimport CustomBlock from \"./CustomBlock.vue\";\nimport DividerBlock from \"./DividerBlock.vue\";\nimport HtmlBlock from \"./HtmlBlock.vue\";\nimport ImageBlock from \"./ImageBlock.vue\";\nimport MenuBlock from \"./MenuBlock.vue\";\nimport SocialIconsBlock from \"./SocialIconsBlock.vue\";\nimport SpacerBlock from \"./SpacerBlock.vue\";\nimport TableBlock from \"./TableBlock.vue\";\nimport TitleBlock from \"./TitleBlock.vue\";\nimport ParagraphBlock from \"./ParagraphBlock.vue\";\nimport VideoBlock from \"./VideoBlock.vue\";\nimport {\n resolveBlockComponent,\n getBlockWrapperStyle,\n} from \"../../utils/blockComponentResolver\";\nimport type {\n Block,\n SectionBlock as SectionBlockType,\n} from \"@templatical/types\";\nimport { computed, inject, type Component } from \"vue\";\nimport { BLOCK_REGISTRY_KEY } from \"../../keys\";\n\nconst previewBlockComponentMap: Record<string, Component> = {\n title: TitleBlock,\n paragraph: ParagraphBlock,\n image: ImageBlock,\n video: VideoBlock,\n button: ButtonBlock,\n divider: DividerBlock,\n social: SocialIconsBlock,\n menu: MenuBlock,\n table: TableBlock,\n spacer: SpacerBlock,\n html: HtmlBlock,\n custom: CustomBlock,\n};\n\nconst props = defineProps<{\n block: SectionBlockType;\n viewport: \"desktop\";\n}>();\n\ndefineOptions({ name: \"PreviewSectionBlock\" });\n\nconst blockRegistry = inject(BLOCK_REGISTRY_KEY, null);\n\nconst columnWidths = computed(() => {\n switch (props.block.columns) {\n case \"2\":\n return [\"50%\", \"50%\"];\n case \"3\":\n return [\"33.33%\", \"33.33%\", \"33.33%\"];\n case \"1-2\":\n return [\"33.33%\", \"66.67%\"];\n case \"2-1\":\n return [\"66.67%\", \"33.33%\"];\n default:\n return [\"100%\"];\n }\n});\n\nconst columns = computed(() => {\n const count = columnWidths.value.length;\n const children = [...props.block.children];\n while (children.length < count) {\n children.push([]);\n }\n return children.slice(0, count);\n});\n\nfunction getColumnBlocks(colIndex: number): Block[] {\n return columns.value[colIndex] || [];\n}\n\nfunction getBlockComponent(block: Block): Component | null {\n return resolveBlockComponent(block, blockRegistry, previewBlockComponentMap);\n}\n</script>\n\n<template>\n <div class=\"tpl:w-full\">\n <div class=\"tpl:flex tpl:gap-0\">\n <div\n v-for=\"(_, colIndex) in columns\"\n :key=\"colIndex\"\n :style=\"{ width: columnWidths[colIndex] }\"\n >\n <div\n v-for=\"childBlock in getColumnBlocks(colIndex)\"\n :key=\"childBlock.id\"\n :style=\"getBlockWrapperStyle(childBlock)\"\n >\n <!-- Recursive self-reference for nested sections -->\n <PreviewSectionBlock\n v-if=\"childBlock.type === 'section'\"\n :block=\"childBlock\"\n viewport=\"desktop\"\n />\n <component\n :is=\"getBlockComponent(childBlock)\"\n v-else\n :block=\"childBlock\"\n viewport=\"desktop\"\n />\n </div>\n </div>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport ButtonBlock from \"./blocks/ButtonBlock.vue\";\nimport CustomBlock from \"./blocks/CustomBlock.vue\";\nimport DividerBlock from \"./blocks/DividerBlock.vue\";\nimport HtmlBlock from \"./blocks/HtmlBlock.vue\";\nimport ImageBlock from \"./blocks/ImageBlock.vue\";\nimport MenuBlock from \"./blocks/MenuBlock.vue\";\nimport PreviewSectionBlock from \"./blocks/PreviewSectionBlock.vue\";\nimport SocialIconsBlock from \"./blocks/SocialIconsBlock.vue\";\nimport SpacerBlock from \"./blocks/SpacerBlock.vue\";\nimport TableBlock from \"./blocks/TableBlock.vue\";\nimport TitleBlock from \"./blocks/TitleBlock.vue\";\nimport ParagraphBlock from \"./blocks/ParagraphBlock.vue\";\nimport VideoBlock from \"./blocks/VideoBlock.vue\";\nimport {\n BLOCK_REGISTRY_KEY,\n CONDITION_PREVIEW_KEY,\n EDITOR_KEY,\n MERGE_TAG_SAMPLE_MODE_KEY,\n USE_MERGE_TAG_SAMPLES_KEY,\n} from \"../keys\";\nimport {\n resolveBlockComponent,\n getBlockWrapperStyle,\n getDocumentStyle,\n} from \"../utils/blockComponentResolver\";\nimport {\n EMAIL_FRAME_WIDTH_TRANSITION,\n EMAIL_GUTTER,\n getEmailFrameWidth,\n} from \"../utils/emailFrameWidth\";\nimport {\n resolveContentDirection,\n type Block,\n type ViewportSize,\n} from \"@templatical/types\";\nimport { computed, inject, provide, type Component } from \"vue\";\n\nconst props = withDefaults(\n defineProps<{\n blocks: Block[];\n /**\n * Which viewport to render. Defaults to `desktop`, so the saved-blocks\n * surfaces — which have no viewport control — behave exactly as before.\n * The test-email dialog passes the user's choice so responsive blocks show\n * the variant a recipient on that device would receive.\n */\n viewport?: ViewportSize;\n /**\n * Whether the hand-toggled display-condition filter still applies here.\n * `false` where a resolver owns the preview: it has already evaluated every\n * condition against real data, so layering a manual hide on top would veto\n * the answer that was asked for.\n *\n * A prop rather than an injected `PREVIEW_RESOLUTION_KEY` read, because the\n * test-email dialog builds its *own* resolution instance (keyed to the\n * selected recipient) — injecting would consult the shared canvas instance\n * and get the wrong answer.\n */\n applyConditionFilter?: boolean;\n }>(),\n { viewport: \"desktop\", applyConditionFilter: true },\n);\n\nconst blockRegistry = inject(BLOCK_REGISTRY_KEY);\nconst editor = inject(EDITOR_KEY, null);\nconst conditionPreview = inject(CONDITION_PREVIEW_KEY, null);\n\n/**\n * This canvas is only ever a preview — nothing here is editable — so it honours\n * the user's mode directly, with no `previewMode` gate of the kind `Canvas.vue`\n * needs. Re-provided rather than left to inherit, because a `BlockPreviewCanvas`\n * inside a dialog may sit under an editing `Canvas` that provides `false`.\n */\nconst mergeTagSampleMode = inject(MERGE_TAG_SAMPLE_MODE_KEY, null);\nprovide(\n USE_MERGE_TAG_SAMPLES_KEY,\n computed(() => mergeTagSampleMode?.value ?? false),\n);\n\n/**\n * Blocks a display condition currently excludes, so a preview never shows\n * content the recipient won't get. The canvas does the same via\n * `v-show=\"!conditionPreview?.isHidden(block.id)\"`.\n *\n * A no-op for the saved-blocks browser: entries there carry their *stored* ids,\n * which aren't in the current template, so nothing is tracked as hidden. It\n * bites where it should — the save dialog and the test-email preview, both of\n * which show blocks that are live on the canvas.\n *\n * Skipped entirely when `applyConditionFilter` is false — see the prop.\n */\nconst visibleBlocks = computed(() =>\n props.applyConditionFilter\n ? props.blocks.filter((block) => !conditionPreview?.isHidden(block.id))\n : props.blocks,\n);\n\n/**\n * Frame width, from the one helper the canvas and the scaled preview rows also\n * use — so all three agree by construction rather than by three constants that\n * happen to match. A template with a custom body width now previews at that\n * width instead of a flat 600.\n */\nconst frameWidth = computed(() =>\n getEmailFrameWidth(editor?.content.value.settings, props.viewport),\n);\n\n/**\n * The same document-level style the canvas applies, so a previewed block\n * renders identically to the canvas one: without it the font falls back to the\n * editor UI's and the link rules hit their unset defaults, dropping the\n * underline and the link colour.\n *\n * Read from the *current* template even in the saved-blocks browser, where the\n * previewed content came from elsewhere — a saved block stores only `Block[]`,\n * and the current settings are what it will actually look like once inserted.\n */\nconst documentStyle = computed(() =>\n editor ? getDocumentStyle(editor.content.value.settings) : {},\n);\n\n/**\n * The email's own language, for the browser's spellchecker and hyphenation —\n * the same binding the editing canvas carries, so a preview is checked by the\n * same rules as the block it mirrors.\n *\n * From `settings.locale`, never `config.locale`: the chrome's language says\n * nothing about the content's. Read from the *current* template for the same\n * reason `documentStyle` is — a saved block stores only `Block[]`. Undefined\n * rather than `\"\"`, which would declare \"unknown language\" and suppress\n * spellcheck instead of falling back.\n */\nconst contentLang = computed(\n () => editor?.content.value.settings?.locale?.trim() || undefined,\n);\nconst contentDir = computed(() =>\n resolveContentDirection(editor?.content.value.settings ?? { locale: \"en\" }),\n);\n\n/**\n * The email's body background — `mj-body background-color` when sent, and the\n * `.tpl-canvas-bg` layer on the editing canvas.\n *\n * Deliberately NOT part of `getDocumentStyle`: the canvas applies that helper\n * to `.tpl-canvas`, which must stay transparent so the invertible background\n * layer beneath it shows through. A background there would double-paint and\n * defeat the dark-mode preview.\n *\n * Truthiness, not `??`: the colour pickers clear to an empty string to mean\n * \"unset\", which as an inline style renders the frame transparent rather than\n * falling back to the neutral preview surface.\n */\nconst emailBackground = computed(\n () =>\n editor?.content.value.settings.backgroundColor || \"var(--tpl-canvas-bg)\",\n);\n\n/**\n * Width the stage asks for: the content column plus a full gutter on each side,\n * the same stage the canvas builds. It is an ask rather than a fixed size —\n * paired with `max-width: 100%` below, so a container with less room hands over\n * what it has and shows a narrower band instead of overflowing.\n *\n * It has to be a specified width, not `100%`: `TplModal`'s wrapper is\n * shrink-to-fit, so a percentage there resolves against a container whose size\n * the stage is itself supposed to influence, and the stage collapses to its\n * `min-width` — no gutters, on the one surface this exists for.\n */\nconst stageWidth = computed(() => frameWidth.value + EMAIL_GUTTER * 2);\n\nconst previewComponentMap: Record<string, Component> = {\n section: PreviewSectionBlock,\n title: TitleBlock,\n paragraph: ParagraphBlock,\n image: ImageBlock,\n video: VideoBlock,\n button: ButtonBlock,\n divider: DividerBlock,\n social: SocialIconsBlock,\n menu: MenuBlock,\n table: TableBlock,\n spacer: SpacerBlock,\n html: HtmlBlock,\n custom: CustomBlock,\n};\n\nfunction getBlockComponent(block: Block): Component | null {\n return resolveBlockComponent(block, blockRegistry, previewComponentMap);\n}\n</script>\n\n<template>\n <!-- STAGE — the email body, holding the background and a gutter of it on\n each side of the content column, the same shape `Canvas.vue` builds.\n `max-width: 100%` is what makes it fit anywhere: a roomy surface shows\n the full band, a dialog shows as much as it has, and a container sized to\n the content column shows none. `min-width` keeps it from squeezing the\n column, so a cramped surface clips exactly as it did when the frame\n carried the width itself. -->\n <div\n data-testid=\"block-preview-stage\"\n class=\"tpl:pointer-events-none tpl:mx-auto tpl:flex tpl:select-none tpl:justify-center tpl:rounded-lg\"\n :style=\"{\n width: `${stageWidth}px`,\n minWidth: `${frameWidth}px`,\n maxWidth: '100%',\n backgroundColor: emailBackground,\n boxShadow: 'var(--tpl-shadow-sm)',\n }\"\n >\n <!-- CONTENT COLUMN — the blocks at their true email width. Keeps the\n testid: this is the element whose width *is* the email width, which is\n what every viewport assertion measures. -->\n <div\n data-testid=\"block-preview-canvas\"\n :lang=\"contentLang\"\n :dir=\"contentDir\"\n :style=\"{\n width: `${frameWidth}px`,\n transition: EMAIL_FRAME_WIDTH_TRANSITION,\n ...documentStyle,\n }\"\n >\n <div\n v-for=\"block in visibleBlocks\"\n :key=\"block.id\"\n :style=\"getBlockWrapperStyle(block)\"\n >\n <component\n :is=\"getBlockComponent(block)\"\n :block=\"block\"\n :viewport=\"viewport\"\n />\n </div>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport ButtonBlock from \"./blocks/ButtonBlock.vue\";\nimport CustomBlock from \"./blocks/CustomBlock.vue\";\nimport DividerBlock from \"./blocks/DividerBlock.vue\";\nimport HtmlBlock from \"./blocks/HtmlBlock.vue\";\nimport ImageBlock from \"./blocks/ImageBlock.vue\";\nimport MenuBlock from \"./blocks/MenuBlock.vue\";\nimport PreviewSectionBlock from \"./blocks/PreviewSectionBlock.vue\";\nimport SocialIconsBlock from \"./blocks/SocialIconsBlock.vue\";\nimport SpacerBlock from \"./blocks/SpacerBlock.vue\";\nimport TableBlock from \"./blocks/TableBlock.vue\";\nimport TitleBlock from \"./blocks/TitleBlock.vue\";\nimport ParagraphBlock from \"./blocks/ParagraphBlock.vue\";\nimport VideoBlock from \"./blocks/VideoBlock.vue\";\nimport {\n BLOCK_REGISTRY_KEY,\n CONDITION_PREVIEW_KEY,\n EDITOR_KEY,\n MERGE_TAG_SAMPLE_MODE_KEY,\n USE_MERGE_TAG_SAMPLES_KEY,\n} from \"../keys\";\nimport {\n resolveBlockComponent,\n getBlockWrapperStyle,\n getDocumentStyle,\n} from \"../utils/blockComponentResolver\";\nimport {\n EMAIL_FRAME_WIDTH_TRANSITION,\n EMAIL_GUTTER,\n getEmailFrameWidth,\n} from \"../utils/emailFrameWidth\";\nimport {\n resolveContentDirection,\n type Block,\n type ViewportSize,\n} from \"@templatical/types\";\nimport { computed, inject, provide, type Component } from \"vue\";\n\nconst props = withDefaults(\n defineProps<{\n blocks: Block[];\n /**\n * Which viewport to render. Defaults to `desktop`, so the saved-blocks\n * surfaces — which have no viewport control — behave exactly as before.\n * The test-email dialog passes the user's choice so responsive blocks show\n * the variant a recipient on that device would receive.\n */\n viewport?: ViewportSize;\n /**\n * Whether the hand-toggled display-condition filter still applies here.\n * `false` where a resolver owns the preview: it has already evaluated every\n * condition against real data, so layering a manual hide on top would veto\n * the answer that was asked for.\n *\n * A prop rather than an injected `PREVIEW_RESOLUTION_KEY` read, because the\n * test-email dialog builds its *own* resolution instance (keyed to the\n * selected recipient) — injecting would consult the shared canvas instance\n * and get the wrong answer.\n */\n applyConditionFilter?: boolean;\n }>(),\n { viewport: \"desktop\", applyConditionFilter: true },\n);\n\nconst blockRegistry = inject(BLOCK_REGISTRY_KEY);\nconst editor = inject(EDITOR_KEY, null);\nconst conditionPreview = inject(CONDITION_PREVIEW_KEY, null);\n\n/**\n * This canvas is only ever a preview — nothing here is editable — so it honours\n * the user's mode directly, with no `previewMode` gate of the kind `Canvas.vue`\n * needs. Re-provided rather than left to inherit, because a `BlockPreviewCanvas`\n * inside a dialog may sit under an editing `Canvas` that provides `false`.\n */\nconst mergeTagSampleMode = inject(MERGE_TAG_SAMPLE_MODE_KEY, null);\nprovide(\n USE_MERGE_TAG_SAMPLES_KEY,\n computed(() => mergeTagSampleMode?.value ?? false),\n);\n\n/**\n * Blocks a display condition currently excludes, so a preview never shows\n * content the recipient won't get. The canvas does the same via\n * `v-show=\"!conditionPreview?.isHidden(block.id)\"`.\n *\n * A no-op for the saved-blocks browser: entries there carry their *stored* ids,\n * which aren't in the current template, so nothing is tracked as hidden. It\n * bites where it should — the save dialog and the test-email preview, both of\n * which show blocks that are live on the canvas.\n *\n * Skipped entirely when `applyConditionFilter` is false — see the prop.\n */\nconst visibleBlocks = computed(() =>\n props.applyConditionFilter\n ? props.blocks.filter((block) => !conditionPreview?.isHidden(block.id))\n : props.blocks,\n);\n\n/**\n * Frame width, from the one helper the canvas and the scaled preview rows also\n * use — so all three agree by construction rather than by three constants that\n * happen to match. A template with a custom body width now previews at that\n * width instead of a flat 600.\n */\nconst frameWidth = computed(() =>\n getEmailFrameWidth(editor?.content.value.settings, props.viewport),\n);\n\n/**\n * The same document-level style the canvas applies, so a previewed block\n * renders identically to the canvas one: without it the font falls back to the\n * editor UI's and the link rules hit their unset defaults, dropping the\n * underline and the link colour.\n *\n * Read from the *current* template even in the saved-blocks browser, where the\n * previewed content came from elsewhere — a saved block stores only `Block[]`,\n * and the current settings are what it will actually look like once inserted.\n */\nconst documentStyle = computed(() =>\n editor ? getDocumentStyle(editor.content.value.settings) : {},\n);\n\n/**\n * The email's own language, for the browser's spellchecker and hyphenation —\n * the same binding the editing canvas carries, so a preview is checked by the\n * same rules as the block it mirrors.\n *\n * From `settings.locale`, never `config.locale`: the chrome's language says\n * nothing about the content's. Read from the *current* template for the same\n * reason `documentStyle` is — a saved block stores only `Block[]`. Undefined\n * rather than `\"\"`, which would declare \"unknown language\" and suppress\n * spellcheck instead of falling back.\n */\nconst contentLang = computed(\n () => editor?.content.value.settings?.locale?.trim() || undefined,\n);\nconst contentDir = computed(() =>\n resolveContentDirection(editor?.content.value.settings ?? { locale: \"en\" }),\n);\n\n/**\n * The email's body background — `mj-body background-color` when sent, and the\n * `.tpl-canvas-bg` layer on the editing canvas.\n *\n * Deliberately NOT part of `getDocumentStyle`: the canvas applies that helper\n * to `.tpl-canvas`, which must stay transparent so the invertible background\n * layer beneath it shows through. A background there would double-paint and\n * defeat the dark-mode preview.\n *\n * Truthiness, not `??`: the colour pickers clear to an empty string to mean\n * \"unset\", which as an inline style renders the frame transparent rather than\n * falling back to the neutral preview surface.\n */\nconst emailBackground = computed(\n () =>\n editor?.content.value.settings.backgroundColor || \"var(--tpl-canvas-bg)\",\n);\n\n/**\n * Width the stage asks for: the content column plus a full gutter on each side,\n * the same stage the canvas builds. It is an ask rather than a fixed size —\n * paired with `max-width: 100%` below, so a container with less room hands over\n * what it has and shows a narrower band instead of overflowing.\n *\n * It has to be a specified width, not `100%`: `TplModal`'s wrapper is\n * shrink-to-fit, so a percentage there resolves against a container whose size\n * the stage is itself supposed to influence, and the stage collapses to its\n * `min-width` — no gutters, on the one surface this exists for.\n */\nconst stageWidth = computed(() => frameWidth.value + EMAIL_GUTTER * 2);\n\nconst previewComponentMap: Record<string, Component> = {\n section: PreviewSectionBlock,\n title: TitleBlock,\n paragraph: ParagraphBlock,\n image: ImageBlock,\n video: VideoBlock,\n button: ButtonBlock,\n divider: DividerBlock,\n social: SocialIconsBlock,\n menu: MenuBlock,\n table: TableBlock,\n spacer: SpacerBlock,\n html: HtmlBlock,\n custom: CustomBlock,\n};\n\nfunction getBlockComponent(block: Block): Component | null {\n return resolveBlockComponent(block, blockRegistry, previewComponentMap);\n}\n</script>\n\n<template>\n <!-- STAGE — the email body, holding the background and a gutter of it on\n each side of the content column, the same shape `Canvas.vue` builds.\n `max-width: 100%` is what makes it fit anywhere: a roomy surface shows\n the full band, a dialog shows as much as it has, and a container sized to\n the content column shows none. `min-width` keeps it from squeezing the\n column, so a cramped surface clips exactly as it did when the frame\n carried the width itself. -->\n <div\n data-testid=\"block-preview-stage\"\n class=\"tpl:pointer-events-none tpl:mx-auto tpl:flex tpl:select-none tpl:justify-center tpl:rounded-lg\"\n :style=\"{\n width: `${stageWidth}px`,\n minWidth: `${frameWidth}px`,\n maxWidth: '100%',\n backgroundColor: emailBackground,\n boxShadow: 'var(--tpl-shadow-sm)',\n }\"\n >\n <!-- CONTENT COLUMN — the blocks at their true email width. Keeps the\n testid: this is the element whose width *is* the email width, which is\n what every viewport assertion measures. -->\n <div\n data-testid=\"block-preview-canvas\"\n :lang=\"contentLang\"\n :dir=\"contentDir\"\n :style=\"{\n width: `${frameWidth}px`,\n transition: EMAIL_FRAME_WIDTH_TRANSITION,\n ...documentStyle,\n }\"\n >\n <div\n v-for=\"block in visibleBlocks\"\n :key=\"block.id\"\n :style=\"getBlockWrapperStyle(block)\"\n >\n <component\n :is=\"getBlockComponent(block)\"\n :block=\"block\"\n :viewport=\"viewport\"\n />\n </div>\n </div>\n </div>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;EAwBA,IAAM,IAAsD;GAC1D,OAAO;GACP,WAAW;GACX,OAAO;GACP,OAAO;GACP,QAAQ;GACR,SAAS;GACT,QAAQ;GACR,MAAM;GACN,OAAO;GACP,QAAQ;GACR,MAAM;GACN,QAAQ;EACV,GAEM,IAAQ,GAOR,IAAgB,EAAO,GAAoB,IAAI,GAE/C,IAAe,QAAe;GAClC,QAAQ,EAAM,MAAM,SAApB;IACE,KAAK,KACH,OAAO,CAAC,OAAO,KAAK;IACtB,KAAK,KACH,OAAO;KAAC;KAAU;KAAU;IAAQ;IACtC,KAAK,OACH,OAAO,CAAC,UAAU,QAAQ;IAC5B,KAAK,OACH,OAAO,CAAC,UAAU,QAAQ;IAC5B,SACE,OAAO,CAAC,MAAM;GAClB;EACF,CAAC,GAEK,IAAU,QAAe;GAC7B,IAAM,IAAQ,EAAa,MAAM,QAC3B,IAAW,CAAC,GAAG,EAAM,MAAM,QAAQ;GACzC,OAAO,EAAS,SAAS,IACvB,EAAS,KAAK,CAAC,CAAC;GAElB,OAAO,EAAS,MAAM,GAAG,CAAK;EAChC,CAAC;EAED,SAAS,EAAgB,GAA2B;GAClD,OAAO,EAAQ,MAAM,MAAa,CAAC;EACrC;EAEA,SAAS,EAAkB,GAAgC;GACzD,OAAO,EAAsB,GAAO,GAAe,CAAwB;EAC7E;;;GAIE,OAAA,EAAA,GAAA,EA2BM,OA3BN,GA2BM,CA1BJ,EAyBM,OAzBN,GAyBM,EAxBJ,EAAA,EAAA,GAAA,EAuBM,GAAA,MAAA,EAtBoB,EAAA,QAAhB,GAAG,OADb,EAAA,GAAA,EAuBM,OAAA;IArBH,KAAK;IACL,OAAK,EAAA,EAAA,OAAW,EAAA,MAAa,GAAQ,CAAA;GAEtC,GAAA,EAAA,EAAA,EAAA,GAAA,EAiBM,GAAA,MAAA,EAhBiB,EAAgB,CAAQ,IAAtC,OADT,EAAA,GAAA,EAiBM,OAAA;IAfH,KAAK,EAAW;IAChB,OAAK,EAAE,EAAA,CAAA,CAAoB,CAAC,CAAU,CAAA;GAI/B,GAAA,CAAA,EAAW,SAAI,aADvB,EAAA,GAAA,EAIE,GAAA;;IAFC,OAAO;IACR,UAAS;GAEX,GAAA,MAAA,GAAA,CAAA,OAAA,CAAA,MAAA,EAAA,GAAA,EAKE,EAJK,EAAkB,CAAU,CAAA,GAAA;;IAEhC,OAAO;IACR,UAAS;;;;;;;;;;;;;;;EElErB,IAAM,IAAQ,GA0BR,IAAgB,EAAO,CAAkB,GACzC,IAAS,EAAO,GAAY,IAAI,GAChC,IAAmB,EAAO,GAAuB,IAAI,GAQrD,IAAqB,EAAO,GAA2B,IAAI;EACjE,EACE,GACA,QAAe,GAAoB,SAAS,EAAK,CACnD;EAcA,IAAM,IAAgB,QACpB,EAAM,uBACF,EAAM,OAAO,QAAQ,MAAU,CAAC,GAAkB,SAAS,EAAM,EAAE,CAAC,IACpE,EAAM,MACZ,GAQM,IAAa,QACjB,EAAmB,GAAQ,QAAQ,MAAM,UAAU,EAAM,QAAQ,CACnE,GAYM,IAAgB,QACpB,IAAS,EAAiB,EAAO,QAAQ,MAAM,QAAQ,IAAI,CAAC,CAC9D,GAaM,IAAc,QACZ,GAAQ,QAAQ,MAAM,UAAU,QAAQ,KAAK,KAAK,KAAA,CAC1D,GACM,IAAa,QACjB,EAAwB,GAAQ,QAAQ,MAAM,YAAY,EAAE,QAAQ,KAAK,CAAC,CAC5E,GAeM,IAAkB,QAEpB,GAAQ,QAAQ,MAAM,SAAS,mBAAmB,sBACtD,GAaM,IAAa,QAAe,EAAW,QAAA,GAAwB,GAE/D,IAAiD;GACrD,SAAS;GACT,OAAO;GACP,WAAW;GACX,OAAO;GACP,OAAO;GACP,QAAQ;GACR,SAAS;GACT,QAAQ;GACR,MAAM;GACN,OAAO;GACP,QAAQ;GACR,MAAM;GACN,QAAQ;EACV;EAEA,SAAS,EAAkB,GAAgC;GACzD,OAAO,EAAsB,GAAO,GAAe,CAAmB;EACxE;oBAWE,EAAA,GAAA,EAoCM,OAAA;GAnCJ,eAAY;GACZ,OAAM;GACL,OAAK,EAAA;IAAoB,OAAA,GAAA,EAAA,MAAU;IAAyB,UAAA,GAAA,EAAA,MAAU;;IAAqD,iBAAA,EAAA;;;EAW5H,GAAA,CAAA,EAqBM,OAAA;GApBJ,eAAY;GACX,MAAM,EAAA;GACN,KAAK,EAAA;GACL,OAAK,EAAA;IAAsB,OAAA,GAAA,EAAA,MAAU;IAA0B,YAAA,EAAA,CAAA;IAAyC,GAAA,EAAA;;EAMzG,GAAA,EAAA,EAAA,EAAA,GAAA,EAUM,GAAA,MAAA,EATY,EAAA,QAAT,OADT,EAAA,GAAA,EAUM,OAAA;GARH,KAAK,EAAM;GACX,OAAK,EAAE,EAAA,CAAA,CAAoB,CAAC,CAAK,CAAA;EAElC,GAAA,EAAA,EAAA,GAAA,EAIE,EAHK,EAAkB,CAAK,CAAA,GAAA;GACpB;GACP,UAAU,EAAA"}
1
+ {"version":3,"file":"BlockPreviewCanvas-DeS7Sa--.js","names":[],"sources":["../../../src/components/blocks/PreviewSectionBlock.vue","../../../src/components/blocks/PreviewSectionBlock.vue","../../../src/components/BlockPreviewCanvas.vue","../../../src/components/BlockPreviewCanvas.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport ButtonBlock from \"./ButtonBlock.vue\";\nimport CustomBlock from \"./CustomBlock.vue\";\nimport DividerBlock from \"./DividerBlock.vue\";\nimport HtmlBlock from \"./HtmlBlock.vue\";\nimport ImageBlock from \"./ImageBlock.vue\";\nimport MenuBlock from \"./MenuBlock.vue\";\nimport SocialIconsBlock from \"./SocialIconsBlock.vue\";\nimport SpacerBlock from \"./SpacerBlock.vue\";\nimport TableBlock from \"./TableBlock.vue\";\nimport TitleBlock from \"./TitleBlock.vue\";\nimport ParagraphBlock from \"./ParagraphBlock.vue\";\nimport VideoBlock from \"./VideoBlock.vue\";\nimport {\n resolveBlockComponent,\n getBlockWrapperStyle,\n} from \"../../utils/blockComponentResolver\";\nimport type {\n Block,\n SectionBlock as SectionBlockType,\n} from \"@templatical/types\";\nimport { computed, inject, type Component } from \"vue\";\nimport { BLOCK_REGISTRY_KEY } from \"../../keys\";\n\nconst previewBlockComponentMap: Record<string, Component> = {\n title: TitleBlock,\n paragraph: ParagraphBlock,\n image: ImageBlock,\n video: VideoBlock,\n button: ButtonBlock,\n divider: DividerBlock,\n social: SocialIconsBlock,\n menu: MenuBlock,\n table: TableBlock,\n spacer: SpacerBlock,\n html: HtmlBlock,\n custom: CustomBlock,\n};\n\nconst props = defineProps<{\n block: SectionBlockType;\n viewport: \"desktop\";\n}>();\n\ndefineOptions({ name: \"PreviewSectionBlock\" });\n\nconst blockRegistry = inject(BLOCK_REGISTRY_KEY, null);\n\nconst columnWidths = computed(() => {\n switch (props.block.columns) {\n case \"2\":\n return [\"50%\", \"50%\"];\n case \"3\":\n return [\"33.33%\", \"33.33%\", \"33.33%\"];\n case \"1-2\":\n return [\"33.33%\", \"66.67%\"];\n case \"2-1\":\n return [\"66.67%\", \"33.33%\"];\n default:\n return [\"100%\"];\n }\n});\n\nconst columns = computed(() => {\n const count = columnWidths.value.length;\n const children = [...props.block.children];\n while (children.length < count) {\n children.push([]);\n }\n return children.slice(0, count);\n});\n\nfunction getColumnBlocks(colIndex: number): Block[] {\n return columns.value[colIndex] || [];\n}\n\nfunction getBlockComponent(block: Block): Component | null {\n return resolveBlockComponent(block, blockRegistry, previewBlockComponentMap);\n}\n</script>\n\n<template>\n <div class=\"tpl:w-full\">\n <div class=\"tpl:flex tpl:gap-0\">\n <div\n v-for=\"(_, colIndex) in columns\"\n :key=\"colIndex\"\n :style=\"{ width: columnWidths[colIndex] }\"\n >\n <div\n v-for=\"childBlock in getColumnBlocks(colIndex)\"\n :key=\"childBlock.id\"\n :style=\"getBlockWrapperStyle(childBlock)\"\n >\n <!-- Recursive self-reference for nested sections -->\n <PreviewSectionBlock\n v-if=\"childBlock.type === 'section'\"\n :block=\"childBlock\"\n viewport=\"desktop\"\n />\n <component\n :is=\"getBlockComponent(childBlock)\"\n v-else\n :block=\"childBlock\"\n viewport=\"desktop\"\n />\n </div>\n </div>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport ButtonBlock from \"./ButtonBlock.vue\";\nimport CustomBlock from \"./CustomBlock.vue\";\nimport DividerBlock from \"./DividerBlock.vue\";\nimport HtmlBlock from \"./HtmlBlock.vue\";\nimport ImageBlock from \"./ImageBlock.vue\";\nimport MenuBlock from \"./MenuBlock.vue\";\nimport SocialIconsBlock from \"./SocialIconsBlock.vue\";\nimport SpacerBlock from \"./SpacerBlock.vue\";\nimport TableBlock from \"./TableBlock.vue\";\nimport TitleBlock from \"./TitleBlock.vue\";\nimport ParagraphBlock from \"./ParagraphBlock.vue\";\nimport VideoBlock from \"./VideoBlock.vue\";\nimport {\n resolveBlockComponent,\n getBlockWrapperStyle,\n} from \"../../utils/blockComponentResolver\";\nimport type {\n Block,\n SectionBlock as SectionBlockType,\n} from \"@templatical/types\";\nimport { computed, inject, type Component } from \"vue\";\nimport { BLOCK_REGISTRY_KEY } from \"../../keys\";\n\nconst previewBlockComponentMap: Record<string, Component> = {\n title: TitleBlock,\n paragraph: ParagraphBlock,\n image: ImageBlock,\n video: VideoBlock,\n button: ButtonBlock,\n divider: DividerBlock,\n social: SocialIconsBlock,\n menu: MenuBlock,\n table: TableBlock,\n spacer: SpacerBlock,\n html: HtmlBlock,\n custom: CustomBlock,\n};\n\nconst props = defineProps<{\n block: SectionBlockType;\n viewport: \"desktop\";\n}>();\n\ndefineOptions({ name: \"PreviewSectionBlock\" });\n\nconst blockRegistry = inject(BLOCK_REGISTRY_KEY, null);\n\nconst columnWidths = computed(() => {\n switch (props.block.columns) {\n case \"2\":\n return [\"50%\", \"50%\"];\n case \"3\":\n return [\"33.33%\", \"33.33%\", \"33.33%\"];\n case \"1-2\":\n return [\"33.33%\", \"66.67%\"];\n case \"2-1\":\n return [\"66.67%\", \"33.33%\"];\n default:\n return [\"100%\"];\n }\n});\n\nconst columns = computed(() => {\n const count = columnWidths.value.length;\n const children = [...props.block.children];\n while (children.length < count) {\n children.push([]);\n }\n return children.slice(0, count);\n});\n\nfunction getColumnBlocks(colIndex: number): Block[] {\n return columns.value[colIndex] || [];\n}\n\nfunction getBlockComponent(block: Block): Component | null {\n return resolveBlockComponent(block, blockRegistry, previewBlockComponentMap);\n}\n</script>\n\n<template>\n <div class=\"tpl:w-full\">\n <div class=\"tpl:flex tpl:gap-0\">\n <div\n v-for=\"(_, colIndex) in columns\"\n :key=\"colIndex\"\n :style=\"{ width: columnWidths[colIndex] }\"\n >\n <div\n v-for=\"childBlock in getColumnBlocks(colIndex)\"\n :key=\"childBlock.id\"\n :style=\"getBlockWrapperStyle(childBlock)\"\n >\n <!-- Recursive self-reference for nested sections -->\n <PreviewSectionBlock\n v-if=\"childBlock.type === 'section'\"\n :block=\"childBlock\"\n viewport=\"desktop\"\n />\n <component\n :is=\"getBlockComponent(childBlock)\"\n v-else\n :block=\"childBlock\"\n viewport=\"desktop\"\n />\n </div>\n </div>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport ButtonBlock from \"./blocks/ButtonBlock.vue\";\nimport CustomBlock from \"./blocks/CustomBlock.vue\";\nimport DividerBlock from \"./blocks/DividerBlock.vue\";\nimport HtmlBlock from \"./blocks/HtmlBlock.vue\";\nimport ImageBlock from \"./blocks/ImageBlock.vue\";\nimport MenuBlock from \"./blocks/MenuBlock.vue\";\nimport PreviewSectionBlock from \"./blocks/PreviewSectionBlock.vue\";\nimport SocialIconsBlock from \"./blocks/SocialIconsBlock.vue\";\nimport SpacerBlock from \"./blocks/SpacerBlock.vue\";\nimport TableBlock from \"./blocks/TableBlock.vue\";\nimport TitleBlock from \"./blocks/TitleBlock.vue\";\nimport ParagraphBlock from \"./blocks/ParagraphBlock.vue\";\nimport VideoBlock from \"./blocks/VideoBlock.vue\";\nimport {\n BLOCK_REGISTRY_KEY,\n CONDITION_PREVIEW_KEY,\n EDITOR_KEY,\n MERGE_TAG_SAMPLE_MODE_KEY,\n USE_MERGE_TAG_SAMPLES_KEY,\n} from \"../keys\";\nimport {\n resolveBlockComponent,\n getBlockWrapperStyle,\n getDocumentStyle,\n} from \"../utils/blockComponentResolver\";\nimport {\n EMAIL_FRAME_WIDTH_TRANSITION,\n EMAIL_GUTTER,\n getEmailFrameWidth,\n} from \"../utils/emailFrameWidth\";\nimport {\n resolveContentDirection,\n type Block,\n type ViewportSize,\n} from \"@templatical/types\";\nimport { computed, inject, provide, type Component } from \"vue\";\n\nconst props = withDefaults(\n defineProps<{\n blocks: Block[];\n /**\n * Which viewport to render. Defaults to `desktop`, so the saved-blocks\n * surfaces — which have no viewport control — behave exactly as before.\n * The test-email dialog passes the user's choice so responsive blocks show\n * the variant a recipient on that device would receive.\n */\n viewport?: ViewportSize;\n /**\n * Whether the hand-toggled display-condition filter still applies here.\n * `false` where a resolver owns the preview: it has already evaluated every\n * condition against real data, so layering a manual hide on top would veto\n * the answer that was asked for.\n *\n * A prop rather than an injected `PREVIEW_RESOLUTION_KEY` read, because the\n * test-email dialog builds its *own* resolution instance (keyed to the\n * selected recipient) — injecting would consult the shared canvas instance\n * and get the wrong answer.\n */\n applyConditionFilter?: boolean;\n }>(),\n { viewport: \"desktop\", applyConditionFilter: true },\n);\n\nconst blockRegistry = inject(BLOCK_REGISTRY_KEY);\nconst editor = inject(EDITOR_KEY, null);\nconst conditionPreview = inject(CONDITION_PREVIEW_KEY, null);\n\n/**\n * This canvas is only ever a preview — nothing here is editable — so it honours\n * the user's mode directly, with no `previewMode` gate of the kind `Canvas.vue`\n * needs. Re-provided rather than left to inherit, because a `BlockPreviewCanvas`\n * inside a dialog may sit under an editing `Canvas` that provides `false`.\n */\nconst mergeTagSampleMode = inject(MERGE_TAG_SAMPLE_MODE_KEY, null);\nprovide(\n USE_MERGE_TAG_SAMPLES_KEY,\n computed(() => mergeTagSampleMode?.value ?? false),\n);\n\n/**\n * Blocks a display condition currently excludes, so a preview never shows\n * content the recipient won't get. The canvas does the same via\n * `v-show=\"!conditionPreview?.isHidden(block.id)\"`.\n *\n * A no-op for the saved-blocks browser: entries there carry their *stored* ids,\n * which aren't in the current template, so nothing is tracked as hidden. It\n * bites where it should — the save dialog and the test-email preview, both of\n * which show blocks that are live on the canvas.\n *\n * Skipped entirely when `applyConditionFilter` is false — see the prop.\n */\nconst visibleBlocks = computed(() =>\n props.applyConditionFilter\n ? props.blocks.filter((block) => !conditionPreview?.isHidden(block.id))\n : props.blocks,\n);\n\n/**\n * Frame width, from the one helper the canvas and the scaled preview rows also\n * use — so all three agree by construction rather than by three constants that\n * happen to match. A template with a custom body width now previews at that\n * width instead of a flat 600.\n */\nconst frameWidth = computed(() =>\n getEmailFrameWidth(editor?.content.value.settings, props.viewport),\n);\n\n/**\n * The same document-level style the canvas applies, so a previewed block\n * renders identically to the canvas one: without it the font falls back to the\n * editor UI's and the link rules hit their unset defaults, dropping the\n * underline and the link colour.\n *\n * Read from the *current* template even in the saved-blocks browser, where the\n * previewed content came from elsewhere — a saved block stores only `Block[]`,\n * and the current settings are what it will actually look like once inserted.\n */\nconst documentStyle = computed(() =>\n editor ? getDocumentStyle(editor.content.value.settings) : {},\n);\n\n/**\n * The email's own language, for the browser's spellchecker and hyphenation —\n * the same binding the editing canvas carries, so a preview is checked by the\n * same rules as the block it mirrors.\n *\n * From `settings.locale`, never `config.locale`: the chrome's language says\n * nothing about the content's. Read from the *current* template for the same\n * reason `documentStyle` is — a saved block stores only `Block[]`. Undefined\n * rather than `\"\"`, which would declare \"unknown language\" and suppress\n * spellcheck instead of falling back.\n */\nconst contentLang = computed(\n () => editor?.content.value.settings?.locale?.trim() || undefined,\n);\nconst contentDir = computed(() =>\n resolveContentDirection(editor?.content.value.settings ?? { locale: \"en\" }),\n);\n\n/**\n * The email's body background — `mj-body background-color` when sent, and the\n * `.tpl-canvas-bg` layer on the editing canvas.\n *\n * Deliberately NOT part of `getDocumentStyle`: the canvas applies that helper\n * to `.tpl-canvas`, which must stay transparent so the invertible background\n * layer beneath it shows through. A background there would double-paint and\n * defeat the dark-mode preview.\n *\n * Truthiness, not `??`: the colour pickers clear to an empty string to mean\n * \"unset\", which as an inline style renders the frame transparent rather than\n * falling back to the neutral preview surface.\n */\nconst emailBackground = computed(\n () =>\n editor?.content.value.settings.backgroundColor || \"var(--tpl-canvas-bg)\",\n);\n\n/**\n * Width the stage asks for: the content column plus a full gutter on each side,\n * the same stage the canvas builds. It is an ask rather than a fixed size —\n * paired with `max-width: 100%` below, so a container with less room hands over\n * what it has and shows a narrower band instead of overflowing.\n *\n * It has to be a specified width, not `100%`: `TplModal`'s wrapper is\n * shrink-to-fit, so a percentage there resolves against a container whose size\n * the stage is itself supposed to influence, and the stage collapses to its\n * `min-width` — no gutters, on the one surface this exists for.\n */\nconst stageWidth = computed(() => frameWidth.value + EMAIL_GUTTER * 2);\n\nconst previewComponentMap: Record<string, Component> = {\n section: PreviewSectionBlock,\n title: TitleBlock,\n paragraph: ParagraphBlock,\n image: ImageBlock,\n video: VideoBlock,\n button: ButtonBlock,\n divider: DividerBlock,\n social: SocialIconsBlock,\n menu: MenuBlock,\n table: TableBlock,\n spacer: SpacerBlock,\n html: HtmlBlock,\n custom: CustomBlock,\n};\n\nfunction getBlockComponent(block: Block): Component | null {\n return resolveBlockComponent(block, blockRegistry, previewComponentMap);\n}\n</script>\n\n<template>\n <!-- STAGE — the email body, holding the background and a gutter of it on\n each side of the content column, the same shape `Canvas.vue` builds.\n `max-width: 100%` is what makes it fit anywhere: a roomy surface shows\n the full band, a dialog shows as much as it has, and a container sized to\n the content column shows none. `min-width` keeps it from squeezing the\n column, so a cramped surface clips exactly as it did when the frame\n carried the width itself. -->\n <div\n data-testid=\"block-preview-stage\"\n class=\"tpl:pointer-events-none tpl:mx-auto tpl:flex tpl:select-none tpl:justify-center tpl:rounded-lg\"\n :style=\"{\n width: `${stageWidth}px`,\n minWidth: `${frameWidth}px`,\n maxWidth: '100%',\n backgroundColor: emailBackground,\n boxShadow: 'var(--tpl-shadow-sm)',\n }\"\n >\n <!-- CONTENT COLUMN — the blocks at their true email width. Keeps the\n testid: this is the element whose width *is* the email width, which is\n what every viewport assertion measures. -->\n <div\n data-testid=\"block-preview-canvas\"\n :lang=\"contentLang\"\n :dir=\"contentDir\"\n :style=\"{\n width: `${frameWidth}px`,\n transition: EMAIL_FRAME_WIDTH_TRANSITION,\n ...documentStyle,\n }\"\n >\n <div\n v-for=\"block in visibleBlocks\"\n :key=\"block.id\"\n :style=\"getBlockWrapperStyle(block)\"\n >\n <component\n :is=\"getBlockComponent(block)\"\n :block=\"block\"\n :viewport=\"viewport\"\n />\n </div>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport ButtonBlock from \"./blocks/ButtonBlock.vue\";\nimport CustomBlock from \"./blocks/CustomBlock.vue\";\nimport DividerBlock from \"./blocks/DividerBlock.vue\";\nimport HtmlBlock from \"./blocks/HtmlBlock.vue\";\nimport ImageBlock from \"./blocks/ImageBlock.vue\";\nimport MenuBlock from \"./blocks/MenuBlock.vue\";\nimport PreviewSectionBlock from \"./blocks/PreviewSectionBlock.vue\";\nimport SocialIconsBlock from \"./blocks/SocialIconsBlock.vue\";\nimport SpacerBlock from \"./blocks/SpacerBlock.vue\";\nimport TableBlock from \"./blocks/TableBlock.vue\";\nimport TitleBlock from \"./blocks/TitleBlock.vue\";\nimport ParagraphBlock from \"./blocks/ParagraphBlock.vue\";\nimport VideoBlock from \"./blocks/VideoBlock.vue\";\nimport {\n BLOCK_REGISTRY_KEY,\n CONDITION_PREVIEW_KEY,\n EDITOR_KEY,\n MERGE_TAG_SAMPLE_MODE_KEY,\n USE_MERGE_TAG_SAMPLES_KEY,\n} from \"../keys\";\nimport {\n resolveBlockComponent,\n getBlockWrapperStyle,\n getDocumentStyle,\n} from \"../utils/blockComponentResolver\";\nimport {\n EMAIL_FRAME_WIDTH_TRANSITION,\n EMAIL_GUTTER,\n getEmailFrameWidth,\n} from \"../utils/emailFrameWidth\";\nimport {\n resolveContentDirection,\n type Block,\n type ViewportSize,\n} from \"@templatical/types\";\nimport { computed, inject, provide, type Component } from \"vue\";\n\nconst props = withDefaults(\n defineProps<{\n blocks: Block[];\n /**\n * Which viewport to render. Defaults to `desktop`, so the saved-blocks\n * surfaces — which have no viewport control — behave exactly as before.\n * The test-email dialog passes the user's choice so responsive blocks show\n * the variant a recipient on that device would receive.\n */\n viewport?: ViewportSize;\n /**\n * Whether the hand-toggled display-condition filter still applies here.\n * `false` where a resolver owns the preview: it has already evaluated every\n * condition against real data, so layering a manual hide on top would veto\n * the answer that was asked for.\n *\n * A prop rather than an injected `PREVIEW_RESOLUTION_KEY` read, because the\n * test-email dialog builds its *own* resolution instance (keyed to the\n * selected recipient) — injecting would consult the shared canvas instance\n * and get the wrong answer.\n */\n applyConditionFilter?: boolean;\n }>(),\n { viewport: \"desktop\", applyConditionFilter: true },\n);\n\nconst blockRegistry = inject(BLOCK_REGISTRY_KEY);\nconst editor = inject(EDITOR_KEY, null);\nconst conditionPreview = inject(CONDITION_PREVIEW_KEY, null);\n\n/**\n * This canvas is only ever a preview — nothing here is editable — so it honours\n * the user's mode directly, with no `previewMode` gate of the kind `Canvas.vue`\n * needs. Re-provided rather than left to inherit, because a `BlockPreviewCanvas`\n * inside a dialog may sit under an editing `Canvas` that provides `false`.\n */\nconst mergeTagSampleMode = inject(MERGE_TAG_SAMPLE_MODE_KEY, null);\nprovide(\n USE_MERGE_TAG_SAMPLES_KEY,\n computed(() => mergeTagSampleMode?.value ?? false),\n);\n\n/**\n * Blocks a display condition currently excludes, so a preview never shows\n * content the recipient won't get. The canvas does the same via\n * `v-show=\"!conditionPreview?.isHidden(block.id)\"`.\n *\n * A no-op for the saved-blocks browser: entries there carry their *stored* ids,\n * which aren't in the current template, so nothing is tracked as hidden. It\n * bites where it should — the save dialog and the test-email preview, both of\n * which show blocks that are live on the canvas.\n *\n * Skipped entirely when `applyConditionFilter` is false — see the prop.\n */\nconst visibleBlocks = computed(() =>\n props.applyConditionFilter\n ? props.blocks.filter((block) => !conditionPreview?.isHidden(block.id))\n : props.blocks,\n);\n\n/**\n * Frame width, from the one helper the canvas and the scaled preview rows also\n * use — so all three agree by construction rather than by three constants that\n * happen to match. A template with a custom body width now previews at that\n * width instead of a flat 600.\n */\nconst frameWidth = computed(() =>\n getEmailFrameWidth(editor?.content.value.settings, props.viewport),\n);\n\n/**\n * The same document-level style the canvas applies, so a previewed block\n * renders identically to the canvas one: without it the font falls back to the\n * editor UI's and the link rules hit their unset defaults, dropping the\n * underline and the link colour.\n *\n * Read from the *current* template even in the saved-blocks browser, where the\n * previewed content came from elsewhere — a saved block stores only `Block[]`,\n * and the current settings are what it will actually look like once inserted.\n */\nconst documentStyle = computed(() =>\n editor ? getDocumentStyle(editor.content.value.settings) : {},\n);\n\n/**\n * The email's own language, for the browser's spellchecker and hyphenation —\n * the same binding the editing canvas carries, so a preview is checked by the\n * same rules as the block it mirrors.\n *\n * From `settings.locale`, never `config.locale`: the chrome's language says\n * nothing about the content's. Read from the *current* template for the same\n * reason `documentStyle` is — a saved block stores only `Block[]`. Undefined\n * rather than `\"\"`, which would declare \"unknown language\" and suppress\n * spellcheck instead of falling back.\n */\nconst contentLang = computed(\n () => editor?.content.value.settings?.locale?.trim() || undefined,\n);\nconst contentDir = computed(() =>\n resolveContentDirection(editor?.content.value.settings ?? { locale: \"en\" }),\n);\n\n/**\n * The email's body background — `mj-body background-color` when sent, and the\n * `.tpl-canvas-bg` layer on the editing canvas.\n *\n * Deliberately NOT part of `getDocumentStyle`: the canvas applies that helper\n * to `.tpl-canvas`, which must stay transparent so the invertible background\n * layer beneath it shows through. A background there would double-paint and\n * defeat the dark-mode preview.\n *\n * Truthiness, not `??`: the colour pickers clear to an empty string to mean\n * \"unset\", which as an inline style renders the frame transparent rather than\n * falling back to the neutral preview surface.\n */\nconst emailBackground = computed(\n () =>\n editor?.content.value.settings.backgroundColor || \"var(--tpl-canvas-bg)\",\n);\n\n/**\n * Width the stage asks for: the content column plus a full gutter on each side,\n * the same stage the canvas builds. It is an ask rather than a fixed size —\n * paired with `max-width: 100%` below, so a container with less room hands over\n * what it has and shows a narrower band instead of overflowing.\n *\n * It has to be a specified width, not `100%`: `TplModal`'s wrapper is\n * shrink-to-fit, so a percentage there resolves against a container whose size\n * the stage is itself supposed to influence, and the stage collapses to its\n * `min-width` — no gutters, on the one surface this exists for.\n */\nconst stageWidth = computed(() => frameWidth.value + EMAIL_GUTTER * 2);\n\nconst previewComponentMap: Record<string, Component> = {\n section: PreviewSectionBlock,\n title: TitleBlock,\n paragraph: ParagraphBlock,\n image: ImageBlock,\n video: VideoBlock,\n button: ButtonBlock,\n divider: DividerBlock,\n social: SocialIconsBlock,\n menu: MenuBlock,\n table: TableBlock,\n spacer: SpacerBlock,\n html: HtmlBlock,\n custom: CustomBlock,\n};\n\nfunction getBlockComponent(block: Block): Component | null {\n return resolveBlockComponent(block, blockRegistry, previewComponentMap);\n}\n</script>\n\n<template>\n <!-- STAGE — the email body, holding the background and a gutter of it on\n each side of the content column, the same shape `Canvas.vue` builds.\n `max-width: 100%` is what makes it fit anywhere: a roomy surface shows\n the full band, a dialog shows as much as it has, and a container sized to\n the content column shows none. `min-width` keeps it from squeezing the\n column, so a cramped surface clips exactly as it did when the frame\n carried the width itself. -->\n <div\n data-testid=\"block-preview-stage\"\n class=\"tpl:pointer-events-none tpl:mx-auto tpl:flex tpl:select-none tpl:justify-center tpl:rounded-lg\"\n :style=\"{\n width: `${stageWidth}px`,\n minWidth: `${frameWidth}px`,\n maxWidth: '100%',\n backgroundColor: emailBackground,\n boxShadow: 'var(--tpl-shadow-sm)',\n }\"\n >\n <!-- CONTENT COLUMN — the blocks at their true email width. Keeps the\n testid: this is the element whose width *is* the email width, which is\n what every viewport assertion measures. -->\n <div\n data-testid=\"block-preview-canvas\"\n :lang=\"contentLang\"\n :dir=\"contentDir\"\n :style=\"{\n width: `${frameWidth}px`,\n transition: EMAIL_FRAME_WIDTH_TRANSITION,\n ...documentStyle,\n }\"\n >\n <div\n v-for=\"block in visibleBlocks\"\n :key=\"block.id\"\n :style=\"getBlockWrapperStyle(block)\"\n >\n <component\n :is=\"getBlockComponent(block)\"\n :block=\"block\"\n :viewport=\"viewport\"\n />\n </div>\n </div>\n </div>\n</template>\n"],"mappings":"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"}
@@ -16,7 +16,7 @@ var v = {
16
16
  cloud: {}
17
17
  },
18
18
  setup(n) {
19
- let x = m(() => import("./CollaboratorBar-DgqcaGGI.js")), S = m(() => import("./AiFeatureMenu-l0K5TOSn.js")), { t: C, format: w } = _();
19
+ let x = m(() => import("./CollaboratorBar-B8ttqAqX.js")), S = m(() => import("./AiFeatureMenu-ByygdH_9.js")), { t: C, format: w } = _();
20
20
  return (m, _) => n.part === "left" ? (t(), o(f, { key: 0 }, [n.cloud.featureFlags.templateLimit.value === null ? s("", !0) : (t(), o("span", v, p(r(w)(r(C).header.templatesUsed, {
21
21
  used: n.cloud.featureFlags.templateCount.value,
22
22
  max: n.cloud.featureFlags.templateLimit.value
@@ -57,4 +57,4 @@ var v = {
57
57
  //#endregion
58
58
  export { x as default };
59
59
 
60
- //# sourceMappingURL=CloudHeaderExtras-CXNe7Q-4.js.map
60
+ //# sourceMappingURL=CloudHeaderExtras-BxsMpvVr.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"CloudHeaderExtras-CXNe7Q-4.js","names":[],"sources":["../../../src/cloud/components/CloudHeaderExtras.vue","../../../src/cloud/components/CloudHeaderExtras.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { defineAsyncComponent } from \"vue\";\nimport { Sparkles } from \"@lucide/vue\";\n\nimport { useCloudI18nStrict } from \"../../composables/useCloudI18n\";\nimport type { CloudAttachment } from \"../runtime\";\n\nconst CollaboratorBar = defineAsyncComponent(\n () => import(\"./CollaboratorBar.vue\"),\n);\nconst AiFeatureMenu = defineAsyncComponent(() => import(\"./AiFeatureMenu.vue\"));\n\n/**\n * The Cloud-only header controls, filled into `EditorHeader`'s `left-extras`\n * and `right-extras` slots.\n *\n * One component with a `part` prop rather than two: both regions share this\n * file's lazy chunk, so a Cloud session pays one round-trip for all of them and\n * an OSS session pays none.\n *\n * There is no `center` part. The header's centre track must hold a constant\n * width, so only the anchored columns take conditional controls — see\n * `EditorHeader.vue`'s centre comment.\n *\n * The plan-usage readout and the AI button live here rather than in the shared\n * header because they render **cloud-chunk** strings; OSS translators never see\n * them. Comments are deliberately not here: that feature is shared and\n * provider-backed, so its trigger and its strings belong to the shared header and\n * the OSS chunk.\n */\ndefineProps<{\n part: \"left\" | \"right\";\n cloud: CloudAttachment;\n}>();\n\nconst { t: cloudT, format: cloudFormat } = useCloudI18nStrict();\n</script>\n\n<template>\n <!-- Left: the plan's template usage, then who else is in the template right\n now. Presence sits with the template's identity rather than in the\n header's centre track: that track's width must stay constant, and a\n collaborator bar changes width every time somebody joins or leaves — which\n used to slide the Preview button sideways under the cursor (#574). This\n column packs from the left, so it grows rightward and moves nothing. -->\n <template v-if=\"part === 'left'\">\n <!-- A plan *limit*, not a templates concern, which is why it sits here and\n its string stays in the cloud chunk. -->\n <span\n v-if=\"cloud.featureFlags.templateLimit.value !== null\"\n data-testid=\"cloud-template-usage\"\n class=\"tpl:text-xs tpl:opacity-60 tpl:text-[var(--tpl-text-muted)]\"\n >\n {{\n cloudFormat(cloudT.header.templatesUsed, {\n used: cloud.featureFlags.templateCount.value,\n max: cloud.featureFlags.templateLimit.value,\n })\n }}\n </span>\n <CollaboratorBar\n v-if=\"cloud.collaboration && cloud.isCollaborationEnabled.value\"\n :collaborators=\"cloud.collaboration.collaborators.value\"\n :is-connected=\"cloud.websocket.isConnected.value\"\n />\n </template>\n\n <!-- Right: AI — after the shared header's Comments button and before the\n test-email button, so the shared header is a superset of the OSS one rather\n than a rearrangement. -->\n <template v-else-if=\"part === 'right'\">\n <div\n v-if=\"\n cloud.featureFlags.canUseAiGeneration.value &&\n cloud.featureFlags.hasTemplateSaved.value\n \"\n :ref=\"\n (el) => (cloud.panelState.aiMenuRef.value = el as HTMLElement | null)\n \"\n class=\"tpl:relative\"\n >\n <button\n type=\"button\"\n data-testid=\"ai-menu-trigger\"\n :aria-expanded=\"cloud.panelState.aiMenuOpen.value\"\n class=\"tpl-ai-btn tpl:inline-flex tpl:items-center tpl:gap-1.5 tpl:rounded-[var(--tpl-radius-sm)] tpl:border-none tpl:px-4 tpl:py-2 tpl:text-sm tpl:font-semibold tpl:whitespace-nowrap tpl:transition-all tpl:duration-200\"\n :class=\"\n cloud.panelState.aiButtonActive.value\n ? 'tpl-ai-btn--active'\n : 'tpl-ai-btn--idle'\n \"\n @click.stop=\"cloud.panelState.toggleAiMenu\"\n >\n <Sparkles :size=\"16\" :stroke-width=\"2\" class=\"tpl-ai-btn-icon\" />\n {{ cloudT.aiChat.button }}\n </button>\n <Transition\n enter-active-class=\"tpl:transition-all tpl:ease-out\"\n enter-from-class=\"tpl:scale-95 tpl:opacity-0\"\n enter-to-class=\"tpl:scale-100 tpl:opacity-100\"\n leave-active-class=\"tpl:transition-all tpl:duration-100 tpl:ease-in\"\n leave-from-class=\"tpl:scale-100 tpl:opacity-100\"\n leave-to-class=\"tpl:scale-95 tpl:opacity-0\"\n >\n <div\n v-if=\"cloud.panelState.aiMenuOpen.value\"\n class=\"tpl:absolute tpl:right-0 tpl:top-full tpl:z-50 tpl:mt-1 tpl:origin-top-right\"\n >\n <AiFeatureMenu\n :active-feature=\"cloud.panelState.activeAiFeature.value\"\n @select=\"cloud.panelState.handleAiFeatureSelect\"\n />\n </div>\n </Transition>\n </div>\n </template>\n</template>\n","<script setup lang=\"ts\">\nimport { defineAsyncComponent } from \"vue\";\nimport { Sparkles } from \"@lucide/vue\";\n\nimport { useCloudI18nStrict } from \"../../composables/useCloudI18n\";\nimport type { CloudAttachment } from \"../runtime\";\n\nconst CollaboratorBar = defineAsyncComponent(\n () => import(\"./CollaboratorBar.vue\"),\n);\nconst AiFeatureMenu = defineAsyncComponent(() => import(\"./AiFeatureMenu.vue\"));\n\n/**\n * The Cloud-only header controls, filled into `EditorHeader`'s `left-extras`\n * and `right-extras` slots.\n *\n * One component with a `part` prop rather than two: both regions share this\n * file's lazy chunk, so a Cloud session pays one round-trip for all of them and\n * an OSS session pays none.\n *\n * There is no `center` part. The header's centre track must hold a constant\n * width, so only the anchored columns take conditional controls — see\n * `EditorHeader.vue`'s centre comment.\n *\n * The plan-usage readout and the AI button live here rather than in the shared\n * header because they render **cloud-chunk** strings; OSS translators never see\n * them. Comments are deliberately not here: that feature is shared and\n * provider-backed, so its trigger and its strings belong to the shared header and\n * the OSS chunk.\n */\ndefineProps<{\n part: \"left\" | \"right\";\n cloud: CloudAttachment;\n}>();\n\nconst { t: cloudT, format: cloudFormat } = useCloudI18nStrict();\n</script>\n\n<template>\n <!-- Left: the plan's template usage, then who else is in the template right\n now. Presence sits with the template's identity rather than in the\n header's centre track: that track's width must stay constant, and a\n collaborator bar changes width every time somebody joins or leaves — which\n used to slide the Preview button sideways under the cursor (#574). This\n column packs from the left, so it grows rightward and moves nothing. -->\n <template v-if=\"part === 'left'\">\n <!-- A plan *limit*, not a templates concern, which is why it sits here and\n its string stays in the cloud chunk. -->\n <span\n v-if=\"cloud.featureFlags.templateLimit.value !== null\"\n data-testid=\"cloud-template-usage\"\n class=\"tpl:text-xs tpl:opacity-60 tpl:text-[var(--tpl-text-muted)]\"\n >\n {{\n cloudFormat(cloudT.header.templatesUsed, {\n used: cloud.featureFlags.templateCount.value,\n max: cloud.featureFlags.templateLimit.value,\n })\n }}\n </span>\n <CollaboratorBar\n v-if=\"cloud.collaboration && cloud.isCollaborationEnabled.value\"\n :collaborators=\"cloud.collaboration.collaborators.value\"\n :is-connected=\"cloud.websocket.isConnected.value\"\n />\n </template>\n\n <!-- Right: AI — after the shared header's Comments button and before the\n test-email button, so the shared header is a superset of the OSS one rather\n than a rearrangement. -->\n <template v-else-if=\"part === 'right'\">\n <div\n v-if=\"\n cloud.featureFlags.canUseAiGeneration.value &&\n cloud.featureFlags.hasTemplateSaved.value\n \"\n :ref=\"\n (el) => (cloud.panelState.aiMenuRef.value = el as HTMLElement | null)\n \"\n class=\"tpl:relative\"\n >\n <button\n type=\"button\"\n data-testid=\"ai-menu-trigger\"\n :aria-expanded=\"cloud.panelState.aiMenuOpen.value\"\n class=\"tpl-ai-btn tpl:inline-flex tpl:items-center tpl:gap-1.5 tpl:rounded-[var(--tpl-radius-sm)] tpl:border-none tpl:px-4 tpl:py-2 tpl:text-sm tpl:font-semibold tpl:whitespace-nowrap tpl:transition-all tpl:duration-200\"\n :class=\"\n cloud.panelState.aiButtonActive.value\n ? 'tpl-ai-btn--active'\n : 'tpl-ai-btn--idle'\n \"\n @click.stop=\"cloud.panelState.toggleAiMenu\"\n >\n <Sparkles :size=\"16\" :stroke-width=\"2\" class=\"tpl-ai-btn-icon\" />\n {{ cloudT.aiChat.button }}\n </button>\n <Transition\n enter-active-class=\"tpl:transition-all tpl:ease-out\"\n enter-from-class=\"tpl:scale-95 tpl:opacity-0\"\n enter-to-class=\"tpl:scale-100 tpl:opacity-100\"\n leave-active-class=\"tpl:transition-all tpl:duration-100 tpl:ease-in\"\n leave-from-class=\"tpl:scale-100 tpl:opacity-100\"\n leave-to-class=\"tpl:scale-95 tpl:opacity-0\"\n >\n <div\n v-if=\"cloud.panelState.aiMenuOpen.value\"\n class=\"tpl:absolute tpl:right-0 tpl:top-full tpl:z-50 tpl:mt-1 tpl:origin-top-right\"\n >\n <AiFeatureMenu\n :active-feature=\"cloud.panelState.activeAiFeature.value\"\n @select=\"cloud.panelState.handleAiFeatureSelect\"\n />\n </div>\n </Transition>\n </div>\n </template>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;EAOA,IAAM,IAAkB,QAChB,OAAO,gCACf,GACM,IAAgB,QAA2B,OAAO,8BAAsB,GAyBxE,EAAE,GAAG,GAAQ,QAAQ,MAAgB,EAAmB;mBAU5C,EAAA,SAAI,UAApB,EAAA,GAAA,EAoBW,GAAA,EAAA,KAAA,EAAA,GAAA,CAhBD,EAAA,MAAM,aAAa,cAAc,UAAK,OAYtC,EAAA,IAAA,EAAA,KAbR,EAAA,GAAA,EAWO,QAXP,GAWO,EALH,EAAA,CAAA,CAAW,CAAC,EAAA,CAAA,CAAM,CAAC,OAAO,eAAa;GAAoB,MAAA,EAAA,MAAM,aAAa,cAAc;GAAsB,KAAA,EAAA,MAAM,aAAa,cAAc;EAO/I,CAAA,CAAA,GAAA,CAAA,IAAA,EAAA,MAAM,iBAAiB,EAAA,MAAM,uBAAuB,SAD5D,EAAA,GAAA,EAIE,EAAA,CAAA,GAAA;;GAFC,eAAe,EAAA,MAAM,cAAc,cAAc;GACjD,gBAAc,EAAA,MAAM,UAAU,YAAY;EAO1B,GAAA,MAAA,GAAA,CAAA,iBAAA,cAAA,CAAA,KAAA,EAAA,IAAA,EAAA,CAAA,GAAA,EAAA,KAAA,EAAA,SAAI,WAAzB,EAAA,GAAA,EA6CW,GAAA,EAAA,KAAA,EAAA,GAAA,CA3CQ,EAAA,MAAM,aAAa,mBAAmB,SAAiB,EAAA,MAAM,aAAa,iBAAiB,SAD5G,EAAA,GAAA,EA2CM,OAAA;;GAtCH,MAAe,MAAQ,EAAA,MAAM,WAAW,UAAU,QAAQ;GAG3D,OAAM;EAEN,GAAA,CAAA,EAcS,UAAA;GAbP,MAAK;GACL,eAAY;GACX,iBAAe,EAAA,MAAM,WAAW,WAAW;GAC5C,OAAK,EAAA,CAAC,wNACa,EAAA,MAAM,WAAW,eAAe,QAAA,uBAAA,kBAAA,CAAA;GAKlD,SAAK,AAAA,EAAA,OAAA,GAAO,GAAA,MAAA,EAAA,MAAM,WAAW,gBAAjB,EAAA,MAAM,WAAW,aAAY,GAAA,CAAA,GAAA,CAAA,MAAA,CAAA;EAE1C,GAAA,CAAA,EAAiE,EAAA,CAAA,GAAA;GAAtD,MAAM;GAAK,gBAAc;GAAG,OAAM;EAAoB,CAAA,GAAA,EAAA,MACjE,EAAG,EAAA,CAAA,CAAM,CAAC,OAAO,MAAM,GAAA,CAAA,CAAA,GAAA,IAAA,CAAA,GAEzB,EAiBa,GAAA;GAhBX,sBAAmB;GACnB,oBAAiB;GACjB,kBAAe;GACf,sBAAmB;GACnB,oBAAiB;GACjB,kBAAe;;GAEf,SAAA,QAQM,CAPE,EAAA,MAAM,WAAW,WAAW,SADpC,EAAA,GAAA,EAQM,OARN,GAQM,CAJJ,EAGE,EAAA,CAAA,GAAA;IAFC,kBAAgB,EAAA,MAAM,WAAW,gBAAgB;IACjD,UAAQ,EAAA,MAAM,WAAW"}
1
+ {"version":3,"file":"CloudHeaderExtras-BxsMpvVr.js","names":[],"sources":["../../../src/cloud/components/CloudHeaderExtras.vue","../../../src/cloud/components/CloudHeaderExtras.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { defineAsyncComponent } from \"vue\";\nimport { Sparkles } from \"@lucide/vue\";\n\nimport { useCloudI18nStrict } from \"../../composables/useCloudI18n\";\nimport type { CloudAttachment } from \"../runtime\";\n\nconst CollaboratorBar = defineAsyncComponent(\n () => import(\"./CollaboratorBar.vue\"),\n);\nconst AiFeatureMenu = defineAsyncComponent(() => import(\"./AiFeatureMenu.vue\"));\n\n/**\n * The Cloud-only header controls, filled into `EditorHeader`'s `left-extras`\n * and `right-extras` slots.\n *\n * One component with a `part` prop rather than two: both regions share this\n * file's lazy chunk, so a Cloud session pays one round-trip for all of them and\n * an OSS session pays none.\n *\n * There is no `center` part. The header's centre track must hold a constant\n * width, so only the anchored columns take conditional controls — see\n * `EditorHeader.vue`'s centre comment.\n *\n * The plan-usage readout and the AI button live here rather than in the shared\n * header because they render **cloud-chunk** strings; OSS translators never see\n * them. Comments are deliberately not here: that feature is shared and\n * provider-backed, so its trigger and its strings belong to the shared header and\n * the OSS chunk.\n */\ndefineProps<{\n part: \"left\" | \"right\";\n cloud: CloudAttachment;\n}>();\n\nconst { t: cloudT, format: cloudFormat } = useCloudI18nStrict();\n</script>\n\n<template>\n <!-- Left: the plan's template usage, then who else is in the template right\n now. Presence sits with the template's identity rather than in the\n header's centre track: that track's width must stay constant, and a\n collaborator bar changes width every time somebody joins or leaves — which\n used to slide the Preview button sideways under the cursor (#574). This\n column packs from the left, so it grows rightward and moves nothing. -->\n <template v-if=\"part === 'left'\">\n <!-- A plan *limit*, not a templates concern, which is why it sits here and\n its string stays in the cloud chunk. -->\n <span\n v-if=\"cloud.featureFlags.templateLimit.value !== null\"\n data-testid=\"cloud-template-usage\"\n class=\"tpl:text-xs tpl:opacity-60 tpl:text-[var(--tpl-text-muted)]\"\n >\n {{\n cloudFormat(cloudT.header.templatesUsed, {\n used: cloud.featureFlags.templateCount.value,\n max: cloud.featureFlags.templateLimit.value,\n })\n }}\n </span>\n <CollaboratorBar\n v-if=\"cloud.collaboration && cloud.isCollaborationEnabled.value\"\n :collaborators=\"cloud.collaboration.collaborators.value\"\n :is-connected=\"cloud.websocket.isConnected.value\"\n />\n </template>\n\n <!-- Right: AI — after the shared header's Comments button and before the\n test-email button, so the shared header is a superset of the OSS one rather\n than a rearrangement. -->\n <template v-else-if=\"part === 'right'\">\n <div\n v-if=\"\n cloud.featureFlags.canUseAiGeneration.value &&\n cloud.featureFlags.hasTemplateSaved.value\n \"\n :ref=\"\n (el) => (cloud.panelState.aiMenuRef.value = el as HTMLElement | null)\n \"\n class=\"tpl:relative\"\n >\n <button\n type=\"button\"\n data-testid=\"ai-menu-trigger\"\n :aria-expanded=\"cloud.panelState.aiMenuOpen.value\"\n class=\"tpl-ai-btn tpl:inline-flex tpl:items-center tpl:gap-1.5 tpl:rounded-[var(--tpl-radius-sm)] tpl:border-none tpl:px-4 tpl:py-2 tpl:text-sm tpl:font-semibold tpl:whitespace-nowrap tpl:transition-all tpl:duration-200\"\n :class=\"\n cloud.panelState.aiButtonActive.value\n ? 'tpl-ai-btn--active'\n : 'tpl-ai-btn--idle'\n \"\n @click.stop=\"cloud.panelState.toggleAiMenu\"\n >\n <Sparkles :size=\"16\" :stroke-width=\"2\" class=\"tpl-ai-btn-icon\" />\n {{ cloudT.aiChat.button }}\n </button>\n <Transition\n enter-active-class=\"tpl:transition-all tpl:ease-out\"\n enter-from-class=\"tpl:scale-95 tpl:opacity-0\"\n enter-to-class=\"tpl:scale-100 tpl:opacity-100\"\n leave-active-class=\"tpl:transition-all tpl:duration-100 tpl:ease-in\"\n leave-from-class=\"tpl:scale-100 tpl:opacity-100\"\n leave-to-class=\"tpl:scale-95 tpl:opacity-0\"\n >\n <div\n v-if=\"cloud.panelState.aiMenuOpen.value\"\n class=\"tpl:absolute tpl:right-0 tpl:top-full tpl:z-50 tpl:mt-1 tpl:origin-top-right\"\n >\n <AiFeatureMenu\n :active-feature=\"cloud.panelState.activeAiFeature.value\"\n @select=\"cloud.panelState.handleAiFeatureSelect\"\n />\n </div>\n </Transition>\n </div>\n </template>\n</template>\n","<script setup lang=\"ts\">\nimport { defineAsyncComponent } from \"vue\";\nimport { Sparkles } from \"@lucide/vue\";\n\nimport { useCloudI18nStrict } from \"../../composables/useCloudI18n\";\nimport type { CloudAttachment } from \"../runtime\";\n\nconst CollaboratorBar = defineAsyncComponent(\n () => import(\"./CollaboratorBar.vue\"),\n);\nconst AiFeatureMenu = defineAsyncComponent(() => import(\"./AiFeatureMenu.vue\"));\n\n/**\n * The Cloud-only header controls, filled into `EditorHeader`'s `left-extras`\n * and `right-extras` slots.\n *\n * One component with a `part` prop rather than two: both regions share this\n * file's lazy chunk, so a Cloud session pays one round-trip for all of them and\n * an OSS session pays none.\n *\n * There is no `center` part. The header's centre track must hold a constant\n * width, so only the anchored columns take conditional controls — see\n * `EditorHeader.vue`'s centre comment.\n *\n * The plan-usage readout and the AI button live here rather than in the shared\n * header because they render **cloud-chunk** strings; OSS translators never see\n * them. Comments are deliberately not here: that feature is shared and\n * provider-backed, so its trigger and its strings belong to the shared header and\n * the OSS chunk.\n */\ndefineProps<{\n part: \"left\" | \"right\";\n cloud: CloudAttachment;\n}>();\n\nconst { t: cloudT, format: cloudFormat } = useCloudI18nStrict();\n</script>\n\n<template>\n <!-- Left: the plan's template usage, then who else is in the template right\n now. Presence sits with the template's identity rather than in the\n header's centre track: that track's width must stay constant, and a\n collaborator bar changes width every time somebody joins or leaves — which\n used to slide the Preview button sideways under the cursor (#574). This\n column packs from the left, so it grows rightward and moves nothing. -->\n <template v-if=\"part === 'left'\">\n <!-- A plan *limit*, not a templates concern, which is why it sits here and\n its string stays in the cloud chunk. -->\n <span\n v-if=\"cloud.featureFlags.templateLimit.value !== null\"\n data-testid=\"cloud-template-usage\"\n class=\"tpl:text-xs tpl:opacity-60 tpl:text-[var(--tpl-text-muted)]\"\n >\n {{\n cloudFormat(cloudT.header.templatesUsed, {\n used: cloud.featureFlags.templateCount.value,\n max: cloud.featureFlags.templateLimit.value,\n })\n }}\n </span>\n <CollaboratorBar\n v-if=\"cloud.collaboration && cloud.isCollaborationEnabled.value\"\n :collaborators=\"cloud.collaboration.collaborators.value\"\n :is-connected=\"cloud.websocket.isConnected.value\"\n />\n </template>\n\n <!-- Right: AI — after the shared header's Comments button and before the\n test-email button, so the shared header is a superset of the OSS one rather\n than a rearrangement. -->\n <template v-else-if=\"part === 'right'\">\n <div\n v-if=\"\n cloud.featureFlags.canUseAiGeneration.value &&\n cloud.featureFlags.hasTemplateSaved.value\n \"\n :ref=\"\n (el) => (cloud.panelState.aiMenuRef.value = el as HTMLElement | null)\n \"\n class=\"tpl:relative\"\n >\n <button\n type=\"button\"\n data-testid=\"ai-menu-trigger\"\n :aria-expanded=\"cloud.panelState.aiMenuOpen.value\"\n class=\"tpl-ai-btn tpl:inline-flex tpl:items-center tpl:gap-1.5 tpl:rounded-[var(--tpl-radius-sm)] tpl:border-none tpl:px-4 tpl:py-2 tpl:text-sm tpl:font-semibold tpl:whitespace-nowrap tpl:transition-all tpl:duration-200\"\n :class=\"\n cloud.panelState.aiButtonActive.value\n ? 'tpl-ai-btn--active'\n : 'tpl-ai-btn--idle'\n \"\n @click.stop=\"cloud.panelState.toggleAiMenu\"\n >\n <Sparkles :size=\"16\" :stroke-width=\"2\" class=\"tpl-ai-btn-icon\" />\n {{ cloudT.aiChat.button }}\n </button>\n <Transition\n enter-active-class=\"tpl:transition-all tpl:ease-out\"\n enter-from-class=\"tpl:scale-95 tpl:opacity-0\"\n enter-to-class=\"tpl:scale-100 tpl:opacity-100\"\n leave-active-class=\"tpl:transition-all tpl:duration-100 tpl:ease-in\"\n leave-from-class=\"tpl:scale-100 tpl:opacity-100\"\n leave-to-class=\"tpl:scale-95 tpl:opacity-0\"\n >\n <div\n v-if=\"cloud.panelState.aiMenuOpen.value\"\n class=\"tpl:absolute tpl:right-0 tpl:top-full tpl:z-50 tpl:mt-1 tpl:origin-top-right\"\n >\n <AiFeatureMenu\n :active-feature=\"cloud.panelState.activeAiFeature.value\"\n @select=\"cloud.panelState.handleAiFeatureSelect\"\n />\n </div>\n </Transition>\n </div>\n </template>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;EAOA,IAAM,IAAkB,QAChB,OAAO,gCACf,GACM,IAAgB,QAA2B,OAAO,8BAAsB,GAyBxE,EAAE,GAAG,GAAQ,QAAQ,MAAgB,EAAmB;mBAU5C,EAAA,SAAI,UAApB,EAAA,GAAA,EAoBW,GAAA,EAAA,KAAA,EAAA,GAAA,CAhBD,EAAA,MAAM,aAAa,cAAc,UAAK,OAYtC,EAAA,IAAA,EAAA,KAbR,EAAA,GAAA,EAWO,QAXP,GAWO,EALH,EAAA,CAAA,CAAW,CAAC,EAAA,CAAA,CAAM,CAAC,OAAO,eAAa;GAAoB,MAAA,EAAA,MAAM,aAAa,cAAc;GAAsB,KAAA,EAAA,MAAM,aAAa,cAAc;EAO/I,CAAA,CAAA,GAAA,CAAA,IAAA,EAAA,MAAM,iBAAiB,EAAA,MAAM,uBAAuB,SAD5D,EAAA,GAAA,EAIE,EAAA,CAAA,GAAA;;GAFC,eAAe,EAAA,MAAM,cAAc,cAAc;GACjD,gBAAc,EAAA,MAAM,UAAU,YAAY;EAO1B,GAAA,MAAA,GAAA,CAAA,iBAAA,cAAA,CAAA,KAAA,EAAA,IAAA,EAAA,CAAA,GAAA,EAAA,KAAA,EAAA,SAAI,WAAzB,EAAA,GAAA,EA6CW,GAAA,EAAA,KAAA,EAAA,GAAA,CA3CQ,EAAA,MAAM,aAAa,mBAAmB,SAAiB,EAAA,MAAM,aAAa,iBAAiB,SAD5G,EAAA,GAAA,EA2CM,OAAA;;GAtCH,MAAe,MAAQ,EAAA,MAAM,WAAW,UAAU,QAAQ;GAG3D,OAAM;EAEN,GAAA,CAAA,EAcS,UAAA;GAbP,MAAK;GACL,eAAY;GACX,iBAAe,EAAA,MAAM,WAAW,WAAW;GAC5C,OAAK,EAAA,CAAC,wNACa,EAAA,MAAM,WAAW,eAAe,QAAA,uBAAA,kBAAA,CAAA;GAKlD,SAAK,AAAA,EAAA,OAAA,GAAO,GAAA,MAAA,EAAA,MAAM,WAAW,gBAAjB,EAAA,MAAM,WAAW,aAAY,GAAA,CAAA,GAAA,CAAA,MAAA,CAAA;EAE1C,GAAA,CAAA,EAAiE,EAAA,CAAA,GAAA;GAAtD,MAAM;GAAK,gBAAc;GAAG,OAAM;EAAoB,CAAA,GAAA,EAAA,MACjE,EAAG,EAAA,CAAA,CAAM,CAAC,OAAO,MAAM,GAAA,CAAA,CAAA,GAAA,IAAA,CAAA,GAEzB,EAiBa,GAAA;GAhBX,sBAAmB;GACnB,oBAAiB;GACjB,kBAAe;GACf,sBAAmB;GACnB,oBAAiB;GACjB,kBAAe;;GAEf,SAAA,QAQM,CAPE,EAAA,MAAM,WAAW,WAAW,SADpC,EAAA,GAAA,EAQM,OARN,GAQM,CAJJ,EAGE,EAAA,CAAA,GAAA;IAFC,kBAAgB,EAAA,MAAM,WAAW,gBAAgB;IACjD,UAAQ,EAAA,MAAM,WAAW"}
@@ -1,13 +1,15 @@
1
- import { F as e, H as t, N as n, S as r, at as i, b as a, g as o, h as s, l as c, m as l, n as u, p as d, u as f, ut as p, v as m, x as h } from "./draggable-IB4x6w2O.js";
1
+ import { F as e, H as t, N as n, S as r, at as i, b as a, ct as o, g as s, h as c, m as l, n as u, p as d, u as f, ut as p, v as m, x as h } from "./draggable-IB4x6w2O.js";
2
2
  import { t as g } from "./useI18n-DTZ9sIcv.js";
3
3
  import { Mt as _, u as v } from "./icons-BKkJ-5xF.js";
4
4
  import { n as y } from "./useCloudI18n-C09JcFS0.js";
5
+ import { d as b, v as x, x as S } from "./styleConstants-C_YAPdQS.js";
6
+ import { t as C } from "./TplModal-BKqDFLq3.js";
5
7
  //#region src/cloud/components/CloudErrorOverlay.vue?vue&type=script&setup=true&lang.ts
6
- var b = {
8
+ var w = {
7
9
  key: 0,
8
10
  role: "alert",
9
11
  class: "tpl-error tpl:absolute tpl:inset-0 tpl:z-[100] tpl:flex tpl:flex-col tpl:items-center tpl:justify-center tpl:gap-6 tpl:px-8 tpl:bg-[var(--tpl-bg)]"
10
- }, x = { class: "tpl:flex tpl:size-16 tpl:items-center tpl:justify-center tpl:rounded-full tpl:bg-[var(--tpl-danger-light)]" }, S = { class: "tpl:flex tpl:flex-col tpl:items-center tpl:gap-2 tpl:text-center" }, C = { class: "tpl:text-lg tpl:font-semibold tpl:text-[var(--tpl-text)]" }, w = { class: "tpl:max-w-md tpl:text-sm tpl:text-[var(--tpl-text-muted)]" }, T = /* @__PURE__ */ r({
12
+ }, T = { class: "tpl:flex tpl:size-16 tpl:items-center tpl:justify-center tpl:rounded-full tpl:bg-[var(--tpl-danger-light)]" }, E = { class: "tpl:flex tpl:flex-col tpl:items-center tpl:gap-2 tpl:text-center" }, D = { class: "tpl:text-lg tpl:font-semibold tpl:text-[var(--tpl-text)]" }, O = { class: "tpl:max-w-md tpl:text-sm tpl:text-[var(--tpl-text-muted)]" }, k = /* @__PURE__ */ r({
11
13
  __name: "CloudErrorOverlay",
12
14
  props: {
13
15
  error: {},
@@ -15,86 +17,98 @@ var b = {
15
17
  },
16
18
  emits: ["retry"],
17
19
  setup(e, { emit: t }) {
18
- let r = t, { t: c } = y();
19
- function l(e) {
20
- return "isUnauthorized" in e && e.isUnauthorized ? c.error.authFailed : "isNotFound" in e && e.isNotFound ? c.error.templateNotFound : c.error.defaultMessage;
21
- }
20
+ let r = t, { t: l } = y();
22
21
  function u(e) {
22
+ return "isUnauthorized" in e && e.isUnauthorized ? l.error.authFailed : "isNotFound" in e && e.isNotFound ? l.error.templateNotFound : l.error.defaultMessage;
23
+ }
24
+ function f(e) {
23
25
  return "isNotFound" in e && !!e.isNotFound;
24
26
  }
25
- return (t, f) => e.visible && e.error ? (n(), o("div", b, [
26
- d("div", x, [a(i(_), {
27
+ return (t, m) => e.visible && e.error ? (n(), s("div", w, [
28
+ d("div", T, [a(i(_), {
27
29
  size: 32,
28
30
  "stroke-width": 1.5,
29
31
  class: "tpl:text-[var(--tpl-danger)]"
30
32
  })]),
31
- d("div", S, [d("h2", C, p(i(c).error.title), 1), d("p", w, p(l(e.error)), 1)]),
32
- u(e.error) ? s("", !0) : (n(), o("button", {
33
+ d("div", E, [d("h2", D, p(i(l).error.title), 1), d("p", O, p(u(e.error)), 1)]),
34
+ f(e.error) ? c("", !0) : (n(), s("button", {
33
35
  key: 0,
34
- class: "tpl-btn tpl-btn-primary tpl:inline-flex tpl:items-center tpl:gap-2 tpl:rounded-md tpl:px-4 tpl:py-2.5 tpl:text-sm tpl:font-medium tpl:shadow-[var(--tpl-shadow-sm)] tpl:transition-all tpl:hover:opacity-90 tpl:bg-[var(--tpl-primary)] tpl:text-[var(--tpl-on-primary)]",
35
- onClick: f[0] ||= (e) => r("retry")
36
- }, p(i(c).error.retry), 1))
37
- ])) : s("", !0);
36
+ type: "button",
37
+ class: o(i(x)),
38
+ onClick: m[0] ||= (e) => r("retry")
39
+ }, p(i(l).error.retry), 3))
40
+ ])) : c("", !0);
38
41
  }
39
- }), E = {
40
- key: 0,
41
- class: "tpl-loading tpl:absolute tpl:inset-0 tpl:z-[100] tpl:flex tpl:flex-col tpl:bg-[var(--tpl-bg)]"
42
- }, D = { class: "tpl:flex tpl:flex-1 tpl:overflow-hidden" }, O = { class: "tpl:flex tpl:w-12 tpl:shrink-0 tpl:flex-col tpl:items-center tpl:gap-4 tpl:py-5 tpl:border-r tpl:border-[var(--tpl-border)]" }, k = /* @__PURE__ */ r({
42
+ }), A = ["aria-label"], j = { class: "tpl:flex tpl:flex-1 tpl:overflow-hidden" }, M = { class: "tpl:flex tpl:w-12 tpl:shrink-0 tpl:flex-col tpl:items-center tpl:gap-4 tpl:py-5 tpl:border-r tpl:border-[var(--tpl-border)]" }, N = /* @__PURE__ */ r({
43
43
  __name: "CloudLoadingOverlay",
44
44
  props: { visible: { type: Boolean } },
45
45
  setup(t) {
46
- return (r, i) => t.visible ? (n(), o("div", E, [i[1] ||= m("<div class=\"tpl:flex tpl:h-14 tpl:shrink-0 tpl:items-center tpl:justify-between tpl:px-4 tpl:border-b tpl:border-[var(--tpl-border)]\"><div class=\"tpl-shimmer tpl:h-5 tpl:w-28 tpl:rounded-[var(--tpl-radius-sm)]\"></div><div class=\"tpl:flex tpl:gap-3\"><div class=\"tpl-shimmer tpl:h-8 tpl:w-20 tpl:rounded-[var(--tpl-radius-sm)]\"></div><div class=\"tpl-shimmer tpl:h-8 tpl:w-20 tpl:rounded-[var(--tpl-radius-sm)]\"></div></div></div>", 1), d("div", D, [d("div", O, [(n(), o(f, null, e(5, (e) => d("div", {
46
+ let { t: r } = y();
47
+ return (a, o) => t.visible ? (n(), s("div", {
48
+ key: 0,
49
+ role: "status",
50
+ "aria-live": "polite",
51
+ "aria-busy": "true",
52
+ "aria-label": i(r).loading.initializing,
53
+ class: "tpl-loading tpl:absolute tpl:inset-0 tpl:z-[100] tpl:flex tpl:flex-col tpl:bg-[var(--tpl-bg)]"
54
+ }, [o[1] ||= m("<div class=\"tpl:flex tpl:h-14 tpl:shrink-0 tpl:items-center tpl:justify-between tpl:px-4 tpl:border-b tpl:border-[var(--tpl-border)]\"><div class=\"tpl-shimmer tpl:h-5 tpl:w-28 tpl:rounded-[var(--tpl-radius-sm)]\"></div><div class=\"tpl:flex tpl:gap-3\"><div class=\"tpl-shimmer tpl:h-8 tpl:w-20 tpl:rounded-[var(--tpl-radius-sm)]\"></div><div class=\"tpl-shimmer tpl:h-8 tpl:w-20 tpl:rounded-[var(--tpl-radius-sm)]\"></div></div></div>", 1), d("div", j, [d("div", M, [(n(), s(f, null, e(5, (e) => d("div", {
47
55
  key: e,
48
56
  class: "tpl-shimmer tpl:size-7 tpl:rounded-[var(--tpl-radius-sm)]"
49
- })), 64))]), i[0] ||= m("<div class=\"tpl:flex tpl:flex-1 tpl:items-start tpl:justify-center tpl:overflow-auto tpl:p-8 tpl:bg-[var(--tpl-canvas-bg)]\"><div class=\"tpl:w-full tpl:max-w-[600px] tpl:rounded-[var(--tpl-radius)] tpl:p-6 tpl:bg-[var(--tpl-bg)] tpl:shadow-[var(--tpl-shadow-sm)]\"><div class=\"tpl:space-y-2 tpl:py-4\"><div class=\"tpl-shimmer tpl:h-3 tpl:w-3/4 tpl:rounded\"></div><div class=\"tpl-shimmer tpl:h-3 tpl:w-full tpl:rounded\"></div><div class=\"tpl-shimmer tpl:h-3 tpl:w-5/6 tpl:rounded\"></div></div><div class=\"tpl:py-4\"><div class=\"tpl-shimmer tpl:h-44 tpl:w-full tpl:rounded-[var(--tpl-radius-sm)]\"></div></div><div class=\"tpl:space-y-2 tpl:py-4\"><div class=\"tpl-shimmer tpl:h-3 tpl:w-full tpl:rounded\"></div><div class=\"tpl-shimmer tpl:h-3 tpl:w-2/3 tpl:rounded\"></div></div><div class=\"tpl:flex tpl:justify-center tpl:py-4\"><div class=\"tpl-shimmer tpl:h-10 tpl:w-36 tpl:rounded-[var(--tpl-radius-sm)]\"></div></div><div class=\"tpl:space-y-2 tpl:py-4\"><div class=\"tpl-shimmer tpl:mx-auto tpl:h-2.5 tpl:w-1/2 tpl:rounded\"></div><div class=\"tpl-shimmer tpl:mx-auto tpl:h-2.5 tpl:w-1/3 tpl:rounded\"></div></div></div></div><div class=\"tpl:flex tpl:w-[320px] tpl:shrink-0 tpl:flex-col tpl:gap-4 tpl:p-4 tpl:border-l tpl:border-[var(--tpl-border)]\"><div class=\"tpl-shimmer tpl:h-8 tpl:rounded-[var(--tpl-radius-sm)]\"></div><div class=\"tpl-shimmer tpl:h-32 tpl:rounded-[var(--tpl-radius)]\"></div><div class=\"tpl-shimmer tpl:h-32 tpl:rounded-[var(--tpl-radius)]\"></div></div>", 2)])])) : s("", !0);
57
+ })), 64))]), o[0] ||= m("<div class=\"tpl:flex tpl:flex-1 tpl:items-start tpl:justify-center tpl:overflow-auto tpl:p-8 tpl:bg-[var(--tpl-canvas-bg)]\"><div class=\"tpl:w-full tpl:max-w-[600px] tpl:rounded-[var(--tpl-radius)] tpl:p-6 tpl:bg-[var(--tpl-bg)] tpl:shadow-[var(--tpl-shadow-sm)]\"><div class=\"tpl:space-y-2 tpl:py-4\"><div class=\"tpl-shimmer tpl:h-3 tpl:w-3/4 tpl:rounded\"></div><div class=\"tpl-shimmer tpl:h-3 tpl:w-full tpl:rounded\"></div><div class=\"tpl-shimmer tpl:h-3 tpl:w-5/6 tpl:rounded\"></div></div><div class=\"tpl:py-4\"><div class=\"tpl-shimmer tpl:h-44 tpl:w-full tpl:rounded-[var(--tpl-radius-sm)]\"></div></div><div class=\"tpl:space-y-2 tpl:py-4\"><div class=\"tpl-shimmer tpl:h-3 tpl:w-full tpl:rounded\"></div><div class=\"tpl-shimmer tpl:h-3 tpl:w-2/3 tpl:rounded\"></div></div><div class=\"tpl:flex tpl:justify-center tpl:py-4\"><div class=\"tpl-shimmer tpl:h-10 tpl:w-36 tpl:rounded-[var(--tpl-radius-sm)]\"></div></div><div class=\"tpl:space-y-2 tpl:py-4\"><div class=\"tpl-shimmer tpl:mx-auto tpl:h-2.5 tpl:w-1/2 tpl:rounded\"></div><div class=\"tpl-shimmer tpl:mx-auto tpl:h-2.5 tpl:w-1/3 tpl:rounded\"></div></div></div></div><div class=\"tpl:flex tpl:w-[320px] tpl:shrink-0 tpl:flex-col tpl:gap-4 tpl:p-4 tpl:border-l tpl:border-[var(--tpl-border)]\"><div class=\"tpl-shimmer tpl:h-8 tpl:rounded-[var(--tpl-radius-sm)]\"></div><div class=\"tpl-shimmer tpl:h-32 tpl:rounded-[var(--tpl-radius)]\"></div><div class=\"tpl-shimmer tpl:h-32 tpl:rounded-[var(--tpl-radius)]\"></div></div>", 2)])], 8, A)) : c("", !0);
50
58
  }
51
- }), A = ["aria-label"], j = { class: "tpl:flex tpl:max-h-[80%] tpl:w-full tpl:max-w-md tpl:flex-col tpl:gap-4 tpl:rounded-lg tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:p-5 tpl:shadow-[var(--tpl-shadow-md)]" }, M = { class: "tpl:flex tpl:items-center tpl:gap-2" }, N = { class: "tpl:m-0 tpl:text-base tpl:font-semibold tpl:text-[var(--tpl-text)]" }, P = { class: "tpl:m-0 tpl:text-sm tpl:text-[var(--tpl-text-muted)]" }, F = { class: "tpl:m-0 tpl:flex tpl:max-h-64 tpl:list-none tpl:flex-col tpl:gap-1.5 tpl:overflow-y-auto tpl:rounded-md tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:p-2" }, I = { class: "tpl:text-xs tpl:text-[var(--tpl-text)]" }, L = { class: "tpl:font-mono tpl:text-[10px] tpl:text-[var(--tpl-text-dim)]" }, R = { class: "tpl:flex tpl:justify-end tpl:gap-2" }, z = /* @__PURE__ */ r({
59
+ }), P = {
60
+ role: "alertdialog",
61
+ "aria-modal": "true",
62
+ "aria-labelledby": "tpl-save-gate-title",
63
+ "aria-describedby": "tpl-save-gate-body",
64
+ "data-testid": "cloud-save-gate-dialog",
65
+ class: "tpl:flex tpl:max-h-[80%] tpl:w-full tpl:max-w-md tpl:flex-col tpl:gap-4 tpl:overflow-y-auto tpl:rounded-[var(--tpl-radius-lg)] tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:p-5 tpl:shadow-[var(--tpl-shadow-lg)]"
66
+ }, F = { class: "tpl:flex tpl:items-center tpl:gap-2" }, I = {
67
+ id: "tpl-save-gate-title",
68
+ class: "tpl:m-0 tpl:text-base tpl:font-semibold tpl:text-[var(--tpl-text)]"
69
+ }, L = {
70
+ id: "tpl-save-gate-body",
71
+ class: "tpl:m-0 tpl:text-sm tpl:text-[var(--tpl-text-muted)]"
72
+ }, R = { class: "tpl:m-0 tpl:flex tpl:max-h-64 tpl:list-none tpl:flex-col tpl:gap-1.5 tpl:overflow-y-auto tpl:rounded-md tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg-elevated)] tpl:p-2" }, z = { class: "tpl:text-xs tpl:text-[var(--tpl-text)]" }, B = { class: "tpl:font-mono tpl:text-[10px] tpl:text-[var(--tpl-text-dim)]" }, V = { class: "tpl:flex tpl:justify-end tpl:gap-2" }, H = /* @__PURE__ */ r({
52
73
  __name: "CloudSaveGateModal",
53
74
  props: {
54
75
  open: { type: Boolean },
55
76
  issues: {}
56
77
  },
57
78
  emits: ["cancel", "confirm"],
58
- setup(r, { emit: m }) {
59
- let h = m, { t: g } = y();
60
- return (m, _) => (n(), l(u, {
61
- "enter-active-class": "tpl:transition-opacity",
62
- "leave-active-class": "tpl:transition-opacity",
63
- "enter-from-class": "tpl:opacity-0",
64
- "leave-to-class": "tpl:opacity-0"
79
+ setup(r, { emit: c }) {
80
+ let u = c, { t: m } = y();
81
+ return (c, h) => (n(), l(C, {
82
+ visible: r.open,
83
+ onClose: h[2] ||= (e) => u("cancel")
65
84
  }, {
66
- default: t(() => [r.open ? (n(), o("div", {
67
- key: 0,
68
- role: "dialog",
69
- "aria-modal": "true",
70
- "aria-label": i(g).saveGate.title,
71
- class: "tpl:fixed tpl:inset-0 tpl:z-50 tpl:flex tpl:items-center tpl:justify-center tpl:bg-black/40 tpl:p-6",
72
- onClick: _[2] ||= c((e) => h("cancel"), ["self"])
73
- }, [d("div", j, [
74
- d("header", M, [a(i(v), {
85
+ default: t(() => [d("div", P, [
86
+ d("header", F, [a(i(v), {
75
87
  size: 18,
76
88
  "stroke-width": 2,
77
89
  class: "tpl:text-[var(--tpl-warning)]"
78
- }), d("h2", N, p(i(g).saveGate.title), 1)]),
79
- d("p", P, p(i(g).saveGate.body), 1),
80
- d("ul", F, [(n(!0), o(f, null, e(r.issues, (e) => (n(), o("li", {
90
+ }), d("h2", I, p(i(m).saveGate.title), 1)]),
91
+ d("p", L, p(i(m).saveGate.body), 1),
92
+ d("ul", R, [(n(!0), s(f, null, e(r.issues, (e) => (n(), s("li", {
81
93
  key: `${e.ruleId}-${e.blockId ?? "template"}`,
82
94
  class: "tpl:flex tpl:flex-col tpl:gap-0.5 tpl:rounded tpl:px-2 tpl:py-1.5"
83
- }, [d("span", I, p(e.message), 1), d("span", L, p(e.ruleId), 1)]))), 128))]),
84
- d("footer", R, [d("button", {
95
+ }, [d("span", z, p(e.message), 1), d("span", B, p(e.ruleId), 1)]))), 128))]),
96
+ d("footer", V, [d("button", {
85
97
  type: "button",
86
- class: "tpl:rounded-md tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:text-[var(--tpl-text)]",
87
- onClick: _[0] ||= (e) => h("cancel")
88
- }, p(i(g).saveGate.cancel), 1), d("button", {
98
+ "data-testid": "cloud-save-gate-cancel",
99
+ class: o(i(S)),
100
+ onClick: h[0] ||= (e) => u("cancel")
101
+ }, p(i(m).saveGate.cancel), 3), d("button", {
89
102
  type: "button",
90
- class: "tpl:rounded-md tpl:bg-[var(--tpl-danger)] tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:text-[var(--tpl-bg)]",
91
- onClick: _[1] ||= (e) => h("confirm")
92
- }, p(i(g).saveGate.confirm), 1)])
93
- ])], 8, A)) : s("", !0)]),
103
+ "data-testid": "cloud-save-gate-confirm",
104
+ class: o(i(b)),
105
+ onClick: h[1] ||= (e) => u("confirm")
106
+ }, p(i(m).saveGate.confirm), 3)])
107
+ ])]),
94
108
  _: 1
95
- }));
109
+ }, 8, ["visible"]));
96
110
  }
97
- }), B = {
111
+ }), U = {
98
112
  key: 0,
99
113
  role: "status",
100
114
  "aria-live": "polite",
@@ -104,14 +118,14 @@ var b = {
104
118
  color: "var(--tpl-text)",
105
119
  border: "1px solid var(--tpl-warning)"
106
120
  }
107
- }, V = /* @__PURE__ */ r({
121
+ }, W = /* @__PURE__ */ r({
108
122
  __name: "CollabUndoToast",
109
123
  props: { visible: { type: Boolean } },
110
124
  setup(e) {
111
125
  let { t } = g();
112
- return (r, a) => e.visible ? (n(), o("div", B, p(i(t).history.collabWarning), 1)) : s("", !0);
126
+ return (r, a) => e.visible ? (n(), s("div", U, p(i(t).history.collabWarning), 1)) : c("", !0);
113
127
  }
114
- }), H = /* @__PURE__ */ r({
128
+ }), G = /* @__PURE__ */ r({
115
129
  __name: "CloudPanels",
116
130
  props: {
117
131
  editor: {},
@@ -121,11 +135,11 @@ var b = {
121
135
  ready: {}
122
136
  },
123
137
  setup(e) {
124
- let r = h(() => import("./AiChatSidebar-CjSEoK4x.js")), s = h(() => import("./DesignReferenceSidebar-Bjo0M-AY.js")), c = h(() => import("./TemplateScoringPanel--JxR6CsQ.js")), l = e;
138
+ let r = h(() => import("./AiChatSidebar-ctfIU4i0.js")), o = h(() => import("./DesignReferenceSidebar-CtaoY-hl.js")), c = h(() => import("./TemplateScoringPanel-CR6RG6dZ.js")), l = e;
125
139
  function d(e) {
126
140
  l.core.history.record(), l.editor.setContent(e), l.core.conditionPreview.reset();
127
141
  }
128
- return (l, p) => (n(), o(f, null, [
142
+ return (l, p) => (n(), s(f, null, [
129
143
  a(u, {
130
144
  "enter-active-class": "tpl:transition-opacity tpl:duration-200",
131
145
  "enter-from-class": "tpl:opacity-100",
@@ -134,7 +148,7 @@ var b = {
134
148
  "leave-from-class": "tpl:opacity-100",
135
149
  "leave-to-class": "tpl:opacity-0"
136
150
  }, {
137
- default: t(() => [a(k, { visible: e.editor.state.isLoading }, null, 8, ["visible"])]),
151
+ default: t(() => [a(N, { visible: e.editor.state.isLoading }, null, 8, ["visible"])]),
138
152
  _: 1
139
153
  }),
140
154
  a(u, {
@@ -145,7 +159,7 @@ var b = {
145
159
  "leave-from-class": "tpl:opacity-100",
146
160
  "leave-to-class": "tpl:opacity-0"
147
161
  }, {
148
- default: t(() => [a(T, {
162
+ default: t(() => [a(k, {
149
163
  error: e.ready.sessionError.value,
150
164
  visible: !!e.ready.sessionError.value,
151
165
  onRetry: e.ready.retry
@@ -156,7 +170,7 @@ var b = {
156
170
  ])]),
157
171
  _: 1
158
172
  }),
159
- a(z, {
173
+ a(H, {
160
174
  open: e.ready.saveGate.modalOpen.value,
161
175
  issues: e.ready.saveGate.blockingIssues.value,
162
176
  onCancel: e.ready.saveGate.cancel,
@@ -175,7 +189,7 @@ var b = {
175
189
  "leave-from-class": "tpl:translate-y-0 tpl:opacity-100",
176
190
  "leave-to-class": "tpl:translate-y-[-8px] tpl:opacity-0"
177
191
  }, {
178
- default: t(() => [a(V, { visible: e.ready.collabWarning.collabUndoWarningVisible.value }, null, 8, ["visible"])]),
192
+ default: t(() => [a(W, { visible: e.ready.collabWarning.collabUndoWarningVisible.value }, null, 8, ["visible"])]),
179
193
  _: 1
180
194
  }),
181
195
  a(i(r), {
@@ -187,7 +201,7 @@ var b = {
187
201
  visible: e.cloud.panelState.scoringPanelOpen.value,
188
202
  onClose: p[1] ||= (t) => e.cloud.panelState.scoringPanelOpen.value = !1
189
203
  }, null, 8, ["visible"]),
190
- a(i(s), {
204
+ a(i(o), {
191
205
  visible: e.cloud.panelState.designReferenceOpen.value,
192
206
  "has-existing-blocks": e.editor.content.value.blocks.length > 0,
193
207
  onClose: p[2] ||= (t) => e.cloud.panelState.designReferenceOpen.value = !1,
@@ -197,6 +211,6 @@ var b = {
197
211
  }
198
212
  });
199
213
  //#endregion
200
- export { H as default };
214
+ export { G as default };
201
215
 
202
- //# sourceMappingURL=CloudPanels-bHhh9AUA.js.map
216
+ //# sourceMappingURL=CloudPanels-DmxmqAS_.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CloudPanels-DmxmqAS_.js","names":[],"sources":["../../../src/cloud/components/CloudErrorOverlay.vue","../../../src/cloud/components/CloudErrorOverlay.vue","../../../src/cloud/components/CloudLoadingOverlay.vue","../../../src/cloud/components/CloudLoadingOverlay.vue","../../../src/cloud/components/CloudSaveGateModal.vue","../../../src/cloud/components/CloudSaveGateModal.vue","../../../src/cloud/components/CollabUndoToast.vue","../../../src/cloud/components/CollabUndoToast.vue","../../../src/cloud/components/CloudPanels.vue","../../../src/cloud/components/CloudPanels.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { CircleAlert } from \"@lucide/vue\";\nimport { primaryBtnClass } from \"../../constants/styleConstants\";\nimport { useCloudI18nStrict } from \"../../composables/useCloudI18n\";\n\ndefineProps<{\n error: Error | null;\n visible: boolean;\n}>();\n\nconst emit = defineEmits<{\n (e: \"retry\"): void;\n}>();\n\nconst { t: cloudT } = useCloudI18nStrict();\n\nfunction getErrorMessage(error: Error): string {\n if (\n \"isUnauthorized\" in error &&\n (error as { isUnauthorized: boolean }).isUnauthorized\n ) {\n return cloudT.error.authFailed;\n }\n if (\"isNotFound\" in error && (error as { isNotFound: boolean }).isNotFound) {\n return cloudT.error.templateNotFound;\n }\n return cloudT.error.defaultMessage;\n}\n\nfunction isNotFoundError(error: Error): boolean {\n return (\n \"isNotFound\" in error && !!(error as { isNotFound: boolean }).isNotFound\n );\n}\n</script>\n\n<template>\n <div\n v-if=\"visible && error\"\n role=\"alert\"\n class=\"tpl-error tpl:absolute tpl:inset-0 tpl:z-[100] tpl:flex tpl:flex-col tpl:items-center tpl:justify-center tpl:gap-6 tpl:px-8 tpl:bg-[var(--tpl-bg)]\"\n >\n <div\n class=\"tpl:flex tpl:size-16 tpl:items-center tpl:justify-center tpl:rounded-full tpl:bg-[var(--tpl-danger-light)]\"\n >\n <CircleAlert\n :size=\"32\"\n :stroke-width=\"1.5\"\n class=\"tpl:text-[var(--tpl-danger)]\"\n />\n </div>\n <div\n class=\"tpl:flex tpl:flex-col tpl:items-center tpl:gap-2 tpl:text-center\"\n >\n <h2 class=\"tpl:text-lg tpl:font-semibold tpl:text-[var(--tpl-text)]\">\n {{ cloudT.error.title }}\n </h2>\n <p class=\"tpl:max-w-md tpl:text-sm tpl:text-[var(--tpl-text-muted)]\">\n {{ getErrorMessage(error) }}\n </p>\n </div>\n <button\n v-if=\"!isNotFoundError(error)\"\n type=\"button\"\n :class=\"primaryBtnClass\"\n @click=\"emit('retry')\"\n >\n {{ cloudT.error.retry }}\n </button>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { CircleAlert } from \"@lucide/vue\";\nimport { primaryBtnClass } from \"../../constants/styleConstants\";\nimport { useCloudI18nStrict } from \"../../composables/useCloudI18n\";\n\ndefineProps<{\n error: Error | null;\n visible: boolean;\n}>();\n\nconst emit = defineEmits<{\n (e: \"retry\"): void;\n}>();\n\nconst { t: cloudT } = useCloudI18nStrict();\n\nfunction getErrorMessage(error: Error): string {\n if (\n \"isUnauthorized\" in error &&\n (error as { isUnauthorized: boolean }).isUnauthorized\n ) {\n return cloudT.error.authFailed;\n }\n if (\"isNotFound\" in error && (error as { isNotFound: boolean }).isNotFound) {\n return cloudT.error.templateNotFound;\n }\n return cloudT.error.defaultMessage;\n}\n\nfunction isNotFoundError(error: Error): boolean {\n return (\n \"isNotFound\" in error && !!(error as { isNotFound: boolean }).isNotFound\n );\n}\n</script>\n\n<template>\n <div\n v-if=\"visible && error\"\n role=\"alert\"\n class=\"tpl-error tpl:absolute tpl:inset-0 tpl:z-[100] tpl:flex tpl:flex-col tpl:items-center tpl:justify-center tpl:gap-6 tpl:px-8 tpl:bg-[var(--tpl-bg)]\"\n >\n <div\n class=\"tpl:flex tpl:size-16 tpl:items-center tpl:justify-center tpl:rounded-full tpl:bg-[var(--tpl-danger-light)]\"\n >\n <CircleAlert\n :size=\"32\"\n :stroke-width=\"1.5\"\n class=\"tpl:text-[var(--tpl-danger)]\"\n />\n </div>\n <div\n class=\"tpl:flex tpl:flex-col tpl:items-center tpl:gap-2 tpl:text-center\"\n >\n <h2 class=\"tpl:text-lg tpl:font-semibold tpl:text-[var(--tpl-text)]\">\n {{ cloudT.error.title }}\n </h2>\n <p class=\"tpl:max-w-md tpl:text-sm tpl:text-[var(--tpl-text-muted)]\">\n {{ getErrorMessage(error) }}\n </p>\n </div>\n <button\n v-if=\"!isNotFoundError(error)\"\n type=\"button\"\n :class=\"primaryBtnClass\"\n @click=\"emit('retry')\"\n >\n {{ cloudT.error.retry }}\n </button>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { useCloudI18nStrict } from \"../../composables/useCloudI18n\";\n\ndefineProps<{\n visible: boolean;\n}>();\n\nconst { t: cloudT } = useCloudI18nStrict();\n</script>\n\n<template>\n <div\n v-if=\"visible\"\n role=\"status\"\n aria-live=\"polite\"\n aria-busy=\"true\"\n :aria-label=\"cloudT.loading.initializing\"\n class=\"tpl-loading tpl:absolute tpl:inset-0 tpl:z-[100] tpl:flex tpl:flex-col tpl:bg-[var(--tpl-bg)]\"\n >\n <!-- Skeleton header -->\n <div\n class=\"tpl:flex tpl:h-14 tpl:shrink-0 tpl:items-center tpl:justify-between tpl:px-4 tpl:border-b tpl:border-[var(--tpl-border)]\"\n >\n <div\n class=\"tpl-shimmer tpl:h-5 tpl:w-28 tpl:rounded-[var(--tpl-radius-sm)]\"\n ></div>\n <div class=\"tpl:flex tpl:gap-3\">\n <div\n class=\"tpl-shimmer tpl:h-8 tpl:w-20 tpl:rounded-[var(--tpl-radius-sm)]\"\n ></div>\n <div\n class=\"tpl-shimmer tpl:h-8 tpl:w-20 tpl:rounded-[var(--tpl-radius-sm)]\"\n ></div>\n </div>\n </div>\n <!-- Skeleton body -->\n <div class=\"tpl:flex tpl:flex-1 tpl:overflow-hidden\">\n <!-- Left sidebar rail -->\n <div\n class=\"tpl:flex tpl:w-12 tpl:shrink-0 tpl:flex-col tpl:items-center tpl:gap-4 tpl:py-5 tpl:border-r tpl:border-[var(--tpl-border)]\"\n >\n <div\n v-for=\"n in 5\"\n :key=\"n\"\n class=\"tpl-shimmer tpl:size-7 tpl:rounded-[var(--tpl-radius-sm)]\"\n ></div>\n </div>\n <!-- Canvas area -->\n <div\n class=\"tpl:flex tpl:flex-1 tpl:items-start tpl:justify-center tpl:overflow-auto tpl:p-8 tpl:bg-[var(--tpl-canvas-bg)]\"\n >\n <div\n class=\"tpl:w-full tpl:max-w-[600px] tpl:rounded-[var(--tpl-radius)] tpl:p-6 tpl:bg-[var(--tpl-bg)] tpl:shadow-[var(--tpl-shadow-sm)]\"\n >\n <div class=\"tpl:space-y-2 tpl:py-4\">\n <div class=\"tpl-shimmer tpl:h-3 tpl:w-3/4 tpl:rounded\"></div>\n <div class=\"tpl-shimmer tpl:h-3 tpl:w-full tpl:rounded\"></div>\n <div class=\"tpl-shimmer tpl:h-3 tpl:w-5/6 tpl:rounded\"></div>\n </div>\n <div class=\"tpl:py-4\">\n <div\n class=\"tpl-shimmer tpl:h-44 tpl:w-full tpl:rounded-[var(--tpl-radius-sm)]\"\n ></div>\n </div>\n <div class=\"tpl:space-y-2 tpl:py-4\">\n <div class=\"tpl-shimmer tpl:h-3 tpl:w-full tpl:rounded\"></div>\n <div class=\"tpl-shimmer tpl:h-3 tpl:w-2/3 tpl:rounded\"></div>\n </div>\n <div class=\"tpl:flex tpl:justify-center tpl:py-4\">\n <div\n class=\"tpl-shimmer tpl:h-10 tpl:w-36 tpl:rounded-[var(--tpl-radius-sm)]\"\n ></div>\n </div>\n <div class=\"tpl:space-y-2 tpl:py-4\">\n <div\n class=\"tpl-shimmer tpl:mx-auto tpl:h-2.5 tpl:w-1/2 tpl:rounded\"\n ></div>\n <div\n class=\"tpl-shimmer tpl:mx-auto tpl:h-2.5 tpl:w-1/3 tpl:rounded\"\n ></div>\n </div>\n </div>\n </div>\n <!-- Right panel -->\n <div\n class=\"tpl:flex tpl:w-[320px] tpl:shrink-0 tpl:flex-col tpl:gap-4 tpl:p-4 tpl:border-l tpl:border-[var(--tpl-border)]\"\n >\n <div\n class=\"tpl-shimmer tpl:h-8 tpl:rounded-[var(--tpl-radius-sm)]\"\n ></div>\n <div class=\"tpl-shimmer tpl:h-32 tpl:rounded-[var(--tpl-radius)]\"></div>\n <div class=\"tpl-shimmer tpl:h-32 tpl:rounded-[var(--tpl-radius)]\"></div>\n </div>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { useCloudI18nStrict } from \"../../composables/useCloudI18n\";\n\ndefineProps<{\n visible: boolean;\n}>();\n\nconst { t: cloudT } = useCloudI18nStrict();\n</script>\n\n<template>\n <div\n v-if=\"visible\"\n role=\"status\"\n aria-live=\"polite\"\n aria-busy=\"true\"\n :aria-label=\"cloudT.loading.initializing\"\n class=\"tpl-loading tpl:absolute tpl:inset-0 tpl:z-[100] tpl:flex tpl:flex-col tpl:bg-[var(--tpl-bg)]\"\n >\n <!-- Skeleton header -->\n <div\n class=\"tpl:flex tpl:h-14 tpl:shrink-0 tpl:items-center tpl:justify-between tpl:px-4 tpl:border-b tpl:border-[var(--tpl-border)]\"\n >\n <div\n class=\"tpl-shimmer tpl:h-5 tpl:w-28 tpl:rounded-[var(--tpl-radius-sm)]\"\n ></div>\n <div class=\"tpl:flex tpl:gap-3\">\n <div\n class=\"tpl-shimmer tpl:h-8 tpl:w-20 tpl:rounded-[var(--tpl-radius-sm)]\"\n ></div>\n <div\n class=\"tpl-shimmer tpl:h-8 tpl:w-20 tpl:rounded-[var(--tpl-radius-sm)]\"\n ></div>\n </div>\n </div>\n <!-- Skeleton body -->\n <div class=\"tpl:flex tpl:flex-1 tpl:overflow-hidden\">\n <!-- Left sidebar rail -->\n <div\n class=\"tpl:flex tpl:w-12 tpl:shrink-0 tpl:flex-col tpl:items-center tpl:gap-4 tpl:py-5 tpl:border-r tpl:border-[var(--tpl-border)]\"\n >\n <div\n v-for=\"n in 5\"\n :key=\"n\"\n class=\"tpl-shimmer tpl:size-7 tpl:rounded-[var(--tpl-radius-sm)]\"\n ></div>\n </div>\n <!-- Canvas area -->\n <div\n class=\"tpl:flex tpl:flex-1 tpl:items-start tpl:justify-center tpl:overflow-auto tpl:p-8 tpl:bg-[var(--tpl-canvas-bg)]\"\n >\n <div\n class=\"tpl:w-full tpl:max-w-[600px] tpl:rounded-[var(--tpl-radius)] tpl:p-6 tpl:bg-[var(--tpl-bg)] tpl:shadow-[var(--tpl-shadow-sm)]\"\n >\n <div class=\"tpl:space-y-2 tpl:py-4\">\n <div class=\"tpl-shimmer tpl:h-3 tpl:w-3/4 tpl:rounded\"></div>\n <div class=\"tpl-shimmer tpl:h-3 tpl:w-full tpl:rounded\"></div>\n <div class=\"tpl-shimmer tpl:h-3 tpl:w-5/6 tpl:rounded\"></div>\n </div>\n <div class=\"tpl:py-4\">\n <div\n class=\"tpl-shimmer tpl:h-44 tpl:w-full tpl:rounded-[var(--tpl-radius-sm)]\"\n ></div>\n </div>\n <div class=\"tpl:space-y-2 tpl:py-4\">\n <div class=\"tpl-shimmer tpl:h-3 tpl:w-full tpl:rounded\"></div>\n <div class=\"tpl-shimmer tpl:h-3 tpl:w-2/3 tpl:rounded\"></div>\n </div>\n <div class=\"tpl:flex tpl:justify-center tpl:py-4\">\n <div\n class=\"tpl-shimmer tpl:h-10 tpl:w-36 tpl:rounded-[var(--tpl-radius-sm)]\"\n ></div>\n </div>\n <div class=\"tpl:space-y-2 tpl:py-4\">\n <div\n class=\"tpl-shimmer tpl:mx-auto tpl:h-2.5 tpl:w-1/2 tpl:rounded\"\n ></div>\n <div\n class=\"tpl-shimmer tpl:mx-auto tpl:h-2.5 tpl:w-1/3 tpl:rounded\"\n ></div>\n </div>\n </div>\n </div>\n <!-- Right panel -->\n <div\n class=\"tpl:flex tpl:w-[320px] tpl:shrink-0 tpl:flex-col tpl:gap-4 tpl:p-4 tpl:border-l tpl:border-[var(--tpl-border)]\"\n >\n <div\n class=\"tpl-shimmer tpl:h-8 tpl:rounded-[var(--tpl-radius-sm)]\"\n ></div>\n <div class=\"tpl-shimmer tpl:h-32 tpl:rounded-[var(--tpl-radius)]\"></div>\n <div class=\"tpl-shimmer tpl:h-32 tpl:rounded-[var(--tpl-radius)]\"></div>\n </div>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { AlertTriangle } from \"@lucide/vue\";\nimport {\n dangerBtnClass,\n secondaryBtnClass,\n} from \"../../constants/styleConstants\";\nimport TplModal from \"../../components/TplModal.vue\";\nimport { useCloudI18nStrict } from \"../../composables/useCloudI18n\";\nimport type { LintIssue } from \"../../composables/useTemplateLint\";\n\ndefineProps<{\n open: boolean;\n issues: LintIssue[];\n}>();\n\nconst emit = defineEmits<{\n (e: \"cancel\"): void;\n (e: \"confirm\"): void;\n}>();\n\nconst { t: cloudT } = useCloudI18nStrict();\n</script>\n\n<template>\n <TplModal :visible=\"open\" @close=\"emit('cancel')\">\n <div\n role=\"alertdialog\"\n aria-modal=\"true\"\n aria-labelledby=\"tpl-save-gate-title\"\n aria-describedby=\"tpl-save-gate-body\"\n data-testid=\"cloud-save-gate-dialog\"\n class=\"tpl:flex tpl:max-h-[80%] tpl:w-full tpl:max-w-md tpl:flex-col tpl:gap-4 tpl:overflow-y-auto tpl:rounded-[var(--tpl-radius-lg)] tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:p-5 tpl:shadow-[var(--tpl-shadow-lg)]\"\n >\n <header class=\"tpl:flex tpl:items-center tpl:gap-2\">\n <AlertTriangle\n :size=\"18\"\n :stroke-width=\"2\"\n class=\"tpl:text-[var(--tpl-warning)]\"\n />\n <h2\n id=\"tpl-save-gate-title\"\n class=\"tpl:m-0 tpl:text-base tpl:font-semibold tpl:text-[var(--tpl-text)]\"\n >\n {{ cloudT.saveGate.title }}\n </h2>\n </header>\n\n <p\n id=\"tpl-save-gate-body\"\n class=\"tpl:m-0 tpl:text-sm tpl:text-[var(--tpl-text-muted)]\"\n >\n {{ cloudT.saveGate.body }}\n </p>\n\n <ul\n class=\"tpl:m-0 tpl:flex tpl:max-h-64 tpl:list-none tpl:flex-col tpl:gap-1.5 tpl:overflow-y-auto tpl:rounded-md tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg-elevated)] tpl:p-2\"\n >\n <li\n v-for=\"issue in issues\"\n :key=\"`${issue.ruleId}-${issue.blockId ?? 'template'}`\"\n class=\"tpl:flex tpl:flex-col tpl:gap-0.5 tpl:rounded tpl:px-2 tpl:py-1.5\"\n >\n <span class=\"tpl:text-xs tpl:text-[var(--tpl-text)]\">\n {{ issue.message }}\n </span>\n <span\n class=\"tpl:font-mono tpl:text-[10px] tpl:text-[var(--tpl-text-dim)]\"\n >\n {{ issue.ruleId }}\n </span>\n </li>\n </ul>\n\n <footer class=\"tpl:flex tpl:justify-end tpl:gap-2\">\n <button\n type=\"button\"\n data-testid=\"cloud-save-gate-cancel\"\n :class=\"secondaryBtnClass\"\n @click=\"emit('cancel')\"\n >\n {{ cloudT.saveGate.cancel }}\n </button>\n <button\n type=\"button\"\n data-testid=\"cloud-save-gate-confirm\"\n :class=\"dangerBtnClass\"\n @click=\"emit('confirm')\"\n >\n {{ cloudT.saveGate.confirm }}\n </button>\n </footer>\n </div>\n </TplModal>\n</template>\n","<script setup lang=\"ts\">\nimport { AlertTriangle } from \"@lucide/vue\";\nimport {\n dangerBtnClass,\n secondaryBtnClass,\n} from \"../../constants/styleConstants\";\nimport TplModal from \"../../components/TplModal.vue\";\nimport { useCloudI18nStrict } from \"../../composables/useCloudI18n\";\nimport type { LintIssue } from \"../../composables/useTemplateLint\";\n\ndefineProps<{\n open: boolean;\n issues: LintIssue[];\n}>();\n\nconst emit = defineEmits<{\n (e: \"cancel\"): void;\n (e: \"confirm\"): void;\n}>();\n\nconst { t: cloudT } = useCloudI18nStrict();\n</script>\n\n<template>\n <TplModal :visible=\"open\" @close=\"emit('cancel')\">\n <div\n role=\"alertdialog\"\n aria-modal=\"true\"\n aria-labelledby=\"tpl-save-gate-title\"\n aria-describedby=\"tpl-save-gate-body\"\n data-testid=\"cloud-save-gate-dialog\"\n class=\"tpl:flex tpl:max-h-[80%] tpl:w-full tpl:max-w-md tpl:flex-col tpl:gap-4 tpl:overflow-y-auto tpl:rounded-[var(--tpl-radius-lg)] tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:p-5 tpl:shadow-[var(--tpl-shadow-lg)]\"\n >\n <header class=\"tpl:flex tpl:items-center tpl:gap-2\">\n <AlertTriangle\n :size=\"18\"\n :stroke-width=\"2\"\n class=\"tpl:text-[var(--tpl-warning)]\"\n />\n <h2\n id=\"tpl-save-gate-title\"\n class=\"tpl:m-0 tpl:text-base tpl:font-semibold tpl:text-[var(--tpl-text)]\"\n >\n {{ cloudT.saveGate.title }}\n </h2>\n </header>\n\n <p\n id=\"tpl-save-gate-body\"\n class=\"tpl:m-0 tpl:text-sm tpl:text-[var(--tpl-text-muted)]\"\n >\n {{ cloudT.saveGate.body }}\n </p>\n\n <ul\n class=\"tpl:m-0 tpl:flex tpl:max-h-64 tpl:list-none tpl:flex-col tpl:gap-1.5 tpl:overflow-y-auto tpl:rounded-md tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg-elevated)] tpl:p-2\"\n >\n <li\n v-for=\"issue in issues\"\n :key=\"`${issue.ruleId}-${issue.blockId ?? 'template'}`\"\n class=\"tpl:flex tpl:flex-col tpl:gap-0.5 tpl:rounded tpl:px-2 tpl:py-1.5\"\n >\n <span class=\"tpl:text-xs tpl:text-[var(--tpl-text)]\">\n {{ issue.message }}\n </span>\n <span\n class=\"tpl:font-mono tpl:text-[10px] tpl:text-[var(--tpl-text-dim)]\"\n >\n {{ issue.ruleId }}\n </span>\n </li>\n </ul>\n\n <footer class=\"tpl:flex tpl:justify-end tpl:gap-2\">\n <button\n type=\"button\"\n data-testid=\"cloud-save-gate-cancel\"\n :class=\"secondaryBtnClass\"\n @click=\"emit('cancel')\"\n >\n {{ cloudT.saveGate.cancel }}\n </button>\n <button\n type=\"button\"\n data-testid=\"cloud-save-gate-confirm\"\n :class=\"dangerBtnClass\"\n @click=\"emit('confirm')\"\n >\n {{ cloudT.saveGate.confirm }}\n </button>\n </footer>\n </div>\n </TplModal>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../composables/useI18n\";\n\ndefineProps<{\n visible: boolean;\n}>();\n\nconst { t } = useI18n();\n</script>\n\n<template>\n <div\n v-if=\"visible\"\n role=\"status\"\n aria-live=\"polite\"\n class=\"tpl:absolute tpl:top-16 tpl:left-1/2 tpl:z-[60] tpl:-translate-x-1/2 tpl:rounded-[var(--tpl-radius)] tpl:px-4 tpl:py-2.5 tpl:text-sm tpl:shadow-[var(--tpl-shadow-lg)]\"\n style=\"\n background-color: var(--tpl-warning-light);\n color: var(--tpl-text);\n border: 1px solid var(--tpl-warning);\n \"\n >\n {{ t.history.collabWarning }}\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../composables/useI18n\";\n\ndefineProps<{\n visible: boolean;\n}>();\n\nconst { t } = useI18n();\n</script>\n\n<template>\n <div\n v-if=\"visible\"\n role=\"status\"\n aria-live=\"polite\"\n class=\"tpl:absolute tpl:top-16 tpl:left-1/2 tpl:z-[60] tpl:-translate-x-1/2 tpl:rounded-[var(--tpl-radius)] tpl:px-4 tpl:py-2.5 tpl:text-sm tpl:shadow-[var(--tpl-shadow-lg)]\"\n style=\"\n background-color: var(--tpl-warning-light);\n color: var(--tpl-text);\n border: 1px solid var(--tpl-warning);\n \"\n >\n {{ t.history.collabWarning }}\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { defineAsyncComponent } from \"vue\";\nimport type { TemplateContent } from \"@templatical/types\";\nimport type { UseEditorReturn } from \"@templatical/core\";\n\nimport type { UseEditorCoreReturn } from \"../../composables/useEditorCore\";\nimport type { CloudAttachment, CloudReady, CloudRuntime } from \"../runtime\";\n\nimport CloudErrorOverlay from \"./CloudErrorOverlay.vue\";\nimport CloudLoadingOverlay from \"./CloudLoadingOverlay.vue\";\nimport CloudSaveGateModal from \"./CloudSaveGateModal.vue\";\nimport CollabUndoToast from \"./CollabUndoToast.vue\";\n\nconst AiChatSidebar = defineAsyncComponent(() => import(\"./AiChatSidebar.vue\"));\nconst DesignReferenceSidebar = defineAsyncComponent(\n () => import(\"./DesignReferenceSidebar.vue\"),\n);\nconst TemplateScoringPanel = defineAsyncComponent(\n () => import(\"./TemplateScoringPanel.vue\"),\n);\n\n/**\n * Every piece of Cloud chrome that renders outside the header.\n *\n * `Editor.vue` mounts this behind one lazy `defineAsyncComponent` gated on a\n * cloud runtime being present, so an OSS consumer downloads none of it — the\n * `SavedBlocksPanels` / `TestEmailPanel` pattern.\n *\n * The overlays live here rather than in the shared editor deliberately: `init()`\n * cannot fail after it mounts, and OSS should not grow the ability.\n *\n * The comments sidebar is deliberately not here: it is shared, as `CommentsPanel`\n * in `Editor.vue` over a `CommentsProvider`. Its filter target lives on the shared\n * feature rather than being relayed through this component, so it survives the\n * lazy panel's mount without a runtime hop.\n *\n * The media library is not here either: it is shared, as `MediaPanels` in\n * `Editor.vue` over a `MediaProvider`. Cloud supplies that provider; it does\n * not mount a second modal.\n */\nconst props = defineProps<{\n editor: UseEditorReturn;\n core: UseEditorCoreReturn;\n runtime: CloudRuntime;\n cloud: CloudAttachment;\n ready: CloudReady;\n}>();\n\nfunction applyContent(content: TemplateContent): void {\n props.core.history.record();\n props.editor.setContent(content);\n props.core.conditionPreview.reset();\n}\n</script>\n\n<template>\n <!-- A template create / load in flight. Cloud reads its content from the\n server, so there is a real gap where the canvas shows the wrong thing. -->\n <Transition\n enter-active-class=\"tpl:transition-opacity tpl:duration-200\"\n enter-from-class=\"tpl:opacity-100\"\n enter-to-class=\"tpl:opacity-100\"\n leave-active-class=\"tpl:transition-opacity tpl:duration-300\"\n leave-from-class=\"tpl:opacity-100\"\n leave-to-class=\"tpl:opacity-0\"\n >\n <CloudLoadingOverlay :visible=\"editor.state.isLoading\" />\n </Transition>\n\n <!-- Session failure — an auth refresh that could not renew the token. The\n *bootstrap* has no overlay any more: it runs before the mount, so a\n failure there rejects `initCloud()` instead of mounting a dead editor. -->\n <Transition\n enter-active-class=\"tpl:transition-opacity tpl:duration-200\"\n enter-from-class=\"tpl:opacity-0\"\n enter-to-class=\"tpl:opacity-100\"\n leave-active-class=\"tpl:transition-opacity tpl:duration-300\"\n leave-from-class=\"tpl:opacity-100\"\n leave-to-class=\"tpl:opacity-0\"\n >\n <CloudErrorOverlay\n :error=\"ready.sessionError.value\"\n :visible=\"!!ready.sessionError.value\"\n @retry=\"ready.retry\"\n />\n </Transition>\n\n <CloudSaveGateModal\n :open=\"ready.saveGate.modalOpen.value\"\n :issues=\"ready.saveGate.blockingIssues.value\"\n @cancel=\"ready.saveGate.cancel\"\n @confirm=\"ready.saveGate.confirmAndSave\"\n />\n\n <Transition\n enter-active-class=\"tpl:transition-all tpl:duration-200 tpl:ease-out\"\n enter-from-class=\"tpl:translate-y-[-8px] tpl:opacity-0\"\n enter-to-class=\"tpl:translate-y-0 tpl:opacity-100\"\n leave-active-class=\"tpl:transition-all tpl:duration-300 tpl:ease-in\"\n leave-from-class=\"tpl:translate-y-0 tpl:opacity-100\"\n leave-to-class=\"tpl:translate-y-[-8px] tpl:opacity-0\"\n >\n <CollabUndoToast\n :visible=\"ready.collabWarning.collabUndoWarningVisible.value\"\n />\n </Transition>\n\n <AiChatSidebar\n :visible=\"cloud.panelState.aiChatOpen.value\"\n :on-apply=\"applyContent\"\n @close=\"cloud.panelState.aiChatOpen.value = false\"\n />\n\n <TemplateScoringPanel\n :visible=\"cloud.panelState.scoringPanelOpen.value\"\n @close=\"cloud.panelState.scoringPanelOpen.value = false\"\n />\n\n <DesignReferenceSidebar\n :visible=\"cloud.panelState.designReferenceOpen.value\"\n :has-existing-blocks=\"editor.content.value.blocks.length > 0\"\n @close=\"cloud.panelState.designReferenceOpen.value = false\"\n @apply=\"applyContent\"\n />\n</template>\n","<script setup lang=\"ts\">\nimport { defineAsyncComponent } from \"vue\";\nimport type { TemplateContent } from \"@templatical/types\";\nimport type { UseEditorReturn } from \"@templatical/core\";\n\nimport type { UseEditorCoreReturn } from \"../../composables/useEditorCore\";\nimport type { CloudAttachment, CloudReady, CloudRuntime } from \"../runtime\";\n\nimport CloudErrorOverlay from \"./CloudErrorOverlay.vue\";\nimport CloudLoadingOverlay from \"./CloudLoadingOverlay.vue\";\nimport CloudSaveGateModal from \"./CloudSaveGateModal.vue\";\nimport CollabUndoToast from \"./CollabUndoToast.vue\";\n\nconst AiChatSidebar = defineAsyncComponent(() => import(\"./AiChatSidebar.vue\"));\nconst DesignReferenceSidebar = defineAsyncComponent(\n () => import(\"./DesignReferenceSidebar.vue\"),\n);\nconst TemplateScoringPanel = defineAsyncComponent(\n () => import(\"./TemplateScoringPanel.vue\"),\n);\n\n/**\n * Every piece of Cloud chrome that renders outside the header.\n *\n * `Editor.vue` mounts this behind one lazy `defineAsyncComponent` gated on a\n * cloud runtime being present, so an OSS consumer downloads none of it — the\n * `SavedBlocksPanels` / `TestEmailPanel` pattern.\n *\n * The overlays live here rather than in the shared editor deliberately: `init()`\n * cannot fail after it mounts, and OSS should not grow the ability.\n *\n * The comments sidebar is deliberately not here: it is shared, as `CommentsPanel`\n * in `Editor.vue` over a `CommentsProvider`. Its filter target lives on the shared\n * feature rather than being relayed through this component, so it survives the\n * lazy panel's mount without a runtime hop.\n *\n * The media library is not here either: it is shared, as `MediaPanels` in\n * `Editor.vue` over a `MediaProvider`. Cloud supplies that provider; it does\n * not mount a second modal.\n */\nconst props = defineProps<{\n editor: UseEditorReturn;\n core: UseEditorCoreReturn;\n runtime: CloudRuntime;\n cloud: CloudAttachment;\n ready: CloudReady;\n}>();\n\nfunction applyContent(content: TemplateContent): void {\n props.core.history.record();\n props.editor.setContent(content);\n props.core.conditionPreview.reset();\n}\n</script>\n\n<template>\n <!-- A template create / load in flight. Cloud reads its content from the\n server, so there is a real gap where the canvas shows the wrong thing. -->\n <Transition\n enter-active-class=\"tpl:transition-opacity tpl:duration-200\"\n enter-from-class=\"tpl:opacity-100\"\n enter-to-class=\"tpl:opacity-100\"\n leave-active-class=\"tpl:transition-opacity tpl:duration-300\"\n leave-from-class=\"tpl:opacity-100\"\n leave-to-class=\"tpl:opacity-0\"\n >\n <CloudLoadingOverlay :visible=\"editor.state.isLoading\" />\n </Transition>\n\n <!-- Session failure — an auth refresh that could not renew the token. The\n *bootstrap* has no overlay any more: it runs before the mount, so a\n failure there rejects `initCloud()` instead of mounting a dead editor. -->\n <Transition\n enter-active-class=\"tpl:transition-opacity tpl:duration-200\"\n enter-from-class=\"tpl:opacity-0\"\n enter-to-class=\"tpl:opacity-100\"\n leave-active-class=\"tpl:transition-opacity tpl:duration-300\"\n leave-from-class=\"tpl:opacity-100\"\n leave-to-class=\"tpl:opacity-0\"\n >\n <CloudErrorOverlay\n :error=\"ready.sessionError.value\"\n :visible=\"!!ready.sessionError.value\"\n @retry=\"ready.retry\"\n />\n </Transition>\n\n <CloudSaveGateModal\n :open=\"ready.saveGate.modalOpen.value\"\n :issues=\"ready.saveGate.blockingIssues.value\"\n @cancel=\"ready.saveGate.cancel\"\n @confirm=\"ready.saveGate.confirmAndSave\"\n />\n\n <Transition\n enter-active-class=\"tpl:transition-all tpl:duration-200 tpl:ease-out\"\n enter-from-class=\"tpl:translate-y-[-8px] tpl:opacity-0\"\n enter-to-class=\"tpl:translate-y-0 tpl:opacity-100\"\n leave-active-class=\"tpl:transition-all tpl:duration-300 tpl:ease-in\"\n leave-from-class=\"tpl:translate-y-0 tpl:opacity-100\"\n leave-to-class=\"tpl:translate-y-[-8px] tpl:opacity-0\"\n >\n <CollabUndoToast\n :visible=\"ready.collabWarning.collabUndoWarningVisible.value\"\n />\n </Transition>\n\n <AiChatSidebar\n :visible=\"cloud.panelState.aiChatOpen.value\"\n :on-apply=\"applyContent\"\n @close=\"cloud.panelState.aiChatOpen.value = false\"\n />\n\n <TemplateScoringPanel\n :visible=\"cloud.panelState.scoringPanelOpen.value\"\n @close=\"cloud.panelState.scoringPanelOpen.value = false\"\n />\n\n <DesignReferenceSidebar\n :visible=\"cloud.panelState.designReferenceOpen.value\"\n :has-existing-blocks=\"editor.content.value.blocks.length > 0\"\n @close=\"cloud.panelState.designReferenceOpen.value = false\"\n @apply=\"applyContent\"\n />\n</template>\n"],"mappings":"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"}