Files
RenewCMS/web/src/views/PublicUrlView.vue
T

177 lines
5.1 KiB
Vue

<script setup lang="ts">
import { t } from '@/i18n'
import { computed, onMounted, toRef, watch } from 'vue'
import { useRoute } from 'vue-router'
import DashboardShell from '@/components/DashboardShell.vue'
import LocaleAlert from '@/components/localization/LocaleAlert.vue'
import LocaleButton from '@/components/localization/LocaleButton.vue'
import LocaleInput from '@/components/localization/LocaleInput.vue'
import RetryAlert from '@/components/RetryAlert.vue'
import { usePublicUrlPage } from '@/composables/usePublicUrlPage'
import { PUBLIC_URL_PATTERN_MAX_LENGTH } from '@/lib/publicUrlPattern'
import { usePermissionsStore } from '@/stores/permissions'
import { useSessionStore } from '@/stores/session'
const route = useRoute()
const session = useSessionStore()
const permissions = usePermissionsStore()
const siteKey = computed(() => String(route.params.key ?? ''))
const page = usePublicUrlPage(toRef(siteKey))
const { loadState, value, saving, saveFailed, unset, canSave, errorMessage } = page
const sessionReady = computed(() => session.status === 'ready')
const owner = computed(() => permissions.can('settings.manage_sites'))
const siteName = computed(() => page.site.value?.name ?? siteKey.value)
const ready = computed(() => loadState.value === 'ready')
const invalid = computed(() => errorMessage.value !== undefined)
onMounted(page.load)
watch(siteKey, page.load)
const breadcrumb = computed(() => [
{ label: t('Settings'), to: '/settings' },
{ label: t('Sites'), to: '/settings/sites' },
{ label: siteName.value },
{ label: t('Public URL') },
])
</script>
<template>
<DashboardShell
compact-padding
:mobile-title="t('{0} · Public URL', [siteName])"
back-to="/settings/sites"
:back-label="t('Back to Sites')"
no-current-nav
>
<template #breadcrumb>
<UBreadcrumb
:items="breadcrumb"
color="neutral"
class="hidden lg:block"
:ui="{ link: 'text-sm font-medium' }"
/>
</template>
<div v-if="sessionReady" class="flex flex-col gap-4">
<div class="flex min-w-0 flex-col gap-0.5">
<h1 class="text-xl font-semibold text-highlighted">{{ t('Public URL') }}</h1>
<p class="text-pretty text-toned">
{{
t(
'Where {0} articles appear on your website. Used for canonical and alternate links.',
[siteName],
)
}}
</p>
</div>
<div
v-if="loadState === 'loading'"
class="h-44 bg-elevated"
role="status"
:aria-label="t('Loading')"
/>
<RetryAlert
v-else-if="loadState === 'error'"
:title="t('Couldn\'t load languages')"
:description="t('Check your connection and try again.')"
@retry="page.load"
/>
<template v-else-if="ready">
<LocaleAlert
v-if="!owner"
tone="neutral"
icon="i-lucide-lock"
:title="t('Only owners can change this')"
:description="t('You can read the pattern. Ask an owner to change it.')"
/>
<LocaleAlert
v-else-if="unset"
tone="info"
icon="i-lucide-info"
:title="t('Public URL not configured')"
:description="
t(
'Page, canonical and alternate addresses stay hidden until you add a pattern. Links by article ID still work.',
)
"
/>
<LocaleAlert
v-if="saveFailed"
tone="error"
icon="i-lucide-circle-x"
:title="t('Couldn\'t save languages')"
:description="t('Nothing changed. Your selection is kept. Try again.')"
/>
<UForm
:state="{ value }"
class="flex flex-col gap-3 border border-default bg-default p-4"
@submit="page.save"
>
<UFormField
:label="t('Public URL pattern')"
:error="errorMessage"
:help="
t(
'Placeholders: {lang} is the language code, such as fr or pt-BR. {slug} is the article address in that language.',
)
"
:ui="{
label: 'font-medium text-highlighted',
container: 'mt-1.5',
help: 'mt-1.5 text-xs/4.5 text-pretty text-muted',
error: 'mt-1.5 flex gap-1.5 text-xs/4.5 text-error',
}"
>
<LocaleInput
:model-value="value"
mono
:readonly="!owner"
:invalid="invalid"
placeholder="https://example.com/{lang}/{slug}"
:aria-label="t('Public URL pattern')"
:maxlength="PUBLIC_URL_PATTERN_MAX_LENGTH"
@update:model-value="page.edit"
/>
<template v-if="errorMessage" #error>
<UIcon name="i-lucide-circle-x" class="mt-0.5 size-3.5 shrink-0" />
<span>{{ errorMessage }}</span>
</template>
</UFormField>
<div
v-if="page.previewUrl.value"
class="flex flex-col gap-1"
data-testid="preview"
>
<span class="text-xs/5 text-muted">{{ page.previewLabel.value }}</span>
<span class="font-mono text-xs/5 wrap-anywhere text-highlighted">{{
page.previewUrl.value
}}</span>
</div>
<div v-if="owner" class="flex gap-2">
<LocaleButton
type="submit"
kind="primary"
:disabled="!canSave"
:loading="saving"
>
{{ t('Save') }}
</LocaleButton>
<LocaleButton :disabled="saving" @click="page.cancel">
{{ t('Cancel') }}
</LocaleButton>
</div>
</UForm>
</template>
</div>
</DashboardShell>
</template>