CommonLink.vue 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. <!-- Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/ -->
  2. <script setup lang="ts">
  3. import { computed, toRef } from 'vue'
  4. import { useLink } from 'vue-router'
  5. import { getLinkClasses } from '#shared/initializer/initializeLinkClasses.ts'
  6. import { useApplicationStore } from '#shared/stores/application.ts'
  7. import type { Link } from '#shared/types/router.ts'
  8. import stopEvent from '#shared/utils/events.ts'
  9. export interface Props {
  10. link: Link
  11. external?: boolean
  12. internal?: boolean
  13. restApi?: boolean
  14. disabled?: boolean
  15. rel?: string
  16. target?: string
  17. openInNewTab?: boolean
  18. replace?: boolean
  19. activeClass?: string
  20. exactActiveClass?: string
  21. }
  22. const props = withDefaults(defineProps<Props>(), {
  23. external: false,
  24. internal: false,
  25. replace: false,
  26. append: false,
  27. openInNewTab: false,
  28. disabled: false,
  29. activeClass: 'router-link-active',
  30. exactActiveClass: 'router-link-exact-active',
  31. })
  32. const emit = defineEmits<{
  33. click: [event: MouseEvent]
  34. }>()
  35. const target = computed(() => {
  36. if (props.target) return props.target
  37. if (props.openInNewTab) return '_blank'
  38. return undefined
  39. })
  40. const linkClass = computed(() => {
  41. const { base } = getLinkClasses()
  42. if (props.disabled) return `${base} pointer-events-none`
  43. return base
  44. })
  45. const { href, route, navigate, isActive, isExactActive } = useLink({
  46. to: toRef(props, 'link'),
  47. replace: toRef(props, 'replace'),
  48. })
  49. const isInternalLink = computed(() => {
  50. if (props.external || props.restApi) return false
  51. if (props.internal) return true
  52. // zammad desktop urls
  53. if (route.value.fullPath.startsWith('/#')) return false
  54. return route.value.matched.length > 0 && route.value.name !== 'Error'
  55. })
  56. const app = useApplicationStore()
  57. const path = computed(() => {
  58. if (isInternalLink.value) {
  59. return href.value
  60. }
  61. if (props.restApi) {
  62. return `${app.config.api_path}${props.link}`
  63. }
  64. return props.link as string
  65. })
  66. const onClick = (event: MouseEvent) => {
  67. if (props.disabled) {
  68. stopEvent(event, { immediatePropagation: true })
  69. return
  70. }
  71. emit('click', event)
  72. if (isInternalLink.value) {
  73. navigate(event)
  74. }
  75. // Stop the scroll-to-top behavior or navigation on regular links when href is just '#'.
  76. if (!isInternalLink.value && props.link === '#') {
  77. stopEvent(event, { propagation: false })
  78. }
  79. }
  80. </script>
  81. <template>
  82. <a
  83. data-test-id="common-link"
  84. :href="path"
  85. :target="target"
  86. :rel="rel"
  87. :class="[
  88. linkClass,
  89. {
  90. [activeClass]: isActive,
  91. [exactActiveClass]: isExactActive,
  92. },
  93. ]"
  94. @click="onClick"
  95. >
  96. <slot></slot>
  97. </a>
  98. </template>