ArticleMetadataDialog.vue 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. <!-- Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/ -->
  2. <script setup lang="ts">
  3. import { computed } from 'vue'
  4. import type { TicketArticle } from '#shared/entities/ticket/types.ts'
  5. import { getArticleChannelIcon } from '#shared/entities/ticket-article/composables/getArticleChannelIcon.ts'
  6. import { translateArticleSecurity } from '#shared/entities/ticket-article/composables/translateArticleSecurity.ts'
  7. import CommonDialog from '#mobile/components/CommonDialog/CommonDialog.vue'
  8. import CommonSectionMenu from '#mobile/components/CommonSectionMenu/CommonSectionMenu.vue'
  9. import CommonSectionMenuItem from '#mobile/components/CommonSectionMenu/CommonSectionMenuItem.vue'
  10. import ArticleMetadataAddress from './ArticleMetadataAddress.vue'
  11. interface Props {
  12. name: string
  13. article: TicketArticle
  14. ticketInternalId: number
  15. }
  16. const props = defineProps<Props>()
  17. const channelIcon = computed(() => {
  18. const name = props.article.type?.name
  19. if (name) return getArticleChannelIcon(name)
  20. return undefined
  21. })
  22. const articleDeliveryStatus = computed(() => {
  23. const { article } = props
  24. if (article.preferences?.whatsapp?.timestamp_read) {
  25. return { message: __('read by the customer'), icon: 'check-double-circle' }
  26. }
  27. if (article.preferences?.whatsapp?.timestamp_delivered) {
  28. return { message: __('delivered to the customer'), icon: 'check-double' }
  29. }
  30. if (article.preferences?.whatsapp?.timestamp_sent) {
  31. return { message: __('sent to the customer'), icon: 'check' }
  32. }
  33. return undefined
  34. })
  35. const links = computed(() => {
  36. const { article } = props
  37. // Example for usage: https://github.com/zammad/zammad/blob/develop/app/jobs/communicate_twitter_job.rb#L65
  38. const links = [...(article.preferences?.links || [])]
  39. if (article.type?.name === 'email') {
  40. links.push({
  41. label: __('Raw'),
  42. api: true,
  43. url: `/ticket_article_plain/${article.internalId}`,
  44. target: '_blank',
  45. })
  46. }
  47. article.attachmentsWithoutInline.forEach((file) => {
  48. if (file.preferences?.['original-format'] !== true) {
  49. return
  50. }
  51. const articleInternalId = props.article.internalId
  52. const attachmentInternalId = file.internalId
  53. const { ticketInternalId } = props
  54. const url = `/ticket_attachment/${ticketInternalId}/${articleInternalId}/${attachmentInternalId}?disposition=attachment`
  55. links.push({
  56. label: __('Original Formatting'),
  57. api: true,
  58. url,
  59. target: '_blank',
  60. })
  61. })
  62. return links
  63. })
  64. const isEncrypted = computed(
  65. () =>
  66. props.article.securityState?.encryptionSuccess ||
  67. (props.article.securityState?.encryptionSuccess === false &&
  68. props.article.securityState?.encryptionMessage),
  69. )
  70. const isSigned = computed(
  71. () =>
  72. props.article.securityState?.signingSuccess ||
  73. (props.article.securityState?.signingSuccess === false &&
  74. props.article.securityState?.signingMessage),
  75. )
  76. const hasSecurityAttribute = computed(
  77. () => props.article.securityState && (isEncrypted.value || isSigned.value),
  78. )
  79. </script>
  80. <template>
  81. <CommonDialog :label="__('Meta Data')" :name="name" class="p-4">
  82. <CommonSectionMenu>
  83. <ArticleMetadataAddress :address="article.from" :label="__('From')" />
  84. <ArticleMetadataAddress
  85. :address="article.replyTo"
  86. :label="__('Reply-To')"
  87. />
  88. <ArticleMetadataAddress :address="article.to" :label="__('To')" />
  89. <ArticleMetadataAddress :address="article.cc" :label="__('CC')" />
  90. <CommonSectionMenuItem v-if="article.subject" :label="__('Subject')">
  91. <div>{{ article.subject }}</div>
  92. </CommonSectionMenuItem>
  93. <CommonSectionMenuItem v-if="article.type?.name" :label="__('Channel')">
  94. <span class="inline-flex items-center gap-1">
  95. <CommonIcon
  96. v-if="channelIcon"
  97. :name="channelIcon"
  98. size="tiny"
  99. class="inline"
  100. />
  101. {{ $t(article.type.name) }}
  102. </span>
  103. <div class="leading-3">
  104. <CommonLink
  105. v-for="{ url, api, label, target } of links"
  106. :key="url"
  107. :link="url"
  108. :rest-api="api"
  109. :target="target"
  110. class="text-sm text-white/75 after:inline after:content-['|'] last:after:hidden ltr:mr-1 ltr:after:ml-1 rtl:ml-1 rtl:after:mr-1"
  111. >
  112. {{ $t(label) }}
  113. </CommonLink>
  114. </div>
  115. </CommonSectionMenuItem>
  116. <CommonSectionMenuItem
  117. v-if="articleDeliveryStatus"
  118. :label="__('Message Status')"
  119. >
  120. <CommonIcon
  121. :name="articleDeliveryStatus.icon"
  122. size="tiny"
  123. class="inline"
  124. />
  125. {{ $t(articleDeliveryStatus.message) }}
  126. </CommonSectionMenuItem>
  127. <CommonSectionMenuItem :label="__('Created')">
  128. <CommonDateTime :date-time="article.createdAt" type="absolute" />
  129. </CommonSectionMenuItem>
  130. <CommonSectionMenuItem
  131. v-if="hasSecurityAttribute"
  132. :label="__('Security')"
  133. >
  134. <div class="flex flex-col gap-1">
  135. <span v-if="article.securityState?.type">
  136. {{ translateArticleSecurity(article.securityState.type) }}
  137. </span>
  138. <span v-if="isEncrypted">
  139. <CommonIcon
  140. class="mb-1 inline"
  141. size="tiny"
  142. :name="
  143. article.securityState?.encryptionSuccess
  144. ? 'lock'
  145. : 'encryption-error'
  146. "
  147. />
  148. {{
  149. article.securityState?.encryptionSuccess
  150. ? $t('Encrypted')
  151. : $t('Encryption error')
  152. }}
  153. <div
  154. v-if="article.securityState?.encryptionMessage"
  155. class="ms-5 break-all"
  156. >
  157. {{ $t(article.securityState.encryptionMessage) }}
  158. </div>
  159. </span>
  160. <span v-if="isSigned">
  161. <CommonIcon
  162. class="mb-1 inline"
  163. size="tiny"
  164. :name="
  165. article.securityState?.signingSuccess ? 'signed' : 'not-signed'
  166. "
  167. />
  168. {{
  169. article.securityState?.signingSuccess
  170. ? $t('Signed')
  171. : $t('Sign error')
  172. }}
  173. <div
  174. v-if="article.securityState?.signingMessage"
  175. class="ms-5 break-all"
  176. >
  177. {{ $t(article.securityState.signingMessage) }}
  178. </div>
  179. </span>
  180. </div>
  181. </CommonSectionMenuItem>
  182. </CommonSectionMenu>
  183. </CommonDialog>
  184. </template>