twitter.ts 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. // Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. import { isArray, isObject, uniq } from 'lodash-es'
  3. import type { FieldEditorProps } from '#shared/components/Form/fields/FieldEditor/types.ts'
  4. import type { FormValues } from '#shared/components/Form/types.ts'
  5. import { EnumTicketArticleSenderName } from '#shared/graphql/types.ts'
  6. import { useSessionStore } from '#shared/stores/session.ts'
  7. import type { ConfigList } from '#shared/types/store.ts'
  8. import type { ConfidentTake } from '#shared/types/utils.ts'
  9. import { getInitials } from '#shared/utils/formatter.ts'
  10. import type {
  11. TicketArticleAction,
  12. TicketArticleActionPlugin,
  13. TicketArticleType,
  14. } from './types.ts'
  15. const replyToTwitterComment = ((
  16. ticket,
  17. article,
  18. { openReplyForm, getNewArticleBody },
  19. ) => {
  20. const articleData: FormValues = {
  21. articleType: 'twitter status',
  22. inReplyTo: article.messageId,
  23. }
  24. const body = getNewArticleBody('text/plain')
  25. const recipients = article.from ? [article.from.raw] : []
  26. if (article.to) recipients.push(article.to.raw)
  27. const recipientsString = uniq(
  28. recipients.filter((recipient) => {
  29. recipient = recipient.trim().toLowerCase()
  30. if (body.toLowerCase().includes(recipient)) return false
  31. if (recipient === `@${ticket.preferences?.channel_screen_name}`)
  32. return false
  33. return true
  34. }),
  35. ).join(' ')
  36. if (body) articleData.body = `${recipientsString} ${body} `
  37. else articleData.body = `${recipientsString} `
  38. openReplyForm(articleData)
  39. }) satisfies TicketArticleAction['perform']
  40. const replyToTwitterDm = ((ticket, article, { openReplyForm }) => {
  41. const sender = article.sender?.name
  42. let to: string | undefined | null
  43. if (sender === EnumTicketArticleSenderName.Customer) to = article.from?.raw
  44. else if (sender === EnumTicketArticleSenderName.Agent) to = article.to?.raw
  45. if (!to) {
  46. const autorization = article.author.authorizations?.find(
  47. (a) => a.provider === 'twitter',
  48. )
  49. to = autorization?.username || autorization?.uid
  50. }
  51. const articleData: FormValues = {
  52. articleType: 'twitter direct-message',
  53. body: '',
  54. to: to ? [to] : [],
  55. inReplyTo: article.messageId,
  56. }
  57. openReplyForm(articleData)
  58. }) satisfies TicketArticleAction['perform']
  59. const getTwitterInitials = (config: ConfigList) => {
  60. if (config.ui_ticket_zoom_article_twitter_initials) {
  61. const { user } = useSessionStore()
  62. if (user) {
  63. const { firstname, lastname, email } = user
  64. return `/${getInitials(firstname, lastname, email)}`
  65. }
  66. }
  67. return null
  68. }
  69. const actionPlugin: TicketArticleActionPlugin = {
  70. order: 300,
  71. addActions(ticket, article) {
  72. const type = article.type?.name
  73. if (type !== 'twitter status' && type !== 'twitter direct-message')
  74. return []
  75. const action: TicketArticleAction = {
  76. apps: ['mobile', 'desktop'],
  77. label: __('Reply'),
  78. name: type,
  79. icon: 'reply',
  80. view: {
  81. agent: ['change'],
  82. },
  83. perform(ticket, article, options) {
  84. if (type === 'twitter status')
  85. return replyToTwitterComment(ticket, article, options)
  86. return replyToTwitterDm(ticket, article, options)
  87. },
  88. }
  89. return [action]
  90. },
  91. addTypes(ticket, { config }) {
  92. const descriptionType = ticket.createArticleType?.name
  93. if (
  94. descriptionType !== 'twitter status' &&
  95. descriptionType !== 'twitter direct-message'
  96. )
  97. return []
  98. const type: TicketArticleType = {
  99. apps: ['mobile', 'desktop'],
  100. value: descriptionType,
  101. label: __('Twitter'),
  102. buttonLabel: __('Add message'),
  103. icon: 'twitter',
  104. view: {
  105. agent: ['change'],
  106. },
  107. fields: {
  108. body: {
  109. required: true,
  110. },
  111. to: {},
  112. },
  113. internal: false,
  114. contentType: 'text/plain',
  115. updateForm(values) {
  116. if (!isObject(values.article) || isArray(values.article)) return values
  117. if (typeof values.article.body === 'string') {
  118. const initials = getTwitterInitials(config)
  119. values.article.body += initials ? `\n${initials}` : ''
  120. }
  121. return values
  122. },
  123. }
  124. let footer: ConfidentTake<FieldEditorProps, 'meta.footer'> = {}
  125. if (descriptionType === 'twitter status' && type.fields.body) {
  126. type.fields.body.validation = 'length:1,280'
  127. footer = {
  128. maxlength: 280,
  129. warningLength: 30,
  130. }
  131. } else if (type.fields.to && type.fields.body) {
  132. type.fields.to.required = true
  133. type.fields.body.validation = 'length:1,10000'
  134. footer = {
  135. maxlength: 10000,
  136. warningLength: 500,
  137. }
  138. }
  139. const initials = getTwitterInitials(config)
  140. if (initials) footer.text = initials
  141. type.editorMeta = {
  142. footer,
  143. }
  144. return [type]
  145. },
  146. }
  147. export default actionPlugin