@webx-ui/module-banners 0.1.10 → 0.1.12

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/dist/index.js CHANGED
@@ -32,6 +32,7 @@ const aa = {
32
32
  places: {
33
33
  hero: "Home page slider",
34
34
  promo: "Promo strip",
35
+ notice: "Announcement bar",
35
36
  list: "Places",
36
37
  new: "New place",
37
38
  title: "Name",
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/api.ts","../src/messages.ts","../src/i18n.ts","../src/BannerEditorPage.vue","../src/PlaceDialog.vue","../src/PlacePane.vue","../src/BannersPage.vue","../src/module.ts"],"sourcesContent":["import type { AdminContext } from '@webx-ui/module-admin'\nimport type { ScreenModel } from '@webx-ui/schema'\nimport type { BannerDetail, BannerRow, PlaceInput, PlaceRow } from './types'\n\nexport interface BannersApi {\n /** Every place: the declared ones in the config's order, then the own ones by name. */\n places(): Promise<PlaceRow[]>\n createPlace(input: PlaceInput): Promise<PlaceRow>\n /** Only a place of somebody's own, and only its name: the key is what a template asks for. */\n renamePlace(key: string, title: PlaceInput['title']): Promise<PlaceRow>\n /** Only an own place with nothing in it, the bin included — a 422 with the count otherwise. */\n removePlace(key: string): Promise<void>\n\n /** The banners of a place in their order, or its bin. A declared place without a row: `[]`. */\n list(place: string, trashed?: boolean): Promise<BannerRow[]>\n get(id: number): Promise<BannerDetail>\n /**\n * A new banner out of the values of its form, in one request: the form is filled in first and\n * the record made on the first save, so nobody leaves an empty banner in a place.\n */\n create(place: string, values: ScreenModel): Promise<BannerDetail>\n /**\n * Refused with a 422 under the name of the field — `buttons.<n>.link` for a button's row. A\n * `place` other than the one it stands in moves it to the end of that one (decision 14).\n */\n save(id: number, values: ScreenModel, place?: string): Promise<BannerDetail>\n remove(id: number): Promise<void>\n restore(id: number): Promise<BannerRow>\n /** The order of one place, written whole. */\n reorder(place: string, ids: number[]): Promise<void>\n}\n\n/** Everything under `/banners`, below the panel's API path (§5.6). */\nexport function createBannersApi(admin: AdminContext): BannersApi {\n const base = `${admin.apiPath}/banners`\n const place = (key: string) => `${base}/places/${encodeURIComponent(key)}`\n const data = <T>(body: { data: T }): T => body.data\n\n return {\n places: () => admin.http.get<{ data: PlaceRow[] }>(`${base}/places`).then(data),\n createPlace: (input) => admin.http.post<{ data: PlaceRow }>(`${base}/places`, input).then(data),\n renamePlace: (key, title) =>\n admin.http.put<{ data: PlaceRow }>(place(key), { title }).then(data),\n removePlace: (key) => admin.http.delete<void>(place(key)).then(() => undefined),\n\n list: (key, trashed = false) =>\n admin.http\n .get<{ data: BannerRow[] }>(`${place(key)}/banners${trashed ? '?trashed=1' : ''}`)\n .then(data),\n get: (id) => admin.http.get<{ data: BannerDetail }>(`${base}/${id}`).then(data),\n create: (key, values) =>\n admin.http.post<{ data: BannerDetail }>(`${place(key)}/banners`, { values }).then(data),\n save: (id, values, key) =>\n admin.http\n .put<{ data: BannerDetail }>(\n `${base}/${id}`,\n key === undefined ? { values } : { values, place: key },\n )\n .then(data),\n remove: (id) => admin.http.delete<void>(`${base}/${id}`).then(() => undefined),\n // The resource of the list line, and not the form's pair: a restored banner stays closed.\n restore: (id) => admin.http.post<{ data: BannerRow }>(`${base}/${id}/restore`, {}).then(data),\n reorder: (key, ids) =>\n admin.http.post<void>(`${place(key)}/reorder`, { ids }).then(() => undefined),\n }\n}\n","import type { Messages } from '@webx-ui/module-admin'\n\n/**\n * The English this package speaks on its own, before the server's dictionary arrives, or without\n * one. The server ships the same lines in ten languages under `webx-banners::` and overrides\n * these; `messages.test.ts` keeps the two sets of keys equal. The server's refusals (`errors`)\n * are not here: the panel only ever shows them as they come.\n */\nexport const bannersMessages: Record<string, Messages> = {\n module: {\n banners: 'Banners',\n },\n places: {\n hero: 'Home page slider',\n promo: 'Promo strip',\n list: 'Places',\n new: 'New place',\n title: 'Name',\n key: 'Key',\n 'key-help':\n 'What the site’s template asks for: lowercase Latin letters, digits and dashes, starting with a letter. It cannot be changed later.',\n declared: 'Declared in the configuration',\n 'in-template': \"In a template: banners(':key')\",\n count: 'Banners',\n rename: 'Rename',\n delete: 'Delete',\n 'delete-confirm': 'Delete the place “:title”?',\n 'delete-not-empty': 'Only an empty place can be deleted — the bin counts too.',\n empty: 'No places yet',\n choose: 'Choose a place on the left',\n create: 'Create',\n save: 'Save',\n cancel: 'Cancel',\n created: 'Place created',\n renamed: 'Place renamed',\n deleted: 'Place deleted',\n places: 'Places',\n 'new-title': 'New place',\n 'rename-title': 'Rename the place',\n 'delete-title': 'Delete the place “:place”?',\n 'delete-text':\n 'There is nothing in it. A template that still asks for it gets an empty list, not an error.',\n 'delete-empty-first': 'Delete: empty it first',\n 'delete-blocked': 'Banners in this place, the bin included: :count. Take them out first.',\n 'field-title': 'Name',\n 'field-key': 'Key',\n 'declared-help': 'Cannot be renamed or deleted here: a template asks for it by its key.',\n usage: 'In a template:',\n 'layout-single': 'One banner',\n 'layout-random': 'A random one',\n 'layout-slider': 'Slider',\n 'empty-help': 'The site’s config declares the places its templates ask for.',\n 'choose-help': 'Its banners open beside the list.',\n },\n banner: {\n new: 'New banner',\n trash: 'Bin',\n 'to-trash': 'Move to the bin',\n restore: 'Restore',\n open: 'Open',\n enable: 'Turn on',\n disable: 'Turn off',\n disabled: 'Off',\n video: 'Video',\n empty: 'No banners in this place yet',\n 'trash-empty': 'The bin is empty',\n place: 'Place',\n 'place-help': 'Moving the banner puts it at the end of the new place.',\n back: 'Back to the place',\n save: 'Save',\n saved: 'Banner saved',\n trashed: 'Bin',\n restored: 'Banner restored',\n 'leave-title': 'Leave without saving?',\n 'leave-text': 'The changes to this banner will be lost.',\n leave: 'Leave',\n stay: 'Stay',\n reorder: 'Reorder',\n untitled: 'Untitled banner',\n all: 'Banners',\n 'empty-help': 'The site shows them wherever a template asks for this place.',\n 'empty-bin': 'The bin is empty.',\n 'no-picture': 'No picture',\n order: 'Drag to change the order on the site.',\n 'switched-on': 'Switched on.',\n 'switched-off': 'Switched off.',\n delete: 'Delete',\n 'delete-title': 'Delete “:name”?',\n 'delete-text': 'It goes to the bin and leaves the site. Restored, it comes back to its place.',\n deleted: 'Moved to the bin.',\n 'reorder-failed': 'The new order was not saved.',\n 'save-failed': 'Not saved.',\n cancel: 'Cancel',\n },\n screen: {\n media: 'Pictures and video',\n image: 'Picture',\n 'image-help': 'Required. Shown on wide screens, and as the poster of the video.',\n 'image-mobile': 'Picture for phones',\n 'image-mobile-help': 'Optional. Narrow screens show it instead of the picture above.',\n video: 'Video',\n 'video-help':\n 'Optional. Plays silently over the picture; the picture becomes its poster and what is seen without it.',\n words: 'Words',\n title: 'Title',\n 'title-help':\n 'A banner with words is shown only in the languages they are written in; one without words is shown everywhere.',\n text: 'Text',\n 'text-help': 'Plain text; line breaks are kept.',\n buttons: 'Buttons',\n 'buttons-help': 'Up to three. A button without a label in a language is not shown in it.',\n 'buttons-add': 'Add a button',\n 'buttons-empty': 'No buttons',\n label: 'Label',\n link: 'Link',\n variant: 'Look',\n 'variant-placeholder': 'The first one',\n settings: 'Settings',\n enabled: 'On',\n 'enabled-help': 'Only a banner that is on is shown on the site. A new one starts off.',\n },\n}\n","import { useAdmin } from '@webx-ui/module-admin'\nimport { bannersMessages } from './messages'\n\nconst seeded = new WeakSet<object>()\n\n/** Puts this package's English under the panel's dictionary, once per panel. */\nexport function useBannersMessages(): void {\n try {\n const { i18n } = useAdmin()\n\n if (!seeded.has(i18n)) {\n i18n.defaults('webx-banners', bannersMessages)\n seeded.add(i18n)\n }\n } catch {\n // Outside a panel there is no dictionary to seed, and `useTranslate` falls back on its own.\n }\n}\n","<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'\nimport { onBeforeRouteLeave, useRoute, useRouter } from 'vue-router'\nimport {\n confirm,\n localizedValue,\n toast,\n useLocales,\n WxActionBar,\n WxBadge,\n WxButton,\n WxCard,\n WxFormItem,\n WxSelect,\n WxSkeleton,\n type LocalizedValue,\n} from '@webx-ui/core'\nimport {\n useAdmin,\n useErrorText,\n useTranslate,\n WxSaveState,\n WxScreen,\n WxScreenHead,\n type ScreenAction,\n useBodyKeys,\n} from '@webx-ui/module-admin'\nimport type { ScreenModel } from '@webx-ui/schema'\nimport { createBannersApi } from './api'\nimport { useBannersMessages } from './i18n'\nimport type { BannerDetail, BannerSummary, PlaceRow } from './types'\n\n/**\n * One banner, on a page of its own: a head, the place, the described screen, and one button.\n *\n * A page and not a pane beside the list, because the form is long — three pictures, the words,\n * up to three buttons — and `banners.form` is a described screen a project adds its own fields\n * to (decision 14). One explicit save, no autosave: a banner has no draft (decision 13), so what\n * is saved is on the site at once, if it is switched on.\n *\n * The place is not a field of the screen (decision 9). The list of places is alive — own places\n * come and go — and the options of a described select are patched on when the server boots, so a\n * live list has nowhere to go there. It is a select of its own above the screen, and rides beside\n * the values: `PUT { values, place }`. Changing it marks the form changed like any field does.\n *\n * A new banner is a form first and a record on the first save: pressing \"New banner\" and walking\n * away should not leave an empty, switched-off row at the end of a place.\n */\ndefineOptions({ name: 'WxBannerEditorPage' })\n\nconst props = withDefaults(defineProps<{ base?: string }>(), { base: '/banners' })\n\nconst admin = useAdmin()\nconst api = createBannersApi(admin)\nconst route = useRoute()\nconst router = useRouter()\nconst locales = useLocales()\nuseBannersMessages()\n\nconst t = useTranslate('webx-banners')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\n/** `null` — a banner not written yet (`/banners/new`). */\nconst id = computed<number | null>(() => {\n const raw = route.params.id\n\n return typeof raw === 'string' && raw !== '' ? Number(raw) : null\n})\n\nconst banner = ref<BannerSummary | null>(null)\nconst values = ref<ScreenModel>({})\nconst snapshot = ref('')\nconst place = ref<string | null>(null)\n/** The place the banner stands in on the server — what \"changed\" and \"back\" are measured by. */\nconst placeSaved = ref<string | null>(null)\nconst places = ref<PlaceRow[]>([])\n\nconst loading = ref(true)\nconst saving = ref(false)\nconst errors = ref<Record<string, string[]>>({})\n\nconst canManage = computed(() => admin.can('banners.manage'))\n\nconst current = computed(() => JSON.stringify(values.value))\nconst dirty = computed(\n () =>\n snapshot.value !== '' && (current.value !== snapshot.value || place.value !== placeSaved.value),\n)\n\nconst state = computed<'saving' | 'unsaved' | 'saved'>(() => {\n if (saving.value) return 'saving'\n\n return dirty.value || id.value === null ? 'unsaved' : 'saved'\n})\n\n/** The title follows the field rather than the answer: an edit shows at the top as it is typed. */\nconst title = computed(() => {\n const written = values.value.title as LocalizedValue | string | null | undefined\n const fallback = id.value === null ? t('banner.new') : (banner.value?.title ?? '')\n\n return localizedValue(written, locales.active.value, fallback) || t('banner.untitled')\n})\n\n/** Back to the place the banner stands in, and not to the section's first one. */\nconst back = computed(() => {\n const key = placeSaved.value ?? place.value\n\n return key === null ? props.base : `${props.base}?place=${encodeURIComponent(key)}`\n})\n\nconst section = computed(\n () =>\n admin.state.manifest?.modules.find((module) => module.id === 'banners')?.title ??\n t('module.banners'),\n)\n\nconst placeOptions = computed(() =>\n places.value.map((one) => ({ value: one.key, label: `${one.title} · ${one.key}` })),\n)\n\nconst placeModel = computed<string | number | null>({\n get: () => place.value,\n set: (value) => {\n place.value = value === null || value === undefined ? null : String(value)\n },\n})\n\n/** What the server would answer for a banner with nothing in it yet (§5.6). */\nfunction blank(): ScreenModel {\n return {\n image: null,\n image_mobile: null,\n video: null,\n title: {},\n text: {},\n buttons: [],\n enabled: false,\n }\n}\n\nfunction take(detail: BannerDetail): void {\n banner.value = detail.banner\n values.value = detail.values\n snapshot.value = JSON.stringify(detail.values)\n place.value = detail.banner.place\n placeSaved.value = detail.banner.place\n}\n\nasync function load(): Promise<void> {\n loading.value = true\n\n try {\n const [list, detail] = await Promise.all([\n api.places(),\n id.value === null ? Promise.resolve(null) : api.get(id.value),\n ])\n\n places.value = list\n\n if (detail !== null) {\n take(detail)\n } else {\n const asked = route.query.place\n const key = typeof asked === 'string' && list.some((one) => one.key === asked) ? asked : null\n const fresh = blank()\n\n banner.value = null\n values.value = fresh\n snapshot.value = JSON.stringify(fresh)\n place.value = key ?? list[0]?.key ?? null\n placeSaved.value = place.value\n }\n } catch (error) {\n toast.danger(message(error))\n void router.push(props.base)\n } finally {\n loading.value = false\n }\n}\n\nasync function save(): Promise<void> {\n if (saving.value || !canManage.value || place.value === null) return\n\n saving.value = true\n errors.value = {}\n\n try {\n const made = id.value === null\n const detail = made\n ? await api.create(place.value, values.value)\n : await api.save(\n id.value as number,\n values.value,\n place.value !== placeSaved.value ? place.value : undefined,\n )\n\n take(detail)\n toast.success(t('banner.saved'))\n\n // A record now, with an address of its own; `replace`, so \"back\" does not reopen a blank form.\n if (made) void router.replace(`${props.base}/${detail.banner.id}`)\n } catch (error) {\n const body = (error as { body?: { errors?: Record<string, string[]> } }).body\n\n if (body?.errors) {\n errors.value = body.errors\n toast.danger(t('banner.save-failed'))\n } else {\n toast.danger(message(error))\n }\n } finally {\n saving.value = false\n }\n}\n\nasync function remove(): Promise<void> {\n const record = banner.value\n\n if (!record) return\n\n const agreed = await confirm({\n title: t('banner.delete-title', { name: record.title }),\n message: t('banner.delete-text'),\n confirmText: t('banner.delete'),\n cancelText: t('banner.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n try {\n await api.remove(record.id)\n toast.success(t('banner.deleted'))\n // Nothing left to protect: the record is in the bin, and asking about its unsaved edits on\n // the way out would be asking about a banner that is gone.\n snapshot.value = current.value\n place.value = placeSaved.value\n void router.push(back.value)\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\n/* The browser's own guard: it cannot wait for a request, so all it can do is ask. */\nfunction leaveGuard(event: BeforeUnloadEvent): void {\n if (dirty.value) event.preventDefault()\n}\n\n/* Ctrl+S saves, as it does everywhere else that has a save button. */\nfunction onKeydown(event: KeyboardEvent): void {\n if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 's') {\n event.preventDefault()\n if (dirty.value || id.value === null) void save()\n }\n}\n\n// And from the page around the form, where a click on empty space leaves the focus.\nuseBodyKeys(onKeydown)\n\n/*\n * `/banners/new` and `/banners/5` are one component on two routes, and the router patches it\n * rather than mounting it again (CLAUDE.md §4) — so a change of banner is watched for. The first\n * save moving the address to the record it just made is not a change: that banner is on screen.\n */\nwatch(id, (next) => {\n if (next !== null && banner.value?.id === next) return\n\n void load()\n})\n\nonMounted(() => {\n void load()\n window.addEventListener('beforeunload', leaveGuard)\n})\n\nonBeforeUnmount(() => {\n window.removeEventListener('beforeunload', leaveGuard)\n})\n\nonBeforeRouteLeave(async () => {\n if (!dirty.value || !canManage.value) return true\n\n return await confirm({\n title: t('banner.leave-title'),\n message: t('banner.leave-text'),\n confirmText: t('banner.leave'),\n cancelText: t('banner.cancel'),\n tone: 'danger',\n })\n})\n\n/* What leads away from the banner. On a phone the head folds it into the ···. */\nconst actions = computed<ScreenAction[]>(() =>\n canManage.value && banner.value !== null\n ? [\n {\n key: 'delete',\n label: t('banner.delete'),\n icon: 'trash',\n danger: true,\n menu: true,\n run: () => void remove(),\n },\n ]\n : [],\n)\n</script>\n\n<template>\n <div class=\"wx-banner-editor\" @keydown=\"onKeydown\">\n <template v-if=\"loading\">\n <!-- Shaped like the screen it stands in for: the head, and a card for what is coming. -->\n <wx-skeleton class=\"wx-banner-editor__ghost\" title :rows=\"1\" />\n <wx-card><wx-skeleton :rows=\"6\" /></wx-card>\n </template>\n\n <template v-else>\n <wx-screen-head divider :back=\"back\" :back-label=\"section\" :title=\"title\" :actions=\"actions\">\n <template v-if=\"banner && !banner.enabled\" #title-after>\n <wx-badge dot>{{ t('banner.disabled') }}</wx-badge>\n </template>\n </wx-screen-head>\n\n <wx-card class=\"wx-banner-editor__place\">\n <wx-form-item\n :label=\"t('banner.place')\"\n :help=\"id === null ? undefined : t('banner.place-help')\"\n :error=\"errors.place?.[0]\"\n required\n >\n <wx-select\n v-model=\"placeModel\"\n :options=\"placeOptions\"\n :disabled=\"!canManage || saving\"\n :aria-label=\"t('banner.place')\"\n />\n </wx-form-item>\n </wx-card>\n\n <wx-screen\n v-model=\"values\"\n name=\"banners.form\"\n :errors=\"errors\"\n :disabled=\"!canManage || saving\"\n />\n\n <wx-action-bar v-if=\"canManage\">\n <template #state>\n <wx-save-state :state=\"state\" />\n </template>\n\n <wx-button\n type=\"primary\"\n :loading=\"saving\"\n :disabled=\"(!dirty && id !== null) || place === null\"\n @click=\"save\"\n >\n {{ t('banner.save') }}\n </wx-button>\n </wx-action-bar>\n </template>\n </div>\n</template>\n\n<style scoped>\n.wx-banner-editor {\n display: flex;\n flex-direction: column;\n gap: var(--wx-gap, var(--wx-space-16));\n container-type: inline-size;\n}\n\n.wx-banner-editor__ghost {\n max-width: 420px;\n}\n\n/* A select as wide as a place's name needs, not as the page: it is one choice, not a text. */\n.wx-banner-editor__place :deep(.wx-form-item) {\n max-width: 420px;\n margin-bottom: 0;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { nextTick, onMounted, ref, useTemplateRef } from 'vue'\nimport {\n useLocales,\n useModal,\n WxButton,\n WxDialog,\n WxFormItem,\n WxInput,\n WxSpace,\n type LocalizedValue,\n} from '@webx-ui/core'\nimport { useAdmin, useTranslate } from '@webx-ui/module-admin'\nimport { createBannersApi } from './api'\nimport { useBannersMessages } from './i18n'\nimport type { PlaceRow } from './types'\n\n/**\n * A place of somebody's own: a name, and a key when it is new (§5.4).\n *\n * The key does not follow the name the way a page's address follows its title: it is typed into\n * `banners('…')` by hand, and a key that quietly became `promo-2` would be a template asking for\n * a place that is not there. For the same reason a rename never touches it.\n *\n * The name is translated, and required in the default language — which is what the list falls\n * back on in a panel open in a language nobody named the place in.\n */\ndefineOptions({ name: 'WxBannerPlaceDialog' })\n\nconst props = withDefaults(defineProps<{ place?: PlaceRow | null }>(), { place: null })\n\nconst { open, resolve, dismiss } = useModal<PlaceRow>()\n\nconst api = createBannersApi(useAdmin())\nconst locales = useLocales()\nuseBannersMessages()\n\nconst t = useTranslate('webx-banners')\n\nconst editing = props.place !== null\n\n/*\n * Every language when the server sent them (`titles`), the one the list shows otherwise — filed\n * under the default language, which is where a place with one name has it.\n */\nfunction initial(): LocalizedValue {\n if (props.place === null) return {}\n if (props.place.titles) return { ...props.place.titles }\n\n const first = locales.list.value[0]?.code\n\n return first === undefined ? {} : { [first]: props.place.title }\n}\n\nconst key = ref(props.place?.key ?? '')\nconst title = ref<LocalizedValue>(initial())\nconst saving = ref(false)\nconst errors = ref<Record<string, string[]>>({})\nconst field = useTemplateRef<HTMLElement>('field')\n\nonMounted(async () => {\n await nextTick()\n field.value?.querySelector('input')?.focus()\n})\n\n/** Something written in at least one language: the server says which one it needs. */\nfunction written(): boolean {\n return Object.values(title.value).some((words) => words.trim() !== '')\n}\n\n/** The first line of a refusal under a field that may be named by language: `title.en`. */\nfunction errorOf(name: string): string | undefined {\n const found = Object.keys(errors.value).find((one) => one === name || one.startsWith(`${name}.`))\n\n return found === undefined ? undefined : errors.value[found]?.[0]\n}\n\nasync function submit(): Promise<void> {\n if (!written() || saving.value) return\n\n saving.value = true\n errors.value = {}\n\n try {\n resolve(\n editing && props.place !== null\n ? await api.renamePlace(props.place.key, title.value)\n : await api.createPlace({ key: key.value.trim(), title: title.value }),\n )\n } catch (error) {\n // Refused under its own field: a key that is taken or spelled in a way no template could\n // write, a name missing in the default language.\n errors.value = (error as { body?: { errors?: Record<string, string[]> } }).body?.errors ?? {}\n } finally {\n saving.value = false\n }\n}\n</script>\n\n<template>\n <wx-dialog\n v-model:open=\"open\"\n :title=\"editing ? t('places.rename-title') : t('places.new-title')\"\n :width=\"440\"\n >\n <wx-form-item :label=\"t('places.field-title')\" :error=\"errorOf('title')\" required>\n <div ref=\"field\">\n <wx-input\n v-model=\"title\"\n localized\n :aria-label=\"t('places.field-title')\"\n @keyup.enter=\"submit\"\n />\n </div>\n </wx-form-item>\n\n <wx-form-item\n :label=\"t('places.field-key')\"\n :error=\"errorOf('key')\"\n :help=\"editing ? undefined : t('places.key-help')\"\n required\n >\n <wx-input\n v-model=\"key\"\n :disabled=\"editing\"\n :aria-label=\"t('places.field-key')\"\n autocomplete=\"off\"\n @keyup.enter=\"submit\"\n />\n </wx-form-item>\n\n <template #footer>\n <wx-space size=\"sm\">\n <wx-button variant=\"outline\" @click=\"dismiss()\">{{ t('places.cancel') }}</wx-button>\n <wx-button\n type=\"primary\"\n :loading=\"saving\"\n :disabled=\"!written() || key.trim() === ''\"\n @click=\"submit\"\n >\n {{ editing ? t('places.save') : t('places.create') }}\n </wx-button>\n </wx-space>\n </template>\n </wx-dialog>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\nimport { useRoute, useRouter } from 'vue-router'\nimport {\n confirm,\n toast,\n WxAlert,\n WxBadge,\n WxEmpty,\n WxIcon,\n WxSegmented,\n WxSkeleton,\n WxSortableList,\n WxTooltip,\n type SegmentedValue,\n} from '@webx-ui/core'\nimport {\n useAdmin,\n useErrorText,\n useTranslate,\n WxRowMenu,\n WxScreenHead,\n type RowAction,\n type ScreenAction,\n} from '@webx-ui/module-admin'\nimport { createBannersApi } from './api'\nimport { useBannersMessages } from './i18n'\nimport type { BannerRow, PlaceRow } from './types'\n\n/**\n * The banners of one place: the right-hand half of the section, and the whole screen on a phone.\n *\n * The order here is the order on the site, and a place is small, so the list is the whole place\n * and a drag writes it whole (`reorder`). The bin is the same list turned over: nothing to drag,\n * and a banner in it opens nothing — it can only come back.\n *\n * The way back is drawn here and not by the pane around it: the drawer a narrow screen opens this\n * in has no close of its own on purpose (CLAUDE.md §4).\n */\ndefineOptions({ name: 'WxBannerPlacePane' })\n\nconst props = withDefaults(\n defineProps<{\n place: PlaceRow\n base: string\n /** False in the drawer a narrow screen opens this in. */\n inline?: boolean\n }>(),\n { inline: true },\n)\n\nconst emit = defineEmits<{\n back: []\n /** Something moved in or out of the place: the count beside its name is stale. */\n changed: []\n}>()\n\nconst admin = useAdmin()\nconst api = createBannersApi(admin)\nconst route = useRoute()\nconst router = useRouter()\nuseBannersMessages()\n\nconst t = useTranslate('webx-banners')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\nconst rows = ref<BannerRow[]>([])\nconst loading = ref(true)\n\nconst canManage = computed(() => admin.can('banners.manage'))\n\n/* The bin is in the address with the place, so coming back from a banner lands where one was. */\nconst view = computed<SegmentedValue>({\n get: () => (route.query.view === 'trashed' ? 'trashed' : 'live'),\n set: (value) => {\n void router.replace({\n query: { ...route.query, view: value === 'trashed' ? 'trashed' : undefined },\n })\n },\n})\n\nconst inBin = computed(() => view.value === 'trashed')\n\nconst views = computed(() => [\n { value: 'live', label: t('banner.all') },\n { value: 'trashed', label: t('banner.trashed'), icon: 'trash' as const },\n])\n\nconst sortable = computed(() => canManage.value && !inBin.value && rows.value.length > 1)\n\nconst layout = computed(() => {\n const known = ['single', 'random', 'slider']\n\n return known.includes(props.place.layout) ? t(`places.layout-${props.place.layout}`) : ''\n})\n\nasync function load(): Promise<void> {\n loading.value = true\n\n try {\n rows.value = await api.list(props.place.key, inBin.value)\n } catch (error) {\n toast.danger(message(error))\n } finally {\n loading.value = false\n }\n}\n\nwatch(inBin, () => void load())\nvoid load()\n\nfunction edit(row: BannerRow): string {\n return `${props.base}/${row.id}`\n}\n\nfunction add(): void {\n void router.push({ path: `${props.base}/new`, query: { place: props.place.key } })\n}\n\n/**\n * The order on screen, written whole. The list is already in its new order (`WxSortableList`\n * reorders the model before it says so), so a failure has to put it back rather than leave the\n * screen disagreeing with the database.\n */\nasync function reorder(): Promise<void> {\n try {\n await api.reorder(\n props.place.key,\n rows.value.map((row) => row.id),\n )\n } catch (error) {\n toast.danger(message(error, t('banner.reorder-failed')))\n await load()\n }\n}\n\n/*\n * On and off from the list, through the form's own door: there is no switch of its own in the\n * API, and a second way to write a banner would be a second set of rules to keep in step. What\n * the form would refuse — a look the config dropped since — is refused here the same way.\n */\nasync function toggle(row: BannerRow): Promise<void> {\n try {\n const detail = await api.get(row.id)\n\n await api.save(row.id, { ...detail.values, enabled: !row.enabled })\n toast.success(row.enabled ? t('banner.switched-off') : t('banner.switched-on'))\n } catch (error) {\n toast.danger(message(error))\n } finally {\n await load()\n }\n}\n\nasync function remove(row: BannerRow): Promise<void> {\n const agreed = await confirm({\n title: t('banner.delete-title', { name: row.title }),\n message: t('banner.delete-text'),\n confirmText: t('banner.delete'),\n cancelText: t('banner.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n try {\n await api.remove(row.id)\n toast.success(t('banner.deleted'))\n emit('changed')\n } catch (error) {\n toast.danger(message(error))\n } finally {\n await load()\n }\n}\n\nasync function restore(row: BannerRow): Promise<void> {\n try {\n await api.restore(row.id)\n toast.success(t('banner.restored'))\n emit('changed')\n } catch (error) {\n toast.danger(message(error))\n } finally {\n await load()\n }\n}\n\nfunction actionsFor(row: BannerRow): RowAction[] {\n if (inBin.value) {\n return canManage.value\n ? [\n {\n key: 'restore',\n icon: 'refresh',\n label: t('banner.restore'),\n run: () => void restore(row),\n },\n ]\n : []\n }\n\n const open: RowAction = {\n key: 'open',\n icon: 'edit',\n label: t('banner.open'),\n run: () => void router.push(edit(row)),\n }\n\n if (!canManage.value) return [open]\n\n return [\n open,\n {\n key: 'toggle',\n icon: row.enabled ? 'eye-off' : 'eye',\n label: row.enabled ? t('banner.disable') : t('banner.enable'),\n run: () => void toggle(row),\n },\n {\n key: 'delete',\n icon: 'trash',\n label: t('banner.delete'),\n danger: true,\n run: () => void remove(row),\n },\n ]\n}\n\n/* The one thing this pane exists for, in its head; on a narrow pane it stays a button. */\nconst actions = computed<ScreenAction[]>(() =>\n canManage.value && !inBin.value\n ? [{ key: 'new', label: t('banner.new'), icon: 'plus', primary: true, run: add }]\n : [],\n)\n</script>\n\n<template>\n <div class=\"wx-banner-place\">\n <wx-screen-head\n :level=\"3\"\n :back=\"!props.inline\"\n :back-label=\"t('places.places')\"\n :title=\"place.title\"\n :actions=\"actions\"\n :collapse-below=\"420\"\n @back=\"emit('back')\"\n >\n <template v-if=\"place.declared\" #title-after>\n <wx-tooltip :content=\"t('places.declared-help')\">\n <span class=\"wx-banner-place__lock\" tabindex=\"0\" :aria-label=\"t('places.declared')\">\n <wx-icon name=\"lock\" size=\"sm\" />\n </span>\n </wx-tooltip>\n </template>\n </wx-screen-head>\n\n <!--\n How a template asks for this place, where somebody writing one will look for it: the key\n is the whole contract between the panel and the site (decision 7).\n -->\n <p class=\"wx-banner-place__usage\">\n <span>{{ t('places.usage') }}</span>\n <code>banners('{{ place.key }}')</code>\n <span v-if=\"layout\" class=\"wx-banner-place__layout\">· {{ layout }}</span>\n </p>\n\n <wx-segmented\n v-model=\"view\"\n class=\"wx-banner-place__views\"\n size=\"sm\"\n :options=\"views\"\n :aria-label=\"t('banner.all')\"\n />\n\n <wx-skeleton v-if=\"loading && rows.length === 0\" :rows=\"4\" />\n\n <wx-empty\n v-else-if=\"rows.length === 0\"\n :title=\"inBin ? t('banner.empty-bin') : t('banner.empty')\"\n :description=\"inBin ? undefined : t('banner.empty-help')\"\n />\n\n <template v-else>\n <!-- A grip and not the whole row: the row opens a banner, and a row that both opens and\n drags is one where one of the two happens by accident. -->\n <wx-sortable-list\n v-model=\"rows\"\n class=\"wx-banner-place__list\"\n :class=\"{ 'is-loading': loading }\"\n plain\n item-key=\"id\"\n :handle=\"sortable ? 'grip' : 'row'\"\n :item-label=\"(item: BannerRow) => item.title\"\n :disabled=\"!sortable\"\n @move=\"reorder\"\n >\n <template #default=\"{ item }\">\n <component\n :is=\"inBin ? 'div' : 'router-link'\"\n :to=\"inBin ? undefined : edit(item)\"\n class=\"wx-banner-row\"\n :class=\"{ 'is-off': !item.enabled && !inBin }\"\n >\n <span class=\"wx-banner-row__thumb\">\n <img v-if=\"item.thumb\" :src=\"item.thumb\" alt=\"\" loading=\"lazy\" />\n <wx-icon v-else name=\"image\" size=\"sm\" :aria-label=\"t('banner.no-picture')\" />\n </span>\n\n <span class=\"wx-banner-row__body\">\n <span class=\"wx-banner-row__title\">{{ item.title }}</span>\n\n <span v-if=\"item.video || (!item.enabled && !inBin)\" class=\"wx-banner-row__facts\">\n <wx-badge v-if=\"item.video\" size=\"sm\" type=\"info\">{{ t('banner.video') }}</wx-badge>\n <wx-badge v-if=\"!item.enabled && !inBin\" dot size=\"sm\">{{\n t('banner.disabled')\n }}</wx-badge>\n </span>\n </span>\n </component>\n </template>\n\n <template #actions=\"{ item }\">\n <wx-row-menu :actions=\"actionsFor(item)\" :label=\"item.title\" />\n </template>\n </wx-sortable-list>\n\n <!-- What a drag does here, said once under the list. -->\n <wx-alert\n v-if=\"sortable\"\n class=\"wx-banner-place__note\"\n type=\"info\"\n :description=\"t('banner.order')\"\n />\n </template>\n </div>\n</template>\n\n<style scoped>\n.wx-banner-place {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-12);\n min-width: 0;\n padding: var(--wx-gap, var(--wx-space-16));\n}\n\n.wx-banner-place__lock {\n display: inline-flex;\n color: var(--wx-text-muted);\n}\n\n.wx-banner-place__usage {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--wx-space-6);\n margin: 0;\n color: var(--wx-text-muted);\n font-size: var(--wx-font-size-sm);\n}\n\n.wx-banner-place__usage code {\n padding: 0 var(--wx-space-4);\n border-radius: var(--wx-radius-xs);\n background: var(--wx-bg-subtle);\n color: var(--wx-text-default);\n font-family: var(--wx-font-family-mono);\n /* Copied whole, as somebody writing a template would: a click selects all of it. */\n user-select: all;\n}\n\n.wx-banner-place__views {\n align-self: flex-start;\n}\n\n.wx-banner-place__list.is-loading {\n opacity: 0.6;\n}\n\n.wx-banner-place__list :deep(.wx-sortable-list__row) {\n gap: var(--wx-space-4);\n padding-inline: var(--wx-space-4);\n border-radius: var(--wx-radius-sm);\n}\n\n.wx-banner-place__list :deep(.wx-sortable-list__row:hover) {\n background: var(--wx-bg-subtle);\n}\n\n.wx-banner-row {\n display: flex;\n align-items: center;\n gap: var(--wx-space-12);\n /* The row is the target: a short title should not leave half the line dead. */\n flex: 1 1 auto;\n width: 100%;\n min-width: 0;\n padding-block: var(--wx-space-8);\n color: inherit;\n text-decoration: none;\n}\n\n/* Off is not gone: still here, still in its place, just quieter than what is on the site. */\n.wx-banner-row.is-off .wx-banner-row__thumb,\n.wx-banner-row.is-off .wx-banner-row__title {\n opacity: 0.55;\n}\n\n.wx-banner-row__thumb {\n display: flex;\n flex: none;\n align-items: center;\n justify-content: center;\n width: 80px;\n aspect-ratio: 16 / 9;\n overflow: hidden;\n border-radius: var(--wx-radius-xs);\n background: var(--wx-bg-fill);\n color: var(--wx-text-muted);\n}\n\n.wx-banner-row__thumb img {\n width: 100%;\n height: 100%;\n /* A thumbnail wider than its frame would otherwise hold the row open (CLAUDE.md §4). */\n min-width: 0;\n object-fit: cover;\n}\n\n.wx-banner-row__body {\n display: flex;\n flex: 1 1 auto;\n flex-direction: column;\n gap: var(--wx-space-4);\n min-width: 0;\n}\n\n.wx-banner-row__title {\n min-width: 0;\n overflow: hidden;\n font-weight: var(--wx-font-weight-medium);\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\na.wx-banner-row:hover .wx-banner-row__title {\n color: var(--wx-color-primary);\n}\n\n.wx-banner-row__facts {\n display: flex;\n flex-wrap: wrap;\n gap: var(--wx-space-4);\n}\n\n.wx-banner-place__note {\n margin-top: var(--wx-space-4);\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\nimport { useRoute, useRouter, type LocationQueryRaw } from 'vue-router'\nimport {\n confirm,\n createModal,\n toast,\n WxCard,\n WxEmpty,\n WxIcon,\n WxIndicator,\n WxListDetail,\n WxSkeleton,\n WxText,\n} from '@webx-ui/core'\nimport {\n useAdmin,\n useErrorText,\n useTranslate,\n WxListScreen,\n WxRowMenu,\n type RowAction,\n type ScreenAction,\n} from '@webx-ui/module-admin'\nimport { createBannersApi } from './api'\nimport { useBannersMessages } from './i18n'\nimport PlaceDialog from './PlaceDialog.vue'\nimport PlacePane from './PlacePane.vue'\nimport type { PlaceRow } from './types'\n\n/**\n * The section: the places of the site on the left, the banners of one of them on the right\n * (§5.4) — the shape of the menus, for the same reason: somebody who opens this is arranging one\n * place, not choosing between them, so the first one opens by itself where there is room for it.\n * On a phone it does not: there the same line would raise a panel over a list nobody touched.\n *\n * A declared place is on the list before anything was ever saved into it (decision 1) — its row\n * in the table is made by its first banner — so places are named by key throughout, not by id.\n */\nconst props = withDefaults(defineProps<{ base?: string }>(), { base: '/banners' })\n\nconst admin = useAdmin()\nconst api = createBannersApi(admin)\nconst route = useRoute()\nconst router = useRouter()\nuseBannersMessages()\n\nconst t = useTranslate('webx-banners')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\nconst places = ref<PlaceRow[]>([])\nconst loading = ref(true)\nconst open = ref(false)\nconst detailInline = ref(true)\n\nconst canManage = computed(() => admin.can('banners.manage'))\n\nconst edit = createModal<PlaceRow, { place?: PlaceRow | null }>(PlaceDialog)\n\nconst title = computed(\n () =>\n admin.state.manifest?.modules.find((module) => module.id === 'banners')?.title ??\n t('module.banners'),\n)\n\n/** Which place is open, kept in the address — so \"back\" from a banner lands on its place. */\nconst current = computed<string | null>(() => {\n const asked = route.query.place\n\n return typeof asked === 'string' && asked !== '' ? asked : null\n})\n\nconst chosen = computed(() => places.value.find((place) => place.key === current.value) ?? null)\n\nasync function load(): Promise<void> {\n loading.value = true\n\n try {\n places.value = await api.places()\n } catch (error) {\n toast.danger(message(error))\n } finally {\n loading.value = false\n }\n}\n\nvoid load()\n\n/*\n * The first place, where there is a column to draw it in — only once the width is measured\n * (`detail-inline` says so), so a phone does not get a drawer over a list it never touched.\n */\nwatch([places, chosen, detailInline], () => {\n if (!detailInline.value || places.value.length === 0 || chosen.value !== null) return\n\n void router.replace({ query: { ...route.query, place: places.value[0]!.key } })\n})\n\nwatch(chosen, (place) => {\n if (place !== null && detailInline.value) open.value = true\n})\n\n/* Coming back from a banner on a phone lands on its place, and that is the drawer, open. */\nif (current.value !== null) open.value = true\n\nfunction choose(place: PlaceRow): void {\n const query: LocationQueryRaw = { ...route.query, place: place.key }\n\n // The bin of one place is not the bin of the next.\n delete query.view\n void router.replace({ query })\n open.value = true\n}\n\nasync function add(): Promise<void> {\n const made = await edit({ place: null })\n\n if (!made) return\n\n toast.success(t('places.created'))\n await load()\n choose(made)\n}\n\nasync function rename(place: PlaceRow): Promise<void> {\n const saved = await edit({ place })\n\n if (!saved) return\n\n toast.success(t('places.renamed'))\n await load()\n}\n\nasync function remove(place: PlaceRow): Promise<void> {\n const agreed = await confirm({\n title: t('places.delete-title', { place: place.title }),\n message: t('places.delete-text'),\n confirmText: t('places.delete'),\n cancelText: t('places.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n try {\n await api.removePlace(place.key)\n toast.success(t('places.deleted'))\n\n if (current.value === place.key) {\n const rest = { ...route.query }\n\n delete rest.place\n delete rest.view\n void router.replace({ query: rest })\n open.value = false\n }\n } catch (error) {\n // The one refusal worth its own words: something is still in it — perhaps only in the bin,\n // which the count beside the name does not show (§5.6).\n toast.danger(message(error))\n } finally {\n await load()\n }\n}\n\nfunction actionsFor(place: PlaceRow): RowAction[] {\n if (!canManage.value || place.declared) return []\n\n return [\n { key: 'rename', icon: 'edit', label: t('places.rename'), run: () => void rename(place) },\n {\n key: 'delete',\n icon: 'trash',\n // Said in the line itself, since a switched-off line cannot carry a tooltip: an empty\n // place is the only kind that goes (decision 1).\n label: place.count > 0 ? t('places.delete-empty-first') : t('places.delete'),\n danger: true,\n disabled: place.count > 0,\n run: () => void remove(place),\n },\n ]\n}\n\nconst actions = computed<ScreenAction[]>(() =>\n canManage.value\n ? [{ key: 'new-place', label: t('places.new'), icon: 'plus', run: () => void add() }]\n : [],\n)\n</script>\n\n<template>\n <wx-list-screen :title=\"title\" :actions=\"actions\" :card=\"false\">\n <wx-card class=\"wx-banners\" padding=\"none\">\n <wx-list-detail\n v-model:open=\"open\"\n class=\"wx-banners__panes\"\n :list-width=\"300\"\n :detail-min=\"440\"\n :detail-label=\"t('places.places')\"\n @detail-inline=\"detailInline = $event\"\n >\n <template #list>\n <wx-skeleton\n v-if=\"loading && places.length === 0\"\n class=\"wx-banners__loading\"\n :rows=\"3\"\n />\n\n <wx-empty\n v-else-if=\"places.length === 0\"\n :title=\"t('places.empty')\"\n :description=\"t('places.empty-help')\"\n />\n\n <ul v-else class=\"wx-banners__list\">\n <li v-for=\"place in places\" :key=\"place.key\" class=\"wx-banners__row\">\n <button\n type=\"button\"\n class=\"wx-banners__open\"\n :class=\"{ 'is-current': place.key === current }\"\n @click=\"choose(place)\"\n >\n <span class=\"wx-banners__name\">\n <wx-text truncate weight=\"medium\">{{ place.title }}</wx-text>\n <wx-icon\n v-if=\"place.declared\"\n class=\"wx-banners__lock\"\n name=\"lock\"\n size=\"sm\"\n :aria-label=\"t('places.declared')\"\n />\n <wx-indicator\n class=\"wx-banners__count\"\n type=\"neutral\"\n :value=\"place.count\"\n :label=\"t('places.count')\"\n />\n </span>\n\n <wx-text size=\"sm\" tone=\"muted\" mono truncate>banners('{{ place.key }}')</wx-text>\n </button>\n\n <wx-row-menu\n v-if=\"actionsFor(place).length > 0\"\n :actions=\"actionsFor(place)\"\n :label=\"place.title\"\n />\n </li>\n </ul>\n </template>\n\n <template #detail=\"{ inline, back }\">\n <place-pane\n v-if=\"chosen\"\n :key=\"chosen.key\"\n :place=\"chosen\"\n :base=\"props.base\"\n :inline=\"inline\"\n @back=\"back\"\n @changed=\"load\"\n />\n </template>\n\n <template #empty>\n <wx-empty :title=\"t('places.choose')\" :description=\"t('places.choose-help')\" />\n </template>\n </wx-list-detail>\n </wx-card>\n </wx-list-screen>\n</template>\n\n<style>\n/*\n * What scrolls here is the page, the way it does on every other list of the panel. `clip`, not\n * `hidden`: `hidden` would make this a scroll container, and everything sticky inside would pin\n * itself to a box that never moves (CLAUDE.md §4).\n */\n.wx-banners > .wx-card__body {\n display: flex;\n flex-direction: column;\n overflow: clip;\n border-radius: inherit;\n}\n</style>\n\n<style scoped>\n.wx-banners__loading {\n padding: var(--wx-space-16);\n}\n\n.wx-banners__list {\n margin: 0;\n padding: var(--wx-space-8);\n list-style: none;\n}\n\n.wx-banners__row {\n display: flex;\n align-items: center;\n gap: var(--wx-space-4);\n margin: 0;\n padding-inline: var(--wx-space-8);\n border-radius: var(--wx-radius-sm);\n}\n\n.wx-banners__row:has(.is-current) {\n background: var(--wx-bg-subtle);\n}\n\n.wx-banners__open {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: var(--wx-space-2);\n flex: 1 1 auto;\n min-width: 0;\n padding-block: var(--wx-space-8);\n padding-inline: 0;\n border: 0;\n background: none;\n font: inherit;\n color: inherit;\n text-align: start;\n cursor: pointer;\n}\n\n.wx-banners__open.is-current {\n color: var(--wx-color-primary);\n}\n\n.wx-banners__name {\n display: flex;\n align-items: center;\n gap: var(--wx-space-6);\n min-width: 0;\n max-width: 100%;\n}\n\n.wx-banners__lock {\n flex: none;\n color: var(--wx-text-muted);\n}\n\n.wx-banners__count {\n flex: 0 0 auto;\n}\n</style>\n","import type { AdminModule } from '@webx-ui/module-admin'\nimport BannerEditorPage from './BannerEditorPage.vue'\nimport BannersPage from './BannersPage.vue'\n\nexport interface BannersOptions {\n /** Where the banners live inside the panel. */\n path?: string\n}\n\n/**\n * Banners as a section of the panel (§5.4): one module and one entry, without a group.\n *\n * Two screens. The places and the banners of one of them are a list and its detail, the way the\n * menus are; a banner is a page of its own, because its form is long — three pictures, the words\n * and up to three buttons — and a pane beside a list is no room for it. A section whose server\n * half is not installed never appears — the entry is built from the manifest.\n */\nexport function banners(options: BannersOptions = {}): AdminModule {\n const path = options.path ?? '/banners'\n\n return {\n id: 'banners',\n path,\n routes: [\n // The open place is in the address (`?place=`), so a link to it is a link to its banners.\n { path, name: 'webx.banners', component: BannersPage, props: { base: path } },\n // A banner not written yet: the place it goes to rides in the query (`?place=hero`).\n {\n path: `${path}/new`,\n name: 'webx.banners.new',\n component: BannerEditorPage,\n props: { base: path },\n },\n {\n path: `${path}/:id(\\\\d+)`,\n name: 'webx.banners.edit',\n component: BannerEditorPage,\n props: { base: path },\n },\n ],\n }\n}\n"],"names":["createBannersApi","admin","base","place","key","data","body","input","title","trashed","id","values","ids","bannersMessages","seeded","useBannersMessages","i18n","useAdmin","props","__props","api","route","useRoute","router","useRouter","locales","useLocales","t","useTranslate","message","useErrorText","computed","raw","banner","ref","snapshot","placeSaved","places","loading","saving","errors","canManage","current","dirty","state","written","fallback","localizedValue","back","section","module","placeOptions","one","placeModel","value","blank","take","detail","load","list","asked","fresh","error","toast","save","made","remove","record","confirm","leaveGuard","event","onKeydown","useBodyKeys","watch","next","onMounted","onBeforeUnmount","onBeforeRouteLeave","actions","_createElementBlock","_Fragment","_createVNode","_unref","WxSkeleton","WxCard","WxScreenHead","WxBadge","WxFormItem","WxSelect","$event","WxScreen","_createBlock","WxActionBar","WxSaveState","WxButton","open","resolve","dismiss","useModal","editing","initial","first","field","useTemplateRef","nextTick","words","errorOf","name","found","submit","WxDialog","WxSpace","_createElementVNode","WxInput","emit","__emit","rows","view","inBin","views","sortable","layout","edit","row","add","reorder","toggle","restore","actionsFor","_openBlock","_hoisted_1","WxTooltip","WxIcon","_hoisted_3","_toDisplayString","_hoisted_4","WxSegmented","WxEmpty","WxSortableList","_normalizeClass","item","_withCtx","_resolveDynamicComponent","_hoisted_5","_hoisted_7","_hoisted_8","_hoisted_9","WxRowMenu","WxAlert","detailInline","createModal","PlaceDialog","chosen","choose","query","rename","rest","WxListScreen","WxListDetail","_cache","_renderList","WxText","_createTextVNode","WxIndicator","inline","PlacePane","banners","options","path","BannersPage","BannerEditorPage"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../src/api.ts","../src/messages.ts","../src/i18n.ts","../src/BannerEditorPage.vue","../src/PlaceDialog.vue","../src/PlacePane.vue","../src/BannersPage.vue","../src/module.ts"],"sourcesContent":["import type { AdminContext } from '@webx-ui/module-admin'\nimport type { ScreenModel } from '@webx-ui/schema'\nimport type { BannerDetail, BannerRow, PlaceInput, PlaceRow } from './types'\n\nexport interface BannersApi {\n /** Every place: the declared ones in the config's order, then the own ones by name. */\n places(): Promise<PlaceRow[]>\n createPlace(input: PlaceInput): Promise<PlaceRow>\n /** Only a place of somebody's own, and only its name: the key is what a template asks for. */\n renamePlace(key: string, title: PlaceInput['title']): Promise<PlaceRow>\n /** Only an own place with nothing in it, the bin included — a 422 with the count otherwise. */\n removePlace(key: string): Promise<void>\n\n /** The banners of a place in their order, or its bin. A declared place without a row: `[]`. */\n list(place: string, trashed?: boolean): Promise<BannerRow[]>\n get(id: number): Promise<BannerDetail>\n /**\n * A new banner out of the values of its form, in one request: the form is filled in first and\n * the record made on the first save, so nobody leaves an empty banner in a place.\n */\n create(place: string, values: ScreenModel): Promise<BannerDetail>\n /**\n * Refused with a 422 under the name of the field — `buttons.<n>.link` for a button's row. A\n * `place` other than the one it stands in moves it to the end of that one (decision 14).\n */\n save(id: number, values: ScreenModel, place?: string): Promise<BannerDetail>\n remove(id: number): Promise<void>\n restore(id: number): Promise<BannerRow>\n /** The order of one place, written whole. */\n reorder(place: string, ids: number[]): Promise<void>\n}\n\n/** Everything under `/banners`, below the panel's API path (§5.6). */\nexport function createBannersApi(admin: AdminContext): BannersApi {\n const base = `${admin.apiPath}/banners`\n const place = (key: string) => `${base}/places/${encodeURIComponent(key)}`\n const data = <T>(body: { data: T }): T => body.data\n\n return {\n places: () => admin.http.get<{ data: PlaceRow[] }>(`${base}/places`).then(data),\n createPlace: (input) => admin.http.post<{ data: PlaceRow }>(`${base}/places`, input).then(data),\n renamePlace: (key, title) =>\n admin.http.put<{ data: PlaceRow }>(place(key), { title }).then(data),\n removePlace: (key) => admin.http.delete<void>(place(key)).then(() => undefined),\n\n list: (key, trashed = false) =>\n admin.http\n .get<{ data: BannerRow[] }>(`${place(key)}/banners${trashed ? '?trashed=1' : ''}`)\n .then(data),\n get: (id) => admin.http.get<{ data: BannerDetail }>(`${base}/${id}`).then(data),\n create: (key, values) =>\n admin.http.post<{ data: BannerDetail }>(`${place(key)}/banners`, { values }).then(data),\n save: (id, values, key) =>\n admin.http\n .put<{ data: BannerDetail }>(\n `${base}/${id}`,\n key === undefined ? { values } : { values, place: key },\n )\n .then(data),\n remove: (id) => admin.http.delete<void>(`${base}/${id}`).then(() => undefined),\n // The resource of the list line, and not the form's pair: a restored banner stays closed.\n restore: (id) => admin.http.post<{ data: BannerRow }>(`${base}/${id}/restore`, {}).then(data),\n reorder: (key, ids) =>\n admin.http.post<void>(`${place(key)}/reorder`, { ids }).then(() => undefined),\n }\n}\n","import type { Messages } from '@webx-ui/module-admin'\n\n/**\n * The English this package speaks on its own, before the server's dictionary arrives, or without\n * one. The server ships the same lines in ten languages under `webx-banners::` and overrides\n * these; `messages.test.ts` keeps the two sets of keys equal. The server's refusals (`errors`)\n * are not here: the panel only ever shows them as they come.\n */\nexport const bannersMessages: Record<string, Messages> = {\n module: {\n banners: 'Banners',\n },\n places: {\n hero: 'Home page slider',\n promo: 'Promo strip',\n notice: 'Announcement bar',\n list: 'Places',\n new: 'New place',\n title: 'Name',\n key: 'Key',\n 'key-help':\n 'What the site’s template asks for: lowercase Latin letters, digits and dashes, starting with a letter. It cannot be changed later.',\n declared: 'Declared in the configuration',\n 'in-template': \"In a template: banners(':key')\",\n count: 'Banners',\n rename: 'Rename',\n delete: 'Delete',\n 'delete-confirm': 'Delete the place “:title”?',\n 'delete-not-empty': 'Only an empty place can be deleted — the bin counts too.',\n empty: 'No places yet',\n choose: 'Choose a place on the left',\n create: 'Create',\n save: 'Save',\n cancel: 'Cancel',\n created: 'Place created',\n renamed: 'Place renamed',\n deleted: 'Place deleted',\n places: 'Places',\n 'new-title': 'New place',\n 'rename-title': 'Rename the place',\n 'delete-title': 'Delete the place “:place”?',\n 'delete-text':\n 'There is nothing in it. A template that still asks for it gets an empty list, not an error.',\n 'delete-empty-first': 'Delete: empty it first',\n 'delete-blocked': 'Banners in this place, the bin included: :count. Take them out first.',\n 'field-title': 'Name',\n 'field-key': 'Key',\n 'declared-help': 'Cannot be renamed or deleted here: a template asks for it by its key.',\n usage: 'In a template:',\n 'layout-single': 'One banner',\n 'layout-random': 'A random one',\n 'layout-slider': 'Slider',\n 'empty-help': 'The site’s config declares the places its templates ask for.',\n 'choose-help': 'Its banners open beside the list.',\n },\n banner: {\n new: 'New banner',\n trash: 'Bin',\n 'to-trash': 'Move to the bin',\n restore: 'Restore',\n open: 'Open',\n enable: 'Turn on',\n disable: 'Turn off',\n disabled: 'Off',\n video: 'Video',\n empty: 'No banners in this place yet',\n 'trash-empty': 'The bin is empty',\n place: 'Place',\n 'place-help': 'Moving the banner puts it at the end of the new place.',\n back: 'Back to the place',\n save: 'Save',\n saved: 'Banner saved',\n trashed: 'Bin',\n restored: 'Banner restored',\n 'leave-title': 'Leave without saving?',\n 'leave-text': 'The changes to this banner will be lost.',\n leave: 'Leave',\n stay: 'Stay',\n reorder: 'Reorder',\n untitled: 'Untitled banner',\n all: 'Banners',\n 'empty-help': 'The site shows them wherever a template asks for this place.',\n 'empty-bin': 'The bin is empty.',\n 'no-picture': 'No picture',\n order: 'Drag to change the order on the site.',\n 'switched-on': 'Switched on.',\n 'switched-off': 'Switched off.',\n delete: 'Delete',\n 'delete-title': 'Delete “:name”?',\n 'delete-text': 'It goes to the bin and leaves the site. Restored, it comes back to its place.',\n deleted: 'Moved to the bin.',\n 'reorder-failed': 'The new order was not saved.',\n 'save-failed': 'Not saved.',\n cancel: 'Cancel',\n },\n screen: {\n media: 'Pictures and video',\n image: 'Picture',\n 'image-help': 'Required. Shown on wide screens, and as the poster of the video.',\n 'image-mobile': 'Picture for phones',\n 'image-mobile-help': 'Optional. Narrow screens show it instead of the picture above.',\n video: 'Video',\n 'video-help':\n 'Optional. Plays silently over the picture; the picture becomes its poster and what is seen without it.',\n words: 'Words',\n title: 'Title',\n 'title-help':\n 'A banner with words is shown only in the languages they are written in; one without words is shown everywhere.',\n text: 'Text',\n 'text-help': 'Plain text; line breaks are kept.',\n buttons: 'Buttons',\n 'buttons-help': 'Up to three. A button without a label in a language is not shown in it.',\n 'buttons-add': 'Add a button',\n 'buttons-empty': 'No buttons',\n label: 'Label',\n link: 'Link',\n variant: 'Look',\n 'variant-placeholder': 'The first one',\n settings: 'Settings',\n enabled: 'On',\n 'enabled-help': 'Only a banner that is on is shown on the site. A new one starts off.',\n },\n}\n","import { useAdmin } from '@webx-ui/module-admin'\nimport { bannersMessages } from './messages'\n\nconst seeded = new WeakSet<object>()\n\n/** Puts this package's English under the panel's dictionary, once per panel. */\nexport function useBannersMessages(): void {\n try {\n const { i18n } = useAdmin()\n\n if (!seeded.has(i18n)) {\n i18n.defaults('webx-banners', bannersMessages)\n seeded.add(i18n)\n }\n } catch {\n // Outside a panel there is no dictionary to seed, and `useTranslate` falls back on its own.\n }\n}\n","<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'\nimport { onBeforeRouteLeave, useRoute, useRouter } from 'vue-router'\nimport {\n confirm,\n localizedValue,\n toast,\n useLocales,\n WxActionBar,\n WxBadge,\n WxButton,\n WxCard,\n WxFormItem,\n WxSelect,\n WxSkeleton,\n type LocalizedValue,\n} from '@webx-ui/core'\nimport {\n useAdmin,\n useErrorText,\n useTranslate,\n WxSaveState,\n WxScreen,\n WxScreenHead,\n type ScreenAction,\n useBodyKeys,\n} from '@webx-ui/module-admin'\nimport type { ScreenModel } from '@webx-ui/schema'\nimport { createBannersApi } from './api'\nimport { useBannersMessages } from './i18n'\nimport type { BannerDetail, BannerSummary, PlaceRow } from './types'\n\n/**\n * One banner, on a page of its own: a head, the place, the described screen, and one button.\n *\n * A page and not a pane beside the list, because the form is long — three pictures, the words,\n * up to three buttons — and `banners.form` is a described screen a project adds its own fields\n * to (decision 14). One explicit save, no autosave: a banner has no draft (decision 13), so what\n * is saved is on the site at once, if it is switched on.\n *\n * The place is not a field of the screen (decision 9). The list of places is alive — own places\n * come and go — and the options of a described select are patched on when the server boots, so a\n * live list has nowhere to go there. It is a select of its own above the screen, and rides beside\n * the values: `PUT { values, place }`. Changing it marks the form changed like any field does.\n *\n * A new banner is a form first and a record on the first save: pressing \"New banner\" and walking\n * away should not leave an empty, switched-off row at the end of a place.\n */\ndefineOptions({ name: 'WxBannerEditorPage' })\n\nconst props = withDefaults(defineProps<{ base?: string }>(), { base: '/banners' })\n\nconst admin = useAdmin()\nconst api = createBannersApi(admin)\nconst route = useRoute()\nconst router = useRouter()\nconst locales = useLocales()\nuseBannersMessages()\n\nconst t = useTranslate('webx-banners')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\n/** `null` — a banner not written yet (`/banners/new`). */\nconst id = computed<number | null>(() => {\n const raw = route.params.id\n\n return typeof raw === 'string' && raw !== '' ? Number(raw) : null\n})\n\nconst banner = ref<BannerSummary | null>(null)\nconst values = ref<ScreenModel>({})\nconst snapshot = ref('')\nconst place = ref<string | null>(null)\n/** The place the banner stands in on the server — what \"changed\" and \"back\" are measured by. */\nconst placeSaved = ref<string | null>(null)\nconst places = ref<PlaceRow[]>([])\n\nconst loading = ref(true)\nconst saving = ref(false)\nconst errors = ref<Record<string, string[]>>({})\n\nconst canManage = computed(() => admin.can('banners.manage'))\n\nconst current = computed(() => JSON.stringify(values.value))\nconst dirty = computed(\n () =>\n snapshot.value !== '' && (current.value !== snapshot.value || place.value !== placeSaved.value),\n)\n\nconst state = computed<'saving' | 'unsaved' | 'saved'>(() => {\n if (saving.value) return 'saving'\n\n return dirty.value || id.value === null ? 'unsaved' : 'saved'\n})\n\n/** The title follows the field rather than the answer: an edit shows at the top as it is typed. */\nconst title = computed(() => {\n const written = values.value.title as LocalizedValue | string | null | undefined\n const fallback = id.value === null ? t('banner.new') : (banner.value?.title ?? '')\n\n return localizedValue(written, locales.active.value, fallback) || t('banner.untitled')\n})\n\n/** Back to the place the banner stands in, and not to the section's first one. */\nconst back = computed(() => {\n const key = placeSaved.value ?? place.value\n\n return key === null ? props.base : `${props.base}?place=${encodeURIComponent(key)}`\n})\n\nconst section = computed(\n () =>\n admin.state.manifest?.modules.find((module) => module.id === 'banners')?.title ??\n t('module.banners'),\n)\n\nconst placeOptions = computed(() =>\n places.value.map((one) => ({ value: one.key, label: `${one.title} · ${one.key}` })),\n)\n\nconst placeModel = computed<string | number | null>({\n get: () => place.value,\n set: (value) => {\n place.value = value === null || value === undefined ? null : String(value)\n },\n})\n\n/** What the server would answer for a banner with nothing in it yet (§5.6). */\nfunction blank(): ScreenModel {\n return {\n image: null,\n image_mobile: null,\n video: null,\n title: {},\n text: {},\n buttons: [],\n enabled: false,\n }\n}\n\nfunction take(detail: BannerDetail): void {\n banner.value = detail.banner\n values.value = detail.values\n snapshot.value = JSON.stringify(detail.values)\n place.value = detail.banner.place\n placeSaved.value = detail.banner.place\n}\n\nasync function load(): Promise<void> {\n loading.value = true\n\n try {\n const [list, detail] = await Promise.all([\n api.places(),\n id.value === null ? Promise.resolve(null) : api.get(id.value),\n ])\n\n places.value = list\n\n if (detail !== null) {\n take(detail)\n } else {\n const asked = route.query.place\n const key = typeof asked === 'string' && list.some((one) => one.key === asked) ? asked : null\n const fresh = blank()\n\n banner.value = null\n values.value = fresh\n snapshot.value = JSON.stringify(fresh)\n place.value = key ?? list[0]?.key ?? null\n placeSaved.value = place.value\n }\n } catch (error) {\n toast.danger(message(error))\n void router.push(props.base)\n } finally {\n loading.value = false\n }\n}\n\nasync function save(): Promise<void> {\n if (saving.value || !canManage.value || place.value === null) return\n\n saving.value = true\n errors.value = {}\n\n try {\n const made = id.value === null\n const detail = made\n ? await api.create(place.value, values.value)\n : await api.save(\n id.value as number,\n values.value,\n place.value !== placeSaved.value ? place.value : undefined,\n )\n\n take(detail)\n toast.success(t('banner.saved'))\n\n // A record now, with an address of its own; `replace`, so \"back\" does not reopen a blank form.\n if (made) void router.replace(`${props.base}/${detail.banner.id}`)\n } catch (error) {\n const body = (error as { body?: { errors?: Record<string, string[]> } }).body\n\n if (body?.errors) {\n errors.value = body.errors\n toast.danger(t('banner.save-failed'))\n } else {\n toast.danger(message(error))\n }\n } finally {\n saving.value = false\n }\n}\n\nasync function remove(): Promise<void> {\n const record = banner.value\n\n if (!record) return\n\n const agreed = await confirm({\n title: t('banner.delete-title', { name: record.title }),\n message: t('banner.delete-text'),\n confirmText: t('banner.delete'),\n cancelText: t('banner.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n try {\n await api.remove(record.id)\n toast.success(t('banner.deleted'))\n // Nothing left to protect: the record is in the bin, and asking about its unsaved edits on\n // the way out would be asking about a banner that is gone.\n snapshot.value = current.value\n place.value = placeSaved.value\n void router.push(back.value)\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\n/* The browser's own guard: it cannot wait for a request, so all it can do is ask. */\nfunction leaveGuard(event: BeforeUnloadEvent): void {\n if (dirty.value) event.preventDefault()\n}\n\n/* Ctrl+S saves, as it does everywhere else that has a save button. */\nfunction onKeydown(event: KeyboardEvent): void {\n if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 's') {\n event.preventDefault()\n if (dirty.value || id.value === null) void save()\n }\n}\n\n// And from the page around the form, where a click on empty space leaves the focus.\nuseBodyKeys(onKeydown)\n\n/*\n * `/banners/new` and `/banners/5` are one component on two routes, and the router patches it\n * rather than mounting it again (CLAUDE.md §4) — so a change of banner is watched for. The first\n * save moving the address to the record it just made is not a change: that banner is on screen.\n */\nwatch(id, (next) => {\n if (next !== null && banner.value?.id === next) return\n\n void load()\n})\n\nonMounted(() => {\n void load()\n window.addEventListener('beforeunload', leaveGuard)\n})\n\nonBeforeUnmount(() => {\n window.removeEventListener('beforeunload', leaveGuard)\n})\n\nonBeforeRouteLeave(async () => {\n if (!dirty.value || !canManage.value) return true\n\n return await confirm({\n title: t('banner.leave-title'),\n message: t('banner.leave-text'),\n confirmText: t('banner.leave'),\n cancelText: t('banner.cancel'),\n tone: 'danger',\n })\n})\n\n/* What leads away from the banner. On a phone the head folds it into the ···. */\nconst actions = computed<ScreenAction[]>(() =>\n canManage.value && banner.value !== null\n ? [\n {\n key: 'delete',\n label: t('banner.delete'),\n icon: 'trash',\n danger: true,\n menu: true,\n run: () => void remove(),\n },\n ]\n : [],\n)\n</script>\n\n<template>\n <div class=\"wx-banner-editor\" @keydown=\"onKeydown\">\n <template v-if=\"loading\">\n <!-- Shaped like the screen it stands in for: the head, and a card for what is coming. -->\n <wx-skeleton class=\"wx-banner-editor__ghost\" title :rows=\"1\" />\n <wx-card><wx-skeleton :rows=\"6\" /></wx-card>\n </template>\n\n <template v-else>\n <wx-screen-head divider :back=\"back\" :back-label=\"section\" :title=\"title\" :actions=\"actions\">\n <template v-if=\"banner && !banner.enabled\" #title-after>\n <wx-badge dot>{{ t('banner.disabled') }}</wx-badge>\n </template>\n </wx-screen-head>\n\n <wx-card class=\"wx-banner-editor__place\">\n <wx-form-item\n :label=\"t('banner.place')\"\n :help=\"id === null ? undefined : t('banner.place-help')\"\n :error=\"errors.place?.[0]\"\n required\n >\n <wx-select\n v-model=\"placeModel\"\n :options=\"placeOptions\"\n :disabled=\"!canManage || saving\"\n :aria-label=\"t('banner.place')\"\n />\n </wx-form-item>\n </wx-card>\n\n <wx-screen\n v-model=\"values\"\n name=\"banners.form\"\n :errors=\"errors\"\n :disabled=\"!canManage || saving\"\n />\n\n <wx-action-bar v-if=\"canManage\">\n <template #state>\n <wx-save-state :state=\"state\" />\n </template>\n\n <wx-button\n type=\"primary\"\n :loading=\"saving\"\n :disabled=\"(!dirty && id !== null) || place === null\"\n @click=\"save\"\n >\n {{ t('banner.save') }}\n </wx-button>\n </wx-action-bar>\n </template>\n </div>\n</template>\n\n<style scoped>\n.wx-banner-editor {\n display: flex;\n flex-direction: column;\n gap: var(--wx-gap, var(--wx-space-16));\n container-type: inline-size;\n}\n\n.wx-banner-editor__ghost {\n max-width: 420px;\n}\n\n/* A select as wide as a place's name needs, not as the page: it is one choice, not a text. */\n.wx-banner-editor__place :deep(.wx-form-item) {\n max-width: 420px;\n margin-bottom: 0;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { nextTick, onMounted, ref, useTemplateRef } from 'vue'\nimport {\n useLocales,\n useModal,\n WxButton,\n WxDialog,\n WxFormItem,\n WxInput,\n WxSpace,\n type LocalizedValue,\n} from '@webx-ui/core'\nimport { useAdmin, useTranslate } from '@webx-ui/module-admin'\nimport { createBannersApi } from './api'\nimport { useBannersMessages } from './i18n'\nimport type { PlaceRow } from './types'\n\n/**\n * A place of somebody's own: a name, and a key when it is new (§5.4).\n *\n * The key does not follow the name the way a page's address follows its title: it is typed into\n * `banners('…')` by hand, and a key that quietly became `promo-2` would be a template asking for\n * a place that is not there. For the same reason a rename never touches it.\n *\n * The name is translated, and required in the default language — which is what the list falls\n * back on in a panel open in a language nobody named the place in.\n */\ndefineOptions({ name: 'WxBannerPlaceDialog' })\n\nconst props = withDefaults(defineProps<{ place?: PlaceRow | null }>(), { place: null })\n\nconst { open, resolve, dismiss } = useModal<PlaceRow>()\n\nconst api = createBannersApi(useAdmin())\nconst locales = useLocales()\nuseBannersMessages()\n\nconst t = useTranslate('webx-banners')\n\nconst editing = props.place !== null\n\n/*\n * Every language when the server sent them (`titles`), the one the list shows otherwise — filed\n * under the default language, which is where a place with one name has it.\n */\nfunction initial(): LocalizedValue {\n if (props.place === null) return {}\n if (props.place.titles) return { ...props.place.titles }\n\n const first = locales.list.value[0]?.code\n\n return first === undefined ? {} : { [first]: props.place.title }\n}\n\nconst key = ref(props.place?.key ?? '')\nconst title = ref<LocalizedValue>(initial())\nconst saving = ref(false)\nconst errors = ref<Record<string, string[]>>({})\nconst field = useTemplateRef<HTMLElement>('field')\n\nonMounted(async () => {\n await nextTick()\n field.value?.querySelector('input')?.focus()\n})\n\n/** Something written in at least one language: the server says which one it needs. */\nfunction written(): boolean {\n return Object.values(title.value).some((words) => words.trim() !== '')\n}\n\n/** The first line of a refusal under a field that may be named by language: `title.en`. */\nfunction errorOf(name: string): string | undefined {\n const found = Object.keys(errors.value).find((one) => one === name || one.startsWith(`${name}.`))\n\n return found === undefined ? undefined : errors.value[found]?.[0]\n}\n\nasync function submit(): Promise<void> {\n if (!written() || saving.value) return\n\n saving.value = true\n errors.value = {}\n\n try {\n resolve(\n editing && props.place !== null\n ? await api.renamePlace(props.place.key, title.value)\n : await api.createPlace({ key: key.value.trim(), title: title.value }),\n )\n } catch (error) {\n // Refused under its own field: a key that is taken or spelled in a way no template could\n // write, a name missing in the default language.\n errors.value = (error as { body?: { errors?: Record<string, string[]> } }).body?.errors ?? {}\n } finally {\n saving.value = false\n }\n}\n</script>\n\n<template>\n <wx-dialog\n v-model:open=\"open\"\n :title=\"editing ? t('places.rename-title') : t('places.new-title')\"\n :width=\"440\"\n >\n <wx-form-item :label=\"t('places.field-title')\" :error=\"errorOf('title')\" required>\n <div ref=\"field\">\n <wx-input\n v-model=\"title\"\n localized\n :aria-label=\"t('places.field-title')\"\n @keyup.enter=\"submit\"\n />\n </div>\n </wx-form-item>\n\n <wx-form-item\n :label=\"t('places.field-key')\"\n :error=\"errorOf('key')\"\n :help=\"editing ? undefined : t('places.key-help')\"\n required\n >\n <wx-input\n v-model=\"key\"\n :disabled=\"editing\"\n :aria-label=\"t('places.field-key')\"\n autocomplete=\"off\"\n @keyup.enter=\"submit\"\n />\n </wx-form-item>\n\n <template #footer>\n <wx-space size=\"sm\">\n <wx-button variant=\"outline\" @click=\"dismiss()\">{{ t('places.cancel') }}</wx-button>\n <wx-button\n type=\"primary\"\n :loading=\"saving\"\n :disabled=\"!written() || key.trim() === ''\"\n @click=\"submit\"\n >\n {{ editing ? t('places.save') : t('places.create') }}\n </wx-button>\n </wx-space>\n </template>\n </wx-dialog>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\nimport { useRoute, useRouter } from 'vue-router'\nimport {\n confirm,\n toast,\n WxAlert,\n WxBadge,\n WxEmpty,\n WxIcon,\n WxSegmented,\n WxSkeleton,\n WxSortableList,\n WxTooltip,\n type SegmentedValue,\n} from '@webx-ui/core'\nimport {\n useAdmin,\n useErrorText,\n useTranslate,\n WxRowMenu,\n WxScreenHead,\n type RowAction,\n type ScreenAction,\n} from '@webx-ui/module-admin'\nimport { createBannersApi } from './api'\nimport { useBannersMessages } from './i18n'\nimport type { BannerRow, PlaceRow } from './types'\n\n/**\n * The banners of one place: the right-hand half of the section, and the whole screen on a phone.\n *\n * The order here is the order on the site, and a place is small, so the list is the whole place\n * and a drag writes it whole (`reorder`). The bin is the same list turned over: nothing to drag,\n * and a banner in it opens nothing — it can only come back.\n *\n * The way back is drawn here and not by the pane around it: the drawer a narrow screen opens this\n * in has no close of its own on purpose (CLAUDE.md §4).\n */\ndefineOptions({ name: 'WxBannerPlacePane' })\n\nconst props = withDefaults(\n defineProps<{\n place: PlaceRow\n base: string\n /** False in the drawer a narrow screen opens this in. */\n inline?: boolean\n }>(),\n { inline: true },\n)\n\nconst emit = defineEmits<{\n back: []\n /** Something moved in or out of the place: the count beside its name is stale. */\n changed: []\n}>()\n\nconst admin = useAdmin()\nconst api = createBannersApi(admin)\nconst route = useRoute()\nconst router = useRouter()\nuseBannersMessages()\n\nconst t = useTranslate('webx-banners')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\nconst rows = ref<BannerRow[]>([])\nconst loading = ref(true)\n\nconst canManage = computed(() => admin.can('banners.manage'))\n\n/* The bin is in the address with the place, so coming back from a banner lands where one was. */\nconst view = computed<SegmentedValue>({\n get: () => (route.query.view === 'trashed' ? 'trashed' : 'live'),\n set: (value) => {\n void router.replace({\n query: { ...route.query, view: value === 'trashed' ? 'trashed' : undefined },\n })\n },\n})\n\nconst inBin = computed(() => view.value === 'trashed')\n\nconst views = computed(() => [\n { value: 'live', label: t('banner.all') },\n { value: 'trashed', label: t('banner.trashed'), icon: 'trash' as const },\n])\n\nconst sortable = computed(() => canManage.value && !inBin.value && rows.value.length > 1)\n\nconst layout = computed(() => {\n const known = ['single', 'random', 'slider']\n\n return known.includes(props.place.layout) ? t(`places.layout-${props.place.layout}`) : ''\n})\n\nasync function load(): Promise<void> {\n loading.value = true\n\n try {\n rows.value = await api.list(props.place.key, inBin.value)\n } catch (error) {\n toast.danger(message(error))\n } finally {\n loading.value = false\n }\n}\n\nwatch(inBin, () => void load())\nvoid load()\n\nfunction edit(row: BannerRow): string {\n return `${props.base}/${row.id}`\n}\n\nfunction add(): void {\n void router.push({ path: `${props.base}/new`, query: { place: props.place.key } })\n}\n\n/**\n * The order on screen, written whole. The list is already in its new order (`WxSortableList`\n * reorders the model before it says so), so a failure has to put it back rather than leave the\n * screen disagreeing with the database.\n */\nasync function reorder(): Promise<void> {\n try {\n await api.reorder(\n props.place.key,\n rows.value.map((row) => row.id),\n )\n } catch (error) {\n toast.danger(message(error, t('banner.reorder-failed')))\n await load()\n }\n}\n\n/*\n * On and off from the list, through the form's own door: there is no switch of its own in the\n * API, and a second way to write a banner would be a second set of rules to keep in step. What\n * the form would refuse — a look the config dropped since — is refused here the same way.\n */\nasync function toggle(row: BannerRow): Promise<void> {\n try {\n const detail = await api.get(row.id)\n\n await api.save(row.id, { ...detail.values, enabled: !row.enabled })\n toast.success(row.enabled ? t('banner.switched-off') : t('banner.switched-on'))\n } catch (error) {\n toast.danger(message(error))\n } finally {\n await load()\n }\n}\n\nasync function remove(row: BannerRow): Promise<void> {\n const agreed = await confirm({\n title: t('banner.delete-title', { name: row.title }),\n message: t('banner.delete-text'),\n confirmText: t('banner.delete'),\n cancelText: t('banner.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n try {\n await api.remove(row.id)\n toast.success(t('banner.deleted'))\n emit('changed')\n } catch (error) {\n toast.danger(message(error))\n } finally {\n await load()\n }\n}\n\nasync function restore(row: BannerRow): Promise<void> {\n try {\n await api.restore(row.id)\n toast.success(t('banner.restored'))\n emit('changed')\n } catch (error) {\n toast.danger(message(error))\n } finally {\n await load()\n }\n}\n\nfunction actionsFor(row: BannerRow): RowAction[] {\n if (inBin.value) {\n return canManage.value\n ? [\n {\n key: 'restore',\n icon: 'refresh',\n label: t('banner.restore'),\n run: () => void restore(row),\n },\n ]\n : []\n }\n\n const open: RowAction = {\n key: 'open',\n icon: 'edit',\n label: t('banner.open'),\n run: () => void router.push(edit(row)),\n }\n\n if (!canManage.value) return [open]\n\n return [\n open,\n {\n key: 'toggle',\n icon: row.enabled ? 'eye-off' : 'eye',\n label: row.enabled ? t('banner.disable') : t('banner.enable'),\n run: () => void toggle(row),\n },\n {\n key: 'delete',\n icon: 'trash',\n label: t('banner.delete'),\n danger: true,\n run: () => void remove(row),\n },\n ]\n}\n\n/* The one thing this pane exists for, in its head; on a narrow pane it stays a button. */\nconst actions = computed<ScreenAction[]>(() =>\n canManage.value && !inBin.value\n ? [{ key: 'new', label: t('banner.new'), icon: 'plus', primary: true, run: add }]\n : [],\n)\n</script>\n\n<template>\n <div class=\"wx-banner-place\">\n <wx-screen-head\n :level=\"3\"\n :back=\"!props.inline\"\n :back-label=\"t('places.places')\"\n :title=\"place.title\"\n :actions=\"actions\"\n :collapse-below=\"420\"\n @back=\"emit('back')\"\n >\n <template v-if=\"place.declared\" #title-after>\n <wx-tooltip :content=\"t('places.declared-help')\">\n <span class=\"wx-banner-place__lock\" tabindex=\"0\" :aria-label=\"t('places.declared')\">\n <wx-icon name=\"lock\" size=\"sm\" />\n </span>\n </wx-tooltip>\n </template>\n </wx-screen-head>\n\n <!--\n How a template asks for this place, where somebody writing one will look for it: the key\n is the whole contract between the panel and the site (decision 7).\n -->\n <p class=\"wx-banner-place__usage\">\n <span>{{ t('places.usage') }}</span>\n <code>banners('{{ place.key }}')</code>\n <span v-if=\"layout\" class=\"wx-banner-place__layout\">· {{ layout }}</span>\n </p>\n\n <wx-segmented\n v-model=\"view\"\n class=\"wx-banner-place__views\"\n size=\"sm\"\n :options=\"views\"\n :aria-label=\"t('banner.all')\"\n />\n\n <wx-skeleton v-if=\"loading && rows.length === 0\" :rows=\"4\" />\n\n <wx-empty\n v-else-if=\"rows.length === 0\"\n :title=\"inBin ? t('banner.empty-bin') : t('banner.empty')\"\n :description=\"inBin ? undefined : t('banner.empty-help')\"\n />\n\n <template v-else>\n <!-- A grip and not the whole row: the row opens a banner, and a row that both opens and\n drags is one where one of the two happens by accident. -->\n <wx-sortable-list\n v-model=\"rows\"\n class=\"wx-banner-place__list\"\n :class=\"{ 'is-loading': loading }\"\n plain\n item-key=\"id\"\n :handle=\"sortable ? 'grip' : 'row'\"\n :item-label=\"(item: BannerRow) => item.title\"\n :disabled=\"!sortable\"\n @move=\"reorder\"\n >\n <template #default=\"{ item }\">\n <component\n :is=\"inBin ? 'div' : 'router-link'\"\n :to=\"inBin ? undefined : edit(item)\"\n class=\"wx-banner-row\"\n :class=\"{ 'is-off': !item.enabled && !inBin }\"\n >\n <span class=\"wx-banner-row__thumb\">\n <img v-if=\"item.thumb\" :src=\"item.thumb\" alt=\"\" loading=\"lazy\" />\n <wx-icon v-else name=\"image\" size=\"sm\" :aria-label=\"t('banner.no-picture')\" />\n </span>\n\n <span class=\"wx-banner-row__body\">\n <span class=\"wx-banner-row__title\">{{ item.title }}</span>\n\n <span v-if=\"item.video || (!item.enabled && !inBin)\" class=\"wx-banner-row__facts\">\n <wx-badge v-if=\"item.video\" size=\"sm\" type=\"info\">{{ t('banner.video') }}</wx-badge>\n <wx-badge v-if=\"!item.enabled && !inBin\" dot size=\"sm\">{{\n t('banner.disabled')\n }}</wx-badge>\n </span>\n </span>\n </component>\n </template>\n\n <template #actions=\"{ item }\">\n <wx-row-menu :actions=\"actionsFor(item)\" :label=\"item.title\" />\n </template>\n </wx-sortable-list>\n\n <!-- What a drag does here, said once under the list. -->\n <wx-alert\n v-if=\"sortable\"\n class=\"wx-banner-place__note\"\n type=\"info\"\n :description=\"t('banner.order')\"\n />\n </template>\n </div>\n</template>\n\n<style scoped>\n.wx-banner-place {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-12);\n min-width: 0;\n padding: var(--wx-gap, var(--wx-space-16));\n}\n\n.wx-banner-place__lock {\n display: inline-flex;\n color: var(--wx-text-muted);\n}\n\n.wx-banner-place__usage {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--wx-space-6);\n margin: 0;\n color: var(--wx-text-muted);\n font-size: var(--wx-font-size-sm);\n}\n\n.wx-banner-place__usage code {\n padding: 0 var(--wx-space-4);\n border-radius: var(--wx-radius-xs);\n background: var(--wx-bg-subtle);\n color: var(--wx-text-default);\n font-family: var(--wx-font-family-mono);\n /* Copied whole, as somebody writing a template would: a click selects all of it. */\n user-select: all;\n}\n\n.wx-banner-place__views {\n align-self: flex-start;\n}\n\n.wx-banner-place__list.is-loading {\n opacity: 0.6;\n}\n\n.wx-banner-place__list :deep(.wx-sortable-list__row) {\n gap: var(--wx-space-4);\n padding-inline: var(--wx-space-4);\n border-radius: var(--wx-radius-sm);\n}\n\n.wx-banner-place__list :deep(.wx-sortable-list__row:hover) {\n background: var(--wx-bg-subtle);\n}\n\n.wx-banner-row {\n display: flex;\n align-items: center;\n gap: var(--wx-space-12);\n /* The row is the target: a short title should not leave half the line dead. */\n flex: 1 1 auto;\n width: 100%;\n min-width: 0;\n padding-block: var(--wx-space-8);\n color: inherit;\n text-decoration: none;\n}\n\n/* Off is not gone: still here, still in its place, just quieter than what is on the site. */\n.wx-banner-row.is-off .wx-banner-row__thumb,\n.wx-banner-row.is-off .wx-banner-row__title {\n opacity: 0.55;\n}\n\n.wx-banner-row__thumb {\n display: flex;\n flex: none;\n align-items: center;\n justify-content: center;\n width: 80px;\n aspect-ratio: 16 / 9;\n overflow: hidden;\n border-radius: var(--wx-radius-xs);\n background: var(--wx-bg-fill);\n color: var(--wx-text-muted);\n}\n\n.wx-banner-row__thumb img {\n width: 100%;\n height: 100%;\n /* A thumbnail wider than its frame would otherwise hold the row open (CLAUDE.md §4). */\n min-width: 0;\n object-fit: cover;\n}\n\n.wx-banner-row__body {\n display: flex;\n flex: 1 1 auto;\n flex-direction: column;\n gap: var(--wx-space-4);\n min-width: 0;\n}\n\n.wx-banner-row__title {\n min-width: 0;\n overflow: hidden;\n font-weight: var(--wx-font-weight-medium);\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\na.wx-banner-row:hover .wx-banner-row__title {\n color: var(--wx-color-primary);\n}\n\n.wx-banner-row__facts {\n display: flex;\n flex-wrap: wrap;\n gap: var(--wx-space-4);\n}\n\n.wx-banner-place__note {\n margin-top: var(--wx-space-4);\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\nimport { useRoute, useRouter, type LocationQueryRaw } from 'vue-router'\nimport {\n confirm,\n createModal,\n toast,\n WxCard,\n WxEmpty,\n WxIcon,\n WxIndicator,\n WxListDetail,\n WxSkeleton,\n WxText,\n} from '@webx-ui/core'\nimport {\n useAdmin,\n useErrorText,\n useTranslate,\n WxListScreen,\n WxRowMenu,\n type RowAction,\n type ScreenAction,\n} from '@webx-ui/module-admin'\nimport { createBannersApi } from './api'\nimport { useBannersMessages } from './i18n'\nimport PlaceDialog from './PlaceDialog.vue'\nimport PlacePane from './PlacePane.vue'\nimport type { PlaceRow } from './types'\n\n/**\n * The section: the places of the site on the left, the banners of one of them on the right\n * (§5.4) — the shape of the menus, for the same reason: somebody who opens this is arranging one\n * place, not choosing between them, so the first one opens by itself where there is room for it.\n * On a phone it does not: there the same line would raise a panel over a list nobody touched.\n *\n * A declared place is on the list before anything was ever saved into it (decision 1) — its row\n * in the table is made by its first banner — so places are named by key throughout, not by id.\n */\nconst props = withDefaults(defineProps<{ base?: string }>(), { base: '/banners' })\n\nconst admin = useAdmin()\nconst api = createBannersApi(admin)\nconst route = useRoute()\nconst router = useRouter()\nuseBannersMessages()\n\nconst t = useTranslate('webx-banners')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\nconst places = ref<PlaceRow[]>([])\nconst loading = ref(true)\nconst open = ref(false)\nconst detailInline = ref(true)\n\nconst canManage = computed(() => admin.can('banners.manage'))\n\nconst edit = createModal<PlaceRow, { place?: PlaceRow | null }>(PlaceDialog)\n\nconst title = computed(\n () =>\n admin.state.manifest?.modules.find((module) => module.id === 'banners')?.title ??\n t('module.banners'),\n)\n\n/** Which place is open, kept in the address — so \"back\" from a banner lands on its place. */\nconst current = computed<string | null>(() => {\n const asked = route.query.place\n\n return typeof asked === 'string' && asked !== '' ? asked : null\n})\n\nconst chosen = computed(() => places.value.find((place) => place.key === current.value) ?? null)\n\nasync function load(): Promise<void> {\n loading.value = true\n\n try {\n places.value = await api.places()\n } catch (error) {\n toast.danger(message(error))\n } finally {\n loading.value = false\n }\n}\n\nvoid load()\n\n/*\n * The first place, where there is a column to draw it in — only once the width is measured\n * (`detail-inline` says so), so a phone does not get a drawer over a list it never touched.\n */\nwatch([places, chosen, detailInline], () => {\n if (!detailInline.value || places.value.length === 0 || chosen.value !== null) return\n\n void router.replace({ query: { ...route.query, place: places.value[0]!.key } })\n})\n\nwatch(chosen, (place) => {\n if (place !== null && detailInline.value) open.value = true\n})\n\n/* Coming back from a banner on a phone lands on its place, and that is the drawer, open. */\nif (current.value !== null) open.value = true\n\nfunction choose(place: PlaceRow): void {\n const query: LocationQueryRaw = { ...route.query, place: place.key }\n\n // The bin of one place is not the bin of the next.\n delete query.view\n void router.replace({ query })\n open.value = true\n}\n\nasync function add(): Promise<void> {\n const made = await edit({ place: null })\n\n if (!made) return\n\n toast.success(t('places.created'))\n await load()\n choose(made)\n}\n\nasync function rename(place: PlaceRow): Promise<void> {\n const saved = await edit({ place })\n\n if (!saved) return\n\n toast.success(t('places.renamed'))\n await load()\n}\n\nasync function remove(place: PlaceRow): Promise<void> {\n const agreed = await confirm({\n title: t('places.delete-title', { place: place.title }),\n message: t('places.delete-text'),\n confirmText: t('places.delete'),\n cancelText: t('places.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n try {\n await api.removePlace(place.key)\n toast.success(t('places.deleted'))\n\n if (current.value === place.key) {\n const rest = { ...route.query }\n\n delete rest.place\n delete rest.view\n void router.replace({ query: rest })\n open.value = false\n }\n } catch (error) {\n // The one refusal worth its own words: something is still in it — perhaps only in the bin,\n // which the count beside the name does not show (§5.6).\n toast.danger(message(error))\n } finally {\n await load()\n }\n}\n\nfunction actionsFor(place: PlaceRow): RowAction[] {\n if (!canManage.value || place.declared) return []\n\n return [\n { key: 'rename', icon: 'edit', label: t('places.rename'), run: () => void rename(place) },\n {\n key: 'delete',\n icon: 'trash',\n // Said in the line itself, since a switched-off line cannot carry a tooltip: an empty\n // place is the only kind that goes (decision 1).\n label: place.count > 0 ? t('places.delete-empty-first') : t('places.delete'),\n danger: true,\n disabled: place.count > 0,\n run: () => void remove(place),\n },\n ]\n}\n\nconst actions = computed<ScreenAction[]>(() =>\n canManage.value\n ? [{ key: 'new-place', label: t('places.new'), icon: 'plus', run: () => void add() }]\n : [],\n)\n</script>\n\n<template>\n <wx-list-screen :title=\"title\" :actions=\"actions\" :card=\"false\">\n <wx-card class=\"wx-banners\" padding=\"none\">\n <wx-list-detail\n v-model:open=\"open\"\n class=\"wx-banners__panes\"\n :list-width=\"300\"\n :detail-min=\"440\"\n :detail-label=\"t('places.places')\"\n @detail-inline=\"detailInline = $event\"\n >\n <template #list>\n <wx-skeleton\n v-if=\"loading && places.length === 0\"\n class=\"wx-banners__loading\"\n :rows=\"3\"\n />\n\n <wx-empty\n v-else-if=\"places.length === 0\"\n :title=\"t('places.empty')\"\n :description=\"t('places.empty-help')\"\n />\n\n <ul v-else class=\"wx-banners__list\">\n <li v-for=\"place in places\" :key=\"place.key\" class=\"wx-banners__row\">\n <button\n type=\"button\"\n class=\"wx-banners__open\"\n :class=\"{ 'is-current': place.key === current }\"\n @click=\"choose(place)\"\n >\n <span class=\"wx-banners__name\">\n <wx-text truncate weight=\"medium\">{{ place.title }}</wx-text>\n <wx-icon\n v-if=\"place.declared\"\n class=\"wx-banners__lock\"\n name=\"lock\"\n size=\"sm\"\n :aria-label=\"t('places.declared')\"\n />\n <wx-indicator\n class=\"wx-banners__count\"\n type=\"neutral\"\n :value=\"place.count\"\n :label=\"t('places.count')\"\n />\n </span>\n\n <wx-text size=\"sm\" tone=\"muted\" mono truncate>banners('{{ place.key }}')</wx-text>\n </button>\n\n <wx-row-menu\n v-if=\"actionsFor(place).length > 0\"\n :actions=\"actionsFor(place)\"\n :label=\"place.title\"\n />\n </li>\n </ul>\n </template>\n\n <template #detail=\"{ inline, back }\">\n <place-pane\n v-if=\"chosen\"\n :key=\"chosen.key\"\n :place=\"chosen\"\n :base=\"props.base\"\n :inline=\"inline\"\n @back=\"back\"\n @changed=\"load\"\n />\n </template>\n\n <template #empty>\n <wx-empty :title=\"t('places.choose')\" :description=\"t('places.choose-help')\" />\n </template>\n </wx-list-detail>\n </wx-card>\n </wx-list-screen>\n</template>\n\n<style>\n/*\n * What scrolls here is the page, the way it does on every other list of the panel. `clip`, not\n * `hidden`: `hidden` would make this a scroll container, and everything sticky inside would pin\n * itself to a box that never moves (CLAUDE.md §4).\n */\n.wx-banners > .wx-card__body {\n display: flex;\n flex-direction: column;\n overflow: clip;\n border-radius: inherit;\n}\n</style>\n\n<style scoped>\n.wx-banners__loading {\n padding: var(--wx-space-16);\n}\n\n.wx-banners__list {\n margin: 0;\n padding: var(--wx-space-8);\n list-style: none;\n}\n\n.wx-banners__row {\n display: flex;\n align-items: center;\n gap: var(--wx-space-4);\n margin: 0;\n padding-inline: var(--wx-space-8);\n border-radius: var(--wx-radius-sm);\n}\n\n.wx-banners__row:has(.is-current) {\n background: var(--wx-bg-subtle);\n}\n\n.wx-banners__open {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: var(--wx-space-2);\n flex: 1 1 auto;\n min-width: 0;\n padding-block: var(--wx-space-8);\n padding-inline: 0;\n border: 0;\n background: none;\n font: inherit;\n color: inherit;\n text-align: start;\n cursor: pointer;\n}\n\n.wx-banners__open.is-current {\n color: var(--wx-color-primary);\n}\n\n.wx-banners__name {\n display: flex;\n align-items: center;\n gap: var(--wx-space-6);\n min-width: 0;\n max-width: 100%;\n}\n\n.wx-banners__lock {\n flex: none;\n color: var(--wx-text-muted);\n}\n\n.wx-banners__count {\n flex: 0 0 auto;\n}\n</style>\n","import type { AdminModule } from '@webx-ui/module-admin'\nimport BannerEditorPage from './BannerEditorPage.vue'\nimport BannersPage from './BannersPage.vue'\n\nexport interface BannersOptions {\n /** Where the banners live inside the panel. */\n path?: string\n}\n\n/**\n * Banners as a section of the panel (§5.4): one module and one entry, without a group.\n *\n * Two screens. The places and the banners of one of them are a list and its detail, the way the\n * menus are; a banner is a page of its own, because its form is long — three pictures, the words\n * and up to three buttons — and a pane beside a list is no room for it. A section whose server\n * half is not installed never appears — the entry is built from the manifest.\n */\nexport function banners(options: BannersOptions = {}): AdminModule {\n const path = options.path ?? '/banners'\n\n return {\n id: 'banners',\n path,\n routes: [\n // The open place is in the address (`?place=`), so a link to it is a link to its banners.\n { path, name: 'webx.banners', component: BannersPage, props: { base: path } },\n // A banner not written yet: the place it goes to rides in the query (`?place=hero`).\n {\n path: `${path}/new`,\n name: 'webx.banners.new',\n component: BannerEditorPage,\n props: { base: path },\n },\n {\n path: `${path}/:id(\\\\d+)`,\n name: 'webx.banners.edit',\n component: BannerEditorPage,\n props: { base: path },\n },\n ],\n }\n}\n"],"names":["createBannersApi","admin","base","place","key","data","body","input","title","trashed","id","values","ids","bannersMessages","seeded","useBannersMessages","i18n","useAdmin","props","__props","api","route","useRoute","router","useRouter","locales","useLocales","t","useTranslate","message","useErrorText","computed","raw","banner","ref","snapshot","placeSaved","places","loading","saving","errors","canManage","current","dirty","state","written","fallback","localizedValue","back","section","module","placeOptions","one","placeModel","value","blank","take","detail","load","list","asked","fresh","error","toast","save","made","remove","record","confirm","leaveGuard","event","onKeydown","useBodyKeys","watch","next","onMounted","onBeforeUnmount","onBeforeRouteLeave","actions","_createElementBlock","_Fragment","_createVNode","_unref","WxSkeleton","WxCard","WxScreenHead","WxBadge","WxFormItem","WxSelect","$event","WxScreen","_createBlock","WxActionBar","WxSaveState","WxButton","open","resolve","dismiss","useModal","editing","initial","first","field","useTemplateRef","nextTick","words","errorOf","name","found","submit","WxDialog","WxSpace","_createElementVNode","WxInput","emit","__emit","rows","view","inBin","views","sortable","layout","edit","row","add","reorder","toggle","restore","actionsFor","_openBlock","_hoisted_1","WxTooltip","WxIcon","_hoisted_3","_toDisplayString","_hoisted_4","WxSegmented","WxEmpty","WxSortableList","_normalizeClass","item","_withCtx","_resolveDynamicComponent","_hoisted_5","_hoisted_7","_hoisted_8","_hoisted_9","WxRowMenu","WxAlert","detailInline","createModal","PlaceDialog","chosen","choose","query","rename","rest","WxListScreen","WxListDetail","_cache","_renderList","WxText","_createTextVNode","WxIndicator","inline","PlacePane","banners","options","path","BannersPage","BannerEditorPage"],"mappings":";;;;AAiCO,SAASA,EAAiBC,GAAiC;AAChE,QAAMC,IAAO,GAAGD,EAAM,OAAO,YACvBE,IAAQ,CAACC,MAAgB,GAAGF,CAAI,WAAW,mBAAmBE,CAAG,CAAC,IAClEC,IAAO,CAAIC,MAAyBA,EAAK;AAE/C,SAAO;AAAA,IACL,QAAQ,MAAML,EAAM,KAAK,IAA0B,GAAGC,CAAI,SAAS,EAAE,KAAKG,CAAI;AAAA,IAC9E,aAAa,CAACE,MAAUN,EAAM,KAAK,KAAyB,GAAGC,CAAI,WAAWK,CAAK,EAAE,KAAKF,CAAI;AAAA,IAC9F,aAAa,CAACD,GAAKI,MACjBP,EAAM,KAAK,IAAwBE,EAAMC,CAAG,GAAG,EAAE,OAAAI,EAAA,CAAO,EAAE,KAAKH,CAAI;AAAA,IACrE,aAAa,CAACD,MAAQH,EAAM,KAAK,OAAaE,EAAMC,CAAG,CAAC,EAAE,KAAK,MAAA;AAAA,KAAe;AAAA,IAE9E,MAAM,CAACA,GAAKK,IAAU,OACpBR,EAAM,KACH,IAA2B,GAAGE,EAAMC,CAAG,CAAC,WAAWK,IAAU,eAAe,EAAE,EAAE,EAChF,KAAKJ,CAAI;AAAA,IACd,KAAK,CAACK,MAAOT,EAAM,KAAK,IAA4B,GAAGC,CAAI,IAAIQ,CAAE,EAAE,EAAE,KAAKL,CAAI;AAAA,IAC9E,QAAQ,CAACD,GAAKO,MACZV,EAAM,KAAK,KAA6B,GAAGE,EAAMC,CAAG,CAAC,YAAY,EAAE,QAAAO,GAAQ,EAAE,KAAKN,CAAI;AAAA,IACxF,MAAM,CAACK,GAAIC,GAAQP,MACjBH,EAAM,KACH;AAAA,MACC,GAAGC,CAAI,IAAIQ,CAAE;AAAA,MACbN,MAAQ,SAAY,EAAE,QAAAO,EAAA,IAAW,EAAE,QAAAA,GAAQ,OAAOP,EAAA;AAAA,IAAI,EAEvD,KAAKC,CAAI;AAAA,IACd,QAAQ,CAACK,MAAOT,EAAM,KAAK,OAAa,GAAGC,CAAI,IAAIQ,CAAE,EAAE,EAAE,KAAK,MAAA;AAAA,KAAe;AAAA;AAAA,IAE7E,SAAS,CAACA,MAAOT,EAAM,KAAK,KAA0B,GAAGC,CAAI,IAAIQ,CAAE,YAAY,CAAA,CAAE,EAAE,KAAKL,CAAI;AAAA,IAC5F,SAAS,CAACD,GAAKQ,MACbX,EAAM,KAAK,KAAW,GAAGE,EAAMC,CAAG,CAAC,YAAY,EAAE,KAAAQ,EAAA,CAAK,EAAE,KAAK;KAAe;AAAA,EAAA;AAElF;ACzDO,MAAMC,KAA4C;AAAA,EACvD,QAAQ;AAAA,IACN,SAAS;AAAA,EAAA;AAAA,EAEX,QAAQ;AAAA,IACN,MAAM;AAAA,IACN,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,KAAK;AAAA,IACL,OAAO;AAAA,IACP,KAAK;AAAA,IACL,YACE;AAAA,IACF,UAAU;AAAA,IACV,eAAe;AAAA,IACf,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,kBAAkB;AAAA,IAClB,oBAAoB;AAAA,IACpB,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,SAAS;AAAA,IACT,SAAS;AAAA,IACT,QAAQ;AAAA,IACR,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,eACE;AAAA,IACF,sBAAsB;AAAA,IACtB,kBAAkB;AAAA,IAClB,eAAe;AAAA,IACf,aAAa;AAAA,IACb,iBAAiB;AAAA,IACjB,OAAO;AAAA,IACP,iBAAiB;AAAA,IACjB,iBAAiB;AAAA,IACjB,iBAAiB;AAAA,IACjB,cAAc;AAAA,IACd,eAAe;AAAA,EAAA;AAAA,EAEjB,QAAQ;AAAA,IACN,KAAK;AAAA,IACL,OAAO;AAAA,IACP,YAAY;AAAA,IACZ,SAAS;AAAA,IACT,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,UAAU;AAAA,IACV,OAAO;AAAA,IACP,OAAO;AAAA,IACP,eAAe;AAAA,IACf,OAAO;AAAA,IACP,cAAc;AAAA,IACd,MAAM;AAAA,IACN,MAAM;AAAA,IACN,OAAO;AAAA,IACP,SAAS;AAAA,IACT,UAAU;AAAA,IACV,eAAe;AAAA,IACf,cAAc;AAAA,IACd,OAAO;AAAA,IACP,MAAM;AAAA,IACN,SAAS;AAAA,IACT,UAAU;AAAA,IACV,KAAK;AAAA,IACL,cAAc;AAAA,IACd,aAAa;AAAA,IACb,cAAc;AAAA,IACd,OAAO;AAAA,IACP,eAAe;AAAA,IACf,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,gBAAgB;AAAA,IAChB,eAAe;AAAA,IACf,SAAS;AAAA,IACT,kBAAkB;AAAA,IAClB,eAAe;AAAA,IACf,QAAQ;AAAA,EAAA;AAAA,EAEV,QAAQ;AAAA,IACN,OAAO;AAAA,IACP,OAAO;AAAA,IACP,cAAc;AAAA,IACd,gBAAgB;AAAA,IAChB,qBAAqB;AAAA,IACrB,OAAO;AAAA,IACP,cACE;AAAA,IACF,OAAO;AAAA,IACP,OAAO;AAAA,IACP,cACE;AAAA,IACF,MAAM;AAAA,IACN,aAAa;AAAA,IACb,SAAS;AAAA,IACT,gBAAgB;AAAA,IAChB,eAAe;AAAA,IACf,iBAAiB;AAAA,IACjB,OAAO;AAAA,IACP,MAAM;AAAA,IACN,SAAS;AAAA,IACT,uBAAuB;AAAA,IACvB,UAAU;AAAA,IACV,SAAS;AAAA,IACT,gBAAgB;AAAA,EAAA;AAEpB,GCvHMC,yBAAa,QAAA;AAGZ,SAASC,KAA2B;AACzC,MAAI;AACF,UAAM,EAAE,MAAAC,EAAA,IAASC,EAAA;AAEjB,IAAKH,GAAO,IAAIE,CAAI,MAClBA,EAAK,SAAS,gBAAgBH,EAAe,GAC7CC,GAAO,IAAIE,CAAI;AAAA,EAEnB,QAAQ;AAAA,EAER;AACF;;;;;;;;ACiCA,UAAME,IAAQC,GAERlB,IAAQgB,EAAA,GACRG,IAAMpB,EAAiBC,CAAK,GAC5BoB,IAAQC,GAAA,GACRC,IAASC,GAAA,GACTC,IAAUC,GAAA;AAChB,IAAAX,GAAA;AAEA,UAAMY,IAAIC,EAAa,cAAc,GAE/BC,IAAUC,GAAA,GAGVpB,IAAKqB,EAAwB,MAAM;AACvC,YAAMC,IAAMX,EAAM,OAAO;AAEzB,aAAO,OAAOW,KAAQ,YAAYA,MAAQ,KAAK,OAAOA,CAAG,IAAI;AAAA,IAC/D,CAAC,GAEKC,IAASC,EAA0B,IAAI,GACvCvB,IAASuB,EAAiB,EAAE,GAC5BC,IAAWD,EAAI,EAAE,GACjB/B,IAAQ+B,EAAmB,IAAI,GAE/BE,IAAaF,EAAmB,IAAI,GACpCG,IAASH,EAAgB,EAAE,GAE3BI,IAAUJ,EAAI,EAAI,GAClBK,IAASL,EAAI,EAAK,GAClBM,IAASN,EAA8B,EAAE,GAEzCO,IAAYV,EAAS,MAAM9B,EAAM,IAAI,gBAAgB,CAAC,GAEtDyC,IAAUX,EAAS,MAAM,KAAK,UAAUpB,EAAO,KAAK,CAAC,GACrDgC,IAAQZ;AAAA,MACZ,MACEI,EAAS,UAAU,OAAOO,EAAQ,UAAUP,EAAS,SAAShC,EAAM,UAAUiC,EAAW;AAAA,IAAA,GAGvFQ,IAAQb,EAAyC,MACjDQ,EAAO,QAAc,WAElBI,EAAM,SAASjC,EAAG,UAAU,OAAO,YAAY,OACvD,GAGKF,IAAQuB,EAAS,MAAM;AAC3B,YAAMc,IAAUlC,EAAO,MAAM,OACvBmC,IAAWpC,EAAG,UAAU,OAAOiB,EAAE,YAAY,IAAKM,EAAO,OAAO,SAAS;AAE/E,aAAOc,GAAeF,GAASpB,EAAQ,OAAO,OAAOqB,CAAQ,KAAKnB,EAAE,iBAAiB;AAAA,IACvF,CAAC,GAGKqB,IAAOjB,EAAS,MAAM;AAC1B,YAAM3B,IAAMgC,EAAW,SAASjC,EAAM;AAEtC,aAAOC,MAAQ,OAAOc,EAAM,OAAO,GAAGA,EAAM,IAAI,UAAU,mBAAmBd,CAAG,CAAC;AAAA,IACnF,CAAC,GAEK6C,IAAUlB;AAAA,MACd,MACE9B,EAAM,MAAM,UAAU,QAAQ,KAAK,CAACiD,MAAWA,EAAO,OAAO,SAAS,GAAG,SACzEvB,EAAE,gBAAgB;AAAA,IAAA,GAGhBwB,IAAepB;AAAA,MAAS,MAC5BM,EAAO,MAAM,IAAI,CAACe,OAAS,EAAE,OAAOA,EAAI,KAAK,OAAO,GAAGA,EAAI,KAAK,MAAMA,EAAI,GAAG,KAAK;AAAA,IAAA,GAG9EC,IAAatB,EAAiC;AAAA,MAClD,KAAK,MAAM5B,EAAM;AAAA,MACjB,KAAK,CAACmD,MAAU;AACd,QAAAnD,EAAM,QAAQmD,KAAU,OAA8B,OAAO,OAAOA,CAAK;AAAA,MAC3E;AAAA,IAAA,CACD;AAGD,aAASC,IAAqB;AAC5B,aAAO;AAAA,QACL,OAAO;AAAA,QACP,cAAc;AAAA,QACd,OAAO;AAAA,QACP,OAAO,CAAA;AAAA,QACP,MAAM,CAAA;AAAA,QACN,SAAS,CAAA;AAAA,QACT,SAAS;AAAA,MAAA;AAAA,IAEb;AAEA,aAASC,EAAKC,GAA4B;AACxC,MAAAxB,EAAO,QAAQwB,EAAO,QACtB9C,EAAO,QAAQ8C,EAAO,QACtBtB,EAAS,QAAQ,KAAK,UAAUsB,EAAO,MAAM,GAC7CtD,EAAM,QAAQsD,EAAO,OAAO,OAC5BrB,EAAW,QAAQqB,EAAO,OAAO;AAAA,IACnC;AAEA,mBAAeC,KAAsB;AACnC,MAAApB,EAAQ,QAAQ;AAEhB,UAAI;AACF,cAAM,CAACqB,GAAMF,CAAM,IAAI,MAAM,QAAQ,IAAI;AAAA,UACvCrC,EAAI,OAAA;AAAA,UACJV,EAAG,UAAU,OAAO,QAAQ,QAAQ,IAAI,IAAIU,EAAI,IAAIV,EAAG,KAAK;AAAA,QAAA,CAC7D;AAID,YAFA2B,EAAO,QAAQsB,GAEXF,MAAW;AACb,UAAAD,EAAKC,CAAM;AAAA,aACN;AACL,gBAAMG,IAAQvC,EAAM,MAAM,OACpBjB,KAAM,OAAOwD,KAAU,YAAYD,EAAK,KAAK,CAACP,OAAQA,GAAI,QAAQQ,CAAK,IAAIA,IAAQ,MACnFC,KAAQN,EAAA;AAEd,UAAAtB,EAAO,QAAQ,MACftB,EAAO,QAAQkD,IACf1B,EAAS,QAAQ,KAAK,UAAU0B,EAAK,GACrC1D,EAAM,QAAQC,MAAOuD,EAAK,CAAC,GAAG,OAAO,MACrCvB,EAAW,QAAQjC,EAAM;AAAA,QAC3B;AAAA,MACF,SAAS2D,GAAO;AACd,QAAAC,EAAM,OAAOlC,EAAQiC,CAAK,CAAC,GACtBvC,EAAO,KAAKL,EAAM,IAAI;AAAA,MAC7B,UAAA;AACE,QAAAoB,EAAQ,QAAQ;AAAA,MAClB;AAAA,IACF;AAEA,mBAAe0B,KAAsB;AACnC,UAAI,EAAAzB,EAAO,SAAS,CAACE,EAAU,SAAStC,EAAM,UAAU,OAExD;AAAA,QAAAoC,EAAO,QAAQ,IACfC,EAAO,QAAQ,CAAA;AAEf,YAAI;AACF,gBAAMyB,IAAOvD,EAAG,UAAU,MACpB+C,IAASQ,IACX,MAAM7C,EAAI,OAAOjB,EAAM,OAAOQ,EAAO,KAAK,IAC1C,MAAMS,EAAI;AAAA,YACRV,EAAG;AAAA,YACHC,EAAO;AAAA,YACPR,EAAM,UAAUiC,EAAW,QAAQjC,EAAM,QAAQ;AAAA,UAAA;AAGvD,UAAAqD,EAAKC,CAAM,GACXM,EAAM,QAAQpC,EAAE,cAAc,CAAC,GAG3BsC,KAAW1C,EAAO,QAAQ,GAAGL,EAAM,IAAI,IAAIuC,EAAO,OAAO,EAAE,EAAE;AAAA,QACnE,SAASK,GAAO;AACd,gBAAMxD,IAAQwD,EAA2D;AAEzE,UAAIxD,GAAM,UACRkC,EAAO,QAAQlC,EAAK,QACpByD,EAAM,OAAOpC,EAAE,oBAAoB,CAAC,KAEpCoC,EAAM,OAAOlC,EAAQiC,CAAK,CAAC;AAAA,QAE/B,UAAA;AACE,UAAAvB,EAAO,QAAQ;AAAA,QACjB;AAAA;AAAA,IACF;AAEA,mBAAe2B,KAAwB;AACrC,YAAMC,IAASlC,EAAO;AAYtB,UAVI,GAACkC,KAUD,CARW,MAAMC,EAAQ;AAAA,QAC3B,OAAOzC,EAAE,uBAAuB,EAAE,MAAMwC,EAAO,OAAO;AAAA,QACtD,SAASxC,EAAE,oBAAoB;AAAA,QAC/B,aAAaA,EAAE,eAAe;AAAA,QAC9B,YAAYA,EAAE,eAAe;AAAA,QAC7B,MAAM;AAAA,MAAA,CACP;AAID,YAAI;AACF,gBAAMP,EAAI,OAAO+C,EAAO,EAAE,GAC1BJ,EAAM,QAAQpC,EAAE,gBAAgB,CAAC,GAGjCQ,EAAS,QAAQO,EAAQ,OACzBvC,EAAM,QAAQiC,EAAW,OACpBb,EAAO,KAAKyB,EAAK,KAAK;AAAA,QAC7B,SAASc,GAAO;AACd,UAAAC,EAAM,OAAOlC,EAAQiC,CAAK,CAAC;AAAA,QAC7B;AAAA,IACF;AAGA,aAASO,GAAWC,GAAgC;AAClD,MAAI3B,EAAM,SAAO2B,EAAM,eAAA;AAAA,IACzB;AAGA,aAASC,GAAUD,GAA4B;AAC7C,OAAKA,EAAM,WAAWA,EAAM,YAAYA,EAAM,IAAI,YAAA,MAAkB,QAClEA,EAAM,eAAA,IACF3B,EAAM,SAASjC,EAAG,UAAU,SAAWsD,GAAA;AAAA,IAE/C;AAGA,IAAAQ,GAAYD,EAAS,GAOrBE,EAAM/D,GAAI,CAACgE,MAAS;AAClB,MAAIA,MAAS,QAAQzC,EAAO,OAAO,OAAOyC,KAErChB,GAAA;AAAA,IACP,CAAC,GAEDiB,GAAU,MAAM;AACd,MAAKjB,GAAA,GACL,OAAO,iBAAiB,gBAAgBW,EAAU;AAAA,IACpD,CAAC,GAEDO,GAAgB,MAAM;AACpB,aAAO,oBAAoB,gBAAgBP,EAAU;AAAA,IACvD,CAAC,GAEDQ,GAAmB,YACb,CAAClC,EAAM,SAAS,CAACF,EAAU,QAAc,KAEtC,MAAM2B,EAAQ;AAAA,MACnB,OAAOzC,EAAE,oBAAoB;AAAA,MAC7B,SAASA,EAAE,mBAAmB;AAAA,MAC9B,aAAaA,EAAE,cAAc;AAAA,MAC7B,YAAYA,EAAE,eAAe;AAAA,MAC7B,MAAM;AAAA,IAAA,CACP,CACF;AAGD,UAAMmD,KAAU/C;AAAA,MAAyB,MACvCU,EAAU,SAASR,EAAO,UAAU,OAChC;AAAA,QACE;AAAA,UACE,KAAK;AAAA,UACL,OAAON,EAAE,eAAe;AAAA,UACxB,MAAM;AAAA,UACN,QAAQ;AAAA,UACR,MAAM;AAAA,UACN,KAAK,MAAA;AAAM,YAAKuC,GAAA;AAAA;AAAA,QAAO;AAAA,MACzB,IAEF,CAAA;AAAA,IAAC;2BAKLa,EAoDM,OAAA;AAAA,MApDD,OAAM;AAAA,MAAoB,WAAAR;AAAA,IAAA;MACbjC,EAAA,cAAhByC,EAIWC,GAAA,EAAA,KAAA,KAAA;AAAA,QAFTC,EAA+DC,EAAAC,CAAA,GAAA;AAAA,UAAlD,OAAM;AAAA,UAA0B,OAAA;AAAA,UAAO,MAAM;AAAA,QAAA;QAC1DF,EAA4CC,EAAAE,EAAA,GAAA,MAAA;AAAA,qBAAnC,MAAyB;AAAA,YAAzBH,EAAyBC,EAAAC,CAAA,GAAA,EAAX,MAAM,GAAC;AAAA,UAAA;;;sBAGhCJ,EA4CWC,GAAA,EAAA,KAAA,KAAA;AAAA,QA3CTC,EAIiBC,EAAAG,EAAA,GAAA;AAAA,UAJD,SAAA;AAAA,UAAS,MAAMrC,EAAA;AAAA,UAAO,cAAYC,EAAA;AAAA,UAAU,OAAOzC,EAAA;AAAA,UAAQ,SAASsE,GAAA;AAAA,QAAA;UAClE7C,EAAA,SAAM,CAAKA,EAAA,MAAO;kBAAU;AAAA,kBAC1C,MAAmD;AAAA,cAAnDgD,EAAmDC,EAAAI,EAAA,GAAA,EAAzC,KAAA,MAAG;AAAA,2BAAC,MAA0B;AAAA,sBAAvBJ,EAAAvD,CAAA,EAAC,iBAAA,CAAA,GAAA,CAAA;AAAA,gBAAA;;;;;;;QAItBsD,EAcUC,EAAAE,EAAA,GAAA,EAdD,OAAM,6BAAyB;AAAA,qBACtC,MAYe;AAAA,YAZfH,EAYeC,EAAAK,EAAA,GAAA;AAAA,cAXZ,OAAOL,EAAAvD,CAAA,EAAC,cAAA;AAAA,cACR,MAAMjB,EAAA,UAAE,OAAY,SAAYwE,EAAAvD,CAAA,EAAC,mBAAA;AAAA,cACjC,OAAOa,EAAA,MAAO,QAAK,CAAA;AAAA,cACpB,UAAA;AAAA,YAAA;yBAEA,MAKE;AAAA,gBALFyC,EAKEC,EAAAM,EAAA,GAAA;AAAA,8BAJSnC,EAAA;AAAA,gEAAAA,EAAU,QAAAoC;AAAA,kBAClB,SAAStC,EAAA;AAAA,kBACT,UAAQ,CAAGV,EAAA,SAAaF,EAAA;AAAA,kBACxB,cAAY2C,EAAAvD,CAAA,EAAC,cAAA;AAAA,gBAAA;;;;;;;QAKpBsD,EAKEC,EAAAQ,EAAA,GAAA;AAAA,sBAJS/E,EAAA;AAAA,wDAAAA,EAAM,QAAA8E;AAAA,UACf,MAAK;AAAA,UACJ,QAAQjD,EAAA;AAAA,UACR,UAAQ,CAAGC,EAAA,SAAaF,EAAA;AAAA,QAAA;QAGNE,EAAA,cAArBkD,EAagBT,EAAAU,EAAA,GAAA,EAAA,KAAA,KAAA;AAAA,UAZH,SACT,MAAgC;AAAA,YAAhCX,EAAgCC,EAAAW,EAAA,GAAA,EAAhB,OAAOjD,EAAA,SAAK,MAAA,GAAA,CAAA,OAAA,CAAA;AAAA,UAAA;qBAG9B,MAOY;AAAA,YAPZqC,EAOYC,EAAAY,EAAA,GAAA;AAAA,cANV,MAAK;AAAA,cACJ,SAASvD,EAAA;AAAA,cACT,UAAQ,CAAII,EAAA,SAASjC,EAAA,kBAAgBP,EAAA,UAAK;AAAA,cAC1C,SAAO6D;AAAA,YAAA;yBAER,MAAsB;AAAA,oBAAnBkB,EAAAvD,CAAA,EAAC,aAAA,CAAA,GAAA,CAAA;AAAA,cAAA;;;;;;;;;;;;;;;;;;;;;ACzUd,UAAMT,IAAQC,GAER,EAAE,MAAA4E,GAAM,SAAAC,GAAS,SAAAC,EAAA,IAAYC,GAAA,GAE7B9E,IAAMpB,EAAiBiB,GAAU,GACjCQ,IAAUC,GAAA;AAChB,IAAAX,GAAA;AAEA,UAAMY,IAAIC,EAAa,cAAc,GAE/BuE,IAAUjF,EAAM,UAAU;AAMhC,aAASkF,IAA0B;AACjC,UAAIlF,EAAM,UAAU,KAAM,QAAO,CAAA;AACjC,UAAIA,EAAM,MAAM,OAAQ,QAAO,EAAE,GAAGA,EAAM,MAAM,OAAA;AAEhD,YAAMmF,IAAQ5E,EAAQ,KAAK,MAAM,CAAC,GAAG;AAErC,aAAO4E,MAAU,SAAY,KAAK,EAAE,CAACA,CAAK,GAAGnF,EAAM,MAAM,MAAA;AAAA,IAC3D;AAEA,UAAMd,IAAM8B,EAAIhB,EAAM,OAAO,OAAO,EAAE,GAChCV,IAAQ0B,EAAoBkE,GAAS,GACrC7D,IAASL,EAAI,EAAK,GAClBM,IAASN,EAA8B,EAAE,GACzCoE,IAAQC,GAA4B,OAAO;AAEjD,IAAA5B,GAAU,YAAY;AACpB,YAAM6B,GAAA,GACNF,EAAM,OAAO,cAAc,OAAO,GAAG,MAAA;AAAA,IACvC,CAAC;AAGD,aAASzD,IAAmB;AAC1B,aAAO,OAAO,OAAOrC,EAAM,KAAK,EAAE,KAAK,CAACiG,MAAUA,EAAM,KAAA,MAAW,EAAE;AAAA,IACvE;AAGA,aAASC,EAAQC,GAAkC;AACjD,YAAMC,IAAQ,OAAO,KAAKpE,EAAO,KAAK,EAAE,KAAK,CAACY,MAAQA,MAAQuD,KAAQvD,EAAI,WAAW,GAAGuD,CAAI,GAAG,CAAC;AAEhG,aAAOC,MAAU,SAAY,SAAYpE,EAAO,MAAMoE,CAAK,IAAI,CAAC;AAAA,IAClE;AAEA,mBAAeC,IAAwB;AACrC,UAAI,GAAChE,EAAA,KAAaN,EAAO,QAEzB;AAAA,QAAAA,EAAO,QAAQ,IACfC,EAAO,QAAQ,CAAA;AAEf,YAAI;AACF,UAAAwD;AAAA,YACEG,KAAWjF,EAAM,UAAU,OACvB,MAAME,EAAI,YAAYF,EAAM,MAAM,KAAKV,EAAM,KAAK,IAClD,MAAMY,EAAI,YAAY,EAAE,KAAKhB,EAAI,MAAM,QAAQ,OAAOI,EAAM,MAAA,CAAO;AAAA,UAAA;AAAA,QAE3E,SAASsD,GAAO;AAGd,UAAAtB,EAAO,QAASsB,EAA2D,MAAM,UAAU,CAAA;AAAA,QAC7F,UAAA;AACE,UAAAvB,EAAO,QAAQ;AAAA,QACjB;AAAA;AAAA,IACF;2BAIEoD,EA4CYT,EAAA4B,EAAA,GAAA;AAAA,MA3CF,MAAM5B,EAAAa,CAAA;AAAA,sDAAAA,EAAI,QAAAN,IAAA;AAAA,MACjB,OAAOU,IAAUjB,EAAAvD,CAAA,2BAA2BuD,EAAAvD,CAAA,EAAC,kBAAA;AAAA,MAC7C,OAAO;AAAA,IAAA;MA4BG,UACT,MAUW;AAAA,QAVXsD,EAUWC,EAAA6B,EAAA,GAAA,EAVD,MAAK,QAAI;AAAA,qBACjB,MAAoF;AAAA,YAApF9B,EAAoFC,EAAAY,EAAA,GAAA;AAAA,cAAzE,SAAQ;AAAA,cAAW,gCAAOZ,EAAAe,CAAA,EAAA;AAAA,YAAO;yBAAI,MAAwB;AAAA,oBAArBf,EAAAvD,CAAA,EAAC,eAAA,CAAA,GAAA,CAAA;AAAA,cAAA;;;YACpDsD,EAOYC,EAAAY,EAAA,GAAA;AAAA,cANV,MAAK;AAAA,cACJ,SAASvD,EAAA;AAAA,cACT,UAAQ,CAAGM,EAAA,KAAazC,EAAA,MAAI,WAAI;AAAA,cAChC,SAAOyG;AAAA,YAAA;yBAER,MAAqD;AAAA,oBAAlDV,IAAUjB,EAAAvD,CAAA,EAAC,aAAA,IAAkBuD,EAAAvD,CAAA,EAAC,eAAA,CAAA,GAAA,CAAA;AAAA,cAAA;;;;;;;iBAnCvC,MASe;AAAA,QATfsD,EASeC,EAAAK,EAAA,GAAA;AAAA,UATA,OAAOL,EAAAvD,CAAA,EAAC,oBAAA;AAAA,UAAyB,OAAO+E,EAAO,OAAA;AAAA,UAAW,UAAA;AAAA,QAAA;qBACvE,MAOM;AAAA,YAPNM,EAOM,OAAA;AAAA,uBAPG;AAAA,cAAJ,KAAIV;AAAA,YAAA;cACPrB,EAKEC,EAAA+B,EAAA,GAAA;AAAA,4BAJSzG,EAAA;AAAA,8DAAAA,EAAK,QAAAiF;AAAA,gBACd,WAAA;AAAA,gBACC,cAAYP,EAAAvD,CAAA,EAAC,oBAAA;AAAA,gBACb,YAAakF,GAAM,CAAA,OAAA,CAAA;AAAA,cAAA;;;;;QAK1B5B,EAaeC,EAAAK,EAAA,GAAA;AAAA,UAZZ,OAAOL,EAAAvD,CAAA,EAAC,kBAAA;AAAA,UACR,OAAO+E,EAAO,KAAA;AAAA,UACd,MAAMP,IAAU,SAAYjB,EAAAvD,CAAA,EAAC,iBAAA;AAAA,UAC9B,UAAA;AAAA,QAAA;qBAEA,MAME;AAAA,YANFsD,EAMEC,EAAA+B,EAAA,GAAA;AAAA,0BALS7G,EAAA;AAAA,4DAAAA,EAAG,QAAAqF;AAAA,cACX,UAAUU;AAAA,cACV,cAAYjB,EAAAvD,CAAA,EAAC,kBAAA;AAAA,cACd,cAAa;AAAA,cACZ,YAAakF,GAAM,CAAA,OAAA,CAAA;AAAA,YAAA;;;;;;;;;;;;;;;;;;;;;;;;ACtF5B,UAAM3F,IAAQC,GAUR+F,IAAOC,GAMPlH,IAAQgB,EAAA,GACRG,IAAMpB,EAAiBC,CAAK,GAC5BoB,IAAQC,GAAA,GACRC,IAASC,GAAA;AACf,IAAAT,GAAA;AAEA,UAAM,IAAIa,EAAa,cAAc,GAE/BC,IAAUC,GAAA,GAEVsF,IAAOlF,EAAiB,EAAE,GAC1BI,IAAUJ,EAAI,EAAI,GAElBO,IAAYV,EAAS,MAAM9B,EAAM,IAAI,gBAAgB,CAAC,GAGtDoH,IAAOtF,EAAyB;AAAA,MACpC,KAAK,MAAOV,EAAM,MAAM,SAAS,YAAY,YAAY;AAAA,MACzD,KAAK,CAACiC,MAAU;AACd,QAAK/B,EAAO,QAAQ;AAAA,UAClB,OAAO,EAAE,GAAGF,EAAM,OAAO,MAAMiC,MAAU,YAAY,YAAY,OAAA;AAAA,QAAU,CAC5E;AAAA,MACH;AAAA,IAAA,CACD,GAEKgE,IAAQvF,EAAS,MAAMsF,EAAK,UAAU,SAAS,GAE/CE,IAAQxF,EAAS,MAAM;AAAA,MAC3B,EAAE,OAAO,QAAQ,OAAO,EAAE,YAAY,EAAA;AAAA,MACtC,EAAE,OAAO,WAAW,OAAO,EAAE,gBAAgB,GAAG,MAAM,QAAA;AAAA,IAAiB,CACxE,GAEKyF,IAAWzF,EAAS,MAAMU,EAAU,SAAS,CAAC6E,EAAM,SAASF,EAAK,MAAM,SAAS,CAAC,GAElFK,IAAS1F,EAAS,MACR,CAAC,UAAU,UAAU,QAAQ,EAE9B,SAASb,EAAM,MAAM,MAAM,IAAI,EAAE,iBAAiBA,EAAM,MAAM,MAAM,EAAE,IAAI,EACxF;AAED,mBAAewC,IAAsB;AACnC,MAAApB,EAAQ,QAAQ;AAEhB,UAAI;AACF,QAAA8E,EAAK,QAAQ,MAAMhG,EAAI,KAAKF,EAAM,MAAM,KAAKoG,EAAM,KAAK;AAAA,MAC1D,SAASxD,GAAO;AACd,QAAAC,EAAM,OAAOlC,EAAQiC,CAAK,CAAC;AAAA,MAC7B,UAAA;AACE,QAAAxB,EAAQ,QAAQ;AAAA,MAClB;AAAA,IACF;AAEA,IAAAmC,EAAM6C,GAAO;AAAM,MAAK5D;KAAM,GACzBA,EAAA;AAEL,aAASgE,EAAKC,GAAwB;AACpC,aAAO,GAAGzG,EAAM,IAAI,IAAIyG,EAAI,EAAE;AAAA,IAChC;AAEA,aAASC,IAAY;AACnB,MAAKrG,EAAO,KAAK,EAAE,MAAM,GAAGL,EAAM,IAAI,QAAQ,OAAO,EAAE,OAAOA,EAAM,MAAM,IAAA,GAAO;AAAA,IACnF;AAOA,mBAAe2G,IAAyB;AACtC,UAAI;AACF,cAAMzG,EAAI;AAAA,UACRF,EAAM,MAAM;AAAA,UACZkG,EAAK,MAAM,IAAI,CAACO,MAAQA,EAAI,EAAE;AAAA,QAAA;AAAA,MAElC,SAAS7D,GAAO;AACd,QAAAC,EAAM,OAAOlC,EAAQiC,GAAO,EAAE,uBAAuB,CAAC,CAAC,GACvD,MAAMJ,EAAA;AAAA,MACR;AAAA,IACF;AAOA,mBAAeoE,EAAOH,GAA+B;AACnD,UAAI;AACF,cAAMlE,IAAS,MAAMrC,EAAI,IAAIuG,EAAI,EAAE;AAEnC,cAAMvG,EAAI,KAAKuG,EAAI,IAAI,EAAE,GAAGlE,EAAO,QAAQ,SAAS,CAACkE,EAAI,QAAA,CAAS,GAClE5D,EAAM,QAAQ4D,EAAI,UAAU,EAAE,qBAAqB,IAAI,EAAE,oBAAoB,CAAC;AAAA,MAChF,SAAS7D,GAAO;AACd,QAAAC,EAAM,OAAOlC,EAAQiC,CAAK,CAAC;AAAA,MAC7B,UAAA;AACE,cAAMJ,EAAA;AAAA,MACR;AAAA,IACF;AAEA,mBAAeQ,EAAOyD,GAA+B;AASnD,UARe,MAAMvD,EAAQ;AAAA,QAC3B,OAAO,EAAE,uBAAuB,EAAE,MAAMuD,EAAI,OAAO;AAAA,QACnD,SAAS,EAAE,oBAAoB;AAAA,QAC/B,aAAa,EAAE,eAAe;AAAA,QAC9B,YAAY,EAAE,eAAe;AAAA,QAC7B,MAAM;AAAA,MAAA,CACP;AAID,YAAI;AACF,gBAAMvG,EAAI,OAAOuG,EAAI,EAAE,GACvB5D,EAAM,QAAQ,EAAE,gBAAgB,CAAC,GACjCmD,EAAK,SAAS;AAAA,QAChB,SAASpD,GAAO;AACd,UAAAC,EAAM,OAAOlC,EAAQiC,CAAK,CAAC;AAAA,QAC7B,UAAA;AACE,gBAAMJ,EAAA;AAAA,QACR;AAAA,IACF;AAEA,mBAAeqE,EAAQJ,GAA+B;AACpD,UAAI;AACF,cAAMvG,EAAI,QAAQuG,EAAI,EAAE,GACxB5D,EAAM,QAAQ,EAAE,iBAAiB,CAAC,GAClCmD,EAAK,SAAS;AAAA,MAChB,SAASpD,GAAO;AACd,QAAAC,EAAM,OAAOlC,EAAQiC,CAAK,CAAC;AAAA,MAC7B,UAAA;AACE,cAAMJ,EAAA;AAAA,MACR;AAAA,IACF;AAEA,aAASsE,EAAWL,GAA6B;AAC/C,UAAIL,EAAM;AACR,eAAO7E,EAAU,QACb;AAAA,UACE;AAAA,YACE,KAAK;AAAA,YACL,MAAM;AAAA,YACN,OAAO,EAAE,gBAAgB;AAAA,YACzB,KAAK,MAAA;AAAM,cAAKsF,EAAQJ,CAAG;AAAA;AAAA,UAAA;AAAA,QAC7B,IAEF,CAAA;AAGN,YAAM5B,IAAkB;AAAA,QACtB,KAAK;AAAA,QACL,MAAM;AAAA,QACN,OAAO,EAAE,aAAa;AAAA,QACtB,KAAK,MAAA;AAAM,UAAKxE,EAAO,KAAKmG,EAAKC,CAAG,CAAC;AAAA;AAAA,MAAA;AAGvC,aAAKlF,EAAU,QAER;AAAA,QACLsD;AAAA,QACA;AAAA,UACE,KAAK;AAAA,UACL,MAAM4B,EAAI,UAAU,YAAY;AAAA,UAChC,OAAOA,EAAI,UAAU,EAAE,gBAAgB,IAAI,EAAE,eAAe;AAAA,UAC5D,KAAK,MAAA;AAAM,YAAKG,EAAOH,CAAG;AAAA;AAAA,QAAA;AAAA,QAE5B;AAAA,UACE,KAAK;AAAA,UACL,MAAM;AAAA,UACN,OAAO,EAAE,eAAe;AAAA,UACxB,QAAQ;AAAA,UACR,KAAK,MAAA;AAAM,YAAKzD,EAAOyD,CAAG;AAAA;AAAA,QAAA;AAAA,MAC5B,IAhB2B,CAAC5B,CAAI;AAAA,IAkBpC;AAGA,UAAMjB,IAAU/C;AAAA,MAAyB,MACvCU,EAAU,SAAS,CAAC6E,EAAM,QACtB,CAAC,EAAE,KAAK,OAAO,OAAO,EAAE,YAAY,GAAG,MAAM,QAAQ,SAAS,IAAM,KAAKM,EAAA,CAAK,IAC9E,CAAA;AAAA,IAAC;sBAKLK,EAAA,GAAAlD,EAiGM,OAjGNmD,IAiGM;AAAA,MAhGJjD,EAgBiBC,EAAAG,EAAA,GAAA;AAAA,QAfd,OAAO;AAAA,QACP,MAAI,CAAGnE,EAAM;AAAA,QACb,cAAYgE,EAAA,CAAA,EAAC,eAAA;AAAA,QACb,OAAO/D,EAAA,MAAM;AAAA,QACb,SAAS2D,EAAA;AAAA,QACT,kBAAgB;AAAA,QAChB,+BAAMoC,EAAI,MAAA;AAAA,MAAA;QAEK/F,EAAA,MAAM;gBAAW;AAAA,gBAC/B,MAIa;AAAA,YAJb8D,EAIaC,EAAAiD,EAAA,GAAA;AAAA,cAJA,SAASjD,EAAA,CAAA,EAAC,sBAAA;AAAA,YAAA;yBACrB,MAEO;AAAA,gBAFP8B,EAEO,QAAA;AAAA,kBAFD,OAAM;AAAA,kBAAwB,UAAS;AAAA,kBAAK,cAAY9B,EAAA,CAAA,EAAC,iBAAA;AAAA,gBAAA;kBAC7DD,EAAiCC,EAAAkD,EAAA,GAAA;AAAA,oBAAxB,MAAK;AAAA,oBAAO,MAAK;AAAA,kBAAA;;;;;;;;;MAUlCpB,EAII,KAJJqB,IAII;AAAA,QAHFrB,EAAoC,gBAA3B9B,EAAA,CAAA,EAAC,cAAA,CAAA,GAAA,CAAA;AAAA,QACV8B,EAAuC,cAAjC,cAASsB,EAAGnH,QAAM,GAAG,IAAG,MAAE,CAAA;AAAA,QACpBsG,EAAA,SAAZQ,EAAA,GAAAlD,EAAyE,QAAzEwD,IAAoD,SAAKd,EAAA,KAAM,GAAA,CAAA;;MAGjExC,EAMEC,EAAAsD,EAAA,GAAA;AAAA,oBALSnB,EAAA;AAAA,sDAAAA,EAAI,QAAA5B;AAAA,QACb,OAAM;AAAA,QACN,MAAK;AAAA,QACJ,SAAS8B,EAAA;AAAA,QACT,cAAYrC,EAAA,CAAA,EAAC,YAAA;AAAA,MAAA;MAGG5C,EAAA,SAAW8E,EAAA,MAAK,WAAM,UAAzCzB,EAA6DT,EAAAC,CAAA,GAAA;AAAA;QAAX,MAAM;AAAA,MAAA,MAG3CiC,EAAA,MAAK,WAAM,UADxBzB,EAIET,EAAAuD,EAAA,GAAA;AAAA;QAFC,OAAOnB,EAAA,QAAQpC,EAAA,CAAA,wBAAwBA,EAAA,CAAA,EAAC,cAAA;AAAA,QACxC,aAAaoC,EAAA,QAAQ,SAAYpC,EAAA,CAAA,EAAC,mBAAA;AAAA,MAAA,+CAGrCH,EAmDWC,GAAA,EAAA,KAAA,KAAA;AAAA,QAhDTC,EAuCmBC,EAAAwD,EAAA,GAAA;AAAA,sBAtCRtB,EAAA;AAAA,wDAAAA,EAAI,QAAA3B;AAAA,UACb,OAAKkD,GAAA,CAAC,yBAAuB,EAAA,cACLrG,EAAA,MAAA,CAAO,CAAA;AAAA,UAC/B,OAAA;AAAA,UACA,YAAS;AAAA,UACR,QAAQkF,EAAA,QAAQ,SAAA;AAAA,UAChB,cAAU,CAAGoB,MAAoBA,EAAK;AAAA,UACtC,WAAWpB,EAAA;AAAA,UACX,QAAMK;AAAA,QAAA;UAEI,SAAOgB,EAChB,CAqBY,EAtBQ,MAAAD,QAAI;AAAA,aACxBX,EAAA,GAAAtC,EAqBYmD,GApBLxB,EAAA,QAAK,QAAA,aAAA,GAAA;AAAA,cACT,IAAIA,EAAA,QAAQ,SAAYI,EAAKkB,CAAI;AAAA,cAClC,WAAM,iBAAe,EAAA,UAAA,CACAA,EAAK,YAAYtB,EAAA,OAAK,CAAA;AAAA,YAAA;yBAE3C,MAGO;AAAA,gBAHPN,EAGO,QAHP+B,IAGO;AAAA,kBAFMH,EAAK,cAAhB7D,EAAiE,OAAA;AAAA;oBAAzC,KAAK6D,EAAK;AAAA,oBAAO,KAAI;AAAA,oBAAG,SAAQ;AAAA,kBAAA,yBACxDjD,EAA8ET,EAAAkD,EAAA,GAAA;AAAA;oBAA9D,MAAK;AAAA,oBAAQ,MAAK;AAAA,oBAAM,cAAYlD,EAAA,CAAA,EAAC,mBAAA;AAAA,kBAAA;;gBAGvD8B,EASO,QATPgC,IASO;AAAA,kBARLhC,EAA0D,QAA1DiC,IAA0DX,EAApBM,EAAK,KAAK,GAAA,CAAA;AAAA,kBAEpCA,EAAK,SAAK,CAAMA,EAAK,YAAYtB,EAAA,SAA7CW,EAAA,GAAAlD,EAKO,QALPmE,IAKO;AAAA,oBAJWN,EAAK,cAArBjD,EAAoFT,EAAAI,EAAA,GAAA;AAAA;sBAAxD,MAAK;AAAA,sBAAK,MAAK;AAAA,oBAAA;iCAAO,MAAuB;AAAA,4BAApBJ,EAAA,CAAA,EAAC,cAAA,CAAA,GAAA,CAAA;AAAA,sBAAA;;;qBACrC0D,EAAK,WAAO,CAAKtB,EAAA,cAAlC3B,EAEaT,EAAAI,EAAA,GAAA;AAAA;sBAF4B,KAAA;AAAA,sBAAI,MAAK;AAAA,oBAAA;iCAAK,MAErD;AAAA,4BADAJ,EAAA,CAAA,EAAC,iBAAA,CAAA,GAAA,CAAA;AAAA,sBAAA;;;;;;;;;UAOA,SAAO2D,EAChB,CAA+D,EAD3C,MAAAD,QAAI;AAAA,YACxB3D,EAA+DC,EAAAiE,EAAA,GAAA;AAAA,cAAjD,SAASnB,EAAWY,CAAI;AAAA,cAAI,OAAOA,EAAK;AAAA,YAAA;;;;QAMlDpB,EAAA,cADR7B,EAKET,EAAAkE,EAAA,GAAA;AAAA;UAHA,OAAM;AAAA,UACN,MAAK;AAAA,UACJ,aAAalE,EAAA,CAAA,EAAC,cAAA;AAAA,QAAA;;;;;;;;;;;;;ACtSvB,UAAMhE,IAAQC,GAERlB,IAAQgB,EAAA,GACRG,IAAMpB,EAAiBC,CAAK,GAC5BoB,IAAQC,GAAA,GACRC,IAASC,GAAA;AACf,IAAAT,GAAA;AAEA,UAAMY,IAAIC,EAAa,cAAc,GAE/BC,IAAUC,GAAA,GAEVO,IAASH,EAAgB,EAAE,GAC3BI,IAAUJ,EAAI,EAAI,GAClB6D,IAAO7D,EAAI,EAAK,GAChBmH,IAAenH,EAAI,EAAI,GAEvBO,IAAYV,EAAS,MAAM9B,EAAM,IAAI,gBAAgB,CAAC,GAEtDyH,IAAO4B,GAAmDC,EAAW,GAErE/I,IAAQuB;AAAA,MACZ,MACE9B,EAAM,MAAM,UAAU,QAAQ,KAAK,CAACiD,MAAWA,EAAO,OAAO,SAAS,GAAG,SACzEvB,EAAE,gBAAgB;AAAA,IAAA,GAIhBe,IAAUX,EAAwB,MAAM;AAC5C,YAAM6B,IAAQvC,EAAM,MAAM;AAE1B,aAAO,OAAOuC,KAAU,YAAYA,MAAU,KAAKA,IAAQ;AAAA,IAC7D,CAAC,GAEK4F,IAASzH,EAAS,MAAMM,EAAO,MAAM,KAAK,CAAClC,MAAUA,EAAM,QAAQuC,EAAQ,KAAK,KAAK,IAAI;AAE/F,mBAAegB,IAAsB;AACnC,MAAApB,EAAQ,QAAQ;AAEhB,UAAI;AACF,QAAAD,EAAO,QAAQ,MAAMjB,EAAI,OAAA;AAAA,MAC3B,SAAS0C,GAAO;AACd,QAAAC,EAAM,OAAOlC,EAAQiC,CAAK,CAAC;AAAA,MAC7B,UAAA;AACE,QAAAxB,EAAQ,QAAQ;AAAA,MAClB;AAAA,IACF;AAEA,IAAKoB,EAAA,GAMLe,EAAM,CAACpC,GAAQmH,GAAQH,CAAY,GAAG,MAAM;AAC1C,MAAI,CAACA,EAAa,SAAShH,EAAO,MAAM,WAAW,KAAKmH,EAAO,UAAU,QAEpEjI,EAAO,QAAQ,EAAE,OAAO,EAAE,GAAGF,EAAM,OAAO,OAAOgB,EAAO,MAAM,CAAC,EAAG,IAAA,GAAO;AAAA,IAChF,CAAC,GAEDoC,EAAM+E,GAAQ,CAACrJ,MAAU;AACvB,MAAIA,MAAU,QAAQkJ,EAAa,YAAY,QAAQ;AAAA,IACzD,CAAC,GAGG3G,EAAQ,UAAU,SAAMqD,EAAK,QAAQ;AAEzC,aAAS0D,EAAOtJ,GAAuB;AACrC,YAAMuJ,IAA0B,EAAE,GAAGrI,EAAM,OAAO,OAAOlB,EAAM,IAAA;AAG/D,aAAOuJ,EAAM,MACRnI,EAAO,QAAQ,EAAE,OAAAmI,GAAO,GAC7B3D,EAAK,QAAQ;AAAA,IACf;AAEA,mBAAe6B,IAAqB;AAClC,YAAM3D,IAAO,MAAMyD,EAAK,EAAE,OAAO,MAAM;AAEvC,MAAKzD,MAELF,EAAM,QAAQpC,EAAE,gBAAgB,CAAC,GACjC,MAAM+B,EAAA,GACN+F,EAAOxF,CAAI;AAAA,IACb;AAEA,mBAAe0F,EAAOxJ,GAAgC;AAGpD,MAFc,MAAMuH,EAAK,EAAE,OAAAvH,GAAO,MAIlC4D,EAAM,QAAQpC,EAAE,gBAAgB,CAAC,GACjC,MAAM+B,EAAA;AAAA,IACR;AAEA,mBAAeQ,EAAO/D,GAAgC;AASpD,UARe,MAAMiE,EAAQ;AAAA,QAC3B,OAAOzC,EAAE,uBAAuB,EAAE,OAAOxB,EAAM,OAAO;AAAA,QACtD,SAASwB,EAAE,oBAAoB;AAAA,QAC/B,aAAaA,EAAE,eAAe;AAAA,QAC9B,YAAYA,EAAE,eAAe;AAAA,QAC7B,MAAM;AAAA,MAAA,CACP;AAID,YAAI;AAIF,cAHA,MAAMP,EAAI,YAAYjB,EAAM,GAAG,GAC/B4D,EAAM,QAAQpC,EAAE,gBAAgB,CAAC,GAE7Be,EAAQ,UAAUvC,EAAM,KAAK;AAC/B,kBAAMyJ,IAAO,EAAE,GAAGvI,EAAM,MAAA;AAExB,mBAAOuI,EAAK,OACZ,OAAOA,EAAK,MACPrI,EAAO,QAAQ,EAAE,OAAOqI,GAAM,GACnC7D,EAAK,QAAQ;AAAA,UACf;AAAA,QACF,SAASjC,GAAO;AAGd,UAAAC,EAAM,OAAOlC,EAAQiC,CAAK,CAAC;AAAA,QAC7B,UAAA;AACE,gBAAMJ,EAAA;AAAA,QACR;AAAA,IACF;AAEA,aAASsE,EAAW7H,GAA8B;AAChD,aAAI,CAACsC,EAAU,SAAStC,EAAM,WAAiB,CAAA,IAExC;AAAA,QACL,EAAE,KAAK,UAAU,MAAM,QAAQ,OAAOwB,EAAE,eAAe,GAAG,KAAK,MAAA;AAAM,UAAKgI,EAAOxJ,CAAK;AAAA,UAAA;AAAA,QACtF;AAAA,UACE,KAAK;AAAA,UACL,MAAM;AAAA;AAAA;AAAA,UAGN,OAAOA,EAAM,QAAQ,IAAIwB,EAAE,2BAA2B,IAAIA,EAAE,eAAe;AAAA,UAC3E,QAAQ;AAAA,UACR,UAAUxB,EAAM,QAAQ;AAAA,UACxB,KAAK,MAAA;AAAM,YAAK+D,EAAO/D,CAAK;AAAA;AAAA,QAAA;AAAA,MAC9B;AAAA,IAEJ;AAEA,UAAM2E,IAAU/C;AAAA,MAAyB,MACvCU,EAAU,QACN,CAAC,EAAE,KAAK,aAAa,OAAOd,EAAE,YAAY,GAAG,MAAM,QAAQ,KAAK,MAAA;AAAM,QAAKiG;QAAI,CAAG,IAClF,CAAA;AAAA,IAAC;2BAKLjC,EA6EiBT,EAAA2E,EAAA,GAAA;AAAA,MA7EA,OAAOrJ,EAAA;AAAA,MAAQ,SAASsE,EAAA;AAAA,MAAU,MAAM;AAAA,IAAA;iBACvD,MA2EU;AAAA,QA3EVG,EA2EUC,EAAAE,EAAA,GAAA;AAAA,UA3ED,OAAM;AAAA,UAAa,SAAQ;AAAA,QAAA;qBAClC,MAyEiB;AAAA,YAzEjBH,EAyEiBC,EAAA4E,EAAA,GAAA;AAAA,cAxEP,MAAM/D,EAAA;AAAA,sDAAAA,EAAI,QAAAN;AAAA,cAClB,OAAM;AAAA,cACL,cAAY;AAAA,cACZ,cAAY;AAAA,cACZ,gBAAcP,EAAAvD,CAAA,EAAC,eAAA;AAAA,cACf,gBAAaoI,EAAA,CAAA,MAAAA,EAAA,CAAA,IAAA,CAAAtE,MAAE4D,EAAA,QAAe5D;AAAA,YAAA;cAEpB,QACT,MAIE;AAAA,gBAHMnD,EAAA,SAAWD,EAAA,MAAO,WAAM,UADhCsD,EAIET,EAAAC,CAAA,GAAA;AAAA;kBAFA,OAAM;AAAA,kBACL,MAAM;AAAA,gBAAA,MAII9C,EAAA,MAAO,WAAM,UAD1BsD,EAIET,EAAAuD,EAAA,GAAA;AAAA;kBAFC,OAAOvD,EAAAvD,CAAA,EAAC,cAAA;AAAA,kBACR,aAAauD,EAAAvD,CAAA,EAAC,mBAAA;AAAA,gBAAA,0CAGjBsG,KAAAlD,EAkCK,MAlCLmD,IAkCK;AAAA,0BAjCHnD,EAgCKC,GAAA,MAAAgF,GAhCe3H,EAAA,OAAM,CAAflC,YAAX4E,EAgCK,MAAA;AAAA,oBAhCwB,KAAK5E,EAAM;AAAA,oBAAK,OAAM;AAAA,kBAAA;oBACjD6G,EAwBS,UAAA;AAAA,sBAvBP,MAAK;AAAA,sBACL,WAAM,oBAAkB,EAAA,cACA7G,EAAM,QAAQuC,EAAA,MAAA,CAAO,CAAA;AAAA,sBAC5C,SAAK,CAAA+C,MAAEgE,EAAOtJ,CAAK;AAAA,oBAAA;sBAEpB6G,EAeO,QAfPqB,IAeO;AAAA,wBAdLpD,EAA6DC,EAAA+E,EAAA,GAAA;AAAA,0BAApD,UAAA;AAAA,0BAAS,QAAO;AAAA,wBAAA;qCAAS,MAAiB;AAAA,4BAAdC,EAAA5B,EAAAnI,EAAM,KAAK,GAAA,CAAA;AAAA,0BAAA;;;wBAExCA,EAAM,iBADdwF,EAMET,EAAAkD,EAAA,GAAA;AAAA;0BAJA,OAAM;AAAA,0BACN,MAAK;AAAA,0BACL,MAAK;AAAA,0BACJ,cAAYlD,EAAAvD,CAAA,EAAC,iBAAA;AAAA,wBAAA;wBAEhBsD,EAKEC,EAAAiF,EAAA,GAAA;AAAA,0BAJA,OAAM;AAAA,0BACN,MAAK;AAAA,0BACJ,OAAOhK,EAAM;AAAA,0BACb,OAAO+E,EAAAvD,CAAA,EAAC,cAAA;AAAA,wBAAA;;sBAIbsD,EAAkFC,EAAA+E,EAAA,GAAA;AAAA,wBAAzE,MAAK;AAAA,wBAAK,MAAK;AAAA,wBAAQ,MAAA;AAAA,wBAAK,UAAA;AAAA,sBAAA;mCAAS,MAAS;AAAA,0BAATC,EAAA,cAAS5B,EAAGnI,EAAM,GAAG,IAAG,MAAE,CAAA;AAAA,wBAAA;;;;oBAIlE6H,EAAW7H,CAAK,EAAE,SAAM,UADhCwF,EAIET,EAAAiE,EAAA,GAAA;AAAA;sBAFC,SAASnB,EAAW7H,CAAK;AAAA,sBACzB,OAAOA,EAAM;AAAA,oBAAA;;;;cAMX,QAAM0I,EACf,CAQE,EATiB,QAAAuB,GAAQ,MAAApH,QAAI;AAAA,gBAEvBwG,EAAA,cADR7D,EAQE0E,IAAA;AAAA,kBANC,KAAKb,EAAA,MAAO;AAAA,kBACZ,OAAOA,EAAA;AAAA,kBACP,MAAMtI,EAAM;AAAA,kBACZ,QAAAkJ;AAAA,kBACA,QAAMpH;AAAA,kBACN,WAASU;AAAA,gBAAA;;cAIH,SACT,MAA+E;AAAA,gBAA/EuB,EAA+EC,EAAAuD,EAAA,GAAA;AAAA,kBAApE,OAAOvD,EAAAvD,CAAA,EAAC,eAAA;AAAA,kBAAoB,aAAauD,EAAAvD,CAAA,EAAC,oBAAA;AAAA,gBAAA;;;;;;;;;;;;ACxPxD,SAAS2I,GAAQC,IAA0B,IAAiB;AACjE,QAAMC,IAAOD,EAAQ,QAAQ;AAE7B,SAAO;AAAA,IACL,IAAI;AAAA,IACJ,MAAAC;AAAA,IACA,QAAQ;AAAA;AAAA,MAEN,EAAE,MAAAA,GAAM,MAAM,gBAAgB,WAAWC,IAAa,OAAO,EAAE,MAAMD,IAAK;AAAA;AAAA,MAE1E;AAAA,QACE,MAAM,GAAGA,CAAI;AAAA,QACb,MAAM;AAAA,QACN,WAAWE;AAAA,QACX,OAAO,EAAE,MAAMF,EAAA;AAAA,MAAK;AAAA,MAEtB;AAAA,QACE,MAAM,GAAGA,CAAI;AAAA,QACb,MAAM;AAAA,QACN,WAAWE;AAAA,QACX,OAAO,EAAE,MAAMF,EAAA;AAAA,MAAK;AAAA,IACtB;AAAA,EACF;AAEJ;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webx-ui/module-banners",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "description": "Banners for the WebX UI admin panel: pictures, a video and up to three buttons in named places, arranged by hand and printed by the site's own templates.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -44,9 +44,9 @@
44
44
  "vue-router": "^4.5.0"
45
45
  },
46
46
  "dependencies": {
47
- "@webx-ui/core": "^0.39.0",
48
- "@webx-ui/module-admin": "^0.24.0",
49
- "@webx-ui/schema": "^0.8.1"
47
+ "@webx-ui/core": "^0.40.0",
48
+ "@webx-ui/module-admin": "^0.25.0",
49
+ "@webx-ui/schema": "^0.9.0"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@types/node": "^24.10.1",