@plutocms/pluto 0.12.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/FEATURES.md +7 -0
- package/app/app.vue +5 -2
- package/app/assets/css/admin-theme.css +16 -7
- package/app/components/ColorModeButton.vue +2 -1
- package/app/components/Pluto/Content/PlutoContentField.vue +17 -3
- package/app/components/Pluto/Content/PlutoContentForm.vue +60 -38
- package/app/components/Pluto/Content/PlutoContentList.vue +84 -42
- package/app/components/Pluto/Content/fields/PlutoFieldReference.vue +2 -1
- package/app/components/Pluto/Dashboard/PlutoDashboardContentActivity.vue +22 -19
- package/app/components/Pluto/Dashboard/PlutoDashboardContentOverview.vue +15 -14
- package/app/components/Pluto/Dashboard/PlutoDashboardQuickActions.vue +6 -5
- package/app/components/Pluto/Navbar/PlutoNavbarAdmin.vue +4 -1
- package/app/components/Pluto/PlutoDashboardWidgets.vue +2 -1
- package/app/components/Pluto/PlutoRoot.vue +18 -1
- package/app/components/Pluto/Settings/PlutoSettingsLanguage.vue +51 -0
- package/app/components/Pluto/View/PlutoViewAdminPanel.vue +9 -1
- package/app/components/Pluto/View/PlutoViewAside.vue +14 -7
- package/app/components/modal/Modal.vue +1 -1
- package/app/components/modal/ModalHeader.vue +3 -1
- package/app/composables/internal/content-text.ts +111 -0
- package/app/composables/internal/error-text.ts +58 -0
- package/app/composables/internal/i18n-core.ts +310 -0
- package/app/composables/internal/i18n-runtime.ts +87 -0
- package/app/composables/pluto-admin-nav.ts +21 -4
- package/app/composables/pluto-admin-pages.ts +13 -3
- package/app/composables/pluto-command-palette.ts +13 -6
- package/app/composables/pluto-content-actions.ts +9 -5
- package/app/composables/pluto-content-list.ts +88 -7
- package/app/composables/pluto-content-text.ts +28 -0
- package/app/composables/pluto-dashboard.ts +15 -2
- package/app/composables/pluto-error.ts +13 -0
- package/app/composables/pluto-extension.ts +4 -0
- package/app/composables/pluto-i18n.ts +171 -0
- package/app/composables/pluto-permissions.ts +16 -2
- package/app/composables/pluto-registry.ts +1 -0
- package/app/composables/pluto-settings.ts +16 -2
- package/app/layouts/admin.vue +2 -1
- package/app/pages/admin/content/[type]/[id].vue +3 -1
- package/app/pages/admin/content/[type]/index.vue +2 -1
- package/app/pages/admin/content/[type]/new.vue +2 -1
- package/app/pages/admin/home.vue +6 -4
- package/app/pages/admin/settings.vue +10 -5
- package/app/plugins/pluto-core-registrations.ts +10 -7
- package/app/plugins/pluto-i18n.ts +20 -0
- package/i18n/locales/en.json +137 -0
- package/i18n/locales/pt-BR.json +137 -0
- package/modules/pluto-i18n/index.ts +89 -0
- package/modules/pluto-i18n/locale-files.ts +187 -0
- package/package.json +1 -1
- package/scripts/generate-admin-theme.cjs +21 -7
- package/server/utils/pluto-content-handlers.ts +30 -7
- package/server/utils/pluto-error.ts +27 -0
- package/shared/types/i18n.d.ts +10 -0
- package/shared/types/i18n.ts +14 -0
- package/shared/types/registry.ts +13 -0
- package/shared/utils/content-validate.ts +17 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,30 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
## [0.14.0](https://github.com/plutocms/pluto/compare/v0.13.0...v0.14.0) (2026-10-18)
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
|
|
9
|
+
* **i18n:** add the admin and site translation system ([#161](https://github.com/plutocms/pluto/issues/161)) ([c8d91fb](https://github.com/plutocms/pluto/commit/c8d91fbc1ad34fb14da36328de41b7e449eb5991))
|
|
10
|
+
* **i18n:** translate the core admin UI ([#163](https://github.com/plutocms/pluto/issues/163)) ([d40750d](https://github.com/plutocms/pluto/commit/d40750d80365041d6b17c9e4e8aa0a4a298653aa))
|
|
11
|
+
|
|
12
|
+
## [0.13.0](https://github.com/plutocms/pluto/compare/v0.12.0...v0.13.0) (2026-10-18)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
* **admin:** mount the aside once and keep it mounted on mobile ([#158](https://github.com/plutocms/pluto/issues/158)) ([dcca464](https://github.com/plutocms/pluto/commit/dcca464ead4ec5f46b2c26c0eea0f94260553ac9))
|
|
18
|
+
* **content:** page and virtualize long content lists ([#154](https://github.com/plutocms/pluto/issues/154)) ([b8d35b0](https://github.com/plutocms/pluto/commit/b8d35b05230169713a3e7cacbdfd95f52c72936a))
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
### Bug Fixes
|
|
22
|
+
|
|
23
|
+
* **admin:** darken the navbar and sidebar in dark mode ([#159](https://github.com/plutocms/pluto/issues/159)) ([011fd2f](https://github.com/plutocms/pluto/commit/011fd2f616daf961486e99f69a0e8f0f0db5e51f))
|
|
24
|
+
* **admin:** make the navbar near black in light mode too ([#160](https://github.com/plutocms/pluto/issues/160)) ([f39656a](https://github.com/plutocms/pluto/commit/f39656a5b52081c05e8128b4d0828970b96a14e5))
|
|
25
|
+
* **admin:** use a dim dark gray instead of pitch black in dark mode ([#156](https://github.com/plutocms/pluto/issues/156)) ([500c900](https://github.com/plutocms/pluto/commit/500c900783980d760d6fade406dfc15334a61a4e))
|
|
26
|
+
* **admin:** use the theme text color in the modal ([#157](https://github.com/plutocms/pluto/issues/157)) ([e73c613](https://github.com/plutocms/pluto/commit/e73c613eead017fabbae66ef6f6a7624ec5a5a04))
|
|
27
|
+
|
|
4
28
|
## [0.12.0](https://github.com/plutocms/pluto/compare/v0.11.0...v0.12.0) (2026-10-17)
|
|
5
29
|
|
|
6
30
|
|
package/FEATURES.md
CHANGED
|
@@ -33,6 +33,13 @@ policies shared by every project here.
|
|
|
33
33
|
- Admin theme: the admin keeps its own palette, chart colors, surfaces, and font under
|
|
34
34
|
`data-pluto-admin`, so the consumer app's site theme does not reach it.
|
|
35
35
|
@.claude/skills/admin-theme/SKILL.md
|
|
36
|
+
- Internationalization (i18n): a custom engine for the admin and the public site. The pluto-i18n
|
|
37
|
+
module merges each layer's `i18n/locales/<code>.json`, `usePlutoI18n()` and
|
|
38
|
+
`usePlutoSiteI18n()` translate and format, a per-user admin language is chosen on the Settings
|
|
39
|
+
page and stored through a registered locale driver. The core admin UI, the registry entries, the
|
|
40
|
+
content-type convention keys (`usePlutoContentText`), the validation codes, and the server errors
|
|
41
|
+
(`createPlutoError`, `usePlutoErrorText`) are translated, in English and Brazilian Portuguese.
|
|
42
|
+
@.claude/skills/i18n/SKILL.md
|
|
36
43
|
- Single Tailwind build: the app owns the only Tailwind build. The pluto-css module generates
|
|
37
44
|
`#build/pluto.css` from each layer's `pluto-layer.css`, adds a fallback entry, and warns
|
|
38
45
|
about a wrong CSS setup. @.claude/skills/single-tailwind-build/SKILL.md
|
package/app/app.vue
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
const
|
|
2
|
+
const { t } = usePlutoI18n()
|
|
3
3
|
|
|
4
4
|
useHead({
|
|
5
|
-
titleTemplate: (title) =>
|
|
5
|
+
titleTemplate: (title) => {
|
|
6
|
+
const websiteName = t('pluto.app.name')
|
|
7
|
+
return title ? `${title} - ${websiteName}` : websiteName
|
|
8
|
+
},
|
|
6
9
|
})
|
|
7
10
|
</script>
|
|
8
11
|
|
|
@@ -9,6 +9,10 @@
|
|
|
9
9
|
* - `[data-pluto-admin-scope]`: for admin chrome that also shows on the
|
|
10
10
|
* public site, such as the admin navbar for a logged-in admin.
|
|
11
11
|
*
|
|
12
|
+
* `[data-pluto-admin-dark]` takes the dark tokens in both color modes. The
|
|
13
|
+
* admin navbar is near black in light mode too, so its text and buttons need
|
|
14
|
+
* the dark values.
|
|
15
|
+
*
|
|
12
16
|
* Every value is a literal. The consumer app styles its public site through
|
|
13
17
|
* its own app.config colors and @theme. Both reach the admin unless the admin
|
|
14
18
|
* pins its own values: app.config colors set Nuxt UI's --ui-color-* scales on
|
|
@@ -149,17 +153,18 @@
|
|
|
149
153
|
--ui-chart-5: oklch(58.8% 0.158 241.966); /* sky-600 */
|
|
150
154
|
--ui-chart-6: oklch(53.2% 0.157 131.589); /* lime-700 */
|
|
151
155
|
|
|
152
|
-
/* Surfaces */
|
|
156
|
+
/* Surfaces. The navbar is near black in both modes. */
|
|
153
157
|
--pluto-admin-canvas: #fff;
|
|
154
158
|
--pluto-admin-sidebar: oklch(98.5% 0 none);
|
|
155
|
-
--pluto-admin-navbar:
|
|
159
|
+
--pluto-admin-navbar: oklch(14.1% 0.005 285.823);
|
|
156
160
|
|
|
157
161
|
--ui-radius: 0.375rem;
|
|
158
162
|
--font-sans: "Outfit", ui-sans-serif, system-ui, sans-serif;
|
|
159
163
|
}
|
|
160
164
|
|
|
161
165
|
:root[data-pluto-admin].dark,
|
|
162
|
-
.dark [data-pluto-admin-scope]
|
|
166
|
+
.dark [data-pluto-admin-scope],
|
|
167
|
+
[data-pluto-admin-dark] {
|
|
163
168
|
--ui-primary: var(--ui-color-primary-400);
|
|
164
169
|
--ui-secondary: var(--ui-color-secondary-400);
|
|
165
170
|
--ui-success: var(--ui-color-success-400);
|
|
@@ -173,7 +178,7 @@
|
|
|
173
178
|
--ui-text: var(--ui-color-neutral-200);
|
|
174
179
|
--ui-text-highlighted: #fff;
|
|
175
180
|
--ui-text-inverted: var(--ui-color-neutral-900);
|
|
176
|
-
--ui-bg:
|
|
181
|
+
--ui-bg: oklch(24% 0.006 286);
|
|
177
182
|
--ui-bg-muted: var(--ui-color-neutral-800);
|
|
178
183
|
--ui-bg-elevated: var(--ui-color-neutral-800);
|
|
179
184
|
--ui-bg-accented: var(--ui-color-neutral-700);
|
|
@@ -190,10 +195,14 @@
|
|
|
190
195
|
--ui-chart-5: oklch(58.8% 0.158 241.966); /* sky-600 */
|
|
191
196
|
--ui-chart-6: oklch(64.8% 0.2 131.684); /* lime-600 */
|
|
192
197
|
|
|
193
|
-
/*
|
|
194
|
-
|
|
195
|
-
|
|
198
|
+
/*
|
|
199
|
+
* Four steps, darkest at the top: a near-black navbar, a darker sidebar,
|
|
200
|
+
* a dim dark gray canvas (not pitch black), and cards (--ui-bg) one step
|
|
201
|
+
* above the canvas.
|
|
202
|
+
*/
|
|
196
203
|
--pluto-admin-navbar: oklch(14.1% 0.005 285.823);
|
|
204
|
+
--pluto-admin-sidebar: oklch(17.5% 0.0055 286);
|
|
205
|
+
--pluto-admin-canvas: oklch(21% 0.006 285.885);
|
|
197
206
|
}
|
|
198
207
|
|
|
199
208
|
:root[data-pluto-admin] body {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
const colorMode = useColorMode()
|
|
3
|
+
const { t } = usePlutoI18n()
|
|
3
4
|
|
|
4
5
|
const isDark = computed({
|
|
5
6
|
get() {
|
|
@@ -15,7 +16,7 @@ const isDark = computed({
|
|
|
15
16
|
<ClientOnly v-if="!colorMode?.forced">
|
|
16
17
|
<UButton
|
|
17
18
|
:icon="isDark ? 'i-lucide-moon' : 'i-lucide-sun'"
|
|
18
|
-
:aria-label="isDark ? '
|
|
19
|
+
:aria-label="isDark ? t('pluto.common.switchToLightMode') : t('pluto.common.switchToDarkMode')"
|
|
19
20
|
color="neutral"
|
|
20
21
|
variant="ghost"
|
|
21
22
|
size="md"
|
|
@@ -7,6 +7,12 @@ const props = defineProps<{
|
|
|
7
7
|
field: PlutoField
|
|
8
8
|
modelValue: unknown
|
|
9
9
|
disabled?: boolean
|
|
10
|
+
/**
|
|
11
|
+
* A content type's `name`. Set it so the field text uses the
|
|
12
|
+
* `content.<name>.fields.*` translation keys. Without it, the field keeps
|
|
13
|
+
* its own English text.
|
|
14
|
+
*/
|
|
15
|
+
type?: string
|
|
10
16
|
}>()
|
|
11
17
|
|
|
12
18
|
const emit = defineEmits<{
|
|
@@ -14,21 +20,29 @@ const emit = defineEmits<{
|
|
|
14
20
|
}>()
|
|
15
21
|
|
|
16
22
|
const widget = usePlutoContentFieldWidget(props.field)
|
|
23
|
+
|
|
24
|
+
const { t } = usePlutoI18n()
|
|
25
|
+
const { byName } = usePlutoContentTypes()
|
|
26
|
+
const text = usePlutoContentText(() => (props.type ? byName(props.type) : undefined))
|
|
27
|
+
|
|
28
|
+
// The widget gets a translated copy, so a widget from any layer shows the
|
|
29
|
+
// translated text with no change. Without a type, the copy keeps the original.
|
|
30
|
+
const translated = computed(() => (props.type ? text.field(props.field) : props.field))
|
|
17
31
|
</script>
|
|
18
32
|
|
|
19
33
|
<template>
|
|
20
34
|
<component
|
|
21
35
|
:is="widget"
|
|
22
36
|
v-if="widget"
|
|
23
|
-
:field="
|
|
37
|
+
:field="translated"
|
|
24
38
|
:model-value="modelValue"
|
|
25
39
|
:disabled="disabled"
|
|
26
40
|
@update:model-value="emit('update:modelValue', $event)"
|
|
27
41
|
/>
|
|
28
42
|
|
|
29
|
-
<UFormField v-else :label="
|
|
43
|
+
<UFormField v-else :label="translated.label">
|
|
30
44
|
<p class="text-muted text-sm">
|
|
31
|
-
|
|
45
|
+
{{ t('pluto.fields.noWidget', { type: field.type }) }}
|
|
32
46
|
</p>
|
|
33
47
|
</UFormField>
|
|
34
48
|
</template>
|
|
@@ -10,6 +10,9 @@ const props = defineProps<{
|
|
|
10
10
|
|
|
11
11
|
const { byName } = usePlutoContentTypes()
|
|
12
12
|
const contentType = computed(() => byName(props.type))
|
|
13
|
+
const { t } = usePlutoI18n()
|
|
14
|
+
const text = usePlutoContentText(contentType)
|
|
15
|
+
const errorText = usePlutoErrorText()
|
|
13
16
|
|
|
14
17
|
if (!contentType.value) {
|
|
15
18
|
// The generated page (`app/pages/admin/content/[type]/{new,[id]}.vue`)
|
|
@@ -139,12 +142,16 @@ const isPublished = computed(
|
|
|
139
142
|
() =>
|
|
140
143
|
statusConfig.value !== null && currentStatus.value === publishedValue.value
|
|
141
144
|
)
|
|
142
|
-
const statusLabel = computed(
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
145
|
+
const statusLabel = computed(() => {
|
|
146
|
+
const value = currentStatus.value
|
|
147
|
+
if (value === undefined) {
|
|
148
|
+
return undefined
|
|
149
|
+
}
|
|
150
|
+
const literal = statusConfig.value?.values.find(
|
|
151
|
+
(entry) => entry.value === value
|
|
152
|
+
)?.label
|
|
153
|
+
return text.statusLabel(value, literal)
|
|
154
|
+
})
|
|
148
155
|
|
|
149
156
|
// --- Save -------------------------------------------------------------------
|
|
150
157
|
|
|
@@ -152,6 +159,19 @@ const toast = useToast()
|
|
|
152
159
|
const saving = ref(false)
|
|
153
160
|
const validationErrors = ref<ContentValidationError[]>([])
|
|
154
161
|
|
|
162
|
+
// A validation error with a `code` shows the translated message. The
|
|
163
|
+
// `message` of the error stays English, and shows when there is no code.
|
|
164
|
+
function validationText(error: ContentValidationError): string {
|
|
165
|
+
if (!error.code) {
|
|
166
|
+
return error.message
|
|
167
|
+
}
|
|
168
|
+
const field = contentType.value?.fields.find((entry) => entry.name === error.field)
|
|
169
|
+
return t(`pluto.validation.${error.code}`, {
|
|
170
|
+
...error.params,
|
|
171
|
+
label: field ? text.fieldLabel(field) : error.field,
|
|
172
|
+
})
|
|
173
|
+
}
|
|
174
|
+
|
|
155
175
|
function buildPayload(statusOverride?: string): Record<string, unknown> {
|
|
156
176
|
const type = contentType.value
|
|
157
177
|
if (!type) {
|
|
@@ -201,23 +221,23 @@ async function onSave(statusOverride?: string) {
|
|
|
201
221
|
|
|
202
222
|
const title =
|
|
203
223
|
statusOverride === publishedValue.value
|
|
204
|
-
? '
|
|
224
|
+
? t('pluto.form.published')
|
|
205
225
|
: statusOverride === draftValue.value
|
|
206
|
-
? '
|
|
207
|
-
: '
|
|
226
|
+
? t('pluto.form.unpublished')
|
|
227
|
+
: t('pluto.form.saved')
|
|
208
228
|
|
|
209
229
|
toast.add({
|
|
210
230
|
title,
|
|
211
|
-
description:
|
|
231
|
+
description: t('pluto.form.updated', { label: text.label }),
|
|
212
232
|
color: 'success',
|
|
213
233
|
})
|
|
214
|
-
} catch {
|
|
234
|
+
} catch (error) {
|
|
215
235
|
// A raced permissions check, or any other server-side rejection
|
|
216
236
|
// (a 403 included), surfaces here rather than as an unhandled
|
|
217
237
|
// rejection.
|
|
218
238
|
toast.add({
|
|
219
|
-
title: '
|
|
220
|
-
description:
|
|
239
|
+
title: t('pluto.common.error'),
|
|
240
|
+
description: errorText(error, 'pluto.form.saveError'),
|
|
221
241
|
color: 'error',
|
|
222
242
|
})
|
|
223
243
|
} finally {
|
|
@@ -245,10 +265,10 @@ async function confirmDelete() {
|
|
|
245
265
|
await remove()
|
|
246
266
|
closeDeleteModal()
|
|
247
267
|
await navigateTo(basePath.value)
|
|
248
|
-
} catch {
|
|
268
|
+
} catch (error) {
|
|
249
269
|
toast.add({
|
|
250
|
-
title: '
|
|
251
|
-
description:
|
|
270
|
+
title: t('pluto.common.error'),
|
|
271
|
+
description: errorText(error, 'pluto.form.removeError'),
|
|
252
272
|
color: 'error',
|
|
253
273
|
})
|
|
254
274
|
} finally {
|
|
@@ -258,6 +278,12 @@ async function confirmDelete() {
|
|
|
258
278
|
|
|
259
279
|
// --- Layout / aside -----------------------------------------------------------
|
|
260
280
|
|
|
281
|
+
const heading = computed(() =>
|
|
282
|
+
editing.value
|
|
283
|
+
? t('pluto.content.edit', { label: text.label })
|
|
284
|
+
: text.newLabel
|
|
285
|
+
)
|
|
286
|
+
|
|
261
287
|
const layout = computed(() => contentType.value?.form?.layout ?? 'default')
|
|
262
288
|
const showAside = computed(() => {
|
|
263
289
|
const configured = contentType.value?.form?.aside
|
|
@@ -304,7 +330,7 @@ const unpublishDeleteItems = computed(() => [
|
|
|
304
330
|
...(statusConfig.value && isPublished.value && canPublish.value
|
|
305
331
|
? [
|
|
306
332
|
{
|
|
307
|
-
label: '
|
|
333
|
+
label: t('pluto.form.unpublish'),
|
|
308
334
|
icon: 'lucide:eye-off',
|
|
309
335
|
onSelect: () => onSave(draftValue.value),
|
|
310
336
|
},
|
|
@@ -313,7 +339,7 @@ const unpublishDeleteItems = computed(() => [
|
|
|
313
339
|
...(editing.value && canDelete.value
|
|
314
340
|
? [
|
|
315
341
|
{
|
|
316
|
-
label: '
|
|
342
|
+
label: t('pluto.common.delete'),
|
|
317
343
|
icon: 'lucide:trash',
|
|
318
344
|
color: 'error' as const,
|
|
319
345
|
onSelect: () => openDeleteModal(),
|
|
@@ -333,13 +359,7 @@ const unpublishDeleteItems = computed(() => [
|
|
|
333
359
|
<template #toolbar>
|
|
334
360
|
<PlutoViewToolbar
|
|
335
361
|
:back-to="basePath"
|
|
336
|
-
:title="
|
|
337
|
-
layout === 'focus'
|
|
338
|
-
? editing
|
|
339
|
-
? `Edit ${contentType.label}`
|
|
340
|
-
: `New ${contentType.label}`
|
|
341
|
-
: undefined
|
|
342
|
-
"
|
|
362
|
+
:title="layout === 'focus' ? heading : undefined"
|
|
343
363
|
>
|
|
344
364
|
<template #right>
|
|
345
365
|
<UBadge
|
|
@@ -370,7 +390,7 @@ const unpublishDeleteItems = computed(() => [
|
|
|
370
390
|
variant="subtle"
|
|
371
391
|
@click="onSave()"
|
|
372
392
|
>
|
|
373
|
-
|
|
393
|
+
{{ statusConfig ? t('pluto.form.saveDraft') : t('pluto.common.save') }}
|
|
374
394
|
</UButton>
|
|
375
395
|
|
|
376
396
|
<UButton
|
|
@@ -379,7 +399,7 @@ const unpublishDeleteItems = computed(() => [
|
|
|
379
399
|
icon="lucide:save"
|
|
380
400
|
@click="onSave()"
|
|
381
401
|
>
|
|
382
|
-
|
|
402
|
+
{{ t('pluto.form.update') }}
|
|
383
403
|
</UButton>
|
|
384
404
|
|
|
385
405
|
<UButton
|
|
@@ -388,7 +408,7 @@ const unpublishDeleteItems = computed(() => [
|
|
|
388
408
|
icon="lucide:upload"
|
|
389
409
|
@click="onSave(publishedValue)"
|
|
390
410
|
>
|
|
391
|
-
|
|
411
|
+
{{ t('pluto.form.publish') }}
|
|
392
412
|
</UButton>
|
|
393
413
|
|
|
394
414
|
<UDropdownMenu
|
|
@@ -422,25 +442,26 @@ const unpublishDeleteItems = computed(() => [
|
|
|
422
442
|
|
|
423
443
|
<UAlert
|
|
424
444
|
v-if="validationErrors.length > 0"
|
|
445
|
+
:title="t('pluto.form.fixErrors')"
|
|
425
446
|
color="error"
|
|
426
|
-
title="Please fix the following before saving"
|
|
427
447
|
>
|
|
428
448
|
<template #description>
|
|
429
449
|
<ul class="list-disc pl-4">
|
|
430
450
|
<li v-for="fieldError in validationErrors" :key="fieldError.field">
|
|
431
|
-
{{ fieldError
|
|
451
|
+
{{ validationText(fieldError) }}
|
|
432
452
|
</li>
|
|
433
453
|
</ul>
|
|
434
454
|
</template>
|
|
435
455
|
</UAlert>
|
|
436
456
|
|
|
437
457
|
<h1 v-if="layout !== 'focus'" class="text-3xl font-bold lg:text-4xl">
|
|
438
|
-
{{
|
|
458
|
+
{{ heading }}
|
|
439
459
|
</h1>
|
|
440
460
|
|
|
441
461
|
<PlutoContentField
|
|
442
462
|
v-for="field in mainFields"
|
|
443
463
|
:key="field.name"
|
|
464
|
+
:type="type"
|
|
444
465
|
:field="field"
|
|
445
466
|
:model-value="item[field.name]"
|
|
446
467
|
:disabled="!canWrite || saving"
|
|
@@ -448,11 +469,12 @@ const unpublishDeleteItems = computed(() => [
|
|
|
448
469
|
/>
|
|
449
470
|
|
|
450
471
|
<template v-if="showAside" #aside>
|
|
451
|
-
<PlutoViewAside v-model:open="asideOpen" title="
|
|
472
|
+
<PlutoViewAside v-model:open="asideOpen" :title="t('pluto.common.settings')">
|
|
452
473
|
<div class="flex flex-col gap-6">
|
|
453
474
|
<PlutoContentField
|
|
454
475
|
v-for="field in sideFields"
|
|
455
476
|
:key="field.name"
|
|
477
|
+
:type="type"
|
|
456
478
|
:field="field"
|
|
457
479
|
:model-value="item[field.name]"
|
|
458
480
|
:disabled="!canWrite || saving"
|
|
@@ -465,19 +487,19 @@ const unpublishDeleteItems = computed(() => [
|
|
|
465
487
|
|
|
466
488
|
<PlutoViewAdminPanel v-else>
|
|
467
489
|
<UAlert
|
|
468
|
-
:description="
|
|
490
|
+
:description="t('pluto.content.typeNotFoundDescription', { type })"
|
|
491
|
+
:title="t('pluto.content.typeNotFound')"
|
|
469
492
|
color="error"
|
|
470
|
-
title="Content type not found"
|
|
471
493
|
/>
|
|
472
494
|
</PlutoViewAdminPanel>
|
|
473
495
|
|
|
474
496
|
<Modal v-model="isDeleteModalOpen" :custom-size="480">
|
|
475
497
|
<ModalHeader @close="closeDeleteModal">
|
|
476
|
-
|
|
498
|
+
{{ t('pluto.form.removeTitle', { label: text.label }) }}
|
|
477
499
|
</ModalHeader>
|
|
478
500
|
|
|
479
501
|
<ModalContent>
|
|
480
|
-
<p>
|
|
502
|
+
<p>{{ t('pluto.content.removeConfirm') }}</p>
|
|
481
503
|
</ModalContent>
|
|
482
504
|
|
|
483
505
|
<ModalFooter>
|
|
@@ -488,7 +510,7 @@ const unpublishDeleteItems = computed(() => [
|
|
|
488
510
|
color="neutral"
|
|
489
511
|
@click="closeDeleteModal"
|
|
490
512
|
>
|
|
491
|
-
|
|
513
|
+
{{ t('pluto.common.cancel') }}
|
|
492
514
|
</UButton>
|
|
493
515
|
|
|
494
516
|
<UButton
|
|
@@ -497,7 +519,7 @@ const unpublishDeleteItems = computed(() => [
|
|
|
497
519
|
color="error"
|
|
498
520
|
@click="confirmDelete"
|
|
499
521
|
>
|
|
500
|
-
|
|
522
|
+
{{ t('pluto.common.remove') }}
|
|
501
523
|
</UButton>
|
|
502
524
|
</div>
|
|
503
525
|
</ModalFooter>
|