@webx-ui/module-admin 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/ListScreen.vue","../src/screenTypes.ts","../src/admin.ts","../src/i18n.ts","../src/AdminNav.vue","../src/AdminShell.vue","../src/http.ts","../src/messages.ts","../src/createAdmin.ts","../src/dates.ts","../src/errors.ts","../src/Screen.vue","../src/RowMenu.vue","../src/DateText.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport {\n WxCard,\n WxHeading,\n WxTabs,\n type CardPadding,\n type TabItem,\n type TabValue,\n} from '@webx-ui/core'\n\n/**\n * The frame every list of the panel is drawn in.\n *\n * One shape for all of them, because five sections that each answered \"where does the heading\n * go\" on their own gave five answers — a heading inside the card on `Administrators`, two\n * headings on `SEO`, a search outside the card on `Blocks`, no heading at all on `Files`. A\n * reader who learns one list should already know the next.\n *\n * The shape is: the section's name on the left of its own line, the one action the section\n * exists for on the right of it, the views of the list as tabs under that, and a card holding\n * nothing but the rows. The card has no heading of its own — the tab says which view it is,\n * and the line above says which section (§19). The search stays inside the table, where it is\n * a property of the rows rather than of the screen (§10).\n *\n * What it deliberately does not do is fetch, filter or page. It is a frame; the section still\n * owns its data, and the tabs only say which view is open.\n */\nwithDefaults(\n defineProps<{\n /** The section's name. The one heading on the screen. */\n title?: string\n /**\n * The views of the same list — \"All / Drafts / Published\", \"Rules / Redirects\". Not a\n * navigation between screens: everything under them is the same table.\n */\n views?: TabItem[]\n /** Wrap the rows in a card. Off for a section that brings its own. */\n card?: boolean\n /** Padding of that card; `WxCard`'s own default when not given. */\n padding?: CardPadding\n /** The screen is as tall as the column it is drawn in — a file manager, a board. */\n fill?: boolean\n /** Container width below which the views fold into one switch. */\n collapseBelow?: number\n }>(),\n {\n title: undefined,\n views: undefined,\n card: true,\n padding: undefined,\n fill: false,\n collapseBelow: 560,\n },\n)\n\ndefineSlots<{\n /** The rows: a table, a grid of cards, a manager. */\n default?: () => unknown\n /**\n * The screen's buttons, beside the heading. One of them — the reason the section exists —\n * is a filled primary button with a word on it; everything else is outlined or an icon.\n */\n actions?: () => unknown\n}>()\n\nconst view = defineModel<TabValue | undefined>('view', { default: undefined })\n</script>\n\n<template>\n <div class=\"wx-list-screen\" :class=\"{ 'is-fill': fill }\" :data-wx-fill=\"fill ? '' : undefined\">\n <div v-if=\"title || $slots.actions\" class=\"wx-list-screen__head\">\n <wx-heading v-if=\"title\" :level=\"2\" class=\"wx-list-screen__title\">{{ title }}</wx-heading>\n <div v-if=\"$slots.actions\" class=\"wx-list-screen__actions\">\n <slot name=\"actions\" />\n </div>\n </div>\n\n <!--\n `items` rather than a tab each with its own panel: switching a view must not take the\n table away and put a new one back, or the search somebody typed goes with it.\n -->\n <wx-tabs\n v-if=\"views && views.length > 0\"\n v-model=\"view\"\n class=\"wx-list-screen__views\"\n :items=\"views\"\n :collapse-below=\"collapseBelow\"\n :aria-label=\"title\"\n >\n <wx-card v-if=\"card\" class=\"wx-list-screen__card\" :padding=\"padding\">\n <slot />\n </wx-card>\n <slot v-else />\n </wx-tabs>\n\n <template v-else>\n <wx-card v-if=\"card\" class=\"wx-list-screen__card\" :padding=\"padding\">\n <slot />\n </wx-card>\n <slot v-else />\n </template>\n </div>\n</template>\n\n<style scoped>\n.wx-list-screen {\n display: flex;\n flex-direction: column;\n gap: var(--wx-gap, var(--wx-space-16));\n min-width: 0;\n /* The width that decides the layout is the screen's own, not the window's. */\n container-type: inline-size;\n}\n\n.wx-list-screen__head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--wx-space-12);\n flex-wrap: wrap;\n}\n\n.wx-list-screen__title {\n min-width: 0;\n}\n\n.wx-list-screen__actions {\n display: flex;\n align-items: center;\n gap: var(--wx-space-8);\n flex-wrap: wrap;\n}\n\n/*\n * On a phone the name takes the first line and the buttons the second, full width: three\n * things elbowing each other across 343 px is not a row, and a primary button too narrow to\n * hold its word is not a button.\n */\n@container (max-width: 480px) {\n .wx-list-screen__head > * {\n flex: 1 1 100%;\n }\n\n .wx-list-screen__actions > * {\n flex: 1 1 auto;\n }\n}\n\n/*\n * A screen that fills gives the height to the rows, not to the heading over them.\n *\n * The chain has to be spelled out all the way down, because a percentage inside a box that\n * does not know its own height resolves to nothing at all (CLAUDE.md §4) — and with tabs the\n * boxes in between belong to `WxTabs`, so `:deep()` is what reaches them. A scoped rule stops\n * at this component's own elements and would silently match nothing.\n */\n.wx-list-screen.is-fill {\n min-height: 0;\n}\n\n.wx-list-screen.is-fill > :last-child {\n flex: 1 1 auto;\n min-height: 0;\n}\n\n.wx-list-screen.is-fill :deep(.wx-tabs),\n.wx-list-screen.is-fill :deep(.wx-tabs__layout),\n.wx-list-screen.is-fill :deep(.wx-tabs__panels) {\n display: flex;\n flex-direction: column;\n flex: 1 1 auto;\n min-height: 0;\n}\n\n.wx-list-screen.is-fill :deep(.wx-tabs__panel),\n.wx-list-screen.is-fill :deep(.wx-tabs__panel > *) {\n flex: 1 1 auto;\n min-height: 0;\n}\n\n/* The bar is the one part that keeps its own height. */\n.wx-list-screen.is-fill :deep(.wx-tabs__bar) {\n flex: 0 0 auto;\n}\n</style>\n","import type { TypeRegistry } from '@webx-ui/schema'\nimport ListScreen from './ListScreen.vue'\n\n/**\n * Node types the panel itself brings, under every module's and every project's.\n *\n * `wx-list` is the frame a section's list is drawn in — the heading, the one action beside it,\n * the views as tabs, the card under them. It is a node type and not five copies of the same\n * markup because that is what stops the next section from inventing a sixth shape: a list\n * screen is described, and what a module supplies is the rows inside it (§19 of the visual\n * spec, §10.3 for the tabs).\n *\n * The rows themselves are still a module's own type — `wx-pages-table`, `wx-media` — because\n * only the module knows what it is listing and where it asks for it.\n */\nexport const adminTypes: TypeRegistry = {\n 'wx-list': { component: ListScreen, kind: 'layout', labelProp: 'title' },\n}\n","import { computed, inject, reactive, type App, type ComputedRef, type InjectionKey } from 'vue'\nimport type { Patch, ScreenNode, TypeRegistry } from '@webx-ui/schema'\nimport { adminTypes } from './screenTypes'\nimport type { Http } from './http'\nimport type { I18n } from './i18n'\nimport type { AdminModule, AdminStatus, AdminUser, Manifest, NavEntry, NavGroup } from './types'\n\nexport interface AdminContext {\n /** The panel's own backend. */\n readonly http: Http\n /** Where the panel is served — the router's base. */\n readonly basePath: string\n /** Where its JSON lives, so a module does not have to be told twice. */\n readonly apiPath: string\n readonly state: AdminState\n /** The interface's own words, and the languages it can be shown in. */\n readonly i18n: I18n\n /** Modules registered on the front end, whether or not the server reports them. */\n readonly modules: readonly AdminModule[]\n /** Navigation, in the order the server gave, for the modules that exist on both sides. */\n readonly nav: ComputedRef<NavEntry[]>\n /** The same navigation, with the top-level entries first and then each group's. */\n readonly groups: ComputedRef<{ top: NavEntry[]; groups: NavGroup[] }>\n /** Node types every screen is drawn with: the modules' and the project's, over the core. */\n readonly types: TypeRegistry\n /** Ask the server what the panel is and who is signed in again. */\n reload(): Promise<void>\n /**\n * Draw the panel in another language: fetches that dictionary and remembers the choice for\n * the next visit. Storing it against the administrator is an auth module's business — this\n * only changes what is on screen.\n */\n setLocale(code: string): Promise<void>\n /** Filled in by an auth module; `null` means nobody is signed in. */\n setUser(user: AdminUser | null): void\n /**\n * How the panel finds out who is signed in, set by an auth module. Without one the panel\n * simply asks for the manifest and lets a 401 answer the question.\n */\n useSessionLoader(loader: () => Promise<AdminUser | null>): void\n can(permission: string): boolean\n /**\n * A screen by name, as the server hands it out — patched, cut to this administrator's\n * permissions, translated. Fetched once per name and language for the session.\n */\n loadScreen(name: string): Promise<ScreenNode[]>\n /** The project's own operations over a screen, from `createAdmin({ screens })`. */\n screenPatch(name: string): Patch\n}\n\nexport interface AdminState {\n status: AdminStatus\n manifest: Manifest | null\n user: AdminUser | null\n error: string | null\n}\n\nexport const adminKey: InjectionKey<AdminContext> = Symbol('webx-admin')\n\nexport function useAdmin(): AdminContext {\n const admin = inject(adminKey, null)\n\n if (admin === null) {\n throw new Error('useAdmin() was called outside a panel created by createAdmin().')\n }\n\n return admin\n}\n\n/**\n * Permissions are flattened by the server, so a check is a lookup. A super administrator\n * carries no permissions and passes everything — the same rule as on the server, in the one\n * place the front end asks the question.\n */\nexport function createAdminContext(options: {\n http: Http\n basePath: string\n apiPath: string\n modules: AdminModule[]\n i18n: I18n\n loadManifest: () => Promise<Manifest>\n loadDictionary?: (locale: string) => Promise<void>\n types?: TypeRegistry\n screens?: Record<string, Patch>\n}): AdminContext {\n const state = reactive<AdminState>({\n status: 'loading',\n manifest: null,\n user: null,\n error: null,\n })\n\n const nav = computed<NavEntry[]>(() => {\n const manifest = state.manifest\n\n if (manifest === null) {\n return []\n }\n\n const entries: NavEntry[] = []\n\n for (const module of manifest.modules) {\n const registered = options.modules.find((candidate) => candidate.id === module.id)\n\n // A module the server has and the front end does not is not a bug worth shouting\n // about — the panel is assembled from two halves and they are deployed separately —\n // but it has nowhere to send anybody, so it stays out of the menu.\n if (registered === undefined) {\n continue\n }\n\n const path = registered.path ?? registered.routes?.[0]?.path\n\n if (path === undefined) {\n continue\n }\n\n entries.push({\n id: module.id,\n title: module.title,\n icon: module.icon,\n path,\n group: module.group ?? null,\n })\n }\n\n return entries\n })\n\n const groups = computed(() => {\n const declared = state.manifest?.groups ?? []\n const top: NavEntry[] = []\n const byGroup = new Map<string, NavEntry[]>()\n\n for (const entry of nav.value) {\n // A group the server never declared is not a group: the entry stays at the top rather\n // than vanishing under a heading nobody can name.\n if (entry.group !== null && declared.some((group) => group.id === entry.group)) {\n const list = byGroup.get(entry.group) ?? []\n list.push(entry)\n byGroup.set(entry.group, list)\n } else {\n top.push(entry)\n }\n }\n\n return {\n top,\n groups: declared\n .filter((group) => byGroup.has(group.id))\n .map((group) => ({\n id: group.id,\n title: group.title,\n entries: byGroup.get(group.id) ?? [],\n })),\n }\n })\n\n // The core types are the renderer's own default; what is merged here is only what the panel\n // adds — its own frame, then a module's, then the project's, which therefore wins.\n const types: TypeRegistry = { ...adminTypes }\n\n for (const module of options.modules) {\n Object.assign(types, module.types)\n }\n\n Object.assign(types, options.types)\n\n const screens = new Map<string, Promise<ScreenNode[]>>()\n\n async function loadScreen(name: string): Promise<ScreenNode[]> {\n // The tree is translated on the server, so a screen is one thing per language.\n const key = `${options.i18n.state.locale}:${name}`\n let pending = screens.get(key)\n\n if (pending === undefined) {\n pending = options.http\n .get<{ data: { screen: string; root: ScreenNode[] } }>(`${options.apiPath}/screens/${name}`)\n .then((body) => body.data.root)\n .catch((error: unknown) => {\n // A failed request is not worth remembering: the next page open asks again.\n screens.delete(key)\n throw error\n })\n screens.set(key, pending)\n }\n\n return pending\n }\n\n let loadSession: (() => Promise<AdminUser | null>) | null = null\n\n async function reload(): Promise<void> {\n state.status = 'loading'\n state.error = null\n\n try {\n if (loadSession !== null) {\n state.user = await loadSession()\n\n // Asking for the manifest as a stranger would only produce the 401 we already know\n // about, and a spurious one in the network log for whoever is debugging.\n if (state.user === null) {\n state.manifest = null\n state.status = 'unauthenticated'\n\n return\n }\n }\n\n const manifest = await options.loadManifest()\n\n state.manifest = manifest\n options.i18n.state.contentLocales = manifest.locales ?? []\n options.i18n.state.panelLocales = manifest.panelLocales ?? options.i18n.state.panelLocales\n\n // The administrator's own choice, which the sign-in screen had no way of knowing: it\n // drew itself in whatever the browser asked for.\n if (manifest.locale !== undefined && manifest.locale !== options.i18n.state.locale) {\n await setLocale(manifest.locale)\n }\n\n state.status = 'ready'\n } catch (error) {\n // 401 is not a failure: it is the panel finding out nobody is signed in, which is the\n // normal way a visit starts.\n if (isUnauthenticated(error)) {\n state.manifest = null\n state.user = null\n state.status = 'unauthenticated'\n\n return\n }\n\n state.error = error instanceof Error ? error.message : String(error)\n state.status = 'error'\n }\n }\n\n async function setLocale(code: string): Promise<void> {\n if (options.loadDictionary === undefined) {\n options.i18n.state.locale = code\n\n return\n }\n\n await options.loadDictionary(code)\n\n // The dictionary is not the whole of the interface. Section titles are translated on the\n // server and travel inside the manifest, which was fetched in the previous language — so\n // without this the panel switches everything except its own navigation, and the sidebar\n // goes on naming the section in the language nobody is reading any more until the page is\n // reloaded. Only worth doing once there is a manifest to replace: during the first load\n // the caller is `reload()` itself, which is about to fetch one.\n if (state.manifest !== null) {\n try {\n state.manifest = await options.loadManifest()\n } catch {\n // A manifest that will not come back is `reload()`'s problem to report. The language\n // did change, and a stale section title is not worth throwing away a working panel.\n }\n }\n }\n\n return {\n http: options.http,\n basePath: options.basePath,\n apiPath: options.apiPath,\n state,\n i18n: options.i18n,\n modules: options.modules,\n nav,\n groups,\n types,\n reload,\n setLocale,\n setUser(user) {\n state.user = user\n },\n useSessionLoader(loader) {\n loadSession = loader\n },\n can(permission) {\n const user = state.user\n\n if (user === null) {\n return false\n }\n\n return user.isSuper || user.permissions.includes(permission)\n },\n loadScreen,\n screenPatch(name) {\n return options.screens?.[name] ?? []\n },\n }\n}\n\nexport function provideAdmin(app: App, admin: AdminContext): void {\n app.provide(adminKey, admin)\n}\n\nfunction isUnauthenticated(error: unknown): boolean {\n return (\n typeof error === 'object' &&\n error !== null &&\n 'status' in error &&\n (error as { status: unknown }).status === 401\n )\n}\n","import { inject, reactive, type App, type InjectionKey } from 'vue'\n\n/**\n * One language the panel can be drawn in. Mirrors what `GET /api/cms/locales` answers with.\n */\nexport interface LocaleDescriptor {\n code: string\n name: string\n /** The language's name in itself — what belongs in a language picker. */\n nativeName: string\n direction: 'ltr' | 'rtl'\n default: boolean\n}\n\n/** A group of lines, nested as deeply as the `lang` file that produced it. */\nexport type Messages = { [key: string]: string | Messages }\n\n/** Namespace → group → lines, which is the shape the server assembles. */\nexport type Dictionary = Record<string, Record<string, Messages>>\n\nexport interface I18nState {\n /** The language the interface is being drawn in. */\n locale: string\n /** Where a missing line is looked for next. */\n fallback: string\n /** Languages the interface can be switched to. */\n panelLocales: LocaleDescriptor[]\n /** Languages the site publishes content in — every editing screen is built around this. */\n contentLocales: LocaleDescriptor[]\n}\n\nexport type Translate = (key: string, params?: Record<string, string | number>) => string\n\nexport interface I18n {\n readonly state: I18nState\n /**\n * Strings a package ships in its own code, used until the server's dictionary arrives and\n * for whatever the dictionary does not carry.\n *\n * This is what lets a package work with no server at all — a story, a test, a panel\n * assembled by hand — and what stops a missing translation from showing a key to somebody.\n */\n defaults(namespace: string, messages: Record<string, Messages>): void\n /** Replace the dictionary with what the server sent. */\n load(dictionary: Dictionary, locale: string, fallback?: string): void\n /** A `t()` bound to one namespace, so a component writes `t('shell.loading')`. */\n scope(namespace: string): Translate\n /** Absolute form: `t('webx-admin::shell.loading')`. */\n t: Translate\n}\n\nexport const i18nKey: InjectionKey<I18n> = Symbol('webx-i18n')\n\nexport function useI18n(): I18n {\n const i18n = inject(i18nKey, null)\n\n if (i18n === null) {\n throw new Error('useI18n() was called outside a panel created by createAdmin().')\n }\n\n return i18n\n}\n\n/**\n * A component that may be used outside a panel — a login card placed by hand — needs a\n * translator either way. This gives it the package's own English when there is no panel.\n */\nexport function useTranslate(namespace: string): Translate {\n const i18n = inject(i18nKey, null)\n\n return i18n === null ? createI18n().scope(namespace) : i18n.scope(namespace)\n}\n\nexport function provideI18n(app: App, i18n: I18n): void {\n app.provide(i18nKey, i18n)\n}\n\nexport function createI18n(options: { locale?: string; fallback?: string } = {}): I18n {\n const fallback = options.fallback ?? 'en'\n\n const state = reactive<I18nState>({\n locale: options.locale ?? fallback,\n fallback,\n panelLocales: [],\n contentLocales: [],\n })\n\n // Kept apart from the dictionary rather than merged into it: the server's answer is\n // replaced wholesale on every language change, and built-in strings have to survive that.\n const builtIn: Dictionary = {}\n const dictionary = reactive<{ value: Dictionary }>({ value: {} })\n\n function lookup(source: Dictionary, namespace: string, path: string[]): string | null {\n let node: string | Messages | undefined = source[namespace]?.[path[0] ?? '']\n\n for (const segment of path.slice(1)) {\n if (typeof node !== 'object' || node === null) {\n return null\n }\n\n node = node[segment]\n }\n\n return typeof node === 'string' ? node : null\n }\n\n function translate(\n namespace: string,\n key: string,\n params?: Record<string, string | number>,\n ): string {\n // An absolute key wins, so one namespace can borrow a line from another without a second\n // translator.\n const [explicitNamespace, rest] = key.includes('::')\n ? (key.split('::', 2) as [string, string])\n : [namespace, key]\n\n const path = rest.split('.')\n\n const line =\n lookup(dictionary.value, explicitNamespace, path) ??\n lookup(builtIn, explicitNamespace, path) ??\n // Not an empty string: a key on screen is ugly, but it says which key, and a blank\n // label says nothing to anybody trying to fix it.\n key\n\n return params === undefined ? line : fill(line, params)\n }\n\n return {\n state,\n defaults(namespace, messages) {\n builtIn[namespace] = { ...builtIn[namespace], ...messages }\n },\n load(next, locale, nextFallback) {\n // Guarded rather than trusted: an answer that is not the shape expected should leave\n // the panel in English, not without any words at all.\n dictionary.value = next ?? {}\n state.locale = locale ?? state.locale\n\n if (nextFallback !== undefined) {\n state.fallback = nextFallback\n }\n },\n scope(namespace) {\n return (key, params) => translate(namespace, key, params)\n },\n t: (key, params) => translate('', key, params),\n }\n}\n\n/**\n * `:name` placeholders, the way Laravel writes them — the strings come from its `lang` files,\n * so they should read the same on both sides.\n */\nfunction fill(line: string, params: Record<string, string | number>): string {\n let filled = line\n\n for (const [name, value] of Object.entries(params)) {\n filled = filled.replaceAll(`:${name}`, String(value))\n }\n\n return filled\n}\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { useRoute, useRouter } from 'vue-router'\nimport { useAdmin } from './admin'\nimport { useTranslate } from './i18n'\n\n/**\n * The menu, built from the manifest rather than written out. What the panel offers is what the\n * installation actually has — adding a module on the server and installing its front end is\n * the whole of \"adding a section\".\n *\n * Sections come first, then the groups the server declared — \"System\" for what keeps the\n * panel running — each as a branch that opens on its own when a section under it is current.\n */\ndefineProps<{ collapsed?: boolean }>()\n\nconst emit = defineEmits<{ select: [] }>()\n\nconst admin = useAdmin()\nconst t = useTranslate('webx-admin')\nconst router = useRouter()\nconst route = useRoute()\n\nconst current = computed<string>({\n get: () => {\n const match = admin.nav.value.find((entry) => route.path.startsWith(entry.path))\n\n return match?.id ?? ''\n },\n set: (id) => {\n const entry = admin.nav.value.find((candidate) => candidate.id === id)\n\n if (entry !== undefined) {\n void router.push(entry.path)\n }\n },\n})\n</script>\n\n<template>\n <wx-menu\n v-model=\"current\"\n :collapsed=\"collapsed\"\n :label=\"t('nav.sections')\"\n @select=\"emit('select')\"\n >\n <wx-menu-item\n v-for=\"entry in admin.groups.value.top\"\n :key=\"entry.id\"\n :value=\"entry.id\"\n :icon=\"entry.icon ?? undefined\"\n :label=\"entry.title\"\n />\n\n <wx-submenu\n v-for=\"group in admin.groups.value.groups\"\n :key=\"group.id\"\n :value=\"`group:${group.id}`\"\n :title=\"group.title\"\n icon=\"gear\"\n >\n <wx-menu-item\n v-for=\"entry in group.entries\"\n :key=\"entry.id\"\n :value=\"entry.id\"\n :icon=\"entry.icon ?? undefined\"\n :label=\"entry.title\"\n />\n </wx-submenu>\n </wx-menu>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\nimport { shellLayoutFor, useResponsiveShell, WxToaster } from '@webx-ui/core'\nimport { useAdmin } from './admin'\nimport { useTranslate } from './i18n'\n\n/**\n * The panel around the screen: navigation built from the manifest and the hole the router\n * fills.\n *\n * Three shapes, chosen by the width of the shell rather than the window: the full sidebar on a\n * desktop, an icon rail on a tablet, a drawer behind a burger on a phone.\n *\n * The sidebar is the whole of the chrome on the two wider ones — brand at the top, menu in the\n * middle with its own scrollbar, the account at the bottom — and there is no bar across the top\n * at all: 56px of the window's height went on a logo and an avatar, which are two things a\n * column 220px wide has room for. A phone has no such column, so there the bar comes back.\n *\n * What scrolls is the page. The sidebar stands still beside it, and screens that have to fill\n * the window measure themselves against the window.\n *\n * It draws nothing until the manifest has arrived, and nothing but the route while nobody is\n * signed in — the sign-in screen is a route like any other, and it has no business being\n * wrapped in a menu of sections the visitor cannot reach.\n */\nconst admin = useAdmin()\nconst t = useTranslate('webx-admin')\nconst shellEl = ref<HTMLElement | null>(null)\n\nconst { width, layout, collapsed, showAside, drawerOpen, toggle, close } = useResponsiveShell(\n shellEl,\n { persist: 'webx-admin-shell' },\n)\n\nconst title = computed(() => admin.state.manifest?.title ?? '')\n\n/*\n * The client's logo, when the settings hold one. The name never leaves: it is the alt of the\n * picture, what stands in the corner until the picture arrives, and what stands there for good\n * if the file behind it is gone.\n */\nconst logo = computed(() => admin.state.manifest?.branding?.logo ?? null)\nconst mark = computed(() => admin.state.manifest?.branding?.mark ?? null)\n\n/*\n * The step the frame is spaced by — 8 on a phone, 12 on a tablet, 16 on a desktop — follows the\n * size of the screen and not the shape the menu is in. A sidebar somebody collapsed by hand on a\n * 1440px desktop is still a desktop, and air measured off the menu would shrink with it.\n */\nconst size = computed(() => shellLayoutFor(width.value, null))\n</script>\n\n<template>\n <wx-toaster />\n\n <div v-if=\"admin.state.status === 'unauthenticated'\" class=\"wx-root wx-admin-plain\">\n <router-view />\n </div>\n\n <div v-else-if=\"admin.state.status === 'loading'\" class=\"wx-root wx-admin-plain\">\n <wx-loading :label=\"t('shell.loading')\" />\n </div>\n\n <div v-else-if=\"admin.state.status === 'error'\" class=\"wx-root wx-admin-plain\">\n <wx-result status=\"error\" :title=\"t('shell.error-title')\" :description=\"admin.state.error\">\n <wx-button type=\"primary\" @click=\"admin.reload()\">{{ t('shell.retry') }}</wx-button>\n </wx-result>\n </div>\n\n <div v-else ref=\"shellEl\" class=\"wx-root wx-admin\" :class=\"`wx-admin--${size}`\">\n <wx-container\n :direction=\"layout === 'drawer' ? 'vertical' : 'horizontal'\"\n full-height\n class=\"wx-admin__frame\"\n >\n <!--\n The phone's bar. It carries what the sidebar carries everywhere else, in the same\n order across instead of down: the way into the menu, the brand, the account.\n -->\n <wx-header\n v-if=\"layout === 'drawer'\"\n class=\"wx-admin__header\"\n floating\n sticky\n :bordered=\"false\"\n padding=\"sm\"\n height=\"var(--wx-admin-header-height)\"\n >\n <wx-action icon=\"menu\" :title=\"t('nav.menu')\" @click=\"toggle\" />\n\n <slot name=\"brand\">\n <img\n v-if=\"logo\"\n class=\"wx-admin__logo\"\n :src=\"logo.url\"\n :alt=\"title\"\n :width=\"logo.width ?? undefined\"\n :height=\"logo.height ?? undefined\"\n />\n <wx-text v-else weight=\"semibold\" truncate>{{ title }}</wx-text>\n </slot>\n\n <template #end>\n <slot name=\"user\" />\n </template>\n </wx-header>\n\n <wx-aside\n v-if=\"showAside\"\n class=\"wx-admin__aside\"\n sticky\n floating\n scroll\n :bordered=\"false\"\n :collapsed=\"collapsed\"\n :width=\"220\"\n :collapsed-width=\"56\"\n >\n <template #top>\n <div class=\"wx-admin__brand\" :class=\"{ 'wx-admin__brand--stacked': collapsed && mark }\">\n <!--\n The rail has no room for a name, and the button is the one thing on it that\n has to stay: without it there is no way back to the full sidebar. A mark fits\n there, but not beside the button — 56px holds one of them at a time, so the two\n stand one above the other.\n -->\n <slot v-if=\"!collapsed\" name=\"brand\">\n <img\n v-if=\"logo\"\n class=\"wx-admin__logo\"\n :src=\"logo.url\"\n :alt=\"title\"\n :width=\"logo.width ?? undefined\"\n :height=\"logo.height ?? undefined\"\n />\n <wx-text v-else weight=\"semibold\" truncate class=\"wx-admin__title\">\n {{ title }}\n </wx-text>\n </slot>\n\n <img v-else-if=\"mark\" class=\"wx-admin__mark\" :src=\"mark.url\" :alt=\"title\" />\n\n <wx-action\n icon=\"sidebar\"\n :title=\"collapsed ? t('nav.expand') : t('nav.collapse')\"\n @click=\"toggle\"\n />\n </div>\n </template>\n\n <slot name=\"nav\" :collapsed=\"collapsed\" />\n\n <template #bottom>\n <div class=\"wx-admin__user\">\n <slot name=\"user\" />\n </div>\n </template>\n </wx-aside>\n\n <wx-main padding=\"none\" class=\"wx-admin__screen\">\n <router-view />\n </wx-main>\n </wx-container>\n\n <!-- The same three zones the sidebar has, in the one place a phone can put them. -->\n <wx-drawer\n v-model:open=\"drawerOpen\"\n :title=\"title === '' ? t('nav.menu') : title\"\n side=\"left\"\n :size=\"260\"\n closable\n >\n <slot name=\"nav\" :collapsed=\"false\" :select=\"close\" />\n\n <template #footer>\n <div class=\"wx-admin__user wx-admin__user--drawer\">\n <slot name=\"user\" />\n </div>\n </template>\n </wx-drawer>\n </div>\n</template>\n\n<style scoped>\n/*\n * The page scrolls, so the shell is at least a window tall rather than exactly one. The body\n * colour is here as well as on the container: it is what shows around the floating cards, and\n * on a short screen the container ends where the content does.\n */\n.wx-admin {\n /* The phone's bar, in one place: it is the bar's own height and part of what is left\n over for a screen that has to fill the window. */\n --wx-admin-header-height: 52px;\n\n min-height: 100dvh;\n background: var(--wx-bg-body);\n}\n\n/*\n * One step for the whole panel (§6 of the visual spec) — the air around the sidebar, around\n * the bar and between them and the screen, and, because a custom property inherits, the air\n * between the cards on a screen, down the columns of a grid, between the fields of a form and\n * inside a card. Everything that lays anything out reads it from here.\n *\n * The width decides, and it is the shell's size class that says which width — not a container\n * query: that would need `container-type` on the panel's root, and with it a containing block\n * for every `position: fixed` inside. A sidebar collapsed by hand at 1440 is still a desktop.\n */\n.wx-admin--drawer {\n --wx-gap: var(--wx-space-8);\n}\n\n.wx-admin--rail {\n --wx-gap: var(--wx-space-12);\n}\n\n.wx-admin--sidebar {\n --wx-gap: var(--wx-space-16);\n}\n\n.wx-admin__frame {\n padding: var(--wx-gap);\n gap: var(--wx-gap);\n}\n\n.wx-admin__header {\n --wx-header-top: var(--wx-gap);\n}\n\n/*\n * The column stands in the window, inset by the same step on both sides, and what scrolls\n * inside it is the menu between the two zones.\n */\n.wx-admin__aside {\n --wx-aside-top: var(--wx-gap);\n --wx-aside-height: calc(100dvh - var(--wx-gap) * 2);\n}\n\n.wx-admin__brand {\n display: flex;\n align-items: center;\n gap: var(--wx-space-8);\n box-sizing: border-box;\n min-height: 48px;\n padding: var(--wx-space-8);\n border-bottom: 1px solid var(--wx-border-muted);\n}\n\n/* On the rail the one button in this zone stands where the icons below it stand. */\n.wx-aside--collapsed .wx-admin__brand {\n justify-content: center;\n padding-inline: var(--wx-space-4);\n}\n\n.wx-admin__brand--stacked {\n flex-direction: column;\n gap: var(--wx-space-4);\n}\n\n.wx-admin__title {\n flex: 1 1 auto;\n min-width: 0;\n}\n\n/*\n * The logo is given a height and takes whatever width that leaves it: a client's file can be\n * any shape, and the one measurement the corner can promise is how tall a brand is allowed to\n * be.\n *\n * `min-width: 0` is what actually holds a wide one in. A picture in a flex row refuses to go\n * below its natural width, and `max-width: 100%` is measured before the button beside it is\n * placed — so a 1600px wordmark took the whole sidebar and pushed the button for collapsing\n * it 15px outside the panel. Measured, not guessed.\n */\n.wx-admin__logo {\n flex: 0 1 auto;\n min-width: 0;\n height: 28px;\n width: auto;\n max-width: 100%;\n object-fit: contain;\n object-position: left center;\n}\n\n.wx-admin__mark {\n height: 24px;\n width: 24px;\n object-fit: contain;\n}\n\n.wx-admin__user {\n display: flex;\n align-items: center;\n padding: var(--wx-space-8);\n border-top: 1px solid var(--wx-border-muted);\n}\n\n.wx-aside--collapsed .wx-admin__user {\n justify-content: center;\n}\n\n/*\n * In the drawer the footer already has its own rule and padding. It also puts what it holds\n * at the far end, where a row of buttons belongs — the account is not that, and it stands\n * where it stands in the sidebar.\n */\n.wx-admin__user--drawer {\n flex: 1 1 auto;\n justify-content: flex-start;\n padding: 0;\n border-top: none;\n}\n\n/*\n * A screen that has to be exactly as tall as the window — the page editor, the kanban, the\n * media library — is told how much of the window the frame has left it. On the two wider\n * shapes that is the air above and below; on a phone the bar and its own step as well.\n */\n.wx-admin__screen {\n --wx-fill-height: calc(100dvh - var(--wx-gap) * 2);\n\n /* And where a screen's own action bar stops, short of the edge, so that it floats with\n the same air as the sidebar beside it. */\n --wx-action-bar-bottom: var(--wx-gap);\n\n min-width: 0;\n min-height: 0;\n}\n\n.wx-admin--drawer .wx-admin__screen {\n --wx-fill-height: calc(100dvh - var(--wx-gap) * 3 - var(--wx-admin-header-height));\n}\n\n/* The states with no shell around them: sign-in, loading, and the one where the panel could\n not start. Each is a single thing in the middle of an empty page. */\n.wx-admin-plain {\n display: grid;\n place-items: center;\n /* Without this the padding is added to the viewport height and the page scrolls by exactly\n the padding. */\n box-sizing: border-box;\n min-height: 100dvh;\n padding: var(--wx-space-16);\n background: var(--wx-bg-body);\n}\n</style>\n\n<style>\n/* Not scoped, and global on purpose: the panel is the whole page, so the browser default\n margin on <body> shows up as a gap around the shell — and an uneven one, since the shell\n already keeps its own air around the frame. */\nhtml:has(> body > #webx-app),\nbody:has(> #webx-app) {\n margin: 0;\n}\n</style>\n","/**\n * The panel's way of talking to its own backend.\n *\n * Small on purpose — it is not a general HTTP library, it is the handful of conventions\n * `webx-ui/module-admin` and `webx-ui/module-auth` answer with: a session cookie rather than a token,\n * 422 for a bad form, 401 for a stranger, 429 with `Retry-After` when somebody is guessing.\n */\n\nexport interface HttpOptions {\n /** Prefixed to every relative path, e.g. `/api/cms`. */\n baseUrl?: string\n /** Where to fetch the CSRF cookie from before an unsafe request. */\n csrfUrl?: string\n /** Called whenever the server answers 401, however deep in the app the call was. */\n onUnauthenticated?: () => void\n /**\n * Headers added to every request, read at the time of the request rather than fixed when\n * the client is made — the panel's language changes while it runs.\n */\n headers?: () => Record<string, string>\n /** Swappable for tests. */\n fetch?: typeof globalThis.fetch\n}\n\nexport interface RequestOptions {\n /** Query parameters; `undefined` and `null` are left out rather than sent empty. */\n query?: Record<string, string | number | boolean | null | undefined>\n headers?: Record<string, string>\n signal?: AbortSignal\n}\n\n/**\n * Everything that went wrong, in the shape the panel needs to react:\n * `errors` goes straight into `WxForm`, `retryAfter` into \"try again in a moment\".\n */\nexport class HttpError extends Error {\n constructor(\n message: string,\n readonly status: number,\n readonly errors: Record<string, string[]> = {},\n readonly retryAfter: number | null = null,\n readonly body: unknown = null,\n ) {\n super(message)\n this.name = 'HttpError'\n }\n\n /** A failed form rather than a failed request. */\n get isValidation(): boolean {\n return this.status === 422\n }\n\n get isUnauthenticated(): boolean {\n return this.status === 401\n }\n\n get isThrottled(): boolean {\n return this.status === 429\n }\n}\n\nexport interface Http {\n get<T>(path: string, options?: RequestOptions): Promise<T>\n post<T>(path: string, body?: unknown, options?: RequestOptions): Promise<T>\n put<T>(path: string, body?: unknown, options?: RequestOptions): Promise<T>\n patch<T>(path: string, body?: unknown, options?: RequestOptions): Promise<T>\n delete<T>(path: string, options?: RequestOptions): Promise<T>\n}\n\nconst UNSAFE = new Set(['POST', 'PUT', 'PATCH', 'DELETE'])\n\nexport function createHttp(options: HttpOptions = {}): Http {\n const baseUrl = (options.baseUrl ?? '').replace(/\\/$/, '')\n const csrfUrl = options.csrfUrl ?? '/sanctum/csrf-cookie'\n const doFetch = options.fetch ?? globalThis.fetch.bind(globalThis)\n const onUnauthenticated = options.onUnauthenticated\n const standingHeaders = options.headers\n\n let csrfFetched = false\n\n async function ensureCsrfCookie(force = false): Promise<void> {\n if (csrfFetched && !force && readCookie('XSRF-TOKEN') !== null) {\n return\n }\n\n await doFetch(csrfUrl, { credentials: 'same-origin' })\n csrfFetched = true\n }\n\n async function request<T>(\n method: string,\n path: string,\n body?: unknown,\n options: RequestOptions = {},\n retried = false,\n ): Promise<T> {\n const unsafe = UNSAFE.has(method)\n\n if (unsafe) {\n await ensureCsrfCookie()\n }\n\n const headers: Record<string, string> = {\n Accept: 'application/json',\n 'X-Requested-With': 'XMLHttpRequest',\n // Standing headers first, so a caller can still override one for a single request.\n ...standingHeaders?.(),\n ...options.headers,\n }\n\n if (unsafe) {\n const token = readCookie('XSRF-TOKEN')\n\n if (token !== null) {\n headers['X-XSRF-TOKEN'] = token\n }\n }\n\n if (body !== undefined) {\n headers['Content-Type'] = 'application/json'\n }\n\n const response = await doFetch(url(baseUrl, path, options.query), {\n method,\n credentials: 'same-origin',\n headers,\n signal: options.signal,\n body: body === undefined ? undefined : JSON.stringify(body),\n })\n\n // 419 is Laravel for \"your CSRF token has gone stale\", which happens after a session is\n // regenerated — at sign-in, most of all. Fetching a fresh one and going again is what a\n // person would do by reloading, without the reload.\n if (response.status === 419 && unsafe && !retried) {\n await ensureCsrfCookie(true)\n\n return request<T>(method, path, body, options, true)\n }\n\n if (response.status === 401) {\n onUnauthenticated?.()\n }\n\n if (!response.ok) {\n throw await toError(response)\n }\n\n if (response.status === 204) {\n return undefined as T\n }\n\n return (await response.json()) as T\n }\n\n return {\n get: (path, options) => request('GET', path, undefined, options),\n post: (path, body, options) => request('POST', path, body, options),\n put: (path, body, options) => request('PUT', path, body, options),\n patch: (path, body, options) => request('PATCH', path, body, options),\n delete: (path, options) => request('DELETE', path, undefined, options),\n }\n}\n\nasync function toError(response: Response): Promise<HttpError> {\n let body: unknown = null\n\n try {\n body = await response.json()\n } catch {\n // A gateway or a fatal error answers with HTML; there is nothing to read out of it.\n }\n\n const payload = (body ?? {}) as { message?: unknown; errors?: unknown }\n const message =\n typeof payload.message === 'string' && payload.message !== ''\n ? payload.message\n : response.statusText || `Request failed with ${response.status}`\n\n const errors =\n payload.errors !== null && typeof payload.errors === 'object'\n ? (payload.errors as Record<string, string[]>)\n : {}\n\n const header = response.headers.get('Retry-After')\n const retryAfter = header === null ? null : Number.parseInt(header, 10)\n\n return new HttpError(\n message,\n response.status,\n errors,\n Number.isFinite(retryAfter) ? retryAfter : null,\n body,\n )\n}\n\nfunction url(baseUrl: string, path: string, query?: RequestOptions['query']): string {\n const absolute = /^https?:\\/\\//i.test(path)\n const full = absolute ? path : `${baseUrl}/${path.replace(/^\\//, '')}`\n\n if (query === undefined) {\n return full\n }\n\n const search = new URLSearchParams()\n\n for (const [key, value] of Object.entries(query)) {\n if (value !== undefined && value !== null) {\n search.set(key, String(value))\n }\n }\n\n const serialised = search.toString()\n\n return serialised === '' ? full : `${full}${full.includes('?') ? '&' : '?'}${serialised}`\n}\n\n/**\n * Laravel writes the token URL-encoded, and it is read back the same way it was written.\n */\nexport function readCookie(name: string): string | null {\n if (typeof document === 'undefined') {\n return null\n }\n\n for (const part of document.cookie.split(';')) {\n const [key, ...rest] = part.trim().split('=')\n\n if (key === name) {\n return decodeURIComponent(rest.join('='))\n }\n }\n\n return null\n}\n","import type { Messages } from './i18n'\n\n/**\n * The panel's own words, in English.\n *\n * The same keys `webx-ui/module-admin` ships as `lang/en/*.php`, kept here so the package works with\n * no server behind it. Anything the server sends wins; this is the floor, not the source of\n * truth. Translations belong in the Composer package, where one file serves both halves.\n */\nexport const adminMessages: Record<string, Messages> = {\n shell: {\n loading: 'Loading the panel…',\n 'error-title': 'The panel could not start',\n retry: 'Try again',\n 'empty-title': 'Nothing is installed yet',\n 'empty-description': 'This panel has no modules. Install one and it will appear here.',\n },\n nav: {\n sections: 'Sections',\n menu: 'Menu',\n collapse: 'Collapse the menu',\n expand: 'Expand the menu',\n language: 'Language',\n // The one menu group the panel names itself; a module's own group is named by the module.\n system: 'System',\n },\n // When something happened, said the way a person would. The month names and the order of\n // the parts come from `Intl` — only the words that no formatter knows are here.\n dates: {\n today: 'today at :time',\n yesterday: 'yesterday at :time',\n // Not an empty cell and not a dash: a column that says nothing leaves a reader wondering\n // whether the panel failed to load it.\n never: 'never',\n },\n // How a request fails, in the panel's words rather than the server's (§13.3). `errors.ts`\n // decides which of these a status gets.\n errors: {\n 'signed-out': 'You are signed out. Sign in again and try once more.',\n forbidden: 'You are not allowed to do that.',\n gone: 'It is not there any more — somebody may have deleted it.',\n conflict: 'Somebody changed this while you were working on it.',\n throttled: 'Too many attempts. Try again in a moment.',\n 'throttled-in': 'Too many attempts. Try again in :seconds seconds.',\n server: 'The server could not do that. Try again in a moment.',\n offline: 'The server did not answer. Check the connection and try again.',\n unknown: 'That did not work.',\n },\n}\n","import { computed, createApp, h, ref, type App, type Component } from 'vue'\nimport { createRouter, createWebHistory, type Router, type RouteRecordRaw } from 'vue-router'\nimport { localesKey, WebxUI, type LocaleOption } from '@webx-ui/core'\nimport AdminNav from './AdminNav.vue'\nimport AdminShell from './AdminShell.vue'\nimport { createAdminContext, provideAdmin, type AdminContext } from './admin'\nimport { createHttp, type Http } from './http'\nimport { createI18n, provideI18n, type Dictionary, type I18n, type LocaleDescriptor } from './i18n'\nimport { adminMessages } from './messages'\nimport type { Patch, TypeRegistry } from '@webx-ui/schema'\nimport type { AdminModule, Manifest } from './types'\n\nconst STORED_LOCALE = 'webx.locale'\n\nexport interface CreateAdminOptions {\n /** Where to mount. Defaults to `#webx-app`, which is what the Blade shell renders. */\n el?: string | Element\n /**\n * Where the manifest lives. Defaults to the `webx-manifest` meta tag the Blade shell writes,\n * and to `/api/cms/manifest` when there is none — which is the case under a dev server,\n * where the page is Vite's own index.html.\n */\n manifestUrl?: string\n /**\n * Where the panel's JSON lives, e.g. `/api/cms`. Modules build their own addresses from it.\n * Derived from the manifest URL when not given.\n */\n apiPath?: string\n /** Sections of the panel. */\n modules?: AdminModule[]\n /** Routes that belong to no module: a dashboard, a 404. */\n routes?: RouteRecordRaw[]\n /**\n * Where the panel is served, for the router's history base. Taken from the manifest when it\n * arrives; given here for the first paint, before it has.\n */\n basePath?: string\n /** Replaces the panel's name in the header — a logo, usually. */\n brand?: Component\n /** The corner of the header: who is signed in, and the way out. */\n userMenu?: Component\n /** Extensions that need the router and the context: an auth module, most of all. */\n plugins?: AdminPlugin[]\n /**\n * The language to draw the panel in before the server has been asked. Defaults to the last\n * one used, then to the page's `lang`, then to the browser's. Whatever is chosen, the server\n * narrows it to a language the panel actually has.\n */\n locale?: string\n /**\n * Screen node types the project adds — `{ map: { component: WxMapField, kind: 'field' } }` —\n * over the core's and the modules'.\n */\n types?: TypeRegistry\n /**\n * The project's patches over the screens modules ship, by screen name, applied on the\n * client on top of what the server hands out. What a patch cannot do from here is open a\n * key for writing: the server decides what is saved.\n */\n screens?: Record<string, Patch>\n /** Swappable for tests. */\n http?: Http\n}\n\n/**\n * Something that needs the assembled panel rather than a slot in it — it adds routes, guards\n * the router, or tells the panel how to find out who is signed in.\n */\nexport interface AdminPlugin {\n install(admin: Admin): void\n}\n\nexport interface Admin {\n app: App\n router: Router\n context: AdminContext\n i18n: I18n\n mount(): Promise<void>\n}\n\n/**\n * Assemble the panel.\n *\n * Mounting does not wait for the server. The manifest needs a signed-in session, so a visit\n * that starts at the sign-in screen would otherwise stare at a blank page until a request it\n * is bound to lose comes back.\n */\nexport function createAdmin(options: CreateAdminOptions = {}): Admin {\n const manifestUrl = options.manifestUrl ?? readManifestUrl() ?? '/api/cms/manifest'\n // The Blade shell writes the manifest address rather than the API root, and every module\n // needs the root, so it is read back out of the one thing the page does say.\n const apiPath = options.apiPath ?? manifestUrl.replace(/\\/manifest\\/?$/, '')\n const basePath = options.basePath ?? '/cms'\n const modules = options.modules ?? []\n\n const routes: RouteRecordRaw[] = [...(options.routes ?? [])]\n\n for (const module of modules) {\n routes.push(...(module.routes ?? []))\n }\n\n const router = createRouter({\n history: createWebHistory(basePath),\n routes,\n })\n\n const i18n = createI18n({ locale: options.locale ?? preferredLocale() })\n\n const http =\n options.http ??\n createHttp({\n baseUrl: '',\n onUnauthenticated: () => {\n context.setUser(null)\n context.state.status = 'unauthenticated'\n },\n // Every request says which language the panel is currently showing. It decides what\n // the server writes its own messages in — a 422 under a field — for anybody who has\n // not stored a preference yet, which is everybody until they choose one. Without it,\n // signing in on a Russian sign-in screen lands in an English panel.\n headers: () => ({ 'X-Webx-Locale': i18n.state.locale }),\n })\n\n i18n.defaults('webx-admin', adminMessages)\n\n async function loadDictionary(locale: string): Promise<void> {\n const body = await http.get<{\n data: { locale: string; fallback: string; namespaces: Dictionary }\n }>(`${apiPath}/translations/${locale}`)\n\n i18n.load(body.data.namespaces, body.data.locale, body.data.fallback)\n rememberLocale(body.data.locale)\n markDocumentLanguage(body.data.locale, i18n.state.panelLocales)\n }\n\n const context = createAdminContext({\n http,\n basePath,\n apiPath,\n modules,\n i18n,\n loadDictionary,\n types: options.types,\n screens: options.screens,\n loadManifest: async () => {\n const body = await http.get<{ data: Manifest }>(manifestUrl)\n\n return body.data\n },\n })\n\n const app = createApp(rootComponent(options))\n\n app.use(WebxUI)\n provideAdmin(app, context)\n provideI18n(app, i18n)\n\n /*\n * The languages a localized field offers are the site's *content* languages, not the ones the\n * panel can be drawn in: a panel in English routinely edits a site published in Ukrainian and\n * Russian. They arrive with the manifest, so this is a computed over what is already there\n * rather than a second request — and a form written before they arrive simply has nothing to\n * switch between yet.\n */\n const editing = ref('')\n\n app.provide(localesKey, {\n list: computed<LocaleOption[]>(() =>\n i18n.state.contentLocales.map((locale) => ({\n code: locale.code,\n label: locale.code.toUpperCase(),\n })),\n ),\n active: computed({\n get: () => editing.value || (i18n.state.contentLocales[0]?.code ?? ''),\n set: (code: string) => {\n editing.value = code\n },\n }),\n })\n\n const admin: Admin = {\n app,\n router,\n context,\n i18n,\n async mount() {\n // The one thing worth waiting for. It is a public, cached request, and painting the\n // sign-in screen in English and then swapping every label a moment later looks like a\n // bug rather than like a translation arriving. The manifest is still not waited for —\n // that one needs a session and is bound to 401 for a visitor.\n await Promise.all([loadPanelLocales(), loadDictionary(i18n.state.locale)]).catch(() => {\n // A server that cannot answer these cannot run a panel either, and the built-in\n // English is a better thing to fail with than a blank page.\n })\n\n app.mount(options.el ?? '#webx-app')\n\n await context.reload()\n },\n }\n\n async function loadPanelLocales(): Promise<void> {\n const body = await http.get<{\n data: { panel: LocaleDescriptor[]; content: LocaleDescriptor[] }\n }>(`${apiPath}/locales`)\n\n i18n.state.panelLocales = body.data.panel\n i18n.state.contentLocales = body.data.content\n }\n\n // Plugins go on before the router does, because installing the router is what starts the\n // first navigation. A route added after that is a route the visit already failed to match:\n // opening /login directly would land on nothing while /cms worked, because / matched and the\n // redirect to /login happened later, by which time the route existed.\n for (const plugin of options.plugins ?? []) {\n plugin.install(admin)\n }\n\n app.use(router)\n\n return admin\n}\n\n/** What the shell tells the navigation slot about where it is drawing it. */\ninterface NavSlotProps {\n collapsed?: boolean\n select?: () => void\n}\n\nfunction rootComponent(options: CreateAdminOptions): Component {\n const slots: Record<string, (props: NavSlotProps) => unknown> = {\n // The menu is the panel's own: it is the manifest, drawn.\n // The shell hands in what a choice means where it drew the menu — in the drawer it is what\n // closes it, and a drawer that stays open over the section it just opened is a drawer the\n // reader has to dismiss by hand.\n nav: (props) => h(AdminNav, { collapsed: props.collapsed === true, onSelect: props.select }),\n }\n\n if (options.brand !== undefined) {\n slots.brand = () => h(options.brand as Component)\n }\n\n if (options.userMenu !== undefined) {\n slots.user = () => h(options.userMenu as Component)\n }\n\n return { render: () => h(AdminShell, null, slots) }\n}\n\n/**\n * The language to ask for first. A guess, and treated as one — the server answers with the\n * language it actually has, and that is what the panel adopts.\n */\nfunction preferredLocale(): string {\n const remembered = read(STORED_LOCALE)\n\n if (remembered !== null) {\n return remembered\n }\n\n if (typeof document !== 'undefined' && document.documentElement.lang !== '') {\n return document.documentElement.lang\n }\n\n return typeof navigator === 'undefined' ? 'en' : navigator.language\n}\n\nfunction rememberLocale(locale: string): void {\n // A per-browser convenience, so a signed-out reload of the sign-in screen keeps the\n // language. The choice that lasts is the one stored against the administrator.\n try {\n localStorage.setItem(STORED_LOCALE, locale)\n } catch {\n // Private windows, blocked site data. Nothing here is worth an error.\n }\n}\n\nfunction read(key: string): string | null {\n try {\n return localStorage.getItem(key)\n } catch {\n return null\n }\n}\n\n/** So the browser hyphenates, spell-checks and reads the page aloud in the right language. */\nfunction markDocumentLanguage(locale: string, locales: LocaleDescriptor[]): void {\n if (typeof document === 'undefined') {\n return\n }\n\n document.documentElement.lang = locale\n document.documentElement.dir =\n locales.find((candidate) => candidate.code === locale)?.direction ?? 'ltr'\n}\n\nfunction readManifestUrl(): string | null {\n if (typeof document === 'undefined') {\n return null\n }\n\n const meta = document.querySelector('meta[name=\"webx-manifest\"]')\n\n return meta?.getAttribute('content') ?? null\n}\n","import { inject } from 'vue'\nimport { createI18n, i18nKey, type I18n } from './i18n'\nimport { adminMessages } from './messages'\n\n/** A moment as it arrives: ISO-8601 from the server, a timestamp, a `Date`, or nothing. */\nexport type DateLike = string | number | Date | null | undefined\n\nexport interface Dates {\n /**\n * The line a reader sees: \"today at 08:10\", \"yesterday at 14:03\", \"16 September at 14:03\",\n * \"16 September 2025\", \"never\".\n */\n short(value: DateLike): string\n /**\n * Everything there is, down to the second — what the tip holds when the short line is not\n * precise enough to settle an argument. Empty when there is no date.\n */\n exact(value: DateLike): string\n /** The machine value for `<time datetime>`. Empty when there is no date. */\n iso(value: DateLike): string\n}\n\n/**\n * How the panel says when something happened — one answer for every screen in it.\n *\n * Three rules it would be easy to lose, and each of them was a bug before this existed:\n *\n * - the language is the panel's, not the browser's. `toLocaleString()` with no locale reads\n * the browser's settings, so a panel drawn in Russian was dating its rows in American\n * order. The locale comes from `i18n`, which is what the administrator chose.\n * - \"today\" is counted in the administrator's own day. The server sends UTC; comparing UTC\n * dates would write \"yesterday\" over anything that happened before 03:00 in Moscow. The\n * comparison is made on local calendar days, which is what the reader means by today.\n * - the shown string is never what sorts. Nothing here touches ordering: a column sorts on\n * the value the server sent, or \"yesterday\" lands between the sixteenth and the seventeenth.\n */\nexport function createDates(i18n: I18n): Dates {\n const t = i18n.scope('webx-admin')\n\n // A table asks for the same two or three shapes once per row, and building an\n // `Intl.DateTimeFormat` is the expensive part of formatting. Keyed by locale as well as by\n // shape, so switching the panel's language does not hand back the old language's months.\n const formatters = new Map<string, Intl.DateTimeFormat>()\n\n function format(shape: string, options: Intl.DateTimeFormatOptions, at: Date): string {\n const locale = i18n.state.locale\n const key = `${locale}:${shape}`\n let formatter = formatters.get(key)\n\n if (formatter === undefined) {\n formatter = new Intl.DateTimeFormat(locale, options)\n formatters.set(key, formatter)\n }\n\n return formatter.format(at)\n }\n\n /* 24 hours everywhere, whatever the locale would have chosen: a panel is read in a hurry and\n * \"08:10\" is one glance where \"8:10 AM\" is two. Seconds are left to `exact()`. */\n const time = (at: Date) =>\n format('time', { hour: '2-digit', minute: '2-digit', hourCycle: 'h23' }, at)\n\n return {\n short(value) {\n const at = toDate(value)\n\n if (at === null) {\n return t('dates.never')\n }\n\n const now = new Date()\n const days = daysApart(at, now)\n\n if (days === 0) return t('dates.today', { time: time(at) })\n if (days === -1) return t('dates.yesterday', { time: time(at) })\n\n // Within this year the year itself says nothing — it is the time of day that tells two\n // entries apart. Further back it is the other way round, and a time nobody will compare\n // is noise in the column.\n return at.getFullYear() === now.getFullYear()\n ? format(\n 'this-year',\n {\n day: 'numeric',\n month: 'long',\n hour: '2-digit',\n minute: '2-digit',\n hourCycle: 'h23',\n },\n at,\n )\n : format('earlier', { day: 'numeric', month: 'long', year: 'numeric' }, at)\n },\n exact(value) {\n const at = toDate(value)\n\n return at === null\n ? ''\n : format('exact', { dateStyle: 'long', timeStyle: 'medium', hourCycle: 'h23' }, at)\n },\n iso(value) {\n const at = toDate(value)\n\n return at === null ? '' : at.toISOString()\n },\n }\n}\n\n/**\n * The formatter, inside a panel or outside one.\n *\n * Outside a panel — a screen mounted by hand, a story, a test — there is no dictionary to read,\n * so one is made once with this package's own English in it. A component that may be used\n * either way should not have to know which it is.\n */\nexport function useDates(): Dates {\n const i18n = inject(i18nKey, null)\n\n return createDates(i18n ?? standalone())\n}\n\nlet loose: I18n | null = null\n\nfunction standalone(): I18n {\n if (loose === null) {\n loose = createI18n()\n loose.defaults('webx-admin', adminMessages)\n }\n\n return loose\n}\n\nfunction toDate(value: DateLike): Date | null {\n if (value === null || value === undefined || value === '') {\n return null\n }\n\n const at = value instanceof Date ? value : new Date(value)\n\n // An unparseable string is a broken date, and `Invalid Date` on a screen says less than the\n // word the empty column already has.\n return Number.isNaN(at.getTime()) ? null : at\n}\n\n/**\n * Whole calendar days between two moments, in the reader's own zone.\n *\n * Built from the date parts rather than from a difference in milliseconds: an hour lost to\n * daylight saving makes \"24 hours ago\" and \"yesterday\" different questions, and it is the\n * second one a reader is asking.\n */\nfunction daysApart(at: Date, now: Date): number {\n const day = (d: Date) => Date.UTC(d.getFullYear(), d.getMonth(), d.getDate())\n\n return Math.round((day(at) - day(now)) / 86_400_000)\n}\n","import { HttpError } from './http'\nimport { useTranslate, type Translate } from './i18n'\n\n/**\n * What to say out loud when a request came back a failure.\n *\n * The panel used to print whatever the server put in `message`, which is fine right up until\n * the server has not written it. `No query results for model [WebxUi\\Pages\\Models\\Page] 8` is\n * Laravel talking to a developer — a namespace, in English, in a panel translated into ten\n * languages — and it reached an editor who had only clicked a row in the bin (§13.3).\n *\n * So the rule is which status the panel trusts to speak for itself, not which words look\n * technical. A 422 is ours: every refusal that reaches one is written by a module to be read,\n * through `trans()`, and the validator's lines are translated by Laravel. Everything else —\n * 404, 403, 500, a server that never answered — carries text nobody wrote for this reader, and\n * the panel says it in its own words instead.\n */\nexport function errorText(error: unknown, t: Translate, fallback?: string): string {\n const status = error instanceof HttpError ? error.status : statusOf(error)\n\n // The one the panel wrote itself. `fallback` under it for the rare 422 with nothing in it —\n // a refusal with only field errors, where the form is already showing them.\n if (status === 422) {\n return bodyMessage(error) ?? fallback ?? t('errors.unknown')\n }\n\n if (status === 401) return t('errors.signed-out')\n if (status === 403) return t('errors.forbidden')\n if (status === 404) return t('errors.gone')\n if (status === 409) return t('errors.conflict')\n\n if (status === 429) {\n const seconds = error instanceof HttpError ? error.retryAfter : null\n\n return seconds === null ? t('errors.throttled') : t('errors.throttled-in', { seconds })\n }\n\n if (status !== null && status >= 500) return t('errors.server')\n\n // No status at all: the request never reached a server, or never came back from one.\n if (status === null) return t('errors.offline')\n\n return fallback ?? t('errors.unknown')\n}\n\n/**\n * The same thing for a component, bound to the panel's own dictionary.\n *\n * A module calls this rather than its own `t`, because these lines belong to the panel: every\n * section fails in the same handful of ways, and translating \"you are not allowed to do that\"\n * once per module is how nine of them end up saying it differently.\n */\nexport function useErrorText(): (error: unknown, fallback?: string) => string {\n const t = useTranslate('webx-admin')\n\n return (error, fallback) => errorText(error, t, fallback)\n}\n\nfunction statusOf(error: unknown): number | null {\n const status = (error as { status?: unknown } | null)?.status\n\n return typeof status === 'number' ? status : null\n}\n\nfunction bodyMessage(error: unknown): string | null {\n const body = (error as { body?: { message?: unknown } } | null)?.body\n const message = body?.message\n\n return typeof message === 'string' && message !== '' ? message : null\n}\n","<script setup lang=\"ts\">\nimport { ref, watch } from 'vue'\nimport { WxAlert, WxSkeleton } from '@webx-ui/core'\nimport { WxScreenRenderer, type ScreenModel, type ScreenNode } from '@webx-ui/schema'\nimport { useAdmin } from './admin'\nimport { useTranslate } from './i18n'\n\n/**\n * A screen of the panel, by name.\n *\n * Fetches the tree the server hands out — patched, permission-filtered, translated — lays the\n * project's own patch over it, and draws it with the renderer against the panel's registry,\n * dictionary and permissions. It does not load values and does not save: that is the page's\n * business, the way it is for any form.\n */\nconst props = withDefaults(\n defineProps<{\n /** `<module>.<screen>`, e.g. `settings.index`. */\n name: string\n /** Server validation errors by field name, shown under the fields. */\n errors?: Record<string, string[]>\n disabled?: boolean\n labelPosition?: 'top' | 'left'\n labelWidth?: string\n size?: 'sm' | 'md' | 'lg'\n }>(),\n {\n errors: undefined,\n disabled: false,\n labelPosition: undefined,\n labelWidth: undefined,\n size: undefined,\n },\n)\n\nconst emit = defineEmits<{\n /** The tree arrived; a page that wants to know what it draws reads it here. */\n loaded: [root: ScreenNode[]]\n}>()\n\nconst model = defineModel<ScreenModel>({ default: () => ({}) })\n\nconst admin = useAdmin()\nconst t = useTranslate('webx-admin')\n\nconst root = ref<ScreenNode[] | null>(null)\nconst failed = ref<string | null>(null)\n\nasync function load(): Promise<void> {\n root.value = null\n failed.value = null\n\n try {\n const tree = await admin.loadScreen(props.name)\n root.value = tree\n emit('loaded', tree)\n } catch (error) {\n failed.value = error instanceof Error ? error.message : String(error)\n }\n}\n\n// A new language means a new tree: the labels inside it were translated on the server.\nwatch([() => props.name, () => admin.i18n.state.locale], () => void load(), { immediate: true })\n\ndefineExpose({ reload: load })\n</script>\n\n<template>\n <div class=\"wx-screen-host\">\n <wx-alert v-if=\"failed\" type=\"danger\" :title=\"t('shell.error-title')\" :description=\"failed\" />\n <wx-skeleton v-else-if=\"root === null\" :rows=\"4\" />\n <wx-screen-renderer\n v-else\n v-model=\"model\"\n :root=\"root\"\n :patch=\"admin.screenPatch(name)\"\n :types=\"admin.types\"\n :errors=\"errors\"\n :translate=\"admin.i18n.t\"\n :can=\"admin.can\"\n :disabled=\"disabled\"\n :label-position=\"labelPosition\"\n :label-width=\"labelWidth\"\n :size=\"size\"\n />\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { WxActions, WxDropdownItem } from '@webx-ui/core'\nimport type { ActionSize } from '@webx-ui/core'\nimport type { RowAction } from './types'\n\n/**\n * What one record offers, in every list of the panel: a `···` at the end of the row.\n *\n * Always a menu, even for a single action. The panel used to have three answers to this —\n * a menu on `Pages`, a bare red bin on `SEO`, a toolbar over the selection in the library —\n * and the cost of that is not the extra click, it is that nobody knows where to look. A\n * menu also has room for the word: an unlabelled icon says what it does only to whoever has\n * already learned it (§20).\n *\n * Two rules it keeps for every list rather than leaving to each one. The destructive action\n * goes last, behind a rule, in red — it is never the thing the reader meant to hit. And what\n * somebody may not do is left out, not shown greyed: a menu is a list of what is possible,\n * and a row of dead entries teaches nothing.\n */\nconst props = withDefaults(\n defineProps<{\n /** What may be done, in the order it should read. */\n actions: RowAction[]\n /** Accessible name of the menu — the record it belongs to. */\n label?: string\n size?: ActionSize\n }>(),\n { label: undefined, size: 'sm' },\n)\n\n/** Destructive last, whatever order the caller wrote them in. */\nconst ordered = computed(() => [\n ...props.actions.filter((action) => !action.danger),\n ...props.actions.filter((action) => action.danger),\n])\n\n/** The first destructive item is the one with the rule above it. */\nconst divided = computed(() => {\n const first = ordered.value.find((action) => action.danger)\n\n return first && ordered.value[0] !== first ? first.key : null\n})\n</script>\n\n<template>\n <!--\n `.stop`: the row itself opens the record, and deleting one is not opening it.\n `collapse=\"always\"` is what makes this a menu at every width — the row of icons the\n actions would otherwise draw is never built.\n -->\n <wx-actions\n v-if=\"ordered.length > 0\"\n collapse=\"always\"\n align=\"end\"\n :size=\"size\"\n :aria-label=\"label\"\n @click.stop\n >\n <template #collapsed>\n <template v-for=\"action in ordered\" :key=\"action.key\">\n <hr v-if=\"action.key === divided\" class=\"wx-dropdown__divider\" />\n\n <wx-dropdown-item\n :icon=\"action.icon\"\n :tone=\"action.danger ? 'danger' : 'default'\"\n :href=\"action.href\"\n :target=\"action.href ? (action.target ?? '_blank') : undefined\"\n :disabled=\"action.disabled\"\n @click=\"action.run?.()\"\n >\n {{ action.label }}\n </wx-dropdown-item>\n </template>\n </template>\n </wx-actions>\n</template>\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { WxText, WxTooltip, type TextSize, type TextTone } from '@webx-ui/core'\nimport { useDates, type DateLike } from './dates'\n\ndefineOptions({ name: 'WxDate' })\n\n/**\n * When something happened, the way the whole panel says it (§21).\n *\n * The short line is what the column holds — \"today at 08:10\" is read at a glance where\n * \"17.09.2026, 08:10\" is read twice. The second down to the second stays in the tip, because\n * the question \"when exactly\" is asked rarely and answered badly by a column.\n *\n * Its own tip rather than `title`: the native one waits a second, cannot be styled, and is\n * invisible to anybody who is not holding a mouse (§16).\n */\nconst props = withDefaults(\n defineProps<{\n /** What the server sent. `null` is a date that never happened, not a missing one. */\n value: DateLike\n size?: TextSize\n tone?: TextTone\n }>(),\n {\n // A date is context beside whatever the row is actually about, never the thing itself.\n size: 'sm',\n tone: 'muted',\n },\n)\n\nconst dates = useDates()\n\nconst text = computed(() => dates.short(props.value))\nconst exact = computed(() => dates.exact(props.value))\nconst iso = computed(() => dates.iso(props.value))\n</script>\n\n<template>\n <wx-tooltip v-if=\"exact\" :content=\"exact\">\n <wx-text as=\"time\" :datetime=\"iso\" :size=\"size\" :tone=\"tone\">{{ text }}</wx-text>\n </wx-tooltip>\n <wx-text v-else :size=\"size\" :tone=\"tone\">{{ text }}</wx-text>\n</template>\n"],"names":["view","_useModel","__props","_createElementBlock","_normalizeClass","$slots","_openBlock","_hoisted_2","_createBlock","_unref","WxHeading","_hoisted_3","_renderSlot","_ctx","WxTabs","$event","WxCard","_Fragment","adminTypes","ListScreen","adminKey","useAdmin","admin","inject","createAdminContext","options","state","reactive","nav","computed","manifest","entries","module","registered","candidate","path","groups","declared","top","byGroup","entry","group","list","types","screens","loadScreen","name","key","pending","body","error","loadSession","reload","setLocale","isUnauthenticated","code","user","loader","permission","provideAdmin","app","i18nKey","useI18n","i18n","useTranslate","namespace","createI18n","provideI18n","fallback","builtIn","dictionary","lookup","source","node","segment","translate","params","explicitNamespace","rest","line","fill","messages","next","locale","nextFallback","filled","value","emit","__emit","t","router","useRouter","route","useRoute","current","id","_component_wx_menu","_renderList","_component_wx_menu_item","_component_wx_submenu","shellEl","ref","width","layout","collapsed","showAside","drawerOpen","toggle","close","useResponsiveShell","title","logo","mark","size","shellLayoutFor","_createVNode","WxToaster","_hoisted_1","_component_router_view","_component_wx_loading","_component_wx_result","_component_wx_button","_cache","_component_wx_container","_component_wx_header","_component_wx_action","_component_wx_text","_component_wx_aside","_createElementVNode","_hoisted_7","_component_wx_main","_component_wx_drawer","_hoisted_8","HttpError","message","status","errors","retryAfter","UNSAFE","createHttp","baseUrl","csrfUrl","doFetch","onUnauthenticated","standingHeaders","csrfFetched","ensureCsrfCookie","force","readCookie","request","method","retried","unsafe","headers","token","response","url","toError","payload","header","query","full","search","serialised","part","adminMessages","STORED_LOCALE","createAdmin","manifestUrl","readManifestUrl","apiPath","basePath","modules","routes","createRouter","createWebHistory","preferredLocale","http","context","loadDictionary","rememberLocale","markDocumentLanguage","createApp","rootComponent","WebxUI","editing","localesKey","loadPanelLocales","plugin","slots","props","h","AdminNav","AdminShell","remembered","read","locales","createDates","formatters","format","shape","at","formatter","time","toDate","now","days","daysApart","useDates","standalone","loose","day","d","errorText","statusOf","bodyMessage","seconds","useErrorText","model","root","failed","load","tree","watch","__expose","WxAlert","WxSkeleton","WxScreenRenderer","ordered","action","divided","first","WxActions","WxDropdownItem","_createTextVNode","_toDisplayString","dates","text","exact","iso","WxTooltip","WxText"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../src/ListScreen.vue","../src/screenTypes.ts","../src/admin.ts","../src/i18n.ts","../src/AdminNav.vue","../src/AdminShell.vue","../src/http.ts","../src/messages.ts","../src/createAdmin.ts","../src/markdown.ts","../src/dates.ts","../src/errors.ts","../src/Screen.vue","../src/RowMenu.vue","../src/BackButton.vue","../src/RenameButton.vue","../src/HelpButton.vue","../src/DateText.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport {\n WxCard,\n WxHeading,\n WxTabs,\n type CardPadding,\n type TabItem,\n type TabValue,\n} from '@webx-ui/core'\n\n/**\n * The frame every list of the panel is drawn in.\n *\n * One shape for all of them, because five sections that each answered \"where does the heading\n * go\" on their own gave five answers — a heading inside the card on `Administrators`, two\n * headings on `SEO`, a search outside the card on `Blocks`, no heading at all on `Files`. A\n * reader who learns one list should already know the next.\n *\n * The shape is: the section's name on the left of its own line, the one action the section\n * exists for on the right of it, the views of the list as tabs under that, and a card holding\n * nothing but the rows. The card has no heading of its own — the tab says which view it is,\n * and the line above says which section (§19). The search stays inside the table, where it is\n * a property of the rows rather than of the screen (§10).\n *\n * What it deliberately does not do is fetch, filter or page. It is a frame; the section still\n * owns its data, and the tabs only say which view is open.\n */\nwithDefaults(\n defineProps<{\n /** The section's name. The one heading on the screen. */\n title?: string\n /**\n * The views of the same list — \"All / Drafts / Published\", \"Rules / Redirects\". Not a\n * navigation between screens: everything under them is the same table.\n */\n views?: TabItem[]\n /** Wrap the rows in a card. Off for a section that brings its own. */\n card?: boolean\n /** Padding of that card; `WxCard`'s own default when not given. */\n padding?: CardPadding\n /** The screen is as tall as the column it is drawn in — a file manager, a board. */\n fill?: boolean\n /** Container width below which the views fold into one switch. */\n collapseBelow?: number\n }>(),\n {\n title: undefined,\n views: undefined,\n card: true,\n padding: undefined,\n fill: false,\n collapseBelow: 560,\n },\n)\n\ndefineSlots<{\n /** The rows: a table, a grid of cards, a manager. */\n default?: () => unknown\n /**\n * The screen's buttons, beside the heading. One of them — the reason the section exists —\n * is a filled primary button with a word on it; everything else is outlined or an icon.\n */\n actions?: () => unknown\n}>()\n\nconst view = defineModel<TabValue | undefined>('view', { default: undefined })\n</script>\n\n<template>\n <div class=\"wx-list-screen\" :class=\"{ 'is-fill': fill }\" :data-wx-fill=\"fill ? '' : undefined\">\n <div v-if=\"title || $slots.actions\" class=\"wx-list-screen__head\">\n <wx-heading v-if=\"title\" :level=\"2\" class=\"wx-list-screen__title\">{{ title }}</wx-heading>\n <div v-if=\"$slots.actions\" class=\"wx-list-screen__actions\">\n <slot name=\"actions\" />\n </div>\n </div>\n\n <!--\n `items` rather than a tab each with its own panel: switching a view must not take the\n table away and put a new one back, or the search somebody typed goes with it.\n -->\n <wx-tabs\n v-if=\"views && views.length > 0\"\n v-model=\"view\"\n class=\"wx-list-screen__views\"\n :items=\"views\"\n :collapse-below=\"collapseBelow\"\n :aria-label=\"title\"\n >\n <wx-card v-if=\"card\" class=\"wx-list-screen__card\" :padding=\"padding\">\n <slot />\n </wx-card>\n <slot v-else />\n </wx-tabs>\n\n <template v-else>\n <wx-card v-if=\"card\" class=\"wx-list-screen__card\" :padding=\"padding\">\n <slot />\n </wx-card>\n <slot v-else />\n </template>\n </div>\n</template>\n\n<style scoped>\n.wx-list-screen {\n display: flex;\n flex-direction: column;\n gap: var(--wx-gap, var(--wx-space-16));\n min-width: 0;\n /* The width that decides the layout is the screen's own, not the window's. */\n container-type: inline-size;\n}\n\n.wx-list-screen__head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--wx-space-12);\n flex-wrap: wrap;\n}\n\n.wx-list-screen__title {\n min-width: 0;\n}\n\n.wx-list-screen__actions {\n display: flex;\n align-items: center;\n gap: var(--wx-space-8);\n flex-wrap: wrap;\n}\n\n/*\n * On a phone the name takes the first line and the buttons the second, full width: three\n * things elbowing each other across 343 px is not a row, and a primary button too narrow to\n * hold its word is not a button.\n */\n@container (max-width: 480px) {\n .wx-list-screen__head > * {\n flex: 1 1 100%;\n }\n\n .wx-list-screen__actions > * {\n flex: 1 1 auto;\n }\n}\n\n/*\n * A screen that fills gives the height to the rows, not to the heading over them.\n *\n * The chain has to be spelled out all the way down, because a percentage inside a box that\n * does not know its own height resolves to nothing at all (CLAUDE.md §4) — and with tabs the\n * boxes in between belong to `WxTabs`, so `:deep()` is what reaches them. A scoped rule stops\n * at this component's own elements and would silently match nothing.\n */\n.wx-list-screen.is-fill {\n min-height: 0;\n}\n\n.wx-list-screen.is-fill > :last-child {\n flex: 1 1 auto;\n min-height: 0;\n}\n\n.wx-list-screen.is-fill :deep(.wx-tabs),\n.wx-list-screen.is-fill :deep(.wx-tabs__layout),\n.wx-list-screen.is-fill :deep(.wx-tabs__panels) {\n display: flex;\n flex-direction: column;\n flex: 1 1 auto;\n min-height: 0;\n}\n\n.wx-list-screen.is-fill :deep(.wx-tabs__panel),\n.wx-list-screen.is-fill :deep(.wx-tabs__panel > *) {\n flex: 1 1 auto;\n min-height: 0;\n}\n\n/* The bar is the one part that keeps its own height. */\n.wx-list-screen.is-fill :deep(.wx-tabs__bar) {\n flex: 0 0 auto;\n}\n</style>\n","import type { TypeRegistry } from '@webx-ui/schema'\nimport ListScreen from './ListScreen.vue'\n\n/**\n * Node types the panel itself brings, under every module's and every project's.\n *\n * `wx-list` is the frame a section's list is drawn in — the heading, the one action beside it,\n * the views as tabs, the card under them. It is a node type and not five copies of the same\n * markup because that is what stops the next section from inventing a sixth shape: a list\n * screen is described, and what a module supplies is the rows inside it (§19 of the visual\n * spec, §10.3 for the tabs).\n *\n * The rows themselves are still a module's own type — `wx-pages-table`, `wx-media` — because\n * only the module knows what it is listing and where it asks for it.\n */\nexport const adminTypes: TypeRegistry = {\n 'wx-list': { component: ListScreen, kind: 'layout', labelProp: 'title' },\n}\n","import { computed, inject, reactive, type App, type ComputedRef, type InjectionKey } from 'vue'\nimport type { Patch, ScreenNode, TypeRegistry } from '@webx-ui/schema'\nimport { adminTypes } from './screenTypes'\nimport type { Http } from './http'\nimport type { I18n } from './i18n'\nimport type { AdminModule, AdminStatus, AdminUser, Manifest, NavEntry, NavGroup } from './types'\n\nexport interface AdminContext {\n /** The panel's own backend. */\n readonly http: Http\n /** Where the panel is served — the router's base. */\n readonly basePath: string\n /** Where its JSON lives, so a module does not have to be told twice. */\n readonly apiPath: string\n readonly state: AdminState\n /** The interface's own words, and the languages it can be shown in. */\n readonly i18n: I18n\n /** Modules registered on the front end, whether or not the server reports them. */\n readonly modules: readonly AdminModule[]\n /** Navigation, in the order the server gave, for the modules that exist on both sides. */\n readonly nav: ComputedRef<NavEntry[]>\n /** The same navigation, with the top-level entries first and then each group's. */\n readonly groups: ComputedRef<{ top: NavEntry[]; groups: NavGroup[] }>\n /** Node types every screen is drawn with: the modules' and the project's, over the core. */\n readonly types: TypeRegistry\n /** Ask the server what the panel is and who is signed in again. */\n reload(): Promise<void>\n /**\n * Fetch the manifest again and adopt it, without the panel going through `loading` — so the\n * chrome changes under whoever is looking at it instead of being replaced by a spinner.\n *\n * For a screen that has just saved something the manifest reports: the branding, the name of\n * the site, what a section is called. A failure is swallowed, because what it would cost is\n * a working panel in exchange for a stale logo.\n */\n refreshManifest(): Promise<void>\n /**\n * Draw the panel in another language: fetches that dictionary and remembers the choice for\n * the next visit. Storing it against the administrator is an auth module's business — this\n * only changes what is on screen.\n */\n setLocale(code: string): Promise<void>\n /** Filled in by an auth module; `null` means nobody is signed in. */\n setUser(user: AdminUser | null): void\n /**\n * How the panel finds out who is signed in, set by an auth module. Without one the panel\n * simply asks for the manifest and lets a 401 answer the question.\n */\n useSessionLoader(loader: () => Promise<AdminUser | null>): void\n can(permission: string): boolean\n /**\n * A screen by name, as the server hands it out — patched, cut to this administrator's\n * permissions, translated. Fetched once per name and language for the session.\n */\n loadScreen(name: string): Promise<ScreenNode[]>\n /** The project's own operations over a screen, from `createAdmin({ screens })`. */\n screenPatch(name: string): Patch\n}\n\nexport interface AdminState {\n status: AdminStatus\n manifest: Manifest | null\n user: AdminUser | null\n error: string | null\n}\n\nexport const adminKey: InjectionKey<AdminContext> = Symbol('webx-admin')\n\nexport function useAdmin(): AdminContext {\n const admin = inject(adminKey, null)\n\n if (admin === null) {\n throw new Error('useAdmin() was called outside a panel created by createAdmin().')\n }\n\n return admin\n}\n\n/**\n * Permissions are flattened by the server, so a check is a lookup. A super administrator\n * carries no permissions and passes everything — the same rule as on the server, in the one\n * place the front end asks the question.\n */\nexport function createAdminContext(options: {\n http: Http\n basePath: string\n apiPath: string\n modules: AdminModule[]\n i18n: I18n\n loadManifest: () => Promise<Manifest>\n loadDictionary?: (locale: string) => Promise<void>\n types?: TypeRegistry\n screens?: Record<string, Patch>\n}): AdminContext {\n const state = reactive<AdminState>({\n status: 'loading',\n manifest: null,\n user: null,\n error: null,\n })\n\n const nav = computed<NavEntry[]>(() => {\n const manifest = state.manifest\n\n if (manifest === null) {\n return []\n }\n\n const entries: NavEntry[] = []\n\n for (const module of manifest.modules) {\n const registered = options.modules.find((candidate) => candidate.id === module.id)\n\n // A module the server has and the front end does not is not a bug worth shouting\n // about — the panel is assembled from two halves and they are deployed separately —\n // but it has nowhere to send anybody, so it stays out of the menu.\n if (registered === undefined) {\n continue\n }\n\n const path = registered.path ?? registered.routes?.[0]?.path\n\n if (path === undefined) {\n continue\n }\n\n entries.push({\n id: module.id,\n title: module.title,\n icon: module.icon,\n path,\n group: module.group ?? null,\n })\n }\n\n return entries\n })\n\n const groups = computed(() => {\n const declared = state.manifest?.groups ?? []\n const top: NavEntry[] = []\n const byGroup = new Map<string, NavEntry[]>()\n\n for (const entry of nav.value) {\n // A group the server never declared is not a group: the entry stays at the top rather\n // than vanishing under a heading nobody can name.\n if (entry.group !== null && declared.some((group) => group.id === entry.group)) {\n const list = byGroup.get(entry.group) ?? []\n list.push(entry)\n byGroup.set(entry.group, list)\n } else {\n top.push(entry)\n }\n }\n\n return {\n top,\n groups: declared\n .filter((group) => byGroup.has(group.id))\n .map((group) => ({\n id: group.id,\n title: group.title,\n entries: byGroup.get(group.id) ?? [],\n })),\n }\n })\n\n // The core types are the renderer's own default; what is merged here is only what the panel\n // adds — its own frame, then a module's, then the project's, which therefore wins.\n const types: TypeRegistry = { ...adminTypes }\n\n for (const module of options.modules) {\n Object.assign(types, module.types)\n }\n\n Object.assign(types, options.types)\n\n const screens = new Map<string, Promise<ScreenNode[]>>()\n\n async function loadScreen(name: string): Promise<ScreenNode[]> {\n // The tree is translated on the server, so a screen is one thing per language.\n const key = `${options.i18n.state.locale}:${name}`\n let pending = screens.get(key)\n\n if (pending === undefined) {\n pending = options.http\n .get<{ data: { screen: string; root: ScreenNode[] } }>(`${options.apiPath}/screens/${name}`)\n .then((body) => body.data.root)\n .catch((error: unknown) => {\n // A failed request is not worth remembering: the next page open asks again.\n screens.delete(key)\n throw error\n })\n screens.set(key, pending)\n }\n\n return pending\n }\n\n let loadSession: (() => Promise<AdminUser | null>) | null = null\n\n async function reload(): Promise<void> {\n state.status = 'loading'\n state.error = null\n\n try {\n if (loadSession !== null) {\n state.user = await loadSession()\n\n // Asking for the manifest as a stranger would only produce the 401 we already know\n // about, and a spurious one in the network log for whoever is debugging.\n if (state.user === null) {\n state.manifest = null\n state.status = 'unauthenticated'\n\n return\n }\n }\n\n const manifest = await options.loadManifest()\n\n state.manifest = manifest\n options.i18n.state.contentLocales = manifest.locales ?? []\n options.i18n.state.panelLocales = manifest.panelLocales ?? options.i18n.state.panelLocales\n\n // The administrator's own choice, which the sign-in screen had no way of knowing: it\n // drew itself in whatever the browser asked for.\n if (manifest.locale !== undefined && manifest.locale !== options.i18n.state.locale) {\n await setLocale(manifest.locale)\n }\n\n state.status = 'ready'\n } catch (error) {\n // 401 is not a failure: it is the panel finding out nobody is signed in, which is the\n // normal way a visit starts.\n if (isUnauthenticated(error)) {\n state.manifest = null\n state.user = null\n state.status = 'unauthenticated'\n\n return\n }\n\n state.error = error instanceof Error ? error.message : String(error)\n state.status = 'error'\n }\n }\n\n async function refreshManifest(): Promise<void> {\n // Nothing to replace before the first load, and nothing worth asking for after a session\n // has ended: `reload()` owns both of those cases.\n if (state.manifest === null) {\n return\n }\n\n try {\n state.manifest = await options.loadManifest()\n } catch {\n // Deliberately silent — see the contract.\n }\n }\n\n async function setLocale(code: string): Promise<void> {\n if (options.loadDictionary === undefined) {\n options.i18n.state.locale = code\n\n return\n }\n\n await options.loadDictionary(code)\n\n // The dictionary is not the whole of the interface. Section titles are translated on the\n // server and travel inside the manifest, which was fetched in the previous language — so\n // without this the panel switches everything except its own navigation, and the sidebar\n // goes on naming the section in the language nobody is reading any more until the page is\n // reloaded. Only worth doing once there is a manifest to replace: during the first load\n // the caller is `reload()` itself, which is about to fetch one. A manifest that will not\n // come back is `reload()`'s problem to report — the language did change, and a stale\n // section title is not worth throwing away a working panel for.\n await refreshManifest()\n }\n\n return {\n http: options.http,\n basePath: options.basePath,\n apiPath: options.apiPath,\n state,\n i18n: options.i18n,\n modules: options.modules,\n nav,\n groups,\n types,\n reload,\n refreshManifest,\n setLocale,\n setUser(user) {\n state.user = user\n },\n useSessionLoader(loader) {\n loadSession = loader\n },\n can(permission) {\n const user = state.user\n\n if (user === null) {\n return false\n }\n\n return user.isSuper || user.permissions.includes(permission)\n },\n loadScreen,\n screenPatch(name) {\n return options.screens?.[name] ?? []\n },\n }\n}\n\nexport function provideAdmin(app: App, admin: AdminContext): void {\n app.provide(adminKey, admin)\n}\n\nfunction isUnauthenticated(error: unknown): boolean {\n return (\n typeof error === 'object' &&\n error !== null &&\n 'status' in error &&\n (error as { status: unknown }).status === 401\n )\n}\n","import { inject, reactive, type App, type InjectionKey } from 'vue'\n\n/**\n * One language the panel can be drawn in. Mirrors what `GET /api/cms/locales` answers with.\n */\nexport interface LocaleDescriptor {\n code: string\n name: string\n /** The language's name in itself — what belongs in a language picker. */\n nativeName: string\n direction: 'ltr' | 'rtl'\n default: boolean\n}\n\n/** A group of lines, nested as deeply as the `lang` file that produced it. */\nexport type Messages = { [key: string]: string | Messages }\n\n/** Namespace → group → lines, which is the shape the server assembles. */\nexport type Dictionary = Record<string, Record<string, Messages>>\n\nexport interface I18nState {\n /** The language the interface is being drawn in. */\n locale: string\n /** Where a missing line is looked for next. */\n fallback: string\n /** Languages the interface can be switched to. */\n panelLocales: LocaleDescriptor[]\n /** Languages the site publishes content in — every editing screen is built around this. */\n contentLocales: LocaleDescriptor[]\n}\n\nexport type Translate = (key: string, params?: Record<string, string | number>) => string\n\nexport interface I18n {\n readonly state: I18nState\n /**\n * Strings a package ships in its own code, used until the server's dictionary arrives and\n * for whatever the dictionary does not carry.\n *\n * This is what lets a package work with no server at all — a story, a test, a panel\n * assembled by hand — and what stops a missing translation from showing a key to somebody.\n */\n defaults(namespace: string, messages: Record<string, Messages>): void\n /** Replace the dictionary with what the server sent. */\n load(dictionary: Dictionary, locale: string, fallback?: string): void\n /** A `t()` bound to one namespace, so a component writes `t('shell.loading')`. */\n scope(namespace: string): Translate\n /** Absolute form: `t('webx-admin::shell.loading')`. */\n t: Translate\n}\n\nexport const i18nKey: InjectionKey<I18n> = Symbol('webx-i18n')\n\nexport function useI18n(): I18n {\n const i18n = inject(i18nKey, null)\n\n if (i18n === null) {\n throw new Error('useI18n() was called outside a panel created by createAdmin().')\n }\n\n return i18n\n}\n\n/**\n * A component that may be used outside a panel — a login card placed by hand — needs a\n * translator either way. This gives it the package's own English when there is no panel.\n */\nexport function useTranslate(namespace: string): Translate {\n const i18n = inject(i18nKey, null)\n\n return i18n === null ? createI18n().scope(namespace) : i18n.scope(namespace)\n}\n\nexport function provideI18n(app: App, i18n: I18n): void {\n app.provide(i18nKey, i18n)\n}\n\nexport function createI18n(options: { locale?: string; fallback?: string } = {}): I18n {\n const fallback = options.fallback ?? 'en'\n\n const state = reactive<I18nState>({\n locale: options.locale ?? fallback,\n fallback,\n panelLocales: [],\n contentLocales: [],\n })\n\n // Kept apart from the dictionary rather than merged into it: the server's answer is\n // replaced wholesale on every language change, and built-in strings have to survive that.\n const builtIn: Dictionary = {}\n const dictionary = reactive<{ value: Dictionary }>({ value: {} })\n\n function lookup(source: Dictionary, namespace: string, path: string[]): string | null {\n let node: string | Messages | undefined = source[namespace]?.[path[0] ?? '']\n\n for (const segment of path.slice(1)) {\n if (typeof node !== 'object' || node === null) {\n return null\n }\n\n node = node[segment]\n }\n\n return typeof node === 'string' ? node : null\n }\n\n function translate(\n namespace: string,\n key: string,\n params?: Record<string, string | number>,\n ): string {\n // An absolute key wins, so one namespace can borrow a line from another without a second\n // translator.\n const [explicitNamespace, rest] = key.includes('::')\n ? (key.split('::', 2) as [string, string])\n : [namespace, key]\n\n const path = rest.split('.')\n\n const line =\n lookup(dictionary.value, explicitNamespace, path) ??\n lookup(builtIn, explicitNamespace, path) ??\n // Not an empty string: a key on screen is ugly, but it says which key, and a blank\n // label says nothing to anybody trying to fix it.\n key\n\n return params === undefined ? line : fill(line, params)\n }\n\n return {\n state,\n defaults(namespace, messages) {\n builtIn[namespace] = { ...builtIn[namespace], ...messages }\n },\n load(next, locale, nextFallback) {\n // Guarded rather than trusted: an answer that is not the shape expected should leave\n // the panel in English, not without any words at all.\n dictionary.value = next ?? {}\n state.locale = locale ?? state.locale\n\n if (nextFallback !== undefined) {\n state.fallback = nextFallback\n }\n },\n scope(namespace) {\n return (key, params) => translate(namespace, key, params)\n },\n t: (key, params) => translate('', key, params),\n }\n}\n\n/**\n * `:name` placeholders, the way Laravel writes them — the strings come from its `lang` files,\n * so they should read the same on both sides.\n */\nfunction fill(line: string, params: Record<string, string | number>): string {\n let filled = line\n\n for (const [name, value] of Object.entries(params)) {\n filled = filled.replaceAll(`:${name}`, String(value))\n }\n\n return filled\n}\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { useRoute, useRouter } from 'vue-router'\nimport { useAdmin } from './admin'\nimport { useTranslate } from './i18n'\n\n/**\n * The menu, built from the manifest rather than written out. What the panel offers is what the\n * installation actually has — adding a module on the server and installing its front end is\n * the whole of \"adding a section\".\n *\n * Sections come first, then the groups the server declared — \"System\" for what keeps the\n * panel running — each as a branch that opens on its own when a section under it is current.\n */\ndefineProps<{ collapsed?: boolean }>()\n\nconst emit = defineEmits<{ select: [] }>()\n\nconst admin = useAdmin()\nconst t = useTranslate('webx-admin')\nconst router = useRouter()\nconst route = useRoute()\n\nconst current = computed<string>({\n get: () => {\n const match = admin.nav.value.find((entry) => route.path.startsWith(entry.path))\n\n return match?.id ?? ''\n },\n set: (id) => {\n const entry = admin.nav.value.find((candidate) => candidate.id === id)\n\n if (entry !== undefined) {\n void router.push(entry.path)\n }\n },\n})\n</script>\n\n<template>\n <wx-menu\n v-model=\"current\"\n :collapsed=\"collapsed\"\n :label=\"t('nav.sections')\"\n @select=\"emit('select')\"\n >\n <wx-menu-item\n v-for=\"entry in admin.groups.value.top\"\n :key=\"entry.id\"\n :value=\"entry.id\"\n :icon=\"entry.icon ?? undefined\"\n :label=\"entry.title\"\n />\n\n <wx-submenu\n v-for=\"group in admin.groups.value.groups\"\n :key=\"group.id\"\n :value=\"`group:${group.id}`\"\n :title=\"group.title\"\n icon=\"gear\"\n >\n <wx-menu-item\n v-for=\"entry in group.entries\"\n :key=\"entry.id\"\n :value=\"entry.id\"\n :icon=\"entry.icon ?? undefined\"\n :label=\"entry.title\"\n />\n </wx-submenu>\n </wx-menu>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\nimport { shellLayoutFor, useResponsiveShell, WxToaster } from '@webx-ui/core'\nimport { useAdmin } from './admin'\nimport { useTranslate } from './i18n'\n\n/**\n * The panel around the screen: navigation built from the manifest and the hole the router\n * fills.\n *\n * Three shapes, chosen by the width of the shell rather than the window: the full sidebar on a\n * desktop, an icon rail on a tablet, a drawer behind a burger on a phone.\n *\n * The sidebar is the whole of the chrome on the two wider ones — brand at the top, menu in the\n * middle with its own scrollbar, the account at the bottom — and there is no bar across the top\n * at all: 56px of the window's height went on a logo and an avatar, which are two things a\n * column 220px wide has room for. A phone has no such column, so there the bar comes back.\n *\n * What scrolls is the page. The sidebar stands still beside it, and screens that have to fill\n * the window measure themselves against the window.\n *\n * It draws nothing until the manifest has arrived, and nothing but the route while nobody is\n * signed in — the sign-in screen is a route like any other, and it has no business being\n * wrapped in a menu of sections the visitor cannot reach.\n */\nconst admin = useAdmin()\nconst t = useTranslate('webx-admin')\nconst shellEl = ref<HTMLElement | null>(null)\n\nconst { width, layout, collapsed, showAside, drawerOpen, toggle, close } = useResponsiveShell(\n shellEl,\n { persist: 'webx-admin-shell' },\n)\n\nconst title = computed(() => admin.state.manifest?.title ?? '')\n\n/*\n * The client's logo, when the settings hold one. The name never leaves: it is the alt of the\n * picture, what stands in the corner until the picture arrives, and what stands there for good\n * if the file behind it is gone.\n */\nconst logo = computed(() => admin.state.manifest?.branding?.logo ?? null)\nconst mark = computed(() => admin.state.manifest?.branding?.mark ?? null)\n\n/*\n * The step the frame is spaced by — 8 on a phone, 12 on a tablet, 16 on a desktop — follows the\n * size of the screen and not the shape the menu is in. A sidebar somebody collapsed by hand on a\n * 1440px desktop is still a desktop, and air measured off the menu would shrink with it.\n */\nconst size = computed(() => shellLayoutFor(width.value, null))\n</script>\n\n<template>\n <wx-toaster />\n\n <div v-if=\"admin.state.status === 'unauthenticated'\" class=\"wx-root wx-admin-plain\">\n <router-view />\n </div>\n\n <div v-else-if=\"admin.state.status === 'loading'\" class=\"wx-root wx-admin-plain\">\n <wx-loading :label=\"t('shell.loading')\" />\n </div>\n\n <div v-else-if=\"admin.state.status === 'error'\" class=\"wx-root wx-admin-plain\">\n <wx-result status=\"error\" :title=\"t('shell.error-title')\" :description=\"admin.state.error\">\n <wx-button type=\"primary\" @click=\"admin.reload()\">{{ t('shell.retry') }}</wx-button>\n </wx-result>\n </div>\n\n <div v-else ref=\"shellEl\" class=\"wx-root wx-admin\" :class=\"`wx-admin--${size}`\">\n <wx-container\n :direction=\"layout === 'drawer' ? 'vertical' : 'horizontal'\"\n full-height\n class=\"wx-admin__frame\"\n >\n <!--\n The phone's bar. It carries what the sidebar carries everywhere else, in the same\n order across instead of down: the way into the menu, the brand, the account.\n -->\n <wx-header\n v-if=\"layout === 'drawer'\"\n class=\"wx-admin__header\"\n floating\n sticky\n :bordered=\"false\"\n padding=\"sm\"\n height=\"var(--wx-admin-header-height)\"\n >\n <wx-action icon=\"menu\" :title=\"t('nav.menu')\" @click=\"toggle\" />\n\n <slot name=\"brand\">\n <img\n v-if=\"logo\"\n class=\"wx-admin__logo\"\n :src=\"logo.url\"\n :alt=\"title\"\n :width=\"logo.width ?? undefined\"\n :height=\"logo.height ?? undefined\"\n />\n <wx-text v-else weight=\"semibold\" truncate>{{ title }}</wx-text>\n </slot>\n\n <!-- The corner of a phone's bar has room for a face and nothing else. -->\n <template #end>\n <slot name=\"user\" :expanded=\"false\" />\n </template>\n </wx-header>\n\n <wx-aside\n v-if=\"showAside\"\n class=\"wx-admin__aside\"\n sticky\n floating\n scroll\n :bordered=\"false\"\n :collapsed=\"collapsed\"\n :width=\"220\"\n :collapsed-width=\"56\"\n >\n <template #top>\n <div class=\"wx-admin__brand\" :class=\"{ 'wx-admin__brand--stacked': collapsed && mark }\">\n <!--\n The rail has no room for a name, and the button is the one thing on it that\n has to stay: without it there is no way back to the full sidebar. A mark fits\n there, but not beside the button — 56px holds one of them at a time, so the two\n stand one above the other.\n -->\n <slot v-if=\"!collapsed\" name=\"brand\">\n <img\n v-if=\"logo\"\n class=\"wx-admin__logo\"\n :src=\"logo.url\"\n :alt=\"title\"\n :width=\"logo.width ?? undefined\"\n :height=\"logo.height ?? undefined\"\n />\n <wx-text v-else weight=\"semibold\" truncate class=\"wx-admin__title\">\n {{ title }}\n </wx-text>\n </slot>\n\n <img v-else-if=\"mark\" class=\"wx-admin__mark\" :src=\"mark.url\" :alt=\"title\" />\n\n <wx-action\n icon=\"sidebar\"\n :title=\"collapsed ? t('nav.expand') : t('nav.collapse')\"\n @click=\"toggle\"\n />\n </div>\n </template>\n\n <slot name=\"nav\" :collapsed=\"collapsed\" />\n\n <!--\n The zone at the foot of the sidebar is as wide as the sidebar, so an open one has\n room to say who this is rather than only show them. The rail does not, and there\n the face is the whole of it.\n -->\n <template #bottom>\n <div class=\"wx-admin__user\">\n <slot name=\"user\" :expanded=\"!collapsed\" />\n </div>\n </template>\n </wx-aside>\n\n <wx-main padding=\"none\" class=\"wx-admin__screen\">\n <router-view />\n </wx-main>\n </wx-container>\n\n <!--\n The same three zones the sidebar has, in the one place a phone can put them.\n\n Not full-screen, which is what a drawer does on a narrow screen by default: this one is\n a menu, and a menu that covers the page reads as having navigated away from it. Kept to\n its width, the page it came from stays visible behind.\n -->\n <wx-drawer\n v-model:open=\"drawerOpen\"\n :title=\"title === '' ? t('nav.menu') : title\"\n side=\"left\"\n :size=\"260\"\n :full-screen=\"false\"\n closable\n >\n <slot name=\"nav\" :collapsed=\"false\" :select=\"close\" />\n\n <template #footer>\n <div class=\"wx-admin__user wx-admin__user--drawer\">\n <slot name=\"user\" :expanded=\"true\" />\n </div>\n </template>\n </wx-drawer>\n </div>\n</template>\n\n<style scoped>\n/*\n * The page scrolls, so the shell is at least a window tall rather than exactly one. The body\n * colour is here as well as on the container: it is what shows around the floating cards, and\n * on a short screen the container ends where the content does.\n */\n.wx-admin {\n /* The phone's bar, in one place: it is the bar's own height and part of what is left\n over for a screen that has to fill the window. */\n --wx-admin-header-height: 52px;\n\n min-height: 100dvh;\n background: var(--wx-bg-body);\n}\n\n/*\n * One step for the whole panel (§6 of the visual spec) — the air around the sidebar, around\n * the bar and between them and the screen, and, because a custom property inherits, the air\n * between the cards on a screen, down the columns of a grid, between the fields of a form and\n * inside a card. Everything that lays anything out reads it from here.\n *\n * The width decides, and it is the shell's size class that says which width — not a container\n * query: that would need `container-type` on the panel's root, and with it a containing block\n * for every `position: fixed` inside. A sidebar collapsed by hand at 1440 is still a desktop.\n */\n.wx-admin--drawer {\n --wx-gap: var(--wx-space-8);\n}\n\n.wx-admin--rail {\n --wx-gap: var(--wx-space-12);\n}\n\n.wx-admin--sidebar {\n --wx-gap: var(--wx-space-16);\n}\n\n.wx-admin__frame {\n padding: var(--wx-gap);\n gap: var(--wx-gap);\n}\n\n.wx-admin__header {\n --wx-header-top: var(--wx-gap);\n}\n\n/*\n * The column stands in the window, inset by the same step on both sides, and what scrolls\n * inside it is the menu between the two zones.\n */\n.wx-admin__aside {\n --wx-aside-top: var(--wx-gap);\n --wx-aside-height: calc(100dvh - var(--wx-gap) * 2);\n}\n\n.wx-admin__brand {\n display: flex;\n align-items: center;\n gap: var(--wx-space-8);\n box-sizing: border-box;\n min-height: 48px;\n padding: var(--wx-space-8);\n border-bottom: 1px solid var(--wx-border-muted);\n}\n\n/* On the rail the one button in this zone stands where the icons below it stand. */\n.wx-aside--collapsed .wx-admin__brand {\n justify-content: center;\n padding-inline: var(--wx-space-4);\n}\n\n.wx-admin__brand--stacked {\n flex-direction: column;\n gap: var(--wx-space-4);\n}\n\n.wx-admin__title {\n flex: 1 1 auto;\n min-width: 0;\n}\n\n/*\n * The button for collapsing the sidebar stands at the far end of the brand row, against the\n * edge the sidebar closes towards. A title gets it there by growing into the space; a logo\n * is only as wide as its file, so the space has to be given to it explicitly.\n *\n * Only in this row: the phone's bar puts the account at its far end by the same means, and a\n * second automatic margin there would share the space between them and leave the logo adrift\n * in the middle.\n */\n.wx-admin__brand > .wx-admin__logo {\n margin-inline-end: auto;\n}\n\n/*\n * The logo is given a height and takes whatever width that leaves it: a client's file can be\n * any shape, and the one measurement the corner can promise is how tall a brand is allowed to\n * be.\n *\n * `min-width: 0` is what actually holds a wide one in. A picture in a flex row refuses to go\n * below its natural width, and `max-width: 100%` is measured before the button beside it is\n * placed — so a 1600px wordmark took the whole sidebar and pushed the button for collapsing\n * it 15px outside the panel. Measured, not guessed.\n */\n.wx-admin__logo {\n flex: 0 1 auto;\n min-width: 0;\n height: 28px;\n width: auto;\n max-width: 100%;\n object-fit: contain;\n object-position: left center;\n}\n\n.wx-admin__mark {\n height: 24px;\n width: 24px;\n object-fit: contain;\n}\n\n.wx-admin__user {\n display: flex;\n align-items: center;\n padding: var(--wx-space-8);\n border-top: 1px solid var(--wx-border-muted);\n}\n\n.wx-aside--collapsed .wx-admin__user {\n justify-content: center;\n}\n\n/*\n * In the drawer the footer already has its own rule and padding. It also puts what it holds\n * at the far end, where a row of buttons belongs — the account is not that, and it stands\n * where it stands in the sidebar.\n */\n.wx-admin__user--drawer {\n flex: 1 1 auto;\n justify-content: flex-start;\n padding: 0;\n border-top: none;\n}\n\n/*\n * A screen that has to be exactly as tall as the window — the page editor, the kanban, the\n * media library — is told how much of the window the frame has left it. On the two wider\n * shapes that is the air above and below; on a phone the bar and its own step as well.\n */\n.wx-admin__screen {\n --wx-fill-height: calc(100dvh - var(--wx-gap) * 2);\n\n /* And where a screen's own action bar stops, short of the edge, so that it floats with\n the same air as the sidebar beside it. */\n --wx-action-bar-bottom: var(--wx-gap);\n\n min-width: 0;\n min-height: 0;\n}\n\n.wx-admin--drawer .wx-admin__screen {\n --wx-fill-height: calc(100dvh - var(--wx-gap) * 3 - var(--wx-admin-header-height));\n}\n\n/* The states with no shell around them: sign-in, loading, and the one where the panel could\n not start. Each is a single thing in the middle of an empty page. */\n.wx-admin-plain {\n display: grid;\n place-items: center;\n /* Without this the padding is added to the viewport height and the page scrolls by exactly\n the padding. */\n box-sizing: border-box;\n min-height: 100dvh;\n padding: var(--wx-space-16);\n background: var(--wx-bg-body);\n}\n</style>\n\n<style>\n/* Not scoped, and global on purpose: the panel is the whole page, so the browser default\n margin on <body> shows up as a gap around the shell — and an uneven one, since the shell\n already keeps its own air around the frame. */\nhtml:has(> body > #webx-app),\nbody:has(> #webx-app) {\n margin: 0;\n}\n\n/*\n * A phone's icon buttons are one size down from a desktop's. On a card list the `···` at the\n * corner of every record was drawn at the same 36px as on a 1440px screen, where it is one\n * item among many — here it is the second thing on the card after its title.\n *\n * Set on the buttons themselves rather than on the shell: `.wx-action--md` declares this\n * variable on its own element, and a value inherited from an ancestor never beats one\n * declared on the element that reads it, however far up the ancestor is. Unscoped for the\n * same reason — the buttons belong to other components, and a scoped rule carries an\n * attribute only this one's elements have.\n *\n * The shell's own size class rather than a media query: it is the panel's width that decides\n * everything else here, and a panel is not always the window.\n */\n.wx-admin--drawer .wx-action--md {\n --wx-action-size: 30px;\n}\n\n.wx-admin--drawer .wx-action--lg {\n --wx-action-size: 36px;\n}\n</style>\n","/**\n * The panel's way of talking to its own backend.\n *\n * Small on purpose — it is not a general HTTP library, it is the handful of conventions\n * `webx-ui/module-admin` and `webx-ui/module-auth` answer with: a session cookie rather than a token,\n * 422 for a bad form, 401 for a stranger, 429 with `Retry-After` when somebody is guessing.\n */\n\nexport interface HttpOptions {\n /** Prefixed to every relative path, e.g. `/api/cms`. */\n baseUrl?: string\n /** Where to fetch the CSRF cookie from before an unsafe request. */\n csrfUrl?: string\n /** Called whenever the server answers 401, however deep in the app the call was. */\n onUnauthenticated?: () => void\n /**\n * Headers added to every request, read at the time of the request rather than fixed when\n * the client is made — the panel's language changes while it runs.\n */\n headers?: () => Record<string, string>\n /** Swappable for tests. */\n fetch?: typeof globalThis.fetch\n}\n\nexport interface RequestOptions {\n /** Query parameters; `undefined` and `null` are left out rather than sent empty. */\n query?: Record<string, string | number | boolean | null | undefined>\n headers?: Record<string, string>\n signal?: AbortSignal\n}\n\n/**\n * Everything that went wrong, in the shape the panel needs to react:\n * `errors` goes straight into `WxForm`, `retryAfter` into \"try again in a moment\".\n */\nexport class HttpError extends Error {\n constructor(\n message: string,\n readonly status: number,\n readonly errors: Record<string, string[]> = {},\n readonly retryAfter: number | null = null,\n readonly body: unknown = null,\n ) {\n super(message)\n this.name = 'HttpError'\n }\n\n /** A failed form rather than a failed request. */\n get isValidation(): boolean {\n return this.status === 422\n }\n\n get isUnauthenticated(): boolean {\n return this.status === 401\n }\n\n get isThrottled(): boolean {\n return this.status === 429\n }\n}\n\nexport interface Http {\n get<T>(path: string, options?: RequestOptions): Promise<T>\n post<T>(path: string, body?: unknown, options?: RequestOptions): Promise<T>\n put<T>(path: string, body?: unknown, options?: RequestOptions): Promise<T>\n patch<T>(path: string, body?: unknown, options?: RequestOptions): Promise<T>\n delete<T>(path: string, options?: RequestOptions): Promise<T>\n}\n\nconst UNSAFE = new Set(['POST', 'PUT', 'PATCH', 'DELETE'])\n\nexport function createHttp(options: HttpOptions = {}): Http {\n const baseUrl = (options.baseUrl ?? '').replace(/\\/$/, '')\n const csrfUrl = options.csrfUrl ?? '/sanctum/csrf-cookie'\n const doFetch = options.fetch ?? globalThis.fetch.bind(globalThis)\n const onUnauthenticated = options.onUnauthenticated\n const standingHeaders = options.headers\n\n let csrfFetched = false\n\n async function ensureCsrfCookie(force = false): Promise<void> {\n if (csrfFetched && !force && readCookie('XSRF-TOKEN') !== null) {\n return\n }\n\n await doFetch(csrfUrl, { credentials: 'same-origin' })\n csrfFetched = true\n }\n\n async function request<T>(\n method: string,\n path: string,\n body?: unknown,\n options: RequestOptions = {},\n retried = false,\n ): Promise<T> {\n const unsafe = UNSAFE.has(method)\n\n if (unsafe) {\n await ensureCsrfCookie()\n }\n\n const headers: Record<string, string> = {\n Accept: 'application/json',\n 'X-Requested-With': 'XMLHttpRequest',\n // Standing headers first, so a caller can still override one for a single request.\n ...standingHeaders?.(),\n ...options.headers,\n }\n\n if (unsafe) {\n const token = readCookie('XSRF-TOKEN')\n\n if (token !== null) {\n headers['X-XSRF-TOKEN'] = token\n }\n }\n\n if (body !== undefined) {\n headers['Content-Type'] = 'application/json'\n }\n\n const response = await doFetch(url(baseUrl, path, options.query), {\n method,\n credentials: 'same-origin',\n headers,\n signal: options.signal,\n body: body === undefined ? undefined : JSON.stringify(body),\n })\n\n // 419 is Laravel for \"your CSRF token has gone stale\", which happens after a session is\n // regenerated — at sign-in, most of all. Fetching a fresh one and going again is what a\n // person would do by reloading, without the reload.\n if (response.status === 419 && unsafe && !retried) {\n await ensureCsrfCookie(true)\n\n return request<T>(method, path, body, options, true)\n }\n\n if (response.status === 401) {\n onUnauthenticated?.()\n }\n\n if (!response.ok) {\n throw await toError(response)\n }\n\n if (response.status === 204) {\n return undefined as T\n }\n\n return (await response.json()) as T\n }\n\n return {\n get: (path, options) => request('GET', path, undefined, options),\n post: (path, body, options) => request('POST', path, body, options),\n put: (path, body, options) => request('PUT', path, body, options),\n patch: (path, body, options) => request('PATCH', path, body, options),\n delete: (path, options) => request('DELETE', path, undefined, options),\n }\n}\n\nasync function toError(response: Response): Promise<HttpError> {\n let body: unknown = null\n\n try {\n body = await response.json()\n } catch {\n // A gateway or a fatal error answers with HTML; there is nothing to read out of it.\n }\n\n const payload = (body ?? {}) as { message?: unknown; errors?: unknown }\n const message =\n typeof payload.message === 'string' && payload.message !== ''\n ? payload.message\n : response.statusText || `Request failed with ${response.status}`\n\n const errors =\n payload.errors !== null && typeof payload.errors === 'object'\n ? (payload.errors as Record<string, string[]>)\n : {}\n\n const header = response.headers.get('Retry-After')\n const retryAfter = header === null ? null : Number.parseInt(header, 10)\n\n return new HttpError(\n message,\n response.status,\n errors,\n Number.isFinite(retryAfter) ? retryAfter : null,\n body,\n )\n}\n\nfunction url(baseUrl: string, path: string, query?: RequestOptions['query']): string {\n const absolute = /^https?:\\/\\//i.test(path)\n const full = absolute ? path : `${baseUrl}/${path.replace(/^\\//, '')}`\n\n if (query === undefined) {\n return full\n }\n\n const search = new URLSearchParams()\n\n for (const [key, value] of Object.entries(query)) {\n if (value !== undefined && value !== null) {\n search.set(key, String(value))\n }\n }\n\n const serialised = search.toString()\n\n return serialised === '' ? full : `${full}${full.includes('?') ? '&' : '?'}${serialised}`\n}\n\n/**\n * Laravel writes the token URL-encoded, and it is read back the same way it was written.\n */\nexport function readCookie(name: string): string | null {\n if (typeof document === 'undefined') {\n return null\n }\n\n for (const part of document.cookie.split(';')) {\n const [key, ...rest] = part.trim().split('=')\n\n if (key === name) {\n return decodeURIComponent(rest.join('='))\n }\n }\n\n return null\n}\n","import type { Messages } from './i18n'\n\n/**\n * The panel's own words, in English.\n *\n * The same keys `webx-ui/module-admin` ships as `lang/en/*.php`, kept here so the package works with\n * no server behind it. Anything the server sends wins; this is the floor, not the source of\n * truth. Translations belong in the Composer package, where one file serves both halves.\n */\nexport const adminMessages: Record<string, Messages> = {\n shell: {\n loading: 'Loading the panel…',\n 'error-title': 'The panel could not start',\n retry: 'Try again',\n 'empty-title': 'Nothing is installed yet',\n 'empty-description': 'This panel has no modules. Install one and it will appear here.',\n },\n nav: {\n sections: 'Sections',\n menu: 'Menu',\n collapse: 'Collapse the menu',\n expand: 'Expand the menu',\n language: 'Language',\n // The one menu group the panel names itself; a module's own group is named by the module.\n system: 'System',\n },\n // What a screen that edits one record offers whatever the record is: the way out of it, and\n // the way to rename it. A module that opens an editor should not be inventing these.\n editor: {\n back: 'Back',\n rename: 'Rename',\n save: 'Save',\n },\n // The page behind a '?'. Only the heading is the panel's: what the page says belongs to\n // whatever is being explained, and travels with that module's own words.\n help: {\n title: 'Help',\n },\n // When something happened, said the way a person would. The month names and the order of\n // the parts come from `Intl` — only the words that no formatter knows are here.\n dates: {\n today: 'today at :time',\n yesterday: 'yesterday at :time',\n // Not an empty cell and not a dash: a column that says nothing leaves a reader wondering\n // whether the panel failed to load it.\n never: 'never',\n },\n // How a request fails, in the panel's words rather than the server's (§13.3). `errors.ts`\n // decides which of these a status gets.\n errors: {\n 'signed-out': 'You are signed out. Sign in again and try once more.',\n forbidden: 'You are not allowed to do that.',\n gone: 'It is not there any more — somebody may have deleted it.',\n conflict: 'Somebody changed this while you were working on it.',\n throttled: 'Too many attempts. Try again in a moment.',\n 'throttled-in': 'Too many attempts. Try again in :seconds seconds.',\n server: 'The server could not do that. Try again in a moment.',\n offline: 'The server did not answer. Check the connection and try again.',\n unknown: 'That did not work.',\n },\n}\n","import { computed, createApp, h, ref, type App, type Component } from 'vue'\nimport { createRouter, createWebHistory, type Router, type RouteRecordRaw } from 'vue-router'\nimport { localesKey, WebxUI, type LocaleOption } from '@webx-ui/core'\nimport AdminNav from './AdminNav.vue'\nimport AdminShell from './AdminShell.vue'\nimport { createAdminContext, provideAdmin, type AdminContext } from './admin'\nimport { createHttp, type Http } from './http'\nimport { createI18n, provideI18n, type Dictionary, type I18n, type LocaleDescriptor } from './i18n'\nimport { adminMessages } from './messages'\nimport type { Patch, TypeRegistry } from '@webx-ui/schema'\nimport type { AdminModule, Manifest } from './types'\n\nconst STORED_LOCALE = 'webx.locale'\n\nexport interface CreateAdminOptions {\n /** Where to mount. Defaults to `#webx-app`, which is what the Blade shell renders. */\n el?: string | Element\n /**\n * Where the manifest lives. Defaults to the `webx-manifest` meta tag the Blade shell writes,\n * and to `/api/cms/manifest` when there is none — which is the case under a dev server,\n * where the page is Vite's own index.html.\n */\n manifestUrl?: string\n /**\n * Where the panel's JSON lives, e.g. `/api/cms`. Modules build their own addresses from it.\n * Derived from the manifest URL when not given.\n */\n apiPath?: string\n /** Sections of the panel. */\n modules?: AdminModule[]\n /** Routes that belong to no module: a dashboard, a 404. */\n routes?: RouteRecordRaw[]\n /**\n * Where the panel is served, for the router's history base. Taken from the manifest when it\n * arrives; given here for the first paint, before it has.\n */\n basePath?: string\n /** Replaces the panel's name in the header — a logo, usually. */\n brand?: Component\n /** The corner of the header: who is signed in, and the way out. */\n userMenu?: Component\n /** Extensions that need the router and the context: an auth module, most of all. */\n plugins?: AdminPlugin[]\n /**\n * The language to draw the panel in before the server has been asked. Defaults to the last\n * one used, then to the page's `lang`, then to the browser's. Whatever is chosen, the server\n * narrows it to a language the panel actually has.\n */\n locale?: string\n /**\n * Screen node types the project adds — `{ map: { component: WxMapField, kind: 'field' } }` —\n * over the core's and the modules'.\n */\n types?: TypeRegistry\n /**\n * The project's patches over the screens modules ship, by screen name, applied on the\n * client on top of what the server hands out. What a patch cannot do from here is open a\n * key for writing: the server decides what is saved.\n */\n screens?: Record<string, Patch>\n /** Swappable for tests. */\n http?: Http\n}\n\n/**\n * Something that needs the assembled panel rather than a slot in it — it adds routes, guards\n * the router, or tells the panel how to find out who is signed in.\n */\nexport interface AdminPlugin {\n install(admin: Admin): void\n}\n\nexport interface Admin {\n app: App\n router: Router\n context: AdminContext\n i18n: I18n\n mount(): Promise<void>\n}\n\n/**\n * Assemble the panel.\n *\n * Mounting does not wait for the server. The manifest needs a signed-in session, so a visit\n * that starts at the sign-in screen would otherwise stare at a blank page until a request it\n * is bound to lose comes back.\n */\nexport function createAdmin(options: CreateAdminOptions = {}): Admin {\n const manifestUrl = options.manifestUrl ?? readManifestUrl() ?? '/api/cms/manifest'\n // The Blade shell writes the manifest address rather than the API root, and every module\n // needs the root, so it is read back out of the one thing the page does say.\n const apiPath = options.apiPath ?? manifestUrl.replace(/\\/manifest\\/?$/, '')\n const basePath = options.basePath ?? '/cms'\n const modules = options.modules ?? []\n\n const routes: RouteRecordRaw[] = [...(options.routes ?? [])]\n\n for (const module of modules) {\n routes.push(...(module.routes ?? []))\n }\n\n const router = createRouter({\n history: createWebHistory(basePath),\n routes,\n })\n\n const i18n = createI18n({ locale: options.locale ?? preferredLocale() })\n\n const http =\n options.http ??\n createHttp({\n baseUrl: '',\n onUnauthenticated: () => {\n context.setUser(null)\n context.state.status = 'unauthenticated'\n },\n // Every request says which language the panel is currently showing. It decides what\n // the server writes its own messages in — a 422 under a field — for anybody who has\n // not stored a preference yet, which is everybody until they choose one. Without it,\n // signing in on a Russian sign-in screen lands in an English panel.\n headers: () => ({ 'X-Webx-Locale': i18n.state.locale }),\n })\n\n i18n.defaults('webx-admin', adminMessages)\n\n async function loadDictionary(locale: string): Promise<void> {\n const body = await http.get<{\n data: { locale: string; fallback: string; namespaces: Dictionary }\n }>(`${apiPath}/translations/${locale}`)\n\n i18n.load(body.data.namespaces, body.data.locale, body.data.fallback)\n rememberLocale(body.data.locale)\n markDocumentLanguage(body.data.locale, i18n.state.panelLocales)\n }\n\n const context = createAdminContext({\n http,\n basePath,\n apiPath,\n modules,\n i18n,\n loadDictionary,\n types: options.types,\n screens: options.screens,\n loadManifest: async () => {\n const body = await http.get<{ data: Manifest }>(manifestUrl)\n\n return body.data\n },\n })\n\n const app = createApp(rootComponent(options))\n\n app.use(WebxUI)\n provideAdmin(app, context)\n provideI18n(app, i18n)\n\n /*\n * The languages a localized field offers are the site's *content* languages, not the ones the\n * panel can be drawn in: a panel in English routinely edits a site published in Ukrainian and\n * Russian. They arrive with the manifest, so this is a computed over what is already there\n * rather than a second request — and a form written before they arrive simply has nothing to\n * switch between yet.\n */\n const editing = ref('')\n\n app.provide(localesKey, {\n list: computed<LocaleOption[]>(() =>\n i18n.state.contentLocales.map((locale) => ({\n code: locale.code,\n label: locale.code.toUpperCase(),\n })),\n ),\n active: computed({\n get: () => editing.value || (i18n.state.contentLocales[0]?.code ?? ''),\n set: (code: string) => {\n editing.value = code\n },\n }),\n })\n\n const admin: Admin = {\n app,\n router,\n context,\n i18n,\n async mount() {\n // The one thing worth waiting for. It is a public, cached request, and painting the\n // sign-in screen in English and then swapping every label a moment later looks like a\n // bug rather than like a translation arriving. The manifest is still not waited for —\n // that one needs a session and is bound to 401 for a visitor.\n await Promise.all([loadPanelLocales(), loadDictionary(i18n.state.locale)]).catch(() => {\n // A server that cannot answer these cannot run a panel either, and the built-in\n // English is a better thing to fail with than a blank page.\n })\n\n app.mount(options.el ?? '#webx-app')\n\n await context.reload()\n },\n }\n\n async function loadPanelLocales(): Promise<void> {\n const body = await http.get<{\n data: { panel: LocaleDescriptor[]; content: LocaleDescriptor[] }\n }>(`${apiPath}/locales`)\n\n i18n.state.panelLocales = body.data.panel\n i18n.state.contentLocales = body.data.content\n }\n\n // Plugins go on before the router does, because installing the router is what starts the\n // first navigation. A route added after that is a route the visit already failed to match:\n // opening /login directly would land on nothing while /cms worked, because / matched and the\n // redirect to /login happened later, by which time the route existed.\n for (const plugin of options.plugins ?? []) {\n plugin.install(admin)\n }\n\n app.use(router)\n\n return admin\n}\n\n/** What the shell tells the navigation slot about where it is drawing it. */\ninterface NavSlotProps {\n collapsed?: boolean\n select?: () => void\n /** Whether the corner it is drawing the account in has room for a name beside the face. */\n expanded?: boolean\n}\n\nfunction rootComponent(options: CreateAdminOptions): Component {\n const slots: Record<string, (props: NavSlotProps) => unknown> = {\n // The menu is the panel's own: it is the manifest, drawn.\n // The shell hands in what a choice means where it drew the menu — in the drawer it is what\n // closes it, and a drawer that stays open over the section it just opened is a drawer the\n // reader has to dismiss by hand.\n nav: (props) => h(AdminNav, { collapsed: props.collapsed === true, onSelect: props.select }),\n }\n\n if (options.brand !== undefined) {\n slots.brand = () => h(options.brand as Component)\n }\n\n // The shell draws the account in three corners of different widths and says which; the menu\n // decides what it can show there.\n if (options.userMenu !== undefined) {\n slots.user = (props) => h(options.userMenu as Component, { expanded: props.expanded === true })\n }\n\n return { render: () => h(AdminShell, null, slots) }\n}\n\n/**\n * The language to ask for first. A guess, and treated as one — the server answers with the\n * language it actually has, and that is what the panel adopts.\n */\nfunction preferredLocale(): string {\n const remembered = read(STORED_LOCALE)\n\n if (remembered !== null) {\n return remembered\n }\n\n if (typeof document !== 'undefined' && document.documentElement.lang !== '') {\n return document.documentElement.lang\n }\n\n return typeof navigator === 'undefined' ? 'en' : navigator.language\n}\n\nfunction rememberLocale(locale: string): void {\n // A per-browser convenience, so a signed-out reload of the sign-in screen keeps the\n // language. The choice that lasts is the one stored against the administrator.\n try {\n localStorage.setItem(STORED_LOCALE, locale)\n } catch {\n // Private windows, blocked site data. Nothing here is worth an error.\n }\n}\n\nfunction read(key: string): string | null {\n try {\n return localStorage.getItem(key)\n } catch {\n return null\n }\n}\n\n/** So the browser hyphenates, spell-checks and reads the page aloud in the right language. */\nfunction markDocumentLanguage(locale: string, locales: LocaleDescriptor[]): void {\n if (typeof document === 'undefined') {\n return\n }\n\n document.documentElement.lang = locale\n document.documentElement.dir =\n locales.find((candidate) => candidate.code === locale)?.direction ?? 'ltr'\n}\n\nfunction readManifestUrl(): string | null {\n if (typeof document === 'undefined') {\n return null\n }\n\n const meta = document.querySelector('meta[name=\"webx-manifest\"]')\n\n return meta?.getAttribute('content') ?? null\n}\n","/**\n * The little of Markdown a page of help needs, turned into HTML.\n *\n * Not a Markdown library. This exists so that one piece of prose can serve two readers — the\n * panel shows it in a dialog, an agent reads the same text over MCP as `text/markdown` — and\n * so it has to understand exactly what such a page is written with: headings, paragraphs,\n * lists, fenced code, and inline code, links and emphasis inside a line.\n *\n * Everything is escaped before any tag is emitted, so the output contains only the tags built\n * here. That is what makes it safe to hand to `v-html`: the text comes from the server's\n * dictionary, which is a site's own `lang` files, and a page of help is not a place where\n * anybody should have to think about whether it is.\n */\n\nexport function renderMarkdown(source: string): string {\n const lines = source.replace(/\\r\\n?/g, '\\n').split('\\n')\n const out: string[] = []\n\n let index = 0\n\n while (index < lines.length) {\n const line = lines[index] ?? ''\n\n if (line.trim() === '') {\n index += 1\n continue\n }\n\n const fence = /^```([\\w-]*)\\s*$/.exec(line)\n\n if (fence) {\n const code: string[] = []\n index += 1\n\n while (index < lines.length && !/^```\\s*$/.test(lines[index] ?? '')) {\n code.push(lines[index] ?? '')\n index += 1\n }\n\n // The closing fence, or the end of the text when the writer forgot one.\n index += 1\n out.push(`<pre><code>${escape(code.join('\\n'))}</code></pre>`)\n continue\n }\n\n const heading = /^(#{1,4})\\s+(.*)$/.exec(line)\n\n if (heading) {\n // Levels start at 2: the dialog's own title is the first heading on screen.\n const level = Math.min(heading[1]!.length + 1, 5)\n out.push(`<h${level}>${inline(heading[2] ?? '')}</h${level}>`)\n index += 1\n continue\n }\n\n if (isItem(line)) {\n const ordered = /^\\s*\\d+[.)]\\s+/.test(line)\n const items: string[] = []\n\n while (index < lines.length && isItem(lines[index] ?? '')) {\n items.push((lines[index] ?? '').replace(/^\\s*(?:[-*+]|\\d+[.)])\\s+/, ''))\n index += 1\n\n /*\n * An item that runs onto the next line is still that item. Without this the rest of a\n * wrapped sentence became a paragraph of its own, outside the list and after it —\n * which is how a page of prose written at 96 columns falls apart on the way in.\n */\n while (index < lines.length) {\n const next = lines[index] ?? ''\n\n if (next.trim() === '' || isItem(next) || /^(#{1,4})\\s/.test(next) || /^```/.test(next)) {\n break\n }\n\n items[items.length - 1] += ` ${next.trim()}`\n index += 1\n }\n }\n\n const tag = ordered ? 'ol' : 'ul'\n out.push(`<${tag}>${items.map((item) => `<li>${inline(item)}</li>`).join('')}</${tag}>`)\n continue\n }\n\n // A paragraph runs to the next blank line; a single newline inside one is not a break.\n const paragraph: string[] = []\n\n while (index < lines.length) {\n const next = lines[index] ?? ''\n\n if (next.trim() === '' || isItem(next) || /^(#{1,4})\\s/.test(next) || /^```/.test(next)) break\n\n paragraph.push(next.trim())\n index += 1\n }\n\n out.push(`<p>${inline(paragraph.join(' '))}</p>`)\n }\n\n return out.join('')\n}\n\n/*\n * What a code span is held out of the line by while the rest is marked up. A NUL, because\n * it is the one character a page of help cannot contain — written as an escape rather than\n * put in the file, where it would be a control character nobody reviewing this can see.\n */\nconst HOLE = '\\u0000'\n\nfunction isItem(line: string): boolean {\n return /^\\s*(?:[-*+]|\\d+[.)])\\s+/.test(line)\n}\n\n/**\n * What can appear inside a line. Code first, and what it holds is put back only after the rest\n * has been marked up — otherwise an underscore or a bracket inside a code span is read as\n * emphasis or as a link, which is exactly what a code span is for avoiding.\n */\nfunction inline(text: string): string {\n const spans: string[] = []\n\n const withHoles = text.replace(/`([^`]+)`/g, (_, code: string) => {\n spans.push(`<code>${escape(code)}</code>`)\n\n return `${HOLE}${spans.length - 1}${HOLE}`\n })\n\n const marked = escape(withHoles)\n .replace(\n /\\[([^\\]]+)\\]\\((https?:\\/\\/[^\\s)]+)\\)/g,\n '<a href=\"$2\" target=\"_blank\" rel=\"noopener\">$1</a>',\n )\n .replace(/\\*\\*([^*]+)\\*\\*/g, '<strong>$1</strong>')\n\n return marked.replace(\n new RegExp(`${HOLE}(\\\\d+)${HOLE}`, 'g'),\n (_, at: string) => spans[Number(at)] ?? '',\n )\n}\n\nfunction escape(text: string): string {\n return text\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n}\n","import { inject } from 'vue'\nimport { createI18n, i18nKey, type I18n } from './i18n'\nimport { adminMessages } from './messages'\n\n/** A moment as it arrives: ISO-8601 from the server, a timestamp, a `Date`, or nothing. */\nexport type DateLike = string | number | Date | null | undefined\n\nexport interface Dates {\n /**\n * The line a reader sees: \"today at 08:10\", \"yesterday at 14:03\", \"16 September at 14:03\",\n * \"16 September 2025\", \"never\".\n */\n short(value: DateLike): string\n /**\n * Everything there is, down to the second — what the tip holds when the short line is not\n * precise enough to settle an argument. Empty when there is no date.\n */\n exact(value: DateLike): string\n /** The machine value for `<time datetime>`. Empty when there is no date. */\n iso(value: DateLike): string\n}\n\n/**\n * How the panel says when something happened — one answer for every screen in it.\n *\n * Three rules it would be easy to lose, and each of them was a bug before this existed:\n *\n * - the language is the panel's, not the browser's. `toLocaleString()` with no locale reads\n * the browser's settings, so a panel drawn in Russian was dating its rows in American\n * order. The locale comes from `i18n`, which is what the administrator chose.\n * - \"today\" is counted in the administrator's own day. The server sends UTC; comparing UTC\n * dates would write \"yesterday\" over anything that happened before 03:00 in Moscow. The\n * comparison is made on local calendar days, which is what the reader means by today.\n * - the shown string is never what sorts. Nothing here touches ordering: a column sorts on\n * the value the server sent, or \"yesterday\" lands between the sixteenth and the seventeenth.\n */\nexport function createDates(i18n: I18n): Dates {\n const t = i18n.scope('webx-admin')\n\n // A table asks for the same two or three shapes once per row, and building an\n // `Intl.DateTimeFormat` is the expensive part of formatting. Keyed by locale as well as by\n // shape, so switching the panel's language does not hand back the old language's months.\n const formatters = new Map<string, Intl.DateTimeFormat>()\n\n function format(shape: string, options: Intl.DateTimeFormatOptions, at: Date): string {\n const locale = i18n.state.locale\n const key = `${locale}:${shape}`\n let formatter = formatters.get(key)\n\n if (formatter === undefined) {\n formatter = new Intl.DateTimeFormat(locale, options)\n formatters.set(key, formatter)\n }\n\n return formatter.format(at)\n }\n\n /* 24 hours everywhere, whatever the locale would have chosen: a panel is read in a hurry and\n * \"08:10\" is one glance where \"8:10 AM\" is two. Seconds are left to `exact()`. */\n const time = (at: Date) =>\n format('time', { hour: '2-digit', minute: '2-digit', hourCycle: 'h23' }, at)\n\n return {\n short(value) {\n const at = toDate(value)\n\n if (at === null) {\n return t('dates.never')\n }\n\n const now = new Date()\n const days = daysApart(at, now)\n\n if (days === 0) return t('dates.today', { time: time(at) })\n if (days === -1) return t('dates.yesterday', { time: time(at) })\n\n // Within this year the year itself says nothing — it is the time of day that tells two\n // entries apart. Further back it is the other way round, and a time nobody will compare\n // is noise in the column.\n return at.getFullYear() === now.getFullYear()\n ? format(\n 'this-year',\n {\n day: 'numeric',\n month: 'long',\n hour: '2-digit',\n minute: '2-digit',\n hourCycle: 'h23',\n },\n at,\n )\n : format('earlier', { day: 'numeric', month: 'long', year: 'numeric' }, at)\n },\n exact(value) {\n const at = toDate(value)\n\n return at === null\n ? ''\n : format('exact', { dateStyle: 'long', timeStyle: 'medium', hourCycle: 'h23' }, at)\n },\n iso(value) {\n const at = toDate(value)\n\n return at === null ? '' : at.toISOString()\n },\n }\n}\n\n/**\n * The formatter, inside a panel or outside one.\n *\n * Outside a panel — a screen mounted by hand, a story, a test — there is no dictionary to read,\n * so one is made once with this package's own English in it. A component that may be used\n * either way should not have to know which it is.\n */\nexport function useDates(): Dates {\n const i18n = inject(i18nKey, null)\n\n return createDates(i18n ?? standalone())\n}\n\nlet loose: I18n | null = null\n\nfunction standalone(): I18n {\n if (loose === null) {\n loose = createI18n()\n loose.defaults('webx-admin', adminMessages)\n }\n\n return loose\n}\n\nfunction toDate(value: DateLike): Date | null {\n if (value === null || value === undefined || value === '') {\n return null\n }\n\n const at = value instanceof Date ? value : new Date(value)\n\n // An unparseable string is a broken date, and `Invalid Date` on a screen says less than the\n // word the empty column already has.\n return Number.isNaN(at.getTime()) ? null : at\n}\n\n/**\n * Whole calendar days between two moments, in the reader's own zone.\n *\n * Built from the date parts rather than from a difference in milliseconds: an hour lost to\n * daylight saving makes \"24 hours ago\" and \"yesterday\" different questions, and it is the\n * second one a reader is asking.\n */\nfunction daysApart(at: Date, now: Date): number {\n const day = (d: Date) => Date.UTC(d.getFullYear(), d.getMonth(), d.getDate())\n\n return Math.round((day(at) - day(now)) / 86_400_000)\n}\n","import { HttpError } from './http'\nimport { useTranslate, type Translate } from './i18n'\n\n/**\n * What to say out loud when a request came back a failure.\n *\n * The panel used to print whatever the server put in `message`, which is fine right up until\n * the server has not written it. `No query results for model [WebxUi\\Pages\\Models\\Page] 8` is\n * Laravel talking to a developer — a namespace, in English, in a panel translated into ten\n * languages — and it reached an editor who had only clicked a row in the bin (§13.3).\n *\n * So the rule is which status the panel trusts to speak for itself, not which words look\n * technical. A 422 is ours: every refusal that reaches one is written by a module to be read,\n * through `trans()`, and the validator's lines are translated by Laravel. Everything else —\n * 404, 403, 500, a server that never answered — carries text nobody wrote for this reader, and\n * the panel says it in its own words instead.\n */\nexport function errorText(error: unknown, t: Translate, fallback?: string): string {\n const status = error instanceof HttpError ? error.status : statusOf(error)\n\n // The one the panel wrote itself. `fallback` under it for the rare 422 with nothing in it —\n // a refusal with only field errors, where the form is already showing them.\n if (status === 422) {\n return bodyMessage(error) ?? fallback ?? t('errors.unknown')\n }\n\n if (status === 401) return t('errors.signed-out')\n if (status === 403) return t('errors.forbidden')\n if (status === 404) return t('errors.gone')\n if (status === 409) return t('errors.conflict')\n\n if (status === 429) {\n const seconds = error instanceof HttpError ? error.retryAfter : null\n\n return seconds === null ? t('errors.throttled') : t('errors.throttled-in', { seconds })\n }\n\n if (status !== null && status >= 500) return t('errors.server')\n\n // No status at all: the request never reached a server, or never came back from one.\n if (status === null) return t('errors.offline')\n\n return fallback ?? t('errors.unknown')\n}\n\n/**\n * The same thing for a component, bound to the panel's own dictionary.\n *\n * A module calls this rather than its own `t`, because these lines belong to the panel: every\n * section fails in the same handful of ways, and translating \"you are not allowed to do that\"\n * once per module is how nine of them end up saying it differently.\n */\nexport function useErrorText(): (error: unknown, fallback?: string) => string {\n const t = useTranslate('webx-admin')\n\n return (error, fallback) => errorText(error, t, fallback)\n}\n\nfunction statusOf(error: unknown): number | null {\n const status = (error as { status?: unknown } | null)?.status\n\n return typeof status === 'number' ? status : null\n}\n\nfunction bodyMessage(error: unknown): string | null {\n const body = (error as { body?: { message?: unknown } } | null)?.body\n const message = body?.message\n\n return typeof message === 'string' && message !== '' ? message : null\n}\n","<script setup lang=\"ts\">\nimport { ref, watch } from 'vue'\nimport { WxAlert, WxSkeleton } from '@webx-ui/core'\nimport { WxScreenRenderer, type ScreenModel, type ScreenNode } from '@webx-ui/schema'\nimport { useAdmin } from './admin'\nimport { useTranslate } from './i18n'\n\n/**\n * A screen of the panel, by name.\n *\n * Fetches the tree the server hands out — patched, permission-filtered, translated — lays the\n * project's own patch over it, and draws it with the renderer against the panel's registry,\n * dictionary and permissions. It does not load values and does not save: that is the page's\n * business, the way it is for any form.\n */\nconst props = withDefaults(\n defineProps<{\n /** `<module>.<screen>`, e.g. `settings.index`. */\n name: string\n /** Server validation errors by field name, shown under the fields. */\n errors?: Record<string, string[]>\n disabled?: boolean\n labelPosition?: 'top' | 'left'\n labelWidth?: string\n size?: 'sm' | 'md' | 'lg'\n }>(),\n {\n errors: undefined,\n disabled: false,\n labelPosition: undefined,\n labelWidth: undefined,\n size: undefined,\n },\n)\n\nconst emit = defineEmits<{\n /** The tree arrived; a page that wants to know what it draws reads it here. */\n loaded: [root: ScreenNode[]]\n}>()\n\nconst model = defineModel<ScreenModel>({ default: () => ({}) })\n\nconst admin = useAdmin()\nconst t = useTranslate('webx-admin')\n\nconst root = ref<ScreenNode[] | null>(null)\nconst failed = ref<string | null>(null)\n\nasync function load(): Promise<void> {\n root.value = null\n failed.value = null\n\n try {\n const tree = await admin.loadScreen(props.name)\n root.value = tree\n emit('loaded', tree)\n } catch (error) {\n failed.value = error instanceof Error ? error.message : String(error)\n }\n}\n\n// A new language means a new tree: the labels inside it were translated on the server.\nwatch([() => props.name, () => admin.i18n.state.locale], () => void load(), { immediate: true })\n\ndefineExpose({ reload: load })\n</script>\n\n<template>\n <div class=\"wx-screen-host\">\n <wx-alert v-if=\"failed\" type=\"danger\" :title=\"t('shell.error-title')\" :description=\"failed\" />\n <wx-skeleton v-else-if=\"root === null\" :rows=\"4\" />\n <wx-screen-renderer\n v-else\n v-model=\"model\"\n :root=\"root\"\n :patch=\"admin.screenPatch(name)\"\n :types=\"admin.types\"\n :errors=\"errors\"\n :translate=\"admin.i18n.t\"\n :can=\"admin.can\"\n :disabled=\"disabled\"\n :label-position=\"labelPosition\"\n :label-width=\"labelWidth\"\n :size=\"size\"\n />\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { WxActions, WxDropdownItem } from '@webx-ui/core'\nimport type { ActionSize } from '@webx-ui/core'\nimport type { RowAction } from './types'\n\n/**\n * What one record offers, in every list of the panel: a `···` at the end of the row.\n *\n * Always a menu, even for a single action. The panel used to have three answers to this —\n * a menu on `Pages`, a bare red bin on `SEO`, a toolbar over the selection in the library —\n * and the cost of that is not the extra click, it is that nobody knows where to look. A\n * menu also has room for the word: an unlabelled icon says what it does only to whoever has\n * already learned it (§20).\n *\n * Two rules it keeps for every list rather than leaving to each one. The destructive action\n * goes last, behind a rule, in red — it is never the thing the reader meant to hit. And what\n * somebody may not do is left out, not shown greyed: a menu is a list of what is possible,\n * and a row of dead entries teaches nothing.\n */\nconst props = withDefaults(\n defineProps<{\n /** What may be done, in the order it should read. */\n actions: RowAction[]\n /** Accessible name of the menu — the record it belongs to. */\n label?: string\n size?: ActionSize\n }>(),\n { label: undefined, size: 'sm' },\n)\n\n/** Destructive last, whatever order the caller wrote them in. */\nconst ordered = computed(() => [\n ...props.actions.filter((action) => !action.danger),\n ...props.actions.filter((action) => action.danger),\n])\n\n/** The first destructive item is the one with the rule above it. */\nconst divided = computed(() => {\n const first = ordered.value.find((action) => action.danger)\n\n return first && ordered.value[0] !== first ? first.key : null\n})\n</script>\n\n<template>\n <!--\n `.stop`: the row itself opens the record, and deleting one is not opening it.\n `collapse=\"always\"` is what makes this a menu at every width — the row of icons the\n actions would otherwise draw is never built.\n -->\n <wx-actions\n v-if=\"ordered.length > 0\"\n collapse=\"always\"\n align=\"end\"\n :size=\"size\"\n :aria-label=\"label\"\n @click.stop\n >\n <template #collapsed>\n <template v-for=\"action in ordered\" :key=\"action.key\">\n <hr v-if=\"action.key === divided\" class=\"wx-dropdown__divider\" />\n\n <wx-dropdown-item\n :icon=\"action.icon\"\n :tone=\"action.danger ? 'danger' : 'default'\"\n :href=\"action.href\"\n :target=\"action.href ? (action.target ?? '_blank') : undefined\"\n :disabled=\"action.disabled\"\n @click=\"action.run?.()\"\n >\n {{ action.label }}\n </wx-dropdown-item>\n </template>\n </template>\n </wx-actions>\n</template>\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { WxAction } from '@webx-ui/core'\nimport type { ActionSize } from '@webx-ui/core'\nimport { RouterLink, type RouteLocationRaw } from 'vue-router'\nimport { useTranslate } from './i18n'\n\n/**\n * The way out of an editor, said plainly.\n *\n * A trail of breadcrumbs is where the reader *is*; it is not a way out, and on a phone it is\n * four words in the smallest type on the screen. Every screen that opens one record — a page,\n * a block, a rule — ends up needing the same control, so it is one control rather than each\n * editor's own arrow.\n *\n * An arrow and nothing else: the label is the tooltip, because the name of the section it\n * returns to is already written next to it in the trail, and twice is noise.\n */\nconst props = withDefaults(\n defineProps<{\n /** Where it leads — the section's list, normally. */\n to: RouteLocationRaw\n /** What the tooltip says. Defaults to the panel's own word for it. */\n label?: string\n size?: ActionSize\n }>(),\n { label: undefined, size: 'sm' },\n)\n\nconst t = useTranslate('webx-admin')\n\nconst title = computed(() => props.label ?? t('editor.back'))\n</script>\n\n<template>\n <!--\n No styles of its own. The class is for the head that holds it to place it with; a rule\n written here would need the scope attribute, and what carries the class is another\n component's element, which does not have one.\n -->\n <wx-action\n icon=\"arrow-left\"\n class=\"wx-back-button\"\n :as=\"RouterLink\"\n :to=\"to\"\n :title=\"title\"\n :size=\"size\"\n tooltip-side=\"bottom\"\n />\n</template>\n","<script setup lang=\"ts\">\nimport { computed, nextTick, ref, watch } from 'vue'\nimport { WxAction, WxButton, WxInput, WxPopover } from '@webx-ui/core'\nimport type { ActionSize } from '@webx-ui/core'\nimport { useTranslate } from './i18n'\n\n/**\n * Renaming, made an act rather than a side effect.\n *\n * A heading that is quietly an `<input>` reads as a heading: nothing says it can be typed in,\n * and the reader finds out by accident — or never. Worse, it is bound straight to the record,\n * so a stray keystroke on what looks like a title is a rename nobody asked for.\n *\n * So the name is text, with a pencil beside it. The pencil opens a small form: one field, one\n * button, and nothing changes until that button is pressed. Escape and a click outside leave\n * the name as it was.\n */\nconst props = withDefaults(\n defineProps<{\n /** What is being renamed. Only written back when the form is submitted. */\n name?: string\n /** Heading of the form, and the pencil's tooltip. Defaults to the panel's own word. */\n label?: string\n placeholder?: string\n disabled?: boolean\n size?: ActionSize\n }>(),\n { name: '', label: undefined, placeholder: undefined, disabled: false, size: 'sm' },\n)\n\nconst emit = defineEmits<{ rename: [name: string] }>()\n\nconst t = useTranslate('webx-admin')\n\nconst open = ref(false)\nconst draft = ref(props.name)\nconst field = ref<InstanceType<typeof WxInput> | null>(null)\n\nconst title = computed(() => props.label ?? t('editor.rename'))\n\n/*\n * Seeded when the form opens rather than whenever the name changes: a rename arriving from\n * somewhere else while somebody is typing here would otherwise overwrite what they typed.\n */\nwatch(open, async (isOpen) => {\n if (!isOpen) return\n\n draft.value = props.name\n\n // The field does not exist until the panel is on screen, so this waits for it. Selected\n // and not merely focused: renaming is usually replacing, not appending.\n await nextTick()\n field.value?.focus()\n field.value?.select()\n})\n\nfunction submit(): void {\n const next = draft.value.trim()\n\n // An empty name is not a rename, it is a record with nothing to call it by.\n if (next === '' || next === props.name) {\n open.value = false\n\n return\n }\n\n emit('rename', next)\n open.value = false\n}\n</script>\n\n<template>\n <wx-popover v-model:open=\"open\" :title=\"title\" :width=\"320\" side=\"bottom\" align=\"start\" teleport>\n <template #trigger>\n <wx-action type=\"edit\" :title=\"title\" :size=\"size\" :disabled=\"disabled\" />\n </template>\n\n <!-- A form, so that Enter does what the button does. -->\n <form class=\"wx-rename\" @submit.prevent=\"submit\">\n <wx-input\n ref=\"field\"\n v-model=\"draft\"\n :aria-label=\"title\"\n :placeholder=\"placeholder\"\n @keydown.esc=\"open = false\"\n />\n\n <div class=\"wx-rename__footer\">\n <wx-button type=\"primary\" size=\"sm\" native-type=\"submit\">{{ t('editor.save') }}</wx-button>\n </div>\n </form>\n </wx-popover>\n</template>\n\n<style scoped>\n.wx-rename {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-12);\n}\n\n.wx-rename__footer {\n display: flex;\n justify-content: flex-end;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\nimport { WxAction, WxDialog } from '@webx-ui/core'\nimport type { ActionSize } from '@webx-ui/core'\nimport { renderMarkdown } from './markdown'\nimport { useTranslate } from './i18n'\n\n/**\n * A `?` beside something that needs explaining, and the page of explanation behind it.\n *\n * The text is Markdown and comes from wherever the caller keeps it — normally a `help` group\n * in a module's `lang` files, which means it is translated in the same place as everything\n * else and is already on the client with the rest of the dictionary. No request, no second\n * store, and a language nobody has translated falls back to the site's own the way every\n * other line does.\n *\n * Keeping it as Markdown rather than as a screen of components is what lets the same words\n * serve an agent: a module can hand the identical text to MCP as `text/markdown`, and then\n * there is one explanation of a thing rather than two that drift.\n */\nconst props = withDefaults(\n defineProps<{\n /** The page, in Markdown. */\n body: string\n /** Heading of the dialog. Defaults to the panel's own word for it. */\n title?: string\n /** Tooltip of the button. Defaults to the heading. */\n label?: string\n size?: ActionSize\n width?: number | string\n }>(),\n { title: undefined, label: undefined, size: 'sm', width: 720 },\n)\n\nconst t = useTranslate('webx-admin')\n\nconst open = ref(false)\n\nconst heading = computed(() => props.title ?? t('help.title'))\nconst html = computed(() => renderMarkdown(props.body))\n</script>\n\n<template>\n <wx-action\n type=\"details\"\n icon=\"question\"\n tone=\"neutral\"\n :size=\"size\"\n :title=\"label ?? heading\"\n @click=\"open = true\"\n />\n\n <wx-dialog v-model:open=\"open\" :title=\"heading\" :width=\"width\">\n <!--\n `v-html`, and the renderer is why it is safe: it escapes every character of the source\n before it emits a tag, so what lands here contains only the tags it built.\n -->\n <!-- eslint-disable-next-line vue/no-v-html -->\n <div class=\"wx-help\" v-html=\"html\" />\n </wx-dialog>\n</template>\n\n<style scoped>\n/*\n * A page of prose rather than a form: room between the paragraphs, and code that is readable\n * at the size the rest of the panel is set in.\n */\n.wx-help {\n color: var(--wx-text-default);\n font-size: var(--wx-font-size-md);\n line-height: var(--wx-font-line-height-normal);\n}\n\n.wx-help :deep(h2),\n.wx-help :deep(h3),\n.wx-help :deep(h4) {\n margin-block: var(--wx-space-18) var(--wx-space-8);\n font-weight: var(--wx-font-weight-semibold);\n}\n\n.wx-help :deep(h2) {\n font-size: var(--wx-font-size-lg);\n}\n\n.wx-help :deep(h3),\n.wx-help :deep(h4) {\n font-size: var(--wx-font-size-md);\n}\n\n.wx-help :deep(:is(h2, h3, h4):first-child) {\n margin-block-start: 0;\n}\n\n.wx-help :deep(p),\n.wx-help :deep(ul),\n.wx-help :deep(ol) {\n margin-block: 0 var(--wx-space-12);\n}\n\n.wx-help :deep(ul),\n.wx-help :deep(ol) {\n padding-inline-start: var(--wx-space-24);\n}\n\n.wx-help :deep(li) {\n margin-block-end: var(--wx-space-4);\n}\n\n.wx-help :deep(code) {\n padding: 0 var(--wx-space-4);\n border-radius: var(--wx-radius-xs);\n background: var(--wx-bg-subtle);\n font-family: var(--wx-font-family-mono);\n font-size: var(--wx-font-size-sm);\n}\n\n.wx-help :deep(pre) {\n margin-block: 0 var(--wx-space-12);\n padding: var(--wx-space-12);\n border-radius: var(--wx-radius-sm);\n background: var(--wx-bg-subtle);\n overflow: auto;\n}\n\n.wx-help :deep(pre code) {\n padding: 0;\n background: none;\n}\n\n.wx-help :deep(a) {\n color: var(--wx-color-primary);\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { WxText, WxTooltip, type TextSize, type TextTone } from '@webx-ui/core'\nimport { useDates, type DateLike } from './dates'\n\ndefineOptions({ name: 'WxDate' })\n\n/**\n * When something happened, the way the whole panel says it (§21).\n *\n * The short line is what the column holds — \"today at 08:10\" is read at a glance where\n * \"17.09.2026, 08:10\" is read twice. The second down to the second stays in the tip, because\n * the question \"when exactly\" is asked rarely and answered badly by a column.\n *\n * Its own tip rather than `title`: the native one waits a second, cannot be styled, and is\n * invisible to anybody who is not holding a mouse (§16).\n */\nconst props = withDefaults(\n defineProps<{\n /** What the server sent. `null` is a date that never happened, not a missing one. */\n value: DateLike\n size?: TextSize\n tone?: TextTone\n }>(),\n {\n // A date is context beside whatever the row is actually about, never the thing itself.\n size: 'sm',\n tone: 'muted',\n },\n)\n\nconst dates = useDates()\n\nconst text = computed(() => dates.short(props.value))\nconst exact = computed(() => dates.exact(props.value))\nconst iso = computed(() => dates.iso(props.value))\n</script>\n\n<template>\n <wx-tooltip v-if=\"exact\" :content=\"exact\">\n <wx-text as=\"time\" :datetime=\"iso\" :size=\"size\" :tone=\"tone\">{{ text }}</wx-text>\n </wx-tooltip>\n <wx-text v-else :size=\"size\" :tone=\"tone\">{{ text }}</wx-text>\n</template>\n"],"names":["view","_useModel","__props","_createElementBlock","_normalizeClass","$slots","_openBlock","_hoisted_2","_createBlock","_unref","WxHeading","_hoisted_3","_renderSlot","_ctx","WxTabs","$event","WxCard","_Fragment","adminTypes","ListScreen","adminKey","useAdmin","admin","inject","createAdminContext","options","state","reactive","nav","computed","manifest","entries","module","registered","candidate","path","groups","declared","top","byGroup","entry","group","list","types","screens","loadScreen","name","key","pending","body","error","loadSession","reload","setLocale","isUnauthenticated","refreshManifest","code","user","loader","permission","provideAdmin","app","i18nKey","useI18n","i18n","useTranslate","namespace","createI18n","provideI18n","fallback","builtIn","dictionary","lookup","source","node","segment","translate","params","explicitNamespace","rest","line","fill","messages","next","locale","nextFallback","filled","value","emit","__emit","t","router","useRouter","route","useRoute","current","id","_component_wx_menu","_renderList","_component_wx_menu_item","_component_wx_submenu","shellEl","ref","width","layout","collapsed","showAside","drawerOpen","toggle","close","useResponsiveShell","title","logo","mark","size","shellLayoutFor","_createVNode","WxToaster","_hoisted_1","_component_router_view","_component_wx_loading","_component_wx_result","_component_wx_button","_cache","_component_wx_container","_component_wx_header","_component_wx_action","_component_wx_text","_component_wx_aside","_createElementVNode","_hoisted_7","_component_wx_main","_component_wx_drawer","_hoisted_8","HttpError","message","status","errors","retryAfter","UNSAFE","createHttp","baseUrl","csrfUrl","doFetch","onUnauthenticated","standingHeaders","csrfFetched","ensureCsrfCookie","force","readCookie","request","method","retried","unsafe","headers","token","response","url","toError","payload","header","query","full","search","serialised","part","adminMessages","STORED_LOCALE","createAdmin","manifestUrl","readManifestUrl","apiPath","basePath","modules","routes","createRouter","createWebHistory","preferredLocale","http","context","loadDictionary","rememberLocale","markDocumentLanguage","createApp","rootComponent","WebxUI","editing","localesKey","loadPanelLocales","plugin","slots","props","h","AdminNav","AdminShell","remembered","read","locales","renderMarkdown","lines","out","index","escape","heading","level","inline","isItem","ordered","items","tag","item","paragraph","HOLE","text","spans","withHoles","_","at","createDates","formatters","format","shape","formatter","time","toDate","now","days","daysApart","useDates","standalone","loose","day","d","errorText","statusOf","bodyMessage","seconds","useErrorText","model","root","failed","load","tree","watch","__expose","WxAlert","WxSkeleton","WxScreenRenderer","action","divided","first","WxActions","WxDropdownItem","_createTextVNode","_toDisplayString","WxAction","RouterLink","open","draft","field","isOpen","nextTick","submit","WxPopover","WxInput","WxButton","html","WxDialog","dates","exact","iso","WxTooltip","WxText"],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * The little of Markdown a page of help needs, turned into HTML.
3
+ *
4
+ * Not a Markdown library. This exists so that one piece of prose can serve two readers — the
5
+ * panel shows it in a dialog, an agent reads the same text over MCP as `text/markdown` — and
6
+ * so it has to understand exactly what such a page is written with: headings, paragraphs,
7
+ * lists, fenced code, and inline code, links and emphasis inside a line.
8
+ *
9
+ * Everything is escaped before any tag is emitted, so the output contains only the tags built
10
+ * here. That is what makes it safe to hand to `v-html`: the text comes from the server's
11
+ * dictionary, which is a site's own `lang` files, and a page of help is not a place where
12
+ * anybody should have to think about whether it is.
13
+ */
14
+ export declare function renderMarkdown(source: string): string;