email.ts 4.2 KB

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