feat: entry editor at /content/:key/new and /content/:key/:id
This commit is contained in:
1 parent
91d916fb74
commit
fba20f83c5
94 files changed
+7116
-423
No files matched your search
@@ -0,0 +1,75 @@
|
||||
<script setup lang="ts">
|
||||
import { t } from '@/i18n'
|
||||
|
||||
import { computed, nextTick, watch } from 'vue'
|
||||
import EntryField from '@/components/EntryField.vue'
|
||||
import type { EntryEditorPage } from '@/composables/useEntryEditorPage'
|
||||
import { groupFields } from '@/lib/entryLayout'
|
||||
|
||||
const FOCUSABLE =
|
||||
'input:not([type="hidden"]), textarea, [role="spinbutton"], [role="switch"], [role="combobox"]'
|
||||
|
||||
const props = defineProps<{ page: EntryEditorPage }>()
|
||||
|
||||
const { form, fields, locked, focusToken } = props.page
|
||||
const rows = computed(() => groupFields(fields.value))
|
||||
const dimmed = computed(() => locked.value)
|
||||
|
||||
async function focusFirstIssue() {
|
||||
await nextTick()
|
||||
const key = form.firstIssue.value
|
||||
if (!key) {
|
||||
return
|
||||
}
|
||||
const wrapper = [...document.querySelectorAll<HTMLElement>('[data-entry-field]')].find(
|
||||
(candidate) => candidate.dataset.entryField === key,
|
||||
)
|
||||
const control = wrapper?.querySelector<HTMLElement>(FOCUSABLE)
|
||||
control?.focus()
|
||||
;(wrapper ?? control)?.scrollIntoView?.({ block: 'center' })
|
||||
}
|
||||
|
||||
watch(focusToken, focusFirstIssue)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form
|
||||
id="entry-form"
|
||||
novalidate
|
||||
data-testid="entry-form"
|
||||
class="flex max-w-190 flex-col gap-5 border border-default bg-default p-4 transition-opacity md:p-6"
|
||||
:class="dimmed ? 'opacity-70' : ''"
|
||||
:aria-busy="page.saving.value ? 'true' : undefined"
|
||||
@submit.prevent="page.submit"
|
||||
>
|
||||
<div
|
||||
v-for="row in rows"
|
||||
:key="row.map((field) => field.key).join('+')"
|
||||
class="grid gap-4"
|
||||
:class="row.length > 1 ? 'md:grid-cols-2' : ''"
|
||||
>
|
||||
<EntryField
|
||||
v-for="field in row"
|
||||
:key="field.key"
|
||||
:field="field"
|
||||
:value="form.values.value[field.key]"
|
||||
:issue="form.issues.value[field.key]"
|
||||
:is-default="form.isDefault(field)"
|
||||
:disabled="locked"
|
||||
@update="page.change"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<dl
|
||||
data-testid="entry-meta"
|
||||
class="grid grid-cols-1 gap-x-4 gap-y-1.5 border-t border-default pt-4 text-xs/4.5 md:grid-cols-[auto_1fr]"
|
||||
>
|
||||
<dt class="text-muted">{{ t('Entry') }}</dt>
|
||||
<dd :class="page.creating.value ? '' : 'font-mono'">{{ page.entryLine.value }}</dd>
|
||||
<dt class="text-muted">{{ t('Created by') }}</dt>
|
||||
<dd>{{ page.createdLine.value }}</dd>
|
||||
<dt class="text-muted">{{ t('Updated by') }}</dt>
|
||||
<dd>{{ page.updatedLine.value }}</dd>
|
||||
</dl>
|
||||
</form>
|
||||
</template>
|
||||
Reference in new issue
Block a user