feat(16.2-01): translatable plugin fields, element uploads and save notices in the admin
- MLPluginField wraps PluginField in the MLField locale switcher; the
element gets the active content locale and that locale's string
- isMLField treats a translatable plugin field as a locale map in the form
state, FormView adopt and the relation child modal
- summer-upload posts the file with the admin session to the field upload
route and answers summer-upload-result {id, url} or {id, error}
- noticeLines shows meta.notices under each field as a warning status line
(text only) and in the saved toast; the stored value is reloaded
- docs/backend/field-types.md documents the full element contract;
modules/boardwalk/dist rebuilt
This commit is contained in:
@@ -24,8 +24,9 @@ import {
|
||||
fieldErrors,
|
||||
focusField,
|
||||
initialValues,
|
||||
isMLFieldType,
|
||||
isMLField,
|
||||
mergeMLValue,
|
||||
noticeLines,
|
||||
panelDomId,
|
||||
presetUpdates,
|
||||
recordDisplayName,
|
||||
@@ -68,6 +69,9 @@ const errors = ref<Record<string, string[]>>({})
|
||||
// The text of a save the server refused with 403 (UI-SPEC S6). It is a
|
||||
// banner, not a toast: it stays readable until the next save attempt.
|
||||
const forbidden = ref<string | null>(null)
|
||||
// What plugin field types changed on the last save (meta.notices, D-10):
|
||||
// lines under each field until the next save attempt.
|
||||
const notices = ref<Record<string, string[]>>({})
|
||||
const loading = ref(true)
|
||||
const failed = ref(false)
|
||||
const busy = ref(false)
|
||||
@@ -177,7 +181,7 @@ function adopt(record: RecordEnvelope | undefined): void {
|
||||
for (const field of fields.value) {
|
||||
if (field.type === 'password') {
|
||||
next[field.name] = ''
|
||||
} else if (isMLFieldType(field.type)) {
|
||||
} else if (isMLField(field)) {
|
||||
next[field.name] = mergeMLValue(record.data[field.name], locales)
|
||||
}
|
||||
}
|
||||
@@ -248,8 +252,7 @@ function update(name: string, value: unknown): void {
|
||||
const previous = values.value[name]
|
||||
const next = { ...values.value, [name]: value }
|
||||
edited.add(name)
|
||||
const type = fields.value.find((field) => field.name === name)?.type
|
||||
if (isMLFieldType(type)) {
|
||||
if (isMLField(fields.value.find((field) => field.name === name))) {
|
||||
let locales = editedLocales.get(name)
|
||||
if (!locales) {
|
||||
locales = new Set<string>()
|
||||
@@ -319,6 +322,7 @@ async function save(): Promise<RecordEnvelope | null> {
|
||||
}
|
||||
busy.value = true
|
||||
forbidden.value = null
|
||||
notices.value = {}
|
||||
try {
|
||||
const body = editablePayload(fields.value, values.value, mode)
|
||||
const result =
|
||||
@@ -330,11 +334,18 @@ async function save(): Promise<RecordEnvelope | null> {
|
||||
})
|
||||
if (result.data) {
|
||||
errors.value = {}
|
||||
// adopt reloads the stored values, so a field whose type changed the
|
||||
// submitted value shows what was stored.
|
||||
adopt(result.data)
|
||||
notices.value = noticeLines(result.data.meta.notices)
|
||||
// The save committed everything held against the session.
|
||||
pendingChanges.value = 0
|
||||
revision.value++
|
||||
showToast(message(schema.value.messages.saved, undefined, { name: recordName.value }))
|
||||
const savedText = message(schema.value.messages.saved, undefined, { name: recordName.value })
|
||||
// The toast repeats the notices, so a save that navigates away (save
|
||||
// and close) still tells the administrator what changed.
|
||||
const noticeText = Object.values(notices.value).flat().join(' ')
|
||||
showToast(noticeText === '' ? savedText : `${savedText} ${noticeText}`)
|
||||
return result.data
|
||||
}
|
||||
if (result.response.status === 422) {
|
||||
@@ -502,6 +513,7 @@ void load()
|
||||
:fields="panelFields"
|
||||
:values="values"
|
||||
:errors="errors"
|
||||
:notices="notices"
|
||||
:labels="labels"
|
||||
:source="path"
|
||||
:record-id="recordId"
|
||||
|
||||
Reference in New Issue
Block a user