ArticleMetadataDialog.vue 5.2 KB

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