zammad/app/frontend/shared/composables/useArticleToggleMore.ts
2026-01-02 15:41:09 +02:00

83 lines
2.1 KiB
TypeScript

// Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
import { onMounted, ref } from 'vue'
import { waitForImagesToLoad } from '#shared/utils/dom.ts'
import { waitForAnimationFrame } from '#shared/utils/helpers.ts'
export const useArticleToggleMore = () => {
const MIN_HEIGHT = 60
const MAX_HEIGHT = 320
let heightHidden = 0
const bubbleElement = ref<HTMLDivElement>()
const hasShowMore = ref(true)
const shownMore = ref(false)
const getSignatureMarker = (element: HTMLElement): HTMLElement | null => {
const marker = element.querySelector('.js-signatureMarker') as HTMLElement
if (marker) return marker
return element.querySelector('div [data-signature=true]')
}
const setHeight = async () => {
if (!bubbleElement.value) return
const styles = bubbleElement.value.style
styles.height = ''
await waitForAnimationFrame()
// it's possible it was remounted somehow
if (!bubbleElement.value) return
const height = bubbleElement.value.clientHeight
const signatureMarker = getSignatureMarker(bubbleElement.value)
const offsetTop = signatureMarker?.offsetTop || 0
if (offsetTop > 0 && offsetTop < MAX_HEIGHT) {
heightHidden = offsetTop < MIN_HEIGHT ? MIN_HEIGHT : offsetTop
hasShowMore.value = true
} else if (height > MAX_HEIGHT) {
heightHidden = MAX_HEIGHT
hasShowMore.value = true
} else {
hasShowMore.value = false
heightHidden = 0
}
if (heightHidden) {
styles.height = `${heightHidden}px`
}
}
onMounted(async () => {
if (!bubbleElement.value) return
// Wait for inline images to load before calculating height
// Resolved immediately if no images are present
await waitForImagesToLoad(bubbleElement)
await setHeight()
})
const toggleShowMore = () => {
if (!bubbleElement.value) return
shownMore.value = !shownMore.value
const styles = bubbleElement.value.style
styles.height = shownMore.value ? 'auto' : `${heightHidden}px`
}
return {
toggleShowMore,
hasShowMore,
shownMore,
bubbleElement,
}
}