@webx-ui/module-events 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../src/api.ts","../src/days.ts","../src/editor.ts","../src/messages.ts","../src/i18n.ts","../src/EventEditorPage.vue","../src/EventHistory.vue","../src/EventCreateDialog.vue","../src/EventsPage.vue","../src/module.ts"],"sourcesContent":["import type { AdminContext } from '@webx-ui/module-admin'\nimport type {\n EventDetail,\n EventInput,\n EventQuery,\n EventRow,\n EventSave,\n EventsPage,\n EventVersion,\n} from './types'\n\nexport interface EventsApi {\n /** A page of events, with what the list can be narrowed by. */\n list(query?: EventQuery): Promise<EventsPage>\n /** One event as its editor opens it: the record, the screen's values and the revision. */\n get(id: number): Promise<EventDetail>\n /** A new event, made in one transaction: a refused address leaves no bare row behind. */\n create(input: EventInput): Promise<EventDetail>\n /**\n * Save the draft. Refused with a 409 when the revision is stale — the body is an\n * {@link EventConflict} with the event as it now is.\n */\n save(id: number, input: EventSave): Promise<EventDetail>\n /** Throw away what is waiting and keep what the site is showing. */\n discard(id: number): Promise<EventDetail>\n /**\n * A copy as a draft that was never published: every field, the categories and the services,\n * the same title and an address with the next free suffix (decision 9). The answer is the\n * copy's form, so the editor can open it straight away.\n */\n duplicate(id: number): Promise<EventDetail>\n publish(id: number): Promise<EventRow>\n unpublish(id: number): Promise<EventRow>\n remove(id: number): Promise<void>\n restore(id: number): Promise<EventRow>\n /** The publications, newest first. */\n versions(id: number): Promise<EventVersion[]>\n /** An old publication becomes the draft; putting it on the site is a separate step. */\n restoreVersion(id: number, number: number): Promise<EventDetail>\n}\n\n/** The path of the list under the panel's API. */\nexport const EVENTS_API = 'events'\n\n/**\n * Everything under `/events`, below the panel's API path (§4.10) — except the categories, which\n * are the panel's shared category API under `events/categories`.\n */\nexport function createEventsApi(admin: AdminContext): EventsApi {\n const base = `${admin.apiPath}/${EVENTS_API}`\n const data = <T>(body: { data: T }): T => body.data\n\n return {\n list: (query = {}) => {\n const search = new URLSearchParams()\n\n // Always said: the server's default is the upcoming ones too, but a list that asks for\n // what it shows does not depend on somebody else's default staying put.\n search.set('when', query.when ?? 'upcoming')\n\n // Only what was asked for: `category=` would ask for the events of no category.\n if (query.q) search.set('q', query.q)\n if (query.category != null) search.set('category', String(query.category))\n if (query.service != null) search.set('service', String(query.service))\n if (query.status) search.set('status', query.status)\n if (query.trashed) search.set('trashed', '1')\n if (query.page && query.page > 1) search.set('page', String(query.page))\n if (query.per_page) search.set('per_page', String(query.per_page))\n\n return admin.http\n .get<{\n data: EventRow[]\n meta: Omit<EventsPage, 'data' | 'filters'>\n filters: EventsPage['filters']\n }>(`${base}?${search}`)\n .then((body) => ({ ...body.meta, data: body.data, filters: body.filters }))\n },\n get: (id) => admin.http.get<{ data: EventDetail }>(`${base}/${id}`).then(data),\n create: (input) => admin.http.post<{ data: EventDetail }>(base, input).then(data),\n save: (id, input) => admin.http.put<{ data: EventDetail }>(`${base}/${id}`, input).then(data),\n discard: (id) => admin.http.post<{ data: EventDetail }>(`${base}/${id}/discard`, {}).then(data),\n duplicate: (id) =>\n admin.http.post<{ data: EventDetail }>(`${base}/${id}/duplicate`, {}).then(data),\n publish: (id) => admin.http.post<{ data: EventRow }>(`${base}/${id}/publish`, {}).then(data),\n unpublish: (id) =>\n admin.http.post<{ data: EventRow }>(`${base}/${id}/unpublish`, {}).then(data),\n remove: (id) => admin.http.delete<void>(`${base}/${id}`).then(() => undefined),\n restore: (id) => admin.http.post<{ data: EventRow }>(`${base}/${id}/restore`, {}).then(data),\n versions: (id) => admin.http.get<{ data: EventVersion[] }>(`${base}/${id}/versions`).then(data),\n restoreVersion: (id, number) =>\n admin.http\n .post<{ data: EventDetail }>(`${base}/${id}/versions/${number}/restore`, {})\n .then(data),\n }\n}\n","import type { ScreenModel } from '@webx-ui/schema'\n\n/** The date and the clock a moment is written with, whatever offset follows them. */\nconst WALL = /^(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})/\n\nconst pad = (value: number, width = 2) => String(value).padStart(width, '0')\n\n/**\n * The same wall clock in the reader's own zone: `2026-10-12T00:00:00+08:00` read in New York\n * becomes `2026-10-12T00:00:00-04:00`.\n *\n * For an event of days the server keeps whole days in its own zone (§4.10) — midnight of the\n * first, `23:59:59` of the last — and the moment is not the point, the calendar date is. A date\n * picker reads a moment, though, and midnight at +08:00 is still the day before in New York, while\n * `23:59:59` in UTC is already the day after at +08:00: the same event would show a different\n * first or last day in every browser that is not in the site's zone. Moved onto the reader's\n * clock, the picker shows the day the server named, and a save sends it back with the reader's\n * offset — which the server cuts to a day in that same offset (`Moment::day()`), so it stays\n * the same day both ways.\n *\n * Anything that is not such a string comes back as it was.\n */\nexport function onReadersClock(value: unknown): unknown {\n if (typeof value !== 'string') return value\n\n const parts = WALL.exec(value)\n\n if (parts === null) return value\n\n const [year, month, day, hours, minutes, seconds] = parts.slice(1).map(Number) as [\n number,\n number,\n number,\n number,\n number,\n number,\n ]\n const local = new Date(year, month - 1, day, hours, minutes, seconds)\n // Minutes west of UTC on that day, not today: the offset changes with summer time.\n const west = local.getTimezoneOffset()\n const sign = west > 0 ? '-' : '+'\n const offset = `${sign}${pad(Math.floor(Math.abs(west) / 60))}:${pad(Math.abs(west) % 60)}`\n\n return (\n `${pad(local.getFullYear(), 4)}-${pad(local.getMonth() + 1)}-${pad(local.getDate())}` +\n `T${pad(local.getHours())}:${pad(local.getMinutes())}:${pad(local.getSeconds())}${offset}`\n )\n}\n\n/** The values of `events.form` with the days of an event of days put on the reader's clock. */\nexport function withDaysAsWritten(values: ScreenModel): ScreenModel {\n if (values.all_day !== true) return values\n\n return {\n ...values,\n starts_at: onReadersClock(values.starts_at),\n ends_at: onReadersClock(values.ends_at),\n } as ScreenModel\n}\n","import { inject, provide, type InjectionKey, type Ref } from 'vue'\nimport type { EventRow } from './types'\n\n/**\n * What the editor knows and the nodes of `events.form` do not: the event being edited, and a way\n * to read it again. The history needs both — its list is keyed by the event, and a restored\n * version has to reach the fields the form is bound to.\n */\nexport interface EventEditorContext {\n /** The event as the server last answered it, or `null` while the first request is out. */\n event: Ref<EventRow | null>\n /** Whether this administrator may write at all. */\n canManage: boolean\n /** Ask the server for the event again — after a restore, a publication, a discard. */\n reload(): Promise<void>\n}\n\nexport const eventEditorKey: InjectionKey<EventEditorContext> = Symbol('wx-event-editor')\n\nexport function provideEventEditor(editor: EventEditorContext): void {\n provide(eventEditorKey, editor)\n}\n\n/** The editor above this node, or `null` outside one — a demo or a test draws nothing. */\nexport function useEventEditor(): EventEditorContext | null {\n return inject(eventEditorKey, null)\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-events::` and overrides\n * these; `messages.test.ts` keeps the two sets of keys equal.\n */\nexport const eventsMessages: Record<string, Messages> = {\n module: {\n group: 'Events',\n events: 'Events',\n categories: 'Categories',\n },\n panel: {\n new: 'New event',\n 'new-title': 'New event',\n 'field-title': 'Title',\n create: 'Create',\n cancel: 'Cancel',\n search: 'Search by title or address',\n empty: 'No events yet.',\n 'empty-upcoming': 'Nothing ahead. The past events are on the next tab.',\n 'empty-past': 'No event is over yet.',\n 'empty-search': 'Nothing matches that.',\n 'empty-bin': 'The bin is empty.',\n\n 'status-draft': 'Draft',\n 'status-published': 'Live',\n 'status-modified': 'Live',\n 'status-unpublished': 'Off the site',\n edits: 'edits',\n past: 'Over',\n 'no-address': 'No address in this language',\n 'no-date': 'No date',\n 'main-category': 'The main category: it goes in the breadcrumbs',\n\n 'view-upcoming': 'Upcoming',\n 'view-past': 'Past',\n 'view-all': 'All',\n bin: 'Bin',\n\n 'column-title': 'Event',\n 'column-when': 'When',\n 'column-deleted': 'Deleted',\n 'column-categories': 'Categories',\n 'column-status': 'State',\n\n 'filter-status': 'State',\n 'any-status': 'Any state',\n 'filter-category': 'Category',\n 'any-category': 'Any category',\n 'filter-service': 'Service',\n 'any-service': 'Any service',\n\n open: 'Open',\n 'open-on-site': 'Open on the site',\n duplicate: 'Duplicate',\n publish: 'Publish',\n unpublish: 'Take off the site',\n delete: 'Delete',\n restore: 'Restore',\n 'delete-title': 'Delete “:title”?',\n 'delete-text': 'It goes to the bin and comes off the site, and its address is free again.',\n deleted: 'The event is in the bin.',\n restored: 'The event is back.',\n published: 'The event is on the site.',\n 'unpublished-done': 'The event is off the site.',\n },\n event: {\n trail: 'Where this event sits',\n untitled: 'Untitled',\n save: 'Save',\n 'save-failed': 'The event was not saved.',\n duplicated: 'This is the copy: a draft. Set its date and publish it.',\n 'publish-title': 'Put “:title” on the site?',\n 'publish-text':\n 'It answers at :address from the moment you do, for everyone — with its categories and services as they are chosen now.',\n 'publish-nowhere': 'It has no address in this language yet, so nothing will answer.',\n preview: 'Preview',\n discard: 'Discard changes',\n 'discard-title': 'Discard what is waiting?',\n 'discard-text':\n 'The event goes back to what the site is showing. What was written since is not listed anywhere.',\n discarded: 'The event is back to what is published.',\n 'conflict-title': 'The event changed while you were editing',\n 'conflict-mine': 'Keep mine',\n 'conflict-theirs': 'Take the newer version',\n 'conflict-theirs-title': 'Give up what you wrote?',\n 'conflict-theirs-text':\n 'The event is read again as it now is, and what you have typed since goes.',\n leave: 'Leave',\n 'leave-title': 'Leave without saving?',\n 'leave-text': 'The event could not be saved, and what you wrote is not on the server.',\n 'live-since': 'On the site since :date',\n 'address-moving':\n 'The address is changing. The old one keeps working and leads to the new one.',\n 'history-empty': 'This event has never been published.',\n version: '#:number',\n 'version-live': 'On the site',\n 'source-panel': 'From the panel',\n 'source-mcp': 'By an agent',\n 'source-import': 'Imported',\n 'restore-title': 'Restore version :number?',\n 'restore-text':\n 'It becomes the draft. The site keeps showing what is published until you publish this.',\n 'restore-version': 'Restore',\n 'restored-version': 'Version :number is now the draft.',\n },\n category: {\n new: 'New category',\n empty: 'No categories yet.',\n 'empty-help':\n 'A category is a kind of event — breakfast meetings, cooking classes — with a page of its own listing what is ahead.',\n order: 'Drag to change the order of the categories on the site.',\n hidden: 'Hidden from the site',\n 'no-address': 'No address in this language',\n events: 'Events: :count',\n 'show-events': 'Show its events',\n 'delete-blocked': 'This category still holds events. Move them first.',\n 'delete-text': 'It goes to the bin and its page comes off the site. Its events stay.',\n deleted: 'The category is in the bin.',\n saved: 'The category is saved.',\n 'field-title': 'Title',\n 'field-slug': 'Address',\n 'address-moving':\n 'The address is changing. The old one keeps working and leads to the new one.',\n },\n}\n","import { useAdmin } from '@webx-ui/module-admin'\nimport { eventsMessages } 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 useEventsMessages(): void {\n try {\n const { i18n } = useAdmin()\n\n if (!seeded.has(i18n)) {\n i18n.defaults('webx-events', eventsMessages)\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 provideRecordAddress,\n provideRelationOwner,\n useAdmin,\n useDates,\n useErrorText,\n useTranslate,\n WxSaveState,\n WxScreen,\n WxScreenHead,\n type ScreenAction,\n} from '@webx-ui/module-admin'\nimport {\n confirm,\n localizedValue,\n toast,\n useLocales,\n WxActionBar,\n WxAlert,\n WxBadge,\n WxBreadcrumb,\n WxBreadcrumbItem,\n WxButton,\n WxCard,\n WxIcon,\n WxSkeleton,\n type LocalizedValue,\n} from '@webx-ui/core'\nimport type { ScreenModel } from '@webx-ui/schema'\nimport { createEventsApi } from './api'\nimport { withDaysAsWritten } from './days'\nimport { provideEventEditor } from './editor'\nimport { useEventsMessages } from './i18n'\nimport type { EventConflict, EventDetail, EventRow } from './types'\n\n/**\n * The editor of one event: a head that stays put, the described screen under it, and the bar\n * along the bottom where it is saved, duplicated and published (§4.9).\n *\n * Everything between the head and the bar is `events.form` — the event, its settings, the SEO\n * card `module-seo` patches on and the history — so a project adds its own field with a patch\n * rather than a fork of this file. Saving is by autosave into the draft, and the draft carries\n * the categories and the services too: the site changes, all of it at once, only on \"Publish\".\n * Edits are guarded by a revision: a save over somebody else's is refused with a 409 and the\n * question of which version the site gets.\n *\n * The dates are moments with their offset (`valueFormat` on the screen's pickers), so the same\n * event reads the same hour in every browser and the server keeps it in its own zone (decision\n * 15). What the head prints under the name is the server's line (`when`, §4.6), not ours: it is\n * the line the site prints, `date_note` included, and a second formatter here would disagree\n * with it sooner or later.\n */\nconst props = withDefaults(defineProps<{ base?: string }>(), { base: '/events' })\n\nconst context = useAdmin()\nconst api = createEventsApi(context)\nconst route = useRoute()\nconst router = useRouter()\nconst locales = useLocales()\nconst dates = useDates()\nuseEventsMessages()\n\nconst t = useTranslate('webx-events')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\n/** How long after the last keystroke the draft goes to the server. */\nconst PAUSE = 1200\n\nconst id = computed(() => Number(route.params.id))\nconst list = computed(() => props.base)\n\nconst event = ref<EventRow | null>(null)\nconst values = ref<ScreenModel>({})\nconst revision = ref('')\nconst prefix = ref<string | null>(null)\nconst previewUrl = ref<string | null>(null)\n\nconst loading = ref(true)\nconst saving = ref(false)\nconst working = ref(false)\nconst errors = ref<Record<string, string[]>>({})\nconst conflict = ref<EventConflict | null>(null)\n\nconst snapshot = ref('')\n\nconst canManage = computed(() => context.can('events.manage'))\n\n/** Closed for writing: no permission, or a publication in flight. */\nconst locked = computed(() => !canManage.value || working.value)\n\nconst current = computed(() => JSON.stringify(values.value))\nconst dirty = computed(() => snapshot.value !== '' && current.value !== snapshot.value)\n\nconst state = computed<'saving' | 'unsaved' | 'saved'>(() => {\n if (saving.value) return 'saving'\n\n return dirty.value ? 'unsaved' : 'saved'\n})\n\n/** The name follows the field rather than the answer: a rename shows at the top as it is typed. */\nconst title = computed(() => {\n const written = values.value.title as LocalizedValue | string | null | undefined\n\n return localizedValue(written, locales.active.value, event.value?.title ?? '')\n})\n\n/**\n * Under the name: when the event is, as the site prints it, and since when it is on the site —\n * the two facts the badges beside the name cannot carry.\n */\nconst subtitle = computed(() => {\n const row = event.value\n\n if (!row) return ''\n\n const parts = [row.when]\n\n if (row.status === 'published' || row.status === 'modified') {\n parts.push(t('event.live-since', { date: dates.short(row.published_at) }))\n }\n\n return parts.filter((part) => part !== '').join(' · ')\n})\n\n/* The event as the owner of its relations: nothing it links to can be the event itself. */\nprovideRelationOwner({ type: 'event', id: computed(() => event.value?.id ?? null) })\n\n/* The address field is the panel's shared one (`wx-slug`), and this is what it prints. */\nprovideRecordAddress({\n values,\n prefix,\n path: computed(() => event.value?.path),\n moving: () => t('event.address-moving'),\n})\n\nprovideEventEditor({ event, canManage: canManage.value, reload: () => load(true) })\n\nfunction take(detail: EventDetail): void {\n // An event of days is its calendar dates, not two moments in the site's zone: see `days.ts`.\n const taken = withDaysAsWritten(detail.values)\n\n event.value = detail.event\n values.value = taken\n revision.value = detail.revision\n prefix.value = detail.prefix\n previewUrl.value = detail.preview_url\n snapshot.value = JSON.stringify(taken)\n conflict.value = null\n}\n\n/**\n * Read the event again — quietly unless this is the first time: the skeleton replaces the whole\n * screen, and a publication answered with it would throw the editor back onto the first tab.\n */\nasync function load(silent = false): Promise<void> {\n if (!silent) loading.value = true\n\n try {\n take(await api.get(id.value))\n } catch (error) {\n toast.danger(message(error))\n void router.push(list.value)\n } finally {\n loading.value = false\n }\n}\n\n/* One pending save at a time, and one pending pause. */\nlet timer: ReturnType<typeof setTimeout> | undefined\n\nfunction schedule(): void {\n if (!canManage.value || conflict.value || !dirty.value) return\n\n clearTimeout(timer)\n timer = setTimeout(() => void save(), PAUSE)\n}\n\n/** A field was left: write now rather than at the end of a pause nobody is waiting through. */\nfunction onFocusOut(): void {\n if (!canManage.value || conflict.value || !dirty.value || saving.value) return\n\n clearTimeout(timer)\n void save()\n}\n\nasync function save(): Promise<void> {\n if (!event.value || saving.value || !canManage.value) return\n\n clearTimeout(timer)\n\n // What is being sent, so that whatever is typed while the request is in flight stays dirty and\n // gets its own save rather than being marked as written.\n const sending = current.value\n const sent = values.value\n\n saving.value = true\n errors.value = {}\n\n try {\n const detail = await api.save(event.value.id, { values: sent, revision: revision.value })\n\n event.value = detail.event\n revision.value = detail.revision\n prefix.value = detail.prefix\n previewUrl.value = detail.preview_url\n conflict.value = null\n\n if (current.value === sending) snapshot.value = sending\n } catch (error) {\n const failure = error as {\n status?: number\n body?: EventConflict & { errors?: Record<string, string[]> }\n }\n\n if (failure.status === 409 && failure.body?.data) {\n conflict.value = failure.body\n } else if (failure.body?.errors) {\n errors.value = failure.body.errors\n toast.danger(t('event.save-failed'))\n } else {\n toast.danger(message(error))\n }\n } finally {\n saving.value = false\n }\n}\n\n/** Give up what was typed and take the event as it now is. */\nasync function takeTheirs(): Promise<void> {\n const agreed = await confirm({\n title: t('event.conflict-theirs-title'),\n message: t('event.conflict-theirs-text'),\n confirmText: t('event.conflict-theirs'),\n cancelText: t('panel.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n await load(true)\n}\n\n/** Keep what was typed and write it over the other version — which is still in the history. */\nasync function keepMine(): Promise<void> {\n const theirs = conflict.value\n\n if (!theirs) return\n\n revision.value = theirs.data.revision\n conflict.value = null\n\n await save()\n}\n\n/** Throw away what is waiting — asked about, because it is the one button here that loses writing. */\nasync function discard(): Promise<void> {\n if (!event.value) return\n\n const agreed = await confirm({\n title: t('event.discard-title'),\n message: t('event.discard-text'),\n confirmText: t('event.discard'),\n cancelText: t('panel.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n working.value = true\n\n try {\n take(await api.discard(event.value.id))\n toast.success(t('event.discarded'))\n } catch (error) {\n toast.danger(message(error))\n } finally {\n working.value = false\n }\n}\n\n/** Publishing is asked about, because it is the one action here that visitors see. */\nasync function publish(): Promise<void> {\n const row = event.value\n\n if (!row) return\n\n const agreed = await confirm({\n title: t('event.publish-title', { title: title.value || row.title }),\n message:\n row.path === null\n ? t('event.publish-nowhere')\n : t('event.publish-text', { address: `/${row.path}` }),\n confirmText: t('panel.publish'),\n cancelText: t('panel.cancel'),\n })\n\n if (!agreed) return\n\n if (dirty.value) await save()\n if (conflict.value) return\n\n working.value = true\n\n try {\n await api.publish(row.id)\n await load(true)\n toast.success(t('panel.published'))\n } catch (error) {\n toast.danger(message(error))\n } finally {\n working.value = false\n }\n}\n\n/**\n * Another date, the same event: a copy as a draft, opened at once (decision 9). What is typed\n * here goes first — the copy is made from what the server holds, and an edit still in the pause\n * would be in the original and missing from the copy.\n */\nasync function duplicate(): Promise<void> {\n const row = event.value\n\n if (!row) return\n\n if (dirty.value) await save()\n if (conflict.value || dirty.value) return\n\n working.value = true\n\n try {\n const copy = await api.duplicate(row.id)\n\n toast.success(t('event.duplicated'))\n // The same route with another id: the editor stays and reads the copy (`watch(id)`).\n await router.push({ path: `${list.value}/${copy.event.id}`, query: { ...route.query } })\n } catch (error) {\n toast.danger(message(error))\n } finally {\n working.value = false\n }\n}\n\nfunction badge(): 'default' | 'success' {\n const status = event.value?.status\n\n return status === 'published' || status === 'modified' ? 'success' : 'default'\n}\n\n/* The browser's own guard: it cannot wait for a request, so all it can do is ask. */\nfunction leaveGuard(unload: BeforeUnloadEvent): void {\n if (dirty.value) unload.preventDefault()\n}\n\nwatch(current, schedule)\nwatch(id, () => void load())\n\nonMounted(() => {\n void load()\n window.addEventListener('beforeunload', leaveGuard)\n})\n\nonBeforeUnmount(() => {\n clearTimeout(timer)\n window.removeEventListener('beforeunload', leaveGuard)\n})\n\n/**\n * Leaving flushes the pause rather than asking about it; the question is asked only when the save\n * did not go through — a conflict, a refused field, a server that is not there.\n */\nonBeforeRouteLeave(async () => {\n if (!dirty.value || !canManage.value) return true\n\n await save()\n\n if (!dirty.value) return true\n\n return await confirm({\n title: t('event.leave-title'),\n message: t('event.leave-text'),\n confirmText: t('event.leave'),\n cancelText: t('panel.cancel'),\n tone: 'danger',\n })\n})\n\n/* What leads away from the event. On a phone the head folds them into the ···. */\nconst actions = computed<ScreenAction[]>(() => {\n const leads: ScreenAction[] = []\n\n if (previewUrl.value) {\n leads.push({ key: 'preview', label: t('event.preview'), icon: 'eye', href: previewUrl.value })\n }\n\n if (event.value?.url && event.value.status !== 'draft') {\n leads.push({\n key: 'site',\n label: t('panel.open-on-site'),\n icon: 'link',\n href: event.value.url,\n })\n }\n\n // Throwing away writing lives in the ···, never beside \"publish\", and only while there is a\n // difference between what is written and what is on the site.\n if (event.value?.status === 'modified') {\n leads.push({\n key: 'discard',\n label: t('event.discard'),\n icon: 'refresh',\n danger: true,\n menu: true,\n run: () => void discard(),\n })\n }\n\n return leads\n})\n</script>\n\n<template>\n <div class=\"wx-event-editor\" @focusout=\"onFocusOut\">\n <template v-if=\"loading || !event\">\n <wx-skeleton class=\"wx-event-editor__ghost\" title :rows=\"1\" />\n <wx-card><wx-skeleton :rows=\"8\" /></wx-card>\n </template>\n\n <template v-else>\n <wx-screen-head\n divider\n :back=\"list\"\n :back-label=\"t('module.events')\"\n :title=\"title || t('event.untitled')\"\n :subtitle=\"subtitle\"\n :actions=\"actions\"\n >\n <template #trail>\n <wx-breadcrumb size=\"sm\" :label=\"t('event.trail')\">\n <wx-breadcrumb-item :as=\"'router-link'\" :to=\"list\">\n {{ t('module.events') }}\n </wx-breadcrumb-item>\n <wx-breadcrumb-item current>{{ title || t('event.untitled') }}</wx-breadcrumb-item>\n </wx-breadcrumb>\n </template>\n\n <template #title-after>\n <wx-badge :type=\"badge()\" dot>{{ t(`panel.status-${event.status}`) }}</wx-badge>\n <wx-badge v-if=\"event.status === 'modified'\" type=\"primary\" round>\n {{ t('panel.edits') }}\n </wx-badge>\n <!-- Over, and still a page of the site: it is a photo report now (decision 6). -->\n <wx-badge v-if=\"event.past\" round>{{ t('panel.past') }}</wx-badge>\n </template>\n </wx-screen-head>\n\n <!-- Somebody else wrote while this editor was typing. Both versions still exist, so the\n question is which one the site gets — a question, not a toast that disappears. -->\n <wx-alert\n v-if=\"conflict\"\n type=\"warning\"\n :title=\"t('event.conflict-title')\"\n :description=\"conflict.message\"\n >\n <template #actions>\n <wx-button size=\"sm\" variant=\"outline\" @click=\"takeTheirs\">\n {{ t('event.conflict-theirs') }}\n </wx-button>\n <wx-button size=\"sm\" type=\"primary\" @click=\"keepMine\">\n {{ t('event.conflict-mine') }}\n </wx-button>\n </template>\n </wx-alert>\n\n <wx-screen v-model=\"values\" name=\"events.form\" :errors=\"errors\" :disabled=\"locked\" />\n\n <wx-action-bar v-if=\"canManage\">\n <template #state>\n <wx-save-state :state=\"state\" />\n </template>\n\n <!-- The way to the next date of the same event: there are no series (decision 9). -->\n <wx-button\n variant=\"text\"\n :disabled=\"working\"\n :aria-label=\"t('panel.duplicate')\"\n @click=\"duplicate\"\n >\n <template #icon><wx-icon name=\"copy\" /></template>\n <span class=\"wx-event-editor__word\">{{ t('panel.duplicate') }}</span>\n </wx-button>\n\n <wx-button variant=\"outline\" :loading=\"saving\" :disabled=\"!dirty\" @click=\"save\">\n {{ t('event.save') }}\n </wx-button>\n\n <wx-button\n type=\"primary\"\n :loading=\"working\"\n :disabled=\"event.status === 'published' && !dirty\"\n @click=\"publish\"\n >\n {{ t('panel.publish') }}\n </wx-button>\n </wx-action-bar>\n </template>\n </div>\n</template>\n\n<style scoped>\n/*\n * No constructor here, so nothing needs the height of the window: the page scrolls, and the\n * panel gives a screen with an action bar a floor tall enough for the bar to sit at the bottom\n * (`WxMain`, CLAUDE.md §4 on `data-wx-fill`).\n */\n.wx-event-editor {\n display: flex;\n flex-direction: column;\n gap: var(--wx-gap, var(--wx-space-16));\n min-height: 0;\n container-type: inline-size;\n}\n\n.wx-event-editor__ghost {\n max-width: 420px;\n}\n\n/*\n * On a phone the bar holds the state and three buttons, and with the word the third one pushes\n * the state onto a line of its own — a strip of nothing over the buttons (CLAUDE.md §4 on\n * `WxActionBar`). The copy icon is enough there; the word stays for a screen reader.\n */\n@container (max-width: 480px) {\n .wx-event-editor__word {\n display: none;\n }\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\nimport { useAdmin, useErrorText, useTranslate, WxDate } from '@webx-ui/module-admin'\nimport { confirm, toast, WxBadge, WxButton, WxEmpty, WxSkeleton, WxText } from '@webx-ui/core'\nimport { createEventsApi } from './api'\nimport { useEventEditor } from './editor'\nimport { useEventsMessages } from './i18n'\nimport type { EventVersion } from './types'\n\n/**\n * What was published, when, by whom and from where — publications only; the autosaves are\n * insurance, not history.\n *\n * Restoring makes the old version the draft. Publishing it is the same separate step it always\n * is, so nothing here changes the site by itself.\n */\ndefineOptions({ name: 'WxEventHistory' })\n\nconst context = useAdmin()\nconst api = createEventsApi(context)\nconst editor = useEventEditor()\nuseEventsMessages()\n\nconst t = useTranslate('webx-events')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\nconst versions = ref<EventVersion[] | null>(null)\nconst working = ref(false)\n\nconst event = computed(() => editor?.event.value ?? null)\nconst canManage = computed(() => editor?.canManage ?? false)\n\n/** The newest publication is the site — while the event is on it at all. */\nconst live = computed(() =>\n event.value?.status === 'published' || event.value?.status === 'modified'\n ? (versions.value?.[0]?.number ?? null)\n : null,\n)\n\nasync function load(): Promise<void> {\n const current = event.value\n\n if (!current) return\n\n try {\n versions.value = await api.versions(current.id)\n } catch (error) {\n versions.value = []\n toast.danger(message(error))\n }\n}\n\nasync function restore(version: EventVersion): Promise<void> {\n const current = event.value\n\n if (!current) return\n\n const agreed = await confirm({\n title: t('event.restore-title', { number: version.number }),\n message: t('event.restore-text'),\n confirmText: t('event.restore-version'),\n cancelText: t('panel.cancel'),\n })\n\n if (!agreed) return\n\n working.value = true\n\n try {\n await api.restoreVersion(current.id, version.number)\n // Through the editor rather than with the answer: the values it holds are what the form is\n // bound to, and a version restored underneath it has to reach the fields.\n await editor?.reload()\n toast.success(t('event.restored-version', { number: version.number }))\n } catch (error) {\n toast.danger(message(error))\n } finally {\n working.value = false\n }\n}\n\n// The event is read again after every publication and every restore, so its stamp is the\n// signal that there is something new to list.\nwatch(\n () => [event.value?.id, event.value?.published_at, event.value?.updated_at],\n () => void load(),\n { immediate: true },\n)\n</script>\n\n<template>\n <div class=\"wx-event-history\">\n <wx-skeleton v-if=\"versions === null\" class=\"wx-event-history__ghost\" :rows=\"3\" />\n <wx-empty v-else-if=\"versions.length === 0\" :description=\"t('event.history-empty')\" />\n <div\n v-for=\"version in versions\"\n v-else\n :key=\"version.number\"\n class=\"wx-event-history__row\"\n :class=\"{ 'is-live': version.number === live }\"\n >\n <code class=\"wx-event-history__number\">{{\n t('event.version', { number: version.number })\n }}</code>\n <div class=\"wx-event-history__who\">\n <wx-date v-if=\"version.created_at\" :value=\"version.created_at\" size=\"md\" tone=\"default\" />\n <wx-text size=\"sm\" tone=\"muted\">\n {{ version.author ?? t(`event.source-${version.source}`) }}\n <template v-if=\"version.comment\"> · {{ version.comment }}</template>\n </wx-text>\n </div>\n <wx-badge v-if=\"version.number === live\" type=\"success\" dot>{{\n t('event.version-live')\n }}</wx-badge>\n <wx-button\n v-if=\"canManage && version.number !== live\"\n size=\"sm\"\n variant=\"outline\"\n :loading=\"working\"\n @click=\"restore(version)\"\n >\n {{ t('event.restore-version') }}\n </wx-button>\n </div>\n </div>\n</template>\n\n<style scoped>\n.wx-event-history {\n display: flex;\n flex-direction: column;\n border: 1px solid var(--wx-border-default);\n border-radius: var(--wx-radius-md);\n background: var(--wx-bg-surface);\n overflow: hidden;\n}\n\n.wx-event-history__ghost {\n padding: var(--wx-space-10) var(--wx-space-12);\n}\n\n.wx-event-history__row {\n display: flex;\n align-items: center;\n gap: var(--wx-space-12);\n padding: var(--wx-space-10) var(--wx-space-12);\n border-block-end: 1px solid var(--wx-border-default);\n flex-wrap: wrap;\n}\n\n.wx-event-history__row:last-child {\n border-block-end: 0;\n}\n\n.wx-event-history__number {\n width: 40px;\n font-family: var(--wx-font-family-mono);\n font-size: var(--wx-font-size-sm);\n color: var(--wx-text-muted);\n}\n\n.wx-event-history__who {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-width: 160px;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { nextTick, onMounted, ref, useTemplateRef } from 'vue'\nimport { useAdmin, useTranslate } from '@webx-ui/module-admin'\nimport { useModal, WxButton, WxDialog, WxFormItem, WxInput, WxSpace } from '@webx-ui/core'\nimport { createEventsApi } from './api'\nimport { useEventsMessages } from './i18n'\nimport type { EventRow } from './types'\n\n/**\n * A new event: what it is called, and nothing else. The server makes the address out of the\n * title; the address field on the settings tab is where there is room to say what changing it\n * costs.\n */\nconst { open, resolve, dismiss } = useModal<EventRow>()\n\nconst context = useAdmin()\nconst api = createEventsApi(context)\nuseEventsMessages()\n\nconst t = useTranslate('webx-events')\n\nconst title = ref('')\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\nasync function submit(): Promise<void> {\n if (title.value.trim() === '' || saving.value) return\n\n saving.value = true\n errors.value = {}\n\n try {\n resolve((await api.create({ title: title.value.trim() })).event)\n } catch (error) {\n // The address is what can be refused here, and it is refused under the title that made it:\n // the registry sees the categories and the pages too, so \"taken\" can mean taken by either.\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 v-model:open=\"open\" :title=\"t('panel.new-title')\" :width=\"440\">\n <wx-form-item\n :label=\"t('panel.field-title')\"\n :error=\"errors.title?.[0] ?? errors.slug?.[0]\"\n required\n >\n <div ref=\"field\">\n <wx-input v-model=\"title\" :aria-label=\"t('panel.field-title')\" @keyup.enter=\"submit\" />\n </div>\n </wx-form-item>\n\n <template #footer>\n <wx-space size=\"sm\">\n <wx-button variant=\"outline\" @click=\"dismiss()\">{{ t('panel.cancel') }}</wx-button>\n <wx-button type=\"primary\" :loading=\"saving\" :disabled=\"title.trim() === ''\" @click=\"submit\">\n {{ t('panel.create') }}\n </wx-button>\n </wx-space>\n </template>\n </wx-dialog>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, ref, useTemplateRef, watch } from 'vue'\nimport { useRoute, useRouter, type LocationQueryRaw } from 'vue-router'\nimport {\n rowMenuWidth,\n useAdmin,\n useDates,\n useErrorText,\n useTranslate,\n WxDate,\n WxFilterChips,\n WxListScreen,\n WxRowMenu,\n type AppliedFilter,\n type RowAction,\n type ScreenAction,\n} from '@webx-ui/module-admin'\nimport {\n confirm,\n createModal,\n toast,\n useElementWidth,\n WxBadge,\n WxButton,\n WxEntityCard,\n WxFormItem,\n WxIcon,\n WxSelect,\n WxTable,\n WxText,\n WxTooltip,\n type TabItem,\n type TableColumn,\n type TableState,\n type TabValue,\n} from '@webx-ui/core'\nimport { createEventsApi } from './api'\nimport EventCreateDialog from './EventCreateDialog.vue'\nimport { useEventsMessages } from './i18n'\nimport type { EventRow, EventStatus, EventsPage, EventWhen } from './types'\n\n/**\n * The section: a page of events with the filters of §4.9 over it.\n *\n * A paginator and not a sortable list, which is the whole difference from recipes: events have\n * no order of their own — the date is the order (decision 4) — and they pile up for years, so the\n * screen is twenty rows, the filters and a page number.\n *\n * The tabs are *when*, not the state: the upcoming ones are what an editor comes here for and\n * what the site's lists show, so they are the list with nothing chosen. The past ones are a tab\n * away, and \"all\" draws the past ones muted, so the line between the two can be seen at a glance.\n * The state, the category and the service live behind the funnel.\n *\n * Below {@link CARDS} the row becomes a card, the way the articles do it: the cover on the left,\n * everything else beside it.\n */\nconst props = withDefaults(defineProps<{ base?: string }>(), { base: '/events' })\n\n/** Where the row stops being a row. Below it the table draws cards and this screen draws one. */\nconst CARDS = 640\n\nconst context = useAdmin()\nconst api = createEventsApi(context)\nconst route = useRoute()\nconst router = useRouter()\nconst dates = useDates()\nuseEventsMessages()\n\nconst t = useTranslate('webx-events')\n/* The two words a list needs as soon as it has filters belong to the panel, not to the events. */\nconst admin = useTranslate('webx-admin')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\nconst root = useTemplateRef<HTMLElement>('root')\nconst width = useElementWidth(root)\n\nconst page = ref<EventsPage | null>(null)\nconst loading = ref(true)\n\nconst create = createModal<EventRow, Record<string, never>>(EventCreateDialog)\n\nconst canManage = computed(() => context.can('events.manage'))\n\nconst title = computed(\n () =>\n context.state.manifest?.modules.find((module) => module.id === 'events')?.title ??\n t('module.events'),\n)\n\n/**\n * Everything the list is looking at lives in the address, so coming back from an event lands on\n * the same tab, the same filters and the same page.\n */\nconst view = computed<TabValue>({\n get: () => (typeof route.query.view === 'string' ? route.query.view : ''),\n set: (value) => {\n // A new view is a new list: the page it was on belongs to the view it was on.\n void router.replace({\n query: { ...route.query, view: value === '' ? undefined : String(value), page: undefined },\n })\n },\n})\n\nconst inBin = computed(() => view.value === 'trashed')\n\n/** The bin holds whatever was deleted, whenever it was to happen. */\nconst when = computed<EventWhen>(() => {\n if (inBin.value || view.value === 'all') return 'all'\n\n return view.value === 'past' ? 'past' : 'upcoming'\n})\n\nconst query = computed(() => ({\n q: typeof route.query.q === 'string' ? route.query.q : '',\n category: number(route.query.category),\n service: number(route.query.service),\n status: (typeof route.query.status === 'string' ? route.query.status : '') as EventStatus | '',\n page: Number(route.query.page ?? 1) || 1,\n}))\n\nfunction number(value: unknown): number | null {\n return typeof value === 'string' && value !== '' ? Number(value) || null : null\n}\n\nconst views = computed<TabItem[]>(() => [\n { value: '', label: t('panel.view-upcoming') },\n { value: 'past', label: t('panel.view-past') },\n { value: 'all', label: t('panel.view-all') },\n { value: 'trashed', label: t('panel.bin'), icon: 'trash' },\n])\n\nconst filters = computed(() => page.value?.filters ?? { categories: [], services: null })\n\nconst statuses = computed(() =>\n (['published', 'draft', 'unpublished'] as const).map((status) => ({\n value: status,\n label: t(`panel.status-${status}`),\n })),\n)\n\nconst asCards = computed(() => width.value > 0 && width.value < CARDS)\n\n/**\n * Six columns, or one. Every column but the title carries a width and the table is laid out\n * `fixed`, so the ones that drop below a breakpoint give what they had to the title (CLAUDE.md §4\n * on `WxActions collapse` in a table). They go in the order they can be spared — the categories\n * first — and what is left is the cover, the title, when and the state: what an editor scanning\n * a list of events is looking for.\n */\nconst columns = computed<TableColumn<EventRow>[]>(() => {\n if (asCards.value) {\n return [{ key: 'card', label: '' }]\n }\n\n return [\n { key: 'cover', label: '', width: 68 },\n /* The one that takes what the others leave, and the only one without a width. */\n { key: 'title', label: t('panel.column-title'), minWidth: 220 },\n {\n key: 'when',\n label: inBin.value ? t('panel.column-deleted') : t('panel.column-when'),\n width: 210,\n hideBelow: 760,\n },\n { key: 'categories', label: t('panel.column-categories'), width: 190, hideBelow: 1080 },\n { key: 'status', label: t('panel.column-status'), width: 150 },\n { key: 'actions', label: '', width: rowMenuWidth, align: 'right' },\n ]\n})\n\nconst emptyText = computed(() => {\n if (query.value.q !== '') return t('panel.empty-search')\n if (inBin.value) return t('panel.empty-bin')\n if (view.value === 'past') return t('panel.empty-past')\n\n return view.value === 'all' ? t('panel.empty') : t('panel.empty-upcoming')\n})\n\n/**\n * What the dropdowns behind the funnel are set to, said in the reader's words. A shut panel says\n * nothing about itself, and a list narrowed by something nobody can see is a list that looks\n * wrong.\n */\nconst applied = computed<AppliedFilter[]>(() => {\n const chips: AppliedFilter[] = []\n\n if (query.value.status !== '') {\n chips.push({\n key: 'status',\n label: `${t('panel.filter-status')}: ${t(`panel.status-${query.value.status}`)}`,\n clear: () => narrow('status', undefined),\n })\n }\n\n const category = filters.value.categories.find((item) => item.id === query.value.category)\n if (category) {\n chips.push({\n key: 'category',\n label: `${t('panel.filter-category')}: ${category.title}`,\n clear: () => narrow('category', undefined),\n })\n }\n\n const service = filters.value.services?.find((item) => item.id === query.value.service)\n if (service) {\n chips.push({\n key: 'service',\n label: `${t('panel.filter-service')}: ${service.title}`,\n clear: () => narrow('service', undefined),\n })\n }\n\n return chips\n})\n\nfunction clearFilters(): void {\n void router.replace({\n query: {\n ...route.query,\n status: undefined,\n category: undefined,\n service: undefined,\n page: undefined,\n },\n })\n}\n\nasync function load(state?: TableState): Promise<void> {\n loading.value = true\n\n try {\n page.value = await api.list({\n when: when.value,\n q: state?.search ?? query.value.q,\n status: inBin.value ? '' : query.value.status,\n trashed: inBin.value,\n category: query.value.category,\n service: query.value.service,\n page: state?.page ?? query.value.page,\n per_page: state?.perPage,\n })\n } catch (error) {\n toast.danger(message(error))\n } finally {\n loading.value = false\n }\n}\n\n/**\n * The table reports everything it knows in one event, and that is the one place the address is\n * written from — so a page and a search both survive opening an event and coming back.\n */\nfunction onState(state: TableState): void {\n const wanted: LocationQueryRaw = {\n ...route.query,\n q: state.search === '' ? undefined : state.search,\n page: state.page === 1 ? undefined : String(state.page),\n }\n\n void router.replace({ query: wanted })\n void load(state)\n}\n\n/** One of the dropdowns moved: a new list, from its first page. */\nfunction narrow(name: 'status' | 'category' | 'service', value: unknown): void {\n void router.replace({\n query: {\n ...route.query,\n [name]:\n typeof value === 'number' || (typeof value === 'string' && value !== '')\n ? String(value)\n : undefined,\n page: undefined,\n },\n })\n}\n\n/*\n * Sources one by one, not a getter returning an array: a fresh array is a new value on every change\n * of the address, and a page turned by the table would be read a second time without its size.\n */\nwatch(\n [view, () => query.value.status, () => query.value.category, () => query.value.service],\n () => void load(),\n)\n\nvoid load()\n\n/** The bin never reaches here: the table is told its rows lead nowhere (CLAUDE.md §4). */\nfunction open(event: EventRow): void {\n void router.push({ path: `${props.base}/${event.id}`, query: { ...route.query } })\n}\n\n/** A new event: the dialog asks for a title, the editor opens. */\nasync function add(): Promise<void> {\n const made = await create({})\n\n if (made) open(made)\n}\n\nfunction actionsFor(event: EventRow): RowAction[] {\n if (!canManage.value) return []\n\n if (inBin.value) {\n return [\n {\n key: 'restore',\n icon: 'refresh',\n label: t('panel.restore'),\n run: () => void restore(event),\n },\n ]\n }\n\n const actions: RowAction[] = [\n { key: 'open', icon: 'edit', label: t('panel.open'), run: () => open(event) },\n {\n key: 'duplicate',\n icon: 'copy',\n label: t('panel.duplicate'),\n run: () => void duplicate(event),\n },\n ]\n\n if (event.url && event.status !== 'draft') {\n actions.push({\n key: 'site',\n icon: 'external-link',\n label: t('panel.open-on-site'),\n href: event.url,\n })\n }\n\n actions.push(\n event.status === 'published' || event.status === 'modified'\n ? {\n key: 'unpublish',\n icon: 'eye-off',\n label: t('panel.unpublish'),\n run: () => void run(() => api.unpublish(event.id), t('panel.unpublished-done')),\n }\n : {\n key: 'publish',\n icon: 'upload',\n label: t('panel.publish'),\n run: () => void run(() => api.publish(event.id), t('panel.published')),\n },\n )\n\n actions.push({\n key: 'delete',\n icon: 'trash',\n label: t('panel.delete'),\n danger: true,\n run: () => void remove(event),\n })\n\n return actions\n}\n\n/**\n * The next date of the same event (decision 9): a copy as a draft, opened straight away — the\n * date is the first thing that differs, and it is on the editor, not here.\n */\nasync function duplicate(event: EventRow): Promise<void> {\n try {\n const copy = await api.duplicate(event.id)\n\n toast.success(t('event.duplicated'))\n open(copy.event)\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\nasync function remove(event: EventRow): Promise<void> {\n const agreed = await confirm({\n title: t('panel.delete-title', { title: event.title }),\n message: t('panel.delete-text'),\n confirmText: t('panel.delete'),\n cancelText: t('panel.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n await run(() => api.remove(event.id), t('panel.deleted'))\n}\n\nasync function restore(event: EventRow): Promise<void> {\n await run(() => api.restore(event.id), t('panel.restored'))\n}\n\n/** One request, one sentence about it, and the list as the server now sees it. */\nasync function run(request: () => Promise<unknown>, said: string): Promise<void> {\n try {\n await request()\n toast.success(said)\n } catch (error) {\n toast.danger(message(error))\n } finally {\n await load()\n }\n}\n\n/* Live and live-with-edits are the same green; what is waiting has a chip of its own. */\nfunction badge(status: EventStatus): 'default' | 'success' {\n return status === 'published' || status === 'modified' ? 'success' : 'default'\n}\n\nfunction address(event: EventRow): string {\n if (inBin.value) return '—'\n\n return event.path === null ? t('panel.no-address') : `/${event.path}`\n}\n\n/**\n * The exact moments behind the printed line, in the reader's own zone — the line is the site's\n * (§4.6), in the site's zone and with `date_note` over it, and the tip is where \"when exactly\" is\n * answered. Not for an event of days: its line is the dates already, and the moments behind it —\n * midnight and the last second in the site's zone — would name other days in the reader's.\n */\nfunction exact(event: EventRow): string {\n if (event.starts_at === null || event.all_day) return ''\n\n const start = dates.exact(event.starts_at)\n\n return event.ends_at === null ? start : `${start} – ${dates.exact(event.ends_at)}`\n}\n\n/** The line in the column: what the site prints, or a word for an event with no date at all. */\nfunction whenOf(event: EventRow): string {\n return event.when !== '' ? event.when : t('panel.no-date')\n}\n\n/* In \"all\" the past ones are muted, so the line between the two halves shows. */\nfunction rowClass(event: EventRow): string | undefined {\n return event.past && view.value === 'all' ? 'is-past' : undefined\n}\n\n/* What the section offers. Declared, because on a phone the head folds it into the ···. */\nconst actions = computed<ScreenAction[]>(() =>\n canManage.value\n ? [{ key: 'new', label: t('panel.new'), icon: 'plus', primary: true, run: () => void add() }]\n : [],\n)\n</script>\n\n<template>\n <div ref=\"root\" class=\"wx-events\">\n <wx-list-screen v-model:view=\"view\" :title=\"title\" :views=\"views\" :actions=\"actions\">\n <wx-table\n :data=\"page\"\n :columns=\"columns\"\n row-key=\"id\"\n searchable\n :clickable=\"!inBin\"\n :hover=\"!inBin\"\n flush\n :loading=\"loading\"\n layout=\"fixed\"\n :cards-below=\"CARDS\"\n :row-class=\"rowClass\"\n :filters-count=\"applied.length\"\n :filters-label=\"admin('filters.title')\"\n :search-placeholder=\"t('panel.search')\"\n :empty-text=\"emptyText\"\n :aria-label=\"title\"\n @row-click=\"open\"\n @state-change=\"onState\"\n >\n <template #filters>\n <wx-form-item v-if=\"!inBin\" :label=\"t('panel.filter-status')\">\n <wx-select\n :model-value=\"query.status === '' ? null : query.status\"\n :options=\"statuses\"\n :placeholder=\"t('panel.any-status')\"\n clearable\n size=\"sm\"\n @update:model-value=\"(value: unknown) => narrow('status', value)\"\n />\n </wx-form-item>\n\n <wx-form-item :label=\"t('panel.filter-category')\">\n <wx-select\n :model-value=\"query.category\"\n :options=\"filters.categories.map((item) => ({ value: item.id, label: item.title }))\"\n :placeholder=\"t('panel.any-category')\"\n clearable\n size=\"sm\"\n @update:model-value=\"(value: unknown) => narrow('category', value)\"\n />\n </wx-form-item>\n\n <!-- `null` from the server: the services module is not installed, and there is nothing\n an event could be related to. -->\n <wx-form-item v-if=\"filters.services !== null\" :label=\"t('panel.filter-service')\">\n <wx-select\n :model-value=\"query.service\"\n :options=\"filters.services.map((item) => ({ value: item.id, label: item.title }))\"\n :placeholder=\"t('panel.any-service')\"\n clearable\n filterable\n size=\"sm\"\n @update:model-value=\"(value: unknown) => narrow('service', value)\"\n />\n </wx-form-item>\n\n <wx-button v-if=\"applied.length > 0\" variant=\"text\" size=\"sm\" block @click=\"clearFilters\">\n <template #icon><wx-icon name=\"close\" /></template>\n {{ admin('filters.reset') }}\n </wx-button>\n </template>\n\n <template #applied>\n <wx-filter-chips :filters=\"applied\" />\n </template>\n\n <template #cell-cover=\"{ row }\">\n <span class=\"wx-events__cover\" :class=\"{ 'is-empty': !row.cover?.thumb }\">\n <img v-if=\"row.cover?.thumb\" :src=\"row.cover.thumb\" alt=\"\" loading=\"lazy\" />\n <wx-icon v-else name=\"calendar\" size=\"sm\" />\n </span>\n </template>\n\n <template #cell-title=\"{ row }\">\n <div class=\"wx-events__name\">\n <span class=\"wx-events__title\">{{ row.title }}</span>\n <span class=\"wx-events__address\">{{ address(row) }}</span>\n </div>\n </template>\n\n <template #cell-when=\"{ row }\">\n <wx-date v-if=\"inBin\" :value=\"row.deleted_at\" compact />\n <wx-tooltip v-else-if=\"exact(row)\" :content=\"exact(row)\">\n <span class=\"wx-events__when\">{{ whenOf(row) }}</span>\n </wx-tooltip>\n <span v-else class=\"wx-events__when\" :class=\"{ 'is-none': row.when === '' }\">\n {{ whenOf(row) }}\n </span>\n </template>\n\n <template #cell-categories=\"{ row }\">\n <span class=\"wx-events__chips\">\n <template v-for=\"(chip, index) in row.categories\" :key=\"chip.id\">\n <wx-tooltip v-if=\"index === 0\" :content=\"t('panel.main-category')\">\n <wx-badge size=\"sm\" round type=\"primary\">{{ chip.title }}</wx-badge>\n </wx-tooltip>\n <wx-badge v-else size=\"sm\" round>{{ chip.title }}</wx-badge>\n </template>\n </span>\n </template>\n\n <template #cell-status=\"{ row }\">\n <span class=\"wx-events__state\">\n <wx-badge :type=\"badge(row.status)\" dot size=\"sm\">\n {{ t(`panel.status-${row.status}`) }}\n </wx-badge>\n <wx-badge v-if=\"row.status === 'modified'\" type=\"primary\" size=\"sm\" round>\n {{ t('panel.edits') }}\n </wx-badge>\n </span>\n </template>\n\n <!-- The card: the cover on the left, everything else beside it. One field and no label,\n because the table's own stack of labelled lines is five lines for one event. -->\n <template #cell-card=\"{ row }\">\n <wx-entity-card\n class=\"wx-events__entity\"\n variant=\"plain\"\n :title=\"row.title\"\n :image=\"row.cover?.thumb ?? undefined\"\n image-size=\"56px\"\n :title-lines=\"2\"\n :subtitle=\"address(row)\"\n >\n <template #meta>\n <wx-text size=\"sm\" :tone=\"row.past ? 'muted' : 'default'\">\n {{ inBin ? dates.short(row.deleted_at) : whenOf(row) }}\n </wx-text>\n <wx-badge :type=\"badge(row.status)\" dot size=\"sm\">\n {{ t(`panel.status-${row.status}`) }}\n </wx-badge>\n <wx-badge v-if=\"row.categories[0]\" size=\"sm\" round>\n {{ row.categories[0].title }}\n </wx-badge>\n </template>\n </wx-entity-card>\n </template>\n\n <template #card-actions=\"{ row }\">\n <wx-row-menu :actions=\"actionsFor(row)\" :label=\"row.title\" />\n </template>\n\n <template #cell-actions=\"{ row }\">\n <wx-row-menu :actions=\"actionsFor(row)\" :label=\"row.title\" />\n </template>\n </wx-table>\n </wx-list-screen>\n </div>\n</template>\n\n<style scoped>\n.wx-events {\n min-width: 0;\n}\n\n/* The picture's box, never the picture: `<img>` has `min-width: auto` (CLAUDE.md §4). */\n.wx-events__cover {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 44px;\n height: 32px;\n overflow: hidden;\n border-radius: var(--wx-radius-xs);\n background: var(--wx-bg-subtle);\n color: var(--wx-text-muted);\n}\n\n.wx-events__cover.is-empty {\n border: 1px dashed var(--wx-border-default);\n background: var(--wx-bg-surface);\n}\n\n.wx-events__cover img {\n width: 100%;\n height: 100%;\n min-width: 0;\n object-fit: cover;\n}\n\n.wx-events__name {\n display: flex;\n flex-direction: column;\n min-width: 0;\n}\n\n.wx-events__title {\n overflow: hidden;\n font-weight: var(--wx-font-weight-medium);\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n\n.wx-events__address {\n overflow: hidden;\n color: var(--wx-text-muted);\n font-family: var(--wx-font-family-mono);\n font-size: var(--wx-font-size-xs);\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n\n.wx-events__when {\n font-size: var(--wx-font-size-sm);\n}\n\n.wx-events__when.is-none {\n color: var(--wx-text-muted);\n}\n\n.wx-events__chips,\n.wx-events__state {\n display: flex;\n align-items: center;\n gap: var(--wx-space-4);\n min-width: 0;\n overflow: hidden;\n}\n\n/*\n * A past event in \"all\": the whole row a step quieter rather than a badge of its own — \"is this\n * still ahead\" is answered by the eye running down the list, not by reading. `:deep()` because\n * the row is the table's element and the class is ours (CLAUDE.md §4).\n */\n.wx-events :deep(.is-past) .wx-events__title,\n.wx-events :deep(.is-past) .wx-events__when {\n color: var(--wx-text-muted);\n}\n\n.wx-events :deep(.is-past) .wx-events__cover {\n opacity: 0.6;\n}\n</style>\n","import { categoryRoutes, type AdminModule, type CategoriesOptions } from '@webx-ui/module-admin'\nimport EventEditorPage from './EventEditorPage.vue'\nimport EventHistory from './EventHistory.vue'\nimport EventsPage from './EventsPage.vue'\n\nexport interface EventsOptions {\n /** Where the events live inside the panel. The categories sit under it. */\n path?: string\n}\n\n/**\n * The categories of events as the panel's shared category screens see them: where they answer,\n * what they are edited on and what this module calls them. Exported so that a panel mounting the\n * screens somewhere of its own does not have to repeat the words.\n */\nexport function eventCategoriesOptions(path = '/events'): CategoriesOptions {\n return {\n api: 'events/categories',\n path: `${path}/categories`,\n name: 'webx.events.categories',\n module: 'event-categories',\n screen: 'events.category-form',\n manage: 'events.categories.manage',\n count: 'events_count',\n // Every event of the category, past ones included: \"what is filed here\" is the question.\n items: (id) => ({ path, query: { category: String(id), view: 'all' } }),\n words: {\n new: 'webx-events::category.new',\n empty: 'webx-events::category.empty',\n 'empty-help': 'webx-events::category.empty-help',\n order: 'webx-events::category.order',\n hidden: 'webx-events::category.hidden',\n 'no-address': 'webx-events::category.no-address',\n count: 'webx-events::category.events',\n 'show-items': 'webx-events::category.show-events',\n 'delete-blocked': 'webx-events::category.delete-blocked',\n 'delete-text': 'webx-events::category.delete-text',\n deleted: 'webx-events::category.deleted',\n saved: 'webx-events::category.saved',\n 'field-title': 'webx-events::category.field-title',\n 'field-slug': 'webx-events::category.field-slug',\n 'address-moving': 'webx-events::category.address-moving',\n },\n }\n}\n\n/**\n * The events as sections of the panel: the events and their categories (§4.9).\n *\n * Two modules rather than one, because the navigation is one entry per module; the server puts\n * them in the `events` group, which is what draws them under one heading. A section whose server\n * half is not installed never appears — the entry is built from the manifest.\n */\nexport function events(options: EventsOptions = {}): AdminModule[] {\n const path = options.path ?? '/events'\n\n return [\n {\n id: 'events',\n path,\n routes: [\n { path, name: 'webx.events', component: EventsPage, props: { base: path } },\n {\n path: `${path}/:id(\\\\d+)`,\n name: 'webx.events.edit',\n component: EventEditorPage,\n props: { base: path },\n },\n ],\n /*\n * The one part of `events.form` only this module can draw. Everything else on it is the\n * panel's (`wx-slug`, `wx-categories`, `wx-relations`, `wx-gallery`, `wx-rich-text`,\n * `wx-repeater`, `wx-date-picker`).\n */\n types: {\n 'wx-event-history': { component: EventHistory, kind: 'display' },\n },\n },\n {\n id: 'event-categories',\n path: `${path}/categories`,\n routes: categoryRoutes(eventCategoriesOptions(path)),\n },\n ]\n}\n"],"names":["EVENTS_API","createEventsApi","admin","base","data","body","query","search","id","input","number","WALL","pad","value","width","onReadersClock","parts","year","month","day","hours","minutes","seconds","local","west","offset","withDaysAsWritten","values","eventEditorKey","provideEventEditor","editor","provide","useEventEditor","inject","eventsMessages","seeded","useEventsMessages","i18n","useAdmin","PAUSE","props","__props","context","api","route","useRoute","router","useRouter","locales","useLocales","dates","useDates","t","useTranslate","message","useErrorText","computed","list","event","ref","revision","prefix","previewUrl","loading","saving","working","errors","conflict","snapshot","canManage","locked","current","dirty","state","title","written","localizedValue","subtitle","row","part","provideRelationOwner","provideRecordAddress","load","take","detail","taken","silent","error","toast","timer","schedule","save","onFocusOut","sending","sent","failure","takeTheirs","confirm","keepMine","theirs","discard","publish","duplicate","copy","badge","status","leaveGuard","unload","watch","onMounted","onBeforeUnmount","onBeforeRouteLeave","actions","leads","_createElementBlock","_Fragment","_createVNode","_unref","WxSkeleton","WxCard","WxScreenHead","WxBreadcrumb","WxBreadcrumbItem","_createTextVNode","_toDisplayString","WxBadge","_createBlock","WxAlert","WxButton","WxScreen","$event","WxActionBar","WxSaveState","WxIcon","_createElementVNode","_hoisted_1","versions","live","restore","version","_openBlock","WxEmpty","_renderList","_hoisted_2","_hoisted_3","WxDate","WxText","open","resolve","dismiss","useModal","field","useTemplateRef","nextTick","submit","WxDialog","WxSpace","WxFormItem","WxInput","CARDS","root","useElementWidth","page","create","createModal","EventCreateDialog","module","view","inBin","when","views","filters","statuses","asCards","columns","rowMenuWidth","emptyText","applied","chips","narrow","category","item","service","clearFilters","onState","wanted","name","add","made","actionsFor","run","remove","request","said","address","exact","start","whenOf","rowClass","WxListScreen","WxTable","WxSelect","_cache","WxFilterChips","_withCtx","_hoisted_4","WxTooltip","_hoisted_5","_normalizeClass","_hoisted_6","chip","index","_hoisted_7","WxEntityCard","WxRowMenu","eventCategoriesOptions","path","events","options","EventsPage","EventEditorPage","EventHistory","categoryRoutes"],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import { Messages } from '@webx-ui/module-admin';
2
+ /**
3
+ * The English this package speaks on its own, before the server's dictionary arrives, or without
4
+ * one. The server ships the same lines in ten languages under `webx-events::` and overrides
5
+ * these; `messages.test.ts` keeps the two sets of keys equal.
6
+ */
7
+ export declare const eventsMessages: Record<string, Messages>;
@@ -0,0 +1,19 @@
1
+ import { AdminModule, CategoriesOptions } from '@webx-ui/module-admin';
2
+ export interface EventsOptions {
3
+ /** Where the events live inside the panel. The categories sit under it. */
4
+ path?: string;
5
+ }
6
+ /**
7
+ * The categories of events as the panel's shared category screens see them: where they answer,
8
+ * what they are edited on and what this module calls them. Exported so that a panel mounting the
9
+ * screens somewhere of its own does not have to repeat the words.
10
+ */
11
+ export declare function eventCategoriesOptions(path?: string): CategoriesOptions;
12
+ /**
13
+ * The events as sections of the panel: the events and their categories (§4.9).
14
+ *
15
+ * Two modules rather than one, because the navigation is one entry per module; the server puts
16
+ * them in the `events` group, which is what draws them under one heading. A section whose server
17
+ * half is not installed never appears — the entry is built from the manifest.
18
+ */
19
+ export declare function events(options?: EventsOptions): AdminModule[];
package/dist/style.css ADDED
@@ -0,0 +1 @@
1
+ .wx-event-editor[data-v-4df973ae]{display:flex;flex-direction:column;gap:var(--wx-gap, var(--wx-space-16));min-height:0;container-type:inline-size}.wx-event-editor__ghost[data-v-4df973ae]{max-width:420px}@container (max-width: 480px){.wx-event-editor__word[data-v-4df973ae]{display:none}}.wx-event-history[data-v-222cbd42]{display:flex;flex-direction:column;border:1px solid var(--wx-border-default);border-radius:var(--wx-radius-md);background:var(--wx-bg-surface);overflow:hidden}.wx-event-history__ghost[data-v-222cbd42]{padding:var(--wx-space-10) var(--wx-space-12)}.wx-event-history__row[data-v-222cbd42]{display:flex;align-items:center;gap:var(--wx-space-12);padding:var(--wx-space-10) var(--wx-space-12);border-block-end:1px solid var(--wx-border-default);flex-wrap:wrap}.wx-event-history__row[data-v-222cbd42]:last-child{border-block-end:0}.wx-event-history__number[data-v-222cbd42]{width:40px;font-family:var(--wx-font-family-mono);font-size:var(--wx-font-size-sm);color:var(--wx-text-muted)}.wx-event-history__who[data-v-222cbd42]{display:flex;flex-direction:column;flex:1;min-width:160px}.wx-events[data-v-1a916120]{min-width:0}.wx-events__cover[data-v-1a916120]{display:flex;align-items:center;justify-content:center;width:44px;height:32px;overflow:hidden;border-radius:var(--wx-radius-xs);background:var(--wx-bg-subtle);color:var(--wx-text-muted)}.wx-events__cover.is-empty[data-v-1a916120]{border:1px dashed var(--wx-border-default);background:var(--wx-bg-surface)}.wx-events__cover img[data-v-1a916120]{width:100%;height:100%;min-width:0;object-fit:cover}.wx-events__name[data-v-1a916120]{display:flex;flex-direction:column;min-width:0}.wx-events__title[data-v-1a916120]{overflow:hidden;font-weight:var(--wx-font-weight-medium);white-space:nowrap;text-overflow:ellipsis}.wx-events__address[data-v-1a916120]{overflow:hidden;color:var(--wx-text-muted);font-family:var(--wx-font-family-mono);font-size:var(--wx-font-size-xs);white-space:nowrap;text-overflow:ellipsis}.wx-events__when[data-v-1a916120]{font-size:var(--wx-font-size-sm)}.wx-events__when.is-none[data-v-1a916120]{color:var(--wx-text-muted)}.wx-events__chips[data-v-1a916120],.wx-events__state[data-v-1a916120]{display:flex;align-items:center;gap:var(--wx-space-4);min-width:0;overflow:hidden}.wx-events[data-v-1a916120] .is-past .wx-events__title,.wx-events[data-v-1a916120] .is-past .wx-events__when{color:var(--wx-text-muted)}.wx-events[data-v-1a916120] .is-past .wx-events__cover{opacity:.6}
@@ -0,0 +1,105 @@
1
+ import { Paginated } from '@webx-ui/core';
2
+ import { ScreenModel } from '@webx-ui/schema';
3
+ /**
4
+ * Never published · on the site · on the site with edits waiting · taken off it — the same four a
5
+ * service and a recipe have (§4.10).
6
+ */
7
+ export type EventStatus = 'draft' | 'published' | 'modified' | 'unpublished';
8
+ /**
9
+ * Which events the list is looking at (decision 6): the ones still ahead — the default, and what
10
+ * the site's lists show — the ones behind, or both.
11
+ */
12
+ export type EventWhen = 'upcoming' | 'past' | 'all';
13
+ /** A category as a row of the list names one. The first one is the main one. */
14
+ export interface EventTermRef {
15
+ id: number;
16
+ title: string;
17
+ }
18
+ /**
19
+ * One event as the section lists it (§4.10): the draft's title beside the registry's address,
20
+ * and the date both as the moments it is kept as and as the line the site prints (§4.6).
21
+ */
22
+ export interface EventRow {
23
+ id: number;
24
+ title: string;
25
+ slug: string;
26
+ /** `null` — no address in the language the panel is open in. */
27
+ path: string | null;
28
+ url: string | null;
29
+ /** The first picture of the gallery, which is what every card of the site shows. */
30
+ cover: {
31
+ thumb: string | null;
32
+ } | null;
33
+ /** ISO 8601 with its offset, or `null` for an event whose date is not set (decision 3). */
34
+ starts_at: string | null;
35
+ ends_at: string | null;
36
+ all_day: boolean;
37
+ /** The date the way the site prints it, `date_note` included — `Rendering\When`. */
38
+ when: string;
39
+ /** The end, or the start when there is no end, is behind us (decision 7). */
40
+ past: boolean;
41
+ status: EventStatus;
42
+ /** In the order they were chosen in: the first is the main one. */
43
+ categories: EventTermRef[];
44
+ published_at: string | null;
45
+ updated_at: string | null;
46
+ deleted_at: string | null;
47
+ /** What this event was when it was read, so a save can be refused rather than written over. */
48
+ revision?: string;
49
+ }
50
+ /**
51
+ * A page of events, as Laravel's paginator has it — events pile up for years, and nothing here is
52
+ * dragged into order (decision 4). The filters travel with it; `services` is `null` on a site
53
+ * without the services module, and then there is no such filter.
54
+ */
55
+ export interface EventsPage extends Paginated<EventRow> {
56
+ filters: {
57
+ categories: EventTermRef[];
58
+ services: EventTermRef[] | null;
59
+ };
60
+ }
61
+ export interface EventQuery {
62
+ when?: EventWhen;
63
+ /** Title or address, in any language the site has. */
64
+ q?: string;
65
+ category?: number | null;
66
+ service?: number | null;
67
+ status?: EventStatus | '';
68
+ /** What was deleted. The only way back to an event in the bin is through this list. */
69
+ trashed?: boolean;
70
+ page?: number;
71
+ per_page?: number;
72
+ }
73
+ export interface EventInput {
74
+ title: string;
75
+ slug?: string;
76
+ }
77
+ /** The values of `events.form` keyed by field name, and the revision they were read at. */
78
+ export interface EventSave {
79
+ values: ScreenModel;
80
+ revision?: string;
81
+ }
82
+ /** One event as its editor opens it. */
83
+ export interface EventDetail {
84
+ event: EventRow;
85
+ values: ScreenModel;
86
+ revision: string;
87
+ /** The first segment of every event address (`webx-events.prefix`); never empty. */
88
+ prefix: string;
89
+ /** `null` without `module-blocks`, which owns the preview by token. */
90
+ preview_url: string | null;
91
+ }
92
+ /** A 409: somebody wrote in between. `data` is the event as it now is. */
93
+ export interface EventConflict {
94
+ message: string;
95
+ data: EventDetail;
96
+ }
97
+ /** One publication in the history. */
98
+ export interface EventVersion {
99
+ number: number;
100
+ created_at: string | null;
101
+ author: string | null;
102
+ source: 'panel' | 'mcp' | 'import' | string;
103
+ comment: string | null;
104
+ is_pinned: boolean;
105
+ }
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@webx-ui/module-events",
3
+ "version": "0.1.0",
4
+ "description": "Events for the WebX UI admin panel: master classes, meetings and webinars with a date, a place, a price and a booking link, flat categories with pages of their own, and the services they belong to.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "sideEffects": [
8
+ "*.css"
9
+ ],
10
+ "keywords": [
11
+ "webx-ui",
12
+ "vue",
13
+ "admin",
14
+ "cms",
15
+ "events"
16
+ ],
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/webx-ui/webx-ui.git",
20
+ "directory": "packages/module-events"
21
+ },
22
+ "homepage": "https://webx-ui.github.io/webx-ui/guide/events.html",
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "files": [
27
+ "dist"
28
+ ],
29
+ "main": "./dist/index.js",
30
+ "module": "./dist/index.js",
31
+ "types": "./dist/index.d.ts",
32
+ "exports": {
33
+ ".": {
34
+ "types": "./dist/index.d.ts",
35
+ "import": "./dist/index.js"
36
+ },
37
+ "./style.css": "./dist/style.css",
38
+ "./dist/style.css": "./dist/style.css",
39
+ "./package.json": "./package.json"
40
+ },
41
+ "peerDependencies": {
42
+ "vue": "^3.5.0",
43
+ "vue-router": "^4.5.0"
44
+ },
45
+ "dependencies": {
46
+ "@webx-ui/core": "^0.33.3",
47
+ "@webx-ui/module-admin": "^0.18.0",
48
+ "@webx-ui/schema": "^0.6.1"
49
+ },
50
+ "devDependencies": {
51
+ "@types/node": "^24.10.1",
52
+ "@vitejs/plugin-vue": "^6.0.1",
53
+ "typescript": "^5.9.3",
54
+ "vite": "^7.1.14",
55
+ "vite-plugin-dts": "^4.5.4",
56
+ "vue": "^3.5.24",
57
+ "vue-router": "^4.5.1",
58
+ "vue-tsc": "^3.1.3"
59
+ },
60
+ "scripts": {
61
+ "build": "vite build",
62
+ "dev": "vite build --watch",
63
+ "typecheck": "vue-tsc -p tsconfig.json --noEmit"
64
+ }
65
+ }