@webx-ui/module-pages 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1338,7 +1338,7 @@ const Kt = /* @__PURE__ */ K({
1338
1338
  loading: f.value,
1339
1339
  "cards-below": 0,
1340
1340
  "search-placeholder": e(t)("page.search"),
1341
- "empty-text": p.value ? e(t)("page.empty-bin") : i.value ? e(t)("page.empty-search") : e(t)("page.empty"),
1341
+ "empty-text": i.value ? e(t)("page.empty-search") : p.value ? e(t)("page.empty-bin") : e(t)("page.empty"),
1342
1342
  "aria-label": $.value,
1343
1343
  onRowClick: ee,
1344
1344
  onStateChange: de,
@@ -1455,7 +1455,7 @@ const Kt = /* @__PURE__ */ K({
1455
1455
  ]), 1032, ["view", "title", "views"])
1456
1456
  ]));
1457
1457
  }
1458
- }), Qt = /* @__PURE__ */ re(Jt, [["__scopeId", "data-v-6c1123b3"]]);
1458
+ }), Qt = /* @__PURE__ */ re(Jt, [["__scopeId", "data-v-580089b5"]]);
1459
1459
  function ta(u = {}) {
1460
1460
  const r = u.path ?? "/pages";
1461
1461
  return {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/api.ts","../src/editor.ts","../src/messages.ts","../src/i18n.ts","../src/PageDanger.vue","../src/PageEditorPage.vue","../src/PageHistory.vue","../src/PagePicker.vue","../src/PagePlace.vue","../src/PageActions.vue","../src/slug.ts","../src/PageCreateDialog.vue","../src/PageMoveDialog.vue","../src/PagesPage.vue","../src/module.ts"],"sourcesContent":["import type { AdminContext } from '@webx-ui/module-admin'\nimport type {\n PageDetail,\n PageDropZone,\n PageInput,\n PageLevel,\n PageMoveResult,\n PageRow,\n PageSave,\n PageVersion,\n} from './types'\n\nexport interface PagesApi {\n /** One level of the tree, or a flat list when searching or looking in the bin. */\n list(query?: {\n parent?: number | null\n search?: string\n status?: string\n trashed?: boolean\n /** The whole tree in one flat list, in the order it reads top to bottom. */\n flat?: boolean\n }): Promise<PageLevel>\n get(id: number): Promise<PageDetail>\n create(input: PageInput): Promise<PageRow>\n /**\n * Save the draft. Refused with a 409 when the revision is not the current one — the error's\n * body is a {@link PageConflict}, and the page it carries is the page as it now is.\n */\n save(id: number, input: PageSave): Promise<PageDetail>\n move(id: number, target: number, zone: PageDropZone): Promise<PageMoveResult>\n duplicate(id: number): Promise<PageRow>\n publish(id: number): Promise<PageRow>\n unpublish(id: number): Promise<PageRow>\n /** Into the bin, with the branch under it. Answers how many went. */\n remove(id: number): Promise<number>\n /** Out of the bin, with whatever went in with it. Answers how many came back. */\n restore(id: number): Promise<number>\n /** The publications, newest first. */\n versions(id: number): Promise<PageVersion[]>\n /** An old publication becomes the draft; putting it on the site is a separate step. */\n restoreVersion(id: number, number: number): Promise<PageDetail>\n}\n\n/** Everything under `/pages`, below the panel's API path. */\nexport function createPagesApi(admin: AdminContext): PagesApi {\n const base = `${admin.apiPath}/pages`\n const data = <T>(body: { data: T }): T => body.data\n\n return {\n list: (query = {}) => {\n const search = new URLSearchParams()\n\n // Only what was asked for: `parent` left out means the home page's children, and\n // `parent=` would ask for the children of nothing.\n if (query.parent != null) search.set('parent', String(query.parent))\n if (query.search) search.set('search', query.search)\n if (query.status) search.set('status', query.status)\n if (query.trashed) search.set('trashed', '1')\n if (query.flat) search.set('flat', '1')\n\n const suffix = search.size > 0 ? `?${search}` : ''\n\n return admin.http.get<{ data: PageLevel }>(`${base}${suffix}`).then(data)\n },\n get: (id) => admin.http.get<{ data: PageDetail }>(`${base}/${id}`).then(data),\n create: (input) => admin.http.post<{ data: PageRow }>(base, input).then(data),\n save: (id, input) => admin.http.put<{ data: PageDetail }>(`${base}/${id}`, input).then(data),\n move: (id, target, zone) =>\n admin.http.post<{ data: PageMoveResult }>(`${base}/${id}/move`, { target, zone }).then(data),\n duplicate: (id) => admin.http.post<{ data: PageRow }>(`${base}/${id}/duplicate`, {}).then(data),\n publish: (id) => admin.http.post<{ data: PageRow }>(`${base}/${id}/publish`, {}).then(data),\n unpublish: (id) => admin.http.post<{ data: PageRow }>(`${base}/${id}/unpublish`, {}).then(data),\n remove: (id) =>\n admin.http\n .delete<{ data: { trashed: number } }>(`${base}/${id}`)\n .then((body) => body.data.trashed),\n restore: (id) =>\n admin.http\n .post<{ data: { restored: number } }>(`${base}/${id}/restore`, {})\n .then((body) => body.data.restored),\n versions: (id) => admin.http.get<{ data: PageVersion[] }>(`${base}/${id}/versions`).then(data),\n restoreVersion: (id, number) =>\n admin.http\n .post<{ data: PageDetail }>(`${base}/${id}/versions/${number}/restore`, {})\n .then(data),\n }\n}\n","import { inject, provide, type InjectionKey, type Ref } from 'vue'\nimport type { ScreenModel } from '@webx-ui/schema'\nimport type { PageRow } from './types'\n\n/**\n * What the editor knows and the nodes of its screen do not.\n *\n * The form is a described screen (`pages.form`), so the parts of it that are not fields —\n * where the page sits, what may be done to it, what its history is — arrive as node types of\n * their own. Each of those needs the page being edited, and none of them can be handed it\n * through the description: a screen is a description, not a binding. So the page that hosts\n * the screen provides it, the same way it provides the preview to `wx-blocks`.\n */\nexport interface PageEditorContext {\n /** The page as the server last answered it, or `null` while the first request is in flight. */\n page: Ref<PageRow | null>\n /** The trail above it, home page first. */\n ancestors: Ref<PageRow[]>\n /**\n * The values of the screen as they are right now, edits included.\n *\n * A node that only draws still has to follow what is being typed: the address is a sentence\n * made of the slug field, and one that updated on save would be showing the old address at\n * the moment the new one is most worth reading.\n */\n values: Ref<ScreenModel>\n /** The address of the page above, by content language. */\n prefixes: Ref<Record<string, string>>\n /** Where the section lives, for the links the nodes draw. */\n base: string\n /** Whether this administrator may write at all. */\n canManage: boolean\n /** Ask the server for the page again — after a move, a restore, a publication. */\n reload(): Promise<void>\n /** Write the draft now rather than at the end of the next pause. */\n save(): Promise<void>\n}\n\nexport const pageEditorKey: InjectionKey<PageEditorContext> = Symbol('wx-page-editor')\n\nexport function providePageEditor(editor: PageEditorContext): void {\n provide(pageEditorKey, editor)\n}\n\n/**\n * The editor above this node, or `null` outside one — a node of `pages.form` drawn in a demo\n * or a test has no page to talk about, and must draw nothing rather than throw.\n */\nexport function usePageEditor(): PageEditorContext | null {\n return inject(pageEditorKey, null)\n}\n","import type { Messages } from '@webx-ui/module-admin'\n\n/**\n * The English this package speaks on its own, before the server's dictionary arrives, or\n * without one. The server ships the same lines in ten languages under `webx-pages::` and\n * overrides these; `messages.test.ts` keeps the two sets of keys equal.\n */\nexport const pagesMessages: Record<string, Messages> = {\n module: {\n title: 'Pages',\n },\n pages: {\n home: 'Home',\n },\n page: {\n new: 'New page',\n search: 'Search by title or address',\n empty: 'No pages yet.',\n 'empty-help': 'A site is its pages. The first one goes inside the home page.',\n 'empty-search': 'Nothing matches that.',\n 'empty-bin': 'The bin is empty.',\n\n 'column-title': 'Title',\n 'column-address': 'Address',\n 'column-status': 'Status',\n 'column-updated': 'Updated',\n 'column-deleted': 'Deleted',\n\n 'status-draft': 'Draft',\n 'status-published': 'Published',\n 'status-modified': 'Edited',\n 'no-address': 'No address in this language',\n\n 'filter-all': 'All',\n 'filter-draft': 'Drafts',\n 'filter-published': 'Published',\n 'filter-modified': 'Edited',\n bin: 'Bin',\n\n open: 'Open',\n 'add-child': 'Add a page inside',\n duplicate: 'Duplicate',\n move: 'Move…',\n 'copy-address': 'Copy the address',\n 'open-on-site': 'Open on the site',\n delete: 'Delete',\n restore: 'Restore',\n cancel: 'Cancel',\n\n 'new-title': 'New page',\n 'field-title': 'Title',\n 'field-slug': 'Address',\n 'slug-help': 'The last part of the address. Left empty, it is made from the title.',\n 'field-parent': 'Inside',\n create: 'Create and open',\n\n 'move-title': 'Move “:title”',\n 'move-help':\n 'Pick the page it goes inside. Everything under it moves too, and their addresses change with it.',\n 'move-confirm': 'Move',\n 'move-branch':\n ':count addresses change — the page and everything inside it. The old ones will redirect to the new ones.',\n moved: 'Moved. :count addresses changed; the old ones now redirect to the new ones.',\n 'moved-one': 'Moved. The address changed; the old one now redirects to it.',\n\n 'delete-title': 'Delete “:title”?',\n 'delete-text': 'The page goes to the bin and stops answering on the site.',\n 'delete-branch': 'The :count pages inside it go with it.',\n deleted: 'The page is in the bin.',\n restored: 'The page is back.',\n 'restore-page-title': 'Bring “:title” back?',\n 'restore-page-text':\n 'The page leaves the bin and takes its address back. One that was on the site answers again.',\n 'restore-branch': 'The :count pages that went in with it come back too.',\n 'restored-branch': ':count pages are back.',\n\n 'copy-of': ':title (copy)',\n duplicated: 'The copy is ready, and it is not on the site.',\n 'address-copied': 'The address is on the clipboard.',\n\n // The editor.\n trail: 'Where this page sits',\n untitled: 'Untitled',\n 'state-saved': 'Saved',\n 'state-saving': 'Saving…',\n 'state-unsaved': 'Not saved yet',\n save: 'Save',\n 'save-failed': 'The page was not saved.',\n publish: 'Publish',\n 'publish-title': 'Put “:title” on the site?',\n 'publish-text': 'It answers at :address from the moment you do, for everyone.',\n 'publish-text-nowhere': 'It starts answering on the site from the moment you do, for everyone.',\n published: 'The page is on the site.',\n preview: 'Preview',\n more: 'More',\n address: 'Address',\n 'parent-help': 'The page it sits inside. A move happens at once, not on publication.',\n 'parent-warning':\n 'Everything inside this page moves with it. The old addresses keep working and lead to the new ones.',\n danger: 'Off the site',\n unpublish: 'Unpublish',\n 'unpublish-title': 'Take “:title” off the site?',\n 'unpublish-text':\n 'The page stops answering. Nothing written is lost, and publishing puts it back.',\n 'unpublish-help': 'The page stops answering on the site. What is written stays.',\n unpublished: 'The page is off the site.',\n 'delete-help': 'The page goes to the bin, with everything inside it.',\n 'delete-home': 'The home page cannot be deleted.',\n 'conflict-title': 'The page changed while you were editing',\n 'conflict-theirs': 'Take the newer version',\n 'conflict-theirs-title': 'Give up what you wrote?',\n 'conflict-theirs-text':\n 'The page is read again as it now is, and what you have typed since goes.',\n 'conflict-mine': 'Keep mine',\n 'leave-title': 'Leave without saving?',\n 'leave-text': 'The page could not be saved, and what you wrote is not on the server.',\n leave: 'Leave',\n 'history-empty': 'This page has never been published.',\n version: '#:number',\n 'version-live': 'On the site',\n 'source-panel': 'From the panel',\n 'source-mcp': 'By an agent',\n 'source-import': 'Imported',\n 'restore-title': 'Restore version :number?',\n 'restore-text':\n 'It becomes the draft. The site keeps showing what is published until you publish this.',\n 'restored-version': 'Version :number is now the draft.',\n },\n}\n","import { useAdmin } from '@webx-ui/module-admin'\nimport { pagesMessages } from './messages'\n\nconst seeded = new WeakSet<object>()\n\n/** Puts this package's English under the panel's dictionary, once per panel. */\nexport function usePagesMessages(): void {\n try {\n const { i18n } = useAdmin()\n\n if (!seeded.has(i18n)) {\n i18n.defaults('webx-pages', pagesMessages)\n seeded.add(i18n)\n }\n } catch {\n // Outside a panel there is no dictionary to seed, and `useTranslate` falls back on its own.\n }\n}\n","<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\nimport { useRouter } from 'vue-router'\nimport { useAdmin, useErrorText, useTranslate } from '@webx-ui/module-admin'\nimport { confirm, toast, WxButton, WxCard, WxText } from '@webx-ui/core'\nimport { createPagesApi } from './api'\nimport { usePageEditor } from './editor'\nimport { usePagesMessages } from './i18n'\n\n/**\n * The two ways a page leaves the site.\n *\n * At the bottom of the settings tab rather than in the head, because both are the opposite of\n * what the head is for: publishing is the thing being aimed at all day, and a button that\n * takes the page down should not be next to it by the width of a finger.\n */\ndefineOptions({ name: 'WxPageDanger' })\n\nconst context = useAdmin()\nconst api = createPagesApi(context)\nconst router = useRouter()\nconst editor = usePageEditor()\nusePagesMessages()\n\nconst t = useTranslate('webx-pages')\n/* Not the server's `message`: the panel says how a request failed in its own words (§13.3). */\nconst message = useErrorText()\n\nconst working = ref(false)\n\nconst page = computed(() => editor?.page.value ?? null)\nconst canManage = computed(() => context.can('pages.manage'))\n\nasync function unpublish(): Promise<void> {\n const current = page.value\n\n if (!current) return\n\n const agreed = await confirm({\n title: t('page.unpublish-title', { title: current.title }),\n message: t('page.unpublish-text'),\n confirmText: t('page.unpublish'),\n cancelText: t('page.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n working.value = true\n\n try {\n await api.unpublish(current.id)\n await editor?.reload()\n toast.success(t('page.unpublished'))\n } catch (error) {\n toast.danger(message(error))\n } finally {\n working.value = false\n }\n}\n\nasync function remove(): Promise<void> {\n const current = page.value\n\n if (!current) return\n\n const agreed = await confirm({\n title: t('page.delete-title', { title: current.title }),\n // The whole branch, not the level under it (§7): a catalogue with two sections and forty\n // products is forty-two pages leaving the site at once.\n message:\n current.descendants_count > 0\n ? `${t('page.delete-text')} ${t('page.delete-branch', { count: current.descendants_count })}`\n : t('page.delete-text'),\n confirmText: t('page.delete'),\n cancelText: t('page.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n working.value = true\n\n try {\n await api.remove(current.id)\n toast.success(t('page.deleted'))\n void router.push(editor?.base ?? '/pages')\n } catch (error) {\n toast.danger(message(error))\n } finally {\n working.value = false\n }\n}\n</script>\n\n<template>\n <wx-card v-if=\"page && canManage\" :title=\"t('page.danger')\">\n <div class=\"wx-page-danger\">\n <div v-if=\"page.status !== 'draft'\" class=\"wx-page-danger__row\">\n <div class=\"wx-page-danger__what\">\n <wx-text weight=\"medium\">{{ t('page.unpublish') }}</wx-text>\n <wx-text size=\"sm\" tone=\"muted\">{{ t('page.unpublish-help') }}</wx-text>\n </div>\n <wx-button variant=\"outline\" :loading=\"working\" @click=\"unpublish\">\n {{ t('page.unpublish') }}\n </wx-button>\n </div>\n\n <div class=\"wx-page-danger__row\">\n <div class=\"wx-page-danger__what\">\n <wx-text weight=\"medium\">{{ t('page.delete') }}</wx-text>\n <wx-text size=\"sm\" tone=\"muted\">{{\n page.can.delete ? t('page.delete-help') : t('page.delete-home')\n }}</wx-text>\n </div>\n <wx-button\n type=\"danger\"\n variant=\"outline\"\n :disabled=\"!page.can.delete\"\n :loading=\"working\"\n @click=\"remove\"\n >\n {{ t('page.delete') }}\n </wx-button>\n </div>\n </div>\n </wx-card>\n</template>\n\n<style scoped>\n.wx-page-danger {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-12);\n}\n\n.wx-page-danger__row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--wx-space-12);\n flex-wrap: wrap;\n}\n\n.wx-page-danger__what {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-2);\n min-width: 200px;\n flex: 1;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, onMounted, ref, useTemplateRef, watch } from 'vue'\nimport { onBeforeRouteLeave, useRoute, useRouter } from 'vue-router'\nimport { useAdmin, useErrorText, useTranslate, WxBackButton, WxScreen } from '@webx-ui/module-admin'\nimport { provideBlocksPreview } from '@webx-ui/module-blocks'\nimport {\n confirm,\n localizedValue,\n toast,\n useElementWidth,\n useLocales,\n WxAction,\n WxActionBar,\n WxAlert,\n WxBadge,\n WxBreadcrumb,\n WxBreadcrumbItem,\n WxButton,\n WxDropdown,\n WxDropdownItem,\n WxSkeleton,\n WxText,\n type LocalizedValue,\n} from '@webx-ui/core'\nimport type { ScreenModel } from '@webx-ui/schema'\nimport { createPagesApi } from './api'\nimport { providePageEditor } from './editor'\nimport { usePagesMessages } from './i18n'\nimport type { PageConflict, PageDetail, PageRow } from './types'\n\n/**\n * The editor of one page: a head that stays put, and the described screen under it.\n *\n * What the page itself owns is the head — where this page sits, what state it is in, and the\n * way out to the site — and the bar along the bottom, which is where the page is saved and\n * published. Everything between them is `pages.form`, so a module or a project adds a tab to\n * the editor with a patch (§12) rather than with a fork of this file.\n *\n * Saving is by autosave: a pause after the last keystroke, and the moment a field is left. The\n * explicit button is still there, because a button that says \"saved\" is the only way a person\n * can be sure — but nothing is ever lost by not pressing it.\n */\nconst props = withDefaults(defineProps<{ base?: string }>(), { base: '/pages' })\n\nconst context = useAdmin()\nconst api = createPagesApi(context)\nconst route = useRoute()\nconst router = useRouter()\nconst locales = useLocales()\nusePagesMessages()\n\nconst t = useTranslate('webx-pages')\n/* Not the server's `message`: the panel says how a request failed in its own words (§13.3). */\nconst message = useErrorText()\n\n/** How long after the last keystroke the draft goes to the server. */\nconst PAUSE = 1200\n\nconst id = computed(() => Number(route.params.id))\n\nconst page = ref<PageRow | null>(null)\nconst ancestors = ref<PageRow[]>([])\nconst values = ref<ScreenModel>({})\nconst revision = ref('')\nconst previewUrl = ref<string | null>(null)\nconst prefixes = ref<Record<string, string>>({})\n\nconst loading = ref(true)\nconst saving = ref(false)\nconst working = ref(false)\nconst errors = ref<Record<string, string[]>>({})\nconst conflict = ref<PageConflict | null>(null)\n\nconst snapshot = ref('')\nconst reloadToken = ref(0)\n\nconst canManage = computed(() => context.can('pages.manage'))\n\n/*\n * Where the head stops being a row and becomes three. Decided by the width of the panel rather\n * than of the window, the same way the section's list decides between a table and cards.\n */\nconst NARROW = 720\n\nconst root = useTemplateRef<HTMLElement>('root')\nconst width = useElementWidth(root)\nconst narrow = computed(() => width.value > 0 && width.value < NARROW)\n\nconst current = computed(() => JSON.stringify(values.value))\nconst dirty = computed(() => snapshot.value !== '' && current.value !== snapshot.value)\n\n/** Saved · saving · not saved yet — the one line of the head that moves while typing. */\nconst state = computed<'saving' | 'unsaved' | 'saved'>(() => {\n if (saving.value) return 'saving'\n\n return dirty.value ? 'unsaved' : 'saved'\n})\n\n/**\n * The name in the head follows the field rather than the answer: an editor who renamed the\n * page in the settings tab should see the new name at the top before the save lands.\n */\nconst title = computed(() => {\n const written = values.value.title as LocalizedValue | string | null | undefined\n\n return localizedValue(written, locales.active.value, page.value?.title ?? '')\n})\n\nconst trail = computed(() =>\n ancestors.value.map((node) => ({\n id: node.id,\n label: node.is_home ? t('pages.home') : node.title,\n })),\n)\n\n/* The constructor shows the draft as a page of the site. The link is the editor's to hand\n over: only this screen knows which entity it is editing. */\nprovideBlocksPreview({ url: computed(() => previewUrl.value), reload: reloadToken })\n\nprovidePageEditor({\n page,\n ancestors,\n values,\n prefixes,\n base: props.base,\n canManage: canManage.value,\n reload: () => load(true),\n save: () => save(),\n})\n\nfunction take(detail: PageDetail): void {\n page.value = detail.page\n ancestors.value = detail.ancestors\n values.value = detail.values\n revision.value = detail.revision\n previewUrl.value = detail.preview_url\n prefixes.value = detail.address_prefix\n snapshot.value = JSON.stringify(detail.values)\n conflict.value = null\n}\n\n/**\n * Read the page again.\n *\n * Quietly unless this is the first time: the skeleton replaces the whole screen, tabs and all,\n * so a publication answered with it would throw an editor who published from the history back\n * onto the first tab — and flash the page on the way.\n */\nasync function load(silent = false): Promise<void> {\n if (!silent) loading.value = true\n\n try {\n take(await api.get(id.value))\n } catch (error) {\n toast.danger(message(error))\n void router.push(props.base)\n } finally {\n loading.value = false\n }\n}\n\n/* One pending save at a time, and one pending pause. */\nlet timer: ReturnType<typeof setTimeout> | undefined\n\nfunction schedule(): void {\n if (!canManage.value || conflict.value || !dirty.value) return\n\n clearTimeout(timer)\n timer = setTimeout(() => void save(), PAUSE)\n}\n\n/** A field was left: write now rather than at the end of a pause nobody is waiting through. */\nfunction onFocusOut(): void {\n if (!canManage.value || conflict.value || !dirty.value || saving.value) return\n\n clearTimeout(timer)\n void save()\n}\n\nasync function save(): Promise<void> {\n if (!page.value || saving.value || !canManage.value) return\n\n clearTimeout(timer)\n\n // What is being sent, so that whatever is typed while the request is in flight stays dirty\n // and gets its own save rather than being marked as written.\n const sending = current.value\n const sent = values.value\n\n saving.value = true\n errors.value = {}\n\n try {\n const detail = await api.save(page.value.id, { values: sent, revision: revision.value })\n\n page.value = detail.page\n ancestors.value = detail.ancestors\n revision.value = detail.revision\n previewUrl.value = detail.preview_url\n prefixes.value = detail.address_prefix\n conflict.value = null\n\n if (current.value === sending) snapshot.value = sending\n\n // The preview is rendered from the draft, and the draft is what was just written.\n reloadToken.value += 1\n } catch (error) {\n const failure = error as {\n status?: number\n body?: PageConflict & { errors?: Record<string, string[]> }\n }\n\n if (failure.status === 409 && failure.body?.data) {\n conflict.value = failure.body\n } else if (failure.body?.errors) {\n errors.value = failure.body.errors\n toast.danger(t('page.save-failed'))\n } else {\n toast.danger(message(error))\n }\n } finally {\n saving.value = false\n }\n}\n\n/** Give up what was typed and take the page as it now is (§6). */\nasync function takeTheirs(): Promise<void> {\n const agreed = await confirm({\n title: t('page.conflict-theirs-title'),\n message: t('page.conflict-theirs-text'),\n confirmText: t('page.conflict-theirs'),\n cancelText: t('page.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n await load(true)\n}\n\n/**\n * Keep what was typed and write it over the other version.\n *\n * Nothing is lost by it: the version that is about to be overwritten is in the history if it\n * was published, and in the autosave ring if it was not.\n */\nasync function keepMine(): Promise<void> {\n const theirs = conflict.value\n\n if (!theirs) return\n\n revision.value = theirs.data.revision\n conflict.value = null\n\n await save()\n}\n\n/**\n * Publishing is asked about, because taking a page off the site is (§14.2).\n *\n * It is the one action here that visitors see: everything else on this screen writes a draft\n * nobody outside the panel can read. The question names the address rather than counting\n * anything — one page goes on the site, and what matters is where.\n */\nasync function publish(): Promise<void> {\n if (!page.value) return\n\n const address = page.value.path === null ? null : `/${page.value.path}`\n\n const agreed = await confirm({\n title: t('page.publish-title', { title: page.value.title }),\n message:\n address === null ? t('page.publish-text-nowhere') : t('page.publish-text', { address }),\n confirmText: t('page.publish'),\n cancelText: t('page.cancel'),\n })\n\n if (!agreed) return\n\n if (dirty.value) await save()\n if (conflict.value) return\n\n working.value = true\n\n try {\n await api.publish(page.value.id)\n await load(true)\n toast.success(t('page.published'))\n } catch (error) {\n toast.danger(message(error))\n } finally {\n working.value = false\n }\n}\n\nfunction badge(): 'default' | 'success' | 'warning' {\n if (page.value?.status === 'published') return 'success'\n\n return page.value?.status === 'modified' ? 'warning' : 'default'\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\nwatch(current, schedule)\nwatch(id, () => void load())\n\nonMounted(() => {\n void load()\n window.addEventListener('beforeunload', leaveGuard)\n})\n\nonBeforeUnmount(() => {\n clearTimeout(timer)\n window.removeEventListener('beforeunload', leaveGuard)\n})\n\n/**\n * Leaving flushes the pause rather than asking about it: everything else on this screen saves\n * by itself, and a dialog that appears only because somebody left within a second of typing\n * would be the one place that does not. The question is asked only when the save did not go\n * through — a conflict, a refused field, a server that is not there.\n */\nonBeforeRouteLeave(async () => {\n if (!dirty.value || !canManage.value) return true\n\n await save()\n\n if (!dirty.value) return true\n\n return await confirm({\n title: t('page.leave-title'),\n message: t('page.leave-text'),\n confirmText: t('page.leave'),\n cancelText: t('page.cancel'),\n tone: 'danger',\n })\n})\n</script>\n\n<template>\n <!-- No `data-wx-fill`: this screen is a window tall only while the constructor is the tab\n on screen, and that is a question only CSS can ask (§10). `WxMain` still gives it a\n floor of one window, because it carries an action bar. -->\n <div ref=\"root\" class=\"wx-page-editor\" @focusout=\"onFocusOut\">\n <template v-if=\"loading || !page\">\n <wx-skeleton class=\"wx-page-editor__ghost\" title :rows=\"1\" />\n <wx-skeleton :rows=\"8\" />\n </template>\n\n <template v-else>\n <div class=\"wx-page-editor__head\">\n <!-- The trail says where the reader is; this is the way out of it, and on a phone it\n is the only one that is a control rather than four words in the smallest type. -->\n <wx-back-button class=\"wx-page-editor__back\" :to=\"base\" :label=\"t('module.title')\" />\n\n <div class=\"wx-page-editor__id\">\n <wx-breadcrumb size=\"sm\" :label=\"t('page.trail')\">\n <wx-breadcrumb-item :as=\"'router-link'\" :to=\"base\">\n {{ t('module.title') }}\n </wx-breadcrumb-item>\n <wx-breadcrumb-item\n v-for=\"node in trail\"\n :key=\"node.id\"\n :as=\"'router-link'\"\n :to=\"`${base}/${node.id}`\"\n >\n {{ node.label }}\n </wx-breadcrumb-item>\n <wx-breadcrumb-item current>\n {{ page.is_home ? t('pages.home') : title }}\n </wx-breadcrumb-item>\n </wx-breadcrumb>\n\n <div class=\"wx-page-editor__name\">\n <span class=\"wx-page-editor__title\">{{\n page.is_home ? t('pages.home') : title || t('page.untitled')\n }}</span>\n <wx-badge :type=\"badge()\" dot>{{ t(`page.status-${page.status}`) }}</wx-badge>\n </div>\n </div>\n\n <!--\n What is left in the head is what leads away from the page: the draft on the site and\n the page on the site. Saving and publishing are down in the bar, where the eye is —\n and only there, because this screen is exactly as tall as the window and the head\n never leaves it. A second copy of a button already on screen is not a reminder.\n -->\n <div class=\"wx-page-editor__actions\">\n <!-- Narrow: the two links fold into a menu. A head three rows tall is a fifth of a\n phone screen given to buttons above the thing being edited. -->\n <wx-dropdown v-if=\"narrow\" align=\"end\">\n <template #trigger>\n <wx-action type=\"more\" :title=\"t('page.more')\" />\n </template>\n <wx-dropdown-item\n v-if=\"previewUrl\"\n icon=\"eye\"\n :href=\"previewUrl\"\n target=\"_blank\"\n rel=\"noopener\"\n >\n {{ t('page.preview') }}\n </wx-dropdown-item>\n <wx-dropdown-item\n v-if=\"page.url && page.status !== 'draft'\"\n icon=\"link\"\n :href=\"page.url\"\n target=\"_blank\"\n rel=\"noopener\"\n >\n {{ t('page.open-on-site') }}\n </wx-dropdown-item>\n </wx-dropdown>\n\n <template v-else>\n <wx-button\n v-if=\"previewUrl\"\n variant=\"outline\"\n icon=\"eye\"\n :href=\"previewUrl\"\n target=\"_blank\"\n rel=\"noopener\"\n >\n {{ t('page.preview') }}\n </wx-button>\n <wx-button\n v-if=\"page.url && page.status !== 'draft'\"\n variant=\"outline\"\n icon=\"link\"\n :href=\"page.url\"\n target=\"_blank\"\n rel=\"noopener\"\n >\n {{ t('page.open-on-site') }}\n </wx-button>\n </template>\n </div>\n </div>\n\n <!-- Somebody else wrote while this editor was typing. Both versions still exist, so the\n question is which one the site gets — and it is a question, not a toast that\n disappears while the answer is being thought about. -->\n <wx-alert\n v-if=\"conflict\"\n type=\"warning\"\n :title=\"t('page.conflict-title')\"\n :description=\"conflict.message\"\n >\n <template #actions>\n <wx-button size=\"sm\" variant=\"outline\" @click=\"takeTheirs\">\n {{ t('page.conflict-theirs') }}\n </wx-button>\n <wx-button size=\"sm\" type=\"primary\" @click=\"keepMine\">\n {{ t('page.conflict-mine') }}\n </wx-button>\n </template>\n </wx-alert>\n\n <div class=\"wx-page-editor__screen\">\n <wx-screen\n v-model=\"values\"\n name=\"pages.form\"\n :errors=\"errors\"\n :disabled=\"!canManage || working\"\n />\n </div>\n\n <!--\n The last row of the screen, not a layer over it: the preview above shrinks by the height\n of the bar and is never covered by it. Nothing scrolls on this screen, so the bar never\n has to stick to anything — it is already where sticking would put it.\n -->\n <wx-action-bar v-if=\"canManage\">\n <template #state>\n <wx-text size=\"sm\" tone=\"muted\">{{ t(`page.state-${state}`) }}</wx-text>\n </template>\n\n <wx-button variant=\"outline\" :loading=\"saving\" :disabled=\"!dirty\" @click=\"save\">\n {{ t('page.save') }}\n </wx-button>\n\n <!-- Only the forward action is here; taking a page off the site and deleting it live\n in the settings tab, where nothing is one slip away from the save button. -->\n <wx-button\n type=\"primary\"\n :loading=\"working\"\n :disabled=\"page.status === 'published' && !dirty\"\n @click=\"publish\"\n >\n {{ t('page.publish') }}\n </wx-button>\n </wx-action-bar>\n </template>\n </div>\n</template>\n\n<style scoped>\n.wx-page-editor {\n display: flex;\n flex-direction: column;\n gap: var(--wx-gap, var(--wx-space-16));\n /* The constructor is the whole screen below the head, so the page is as tall as what it\n is drawn in and never taller: the tree, the form and the preview scroll inside\n themselves, and the head stays where it was put (§10). */\n height: 100%;\n min-height: 0;\n container-type: inline-size;\n}\n\n.wx-page-editor__ghost {\n max-width: 420px;\n}\n\n/*\n * The head stays put by standing still: the screen is as tall as its column, so nothing under\n * it scrolls the page — each tab scrolls inside itself. A sticky bar would be a bar that never\n * has anything to stick to.\n */\n/*\n * Three columns: the way out, what the page is called, and what leads away from it.\n *\n * A grid rather than the flex row this was: as flex items the three parts were sized from\n * their own content, and on a phone the menu — one 30px button — claimed 225px of a 359px head\n * while the name was squeezed to 81 and wrapped away from its badge.\n *\n * Everything sits on the bottom line, which is the name. The trail above it is the smaller\n * type, and a button lined up with that hangs over the title.\n */\n.wx-page-editor__head {\n flex: none;\n display: grid;\n grid-template-columns: auto minmax(0, 1fr) auto;\n align-items: end;\n gap: var(--wx-space-12);\n padding-block-end: var(--wx-space-12);\n border-block-end: 1px solid var(--wx-border-default);\n}\n\n/* The arrow belongs to the trail, which is the top line. */\n.wx-page-editor__back {\n align-self: start;\n}\n\n.wx-page-editor__id {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-4);\n flex: 1 1 260px;\n min-width: 0;\n}\n\n.wx-page-editor__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-page-editor__title {\n font-size: var(--wx-font-size-xl);\n font-weight: var(--wx-font-weight-bold);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.wx-page-editor__actions {\n display: flex;\n align-items: center;\n gap: var(--wx-space-8);\n flex-wrap: wrap;\n}\n\n.wx-page-editor__screen {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-height: 0;\n}\n\n/*\n * The tabs are the screen's, not this page's, so the height has to travel through them: every\n * box between the page and the constructor is a flex column that may shrink, or the\n * constructor sizes itself to its content and the whole page scrolls with the head on top of\n * it — which is the one thing §10 is about.\n */\n.wx-page-editor__screen :deep(.wx-screen-host),\n.wx-page-editor__screen :deep(.wx-screen),\n.wx-page-editor__screen :deep(.wx-tabs),\n.wx-page-editor__screen :deep(.wx-tabs__layout),\n.wx-page-editor__screen :deep(.wx-tabs__panels),\n.wx-page-editor__screen :deep(.wx-tab:not([hidden])) {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-height: 0;\n}\n\n/*\n * The tab that is not shown keeps its `display: none`. Said out loud because the rule above\n * would take it away: the tabs are kept alive, so the three that are hidden are still in the\n * document, and a `display: flex` that reaches them makes each one a quarter of the height the\n * one on screen should have had. Measured — a constructor 181px tall in a 740px column.\n */\n\n/*\n * Only the tab that holds the constructor is a box of a fixed height with its own scrollbar.\n *\n * Everywhere else the tab grows with its content and the page scrolls, because a scroll box\n * clips: the cards inside one had their shadows cut off square at all four edges, which reads\n * as a drawing fault rather than as a scrolling region. The screen is exactly a window tall\n * for the same reason and under the same condition — `WxMain` keeps a screen with an action\n * bar at least that tall anyway, which is what holds the bar at the bottom of a short tab.\n */\n.wx-page-editor:has(.wx-tab:not([hidden]) .wx-blocks-host.is-fill) {\n height: var(--wx-fill-height);\n}\n\n.wx-page-editor__screen :deep(.wx-tab:not([hidden]):has(.wx-blocks-host.is-fill)) {\n overflow: auto;\n}\n\n/*\n * The renderer wraps every field in a form item, so the height has to travel through that as\n * well — and through that one only. `:has()` picks the wrapper of a field that fills and leaves\n * the ordinary fields of the settings tab the height of their own contents.\n */\n.wx-page-editor__screen :deep(.wx-form-item:has(.wx-blocks-host.is-fill)),\n.wx-page-editor__screen :deep(.wx-form-item:has(.wx-blocks-host.is-fill) .wx-form-item__control) {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-height: 0;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\nimport { useAdmin, useErrorText, useTranslate, WxDate } from '@webx-ui/module-admin'\nimport { confirm, toast, WxBadge, WxButton, WxEmpty, WxSkeleton, WxText } from '@webx-ui/core'\nimport { createPagesApi } from './api'\nimport { usePageEditor } from './editor'\nimport { usePagesMessages } from './i18n'\nimport type { PageVersion } from './types'\n\n/**\n * What was published, when, by whom and from where.\n *\n * Publications only. The autosaves a save writes are insurance rather than history — a ring of\n * the last few copies of the draft, replaced every couple of minutes — and a list with them in\n * it would be a list nobody can read.\n *\n * Restoring makes the old version the draft. Publishing it is the same separate step it always\n * is, which is what keeps the history a line: nothing here changes the site by itself.\n */\ndefineOptions({ name: 'WxPageHistory' })\n\nconst context = useAdmin()\nconst api = createPagesApi(context)\nconst editor = usePageEditor()\nusePagesMessages()\n\nconst t = useTranslate('webx-pages')\n/* Not the server's `message`: the panel says how a request failed in its own words (§13.3). */\nconst message = useErrorText()\n\nconst versions = ref<PageVersion[] | null>(null)\nconst working = ref(false)\n\nconst page = computed(() => editor?.page.value ?? null)\nconst canManage = computed(() => context.can('pages.manage'))\n\n/**\n * Which line is the site: the newest publication, and only while the page is on the site at\n * all. A page taken down still has its history, and none of it is live.\n */\nconst live = computed(() =>\n page.value?.published_at ? (versions.value?.[0]?.number ?? null) : null,\n)\n\nasync function load(): Promise<void> {\n const current = page.value\n\n if (!current) return\n\n try {\n versions.value = await api.versions(current.id)\n } catch (error) {\n versions.value = []\n toast.danger(message(error))\n }\n}\n\nasync function restore(version: PageVersion): Promise<void> {\n const current = page.value\n\n if (!current) return\n\n const agreed = await confirm({\n title: t('page.restore-title', { number: version.number }),\n message: t('page.restore-text'),\n confirmText: t('page.restore'),\n cancelText: t('page.cancel'),\n })\n\n if (!agreed) return\n\n working.value = true\n\n try {\n await api.restoreVersion(current.id, version.number)\n // Through the editor rather than with the answer: the values it holds are what the form is\n // bound to, and a version restored underneath it has to reach the fields.\n await editor?.reload()\n toast.success(t('page.restored-version', { number: version.number }))\n } catch (error) {\n toast.danger(message(error))\n } finally {\n working.value = false\n }\n}\n\n// The page is reloaded after every publication and every restore, so its stamp is the signal\n// that there is something new to list.\nwatch(\n () => [page.value?.id, page.value?.published_at, page.value?.updated_at],\n () => void load(),\n {\n immediate: true,\n },\n)\n</script>\n\n<template>\n <div class=\"wx-page-history\">\n <!-- Where a row would stand, not against the border: a skeleton flush with a rounded\n corner has its ends clipped by it, which reads as a drawing fault rather than as\n something loading. -->\n <wx-skeleton v-if=\"versions === null\" class=\"wx-page-history__ghost\" :rows=\"3\" />\n <wx-empty v-else-if=\"versions.length === 0\" :description=\"t('page.history-empty')\" />\n <div\n v-for=\"version in versions\"\n v-else\n :key=\"version.number\"\n class=\"wx-page-history__row\"\n :class=\"{ 'is-live': version.number === live }\"\n >\n <code class=\"wx-page-history__number\">{{\n t('page.version', { number: version.number })\n }}</code>\n <div class=\"wx-page-history__who\">\n <wx-date v-if=\"version.created_at\" :value=\"version.created_at\" size=\"md\" tone=\"default\" />\n <wx-text size=\"sm\" tone=\"muted\">\n {{ version.author ?? t(`page.source-${version.source}`) }}\n <template v-if=\"version.comment\"> · {{ version.comment }}</template>\n </wx-text>\n </div>\n <wx-badge v-if=\"version.number === live\" type=\"success\" dot>{{\n t('page.version-live')\n }}</wx-badge>\n <wx-button\n v-if=\"canManage && version.number !== live\"\n size=\"sm\"\n variant=\"outline\"\n :loading=\"working\"\n @click=\"restore(version)\"\n >\n {{ t('page.restore') }}\n </wx-button>\n </div>\n </div>\n</template>\n\n<style scoped>\n.wx-page-history {\n display: flex;\n flex-direction: column;\n border: 1px solid var(--wx-border-default);\n border-radius: var(--wx-radius-md);\n background: var(--wx-bg-surface);\n /* Nothing reaches past the rounding: a row, or the skeleton standing in for three of them,\n is a square box and its corners showed through the panel's. */\n overflow: hidden;\n}\n\n.wx-page-history__ghost {\n padding: var(--wx-space-10) var(--wx-space-12);\n}\n\n.wx-page-history__row {\n display: flex;\n align-items: center;\n gap: var(--wx-space-12);\n padding: var(--wx-space-10) var(--wx-space-12);\n border-block-end: 1px solid var(--wx-border-default);\n flex-wrap: wrap;\n}\n\n.wx-page-history__row:last-child {\n border-block-end: 0;\n}\n\n.wx-page-history__number {\n width: 40px;\n font-family: var(--wx-font-family-mono);\n font-size: var(--wx-font-size-sm);\n color: var(--wx-text-muted);\n}\n\n.wx-page-history__who {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-width: 160px;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { onMounted, ref } from 'vue'\nimport { useAdmin, useTranslate } from '@webx-ui/module-admin'\nimport { WxTreeSelect, type TreeNode } from '@webx-ui/core'\nimport { createPagesApi } from './api'\nimport { usePagesMessages } from './i18n'\nimport type { PageRow } from './types'\n\n/**\n * A page chosen out of the tree — where a new page goes, or where a moved one lands.\n *\n * Lazy, like the section's own list: a catalogue is fetched a level at a time, and a picker\n * that pulled the whole tree down to offer one parent would be the one screen of the section\n * that does not.\n */\nconst props = withDefaults(\n defineProps<{\n /** Pages that may not be chosen — the page being moved, above all. */\n exclude?: number[]\n placeholder?: string\n /** The page the value names, with its ancestors, so a lazy tree can show it unopened. */\n selected?: PageRow[]\n }>(),\n { exclude: () => [], placeholder: undefined, selected: () => [] },\n)\n\nconst value = defineModel<number | null>({ default: null })\n\nconst context = useAdmin()\nconst api = createPagesApi(context)\nusePagesMessages()\n\nconst t = useTranslate('webx-pages')\n\nconst nodes = ref<TreeNode[]>([])\n\n/**\n * The home page is the root of the picker as well as of the tree: everything on the site is\n * inside it, so \"at the top level\" and \"inside the home page\" are the same choice, and offering\n * both would be offering one thing twice.\n */\nonMounted(async () => {\n const level = await api.list()\n\n if (level.home)\n nodes.value = [\n toNode(\n level.home,\n level.items.map((page) => toNode(page)),\n ),\n ]\n})\n\nfunction toNode(page: PageRow, children?: TreeNode[]): TreeNode {\n return {\n id: page.id,\n label: page.is_home ? t('pages.home') : page.title,\n disabled: props.exclude.includes(page.id),\n leaf: page.children_count === 0,\n ...(children ? { children } : {}),\n }\n}\n\nasync function load(node: TreeNode): Promise<TreeNode[]> {\n const level = await api.list({ parent: Number(node.id) })\n\n return level.items.map((page) => toNode(page))\n}\n</script>\n\n<template>\n <wx-tree-select\n v-model=\"value\"\n :nodes=\"nodes\"\n lazy\n :load=\"load\"\n show-path\n clearable\n :selected-path=\"\n props.selected.map((page) => ({\n id: page.id,\n label: page.is_home ? t('pages.home') : page.title,\n }))\n \"\n :placeholder=\"props.placeholder ?? t('page.field-parent')\"\n :aria-label=\"t('page.field-parent')\"\n teleport\n />\n</template>\n","<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\nimport { useAdmin, useErrorText, useTranslate } from '@webx-ui/module-admin'\nimport { confirm, toast, useLocales, WxAlert, WxFormItem, WxText } from '@webx-ui/core'\nimport PagePicker from './PagePicker.vue'\nimport { createPagesApi } from './api'\nimport { usePageEditor } from './editor'\nimport { usePagesMessages } from './i18n'\n\n/**\n * Where the page sits: the whole address it answers at, and the page it is inside.\n *\n * The address is shown built rather than left as a slug in a box. `shoes` says nothing about\n * whether the page is under the catalogue or at the top of the site, and the tree is exactly\n * what an editor is deciding when they look at this — so the field edits the last segment and\n * this prints the sentence it is part of, following the field as it is typed.\n *\n * Moving is not a draft. The tree is applied at once, because a page that sits in two places\n * until it is published is a tree no screen can draw — so the picker asks the server there and\n * then, and the warning beside it says what that costs.\n */\ndefineOptions({ name: 'WxPagePlace' })\n\nconst context = useAdmin()\nconst api = createPagesApi(context)\nconst editor = usePageEditor()\nconst locales = useLocales()\nusePagesMessages()\n\nconst t = useTranslate('webx-pages')\n/* Not the server's `message`: the panel says how a request failed in its own words (§13.3). */\nconst message = useErrorText()\n\nconst moving = ref(false)\nconst parent = ref<number | null>(null)\n\nconst page = computed(() => editor?.page.value ?? null)\n\n/**\n * The address of the page above in the language being edited, or `null` where it has none.\n *\n * The home page's is `''` — the front page of the site — so the two are not the same answer\n * and cannot both be an empty string.\n */\nconst prefix = computed<string | null>(() => {\n if (editor?.ancestors.value.length === 0) return null\n\n return editor?.prefixes.value[locales.active.value] ?? null\n})\n\n/** The last segment as it stands in the field, not as the registry last heard it. */\nconst slug = computed(() => {\n const written = editor?.values.value.slug\n\n if (typeof written === 'string') return written\n\n // The language being edited and no other. Falling back on a language that does have words\n // would print an address the site does not answer at (§8).\n return (written as Record<string, string> | undefined)?.[locales.active.value] ?? ''\n})\n\nconst address = computed(() => {\n if (page.value?.is_home) return '/'\n if (prefix.value === null || slug.value === '') return null\n\n return `/${[prefix.value, slug.value].filter(Boolean).join('/')}`\n})\n\nwatch(\n page,\n (current) => {\n parent.value = current?.parent_id ?? null\n },\n { immediate: true },\n)\n\n/**\n * The same question a drop in the list asks, and for the same reason (§14.3): a move rewrites\n * the address of the page and of everything under it, and leaves a redirect on each of the old\n * ones. Picking a parent from a list is deliberate enough that one page moving alone goes\n * through without a dialog; a branch does not.\n */\nasync function move(target: number | null): Promise<void> {\n const current = page.value\n\n if (!current || target === null || target === current.parent_id || moving.value) return\n\n const moves = current.descendants_count + 1\n\n if (moves > 1) {\n const agreed = await confirm({\n title: t('page.move-title', { title: current.title }),\n message: t('page.move-branch', { count: moves }),\n confirmText: t('page.move-confirm'),\n cancelText: t('page.cancel'),\n })\n\n if (!agreed) {\n parent.value = current.parent_id\n\n return\n }\n }\n\n moving.value = true\n\n try {\n const result = await api.move(current.id, target, 'inside')\n\n toast.success(\n result.addresses_changed > 1\n ? t('page.moved', { count: result.addresses_changed })\n : t('page.moved-one'),\n )\n await editor?.reload()\n } catch (error) {\n toast.danger(message(error))\n parent.value = current.parent_id\n } finally {\n moving.value = false\n }\n}\n</script>\n\n<template>\n <div v-if=\"page\" class=\"wx-page-place\">\n <wx-form-item :label=\"t('page.address')\">\n <wx-text v-if=\"address\" mono class=\"wx-page-place__address\">{{ address }}</wx-text>\n <wx-text v-else size=\"sm\" tone=\"muted\">{{ t('page.no-address') }}</wx-text>\n </wx-form-item>\n\n <template v-if=\"page.can.move\">\n <wx-form-item :label=\"t('page.field-parent')\" :help=\"t('page.parent-help')\">\n <page-picker\n v-model=\"parent\"\n :exclude=\"[page.id]\"\n :selected=\"editor?.ancestors.value ?? []\"\n @update:model-value=\"move\"\n />\n </wx-form-item>\n\n <!-- Said before the move rather than after it: the old addresses keep working as\n redirects, which is the part that decides whether this is safe to do on a live\n site, and nobody reads it in a toast that has already gone. -->\n <wx-alert type=\"info\" variant=\"soft\" :description=\"t('page.parent-warning')\" />\n </template>\n </div>\n</template>\n\n<style scoped>\n.wx-page-place {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-8);\n}\n\n.wx-page-place__address {\n word-break: break-all;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { useTranslate, WxRowMenu, type RowAction } from '@webx-ui/module-admin'\nimport { usePagesMessages } from './i18n'\nimport type { PageRow } from './types'\n\n/**\n * What a row of the page tree offers.\n *\n * A list of actions rather than markup, handed to the panel's one row menu: a table cell and\n * a card on a phone both get the same `···` in the same place, and so does every other\n * section (§20). What is written here is only which actions this row has — a page that is the\n * home page has no copy and nowhere else to be, and a page in the bin has one way out.\n *\n * Nothing here is offered and then refused: an action somebody has no right to is left out.\n */\nconst props = withDefaults(defineProps<{ page: PageRow; inBin?: boolean }>(), { inBin: false })\n\nconst emit = defineEmits<{\n open: [page: PageRow]\n add: [page: PageRow]\n duplicate: [page: PageRow]\n move: [page: PageRow]\n copy: [page: PageRow]\n remove: [page: PageRow]\n restore: [page: PageRow]\n}>()\n\nusePagesMessages()\n\nconst t = useTranslate('webx-pages')\n\nconst actions = computed<RowAction[]>(() => {\n const page = props.page\n\n if (props.inBin) {\n return [\n {\n key: 'restore',\n icon: 'refresh',\n label: t('page.restore'),\n run: () => emit('restore', page),\n },\n ]\n }\n\n const list: RowAction[] = [\n { key: 'open', icon: 'edit', label: t('page.open'), run: () => emit('open', page) },\n { key: 'add', icon: 'plus', label: t('page.add-child'), run: () => emit('add', page) },\n ]\n\n // The home page has no copy and no other place to be: the two actions that would make one\n // are simply not offered rather than offered and refused.\n if (!page.is_home) {\n list.push({\n key: 'duplicate',\n icon: 'copy',\n label: t('page.duplicate'),\n run: () => emit('duplicate', page),\n })\n }\n\n if (page.can.move) {\n list.push({ key: 'move', icon: 'drag', label: t('page.move'), run: () => emit('move', page) })\n }\n\n list.push(\n {\n key: 'copy-address',\n icon: 'link',\n label: t('page.copy-address'),\n disabled: !page.url,\n run: () => emit('copy', page),\n },\n {\n key: 'on-site',\n icon: 'external-link',\n label: t('page.open-on-site'),\n disabled: !page.url,\n href: page.url ?? undefined,\n },\n )\n\n if (page.can.delete) {\n list.push({\n key: 'delete',\n icon: 'trash',\n label: t('page.delete'),\n danger: true,\n run: () => emit('remove', page),\n })\n }\n\n return list\n})\n</script>\n\n<template>\n <wx-row-menu :actions=\"actions\" :label=\"props.page.title\" />\n</template>\n","/**\n * Cyrillic and the other alphabets the panel is translated into, as the Latin a URL can hold.\n *\n * The server transliterates too, and the same way — `Str::slug` is what actually decides the\n * address. This is the field following the title as it is typed, so that what the editor sees\n * before saving is what they get; where the two ever disagree, the server wins and the address\n * that comes back is the address.\n */\nconst TRANSLITERATION: Record<string, string> = {\n а: 'a',\n б: 'b',\n в: 'v',\n г: 'g',\n ґ: 'g',\n д: 'd',\n е: 'e',\n ё: 'e',\n є: 'ie',\n ж: 'zh',\n з: 'z',\n и: 'i',\n і: 'i',\n ї: 'i',\n й: 'i',\n к: 'k',\n л: 'l',\n м: 'm',\n н: 'n',\n о: 'o',\n п: 'p',\n р: 'r',\n с: 's',\n т: 't',\n у: 'u',\n ф: 'f',\n х: 'h',\n ц: 'ts',\n ч: 'ch',\n ш: 'sh',\n щ: 'shch',\n ъ: '',\n ы: 'y',\n ь: '',\n э: 'e',\n ю: 'iu',\n я: 'ia',\n ä: 'a',\n ö: 'o',\n ü: 'u',\n ß: 'ss',\n å: 'a',\n ø: 'o',\n æ: 'ae',\n ç: 'c',\n ğ: 'g',\n ı: 'i',\n ş: 's',\n ą: 'a',\n ć: 'c',\n ę: 'e',\n ł: 'l',\n ń: 'n',\n ó: 'o',\n ś: 's',\n ź: 'z',\n ż: 'z',\n}\n\n/** A title as the last part of an address: lower case, Latin, words joined by hyphens. */\nexport function slugify(title: string): string {\n return (\n title\n .toLowerCase()\n .split('')\n .map((character) => TRANSLITERATION[character] ?? character)\n .join('')\n // Accents that survived the map — `é`, `à` — come apart into a letter and a mark, and the\n // mark is what has to go; `é` must not become `-`.\n .normalize('NFD')\n .replace(/[̀-ͯ]/g, '')\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n )\n}\n","<script setup lang=\"ts\">\nimport { nextTick, onMounted, ref, useTemplateRef, watch } from 'vue'\nimport { useAdmin, useTranslate } from '@webx-ui/module-admin'\nimport { useModal, WxButton, WxDialog, WxFormItem, WxInput, WxSpace } from '@webx-ui/core'\nimport PagePicker from './PagePicker.vue'\nimport { createPagesApi } from './api'\nimport { usePagesMessages } from './i18n'\nimport { slugify } from './slug'\nimport type { PageRow } from './types'\n\n/**\n * A new page: what it is called, where it lives, and what it is called in the address.\n *\n * The address follows the title while nobody has touched it, and stops the moment somebody\n * does — a field that keeps rewriting what was typed into it is a field people fight.\n */\nconst props = withDefaults(defineProps<{ parent?: PageRow | null }>(), { parent: null })\n\nconst { open, resolve, dismiss } = useModal<PageRow>()\n\nconst context = useAdmin()\nconst api = createPagesApi(context)\nusePagesMessages()\n\nconst t = useTranslate('webx-pages')\n\nconst title = ref('')\nconst slug = ref('')\nconst parentId = ref<number | null>(props.parent?.id ?? null)\nconst saving = ref(false)\nconst errors = ref<Record<string, string[]>>({})\nconst field = useTemplateRef<HTMLElement>('field')\n\nconst edited = ref(false)\n\nwatch(title, (value) => {\n if (!edited.value) slug.value = slugify(value)\n})\n\nonMounted(async () => {\n await nextTick()\n field.value?.querySelector('input')?.focus()\n})\n\nasync function submit(): Promise<void> {\n if (title.value.trim() === '' || saving.value) return\n\n saving.value = true\n errors.value = {}\n\n try {\n resolve(\n await api.create({\n title: title.value.trim(),\n slug: slug.value.trim(),\n parent_id: parentId.value,\n }),\n )\n } catch (error) {\n // The address is the one thing that can be refused here, and it is refused under its own\n // field: the registry sees every kind of entity, so \"taken\" can mean taken by a product.\n errors.value = (error as { body?: { errors?: Record<string, string[]> } }).body?.errors ?? {}\n } finally {\n saving.value = false\n }\n}\n</script>\n\n<template>\n <wx-dialog v-model:open=\"open\" :title=\"t('page.new-title')\" :width=\"460\">\n <wx-form-item :label=\"t('page.field-title')\" :error=\"errors.title?.[0]\" required>\n <div ref=\"field\">\n <wx-input v-model=\"title\" :aria-label=\"t('page.field-title')\" @keyup.enter=\"submit\" />\n </div>\n </wx-form-item>\n\n <wx-form-item\n :label=\"t('page.field-slug')\"\n :error=\"errors.slug?.[0]\"\n :help=\"t('page.slug-help')\"\n >\n <wx-input\n v-model=\"slug\"\n :aria-label=\"t('page.field-slug')\"\n @input=\"edited = true\"\n @keyup.enter=\"submit\"\n />\n </wx-form-item>\n\n <wx-form-item :label=\"t('page.field-parent')\" :error=\"errors.parent_id?.[0]\">\n <page-picker v-model=\"parentId\" :selected=\"props.parent ? [props.parent] : []\" />\n </wx-form-item>\n\n <template #footer>\n <wx-space size=\"sm\">\n <wx-button variant=\"outline\" @click=\"dismiss()\">{{ t('page.cancel') }}</wx-button>\n <wx-button type=\"primary\" :loading=\"saving\" :disabled=\"title.trim() === ''\" @click=\"submit\">\n {{ t('page.create') }}\n </wx-button>\n </wx-space>\n </template>\n </wx-dialog>\n</template>\n","<script setup lang=\"ts\">\nimport { ref } from 'vue'\nimport { useTranslate } from '@webx-ui/module-admin'\nimport { useModal, WxAlert, WxButton, WxDialog, WxSpace } from '@webx-ui/core'\nimport PagePicker from './PagePicker.vue'\nimport { usePagesMessages } from './i18n'\nimport type { PageRow } from './types'\n\n/**\n * Where a page goes, chosen from a tree rather than dragged.\n *\n * Dragging is the quick way and it is in the table already; this is the one that works — on a\n * touch screen, and in a catalogue where the page and its new parent are four screens apart.\n *\n * Inside, and only inside. Order among siblings is what dragging is for, and a dialog that also\n * asked \"before or after what\" would be asking about the one thing nobody opened it for.\n */\nconst props = defineProps<{ page: PageRow }>()\n\nconst { open, resolve, dismiss } = useModal<number>()\n\nusePagesMessages()\n\nconst t = useTranslate('webx-pages')\n\nconst target = ref<number | null>(props.page.parent_id)\n</script>\n\n<template>\n <wx-dialog\n v-model:open=\"open\"\n :title=\"t('page.move-title', { title: props.page.title })\"\n :width=\"460\"\n >\n <wx-space direction=\"vertical\" size=\"md\" fill>\n <wx-alert type=\"info\" variant=\"soft\" :description=\"t('page.move-help')\" />\n\n <!-- Its own branch is not a place it can go, and the page itself least of all. The rest\n of the branch is refused by the server, which is the only side that knows the whole\n tree without fetching it. -->\n <page-picker v-model=\"target\" :exclude=\"[props.page.id]\" />\n </wx-space>\n\n <template #footer>\n <wx-space size=\"sm\">\n <wx-button variant=\"outline\" @click=\"dismiss()\">{{ t('page.cancel') }}</wx-button>\n <wx-button\n type=\"primary\"\n :disabled=\"target === null || target === props.page.parent_id\"\n @click=\"resolve(target!)\"\n >\n {{ t('page.move-confirm') }}\n </wx-button>\n </wx-space>\n </template>\n </wx-dialog>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, onMounted, ref, watch } from 'vue'\nimport { useRouter } from 'vue-router'\nimport { useAdmin, useErrorText, useTranslate, WxDate, WxListScreen } from '@webx-ui/module-admin'\nimport {\n confirm,\n createModal,\n toast,\n WxBadge,\n WxButton,\n WxTable,\n WxText,\n type TabItem,\n type TableColumn,\n type TableNodeDropEvent,\n type TableState,\n type TableTreeOptions,\n type TabValue,\n type TreeDropZone,\n} from '@webx-ui/core'\nimport PageActions from './PageActions.vue'\nimport PageCreateDialog from './PageCreateDialog.vue'\nimport PageMoveDialog from './PageMoveDialog.vue'\nimport { createPagesApi } from './api'\nimport { usePagesMessages } from './i18n'\nimport type { PageRow, PageStatus } from './types'\n\n/**\n * The section: the site's pages as a tree, a level at a time.\n *\n * The home page is pinned at the top and its children are the top level, because every page of\n * the site is inside it — drawn as a branch, every row of the table would carry one step of\n * indentation that says nothing (§9).\n *\n * Searching puts the tree away. A branch drawn for the sake of one match deep inside it tells\n * the reader nothing they asked about, so a search is a flat list of matches with the address\n * under each.\n */\nconst props = withDefaults(defineProps<{ base?: string }>(), { base: '/pages' })\n\nconst context = useAdmin()\nconst api = createPagesApi(context)\nconst router = useRouter()\nusePagesMessages()\n\nconst t = useTranslate('webx-pages')\n/* Not the server's `message`: the panel says how a request failed in its own words (§13.3). */\nconst message = useErrorText()\n\nconst home = ref<PageRow | null>(null)\nconst items = ref<PageRow[]>([])\nconst loading = ref(true)\nconst search = ref('')\n/* A tab's value is a string; what the server is asked for is read back out of it. */\nconst filter = ref<TabValue>('')\n\nconst create = createModal<PageRow, { parent: PageRow | null }>(PageCreateDialog)\nconst pickTarget = createModal<number, { page: PageRow }>(PageMoveDialog)\n\nconst canManage = computed(() => context.can('pages.manage'))\nconst inBin = computed(() => filter.value === 'trashed')\n/**\n * A search and the bin are flat lists; everything else is a level of the tree, at every width.\n *\n * A narrow screen used to get a flat list instead, on the grounds that cards have no\n * indentation to read and no chevron to open. The cards were the mistake, not the tree (§11):\n * the table stays a table and drops columns until only the title and the `···` are left, so the\n * tree is walked on a phone the same way it is walked on a desk.\n */\nconst asTree = computed(() => !inBin.value && search.value.trim() === '')\n\nconst title = computed(\n () =>\n context.state.manifest?.modules.find((module) => module.id === 'pages')?.title ??\n t('module.title'),\n)\n\n/*\n * The views of the same list, as tabs over the card (§10). The bin rides in the same row with\n * an icon rather than standing apart: it is still this list, and a reader who wants what they\n * deleted looks for it beside what they did not.\n */\nconst views = computed<TabItem[]>(() => [\n { value: '', label: t('page.filter-all') },\n { value: 'draft', label: t('page.filter-draft') },\n { value: 'published', label: t('page.filter-published') },\n { value: 'modified', label: t('page.filter-modified') },\n { value: 'trashed', label: t('page.bin'), icon: 'trash' },\n])\n\n/**\n * The home page rides in the rows rather than beside them, with no children of its own: what\n * would hang under it is already the level below, and a chevron that opened a copy of the list\n * is the one thing worse than no chevron.\n */\nconst rows = computed<PageRow[]>(() =>\n asTree.value && home.value ? [{ ...home.value, children_count: 0 }, ...items.value] : items.value,\n)\n\n/**\n * Every column but the title is given a width, and the table is laid out `fixed`, so the\n * widths that are hidden below a breakpoint give what they had to the title rather than to\n * whichever column happened to hold the longest string.\n *\n * They go in the order they can be spared — when it was last touched, then what state it is in,\n * then where it lives — and below the narrowest of them the row is the title and the `···`,\n * which is the whole of §11: two columns, still a tree.\n */\nconst columns = computed<TableColumn<PageRow>[]>(() => [\n { key: 'title', label: t('page.column-title'), minWidth: 200 },\n { key: 'path', label: t('page.column-address'), width: 200, hideBelow: 560 },\n // In the bin the state of a page is not what it was published as — it is off the site either\n // way — so the column says when it went in, which is what decides whether to bring it back.\n {\n key: 'status',\n label: inBin.value ? t('page.column-deleted') : t('page.column-status'),\n align: 'center',\n width: 130,\n hideBelow: 620,\n },\n {\n key: 'updated_at',\n label: t('page.column-updated'),\n width: 180,\n hideBelow: 900,\n hidden: inBin.value,\n },\n { key: 'actions', label: '', width: 56, align: 'right' },\n])\n\nconst tree = computed<TableTreeOptions<PageRow> | undefined>(() =>\n asTree.value\n ? {\n childrenKey: 'children',\n hasChildrenKey: 'children_count',\n lazy: true,\n load: (row) => api.list({ parent: row.id }).then((level) => level.items),\n draggable: canManage.value,\n allowDrag: (row) => row.can.move,\n allowDrop,\n }\n : undefined,\n)\n\n/**\n * Nothing goes beside the home page: that place is a second root, and the tree has one. Inside\n * it is the ordinary case — everything on the site is.\n */\nfunction allowDrop(drag: PageRow, drop: PageRow, zone: TreeDropZone): boolean {\n if (drop.is_home) return zone === 'inside'\n\n return drag.id !== drop.id\n}\n\nasync function load(): Promise<void> {\n loading.value = true\n\n try {\n const level = await api.list({\n search: search.value.trim(),\n status: inBin.value ? '' : (filter.value as PageStatus | ''),\n trashed: inBin.value,\n })\n\n home.value = level.home\n items.value = level.items\n } catch (error) {\n toast.danger(message(error))\n } finally {\n loading.value = false\n }\n}\n\nfunction onState(state: TableState): void {\n if (state.search === search.value) return\n\n search.value = state.search\n void load()\n}\n\n/**\n * The editor of a page arrives with the form; until it does, the row opens nothing rather than\n * pushing the router at a route that is not there.\n *\n * The bin never reaches here: a page in it has no editor to open — the API answers a 404 for\n * one — so the table is told the rows lead nowhere (`:clickable=\"!inBin\"`), and it withholds\n * the click along with the pointer and the highlight (§13). Everything a deleted page can have\n * done to it is in its `···`.\n */\nfunction open(page: PageRow): void {\n const path = `${props.base}/${page.id}`\n\n if (router.resolve(path).matched.length > 0) void router.push(path)\n}\n\nasync function add(parent: PageRow | null): Promise<void> {\n const page = await create({ parent })\n\n if (!page) return\n\n await load()\n open(page)\n}\n\nasync function duplicate(page: PageRow): Promise<void> {\n try {\n await api.duplicate(page.id)\n toast.success(t('page.duplicated'))\n await load()\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\nasync function move(page: PageRow): Promise<void> {\n const target = await pickTarget({ page })\n\n if (target == null) return\n\n await apply(page, target, 'inside')\n}\n\n/**\n * A drop asks only when it rewrites more than one address (§14.3).\n *\n * The other way out — no question, and an \"Undo\" in the toast — reads better but is not one:\n * a move leaves a redirect on every address it vacated, so putting the page back would be a\n * second move and a pile of redirects, not an undo. Until the server can actually undo one,\n * dragging a single page stays a gesture and dragging a branch stays a decision.\n *\n * The number is known before the request: a move changes the address of the page and of\n * everything under it, which is what `descendants_count` counts. Landing in the same parent\n * changes nothing at all, so a reorder is never questioned.\n */\nasync function dropped(event: TableNodeDropEvent<PageRow>): Promise<void> {\n const parent = event.zone === 'inside' ? event.target.id : event.target.parent_id\n const moves = event.row.descendants_count + 1\n\n if (parent !== event.row.parent_id && moves > 1) {\n const agreed = await confirm({\n title: t('page.move-title', { title: event.row.title }),\n message: t('page.move-branch', { count: moves }),\n confirmText: t('page.move-confirm'),\n cancelText: t('page.cancel'),\n })\n\n if (!agreed) {\n // The table is showing the row where it was dropped; the server never heard about it.\n await load()\n\n return\n }\n }\n\n await apply(event.row, event.target.id, event.zone)\n}\n\n/** One move, one sentence about the addresses it rewrote, and the list as the server sees it. */\nasync function apply(page: PageRow, target: number, zone: TreeDropZone): Promise<void> {\n try {\n const result = await api.move(page.id, target, zone)\n\n toast.success(\n result.addresses_changed > 1\n ? t('page.moved', { count: result.addresses_changed })\n : t('page.moved-one'),\n )\n } catch (error) {\n toast.danger(message(error))\n } finally {\n // Either way: a refused move has left the table showing the row where it was dropped, and\n // a move that worked has changed addresses the answer does not carry row by row.\n await load()\n }\n}\n\nasync function remove(page: PageRow): Promise<void> {\n const agreed = await confirm({\n title: t('page.delete-title', { title: page.title }),\n message:\n // The whole branch, not the level under it: a catalogue with two sections and forty\n // products is forty-two pages leaving the site, and \"2\" would be a comforting lie.\n page.descendants_count > 0\n ? `${t('page.delete-text')} ${t('page.delete-branch', { count: page.descendants_count })}`\n : t('page.delete-text'),\n confirmText: t('page.delete'),\n cancelText: t('page.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n try {\n await api.remove(page.id)\n toast.success(t('page.deleted'))\n await load()\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\n/**\n * Coming back out of the bin is asked about too (§14.2): a branch restored by accident is a\n * section of the site back on it, and the count says how much of one.\n */\nasync function restore(page: PageRow): Promise<void> {\n const agreed = await confirm({\n title: t('page.restore-page-title', { title: page.title }),\n message:\n page.descendants_count > 0\n ? `${t('page.restore-page-text')} ${t('page.restore-branch', { count: page.descendants_count })}`\n : t('page.restore-page-text'),\n confirmText: t('page.restore'),\n cancelText: t('page.cancel'),\n })\n\n if (!agreed) return\n\n try {\n // The server knows what it actually brought back; the question could only guess at it.\n const restored = await api.restore(page.id)\n\n toast.success(\n restored > 1 ? t('page.restored-branch', { count: restored }) : t('page.restored'),\n )\n await load()\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\nasync function copyAddress(page: PageRow): Promise<void> {\n if (!page.url) return\n\n try {\n await navigator.clipboard.writeText(page.url)\n toast.success(t('page.address-copied'))\n } catch {\n // A browser that refuses the clipboard without a gesture it trusts; the address is on\n // screen and can be copied from there.\n }\n}\n\nfunction badge(status: PageStatus): 'default' | 'success' | 'warning' {\n if (status === 'published') return 'success'\n\n return status === 'modified' ? 'warning' : 'default'\n}\n\nwatch(filter, () => void load())\n\nonMounted(load)\n</script>\n\n<template>\n <div class=\"wx-pages\">\n <wx-list-screen v-model:view=\"filter\" :title=\"title\" :views=\"views\">\n <template v-if=\"canManage\" #actions>\n <wx-button type=\"primary\" icon=\"plus\" @click=\"add(null)\">\n {{ t('page.new') }}\n </wx-button>\n </template>\n\n <wx-table\n :data=\"rows\"\n :columns=\"columns\"\n row-key=\"id\"\n :tree=\"tree\"\n searchable\n :clickable=\"!inBin\"\n :hover=\"!inBin\"\n flush\n layout=\"fixed\"\n :loading=\"loading\"\n :cards-below=\"0\"\n :search-placeholder=\"t('page.search')\"\n :empty-text=\"\n inBin ? t('page.empty-bin') : search ? t('page.empty-search') : t('page.empty')\n \"\n :aria-label=\"title\"\n @row-click=\"open\"\n @state-change=\"onState\"\n @node-drop=\"dropped\"\n >\n <template #cell-title=\"{ row }\">\n <span class=\"wx-pages__title\">\n {{ row.is_home ? t('pages.home') : row.title }}\n </span>\n </template>\n\n <template #cell-path=\"{ row }\">\n <!-- A page in the bin holds no address: the registry let it go when it went in, and\n a live address beside a page nobody can reach would be the wrong promise. -->\n <wx-text v-if=\"inBin\" size=\"sm\" tone=\"muted\">—</wx-text>\n <!-- The address of a live page is a link to it; a draft's is the address it will\n have, which is worth showing and not worth clicking. -->\n <a\n v-else-if=\"row.url && row.status !== 'draft'\"\n class=\"wx-pages__address\"\n :href=\"row.url\"\n target=\"_blank\"\n rel=\"noopener\"\n @click.stop\n >\n /{{ row.path }}\n </a>\n <wx-text v-else-if=\"row.path !== null\" mono size=\"sm\" tone=\"muted\"\n >/{{ row.path }}</wx-text\n >\n <wx-text v-else size=\"sm\" tone=\"muted\">{{ t('page.no-address') }}</wx-text>\n </template>\n\n <template #cell-status=\"{ row }\">\n <wx-date v-if=\"inBin\" :value=\"row.deleted_at\" />\n <wx-badge v-else :type=\"badge(row.status)\" dot>\n {{ t(`page.status-${row.status}`) }}\n </wx-badge>\n </template>\n\n <template #cell-updated_at=\"{ row }\">\n <wx-text size=\"sm\" tone=\"muted\">\n <wx-date :value=\"row.updated_at\" /><template v-if=\"row.edited_by && !inBin\"\n >, {{ row.edited_by }}</template\n >\n </wx-text>\n </template>\n\n <template #cell-actions=\"{ row }\">\n <page-actions\n v-if=\"canManage\"\n :page=\"row\"\n :in-bin=\"inBin\"\n @open=\"open\"\n @add=\"add\"\n @duplicate=\"duplicate\"\n @move=\"move\"\n @copy=\"copyAddress\"\n @remove=\"remove\"\n @restore=\"restore\"\n />\n </template>\n </wx-table>\n </wx-list-screen>\n </div>\n</template>\n\n<style scoped>\n/* The list screen inside it lays the screen out; this only keeps a wide table from pushing the\n column it sits in. */\n.wx-pages {\n min-width: 0;\n}\n\n.wx-pages__title {\n font-weight: var(--wx-font-weight-medium);\n}\n\n.wx-pages__address {\n font-family: var(--wx-font-family-mono);\n font-size: var(--wx-font-size-sm);\n color: var(--wx-color-primary);\n text-decoration: none;\n}\n\n.wx-pages__address:hover {\n text-decoration: underline;\n}\n</style>\n","import type { AdminModule } from '@webx-ui/module-admin'\nimport PageDanger from './PageDanger.vue'\nimport PageEditorPage from './PageEditorPage.vue'\nimport PageHistory from './PageHistory.vue'\nimport PagePlace from './PagePlace.vue'\nimport PagesPage from './PagesPage.vue'\n\nexport interface PagesOptions {\n /** Where the section lives inside the panel. */\n path?: string\n}\n\n/**\n * The site's pages as a section of the panel.\n *\n * The id matches the module the server reports, which is what makes the entry appear in the\n * navigation: the section shows up when both halves are installed.\n */\nexport function pages(options: PagesOptions = {}): AdminModule {\n const path = options.path ?? '/pages'\n\n return {\n id: 'pages',\n path,\n routes: [\n { path, name: 'webx.pages', component: PagesPage, props: { base: path } },\n {\n path: `${path}/:id(\\\\d+)`,\n name: 'webx.pages.edit',\n component: PageEditorPage,\n props: { base: path },\n },\n ],\n /*\n * The parts of `pages.form` that are not fields.\n *\n * The editor is a described screen so that a module can add a tab to it with a patch, and\n * the price of that is that everything on it has to be a node type — where the page sits,\n * what may be done to it, what its history is. Each of these draws only, and each reads the\n * page from the editor above it rather than from the description.\n */\n types: {\n 'wx-page-place': { component: PagePlace, kind: 'display' },\n 'wx-page-danger': { component: PageDanger, kind: 'display' },\n 'wx-page-history': { component: PageHistory, kind: 'display' },\n },\n }\n}\n"],"names":["createPagesApi","admin","base","data","body","query","search","suffix","id","input","target","zone","number","pageEditorKey","providePageEditor","editor","provide","usePageEditor","inject","pagesMessages","seeded","usePagesMessages","i18n","useAdmin","context","api","router","useRouter","useTranslate","message","useErrorText","working","ref","page","computed","canManage","unpublish","current","confirm","toast","error","remove","_createBlock","_unref","WxCard","_createElementVNode","_hoisted_1","_openBlock","_createElementBlock","_hoisted_2","_hoisted_3","_createVNode","WxText","WxButton","_hoisted_4","_hoisted_5","_createTextVNode","_toDisplayString","PAUSE","NARROW","props","__props","route","useRoute","locales","useLocales","t","ancestors","values","revision","previewUrl","prefixes","loading","saving","errors","conflict","snapshot","reloadToken","root","useTemplateRef","width","useElementWidth","narrow","dirty","state","title","written","localizedValue","trail","node","provideBlocksPreview","load","save","take","detail","silent","timer","schedule","onFocusOut","sending","sent","failure","takeTheirs","keepMine","theirs","publish","address","badge","leaveGuard","event","watch","onMounted","onBeforeUnmount","onBeforeRouteLeave","_Fragment","WxSkeleton","WxBackButton","WxBreadcrumb","WxBreadcrumbItem","_renderList","WxBadge","WxDropdown","WxAction","WxDropdownItem","WxAlert","_hoisted_6","WxScreen","$event","WxActionBar","versions","live","restore","version","WxEmpty","WxDate","value","_useModel","nodes","level","toNode","children","WxTreeSelect","moving","parent","prefix","slug","move","moves","result","WxFormItem","PagePicker","emit","__emit","actions","list","WxRowMenu","TRANSLITERATION","slugify","character","open","resolve","dismiss","useModal","parentId","field","edited","nextTick","submit","WxDialog","WxSpace","WxInput","_cache","home","items","filter","create","createModal","PageCreateDialog","pickTarget","PageMoveDialog","inBin","asTree","module","views","rows","columns","tree","row","allowDrop","drag","drop","onState","path","add","duplicate","apply","dropped","restored","copyAddress","status","WxListScreen","WxTable","_withCtx","PageActions","pages","options","PagesPage","PageEditorPage","PagePlace","PageDanger","PageHistory"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../src/api.ts","../src/editor.ts","../src/messages.ts","../src/i18n.ts","../src/PageDanger.vue","../src/PageEditorPage.vue","../src/PageHistory.vue","../src/PagePicker.vue","../src/PagePlace.vue","../src/PageActions.vue","../src/slug.ts","../src/PageCreateDialog.vue","../src/PageMoveDialog.vue","../src/PagesPage.vue","../src/module.ts"],"sourcesContent":["import type { AdminContext } from '@webx-ui/module-admin'\nimport type {\n PageDetail,\n PageDropZone,\n PageInput,\n PageLevel,\n PageMoveResult,\n PageRow,\n PageSave,\n PageVersion,\n} from './types'\n\nexport interface PagesApi {\n /** One level of the tree, or a flat list when searching or looking in the bin. */\n list(query?: {\n parent?: number | null\n search?: string\n status?: string\n trashed?: boolean\n /** The whole tree in one flat list, in the order it reads top to bottom. */\n flat?: boolean\n }): Promise<PageLevel>\n get(id: number): Promise<PageDetail>\n create(input: PageInput): Promise<PageRow>\n /**\n * Save the draft. Refused with a 409 when the revision is not the current one — the error's\n * body is a {@link PageConflict}, and the page it carries is the page as it now is.\n */\n save(id: number, input: PageSave): Promise<PageDetail>\n move(id: number, target: number, zone: PageDropZone): Promise<PageMoveResult>\n duplicate(id: number): Promise<PageRow>\n publish(id: number): Promise<PageRow>\n unpublish(id: number): Promise<PageRow>\n /** Into the bin, with the branch under it. Answers how many went. */\n remove(id: number): Promise<number>\n /** Out of the bin, with whatever went in with it. Answers how many came back. */\n restore(id: number): Promise<number>\n /** The publications, newest first. */\n versions(id: number): Promise<PageVersion[]>\n /** An old publication becomes the draft; putting it on the site is a separate step. */\n restoreVersion(id: number, number: number): Promise<PageDetail>\n}\n\n/** Everything under `/pages`, below the panel's API path. */\nexport function createPagesApi(admin: AdminContext): PagesApi {\n const base = `${admin.apiPath}/pages`\n const data = <T>(body: { data: T }): T => body.data\n\n return {\n list: (query = {}) => {\n const search = new URLSearchParams()\n\n // Only what was asked for: `parent` left out means the home page's children, and\n // `parent=` would ask for the children of nothing.\n if (query.parent != null) search.set('parent', String(query.parent))\n if (query.search) search.set('search', query.search)\n if (query.status) search.set('status', query.status)\n if (query.trashed) search.set('trashed', '1')\n if (query.flat) search.set('flat', '1')\n\n const suffix = search.size > 0 ? `?${search}` : ''\n\n return admin.http.get<{ data: PageLevel }>(`${base}${suffix}`).then(data)\n },\n get: (id) => admin.http.get<{ data: PageDetail }>(`${base}/${id}`).then(data),\n create: (input) => admin.http.post<{ data: PageRow }>(base, input).then(data),\n save: (id, input) => admin.http.put<{ data: PageDetail }>(`${base}/${id}`, input).then(data),\n move: (id, target, zone) =>\n admin.http.post<{ data: PageMoveResult }>(`${base}/${id}/move`, { target, zone }).then(data),\n duplicate: (id) => admin.http.post<{ data: PageRow }>(`${base}/${id}/duplicate`, {}).then(data),\n publish: (id) => admin.http.post<{ data: PageRow }>(`${base}/${id}/publish`, {}).then(data),\n unpublish: (id) => admin.http.post<{ data: PageRow }>(`${base}/${id}/unpublish`, {}).then(data),\n remove: (id) =>\n admin.http\n .delete<{ data: { trashed: number } }>(`${base}/${id}`)\n .then((body) => body.data.trashed),\n restore: (id) =>\n admin.http\n .post<{ data: { restored: number } }>(`${base}/${id}/restore`, {})\n .then((body) => body.data.restored),\n versions: (id) => admin.http.get<{ data: PageVersion[] }>(`${base}/${id}/versions`).then(data),\n restoreVersion: (id, number) =>\n admin.http\n .post<{ data: PageDetail }>(`${base}/${id}/versions/${number}/restore`, {})\n .then(data),\n }\n}\n","import { inject, provide, type InjectionKey, type Ref } from 'vue'\nimport type { ScreenModel } from '@webx-ui/schema'\nimport type { PageRow } from './types'\n\n/**\n * What the editor knows and the nodes of its screen do not.\n *\n * The form is a described screen (`pages.form`), so the parts of it that are not fields —\n * where the page sits, what may be done to it, what its history is — arrive as node types of\n * their own. Each of those needs the page being edited, and none of them can be handed it\n * through the description: a screen is a description, not a binding. So the page that hosts\n * the screen provides it, the same way it provides the preview to `wx-blocks`.\n */\nexport interface PageEditorContext {\n /** The page as the server last answered it, or `null` while the first request is in flight. */\n page: Ref<PageRow | null>\n /** The trail above it, home page first. */\n ancestors: Ref<PageRow[]>\n /**\n * The values of the screen as they are right now, edits included.\n *\n * A node that only draws still has to follow what is being typed: the address is a sentence\n * made of the slug field, and one that updated on save would be showing the old address at\n * the moment the new one is most worth reading.\n */\n values: Ref<ScreenModel>\n /** The address of the page above, by content language. */\n prefixes: Ref<Record<string, string>>\n /** Where the section lives, for the links the nodes draw. */\n base: string\n /** Whether this administrator may write at all. */\n canManage: boolean\n /** Ask the server for the page again — after a move, a restore, a publication. */\n reload(): Promise<void>\n /** Write the draft now rather than at the end of the next pause. */\n save(): Promise<void>\n}\n\nexport const pageEditorKey: InjectionKey<PageEditorContext> = Symbol('wx-page-editor')\n\nexport function providePageEditor(editor: PageEditorContext): void {\n provide(pageEditorKey, editor)\n}\n\n/**\n * The editor above this node, or `null` outside one — a node of `pages.form` drawn in a demo\n * or a test has no page to talk about, and must draw nothing rather than throw.\n */\nexport function usePageEditor(): PageEditorContext | null {\n return inject(pageEditorKey, null)\n}\n","import type { Messages } from '@webx-ui/module-admin'\n\n/**\n * The English this package speaks on its own, before the server's dictionary arrives, or\n * without one. The server ships the same lines in ten languages under `webx-pages::` and\n * overrides these; `messages.test.ts` keeps the two sets of keys equal.\n */\nexport const pagesMessages: Record<string, Messages> = {\n module: {\n title: 'Pages',\n },\n pages: {\n home: 'Home',\n },\n page: {\n new: 'New page',\n search: 'Search by title or address',\n empty: 'No pages yet.',\n 'empty-help': 'A site is its pages. The first one goes inside the home page.',\n 'empty-search': 'Nothing matches that.',\n 'empty-bin': 'The bin is empty.',\n\n 'column-title': 'Title',\n 'column-address': 'Address',\n 'column-status': 'Status',\n 'column-updated': 'Updated',\n 'column-deleted': 'Deleted',\n\n 'status-draft': 'Draft',\n 'status-published': 'Published',\n 'status-modified': 'Edited',\n 'no-address': 'No address in this language',\n\n 'filter-all': 'All',\n 'filter-draft': 'Drafts',\n 'filter-published': 'Published',\n 'filter-modified': 'Edited',\n bin: 'Bin',\n\n open: 'Open',\n 'add-child': 'Add a page inside',\n duplicate: 'Duplicate',\n move: 'Move…',\n 'copy-address': 'Copy the address',\n 'open-on-site': 'Open on the site',\n delete: 'Delete',\n restore: 'Restore',\n cancel: 'Cancel',\n\n 'new-title': 'New page',\n 'field-title': 'Title',\n 'field-slug': 'Address',\n 'slug-help': 'The last part of the address. Left empty, it is made from the title.',\n 'field-parent': 'Inside',\n create: 'Create and open',\n\n 'move-title': 'Move “:title”',\n 'move-help':\n 'Pick the page it goes inside. Everything under it moves too, and their addresses change with it.',\n 'move-confirm': 'Move',\n 'move-branch':\n ':count addresses change — the page and everything inside it. The old ones will redirect to the new ones.',\n moved: 'Moved. :count addresses changed; the old ones now redirect to the new ones.',\n 'moved-one': 'Moved. The address changed; the old one now redirects to it.',\n\n 'delete-title': 'Delete “:title”?',\n 'delete-text': 'The page goes to the bin and stops answering on the site.',\n 'delete-branch': 'The :count pages inside it go with it.',\n deleted: 'The page is in the bin.',\n restored: 'The page is back.',\n 'restore-page-title': 'Bring “:title” back?',\n 'restore-page-text':\n 'The page leaves the bin and takes its address back. One that was on the site answers again.',\n 'restore-branch': 'The :count pages that went in with it come back too.',\n 'restored-branch': ':count pages are back.',\n\n 'copy-of': ':title (copy)',\n duplicated: 'The copy is ready, and it is not on the site.',\n 'address-copied': 'The address is on the clipboard.',\n\n // The editor.\n trail: 'Where this page sits',\n untitled: 'Untitled',\n 'state-saved': 'Saved',\n 'state-saving': 'Saving…',\n 'state-unsaved': 'Not saved yet',\n save: 'Save',\n 'save-failed': 'The page was not saved.',\n publish: 'Publish',\n 'publish-title': 'Put “:title” on the site?',\n 'publish-text': 'It answers at :address from the moment you do, for everyone.',\n 'publish-text-nowhere': 'It starts answering on the site from the moment you do, for everyone.',\n published: 'The page is on the site.',\n preview: 'Preview',\n more: 'More',\n address: 'Address',\n 'parent-help': 'The page it sits inside. A move happens at once, not on publication.',\n 'parent-warning':\n 'Everything inside this page moves with it. The old addresses keep working and lead to the new ones.',\n danger: 'Off the site',\n unpublish: 'Unpublish',\n 'unpublish-title': 'Take “:title” off the site?',\n 'unpublish-text':\n 'The page stops answering. Nothing written is lost, and publishing puts it back.',\n 'unpublish-help': 'The page stops answering on the site. What is written stays.',\n unpublished: 'The page is off the site.',\n 'delete-help': 'The page goes to the bin, with everything inside it.',\n 'delete-home': 'The home page cannot be deleted.',\n 'conflict-title': 'The page changed while you were editing',\n 'conflict-theirs': 'Take the newer version',\n 'conflict-theirs-title': 'Give up what you wrote?',\n 'conflict-theirs-text':\n 'The page is read again as it now is, and what you have typed since goes.',\n 'conflict-mine': 'Keep mine',\n 'leave-title': 'Leave without saving?',\n 'leave-text': 'The page could not be saved, and what you wrote is not on the server.',\n leave: 'Leave',\n 'history-empty': 'This page has never been published.',\n version: '#:number',\n 'version-live': 'On the site',\n 'source-panel': 'From the panel',\n 'source-mcp': 'By an agent',\n 'source-import': 'Imported',\n 'restore-title': 'Restore version :number?',\n 'restore-text':\n 'It becomes the draft. The site keeps showing what is published until you publish this.',\n 'restored-version': 'Version :number is now the draft.',\n },\n}\n","import { useAdmin } from '@webx-ui/module-admin'\nimport { pagesMessages } from './messages'\n\nconst seeded = new WeakSet<object>()\n\n/** Puts this package's English under the panel's dictionary, once per panel. */\nexport function usePagesMessages(): void {\n try {\n const { i18n } = useAdmin()\n\n if (!seeded.has(i18n)) {\n i18n.defaults('webx-pages', pagesMessages)\n seeded.add(i18n)\n }\n } catch {\n // Outside a panel there is no dictionary to seed, and `useTranslate` falls back on its own.\n }\n}\n","<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\nimport { useRouter } from 'vue-router'\nimport { useAdmin, useErrorText, useTranslate } from '@webx-ui/module-admin'\nimport { confirm, toast, WxButton, WxCard, WxText } from '@webx-ui/core'\nimport { createPagesApi } from './api'\nimport { usePageEditor } from './editor'\nimport { usePagesMessages } from './i18n'\n\n/**\n * The two ways a page leaves the site.\n *\n * At the bottom of the settings tab rather than in the head, because both are the opposite of\n * what the head is for: publishing is the thing being aimed at all day, and a button that\n * takes the page down should not be next to it by the width of a finger.\n */\ndefineOptions({ name: 'WxPageDanger' })\n\nconst context = useAdmin()\nconst api = createPagesApi(context)\nconst router = useRouter()\nconst editor = usePageEditor()\nusePagesMessages()\n\nconst t = useTranslate('webx-pages')\n/* Not the server's `message`: the panel says how a request failed in its own words (§13.3). */\nconst message = useErrorText()\n\nconst working = ref(false)\n\nconst page = computed(() => editor?.page.value ?? null)\nconst canManage = computed(() => context.can('pages.manage'))\n\nasync function unpublish(): Promise<void> {\n const current = page.value\n\n if (!current) return\n\n const agreed = await confirm({\n title: t('page.unpublish-title', { title: current.title }),\n message: t('page.unpublish-text'),\n confirmText: t('page.unpublish'),\n cancelText: t('page.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n working.value = true\n\n try {\n await api.unpublish(current.id)\n await editor?.reload()\n toast.success(t('page.unpublished'))\n } catch (error) {\n toast.danger(message(error))\n } finally {\n working.value = false\n }\n}\n\nasync function remove(): Promise<void> {\n const current = page.value\n\n if (!current) return\n\n const agreed = await confirm({\n title: t('page.delete-title', { title: current.title }),\n // The whole branch, not the level under it (§7): a catalogue with two sections and forty\n // products is forty-two pages leaving the site at once.\n message:\n current.descendants_count > 0\n ? `${t('page.delete-text')} ${t('page.delete-branch', { count: current.descendants_count })}`\n : t('page.delete-text'),\n confirmText: t('page.delete'),\n cancelText: t('page.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n working.value = true\n\n try {\n await api.remove(current.id)\n toast.success(t('page.deleted'))\n void router.push(editor?.base ?? '/pages')\n } catch (error) {\n toast.danger(message(error))\n } finally {\n working.value = false\n }\n}\n</script>\n\n<template>\n <wx-card v-if=\"page && canManage\" :title=\"t('page.danger')\">\n <div class=\"wx-page-danger\">\n <div v-if=\"page.status !== 'draft'\" class=\"wx-page-danger__row\">\n <div class=\"wx-page-danger__what\">\n <wx-text weight=\"medium\">{{ t('page.unpublish') }}</wx-text>\n <wx-text size=\"sm\" tone=\"muted\">{{ t('page.unpublish-help') }}</wx-text>\n </div>\n <wx-button variant=\"outline\" :loading=\"working\" @click=\"unpublish\">\n {{ t('page.unpublish') }}\n </wx-button>\n </div>\n\n <div class=\"wx-page-danger__row\">\n <div class=\"wx-page-danger__what\">\n <wx-text weight=\"medium\">{{ t('page.delete') }}</wx-text>\n <wx-text size=\"sm\" tone=\"muted\">{{\n page.can.delete ? t('page.delete-help') : t('page.delete-home')\n }}</wx-text>\n </div>\n <wx-button\n type=\"danger\"\n variant=\"outline\"\n :disabled=\"!page.can.delete\"\n :loading=\"working\"\n @click=\"remove\"\n >\n {{ t('page.delete') }}\n </wx-button>\n </div>\n </div>\n </wx-card>\n</template>\n\n<style scoped>\n.wx-page-danger {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-12);\n}\n\n.wx-page-danger__row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--wx-space-12);\n flex-wrap: wrap;\n}\n\n.wx-page-danger__what {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-2);\n min-width: 200px;\n flex: 1;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, onMounted, ref, useTemplateRef, watch } from 'vue'\nimport { onBeforeRouteLeave, useRoute, useRouter } from 'vue-router'\nimport { useAdmin, useErrorText, useTranslate, WxBackButton, WxScreen } from '@webx-ui/module-admin'\nimport { provideBlocksPreview } from '@webx-ui/module-blocks'\nimport {\n confirm,\n localizedValue,\n toast,\n useElementWidth,\n useLocales,\n WxAction,\n WxActionBar,\n WxAlert,\n WxBadge,\n WxBreadcrumb,\n WxBreadcrumbItem,\n WxButton,\n WxDropdown,\n WxDropdownItem,\n WxSkeleton,\n WxText,\n type LocalizedValue,\n} from '@webx-ui/core'\nimport type { ScreenModel } from '@webx-ui/schema'\nimport { createPagesApi } from './api'\nimport { providePageEditor } from './editor'\nimport { usePagesMessages } from './i18n'\nimport type { PageConflict, PageDetail, PageRow } from './types'\n\n/**\n * The editor of one page: a head that stays put, and the described screen under it.\n *\n * What the page itself owns is the head — where this page sits, what state it is in, and the\n * way out to the site — and the bar along the bottom, which is where the page is saved and\n * published. Everything between them is `pages.form`, so a module or a project adds a tab to\n * the editor with a patch (§12) rather than with a fork of this file.\n *\n * Saving is by autosave: a pause after the last keystroke, and the moment a field is left. The\n * explicit button is still there, because a button that says \"saved\" is the only way a person\n * can be sure — but nothing is ever lost by not pressing it.\n */\nconst props = withDefaults(defineProps<{ base?: string }>(), { base: '/pages' })\n\nconst context = useAdmin()\nconst api = createPagesApi(context)\nconst route = useRoute()\nconst router = useRouter()\nconst locales = useLocales()\nusePagesMessages()\n\nconst t = useTranslate('webx-pages')\n/* Not the server's `message`: the panel says how a request failed in its own words (§13.3). */\nconst message = useErrorText()\n\n/** How long after the last keystroke the draft goes to the server. */\nconst PAUSE = 1200\n\nconst id = computed(() => Number(route.params.id))\n\nconst page = ref<PageRow | null>(null)\nconst ancestors = ref<PageRow[]>([])\nconst values = ref<ScreenModel>({})\nconst revision = ref('')\nconst previewUrl = ref<string | null>(null)\nconst prefixes = ref<Record<string, string>>({})\n\nconst loading = ref(true)\nconst saving = ref(false)\nconst working = ref(false)\nconst errors = ref<Record<string, string[]>>({})\nconst conflict = ref<PageConflict | null>(null)\n\nconst snapshot = ref('')\nconst reloadToken = ref(0)\n\nconst canManage = computed(() => context.can('pages.manage'))\n\n/*\n * Where the head stops being a row and becomes three. Decided by the width of the panel rather\n * than of the window, the same way the section's list decides between a table and cards.\n */\nconst NARROW = 720\n\nconst root = useTemplateRef<HTMLElement>('root')\nconst width = useElementWidth(root)\nconst narrow = computed(() => width.value > 0 && width.value < NARROW)\n\nconst current = computed(() => JSON.stringify(values.value))\nconst dirty = computed(() => snapshot.value !== '' && current.value !== snapshot.value)\n\n/** Saved · saving · not saved yet — the one line of the head that moves while typing. */\nconst state = computed<'saving' | 'unsaved' | 'saved'>(() => {\n if (saving.value) return 'saving'\n\n return dirty.value ? 'unsaved' : 'saved'\n})\n\n/**\n * The name in the head follows the field rather than the answer: an editor who renamed the\n * page in the settings tab should see the new name at the top before the save lands.\n */\nconst title = computed(() => {\n const written = values.value.title as LocalizedValue | string | null | undefined\n\n return localizedValue(written, locales.active.value, page.value?.title ?? '')\n})\n\nconst trail = computed(() =>\n ancestors.value.map((node) => ({\n id: node.id,\n label: node.is_home ? t('pages.home') : node.title,\n })),\n)\n\n/* The constructor shows the draft as a page of the site. The link is the editor's to hand\n over: only this screen knows which entity it is editing. */\nprovideBlocksPreview({ url: computed(() => previewUrl.value), reload: reloadToken })\n\nprovidePageEditor({\n page,\n ancestors,\n values,\n prefixes,\n base: props.base,\n canManage: canManage.value,\n reload: () => load(true),\n save: () => save(),\n})\n\nfunction take(detail: PageDetail): void {\n page.value = detail.page\n ancestors.value = detail.ancestors\n values.value = detail.values\n revision.value = detail.revision\n previewUrl.value = detail.preview_url\n prefixes.value = detail.address_prefix\n snapshot.value = JSON.stringify(detail.values)\n conflict.value = null\n}\n\n/**\n * Read the page again.\n *\n * Quietly unless this is the first time: the skeleton replaces the whole screen, tabs and all,\n * so a publication answered with it would throw an editor who published from the history back\n * onto the first tab — and flash the page on the way.\n */\nasync function load(silent = false): Promise<void> {\n if (!silent) loading.value = true\n\n try {\n take(await api.get(id.value))\n } catch (error) {\n toast.danger(message(error))\n void router.push(props.base)\n } finally {\n loading.value = false\n }\n}\n\n/* One pending save at a time, and one pending pause. */\nlet timer: ReturnType<typeof setTimeout> | undefined\n\nfunction schedule(): void {\n if (!canManage.value || conflict.value || !dirty.value) return\n\n clearTimeout(timer)\n timer = setTimeout(() => void save(), PAUSE)\n}\n\n/** A field was left: write now rather than at the end of a pause nobody is waiting through. */\nfunction onFocusOut(): void {\n if (!canManage.value || conflict.value || !dirty.value || saving.value) return\n\n clearTimeout(timer)\n void save()\n}\n\nasync function save(): Promise<void> {\n if (!page.value || saving.value || !canManage.value) return\n\n clearTimeout(timer)\n\n // What is being sent, so that whatever is typed while the request is in flight stays dirty\n // and gets its own save rather than being marked as written.\n const sending = current.value\n const sent = values.value\n\n saving.value = true\n errors.value = {}\n\n try {\n const detail = await api.save(page.value.id, { values: sent, revision: revision.value })\n\n page.value = detail.page\n ancestors.value = detail.ancestors\n revision.value = detail.revision\n previewUrl.value = detail.preview_url\n prefixes.value = detail.address_prefix\n conflict.value = null\n\n if (current.value === sending) snapshot.value = sending\n\n // The preview is rendered from the draft, and the draft is what was just written.\n reloadToken.value += 1\n } catch (error) {\n const failure = error as {\n status?: number\n body?: PageConflict & { errors?: Record<string, string[]> }\n }\n\n if (failure.status === 409 && failure.body?.data) {\n conflict.value = failure.body\n } else if (failure.body?.errors) {\n errors.value = failure.body.errors\n toast.danger(t('page.save-failed'))\n } else {\n toast.danger(message(error))\n }\n } finally {\n saving.value = false\n }\n}\n\n/** Give up what was typed and take the page as it now is (§6). */\nasync function takeTheirs(): Promise<void> {\n const agreed = await confirm({\n title: t('page.conflict-theirs-title'),\n message: t('page.conflict-theirs-text'),\n confirmText: t('page.conflict-theirs'),\n cancelText: t('page.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n await load(true)\n}\n\n/**\n * Keep what was typed and write it over the other version.\n *\n * Nothing is lost by it: the version that is about to be overwritten is in the history if it\n * was published, and in the autosave ring if it was not.\n */\nasync function keepMine(): Promise<void> {\n const theirs = conflict.value\n\n if (!theirs) return\n\n revision.value = theirs.data.revision\n conflict.value = null\n\n await save()\n}\n\n/**\n * Publishing is asked about, because taking a page off the site is (§14.2).\n *\n * It is the one action here that visitors see: everything else on this screen writes a draft\n * nobody outside the panel can read. The question names the address rather than counting\n * anything — one page goes on the site, and what matters is where.\n */\nasync function publish(): Promise<void> {\n if (!page.value) return\n\n const address = page.value.path === null ? null : `/${page.value.path}`\n\n const agreed = await confirm({\n title: t('page.publish-title', { title: page.value.title }),\n message:\n address === null ? t('page.publish-text-nowhere') : t('page.publish-text', { address }),\n confirmText: t('page.publish'),\n cancelText: t('page.cancel'),\n })\n\n if (!agreed) return\n\n if (dirty.value) await save()\n if (conflict.value) return\n\n working.value = true\n\n try {\n await api.publish(page.value.id)\n await load(true)\n toast.success(t('page.published'))\n } catch (error) {\n toast.danger(message(error))\n } finally {\n working.value = false\n }\n}\n\nfunction badge(): 'default' | 'success' | 'warning' {\n if (page.value?.status === 'published') return 'success'\n\n return page.value?.status === 'modified' ? 'warning' : 'default'\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\nwatch(current, schedule)\nwatch(id, () => void load())\n\nonMounted(() => {\n void load()\n window.addEventListener('beforeunload', leaveGuard)\n})\n\nonBeforeUnmount(() => {\n clearTimeout(timer)\n window.removeEventListener('beforeunload', leaveGuard)\n})\n\n/**\n * Leaving flushes the pause rather than asking about it: everything else on this screen saves\n * by itself, and a dialog that appears only because somebody left within a second of typing\n * would be the one place that does not. The question is asked only when the save did not go\n * through — a conflict, a refused field, a server that is not there.\n */\nonBeforeRouteLeave(async () => {\n if (!dirty.value || !canManage.value) return true\n\n await save()\n\n if (!dirty.value) return true\n\n return await confirm({\n title: t('page.leave-title'),\n message: t('page.leave-text'),\n confirmText: t('page.leave'),\n cancelText: t('page.cancel'),\n tone: 'danger',\n })\n})\n</script>\n\n<template>\n <!-- No `data-wx-fill`: this screen is a window tall only while the constructor is the tab\n on screen, and that is a question only CSS can ask (§10). `WxMain` still gives it a\n floor of one window, because it carries an action bar. -->\n <div ref=\"root\" class=\"wx-page-editor\" @focusout=\"onFocusOut\">\n <template v-if=\"loading || !page\">\n <wx-skeleton class=\"wx-page-editor__ghost\" title :rows=\"1\" />\n <wx-skeleton :rows=\"8\" />\n </template>\n\n <template v-else>\n <div class=\"wx-page-editor__head\">\n <!-- The trail says where the reader is; this is the way out of it, and on a phone it\n is the only one that is a control rather than four words in the smallest type. -->\n <wx-back-button class=\"wx-page-editor__back\" :to=\"base\" :label=\"t('module.title')\" />\n\n <div class=\"wx-page-editor__id\">\n <wx-breadcrumb size=\"sm\" :label=\"t('page.trail')\">\n <wx-breadcrumb-item :as=\"'router-link'\" :to=\"base\">\n {{ t('module.title') }}\n </wx-breadcrumb-item>\n <wx-breadcrumb-item\n v-for=\"node in trail\"\n :key=\"node.id\"\n :as=\"'router-link'\"\n :to=\"`${base}/${node.id}`\"\n >\n {{ node.label }}\n </wx-breadcrumb-item>\n <wx-breadcrumb-item current>\n {{ page.is_home ? t('pages.home') : title }}\n </wx-breadcrumb-item>\n </wx-breadcrumb>\n\n <div class=\"wx-page-editor__name\">\n <span class=\"wx-page-editor__title\">{{\n page.is_home ? t('pages.home') : title || t('page.untitled')\n }}</span>\n <wx-badge :type=\"badge()\" dot>{{ t(`page.status-${page.status}`) }}</wx-badge>\n </div>\n </div>\n\n <!--\n What is left in the head is what leads away from the page: the draft on the site and\n the page on the site. Saving and publishing are down in the bar, where the eye is —\n and only there, because this screen is exactly as tall as the window and the head\n never leaves it. A second copy of a button already on screen is not a reminder.\n -->\n <div class=\"wx-page-editor__actions\">\n <!-- Narrow: the two links fold into a menu. A head three rows tall is a fifth of a\n phone screen given to buttons above the thing being edited. -->\n <wx-dropdown v-if=\"narrow\" align=\"end\">\n <template #trigger>\n <wx-action type=\"more\" :title=\"t('page.more')\" />\n </template>\n <wx-dropdown-item\n v-if=\"previewUrl\"\n icon=\"eye\"\n :href=\"previewUrl\"\n target=\"_blank\"\n rel=\"noopener\"\n >\n {{ t('page.preview') }}\n </wx-dropdown-item>\n <wx-dropdown-item\n v-if=\"page.url && page.status !== 'draft'\"\n icon=\"link\"\n :href=\"page.url\"\n target=\"_blank\"\n rel=\"noopener\"\n >\n {{ t('page.open-on-site') }}\n </wx-dropdown-item>\n </wx-dropdown>\n\n <template v-else>\n <wx-button\n v-if=\"previewUrl\"\n variant=\"outline\"\n icon=\"eye\"\n :href=\"previewUrl\"\n target=\"_blank\"\n rel=\"noopener\"\n >\n {{ t('page.preview') }}\n </wx-button>\n <wx-button\n v-if=\"page.url && page.status !== 'draft'\"\n variant=\"outline\"\n icon=\"link\"\n :href=\"page.url\"\n target=\"_blank\"\n rel=\"noopener\"\n >\n {{ t('page.open-on-site') }}\n </wx-button>\n </template>\n </div>\n </div>\n\n <!-- Somebody else wrote while this editor was typing. Both versions still exist, so the\n question is which one the site gets — and it is a question, not a toast that\n disappears while the answer is being thought about. -->\n <wx-alert\n v-if=\"conflict\"\n type=\"warning\"\n :title=\"t('page.conflict-title')\"\n :description=\"conflict.message\"\n >\n <template #actions>\n <wx-button size=\"sm\" variant=\"outline\" @click=\"takeTheirs\">\n {{ t('page.conflict-theirs') }}\n </wx-button>\n <wx-button size=\"sm\" type=\"primary\" @click=\"keepMine\">\n {{ t('page.conflict-mine') }}\n </wx-button>\n </template>\n </wx-alert>\n\n <div class=\"wx-page-editor__screen\">\n <wx-screen\n v-model=\"values\"\n name=\"pages.form\"\n :errors=\"errors\"\n :disabled=\"!canManage || working\"\n />\n </div>\n\n <!--\n The last row of the screen, not a layer over it: the preview above shrinks by the height\n of the bar and is never covered by it. Nothing scrolls on this screen, so the bar never\n has to stick to anything — it is already where sticking would put it.\n -->\n <wx-action-bar v-if=\"canManage\">\n <template #state>\n <wx-text size=\"sm\" tone=\"muted\">{{ t(`page.state-${state}`) }}</wx-text>\n </template>\n\n <wx-button variant=\"outline\" :loading=\"saving\" :disabled=\"!dirty\" @click=\"save\">\n {{ t('page.save') }}\n </wx-button>\n\n <!-- Only the forward action is here; taking a page off the site and deleting it live\n in the settings tab, where nothing is one slip away from the save button. -->\n <wx-button\n type=\"primary\"\n :loading=\"working\"\n :disabled=\"page.status === 'published' && !dirty\"\n @click=\"publish\"\n >\n {{ t('page.publish') }}\n </wx-button>\n </wx-action-bar>\n </template>\n </div>\n</template>\n\n<style scoped>\n.wx-page-editor {\n display: flex;\n flex-direction: column;\n gap: var(--wx-gap, var(--wx-space-16));\n /* The constructor is the whole screen below the head, so the page is as tall as what it\n is drawn in and never taller: the tree, the form and the preview scroll inside\n themselves, and the head stays where it was put (§10). */\n height: 100%;\n min-height: 0;\n container-type: inline-size;\n}\n\n.wx-page-editor__ghost {\n max-width: 420px;\n}\n\n/*\n * The head stays put by standing still: the screen is as tall as its column, so nothing under\n * it scrolls the page — each tab scrolls inside itself. A sticky bar would be a bar that never\n * has anything to stick to.\n */\n/*\n * Three columns: the way out, what the page is called, and what leads away from it.\n *\n * A grid rather than the flex row this was: as flex items the three parts were sized from\n * their own content, and on a phone the menu — one 30px button — claimed 225px of a 359px head\n * while the name was squeezed to 81 and wrapped away from its badge.\n *\n * Everything sits on the bottom line, which is the name. The trail above it is the smaller\n * type, and a button lined up with that hangs over the title.\n */\n.wx-page-editor__head {\n flex: none;\n display: grid;\n grid-template-columns: auto minmax(0, 1fr) auto;\n align-items: end;\n gap: var(--wx-space-12);\n padding-block-end: var(--wx-space-12);\n border-block-end: 1px solid var(--wx-border-default);\n}\n\n/* The arrow belongs to the trail, which is the top line. */\n.wx-page-editor__back {\n align-self: start;\n}\n\n.wx-page-editor__id {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-4);\n flex: 1 1 260px;\n min-width: 0;\n}\n\n.wx-page-editor__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-page-editor__title {\n font-size: var(--wx-font-size-xl);\n font-weight: var(--wx-font-weight-bold);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.wx-page-editor__actions {\n display: flex;\n align-items: center;\n gap: var(--wx-space-8);\n flex-wrap: wrap;\n}\n\n.wx-page-editor__screen {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-height: 0;\n}\n\n/*\n * The tabs are the screen's, not this page's, so the height has to travel through them: every\n * box between the page and the constructor is a flex column that may shrink, or the\n * constructor sizes itself to its content and the whole page scrolls with the head on top of\n * it — which is the one thing §10 is about.\n */\n.wx-page-editor__screen :deep(.wx-screen-host),\n.wx-page-editor__screen :deep(.wx-screen),\n.wx-page-editor__screen :deep(.wx-tabs),\n.wx-page-editor__screen :deep(.wx-tabs__layout),\n.wx-page-editor__screen :deep(.wx-tabs__panels),\n.wx-page-editor__screen :deep(.wx-tab:not([hidden])) {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-height: 0;\n}\n\n/*\n * The tab that is not shown keeps its `display: none`. Said out loud because the rule above\n * would take it away: the tabs are kept alive, so the three that are hidden are still in the\n * document, and a `display: flex` that reaches them makes each one a quarter of the height the\n * one on screen should have had. Measured — a constructor 181px tall in a 740px column.\n */\n\n/*\n * Only the tab that holds the constructor is a box of a fixed height with its own scrollbar.\n *\n * Everywhere else the tab grows with its content and the page scrolls, because a scroll box\n * clips: the cards inside one had their shadows cut off square at all four edges, which reads\n * as a drawing fault rather than as a scrolling region. The screen is exactly a window tall\n * for the same reason and under the same condition — `WxMain` keeps a screen with an action\n * bar at least that tall anyway, which is what holds the bar at the bottom of a short tab.\n */\n.wx-page-editor:has(.wx-tab:not([hidden]) .wx-blocks-host.is-fill) {\n height: var(--wx-fill-height);\n}\n\n.wx-page-editor__screen :deep(.wx-tab:not([hidden]):has(.wx-blocks-host.is-fill)) {\n overflow: auto;\n}\n\n/*\n * The renderer wraps every field in a form item, so the height has to travel through that as\n * well — and through that one only. `:has()` picks the wrapper of a field that fills and leaves\n * the ordinary fields of the settings tab the height of their own contents.\n */\n.wx-page-editor__screen :deep(.wx-form-item:has(.wx-blocks-host.is-fill)),\n.wx-page-editor__screen :deep(.wx-form-item:has(.wx-blocks-host.is-fill) .wx-form-item__control) {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-height: 0;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\nimport { useAdmin, useErrorText, useTranslate, WxDate } from '@webx-ui/module-admin'\nimport { confirm, toast, WxBadge, WxButton, WxEmpty, WxSkeleton, WxText } from '@webx-ui/core'\nimport { createPagesApi } from './api'\nimport { usePageEditor } from './editor'\nimport { usePagesMessages } from './i18n'\nimport type { PageVersion } from './types'\n\n/**\n * What was published, when, by whom and from where.\n *\n * Publications only. The autosaves a save writes are insurance rather than history — a ring of\n * the last few copies of the draft, replaced every couple of minutes — and a list with them in\n * it would be a list nobody can read.\n *\n * Restoring makes the old version the draft. Publishing it is the same separate step it always\n * is, which is what keeps the history a line: nothing here changes the site by itself.\n */\ndefineOptions({ name: 'WxPageHistory' })\n\nconst context = useAdmin()\nconst api = createPagesApi(context)\nconst editor = usePageEditor()\nusePagesMessages()\n\nconst t = useTranslate('webx-pages')\n/* Not the server's `message`: the panel says how a request failed in its own words (§13.3). */\nconst message = useErrorText()\n\nconst versions = ref<PageVersion[] | null>(null)\nconst working = ref(false)\n\nconst page = computed(() => editor?.page.value ?? null)\nconst canManage = computed(() => context.can('pages.manage'))\n\n/**\n * Which line is the site: the newest publication, and only while the page is on the site at\n * all. A page taken down still has its history, and none of it is live.\n */\nconst live = computed(() =>\n page.value?.published_at ? (versions.value?.[0]?.number ?? null) : null,\n)\n\nasync function load(): Promise<void> {\n const current = page.value\n\n if (!current) return\n\n try {\n versions.value = await api.versions(current.id)\n } catch (error) {\n versions.value = []\n toast.danger(message(error))\n }\n}\n\nasync function restore(version: PageVersion): Promise<void> {\n const current = page.value\n\n if (!current) return\n\n const agreed = await confirm({\n title: t('page.restore-title', { number: version.number }),\n message: t('page.restore-text'),\n confirmText: t('page.restore'),\n cancelText: t('page.cancel'),\n })\n\n if (!agreed) return\n\n working.value = true\n\n try {\n await api.restoreVersion(current.id, version.number)\n // Through the editor rather than with the answer: the values it holds are what the form is\n // bound to, and a version restored underneath it has to reach the fields.\n await editor?.reload()\n toast.success(t('page.restored-version', { number: version.number }))\n } catch (error) {\n toast.danger(message(error))\n } finally {\n working.value = false\n }\n}\n\n// The page is reloaded after every publication and every restore, so its stamp is the signal\n// that there is something new to list.\nwatch(\n () => [page.value?.id, page.value?.published_at, page.value?.updated_at],\n () => void load(),\n {\n immediate: true,\n },\n)\n</script>\n\n<template>\n <div class=\"wx-page-history\">\n <!-- Where a row would stand, not against the border: a skeleton flush with a rounded\n corner has its ends clipped by it, which reads as a drawing fault rather than as\n something loading. -->\n <wx-skeleton v-if=\"versions === null\" class=\"wx-page-history__ghost\" :rows=\"3\" />\n <wx-empty v-else-if=\"versions.length === 0\" :description=\"t('page.history-empty')\" />\n <div\n v-for=\"version in versions\"\n v-else\n :key=\"version.number\"\n class=\"wx-page-history__row\"\n :class=\"{ 'is-live': version.number === live }\"\n >\n <code class=\"wx-page-history__number\">{{\n t('page.version', { number: version.number })\n }}</code>\n <div class=\"wx-page-history__who\">\n <wx-date v-if=\"version.created_at\" :value=\"version.created_at\" size=\"md\" tone=\"default\" />\n <wx-text size=\"sm\" tone=\"muted\">\n {{ version.author ?? t(`page.source-${version.source}`) }}\n <template v-if=\"version.comment\"> · {{ version.comment }}</template>\n </wx-text>\n </div>\n <wx-badge v-if=\"version.number === live\" type=\"success\" dot>{{\n t('page.version-live')\n }}</wx-badge>\n <wx-button\n v-if=\"canManage && version.number !== live\"\n size=\"sm\"\n variant=\"outline\"\n :loading=\"working\"\n @click=\"restore(version)\"\n >\n {{ t('page.restore') }}\n </wx-button>\n </div>\n </div>\n</template>\n\n<style scoped>\n.wx-page-history {\n display: flex;\n flex-direction: column;\n border: 1px solid var(--wx-border-default);\n border-radius: var(--wx-radius-md);\n background: var(--wx-bg-surface);\n /* Nothing reaches past the rounding: a row, or the skeleton standing in for three of them,\n is a square box and its corners showed through the panel's. */\n overflow: hidden;\n}\n\n.wx-page-history__ghost {\n padding: var(--wx-space-10) var(--wx-space-12);\n}\n\n.wx-page-history__row {\n display: flex;\n align-items: center;\n gap: var(--wx-space-12);\n padding: var(--wx-space-10) var(--wx-space-12);\n border-block-end: 1px solid var(--wx-border-default);\n flex-wrap: wrap;\n}\n\n.wx-page-history__row:last-child {\n border-block-end: 0;\n}\n\n.wx-page-history__number {\n width: 40px;\n font-family: var(--wx-font-family-mono);\n font-size: var(--wx-font-size-sm);\n color: var(--wx-text-muted);\n}\n\n.wx-page-history__who {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-width: 160px;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { onMounted, ref } from 'vue'\nimport { useAdmin, useTranslate } from '@webx-ui/module-admin'\nimport { WxTreeSelect, type TreeNode } from '@webx-ui/core'\nimport { createPagesApi } from './api'\nimport { usePagesMessages } from './i18n'\nimport type { PageRow } from './types'\n\n/**\n * A page chosen out of the tree — where a new page goes, or where a moved one lands.\n *\n * Lazy, like the section's own list: a catalogue is fetched a level at a time, and a picker\n * that pulled the whole tree down to offer one parent would be the one screen of the section\n * that does not.\n */\nconst props = withDefaults(\n defineProps<{\n /** Pages that may not be chosen — the page being moved, above all. */\n exclude?: number[]\n placeholder?: string\n /** The page the value names, with its ancestors, so a lazy tree can show it unopened. */\n selected?: PageRow[]\n }>(),\n { exclude: () => [], placeholder: undefined, selected: () => [] },\n)\n\nconst value = defineModel<number | null>({ default: null })\n\nconst context = useAdmin()\nconst api = createPagesApi(context)\nusePagesMessages()\n\nconst t = useTranslate('webx-pages')\n\nconst nodes = ref<TreeNode[]>([])\n\n/**\n * The home page is the root of the picker as well as of the tree: everything on the site is\n * inside it, so \"at the top level\" and \"inside the home page\" are the same choice, and offering\n * both would be offering one thing twice.\n */\nonMounted(async () => {\n const level = await api.list()\n\n if (level.home)\n nodes.value = [\n toNode(\n level.home,\n level.items.map((page) => toNode(page)),\n ),\n ]\n})\n\nfunction toNode(page: PageRow, children?: TreeNode[]): TreeNode {\n return {\n id: page.id,\n label: page.is_home ? t('pages.home') : page.title,\n disabled: props.exclude.includes(page.id),\n leaf: page.children_count === 0,\n ...(children ? { children } : {}),\n }\n}\n\nasync function load(node: TreeNode): Promise<TreeNode[]> {\n const level = await api.list({ parent: Number(node.id) })\n\n return level.items.map((page) => toNode(page))\n}\n</script>\n\n<template>\n <wx-tree-select\n v-model=\"value\"\n :nodes=\"nodes\"\n lazy\n :load=\"load\"\n show-path\n clearable\n :selected-path=\"\n props.selected.map((page) => ({\n id: page.id,\n label: page.is_home ? t('pages.home') : page.title,\n }))\n \"\n :placeholder=\"props.placeholder ?? t('page.field-parent')\"\n :aria-label=\"t('page.field-parent')\"\n teleport\n />\n</template>\n","<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\nimport { useAdmin, useErrorText, useTranslate } from '@webx-ui/module-admin'\nimport { confirm, toast, useLocales, WxAlert, WxFormItem, WxText } from '@webx-ui/core'\nimport PagePicker from './PagePicker.vue'\nimport { createPagesApi } from './api'\nimport { usePageEditor } from './editor'\nimport { usePagesMessages } from './i18n'\n\n/**\n * Where the page sits: the whole address it answers at, and the page it is inside.\n *\n * The address is shown built rather than left as a slug in a box. `shoes` says nothing about\n * whether the page is under the catalogue or at the top of the site, and the tree is exactly\n * what an editor is deciding when they look at this — so the field edits the last segment and\n * this prints the sentence it is part of, following the field as it is typed.\n *\n * Moving is not a draft. The tree is applied at once, because a page that sits in two places\n * until it is published is a tree no screen can draw — so the picker asks the server there and\n * then, and the warning beside it says what that costs.\n */\ndefineOptions({ name: 'WxPagePlace' })\n\nconst context = useAdmin()\nconst api = createPagesApi(context)\nconst editor = usePageEditor()\nconst locales = useLocales()\nusePagesMessages()\n\nconst t = useTranslate('webx-pages')\n/* Not the server's `message`: the panel says how a request failed in its own words (§13.3). */\nconst message = useErrorText()\n\nconst moving = ref(false)\nconst parent = ref<number | null>(null)\n\nconst page = computed(() => editor?.page.value ?? null)\n\n/**\n * The address of the page above in the language being edited, or `null` where it has none.\n *\n * The home page's is `''` — the front page of the site — so the two are not the same answer\n * and cannot both be an empty string.\n */\nconst prefix = computed<string | null>(() => {\n if (editor?.ancestors.value.length === 0) return null\n\n return editor?.prefixes.value[locales.active.value] ?? null\n})\n\n/** The last segment as it stands in the field, not as the registry last heard it. */\nconst slug = computed(() => {\n const written = editor?.values.value.slug\n\n if (typeof written === 'string') return written\n\n // The language being edited and no other. Falling back on a language that does have words\n // would print an address the site does not answer at (§8).\n return (written as Record<string, string> | undefined)?.[locales.active.value] ?? ''\n})\n\nconst address = computed(() => {\n if (page.value?.is_home) return '/'\n if (prefix.value === null || slug.value === '') return null\n\n return `/${[prefix.value, slug.value].filter(Boolean).join('/')}`\n})\n\nwatch(\n page,\n (current) => {\n parent.value = current?.parent_id ?? null\n },\n { immediate: true },\n)\n\n/**\n * The same question a drop in the list asks, and for the same reason (§14.3): a move rewrites\n * the address of the page and of everything under it, and leaves a redirect on each of the old\n * ones. Picking a parent from a list is deliberate enough that one page moving alone goes\n * through without a dialog; a branch does not.\n */\nasync function move(target: number | null): Promise<void> {\n const current = page.value\n\n if (!current || target === null || target === current.parent_id || moving.value) return\n\n const moves = current.descendants_count + 1\n\n if (moves > 1) {\n const agreed = await confirm({\n title: t('page.move-title', { title: current.title }),\n message: t('page.move-branch', { count: moves }),\n confirmText: t('page.move-confirm'),\n cancelText: t('page.cancel'),\n })\n\n if (!agreed) {\n parent.value = current.parent_id\n\n return\n }\n }\n\n moving.value = true\n\n try {\n const result = await api.move(current.id, target, 'inside')\n\n toast.success(\n result.addresses_changed > 1\n ? t('page.moved', { count: result.addresses_changed })\n : t('page.moved-one'),\n )\n await editor?.reload()\n } catch (error) {\n toast.danger(message(error))\n parent.value = current.parent_id\n } finally {\n moving.value = false\n }\n}\n</script>\n\n<template>\n <div v-if=\"page\" class=\"wx-page-place\">\n <wx-form-item :label=\"t('page.address')\">\n <wx-text v-if=\"address\" mono class=\"wx-page-place__address\">{{ address }}</wx-text>\n <wx-text v-else size=\"sm\" tone=\"muted\">{{ t('page.no-address') }}</wx-text>\n </wx-form-item>\n\n <template v-if=\"page.can.move\">\n <wx-form-item :label=\"t('page.field-parent')\" :help=\"t('page.parent-help')\">\n <page-picker\n v-model=\"parent\"\n :exclude=\"[page.id]\"\n :selected=\"editor?.ancestors.value ?? []\"\n @update:model-value=\"move\"\n />\n </wx-form-item>\n\n <!-- Said before the move rather than after it: the old addresses keep working as\n redirects, which is the part that decides whether this is safe to do on a live\n site, and nobody reads it in a toast that has already gone. -->\n <wx-alert type=\"info\" variant=\"soft\" :description=\"t('page.parent-warning')\" />\n </template>\n </div>\n</template>\n\n<style scoped>\n.wx-page-place {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-8);\n}\n\n.wx-page-place__address {\n word-break: break-all;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { useTranslate, WxRowMenu, type RowAction } from '@webx-ui/module-admin'\nimport { usePagesMessages } from './i18n'\nimport type { PageRow } from './types'\n\n/**\n * What a row of the page tree offers.\n *\n * A list of actions rather than markup, handed to the panel's one row menu: a table cell and\n * a card on a phone both get the same `···` in the same place, and so does every other\n * section (§20). What is written here is only which actions this row has — a page that is the\n * home page has no copy and nowhere else to be, and a page in the bin has one way out.\n *\n * Nothing here is offered and then refused: an action somebody has no right to is left out.\n */\nconst props = withDefaults(defineProps<{ page: PageRow; inBin?: boolean }>(), { inBin: false })\n\nconst emit = defineEmits<{\n open: [page: PageRow]\n add: [page: PageRow]\n duplicate: [page: PageRow]\n move: [page: PageRow]\n copy: [page: PageRow]\n remove: [page: PageRow]\n restore: [page: PageRow]\n}>()\n\nusePagesMessages()\n\nconst t = useTranslate('webx-pages')\n\nconst actions = computed<RowAction[]>(() => {\n const page = props.page\n\n if (props.inBin) {\n return [\n {\n key: 'restore',\n icon: 'refresh',\n label: t('page.restore'),\n run: () => emit('restore', page),\n },\n ]\n }\n\n const list: RowAction[] = [\n { key: 'open', icon: 'edit', label: t('page.open'), run: () => emit('open', page) },\n { key: 'add', icon: 'plus', label: t('page.add-child'), run: () => emit('add', page) },\n ]\n\n // The home page has no copy and no other place to be: the two actions that would make one\n // are simply not offered rather than offered and refused.\n if (!page.is_home) {\n list.push({\n key: 'duplicate',\n icon: 'copy',\n label: t('page.duplicate'),\n run: () => emit('duplicate', page),\n })\n }\n\n if (page.can.move) {\n list.push({ key: 'move', icon: 'drag', label: t('page.move'), run: () => emit('move', page) })\n }\n\n list.push(\n {\n key: 'copy-address',\n icon: 'link',\n label: t('page.copy-address'),\n disabled: !page.url,\n run: () => emit('copy', page),\n },\n {\n key: 'on-site',\n icon: 'external-link',\n label: t('page.open-on-site'),\n disabled: !page.url,\n href: page.url ?? undefined,\n },\n )\n\n if (page.can.delete) {\n list.push({\n key: 'delete',\n icon: 'trash',\n label: t('page.delete'),\n danger: true,\n run: () => emit('remove', page),\n })\n }\n\n return list\n})\n</script>\n\n<template>\n <wx-row-menu :actions=\"actions\" :label=\"props.page.title\" />\n</template>\n","/**\n * Cyrillic and the other alphabets the panel is translated into, as the Latin a URL can hold.\n *\n * The server transliterates too, and the same way — `Str::slug` is what actually decides the\n * address. This is the field following the title as it is typed, so that what the editor sees\n * before saving is what they get; where the two ever disagree, the server wins and the address\n * that comes back is the address.\n */\nconst TRANSLITERATION: Record<string, string> = {\n а: 'a',\n б: 'b',\n в: 'v',\n г: 'g',\n ґ: 'g',\n д: 'd',\n е: 'e',\n ё: 'e',\n є: 'ie',\n ж: 'zh',\n з: 'z',\n и: 'i',\n і: 'i',\n ї: 'i',\n й: 'i',\n к: 'k',\n л: 'l',\n м: 'm',\n н: 'n',\n о: 'o',\n п: 'p',\n р: 'r',\n с: 's',\n т: 't',\n у: 'u',\n ф: 'f',\n х: 'h',\n ц: 'ts',\n ч: 'ch',\n ш: 'sh',\n щ: 'shch',\n ъ: '',\n ы: 'y',\n ь: '',\n э: 'e',\n ю: 'iu',\n я: 'ia',\n ä: 'a',\n ö: 'o',\n ü: 'u',\n ß: 'ss',\n å: 'a',\n ø: 'o',\n æ: 'ae',\n ç: 'c',\n ğ: 'g',\n ı: 'i',\n ş: 's',\n ą: 'a',\n ć: 'c',\n ę: 'e',\n ł: 'l',\n ń: 'n',\n ó: 'o',\n ś: 's',\n ź: 'z',\n ż: 'z',\n}\n\n/** A title as the last part of an address: lower case, Latin, words joined by hyphens. */\nexport function slugify(title: string): string {\n return (\n title\n .toLowerCase()\n .split('')\n .map((character) => TRANSLITERATION[character] ?? character)\n .join('')\n // Accents that survived the map — `é`, `à` — come apart into a letter and a mark, and the\n // mark is what has to go; `é` must not become `-`.\n .normalize('NFD')\n .replace(/[̀-ͯ]/g, '')\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n )\n}\n","<script setup lang=\"ts\">\nimport { nextTick, onMounted, ref, useTemplateRef, watch } from 'vue'\nimport { useAdmin, useTranslate } from '@webx-ui/module-admin'\nimport { useModal, WxButton, WxDialog, WxFormItem, WxInput, WxSpace } from '@webx-ui/core'\nimport PagePicker from './PagePicker.vue'\nimport { createPagesApi } from './api'\nimport { usePagesMessages } from './i18n'\nimport { slugify } from './slug'\nimport type { PageRow } from './types'\n\n/**\n * A new page: what it is called, where it lives, and what it is called in the address.\n *\n * The address follows the title while nobody has touched it, and stops the moment somebody\n * does — a field that keeps rewriting what was typed into it is a field people fight.\n */\nconst props = withDefaults(defineProps<{ parent?: PageRow | null }>(), { parent: null })\n\nconst { open, resolve, dismiss } = useModal<PageRow>()\n\nconst context = useAdmin()\nconst api = createPagesApi(context)\nusePagesMessages()\n\nconst t = useTranslate('webx-pages')\n\nconst title = ref('')\nconst slug = ref('')\nconst parentId = ref<number | null>(props.parent?.id ?? null)\nconst saving = ref(false)\nconst errors = ref<Record<string, string[]>>({})\nconst field = useTemplateRef<HTMLElement>('field')\n\nconst edited = ref(false)\n\nwatch(title, (value) => {\n if (!edited.value) slug.value = slugify(value)\n})\n\nonMounted(async () => {\n await nextTick()\n field.value?.querySelector('input')?.focus()\n})\n\nasync function submit(): Promise<void> {\n if (title.value.trim() === '' || saving.value) return\n\n saving.value = true\n errors.value = {}\n\n try {\n resolve(\n await api.create({\n title: title.value.trim(),\n slug: slug.value.trim(),\n parent_id: parentId.value,\n }),\n )\n } catch (error) {\n // The address is the one thing that can be refused here, and it is refused under its own\n // field: the registry sees every kind of entity, so \"taken\" can mean taken by a product.\n errors.value = (error as { body?: { errors?: Record<string, string[]> } }).body?.errors ?? {}\n } finally {\n saving.value = false\n }\n}\n</script>\n\n<template>\n <wx-dialog v-model:open=\"open\" :title=\"t('page.new-title')\" :width=\"460\">\n <wx-form-item :label=\"t('page.field-title')\" :error=\"errors.title?.[0]\" required>\n <div ref=\"field\">\n <wx-input v-model=\"title\" :aria-label=\"t('page.field-title')\" @keyup.enter=\"submit\" />\n </div>\n </wx-form-item>\n\n <wx-form-item\n :label=\"t('page.field-slug')\"\n :error=\"errors.slug?.[0]\"\n :help=\"t('page.slug-help')\"\n >\n <wx-input\n v-model=\"slug\"\n :aria-label=\"t('page.field-slug')\"\n @input=\"edited = true\"\n @keyup.enter=\"submit\"\n />\n </wx-form-item>\n\n <wx-form-item :label=\"t('page.field-parent')\" :error=\"errors.parent_id?.[0]\">\n <page-picker v-model=\"parentId\" :selected=\"props.parent ? [props.parent] : []\" />\n </wx-form-item>\n\n <template #footer>\n <wx-space size=\"sm\">\n <wx-button variant=\"outline\" @click=\"dismiss()\">{{ t('page.cancel') }}</wx-button>\n <wx-button type=\"primary\" :loading=\"saving\" :disabled=\"title.trim() === ''\" @click=\"submit\">\n {{ t('page.create') }}\n </wx-button>\n </wx-space>\n </template>\n </wx-dialog>\n</template>\n","<script setup lang=\"ts\">\nimport { ref } from 'vue'\nimport { useTranslate } from '@webx-ui/module-admin'\nimport { useModal, WxAlert, WxButton, WxDialog, WxSpace } from '@webx-ui/core'\nimport PagePicker from './PagePicker.vue'\nimport { usePagesMessages } from './i18n'\nimport type { PageRow } from './types'\n\n/**\n * Where a page goes, chosen from a tree rather than dragged.\n *\n * Dragging is the quick way and it is in the table already; this is the one that works — on a\n * touch screen, and in a catalogue where the page and its new parent are four screens apart.\n *\n * Inside, and only inside. Order among siblings is what dragging is for, and a dialog that also\n * asked \"before or after what\" would be asking about the one thing nobody opened it for.\n */\nconst props = defineProps<{ page: PageRow }>()\n\nconst { open, resolve, dismiss } = useModal<number>()\n\nusePagesMessages()\n\nconst t = useTranslate('webx-pages')\n\nconst target = ref<number | null>(props.page.parent_id)\n</script>\n\n<template>\n <wx-dialog\n v-model:open=\"open\"\n :title=\"t('page.move-title', { title: props.page.title })\"\n :width=\"460\"\n >\n <wx-space direction=\"vertical\" size=\"md\" fill>\n <wx-alert type=\"info\" variant=\"soft\" :description=\"t('page.move-help')\" />\n\n <!-- Its own branch is not a place it can go, and the page itself least of all. The rest\n of the branch is refused by the server, which is the only side that knows the whole\n tree without fetching it. -->\n <page-picker v-model=\"target\" :exclude=\"[props.page.id]\" />\n </wx-space>\n\n <template #footer>\n <wx-space size=\"sm\">\n <wx-button variant=\"outline\" @click=\"dismiss()\">{{ t('page.cancel') }}</wx-button>\n <wx-button\n type=\"primary\"\n :disabled=\"target === null || target === props.page.parent_id\"\n @click=\"resolve(target!)\"\n >\n {{ t('page.move-confirm') }}\n </wx-button>\n </wx-space>\n </template>\n </wx-dialog>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, onMounted, ref, watch } from 'vue'\nimport { useRouter } from 'vue-router'\nimport { useAdmin, useErrorText, useTranslate, WxDate, WxListScreen } from '@webx-ui/module-admin'\nimport {\n confirm,\n createModal,\n toast,\n WxBadge,\n WxButton,\n WxTable,\n WxText,\n type TabItem,\n type TableColumn,\n type TableNodeDropEvent,\n type TableState,\n type TableTreeOptions,\n type TabValue,\n type TreeDropZone,\n} from '@webx-ui/core'\nimport PageActions from './PageActions.vue'\nimport PageCreateDialog from './PageCreateDialog.vue'\nimport PageMoveDialog from './PageMoveDialog.vue'\nimport { createPagesApi } from './api'\nimport { usePagesMessages } from './i18n'\nimport type { PageRow, PageStatus } from './types'\n\n/**\n * The section: the site's pages as a tree, a level at a time.\n *\n * The home page is pinned at the top and its children are the top level, because every page of\n * the site is inside it — drawn as a branch, every row of the table would carry one step of\n * indentation that says nothing (§9).\n *\n * Searching puts the tree away. A branch drawn for the sake of one match deep inside it tells\n * the reader nothing they asked about, so a search is a flat list of matches with the address\n * under each.\n */\nconst props = withDefaults(defineProps<{ base?: string }>(), { base: '/pages' })\n\nconst context = useAdmin()\nconst api = createPagesApi(context)\nconst router = useRouter()\nusePagesMessages()\n\nconst t = useTranslate('webx-pages')\n/* Not the server's `message`: the panel says how a request failed in its own words (§13.3). */\nconst message = useErrorText()\n\nconst home = ref<PageRow | null>(null)\nconst items = ref<PageRow[]>([])\nconst loading = ref(true)\nconst search = ref('')\n/* A tab's value is a string; what the server is asked for is read back out of it. */\nconst filter = ref<TabValue>('')\n\nconst create = createModal<PageRow, { parent: PageRow | null }>(PageCreateDialog)\nconst pickTarget = createModal<number, { page: PageRow }>(PageMoveDialog)\n\nconst canManage = computed(() => context.can('pages.manage'))\nconst inBin = computed(() => filter.value === 'trashed')\n/**\n * A search and the bin are flat lists; everything else is a level of the tree, at every width.\n *\n * A narrow screen used to get a flat list instead, on the grounds that cards have no\n * indentation to read and no chevron to open. The cards were the mistake, not the tree (§11):\n * the table stays a table and drops columns until only the title and the `···` are left, so the\n * tree is walked on a phone the same way it is walked on a desk.\n */\nconst asTree = computed(() => !inBin.value && search.value.trim() === '')\n\nconst title = computed(\n () =>\n context.state.manifest?.modules.find((module) => module.id === 'pages')?.title ??\n t('module.title'),\n)\n\n/*\n * The views of the same list, as tabs over the card (§10). The bin rides in the same row with\n * an icon rather than standing apart: it is still this list, and a reader who wants what they\n * deleted looks for it beside what they did not.\n */\nconst views = computed<TabItem[]>(() => [\n { value: '', label: t('page.filter-all') },\n { value: 'draft', label: t('page.filter-draft') },\n { value: 'published', label: t('page.filter-published') },\n { value: 'modified', label: t('page.filter-modified') },\n { value: 'trashed', label: t('page.bin'), icon: 'trash' },\n])\n\n/**\n * The home page rides in the rows rather than beside them, with no children of its own: what\n * would hang under it is already the level below, and a chevron that opened a copy of the list\n * is the one thing worse than no chevron.\n */\nconst rows = computed<PageRow[]>(() =>\n asTree.value && home.value ? [{ ...home.value, children_count: 0 }, ...items.value] : items.value,\n)\n\n/**\n * Every column but the title is given a width, and the table is laid out `fixed`, so the\n * widths that are hidden below a breakpoint give what they had to the title rather than to\n * whichever column happened to hold the longest string.\n *\n * They go in the order they can be spared — when it was last touched, then what state it is in,\n * then where it lives — and below the narrowest of them the row is the title and the `···`,\n * which is the whole of §11: two columns, still a tree.\n */\nconst columns = computed<TableColumn<PageRow>[]>(() => [\n { key: 'title', label: t('page.column-title'), minWidth: 200 },\n { key: 'path', label: t('page.column-address'), width: 200, hideBelow: 560 },\n // In the bin the state of a page is not what it was published as — it is off the site either\n // way — so the column says when it went in, which is what decides whether to bring it back.\n {\n key: 'status',\n label: inBin.value ? t('page.column-deleted') : t('page.column-status'),\n align: 'center',\n width: 130,\n hideBelow: 620,\n },\n {\n key: 'updated_at',\n label: t('page.column-updated'),\n width: 180,\n hideBelow: 900,\n hidden: inBin.value,\n },\n { key: 'actions', label: '', width: 56, align: 'right' },\n])\n\nconst tree = computed<TableTreeOptions<PageRow> | undefined>(() =>\n asTree.value\n ? {\n childrenKey: 'children',\n hasChildrenKey: 'children_count',\n lazy: true,\n load: (row) => api.list({ parent: row.id }).then((level) => level.items),\n draggable: canManage.value,\n allowDrag: (row) => row.can.move,\n allowDrop,\n }\n : undefined,\n)\n\n/**\n * Nothing goes beside the home page: that place is a second root, and the tree has one. Inside\n * it is the ordinary case — everything on the site is.\n */\nfunction allowDrop(drag: PageRow, drop: PageRow, zone: TreeDropZone): boolean {\n if (drop.is_home) return zone === 'inside'\n\n return drag.id !== drop.id\n}\n\nasync function load(): Promise<void> {\n loading.value = true\n\n try {\n const level = await api.list({\n search: search.value.trim(),\n status: inBin.value ? '' : (filter.value as PageStatus | ''),\n trashed: inBin.value,\n })\n\n home.value = level.home\n items.value = level.items\n } catch (error) {\n toast.danger(message(error))\n } finally {\n loading.value = false\n }\n}\n\nfunction onState(state: TableState): void {\n if (state.search === search.value) return\n\n search.value = state.search\n void load()\n}\n\n/**\n * The editor of a page arrives with the form; until it does, the row opens nothing rather than\n * pushing the router at a route that is not there.\n *\n * The bin never reaches here: a page in it has no editor to open — the API answers a 404 for\n * one — so the table is told the rows lead nowhere (`:clickable=\"!inBin\"`), and it withholds\n * the click along with the pointer and the highlight (§13). Everything a deleted page can have\n * done to it is in its `···`.\n */\nfunction open(page: PageRow): void {\n const path = `${props.base}/${page.id}`\n\n if (router.resolve(path).matched.length > 0) void router.push(path)\n}\n\nasync function add(parent: PageRow | null): Promise<void> {\n const page = await create({ parent })\n\n if (!page) return\n\n await load()\n open(page)\n}\n\nasync function duplicate(page: PageRow): Promise<void> {\n try {\n await api.duplicate(page.id)\n toast.success(t('page.duplicated'))\n await load()\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\nasync function move(page: PageRow): Promise<void> {\n const target = await pickTarget({ page })\n\n if (target == null) return\n\n await apply(page, target, 'inside')\n}\n\n/**\n * A drop asks only when it rewrites more than one address (§14.3).\n *\n * The other way out — no question, and an \"Undo\" in the toast — reads better but is not one:\n * a move leaves a redirect on every address it vacated, so putting the page back would be a\n * second move and a pile of redirects, not an undo. Until the server can actually undo one,\n * dragging a single page stays a gesture and dragging a branch stays a decision.\n *\n * The number is known before the request: a move changes the address of the page and of\n * everything under it, which is what `descendants_count` counts. Landing in the same parent\n * changes nothing at all, so a reorder is never questioned.\n */\nasync function dropped(event: TableNodeDropEvent<PageRow>): Promise<void> {\n const parent = event.zone === 'inside' ? event.target.id : event.target.parent_id\n const moves = event.row.descendants_count + 1\n\n if (parent !== event.row.parent_id && moves > 1) {\n const agreed = await confirm({\n title: t('page.move-title', { title: event.row.title }),\n message: t('page.move-branch', { count: moves }),\n confirmText: t('page.move-confirm'),\n cancelText: t('page.cancel'),\n })\n\n if (!agreed) {\n // The table is showing the row where it was dropped; the server never heard about it.\n await load()\n\n return\n }\n }\n\n await apply(event.row, event.target.id, event.zone)\n}\n\n/** One move, one sentence about the addresses it rewrote, and the list as the server sees it. */\nasync function apply(page: PageRow, target: number, zone: TreeDropZone): Promise<void> {\n try {\n const result = await api.move(page.id, target, zone)\n\n toast.success(\n result.addresses_changed > 1\n ? t('page.moved', { count: result.addresses_changed })\n : t('page.moved-one'),\n )\n } catch (error) {\n toast.danger(message(error))\n } finally {\n // Either way: a refused move has left the table showing the row where it was dropped, and\n // a move that worked has changed addresses the answer does not carry row by row.\n await load()\n }\n}\n\nasync function remove(page: PageRow): Promise<void> {\n const agreed = await confirm({\n title: t('page.delete-title', { title: page.title }),\n message:\n // The whole branch, not the level under it: a catalogue with two sections and forty\n // products is forty-two pages leaving the site, and \"2\" would be a comforting lie.\n page.descendants_count > 0\n ? `${t('page.delete-text')} ${t('page.delete-branch', { count: page.descendants_count })}`\n : t('page.delete-text'),\n confirmText: t('page.delete'),\n cancelText: t('page.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n try {\n await api.remove(page.id)\n toast.success(t('page.deleted'))\n await load()\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\n/**\n * Coming back out of the bin is asked about too (§14.2): a branch restored by accident is a\n * section of the site back on it, and the count says how much of one.\n */\nasync function restore(page: PageRow): Promise<void> {\n const agreed = await confirm({\n title: t('page.restore-page-title', { title: page.title }),\n message:\n page.descendants_count > 0\n ? `${t('page.restore-page-text')} ${t('page.restore-branch', { count: page.descendants_count })}`\n : t('page.restore-page-text'),\n confirmText: t('page.restore'),\n cancelText: t('page.cancel'),\n })\n\n if (!agreed) return\n\n try {\n // The server knows what it actually brought back; the question could only guess at it.\n const restored = await api.restore(page.id)\n\n toast.success(\n restored > 1 ? t('page.restored-branch', { count: restored }) : t('page.restored'),\n )\n await load()\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\nasync function copyAddress(page: PageRow): Promise<void> {\n if (!page.url) return\n\n try {\n await navigator.clipboard.writeText(page.url)\n toast.success(t('page.address-copied'))\n } catch {\n // A browser that refuses the clipboard without a gesture it trusts; the address is on\n // screen and can be copied from there.\n }\n}\n\nfunction badge(status: PageStatus): 'default' | 'success' | 'warning' {\n if (status === 'published') return 'success'\n\n return status === 'modified' ? 'warning' : 'default'\n}\n\nwatch(filter, () => void load())\n\nonMounted(load)\n</script>\n\n<template>\n <div class=\"wx-pages\">\n <wx-list-screen v-model:view=\"filter\" :title=\"title\" :views=\"views\">\n <template v-if=\"canManage\" #actions>\n <wx-button type=\"primary\" icon=\"plus\" @click=\"add(null)\">\n {{ t('page.new') }}\n </wx-button>\n </template>\n\n <wx-table\n :data=\"rows\"\n :columns=\"columns\"\n row-key=\"id\"\n :tree=\"tree\"\n searchable\n :clickable=\"!inBin\"\n :hover=\"!inBin\"\n flush\n layout=\"fixed\"\n :loading=\"loading\"\n :cards-below=\"0\"\n :search-placeholder=\"t('page.search')\"\n :empty-text=\"\n search ? t('page.empty-search') : inBin ? t('page.empty-bin') : t('page.empty')\n \"\n :aria-label=\"title\"\n @row-click=\"open\"\n @state-change=\"onState\"\n @node-drop=\"dropped\"\n >\n <template #cell-title=\"{ row }\">\n <span class=\"wx-pages__title\">\n {{ row.is_home ? t('pages.home') : row.title }}\n </span>\n </template>\n\n <template #cell-path=\"{ row }\">\n <!-- A page in the bin holds no address: the registry let it go when it went in, and\n a live address beside a page nobody can reach would be the wrong promise. -->\n <wx-text v-if=\"inBin\" size=\"sm\" tone=\"muted\">—</wx-text>\n <!-- The address of a live page is a link to it; a draft's is the address it will\n have, which is worth showing and not worth clicking. -->\n <a\n v-else-if=\"row.url && row.status !== 'draft'\"\n class=\"wx-pages__address\"\n :href=\"row.url\"\n target=\"_blank\"\n rel=\"noopener\"\n @click.stop\n >\n /{{ row.path }}\n </a>\n <wx-text v-else-if=\"row.path !== null\" mono size=\"sm\" tone=\"muted\"\n >/{{ row.path }}</wx-text\n >\n <wx-text v-else size=\"sm\" tone=\"muted\">{{ t('page.no-address') }}</wx-text>\n </template>\n\n <template #cell-status=\"{ row }\">\n <wx-date v-if=\"inBin\" :value=\"row.deleted_at\" />\n <wx-badge v-else :type=\"badge(row.status)\" dot>\n {{ t(`page.status-${row.status}`) }}\n </wx-badge>\n </template>\n\n <template #cell-updated_at=\"{ row }\">\n <wx-text size=\"sm\" tone=\"muted\">\n <wx-date :value=\"row.updated_at\" /><template v-if=\"row.edited_by && !inBin\"\n >, {{ row.edited_by }}</template\n >\n </wx-text>\n </template>\n\n <template #cell-actions=\"{ row }\">\n <page-actions\n v-if=\"canManage\"\n :page=\"row\"\n :in-bin=\"inBin\"\n @open=\"open\"\n @add=\"add\"\n @duplicate=\"duplicate\"\n @move=\"move\"\n @copy=\"copyAddress\"\n @remove=\"remove\"\n @restore=\"restore\"\n />\n </template>\n </wx-table>\n </wx-list-screen>\n </div>\n</template>\n\n<style scoped>\n/* The list screen inside it lays the screen out; this only keeps a wide table from pushing the\n column it sits in. */\n.wx-pages {\n min-width: 0;\n}\n\n.wx-pages__title {\n font-weight: var(--wx-font-weight-medium);\n}\n\n.wx-pages__address {\n font-family: var(--wx-font-family-mono);\n font-size: var(--wx-font-size-sm);\n color: var(--wx-color-primary);\n text-decoration: none;\n}\n\n.wx-pages__address:hover {\n text-decoration: underline;\n}\n</style>\n","import type { AdminModule } from '@webx-ui/module-admin'\nimport PageDanger from './PageDanger.vue'\nimport PageEditorPage from './PageEditorPage.vue'\nimport PageHistory from './PageHistory.vue'\nimport PagePlace from './PagePlace.vue'\nimport PagesPage from './PagesPage.vue'\n\nexport interface PagesOptions {\n /** Where the section lives inside the panel. */\n path?: string\n}\n\n/**\n * The site's pages as a section of the panel.\n *\n * The id matches the module the server reports, which is what makes the entry appear in the\n * navigation: the section shows up when both halves are installed.\n */\nexport function pages(options: PagesOptions = {}): AdminModule {\n const path = options.path ?? '/pages'\n\n return {\n id: 'pages',\n path,\n routes: [\n { path, name: 'webx.pages', component: PagesPage, props: { base: path } },\n {\n path: `${path}/:id(\\\\d+)`,\n name: 'webx.pages.edit',\n component: PageEditorPage,\n props: { base: path },\n },\n ],\n /*\n * The parts of `pages.form` that are not fields.\n *\n * The editor is a described screen so that a module can add a tab to it with a patch, and\n * the price of that is that everything on it has to be a node type — where the page sits,\n * what may be done to it, what its history is. Each of these draws only, and each reads the\n * page from the editor above it rather than from the description.\n */\n types: {\n 'wx-page-place': { component: PagePlace, kind: 'display' },\n 'wx-page-danger': { component: PageDanger, kind: 'display' },\n 'wx-page-history': { component: PageHistory, kind: 'display' },\n },\n }\n}\n"],"names":["createPagesApi","admin","base","data","body","query","search","suffix","id","input","target","zone","number","pageEditorKey","providePageEditor","editor","provide","usePageEditor","inject","pagesMessages","seeded","usePagesMessages","i18n","useAdmin","context","api","router","useRouter","useTranslate","message","useErrorText","working","ref","page","computed","canManage","unpublish","current","confirm","toast","error","remove","_createBlock","_unref","WxCard","_createElementVNode","_hoisted_1","_openBlock","_createElementBlock","_hoisted_2","_hoisted_3","_createVNode","WxText","WxButton","_hoisted_4","_hoisted_5","_createTextVNode","_toDisplayString","PAUSE","NARROW","props","__props","route","useRoute","locales","useLocales","t","ancestors","values","revision","previewUrl","prefixes","loading","saving","errors","conflict","snapshot","reloadToken","root","useTemplateRef","width","useElementWidth","narrow","dirty","state","title","written","localizedValue","trail","node","provideBlocksPreview","load","save","take","detail","silent","timer","schedule","onFocusOut","sending","sent","failure","takeTheirs","keepMine","theirs","publish","address","badge","leaveGuard","event","watch","onMounted","onBeforeUnmount","onBeforeRouteLeave","_Fragment","WxSkeleton","WxBackButton","WxBreadcrumb","WxBreadcrumbItem","_renderList","WxBadge","WxDropdown","WxAction","WxDropdownItem","WxAlert","_hoisted_6","WxScreen","$event","WxActionBar","versions","live","restore","version","WxEmpty","WxDate","value","_useModel","nodes","level","toNode","children","WxTreeSelect","moving","parent","prefix","slug","move","moves","result","WxFormItem","PagePicker","emit","__emit","actions","list","WxRowMenu","TRANSLITERATION","slugify","character","open","resolve","dismiss","useModal","parentId","field","edited","nextTick","submit","WxDialog","WxSpace","WxInput","_cache","home","items","filter","create","createModal","PageCreateDialog","pickTarget","PageMoveDialog","inBin","asTree","module","views","rows","columns","tree","row","allowDrop","drag","drop","onState","path","add","duplicate","apply","dropped","restored","copyAddress","status","WxListScreen","WxTable","_withCtx","PageActions","pages","options","PagesPage","PageEditorPage","PagePlace","PageDanger","PageHistory"],"mappings":"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"}
package/dist/style.css CHANGED
@@ -1 +1 @@
1
- .wx-page-danger[data-v-d4a9083f]{display:flex;flex-direction:column;gap:var(--wx-space-12)}.wx-page-danger__row[data-v-d4a9083f]{display:flex;align-items:center;justify-content:space-between;gap:var(--wx-space-12);flex-wrap:wrap}.wx-page-danger__what[data-v-d4a9083f]{display:flex;flex-direction:column;gap:var(--wx-space-2);min-width:200px;flex:1}.wx-page-editor[data-v-268d0501]{display:flex;flex-direction:column;gap:var(--wx-gap, var(--wx-space-16));height:100%;min-height:0;container-type:inline-size}.wx-page-editor__ghost[data-v-268d0501]{max-width:420px}.wx-page-editor__head[data-v-268d0501]{flex:none;display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:end;gap:var(--wx-space-12);padding-block-end:var(--wx-space-12);border-block-end:1px solid var(--wx-border-default)}.wx-page-editor__back[data-v-268d0501]{align-self:start}.wx-page-editor__id[data-v-268d0501]{display:flex;flex-direction:column;gap:var(--wx-space-4);flex:1 1 260px;min-width:0}.wx-page-editor__name[data-v-268d0501]{display:flex;align-items:center;gap:var(--wx-space-8);flex-wrap:wrap;min-width:0}.wx-page-editor__title[data-v-268d0501]{font-size:var(--wx-font-size-xl);font-weight:var(--wx-font-weight-bold);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.wx-page-editor__actions[data-v-268d0501]{display:flex;align-items:center;gap:var(--wx-space-8);flex-wrap:wrap}.wx-page-editor__screen[data-v-268d0501]{display:flex;flex-direction:column;flex:1;min-height:0}.wx-page-editor__screen[data-v-268d0501] .wx-screen-host,.wx-page-editor__screen[data-v-268d0501] .wx-screen,.wx-page-editor__screen[data-v-268d0501] .wx-tabs,.wx-page-editor__screen[data-v-268d0501] .wx-tabs__layout,.wx-page-editor__screen[data-v-268d0501] .wx-tabs__panels,.wx-page-editor__screen[data-v-268d0501] .wx-tab:not([hidden]){display:flex;flex-direction:column;flex:1;min-height:0}.wx-page-editor[data-v-268d0501]:has(.wx-tab:not([hidden]) .wx-blocks-host.is-fill){height:var(--wx-fill-height)}.wx-page-editor__screen[data-v-268d0501] .wx-tab:not([hidden]):has(.wx-blocks-host.is-fill){overflow:auto}.wx-page-editor__screen[data-v-268d0501] .wx-form-item:has(.wx-blocks-host.is-fill),.wx-page-editor__screen[data-v-268d0501] .wx-form-item:has(.wx-blocks-host.is-fill) .wx-form-item__control{display:flex;flex-direction:column;flex:1;min-height:0}.wx-page-history[data-v-41bcd106]{display:flex;flex-direction:column;border:1px solid var(--wx-border-default);border-radius:var(--wx-radius-md);background:var(--wx-bg-surface);overflow:hidden}.wx-page-history__ghost[data-v-41bcd106]{padding:var(--wx-space-10) var(--wx-space-12)}.wx-page-history__row[data-v-41bcd106]{display:flex;align-items:center;gap:var(--wx-space-12);padding:var(--wx-space-10) var(--wx-space-12);border-block-end:1px solid var(--wx-border-default);flex-wrap:wrap}.wx-page-history__row[data-v-41bcd106]:last-child{border-block-end:0}.wx-page-history__number[data-v-41bcd106]{width:40px;font-family:var(--wx-font-family-mono);font-size:var(--wx-font-size-sm);color:var(--wx-text-muted)}.wx-page-history__who[data-v-41bcd106]{display:flex;flex-direction:column;flex:1;min-width:160px}.wx-page-place[data-v-bf094be2]{display:flex;flex-direction:column;gap:var(--wx-space-8)}.wx-page-place__address[data-v-bf094be2]{word-break:break-all}.wx-pages[data-v-6c1123b3]{min-width:0}.wx-pages__title[data-v-6c1123b3]{font-weight:var(--wx-font-weight-medium)}.wx-pages__address[data-v-6c1123b3]{font-family:var(--wx-font-family-mono);font-size:var(--wx-font-size-sm);color:var(--wx-color-primary);text-decoration:none}.wx-pages__address[data-v-6c1123b3]:hover{text-decoration:underline}
1
+ .wx-page-danger[data-v-d4a9083f]{display:flex;flex-direction:column;gap:var(--wx-space-12)}.wx-page-danger__row[data-v-d4a9083f]{display:flex;align-items:center;justify-content:space-between;gap:var(--wx-space-12);flex-wrap:wrap}.wx-page-danger__what[data-v-d4a9083f]{display:flex;flex-direction:column;gap:var(--wx-space-2);min-width:200px;flex:1}.wx-page-editor[data-v-268d0501]{display:flex;flex-direction:column;gap:var(--wx-gap, var(--wx-space-16));height:100%;min-height:0;container-type:inline-size}.wx-page-editor__ghost[data-v-268d0501]{max-width:420px}.wx-page-editor__head[data-v-268d0501]{flex:none;display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:end;gap:var(--wx-space-12);padding-block-end:var(--wx-space-12);border-block-end:1px solid var(--wx-border-default)}.wx-page-editor__back[data-v-268d0501]{align-self:start}.wx-page-editor__id[data-v-268d0501]{display:flex;flex-direction:column;gap:var(--wx-space-4);flex:1 1 260px;min-width:0}.wx-page-editor__name[data-v-268d0501]{display:flex;align-items:center;gap:var(--wx-space-8);flex-wrap:wrap;min-width:0}.wx-page-editor__title[data-v-268d0501]{font-size:var(--wx-font-size-xl);font-weight:var(--wx-font-weight-bold);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.wx-page-editor__actions[data-v-268d0501]{display:flex;align-items:center;gap:var(--wx-space-8);flex-wrap:wrap}.wx-page-editor__screen[data-v-268d0501]{display:flex;flex-direction:column;flex:1;min-height:0}.wx-page-editor__screen[data-v-268d0501] .wx-screen-host,.wx-page-editor__screen[data-v-268d0501] .wx-screen,.wx-page-editor__screen[data-v-268d0501] .wx-tabs,.wx-page-editor__screen[data-v-268d0501] .wx-tabs__layout,.wx-page-editor__screen[data-v-268d0501] .wx-tabs__panels,.wx-page-editor__screen[data-v-268d0501] .wx-tab:not([hidden]){display:flex;flex-direction:column;flex:1;min-height:0}.wx-page-editor[data-v-268d0501]:has(.wx-tab:not([hidden]) .wx-blocks-host.is-fill){height:var(--wx-fill-height)}.wx-page-editor__screen[data-v-268d0501] .wx-tab:not([hidden]):has(.wx-blocks-host.is-fill){overflow:auto}.wx-page-editor__screen[data-v-268d0501] .wx-form-item:has(.wx-blocks-host.is-fill),.wx-page-editor__screen[data-v-268d0501] .wx-form-item:has(.wx-blocks-host.is-fill) .wx-form-item__control{display:flex;flex-direction:column;flex:1;min-height:0}.wx-page-history[data-v-41bcd106]{display:flex;flex-direction:column;border:1px solid var(--wx-border-default);border-radius:var(--wx-radius-md);background:var(--wx-bg-surface);overflow:hidden}.wx-page-history__ghost[data-v-41bcd106]{padding:var(--wx-space-10) var(--wx-space-12)}.wx-page-history__row[data-v-41bcd106]{display:flex;align-items:center;gap:var(--wx-space-12);padding:var(--wx-space-10) var(--wx-space-12);border-block-end:1px solid var(--wx-border-default);flex-wrap:wrap}.wx-page-history__row[data-v-41bcd106]:last-child{border-block-end:0}.wx-page-history__number[data-v-41bcd106]{width:40px;font-family:var(--wx-font-family-mono);font-size:var(--wx-font-size-sm);color:var(--wx-text-muted)}.wx-page-history__who[data-v-41bcd106]{display:flex;flex-direction:column;flex:1;min-width:160px}.wx-page-place[data-v-bf094be2]{display:flex;flex-direction:column;gap:var(--wx-space-8)}.wx-page-place__address[data-v-bf094be2]{word-break:break-all}.wx-pages[data-v-580089b5]{min-width:0}.wx-pages__title[data-v-580089b5]{font-weight:var(--wx-font-weight-medium)}.wx-pages__address[data-v-580089b5]{font-family:var(--wx-font-family-mono);font-size:var(--wx-font-size-sm);color:var(--wx-color-primary);text-decoration:none}.wx-pages__address[data-v-580089b5]:hover{text-decoration:underline}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webx-ui/module-pages",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Pages for the WebX UI admin panel: the tree of a site's pages, their addresses, and the section that edits them.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -44,8 +44,8 @@
44
44
  },
45
45
  "dependencies": {
46
46
  "@webx-ui/core": "^0.22.0",
47
- "@webx-ui/module-admin": "^0.6.0",
48
- "@webx-ui/module-blocks": "^0.4.0",
47
+ "@webx-ui/module-admin": "^0.7.0",
48
+ "@webx-ui/module-blocks": "^0.4.1",
49
49
  "@webx-ui/schema": "^0.2.3"
50
50
  },
51
51
  "devDependencies": {