refac: duplicates, crap, dry etc. on last features commits

This commit is contained in:
Florian Sylvain committed 2026-10-21 00:52:14 +02:00
1 parent fba20f83c5
commit 89051b3f1f
12 files changed
+260 -153

No files matched your search

+35 -17
View File
@@ -6,8 +6,26 @@ import type { FieldErrors } from '@/lib/schemaBuilderValidation'
type Builder = ReturnType<typeof useSchemaBuilder>
type InspectorActions = Pick<
Builder,
| 'setLabel'
| 'setFieldKey'
| 'unlockFieldKey'
| 'resetFieldKey'
| 'setFieldType'
| 'patchField'
| 'setLimit'
| 'setDefault'
| 'addChoice'
| 'removeChoice'
| 'renameChoice'
| 'reorderChoices'
>
const props = defineProps<{
builder: Builder
actions: InspectorActions
saving: boolean
focusToken: number
field: BuilderField
errors: FieldErrors
compact: boolean
@@ -20,22 +38,22 @@ const uid = () => props.field.uid
<FieldInspector
:field="field"
:errors="errors"
:disabled="builder.saving.value"
:disabled="saving"
:compact="compact"
:focus-token="builder.labelFocusToken.value"
@label="builder.setLabel(uid(), $event)"
@key="builder.setFieldKey(uid(), $event)"
@unlock="builder.unlockFieldKey(uid())"
@reset="builder.resetFieldKey(uid())"
@type="builder.setFieldType(uid(), $event)"
@required="builder.patchField(uid(), { required: $event })"
@unique="builder.patchField(uid(), { unique: $event })"
@limit="(which, value) => builder.setLimit(uid(), which, value)"
@default="builder.setDefault(uid(), $event)"
@default-on="builder.patchField(uid(), { defaultOn: $event })"
@add-choice="builder.addChoice(uid(), $event)"
@remove-choice="builder.removeChoice(uid(), $event)"
@rename-choice="(index, text) => builder.renameChoice(uid(), index, text)"
@reorder-choices="builder.reorderChoices(uid(), $event)"
:focus-token="focusToken"
@label="actions.setLabel(uid(), $event)"
@key="actions.setFieldKey(uid(), $event)"
@unlock="actions.unlockFieldKey(uid())"
@reset="actions.resetFieldKey(uid())"
@type="actions.setFieldType(uid(), $event)"
@required="actions.patchField(uid(), { required: $event })"
@unique="actions.patchField(uid(), { unique: $event })"
@limit="(which, value) => actions.setLimit(uid(), which, value)"
@default="actions.setDefault(uid(), $event)"
@default-on="actions.patchField(uid(), { defaultOn: $event })"
@add-choice="actions.addChoice(uid(), $event)"
@remove-choice="actions.removeChoice(uid(), $event)"
@rename-choice="(index, text) => actions.renameChoice(uid(), index, text)"
@reorder-choices="actions.reorderChoices(uid(), $event)"
/>
</template>
+6 -31
View File
@@ -2,11 +2,10 @@
import { t, translate } from '@/i18n'
import { computed, nextTick, ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useContentTypesStore } from '@/stores/contentTypes'
import { useRoute } from 'vue-router'
import { useSessionStore } from '@/stores/session'
import { useUpdateStore } from '@/stores/update'
import { useApi } from '@/composables/useApi'
import { useContentTypesPreload, useShellLogout } from '@/composables/useShellSession'
import { useSidebarCollapsed } from '@/composables/useSidebarCollapsed'
import AppBoot from '@/components/AppBoot.vue'
import AccountMenuSheet from '@/components/AccountMenuSheet.vue'
@@ -68,27 +67,15 @@ const bodyUi = computed(() => {
})
const route = useRoute()
const router = useRouter()
const session = useSessionStore()
const update = useUpdateStore()
const api = useApi()
const contentTypes = useContentTypesStore()
const { loggingOut, logout } = useShellLogout()
const wantsBuiltin = computed(
() => session.canManageContent && route.meta.needsBuiltinTypes === true,
)
watch(
[() => session.status === 'ready' && session.session?.verified === true, wantsBuiltin],
([verified, builtin], [wasVerified]) => {
if (verified) {
contentTypes.load({ builtin })
} else if (wasVerified) {
contentTypes.reset()
}
},
{ immediate: true },
)
useContentTypesPreload(() => wantsBuiltin.value)
const publishingChildren = computed(() => [
{ label: t('Publishing'), type: 'label' as const },
@@ -237,7 +224,6 @@ const navUi = computed(() => ({
childLink: 'h-8 max-lg:h-11 gap-2.5 py-0 pr-2.5 pl-3 text-sm',
childLinkIcon: 'size-3.5',
}))
const loggingOut = ref(false)
const sidebarOpen = ref(false)
const accountSheetOpen = ref(false)
const NAVBAR_TOGGLE_SELECTOR = '[data-slot="toggle"]'
@@ -272,17 +258,6 @@ const showBoot = computed(() => session.status === 'checking' && !props.renderWh
function onRetry() {
session.retry()
}
async function onLogout() {
loggingOut.value = true
try {
await api.request('/v1/auth/logout', { method: 'POST' })
} finally {
loggingOut.value = false
}
session.clear()
await router.push('/login')
}
</script>
<template>
@@ -335,7 +310,7 @@ async function onLogout() {
:email="session.session?.email"
:collapsed="sidebarCollapsed"
:sheet-open="accountSheetOpen"
@logout="onLogout"
@logout="logout"
@open-sheet="openAccountSheet"
/>
</template>
@@ -438,7 +413,7 @@ async function onLogout() {
:username="session.session?.username ?? ''"
:display-name="session.session?.displayName"
:email="session.session?.email"
@logout="onLogout"
@logout="logout"
/>
</UDashboardGroup>
</template>
+21 -19
View File
@@ -2,9 +2,18 @@
import { t } from '@/i18n'
import BuilderStatusChip from '@/components/BuilderStatusChip.vue'
import type { EntryEditorPage } from '@/composables/useEntryEditorPage'
import type { EntryChipStatus } from '@/composables/useEntryEditorPage'
defineProps<{ page: EntryEditorPage; bar?: boolean }>()
defineProps<{
chip: EntryChipStatus
savedClock: string
creating: boolean
saving: boolean
canSave: boolean
bar?: boolean
}>()
defineEmits<{ remove: []; cancel: [] }>()
</script>
<template>
@@ -13,20 +22,16 @@ defineProps<{ page: EntryEditorPage; bar?: boolean }>()
:class="bar ? 'w-full gap-2' : 'gap-3'"
:data-testid="bar ? 'entry-bar' : 'entry-actions'"
>
<BuilderStatusChip
v-if="!bar"
:status="page.chip.value"
:saved-at="page.savedClock.value"
/>
<BuilderStatusChip v-if="!bar" :status="chip" :saved-at="savedClock" />
<UButton
v-if="!bar && !page.creating.value"
v-if="!bar && !creating"
type="button"
color="error"
variant="outline"
icon="i-lucide-trash-2"
:disabled="page.saving.value"
:disabled="saving"
class="h-8 px-3"
@click="page.removal.open"
@click="$emit('remove')"
>
{{ t('Delete') }}
</UButton>
@@ -34,25 +39,22 @@ defineProps<{ page: EntryEditorPage; bar?: boolean }>()
type="button"
color="neutral"
variant="outline"
:disabled="page.saving.value"
:disabled="saving"
class="h-11 border border-accented bg-default px-3 text-highlighted ring-0 md:h-8"
:class="bar ? 'flex-1 justify-center' : ''"
@click="page.cancel"
@click="$emit('cancel')"
>
{{ t('Cancel') }}
</UButton>
<UButton
type="submit"
form="entry-form"
:disabled="!page.canSave.value"
:loading="page.saving.value"
:disabled="!canSave"
:loading="saving"
class="h-11 px-4 md:h-8"
:class="[
bar ? 'flex-1 justify-center' : '',
page.saving.value ? 'disabled:opacity-100' : '',
]"
:class="[bar ? 'flex-1 justify-center' : '', saving ? 'disabled:opacity-100' : '']"
>
{{ page.saving.value ? t('Saving…') : t('Save') }}
{{ saving ? t('Saving…') : t('Save') }}
</UButton>
</div>
</template>
+42 -4
View File
@@ -1,6 +1,7 @@
<script setup lang="ts">
import { t } from '@/i18n'
import { computed } from 'vue'
import BuilderStatusChip from '@/components/BuilderStatusChip.vue'
import ContentState from '@/components/ContentState.vue'
import DirectoryLoadError from '@/components/DirectoryLoadError.vue'
@@ -28,6 +29,14 @@ function loadErrorLine() {
)
}
const actionProps = computed(() => ({
chip: props.page.chip.value,
savedClock: props.page.savedClock.value,
creating: props.page.creating.value,
saving: props.page.saving.value,
canSave: props.page.canSave.value,
}))
function goHome() {
router.push(HOME_PATH)
}
@@ -65,7 +74,12 @@ function backToList() {
<p class="text-muted">{{ page.subtitle.value }}</p>
</template>
</div>
<EntryActions v-if="isDesktop && phase === 'ready'" :page="page" />
<EntryActions
v-if="isDesktop && phase === 'ready'"
v-bind="actionProps"
@remove="page.removal.open"
@cancel="page.cancel"
/>
</div>
<div
@@ -83,8 +97,32 @@ function backToList() {
/>
<EntryEditorSkeleton v-else-if="phase === 'loading'" />
<template v-else>
<EntryNotices :page="page" />
<EntryForm :page="page" />
<EntryNotices
:failure="page.failure.value"
:schema-changed="page.schemaChanged.value"
:schema-field="page.schemaField.value"
:issues="page.form.issues.value"
:fields="page.fields.value"
:type-name="page.typeName.value"
@retry="page.submit"
@reload="page.reload"
/>
<EntryForm
:fields="page.fields.value"
:values="page.form.values.value"
:issues="page.form.issues.value"
:first-issue="page.form.firstIssue.value"
:focus-token="page.focusToken.value"
:locked="page.locked.value"
:saving="page.saving.value"
:creating="page.creating.value"
:is-default="page.form.isDefault"
:entry-line="page.entryLine.value"
:created-line="page.createdLine.value"
:updated-line="page.updatedLine.value"
@update="page.change"
@submit="page.submit"
/>
</template>
</div>
@@ -92,7 +130,7 @@ function backToList() {
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 />
<EntryActions v-bind="actionProps" bar @remove="page.removal.open" @cancel="page.cancel" />
</div>
<EntryDeleteDialog
+31 -17
View File
@@ -3,21 +3,35 @@ import { t } from '@/i18n'
import { computed, nextTick, watch } from 'vue'
import EntryField from '@/components/EntryField.vue'
import type { EntryEditorPage } from '@/composables/useEntryEditorPage'
import type { BuiltinField } from '@/lib/contentTypes'
import type { EntryIssues, EntryValues } from '@/lib/entryForm'
import { groupFields } from '@/lib/entryLayout'
const FOCUSABLE =
'input:not([type="hidden"]), textarea, [role="spinbutton"], [role="switch"], [role="combobox"]'
const props = defineProps<{ page: EntryEditorPage }>()
const props = defineProps<{
fields: BuiltinField[]
values: EntryValues
issues: EntryIssues
firstIssue: string | null
focusToken: number
locked: boolean
saving: boolean
creating: boolean
isDefault: (field: BuiltinField) => boolean
entryLine: string
createdLine: string
updatedLine: string
}>()
const { form, fields, locked, focusToken } = props.page
const rows = computed(() => groupFields(fields.value))
const dimmed = computed(() => locked.value)
const emit = defineEmits<{ update: [key: string, value: unknown]; submit: [] }>()
const rows = computed(() => groupFields(props.fields))
async function focusFirstIssue() {
await nextTick()
const key = form.firstIssue.value
const key = props.firstIssue
if (!key) {
return
}
@@ -29,7 +43,7 @@ async function focusFirstIssue() {
;(wrapper ?? control)?.scrollIntoView?.({ block: 'center' })
}
watch(focusToken, focusFirstIssue)
watch(() => props.focusToken, focusFirstIssue)
</script>
<template>
@@ -38,9 +52,9 @@ watch(focusToken, focusFirstIssue)
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"
:class="locked ? 'opacity-70' : ''"
:aria-busy="saving ? 'true' : undefined"
@submit.prevent="emit('submit')"
>
<div
v-for="row in rows"
@@ -52,11 +66,11 @@ watch(focusToken, focusFirstIssue)
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)"
:value="values[field.key]"
:issue="issues[field.key]"
:is-default="isDefault(field)"
:disabled="locked"
@update="page.change"
@update="(key, value) => emit('update', key, value)"
/>
</div>
@@ -65,11 +79,11 @@ watch(focusToken, focusFirstIssue)
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>
<dd :class="creating ? '' : 'font-mono'">{{ entryLine }}</dd>
<dt class="text-muted">{{ t('Created by') }}</dt>
<dd>{{ page.createdLine.value }}</dd>
<dd>{{ createdLine }}</dd>
<dt class="text-muted">{{ t('Updated by') }}</dt>
<dd>{{ page.updatedLine.value }}</dd>
<dd>{{ updatedLine }}</dd>
</dl>
</form>
</template>
+15 -6
View File
@@ -3,15 +3,24 @@ import { t } from '@/i18n'
import { computed } from 'vue'
import RetryAlert from '@/components/RetryAlert.vue'
import type { EntryEditorPage } from '@/composables/useEntryEditorPage'
import type { SaveFailure } from '@/composables/useEntryDraft'
import type { BuiltinField } from '@/lib/contentTypes'
import type { EntryIssues } from '@/lib/entryForm'
import { schemaChangedLine, uniqueAlert } from '@/lib/entryMessages'
const props = defineProps<{ page: EntryEditorPage }>()
const props = defineProps<{
failure: SaveFailure
schemaChanged: boolean
schemaField: string
issues: EntryIssues
fields: BuiltinField[]
typeName: string
}>()
const { failure, schemaChanged, schemaField, form, fields, typeName } = props.page
defineEmits<{ retry: []; reload: [] }>()
const uniqueField = computed(() =>
fields.value.find((field) => form.issues.value[field.key]?.rule === 'unique'),
props.fields.find((field) => props.issues[field.key]?.rule === 'unique'),
)
const alertUi = {
root: 'items-start gap-2.5 px-3.5 py-3',
@@ -29,7 +38,7 @@ const alertUi = {
'Something went wrong on our side. Your changes are still here. Try again in a moment.',
)
"
@retry="page.submit"
@retry="$emit('retry')"
/>
<UAlert
@@ -61,7 +70,7 @@ const alertUi = {
icon="i-lucide-rotate-cw"
class="h-11 gap-1.5 border border-accented bg-default px-2.5 text-xs text-highlighted ring-0 md:h-8"
:ui="{ leadingIcon: 'size-3.5' }"
@click="page.reload"
@click="$emit('reload')"
>
{{ t('Reload form') }}
</UButton>
+1 -1
View File
@@ -42,7 +42,7 @@ const height = computed(() => (props.touch ? 'h-11' : 'h-8'))
trailingIcon: 'size-3.5 text-muted',
content: 'w-40 rounded-none',
itemTrailingIcon: 'hidden',
item: `${height} rounded-none data-[state=checked]:bg-(--rc-sand) data-[state=checked]:font-semibold data-[state=checked]:text-(--rc-on-sand)`,
item: `${height} items-center rounded-none py-0 data-[state=checked]:bg-(--rc-sand) data-[state=checked]:font-semibold data-[state=checked]:text-(--rc-on-sand)`,
}"
@update:model-value="emit('update:size', $event as number)"
/>
+6 -2
View File
@@ -256,7 +256,9 @@ async function restoreRowFocus() {
<BuilderBadge v-if="selectedMarks?.changed" kind="changed" />
</div>
<BuilderInspector
:builder="builder"
:actions="builder"
:saving="builder.saving.value"
:focus-token="builder.labelFocusToken.value"
:field="builder.selected.value"
:errors="selectedErrors"
:compact="false"
@@ -313,7 +315,9 @@ async function restoreRowFocus() {
>
<BuilderInspector
v-if="builder.selected.value"
:builder="builder"
:actions="builder"
:saving="builder.saving.value"
:focus-token="builder.labelFocusToken.value"
:field="builder.selected.value"
:errors="selectedErrors"
compact