112 lines
3.3 KiB
TypeScript
112 lines
3.3 KiB
TypeScript
import { t } from '@/i18n'
|
|
import type { BuiltinField, ContentEntry } from '@/lib/contentTypes'
|
|
import { describeCell, plainCellText } from '@/lib/entryCells'
|
|
import { ruleMessage, type EntryRule } from '@/lib/entryMessages'
|
|
import { isBlank, sameValue } from '@/lib/entryValue'
|
|
import { fieldTypeSpec } from '@/lib/fieldTypes'
|
|
import { mediaOf, type MediaMap } from '@/lib/mediaField'
|
|
import { relationSummariesFor, type RelationSummaries } from '@/lib/relationField'
|
|
|
|
export type EntryValues = Record<string, unknown>
|
|
|
|
export interface FieldIssue {
|
|
rule: EntryRule
|
|
message: string
|
|
}
|
|
|
|
export type EntryIssues = Record<string, FieldIssue>
|
|
|
|
export function initialValues(fields: BuiltinField[], stored: ContentEntry | null): EntryValues {
|
|
return Object.fromEntries(
|
|
fields.map((field) => [
|
|
field.key,
|
|
fieldTypeSpec(field.type).editor.initial(
|
|
field,
|
|
stored?.data[field.key],
|
|
stored === null,
|
|
),
|
|
]),
|
|
)
|
|
}
|
|
|
|
export function initialImages(fields: BuiltinField[], stored: ContentEntry | null): MediaMap {
|
|
const images: MediaMap = {}
|
|
for (const field of fields) {
|
|
const summary = mediaOf(stored, field.key)
|
|
if (summary && stored?.data[field.key] === summary.id) {
|
|
images[field.key] = summary
|
|
}
|
|
}
|
|
return images
|
|
}
|
|
|
|
export function isRelationField(field: BuiltinField) {
|
|
return fieldTypeSpec(field.type).editor.control === 'relation'
|
|
}
|
|
|
|
export function initialRelations(
|
|
fields: BuiltinField[],
|
|
stored: ContentEntry | null,
|
|
): RelationSummaries {
|
|
return relationSummariesFor(fields, stored, isRelationField)
|
|
}
|
|
|
|
export function buildData(fields: BuiltinField[], values: EntryValues) {
|
|
return Object.fromEntries(
|
|
fields.map((field) => [
|
|
field.key,
|
|
fieldTypeSpec(field.type).editor.payload(field, values[field.key]),
|
|
]),
|
|
)
|
|
}
|
|
|
|
export function issueFor(field: BuiltinField, rule: EntryRule): FieldIssue {
|
|
return { rule, message: ruleMessage(rule, field) }
|
|
}
|
|
|
|
export function validateEntry(
|
|
fields: BuiltinField[],
|
|
values: EntryValues,
|
|
images: MediaMap = {},
|
|
relations: RelationSummaries = {},
|
|
): EntryIssues {
|
|
const issues: EntryIssues = {}
|
|
for (const field of fields) {
|
|
const rule = fieldTypeSpec(field.type).editor.validate(field, values[field.key], {
|
|
image: images[field.key] ?? null,
|
|
relations: relations[field.key] ?? [],
|
|
})
|
|
if (rule) {
|
|
issues[field.key] = issueFor(field, rule)
|
|
}
|
|
}
|
|
return issues
|
|
}
|
|
|
|
export function firstIssueKey(fields: BuiltinField[], issues: EntryIssues) {
|
|
return fields.find((field) => issues[field.key])?.key ?? null
|
|
}
|
|
|
|
export function valuesDiffer(fields: BuiltinField[], current: EntryValues, saved: EntryValues) {
|
|
return fields.some((field) => !sameValue(current[field.key], saved[field.key]))
|
|
}
|
|
|
|
export function carriesDefault(field: BuiltinField, value: unknown) {
|
|
if (isBlank(field.default)) {
|
|
return false
|
|
}
|
|
const initial = fieldTypeSpec(field.type).editor.initial(field, undefined, true)
|
|
return Object.is(initial, value)
|
|
}
|
|
|
|
export function entryTitle(fields: BuiltinField[], entry: ContentEntry | null) {
|
|
const first = fields[0]
|
|
if (!first || !entry) {
|
|
return entry ? t('Entry {0}', [entry.id]) : t('New entry')
|
|
}
|
|
const cell = describeCell(first, entry.data[first.key])
|
|
return cell.kind === 'empty' || cell.kind === 'thumbnail' || cell.kind === 'relation'
|
|
? t('Entry {0}', [entry.id])
|
|
: plainCellText(cell)
|
|
}
|