@michaelthielemann/kestrel 4.1.2 → 5.0.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/README.md +26 -201
- package/dist/boot.d.ts +45 -0
- package/dist/boot.js +274 -0
- package/dist/cast.d.ts +3 -0
- package/dist/cast.js +6 -0
- package/dist/catalogue.d.ts +11 -0
- package/dist/catalogue.js +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +55 -0
- package/dist/context.d.ts +62 -0
- package/dist/context.js +61 -0
- package/dist/dataflow.d.ts +3 -0
- package/dist/dataflow.js +65 -0
- package/dist/defineConfig.d.ts +157 -0
- package/dist/defineConfig.js +41 -0
- package/dist/defineContract.d.ts +12 -0
- package/dist/defineContract.js +16 -0
- package/dist/defineModule.d.ts +75 -0
- package/dist/defineModule.js +9 -0
- package/dist/definePipeline.d.ts +7 -0
- package/dist/definePipeline.js +11 -0
- package/dist/errors.d.ts +26 -0
- package/dist/errors.js +48 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +14 -0
- package/dist/load.d.ts +7 -0
- package/dist/load.js +79 -0
- package/dist/logger.d.ts +18 -0
- package/dist/logger.js +36 -0
- package/dist/registry.d.ts +14 -0
- package/dist/registry.js +87 -0
- package/dist/result.d.ts +19 -0
- package/dist/result.js +19 -0
- package/dist/runner.d.ts +29 -0
- package/dist/runner.js +133 -0
- package/dist/schema.d.ts +7 -0
- package/dist/schema.js +142 -0
- package/dist/sort.d.ts +2 -0
- package/dist/sort.js +50 -0
- package/dist/testing/runPipeline.d.ts +9 -0
- package/dist/testing/runPipeline.js +22 -0
- package/dist/triggers/cron.d.ts +10 -0
- package/dist/triggers/cron.js +44 -0
- package/dist/triggers/cronMatch.d.ts +10 -0
- package/dist/triggers/cronMatch.js +68 -0
- package/dist/triggers/http.d.ts +58 -0
- package/dist/triggers/http.js +292 -0
- package/package.json +25 -151
- package/NOTICE +0 -12
- package/kestrel.config.ts +0 -28
- package/layers/access/nuxt.config.ts +0 -5
- package/layers/access/server/middleware/00.ip-allowlist.ts +0 -52
- package/layers/access/server/middleware/access-guard.ts +0 -32
- package/layers/access/server/types.d.ts +0 -7
- package/layers/admin/app/components/AdminAccount.vue +0 -212
- package/layers/admin/app/components/AdminNav.vue +0 -44
- package/layers/admin/app/components/BlockFields.vue +0 -67
- package/layers/admin/app/components/BlockPreview.vue +0 -413
- package/layers/admin/app/components/BlockTree.vue +0 -433
- package/layers/admin/app/components/BlocksBody.vue +0 -163
- package/layers/admin/app/components/CollectionDeleteDialog.vue +0 -38
- package/layers/admin/app/components/CollectionEditor.vue +0 -324
- package/layers/admin/app/components/CollectionList.vue +0 -429
- package/layers/admin/app/components/CollectionListBulkBar.vue +0 -62
- package/layers/admin/app/components/CollectionListColumnsPanel.vue +0 -37
- package/layers/admin/app/components/CollectionListFilterPanel.vue +0 -145
- package/layers/admin/app/components/CollectionListPager.vue +0 -70
- package/layers/admin/app/components/CollectionListTable.vue +0 -347
- package/layers/admin/app/components/EditorStatus.vue +0 -211
- package/layers/admin/app/components/EditorUnsupported.vue +0 -28
- package/layers/admin/app/components/FieldsBody.vue +0 -23
- package/layers/admin/app/components/LocaleBar.vue +0 -107
- package/layers/admin/app/components/PageFields.vue +0 -129
- package/layers/admin/app/components/PageFieldsPane.vue +0 -39
- package/layers/admin/app/components/SeoFields.vue +0 -210
- package/layers/admin/app/components/SingletonEditor.vue +0 -93
- package/layers/admin/app/composables/useAuth.ts +0 -68
- package/layers/admin/app/composables/useBlockTree.ts +0 -95
- package/layers/admin/app/composables/useBlocks.ts +0 -7
- package/layers/admin/app/composables/useCachedResource.ts +0 -35
- package/layers/admin/app/composables/useCollectionOps.ts +0 -102
- package/layers/admin/app/composables/useCollections.ts +0 -6
- package/layers/admin/app/composables/useContentLocales.ts +0 -23
- package/layers/admin/app/composables/useEditForm.ts +0 -402
- package/layers/admin/app/composables/useLinkResolver.ts +0 -40
- package/layers/admin/app/composables/useListBatchActions.ts +0 -78
- package/layers/admin/app/composables/useListColumns.ts +0 -53
- package/layers/admin/app/composables/useListFilterDraft.ts +0 -129
- package/layers/admin/app/composables/useListRows.ts +0 -75
- package/layers/admin/app/composables/useListSelection.ts +0 -28
- package/layers/admin/app/composables/useListUrlState.ts +0 -53
- package/layers/admin/app/composables/usePendingPublishPoll.ts +0 -15
- package/layers/admin/app/composables/usePreviewViewport.ts +0 -30
- package/layers/admin/app/composables/usePublishStatus.ts +0 -107
- package/layers/admin/app/composables/useRailCollapsed.ts +0 -14
- package/layers/admin/app/composables/useTheme.ts +0 -36
- package/layers/admin/app/composables/useToolbarPanel.ts +0 -38
- package/layers/admin/app/composables/useUnsavedGuard.ts +0 -22
- package/layers/admin/app/layouts/admin.vue +0 -266
- package/layers/admin/app/middleware/admin-auth.ts +0 -4
- package/layers/admin/app/pages/admin/[collection]/[id].vue +0 -203
- package/layers/admin/app/pages/admin/[collection]/index.vue +0 -62
- package/layers/admin/app/pages/admin/index.vue +0 -118
- package/layers/admin/app/pages/admin/login.vue +0 -68
- package/layers/admin/app/pages/admin/media.vue +0 -28
- package/layers/admin/app/pages/admin/references.vue +0 -146
- package/layers/admin/app/plugins/admin-style-guard.client.ts +0 -76
- package/layers/admin/app/plugins/reauth.client.ts +0 -20
- package/layers/admin/app/utils/admin-style-guard.ts +0 -20
- package/layers/admin/app/utils/block-tree.ts +0 -214
- package/layers/admin/app/utils/collection-ops.ts +0 -69
- package/layers/admin/app/utils/dead-refs.ts +0 -30
- package/layers/admin/app/utils/edit-form.ts +0 -267
- package/layers/admin/app/utils/editor-expose.ts +0 -36
- package/layers/admin/app/utils/editor-form-context.ts +0 -82
- package/layers/admin/app/utils/editor-registry.ts +0 -20
- package/layers/admin/app/utils/field-keys.ts +0 -11
- package/layers/admin/app/utils/list-cell.ts +0 -46
- package/layers/admin/app/utils/list-columns.ts +0 -142
- package/layers/admin/app/utils/list-query.ts +0 -96
- package/layers/admin/app/utils/nav-active.ts +0 -9
- package/layers/admin/app/utils/populate-blocks.ts +0 -288
- package/layers/admin/app/utils/preview-channel.ts +0 -62
- package/layers/admin/app/utils/preview-viewport.ts +0 -80
- package/layers/admin/app/utils/reauth.ts +0 -67
- package/layers/admin/app/utils/record-title.ts +0 -15
- package/layers/admin/app/utils/register-builtin-editors.ts +0 -9
- package/layers/admin/app/utils/safe-redirect.ts +0 -12
- package/layers/admin/nuxt.config.ts +0 -20
- package/layers/auth/nuxt.config.ts +0 -1
- package/layers/auth/server/plugins/01.register-auth-pipelines.ts +0 -7
- package/layers/collections/nuxt.config.ts +0 -1
- package/layers/collections/server/plugins/01.register.ts +0 -29
- package/layers/collections/server/plugins/02.register-relation-populate.ts +0 -18
- package/layers/core/app/composables/layouts.ts +0 -5
- package/layers/core/app/utils/field-factories.ts +0 -129
- package/layers/core/app/utils/layouts.ts +0 -38
- package/layers/core/modules/auto-discovery/extract-block.ts +0 -179
- package/layers/core/modules/auto-discovery/index.ts +0 -112
- package/layers/core/modules/auto-discovery/package-registry.ts +0 -19
- package/layers/core/modules/auto-discovery/scan.ts +0 -170
- package/layers/core/modules/auto-discovery/virtual.d.ts +0 -20
- package/layers/core/modules/component-namespace/index.ts +0 -44
- package/layers/core/modules/component-namespace/shared.ts +0 -24
- package/layers/core/modules/kestrel/app-shell.ts +0 -55
- package/layers/core/modules/kestrel/auto-imports.ts +0 -44
- package/layers/core/modules/kestrel/index.ts +0 -144
- package/layers/core/modules/plugin-order/index.ts +0 -69
- package/layers/core/modules/plugin-order/plugin-order.ts +0 -273
- package/layers/core/nuxt.config.ts +0 -10
- package/layers/core/server/api/[...path].ts +0 -98
- package/layers/core/server/plugins/00.config.ts +0 -12
- package/layers/core/server/plugins/00.migrate.ts +0 -12
- package/layers/core/server/plugins/01.register-introspection-pipeline.ts +0 -5
- package/layers/core/server/plugins/01.register-openapi-pipeline.ts +0 -5
- package/layers/core/server/plugins/01.register-tooling-pipelines.ts +0 -5
- package/layers/core/server/plugins/02.schema-sync.ts +0 -58
- package/layers/core/server/plugins/04.outbox-worker.ts +0 -31
- package/layers/core/server/plugins/05.reindex-refs.ts +0 -5
- package/layers/core/server/routes/__kestrel/dashboard.get.ts +0 -29
- package/layers/core/server/tasks/db/migrate-module.ts +0 -83
- package/layers/core/server/tasks/db/migrate-revisions.ts +0 -20
- package/layers/core/server/tasks/db/migrate.ts +0 -50
- package/layers/core/server/utils/server-config.ts +0 -81
- package/layers/fields/nuxt.config.ts +0 -1
- package/layers/fields/server/plugins/01.register-blocks-pipeline.ts +0 -7
- package/layers/fields/server/plugins/01.register-field-populate.ts +0 -11
- package/layers/media/app/components/KestrelImg.vue +0 -94
- package/layers/media/app/components/MediaConflictDialog.vue +0 -35
- package/layers/media/app/components/MediaDeleteDialog.vue +0 -52
- package/layers/media/app/components/MediaGrid.vue +0 -113
- package/layers/media/app/components/MediaImage.vue +0 -70
- package/layers/media/app/components/MediaLibrary.vue +0 -456
- package/layers/media/app/components/MediaNewFolderDialog.vue +0 -32
- package/layers/media/app/components/MediaPathBar.vue +0 -163
- package/layers/media/app/components/MediaPicker.vue +0 -19
- package/layers/media/app/components/MediaRenameDialog.vue +0 -32
- package/layers/media/app/components/MediaTable.vue +0 -75
- package/layers/media/app/components/MediaThumb.vue +0 -25
- package/layers/media/app/components/MediaToolbar.vue +0 -90
- package/layers/media/app/components/MediaUploadDialog.vue +0 -87
- package/layers/media/app/components/MediaViewer.vue +0 -137
- package/layers/media/app/components/field/Media.vue +0 -263
- package/layers/media/app/composables/useBlurUp.ts +0 -55
- package/layers/media/app/composables/useMediaClipboard.ts +0 -14
- package/layers/media/app/composables/useMediaContextMenu.ts +0 -56
- package/layers/media/app/composables/useMediaDnd.ts +0 -56
- package/layers/media/app/composables/useMediaLibrary.ts +0 -152
- package/layers/media/app/composables/useMediaOps.ts +0 -50
- package/layers/media/app/composables/useMediaRelocate.ts +0 -39
- package/layers/media/app/composables/useMediaResolver.ts +0 -50
- package/layers/media/app/composables/useMediaUpload.ts +0 -93
- package/layers/media/app/composables/useMediaVariant.ts +0 -34
- package/layers/media/app/plugins/field-media.client.ts +0 -6
- package/layers/media/app/utils/ai-disclosure.ts +0 -19
- package/layers/media/app/utils/dnd.ts +0 -69
- package/layers/media/app/utils/library.ts +0 -81
- package/layers/media/app/utils/ops.ts +0 -60
- package/layers/media/app/utils/picture-model.ts +0 -125
- package/layers/media/app/utils/thumbhash.ts +0 -19
- package/layers/media/app/utils/variant-name.ts +0 -20
- package/layers/media/nuxt.config.ts +0 -7
- package/layers/media/server/middleware/ondemand-variants.ts +0 -38
- package/layers/media/server/plugins/01.register-media-pipelines.ts +0 -7
- package/layers/media/server/plugins/02.register-media.ts +0 -22
- package/layers/media/server/plugins/04.variant-capture.ts +0 -27
- package/layers/media/server/plugins/05.media-cleanup.ts +0 -5
- package/layers/media/server/tasks/media/backfill.ts +0 -17
- package/layers/public/app/app.vue +0 -5
- package/layers/public/app/components/BlockRenderer.vue +0 -56
- package/layers/public/app/components/KestrelLink.vue +0 -26
- package/layers/public/app/components/KestrelPreviewBridge.vue +0 -133
- package/layers/public/app/composables/public-page.ts +0 -24
- package/layers/public/app/error.vue +0 -12
- package/layers/public/app/layouts/default.vue +0 -5
- package/layers/public/app/pages/[...slug].vue +0 -239
- package/layers/public/app/pages/__kestrel/preview.vue +0 -46
- package/layers/public/app/utils/block-edit-context.ts +0 -16
- package/layers/public/app/utils/json-ld.ts +0 -139
- package/layers/public/app/utils/link-href.ts +0 -46
- package/layers/public/app/utils/page-head.ts +0 -84
- package/layers/public/app/utils/page-layout.ts +0 -18
- package/layers/public/app/utils/preview-protocol.ts +0 -117
- package/layers/public/app/utils/public-route.ts +0 -46
- package/layers/public/app/utils/site-head.ts +0 -43
- package/layers/public/modules/deploy-output/index.ts +0 -80
- package/layers/public/modules/prerender-routes/discover.ts +0 -37
- package/layers/public/modules/prerender-routes/index.ts +0 -88
- package/layers/public/modules/prerender-routes/routes.ts +0 -16
- package/layers/public/modules/prune-media/index.ts +0 -48
- package/layers/public/modules/prune-media/prune-media.ts +0 -81
- package/layers/public/nuxt.config.ts +0 -14
- package/layers/public/server/delivery-live/serve.ts +0 -111
- package/layers/public/server/middleware/delivery-live-catchall.ts +0 -7
- package/layers/public/server/pipelines/index.ts +0 -23
- package/layers/public/server/plugins/00.ensure-snapshot-triggers.ts +0 -14
- package/layers/public/server/plugins/01.register-public-pipelines.ts +0 -7
- package/layers/public/server/plugins/02.register-links.ts +0 -16
- package/layers/public/server/plugins/03.redirects.ts +0 -55
- package/layers/public/server/plugins/05.plan-publish.ts +0 -5
- package/layers/public/server/plugins/zz.publish.ts +0 -127
- package/layers/public/server/routes/llms-full.txt.get.ts +0 -99
- package/layers/public/server/routes/llms.txt.get.ts +0 -66
- package/layers/public/server/routes/redirects.json.get.ts +0 -58
- package/layers/public/server/routes/robots.txt.get.ts +0 -11
- package/layers/public/server/routes/sitemap.xml.get.ts +0 -79
- package/layers/public/server/tasks/publish/run.ts +0 -32
- package/layers/public/server/utils/publish/render-live.ts +0 -26
- package/layers/ui/app/assets/scss/_base.scss +0 -16
- package/layers/ui/app/assets/scss/_datepicker.scss +0 -82
- package/layers/ui/app/assets/scss/_mixins.scss +0 -50
- package/layers/ui/app/assets/scss/_reset.scss +0 -65
- package/layers/ui/app/assets/scss/_tokens.scss +0 -183
- package/layers/ui/app/assets/scss/main.scss +0 -4
- package/layers/ui/app/components/field/Boolean.vue +0 -30
- package/layers/ui/app/components/field/Choice.vue +0 -73
- package/layers/ui/app/components/field/Datetime.vue +0 -98
- package/layers/ui/app/components/field/DeadRefNote.vue +0 -28
- package/layers/ui/app/components/field/Json.vue +0 -62
- package/layers/ui/app/components/field/Layout.vue +0 -138
- package/layers/ui/app/components/field/Link.vue +0 -112
- package/layers/ui/app/components/field/LinkInternalPicker.vue +0 -95
- package/layers/ui/app/components/field/Number.vue +0 -30
- package/layers/ui/app/components/field/Relation.vue +0 -49
- package/layers/ui/app/components/field/Renderer.vue +0 -30
- package/layers/ui/app/components/field/Repeater.vue +0 -460
- package/layers/ui/app/components/field/Richtext.vue +0 -26
- package/layers/ui/app/components/field/Slug.vue +0 -65
- package/layers/ui/app/components/field/Text.vue +0 -35
- package/layers/ui/app/components/field/Unsupported.vue +0 -34
- package/layers/ui/app/components/ui/Alert.vue +0 -50
- package/layers/ui/app/components/ui/Brand.vue +0 -22
- package/layers/ui/app/components/ui/Button.vue +0 -116
- package/layers/ui/app/components/ui/ButtonGroup.vue +0 -96
- package/layers/ui/app/components/ui/Checkbox.vue +0 -53
- package/layers/ui/app/components/ui/CheckboxGroup.vue +0 -51
- package/layers/ui/app/components/ui/Combobox.vue +0 -345
- package/layers/ui/app/components/ui/DatePicker.vue +0 -92
- package/layers/ui/app/components/ui/DateRangePicker.vue +0 -100
- package/layers/ui/app/components/ui/Dialog.vue +0 -107
- package/layers/ui/app/components/ui/EmptyState.vue +0 -46
- package/layers/ui/app/components/ui/Field.vue +0 -58
- package/layers/ui/app/components/ui/Fieldset.vue +0 -65
- package/layers/ui/app/components/ui/Icon.vue +0 -44
- package/layers/ui/app/components/ui/Menu.vue +0 -54
- package/layers/ui/app/components/ui/NumberInput.vue +0 -175
- package/layers/ui/app/components/ui/Richtext.vue +0 -141
- package/layers/ui/app/components/ui/RichtextToolbar.vue +0 -251
- package/layers/ui/app/components/ui/Select.vue +0 -52
- package/layers/ui/app/components/ui/TextInput.vue +0 -111
- package/layers/ui/app/components/ui/Textarea.vue +0 -39
- package/layers/ui/app/components/ui/TimeInput.vue +0 -18
- package/layers/ui/app/components/ui/Toasts.vue +0 -103
- package/layers/ui/app/components/ui/Tooltip.vue +0 -45
- package/layers/ui/app/composables/useAdminLang.ts +0 -8
- package/layers/ui/app/composables/useDragReorder.ts +0 -41
- package/layers/ui/app/composables/useEchoGuard.ts +0 -28
- package/layers/ui/app/composables/useLinkField.ts +0 -70
- package/layers/ui/app/composables/useRecordOptions.ts +0 -84
- package/layers/ui/app/composables/useRepeater.ts +0 -99
- package/layers/ui/app/composables/useT.ts +0 -32
- package/layers/ui/app/composables/useToast.ts +0 -46
- package/layers/ui/app/i18n/de.ts +0 -432
- package/layers/ui/app/i18n/en.ts +0 -434
- package/layers/ui/app/utils/boolean-field.ts +0 -10
- package/layers/ui/app/utils/field-component.ts +0 -22
- package/layers/ui/app/utils/field-empty.ts +0 -21
- package/layers/ui/app/utils/field-registry.ts +0 -36
- package/layers/ui/app/utils/humanize.ts +0 -14
- package/layers/ui/app/utils/icons.ts +0 -143
- package/layers/ui/app/utils/iso-date.ts +0 -18
- package/layers/ui/app/utils/localized.ts +0 -13
- package/layers/ui/app/utils/record-context.ts +0 -15
- package/layers/ui/app/utils/reorder.ts +0 -8
- package/layers/ui/app/utils/richtext-preserve-class.ts +0 -31
- package/layers/ui/app/utils/useFieldA11y.ts +0 -40
- package/layers/ui/nuxt.config.ts +0 -9
- package/nuxt.config.ts +0 -108
- package/scripts/consumer-template-ci.mjs +0 -160
- package/scripts/copy-create-payload.mjs +0 -49
- package/scripts/coverage-all.mjs +0 -163
- package/scripts/dashboard.mjs +0 -89
- package/scripts/hash-password.mjs +0 -29
- package/scripts/kestrel.mjs +0 -240
- package/scripts/lib/cli.mjs +0 -129
- package/scripts/lib/coverage-model.mjs +0 -51
- package/scripts/lib/password.mjs +0 -19
- package/scripts/lib/scaffold.mjs +0 -205
- package/scripts/port-graph.mjs +0 -350
- package/scripts/typecheck.mjs +0 -62
- package/templates/starter/README.md +0 -65
- package/templates/starter/_env.example +0 -17
- package/templates/starter/_gitignore +0 -26
- package/templates/starter/_package.json +0 -22
- package/templates/starter/app/app.vue +0 -7
- package/templates/starter/app/blocks/Prose.vue +0 -13
- package/templates/starter/app/layouts/default.vue +0 -6
- package/templates/starter/nuxt.config.ts +0 -23
- package/templates/starter/pnpm-workspace.yaml +0 -8
- package/templates/starter/tsconfig.json +0 -3
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
*,
|
|
2
|
-
*::before,
|
|
3
|
-
*::after {
|
|
4
|
-
box-sizing: border-box;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
* {
|
|
8
|
-
margin: 0;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
html {
|
|
12
|
-
-webkit-text-size-adjust: 100%;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
body {
|
|
16
|
-
min-height: 100svh;
|
|
17
|
-
line-height: var(--leading-normal);
|
|
18
|
-
-webkit-font-smoothing: antialiased;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
img,
|
|
22
|
-
picture,
|
|
23
|
-
svg,
|
|
24
|
-
video,
|
|
25
|
-
canvas {
|
|
26
|
-
display: block;
|
|
27
|
-
max-width: 100%;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
input,
|
|
31
|
-
button,
|
|
32
|
-
textarea,
|
|
33
|
-
select {
|
|
34
|
-
font: inherit;
|
|
35
|
-
color: inherit;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
button {
|
|
39
|
-
cursor: pointer;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
button:disabled {
|
|
43
|
-
cursor: not-allowed;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
p,
|
|
47
|
-
h1,
|
|
48
|
-
h2,
|
|
49
|
-
h3,
|
|
50
|
-
h4,
|
|
51
|
-
h5,
|
|
52
|
-
h6 {
|
|
53
|
-
overflow-wrap: break-word;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
@media (prefers-reduced-motion: reduce) {
|
|
57
|
-
*,
|
|
58
|
-
*::before,
|
|
59
|
-
*::after {
|
|
60
|
-
animation-duration: 0.01ms !important;
|
|
61
|
-
animation-iteration-count: 1 !important;
|
|
62
|
-
transition-duration: 0.01ms !important;
|
|
63
|
-
scroll-behavior: auto !important;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
/* Design tokens.
|
|
2
|
-
*
|
|
3
|
-
* `:root` holds the SHARED structural scale (space / type / radius / shadow / z / motion / layout)
|
|
4
|
-
* plus the PUBLIC SSG site's colour defaults — left untouched so the generated site is unaffected.
|
|
5
|
-
*
|
|
6
|
-
* The admin shell opts into a richer, neutral "depth" palette (and dark mode) by setting
|
|
7
|
-
* `data-theme="light|dark"` on <html> (see `useTheme` + the admin layout). Because the attribute
|
|
8
|
-
* lives on <html>, teleported widgets (dialogs, comboboxes) resolve the same tokens.
|
|
9
|
-
*/
|
|
10
|
-
:root {
|
|
11
|
-
/* ---- color: public defaults (do not retune — drives the SSG site) ---- */
|
|
12
|
-
--color-bg: #ffffff;
|
|
13
|
-
--color-surface: #ffffff;
|
|
14
|
-
--color-surface-2: #f4f4f5;
|
|
15
|
-
--color-text: #1a1a1a;
|
|
16
|
-
--color-text-muted: #5c5c5c;
|
|
17
|
-
--color-text-subtle: #a1a1aa;
|
|
18
|
-
--color-border: #d4d4d8;
|
|
19
|
-
--color-border-strong: #b4b4bb;
|
|
20
|
-
--color-primary: #2563eb;
|
|
21
|
-
--color-primary-hover: #1d4ed8;
|
|
22
|
-
--color-on-primary: #ffffff;
|
|
23
|
-
--color-primary-solid: #2563eb; /* fill for solid primary controls (keeps AA with on-primary text) */
|
|
24
|
-
--color-danger: #dc2626;
|
|
25
|
-
--color-danger-solid: #dc2626; /* fill for solid danger controls (keeps AA with on-danger text) */
|
|
26
|
-
--color-on-danger: #ffffff;
|
|
27
|
-
--color-success: #16a34a;
|
|
28
|
-
--color-warning: #d97706; /* amber accent for icons/borders (non-text, ≥3:1) */
|
|
29
|
-
--color-warning-text: #b45309; /* darker amber for warning TEXT (≥4.5:1 on surface, WCAG 1.4.3) */
|
|
30
|
-
--color-highlight: #fef08a; /* <mark> background */
|
|
31
|
-
--color-on-highlight: #1a1a1a;
|
|
32
|
-
--color-focus: #2563eb;
|
|
33
|
-
--color-scrim: rgba(0, 0, 0, 0.5);
|
|
34
|
-
--color-rail-bg: #f7f7f8;
|
|
35
|
-
--color-hover: rgba(24, 24, 27, 0.06);
|
|
36
|
-
--color-primary-soft: rgba(37, 99, 235, 0.1);
|
|
37
|
-
|
|
38
|
-
/* space (rem step scale) */
|
|
39
|
-
--space-1: 0.25rem;
|
|
40
|
-
--space-2: 0.5rem;
|
|
41
|
-
--space-3: 0.75rem;
|
|
42
|
-
--space-4: 1rem;
|
|
43
|
-
--space-5: 1.5rem;
|
|
44
|
-
--space-6: 2rem;
|
|
45
|
-
--space-7: 3rem;
|
|
46
|
-
--space-8: 4rem;
|
|
47
|
-
|
|
48
|
-
/* type */
|
|
49
|
-
--font-sans: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
|
50
|
-
--text-xs: 0.75rem;
|
|
51
|
-
--text-sm: 0.875rem;
|
|
52
|
-
--text-base: 1rem;
|
|
53
|
-
--text-lg: 1.125rem;
|
|
54
|
-
--text-xl: 1.5rem;
|
|
55
|
-
--leading-normal: 1.5;
|
|
56
|
-
--weight-normal: 400;
|
|
57
|
-
--weight-medium: 500;
|
|
58
|
-
--weight-bold: 700;
|
|
59
|
-
|
|
60
|
-
/* radius */
|
|
61
|
-
--radius-sm: 4px;
|
|
62
|
-
--radius-md: 8px;
|
|
63
|
-
--radius-lg: 12px;
|
|
64
|
-
--radius-full: 9999px;
|
|
65
|
-
|
|
66
|
-
/* shadow */
|
|
67
|
-
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.06);
|
|
68
|
-
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
69
|
-
--shadow-lg: 0 12px 32px rgba(0, 0, 0, 0.16);
|
|
70
|
-
|
|
71
|
-
/* z-index */
|
|
72
|
-
--z-dropdown: 1000;
|
|
73
|
-
--z-overlay: 1100;
|
|
74
|
-
--z-dialog: 1200;
|
|
75
|
-
--z-toast: 1300;
|
|
76
|
-
|
|
77
|
-
/* motion */
|
|
78
|
-
--motion-fast: 120ms;
|
|
79
|
-
--motion-base: 200ms;
|
|
80
|
-
--ease-standard: cubic-bezier(0.2, 0, 0, 1);
|
|
81
|
-
|
|
82
|
-
/* layout */
|
|
83
|
-
--rail-width: 15rem;
|
|
84
|
-
--rail-width-collapsed: 4rem;
|
|
85
|
-
--content-measure: 72rem;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/* ===================================================================== *
|
|
89
|
-
* Admin theme — Light (neutral zinc + calm indigo, soft depth)
|
|
90
|
-
* ===================================================================== */
|
|
91
|
-
:root[data-theme="light"] {
|
|
92
|
-
color-scheme: light;
|
|
93
|
-
|
|
94
|
-
--color-bg: #fafafa; /* tinted app canvas so surfaces lift off it */
|
|
95
|
-
--color-surface: #ffffff; /* cards, panels, inputs-on-card */
|
|
96
|
-
--color-surface-2: #f4f4f5; /* sunken / secondary fills, input wells */
|
|
97
|
-
--color-text: #18181b; /* zinc-900 */
|
|
98
|
-
--color-text-muted: #71717a; /* zinc-500 */
|
|
99
|
-
--color-text-subtle: #a1a1aa; /* zinc-400 — hints, placeholders */
|
|
100
|
-
--color-border: #e4e4e7; /* zinc-200 — hairline (decorative) */
|
|
101
|
-
--color-border-strong: #d4d4d8; /* zinc-300 — dividers */
|
|
102
|
-
--color-control-border: #8e8e96; /* interactive control boundary — ~3.3:1 on surface (WCAG 1.4.11) */
|
|
103
|
-
--color-primary: #4f46e5; /* indigo-600 */
|
|
104
|
-
--color-primary-hover: #4338ca; /* indigo-700 */
|
|
105
|
-
--color-on-primary: #ffffff;
|
|
106
|
-
--color-primary-solid: #4f46e5; /* solid indigo fill (white text = 6.29:1, AA) */
|
|
107
|
-
--color-primary-soft: rgba(79, 70, 229, 0.1);
|
|
108
|
-
--color-danger: #dc2626;
|
|
109
|
-
--color-danger-solid: #dc2626;
|
|
110
|
-
--color-on-danger: #ffffff;
|
|
111
|
-
--color-success: #16a34a;
|
|
112
|
-
--color-warning: #d97706;
|
|
113
|
-
--color-warning-text: #b45309; /* amber-700 — warning text ≈4.9:1 on the light surfaces */
|
|
114
|
-
--color-highlight: #fde68a;
|
|
115
|
-
--color-on-highlight: #18181b;
|
|
116
|
-
--color-focus: #4f46e5;
|
|
117
|
-
--color-link: #1a0dab; /* SERP-style link (SEO Google preview) — high contrast on light */
|
|
118
|
-
--color-scrim: rgba(9, 9, 11, 0.45);
|
|
119
|
-
--color-rail-bg: #ffffff;
|
|
120
|
-
--color-hover: rgba(24, 24, 27, 0.05);
|
|
121
|
-
--color-active: #f1f1f4; /* selected-row / active-nav neutral fill */
|
|
122
|
-
--color-primary-on-fill: #4f46e5; /* primary-tinted text/icon on an active/raised fill, ≥4.5:1 */
|
|
123
|
-
|
|
124
|
-
--shadow-sm: 0 1px 1px rgba(16, 24, 40, 0.04);
|
|
125
|
-
--shadow-md: 0 2px 6px -2px rgba(16, 24, 40, 0.08), 0 1px 2px rgba(16, 24, 40, 0.04);
|
|
126
|
-
--shadow-lg: 0 10px 28px -10px rgba(16, 24, 40, 0.16);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/* ===================================================================== *
|
|
130
|
-
* Admin theme — Dark (zinc-950 canvas, brighter indigo, border-led depth)
|
|
131
|
-
* ===================================================================== */
|
|
132
|
-
:root[data-theme="dark"] {
|
|
133
|
-
color-scheme: dark;
|
|
134
|
-
|
|
135
|
-
--color-bg: #09090b; /* zinc-950 */
|
|
136
|
-
--color-surface: #18181b; /* zinc-900 */
|
|
137
|
-
--color-surface-2: #27272a; /* zinc-800 */
|
|
138
|
-
--color-text: #fafafa;
|
|
139
|
-
--color-text-muted: #a1a1aa; /* zinc-400 */
|
|
140
|
-
--color-text-subtle: #71717a; /* zinc-500 */
|
|
141
|
-
--color-border: #27272a; /* zinc-800 — hairline (decorative) */
|
|
142
|
-
--color-border-strong: #3f3f46; /* zinc-700 */
|
|
143
|
-
--color-control-border: #71717a; /* zinc-500 — ~3.6:1 on the dark surface (WCAG 1.4.11) */
|
|
144
|
-
--color-primary: #6366f1; /* indigo-500 — lifts on dark */
|
|
145
|
-
--color-primary-hover: #818cf8; /* indigo-400 */
|
|
146
|
-
--color-on-primary: #ffffff;
|
|
147
|
-
--color-primary-solid: #4f46e5; /* stays indigo-600 (not primary-500) so white text keeps AA (6.29:1) */
|
|
148
|
-
--color-primary-soft: rgba(99, 102, 241, 0.16);
|
|
149
|
-
--color-danger: #f87171;
|
|
150
|
-
--color-danger-solid: #dc2626; /* deep enough for white text on the dark canvas */
|
|
151
|
-
--color-on-danger: #ffffff;
|
|
152
|
-
--color-success: #4ade80;
|
|
153
|
-
--color-warning: #fbbf24;
|
|
154
|
-
--color-warning-text: #fbbf24; /* on the dark canvas the bright amber already clears 4.5:1 */
|
|
155
|
-
--color-highlight: #854d0e;
|
|
156
|
-
--color-on-highlight: #fef3c7;
|
|
157
|
-
--color-focus: #818cf8;
|
|
158
|
-
--color-link: #8ab4f8; /* SERP-style link, dark mode — ~8:1 on the dark surface (WCAG AA) */
|
|
159
|
-
--color-scrim: rgba(0, 0, 0, 0.6);
|
|
160
|
-
--color-rail-bg: #111113;
|
|
161
|
-
--color-hover: rgba(255, 255, 255, 0.06);
|
|
162
|
-
|
|
163
|
-
--shadow-sm: 0 1px 1px rgba(0, 0, 0, 0.4);
|
|
164
|
-
--shadow-md: 0 2px 8px -2px rgba(0, 0, 0, 0.5), 0 1px 2px rgba(0, 0, 0, 0.4);
|
|
165
|
-
--shadow-lg: 0 10px 28px -8px rgba(0, 0, 0, 0.6);
|
|
166
|
-
|
|
167
|
-
/* active nav / selected-row fill reads against the darker rail */
|
|
168
|
-
--color-active: #232327;
|
|
169
|
-
--color-primary-on-fill: #818cf8; /* indigo-400 — 5.25:1 on --color-active (--color-primary is only ~3.5:1) */
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/* ===================================================================== *
|
|
173
|
-
* Admin shell — structural refinements (border-led depth, tighter radii)
|
|
174
|
-
*
|
|
175
|
-
* Scoped to the themed admin shell (any `data-theme`) so the public SSG
|
|
176
|
-
* site keeps the looser `:root` radii. The admin leans on hairline borders
|
|
177
|
-
* rather than soft shadows; the tighter radii read as a tool, not a card UI.
|
|
178
|
-
* ===================================================================== */
|
|
179
|
-
:root[data-theme] {
|
|
180
|
-
--radius-sm: 4px;
|
|
181
|
-
--radius-md: 6px;
|
|
182
|
-
--radius-lg: 8px;
|
|
183
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import { computed } from 'vue'
|
|
3
|
-
import UiFieldset from '../ui/Fieldset.vue'
|
|
4
|
-
import UiButtonGroup from '../ui/ButtonGroup.vue'
|
|
5
|
-
import { nextBooleanValue } from '../../utils/boolean-field'
|
|
6
|
-
import type { FieldComponentProps } from '../../utils/field-component'
|
|
7
|
-
|
|
8
|
-
const props = defineProps<FieldComponentProps>()
|
|
9
|
-
const model = defineModel<boolean>()
|
|
10
|
-
|
|
11
|
-
const { t } = useT()
|
|
12
|
-
const boolOptions = computed(() => [
|
|
13
|
-
{ label: t('field.boolean.no'), value: 'false' },
|
|
14
|
-
{ label: t('field.boolean.yes'), value: 'true' },
|
|
15
|
-
])
|
|
16
|
-
|
|
17
|
-
const proxy = computed<string | string[] | null>({
|
|
18
|
-
get: () => (model.value === true ? 'true' : model.value === false ? 'false' : null),
|
|
19
|
-
set: (v) => { model.value = nextBooleanValue(model.value, v) },
|
|
20
|
-
})
|
|
21
|
-
const required = computed(() => !!props.field.required)
|
|
22
|
-
</script>
|
|
23
|
-
|
|
24
|
-
<template>
|
|
25
|
-
<UiFieldset :id="id" :label="name" :error="error" :required="required">
|
|
26
|
-
<template #default="f">
|
|
27
|
-
<UiButtonGroup v-model="proxy" :options="boolOptions" :disabled="disabled" v-bind="f" />
|
|
28
|
-
</template>
|
|
29
|
-
</UiFieldset>
|
|
30
|
-
</template>
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import { computed } from 'vue'
|
|
3
|
-
import UiField from '../ui/Field.vue'
|
|
4
|
-
import UiFieldset from '../ui/Fieldset.vue'
|
|
5
|
-
import UiSelect from '../ui/Select.vue'
|
|
6
|
-
import UiButtonGroup from '../ui/ButtonGroup.vue'
|
|
7
|
-
import UiCheckboxGroup from '../ui/CheckboxGroup.vue'
|
|
8
|
-
import { resolveLocalized } from '../../utils/localized'
|
|
9
|
-
import type { FieldComponentProps } from '../../utils/field-component'
|
|
10
|
-
import type { FieldOf } from '@michaelthielemann/kestrel-core'
|
|
11
|
-
|
|
12
|
-
const props = defineProps<FieldComponentProps>()
|
|
13
|
-
const model = defineModel<string | string[] | null>()
|
|
14
|
-
|
|
15
|
-
const { lang } = useT()
|
|
16
|
-
|
|
17
|
-
const cfg = computed(() => {
|
|
18
|
-
if (props.field.type !== 'choice') return { choices: [] as { label: string; value: string }[], multiple: false, buttons: false }
|
|
19
|
-
const o = (props.field as FieldOf<'choice'>).options
|
|
20
|
-
return {
|
|
21
|
-
// A choice label is `Localized` like every other author-supplied label. Resolving it HERE (not in the
|
|
22
|
-
// controls) is what keeps it out of the markup as a JSON blob — `{{ o.label }}` would stringify the map.
|
|
23
|
-
choices: o.choices.map((c) => ({ ...c, label: resolveLocalized(c.label, lang.value) ?? c.value })),
|
|
24
|
-
multiple: !!o.multiple,
|
|
25
|
-
buttons: o.display === 'buttons',
|
|
26
|
-
}
|
|
27
|
-
})
|
|
28
|
-
const required = computed(() => !!props.field.required)
|
|
29
|
-
|
|
30
|
-
// Narrowing proxies so each control gets its precise model type. For the single <select>, map null↔''
|
|
31
|
-
// (the empty option's value) so an optional choice can REPRESENT "no value" (null shows the empty option
|
|
32
|
-
// instead of defaulting to the first choice) and be CLEARED back to null by re-selecting it.
|
|
33
|
-
const single = computed<string | null>({
|
|
34
|
-
get: () => (typeof model.value === 'string' ? model.value : ''),
|
|
35
|
-
set: (v) => { model.value = v ? v : null },
|
|
36
|
-
})
|
|
37
|
-
// An optional single choice gets a leading empty option so null is selectable/clearable; a required one
|
|
38
|
-
// has none (validation enforces a pick), keeping its existing behaviour.
|
|
39
|
-
const singleOptions = computed(() =>
|
|
40
|
-
required.value ? cfg.value.choices : [{ label: '—', value: '' }, ...cfg.value.choices],
|
|
41
|
-
)
|
|
42
|
-
const multi = computed<string[]>({
|
|
43
|
-
get: () => (Array.isArray(model.value) ? model.value : []),
|
|
44
|
-
set: (v) => { model.value = v },
|
|
45
|
-
})
|
|
46
|
-
</script>
|
|
47
|
-
|
|
48
|
-
<template>
|
|
49
|
-
<UiField v-if="!cfg.multiple && !cfg.buttons" :id="id" :label="name" :error="error" :required="required">
|
|
50
|
-
<template #default="f">
|
|
51
|
-
<UiSelect v-model="single" :options="singleOptions" :disabled="disabled" v-bind="f" />
|
|
52
|
-
</template>
|
|
53
|
-
</UiField>
|
|
54
|
-
<UiFieldset v-else :id="id" :label="name" :error="error" :required="required">
|
|
55
|
-
<template #default="f">
|
|
56
|
-
<UiButtonGroup
|
|
57
|
-
v-if="cfg.buttons"
|
|
58
|
-
v-model="model"
|
|
59
|
-
:options="cfg.choices"
|
|
60
|
-
:multiple="cfg.multiple"
|
|
61
|
-
:disabled="disabled"
|
|
62
|
-
v-bind="f"
|
|
63
|
-
/>
|
|
64
|
-
<UiCheckboxGroup
|
|
65
|
-
v-else
|
|
66
|
-
v-model="multi"
|
|
67
|
-
:options="cfg.choices"
|
|
68
|
-
:disabled="disabled"
|
|
69
|
-
v-bind="f"
|
|
70
|
-
/>
|
|
71
|
-
</template>
|
|
72
|
-
</UiFieldset>
|
|
73
|
-
</template>
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import { computed } from 'vue'
|
|
3
|
-
import UiField from '../ui/Field.vue'
|
|
4
|
-
import UiFieldset from '../ui/Fieldset.vue'
|
|
5
|
-
import UiTimeInput from '../ui/TimeInput.vue'
|
|
6
|
-
import UiDatePicker from '../ui/DatePicker.vue'
|
|
7
|
-
import UiDateRangePicker from '../ui/DateRangePicker.vue'
|
|
8
|
-
import type { FieldComponentProps } from '../../utils/field-component'
|
|
9
|
-
import type { FieldOf } from '@michaelthielemann/kestrel-core'
|
|
10
|
-
|
|
11
|
-
const { t } = useT()
|
|
12
|
-
const props = defineProps<FieldComponentProps>()
|
|
13
|
-
const model = defineModel<string | { start: string; end: string } | null>()
|
|
14
|
-
|
|
15
|
-
const cfg = computed(() => {
|
|
16
|
-
if (props.field.type !== 'datetime') return { precision: 'datetime' as const, range: false }
|
|
17
|
-
const o = (props.field as FieldOf<'datetime'>).options
|
|
18
|
-
return { precision: o?.precision ?? 'datetime', range: !!o?.range }
|
|
19
|
-
})
|
|
20
|
-
const required = computed(() => !!props.field.required)
|
|
21
|
-
const isTime = computed(() => cfg.value.precision === 'time')
|
|
22
|
-
const pickerPrecision = computed<'date' | 'datetime'>(() => (cfg.value.precision === 'date' ? 'date' : 'datetime'))
|
|
23
|
-
|
|
24
|
-
const single = computed<string | null>({
|
|
25
|
-
get: () => (typeof model.value === 'string' ? model.value : null),
|
|
26
|
-
set: (v) => { model.value = v },
|
|
27
|
-
})
|
|
28
|
-
const range = computed<{ start: string; end: string } | null>({
|
|
29
|
-
get: () => (model.value && typeof model.value === 'object' ? model.value : null),
|
|
30
|
-
set: (v) => { model.value = v },
|
|
31
|
-
})
|
|
32
|
-
const rangeStart = computed<string | null>({
|
|
33
|
-
get: () => range.value?.start ?? null,
|
|
34
|
-
set: (v) => { model.value = { start: v ?? '', end: range.value?.end ?? '' } },
|
|
35
|
-
})
|
|
36
|
-
const rangeEnd = computed<string | null>({
|
|
37
|
-
get: () => range.value?.end ?? null,
|
|
38
|
-
set: (v) => { model.value = { start: range.value?.start ?? '', end: v ?? '' } },
|
|
39
|
-
})
|
|
40
|
-
</script>
|
|
41
|
-
|
|
42
|
-
<template>
|
|
43
|
-
<UiField v-if="!cfg.range" :id="id" :label="name" :error="error" :required="required">
|
|
44
|
-
<template #default="f">
|
|
45
|
-
<UiTimeInput v-if="isTime" v-model="single" :disabled="disabled" v-bind="f" />
|
|
46
|
-
<UiDatePicker
|
|
47
|
-
v-else
|
|
48
|
-
v-model="single"
|
|
49
|
-
:precision="pickerPrecision"
|
|
50
|
-
:disabled="disabled"
|
|
51
|
-
:aria-label="name"
|
|
52
|
-
:describedby="f['aria-describedby']"
|
|
53
|
-
:invalid="f['aria-invalid'] === 'true'"
|
|
54
|
-
:required="!!f.required"
|
|
55
|
-
/>
|
|
56
|
-
</template>
|
|
57
|
-
</UiField>
|
|
58
|
-
|
|
59
|
-
<UiFieldset v-else :id="id" :label="name" :error="error" :required="required">
|
|
60
|
-
<template #default="f">
|
|
61
|
-
<template v-if="isTime">
|
|
62
|
-
<!-- eslint-disable-next-line vuejs-accessibility/label-has-for -- native wrapping label around a custom UiTimeInput; no `for`/`id` pair needed, invisible to static analysis -->
|
|
63
|
-
<label class="field-datetime__sub">
|
|
64
|
-
<span class="field-datetime__sublabel">{{ t('field.datetime.range_start') }}</span>
|
|
65
|
-
<UiTimeInput v-model="rangeStart" :disabled="disabled" v-bind="f" />
|
|
66
|
-
</label>
|
|
67
|
-
<!-- eslint-disable-next-line vuejs-accessibility/label-has-for -- native wrapping label around a custom UiTimeInput; no `for`/`id` pair needed, invisible to static analysis -->
|
|
68
|
-
<label class="field-datetime__sub">
|
|
69
|
-
<span class="field-datetime__sublabel">{{ t('field.datetime.range_end') }}</span>
|
|
70
|
-
<UiTimeInput v-model="rangeEnd" :disabled="disabled" v-bind="f" />
|
|
71
|
-
</label>
|
|
72
|
-
</template>
|
|
73
|
-
<UiDateRangePicker
|
|
74
|
-
v-else
|
|
75
|
-
v-model="range"
|
|
76
|
-
:precision="pickerPrecision"
|
|
77
|
-
:disabled="disabled"
|
|
78
|
-
:aria-label="name"
|
|
79
|
-
:describedby="f['aria-describedby']"
|
|
80
|
-
:invalid="f['aria-invalid'] === 'true'"
|
|
81
|
-
:required="!!f.required"
|
|
82
|
-
/>
|
|
83
|
-
</template>
|
|
84
|
-
</UiFieldset>
|
|
85
|
-
</template>
|
|
86
|
-
|
|
87
|
-
<style lang="scss">
|
|
88
|
-
.field-datetime__sub {
|
|
89
|
-
display: flex;
|
|
90
|
-
align-items: center;
|
|
91
|
-
gap: var(--space-2);
|
|
92
|
-
}
|
|
93
|
-
.field-datetime__sublabel {
|
|
94
|
-
font-size: var(--text-sm);
|
|
95
|
-
color: var(--color-text-muted);
|
|
96
|
-
min-width: 3rem;
|
|
97
|
-
}
|
|
98
|
-
</style>
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
// The per-field stale-reference warning, rendered by the shared FieldLayout. Non-blocking note,
|
|
3
|
-
// distinct from the field's red validation error. Lives in the ui layer so the shared renderer
|
|
4
|
-
// references it without a ui→admin dependency; its path-derived auto-import name stays
|
|
5
|
-
// `FieldDeadRefNote`, so callers are unchanged.
|
|
6
|
-
defineProps<{ show: boolean }>()
|
|
7
|
-
const { t } = useT()
|
|
8
|
-
</script>
|
|
9
|
-
|
|
10
|
-
<template>
|
|
11
|
-
<p v-if="show" class="field-dead-ref" role="note">
|
|
12
|
-
<KestrelUiIcon name="triangle-alert" :size="13" class="field-dead-ref__icon" />{{ t('deadRefs.fieldNote') }}
|
|
13
|
-
</p>
|
|
14
|
-
</template>
|
|
15
|
-
|
|
16
|
-
<style lang="scss">
|
|
17
|
-
.field-dead-ref {
|
|
18
|
-
display: flex;
|
|
19
|
-
align-items: center;
|
|
20
|
-
gap: var(--space-1);
|
|
21
|
-
margin: 0;
|
|
22
|
-
font-size: var(--text-xs);
|
|
23
|
-
color: var(--color-warning-text); /* warning conveyed as TEXT → needs 4.5:1, not the 3:1 icon accent */
|
|
24
|
-
}
|
|
25
|
-
.field-dead-ref__icon {
|
|
26
|
-
flex-shrink: 0;
|
|
27
|
-
}
|
|
28
|
-
</style>
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import { computed, ref, watch } from 'vue'
|
|
3
|
-
import UiField from '../ui/Field.vue'
|
|
4
|
-
import UiTextarea from '../ui/Textarea.vue'
|
|
5
|
-
import { fieldConstraints, tryParseJson } from '@michaelthielemann/kestrel-fields/client'
|
|
6
|
-
import type { FieldComponentProps } from '../../utils/field-component'
|
|
7
|
-
|
|
8
|
-
const props = defineProps<FieldComponentProps>()
|
|
9
|
-
const model = defineModel<unknown>()
|
|
10
|
-
const c = computed(() => fieldConstraints(props.field))
|
|
11
|
-
|
|
12
|
-
const serialize = (v: unknown) => (v == null ? '' : JSON.stringify(v, null, 2))
|
|
13
|
-
const raw = ref(serialize(model.value))
|
|
14
|
-
const localError = ref<string | null>(null)
|
|
15
|
-
|
|
16
|
-
// Reseed only when the model genuinely differs from what the textarea holds, so our own valid edits
|
|
17
|
-
// never clobber the user's in-progress text. The watch only fires on a model CHANGE, and onInput never
|
|
18
|
-
// writes `model` while the text is invalid — so a fire while the textarea holds invalid JSON is always
|
|
19
|
-
// an EXTERNAL change (applyFrom / reset). That invalid text is not an edit worth protecting: take the
|
|
20
|
-
// external value and clear the stale error, rather than silently dropping the update.
|
|
21
|
-
watch(model, (v) => {
|
|
22
|
-
const current = tryParseJson(raw.value)
|
|
23
|
-
if (!current.ok) {
|
|
24
|
-
raw.value = serialize(v)
|
|
25
|
-
localError.value = null
|
|
26
|
-
return
|
|
27
|
-
}
|
|
28
|
-
if (JSON.stringify(v) !== JSON.stringify(current.value)) raw.value = serialize(v)
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
function onInput(value: string | null | undefined) {
|
|
32
|
-
raw.value = value ?? ''
|
|
33
|
-
if (raw.value.trim() === '') {
|
|
34
|
-
localError.value = null
|
|
35
|
-
model.value = null
|
|
36
|
-
return
|
|
37
|
-
}
|
|
38
|
-
const parsed = tryParseJson(raw.value)
|
|
39
|
-
if (parsed.ok) {
|
|
40
|
-
localError.value = null
|
|
41
|
-
model.value = parsed.value
|
|
42
|
-
} else {
|
|
43
|
-
localError.value = 'Invalid JSON.'
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const shownError = computed(() => localError.value ?? props.error ?? null)
|
|
48
|
-
</script>
|
|
49
|
-
|
|
50
|
-
<template>
|
|
51
|
-
<UiField :id="id" :label="name" :error="shownError" :required="c.required">
|
|
52
|
-
<template #default="f">
|
|
53
|
-
<UiTextarea
|
|
54
|
-
:model-value="raw"
|
|
55
|
-
:rows="6"
|
|
56
|
-
:disabled="disabled"
|
|
57
|
-
v-bind="f"
|
|
58
|
-
@update:model-value="onInput"
|
|
59
|
-
/>
|
|
60
|
-
</template>
|
|
61
|
-
</UiField>
|
|
62
|
-
</template>
|