funkwhale/front/src/components/common/ContentForm.vue

147 wiersze
3.7 KiB
Vue

<script setup lang="ts">
import axios from 'axios'
import { useVModel, watchDebounced, useTextareaAutosize, syncRef } from '@vueuse/core'
import { ref, computed, watchEffect, onMounted, nextTick, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import useLogger from '~/composables/useLogger'
interface Events {
(e: 'update:modelValue', value: string): void
}
interface Props {
modelValue: string
placeholder?: string
autofocus?: boolean
permissive?: boolean
required?: boolean
charLimit?: number
}
const emit = defineEmits<Events>()
const props = withDefaults(defineProps<Props>(), {
placeholder: undefined,
autofocus: false,
charLimit: 5000,
permissive: false,
required: false
})
const logger = useLogger()
const { t } = useI18n()
const { textarea, input } = useTextareaAutosize()
const value = useVModel(props, 'modelValue', emit)
syncRef(value, input)
const isPreviewing = ref(false)
const preview = ref()
const isLoadingPreview = ref(false)
const labels = computed(() => ({
placeholder: props.placeholder ?? t('components.common.ContentForm.placeholder.input')
}))
const remainingChars = computed(() => props.charLimit - props.modelValue.length)
const loadPreview = async () => {
isLoadingPreview.value = true
try {
const response = await axios.post('text-preview/', { text: value.value, permissive: props.permissive })
preview.value = response.data.rendered
} catch (error) {
logger.error(error)
}
isLoadingPreview.value = false
}
watchDebounced(value, async () => {
await loadPreview()
}, { immediate: true, debounce: 500 })
watchEffect(async () => {
if (isPreviewing.value) {
if (value.value && !preview.value && !isLoadingPreview.value) {
await loadPreview()
}
}
})
watch(isPreviewing, (to, from) => {
if (from === true) {
textarea.value.focus()
}
}, { flush: 'post' })
onMounted(async () => {
if (props.autofocus) {
await nextTick()
textarea.value.focus()
}
})
</script>
<template>
<div class="content-form ui segments">
<div class="ui segment">
<div class="ui tiny secondary pointing menu">
<button
:class="[{active: !isPreviewing}, 'item']"
@click.prevent="isPreviewing = false"
>
{{ $t('components.common.ContentForm.button.write') }}
</button>
<button
:class="[{active: isPreviewing}, 'item']"
@click.prevent="isPreviewing = true"
>
{{ $t('components.common.ContentForm.button.preview') }}
</button>
</div>
<template v-if="isPreviewing">
<div
v-if="isLoadingPreview"
class="ui placeholder"
>
<div class="paragraph">
<div class="line" />
<div class="line" />
<div class="line" />
<div class="line" />
</div>
</div>
<p v-else-if="!preview">
{{ $t('components.common.ContentForm.empty.noContent') }}
</p>
<sanitized-html
v-else
:html="preview"
/>
</template>
<template v-else>
<div class="ui transparent input">
<textarea
ref="textarea"
v-model="value"
:required="required"
:placeholder="labels.placeholder"
/>
</div>
<div class="ui very small hidden divider" />
</template>
</div>
<div class="ui bottom attached segment">
<span
v-if="charLimit"
:class="['right', 'floated', {'ui danger text': remainingChars < 0}]"
>
{{ remainingChars }}
</span>
<p>
{{ $t('components.common.ContentForm.help.markdown') }}
</p>
</div>
</div>
</template>