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,155 @@
|
||||
import { t } from '@/i18n'
|
||||
import type { BuiltinField } from '@/lib/contentTypes'
|
||||
import { formatDayValue } from '@/lib/entryValue'
|
||||
|
||||
export type EntryRule = 'required' | 'type' | 'min' | 'max' | 'options' | 'unique'
|
||||
|
||||
function bound(field: BuiltinField, side: 'min' | 'max') {
|
||||
return field[side] ?? ''
|
||||
}
|
||||
|
||||
function choices(field: BuiltinField) {
|
||||
const options = (field.options ?? []).map(String)
|
||||
if (options.length < 2) {
|
||||
return options.join('')
|
||||
}
|
||||
return t('{0} or {1}', [options.slice(0, -1).join(', '), options[options.length - 1]!])
|
||||
}
|
||||
|
||||
function requiredMessage(field: BuiltinField) {
|
||||
switch (field.type) {
|
||||
case 'date':
|
||||
return t('Pick a date for {0}.', [field.label])
|
||||
case 'select':
|
||||
return t('Choose an option for {0}.', [field.label])
|
||||
case 'boolean':
|
||||
return t('Choose yes or no for {0}.', [field.label])
|
||||
default:
|
||||
return t('Fill in {0}.', [field.label])
|
||||
}
|
||||
}
|
||||
|
||||
function typeMessage(field: BuiltinField) {
|
||||
switch (field.type) {
|
||||
case 'number':
|
||||
return t('{0} must be a number.', [field.label])
|
||||
case 'boolean':
|
||||
return t('{0} must be yes or no.', [field.label])
|
||||
case 'date':
|
||||
return t('{0} must be a date, like 2026-11-21.', [field.label])
|
||||
case 'select':
|
||||
return t('{0} must be one of the choices.', [field.label])
|
||||
default:
|
||||
return t('{0} must be text.', [field.label])
|
||||
}
|
||||
}
|
||||
|
||||
function minMessage(field: BuiltinField) {
|
||||
const limit = bound(field, 'min')
|
||||
if (field.type === 'number') {
|
||||
return t('{0} must be at least {1}.', [field.label, String(limit)])
|
||||
}
|
||||
if (field.type === 'date') {
|
||||
return t("{0} can't be before {1}.", [field.label, formatDayValue(String(limit))])
|
||||
}
|
||||
return t('{0} needs at least {1} characters.', [field.label, String(limit)])
|
||||
}
|
||||
|
||||
function maxMessage(field: BuiltinField) {
|
||||
const limit = bound(field, 'max')
|
||||
if (field.type === 'number') {
|
||||
return t('{0} must be at most {1}.', [field.label, String(limit)])
|
||||
}
|
||||
if (field.type === 'date') {
|
||||
return t("{0} can't be after {1}.", [field.label, formatDayValue(String(limit))])
|
||||
}
|
||||
return t('{0} can be up to {1} characters.', [field.label, String(limit)])
|
||||
}
|
||||
|
||||
function uniqueMessage(field: BuiltinField) {
|
||||
switch (field.type) {
|
||||
case 'select':
|
||||
return t('Another entry already uses this choice for {0}.', [field.label])
|
||||
case 'date':
|
||||
return t('Another entry already uses this date for {0}.', [field.label])
|
||||
default:
|
||||
return t('Another entry already uses this {0}. Enter a different one.', [field.label])
|
||||
}
|
||||
}
|
||||
|
||||
export function ruleMessage(rule: EntryRule, field: BuiltinField) {
|
||||
switch (rule) {
|
||||
case 'required':
|
||||
return requiredMessage(field)
|
||||
case 'type':
|
||||
return typeMessage(field)
|
||||
case 'min':
|
||||
return minMessage(field)
|
||||
case 'max':
|
||||
return maxMessage(field)
|
||||
case 'options':
|
||||
return t('Choose one of: {0}.', [choices(field)])
|
||||
default:
|
||||
return uniqueMessage(field)
|
||||
}
|
||||
}
|
||||
|
||||
export function uniqueAlert(field: BuiltinField) {
|
||||
switch (field.type) {
|
||||
case 'select':
|
||||
return t('Another entry already uses this choice. Change it and save again.')
|
||||
case 'date':
|
||||
return t('Another entry already uses this date. Change it and save again.')
|
||||
default:
|
||||
return t('Another entry already uses this {0}. Change it and save again.', [
|
||||
field.label.toLowerCase(),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
export function uniqueHelp(field: BuiltinField) {
|
||||
return t('Each entry needs a different {0}.', [field.label.toLowerCase()])
|
||||
}
|
||||
|
||||
export function rangeHelp(field: BuiltinField) {
|
||||
const min = field.min
|
||||
const max = field.max
|
||||
if (field.type === 'date') {
|
||||
const from = min === null || min === undefined ? '' : formatDayValue(String(min))
|
||||
const until = max === null || max === undefined ? '' : formatDayValue(String(max))
|
||||
if (from && until) {
|
||||
return t('From {0} until {1}.', [from, until])
|
||||
}
|
||||
if (from) {
|
||||
return t('From {0}.', [from])
|
||||
}
|
||||
return until ? t('Until {0}.', [until]) : ''
|
||||
}
|
||||
if (typeof min === 'number' && typeof max === 'number') {
|
||||
return t('Between {0} and {1}.', [String(min), String(max)])
|
||||
}
|
||||
if (typeof min === 'number') {
|
||||
return t('At least {0}.', [String(min)])
|
||||
}
|
||||
return typeof max === 'number' ? t('At most {0}.', [String(max)]) : ''
|
||||
}
|
||||
|
||||
export function schemaChangedLine(key: string) {
|
||||
const name = key.replace(/_/g, ' ')
|
||||
return t(
|
||||
"A field was removed and a new required field, {0}, was added after you opened this form. Reload the form to continue. What you typed here can't be saved against the old fields.",
|
||||
[name.charAt(0).toUpperCase() + name.slice(1)],
|
||||
)
|
||||
}
|
||||
|
||||
export function entryLoadErrorLine(path: string) {
|
||||
return t('GET {0} failed. Check your connection and try again.', [path])
|
||||
}
|
||||
|
||||
export function typeLoadErrorLine(key: string) {
|
||||
return t('GET /v1/content-type/{0} failed. Check your connection and try again.', [key])
|
||||
}
|
||||
|
||||
export function entriesLoadErrorLine(key: string) {
|
||||
return t('GET /v1/content-type/{0}/entry failed. Check your connection and try again.', [key])
|
||||
}
|
||||
Reference in new issue
Block a user