2025-08-05 11:51:07 +01:00

92 lines
1.9 KiB
Vue

<template>
<div
class="parsed-log relative flex h-8 w-full items-center overflow-hidden rounded-lg px-6"
@mouseenter="checkOverflow"
@touchstart="checkOverflow"
>
<div ref="logContent" class="log-content flex-1 truncate whitespace-pre">
<span v-html="sanitizedLog"></span>
</div>
<button
v-if="isOverflowing"
class="ml-2 flex h-6 items-center rounded-md bg-bg px-2 text-xs text-contrast opacity-50 transition-opacity hover:opacity-100"
type="button"
@click.stop="$emit('show-full-log', props.log)"
>
...
</button>
</div>
</template>
<script setup lang="ts">
import Convert from 'ansi-to-html'
import DOMPurify from 'dompurify'
import { computed, onMounted, onUnmounted, ref } from 'vue'
const props = defineProps<{
log: string
}>()
defineEmits<{
'show-full-log': [log: string]
}>()
const logContent = ref<HTMLElement | null>(null)
const isOverflowing = ref(false)
const checkOverflow = () => {
if (logContent.value && !isOverflowing.value) {
isOverflowing.value = logContent.value.scrollWidth > logContent.value.clientWidth
}
}
const convert = new Convert({
fg: '#FFF',
bg: '#000',
newline: false,
escapeXML: true,
stream: false,
})
const sanitizedLog = computed(() =>
DOMPurify.sanitize(convert.toHtml(props.log), {
ALLOWED_TAGS: ['span'],
ALLOWED_ATTR: ['style'],
USE_PROFILES: { html: true },
}),
)
const preventSelection = (e: MouseEvent) => {
e.preventDefault()
}
onMounted(() => {
logContent.value?.addEventListener('mousedown', preventSelection)
})
onUnmounted(() => {
logContent.value?.removeEventListener('mousedown', preventSelection)
})
</script>
<style scoped>
.parsed-log {
background: transparent;
transition: background-color 0.1s;
}
.parsed-log:hover {
background: rgba(128, 128, 128, 0.25);
transition: 0s;
}
.log-content > span {
user-select: none;
white-space: pre;
}
.log-content {
white-space: pre;
}
</style>