useTicketArticleContext.ts 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. // Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. import { computed, ref, shallowRef } from 'vue'
  3. import { useTicketArticleReplyAction } from '#shared/entities/ticket/composables/useTicketArticleReplyAction.ts'
  4. import type {
  5. TicketArticle,
  6. TicketById,
  7. } from '#shared/entities/ticket/types.ts'
  8. import { createArticleActions } from '#shared/entities/ticket-article/action/plugins/index.ts'
  9. import { getArticleSelection } from '#shared/entities/ticket-article/composables/getArticleSelection.ts'
  10. import log from '#shared/utils/log.ts'
  11. import type { SelectionData } from '#shared/utils/selection.ts'
  12. import type { PopupItemDescriptor } from '#mobile/components/CommonSectionPopup/types.ts'
  13. import { useDialog } from '#mobile/composables/useDialog.ts'
  14. import { useTicketInformation } from './useTicketInformation.ts'
  15. export const useTicketArticleContext = () => {
  16. const articleForContext = shallowRef<TicketArticle>()
  17. const ticketForContext = shallowRef<TicketById>()
  18. const selectionData = ref<SelectionData>()
  19. const metadataDialog = useDialog({
  20. name: 'article-metadata',
  21. component: () =>
  22. import('../components/TicketDetailView/ArticleMetadataDialog.vue'),
  23. })
  24. const { showArticleReplyDialog, form } = useTicketInformation()
  25. const { openReplyForm, getNewArticleBody } = useTicketArticleReplyAction(
  26. form,
  27. showArticleReplyDialog,
  28. )
  29. const triggerId = ref(0)
  30. const recalculate = () => {
  31. triggerId.value += 1
  32. }
  33. const disposeCallbacks: (() => unknown)[] = []
  34. const onDispose = (callback: () => unknown) => {
  35. disposeCallbacks.push(callback)
  36. }
  37. const contextOptions = computed<PopupItemDescriptor[]>(() => {
  38. const ticket = ticketForContext.value
  39. const article = articleForContext.value
  40. // trigger ID cannot be less than 0, so it's just a hint for vue to recalculate computed
  41. if (!article || !ticket || triggerId.value < 0) return []
  42. // clear all side effects before recalculating
  43. disposeCallbacks.forEach((callback) => callback())
  44. disposeCallbacks.length = 0
  45. const actions = createArticleActions(ticket, article, 'mobile', {
  46. recalculate,
  47. onDispose,
  48. }).map<PopupItemDescriptor>((action) => {
  49. const { perform, link, label } = action
  50. if (!perform) return { ...action, type: 'link' }
  51. return {
  52. type: link ? 'link' : 'button',
  53. label,
  54. link,
  55. onAction: () =>
  56. perform(ticket, article, {
  57. formId: form.value?.formId || '',
  58. selection: selectionData.value,
  59. openReplyForm,
  60. getNewArticleBody,
  61. }),
  62. }
  63. })
  64. return [
  65. ...actions,
  66. {
  67. type: 'button',
  68. label: __('Show meta data'),
  69. onAction() {
  70. metadataDialog.open({
  71. name: metadataDialog.name,
  72. article,
  73. ticketInternalId: ticket.internalId,
  74. })
  75. },
  76. },
  77. ]
  78. })
  79. const articleContextShown = computed({
  80. get: () => articleForContext.value != null,
  81. set: (value) => {
  82. // we don't care for "true", because to make it truthy we
  83. // call showArticleContext
  84. // setting it to "false" is done via "update:modelValue"
  85. if (!value) {
  86. articleForContext.value = undefined
  87. disposeCallbacks.forEach((callback) => callback())
  88. disposeCallbacks.length = 0
  89. }
  90. },
  91. })
  92. const showArticleContext = (article: TicketArticle, ticket: TicketById) => {
  93. metadataDialog.prefetch()
  94. articleForContext.value = article
  95. ticketForContext.value = ticket
  96. try {
  97. // can throw RangeError
  98. selectionData.value = getArticleSelection(article.internalId)
  99. } catch (err) {
  100. log.error('[Article Quote] Failed to parse article selection', err)
  101. selectionData.value = undefined
  102. }
  103. }
  104. return {
  105. contextOptions,
  106. articleContextShown,
  107. showArticleContext,
  108. }
  109. }