ArticleMetadataDialog.vue 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. <!-- Copyright (C) 2012-2025 Zammad Foundation, https://zammad-foundation.org/ -->
  2. <script setup lang="ts">
  3. import { computed, toRef } from 'vue'
  4. import ObjectAttributeContent from '#shared/components/ObjectAttributes/ObjectAttribute.vue'
  5. import { useArticleSecurity } from '#shared/composables/useArticleSecurity.ts'
  6. import { useObjectAttributes } from '#shared/entities/object-attributes/composables/useObjectAttributes.ts'
  7. import { useWhatsapp } from '#shared/entities/ticket/channel/composables/useWhatsapp.ts'
  8. import type { TicketArticle } from '#shared/entities/ticket/types.ts'
  9. import { getArticleChannelIcon } from '#shared/entities/ticket-article/composables/getArticleChannelIcon.ts'
  10. import { translateArticleSecurity } from '#shared/entities/ticket-article/composables/translateArticleSecurity.ts'
  11. import { EnumObjectManagerObjects } from '#shared/graphql/types.ts'
  12. import CommonDialog from '#mobile/components/CommonDialog/CommonDialog.vue'
  13. import CommonSectionMenu from '#mobile/components/CommonSectionMenu/CommonSectionMenu.vue'
  14. import CommonSectionMenuItem from '#mobile/components/CommonSectionMenu/CommonSectionMenuItem.vue'
  15. import ArticleMetadataAddress from './ArticleMetadataAddress.vue'
  16. interface Props {
  17. name: string
  18. article: TicketArticle
  19. ticketInternalId: number
  20. }
  21. const props = defineProps<Props>()
  22. const channelIcon = computed(() => {
  23. const name = props.article.type?.name
  24. if (name) return getArticleChannelIcon(name)
  25. return undefined
  26. })
  27. const { articleDeliveryStatus } = useWhatsapp(toRef(props.article))
  28. const links = computed(() => {
  29. const { article } = props
  30. // Example for usage: https://github.com/zammad/zammad/blob/develop/app/jobs/communicate_twitter_job.rb#L65
  31. const links = [...(article.preferences?.links || [])]
  32. if (article.type?.name === 'email') {
  33. links.push({
  34. label: __('Raw'),
  35. api: true,
  36. url: `/ticket_article_plain/${article.internalId}`,
  37. target: '_blank',
  38. })
  39. }
  40. article.attachmentsWithoutInline.forEach((file) => {
  41. if (file.preferences?.['original-format'] !== true) {
  42. return
  43. }
  44. const articleInternalId = props.article.internalId
  45. const attachmentInternalId = file.internalId
  46. const { ticketInternalId } = props
  47. const url = `/ticket_attachment/${ticketInternalId}/${articleInternalId}/${attachmentInternalId}?disposition=attachment`
  48. links.push({
  49. label: __('Original Formatting'),
  50. api: true,
  51. url,
  52. target: '_blank',
  53. })
  54. })
  55. return links
  56. })
  57. const {
  58. signingIcon,
  59. signingMessage,
  60. encryptionMessage,
  61. isEncrypted,
  62. isSigned,
  63. hasSecurityAttribute,
  64. encryptionIcon,
  65. encryptedStatusMessage,
  66. signedStatusMessage,
  67. } = useArticleSecurity(toRef(props.article))
  68. const { attributesLookup } = useObjectAttributes(
  69. EnumObjectManagerObjects.TicketArticle,
  70. )
  71. const detectedLanguageAttribute = computed(() =>
  72. attributesLookup.value.get('detected_language'),
  73. )
  74. </script>
  75. <template>
  76. <CommonDialog :label="__('Meta Data')" :name="name" class="p-4">
  77. <CommonSectionMenu>
  78. <ArticleMetadataAddress :address="article.from" :label="__('From')" />
  79. <ArticleMetadataAddress
  80. :address="article.replyTo"
  81. :label="__('Reply-To')"
  82. />
  83. <ArticleMetadataAddress :address="article.to" :label="__('To')" />
  84. <ArticleMetadataAddress :address="article.cc" :label="__('CC')" />
  85. <CommonSectionMenuItem v-if="article.subject" :label="__('Subject')">
  86. <div>{{ article.subject }}</div>
  87. </CommonSectionMenuItem>
  88. <CommonSectionMenuItem
  89. v-if="article.detectedLanguage"
  90. :label="__('Detected language')"
  91. >
  92. <ObjectAttributeContent
  93. v-if="detectedLanguageAttribute"
  94. :attribute="detectedLanguageAttribute"
  95. :object="article"
  96. />
  97. <div v-else>{{ article.detectedLanguage }}</div>
  98. </CommonSectionMenuItem>
  99. <CommonSectionMenuItem v-if="article.type?.name" :label="__('Channel')">
  100. <span class="inline-flex items-center gap-1">
  101. <CommonIcon
  102. v-if="channelIcon"
  103. :name="channelIcon"
  104. size="tiny"
  105. class="inline"
  106. />
  107. {{ $t(article.type.name) }}
  108. </span>
  109. <div class="leading-3">
  110. <CommonLink
  111. v-for="{ url, api, label, target } of links"
  112. :key="url"
  113. :link="url"
  114. :rest-api="api"
  115. :target="target"
  116. 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"
  117. >
  118. {{ $t(label) }}
  119. </CommonLink>
  120. </div>
  121. </CommonSectionMenuItem>
  122. <CommonSectionMenuItem
  123. v-if="articleDeliveryStatus"
  124. :label="__('Message Status')"
  125. >
  126. <CommonIcon
  127. :name="articleDeliveryStatus.icon"
  128. size="tiny"
  129. class="inline"
  130. />
  131. {{ $t(articleDeliveryStatus.message) }}
  132. </CommonSectionMenuItem>
  133. <CommonSectionMenuItem :label="__('Created')">
  134. <CommonDateTime :date-time="article.createdAt" type="absolute" />
  135. </CommonSectionMenuItem>
  136. <CommonSectionMenuItem
  137. v-if="hasSecurityAttribute"
  138. :label="__('Security')"
  139. >
  140. <div class="flex flex-col gap-1">
  141. <span v-if="article.securityState?.type">
  142. {{ translateArticleSecurity(article.securityState.type) }}
  143. </span>
  144. <span v-if="isEncrypted">
  145. <CommonIcon
  146. class="mb-1 inline"
  147. size="tiny"
  148. :name="encryptionIcon"
  149. />
  150. {{ $t(encryptedStatusMessage) }}
  151. <div v-if="encryptionMessage" class="ms-5 break-all">
  152. {{ $t(encryptionMessage) }}
  153. </div>
  154. </span>
  155. <span v-if="isSigned">
  156. <CommonIcon class="mb-1 inline" size="tiny" :name="signingIcon" />
  157. {{ $t(signedStatusMessage) }}
  158. <div v-if="signingMessage" class="ms-5 break-all">
  159. {{ $t(signingMessage) }}
  160. </div>
  161. </span>
  162. </div>
  163. </CommonSectionMenuItem>
  164. </CommonSectionMenu>
  165. </CommonDialog>
  166. </template>