ArticleMetadataDialog.vue 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. <!-- Copyright (C) 2012-2023 Zammad Foundation, https://zammad-foundation.org/ -->
  2. <script setup lang="ts">
  3. import { getArticleChannelIcon } from '@shared/entities/ticket-article/composables/getArticleChannelIcon'
  4. import CommonDialog from '@mobile/components/CommonDialog/CommonDialog.vue'
  5. import CommonSectionMenu from '@mobile/components/CommonSectionMenu/CommonSectionMenu.vue'
  6. import CommonSectionMenuItem from '@mobile/components/CommonSectionMenu/CommonSectionMenuItem.vue'
  7. import { computed } from 'vue'
  8. import { i18n } from '@shared/i18n'
  9. import type { TicketArticle } from '@shared/entities/ticket/types'
  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 links = computed(() => {
  23. const { article } = props
  24. // Example for usage: https://github.com/zammad/zammad/blob/develop/app/jobs/communicate_twitter_job.rb#L65
  25. const links = [...(article.preferences?.links || [])]
  26. if (article.type?.name === 'email') {
  27. links.push({
  28. label: __('Raw'),
  29. api: true,
  30. url: `/ticket_article_plain/${article.internalId}`,
  31. target: '_blank',
  32. })
  33. }
  34. article.attachmentsWithoutInline.forEach((file) => {
  35. if (file.preferences?.['original-format'] !== true) {
  36. return
  37. }
  38. const articleInternalId = props.article.internalId
  39. const attachmentInternalId = file.internalId
  40. const { ticketInternalId } = props
  41. const url = `/ticket_attachment/${ticketInternalId}/${articleInternalId}/${attachmentInternalId}?disposition=attachment`
  42. links.push({
  43. label: __('Original Formatting'),
  44. api: true,
  45. url,
  46. target: '_blank',
  47. })
  48. })
  49. return links
  50. })
  51. const sign = computed(() => {
  52. const security = props.article.securityState
  53. if (!security || security.signingSuccess == null) return null
  54. return {
  55. message: security.signingMessage,
  56. success: security.signingSuccess,
  57. }
  58. })
  59. const encryptionMessage = computed(() => {
  60. const security = props.article.securityState
  61. if (!security?.encryptionSuccess) return null
  62. let message = i18n.t('Encrypted')
  63. if (security.encryptionMessage)
  64. message += ` (${i18n.t(security.encryptionMessage)})`
  65. return message
  66. })
  67. </script>
  68. <template>
  69. <CommonDialog :label="__('Meta Data')" :name="name" class="p-4">
  70. <CommonSectionMenu>
  71. <ArticleMetadataAddress :address="article.from" :label="__('From')" />
  72. <ArticleMetadataAddress
  73. :address="article.replyTo"
  74. :label="__('Reply-To')"
  75. />
  76. <ArticleMetadataAddress :address="article.to" :label="__('To')" />
  77. <ArticleMetadataAddress :address="article.cc" :label="__('CC')" />
  78. <CommonSectionMenuItem v-if="article.subject" :label="__('Subject')">
  79. <div>{{ article.subject }}</div>
  80. </CommonSectionMenuItem>
  81. <CommonSectionMenuItem v-if="article.type?.name" :label="__('Channel')">
  82. <span class="inline-flex items-center gap-1">
  83. <CommonIcon
  84. v-if="channelIcon"
  85. :name="channelIcon"
  86. size="tiny"
  87. class="inline"
  88. />
  89. {{ $t(article.type.name) }}
  90. </span>
  91. <div class="leading-3">
  92. <CommonLink
  93. v-for="{ url, api, label, target } of links"
  94. :key="url"
  95. :link="url"
  96. :rest-api="api"
  97. :target="target"
  98. 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"
  99. >
  100. {{ $t(label) }}
  101. </CommonLink>
  102. </div>
  103. </CommonSectionMenuItem>
  104. <CommonSectionMenuItem :label="__('Sent')">
  105. <CommonDateTime :date-time="article.createdAt" type="absolute" />
  106. </CommonSectionMenuItem>
  107. <!-- app/assets/javascripts/app/views/ticket_zoom/article_view.jst.eco:34 -->
  108. <CommonSectionMenuItem
  109. v-if="sign || encryptionMessage"
  110. :label="__('Security')"
  111. >
  112. <div class="flex gap-1">
  113. <span v-if="encryptionMessage" class="inline-flex items-center gap-1">
  114. <CommonIcon name="mobile-lock" size="tiny" />
  115. {{ encryptionMessage }}
  116. </span>
  117. <span
  118. v-if="sign"
  119. class="inline-flex items-center gap-1"
  120. :class="{ 'text-orange': !sign.success }"
  121. >
  122. <CommonIcon
  123. :name="sign.success ? 'mobile-signed' : 'mobile-not-signed'"
  124. size="tiny"
  125. />
  126. {{ sign.success ? $t('Signed') : $t('Unsigned') }}
  127. {{ sign.message ? ` (${sign.message})` : '' }}
  128. </span>
  129. </div>
  130. </CommonSectionMenuItem>
  131. </CommonSectionMenu>
  132. </CommonDialog>
  133. </template>