email.ts 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. // Copyright (C) 2012-2023 Zammad Foundation, https://zammad-foundation.org/
  2. import { getTicketSignatureQuery } from '@shared/composables/useTicketSignature'
  3. import type { TicketArticle, TicketById } from '@shared/entities/ticket/types'
  4. import { getIdFromGraphQLId } from '@shared/graphql/utils'
  5. import { uniq } from 'lodash-es'
  6. import { replyToEmail } from './email/reply'
  7. import type {
  8. TicketArticleAction,
  9. TicketArticleActionPlugin,
  10. TicketArticleSelectionOptions,
  11. TicketArticleType,
  12. } from './types'
  13. const canReplyAll = (article: TicketArticle) => {
  14. const addresses = [article.to, article.cc]
  15. if (article.sender?.name === 'Customer') {
  16. addresses.push(article.from)
  17. }
  18. const foreignRecipients = addresses
  19. .flatMap((address) => address?.parsed || [])
  20. .filter((address) => address.emailAddress && !address.isSystemAddress)
  21. .map((address) => address.emailAddress)
  22. return uniq(foreignRecipients).length > 1
  23. }
  24. const addSignature = async (
  25. ticket: TicketById,
  26. { body }: TicketArticleSelectionOptions,
  27. position?: number,
  28. ) => {
  29. const query = getTicketSignatureQuery()
  30. const signature = await query.trigger({
  31. groupId: ticket.group.id,
  32. ticketId: ticket.id,
  33. })
  34. const text = signature?.ticketSignature?.renderedBody
  35. const id = signature?.ticketSignature?.id
  36. if (!text || !id) {
  37. body.removeSignature()
  38. return
  39. }
  40. body.addSignature({
  41. body: text,
  42. id: getIdFromGraphQLId(id),
  43. position,
  44. })
  45. }
  46. const actionPlugin: TicketArticleActionPlugin = {
  47. order: 200,
  48. addActions(ticket, article, { config }) {
  49. if (!ticket.group.emailAddress) return []
  50. const type = article.type?.name
  51. const sender = article.sender?.name
  52. const actions: TicketArticleAction[] = []
  53. const isEmail = type === 'email' || type === 'web'
  54. const isPhone =
  55. type === 'phone' && (sender === 'Customer' || sender === 'Agent')
  56. if (isEmail || isPhone) {
  57. actions.push(
  58. {
  59. apps: ['mobile'],
  60. name: 'email-reply',
  61. view: { agent: ['change'] },
  62. label: __('Reply'),
  63. icon: { mobile: 'mobile-reply' },
  64. perform: (t, a, o) => replyToEmail(t, a, o, config),
  65. },
  66. // {
  67. // apps: ['mobile'],
  68. // name: 'email-forward',
  69. // view: { agent: ['change'] },
  70. // label: __('Forward'),
  71. // icon: { mobile: 'mobile-forward' },
  72. // },
  73. )
  74. }
  75. if (isEmail && canReplyAll(article)) {
  76. actions.push({
  77. apps: ['mobile'],
  78. name: 'email-reply-all',
  79. view: { agent: ['change'] },
  80. label: __('Reply All'),
  81. icon: { mobile: 'mobile-reply-alt' },
  82. perform: (t, a, o) => replyToEmail(t, a, o, config, true),
  83. })
  84. }
  85. return actions
  86. },
  87. addTypes(ticket, { config }) {
  88. if (!ticket.group.emailAddress) return []
  89. const attributes = new Set([
  90. 'to',
  91. 'cc',
  92. 'subject',
  93. 'subtype',
  94. 'attachments',
  95. 'security',
  96. ])
  97. if (!config.ui_ticket_zoom_article_email_subject)
  98. attributes.delete('subject')
  99. const type: TicketArticleType = {
  100. value: 'email',
  101. label: __('Email'),
  102. apps: ['mobile'],
  103. icon: { mobile: 'mobile-mail' },
  104. attributes: Array.from(attributes),
  105. view: { agent: ['change'] },
  106. onDeselected(_, { body }) {
  107. body.removeSignature()
  108. },
  109. onOpened(_, { body }) {
  110. // always reset position if reply is added as a new article
  111. return addSignature(ticket, { body }, 1)
  112. },
  113. // TODO: possible race condition
  114. onSelected(_, { body }) {
  115. // try to dynamically set cursor position, dependeing on where it was before signature was added
  116. return addSignature(ticket, { body })
  117. },
  118. internal: false,
  119. }
  120. return [type]
  121. },
  122. }
  123. export default actionPlugin