feat: entry editor at /content/:key/new and /content/:key/:id

This commit is contained in:
Florian Sylvain committed 2026-10-21 00:12:34 +02:00
1 parent 91d916fb74
commit fba20f83c5
94 files changed
+7116 -423

No files matched your search

+107
View File
@@ -0,0 +1,107 @@
<script setup lang="ts">
import { t } from '@/i18n'
import BuilderStatusChip from '@/components/BuilderStatusChip.vue'
import ContentState from '@/components/ContentState.vue'
import DirectoryLoadError from '@/components/DirectoryLoadError.vue'
import EntryActions from '@/components/EntryActions.vue'
import EntryDeleteDialog from '@/components/EntryDeleteDialog.vue'
import EntryEditorSkeleton from '@/components/EntryEditorSkeleton.vue'
import EntryForm from '@/components/EntryForm.vue'
import EntryNotices from '@/components/EntryNotices.vue'
import type { EntryEditorPage } from '@/composables/useEntryEditorPage'
import { typeEndpoint } from '@/lib/contentEndpoints'
import { entryLoadErrorLine } from '@/lib/entryMessages'
import { HOME_PATH, contentPath } from '@/lib/routePaths'
import { useRouter } from 'vue-router'
const props = defineProps<{ page: EntryEditorPage }>()
const router = useRouter()
const { phase, isDesktop, typeKey } = props.page
function loadErrorLine() {
const id = props.page.entryId.value
const base = typeEndpoint(typeKey)
return entryLoadErrorLine(
phase.value === 'entryError' && id !== null ? `${base}/entry/${id}` : base,
)
}
function goHome() {
router.push(HOME_PATH)
}
function backToList() {
router.push(contentPath(typeKey))
}
</script>
<template>
<ContentState
v-if="phase === 'forbidden' || phase === 'typeNotFound'"
:kind="phase === 'forbidden' ? 'forbidden' : 'notFound'"
:name="page.typeName.value"
@home="goHome"
/>
<ContentState
v-else-if="phase === 'entryNotFound'"
kind="entryNotFound"
:name="page.typeName.value"
@back="backToList"
/>
<div v-else class="flex flex-col gap-4" data-testid="entry-editor">
<div class="flex flex-wrap items-end gap-4">
<div class="flex min-w-50 flex-1 flex-col gap-0.5">
<template v-if="phase === 'loading'">
<USkeleton class="h-7 w-40 rounded-none bg-accented max-lg:hidden" />
<USkeleton class="h-5 w-28 rounded-none bg-accented" />
</template>
<template v-else>
<h1 class="text-xl font-semibold text-highlighted max-lg:sr-only">
{{ page.title.value }}
</h1>
<p class="text-muted">{{ page.subtitle.value }}</p>
</template>
</div>
<EntryActions v-if="isDesktop && phase === 'ready'" :page="page" />
</div>
<div
v-if="!isDesktop && phase === 'ready'"
class="flex min-h-5 items-center justify-between"
>
<BuilderStatusChip :status="page.chip.value" :saved-at="page.savedClock.value" />
</div>
<DirectoryLoadError
v-if="phase === 'typeError' || phase === 'entryError'"
:title="t('Couldn\'t load the entry')"
:description="loadErrorLine()"
@retry="page.retryLoad"
/>
<EntryEditorSkeleton v-else-if="phase === 'loading'" />
<template v-else>
<EntryNotices :page="page" />
<EntryForm :page="page" />
</template>
</div>
<div
v-if="!isDesktop && phase === 'ready'"
class="sticky bottom-0 -mx-4 mt-auto flex border-t border-default bg-default px-4 py-3"
>
<EntryActions :page="page" bar />
</div>
<EntryDeleteDialog
v-if="page.removal.stage.value !== 'closed'"
:title="page.title.value"
:type-name="page.typeName.value"
:pending="page.removal.pending.value"
:failed="page.removal.stage.value === 'failed'"
@confirm="page.removal.confirm"
@close="page.removal.close"
/>
</template>