@templatical/editor 0.25.1 → 0.25.2

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 (185) hide show
  1. package/dist/{AiChatSidebar-BYMdVldY.js → AiChatSidebar-JLsbPnsH.js} +14 -14
  2. package/dist/{AiFeatureMenu-BhHhnu9G.js → AiFeatureMenu-BvA-lViN.js} +7 -7
  3. package/dist/{BlockIssueBadge-De36PJC4.js → BlockIssueBadge-Cf0-GC3o.js} +5 -5
  4. package/dist/{BlockPreviewCanvas-BxSoA1QV.js → BlockPreviewCanvas-CnJIM5TO.js} +3 -3
  5. package/dist/{CloudEditor-BXfuytQj.js → CloudEditor-Caq7z4-7.js} +25 -25
  6. package/dist/{CollaboratorBar-D9AfWRTH.js → CollaboratorBar-BFz4LgA0.js} +4 -4
  7. package/dist/{ColorPicker-VPq4ASC8.js → ColorPicker-B5xXWWX5.js} +6 -6
  8. package/dist/{CommentsSidebar-C8BLy3Pr.js → CommentsSidebar-CGihWwn9.js} +15 -15
  9. package/dist/{CountdownBlock-Bs2Q_bFX.js → CountdownBlock-DuyvGcr8.js} +3 -3
  10. package/dist/{CountdownToolbar-Cic6cdOw.js → CountdownToolbar-DpNCZAUI.js} +4 -4
  11. package/dist/{DesignReferenceSidebar-k-26nkJI.js → DesignReferenceSidebar-CiIHHiil.js} +11 -11
  12. package/dist/{IssuesPanel-NPzwTPYW.js → IssuesPanel-CUkmAC_S.js} +8 -8
  13. package/dist/{LoadingTrack-DH3OEM3z.js → LoadingTrack-BexpNuTV.js} +1 -1
  14. package/dist/{MergeTagModeToggle-DEGyx8tw.js → MergeTagModeToggle-C6nS3qri.js} +5 -5
  15. package/dist/{MergeTagSuggestion-DvisJ9im.js → MergeTagSuggestion-98fAcuM-.js} +26 -24
  16. package/dist/{NumberWithSuffix-CuHCIwSn.js → NumberWithSuffix-DB-1GNvx.js} +2 -2
  17. package/dist/{ParagraphEditor-DK_IPi9H.js → ParagraphEditor-NgW-T9YV.js} +74 -80
  18. package/dist/{RichTextEditorContent-DznIQcdY.js → RichTextEditorContent-CN7OVR6u.js} +8 -8
  19. package/dist/{SaveBlockDialog-R9PbMsy-.js → SaveBlockDialog-lgcmhAsO.js} +10 -10
  20. package/dist/{SavedBlocksBrowserModal-ttPAklPE.js → SavedBlocksBrowserModal-3u247V2z.js} +11 -11
  21. package/dist/{SavedBlocksPanels-BCwQgKLF.js → SavedBlocksPanels-Dh6dABJt.js} +2 -2
  22. package/dist/{SavedBlocksPickBar-DD5Ddv9t.js → SavedBlocksPickBar-Bo82D3Qy.js} +4 -4
  23. package/dist/{SnapshotHistory-BXSTGgBh.js → SnapshotHistory-Dsb1i40w.js} +16 -16
  24. package/dist/{TemplateScoringPanel-Ue6Dfnjb.js → TemplateScoringPanel-Bzd_YSag.js} +15 -15
  25. package/dist/{TestEmailModal-KPFc5oSv.js → TestEmailModal-DvqDTOcO.js} +10 -10
  26. package/dist/{TestEmailPanel-b8GHTrnE.js → TestEmailPanel-BXoVnAqu.js} +2 -2
  27. package/dist/{TitleEditor-8ln7UBr_.js → TitleEditor-DWSi998C.js} +12 -12
  28. package/dist/{TplModal-D_FA3Wm3.js → TplModal-DKn0h2qe.js} +5 -5
  29. package/dist/{blockTypeIcons-BoZeK6l7.js → blockTypeIcons-DKlza6aR.js} +2 -2
  30. package/dist/bundle-stats.json +6 -6
  31. package/dist/cdn/chunks/{AiChatSidebar-D6jGwb4y.js → AiChatSidebar-BY1Iy-e8.js} +15 -15
  32. package/dist/cdn/chunks/{AiChatSidebar-D6jGwb4y.js.map → AiChatSidebar-BY1Iy-e8.js.map} +1 -1
  33. package/dist/cdn/chunks/{AiFeatureMenu-BtEcjxLL.js → AiFeatureMenu-BS4Rek6j.js} +8 -8
  34. package/dist/cdn/chunks/{AiFeatureMenu-BtEcjxLL.js.map → AiFeatureMenu-BS4Rek6j.js.map} +1 -1
  35. package/dist/cdn/chunks/{BlockIssueBadge-dnqZk6pl.js → BlockIssueBadge-BcIQUe_Q.js} +5 -5
  36. package/dist/cdn/chunks/{BlockIssueBadge-dnqZk6pl.js.map → BlockIssueBadge-BcIQUe_Q.js.map} +1 -1
  37. package/dist/cdn/chunks/{BlockPreviewCanvas-D8pfTFWi.js → BlockPreviewCanvas-oND1iXLL.js} +4 -4
  38. package/dist/cdn/chunks/{BlockPreviewCanvas-D8pfTFWi.js.map → BlockPreviewCanvas-oND1iXLL.js.map} +1 -1
  39. package/dist/cdn/chunks/{CloudEditor-BJ4-pPAl.js → CloudEditor-BaqiFbf0.js} +21 -21
  40. package/dist/cdn/chunks/{CloudEditor-BJ4-pPAl.js.map → CloudEditor-BaqiFbf0.js.map} +1 -1
  41. package/dist/cdn/chunks/{CollaboratorBar-SeUOuLeJ.js → CollaboratorBar-DF02nTwF.js} +5 -5
  42. package/dist/cdn/chunks/{CollaboratorBar-SeUOuLeJ.js.map → CollaboratorBar-DF02nTwF.js.map} +1 -1
  43. package/dist/cdn/chunks/{ColorPicker-yxvrro60.js → ColorPicker-BXH44v2H.js} +7 -7
  44. package/dist/cdn/chunks/{ColorPicker-yxvrro60.js.map → ColorPicker-BXH44v2H.js.map} +1 -1
  45. package/dist/cdn/chunks/{CommentsSidebar-FPbg-FuU.js → CommentsSidebar-B9bWvvbl.js} +19 -19
  46. package/dist/cdn/chunks/{CommentsSidebar-FPbg-FuU.js.map → CommentsSidebar-B9bWvvbl.js.map} +1 -1
  47. package/dist/cdn/chunks/{CountdownBlock-D2Oth7YY.js → CountdownBlock-BTpRd0Hn.js} +4 -4
  48. package/dist/cdn/chunks/{CountdownBlock-D2Oth7YY.js.map → CountdownBlock-BTpRd0Hn.js.map} +1 -1
  49. package/dist/cdn/chunks/{CountdownToolbar-BNgTBQGB.js → CountdownToolbar-CW10rbWt.js} +5 -5
  50. package/dist/cdn/chunks/{CountdownToolbar-BNgTBQGB.js.map → CountdownToolbar-CW10rbWt.js.map} +1 -1
  51. package/dist/cdn/chunks/{DesignReferenceSidebar-ChB_HBK1.js → DesignReferenceSidebar-BXjH47QC.js} +13 -13
  52. package/dist/cdn/chunks/{DesignReferenceSidebar-ChB_HBK1.js.map → DesignReferenceSidebar-BXjH47QC.js.map} +1 -1
  53. package/dist/cdn/chunks/{IssuesPanel-CjiZ8cLW.js → IssuesPanel-DStHCHfr.js} +8 -8
  54. package/dist/cdn/chunks/{IssuesPanel-CjiZ8cLW.js.map → IssuesPanel-DStHCHfr.js.map} +1 -1
  55. package/dist/cdn/chunks/{LoadingTrack-CiNg9s16.js → LoadingTrack-DQowK3MV.js} +2 -2
  56. package/dist/cdn/chunks/{LoadingTrack-CiNg9s16.js.map → LoadingTrack-DQowK3MV.js.map} +1 -1
  57. package/dist/cdn/chunks/{MergeTagModeToggle-CiYbOKzd.js → MergeTagModeToggle-BCmQg4Qe.js} +15 -15
  58. package/dist/cdn/chunks/{MergeTagModeToggle-CiYbOKzd.js.map → MergeTagModeToggle-BCmQg4Qe.js.map} +1 -1
  59. package/dist/cdn/chunks/{MergeTagSuggestion-CvZLI6Np.js → MergeTagSuggestion-BgExdloL.js} +3 -3
  60. package/dist/cdn/chunks/{MergeTagSuggestion-CvZLI6Np.js.map → MergeTagSuggestion-BgExdloL.js.map} +1 -1
  61. package/dist/cdn/chunks/{NumberWithSuffix-vVwCWrd_.js → NumberWithSuffix-BANVbpT0.js} +3 -3
  62. package/dist/cdn/chunks/{NumberWithSuffix-vVwCWrd_.js.map → NumberWithSuffix-BANVbpT0.js.map} +1 -1
  63. package/dist/cdn/chunks/{ParagraphEditor-o4hs9mgy.js → ParagraphEditor-DqilB4_f.js} +37 -37
  64. package/dist/cdn/chunks/{ParagraphEditor-o4hs9mgy.js.map → ParagraphEditor-DqilB4_f.js.map} +1 -1
  65. package/dist/cdn/chunks/{RichTextEditorContent-BAriGldd.js → RichTextEditorContent-BeJx4LVH.js} +7 -7
  66. package/dist/cdn/chunks/{RichTextEditorContent-BAriGldd.js.map → RichTextEditorContent-BeJx4LVH.js.map} +1 -1
  67. package/dist/cdn/chunks/{SaveBlockDialog-D3-oILb5.js → SaveBlockDialog-Dm31xRZy.js} +19 -19
  68. package/dist/cdn/chunks/{SaveBlockDialog-D3-oILb5.js.map → SaveBlockDialog-Dm31xRZy.js.map} +1 -1
  69. package/dist/cdn/chunks/{SavedBlocksBrowserModal-jMnNd4wn.js → SavedBlocksBrowserModal-Tp37b4y7.js} +13 -13
  70. package/dist/cdn/chunks/{SavedBlocksBrowserModal-jMnNd4wn.js.map → SavedBlocksBrowserModal-Tp37b4y7.js.map} +1 -1
  71. package/dist/cdn/chunks/{SavedBlocksPanels-BDd8FH6l.js → SavedBlocksPanels-ElerTTa_.js} +3 -3
  72. package/dist/cdn/chunks/{SavedBlocksPanels-BDd8FH6l.js.map → SavedBlocksPanels-ElerTTa_.js.map} +1 -1
  73. package/dist/cdn/chunks/{SavedBlocksPickBar-BNkLe8In.js → SavedBlocksPickBar-QHJ-Mjx0.js} +5 -5
  74. package/dist/cdn/chunks/{SavedBlocksPickBar-BNkLe8In.js.map → SavedBlocksPickBar-QHJ-Mjx0.js.map} +1 -1
  75. package/dist/cdn/chunks/{SnapshotHistory-CYVi7fvQ.js → SnapshotHistory-jogWGLB-.js} +9 -9
  76. package/dist/cdn/chunks/{SnapshotHistory-CYVi7fvQ.js.map → SnapshotHistory-jogWGLB-.js.map} +1 -1
  77. package/dist/cdn/chunks/{TemplateScoringPanel-D0KrxVnr.js → TemplateScoringPanel-B2XIOTl-.js} +17 -17
  78. package/dist/cdn/chunks/{TemplateScoringPanel-D0KrxVnr.js.map → TemplateScoringPanel-B2XIOTl-.js.map} +1 -1
  79. package/dist/cdn/chunks/{TestEmailModal-_RbTD787.js → TestEmailModal-BcPjQygV.js} +9 -9
  80. package/dist/cdn/chunks/{TestEmailModal-_RbTD787.js.map → TestEmailModal-BcPjQygV.js.map} +1 -1
  81. package/dist/cdn/chunks/{TestEmailPanel-BAnKtbSu.js → TestEmailPanel-1zxRa00s.js} +3 -3
  82. package/dist/cdn/chunks/{TestEmailPanel-BAnKtbSu.js.map → TestEmailPanel-1zxRa00s.js.map} +1 -1
  83. package/dist/cdn/chunks/{TitleEditor-LiUvXGPw.js → TitleEditor-DTrmublj.js} +15 -15
  84. package/dist/cdn/chunks/{TitleEditor-LiUvXGPw.js.map → TitleEditor-DTrmublj.js.map} +1 -1
  85. package/dist/cdn/chunks/{TplModal-C1iguuZz.js → TplModal-By50gtwa.js} +5 -5
  86. package/dist/cdn/chunks/{TplModal-C1iguuZz.js.map → TplModal-By50gtwa.js.map} +1 -1
  87. package/dist/cdn/chunks/blockTypeIcons-BhFhHuFt.js +21 -0
  88. package/dist/cdn/chunks/{blockTypeIcons-CrPzyP_k.js.map → blockTypeIcons-BhFhHuFt.js.map} +1 -1
  89. package/dist/cdn/chunks/{cloud-CxNsW3hp.js → cloud-B0f7Ub8f.js} +3 -3
  90. package/dist/cdn/chunks/{cloud-CxNsW3hp.js.map → cloud-B0f7Ub8f.js.map} +1 -1
  91. package/dist/cdn/chunks/{dist-Dp46rwVY.js → dist-B4VIlp1g.js} +2 -2
  92. package/dist/cdn/chunks/{dist-Dp46rwVY.js.map → dist-B4VIlp1g.js.map} +1 -1
  93. package/dist/cdn/chunks/{dist-BLF-S9_A.js → dist-H51neg3K.js} +9 -7
  94. package/dist/cdn/chunks/{dist-BLF-S9_A.js.map → dist-H51neg3K.js.map} +1 -1
  95. package/dist/cdn/chunks/{draggable-BRF_Q_jB.js → draggable-Z4XXmbPC.js} +113 -91
  96. package/dist/cdn/chunks/{draggable-BRF_Q_jB.js.map → draggable-Z4XXmbPC.js.map} +1 -1
  97. package/dist/cdn/chunks/{extensions-yNQlgLNE.js → extensions-Dq0D79tq.js} +6 -6
  98. package/dist/cdn/chunks/{extensions-yNQlgLNE.js.map → extensions-Dq0D79tq.js.map} +1 -1
  99. package/dist/cdn/chunks/{htmlparser-CPhs2cYE.js → htmlparser-DTML8Gvz.js} +8 -7
  100. package/dist/cdn/chunks/{htmlparser-CPhs2cYE.js.map → htmlparser-DTML8Gvz.js.map} +1 -1
  101. package/dist/cdn/chunks/{icons-DN008liP.js → icons-C_kRN0_C.js} +78 -84
  102. package/dist/cdn/chunks/icons-C_kRN0_C.js.map +1 -0
  103. package/dist/cdn/chunks/{keys-CZOBuCQd.js → keys-DsoX52o_.js} +2 -2
  104. package/dist/cdn/chunks/{keys-CZOBuCQd.js.map → keys-DsoX52o_.js.map} +1 -1
  105. package/dist/cdn/chunks/{liquid.browser-CZaQe87t.js → liquid.browser-DrGys_ek.js} +22 -20
  106. package/dist/cdn/chunks/{liquid.browser-CZaQe87t.js.map → liquid.browser-DrGys_ek.js.map} +1 -1
  107. package/dist/cdn/chunks/{pusher-kUlkqMvR.js → pusher-BbIHJKf3.js} +29 -25
  108. package/dist/cdn/chunks/{pusher-kUlkqMvR.js.map → pusher-BbIHJKf3.js.map} +1 -1
  109. package/dist/cdn/chunks/{src-uc_Mb_yH.js → src-Aea1Vl1H.js} +2 -2
  110. package/dist/cdn/chunks/{src-uc_Mb_yH.js.map → src-Aea1Vl1H.js.map} +1 -1
  111. package/dist/cdn/chunks/{src-B2LpXMIw.js → src-CRvTVX4F.js} +102 -99
  112. package/dist/cdn/chunks/{src-B2LpXMIw.js.map → src-CRvTVX4F.js.map} +1 -1
  113. package/dist/cdn/chunks/{src-D_3bXt7-.js → src-_GNJgPcS.js} +2 -2
  114. package/dist/cdn/chunks/{src-D_3bXt7-.js.map → src-_GNJgPcS.js.map} +1 -1
  115. package/dist/cdn/chunks/{styles-BzP13r-I.js → styles-znpPfrFd.js} +78 -78
  116. package/dist/cdn/chunks/{styles-BzP13r-I.js.map → styles-znpPfrFd.js.map} +1 -1
  117. package/dist/cdn/chunks/{tiptap-CgwK_fKJ.js → tiptap-C5vzWlHv.js} +225 -183
  118. package/dist/cdn/chunks/tiptap-C5vzWlHv.js.map +1 -0
  119. package/dist/cdn/chunks/{useAliveFlag-Ctyt3GgH.js → useAliveFlag-4IDYQRNu.js} +2 -2
  120. package/dist/cdn/chunks/{useAliveFlag-Ctyt3GgH.js.map → useAliveFlag-4IDYQRNu.js.map} +1 -1
  121. package/dist/cdn/chunks/{useCloudI18n-CML0BxqX.js → useCloudI18n-D4JpHTPk.js} +3 -3
  122. package/dist/cdn/chunks/{useCloudI18n-CML0BxqX.js.map → useCloudI18n-D4JpHTPk.js.map} +1 -1
  123. package/dist/cdn/chunks/{useEditorCore-CTYH6u4r.js → useEditorCore-BMXBmdd8.js} +32 -32
  124. package/dist/cdn/chunks/useEditorCore-BMXBmdd8.js.map +1 -0
  125. package/dist/cdn/chunks/{useI18n-aRMtgYRj.js → useI18n-BvcEYzrI.js} +3 -3
  126. package/dist/cdn/chunks/{useI18n-aRMtgYRj.js.map → useI18n-BvcEYzrI.js.map} +1 -1
  127. package/dist/cdn/chunks/{useMergeTag-8a8BnIZp.js → useMergeTag-BSdxMUzg.js} +3 -3
  128. package/dist/cdn/chunks/{useMergeTag-8a8BnIZp.js.map → useMergeTag-BSdxMUzg.js.map} +1 -1
  129. package/dist/cdn/chunks/{usePopoverPosition-Dm1jv3y5.js → usePopoverPosition-ig-71caO.js} +3 -3
  130. package/dist/cdn/chunks/{usePopoverPosition-Dm1jv3y5.js.map → usePopoverPosition-ig-71caO.js.map} +1 -1
  131. package/dist/cdn/editor.js +8 -8
  132. package/dist/{check-Bqimqf7l.js → check-COV27dGb.js} +1 -1
  133. package/dist/{chevron-down-CYOoeGBd.js → chevron-down-vsU88vcr.js} +1 -1
  134. package/dist/{chevron-up-DYknsfm0.js → chevron-up-RrZ0Rjn9.js} +1 -1
  135. package/dist/{circle-alert-6q_jgtv_.js → circle-alert-B6GlHmmi.js} +1 -1
  136. package/dist/{clock-CnadSSTD.js → clock-egOt8zjQ.js} +1 -1
  137. package/dist/{cloud-BJLg_SlM.js → cloud-D-2StbvU.js} +1 -1
  138. package/dist/{createLucideIcon-D7GKhya2.js → createLucideIcon-D6cqT5Pm.js} +3 -3
  139. package/dist/{dist-iCEDH3cC.js → dist-B56SufAv.js} +1 -1
  140. package/dist/{dist-CkPvZCld.js → dist-BQZkRg05.js} +1 -1
  141. package/dist/{dist-DDfXShPv.js → dist-BpKxrUi2.js} +1 -1
  142. package/dist/{dist-CcQFPJMF.js → dist-CHxPjcxA.js} +181 -145
  143. package/dist/{dist-BxbTVeY0.js → dist-CqV4XslQ.js} +1 -1
  144. package/dist/{dist-Dx71pluS.js → dist-D4GoAr2g.js} +9 -7
  145. package/dist/{dist-DSIgxp0v.js → dist-DHwnYYx0.js} +1 -1
  146. package/dist/{dist-DX4x6mQ1.js → dist-Du95Y9M3.js} +1 -1
  147. package/dist/{dist-B6Px6GPj.js → dist-DzzTB4eE.js} +1 -1
  148. package/dist/{dist-DyCHUyRR.js → dist-GUZZpDK3.js} +1 -1
  149. package/dist/{dist-nR6mxyqI.js → dist-MwT7T6Xp.js} +1 -1
  150. package/dist/{dist-C31bMR4x.js → dist-UkG5ZJ1P.js} +16 -14
  151. package/dist/{dist-CRH880TV.js → dist-vpuiQ7_Q.js} +1 -1
  152. package/dist/{extensions-CbiWtzS6.js → extensions-y6KjLr9t.js} +7 -7
  153. package/dist/{image-up-BfukboIn.js → image-up-CycES-MQ.js} +1 -1
  154. package/dist/{info-DSYRzSZo.js → info-BUdvm046.js} +1 -1
  155. package/dist/{keys-BI6VSUh4.js → keys-fwF3qZax.js} +1 -1
  156. package/dist/{liquid.browser-BECWFL5Z.js → liquid.browser-Di5zMIEx.js} +21 -19
  157. package/dist/{list-checks-URZOyLhr.js → list-checks-BAeuuATD.js} +1 -1
  158. package/dist/{loader-circle-GADaYcyQ.js → loader-circle-L5yuAx_j.js} +1 -1
  159. package/dist/{message-circle-B62fdo0b.js → message-circle-DRdaapVc.js} +1 -1
  160. package/dist/{pencil-BGwkOpD_.js → pencil-BfErVPf_.js} +1 -1
  161. package/dist/{refresh-cw-Z4wXfduO.js → refresh-cw-D0PYatER.js} +1 -1
  162. package/dist/{runtime-dom.esm-bundler-BkOsapVg.js → runtime-dom.esm-bundler-CepEmvwF.js} +13 -10
  163. package/dist/{scan-line-D8Oh5xos.js → scan-line-Byi3fJcr.js} +1 -1
  164. package/dist/{send-BYp4k38T.js → send-DuyNexMY.js} +1 -1
  165. package/dist/{shield-check-Cm0RePnW.js → shield-check-BkkN_1qw.js} +1 -1
  166. package/dist/{sparkles-BHEE-vRD.js → sparkles-DAw2sBXk.js} +1 -1
  167. package/dist/{styles-B0MEBpOK.js → styles-BzwheHH_.js} +32 -32
  168. package/dist/templatical-editor.js +10 -10
  169. package/dist/{text-align-start-CHrg3vK8.js → text-align-start-Ba3xJKn6.js} +1 -1
  170. package/dist/{trash-2-BoNT8wbq.js → trash-2-CapEBXBI.js} +1 -1
  171. package/dist/{triangle-alert-CiN0ssB3.js → triangle-alert-52pdw8jI.js} +1 -1
  172. package/dist/{upload-WvZGBySw.js → upload-oAgigC31.js} +1 -1
  173. package/dist/{useAliveFlag-eT67QCMf.js → useAliveFlag-cvyBxHp7.js} +1 -1
  174. package/dist/{useCloudI18n-KgIWl-IE.js → useCloudI18n-DKVm6PCa.js} +2 -2
  175. package/dist/{useEditorCore-BMbxdUbY.js → useEditorCore-BhjEMSeE.js} +23 -23
  176. package/dist/{useI18n-BkHfCWC6.js → useI18n-DoVKdCTH.js} +2 -2
  177. package/dist/{useMergeTag-CZ4XYAxu.js → useMergeTag-DTSOQnyr.js} +2 -2
  178. package/dist/{usePopoverPosition-D93u-EZm.js → usePopoverPosition-C0dPEF4X.js} +3 -3
  179. package/dist/{vue.runtime.esm-bundler-0R1UhmZB.js → vue.runtime.esm-bundler-D1diBMsm.js} +119 -95
  180. package/dist/{x-B4WnJVRx.js → x-DvBgbDuf.js} +1 -1
  181. package/package.json +9 -9
  182. package/dist/cdn/chunks/blockTypeIcons-CrPzyP_k.js +0 -21
  183. package/dist/cdn/chunks/icons-DN008liP.js.map +0 -1
  184. package/dist/cdn/chunks/tiptap-CgwK_fKJ.js.map +0 -1
  185. package/dist/cdn/chunks/useEditorCore-CTYH6u4r.js.map +0 -1
@@ -1,9 +1,9 @@
1
- import { H as e, M as t, O as n, P as r, V as i, Z as a, ct as o, f as s, g as c, h as l, it as u, m as d, n as f, o as p, ot as m, p as h, st as ee, u as g, v as te, x as _, y as v, z as y } from "./draggable-BRF_Q_jB.js";
2
- import { G as b, d as ne, g as re, i as ie } from "./keys-CZOBuCQd.js";
3
- import { t as ae } from "./useI18n-aRMtgYRj.js";
4
- import { It as oe, Lt as se, N as x, Nt as ce, R as le, at as S, d as C, jt as ue, n as w, nt as de, q as T } from "./icons-DN008liP.js";
1
+ import { H as e, M as t, O as n, P as r, V as i, Z as a, ct as o, f as s, g as c, h as l, it as u, m as d, n as f, o as p, ot as m, p as h, st as ee, u as g, v as te, x as _, y as v, z as y } from "./draggable-Z4XXmbPC.js";
2
+ import { G as b, d as ne, g as re, i as ie } from "./keys-DsoX52o_.js";
3
+ import { t as ae } from "./useI18n-BvcEYzrI.js";
4
+ import { It as oe, K as x, L as se, Lt as ce, M as S, Nt as le, d as C, it as w, jt as ue, n as de, tt as T } from "./icons-C_kRN0_C.js";
5
5
  import { t as E } from "./_plugin-vue_export-helper-B3ysoDQm.js";
6
- import { n as fe } from "./useCloudI18n-CML0BxqX.js";
6
+ import { n as fe } from "./useCloudI18n-D4JpHTPk.js";
7
7
  import { t as pe } from "./formatRelativeTime-CtUU-QZ8.js";
8
8
  //#region src/cloud/components/CommentsSidebar.vue?vue&type=script&setup=true&lang.ts
9
9
  var me = {
@@ -168,7 +168,7 @@ var me = {
168
168
  }, {
169
169
  default: i(() => [_.visible ? (t(), c("div", me, [
170
170
  h("div", he, [h("div", ge, [
171
- v(u(de), {
171
+ v(u(T), {
172
172
  size: 13,
173
173
  "stroke-width": 2
174
174
  }),
@@ -177,7 +177,7 @@ var me = {
177
177
  ]), h("button", {
178
178
  class: "tpl:rounded-md tpl:p-0.5 tpl:transition-colors tpl:duration-150 tpl:text-[var(--tpl-text-muted)]",
179
179
  onClick: a[0] ||= (e) => O("close")
180
- }, [v(u(w), {
180
+ }, [v(u(de), {
181
181
  size: 14,
182
182
  "stroke-width": 2
183
183
  })])]),
@@ -196,11 +196,11 @@ var me = {
196
196
  onClick: a[3] ||= (e) => W("block", u(j).state.selectedBlockId ?? void 0)
197
197
  }, o(u(A).comments.filterBlock), 3)) : l("", !0)
198
198
  ]),
199
- h("div", ye, [u(M).isLoading.value ? (t(), c("div", be, [v(u(S), {
199
+ h("div", ye, [u(M).isLoading.value ? (t(), c("div", be, [v(u(w), {
200
200
  class: "tpl-spinner tpl:text-[var(--tpl-text-muted)]",
201
201
  size: 24,
202
202
  "stroke-width": 2
203
- })])) : U.value.length === 0 ? (t(), c("div", xe, [v(u(de), {
203
+ })])) : U.value.length === 0 ? (t(), c("div", xe, [v(u(T), {
204
204
  size: 32,
205
205
  "stroke-width": 1.5,
206
206
  class: "tpl:text-[var(--tpl-text-dim)]"
@@ -229,7 +229,7 @@ var me = {
229
229
  class: "tpl-comment-action tpl:rounded tpl:p-1 tpl:transition-colors tpl:duration-150",
230
230
  title: u(A).comments.edit,
231
231
  onClick: (e) => G(n)
232
- }, [v(u(T), {
232
+ }, [v(u(x), {
233
233
  size: 12,
234
234
  "stroke-width": 2
235
235
  })], 8, Me)) : l("", !0),
@@ -244,7 +244,7 @@ var me = {
244
244
  })], 8, Ne)) : l("", !0)
245
245
  ])]),
246
246
  v(f, { name: "tpl-resolve" }, {
247
- default: i(() => [n.resolved_at ? (t(), c("div", Pe, [v(u(se), {
247
+ default: i(() => [n.resolved_at ? (t(), c("div", Pe, [v(u(ce), {
248
248
  size: 10,
249
249
  "stroke-width": 2.5
250
250
  }), h("span", null, o(u(k)(u(A).comments.resolvedBy, { name: n.resolved_by_name ?? "" })), 1)])) : l("", !0)]),
@@ -283,7 +283,7 @@ var me = {
283
283
  class: "tpl-comment-action tpl:rounded tpl:p-1 tpl:transition-colors tpl:duration-150",
284
284
  title: u(A).comments.reply,
285
285
  onClick: (e) => kt(n.id)
286
- }, [v(u(le), {
286
+ }, [v(u(se), {
287
287
  size: 13,
288
288
  "stroke-width": 2,
289
289
  class: "tpl:text-[var(--tpl-primary)]"
@@ -291,7 +291,7 @@ var me = {
291
291
  key: 0,
292
292
  class: "tpl:flex tpl:items-center tpl:gap-0.5 tpl:text-[11px] tpl:font-medium tpl:transition-colors tpl:duration-150 tpl:text-[var(--tpl-text-muted)]",
293
293
  onClick: (e) => Ot(n.id)
294
- }, [B.value.has(n.id) ? (t(), d(u(ce), {
294
+ }, [B.value.has(n.id) ? (t(), d(u(le), {
295
295
  key: 0,
296
296
  size: 11,
297
297
  "stroke-width": 2
@@ -315,7 +315,7 @@ var me = {
315
315
  class: "tpl-comment-action tpl:rounded tpl:p-1 tpl:transition-colors tpl:duration-150",
316
316
  title: u(A).comments.edit,
317
317
  onClick: (e) => G(r)
318
- }, [v(u(T), {
318
+ }, [v(u(x), {
319
319
  size: 11,
320
320
  "stroke-width": 2
321
321
  })], 8, tt)) : l("", !0), $(r) ? (t(), c("button", {
@@ -365,13 +365,13 @@ var me = {
365
365
  class: "tpl:flex tpl:items-center tpl:justify-center tpl:rounded-md tpl:p-1.5 tpl:transition-colors tpl:duration-150 tpl:disabled:opacity-40 tpl:text-[var(--tpl-primary)]",
366
366
  disabled: !L.value.trim() || u(M).isSubmitting.value,
367
367
  onClick: (e) => X(n.id)
368
- }, [v(u(x), {
368
+ }, [v(u(S), {
369
369
  size: 14,
370
370
  "stroke-width": 2
371
371
  })], 8, ht), h("button", {
372
372
  class: "tpl:flex tpl:items-center tpl:justify-center tpl:rounded-md tpl:p-1.5 tpl:transition-colors tpl:duration-150 tpl:text-[var(--tpl-text-muted)]",
373
373
  onClick: a[11] ||= (e) => At()
374
- }, [v(u(w), {
374
+ }, [v(u(de), {
375
375
  size: 14,
376
376
  "stroke-width": 2
377
377
  })])])])])) : l("", !0)]),
@@ -391,12 +391,12 @@ var me = {
391
391
  class: "tpl-comments-send-btn tpl:flex tpl:shrink-0 tpl:items-center tpl:justify-center tpl:rounded-md tpl:p-1.5 tpl:transition-all tpl:duration-150 tpl:disabled:opacity-40 tpl:text-[var(--tpl-primary)]",
392
392
  disabled: !F.value.trim() || u(M).isSubmitting.value,
393
393
  onClick: Y
394
- }, [u(M).isSubmitting.value ? (t(), d(u(S), {
394
+ }, [u(M).isSubmitting.value ? (t(), d(u(w), {
395
395
  key: 0,
396
396
  class: "tpl-spinner",
397
397
  size: 16,
398
398
  "stroke-width": 2
399
- })) : (t(), d(u(x), {
399
+ })) : (t(), d(u(S), {
400
400
  key: 1,
401
401
  size: 16,
402
402
  "stroke-width": 2
@@ -409,4 +409,4 @@ var me = {
409
409
  //#endregion
410
410
  export { D as default };
411
411
 
412
- //# sourceMappingURL=CommentsSidebar-FPbg-FuU.js.map
412
+ //# sourceMappingURL=CommentsSidebar-B9bWvvbl.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"CommentsSidebar-FPbg-FuU.js","names":[],"sources":["../../../src/cloud/components/CommentsSidebar.vue","../../../src/cloud/components/CommentsSidebar.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { formatRelativeTime } from \"../../utils/formatRelativeTime\";\nimport {\n EDITOR_KEY,\n AUTH_MANAGER_KEY,\n COMMENTS_KEY,\n requireInject,\n} from \"../../keys\";\nimport { useI18n } from \"../../composables/useI18n\";\nimport { useCloudI18nStrict } from \"../../composables/useCloudI18n\";\nimport type { Comment } from \"@templatical/types\";\nimport {\n Check,\n CircleCheck,\n ChevronDown,\n ChevronUp,\n LoaderCircle,\n MessageCircle,\n Pencil,\n Reply,\n Send,\n Trash2,\n X,\n} from \"@lucide/vue\";\nimport { computed, nextTick, ref, watch } from \"vue\";\n\nconst props = defineProps<{\n visible: boolean;\n}>();\n\nconst emit = defineEmits<{\n (e: \"close\"): void;\n (e: \"filterBlock\", blockId: string | null): void;\n}>();\n\nconst { format } = useI18n();\nconst { t: cloudT } = useCloudI18nStrict();\nconst editor = requireInject(EDITOR_KEY, \"CommentsSidebar\");\nconst authManager = requireInject(AUTH_MANAGER_KEY, \"CommentsSidebar\");\nconst comments = requireInject(COMMENTS_KEY, \"CommentsSidebar\");\n\ntype FilterMode = \"all\" | \"unresolved\" | \"block\";\nconst filterMode = ref<FilterMode>(\"unresolved\");\nconst filterBlockId = ref<string | null>(null);\n\nconst newCommentBody = ref(\"\");\nconst replyingTo = ref<string | null>(null);\nconst replyBody = ref(\"\");\nconst editingId = ref<string | null>(null);\nconst editBody = ref(\"\");\nconst expandedThreads = ref<Set<string>>(new Set());\nconst deletingId = ref<string | null>(null);\nconst newCommentInput = ref<HTMLTextAreaElement | null>(null);\n\nconst currentUserId = computed(() => authManager.userConfig?.id ?? null);\n\nconst allBlockIds = computed<Set<string>>(() => {\n const ids = new Set<string>();\n for (const block of editor.content.value.blocks) {\n ids.add(block.id);\n if (block.type === \"section\") {\n for (const column of block.children) {\n for (const child of column) {\n ids.add(child.id);\n }\n }\n }\n }\n return ids;\n});\n\nfunction isBlockMissing(blockId: string | null): boolean {\n if (!blockId) {\n return false;\n }\n return !allBlockIds.value.has(blockId);\n}\n\nconst isBlockFilterUnsaved = computed(() => {\n if (filterMode.value !== \"block\" || !filterBlockId.value) {\n return false;\n }\n return !(editor.savedBlockIds?.value.has(filterBlockId.value) ?? true);\n});\n\nconst filteredComments = computed(() => {\n let items = comments.comments.value;\n\n if (filterMode.value === \"unresolved\") {\n items = items.filter((c) => !c.resolved_at);\n } else if (filterMode.value === \"block\" && filterBlockId.value) {\n items = items.filter((c) => c.block_id === filterBlockId.value);\n }\n\n return items;\n});\n\nwatch(\n () => props.visible,\n (isVisible) => {\n if (isVisible) {\n comments.loadComments();\n }\n },\n);\n\nwatch(\n () => editor.state.selectedBlockId,\n (newBlockId) => {\n if (filterMode.value === \"block\" && newBlockId) {\n filterBlockId.value = newBlockId;\n }\n },\n);\n\nfunction setFilter(mode: FilterMode, blockId?: string): void {\n filterMode.value = mode;\n filterBlockId.value = mode === \"block\" ? (blockId ?? null) : null;\n}\n\nfunction filterByBlock(blockId: string): void {\n setFilter(\"block\", blockId);\n emit(\"filterBlock\", blockId);\n}\n\nfunction toggleThread(threadId: string): void {\n if (expandedThreads.value.has(threadId)) {\n expandedThreads.value.delete(threadId);\n } else {\n expandedThreads.value.add(threadId);\n }\n}\n\nfunction startReply(threadId: string): void {\n replyingTo.value = threadId;\n replyBody.value = \"\";\n editingId.value = null;\n\n if (!expandedThreads.value.has(threadId)) {\n expandedThreads.value.add(threadId);\n }\n}\n\nfunction cancelReply(): void {\n replyingTo.value = null;\n replyBody.value = \"\";\n}\n\nfunction startEdit(comment: Comment): void {\n editingId.value = comment.id;\n editBody.value = comment.body;\n replyingTo.value = null;\n}\n\nfunction cancelEdit(): void {\n editingId.value = null;\n editBody.value = \"\";\n}\n\nfunction confirmDelete(commentId: string): void {\n deletingId.value = commentId;\n}\n\nfunction cancelDelete(): void {\n deletingId.value = null;\n}\n\nasync function handleAddComment(): Promise<void> {\n const body = newCommentBody.value.trim();\n if (!body) {\n return;\n }\n\n const blockId =\n filterMode.value === \"block\" ? filterBlockId.value : undefined;\n await comments.addComment(body, blockId ?? undefined);\n newCommentBody.value = \"\";\n}\n\nasync function handleReply(parentId: string): Promise<void> {\n const body = replyBody.value.trim();\n if (!body) {\n return;\n }\n\n const parent = comments.comments.value.find((c) => c.id === parentId);\n await comments.addComment(body, parent?.block_id ?? undefined, parentId);\n replyingTo.value = null;\n replyBody.value = \"\";\n}\n\nasync function handleEdit(commentId: string): Promise<void> {\n const body = editBody.value.trim();\n if (!body) {\n return;\n }\n\n await comments.editComment(commentId, body);\n editingId.value = null;\n editBody.value = \"\";\n}\n\nasync function handleDelete(commentId: string): Promise<void> {\n await comments.removeComment(commentId);\n deletingId.value = null;\n}\n\nasync function handleResolve(commentId: string): Promise<void> {\n await comments.toggleResolve(commentId);\n}\n\nfunction isOwnComment(comment: Comment): boolean {\n return comment.author_identifier === currentUserId.value;\n}\n\nfunction formatTime(dateString: string): string {\n return formatRelativeTime(dateString, cloudT.snapshotHistory) ?? dateString;\n}\n\nfunction handleNewCommentKeydown(event: KeyboardEvent): void {\n if (event.key === \"Enter\" && !event.shiftKey) {\n event.preventDefault();\n handleAddComment();\n }\n}\n\nfunction handleReplyKeydown(event: KeyboardEvent, parentId: string): void {\n if (event.key === \"Enter\" && !event.shiftKey) {\n event.preventDefault();\n handleReply(parentId);\n }\n}\n\nfunction handleEditKeydown(event: KeyboardEvent, commentId: string): void {\n if (event.key === \"Enter\" && !event.shiftKey) {\n event.preventDefault();\n handleEdit(commentId);\n }\n if (event.key === \"Escape\") {\n cancelEdit();\n }\n}\n\nfunction focusNewComment(): void {\n nextTick(() => {\n newCommentInput.value?.focus();\n });\n}\n\ndefineExpose({ filterByBlock, focusNewComment });\n</script>\n\n<template>\n <Transition\n enter-active-class=\"tpl-comments-slide-enter-active\"\n enter-from-class=\"tpl:translate-x-full\"\n enter-to-class=\"tpl:translate-x-0\"\n leave-active-class=\"tpl-comments-slide-leave-active\"\n leave-from-class=\"tpl:translate-x-0\"\n leave-to-class=\"tpl:translate-x-full\"\n >\n <div\n v-if=\"visible\"\n class=\"tpl-comments-sidebar tpl:absolute tpl:top-14 tpl:right-0 tpl:bottom-0 tpl:z-panel tpl:flex tpl:w-[360px] tpl:flex-col tpl:border-l tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg-elevated)]\"\n >\n <!-- Header -->\n <div\n class=\"tpl:flex tpl:items-center tpl:justify-between tpl:border-b tpl:border-[var(--tpl-border)] tpl:px-4 tpl:py-3\"\n >\n <div\n class=\"tpl:flex tpl:items-center tpl:gap-1.5 tpl:text-sm tpl:font-medium tpl:text-[var(--tpl-text)]\"\n >\n <MessageCircle :size=\"13\" :stroke-width=\"2\" />\n <span>{{ cloudT.comments.title }}</span>\n <span\n v-if=\"comments.unresolvedCount.value > 0\"\n class=\"tpl:ml-1 tpl:inline-flex tpl:size-5 tpl:items-center tpl:justify-center tpl:rounded-full tpl:text-[10px] tpl:font-semibold tpl:bg-[var(--tpl-primary)] tpl:text-[var(--tpl-bg)]\"\n >\n {{ comments.unresolvedCount.value }}\n </span>\n </div>\n <button\n class=\"tpl:rounded-md tpl:p-0.5 tpl:transition-colors tpl:duration-150 tpl:text-[var(--tpl-text-muted)]\"\n @click=\"emit('close')\"\n >\n <X :size=\"14\" :stroke-width=\"2\" />\n </button>\n </div>\n\n <!-- Filter bar -->\n <div\n class=\"tpl:flex tpl:gap-1 tpl:border-b tpl:border-[var(--tpl-border)] tpl:px-4 tpl:py-2\"\n >\n <button\n class=\"tpl-comment-filter tpl:rounded-md tpl:px-2.5 tpl:py-1 tpl:text-xs tpl:font-medium tpl:transition-colors tpl:duration-150\"\n :class=\"\n filterMode === 'unresolved' ? 'tpl-comment-filter--active' : ''\n \"\n @click=\"setFilter('unresolved')\"\n >\n {{ cloudT.comments.filterUnresolved }}\n </button>\n <button\n class=\"tpl-comment-filter tpl:rounded-md tpl:px-2.5 tpl:py-1 tpl:text-xs tpl:font-medium tpl:transition-colors tpl:duration-150\"\n :class=\"filterMode === 'all' ? 'tpl-comment-filter--active' : ''\"\n @click=\"setFilter('all')\"\n >\n {{ cloudT.comments.filterAll }}\n </button>\n <button\n v-if=\"editor.state.selectedBlockId\"\n class=\"tpl-comment-filter tpl:rounded-md tpl:px-2.5 tpl:py-1 tpl:text-xs tpl:font-medium tpl:transition-colors tpl:duration-150\"\n :class=\"filterMode === 'block' ? 'tpl-comment-filter--active' : ''\"\n @click=\"setFilter('block', editor.state.selectedBlockId ?? undefined)\"\n >\n {{ cloudT.comments.filterBlock }}\n </button>\n </div>\n\n <!-- Thread list -->\n <div class=\"tpl:flex-1 tpl:overflow-y-auto\">\n <!-- Loading state -->\n <div\n v-if=\"comments.isLoading.value\"\n class=\"tpl:flex tpl:h-full tpl:items-center tpl:justify-center\"\n >\n <LoaderCircle\n class=\"tpl-spinner tpl:text-[var(--tpl-text-muted)]\"\n :size=\"24\"\n :stroke-width=\"2\"\n />\n </div>\n\n <!-- Empty state -->\n <div\n v-else-if=\"filteredComments.length === 0\"\n class=\"tpl:flex tpl:h-full tpl:flex-col tpl:items-center tpl:justify-center tpl:gap-3 tpl:px-6 tpl:text-center\"\n >\n <MessageCircle\n :size=\"32\"\n :stroke-width=\"1.5\"\n class=\"tpl:text-[var(--tpl-text-dim)]\"\n />\n <p\n class=\"tpl:max-w-[240px] tpl:text-sm tpl:text-[var(--tpl-text-muted)]\"\n >\n {{\n filterMode === \"all\"\n ? cloudT.comments.noCommentsHint\n : cloudT.comments.noComments\n }}\n </p>\n </div>\n\n <!-- Comment threads -->\n <div v-else class=\"tpl:flex tpl:flex-col tpl:gap-3 tpl:p-3\">\n <div\n v-for=\"thread in filteredComments\"\n :key=\"thread.id\"\n class=\"tpl-comment-thread\"\n >\n <!-- Root comment card -->\n <div\n class=\"tpl-comment-card tpl:rounded-lg tpl:border tpl:px-3.5 tpl:py-3\"\n >\n <!-- Comment header -->\n <div\n class=\"tpl:flex tpl:items-start tpl:justify-between tpl:gap-2\"\n >\n <div class=\"tpl:flex tpl:items-center tpl:gap-1.5\">\n <span\n class=\"tpl:text-xs tpl:font-semibold tpl:text-[var(--tpl-text)]\"\n >\n {{\n isOwnComment(thread)\n ? cloudT.comments.ownedByYou\n : thread.author_name\n }}\n </span>\n <span class=\"tpl:text-[10px] tpl:text-[var(--tpl-text-dim)]\">\n {{ formatTime(thread.created_at) }}\n </span>\n <span\n v-if=\"thread.updated_at !== thread.created_at\"\n class=\"tpl:text-[10px] tpl:italic tpl:text-[var(--tpl-text-dim)]\"\n >\n ({{ cloudT.comments.edited }})\n </span>\n </div>\n <div class=\"tpl:flex tpl:items-center tpl:gap-0.5\">\n <!-- Resolve toggle -->\n <button\n class=\"tpl-comment-action tpl:rounded tpl:p-1 tpl:transition-colors tpl:duration-150\"\n :title=\"\n thread.resolved_at\n ? cloudT.comments.unresolve\n : cloudT.comments.resolve\n \"\n @click=\"handleResolve(thread.id)\"\n >\n <CircleCheck\n :size=\"13\"\n :stroke-width=\"2\"\n class=\"tpl-resolve-icon\"\n :style=\"{\n color: thread.resolved_at\n ? 'var(--tpl-primary)'\n : undefined,\n }\"\n />\n </button>\n <!-- Edit (own only) -->\n <button\n v-if=\"isOwnComment(thread)\"\n class=\"tpl-comment-action tpl:rounded tpl:p-1 tpl:transition-colors tpl:duration-150\"\n :title=\"cloudT.comments.edit\"\n @click=\"startEdit(thread)\"\n >\n <Pencil :size=\"12\" :stroke-width=\"2\" />\n </button>\n <!-- Delete (own only) -->\n <button\n v-if=\"isOwnComment(thread)\"\n class=\"tpl-comment-action tpl-comment-delete tpl:rounded tpl:p-1 tpl:transition-colors tpl:duration-150\"\n :title=\"cloudT.comments.delete\"\n @click=\"confirmDelete(thread.id)\"\n >\n <Trash2 :size=\"12\" :stroke-width=\"2\" />\n </button>\n </div>\n </div>\n\n <!-- Resolved badge -->\n <Transition name=\"tpl-resolve\">\n <div\n v-if=\"thread.resolved_at\"\n class=\"tpl:mt-1 tpl:flex tpl:items-center tpl:gap-1 tpl:text-[10px] tpl:text-[var(--tpl-primary)]\"\n >\n <Check :size=\"10\" :stroke-width=\"2.5\" />\n <span>\n {{\n format(cloudT.comments.resolvedBy, {\n name: thread.resolved_by_name ?? \"\",\n })\n }}\n </span>\n </div>\n </Transition>\n\n <!-- Block anchor indicator -->\n <span\n v-if=\"thread.block_id && isBlockMissing(thread.block_id)\"\n class=\"tpl:mt-1 tpl:inline-flex tpl:items-center tpl:gap-1 tpl:rounded tpl:px-1.5 tpl:py-0.5 tpl:text-[10px] tpl:font-medium tpl:bg-[var(--tpl-warning-light)] tpl:text-[var(--tpl-warning)]\"\n >\n {{ cloudT.comments.missingBlock }}\n </span>\n <button\n v-else-if=\"thread.block_id\"\n class=\"tpl:mt-1 tpl:inline-flex tpl:items-center tpl:gap-1 tpl:rounded tpl:px-1.5 tpl:py-0.5 tpl:text-[10px] tpl:font-medium tpl:transition-colors tpl:duration-150 tpl:bg-[var(--tpl-bg-hover)] tpl:text-[var(--tpl-text-muted)]\"\n @click=\"editor.selectBlock(thread.block_id ?? '')\"\n >\n Block\n </button>\n\n <!-- Comment body (or edit mode) -->\n <div v-if=\"editingId === thread.id\" class=\"tpl:mt-2\">\n <textarea\n v-model=\"editBody\"\n class=\"tpl:w-full tpl:resize-none tpl:rounded-md tpl:border tpl:px-2.5 tpl:py-2 tpl:font-sans tpl:text-xs tpl:outline-none tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:text-[var(--tpl-text)]\"\n rows=\"3\"\n @keydown=\"handleEditKeydown($event, thread.id)\"\n />\n <div class=\"tpl:mt-1.5 tpl:flex tpl:gap-1.5\">\n <button\n class=\"tpl:rounded-md tpl:px-2.5 tpl:py-1 tpl:text-xs tpl:font-medium tpl:transition-colors tpl:duration-150 tpl:bg-[var(--tpl-primary)] tpl:text-[var(--tpl-bg)]\"\n :disabled=\"!editBody.trim() || comments.isSubmitting.value\"\n @click=\"handleEdit(thread.id)\"\n >\n {{ cloudT.comments.save }}\n </button>\n <button\n class=\"tpl:rounded-md tpl:px-2.5 tpl:py-1 tpl:text-xs tpl:font-medium tpl:transition-colors tpl:duration-150 tpl:text-[var(--tpl-text-muted)]\"\n @click=\"cancelEdit()\"\n >\n {{ cloudT.comments.cancel }}\n </button>\n </div>\n </div>\n <p\n v-else\n class=\"tpl:mt-1.5 tpl:text-xs tpl:leading-relaxed tpl:whitespace-pre-wrap tpl:text-[var(--tpl-text)]\"\n >\n {{ thread.body }}\n </p>\n\n <!-- Delete confirmation -->\n <div\n v-if=\"deletingId === thread.id\"\n class=\"tpl:mt-2 tpl:flex tpl:items-center tpl:gap-2 tpl:rounded-md tpl:px-2.5 tpl:py-2 tpl:text-xs tpl:bg-[var(--tpl-danger-light)] tpl:text-[var(--tpl-danger)]\"\n >\n <span class=\"tpl:flex-1\">\n {{ cloudT.comments.deleteConfirm }}\n </span>\n <button\n class=\"tpl:rounded tpl:px-2 tpl:py-0.5 tpl:text-xs tpl:font-medium tpl:bg-[var(--tpl-danger)] tpl:text-[var(--tpl-bg)]\"\n @click=\"handleDelete(thread.id)\"\n >\n {{ cloudT.comments.delete }}\n </button>\n <button\n class=\"tpl:text-xs tpl:font-medium tpl:text-[var(--tpl-text-muted)]\"\n @click=\"cancelDelete()\"\n >\n {{ cloudT.comments.cancel }}\n </button>\n </div>\n\n <!-- Thread actions -->\n <div\n v-if=\"editingId !== thread.id && deletingId !== thread.id\"\n class=\"tpl:mt-2 tpl:flex tpl:items-center tpl:gap-2\"\n >\n <button\n class=\"tpl-comment-action tpl:rounded tpl:p-1 tpl:transition-colors tpl:duration-150\"\n :title=\"cloudT.comments.reply\"\n @click=\"startReply(thread.id)\"\n >\n <Reply\n :size=\"13\"\n :stroke-width=\"2\"\n class=\"tpl:text-[var(--tpl-primary)]\"\n />\n </button>\n <button\n v-if=\"(thread.replies?.length ?? 0) > 0\"\n class=\"tpl:flex tpl:items-center tpl:gap-0.5 tpl:text-[11px] tpl:font-medium tpl:transition-colors tpl:duration-150 tpl:text-[var(--tpl-text-muted)]\"\n @click=\"toggleThread(thread.id)\"\n >\n <template v-if=\"expandedThreads.has(thread.id)\">\n <ChevronUp :size=\"11\" :stroke-width=\"2\" />\n </template>\n <template v-else>\n <ChevronDown :size=\"11\" :stroke-width=\"2\" />\n </template>\n {{\n (thread.replies?.length ?? 0) === 1\n ? format(cloudT.comments.replyOne, {\n count: String(thread.replies?.length ?? 0),\n })\n : format(cloudT.comments.replyMany, {\n count: String(thread.replies?.length ?? 0),\n })\n }}\n </button>\n </div>\n </div>\n\n <!-- Replies -->\n <Transition name=\"tpl-replies\">\n <div\n v-if=\"\n expandedThreads.has(thread.id) &&\n (thread.replies?.length ?? 0) > 0\n \"\n class=\"tpl-comment-replies tpl:ml-5 tpl:pl-3 tpl:pt-2\"\n >\n <div\n v-for=\"(reply, index) in thread.replies\"\n :key=\"reply.id\"\n class=\"tpl-comment-reply-card tpl:relative tpl:rounded-lg tpl:border tpl:px-3.5 tpl:py-2.5\"\n :class=\"\n index < (thread.replies?.length ?? 0) - 1 ? 'tpl:mb-2' : ''\n \"\n >\n <div\n class=\"tpl:flex tpl:items-start tpl:justify-between tpl:gap-2\"\n >\n <div class=\"tpl:flex tpl:items-center tpl:gap-1.5\">\n <span\n class=\"tpl:text-xs tpl:font-semibold tpl:text-[var(--tpl-text)]\"\n >\n {{\n isOwnComment(reply)\n ? cloudT.comments.ownedByYou\n : reply.author_name\n }}\n </span>\n <span\n class=\"tpl:text-[10px] tpl:text-[var(--tpl-text-dim)]\"\n >\n {{ formatTime(reply.created_at) }}\n </span>\n <span\n v-if=\"reply.updated_at !== reply.created_at\"\n class=\"tpl:text-[10px] tpl:italic tpl:text-[var(--tpl-text-dim)]\"\n >\n ({{ cloudT.comments.edited }})\n </span>\n </div>\n <div class=\"tpl:flex tpl:items-center tpl:gap-0.5\">\n <button\n v-if=\"isOwnComment(reply)\"\n class=\"tpl-comment-action tpl:rounded tpl:p-1 tpl:transition-colors tpl:duration-150\"\n :title=\"cloudT.comments.edit\"\n @click=\"startEdit(reply)\"\n >\n <Pencil :size=\"11\" :stroke-width=\"2\" />\n </button>\n <button\n v-if=\"isOwnComment(reply)\"\n class=\"tpl-comment-action tpl-comment-delete tpl:rounded tpl:p-1 tpl:transition-colors tpl:duration-150\"\n :title=\"cloudT.comments.delete\"\n @click=\"confirmDelete(reply.id)\"\n >\n <Trash2 :size=\"11\" :stroke-width=\"2\" />\n </button>\n </div>\n </div>\n\n <!-- Reply body (or edit mode) -->\n <div v-if=\"editingId === reply.id\" class=\"tpl:mt-1.5\">\n <textarea\n v-model=\"editBody\"\n class=\"tpl:w-full tpl:resize-none tpl:rounded-md tpl:border tpl:px-2.5 tpl:py-2 tpl:font-sans tpl:text-xs tpl:outline-none tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:text-[var(--tpl-text)]\"\n rows=\"2\"\n @keydown=\"handleEditKeydown($event, reply.id)\"\n />\n <div class=\"tpl:mt-1.5 tpl:flex tpl:gap-1.5\">\n <button\n class=\"tpl:rounded-md tpl:px-2.5 tpl:py-1 tpl:text-xs tpl:font-medium tpl:bg-[var(--tpl-primary)] tpl:text-[var(--tpl-bg)]\"\n :disabled=\"\n !editBody.trim() || comments.isSubmitting.value\n \"\n @click=\"handleEdit(reply.id)\"\n >\n {{ cloudT.comments.save }}\n </button>\n <button\n class=\"tpl:rounded-md tpl:px-2.5 tpl:py-1 tpl:text-xs tpl:font-medium tpl:text-[var(--tpl-text-muted)]\"\n @click=\"cancelEdit()\"\n >\n {{ cloudT.comments.cancel }}\n </button>\n </div>\n </div>\n <p\n v-else\n class=\"tpl:mt-1 tpl:text-xs tpl:leading-relaxed tpl:whitespace-pre-wrap tpl:text-[var(--tpl-text)]\"\n >\n {{ reply.body }}\n </p>\n\n <!-- Delete confirmation for reply -->\n <div\n v-if=\"deletingId === reply.id\"\n class=\"tpl:mt-2 tpl:flex tpl:items-center tpl:gap-2 tpl:rounded-md tpl:px-2.5 tpl:py-2 tpl:text-xs tpl:bg-[var(--tpl-danger-light)] tpl:text-[var(--tpl-danger)]\"\n >\n <span class=\"tpl:flex-1\">\n {{ cloudT.comments.deleteConfirm }}\n </span>\n <button\n class=\"tpl:rounded tpl:px-2 tpl:py-0.5 tpl:text-xs tpl:font-medium tpl:bg-[var(--tpl-danger)] tpl:text-[var(--tpl-bg)]\"\n @click=\"handleDelete(reply.id)\"\n >\n {{ cloudT.comments.delete }}\n </button>\n <button\n class=\"tpl:text-xs tpl:font-medium tpl:text-[var(--tpl-text-muted)]\"\n @click=\"cancelDelete()\"\n >\n {{ cloudT.comments.cancel }}\n </button>\n </div>\n </div>\n </div>\n </Transition>\n\n <!-- Reply input -->\n <Transition name=\"tpl-replies\">\n <div\n v-if=\"replyingTo === thread.id\"\n class=\"tpl-comment-replies tpl:ml-5 tpl:pl-3 tpl:pt-2\"\n >\n <div class=\"tpl:flex tpl:items-end tpl:gap-2\">\n <textarea\n v-model=\"replyBody\"\n class=\"tpl:flex-1 tpl:resize-none tpl:rounded-md tpl:border tpl:px-2.5 tpl:py-2 tpl:font-sans tpl:text-xs tpl:outline-none tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:text-[var(--tpl-text)]\"\n :placeholder=\"cloudT.comments.replyPlaceholder\"\n rows=\"2\"\n @keydown=\"handleReplyKeydown($event, thread.id)\"\n />\n <div class=\"tpl:flex tpl:shrink-0 tpl:flex-col tpl:gap-1\">\n <button\n class=\"tpl:flex tpl:items-center tpl:justify-center tpl:rounded-md tpl:p-1.5 tpl:transition-colors tpl:duration-150 tpl:disabled:opacity-40 tpl:text-[var(--tpl-primary)]\"\n :disabled=\"\n !replyBody.trim() || comments.isSubmitting.value\n \"\n @click=\"handleReply(thread.id)\"\n >\n <Send :size=\"14\" :stroke-width=\"2\" />\n </button>\n <button\n class=\"tpl:flex tpl:items-center tpl:justify-center tpl:rounded-md tpl:p-1.5 tpl:transition-colors tpl:duration-150 tpl:text-[var(--tpl-text-muted)]\"\n @click=\"cancelReply()\"\n >\n <X :size=\"14\" :stroke-width=\"2\" />\n </button>\n </div>\n </div>\n </div>\n </Transition>\n </div>\n </div>\n </div>\n\n <!-- New comment input -->\n <div class=\"tpl:border-t tpl:p-3 tpl:border-[var(--tpl-border)]\">\n <div\n v-if=\"isBlockFilterUnsaved\"\n class=\"tpl:flex tpl:min-h-[68px] tpl:items-center tpl:rounded-md tpl:px-3 tpl:py-2 tpl:text-xs tpl:bg-[var(--tpl-warning-light)] tpl:text-[var(--tpl-warning)]\"\n >\n {{ cloudT.comments.saveTemplateFirst }}\n </div>\n <div\n v-else\n class=\"tpl-comments-input-wrapper tpl:flex tpl:min-h-[68px] tpl:items-end tpl:gap-2 tpl:rounded-[var(--tpl-radius)] tpl:border tpl:px-3 tpl:py-2 tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)]\"\n >\n <textarea\n ref=\"newCommentInput\"\n v-model=\"newCommentBody\"\n class=\"tpl:max-h-24 tpl:min-h-[48px] tpl:flex-1 tpl:resize-none tpl:border-none tpl:bg-transparent tpl:font-sans tpl:text-xs tpl:outline-none tpl:text-[var(--tpl-text)]\"\n :placeholder=\"cloudT.comments.placeholder\"\n :disabled=\"comments.isSubmitting.value\"\n rows=\"2\"\n @keydown=\"handleNewCommentKeydown\"\n />\n <button\n class=\"tpl-comments-send-btn tpl:flex tpl:shrink-0 tpl:items-center tpl:justify-center tpl:rounded-md tpl:p-1.5 tpl:transition-all tpl:duration-150 tpl:disabled:opacity-40 tpl:text-[var(--tpl-primary)]\"\n :disabled=\"!newCommentBody.trim() || comments.isSubmitting.value\"\n @click=\"handleAddComment\"\n >\n <LoaderCircle\n v-if=\"comments.isSubmitting.value\"\n class=\"tpl-spinner\"\n :size=\"16\"\n :stroke-width=\"2\"\n />\n <Send v-else :size=\"16\" :stroke-width=\"2\" />\n </button>\n </div>\n </div>\n </div>\n </Transition>\n</template>\n\n<style scoped>\n.tpl-comments-slide-enter-active {\n transition: transform 280ms cubic-bezier(0.16, 1, 0.3, 1);\n}\n\n.tpl-comments-slide-leave-active {\n transition: transform 200ms cubic-bezier(0.16, 1, 0.3, 1);\n}\n\n.tpl-comments-input-wrapper:focus-within {\n border-color: var(--tpl-primary);\n box-shadow: var(--tpl-ring);\n}\n\n.tpl-comments-send-btn:not(:disabled):hover {\n transform: scale(1.05);\n}\n\n.tpl-comment-filter {\n color: var(--tpl-text-muted);\n background-color: transparent;\n}\n\n.tpl-comment-filter:hover {\n background-color: var(--tpl-bg-hover);\n color: var(--tpl-text);\n}\n\n.tpl-comment-filter--active {\n background-color: var(--tpl-primary-light);\n color: var(--tpl-primary);\n}\n\n.tpl-comment-action {\n color: var(--tpl-text-dim);\n background-color: transparent;\n}\n\n.tpl-comment-action:hover {\n background-color: var(--tpl-bg-hover);\n color: var(--tpl-text);\n}\n\n.tpl-comment-delete:hover {\n background-color: var(--tpl-danger-light);\n color: var(--tpl-danger);\n}\n\n.tpl-comment-card {\n border-color: var(--tpl-border);\n background-color: var(--tpl-bg);\n}\n\n.tpl-comment-reply-card {\n border-color: var(--tpl-border);\n background-color: var(--tpl-bg);\n}\n\n.tpl-resolve-enter-active,\n.tpl-resolve-leave-active {\n transition:\n opacity 0.3s ease,\n transform 0.3s ease;\n}\n\n.tpl-resolve-enter-from,\n.tpl-resolve-leave-to {\n opacity: 0;\n transform: translateY(-4px);\n}\n\n.tpl-resolve-icon {\n transition:\n color 0.3s ease,\n transform 0.2s ease;\n}\n\n.tpl-resolve-icon:active {\n transform: scale(1.3);\n}\n\n.tpl-replies-enter-active,\n.tpl-replies-leave-active {\n transition:\n opacity 0.25s ease,\n transform 0.25s ease;\n}\n\n.tpl-replies-enter-from,\n.tpl-replies-leave-to {\n opacity: 0;\n transform: translateY(-4px);\n}\n</style>\n","<script setup lang=\"ts\">\nimport { formatRelativeTime } from \"../../utils/formatRelativeTime\";\nimport {\n EDITOR_KEY,\n AUTH_MANAGER_KEY,\n COMMENTS_KEY,\n requireInject,\n} from \"../../keys\";\nimport { useI18n } from \"../../composables/useI18n\";\nimport { useCloudI18nStrict } from \"../../composables/useCloudI18n\";\nimport type { Comment } from \"@templatical/types\";\nimport {\n Check,\n CircleCheck,\n ChevronDown,\n ChevronUp,\n LoaderCircle,\n MessageCircle,\n Pencil,\n Reply,\n Send,\n Trash2,\n X,\n} from \"@lucide/vue\";\nimport { computed, nextTick, ref, watch } from \"vue\";\n\nconst props = defineProps<{\n visible: boolean;\n}>();\n\nconst emit = defineEmits<{\n (e: \"close\"): void;\n (e: \"filterBlock\", blockId: string | null): void;\n}>();\n\nconst { format } = useI18n();\nconst { t: cloudT } = useCloudI18nStrict();\nconst editor = requireInject(EDITOR_KEY, \"CommentsSidebar\");\nconst authManager = requireInject(AUTH_MANAGER_KEY, \"CommentsSidebar\");\nconst comments = requireInject(COMMENTS_KEY, \"CommentsSidebar\");\n\ntype FilterMode = \"all\" | \"unresolved\" | \"block\";\nconst filterMode = ref<FilterMode>(\"unresolved\");\nconst filterBlockId = ref<string | null>(null);\n\nconst newCommentBody = ref(\"\");\nconst replyingTo = ref<string | null>(null);\nconst replyBody = ref(\"\");\nconst editingId = ref<string | null>(null);\nconst editBody = ref(\"\");\nconst expandedThreads = ref<Set<string>>(new Set());\nconst deletingId = ref<string | null>(null);\nconst newCommentInput = ref<HTMLTextAreaElement | null>(null);\n\nconst currentUserId = computed(() => authManager.userConfig?.id ?? null);\n\nconst allBlockIds = computed<Set<string>>(() => {\n const ids = new Set<string>();\n for (const block of editor.content.value.blocks) {\n ids.add(block.id);\n if (block.type === \"section\") {\n for (const column of block.children) {\n for (const child of column) {\n ids.add(child.id);\n }\n }\n }\n }\n return ids;\n});\n\nfunction isBlockMissing(blockId: string | null): boolean {\n if (!blockId) {\n return false;\n }\n return !allBlockIds.value.has(blockId);\n}\n\nconst isBlockFilterUnsaved = computed(() => {\n if (filterMode.value !== \"block\" || !filterBlockId.value) {\n return false;\n }\n return !(editor.savedBlockIds?.value.has(filterBlockId.value) ?? true);\n});\n\nconst filteredComments = computed(() => {\n let items = comments.comments.value;\n\n if (filterMode.value === \"unresolved\") {\n items = items.filter((c) => !c.resolved_at);\n } else if (filterMode.value === \"block\" && filterBlockId.value) {\n items = items.filter((c) => c.block_id === filterBlockId.value);\n }\n\n return items;\n});\n\nwatch(\n () => props.visible,\n (isVisible) => {\n if (isVisible) {\n comments.loadComments();\n }\n },\n);\n\nwatch(\n () => editor.state.selectedBlockId,\n (newBlockId) => {\n if (filterMode.value === \"block\" && newBlockId) {\n filterBlockId.value = newBlockId;\n }\n },\n);\n\nfunction setFilter(mode: FilterMode, blockId?: string): void {\n filterMode.value = mode;\n filterBlockId.value = mode === \"block\" ? (blockId ?? null) : null;\n}\n\nfunction filterByBlock(blockId: string): void {\n setFilter(\"block\", blockId);\n emit(\"filterBlock\", blockId);\n}\n\nfunction toggleThread(threadId: string): void {\n if (expandedThreads.value.has(threadId)) {\n expandedThreads.value.delete(threadId);\n } else {\n expandedThreads.value.add(threadId);\n }\n}\n\nfunction startReply(threadId: string): void {\n replyingTo.value = threadId;\n replyBody.value = \"\";\n editingId.value = null;\n\n if (!expandedThreads.value.has(threadId)) {\n expandedThreads.value.add(threadId);\n }\n}\n\nfunction cancelReply(): void {\n replyingTo.value = null;\n replyBody.value = \"\";\n}\n\nfunction startEdit(comment: Comment): void {\n editingId.value = comment.id;\n editBody.value = comment.body;\n replyingTo.value = null;\n}\n\nfunction cancelEdit(): void {\n editingId.value = null;\n editBody.value = \"\";\n}\n\nfunction confirmDelete(commentId: string): void {\n deletingId.value = commentId;\n}\n\nfunction cancelDelete(): void {\n deletingId.value = null;\n}\n\nasync function handleAddComment(): Promise<void> {\n const body = newCommentBody.value.trim();\n if (!body) {\n return;\n }\n\n const blockId =\n filterMode.value === \"block\" ? filterBlockId.value : undefined;\n await comments.addComment(body, blockId ?? undefined);\n newCommentBody.value = \"\";\n}\n\nasync function handleReply(parentId: string): Promise<void> {\n const body = replyBody.value.trim();\n if (!body) {\n return;\n }\n\n const parent = comments.comments.value.find((c) => c.id === parentId);\n await comments.addComment(body, parent?.block_id ?? undefined, parentId);\n replyingTo.value = null;\n replyBody.value = \"\";\n}\n\nasync function handleEdit(commentId: string): Promise<void> {\n const body = editBody.value.trim();\n if (!body) {\n return;\n }\n\n await comments.editComment(commentId, body);\n editingId.value = null;\n editBody.value = \"\";\n}\n\nasync function handleDelete(commentId: string): Promise<void> {\n await comments.removeComment(commentId);\n deletingId.value = null;\n}\n\nasync function handleResolve(commentId: string): Promise<void> {\n await comments.toggleResolve(commentId);\n}\n\nfunction isOwnComment(comment: Comment): boolean {\n return comment.author_identifier === currentUserId.value;\n}\n\nfunction formatTime(dateString: string): string {\n return formatRelativeTime(dateString, cloudT.snapshotHistory) ?? dateString;\n}\n\nfunction handleNewCommentKeydown(event: KeyboardEvent): void {\n if (event.key === \"Enter\" && !event.shiftKey) {\n event.preventDefault();\n handleAddComment();\n }\n}\n\nfunction handleReplyKeydown(event: KeyboardEvent, parentId: string): void {\n if (event.key === \"Enter\" && !event.shiftKey) {\n event.preventDefault();\n handleReply(parentId);\n }\n}\n\nfunction handleEditKeydown(event: KeyboardEvent, commentId: string): void {\n if (event.key === \"Enter\" && !event.shiftKey) {\n event.preventDefault();\n handleEdit(commentId);\n }\n if (event.key === \"Escape\") {\n cancelEdit();\n }\n}\n\nfunction focusNewComment(): void {\n nextTick(() => {\n newCommentInput.value?.focus();\n });\n}\n\ndefineExpose({ filterByBlock, focusNewComment });\n</script>\n\n<template>\n <Transition\n enter-active-class=\"tpl-comments-slide-enter-active\"\n enter-from-class=\"tpl:translate-x-full\"\n enter-to-class=\"tpl:translate-x-0\"\n leave-active-class=\"tpl-comments-slide-leave-active\"\n leave-from-class=\"tpl:translate-x-0\"\n leave-to-class=\"tpl:translate-x-full\"\n >\n <div\n v-if=\"visible\"\n class=\"tpl-comments-sidebar tpl:absolute tpl:top-14 tpl:right-0 tpl:bottom-0 tpl:z-panel tpl:flex tpl:w-[360px] tpl:flex-col tpl:border-l tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg-elevated)]\"\n >\n <!-- Header -->\n <div\n class=\"tpl:flex tpl:items-center tpl:justify-between tpl:border-b tpl:border-[var(--tpl-border)] tpl:px-4 tpl:py-3\"\n >\n <div\n class=\"tpl:flex tpl:items-center tpl:gap-1.5 tpl:text-sm tpl:font-medium tpl:text-[var(--tpl-text)]\"\n >\n <MessageCircle :size=\"13\" :stroke-width=\"2\" />\n <span>{{ cloudT.comments.title }}</span>\n <span\n v-if=\"comments.unresolvedCount.value > 0\"\n class=\"tpl:ml-1 tpl:inline-flex tpl:size-5 tpl:items-center tpl:justify-center tpl:rounded-full tpl:text-[10px] tpl:font-semibold tpl:bg-[var(--tpl-primary)] tpl:text-[var(--tpl-bg)]\"\n >\n {{ comments.unresolvedCount.value }}\n </span>\n </div>\n <button\n class=\"tpl:rounded-md tpl:p-0.5 tpl:transition-colors tpl:duration-150 tpl:text-[var(--tpl-text-muted)]\"\n @click=\"emit('close')\"\n >\n <X :size=\"14\" :stroke-width=\"2\" />\n </button>\n </div>\n\n <!-- Filter bar -->\n <div\n class=\"tpl:flex tpl:gap-1 tpl:border-b tpl:border-[var(--tpl-border)] tpl:px-4 tpl:py-2\"\n >\n <button\n class=\"tpl-comment-filter tpl:rounded-md tpl:px-2.5 tpl:py-1 tpl:text-xs tpl:font-medium tpl:transition-colors tpl:duration-150\"\n :class=\"\n filterMode === 'unresolved' ? 'tpl-comment-filter--active' : ''\n \"\n @click=\"setFilter('unresolved')\"\n >\n {{ cloudT.comments.filterUnresolved }}\n </button>\n <button\n class=\"tpl-comment-filter tpl:rounded-md tpl:px-2.5 tpl:py-1 tpl:text-xs tpl:font-medium tpl:transition-colors tpl:duration-150\"\n :class=\"filterMode === 'all' ? 'tpl-comment-filter--active' : ''\"\n @click=\"setFilter('all')\"\n >\n {{ cloudT.comments.filterAll }}\n </button>\n <button\n v-if=\"editor.state.selectedBlockId\"\n class=\"tpl-comment-filter tpl:rounded-md tpl:px-2.5 tpl:py-1 tpl:text-xs tpl:font-medium tpl:transition-colors tpl:duration-150\"\n :class=\"filterMode === 'block' ? 'tpl-comment-filter--active' : ''\"\n @click=\"setFilter('block', editor.state.selectedBlockId ?? undefined)\"\n >\n {{ cloudT.comments.filterBlock }}\n </button>\n </div>\n\n <!-- Thread list -->\n <div class=\"tpl:flex-1 tpl:overflow-y-auto\">\n <!-- Loading state -->\n <div\n v-if=\"comments.isLoading.value\"\n class=\"tpl:flex tpl:h-full tpl:items-center tpl:justify-center\"\n >\n <LoaderCircle\n class=\"tpl-spinner tpl:text-[var(--tpl-text-muted)]\"\n :size=\"24\"\n :stroke-width=\"2\"\n />\n </div>\n\n <!-- Empty state -->\n <div\n v-else-if=\"filteredComments.length === 0\"\n class=\"tpl:flex tpl:h-full tpl:flex-col tpl:items-center tpl:justify-center tpl:gap-3 tpl:px-6 tpl:text-center\"\n >\n <MessageCircle\n :size=\"32\"\n :stroke-width=\"1.5\"\n class=\"tpl:text-[var(--tpl-text-dim)]\"\n />\n <p\n class=\"tpl:max-w-[240px] tpl:text-sm tpl:text-[var(--tpl-text-muted)]\"\n >\n {{\n filterMode === \"all\"\n ? cloudT.comments.noCommentsHint\n : cloudT.comments.noComments\n }}\n </p>\n </div>\n\n <!-- Comment threads -->\n <div v-else class=\"tpl:flex tpl:flex-col tpl:gap-3 tpl:p-3\">\n <div\n v-for=\"thread in filteredComments\"\n :key=\"thread.id\"\n class=\"tpl-comment-thread\"\n >\n <!-- Root comment card -->\n <div\n class=\"tpl-comment-card tpl:rounded-lg tpl:border tpl:px-3.5 tpl:py-3\"\n >\n <!-- Comment header -->\n <div\n class=\"tpl:flex tpl:items-start tpl:justify-between tpl:gap-2\"\n >\n <div class=\"tpl:flex tpl:items-center tpl:gap-1.5\">\n <span\n class=\"tpl:text-xs tpl:font-semibold tpl:text-[var(--tpl-text)]\"\n >\n {{\n isOwnComment(thread)\n ? cloudT.comments.ownedByYou\n : thread.author_name\n }}\n </span>\n <span class=\"tpl:text-[10px] tpl:text-[var(--tpl-text-dim)]\">\n {{ formatTime(thread.created_at) }}\n </span>\n <span\n v-if=\"thread.updated_at !== thread.created_at\"\n class=\"tpl:text-[10px] tpl:italic tpl:text-[var(--tpl-text-dim)]\"\n >\n ({{ cloudT.comments.edited }})\n </span>\n </div>\n <div class=\"tpl:flex tpl:items-center tpl:gap-0.5\">\n <!-- Resolve toggle -->\n <button\n class=\"tpl-comment-action tpl:rounded tpl:p-1 tpl:transition-colors tpl:duration-150\"\n :title=\"\n thread.resolved_at\n ? cloudT.comments.unresolve\n : cloudT.comments.resolve\n \"\n @click=\"handleResolve(thread.id)\"\n >\n <CircleCheck\n :size=\"13\"\n :stroke-width=\"2\"\n class=\"tpl-resolve-icon\"\n :style=\"{\n color: thread.resolved_at\n ? 'var(--tpl-primary)'\n : undefined,\n }\"\n />\n </button>\n <!-- Edit (own only) -->\n <button\n v-if=\"isOwnComment(thread)\"\n class=\"tpl-comment-action tpl:rounded tpl:p-1 tpl:transition-colors tpl:duration-150\"\n :title=\"cloudT.comments.edit\"\n @click=\"startEdit(thread)\"\n >\n <Pencil :size=\"12\" :stroke-width=\"2\" />\n </button>\n <!-- Delete (own only) -->\n <button\n v-if=\"isOwnComment(thread)\"\n class=\"tpl-comment-action tpl-comment-delete tpl:rounded tpl:p-1 tpl:transition-colors tpl:duration-150\"\n :title=\"cloudT.comments.delete\"\n @click=\"confirmDelete(thread.id)\"\n >\n <Trash2 :size=\"12\" :stroke-width=\"2\" />\n </button>\n </div>\n </div>\n\n <!-- Resolved badge -->\n <Transition name=\"tpl-resolve\">\n <div\n v-if=\"thread.resolved_at\"\n class=\"tpl:mt-1 tpl:flex tpl:items-center tpl:gap-1 tpl:text-[10px] tpl:text-[var(--tpl-primary)]\"\n >\n <Check :size=\"10\" :stroke-width=\"2.5\" />\n <span>\n {{\n format(cloudT.comments.resolvedBy, {\n name: thread.resolved_by_name ?? \"\",\n })\n }}\n </span>\n </div>\n </Transition>\n\n <!-- Block anchor indicator -->\n <span\n v-if=\"thread.block_id && isBlockMissing(thread.block_id)\"\n class=\"tpl:mt-1 tpl:inline-flex tpl:items-center tpl:gap-1 tpl:rounded tpl:px-1.5 tpl:py-0.5 tpl:text-[10px] tpl:font-medium tpl:bg-[var(--tpl-warning-light)] tpl:text-[var(--tpl-warning)]\"\n >\n {{ cloudT.comments.missingBlock }}\n </span>\n <button\n v-else-if=\"thread.block_id\"\n class=\"tpl:mt-1 tpl:inline-flex tpl:items-center tpl:gap-1 tpl:rounded tpl:px-1.5 tpl:py-0.5 tpl:text-[10px] tpl:font-medium tpl:transition-colors tpl:duration-150 tpl:bg-[var(--tpl-bg-hover)] tpl:text-[var(--tpl-text-muted)]\"\n @click=\"editor.selectBlock(thread.block_id ?? '')\"\n >\n Block\n </button>\n\n <!-- Comment body (or edit mode) -->\n <div v-if=\"editingId === thread.id\" class=\"tpl:mt-2\">\n <textarea\n v-model=\"editBody\"\n class=\"tpl:w-full tpl:resize-none tpl:rounded-md tpl:border tpl:px-2.5 tpl:py-2 tpl:font-sans tpl:text-xs tpl:outline-none tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:text-[var(--tpl-text)]\"\n rows=\"3\"\n @keydown=\"handleEditKeydown($event, thread.id)\"\n />\n <div class=\"tpl:mt-1.5 tpl:flex tpl:gap-1.5\">\n <button\n class=\"tpl:rounded-md tpl:px-2.5 tpl:py-1 tpl:text-xs tpl:font-medium tpl:transition-colors tpl:duration-150 tpl:bg-[var(--tpl-primary)] tpl:text-[var(--tpl-bg)]\"\n :disabled=\"!editBody.trim() || comments.isSubmitting.value\"\n @click=\"handleEdit(thread.id)\"\n >\n {{ cloudT.comments.save }}\n </button>\n <button\n class=\"tpl:rounded-md tpl:px-2.5 tpl:py-1 tpl:text-xs tpl:font-medium tpl:transition-colors tpl:duration-150 tpl:text-[var(--tpl-text-muted)]\"\n @click=\"cancelEdit()\"\n >\n {{ cloudT.comments.cancel }}\n </button>\n </div>\n </div>\n <p\n v-else\n class=\"tpl:mt-1.5 tpl:text-xs tpl:leading-relaxed tpl:whitespace-pre-wrap tpl:text-[var(--tpl-text)]\"\n >\n {{ thread.body }}\n </p>\n\n <!-- Delete confirmation -->\n <div\n v-if=\"deletingId === thread.id\"\n class=\"tpl:mt-2 tpl:flex tpl:items-center tpl:gap-2 tpl:rounded-md tpl:px-2.5 tpl:py-2 tpl:text-xs tpl:bg-[var(--tpl-danger-light)] tpl:text-[var(--tpl-danger)]\"\n >\n <span class=\"tpl:flex-1\">\n {{ cloudT.comments.deleteConfirm }}\n </span>\n <button\n class=\"tpl:rounded tpl:px-2 tpl:py-0.5 tpl:text-xs tpl:font-medium tpl:bg-[var(--tpl-danger)] tpl:text-[var(--tpl-bg)]\"\n @click=\"handleDelete(thread.id)\"\n >\n {{ cloudT.comments.delete }}\n </button>\n <button\n class=\"tpl:text-xs tpl:font-medium tpl:text-[var(--tpl-text-muted)]\"\n @click=\"cancelDelete()\"\n >\n {{ cloudT.comments.cancel }}\n </button>\n </div>\n\n <!-- Thread actions -->\n <div\n v-if=\"editingId !== thread.id && deletingId !== thread.id\"\n class=\"tpl:mt-2 tpl:flex tpl:items-center tpl:gap-2\"\n >\n <button\n class=\"tpl-comment-action tpl:rounded tpl:p-1 tpl:transition-colors tpl:duration-150\"\n :title=\"cloudT.comments.reply\"\n @click=\"startReply(thread.id)\"\n >\n <Reply\n :size=\"13\"\n :stroke-width=\"2\"\n class=\"tpl:text-[var(--tpl-primary)]\"\n />\n </button>\n <button\n v-if=\"(thread.replies?.length ?? 0) > 0\"\n class=\"tpl:flex tpl:items-center tpl:gap-0.5 tpl:text-[11px] tpl:font-medium tpl:transition-colors tpl:duration-150 tpl:text-[var(--tpl-text-muted)]\"\n @click=\"toggleThread(thread.id)\"\n >\n <template v-if=\"expandedThreads.has(thread.id)\">\n <ChevronUp :size=\"11\" :stroke-width=\"2\" />\n </template>\n <template v-else>\n <ChevronDown :size=\"11\" :stroke-width=\"2\" />\n </template>\n {{\n (thread.replies?.length ?? 0) === 1\n ? format(cloudT.comments.replyOne, {\n count: String(thread.replies?.length ?? 0),\n })\n : format(cloudT.comments.replyMany, {\n count: String(thread.replies?.length ?? 0),\n })\n }}\n </button>\n </div>\n </div>\n\n <!-- Replies -->\n <Transition name=\"tpl-replies\">\n <div\n v-if=\"\n expandedThreads.has(thread.id) &&\n (thread.replies?.length ?? 0) > 0\n \"\n class=\"tpl-comment-replies tpl:ml-5 tpl:pl-3 tpl:pt-2\"\n >\n <div\n v-for=\"(reply, index) in thread.replies\"\n :key=\"reply.id\"\n class=\"tpl-comment-reply-card tpl:relative tpl:rounded-lg tpl:border tpl:px-3.5 tpl:py-2.5\"\n :class=\"\n index < (thread.replies?.length ?? 0) - 1 ? 'tpl:mb-2' : ''\n \"\n >\n <div\n class=\"tpl:flex tpl:items-start tpl:justify-between tpl:gap-2\"\n >\n <div class=\"tpl:flex tpl:items-center tpl:gap-1.5\">\n <span\n class=\"tpl:text-xs tpl:font-semibold tpl:text-[var(--tpl-text)]\"\n >\n {{\n isOwnComment(reply)\n ? cloudT.comments.ownedByYou\n : reply.author_name\n }}\n </span>\n <span\n class=\"tpl:text-[10px] tpl:text-[var(--tpl-text-dim)]\"\n >\n {{ formatTime(reply.created_at) }}\n </span>\n <span\n v-if=\"reply.updated_at !== reply.created_at\"\n class=\"tpl:text-[10px] tpl:italic tpl:text-[var(--tpl-text-dim)]\"\n >\n ({{ cloudT.comments.edited }})\n </span>\n </div>\n <div class=\"tpl:flex tpl:items-center tpl:gap-0.5\">\n <button\n v-if=\"isOwnComment(reply)\"\n class=\"tpl-comment-action tpl:rounded tpl:p-1 tpl:transition-colors tpl:duration-150\"\n :title=\"cloudT.comments.edit\"\n @click=\"startEdit(reply)\"\n >\n <Pencil :size=\"11\" :stroke-width=\"2\" />\n </button>\n <button\n v-if=\"isOwnComment(reply)\"\n class=\"tpl-comment-action tpl-comment-delete tpl:rounded tpl:p-1 tpl:transition-colors tpl:duration-150\"\n :title=\"cloudT.comments.delete\"\n @click=\"confirmDelete(reply.id)\"\n >\n <Trash2 :size=\"11\" :stroke-width=\"2\" />\n </button>\n </div>\n </div>\n\n <!-- Reply body (or edit mode) -->\n <div v-if=\"editingId === reply.id\" class=\"tpl:mt-1.5\">\n <textarea\n v-model=\"editBody\"\n class=\"tpl:w-full tpl:resize-none tpl:rounded-md tpl:border tpl:px-2.5 tpl:py-2 tpl:font-sans tpl:text-xs tpl:outline-none tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:text-[var(--tpl-text)]\"\n rows=\"2\"\n @keydown=\"handleEditKeydown($event, reply.id)\"\n />\n <div class=\"tpl:mt-1.5 tpl:flex tpl:gap-1.5\">\n <button\n class=\"tpl:rounded-md tpl:px-2.5 tpl:py-1 tpl:text-xs tpl:font-medium tpl:bg-[var(--tpl-primary)] tpl:text-[var(--tpl-bg)]\"\n :disabled=\"\n !editBody.trim() || comments.isSubmitting.value\n \"\n @click=\"handleEdit(reply.id)\"\n >\n {{ cloudT.comments.save }}\n </button>\n <button\n class=\"tpl:rounded-md tpl:px-2.5 tpl:py-1 tpl:text-xs tpl:font-medium tpl:text-[var(--tpl-text-muted)]\"\n @click=\"cancelEdit()\"\n >\n {{ cloudT.comments.cancel }}\n </button>\n </div>\n </div>\n <p\n v-else\n class=\"tpl:mt-1 tpl:text-xs tpl:leading-relaxed tpl:whitespace-pre-wrap tpl:text-[var(--tpl-text)]\"\n >\n {{ reply.body }}\n </p>\n\n <!-- Delete confirmation for reply -->\n <div\n v-if=\"deletingId === reply.id\"\n class=\"tpl:mt-2 tpl:flex tpl:items-center tpl:gap-2 tpl:rounded-md tpl:px-2.5 tpl:py-2 tpl:text-xs tpl:bg-[var(--tpl-danger-light)] tpl:text-[var(--tpl-danger)]\"\n >\n <span class=\"tpl:flex-1\">\n {{ cloudT.comments.deleteConfirm }}\n </span>\n <button\n class=\"tpl:rounded tpl:px-2 tpl:py-0.5 tpl:text-xs tpl:font-medium tpl:bg-[var(--tpl-danger)] tpl:text-[var(--tpl-bg)]\"\n @click=\"handleDelete(reply.id)\"\n >\n {{ cloudT.comments.delete }}\n </button>\n <button\n class=\"tpl:text-xs tpl:font-medium tpl:text-[var(--tpl-text-muted)]\"\n @click=\"cancelDelete()\"\n >\n {{ cloudT.comments.cancel }}\n </button>\n </div>\n </div>\n </div>\n </Transition>\n\n <!-- Reply input -->\n <Transition name=\"tpl-replies\">\n <div\n v-if=\"replyingTo === thread.id\"\n class=\"tpl-comment-replies tpl:ml-5 tpl:pl-3 tpl:pt-2\"\n >\n <div class=\"tpl:flex tpl:items-end tpl:gap-2\">\n <textarea\n v-model=\"replyBody\"\n class=\"tpl:flex-1 tpl:resize-none tpl:rounded-md tpl:border tpl:px-2.5 tpl:py-2 tpl:font-sans tpl:text-xs tpl:outline-none tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:text-[var(--tpl-text)]\"\n :placeholder=\"cloudT.comments.replyPlaceholder\"\n rows=\"2\"\n @keydown=\"handleReplyKeydown($event, thread.id)\"\n />\n <div class=\"tpl:flex tpl:shrink-0 tpl:flex-col tpl:gap-1\">\n <button\n class=\"tpl:flex tpl:items-center tpl:justify-center tpl:rounded-md tpl:p-1.5 tpl:transition-colors tpl:duration-150 tpl:disabled:opacity-40 tpl:text-[var(--tpl-primary)]\"\n :disabled=\"\n !replyBody.trim() || comments.isSubmitting.value\n \"\n @click=\"handleReply(thread.id)\"\n >\n <Send :size=\"14\" :stroke-width=\"2\" />\n </button>\n <button\n class=\"tpl:flex tpl:items-center tpl:justify-center tpl:rounded-md tpl:p-1.5 tpl:transition-colors tpl:duration-150 tpl:text-[var(--tpl-text-muted)]\"\n @click=\"cancelReply()\"\n >\n <X :size=\"14\" :stroke-width=\"2\" />\n </button>\n </div>\n </div>\n </div>\n </Transition>\n </div>\n </div>\n </div>\n\n <!-- New comment input -->\n <div class=\"tpl:border-t tpl:p-3 tpl:border-[var(--tpl-border)]\">\n <div\n v-if=\"isBlockFilterUnsaved\"\n class=\"tpl:flex tpl:min-h-[68px] tpl:items-center tpl:rounded-md tpl:px-3 tpl:py-2 tpl:text-xs tpl:bg-[var(--tpl-warning-light)] tpl:text-[var(--tpl-warning)]\"\n >\n {{ cloudT.comments.saveTemplateFirst }}\n </div>\n <div\n v-else\n class=\"tpl-comments-input-wrapper tpl:flex tpl:min-h-[68px] tpl:items-end tpl:gap-2 tpl:rounded-[var(--tpl-radius)] tpl:border tpl:px-3 tpl:py-2 tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)]\"\n >\n <textarea\n ref=\"newCommentInput\"\n v-model=\"newCommentBody\"\n class=\"tpl:max-h-24 tpl:min-h-[48px] tpl:flex-1 tpl:resize-none tpl:border-none tpl:bg-transparent tpl:font-sans tpl:text-xs tpl:outline-none tpl:text-[var(--tpl-text)]\"\n :placeholder=\"cloudT.comments.placeholder\"\n :disabled=\"comments.isSubmitting.value\"\n rows=\"2\"\n @keydown=\"handleNewCommentKeydown\"\n />\n <button\n class=\"tpl-comments-send-btn tpl:flex tpl:shrink-0 tpl:items-center tpl:justify-center tpl:rounded-md tpl:p-1.5 tpl:transition-all tpl:duration-150 tpl:disabled:opacity-40 tpl:text-[var(--tpl-primary)]\"\n :disabled=\"!newCommentBody.trim() || comments.isSubmitting.value\"\n @click=\"handleAddComment\"\n >\n <LoaderCircle\n v-if=\"comments.isSubmitting.value\"\n class=\"tpl-spinner\"\n :size=\"16\"\n :stroke-width=\"2\"\n />\n <Send v-else :size=\"16\" :stroke-width=\"2\" />\n </button>\n </div>\n </div>\n </div>\n </Transition>\n</template>\n\n<style scoped>\n.tpl-comments-slide-enter-active {\n transition: transform 280ms cubic-bezier(0.16, 1, 0.3, 1);\n}\n\n.tpl-comments-slide-leave-active {\n transition: transform 200ms cubic-bezier(0.16, 1, 0.3, 1);\n}\n\n.tpl-comments-input-wrapper:focus-within {\n border-color: var(--tpl-primary);\n box-shadow: var(--tpl-ring);\n}\n\n.tpl-comments-send-btn:not(:disabled):hover {\n transform: scale(1.05);\n}\n\n.tpl-comment-filter {\n color: var(--tpl-text-muted);\n background-color: transparent;\n}\n\n.tpl-comment-filter:hover {\n background-color: var(--tpl-bg-hover);\n color: var(--tpl-text);\n}\n\n.tpl-comment-filter--active {\n background-color: var(--tpl-primary-light);\n color: var(--tpl-primary);\n}\n\n.tpl-comment-action {\n color: var(--tpl-text-dim);\n background-color: transparent;\n}\n\n.tpl-comment-action:hover {\n background-color: var(--tpl-bg-hover);\n color: var(--tpl-text);\n}\n\n.tpl-comment-delete:hover {\n background-color: var(--tpl-danger-light);\n color: var(--tpl-danger);\n}\n\n.tpl-comment-card {\n border-color: var(--tpl-border);\n background-color: var(--tpl-bg);\n}\n\n.tpl-comment-reply-card {\n border-color: var(--tpl-border);\n background-color: var(--tpl-bg);\n}\n\n.tpl-resolve-enter-active,\n.tpl-resolve-leave-active {\n transition:\n opacity 0.3s ease,\n transform 0.3s ease;\n}\n\n.tpl-resolve-enter-from,\n.tpl-resolve-leave-to {\n opacity: 0;\n transform: translateY(-4px);\n}\n\n.tpl-resolve-icon {\n transition:\n color 0.3s ease,\n transform 0.2s ease;\n}\n\n.tpl-resolve-icon:active {\n transform: scale(1.3);\n}\n\n.tpl-replies-enter-active,\n.tpl-replies-leave-active {\n transition:\n opacity 0.25s ease,\n transform 0.25s ease;\n}\n\n.tpl-replies-enter-from,\n.tpl-replies-leave-to {\n opacity: 0;\n transform: translateY(-4px);\n}\n</style>\n"],"mappings":"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"}
1
+ {"version":3,"file":"CommentsSidebar-B9bWvvbl.js","names":[],"sources":["../../../src/cloud/components/CommentsSidebar.vue","../../../src/cloud/components/CommentsSidebar.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { formatRelativeTime } from \"../../utils/formatRelativeTime\";\nimport {\n EDITOR_KEY,\n AUTH_MANAGER_KEY,\n COMMENTS_KEY,\n requireInject,\n} from \"../../keys\";\nimport { useI18n } from \"../../composables/useI18n\";\nimport { useCloudI18nStrict } from \"../../composables/useCloudI18n\";\nimport type { Comment } from \"@templatical/types\";\nimport {\n Check,\n CircleCheck,\n ChevronDown,\n ChevronUp,\n LoaderCircle,\n MessageCircle,\n Pencil,\n Reply,\n Send,\n Trash2,\n X,\n} from \"@lucide/vue\";\nimport { computed, nextTick, ref, watch } from \"vue\";\n\nconst props = defineProps<{\n visible: boolean;\n}>();\n\nconst emit = defineEmits<{\n (e: \"close\"): void;\n (e: \"filterBlock\", blockId: string | null): void;\n}>();\n\nconst { format } = useI18n();\nconst { t: cloudT } = useCloudI18nStrict();\nconst editor = requireInject(EDITOR_KEY, \"CommentsSidebar\");\nconst authManager = requireInject(AUTH_MANAGER_KEY, \"CommentsSidebar\");\nconst comments = requireInject(COMMENTS_KEY, \"CommentsSidebar\");\n\ntype FilterMode = \"all\" | \"unresolved\" | \"block\";\nconst filterMode = ref<FilterMode>(\"unresolved\");\nconst filterBlockId = ref<string | null>(null);\n\nconst newCommentBody = ref(\"\");\nconst replyingTo = ref<string | null>(null);\nconst replyBody = ref(\"\");\nconst editingId = ref<string | null>(null);\nconst editBody = ref(\"\");\nconst expandedThreads = ref<Set<string>>(new Set());\nconst deletingId = ref<string | null>(null);\nconst newCommentInput = ref<HTMLTextAreaElement | null>(null);\n\nconst currentUserId = computed(() => authManager.userConfig?.id ?? null);\n\nconst allBlockIds = computed<Set<string>>(() => {\n const ids = new Set<string>();\n for (const block of editor.content.value.blocks) {\n ids.add(block.id);\n if (block.type === \"section\") {\n for (const column of block.children) {\n for (const child of column) {\n ids.add(child.id);\n }\n }\n }\n }\n return ids;\n});\n\nfunction isBlockMissing(blockId: string | null): boolean {\n if (!blockId) {\n return false;\n }\n return !allBlockIds.value.has(blockId);\n}\n\nconst isBlockFilterUnsaved = computed(() => {\n if (filterMode.value !== \"block\" || !filterBlockId.value) {\n return false;\n }\n return !(editor.savedBlockIds?.value.has(filterBlockId.value) ?? true);\n});\n\nconst filteredComments = computed(() => {\n let items = comments.comments.value;\n\n if (filterMode.value === \"unresolved\") {\n items = items.filter((c) => !c.resolved_at);\n } else if (filterMode.value === \"block\" && filterBlockId.value) {\n items = items.filter((c) => c.block_id === filterBlockId.value);\n }\n\n return items;\n});\n\nwatch(\n () => props.visible,\n (isVisible) => {\n if (isVisible) {\n comments.loadComments();\n }\n },\n);\n\nwatch(\n () => editor.state.selectedBlockId,\n (newBlockId) => {\n if (filterMode.value === \"block\" && newBlockId) {\n filterBlockId.value = newBlockId;\n }\n },\n);\n\nfunction setFilter(mode: FilterMode, blockId?: string): void {\n filterMode.value = mode;\n filterBlockId.value = mode === \"block\" ? (blockId ?? null) : null;\n}\n\nfunction filterByBlock(blockId: string): void {\n setFilter(\"block\", blockId);\n emit(\"filterBlock\", blockId);\n}\n\nfunction toggleThread(threadId: string): void {\n if (expandedThreads.value.has(threadId)) {\n expandedThreads.value.delete(threadId);\n } else {\n expandedThreads.value.add(threadId);\n }\n}\n\nfunction startReply(threadId: string): void {\n replyingTo.value = threadId;\n replyBody.value = \"\";\n editingId.value = null;\n\n if (!expandedThreads.value.has(threadId)) {\n expandedThreads.value.add(threadId);\n }\n}\n\nfunction cancelReply(): void {\n replyingTo.value = null;\n replyBody.value = \"\";\n}\n\nfunction startEdit(comment: Comment): void {\n editingId.value = comment.id;\n editBody.value = comment.body;\n replyingTo.value = null;\n}\n\nfunction cancelEdit(): void {\n editingId.value = null;\n editBody.value = \"\";\n}\n\nfunction confirmDelete(commentId: string): void {\n deletingId.value = commentId;\n}\n\nfunction cancelDelete(): void {\n deletingId.value = null;\n}\n\nasync function handleAddComment(): Promise<void> {\n const body = newCommentBody.value.trim();\n if (!body) {\n return;\n }\n\n const blockId =\n filterMode.value === \"block\" ? filterBlockId.value : undefined;\n await comments.addComment(body, blockId ?? undefined);\n newCommentBody.value = \"\";\n}\n\nasync function handleReply(parentId: string): Promise<void> {\n const body = replyBody.value.trim();\n if (!body) {\n return;\n }\n\n const parent = comments.comments.value.find((c) => c.id === parentId);\n await comments.addComment(body, parent?.block_id ?? undefined, parentId);\n replyingTo.value = null;\n replyBody.value = \"\";\n}\n\nasync function handleEdit(commentId: string): Promise<void> {\n const body = editBody.value.trim();\n if (!body) {\n return;\n }\n\n await comments.editComment(commentId, body);\n editingId.value = null;\n editBody.value = \"\";\n}\n\nasync function handleDelete(commentId: string): Promise<void> {\n await comments.removeComment(commentId);\n deletingId.value = null;\n}\n\nasync function handleResolve(commentId: string): Promise<void> {\n await comments.toggleResolve(commentId);\n}\n\nfunction isOwnComment(comment: Comment): boolean {\n return comment.author_identifier === currentUserId.value;\n}\n\nfunction formatTime(dateString: string): string {\n return formatRelativeTime(dateString, cloudT.snapshotHistory) ?? dateString;\n}\n\nfunction handleNewCommentKeydown(event: KeyboardEvent): void {\n if (event.key === \"Enter\" && !event.shiftKey) {\n event.preventDefault();\n handleAddComment();\n }\n}\n\nfunction handleReplyKeydown(event: KeyboardEvent, parentId: string): void {\n if (event.key === \"Enter\" && !event.shiftKey) {\n event.preventDefault();\n handleReply(parentId);\n }\n}\n\nfunction handleEditKeydown(event: KeyboardEvent, commentId: string): void {\n if (event.key === \"Enter\" && !event.shiftKey) {\n event.preventDefault();\n handleEdit(commentId);\n }\n if (event.key === \"Escape\") {\n cancelEdit();\n }\n}\n\nfunction focusNewComment(): void {\n nextTick(() => {\n newCommentInput.value?.focus();\n });\n}\n\ndefineExpose({ filterByBlock, focusNewComment });\n</script>\n\n<template>\n <Transition\n enter-active-class=\"tpl-comments-slide-enter-active\"\n enter-from-class=\"tpl:translate-x-full\"\n enter-to-class=\"tpl:translate-x-0\"\n leave-active-class=\"tpl-comments-slide-leave-active\"\n leave-from-class=\"tpl:translate-x-0\"\n leave-to-class=\"tpl:translate-x-full\"\n >\n <div\n v-if=\"visible\"\n class=\"tpl-comments-sidebar tpl:absolute tpl:top-14 tpl:right-0 tpl:bottom-0 tpl:z-panel tpl:flex tpl:w-[360px] tpl:flex-col tpl:border-l tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg-elevated)]\"\n >\n <!-- Header -->\n <div\n class=\"tpl:flex tpl:items-center tpl:justify-between tpl:border-b tpl:border-[var(--tpl-border)] tpl:px-4 tpl:py-3\"\n >\n <div\n class=\"tpl:flex tpl:items-center tpl:gap-1.5 tpl:text-sm tpl:font-medium tpl:text-[var(--tpl-text)]\"\n >\n <MessageCircle :size=\"13\" :stroke-width=\"2\" />\n <span>{{ cloudT.comments.title }}</span>\n <span\n v-if=\"comments.unresolvedCount.value > 0\"\n class=\"tpl:ml-1 tpl:inline-flex tpl:size-5 tpl:items-center tpl:justify-center tpl:rounded-full tpl:text-[10px] tpl:font-semibold tpl:bg-[var(--tpl-primary)] tpl:text-[var(--tpl-bg)]\"\n >\n {{ comments.unresolvedCount.value }}\n </span>\n </div>\n <button\n class=\"tpl:rounded-md tpl:p-0.5 tpl:transition-colors tpl:duration-150 tpl:text-[var(--tpl-text-muted)]\"\n @click=\"emit('close')\"\n >\n <X :size=\"14\" :stroke-width=\"2\" />\n </button>\n </div>\n\n <!-- Filter bar -->\n <div\n class=\"tpl:flex tpl:gap-1 tpl:border-b tpl:border-[var(--tpl-border)] tpl:px-4 tpl:py-2\"\n >\n <button\n class=\"tpl-comment-filter tpl:rounded-md tpl:px-2.5 tpl:py-1 tpl:text-xs tpl:font-medium tpl:transition-colors tpl:duration-150\"\n :class=\"\n filterMode === 'unresolved' ? 'tpl-comment-filter--active' : ''\n \"\n @click=\"setFilter('unresolved')\"\n >\n {{ cloudT.comments.filterUnresolved }}\n </button>\n <button\n class=\"tpl-comment-filter tpl:rounded-md tpl:px-2.5 tpl:py-1 tpl:text-xs tpl:font-medium tpl:transition-colors tpl:duration-150\"\n :class=\"filterMode === 'all' ? 'tpl-comment-filter--active' : ''\"\n @click=\"setFilter('all')\"\n >\n {{ cloudT.comments.filterAll }}\n </button>\n <button\n v-if=\"editor.state.selectedBlockId\"\n class=\"tpl-comment-filter tpl:rounded-md tpl:px-2.5 tpl:py-1 tpl:text-xs tpl:font-medium tpl:transition-colors tpl:duration-150\"\n :class=\"filterMode === 'block' ? 'tpl-comment-filter--active' : ''\"\n @click=\"setFilter('block', editor.state.selectedBlockId ?? undefined)\"\n >\n {{ cloudT.comments.filterBlock }}\n </button>\n </div>\n\n <!-- Thread list -->\n <div class=\"tpl:flex-1 tpl:overflow-y-auto\">\n <!-- Loading state -->\n <div\n v-if=\"comments.isLoading.value\"\n class=\"tpl:flex tpl:h-full tpl:items-center tpl:justify-center\"\n >\n <LoaderCircle\n class=\"tpl-spinner tpl:text-[var(--tpl-text-muted)]\"\n :size=\"24\"\n :stroke-width=\"2\"\n />\n </div>\n\n <!-- Empty state -->\n <div\n v-else-if=\"filteredComments.length === 0\"\n class=\"tpl:flex tpl:h-full tpl:flex-col tpl:items-center tpl:justify-center tpl:gap-3 tpl:px-6 tpl:text-center\"\n >\n <MessageCircle\n :size=\"32\"\n :stroke-width=\"1.5\"\n class=\"tpl:text-[var(--tpl-text-dim)]\"\n />\n <p\n class=\"tpl:max-w-[240px] tpl:text-sm tpl:text-[var(--tpl-text-muted)]\"\n >\n {{\n filterMode === \"all\"\n ? cloudT.comments.noCommentsHint\n : cloudT.comments.noComments\n }}\n </p>\n </div>\n\n <!-- Comment threads -->\n <div v-else class=\"tpl:flex tpl:flex-col tpl:gap-3 tpl:p-3\">\n <div\n v-for=\"thread in filteredComments\"\n :key=\"thread.id\"\n class=\"tpl-comment-thread\"\n >\n <!-- Root comment card -->\n <div\n class=\"tpl-comment-card tpl:rounded-lg tpl:border tpl:px-3.5 tpl:py-3\"\n >\n <!-- Comment header -->\n <div\n class=\"tpl:flex tpl:items-start tpl:justify-between tpl:gap-2\"\n >\n <div class=\"tpl:flex tpl:items-center tpl:gap-1.5\">\n <span\n class=\"tpl:text-xs tpl:font-semibold tpl:text-[var(--tpl-text)]\"\n >\n {{\n isOwnComment(thread)\n ? cloudT.comments.ownedByYou\n : thread.author_name\n }}\n </span>\n <span class=\"tpl:text-[10px] tpl:text-[var(--tpl-text-dim)]\">\n {{ formatTime(thread.created_at) }}\n </span>\n <span\n v-if=\"thread.updated_at !== thread.created_at\"\n class=\"tpl:text-[10px] tpl:italic tpl:text-[var(--tpl-text-dim)]\"\n >\n ({{ cloudT.comments.edited }})\n </span>\n </div>\n <div class=\"tpl:flex tpl:items-center tpl:gap-0.5\">\n <!-- Resolve toggle -->\n <button\n class=\"tpl-comment-action tpl:rounded tpl:p-1 tpl:transition-colors tpl:duration-150\"\n :title=\"\n thread.resolved_at\n ? cloudT.comments.unresolve\n : cloudT.comments.resolve\n \"\n @click=\"handleResolve(thread.id)\"\n >\n <CircleCheck\n :size=\"13\"\n :stroke-width=\"2\"\n class=\"tpl-resolve-icon\"\n :style=\"{\n color: thread.resolved_at\n ? 'var(--tpl-primary)'\n : undefined,\n }\"\n />\n </button>\n <!-- Edit (own only) -->\n <button\n v-if=\"isOwnComment(thread)\"\n class=\"tpl-comment-action tpl:rounded tpl:p-1 tpl:transition-colors tpl:duration-150\"\n :title=\"cloudT.comments.edit\"\n @click=\"startEdit(thread)\"\n >\n <Pencil :size=\"12\" :stroke-width=\"2\" />\n </button>\n <!-- Delete (own only) -->\n <button\n v-if=\"isOwnComment(thread)\"\n class=\"tpl-comment-action tpl-comment-delete tpl:rounded tpl:p-1 tpl:transition-colors tpl:duration-150\"\n :title=\"cloudT.comments.delete\"\n @click=\"confirmDelete(thread.id)\"\n >\n <Trash2 :size=\"12\" :stroke-width=\"2\" />\n </button>\n </div>\n </div>\n\n <!-- Resolved badge -->\n <Transition name=\"tpl-resolve\">\n <div\n v-if=\"thread.resolved_at\"\n class=\"tpl:mt-1 tpl:flex tpl:items-center tpl:gap-1 tpl:text-[10px] tpl:text-[var(--tpl-primary)]\"\n >\n <Check :size=\"10\" :stroke-width=\"2.5\" />\n <span>\n {{\n format(cloudT.comments.resolvedBy, {\n name: thread.resolved_by_name ?? \"\",\n })\n }}\n </span>\n </div>\n </Transition>\n\n <!-- Block anchor indicator -->\n <span\n v-if=\"thread.block_id && isBlockMissing(thread.block_id)\"\n class=\"tpl:mt-1 tpl:inline-flex tpl:items-center tpl:gap-1 tpl:rounded tpl:px-1.5 tpl:py-0.5 tpl:text-[10px] tpl:font-medium tpl:bg-[var(--tpl-warning-light)] tpl:text-[var(--tpl-warning)]\"\n >\n {{ cloudT.comments.missingBlock }}\n </span>\n <button\n v-else-if=\"thread.block_id\"\n class=\"tpl:mt-1 tpl:inline-flex tpl:items-center tpl:gap-1 tpl:rounded tpl:px-1.5 tpl:py-0.5 tpl:text-[10px] tpl:font-medium tpl:transition-colors tpl:duration-150 tpl:bg-[var(--tpl-bg-hover)] tpl:text-[var(--tpl-text-muted)]\"\n @click=\"editor.selectBlock(thread.block_id ?? '')\"\n >\n Block\n </button>\n\n <!-- Comment body (or edit mode) -->\n <div v-if=\"editingId === thread.id\" class=\"tpl:mt-2\">\n <textarea\n v-model=\"editBody\"\n class=\"tpl:w-full tpl:resize-none tpl:rounded-md tpl:border tpl:px-2.5 tpl:py-2 tpl:font-sans tpl:text-xs tpl:outline-none tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:text-[var(--tpl-text)]\"\n rows=\"3\"\n @keydown=\"handleEditKeydown($event, thread.id)\"\n />\n <div class=\"tpl:mt-1.5 tpl:flex tpl:gap-1.5\">\n <button\n class=\"tpl:rounded-md tpl:px-2.5 tpl:py-1 tpl:text-xs tpl:font-medium tpl:transition-colors tpl:duration-150 tpl:bg-[var(--tpl-primary)] tpl:text-[var(--tpl-bg)]\"\n :disabled=\"!editBody.trim() || comments.isSubmitting.value\"\n @click=\"handleEdit(thread.id)\"\n >\n {{ cloudT.comments.save }}\n </button>\n <button\n class=\"tpl:rounded-md tpl:px-2.5 tpl:py-1 tpl:text-xs tpl:font-medium tpl:transition-colors tpl:duration-150 tpl:text-[var(--tpl-text-muted)]\"\n @click=\"cancelEdit()\"\n >\n {{ cloudT.comments.cancel }}\n </button>\n </div>\n </div>\n <p\n v-else\n class=\"tpl:mt-1.5 tpl:text-xs tpl:leading-relaxed tpl:whitespace-pre-wrap tpl:text-[var(--tpl-text)]\"\n >\n {{ thread.body }}\n </p>\n\n <!-- Delete confirmation -->\n <div\n v-if=\"deletingId === thread.id\"\n class=\"tpl:mt-2 tpl:flex tpl:items-center tpl:gap-2 tpl:rounded-md tpl:px-2.5 tpl:py-2 tpl:text-xs tpl:bg-[var(--tpl-danger-light)] tpl:text-[var(--tpl-danger)]\"\n >\n <span class=\"tpl:flex-1\">\n {{ cloudT.comments.deleteConfirm }}\n </span>\n <button\n class=\"tpl:rounded tpl:px-2 tpl:py-0.5 tpl:text-xs tpl:font-medium tpl:bg-[var(--tpl-danger)] tpl:text-[var(--tpl-bg)]\"\n @click=\"handleDelete(thread.id)\"\n >\n {{ cloudT.comments.delete }}\n </button>\n <button\n class=\"tpl:text-xs tpl:font-medium tpl:text-[var(--tpl-text-muted)]\"\n @click=\"cancelDelete()\"\n >\n {{ cloudT.comments.cancel }}\n </button>\n </div>\n\n <!-- Thread actions -->\n <div\n v-if=\"editingId !== thread.id && deletingId !== thread.id\"\n class=\"tpl:mt-2 tpl:flex tpl:items-center tpl:gap-2\"\n >\n <button\n class=\"tpl-comment-action tpl:rounded tpl:p-1 tpl:transition-colors tpl:duration-150\"\n :title=\"cloudT.comments.reply\"\n @click=\"startReply(thread.id)\"\n >\n <Reply\n :size=\"13\"\n :stroke-width=\"2\"\n class=\"tpl:text-[var(--tpl-primary)]\"\n />\n </button>\n <button\n v-if=\"(thread.replies?.length ?? 0) > 0\"\n class=\"tpl:flex tpl:items-center tpl:gap-0.5 tpl:text-[11px] tpl:font-medium tpl:transition-colors tpl:duration-150 tpl:text-[var(--tpl-text-muted)]\"\n @click=\"toggleThread(thread.id)\"\n >\n <template v-if=\"expandedThreads.has(thread.id)\">\n <ChevronUp :size=\"11\" :stroke-width=\"2\" />\n </template>\n <template v-else>\n <ChevronDown :size=\"11\" :stroke-width=\"2\" />\n </template>\n {{\n (thread.replies?.length ?? 0) === 1\n ? format(cloudT.comments.replyOne, {\n count: String(thread.replies?.length ?? 0),\n })\n : format(cloudT.comments.replyMany, {\n count: String(thread.replies?.length ?? 0),\n })\n }}\n </button>\n </div>\n </div>\n\n <!-- Replies -->\n <Transition name=\"tpl-replies\">\n <div\n v-if=\"\n expandedThreads.has(thread.id) &&\n (thread.replies?.length ?? 0) > 0\n \"\n class=\"tpl-comment-replies tpl:ml-5 tpl:pl-3 tpl:pt-2\"\n >\n <div\n v-for=\"(reply, index) in thread.replies\"\n :key=\"reply.id\"\n class=\"tpl-comment-reply-card tpl:relative tpl:rounded-lg tpl:border tpl:px-3.5 tpl:py-2.5\"\n :class=\"\n index < (thread.replies?.length ?? 0) - 1 ? 'tpl:mb-2' : ''\n \"\n >\n <div\n class=\"tpl:flex tpl:items-start tpl:justify-between tpl:gap-2\"\n >\n <div class=\"tpl:flex tpl:items-center tpl:gap-1.5\">\n <span\n class=\"tpl:text-xs tpl:font-semibold tpl:text-[var(--tpl-text)]\"\n >\n {{\n isOwnComment(reply)\n ? cloudT.comments.ownedByYou\n : reply.author_name\n }}\n </span>\n <span\n class=\"tpl:text-[10px] tpl:text-[var(--tpl-text-dim)]\"\n >\n {{ formatTime(reply.created_at) }}\n </span>\n <span\n v-if=\"reply.updated_at !== reply.created_at\"\n class=\"tpl:text-[10px] tpl:italic tpl:text-[var(--tpl-text-dim)]\"\n >\n ({{ cloudT.comments.edited }})\n </span>\n </div>\n <div class=\"tpl:flex tpl:items-center tpl:gap-0.5\">\n <button\n v-if=\"isOwnComment(reply)\"\n class=\"tpl-comment-action tpl:rounded tpl:p-1 tpl:transition-colors tpl:duration-150\"\n :title=\"cloudT.comments.edit\"\n @click=\"startEdit(reply)\"\n >\n <Pencil :size=\"11\" :stroke-width=\"2\" />\n </button>\n <button\n v-if=\"isOwnComment(reply)\"\n class=\"tpl-comment-action tpl-comment-delete tpl:rounded tpl:p-1 tpl:transition-colors tpl:duration-150\"\n :title=\"cloudT.comments.delete\"\n @click=\"confirmDelete(reply.id)\"\n >\n <Trash2 :size=\"11\" :stroke-width=\"2\" />\n </button>\n </div>\n </div>\n\n <!-- Reply body (or edit mode) -->\n <div v-if=\"editingId === reply.id\" class=\"tpl:mt-1.5\">\n <textarea\n v-model=\"editBody\"\n class=\"tpl:w-full tpl:resize-none tpl:rounded-md tpl:border tpl:px-2.5 tpl:py-2 tpl:font-sans tpl:text-xs tpl:outline-none tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:text-[var(--tpl-text)]\"\n rows=\"2\"\n @keydown=\"handleEditKeydown($event, reply.id)\"\n />\n <div class=\"tpl:mt-1.5 tpl:flex tpl:gap-1.5\">\n <button\n class=\"tpl:rounded-md tpl:px-2.5 tpl:py-1 tpl:text-xs tpl:font-medium tpl:bg-[var(--tpl-primary)] tpl:text-[var(--tpl-bg)]\"\n :disabled=\"\n !editBody.trim() || comments.isSubmitting.value\n \"\n @click=\"handleEdit(reply.id)\"\n >\n {{ cloudT.comments.save }}\n </button>\n <button\n class=\"tpl:rounded-md tpl:px-2.5 tpl:py-1 tpl:text-xs tpl:font-medium tpl:text-[var(--tpl-text-muted)]\"\n @click=\"cancelEdit()\"\n >\n {{ cloudT.comments.cancel }}\n </button>\n </div>\n </div>\n <p\n v-else\n class=\"tpl:mt-1 tpl:text-xs tpl:leading-relaxed tpl:whitespace-pre-wrap tpl:text-[var(--tpl-text)]\"\n >\n {{ reply.body }}\n </p>\n\n <!-- Delete confirmation for reply -->\n <div\n v-if=\"deletingId === reply.id\"\n class=\"tpl:mt-2 tpl:flex tpl:items-center tpl:gap-2 tpl:rounded-md tpl:px-2.5 tpl:py-2 tpl:text-xs tpl:bg-[var(--tpl-danger-light)] tpl:text-[var(--tpl-danger)]\"\n >\n <span class=\"tpl:flex-1\">\n {{ cloudT.comments.deleteConfirm }}\n </span>\n <button\n class=\"tpl:rounded tpl:px-2 tpl:py-0.5 tpl:text-xs tpl:font-medium tpl:bg-[var(--tpl-danger)] tpl:text-[var(--tpl-bg)]\"\n @click=\"handleDelete(reply.id)\"\n >\n {{ cloudT.comments.delete }}\n </button>\n <button\n class=\"tpl:text-xs tpl:font-medium tpl:text-[var(--tpl-text-muted)]\"\n @click=\"cancelDelete()\"\n >\n {{ cloudT.comments.cancel }}\n </button>\n </div>\n </div>\n </div>\n </Transition>\n\n <!-- Reply input -->\n <Transition name=\"tpl-replies\">\n <div\n v-if=\"replyingTo === thread.id\"\n class=\"tpl-comment-replies tpl:ml-5 tpl:pl-3 tpl:pt-2\"\n >\n <div class=\"tpl:flex tpl:items-end tpl:gap-2\">\n <textarea\n v-model=\"replyBody\"\n class=\"tpl:flex-1 tpl:resize-none tpl:rounded-md tpl:border tpl:px-2.5 tpl:py-2 tpl:font-sans tpl:text-xs tpl:outline-none tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:text-[var(--tpl-text)]\"\n :placeholder=\"cloudT.comments.replyPlaceholder\"\n rows=\"2\"\n @keydown=\"handleReplyKeydown($event, thread.id)\"\n />\n <div class=\"tpl:flex tpl:shrink-0 tpl:flex-col tpl:gap-1\">\n <button\n class=\"tpl:flex tpl:items-center tpl:justify-center tpl:rounded-md tpl:p-1.5 tpl:transition-colors tpl:duration-150 tpl:disabled:opacity-40 tpl:text-[var(--tpl-primary)]\"\n :disabled=\"\n !replyBody.trim() || comments.isSubmitting.value\n \"\n @click=\"handleReply(thread.id)\"\n >\n <Send :size=\"14\" :stroke-width=\"2\" />\n </button>\n <button\n class=\"tpl:flex tpl:items-center tpl:justify-center tpl:rounded-md tpl:p-1.5 tpl:transition-colors tpl:duration-150 tpl:text-[var(--tpl-text-muted)]\"\n @click=\"cancelReply()\"\n >\n <X :size=\"14\" :stroke-width=\"2\" />\n </button>\n </div>\n </div>\n </div>\n </Transition>\n </div>\n </div>\n </div>\n\n <!-- New comment input -->\n <div class=\"tpl:border-t tpl:p-3 tpl:border-[var(--tpl-border)]\">\n <div\n v-if=\"isBlockFilterUnsaved\"\n class=\"tpl:flex tpl:min-h-[68px] tpl:items-center tpl:rounded-md tpl:px-3 tpl:py-2 tpl:text-xs tpl:bg-[var(--tpl-warning-light)] tpl:text-[var(--tpl-warning)]\"\n >\n {{ cloudT.comments.saveTemplateFirst }}\n </div>\n <div\n v-else\n class=\"tpl-comments-input-wrapper tpl:flex tpl:min-h-[68px] tpl:items-end tpl:gap-2 tpl:rounded-[var(--tpl-radius)] tpl:border tpl:px-3 tpl:py-2 tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)]\"\n >\n <textarea\n ref=\"newCommentInput\"\n v-model=\"newCommentBody\"\n class=\"tpl:max-h-24 tpl:min-h-[48px] tpl:flex-1 tpl:resize-none tpl:border-none tpl:bg-transparent tpl:font-sans tpl:text-xs tpl:outline-none tpl:text-[var(--tpl-text)]\"\n :placeholder=\"cloudT.comments.placeholder\"\n :disabled=\"comments.isSubmitting.value\"\n rows=\"2\"\n @keydown=\"handleNewCommentKeydown\"\n />\n <button\n class=\"tpl-comments-send-btn tpl:flex tpl:shrink-0 tpl:items-center tpl:justify-center tpl:rounded-md tpl:p-1.5 tpl:transition-all tpl:duration-150 tpl:disabled:opacity-40 tpl:text-[var(--tpl-primary)]\"\n :disabled=\"!newCommentBody.trim() || comments.isSubmitting.value\"\n @click=\"handleAddComment\"\n >\n <LoaderCircle\n v-if=\"comments.isSubmitting.value\"\n class=\"tpl-spinner\"\n :size=\"16\"\n :stroke-width=\"2\"\n />\n <Send v-else :size=\"16\" :stroke-width=\"2\" />\n </button>\n </div>\n </div>\n </div>\n </Transition>\n</template>\n\n<style scoped>\n.tpl-comments-slide-enter-active {\n transition: transform 280ms cubic-bezier(0.16, 1, 0.3, 1);\n}\n\n.tpl-comments-slide-leave-active {\n transition: transform 200ms cubic-bezier(0.16, 1, 0.3, 1);\n}\n\n.tpl-comments-input-wrapper:focus-within {\n border-color: var(--tpl-primary);\n box-shadow: var(--tpl-ring);\n}\n\n.tpl-comments-send-btn:not(:disabled):hover {\n transform: scale(1.05);\n}\n\n.tpl-comment-filter {\n color: var(--tpl-text-muted);\n background-color: transparent;\n}\n\n.tpl-comment-filter:hover {\n background-color: var(--tpl-bg-hover);\n color: var(--tpl-text);\n}\n\n.tpl-comment-filter--active {\n background-color: var(--tpl-primary-light);\n color: var(--tpl-primary);\n}\n\n.tpl-comment-action {\n color: var(--tpl-text-dim);\n background-color: transparent;\n}\n\n.tpl-comment-action:hover {\n background-color: var(--tpl-bg-hover);\n color: var(--tpl-text);\n}\n\n.tpl-comment-delete:hover {\n background-color: var(--tpl-danger-light);\n color: var(--tpl-danger);\n}\n\n.tpl-comment-card {\n border-color: var(--tpl-border);\n background-color: var(--tpl-bg);\n}\n\n.tpl-comment-reply-card {\n border-color: var(--tpl-border);\n background-color: var(--tpl-bg);\n}\n\n.tpl-resolve-enter-active,\n.tpl-resolve-leave-active {\n transition:\n opacity 0.3s ease,\n transform 0.3s ease;\n}\n\n.tpl-resolve-enter-from,\n.tpl-resolve-leave-to {\n opacity: 0;\n transform: translateY(-4px);\n}\n\n.tpl-resolve-icon {\n transition:\n color 0.3s ease,\n transform 0.2s ease;\n}\n\n.tpl-resolve-icon:active {\n transform: scale(1.3);\n}\n\n.tpl-replies-enter-active,\n.tpl-replies-leave-active {\n transition:\n opacity 0.25s ease,\n transform 0.25s ease;\n}\n\n.tpl-replies-enter-from,\n.tpl-replies-leave-to {\n opacity: 0;\n transform: translateY(-4px);\n}\n</style>\n","<script setup lang=\"ts\">\nimport { formatRelativeTime } from \"../../utils/formatRelativeTime\";\nimport {\n EDITOR_KEY,\n AUTH_MANAGER_KEY,\n COMMENTS_KEY,\n requireInject,\n} from \"../../keys\";\nimport { useI18n } from \"../../composables/useI18n\";\nimport { useCloudI18nStrict } from \"../../composables/useCloudI18n\";\nimport type { Comment } from \"@templatical/types\";\nimport {\n Check,\n CircleCheck,\n ChevronDown,\n ChevronUp,\n LoaderCircle,\n MessageCircle,\n Pencil,\n Reply,\n Send,\n Trash2,\n X,\n} from \"@lucide/vue\";\nimport { computed, nextTick, ref, watch } from \"vue\";\n\nconst props = defineProps<{\n visible: boolean;\n}>();\n\nconst emit = defineEmits<{\n (e: \"close\"): void;\n (e: \"filterBlock\", blockId: string | null): void;\n}>();\n\nconst { format } = useI18n();\nconst { t: cloudT } = useCloudI18nStrict();\nconst editor = requireInject(EDITOR_KEY, \"CommentsSidebar\");\nconst authManager = requireInject(AUTH_MANAGER_KEY, \"CommentsSidebar\");\nconst comments = requireInject(COMMENTS_KEY, \"CommentsSidebar\");\n\ntype FilterMode = \"all\" | \"unresolved\" | \"block\";\nconst filterMode = ref<FilterMode>(\"unresolved\");\nconst filterBlockId = ref<string | null>(null);\n\nconst newCommentBody = ref(\"\");\nconst replyingTo = ref<string | null>(null);\nconst replyBody = ref(\"\");\nconst editingId = ref<string | null>(null);\nconst editBody = ref(\"\");\nconst expandedThreads = ref<Set<string>>(new Set());\nconst deletingId = ref<string | null>(null);\nconst newCommentInput = ref<HTMLTextAreaElement | null>(null);\n\nconst currentUserId = computed(() => authManager.userConfig?.id ?? null);\n\nconst allBlockIds = computed<Set<string>>(() => {\n const ids = new Set<string>();\n for (const block of editor.content.value.blocks) {\n ids.add(block.id);\n if (block.type === \"section\") {\n for (const column of block.children) {\n for (const child of column) {\n ids.add(child.id);\n }\n }\n }\n }\n return ids;\n});\n\nfunction isBlockMissing(blockId: string | null): boolean {\n if (!blockId) {\n return false;\n }\n return !allBlockIds.value.has(blockId);\n}\n\nconst isBlockFilterUnsaved = computed(() => {\n if (filterMode.value !== \"block\" || !filterBlockId.value) {\n return false;\n }\n return !(editor.savedBlockIds?.value.has(filterBlockId.value) ?? true);\n});\n\nconst filteredComments = computed(() => {\n let items = comments.comments.value;\n\n if (filterMode.value === \"unresolved\") {\n items = items.filter((c) => !c.resolved_at);\n } else if (filterMode.value === \"block\" && filterBlockId.value) {\n items = items.filter((c) => c.block_id === filterBlockId.value);\n }\n\n return items;\n});\n\nwatch(\n () => props.visible,\n (isVisible) => {\n if (isVisible) {\n comments.loadComments();\n }\n },\n);\n\nwatch(\n () => editor.state.selectedBlockId,\n (newBlockId) => {\n if (filterMode.value === \"block\" && newBlockId) {\n filterBlockId.value = newBlockId;\n }\n },\n);\n\nfunction setFilter(mode: FilterMode, blockId?: string): void {\n filterMode.value = mode;\n filterBlockId.value = mode === \"block\" ? (blockId ?? null) : null;\n}\n\nfunction filterByBlock(blockId: string): void {\n setFilter(\"block\", blockId);\n emit(\"filterBlock\", blockId);\n}\n\nfunction toggleThread(threadId: string): void {\n if (expandedThreads.value.has(threadId)) {\n expandedThreads.value.delete(threadId);\n } else {\n expandedThreads.value.add(threadId);\n }\n}\n\nfunction startReply(threadId: string): void {\n replyingTo.value = threadId;\n replyBody.value = \"\";\n editingId.value = null;\n\n if (!expandedThreads.value.has(threadId)) {\n expandedThreads.value.add(threadId);\n }\n}\n\nfunction cancelReply(): void {\n replyingTo.value = null;\n replyBody.value = \"\";\n}\n\nfunction startEdit(comment: Comment): void {\n editingId.value = comment.id;\n editBody.value = comment.body;\n replyingTo.value = null;\n}\n\nfunction cancelEdit(): void {\n editingId.value = null;\n editBody.value = \"\";\n}\n\nfunction confirmDelete(commentId: string): void {\n deletingId.value = commentId;\n}\n\nfunction cancelDelete(): void {\n deletingId.value = null;\n}\n\nasync function handleAddComment(): Promise<void> {\n const body = newCommentBody.value.trim();\n if (!body) {\n return;\n }\n\n const blockId =\n filterMode.value === \"block\" ? filterBlockId.value : undefined;\n await comments.addComment(body, blockId ?? undefined);\n newCommentBody.value = \"\";\n}\n\nasync function handleReply(parentId: string): Promise<void> {\n const body = replyBody.value.trim();\n if (!body) {\n return;\n }\n\n const parent = comments.comments.value.find((c) => c.id === parentId);\n await comments.addComment(body, parent?.block_id ?? undefined, parentId);\n replyingTo.value = null;\n replyBody.value = \"\";\n}\n\nasync function handleEdit(commentId: string): Promise<void> {\n const body = editBody.value.trim();\n if (!body) {\n return;\n }\n\n await comments.editComment(commentId, body);\n editingId.value = null;\n editBody.value = \"\";\n}\n\nasync function handleDelete(commentId: string): Promise<void> {\n await comments.removeComment(commentId);\n deletingId.value = null;\n}\n\nasync function handleResolve(commentId: string): Promise<void> {\n await comments.toggleResolve(commentId);\n}\n\nfunction isOwnComment(comment: Comment): boolean {\n return comment.author_identifier === currentUserId.value;\n}\n\nfunction formatTime(dateString: string): string {\n return formatRelativeTime(dateString, cloudT.snapshotHistory) ?? dateString;\n}\n\nfunction handleNewCommentKeydown(event: KeyboardEvent): void {\n if (event.key === \"Enter\" && !event.shiftKey) {\n event.preventDefault();\n handleAddComment();\n }\n}\n\nfunction handleReplyKeydown(event: KeyboardEvent, parentId: string): void {\n if (event.key === \"Enter\" && !event.shiftKey) {\n event.preventDefault();\n handleReply(parentId);\n }\n}\n\nfunction handleEditKeydown(event: KeyboardEvent, commentId: string): void {\n if (event.key === \"Enter\" && !event.shiftKey) {\n event.preventDefault();\n handleEdit(commentId);\n }\n if (event.key === \"Escape\") {\n cancelEdit();\n }\n}\n\nfunction focusNewComment(): void {\n nextTick(() => {\n newCommentInput.value?.focus();\n });\n}\n\ndefineExpose({ filterByBlock, focusNewComment });\n</script>\n\n<template>\n <Transition\n enter-active-class=\"tpl-comments-slide-enter-active\"\n enter-from-class=\"tpl:translate-x-full\"\n enter-to-class=\"tpl:translate-x-0\"\n leave-active-class=\"tpl-comments-slide-leave-active\"\n leave-from-class=\"tpl:translate-x-0\"\n leave-to-class=\"tpl:translate-x-full\"\n >\n <div\n v-if=\"visible\"\n class=\"tpl-comments-sidebar tpl:absolute tpl:top-14 tpl:right-0 tpl:bottom-0 tpl:z-panel tpl:flex tpl:w-[360px] tpl:flex-col tpl:border-l tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg-elevated)]\"\n >\n <!-- Header -->\n <div\n class=\"tpl:flex tpl:items-center tpl:justify-between tpl:border-b tpl:border-[var(--tpl-border)] tpl:px-4 tpl:py-3\"\n >\n <div\n class=\"tpl:flex tpl:items-center tpl:gap-1.5 tpl:text-sm tpl:font-medium tpl:text-[var(--tpl-text)]\"\n >\n <MessageCircle :size=\"13\" :stroke-width=\"2\" />\n <span>{{ cloudT.comments.title }}</span>\n <span\n v-if=\"comments.unresolvedCount.value > 0\"\n class=\"tpl:ml-1 tpl:inline-flex tpl:size-5 tpl:items-center tpl:justify-center tpl:rounded-full tpl:text-[10px] tpl:font-semibold tpl:bg-[var(--tpl-primary)] tpl:text-[var(--tpl-bg)]\"\n >\n {{ comments.unresolvedCount.value }}\n </span>\n </div>\n <button\n class=\"tpl:rounded-md tpl:p-0.5 tpl:transition-colors tpl:duration-150 tpl:text-[var(--tpl-text-muted)]\"\n @click=\"emit('close')\"\n >\n <X :size=\"14\" :stroke-width=\"2\" />\n </button>\n </div>\n\n <!-- Filter bar -->\n <div\n class=\"tpl:flex tpl:gap-1 tpl:border-b tpl:border-[var(--tpl-border)] tpl:px-4 tpl:py-2\"\n >\n <button\n class=\"tpl-comment-filter tpl:rounded-md tpl:px-2.5 tpl:py-1 tpl:text-xs tpl:font-medium tpl:transition-colors tpl:duration-150\"\n :class=\"\n filterMode === 'unresolved' ? 'tpl-comment-filter--active' : ''\n \"\n @click=\"setFilter('unresolved')\"\n >\n {{ cloudT.comments.filterUnresolved }}\n </button>\n <button\n class=\"tpl-comment-filter tpl:rounded-md tpl:px-2.5 tpl:py-1 tpl:text-xs tpl:font-medium tpl:transition-colors tpl:duration-150\"\n :class=\"filterMode === 'all' ? 'tpl-comment-filter--active' : ''\"\n @click=\"setFilter('all')\"\n >\n {{ cloudT.comments.filterAll }}\n </button>\n <button\n v-if=\"editor.state.selectedBlockId\"\n class=\"tpl-comment-filter tpl:rounded-md tpl:px-2.5 tpl:py-1 tpl:text-xs tpl:font-medium tpl:transition-colors tpl:duration-150\"\n :class=\"filterMode === 'block' ? 'tpl-comment-filter--active' : ''\"\n @click=\"setFilter('block', editor.state.selectedBlockId ?? undefined)\"\n >\n {{ cloudT.comments.filterBlock }}\n </button>\n </div>\n\n <!-- Thread list -->\n <div class=\"tpl:flex-1 tpl:overflow-y-auto\">\n <!-- Loading state -->\n <div\n v-if=\"comments.isLoading.value\"\n class=\"tpl:flex tpl:h-full tpl:items-center tpl:justify-center\"\n >\n <LoaderCircle\n class=\"tpl-spinner tpl:text-[var(--tpl-text-muted)]\"\n :size=\"24\"\n :stroke-width=\"2\"\n />\n </div>\n\n <!-- Empty state -->\n <div\n v-else-if=\"filteredComments.length === 0\"\n class=\"tpl:flex tpl:h-full tpl:flex-col tpl:items-center tpl:justify-center tpl:gap-3 tpl:px-6 tpl:text-center\"\n >\n <MessageCircle\n :size=\"32\"\n :stroke-width=\"1.5\"\n class=\"tpl:text-[var(--tpl-text-dim)]\"\n />\n <p\n class=\"tpl:max-w-[240px] tpl:text-sm tpl:text-[var(--tpl-text-muted)]\"\n >\n {{\n filterMode === \"all\"\n ? cloudT.comments.noCommentsHint\n : cloudT.comments.noComments\n }}\n </p>\n </div>\n\n <!-- Comment threads -->\n <div v-else class=\"tpl:flex tpl:flex-col tpl:gap-3 tpl:p-3\">\n <div\n v-for=\"thread in filteredComments\"\n :key=\"thread.id\"\n class=\"tpl-comment-thread\"\n >\n <!-- Root comment card -->\n <div\n class=\"tpl-comment-card tpl:rounded-lg tpl:border tpl:px-3.5 tpl:py-3\"\n >\n <!-- Comment header -->\n <div\n class=\"tpl:flex tpl:items-start tpl:justify-between tpl:gap-2\"\n >\n <div class=\"tpl:flex tpl:items-center tpl:gap-1.5\">\n <span\n class=\"tpl:text-xs tpl:font-semibold tpl:text-[var(--tpl-text)]\"\n >\n {{\n isOwnComment(thread)\n ? cloudT.comments.ownedByYou\n : thread.author_name\n }}\n </span>\n <span class=\"tpl:text-[10px] tpl:text-[var(--tpl-text-dim)]\">\n {{ formatTime(thread.created_at) }}\n </span>\n <span\n v-if=\"thread.updated_at !== thread.created_at\"\n class=\"tpl:text-[10px] tpl:italic tpl:text-[var(--tpl-text-dim)]\"\n >\n ({{ cloudT.comments.edited }})\n </span>\n </div>\n <div class=\"tpl:flex tpl:items-center tpl:gap-0.5\">\n <!-- Resolve toggle -->\n <button\n class=\"tpl-comment-action tpl:rounded tpl:p-1 tpl:transition-colors tpl:duration-150\"\n :title=\"\n thread.resolved_at\n ? cloudT.comments.unresolve\n : cloudT.comments.resolve\n \"\n @click=\"handleResolve(thread.id)\"\n >\n <CircleCheck\n :size=\"13\"\n :stroke-width=\"2\"\n class=\"tpl-resolve-icon\"\n :style=\"{\n color: thread.resolved_at\n ? 'var(--tpl-primary)'\n : undefined,\n }\"\n />\n </button>\n <!-- Edit (own only) -->\n <button\n v-if=\"isOwnComment(thread)\"\n class=\"tpl-comment-action tpl:rounded tpl:p-1 tpl:transition-colors tpl:duration-150\"\n :title=\"cloudT.comments.edit\"\n @click=\"startEdit(thread)\"\n >\n <Pencil :size=\"12\" :stroke-width=\"2\" />\n </button>\n <!-- Delete (own only) -->\n <button\n v-if=\"isOwnComment(thread)\"\n class=\"tpl-comment-action tpl-comment-delete tpl:rounded tpl:p-1 tpl:transition-colors tpl:duration-150\"\n :title=\"cloudT.comments.delete\"\n @click=\"confirmDelete(thread.id)\"\n >\n <Trash2 :size=\"12\" :stroke-width=\"2\" />\n </button>\n </div>\n </div>\n\n <!-- Resolved badge -->\n <Transition name=\"tpl-resolve\">\n <div\n v-if=\"thread.resolved_at\"\n class=\"tpl:mt-1 tpl:flex tpl:items-center tpl:gap-1 tpl:text-[10px] tpl:text-[var(--tpl-primary)]\"\n >\n <Check :size=\"10\" :stroke-width=\"2.5\" />\n <span>\n {{\n format(cloudT.comments.resolvedBy, {\n name: thread.resolved_by_name ?? \"\",\n })\n }}\n </span>\n </div>\n </Transition>\n\n <!-- Block anchor indicator -->\n <span\n v-if=\"thread.block_id && isBlockMissing(thread.block_id)\"\n class=\"tpl:mt-1 tpl:inline-flex tpl:items-center tpl:gap-1 tpl:rounded tpl:px-1.5 tpl:py-0.5 tpl:text-[10px] tpl:font-medium tpl:bg-[var(--tpl-warning-light)] tpl:text-[var(--tpl-warning)]\"\n >\n {{ cloudT.comments.missingBlock }}\n </span>\n <button\n v-else-if=\"thread.block_id\"\n class=\"tpl:mt-1 tpl:inline-flex tpl:items-center tpl:gap-1 tpl:rounded tpl:px-1.5 tpl:py-0.5 tpl:text-[10px] tpl:font-medium tpl:transition-colors tpl:duration-150 tpl:bg-[var(--tpl-bg-hover)] tpl:text-[var(--tpl-text-muted)]\"\n @click=\"editor.selectBlock(thread.block_id ?? '')\"\n >\n Block\n </button>\n\n <!-- Comment body (or edit mode) -->\n <div v-if=\"editingId === thread.id\" class=\"tpl:mt-2\">\n <textarea\n v-model=\"editBody\"\n class=\"tpl:w-full tpl:resize-none tpl:rounded-md tpl:border tpl:px-2.5 tpl:py-2 tpl:font-sans tpl:text-xs tpl:outline-none tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:text-[var(--tpl-text)]\"\n rows=\"3\"\n @keydown=\"handleEditKeydown($event, thread.id)\"\n />\n <div class=\"tpl:mt-1.5 tpl:flex tpl:gap-1.5\">\n <button\n class=\"tpl:rounded-md tpl:px-2.5 tpl:py-1 tpl:text-xs tpl:font-medium tpl:transition-colors tpl:duration-150 tpl:bg-[var(--tpl-primary)] tpl:text-[var(--tpl-bg)]\"\n :disabled=\"!editBody.trim() || comments.isSubmitting.value\"\n @click=\"handleEdit(thread.id)\"\n >\n {{ cloudT.comments.save }}\n </button>\n <button\n class=\"tpl:rounded-md tpl:px-2.5 tpl:py-1 tpl:text-xs tpl:font-medium tpl:transition-colors tpl:duration-150 tpl:text-[var(--tpl-text-muted)]\"\n @click=\"cancelEdit()\"\n >\n {{ cloudT.comments.cancel }}\n </button>\n </div>\n </div>\n <p\n v-else\n class=\"tpl:mt-1.5 tpl:text-xs tpl:leading-relaxed tpl:whitespace-pre-wrap tpl:text-[var(--tpl-text)]\"\n >\n {{ thread.body }}\n </p>\n\n <!-- Delete confirmation -->\n <div\n v-if=\"deletingId === thread.id\"\n class=\"tpl:mt-2 tpl:flex tpl:items-center tpl:gap-2 tpl:rounded-md tpl:px-2.5 tpl:py-2 tpl:text-xs tpl:bg-[var(--tpl-danger-light)] tpl:text-[var(--tpl-danger)]\"\n >\n <span class=\"tpl:flex-1\">\n {{ cloudT.comments.deleteConfirm }}\n </span>\n <button\n class=\"tpl:rounded tpl:px-2 tpl:py-0.5 tpl:text-xs tpl:font-medium tpl:bg-[var(--tpl-danger)] tpl:text-[var(--tpl-bg)]\"\n @click=\"handleDelete(thread.id)\"\n >\n {{ cloudT.comments.delete }}\n </button>\n <button\n class=\"tpl:text-xs tpl:font-medium tpl:text-[var(--tpl-text-muted)]\"\n @click=\"cancelDelete()\"\n >\n {{ cloudT.comments.cancel }}\n </button>\n </div>\n\n <!-- Thread actions -->\n <div\n v-if=\"editingId !== thread.id && deletingId !== thread.id\"\n class=\"tpl:mt-2 tpl:flex tpl:items-center tpl:gap-2\"\n >\n <button\n class=\"tpl-comment-action tpl:rounded tpl:p-1 tpl:transition-colors tpl:duration-150\"\n :title=\"cloudT.comments.reply\"\n @click=\"startReply(thread.id)\"\n >\n <Reply\n :size=\"13\"\n :stroke-width=\"2\"\n class=\"tpl:text-[var(--tpl-primary)]\"\n />\n </button>\n <button\n v-if=\"(thread.replies?.length ?? 0) > 0\"\n class=\"tpl:flex tpl:items-center tpl:gap-0.5 tpl:text-[11px] tpl:font-medium tpl:transition-colors tpl:duration-150 tpl:text-[var(--tpl-text-muted)]\"\n @click=\"toggleThread(thread.id)\"\n >\n <template v-if=\"expandedThreads.has(thread.id)\">\n <ChevronUp :size=\"11\" :stroke-width=\"2\" />\n </template>\n <template v-else>\n <ChevronDown :size=\"11\" :stroke-width=\"2\" />\n </template>\n {{\n (thread.replies?.length ?? 0) === 1\n ? format(cloudT.comments.replyOne, {\n count: String(thread.replies?.length ?? 0),\n })\n : format(cloudT.comments.replyMany, {\n count: String(thread.replies?.length ?? 0),\n })\n }}\n </button>\n </div>\n </div>\n\n <!-- Replies -->\n <Transition name=\"tpl-replies\">\n <div\n v-if=\"\n expandedThreads.has(thread.id) &&\n (thread.replies?.length ?? 0) > 0\n \"\n class=\"tpl-comment-replies tpl:ml-5 tpl:pl-3 tpl:pt-2\"\n >\n <div\n v-for=\"(reply, index) in thread.replies\"\n :key=\"reply.id\"\n class=\"tpl-comment-reply-card tpl:relative tpl:rounded-lg tpl:border tpl:px-3.5 tpl:py-2.5\"\n :class=\"\n index < (thread.replies?.length ?? 0) - 1 ? 'tpl:mb-2' : ''\n \"\n >\n <div\n class=\"tpl:flex tpl:items-start tpl:justify-between tpl:gap-2\"\n >\n <div class=\"tpl:flex tpl:items-center tpl:gap-1.5\">\n <span\n class=\"tpl:text-xs tpl:font-semibold tpl:text-[var(--tpl-text)]\"\n >\n {{\n isOwnComment(reply)\n ? cloudT.comments.ownedByYou\n : reply.author_name\n }}\n </span>\n <span\n class=\"tpl:text-[10px] tpl:text-[var(--tpl-text-dim)]\"\n >\n {{ formatTime(reply.created_at) }}\n </span>\n <span\n v-if=\"reply.updated_at !== reply.created_at\"\n class=\"tpl:text-[10px] tpl:italic tpl:text-[var(--tpl-text-dim)]\"\n >\n ({{ cloudT.comments.edited }})\n </span>\n </div>\n <div class=\"tpl:flex tpl:items-center tpl:gap-0.5\">\n <button\n v-if=\"isOwnComment(reply)\"\n class=\"tpl-comment-action tpl:rounded tpl:p-1 tpl:transition-colors tpl:duration-150\"\n :title=\"cloudT.comments.edit\"\n @click=\"startEdit(reply)\"\n >\n <Pencil :size=\"11\" :stroke-width=\"2\" />\n </button>\n <button\n v-if=\"isOwnComment(reply)\"\n class=\"tpl-comment-action tpl-comment-delete tpl:rounded tpl:p-1 tpl:transition-colors tpl:duration-150\"\n :title=\"cloudT.comments.delete\"\n @click=\"confirmDelete(reply.id)\"\n >\n <Trash2 :size=\"11\" :stroke-width=\"2\" />\n </button>\n </div>\n </div>\n\n <!-- Reply body (or edit mode) -->\n <div v-if=\"editingId === reply.id\" class=\"tpl:mt-1.5\">\n <textarea\n v-model=\"editBody\"\n class=\"tpl:w-full tpl:resize-none tpl:rounded-md tpl:border tpl:px-2.5 tpl:py-2 tpl:font-sans tpl:text-xs tpl:outline-none tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:text-[var(--tpl-text)]\"\n rows=\"2\"\n @keydown=\"handleEditKeydown($event, reply.id)\"\n />\n <div class=\"tpl:mt-1.5 tpl:flex tpl:gap-1.5\">\n <button\n class=\"tpl:rounded-md tpl:px-2.5 tpl:py-1 tpl:text-xs tpl:font-medium tpl:bg-[var(--tpl-primary)] tpl:text-[var(--tpl-bg)]\"\n :disabled=\"\n !editBody.trim() || comments.isSubmitting.value\n \"\n @click=\"handleEdit(reply.id)\"\n >\n {{ cloudT.comments.save }}\n </button>\n <button\n class=\"tpl:rounded-md tpl:px-2.5 tpl:py-1 tpl:text-xs tpl:font-medium tpl:text-[var(--tpl-text-muted)]\"\n @click=\"cancelEdit()\"\n >\n {{ cloudT.comments.cancel }}\n </button>\n </div>\n </div>\n <p\n v-else\n class=\"tpl:mt-1 tpl:text-xs tpl:leading-relaxed tpl:whitespace-pre-wrap tpl:text-[var(--tpl-text)]\"\n >\n {{ reply.body }}\n </p>\n\n <!-- Delete confirmation for reply -->\n <div\n v-if=\"deletingId === reply.id\"\n class=\"tpl:mt-2 tpl:flex tpl:items-center tpl:gap-2 tpl:rounded-md tpl:px-2.5 tpl:py-2 tpl:text-xs tpl:bg-[var(--tpl-danger-light)] tpl:text-[var(--tpl-danger)]\"\n >\n <span class=\"tpl:flex-1\">\n {{ cloudT.comments.deleteConfirm }}\n </span>\n <button\n class=\"tpl:rounded tpl:px-2 tpl:py-0.5 tpl:text-xs tpl:font-medium tpl:bg-[var(--tpl-danger)] tpl:text-[var(--tpl-bg)]\"\n @click=\"handleDelete(reply.id)\"\n >\n {{ cloudT.comments.delete }}\n </button>\n <button\n class=\"tpl:text-xs tpl:font-medium tpl:text-[var(--tpl-text-muted)]\"\n @click=\"cancelDelete()\"\n >\n {{ cloudT.comments.cancel }}\n </button>\n </div>\n </div>\n </div>\n </Transition>\n\n <!-- Reply input -->\n <Transition name=\"tpl-replies\">\n <div\n v-if=\"replyingTo === thread.id\"\n class=\"tpl-comment-replies tpl:ml-5 tpl:pl-3 tpl:pt-2\"\n >\n <div class=\"tpl:flex tpl:items-end tpl:gap-2\">\n <textarea\n v-model=\"replyBody\"\n class=\"tpl:flex-1 tpl:resize-none tpl:rounded-md tpl:border tpl:px-2.5 tpl:py-2 tpl:font-sans tpl:text-xs tpl:outline-none tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:text-[var(--tpl-text)]\"\n :placeholder=\"cloudT.comments.replyPlaceholder\"\n rows=\"2\"\n @keydown=\"handleReplyKeydown($event, thread.id)\"\n />\n <div class=\"tpl:flex tpl:shrink-0 tpl:flex-col tpl:gap-1\">\n <button\n class=\"tpl:flex tpl:items-center tpl:justify-center tpl:rounded-md tpl:p-1.5 tpl:transition-colors tpl:duration-150 tpl:disabled:opacity-40 tpl:text-[var(--tpl-primary)]\"\n :disabled=\"\n !replyBody.trim() || comments.isSubmitting.value\n \"\n @click=\"handleReply(thread.id)\"\n >\n <Send :size=\"14\" :stroke-width=\"2\" />\n </button>\n <button\n class=\"tpl:flex tpl:items-center tpl:justify-center tpl:rounded-md tpl:p-1.5 tpl:transition-colors tpl:duration-150 tpl:text-[var(--tpl-text-muted)]\"\n @click=\"cancelReply()\"\n >\n <X :size=\"14\" :stroke-width=\"2\" />\n </button>\n </div>\n </div>\n </div>\n </Transition>\n </div>\n </div>\n </div>\n\n <!-- New comment input -->\n <div class=\"tpl:border-t tpl:p-3 tpl:border-[var(--tpl-border)]\">\n <div\n v-if=\"isBlockFilterUnsaved\"\n class=\"tpl:flex tpl:min-h-[68px] tpl:items-center tpl:rounded-md tpl:px-3 tpl:py-2 tpl:text-xs tpl:bg-[var(--tpl-warning-light)] tpl:text-[var(--tpl-warning)]\"\n >\n {{ cloudT.comments.saveTemplateFirst }}\n </div>\n <div\n v-else\n class=\"tpl-comments-input-wrapper tpl:flex tpl:min-h-[68px] tpl:items-end tpl:gap-2 tpl:rounded-[var(--tpl-radius)] tpl:border tpl:px-3 tpl:py-2 tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)]\"\n >\n <textarea\n ref=\"newCommentInput\"\n v-model=\"newCommentBody\"\n class=\"tpl:max-h-24 tpl:min-h-[48px] tpl:flex-1 tpl:resize-none tpl:border-none tpl:bg-transparent tpl:font-sans tpl:text-xs tpl:outline-none tpl:text-[var(--tpl-text)]\"\n :placeholder=\"cloudT.comments.placeholder\"\n :disabled=\"comments.isSubmitting.value\"\n rows=\"2\"\n @keydown=\"handleNewCommentKeydown\"\n />\n <button\n class=\"tpl-comments-send-btn tpl:flex tpl:shrink-0 tpl:items-center tpl:justify-center tpl:rounded-md tpl:p-1.5 tpl:transition-all tpl:duration-150 tpl:disabled:opacity-40 tpl:text-[var(--tpl-primary)]\"\n :disabled=\"!newCommentBody.trim() || comments.isSubmitting.value\"\n @click=\"handleAddComment\"\n >\n <LoaderCircle\n v-if=\"comments.isSubmitting.value\"\n class=\"tpl-spinner\"\n :size=\"16\"\n :stroke-width=\"2\"\n />\n <Send v-else :size=\"16\" :stroke-width=\"2\" />\n </button>\n </div>\n </div>\n </div>\n </Transition>\n</template>\n\n<style scoped>\n.tpl-comments-slide-enter-active {\n transition: transform 280ms cubic-bezier(0.16, 1, 0.3, 1);\n}\n\n.tpl-comments-slide-leave-active {\n transition: transform 200ms cubic-bezier(0.16, 1, 0.3, 1);\n}\n\n.tpl-comments-input-wrapper:focus-within {\n border-color: var(--tpl-primary);\n box-shadow: var(--tpl-ring);\n}\n\n.tpl-comments-send-btn:not(:disabled):hover {\n transform: scale(1.05);\n}\n\n.tpl-comment-filter {\n color: var(--tpl-text-muted);\n background-color: transparent;\n}\n\n.tpl-comment-filter:hover {\n background-color: var(--tpl-bg-hover);\n color: var(--tpl-text);\n}\n\n.tpl-comment-filter--active {\n background-color: var(--tpl-primary-light);\n color: var(--tpl-primary);\n}\n\n.tpl-comment-action {\n color: var(--tpl-text-dim);\n background-color: transparent;\n}\n\n.tpl-comment-action:hover {\n background-color: var(--tpl-bg-hover);\n color: var(--tpl-text);\n}\n\n.tpl-comment-delete:hover {\n background-color: var(--tpl-danger-light);\n color: var(--tpl-danger);\n}\n\n.tpl-comment-card {\n border-color: var(--tpl-border);\n background-color: var(--tpl-bg);\n}\n\n.tpl-comment-reply-card {\n border-color: var(--tpl-border);\n background-color: var(--tpl-bg);\n}\n\n.tpl-resolve-enter-active,\n.tpl-resolve-leave-active {\n transition:\n opacity 0.3s ease,\n transform 0.3s ease;\n}\n\n.tpl-resolve-enter-from,\n.tpl-resolve-leave-to {\n opacity: 0;\n transform: translateY(-4px);\n}\n\n.tpl-resolve-icon {\n transition:\n color 0.3s ease,\n transform 0.2s ease;\n}\n\n.tpl-resolve-icon:active {\n transform: scale(1.3);\n}\n\n.tpl-replies-enter-active,\n.tpl-replies-leave-active {\n transition:\n opacity 0.25s ease,\n transform 0.25s ease;\n}\n\n.tpl-replies-enter-from,\n.tpl-replies-leave-to {\n opacity: 0;\n transform: translateY(-4px);\n}\n</style>\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
- import { M as e, P as t, Z as n, ct as r, f as i, g as a, h as o, it as s, p as c, st as l, u, x as d } from "./draggable-BRF_Q_jB.js";
2
- import { g as f } from "./dist-Dp46rwVY.js";
3
- import { t as p } from "./useI18n-aRMtgYRj.js";
1
+ import { M as e, P as t, Z as n, ct as r, f as i, g as a, h as o, it as s, p as c, st as l, u, x as d } from "./draggable-Z4XXmbPC.js";
2
+ import { g as f } from "./dist-B4VIlp1g.js";
3
+ import { t as p } from "./useI18n-BvcEYzrI.js";
4
4
  //#region src/components/blocks/CountdownBlock.vue?vue&type=script&setup=true&lang.ts
5
5
  var m = {
6
6
  key: 0,
@@ -90,4 +90,4 @@ var m = {
90
90
  //#endregion
91
91
  export { _ as default };
92
92
 
93
- //# sourceMappingURL=CountdownBlock-D2Oth7YY.js.map
93
+ //# sourceMappingURL=CountdownBlock-BTpRd0Hn.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"CountdownBlock-D2Oth7YY.js","names":[],"sources":["../../../src/components/blocks/CountdownBlock.vue","../../../src/components/blocks/CountdownBlock.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type {\n CountdownBlock as CountdownBlockType,\n ViewportSize,\n} from \"@templatical/types\";\nimport { useIntervalFn } from \"@vueuse/core\";\nimport { computed, ref } from \"vue\";\nimport { useI18n } from \"../../composables/useI18n\";\n\nconst props = defineProps<{\n block: CountdownBlockType;\n viewport: ViewportSize;\n}>();\n\nconst { t } = useI18n();\n\nconst now = ref(Date.now());\nuseIntervalFn(() => {\n now.value = Date.now();\n}, 1000);\n\nconst targetTime = computed(() => {\n if (!props.block.targetDate) return null;\n const date = new Date(props.block.targetDate);\n return isNaN(date.getTime()) ? null : date.getTime();\n});\n\nconst isExpired = computed(() => {\n if (!targetTime.value) return false;\n return now.value >= targetTime.value;\n});\n\nconst remaining = computed(() => {\n if (!targetTime.value || isExpired.value) {\n return { days: 0, hours: 0, minutes: 0, seconds: 0 };\n }\n const diff = targetTime.value - now.value;\n const totalSeconds = Math.floor(diff / 1000);\n return {\n days: Math.floor(totalSeconds / 86400),\n hours: Math.floor((totalSeconds % 86400) / 3600),\n minutes: Math.floor((totalSeconds % 3600) / 60),\n seconds: totalSeconds % 60,\n };\n});\n\nconst segments = computed(() => {\n const result: Array<{ value: string; label: string }> = [];\n if (props.block.showDays) {\n result.push({\n value: String(remaining.value.days).padStart(2, \"0\"),\n label: props.block.labelDays,\n });\n }\n if (props.block.showHours) {\n result.push({\n value: String(remaining.value.hours).padStart(2, \"0\"),\n label: props.block.labelHours,\n });\n }\n if (props.block.showMinutes) {\n result.push({\n value: String(remaining.value.minutes).padStart(2, \"0\"),\n label: props.block.labelMinutes,\n });\n }\n if (props.block.showSeconds) {\n result.push({\n value: String(remaining.value.seconds).padStart(2, \"0\"),\n label: props.block.labelSeconds,\n });\n }\n return result;\n});\n\nconst containerStyle = computed(() => ({\n backgroundColor: props.block.backgroundColor,\n fontFamily: props.block.fontFamily || \"inherit\",\n}));\n\nconst digitStyle = computed(() => ({\n fontSize: `${props.block.digitFontSize}px`,\n fontWeight: \"bold\" as const,\n color: props.block.digitColor,\n lineHeight: \"1.2\",\n}));\n\nconst labelStyle = computed(() => ({\n fontSize: `${props.block.labelFontSize}px`,\n color: props.block.labelColor,\n marginTop: \"4px\",\n}));\n\nconst separatorStyle = computed(() => ({\n fontSize: `${props.block.digitFontSize}px`,\n color: props.block.digitColor,\n fontFamily: props.block.fontFamily || \"inherit\",\n}));\n</script>\n\n<template>\n <!-- Empty state: no target date -->\n <div\n v-if=\"!block.targetDate\"\n class=\"tpl:flex tpl:items-center tpl:justify-center tpl:py-8 tpl:text-sm tpl:text-[var(--tpl-text-dim)]\"\n >\n {{ t.countdown.setDate }}\n </div>\n\n <!-- Expired + hidden -->\n <div\n v-else-if=\"isExpired && block.hideOnExpiry\"\n class=\"tpl:flex tpl:items-center tpl:justify-center tpl:py-4 tpl:text-xs tpl:text-[var(--tpl-text-dim)] tpl:opacity-50\"\n >\n {{ t.countdown.hidden }}\n </div>\n\n <!-- Expired with message -->\n <div\n v-else-if=\"isExpired\"\n :style=\"containerStyle\"\n class=\"tpl:py-4 tpl:text-center\"\n >\n <span\n :style=\"{\n fontSize: `${block.digitFontSize}px`,\n color: block.digitColor,\n }\"\n >\n {{ block.expiredMessage }}\n </span>\n </div>\n\n <!-- Active countdown -->\n <div\n v-else\n :style=\"containerStyle\"\n class=\"tpl:flex tpl:items-center tpl:justify-center tpl:gap-2 tpl:py-4\"\n >\n <template v-for=\"(segment, index) in segments\" :key=\"segment.label\">\n <span v-if=\"index > 0\" :style=\"separatorStyle\" class=\"tpl:self-start\">\n {{ block.separator }}\n </span>\n <div class=\"tpl:text-center\">\n <div :style=\"digitStyle\">{{ segment.value }}</div>\n <div :style=\"labelStyle\">{{ segment.label }}</div>\n </div>\n </template>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport type {\n CountdownBlock as CountdownBlockType,\n ViewportSize,\n} from \"@templatical/types\";\nimport { useIntervalFn } from \"@vueuse/core\";\nimport { computed, ref } from \"vue\";\nimport { useI18n } from \"../../composables/useI18n\";\n\nconst props = defineProps<{\n block: CountdownBlockType;\n viewport: ViewportSize;\n}>();\n\nconst { t } = useI18n();\n\nconst now = ref(Date.now());\nuseIntervalFn(() => {\n now.value = Date.now();\n}, 1000);\n\nconst targetTime = computed(() => {\n if (!props.block.targetDate) return null;\n const date = new Date(props.block.targetDate);\n return isNaN(date.getTime()) ? null : date.getTime();\n});\n\nconst isExpired = computed(() => {\n if (!targetTime.value) return false;\n return now.value >= targetTime.value;\n});\n\nconst remaining = computed(() => {\n if (!targetTime.value || isExpired.value) {\n return { days: 0, hours: 0, minutes: 0, seconds: 0 };\n }\n const diff = targetTime.value - now.value;\n const totalSeconds = Math.floor(diff / 1000);\n return {\n days: Math.floor(totalSeconds / 86400),\n hours: Math.floor((totalSeconds % 86400) / 3600),\n minutes: Math.floor((totalSeconds % 3600) / 60),\n seconds: totalSeconds % 60,\n };\n});\n\nconst segments = computed(() => {\n const result: Array<{ value: string; label: string }> = [];\n if (props.block.showDays) {\n result.push({\n value: String(remaining.value.days).padStart(2, \"0\"),\n label: props.block.labelDays,\n });\n }\n if (props.block.showHours) {\n result.push({\n value: String(remaining.value.hours).padStart(2, \"0\"),\n label: props.block.labelHours,\n });\n }\n if (props.block.showMinutes) {\n result.push({\n value: String(remaining.value.minutes).padStart(2, \"0\"),\n label: props.block.labelMinutes,\n });\n }\n if (props.block.showSeconds) {\n result.push({\n value: String(remaining.value.seconds).padStart(2, \"0\"),\n label: props.block.labelSeconds,\n });\n }\n return result;\n});\n\nconst containerStyle = computed(() => ({\n backgroundColor: props.block.backgroundColor,\n fontFamily: props.block.fontFamily || \"inherit\",\n}));\n\nconst digitStyle = computed(() => ({\n fontSize: `${props.block.digitFontSize}px`,\n fontWeight: \"bold\" as const,\n color: props.block.digitColor,\n lineHeight: \"1.2\",\n}));\n\nconst labelStyle = computed(() => ({\n fontSize: `${props.block.labelFontSize}px`,\n color: props.block.labelColor,\n marginTop: \"4px\",\n}));\n\nconst separatorStyle = computed(() => ({\n fontSize: `${props.block.digitFontSize}px`,\n color: props.block.digitColor,\n fontFamily: props.block.fontFamily || \"inherit\",\n}));\n</script>\n\n<template>\n <!-- Empty state: no target date -->\n <div\n v-if=\"!block.targetDate\"\n class=\"tpl:flex tpl:items-center tpl:justify-center tpl:py-8 tpl:text-sm tpl:text-[var(--tpl-text-dim)]\"\n >\n {{ t.countdown.setDate }}\n </div>\n\n <!-- Expired + hidden -->\n <div\n v-else-if=\"isExpired && block.hideOnExpiry\"\n class=\"tpl:flex tpl:items-center tpl:justify-center tpl:py-4 tpl:text-xs tpl:text-[var(--tpl-text-dim)] tpl:opacity-50\"\n >\n {{ t.countdown.hidden }}\n </div>\n\n <!-- Expired with message -->\n <div\n v-else-if=\"isExpired\"\n :style=\"containerStyle\"\n class=\"tpl:py-4 tpl:text-center\"\n >\n <span\n :style=\"{\n fontSize: `${block.digitFontSize}px`,\n color: block.digitColor,\n }\"\n >\n {{ block.expiredMessage }}\n </span>\n </div>\n\n <!-- Active countdown -->\n <div\n v-else\n :style=\"containerStyle\"\n class=\"tpl:flex tpl:items-center tpl:justify-center tpl:gap-2 tpl:py-4\"\n >\n <template v-for=\"(segment, index) in segments\" :key=\"segment.label\">\n <span v-if=\"index > 0\" :style=\"separatorStyle\" class=\"tpl:self-start\">\n {{ block.separator }}\n </span>\n <div class=\"tpl:text-center\">\n <div :style=\"digitStyle\">{{ segment.value }}</div>\n <div :style=\"labelStyle\">{{ segment.label }}</div>\n </div>\n </template>\n </div>\n</template>\n"],"mappings":"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"}
1
+ {"version":3,"file":"CountdownBlock-BTpRd0Hn.js","names":[],"sources":["../../../src/components/blocks/CountdownBlock.vue","../../../src/components/blocks/CountdownBlock.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type {\n CountdownBlock as CountdownBlockType,\n ViewportSize,\n} from \"@templatical/types\";\nimport { useIntervalFn } from \"@vueuse/core\";\nimport { computed, ref } from \"vue\";\nimport { useI18n } from \"../../composables/useI18n\";\n\nconst props = defineProps<{\n block: CountdownBlockType;\n viewport: ViewportSize;\n}>();\n\nconst { t } = useI18n();\n\nconst now = ref(Date.now());\nuseIntervalFn(() => {\n now.value = Date.now();\n}, 1000);\n\nconst targetTime = computed(() => {\n if (!props.block.targetDate) return null;\n const date = new Date(props.block.targetDate);\n return isNaN(date.getTime()) ? null : date.getTime();\n});\n\nconst isExpired = computed(() => {\n if (!targetTime.value) return false;\n return now.value >= targetTime.value;\n});\n\nconst remaining = computed(() => {\n if (!targetTime.value || isExpired.value) {\n return { days: 0, hours: 0, minutes: 0, seconds: 0 };\n }\n const diff = targetTime.value - now.value;\n const totalSeconds = Math.floor(diff / 1000);\n return {\n days: Math.floor(totalSeconds / 86400),\n hours: Math.floor((totalSeconds % 86400) / 3600),\n minutes: Math.floor((totalSeconds % 3600) / 60),\n seconds: totalSeconds % 60,\n };\n});\n\nconst segments = computed(() => {\n const result: Array<{ value: string; label: string }> = [];\n if (props.block.showDays) {\n result.push({\n value: String(remaining.value.days).padStart(2, \"0\"),\n label: props.block.labelDays,\n });\n }\n if (props.block.showHours) {\n result.push({\n value: String(remaining.value.hours).padStart(2, \"0\"),\n label: props.block.labelHours,\n });\n }\n if (props.block.showMinutes) {\n result.push({\n value: String(remaining.value.minutes).padStart(2, \"0\"),\n label: props.block.labelMinutes,\n });\n }\n if (props.block.showSeconds) {\n result.push({\n value: String(remaining.value.seconds).padStart(2, \"0\"),\n label: props.block.labelSeconds,\n });\n }\n return result;\n});\n\nconst containerStyle = computed(() => ({\n backgroundColor: props.block.backgroundColor,\n fontFamily: props.block.fontFamily || \"inherit\",\n}));\n\nconst digitStyle = computed(() => ({\n fontSize: `${props.block.digitFontSize}px`,\n fontWeight: \"bold\" as const,\n color: props.block.digitColor,\n lineHeight: \"1.2\",\n}));\n\nconst labelStyle = computed(() => ({\n fontSize: `${props.block.labelFontSize}px`,\n color: props.block.labelColor,\n marginTop: \"4px\",\n}));\n\nconst separatorStyle = computed(() => ({\n fontSize: `${props.block.digitFontSize}px`,\n color: props.block.digitColor,\n fontFamily: props.block.fontFamily || \"inherit\",\n}));\n</script>\n\n<template>\n <!-- Empty state: no target date -->\n <div\n v-if=\"!block.targetDate\"\n class=\"tpl:flex tpl:items-center tpl:justify-center tpl:py-8 tpl:text-sm tpl:text-[var(--tpl-text-dim)]\"\n >\n {{ t.countdown.setDate }}\n </div>\n\n <!-- Expired + hidden -->\n <div\n v-else-if=\"isExpired && block.hideOnExpiry\"\n class=\"tpl:flex tpl:items-center tpl:justify-center tpl:py-4 tpl:text-xs tpl:text-[var(--tpl-text-dim)] tpl:opacity-50\"\n >\n {{ t.countdown.hidden }}\n </div>\n\n <!-- Expired with message -->\n <div\n v-else-if=\"isExpired\"\n :style=\"containerStyle\"\n class=\"tpl:py-4 tpl:text-center\"\n >\n <span\n :style=\"{\n fontSize: `${block.digitFontSize}px`,\n color: block.digitColor,\n }\"\n >\n {{ block.expiredMessage }}\n </span>\n </div>\n\n <!-- Active countdown -->\n <div\n v-else\n :style=\"containerStyle\"\n class=\"tpl:flex tpl:items-center tpl:justify-center tpl:gap-2 tpl:py-4\"\n >\n <template v-for=\"(segment, index) in segments\" :key=\"segment.label\">\n <span v-if=\"index > 0\" :style=\"separatorStyle\" class=\"tpl:self-start\">\n {{ block.separator }}\n </span>\n <div class=\"tpl:text-center\">\n <div :style=\"digitStyle\">{{ segment.value }}</div>\n <div :style=\"labelStyle\">{{ segment.label }}</div>\n </div>\n </template>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport type {\n CountdownBlock as CountdownBlockType,\n ViewportSize,\n} from \"@templatical/types\";\nimport { useIntervalFn } from \"@vueuse/core\";\nimport { computed, ref } from \"vue\";\nimport { useI18n } from \"../../composables/useI18n\";\n\nconst props = defineProps<{\n block: CountdownBlockType;\n viewport: ViewportSize;\n}>();\n\nconst { t } = useI18n();\n\nconst now = ref(Date.now());\nuseIntervalFn(() => {\n now.value = Date.now();\n}, 1000);\n\nconst targetTime = computed(() => {\n if (!props.block.targetDate) return null;\n const date = new Date(props.block.targetDate);\n return isNaN(date.getTime()) ? null : date.getTime();\n});\n\nconst isExpired = computed(() => {\n if (!targetTime.value) return false;\n return now.value >= targetTime.value;\n});\n\nconst remaining = computed(() => {\n if (!targetTime.value || isExpired.value) {\n return { days: 0, hours: 0, minutes: 0, seconds: 0 };\n }\n const diff = targetTime.value - now.value;\n const totalSeconds = Math.floor(diff / 1000);\n return {\n days: Math.floor(totalSeconds / 86400),\n hours: Math.floor((totalSeconds % 86400) / 3600),\n minutes: Math.floor((totalSeconds % 3600) / 60),\n seconds: totalSeconds % 60,\n };\n});\n\nconst segments = computed(() => {\n const result: Array<{ value: string; label: string }> = [];\n if (props.block.showDays) {\n result.push({\n value: String(remaining.value.days).padStart(2, \"0\"),\n label: props.block.labelDays,\n });\n }\n if (props.block.showHours) {\n result.push({\n value: String(remaining.value.hours).padStart(2, \"0\"),\n label: props.block.labelHours,\n });\n }\n if (props.block.showMinutes) {\n result.push({\n value: String(remaining.value.minutes).padStart(2, \"0\"),\n label: props.block.labelMinutes,\n });\n }\n if (props.block.showSeconds) {\n result.push({\n value: String(remaining.value.seconds).padStart(2, \"0\"),\n label: props.block.labelSeconds,\n });\n }\n return result;\n});\n\nconst containerStyle = computed(() => ({\n backgroundColor: props.block.backgroundColor,\n fontFamily: props.block.fontFamily || \"inherit\",\n}));\n\nconst digitStyle = computed(() => ({\n fontSize: `${props.block.digitFontSize}px`,\n fontWeight: \"bold\" as const,\n color: props.block.digitColor,\n lineHeight: \"1.2\",\n}));\n\nconst labelStyle = computed(() => ({\n fontSize: `${props.block.labelFontSize}px`,\n color: props.block.labelColor,\n marginTop: \"4px\",\n}));\n\nconst separatorStyle = computed(() => ({\n fontSize: `${props.block.digitFontSize}px`,\n color: props.block.digitColor,\n fontFamily: props.block.fontFamily || \"inherit\",\n}));\n</script>\n\n<template>\n <!-- Empty state: no target date -->\n <div\n v-if=\"!block.targetDate\"\n class=\"tpl:flex tpl:items-center tpl:justify-center tpl:py-8 tpl:text-sm tpl:text-[var(--tpl-text-dim)]\"\n >\n {{ t.countdown.setDate }}\n </div>\n\n <!-- Expired + hidden -->\n <div\n v-else-if=\"isExpired && block.hideOnExpiry\"\n class=\"tpl:flex tpl:items-center tpl:justify-center tpl:py-4 tpl:text-xs tpl:text-[var(--tpl-text-dim)] tpl:opacity-50\"\n >\n {{ t.countdown.hidden }}\n </div>\n\n <!-- Expired with message -->\n <div\n v-else-if=\"isExpired\"\n :style=\"containerStyle\"\n class=\"tpl:py-4 tpl:text-center\"\n >\n <span\n :style=\"{\n fontSize: `${block.digitFontSize}px`,\n color: block.digitColor,\n }\"\n >\n {{ block.expiredMessage }}\n </span>\n </div>\n\n <!-- Active countdown -->\n <div\n v-else\n :style=\"containerStyle\"\n class=\"tpl:flex tpl:items-center tpl:justify-center tpl:gap-2 tpl:py-4\"\n >\n <template v-for=\"(segment, index) in segments\" :key=\"segment.label\">\n <span v-if=\"index > 0\" :style=\"separatorStyle\" class=\"tpl:self-start\">\n {{ block.separator }}\n </span>\n <div class=\"tpl:text-center\">\n <div :style=\"digitStyle\">{{ segment.value }}</div>\n <div :style=\"labelStyle\">{{ segment.label }}</div>\n </div>\n </template>\n </div>\n</template>\n"],"mappings":"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"}