@webx-ui/module-admin 0.26.1 → 0.28.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/i18n.ts","../src/categories/api.ts","../src/categories/CategoriesField.vue","../src/categories/SourceSelect.vue","../src/http.ts","../src/relations/api.ts","../src/relations/RelationsField.vue","../src/collections/api.ts","../src/collections/CollectionField.vue","../src/links.ts","../src/LinkPicker.vue","../src/LinkField.vue","../src/messages.ts","../src/dates.ts","../src/DateText.vue","../src/errors.ts","../src/history.ts","../src/HistoryFeed.vue","../src/BackButton.vue","../src/RowMenu.vue","../src/ScreenHead.vue","../src/ListScreen.vue","../src/richTextAddresses.ts","../src/RichTextField.vue","../src/address.ts","../src/SlugField.vue","../src/screenTypes.ts","../src/admin.ts","../src/AdminLanding.vue","../src/AdminNav.vue","../src/AdminNotFound.vue","../src/AdminShell.vue","../src/siteBar.ts","../src/SiteBarScreen.vue","../src/theme.ts","../src/createAdmin.ts","../src/markdown.ts","../src/plural.ts","../src/keys.ts","../src/merge.ts","../src/editingLabels.ts","../src/editing.ts","../src/notes.ts","../src/shortcodes.ts","../src/uploads.ts","../src/categories/editor.ts","../src/categories/ordering.ts","../src/AddressNote.vue","../src/categories/words.ts","../src/categories/CategoryCreateDialog.vue","../src/categories/CategoriesPage.vue","../src/SaveState.vue","../src/Screen.vue","../src/categories/CategoryEditorPage.vue","../src/categories/routes.ts","../src/rowMenu.ts","../src/FilterChips.vue","../src/RenameButton.vue","../src/HelpButton.vue","../src/NotesFeed.vue","../src/EditingAlerts.vue","../src/DraftsList.vue","../src/BackupNote.vue"],"sourcesContent":["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","import type { ScreenModel } from '@webx-ui/schema'\nimport type { AdminContext } from '../admin'\nimport type { CategoriesPayload, CategoryDetail, CategoryRow } from './types'\n\n/**\n * The categories of one module, behind the routes `CategoryRoutes::register()` gives it.\n *\n * The path is the module's (`blog/rubrics`), the answers are everybody's: one controller serves\n * every module, so one client does too.\n */\nexport interface CategoriesApi {\n /** Every category, in the order of the site's menu. `q` narrows by name or address. */\n list(query?: { q?: string; visible?: boolean }): Promise<CategoriesPayload>\n /** A name, and an address made of it unless one is given. */\n create(input: { title: string; slug?: string }): Promise<CategoryRow>\n get(id: number): Promise<CategoryDetail>\n /** The values of the screen. A refused field comes back as a 422 under its name. */\n save(id: number, values: ScreenModel): Promise<CategoryDetail>\n /** Into the bin — a 422 naming the number of items while it still holds any. */\n remove(id: number): Promise<void>\n restore(id: number): Promise<CategoryRow>\n /** The whole order, as it is on screen. */\n reorder(ids: number[]): Promise<void>\n}\n\nexport function createCategoriesApi(admin: AdminContext, path: string): CategoriesApi {\n const base = `${admin.apiPath}/${path.replace(/^\\/+|\\/+$/g, '')}`\n const data = <T>(body: { data: T }): T => body.data\n\n return {\n list: (query = {}) => {\n const search = new URLSearchParams()\n\n if (query.q) search.set('q', query.q)\n if (query.visible) search.set('visible', '1')\n\n return admin.http.get<CategoriesPayload>(search.size > 0 ? `${base}?${search}` : base)\n },\n create: (input) => admin.http.post<{ data: CategoryRow }>(base, input).then(data),\n get: (id) => admin.http.get<{ data: CategoryDetail }>(`${base}/${id}`).then(data),\n save: (id, values) =>\n admin.http.put<{ data: CategoryDetail }>(`${base}/${id}`, { values }).then(data),\n remove: (id) => admin.http.delete<void>(`${base}/${id}`).then(() => undefined),\n restore: (id) => admin.http.post<{ data: CategoryRow }>(`${base}/${id}/restore`, {}).then(data),\n reorder: (ids) => admin.http.post<void>(`${base}/reorder`, { ids }).then(() => undefined),\n }\n}\n\n/**\n * The order of a module's records, written the way it is dragged (§2.5 of the services spec):\n * without `category` the whole list, with it the order inside that one category.\n *\n * `path` is the module's list of records (`services/items`); the server half is\n * `CategoryRoutes::items()`. A module that orders by date — the blog — never calls it.\n */\nexport function reorderItems(\n admin: AdminContext,\n path: string,\n ids: number[],\n category: number | null = null,\n): Promise<void> {\n const base = `${admin.apiPath}/${path.replace(/^\\/+|\\/+$/g, '')}`\n\n return admin.http\n .post<void>(`${base}/reorder`, category === null ? { ids } : { ids, category })\n .then(() => undefined)\n}\n","<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\nimport {\n WxAction,\n WxBadge,\n WxSelect,\n WxSortableList,\n type SelectModelValue,\n type SelectOption,\n} from '@webx-ui/core'\nimport { useAdmin } from '../admin'\nimport { useTranslate } from '../i18n'\nimport { createCategoriesApi } from './api'\n\n/**\n * The categories a record is in, in the order somebody dragged them into (`wx-categories`).\n *\n * The order is the whole control. The first category is the main one — it goes into the\n * breadcrumbs — so a separate switch for \"which is the main one\" would be a second control\n * saying what this one already says. A list and not a multi-select for the same reason: a bag of\n * checkboxes has no first. A module without a main category says `main: false`, and the order\n * is then only the order.\n *\n * Which categories are on offer is the module's list, asked from `source` — the path its\n * categories answer at (`blog/rubrics`). The server's type of the same name checks the ids\n * against the model registered for that path, so both halves read the one string.\n *\n * The words are props, with the panel's \"category\" as the default: a module names its own in the\n * screen description (`trans::webx-blog::article.rubric-add`).\n */\ndefineOptions({ name: 'WxCategoriesField' })\n\nconst props = withDefaults(\n defineProps<{\n /** The API the categories answer at, under the panel's: `blog/rubrics`. */\n source: string\n /** Whether the first one is the main one, and wears the badge that says so. */\n main?: boolean\n disabled?: boolean\n mainText?: string\n addText?: string\n removeText?: string\n emptyText?: string\n noneLeftText?: string\n }>(),\n {\n main: true,\n disabled: false,\n mainText: undefined,\n addText: undefined,\n removeText: undefined,\n emptyText: undefined,\n noneLeftText: undefined,\n },\n)\n\nconst value = defineModel<number[] | null>({ default: () => [] })\n\nconst admin = useAdmin()\nconst t = useTranslate('webx-admin')\n\ninterface Choice {\n id: number\n name: string\n}\n\nconst catalogue = ref<Choice[]>([])\n\nwatch(\n () => props.source,\n async (source) => {\n try {\n const payload = await createCategoriesApi(admin, source).list()\n catalogue.value = payload.data.map((row) => ({ id: row.id, name: row.name }))\n } catch {\n // The chosen ones still draw — by number — and the box offers nothing: a list that did not\n // arrive is not a reason to lose the categories already on the record.\n catalogue.value = []\n }\n },\n { immediate: true },\n)\n\nconst chosen = computed<number[]>(() => (Array.isArray(value.value) ? value.value : []))\n\nconst names = computed(() => new Map(catalogue.value.map((one) => [one.id, one.name])))\n\n/**\n * The chosen categories as rows. Writable, because that is how `WxSortableList` hands a reorder\n * back — the ids are the value, and the rows are only what they are drawn as.\n */\nconst rows = computed<Choice[]>({\n get: () => chosen.value.map((id) => ({ id, name: names.value.get(id) ?? `#${id}` })),\n set: (next) => {\n value.value = next.map((row) => row.id)\n },\n})\n\nconst available = computed<SelectOption[]>(() =>\n catalogue.value\n .filter((one) => !chosen.value.includes(one.id))\n .map((one) => ({ label: one.name, value: one.id })),\n)\n\nfunction add(picked: SelectModelValue): void {\n if (typeof picked !== 'number' || chosen.value.includes(picked)) return\n\n value.value = [...chosen.value, picked]\n}\n\nfunction remove(id: number): void {\n value.value = chosen.value.filter((one) => one !== id)\n}\n</script>\n\n<template>\n <div class=\"wx-categories-field\">\n <wx-sortable-list\n v-model=\"rows\"\n plain\n size=\"sm\"\n item-key=\"id\"\n item-label=\"name\"\n :disabled=\"props.disabled\"\n :empty-text=\"props.emptyText ?? t('categories.field-empty')\"\n >\n <template #default=\"{ item, index }\">\n <span class=\"wx-categories-field__name\">{{ (item as Choice).name }}</span>\n <!-- Said on the row rather than in a legend under the list: the rule is \"the first\n one\", and the place to say so is the first one. -->\n <wx-badge v-if=\"props.main && index === 0\" type=\"primary\">\n {{ props.mainText ?? t('categories.field-main') }}\n </wx-badge>\n </template>\n\n <template #actions=\"{ item }\">\n <wx-action\n type=\"remove\"\n size=\"sm\"\n :disabled=\"props.disabled\"\n :title=\"props.removeText ?? t('categories.field-remove')\"\n @click=\"remove((item as Choice).id)\"\n />\n </template>\n </wx-sortable-list>\n\n <wx-select\n :model-value=\"null\"\n :options=\"available\"\n :placeholder=\"props.addText ?? t('categories.field-add')\"\n :disabled=\"props.disabled || available.length === 0\"\n :empty-text=\"props.noneLeftText ?? t('categories.field-none-left')\"\n filterable\n teleport\n :aria-label=\"props.addText ?? t('categories.field-add')\"\n @update:model-value=\"add\"\n />\n </div>\n</template>\n\n<style scoped>\n.wx-categories-field {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-8);\n}\n\n/*\n * The cell a row's content goes into is a block, so the name and the \"main\" badge stood against\n * each other with nothing between them. The slot makes a line of its own contents; through\n * `:deep()`, because the cell belongs to `WxSortableList`.\n */\n.wx-categories-field :deep(.wx-sortable-list__content) {\n display: flex;\n align-items: center;\n gap: var(--wx-space-8);\n min-width: 0;\n}\n\n/* Shrinks rather than grows: a name that pushed the badge to the far end of the row would say\n \"main\" about the distance instead of about the category. */\n.wx-categories-field__name {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\nimport { WxSelect, type SelectOption } from '@webx-ui/core'\nimport { useAdmin } from '../admin'\nimport { createCategoriesApi } from './api'\n\n/**\n * `wx-select` as the panel draws it: the core's select, whose options may be a module's list\n * rather than written into the screen.\n *\n * A screen names the list by `source` — the path it answers at under the panel's API\n * (`catalog/brands`), the same string `wx-categories` reads — and the options are its records,\n * by name. A reference book is edited while the screen stays the same, so its options cannot be\n * part of the JSON. Without `source` this is the core's select with the options it was given.\n *\n * Everything else — `filterable`, `clearable`, the placeholder that says what empty means — goes\n * through to the core's select untouched.\n */\ndefineOptions({ name: 'WxSourceSelect', inheritAttrs: false })\n\nconst props = withDefaults(\n defineProps<{\n /** The API a module's list answers at, under the panel's: `catalog/stock`. */\n source?: string\n options?: SelectOption[]\n }>(),\n { source: undefined, options: () => [] },\n)\n\nconst admin = useAdmin()\nconst loaded = ref<SelectOption[]>([])\n\nwatch(\n () => props.source,\n async (source) => {\n if (!source) {\n loaded.value = []\n\n return\n }\n\n try {\n const payload = await createCategoriesApi(admin, source).list()\n loaded.value = payload.data.map((row) => ({ label: row.name, value: row.id }))\n } catch {\n // The value stays and shows as it is; a list that did not arrive is not a reason to lose it.\n loaded.value = []\n }\n },\n { immediate: true },\n)\n\nconst offered = computed<SelectOption[]>(() => (props.source ? loaded.value : props.options))\n</script>\n\n<template>\n <wx-select v-bind=\"$attrs\" :options=\"offered\" />\n</template>\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 /**\n * Where to fetch the CSRF cookie from before an unsafe request. The default is the panel's\n * own route; a panel served under another `api_path` passes its own, as `createAdmin` does.\n */\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 SendOptions extends RequestOptions {\n /** Sent as it is — bytes, a `Blob`, a string — with no `Content-Type` added for it. */\n body?: BodyInit | null\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 * A request with the panel's conventions (the CSRF cookie, the standing headers, 419 and 401)\n * and nothing else: the body goes as it is and the response comes back whatever its status.\n * For what JSON does not fit — the pieces of a chunked upload, a `HEAD` asked for a header.\n * Optional so that a hand-made client in a test or a project does not have to grow one.\n */\n send?(method: string, path: string, options?: SendOptions): Promise<Response>\n}\n\nconst UNSAFE = new Set(['POST', 'PUT', 'PATCH', 'DELETE'])\n\nexport function createHttp(options: HttpOptions = {}): Http {\n const baseUrl = (options.baseUrl ?? '').replace(/\\/$/, '')\n // The panel's own, since `webx-ui/module-auth` dropped Sanctum: a route in the `web` group\n // that does nothing but let the framework set the cookie.\n const csrfUrl = options.csrfUrl ?? '/api/cms/auth/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 send(\n method: string,\n path: string,\n options: SendOptions & { json?: boolean } = {},\n retried = false,\n ): Promise<Response> {\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 (options.json === true) {\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: options.body ?? undefined,\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 send(method, path, options, true)\n }\n\n if (response.status === 401) {\n onUnauthenticated?.()\n }\n\n return response\n }\n\n async function request<T>(\n method: string,\n path: string,\n body?: unknown,\n options: RequestOptions = {},\n ): Promise<T> {\n const response = await send(method, path, {\n ...options,\n json: body !== undefined,\n body: body === undefined ? undefined : JSON.stringify(body),\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 send: (method, path, options) => send(method.toUpperCase(), path, options),\n }\n}\n\nexport async 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 { inject, provide, type InjectionKey, type Ref } from 'vue'\nimport type { AdminContext } from '../admin'\n\n/**\n * One record a relation can point at, as `GET /api/cms/relations/{target}` draws it (§3.7 of the\n * recipes spec) — already in the panel's language.\n */\nexport interface RelationCandidate {\n id: number\n title: string\n /** A second line: a category, an address. */\n subtitle: string | null\n /** A small picture's address, when the target has one. */\n thumb: string | null\n /** Whether the site shows it. A hidden one stays chosen and says so: the site skips it. */\n visible: boolean\n /** In the bin — a kind of hidden that says where to look. Only names of the chosen carry it. */\n trashed?: boolean\n}\n\nexport interface RelationsApi {\n /** Candidates for the \"Add\" box, searched by the server. */\n search(query: string): Promise<RelationCandidate[]>\n /** The chosen ones by id: a form opens with numbers, and a row needs a name. */\n named(ids: number[]): Promise<RelationCandidate[]>\n}\n\n/**\n * The target's key is a path segment, and the ids go as `ids[]` — the panel's `query` option\n * holds one value per key, so the list is written into the path by hand.\n */\nexport function createRelationsApi(admin: AdminContext, target: string): RelationsApi {\n const path = `${admin.apiPath}/relations/${encodeURIComponent(target)}`\n\n return {\n search: (query) =>\n admin.http\n .get<{ data: RelationCandidate[] }>(path, { query: { q: query } })\n .then((body) => body.data),\n named: (ids) =>\n ids.length === 0\n ? Promise.resolve([])\n : admin.http\n .get<{\n data: RelationCandidate[]\n }>(`${path}?${ids.map((id) => `ids[]=${id}`).join('&')}`)\n .then((body) => body.data),\n }\n}\n\n/** A list of ids, in order, without repeats or anything that is not one — what the server keeps. */\nexport function normaliseRelations(value: unknown): number[] {\n if (!Array.isArray(value)) return []\n\n return [...new Set(value.map(Number).filter((id) => Number.isInteger(id) && id > 0))]\n}\n\n/**\n * The record whose form the field is on — so that \"similar recipes\" does not offer the recipe\n * itself. A described screen has no way to say which record it is editing, and the editor that\n * hosts it does, so the editor provides it the way it provides the address (`address.ts`).\n */\nexport interface RelationOwner {\n /** Its key in the registry of targets: `recipe`. */\n type: string\n /** `null` for a record not saved yet, which nothing can have chosen. */\n id: Ref<number | null>\n}\n\nexport const relationOwnerKey: InjectionKey<RelationOwner> = Symbol('wx-relation-owner')\n\nexport function provideRelationOwner(owner: RelationOwner): void {\n provide(relationOwnerKey, owner)\n}\n\nexport function useRelationOwner(): RelationOwner | null {\n return inject(relationOwnerKey, null)\n}\n","<script setup lang=\"ts\">\nimport { computed, nextTick, ref, watch } from 'vue'\nimport {\n WxAction,\n WxAutocomplete,\n WxBadge,\n WxSortableList,\n type AutocompleteOption,\n} from '@webx-ui/core'\nimport { useAdmin } from '../admin'\nimport { HttpError } from '../http'\nimport { useTranslate } from '../i18n'\nimport {\n createRelationsApi,\n normaliseRelations,\n useRelationOwner,\n type RelationCandidate,\n} from './api'\n\n/**\n * The records of another section this one points at, in the order they were put in\n * (`wx-relations`): the services a recipe is for, the recipes that go with it.\n *\n * The value is ids and nothing else. What they are called, whether the site still shows them, the\n * candidates for \"Add\" — all of that is the target's to say, over `GET /api/cms/relations/{target}`\n * (§3.7 of the recipes spec): with `q` it searches, with `ids[]` it names what is already chosen.\n * The target is the schema's (`props.target`), and a target the server does not know never reaches\n * the panel — the server takes the node off the screen, so recipes without the services module\n * have no \"Services\" field at all rather than a broken one.\n *\n * A chosen record the site does not show — unpublished, in the bin — stays in the list, marked:\n * dropping it silently would change the record without anybody having touched it, and the site\n * skips it anyway. One the server does not know at all is drawn by number, for the same reason.\n *\n * An administrator who may not see the target's records gets a 403 on both questions, and the\n * field becomes a list of what is chosen and nothing more: no names to reorder by, nothing to add\n * from — and removing a record you cannot see is not a choice anybody can make well.\n *\n * The order is kept because a template reads it (\"the main service first\"); `sortable: false` is\n * for a choice whose order means nothing, as in a block's filter.\n */\ndefineOptions({ name: 'WxRelationsField' })\n\nconst props = withDefaults(\n defineProps<{\n /** The target's key in the server's registry: `service`, `recipe`. */\n target: string\n /** How many may be chosen; `null` for any number. */\n max?: number | null\n sortable?: boolean\n disabled?: boolean\n addText?: string\n emptyText?: string\n }>(),\n {\n max: null,\n sortable: true,\n disabled: false,\n addText: undefined,\n emptyText: undefined,\n },\n)\n\nconst value = defineModel<number[] | null>({ default: () => [] })\n\nconst admin = useAdmin()\nconst owner = useRelationOwner()\nconst t = useTranslate('webx-admin')\n\nconst api = computed(() => createRelationsApi(admin, props.target))\n\n/** What the server said about each id it was asked about, kept for the life of the field. */\nconst known = ref(new Map<number, RelationCandidate>())\n/** Ids the server was asked to name and did not: gone, or behind a module that is not installed. */\nconst missing = ref(new Set<number>())\nconst forbidden = ref(false)\n\nconst candidates = ref<RelationCandidate[]>([])\nconst searching = ref(false)\nconst term = ref('')\n\nconst chosen = computed(() => normaliseRelations(value.value))\n\nfunction remember(rows: RelationCandidate[]): void {\n const next = new Map(known.value)\n\n for (const row of rows) next.set(row.id, row)\n\n known.value = next\n}\n\n/**\n * Only what has no name yet is asked about: picking from the search already brought the name of\n * what was picked, and a form that asks again on every edit asks about a list it just drew.\n */\nasync function name(ids: number[]): Promise<void> {\n const unknown = ids.filter((id) => !known.value.has(id) && !missing.value.has(id))\n\n if (unknown.length === 0 || forbidden.value) return\n\n try {\n const rows = await api.value.named(unknown)\n remember(rows)\n\n const answered = new Set(rows.map((row) => row.id))\n missing.value = new Set([...missing.value, ...unknown.filter((id) => !answered.has(id))])\n } catch (error) {\n // Anything else is a request that did not come back, and the rows keep their numbers until\n // the next change asks again. A refusal is an answer, and it is the same for every question.\n if (error instanceof HttpError && error.status === 403) forbidden.value = true\n }\n}\n\nwatch(\n () => props.target,\n () => {\n known.value = new Map()\n missing.value = new Set()\n forbidden.value = false\n candidates.value = []\n void name(chosen.value)\n },\n)\n\nwatch(chosen, (ids) => void name(ids), { immediate: true })\n\ninterface Row {\n id: number\n title: string\n candidate: RelationCandidate | null\n}\n\n/** Writable, because that is how `WxSortableList` hands a reorder back. */\nconst rows = computed<Row[]>({\n get: () =>\n chosen.value.map((id) => {\n const candidate = known.value.get(id) ?? null\n\n return { id, title: candidate?.title || `#${id}`, candidate }\n }),\n set: (next) => {\n value.value = next.map((row) => row.id)\n },\n})\n\nconst full = computed(() => props.max !== null && chosen.value.length >= props.max)\n\n/** The record being edited is never its own relation — \"similar recipes\" is about the others. */\nconst self = computed(() => (owner !== null && owner.type === props.target ? owner.id.value : null))\n\nconst options = computed<AutocompleteOption[]>(() =>\n candidates.value\n .filter((one) => one.id !== self.value && !chosen.value.includes(one.id))\n .map((one) => ({\n value: one.title,\n description: [one.subtitle, one.visible ? null : t('relations.field-hidden')]\n .filter((part): part is string => typeof part === 'string' && part !== '')\n .join(' · '),\n id: one.id,\n })),\n)\n\nasync function search(query: string): Promise<void> {\n searching.value = true\n\n try {\n const found = await api.value.search(query)\n candidates.value = found\n remember(found)\n } catch (error) {\n candidates.value = []\n if (error instanceof HttpError && error.status === 403) forbidden.value = true\n } finally {\n searching.value = false\n }\n}\n\nfunction pick(option: AutocompleteOption): void {\n const id = Number(option.id)\n\n if (!Number.isInteger(id) || chosen.value.includes(id) || full.value) return\n\n value.value = [...chosen.value, id]\n\n // The box writes the picked title into itself after this handler; emptied on the next tick it\n // is ready for the next one instead of holding the name of something already in the list.\n void nextTick(() => {\n term.value = ''\n })\n}\n\nfunction remove(id: number): void {\n value.value = chosen.value.filter((one) => one !== id)\n}\n\nconst locked = computed(() => props.disabled || forbidden.value)\n\nconst placeholder = computed(() =>\n full.value\n ? t('relations.field-full', { max: String(props.max) })\n : (props.addText ?? t('relations.field-add')),\n)\n\n/** A mark for a row whose record the site does not show, or the server does not know. */\nfunction mark(row: Row): string | null {\n if (row.candidate === null) return missing.value.has(row.id) ? t('relations.field-missing') : null\n\n if (row.candidate.trashed === true) return t('relations.field-trashed')\n\n return row.candidate.visible ? null : t('relations.field-hidden')\n}\n</script>\n\n<template>\n <div class=\"wx-relations-field\" :class=\"{ 'is-unordered': !props.sortable }\">\n <wx-sortable-list\n v-model=\"rows\"\n plain\n size=\"sm\"\n item-key=\"id\"\n item-label=\"title\"\n :disabled=\"locked || !props.sortable\"\n :empty-text=\"props.emptyText ?? t('relations.field-empty')\"\n :drag-label=\"t('relations.field-drag')\"\n >\n <template #default=\"{ item }\">\n <img\n v-if=\"(item as Row).candidate?.thumb\"\n class=\"wx-relations-field__thumb\"\n :src=\"(item as Row).candidate!.thumb!\"\n alt=\"\"\n />\n <span class=\"wx-relations-field__text\">\n <span class=\"wx-relations-field__title\">{{ (item as Row).title }}</span>\n <span v-if=\"(item as Row).candidate?.subtitle\" class=\"wx-relations-field__subtitle\">\n {{ (item as Row).candidate!.subtitle }}\n </span>\n </span>\n <wx-badge v-if=\"mark(item as Row)\" type=\"warning\">{{ mark(item as Row) }}</wx-badge>\n </template>\n\n <template v-if=\"!locked\" #actions=\"{ item }\">\n <wx-action\n type=\"remove\"\n size=\"sm\"\n :title=\"t('relations.field-remove')\"\n @click=\"remove((item as Row).id)\"\n />\n </template>\n </wx-sortable-list>\n\n <p v-if=\"forbidden\" class=\"wx-relations-field__note\">\n {{ t('relations.field-forbidden') }}\n </p>\n\n <!-- A wrapper rather than a class on the box: its root is a fragment, and a scoped rule on it\n would reach nothing. -->\n <div v-else class=\"wx-relations-field__add\">\n <wx-autocomplete\n v-model=\"term\"\n :options=\"options\"\n remote\n teleport\n :loading=\"searching\"\n :loading-text=\"t('relations.field-searching')\"\n :empty-text=\"t('relations.field-nothing')\"\n :placeholder=\"placeholder\"\n :aria-label=\"placeholder\"\n :disabled=\"props.disabled || full\"\n @search=\"search\"\n @select=\"pick\"\n />\n </div>\n </div>\n</template>\n\n<style scoped>\n.wx-relations-field {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-8);\n min-width: 0;\n}\n\n/* The row's cell is `WxSortableList`'s; the picture, the name and the mark make one line of it. */\n.wx-relations-field :deep(.wx-sortable-list__content) {\n display: flex;\n align-items: center;\n gap: var(--wx-space-8);\n min-width: 0;\n}\n\n/* Not shrunk by the name beside it: in a flex row an image keeps its natural width otherwise. */\n.wx-relations-field__thumb {\n flex: none;\n width: 32px;\n height: 32px;\n min-width: 0;\n object-fit: cover;\n border-radius: var(--wx-radius-xs);\n background: var(--wx-bg-fill);\n}\n\n.wx-relations-field__text {\n display: flex;\n flex-direction: column;\n flex: 1 1 auto;\n min-width: 0;\n}\n\n.wx-relations-field__title,\n.wx-relations-field__subtitle {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.wx-relations-field__subtitle,\n.wx-relations-field__note {\n font-size: var(--wx-font-size-sm);\n color: var(--wx-text-muted);\n}\n\n.wx-relations-field__note {\n margin: 0;\n}\n\n/* A choice whose order means nothing has nothing to hold: a greyed grip would read as \"locked\". */\n.wx-relations-field.is-unordered :deep(.wx-sortable-list__grip) {\n display: none;\n}\n\n.wx-relations-field :deep(.wx-badge) {\n flex: none;\n}\n</style>\n","import type { AdminContext } from '../admin'\n\n/**\n * One section a block can show records from, as `GET /api/cms/collections` describes it\n * (`WebxUi\\Admin\\Collections\\CollectionSource` on the server).\n */\nexport interface CollectionSourceInfo {\n /** What a block schema writes in `props.source`: `faq`. */\n key: string\n /** Already in the panel's language. */\n title: string\n /** The path its categories answer at (`faq/categories`); `null` — a source without them. */\n categories: string | null\n /** Whether it can mark its records up for search engines. */\n markup: boolean\n /**\n * The targets its records can be related to (`CollectionSource::relations()`, §3.6 of the\n * recipes spec) — empty for a source without relations, which has no \"only related to\".\n */\n relations: CollectionRelationTarget[]\n}\n\n/** A target a source's records point at, named for the choice \"only related to …\". */\nexport interface CollectionRelationTarget {\n /** Its key in the server's registry of targets: `service`. */\n key: string\n /** Already in the panel's language. */\n title: string\n}\n\n/** \"Only the records related to these\": one target, and the ones of it that were chosen. */\nexport interface CollectionRelated {\n type: string\n ids: number[]\n /**\n * \"Related to the record of the page the block stands on\" — written only when on, and then\n * `ids` is always empty: the record is the page's, not a choice.\n */\n current?: true\n}\n\n/** What a `wx-collection` field keeps: the choice, never the records. */\nexport interface CollectionValue {\n /** Empty — all of them. */\n categories: number[]\n /** `null` — not narrowed by relations. */\n related: CollectionRelated | null\n /** `null` — all of them. */\n limit: number | null\n filter: boolean\n /** `null` — the default: on when no category is chosen (decision 8 of the FAQ spec). */\n markup: boolean | null\n}\n\n/** The server's ceiling for `limit` (`Selection::MAX_LIMIT`). */\nexport const COLLECTION_MAX_LIMIT = 100\n\n/**\n * The list is the same for every field on every block of a session, and a page of ten FAQ blocks\n * asking ten times would be ten answers to one question. Kept per panel rather than per module,\n * so a test's fresh panel asks afresh.\n */\nconst asked = new WeakMap<AdminContext, Promise<CollectionSourceInfo[]>>()\n\nexport function collectionSources(admin: AdminContext): Promise<CollectionSourceInfo[]> {\n let sources = asked.get(admin)\n\n if (sources === undefined) {\n sources = admin.http\n .get<{ data: CollectionSourceInfo[] }>(`${admin.apiPath}/collections`)\n .then((body) => body.data.map((one) => ({ ...one, relations: relationTargets(one) })))\n\n // A failed answer is not kept: the next field to ask gets a real one rather than the same error.\n sources.catch(() => asked.delete(admin))\n asked.set(admin, sources)\n }\n\n return sources\n}\n\n/**\n * A server from before relations says nothing about them, and a list of bare keys names nothing:\n * both come to the same shape, a key without a title standing for itself.\n */\nfunction relationTargets(source: { relations?: unknown }): CollectionRelationTarget[] {\n if (!Array.isArray(source.relations)) return []\n\n return source.relations.flatMap((one: unknown): CollectionRelationTarget[] => {\n if (typeof one === 'string') return one === '' ? [] : [{ key: one, title: one }]\n\n if (\n typeof one === 'object' &&\n one !== null &&\n typeof (one as { key?: unknown }).key === 'string'\n ) {\n const { key, title } = one as { key: string; title?: unknown }\n\n return [{ key, title: typeof title === 'string' && title !== '' ? title : key }]\n }\n\n return []\n })\n}\n\nfunction ids(value: unknown): number[] {\n return Array.isArray(value)\n ? [...new Set(value.map(Number).filter((id) => Number.isInteger(id) && id > 0))].sort(\n (a, b) => a - b,\n )\n : []\n}\n\n/**\n * A relation filter without a target or without anything chosen of it narrows nothing, and is\n * written as none at all — so \"not narrowed\" has one spelling in the database.\n */\nfunction related(value: unknown): CollectionRelated | null {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return null\n\n const {\n type,\n ids: chosen,\n current,\n } = value as { type?: unknown; ids?: unknown; current?: unknown }\n\n if (current === true && typeof type === 'string' && type !== '') {\n return { type, ids: [], current: true }\n }\n\n const picked = ids(chosen)\n\n return typeof type === 'string' && type !== '' && picked.length > 0 ? { type, ids: picked } : null\n}\n\n/** Whatever arrived, as the five keys the server writes — the same `Selection::normalise()` does. */\nexport function normaliseCollection(value: unknown): CollectionValue {\n const fields =\n typeof value === 'object' && value !== null && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : {}\n\n const limit =\n typeof fields.limit === 'number' && Number.isInteger(fields.limit) ? fields.limit : null\n\n return {\n categories: ids(fields.categories),\n related: related(fields.related),\n limit: limit !== null && limit >= 1 ? Math.min(limit, COLLECTION_MAX_LIMIT) : null,\n filter: fields.filter === true,\n markup: typeof fields.markup === 'boolean' ? fields.markup : null,\n }\n}\n\n/**\n * What `markup: null` means for this choice: on for the whole collection, off for a part of it —\n * and \"the recipes of this service\" is a part as much as a category is.\n */\nexport function defaultMarkup(value: CollectionValue): boolean {\n return value.categories.length === 0 && value.related === null\n}\n","<script setup lang=\"ts\">\nimport { computed, ref, useId, watch } from 'vue'\nimport {\n WxButton,\n WxInputNumber,\n WxSelect,\n WxSwitch,\n type SelectModelValue,\n type SelectOption,\n} from '@webx-ui/core'\nimport { useAdmin } from '../admin'\nimport { createCategoriesApi } from '../categories/api'\nimport { useTranslate } from '../i18n'\nimport RelationsField from '../relations/RelationsField.vue'\nimport {\n COLLECTION_MAX_LIMIT,\n collectionSources,\n defaultMarkup,\n normaliseCollection,\n type CollectionSourceInfo,\n type CollectionValue,\n} from './api'\n\n/**\n * What a block shows from another section (`wx-collection`): which of its categories, how many,\n * whether with a filter, whether marked up for search engines.\n *\n * The records themselves are never here. The block keeps the choice, and the site reads the\n * records when it prints the page — so a question edited in the FAQ is edited on every page that\n * shows it, and nothing here goes stale.\n *\n * Which section is the schema's (`props.source`), not the value's: a block built for questions\n * does not turn into a block of reviews by an edit of its content. What that section is — its\n * name, where its categories answer, whether it can mark up — the panel asks the server once\n * (`GET /api/cms/collections`), and a section this administrator may not place is not offered.\n *\n * The markup switch is three-valued underneath. `null` is \"as the rule says\": on for the whole\n * collection, off for a part of it, because search engines ask not to mark the same question up on\n * several pages and \"questions about payment\" stands on every service. The switch shows what the\n * rule gives until somebody touches it, and after that it is theirs — with a way back to the rule.\n */\ndefineOptions({ name: 'WxCollectionField' })\n\nconst props = withDefaults(\n defineProps<{\n /** The source's key, as the server registered it: `faq`. */\n source: string\n disabled?: boolean\n }>(),\n { disabled: false },\n)\n\nconst model = defineModel<CollectionValue | null>({ default: null })\n\nconst admin = useAdmin()\nconst t = useTranslate('webx-admin')\nconst id = useId()\n\n/** `undefined` while the answer is on its way, `null` when it came and this source is not in it. */\nconst info = ref<CollectionSourceInfo | null | undefined>(undefined)\nconst categories = ref<SelectOption[]>([])\n\nwatch(\n () => props.source,\n async (source) => {\n info.value = undefined\n categories.value = []\n\n try {\n info.value = (await collectionSources(admin)).find((one) => one.key === source) ?? null\n } catch {\n info.value = null\n }\n\n if (!info.value?.categories) return\n\n try {\n const payload = await createCategoriesApi(admin, info.value.categories).list()\n categories.value = payload.data.map((row) => ({ label: row.name, value: row.id }))\n } catch {\n // The chosen ones still stand in the value; only the names are missing, and the list can be\n // opened again later. Losing the choice because a list did not arrive would be worse.\n categories.value = []\n }\n },\n { immediate: true },\n)\n\nconst value = computed(() => normaliseCollection(model.value))\n\nfunction write(patch: Partial<CollectionValue>): void {\n model.value = normaliseCollection({ ...value.value, ...patch })\n}\n\n/**\n * A chosen category that is not in the list any more — deleted, or in the bin — is still drawn,\n * by number: dropping it silently would change the block without anybody having touched it.\n */\nconst options = computed<SelectOption[]>(() => {\n const known = new Set(categories.value.map((one) => one.value))\n const missing = value.value.categories\n .filter((one) => !known.has(one))\n .map((one) => ({ label: `#${one}`, value: one }))\n\n return [...categories.value, ...missing]\n})\n\nfunction pick(picked: SelectModelValue): void {\n write({ categories: Array.isArray(picked) ? picked.map(Number) : [] })\n}\n\nfunction limit(picked: number | null | undefined): void {\n write({ limit: picked ?? null })\n}\n\n/*\n * \"Only related to\": which target, then which of its records. The target is chosen here rather\n * than fixed by the schema because a source can be related to more than one (recipes to services\n * and to other recipes), and with only one there is nothing to choose and the box is not drawn.\n * The target a choice was started in is kept while nothing of it is chosen yet — the value has no\n * way to say \"a target and no records\", and a box that jumped back would undo the click.\n */\nconst relatedType = ref<string | null>(null)\n\nconst relationOptions = computed<SelectOption[]>(() =>\n (info.value?.relations ?? []).map((one) => ({ label: one.title, value: one.key })),\n)\n\nconst relatedTarget = computed(() => {\n const targets = info.value?.relations ?? []\n\n if (targets.length === 1) return targets[0]!\n\n const key = value.value.related?.type ?? relatedType.value\n\n return targets.find((one) => one.key === key) ?? null\n})\n\nfunction relateTo(picked: SelectModelValue): void {\n relatedType.value = typeof picked === 'string' ? picked : null\n write({ related: null })\n}\n\nfunction relate(ids: number[] | null): void {\n const type = relatedTarget.value?.key\n\n write({ related: type && ids && ids.length > 0 ? { type, ids } : null })\n}\n\n/**\n * \"The record of the page it stands on\": on the page of a service, the recipes of that service —\n * one block in a service's layout instead of one per service. The choice of records gives way to\n * it, because there is nothing left to choose.\n */\nfunction relateToCurrent(on: boolean): void {\n const type = relatedTarget.value?.key\n\n write({ related: on && type ? { type, ids: [], current: true } : null })\n}\n\nconst markup = computed(() => value.value.markup ?? defaultMarkup(value.value))\n\nfunction mark(on: unknown): void {\n write({ markup: on === true })\n}\n\nconst markupHint = computed(() =>\n t(\n defaultMarkup(value.value)\n ? 'collections.field-markup-auto-on'\n : 'collections.field-markup-auto-off',\n ),\n)\n</script>\n\n<template>\n <div class=\"wx-collection-field\" :aria-busy=\"info === undefined\">\n <p v-if=\"info === null\" class=\"wx-collection-field__note\">\n {{ t('collections.field-unavailable', { source: props.source }) }}\n </p>\n\n <template v-else-if=\"info\">\n <p class=\"wx-collection-field__note\">\n {{ t('collections.field-source', { source: info.title }) }}\n </p>\n\n <div v-if=\"info.categories\" class=\"wx-collection-field__row\">\n <label class=\"wx-collection-field__label\" :for=\"`${id}-categories`\">\n {{ t('collections.field-categories') }}\n </label>\n <!-- The placeholder is what an empty choice means, so it goes once there is a choice: the\n search box stays beside the chips, and \"Payment · All categories\" says two things. -->\n <wx-select\n :id=\"`${id}-categories`\"\n :model-value=\"value.categories\"\n :options=\"options\"\n :placeholder=\"value.categories.length === 0 ? t('collections.field-all') : ''\"\n :empty-text=\"t('collections.field-no-categories')\"\n :disabled=\"props.disabled\"\n multiple\n filterable\n clearable\n teleport\n @update:model-value=\"pick\"\n />\n </div>\n\n <div v-if=\"info.relations.length > 0\" class=\"wx-collection-field__row\">\n <span class=\"wx-collection-field__label\">\n {{\n info.relations.length === 1\n ? t('relations.collection-related-to', { target: info.relations[0]!.title })\n : t('relations.collection-related')\n }}\n </span>\n <wx-select\n v-if=\"info.relations.length > 1\"\n :model-value=\"relatedTarget?.key ?? null\"\n :options=\"relationOptions\"\n :placeholder=\"t('relations.collection-related-type')\"\n :aria-label=\"t('relations.collection-related-type')\"\n :disabled=\"props.disabled\"\n clearable\n teleport\n @update:model-value=\"relateTo\"\n />\n <template v-if=\"relatedTarget\">\n <wx-switch\n :model-value=\"value.related?.current === true\"\n :label=\"t('relations.collection-related-current')\"\n :disabled=\"props.disabled\"\n @update:model-value=\"relateToCurrent($event === true)\"\n />\n <p class=\"wx-collection-field__hint\">\n {{ t('relations.collection-related-current-hint', { target: relatedTarget.title }) }}\n </p>\n </template>\n <relations-field\n v-if=\"relatedTarget && value.related?.current !== true\"\n :model-value=\"value.related?.ids ?? []\"\n :target=\"relatedTarget.key\"\n :sortable=\"false\"\n :empty-text=\"t('relations.collection-related-any')\"\n :disabled=\"props.disabled\"\n @update:model-value=\"relate\"\n />\n </div>\n\n <div class=\"wx-collection-field__row\">\n <label class=\"wx-collection-field__label\" :for=\"`${id}-limit`\">\n {{ t('collections.field-limit') }}\n </label>\n <!-- Wrapped rather than given the class: a scoped rule on a core component's own element\n does not reach it when its root is a fragment. -->\n <div class=\"wx-collection-field__limit\">\n <wx-input-number\n :id=\"`${id}-limit`\"\n :model-value=\"value.limit\"\n :min=\"1\"\n :max=\"COLLECTION_MAX_LIMIT\"\n :step=\"1\"\n :precision=\"0\"\n :placeholder=\"t('collections.field-limit-all')\"\n :disabled=\"props.disabled\"\n @update:model-value=\"limit\"\n />\n </div>\n </div>\n\n <wx-switch\n v-if=\"info.categories\"\n :model-value=\"value.filter\"\n :label=\"t('collections.field-filter')\"\n :disabled=\"props.disabled\"\n @update:model-value=\"write({ filter: $event === true })\"\n />\n\n <div v-if=\"info.markup\" class=\"wx-collection-field__markup\">\n <wx-switch\n :model-value=\"markup\"\n :label=\"t('collections.field-markup')\"\n :disabled=\"props.disabled\"\n @update:model-value=\"mark\"\n />\n <p class=\"wx-collection-field__hint\">{{ markupHint }}</p>\n <wx-button\n v-if=\"value.markup !== null\"\n class=\"wx-collection-field__reset\"\n variant=\"text\"\n size=\"sm\"\n :disabled=\"props.disabled\"\n @click=\"write({ markup: null })\"\n >\n {{ t('collections.field-markup-reset') }}\n </wx-button>\n </div>\n </template>\n </div>\n</template>\n\n<style scoped>\n.wx-collection-field {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-12);\n min-width: 0;\n}\n\n.wx-collection-field__row {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-4);\n min-width: 0;\n}\n\n.wx-collection-field__label {\n font-size: var(--wx-font-size-sm);\n color: var(--wx-text-muted);\n}\n\n/* A number of up to three digits: the whole line would promise room for a sentence. */\n.wx-collection-field__limit {\n max-width: 160px;\n}\n\n.wx-collection-field__markup {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-4);\n}\n\n/* Its own width, at the start of the column: stretched, a text button reads as a bar. */\n.wx-collection-field__reset {\n align-self: flex-start;\n}\n\n.wx-collection-field__note,\n.wx-collection-field__hint {\n margin: 0;\n font-size: var(--wx-font-size-sm);\n color: var(--wx-text-muted);\n}\n</style>\n","import type { AdminContext } from './admin'\n\n/** What a link points at, said out loud rather than guessed from what is filled in. */\nexport type LinkTarget = 'entity' | 'url' | 'none'\n\n/** The three `rel` values an editor may choose. `noopener` is added by whoever renders. */\nexport type LinkRel = 'nofollow' | 'sponsored' | 'ugc'\n\n/**\n * A link, in the shape the server keeps it — the same keys the menu's columns use, so a menu item\n * and a field hold one value and go through one validation.\n */\nexport interface LinkValue {\n target: LinkTarget\n entity_type: string | null\n entity_id: number | null\n url: string | null\n /**\n * The fragment, without its `#` — kept apart from the address because a chosen page has\n * nowhere to write one otherwise, and because what the address is differs by target while the\n * anchor does not.\n */\n hash: string | null\n new_tab: boolean\n rel: LinkRel[]\n}\n\n/** One section of the picker: a kind of thing this administrator may link to. */\nexport interface LinkSourceInfo {\n type: string\n title: string\n icon: string | null\n}\n\n/** One thing that can be linked to, as the picker draws it. */\nexport interface LinkCandidate {\n id: number\n title: string\n url: string | null\n /**\n * Whether the site would show it right now. A draft is offered and drawn dimmed: a menu is\n * built before the pages in it are published.\n */\n available: boolean\n /** The line under the title: a path in the tree, a rubric, a date. */\n hint: string | null\n}\n\n/** The same, once the type it came from matters — what `resolve` answers with. */\nexport interface ResolvedLink extends LinkCandidate {\n type: string\n}\n\nexport interface LinksApi {\n sources(): Promise<LinkSourceInfo[]>\n search(type: string, query: string, locale?: string): Promise<LinkCandidate[]>\n resolve(links: { type: string; id: number }[], locale?: string): Promise<ResolvedLink[]>\n /** Named addresses of the site itself, for the field where a path is typed. */\n routes(): Promise<{ name: string; path: string }[]>\n}\n\n/** An empty link — what a field holds before anybody has chosen anything. */\nexport function emptyLink(): LinkValue {\n return {\n target: 'entity',\n entity_type: null,\n entity_id: null,\n url: null,\n hash: null,\n new_tab: false,\n rel: [],\n }\n}\n\n/**\n * What the panel can be asked to link to.\n *\n * The panel's own addresses rather than a section's: the same picker opens in a menu, in a block\n * field and on a described screen, and a copy of it per module would be a copy of the permission\n * rules to get wrong.\n */\nexport function createLinksApi(admin: AdminContext): LinksApi {\n const base = `${admin.apiPath}/links`\n const data = <T>(body: { data: T }): T => body.data\n\n return {\n sources: () => admin.http.get<{ data: LinkSourceInfo[] }>(`${base}/sources`).then(data),\n search: (type, query, locale) =>\n admin.http\n .get<{ data: LinkCandidate[] }>(`${base}/search`, { query: { type, q: query, locale } })\n .then(data),\n resolve: (links, locale) =>\n admin.http.post<{ data: ResolvedLink[] }>(`${base}/resolve`, { links, locale }).then(data),\n routes: () =>\n admin.http.get<{ data: { name: string; path: string }[] }>(`${base}/routes`).then(data),\n }\n}\n","<script setup lang=\"ts\">\nimport { computed, onMounted, ref, watch } from 'vue'\nimport {\n WxAutocomplete,\n WxCheckbox,\n WxInput,\n WxSegmented,\n WxSelect,\n WxText,\n type AutocompleteOption,\n type InputModelValue,\n type SelectOption,\n} from '@webx-ui/core'\nimport { useAdmin } from './admin'\nimport { useI18n, useTranslate } from './i18n'\nimport {\n createLinksApi,\n emptyLink,\n type LinkCandidate,\n type LinkRel,\n type LinkSourceInfo,\n type LinkTarget,\n type LinkValue,\n} from './links'\n\n/**\n * Where a link goes, chosen rather than typed.\n *\n * Three kinds of target and the kind is said out loud, because \"there is no entity, so it must be a\n * URL\" cannot tell a choice nobody has finished from one that deliberately goes nowhere. Which\n * sections are on offer is the server's answer: a source appears once its module is installed and\n * the reader may open it, so this component knows about pages and articles only in the sense that\n * it draws whatever came back.\n *\n * What is chosen is the entity, never its address. The title and the address beside it are only\n * there to show — a value saved last month has a morph pair and nothing to draw, which is what the\n * resolve on mount is for.\n */\ndefineOptions({ name: 'WxLinkPicker' })\n\nconst props = withDefaults(\n defineProps<{\n /** Off where a link that goes nowhere makes no sense — a button, a card. */\n allowNone?: boolean\n /** Off where the two attributes are somebody else's business, as in a menu item's own form. */\n attributes?: boolean\n disabled?: boolean\n }>(),\n { allowNone: true, attributes: true, disabled: false },\n)\n\nconst value = defineModel<LinkValue | null>({ default: null })\n\nconst admin = useAdmin()\nconst i18n = useI18n()\nconst api = createLinksApi(admin)\nconst t = useTranslate('webx-admin')\n\nconst sources = ref<LinkSourceInfo[]>([])\nconst candidates = ref<LinkCandidate[]>([])\nconst routes = ref<{ name: string; path: string }[]>([])\nconst searching = ref(false)\n\n/** The section being searched in, which is not part of the value: what is stored is the type. */\nconst section = ref<string | null>(null)\n\n/** What the input shows: the chosen entity's title, or what somebody is typing. */\nconst term = ref('')\n\n/** The entity behind the saved value, once the server has said what it is called. */\nconst chosen = ref<LinkCandidate | null>(null)\n\n/**\n * The one search to ignore: the title this component just wrote into the field itself.\n *\n * Writing a value into `WxAutocomplete` is indistinguishable from typing it, so opening a form\n * with a saved link asks the server to search for the name of the page that is already chosen —\n * once per link field on the form. Held as the text rather than as a flag, because a flag would\n * have to be cleared on a timer and would swallow whatever the editor typed first.\n */\nlet echo: string | null = null\n\nconst link = computed<LinkValue>(() => value.value ?? emptyLink())\n\nconst targets = computed(() => {\n const options = [\n { value: 'entity', label: t('links.target-entity') },\n { value: 'url', label: t('links.target-url') },\n ]\n\n return props.allowNone ? [...options, { value: 'none', label: t('links.target-none') }] : options\n})\n\nconst sectionOptions = computed<SelectOption[]>(() =>\n sources.value.map((source) => ({ value: source.type, label: source.title })),\n)\n\n/**\n * The results, with the two things that are not the title: where it lives, and whether the site\n * would show it. A draft stays in the list — it is drawn dimmed and says so — because a menu is\n * built before the pages in it are published.\n */\nconst options = computed<AutocompleteOption[]>(() =>\n candidates.value.map((candidate) => ({\n value: candidate.title,\n description: hint(candidate),\n id: candidate.id,\n })),\n)\n\n/** The addresses of the site, offered so that nobody has to remember `/account`. */\nconst routeOptions = computed<AutocompleteOption[]>(() =>\n routes.value.map((route) => ({ value: route.path, description: route.name })),\n)\n\nonMounted(async () => {\n sources.value = await api.sources()\n\n // The section the value already names, so opening a saved link lands in the right list.\n section.value = link.value.entity_type ?? sources.value[0]?.type ?? null\n\n await Promise.all([restore(), loadRoutes()])\n})\n\n/*\n * What a saved value cannot carry: the title of the thing it points at. Asked for once, on open,\n * and again whenever the pair changes from outside — a form that discarded a draft, an undo.\n */\nwatch(\n () => [link.value.entity_type, link.value.entity_id].join(':'),\n () => {\n void restore()\n },\n)\n\nasync function restore(): Promise<void> {\n const { entity_type: type, entity_id: id } = link.value\n\n if (type === null || id === null) {\n chosen.value = null\n term.value = ''\n\n return\n }\n\n if (chosen.value?.id === id) {\n return\n }\n\n const [resolved] = await api.resolve([{ type, id }], i18n.state.locale)\n\n chosen.value = resolved ?? null\n term.value = resolved?.title ?? t('links.missing')\n echo = term.value\n}\n\nasync function loadRoutes(): Promise<void> {\n routes.value = await api.routes()\n}\n\nasync function search(query: string): Promise<void> {\n if (query === echo) {\n echo = null\n\n return\n }\n\n echo = null\n\n if (section.value === null) {\n return\n }\n\n searching.value = true\n\n try {\n candidates.value = await api.search(section.value, query, i18n.state.locale)\n } finally {\n searching.value = false\n }\n}\n\nfunction setTarget(target: string | number | undefined): void {\n if (target === undefined) {\n return\n }\n\n // Only the kind changes: an editor who switches to an address and back should find the page\n // they had chosen still chosen, so nothing else in the value is cleared here.\n value.value = { ...link.value, target: target as LinkTarget }\n}\n\nfunction setSection(type: unknown): void {\n section.value = type === null || type === undefined ? null : String(type)\n candidates.value = []\n void search('')\n}\n\nfunction pick(option: AutocompleteOption): void {\n const candidate = candidates.value.find((one) => one.id === option.id)\n\n if (candidate === undefined || section.value === null) {\n return\n }\n\n chosen.value = candidate\n term.value = candidate.title\n value.value = { ...link.value, entity_type: section.value, entity_id: candidate.id }\n}\n\nfunction clearEntity(): void {\n chosen.value = null\n term.value = ''\n candidates.value = []\n value.value = { ...link.value, entity_type: null, entity_id: null }\n}\n\nfunction setUrl(url: string): void {\n value.value = { ...link.value, url: url === '' ? null : url }\n}\n\n/**\n * The `#` is taken off as it is typed rather than on the way to the server: the field shows one\n * of its own, so leaving a second in the value would put two in front of the editor.\n */\nfunction setHash(hash: InputModelValue): void {\n // A string, always: the field is not localized, so the map half of `InputModelValue` never\n // arrives here — and anything that did would be a value this field cannot mean.\n const anchor = (typeof hash === 'string' || typeof hash === 'number' ? String(hash) : '')\n .replace(/^#+/, '')\n .trim()\n\n value.value = { ...link.value, hash: anchor === '' ? null : anchor }\n}\n\nfunction setNewTab(on: boolean): void {\n value.value = { ...link.value, new_tab: on }\n}\n\nfunction toggleRel(rel: LinkRel, on: boolean): void {\n const kept = link.value.rel.filter((one) => one !== rel)\n\n value.value = { ...link.value, rel: on ? [...kept, rel] : kept }\n}\n\nfunction hint(candidate: LinkCandidate): string {\n const parts = [candidate.hint, candidate.url ?? undefined].filter(\n (part): part is string => typeof part === 'string' && part !== '',\n )\n\n if (!candidate.available) {\n parts.push(t('links.unavailable'))\n }\n\n return parts.join(' · ')\n}\n\nconst REL: LinkRel[] = ['nofollow', 'sponsored', 'ugc']\n</script>\n\n<template>\n <div class=\"wx-link-picker\">\n <wx-segmented\n :options=\"targets\"\n :model-value=\"link.target\"\n :disabled=\"props.disabled\"\n :aria-label=\"t('links.target-entity')\"\n @update:model-value=\"setTarget\"\n />\n\n <div v-if=\"link.target === 'entity'\" class=\"wx-link-picker__entity\">\n <wx-select\n v-if=\"sectionOptions.length > 1\"\n class=\"wx-link-picker__section\"\n :options=\"sectionOptions\"\n :model-value=\"section\"\n :disabled=\"props.disabled\"\n :aria-label=\"t('links.section')\"\n @update:model-value=\"setSection\"\n />\n\n <wx-autocomplete\n class=\"wx-link-picker__search\"\n :model-value=\"term\"\n :options=\"options\"\n remote\n clearable\n :loading=\"searching\"\n :loading-text=\"t('links.searching')\"\n :empty-text=\"t('links.empty')\"\n :placeholder=\"t('links.search')\"\n :aria-label=\"t('links.search')\"\n :disabled=\"props.disabled\"\n @update:model-value=\"(text: string) => (term = text)\"\n @search=\"search\"\n @select=\"pick\"\n @clear=\"clearEntity\"\n />\n </div>\n\n <div v-else-if=\"link.target === 'url'\" class=\"wx-link-picker__url\">\n <wx-autocomplete\n :model-value=\"link.url ?? ''\"\n :options=\"routeOptions\"\n clearable\n open-on-focus\n :empty-text=\"t('links.url-routes')\"\n :placeholder=\"t('links.url-placeholder')\"\n :aria-label=\"t('links.url-label')\"\n :disabled=\"props.disabled\"\n @update:model-value=\"setUrl\"\n />\n </div>\n\n <!--\n The anchor belongs to both kinds of target: an address can carry one inline, a chosen page\n has nowhere else to put one. The `#` is drawn rather than typed, so what is in the field is\n the name and nothing else.\n -->\n <label v-if=\"link.target !== 'none'\" class=\"wx-link-picker__hash\">\n <wx-text size=\"sm\" tone=\"muted\">{{ t('links.hash') }}</wx-text>\n <span class=\"wx-link-picker__sign\" aria-hidden=\"true\">#</span>\n <wx-input\n :model-value=\"link.hash ?? ''\"\n :placeholder=\"t('links.hash-placeholder')\"\n :aria-label=\"t('links.hash')\"\n :disabled=\"props.disabled\"\n clearable\n @update:model-value=\"setHash\"\n />\n </label>\n\n <!--\n Nothing to ask about a link that goes nowhere, and the two attributes below would be a\n question about something that is not there.\n -->\n <div v-if=\"props.attributes && link.target !== 'none'\" class=\"wx-link-picker__attributes\">\n <wx-checkbox\n :model-value=\"link.new_tab\"\n :disabled=\"props.disabled\"\n @update:model-value=\"(on: boolean) => setNewTab(on)\"\n >\n {{ t('links.new-tab') }}\n </wx-checkbox>\n\n <div class=\"wx-link-picker__rel\">\n <wx-text size=\"sm\" tone=\"muted\">{{ t('links.rel') }}</wx-text>\n <wx-checkbox\n v-for=\"rel in REL\"\n :key=\"rel\"\n :model-value=\"link.rel.includes(rel)\"\n :disabled=\"props.disabled\"\n @update:model-value=\"(on: boolean) => toggleRel(rel, on)\"\n >\n {{ t(`links.rel-${rel}`) }}\n </wx-checkbox>\n </div>\n </div>\n </div>\n</template>\n\n<style scoped>\n.wx-link-picker {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-8);\n}\n\n.wx-link-picker__entity {\n display: flex;\n flex-wrap: wrap;\n gap: var(--wx-space-8);\n}\n\n/*\n * `:deep()`, because neither of these is one of our elements: `WxSelect` and `WxAutocomplete`\n * carry a portal beside the control, which makes their root a fragment — and Vue puts the scope\n * attribute only on a child that has a single root. Without it the rules match nothing at all,\n * silently, and both controls stand at their natural width.\n */\n.wx-link-picker__entity > :deep(.wx-link-picker__section) {\n flex: 0 0 auto;\n min-width: 160px;\n}\n\n.wx-link-picker__entity > :deep(.wx-link-picker__search) {\n flex: 1 1 220px;\n min-width: 0;\n}\n\n/*\n * The `#` sits in the row above the field rather than inside it: an addon inside the control is\n * the design system's business and this field does not have one, and a sign drawn over the input\n * would be a second thing to keep in step with its padding.\n */\n.wx-link-picker__hash {\n display: grid;\n grid-template-columns: auto 1fr;\n align-items: center;\n gap: var(--wx-space-4) var(--wx-space-6);\n}\n\n.wx-link-picker__hash > :first-child {\n grid-column: 1 / -1;\n}\n\n.wx-link-picker__sign {\n color: var(--wx-text-placeholder);\n}\n\n.wx-link-picker__attributes {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-12);\n}\n\n/*\n * A column, not a wrapping row. Measured in the panel's editing column at 330px: the heading sat\n * on the first checkbox's line and the other two stacked under it, so \"Relationship\" read as the\n * name of the first box rather than of the three. Three labels of this length never share a row\n * at that width anyway.\n */\n.wx-link-picker__rel {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-6);\n}\n</style>\n","<script setup lang=\"ts\">\nimport LinkPicker from './LinkPicker.vue'\nimport type { LinkValue } from './links'\n\n/**\n * `wx-link` on a described screen.\n *\n * The label, the help line and the errors are the renderer's — a field of kind `field` arrives\n * already wrapped in a `WxFormItem` — so all this adds is the two props a node may set and the\n * model. Everything else the node wrote travels as an attribute rather than as a declared prop:\n * declaring the picker's would turn every boolean the node left alone into `false` on the way\n * through.\n */\ndefineOptions({ name: 'WxLinkField', inheritAttrs: false })\n\nconst props = withDefaults(\n defineProps<{\n /** A link that goes nowhere is a choice on a menu item and noise on a button. */\n allowNone?: boolean\n /** Off where the two attributes belong to whatever holds the link rather than to the field. */\n attributes?: boolean\n disabled?: boolean\n }>(),\n { allowNone: true, attributes: true, disabled: false },\n)\n\nconst value = defineModel<LinkValue | null>({ default: null })\n</script>\n\n<template>\n <link-picker\n v-model=\"value\"\n v-bind=\"$attrs\"\n :allow-none=\"props.allowNone\"\n :attributes=\"props.attributes\"\n :disabled=\"props.disabled\"\n />\n</template>\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 'not-found-title': 'There is nothing at this address',\n 'not-found-description':\n 'The address may be mistyped, or the section it leads to is not installed on this site.',\n 'not-found-home': 'Back to the start',\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 'system-site': 'Site',\n 'system-search': 'Search and checks',\n 'system-access': 'Access',\n },\n // The panel on the site: the drawer beside a page of the site, and the tab that opens it.\n 'site-bar': {\n 'loader-open': 'Open the site panel',\n 'loader-close': 'Close the site panel',\n 'loader-title': 'Site panel',\n title: 'This page',\n close: 'Close',\n 'open-in-panel': 'Open in panel',\n 'tab-page': 'Page',\n 'no-entity':\n 'Nothing in the panel stands behind this address: it is a page of the site’s own code.',\n address: 'Address',\n status: 'Status',\n edited: 'Edited',\n draft: 'There are unpublished changes',\n 'draft-by': ':name has unpublished changes here',\n 'status-published': 'Published',\n 'status-scheduled': 'Scheduled',\n 'status-unpublished': 'Not on the site',\n 'status-trashed': 'In the bin',\n error: 'Could not find out what this page is.',\n retry: 'Try again',\n foreign: 'Only an address of this site can be looked up.',\n forbidden: 'There is nothing on this page you may edit.',\n 'kind-page': 'Page',\n 'kind-article': 'Article',\n 'kind-rubric': 'Rubric',\n 'kind-tag': 'Tag',\n 'kind-category': 'Category',\n 'kind-product': 'Product',\n 'kind-brand': 'Brand',\n 'kind-landing': 'Landing page',\n 'kind-service': 'Service',\n 'kind-vacancy': 'Vacancy',\n 'kind-recipe': 'Recipe',\n 'kind-event': 'Event',\n 'kind-outlet': 'Press outlet',\n changes: 'Unpublished changes: :count',\n publish: 'Publish',\n discard: 'Discard',\n 'draft-other': ':name has a draft here; your edits go into it',\n 'discard-title': 'Discard the unpublished changes?',\n 'discard-text':\n 'The record goes back to what the site shows now. Everything in the draft goes, other people’s changes included.',\n cancel: 'Cancel',\n 'loader-draft': 'Draft',\n 'loader-temporary': 'Open in panel to edit',\n 'loader-add': 'Add below',\n preview: 'Preview draft',\n },\n // The three words on the theme switch. They belong to the panel rather than to whoever\n // places the switch: a core component ships English prop defaults and knows nothing about\n // a dictionary, so the panel is the one that has to say them.\n theme: {\n label: 'Theme',\n light: 'Light',\n dark: 'Dark',\n system: 'Follow the 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 // The head's ··· on a screen that has no name to give the menu.\n more: 'More',\n rename: 'Rename',\n save: 'Save',\n // Said out loud to a screen reader while the little wheel turns, and after it stops.\n saving: 'Saving…',\n saved: 'Saved',\n unpublish: 'Take off the site',\n 'unpublish-title': 'Take it off the site?',\n 'unpublish-text':\n 'Visitors will no longer see it: its page will answer «not found» and leave the sitemap and search. Everything written stays in the panel, and «Publish» brings it back at the same address.',\n 'keep-published': 'Keep it on the site',\n 'discard-title': 'Discard changes?',\n 'discard-text':\n 'The published version comes back. Everything changed since publishing is lost and cannot be brought back.',\n 'keep-changes': 'Keep',\n 'publish-moves': 'The old address :old will lead to the new one.',\n unpublished: 'Taken off the site.',\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 // Two people — or a person and an agent — editing one record. The panel merges what does not\n // overlap without asking, and these are the words for the rest.\n editing: {\n merged: 'Merged with the changes by :who.',\n somebody: 'Somebody',\n 'via-agent': ':name, through an agent',\n 'via-import': ':name, through an import',\n 'conflict-title': 'You and :who changed the same places',\n 'conflict-text': 'Everything else was merged. Pick a version for each place below, then save.',\n base: 'Before',\n mine: 'Yours',\n theirs: 'Theirs',\n 'take-theirs': 'Take theirs everywhere',\n removed: 'Removed',\n empty: 'Empty',\n 'removed-mine': 'you removed this block, they edited it',\n 'removed-theirs': 'they removed this block, you edited it',\n apply: 'Save my choices',\n 'theirs-title': 'Take their version everywhere?',\n 'theirs-text':\n 'The form shows the record as it is now, and what you typed since your last save is gone.',\n cancel: 'Cancel',\n incoming: ':who changed :what · :when',\n pull: 'Pull in',\n others: 'Also open by :names',\n order: 'Order of blocks',\n block: 'Block',\n drafts: 'Drafts',\n 'drafts-text':\n 'Copies of the draft between publications. A draft somebody else’s save wrote over stays here too.',\n 'drafts-empty': 'No copies of the draft yet.',\n autosave: 'Autosave',\n overwritten: 'Written over',\n restore: 'Restore',\n 'restore-title': 'Put this draft back?',\n 'restore-text':\n 'It becomes the draft in the editor. The site does not change until you publish.',\n restored: 'The draft is back in the editor.',\n 'source-panel': 'panel',\n 'source-mcp': 'agent',\n 'source-import': 'import',\n 'and-more': ':what and more',\n and: 'and',\n event: ':who :what · :when',\n 'event-published': 'published',\n 'event-unpublished': 'took it off the site',\n 'event-discarded': 'discarded the draft',\n 'event-restored-version': 'restored version :number',\n 'event-restored-draft': 'put an earlier draft back',\n 'event-moved': 'moved it',\n 'event-moved-to': 'moved it to :path',\n 'event-trashed': 'moved it to the bin',\n 'event-restored': 'took it out of the bin',\n 'event-purged': 'deleted it for good',\n dismiss: 'Got it',\n 'trashed-title': ':who moved this to the bin · :when',\n 'trashed-anonymous': 'This is in the bin · :when',\n 'trashed-text':\n 'Nothing can be saved here until it is out of the bin. What you typed is still in the form.',\n 'restore-bin': 'Restore',\n 'restore-save': 'Restore and save my changes',\n 'restore-failed': 'Could not take it out of the bin.',\n 'out-of-bin': 'Out of the bin.',\n 'purged-title': ':who deleted this for good · :when',\n 'purged-anonymous': 'This was deleted for good',\n 'purged-text':\n 'There is nothing left to save it into. What you typed is still in the form — copy it before you leave.',\n 'copy-text': 'Copy my text',\n copied: 'Copied. Paste it wherever you need it.',\n 'copy-failed': 'Could not copy. Select the text in the form instead.',\n 'purged-message': ':who deleted this for good at :when.',\n 'blocked-trashed': 'This is in the bin',\n 'blocked-purged': 'This was deleted for good',\n 'publish-unseen-title': 'Publish changes you have not seen?',\n 'publish-unseen-text': 'Publishing puts them on the site together with yours.',\n 'publish-with': 'Publish with them',\n 'review-first': 'Review first',\n 'publish-moved':\n 'Somebody changed the draft a moment ago. Look at their changes, then publish again.',\n 'stale-publish':\n ':name changed the draft after you opened it. Look at the changes, then publish again.',\n },\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 // The two words every list needs the moment it has filters: what the funnel is called,\n // and the way out of all of them at once. The names of the filters themselves belong to\n // whatever is being filtered, and travel with that module's own words. The checkboxes of a\n // list that can be selected say what they do to a screen reader, and do it here too.\n filters: {\n title: 'Filters',\n reset: 'Reset all',\n 'select-row': 'Select row',\n 'select-all': 'Select every row on this page',\n },\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 // The one line the panel says about the nightly database dump. It lives at the foot of the\n // settings screen, and the warning is the whole point of it: a backup whose breakage is\n // discovered on the day it was needed is not a backup.\n backup: {\n title: 'Last database snapshot:',\n stale: 'No database snapshot since :date. Check the schedule.',\n never: 'The database has never been backed up. Check the schedule.',\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 // The toolbar of `wx-rich-text`. The editor is a component of the design system and carries\n // English defaults; these are what the panel calls the same buttons.\n 'rich-text': {\n bold: 'Bold',\n italic: 'Italic',\n accent: 'Accent',\n strike: 'Strikethrough',\n code: 'Inline code',\n h2: 'Heading 2',\n h3: 'Heading 3',\n h4: 'Heading 4',\n 'bullet-list': 'Bulleted list',\n 'ordered-list': 'Numbered list',\n blockquote: 'Quote',\n hr: 'Divider',\n link: 'Link',\n table: 'Table',\n image: 'Image',\n youtube: 'YouTube video',\n undo: 'Undo',\n redo: 'Redo',\n 'row-below': 'Row below',\n 'row-above': 'Row above',\n 'column-after': 'Column after',\n 'column-before': 'Column before',\n 'delete-row': 'Delete row',\n 'delete-column': 'Delete column',\n 'merge-cells': 'Merge or split cells',\n 'delete-table': 'Delete table',\n toolbar: 'Text formatting',\n 'link-address': 'Link address',\n 'youtube-address': 'YouTube URL',\n apply: 'Apply',\n cancel: 'Cancel',\n uploading: 'Uploading…',\n source: 'HTML source',\n 'source-loss': 'The editor does not keep this markup and will remove it:',\n 'source-drop': 'Remove it',\n 'source-keep': 'Keep editing',\n },\n // What one administrator writes on a record for the next one. Not the property of any\n // section: the same feed hangs off a submission, an order and a client, so the words are\n // the panel's own.\n notes: {\n title: 'Notes',\n placeholder: 'A note for whoever picks this up next…',\n add: 'Add a note',\n empty: 'No notes yet.',\n edit: 'Edit',\n delete: 'Delete',\n save: 'Save',\n cancel: 'Cancel',\n saved: 'Saved.',\n deleted: 'Deleted.',\n 'delete-title': 'Delete this note?',\n 'delete-text': 'It goes for everybody. This cannot be undone.',\n 'unknown-author': 'A deleted account',\n // The three the server says and the browser only ever repeats.\n 'no-type': 'That kind of record does not carry notes.',\n missing: 'That record no longer exists.',\n forbidden: 'You may not read the notes of this record.',\n 'not-yours': 'A note is edited by whoever wrote it.',\n },\n // Who changed a record and what (`wx-history`). The words of the fields are the modules' and\n // arrive with the rows; these are the frame's: the events, the doors, the run.\n history: {\n title: 'History',\n empty: 'Nothing has been changed here yet.',\n unsaved: 'The history begins with the first save.',\n 'unknown-author': 'Nobody signed in',\n 'event-created': 'Created',\n 'event-updated': 'Changed',\n 'event-deleted': 'Deleted',\n 'event-restored': 'Restored',\n 'event-published': 'Published',\n 'event-unpublished': 'Unpublished',\n 'event-run': 'Run',\n 'source-panel': 'in the panel',\n 'source-mcp': 'through an agent',\n 'source-import': 'by an import',\n 'source-bulk': 'by a bulk action',\n 'source-api': 'through the API',\n 'source-console': 'from the console',\n 'empty-value': 'empty',\n long: 'changed, :from → :to characters',\n run: 'Part of a run',\n 'run-title': 'Run',\n 'run-rows': 'Rows: :count',\n 'run-failed': 'Stopped: :message',\n search: 'Record id',\n more: 'Show more',\n // The three the server says and the browser only ever repeats.\n 'no-type': 'That kind of record keeps no history.',\n missing: 'That run no longer exists.',\n forbidden: 'You may not read the history of this record.',\n },\n // Where a link goes. The sections of the picker are named by whichever modules registered them;\n // these are the words around them, plus the six the server says when a link will not do.\n links: {\n 'target-entity': 'This site',\n 'target-url': 'An address',\n 'target-none': 'Nowhere',\n section: 'Section',\n search: 'Start typing a name',\n searching: 'Searching…',\n empty: 'Nothing found',\n clear: 'Clear the link',\n unavailable: 'Not on the site yet',\n missing: 'What this pointed at is gone',\n 'url-label': 'Address',\n 'url-placeholder': '/account or https://example.com',\n hash: 'Anchor',\n 'hash-placeholder': 'section-on-the-page',\n 'url-routes': 'Addresses of this site',\n 'new-tab': 'Open in a new tab',\n rel: 'Relationship',\n 'rel-nofollow': 'Pass no link weight',\n 'rel-sponsored': 'Paid placement',\n 'rel-ugc': 'Written by a visitor',\n shape: 'This field takes a link.',\n target: 'This field does not know that kind of target.',\n 'no-source': 'Nothing here can link to that.',\n 'no-entity': 'Choose what to link to.',\n 'url-length': 'An address may be at most :max characters long.',\n 'url-scheme': 'An address has to be a path or start with http, https, mailto or tel.',\n 'hash-length': 'An anchor may be at most :max characters long.',\n 'hash-shape': 'An anchor is a name on the page: no spaces in it.',\n 'not-localized': 'A link is the same in every language and cannot be translated.',\n 'address-fallback':\n \"No address in this language yet — this is the address in :locale, the site's main language.\",\n 'address-fallback-label': 'Address in the main language',\n },\n // The categories every module shares: the refusals the server says, and the words of the\n // shared screens under a module's own.\n categories: {\n 'in-use': 'There are :count entries in it. Move them to another category first.',\n 'slug-shape': 'Letters, digits and single hyphens between them.',\n new: 'New category',\n empty: 'No categories yet.',\n 'empty-help': 'A category groups entries. One entry can be in several of them.',\n order: 'The order here is the order on the site',\n hidden: 'Hidden from the site',\n 'no-address': 'No address in this language',\n count: 'Entries: :count',\n 'show-items': 'Show its entries',\n edit: 'Edit',\n 'open-on-site': 'Open on the site',\n delete: 'Delete',\n 'delete-blocked': 'While it holds entries it cannot go — move them first.',\n 'delete-title': 'Delete “:name”?',\n 'delete-text': 'It goes to the bin and comes off the site, and its address is free again.',\n deleted: 'The category is in the bin.',\n cancel: 'Cancel',\n create: 'Create',\n save: 'Save',\n saved: 'Saved.',\n 'save-failed': 'Not saved — look at the marked fields.',\n 'reorder-failed': 'The new order was not saved.',\n 'field-title': 'Name',\n 'field-slug': 'Address',\n 'address-moving':\n 'The address is changing. The old one keeps working and leads to the new one.',\n untitled: 'Untitled',\n trail: 'Where you are',\n 'leave-title': 'Leave without saving?',\n 'leave-text': 'What was changed here since the last save will be lost.',\n leave: 'Leave',\n 'field-main': 'Main',\n 'field-add': 'Add a category',\n 'field-remove': 'Take out of this category',\n 'field-empty': 'In no category yet.',\n 'field-none-left': 'Every category is already chosen.',\n 'order-all': 'Drag to change the order on the site.',\n 'order-category':\n 'Drag to change the order inside this category. The rest of the list keeps its own.',\n 'order-locked':\n 'Clear the search and the filters to change the order — only a whole list, or one category, can be dragged.',\n unknown: 'One of the chosen categories is not there any more.',\n },\n // What a block shows from another section (`wx-collection`): the server's refusals, then the\n // words of the field that makes the choice. The name of the section is the server's, translated.\n collections: {\n 'unknown-source': 'There is no “:source” on this site to show records from.',\n 'unknown-category': 'One of the chosen categories no longer exists.',\n limit: 'How many to show: a whole number from 1 to :max, or empty for all.',\n flag: 'This switch takes yes or no.',\n 'unknown-relation': 'Records of this section cannot be filtered by that.',\n 'unknown-related': 'One of the chosen related records no longer exists.',\n 'field-source': 'Shows records from “:source”.',\n 'field-unavailable':\n 'Records from “:source” cannot be chosen here: the section is not installed, or you have no access to it.',\n 'field-categories': 'Categories',\n 'field-all': 'All categories',\n 'field-no-categories': 'No such category.',\n 'field-limit': 'How many to show',\n 'field-limit-all': 'All',\n 'field-filter': 'A filter by category above the list',\n 'field-markup': 'Markup for search engines',\n 'field-markup-auto-on': 'By default it is on: the block shows every category.',\n 'field-markup-auto-off':\n 'By default it is off: search engines ask not to mark the same records up on several pages, and a chosen category usually stands on several.',\n 'field-markup-reset': 'Back to the default',\n },\n // The records of another section a record points at (`wx-relations`), and the same choice as a\n // block's filter (\"only related to\" in `wx-collection`). Kept apart from `collections` because\n // the field is a field of its own, on any screen.\n relations: {\n 'field-add': 'Add…',\n 'field-searching': 'Searching…',\n 'field-nothing': 'Nothing found.',\n 'field-empty': 'Nothing chosen yet.',\n 'field-remove': 'Remove',\n 'field-drag': 'Drag to change the order',\n 'field-hidden': 'Not on the site',\n 'field-trashed': 'In the bin',\n 'field-missing': 'Not found',\n 'field-full': 'No more than :max can be chosen.',\n 'field-forbidden': 'You cannot see these records, so the choice cannot be changed here.',\n 'collection-related': 'Only related to',\n 'collection-related-to': 'Only related to “:target”',\n 'collection-related-type': 'Which section',\n 'collection-related-any': 'Not narrowed: every record, related or not.',\n 'collection-related-current': 'The record of the page it stands on',\n 'collection-related-current-hint':\n 'On the page of a record of “:target” the block shows what is related to it; on any other page it shows nothing.',\n },\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 * The same moment, as narrow as a column can take it: the time alone for today, the day and a\n * short month for the rest of this year, digits for anything older.\n *\n * A column is scanned, not read — and the full line is the reason a date column had to be 185px\n * wide in Russian and wider in German. What it drops is in the tip, which is where \"when\n * exactly\" was always answered.\n */\n compact(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 compact(value) {\n const at = toDate(value)\n\n if (at === null) {\n return t('dates.never')\n }\n\n const now = new Date()\n\n /*\n * Today is the time and nothing else: it is the only row in the column that shows a clock,\n * so it reads as today without spending a word on saying so. Yesterday takes the ordinary\n * date — one day back is not worth a word either, and the tip has the whole of it.\n */\n if (daysApart(at, now) === 0) return time(at)\n\n return at.getFullYear() === now.getFullYear()\n ? format('compact', { day: 'numeric', month: 'short' }, at)\n : format('compact-earlier', { dateStyle: 'short' }, 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","<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 /**\n * The column form: the time for today, a short month for the rest of the year, digits before\n * that. What it leaves out is in the tip, which is the whole point of having one.\n */\n compact?: boolean\n size?: TextSize\n tone?: TextTone\n }>(),\n {\n compact: false,\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(() => (props.compact ? dates.compact(props.value) : 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","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","import { inject, provide, type InjectionKey, type Ref } from 'vue'\nimport type { AdminContext } from './admin'\n\n/** What happened to a record. `run` is the parent row of an import or a bulk action. */\nexport type HistoryEvent =\n 'created' | 'updated' | 'deleted' | 'restored' | 'published' | 'unpublished' | 'run'\n\n/** Which door the change came through. */\nexport type HistorySource = 'panel' | 'mcp' | 'import' | 'bulk' | 'api' | 'console'\n\n/**\n * One field of one save. A long value (rich text, a repeater) comes without `from` and `to`:\n * the journal keeps that it changed and how long it was, and the versions keep the text.\n */\nexport interface HistoryChange {\n /** `price`, or `name.ru` for one language of a translated field. */\n field: string\n /** The module's word for it, in the reader's language. */\n label: string\n from: unknown\n to: unknown\n long?: boolean\n from_length?: number\n to_length?: number\n}\n\nexport interface HistoryEntry {\n id: number\n event: HistoryEvent\n source: HistorySource\n subject: { type: string; id: number | null }\n /** The name as it was signed; `null` when nobody was signed in (a console command). */\n admin: { id: number | null; name: string | null } | null\n /** The agent's connection, when the source is `mcp`. */\n grant_id: number | null\n changes: HistoryChange[]\n /** The run this save was made in, with what the run was when the server had it at hand. */\n run: { id: number; summary: Record<string, unknown> | null } | null\n created_at: string | null\n /** A run's own: what was done, with which profile, how many rows. */\n summary?: Record<string, unknown>\n rows?: number\n}\n\n/** A page as `->paginate()` has it. */\nexport interface HistoryPage {\n data: HistoryEntry[]\n current_page: number\n last_page: number\n total: number\n}\n\nexport interface HistoryRun {\n run: HistoryEntry\n rows: HistoryPage\n}\n\nexport interface HistoryApi {\n list(page?: number): Promise<HistoryPage>\n /** A run and its rows, or only the rows about one record. */\n run(id: number, page?: number, search?: number | null): Promise<HistoryRun>\n}\n\n/**\n * The journal of one record.\n *\n * `type` is what the module registered (`catalog.product`) — never a class name — and the\n * address is the panel's own, like the notes': one feed for every section, and the server says\n * which permission each type is behind.\n */\nexport function createHistoryApi(\n admin: AdminContext,\n type: string,\n id: number | string,\n): HistoryApi {\n return {\n list: (page = 1) =>\n admin.http.get<HistoryPage>(`${admin.apiPath}/history/${type}/${id}`, { query: { page } }),\n run: (runId, page = 1, search = null) =>\n admin.http.get<HistoryRun>(`${admin.apiPath}/history/runs/${runId}`, {\n query: { page, search },\n }),\n }\n}\n\n/**\n * The id of the record an editor is showing, for a `wx-history` node on its screen.\n *\n * A described screen is the same JSON for every record, so the id cannot be written into it:\n * the page that hosts the screen knows it, and provides it here — the way it provides the\n * address to `wx-slug`. `null` is a record not saved yet, which has no history.\n */\nexport interface HistorySubject {\n id: Ref<number | string | null | undefined>\n}\n\nexport const historySubjectKey: InjectionKey<HistorySubject> = Symbol('wx-history-subject')\n\nexport function provideHistorySubject(subject: HistorySubject): void {\n provide(historySubjectKey, subject)\n}\n\n/** The editor above the node, or `null` outside one. */\nexport function useHistorySubject(): HistorySubject | null {\n return inject(historySubjectKey, null)\n}\n\n/**\n * A value as a line of text. Words for \"nothing\" are the caller's. A list of names — the extra\n * categories of a product, the tags of an article — reads as the names, comma-separated; any\n * other list or object is shown as JSON, cut, because a repeater row printed whole is a\n * paragraph in a feed of one-liners.\n */\nexport function historyValue(value: unknown, empty: string, max = 120): string {\n if (value === null || value === undefined || value === '') return empty\n if (typeof value === 'boolean') return value ? '✓' : '✗'\n if (typeof value === 'string') return cut(value, max)\n if (typeof value === 'number') return String(value)\n\n if (\n Array.isArray(value) &&\n value.every((item) => typeof item === 'string' || typeof item === 'number')\n ) {\n return value.length === 0 ? empty : cut(value.join(', '), max)\n }\n\n return cut(JSON.stringify(value), max)\n}\n\nfunction cut(text: string, max: number): string {\n return text.length > max ? `${text.slice(0, max - 1)}…` : text\n}\n","<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\nimport {\n toast,\n WxAvatar,\n WxButton,\n WxDialog,\n WxEmpty,\n WxInput,\n WxSkeleton,\n WxText,\n} from '@webx-ui/core'\nimport DateText from './DateText.vue'\nimport { useAdmin } from './admin'\nimport { useErrorText } from './errors'\nimport {\n createHistoryApi,\n historyValue,\n useHistorySubject,\n type HistoryChange,\n type HistoryEntry,\n type HistoryRun,\n} from './history'\nimport { useTranslate } from './i18n'\n\ndefineOptions({ name: 'WxHistory' })\n\n/**\n * Who changed a record, when, through which door, and what exactly (WEBX_UI_HISTORY.md §5).\n *\n * The frame's and not a section's, like the notes: the catalogue wants it first, and a copy per\n * module is a copy of the rule about who may read it. On a screen it is the `wx-history` node —\n * a tab of a form is one line of the description or one patch of a project — and the id comes\n * from the editor hosting the screen (`provideHistorySubject`), since the same JSON describes\n * every record.\n *\n * A row made inside an import or a bulk action links to the run: the run is where the journal\n * says what was done to all of them at once, and the row only says what this record got.\n */\nconst props = withDefaults(\n defineProps<{\n /** The type the module registered: `catalog.product`. */\n type: string\n /** The record; the hosting editor's when omitted. */\n id?: number | string | null\n /** Heading above the feed; the panel's own word by default, none with `''`. */\n title?: string\n }>(),\n { id: undefined, title: undefined },\n)\n\nconst admin = useAdmin()\nconst t = useTranslate('webx-admin')\nconst message = useErrorText()\nconst subject = useHistorySubject()\n\nconst entries = ref<HistoryEntry[]>([])\nconst page = ref(1)\nconst lastPage = ref(1)\nconst loading = ref(false)\nconst loadingMore = ref(false)\n\nconst recordId = computed(() => props.id ?? subject?.id.value ?? null)\nconst api = computed(() =>\n recordId.value === null ? null : createHistoryApi(admin, props.type, recordId.value),\n)\nconst heading = computed(() => props.title ?? t('history.title'))\n\nasync function load(): Promise<void> {\n entries.value = []\n page.value = 1\n lastPage.value = 1\n\n if (api.value === null) return\n\n loading.value = true\n\n try {\n const first = await api.value.list(1)\n entries.value = first.data\n lastPage.value = first.last_page\n } catch (error) {\n toast.danger(message(error))\n } finally {\n loading.value = false\n }\n}\n\nasync function more(): Promise<void> {\n if (api.value === null || loadingMore.value) return\n\n loadingMore.value = true\n\n try {\n const next = await api.value.list(page.value + 1)\n entries.value = [...entries.value, ...next.data]\n page.value = next.current_page\n lastPage.value = next.last_page\n } catch (error) {\n toast.danger(message(error))\n } finally {\n loadingMore.value = false\n }\n}\n\n/* The feed follows the record: an editor that swaps one record for the next in place would\n otherwise show the previous one's history under the new name. */\nwatch([() => props.type, recordId], () => void load(), { immediate: true })\n\ndefineExpose({ reload: load })\n\nfunction who(entry: HistoryEntry): string {\n return entry.admin?.name ?? t('history.unknown-author')\n}\n\n/** \"Changed · in the panel\" — what happened and through which door, in one muted line. */\nfunction what(entry: HistoryEntry): string {\n return `${t(`history.event-${entry.event}`)} · ${t(`history.source-${entry.source}`)}`\n}\n\nfunction from(change: HistoryChange): string {\n return historyValue(change.from, t('history.empty-value'))\n}\n\nfunction to(change: HistoryChange): string {\n return historyValue(change.to, t('history.empty-value'))\n}\n\nfunction long(change: HistoryChange): string {\n return t('history.long', {\n from: String(change.from_length ?? 0),\n to: String(change.to_length ?? 0),\n })\n}\n\n/** The run's own words beside the link, when it has some: \"Part of a run · prices.csv\". */\nfunction runLabel(entry: HistoryEntry): string {\n const name = entry.run?.summary?.what\n\n return typeof name === 'string' && name !== ''\n ? `${t('history.run')} · ${name}`\n : t('history.run')\n}\n\n/* The run, in a dialog over the feed: what it was, and its rows with a search by record. */\nconst runOpen = ref(false)\nconst run = ref<HistoryRun | null>(null)\nconst runId = ref<number | null>(null)\nconst runSearch = ref('')\nconst runLoading = ref(false)\nlet runTicket = 0\n\nasync function loadRun(id: number, next = 1): Promise<void> {\n if (api.value === null) return\n\n runLoading.value = true\n const ticket = ++runTicket\n\n try {\n const search = /^\\d+$/.test(runSearch.value.trim()) ? Number(runSearch.value.trim()) : null\n const answer = await api.value.run(id, next, search)\n\n // Typing \"105\" asks three times; only the last answer is the one the box is showing.\n if (ticket !== runTicket) return\n\n run.value =\n next === 1 || run.value === null\n ? answer\n : {\n run: answer.run,\n rows: { ...answer.rows, data: [...run.value.rows.data, ...answer.rows.data] },\n }\n } catch (error) {\n toast.danger(message(error))\n } finally {\n runLoading.value = false\n }\n}\n\nfunction openRun(id: number): void {\n runId.value = id\n run.value = null\n runSearch.value = ''\n runOpen.value = true\n void loadRun(id)\n}\n\n/* A search is typed, not submitted: the rows follow what is in the box. */\nwatch(runSearch, () => {\n if (runId.value !== null && runOpen.value) void loadRun(runId.value)\n})\n\n/** What a run was, as lines: the module's own keys, except the two the panel says itself. */\nconst summaryLines = computed(() => {\n const summary = run.value?.run.summary ?? {}\n\n return Object.entries(summary)\n .filter(([key]) => key !== 'rows' && key !== 'failed')\n .map(([key, value]) => ({ key, value: historyValue(value, t('history.empty-value')) }))\n})\n\nconst runFailed = computed(() => {\n const failed = run.value?.run.summary?.failed\n\n return typeof failed === 'string' ? failed : null\n})\n</script>\n\n<template>\n <section class=\"wx-history\">\n <wx-text v-if=\"heading\" weight=\"medium\" class=\"wx-history__title\">{{ heading }}</wx-text>\n\n <wx-empty v-if=\"recordId === null\" size=\"sm\" :description=\"t('history.unsaved')\" />\n\n <wx-skeleton v-else-if=\"loading\" :rows=\"3\" />\n\n <wx-empty v-else-if=\"entries.length === 0\" size=\"sm\" :description=\"t('history.empty')\" />\n\n <ul v-else class=\"wx-history__list\">\n <li v-for=\"entry in entries\" :key=\"entry.id\" class=\"wx-history-entry\">\n <wx-avatar :name=\"who(entry)\" size=\"sm\" tone=\"auto\" />\n\n <div class=\"wx-history-entry__body\">\n <div class=\"wx-history-entry__head\">\n <wx-text size=\"sm\" weight=\"medium\" truncate>{{ who(entry) }}</wx-text>\n <date-text :value=\"entry.created_at\" />\n </div>\n\n <wx-text size=\"sm\" tone=\"muted\">{{ what(entry) }}</wx-text>\n\n <ul v-if=\"entry.changes.length > 0\" class=\"wx-history-entry__changes\">\n <li v-for=\"change in entry.changes\" :key=\"change.field\" class=\"wx-history-change\">\n <span class=\"wx-history-change__label\">{{ change.label }}:</span>\n <template v-if=\"change.long\">\n <span class=\"wx-history-change__long\">{{ long(change) }}</span>\n </template>\n <template v-else>\n <span class=\"wx-history-change__from\">{{ from(change) }}</span>\n <span class=\"wx-history-change__arrow\" aria-hidden=\"true\">→</span>\n <span class=\"wx-history-change__to\">{{ to(change) }}</span>\n </template>\n </li>\n </ul>\n\n <!-- A link in looks and a button in the form around the screen: `type=\"button\"`, or\n the editor's form would be submitted by it. -->\n <button\n v-if=\"entry.run\"\n type=\"button\"\n class=\"wx-history-entry__run\"\n @click=\"openRun(entry.run.id)\"\n >\n {{ runLabel(entry) }}\n </button>\n </div>\n </li>\n </ul>\n\n <div v-if=\"page < lastPage\" class=\"wx-history__more\">\n <wx-button size=\"sm\" :loading=\"loadingMore\" @click=\"more\">{{ t('history.more') }}</wx-button>\n </div>\n\n <wx-dialog v-model:open=\"runOpen\" :title=\"t('history.run-title')\" :width=\"640\">\n <wx-skeleton v-if=\"run === null\" :rows=\"4\" />\n\n <div v-else class=\"wx-history-run\">\n <div class=\"wx-history-run__head\">\n <wx-text size=\"sm\" weight=\"medium\">{{ who(run.run) }}</wx-text>\n <date-text :value=\"run.run.created_at\" />\n </div>\n <wx-text size=\"sm\" tone=\"muted\">{{ what(run.run) }}</wx-text>\n\n <dl v-if=\"summaryLines.length > 0\" class=\"wx-history-run__summary\">\n <template v-for=\"line in summaryLines\" :key=\"line.key\">\n <dt>{{ line.key }}</dt>\n <dd>{{ line.value }}</dd>\n </template>\n </dl>\n\n <wx-text size=\"sm\">{{\n t('history.run-rows', { count: String(run.run.rows ?? 0) })\n }}</wx-text>\n <wx-text v-if=\"runFailed\" size=\"sm\" tone=\"danger\">\n {{ t('history.run-failed', { message: runFailed }) }}\n </wx-text>\n\n <wx-input\n v-model=\"runSearch\"\n size=\"sm\"\n inputmode=\"numeric\"\n :placeholder=\"t('history.search')\"\n clearable\n />\n\n <ul class=\"wx-history__list\">\n <li v-for=\"row in run.rows.data\" :key=\"row.id\" class=\"wx-history-run__row\">\n <wx-text size=\"sm\" weight=\"medium\">#{{ row.subject.id }}</wx-text>\n <ul class=\"wx-history-entry__changes\">\n <li v-for=\"change in row.changes\" :key=\"change.field\" class=\"wx-history-change\">\n <span class=\"wx-history-change__label\">{{ change.label }}:</span>\n <template v-if=\"change.long\">\n <span class=\"wx-history-change__long\">{{ long(change) }}</span>\n </template>\n <template v-else>\n <span class=\"wx-history-change__from\">{{ from(change) }}</span>\n <span class=\"wx-history-change__arrow\" aria-hidden=\"true\">→</span>\n <span class=\"wx-history-change__to\">{{ to(change) }}</span>\n </template>\n </li>\n </ul>\n </li>\n </ul>\n\n <div v-if=\"run.rows.current_page < run.rows.last_page\" class=\"wx-history__more\">\n <wx-button\n size=\"sm\"\n :loading=\"runLoading\"\n @click=\"runId !== null && loadRun(runId, run.rows.current_page + 1)\"\n >\n {{ t('history.more') }}\n </wx-button>\n </div>\n </div>\n </wx-dialog>\n </section>\n</template>\n\n<style scoped>\n.wx-history {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-12);\n min-width: 0;\n /* Laid out by its own width: it hangs in a tab or a card, whose width the window does not know. */\n container-type: inline-size;\n}\n\n.wx-history__list {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-16);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.wx-history-entry {\n display: flex;\n gap: var(--wx-space-8);\n min-width: 0;\n}\n\n.wx-history-entry__body {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-4);\n flex: 1 1 auto;\n min-width: 0;\n}\n\n/* The date keeps the end of the line, so entries by long and short names line up. */\n.wx-history-entry__head,\n.wx-history-run__head {\n display: flex;\n align-items: center;\n gap: var(--wx-space-8);\n min-width: 0;\n}\n\n.wx-history-entry__head > :last-child,\n.wx-history-run__head > :last-child {\n margin-inline-start: auto;\n flex: none;\n}\n\n.wx-history-entry__changes {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-2);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.wx-history-change {\n font-size: var(--wx-font-size-sm);\n color: var(--wx-text-default);\n overflow-wrap: anywhere;\n}\n\n.wx-history-change__label {\n color: var(--wx-text-muted);\n margin-inline-end: var(--wx-space-4);\n}\n\n/* Was and is are told apart by more than colour: the old value is struck through. */\n.wx-history-change__from {\n color: var(--wx-text-muted);\n text-decoration: line-through;\n}\n\n.wx-history-change__arrow {\n margin-inline: var(--wx-space-4);\n color: var(--wx-text-muted);\n}\n\n.wx-history-change__long {\n color: var(--wx-text-muted);\n font-style: italic;\n}\n\n/* Unpadded, so its words start where the lines above them do. */\n.wx-history-entry__run {\n align-self: flex-start;\n margin: 0;\n padding: 0;\n border: 0;\n border-radius: var(--wx-radius-xs);\n background: none;\n font: inherit;\n font-size: var(--wx-font-size-sm);\n text-align: start;\n color: var(--wx-text-link);\n cursor: pointer;\n}\n\n.wx-history-entry__run:hover {\n text-decoration: underline;\n}\n\n.wx-history-entry__run:focus-visible {\n outline: none;\n box-shadow: var(--wx-ring-focus);\n}\n\n.wx-history__more {\n display: flex;\n justify-content: center;\n}\n\n.wx-history-run {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-8);\n min-width: 0;\n}\n\n.wx-history-run__summary {\n display: grid;\n grid-template-columns: max-content 1fr;\n gap: var(--wx-space-4) var(--wx-space-12);\n margin: 0;\n font-size: var(--wx-font-size-sm);\n}\n\n.wx-history-run__summary dt {\n color: var(--wx-text-muted);\n}\n\n.wx-history-run__summary dd {\n margin: 0;\n overflow-wrap: anywhere;\n}\n\n.wx-history-run__row {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-2);\n}\n</style>\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 } from 'vue'\nimport { WxAction, WxActions, WxDropdownItem } from '@webx-ui/core'\nimport type { ActionSize } from '@webx-ui/core'\nimport { useTranslate } from './i18n'\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 t = useTranslate('webx-admin')\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 class=\"wx-row-menu\"\n collapse=\"always\"\n align=\"end\"\n :size=\"size\"\n :aria-label=\"label\"\n @click.stop\n >\n <!-- The core carries English defaults and knows nothing of the panel's dictionary, so the\n word on the tooltip is the panel's to give (CLAUDE.md §4). -->\n <template #trigger>\n <wx-action type=\"more\" :size=\"size\" :title=\"t('editor.more')\" />\n </template>\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 <span v-if=\"action.hint\" class=\"wx-row-menu__entry\">\n <span>{{ action.label }}</span>\n <span class=\"wx-row-menu__hint\">{{ action.hint }}</span>\n </span>\n <template v-else>{{ action.label }}</template>\n </wx-dropdown-item>\n </template>\n </template>\n </wx-actions>\n</template>\n\n<style scoped>\n/*\n * Nothing at rest, grey under the pointer.\n *\n * A grey square at the end of every row is a column of grey squares, and the eye reads them\n * before it reads the rows. Worse, the fill was the same colour the row takes when it is\n * hovered, so the one control the row carries went missing exactly when somebody reached for\n * it. The variable rather than a rule on the button: `WxAction` only ever reads\n * `--wx-action-bg`, never declares it, so an inherited value is what it uses (CLAUDE.md §4).\n */\n.wx-row-menu {\n --wx-action-bg: transparent;\n}\n\n/*\n * The reason a line is greyed, under its words and quieter than them. Under rather than beside: a\n * reason is a sentence, and beside the words it took all of a narrow menu and left the words none.\n * The item keeps its label on one line; the sentence under it wraps.\n */\n.wx-row-menu__entry {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-2);\n white-space: normal;\n}\n\n.wx-row-menu__hint {\n color: var(--wx-text-muted);\n font-size: var(--wx-font-size-xs);\n font-weight: var(--wx-font-weight-regular);\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, useSlots, useTemplateRef } from 'vue'\nimport { WxAction, WxButton, WxHeading, WxIcon, WxText, useElementWidth } from '@webx-ui/core'\nimport type { HeadingLevel } from '@webx-ui/core'\nimport type { RouteLocationRaw } from 'vue-router'\nimport WxBackButton from './BackButton.vue'\nimport WxRowMenu from './RowMenu.vue'\nimport { useTranslate } from './i18n'\nimport type { ScreenAction } from './types'\n\n/**\n * The first line of every screen in the panel: where the reader is, what it is called, and\n * what can be done with it.\n *\n * Every screen used to answer that on its own, and eight screens gave eight answers — a\n * heading at three different sizes, the way out as an arrow on four of them and as a line of\n * breadcrumbs on the rest, the row of buttons folding into a `···` on two editors and simply\n * wrapping onto a third line everywhere else. None of it is a screen's business: a reader who\n * has learned one screen already knows where to look on the next, and that only holds if there\n * is one head rather than one per section.\n *\n * The shape is four things, in this order: the way out, the name (with the state said beside\n * it), the line under it that says which record this is, and the actions at the other end.\n *\n * Actions are declared rather than drawn, because the same action has to appear as a button on\n * a desktop and as a line of a menu on a phone — and one vnode cannot be mounted in two places\n * (CLAUDE.md §4). Written as markup they had to be written twice, which is what the page and\n * article editors did; written as data they are written once and this decides. Two rules it\n * keeps rather than leaving to each screen: the destructive action is never a button in the\n * head — it lives in the `···`, where it is a word rather than a red rectangle beside the\n * name — and the one action the screen exists for (`primary`) is the one that survives a\n * narrow screen, with everything else folded behind the menu (§18.3).\n *\n * What it does not do is scroll with anything, stick to anything or know what saving means.\n * A screen that has something to save carries `WxActionBar` along its bottom, and on the\n * screens where the head never leaves the window that bar is the only place the save lives\n * (§3.3) — the head does not duplicate it.\n */\nconst props = withDefaults(\n defineProps<{\n /** What the screen is called. The one heading on it (§19). */\n title?: string\n /** The line under the name: which record this is — a slug, a form, a count. */\n subtitle?: string\n /**\n * The way out of the screen. A route draws a link; `true` draws the same arrow and emits\n * `back` instead, which is what a pane inside `WxListDetail` needs — on a phone the drawer\n * carries no close of its own and the pane has to bring one (CLAUDE.md §4).\n *\n * `string` and `object` are spelled out beside `RouteLocationRaw` for the compiler: it\n * cannot resolve the router's type, drops it and leaves `Boolean` alone as the runtime\n * type, so every editor passing its list's path warned \"Expected Boolean, got String\".\n */\n back?: RouteLocationRaw | string | object | boolean\n /** What the arrow's tooltip says. The panel's own word for it when not given. */\n backLabel?: string\n /** `2` for a screen, `3` for a pane standing inside one. */\n level?: HeadingLevel\n /** What can be done here, in the order it should read. */\n actions?: ScreenAction[]\n /** Width of the head below which the actions fold into the `···`. */\n collapseBelow?: number\n /**\n * Draws a rule under the head. For a screen exactly as tall as the window, where the head\n * never scrolls away and needs an edge of its own; a head that scrolls does not.\n */\n divider?: boolean\n }>(),\n {\n title: undefined,\n subtitle: undefined,\n back: undefined,\n backLabel: undefined,\n level: 2,\n actions: undefined,\n collapseBelow: 720,\n divider: false,\n },\n)\n\nconst emit = defineEmits<{\n /** The way out was taken on a head whose `back` is `true` rather than a route. */\n back: []\n}>()\n\ndefineSlots<{\n /** Above the name: breadcrumbs, when the screen stands somewhere in a tree. */\n trail?: () => unknown\n /** Beside the name: the state of the record — a status badge, the way to rename it. */\n 'title-after'?: () => unknown\n /** The line under the name, when it is more than a string. */\n subtitle?: () => unknown\n /**\n * Controls that are not one-word actions and stay whatever the width — the pair of arrows\n * that walks a pile of records one at a time, say.\n */\n extra?: () => unknown\n}>()\n\nconst t = useTranslate('webx-admin')\nconst slots = useSlots()\n\nconst root = useTemplateRef<HTMLElement>('root')\nconst width = useElementWidth(root)\n\n/* `0` is \"not measured yet\", and the roomy case is the one to assume until it is. */\nconst narrow = computed(() => width.value > 0 && width.value < props.collapseBelow)\n\nconst offered = computed<ScreenAction[]>(() => props.actions ?? [])\n\n/** At most one, whatever the screen declared: a screen with two main actions has none (§18.3). */\nconst primary = computed<ScreenAction | null>(\n () => offered.value.find((action) => action.primary) ?? null,\n)\n\n/** Wide: everything that is not a menu line. Narrow: the one action worth the room. */\nconst buttons = computed<ScreenAction[]>(() => {\n if (narrow.value) return primary.value ? [primary.value] : []\n\n return offered.value.filter((action) => !action.menu && !action.danger)\n})\n\nconst menu = computed<ScreenAction[]>(() => {\n if (narrow.value) return offered.value.filter((action) => action !== primary.value)\n\n return offered.value.filter((action) => action.menu || action.danger)\n})\n\n/** A route rather than the bare `true` that asks for the event. */\nconst backTo = computed<RouteLocationRaw | null>(() =>\n props.back && props.back !== true ? (props.back as RouteLocationRaw) : null,\n)\n\nconst hasActions = computed(\n () => buttons.value.length > 0 || menu.value.length > 0 || Boolean(slots.extra),\n)\n</script>\n\n<template>\n <div\n ref=\"root\"\n class=\"wx-screen-head\"\n :data-level=\"level\"\n :class=\"{\n 'is-narrow': narrow,\n 'has-button': buttons.length > 0,\n 'has-trail': Boolean($slots.trail),\n 'has-divider': divider,\n }\"\n >\n <!-- Its own line, and it scrolls sideways rather than wrapping: a trail is one line by\n definition, and a second line of it pushes the name of the record down the screen. -->\n <div v-if=\"$slots.trail\" class=\"wx-screen-head__trail\"><slot name=\"trail\" /></div>\n\n <wx-back-button\n v-if=\"backTo\"\n class=\"wx-screen-head__back\"\n :to=\"backTo\"\n :label=\"backLabel\"\n tooltip-side=\"bottom\"\n />\n <wx-action\n v-else-if=\"back\"\n class=\"wx-screen-head__back\"\n icon=\"arrow-left\"\n size=\"sm\"\n tooltip-side=\"bottom\"\n :title=\"backLabel ?? t('editor.back')\"\n @click=\"emit('back')\"\n />\n\n <div class=\"wx-screen-head__name\">\n <wx-heading v-if=\"title\" :level=\"level\" truncate class=\"wx-screen-head__title\">\n {{ title }}\n </wx-heading>\n <slot name=\"title-after\" />\n </div>\n\n <wx-text\n v-if=\"subtitle || $slots.subtitle\"\n class=\"wx-screen-head__subtitle\"\n size=\"sm\"\n tone=\"muted\"\n truncate\n >\n <slot name=\"subtitle\">{{ subtitle }}</slot>\n </wx-text>\n\n <div v-if=\"hasActions\" class=\"wx-screen-head__actions\">\n <slot name=\"extra\" />\n\n <wx-button\n v-for=\"action in buttons\"\n :key=\"action.key\"\n class=\"wx-screen-head__button\"\n :class=\"{ 'is-primary': action.primary }\"\n :type=\"action.primary ? 'primary' : 'default'\"\n :variant=\"action.primary ? 'solid' : (action.variant ?? 'outline')\"\n :loading=\"action.loading\"\n :disabled=\"action.disabled\"\n :href=\"action.href\"\n :target=\"action.href ? (action.target ?? '_blank') : undefined\"\n :rel=\"action.href ? 'noopener' : undefined\"\n @click=\"action.run?.()\"\n >\n <template v-if=\"action.icon\" #icon><wx-icon :name=\"action.icon\" /></template>\n {{ action.label }}\n </wx-button>\n\n <!-- The same menu a row of a list carries, so that one `···` means one thing in the\n panel — destructive last, behind a rule, and every line with the word on it. -->\n <wx-row-menu\n v-if=\"menu.length > 0\"\n :actions=\"menu\"\n size=\"lg\"\n :label=\"title ?? t('editor.more')\"\n />\n </div>\n </div>\n</template>\n\n<style scoped>\n/*\n * A grid of named areas rather than a row that wraps.\n *\n * Everything on the head answers to the name: the way out is at the start of its line and the\n * actions at the end of the same one, both centred on it — as flex items they lined up with the\n * top of the block instead, which on a screen with a trail put them level with four words of\n * small grey type and left the name hanging under them. The trail takes a line of its own above,\n * and the line that says which record this is takes one below, indented to the name.\n *\n * The space between the columns is margins on the two outer elements rather than a `column-gap`:\n * a head with no way out and no actions still has those tracks, and a gap would indent the name\n * by the width of two things that are not there.\n */\n.wx-screen-head {\n /*\n * The line the way out and the actions are centred on: the heading's own, which is its font\n * size times the tight leading both of them are set in. It has to be written out because what\n * they stand beside is the name *block*, and that block is two lines whenever a badge does not\n * fit next to the name — centring on it would put the arrow between the two, which is what a\n * head that had no rule about this did.\n */\n --wx-head-line: calc(var(--wx-font-size-2xl) * var(--wx-font-line-height-tight));\n\n display: grid;\n grid-template-columns: auto minmax(0, 1fr) auto;\n grid-template-areas:\n 'back name actions'\n '. subtitle .';\n align-items: start;\n row-gap: var(--wx-space-4);\n /* A head inside a column that gives its height to the rows keeps only what it needs. */\n flex: none;\n min-width: 0;\n}\n\n/* A pane inside a screen carries `level=\"3\"`, and its heading is a size smaller. */\n.wx-screen-head[data-level='3'] {\n --wx-head-line: calc(var(--wx-font-size-xl) * var(--wx-font-line-height-tight));\n}\n\n.wx-screen-head.has-trail {\n grid-template-areas:\n 'trail trail trail'\n 'back name actions'\n '. subtitle .';\n}\n\n.wx-screen-head.has-divider {\n padding-block-end: var(--wx-space-12);\n border-block-end: 1px solid var(--wx-border-default);\n}\n\n/*\n * The trail scrolls sideways and says nothing about it.\n *\n * A path to a page four levels down is longer than a phone is wide, and a trail that wraps is\n * two lines of the smallest type on the screen standing between the reader and the name of what\n * they opened. A scrollbar under it would be a third thing to look at, so it is hidden: the\n * trail is a finger away on a touch screen, and on a desktop there is room for it.\n */\n.wx-screen-head__trail {\n grid-area: trail;\n min-width: 0;\n overflow-x: auto;\n /* Nothing above should scroll because the trail reached its end. */\n overscroll-behavior-x: contain;\n /*\n * And it starts at the beginning of the path. The crumbs arrive after the record does, and a\n * scroller whose content grows under it is one the browser anchors — which left the trail\n * scrolled to its far end, with the section's own name cut in half at the left edge.\n */\n overflow-anchor: none;\n scrollbar-width: none;\n -ms-overflow-style: none;\n}\n\n.wx-screen-head__trail::-webkit-scrollbar {\n display: none;\n}\n\n/* The trail is one line whatever its length — wrapping is what the scroll replaces. */\n.wx-screen-head__trail > :deep(*) {\n flex-wrap: nowrap;\n white-space: nowrap;\n}\n\n/*\n * And a crumb keeps its whole width. `WxBreadcrumb` lays its items out to shrink and cut\n * themselves off with an ellipsis, which is the right answer inside a box that cannot scroll and\n * the wrong one inside this: what shrinks away is exactly what the scroll exists to reach.\n */\n.wx-screen-head__trail :deep(.wx-breadcrumb__list) {\n flex-wrap: nowrap;\n}\n\n.wx-screen-head__trail :deep(.wx-breadcrumb-item) {\n flex: none;\n}\n\n.wx-screen-head__trail :deep(.wx-breadcrumb-item__link) {\n overflow: visible;\n}\n\n/*\n * The way out stays small (30) while the actions at the other end are 42: it belongs to the\n * heading beside it rather than to that row, and at 42 it argues with the heading itself (§23.8).\n */\n/*\n * `:deep()` because the class is ours and the element it rides belongs to `WxAction`, which\n * carries a tooltip beside itself and so has a fragment for a root: Vue puts this component's\n * scope attribute only on a single-rooted child, and a scoped rule without it matches nothing\n * at all (CLAUDE.md §4). Silently — the button kept its class, landed in the right cell because\n * an item with no area of its own is placed in the first free one, and stood flush against the\n * heading because the margin was never applied.\n */\n.wx-screen-head > :deep(.wx-screen-head__back) {\n grid-area: back;\n margin-inline-end: var(--wx-space-8);\n /* Half the difference between the line and the control puts the two centres together. */\n margin-block-start: calc((var(--wx-head-line) - 30px) / 2);\n}\n\n.wx-screen-head__name {\n grid-area: name;\n display: flex;\n align-items: center;\n gap: var(--wx-space-8);\n flex-wrap: wrap;\n min-width: 0;\n}\n\n.wx-screen-head__title {\n min-width: 0;\n}\n\n.wx-screen-head__subtitle {\n grid-area: subtitle;\n min-width: 0;\n}\n\n.wx-screen-head__actions {\n grid-area: actions;\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: var(--wx-space-8);\n flex-wrap: wrap;\n min-width: 0;\n margin-inline-start: var(--wx-space-12);\n /* Centred on the name's line, the way the arrow at the other end of it is. */\n margin-block-start: calc((var(--wx-head-line) - var(--wx-size-control-md)) / 2);\n}\n\n/*\n * One height for everything on this line (§23.8): a button with a word on it is 42, and an\n * icon button matches it at `lg`. On a phone the shell drops icon buttons to 36, and the words\n * follow — the height of a button is read off `--wx-size-control-md` and never declared on the\n * button itself, so handing the row a different value is all it takes (CLAUDE.md §4).\n */\n.wx-screen-head__actions :deep(.wx-action) {\n --wx-action-size: var(--wx-size-control-md);\n}\n\n.wx-admin--drawer .wx-screen-head__actions {\n --wx-size-control-md: 36px;\n}\n\n/*\n * Narrow, and there is a button: the actions take the line under the name, whole, and the one\n * button with a word on it takes what the `···` leaves. Three things elbowing each other across\n * 343px is not a row, and a primary button too narrow to hold its word is not a button.\n *\n * Narrow with nothing but the `···` — a pane whose tools all folded — keeps it up on the name's\n * line: a menu alone on a line of its own reads as something left over.\n */\n.wx-screen-head.is-narrow.has-button {\n grid-template-areas:\n 'back name name'\n '. subtitle subtitle'\n 'actions actions actions';\n}\n\n.wx-screen-head.is-narrow.has-button.has-trail {\n grid-template-areas:\n 'trail trail trail'\n 'back name name'\n '. subtitle subtitle'\n 'actions actions actions';\n}\n\n.wx-screen-head.is-narrow.has-button .wx-screen-head__actions {\n margin-inline-start: 0;\n margin-block-start: var(--wx-space-8);\n}\n\n/* `:deep()` because the class is ours and the element it rides belongs to `WxButton`. */\n.wx-screen-head.is-narrow .wx-screen-head__actions > :deep(.wx-screen-head__button) {\n flex: 1 1 auto;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { WxCard, WxTabs, type CardPadding, type TabItem, type TabValue } from '@webx-ui/core'\nimport type { RouteLocationRaw } from 'vue-router'\nimport WxScreenHead from './ScreenHead.vue'\nimport type { ScreenAction } from './types'\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 * That first line is `WxScreenHead`, the same one an editor carries: a list and the record it\n * opens are two screens of one panel, and a head that differs between them is a head the\n * reader has to read twice.\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 /** The line under it, when the name alone does not say which list this is. */\n subtitle?: string\n /** Where the list goes back to, for one that stands under another screen. */\n back?: RouteLocationRaw\n /** What the arrow's tooltip says. The panel's own word for it when not given. */\n backLabel?: string\n /**\n * What the section offers: `New article`, `Check an address`. The one it exists for is\n * `primary`, and it is the one that survives a narrow screen.\n */\n actions?: ScreenAction[]\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 subtitle: undefined,\n back: undefined,\n backLabel: undefined,\n actions: 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 * Controls beside the heading that are not one-word actions and so cannot be declared —\n * a switch, a picker. Everything a button can say belongs in `actions`, which is what folds\n * into the `···` on a phone; what is written here stays drawn at every width.\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 <!-- A list folds its buttons later than an editor does: what stands beside the name here is\n one word and no trail, and a `New page` that takes the whole line on a tablet reads as\n a screen with nothing else on it. -->\n <wx-screen-head\n v-if=\"title || actions || $slots.actions\"\n class=\"wx-list-screen__head\"\n :title=\"title\"\n :subtitle=\"subtitle\"\n :back=\"back\"\n :back-label=\"backLabel\"\n :actions=\"actions\"\n :collapse-below=\"480\"\n >\n <template v-if=\"$slots.actions\" #extra><slot name=\"actions\" /></template>\n </wx-screen-head>\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/*\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","/**\n * The attribute a library picture carries its key in — the same one the server reads\n * (`Html::KEY` in the Composer half). The address beside it is a cache; this is the record.\n */\nconst KEY = 'data-wx-path'\n\n/** A rich text value as a screen holds it: one document, or one per language. */\ntype Value = unknown\n\nfunction documents(value: Value): string[] {\n if (typeof value === 'string') return [value]\n\n if (value !== null && typeof value === 'object') {\n return Object.values(value).filter((one): one is string => typeof one === 'string')\n }\n\n return []\n}\n\n/**\n * Every library key in the value, once each. Read off the markup without parsing it: the\n * attribute is written by the editor in one shape, and most documents have no pictures at all.\n */\nexport function richTextKeys(value: Value): string[] {\n const keys = new Set<string>()\n const pattern = new RegExp(`${KEY}=\"([^\"]+)\"`, 'g')\n\n for (const html of documents(value)) {\n for (const match of html.matchAll(pattern)) keys.add(decode(match[1]))\n }\n\n return [...keys]\n}\n\n/** `&amp;` and friends, the way the attribute was escaped when the editor wrote it. */\nfunction decode(text: string): string {\n const area = document.createElement('textarea')\n area.innerHTML = text\n\n return area.value\n}\n\nfunction rewrite(html: string, addresses: Record<string, string | null>): string {\n const template = document.createElement('template')\n template.innerHTML = html\n\n let changed = false\n\n for (const element of template.content.querySelectorAll<HTMLElement>(`[${KEY}]`)) {\n const address = addresses[element.getAttribute(KEY) ?? '']\n // A link to a file points with `href`, a picture with `src` — as on the server.\n const attribute = element.tagName === 'A' ? 'href' : 'src'\n\n // A key the library no longer has keeps the address it was saved with: a broken picture\n // that still says where it came from is easier to replace than an empty one.\n if (!address || element.getAttribute(attribute) === address) continue\n\n element.setAttribute(attribute, address)\n changed = true\n }\n\n // The same string back when nothing moved, so the editor is not handed a document that only\n // differs in how the parser chose to serialise it.\n return changed ? template.innerHTML : html\n}\n\n/**\n * The value with every library picture pointed at where it lives now. A language map stays a\n * map; anything that is not a document comes back as it was.\n */\nexport function rewriteRichText(value: Value, addresses: Record<string, string | null>): Value {\n if (typeof value === 'string') return rewrite(value, addresses)\n\n if (value !== null && typeof value === 'object') {\n const entries = Object.entries(value).map(([locale, html]) => [\n locale,\n typeof html === 'string' ? rewrite(html, addresses) : html,\n ])\n const changed = entries.some(\n ([locale, html]) => html !== (value as Record<string, unknown>)[locale],\n )\n\n return changed ? Object.fromEntries(entries) : value\n }\n\n return value\n}\n","<script setup lang=\"ts\">\nimport { computed, ref, useAttrs, watch } from 'vue'\nimport { WxRichText, type RichTextLabels, type RichTextModelValue } from '@webx-ui/core'\nimport { useAdmin } from './admin'\nimport { useTranslate } from './i18n'\nimport { richTextKeys, rewriteRichText } from './richTextAddresses'\n\n/**\n * `wx-rich-text` on a screen: the editor, in the panel's language, with the panel's library\n * behind its image button.\n *\n * The editor itself is a component of the design system and knows nothing about either — its\n * words are props and its pictures come from a function it is handed. Both are supplied here,\n * which is the one place that knows the panel: a module translated into ten languages with an\n * English toolbar in the middle of it is the failure this exists to avoid.\n *\n * No props of its own. Declaring the editor's would turn every boolean the node did not set\n * into `false` on the way through, so everything the node wrote travels as attributes and\n * arrives as it was written — `localized` included, which the editor handles itself.\n */\ndefineOptions({ name: 'WxRichTextField', inheritAttrs: false })\n\nconst admin = useAdmin()\nconst t = useTranslate('webx-admin')\n\n/*\n * The library is a module's, not the panel's, so a panel without a file manager hands the\n * editor nothing — and the editor then does not draw an image button it cannot honour.\n */\nconst pickImage = computed(() => admin.pickImage ?? undefined)\n\n/*\n * A document keeps each library picture's key beside an address, and the address is only true\n * on the day the paragraph was written — the site may have moved to another domain since, the\n * library to another disk, the picture been cropped. The site works the address out again on\n * every read; the editor is handed the value as stored, so it is worked out here, before the\n * editor sees it.\n *\n * What the editor sends back is passed on untouched and not looked at again: its pictures were\n * either refreshed here or picked from the library a moment ago. The editor takes the refreshed\n * document without announcing a change, so the form does not turn dirty by being opened.\n */\nconst attrs = useAttrs()\nconst shown = ref(attrs.modelValue as RichTextModelValue | undefined)\nlet sent: unknown\n\nwatch(\n () => attrs.modelValue,\n async (value) => {\n shown.value = value as RichTextModelValue | undefined\n\n const assetUrls = admin.assetUrls\n const keys = value === sent || !assetUrls ? [] : richTextKeys(value)\n\n if (!assetUrls || keys.length === 0) return\n\n const addresses = await assetUrls(keys).catch(() => null)\n\n // Somebody typed, or another record was opened, while the library was answering.\n if (addresses && attrs.modelValue === value) {\n shown.value = rewriteRichText(value, addresses) as RichTextModelValue\n }\n },\n { immediate: true },\n)\n\nfunction remember(value: unknown): void {\n sent = value\n}\n\nconst labels = computed<RichTextLabels>(() => ({\n bold: t('rich-text.bold'),\n italic: t('rich-text.italic'),\n accent: t('rich-text.accent'),\n strike: t('rich-text.strike'),\n code: t('rich-text.code'),\n h2: t('rich-text.h2'),\n h3: t('rich-text.h3'),\n h4: t('rich-text.h4'),\n bulletList: t('rich-text.bullet-list'),\n orderedList: t('rich-text.ordered-list'),\n blockquote: t('rich-text.blockquote'),\n hr: t('rich-text.hr'),\n link: t('rich-text.link'),\n table: t('rich-text.table'),\n image: t('rich-text.image'),\n youtube: t('rich-text.youtube'),\n undo: t('rich-text.undo'),\n redo: t('rich-text.redo'),\n source: t('rich-text.source'),\n addRowAfter: t('rich-text.row-below'),\n addRowBefore: t('rich-text.row-above'),\n addColumnAfter: t('rich-text.column-after'),\n addColumnBefore: t('rich-text.column-before'),\n deleteRow: t('rich-text.delete-row'),\n deleteColumn: t('rich-text.delete-column'),\n mergeOrSplit: t('rich-text.merge-cells'),\n deleteTable: t('rich-text.delete-table'),\n toolbar: t('rich-text.toolbar'),\n linkAddress: t('rich-text.link-address'),\n youtubeAddress: t('rich-text.youtube-address'),\n apply: t('rich-text.apply'),\n cancel: t('rich-text.cancel'),\n uploading: t('rich-text.uploading'),\n sourceLoss: t('rich-text.source-loss'),\n sourceDrop: t('rich-text.source-drop'),\n sourceKeep: t('rich-text.source-keep'),\n}))\n</script>\n\n<template>\n <wx-rich-text\n v-bind=\"$attrs\"\n :model-value=\"shown\"\n :labels=\"labels\"\n :pick-image=\"pickImage\"\n @update:model-value=\"remember\"\n />\n</template>\n","import { inject, provide, type InjectionKey, type Ref } from 'vue'\nimport type { ScreenModel } from '@webx-ui/schema'\n\n/**\n * What a record's editor knows about its address and a described screen cannot carry.\n *\n * The address of a service, a category, an article is the module's prefix and the record's slug.\n * The slug is a field of the screen; the prefix is configuration the editor was handed with the\n * record, and the address the site answers at right now is the registry's. `wx-slug` needs all\n * three to print the whole address and to warn before it moves — so the page that hosts the\n * screen provides them, the same way it provides the preview to `wx-blocks`.\n */\nexport interface RecordAddress {\n /** The values of the screen as they are right now, edits included. */\n values: Ref<ScreenModel>\n /** Where the module's addresses start: `services`, `''` for the root, `null` for none at all. */\n prefix: Ref<string | null>\n /** The address the registry holds for the record in this language, without a leading slash. */\n path: Ref<string | null | undefined>\n /** \"The address is changing\" — in the module's words. */\n moving: () => string\n /**\n * \"No address in this language\" — for a record whose prefix is a per-language fact rather than\n * configuration: a page inside one that has no address in a language has none there either.\n * Given, a `null` prefix means exactly that, and the field says so instead of printing `/`.\n */\n missing?: () => string\n}\n\nexport const recordAddressKey: InjectionKey<RecordAddress> = Symbol('wx-record-address')\n\nexport function provideRecordAddress(address: RecordAddress): void {\n provide(recordAddressKey, address)\n}\n\n/** The editor above this field, or `null` outside one — a demo, a test, a stray screen. */\nexport function useRecordAddress(): RecordAddress | null {\n return inject(recordAddressKey, null)\n}\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { useLocales, WxAlert, WxInput } from '@webx-ui/core'\nimport { useRecordAddress } from './address'\n\n/**\n * The address of a record: the part that is edited, with the module's prefix in front of it.\n *\n * `implants` on its own says nothing about whether the catalogue lives under `/services/` or at\n * the root of the site, which is what somebody checking an address wants to see — so the prefix\n * stands inside the control, where the address is read and written in one place. The editor\n * hosting the screen hands it over with {@link provideRecordAddress}.\n *\n * The line under it appears only once there is something to lose: a record on the site whose\n * address is being changed leaves a redirect behind, and that is the fact that decides whether\n * this is safe to do at all. Said before the save rather than in a toast afterwards.\n *\n * Nothing is declared as a prop on purpose: a wrapper that declared what `WxInput` takes would\n * hand it a `false` where the screen asked for nothing (CLAUDE.md §4).\n */\ndefineOptions({ name: 'WxSlugField', inheritAttrs: false })\n\nconst address = useRecordAddress()\nconst locales = useLocales()\n\n/** Nothing to put in front of the slug: the record has no address in this language at all. */\nconst orphaned = computed(() => address?.prefix.value === null && address.missing !== undefined)\n\n/** `/services/`, or `/` for a module that lives at the root of the site. */\nconst prefix = computed(() => {\n const head = address?.prefix.value ?? ''\n\n return head === '' ? '/' : `/${head}/`\n})\n\n/**\n * The segment as it stands in the field, in the language being edited and no other: falling\n * back on a language that has words would print an address the site does not answer at.\n */\nconst slug = computed(() => {\n const written = address?.values.value.slug\n\n if (typeof written === 'string') return written\n\n return (written as Record<string, string> | null | undefined)?.[locales.active.value] ?? ''\n})\n\n/** On the site at an address that is about to become a different one. */\nconst moving = computed(() => {\n const current = address?.path.value\n\n return (\n !orphaned.value &&\n typeof current === 'string' &&\n slug.value !== '' &&\n `${prefix.value}${slug.value}` !== `/${current}`\n )\n})\n</script>\n\n<template>\n <div class=\"wx-slug\">\n <wx-input v-bind=\"$attrs\">\n <template v-if=\"!orphaned\" #prefix>\n <span class=\"wx-slug__prefix\">{{ prefix }}</span>\n </template>\n </wx-input>\n\n <wx-alert\n v-if=\"orphaned && address?.missing\"\n type=\"info\"\n variant=\"soft\"\n :description=\"address.missing()\"\n />\n <wx-alert\n v-else-if=\"moving && address\"\n type=\"info\"\n variant=\"soft\"\n :description=\"address.moving()\"\n />\n </div>\n</template>\n\n<style scoped>\n.wx-slug {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-8);\n}\n\n/*\n * One address in one face. The prefix and what is typed after it are one string; two faces on\n * one line do not sit on one line, and only the muting should say which part cannot be typed\n * over.\n */\n.wx-slug__prefix,\n.wx-slug :deep(.wx-input__inner) {\n font-family: var(--wx-font-family-mono);\n}\n\n.wx-slug__prefix {\n color: var(--wx-text-muted);\n}\n</style>\n","import type { TypeRegistry } from '@webx-ui/schema'\nimport CategoriesField from './categories/CategoriesField.vue'\nimport SourceSelect from './categories/SourceSelect.vue'\nimport CollectionField from './collections/CollectionField.vue'\nimport LinkField from './LinkField.vue'\nimport RelationsField from './relations/RelationsField.vue'\nimport HistoryFeed from './HistoryFeed.vue'\nimport ListScreen from './ListScreen.vue'\nimport RichTextField from './RichTextField.vue'\nimport SlugField from './SlugField.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 *\n * `wx-rich-text` is here rather than in `@webx-ui/schema` because it is not the editor that\n * makes it a panel field: it is the panel's words on its toolbar and the panel's library behind\n * its image button, and the schema package knows about neither.\n *\n * `wx-link` is here for the same reason twice over: what it can point at is whatever the installed\n * content modules registered, and the only thing that knows what those are is the panel's own\n * backend.\n *\n * `wx-categories` files a record under a module's categories (`categories/`). `wx-slug` is the\n * address of any record with the module's prefix in front of it — a service, a category; the\n * editor hosting the screen hands it the prefix (`provideRecordAddress`). `wx-category-slug` is\n * the same field under the name the category screens were first described with.\n *\n * `wx-select` is the core's select that also takes `source` — a module's list as its options, the\n * way `wx-categories` has it: the stock status of a product, its brand. Over the schema's own\n * `wx-select`, which it is when there is no `source`.\n *\n * `wx-collection` is a block's window onto another section — the FAQ's questions, a team — and\n * keeps which part of it to show, never the records (`collections/`).\n *\n * `wx-relations` is a record pointing at records of another section — the services of a recipe,\n * its similar recipes — by id, in order (`relations/`).\n *\n * `wx-history` is who changed the record and what (WEBX_UI_HISTORY.md §5): a tab of any form is\n * `{ \"type\": \"wx-history\", \"props\": { \"type\": \"catalog.product\" } }`, and the record's id comes\n * from the editor hosting the screen (`provideHistorySubject`). It draws and edits nothing.\n */\nexport const adminTypes: TypeRegistry = {\n 'wx-list': { component: ListScreen, kind: 'layout', labelProp: 'title' },\n // Full width: an editor shares a row with nothing, and a form of two columns would give it\n // half a line to write a page of text in.\n 'wx-rich-text': { component: RichTextField, kind: 'field', wide: true },\n // Wide as well: a row of the picker is a segmented switch, a section and a search box, and half\n // a form's width leaves the search box too narrow to read a page title in.\n 'wx-link': { component: LinkField, kind: 'field', wide: true },\n 'wx-categories': { component: CategoriesField, kind: 'field' },\n 'wx-select': { component: SourceSelect, kind: 'field' },\n // Wide: four controls in a column, and half a line leaves the category chips one to a row.\n 'wx-collection': { component: CollectionField, kind: 'field', wide: true },\n // Wide: a row carries a picture, a name with a line under it and a mark, and the search box\n // under the list reads titles.\n 'wx-relations': { component: RelationsField, kind: 'field', wide: true },\n 'wx-slug': { component: SlugField, kind: 'field' },\n 'wx-category-slug': { component: SlugField, kind: 'field' },\n // The node's label is the feed's heading; inside a tab that already names it, `props.title: \"\"`.\n 'wx-history': { component: HistoryFeed, kind: 'display', 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 { ThemeController } from './theme'\nimport type {\n AdminModule,\n AdminStatus,\n AdminUser,\n BlockLabels,\n Manifest,\n NavEntry,\n NavGroup,\n PickedImage,\n} 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 /**\n * How the panel picks a picture, when a module installed here has a library. `null` when\n * none does — and a field that needs one then does not offer to.\n */\n readonly pickImage: (() => Promise<PickedImage | null>) | null\n /**\n * Where library keys live now, when a module installed here has a library; `null` when none\n * does, and a document is then shown with the addresses it was saved with.\n */\n readonly assetUrls: ((paths: string[]) => Promise<Record<string, string | null>>) | null\n /**\n * Names of block types and their fields, when a module installed here has a block library;\n * fetched once for the session. Optional on the context so a hand-made one in a test needs none.\n */\n readonly blockLabels?: () => Promise<BlockLabels | null>\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 /** Told who is signed in, so the theme they chose follows them to this machine. */\n theme?: ThemeController\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 section: module.section ?? 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 const entries = byGroup.get(group.id) ?? []\n const sections = group.sections ?? []\n // Like a group, a caption the server never declared is no caption: the entry stays\n // with the plain ones rather than vanishing under a heading nobody can name.\n const captioned = (entry: NavEntry) =>\n sections.some((section) => section.id === entry.section)\n\n return {\n id: group.id,\n title: group.title,\n icon: group.icon ?? null,\n entries: entries.filter((entry) => !captioned(entry)),\n sections: sections\n .map((section) => ({\n id: section.id,\n title: section.title,\n entries: entries.filter((entry) => entry.section === section.id),\n }))\n .filter((section) => section.entries.length > 0),\n }\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 // The first module that has a library wins. Two of them is not a case worth a setting: a\n // panel with two file managers has a bigger question to answer than which one this opens.\n const pickImage = options.modules.find((module) => module.pickImage !== undefined)?.pickImage\n const assetUrls = options.modules.find((module) => module.assetUrls !== undefined)?.assetUrls\n const blockLabelsOf = options.modules.find(\n (module) => module.blockLabels !== undefined,\n )?.blockLabels\n let blockLabelsPending: Promise<BlockLabels | null> | null = null\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 adoptTheme(state.user)\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. The manifest itself is already in that\n // language — the server answers a signed-in administrator in their stored one — so only\n // the dictionary is missing; asking for the manifest again here was the same request twice\n // on every visit that started in another language.\n if (manifest.locale !== undefined && manifest.locale !== options.i18n.state.locale) {\n await switchLocale(manifest.locale, false)\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 /**\n * The theme is a property of the person, like the language: chosen once and found again on\n * the next machine they sign in on. Until then the panel is drawn in whatever this browser\n * remembers, which is what the sign-in screen had to go on.\n */\n function adoptTheme(user: AdminUser | null): void {\n if (user !== null) {\n options.theme?.adopt(user.theme)\n }\n }\n\n function setLocale(code: string): Promise<void> {\n return switchLocale(code, true)\n }\n\n async function switchLocale(code: string, refresh: boolean): 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, and not when the caller\n // is `reload()` itself, which has just fetched one in this language. A manifest that will\n // not 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 if (refresh) {\n await refreshManifest()\n }\n }\n\n /*\n * Where a stored key lives, remembered for the visit. The corner of the panel draws the\n * signed-in person in up to three places and redraws them as the layout changes, and every\n * mount asked the library about the same photograph again. A question already on its way is\n * shared rather than asked twice; a key the library has lost, or a request that failed, is\n * forgotten, since an upload or a retry may find it.\n */\n const knownAssets = new Map<string, Promise<string | null>>()\n\n async function cachedAssetUrls(paths: string[]): Promise<Record<string, string | null>> {\n const missing = [...new Set(paths.filter((path) => !knownAssets.has(path)))]\n\n if (missing.length > 0 && assetUrls) {\n const batch = assetUrls(missing, context)\n\n for (const path of missing) {\n const one = batch.then((found) => found[path] ?? null)\n\n knownAssets.set(path, one)\n one.then(\n (url) => {\n if (url === null) knownAssets.delete(path)\n },\n () => knownAssets.delete(path),\n )\n }\n }\n\n const urls = await Promise.all(paths.map((path) => knownAssets.get(path) ?? null))\n\n return Object.fromEntries(paths.map((path, index) => [path, urls[index] ?? null]))\n }\n\n const context: AdminContext = {\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 pickImage: pickImage ?? null,\n assetUrls: assetUrls ? cachedAssetUrls : null,\n blockLabels() {\n // Once for the session, and a failure is not remembered: the next notice asks again.\n blockLabelsPending ??= blockLabelsOf\n ? blockLabelsOf(context).catch(() => {\n blockLabelsPending = null\n\n return null\n })\n : Promise.resolve(null)\n\n return blockLabelsPending\n },\n reload,\n refreshManifest,\n setLocale,\n setUser(user) {\n state.user = user\n adoptTheme(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 return context\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","<script setup lang=\"ts\">\nimport { computed, watch } from 'vue'\nimport { useRouter } from 'vue-router'\nimport { WxResult } from '@webx-ui/core'\nimport { useAdmin } from './admin'\nimport { useTranslate } from './i18n'\n\n/**\n * What the panel opens on.\n *\n * Until now nothing answered at `/`: the routes are the modules', and no module claimed the\n * root — so signing in landed on a blank page and the first thing anybody did was click a\n * section. A module says which one that should be with `landing: true`, and here is where it\n * is honoured.\n *\n * It waits for the manifest rather than redirecting on the spot, because what a panel has is\n * something only the server knows: a landing module whose Composer half is not installed is\n * not in the navigation, and sending somebody to a section that is not there would be worse\n * than sending them nowhere. The fallback is the first entry of the menu, which is the section\n * this panel would call its front page anyway.\n */\nconst props = withDefaults(defineProps<{ landing?: string | null }>(), { landing: null })\n\nconst admin = useAdmin()\nconst router = useRouter()\nconst t = useTranslate('webx-admin')\n\nconst destination = computed<string | null>(() => {\n const entries = admin.nav.value\n\n if (entries.length === 0) {\n return null\n }\n\n const claimed = props.landing === null ? undefined : entries.find((e) => e.path === props.landing)\n\n return (claimed ?? entries[0])?.path ?? null\n})\n\nwatch(\n destination,\n (path) => {\n if (path !== null) void router.replace(path)\n },\n { immediate: true },\n)\n</script>\n\n<template>\n <!--\n Only for a panel that has nothing to show. While the manifest is on its way the shell is\n already drawing its own spinner, and a second one inside it would be two.\n -->\n <wx-result\n v-if=\"destination === null && admin.state.status === 'ready'\"\n status=\"info\"\n :title=\"t('shell.empty-title')\"\n :subtitle=\"t('shell.empty-description')\"\n />\n</template>\n","<script setup lang=\"ts\">\nimport { computed, onMounted, useTemplateRef, watch } 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 *\n * A group names its own icon; the gear is what it falls back to, because that is the picture\n * every group had before groups could carry one. Inside a group, the entries under no caption\n * come first and each caption follows with its own — a caption adds no depth, and on the narrow\n * rail it is a rule.\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 /*\n * The longest path that contains this one, on a segment boundary: a section may live inside\n * another's path (`/services/categories` under `/services`), and the first match in the order\n * of the menu would light up the outer one.\n */\n get: () => {\n const match = admin.nav.value\n .filter((entry) => route.path === entry.path || route.path.startsWith(`${entry.path}/`))\n .sort((a, b) => b.path.length - a.path.length)[0]\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\n/*\n * A long menu scrolls on its own, and a reload puts it back at the top — with the current\n * section, often in a branch near the foot, out of sight. Once, on arrival: the menu comes with\n * the manifest, so that is when there first is a current entry, and its branch opens with a\n * transition, so the row is only where it will stay once that has run. Afterwards the user\n * clicked the entry, so it is already where they are looking.\n */\nconst root = useTemplateRef<{ $el: Element | null }>('root')\n\nlet revealed = false\n\nasync function reveal(): Promise<void> {\n const el = root.value?.$el\n\n if (revealed || current.value === '' || !(el instanceof HTMLElement)) return\n revealed = true\n await Promise.allSettled((el.getAnimations?.({ subtree: true }) ?? []).map((a) => a.finished))\n el.querySelector('.wx-menu-row.is-active')?.scrollIntoView?.({ block: 'center' })\n}\n\nonMounted(reveal)\nwatch(current, reveal, { flush: 'post' })\n</script>\n\n<template>\n <wx-menu\n ref=\"root\"\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=\"group.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-menu-group v-for=\"section in group.sections\" :key=\"section.id\" :title=\"section.title\">\n <wx-menu-item\n v-for=\"entry in section.entries\"\n :key=\"entry.id\"\n :value=\"entry.id\"\n :icon=\"entry.icon ?? undefined\"\n :label=\"entry.title\"\n />\n </wx-menu-group>\n </wx-submenu>\n </wx-menu>\n</template>\n","<script setup lang=\"ts\">\nimport { useRouter } from 'vue-router'\nimport { WxButton, WxResult } from '@webx-ui/core'\nimport { useAdmin } from './admin'\nimport { useTranslate } from './i18n'\n\n/**\n * What an address no section answers for shows.\n *\n * Until now nothing did, and the content area simply stayed empty — an old bookmark, a section\n * this site does not have installed, or a typo all looked like a panel that had broken. The way\n * back is the panel's start, which is wherever `/` leads on this site.\n *\n * Drawn only once the panel is ready: while the manifest is on its way the shell shows its own\n * spinner, and a section that does exist is never «not found» for the second before it loads.\n */\nconst admin = useAdmin()\nconst router = useRouter()\nconst t = useTranslate('webx-admin')\n</script>\n\n<template>\n <wx-result\n v-if=\"admin.state.status === 'ready'\"\n class=\"wx-admin-not-found\"\n status=\"404\"\n :title=\"t('shell.not-found-title')\"\n :subtitle=\"t('shell.not-found-description')\"\n >\n <template #actions>\n <wx-button type=\"primary\" @click=\"router.push('/')\">{{\n t('shell.not-found-home')\n }}</wx-button>\n </template>\n </wx-result>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, onUnmounted, ref, watchEffect } from 'vue'\nimport { useRoute } from 'vue-router'\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 * A screen that is framed by something else — the panel on the site — and brings its own head:\n * no menu, no account corner, nothing to navigate to from inside a drawer beside the site.\n */\nconst route = useRoute()\n// Optional: a test may mount the shell without a router.\nconst bare = computed(() => route?.meta?.bare === true)\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\n/*\n * The same size, written on the document as well as on the shell.\n *\n * A custom property inherits down the tree, and a drawer, a dialog and a toast are not in the\n * tree: they are teleported to the end of `<body>`, outside the element that declares the step.\n * So the panel's own drawer — which on a phone is the whole screen — laid itself out with the\n * desktop step of 16 while everything behind it used 8. Declared on the root as well, the step\n * reaches the portals too, and nothing inside them has to know it is in one.\n */\nwatchEffect(() => {\n if (typeof document === 'undefined') return\n\n document.documentElement.dataset.wxShell = size.value\n})\n\nonUnmounted(() => {\n if (typeof document !== 'undefined') delete document.documentElement.dataset.wxShell\n})\n</script>\n\n<template>\n <wx-toaster placement=\"bottom-center\" />\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-if=\"bare\" class=\"wx-root wx-admin-bare\">\n <router-view />\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 class=\"wx-admin__drawer\"\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/*\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.\n *\n * Off the attribute the shell writes on the root as well as off the mount point, because\n * `#webx-app` is only what the Blade shell renders and a panel mounted anywhere else kept the\n * margin. What that cost was not the gap. Every dialog locks the page, and the lock zeroes\n * `margin-right` and pays the scrollbar back as padding — so a body with a margin gets that\n * margin's width back as content, and the whole page steps sideways each time a dialog opens\n * (CLAUDE.md §4). The two `:has()` selectors stay for the Blade page, where they hold from the\n * first paint rather than from the mount.\n */\n:root[data-wx-shell],\n:root[data-wx-shell] > body,\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\n/*\n * One left edge down the drawer.\n *\n * The drawer already insets what it holds by its own step, and `WxMenu` adds a second one of\n * its own — so the highlighted row started eight pixels further in than the account block under\n * it and the name over it. With every block the same colour as the sheet that is invisible; in\n * the dark theme, where each tint is a shape of its own, it is three blocks and three edges.\n * Unscoped and named on the drawer itself: the drawer is teleported to the end of the document,\n * so nothing that begins at `.wx-admin` reaches it.\n */\n.wx-admin__drawer .wx-drawer__content > .wx-menu {\n padding-inline: 0;\n}\n\n/*\n * The panel's step where the tree cannot reach: on the document root, for everything the panel\n * teleports out of itself — drawers, dialogs, the toaster. The shell writes the size class here\n * as a data attribute, and these three lines say the same thing the three above `.wx-admin`\n * say. Unscoped, because `:root` is nobody's element.\n */\n:root[data-wx-shell='drawer'] {\n --wx-gap: var(--wx-space-8);\n}\n\n:root[data-wx-shell='rail'] {\n --wx-gap: var(--wx-space-12);\n}\n\n:root[data-wx-shell='sidebar'] {\n --wx-gap: var(--wx-space-16);\n}\n</style>\n","import { inject, type Component, type InjectionKey } from 'vue'\nimport type { AdminContext } from './admin'\nimport type { Http } from './http'\n\n/**\n * What `GET /api/cms/site-bar/context` says about the page in front of the panel on the site —\n * mirrors `WebxUi\\Admin\\Http\\Controllers\\SiteBarController::context()`.\n */\nexport interface SiteBarContext {\n /** The path that was asked about, without its query. */\n url: string\n /** The language of the address, when the registry knows it. */\n locale: string | null\n /** The record behind the address, or null — a page of the site's own code, or none it may open. */\n entity: SiteBarEntity | null\n /** The tabs this administrator gets here, in order. */\n sections: string[]\n}\n\nexport type SiteBarStatus = 'published' | 'scheduled' | 'unpublished' | 'trashed'\n\nexport interface SiteBarEntity {\n type: string\n id: number | string\n /** What the record is, in the panel's language: «Page», «Article». */\n kind: string\n label: string\n status: SiteBarStatus\n /** Absent for a record that keeps no draft. */\n draft: SiteBarDraft | null\n updatedAt: string | null\n /** Where the panel edits it, relative to the panel. */\n edit: string | null\n /** A signed link to the draft shown as the page it will be; only while there is a draft. */\n preview?: string | null\n /** What the record's editor in the panel calls it — `editing/{editing}/{id}` is its heartbeat. */\n editing?: string | null\n /** The revision the record's editor holds; sent with «Publish» so an unseen edit is not published. */\n revision?: string | null\n can: { edit: boolean; publish: boolean }\n}\n\nexport interface SiteBarDraft {\n differs: boolean\n by: string | null\n at: string | null\n /** How many fields of the record the draft changes. */\n changes?: number\n /** Whether the last hand on the draft was this administrator's. */\n mine?: boolean\n}\n\n/**\n * A tab a section adds to the panel on the site — «SEO» from `module-seo`, «Blocks» from\n * `module-blocks`.\n *\n * The server decides whether the tab is there (`SiteBar::section()` answers per page and per\n * administrator, and `context.sections` lists the ones that said yes); the module brings what\n * it looks like. Split that way so the frame never learns what its tabs are about, and a tab\n * the server offers without a module on this side is simply not drawn.\n */\nexport interface SiteBarTab {\n /** The name the server registered the section under — and the tab's own, as `show()` names it. */\n name: string\n /**\n * More sections the same tab stands for: drawn once when the server offered any of them.\n * «Blocks» is the record's blocks and the layout's regions — two rights, one picker on the page.\n */\n sections?: string[]\n /** What the tab is called, in the panel's language. */\n label: (admin: AdminContext) => string\n /** Drawn inside the tab, with the page's context as its `context` prop. */\n component: Component\n}\n\n/**\n * @param locale The language the page says it is in (`<html lang>`). The address's own row wins\n * over it; it only matters for a page of the site's own code, which has no row.\n */\nexport function fetchSiteBarContext(\n http: Http,\n apiPath: string,\n url: string,\n locale?: string | null,\n): Promise<{ data: SiteBarContext }> {\n const language = locale ? `&locale=${encodeURIComponent(locale)}` : ''\n\n return http.get<{ data: SiteBarContext }>(\n `${apiPath}/site-bar/context?url=${encodeURIComponent(url)}${language}`,\n )\n}\n\n/**\n * What the panel can ask of the page it is framed in:\n *\n * - `close` — shut the drawer;\n * - `title` — put this in the browser's tab: the page's `<title>` after an edit, so the editor\n * sees it changed without reloading (the rest of `<head>` waits for the next load);\n * - `reload` — load the page again: after «Publish» and «Discard» it is the site's again;\n * - `tab` — the tab in front, for the page to remember and open the next frame on;\n * - `blocks` — whose blocks may be picked on the page — the record's, a region's — and what each\n * is called;\n * - `mode` — picking blocks on the page on or off (§8);\n * - `select` / `highlight` — frame a block on the page and bring it into view, or stop;\n * - `replace` — put this markup in place of the block's range: the block drawn from the draft;\n * - `insert` — the same when the page has the block, otherwise put it in after the first of\n * `after` the page has, before the first of `before`, or — `top` — at the end of its tree;\n * - `move` — carry the block's range to where `after`/`before` say, `html` if the page lacks it;\n * - `remove` — take the block's range out (removed, or hidden: the site does not draw it);\n * - `assets` — add the styles and script of block types the page did not have.\n *\n * A block is named by its key and its `scope` — `{type}:{id}` of a record, `region:{name}` of a\n * region — since the header and the page are two trees that never promised each other unique keys.\n */\nexport type SiteBarMessage =\n | { type: 'close' }\n | { type: 'title'; title: string }\n | { type: 'reload' }\n | { type: 'tab'; name: string }\n | { type: 'blocks'; sources: SiteBarBlockSource[] }\n | { type: 'mode'; on: boolean }\n | { type: 'select'; key: string | null; scope?: string }\n | { type: 'highlight'; key: string | null; scope?: string }\n | { type: 'replace'; key: string; scope?: string; html: string }\n | ({ type: 'insert' | 'move'; key: string; scope?: string; html: string } & SiteBarPlace)\n | { type: 'remove'; key: string; scope?: string }\n | { type: 'assets'; hash: string; css: string | null; js: string | null }\n\n/** Where a block that is not on the page goes: by its neighbours, nearest first. */\nexport interface SiteBarPlace {\n after?: string[]\n before?: string[]\n /** It stands at the top level of its tree: with no neighbour on the page, at the tree's end. */\n top?: boolean\n}\n\n/** One tree of blocks the page may pick from. */\nexport interface SiteBarBlockSource {\n /** `{type}:{id}` of a record (`page:12`), `region:{name}` of a region of the layout. */\n scope: string\n /** Said on the frame beside the block's type — «Header» — when the block is not the page's own. */\n title?: string\n items: { key: string; label: string }[]\n}\n\n/**\n * What the page says to the panel: a block picked on it, picking switched off there (Esc),\n * «Add below» pressed on the picked block, and the sets of block styles and scripts the page has —\n * sent with an edit, so that a type the page lacks brings its own and one it has brings nothing.\n */\nexport type SitePageMessage =\n | { type: 'select'; key: string; scope?: string }\n | { type: 'mode'; on: boolean }\n | { type: 'add'; key: string; scope?: string }\n | { type: 'bundles'; hashes: string[] }\n /** The regions the page prints — the layout's, and the shared blocks placed on this page. */\n | { type: 'regions'; names: string[] }\n\n/**\n * Listen to the page the panel is framed in. Only that page — the parent window, from this\n * origin — and only messages of the bar: anything else on the page can post messages too.\n * Returns the way to stop listening.\n */\nexport function onSitePage(listener: (message: SitePageMessage) => void): () => void {\n if (typeof window === 'undefined' || window.parent === window) return () => {}\n\n const handler = (event: MessageEvent): void => {\n if (event.origin !== window.location.origin || event.source !== window.parent) return\n\n const data = event.data as { wxBar?: unknown; type?: unknown } | null\n\n if (typeof data !== 'object' || data === null || data.wxBar !== 1) return\n if (\n data.type !== 'select' &&\n data.type !== 'mode' &&\n data.type !== 'add' &&\n data.type !== 'bundles' &&\n data.type !== 'regions'\n )\n return\n\n listener(data as SitePageMessage)\n }\n\n window.addEventListener('message', handler)\n\n return () => window.removeEventListener('message', handler)\n}\n\n/**\n * What the screen of the bar offers the tabs inside it: bring a tab forward (a block picked on\n * the page opens «Blocks»), and read the page's context again (an edit changes the draft the\n * head counts).\n */\nexport interface SiteBarHost {\n show(name: string): void\n refresh(): Promise<void>\n}\n\nexport const siteBarKey: InjectionKey<SiteBarHost> = Symbol('wx-site-bar')\n\n/** The bar around a tab; null outside one — a tab drawn in a test or a demo. */\nexport function useSiteBar(): SiteBarHost | null {\n return inject(siteBarKey, null)\n}\n\n/**\n * «Publish» in the head of the bar: the record's draft on the site. `revision` is the one the bar\n * held; an edit made since is answered 409 with who made it, and nothing is published.\n */\nexport function publishSiteBarEntity(\n http: Http,\n apiPath: string,\n entity: { type: string; id: number | string; revision?: string | null },\n): Promise<unknown> {\n return http.post(`${apiPath}/site-bar/publish`, {\n type: entity.type,\n id: entity.id,\n ...(entity.revision ? { revision: entity.revision } : {}),\n })\n}\n\n/** «Discard»: the record's draft goes, and the record is what the site shows. */\nexport function discardSiteBarEntity(\n http: Http,\n apiPath: string,\n entity: { type: string; id: number | string },\n): Promise<unknown> {\n return http.post(`${apiPath}/site-bar/discard`, { type: entity.type, id: entity.id })\n}\n\n/**\n * Say something to the page the panel is framed in. Only to that page's own origin: the screen\n * may be opened on its own, or framed by somebody else, and neither should hear it.\n */\nexport function tellSitePage(message: SiteBarMessage | 'close'): void {\n if (typeof window === 'undefined' || window.parent === window) return\n\n const body = typeof message === 'string' ? { type: message } : message\n\n window.parent.postMessage({ wxBar: 1, ...body }, window.location.origin)\n}\n","<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, onMounted, provide, ref, watch } from 'vue'\nimport { useRoute } from 'vue-router'\nimport { confirm, toast, type BadgeType } from '@webx-ui/core'\nimport { useAdmin } from './admin'\nimport DateText from './DateText.vue'\nimport { useErrorText } from './errors'\nimport { HttpError } from './http'\nimport { useTranslate } from './i18n'\nimport {\n discardSiteBarEntity,\n fetchSiteBarContext,\n publishSiteBarEntity,\n siteBarKey,\n tellSitePage,\n type SiteBarContext,\n type SiteBarStatus,\n type SiteBarTab,\n} from './siteBar'\n\n/**\n * `/cms/_bar` — the panel on the site, as the drawer beside a page of the site shows it\n * (WEBX_UI_SITE_BAR.md, §5).\n *\n * The same panel as everywhere else, in a shell without its navigation: the page it is about is\n * the one beside it, and the way into the rest of the panel is «Open in panel», which leaves the\n * site for the record's own screen. The page is named by `?url=`, which the script on the site\n * writes from its own address, and its language by `?locale=`, from its `<html lang>`.\n *\n * «Page» is the frame's own tab; the others come from the modules (`siteBarTabs`), each drawn\n * only where the server listed it for this page.\n */\nconst admin = useAdmin()\nconst route = useRoute()\nconst t = useTranslate('webx-admin')\nconst errorText = useErrorText()\n\nconst context = ref<SiteBarContext | null>(null)\nconst loading = ref(true)\nconst error = ref<string | null>(null)\nconst tab = ref<string | number>('page')\n/* Set while the bar itself moves the tab — on reading the page — rather than the editor. */\nlet settling = false\n\nconst url = computed(() => (typeof route.query.url === 'string' ? route.query.url : '/'))\nconst locale = computed(() =>\n typeof route.query.locale === 'string' && route.query.locale !== '' ? route.query.locale : null,\n)\n\n/* The modules' tabs the server offered here, in its order. One it offered that no module on this\n side draws is left out rather than shown empty; a tab that stands for several sections is\n drawn once, where the first of them stands. */\nconst moduleTabs = computed<SiteBarTab[]>(() => {\n const known = (admin.modules ?? []).flatMap((module) => module.siteBarTabs ?? [])\n const drawn: SiteBarTab[] = []\n\n for (const name of context.value?.sections ?? []) {\n const tab = name === 'page' ? undefined : known.find((one) => namesOf(one).includes(name))\n\n if (tab && !drawn.includes(tab)) drawn.push(tab)\n }\n\n return drawn\n})\n\nfunction namesOf(tab: SiteBarTab): string[] {\n return [tab.name, ...(tab.sections ?? [])]\n}\n\n/* The tabs on screen by the names they answer to: «Page», then the modules'. */\nconst tabNames = computed(() => [\n ...(context.value?.sections.includes('page') ? ['page'] : []),\n ...moduleTabs.value.map((one) => one.name),\n])\n\n/* The tab the page remembered from the last time — the frame is made again on every page. */\nconst remembered = computed(() =>\n typeof route.query.tab === 'string' && route.query.tab !== '' ? route.query.tab : null,\n)\nconst entity = computed(() => context.value?.entity ?? null)\n\n/*\n * Out of the frame to the panel's own address, in a tab of its own: a second panel inside the\n * drawer is a panel nobody can work in, and taking the whole window would close the page the\n * editor came from — the one they are about to check the edit on.\n */\nconst panelHref = computed(() =>\n entity.value?.edit ? `${admin.basePath.replace(/\\/$/, '')}${entity.value.edit}` : null,\n)\n\nconst STATUS_TYPES: Record<SiteBarStatus, BadgeType> = {\n published: 'success',\n scheduled: 'info',\n unpublished: 'warning',\n trashed: 'danger',\n}\n\n/**\n * @param quiet Read again behind what is on screen — after an edit in a tab — rather than from\n * scratch: the tabs stay, and the one that is open stays open.\n */\nasync function load(quiet = false): Promise<void> {\n loading.value = !quiet\n error.value = null\n\n try {\n const body = await fetchSiteBarContext(admin.http, admin.apiPath, url.value, locale.value)\n\n context.value = body.data\n\n if (!quiet || !tabNames.value.includes(String(tab.value))) {\n const chosen =\n remembered.value !== null && tabNames.value.includes(remembered.value)\n ? remembered.value\n : (tabNames.value[0] ?? 'page')\n\n // Not a choice of the editor's, so not one for the page to remember.\n if (chosen !== tab.value) settling = true\n tab.value = chosen\n }\n } catch (caught) {\n if (quiet && context.value !== null) return\n\n context.value = null\n // A refusal is the server's to word — «nothing here you may edit»; anything else is ours.\n error.value =\n caught instanceof HttpError && caught.status === 403 && caught.message !== ''\n ? caught.message\n : t('site-bar.error')\n } finally {\n loading.value = false\n }\n}\n\nwatch(url, () => void load())\n\n// The page keeps the tab the editor chose in its `wx-bar` state (§5): the next page's frame opens\n// on it. A tab the bar fell back on — the remembered one is not on this page — is not a choice,\n// and the page keeps the one it had.\nwatch(tab, (name) => {\n if (settling) {\n settling = false\n\n return\n }\n\n tellSitePage({ type: 'tab', name: String(name) })\n})\n\nprovide(siteBarKey, {\n show: (name: string) => {\n const found =\n name === 'page' ? 'page' : moduleTabs.value.find((one) => namesOf(one).includes(name))?.name\n\n if (found && tabNames.value.includes(found)) tab.value = found\n },\n refresh: () => load(true),\n})\n\n/*\n * The draft's buttons are the record's (§9): there when its section said how it is published and\n * the administrator may, whoever left the draft. Without them the bar says the draft is there and\n * the way into the panel stays in the head.\n */\nconst draft = computed(() => (entity.value?.draft?.differs ? entity.value.draft : null))\nconst mayPublish = computed(() => draft.value !== null && entity.value?.can.publish === true)\nconst busy = ref(false)\n\nasync function publish(): Promise<void> {\n const found = entity.value\n\n if (!found || busy.value) return\n\n busy.value = true\n\n try {\n await publishSiteBarEntity(admin.http, admin.apiPath, found)\n // The page beside the bar is the site again, and shows what was just published.\n tellSitePage({ type: 'reload' })\n } catch (caught) {\n toast.danger(errorText(caught))\n // A 409 is somebody's edit the bar had not seen: the head counts it now.\n void load(true)\n } finally {\n busy.value = false\n }\n}\n\nasync function discard(): Promise<void> {\n const found = entity.value\n\n if (!found || busy.value) return\n\n const agreed = await confirm({\n title: t('site-bar.discard-title'),\n message: t('site-bar.discard-text'),\n confirmText: t('site-bar.discard'),\n cancelText: t('site-bar.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n busy.value = true\n\n try {\n await discardSiteBarEntity(admin.http, admin.apiPath, found)\n tellSitePage({ type: 'reload' })\n } catch (caught) {\n toast.danger(errorText(caught))\n } finally {\n busy.value = false\n }\n}\n\nfunction close(): void {\n tellSitePage('close')\n}\n\n// Esc inside the frame never reaches the page's own listener, so it is passed on.\nfunction onKeydown(event: KeyboardEvent): void {\n if (event.key === 'Escape' && !event.defaultPrevented) close()\n}\n\nonMounted(() => {\n void load()\n window.addEventListener('keydown', onKeydown)\n})\n\nonBeforeUnmount(() => window.removeEventListener('keydown', onKeydown))\n</script>\n\n<template>\n <div class=\"wx-site-bar\">\n <header class=\"wx-site-bar__head\">\n <div class=\"wx-site-bar__what\">\n <wx-text weight=\"semibold\" truncate class=\"wx-site-bar__title\">\n <template v-if=\"entity\">{{ entity.kind }} · {{ entity.label }}</template>\n <template v-else>{{ t('site-bar.title') }}</template>\n </wx-text>\n\n <div v-if=\"entity\" class=\"wx-site-bar__state\">\n <wx-badge :type=\"STATUS_TYPES[entity.status]\" dot size=\"sm\">\n {{ t(`site-bar.status-${entity.status}`) }}\n </wx-badge>\n <wx-text v-if=\"entity.updatedAt\" size=\"sm\" tone=\"muted\">\n {{ t('site-bar.edited') }} <DateText :value=\"entity.updatedAt\" size=\"sm\" tone=\"muted\" />\n </wx-text>\n </div>\n </div>\n\n <wx-action\n v-if=\"panelHref\"\n icon=\"external-link\"\n :title=\"t('site-bar.open-in-panel')\"\n :href=\"panelHref\"\n target=\"_blank\"\n rel=\"noopener\"\n />\n <wx-action icon=\"close\" :title=\"t('site-bar.close')\" @click=\"close\" />\n </header>\n\n <wx-alert\n v-if=\"draft && mayPublish\"\n type=\"warning\"\n class=\"wx-site-bar__draft\"\n :title=\"t('site-bar.changes', { count: draft.changes ?? 1 })\"\n :description=\"\n draft.mine === false && draft.by ? t('site-bar.draft-other', { name: draft.by }) : undefined\n \"\n >\n <template #actions>\n <wx-button size=\"sm\" type=\"primary\" :loading=\"busy\" @click=\"publish\">\n {{ t('site-bar.publish') }}\n </wx-button>\n <wx-button size=\"sm\" variant=\"outline\" :disabled=\"busy\" @click=\"discard\">\n {{ t('site-bar.discard') }}\n </wx-button>\n <!-- After a reload the page is the site again: the whole draft is in its preview. -->\n <wx-button\n v-if=\"entity?.preview\"\n size=\"sm\"\n variant=\"text\"\n :href=\"entity.preview\"\n target=\"_blank\"\n rel=\"noopener\"\n >\n {{ t('site-bar.preview') }}\n </wx-button>\n </template>\n </wx-alert>\n\n <wx-alert\n v-else-if=\"draft\"\n type=\"warning\"\n class=\"wx-site-bar__draft\"\n :title=\"draft.by ? t('site-bar.draft-by', { name: draft.by }) : t('site-bar.draft')\"\n >\n <template v-if=\"entity?.preview\" #actions>\n <wx-button size=\"sm\" variant=\"text\" :href=\"entity.preview\" target=\"_blank\" rel=\"noopener\">\n {{ t('site-bar.preview') }}\n </wx-button>\n </template>\n </wx-alert>\n\n <div v-if=\"loading\" class=\"wx-site-bar__body\">\n <wx-loading />\n </div>\n\n <div v-else-if=\"error !== null\" class=\"wx-site-bar__body\">\n <wx-result status=\"info\" :title=\"error\">\n <wx-button @click=\"load\">{{ t('site-bar.retry') }}</wx-button>\n </wx-result>\n </div>\n\n <!-- Kept alive: a half-typed form survives a look at another tab, and a block picked on the\n page reaches «Blocks» even while «Page» is in front. -->\n <!-- Segmented and stretched: three or four short words share the drawer's width, and a strip of\n underlined words read as a second heading under the record's. -->\n <wx-tabs\n v-else-if=\"context\"\n v-model=\"tab\"\n variant=\"pill\"\n size=\"md\"\n align=\"stretch\"\n keep-alive\n class=\"wx-site-bar__tabs\"\n >\n <wx-tab v-if=\"context.sections.includes('page')\" value=\"page\" :label=\"t('site-bar.tab-page')\">\n <div class=\"wx-site-bar__body\">\n <wx-descriptions v-if=\"entity\" :columns=\"1\" size=\"sm\">\n <wx-descriptions-item :label=\"t('site-bar.address')\">\n <wx-text size=\"sm\" class=\"wx-site-bar__url\">{{ context.url }}</wx-text>\n </wx-descriptions-item>\n <wx-descriptions-item :label=\"t('site-bar.status')\">\n {{ t(`site-bar.status-${entity.status}`) }}\n </wx-descriptions-item>\n </wx-descriptions>\n\n <wx-text v-else size=\"sm\" tone=\"muted\">{{ t('site-bar.no-entity') }}</wx-text>\n\n <wx-button\n v-if=\"panelHref\"\n type=\"primary\"\n block\n :href=\"panelHref\"\n target=\"_blank\"\n rel=\"noopener\"\n class=\"wx-site-bar__open\"\n >\n {{ t('site-bar.open-in-panel') }}\n </wx-button>\n </div>\n </wx-tab>\n\n <wx-tab v-for=\"one in moduleTabs\" :key=\"one.name\" :value=\"one.name\" :label=\"one.label(admin)\">\n <div class=\"wx-site-bar__body\">\n <component :is=\"one.component\" :context=\"context\" />\n </div>\n </wx-tab>\n </wx-tabs>\n </div>\n</template>\n\n<style scoped>\n/*\n * The whole of the frame, drawer-wide: 380px on a desktop, a phone's width on a phone. Spaced\n * like a dialog rather than like a screen of the panel — there is no sidebar here to line up\n * with.\n */\n.wx-site-bar {\n display: flex;\n flex-direction: column;\n min-height: 100vh;\n background: var(--wx-bg-surface);\n color: var(--wx-text-default);\n}\n\n.wx-site-bar__head {\n display: flex;\n align-items: flex-start;\n gap: var(--wx-space-4);\n padding: var(--wx-space-12) var(--wx-space-12) var(--wx-space-8) var(--wx-space-16);\n border-bottom: 1px solid var(--wx-border-default);\n}\n\n.wx-site-bar__what {\n display: flex;\n flex: 1 1 auto;\n flex-direction: column;\n gap: var(--wx-space-4);\n min-width: 0;\n}\n\n.wx-site-bar__state {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--wx-space-8);\n}\n\n.wx-site-bar__draft {\n margin: var(--wx-space-12) var(--wx-space-16) 0;\n}\n\n.wx-site-bar__tabs {\n padding: var(--wx-space-8) var(--wx-space-16) 0;\n}\n\n.wx-site-bar__body {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-16);\n padding: var(--wx-space-16) 0;\n}\n\n.wx-site-bar > .wx-site-bar__body {\n padding: var(--wx-space-16);\n}\n\n.wx-site-bar__url {\n word-break: break-all;\n}\n</style>\n","import { inject, reactive, type App, type InjectionKey } from 'vue'\nimport {\n applyTheme,\n systemTheme,\n watchSystemTheme,\n type Theme,\n type ThemePreference,\n} from '@webx-ui/tokens'\n\nexport type { Theme, ThemePreference }\n\nconst STORED_THEME = 'webx.theme'\n\nexport interface ThemeState {\n /** What was chosen: one of the two themes, or an instruction to follow the machine. */\n preference: ThemePreference\n /** What that works out to at this moment — what is actually on the screen. */\n resolved: Theme\n}\n\nexport interface ThemeController {\n readonly state: ThemeState\n /**\n * Choose, paint and remember in this browser.\n *\n * Writing the choice down against the person is somebody else's job: this package knows\n * nothing about accounts, and a panel with no sign-in still deserves a theme.\n */\n set(preference: ThemePreference): void\n /**\n * Take up what the administrator's record says, without writing anything back to it.\n *\n * `null` is an answer — \"follow the machine\" is what somebody chose, and it has to travel\n * between their machines like any other choice. `undefined` is not: it is a server that\n * has never heard of themes, and the browser's own choice should survive it.\n */\n adopt(preference: ThemePreference | null | undefined): void\n}\n\nexport const themeKey: InjectionKey<ThemeController> = Symbol('webx-theme')\n\nexport function useTheme(): ThemeController {\n const theme = inject(themeKey, null)\n\n if (theme === null) {\n throw new Error('useTheme() was called outside a panel created by createAdmin().')\n }\n\n return theme\n}\n\nexport function provideTheme(app: App, theme: ThemeController): void {\n app.provide(themeKey, theme)\n}\n\nexport interface CreateThemeOptions {\n /**\n * Where the choice is kept in this browser, or `null` to keep it nowhere. It is read before\n * the panel paints anything, so the sign-in screen is already the right colour — the copy\n * that follows somebody between machines is the one on their account.\n */\n storageKey?: string | null\n}\n\nexport function createTheme(options: CreateThemeOptions = {}): ThemeController {\n const key = options.storageKey === undefined ? STORED_THEME : options.storageKey\n\n const state = reactive<ThemeState>({\n preference: read(key) ?? 'system',\n resolved: 'light',\n })\n\n function paint(): void {\n applyTheme(state.preference)\n state.resolved = state.preference === 'system' ? systemTheme() : state.preference\n }\n\n paint()\n\n /*\n * The stylesheet follows the machine on its own — nothing here has to repaint. This is so\n * that anything *reading* `resolved`, to paint a canvas or hand an editor a colour scheme,\n * hears about a room whose lights just went out.\n */\n watchSystemTheme((theme) => {\n if (state.preference === 'system') {\n state.resolved = theme\n }\n })\n\n return {\n state,\n set(preference) {\n state.preference = preference\n paint()\n remember(key, preference)\n },\n adopt(preference) {\n if (preference === undefined) {\n return\n }\n\n const chosen = preference ?? 'system'\n\n if (chosen === state.preference) {\n return\n }\n\n state.preference = chosen\n paint()\n remember(key, chosen)\n },\n }\n}\n\nfunction remember(key: string | null, preference: ThemePreference): void {\n if (key === null) {\n return\n }\n\n try {\n localStorage.setItem(key, preference)\n } catch {\n // Private windows, blocked site data. A theme is not worth an error.\n }\n}\n\nfunction read(key: string | null): ThemePreference | null {\n if (key === null) {\n return null\n }\n\n try {\n const stored = localStorage.getItem(key)\n\n return stored === 'light' || stored === 'dark' || stored === 'system' ? stored : null\n } catch {\n return null\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 {\n dateLocaleKey,\n dateTimezoneKey,\n localesKey,\n WebxUI,\n type LocaleOption,\n} from '@webx-ui/core'\nimport AdminLanding from './AdminLanding.vue'\nimport AdminNav from './AdminNav.vue'\nimport AdminNotFound from './AdminNotFound.vue'\nimport AdminShell from './AdminShell.vue'\nimport SiteBarScreen from './SiteBarScreen.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 { createTheme, provideTheme, type ThemeController } from './theme'\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 /** Light, dark or the machine's — applied before anything is drawn. */\n theme: ThemeController\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 // The root, unless the panel brought its own — a project with a dashboard has already\n // answered this question and should not be overruled.\n if (!routes.some((route) => route.path === '/')) {\n const landing = modules.find((module) => module.landing === true)\n\n routes.push({\n path: '/',\n name: 'webx.home',\n component: AdminLanding,\n props: { landing: landing?.path ?? landing?.routes?.[0]?.path ?? null },\n })\n }\n\n /*\n * The panel on the site (WEBX_UI_SITE_BAR.md): the drawer a page of the site frames. A route\n * of the same app rather than an app of its own, so it has the session, the words, the types\n * of fields and every module's tabs without a second bundle — the shell only leaves out its\n * navigation for it (`bare`).\n */\n routes.push({\n path: '/_bar',\n name: 'webx.site-bar',\n component: SiteBarScreen,\n meta: { bare: true },\n })\n\n // An address nothing answers for says so, instead of leaving the content area empty — unless\n // the project brought its own catch-all. Matched last whatever the order: the router ranks a\n // catch-all below every other route, including those a plugin adds later.\n if (!routes.some((route) => route.path.includes('(.*)'))) {\n routes.push({ path: '/:pathMatch(.*)*', name: 'webx.not-found', component: AdminNotFound })\n }\n\n const router = createRouter({\n history: createWebHistory(basePath),\n routes,\n })\n\n const i18n = createI18n({ locale: options.locale ?? preferredLocale() })\n\n /*\n * Made here rather than after mounting, because making it is what paints: the sign-in\n * screen is the first thing anybody sees and it should already be the colour they left the\n * panel in. What this browser remembers is the guess; the administrator's own record\n * replaces it the moment the session says who they are.\n */\n const theme = createTheme()\n\n const http =\n options.http ??\n createHttp({\n baseUrl: '',\n csrfUrl: `${apiPath}/auth/csrf-cookie`,\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 /*\n * A dictionary is asked for once per language and visit: the words do not change while the\n * panel is open, and switching back to a language already read should not be a request. A\n * failed one is forgotten, so the next switch tries again.\n */\n const dictionaries = new Map<\n string,\n Promise<{ data: { locale: string; fallback: string; namespaces: Dictionary } }>\n >()\n\n async function loadDictionary(locale: string): Promise<void> {\n let pending = dictionaries.get(locale)\n\n if (pending === undefined) {\n pending = http.get<{\n data: { locale: string; fallback: string; namespaces: Dictionary }\n }>(`${apiPath}/translations/${locale}`)\n pending.catch(() => dictionaries.delete(locale))\n dictionaries.set(locale, pending)\n }\n\n const body = await pending\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 theme,\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 provideTheme(app, theme)\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 /*\n * A calendar is drawn in the language of the panel, not of the browser: the picker\n * would otherwise head a Russian screen with \"Sep 2026\" while every other date on it\n * goes through `useDates()` and reads Russian.\n */\n app.provide(\n dateLocaleKey,\n computed(() => i18n.state.locale),\n )\n\n /*\n * A moment is shown and entered on the site's clock, not the editor's: an event at 09:30 in\n * the site's zone read 04:30 to an editor abroad, and their pick went back with their own\n * offset. Until the manifest arrives there is no form to draw anyway.\n */\n app.provide(\n dateTimezoneKey,\n computed(() => context.state.manifest?.timezone),\n )\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 theme,\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","/** The forms a counted line is written in. Languages without `few` or `many` repeat `other`. */\nexport type PluralForm = 'one' | 'few' | 'many' | 'other'\n\n/**\n * Which form of a counted line to use for `count` in `locale`.\n *\n * The panel has no plural syntax of its own, so a line that has to carry its number up front —\n * \"5 pages are deleted\" — is written once per CLDR category and picked here. `zero` and `two`\n * fall back on `other`: none of the panel's languages writes them differently.\n */\nexport function pluralForm(count: number, locale: string): PluralForm {\n let form: Intl.LDMLPluralRule\n\n try {\n form = new Intl.PluralRules(locale).select(count)\n } catch {\n // A locale the browser does not know: English rules are the least wrong guess.\n form = new Intl.PluralRules('en').select(count)\n }\n\n return form === 'one' || form === 'few' || form === 'many' ? form : 'other'\n}\n","import { onBeforeUnmount, onMounted } from 'vue'\n\n/**\n * Hands an editor the keys pressed while nothing on the page has focus.\n *\n * An editor listens with `@keydown` on its own root, so Ctrl+S reaches it from any field inside.\n * A click on empty space — the page around the form, a label, a gap between cards — moves focus\n * to `<body>`, and from there the key never passes through the editor: the browser offered to\n * save the page as HTML instead. Only keys from `<body>` are passed on; one pressed in a dialog or\n * a menu belongs to that, and the editor's own root still gets everything from inside it.\n */\nexport function useBodyKeys(handler: (event: KeyboardEvent) => void): void {\n function listener(event: KeyboardEvent): void {\n if (event.target === document.body || event.target === document.documentElement) handler(event)\n }\n\n onMounted(() => window.addEventListener('keydown', listener))\n onBeforeUnmount(() => window.removeEventListener('keydown', listener))\n}\n","/**\n * Two edits of one record, merged against the version both started from.\n *\n * An editor holds three versions when its save is refused: what it opened with (base), what is\n * in the form now (mine) and what the record is now (theirs). A field only one side changed is\n * not a question — it goes in from whichever side changed it. Only a field both sides changed,\n * to different values, is a conflict, and only a conflict is put to the person.\n *\n * The values are the form's model, so the walk knows three shapes:\n *\n * - a plain object — the fields of a screen, the values of a block, the languages of a localized\n * field — merged key by key, so that two people writing two languages of one title do not\n * collide;\n * - a list of blocks (objects with a string `key`) — merged block by block: one side adding,\n * removing or moving a block is applied by key, and a block one side removed while the other\n * edited it is a conflict of its own;\n * - anything else — a string, a number, a list of ids — as a whole value.\n *\n * Nothing here knows what a page is: the same walk merges a service, a recipe or a region.\n */\n\nexport type MergeChoice = 'mine' | 'theirs'\n\n/** One step of the way to a value: a field (or a language) by name, or a block by key. */\nexport type MergeSegment = { field: string } | { block: string; type: string }\n\nexport interface MergeConflict {\n /** Stable for one place in the tree, so a choice survives a merge being run again. */\n id: string\n path: MergeSegment[]\n /**\n * `changed` — both sides wrote the value; `removed-mine` — this editor removed a block the\n * other side edited; `removed-theirs` — the other side removed a block this editor edited.\n */\n kind: 'changed' | 'removed-mine' | 'removed-theirs'\n base: unknown\n mine: unknown\n theirs: unknown\n}\n\nexport interface MergeResult<T> {\n /** The merge, with every conflict settled by its choice — this editor's side when there is none. */\n value: T\n conflicts: MergeConflict[]\n}\n\ntype Plain = Record<string, unknown>\n\ninterface Node extends Plain {\n key: string\n type?: unknown\n}\n\ninterface Walk {\n choices: Record<string, MergeChoice>\n conflicts: MergeConflict[]\n}\n\n/** Deleted on one side: what a walk answers to drop the key or the block. */\nconst GONE = undefined\n\n/**\n * Merge `mine` and `theirs` against `base`.\n *\n * `choices` settles conflicts by id; one with no choice keeps this editor's side in `value` and\n * is listed in `conflicts` all the same, so the caller can tell a clean merge from a settled one.\n */\nexport function mergeThreeWay<T>(\n base: T,\n mine: T,\n theirs: T,\n choices: Record<string, MergeChoice> = {},\n): MergeResult<T> {\n const walk: Walk = { choices, conflicts: [] }\n\n return { value: merge(base, mine, theirs, [], walk) as T, conflicts: walk.conflicts }\n}\n\n/**\n * Where `after` differs from `before`, as paths — what the other side changed, said to the\n * person before they pull it in. A block added or removed is one path, not one per field.\n */\nexport function changedPaths(before: unknown, after: unknown): MergeSegment[][] {\n const found: MergeSegment[][] = []\n\n diff(before, after, [], found)\n\n return found\n}\n\n/** Equal as values: key order, and `null` against a missing key, do not count as a difference. */\nexport function sameValue(a: unknown, b: unknown): boolean {\n if (a === b) return true\n if (a == null || b == null) return a == null && b == null\n\n if (Array.isArray(a) || Array.isArray(b)) {\n if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false\n\n return a.every((item, index) => sameValue(item, b[index]))\n }\n\n if (isPlain(a) && isPlain(b)) {\n const keys = new Set([...Object.keys(a), ...Object.keys(b)])\n\n for (const key of keys) {\n if (!sameValue(a[key], b[key])) return false\n }\n\n return true\n }\n\n return false\n}\n\nexport function conflictId(path: MergeSegment[]): string {\n return path.map((step) => ('block' in step ? `#${step.block}` : step.field)).join('/')\n}\n\nfunction merge(\n base: unknown,\n mine: unknown,\n theirs: unknown,\n path: MergeSegment[],\n walk: Walk,\n): unknown {\n if (sameValue(mine, theirs)) return mine\n if (sameValue(base, mine)) return theirs\n if (sameValue(base, theirs)) return mine\n\n if (isNodeList(mine) && isNodeList(theirs) && (base == null || isNodeList(base))) {\n return mergeList(base ?? [], mine, theirs, path, walk)\n }\n\n if (isPlain(mine) && isPlain(theirs) && (base == null || isPlain(base))) {\n return mergeObject(base ?? {}, mine, theirs, path, walk)\n }\n\n return settle(walk, path, 'changed', base, mine, theirs, mine, theirs)\n}\n\nfunction mergeObject(\n base: Plain,\n mine: Plain,\n theirs: Plain,\n path: MergeSegment[],\n walk: Walk,\n): Plain {\n const merged: Plain = {}\n const keys = [...Object.keys(mine), ...Object.keys(theirs).filter((key) => !(key in mine))]\n\n for (const key of keys) {\n const value = merge(base[key], mine[key], theirs[key], [...path, { field: key }], walk)\n\n if (value !== GONE) merged[key] = value\n }\n\n return merged\n}\n\nfunction mergeList(\n base: Node[],\n mine: Node[],\n theirs: Node[],\n path: MergeSegment[],\n walk: Walk,\n): Node[] {\n const before = byKey(base)\n const ours = byKey(mine)\n const other = byKey(theirs)\n const kept = new Map<string, Node>()\n\n for (const key of new Set([...ours.keys(), ...other.keys()])) {\n const b = before.get(key)\n const m = ours.get(key)\n const t = other.get(key)\n const at: MergeSegment[] = [...path, { block: key, type: typeOf(m ?? t) }]\n let value: unknown\n\n if (m && t) {\n value = merge(b, m, t, at, walk)\n } else if (m) {\n // Theirs has no such block: this editor added it, they removed it untouched, or they\n // removed a block this editor was editing — the one case that is a question.\n if (b === undefined) value = m\n else if (sameValue(b, m)) value = GONE\n else value = settle(walk, at, 'removed-theirs', b, m, GONE, m, GONE)\n } else if (t) {\n if (b === undefined) value = t\n else if (sameValue(b, t)) value = GONE\n else value = settle(walk, at, 'removed-mine', b, GONE, t, GONE, t)\n }\n\n if (value !== GONE) kept.set(key, value as Node)\n }\n\n // The order of whichever side moved something; this editor's when both did, or neither.\n const theirsMoved = !sameOrder(base, theirs)\n const mineMoved = !sameOrder(base, mine)\n const [lead, follow] = theirsMoved && !mineMoved ? [theirs, mine] : [mine, theirs]\n\n return order(lead, follow, kept)\n}\n\n/** Lay the kept blocks out in the lead's order, and slot the others in after their neighbour. */\nfunction order(lead: Node[], follow: Node[], kept: Map<string, Node>): Node[] {\n const keys = lead.map((node) => node.key).filter((key) => kept.has(key))\n const placed = new Set(keys)\n\n follow.forEach((node, index) => {\n if (placed.has(node.key) || !kept.has(node.key)) return\n\n let at = 0\n\n for (let back = index - 1; back >= 0; back--) {\n const position = keys.indexOf(follow[back]!.key)\n\n if (position !== -1) {\n at = position + 1\n break\n }\n }\n\n keys.splice(at, 0, node.key)\n placed.add(node.key)\n })\n\n return keys.map((key) => kept.get(key)!)\n}\n\n/** Record a conflict and answer the side its choice names — this editor's when there is none. */\nfunction settle(\n walk: Walk,\n path: MergeSegment[],\n kind: MergeConflict['kind'],\n base: unknown,\n mine: unknown,\n theirs: unknown,\n keepMine: unknown,\n keepTheirs: unknown,\n): unknown {\n const id = conflictId(path)\n\n walk.conflicts.push({ id, path, kind, base, mine, theirs })\n\n return walk.choices[id] === 'theirs' ? keepTheirs : keepMine\n}\n\nfunction diff(\n before: unknown,\n after: unknown,\n path: MergeSegment[],\n found: MergeSegment[][],\n): void {\n if (sameValue(before, after)) return\n\n if (isNodeList(before) && isNodeList(after)) {\n const old = byKey(before)\n const now = byKey(after)\n\n for (const key of new Set([...old.keys(), ...now.keys()])) {\n const at: MergeSegment[] = [\n ...path,\n { block: key, type: typeOf(now.get(key) ?? old.get(key)) },\n ]\n\n if (old.has(key) && now.has(key)) diff(old.get(key), now.get(key), at, found)\n else found.push(at)\n }\n\n // Moved without anything else changing: the list itself is what changed.\n if (!sameOrder(before, after)) found.push(path)\n\n return\n }\n\n if (isPlain(before) && isPlain(after)) {\n for (const key of new Set([...Object.keys(before), ...Object.keys(after)])) {\n diff(before[key], after[key], [...path, { field: key }], found)\n }\n\n return\n }\n\n found.push(path)\n}\n\n/** Whether the blocks both sides still have stand in the same order. */\nfunction sameOrder(a: Node[], b: Node[]): boolean {\n const inB = new Set(b.map((node) => node.key))\n const inA = new Set(a.map((node) => node.key))\n const left = a.map((node) => node.key).filter((key) => inB.has(key))\n const right = b.map((node) => node.key).filter((key) => inA.has(key))\n\n return left.every((key, index) => key === right[index])\n}\n\nfunction byKey(list: Node[]): Map<string, Node> {\n return new Map(list.map((node) => [node.key, node]))\n}\n\nfunction typeOf(node: Node | undefined): string {\n return typeof node?.type === 'string' ? node.type : ''\n}\n\nfunction isPlain(value: unknown): value is Plain {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction isNodeList(value: unknown): value is Node[] {\n return (\n Array.isArray(value) && value.every((item) => isPlain(item) && typeof item.key === 'string')\n )\n}\n","import { onMounted, shallowRef } from 'vue'\nimport { useLocales } from '@webx-ui/core'\nimport type { ScreenNode } from '@webx-ui/schema'\nimport { useAdmin } from './admin'\nimport { useTranslate } from './i18n'\nimport type { MergeSegment } from './merge'\nimport type { BlockLabels } from './types'\n\nexport interface EditingLabelsOptions {\n /** The screen the record is edited on: its fields are named the way the form names them. */\n screen?: string\n /** What a top-level field is called, when the editor knows better than the screen. */\n field?: (name: string) => string | undefined\n /** What a type of block is called, when the editor knows better than the block library. */\n block?: (type: string) => string | undefined\n}\n\nexport interface EditingLabels {\n /** A place in the record as the person reads it: «Hero › Eyebrow · EN». */\n label(path: MergeSegment[]): string\n}\n\n/**\n * Names for the places in a record, the way the person who edits it knows them.\n *\n * A notice that says «Hero › Below cta · EN» has turned `below_cta` into words by guessing; the\n * block type calls that field «Text below the button», and that is the name the person looks for\n * in the form. The screen names the record's own fields, the block library names the blocks and\n * their fields, and a key nobody named is turned into words as a last resort.\n */\nexport function useEditingLabels(options: EditingLabelsOptions = {}): EditingLabels {\n const admin = useAdmin()\n const t = useTranslate('webx-admin')\n const locales = useLocales()\n\n const fields = shallowRef<Map<string, string>>(new Map())\n const blocks = shallowRef<BlockLabels | null>(null)\n\n onMounted(() => {\n if (options.screen && typeof admin.loadScreen === 'function') {\n admin\n .loadScreen(options.screen)\n .then((root) => (fields.value = screenLabels(root)))\n .catch(() => undefined)\n }\n\n // A hand-made context in a test, or a panel without the block library: keys turned into words.\n admin\n .blockLabels?.()\n .then((labels) => (blocks.value = labels))\n .catch(() => undefined)\n })\n\n function blockName(type: string): string {\n return options.block?.(type) ?? blocks.value?.type(type) ?? humanize(type || t('editing.block'))\n }\n\n function label(path: MergeSegment[]): string {\n const codes = new Set(locales.list.value.map((locale) => locale.code))\n const parts: string[] = []\n let language = ''\n // The type of the block whose own field this step may be: set on a block, kept over `values`.\n let inBlock: string | null = null\n\n path.forEach((step, index) => {\n if ('block' in step) {\n parts.push(blockName(step.type))\n inBlock = step.type\n\n return\n }\n\n // A block's fields sit under `values`, which is the shape of a node and not a word anybody\n // would recognise.\n if (step.field === 'values' && index > 0 && 'block' in path[index - 1]!) return\n\n // Nor is the field that holds a list of blocks: «Hero» says where it is, «Blocks › Hero»\n // only says it twice.\n const next = path[index + 1]\n\n if (next !== undefined && 'block' in next) return\n\n if (index === path.length - 1 && index > 0 && codes.has(step.field)) {\n language = step.field.toUpperCase()\n\n return\n }\n\n const owner = inBlock\n inBlock = null\n\n const named =\n owner !== null\n ? blocks.value?.field(owner, step.field)\n : index === 0\n ? (options.field?.(step.field) ?? fields.value.get(step.field))\n : undefined\n\n parts.push(named ?? humanize(step.field))\n })\n\n const named = parts.length === 0 ? t('editing.order') : parts.join(' › ')\n\n return language === '' ? named : `${named} · ${language}`\n }\n\n return { label }\n}\n\n/** Every named field of a screen tree with its label, wherever it sits — in a tab, in a card. */\nexport function screenLabels(root: ScreenNode[]): Map<string, string> {\n const found = new Map<string, string>()\n\n const walk = (nodes: ScreenNode[]): void => {\n for (const node of nodes) {\n // A block's schema keys its values by `id` where a screen names them; both are read.\n const name = (node as { name?: unknown }).name ?? (node as { id?: unknown }).id\n const label = (node as { label?: unknown }).label\n\n if (typeof name === 'string' && typeof label === 'string' && label !== '' && !found.has(name))\n found.set(name, label)\n\n if (Array.isArray(node.children)) walk(node.children)\n }\n }\n\n walk(root)\n\n return found\n}\n\nexport function humanize(name: string): string {\n const words = name.replace(/[_-]+/g, ' ').trim()\n\n return words.charAt(0).toUpperCase() + words.slice(1)\n}\n","import {\n computed,\n onBeforeUnmount,\n onMounted,\n ref,\n shallowRef,\n watch,\n type ComputedRef,\n type Ref,\n type ShallowRef,\n} from 'vue'\nimport { confirm, toast } from '@webx-ui/core'\nimport { useAdmin } from './admin'\nimport { useDates } from './dates'\nimport { humanize, useEditingLabels } from './editingLabels'\nimport { useTranslate } from './i18n'\nimport {\n changedPaths,\n mergeThreeWay,\n sameValue,\n type MergeChoice,\n type MergeConflict,\n type MergeSegment,\n} from './merge'\n\n/** Who wrote a record last, through which door, and when — the server's `changed`. */\nexport interface EditingChange {\n author: string | null\n author_id: number | null\n /** `panel`, `mcp` (an agent) or `import`. */\n source: string\n at: string | null\n}\n\n/** Somebody else who has the record open in the panel right now. */\nexport interface EditingEditor {\n id: number\n name: string\n since: string\n seen_at: string\n}\n\n/** Where the record stands, as the heartbeat reports it. */\nexport interface EditingState {\n status: string\n has_draft: boolean\n published_at: string | null\n trashed: boolean\n deleted_at: string | null\n}\n\n/**\n * Something that happened to the record besides its content: published, taken off the site, its\n * draft thrown away, an old version put back, moved, put in the bin or taken out of it.\n */\nexport interface EditingEvent {\n id: number\n kind:\n | 'published'\n | 'unpublished'\n | 'discarded'\n | 'restored_version'\n | 'moved'\n | 'trashed'\n | 'restored'\n | 'purged'\n | string\n author: string | null\n author_id: number | null\n source: string\n at: string\n detail: Record<string, unknown> | null\n}\n\n/** One copy of the draft, as `editing/{entity}/{id}/drafts` lists it. */\nexport interface DraftCopy {\n id: number\n kind: 'autosave' | 'overwritten'\n created_at: string | null\n author: string | null\n source: string\n fields: string[]\n /** What this copy changed against the one before it. */\n paths?: MergeSegment[][]\n}\n\n/** The record as the server has it now. */\nexport interface EditingVersion<T> {\n values: T\n revision: string\n changed?: EditingChange | null\n}\n\nexport interface EditingConflict<T> {\n /** The places both sides changed, each with its three values. */\n conflicts: MergeConflict[]\n /** The side each one is settled to; this editor's until somebody picks. */\n choices: Record<string, MergeChoice>\n theirs: EditingVersion<T>\n}\n\n/** Somebody else saved while this editor is open, and it has not been pulled in yet. */\nexport interface EditingIncoming<T> {\n theirs: EditingVersion<T>\n paths: MergeSegment[][]\n}\n\nexport interface EditingOptions<T> {\n /** The record's name on the server's `editing/{entity}/{id}`: `pages`, `services`, `regions`. */\n entity: string\n id: () => string | number | null | undefined\n /** The form: what this editor has now. */\n values: Ref<T>\n /** The revision the form was read at; a merge moves it to the one it merged with. */\n revision: Ref<string>\n /** Read the record again, for the notice's «Pull in». */\n read: () => Promise<EditingVersion<T>>\n /**\n * The server's version was taken in as the new base — after a merge, a pull or «Take theirs».\n * The editor moves its own idea of \"saved\" there: whatever differs from it now is unsaved.\n */\n adopt?: (theirs: EditingVersion<T>) => void\n /**\n * Somebody published, unpublished, moved or restored the record: read what the editor shows\n * around the form again — the status, the trail, the address — without touching the form.\n */\n refresh?: () => void | Promise<void>\n /** Take the record out of the bin. Without it, the notice of the bin offers no way back. */\n restore?: () => Promise<void>\n /** Whether this editor writes at all; a reader is not asked about anybody's changes. */\n canWrite?: () => boolean\n /** Whether a save is on its way: a heartbeat answered meanwhile may be reporting it. */\n busy?: () => boolean\n /** The screen the record is edited on, so its fields are named the way the form names them. */\n screen?: string\n /** What a top-level field is called on screen, for the conflict list. */\n field?: (name: string) => string | undefined\n /** What a type of block is called. */\n block?: (type: string) => string | undefined\n /** Seconds between heartbeats; `false` for none (a test, a screen that is not an editor). */\n heartbeat?: number | false\n}\n\nexport interface Editing<T> {\n /** What the form was opened with, or last saved — the common ancestor of a merge. */\n base: ShallowRef<T>\n conflict: Ref<EditingConflict<T> | null>\n incoming: Ref<EditingIncoming<T> | null>\n editors: Ref<EditingEditor[]>\n /** Where the record stands, by the last heartbeat. */\n state: Ref<EditingState | null>\n /** What others did to the record since this editor opened it, not yet acknowledged. */\n events: Ref<EditingEvent[]>\n /** The record is in the bin. */\n trashed: ComputedRef<boolean>\n /** Who put it there and when, when the server knows. */\n trashedBy: ComputedRef<EditingEvent | null>\n /**\n * The record was deleted for good: who did it and when. Nothing can be saved or restored —\n * the form keeps what it holds, to be copied out with `copyText`.\n */\n gone: Ref<EditingEvent | null>\n /** In the bin or gone: autosave waits, and a save would only fail. */\n stopped: ComputedRef<boolean>\n /**\n * Why Save and Publish are off, for their `title`: «This is in the bin», «This was deleted for\n * good». `undefined` while they are not — a disabled button with no reason looks frozen.\n */\n blocked: ComputedRef<string | undefined>\n /** Whether the form holds something the server does not. */\n unsaved: ComputedRef<boolean>\n /** Whether this editor can take the record out of the bin. */\n canRestore: () => boolean\n /** Call with what the form now matches on the server: on opening, and after each save. */\n opened(values: T): void\n /**\n * A save was refused because the record moved. Merges it with what is in the form: `true`\n * when nothing overlapped — the form holds both edits and the editor saves it again — and\n * `false` when a conflict is waiting for the person.\n */\n refused(theirs: EditingVersion<T>): boolean\n /** Pull the other side's save in before saving. Same answer as `refused`. */\n pull(): Promise<boolean>\n choose(id: string, choice: MergeChoice): void\n /** Settle the conflict by the choices; the editor saves next. */\n resolve(): void\n /** Settle every conflict their way at once. */\n resolveTheirs(): void\n /**\n * Before publishing: whether the draft on the server is still the one this editor holds. When\n * somebody else changed it, the person is told whose changes, where and when, and chooses to\n * publish with them or to review them first — which pulls them into the form. Answers the\n * revision to publish with, and whether the person was already asked; `null` to stop.\n */\n beforePublish(): Promise<{ revision: string; asked: boolean } | null>\n /**\n * A save or a publication failed. `true` when this was about other people — the record is in\n * the bin, or a publication found a draft it had not seen — and a notice says so; the editor\n * then shows nothing of its own.\n */\n failed(error: unknown): Promise<boolean>\n /** Out of the bin. The form keeps what it holds; saving it is the caller's next step. */\n restoreFromBin(): Promise<boolean>\n /** What the form holds, as text a person can paste somewhere else: each field under its name. */\n text(): string\n /** That text onto the clipboard, said with a toast either way. */\n copyText(): Promise<boolean>\n /** The events have been read. */\n dismissEvents(): void\n /** Ask the server now rather than at the next heartbeat. */\n check(): Promise<void>\n /** A place in the record as the person reads it: «Hero › Heading · EN». */\n label(path: MergeSegment[]): string\n /** What a list of changed places says: «Hero › Heading, Title · EN and more». */\n places(paths: MergeSegment[][]): string\n /** What happened, in a sentence: «Owner restored version 21 and published · 16:40». */\n describe(events: EditingEvent[]): string\n /** A value, short, as text. */\n preview(value: unknown): string\n /** Who changed it: «Anna», «Anna, through an agent». */\n who(changed: EditingChange | null | undefined): string\n}\n\n/** Heartbeat answer. */\ninterface Ping {\n revision: string\n changed: EditingChange | null\n editors: EditingEditor[]\n state?: EditingState | null\n place?: Record<string, unknown> | null\n events?: EditingEvent[]\n heartbeat: number\n}\n\n/** Events that change the content: a notice of incoming changes says these instead of fields. */\nconst EXPLAINING = new Set(['restored_version', 'discarded'])\n\n/**\n * What every drafted editor does about other people editing the same record (§ «Two editors»):\n * merges a refused save instead of asking, asks only about a field both sides changed, says who\n * the other side was and through which door, and keeps an ear on the server while open — who\n * else has the record, whether somebody saved in the meantime, and what they did to it besides\n * saving: published it, moved it, put it in the bin.\n *\n * The editor keeps its own save loop; this is the part that is the same in all of them.\n */\nexport function useEditing<T>(options: EditingOptions<T>): Editing<T> {\n const admin = useAdmin()\n const t = useTranslate('webx-admin')\n const dates = useDates()\n const labels = useEditingLabels({\n screen: options.screen,\n field: options.field,\n block: options.block,\n })\n\n const base = shallowRef<T>(clone(options.values.value))\n const conflict = ref<EditingConflict<T> | null>(null) as Ref<EditingConflict<T> | null>\n const incoming = ref<EditingIncoming<T> | null>(null) as Ref<EditingIncoming<T> | null>\n const editors = ref<EditingEditor[]>([])\n const state = ref<EditingState | null>(null)\n const place = ref<Record<string, unknown> | null>(null)\n const events = ref<EditingEvent[]>([])\n /** Every event the server still keeps, this editor's own included: who put it in the bin. */\n const recent = ref<EditingEvent[]>([])\n let changedLast: EditingChange | null = null\n\n const canWrite = (): boolean => options.canWrite?.() ?? true\n\n const trashed = computed(() => state.value?.trashed === true)\n const gone = ref<EditingEvent | null>(null)\n const stopped = computed(() => trashed.value || gone.value !== null)\n const blocked = computed(() =>\n gone.value !== null\n ? t('editing.blocked-purged')\n : trashed.value\n ? t('editing.blocked-trashed')\n : undefined,\n )\n const trashedBy = computed(\n () => [...recent.value].reverse().find((event) => event.kind === 'trashed') ?? null,\n )\n const unsaved = computed(() => !sameValue(options.values.value, base.value))\n\n /*\n * Whether the editor has said what it opened. Until it has, its revision and its form are\n * whatever it started with — empty — and a heartbeat answered in that moment would read the\n * record and offer all of it as somebody else's change, or, worse, take the record's revision\n * for one the editor held. A heartbeat before then only says the editor is here.\n */\n let ready = false\n let mounted = false\n\n function opened(values: T): void {\n base.value = clone(values)\n incoming.value = null\n\n if (!ready) {\n ready = true\n\n // The heartbeat that went out before the record was in the form could not compare\n // anything: ask again now, rather than twenty seconds from now.\n if (mounted && options.heartbeat !== false) void beat()\n }\n }\n\n /** Take the server's version in as the base, with the form holding `merged`. */\n function take(theirs: EditingVersion<T>, merged: T): void {\n options.revision.value = theirs.revision\n base.value = clone(theirs.values)\n options.adopt?.(theirs)\n options.values.value = clone(merged)\n incoming.value = null\n // What explained the change is in the form now — and the notice that explained it said every\n // event it had, so none of them is news any more.\n if (events.value.some((event) => EXPLAINING.has(event.kind))) events.value = []\n }\n\n function settleWith(theirs: EditingVersion<T>): boolean {\n const { value, conflicts } = mergeThreeWay(base.value, options.values.value, theirs.values)\n\n if (conflicts.length === 0) {\n take(theirs, value)\n conflict.value = null\n\n return true\n }\n\n conflict.value = { conflicts, choices: {}, theirs }\n incoming.value = null\n\n return false\n }\n\n function refused(theirs: EditingVersion<T>): boolean {\n const clean = settleWith(theirs)\n\n if (clean) toast.info(t('editing.merged', { who: who(theirs.changed) }))\n\n return clean\n }\n\n async function pull(): Promise<boolean> {\n const theirs = incoming.value?.theirs ?? (await options.read())\n\n return settleWith(theirs)\n }\n\n function choose(id: string, choice: MergeChoice): void {\n if (conflict.value)\n conflict.value = { ...conflict.value, choices: { ...conflict.value.choices, [id]: choice } }\n }\n\n function resolve(): void {\n const open = conflict.value\n\n if (!open) return\n\n const { value } = mergeThreeWay(\n base.value,\n options.values.value,\n open.theirs.values,\n open.choices,\n )\n\n take(open.theirs, value)\n conflict.value = null\n }\n\n function resolveTheirs(): void {\n const open = conflict.value\n\n if (!open) return\n\n open.conflicts.forEach((one) => choose(one.id, 'theirs'))\n resolve()\n }\n\n function who(changed: EditingChange | null | undefined): string {\n const name = changed?.author ?? t('editing.somebody')\n\n if (changed?.source === 'mcp') return t('editing.via-agent', { name })\n if (changed?.source === 'import') return t('editing.via-import', { name })\n\n return name\n }\n\n function when(at: string | null | undefined): string {\n return at ? dates.short(at) : ''\n }\n\n function places(paths: MergeSegment[][]): string {\n const named = [...new Set(paths.map((path) => labels.label(path)))]\n const what = named.slice(0, 3).join(', ') || t('editing.order')\n\n return named.length > 3 ? t('editing.and-more', { what }) : what\n }\n\n /** One thing that happened, as a verb phrase: «restored version 21», «moved it to /contact/». */\n function deed(event: EditingEvent): string {\n const detail = event.detail ?? {}\n\n switch (event.kind) {\n case 'restored_version':\n return typeof detail.number === 'number'\n ? t('editing.event-restored-version', { number: detail.number })\n : t('editing.event-restored-draft')\n case 'moved': {\n const path = mainPath()\n\n return path === null ? t('editing.event-moved') : t('editing.event-moved-to', { path })\n }\n default:\n return t(`editing.event-${event.kind.replace(/_/g, '-')}`)\n }\n }\n\n function describe(list: EditingEvent[]): string {\n const lines: string[] = []\n let i = 0\n\n // One sentence per person in a row: «Owner restored version 21 and published», not two.\n while (i < list.length) {\n const first = list[i]!\n const deeds: string[] = []\n let last = first\n\n while (\n i < list.length &&\n list[i]!.author_id === first.author_id &&\n list[i]!.source === first.source\n ) {\n last = list[i]!\n const phrase = deed(last)\n\n if (deeds.at(-1) !== phrase) deeds.push(phrase)\n i++\n }\n\n const done =\n deeds.length === 1\n ? deeds[0]!\n : `${deeds.slice(0, -1).join(', ')} ${t('editing.and')} ${deeds.at(-1)}`\n\n lines.push(timeless(t('editing.event', { who: who(last), what: done, when: when(last.at) })))\n }\n\n return lines.join(' · ')\n }\n\n /** The address the record answers at now, in the panel's language or the first it has. */\n function mainPath(): string | null {\n const paths = place.value?.paths\n\n if (!paths || typeof paths !== 'object') return null\n\n const map = paths as Record<string, unknown>\n const own = map[admin.i18n?.state?.locale ?? ''] ?? Object.values(map)[0]\n\n return typeof own === 'string' ? own : null\n }\n\n async function beforePublish(): Promise<{ revision: string; asked: boolean } | null> {\n const held = options.revision.value\n let theirs: EditingVersion<T>\n\n try {\n theirs = await options.read()\n } catch {\n // Not readable just now: publish what the editor holds, and the server checks the revision.\n return { revision: held, asked: false }\n }\n\n if (theirs.revision === held) return { revision: held, asked: false }\n\n if (sameValue(theirs.values, options.values.value)) {\n take(theirs, options.values.value)\n\n return { revision: theirs.revision, asked: false }\n }\n\n theirs.changed ??= changedLast\n\n const explained = events.value.filter((event) => EXPLAINING.has(event.kind))\n const what =\n explained.length > 0\n ? describe(explained)\n : timeless(\n t('editing.incoming', {\n who: who(theirs.changed),\n what: places(changedPaths(base.value, theirs.values)),\n when: when(theirs.changed?.at),\n }),\n )\n\n const agreed = await confirm({\n title: t('editing.publish-unseen-title'),\n message: `${what}. ${t('editing.publish-unseen-text')}`,\n confirmText: t('editing.publish-with'),\n cancelText: t('editing.review-first'),\n })\n\n if (agreed) return { revision: theirs.revision, asked: true }\n\n // Review first: their changes into the form, beside whatever is there, to be looked at and\n // published with the next press of the button.\n if (settleWith(theirs)) toast.info(t('editing.merged', { who: who(theirs.changed) }))\n\n return null\n }\n\n async function failed(error: unknown): Promise<boolean> {\n const failure = error as {\n status?: number\n body?: { message?: string; revision?: unknown; gone?: unknown }\n }\n\n if (failure?.status === 410 && heardGone(failure.body?.gone)) return true\n\n if (failure?.status === 404 || failure?.status === 410) {\n await beat()\n\n return stopped.value\n }\n\n // A publication that carried a revision the draft is no longer at: somebody wrote between\n // the question and the click. The notice of their change follows from the heartbeat.\n if (failure?.status === 409 && typeof failure.body?.revision === 'string') {\n toast.warning(failure.body.message || t('editing.publish-moved'))\n await beat()\n\n return true\n }\n\n return false\n }\n\n const canRestore = (): boolean => options.restore !== undefined && canWrite()\n\n async function restoreFromBin(): Promise<boolean> {\n if (!options.restore) return false\n\n try {\n await options.restore()\n } catch (error) {\n toast.danger(\n (error as { body?: { message?: string } })?.body?.message || t('editing.restore-failed'),\n )\n\n return false\n }\n\n // Who put it in the bin was the notice's to say; out of it, that is no longer news.\n events.value = events.value.filter((event) => event.kind !== 'trashed')\n await beat()\n await options.refresh?.()\n toast.success(t('editing.out-of-bin'))\n\n return true\n }\n\n function dismissEvents(): void {\n events.value = []\n }\n\n /**\n * A 410 with the purge in it. Said once and kept: the record will not come back, and every\n * heartbeat after this one would only hear the same.\n */\n function heardGone(event: unknown): boolean {\n if (gone.value !== null) return true\n if (event === undefined) return false\n\n const purge = (event ?? {}) as Partial<EditingEvent>\n\n gone.value = {\n id: typeof purge.id === 'number' ? purge.id : 0,\n kind: 'purged',\n author: typeof purge.author === 'string' ? purge.author : null,\n author_id: typeof purge.author_id === 'number' ? purge.author_id : null,\n source: typeof purge.source === 'string' ? purge.source : 'panel',\n at: typeof purge.at === 'string' ? purge.at : '',\n detail: null,\n }\n incoming.value = null\n conflict.value = null\n editors.value = []\n\n return true\n }\n\n function text(): string {\n return texts(options.values.value)\n .map(([path, words]) => `${labels.label(path)}\\n${words}`)\n .join('\\n\\n')\n }\n\n async function copyText(): Promise<boolean> {\n try {\n await navigator.clipboard.writeText(text())\n } catch {\n toast.danger(t('editing.copy-failed'))\n\n return false\n }\n\n toast.success(t('editing.copied'))\n\n return true\n }\n\n /* The heartbeat. */\n let timer: ReturnType<typeof setInterval> | undefined\n let reading = false\n let stopWatching: (() => void) | undefined\n /** The newest event this editor has heard of; `null` until the first answer sets the line. */\n let seen: number | null = null\n let stateKey: string | null = null\n\n const address = (): string | null => {\n const id = options.id()\n\n return id === null || id === undefined || id === ''\n ? null\n : `${admin.apiPath}/editing/${options.entity}/${encodeURIComponent(String(id))}`\n }\n\n const me = (): number | null => {\n const id = admin.state?.user?.id\n\n return id === undefined || id === null ? null : Number(id)\n }\n\n /** What the heartbeat says besides the revision: the state, the place, what happened. */\n function absorb(ping: Ping): void {\n const list = Array.isArray(ping.events) ? ping.events : []\n const newest = list.reduce((top, event) => Math.max(top, event.id), seen ?? 0)\n let fresh: EditingEvent[] = []\n\n recent.value = list\n\n if (seen === null) {\n // Whatever happened before this editor opened is not news to it.\n seen = newest\n } else {\n const line = seen\n\n // This editor's own doing is not news either — but the same person through an agent is.\n fresh = list.filter(\n (event) =>\n event.id > line &&\n !(event.author_id === me() && event.source === 'panel' && me() !== null),\n )\n seen = newest\n }\n\n if (fresh.length > 0) events.value = [...events.value, ...fresh]\n\n state.value = ping.state ?? null\n place.value = ping.place ?? null\n\n const key = JSON.stringify([ping.state ?? null, ping.place ?? null])\n const moved = stateKey !== null && key !== stateKey\n\n stateKey = key\n\n // The badge, the trail and the address are the editor's: it reads them again.\n if ((moved || fresh.length > 0) && !trashed.value) void options.refresh?.()\n }\n\n async function beat(): Promise<void> {\n const url = address()\n\n if (url === null) return\n\n const held = options.revision.value\n let ping: Ping\n\n // Gone is gone: the next record the screen opens starts over (see the watch on the id).\n if (gone.value !== null) return\n\n try {\n ping = (await admin.http.post<{ data: Ping }>(url)).data\n } catch (error) {\n const failure = error as { status?: number; body?: { gone?: unknown } }\n\n // Deleted for good is the one silence that is not a courtesy: what is typed here has\n // nowhere to go, and the person has to hear it before they type more.\n if (failure?.status === 410) {\n heardGone(failure.body?.gone ?? null)\n\n return\n }\n\n // A record nobody has saved yet, a panel without the endpoint, a network that blinked:\n // the heartbeat is a courtesy, and its absence is not worth a word on screen.\n return\n }\n\n if (typeof ping?.revision !== 'string') return\n\n editors.value = Array.isArray(ping.editors) ? ping.editors : []\n changedLast = ping.changed ?? null\n absorb(ping)\n\n // Nothing to compare with until the editor has its record: see `ready`.\n if (!ready) return\n\n // Our own save may have landed while the heartbeat was out, and a stale answer would then\n // report it as somebody else's.\n if (held !== options.revision.value || options.busy?.()) return\n if (ping.revision === held || !canWrite() || conflict.value || reading) return\n if (incoming.value?.theirs.revision === ping.revision) return\n\n reading = true\n\n try {\n const theirs = await options.read()\n\n if (held !== options.revision.value || options.busy?.()) return\n\n theirs.changed ??= ping.changed\n\n if (sameValue(theirs.values, options.values.value)) {\n // The server already holds what the form holds — nothing to pull, only a revision.\n take(theirs, options.values.value)\n\n return\n }\n\n incoming.value = { theirs, paths: changedPaths(base.value, theirs.values) }\n } catch {\n // As above: the notice is offered when it can be, never insisted on.\n } finally {\n reading = false\n }\n }\n\n function leave(): void {\n const url = address()\n\n if (url === null) return\n\n try {\n void admin.http.delete(url).catch(() => undefined)\n } catch {\n // A hand-made client without `delete`: the entry runs out on its own within a minute.\n }\n }\n\n onMounted(() => {\n mounted = true\n\n if (options.heartbeat === false) return\n\n const seconds = options.heartbeat ?? 20\n\n timer = setInterval(() => void beat(), seconds * 1000)\n // The first one as soon as the record is known: an agent asking a second after the page\n // opened should hear about it, and so should the next record the same screen opens.\n stopWatching = watch(\n options.id,\n () => {\n // Another record: what was heard about the last one is not about this one.\n seen = null\n stateKey = null\n events.value = []\n gone.value = null\n void beat()\n },\n { immediate: true },\n )\n })\n\n onBeforeUnmount(() => {\n clearInterval(timer)\n stopWatching?.()\n if (options.heartbeat !== false) leave()\n })\n\n return {\n base,\n conflict,\n incoming,\n editors,\n state,\n events,\n trashed,\n trashedBy,\n gone,\n stopped,\n blocked,\n unsaved,\n canRestore,\n opened,\n refused,\n pull,\n choose,\n resolve,\n resolveTheirs,\n beforePublish,\n failed,\n restoreFromBin,\n text,\n copyText,\n dismissEvents,\n check: beat,\n label: labels.label,\n places,\n describe,\n preview,\n who,\n }\n}\n\n/** A value, short, as text: the words of a rich text without its tags, a block by its type. */\nexport function preview(value: unknown): string {\n if (value === undefined || value === null || value === '') return ''\n if (typeof value === 'string')\n return clip(\n value\n .replace(/<[^>]*>/g, '')\n .replace(/\\s+/g, ' ')\n .trim(),\n )\n if (typeof value === 'number' || typeof value === 'boolean') return String(value)\n\n if (\n typeof value === 'object' &&\n !Array.isArray(value) &&\n typeof (value as { type?: unknown }).type === 'string'\n ) {\n return humanize((value as { type: string }).type)\n }\n\n return clip(JSON.stringify(value))\n}\n\n/**\n * Every piece of text in a value with where it sits — what a person typed, to be carried out of\n * a form whose record is gone. Rich text comes out as its words, a paragraph to a line; a block's\n * own `key` and `type` are its shape, not anything anybody wrote.\n */\nexport function texts(value: unknown, path: MergeSegment[] = []): [MergeSegment[], string][] {\n if (typeof value === 'string') {\n const words = plain(value)\n\n return words === '' ? [] : [[path, words]]\n }\n\n if (Array.isArray(value)) {\n return value.flatMap((item) => {\n const node = item as { key?: unknown; type?: unknown } | null\n\n return node !== null && typeof node === 'object' && typeof node.key === 'string'\n ? texts(item, [\n ...path,\n { block: node.key, type: typeof node.type === 'string' ? node.type : '' },\n ])\n : texts(item, path)\n })\n }\n\n if (value === null || typeof value !== 'object') return []\n\n const inBlock = path.length > 0 && 'block' in path[path.length - 1]!\n\n return Object.entries(value).flatMap(([key, item]) =>\n inBlock && (key === 'key' || key === 'type') ? [] : texts(item, [...path, { field: key }]),\n )\n}\n\nfunction plain(html: string): string {\n return html\n .replace(/<br\\s*\\/?>/gi, '\\n')\n .replace(/<\\/(p|h[1-6]|li|blockquote|div)>/gi, '\\n')\n .replace(/<[^>]*>/g, '')\n .replace(/&nbsp;/g, ' ')\n .replace(/&lt;/g, '<')\n .replace(/&gt;/g, '>')\n .replace(/&quot;/g, '\"')\n .replace(/&#39;/g, \"'\")\n .replace(/&amp;/g, '&')\n .replace(/[ \\t]+\\n/g, '\\n')\n .replace(/\\n{3,}/g, '\\n\\n')\n .trim()\n}\n\n/** A sentence whose time was not known ends where the time would have been, not on a «·». */\nexport function timeless(text: string): string {\n return text.replace(/\\s*·\\s*$/, '')\n}\n\nfunction clip(text: string): string {\n return text.length > 140 ? `${text.slice(0, 139)}…` : text\n}\n\nfunction clone<T>(value: T): T {\n return value === undefined ? value : (JSON.parse(JSON.stringify(value)) as T)\n}\n","import type { AdminContext } from './admin'\n\n/** Who wrote a note. `name` is null once that account has been deleted. */\nexport interface NoteAuthor {\n id: number\n name: string | null\n}\n\n/** One line somebody left on a record for whoever picks it up next. */\nexport interface EntityNote {\n id: number\n body: string\n author: NoteAuthor | null\n /**\n * Whether the reader is the author — answered by the server, because who may edit a note is\n * a rule, and a rule the browser works out for itself is the copy that drifts.\n */\n is_mine: boolean\n created_at: string | null\n updated_at: string | null\n}\n\nexport interface NotesApi {\n list(): Promise<EntityNote[]>\n add(body: string): Promise<EntityNote>\n save(id: number, body: string): Promise<EntityNote>\n remove(id: number): Promise<void>\n}\n\n/**\n * The notes of one record.\n *\n * `type` is the alias the server registered the model under — never a class name — and the\n * address is the panel's own, not the section's: the same feed hangs off a submission, an\n * order and a client, and a second copy of it in each module is a second place for the rules\n * about who may write one to be got wrong.\n */\nexport function createNotesApi(admin: AdminContext, type: string, id: number | string): NotesApi {\n const base = `${admin.apiPath}/entities/${type}/${id}/notes`\n const one = (noteId: number) => `${admin.apiPath}/notes/${noteId}`\n const data = <T>(body: { data: T }): T => body.data\n\n return {\n list: () => admin.http.get<{ data: EntityNote[] }>(base).then(data),\n add: (body) => admin.http.post<{ data: EntityNote }>(base, { body }).then(data),\n save: (id, body) => admin.http.put<{ data: EntityNote }>(one(id), { body }).then(data),\n remove: (id) => admin.http.delete(one(id)).then(() => undefined),\n }\n}\n","import { inject, shallowRef, type ShallowRef } from 'vue'\nimport { adminKey, type AdminContext } from './admin'\n\n/** One shortcode of the site, as `GET <api>/shortcodes` answers with it. */\nexport interface Shortcode {\n name: string\n description: string | null\n /** What the site prints for it — trusted markup. */\n html: string\n /** The same as text: what a text field shows as its value. */\n plain: string\n /** Declared by a package in code, or added by hand in the settings. */\n origin: 'code' | 'settings'\n}\n\n/**\n * A shortcode as a text field offers it — the shape of `TokenOption` in `@webx-ui/core`, spelled\n * out here rather than imported so a panel built against an older core still compiles.\n */\nexport interface ShortcodeToken {\n name: string\n value?: string\n description?: string\n}\n\nexport function shortcodeToken(shortcode: Shortcode): ShortcodeToken {\n return {\n name: shortcode.name,\n value: shortcode.plain,\n description: shortcode.description ?? undefined,\n }\n}\n\n/*\n * One request per panel, not per field: a page of blocks has dozens of text fields, and the\n * list changes when somebody edits the settings, not while a page is being written. Kept by the\n * context rather than in a module variable, so two panels in one tab — or two tests — do not\n * share one answer.\n */\nconst loaded = new WeakMap<AdminContext, Promise<ShortcodeToken[]>>()\n\nexport function loadShortcodes(admin: AdminContext): Promise<ShortcodeToken[]> {\n let pending = loaded.get(admin)\n\n if (!pending) {\n pending = admin.http\n .get<{ data: Shortcode[] }>(`${admin.apiPath}/shortcodes`)\n .then((body) => (Array.isArray(body.data) ? body.data.map(shortcodeToken) : []))\n .catch(() => {\n // A field works without suggestions; the next field opened asks again.\n loaded.delete(admin)\n return []\n })\n loaded.set(admin, pending)\n }\n\n return pending\n}\n\n/** Drops what was loaded, so the next field asks again — after the settings changed one. */\nexport function forgetShortcodes(admin: AdminContext): void {\n loaded.delete(admin)\n}\n\n/**\n * The site's shortcodes for a text field's `tokens`: empty at first and filled once the panel\n * has answered. Outside a panel — a demo, a test — it stays empty.\n */\nexport function useShortcodes(): ShallowRef<ShortcodeToken[]> {\n const admin = inject(adminKey, null)\n const tokens = shallowRef<ShortcodeToken[]>([])\n\n if (admin) void loadShortcodes(admin).then((list) => (tokens.value = list))\n\n return tokens\n}\n","import { computed, getCurrentScope, onScopeDispose, readonly, ref, type Ref } from 'vue'\nimport { useAdmin, type AdminContext } from './admin'\nimport { toError, type Http, type SendOptions } from './http'\n\n/**\n * Large files a piece at a time, through the panel's own four requests (§4 of the video spec):\n *\n * POST uploads { name, size, type, fingerprint, purpose } → { id, offset, size, chunk_size }\n * HEAD uploads/{id} → Upload-Offset\n * PATCH uploads/{id} a piece, Upload-Offset: n → 204, Upload-Offset: n + length\n * DELETE uploads/{id}\n *\n * The server holds the offset and the client follows it: a 409 names where the file really\n * ends, a HEAD asks after a dropped connection, and the same file chosen again after a reload\n * finds its session by the fingerprint. What the server cannot know is the web server's own\n * limit, so a 413 halves the piece — down to 256 KB — and the smaller piece stays for the rest\n * of the file.\n */\n\nexport type ChunkedUploadState = 'idle' | 'uploading' | 'paused' | 'offline' | 'done' | 'failed'\n\n/** An upload that did not finish, as remembered in this browser for the next visit. */\nexport interface UnfinishedUpload {\n fingerprint: string\n purpose: string\n name: string\n size: number\n offset: number\n /** Milliseconds since the epoch, of the last piece that landed. */\n updatedAt: number\n}\n\nexport interface ChunkedUploadOptions {\n /** The panel to talk to; `useAdmin()` when left out. */\n admin?: Pick<AdminContext, 'http' | 'apiPath'>\n /** Where unfinished uploads are remembered; `localStorage` when left out, `null` for nowhere. */\n storage?: Storage | null\n /** How many times a piece lost to the network is sent again before the upload fails. */\n retries?: number\n /** The wait before the first of those, doubled for each after it. */\n retryDelay?: number\n}\n\nexport interface ChunkedUpload {\n readonly state: Readonly<Ref<ChunkedUploadState>>\n /** 0 to 1. */\n readonly progress: Readonly<Ref<number>>\n /** Bytes the server has. */\n readonly uploaded: Readonly<Ref<number>>\n readonly total: Readonly<Ref<number>>\n /** Bytes a second, over the last few seconds; 0 until there is something to measure. */\n readonly speed: Readonly<Ref<number>>\n /** Seconds left at that speed, or null while there is no speed to go by. */\n readonly remaining: Readonly<Ref<number | null>>\n /** The session's id, which is what the module the file was for claims it by. */\n readonly id: Readonly<Ref<string | null>>\n readonly error: Readonly<Ref<Error | null>>\n /**\n * Send a file. Resolves with the session's id once the whole of it is on the server, with\n * null when it is cancelled; rejects when it fails. A pause does neither — the same promise\n * resolves once the upload is resumed and finished.\n */\n start(file: File, purpose: string): Promise<string | null>\n pause(): void\n /** Carry on after a pause, going offline or a failure; the promise of the upload. */\n resume(): Promise<string | null>\n /** Stop and throw away what the server has. */\n cancel(): Promise<void>\n}\n\n/** The smallest piece the client falls back to on a 413 (the server never advises less). */\nexport const MIN_CHUNK = 256 * 1024\n\nconst STORAGE_KEY = 'webx:uploads'\nconst SPEED_WINDOW = 8000\n\n/**\n * What identifies a file without reading it: the same name, size and date of change is taken\n * for the same file. Cheap on purpose — hashing gigabytes to find out where to resume would take\n * longer than a good part of the upload.\n */\nexport function uploadFingerprint(file: Pick<File, 'name' | 'size' | 'lastModified'>): string {\n return `${file.name}|${file.size}|${file.lastModified}`\n}\n\n/**\n * The uploads this browser started and did not finish — what a panel offers to continue after a\n * reload (\"choose the same file\"). Empty wherever storage is refused.\n */\nexport function unfinishedUploads(\n purpose?: string,\n storage: Storage | null = defaultStorage(),\n): UnfinishedUpload[] {\n return readAll(storage).filter((entry) => purpose === undefined || entry.purpose === purpose)\n}\n\n/** Stop offering to continue an upload — the person chose not to. */\nexport function forgetUnfinishedUpload(\n fingerprint: string,\n purpose: string,\n storage: Storage | null = defaultStorage(),\n): void {\n writeAll(\n storage,\n readAll(storage).filter(\n (entry) => entry.fingerprint !== fingerprint || entry.purpose !== purpose,\n ),\n )\n}\n\nexport function useChunkedUpload(options: ChunkedUploadOptions = {}): ChunkedUpload {\n const admin = options.admin ?? useAdmin()\n const storage = options.storage === undefined ? defaultStorage() : options.storage\n const retries = options.retries ?? 3\n const retryDelay = options.retryDelay ?? 1000\n const base = `${admin.apiPath}/uploads`\n\n const state = ref<ChunkedUploadState>('idle')\n const uploaded = ref(0)\n const total = ref(0)\n const speed = ref(0)\n const id = ref<string | null>(null)\n const error = ref<Error | null>(null)\n\n const progress = computed(() => (total.value > 0 ? Math.min(1, uploaded.value / total.value) : 0))\n const remaining = computed(() =>\n speed.value > 0 ? Math.ceil((total.value - uploaded.value) / speed.value) : null,\n )\n\n let file: File | null = null\n let purpose = ''\n let chunk = MIN_CHUNK\n // Every pause, cancel and trip offline moves this on; a loop that finds it moved stops.\n let generation = 0\n let controller: AbortController | null = null\n let samples: Array<{ at: number; bytes: number }> = []\n let settle: { resolve: (id: string | null) => void; reject: (error: Error) => void } | null = null\n let pending: Promise<string | null> | null = null\n\n function send(method: string, path: string, init: SendOptions = {}): Promise<Response> {\n return sendWith(admin.http, method, path, init)\n }\n\n function promise(): Promise<string | null> {\n if (pending === null) {\n pending = new Promise<string | null>((resolve, reject) => {\n settle = { resolve, reject }\n })\n // Handled here as well, so that a caller who only watches `state` does not get an\n // unhandled rejection; one who awaits it still sees the failure.\n pending.catch(() => undefined)\n }\n\n return pending\n }\n\n function finish(result: string | null, failure?: Error): void {\n const done = settle\n settle = null\n pending = null\n\n if (failure !== undefined) {\n done?.reject(failure)\n } else {\n done?.resolve(result)\n }\n }\n\n function fail(failure: unknown): void {\n error.value = failure instanceof Error ? failure : new Error(String(failure))\n state.value = 'failed'\n speed.value = 0\n finish(null, error.value)\n }\n\n function setOffset(offset: number): void {\n uploaded.value = Math.min(offset, total.value)\n\n const now = Date.now()\n samples.push({ at: now, bytes: uploaded.value })\n samples = samples.filter((sample) => now - sample.at <= SPEED_WINDOW)\n\n const first = samples[0]\n const seconds = first === undefined ? 0 : (now - first.at) / 1000\n speed.value = first !== undefined && seconds > 0 ? (uploaded.value - first.bytes) / seconds : 0\n\n if (file !== null) {\n remember(file, purpose, uploaded.value)\n }\n }\n\n function remember(target: File, forPurpose: string, offset: number): void {\n const fingerprint = uploadFingerprint(target)\n const others = readAll(storage).filter(\n (entry) => entry.fingerprint !== fingerprint || entry.purpose !== forPurpose,\n )\n\n writeAll(storage, [\n ...others,\n {\n fingerprint,\n purpose: forPurpose,\n name: target.name,\n size: target.size,\n offset,\n updatedAt: Date.now(),\n },\n ])\n }\n\n function forget(): void {\n if (file !== null) {\n forgetUnfinishedUpload(uploadFingerprint(file), purpose, storage)\n }\n }\n\n function stop(): void {\n generation += 1\n controller?.abort()\n controller = null\n speed.value = 0\n samples = []\n }\n\n async function start(target: File, forPurpose: string): Promise<string | null> {\n if (state.value === 'uploading' || state.value === 'paused' || state.value === 'offline') {\n throw new Error('This upload is still going; cancel it before starting another.')\n }\n\n stop()\n file = target\n purpose = forPurpose\n id.value = null\n error.value = null\n uploaded.value = 0\n total.value = target.size\n state.value = 'uploading'\n\n const result = promise()\n const token = generation\n\n try {\n const body = await admin.http.post<{\n data: { id: string; offset: number; size: number; chunk_size: number }\n }>(base, {\n name: target.name,\n size: target.size,\n type: target.type,\n fingerprint: uploadFingerprint(target),\n purpose: forPurpose,\n })\n\n if (token !== generation) {\n return result\n }\n\n id.value = body.data.id\n chunk = Math.max(MIN_CHUNK, body.data.chunk_size)\n setOffset(body.data.offset)\n void run()\n } catch (failure) {\n if (token === generation) {\n fail(failure)\n }\n }\n\n return result\n }\n\n /** Where the server says the file ends — after a pause, a trip offline or a lost piece. */\n async function sync(): Promise<boolean> {\n const response = await send('HEAD', `${base}/${id.value}`)\n\n if (!response.ok) {\n fail(await toError(response))\n\n return false\n }\n\n const offset = headerOffset(response)\n\n if (offset !== null) {\n setOffset(offset)\n }\n\n return true\n }\n\n async function run(): Promise<void> {\n const token = generation\n let failures = 0\n\n state.value = 'uploading'\n\n while (token === generation && file !== null && id.value !== null) {\n if (uploaded.value >= total.value) {\n state.value = 'done'\n speed.value = 0\n forget()\n finish(id.value)\n\n return\n }\n\n const from = uploaded.value\n const to = Math.min(total.value, from + chunk)\n controller = new AbortController()\n\n let response: Response\n\n try {\n response = await send('PATCH', `${base}/${id.value}`, {\n body: file.slice(from, to),\n headers: {\n 'Upload-Offset': String(from),\n 'Content-Type': 'application/offset+octet-stream',\n },\n signal: controller.signal,\n })\n } catch (failure) {\n // A pause or a cancel aborted it; whoever did that has already said what happens next.\n if (token !== generation) {\n return\n }\n\n if (isOffline()) {\n goOffline()\n\n return\n }\n\n failures += 1\n\n if (failures > retries) {\n fail(failure)\n\n return\n }\n\n await wait(retryDelay * 2 ** (failures - 1))\n\n if (token !== generation) {\n return\n }\n\n // Some of the piece may have landed before the connection went; the server knows.\n try {\n if (!(await sync())) {\n return\n }\n } catch {\n // Still unreachable; the next attempt counts as another failure.\n }\n\n continue\n }\n\n if (token !== generation) {\n return\n }\n\n if (response.ok) {\n failures = 0\n setOffset(headerOffset(response) ?? to)\n\n continue\n }\n\n if (response.status === 409) {\n const offset = headerOffset(response)\n\n if (offset !== null) {\n setOffset(offset)\n\n continue\n }\n }\n\n // The web server's limit, which the server behind it could not know about.\n if (response.status === 413 && chunk > MIN_CHUNK) {\n chunk = Math.max(MIN_CHUNK, Math.floor(chunk / 2))\n\n continue\n }\n\n fail(await toError(response))\n\n return\n }\n }\n\n function pause(): void {\n if (state.value !== 'uploading') {\n return\n }\n\n stop()\n state.value = 'paused'\n }\n\n function goOffline(): void {\n stop()\n state.value = 'offline'\n }\n\n async function resume(): Promise<string | null> {\n if (state.value === 'uploading' || state.value === 'done' || state.value === 'idle') {\n return pending ?? Promise.resolve(state.value === 'done' ? id.value : null)\n }\n\n if (file === null) {\n return Promise.resolve(null)\n }\n\n // A failure before the session existed leaves nothing to resume; start it again instead.\n if (id.value === null) {\n state.value = 'idle'\n\n return start(file, purpose)\n }\n\n stop()\n error.value = null\n state.value = 'uploading'\n\n const result = promise()\n const token = generation\n\n try {\n if ((await sync()) && token === generation) {\n void run()\n }\n } catch (failure) {\n if (token === generation) {\n if (isOffline()) {\n goOffline()\n } else {\n fail(failure)\n }\n }\n }\n\n return result\n }\n\n async function cancel(): Promise<void> {\n const session = id.value\n\n stop()\n forget()\n state.value = 'idle'\n uploaded.value = 0\n id.value = null\n finish(null)\n\n if (session !== null) {\n try {\n await send('DELETE', `${base}/${session}`)\n } catch {\n // Gone or unreachable: the server sweeps what nobody finishes.\n }\n }\n }\n\n function onOffline(): void {\n if (state.value === 'uploading') {\n goOffline()\n }\n }\n\n function onOnline(): void {\n if (state.value === 'offline') {\n void resume()\n }\n }\n\n if (typeof window !== 'undefined') {\n window.addEventListener('offline', onOffline)\n window.addEventListener('online', onOnline)\n }\n\n if (getCurrentScope() !== undefined) {\n onScopeDispose(() => {\n if (typeof window !== 'undefined') {\n window.removeEventListener('offline', onOffline)\n window.removeEventListener('online', onOnline)\n }\n\n // Nothing is left to show the progress; the fingerprint stays, so it can be continued.\n pause()\n })\n }\n\n return {\n state: readonly(state),\n progress,\n uploaded: readonly(uploaded),\n total: readonly(total),\n speed: readonly(speed),\n remaining,\n id: readonly(id),\n error: readonly(error) as Readonly<Ref<Error | null>>,\n start,\n pause,\n resume,\n cancel,\n }\n}\n\n/**\n * The client's own `send` when it has one, so the CSRF cookie and the standing headers are the\n * same as everywhere else; a bare fetch for a hand-made client that does not.\n */\nasync function sendWith(\n http: Http,\n method: string,\n path: string,\n init: SendOptions,\n): Promise<Response> {\n if (http.send !== undefined) {\n return http.send(method, path, init)\n }\n\n return globalThis.fetch(path, {\n method,\n credentials: 'same-origin',\n headers: { Accept: 'application/json', ...init.headers },\n body: init.body ?? undefined,\n signal: init.signal,\n })\n}\n\nfunction headerOffset(response: Response): number | null {\n const value = response.headers.get('Upload-Offset')\n\n if (value === null || !/^\\d+$/.test(value)) {\n return null\n }\n\n return Number.parseInt(value, 10)\n}\n\nfunction isOffline(): boolean {\n return typeof navigator !== 'undefined' && navigator.onLine === false\n}\n\nfunction wait(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms))\n}\n\nfunction defaultStorage(): Storage | null {\n try {\n return typeof localStorage === 'undefined' ? null : localStorage\n } catch {\n // A browser that blocks site data throws on the very access.\n return null\n }\n}\n\nfunction readAll(storage: Storage | null): UnfinishedUpload[] {\n if (storage === null) {\n return []\n }\n\n try {\n const parsed: unknown = JSON.parse(storage.getItem(STORAGE_KEY) ?? '[]')\n\n return Array.isArray(parsed) ? (parsed as UnfinishedUpload[]) : []\n } catch {\n return []\n }\n}\n\nfunction writeAll(storage: Storage | null, entries: UnfinishedUpload[]): void {\n if (storage === null) {\n return\n }\n\n try {\n if (entries.length === 0) {\n storage.removeItem(STORAGE_KEY)\n } else {\n storage.setItem(STORAGE_KEY, JSON.stringify(entries))\n }\n } catch {\n // Full or refused: continuing after a reload is a courtesy, not something to fail over.\n }\n}\n","import { inject, provide, type InjectionKey, type Ref } from 'vue'\nimport type { ScreenModel } from '@webx-ui/schema'\nimport type { CategoryRow } from './types'\n\n/**\n * What the category editor knows and the nodes of its screen do not — the same seam the article\n * editor has. `wx-category-slug` (`wx-slug`) prints the whole address, and the prefix of the module is\n * configuration the editor was handed with the record, not something a description can carry.\n */\nexport interface CategoryEditorContext {\n /** The category as the server last answered it, or `null` while the first request is out. */\n category: Ref<CategoryRow | null>\n /** The values of the screen as they are right now, edits included. */\n values: Ref<ScreenModel>\n /** Where the module's addresses start; `null` for categories without an address. */\n prefix: Ref<string | null>\n /** \"The address is changing\" — in the module's words. */\n moving: () => string\n}\n\nexport const categoryEditorKey: InjectionKey<CategoryEditorContext> = Symbol('wx-category-editor')\n\nexport function provideCategoryEditor(editor: CategoryEditorContext): void {\n provide(categoryEditorKey, editor)\n}\n\n/** The editor above this node, or `null` outside one — a demo, a test, a stray screen. */\nexport function useCategoryEditor(): CategoryEditorContext | null {\n return inject(categoryEditorKey, null)\n}\n","import { computed, type ComputedRef } from 'vue'\nimport { useAdmin } from '../admin'\nimport { useTranslate } from '../i18n'\nimport { reorderItems } from './api'\n\n/**\n * What the list of a module's records looks like right now, as far as its order is concerned.\n */\nexport interface ItemOrderState {\n /** What is typed in the search box. */\n q?: string | null\n /** The one category the list is narrowed to, if it is. */\n category?: number | null\n /** Whether any filter other than the category is on — status, author, the bin. */\n filtered?: boolean\n}\n\n/**\n * Which order a drag would write (decision 5 of the services spec).\n *\n * - `all` — the whole list is on screen, and dragging moves the common order;\n * - `category` — one category and nothing else, and dragging moves that category's own order;\n * - `locked` — a search or another filter is on, and the rows on screen are a selection: the\n * gaps between them are records nobody can see, so a drag here would move them blind.\n */\nexport type ItemOrderMode = 'all' | 'category' | 'locked'\n\nexport function itemOrderMode(state: ItemOrderState): ItemOrderMode {\n if ((state.q ?? '').trim() !== '' || state.filtered) return 'locked'\n\n return state.category == null ? 'all' : 'category'\n}\n\nexport interface ItemOrder {\n mode: ComputedRef<ItemOrderMode>\n /** Whether the handles are drawn at all. */\n sortable: ComputedRef<boolean>\n /** One line under the list: what a drag does here, or why there is nothing to drag. */\n hint: ComputedRef<string>\n /** Write the order the rows are in on screen — the whole list, or the category's. */\n move(ids: number[]): Promise<void>\n}\n\n/**\n * The order of a module's records, dragged in the list the editor is looking at.\n *\n * A composable rather than a component, because the list it belongs to is the module's own —\n * a table, a sortable list, cards — and what is shared is the rule about which order a drag\n * writes and the sentence that says so. `path` is the list's API (`services/items`), served by\n * `CategoryRoutes::items()`. A module whose records are ordered by something else (the blog, by\n * date) does not call it, and its list has no handles.\n */\nexport function useItemOrder(path: string, state: () => ItemOrderState): ItemOrder {\n const admin = useAdmin()\n const t = useTranslate('webx-admin')\n\n const mode = computed(() => itemOrderMode(state()))\n\n return {\n mode,\n sortable: computed(() => mode.value !== 'locked'),\n hint: computed(() => t(`categories.order-${mode.value}`)),\n move: (ids) =>\n reorderItems(admin, path, ids, mode.value === 'category' ? (state().category ?? null) : null),\n }\n}\n","<script setup lang=\"ts\">\nimport { WxIcon, WxTooltip } from '@webx-ui/core'\nimport { useTranslate } from './i18n'\n\n/**\n * A quiet mark beside an address a list shows in the site's main language, because the record\n * has none in the language the list is read in. The address stays in the row — the page opens\n * there — and the mark says, on hover, why it is not this language's.\n *\n * Nothing at all when `locale` is empty: that is the usual row.\n */\nconst props = defineProps<{ locale?: string | null }>()\n\nconst t = useTranslate('webx-admin')\n</script>\n\n<template>\n <wx-tooltip\n v-if=\"props.locale\"\n :content=\"t('links.address-fallback', { locale: props.locale.toUpperCase() })\"\n :max-width=\"280\"\n >\n <span class=\"wx-address-note\" tabindex=\"0\" @click.stop>\n <wx-icon name=\"info\" size=\"14px\" :label=\"t('links.address-fallback-label')\" />\n </span>\n </wx-tooltip>\n</template>\n\n<style scoped>\n.wx-address-note {\n display: inline-flex;\n align-items: center;\n margin-inline-start: var(--wx-space-4);\n color: var(--wx-text-muted);\n vertical-align: middle;\n cursor: help;\n}\n\n.wx-address-note:focus-visible {\n outline: none;\n border-radius: var(--wx-radius-xs);\n box-shadow: var(--wx-ring-focus);\n}\n</style>\n","import { useTranslate, type Translate } from '../i18n'\nimport type { CategoryWord } from './types'\n\n/**\n * The shared screens' words, the module's where it has them and the panel's where it does not.\n *\n * The panel's say \"category\"; a module that calls them something else hands its own keys in,\n * because a blog that says \"rubric\" on its list and \"category\" in the dialog over it has two\n * words for one thing (CLAUDE.md §4 — the words of a shared component are the caller's).\n */\nexport function useCategoryWords(\n words: Partial<Record<CategoryWord, string>> = {},\n): (word: CategoryWord, params?: Record<string, string | number>) => string {\n const t: Translate = useTranslate('webx-admin')\n\n return (word, params) => t(words[word] ?? `webx-admin::categories.${word}`, params)\n}\n","<script setup lang=\"ts\">\nimport { nextTick, onMounted, ref, useTemplateRef } from 'vue'\nimport { useModal, WxButton, WxDialog, WxFormItem, WxInput, WxSpace } from '@webx-ui/core'\nimport { useAdmin } from '../admin'\nimport { createCategoriesApi } from './api'\nimport type { CategoriesOptions, CategoryRow } from './types'\nimport { useCategoryWords } from './words'\n\n/**\n * A new category: what it is called, and nothing else.\n *\n * The server makes the address out of the name — the same transliteration it would apply to\n * anything typed in the field — and the page that opens next is where the address, the picture\n * and the fields of the project are written, with room to say what changing the address costs.\n */\ndefineOptions({ name: 'WxCategoryCreateDialog' })\n\nconst props = defineProps<{ options: CategoriesOptions }>()\n\nconst { open, resolve, dismiss } = useModal<CategoryRow>()\n\nconst api = createCategoriesApi(useAdmin(), props.options.api)\nconst w = useCategoryWords(props.options.words)\n\nconst title = ref('')\nconst saving = ref(false)\nconst errors = ref<Record<string, string[]>>({})\nconst field = useTemplateRef<HTMLElement>('field')\n\nonMounted(async () => {\n await nextTick()\n field.value?.querySelector('input')?.focus()\n})\n\n/** The first line of a refusal under a field that may be named by language: `slug.en`. */\nfunction errorOf(name: string): string | undefined {\n const key = Object.keys(errors.value).find((one) => one === name || one.startsWith(`${name}.`))\n\n return key === undefined ? undefined : errors.value[key]?.[0]\n}\n\nasync function submit(): Promise<void> {\n if (title.value.trim() === '' || saving.value) return\n\n saving.value = true\n errors.value = {}\n\n try {\n resolve(await api.create({ title: title.value.trim() }))\n } catch (error) {\n // The address is what can be refused here, and it is refused under the name that made it:\n // the registry sees every kind of page at once, so \"taken\" can mean taken by an article.\n errors.value = (error as { body?: { errors?: Record<string, string[]> } }).body?.errors ?? {}\n } finally {\n saving.value = false\n }\n}\n</script>\n\n<template>\n <wx-dialog v-model:open=\"open\" :title=\"w('new')\" :width=\"440\">\n <wx-form-item :label=\"w('field-title')\" :error=\"errorOf('title') ?? errorOf('slug')\" required>\n <div ref=\"field\">\n <wx-input v-model=\"title\" :aria-label=\"w('field-title')\" @keyup.enter=\"submit\" />\n </div>\n </wx-form-item>\n\n <template #footer>\n <wx-space size=\"sm\">\n <wx-button variant=\"outline\" @click=\"dismiss()\">{{ w('cancel') }}</wx-button>\n <wx-button type=\"primary\" :loading=\"saving\" :disabled=\"title.trim() === ''\" @click=\"submit\">\n {{ w('create') }}\n </wx-button>\n </wx-space>\n </template>\n </wx-dialog>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\nimport { useRouter } from 'vue-router'\nimport {\n confirm,\n createModal,\n toast,\n WxAlert,\n WxEmpty,\n WxIcon,\n WxSkeleton,\n WxSortableList,\n WxText,\n WxTooltip,\n} from '@webx-ui/core'\nimport WxAddressNote from '../AddressNote.vue'\nimport { useAdmin } from '../admin'\nimport { useErrorText } from '../errors'\nimport { useTranslate } from '../i18n'\nimport ListScreen from '../ListScreen.vue'\nimport RowMenu from '../RowMenu.vue'\nimport type { RowAction, ScreenAction } from '../types'\nimport { createCategoriesApi } from './api'\nimport CategoryCreateDialog from './CategoryCreateDialog.vue'\nimport type { CategoriesOptions, CategoryRow } from './types'\nimport { useCategoryWords } from './words'\n\n/**\n * The categories of a module: the whole menu of the site, in the order it has on it.\n *\n * One list and nothing beside it. The order here *is* the order on the site, so the screen the\n * section needs most is the one where every category is visible at once and can be dragged past\n * the others. A category is edited on a page of its own ({@link CategoryEditorPage}) — it has an\n * address, SEO and the fields of the project, and it is a record rather than a line of a menu.\n *\n * There is no paginator and no search: a site has eight of them, and a menu you have to search\n * is a menu that is already wrong.\n */\ndefineOptions({ name: 'WxCategoriesPage' })\n\nconst props = defineProps<{ options: CategoriesOptions }>()\n\nconst context = useAdmin()\nconst api = createCategoriesApi(context, props.options.api)\nconst router = useRouter()\nconst w = useCategoryWords(props.options.words)\n/* The heading and the way back, when the options name their own keys. */\nconst t = useTranslate('webx-admin')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\nconst rows = ref<CategoryRow[]>([])\nconst loading = ref(true)\n/*\n * Whether this module's categories have addresses at all. The FAQ's do not (a category there is\n * a button of a filter), and a line reading \"no address in this language\" under every one of\n * them would be a warning about nothing.\n */\nconst addressed = ref(true)\n\nconst canManage = computed(() => context.can(props.options.manage))\n\nconst title = computed(() =>\n props.options.title\n ? t(props.options.title)\n : (context.state.manifest?.modules.find((module) => module.id === props.options.module)\n ?.title ?? ''),\n)\n\nconst create = createModal<CategoryRow, { options: CategoriesOptions }>(CategoryCreateDialog)\n\n/* What the section offers. Declared, because on a phone the head folds it into the ···. */\nconst actions = computed<ScreenAction[]>(() =>\n canManage.value\n ? [{ key: 'new', label: w('new'), icon: 'plus', primary: true, run: () => void add() }]\n : [],\n)\n\nfunction countOf(row: CategoryRow): number {\n return Number((row as unknown as Record<string, unknown>)[props.options.count] ?? 0)\n}\n\nasync function load(): Promise<void> {\n loading.value = true\n\n try {\n const answer = await api.list()\n\n rows.value = answer.data\n addressed.value = answer.prefix !== null\n } catch (error) {\n toast.danger(message(error))\n } finally {\n loading.value = false\n }\n}\n\nvoid load()\n\nfunction open(row: CategoryRow): void {\n void router.push(`${props.options.path}/${row.id}`)\n}\n\n/** A name in a dialog, and then the page: the rest of a category is written there. */\nasync function add(): Promise<void> {\n const made = await create({ options: props.options })\n\n if (made !== undefined) open(made)\n}\n\n/**\n * Into the bin, and only while it is empty.\n *\n * The line stays in the menu and refuses rather than being left out, because \"why can I not\n * delete this\" is the question, and an item that is not there does not answer it. The number it\n * refuses with is already in the row.\n */\nasync function remove(row: CategoryRow): Promise<void> {\n if (countOf(row) > 0) {\n toast.warning(w('delete-blocked'))\n\n return\n }\n\n const agreed = await confirm({\n title: w('delete-title', { name: row.name }),\n message: w('delete-text'),\n confirmText: w('delete'),\n cancelText: w('cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n try {\n await api.remove(row.id)\n toast.success(w('deleted'))\n await load()\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\nfunction menuOf(row: CategoryRow): RowAction[] {\n const items: RowAction[] = [{ key: 'edit', label: w('edit'), icon: 'edit', run: () => open(row) }]\n const itemsOf = props.options.items\n\n if (itemsOf && countOf(row) > 0) {\n items.push({\n key: 'items',\n label: w('show-items'),\n icon: 'file-txt',\n run: () => void router.push(itemsOf(row.id)),\n })\n }\n\n if (row.url && row.is_visible) {\n items.push({ key: 'site', label: w('open-on-site'), icon: 'link', href: row.url })\n }\n\n if (canManage.value) {\n items.push({\n key: 'delete',\n label: w('delete'),\n icon: 'trash',\n danger: true,\n run: () => void remove(row),\n })\n }\n\n return items\n}\n\n/**\n * The whole order, every time.\n *\n * The list is already in its new order on screen — `WxSortableList` reorders the model before\n * it says anything — so this only has to agree with what is there, and a failure has to put the\n * list back rather than leave the screen disagreeing with the database.\n */\nasync function reorder(): Promise<void> {\n try {\n await api.reorder(rows.value.map((row) => row.id))\n } catch (error) {\n toast.danger(message(error, w('reorder-failed')))\n await load()\n }\n}\n</script>\n\n<template>\n <list-screen\n class=\"wx-categories\"\n :title=\"title\"\n :back=\"props.options.back?.path\"\n :back-label=\"props.options.back ? t(props.options.back.label) : undefined\"\n :actions=\"actions\"\n padding=\"sm\"\n >\n <wx-skeleton v-if=\"loading\" class=\"wx-categories__loading\" :rows=\"5\" />\n\n <wx-empty v-else-if=\"rows.length === 0\" :title=\"w('empty')\" :description=\"w('empty-help')\" />\n\n <template v-else>\n <!--\n A grip and not the whole row: the row is what opens a category, and a list whose rows\n both open and drag is a list where one of the two happens by accident.\n -->\n <wx-sortable-list\n v-model=\"rows\"\n class=\"wx-categories__list\"\n hoverable\n plain\n item-key=\"id\"\n :item-label=\"(item: CategoryRow) => item.name\"\n :disabled=\"!canManage\"\n @move=\"reorder\"\n >\n <template #default=\"{ item }\">\n <router-link\n class=\"wx-category-row\"\n :class=\"{ 'is-hidden': !item.is_visible }\"\n :to=\"`${props.options.path}/${item.id}`\"\n >\n <span class=\"wx-category-row__name\">\n <wx-text truncate weight=\"medium\">{{ item.name }}</wx-text>\n <wx-tooltip v-if=\"!item.is_visible\" :content=\"w('hidden')\">\n <wx-icon name=\"eye-off\" size=\"sm\" />\n </wx-tooltip>\n </span>\n <wx-text v-if=\"addressed\" size=\"sm\" tone=\"muted\" truncate>\n {{ item.path === null ? w('no-address') : `/${item.path}` }}\n <wx-address-note v-if=\"item.path !== null\" :locale=\"item.address_locale\" />\n </wx-text>\n </router-link>\n </template>\n\n <!-- How many items would be left without this category: the number the refusal to\n delete names, said before anybody presses anything. -->\n <template #actions=\"{ item }\">\n <wx-text class=\"wx-category-row__count\" size=\"sm\" tone=\"muted\">\n {{ w('count', { count: countOf(item) }) }}\n </wx-text>\n\n <row-menu :actions=\"menuOf(item)\" :label=\"item.name\" />\n </template>\n </wx-sortable-list>\n\n <!-- What the drag is for, under the rows it is about: over them it read as a heading for\n the list, which it is not — the list is already named by the screen. -->\n <wx-alert class=\"wx-categories__note\" type=\"info\" :description=\"w('order')\" />\n </template>\n </list-screen>\n</template>\n\n<style>\n.wx-categories__loading {\n padding: var(--wx-space-8);\n}\n\n/*\n * A plain list draws its rows edge to edge, which is right until one of them is tinted: the\n * highlight then starts exactly at the first letter, so a hovered row reads as a stain rather\n * than as a row. The padding is inside the tint, not around it.\n */\n.wx-categories__list.is-plain .wx-sortable-list__row {\n padding-inline: var(--wx-space-8);\n}\n\n.wx-categories__note {\n margin-block-start: var(--wx-space-8);\n}\n\n.wx-category-row {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-2);\n align-items: flex-start;\n /* The row is the target, so it takes the row: a name of four letters should not leave three\n quarters of the line dead. */\n flex: 1 1 auto;\n min-width: 0;\n color: inherit;\n text-decoration: none;\n}\n\n.wx-category-row__name {\n display: flex;\n align-items: center;\n gap: var(--wx-space-6);\n min-width: 0;\n max-width: 100%;\n}\n\n/* A category that is not on the site says so quietly, the way the site does: it is still a\n category, and its items still answer. */\n.wx-category-row.is-hidden {\n color: var(--wx-text-muted);\n}\n\n/* The count is beside the menu and must not be pushed into it by a long name. */\n.wx-category-row__count {\n flex: none;\n font-variant-numeric: tabular-nums;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, ref, watch } from 'vue'\nimport { WxIcon } from '@webx-ui/core'\nimport { useTranslate } from './i18n'\n\n/**\n * Whether the work is saved, said in the smallest way that says it.\n *\n * A word in the bar — \"Saved\" — is a word that is right nearly all of the time and therefore\n * reads as furniture: it is on screen when nothing is happening, which is exactly when nobody\n * is asking. What anyone actually wants to know is whether *this* save landed, and only for\n * as long as it takes to land. So: a wheel while it is in flight, a tick when it lands, and\n * nothing a couple of seconds later.\n *\n * Nothing at all for unsaved work either. That is not a small decision, and it holds because\n * the screens that use this say so twice over: the head carries a badge beside the name, and\n * the save button is enabled — a button offering to do something is the clearest statement\n * that there is something to do.\n *\n * The words stay, for whoever is not looking at the bar: the element is a live region and the\n * icon carries the sentence, so a screen reader hears \"Saving…\" and then \"Saved\".\n */\nconst props = withDefaults(\n defineProps<{\n /** What the screen is doing. Anything but `saving` and `saved` shows nothing. */\n state: 'saving' | 'unsaved' | 'saved' | string\n /** How long the tick stays after a save lands, in milliseconds. */\n linger?: number\n }>(),\n { linger: 2000 },\n)\n\nconst t = useTranslate('webx-admin')\n\n/*\n * The tick is shown for a save that happened, not for a screen that opens on saved work —\n * which is every screen, every time. So it is the *change* out of `saving` that lights it,\n * and never the value on its own.\n */\nconst landed = ref(false)\nlet fade: number | null = null\n\nwatch(\n () => props.state,\n (now, before) => {\n if (fade !== null) clearTimeout(fade)\n\n landed.value = before === 'saving' && now === 'saved'\n\n if (!landed.value) return\n\n fade = window.setTimeout(() => {\n landed.value = false\n fade = null\n }, props.linger)\n },\n)\n\nconst showing = computed(() =>\n props.state === 'saving' ? 'saving' : landed.value ? 'saved' : null,\n)\n\nonBeforeUnmount(() => {\n if (fade !== null) clearTimeout(fade)\n})\n</script>\n\n<template>\n <span class=\"wx-save-state\" role=\"status\" aria-live=\"polite\">\n <wx-icon\n v-if=\"showing\"\n class=\"wx-save-state__mark\"\n :class=\"{ 'is-saved': showing === 'saved' }\"\n :name=\"showing === 'saving' ? 'loader' : 'check'\"\n :spin=\"showing === 'saving'\"\n :label=\"t(`editor.${showing}`)\"\n />\n </span>\n</template>\n\n<style scoped>\n/*\n * It keeps its place while it is empty. The bar lays the state out beside the buttons, and a\n * box that appears and disappears would shift them by its width twice per save.\n */\n.wx-save-state {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: none;\n width: var(--wx-size-control-sm);\n height: var(--wx-size-control-sm);\n}\n\n.wx-save-state__mark {\n font-size: var(--wx-font-size-lg);\n color: var(--wx-text-muted);\n}\n\n.wx-save-state__mark.is-saved {\n color: var(--wx-color-success);\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\nimport { WxAlert, WxSkeleton } from '@webx-ui/core'\nimport { WxScreenRenderer, type Patch, 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 * The page's own patch, laid under the project's: what only the page can know — options\n * read from the values it loaded, say. The project's patch still has the last word.\n */\n patch?: Patch\n }>(),\n {\n errors: undefined,\n disabled: false,\n labelPosition: undefined,\n labelWidth: undefined,\n size: undefined,\n patch: () => [],\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\nconst patch = computed<Patch>(() => [...props.patch, ...admin.screenPatch(props.name)])\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 <!-- A page that knows the shape of its screen — tabs, a card — draws the wait in that shape. -->\n <slot v-else-if=\"root === null\" name=\"loading\"><wx-skeleton :rows=\"4\" /></slot>\n <wx-screen-renderer\n v-else\n v-model=\"model\"\n :root=\"root\"\n :patch=\"patch\"\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, onBeforeUnmount, onMounted, ref, watch } from 'vue'\nimport { onBeforeRouteLeave, useRoute, useRouter } from 'vue-router'\nimport {\n confirm,\n localizedValue,\n toast,\n useLocales,\n WxActionBar,\n WxBadge,\n WxBreadcrumb,\n WxBreadcrumbItem,\n WxButton,\n WxCard,\n WxSkeleton,\n type LocalizedValue,\n} from '@webx-ui/core'\nimport type { ScreenModel } from '@webx-ui/schema'\nimport { provideRecordAddress } from '../address'\nimport { useAdmin } from '../admin'\nimport { useErrorText } from '../errors'\nimport { useTranslate } from '../i18n'\nimport { provideHistorySubject } from '../history'\nimport { useBodyKeys } from '../keys'\nimport SaveState from '../SaveState.vue'\nimport Screen from '../Screen.vue'\nimport ScreenHead from '../ScreenHead.vue'\nimport type { ScreenAction } from '../types'\nimport { createCategoriesApi } from './api'\nimport { provideCategoryEditor } from './editor'\nimport type { CategoriesOptions, CategoryRow } from './types'\nimport { useCategoryWords } from './words'\n\n/**\n * One category, on a page of its own: a head, the described screen under it, and one button.\n *\n * A page and not a dialog, because a category is a record — an address, a picture, SEO and the\n * fields a project patched on — and a project that adds a tab to it needs a screen to add it to\n * (decision 7 of the services spec). The tabs are the module's screen (`blog.category-form`);\n * what this page owns is the head, the bar and the save.\n *\n * One explicit save rather than the autosave of an article: a category has no draft, so what is\n * saved is on the site at once, and a menu entry that renames itself between two keystrokes is\n * not something to do behind the editor's back. A refused field opens its own tab — the screen's\n * tabs do that themselves (`ScreenTabs.vue` in `@webx-ui/schema`).\n */\ndefineOptions({ name: 'WxCategoryEditorPage' })\n\nconst props = defineProps<{ options: CategoriesOptions }>()\n\nconst context = useAdmin()\nconst api = createCategoriesApi(context, props.options.api)\nconst route = useRoute()\nconst router = useRouter()\nconst locales = useLocales()\nconst w = useCategoryWords(props.options.words)\n/* The heading and the way back, when the options name their own keys. */\nconst t = useTranslate('webx-admin')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\nconst id = computed(() => Number(route.params.id))\nconst list = computed(() => props.options.path)\n\nconst category = ref<CategoryRow | null>(null)\nconst values = ref<ScreenModel>({})\nconst prefix = ref<string | null>(null)\nconst snapshot = ref('')\n\nconst loading = ref(true)\nconst saving = ref(false)\nconst errors = ref<Record<string, string[]>>({})\n\nconst canManage = computed(() => context.can(props.options.manage))\n\nconst current = computed(() => JSON.stringify(values.value))\nconst dirty = computed(() => snapshot.value !== '' && current.value !== snapshot.value)\n\nconst state = computed<'saving' | 'unsaved' | 'saved'>(() => {\n if (saving.value) return 'saving'\n\n return dirty.value ? 'unsaved' : 'saved'\n})\n\nconst section = computed(() =>\n props.options.title\n ? t(props.options.title)\n : (context.state.manifest?.modules.find((module) => module.id === props.options.module)\n ?.title ?? ''),\n)\n\n/** The name follows the field rather than the answer: a rename shows at the top as it is typed. */\nconst title = computed(() => {\n const written = values.value.title as LocalizedValue | string | null | undefined\n\n return localizedValue(written, locales.active.value, category.value?.name ?? '') || w('untitled')\n})\n\nprovideCategoryEditor({ category, values, prefix, moving: () => w('address-moving') })\n\n/* A form with a journal tab (`wx-history`, the brands' one) reads whose journal it is here. */\nprovideHistorySubject({ id: computed(() => category.value?.id ?? null) })\n\n/* The address field is the panel's shared one (`wx-slug`), and this is what it prints. */\nprovideRecordAddress({\n values,\n prefix,\n path: computed(() => category.value?.path),\n moving: () => w('address-moving'),\n})\n\nfunction take(detail: { category: CategoryRow; values: ScreenModel; prefix: string | null }): void {\n category.value = detail.category\n values.value = detail.values\n prefix.value = detail.prefix\n snapshot.value = JSON.stringify(detail.values)\n}\n\nasync function load(): Promise<void> {\n loading.value = true\n\n try {\n take(await api.get(id.value))\n } catch (error) {\n toast.danger(message(error))\n void router.push(list.value)\n } finally {\n loading.value = false\n }\n}\n\nasync function save(): Promise<void> {\n if (!category.value || saving.value || !canManage.value) return\n\n saving.value = true\n errors.value = {}\n\n try {\n take(await api.save(category.value.id, values.value))\n toast.success(w('saved'))\n } catch (error) {\n const body = (error as { body?: { errors?: Record<string, string[]> } }).body\n\n if (body?.errors) {\n errors.value = body.errors\n toast.danger(w('save-failed'))\n } else {\n toast.danger(message(error))\n }\n } finally {\n saving.value = false\n }\n}\n\nasync function remove(): Promise<void> {\n const row = category.value\n\n if (!row) return\n\n const agreed = await confirm({\n title: w('delete-title', { name: row.name }),\n message: w('delete-text'),\n confirmText: w('delete'),\n cancelText: w('cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n try {\n await api.remove(row.id)\n toast.success(w('deleted'))\n // Nothing left to protect: the record is in the bin, and asking about its unsaved edits on\n // the way out would be asking about a page that is gone.\n snapshot.value = current.value\n void router.push(list.value)\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\n/* The browser's own guard: it cannot wait for a request, so all it can do is ask. */\nfunction leaveGuard(event: BeforeUnloadEvent): void {\n if (dirty.value) event.preventDefault()\n}\n\n/* Ctrl+S saves, as it does everywhere else that has a save button. */\nfunction onKeydown(event: KeyboardEvent): void {\n if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 's') {\n event.preventDefault()\n if (dirty.value) void save()\n }\n}\n\n// And from the page around the form, where a click on empty space leaves the focus.\nuseBodyKeys(onKeydown)\n\nwatch(id, () => void load())\n\nonMounted(() => {\n void load()\n window.addEventListener('beforeunload', leaveGuard)\n})\n\nonBeforeUnmount(() => {\n window.removeEventListener('beforeunload', leaveGuard)\n})\n\nonBeforeRouteLeave(async () => {\n if (!dirty.value || !canManage.value) return true\n\n return await confirm({\n title: w('leave-title'),\n message: w('leave-text'),\n confirmText: w('leave'),\n cancelText: w('cancel'),\n tone: 'danger',\n })\n})\n\n/* What leads away from the category. On a phone the head folds them into the ···. */\nconst actions = computed<ScreenAction[]>(() => {\n const row = category.value\n const leads: ScreenAction[] = []\n\n if (!row) return leads\n\n if (row.url && row.is_visible) {\n leads.push({ key: 'site', label: w('open-on-site'), icon: 'link', href: row.url })\n }\n\n if (canManage.value) {\n leads.push({\n key: 'delete',\n label: w('delete'),\n icon: 'trash',\n danger: true,\n menu: true,\n run: () => void remove(),\n })\n }\n\n return leads\n})\n</script>\n\n<template>\n <div class=\"wx-category-editor\" @keydown=\"onKeydown\">\n <template v-if=\"loading || !category\">\n <!-- Shaped like the screen it stands in for: the head, and a card for what is coming. -->\n <wx-skeleton class=\"wx-category-editor__ghost\" title :rows=\"1\" />\n <wx-card><wx-skeleton :rows=\"6\" /></wx-card>\n </template>\n\n <template v-else>\n <screen-head\n divider\n :back=\"list\"\n :back-label=\"section\"\n :title=\"title\"\n :subtitle=\"category.path === null ? undefined : `/${category.path}`\"\n :actions=\"actions\"\n >\n <template #trail>\n <wx-breadcrumb size=\"sm\" :label=\"w('trail')\">\n <wx-breadcrumb-item :as=\"'router-link'\" :to=\"list\">{{ section }}</wx-breadcrumb-item>\n <wx-breadcrumb-item current>{{ title }}</wx-breadcrumb-item>\n </wx-breadcrumb>\n </template>\n\n <!-- Hidden is the one state a category has, and it is said where the name is. -->\n <template v-if=\"!category.is_visible\" #title-after>\n <wx-badge dot>{{ w('hidden') }}</wx-badge>\n </template>\n </screen-head>\n\n <screen\n v-model=\"values\"\n :name=\"props.options.screen\"\n :errors=\"errors\"\n :disabled=\"!canManage || saving\"\n />\n\n <wx-action-bar v-if=\"canManage\">\n <template #state>\n <save-state :state=\"state\" />\n </template>\n\n <wx-button type=\"primary\" :loading=\"saving\" :disabled=\"!dirty\" @click=\"save\">\n {{ w('save') }}\n </wx-button>\n </wx-action-bar>\n </template>\n </div>\n</template>\n\n<style scoped>\n.wx-category-editor {\n display: flex;\n flex-direction: column;\n gap: var(--wx-gap, var(--wx-space-16));\n container-type: inline-size;\n}\n\n.wx-category-editor__ghost {\n max-width: 420px;\n}\n</style>\n","import { defineComponent, h, type Component } from 'vue'\nimport type { RouteRecordRaw } from 'vue-router'\nimport CategoriesPage from './CategoriesPage.vue'\nimport CategoryEditorPage from './CategoryEditorPage.vue'\nimport type { CategoriesOptions } from './types'\n\n/**\n * The page, as a component of this module's own.\n *\n * Two modules' categories are the same two pages with different options, and `RouterView`\n * patches a component that stays the same rather than mounting it anew: from the blog's rubrics\n * to the services' categories the list kept its instance, took the new title from the props and\n * went on showing the rubrics it had loaded — everything else was read once, in `setup`. A\n * component per call is a different type for each module, so the switch is a fresh mount.\n */\nfunction bound(page: Component, options: CategoriesOptions): Component {\n return defineComponent({\n name: 'WxCategoriesView',\n setup: () => () => h(page, { options }),\n })\n}\n\n/**\n * The two screens of a module's categories — the list and the page of one — as routes to put\n * into the module's section:\n *\n * { id: 'rubrics', path: '/blog/rubrics', routes: categoryRoutes({ api: 'blog/rubrics', … }) }\n *\n * One description for both, so the list links to the editor and the editor back to the list\n * without either being told the other's address.\n */\nexport function categoryRoutes(options: CategoriesOptions): RouteRecordRaw[] {\n return [\n { path: options.path, name: options.name, component: bound(CategoriesPage, options) },\n {\n path: `${options.path}/:id(\\\\d+)`,\n name: `${options.name}.edit`,\n component: bound(CategoryEditorPage, options),\n },\n ]\n}\n","/**\n * How wide the column holding a row's `···` has to be.\n *\n * The menu is a finger target — 44px under `(pointer: coarse)`, which is what `WxActions` gives\n * a collapsed row — and the cell around it keeps `--wx-table-padding-x` on both sides. 44 and 16\n * and 16 is this number; the 56 the sections used to declare was never enough for it, and the\n * button simply painted outside its column, which nothing said out loud until cells began to\n * clip what does not fit.\n *\n * One constant rather than a number in seven files: when the touch target changes, the columns\n * that hold it change with it.\n */\nexport const rowMenuWidth = 76\n","<script setup lang=\"ts\">\nimport { WxBadge } from '@webx-ui/core'\nimport type { AppliedFilter } from './types'\n\n/**\n * What the filters are set to, in the head of every list of the panel.\n *\n * The table gives the strip its place and its spacing (`#applied`); this gives every section the\n * same chip. Written once because the alternative is what the panel already learned about\n * headings: five sections each answering \"what does an applied filter look like\" gives five\n * answers, and a reader who learns one list should already know the next.\n *\n * A chip takes one filter off. Taking all of them off lives with the fields it resets, inside\n * the panel the funnel opens — beside the chips it reads as one more chip, and it is the only\n * control in that row that does not remove exactly one thing.\n */\ndefineProps<{ filters: AppliedFilter[] }>()\n</script>\n\n<template>\n <wx-badge\n v-for=\"filter in filters\"\n :key=\"filter.key\"\n class=\"wx-filter-chip\"\n size=\"sm\"\n round\n closable\n @close=\"filter.clear()\"\n >\n {{ filter.label }}\n </wx-badge>\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, ref, watch } from 'vue'\nimport {\n confirm,\n toast,\n WxAvatar,\n WxButton,\n WxEmpty,\n WxSkeleton,\n WxText,\n WxTextarea,\n} from '@webx-ui/core'\nimport DateText from './DateText.vue'\nimport RowMenu from './RowMenu.vue'\nimport { useAdmin } from './admin'\nimport { useErrorText } from './errors'\nimport { useTranslate } from './i18n'\nimport { createNotesApi, type EntityNote } from './notes'\nimport type { RowAction } from './types'\n\ndefineOptions({ name: 'WxNotes' })\n\n/**\n * What one administrator wrote on a record for the next one.\n *\n * Deliberately a component of the frame and not of any section. The first place that wanted\n * one was the inbox, the next will be an order and the one after that a client, and three\n * copies of \"who may write here, who may edit what they wrote\" is two copies too many — so the\n * feed, its address and its rules live here, and a section only says which record it is about.\n *\n * Nothing is drawn while the reader may not write: the server refuses either way, and a box\n * that posts a 403 is worse than no box. Whether they may is the section's answer, because it\n * is the section's permission (`can`).\n */\nconst props = withDefaults(\n defineProps<{\n /** The alias the server registered the model under — never a class name. */\n type: string\n id: number | string\n /** Whether this reader may write and edit their own. */\n can?: boolean\n /** Heading above the feed; the panel's own word for it by default. */\n title?: string\n }>(),\n { can: true, title: undefined },\n)\n\nconst emit = defineEmits<{\n /** A note was written, changed or removed — a log beside the feed has to redraw. */\n change: []\n}>()\n\nconst admin = useAdmin()\nconst t = useTranslate('webx-admin')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\nconst notes = ref<EntityNote[]>([])\nconst loading = ref(true)\nconst draft = ref('')\nconst sending = ref(false)\nconst editing = ref<number | null>(null)\nconst edited = ref('')\n\nconst api = computed(() => createNotesApi(admin, props.type, props.id))\n\nconst heading = computed(() => props.title ?? t('notes.title'))\n\nasync function load(): Promise<void> {\n loading.value = true\n\n try {\n notes.value = await api.value.list()\n } catch (error) {\n toast.danger(message(error))\n } finally {\n loading.value = false\n }\n}\n\n/* The feed follows the record: on this screen the next submission replaces this one in\n place, and a feed that did not notice would be the previous one's notes under a new name. */\nwatch(\n () => [props.type, props.id] as const,\n () => {\n editing.value = null\n draft.value = ''\n void load()\n },\n { immediate: true },\n)\n\nasync function add(): Promise<void> {\n const body = draft.value.trim()\n\n if (body === '' || sending.value) return\n\n sending.value = true\n\n try {\n notes.value = [...notes.value, await api.value.add(body)]\n draft.value = ''\n emit('change')\n } catch (error) {\n toast.danger(message(error))\n } finally {\n sending.value = false\n }\n}\n\nfunction edit(note: EntityNote): void {\n editing.value = note.id\n edited.value = note.body\n}\n\nasync function save(note: EntityNote): Promise<void> {\n const body = edited.value.trim()\n\n if (body === '') return\n\n try {\n const saved = await api.value.save(note.id, body)\n notes.value = notes.value.map((one) => (one.id === saved.id ? saved : one))\n editing.value = null\n toast.success(t('notes.saved'))\n emit('change')\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\nasync function remove(note: EntityNote): Promise<void> {\n const agreed = await confirm({\n title: t('notes.delete-title'),\n message: t('notes.delete-text'),\n confirmText: t('notes.delete'),\n cancelText: t('notes.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n try {\n await api.value.remove(note.id)\n notes.value = notes.value.filter((one) => one.id !== note.id)\n toast.success(t('notes.deleted'))\n emit('change')\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\n/** Only the author's own, and only where writing is allowed at all. */\nfunction actionsFor(note: EntityNote): RowAction[] {\n if (!props.can || !note.is_mine) return []\n\n return [\n { key: 'edit', icon: 'edit', label: t('notes.edit'), run: () => edit(note) },\n {\n key: 'delete',\n icon: 'trash',\n label: t('notes.delete'),\n danger: true,\n run: () => void remove(note),\n },\n ]\n}\n\nfunction who(note: EntityNote): string {\n return note.author?.name ?? t('notes.unknown-author')\n}\n</script>\n\n<template>\n <section class=\"wx-notes\">\n <wx-text v-if=\"heading\" weight=\"medium\" class=\"wx-notes__title\">{{ heading }}</wx-text>\n\n <wx-skeleton v-if=\"loading\" :rows=\"2\" />\n\n <wx-empty v-else-if=\"notes.length === 0\" size=\"sm\" :description=\"t('notes.empty')\" />\n\n <ul v-else class=\"wx-notes__list\">\n <li v-for=\"note in notes\" :key=\"note.id\" class=\"wx-note\">\n <wx-avatar :name=\"who(note)\" size=\"sm\" tone=\"auto\" />\n\n <div class=\"wx-note__body\">\n <div class=\"wx-note__head\">\n <wx-text size=\"sm\" weight=\"medium\" truncate>{{ who(note) }}</wx-text>\n <date-text :value=\"note.created_at\" />\n <row-menu :actions=\"actionsFor(note)\" :label=\"who(note)\" />\n </div>\n\n <template v-if=\"editing === note.id\">\n <wx-textarea v-model=\"edited\" :autosize=\"{ minRows: 2, maxRows: 10 }\" />\n <div class=\"wx-note__buttons\">\n <wx-button size=\"sm\" type=\"primary\" @click=\"save(note)\">\n {{ t('notes.save') }}\n </wx-button>\n <wx-button size=\"sm\" variant=\"text\" @click=\"editing = null\">\n {{ t('notes.cancel') }}\n </wx-button>\n </div>\n </template>\n\n <!-- `white-space: pre-wrap` and nothing else: a note is typed, not authored, and\n everything that renders markup here is a way to put a link nobody wrote into a\n panel. -->\n <p v-else class=\"wx-note__text\">{{ note.body }}</p>\n </div>\n </li>\n </ul>\n\n <div v-if=\"can\" class=\"wx-notes__new\">\n <wx-textarea\n v-model=\"draft\"\n :placeholder=\"t('notes.placeholder')\"\n :autosize=\"{ minRows: 2, maxRows: 8 }\"\n />\n <wx-button\n size=\"sm\"\n type=\"primary\"\n :disabled=\"draft.trim() === ''\"\n :loading=\"sending\"\n @click=\"add\"\n >\n {{ t('notes.add') }}\n </wx-button>\n </div>\n </section>\n</template>\n\n<style scoped>\n.wx-notes {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-12);\n min-width: 0;\n /* The feed decides its own layout from its own width and not the window's: it hangs in a\n card beside other cards, and the window knows nothing about how wide that is. */\n container-type: inline-size;\n}\n\n.wx-notes__list {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-12);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.wx-note {\n display: flex;\n gap: var(--wx-space-8);\n min-width: 0;\n}\n\n.wx-note__body {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-4);\n flex: 1 1 auto;\n min-width: 0;\n}\n\n/* The menu keeps the end of the line, so a note by somebody with a long name and one by\n somebody with a short one still have their controls in the same place. */\n.wx-note__head {\n display: flex;\n align-items: center;\n gap: var(--wx-space-8);\n min-width: 0;\n}\n\n.wx-note__head > :last-child {\n margin-inline-start: auto;\n}\n\n.wx-note__text {\n margin: 0;\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n font-size: var(--wx-font-size-sm);\n color: var(--wx-text-default);\n}\n\n.wx-note__buttons {\n display: flex;\n align-items: flex-start;\n gap: var(--wx-space-8);\n}\n\n/*\n * The button stands under the box, not beside it.\n *\n * Beside it, the button took its own width off the box at every width, so the thing being\n * written was always narrower than the room there was for it — and the two lined up along\n * their tops, which put a button at the height of the first line of a note that had not been\n * typed yet. Under it, the box is the width of the card and the button is the width of its\n * own word.\n */\n.wx-notes__new {\n display: flex;\n flex-direction: column;\n align-items: stretch;\n gap: var(--wx-space-8);\n}\n\n.wx-notes__new > :first-child {\n min-width: 0;\n}\n\n.wx-notes__new > :last-child {\n align-self: flex-start;\n}\n\n/* On a phone it takes the width instead: a target for a finger, and there is nothing beside\n it to line up with anyway. */\n@container (max-width: 440px) {\n .wx-note__buttons {\n flex-direction: column;\n align-items: stretch;\n }\n\n .wx-notes__new > :last-child {\n align-self: stretch;\n }\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, nextTick, ref, watch } from 'vue'\nimport { confirm, WxAlert, WxButton } from '@webx-ui/core'\nimport { useDates } from './dates'\nimport { timeless, type Editing } from './editing'\nimport { useTranslate } from './i18n'\nimport type { MergeChoice, MergeConflict } from './merge'\n\n/**\n * What an editor says about other people editing the same record, above its form.\n *\n * Six things, the loudest first. The record was deleted for good, with a way to copy out what was\n * typed. The record is in the bin, with the way out of it. A conflict: both sides changed the same place, and each such\n * place is listed with what it was, what this editor wrote and what the other side wrote, to be\n * settled one by one — everything else has already been merged. A save that came in while this\n * editor was open, with what it changed and an offer to pull it in before saving over it. What\n * others did besides writing — published it, moved it, put an old version back. And,\n * quietest, who else has the record open right now.\n */\nconst props = defineProps<{ editing: Editing<unknown> }>()\n\nconst emit = defineEmits<{\n /** The conflict is settled into the form: save it. */\n save: []\n /** Give up this editor's side and read the record again. */\n theirs: []\n /** Pulled in with nothing overlapping: the form holds both edits now. */\n pulled: []\n}>()\n\nconst t = useTranslate('webx-admin')\nconst dates = useDates()\n\nconst conflict = computed(() => props.editing.conflict.value)\nconst incoming = computed(() => props.editing.incoming.value)\nconst others = computed(() => props.editing.editors.value)\n\nconst conflictTitle = computed(() =>\n t('editing.conflict-title', { who: props.editing.who(conflict.value?.theirs.changed) }),\n)\n\n/*\n * A conflict is the one thing here that waits for an answer, and it appears after a save — often\n * with the editor scrolled deep into a long page, where the top of the screen is out of sight.\n * Brought into view once, when it appears; the notice of an incoming save is not, because it asks\n * for nothing and pulling the page away from what somebody is reading is worse than missing it.\n */\nconst conflictAlert = ref<{ $el?: Element } | null>(null)\n\nwatch(\n () => conflict.value !== null,\n async (open) => {\n if (!open) return\n\n await nextTick()\n conflictAlert.value?.$el?.scrollIntoView?.({ block: 'nearest', behavior: 'smooth' })\n },\n)\n\nfunction when(at: string | null | undefined): string {\n return at ? dates.short(at) : ''\n}\n\n/* What others did besides writing: published, moved, restored an old version. */\nconst events = computed(() => props.editing.events.value)\n\n/*\n * An old version put back changes every field it touched, and «changed Hero › Eyebrow, Heading,\n * Below the button and more» hides the one thing worth saying: which version, and that it went\n * on the site. When an event explains the change, the notice says the event instead.\n */\nconst explained = computed(() =>\n events.value.some((event) => event.kind === 'restored_version' || event.kind === 'discarded'),\n)\n\nconst incomingText = computed(() => {\n const now = incoming.value\n\n if (!now) return ''\n\n if (explained.value) return props.editing.describe(events.value)\n\n return timeless(\n t('editing.incoming', {\n who: props.editing.who(now.theirs.changed),\n what: props.editing.places(now.paths),\n when: when(now.theirs.changed?.at),\n }),\n )\n})\n\nconst eventsText = computed(() => props.editing.describe(events.value))\n\n/* In the bin: who put it there and when, and the way back for whoever may take it. */\nconst trashed = computed(() => props.editing.trashed.value)\n\nconst trashedTitle = computed(() => {\n const by = props.editing.trashedBy.value\n\n if (by) return t('editing.trashed-title', { who: props.editing.who(by), when: when(by.at) })\n\n return t('editing.trashed-anonymous', {\n when: when(props.editing.state.value?.deleted_at),\n })\n})\n\n/* Deleted for good: who did it and when. No way back — only the text, to be carried out. */\nconst gone = computed(() => props.editing.gone.value)\n\nconst goneTitle = computed(() => {\n const by = gone.value\n\n // A 410 with nothing in it — an older server — says only that it is gone.\n if (!by?.at) return t('editing.purged-anonymous')\n\n return timeless(t('editing.purged-title', { who: props.editing.who(by), when: when(by.at) }))\n})\n\nconst restoring = ref(false)\n\n/** Out of the bin, and what the form holds saved right after when there is anything. */\nasync function restore(): Promise<void> {\n const keep = props.editing.unsaved.value\n\n restoring.value = true\n\n try {\n if ((await props.editing.restoreFromBin()) && keep) emit('save')\n } finally {\n restoring.value = false\n }\n}\n\nfunction chosen(one: MergeConflict): MergeChoice {\n return conflict.value?.choices[one.id] ?? 'mine'\n}\n\nfunction side(one: MergeConflict, which: 'base' | MergeChoice): string {\n if (which === 'mine' && one.kind === 'removed-mine') return t('editing.removed')\n if (which === 'theirs' && one.kind === 'removed-theirs') return t('editing.removed')\n\n return props.editing.preview(one[which]) || t('editing.empty')\n}\n\nfunction save(): void {\n props.editing.resolve()\n emit('save')\n}\n\nasync function takeTheirs(): Promise<void> {\n const agreed = await confirm({\n title: t('editing.theirs-title'),\n message: t('editing.theirs-text'),\n confirmText: t('editing.take-theirs'),\n cancelText: t('editing.cancel'),\n tone: 'danger',\n })\n\n if (agreed) emit('theirs')\n}\n\nasync function pull(): Promise<void> {\n if (await props.editing.pull()) emit('pulled')\n}\n</script>\n\n<template>\n <!-- Deleted for good: there is nothing to save into and nothing to restore. The form keeps what\n it holds, and the one thing left to offer is carrying it out. -->\n <wx-alert\n v-if=\"gone\"\n class=\"wx-editing-alerts\"\n type=\"danger\"\n :title=\"goneTitle\"\n :description=\"t('editing.purged-text')\"\n live\n >\n <template #actions>\n <wx-button size=\"sm\" type=\"primary\" @click=\"editing.copyText()\">\n {{ t('editing.copy-text') }}\n </wx-button>\n </template>\n </wx-alert>\n\n <!-- In the bin: nothing typed here can be saved until it is out. The form keeps what it holds,\n so the text can be copied even by somebody who may not restore it. -->\n <wx-alert\n v-else-if=\"trashed\"\n class=\"wx-editing-alerts\"\n type=\"danger\"\n :title=\"trashedTitle\"\n :description=\"t('editing.trashed-text')\"\n live\n >\n <template v-if=\"editing.canRestore()\" #actions>\n <wx-button size=\"sm\" type=\"primary\" :loading=\"restoring\" @click=\"restore\">\n {{ editing.unsaved.value ? t('editing.restore-save') : t('editing.restore-bin') }}\n </wx-button>\n </template>\n </wx-alert>\n\n <wx-alert\n v-else-if=\"conflict\"\n ref=\"conflictAlert\"\n class=\"wx-editing-alerts\"\n type=\"warning\"\n :title=\"conflictTitle\"\n live\n >\n <p class=\"wx-editing-alerts__lead\">{{ t('editing.conflict-text') }}</p>\n\n <ul class=\"wx-editing-alerts__list\">\n <li v-for=\"one in conflict.conflicts\" :key=\"one.id\" class=\"wx-editing-alerts__item\">\n <div class=\"wx-editing-alerts__place\">\n {{ editing.label(one.path) }}\n <span v-if=\"one.kind !== 'changed'\" class=\"wx-editing-alerts__note\">\n {{ t(`editing.${one.kind}`) }}\n </span>\n </div>\n\n <div class=\"wx-editing-alerts__was\">\n <span class=\"wx-editing-alerts__side\">{{ t('editing.base') }}</span>\n {{ side(one, 'base') }}\n </div>\n\n <div\n class=\"wx-editing-alerts__choices\"\n role=\"radiogroup\"\n :aria-label=\"editing.label(one.path)\"\n >\n <button\n v-for=\"which in ['mine', 'theirs'] as const\"\n :key=\"which\"\n type=\"button\"\n role=\"radio\"\n class=\"wx-editing-alerts__choice\"\n :class=\"{ 'is-chosen': chosen(one) === which }\"\n :aria-checked=\"chosen(one) === which\"\n @click=\"editing.choose(one.id, which)\"\n >\n <span class=\"wx-editing-alerts__side\">{{ t(`editing.${which}`) }}</span>\n <span class=\"wx-editing-alerts__value\">{{ side(one, which) }}</span>\n </button>\n </div>\n </li>\n </ul>\n\n <template #actions>\n <wx-button size=\"sm\" variant=\"outline\" @click=\"takeTheirs\">\n {{ t('editing.take-theirs') }}\n </wx-button>\n <wx-button size=\"sm\" type=\"primary\" @click=\"save\">\n {{ t('editing.apply') }}\n </wx-button>\n </template>\n </wx-alert>\n\n <wx-alert v-else-if=\"incoming\" class=\"wx-editing-alerts\" type=\"info\" :title=\"incomingText\" live>\n <template #actions>\n <wx-button size=\"sm\" type=\"primary\" @click=\"pull\">{{ t('editing.pull') }}</wx-button>\n </template>\n </wx-alert>\n\n <wx-alert\n v-else-if=\"events.length > 0\"\n class=\"wx-editing-alerts\"\n type=\"info\"\n :title=\"eventsText\"\n live\n >\n <template #actions>\n <wx-button size=\"sm\" variant=\"outline\" @click=\"editing.dismissEvents()\">\n {{ t('editing.dismiss') }}\n </wx-button>\n </template>\n </wx-alert>\n\n <p v-else-if=\"others.length > 0\" class=\"wx-editing-alerts__others\">\n {{ t('editing.others', { names: others.map((one) => one.name).join(', ') }) }}\n </p>\n</template>\n\n<style scoped>\n.wx-editing-alerts__lead {\n margin: 0 0 var(--wx-space-8);\n}\n\n.wx-editing-alerts__list {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-12);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.wx-editing-alerts__item {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-4);\n}\n\n.wx-editing-alerts__place {\n font-weight: var(--wx-font-weight-medium);\n}\n\n.wx-editing-alerts__note,\n.wx-editing-alerts__was,\n.wx-editing-alerts__side {\n color: var(--wx-text-muted);\n}\n\n.wx-editing-alerts__note {\n margin-inline-start: var(--wx-space-8);\n font-weight: var(--wx-font-weight-regular);\n}\n\n.wx-editing-alerts__side {\n margin-inline-end: var(--wx-space-6);\n font-size: var(--wx-font-size-sm);\n}\n\n.wx-editing-alerts__choices {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(min(100%, 220px), 1fr));\n gap: var(--wx-space-8);\n}\n\n.wx-editing-alerts__choice {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: var(--wx-space-2);\n min-width: 0;\n padding: var(--wx-space-8) var(--wx-space-10);\n border: 1px solid var(--wx-border-default);\n border-radius: var(--wx-radius-sm);\n background: var(--wx-bg-surface);\n color: var(--wx-text-default);\n font: inherit;\n text-align: start;\n cursor: pointer;\n}\n\n.wx-editing-alerts__choice.is-chosen {\n border-color: var(--wx-border-focus);\n box-shadow: 0 0 0 1px var(--wx-border-focus);\n}\n\n.wx-editing-alerts__choice:focus-visible {\n outline: 2px solid var(--wx-border-focus);\n outline-offset: 2px;\n}\n\n.wx-editing-alerts__value {\n overflow-wrap: anywhere;\n}\n\n.wx-editing-alerts__others {\n margin: 0;\n color: var(--wx-text-muted);\n font-size: var(--wx-font-size-sm);\n}\n</style>\n","<script setup lang=\"ts\">\nimport { ref, watch } from 'vue'\nimport { confirm, toast, WxBadge, WxButton, WxHeading, WxSkeleton, WxText } from '@webx-ui/core'\nimport { useAdmin } from './admin'\nimport DateText from './DateText.vue'\nimport { useErrorText } from './errors'\nimport type { DraftCopy } from './editing'\nimport { useEditingLabels } from './editingLabels'\nimport { useTranslate } from './i18n'\n\n/**\n * The copies of the draft between publications: what the history does not list.\n *\n * The autosave ring, and the drafts somebody else's save wrote over — an agent's edit under an\n * editor's «Keep mine», or the other way round. Put back with «Restore», which makes the copy the\n * draft again; nothing reaches the site until somebody publishes. Shared by every drafted\n * editor's history tab: the list is the same whatever the record is.\n */\ndefineOptions({ name: 'WxDrafts' })\n\nconst props = withDefaults(\n defineProps<{\n /** The record's name on the server's `editing/{entity}/{id}`. */\n entity: string\n id: string | number | null | undefined\n /** Whether this reader may put a copy back. */\n canRestore?: boolean\n /** Anything that changes when the record is saved or published: the list is read again. */\n stamp?: unknown\n /** The screen the record is edited on, so the changed places read as the form names them. */\n screen?: string\n }>(),\n { canRestore: false, stamp: undefined, screen: undefined },\n)\n\nconst emit = defineEmits<{\n /** A copy is the draft now: the editor reads the record again. */\n restored: []\n}>()\n\nconst admin = useAdmin()\nconst t = useTranslate('webx-admin')\nconst message = useErrorText()\n\nconst drafts = ref<DraftCopy[] | null>(null)\nconst labels = useEditingLabels({ screen: props.screen })\n\n/**\n * What a copy changed against the one before it: «Hero › Eyebrow · EN», so that the edit somebody\n * lost can be found by the place it was in. Three, and «and more» after them.\n */\nfunction changed(copy: DraftCopy): string {\n const named = [...new Set((copy.paths ?? []).map((path) => labels.label(path)))]\n\n if (named.length === 0) return ''\n\n const what = named.slice(0, 3).join(', ')\n\n return named.length > 3 ? t('editing.and-more', { what }) : what\n}\nconst working = ref(false)\n\nconst base = (): string | null =>\n props.id === null || props.id === undefined || props.id === ''\n ? null\n : `${admin.apiPath}/editing/${props.entity}/${encodeURIComponent(String(props.id))}/drafts`\n\nasync function load(): Promise<void> {\n const url = base()\n\n if (url === null) return\n\n try {\n drafts.value = (await admin.http.get<{ data: DraftCopy[] }>(url)).data\n } catch {\n // A panel without the endpoint, or a record with no copies to speak of: say nothing.\n drafts.value = []\n }\n}\n\nasync function restore(copy: DraftCopy): Promise<void> {\n const url = base()\n\n if (url === null) return\n\n const agreed = await confirm({\n title: t('editing.restore-title'),\n message: t('editing.restore-text'),\n confirmText: t('editing.restore'),\n cancelText: t('editing.cancel'),\n })\n\n if (!agreed) return\n\n working.value = true\n\n try {\n await admin.http.post(`${url}/${copy.id}/restore`)\n emit('restored')\n toast.success(t('editing.restored'))\n await load()\n } catch (error) {\n toast.danger(message(error))\n } finally {\n working.value = false\n }\n}\n\nwatch(\n () => [props.entity, props.id, props.stamp],\n () => void load(),\n { immediate: true },\n)\n</script>\n\n<template>\n <section v-if=\"drafts === null || drafts.length > 0\" class=\"wx-drafts\">\n <header class=\"wx-drafts__head\">\n <wx-heading :level=\"3\" size=\"sm\">{{ t('editing.drafts') }}</wx-heading>\n <wx-text size=\"sm\" tone=\"muted\">{{ t('editing.drafts-text') }}</wx-text>\n </header>\n\n <div class=\"wx-drafts__list\">\n <wx-skeleton v-if=\"drafts === null\" class=\"wx-drafts__ghost\" :rows=\"2\" />\n <div v-for=\"copy in drafts ?? []\" :key=\"copy.id\" class=\"wx-drafts__row\">\n <div class=\"wx-drafts__who\">\n <date-text v-if=\"copy.created_at\" :value=\"copy.created_at\" size=\"md\" tone=\"default\" />\n <wx-text size=\"sm\" tone=\"muted\">\n {{ copy.author ?? t('editing.somebody') }} · {{ t(`editing.source-${copy.source}`) }}\n </wx-text>\n <wx-text v-if=\"changed(copy)\" size=\"sm\" class=\"wx-drafts__changed\">\n {{ changed(copy) }}\n </wx-text>\n </div>\n <wx-badge :type=\"copy.kind === 'overwritten' ? 'warning' : 'default'\" dot>\n {{ t(`editing.${copy.kind}`) }}\n </wx-badge>\n <wx-button\n v-if=\"canRestore\"\n size=\"sm\"\n variant=\"outline\"\n :loading=\"working\"\n @click=\"restore(copy)\"\n >\n {{ t('editing.restore') }}\n </wx-button>\n </div>\n </div>\n </section>\n</template>\n\n<style scoped>\n.wx-drafts {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-8);\n}\n\n.wx-drafts__head {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-2);\n}\n\n.wx-drafts__list {\n display: flex;\n flex-direction: column;\n border: 1px solid var(--wx-border-default);\n border-radius: var(--wx-radius-md);\n background: var(--wx-bg-surface);\n overflow: hidden;\n}\n\n.wx-drafts__ghost {\n padding: var(--wx-space-10) var(--wx-space-12);\n}\n\n.wx-drafts__row {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: var(--wx-space-12);\n padding: var(--wx-space-10) var(--wx-space-12);\n border-block-end: 1px solid var(--wx-border-default);\n}\n\n.wx-drafts__row:last-child {\n border-block-end: 0;\n}\n\n.wx-drafts__changed {\n overflow-wrap: anywhere;\n}\n\n.wx-drafts__who {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-width: 160px;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { WxAlert, WxText } from '@webx-ui/core'\nimport { useAdmin } from './admin'\nimport { useDates } from './dates'\nimport { useTranslate } from './i18n'\nimport DateText from './DateText.vue'\n\ndefineOptions({ name: 'WxBackupNote' })\n\n/**\n * One line about the nightly database dump (§6 of the backups spec).\n *\n * A backup whose breakage is discovered on the day it was needed is not a backup, and the\n * cheapest guard against that is a sentence somewhere a person already looks. This is that\n * sentence: no screen, no buttons, nothing to click. When the newest file is older than two\n * days — or there is no file at all — the same line turns into a warning, because by then the\n * only useful thing to say is \"go and look at the cron\".\n *\n * It renders nothing at all for a site that has switched the dump off, and nothing for an\n * administrator without `settings.view`: whoever may see the system section may see this, and\n * for everybody else it is a fact about the server that is none of their business.\n */\nconst STALE_AFTER_HOURS = 48\n\nconst admin = useAdmin()\nconst dates = useDates()\nconst t = useTranslate('webx-admin')\n\nconst backup = computed(() => admin.state.manifest?.backup ?? null)\nconst visible = computed(() => backup.value !== null && admin.can('settings.view'))\n\nconst stale = computed(() => {\n const at = backup.value?.at\n\n if (at === null || at === undefined) return true\n\n const taken = new Date(at).getTime()\n\n return Number.isNaN(taken) || Date.now() - taken > STALE_AFTER_HOURS * 3600_000\n})\n\n/*\n * \"4.2 MB\" in English, \"4,2 МБ\" in Russian, and the right abbreviation in the seven others —\n * from `Intl` rather than from three words in every language file, which is what a unit of\n * measure is for.\n */\nconst size = computed(() => {\n const bytes = backup.value?.bytes\n\n if (bytes === null || bytes === undefined) return ''\n\n const units = ['byte', 'kilobyte', 'megabyte', 'gigabyte', 'terabyte'] as const\n let value = bytes\n let unit = 0\n\n while (value >= 1024 && unit < units.length - 1) {\n value /= 1024\n unit++\n }\n\n return new Intl.NumberFormat(admin.i18n.state.locale, {\n style: 'unit',\n unit: units[unit],\n unitDisplay: 'short',\n maximumFractionDigits: value >= 10 || unit === 0 ? 0 : 1,\n }).format(value)\n})\n\n/* The warning names the day rather than saying \"two days ago\": the reader is about to go and\n look at a log, and a date is what they will be looking for in it. */\nconst warning = computed(() =>\n backup.value?.at == null\n ? t('backup.never')\n : t('backup.stale', { date: dates.short(backup.value.at) }),\n)\n</script>\n\n<template>\n <wx-alert v-if=\"visible && stale\" type=\"warning\" variant=\"soft\" :description=\"warning\" />\n\n <p v-else-if=\"visible\" class=\"wx-backup-note\">\n <wx-text size=\"sm\" tone=\"muted\">{{ t('backup.title') }}</wx-text>\n <date-text :value=\"backup?.at\" />\n <wx-text v-if=\"size\" size=\"sm\" tone=\"muted\">· {{ size }}</wx-text>\n </p>\n</template>\n\n<style scoped>\n/*\n * A footnote, and shaped like one: it wraps rather than pushes, because the three parts of it\n * are three separate elements and a narrow panel has to be allowed to break between them.\n */\n.wx-backup-note {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--wx-space-4) var(--wx-space-6);\n margin: 0;\n}\n</style>\n"],"names":["i18nKey","useI18n","i18n","inject","useTranslate","namespace","createI18n","provideI18n","app","options","fallback","state","reactive","builtIn","dictionary","lookup","source","path","node","segment","translate","key","params","explicitNamespace","rest","line","fill","messages","next","locale","nextFallback","filled","name","value","createCategoriesApi","admin","base","data","body","query","search","input","id","values","ids","reorderItems","category","props","__props","_useModel","useAdmin","t","catalogue","ref","watch","payload","row","chosen","computed","names","one","rows","available","add","picked","remove","_openBlock","_createElementBlock","_hoisted_1","_createVNode","_unref","WxSortableList","$event","_withCtx","item","index","_createElementVNode","_hoisted_2","_toDisplayString","_createBlock","WxBadge","WxAction","WxSelect","loaded","offered","_mergeProps","_ctx","HttpError","message","status","errors","retryAfter","UNSAFE","createHttp","baseUrl","csrfUrl","doFetch","onUnauthenticated","standingHeaders","csrfFetched","ensureCsrfCookie","force","readCookie","send","method","retried","unsafe","headers","token","response","url","request","toError","header","full","serialised","part","createRelationsApi","target","normaliseRelations","relationOwnerKey","provideRelationOwner","owner","provide","useRelationOwner","api","known","missing","forbidden","candidates","searching","term","remember","unknown","answered","error","candidate","self","found","pick","option","nextTick","locked","placeholder","mark","_normalizeClass","_hoisted_3","_hoisted_4","_createTextVNode","_hoisted_5","_hoisted_6","WxAutocomplete","COLLECTION_MAX_LIMIT","asked","collectionSources","sources","relationTargets","title","a","b","related","type","current","normaliseCollection","fields","limit","defaultMarkup","model","useId","info","categories","write","patch","relatedType","relationOptions","relatedTarget","targets","relateTo","relate","relateToCurrent","on","markup","markupHint","_Fragment","_hoisted_7","WxSwitch","_cache","_hoisted_8","RelationsField","_hoisted_9","_hoisted_10","_hoisted_11","WxInputNumber","_hoisted_12","_hoisted_13","WxButton","emptyLink","createLinksApi","links","routes","section","echo","link","sectionOptions","hint","routeOptions","route","onMounted","restore","loadRoutes","resolved","setTarget","setSection","clearEntity","setUrl","setHash","hash","anchor","setNewTab","toggleRel","rel","kept","parts","REL","WxSegmented","text","WxText","WxInput","WxCheckbox","_renderList","LinkPicker","$attrs","adminMessages","createDates","formatters","format","shape","at","formatter","time","toDate","now","days","daysApart","useDates","standalone","loose","day","d","dates","exact","iso","WxTooltip","errorText","statusOf","bodyMessage","seconds","useErrorText","createHistoryApi","page","runId","historySubjectKey","provideHistorySubject","subject","useHistorySubject","historyValue","empty","max","cut","entries","lastPage","loading","loadingMore","recordId","heading","load","first","toast","more","__expose","who","entry","what","from","change","to","long","runLabel","runOpen","run","runSearch","runLoading","runTicket","loadRun","ticket","answer","openRun","summaryLines","summary","runFailed","failed","WxEmpty","WxSkeleton","WxAvatar","DateText","WxDialog","_hoisted_14","_hoisted_15","_hoisted_16","_hoisted_17","_hoisted_18","_hoisted_19","_hoisted_20","_hoisted_21","RouterLink","ordered","action","divided","WxActions","WxDropdownItem","emit","__emit","slots","useSlots","root","useTemplateRef","width","useElementWidth","narrow","primary","buttons","menu","backTo","hasActions","$slots","_renderSlot","WxBackButton","WxHeading","WxIcon","WxRowMenu","view","WxScreenHead","WxTabs","WxCard","KEY","documents","richTextKeys","keys","pattern","html","match","decode","area","rewrite","addresses","template","changed","element","address","attribute","rewriteRichText","pickImage","attrs","useAttrs","shown","sent","assetUrls","labels","WxRichText","recordAddressKey","provideRecordAddress","useRecordAddress","locales","useLocales","orphaned","prefix","head","slug","written","moving","_createSlots","WxAlert","adminTypes","ListScreen","RichTextField","LinkField","CategoriesField","SourceSelect","CollectionField","SlugField","HistoryFeed","adminKey","createAdminContext","nav","manifest","module","registered","groups","declared","top","byGroup","group","list","sections","captioned","types","blockLabelsOf","blockLabelsPending","screens","loadScreen","pending","loadSession","reload","adoptTheme","switchLocale","isUnauthenticated","refreshManifest","user","setLocale","code","refresh","knownAssets","cachedAssetUrls","paths","batch","context","urls","loader","permission","provideAdmin","router","useRouter","destination","e","WxResult","useRoute","revealed","reveal","el","_component_wx_menu","_component_wx_menu_item","_component_wx_submenu","_component_wx_menu_group","shellEl","layout","collapsed","showAside","drawerOpen","toggle","close","useResponsiveShell","bare","logo","size","shellLayoutFor","watchEffect","onUnmounted","WxToaster","_component_router_view","_component_wx_loading","_component_wx_result","_component_wx_button","_component_wx_container","_component_wx_header","_component_wx_action","_component_wx_text","_component_wx_aside","_component_wx_main","_component_wx_drawer","fetchSiteBarContext","http","apiPath","language","onSitePage","listener","handler","event","siteBarKey","useSiteBar","publishSiteBarEntity","entity","discardSiteBarEntity","tellSitePage","tab","settling","moduleTabs","drawn","namesOf","tabNames","remembered","panelHref","STATUS_TYPES","quiet","caught","draft","mayPublish","busy","publish","discard","confirm","onKeydown","onBeforeUnmount","_component_wx_badge","_component_wx_alert","_component_wx_tabs","_component_wx_tab","_component_wx_descriptions","_component_wx_descriptions_item","_resolveDynamicComponent","STORED_THEME","themeKey","useTheme","theme","provideTheme","createTheme","read","paint","applyTheme","systemTheme","watchSystemTheme","preference","stored","STORED_LOCALE","createAdmin","manifestUrl","readManifestUrl","basePath","modules","landing","AdminLanding","SiteBarScreen","AdminNotFound","createRouter","createWebHistory","preferredLocale","dictionaries","loadDictionary","rememberLocale","markDocumentLanguage","createApp","rootComponent","WebxUI","editing","dateLocaleKey","dateTimezoneKey","localesKey","loadPanelLocales","plugin","h","AdminNav","AdminShell","renderMarkdown","lines","out","escape","level","inline","isItem","items","tag","paragraph","HOLE","spans","withHoles","_","pluralForm","count","form","useBodyKeys","GONE","mergeThreeWay","mine","theirs","choices","walk","merge","changedPaths","before","after","diff","sameValue","isPlain","conflictId","step","isNodeList","mergeList","mergeObject","settle","merged","byKey","ours","other","m","typeOf","theirsMoved","sameOrder","mineMoved","lead","follow","order","placed","back","position","kind","keepMine","keepTheirs","old","inB","inA","left","right","useEditingLabels","shallowRef","blocks","screenLabels","blockName","humanize","label","codes","inBlock","named","nodes","words","EXPLAINING","useEditing","clone","conflict","incoming","editors","place","events","recent","changedLast","canWrite","trashed","gone","stopped","blocked","trashedBy","unsaved","ready","mounted","opened","beat","take","settleWith","conflicts","refused","clean","pull","choose","choice","resolve","open","resolveTheirs","when","places","deed","detail","mainPath","describe","i","deeds","last","phrase","done","timeless","map","own","beforePublish","held","explained","failure","heardGone","canRestore","restoreFromBin","dismissEvents","purge","texts","copyText","timer","reading","stopWatching","seen","stateKey","me","absorb","ping","newest","fresh","moved","leave","preview","clip","plain","createNotesApi","noteId","shortcodeToken","shortcode","loadShortcodes","forgetShortcodes","useShortcodes","tokens","MIN_CHUNK","STORAGE_KEY","SPEED_WINDOW","uploadFingerprint","file","unfinishedUploads","purpose","storage","defaultStorage","readAll","forgetUnfinishedUpload","fingerprint","writeAll","useChunkedUpload","retries","retryDelay","uploaded","total","speed","progress","remaining","chunk","generation","controller","samples","init","sendWith","promise","reject","finish","result","fail","setOffset","offset","sample","forPurpose","others","forget","stop","start","sync","headerOffset","failures","isOffline","goOffline","wait","pause","resume","cancel","session","onOffline","onOnline","getCurrentScope","onScopeDispose","readonly","ms","parsed","categoryEditorKey","provideCategoryEditor","editor","useCategoryEditor","itemOrderMode","useItemOrder","mode","useCategoryWords","word","dismiss","useModal","w","saving","field","errorOf","submit","WxSpace","WxFormItem","addressed","canManage","create","createModal","CategoryCreateDialog","actions","countOf","made","menuOf","itemsOf","reorder","_component_router_link","WxAddressNote","RowMenu","landed","fade","showing","tree","WxScreenRenderer","snapshot","dirty","localizedValue","save","leaveGuard","onBeforeRouteLeave","leads","ScreenHead","WxBreadcrumb","WxBreadcrumbItem","Screen","WxActionBar","SaveState","bound","defineComponent","categoryRoutes","CategoriesPage","CategoryEditorPage","rowMenuWidth","filter","isOpen","WxPopover","notes","sending","edited","edit","note","saved","actionsFor","WxTextarea","conflictTitle","conflictAlert","incomingText","eventsText","trashedTitle","by","goneTitle","restoring","keep","side","which","takeTheirs","drafts","copy","working","STALE_AFTER_HOURS","backup","visible","stale","taken","bytes","units","unit","warning"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../src/i18n.ts","../src/categories/api.ts","../src/categories/CategoriesField.vue","../src/categories/SourceSelect.vue","../src/http.ts","../src/relations/api.ts","../src/relations/RelationsField.vue","../src/collections/api.ts","../src/collections/CollectionField.vue","../src/links.ts","../src/LinkPicker.vue","../src/LinkField.vue","../src/messages.ts","../src/dates.ts","../src/DateText.vue","../src/errors.ts","../src/history.ts","../src/HistoryFeed.vue","../src/BackButton.vue","../src/RowMenu.vue","../src/ScreenHead.vue","../src/ListScreen.vue","../src/richTextAddresses.ts","../src/RichTextField.vue","../src/address.ts","../src/SlugField.vue","../src/screenTypes.ts","../src/admin.ts","../src/AdminLanding.vue","../src/AdminNav.vue","../src/AdminNotFound.vue","../src/AdminShell.vue","../src/siteBar.ts","../src/SiteBarScreen.vue","../src/theme.ts","../src/createAdmin.ts","../src/markdown.ts","../src/plural.ts","../src/keys.ts","../src/merge.ts","../src/editingLabels.ts","../src/editing.ts","../src/notes.ts","../src/shortcodes.ts","../src/uploads.ts","../src/categories/editor.ts","../src/categories/ordering.ts","../src/AddressNote.vue","../src/categories/words.ts","../src/categories/CategoryCreateDialog.vue","../src/categories/CategoriesPage.vue","../src/SaveState.vue","../src/Screen.vue","../src/categories/CategoryEditorPage.vue","../src/categories/routes.ts","../src/rowMenu.ts","../src/FilterChips.vue","../src/RenameButton.vue","../src/HelpButton.vue","../src/NotesFeed.vue","../src/EditingAlerts.vue","../src/DraftsList.vue","../src/BackupNote.vue"],"sourcesContent":["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","import type { ScreenModel } from '@webx-ui/schema'\nimport type { AdminContext } from '../admin'\nimport type { CategoriesPayload, CategoryDetail, CategoryRow } from './types'\n\n/**\n * The categories of one module, behind the routes `CategoryRoutes::register()` gives it.\n *\n * The path is the module's (`blog/rubrics`), the answers are everybody's: one controller serves\n * every module, so one client does too.\n */\nexport interface CategoriesApi {\n /** Every category, in the order of the site's menu. `q` narrows by name or address. */\n list(query?: { q?: string; visible?: boolean }): Promise<CategoriesPayload>\n /** A name, and an address made of it unless one is given. */\n create(input: { title: string; slug?: string }): Promise<CategoryRow>\n get(id: number): Promise<CategoryDetail>\n /** The values of the screen. A refused field comes back as a 422 under its name. */\n save(id: number, values: ScreenModel): Promise<CategoryDetail>\n /** Into the bin — a 422 naming the number of items while it still holds any. */\n remove(id: number): Promise<void>\n restore(id: number): Promise<CategoryRow>\n /** The whole order, as it is on screen. */\n reorder(ids: number[]): Promise<void>\n}\n\nexport function createCategoriesApi(admin: AdminContext, path: string): CategoriesApi {\n const base = `${admin.apiPath}/${path.replace(/^\\/+|\\/+$/g, '')}`\n const data = <T>(body: { data: T }): T => body.data\n\n return {\n list: (query = {}) => {\n const search = new URLSearchParams()\n\n if (query.q) search.set('q', query.q)\n if (query.visible) search.set('visible', '1')\n\n return admin.http.get<CategoriesPayload>(search.size > 0 ? `${base}?${search}` : base)\n },\n create: (input) => admin.http.post<{ data: CategoryRow }>(base, input).then(data),\n get: (id) => admin.http.get<{ data: CategoryDetail }>(`${base}/${id}`).then(data),\n save: (id, values) =>\n admin.http.put<{ data: CategoryDetail }>(`${base}/${id}`, { values }).then(data),\n remove: (id) => admin.http.delete<void>(`${base}/${id}`).then(() => undefined),\n restore: (id) => admin.http.post<{ data: CategoryRow }>(`${base}/${id}/restore`, {}).then(data),\n reorder: (ids) => admin.http.post<void>(`${base}/reorder`, { ids }).then(() => undefined),\n }\n}\n\n/**\n * The order of a module's records, written the way it is dragged (§2.5 of the services spec):\n * without `category` the whole list, with it the order inside that one category.\n *\n * `path` is the module's list of records (`services/items`); the server half is\n * `CategoryRoutes::items()`. A module that orders by date — the blog — never calls it.\n */\nexport function reorderItems(\n admin: AdminContext,\n path: string,\n ids: number[],\n category: number | null = null,\n): Promise<void> {\n const base = `${admin.apiPath}/${path.replace(/^\\/+|\\/+$/g, '')}`\n\n return admin.http\n .post<void>(`${base}/reorder`, category === null ? { ids } : { ids, category })\n .then(() => undefined)\n}\n","<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\nimport {\n WxAction,\n WxBadge,\n WxSelect,\n WxSortableList,\n type SelectModelValue,\n type SelectOption,\n} from '@webx-ui/core'\nimport { useAdmin } from '../admin'\nimport { useTranslate } from '../i18n'\nimport { createCategoriesApi } from './api'\n\n/**\n * The categories a record is in, in the order somebody dragged them into (`wx-categories`).\n *\n * The order is the whole control. The first category is the main one — it goes into the\n * breadcrumbs — so a separate switch for \"which is the main one\" would be a second control\n * saying what this one already says. A list and not a multi-select for the same reason: a bag of\n * checkboxes has no first. A module without a main category says `main: false`, and the order\n * is then only the order.\n *\n * Which categories are on offer is the module's list, asked from `source` — the path its\n * categories answer at (`blog/rubrics`). The server's type of the same name checks the ids\n * against the model registered for that path, so both halves read the one string.\n *\n * The words are props, with the panel's \"category\" as the default: a module names its own in the\n * screen description (`trans::webx-blog::article.rubric-add`).\n */\ndefineOptions({ name: 'WxCategoriesField' })\n\nconst props = withDefaults(\n defineProps<{\n /** The API the categories answer at, under the panel's: `blog/rubrics`. */\n source: string\n /** Whether the first one is the main one, and wears the badge that says so. */\n main?: boolean\n disabled?: boolean\n mainText?: string\n addText?: string\n removeText?: string\n emptyText?: string\n noneLeftText?: string\n }>(),\n {\n main: true,\n disabled: false,\n mainText: undefined,\n addText: undefined,\n removeText: undefined,\n emptyText: undefined,\n noneLeftText: undefined,\n },\n)\n\nconst value = defineModel<number[] | null>({ default: () => [] })\n\nconst admin = useAdmin()\nconst t = useTranslate('webx-admin')\n\ninterface Choice {\n id: number\n name: string\n}\n\nconst catalogue = ref<Choice[]>([])\n\nwatch(\n () => props.source,\n async (source) => {\n try {\n const payload = await createCategoriesApi(admin, source).list()\n catalogue.value = payload.data.map((row) => ({ id: row.id, name: row.name }))\n } catch {\n // The chosen ones still draw — by number — and the box offers nothing: a list that did not\n // arrive is not a reason to lose the categories already on the record.\n catalogue.value = []\n }\n },\n { immediate: true },\n)\n\nconst chosen = computed<number[]>(() => (Array.isArray(value.value) ? value.value : []))\n\nconst names = computed(() => new Map(catalogue.value.map((one) => [one.id, one.name])))\n\n/**\n * The chosen categories as rows. Writable, because that is how `WxSortableList` hands a reorder\n * back — the ids are the value, and the rows are only what they are drawn as.\n */\nconst rows = computed<Choice[]>({\n get: () => chosen.value.map((id) => ({ id, name: names.value.get(id) ?? `#${id}` })),\n set: (next) => {\n value.value = next.map((row) => row.id)\n },\n})\n\nconst available = computed<SelectOption[]>(() =>\n catalogue.value\n .filter((one) => !chosen.value.includes(one.id))\n .map((one) => ({ label: one.name, value: one.id })),\n)\n\nfunction add(picked: SelectModelValue): void {\n if (typeof picked !== 'number' || chosen.value.includes(picked)) return\n\n value.value = [...chosen.value, picked]\n}\n\nfunction remove(id: number): void {\n value.value = chosen.value.filter((one) => one !== id)\n}\n</script>\n\n<template>\n <div class=\"wx-categories-field\">\n <wx-sortable-list\n v-model=\"rows\"\n plain\n size=\"sm\"\n item-key=\"id\"\n item-label=\"name\"\n :disabled=\"props.disabled\"\n :empty-text=\"props.emptyText ?? t('categories.field-empty')\"\n >\n <template #default=\"{ item, index }\">\n <span class=\"wx-categories-field__name\">{{ (item as Choice).name }}</span>\n <!-- Said on the row rather than in a legend under the list: the rule is \"the first\n one\", and the place to say so is the first one. -->\n <wx-badge v-if=\"props.main && index === 0\" type=\"primary\">\n {{ props.mainText ?? t('categories.field-main') }}\n </wx-badge>\n </template>\n\n <template #actions=\"{ item }\">\n <wx-action\n type=\"remove\"\n size=\"sm\"\n :disabled=\"props.disabled\"\n :title=\"props.removeText ?? t('categories.field-remove')\"\n @click=\"remove((item as Choice).id)\"\n />\n </template>\n </wx-sortable-list>\n\n <wx-select\n :model-value=\"null\"\n :options=\"available\"\n :placeholder=\"props.addText ?? t('categories.field-add')\"\n :disabled=\"props.disabled || available.length === 0\"\n :empty-text=\"props.noneLeftText ?? t('categories.field-none-left')\"\n filterable\n teleport\n :aria-label=\"props.addText ?? t('categories.field-add')\"\n @update:model-value=\"add\"\n />\n </div>\n</template>\n\n<style scoped>\n.wx-categories-field {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-8);\n}\n\n/*\n * The cell a row's content goes into is a block, so the name and the \"main\" badge stood against\n * each other with nothing between them. The slot makes a line of its own contents; through\n * `:deep()`, because the cell belongs to `WxSortableList`.\n */\n.wx-categories-field :deep(.wx-sortable-list__content) {\n display: flex;\n align-items: center;\n gap: var(--wx-space-8);\n min-width: 0;\n}\n\n/* Shrinks rather than grows: a name that pushed the badge to the far end of the row would say\n \"main\" about the distance instead of about the category. */\n.wx-categories-field__name {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\nimport { WxSelect, type SelectOption } from '@webx-ui/core'\nimport { useAdmin } from '../admin'\nimport { createCategoriesApi } from './api'\n\n/**\n * `wx-select` as the panel draws it: the core's select, whose options may be a module's list\n * rather than written into the screen.\n *\n * A screen names the list by `source` — the path it answers at under the panel's API\n * (`catalog/brands`), the same string `wx-categories` reads — and the options are its records,\n * by name. A reference book is edited while the screen stays the same, so its options cannot be\n * part of the JSON. Without `source` this is the core's select with the options it was given.\n *\n * Everything else — `filterable`, `clearable`, the placeholder that says what empty means — goes\n * through to the core's select untouched.\n */\ndefineOptions({ name: 'WxSourceSelect', inheritAttrs: false })\n\nconst props = withDefaults(\n defineProps<{\n /** The API a module's list answers at, under the panel's: `catalog/stock`. */\n source?: string\n options?: SelectOption[]\n }>(),\n { source: undefined, options: () => [] },\n)\n\nconst admin = useAdmin()\nconst loaded = ref<SelectOption[]>([])\n\nwatch(\n () => props.source,\n async (source) => {\n if (!source) {\n loaded.value = []\n\n return\n }\n\n try {\n const payload = await createCategoriesApi(admin, source).list()\n loaded.value = payload.data.map((row) => ({ label: row.name, value: row.id }))\n } catch {\n // The value stays and shows as it is; a list that did not arrive is not a reason to lose it.\n loaded.value = []\n }\n },\n { immediate: true },\n)\n\nconst offered = computed<SelectOption[]>(() => (props.source ? loaded.value : props.options))\n</script>\n\n<template>\n <wx-select v-bind=\"$attrs\" :options=\"offered\" />\n</template>\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 /**\n * Where to fetch the CSRF cookie from before an unsafe request. The default is the panel's\n * own route; a panel served under another `api_path` passes its own, as `createAdmin` does.\n */\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 * For a host whose web server lets through nothing but GET and POST, as some shared hosting\n * does before PHP is even reached: PUT, PATCH and DELETE go out as POST and say what they\n * really are. `'header'` (or `true`) says it in `X-HTTP-Method-Override`; `'field'` in a\n * `_method` field, for a firewall that strips the header too. Laravel understands both.\n */\n methodOverride?: MethodOverride\n}\n\n/** How a method a web server refuses travels inside a POST; `true` is `'header'`. */\nexport type MethodOverride = boolean | 'header' | 'field'\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 SendOptions extends RequestOptions {\n /** Sent as it is — bytes, a `Blob`, a string — with no `Content-Type` added for it. */\n body?: BodyInit | null\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 * A request with the panel's conventions (the CSRF cookie, the standing headers, 419 and 401)\n * and nothing else: the body goes as it is and the response comes back whatever its status.\n * For what JSON does not fit — the pieces of a chunked upload, a `HEAD` asked for a header.\n * Optional so that a hand-made client in a test or a project does not have to grow one.\n */\n send?(method: string, path: string, options?: SendOptions): Promise<Response>\n}\n\nconst UNSAFE = new Set(['POST', 'PUT', 'PATCH', 'DELETE'])\nconst OVERRIDABLE = new Set(['PUT', 'PATCH', 'DELETE'])\n\nexport function createHttp(options: HttpOptions = {}): Http {\n const baseUrl = (options.baseUrl ?? '').replace(/\\/$/, '')\n // The panel's own, since `webx-ui/module-auth` dropped Sanctum: a route in the `web` group\n // that does nothing but let the framework set the cookie.\n const csrfUrl = options.csrfUrl ?? '/api/cms/auth/csrf-cookie'\n const doFetch = options.fetch ?? globalThis.fetch.bind(globalThis)\n const onUnauthenticated = options.onUnauthenticated\n const standingHeaders = options.headers\n const override = options.methodOverride === true ? 'header' : options.methodOverride || null\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 send(\n method: string,\n path: string,\n options: SendOptions & { json?: boolean } = {},\n retried = false,\n ): Promise<Response> {\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 (options.json === true) {\n headers['Content-Type'] = 'application/json'\n }\n\n // Everything above — the CSRF token most of all — follows the method the caller meant;\n // only what goes on the wire changes.\n let sentMethod = method\n let body = options.body ?? undefined\n let query = options.query\n\n if (override !== null && OVERRIDABLE.has(method)) {\n sentMethod = 'POST'\n\n if (override === 'header') {\n headers['X-HTTP-Method-Override'] = method\n } else {\n const field = withMethodField(method, body, options.json === true)\n\n if (field === null) {\n // Bytes or a blob have nowhere to carry a field; Laravel reads `_method` from the\n // query string as well.\n query = { ...query, _method: method }\n } else {\n body = field.body\n\n if (field.json) {\n headers['Content-Type'] = 'application/json'\n }\n }\n }\n }\n\n const response = await doFetch(url(baseUrl, path, query), {\n method: sentMethod,\n credentials: 'same-origin',\n headers,\n signal: options.signal,\n 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 send(method, path, options, true)\n }\n\n if (response.status === 401) {\n onUnauthenticated?.()\n }\n\n return response\n }\n\n async function request<T>(\n method: string,\n path: string,\n body?: unknown,\n options: RequestOptions = {},\n ): Promise<T> {\n const response = await send(method, path, {\n ...options,\n json: body !== undefined,\n body: body === undefined ? undefined : JSON.stringify(body),\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 send: (method, path, options) => send(method.toUpperCase(), path, options),\n }\n}\n\n/**\n * The body with `_method` in it, or `null` when the body is not something a field fits in.\n * No body at all becomes a JSON one, which Laravel reads the field out of.\n */\nfunction withMethodField(\n method: string,\n body: BodyInit | undefined,\n json: boolean,\n): { body: BodyInit; json: boolean } | null {\n if (body === undefined) {\n return { body: JSON.stringify({ _method: method }), json: true }\n }\n\n if (typeof FormData !== 'undefined' && body instanceof FormData) {\n body.set('_method', method)\n\n return { body, json: false }\n }\n\n if (typeof URLSearchParams !== 'undefined' && body instanceof URLSearchParams) {\n body.set('_method', method)\n\n return { body, json: false }\n }\n\n if (json && typeof body === 'string') {\n const parsed: unknown = JSON.parse(body)\n\n if (parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)) {\n return { body: JSON.stringify({ ...parsed, _method: method }), json: true }\n }\n }\n\n return null\n}\n\nexport async 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 { inject, provide, type InjectionKey, type Ref } from 'vue'\nimport type { AdminContext } from '../admin'\n\n/**\n * One record a relation can point at, as `GET /api/cms/relations/{target}` draws it (§3.7 of the\n * recipes spec) — already in the panel's language.\n */\nexport interface RelationCandidate {\n id: number\n title: string\n /** A second line: a category, an address. */\n subtitle: string | null\n /** A small picture's address, when the target has one. */\n thumb: string | null\n /** Whether the site shows it. A hidden one stays chosen and says so: the site skips it. */\n visible: boolean\n /** In the bin — a kind of hidden that says where to look. Only names of the chosen carry it. */\n trashed?: boolean\n}\n\nexport interface RelationsApi {\n /** Candidates for the \"Add\" box, searched by the server. */\n search(query: string): Promise<RelationCandidate[]>\n /** The chosen ones by id: a form opens with numbers, and a row needs a name. */\n named(ids: number[]): Promise<RelationCandidate[]>\n}\n\n/**\n * The target's key is a path segment, and the ids go as `ids[]` — the panel's `query` option\n * holds one value per key, so the list is written into the path by hand.\n */\nexport function createRelationsApi(admin: AdminContext, target: string): RelationsApi {\n const path = `${admin.apiPath}/relations/${encodeURIComponent(target)}`\n\n return {\n search: (query) =>\n admin.http\n .get<{ data: RelationCandidate[] }>(path, { query: { q: query } })\n .then((body) => body.data),\n named: (ids) =>\n ids.length === 0\n ? Promise.resolve([])\n : admin.http\n .get<{\n data: RelationCandidate[]\n }>(`${path}?${ids.map((id) => `ids[]=${id}`).join('&')}`)\n .then((body) => body.data),\n }\n}\n\n/** A list of ids, in order, without repeats or anything that is not one — what the server keeps. */\nexport function normaliseRelations(value: unknown): number[] {\n if (!Array.isArray(value)) return []\n\n return [...new Set(value.map(Number).filter((id) => Number.isInteger(id) && id > 0))]\n}\n\n/**\n * The record whose form the field is on — so that \"similar recipes\" does not offer the recipe\n * itself. A described screen has no way to say which record it is editing, and the editor that\n * hosts it does, so the editor provides it the way it provides the address (`address.ts`).\n */\nexport interface RelationOwner {\n /** Its key in the registry of targets: `recipe`. */\n type: string\n /** `null` for a record not saved yet, which nothing can have chosen. */\n id: Ref<number | null>\n}\n\nexport const relationOwnerKey: InjectionKey<RelationOwner> = Symbol('wx-relation-owner')\n\nexport function provideRelationOwner(owner: RelationOwner): void {\n provide(relationOwnerKey, owner)\n}\n\nexport function useRelationOwner(): RelationOwner | null {\n return inject(relationOwnerKey, null)\n}\n","<script setup lang=\"ts\">\nimport { computed, nextTick, ref, watch } from 'vue'\nimport {\n WxAction,\n WxAutocomplete,\n WxBadge,\n WxSortableList,\n type AutocompleteOption,\n} from '@webx-ui/core'\nimport { useAdmin } from '../admin'\nimport { HttpError } from '../http'\nimport { useTranslate } from '../i18n'\nimport {\n createRelationsApi,\n normaliseRelations,\n useRelationOwner,\n type RelationCandidate,\n} from './api'\n\n/**\n * The records of another section this one points at, in the order they were put in\n * (`wx-relations`): the services a recipe is for, the recipes that go with it.\n *\n * The value is ids and nothing else. What they are called, whether the site still shows them, the\n * candidates for \"Add\" — all of that is the target's to say, over `GET /api/cms/relations/{target}`\n * (§3.7 of the recipes spec): with `q` it searches, with `ids[]` it names what is already chosen.\n * The target is the schema's (`props.target`), and a target the server does not know never reaches\n * the panel — the server takes the node off the screen, so recipes without the services module\n * have no \"Services\" field at all rather than a broken one.\n *\n * A chosen record the site does not show — unpublished, in the bin — stays in the list, marked:\n * dropping it silently would change the record without anybody having touched it, and the site\n * skips it anyway. One the server does not know at all is drawn by number, for the same reason.\n *\n * An administrator who may not see the target's records gets a 403 on both questions, and the\n * field becomes a list of what is chosen and nothing more: no names to reorder by, nothing to add\n * from — and removing a record you cannot see is not a choice anybody can make well.\n *\n * The order is kept because a template reads it (\"the main service first\"); `sortable: false` is\n * for a choice whose order means nothing, as in a block's filter.\n */\ndefineOptions({ name: 'WxRelationsField' })\n\nconst props = withDefaults(\n defineProps<{\n /** The target's key in the server's registry: `service`, `recipe`. */\n target: string\n /** How many may be chosen; `null` for any number. */\n max?: number | null\n sortable?: boolean\n disabled?: boolean\n addText?: string\n emptyText?: string\n }>(),\n {\n max: null,\n sortable: true,\n disabled: false,\n addText: undefined,\n emptyText: undefined,\n },\n)\n\nconst value = defineModel<number[] | null>({ default: () => [] })\n\nconst admin = useAdmin()\nconst owner = useRelationOwner()\nconst t = useTranslate('webx-admin')\n\nconst api = computed(() => createRelationsApi(admin, props.target))\n\n/** What the server said about each id it was asked about, kept for the life of the field. */\nconst known = ref(new Map<number, RelationCandidate>())\n/** Ids the server was asked to name and did not: gone, or behind a module that is not installed. */\nconst missing = ref(new Set<number>())\nconst forbidden = ref(false)\n\nconst candidates = ref<RelationCandidate[]>([])\nconst searching = ref(false)\nconst term = ref('')\n\nconst chosen = computed(() => normaliseRelations(value.value))\n\nfunction remember(rows: RelationCandidate[]): void {\n const next = new Map(known.value)\n\n for (const row of rows) next.set(row.id, row)\n\n known.value = next\n}\n\n/**\n * Only what has no name yet is asked about: picking from the search already brought the name of\n * what was picked, and a form that asks again on every edit asks about a list it just drew.\n */\nasync function name(ids: number[]): Promise<void> {\n const unknown = ids.filter((id) => !known.value.has(id) && !missing.value.has(id))\n\n if (unknown.length === 0 || forbidden.value) return\n\n try {\n const rows = await api.value.named(unknown)\n remember(rows)\n\n const answered = new Set(rows.map((row) => row.id))\n missing.value = new Set([...missing.value, ...unknown.filter((id) => !answered.has(id))])\n } catch (error) {\n // Anything else is a request that did not come back, and the rows keep their numbers until\n // the next change asks again. A refusal is an answer, and it is the same for every question.\n if (error instanceof HttpError && error.status === 403) forbidden.value = true\n }\n}\n\nwatch(\n () => props.target,\n () => {\n known.value = new Map()\n missing.value = new Set()\n forbidden.value = false\n candidates.value = []\n void name(chosen.value)\n },\n)\n\nwatch(chosen, (ids) => void name(ids), { immediate: true })\n\ninterface Row {\n id: number\n title: string\n candidate: RelationCandidate | null\n}\n\n/** Writable, because that is how `WxSortableList` hands a reorder back. */\nconst rows = computed<Row[]>({\n get: () =>\n chosen.value.map((id) => {\n const candidate = known.value.get(id) ?? null\n\n return { id, title: candidate?.title || `#${id}`, candidate }\n }),\n set: (next) => {\n value.value = next.map((row) => row.id)\n },\n})\n\nconst full = computed(() => props.max !== null && chosen.value.length >= props.max)\n\n/** The record being edited is never its own relation — \"similar recipes\" is about the others. */\nconst self = computed(() => (owner !== null && owner.type === props.target ? owner.id.value : null))\n\nconst options = computed<AutocompleteOption[]>(() =>\n candidates.value\n .filter((one) => one.id !== self.value && !chosen.value.includes(one.id))\n .map((one) => ({\n value: one.title,\n description: [one.subtitle, one.visible ? null : t('relations.field-hidden')]\n .filter((part): part is string => typeof part === 'string' && part !== '')\n .join(' · '),\n id: one.id,\n })),\n)\n\nasync function search(query: string): Promise<void> {\n searching.value = true\n\n try {\n const found = await api.value.search(query)\n candidates.value = found\n remember(found)\n } catch (error) {\n candidates.value = []\n if (error instanceof HttpError && error.status === 403) forbidden.value = true\n } finally {\n searching.value = false\n }\n}\n\nfunction pick(option: AutocompleteOption): void {\n const id = Number(option.id)\n\n if (!Number.isInteger(id) || chosen.value.includes(id) || full.value) return\n\n value.value = [...chosen.value, id]\n\n // The box writes the picked title into itself after this handler; emptied on the next tick it\n // is ready for the next one instead of holding the name of something already in the list.\n void nextTick(() => {\n term.value = ''\n })\n}\n\nfunction remove(id: number): void {\n value.value = chosen.value.filter((one) => one !== id)\n}\n\nconst locked = computed(() => props.disabled || forbidden.value)\n\nconst placeholder = computed(() =>\n full.value\n ? t('relations.field-full', { max: String(props.max) })\n : (props.addText ?? t('relations.field-add')),\n)\n\n/** A mark for a row whose record the site does not show, or the server does not know. */\nfunction mark(row: Row): string | null {\n if (row.candidate === null) return missing.value.has(row.id) ? t('relations.field-missing') : null\n\n if (row.candidate.trashed === true) return t('relations.field-trashed')\n\n return row.candidate.visible ? null : t('relations.field-hidden')\n}\n</script>\n\n<template>\n <div class=\"wx-relations-field\" :class=\"{ 'is-unordered': !props.sortable }\">\n <wx-sortable-list\n v-model=\"rows\"\n plain\n size=\"sm\"\n item-key=\"id\"\n item-label=\"title\"\n :disabled=\"locked || !props.sortable\"\n :empty-text=\"props.emptyText ?? t('relations.field-empty')\"\n :drag-label=\"t('relations.field-drag')\"\n >\n <template #default=\"{ item }\">\n <img\n v-if=\"(item as Row).candidate?.thumb\"\n class=\"wx-relations-field__thumb\"\n :src=\"(item as Row).candidate!.thumb!\"\n alt=\"\"\n />\n <span class=\"wx-relations-field__text\">\n <span class=\"wx-relations-field__title\">{{ (item as Row).title }}</span>\n <span v-if=\"(item as Row).candidate?.subtitle\" class=\"wx-relations-field__subtitle\">\n {{ (item as Row).candidate!.subtitle }}\n </span>\n </span>\n <wx-badge v-if=\"mark(item as Row)\" type=\"warning\">{{ mark(item as Row) }}</wx-badge>\n </template>\n\n <template v-if=\"!locked\" #actions=\"{ item }\">\n <wx-action\n type=\"remove\"\n size=\"sm\"\n :title=\"t('relations.field-remove')\"\n @click=\"remove((item as Row).id)\"\n />\n </template>\n </wx-sortable-list>\n\n <p v-if=\"forbidden\" class=\"wx-relations-field__note\">\n {{ t('relations.field-forbidden') }}\n </p>\n\n <!-- A wrapper rather than a class on the box: its root is a fragment, and a scoped rule on it\n would reach nothing. -->\n <div v-else class=\"wx-relations-field__add\">\n <wx-autocomplete\n v-model=\"term\"\n :options=\"options\"\n remote\n teleport\n :loading=\"searching\"\n :loading-text=\"t('relations.field-searching')\"\n :empty-text=\"t('relations.field-nothing')\"\n :placeholder=\"placeholder\"\n :aria-label=\"placeholder\"\n :disabled=\"props.disabled || full\"\n @search=\"search\"\n @select=\"pick\"\n />\n </div>\n </div>\n</template>\n\n<style scoped>\n.wx-relations-field {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-8);\n min-width: 0;\n}\n\n/* The row's cell is `WxSortableList`'s; the picture, the name and the mark make one line of it. */\n.wx-relations-field :deep(.wx-sortable-list__content) {\n display: flex;\n align-items: center;\n gap: var(--wx-space-8);\n min-width: 0;\n}\n\n/* Not shrunk by the name beside it: in a flex row an image keeps its natural width otherwise. */\n.wx-relations-field__thumb {\n flex: none;\n width: 32px;\n height: 32px;\n min-width: 0;\n object-fit: cover;\n border-radius: var(--wx-radius-xs);\n background: var(--wx-bg-fill);\n}\n\n.wx-relations-field__text {\n display: flex;\n flex-direction: column;\n flex: 1 1 auto;\n min-width: 0;\n}\n\n.wx-relations-field__title,\n.wx-relations-field__subtitle {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.wx-relations-field__subtitle,\n.wx-relations-field__note {\n font-size: var(--wx-font-size-sm);\n color: var(--wx-text-muted);\n}\n\n.wx-relations-field__note {\n margin: 0;\n}\n\n/* A choice whose order means nothing has nothing to hold: a greyed grip would read as \"locked\". */\n.wx-relations-field.is-unordered :deep(.wx-sortable-list__grip) {\n display: none;\n}\n\n.wx-relations-field :deep(.wx-badge) {\n flex: none;\n}\n</style>\n","import type { AdminContext } from '../admin'\n\n/**\n * One section a block can show records from, as `GET /api/cms/collections` describes it\n * (`WebxUi\\Admin\\Collections\\CollectionSource` on the server).\n */\nexport interface CollectionSourceInfo {\n /** What a block schema writes in `props.source`: `faq`. */\n key: string\n /** Already in the panel's language. */\n title: string\n /** The path its categories answer at (`faq/categories`); `null` — a source without them. */\n categories: string | null\n /** Whether it can mark its records up for search engines. */\n markup: boolean\n /**\n * The targets its records can be related to (`CollectionSource::relations()`, §3.6 of the\n * recipes spec) — empty for a source without relations, which has no \"only related to\".\n */\n relations: CollectionRelationTarget[]\n}\n\n/** A target a source's records point at, named for the choice \"only related to …\". */\nexport interface CollectionRelationTarget {\n /** Its key in the server's registry of targets: `service`. */\n key: string\n /** Already in the panel's language. */\n title: string\n}\n\n/** \"Only the records related to these\": one target, and the ones of it that were chosen. */\nexport interface CollectionRelated {\n type: string\n ids: number[]\n /**\n * \"Related to the record of the page the block stands on\" — written only when on, and then\n * `ids` is always empty: the record is the page's, not a choice.\n */\n current?: true\n}\n\n/** What a `wx-collection` field keeps: the choice, never the records. */\nexport interface CollectionValue {\n /** Empty — all of them. */\n categories: number[]\n /** `null` — not narrowed by relations. */\n related: CollectionRelated | null\n /** `null` — all of them. */\n limit: number | null\n filter: boolean\n /** `null` — the default: on when no category is chosen (decision 8 of the FAQ spec). */\n markup: boolean | null\n}\n\n/** The server's ceiling for `limit` (`Selection::MAX_LIMIT`). */\nexport const COLLECTION_MAX_LIMIT = 100\n\n/**\n * The list is the same for every field on every block of a session, and a page of ten FAQ blocks\n * asking ten times would be ten answers to one question. Kept per panel rather than per module,\n * so a test's fresh panel asks afresh.\n */\nconst asked = new WeakMap<AdminContext, Promise<CollectionSourceInfo[]>>()\n\nexport function collectionSources(admin: AdminContext): Promise<CollectionSourceInfo[]> {\n let sources = asked.get(admin)\n\n if (sources === undefined) {\n sources = admin.http\n .get<{ data: CollectionSourceInfo[] }>(`${admin.apiPath}/collections`)\n .then((body) => body.data.map((one) => ({ ...one, relations: relationTargets(one) })))\n\n // A failed answer is not kept: the next field to ask gets a real one rather than the same error.\n sources.catch(() => asked.delete(admin))\n asked.set(admin, sources)\n }\n\n return sources\n}\n\n/**\n * A server from before relations says nothing about them, and a list of bare keys names nothing:\n * both come to the same shape, a key without a title standing for itself.\n */\nfunction relationTargets(source: { relations?: unknown }): CollectionRelationTarget[] {\n if (!Array.isArray(source.relations)) return []\n\n return source.relations.flatMap((one: unknown): CollectionRelationTarget[] => {\n if (typeof one === 'string') return one === '' ? [] : [{ key: one, title: one }]\n\n if (\n typeof one === 'object' &&\n one !== null &&\n typeof (one as { key?: unknown }).key === 'string'\n ) {\n const { key, title } = one as { key: string; title?: unknown }\n\n return [{ key, title: typeof title === 'string' && title !== '' ? title : key }]\n }\n\n return []\n })\n}\n\nfunction ids(value: unknown): number[] {\n return Array.isArray(value)\n ? [...new Set(value.map(Number).filter((id) => Number.isInteger(id) && id > 0))].sort(\n (a, b) => a - b,\n )\n : []\n}\n\n/**\n * A relation filter without a target or without anything chosen of it narrows nothing, and is\n * written as none at all — so \"not narrowed\" has one spelling in the database.\n */\nfunction related(value: unknown): CollectionRelated | null {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return null\n\n const {\n type,\n ids: chosen,\n current,\n } = value as { type?: unknown; ids?: unknown; current?: unknown }\n\n if (current === true && typeof type === 'string' && type !== '') {\n return { type, ids: [], current: true }\n }\n\n const picked = ids(chosen)\n\n return typeof type === 'string' && type !== '' && picked.length > 0 ? { type, ids: picked } : null\n}\n\n/** Whatever arrived, as the five keys the server writes — the same `Selection::normalise()` does. */\nexport function normaliseCollection(value: unknown): CollectionValue {\n const fields =\n typeof value === 'object' && value !== null && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : {}\n\n const limit =\n typeof fields.limit === 'number' && Number.isInteger(fields.limit) ? fields.limit : null\n\n return {\n categories: ids(fields.categories),\n related: related(fields.related),\n limit: limit !== null && limit >= 1 ? Math.min(limit, COLLECTION_MAX_LIMIT) : null,\n filter: fields.filter === true,\n markup: typeof fields.markup === 'boolean' ? fields.markup : null,\n }\n}\n\n/**\n * What `markup: null` means for this choice: on for the whole collection, off for a part of it —\n * and \"the recipes of this service\" is a part as much as a category is.\n */\nexport function defaultMarkup(value: CollectionValue): boolean {\n return value.categories.length === 0 && value.related === null\n}\n","<script setup lang=\"ts\">\nimport { computed, ref, useId, watch } from 'vue'\nimport {\n WxButton,\n WxInputNumber,\n WxSelect,\n WxSwitch,\n type SelectModelValue,\n type SelectOption,\n} from '@webx-ui/core'\nimport { useAdmin } from '../admin'\nimport { createCategoriesApi } from '../categories/api'\nimport { useTranslate } from '../i18n'\nimport RelationsField from '../relations/RelationsField.vue'\nimport {\n COLLECTION_MAX_LIMIT,\n collectionSources,\n defaultMarkup,\n normaliseCollection,\n type CollectionSourceInfo,\n type CollectionValue,\n} from './api'\n\n/**\n * What a block shows from another section (`wx-collection`): which of its categories, how many,\n * whether with a filter, whether marked up for search engines.\n *\n * The records themselves are never here. The block keeps the choice, and the site reads the\n * records when it prints the page — so a question edited in the FAQ is edited on every page that\n * shows it, and nothing here goes stale.\n *\n * Which section is the schema's (`props.source`), not the value's: a block built for questions\n * does not turn into a block of reviews by an edit of its content. What that section is — its\n * name, where its categories answer, whether it can mark up — the panel asks the server once\n * (`GET /api/cms/collections`), and a section this administrator may not place is not offered.\n *\n * The markup switch is three-valued underneath. `null` is \"as the rule says\": on for the whole\n * collection, off for a part of it, because search engines ask not to mark the same question up on\n * several pages and \"questions about payment\" stands on every service. The switch shows what the\n * rule gives until somebody touches it, and after that it is theirs — with a way back to the rule.\n */\ndefineOptions({ name: 'WxCollectionField' })\n\nconst props = withDefaults(\n defineProps<{\n /** The source's key, as the server registered it: `faq`. */\n source: string\n disabled?: boolean\n }>(),\n { disabled: false },\n)\n\nconst model = defineModel<CollectionValue | null>({ default: null })\n\nconst admin = useAdmin()\nconst t = useTranslate('webx-admin')\nconst id = useId()\n\n/** `undefined` while the answer is on its way, `null` when it came and this source is not in it. */\nconst info = ref<CollectionSourceInfo | null | undefined>(undefined)\nconst categories = ref<SelectOption[]>([])\n\nwatch(\n () => props.source,\n async (source) => {\n info.value = undefined\n categories.value = []\n\n try {\n info.value = (await collectionSources(admin)).find((one) => one.key === source) ?? null\n } catch {\n info.value = null\n }\n\n if (!info.value?.categories) return\n\n try {\n const payload = await createCategoriesApi(admin, info.value.categories).list()\n categories.value = payload.data.map((row) => ({ label: row.name, value: row.id }))\n } catch {\n // The chosen ones still stand in the value; only the names are missing, and the list can be\n // opened again later. Losing the choice because a list did not arrive would be worse.\n categories.value = []\n }\n },\n { immediate: true },\n)\n\nconst value = computed(() => normaliseCollection(model.value))\n\nfunction write(patch: Partial<CollectionValue>): void {\n model.value = normaliseCollection({ ...value.value, ...patch })\n}\n\n/**\n * A chosen category that is not in the list any more — deleted, or in the bin — is still drawn,\n * by number: dropping it silently would change the block without anybody having touched it.\n */\nconst options = computed<SelectOption[]>(() => {\n const known = new Set(categories.value.map((one) => one.value))\n const missing = value.value.categories\n .filter((one) => !known.has(one))\n .map((one) => ({ label: `#${one}`, value: one }))\n\n return [...categories.value, ...missing]\n})\n\nfunction pick(picked: SelectModelValue): void {\n write({ categories: Array.isArray(picked) ? picked.map(Number) : [] })\n}\n\nfunction limit(picked: number | null | undefined): void {\n write({ limit: picked ?? null })\n}\n\n/*\n * \"Only related to\": which target, then which of its records. The target is chosen here rather\n * than fixed by the schema because a source can be related to more than one (recipes to services\n * and to other recipes), and with only one there is nothing to choose and the box is not drawn.\n * The target a choice was started in is kept while nothing of it is chosen yet — the value has no\n * way to say \"a target and no records\", and a box that jumped back would undo the click.\n */\nconst relatedType = ref<string | null>(null)\n\nconst relationOptions = computed<SelectOption[]>(() =>\n (info.value?.relations ?? []).map((one) => ({ label: one.title, value: one.key })),\n)\n\nconst relatedTarget = computed(() => {\n const targets = info.value?.relations ?? []\n\n if (targets.length === 1) return targets[0]!\n\n const key = value.value.related?.type ?? relatedType.value\n\n return targets.find((one) => one.key === key) ?? null\n})\n\nfunction relateTo(picked: SelectModelValue): void {\n relatedType.value = typeof picked === 'string' ? picked : null\n write({ related: null })\n}\n\nfunction relate(ids: number[] | null): void {\n const type = relatedTarget.value?.key\n\n write({ related: type && ids && ids.length > 0 ? { type, ids } : null })\n}\n\n/**\n * \"The record of the page it stands on\": on the page of a service, the recipes of that service —\n * one block in a service's layout instead of one per service. The choice of records gives way to\n * it, because there is nothing left to choose.\n */\nfunction relateToCurrent(on: boolean): void {\n const type = relatedTarget.value?.key\n\n write({ related: on && type ? { type, ids: [], current: true } : null })\n}\n\nconst markup = computed(() => value.value.markup ?? defaultMarkup(value.value))\n\nfunction mark(on: unknown): void {\n write({ markup: on === true })\n}\n\nconst markupHint = computed(() =>\n t(\n defaultMarkup(value.value)\n ? 'collections.field-markup-auto-on'\n : 'collections.field-markup-auto-off',\n ),\n)\n</script>\n\n<template>\n <div class=\"wx-collection-field\" :aria-busy=\"info === undefined\">\n <p v-if=\"info === null\" class=\"wx-collection-field__note\">\n {{ t('collections.field-unavailable', { source: props.source }) }}\n </p>\n\n <template v-else-if=\"info\">\n <p class=\"wx-collection-field__note\">\n {{ t('collections.field-source', { source: info.title }) }}\n </p>\n\n <div v-if=\"info.categories\" class=\"wx-collection-field__row\">\n <label class=\"wx-collection-field__label\" :for=\"`${id}-categories`\">\n {{ t('collections.field-categories') }}\n </label>\n <!-- The placeholder is what an empty choice means, so it goes once there is a choice: the\n search box stays beside the chips, and \"Payment · All categories\" says two things. -->\n <wx-select\n :id=\"`${id}-categories`\"\n :model-value=\"value.categories\"\n :options=\"options\"\n :placeholder=\"value.categories.length === 0 ? t('collections.field-all') : ''\"\n :empty-text=\"t('collections.field-no-categories')\"\n :disabled=\"props.disabled\"\n multiple\n filterable\n clearable\n teleport\n @update:model-value=\"pick\"\n />\n </div>\n\n <div v-if=\"info.relations.length > 0\" class=\"wx-collection-field__row\">\n <span class=\"wx-collection-field__label\">\n {{\n info.relations.length === 1\n ? t('relations.collection-related-to', { target: info.relations[0]!.title })\n : t('relations.collection-related')\n }}\n </span>\n <wx-select\n v-if=\"info.relations.length > 1\"\n :model-value=\"relatedTarget?.key ?? null\"\n :options=\"relationOptions\"\n :placeholder=\"t('relations.collection-related-type')\"\n :aria-label=\"t('relations.collection-related-type')\"\n :disabled=\"props.disabled\"\n clearable\n teleport\n @update:model-value=\"relateTo\"\n />\n <template v-if=\"relatedTarget\">\n <wx-switch\n :model-value=\"value.related?.current === true\"\n :label=\"t('relations.collection-related-current')\"\n :disabled=\"props.disabled\"\n @update:model-value=\"relateToCurrent($event === true)\"\n />\n <p class=\"wx-collection-field__hint\">\n {{ t('relations.collection-related-current-hint', { target: relatedTarget.title }) }}\n </p>\n </template>\n <relations-field\n v-if=\"relatedTarget && value.related?.current !== true\"\n :model-value=\"value.related?.ids ?? []\"\n :target=\"relatedTarget.key\"\n :sortable=\"false\"\n :empty-text=\"t('relations.collection-related-any')\"\n :disabled=\"props.disabled\"\n @update:model-value=\"relate\"\n />\n </div>\n\n <div class=\"wx-collection-field__row\">\n <label class=\"wx-collection-field__label\" :for=\"`${id}-limit`\">\n {{ t('collections.field-limit') }}\n </label>\n <!-- Wrapped rather than given the class: a scoped rule on a core component's own element\n does not reach it when its root is a fragment. -->\n <div class=\"wx-collection-field__limit\">\n <wx-input-number\n :id=\"`${id}-limit`\"\n :model-value=\"value.limit\"\n :min=\"1\"\n :max=\"COLLECTION_MAX_LIMIT\"\n :step=\"1\"\n :precision=\"0\"\n :placeholder=\"t('collections.field-limit-all')\"\n :disabled=\"props.disabled\"\n @update:model-value=\"limit\"\n />\n </div>\n </div>\n\n <wx-switch\n v-if=\"info.categories\"\n :model-value=\"value.filter\"\n :label=\"t('collections.field-filter')\"\n :disabled=\"props.disabled\"\n @update:model-value=\"write({ filter: $event === true })\"\n />\n\n <div v-if=\"info.markup\" class=\"wx-collection-field__markup\">\n <wx-switch\n :model-value=\"markup\"\n :label=\"t('collections.field-markup')\"\n :disabled=\"props.disabled\"\n @update:model-value=\"mark\"\n />\n <p class=\"wx-collection-field__hint\">{{ markupHint }}</p>\n <wx-button\n v-if=\"value.markup !== null\"\n class=\"wx-collection-field__reset\"\n variant=\"text\"\n size=\"sm\"\n :disabled=\"props.disabled\"\n @click=\"write({ markup: null })\"\n >\n {{ t('collections.field-markup-reset') }}\n </wx-button>\n </div>\n </template>\n </div>\n</template>\n\n<style scoped>\n.wx-collection-field {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-12);\n min-width: 0;\n}\n\n.wx-collection-field__row {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-4);\n min-width: 0;\n}\n\n.wx-collection-field__label {\n font-size: var(--wx-font-size-sm);\n color: var(--wx-text-muted);\n}\n\n/* A number of up to three digits: the whole line would promise room for a sentence. */\n.wx-collection-field__limit {\n max-width: 160px;\n}\n\n.wx-collection-field__markup {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-4);\n}\n\n/* Its own width, at the start of the column: stretched, a text button reads as a bar. */\n.wx-collection-field__reset {\n align-self: flex-start;\n}\n\n.wx-collection-field__note,\n.wx-collection-field__hint {\n margin: 0;\n font-size: var(--wx-font-size-sm);\n color: var(--wx-text-muted);\n}\n</style>\n","import type { AdminContext } from './admin'\n\n/** What a link points at, said out loud rather than guessed from what is filled in. */\nexport type LinkTarget = 'entity' | 'url' | 'none'\n\n/** The three `rel` values an editor may choose. `noopener` is added by whoever renders. */\nexport type LinkRel = 'nofollow' | 'sponsored' | 'ugc'\n\n/**\n * A link, in the shape the server keeps it — the same keys the menu's columns use, so a menu item\n * and a field hold one value and go through one validation.\n */\nexport interface LinkValue {\n target: LinkTarget\n entity_type: string | null\n entity_id: number | null\n url: string | null\n /**\n * The fragment, without its `#` — kept apart from the address because a chosen page has\n * nowhere to write one otherwise, and because what the address is differs by target while the\n * anchor does not.\n */\n hash: string | null\n new_tab: boolean\n rel: LinkRel[]\n}\n\n/** One section of the picker: a kind of thing this administrator may link to. */\nexport interface LinkSourceInfo {\n type: string\n title: string\n icon: string | null\n}\n\n/** One thing that can be linked to, as the picker draws it. */\nexport interface LinkCandidate {\n id: number\n title: string\n url: string | null\n /**\n * Whether the site would show it right now. A draft is offered and drawn dimmed: a menu is\n * built before the pages in it are published.\n */\n available: boolean\n /** The line under the title: a path in the tree, a rubric, a date. */\n hint: string | null\n}\n\n/** The same, once the type it came from matters — what `resolve` answers with. */\nexport interface ResolvedLink extends LinkCandidate {\n type: string\n}\n\nexport interface LinksApi {\n sources(): Promise<LinkSourceInfo[]>\n search(type: string, query: string, locale?: string): Promise<LinkCandidate[]>\n resolve(links: { type: string; id: number }[], locale?: string): Promise<ResolvedLink[]>\n /** Named addresses of the site itself, for the field where a path is typed. */\n routes(): Promise<{ name: string; path: string }[]>\n}\n\n/** An empty link — what a field holds before anybody has chosen anything. */\nexport function emptyLink(): LinkValue {\n return {\n target: 'entity',\n entity_type: null,\n entity_id: null,\n url: null,\n hash: null,\n new_tab: false,\n rel: [],\n }\n}\n\n/**\n * What the panel can be asked to link to.\n *\n * The panel's own addresses rather than a section's: the same picker opens in a menu, in a block\n * field and on a described screen, and a copy of it per module would be a copy of the permission\n * rules to get wrong.\n */\nexport function createLinksApi(admin: AdminContext): LinksApi {\n const base = `${admin.apiPath}/links`\n const data = <T>(body: { data: T }): T => body.data\n\n return {\n sources: () => admin.http.get<{ data: LinkSourceInfo[] }>(`${base}/sources`).then(data),\n search: (type, query, locale) =>\n admin.http\n .get<{ data: LinkCandidate[] }>(`${base}/search`, { query: { type, q: query, locale } })\n .then(data),\n resolve: (links, locale) =>\n admin.http.post<{ data: ResolvedLink[] }>(`${base}/resolve`, { links, locale }).then(data),\n routes: () =>\n admin.http.get<{ data: { name: string; path: string }[] }>(`${base}/routes`).then(data),\n }\n}\n","<script setup lang=\"ts\">\nimport { computed, onMounted, ref, watch } from 'vue'\nimport {\n WxAutocomplete,\n WxCheckbox,\n WxInput,\n WxSegmented,\n WxSelect,\n WxText,\n type AutocompleteOption,\n type InputModelValue,\n type SelectOption,\n} from '@webx-ui/core'\nimport { useAdmin } from './admin'\nimport { useI18n, useTranslate } from './i18n'\nimport {\n createLinksApi,\n emptyLink,\n type LinkCandidate,\n type LinkRel,\n type LinkSourceInfo,\n type LinkTarget,\n type LinkValue,\n} from './links'\n\n/**\n * Where a link goes, chosen rather than typed.\n *\n * Three kinds of target and the kind is said out loud, because \"there is no entity, so it must be a\n * URL\" cannot tell a choice nobody has finished from one that deliberately goes nowhere. Which\n * sections are on offer is the server's answer: a source appears once its module is installed and\n * the reader may open it, so this component knows about pages and articles only in the sense that\n * it draws whatever came back.\n *\n * What is chosen is the entity, never its address. The title and the address beside it are only\n * there to show — a value saved last month has a morph pair and nothing to draw, which is what the\n * resolve on mount is for.\n */\ndefineOptions({ name: 'WxLinkPicker' })\n\nconst props = withDefaults(\n defineProps<{\n /** Off where a link that goes nowhere makes no sense — a button, a card. */\n allowNone?: boolean\n /** Off where the two attributes are somebody else's business, as in a menu item's own form. */\n attributes?: boolean\n disabled?: boolean\n }>(),\n { allowNone: true, attributes: true, disabled: false },\n)\n\nconst value = defineModel<LinkValue | null>({ default: null })\n\nconst admin = useAdmin()\nconst i18n = useI18n()\nconst api = createLinksApi(admin)\nconst t = useTranslate('webx-admin')\n\nconst sources = ref<LinkSourceInfo[]>([])\nconst candidates = ref<LinkCandidate[]>([])\nconst routes = ref<{ name: string; path: string }[]>([])\nconst searching = ref(false)\n\n/** The section being searched in, which is not part of the value: what is stored is the type. */\nconst section = ref<string | null>(null)\n\n/** What the input shows: the chosen entity's title, or what somebody is typing. */\nconst term = ref('')\n\n/** The entity behind the saved value, once the server has said what it is called. */\nconst chosen = ref<LinkCandidate | null>(null)\n\n/**\n * The one search to ignore: the title this component just wrote into the field itself.\n *\n * Writing a value into `WxAutocomplete` is indistinguishable from typing it, so opening a form\n * with a saved link asks the server to search for the name of the page that is already chosen —\n * once per link field on the form. Held as the text rather than as a flag, because a flag would\n * have to be cleared on a timer and would swallow whatever the editor typed first.\n */\nlet echo: string | null = null\n\nconst link = computed<LinkValue>(() => value.value ?? emptyLink())\n\nconst targets = computed(() => {\n const options = [\n { value: 'entity', label: t('links.target-entity') },\n { value: 'url', label: t('links.target-url') },\n ]\n\n return props.allowNone ? [...options, { value: 'none', label: t('links.target-none') }] : options\n})\n\nconst sectionOptions = computed<SelectOption[]>(() =>\n sources.value.map((source) => ({ value: source.type, label: source.title })),\n)\n\n/**\n * The results, with the two things that are not the title: where it lives, and whether the site\n * would show it. A draft stays in the list — it is drawn dimmed and says so — because a menu is\n * built before the pages in it are published.\n */\nconst options = computed<AutocompleteOption[]>(() =>\n candidates.value.map((candidate) => ({\n value: candidate.title,\n description: hint(candidate),\n id: candidate.id,\n })),\n)\n\n/** The addresses of the site, offered so that nobody has to remember `/account`. */\nconst routeOptions = computed<AutocompleteOption[]>(() =>\n routes.value.map((route) => ({ value: route.path, description: route.name })),\n)\n\nonMounted(async () => {\n sources.value = await api.sources()\n\n // The section the value already names, so opening a saved link lands in the right list.\n section.value = link.value.entity_type ?? sources.value[0]?.type ?? null\n\n await Promise.all([restore(), loadRoutes()])\n})\n\n/*\n * What a saved value cannot carry: the title of the thing it points at. Asked for once, on open,\n * and again whenever the pair changes from outside — a form that discarded a draft, an undo.\n */\nwatch(\n () => [link.value.entity_type, link.value.entity_id].join(':'),\n () => {\n void restore()\n },\n)\n\nasync function restore(): Promise<void> {\n const { entity_type: type, entity_id: id } = link.value\n\n if (type === null || id === null) {\n chosen.value = null\n term.value = ''\n\n return\n }\n\n if (chosen.value?.id === id) {\n return\n }\n\n const [resolved] = await api.resolve([{ type, id }], i18n.state.locale)\n\n chosen.value = resolved ?? null\n term.value = resolved?.title ?? t('links.missing')\n echo = term.value\n}\n\nasync function loadRoutes(): Promise<void> {\n routes.value = await api.routes()\n}\n\nasync function search(query: string): Promise<void> {\n if (query === echo) {\n echo = null\n\n return\n }\n\n echo = null\n\n if (section.value === null) {\n return\n }\n\n searching.value = true\n\n try {\n candidates.value = await api.search(section.value, query, i18n.state.locale)\n } finally {\n searching.value = false\n }\n}\n\nfunction setTarget(target: string | number | undefined): void {\n if (target === undefined) {\n return\n }\n\n // Only the kind changes: an editor who switches to an address and back should find the page\n // they had chosen still chosen, so nothing else in the value is cleared here.\n value.value = { ...link.value, target: target as LinkTarget }\n}\n\nfunction setSection(type: unknown): void {\n section.value = type === null || type === undefined ? null : String(type)\n candidates.value = []\n void search('')\n}\n\nfunction pick(option: AutocompleteOption): void {\n const candidate = candidates.value.find((one) => one.id === option.id)\n\n if (candidate === undefined || section.value === null) {\n return\n }\n\n chosen.value = candidate\n term.value = candidate.title\n value.value = { ...link.value, entity_type: section.value, entity_id: candidate.id }\n}\n\nfunction clearEntity(): void {\n chosen.value = null\n term.value = ''\n candidates.value = []\n value.value = { ...link.value, entity_type: null, entity_id: null }\n}\n\nfunction setUrl(url: string): void {\n value.value = { ...link.value, url: url === '' ? null : url }\n}\n\n/**\n * The `#` is taken off as it is typed rather than on the way to the server: the field shows one\n * of its own, so leaving a second in the value would put two in front of the editor.\n */\nfunction setHash(hash: InputModelValue): void {\n // A string, always: the field is not localized, so the map half of `InputModelValue` never\n // arrives here — and anything that did would be a value this field cannot mean.\n const anchor = (typeof hash === 'string' || typeof hash === 'number' ? String(hash) : '')\n .replace(/^#+/, '')\n .trim()\n\n value.value = { ...link.value, hash: anchor === '' ? null : anchor }\n}\n\nfunction setNewTab(on: boolean): void {\n value.value = { ...link.value, new_tab: on }\n}\n\nfunction toggleRel(rel: LinkRel, on: boolean): void {\n const kept = link.value.rel.filter((one) => one !== rel)\n\n value.value = { ...link.value, rel: on ? [...kept, rel] : kept }\n}\n\nfunction hint(candidate: LinkCandidate): string {\n const parts = [candidate.hint, candidate.url ?? undefined].filter(\n (part): part is string => typeof part === 'string' && part !== '',\n )\n\n if (!candidate.available) {\n parts.push(t('links.unavailable'))\n }\n\n return parts.join(' · ')\n}\n\nconst REL: LinkRel[] = ['nofollow', 'sponsored', 'ugc']\n</script>\n\n<template>\n <div class=\"wx-link-picker\">\n <wx-segmented\n :options=\"targets\"\n :model-value=\"link.target\"\n :disabled=\"props.disabled\"\n :aria-label=\"t('links.target-entity')\"\n @update:model-value=\"setTarget\"\n />\n\n <div v-if=\"link.target === 'entity'\" class=\"wx-link-picker__entity\">\n <wx-select\n v-if=\"sectionOptions.length > 1\"\n class=\"wx-link-picker__section\"\n :options=\"sectionOptions\"\n :model-value=\"section\"\n :disabled=\"props.disabled\"\n :aria-label=\"t('links.section')\"\n @update:model-value=\"setSection\"\n />\n\n <wx-autocomplete\n class=\"wx-link-picker__search\"\n :model-value=\"term\"\n :options=\"options\"\n remote\n clearable\n :loading=\"searching\"\n :loading-text=\"t('links.searching')\"\n :empty-text=\"t('links.empty')\"\n :placeholder=\"t('links.search')\"\n :aria-label=\"t('links.search')\"\n :disabled=\"props.disabled\"\n @update:model-value=\"(text: string) => (term = text)\"\n @search=\"search\"\n @select=\"pick\"\n @clear=\"clearEntity\"\n />\n </div>\n\n <div v-else-if=\"link.target === 'url'\" class=\"wx-link-picker__url\">\n <wx-autocomplete\n :model-value=\"link.url ?? ''\"\n :options=\"routeOptions\"\n clearable\n open-on-focus\n :empty-text=\"t('links.url-routes')\"\n :placeholder=\"t('links.url-placeholder')\"\n :aria-label=\"t('links.url-label')\"\n :disabled=\"props.disabled\"\n @update:model-value=\"setUrl\"\n />\n </div>\n\n <!--\n The anchor belongs to both kinds of target: an address can carry one inline, a chosen page\n has nowhere else to put one. The `#` is drawn rather than typed, so what is in the field is\n the name and nothing else.\n -->\n <label v-if=\"link.target !== 'none'\" class=\"wx-link-picker__hash\">\n <wx-text size=\"sm\" tone=\"muted\">{{ t('links.hash') }}</wx-text>\n <span class=\"wx-link-picker__sign\" aria-hidden=\"true\">#</span>\n <wx-input\n :model-value=\"link.hash ?? ''\"\n :placeholder=\"t('links.hash-placeholder')\"\n :aria-label=\"t('links.hash')\"\n :disabled=\"props.disabled\"\n clearable\n @update:model-value=\"setHash\"\n />\n </label>\n\n <!--\n Nothing to ask about a link that goes nowhere, and the two attributes below would be a\n question about something that is not there.\n -->\n <div v-if=\"props.attributes && link.target !== 'none'\" class=\"wx-link-picker__attributes\">\n <wx-checkbox\n :model-value=\"link.new_tab\"\n :disabled=\"props.disabled\"\n @update:model-value=\"(on: boolean) => setNewTab(on)\"\n >\n {{ t('links.new-tab') }}\n </wx-checkbox>\n\n <div class=\"wx-link-picker__rel\">\n <wx-text size=\"sm\" tone=\"muted\">{{ t('links.rel') }}</wx-text>\n <wx-checkbox\n v-for=\"rel in REL\"\n :key=\"rel\"\n :model-value=\"link.rel.includes(rel)\"\n :disabled=\"props.disabled\"\n @update:model-value=\"(on: boolean) => toggleRel(rel, on)\"\n >\n {{ t(`links.rel-${rel}`) }}\n </wx-checkbox>\n </div>\n </div>\n </div>\n</template>\n\n<style scoped>\n.wx-link-picker {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-8);\n}\n\n.wx-link-picker__entity {\n display: flex;\n flex-wrap: wrap;\n gap: var(--wx-space-8);\n}\n\n/*\n * `:deep()`, because neither of these is one of our elements: `WxSelect` and `WxAutocomplete`\n * carry a portal beside the control, which makes their root a fragment — and Vue puts the scope\n * attribute only on a child that has a single root. Without it the rules match nothing at all,\n * silently, and both controls stand at their natural width.\n */\n.wx-link-picker__entity > :deep(.wx-link-picker__section) {\n flex: 0 0 auto;\n min-width: 160px;\n}\n\n.wx-link-picker__entity > :deep(.wx-link-picker__search) {\n flex: 1 1 220px;\n min-width: 0;\n}\n\n/*\n * The `#` sits in the row above the field rather than inside it: an addon inside the control is\n * the design system's business and this field does not have one, and a sign drawn over the input\n * would be a second thing to keep in step with its padding.\n */\n.wx-link-picker__hash {\n display: grid;\n grid-template-columns: auto 1fr;\n align-items: center;\n gap: var(--wx-space-4) var(--wx-space-6);\n}\n\n.wx-link-picker__hash > :first-child {\n grid-column: 1 / -1;\n}\n\n.wx-link-picker__sign {\n color: var(--wx-text-placeholder);\n}\n\n.wx-link-picker__attributes {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-12);\n}\n\n/*\n * A column, not a wrapping row. Measured in the panel's editing column at 330px: the heading sat\n * on the first checkbox's line and the other two stacked under it, so \"Relationship\" read as the\n * name of the first box rather than of the three. Three labels of this length never share a row\n * at that width anyway.\n */\n.wx-link-picker__rel {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-6);\n}\n</style>\n","<script setup lang=\"ts\">\nimport LinkPicker from './LinkPicker.vue'\nimport type { LinkValue } from './links'\n\n/**\n * `wx-link` on a described screen.\n *\n * The label, the help line and the errors are the renderer's — a field of kind `field` arrives\n * already wrapped in a `WxFormItem` — so all this adds is the two props a node may set and the\n * model. Everything else the node wrote travels as an attribute rather than as a declared prop:\n * declaring the picker's would turn every boolean the node left alone into `false` on the way\n * through.\n */\ndefineOptions({ name: 'WxLinkField', inheritAttrs: false })\n\nconst props = withDefaults(\n defineProps<{\n /** A link that goes nowhere is a choice on a menu item and noise on a button. */\n allowNone?: boolean\n /** Off where the two attributes belong to whatever holds the link rather than to the field. */\n attributes?: boolean\n disabled?: boolean\n }>(),\n { allowNone: true, attributes: true, disabled: false },\n)\n\nconst value = defineModel<LinkValue | null>({ default: null })\n</script>\n\n<template>\n <link-picker\n v-model=\"value\"\n v-bind=\"$attrs\"\n :allow-none=\"props.allowNone\"\n :attributes=\"props.attributes\"\n :disabled=\"props.disabled\"\n />\n</template>\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 'not-found-title': 'There is nothing at this address',\n 'not-found-description':\n 'The address may be mistyped, or the section it leads to is not installed on this site.',\n 'not-found-home': 'Back to the start',\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 'system-site': 'Site',\n 'system-search': 'Search and checks',\n 'system-access': 'Access',\n // The count beside a menu entry, said to a screen reader: what waits there for this person.\n badge: 'Waiting for you: :count',\n },\n // The panel on the site: the drawer beside a page of the site, and the tab that opens it.\n 'site-bar': {\n 'loader-open': 'Open the site panel',\n 'loader-close': 'Close the site panel',\n 'loader-title': 'Site panel',\n title: 'This page',\n close: 'Close',\n 'open-in-panel': 'Open in panel',\n 'tab-page': 'Page',\n 'no-entity':\n 'Nothing in the panel stands behind this address: it is a page of the site’s own code.',\n address: 'Address',\n status: 'Status',\n edited: 'Edited',\n draft: 'There are unpublished changes',\n 'draft-by': ':name has unpublished changes here',\n 'status-published': 'Published',\n 'status-scheduled': 'Scheduled',\n 'status-unpublished': 'Not on the site',\n 'status-trashed': 'In the bin',\n error: 'Could not find out what this page is.',\n retry: 'Try again',\n foreign: 'Only an address of this site can be looked up.',\n forbidden: 'There is nothing on this page you may edit.',\n 'kind-page': 'Page',\n 'kind-article': 'Article',\n 'kind-rubric': 'Rubric',\n 'kind-tag': 'Tag',\n 'kind-category': 'Category',\n 'kind-product': 'Product',\n 'kind-brand': 'Brand',\n 'kind-landing': 'Landing page',\n 'kind-service': 'Service',\n 'kind-vacancy': 'Vacancy',\n 'kind-recipe': 'Recipe',\n 'kind-event': 'Event',\n 'kind-outlet': 'Press outlet',\n changes: 'Unpublished changes: :count',\n publish: 'Publish',\n discard: 'Discard',\n 'draft-other': ':name has a draft here; your edits go into it',\n 'discard-title': 'Discard the unpublished changes?',\n 'discard-text':\n 'The record goes back to what the site shows now. Everything in the draft goes, other people’s changes included.',\n cancel: 'Cancel',\n 'loader-draft': 'Draft',\n 'loader-temporary': 'Open in panel to edit',\n 'loader-add': 'Add below',\n preview: 'Preview draft',\n },\n // The three words on the theme switch. They belong to the panel rather than to whoever\n // places the switch: a core component ships English prop defaults and knows nothing about\n // a dictionary, so the panel is the one that has to say them.\n theme: {\n label: 'Theme',\n light: 'Light',\n dark: 'Dark',\n system: 'Follow the 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 // The head's ··· on a screen that has no name to give the menu.\n more: 'More',\n rename: 'Rename',\n save: 'Save',\n // Said out loud to a screen reader while the little wheel turns, and after it stops.\n saving: 'Saving…',\n saved: 'Saved',\n unpublish: 'Take off the site',\n 'unpublish-title': 'Take it off the site?',\n 'unpublish-text':\n 'Visitors will no longer see it: its page will answer «not found» and leave the sitemap and search. Everything written stays in the panel, and «Publish» brings it back at the same address.',\n 'keep-published': 'Keep it on the site',\n 'discard-title': 'Discard changes?',\n 'discard-text':\n 'The published version comes back. Everything changed since publishing is lost and cannot be brought back.',\n 'keep-changes': 'Keep',\n 'publish-moves': 'The old address :old will lead to the new one.',\n unpublished: 'Taken off the site.',\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 // Two people — or a person and an agent — editing one record. The panel merges what does not\n // overlap without asking, and these are the words for the rest.\n editing: {\n merged: 'Merged with the changes by :who.',\n somebody: 'Somebody',\n 'via-agent': ':name, through an agent',\n 'via-import': ':name, through an import',\n 'conflict-title': 'You and :who changed the same places',\n 'conflict-text': 'Everything else was merged. Pick a version for each place below, then save.',\n base: 'Before',\n mine: 'Yours',\n theirs: 'Theirs',\n 'take-theirs': 'Take theirs everywhere',\n removed: 'Removed',\n empty: 'Empty',\n 'removed-mine': 'you removed this block, they edited it',\n 'removed-theirs': 'they removed this block, you edited it',\n apply: 'Save my choices',\n 'theirs-title': 'Take their version everywhere?',\n 'theirs-text':\n 'The form shows the record as it is now, and what you typed since your last save is gone.',\n cancel: 'Cancel',\n incoming: ':who changed :what · :when',\n pull: 'Pull in',\n others: 'Also open by :names',\n order: 'Order of blocks',\n block: 'Block',\n drafts: 'Drafts',\n 'drafts-text':\n 'Copies of the draft between publications. A draft somebody else’s save wrote over stays here too.',\n 'drafts-empty': 'No copies of the draft yet.',\n autosave: 'Autosave',\n overwritten: 'Written over',\n restore: 'Restore',\n 'restore-title': 'Put this draft back?',\n 'restore-text':\n 'It becomes the draft in the editor. The site does not change until you publish.',\n restored: 'The draft is back in the editor.',\n 'source-panel': 'panel',\n 'source-mcp': 'agent',\n 'source-import': 'import',\n 'and-more': ':what and more',\n and: 'and',\n event: ':who :what · :when',\n 'event-published': 'published',\n 'event-unpublished': 'took it off the site',\n 'event-discarded': 'discarded the draft',\n 'event-restored-version': 'restored version :number',\n 'event-restored-draft': 'put an earlier draft back',\n 'event-moved': 'moved it',\n 'event-moved-to': 'moved it to :path',\n 'event-trashed': 'moved it to the bin',\n 'event-restored': 'took it out of the bin',\n 'event-purged': 'deleted it for good',\n dismiss: 'Got it',\n 'trashed-title': ':who moved this to the bin · :when',\n 'trashed-anonymous': 'This is in the bin · :when',\n 'trashed-text':\n 'Nothing can be saved here until it is out of the bin. What you typed is still in the form.',\n 'restore-bin': 'Restore',\n 'restore-save': 'Restore and save my changes',\n 'restore-failed': 'Could not take it out of the bin.',\n 'out-of-bin': 'Out of the bin.',\n 'purged-title': ':who deleted this for good · :when',\n 'purged-anonymous': 'This was deleted for good',\n 'purged-text':\n 'There is nothing left to save it into. What you typed is still in the form — copy it before you leave.',\n 'copy-text': 'Copy my text',\n copied: 'Copied. Paste it wherever you need it.',\n 'copy-failed': 'Could not copy. Select the text in the form instead.',\n 'purged-message': ':who deleted this for good at :when.',\n 'blocked-trashed': 'This is in the bin',\n 'blocked-purged': 'This was deleted for good',\n 'publish-unseen-title': 'Publish changes you have not seen?',\n 'publish-unseen-text': 'Publishing puts them on the site together with yours.',\n 'publish-with': 'Publish with them',\n 'review-first': 'Review first',\n 'publish-moved':\n 'Somebody changed the draft a moment ago. Look at their changes, then publish again.',\n 'stale-publish':\n ':name changed the draft after you opened it. Look at the changes, then publish again.',\n },\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 // The two words every list needs the moment it has filters: what the funnel is called,\n // and the way out of all of them at once. The names of the filters themselves belong to\n // whatever is being filtered, and travel with that module's own words. The checkboxes of a\n // list that can be selected say what they do to a screen reader, and do it here too.\n filters: {\n title: 'Filters',\n reset: 'Reset all',\n 'select-row': 'Select row',\n 'select-all': 'Select every row on this page',\n },\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 // The one line the panel says about the nightly database dump. It lives at the foot of the\n // settings screen, and the warning is the whole point of it: a backup whose breakage is\n // discovered on the day it was needed is not a backup.\n backup: {\n title: 'Last database snapshot:',\n stale: 'No database snapshot since :date. Check the schedule.',\n never: 'The database has never been backed up. Check the schedule.',\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 // The toolbar of `wx-rich-text`. The editor is a component of the design system and carries\n // English defaults; these are what the panel calls the same buttons.\n 'rich-text': {\n bold: 'Bold',\n italic: 'Italic',\n accent: 'Accent',\n strike: 'Strikethrough',\n code: 'Inline code',\n h2: 'Heading 2',\n h3: 'Heading 3',\n h4: 'Heading 4',\n 'bullet-list': 'Bulleted list',\n 'ordered-list': 'Numbered list',\n blockquote: 'Quote',\n hr: 'Divider',\n link: 'Link',\n table: 'Table',\n image: 'Image',\n youtube: 'YouTube video',\n undo: 'Undo',\n redo: 'Redo',\n 'row-below': 'Row below',\n 'row-above': 'Row above',\n 'column-after': 'Column after',\n 'column-before': 'Column before',\n 'delete-row': 'Delete row',\n 'delete-column': 'Delete column',\n 'merge-cells': 'Merge or split cells',\n 'delete-table': 'Delete table',\n toolbar: 'Text formatting',\n 'link-address': 'Link address',\n 'youtube-address': 'YouTube URL',\n apply: 'Apply',\n cancel: 'Cancel',\n uploading: 'Uploading…',\n source: 'HTML source',\n 'source-loss': 'The editor does not keep this markup and will remove it:',\n 'source-drop': 'Remove it',\n 'source-keep': 'Keep editing',\n },\n // What one administrator writes on a record for the next one. Not the property of any\n // section: the same feed hangs off a submission, an order and a client, so the words are\n // the panel's own.\n notes: {\n title: 'Notes',\n placeholder: 'A note for whoever picks this up next…',\n add: 'Add a note',\n empty: 'No notes yet.',\n edit: 'Edit',\n delete: 'Delete',\n save: 'Save',\n cancel: 'Cancel',\n saved: 'Saved.',\n deleted: 'Deleted.',\n 'delete-title': 'Delete this note?',\n 'delete-text': 'It goes for everybody. This cannot be undone.',\n 'unknown-author': 'A deleted account',\n // The three the server says and the browser only ever repeats.\n 'no-type': 'That kind of record does not carry notes.',\n missing: 'That record no longer exists.',\n forbidden: 'You may not read the notes of this record.',\n 'not-yours': 'A note is edited by whoever wrote it.',\n },\n // Who changed a record and what (`wx-history`). The words of the fields are the modules' and\n // arrive with the rows; these are the frame's: the events, the doors, the run.\n history: {\n title: 'History',\n empty: 'Nothing has been changed here yet.',\n unsaved: 'The history begins with the first save.',\n 'unknown-author': 'Nobody signed in',\n 'event-created': 'Created',\n 'event-updated': 'Changed',\n 'event-deleted': 'Deleted',\n 'event-restored': 'Restored',\n 'event-published': 'Published',\n 'event-unpublished': 'Unpublished',\n 'event-run': 'Run',\n 'source-panel': 'in the panel',\n 'source-mcp': 'through an agent',\n 'source-import': 'by an import',\n 'source-bulk': 'by a bulk action',\n 'source-api': 'through the API',\n 'source-console': 'from the console',\n 'empty-value': 'empty',\n long: 'changed, :from → :to characters',\n run: 'Part of a run',\n 'run-title': 'Run',\n 'run-rows': 'Rows: :count',\n 'run-failed': 'Stopped: :message',\n search: 'Record id',\n more: 'Show more',\n // The three the server says and the browser only ever repeats.\n 'no-type': 'That kind of record keeps no history.',\n missing: 'That run no longer exists.',\n forbidden: 'You may not read the history of this record.',\n },\n // Where a link goes. The sections of the picker are named by whichever modules registered them;\n // these are the words around them, plus the six the server says when a link will not do.\n links: {\n 'target-entity': 'This site',\n 'target-url': 'An address',\n 'target-none': 'Nowhere',\n section: 'Section',\n search: 'Start typing a name',\n searching: 'Searching…',\n empty: 'Nothing found',\n clear: 'Clear the link',\n unavailable: 'Not on the site yet',\n missing: 'What this pointed at is gone',\n 'url-label': 'Address',\n 'url-placeholder': '/account or https://example.com',\n hash: 'Anchor',\n 'hash-placeholder': 'section-on-the-page',\n 'url-routes': 'Addresses of this site',\n 'new-tab': 'Open in a new tab',\n rel: 'Relationship',\n 'rel-nofollow': 'Pass no link weight',\n 'rel-sponsored': 'Paid placement',\n 'rel-ugc': 'Written by a visitor',\n shape: 'This field takes a link.',\n target: 'This field does not know that kind of target.',\n 'no-source': 'Nothing here can link to that.',\n 'no-entity': 'Choose what to link to.',\n 'url-length': 'An address may be at most :max characters long.',\n 'url-scheme': 'An address has to be a path or start with http, https, mailto or tel.',\n 'hash-length': 'An anchor may be at most :max characters long.',\n 'hash-shape': 'An anchor is a name on the page: no spaces in it.',\n 'not-localized': 'A link is the same in every language and cannot be translated.',\n 'address-fallback':\n \"No address in this language yet — this is the address in :locale, the site's main language.\",\n 'address-fallback-label': 'Address in the main language',\n },\n // The categories every module shares: the refusals the server says, and the words of the\n // shared screens under a module's own.\n categories: {\n 'in-use': 'There are :count entries in it. Move them to another category first.',\n 'slug-shape': 'Letters, digits and single hyphens between them.',\n new: 'New category',\n empty: 'No categories yet.',\n 'empty-help': 'A category groups entries. One entry can be in several of them.',\n order: 'The order here is the order on the site',\n hidden: 'Hidden from the site',\n 'no-address': 'No address in this language',\n count: 'Entries: :count',\n 'show-items': 'Show its entries',\n edit: 'Edit',\n 'open-on-site': 'Open on the site',\n delete: 'Delete',\n 'delete-blocked': 'While it holds entries it cannot go — move them first.',\n 'delete-title': 'Delete “:name”?',\n 'delete-text': 'It goes to the bin and comes off the site, and its address is free again.',\n deleted: 'The category is in the bin.',\n cancel: 'Cancel',\n create: 'Create',\n save: 'Save',\n saved: 'Saved.',\n 'save-failed': 'Not saved — look at the marked fields.',\n 'reorder-failed': 'The new order was not saved.',\n 'field-title': 'Name',\n 'field-slug': 'Address',\n 'address-moving':\n 'The address is changing. The old one keeps working and leads to the new one.',\n untitled: 'Untitled',\n trail: 'Where you are',\n 'leave-title': 'Leave without saving?',\n 'leave-text': 'What was changed here since the last save will be lost.',\n leave: 'Leave',\n 'field-main': 'Main',\n 'field-add': 'Add a category',\n 'field-remove': 'Take out of this category',\n 'field-empty': 'In no category yet.',\n 'field-none-left': 'Every category is already chosen.',\n 'order-all': 'Drag to change the order on the site.',\n 'order-category':\n 'Drag to change the order inside this category. The rest of the list keeps its own.',\n 'order-locked':\n 'Clear the search and the filters to change the order — only a whole list, or one category, can be dragged.',\n unknown: 'One of the chosen categories is not there any more.',\n },\n // What a block shows from another section (`wx-collection`): the server's refusals, then the\n // words of the field that makes the choice. The name of the section is the server's, translated.\n collections: {\n 'unknown-source': 'There is no “:source” on this site to show records from.',\n 'unknown-category': 'One of the chosen categories no longer exists.',\n limit: 'How many to show: a whole number from 1 to :max, or empty for all.',\n flag: 'This switch takes yes or no.',\n 'unknown-relation': 'Records of this section cannot be filtered by that.',\n 'unknown-related': 'One of the chosen related records no longer exists.',\n 'field-source': 'Shows records from “:source”.',\n 'field-unavailable':\n 'Records from “:source” cannot be chosen here: the section is not installed, or you have no access to it.',\n 'field-categories': 'Categories',\n 'field-all': 'All categories',\n 'field-no-categories': 'No such category.',\n 'field-limit': 'How many to show',\n 'field-limit-all': 'All',\n 'field-filter': 'A filter by category above the list',\n 'field-markup': 'Markup for search engines',\n 'field-markup-auto-on': 'By default it is on: the block shows every category.',\n 'field-markup-auto-off':\n 'By default it is off: search engines ask not to mark the same records up on several pages, and a chosen category usually stands on several.',\n 'field-markup-reset': 'Back to the default',\n },\n // The records of another section a record points at (`wx-relations`), and the same choice as a\n // block's filter (\"only related to\" in `wx-collection`). Kept apart from `collections` because\n // the field is a field of its own, on any screen.\n relations: {\n 'field-add': 'Add…',\n 'field-searching': 'Searching…',\n 'field-nothing': 'Nothing found.',\n 'field-empty': 'Nothing chosen yet.',\n 'field-remove': 'Remove',\n 'field-drag': 'Drag to change the order',\n 'field-hidden': 'Not on the site',\n 'field-trashed': 'In the bin',\n 'field-missing': 'Not found',\n 'field-full': 'No more than :max can be chosen.',\n 'field-forbidden': 'You cannot see these records, so the choice cannot be changed here.',\n 'collection-related': 'Only related to',\n 'collection-related-to': 'Only related to “:target”',\n 'collection-related-type': 'Which section',\n 'collection-related-any': 'Not narrowed: every record, related or not.',\n 'collection-related-current': 'The record of the page it stands on',\n 'collection-related-current-hint':\n 'On the page of a record of “:target” the block shows what is related to it; on any other page it shows nothing.',\n },\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 * The same moment, as narrow as a column can take it: the time alone for today, the day and a\n * short month for the rest of this year, digits for anything older.\n *\n * A column is scanned, not read — and the full line is the reason a date column had to be 185px\n * wide in Russian and wider in German. What it drops is in the tip, which is where \"when\n * exactly\" was always answered.\n */\n compact(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 compact(value) {\n const at = toDate(value)\n\n if (at === null) {\n return t('dates.never')\n }\n\n const now = new Date()\n\n /*\n * Today is the time and nothing else: it is the only row in the column that shows a clock,\n * so it reads as today without spending a word on saying so. Yesterday takes the ordinary\n * date — one day back is not worth a word either, and the tip has the whole of it.\n */\n if (daysApart(at, now) === 0) return time(at)\n\n return at.getFullYear() === now.getFullYear()\n ? format('compact', { day: 'numeric', month: 'short' }, at)\n : format('compact-earlier', { dateStyle: 'short' }, 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","<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 /**\n * The column form: the time for today, a short month for the rest of the year, digits before\n * that. What it leaves out is in the tip, which is the whole point of having one.\n */\n compact?: boolean\n size?: TextSize\n tone?: TextTone\n }>(),\n {\n compact: false,\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(() => (props.compact ? dates.compact(props.value) : 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","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","import { inject, provide, type InjectionKey, type Ref } from 'vue'\nimport type { AdminContext } from './admin'\n\n/** What happened to a record. `run` is the parent row of an import or a bulk action. */\nexport type HistoryEvent =\n 'created' | 'updated' | 'deleted' | 'restored' | 'published' | 'unpublished' | 'run'\n\n/** Which door the change came through. */\nexport type HistorySource = 'panel' | 'mcp' | 'import' | 'bulk' | 'api' | 'console'\n\n/**\n * One field of one save. A long value (rich text, a repeater) comes without `from` and `to`:\n * the journal keeps that it changed and how long it was, and the versions keep the text.\n */\nexport interface HistoryChange {\n /** `price`, or `name.ru` for one language of a translated field. */\n field: string\n /** The module's word for it, in the reader's language. */\n label: string\n from: unknown\n to: unknown\n long?: boolean\n from_length?: number\n to_length?: number\n}\n\nexport interface HistoryEntry {\n id: number\n event: HistoryEvent\n source: HistorySource\n subject: { type: string; id: number | null }\n /** The name as it was signed; `null` when nobody was signed in (a console command). */\n admin: { id: number | null; name: string | null } | null\n /** The agent's connection, when the source is `mcp`. */\n grant_id: number | null\n changes: HistoryChange[]\n /** The run this save was made in, with what the run was when the server had it at hand. */\n run: { id: number; summary: Record<string, unknown> | null } | null\n created_at: string | null\n /** A run's own: what was done, with which profile, how many rows. */\n summary?: Record<string, unknown>\n rows?: number\n}\n\n/** A page as `->paginate()` has it. */\nexport interface HistoryPage {\n data: HistoryEntry[]\n current_page: number\n last_page: number\n total: number\n}\n\nexport interface HistoryRun {\n run: HistoryEntry\n rows: HistoryPage\n}\n\nexport interface HistoryApi {\n list(page?: number): Promise<HistoryPage>\n /** A run and its rows, or only the rows about one record. */\n run(id: number, page?: number, search?: number | null): Promise<HistoryRun>\n}\n\n/**\n * The journal of one record.\n *\n * `type` is what the module registered (`catalog.product`) — never a class name — and the\n * address is the panel's own, like the notes': one feed for every section, and the server says\n * which permission each type is behind.\n */\nexport function createHistoryApi(\n admin: AdminContext,\n type: string,\n id: number | string,\n): HistoryApi {\n return {\n list: (page = 1) =>\n admin.http.get<HistoryPage>(`${admin.apiPath}/history/${type}/${id}`, { query: { page } }),\n run: (runId, page = 1, search = null) =>\n admin.http.get<HistoryRun>(`${admin.apiPath}/history/runs/${runId}`, {\n query: { page, search },\n }),\n }\n}\n\n/**\n * The id of the record an editor is showing, for a `wx-history` node on its screen.\n *\n * A described screen is the same JSON for every record, so the id cannot be written into it:\n * the page that hosts the screen knows it, and provides it here — the way it provides the\n * address to `wx-slug`. `null` is a record not saved yet, which has no history.\n */\nexport interface HistorySubject {\n id: Ref<number | string | null | undefined>\n}\n\nexport const historySubjectKey: InjectionKey<HistorySubject> = Symbol('wx-history-subject')\n\nexport function provideHistorySubject(subject: HistorySubject): void {\n provide(historySubjectKey, subject)\n}\n\n/** The editor above the node, or `null` outside one. */\nexport function useHistorySubject(): HistorySubject | null {\n return inject(historySubjectKey, null)\n}\n\n/**\n * A value as a line of text. Words for \"nothing\" are the caller's. A list of names — the extra\n * categories of a product, the tags of an article — reads as the names, comma-separated; any\n * other list or object is shown as JSON, cut, because a repeater row printed whole is a\n * paragraph in a feed of one-liners.\n */\nexport function historyValue(value: unknown, empty: string, max = 120): string {\n if (value === null || value === undefined || value === '') return empty\n if (typeof value === 'boolean') return value ? '✓' : '✗'\n if (typeof value === 'string') return cut(value, max)\n if (typeof value === 'number') return String(value)\n\n if (\n Array.isArray(value) &&\n value.every((item) => typeof item === 'string' || typeof item === 'number')\n ) {\n return value.length === 0 ? empty : cut(value.join(', '), max)\n }\n\n return cut(JSON.stringify(value), max)\n}\n\nfunction cut(text: string, max: number): string {\n return text.length > max ? `${text.slice(0, max - 1)}…` : text\n}\n","<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\nimport {\n toast,\n WxAvatar,\n WxButton,\n WxDialog,\n WxEmpty,\n WxInput,\n WxSkeleton,\n WxText,\n} from '@webx-ui/core'\nimport DateText from './DateText.vue'\nimport { useAdmin } from './admin'\nimport { useErrorText } from './errors'\nimport {\n createHistoryApi,\n historyValue,\n useHistorySubject,\n type HistoryChange,\n type HistoryEntry,\n type HistoryRun,\n} from './history'\nimport { useTranslate } from './i18n'\n\ndefineOptions({ name: 'WxHistory' })\n\n/**\n * Who changed a record, when, through which door, and what exactly (WEBX_UI_HISTORY.md §5).\n *\n * The frame's and not a section's, like the notes: the catalogue wants it first, and a copy per\n * module is a copy of the rule about who may read it. On a screen it is the `wx-history` node —\n * a tab of a form is one line of the description or one patch of a project — and the id comes\n * from the editor hosting the screen (`provideHistorySubject`), since the same JSON describes\n * every record.\n *\n * A row made inside an import or a bulk action links to the run: the run is where the journal\n * says what was done to all of them at once, and the row only says what this record got.\n */\nconst props = withDefaults(\n defineProps<{\n /** The type the module registered: `catalog.product`. */\n type: string\n /** The record; the hosting editor's when omitted. */\n id?: number | string | null\n /** Heading above the feed; the panel's own word by default, none with `''`. */\n title?: string\n }>(),\n { id: undefined, title: undefined },\n)\n\nconst admin = useAdmin()\nconst t = useTranslate('webx-admin')\nconst message = useErrorText()\nconst subject = useHistorySubject()\n\nconst entries = ref<HistoryEntry[]>([])\nconst page = ref(1)\nconst lastPage = ref(1)\nconst loading = ref(false)\nconst loadingMore = ref(false)\n\nconst recordId = computed(() => props.id ?? subject?.id.value ?? null)\nconst api = computed(() =>\n recordId.value === null ? null : createHistoryApi(admin, props.type, recordId.value),\n)\nconst heading = computed(() => props.title ?? t('history.title'))\n\nasync function load(): Promise<void> {\n entries.value = []\n page.value = 1\n lastPage.value = 1\n\n if (api.value === null) return\n\n loading.value = true\n\n try {\n const first = await api.value.list(1)\n entries.value = first.data\n lastPage.value = first.last_page\n } catch (error) {\n toast.danger(message(error))\n } finally {\n loading.value = false\n }\n}\n\nasync function more(): Promise<void> {\n if (api.value === null || loadingMore.value) return\n\n loadingMore.value = true\n\n try {\n const next = await api.value.list(page.value + 1)\n entries.value = [...entries.value, ...next.data]\n page.value = next.current_page\n lastPage.value = next.last_page\n } catch (error) {\n toast.danger(message(error))\n } finally {\n loadingMore.value = false\n }\n}\n\n/* The feed follows the record: an editor that swaps one record for the next in place would\n otherwise show the previous one's history under the new name. */\nwatch([() => props.type, recordId], () => void load(), { immediate: true })\n\ndefineExpose({ reload: load })\n\nfunction who(entry: HistoryEntry): string {\n return entry.admin?.name ?? t('history.unknown-author')\n}\n\n/** \"Changed · in the panel\" — what happened and through which door, in one muted line. */\nfunction what(entry: HistoryEntry): string {\n return `${t(`history.event-${entry.event}`)} · ${t(`history.source-${entry.source}`)}`\n}\n\nfunction from(change: HistoryChange): string {\n return historyValue(change.from, t('history.empty-value'))\n}\n\nfunction to(change: HistoryChange): string {\n return historyValue(change.to, t('history.empty-value'))\n}\n\nfunction long(change: HistoryChange): string {\n return t('history.long', {\n from: String(change.from_length ?? 0),\n to: String(change.to_length ?? 0),\n })\n}\n\n/** The run's own words beside the link, when it has some: \"Part of a run · prices.csv\". */\nfunction runLabel(entry: HistoryEntry): string {\n const name = entry.run?.summary?.what\n\n return typeof name === 'string' && name !== ''\n ? `${t('history.run')} · ${name}`\n : t('history.run')\n}\n\n/* The run, in a dialog over the feed: what it was, and its rows with a search by record. */\nconst runOpen = ref(false)\nconst run = ref<HistoryRun | null>(null)\nconst runId = ref<number | null>(null)\nconst runSearch = ref('')\nconst runLoading = ref(false)\nlet runTicket = 0\n\nasync function loadRun(id: number, next = 1): Promise<void> {\n if (api.value === null) return\n\n runLoading.value = true\n const ticket = ++runTicket\n\n try {\n const search = /^\\d+$/.test(runSearch.value.trim()) ? Number(runSearch.value.trim()) : null\n const answer = await api.value.run(id, next, search)\n\n // Typing \"105\" asks three times; only the last answer is the one the box is showing.\n if (ticket !== runTicket) return\n\n run.value =\n next === 1 || run.value === null\n ? answer\n : {\n run: answer.run,\n rows: { ...answer.rows, data: [...run.value.rows.data, ...answer.rows.data] },\n }\n } catch (error) {\n toast.danger(message(error))\n } finally {\n runLoading.value = false\n }\n}\n\nfunction openRun(id: number): void {\n runId.value = id\n run.value = null\n runSearch.value = ''\n runOpen.value = true\n void loadRun(id)\n}\n\n/* A search is typed, not submitted: the rows follow what is in the box. */\nwatch(runSearch, () => {\n if (runId.value !== null && runOpen.value) void loadRun(runId.value)\n})\n\n/** What a run was, as lines: the module's own keys, except the two the panel says itself. */\nconst summaryLines = computed(() => {\n const summary = run.value?.run.summary ?? {}\n\n return Object.entries(summary)\n .filter(([key]) => key !== 'rows' && key !== 'failed')\n .map(([key, value]) => ({ key, value: historyValue(value, t('history.empty-value')) }))\n})\n\nconst runFailed = computed(() => {\n const failed = run.value?.run.summary?.failed\n\n return typeof failed === 'string' ? failed : null\n})\n</script>\n\n<template>\n <section class=\"wx-history\">\n <wx-text v-if=\"heading\" weight=\"medium\" class=\"wx-history__title\">{{ heading }}</wx-text>\n\n <wx-empty v-if=\"recordId === null\" size=\"sm\" :description=\"t('history.unsaved')\" />\n\n <wx-skeleton v-else-if=\"loading\" :rows=\"3\" />\n\n <wx-empty v-else-if=\"entries.length === 0\" size=\"sm\" :description=\"t('history.empty')\" />\n\n <ul v-else class=\"wx-history__list\">\n <li v-for=\"entry in entries\" :key=\"entry.id\" class=\"wx-history-entry\">\n <wx-avatar :name=\"who(entry)\" size=\"sm\" tone=\"auto\" />\n\n <div class=\"wx-history-entry__body\">\n <div class=\"wx-history-entry__head\">\n <wx-text size=\"sm\" weight=\"medium\" truncate>{{ who(entry) }}</wx-text>\n <date-text :value=\"entry.created_at\" />\n </div>\n\n <wx-text size=\"sm\" tone=\"muted\">{{ what(entry) }}</wx-text>\n\n <ul v-if=\"entry.changes.length > 0\" class=\"wx-history-entry__changes\">\n <li v-for=\"change in entry.changes\" :key=\"change.field\" class=\"wx-history-change\">\n <span class=\"wx-history-change__label\">{{ change.label }}:</span>\n <template v-if=\"change.long\">\n <span class=\"wx-history-change__long\">{{ long(change) }}</span>\n </template>\n <template v-else>\n <span class=\"wx-history-change__from\">{{ from(change) }}</span>\n <span class=\"wx-history-change__arrow\" aria-hidden=\"true\">→</span>\n <span class=\"wx-history-change__to\">{{ to(change) }}</span>\n </template>\n </li>\n </ul>\n\n <!-- A link in looks and a button in the form around the screen: `type=\"button\"`, or\n the editor's form would be submitted by it. -->\n <button\n v-if=\"entry.run\"\n type=\"button\"\n class=\"wx-history-entry__run\"\n @click=\"openRun(entry.run.id)\"\n >\n {{ runLabel(entry) }}\n </button>\n </div>\n </li>\n </ul>\n\n <div v-if=\"page < lastPage\" class=\"wx-history__more\">\n <wx-button size=\"sm\" :loading=\"loadingMore\" @click=\"more\">{{ t('history.more') }}</wx-button>\n </div>\n\n <wx-dialog v-model:open=\"runOpen\" :title=\"t('history.run-title')\" :width=\"640\">\n <wx-skeleton v-if=\"run === null\" :rows=\"4\" />\n\n <div v-else class=\"wx-history-run\">\n <div class=\"wx-history-run__head\">\n <wx-text size=\"sm\" weight=\"medium\">{{ who(run.run) }}</wx-text>\n <date-text :value=\"run.run.created_at\" />\n </div>\n <wx-text size=\"sm\" tone=\"muted\">{{ what(run.run) }}</wx-text>\n\n <dl v-if=\"summaryLines.length > 0\" class=\"wx-history-run__summary\">\n <template v-for=\"line in summaryLines\" :key=\"line.key\">\n <dt>{{ line.key }}</dt>\n <dd>{{ line.value }}</dd>\n </template>\n </dl>\n\n <wx-text size=\"sm\">{{\n t('history.run-rows', { count: String(run.run.rows ?? 0) })\n }}</wx-text>\n <wx-text v-if=\"runFailed\" size=\"sm\" tone=\"danger\">\n {{ t('history.run-failed', { message: runFailed }) }}\n </wx-text>\n\n <wx-input\n v-model=\"runSearch\"\n size=\"sm\"\n inputmode=\"numeric\"\n :placeholder=\"t('history.search')\"\n clearable\n />\n\n <ul class=\"wx-history__list\">\n <li v-for=\"row in run.rows.data\" :key=\"row.id\" class=\"wx-history-run__row\">\n <wx-text size=\"sm\" weight=\"medium\">#{{ row.subject.id }}</wx-text>\n <ul class=\"wx-history-entry__changes\">\n <li v-for=\"change in row.changes\" :key=\"change.field\" class=\"wx-history-change\">\n <span class=\"wx-history-change__label\">{{ change.label }}:</span>\n <template v-if=\"change.long\">\n <span class=\"wx-history-change__long\">{{ long(change) }}</span>\n </template>\n <template v-else>\n <span class=\"wx-history-change__from\">{{ from(change) }}</span>\n <span class=\"wx-history-change__arrow\" aria-hidden=\"true\">→</span>\n <span class=\"wx-history-change__to\">{{ to(change) }}</span>\n </template>\n </li>\n </ul>\n </li>\n </ul>\n\n <div v-if=\"run.rows.current_page < run.rows.last_page\" class=\"wx-history__more\">\n <wx-button\n size=\"sm\"\n :loading=\"runLoading\"\n @click=\"runId !== null && loadRun(runId, run.rows.current_page + 1)\"\n >\n {{ t('history.more') }}\n </wx-button>\n </div>\n </div>\n </wx-dialog>\n </section>\n</template>\n\n<style scoped>\n.wx-history {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-12);\n min-width: 0;\n /* Laid out by its own width: it hangs in a tab or a card, whose width the window does not know. */\n container-type: inline-size;\n}\n\n.wx-history__list {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-16);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.wx-history-entry {\n display: flex;\n gap: var(--wx-space-8);\n min-width: 0;\n}\n\n.wx-history-entry__body {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-4);\n flex: 1 1 auto;\n min-width: 0;\n}\n\n/* The date keeps the end of the line, so entries by long and short names line up. */\n.wx-history-entry__head,\n.wx-history-run__head {\n display: flex;\n align-items: center;\n gap: var(--wx-space-8);\n min-width: 0;\n}\n\n.wx-history-entry__head > :last-child,\n.wx-history-run__head > :last-child {\n margin-inline-start: auto;\n flex: none;\n}\n\n.wx-history-entry__changes {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-2);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.wx-history-change {\n font-size: var(--wx-font-size-sm);\n color: var(--wx-text-default);\n overflow-wrap: anywhere;\n}\n\n.wx-history-change__label {\n color: var(--wx-text-muted);\n margin-inline-end: var(--wx-space-4);\n}\n\n/* Was and is are told apart by more than colour: the old value is struck through. */\n.wx-history-change__from {\n color: var(--wx-text-muted);\n text-decoration: line-through;\n}\n\n.wx-history-change__arrow {\n margin-inline: var(--wx-space-4);\n color: var(--wx-text-muted);\n}\n\n.wx-history-change__long {\n color: var(--wx-text-muted);\n font-style: italic;\n}\n\n/* Unpadded, so its words start where the lines above them do. */\n.wx-history-entry__run {\n align-self: flex-start;\n margin: 0;\n padding: 0;\n border: 0;\n border-radius: var(--wx-radius-xs);\n background: none;\n font: inherit;\n font-size: var(--wx-font-size-sm);\n text-align: start;\n color: var(--wx-text-link);\n cursor: pointer;\n}\n\n.wx-history-entry__run:hover {\n text-decoration: underline;\n}\n\n.wx-history-entry__run:focus-visible {\n outline: none;\n box-shadow: var(--wx-ring-focus);\n}\n\n.wx-history__more {\n display: flex;\n justify-content: center;\n}\n\n.wx-history-run {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-8);\n min-width: 0;\n}\n\n.wx-history-run__summary {\n display: grid;\n grid-template-columns: max-content 1fr;\n gap: var(--wx-space-4) var(--wx-space-12);\n margin: 0;\n font-size: var(--wx-font-size-sm);\n}\n\n.wx-history-run__summary dt {\n color: var(--wx-text-muted);\n}\n\n.wx-history-run__summary dd {\n margin: 0;\n overflow-wrap: anywhere;\n}\n\n.wx-history-run__row {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-2);\n}\n</style>\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 } from 'vue'\nimport { WxAction, WxActions, WxDropdownItem } from '@webx-ui/core'\nimport type { ActionSize } from '@webx-ui/core'\nimport { useTranslate } from './i18n'\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 t = useTranslate('webx-admin')\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 class=\"wx-row-menu\"\n collapse=\"always\"\n align=\"end\"\n :size=\"size\"\n :aria-label=\"label\"\n @click.stop\n >\n <!-- The core carries English defaults and knows nothing of the panel's dictionary, so the\n word on the tooltip is the panel's to give (CLAUDE.md §4). -->\n <template #trigger>\n <wx-action type=\"more\" :size=\"size\" :title=\"t('editor.more')\" />\n </template>\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 <span v-if=\"action.hint\" class=\"wx-row-menu__entry\">\n <span>{{ action.label }}</span>\n <span class=\"wx-row-menu__hint\">{{ action.hint }}</span>\n </span>\n <template v-else>{{ action.label }}</template>\n </wx-dropdown-item>\n </template>\n </template>\n </wx-actions>\n</template>\n\n<style scoped>\n/*\n * Nothing at rest, grey under the pointer.\n *\n * A grey square at the end of every row is a column of grey squares, and the eye reads them\n * before it reads the rows. Worse, the fill was the same colour the row takes when it is\n * hovered, so the one control the row carries went missing exactly when somebody reached for\n * it. The variable rather than a rule on the button: `WxAction` only ever reads\n * `--wx-action-bg`, never declares it, so an inherited value is what it uses (CLAUDE.md §4).\n */\n.wx-row-menu {\n --wx-action-bg: transparent;\n}\n\n/*\n * The reason a line is greyed, under its words and quieter than them. Under rather than beside: a\n * reason is a sentence, and beside the words it took all of a narrow menu and left the words none.\n * The item keeps its label on one line; the sentence under it wraps.\n */\n.wx-row-menu__entry {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-2);\n white-space: normal;\n}\n\n.wx-row-menu__hint {\n color: var(--wx-text-muted);\n font-size: var(--wx-font-size-xs);\n font-weight: var(--wx-font-weight-regular);\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, useSlots, useTemplateRef } from 'vue'\nimport { WxAction, WxButton, WxHeading, WxIcon, WxText, useElementWidth } from '@webx-ui/core'\nimport type { HeadingLevel } from '@webx-ui/core'\nimport type { RouteLocationRaw } from 'vue-router'\nimport WxBackButton from './BackButton.vue'\nimport WxRowMenu from './RowMenu.vue'\nimport { useTranslate } from './i18n'\nimport type { ScreenAction } from './types'\n\n/**\n * The first line of every screen in the panel: where the reader is, what it is called, and\n * what can be done with it.\n *\n * Every screen used to answer that on its own, and eight screens gave eight answers — a\n * heading at three different sizes, the way out as an arrow on four of them and as a line of\n * breadcrumbs on the rest, the row of buttons folding into a `···` on two editors and simply\n * wrapping onto a third line everywhere else. None of it is a screen's business: a reader who\n * has learned one screen already knows where to look on the next, and that only holds if there\n * is one head rather than one per section.\n *\n * The shape is four things, in this order: the way out, the name (with the state said beside\n * it), the line under it that says which record this is, and the actions at the other end.\n *\n * Actions are declared rather than drawn, because the same action has to appear as a button on\n * a desktop and as a line of a menu on a phone — and one vnode cannot be mounted in two places\n * (CLAUDE.md §4). Written as markup they had to be written twice, which is what the page and\n * article editors did; written as data they are written once and this decides. Two rules it\n * keeps rather than leaving to each screen: the destructive action is never a button in the\n * head — it lives in the `···`, where it is a word rather than a red rectangle beside the\n * name — and the one action the screen exists for (`primary`) is the one that survives a\n * narrow screen, with everything else folded behind the menu (§18.3).\n *\n * What it does not do is scroll with anything, stick to anything or know what saving means.\n * A screen that has something to save carries `WxActionBar` along its bottom, and on the\n * screens where the head never leaves the window that bar is the only place the save lives\n * (§3.3) — the head does not duplicate it.\n */\nconst props = withDefaults(\n defineProps<{\n /** What the screen is called. The one heading on it (§19). */\n title?: string\n /** The line under the name: which record this is — a slug, a form, a count. */\n subtitle?: string\n /**\n * The way out of the screen. A route draws a link; `true` draws the same arrow and emits\n * `back` instead, which is what a pane inside `WxListDetail` needs — on a phone the drawer\n * carries no close of its own and the pane has to bring one (CLAUDE.md §4).\n *\n * `string` and `object` are spelled out beside `RouteLocationRaw` for the compiler: it\n * cannot resolve the router's type, drops it and leaves `Boolean` alone as the runtime\n * type, so every editor passing its list's path warned \"Expected Boolean, got String\".\n */\n back?: RouteLocationRaw | string | object | boolean\n /** What the arrow's tooltip says. The panel's own word for it when not given. */\n backLabel?: string\n /** `2` for a screen, `3` for a pane standing inside one. */\n level?: HeadingLevel\n /** What can be done here, in the order it should read. */\n actions?: ScreenAction[]\n /** Width of the head below which the actions fold into the `···`. */\n collapseBelow?: number\n /**\n * Draws a rule under the head. For a screen exactly as tall as the window, where the head\n * never scrolls away and needs an edge of its own; a head that scrolls does not.\n */\n divider?: boolean\n }>(),\n {\n title: undefined,\n subtitle: undefined,\n back: undefined,\n backLabel: undefined,\n level: 2,\n actions: undefined,\n collapseBelow: 720,\n divider: false,\n },\n)\n\nconst emit = defineEmits<{\n /** The way out was taken on a head whose `back` is `true` rather than a route. */\n back: []\n}>()\n\ndefineSlots<{\n /** Above the name: breadcrumbs, when the screen stands somewhere in a tree. */\n trail?: () => unknown\n /** Beside the name: the state of the record — a status badge, the way to rename it. */\n 'title-after'?: () => unknown\n /** The line under the name, when it is more than a string. */\n subtitle?: () => unknown\n /**\n * Controls that are not one-word actions and stay whatever the width — the pair of arrows\n * that walks a pile of records one at a time, say.\n */\n extra?: () => unknown\n}>()\n\nconst t = useTranslate('webx-admin')\nconst slots = useSlots()\n\nconst root = useTemplateRef<HTMLElement>('root')\nconst width = useElementWidth(root)\n\n/* `0` is \"not measured yet\", and the roomy case is the one to assume until it is. */\nconst narrow = computed(() => width.value > 0 && width.value < props.collapseBelow)\n\nconst offered = computed<ScreenAction[]>(() => props.actions ?? [])\n\n/** At most one, whatever the screen declared: a screen with two main actions has none (§18.3). */\nconst primary = computed<ScreenAction | null>(\n () => offered.value.find((action) => action.primary) ?? null,\n)\n\n/** Wide: everything that is not a menu line. Narrow: the one action worth the room. */\nconst buttons = computed<ScreenAction[]>(() => {\n if (narrow.value) return primary.value ? [primary.value] : []\n\n return offered.value.filter((action) => !action.menu && !action.danger)\n})\n\nconst menu = computed<ScreenAction[]>(() => {\n if (narrow.value) return offered.value.filter((action) => action !== primary.value)\n\n return offered.value.filter((action) => action.menu || action.danger)\n})\n\n/** A route rather than the bare `true` that asks for the event. */\nconst backTo = computed<RouteLocationRaw | null>(() =>\n props.back && props.back !== true ? (props.back as RouteLocationRaw) : null,\n)\n\nconst hasActions = computed(\n () => buttons.value.length > 0 || menu.value.length > 0 || Boolean(slots.extra),\n)\n</script>\n\n<template>\n <div\n ref=\"root\"\n class=\"wx-screen-head\"\n :data-level=\"level\"\n :class=\"{\n 'is-narrow': narrow,\n 'has-button': buttons.length > 0,\n 'has-trail': Boolean($slots.trail),\n 'has-divider': divider,\n }\"\n >\n <!-- Its own line, and it scrolls sideways rather than wrapping: a trail is one line by\n definition, and a second line of it pushes the name of the record down the screen. -->\n <div v-if=\"$slots.trail\" class=\"wx-screen-head__trail\"><slot name=\"trail\" /></div>\n\n <wx-back-button\n v-if=\"backTo\"\n class=\"wx-screen-head__back\"\n :to=\"backTo\"\n :label=\"backLabel\"\n tooltip-side=\"bottom\"\n />\n <wx-action\n v-else-if=\"back\"\n class=\"wx-screen-head__back\"\n icon=\"arrow-left\"\n size=\"sm\"\n tooltip-side=\"bottom\"\n :title=\"backLabel ?? t('editor.back')\"\n @click=\"emit('back')\"\n />\n\n <div class=\"wx-screen-head__name\">\n <wx-heading v-if=\"title\" :level=\"level\" truncate class=\"wx-screen-head__title\">\n {{ title }}\n </wx-heading>\n <slot name=\"title-after\" />\n </div>\n\n <wx-text\n v-if=\"subtitle || $slots.subtitle\"\n class=\"wx-screen-head__subtitle\"\n size=\"sm\"\n tone=\"muted\"\n truncate\n >\n <slot name=\"subtitle\">{{ subtitle }}</slot>\n </wx-text>\n\n <div v-if=\"hasActions\" class=\"wx-screen-head__actions\">\n <slot name=\"extra\" />\n\n <wx-button\n v-for=\"action in buttons\"\n :key=\"action.key\"\n class=\"wx-screen-head__button\"\n :class=\"{ 'is-primary': action.primary }\"\n :type=\"action.primary ? 'primary' : 'default'\"\n :variant=\"action.primary ? 'solid' : (action.variant ?? 'outline')\"\n :loading=\"action.loading\"\n :disabled=\"action.disabled\"\n :href=\"action.href\"\n :target=\"action.href ? (action.target ?? '_blank') : undefined\"\n :rel=\"action.href ? 'noopener' : undefined\"\n @click=\"action.run?.()\"\n >\n <template v-if=\"action.icon\" #icon><wx-icon :name=\"action.icon\" /></template>\n {{ action.label }}\n </wx-button>\n\n <!-- The same menu a row of a list carries, so that one `···` means one thing in the\n panel — destructive last, behind a rule, and every line with the word on it. -->\n <wx-row-menu\n v-if=\"menu.length > 0\"\n :actions=\"menu\"\n size=\"lg\"\n :label=\"title ?? t('editor.more')\"\n />\n </div>\n </div>\n</template>\n\n<style scoped>\n/*\n * A grid of named areas rather than a row that wraps.\n *\n * Everything on the head answers to the name: the way out is at the start of its line and the\n * actions at the end of the same one, both centred on it — as flex items they lined up with the\n * top of the block instead, which on a screen with a trail put them level with four words of\n * small grey type and left the name hanging under them. The trail takes a line of its own above,\n * and the line that says which record this is takes one below, indented to the name.\n *\n * The space between the columns is margins on the two outer elements rather than a `column-gap`:\n * a head with no way out and no actions still has those tracks, and a gap would indent the name\n * by the width of two things that are not there.\n */\n.wx-screen-head {\n /*\n * The line the way out and the actions are centred on: the heading's own, which is its font\n * size times the tight leading both of them are set in. It has to be written out because what\n * they stand beside is the name *block*, and that block is two lines whenever a badge does not\n * fit next to the name — centring on it would put the arrow between the two, which is what a\n * head that had no rule about this did.\n */\n --wx-head-line: calc(var(--wx-font-size-2xl) * var(--wx-font-line-height-tight));\n\n display: grid;\n grid-template-columns: auto minmax(0, 1fr) auto;\n grid-template-areas:\n 'back name actions'\n '. subtitle .';\n align-items: start;\n row-gap: var(--wx-space-4);\n /* A head inside a column that gives its height to the rows keeps only what it needs. */\n flex: none;\n min-width: 0;\n}\n\n/* A pane inside a screen carries `level=\"3\"`, and its heading is a size smaller. */\n.wx-screen-head[data-level='3'] {\n --wx-head-line: calc(var(--wx-font-size-xl) * var(--wx-font-line-height-tight));\n}\n\n.wx-screen-head.has-trail {\n grid-template-areas:\n 'trail trail trail'\n 'back name actions'\n '. subtitle .';\n}\n\n.wx-screen-head.has-divider {\n padding-block-end: var(--wx-space-12);\n border-block-end: 1px solid var(--wx-border-default);\n}\n\n/*\n * The trail scrolls sideways and says nothing about it.\n *\n * A path to a page four levels down is longer than a phone is wide, and a trail that wraps is\n * two lines of the smallest type on the screen standing between the reader and the name of what\n * they opened. A scrollbar under it would be a third thing to look at, so it is hidden: the\n * trail is a finger away on a touch screen, and on a desktop there is room for it.\n */\n.wx-screen-head__trail {\n grid-area: trail;\n min-width: 0;\n overflow-x: auto;\n /* Nothing above should scroll because the trail reached its end. */\n overscroll-behavior-x: contain;\n /*\n * And it starts at the beginning of the path. The crumbs arrive after the record does, and a\n * scroller whose content grows under it is one the browser anchors — which left the trail\n * scrolled to its far end, with the section's own name cut in half at the left edge.\n */\n overflow-anchor: none;\n scrollbar-width: none;\n -ms-overflow-style: none;\n}\n\n.wx-screen-head__trail::-webkit-scrollbar {\n display: none;\n}\n\n/* The trail is one line whatever its length — wrapping is what the scroll replaces. */\n.wx-screen-head__trail > :deep(*) {\n flex-wrap: nowrap;\n white-space: nowrap;\n}\n\n/*\n * And a crumb keeps its whole width. `WxBreadcrumb` lays its items out to shrink and cut\n * themselves off with an ellipsis, which is the right answer inside a box that cannot scroll and\n * the wrong one inside this: what shrinks away is exactly what the scroll exists to reach.\n */\n.wx-screen-head__trail :deep(.wx-breadcrumb__list) {\n flex-wrap: nowrap;\n}\n\n.wx-screen-head__trail :deep(.wx-breadcrumb-item) {\n flex: none;\n}\n\n.wx-screen-head__trail :deep(.wx-breadcrumb-item__link) {\n overflow: visible;\n}\n\n/*\n * The way out stays small (30) while the actions at the other end are 42: it belongs to the\n * heading beside it rather than to that row, and at 42 it argues with the heading itself (§23.8).\n */\n/*\n * `:deep()` because the class is ours and the element it rides belongs to `WxAction`, which\n * carries a tooltip beside itself and so has a fragment for a root: Vue puts this component's\n * scope attribute only on a single-rooted child, and a scoped rule without it matches nothing\n * at all (CLAUDE.md §4). Silently — the button kept its class, landed in the right cell because\n * an item with no area of its own is placed in the first free one, and stood flush against the\n * heading because the margin was never applied.\n */\n.wx-screen-head > :deep(.wx-screen-head__back) {\n grid-area: back;\n margin-inline-end: var(--wx-space-8);\n /* Half the difference between the line and the control puts the two centres together. */\n margin-block-start: calc((var(--wx-head-line) - 30px) / 2);\n}\n\n.wx-screen-head__name {\n grid-area: name;\n display: flex;\n align-items: center;\n gap: var(--wx-space-8);\n flex-wrap: wrap;\n min-width: 0;\n}\n\n.wx-screen-head__title {\n min-width: 0;\n}\n\n.wx-screen-head__subtitle {\n grid-area: subtitle;\n min-width: 0;\n}\n\n.wx-screen-head__actions {\n grid-area: actions;\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: var(--wx-space-8);\n flex-wrap: wrap;\n min-width: 0;\n margin-inline-start: var(--wx-space-12);\n /* Centred on the name's line, the way the arrow at the other end of it is. */\n margin-block-start: calc((var(--wx-head-line) - var(--wx-size-control-md)) / 2);\n}\n\n/*\n * One height for everything on this line (§23.8): a button with a word on it is 42, and an\n * icon button matches it at `lg`. On a phone the shell drops icon buttons to 36, and the words\n * follow — the height of a button is read off `--wx-size-control-md` and never declared on the\n * button itself, so handing the row a different value is all it takes (CLAUDE.md §4).\n */\n.wx-screen-head__actions :deep(.wx-action) {\n --wx-action-size: var(--wx-size-control-md);\n}\n\n.wx-admin--drawer .wx-screen-head__actions {\n --wx-size-control-md: 36px;\n}\n\n/*\n * Narrow, and there is a button: the actions take the line under the name, whole, and the one\n * button with a word on it takes what the `···` leaves. Three things elbowing each other across\n * 343px is not a row, and a primary button too narrow to hold its word is not a button.\n *\n * Narrow with nothing but the `···` — a pane whose tools all folded — keeps it up on the name's\n * line: a menu alone on a line of its own reads as something left over.\n */\n.wx-screen-head.is-narrow.has-button {\n grid-template-areas:\n 'back name name'\n '. subtitle subtitle'\n 'actions actions actions';\n}\n\n.wx-screen-head.is-narrow.has-button.has-trail {\n grid-template-areas:\n 'trail trail trail'\n 'back name name'\n '. subtitle subtitle'\n 'actions actions actions';\n}\n\n.wx-screen-head.is-narrow.has-button .wx-screen-head__actions {\n margin-inline-start: 0;\n margin-block-start: var(--wx-space-8);\n}\n\n/* `:deep()` because the class is ours and the element it rides belongs to `WxButton`. */\n.wx-screen-head.is-narrow .wx-screen-head__actions > :deep(.wx-screen-head__button) {\n flex: 1 1 auto;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { WxCard, WxTabs, type CardPadding, type TabItem, type TabValue } from '@webx-ui/core'\nimport type { RouteLocationRaw } from 'vue-router'\nimport WxScreenHead from './ScreenHead.vue'\nimport type { ScreenAction } from './types'\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 * That first line is `WxScreenHead`, the same one an editor carries: a list and the record it\n * opens are two screens of one panel, and a head that differs between them is a head the\n * reader has to read twice.\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 /** The line under it, when the name alone does not say which list this is. */\n subtitle?: string\n /** Where the list goes back to, for one that stands under another screen. */\n back?: RouteLocationRaw\n /** What the arrow's tooltip says. The panel's own word for it when not given. */\n backLabel?: string\n /**\n * What the section offers: `New article`, `Check an address`. The one it exists for is\n * `primary`, and it is the one that survives a narrow screen.\n */\n actions?: ScreenAction[]\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 subtitle: undefined,\n back: undefined,\n backLabel: undefined,\n actions: 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 * Controls beside the heading that are not one-word actions and so cannot be declared —\n * a switch, a picker. Everything a button can say belongs in `actions`, which is what folds\n * into the `···` on a phone; what is written here stays drawn at every width.\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 <!-- A list folds its buttons later than an editor does: what stands beside the name here is\n one word and no trail, and a `New page` that takes the whole line on a tablet reads as\n a screen with nothing else on it. -->\n <wx-screen-head\n v-if=\"title || actions || $slots.actions\"\n class=\"wx-list-screen__head\"\n :title=\"title\"\n :subtitle=\"subtitle\"\n :back=\"back\"\n :back-label=\"backLabel\"\n :actions=\"actions\"\n :collapse-below=\"480\"\n >\n <template v-if=\"$slots.actions\" #extra><slot name=\"actions\" /></template>\n </wx-screen-head>\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/*\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","/**\n * The attribute a library picture carries its key in — the same one the server reads\n * (`Html::KEY` in the Composer half). The address beside it is a cache; this is the record.\n */\nconst KEY = 'data-wx-path'\n\n/** A rich text value as a screen holds it: one document, or one per language. */\ntype Value = unknown\n\nfunction documents(value: Value): string[] {\n if (typeof value === 'string') return [value]\n\n if (value !== null && typeof value === 'object') {\n return Object.values(value).filter((one): one is string => typeof one === 'string')\n }\n\n return []\n}\n\n/**\n * Every library key in the value, once each. Read off the markup without parsing it: the\n * attribute is written by the editor in one shape, and most documents have no pictures at all.\n */\nexport function richTextKeys(value: Value): string[] {\n const keys = new Set<string>()\n const pattern = new RegExp(`${KEY}=\"([^\"]+)\"`, 'g')\n\n for (const html of documents(value)) {\n for (const match of html.matchAll(pattern)) keys.add(decode(match[1]))\n }\n\n return [...keys]\n}\n\n/** `&amp;` and friends, the way the attribute was escaped when the editor wrote it. */\nfunction decode(text: string): string {\n const area = document.createElement('textarea')\n area.innerHTML = text\n\n return area.value\n}\n\nfunction rewrite(html: string, addresses: Record<string, string | null>): string {\n const template = document.createElement('template')\n template.innerHTML = html\n\n let changed = false\n\n for (const element of template.content.querySelectorAll<HTMLElement>(`[${KEY}]`)) {\n const address = addresses[element.getAttribute(KEY) ?? '']\n // A link to a file points with `href`, a picture with `src` — as on the server.\n const attribute = element.tagName === 'A' ? 'href' : 'src'\n\n // A key the library no longer has keeps the address it was saved with: a broken picture\n // that still says where it came from is easier to replace than an empty one.\n if (!address || element.getAttribute(attribute) === address) continue\n\n element.setAttribute(attribute, address)\n changed = true\n }\n\n // The same string back when nothing moved, so the editor is not handed a document that only\n // differs in how the parser chose to serialise it.\n return changed ? template.innerHTML : html\n}\n\n/**\n * The value with every library picture pointed at where it lives now. A language map stays a\n * map; anything that is not a document comes back as it was.\n */\nexport function rewriteRichText(value: Value, addresses: Record<string, string | null>): Value {\n if (typeof value === 'string') return rewrite(value, addresses)\n\n if (value !== null && typeof value === 'object') {\n const entries = Object.entries(value).map(([locale, html]) => [\n locale,\n typeof html === 'string' ? rewrite(html, addresses) : html,\n ])\n const changed = entries.some(\n ([locale, html]) => html !== (value as Record<string, unknown>)[locale],\n )\n\n return changed ? Object.fromEntries(entries) : value\n }\n\n return value\n}\n","<script setup lang=\"ts\">\nimport { computed, ref, useAttrs, watch } from 'vue'\nimport { WxRichText, type RichTextLabels, type RichTextModelValue } from '@webx-ui/core'\nimport { useAdmin } from './admin'\nimport { useTranslate } from './i18n'\nimport { richTextKeys, rewriteRichText } from './richTextAddresses'\n\n/**\n * `wx-rich-text` on a screen: the editor, in the panel's language, with the panel's library\n * behind its image button.\n *\n * The editor itself is a component of the design system and knows nothing about either — its\n * words are props and its pictures come from a function it is handed. Both are supplied here,\n * which is the one place that knows the panel: a module translated into ten languages with an\n * English toolbar in the middle of it is the failure this exists to avoid.\n *\n * No props of its own. Declaring the editor's would turn every boolean the node did not set\n * into `false` on the way through, so everything the node wrote travels as attributes and\n * arrives as it was written — `localized` included, which the editor handles itself.\n */\ndefineOptions({ name: 'WxRichTextField', inheritAttrs: false })\n\nconst admin = useAdmin()\nconst t = useTranslate('webx-admin')\n\n/*\n * The library is a module's, not the panel's, so a panel without a file manager hands the\n * editor nothing — and the editor then does not draw an image button it cannot honour.\n */\nconst pickImage = computed(() => admin.pickImage ?? undefined)\n\n/*\n * A document keeps each library picture's key beside an address, and the address is only true\n * on the day the paragraph was written — the site may have moved to another domain since, the\n * library to another disk, the picture been cropped. The site works the address out again on\n * every read; the editor is handed the value as stored, so it is worked out here, before the\n * editor sees it.\n *\n * What the editor sends back is passed on untouched and not looked at again: its pictures were\n * either refreshed here or picked from the library a moment ago. The editor takes the refreshed\n * document without announcing a change, so the form does not turn dirty by being opened.\n */\nconst attrs = useAttrs()\nconst shown = ref(attrs.modelValue as RichTextModelValue | undefined)\nlet sent: unknown\n\nwatch(\n () => attrs.modelValue,\n async (value) => {\n shown.value = value as RichTextModelValue | undefined\n\n const assetUrls = admin.assetUrls\n const keys = value === sent || !assetUrls ? [] : richTextKeys(value)\n\n if (!assetUrls || keys.length === 0) return\n\n const addresses = await assetUrls(keys).catch(() => null)\n\n // Somebody typed, or another record was opened, while the library was answering.\n if (addresses && attrs.modelValue === value) {\n shown.value = rewriteRichText(value, addresses) as RichTextModelValue\n }\n },\n { immediate: true },\n)\n\nfunction remember(value: unknown): void {\n sent = value\n}\n\nconst labels = computed<RichTextLabels>(() => ({\n bold: t('rich-text.bold'),\n italic: t('rich-text.italic'),\n accent: t('rich-text.accent'),\n strike: t('rich-text.strike'),\n code: t('rich-text.code'),\n h2: t('rich-text.h2'),\n h3: t('rich-text.h3'),\n h4: t('rich-text.h4'),\n bulletList: t('rich-text.bullet-list'),\n orderedList: t('rich-text.ordered-list'),\n blockquote: t('rich-text.blockquote'),\n hr: t('rich-text.hr'),\n link: t('rich-text.link'),\n table: t('rich-text.table'),\n image: t('rich-text.image'),\n youtube: t('rich-text.youtube'),\n undo: t('rich-text.undo'),\n redo: t('rich-text.redo'),\n source: t('rich-text.source'),\n addRowAfter: t('rich-text.row-below'),\n addRowBefore: t('rich-text.row-above'),\n addColumnAfter: t('rich-text.column-after'),\n addColumnBefore: t('rich-text.column-before'),\n deleteRow: t('rich-text.delete-row'),\n deleteColumn: t('rich-text.delete-column'),\n mergeOrSplit: t('rich-text.merge-cells'),\n deleteTable: t('rich-text.delete-table'),\n toolbar: t('rich-text.toolbar'),\n linkAddress: t('rich-text.link-address'),\n youtubeAddress: t('rich-text.youtube-address'),\n apply: t('rich-text.apply'),\n cancel: t('rich-text.cancel'),\n uploading: t('rich-text.uploading'),\n sourceLoss: t('rich-text.source-loss'),\n sourceDrop: t('rich-text.source-drop'),\n sourceKeep: t('rich-text.source-keep'),\n}))\n</script>\n\n<template>\n <wx-rich-text\n v-bind=\"$attrs\"\n :model-value=\"shown\"\n :labels=\"labels\"\n :pick-image=\"pickImage\"\n @update:model-value=\"remember\"\n />\n</template>\n","import { inject, provide, type InjectionKey, type Ref } from 'vue'\nimport type { ScreenModel } from '@webx-ui/schema'\n\n/**\n * What a record's editor knows about its address and a described screen cannot carry.\n *\n * The address of a service, a category, an article is the module's prefix and the record's slug.\n * The slug is a field of the screen; the prefix is configuration the editor was handed with the\n * record, and the address the site answers at right now is the registry's. `wx-slug` needs all\n * three to print the whole address and to warn before it moves — so the page that hosts the\n * screen provides them, the same way it provides the preview to `wx-blocks`.\n */\nexport interface RecordAddress {\n /** The values of the screen as they are right now, edits included. */\n values: Ref<ScreenModel>\n /** Where the module's addresses start: `services`, `''` for the root, `null` for none at all. */\n prefix: Ref<string | null>\n /** The address the registry holds for the record in this language, without a leading slash. */\n path: Ref<string | null | undefined>\n /** \"The address is changing\" — in the module's words. */\n moving: () => string\n /**\n * \"No address in this language\" — for a record whose prefix is a per-language fact rather than\n * configuration: a page inside one that has no address in a language has none there either.\n * Given, a `null` prefix means exactly that, and the field says so instead of printing `/`.\n */\n missing?: () => string\n}\n\nexport const recordAddressKey: InjectionKey<RecordAddress> = Symbol('wx-record-address')\n\nexport function provideRecordAddress(address: RecordAddress): void {\n provide(recordAddressKey, address)\n}\n\n/** The editor above this field, or `null` outside one — a demo, a test, a stray screen. */\nexport function useRecordAddress(): RecordAddress | null {\n return inject(recordAddressKey, null)\n}\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { useLocales, WxAlert, WxInput } from '@webx-ui/core'\nimport { useRecordAddress } from './address'\n\n/**\n * The address of a record: the part that is edited, with the module's prefix in front of it.\n *\n * `implants` on its own says nothing about whether the catalogue lives under `/services/` or at\n * the root of the site, which is what somebody checking an address wants to see — so the prefix\n * stands inside the control, where the address is read and written in one place. The editor\n * hosting the screen hands it over with {@link provideRecordAddress}.\n *\n * The line under it appears only once there is something to lose: a record on the site whose\n * address is being changed leaves a redirect behind, and that is the fact that decides whether\n * this is safe to do at all. Said before the save rather than in a toast afterwards.\n *\n * Nothing is declared as a prop on purpose: a wrapper that declared what `WxInput` takes would\n * hand it a `false` where the screen asked for nothing (CLAUDE.md §4).\n */\ndefineOptions({ name: 'WxSlugField', inheritAttrs: false })\n\nconst address = useRecordAddress()\nconst locales = useLocales()\n\n/** Nothing to put in front of the slug: the record has no address in this language at all. */\nconst orphaned = computed(() => address?.prefix.value === null && address.missing !== undefined)\n\n/** `/services/`, or `/` for a module that lives at the root of the site. */\nconst prefix = computed(() => {\n const head = address?.prefix.value ?? ''\n\n return head === '' ? '/' : `/${head}/`\n})\n\n/**\n * The segment as it stands in the field, in the language being edited and no other: falling\n * back on a language that has words would print an address the site does not answer at.\n */\nconst slug = computed(() => {\n const written = address?.values.value.slug\n\n if (typeof written === 'string') return written\n\n return (written as Record<string, string> | null | undefined)?.[locales.active.value] ?? ''\n})\n\n/** On the site at an address that is about to become a different one. */\nconst moving = computed(() => {\n const current = address?.path.value\n\n return (\n !orphaned.value &&\n typeof current === 'string' &&\n slug.value !== '' &&\n `${prefix.value}${slug.value}` !== `/${current}`\n )\n})\n</script>\n\n<template>\n <div class=\"wx-slug\">\n <wx-input v-bind=\"$attrs\">\n <template v-if=\"!orphaned\" #prefix>\n <span class=\"wx-slug__prefix\">{{ prefix }}</span>\n </template>\n </wx-input>\n\n <wx-alert\n v-if=\"orphaned && address?.missing\"\n type=\"info\"\n variant=\"soft\"\n :description=\"address.missing()\"\n />\n <wx-alert\n v-else-if=\"moving && address\"\n type=\"info\"\n variant=\"soft\"\n :description=\"address.moving()\"\n />\n </div>\n</template>\n\n<style scoped>\n.wx-slug {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-8);\n}\n\n/*\n * One address in one face. The prefix and what is typed after it are one string; two faces on\n * one line do not sit on one line, and only the muting should say which part cannot be typed\n * over.\n */\n.wx-slug__prefix,\n.wx-slug :deep(.wx-input__inner) {\n font-family: var(--wx-font-family-mono);\n}\n\n.wx-slug__prefix {\n color: var(--wx-text-muted);\n}\n</style>\n","import type { TypeRegistry } from '@webx-ui/schema'\nimport CategoriesField from './categories/CategoriesField.vue'\nimport SourceSelect from './categories/SourceSelect.vue'\nimport CollectionField from './collections/CollectionField.vue'\nimport LinkField from './LinkField.vue'\nimport RelationsField from './relations/RelationsField.vue'\nimport HistoryFeed from './HistoryFeed.vue'\nimport ListScreen from './ListScreen.vue'\nimport RichTextField from './RichTextField.vue'\nimport SlugField from './SlugField.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 *\n * `wx-rich-text` is here rather than in `@webx-ui/schema` because it is not the editor that\n * makes it a panel field: it is the panel's words on its toolbar and the panel's library behind\n * its image button, and the schema package knows about neither.\n *\n * `wx-link` is here for the same reason twice over: what it can point at is whatever the installed\n * content modules registered, and the only thing that knows what those are is the panel's own\n * backend.\n *\n * `wx-categories` files a record under a module's categories (`categories/`). `wx-slug` is the\n * address of any record with the module's prefix in front of it — a service, a category; the\n * editor hosting the screen hands it the prefix (`provideRecordAddress`). `wx-category-slug` is\n * the same field under the name the category screens were first described with.\n *\n * `wx-select` is the core's select that also takes `source` — a module's list as its options, the\n * way `wx-categories` has it: the stock status of a product, its brand. Over the schema's own\n * `wx-select`, which it is when there is no `source`.\n *\n * `wx-collection` is a block's window onto another section — the FAQ's questions, a team — and\n * keeps which part of it to show, never the records (`collections/`).\n *\n * `wx-relations` is a record pointing at records of another section — the services of a recipe,\n * its similar recipes — by id, in order (`relations/`).\n *\n * `wx-history` is who changed the record and what (WEBX_UI_HISTORY.md §5): a tab of any form is\n * `{ \"type\": \"wx-history\", \"props\": { \"type\": \"catalog.product\" } }`, and the record's id comes\n * from the editor hosting the screen (`provideHistorySubject`). It draws and edits nothing.\n */\nexport const adminTypes: TypeRegistry = {\n 'wx-list': { component: ListScreen, kind: 'layout', labelProp: 'title' },\n // Full width: an editor shares a row with nothing, and a form of two columns would give it\n // half a line to write a page of text in.\n 'wx-rich-text': { component: RichTextField, kind: 'field', wide: true },\n // Wide as well: a row of the picker is a segmented switch, a section and a search box, and half\n // a form's width leaves the search box too narrow to read a page title in.\n 'wx-link': { component: LinkField, kind: 'field', wide: true },\n 'wx-categories': { component: CategoriesField, kind: 'field' },\n 'wx-select': { component: SourceSelect, kind: 'field' },\n // Wide: four controls in a column, and half a line leaves the category chips one to a row.\n 'wx-collection': { component: CollectionField, kind: 'field', wide: true },\n // Wide: a row carries a picture, a name with a line under it and a mark, and the search box\n // under the list reads titles.\n 'wx-relations': { component: RelationsField, kind: 'field', wide: true },\n 'wx-slug': { component: SlugField, kind: 'field' },\n 'wx-category-slug': { component: SlugField, kind: 'field' },\n // The node's label is the feed's heading; inside a tab that already names it, `props.title: \"\"`.\n 'wx-history': { component: HistoryFeed, kind: 'display', 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 { ThemeController } from './theme'\nimport type {\n AdminModule,\n AdminStatus,\n AdminUser,\n BlockLabels,\n Manifest,\n NavEntry,\n NavGroup,\n PickedImage,\n} 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 /**\n * How the panel picks a picture, when a module installed here has a library. `null` when\n * none does — and a field that needs one then does not offer to.\n */\n readonly pickImage: (() => Promise<PickedImage | null>) | null\n /**\n * Where library keys live now, when a module installed here has a library; `null` when none\n * does, and a document is then shown with the addresses it was saved with.\n */\n readonly assetUrls: ((paths: string[]) => Promise<Record<string, string | null>>) | null\n /**\n * Names of block types and their fields, when a module installed here has a block library;\n * fetched once for the session. Optional on the context so a hand-made one in a test needs none.\n */\n readonly blockLabels?: () => Promise<BlockLabels | null>\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/** A positive whole number under `meta.badge`, or nothing. */\nfunction badgeOf(meta: Record<string, unknown> | undefined): number | undefined {\n const badge = meta?.badge\n\n return typeof badge === 'number' && Number.isInteger(badge) && badge > 0 ? badge : undefined\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 /** Told who is signed in, so the theme they chose follows them to this machine. */\n theme?: ThemeController\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 section: module.section ?? null,\n badge: badgeOf(module.meta),\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 const entries = byGroup.get(group.id) ?? []\n const sections = group.sections ?? []\n // Like a group, a caption the server never declared is no caption: the entry stays\n // with the plain ones rather than vanishing under a heading nobody can name.\n const captioned = (entry: NavEntry) =>\n sections.some((section) => section.id === entry.section)\n\n return {\n id: group.id,\n title: group.title,\n icon: group.icon ?? null,\n entries: entries.filter((entry) => !captioned(entry)),\n sections: sections\n .map((section) => ({\n id: section.id,\n title: section.title,\n entries: entries.filter((entry) => entry.section === section.id),\n }))\n .filter((section) => section.entries.length > 0),\n }\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 // The first module that has a library wins. Two of them is not a case worth a setting: a\n // panel with two file managers has a bigger question to answer than which one this opens.\n const pickImage = options.modules.find((module) => module.pickImage !== undefined)?.pickImage\n const assetUrls = options.modules.find((module) => module.assetUrls !== undefined)?.assetUrls\n const blockLabelsOf = options.modules.find(\n (module) => module.blockLabels !== undefined,\n )?.blockLabels\n let blockLabelsPending: Promise<BlockLabels | null> | null = null\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 adoptTheme(state.user)\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. The manifest itself is already in that\n // language — the server answers a signed-in administrator in their stored one — so only\n // the dictionary is missing; asking for the manifest again here was the same request twice\n // on every visit that started in another language.\n if (manifest.locale !== undefined && manifest.locale !== options.i18n.state.locale) {\n await switchLocale(manifest.locale, false)\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 /**\n * The theme is a property of the person, like the language: chosen once and found again on\n * the next machine they sign in on. Until then the panel is drawn in whatever this browser\n * remembers, which is what the sign-in screen had to go on.\n */\n function adoptTheme(user: AdminUser | null): void {\n if (user !== null) {\n options.theme?.adopt(user.theme)\n }\n }\n\n function setLocale(code: string): Promise<void> {\n return switchLocale(code, true)\n }\n\n async function switchLocale(code: string, refresh: boolean): 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, and not when the caller\n // is `reload()` itself, which has just fetched one in this language. A manifest that will\n // not 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 if (refresh) {\n await refreshManifest()\n }\n }\n\n /*\n * Where a stored key lives, remembered for the visit. The corner of the panel draws the\n * signed-in person in up to three places and redraws them as the layout changes, and every\n * mount asked the library about the same photograph again. A question already on its way is\n * shared rather than asked twice; a key the library has lost, or a request that failed, is\n * forgotten, since an upload or a retry may find it.\n */\n const knownAssets = new Map<string, Promise<string | null>>()\n\n async function cachedAssetUrls(paths: string[]): Promise<Record<string, string | null>> {\n const missing = [...new Set(paths.filter((path) => !knownAssets.has(path)))]\n\n if (missing.length > 0 && assetUrls) {\n const batch = assetUrls(missing, context)\n\n for (const path of missing) {\n const one = batch.then((found) => found[path] ?? null)\n\n knownAssets.set(path, one)\n one.then(\n (url) => {\n if (url === null) knownAssets.delete(path)\n },\n () => knownAssets.delete(path),\n )\n }\n }\n\n const urls = await Promise.all(paths.map((path) => knownAssets.get(path) ?? null))\n\n return Object.fromEntries(paths.map((path, index) => [path, urls[index] ?? null]))\n }\n\n const context: AdminContext = {\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 pickImage: pickImage ?? null,\n assetUrls: assetUrls ? cachedAssetUrls : null,\n blockLabels() {\n // Once for the session, and a failure is not remembered: the next notice asks again.\n blockLabelsPending ??= blockLabelsOf\n ? blockLabelsOf(context).catch(() => {\n blockLabelsPending = null\n\n return null\n })\n : Promise.resolve(null)\n\n return blockLabelsPending\n },\n reload,\n refreshManifest,\n setLocale,\n setUser(user) {\n state.user = user\n adoptTheme(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 return context\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","<script setup lang=\"ts\">\nimport { computed, watch } from 'vue'\nimport { useRouter } from 'vue-router'\nimport { WxResult } from '@webx-ui/core'\nimport { useAdmin } from './admin'\nimport { useTranslate } from './i18n'\n\n/**\n * What the panel opens on.\n *\n * Until now nothing answered at `/`: the routes are the modules', and no module claimed the\n * root — so signing in landed on a blank page and the first thing anybody did was click a\n * section. A module says which one that should be with `landing: true`, and here is where it\n * is honoured.\n *\n * It waits for the manifest rather than redirecting on the spot, because what a panel has is\n * something only the server knows: a landing module whose Composer half is not installed is\n * not in the navigation, and sending somebody to a section that is not there would be worse\n * than sending them nowhere. The fallback is the first entry of the menu, which is the section\n * this panel would call its front page anyway.\n */\nconst props = withDefaults(defineProps<{ landing?: string | null }>(), { landing: null })\n\nconst admin = useAdmin()\nconst router = useRouter()\nconst t = useTranslate('webx-admin')\n\nconst destination = computed<string | null>(() => {\n const entries = admin.nav.value\n\n if (entries.length === 0) {\n return null\n }\n\n const claimed = props.landing === null ? undefined : entries.find((e) => e.path === props.landing)\n\n return (claimed ?? entries[0])?.path ?? null\n})\n\nwatch(\n destination,\n (path) => {\n if (path !== null) void router.replace(path)\n },\n { immediate: true },\n)\n</script>\n\n<template>\n <!--\n Only for a panel that has nothing to show. While the manifest is on its way the shell is\n already drawing its own spinner, and a second one inside it would be two.\n -->\n <wx-result\n v-if=\"destination === null && admin.state.status === 'ready'\"\n status=\"info\"\n :title=\"t('shell.empty-title')\"\n :subtitle=\"t('shell.empty-description')\"\n />\n</template>\n","<script setup lang=\"ts\">\nimport { computed, onMounted, useTemplateRef, watch } 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 *\n * A group names its own icon; the gear is what it falls back to, because that is the picture\n * every group had before groups could carry one. Inside a group, the entries under no caption\n * come first and each caption follows with its own — a caption adds no depth, and on the narrow\n * rail it is a rule.\n *\n * An entry may carry a count — what waits for this person in that section, as the module said\n * under `meta.badge` of the manifest. On the collapsed rail the label is gone and so is the count\n * beside it; the entry's title still says where it goes.\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 /*\n * The longest path that contains this one, on a segment boundary: a section may live inside\n * another's path (`/services/categories` under `/services`), and the first match in the order\n * of the menu would light up the outer one.\n */\n get: () => {\n const match = admin.nav.value\n .filter((entry) => route.path === entry.path || route.path.startsWith(`${entry.path}/`))\n .sort((a, b) => b.path.length - a.path.length)[0]\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\n/*\n * A long menu scrolls on its own, and a reload puts it back at the top — with the current\n * section, often in a branch near the foot, out of sight. Once, on arrival: the menu comes with\n * the manifest, so that is when there first is a current entry, and its branch opens with a\n * transition, so the row is only where it will stay once that has run. Afterwards the user\n * clicked the entry, so it is already where they are looking.\n */\nconst root = useTemplateRef<{ $el: Element | null }>('root')\n\nlet revealed = false\n\nasync function reveal(): Promise<void> {\n const el = root.value?.$el\n\n if (revealed || current.value === '' || !(el instanceof HTMLElement)) return\n revealed = true\n await Promise.allSettled((el.getAnimations?.({ subtree: true }) ?? []).map((a) => a.finished))\n el.querySelector('.wx-menu-row.is-active')?.scrollIntoView?.({ block: 'center' })\n}\n\nonMounted(reveal)\nwatch(current, reveal, { flush: 'post' })\n</script>\n\n<template>\n <wx-menu\n ref=\"root\"\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 <template v-if=\"entry.badge\" #trailing>\n <wx-badge\n size=\"sm\"\n type=\"warning\"\n round\n :aria-label=\"t('nav.badge', { count: entry.badge })\"\n >\n {{ entry.badge }}\n </wx-badge>\n </template>\n </wx-menu-item>\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=\"group.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 <template v-if=\"entry.badge\" #trailing>\n <wx-badge\n size=\"sm\"\n type=\"warning\"\n round\n :aria-label=\"t('nav.badge', { count: entry.badge })\"\n >\n {{ entry.badge }}\n </wx-badge>\n </template>\n </wx-menu-item>\n <wx-menu-group v-for=\"section in group.sections\" :key=\"section.id\" :title=\"section.title\">\n <wx-menu-item\n v-for=\"entry in section.entries\"\n :key=\"entry.id\"\n :value=\"entry.id\"\n :icon=\"entry.icon ?? undefined\"\n :label=\"entry.title\"\n >\n <template v-if=\"entry.badge\" #trailing>\n <wx-badge\n size=\"sm\"\n type=\"warning\"\n round\n :aria-label=\"t('nav.badge', { count: entry.badge })\"\n >\n {{ entry.badge }}\n </wx-badge>\n </template>\n </wx-menu-item>\n </wx-menu-group>\n </wx-submenu>\n </wx-menu>\n</template>\n","<script setup lang=\"ts\">\nimport { useRouter } from 'vue-router'\nimport { WxButton, WxResult } from '@webx-ui/core'\nimport { useAdmin } from './admin'\nimport { useTranslate } from './i18n'\n\n/**\n * What an address no section answers for shows.\n *\n * Until now nothing did, and the content area simply stayed empty — an old bookmark, a section\n * this site does not have installed, or a typo all looked like a panel that had broken. The way\n * back is the panel's start, which is wherever `/` leads on this site.\n *\n * Drawn only once the panel is ready: while the manifest is on its way the shell shows its own\n * spinner, and a section that does exist is never «not found» for the second before it loads.\n */\nconst admin = useAdmin()\nconst router = useRouter()\nconst t = useTranslate('webx-admin')\n</script>\n\n<template>\n <wx-result\n v-if=\"admin.state.status === 'ready'\"\n class=\"wx-admin-not-found\"\n status=\"404\"\n :title=\"t('shell.not-found-title')\"\n :subtitle=\"t('shell.not-found-description')\"\n >\n <template #actions>\n <wx-button type=\"primary\" @click=\"router.push('/')\">{{\n t('shell.not-found-home')\n }}</wx-button>\n </template>\n </wx-result>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, onUnmounted, ref, watchEffect } from 'vue'\nimport { useRoute } from 'vue-router'\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 * A screen that is framed by something else — the panel on the site — and brings its own head:\n * no menu, no account corner, nothing to navigate to from inside a drawer beside the site.\n */\nconst route = useRoute()\n// Optional: a test may mount the shell without a router.\nconst bare = computed(() => route?.meta?.bare === true)\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\n/*\n * The same size, written on the document as well as on the shell.\n *\n * A custom property inherits down the tree, and a drawer, a dialog and a toast are not in the\n * tree: they are teleported to the end of `<body>`, outside the element that declares the step.\n * So the panel's own drawer — which on a phone is the whole screen — laid itself out with the\n * desktop step of 16 while everything behind it used 8. Declared on the root as well, the step\n * reaches the portals too, and nothing inside them has to know it is in one.\n */\nwatchEffect(() => {\n if (typeof document === 'undefined') return\n\n document.documentElement.dataset.wxShell = size.value\n})\n\nonUnmounted(() => {\n if (typeof document !== 'undefined') delete document.documentElement.dataset.wxShell\n})\n</script>\n\n<template>\n <wx-toaster placement=\"bottom-center\" />\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-if=\"bare\" class=\"wx-root wx-admin-bare\">\n <router-view />\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 class=\"wx-admin__drawer\"\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/*\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.\n *\n * Off the attribute the shell writes on the root as well as off the mount point, because\n * `#webx-app` is only what the Blade shell renders and a panel mounted anywhere else kept the\n * margin. What that cost was not the gap. Every dialog locks the page, and the lock zeroes\n * `margin-right` and pays the scrollbar back as padding — so a body with a margin gets that\n * margin's width back as content, and the whole page steps sideways each time a dialog opens\n * (CLAUDE.md §4). The two `:has()` selectors stay for the Blade page, where they hold from the\n * first paint rather than from the mount.\n */\n:root[data-wx-shell],\n:root[data-wx-shell] > body,\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\n/*\n * One left edge down the drawer.\n *\n * The drawer already insets what it holds by its own step, and `WxMenu` adds a second one of\n * its own — so the highlighted row started eight pixels further in than the account block under\n * it and the name over it. With every block the same colour as the sheet that is invisible; in\n * the dark theme, where each tint is a shape of its own, it is three blocks and three edges.\n * Unscoped and named on the drawer itself: the drawer is teleported to the end of the document,\n * so nothing that begins at `.wx-admin` reaches it.\n */\n.wx-admin__drawer .wx-drawer__content > .wx-menu {\n padding-inline: 0;\n}\n\n/*\n * The panel's step where the tree cannot reach: on the document root, for everything the panel\n * teleports out of itself — drawers, dialogs, the toaster. The shell writes the size class here\n * as a data attribute, and these three lines say the same thing the three above `.wx-admin`\n * say. Unscoped, because `:root` is nobody's element.\n */\n:root[data-wx-shell='drawer'] {\n --wx-gap: var(--wx-space-8);\n}\n\n:root[data-wx-shell='rail'] {\n --wx-gap: var(--wx-space-12);\n}\n\n:root[data-wx-shell='sidebar'] {\n --wx-gap: var(--wx-space-16);\n}\n</style>\n","import { inject, type Component, type InjectionKey } from 'vue'\nimport type { AdminContext } from './admin'\nimport type { Http } from './http'\n\n/**\n * What `GET /api/cms/site-bar/context` says about the page in front of the panel on the site —\n * mirrors `WebxUi\\Admin\\Http\\Controllers\\SiteBarController::context()`.\n */\nexport interface SiteBarContext {\n /** The path that was asked about, without its query. */\n url: string\n /** The language of the address, when the registry knows it. */\n locale: string | null\n /** The record behind the address, or null — a page of the site's own code, or none it may open. */\n entity: SiteBarEntity | null\n /** The tabs this administrator gets here, in order. */\n sections: string[]\n}\n\nexport type SiteBarStatus = 'published' | 'scheduled' | 'unpublished' | 'trashed'\n\nexport interface SiteBarEntity {\n type: string\n id: number | string\n /** What the record is, in the panel's language: «Page», «Article». */\n kind: string\n label: string\n status: SiteBarStatus\n /** Absent for a record that keeps no draft. */\n draft: SiteBarDraft | null\n updatedAt: string | null\n /** Where the panel edits it, relative to the panel. */\n edit: string | null\n /** A signed link to the draft shown as the page it will be; only while there is a draft. */\n preview?: string | null\n /** What the record's editor in the panel calls it — `editing/{editing}/{id}` is its heartbeat. */\n editing?: string | null\n /** The revision the record's editor holds; sent with «Publish» so an unseen edit is not published. */\n revision?: string | null\n can: { edit: boolean; publish: boolean }\n}\n\nexport interface SiteBarDraft {\n differs: boolean\n by: string | null\n at: string | null\n /** How many fields of the record the draft changes. */\n changes?: number\n /** Whether the last hand on the draft was this administrator's. */\n mine?: boolean\n}\n\n/**\n * A tab a section adds to the panel on the site — «SEO» from `module-seo`, «Blocks» from\n * `module-blocks`.\n *\n * The server decides whether the tab is there (`SiteBar::section()` answers per page and per\n * administrator, and `context.sections` lists the ones that said yes); the module brings what\n * it looks like. Split that way so the frame never learns what its tabs are about, and a tab\n * the server offers without a module on this side is simply not drawn.\n */\nexport interface SiteBarTab {\n /** The name the server registered the section under — and the tab's own, as `show()` names it. */\n name: string\n /**\n * More sections the same tab stands for: drawn once when the server offered any of them.\n * «Blocks» is the record's blocks and the layout's regions — two rights, one picker on the page.\n */\n sections?: string[]\n /** What the tab is called, in the panel's language. */\n label: (admin: AdminContext) => string\n /** Drawn inside the tab, with the page's context as its `context` prop. */\n component: Component\n}\n\n/**\n * @param locale The language the page says it is in (`<html lang>`). The address's own row wins\n * over it; it only matters for a page of the site's own code, which has no row.\n */\nexport function fetchSiteBarContext(\n http: Http,\n apiPath: string,\n url: string,\n locale?: string | null,\n): Promise<{ data: SiteBarContext }> {\n const language = locale ? `&locale=${encodeURIComponent(locale)}` : ''\n\n return http.get<{ data: SiteBarContext }>(\n `${apiPath}/site-bar/context?url=${encodeURIComponent(url)}${language}`,\n )\n}\n\n/**\n * What the panel can ask of the page it is framed in:\n *\n * - `close` — shut the drawer;\n * - `title` — put this in the browser's tab: the page's `<title>` after an edit, so the editor\n * sees it changed without reloading (the rest of `<head>` waits for the next load);\n * - `reload` — load the page again: after «Publish» and «Discard» it is the site's again;\n * - `tab` — the tab in front, for the page to remember and open the next frame on;\n * - `blocks` — whose blocks may be picked on the page — the record's, a region's — and what each\n * is called;\n * - `mode` — picking blocks on the page on or off (§8);\n * - `select` / `highlight` — frame a block on the page and bring it into view, or stop;\n * - `replace` — put this markup in place of the block's range: the block drawn from the draft;\n * - `insert` — the same when the page has the block, otherwise put it in after the first of\n * `after` the page has, before the first of `before`, or — `top` — at the end of its tree;\n * - `move` — carry the block's range to where `after`/`before` say, `html` if the page lacks it;\n * - `remove` — take the block's range out (removed, or hidden: the site does not draw it);\n * - `assets` — add the styles and script of block types the page did not have.\n *\n * A block is named by its key and its `scope` — `{type}:{id}` of a record, `region:{name}` of a\n * region — since the header and the page are two trees that never promised each other unique keys.\n */\nexport type SiteBarMessage =\n | { type: 'close' }\n | { type: 'title'; title: string }\n | { type: 'reload' }\n | { type: 'tab'; name: string }\n | { type: 'blocks'; sources: SiteBarBlockSource[] }\n | { type: 'mode'; on: boolean }\n | { type: 'select'; key: string | null; scope?: string }\n | { type: 'highlight'; key: string | null; scope?: string }\n | { type: 'replace'; key: string; scope?: string; html: string }\n | ({ type: 'insert' | 'move'; key: string; scope?: string; html: string } & SiteBarPlace)\n | { type: 'remove'; key: string; scope?: string }\n | { type: 'assets'; hash: string; css: string | null; js: string | null }\n\n/** Where a block that is not on the page goes: by its neighbours, nearest first. */\nexport interface SiteBarPlace {\n after?: string[]\n before?: string[]\n /** It stands at the top level of its tree: with no neighbour on the page, at the tree's end. */\n top?: boolean\n}\n\n/** One tree of blocks the page may pick from. */\nexport interface SiteBarBlockSource {\n /** `{type}:{id}` of a record (`page:12`), `region:{name}` of a region of the layout. */\n scope: string\n /** Said on the frame beside the block's type — «Header» — when the block is not the page's own. */\n title?: string\n items: { key: string; label: string }[]\n}\n\n/**\n * What the page says to the panel: a block picked on it, picking switched off there (Esc),\n * «Add below» pressed on the picked block, and the sets of block styles and scripts the page has —\n * sent with an edit, so that a type the page lacks brings its own and one it has brings nothing.\n */\nexport type SitePageMessage =\n | { type: 'select'; key: string; scope?: string }\n | { type: 'mode'; on: boolean }\n | { type: 'add'; key: string; scope?: string }\n | { type: 'bundles'; hashes: string[] }\n /** The regions the page prints — the layout's, and the shared blocks placed on this page. */\n | { type: 'regions'; names: string[] }\n\n/**\n * Listen to the page the panel is framed in. Only that page — the parent window, from this\n * origin — and only messages of the bar: anything else on the page can post messages too.\n * Returns the way to stop listening.\n */\nexport function onSitePage(listener: (message: SitePageMessage) => void): () => void {\n if (typeof window === 'undefined' || window.parent === window) return () => {}\n\n const handler = (event: MessageEvent): void => {\n if (event.origin !== window.location.origin || event.source !== window.parent) return\n\n const data = event.data as { wxBar?: unknown; type?: unknown } | null\n\n if (typeof data !== 'object' || data === null || data.wxBar !== 1) return\n if (\n data.type !== 'select' &&\n data.type !== 'mode' &&\n data.type !== 'add' &&\n data.type !== 'bundles' &&\n data.type !== 'regions'\n )\n return\n\n listener(data as SitePageMessage)\n }\n\n window.addEventListener('message', handler)\n\n return () => window.removeEventListener('message', handler)\n}\n\n/**\n * What the screen of the bar offers the tabs inside it: bring a tab forward (a block picked on\n * the page opens «Blocks»), and read the page's context again (an edit changes the draft the\n * head counts).\n */\nexport interface SiteBarHost {\n show(name: string): void\n refresh(): Promise<void>\n}\n\nexport const siteBarKey: InjectionKey<SiteBarHost> = Symbol('wx-site-bar')\n\n/** The bar around a tab; null outside one — a tab drawn in a test or a demo. */\nexport function useSiteBar(): SiteBarHost | null {\n return inject(siteBarKey, null)\n}\n\n/**\n * «Publish» in the head of the bar: the record's draft on the site. `revision` is the one the bar\n * held; an edit made since is answered 409 with who made it, and nothing is published.\n */\nexport function publishSiteBarEntity(\n http: Http,\n apiPath: string,\n entity: { type: string; id: number | string; revision?: string | null },\n): Promise<unknown> {\n return http.post(`${apiPath}/site-bar/publish`, {\n type: entity.type,\n id: entity.id,\n ...(entity.revision ? { revision: entity.revision } : {}),\n })\n}\n\n/** «Discard»: the record's draft goes, and the record is what the site shows. */\nexport function discardSiteBarEntity(\n http: Http,\n apiPath: string,\n entity: { type: string; id: number | string },\n): Promise<unknown> {\n return http.post(`${apiPath}/site-bar/discard`, { type: entity.type, id: entity.id })\n}\n\n/**\n * Say something to the page the panel is framed in. Only to that page's own origin: the screen\n * may be opened on its own, or framed by somebody else, and neither should hear it.\n */\nexport function tellSitePage(message: SiteBarMessage | 'close'): void {\n if (typeof window === 'undefined' || window.parent === window) return\n\n const body = typeof message === 'string' ? { type: message } : message\n\n window.parent.postMessage({ wxBar: 1, ...body }, window.location.origin)\n}\n","<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, onMounted, provide, ref, watch } from 'vue'\nimport { useRoute } from 'vue-router'\nimport { confirm, toast, type BadgeType } from '@webx-ui/core'\nimport { useAdmin } from './admin'\nimport DateText from './DateText.vue'\nimport { useErrorText } from './errors'\nimport { HttpError } from './http'\nimport { useTranslate } from './i18n'\nimport {\n discardSiteBarEntity,\n fetchSiteBarContext,\n publishSiteBarEntity,\n siteBarKey,\n tellSitePage,\n type SiteBarContext,\n type SiteBarStatus,\n type SiteBarTab,\n} from './siteBar'\n\n/**\n * `/cms/_bar` — the panel on the site, as the drawer beside a page of the site shows it\n * (WEBX_UI_SITE_BAR.md, §5).\n *\n * The same panel as everywhere else, in a shell without its navigation: the page it is about is\n * the one beside it, and the way into the rest of the panel is «Open in panel», which leaves the\n * site for the record's own screen. The page is named by `?url=`, which the script on the site\n * writes from its own address, and its language by `?locale=`, from its `<html lang>`.\n *\n * «Page» is the frame's own tab; the others come from the modules (`siteBarTabs`), each drawn\n * only where the server listed it for this page.\n */\nconst admin = useAdmin()\nconst route = useRoute()\nconst t = useTranslate('webx-admin')\nconst errorText = useErrorText()\n\nconst context = ref<SiteBarContext | null>(null)\nconst loading = ref(true)\nconst error = ref<string | null>(null)\nconst tab = ref<string | number>('page')\n/* Set while the bar itself moves the tab — on reading the page — rather than the editor. */\nlet settling = false\n\nconst url = computed(() => (typeof route.query.url === 'string' ? route.query.url : '/'))\nconst locale = computed(() =>\n typeof route.query.locale === 'string' && route.query.locale !== '' ? route.query.locale : null,\n)\n\n/* The modules' tabs the server offered here, in its order. One it offered that no module on this\n side draws is left out rather than shown empty; a tab that stands for several sections is\n drawn once, where the first of them stands. */\nconst moduleTabs = computed<SiteBarTab[]>(() => {\n const known = (admin.modules ?? []).flatMap((module) => module.siteBarTabs ?? [])\n const drawn: SiteBarTab[] = []\n\n for (const name of context.value?.sections ?? []) {\n const tab = name === 'page' ? undefined : known.find((one) => namesOf(one).includes(name))\n\n if (tab && !drawn.includes(tab)) drawn.push(tab)\n }\n\n return drawn\n})\n\nfunction namesOf(tab: SiteBarTab): string[] {\n return [tab.name, ...(tab.sections ?? [])]\n}\n\n/* The tabs on screen by the names they answer to: «Page», then the modules'. */\nconst tabNames = computed(() => [\n ...(context.value?.sections.includes('page') ? ['page'] : []),\n ...moduleTabs.value.map((one) => one.name),\n])\n\n/* The tab the page remembered from the last time — the frame is made again on every page. */\nconst remembered = computed(() =>\n typeof route.query.tab === 'string' && route.query.tab !== '' ? route.query.tab : null,\n)\nconst entity = computed(() => context.value?.entity ?? null)\n\n/*\n * Out of the frame to the panel's own address, in a tab of its own: a second panel inside the\n * drawer is a panel nobody can work in, and taking the whole window would close the page the\n * editor came from — the one they are about to check the edit on.\n */\nconst panelHref = computed(() =>\n entity.value?.edit ? `${admin.basePath.replace(/\\/$/, '')}${entity.value.edit}` : null,\n)\n\nconst STATUS_TYPES: Record<SiteBarStatus, BadgeType> = {\n published: 'success',\n scheduled: 'info',\n unpublished: 'warning',\n trashed: 'danger',\n}\n\n/**\n * @param quiet Read again behind what is on screen — after an edit in a tab — rather than from\n * scratch: the tabs stay, and the one that is open stays open.\n */\nasync function load(quiet = false): Promise<void> {\n loading.value = !quiet\n error.value = null\n\n try {\n const body = await fetchSiteBarContext(admin.http, admin.apiPath, url.value, locale.value)\n\n context.value = body.data\n\n if (!quiet || !tabNames.value.includes(String(tab.value))) {\n const chosen =\n remembered.value !== null && tabNames.value.includes(remembered.value)\n ? remembered.value\n : (tabNames.value[0] ?? 'page')\n\n // Not a choice of the editor's, so not one for the page to remember.\n if (chosen !== tab.value) settling = true\n tab.value = chosen\n }\n } catch (caught) {\n if (quiet && context.value !== null) return\n\n context.value = null\n // A refusal is the server's to word — «nothing here you may edit»; anything else is ours.\n error.value =\n caught instanceof HttpError && caught.status === 403 && caught.message !== ''\n ? caught.message\n : t('site-bar.error')\n } finally {\n loading.value = false\n }\n}\n\nwatch(url, () => void load())\n\n// The page keeps the tab the editor chose in its `wx-bar` state (§5): the next page's frame opens\n// on it. A tab the bar fell back on — the remembered one is not on this page — is not a choice,\n// and the page keeps the one it had.\nwatch(tab, (name) => {\n if (settling) {\n settling = false\n\n return\n }\n\n tellSitePage({ type: 'tab', name: String(name) })\n})\n\nprovide(siteBarKey, {\n show: (name: string) => {\n const found =\n name === 'page' ? 'page' : moduleTabs.value.find((one) => namesOf(one).includes(name))?.name\n\n if (found && tabNames.value.includes(found)) tab.value = found\n },\n refresh: () => load(true),\n})\n\n/*\n * The draft's buttons are the record's (§9): there when its section said how it is published and\n * the administrator may, whoever left the draft. Without them the bar says the draft is there and\n * the way into the panel stays in the head.\n */\nconst draft = computed(() => (entity.value?.draft?.differs ? entity.value.draft : null))\nconst mayPublish = computed(() => draft.value !== null && entity.value?.can.publish === true)\nconst busy = ref(false)\n\nasync function publish(): Promise<void> {\n const found = entity.value\n\n if (!found || busy.value) return\n\n busy.value = true\n\n try {\n await publishSiteBarEntity(admin.http, admin.apiPath, found)\n // The page beside the bar is the site again, and shows what was just published.\n tellSitePage({ type: 'reload' })\n } catch (caught) {\n toast.danger(errorText(caught))\n // A 409 is somebody's edit the bar had not seen: the head counts it now.\n void load(true)\n } finally {\n busy.value = false\n }\n}\n\nasync function discard(): Promise<void> {\n const found = entity.value\n\n if (!found || busy.value) return\n\n const agreed = await confirm({\n title: t('site-bar.discard-title'),\n message: t('site-bar.discard-text'),\n confirmText: t('site-bar.discard'),\n cancelText: t('site-bar.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n busy.value = true\n\n try {\n await discardSiteBarEntity(admin.http, admin.apiPath, found)\n tellSitePage({ type: 'reload' })\n } catch (caught) {\n toast.danger(errorText(caught))\n } finally {\n busy.value = false\n }\n}\n\nfunction close(): void {\n tellSitePage('close')\n}\n\n// Esc inside the frame never reaches the page's own listener, so it is passed on.\nfunction onKeydown(event: KeyboardEvent): void {\n if (event.key === 'Escape' && !event.defaultPrevented) close()\n}\n\nonMounted(() => {\n void load()\n window.addEventListener('keydown', onKeydown)\n})\n\nonBeforeUnmount(() => window.removeEventListener('keydown', onKeydown))\n</script>\n\n<template>\n <div class=\"wx-site-bar\">\n <header class=\"wx-site-bar__head\">\n <div class=\"wx-site-bar__what\">\n <wx-text weight=\"semibold\" truncate class=\"wx-site-bar__title\">\n <template v-if=\"entity\">{{ entity.kind }} · {{ entity.label }}</template>\n <template v-else>{{ t('site-bar.title') }}</template>\n </wx-text>\n\n <div v-if=\"entity\" class=\"wx-site-bar__state\">\n <wx-badge :type=\"STATUS_TYPES[entity.status]\" dot size=\"sm\">\n {{ t(`site-bar.status-${entity.status}`) }}\n </wx-badge>\n <wx-text v-if=\"entity.updatedAt\" size=\"sm\" tone=\"muted\">\n {{ t('site-bar.edited') }} <DateText :value=\"entity.updatedAt\" size=\"sm\" tone=\"muted\" />\n </wx-text>\n </div>\n </div>\n\n <wx-action\n v-if=\"panelHref\"\n icon=\"external-link\"\n :title=\"t('site-bar.open-in-panel')\"\n :href=\"panelHref\"\n target=\"_blank\"\n rel=\"noopener\"\n />\n <wx-action icon=\"close\" :title=\"t('site-bar.close')\" @click=\"close\" />\n </header>\n\n <wx-alert\n v-if=\"draft && mayPublish\"\n type=\"warning\"\n class=\"wx-site-bar__draft\"\n :title=\"t('site-bar.changes', { count: draft.changes ?? 1 })\"\n :description=\"\n draft.mine === false && draft.by ? t('site-bar.draft-other', { name: draft.by }) : undefined\n \"\n >\n <template #actions>\n <wx-button size=\"sm\" type=\"primary\" :loading=\"busy\" @click=\"publish\">\n {{ t('site-bar.publish') }}\n </wx-button>\n <wx-button size=\"sm\" variant=\"outline\" :disabled=\"busy\" @click=\"discard\">\n {{ t('site-bar.discard') }}\n </wx-button>\n <!-- After a reload the page is the site again: the whole draft is in its preview. -->\n <wx-button\n v-if=\"entity?.preview\"\n size=\"sm\"\n variant=\"text\"\n :href=\"entity.preview\"\n target=\"_blank\"\n rel=\"noopener\"\n >\n {{ t('site-bar.preview') }}\n </wx-button>\n </template>\n </wx-alert>\n\n <wx-alert\n v-else-if=\"draft\"\n type=\"warning\"\n class=\"wx-site-bar__draft\"\n :title=\"draft.by ? t('site-bar.draft-by', { name: draft.by }) : t('site-bar.draft')\"\n >\n <template v-if=\"entity?.preview\" #actions>\n <wx-button size=\"sm\" variant=\"text\" :href=\"entity.preview\" target=\"_blank\" rel=\"noopener\">\n {{ t('site-bar.preview') }}\n </wx-button>\n </template>\n </wx-alert>\n\n <div v-if=\"loading\" class=\"wx-site-bar__body\">\n <wx-loading />\n </div>\n\n <div v-else-if=\"error !== null\" class=\"wx-site-bar__body\">\n <wx-result status=\"info\" :title=\"error\">\n <wx-button @click=\"load\">{{ t('site-bar.retry') }}</wx-button>\n </wx-result>\n </div>\n\n <!-- Kept alive: a half-typed form survives a look at another tab, and a block picked on the\n page reaches «Blocks» even while «Page» is in front. -->\n <!-- Segmented and stretched: three or four short words share the drawer's width, and a strip of\n underlined words read as a second heading under the record's. -->\n <wx-tabs\n v-else-if=\"context\"\n v-model=\"tab\"\n variant=\"pill\"\n size=\"md\"\n align=\"stretch\"\n keep-alive\n class=\"wx-site-bar__tabs\"\n >\n <wx-tab v-if=\"context.sections.includes('page')\" value=\"page\" :label=\"t('site-bar.tab-page')\">\n <div class=\"wx-site-bar__body\">\n <wx-descriptions v-if=\"entity\" :columns=\"1\" size=\"sm\">\n <wx-descriptions-item :label=\"t('site-bar.address')\">\n <wx-text size=\"sm\" class=\"wx-site-bar__url\">{{ context.url }}</wx-text>\n </wx-descriptions-item>\n <wx-descriptions-item :label=\"t('site-bar.status')\">\n {{ t(`site-bar.status-${entity.status}`) }}\n </wx-descriptions-item>\n </wx-descriptions>\n\n <wx-text v-else size=\"sm\" tone=\"muted\">{{ t('site-bar.no-entity') }}</wx-text>\n\n <wx-button\n v-if=\"panelHref\"\n type=\"primary\"\n block\n :href=\"panelHref\"\n target=\"_blank\"\n rel=\"noopener\"\n class=\"wx-site-bar__open\"\n >\n {{ t('site-bar.open-in-panel') }}\n </wx-button>\n </div>\n </wx-tab>\n\n <wx-tab v-for=\"one in moduleTabs\" :key=\"one.name\" :value=\"one.name\" :label=\"one.label(admin)\">\n <div class=\"wx-site-bar__body\">\n <component :is=\"one.component\" :context=\"context\" />\n </div>\n </wx-tab>\n </wx-tabs>\n </div>\n</template>\n\n<style scoped>\n/*\n * The whole of the frame, drawer-wide: 380px on a desktop, a phone's width on a phone. Spaced\n * like a dialog rather than like a screen of the panel — there is no sidebar here to line up\n * with.\n */\n.wx-site-bar {\n display: flex;\n flex-direction: column;\n min-height: 100vh;\n background: var(--wx-bg-surface);\n color: var(--wx-text-default);\n}\n\n.wx-site-bar__head {\n display: flex;\n align-items: flex-start;\n gap: var(--wx-space-4);\n padding: var(--wx-space-12) var(--wx-space-12) var(--wx-space-8) var(--wx-space-16);\n border-bottom: 1px solid var(--wx-border-default);\n}\n\n.wx-site-bar__what {\n display: flex;\n flex: 1 1 auto;\n flex-direction: column;\n gap: var(--wx-space-4);\n min-width: 0;\n}\n\n.wx-site-bar__state {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--wx-space-8);\n}\n\n.wx-site-bar__draft {\n margin: var(--wx-space-12) var(--wx-space-16) 0;\n}\n\n.wx-site-bar__tabs {\n padding: var(--wx-space-8) var(--wx-space-16) 0;\n}\n\n.wx-site-bar__body {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-16);\n padding: var(--wx-space-16) 0;\n}\n\n.wx-site-bar > .wx-site-bar__body {\n padding: var(--wx-space-16);\n}\n\n.wx-site-bar__url {\n word-break: break-all;\n}\n</style>\n","import { inject, reactive, type App, type InjectionKey } from 'vue'\nimport {\n applyTheme,\n systemTheme,\n watchSystemTheme,\n type Theme,\n type ThemePreference,\n} from '@webx-ui/tokens'\n\nexport type { Theme, ThemePreference }\n\nconst STORED_THEME = 'webx.theme'\n\nexport interface ThemeState {\n /** What was chosen: one of the two themes, or an instruction to follow the machine. */\n preference: ThemePreference\n /** What that works out to at this moment — what is actually on the screen. */\n resolved: Theme\n}\n\nexport interface ThemeController {\n readonly state: ThemeState\n /**\n * Choose, paint and remember in this browser.\n *\n * Writing the choice down against the person is somebody else's job: this package knows\n * nothing about accounts, and a panel with no sign-in still deserves a theme.\n */\n set(preference: ThemePreference): void\n /**\n * Take up what the administrator's record says, without writing anything back to it.\n *\n * `null` is an answer — \"follow the machine\" is what somebody chose, and it has to travel\n * between their machines like any other choice. `undefined` is not: it is a server that\n * has never heard of themes, and the browser's own choice should survive it.\n */\n adopt(preference: ThemePreference | null | undefined): void\n}\n\nexport const themeKey: InjectionKey<ThemeController> = Symbol('webx-theme')\n\nexport function useTheme(): ThemeController {\n const theme = inject(themeKey, null)\n\n if (theme === null) {\n throw new Error('useTheme() was called outside a panel created by createAdmin().')\n }\n\n return theme\n}\n\nexport function provideTheme(app: App, theme: ThemeController): void {\n app.provide(themeKey, theme)\n}\n\nexport interface CreateThemeOptions {\n /**\n * Where the choice is kept in this browser, or `null` to keep it nowhere. It is read before\n * the panel paints anything, so the sign-in screen is already the right colour — the copy\n * that follows somebody between machines is the one on their account.\n */\n storageKey?: string | null\n}\n\nexport function createTheme(options: CreateThemeOptions = {}): ThemeController {\n const key = options.storageKey === undefined ? STORED_THEME : options.storageKey\n\n const state = reactive<ThemeState>({\n preference: read(key) ?? 'system',\n resolved: 'light',\n })\n\n function paint(): void {\n applyTheme(state.preference)\n state.resolved = state.preference === 'system' ? systemTheme() : state.preference\n }\n\n paint()\n\n /*\n * The stylesheet follows the machine on its own — nothing here has to repaint. This is so\n * that anything *reading* `resolved`, to paint a canvas or hand an editor a colour scheme,\n * hears about a room whose lights just went out.\n */\n watchSystemTheme((theme) => {\n if (state.preference === 'system') {\n state.resolved = theme\n }\n })\n\n return {\n state,\n set(preference) {\n state.preference = preference\n paint()\n remember(key, preference)\n },\n adopt(preference) {\n if (preference === undefined) {\n return\n }\n\n const chosen = preference ?? 'system'\n\n if (chosen === state.preference) {\n return\n }\n\n state.preference = chosen\n paint()\n remember(key, chosen)\n },\n }\n}\n\nfunction remember(key: string | null, preference: ThemePreference): void {\n if (key === null) {\n return\n }\n\n try {\n localStorage.setItem(key, preference)\n } catch {\n // Private windows, blocked site data. A theme is not worth an error.\n }\n}\n\nfunction read(key: string | null): ThemePreference | null {\n if (key === null) {\n return null\n }\n\n try {\n const stored = localStorage.getItem(key)\n\n return stored === 'light' || stored === 'dark' || stored === 'system' ? stored : null\n } catch {\n return null\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 {\n dateLocaleKey,\n dateTimezoneKey,\n localesKey,\n WebxUI,\n type LocaleOption,\n} from '@webx-ui/core'\nimport AdminLanding from './AdminLanding.vue'\nimport AdminNav from './AdminNav.vue'\nimport AdminNotFound from './AdminNotFound.vue'\nimport AdminShell from './AdminShell.vue'\nimport SiteBarScreen from './SiteBarScreen.vue'\nimport { createAdminContext, provideAdmin, type AdminContext } from './admin'\nimport { createHttp, type Http, type MethodOverride } from './http'\nimport { createI18n, provideI18n, type Dictionary, type I18n, type LocaleDescriptor } from './i18n'\nimport { createTheme, provideTheme, type ThemeController } from './theme'\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 /**\n * Send PUT, PATCH and DELETE as POST, for a host whose web server refuses them before PHP is\n * reached: `true` or `'header'` names the method in `X-HTTP-Method-Override`, `'field'` in\n * a `_method` field of the body. Off by default; ignored when `http` is given.\n */\n methodOverride?: MethodOverride\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 /** Light, dark or the machine's — applied before anything is drawn. */\n theme: ThemeController\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 // The root, unless the panel brought its own — a project with a dashboard has already\n // answered this question and should not be overruled.\n if (!routes.some((route) => route.path === '/')) {\n const landing = modules.find((module) => module.landing === true)\n\n routes.push({\n path: '/',\n name: 'webx.home',\n component: AdminLanding,\n props: { landing: landing?.path ?? landing?.routes?.[0]?.path ?? null },\n })\n }\n\n /*\n * The panel on the site (WEBX_UI_SITE_BAR.md): the drawer a page of the site frames. A route\n * of the same app rather than an app of its own, so it has the session, the words, the types\n * of fields and every module's tabs without a second bundle — the shell only leaves out its\n * navigation for it (`bare`).\n */\n routes.push({\n path: '/_bar',\n name: 'webx.site-bar',\n component: SiteBarScreen,\n meta: { bare: true },\n })\n\n // An address nothing answers for says so, instead of leaving the content area empty — unless\n // the project brought its own catch-all. Matched last whatever the order: the router ranks a\n // catch-all below every other route, including those a plugin adds later.\n if (!routes.some((route) => route.path.includes('(.*)'))) {\n routes.push({ path: '/:pathMatch(.*)*', name: 'webx.not-found', component: AdminNotFound })\n }\n\n const router = createRouter({\n history: createWebHistory(basePath),\n routes,\n })\n\n const i18n = createI18n({ locale: options.locale ?? preferredLocale() })\n\n /*\n * Made here rather than after mounting, because making it is what paints: the sign-in\n * screen is the first thing anybody sees and it should already be the colour they left the\n * panel in. What this browser remembers is the guess; the administrator's own record\n * replaces it the moment the session says who they are.\n */\n const theme = createTheme()\n\n const http =\n options.http ??\n createHttp({\n baseUrl: '',\n csrfUrl: `${apiPath}/auth/csrf-cookie`,\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 methodOverride: options.methodOverride,\n })\n\n i18n.defaults('webx-admin', adminMessages)\n\n /*\n * A dictionary is asked for once per language and visit: the words do not change while the\n * panel is open, and switching back to a language already read should not be a request. A\n * failed one is forgotten, so the next switch tries again.\n */\n const dictionaries = new Map<\n string,\n Promise<{ data: { locale: string; fallback: string; namespaces: Dictionary } }>\n >()\n\n async function loadDictionary(locale: string): Promise<void> {\n let pending = dictionaries.get(locale)\n\n if (pending === undefined) {\n pending = http.get<{\n data: { locale: string; fallback: string; namespaces: Dictionary }\n }>(`${apiPath}/translations/${locale}`)\n pending.catch(() => dictionaries.delete(locale))\n dictionaries.set(locale, pending)\n }\n\n const body = await pending\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 theme,\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 provideTheme(app, theme)\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 /*\n * A calendar is drawn in the language of the panel, not of the browser: the picker\n * would otherwise head a Russian screen with \"Sep 2026\" while every other date on it\n * goes through `useDates()` and reads Russian.\n */\n app.provide(\n dateLocaleKey,\n computed(() => i18n.state.locale),\n )\n\n /*\n * A moment is shown and entered on the site's clock, not the editor's: an event at 09:30 in\n * the site's zone read 04:30 to an editor abroad, and their pick went back with their own\n * offset. Until the manifest arrives there is no form to draw anyway.\n */\n app.provide(\n dateTimezoneKey,\n computed(() => context.state.manifest?.timezone),\n )\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 theme,\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","/** The forms a counted line is written in. Languages without `few` or `many` repeat `other`. */\nexport type PluralForm = 'one' | 'few' | 'many' | 'other'\n\n/**\n * Which form of a counted line to use for `count` in `locale`.\n *\n * The panel has no plural syntax of its own, so a line that has to carry its number up front —\n * \"5 pages are deleted\" — is written once per CLDR category and picked here. `zero` and `two`\n * fall back on `other`: none of the panel's languages writes them differently.\n */\nexport function pluralForm(count: number, locale: string): PluralForm {\n let form: Intl.LDMLPluralRule\n\n try {\n form = new Intl.PluralRules(locale).select(count)\n } catch {\n // A locale the browser does not know: English rules are the least wrong guess.\n form = new Intl.PluralRules('en').select(count)\n }\n\n return form === 'one' || form === 'few' || form === 'many' ? form : 'other'\n}\n","import { onBeforeUnmount, onMounted } from 'vue'\n\n/**\n * Hands an editor the keys pressed while nothing on the page has focus.\n *\n * An editor listens with `@keydown` on its own root, so Ctrl+S reaches it from any field inside.\n * A click on empty space — the page around the form, a label, a gap between cards — moves focus\n * to `<body>`, and from there the key never passes through the editor: the browser offered to\n * save the page as HTML instead. Only keys from `<body>` are passed on; one pressed in a dialog or\n * a menu belongs to that, and the editor's own root still gets everything from inside it.\n */\nexport function useBodyKeys(handler: (event: KeyboardEvent) => void): void {\n function listener(event: KeyboardEvent): void {\n if (event.target === document.body || event.target === document.documentElement) handler(event)\n }\n\n onMounted(() => window.addEventListener('keydown', listener))\n onBeforeUnmount(() => window.removeEventListener('keydown', listener))\n}\n","/**\n * Two edits of one record, merged against the version both started from.\n *\n * An editor holds three versions when its save is refused: what it opened with (base), what is\n * in the form now (mine) and what the record is now (theirs). A field only one side changed is\n * not a question — it goes in from whichever side changed it. Only a field both sides changed,\n * to different values, is a conflict, and only a conflict is put to the person.\n *\n * The values are the form's model, so the walk knows three shapes:\n *\n * - a plain object — the fields of a screen, the values of a block, the languages of a localized\n * field — merged key by key, so that two people writing two languages of one title do not\n * collide;\n * - a list of blocks (objects with a string `key`) — merged block by block: one side adding,\n * removing or moving a block is applied by key, and a block one side removed while the other\n * edited it is a conflict of its own;\n * - anything else — a string, a number, a list of ids — as a whole value.\n *\n * Nothing here knows what a page is: the same walk merges a service, a recipe or a region.\n */\n\nexport type MergeChoice = 'mine' | 'theirs'\n\n/** One step of the way to a value: a field (or a language) by name, or a block by key. */\nexport type MergeSegment = { field: string } | { block: string; type: string }\n\nexport interface MergeConflict {\n /** Stable for one place in the tree, so a choice survives a merge being run again. */\n id: string\n path: MergeSegment[]\n /**\n * `changed` — both sides wrote the value; `removed-mine` — this editor removed a block the\n * other side edited; `removed-theirs` — the other side removed a block this editor edited.\n */\n kind: 'changed' | 'removed-mine' | 'removed-theirs'\n base: unknown\n mine: unknown\n theirs: unknown\n}\n\nexport interface MergeResult<T> {\n /** The merge, with every conflict settled by its choice — this editor's side when there is none. */\n value: T\n conflicts: MergeConflict[]\n}\n\ntype Plain = Record<string, unknown>\n\ninterface Node extends Plain {\n key: string\n type?: unknown\n}\n\ninterface Walk {\n choices: Record<string, MergeChoice>\n conflicts: MergeConflict[]\n}\n\n/** Deleted on one side: what a walk answers to drop the key or the block. */\nconst GONE = undefined\n\n/**\n * Merge `mine` and `theirs` against `base`.\n *\n * `choices` settles conflicts by id; one with no choice keeps this editor's side in `value` and\n * is listed in `conflicts` all the same, so the caller can tell a clean merge from a settled one.\n */\nexport function mergeThreeWay<T>(\n base: T,\n mine: T,\n theirs: T,\n choices: Record<string, MergeChoice> = {},\n): MergeResult<T> {\n const walk: Walk = { choices, conflicts: [] }\n\n return { value: merge(base, mine, theirs, [], walk) as T, conflicts: walk.conflicts }\n}\n\n/**\n * Where `after` differs from `before`, as paths — what the other side changed, said to the\n * person before they pull it in. A block added or removed is one path, not one per field.\n */\nexport function changedPaths(before: unknown, after: unknown): MergeSegment[][] {\n const found: MergeSegment[][] = []\n\n diff(before, after, [], found)\n\n return found\n}\n\n/** Equal as values: key order, and `null` against a missing key, do not count as a difference. */\nexport function sameValue(a: unknown, b: unknown): boolean {\n if (a === b) return true\n if (a == null || b == null) return a == null && b == null\n\n if (Array.isArray(a) || Array.isArray(b)) {\n if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false\n\n return a.every((item, index) => sameValue(item, b[index]))\n }\n\n if (isPlain(a) && isPlain(b)) {\n const keys = new Set([...Object.keys(a), ...Object.keys(b)])\n\n for (const key of keys) {\n if (!sameValue(a[key], b[key])) return false\n }\n\n return true\n }\n\n return false\n}\n\nexport function conflictId(path: MergeSegment[]): string {\n return path.map((step) => ('block' in step ? `#${step.block}` : step.field)).join('/')\n}\n\nfunction merge(\n base: unknown,\n mine: unknown,\n theirs: unknown,\n path: MergeSegment[],\n walk: Walk,\n): unknown {\n if (sameValue(mine, theirs)) return mine\n if (sameValue(base, mine)) return theirs\n if (sameValue(base, theirs)) return mine\n\n if (isNodeList(mine) && isNodeList(theirs) && (base == null || isNodeList(base))) {\n return mergeList(base ?? [], mine, theirs, path, walk)\n }\n\n if (isPlain(mine) && isPlain(theirs) && (base == null || isPlain(base))) {\n return mergeObject(base ?? {}, mine, theirs, path, walk)\n }\n\n return settle(walk, path, 'changed', base, mine, theirs, mine, theirs)\n}\n\nfunction mergeObject(\n base: Plain,\n mine: Plain,\n theirs: Plain,\n path: MergeSegment[],\n walk: Walk,\n): Plain {\n const merged: Plain = {}\n const keys = [...Object.keys(mine), ...Object.keys(theirs).filter((key) => !(key in mine))]\n\n for (const key of keys) {\n const value = merge(base[key], mine[key], theirs[key], [...path, { field: key }], walk)\n\n if (value !== GONE) merged[key] = value\n }\n\n return merged\n}\n\nfunction mergeList(\n base: Node[],\n mine: Node[],\n theirs: Node[],\n path: MergeSegment[],\n walk: Walk,\n): Node[] {\n const before = byKey(base)\n const ours = byKey(mine)\n const other = byKey(theirs)\n const kept = new Map<string, Node>()\n\n for (const key of new Set([...ours.keys(), ...other.keys()])) {\n const b = before.get(key)\n const m = ours.get(key)\n const t = other.get(key)\n const at: MergeSegment[] = [...path, { block: key, type: typeOf(m ?? t) }]\n let value: unknown\n\n if (m && t) {\n value = merge(b, m, t, at, walk)\n } else if (m) {\n // Theirs has no such block: this editor added it, they removed it untouched, or they\n // removed a block this editor was editing — the one case that is a question.\n if (b === undefined) value = m\n else if (sameValue(b, m)) value = GONE\n else value = settle(walk, at, 'removed-theirs', b, m, GONE, m, GONE)\n } else if (t) {\n if (b === undefined) value = t\n else if (sameValue(b, t)) value = GONE\n else value = settle(walk, at, 'removed-mine', b, GONE, t, GONE, t)\n }\n\n if (value !== GONE) kept.set(key, value as Node)\n }\n\n // The order of whichever side moved something; this editor's when both did, or neither.\n const theirsMoved = !sameOrder(base, theirs)\n const mineMoved = !sameOrder(base, mine)\n const [lead, follow] = theirsMoved && !mineMoved ? [theirs, mine] : [mine, theirs]\n\n return order(lead, follow, kept)\n}\n\n/** Lay the kept blocks out in the lead's order, and slot the others in after their neighbour. */\nfunction order(lead: Node[], follow: Node[], kept: Map<string, Node>): Node[] {\n const keys = lead.map((node) => node.key).filter((key) => kept.has(key))\n const placed = new Set(keys)\n\n follow.forEach((node, index) => {\n if (placed.has(node.key) || !kept.has(node.key)) return\n\n let at = 0\n\n for (let back = index - 1; back >= 0; back--) {\n const position = keys.indexOf(follow[back]!.key)\n\n if (position !== -1) {\n at = position + 1\n break\n }\n }\n\n keys.splice(at, 0, node.key)\n placed.add(node.key)\n })\n\n return keys.map((key) => kept.get(key)!)\n}\n\n/** Record a conflict and answer the side its choice names — this editor's when there is none. */\nfunction settle(\n walk: Walk,\n path: MergeSegment[],\n kind: MergeConflict['kind'],\n base: unknown,\n mine: unknown,\n theirs: unknown,\n keepMine: unknown,\n keepTheirs: unknown,\n): unknown {\n const id = conflictId(path)\n\n walk.conflicts.push({ id, path, kind, base, mine, theirs })\n\n return walk.choices[id] === 'theirs' ? keepTheirs : keepMine\n}\n\nfunction diff(\n before: unknown,\n after: unknown,\n path: MergeSegment[],\n found: MergeSegment[][],\n): void {\n if (sameValue(before, after)) return\n\n if (isNodeList(before) && isNodeList(after)) {\n const old = byKey(before)\n const now = byKey(after)\n\n for (const key of new Set([...old.keys(), ...now.keys()])) {\n const at: MergeSegment[] = [\n ...path,\n { block: key, type: typeOf(now.get(key) ?? old.get(key)) },\n ]\n\n if (old.has(key) && now.has(key)) diff(old.get(key), now.get(key), at, found)\n else found.push(at)\n }\n\n // Moved without anything else changing: the list itself is what changed.\n if (!sameOrder(before, after)) found.push(path)\n\n return\n }\n\n if (isPlain(before) && isPlain(after)) {\n for (const key of new Set([...Object.keys(before), ...Object.keys(after)])) {\n diff(before[key], after[key], [...path, { field: key }], found)\n }\n\n return\n }\n\n found.push(path)\n}\n\n/** Whether the blocks both sides still have stand in the same order. */\nfunction sameOrder(a: Node[], b: Node[]): boolean {\n const inB = new Set(b.map((node) => node.key))\n const inA = new Set(a.map((node) => node.key))\n const left = a.map((node) => node.key).filter((key) => inB.has(key))\n const right = b.map((node) => node.key).filter((key) => inA.has(key))\n\n return left.every((key, index) => key === right[index])\n}\n\nfunction byKey(list: Node[]): Map<string, Node> {\n return new Map(list.map((node) => [node.key, node]))\n}\n\nfunction typeOf(node: Node | undefined): string {\n return typeof node?.type === 'string' ? node.type : ''\n}\n\nfunction isPlain(value: unknown): value is Plain {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction isNodeList(value: unknown): value is Node[] {\n return (\n Array.isArray(value) && value.every((item) => isPlain(item) && typeof item.key === 'string')\n )\n}\n","import { onMounted, shallowRef } from 'vue'\nimport { useLocales } from '@webx-ui/core'\nimport type { ScreenNode } from '@webx-ui/schema'\nimport { useAdmin } from './admin'\nimport { useTranslate } from './i18n'\nimport type { MergeSegment } from './merge'\nimport type { BlockLabels } from './types'\n\nexport interface EditingLabelsOptions {\n /** The screen the record is edited on: its fields are named the way the form names them. */\n screen?: string\n /** What a top-level field is called, when the editor knows better than the screen. */\n field?: (name: string) => string | undefined\n /** What a type of block is called, when the editor knows better than the block library. */\n block?: (type: string) => string | undefined\n}\n\nexport interface EditingLabels {\n /** A place in the record as the person reads it: «Hero › Eyebrow · EN». */\n label(path: MergeSegment[]): string\n}\n\n/**\n * Names for the places in a record, the way the person who edits it knows them.\n *\n * A notice that says «Hero › Below cta · EN» has turned `below_cta` into words by guessing; the\n * block type calls that field «Text below the button», and that is the name the person looks for\n * in the form. The screen names the record's own fields, the block library names the blocks and\n * their fields, and a key nobody named is turned into words as a last resort.\n */\nexport function useEditingLabels(options: EditingLabelsOptions = {}): EditingLabels {\n const admin = useAdmin()\n const t = useTranslate('webx-admin')\n const locales = useLocales()\n\n const fields = shallowRef<Map<string, string>>(new Map())\n const blocks = shallowRef<BlockLabels | null>(null)\n\n onMounted(() => {\n if (options.screen && typeof admin.loadScreen === 'function') {\n admin\n .loadScreen(options.screen)\n .then((root) => (fields.value = screenLabels(root)))\n .catch(() => undefined)\n }\n\n // A hand-made context in a test, or a panel without the block library: keys turned into words.\n admin\n .blockLabels?.()\n .then((labels) => (blocks.value = labels))\n .catch(() => undefined)\n })\n\n function blockName(type: string): string {\n return options.block?.(type) ?? blocks.value?.type(type) ?? humanize(type || t('editing.block'))\n }\n\n function label(path: MergeSegment[]): string {\n const codes = new Set(locales.list.value.map((locale) => locale.code))\n const parts: string[] = []\n let language = ''\n // The type of the block whose own field this step may be: set on a block, kept over `values`.\n let inBlock: string | null = null\n\n path.forEach((step, index) => {\n if ('block' in step) {\n parts.push(blockName(step.type))\n inBlock = step.type\n\n return\n }\n\n // A block's fields sit under `values`, which is the shape of a node and not a word anybody\n // would recognise.\n if (step.field === 'values' && index > 0 && 'block' in path[index - 1]!) return\n\n // Nor is the field that holds a list of blocks: «Hero» says where it is, «Blocks › Hero»\n // only says it twice.\n const next = path[index + 1]\n\n if (next !== undefined && 'block' in next) return\n\n if (index === path.length - 1 && index > 0 && codes.has(step.field)) {\n language = step.field.toUpperCase()\n\n return\n }\n\n const owner = inBlock\n inBlock = null\n\n const named =\n owner !== null\n ? blocks.value?.field(owner, step.field)\n : index === 0\n ? (options.field?.(step.field) ?? fields.value.get(step.field))\n : undefined\n\n parts.push(named ?? humanize(step.field))\n })\n\n const named = parts.length === 0 ? t('editing.order') : parts.join(' › ')\n\n return language === '' ? named : `${named} · ${language}`\n }\n\n return { label }\n}\n\n/** Every named field of a screen tree with its label, wherever it sits — in a tab, in a card. */\nexport function screenLabels(root: ScreenNode[]): Map<string, string> {\n const found = new Map<string, string>()\n\n const walk = (nodes: ScreenNode[]): void => {\n for (const node of nodes) {\n // A block's schema keys its values by `id` where a screen names them; both are read.\n const name = (node as { name?: unknown }).name ?? (node as { id?: unknown }).id\n const label = (node as { label?: unknown }).label\n\n if (typeof name === 'string' && typeof label === 'string' && label !== '' && !found.has(name))\n found.set(name, label)\n\n if (Array.isArray(node.children)) walk(node.children)\n }\n }\n\n walk(root)\n\n return found\n}\n\nexport function humanize(name: string): string {\n const words = name.replace(/[_-]+/g, ' ').trim()\n\n return words.charAt(0).toUpperCase() + words.slice(1)\n}\n","import {\n computed,\n onBeforeUnmount,\n onMounted,\n ref,\n shallowRef,\n watch,\n type ComputedRef,\n type Ref,\n type ShallowRef,\n} from 'vue'\nimport { confirm, toast } from '@webx-ui/core'\nimport { useAdmin } from './admin'\nimport { useDates } from './dates'\nimport { humanize, useEditingLabels } from './editingLabels'\nimport { useTranslate } from './i18n'\nimport {\n changedPaths,\n mergeThreeWay,\n sameValue,\n type MergeChoice,\n type MergeConflict,\n type MergeSegment,\n} from './merge'\n\n/** Who wrote a record last, through which door, and when — the server's `changed`. */\nexport interface EditingChange {\n author: string | null\n author_id: number | null\n /** `panel`, `mcp` (an agent) or `import`. */\n source: string\n at: string | null\n}\n\n/** Somebody else who has the record open in the panel right now. */\nexport interface EditingEditor {\n id: number\n name: string\n since: string\n seen_at: string\n}\n\n/** Where the record stands, as the heartbeat reports it. */\nexport interface EditingState {\n status: string\n has_draft: boolean\n published_at: string | null\n trashed: boolean\n deleted_at: string | null\n}\n\n/**\n * Something that happened to the record besides its content: published, taken off the site, its\n * draft thrown away, an old version put back, moved, put in the bin or taken out of it.\n */\nexport interface EditingEvent {\n id: number\n kind:\n | 'published'\n | 'unpublished'\n | 'discarded'\n | 'restored_version'\n | 'moved'\n | 'trashed'\n | 'restored'\n | 'purged'\n | string\n author: string | null\n author_id: number | null\n source: string\n at: string\n detail: Record<string, unknown> | null\n}\n\n/** One copy of the draft, as `editing/{entity}/{id}/drafts` lists it. */\nexport interface DraftCopy {\n id: number\n kind: 'autosave' | 'overwritten'\n created_at: string | null\n author: string | null\n source: string\n fields: string[]\n /** What this copy changed against the one before it. */\n paths?: MergeSegment[][]\n}\n\n/** The record as the server has it now. */\nexport interface EditingVersion<T> {\n values: T\n revision: string\n changed?: EditingChange | null\n}\n\nexport interface EditingConflict<T> {\n /** The places both sides changed, each with its three values. */\n conflicts: MergeConflict[]\n /** The side each one is settled to; this editor's until somebody picks. */\n choices: Record<string, MergeChoice>\n theirs: EditingVersion<T>\n}\n\n/** Somebody else saved while this editor is open, and it has not been pulled in yet. */\nexport interface EditingIncoming<T> {\n theirs: EditingVersion<T>\n paths: MergeSegment[][]\n}\n\nexport interface EditingOptions<T> {\n /** The record's name on the server's `editing/{entity}/{id}`: `pages`, `services`, `regions`. */\n entity: string\n id: () => string | number | null | undefined\n /** The form: what this editor has now. */\n values: Ref<T>\n /** The revision the form was read at; a merge moves it to the one it merged with. */\n revision: Ref<string>\n /** Read the record again, for the notice's «Pull in». */\n read: () => Promise<EditingVersion<T>>\n /**\n * The server's version was taken in as the new base — after a merge, a pull or «Take theirs».\n * The editor moves its own idea of \"saved\" there: whatever differs from it now is unsaved.\n */\n adopt?: (theirs: EditingVersion<T>) => void\n /**\n * Somebody published, unpublished, moved or restored the record: read what the editor shows\n * around the form again — the status, the trail, the address — without touching the form.\n */\n refresh?: () => void | Promise<void>\n /** Take the record out of the bin. Without it, the notice of the bin offers no way back. */\n restore?: () => Promise<void>\n /** Whether this editor writes at all; a reader is not asked about anybody's changes. */\n canWrite?: () => boolean\n /** Whether a save is on its way: a heartbeat answered meanwhile may be reporting it. */\n busy?: () => boolean\n /** The screen the record is edited on, so its fields are named the way the form names them. */\n screen?: string\n /** What a top-level field is called on screen, for the conflict list. */\n field?: (name: string) => string | undefined\n /** What a type of block is called. */\n block?: (type: string) => string | undefined\n /** Seconds between heartbeats; `false` for none (a test, a screen that is not an editor). */\n heartbeat?: number | false\n}\n\nexport interface Editing<T> {\n /** What the form was opened with, or last saved — the common ancestor of a merge. */\n base: ShallowRef<T>\n conflict: Ref<EditingConflict<T> | null>\n incoming: Ref<EditingIncoming<T> | null>\n editors: Ref<EditingEditor[]>\n /** Where the record stands, by the last heartbeat. */\n state: Ref<EditingState | null>\n /** What others did to the record since this editor opened it, not yet acknowledged. */\n events: Ref<EditingEvent[]>\n /** The record is in the bin. */\n trashed: ComputedRef<boolean>\n /** Who put it there and when, when the server knows. */\n trashedBy: ComputedRef<EditingEvent | null>\n /**\n * The record was deleted for good: who did it and when. Nothing can be saved or restored —\n * the form keeps what it holds, to be copied out with `copyText`.\n */\n gone: Ref<EditingEvent | null>\n /** In the bin or gone: autosave waits, and a save would only fail. */\n stopped: ComputedRef<boolean>\n /**\n * Why Save and Publish are off, for their `title`: «This is in the bin», «This was deleted for\n * good». `undefined` while they are not — a disabled button with no reason looks frozen.\n */\n blocked: ComputedRef<string | undefined>\n /** Whether the form holds something the server does not. */\n unsaved: ComputedRef<boolean>\n /** Whether this editor can take the record out of the bin. */\n canRestore: () => boolean\n /** Call with what the form now matches on the server: on opening, and after each save. */\n opened(values: T): void\n /**\n * A save was refused because the record moved. Merges it with what is in the form: `true`\n * when nothing overlapped — the form holds both edits and the editor saves it again — and\n * `false` when a conflict is waiting for the person.\n */\n refused(theirs: EditingVersion<T>): boolean\n /** Pull the other side's save in before saving. Same answer as `refused`. */\n pull(): Promise<boolean>\n choose(id: string, choice: MergeChoice): void\n /** Settle the conflict by the choices; the editor saves next. */\n resolve(): void\n /** Settle every conflict their way at once. */\n resolveTheirs(): void\n /**\n * Before publishing: whether the draft on the server is still the one this editor holds. When\n * somebody else changed it, the person is told whose changes, where and when, and chooses to\n * publish with them or to review them first — which pulls them into the form. Answers the\n * revision to publish with, and whether the person was already asked; `null` to stop.\n */\n beforePublish(): Promise<{ revision: string; asked: boolean } | null>\n /**\n * A save or a publication failed. `true` when this was about other people — the record is in\n * the bin, or a publication found a draft it had not seen — and a notice says so; the editor\n * then shows nothing of its own.\n */\n failed(error: unknown): Promise<boolean>\n /** Out of the bin. The form keeps what it holds; saving it is the caller's next step. */\n restoreFromBin(): Promise<boolean>\n /** What the form holds, as text a person can paste somewhere else: each field under its name. */\n text(): string\n /** That text onto the clipboard, said with a toast either way. */\n copyText(): Promise<boolean>\n /** The events have been read. */\n dismissEvents(): void\n /** Ask the server now rather than at the next heartbeat. */\n check(): Promise<void>\n /** A place in the record as the person reads it: «Hero › Heading · EN». */\n label(path: MergeSegment[]): string\n /** What a list of changed places says: «Hero › Heading, Title · EN and more». */\n places(paths: MergeSegment[][]): string\n /** What happened, in a sentence: «Owner restored version 21 and published · 16:40». */\n describe(events: EditingEvent[]): string\n /** A value, short, as text. */\n preview(value: unknown): string\n /** Who changed it: «Anna», «Anna, through an agent». */\n who(changed: EditingChange | null | undefined): string\n}\n\n/** Heartbeat answer. */\ninterface Ping {\n revision: string\n changed: EditingChange | null\n editors: EditingEditor[]\n state?: EditingState | null\n place?: Record<string, unknown> | null\n events?: EditingEvent[]\n heartbeat: number\n}\n\n/** Events that change the content: a notice of incoming changes says these instead of fields. */\nconst EXPLAINING = new Set(['restored_version', 'discarded'])\n\n/**\n * What every drafted editor does about other people editing the same record (§ «Two editors»):\n * merges a refused save instead of asking, asks only about a field both sides changed, says who\n * the other side was and through which door, and keeps an ear on the server while open — who\n * else has the record, whether somebody saved in the meantime, and what they did to it besides\n * saving: published it, moved it, put it in the bin.\n *\n * The editor keeps its own save loop; this is the part that is the same in all of them.\n */\nexport function useEditing<T>(options: EditingOptions<T>): Editing<T> {\n const admin = useAdmin()\n const t = useTranslate('webx-admin')\n const dates = useDates()\n const labels = useEditingLabels({\n screen: options.screen,\n field: options.field,\n block: options.block,\n })\n\n const base = shallowRef<T>(clone(options.values.value))\n const conflict = ref<EditingConflict<T> | null>(null) as Ref<EditingConflict<T> | null>\n const incoming = ref<EditingIncoming<T> | null>(null) as Ref<EditingIncoming<T> | null>\n const editors = ref<EditingEditor[]>([])\n const state = ref<EditingState | null>(null)\n const place = ref<Record<string, unknown> | null>(null)\n const events = ref<EditingEvent[]>([])\n /** Every event the server still keeps, this editor's own included: who put it in the bin. */\n const recent = ref<EditingEvent[]>([])\n let changedLast: EditingChange | null = null\n\n const canWrite = (): boolean => options.canWrite?.() ?? true\n\n const trashed = computed(() => state.value?.trashed === true)\n const gone = ref<EditingEvent | null>(null)\n const stopped = computed(() => trashed.value || gone.value !== null)\n const blocked = computed(() =>\n gone.value !== null\n ? t('editing.blocked-purged')\n : trashed.value\n ? t('editing.blocked-trashed')\n : undefined,\n )\n const trashedBy = computed(\n () => [...recent.value].reverse().find((event) => event.kind === 'trashed') ?? null,\n )\n const unsaved = computed(() => !sameValue(options.values.value, base.value))\n\n /*\n * Whether the editor has said what it opened. Until it has, its revision and its form are\n * whatever it started with — empty — and a heartbeat answered in that moment would read the\n * record and offer all of it as somebody else's change, or, worse, take the record's revision\n * for one the editor held. A heartbeat before then only says the editor is here.\n */\n let ready = false\n let mounted = false\n\n function opened(values: T): void {\n base.value = clone(values)\n incoming.value = null\n\n if (!ready) {\n ready = true\n\n // The heartbeat that went out before the record was in the form could not compare\n // anything: ask again now, rather than twenty seconds from now.\n if (mounted && options.heartbeat !== false) void beat()\n }\n }\n\n /** Take the server's version in as the base, with the form holding `merged`. */\n function take(theirs: EditingVersion<T>, merged: T): void {\n options.revision.value = theirs.revision\n base.value = clone(theirs.values)\n options.adopt?.(theirs)\n options.values.value = clone(merged)\n incoming.value = null\n // What explained the change is in the form now — and the notice that explained it said every\n // event it had, so none of them is news any more.\n if (events.value.some((event) => EXPLAINING.has(event.kind))) events.value = []\n }\n\n function settleWith(theirs: EditingVersion<T>): boolean {\n const { value, conflicts } = mergeThreeWay(base.value, options.values.value, theirs.values)\n\n if (conflicts.length === 0) {\n take(theirs, value)\n conflict.value = null\n\n return true\n }\n\n conflict.value = { conflicts, choices: {}, theirs }\n incoming.value = null\n\n return false\n }\n\n function refused(theirs: EditingVersion<T>): boolean {\n const clean = settleWith(theirs)\n\n if (clean) toast.info(t('editing.merged', { who: who(theirs.changed) }))\n\n return clean\n }\n\n async function pull(): Promise<boolean> {\n const theirs = incoming.value?.theirs ?? (await options.read())\n\n return settleWith(theirs)\n }\n\n function choose(id: string, choice: MergeChoice): void {\n if (conflict.value)\n conflict.value = { ...conflict.value, choices: { ...conflict.value.choices, [id]: choice } }\n }\n\n function resolve(): void {\n const open = conflict.value\n\n if (!open) return\n\n const { value } = mergeThreeWay(\n base.value,\n options.values.value,\n open.theirs.values,\n open.choices,\n )\n\n take(open.theirs, value)\n conflict.value = null\n }\n\n function resolveTheirs(): void {\n const open = conflict.value\n\n if (!open) return\n\n open.conflicts.forEach((one) => choose(one.id, 'theirs'))\n resolve()\n }\n\n function who(changed: EditingChange | null | undefined): string {\n const name = changed?.author ?? t('editing.somebody')\n\n if (changed?.source === 'mcp') return t('editing.via-agent', { name })\n if (changed?.source === 'import') return t('editing.via-import', { name })\n\n return name\n }\n\n function when(at: string | null | undefined): string {\n return at ? dates.short(at) : ''\n }\n\n function places(paths: MergeSegment[][]): string {\n const named = [...new Set(paths.map((path) => labels.label(path)))]\n const what = named.slice(0, 3).join(', ') || t('editing.order')\n\n return named.length > 3 ? t('editing.and-more', { what }) : what\n }\n\n /** One thing that happened, as a verb phrase: «restored version 21», «moved it to /contact/». */\n function deed(event: EditingEvent): string {\n const detail = event.detail ?? {}\n\n switch (event.kind) {\n case 'restored_version':\n return typeof detail.number === 'number'\n ? t('editing.event-restored-version', { number: detail.number })\n : t('editing.event-restored-draft')\n case 'moved': {\n const path = mainPath()\n\n return path === null ? t('editing.event-moved') : t('editing.event-moved-to', { path })\n }\n default:\n return t(`editing.event-${event.kind.replace(/_/g, '-')}`)\n }\n }\n\n function describe(list: EditingEvent[]): string {\n const lines: string[] = []\n let i = 0\n\n // One sentence per person in a row: «Owner restored version 21 and published», not two.\n while (i < list.length) {\n const first = list[i]!\n const deeds: string[] = []\n let last = first\n\n while (\n i < list.length &&\n list[i]!.author_id === first.author_id &&\n list[i]!.source === first.source\n ) {\n last = list[i]!\n const phrase = deed(last)\n\n if (deeds.at(-1) !== phrase) deeds.push(phrase)\n i++\n }\n\n const done =\n deeds.length === 1\n ? deeds[0]!\n : `${deeds.slice(0, -1).join(', ')} ${t('editing.and')} ${deeds.at(-1)}`\n\n lines.push(timeless(t('editing.event', { who: who(last), what: done, when: when(last.at) })))\n }\n\n return lines.join(' · ')\n }\n\n /** The address the record answers at now, in the panel's language or the first it has. */\n function mainPath(): string | null {\n const paths = place.value?.paths\n\n if (!paths || typeof paths !== 'object') return null\n\n const map = paths as Record<string, unknown>\n const own = map[admin.i18n?.state?.locale ?? ''] ?? Object.values(map)[0]\n\n return typeof own === 'string' ? own : null\n }\n\n async function beforePublish(): Promise<{ revision: string; asked: boolean } | null> {\n const held = options.revision.value\n let theirs: EditingVersion<T>\n\n try {\n theirs = await options.read()\n } catch {\n // Not readable just now: publish what the editor holds, and the server checks the revision.\n return { revision: held, asked: false }\n }\n\n if (theirs.revision === held) return { revision: held, asked: false }\n\n if (sameValue(theirs.values, options.values.value)) {\n take(theirs, options.values.value)\n\n return { revision: theirs.revision, asked: false }\n }\n\n theirs.changed ??= changedLast\n\n const explained = events.value.filter((event) => EXPLAINING.has(event.kind))\n const what =\n explained.length > 0\n ? describe(explained)\n : timeless(\n t('editing.incoming', {\n who: who(theirs.changed),\n what: places(changedPaths(base.value, theirs.values)),\n when: when(theirs.changed?.at),\n }),\n )\n\n const agreed = await confirm({\n title: t('editing.publish-unseen-title'),\n message: `${what}. ${t('editing.publish-unseen-text')}`,\n confirmText: t('editing.publish-with'),\n cancelText: t('editing.review-first'),\n })\n\n if (agreed) return { revision: theirs.revision, asked: true }\n\n // Review first: their changes into the form, beside whatever is there, to be looked at and\n // published with the next press of the button.\n if (settleWith(theirs)) toast.info(t('editing.merged', { who: who(theirs.changed) }))\n\n return null\n }\n\n async function failed(error: unknown): Promise<boolean> {\n const failure = error as {\n status?: number\n body?: { message?: string; revision?: unknown; gone?: unknown }\n }\n\n if (failure?.status === 410 && heardGone(failure.body?.gone)) return true\n\n if (failure?.status === 404 || failure?.status === 410) {\n await beat()\n\n return stopped.value\n }\n\n // A publication that carried a revision the draft is no longer at: somebody wrote between\n // the question and the click. The notice of their change follows from the heartbeat.\n if (failure?.status === 409 && typeof failure.body?.revision === 'string') {\n toast.warning(failure.body.message || t('editing.publish-moved'))\n await beat()\n\n return true\n }\n\n return false\n }\n\n const canRestore = (): boolean => options.restore !== undefined && canWrite()\n\n async function restoreFromBin(): Promise<boolean> {\n if (!options.restore) return false\n\n try {\n await options.restore()\n } catch (error) {\n toast.danger(\n (error as { body?: { message?: string } })?.body?.message || t('editing.restore-failed'),\n )\n\n return false\n }\n\n // Who put it in the bin was the notice's to say; out of it, that is no longer news.\n events.value = events.value.filter((event) => event.kind !== 'trashed')\n await beat()\n await options.refresh?.()\n toast.success(t('editing.out-of-bin'))\n\n return true\n }\n\n function dismissEvents(): void {\n events.value = []\n }\n\n /**\n * A 410 with the purge in it. Said once and kept: the record will not come back, and every\n * heartbeat after this one would only hear the same.\n */\n function heardGone(event: unknown): boolean {\n if (gone.value !== null) return true\n if (event === undefined) return false\n\n const purge = (event ?? {}) as Partial<EditingEvent>\n\n gone.value = {\n id: typeof purge.id === 'number' ? purge.id : 0,\n kind: 'purged',\n author: typeof purge.author === 'string' ? purge.author : null,\n author_id: typeof purge.author_id === 'number' ? purge.author_id : null,\n source: typeof purge.source === 'string' ? purge.source : 'panel',\n at: typeof purge.at === 'string' ? purge.at : '',\n detail: null,\n }\n incoming.value = null\n conflict.value = null\n editors.value = []\n\n return true\n }\n\n function text(): string {\n return texts(options.values.value)\n .map(([path, words]) => `${labels.label(path)}\\n${words}`)\n .join('\\n\\n')\n }\n\n async function copyText(): Promise<boolean> {\n try {\n await navigator.clipboard.writeText(text())\n } catch {\n toast.danger(t('editing.copy-failed'))\n\n return false\n }\n\n toast.success(t('editing.copied'))\n\n return true\n }\n\n /* The heartbeat. */\n let timer: ReturnType<typeof setInterval> | undefined\n let reading = false\n let stopWatching: (() => void) | undefined\n /** The newest event this editor has heard of; `null` until the first answer sets the line. */\n let seen: number | null = null\n let stateKey: string | null = null\n\n const address = (): string | null => {\n const id = options.id()\n\n return id === null || id === undefined || id === ''\n ? null\n : `${admin.apiPath}/editing/${options.entity}/${encodeURIComponent(String(id))}`\n }\n\n const me = (): number | null => {\n const id = admin.state?.user?.id\n\n return id === undefined || id === null ? null : Number(id)\n }\n\n /** What the heartbeat says besides the revision: the state, the place, what happened. */\n function absorb(ping: Ping): void {\n const list = Array.isArray(ping.events) ? ping.events : []\n const newest = list.reduce((top, event) => Math.max(top, event.id), seen ?? 0)\n let fresh: EditingEvent[] = []\n\n recent.value = list\n\n if (seen === null) {\n // Whatever happened before this editor opened is not news to it.\n seen = newest\n } else {\n const line = seen\n\n // This editor's own doing is not news either — but the same person through an agent is.\n fresh = list.filter(\n (event) =>\n event.id > line &&\n !(event.author_id === me() && event.source === 'panel' && me() !== null),\n )\n seen = newest\n }\n\n if (fresh.length > 0) events.value = [...events.value, ...fresh]\n\n state.value = ping.state ?? null\n place.value = ping.place ?? null\n\n const key = JSON.stringify([ping.state ?? null, ping.place ?? null])\n const moved = stateKey !== null && key !== stateKey\n\n stateKey = key\n\n // The badge, the trail and the address are the editor's: it reads them again.\n if ((moved || fresh.length > 0) && !trashed.value) void options.refresh?.()\n }\n\n async function beat(): Promise<void> {\n const url = address()\n\n if (url === null) return\n\n const held = options.revision.value\n let ping: Ping\n\n // Gone is gone: the next record the screen opens starts over (see the watch on the id).\n if (gone.value !== null) return\n\n try {\n ping = (await admin.http.post<{ data: Ping }>(url)).data\n } catch (error) {\n const failure = error as { status?: number; body?: { gone?: unknown } }\n\n // Deleted for good is the one silence that is not a courtesy: what is typed here has\n // nowhere to go, and the person has to hear it before they type more.\n if (failure?.status === 410) {\n heardGone(failure.body?.gone ?? null)\n\n return\n }\n\n // A record nobody has saved yet, a panel without the endpoint, a network that blinked:\n // the heartbeat is a courtesy, and its absence is not worth a word on screen.\n return\n }\n\n if (typeof ping?.revision !== 'string') return\n\n editors.value = Array.isArray(ping.editors) ? ping.editors : []\n changedLast = ping.changed ?? null\n absorb(ping)\n\n // Nothing to compare with until the editor has its record: see `ready`.\n if (!ready) return\n\n // Our own save may have landed while the heartbeat was out, and a stale answer would then\n // report it as somebody else's.\n if (held !== options.revision.value || options.busy?.()) return\n if (ping.revision === held || !canWrite() || conflict.value || reading) return\n if (incoming.value?.theirs.revision === ping.revision) return\n\n reading = true\n\n try {\n const theirs = await options.read()\n\n if (held !== options.revision.value || options.busy?.()) return\n\n theirs.changed ??= ping.changed\n\n if (sameValue(theirs.values, options.values.value)) {\n // The server already holds what the form holds — nothing to pull, only a revision.\n take(theirs, options.values.value)\n\n return\n }\n\n incoming.value = { theirs, paths: changedPaths(base.value, theirs.values) }\n } catch {\n // As above: the notice is offered when it can be, never insisted on.\n } finally {\n reading = false\n }\n }\n\n function leave(): void {\n const url = address()\n\n if (url === null) return\n\n try {\n void admin.http.delete(url).catch(() => undefined)\n } catch {\n // A hand-made client without `delete`: the entry runs out on its own within a minute.\n }\n }\n\n onMounted(() => {\n mounted = true\n\n if (options.heartbeat === false) return\n\n const seconds = options.heartbeat ?? 20\n\n timer = setInterval(() => void beat(), seconds * 1000)\n // The first one as soon as the record is known: an agent asking a second after the page\n // opened should hear about it, and so should the next record the same screen opens.\n stopWatching = watch(\n options.id,\n () => {\n // Another record: what was heard about the last one is not about this one.\n seen = null\n stateKey = null\n events.value = []\n gone.value = null\n void beat()\n },\n { immediate: true },\n )\n })\n\n onBeforeUnmount(() => {\n clearInterval(timer)\n stopWatching?.()\n if (options.heartbeat !== false) leave()\n })\n\n return {\n base,\n conflict,\n incoming,\n editors,\n state,\n events,\n trashed,\n trashedBy,\n gone,\n stopped,\n blocked,\n unsaved,\n canRestore,\n opened,\n refused,\n pull,\n choose,\n resolve,\n resolveTheirs,\n beforePublish,\n failed,\n restoreFromBin,\n text,\n copyText,\n dismissEvents,\n check: beat,\n label: labels.label,\n places,\n describe,\n preview,\n who,\n }\n}\n\n/** A value, short, as text: the words of a rich text without its tags, a block by its type. */\nexport function preview(value: unknown): string {\n if (value === undefined || value === null || value === '') return ''\n if (typeof value === 'string')\n return clip(\n value\n .replace(/<[^>]*>/g, '')\n .replace(/\\s+/g, ' ')\n .trim(),\n )\n if (typeof value === 'number' || typeof value === 'boolean') return String(value)\n\n if (\n typeof value === 'object' &&\n !Array.isArray(value) &&\n typeof (value as { type?: unknown }).type === 'string'\n ) {\n return humanize((value as { type: string }).type)\n }\n\n return clip(JSON.stringify(value))\n}\n\n/**\n * Every piece of text in a value with where it sits — what a person typed, to be carried out of\n * a form whose record is gone. Rich text comes out as its words, a paragraph to a line; a block's\n * own `key` and `type` are its shape, not anything anybody wrote.\n */\nexport function texts(value: unknown, path: MergeSegment[] = []): [MergeSegment[], string][] {\n if (typeof value === 'string') {\n const words = plain(value)\n\n return words === '' ? [] : [[path, words]]\n }\n\n if (Array.isArray(value)) {\n return value.flatMap((item) => {\n const node = item as { key?: unknown; type?: unknown } | null\n\n return node !== null && typeof node === 'object' && typeof node.key === 'string'\n ? texts(item, [\n ...path,\n { block: node.key, type: typeof node.type === 'string' ? node.type : '' },\n ])\n : texts(item, path)\n })\n }\n\n if (value === null || typeof value !== 'object') return []\n\n const inBlock = path.length > 0 && 'block' in path[path.length - 1]!\n\n return Object.entries(value).flatMap(([key, item]) =>\n inBlock && (key === 'key' || key === 'type') ? [] : texts(item, [...path, { field: key }]),\n )\n}\n\nfunction plain(html: string): string {\n return html\n .replace(/<br\\s*\\/?>/gi, '\\n')\n .replace(/<\\/(p|h[1-6]|li|blockquote|div)>/gi, '\\n')\n .replace(/<[^>]*>/g, '')\n .replace(/&nbsp;/g, ' ')\n .replace(/&lt;/g, '<')\n .replace(/&gt;/g, '>')\n .replace(/&quot;/g, '\"')\n .replace(/&#39;/g, \"'\")\n .replace(/&amp;/g, '&')\n .replace(/[ \\t]+\\n/g, '\\n')\n .replace(/\\n{3,}/g, '\\n\\n')\n .trim()\n}\n\n/** A sentence whose time was not known ends where the time would have been, not on a «·». */\nexport function timeless(text: string): string {\n return text.replace(/\\s*·\\s*$/, '')\n}\n\nfunction clip(text: string): string {\n return text.length > 140 ? `${text.slice(0, 139)}…` : text\n}\n\nfunction clone<T>(value: T): T {\n return value === undefined ? value : (JSON.parse(JSON.stringify(value)) as T)\n}\n","import type { AdminContext } from './admin'\n\n/** Who wrote a note. `name` is null once that account has been deleted. */\nexport interface NoteAuthor {\n id: number\n name: string | null\n}\n\n/** One line somebody left on a record for whoever picks it up next. */\nexport interface EntityNote {\n id: number\n body: string\n author: NoteAuthor | null\n /**\n * Whether the reader is the author — answered by the server, because who may edit a note is\n * a rule, and a rule the browser works out for itself is the copy that drifts.\n */\n is_mine: boolean\n created_at: string | null\n updated_at: string | null\n}\n\nexport interface NotesApi {\n list(): Promise<EntityNote[]>\n add(body: string): Promise<EntityNote>\n save(id: number, body: string): Promise<EntityNote>\n remove(id: number): Promise<void>\n}\n\n/**\n * The notes of one record.\n *\n * `type` is the alias the server registered the model under — never a class name — and the\n * address is the panel's own, not the section's: the same feed hangs off a submission, an\n * order and a client, and a second copy of it in each module is a second place for the rules\n * about who may write one to be got wrong.\n */\nexport function createNotesApi(admin: AdminContext, type: string, id: number | string): NotesApi {\n const base = `${admin.apiPath}/entities/${type}/${id}/notes`\n const one = (noteId: number) => `${admin.apiPath}/notes/${noteId}`\n const data = <T>(body: { data: T }): T => body.data\n\n return {\n list: () => admin.http.get<{ data: EntityNote[] }>(base).then(data),\n add: (body) => admin.http.post<{ data: EntityNote }>(base, { body }).then(data),\n save: (id, body) => admin.http.put<{ data: EntityNote }>(one(id), { body }).then(data),\n remove: (id) => admin.http.delete(one(id)).then(() => undefined),\n }\n}\n","import { inject, shallowRef, type ShallowRef } from 'vue'\nimport { adminKey, type AdminContext } from './admin'\n\n/** One shortcode of the site, as `GET <api>/shortcodes` answers with it. */\nexport interface Shortcode {\n name: string\n description: string | null\n /** What the site prints for it — trusted markup. */\n html: string\n /** The same as text: what a text field shows as its value. */\n plain: string\n /** Declared by a package in code, or added by hand in the settings. */\n origin: 'code' | 'settings'\n}\n\n/**\n * A shortcode as a text field offers it — the shape of `TokenOption` in `@webx-ui/core`, spelled\n * out here rather than imported so a panel built against an older core still compiles.\n */\nexport interface ShortcodeToken {\n name: string\n value?: string\n description?: string\n}\n\nexport function shortcodeToken(shortcode: Shortcode): ShortcodeToken {\n return {\n name: shortcode.name,\n value: shortcode.plain,\n description: shortcode.description ?? undefined,\n }\n}\n\n/*\n * One request per panel, not per field: a page of blocks has dozens of text fields, and the\n * list changes when somebody edits the settings, not while a page is being written. Kept by the\n * context rather than in a module variable, so two panels in one tab — or two tests — do not\n * share one answer.\n */\nconst loaded = new WeakMap<AdminContext, Promise<ShortcodeToken[]>>()\n\nexport function loadShortcodes(admin: AdminContext): Promise<ShortcodeToken[]> {\n let pending = loaded.get(admin)\n\n if (!pending) {\n pending = admin.http\n .get<{ data: Shortcode[] }>(`${admin.apiPath}/shortcodes`)\n .then((body) => (Array.isArray(body.data) ? body.data.map(shortcodeToken) : []))\n .catch(() => {\n // A field works without suggestions; the next field opened asks again.\n loaded.delete(admin)\n return []\n })\n loaded.set(admin, pending)\n }\n\n return pending\n}\n\n/** Drops what was loaded, so the next field asks again — after the settings changed one. */\nexport function forgetShortcodes(admin: AdminContext): void {\n loaded.delete(admin)\n}\n\n/**\n * The site's shortcodes for a text field's `tokens`: empty at first and filled once the panel\n * has answered. Outside a panel — a demo, a test — it stays empty.\n */\nexport function useShortcodes(): ShallowRef<ShortcodeToken[]> {\n const admin = inject(adminKey, null)\n const tokens = shallowRef<ShortcodeToken[]>([])\n\n if (admin) void loadShortcodes(admin).then((list) => (tokens.value = list))\n\n return tokens\n}\n","import { computed, getCurrentScope, onScopeDispose, readonly, ref, type Ref } from 'vue'\nimport { useAdmin, type AdminContext } from './admin'\nimport { toError, type Http, type SendOptions } from './http'\n\n/**\n * Large files a piece at a time, through the panel's own four requests (§4 of the video spec):\n *\n * POST uploads { name, size, type, fingerprint, purpose } → { id, offset, size, chunk_size }\n * HEAD uploads/{id} → Upload-Offset\n * PATCH uploads/{id} a piece, Upload-Offset: n → 204, Upload-Offset: n + length\n * DELETE uploads/{id}\n *\n * The server holds the offset and the client follows it: a 409 names where the file really\n * ends, a HEAD asks after a dropped connection, and the same file chosen again after a reload\n * finds its session by the fingerprint. What the server cannot know is the web server's own\n * limit, so a 413 halves the piece — down to 256 KB — and the smaller piece stays for the rest\n * of the file.\n */\n\nexport type ChunkedUploadState = 'idle' | 'uploading' | 'paused' | 'offline' | 'done' | 'failed'\n\n/** An upload that did not finish, as remembered in this browser for the next visit. */\nexport interface UnfinishedUpload {\n fingerprint: string\n purpose: string\n name: string\n size: number\n offset: number\n /** Milliseconds since the epoch, of the last piece that landed. */\n updatedAt: number\n}\n\nexport interface ChunkedUploadOptions {\n /** The panel to talk to; `useAdmin()` when left out. */\n admin?: Pick<AdminContext, 'http' | 'apiPath'>\n /** Where unfinished uploads are remembered; `localStorage` when left out, `null` for nowhere. */\n storage?: Storage | null\n /** How many times a piece lost to the network is sent again before the upload fails. */\n retries?: number\n /** The wait before the first of those, doubled for each after it. */\n retryDelay?: number\n}\n\nexport interface ChunkedUpload {\n readonly state: Readonly<Ref<ChunkedUploadState>>\n /** 0 to 1. */\n readonly progress: Readonly<Ref<number>>\n /** Bytes the server has. */\n readonly uploaded: Readonly<Ref<number>>\n readonly total: Readonly<Ref<number>>\n /** Bytes a second, over the last few seconds; 0 until there is something to measure. */\n readonly speed: Readonly<Ref<number>>\n /** Seconds left at that speed, or null while there is no speed to go by. */\n readonly remaining: Readonly<Ref<number | null>>\n /** The session's id, which is what the module the file was for claims it by. */\n readonly id: Readonly<Ref<string | null>>\n readonly error: Readonly<Ref<Error | null>>\n /**\n * Send a file. Resolves with the session's id once the whole of it is on the server, with\n * null when it is cancelled; rejects when it fails. A pause does neither — the same promise\n * resolves once the upload is resumed and finished.\n */\n start(file: File, purpose: string): Promise<string | null>\n pause(): void\n /** Carry on after a pause, going offline or a failure; the promise of the upload. */\n resume(): Promise<string | null>\n /** Stop and throw away what the server has. */\n cancel(): Promise<void>\n}\n\n/** The smallest piece the client falls back to on a 413 (the server never advises less). */\nexport const MIN_CHUNK = 256 * 1024\n\nconst STORAGE_KEY = 'webx:uploads'\nconst SPEED_WINDOW = 8000\n\n/**\n * What identifies a file without reading it: the same name, size and date of change is taken\n * for the same file. Cheap on purpose — hashing gigabytes to find out where to resume would take\n * longer than a good part of the upload.\n */\nexport function uploadFingerprint(file: Pick<File, 'name' | 'size' | 'lastModified'>): string {\n return `${file.name}|${file.size}|${file.lastModified}`\n}\n\n/**\n * The uploads this browser started and did not finish — what a panel offers to continue after a\n * reload (\"choose the same file\"). Empty wherever storage is refused.\n */\nexport function unfinishedUploads(\n purpose?: string,\n storage: Storage | null = defaultStorage(),\n): UnfinishedUpload[] {\n return readAll(storage).filter((entry) => purpose === undefined || entry.purpose === purpose)\n}\n\n/** Stop offering to continue an upload — the person chose not to. */\nexport function forgetUnfinishedUpload(\n fingerprint: string,\n purpose: string,\n storage: Storage | null = defaultStorage(),\n): void {\n writeAll(\n storage,\n readAll(storage).filter(\n (entry) => entry.fingerprint !== fingerprint || entry.purpose !== purpose,\n ),\n )\n}\n\nexport function useChunkedUpload(options: ChunkedUploadOptions = {}): ChunkedUpload {\n const admin = options.admin ?? useAdmin()\n const storage = options.storage === undefined ? defaultStorage() : options.storage\n const retries = options.retries ?? 3\n const retryDelay = options.retryDelay ?? 1000\n const base = `${admin.apiPath}/uploads`\n\n const state = ref<ChunkedUploadState>('idle')\n const uploaded = ref(0)\n const total = ref(0)\n const speed = ref(0)\n const id = ref<string | null>(null)\n const error = ref<Error | null>(null)\n\n const progress = computed(() => (total.value > 0 ? Math.min(1, uploaded.value / total.value) : 0))\n const remaining = computed(() =>\n speed.value > 0 ? Math.ceil((total.value - uploaded.value) / speed.value) : null,\n )\n\n let file: File | null = null\n let purpose = ''\n let chunk = MIN_CHUNK\n // Every pause, cancel and trip offline moves this on; a loop that finds it moved stops.\n let generation = 0\n let controller: AbortController | null = null\n let samples: Array<{ at: number; bytes: number }> = []\n let settle: { resolve: (id: string | null) => void; reject: (error: Error) => void } | null = null\n let pending: Promise<string | null> | null = null\n\n function send(method: string, path: string, init: SendOptions = {}): Promise<Response> {\n return sendWith(admin.http, method, path, init)\n }\n\n function promise(): Promise<string | null> {\n if (pending === null) {\n pending = new Promise<string | null>((resolve, reject) => {\n settle = { resolve, reject }\n })\n // Handled here as well, so that a caller who only watches `state` does not get an\n // unhandled rejection; one who awaits it still sees the failure.\n pending.catch(() => undefined)\n }\n\n return pending\n }\n\n function finish(result: string | null, failure?: Error): void {\n const done = settle\n settle = null\n pending = null\n\n if (failure !== undefined) {\n done?.reject(failure)\n } else {\n done?.resolve(result)\n }\n }\n\n function fail(failure: unknown): void {\n error.value = failure instanceof Error ? failure : new Error(String(failure))\n state.value = 'failed'\n speed.value = 0\n finish(null, error.value)\n }\n\n function setOffset(offset: number): void {\n uploaded.value = Math.min(offset, total.value)\n\n const now = Date.now()\n samples.push({ at: now, bytes: uploaded.value })\n samples = samples.filter((sample) => now - sample.at <= SPEED_WINDOW)\n\n const first = samples[0]\n const seconds = first === undefined ? 0 : (now - first.at) / 1000\n speed.value = first !== undefined && seconds > 0 ? (uploaded.value - first.bytes) / seconds : 0\n\n if (file !== null) {\n remember(file, purpose, uploaded.value)\n }\n }\n\n function remember(target: File, forPurpose: string, offset: number): void {\n const fingerprint = uploadFingerprint(target)\n const others = readAll(storage).filter(\n (entry) => entry.fingerprint !== fingerprint || entry.purpose !== forPurpose,\n )\n\n writeAll(storage, [\n ...others,\n {\n fingerprint,\n purpose: forPurpose,\n name: target.name,\n size: target.size,\n offset,\n updatedAt: Date.now(),\n },\n ])\n }\n\n function forget(): void {\n if (file !== null) {\n forgetUnfinishedUpload(uploadFingerprint(file), purpose, storage)\n }\n }\n\n function stop(): void {\n generation += 1\n controller?.abort()\n controller = null\n speed.value = 0\n samples = []\n }\n\n async function start(target: File, forPurpose: string): Promise<string | null> {\n if (state.value === 'uploading' || state.value === 'paused' || state.value === 'offline') {\n throw new Error('This upload is still going; cancel it before starting another.')\n }\n\n stop()\n file = target\n purpose = forPurpose\n id.value = null\n error.value = null\n uploaded.value = 0\n total.value = target.size\n state.value = 'uploading'\n\n const result = promise()\n const token = generation\n\n try {\n const body = await admin.http.post<{\n data: { id: string; offset: number; size: number; chunk_size: number }\n }>(base, {\n name: target.name,\n size: target.size,\n type: target.type,\n fingerprint: uploadFingerprint(target),\n purpose: forPurpose,\n })\n\n if (token !== generation) {\n return result\n }\n\n id.value = body.data.id\n chunk = Math.max(MIN_CHUNK, body.data.chunk_size)\n setOffset(body.data.offset)\n void run()\n } catch (failure) {\n if (token === generation) {\n fail(failure)\n }\n }\n\n return result\n }\n\n /** Where the server says the file ends — after a pause, a trip offline or a lost piece. */\n async function sync(): Promise<boolean> {\n const response = await send('HEAD', `${base}/${id.value}`)\n\n if (!response.ok) {\n fail(await toError(response))\n\n return false\n }\n\n const offset = headerOffset(response)\n\n if (offset !== null) {\n setOffset(offset)\n }\n\n return true\n }\n\n async function run(): Promise<void> {\n const token = generation\n let failures = 0\n\n state.value = 'uploading'\n\n while (token === generation && file !== null && id.value !== null) {\n if (uploaded.value >= total.value) {\n state.value = 'done'\n speed.value = 0\n forget()\n finish(id.value)\n\n return\n }\n\n const from = uploaded.value\n const to = Math.min(total.value, from + chunk)\n controller = new AbortController()\n\n let response: Response\n\n try {\n response = await send('PATCH', `${base}/${id.value}`, {\n body: file.slice(from, to),\n headers: {\n 'Upload-Offset': String(from),\n 'Content-Type': 'application/offset+octet-stream',\n },\n signal: controller.signal,\n })\n } catch (failure) {\n // A pause or a cancel aborted it; whoever did that has already said what happens next.\n if (token !== generation) {\n return\n }\n\n if (isOffline()) {\n goOffline()\n\n return\n }\n\n failures += 1\n\n if (failures > retries) {\n fail(failure)\n\n return\n }\n\n await wait(retryDelay * 2 ** (failures - 1))\n\n if (token !== generation) {\n return\n }\n\n // Some of the piece may have landed before the connection went; the server knows.\n try {\n if (!(await sync())) {\n return\n }\n } catch {\n // Still unreachable; the next attempt counts as another failure.\n }\n\n continue\n }\n\n if (token !== generation) {\n return\n }\n\n if (response.ok) {\n failures = 0\n setOffset(headerOffset(response) ?? to)\n\n continue\n }\n\n if (response.status === 409) {\n const offset = headerOffset(response)\n\n if (offset !== null) {\n setOffset(offset)\n\n continue\n }\n }\n\n // The web server's limit, which the server behind it could not know about.\n if (response.status === 413 && chunk > MIN_CHUNK) {\n chunk = Math.max(MIN_CHUNK, Math.floor(chunk / 2))\n\n continue\n }\n\n fail(await toError(response))\n\n return\n }\n }\n\n function pause(): void {\n if (state.value !== 'uploading') {\n return\n }\n\n stop()\n state.value = 'paused'\n }\n\n function goOffline(): void {\n stop()\n state.value = 'offline'\n }\n\n async function resume(): Promise<string | null> {\n if (state.value === 'uploading' || state.value === 'done' || state.value === 'idle') {\n return pending ?? Promise.resolve(state.value === 'done' ? id.value : null)\n }\n\n if (file === null) {\n return Promise.resolve(null)\n }\n\n // A failure before the session existed leaves nothing to resume; start it again instead.\n if (id.value === null) {\n state.value = 'idle'\n\n return start(file, purpose)\n }\n\n stop()\n error.value = null\n state.value = 'uploading'\n\n const result = promise()\n const token = generation\n\n try {\n if ((await sync()) && token === generation) {\n void run()\n }\n } catch (failure) {\n if (token === generation) {\n if (isOffline()) {\n goOffline()\n } else {\n fail(failure)\n }\n }\n }\n\n return result\n }\n\n async function cancel(): Promise<void> {\n const session = id.value\n\n stop()\n forget()\n state.value = 'idle'\n uploaded.value = 0\n id.value = null\n finish(null)\n\n if (session !== null) {\n try {\n await send('DELETE', `${base}/${session}`)\n } catch {\n // Gone or unreachable: the server sweeps what nobody finishes.\n }\n }\n }\n\n function onOffline(): void {\n if (state.value === 'uploading') {\n goOffline()\n }\n }\n\n function onOnline(): void {\n if (state.value === 'offline') {\n void resume()\n }\n }\n\n if (typeof window !== 'undefined') {\n window.addEventListener('offline', onOffline)\n window.addEventListener('online', onOnline)\n }\n\n if (getCurrentScope() !== undefined) {\n onScopeDispose(() => {\n if (typeof window !== 'undefined') {\n window.removeEventListener('offline', onOffline)\n window.removeEventListener('online', onOnline)\n }\n\n // Nothing is left to show the progress; the fingerprint stays, so it can be continued.\n pause()\n })\n }\n\n return {\n state: readonly(state),\n progress,\n uploaded: readonly(uploaded),\n total: readonly(total),\n speed: readonly(speed),\n remaining,\n id: readonly(id),\n error: readonly(error) as Readonly<Ref<Error | null>>,\n start,\n pause,\n resume,\n cancel,\n }\n}\n\n/**\n * The client's own `send` when it has one, so the CSRF cookie and the standing headers are the\n * same as everywhere else; a bare fetch for a hand-made client that does not.\n */\nasync function sendWith(\n http: Http,\n method: string,\n path: string,\n init: SendOptions,\n): Promise<Response> {\n if (http.send !== undefined) {\n return http.send(method, path, init)\n }\n\n return globalThis.fetch(path, {\n method,\n credentials: 'same-origin',\n headers: { Accept: 'application/json', ...init.headers },\n body: init.body ?? undefined,\n signal: init.signal,\n })\n}\n\nfunction headerOffset(response: Response): number | null {\n const value = response.headers.get('Upload-Offset')\n\n if (value === null || !/^\\d+$/.test(value)) {\n return null\n }\n\n return Number.parseInt(value, 10)\n}\n\nfunction isOffline(): boolean {\n return typeof navigator !== 'undefined' && navigator.onLine === false\n}\n\nfunction wait(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms))\n}\n\nfunction defaultStorage(): Storage | null {\n try {\n return typeof localStorage === 'undefined' ? null : localStorage\n } catch {\n // A browser that blocks site data throws on the very access.\n return null\n }\n}\n\nfunction readAll(storage: Storage | null): UnfinishedUpload[] {\n if (storage === null) {\n return []\n }\n\n try {\n const parsed: unknown = JSON.parse(storage.getItem(STORAGE_KEY) ?? '[]')\n\n return Array.isArray(parsed) ? (parsed as UnfinishedUpload[]) : []\n } catch {\n return []\n }\n}\n\nfunction writeAll(storage: Storage | null, entries: UnfinishedUpload[]): void {\n if (storage === null) {\n return\n }\n\n try {\n if (entries.length === 0) {\n storage.removeItem(STORAGE_KEY)\n } else {\n storage.setItem(STORAGE_KEY, JSON.stringify(entries))\n }\n } catch {\n // Full or refused: continuing after a reload is a courtesy, not something to fail over.\n }\n}\n","import { inject, provide, type InjectionKey, type Ref } from 'vue'\nimport type { ScreenModel } from '@webx-ui/schema'\nimport type { CategoryRow } from './types'\n\n/**\n * What the category editor knows and the nodes of its screen do not — the same seam the article\n * editor has. `wx-category-slug` (`wx-slug`) prints the whole address, and the prefix of the module is\n * configuration the editor was handed with the record, not something a description can carry.\n */\nexport interface CategoryEditorContext {\n /** The category as the server last answered it, or `null` while the first request is out. */\n category: Ref<CategoryRow | null>\n /** The values of the screen as they are right now, edits included. */\n values: Ref<ScreenModel>\n /** Where the module's addresses start; `null` for categories without an address. */\n prefix: Ref<string | null>\n /** \"The address is changing\" — in the module's words. */\n moving: () => string\n}\n\nexport const categoryEditorKey: InjectionKey<CategoryEditorContext> = Symbol('wx-category-editor')\n\nexport function provideCategoryEditor(editor: CategoryEditorContext): void {\n provide(categoryEditorKey, editor)\n}\n\n/** The editor above this node, or `null` outside one — a demo, a test, a stray screen. */\nexport function useCategoryEditor(): CategoryEditorContext | null {\n return inject(categoryEditorKey, null)\n}\n","import { computed, type ComputedRef } from 'vue'\nimport { useAdmin } from '../admin'\nimport { useTranslate } from '../i18n'\nimport { reorderItems } from './api'\n\n/**\n * What the list of a module's records looks like right now, as far as its order is concerned.\n */\nexport interface ItemOrderState {\n /** What is typed in the search box. */\n q?: string | null\n /** The one category the list is narrowed to, if it is. */\n category?: number | null\n /** Whether any filter other than the category is on — status, author, the bin. */\n filtered?: boolean\n}\n\n/**\n * Which order a drag would write (decision 5 of the services spec).\n *\n * - `all` — the whole list is on screen, and dragging moves the common order;\n * - `category` — one category and nothing else, and dragging moves that category's own order;\n * - `locked` — a search or another filter is on, and the rows on screen are a selection: the\n * gaps between them are records nobody can see, so a drag here would move them blind.\n */\nexport type ItemOrderMode = 'all' | 'category' | 'locked'\n\nexport function itemOrderMode(state: ItemOrderState): ItemOrderMode {\n if ((state.q ?? '').trim() !== '' || state.filtered) return 'locked'\n\n return state.category == null ? 'all' : 'category'\n}\n\nexport interface ItemOrder {\n mode: ComputedRef<ItemOrderMode>\n /** Whether the handles are drawn at all. */\n sortable: ComputedRef<boolean>\n /** One line under the list: what a drag does here, or why there is nothing to drag. */\n hint: ComputedRef<string>\n /** Write the order the rows are in on screen — the whole list, or the category's. */\n move(ids: number[]): Promise<void>\n}\n\n/**\n * The order of a module's records, dragged in the list the editor is looking at.\n *\n * A composable rather than a component, because the list it belongs to is the module's own —\n * a table, a sortable list, cards — and what is shared is the rule about which order a drag\n * writes and the sentence that says so. `path` is the list's API (`services/items`), served by\n * `CategoryRoutes::items()`. A module whose records are ordered by something else (the blog, by\n * date) does not call it, and its list has no handles.\n */\nexport function useItemOrder(path: string, state: () => ItemOrderState): ItemOrder {\n const admin = useAdmin()\n const t = useTranslate('webx-admin')\n\n const mode = computed(() => itemOrderMode(state()))\n\n return {\n mode,\n sortable: computed(() => mode.value !== 'locked'),\n hint: computed(() => t(`categories.order-${mode.value}`)),\n move: (ids) =>\n reorderItems(admin, path, ids, mode.value === 'category' ? (state().category ?? null) : null),\n }\n}\n","<script setup lang=\"ts\">\nimport { WxIcon, WxTooltip } from '@webx-ui/core'\nimport { useTranslate } from './i18n'\n\n/**\n * A quiet mark beside an address a list shows in the site's main language, because the record\n * has none in the language the list is read in. The address stays in the row — the page opens\n * there — and the mark says, on hover, why it is not this language's.\n *\n * Nothing at all when `locale` is empty: that is the usual row.\n */\nconst props = defineProps<{ locale?: string | null }>()\n\nconst t = useTranslate('webx-admin')\n</script>\n\n<template>\n <wx-tooltip\n v-if=\"props.locale\"\n :content=\"t('links.address-fallback', { locale: props.locale.toUpperCase() })\"\n :max-width=\"280\"\n >\n <span class=\"wx-address-note\" tabindex=\"0\" @click.stop>\n <wx-icon name=\"info\" size=\"14px\" :label=\"t('links.address-fallback-label')\" />\n </span>\n </wx-tooltip>\n</template>\n\n<style scoped>\n.wx-address-note {\n display: inline-flex;\n align-items: center;\n margin-inline-start: var(--wx-space-4);\n color: var(--wx-text-muted);\n vertical-align: middle;\n cursor: help;\n}\n\n.wx-address-note:focus-visible {\n outline: none;\n border-radius: var(--wx-radius-xs);\n box-shadow: var(--wx-ring-focus);\n}\n</style>\n","import { useTranslate, type Translate } from '../i18n'\nimport type { CategoryWord } from './types'\n\n/**\n * The shared screens' words, the module's where it has them and the panel's where it does not.\n *\n * The panel's say \"category\"; a module that calls them something else hands its own keys in,\n * because a blog that says \"rubric\" on its list and \"category\" in the dialog over it has two\n * words for one thing (CLAUDE.md §4 — the words of a shared component are the caller's).\n */\nexport function useCategoryWords(\n words: Partial<Record<CategoryWord, string>> = {},\n): (word: CategoryWord, params?: Record<string, string | number>) => string {\n const t: Translate = useTranslate('webx-admin')\n\n return (word, params) => t(words[word] ?? `webx-admin::categories.${word}`, params)\n}\n","<script setup lang=\"ts\">\nimport { nextTick, onMounted, ref, useTemplateRef } from 'vue'\nimport { useModal, WxButton, WxDialog, WxFormItem, WxInput, WxSpace } from '@webx-ui/core'\nimport { useAdmin } from '../admin'\nimport { createCategoriesApi } from './api'\nimport type { CategoriesOptions, CategoryRow } from './types'\nimport { useCategoryWords } from './words'\n\n/**\n * A new category: what it is called, and nothing else.\n *\n * The server makes the address out of the name — the same transliteration it would apply to\n * anything typed in the field — and the page that opens next is where the address, the picture\n * and the fields of the project are written, with room to say what changing the address costs.\n */\ndefineOptions({ name: 'WxCategoryCreateDialog' })\n\nconst props = defineProps<{ options: CategoriesOptions }>()\n\nconst { open, resolve, dismiss } = useModal<CategoryRow>()\n\nconst api = createCategoriesApi(useAdmin(), props.options.api)\nconst w = useCategoryWords(props.options.words)\n\nconst title = ref('')\nconst saving = ref(false)\nconst errors = ref<Record<string, string[]>>({})\nconst field = useTemplateRef<HTMLElement>('field')\n\nonMounted(async () => {\n await nextTick()\n field.value?.querySelector('input')?.focus()\n})\n\n/** The first line of a refusal under a field that may be named by language: `slug.en`. */\nfunction errorOf(name: string): string | undefined {\n const key = Object.keys(errors.value).find((one) => one === name || one.startsWith(`${name}.`))\n\n return key === undefined ? undefined : errors.value[key]?.[0]\n}\n\nasync function submit(): Promise<void> {\n if (title.value.trim() === '' || saving.value) return\n\n saving.value = true\n errors.value = {}\n\n try {\n resolve(await api.create({ title: title.value.trim() }))\n } catch (error) {\n // The address is what can be refused here, and it is refused under the name that made it:\n // the registry sees every kind of page at once, so \"taken\" can mean taken by an article.\n errors.value = (error as { body?: { errors?: Record<string, string[]> } }).body?.errors ?? {}\n } finally {\n saving.value = false\n }\n}\n</script>\n\n<template>\n <wx-dialog v-model:open=\"open\" :title=\"w('new')\" :width=\"440\">\n <wx-form-item :label=\"w('field-title')\" :error=\"errorOf('title') ?? errorOf('slug')\" required>\n <div ref=\"field\">\n <wx-input v-model=\"title\" :aria-label=\"w('field-title')\" @keyup.enter=\"submit\" />\n </div>\n </wx-form-item>\n\n <template #footer>\n <wx-space size=\"sm\">\n <wx-button variant=\"outline\" @click=\"dismiss()\">{{ w('cancel') }}</wx-button>\n <wx-button type=\"primary\" :loading=\"saving\" :disabled=\"title.trim() === ''\" @click=\"submit\">\n {{ w('create') }}\n </wx-button>\n </wx-space>\n </template>\n </wx-dialog>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\nimport { useRouter } from 'vue-router'\nimport {\n confirm,\n createModal,\n toast,\n WxAlert,\n WxEmpty,\n WxIcon,\n WxSkeleton,\n WxSortableList,\n WxText,\n WxTooltip,\n} from '@webx-ui/core'\nimport WxAddressNote from '../AddressNote.vue'\nimport { useAdmin } from '../admin'\nimport { useErrorText } from '../errors'\nimport { useTranslate } from '../i18n'\nimport ListScreen from '../ListScreen.vue'\nimport RowMenu from '../RowMenu.vue'\nimport type { RowAction, ScreenAction } from '../types'\nimport { createCategoriesApi } from './api'\nimport CategoryCreateDialog from './CategoryCreateDialog.vue'\nimport type { CategoriesOptions, CategoryRow } from './types'\nimport { useCategoryWords } from './words'\n\n/**\n * The categories of a module: the whole menu of the site, in the order it has on it.\n *\n * One list and nothing beside it. The order here *is* the order on the site, so the screen the\n * section needs most is the one where every category is visible at once and can be dragged past\n * the others. A category is edited on a page of its own ({@link CategoryEditorPage}) — it has an\n * address, SEO and the fields of the project, and it is a record rather than a line of a menu.\n *\n * There is no paginator and no search: a site has eight of them, and a menu you have to search\n * is a menu that is already wrong.\n */\ndefineOptions({ name: 'WxCategoriesPage' })\n\nconst props = defineProps<{ options: CategoriesOptions }>()\n\nconst context = useAdmin()\nconst api = createCategoriesApi(context, props.options.api)\nconst router = useRouter()\nconst w = useCategoryWords(props.options.words)\n/* The heading and the way back, when the options name their own keys. */\nconst t = useTranslate('webx-admin')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\nconst rows = ref<CategoryRow[]>([])\nconst loading = ref(true)\n/*\n * Whether this module's categories have addresses at all. The FAQ's do not (a category there is\n * a button of a filter), and a line reading \"no address in this language\" under every one of\n * them would be a warning about nothing.\n */\nconst addressed = ref(true)\n\nconst canManage = computed(() => context.can(props.options.manage))\n\nconst title = computed(() =>\n props.options.title\n ? t(props.options.title)\n : (context.state.manifest?.modules.find((module) => module.id === props.options.module)\n ?.title ?? ''),\n)\n\nconst create = createModal<CategoryRow, { options: CategoriesOptions }>(CategoryCreateDialog)\n\n/* What the section offers. Declared, because on a phone the head folds it into the ···. */\nconst actions = computed<ScreenAction[]>(() =>\n canManage.value\n ? [{ key: 'new', label: w('new'), icon: 'plus', primary: true, run: () => void add() }]\n : [],\n)\n\nfunction countOf(row: CategoryRow): number {\n return Number((row as unknown as Record<string, unknown>)[props.options.count] ?? 0)\n}\n\nasync function load(): Promise<void> {\n loading.value = true\n\n try {\n const answer = await api.list()\n\n rows.value = answer.data\n addressed.value = answer.prefix !== null\n } catch (error) {\n toast.danger(message(error))\n } finally {\n loading.value = false\n }\n}\n\nvoid load()\n\nfunction open(row: CategoryRow): void {\n void router.push(`${props.options.path}/${row.id}`)\n}\n\n/** A name in a dialog, and then the page: the rest of a category is written there. */\nasync function add(): Promise<void> {\n const made = await create({ options: props.options })\n\n if (made !== undefined) open(made)\n}\n\n/**\n * Into the bin, and only while it is empty.\n *\n * The line stays in the menu and refuses rather than being left out, because \"why can I not\n * delete this\" is the question, and an item that is not there does not answer it. The number it\n * refuses with is already in the row.\n */\nasync function remove(row: CategoryRow): Promise<void> {\n if (countOf(row) > 0) {\n toast.warning(w('delete-blocked'))\n\n return\n }\n\n const agreed = await confirm({\n title: w('delete-title', { name: row.name }),\n message: w('delete-text'),\n confirmText: w('delete'),\n cancelText: w('cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n try {\n await api.remove(row.id)\n toast.success(w('deleted'))\n await load()\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\nfunction menuOf(row: CategoryRow): RowAction[] {\n const items: RowAction[] = [{ key: 'edit', label: w('edit'), icon: 'edit', run: () => open(row) }]\n const itemsOf = props.options.items\n\n if (itemsOf && countOf(row) > 0) {\n items.push({\n key: 'items',\n label: w('show-items'),\n icon: 'file-txt',\n run: () => void router.push(itemsOf(row.id)),\n })\n }\n\n if (row.url && row.is_visible) {\n items.push({ key: 'site', label: w('open-on-site'), icon: 'link', href: row.url })\n }\n\n if (canManage.value) {\n items.push({\n key: 'delete',\n label: w('delete'),\n icon: 'trash',\n danger: true,\n run: () => void remove(row),\n })\n }\n\n return items\n}\n\n/**\n * The whole order, every time.\n *\n * The list is already in its new order on screen — `WxSortableList` reorders the model before\n * it says anything — so this only has to agree with what is there, and a failure has to put the\n * list back rather than leave the screen disagreeing with the database.\n */\nasync function reorder(): Promise<void> {\n try {\n await api.reorder(rows.value.map((row) => row.id))\n } catch (error) {\n toast.danger(message(error, w('reorder-failed')))\n await load()\n }\n}\n</script>\n\n<template>\n <list-screen\n class=\"wx-categories\"\n :title=\"title\"\n :back=\"props.options.back?.path\"\n :back-label=\"props.options.back ? t(props.options.back.label) : undefined\"\n :actions=\"actions\"\n padding=\"sm\"\n >\n <wx-skeleton v-if=\"loading\" class=\"wx-categories__loading\" :rows=\"5\" />\n\n <wx-empty v-else-if=\"rows.length === 0\" :title=\"w('empty')\" :description=\"w('empty-help')\" />\n\n <template v-else>\n <!--\n A grip and not the whole row: the row is what opens a category, and a list whose rows\n both open and drag is a list where one of the two happens by accident.\n -->\n <wx-sortable-list\n v-model=\"rows\"\n class=\"wx-categories__list\"\n hoverable\n plain\n item-key=\"id\"\n :item-label=\"(item: CategoryRow) => item.name\"\n :disabled=\"!canManage\"\n @move=\"reorder\"\n >\n <template #default=\"{ item }\">\n <router-link\n class=\"wx-category-row\"\n :class=\"{ 'is-hidden': !item.is_visible }\"\n :to=\"`${props.options.path}/${item.id}`\"\n >\n <span class=\"wx-category-row__name\">\n <wx-text truncate weight=\"medium\">{{ item.name }}</wx-text>\n <wx-tooltip v-if=\"!item.is_visible\" :content=\"w('hidden')\">\n <wx-icon name=\"eye-off\" size=\"sm\" />\n </wx-tooltip>\n </span>\n <wx-text v-if=\"addressed\" size=\"sm\" tone=\"muted\" truncate>\n {{ item.path === null ? w('no-address') : `/${item.path}` }}\n <wx-address-note v-if=\"item.path !== null\" :locale=\"item.address_locale\" />\n </wx-text>\n </router-link>\n </template>\n\n <!-- How many items would be left without this category: the number the refusal to\n delete names, said before anybody presses anything. -->\n <template #actions=\"{ item }\">\n <wx-text class=\"wx-category-row__count\" size=\"sm\" tone=\"muted\">\n {{ w('count', { count: countOf(item) }) }}\n </wx-text>\n\n <row-menu :actions=\"menuOf(item)\" :label=\"item.name\" />\n </template>\n </wx-sortable-list>\n\n <!-- What the drag is for, under the rows it is about: over them it read as a heading for\n the list, which it is not — the list is already named by the screen. -->\n <wx-alert class=\"wx-categories__note\" type=\"info\" :description=\"w('order')\" />\n </template>\n </list-screen>\n</template>\n\n<style>\n.wx-categories__loading {\n padding: var(--wx-space-8);\n}\n\n/*\n * A plain list draws its rows edge to edge, which is right until one of them is tinted: the\n * highlight then starts exactly at the first letter, so a hovered row reads as a stain rather\n * than as a row. The padding is inside the tint, not around it.\n */\n.wx-categories__list.is-plain .wx-sortable-list__row {\n padding-inline: var(--wx-space-8);\n}\n\n.wx-categories__note {\n margin-block-start: var(--wx-space-8);\n}\n\n.wx-category-row {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-2);\n align-items: flex-start;\n /* The row is the target, so it takes the row: a name of four letters should not leave three\n quarters of the line dead. */\n flex: 1 1 auto;\n min-width: 0;\n color: inherit;\n text-decoration: none;\n}\n\n.wx-category-row__name {\n display: flex;\n align-items: center;\n gap: var(--wx-space-6);\n min-width: 0;\n max-width: 100%;\n}\n\n/* A category that is not on the site says so quietly, the way the site does: it is still a\n category, and its items still answer. */\n.wx-category-row.is-hidden {\n color: var(--wx-text-muted);\n}\n\n/* The count is beside the menu and must not be pushed into it by a long name. */\n.wx-category-row__count {\n flex: none;\n font-variant-numeric: tabular-nums;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, ref, watch } from 'vue'\nimport { WxIcon } from '@webx-ui/core'\nimport { useTranslate } from './i18n'\n\n/**\n * Whether the work is saved, said in the smallest way that says it.\n *\n * A word in the bar — \"Saved\" — is a word that is right nearly all of the time and therefore\n * reads as furniture: it is on screen when nothing is happening, which is exactly when nobody\n * is asking. What anyone actually wants to know is whether *this* save landed, and only for\n * as long as it takes to land. So: a wheel while it is in flight, a tick when it lands, and\n * nothing a couple of seconds later.\n *\n * Nothing at all for unsaved work either. That is not a small decision, and it holds because\n * the screens that use this say so twice over: the head carries a badge beside the name, and\n * the save button is enabled — a button offering to do something is the clearest statement\n * that there is something to do.\n *\n * The words stay, for whoever is not looking at the bar: the element is a live region and the\n * icon carries the sentence, so a screen reader hears \"Saving…\" and then \"Saved\".\n */\nconst props = withDefaults(\n defineProps<{\n /** What the screen is doing. Anything but `saving` and `saved` shows nothing. */\n state: 'saving' | 'unsaved' | 'saved' | string\n /** How long the tick stays after a save lands, in milliseconds. */\n linger?: number\n }>(),\n { linger: 2000 },\n)\n\nconst t = useTranslate('webx-admin')\n\n/*\n * The tick is shown for a save that happened, not for a screen that opens on saved work —\n * which is every screen, every time. So it is the *change* out of `saving` that lights it,\n * and never the value on its own.\n */\nconst landed = ref(false)\nlet fade: number | null = null\n\nwatch(\n () => props.state,\n (now, before) => {\n if (fade !== null) clearTimeout(fade)\n\n landed.value = before === 'saving' && now === 'saved'\n\n if (!landed.value) return\n\n fade = window.setTimeout(() => {\n landed.value = false\n fade = null\n }, props.linger)\n },\n)\n\nconst showing = computed(() =>\n props.state === 'saving' ? 'saving' : landed.value ? 'saved' : null,\n)\n\nonBeforeUnmount(() => {\n if (fade !== null) clearTimeout(fade)\n})\n</script>\n\n<template>\n <span class=\"wx-save-state\" role=\"status\" aria-live=\"polite\">\n <wx-icon\n v-if=\"showing\"\n class=\"wx-save-state__mark\"\n :class=\"{ 'is-saved': showing === 'saved' }\"\n :name=\"showing === 'saving' ? 'loader' : 'check'\"\n :spin=\"showing === 'saving'\"\n :label=\"t(`editor.${showing}`)\"\n />\n </span>\n</template>\n\n<style scoped>\n/*\n * It keeps its place while it is empty. The bar lays the state out beside the buttons, and a\n * box that appears and disappears would shift them by its width twice per save.\n */\n.wx-save-state {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: none;\n width: var(--wx-size-control-sm);\n height: var(--wx-size-control-sm);\n}\n\n.wx-save-state__mark {\n font-size: var(--wx-font-size-lg);\n color: var(--wx-text-muted);\n}\n\n.wx-save-state__mark.is-saved {\n color: var(--wx-color-success);\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\nimport { WxAlert, WxSkeleton } from '@webx-ui/core'\nimport { WxScreenRenderer, type Patch, 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 * The page's own patch, laid under the project's: what only the page can know — options\n * read from the values it loaded, say. The project's patch still has the last word.\n */\n patch?: Patch\n }>(),\n {\n errors: undefined,\n disabled: false,\n labelPosition: undefined,\n labelWidth: undefined,\n size: undefined,\n patch: () => [],\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\nconst patch = computed<Patch>(() => [...props.patch, ...admin.screenPatch(props.name)])\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 <!-- A page that knows the shape of its screen — tabs, a card — draws the wait in that shape. -->\n <slot v-else-if=\"root === null\" name=\"loading\"><wx-skeleton :rows=\"4\" /></slot>\n <wx-screen-renderer\n v-else\n v-model=\"model\"\n :root=\"root\"\n :patch=\"patch\"\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, onBeforeUnmount, onMounted, ref, watch } from 'vue'\nimport { onBeforeRouteLeave, useRoute, useRouter } from 'vue-router'\nimport {\n confirm,\n localizedValue,\n toast,\n useLocales,\n WxActionBar,\n WxBadge,\n WxBreadcrumb,\n WxBreadcrumbItem,\n WxButton,\n WxCard,\n WxSkeleton,\n type LocalizedValue,\n} from '@webx-ui/core'\nimport type { ScreenModel } from '@webx-ui/schema'\nimport { provideRecordAddress } from '../address'\nimport { useAdmin } from '../admin'\nimport { useErrorText } from '../errors'\nimport { useTranslate } from '../i18n'\nimport { provideHistorySubject } from '../history'\nimport { useBodyKeys } from '../keys'\nimport SaveState from '../SaveState.vue'\nimport Screen from '../Screen.vue'\nimport ScreenHead from '../ScreenHead.vue'\nimport type { ScreenAction } from '../types'\nimport { createCategoriesApi } from './api'\nimport { provideCategoryEditor } from './editor'\nimport type { CategoriesOptions, CategoryRow } from './types'\nimport { useCategoryWords } from './words'\n\n/**\n * One category, on a page of its own: a head, the described screen under it, and one button.\n *\n * A page and not a dialog, because a category is a record — an address, a picture, SEO and the\n * fields a project patched on — and a project that adds a tab to it needs a screen to add it to\n * (decision 7 of the services spec). The tabs are the module's screen (`blog.category-form`);\n * what this page owns is the head, the bar and the save.\n *\n * One explicit save rather than the autosave of an article: a category has no draft, so what is\n * saved is on the site at once, and a menu entry that renames itself between two keystrokes is\n * not something to do behind the editor's back. A refused field opens its own tab — the screen's\n * tabs do that themselves (`ScreenTabs.vue` in `@webx-ui/schema`).\n */\ndefineOptions({ name: 'WxCategoryEditorPage' })\n\nconst props = defineProps<{ options: CategoriesOptions }>()\n\nconst context = useAdmin()\nconst api = createCategoriesApi(context, props.options.api)\nconst route = useRoute()\nconst router = useRouter()\nconst locales = useLocales()\nconst w = useCategoryWords(props.options.words)\n/* The heading and the way back, when the options name their own keys. */\nconst t = useTranslate('webx-admin')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\nconst id = computed(() => Number(route.params.id))\nconst list = computed(() => props.options.path)\n\nconst category = ref<CategoryRow | null>(null)\nconst values = ref<ScreenModel>({})\nconst prefix = ref<string | null>(null)\nconst snapshot = ref('')\n\nconst loading = ref(true)\nconst saving = ref(false)\nconst errors = ref<Record<string, string[]>>({})\n\nconst canManage = computed(() => context.can(props.options.manage))\n\nconst current = computed(() => JSON.stringify(values.value))\nconst dirty = computed(() => snapshot.value !== '' && current.value !== snapshot.value)\n\nconst state = computed<'saving' | 'unsaved' | 'saved'>(() => {\n if (saving.value) return 'saving'\n\n return dirty.value ? 'unsaved' : 'saved'\n})\n\nconst section = computed(() =>\n props.options.title\n ? t(props.options.title)\n : (context.state.manifest?.modules.find((module) => module.id === props.options.module)\n ?.title ?? ''),\n)\n\n/** The name follows the field rather than the answer: a rename shows at the top as it is typed. */\nconst title = computed(() => {\n const written = values.value.title as LocalizedValue | string | null | undefined\n\n return localizedValue(written, locales.active.value, category.value?.name ?? '') || w('untitled')\n})\n\nprovideCategoryEditor({ category, values, prefix, moving: () => w('address-moving') })\n\n/* A form with a journal tab (`wx-history`, the brands' one) reads whose journal it is here. */\nprovideHistorySubject({ id: computed(() => category.value?.id ?? null) })\n\n/* The address field is the panel's shared one (`wx-slug`), and this is what it prints. */\nprovideRecordAddress({\n values,\n prefix,\n path: computed(() => category.value?.path),\n moving: () => w('address-moving'),\n})\n\nfunction take(detail: { category: CategoryRow; values: ScreenModel; prefix: string | null }): void {\n category.value = detail.category\n values.value = detail.values\n prefix.value = detail.prefix\n snapshot.value = JSON.stringify(detail.values)\n}\n\nasync function load(): Promise<void> {\n loading.value = true\n\n try {\n take(await api.get(id.value))\n } catch (error) {\n toast.danger(message(error))\n void router.push(list.value)\n } finally {\n loading.value = false\n }\n}\n\nasync function save(): Promise<void> {\n if (!category.value || saving.value || !canManage.value) return\n\n saving.value = true\n errors.value = {}\n\n try {\n take(await api.save(category.value.id, values.value))\n toast.success(w('saved'))\n } catch (error) {\n const body = (error as { body?: { errors?: Record<string, string[]> } }).body\n\n if (body?.errors) {\n errors.value = body.errors\n toast.danger(w('save-failed'))\n } else {\n toast.danger(message(error))\n }\n } finally {\n saving.value = false\n }\n}\n\nasync function remove(): Promise<void> {\n const row = category.value\n\n if (!row) return\n\n const agreed = await confirm({\n title: w('delete-title', { name: row.name }),\n message: w('delete-text'),\n confirmText: w('delete'),\n cancelText: w('cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n try {\n await api.remove(row.id)\n toast.success(w('deleted'))\n // Nothing left to protect: the record is in the bin, and asking about its unsaved edits on\n // the way out would be asking about a page that is gone.\n snapshot.value = current.value\n void router.push(list.value)\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\n/* The browser's own guard: it cannot wait for a request, so all it can do is ask. */\nfunction leaveGuard(event: BeforeUnloadEvent): void {\n if (dirty.value) event.preventDefault()\n}\n\n/* Ctrl+S saves, as it does everywhere else that has a save button. */\nfunction onKeydown(event: KeyboardEvent): void {\n if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 's') {\n event.preventDefault()\n if (dirty.value) void save()\n }\n}\n\n// And from the page around the form, where a click on empty space leaves the focus.\nuseBodyKeys(onKeydown)\n\nwatch(id, () => void load())\n\nonMounted(() => {\n void load()\n window.addEventListener('beforeunload', leaveGuard)\n})\n\nonBeforeUnmount(() => {\n window.removeEventListener('beforeunload', leaveGuard)\n})\n\nonBeforeRouteLeave(async () => {\n if (!dirty.value || !canManage.value) return true\n\n return await confirm({\n title: w('leave-title'),\n message: w('leave-text'),\n confirmText: w('leave'),\n cancelText: w('cancel'),\n tone: 'danger',\n })\n})\n\n/* What leads away from the category. On a phone the head folds them into the ···. */\nconst actions = computed<ScreenAction[]>(() => {\n const row = category.value\n const leads: ScreenAction[] = []\n\n if (!row) return leads\n\n if (row.url && row.is_visible) {\n leads.push({ key: 'site', label: w('open-on-site'), icon: 'link', href: row.url })\n }\n\n if (canManage.value) {\n leads.push({\n key: 'delete',\n label: w('delete'),\n icon: 'trash',\n danger: true,\n menu: true,\n run: () => void remove(),\n })\n }\n\n return leads\n})\n</script>\n\n<template>\n <div class=\"wx-category-editor\" @keydown=\"onKeydown\">\n <template v-if=\"loading || !category\">\n <!-- Shaped like the screen it stands in for: the head, and a card for what is coming. -->\n <wx-skeleton class=\"wx-category-editor__ghost\" title :rows=\"1\" />\n <wx-card><wx-skeleton :rows=\"6\" /></wx-card>\n </template>\n\n <template v-else>\n <screen-head\n divider\n :back=\"list\"\n :back-label=\"section\"\n :title=\"title\"\n :subtitle=\"category.path === null ? undefined : `/${category.path}`\"\n :actions=\"actions\"\n >\n <template #trail>\n <wx-breadcrumb size=\"sm\" :label=\"w('trail')\">\n <wx-breadcrumb-item :as=\"'router-link'\" :to=\"list\">{{ section }}</wx-breadcrumb-item>\n <wx-breadcrumb-item current>{{ title }}</wx-breadcrumb-item>\n </wx-breadcrumb>\n </template>\n\n <!-- Hidden is the one state a category has, and it is said where the name is. -->\n <template v-if=\"!category.is_visible\" #title-after>\n <wx-badge dot>{{ w('hidden') }}</wx-badge>\n </template>\n </screen-head>\n\n <screen\n v-model=\"values\"\n :name=\"props.options.screen\"\n :errors=\"errors\"\n :disabled=\"!canManage || saving\"\n />\n\n <wx-action-bar v-if=\"canManage\">\n <template #state>\n <save-state :state=\"state\" />\n </template>\n\n <wx-button type=\"primary\" :loading=\"saving\" :disabled=\"!dirty\" @click=\"save\">\n {{ w('save') }}\n </wx-button>\n </wx-action-bar>\n </template>\n </div>\n</template>\n\n<style scoped>\n.wx-category-editor {\n display: flex;\n flex-direction: column;\n gap: var(--wx-gap, var(--wx-space-16));\n container-type: inline-size;\n}\n\n.wx-category-editor__ghost {\n max-width: 420px;\n}\n</style>\n","import { defineComponent, h, type Component } from 'vue'\nimport type { RouteRecordRaw } from 'vue-router'\nimport CategoriesPage from './CategoriesPage.vue'\nimport CategoryEditorPage from './CategoryEditorPage.vue'\nimport type { CategoriesOptions } from './types'\n\n/**\n * The page, as a component of this module's own.\n *\n * Two modules' categories are the same two pages with different options, and `RouterView`\n * patches a component that stays the same rather than mounting it anew: from the blog's rubrics\n * to the services' categories the list kept its instance, took the new title from the props and\n * went on showing the rubrics it had loaded — everything else was read once, in `setup`. A\n * component per call is a different type for each module, so the switch is a fresh mount.\n */\nfunction bound(page: Component, options: CategoriesOptions): Component {\n return defineComponent({\n name: 'WxCategoriesView',\n setup: () => () => h(page, { options }),\n })\n}\n\n/**\n * The two screens of a module's categories — the list and the page of one — as routes to put\n * into the module's section:\n *\n * { id: 'rubrics', path: '/blog/rubrics', routes: categoryRoutes({ api: 'blog/rubrics', … }) }\n *\n * One description for both, so the list links to the editor and the editor back to the list\n * without either being told the other's address.\n */\nexport function categoryRoutes(options: CategoriesOptions): RouteRecordRaw[] {\n return [\n { path: options.path, name: options.name, component: bound(CategoriesPage, options) },\n {\n path: `${options.path}/:id(\\\\d+)`,\n name: `${options.name}.edit`,\n component: bound(CategoryEditorPage, options),\n },\n ]\n}\n","/**\n * How wide the column holding a row's `···` has to be.\n *\n * The menu is a finger target — 44px under `(pointer: coarse)`, which is what `WxActions` gives\n * a collapsed row — and the cell around it keeps `--wx-table-padding-x` on both sides. 44 and 16\n * and 16 is this number; the 56 the sections used to declare was never enough for it, and the\n * button simply painted outside its column, which nothing said out loud until cells began to\n * clip what does not fit.\n *\n * One constant rather than a number in seven files: when the touch target changes, the columns\n * that hold it change with it.\n */\nexport const rowMenuWidth = 76\n","<script setup lang=\"ts\">\nimport { WxBadge } from '@webx-ui/core'\nimport type { AppliedFilter } from './types'\n\n/**\n * What the filters are set to, in the head of every list of the panel.\n *\n * The table gives the strip its place and its spacing (`#applied`); this gives every section the\n * same chip. Written once because the alternative is what the panel already learned about\n * headings: five sections each answering \"what does an applied filter look like\" gives five\n * answers, and a reader who learns one list should already know the next.\n *\n * A chip takes one filter off. Taking all of them off lives with the fields it resets, inside\n * the panel the funnel opens — beside the chips it reads as one more chip, and it is the only\n * control in that row that does not remove exactly one thing.\n */\ndefineProps<{ filters: AppliedFilter[] }>()\n</script>\n\n<template>\n <wx-badge\n v-for=\"filter in filters\"\n :key=\"filter.key\"\n class=\"wx-filter-chip\"\n size=\"sm\"\n round\n closable\n @close=\"filter.clear()\"\n >\n {{ filter.label }}\n </wx-badge>\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, ref, watch } from 'vue'\nimport {\n confirm,\n toast,\n WxAvatar,\n WxButton,\n WxEmpty,\n WxSkeleton,\n WxText,\n WxTextarea,\n} from '@webx-ui/core'\nimport DateText from './DateText.vue'\nimport RowMenu from './RowMenu.vue'\nimport { useAdmin } from './admin'\nimport { useErrorText } from './errors'\nimport { useTranslate } from './i18n'\nimport { createNotesApi, type EntityNote } from './notes'\nimport type { RowAction } from './types'\n\ndefineOptions({ name: 'WxNotes' })\n\n/**\n * What one administrator wrote on a record for the next one.\n *\n * Deliberately a component of the frame and not of any section. The first place that wanted\n * one was the inbox, the next will be an order and the one after that a client, and three\n * copies of \"who may write here, who may edit what they wrote\" is two copies too many — so the\n * feed, its address and its rules live here, and a section only says which record it is about.\n *\n * Nothing is drawn while the reader may not write: the server refuses either way, and a box\n * that posts a 403 is worse than no box. Whether they may is the section's answer, because it\n * is the section's permission (`can`).\n */\nconst props = withDefaults(\n defineProps<{\n /** The alias the server registered the model under — never a class name. */\n type: string\n id: number | string\n /** Whether this reader may write and edit their own. */\n can?: boolean\n /** Heading above the feed; the panel's own word for it by default. */\n title?: string\n }>(),\n { can: true, title: undefined },\n)\n\nconst emit = defineEmits<{\n /** A note was written, changed or removed — a log beside the feed has to redraw. */\n change: []\n}>()\n\nconst admin = useAdmin()\nconst t = useTranslate('webx-admin')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\nconst notes = ref<EntityNote[]>([])\nconst loading = ref(true)\nconst draft = ref('')\nconst sending = ref(false)\nconst editing = ref<number | null>(null)\nconst edited = ref('')\n\nconst api = computed(() => createNotesApi(admin, props.type, props.id))\n\nconst heading = computed(() => props.title ?? t('notes.title'))\n\nasync function load(): Promise<void> {\n loading.value = true\n\n try {\n notes.value = await api.value.list()\n } catch (error) {\n toast.danger(message(error))\n } finally {\n loading.value = false\n }\n}\n\n/* The feed follows the record: on this screen the next submission replaces this one in\n place, and a feed that did not notice would be the previous one's notes under a new name. */\nwatch(\n () => [props.type, props.id] as const,\n () => {\n editing.value = null\n draft.value = ''\n void load()\n },\n { immediate: true },\n)\n\nasync function add(): Promise<void> {\n const body = draft.value.trim()\n\n if (body === '' || sending.value) return\n\n sending.value = true\n\n try {\n notes.value = [...notes.value, await api.value.add(body)]\n draft.value = ''\n emit('change')\n } catch (error) {\n toast.danger(message(error))\n } finally {\n sending.value = false\n }\n}\n\nfunction edit(note: EntityNote): void {\n editing.value = note.id\n edited.value = note.body\n}\n\nasync function save(note: EntityNote): Promise<void> {\n const body = edited.value.trim()\n\n if (body === '') return\n\n try {\n const saved = await api.value.save(note.id, body)\n notes.value = notes.value.map((one) => (one.id === saved.id ? saved : one))\n editing.value = null\n toast.success(t('notes.saved'))\n emit('change')\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\nasync function remove(note: EntityNote): Promise<void> {\n const agreed = await confirm({\n title: t('notes.delete-title'),\n message: t('notes.delete-text'),\n confirmText: t('notes.delete'),\n cancelText: t('notes.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n try {\n await api.value.remove(note.id)\n notes.value = notes.value.filter((one) => one.id !== note.id)\n toast.success(t('notes.deleted'))\n emit('change')\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\n/** Only the author's own, and only where writing is allowed at all. */\nfunction actionsFor(note: EntityNote): RowAction[] {\n if (!props.can || !note.is_mine) return []\n\n return [\n { key: 'edit', icon: 'edit', label: t('notes.edit'), run: () => edit(note) },\n {\n key: 'delete',\n icon: 'trash',\n label: t('notes.delete'),\n danger: true,\n run: () => void remove(note),\n },\n ]\n}\n\nfunction who(note: EntityNote): string {\n return note.author?.name ?? t('notes.unknown-author')\n}\n</script>\n\n<template>\n <section class=\"wx-notes\">\n <wx-text v-if=\"heading\" weight=\"medium\" class=\"wx-notes__title\">{{ heading }}</wx-text>\n\n <wx-skeleton v-if=\"loading\" :rows=\"2\" />\n\n <wx-empty v-else-if=\"notes.length === 0\" size=\"sm\" :description=\"t('notes.empty')\" />\n\n <ul v-else class=\"wx-notes__list\">\n <li v-for=\"note in notes\" :key=\"note.id\" class=\"wx-note\">\n <wx-avatar :name=\"who(note)\" size=\"sm\" tone=\"auto\" />\n\n <div class=\"wx-note__body\">\n <div class=\"wx-note__head\">\n <wx-text size=\"sm\" weight=\"medium\" truncate>{{ who(note) }}</wx-text>\n <date-text :value=\"note.created_at\" />\n <row-menu :actions=\"actionsFor(note)\" :label=\"who(note)\" />\n </div>\n\n <template v-if=\"editing === note.id\">\n <wx-textarea v-model=\"edited\" :autosize=\"{ minRows: 2, maxRows: 10 }\" />\n <div class=\"wx-note__buttons\">\n <wx-button size=\"sm\" type=\"primary\" @click=\"save(note)\">\n {{ t('notes.save') }}\n </wx-button>\n <wx-button size=\"sm\" variant=\"text\" @click=\"editing = null\">\n {{ t('notes.cancel') }}\n </wx-button>\n </div>\n </template>\n\n <!-- `white-space: pre-wrap` and nothing else: a note is typed, not authored, and\n everything that renders markup here is a way to put a link nobody wrote into a\n panel. -->\n <p v-else class=\"wx-note__text\">{{ note.body }}</p>\n </div>\n </li>\n </ul>\n\n <div v-if=\"can\" class=\"wx-notes__new\">\n <wx-textarea\n v-model=\"draft\"\n :placeholder=\"t('notes.placeholder')\"\n :autosize=\"{ minRows: 2, maxRows: 8 }\"\n />\n <wx-button\n size=\"sm\"\n type=\"primary\"\n :disabled=\"draft.trim() === ''\"\n :loading=\"sending\"\n @click=\"add\"\n >\n {{ t('notes.add') }}\n </wx-button>\n </div>\n </section>\n</template>\n\n<style scoped>\n.wx-notes {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-12);\n min-width: 0;\n /* The feed decides its own layout from its own width and not the window's: it hangs in a\n card beside other cards, and the window knows nothing about how wide that is. */\n container-type: inline-size;\n}\n\n.wx-notes__list {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-12);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.wx-note {\n display: flex;\n gap: var(--wx-space-8);\n min-width: 0;\n}\n\n.wx-note__body {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-4);\n flex: 1 1 auto;\n min-width: 0;\n}\n\n/* The menu keeps the end of the line, so a note by somebody with a long name and one by\n somebody with a short one still have their controls in the same place. */\n.wx-note__head {\n display: flex;\n align-items: center;\n gap: var(--wx-space-8);\n min-width: 0;\n}\n\n.wx-note__head > :last-child {\n margin-inline-start: auto;\n}\n\n.wx-note__text {\n margin: 0;\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n font-size: var(--wx-font-size-sm);\n color: var(--wx-text-default);\n}\n\n.wx-note__buttons {\n display: flex;\n align-items: flex-start;\n gap: var(--wx-space-8);\n}\n\n/*\n * The button stands under the box, not beside it.\n *\n * Beside it, the button took its own width off the box at every width, so the thing being\n * written was always narrower than the room there was for it — and the two lined up along\n * their tops, which put a button at the height of the first line of a note that had not been\n * typed yet. Under it, the box is the width of the card and the button is the width of its\n * own word.\n */\n.wx-notes__new {\n display: flex;\n flex-direction: column;\n align-items: stretch;\n gap: var(--wx-space-8);\n}\n\n.wx-notes__new > :first-child {\n min-width: 0;\n}\n\n.wx-notes__new > :last-child {\n align-self: flex-start;\n}\n\n/* On a phone it takes the width instead: a target for a finger, and there is nothing beside\n it to line up with anyway. */\n@container (max-width: 440px) {\n .wx-note__buttons {\n flex-direction: column;\n align-items: stretch;\n }\n\n .wx-notes__new > :last-child {\n align-self: stretch;\n }\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, nextTick, ref, watch } from 'vue'\nimport { confirm, WxAlert, WxButton } from '@webx-ui/core'\nimport { useDates } from './dates'\nimport { timeless, type Editing } from './editing'\nimport { useTranslate } from './i18n'\nimport type { MergeChoice, MergeConflict } from './merge'\n\n/**\n * What an editor says about other people editing the same record, above its form.\n *\n * Six things, the loudest first. The record was deleted for good, with a way to copy out what was\n * typed. The record is in the bin, with the way out of it. A conflict: both sides changed the same place, and each such\n * place is listed with what it was, what this editor wrote and what the other side wrote, to be\n * settled one by one — everything else has already been merged. A save that came in while this\n * editor was open, with what it changed and an offer to pull it in before saving over it. What\n * others did besides writing — published it, moved it, put an old version back. And,\n * quietest, who else has the record open right now.\n */\nconst props = defineProps<{ editing: Editing<unknown> }>()\n\nconst emit = defineEmits<{\n /** The conflict is settled into the form: save it. */\n save: []\n /** Give up this editor's side and read the record again. */\n theirs: []\n /** Pulled in with nothing overlapping: the form holds both edits now. */\n pulled: []\n}>()\n\nconst t = useTranslate('webx-admin')\nconst dates = useDates()\n\nconst conflict = computed(() => props.editing.conflict.value)\nconst incoming = computed(() => props.editing.incoming.value)\nconst others = computed(() => props.editing.editors.value)\n\nconst conflictTitle = computed(() =>\n t('editing.conflict-title', { who: props.editing.who(conflict.value?.theirs.changed) }),\n)\n\n/*\n * A conflict is the one thing here that waits for an answer, and it appears after a save — often\n * with the editor scrolled deep into a long page, where the top of the screen is out of sight.\n * Brought into view once, when it appears; the notice of an incoming save is not, because it asks\n * for nothing and pulling the page away from what somebody is reading is worse than missing it.\n */\nconst conflictAlert = ref<{ $el?: Element } | null>(null)\n\nwatch(\n () => conflict.value !== null,\n async (open) => {\n if (!open) return\n\n await nextTick()\n conflictAlert.value?.$el?.scrollIntoView?.({ block: 'nearest', behavior: 'smooth' })\n },\n)\n\nfunction when(at: string | null | undefined): string {\n return at ? dates.short(at) : ''\n}\n\n/* What others did besides writing: published, moved, restored an old version. */\nconst events = computed(() => props.editing.events.value)\n\n/*\n * An old version put back changes every field it touched, and «changed Hero › Eyebrow, Heading,\n * Below the button and more» hides the one thing worth saying: which version, and that it went\n * on the site. When an event explains the change, the notice says the event instead.\n */\nconst explained = computed(() =>\n events.value.some((event) => event.kind === 'restored_version' || event.kind === 'discarded'),\n)\n\nconst incomingText = computed(() => {\n const now = incoming.value\n\n if (!now) return ''\n\n if (explained.value) return props.editing.describe(events.value)\n\n return timeless(\n t('editing.incoming', {\n who: props.editing.who(now.theirs.changed),\n what: props.editing.places(now.paths),\n when: when(now.theirs.changed?.at),\n }),\n )\n})\n\nconst eventsText = computed(() => props.editing.describe(events.value))\n\n/* In the bin: who put it there and when, and the way back for whoever may take it. */\nconst trashed = computed(() => props.editing.trashed.value)\n\nconst trashedTitle = computed(() => {\n const by = props.editing.trashedBy.value\n\n if (by) return t('editing.trashed-title', { who: props.editing.who(by), when: when(by.at) })\n\n return t('editing.trashed-anonymous', {\n when: when(props.editing.state.value?.deleted_at),\n })\n})\n\n/* Deleted for good: who did it and when. No way back — only the text, to be carried out. */\nconst gone = computed(() => props.editing.gone.value)\n\nconst goneTitle = computed(() => {\n const by = gone.value\n\n // A 410 with nothing in it — an older server — says only that it is gone.\n if (!by?.at) return t('editing.purged-anonymous')\n\n return timeless(t('editing.purged-title', { who: props.editing.who(by), when: when(by.at) }))\n})\n\nconst restoring = ref(false)\n\n/** Out of the bin, and what the form holds saved right after when there is anything. */\nasync function restore(): Promise<void> {\n const keep = props.editing.unsaved.value\n\n restoring.value = true\n\n try {\n if ((await props.editing.restoreFromBin()) && keep) emit('save')\n } finally {\n restoring.value = false\n }\n}\n\nfunction chosen(one: MergeConflict): MergeChoice {\n return conflict.value?.choices[one.id] ?? 'mine'\n}\n\nfunction side(one: MergeConflict, which: 'base' | MergeChoice): string {\n if (which === 'mine' && one.kind === 'removed-mine') return t('editing.removed')\n if (which === 'theirs' && one.kind === 'removed-theirs') return t('editing.removed')\n\n return props.editing.preview(one[which]) || t('editing.empty')\n}\n\nfunction save(): void {\n props.editing.resolve()\n emit('save')\n}\n\nasync function takeTheirs(): Promise<void> {\n const agreed = await confirm({\n title: t('editing.theirs-title'),\n message: t('editing.theirs-text'),\n confirmText: t('editing.take-theirs'),\n cancelText: t('editing.cancel'),\n tone: 'danger',\n })\n\n if (agreed) emit('theirs')\n}\n\nasync function pull(): Promise<void> {\n if (await props.editing.pull()) emit('pulled')\n}\n</script>\n\n<template>\n <!-- Deleted for good: there is nothing to save into and nothing to restore. The form keeps what\n it holds, and the one thing left to offer is carrying it out. -->\n <wx-alert\n v-if=\"gone\"\n class=\"wx-editing-alerts\"\n type=\"danger\"\n :title=\"goneTitle\"\n :description=\"t('editing.purged-text')\"\n live\n >\n <template #actions>\n <wx-button size=\"sm\" type=\"primary\" @click=\"editing.copyText()\">\n {{ t('editing.copy-text') }}\n </wx-button>\n </template>\n </wx-alert>\n\n <!-- In the bin: nothing typed here can be saved until it is out. The form keeps what it holds,\n so the text can be copied even by somebody who may not restore it. -->\n <wx-alert\n v-else-if=\"trashed\"\n class=\"wx-editing-alerts\"\n type=\"danger\"\n :title=\"trashedTitle\"\n :description=\"t('editing.trashed-text')\"\n live\n >\n <template v-if=\"editing.canRestore()\" #actions>\n <wx-button size=\"sm\" type=\"primary\" :loading=\"restoring\" @click=\"restore\">\n {{ editing.unsaved.value ? t('editing.restore-save') : t('editing.restore-bin') }}\n </wx-button>\n </template>\n </wx-alert>\n\n <wx-alert\n v-else-if=\"conflict\"\n ref=\"conflictAlert\"\n class=\"wx-editing-alerts\"\n type=\"warning\"\n :title=\"conflictTitle\"\n live\n >\n <p class=\"wx-editing-alerts__lead\">{{ t('editing.conflict-text') }}</p>\n\n <ul class=\"wx-editing-alerts__list\">\n <li v-for=\"one in conflict.conflicts\" :key=\"one.id\" class=\"wx-editing-alerts__item\">\n <div class=\"wx-editing-alerts__place\">\n {{ editing.label(one.path) }}\n <span v-if=\"one.kind !== 'changed'\" class=\"wx-editing-alerts__note\">\n {{ t(`editing.${one.kind}`) }}\n </span>\n </div>\n\n <div class=\"wx-editing-alerts__was\">\n <span class=\"wx-editing-alerts__side\">{{ t('editing.base') }}</span>\n {{ side(one, 'base') }}\n </div>\n\n <div\n class=\"wx-editing-alerts__choices\"\n role=\"radiogroup\"\n :aria-label=\"editing.label(one.path)\"\n >\n <button\n v-for=\"which in ['mine', 'theirs'] as const\"\n :key=\"which\"\n type=\"button\"\n role=\"radio\"\n class=\"wx-editing-alerts__choice\"\n :class=\"{ 'is-chosen': chosen(one) === which }\"\n :aria-checked=\"chosen(one) === which\"\n @click=\"editing.choose(one.id, which)\"\n >\n <span class=\"wx-editing-alerts__side\">{{ t(`editing.${which}`) }}</span>\n <span class=\"wx-editing-alerts__value\">{{ side(one, which) }}</span>\n </button>\n </div>\n </li>\n </ul>\n\n <template #actions>\n <wx-button size=\"sm\" variant=\"outline\" @click=\"takeTheirs\">\n {{ t('editing.take-theirs') }}\n </wx-button>\n <wx-button size=\"sm\" type=\"primary\" @click=\"save\">\n {{ t('editing.apply') }}\n </wx-button>\n </template>\n </wx-alert>\n\n <wx-alert v-else-if=\"incoming\" class=\"wx-editing-alerts\" type=\"info\" :title=\"incomingText\" live>\n <template #actions>\n <wx-button size=\"sm\" type=\"primary\" @click=\"pull\">{{ t('editing.pull') }}</wx-button>\n </template>\n </wx-alert>\n\n <wx-alert\n v-else-if=\"events.length > 0\"\n class=\"wx-editing-alerts\"\n type=\"info\"\n :title=\"eventsText\"\n live\n >\n <template #actions>\n <wx-button size=\"sm\" variant=\"outline\" @click=\"editing.dismissEvents()\">\n {{ t('editing.dismiss') }}\n </wx-button>\n </template>\n </wx-alert>\n\n <p v-else-if=\"others.length > 0\" class=\"wx-editing-alerts__others\">\n {{ t('editing.others', { names: others.map((one) => one.name).join(', ') }) }}\n </p>\n</template>\n\n<style scoped>\n.wx-editing-alerts__lead {\n margin: 0 0 var(--wx-space-8);\n}\n\n.wx-editing-alerts__list {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-12);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.wx-editing-alerts__item {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-4);\n}\n\n.wx-editing-alerts__place {\n font-weight: var(--wx-font-weight-medium);\n}\n\n.wx-editing-alerts__note,\n.wx-editing-alerts__was,\n.wx-editing-alerts__side {\n color: var(--wx-text-muted);\n}\n\n.wx-editing-alerts__note {\n margin-inline-start: var(--wx-space-8);\n font-weight: var(--wx-font-weight-regular);\n}\n\n.wx-editing-alerts__side {\n margin-inline-end: var(--wx-space-6);\n font-size: var(--wx-font-size-sm);\n}\n\n.wx-editing-alerts__choices {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(min(100%, 220px), 1fr));\n gap: var(--wx-space-8);\n}\n\n.wx-editing-alerts__choice {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: var(--wx-space-2);\n min-width: 0;\n padding: var(--wx-space-8) var(--wx-space-10);\n border: 1px solid var(--wx-border-default);\n border-radius: var(--wx-radius-sm);\n background: var(--wx-bg-surface);\n color: var(--wx-text-default);\n font: inherit;\n text-align: start;\n cursor: pointer;\n}\n\n.wx-editing-alerts__choice.is-chosen {\n border-color: var(--wx-border-focus);\n box-shadow: 0 0 0 1px var(--wx-border-focus);\n}\n\n.wx-editing-alerts__choice:focus-visible {\n outline: 2px solid var(--wx-border-focus);\n outline-offset: 2px;\n}\n\n.wx-editing-alerts__value {\n overflow-wrap: anywhere;\n}\n\n.wx-editing-alerts__others {\n margin: 0;\n color: var(--wx-text-muted);\n font-size: var(--wx-font-size-sm);\n}\n</style>\n","<script setup lang=\"ts\">\nimport { ref, watch } from 'vue'\nimport { confirm, toast, WxBadge, WxButton, WxHeading, WxSkeleton, WxText } from '@webx-ui/core'\nimport { useAdmin } from './admin'\nimport DateText from './DateText.vue'\nimport { useErrorText } from './errors'\nimport type { DraftCopy } from './editing'\nimport { useEditingLabels } from './editingLabels'\nimport { useTranslate } from './i18n'\n\n/**\n * The copies of the draft between publications: what the history does not list.\n *\n * The autosave ring, and the drafts somebody else's save wrote over — an agent's edit under an\n * editor's «Keep mine», or the other way round. Put back with «Restore», which makes the copy the\n * draft again; nothing reaches the site until somebody publishes. Shared by every drafted\n * editor's history tab: the list is the same whatever the record is.\n */\ndefineOptions({ name: 'WxDrafts' })\n\nconst props = withDefaults(\n defineProps<{\n /** The record's name on the server's `editing/{entity}/{id}`. */\n entity: string\n id: string | number | null | undefined\n /** Whether this reader may put a copy back. */\n canRestore?: boolean\n /** Anything that changes when the record is saved or published: the list is read again. */\n stamp?: unknown\n /** The screen the record is edited on, so the changed places read as the form names them. */\n screen?: string\n }>(),\n { canRestore: false, stamp: undefined, screen: undefined },\n)\n\nconst emit = defineEmits<{\n /** A copy is the draft now: the editor reads the record again. */\n restored: []\n}>()\n\nconst admin = useAdmin()\nconst t = useTranslate('webx-admin')\nconst message = useErrorText()\n\nconst drafts = ref<DraftCopy[] | null>(null)\nconst labels = useEditingLabels({ screen: props.screen })\n\n/**\n * What a copy changed against the one before it: «Hero › Eyebrow · EN», so that the edit somebody\n * lost can be found by the place it was in. Three, and «and more» after them.\n */\nfunction changed(copy: DraftCopy): string {\n const named = [...new Set((copy.paths ?? []).map((path) => labels.label(path)))]\n\n if (named.length === 0) return ''\n\n const what = named.slice(0, 3).join(', ')\n\n return named.length > 3 ? t('editing.and-more', { what }) : what\n}\nconst working = ref(false)\n\nconst base = (): string | null =>\n props.id === null || props.id === undefined || props.id === ''\n ? null\n : `${admin.apiPath}/editing/${props.entity}/${encodeURIComponent(String(props.id))}/drafts`\n\nasync function load(): Promise<void> {\n const url = base()\n\n if (url === null) return\n\n try {\n drafts.value = (await admin.http.get<{ data: DraftCopy[] }>(url)).data\n } catch {\n // A panel without the endpoint, or a record with no copies to speak of: say nothing.\n drafts.value = []\n }\n}\n\nasync function restore(copy: DraftCopy): Promise<void> {\n const url = base()\n\n if (url === null) return\n\n const agreed = await confirm({\n title: t('editing.restore-title'),\n message: t('editing.restore-text'),\n confirmText: t('editing.restore'),\n cancelText: t('editing.cancel'),\n })\n\n if (!agreed) return\n\n working.value = true\n\n try {\n await admin.http.post(`${url}/${copy.id}/restore`)\n emit('restored')\n toast.success(t('editing.restored'))\n await load()\n } catch (error) {\n toast.danger(message(error))\n } finally {\n working.value = false\n }\n}\n\nwatch(\n () => [props.entity, props.id, props.stamp],\n () => void load(),\n { immediate: true },\n)\n</script>\n\n<template>\n <section v-if=\"drafts === null || drafts.length > 0\" class=\"wx-drafts\">\n <header class=\"wx-drafts__head\">\n <wx-heading :level=\"3\" size=\"sm\">{{ t('editing.drafts') }}</wx-heading>\n <wx-text size=\"sm\" tone=\"muted\">{{ t('editing.drafts-text') }}</wx-text>\n </header>\n\n <div class=\"wx-drafts__list\">\n <wx-skeleton v-if=\"drafts === null\" class=\"wx-drafts__ghost\" :rows=\"2\" />\n <div v-for=\"copy in drafts ?? []\" :key=\"copy.id\" class=\"wx-drafts__row\">\n <div class=\"wx-drafts__who\">\n <date-text v-if=\"copy.created_at\" :value=\"copy.created_at\" size=\"md\" tone=\"default\" />\n <wx-text size=\"sm\" tone=\"muted\">\n {{ copy.author ?? t('editing.somebody') }} · {{ t(`editing.source-${copy.source}`) }}\n </wx-text>\n <wx-text v-if=\"changed(copy)\" size=\"sm\" class=\"wx-drafts__changed\">\n {{ changed(copy) }}\n </wx-text>\n </div>\n <wx-badge :type=\"copy.kind === 'overwritten' ? 'warning' : 'default'\" dot>\n {{ t(`editing.${copy.kind}`) }}\n </wx-badge>\n <wx-button\n v-if=\"canRestore\"\n size=\"sm\"\n variant=\"outline\"\n :loading=\"working\"\n @click=\"restore(copy)\"\n >\n {{ t('editing.restore') }}\n </wx-button>\n </div>\n </div>\n </section>\n</template>\n\n<style scoped>\n.wx-drafts {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-8);\n}\n\n.wx-drafts__head {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-2);\n}\n\n.wx-drafts__list {\n display: flex;\n flex-direction: column;\n border: 1px solid var(--wx-border-default);\n border-radius: var(--wx-radius-md);\n background: var(--wx-bg-surface);\n overflow: hidden;\n}\n\n.wx-drafts__ghost {\n padding: var(--wx-space-10) var(--wx-space-12);\n}\n\n.wx-drafts__row {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: var(--wx-space-12);\n padding: var(--wx-space-10) var(--wx-space-12);\n border-block-end: 1px solid var(--wx-border-default);\n}\n\n.wx-drafts__row:last-child {\n border-block-end: 0;\n}\n\n.wx-drafts__changed {\n overflow-wrap: anywhere;\n}\n\n.wx-drafts__who {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-width: 160px;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { WxAlert, WxText } from '@webx-ui/core'\nimport { useAdmin } from './admin'\nimport { useDates } from './dates'\nimport { useTranslate } from './i18n'\nimport DateText from './DateText.vue'\n\ndefineOptions({ name: 'WxBackupNote' })\n\n/**\n * One line about the nightly database dump (§6 of the backups spec).\n *\n * A backup whose breakage is discovered on the day it was needed is not a backup, and the\n * cheapest guard against that is a sentence somewhere a person already looks. This is that\n * sentence: no screen, no buttons, nothing to click. When the newest file is older than two\n * days — or there is no file at all — the same line turns into a warning, because by then the\n * only useful thing to say is \"go and look at the cron\".\n *\n * It renders nothing at all for a site that has switched the dump off, and nothing for an\n * administrator without `settings.view`: whoever may see the system section may see this, and\n * for everybody else it is a fact about the server that is none of their business.\n */\nconst STALE_AFTER_HOURS = 48\n\nconst admin = useAdmin()\nconst dates = useDates()\nconst t = useTranslate('webx-admin')\n\nconst backup = computed(() => admin.state.manifest?.backup ?? null)\nconst visible = computed(() => backup.value !== null && admin.can('settings.view'))\n\nconst stale = computed(() => {\n const at = backup.value?.at\n\n if (at === null || at === undefined) return true\n\n const taken = new Date(at).getTime()\n\n return Number.isNaN(taken) || Date.now() - taken > STALE_AFTER_HOURS * 3600_000\n})\n\n/*\n * \"4.2 MB\" in English, \"4,2 МБ\" in Russian, and the right abbreviation in the seven others —\n * from `Intl` rather than from three words in every language file, which is what a unit of\n * measure is for.\n */\nconst size = computed(() => {\n const bytes = backup.value?.bytes\n\n if (bytes === null || bytes === undefined) return ''\n\n const units = ['byte', 'kilobyte', 'megabyte', 'gigabyte', 'terabyte'] as const\n let value = bytes\n let unit = 0\n\n while (value >= 1024 && unit < units.length - 1) {\n value /= 1024\n unit++\n }\n\n return new Intl.NumberFormat(admin.i18n.state.locale, {\n style: 'unit',\n unit: units[unit],\n unitDisplay: 'short',\n maximumFractionDigits: value >= 10 || unit === 0 ? 0 : 1,\n }).format(value)\n})\n\n/* The warning names the day rather than saying \"two days ago\": the reader is about to go and\n look at a log, and a date is what they will be looking for in it. */\nconst warning = computed(() =>\n backup.value?.at == null\n ? t('backup.never')\n : t('backup.stale', { date: dates.short(backup.value.at) }),\n)\n</script>\n\n<template>\n <wx-alert v-if=\"visible && stale\" type=\"warning\" variant=\"soft\" :description=\"warning\" />\n\n <p v-else-if=\"visible\" class=\"wx-backup-note\">\n <wx-text size=\"sm\" tone=\"muted\">{{ t('backup.title') }}</wx-text>\n <date-text :value=\"backup?.at\" />\n <wx-text v-if=\"size\" size=\"sm\" tone=\"muted\">· {{ size }}</wx-text>\n </p>\n</template>\n\n<style scoped>\n/*\n * A footnote, and shaped like one: it wraps rather than pushes, because the three parts of it\n * are three separate elements and a narrow panel has to be allowed to break between them.\n */\n.wx-backup-note {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--wx-space-4) var(--wx-space-6);\n margin: 0;\n}\n</style>\n"],"names":["i18nKey","useI18n","i18n","inject","useTranslate","namespace","createI18n","provideI18n","app","options","fallback","state","reactive","builtIn","dictionary","lookup","source","path","node","segment","translate","key","params","explicitNamespace","rest","line","fill","messages","next","locale","nextFallback","filled","name","value","createCategoriesApi","admin","base","data","body","query","search","input","id","values","ids","reorderItems","category","props","__props","_useModel","useAdmin","t","catalogue","ref","watch","payload","row","chosen","computed","names","one","rows","available","add","picked","remove","_openBlock","_createElementBlock","_hoisted_1","_createVNode","_unref","WxSortableList","$event","_withCtx","item","index","_createElementVNode","_hoisted_2","_toDisplayString","_createBlock","WxBadge","WxAction","WxSelect","loaded","offered","_mergeProps","_ctx","HttpError","message","status","errors","retryAfter","UNSAFE","OVERRIDABLE","createHttp","baseUrl","csrfUrl","doFetch","onUnauthenticated","standingHeaders","override","csrfFetched","ensureCsrfCookie","force","readCookie","send","method","retried","unsafe","headers","token","sentMethod","field","withMethodField","response","url","request","toError","json","parsed","header","full","serialised","part","createRelationsApi","target","normaliseRelations","relationOwnerKey","provideRelationOwner","owner","provide","useRelationOwner","api","known","missing","forbidden","candidates","searching","term","remember","unknown","answered","error","candidate","self","found","pick","option","nextTick","locked","placeholder","mark","_normalizeClass","_hoisted_3","_hoisted_4","_createTextVNode","_hoisted_5","_hoisted_6","WxAutocomplete","COLLECTION_MAX_LIMIT","asked","collectionSources","sources","relationTargets","title","a","b","related","type","current","normaliseCollection","fields","limit","defaultMarkup","model","useId","info","categories","write","patch","relatedType","relationOptions","relatedTarget","targets","relateTo","relate","relateToCurrent","on","markup","markupHint","_Fragment","_hoisted_7","WxSwitch","_cache","_hoisted_8","RelationsField","_hoisted_9","_hoisted_10","_hoisted_11","WxInputNumber","_hoisted_12","_hoisted_13","WxButton","emptyLink","createLinksApi","links","routes","section","echo","link","sectionOptions","hint","routeOptions","route","onMounted","restore","loadRoutes","resolved","setTarget","setSection","clearEntity","setUrl","setHash","hash","anchor","setNewTab","toggleRel","rel","kept","parts","REL","WxSegmented","text","WxText","WxInput","WxCheckbox","_renderList","LinkPicker","$attrs","adminMessages","createDates","formatters","format","shape","at","formatter","time","toDate","now","days","daysApart","useDates","standalone","loose","day","d","dates","exact","iso","WxTooltip","errorText","statusOf","bodyMessage","seconds","useErrorText","createHistoryApi","page","runId","historySubjectKey","provideHistorySubject","subject","useHistorySubject","historyValue","empty","max","cut","entries","lastPage","loading","loadingMore","recordId","heading","load","first","toast","more","__expose","who","entry","what","from","change","to","long","runLabel","runOpen","run","runSearch","runLoading","runTicket","loadRun","ticket","answer","openRun","summaryLines","summary","runFailed","failed","WxEmpty","WxSkeleton","WxAvatar","DateText","WxDialog","_hoisted_14","_hoisted_15","_hoisted_16","_hoisted_17","_hoisted_18","_hoisted_19","_hoisted_20","_hoisted_21","RouterLink","ordered","action","divided","WxActions","WxDropdownItem","emit","__emit","slots","useSlots","root","useTemplateRef","width","useElementWidth","narrow","primary","buttons","menu","backTo","hasActions","$slots","_renderSlot","WxBackButton","WxHeading","WxIcon","WxRowMenu","view","WxScreenHead","WxTabs","WxCard","KEY","documents","richTextKeys","keys","pattern","html","match","decode","area","rewrite","addresses","template","changed","element","address","attribute","rewriteRichText","pickImage","attrs","useAttrs","shown","sent","assetUrls","labels","WxRichText","recordAddressKey","provideRecordAddress","useRecordAddress","locales","useLocales","orphaned","prefix","head","slug","written","moving","_createSlots","WxAlert","adminTypes","ListScreen","RichTextField","LinkField","CategoriesField","SourceSelect","CollectionField","SlugField","HistoryFeed","adminKey","badgeOf","meta","badge","createAdminContext","nav","manifest","module","registered","groups","declared","top","byGroup","group","list","sections","captioned","types","blockLabelsOf","blockLabelsPending","screens","loadScreen","pending","loadSession","reload","adoptTheme","switchLocale","isUnauthenticated","refreshManifest","user","setLocale","code","refresh","knownAssets","cachedAssetUrls","paths","batch","context","urls","loader","permission","provideAdmin","router","useRouter","destination","e","WxResult","useRoute","revealed","reveal","el","_component_wx_menu","_component_wx_menu_item","_component_wx_badge","_component_wx_submenu","_component_wx_menu_group","shellEl","layout","collapsed","showAside","drawerOpen","toggle","close","useResponsiveShell","bare","logo","size","shellLayoutFor","watchEffect","onUnmounted","WxToaster","_component_router_view","_component_wx_loading","_component_wx_result","_component_wx_button","_component_wx_container","_component_wx_header","_component_wx_action","_component_wx_text","_component_wx_aside","_component_wx_main","_component_wx_drawer","fetchSiteBarContext","http","apiPath","language","onSitePage","listener","handler","event","siteBarKey","useSiteBar","publishSiteBarEntity","entity","discardSiteBarEntity","tellSitePage","tab","settling","moduleTabs","drawn","namesOf","tabNames","remembered","panelHref","STATUS_TYPES","quiet","caught","draft","mayPublish","busy","publish","discard","confirm","onKeydown","onBeforeUnmount","_component_wx_alert","_component_wx_tabs","_component_wx_tab","_component_wx_descriptions","_component_wx_descriptions_item","_resolveDynamicComponent","STORED_THEME","themeKey","useTheme","theme","provideTheme","createTheme","read","paint","applyTheme","systemTheme","watchSystemTheme","preference","stored","STORED_LOCALE","createAdmin","manifestUrl","readManifestUrl","basePath","modules","landing","AdminLanding","SiteBarScreen","AdminNotFound","createRouter","createWebHistory","preferredLocale","dictionaries","loadDictionary","rememberLocale","markDocumentLanguage","createApp","rootComponent","WebxUI","editing","dateLocaleKey","dateTimezoneKey","localesKey","loadPanelLocales","plugin","h","AdminNav","AdminShell","renderMarkdown","lines","out","escape","level","inline","isItem","items","tag","paragraph","HOLE","spans","withHoles","_","pluralForm","count","form","useBodyKeys","GONE","mergeThreeWay","mine","theirs","choices","walk","merge","changedPaths","before","after","diff","sameValue","isPlain","conflictId","step","isNodeList","mergeList","mergeObject","settle","merged","byKey","ours","other","typeOf","theirsMoved","sameOrder","mineMoved","lead","follow","order","placed","back","position","kind","keepMine","keepTheirs","old","inB","inA","left","right","useEditingLabels","shallowRef","blocks","screenLabels","blockName","humanize","label","codes","inBlock","named","nodes","words","EXPLAINING","useEditing","clone","conflict","incoming","editors","place","events","recent","changedLast","canWrite","trashed","gone","stopped","blocked","trashedBy","unsaved","ready","mounted","opened","beat","take","settleWith","conflicts","refused","clean","pull","choose","choice","resolve","open","resolveTheirs","when","places","deed","detail","mainPath","describe","i","deeds","last","phrase","done","timeless","map","own","beforePublish","held","explained","failure","heardGone","canRestore","restoreFromBin","dismissEvents","purge","texts","copyText","timer","reading","stopWatching","seen","stateKey","me","absorb","ping","newest","fresh","moved","leave","preview","clip","plain","createNotesApi","noteId","shortcodeToken","shortcode","loadShortcodes","forgetShortcodes","useShortcodes","tokens","MIN_CHUNK","STORAGE_KEY","SPEED_WINDOW","uploadFingerprint","file","unfinishedUploads","purpose","storage","defaultStorage","readAll","forgetUnfinishedUpload","fingerprint","writeAll","useChunkedUpload","retries","retryDelay","uploaded","total","speed","progress","remaining","chunk","generation","controller","samples","init","sendWith","promise","reject","finish","result","fail","setOffset","offset","sample","forPurpose","others","forget","stop","start","sync","headerOffset","failures","isOffline","goOffline","wait","pause","resume","cancel","session","onOffline","onOnline","getCurrentScope","onScopeDispose","readonly","ms","categoryEditorKey","provideCategoryEditor","editor","useCategoryEditor","itemOrderMode","useItemOrder","mode","useCategoryWords","word","dismiss","useModal","w","saving","errorOf","submit","WxSpace","WxFormItem","addressed","canManage","create","createModal","CategoryCreateDialog","actions","countOf","made","menuOf","itemsOf","reorder","_component_router_link","WxAddressNote","RowMenu","landed","fade","showing","tree","WxScreenRenderer","snapshot","dirty","localizedValue","save","leaveGuard","onBeforeRouteLeave","leads","ScreenHead","WxBreadcrumb","WxBreadcrumbItem","Screen","WxActionBar","SaveState","bound","defineComponent","categoryRoutes","CategoriesPage","CategoryEditorPage","rowMenuWidth","filter","isOpen","WxPopover","notes","sending","edited","edit","note","saved","actionsFor","WxTextarea","conflictTitle","conflictAlert","incomingText","eventsText","trashedTitle","by","goneTitle","restoring","keep","side","which","takeTheirs","drafts","copy","working","STALE_AFTER_HOURS","backup","visible","stale","taken","bytes","units","unit","warning"],"mappings":"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"}