@webx-ui/module-menu 0.1.0 → 0.1.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
@@ -370,7 +370,7 @@ const Se = /* @__PURE__ */ F({
370
370
  for (const [t, l] of n)
371
371
  u[t] = l;
372
372
  return u;
373
- }, Pe = /* @__PURE__ */ ne(Oe, [["__scopeId", "data-v-4551b4d6"]]), Fe = /* @__PURE__ */ F({
373
+ }, Pe = /* @__PURE__ */ ne(Oe, [["__scopeId", "data-v-fdb72572"]]), Fe = /* @__PURE__ */ F({
374
374
  name: "WxMenuItemDialog",
375
375
  __name: "MenuItemDialog",
376
376
  props: {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/api.ts","../src/messages.ts","../src/i18n.ts","../src/MenuCreateDialog.vue","../src/MenuBranch.vue","../src/MenuItemDialog.vue","../src/tree.ts","../src/MenuTreePane.vue","../src/MenusPage.vue","../src/module.ts"],"sourcesContent":["import type { AdminContext } from '@webx-ui/module-admin'\nimport type { MenuInput, MenuItemInput, MenuItemRow, MenuRow } from './types'\n\nexport interface MenusApi {\n /** Every menu: the ones a template asks for and the ones somebody made. */\n list(): Promise<MenuRow[]>\n create(input: MenuInput): Promise<MenuRow>\n /** The name, and for a menu of somebody's own the key as well. */\n update(key: string, input: Partial<MenuInput>): Promise<MenuRow>\n /** Only a menu of somebody's own, and its items go with it. */\n remove(key: string): Promise<void>\n\n /** The whole tree, nested. Menus are small enough that a level at a time would only cost. */\n items(key: string): Promise<MenuItemRow[]>\n addItem(key: string, input: MenuItemInput): Promise<MenuItemRow>\n saveItem(key: string, id: number, input: MenuItemInput): Promise<MenuItemRow>\n /** Under this parent, at this position among its children. */\n moveItem(key: string, id: number, parentId: number | null, index: number): Promise<void>\n /** With the subtree under it. Answers how many went. */\n removeItem(key: string, id: number): Promise<number>\n\n /** Forget one menu's cache, in every language. */\n flush(key: string): Promise<void>\n /** The same for every menu at once. */\n flushAll(): Promise<void>\n}\n\n/** Everything under `/menus`, below the panel's API path. */\nexport function createMenusApi(admin: AdminContext): MenusApi {\n const base = `${admin.apiPath}/menus`\n const data = <T>(body: { data: T }): T => body.data\n\n return {\n list: () => admin.http.get<{ data: MenuRow[] }>(base).then(data),\n create: (input) => admin.http.post<{ data: MenuRow }>(base, input).then(data),\n update: (key, input) =>\n admin.http.patch<{ data: MenuRow }>(`${base}/${encodeURIComponent(key)}`, input).then(data),\n remove: (key) => admin.http.delete(`${base}/${encodeURIComponent(key)}`).then(() => undefined),\n\n items: (key) =>\n admin.http\n .get<{ data: MenuItemRow[] }>(`${base}/${encodeURIComponent(key)}/items`)\n .then(data),\n addItem: (key, input) =>\n admin.http\n .post<{ data: MenuItemRow }>(`${base}/${encodeURIComponent(key)}/items`, input)\n .then(data),\n saveItem: (key, id, input) =>\n admin.http\n .patch<{ data: MenuItemRow }>(`${base}/${encodeURIComponent(key)}/items/${id}`, input)\n .then(data),\n moveItem: (key, id, parentId, index) =>\n admin.http\n .post(`${base}/${encodeURIComponent(key)}/items/${id}/move`, {\n parent_id: parentId,\n index,\n })\n .then(() => undefined),\n removeItem: (key, id) =>\n admin.http\n .delete<{ data: { deleted: number } }>(`${base}/${encodeURIComponent(key)}/items/${id}`)\n .then((body) => body.data.deleted),\n\n flush: (key) =>\n admin.http.post(`${base}/${encodeURIComponent(key)}/cache/flush`, {}).then(() => undefined),\n flushAll: () => admin.http.post(`${base}/cache/flush`, {}).then(() => undefined),\n }\n}\n","import type { Messages } from '@webx-ui/module-admin'\n\n/**\n * The English this package speaks on its own, before the server's dictionary arrives, or without\n * one. The server ships the same lines in ten languages under `webx-menu::` and overrides these;\n * `messages.test.ts` keeps the two sets of keys equal.\n */\nexport const menuMessages: Record<string, Messages> = {\n module: {\n title: 'Menus',\n },\n menu: {\n menus: 'Menus',\n 'new-menu': 'New menu',\n 'new-menu-title': 'New menu',\n 'field-key': 'Key',\n 'key-help': \"What a template calls it by: menu('header').\",\n 'field-name': 'Name',\n create: 'Create',\n cancel: 'Cancel',\n save: 'Save',\n rename: 'Rename',\n 'rename-title': 'Rename menu',\n renamed: 'Menu saved',\n delete: 'Delete',\n 'delete-menu-title': 'Delete “:menu”?',\n 'delete-menu-text': 'The menu goes, and every item in it. Nothing it points at is touched.',\n deleted: 'Menu deleted',\n declared: 'A template of this site asks for it',\n 'no-menus': 'No menus yet.',\n 'no-menus-help': 'A menu is declared in the configuration, or made here.',\n 'choose-menu': 'Choose a menu',\n 'choose-menu-help': 'Its items are arranged on the right.',\n back: 'Back',\n\n 'new-item': 'New item',\n 'item-title': 'Menu item',\n 'empty-tree': 'Nothing in this menu yet.',\n 'empty-tree-help': 'An item points at a page, at an address of your own, or at nothing.',\n edit: 'Edit',\n duplicate: 'Duplicate',\n duplicated: 'Item duplicated',\n 'delete-item-title': 'Delete “:item”?',\n 'delete-item-text': 'Only this item goes.',\n 'delete-item-branch': 'Items that go with it: :count.',\n 'item-deleted': 'Item deleted',\n 'item-saved': 'Item saved',\n 'move-failed': 'The item could not be moved.',\n 'untitled-item': 'Untitled',\n\n heading: 'Heading',\n 'goes-nowhere': 'Goes nowhere',\n unavailable: 'Not on the site',\n hidden: 'Hidden',\n external: 'An address off this site',\n 'new-tab': 'Opens in a new tab',\n 'missing-target': 'What this pointed at is gone',\n\n 'field-label': 'Label',\n 'label-help': 'Left empty, the item is called what the thing it points at is called.',\n 'field-where': 'Where it goes',\n 'field-variant': 'Look',\n 'field-heading': 'A heading for the items under it',\n 'heading-help': 'Drawn as a caption. It may still be a link.',\n 'field-visible': 'Shown on the site',\n 'field-locales': 'Languages',\n 'locales-all': 'Every language',\n 'locales-help': 'None chosen means every language.',\n\n 'cache-built': 'Cache built',\n 'cache-empty': 'Cache not built',\n 'cache-off': 'Cache off',\n flush: 'Reset the cache of this menu',\n 'flush-all': 'Reset every menu',\n flushed: 'Cache reset',\n },\n}\n","import { useAdmin } from '@webx-ui/module-admin'\nimport { menuMessages } 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 useMenuMessages(): void {\n try {\n const { i18n } = useAdmin()\n\n if (!seeded.has(i18n)) {\n i18n.defaults('webx-menu', menuMessages)\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 { nextTick, onMounted, ref, useTemplateRef } from 'vue'\nimport { useModal, WxButton, WxDialog, WxFormItem, WxInput, WxSpace } from '@webx-ui/core'\nimport { useAdmin, useTranslate } from '@webx-ui/module-admin'\nimport { createMenusApi } from './api'\nimport { useMenuMessages } from './i18n'\nimport type { MenuRow } from './types'\n\n/**\n * A menu of somebody's own: a key and a name (§9).\n *\n * Two fields and no more. A menu declared in the configuration is not made here and cannot be —\n * it is a template asking for a name — so everything this dialog writes is a menu somebody will\n * read out of a block or a template they are about to write.\n *\n * The key does not follow the name the way a page's address follows its title: this one is typed\n * into `menu('…')` by hand, and a key that quietly became `main-menu-2` would be a template\n * asking for a menu that is not there.\n */\ndefineOptions({ name: 'WxMenuCreateDialog' })\n\nconst props = withDefaults(defineProps<{ menu?: MenuRow | null }>(), { menu: null })\n\nconst { open, resolve, dismiss } = useModal<MenuRow>()\n\nconst admin = useAdmin()\nconst api = createMenusApi(admin)\nuseMenuMessages()\n\nconst t = useTranslate('webx-menu')\n\nconst editing = props.menu !== null\n\nconst key = ref(props.menu?.key ?? '')\nconst title = ref(props.menu?.title ?? '')\nconst saving = ref(false)\nconst errors = ref<Record<string, string[]>>({})\nconst field = useTemplateRef<HTMLElement>('field')\n\nonMounted(async () => {\n await nextTick()\n field.value?.querySelector('input')?.focus()\n})\n\nasync function submit(): Promise<void> {\n if (title.value.trim() === '' || saving.value) return\n\n saving.value = true\n errors.value = {}\n\n try {\n const input = { key: key.value.trim(), title: title.value.trim() }\n\n resolve(\n editing && props.menu !== null\n ? await api.update(props.menu.key, props.menu.can.rename ? input : { title: input.title })\n : await api.create(input),\n )\n } catch (error) {\n // The key is the one thing that can be refused, and it is refused under its own field:\n // taken already, or spelled in a way no template could write.\n errors.value = (error as { body?: { errors?: Record<string, string[]> } }).body?.errors ?? {}\n } finally {\n saving.value = false\n }\n}\n</script>\n\n<template>\n <wx-dialog\n v-model:open=\"open\"\n :title=\"editing ? t('menu.rename-title') : t('menu.new-menu-title')\"\n :width=\"440\"\n >\n <wx-form-item :label=\"t('menu.field-name')\" :error=\"errors.title?.[0]\" required>\n <div ref=\"field\">\n <wx-input v-model=\"title\" :aria-label=\"t('menu.field-name')\" @keyup.enter=\"submit\" />\n </div>\n </wx-form-item>\n\n <!--\n Locked for a menu a template asks for: that spelling is the reference, and renaming it\n here would break a view nobody is looking at.\n -->\n <wx-form-item\n :label=\"t('menu.field-key')\"\n :error=\"errors.key?.[0]\"\n :help=\"t('menu.key-help')\"\n required\n >\n <wx-input\n v-model=\"key\"\n :disabled=\"editing && !props.menu?.can.rename\"\n :aria-label=\"t('menu.field-key')\"\n @keyup.enter=\"submit\"\n />\n </wx-form-item>\n\n <template #footer>\n <wx-space size=\"sm\">\n <wx-button variant=\"outline\" @click=\"dismiss()\">{{ t('menu.cancel') }}</wx-button>\n <wx-button\n type=\"primary\"\n :loading=\"saving\"\n :disabled=\"title.trim() === '' || key.trim() === ''\"\n @click=\"submit\"\n >\n {{ editing ? t('menu.save') : t('menu.create') }}\n </wx-button>\n </wx-space>\n </template>\n </wx-dialog>\n</template>\n","<script setup lang=\"ts\">\nimport { WxIcon, WxSortableList, WxText, WxTooltip } from '@webx-ui/core'\nimport { useTranslate, WxRowMenu, type RowAction } from '@webx-ui/module-admin'\nimport type { MenuItemRow } from './types'\n\n/**\n * One level of the tree, and every level under it.\n *\n * Nested sortable lists rather than one flat list with indentation: dragging has to change both\n * the order and the parent, and a list that draws nesting without holding it makes the second\n * half a guess about how far sideways somebody dropped a row. Each level is its own list, they\n * share a group, and where a row ends up is where it is.\n *\n * Which level changed is not something the list says — a drag between two of them changes two\n * models and neither is a move within one — so every level reports its own new order and the\n * screen above works out what moved. That keeps this component with nothing to remember.\n */\ndefineOptions({ name: 'WxMenuBranch' })\n\nconst props = withDefaults(\n defineProps<{\n items: MenuItemRow[]\n /** Null for the top level. It travels back out with the new order. */\n parentId?: number | null\n disabled?: boolean\n }>(),\n { parentId: null, disabled: false },\n)\n\nconst emit = defineEmits<{\n /** This level, in its new order. The screen above decides what that means. */\n reorder: [parentId: number | null, items: MenuItemRow[]]\n edit: [item: MenuItemRow]\n duplicate: [item: MenuItemRow]\n remove: [item: MenuItemRow]\n}>()\n\nconst t = useTranslate('webx-menu')\n\nfunction name(item: MenuItemRow): string {\n return item.label === '' ? t('menu.untitled-item') : item.label\n}\n\n/**\n * The line under the label: where the item goes, or what it is instead.\n *\n * An item that points at something that is gone says so rather than showing an empty line —\n * that is what a menu built before a module was removed looks like, and it is the one state\n * nobody can work out from the row.\n */\nfunction where(item: MenuItemRow): string {\n if (item.target === 'none') return t('menu.goes-nowhere')\n if (item.target === 'entity' && item.resolved === null) return t('menu.missing-target')\n\n return item.href ?? t('menu.goes-nowhere')\n}\n\n/** An address on somebody else's site: it has a scheme and it is not a path of ours. */\nfunction isExternal(item: MenuItemRow): boolean {\n return item.target === 'url' && /^[a-z][a-z0-9+.-]*:/i.test(item.url ?? '')\n}\n\nfunction actionsFor(item: MenuItemRow): RowAction[] {\n if (props.disabled) return []\n\n return [\n { key: 'edit', icon: 'edit', label: t('menu.edit'), run: () => emit('edit', item) },\n {\n key: 'duplicate',\n icon: 'copy',\n label: t('menu.duplicate'),\n run: () => emit('duplicate', item),\n },\n {\n key: 'delete',\n icon: 'trash',\n label: t('menu.delete'),\n danger: true,\n run: () => emit('remove', item),\n },\n ]\n}\n</script>\n\n<template>\n <wx-sortable-list\n class=\"wx-menu-branch\"\n :class=\"{ 'is-nested': props.parentId !== null }\"\n :model-value=\"props.items\"\n plain\n size=\"sm\"\n group=\"wx-menu-items\"\n item-key=\"id\"\n :item-label=\"name\"\n :disabled=\"props.disabled\"\n :empty-text=\"''\"\n @update:model-value=\"(next: MenuItemRow[]) => emit('reorder', props.parentId, next)\"\n >\n <!--\n Nothing, and on purpose: a leaf's empty list is what makes the leaf something a row can be\n dropped into. The words `Nothing here yet` under every item without children would be the\n loudest thing on the screen.\n -->\n <template #empty><span class=\"wx-menu-branch__drop\" /></template>\n\n <template #default=\"{ item }\">\n <div class=\"wx-menu-item\" :class=\"{ 'is-off': !item.visible || !item.available }\">\n <button\n type=\"button\"\n class=\"wx-menu-item__open\"\n :disabled=\"props.disabled\"\n @click=\"emit('edit', item)\"\n >\n <span class=\"wx-menu-item__line\">\n <wx-text truncate :weight=\"item.is_heading ? 'semibold' : 'medium'\">\n {{ name(item) }}\n </wx-text>\n\n <!--\n Two facts and two marks, because they are independent: an internal PDF is opened\n in a new tab, and a partner's link sometimes is not.\n -->\n <wx-tooltip v-if=\"isExternal(item)\" :content=\"t('menu.external')\">\n <wx-icon class=\"wx-menu-item__mark\" name=\"link\" :label=\"t('menu.external')\" />\n </wx-tooltip>\n\n <wx-tooltip v-if=\"item.new_tab\" :content=\"t('menu.new-tab')\">\n <wx-icon class=\"wx-menu-item__mark\" name=\"external-link\" :label=\"t('menu.new-tab')\" />\n </wx-tooltip>\n </span>\n\n <wx-text size=\"sm\" tone=\"muted\" truncate>\n <template v-if=\"item.is_heading\">{{ t('menu.heading') }}</template>\n <template v-else>{{ where(item) }}</template>\n </wx-text>\n </button>\n\n <!--\n The two states that dim a row are said in words as well as in colour: \"hidden\" is a\n decision somebody made and \"not on the site\" is one they did not, and grey alone\n cannot tell the two apart.\n -->\n <wx-text v-if=\"!item.visible\" class=\"wx-menu-item__state\" size=\"sm\" tone=\"muted\">\n {{ t('menu.hidden') }}\n </wx-text>\n <wx-text v-else-if=\"!item.available\" class=\"wx-menu-item__state\" size=\"sm\" tone=\"warning\">\n {{ t('menu.unavailable') }}\n </wx-text>\n </div>\n\n <!--\n The level under this item, always drawn: an empty one is the target that makes nesting\n something a drag can do, rather than something only a form could say.\n -->\n <wx-menu-branch\n :items=\"item.children\"\n :parent-id=\"item.id\"\n :disabled=\"props.disabled\"\n @reorder=\"(parent: number | null, next: MenuItemRow[]) => emit('reorder', parent, next)\"\n @edit=\"emit('edit', $event)\"\n @duplicate=\"emit('duplicate', $event)\"\n @remove=\"emit('remove', $event)\"\n />\n </template>\n\n <!--\n Left out rather than greyed for a reader who may only look: a menu is a list of what is\n possible, and a row of dead entries teaches nothing.\n -->\n <template v-if=\"!props.disabled\" #actions=\"{ item }\">\n <wx-row-menu :actions=\"actionsFor(item)\" :label=\"name(item)\" />\n </template>\n </wx-sortable-list>\n</template>\n\n<style scoped>\n.wx-menu-branch.is-nested {\n /* The step that says \"under\": the grip of a child stands clear of its parent's label. */\n padding-inline-start: var(--wx-space-16);\n}\n\n/*\n * A row holds its own children, so the grip and the `···` line up with the label rather than\n * with the middle of the whole branch. The list's rows are its elements and not ours, so this\n * reaches them with `:deep()` — a scope attribute would be on neither.\n */\n.wx-menu-branch :deep(.wx-sortable-list__row) {\n align-items: flex-start;\n}\n\n.wx-menu-branch :deep(.wx-sortable-list__grip) {\n /* The height of one line of the label, so the grip sits on it and not above it. */\n height: 28px;\n}\n\n/* An empty level is a target and not a message: as little of it as can still be hit. */\n.wx-menu-branch :deep(.wx-sortable-list__empty) {\n padding: 0;\n}\n\n.wx-menu-branch__drop {\n display: block;\n height: var(--wx-space-8);\n}\n\n.wx-menu-item {\n display: flex;\n align-items: flex-start;\n gap: var(--wx-space-8);\n min-width: 0;\n padding-block: var(--wx-space-2);\n}\n\n.wx-menu-item__open {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: var(--wx-space-2);\n /* The label is the target: a four-letter item should not leave the rest of the line dead. */\n flex: 1 1 auto;\n min-width: 0;\n padding: 0;\n border: 0;\n background: none;\n font: inherit;\n color: inherit;\n text-align: start;\n cursor: pointer;\n}\n\n.wx-menu-item__open:disabled {\n cursor: default;\n}\n\n.wx-menu-item__line {\n display: flex;\n align-items: center;\n gap: var(--wx-space-6);\n min-width: 0;\n max-width: 100%;\n}\n\n.wx-menu-item__mark {\n flex: 0 0 auto;\n color: var(--wx-text-placeholder);\n}\n\n.wx-menu-item__state {\n flex: 0 0 auto;\n}\n\n/*\n * Switched off, or pointing at something the site would not show. Dimmed on the label and the\n * address rather than on the row, so the word that says which of the two it is keeps its own\n * colour — that word is the only thing on the row that explains the grey.\n */\n.wx-menu-item.is-off .wx-menu-item__open {\n opacity: 0.55;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\nimport {\n useLocales,\n useModal,\n WxButton,\n WxCheckbox,\n WxDialog,\n WxFormItem,\n WxInput,\n WxSelect,\n WxSpace,\n WxSwitch,\n type SelectOption,\n} from '@webx-ui/core'\nimport { emptyLink, useTranslate, WxLinkPicker, type LinkValue } from '@webx-ui/module-admin'\nimport { useMenuMessages } from './i18n'\nimport type { LocalizedText, MenuItemInput, MenuItemRow, MenuRow } from './types'\n\n/**\n * One menu item, in a dialog raised from code (§9).\n *\n * A dialog and not a tab or a screen of its own, because an item is small and is always edited\n * while looking at the tree it sits in: what somebody is deciding is where this one goes\n * relative to the others, and a screen that replaced the tree would take that away.\n *\n * Where it points is `WxLinkPicker` and nothing of this package's own — a menu item and a link\n * field of a block are the same choice, and the day they stop looking alike is the day one of\n * them grows a second way of saying `nofollow`.\n */\ndefineOptions({ name: 'WxMenuItemDialog' })\n\nconst props = withDefaults(\n defineProps<{\n menu: MenuRow\n /** The item being edited, or null for a new one. */\n item?: MenuItemRow | null\n }>(),\n { item: null },\n)\n\nconst { open, resolve, dismiss } = useModal<MenuItemInput>()\n\nconst locales = useLocales()\nuseMenuMessages()\n\nconst t = useTranslate('webx-menu')\n\nconst link = ref<LinkValue>(props.item === null ? emptyLink() : toLink(props.item))\nconst title = ref<LocalizedText>({ ...(props.item?.title ?? {}) })\nconst variant = ref(props.item?.variant ?? props.menu.variants[0] ?? 'link')\nconst isHeading = ref(props.item?.is_heading ?? false)\nconst visible = ref(props.item?.visible ?? true)\nconst chosenLocales = ref<string[]>([...(props.item?.locales ?? [])])\n\n/**\n * What the item would be called if nobody wrote a label: the name of the thing it points at.\n *\n * Shown as the placeholder rather than filled in, because filling it in would make it a label —\n * and then renaming the page would stop renaming the item, which is the whole property the\n * empty label has (§4).\n */\nconst fallback = computed(() => props.item?.resolved?.title ?? '')\n\n/** Only where the site has more than one look to choose between. */\nconst variantOptions = computed<SelectOption[]>(() =>\n props.menu.variants.map((name) => ({ value: name, label: name })),\n)\n\nconst localeOptions = computed<SelectOption[]>(() =>\n locales.list.value.map((locale) => ({ value: locale.code, label: locale.label ?? locale.code })),\n)\n\nfunction toLink(item: MenuItemRow): LinkValue {\n return {\n target: item.target,\n entity_type: item.entity_type,\n entity_id: item.entity_id,\n url: item.url,\n hash: item.hash,\n new_tab: item.new_tab,\n rel: item.rel,\n }\n}\n\nfunction submit(): void {\n resolve({\n title: title.value,\n link: link.value,\n variant: variant.value,\n is_heading: isHeading.value,\n locales: chosenLocales.value,\n visible: visible.value,\n })\n}\n</script>\n\n<template>\n <wx-dialog v-model:open=\"open\" :title=\"t('menu.item-title')\" :width=\"520\">\n <wx-form-item :label=\"t('menu.field-where')\">\n <wx-link-picker v-model=\"link\" />\n </wx-form-item>\n\n <wx-form-item :label=\"t('menu.field-label')\" :help=\"t('menu.label-help')\">\n <wx-input\n v-model=\"title\"\n localized\n :placeholder=\"fallback\"\n :aria-label=\"t('menu.field-label')\"\n />\n </wx-form-item>\n\n <wx-form-item v-if=\"variantOptions.length > 1\" :label=\"t('menu.field-variant')\">\n <wx-select\n v-model=\"variant\"\n :options=\"variantOptions\"\n :aria-label=\"t('menu.field-variant')\"\n />\n </wx-form-item>\n\n <!--\n Empty means every language, so this is a `multiple` select with no \"all\" in it: a row\n called \"All languages\" is a row somebody picks alongside Ukrainian.\n -->\n <wx-form-item\n v-if=\"localeOptions.length > 1\"\n :label=\"t('menu.field-locales')\"\n :help=\"t('menu.locales-help')\"\n >\n <wx-select\n v-model=\"chosenLocales\"\n :options=\"localeOptions\"\n multiple\n clearable\n :placeholder=\"t('menu.locales-all')\"\n :aria-label=\"t('menu.field-locales')\"\n />\n </wx-form-item>\n\n <wx-form-item :help=\"t('menu.heading-help')\">\n <wx-checkbox v-model=\"isHeading\">{{ t('menu.field-heading') }}</wx-checkbox>\n </wx-form-item>\n\n <wx-form-item>\n <wx-switch v-model=\"visible\" :label=\"t('menu.field-visible')\" />\n </wx-form-item>\n\n <template #footer>\n <wx-space size=\"sm\">\n <wx-button variant=\"outline\" @click=\"dismiss()\">{{ t('menu.cancel') }}</wx-button>\n <wx-button type=\"primary\" @click=\"submit\">{{ t('menu.save') }}</wx-button>\n </wx-space>\n </template>\n </wx-dialog>\n</template>\n","import type { MenuItemRow } from './types'\n\n/** The item with this id, anywhere in the tree. */\nexport function findItem(items: MenuItemRow[], id: number): MenuItemRow | null {\n for (const item of items) {\n if (item.id === id) return item\n\n const found = findItem(item.children, id)\n\n if (found !== null) return found\n }\n\n return null\n}\n\n/** How many items are under this one — what a delete takes with it. */\nexport function countBranch(item: MenuItemRow): number {\n return item.children.reduce((total, child) => total + 1 + countBranch(child), 0)\n}\n\n/**\n * Which item moved, given one level before and after a drag.\n *\n * A drag between two levels changes two lists and the library says nothing about either beyond\n * their new contents, so what moved has to be read out of the difference. Three cases, and one\n * rule covers them: an id that is here and was not is the one that arrived; a level that lost\n * one says nothing, because the level that gained it will; and a level whose contents are the\n * same set in another order gives up the one id that, taken out of both, leaves them equal.\n *\n * Null means \"nothing for the server to hear about\" — a removal, or a drag that ended where it\n * started.\n */\nexport function movedId(before: number[], after: number[]): number | null {\n const arrived = after.find((id) => !before.includes(id))\n\n if (arrived !== undefined) return arrived\n if (after.length !== before.length) return null\n\n for (const id of after) {\n if (after.indexOf(id) === before.indexOf(id)) continue\n\n const rest = after.filter((other) => other !== id)\n const was = before.filter((other) => other !== id)\n\n if (rest.length === was.length && rest.every((other, index) => other === was[index])) {\n return id\n }\n }\n\n return null\n}\n","<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\nimport {\n confirm,\n createModal,\n toast,\n WxAction,\n WxButton,\n WxEmpty,\n WxIcon,\n WxSkeleton,\n WxText,\n} from '@webx-ui/core'\nimport { useAdmin, useErrorText, useTranslate } from '@webx-ui/module-admin'\nimport MenuBranch from './MenuBranch.vue'\nimport MenuItemDialog from './MenuItemDialog.vue'\nimport { createMenusApi } from './api'\nimport { useMenuMessages } from './i18n'\nimport { countBranch, findItem, movedId } from './tree'\nimport type { MenuItemInput, MenuItemRow, MenuRow } from './types'\n\n/**\n * The items of one menu: the pane beside the list, and the whole screen on a phone.\n *\n * The way back out is drawn here and not by the pane around it, because the drawer a narrow\n * screen puts this in has no close of its own on purpose (§9): what closes it is `Back` in the\n * head of what is inside, the way every other screen of the panel goes back.\n *\n * Mounted afresh for each menu — the tree, the drag state and the scroll all belong to the menu\n * being looked at, and keeping them across a switch is how a drop lands in the wrong one.\n */\ndefineOptions({ name: 'WxMenuTreePane' })\n\nconst props = withDefaults(\n defineProps<{\n menu: MenuRow\n /** False in the drawer a narrow screen opens this in. */\n inline?: boolean\n back?: () => void\n }>(),\n { inline: true, back: undefined },\n)\n\nconst emit = defineEmits<{\n /** Something about the menu changed: its item count, and the age of its cache. */\n changed: []\n}>()\n\nconst admin = useAdmin()\nconst api = createMenusApi(admin)\nuseMenuMessages()\n\nconst t = useTranslate('webx-menu')\nconst message = useErrorText()\n\nconst items = ref<MenuItemRow[]>([])\nconst loading = ref(true)\n\nconst canManage = computed(() => admin.can('menu.manage'))\n\nconst edit = createModal<MenuItemInput, { menu: MenuRow; item: MenuItemRow | null }>(MenuItemDialog)\n\nasync function load(): Promise<void> {\n loading.value = true\n\n try {\n items.value = await api.items(props.menu.key)\n } catch (error) {\n toast.danger(message(error))\n } finally {\n loading.value = false\n }\n}\n\nvoid load()\n\n/** The level under a parent, as it stands right now. */\nfunction levelOf(parentId: number | null): MenuItemRow[] {\n if (parentId === null) return items.value\n\n return findItem(items.value, parentId)?.children ?? []\n}\n\nfunction setLevel(parentId: number | null, next: MenuItemRow[]): void {\n if (parentId === null) {\n items.value = next\n\n return\n }\n\n const parent = findItem(items.value, parentId)\n\n if (parent !== null) parent.children = next\n}\n\n/**\n * A level came back in a new order. One drag reports twice when it crosses levels — the one it\n * left and the one it landed in — and only the second of those is a move to send.\n *\n * The screen is already showing the new order: the list reorders its model before it says\n * anything. So a refusal has to put the tree back rather than leave the screen disagreeing with\n * the database.\n */\nasync function reorder(parentId: number | null, next: MenuItemRow[]): Promise<void> {\n const before = levelOf(parentId).map((item) => item.id)\n\n setLevel(parentId, next)\n\n const after = next.map((item) => item.id)\n const moved = movedId(before, after)\n\n if (moved === null) return\n\n try {\n await api.moveItem(props.menu.key, moved, parentId, after.indexOf(moved))\n emit('changed')\n } catch (error) {\n toast.danger(message(error, t('menu.move-failed')))\n await load()\n }\n}\n\nasync function add(): Promise<void> {\n const input = await edit({ menu: props.menu, item: null })\n\n if (!input) return\n\n try {\n await api.addItem(props.menu.key, input)\n toast.success(t('menu.item-saved'))\n await load()\n emit('changed')\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\nasync function save(item: MenuItemRow): Promise<void> {\n const input = await edit({ menu: props.menu, item })\n\n if (!input) return\n\n try {\n await api.saveItem(props.menu.key, item.id, input)\n toast.success(t('menu.item-saved'))\n await load()\n emit('changed')\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\n/**\n * A copy of an item, put directly under the one it was copied from.\n *\n * At the end of the level first and then moved, because where a new item goes is the tree's\n * business and there is one way of saying it. A copy that landed at the bottom of a menu of\n * twenty would be a copy somebody has to go and find.\n */\nasync function duplicate(item: MenuItemRow): Promise<void> {\n try {\n const copy = await api.addItem(props.menu.key, {\n title: item.title,\n link: {\n target: item.target,\n entity_type: item.entity_type,\n entity_id: item.entity_id,\n url: item.url,\n hash: item.hash,\n new_tab: item.new_tab,\n rel: item.rel,\n },\n variant: item.variant,\n is_heading: item.is_heading,\n locales: item.locales,\n visible: item.visible,\n parent_id: item.parent_id,\n })\n\n const siblings = levelOf(item.parent_id)\n await api.moveItem(\n props.menu.key,\n copy.id,\n item.parent_id,\n siblings.findIndex((one) => one.id === item.id) + 1,\n )\n\n toast.success(t('menu.duplicated'))\n await load()\n emit('changed')\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\n/**\n * An item, and whatever is under it.\n *\n * The number is counted off the tree that is on the screen, which is the same tree the delete\n * walks — the promise and the act have to be the same question, and bounds read a while ago are\n * bounds that have since moved (§10).\n */\nasync function remove(item: MenuItemRow): Promise<void> {\n const under = countBranch(item)\n\n const agreed = await confirm({\n title: t('menu.delete-item-title', { item: item.label || t('menu.untitled-item') }),\n message:\n under === 0 ? t('menu.delete-item-text') : t('menu.delete-item-branch', { count: under }),\n confirmText: t('menu.delete'),\n cancelText: t('menu.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n try {\n await api.removeItem(props.menu.key, item.id)\n toast.success(t('menu.item-deleted'))\n await load()\n emit('changed')\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\ndefineExpose({ reload: load })\n</script>\n\n<template>\n <div class=\"wx-menu-pane\">\n <header class=\"wx-menu-pane__head\">\n <!--\n The drawer this opens in on a phone has no cross: closing it is `Back`, in the head of\n what is inside it, where every other screen of the panel keeps it.\n -->\n <wx-action\n v-if=\"!props.inline\"\n icon=\"arrow-left\"\n size=\"sm\"\n :title=\"t('menu.back')\"\n @click=\"props.back?.()\"\n />\n\n <div class=\"wx-menu-pane__name\">\n <wx-text weight=\"semibold\" truncate>{{ props.menu.title }}</wx-text>\n <wx-text size=\"sm\" tone=\"muted\" mono truncate>{{ props.menu.key }}</wx-text>\n </div>\n\n <wx-button v-if=\"canManage\" variant=\"outline\" size=\"sm\" @click=\"add\">\n <template #icon><wx-icon name=\"plus\" /></template>\n {{ t('menu.new-item') }}\n </wx-button>\n </header>\n\n <div class=\"wx-menu-pane__body\">\n <wx-skeleton v-if=\"loading\" :rows=\"5\" />\n\n <wx-empty\n v-else-if=\"items.length === 0\"\n :title=\"t('menu.empty-tree')\"\n :description=\"t('menu.empty-tree-help')\"\n />\n\n <menu-branch\n v-else\n :items=\"items\"\n :disabled=\"!canManage\"\n @reorder=\"reorder\"\n @edit=\"save\"\n @duplicate=\"duplicate\"\n @remove=\"remove\"\n />\n </div>\n </div>\n</template>\n\n<style scoped>\n.wx-menu-pane {\n display: flex;\n flex-direction: column;\n min-height: 0;\n}\n\n.wx-menu-pane__head {\n display: flex;\n align-items: center;\n gap: var(--wx-space-12);\n padding: var(--wx-space-12) var(--wx-space-16);\n border-bottom: 1px solid var(--wx-border-muted);\n}\n\n.wx-menu-pane__name {\n display: flex;\n flex-direction: column;\n /* A long name shortens; it does not push the button that makes items off the line. */\n flex: 1 1 auto;\n min-width: 0;\n}\n\n.wx-menu-pane__body {\n padding: var(--wx-space-12) var(--wx-space-16);\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\nimport { useRoute, useRouter } from 'vue-router'\nimport {\n confirm,\n createModal,\n toast,\n WxAction,\n WxCard,\n WxEmpty,\n WxIndicator,\n WxListDetail,\n WxSkeleton,\n WxText,\n} from '@webx-ui/core'\nimport {\n useAdmin,\n useErrorText,\n useTranslate,\n WxDate,\n WxListScreen,\n WxRowMenu,\n type RowAction,\n type ScreenAction,\n} from '@webx-ui/module-admin'\nimport MenuCreateDialog from './MenuCreateDialog.vue'\nimport MenuTreePane from './MenuTreePane.vue'\nimport { createMenusApi } from './api'\nimport { useMenuMessages } from './i18n'\nimport type { MenuRow } from './types'\n\n/**\n * The section: the menus of the site on the left, the items of one of them on the right (§9).\n *\n * The menus are the list and the tree is the record beside it, rather than the other way round.\n * Somebody who opens this section is arranging a menu, not choosing between four of them — so\n * the first one opens by itself where there is room for it, and on a phone it does not, because\n * there the same line would raise a panel over a list nobody has touched.\n */\nconst admin = useAdmin()\nconst api = createMenusApi(admin)\nconst route = useRoute()\nconst router = useRouter()\nuseMenuMessages()\n\nconst t = useTranslate('webx-menu')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\nconst menus = ref<MenuRow[]>([])\nconst loading = ref(true)\nconst open = ref(false)\nconst detailInline = ref(true)\n\nconst canManage = computed(() => admin.can('menu.manage'))\n\nconst edit = createModal<MenuRow, { menu?: MenuRow | null }>(MenuCreateDialog)\n\nconst title = computed(\n () =>\n admin.state.manifest?.modules.find((module) => module.id === 'menu')?.title ??\n t('module.title'),\n)\n\n/**\n * Which menu is open, kept in the address — so that coming back from anywhere lands on the\n * menu somebody was arranging, and so that \"the footer\" is a link they can send.\n */\nconst current = computed<string | null>(() => {\n const asked = route.query.menu\n\n return typeof asked === 'string' && asked !== '' ? asked : null\n})\n\nconst chosen = computed(() => menus.value.find((menu) => menu.key === current.value) ?? null)\n\nasync function load(): Promise<void> {\n loading.value = true\n\n try {\n menus.value = await api.list()\n } catch (error) {\n toast.danger(message(error))\n } finally {\n loading.value = false\n }\n}\n\nvoid load()\n\n/*\n * The first menu, where there is a column to draw it in. A screen whose whole right-hand half\n * says \"choose one\" has spent its width on an instruction; a phone, where the same thing means\n * opening a panel over the list, gets the list.\n */\nwatch([menus, chosen, detailInline], () => {\n if (!detailInline.value || menus.value.length === 0 || chosen.value !== null) return\n\n void router.replace({ query: { ...route.query, menu: menus.value[0].key } })\n})\n\nwatch(chosen, (menu) => {\n if (menu !== null && detailInline.value) open.value = true\n})\n\nfunction choose(menu: MenuRow): void {\n void router.replace({ query: { ...route.query, menu: menu.key } })\n open.value = true\n}\n\nasync function add(): Promise<void> {\n const made = await edit({ menu: null })\n\n if (!made) return\n\n await load()\n choose(made)\n}\n\nasync function rename(menu: MenuRow): Promise<void> {\n const saved = await edit({ menu })\n\n if (!saved) return\n\n toast.success(t('menu.renamed'))\n\n // The key may have changed with it, and the address names the old one.\n if (current.value === menu.key && saved.key !== menu.key) {\n void router.replace({ query: { ...route.query, menu: saved.key } })\n }\n\n await load()\n}\n\nasync function remove(menu: MenuRow): Promise<void> {\n const agreed = await confirm({\n title: t('menu.delete-menu-title', { menu: menu.title }),\n message: t('menu.delete-menu-text'),\n confirmText: t('menu.delete'),\n cancelText: t('menu.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n try {\n await api.remove(menu.key)\n toast.success(t('menu.deleted'))\n\n if (current.value === menu.key) {\n const rest = { ...route.query }\n delete rest.menu\n void router.replace({ query: rest })\n open.value = false\n }\n\n await load()\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\n/**\n * Forget what this menu was built into, in every language.\n *\n * The list is loaded again afterwards rather than trusted: the mark under the name is the whole\n * of what this button says out loud, and a button that leaves it reading \"built today at 08:10\"\n * is a button nobody believes the second time.\n */\nasync function flush(menu: MenuRow): Promise<void> {\n try {\n await api.flush(menu.key)\n toast.success(t('menu.flushed'))\n await load()\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\nasync function flushAll(): Promise<void> {\n try {\n await api.flushAll()\n toast.success(t('menu.flushed'))\n await load()\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\nfunction actionsFor(menu: MenuRow): RowAction[] {\n if (!canManage.value) return []\n\n return [\n { key: 'rename', icon: 'edit', label: t('menu.rename'), run: () => void rename(menu) },\n { key: 'flush', icon: 'refresh', label: t('menu.flush'), run: () => void flush(menu) },\n ...(menu.can.delete\n ? [\n {\n key: 'delete',\n icon: 'trash' as const,\n label: t('menu.delete'),\n danger: true,\n run: () => void remove(menu),\n },\n ]\n : []),\n ]\n}\n\nconst actions = computed<ScreenAction[]>(() => {\n if (!canManage.value) return []\n\n return [\n {\n key: 'flush-all',\n label: t('menu.flush-all'),\n icon: 'refresh',\n menu: true,\n run: () => void flushAll(),\n },\n {\n key: 'new-menu',\n label: t('menu.new-menu'),\n icon: 'plus',\n primary: true,\n run: () => void add(),\n },\n ]\n})\n</script>\n\n<template>\n <wx-list-screen :title=\"title\" :actions=\"actions\" :card=\"false\">\n <wx-card class=\"wx-menus\" padding=\"none\">\n <wx-list-detail\n v-model:open=\"open\"\n class=\"wx-menus__panes\"\n :list-width=\"320\"\n :detail-min=\"420\"\n :detail-label=\"t('menu.menus')\"\n @detail-inline=\"detailInline = $event\"\n >\n <template #list>\n <wx-skeleton v-if=\"loading\" class=\"wx-menus__loading\" :rows=\"4\" />\n\n <wx-empty\n v-else-if=\"menus.length === 0\"\n :title=\"t('menu.no-menus')\"\n :description=\"t('menu.no-menus-help')\"\n />\n\n <ul v-else class=\"wx-menus__list\">\n <li v-for=\"menu in menus\" :key=\"menu.key\" class=\"wx-menus__row\">\n <button\n type=\"button\"\n class=\"wx-menus__open\"\n :class=\"{ 'is-current': menu.key === current }\"\n @click=\"choose(menu)\"\n >\n <span class=\"wx-menus__name\">\n <wx-text truncate weight=\"medium\">{{ menu.title }}</wx-text>\n <wx-indicator\n class=\"wx-menus__count\"\n type=\"neutral\"\n :value=\"menu.items_count\"\n :label=\"t('menu.menus')\"\n />\n </span>\n\n <wx-text size=\"sm\" tone=\"muted\" mono truncate>{{ menu.key }}</wx-text>\n\n <!--\n When the cache was built, in the panel's own words — and it is a sentence\n rather than a date, because \"built\" is the part that answers the question\n somebody pressed the button next to it to ask.\n -->\n <span class=\"wx-menus__cache\">\n <wx-text v-if=\"!menu.cache.enabled\" size=\"sm\" tone=\"muted\">\n {{ t('menu.cache-off') }}\n </wx-text>\n <template v-else-if=\"menu.cache.built_at !== null\">\n <wx-text size=\"sm\" tone=\"muted\">{{ t('menu.cache-built') }}</wx-text>\n <wx-date :value=\"menu.cache.built_at\" />\n </template>\n <wx-text v-else size=\"sm\" tone=\"muted\">{{ t('menu.cache-empty') }}</wx-text>\n </span>\n </button>\n\n <!--\n The reset stands in the row and not only in the `···`: it is what somebody\n presses to test the guess that they are looking at something stale, and a guess\n behind a menu is one they check by reloading the site instead (§9).\n -->\n <wx-action\n v-if=\"canManage && menu.cache.enabled\"\n class=\"wx-menus__flush\"\n icon=\"refresh\"\n size=\"sm\"\n :title=\"t('menu.flush')\"\n @click=\"flush(menu)\"\n />\n\n <wx-row-menu v-if=\"canManage\" :actions=\"actionsFor(menu)\" :label=\"menu.title\" />\n </li>\n </ul>\n </template>\n\n <template #detail=\"{ inline, back }\">\n <menu-tree-pane\n v-if=\"chosen\"\n :key=\"chosen.key\"\n :menu=\"chosen\"\n :inline=\"inline\"\n :back=\"back\"\n @changed=\"load\"\n />\n </template>\n\n <template #empty>\n <wx-empty :title=\"t('menu.choose-menu')\" :description=\"t('menu.choose-menu-help')\" />\n </template>\n </wx-list-detail>\n </wx-card>\n </wx-list-screen>\n</template>\n\n<style>\n/*\n * What scrolls here is the page, the way it does on every other list of the panel.\n *\n * The card's corners are the screen's corners, and the two panes inside are square and paint\n * their own background to the edge — without the clip they cover the rounding. `clip` and not\n * `hidden`: `hidden` would make this a scroll container, and everything sticky inside would pin\n * itself to a box that never moves (CLAUDE.md §4).\n */\n.wx-menus > .wx-card__body {\n display: flex;\n flex-direction: column;\n overflow: clip;\n border-radius: inherit;\n}\n</style>\n\n<style scoped>\n.wx-menus__loading {\n padding: var(--wx-space-16);\n}\n\n.wx-menus__list {\n margin: 0;\n padding: var(--wx-space-8);\n list-style: none;\n}\n\n.wx-menus__row {\n display: flex;\n align-items: center;\n gap: var(--wx-space-4);\n margin: 0;\n padding-inline: var(--wx-space-8);\n border-radius: var(--wx-radius-sm);\n}\n\n.wx-menus__row:has(.is-current) {\n background: var(--wx-bg-subtle);\n}\n\n.wx-menus__open {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: var(--wx-space-2);\n /* The row is the target: a menu called `header` should not leave the line dead. */\n flex: 1 1 auto;\n min-width: 0;\n padding-block: var(--wx-space-8);\n padding-inline: 0;\n border: 0;\n background: none;\n font: inherit;\n color: inherit;\n text-align: start;\n cursor: pointer;\n}\n\n.wx-menus__open.is-current {\n color: var(--wx-color-primary);\n}\n\n.wx-menus__name {\n display: flex;\n align-items: center;\n gap: var(--wx-space-6);\n min-width: 0;\n max-width: 100%;\n}\n\n.wx-menus__count {\n flex: 0 0 auto;\n}\n\n.wx-menus__cache {\n display: flex;\n align-items: center;\n gap: var(--wx-space-4);\n min-width: 0;\n}\n\n/*\n * `:deep()`, because `WxAction` carries its tooltip's portal beside the button and so has a\n * fragment for a root — Vue puts the scope attribute only on a child with a single root, and\n * without this the rule matches nothing at all, silently (CLAUDE.md §4).\n */\n.wx-menus__row > :deep(.wx-menus__flush) {\n flex: 0 0 auto;\n}\n</style>\n","import type { AdminModule } from '@webx-ui/module-admin'\nimport MenusPage from './MenusPage.vue'\n\nexport interface MenuOptions {\n /** Where the section lives inside the panel. */\n path?: string\n}\n\n/**\n * The menus of the site as a section of the panel.\n *\n * One route and no editor under it: a menu is arranged in place, and an item is a dialog over\n * the tree it belongs to. Which menu is open travels in the address instead, so a link to the\n * footer is a link somebody can send.\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 menu(options: MenuOptions = {}): AdminModule {\n const path = options.path ?? '/menus'\n\n return {\n id: 'menu',\n path,\n routes: [{ path, name: 'webx.menus', component: MenusPage }],\n }\n}\n"],"names":["createMenusApi","admin","base","data","body","input","key","id","parentId","index","menuMessages","seeded","useMenuMessages","i18n","useAdmin","props","__props","open","resolve","dismiss","useModal","api","t","useTranslate","editing","ref","title","saving","errors","field","useTemplateRef","onMounted","nextTick","submit","error","_createBlock","_unref","WxDialog","$event","_createVNode","WxSpace","WxButton","WxFormItem","_createElementVNode","WxInput","emit","__emit","name","item","where","isExternal","actionsFor","WxSortableList","_normalizeClass","_cache","next","_withCtx","_hoisted_2","WxText","_createTextVNode","_toDisplayString","WxTooltip","WxIcon","_createElementBlock","_Fragment","_component_wx_menu_branch","parent","WxRowMenu","locales","useLocales","link","emptyLink","toLink","variant","isHeading","visible","chosenLocales","fallback","computed","variantOptions","localeOptions","locale","WxLinkPicker","WxSelect","WxCheckbox","WxSwitch","findItem","items","found","countBranch","total","child","movedId","before","after","arrived","rest","other","was","message","useErrorText","loading","canManage","edit","createModal","MenuItemDialog","load","toast","levelOf","setLevel","reorder","moved","add","save","duplicate","copy","siblings","one","remove","under","confirm","__expose","_openBlock","_hoisted_1","WxAction","_hoisted_3","_hoisted_4","WxSkeleton","WxEmpty","MenuBranch","route","useRoute","router","useRouter","menus","detailInline","MenuCreateDialog","module","current","asked","chosen","menu","watch","choose","made","rename","saved","flush","flushAll","actions","WxListScreen","WxCard","WxListDetail","_renderList","WxIndicator","WxDate","inline","back","MenuTreePane","options","path","MenusPage"],"mappings":";;;;AA4BO,SAASA,GAAeC,GAA+B;AAC5D,QAAMC,IAAO,GAAGD,EAAM,OAAO,UACvBE,IAAO,CAAIC,MAAyBA,EAAK;AAE/C,SAAO;AAAA,IACL,MAAM,MAAMH,EAAM,KAAK,IAAyBC,CAAI,EAAE,KAAKC,CAAI;AAAA,IAC/D,QAAQ,CAACE,MAAUJ,EAAM,KAAK,KAAwBC,GAAMG,CAAK,EAAE,KAAKF,CAAI;AAAA,IAC5E,QAAQ,CAACG,GAAKD,MACZJ,EAAM,KAAK,MAAyB,GAAGC,CAAI,IAAI,mBAAmBI,CAAG,CAAC,IAAID,CAAK,EAAE,KAAKF,CAAI;AAAA,IAC5F,QAAQ,CAACG,MAAQL,EAAM,KAAK,OAAO,GAAGC,CAAI,IAAI,mBAAmBI,CAAG,CAAC,EAAE,EAAE,KAAK,MAAA;AAAA,KAAe;AAAA,IAE7F,OAAO,CAACA,MACNL,EAAM,KACH,IAA6B,GAAGC,CAAI,IAAI,mBAAmBI,CAAG,CAAC,QAAQ,EACvE,KAAKH,CAAI;AAAA,IACd,SAAS,CAACG,GAAKD,MACbJ,EAAM,KACH,KAA4B,GAAGC,CAAI,IAAI,mBAAmBI,CAAG,CAAC,UAAUD,CAAK,EAC7E,KAAKF,CAAI;AAAA,IACd,UAAU,CAACG,GAAKC,GAAIF,MAClBJ,EAAM,KACH,MAA6B,GAAGC,CAAI,IAAI,mBAAmBI,CAAG,CAAC,UAAUC,CAAE,IAAIF,CAAK,EACpF,KAAKF,CAAI;AAAA,IACd,UAAU,CAACG,GAAKC,GAAIC,GAAUC,MAC5BR,EAAM,KACH,KAAK,GAAGC,CAAI,IAAI,mBAAmBI,CAAG,CAAC,UAAUC,CAAE,SAAS;AAAA,MAC3D,WAAWC;AAAA,MACX,OAAAC;AAAA,IAAA,CACD,EACA,KAAK,MAAA;AAAA,KAAe;AAAA,IACzB,YAAY,CAACH,GAAKC,MAChBN,EAAM,KACH,OAAsC,GAAGC,CAAI,IAAI,mBAAmBI,CAAG,CAAC,UAAUC,CAAE,EAAE,EACtF,KAAK,CAACH,MAASA,EAAK,KAAK,OAAO;AAAA,IAErC,OAAO,CAACE,MACNL,EAAM,KAAK,KAAK,GAAGC,CAAI,IAAI,mBAAmBI,CAAG,CAAC,gBAAgB,CAAA,CAAE,EAAE,KAAK,MAAA;AAAA,KAAe;AAAA,IAC5F,UAAU,MAAML,EAAM,KAAK,KAAK,GAAGC,CAAI,gBAAgB,CAAA,CAAE,EAAE,KAAK,MAAA;AAAA,KAAe;AAAA,EAAA;AAEnF;AC5DO,MAAMQ,KAAyC;AAAA,EACpD,QAAQ;AAAA,IACN,OAAO;AAAA,EAAA;AAAA,EAET,MAAM;AAAA,IACJ,OAAO;AAAA,IACP,YAAY;AAAA,IACZ,kBAAkB;AAAA,IAClB,aAAa;AAAA,IACb,YAAY;AAAA,IACZ,cAAc;AAAA,IACd,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,gBAAgB;AAAA,IAChB,SAAS;AAAA,IACT,QAAQ;AAAA,IACR,qBAAqB;AAAA,IACrB,oBAAoB;AAAA,IACpB,SAAS;AAAA,IACT,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,iBAAiB;AAAA,IACjB,eAAe;AAAA,IACf,oBAAoB;AAAA,IACpB,MAAM;AAAA,IAEN,YAAY;AAAA,IACZ,cAAc;AAAA,IACd,cAAc;AAAA,IACd,mBAAmB;AAAA,IACnB,MAAM;AAAA,IACN,WAAW;AAAA,IACX,YAAY;AAAA,IACZ,qBAAqB;AAAA,IACrB,oBAAoB;AAAA,IACpB,sBAAsB;AAAA,IACtB,gBAAgB;AAAA,IAChB,cAAc;AAAA,IACd,eAAe;AAAA,IACf,iBAAiB;AAAA,IAEjB,SAAS;AAAA,IACT,gBAAgB;AAAA,IAChB,aAAa;AAAA,IACb,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,WAAW;AAAA,IACX,kBAAkB;AAAA,IAElB,eAAe;AAAA,IACf,cAAc;AAAA,IACd,eAAe;AAAA,IACf,iBAAiB;AAAA,IACjB,iBAAiB;AAAA,IACjB,gBAAgB;AAAA,IAChB,iBAAiB;AAAA,IACjB,iBAAiB;AAAA,IACjB,eAAe;AAAA,IACf,gBAAgB;AAAA,IAEhB,eAAe;AAAA,IACf,eAAe;AAAA,IACf,aAAa;AAAA,IACb,OAAO;AAAA,IACP,aAAa;AAAA,IACb,SAAS;AAAA,EAAA;AAEb,GCzEMC,yBAAa,QAAA;AAGZ,SAASC,IAAwB;AACtC,MAAI;AACF,UAAM,EAAE,MAAAC,EAAA,IAASC,EAAA;AAEjB,IAAKH,GAAO,IAAIE,CAAI,MAClBA,EAAK,SAAS,aAAaH,EAAY,GACvCC,GAAO,IAAIE,CAAI;AAAA,EAEnB,QAAQ;AAAA,EAER;AACF;;;;;;;;ACIA,UAAME,IAAQC,GAER,EAAE,MAAAC,GAAM,SAAAC,GAAS,SAAAC,EAAA,IAAYC,GAAA,GAE7BnB,IAAQa,EAAA,GACRO,IAAMrB,GAAeC,CAAK;AAChC,IAAAW,EAAA;AAEA,UAAMU,IAAIC,EAAa,WAAW,GAE5BC,IAAUT,EAAM,SAAS,MAEzBT,IAAMmB,EAAIV,EAAM,MAAM,OAAO,EAAE,GAC/BW,IAAQD,EAAIV,EAAM,MAAM,SAAS,EAAE,GACnCY,IAASF,EAAI,EAAK,GAClBG,IAASH,EAA8B,EAAE,GACzCI,IAAQC,GAA4B,OAAO;AAEjD,IAAAC,GAAU,YAAY;AACpB,YAAMC,GAAA,GACNH,EAAM,OAAO,cAAc,OAAO,GAAG,MAAA;AAAA,IACvC,CAAC;AAED,mBAAeI,IAAwB;AACrC,UAAI,EAAAP,EAAM,MAAM,KAAA,MAAW,MAAMC,EAAO,QAExC;AAAA,QAAAA,EAAO,QAAQ,IACfC,EAAO,QAAQ,CAAA;AAEf,YAAI;AACF,gBAAMvB,IAAQ,EAAE,KAAKC,EAAI,MAAM,KAAA,GAAQ,OAAOoB,EAAM,MAAM,KAAA,EAAK;AAE/D,UAAAR;AAAA,YACEM,KAAWT,EAAM,SAAS,OACtB,MAAMM,EAAI,OAAON,EAAM,KAAK,KAAKA,EAAM,KAAK,IAAI,SAASV,IAAQ,EAAE,OAAOA,EAAM,MAAA,CAAO,IACvF,MAAMgB,EAAI,OAAOhB,CAAK;AAAA,UAAA;AAAA,QAE9B,SAAS6B,GAAO;AAGd,UAAAN,EAAO,QAASM,EAA2D,MAAM,UAAU,CAAA;AAAA,QAC7F,UAAA;AACE,UAAAP,EAAO,QAAQ;AAAA,QACjB;AAAA;AAAA,IACF;2BAIEQ,EA0CYC,EAAAC,EAAA,GAAA;AAAA,MAzCF,MAAMD,EAAAnB,CAAA;AAAA,sDAAAA,EAAI,QAAAqB,IAAA;AAAA,MACjB,OAAOd,IAAUY,EAAAd,CAAA,yBAAyBc,EAAAd,CAAA,EAAC,qBAAA;AAAA,MAC3C,OAAO;AAAA,IAAA;MA0BG,UACT,MAUW;AAAA,QAVXiB,EAUWH,EAAAI,EAAA,GAAA,EAVD,MAAK,QAAI;AAAA,qBACjB,MAAkF;AAAA,YAAlFD,EAAkFH,EAAAK,CAAA,GAAA;AAAA,cAAvE,SAAQ;AAAA,cAAW,gCAAOL,EAAAjB,CAAA,EAAA;AAAA,YAAO;yBAAI,MAAsB;AAAA,oBAAnBiB,EAAAd,CAAA,EAAC,aAAA,CAAA,GAAA,CAAA;AAAA,cAAA;;;YACpDiB,EAOYH,EAAAK,CAAA,GAAA;AAAA,cANV,MAAK;AAAA,cACJ,SAASd,EAAA;AAAA,cACT,UAAUD,EAAA,MAAM,KAAA,MAAI,MAAapB,EAAA,MAAI,KAAA,MAAI;AAAA,cACzC,SAAO2B;AAAA,YAAA;yBAER,MAAiD;AAAA,oBAA9CT,IAAUY,EAAAd,CAAA,EAAC,WAAA,IAAgBc,EAAAd,CAAA,EAAC,aAAA,CAAA,GAAA,CAAA;AAAA,cAAA;;;;;;;iBAjCrC,MAIe;AAAA,QAJfiB,EAIeH,EAAAM,CAAA,GAAA;AAAA,UAJA,OAAON,EAAAd,CAAA,EAAC,iBAAA;AAAA,UAAsB,OAAOM,EAAA,MAAO,QAAK,CAAA;AAAA,UAAO,UAAA;AAAA,QAAA;qBACrE,MAEM;AAAA,YAFNe,EAEM,OAAA;AAAA,uBAFG;AAAA,cAAJ,KAAId;AAAA,YAAA;cACPU,EAAqFH,EAAAQ,CAAA,GAAA;AAAA,4BAAlElB,EAAA;AAAA,8DAAAA,EAAK,QAAAY;AAAA,gBAAG,cAAYF,EAAAd,CAAA,EAAC,iBAAA;AAAA,gBAAsB,YAAaW,GAAM,CAAA,OAAA,CAAA;AAAA,cAAA;;;;;QAQrFM,EAYeH,EAAAM,CAAA,GAAA;AAAA,UAXZ,OAAON,EAAAd,CAAA,EAAC,gBAAA;AAAA,UACR,OAAOM,EAAA,MAAO,MAAG,CAAA;AAAA,UACjB,MAAMQ,EAAAd,CAAA,EAAC,eAAA;AAAA,UACR,UAAA;AAAA,QAAA;qBAEA,MAKE;AAAA,YALFiB,EAKEH,EAAAQ,CAAA,GAAA;AAAA,0BAJStC,EAAA;AAAA,4DAAAA,EAAG,QAAAgC;AAAA,cACX,UAAUd,KAAO,CAAKT,EAAM,MAAM,IAAI;AAAA,cACtC,cAAYqB,EAAAd,CAAA,EAAC,gBAAA;AAAA,cACb,YAAaW,GAAM,CAAA,OAAA,CAAA;AAAA,YAAA;;;;;;;;;;;;;;;;;;AC3E5B,UAAMlB,IAAQC,GAUR6B,IAAOC,GAQPxB,IAAIC,EAAa,WAAW;AAElC,aAASwB,EAAKC,GAA2B;AACvC,aAAOA,EAAK,UAAU,KAAK1B,EAAE,oBAAoB,IAAI0B,EAAK;AAAA,IAC5D;AASA,aAASC,EAAMD,GAA2B;AACxC,aAAIA,EAAK,WAAW,SAAe1B,EAAE,mBAAmB,IACpD0B,EAAK,WAAW,YAAYA,EAAK,aAAa,OAAa1B,EAAE,qBAAqB,IAE/E0B,EAAK,QAAQ1B,EAAE,mBAAmB;AAAA,IAC3C;AAGA,aAAS4B,EAAWF,GAA4B;AAC9C,aAAOA,EAAK,WAAW,SAAS,uBAAuB,KAAKA,EAAK,OAAO,EAAE;AAAA,IAC5E;AAEA,aAASG,EAAWH,GAAgC;AAClD,aAAIjC,EAAM,WAAiB,CAAA,IAEpB;AAAA,QACL,EAAE,KAAK,QAAQ,MAAM,QAAQ,OAAOO,EAAE,WAAW,GAAG,KAAK,MAAMuB,EAAK,QAAQG,CAAI,EAAA;AAAA,QAChF;AAAA,UACE,KAAK;AAAA,UACL,MAAM;AAAA,UACN,OAAO1B,EAAE,gBAAgB;AAAA,UACzB,KAAK,MAAMuB,EAAK,aAAaG,CAAI;AAAA,QAAA;AAAA,QAEnC;AAAA,UACE,KAAK;AAAA,UACL,MAAM;AAAA,UACN,OAAO1B,EAAE,aAAa;AAAA,UACtB,QAAQ;AAAA,UACR,KAAK,MAAMuB,EAAK,UAAUG,CAAI;AAAA,QAAA;AAAA,MAChC;AAAA,IAEJ;;;kBAIEb,EAuFmBC,EAAAgB,EAAA,GAAA;AAAA,QAtFjB,OAAKC,EAAA,CAAC,kBAAgB,EAAA,aACCtC,EAAM,aAAQ,KAAA,CAAA,CAAA;AAAA,QACpC,eAAaA,EAAM;AAAA,QACpB,OAAA;AAAA,QACA,MAAK;AAAA,QACL,OAAM;AAAA,QACN,YAAS;AAAA,QACR,cAAYgC;AAAA,QACZ,UAAUhC,EAAM;AAAA,QAChB,cAAY;AAAA,QACZ,uBAAkBuC,EAAA,CAAA,MAAAA,EAAA,CAAA,IAAA,CAAGC,MAAwBV,aAAgB9B,EAAM,UAAUwC,CAAI;AAAA,MAAA;QAOvE,SAAM,MAAqC;AAAA,0BAArCZ,EAAqC,QAAA,EAA/B,OAAM,0BAAsB,MAAA,EAAA;AAAA,QAAA;QAExC,SAAOa,EAChB,CA0CM,EA3Cc,MAAAR,QAAI;AAAA,UACxBL,EA0CM,OAAA;AAAA,YA1CD,OAAKU,EAAA,CAAC,gBAAc,EAAA,UAAA,CAAsBL,EAAK,WAAO,CAAKA,EAAK,WAAS,CAAA;AAAA,UAAA;YAC5EL,EA4BS,UAAA;AAAA,cA3BP,MAAK;AAAA,cACL,OAAM;AAAA,cACL,UAAU5B,EAAM;AAAA,cAChB,SAAK,CAAAuB,MAAEO,EAAI,QAASG,CAAI;AAAA,YAAA;cAEzBL,EAgBO,QAhBPc,IAgBO;AAAA,gBAfLlB,EAEUH,EAAAsB,CAAA,GAAA;AAAA,kBAFD,UAAA;AAAA,kBAAU,QAAQV,EAAK,aAAU,aAAA;AAAA,gBAAA;6BACxC,MAAgB;AAAA,oBAAbW,EAAAC,EAAAb,EAAKC,CAAI,CAAA,GAAA,CAAA;AAAA,kBAAA;;;gBAOIE,EAAWF,CAAI,UAAjCb,EAEaC,EAAAyB,EAAA,GAAA;AAAA;kBAFwB,SAASzB,EAAAd,CAAA,EAAC,eAAA;AAAA,gBAAA;6BAC7C,MAA8E;AAAA,oBAA9EiB,EAA8EH,EAAA0B,CAAA,GAAA;AAAA,sBAArE,OAAM;AAAA,sBAAqB,MAAK;AAAA,sBAAQ,OAAO1B,EAAAd,CAAA,EAAC,eAAA;AAAA,oBAAA;;;;gBAGzC0B,EAAK,gBAAvBb,EAEaC,EAAAyB,EAAA,GAAA;AAAA;kBAFoB,SAASzB,EAAAd,CAAA,EAAC,cAAA;AAAA,gBAAA;6BACzC,MAAsF;AAAA,oBAAtFiB,EAAsFH,EAAA0B,CAAA,GAAA;AAAA,sBAA7E,OAAM;AAAA,sBAAqB,MAAK;AAAA,sBAAiB,OAAO1B,EAAAd,CAAA,EAAC,cAAA;AAAA,oBAAA;;;;;cAItEiB,EAGUH,EAAAsB,CAAA,GAAA;AAAA,gBAHD,MAAK;AAAA,gBAAK,MAAK;AAAA,gBAAQ,UAAA;AAAA,cAAA;2BAC9B,MAAmE;AAAA,kBAAnDV,EAAK,mBAArBe,EAAmEC,GAAA,EAAA,KAAA,KAAA;AAAA,wBAA/B5B,EAAAd,CAAA,EAAC,cAAA,CAAA,GAAA,CAAA;AAAA,kBAAA,gBACrCyC,EAA6CC,GAAA,EAAA,KAAA,KAAA;AAAA,oBAAzBL,EAAAC,EAAAX,EAAMD,CAAI,CAAA,GAAA,CAAA;AAAA,kBAAA;;;;;YASlBA,EAAK,UAGAA,EAAK,8BAA1Bb,EAEUC,EAAAsB,CAAA,GAAA;AAAA;cAF2B,OAAM;AAAA,cAAsB,MAAK;AAAA,cAAK,MAAK;AAAA,YAAA;yBAC9E,MAA2B;AAAA,oBAAxBtB,EAAAd,CAAA,EAAC,kBAAA,CAAA,GAAA,CAAA;AAAA,cAAA;;wBAJNa,EAEUC,EAAAsB,CAAA,GAAA;AAAA;cAFoB,OAAM;AAAA,cAAsB,MAAK;AAAA,cAAK,MAAK;AAAA,YAAA;yBACvE,MAAsB;AAAA,oBAAnBtB,EAAAd,CAAA,EAAC,aAAA,CAAA,GAAA,CAAA;AAAA,cAAA;;;;UAWRiB,EAQE0B,GAAA;AAAA,YAPC,OAAOjB,EAAK;AAAA,YACZ,aAAWA,EAAK;AAAA,YAChB,UAAUjC,EAAM;AAAA,YAChB,WAAOuC,EAAA,CAAA,MAAAA,EAAA,CAAA,IAAA,CAAGY,GAAuBX,MAAwBV,EAAI,WAAYqB,GAAQX,CAAI;AAAA,YACrF,QAAID,EAAA,CAAA,MAAAA,EAAA,CAAA,IAAA,CAAAhB,MAAEO,EAAI,QAASP,CAAM;AAAA,YACzB,aAASgB,EAAA,CAAA,MAAAA,EAAA,CAAA,IAAA,CAAAhB,MAAEO,EAAI,aAAcP,CAAM;AAAA,YACnC,UAAMgB,EAAA,CAAA,MAAAA,EAAA,CAAA,IAAA,CAAAhB,MAAEO,EAAI,UAAWP,CAAM;AAAA,UAAA;;;;QAQjBvB,EAAM;gBAAW;AAAA,UAChC,IAAAyC,EAAA,CAA+D,EADpB,MAAAR,QAAI;AAAA,YAC/CT,EAA+DH,EAAA+B,EAAA,GAAA;AAAA,cAAjD,SAAShB,EAAWH,CAAI;AAAA,cAAI,OAAOD,EAAKC,CAAI;AAAA,YAAA;;;;;;;;;;;;;;;;;;;;AC1IhE,UAAMjC,IAAQC,GASR,EAAE,MAAAC,GAAM,SAAAC,GAAS,SAAAC,EAAA,IAAYC,GAAA,GAE7BgD,IAAUC,GAAA;AAChB,IAAAzD,EAAA;AAEA,UAAMU,IAAIC,EAAa,WAAW,GAE5B+C,IAAO7C,EAAeV,EAAM,SAAS,OAAOwD,OAAcC,EAAOzD,EAAM,IAAI,CAAC,GAC5EW,IAAQD,EAAmB,EAAE,GAAIV,EAAM,MAAM,SAAS,CAAA,GAAK,GAC3D0D,IAAUhD,EAAIV,EAAM,MAAM,WAAWA,EAAM,KAAK,SAAS,CAAC,KAAK,MAAM,GACrE2D,IAAYjD,EAAIV,EAAM,MAAM,cAAc,EAAK,GAC/C4D,IAAUlD,EAAIV,EAAM,MAAM,WAAW,EAAI,GACzC6D,IAAgBnD,EAAc,CAAC,GAAIV,EAAM,MAAM,WAAW,CAAA,CAAG,CAAC,GAS9D8D,IAAWC,EAAS,MAAM/D,EAAM,MAAM,UAAU,SAAS,EAAE,GAG3DgE,IAAiBD;AAAA,MAAyB,MAC9C/D,EAAM,KAAK,SAAS,IAAI,CAACgC,OAAU,EAAE,OAAOA,GAAM,OAAOA,EAAA,EAAO;AAAA,IAAA,GAG5DiC,IAAgBF;AAAA,MAAyB,MAC7CV,EAAQ,KAAK,MAAM,IAAI,CAACa,OAAY,EAAE,OAAOA,EAAO,MAAM,OAAOA,EAAO,SAASA,EAAO,OAAO;AAAA,IAAA;AAGjG,aAAST,EAAOxB,GAA8B;AAC5C,aAAO;AAAA,QACL,QAAQA,EAAK;AAAA,QACb,aAAaA,EAAK;AAAA,QAClB,WAAWA,EAAK;AAAA,QAChB,KAAKA,EAAK;AAAA,QACV,MAAMA,EAAK;AAAA,QACX,SAASA,EAAK;AAAA,QACd,KAAKA,EAAK;AAAA,MAAA;AAAA,IAEd;AAEA,aAASf,IAAe;AACtB,MAAAf,EAAQ;AAAA,QACN,OAAOQ,EAAM;AAAA,QACb,MAAM4C,EAAK;AAAA,QACX,SAASG,EAAQ;AAAA,QACjB,YAAYC,EAAU;AAAA,QACtB,SAASE,EAAc;AAAA,QACvB,SAASD,EAAQ;AAAA,MAAA,CAClB;AAAA,IACH;2BAIExC,EAuDYC,EAAAC,EAAA,GAAA;AAAA,MAvDO,MAAMD,EAAAnB,CAAA;AAAA,sDAAAA,EAAI,QAAAqB,IAAA;AAAA,MAAG,OAAOF,EAAAd,CAAA,EAAC,iBAAA;AAAA,MAAsB,OAAO;AAAA,IAAA;MAiDxD,UACT,MAGW;AAAA,QAHXiB,EAGWH,EAAAI,EAAA,GAAA,EAHD,MAAK,QAAI;AAAA,qBACjB,MAAkF;AAAA,YAAlFD,EAAkFH,EAAAK,CAAA,GAAA;AAAA,cAAvE,SAAQ;AAAA,cAAW,gCAAOL,EAAAjB,CAAA,EAAA;AAAA,YAAO;yBAAI,MAAsB;AAAA,oBAAnBiB,EAAAd,CAAA,EAAC,aAAA,CAAA,GAAA,CAAA;AAAA,cAAA;;;YACpDiB,EAA0EH,EAAAK,CAAA,GAAA;AAAA,cAA/D,MAAK;AAAA,cAAW,SAAOR;AAAA,YAAA;yBAAQ,MAAoB;AAAA,oBAAjBG,EAAAd,CAAA,EAAC,WAAA,CAAA,GAAA,CAAA;AAAA,cAAA;;;;;;;iBAnDlD,MAEe;AAAA,QAFfiB,EAEeH,EAAAM,CAAA,GAAA;AAAA,UAFA,OAAON,EAAAd,CAAA,EAAC,kBAAA;AAAA,QAAA;qBACrB,MAAiC;AAAA,YAAjCiB,EAAiCH,EAAA8C,EAAA,GAAA;AAAA,0BAARZ,EAAA;AAAA,4DAAAA,EAAI,QAAAhC;AAAA,YAAA;;;;QAG/BC,EAOeH,EAAAM,CAAA,GAAA;AAAA,UAPA,OAAON,EAAAd,CAAA,EAAC,kBAAA;AAAA,UAAuB,MAAMc,EAAAd,CAAA,EAAC,iBAAA;AAAA,QAAA;qBACnD,MAKE;AAAA,YALFiB,EAKEH,EAAAQ,CAAA,GAAA;AAAA,0BAJSlB,EAAA;AAAA,4DAAAA,EAAK,QAAAY;AAAA,cACd,WAAA;AAAA,cACC,aAAauC,EAAA;AAAA,cACb,cAAYzC,EAAAd,CAAA,EAAC,kBAAA;AAAA,YAAA;;;;QAIEyD,EAAA,MAAe,SAAM,UAAzC5C,EAMeC,EAAAM,CAAA,GAAA;AAAA;UANiC,OAAON,EAAAd,CAAA,EAAC,oBAAA;AAAA,QAAA;qBACtD,MAIE;AAAA,YAJFiB,EAIEH,EAAA+C,EAAA,GAAA;AAAA,0BAHSV,EAAA;AAAA,4DAAAA,EAAO,QAAAnC;AAAA,cACf,SAASyC,EAAA;AAAA,cACT,cAAY3C,EAAAd,CAAA,EAAC,oBAAA;AAAA,YAAA;;;;QASV0D,EAAA,MAAc,SAAM,UAD5B7C,EAaeC,EAAAM,CAAA,GAAA;AAAA;UAXZ,OAAON,EAAAd,CAAA,EAAC,oBAAA;AAAA,UACR,MAAMc,EAAAd,CAAA,EAAC,mBAAA;AAAA,QAAA;qBAER,MAOE;AAAA,YAPFiB,EAOEH,EAAA+C,EAAA,GAAA;AAAA,0BANSP,EAAA;AAAA,4DAAAA,EAAa,QAAAtC;AAAA,cACrB,SAAS0C,EAAA;AAAA,cACV,UAAA;AAAA,cACA,WAAA;AAAA,cACC,aAAa5C,EAAAd,CAAA,EAAC,kBAAA;AAAA,cACd,cAAYc,EAAAd,CAAA,EAAC,oBAAA;AAAA,YAAA;;;;QAIlBiB,EAEeH,EAAAM,CAAA,GAAA;AAAA,UAFA,MAAMN,EAAAd,CAAA,EAAC,mBAAA;AAAA,QAAA;qBACpB,MAA4E;AAAA,YAA5EiB,EAA4EH,EAAAgD,EAAA,GAAA;AAAA,0BAAtDV,EAAA;AAAA,4DAAAA,EAAS,QAAApC;AAAA,YAAA;yBAAE,MAA6B;AAAA,oBAA1BF,EAAAd,CAAA,EAAC,oBAAA,CAAA,GAAA,CAAA;AAAA,cAAA;;;;;;QAGvCiB,EAEeH,EAAAM,CAAA,GAAA,MAAA;AAAA,qBADb,MAAgE;AAAA,YAAhEH,EAAgEH,EAAAiD,EAAA,GAAA;AAAA,0BAA5CV,EAAA;AAAA,4DAAAA,EAAO,QAAArC;AAAA,cAAG,OAAOF,EAAAd,CAAA,EAAC,oBAAA;AAAA,YAAA;;;;;;;;;AC7IrC,SAASgE,GAASC,GAAsBhF,GAAgC;AAC7E,aAAWyC,KAAQuC,GAAO;AACxB,QAAIvC,EAAK,OAAOzC,EAAI,QAAOyC;AAE3B,UAAMwC,IAAQF,GAAStC,EAAK,UAAUzC,CAAE;AAExC,QAAIiF,MAAU,KAAM,QAAOA;AAAA,EAC7B;AAEA,SAAO;AACT;AAGO,SAASC,GAAYzC,GAA2B;AACrD,SAAOA,EAAK,SAAS,OAAO,CAAC0C,GAAOC,MAAUD,IAAQ,IAAID,GAAYE,CAAK,GAAG,CAAC;AACjF;AAcO,SAASC,GAAQC,GAAkBC,GAAgC;AACxE,QAAMC,IAAUD,EAAM,KAAK,CAACvF,MAAO,CAACsF,EAAO,SAAStF,CAAE,CAAC;AAEvD,MAAIwF,MAAY,OAAW,QAAOA;AAClC,MAAID,EAAM,WAAWD,EAAO,OAAQ,QAAO;AAE3C,aAAWtF,KAAMuF,GAAO;AACtB,QAAIA,EAAM,QAAQvF,CAAE,MAAMsF,EAAO,QAAQtF,CAAE,EAAG;AAE9C,UAAMyF,IAAOF,EAAM,OAAO,CAACG,MAAUA,MAAU1F,CAAE,GAC3C2F,IAAML,EAAO,OAAO,CAACI,MAAUA,MAAU1F,CAAE;AAEjD,QAAIyF,EAAK,WAAWE,EAAI,UAAUF,EAAK,MAAM,CAACC,GAAOxF,MAAUwF,MAAUC,EAAIzF,CAAK,CAAC;AACjF,aAAOF;AAAA,EAEX;AAEA,SAAO;AACT;;;;;;;;;;;ACjBA,UAAMQ,IAAQC,GAUR6B,IAAOC,GAKP7C,IAAQa,EAAA,GACRO,IAAMrB,GAAeC,CAAK;AAChC,IAAAW,EAAA;AAEA,UAAMU,IAAIC,EAAa,WAAW,GAC5B4E,IAAUC,GAAA,GAEVb,IAAQ9D,EAAmB,EAAE,GAC7B4E,IAAU5E,EAAI,EAAI,GAElB6E,IAAYxB,EAAS,MAAM7E,EAAM,IAAI,aAAa,CAAC,GAEnDsG,IAAOC,GAAwEC,EAAc;AAEnG,mBAAeC,IAAsB;AACnC,MAAAL,EAAQ,QAAQ;AAEhB,UAAI;AACF,QAAAd,EAAM,QAAQ,MAAMlE,EAAI,MAAMN,EAAM,KAAK,GAAG;AAAA,MAC9C,SAASmB,GAAO;AACd,QAAAyE,EAAM,OAAOR,EAAQjE,CAAK,CAAC;AAAA,MAC7B,UAAA;AACE,QAAAmE,EAAQ,QAAQ;AAAA,MAClB;AAAA,IACF;AAEA,IAAKK,EAAA;AAGL,aAASE,EAAQpG,GAAwC;AACvD,aAAIA,MAAa,OAAa+E,EAAM,QAE7BD,GAASC,EAAM,OAAO/E,CAAQ,GAAG,YAAY,CAAA;AAAA,IACtD;AAEA,aAASqG,EAASrG,GAAyB+C,GAA2B;AACpE,UAAI/C,MAAa,MAAM;AACrB,QAAA+E,EAAM,QAAQhC;AAEd;AAAA,MACF;AAEA,YAAMW,IAASoB,GAASC,EAAM,OAAO/E,CAAQ;AAE7C,MAAI0D,MAAW,SAAMA,EAAO,WAAWX;AAAA,IACzC;AAUA,mBAAeuD,EAAQtG,GAAyB+C,GAAoC;AAClF,YAAMsC,IAASe,EAAQpG,CAAQ,EAAE,IAAI,CAACwC,MAASA,EAAK,EAAE;AAEtD,MAAA6D,EAASrG,GAAU+C,CAAI;AAEvB,YAAMuC,IAAQvC,EAAK,IAAI,CAACP,MAASA,EAAK,EAAE,GAClC+D,IAAQnB,GAAQC,GAAQC,CAAK;AAEnC,UAAIiB,MAAU;AAEd,YAAI;AACF,gBAAM1F,EAAI,SAASN,EAAM,KAAK,KAAKgG,GAAOvG,GAAUsF,EAAM,QAAQiB,CAAK,CAAC,GACxElE,EAAK,SAAS;AAAA,QAChB,SAASX,GAAO;AACd,UAAAyE,EAAM,OAAOR,EAAQjE,GAAOZ,EAAE,kBAAkB,CAAC,CAAC,GAClD,MAAMoF,EAAA;AAAA,QACR;AAAA,IACF;AAEA,mBAAeM,IAAqB;AAClC,YAAM3G,IAAQ,MAAMkG,EAAK,EAAE,MAAMxF,EAAM,MAAM,MAAM,MAAM;AAEzD,UAAKV;AAEL,YAAI;AACF,gBAAMgB,EAAI,QAAQN,EAAM,KAAK,KAAKV,CAAK,GACvCsG,EAAM,QAAQrF,EAAE,iBAAiB,CAAC,GAClC,MAAMoF,EAAA,GACN7D,EAAK,SAAS;AAAA,QAChB,SAASX,GAAO;AACd,UAAAyE,EAAM,OAAOR,EAAQjE,CAAK,CAAC;AAAA,QAC7B;AAAA,IACF;AAEA,mBAAe+E,EAAKjE,GAAkC;AACpD,YAAM3C,IAAQ,MAAMkG,EAAK,EAAE,MAAMxF,EAAM,MAAM,MAAAiC,GAAM;AAEnD,UAAK3C;AAEL,YAAI;AACF,gBAAMgB,EAAI,SAASN,EAAM,KAAK,KAAKiC,EAAK,IAAI3C,CAAK,GACjDsG,EAAM,QAAQrF,EAAE,iBAAiB,CAAC,GAClC,MAAMoF,EAAA,GACN7D,EAAK,SAAS;AAAA,QAChB,SAASX,GAAO;AACd,UAAAyE,EAAM,OAAOR,EAAQjE,CAAK,CAAC;AAAA,QAC7B;AAAA,IACF;AASA,mBAAegF,EAAUlE,GAAkC;AACzD,UAAI;AACF,cAAMmE,IAAO,MAAM9F,EAAI,QAAQN,EAAM,KAAK,KAAK;AAAA,UAC7C,OAAOiC,EAAK;AAAA,UACZ,MAAM;AAAA,YACJ,QAAQA,EAAK;AAAA,YACb,aAAaA,EAAK;AAAA,YAClB,WAAWA,EAAK;AAAA,YAChB,KAAKA,EAAK;AAAA,YACV,MAAMA,EAAK;AAAA,YACX,SAASA,EAAK;AAAA,YACd,KAAKA,EAAK;AAAA,UAAA;AAAA,UAEZ,SAASA,EAAK;AAAA,UACd,YAAYA,EAAK;AAAA,UACjB,SAASA,EAAK;AAAA,UACd,SAASA,EAAK;AAAA,UACd,WAAWA,EAAK;AAAA,QAAA,CACjB,GAEKoE,IAAWR,EAAQ5D,EAAK,SAAS;AACvC,cAAM3B,EAAI;AAAA,UACRN,EAAM,KAAK;AAAA,UACXoG,EAAK;AAAA,UACLnE,EAAK;AAAA,UACLoE,EAAS,UAAU,CAACC,MAAQA,EAAI,OAAOrE,EAAK,EAAE,IAAI;AAAA,QAAA,GAGpD2D,EAAM,QAAQrF,EAAE,iBAAiB,CAAC,GAClC,MAAMoF,EAAA,GACN7D,EAAK,SAAS;AAAA,MAChB,SAASX,GAAO;AACd,QAAAyE,EAAM,OAAOR,EAAQjE,CAAK,CAAC;AAAA,MAC7B;AAAA,IACF;AASA,mBAAeoF,EAAOtE,GAAkC;AACtD,YAAMuE,IAAQ9B,GAAYzC,CAAI;AAW9B,UATe,MAAMwE,GAAQ;AAAA,QAC3B,OAAOlG,EAAE,0BAA0B,EAAE,MAAM0B,EAAK,SAAS1B,EAAE,oBAAoB,GAAG;AAAA,QAClF,SACEiG,MAAU,IAAIjG,EAAE,uBAAuB,IAAIA,EAAE,2BAA2B,EAAE,OAAOiG,GAAO;AAAA,QAC1F,aAAajG,EAAE,aAAa;AAAA,QAC5B,YAAYA,EAAE,aAAa;AAAA,QAC3B,MAAM;AAAA,MAAA,CACP;AAID,YAAI;AACF,gBAAMD,EAAI,WAAWN,EAAM,KAAK,KAAKiC,EAAK,EAAE,GAC5C2D,EAAM,QAAQrF,EAAE,mBAAmB,CAAC,GACpC,MAAMoF,EAAA,GACN7D,EAAK,SAAS;AAAA,QAChB,SAASX,GAAO;AACd,UAAAyE,EAAM,OAAOR,EAAQjE,CAAK,CAAC;AAAA,QAC7B;AAAA,IACF;AAEA,WAAAuF,EAAa,EAAE,QAAQf,GAAM,cAI3BgB,EAAA,GAAA3D,EA4CM,OA5CN4D,IA4CM;AAAA,MA3CJhF,EAsBS,UAtBTc,IAsBS;AAAA,QAhBE1C,EAAM,2BADfoB,EAMEC,EAAAwF,EAAA,GAAA;AAAA;UAJA,MAAK;AAAA,UACL,MAAK;AAAA,UACJ,OAAOxF,EAAAd,CAAA,EAAC,WAAA;AAAA,UACR,SAAKgC,EAAA,CAAA,MAAAA,EAAA,CAAA,IAAA,CAAAhB,MAAEvB,EAAM,OAAA;AAAA,QAAI;QAGpB4B,EAGM,OAHNkF,IAGM;AAAA,UAFJtF,EAAoEH,EAAAsB,CAAA,GAAA;AAAA,YAA3D,QAAO;AAAA,YAAW,UAAA;AAAA,UAAA;uBAAS,MAAsB;AAAA,kBAAnB3C,EAAM,KAAK,KAAK,GAAA,CAAA;AAAA,YAAA;;;UACvDwB,EAA4EH,EAAAsB,CAAA,GAAA;AAAA,YAAnE,MAAK;AAAA,YAAK,MAAK;AAAA,YAAQ,MAAA;AAAA,YAAK,UAAA;AAAA,UAAA;uBAAS,MAAoB;AAAA,kBAAjB3C,EAAM,KAAK,GAAG,GAAA,CAAA;AAAA,YAAA;;;;QAGhDuF,EAAA,cAAjBnE,EAGYC,EAAAK,CAAA,GAAA;AAAA;UAHgB,SAAQ;AAAA,UAAU,MAAK;AAAA,UAAM,SAAOuE;AAAA,QAAA;UACnD,QAAK,MAAuB;AAAA,YAAvBzE,EAAuBH,EAAA0B,CAAA,GAAA,EAAd,MAAK,QAAM;AAAA,UAAA;qBAAc,MAClD;AAAA,YADkDH,EAAA,QAC/CvB,EAAAd,CAAA,EAAC,eAAA,CAAA,GAAA,CAAA;AAAA,UAAA;;;;MAIRqB,EAkBM,OAlBNmF,IAkBM;AAAA,QAjBezB,EAAA,cAAnBlE,EAAwCC,EAAA2F,EAAA,GAAA;AAAA;UAAX,MAAM;AAAA,QAAA,MAGtBxC,EAAA,MAAM,WAAM,UADzBpD,EAIEC,EAAA4F,CAAA,GAAA;AAAA;UAFC,OAAO5F,EAAAd,CAAA,EAAC,iBAAA;AAAA,UACR,aAAac,EAAAd,CAAA,EAAC,sBAAA;AAAA,QAAA,+CAGjBa,EAQE8F,IAAA;AAAA;UANC,OAAO1C,EAAA;AAAA,UACP,WAAWe,EAAA;AAAA,UACX,WAASQ;AAAA,UACT,QAAMG;AAAA,UACN,aAAWC;AAAA,UACX,UAAQI;AAAA,QAAA;;;;;;;;;;ACxOjB,UAAMrH,IAAQa,EAAA,GACRO,IAAMrB,GAAeC,CAAK,GAC1BiI,IAAQC,GAAA,GACRC,IAASC,GAAA;AACf,IAAAzH,EAAA;AAEA,UAAMU,IAAIC,EAAa,WAAW,GAE5B4E,IAAUC,GAAA,GAEVkC,IAAQ7G,EAAe,EAAE,GACzB4E,IAAU5E,EAAI,EAAI,GAClBR,IAAOQ,EAAI,EAAK,GAChB8G,IAAe9G,EAAI,EAAI,GAEvB6E,IAAYxB,EAAS,MAAM7E,EAAM,IAAI,aAAa,CAAC,GAEnDsG,IAAOC,GAAgDgC,EAAgB,GAEvE9G,IAAQoD;AAAA,MACZ,MACE7E,EAAM,MAAM,UAAU,QAAQ,KAAK,CAACwI,MAAWA,EAAO,OAAO,MAAM,GAAG,SACtEnH,EAAE,cAAc;AAAA,IAAA,GAOdoH,IAAU5D,EAAwB,MAAM;AAC5C,YAAM6D,IAAQT,EAAM,MAAM;AAE1B,aAAO,OAAOS,KAAU,YAAYA,MAAU,KAAKA,IAAQ;AAAA,IAC7D,CAAC,GAEKC,IAAS9D,EAAS,MAAMwD,EAAM,MAAM,KAAK,CAACO,MAASA,EAAK,QAAQH,EAAQ,KAAK,KAAK,IAAI;AAE5F,mBAAehC,IAAsB;AACnC,MAAAL,EAAQ,QAAQ;AAEhB,UAAI;AACF,QAAAiC,EAAM,QAAQ,MAAMjH,EAAI,KAAA;AAAA,MAC1B,SAASa,GAAO;AACd,QAAAyE,EAAM,OAAOR,EAAQjE,CAAK,CAAC;AAAA,MAC7B,UAAA;AACE,QAAAmE,EAAQ,QAAQ;AAAA,MAClB;AAAA,IACF;AAEA,IAAKK,EAAA,GAOLoC,GAAM,CAACR,GAAOM,GAAQL,CAAY,GAAG,MAAM;AACzC,MAAI,CAACA,EAAa,SAASD,EAAM,MAAM,WAAW,KAAKM,EAAO,UAAU,QAEnER,EAAO,QAAQ,EAAE,OAAO,EAAE,GAAGF,EAAM,OAAO,MAAMI,EAAM,MAAM,CAAC,EAAE,IAAA,GAAO;AAAA,IAC7E,CAAC,GAEDQ,GAAMF,GAAQ,CAACC,MAAS;AACtB,MAAIA,MAAS,QAAQN,EAAa,YAAY,QAAQ;AAAA,IACxD,CAAC;AAED,aAASQ,EAAOF,GAAqB;AACnC,MAAKT,EAAO,QAAQ,EAAE,OAAO,EAAE,GAAGF,EAAM,OAAO,MAAMW,EAAK,IAAA,EAAI,CAAG,GACjE5H,EAAK,QAAQ;AAAA,IACf;AAEA,mBAAe+F,IAAqB;AAClC,YAAMgC,IAAO,MAAMzC,EAAK,EAAE,MAAM,MAAM;AAEtC,MAAKyC,MAEL,MAAMtC,EAAA,GACNqC,EAAOC,CAAI;AAAA,IACb;AAEA,mBAAeC,EAAOJ,GAA8B;AAClD,YAAMK,IAAQ,MAAM3C,EAAK,EAAE,MAAAsC,GAAM;AAEjC,MAAKK,MAELvC,EAAM,QAAQrF,EAAE,cAAc,CAAC,GAG3BoH,EAAQ,UAAUG,EAAK,OAAOK,EAAM,QAAQL,EAAK,OAC9CT,EAAO,QAAQ,EAAE,OAAO,EAAE,GAAGF,EAAM,OAAO,MAAMgB,EAAM,IAAA,EAAI,CAAG,GAGpE,MAAMxC,EAAA;AAAA,IACR;AAEA,mBAAeY,EAAOuB,GAA8B;AASlD,UARe,MAAMrB,GAAQ;AAAA,QAC3B,OAAOlG,EAAE,0BAA0B,EAAE,MAAMuH,EAAK,OAAO;AAAA,QACvD,SAASvH,EAAE,uBAAuB;AAAA,QAClC,aAAaA,EAAE,aAAa;AAAA,QAC5B,YAAYA,EAAE,aAAa;AAAA,QAC3B,MAAM;AAAA,MAAA,CACP;AAID,YAAI;AAIF,cAHA,MAAMD,EAAI,OAAOwH,EAAK,GAAG,GACzBlC,EAAM,QAAQrF,EAAE,cAAc,CAAC,GAE3BoH,EAAQ,UAAUG,EAAK,KAAK;AAC9B,kBAAM7C,IAAO,EAAE,GAAGkC,EAAM,MAAA;AACxB,mBAAOlC,EAAK,MACPoC,EAAO,QAAQ,EAAE,OAAOpC,GAAM,GACnC/E,EAAK,QAAQ;AAAA,UACf;AAEA,gBAAMyF,EAAA;AAAA,QACR,SAASxE,GAAO;AACd,UAAAyE,EAAM,OAAOR,EAAQjE,CAAK,CAAC;AAAA,QAC7B;AAAA,IACF;AASA,mBAAeiH,EAAMN,GAA8B;AACjD,UAAI;AACF,cAAMxH,EAAI,MAAMwH,EAAK,GAAG,GACxBlC,EAAM,QAAQrF,EAAE,cAAc,CAAC,GAC/B,MAAMoF,EAAA;AAAA,MACR,SAASxE,GAAO;AACd,QAAAyE,EAAM,OAAOR,EAAQjE,CAAK,CAAC;AAAA,MAC7B;AAAA,IACF;AAEA,mBAAekH,IAA0B;AACvC,UAAI;AACF,cAAM/H,EAAI,SAAA,GACVsF,EAAM,QAAQrF,EAAE,cAAc,CAAC,GAC/B,MAAMoF,EAAA;AAAA,MACR,SAASxE,GAAO;AACd,QAAAyE,EAAM,OAAOR,EAAQjE,CAAK,CAAC;AAAA,MAC7B;AAAA,IACF;AAEA,aAASiB,EAAW0F,GAA4B;AAC9C,aAAKvC,EAAU,QAER;AAAA,QACL,EAAE,KAAK,UAAU,MAAM,QAAQ,OAAOhF,EAAE,aAAa,GAAG,KAAK,MAAA;AAAM,UAAK2H,EAAOJ,CAAI;AAAA,UAAA;AAAA,QACnF,EAAE,KAAK,SAAS,MAAM,WAAW,OAAOvH,EAAE,YAAY,GAAG,KAAK,MAAA;AAAM,UAAK6H,EAAMN,CAAI;AAAA,UAAA;AAAA,QACnF,GAAIA,EAAK,IAAI,SACT;AAAA,UACE;AAAA,YACE,KAAK;AAAA,YACL,MAAM;AAAA,YACN,OAAOvH,EAAE,aAAa;AAAA,YACtB,QAAQ;AAAA,YACR,KAAK,MAAA;AAAM,cAAKgG,EAAOuB,CAAI;AAAA;AAAA,UAAA;AAAA,QAC7B,IAEF,CAAA;AAAA,MAAC,IAfsB,CAAA;AAAA,IAiB/B;AAEA,UAAMQ,IAAUvE,EAAyB,MAClCwB,EAAU,QAER;AAAA,MACL;AAAA,QACE,KAAK;AAAA,QACL,OAAOhF,EAAE,gBAAgB;AAAA,QACzB,MAAM;AAAA,QACN,MAAM;AAAA,QACN,KAAK,MAAA;AAAM,UAAK8H,EAAA;AAAA;AAAA,MAAS;AAAA,MAE3B;AAAA,QACE,KAAK;AAAA,QACL,OAAO9H,EAAE,eAAe;AAAA,QACxB,MAAM;AAAA,QACN,SAAS;AAAA,QACT,KAAK,MAAA;AAAM,UAAK0F,EAAA;AAAA;AAAA,MAAI;AAAA,IACtB,IAhB2B,CAAA,CAkB9B;2BAIC7E,EA2FiBC,EAAAkH,EAAA,GAAA;AAAA,MA3FA,OAAO5H,EAAA;AAAA,MAAQ,SAAS2H,EAAA;AAAA,MAAU,MAAM;AAAA,IAAA;iBACvD,MAyFU;AAAA,QAzFV9G,EAyFUH,EAAAmH,EAAA,GAAA;AAAA,UAzFD,OAAM;AAAA,UAAW,SAAQ;AAAA,QAAA;qBAChC,MAuFiB;AAAA,YAvFjBhH,EAuFiBH,EAAAoH,EAAA,GAAA;AAAA,cAtFP,MAAMvI,EAAA;AAAA,sDAAAA,EAAI,QAAAqB;AAAA,cAClB,OAAM;AAAA,cACL,cAAY;AAAA,cACZ,cAAY;AAAA,cACZ,gBAAcF,EAAAd,CAAA,EAAC,YAAA;AAAA,cACf,gBAAagC,EAAA,CAAA,MAAAA,EAAA,CAAA,IAAA,CAAAhB,MAAEiG,EAAA,QAAejG;AAAA,YAAA;cAEpB,QACT,MAAkE;AAAA,gBAA/C+D,EAAA,cAAnBlE,EAAkEC,EAAA2F,EAAA,GAAA;AAAA;kBAAtC,OAAM;AAAA,kBAAqB,MAAM;AAAA,gBAAA,MAGhDO,EAAA,MAAM,WAAM,UADzBnG,EAIEC,EAAA4F,CAAA,GAAA;AAAA;kBAFC,OAAO5F,EAAAd,CAAA,EAAC,eAAA;AAAA,kBACR,aAAac,EAAAd,CAAA,EAAC,oBAAA;AAAA,gBAAA,0CAGjBoG,KAAA3D,EAqDK,MArDL4D,IAqDK;AAAA,0BApDH5D,EAmDKC,GAAA,MAAAyF,GAnDcnB,EAAA,OAAK,CAAbO,YAAX9E,EAmDK,MAAA;AAAA,oBAnDsB,KAAK8E,EAAK;AAAA,oBAAK,OAAM;AAAA,kBAAA;oBAC9ClG,EAiCS,UAAA;AAAA,sBAhCP,MAAK;AAAA,sBACL,UAAM,kBAAgB,EAAA,cACEkG,EAAK,QAAQH,EAAA,MAAA,CAAO,CAAA;AAAA,sBAC3C,SAAK,CAAApG,MAAEyG,EAAOF,CAAI;AAAA,oBAAA;sBAEnBlG,EAQO,QARPkF,IAQO;AAAA,wBAPLtF,EAA4DH,EAAAsB,CAAA,GAAA;AAAA,0BAAnD,UAAA;AAAA,0BAAS,QAAO;AAAA,wBAAA;qCAAS,MAAgB;AAAA,4BAAbC,EAAAC,EAAAiF,EAAK,KAAK,GAAA,CAAA;AAAA,0BAAA;;;wBAC/CtG,EAKEH,EAAAsH,EAAA,GAAA;AAAA,0BAJA,OAAM;AAAA,0BACN,MAAK;AAAA,0BACJ,OAAOb,EAAK;AAAA,0BACZ,OAAOzG,EAAAd,CAAA,EAAC,YAAA;AAAA,wBAAA;;sBAIbiB,EAAsEH,EAAAsB,CAAA,GAAA;AAAA,wBAA7D,MAAK;AAAA,wBAAK,MAAK;AAAA,wBAAQ,MAAA;AAAA,wBAAK,UAAA;AAAA,sBAAA;mCAAS,MAAc;AAAA,0BAAXC,EAAAC,EAAAiF,EAAK,GAAG,GAAA,CAAA;AAAA,wBAAA;;;sBAOzDlG,EASO,QATPmF,IASO;AAAA,wBARWe,EAAK,MAAM,UAGNA,EAAK,MAAM,aAAQ,aAAxC9E,EAGWC,GAAA,EAAA,KAAA,KAAA;AAAA,0BAFTzB,EAAqEH,EAAAsB,CAAA,GAAA;AAAA,4BAA5D,MAAK;AAAA,4BAAK,MAAK;AAAA,0BAAA;uCAAQ,MAA2B;AAAA,kCAAxBtB,EAAAd,CAAA,EAAC,kBAAA,CAAA,GAAA,CAAA;AAAA,4BAAA;;;0BACpCiB,EAAwCH,EAAAuH,EAAA,GAAA;AAAA,4BAA9B,OAAOd,EAAK,MAAM;AAAA,0BAAA;wCAE9B1G,EAA4EC,EAAAsB,CAAA,GAAA;AAAA;0BAA5D,MAAK;AAAA,0BAAK,MAAK;AAAA,wBAAA;qCAAQ,MAA2B;AAAA,gCAAxBtB,EAAAd,CAAA,EAAC,kBAAA,CAAA,GAAA,CAAA;AAAA,0BAAA;;oCAP3Ca,EAEUC,EAAAsB,CAAA,GAAA;AAAA;0BAF0B,MAAK;AAAA,0BAAK,MAAK;AAAA,wBAAA;qCACjD,MAAyB;AAAA,gCAAtBtB,EAAAd,CAAA,EAAC,gBAAA,CAAA,GAAA,CAAA;AAAA,0BAAA;;;;;oBAgBFgF,EAAA,SAAauC,EAAK,MAAM,gBADhC1G,EAOEC,EAAAwF,EAAA,GAAA;AAAA;sBALA,OAAM;AAAA,sBACN,MAAK;AAAA,sBACL,MAAK;AAAA,sBACJ,OAAOxF,EAAAd,CAAA,EAAC,YAAA;AAAA,sBACR,SAAK,CAAAgB,MAAE6G,EAAMN,CAAI;AAAA,oBAAA;oBAGDvC,EAAA,cAAnBnE,EAAgFC,EAAA+B,EAAA,GAAA;AAAA;sBAAjD,SAAShB,EAAW0F,CAAI;AAAA,sBAAI,OAAOA,EAAK;AAAA,oBAAA;;;;cAKlE,QAAMrF,EACf,CAOE,EARiB,QAAAoG,GAAQ,MAAAC,QAAI;AAAA,gBAEvBjB,EAAA,cADRzG,EAOE2H,IAAA;AAAA,kBALC,KAAKlB,EAAA,MAAO;AAAA,kBACZ,MAAMA,EAAA;AAAA,kBACN,QAAAgB;AAAA,kBACA,MAAAC;AAAA,kBACA,WAASnD;AAAA,gBAAA;;cAIH,SACT,MAAqF;AAAA,gBAArFnE,EAAqFH,EAAA4F,CAAA,GAAA;AAAA,kBAA1E,OAAO5F,EAAAd,CAAA,EAAC,kBAAA;AAAA,kBAAuB,aAAac,EAAAd,CAAA,EAAC,uBAAA;AAAA,gBAAA;;;;;;;;;;;;AC7S3D,SAASuH,GAAKkB,IAAuB,IAAiB;AAC3D,QAAMC,IAAOD,EAAQ,QAAQ;AAE7B,SAAO;AAAA,IACL,IAAI;AAAA,IACJ,MAAAC;AAAA,IACA,QAAQ,CAAC,EAAE,MAAAA,GAAM,MAAM,cAAc,WAAWC,IAAW;AAAA,EAAA;AAE/D;"}
1
+ {"version":3,"file":"index.js","sources":["../src/api.ts","../src/messages.ts","../src/i18n.ts","../src/MenuCreateDialog.vue","../src/MenuBranch.vue","../src/MenuItemDialog.vue","../src/tree.ts","../src/MenuTreePane.vue","../src/MenusPage.vue","../src/module.ts"],"sourcesContent":["import type { AdminContext } from '@webx-ui/module-admin'\nimport type { MenuInput, MenuItemInput, MenuItemRow, MenuRow } from './types'\n\nexport interface MenusApi {\n /** Every menu: the ones a template asks for and the ones somebody made. */\n list(): Promise<MenuRow[]>\n create(input: MenuInput): Promise<MenuRow>\n /** The name, and for a menu of somebody's own the key as well. */\n update(key: string, input: Partial<MenuInput>): Promise<MenuRow>\n /** Only a menu of somebody's own, and its items go with it. */\n remove(key: string): Promise<void>\n\n /** The whole tree, nested. Menus are small enough that a level at a time would only cost. */\n items(key: string): Promise<MenuItemRow[]>\n addItem(key: string, input: MenuItemInput): Promise<MenuItemRow>\n saveItem(key: string, id: number, input: MenuItemInput): Promise<MenuItemRow>\n /** Under this parent, at this position among its children. */\n moveItem(key: string, id: number, parentId: number | null, index: number): Promise<void>\n /** With the subtree under it. Answers how many went. */\n removeItem(key: string, id: number): Promise<number>\n\n /** Forget one menu's cache, in every language. */\n flush(key: string): Promise<void>\n /** The same for every menu at once. */\n flushAll(): Promise<void>\n}\n\n/** Everything under `/menus`, below the panel's API path. */\nexport function createMenusApi(admin: AdminContext): MenusApi {\n const base = `${admin.apiPath}/menus`\n const data = <T>(body: { data: T }): T => body.data\n\n return {\n list: () => admin.http.get<{ data: MenuRow[] }>(base).then(data),\n create: (input) => admin.http.post<{ data: MenuRow }>(base, input).then(data),\n update: (key, input) =>\n admin.http.patch<{ data: MenuRow }>(`${base}/${encodeURIComponent(key)}`, input).then(data),\n remove: (key) => admin.http.delete(`${base}/${encodeURIComponent(key)}`).then(() => undefined),\n\n items: (key) =>\n admin.http\n .get<{ data: MenuItemRow[] }>(`${base}/${encodeURIComponent(key)}/items`)\n .then(data),\n addItem: (key, input) =>\n admin.http\n .post<{ data: MenuItemRow }>(`${base}/${encodeURIComponent(key)}/items`, input)\n .then(data),\n saveItem: (key, id, input) =>\n admin.http\n .patch<{ data: MenuItemRow }>(`${base}/${encodeURIComponent(key)}/items/${id}`, input)\n .then(data),\n moveItem: (key, id, parentId, index) =>\n admin.http\n .post(`${base}/${encodeURIComponent(key)}/items/${id}/move`, {\n parent_id: parentId,\n index,\n })\n .then(() => undefined),\n removeItem: (key, id) =>\n admin.http\n .delete<{ data: { deleted: number } }>(`${base}/${encodeURIComponent(key)}/items/${id}`)\n .then((body) => body.data.deleted),\n\n flush: (key) =>\n admin.http.post(`${base}/${encodeURIComponent(key)}/cache/flush`, {}).then(() => undefined),\n flushAll: () => admin.http.post(`${base}/cache/flush`, {}).then(() => undefined),\n }\n}\n","import type { Messages } from '@webx-ui/module-admin'\n\n/**\n * The English this package speaks on its own, before the server's dictionary arrives, or without\n * one. The server ships the same lines in ten languages under `webx-menu::` and overrides these;\n * `messages.test.ts` keeps the two sets of keys equal.\n */\nexport const menuMessages: Record<string, Messages> = {\n module: {\n title: 'Menus',\n },\n menu: {\n menus: 'Menus',\n 'new-menu': 'New menu',\n 'new-menu-title': 'New menu',\n 'field-key': 'Key',\n 'key-help': \"What a template calls it by: menu('header').\",\n 'field-name': 'Name',\n create: 'Create',\n cancel: 'Cancel',\n save: 'Save',\n rename: 'Rename',\n 'rename-title': 'Rename menu',\n renamed: 'Menu saved',\n delete: 'Delete',\n 'delete-menu-title': 'Delete “:menu”?',\n 'delete-menu-text': 'The menu goes, and every item in it. Nothing it points at is touched.',\n deleted: 'Menu deleted',\n declared: 'A template of this site asks for it',\n 'no-menus': 'No menus yet.',\n 'no-menus-help': 'A menu is declared in the configuration, or made here.',\n 'choose-menu': 'Choose a menu',\n 'choose-menu-help': 'Its items are arranged on the right.',\n back: 'Back',\n\n 'new-item': 'New item',\n 'item-title': 'Menu item',\n 'empty-tree': 'Nothing in this menu yet.',\n 'empty-tree-help': 'An item points at a page, at an address of your own, or at nothing.',\n edit: 'Edit',\n duplicate: 'Duplicate',\n duplicated: 'Item duplicated',\n 'delete-item-title': 'Delete “:item”?',\n 'delete-item-text': 'Only this item goes.',\n 'delete-item-branch': 'Items that go with it: :count.',\n 'item-deleted': 'Item deleted',\n 'item-saved': 'Item saved',\n 'move-failed': 'The item could not be moved.',\n 'untitled-item': 'Untitled',\n\n heading: 'Heading',\n 'goes-nowhere': 'Goes nowhere',\n unavailable: 'Not on the site',\n hidden: 'Hidden',\n external: 'An address off this site',\n 'new-tab': 'Opens in a new tab',\n 'missing-target': 'What this pointed at is gone',\n\n 'field-label': 'Label',\n 'label-help': 'Left empty, the item is called what the thing it points at is called.',\n 'field-where': 'Where it goes',\n 'field-variant': 'Look',\n 'field-heading': 'A heading for the items under it',\n 'heading-help': 'Drawn as a caption. It may still be a link.',\n 'field-visible': 'Shown on the site',\n 'field-locales': 'Languages',\n 'locales-all': 'Every language',\n 'locales-help': 'None chosen means every language.',\n\n 'cache-built': 'Cache built',\n 'cache-empty': 'Cache not built',\n 'cache-off': 'Cache off',\n flush: 'Reset the cache of this menu',\n 'flush-all': 'Reset every menu',\n flushed: 'Cache reset',\n },\n}\n","import { useAdmin } from '@webx-ui/module-admin'\nimport { menuMessages } 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 useMenuMessages(): void {\n try {\n const { i18n } = useAdmin()\n\n if (!seeded.has(i18n)) {\n i18n.defaults('webx-menu', menuMessages)\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 { nextTick, onMounted, ref, useTemplateRef } from 'vue'\nimport { useModal, WxButton, WxDialog, WxFormItem, WxInput, WxSpace } from '@webx-ui/core'\nimport { useAdmin, useTranslate } from '@webx-ui/module-admin'\nimport { createMenusApi } from './api'\nimport { useMenuMessages } from './i18n'\nimport type { MenuRow } from './types'\n\n/**\n * A menu of somebody's own: a key and a name (§9).\n *\n * Two fields and no more. A menu declared in the configuration is not made here and cannot be —\n * it is a template asking for a name — so everything this dialog writes is a menu somebody will\n * read out of a block or a template they are about to write.\n *\n * The key does not follow the name the way a page's address follows its title: this one is typed\n * into `menu('…')` by hand, and a key that quietly became `main-menu-2` would be a template\n * asking for a menu that is not there.\n */\ndefineOptions({ name: 'WxMenuCreateDialog' })\n\nconst props = withDefaults(defineProps<{ menu?: MenuRow | null }>(), { menu: null })\n\nconst { open, resolve, dismiss } = useModal<MenuRow>()\n\nconst admin = useAdmin()\nconst api = createMenusApi(admin)\nuseMenuMessages()\n\nconst t = useTranslate('webx-menu')\n\nconst editing = props.menu !== null\n\nconst key = ref(props.menu?.key ?? '')\nconst title = ref(props.menu?.title ?? '')\nconst saving = ref(false)\nconst errors = ref<Record<string, string[]>>({})\nconst field = useTemplateRef<HTMLElement>('field')\n\nonMounted(async () => {\n await nextTick()\n field.value?.querySelector('input')?.focus()\n})\n\nasync function submit(): Promise<void> {\n if (title.value.trim() === '' || saving.value) return\n\n saving.value = true\n errors.value = {}\n\n try {\n const input = { key: key.value.trim(), title: title.value.trim() }\n\n resolve(\n editing && props.menu !== null\n ? await api.update(props.menu.key, props.menu.can.rename ? input : { title: input.title })\n : await api.create(input),\n )\n } catch (error) {\n // The key is the one thing that can be refused, and it is refused under its own field:\n // taken already, or spelled in a way no template could write.\n errors.value = (error as { body?: { errors?: Record<string, string[]> } }).body?.errors ?? {}\n } finally {\n saving.value = false\n }\n}\n</script>\n\n<template>\n <wx-dialog\n v-model:open=\"open\"\n :title=\"editing ? t('menu.rename-title') : t('menu.new-menu-title')\"\n :width=\"440\"\n >\n <wx-form-item :label=\"t('menu.field-name')\" :error=\"errors.title?.[0]\" required>\n <div ref=\"field\">\n <wx-input v-model=\"title\" :aria-label=\"t('menu.field-name')\" @keyup.enter=\"submit\" />\n </div>\n </wx-form-item>\n\n <!--\n Locked for a menu a template asks for: that spelling is the reference, and renaming it\n here would break a view nobody is looking at.\n -->\n <wx-form-item\n :label=\"t('menu.field-key')\"\n :error=\"errors.key?.[0]\"\n :help=\"t('menu.key-help')\"\n required\n >\n <wx-input\n v-model=\"key\"\n :disabled=\"editing && !props.menu?.can.rename\"\n :aria-label=\"t('menu.field-key')\"\n @keyup.enter=\"submit\"\n />\n </wx-form-item>\n\n <template #footer>\n <wx-space size=\"sm\">\n <wx-button variant=\"outline\" @click=\"dismiss()\">{{ t('menu.cancel') }}</wx-button>\n <wx-button\n type=\"primary\"\n :loading=\"saving\"\n :disabled=\"title.trim() === '' || key.trim() === ''\"\n @click=\"submit\"\n >\n {{ editing ? t('menu.save') : t('menu.create') }}\n </wx-button>\n </wx-space>\n </template>\n </wx-dialog>\n</template>\n","<script setup lang=\"ts\">\nimport { WxIcon, WxSortableList, WxText, WxTooltip } from '@webx-ui/core'\nimport { useTranslate, WxRowMenu, type RowAction } from '@webx-ui/module-admin'\nimport type { MenuItemRow } from './types'\n\n/**\n * One level of the tree, and every level under it.\n *\n * Nested sortable lists rather than one flat list with indentation: dragging has to change both\n * the order and the parent, and a list that draws nesting without holding it makes the second\n * half a guess about how far sideways somebody dropped a row. Each level is its own list, they\n * share a group, and where a row ends up is where it is.\n *\n * Which level changed is not something the list says — a drag between two of them changes two\n * models and neither is a move within one — so every level reports its own new order and the\n * screen above works out what moved. That keeps this component with nothing to remember.\n */\ndefineOptions({ name: 'WxMenuBranch' })\n\nconst props = withDefaults(\n defineProps<{\n items: MenuItemRow[]\n /** Null for the top level. It travels back out with the new order. */\n parentId?: number | null\n disabled?: boolean\n }>(),\n { parentId: null, disabled: false },\n)\n\nconst emit = defineEmits<{\n /** This level, in its new order. The screen above decides what that means. */\n reorder: [parentId: number | null, items: MenuItemRow[]]\n edit: [item: MenuItemRow]\n duplicate: [item: MenuItemRow]\n remove: [item: MenuItemRow]\n}>()\n\nconst t = useTranslate('webx-menu')\n\nfunction name(item: MenuItemRow): string {\n return item.label === '' ? t('menu.untitled-item') : item.label\n}\n\n/**\n * The line under the label: where the item goes, or what it is instead.\n *\n * An item that points at something that is gone says so rather than showing an empty line —\n * that is what a menu built before a module was removed looks like, and it is the one state\n * nobody can work out from the row.\n */\nfunction where(item: MenuItemRow): string {\n if (item.target === 'none') return t('menu.goes-nowhere')\n if (item.target === 'entity' && item.resolved === null) return t('menu.missing-target')\n\n return item.href ?? t('menu.goes-nowhere')\n}\n\n/** An address on somebody else's site: it has a scheme and it is not a path of ours. */\nfunction isExternal(item: MenuItemRow): boolean {\n return item.target === 'url' && /^[a-z][a-z0-9+.-]*:/i.test(item.url ?? '')\n}\n\nfunction actionsFor(item: MenuItemRow): RowAction[] {\n if (props.disabled) return []\n\n return [\n { key: 'edit', icon: 'edit', label: t('menu.edit'), run: () => emit('edit', item) },\n {\n key: 'duplicate',\n icon: 'copy',\n label: t('menu.duplicate'),\n run: () => emit('duplicate', item),\n },\n {\n key: 'delete',\n icon: 'trash',\n label: t('menu.delete'),\n danger: true,\n run: () => emit('remove', item),\n },\n ]\n}\n</script>\n\n<template>\n <wx-sortable-list\n class=\"wx-menu-branch\"\n :class=\"{ 'is-nested': props.parentId !== null }\"\n :model-value=\"props.items\"\n plain\n size=\"sm\"\n group=\"wx-menu-items\"\n item-key=\"id\"\n :item-label=\"name\"\n :disabled=\"props.disabled\"\n :empty-text=\"''\"\n @update:model-value=\"(next: MenuItemRow[]) => emit('reorder', props.parentId, next)\"\n >\n <!--\n Nothing, and on purpose: a leaf's empty list is what makes the leaf something a row can be\n dropped into. The words `Nothing here yet` under every item without children would be the\n loudest thing on the screen.\n -->\n <template #empty><span class=\"wx-menu-branch__drop\" /></template>\n\n <template #default=\"{ item }\">\n <div class=\"wx-menu-item\" :class=\"{ 'is-off': !item.visible || !item.available }\">\n <button\n type=\"button\"\n class=\"wx-menu-item__open\"\n :disabled=\"props.disabled\"\n @click=\"emit('edit', item)\"\n >\n <span class=\"wx-menu-item__line\">\n <wx-text truncate :weight=\"item.is_heading ? 'semibold' : 'medium'\">\n {{ name(item) }}\n </wx-text>\n\n <!--\n Two facts and two marks, because they are independent: an internal PDF is opened\n in a new tab, and a partner's link sometimes is not.\n -->\n <wx-tooltip v-if=\"isExternal(item)\" :content=\"t('menu.external')\">\n <wx-icon class=\"wx-menu-item__mark\" name=\"link\" :label=\"t('menu.external')\" />\n </wx-tooltip>\n\n <wx-tooltip v-if=\"item.new_tab\" :content=\"t('menu.new-tab')\">\n <wx-icon class=\"wx-menu-item__mark\" name=\"external-link\" :label=\"t('menu.new-tab')\" />\n </wx-tooltip>\n </span>\n\n <wx-text size=\"sm\" tone=\"muted\" truncate>\n <template v-if=\"item.is_heading\">{{ t('menu.heading') }}</template>\n <template v-else>{{ where(item) }}</template>\n </wx-text>\n </button>\n\n <!--\n The two states that dim a row are said in words as well as in colour: \"hidden\" is a\n decision somebody made and \"not on the site\" is one they did not, and grey alone\n cannot tell the two apart.\n -->\n <wx-text v-if=\"!item.visible\" class=\"wx-menu-item__state\" size=\"sm\" tone=\"muted\">\n {{ t('menu.hidden') }}\n </wx-text>\n <wx-text v-else-if=\"!item.available\" class=\"wx-menu-item__state\" size=\"sm\" tone=\"warning\">\n {{ t('menu.unavailable') }}\n </wx-text>\n </div>\n\n <!--\n The level under this item, always drawn: an empty one is the target that makes nesting\n something a drag can do, rather than something only a form could say.\n -->\n <wx-menu-branch\n :items=\"item.children\"\n :parent-id=\"item.id\"\n :disabled=\"props.disabled\"\n @reorder=\"(parent: number | null, next: MenuItemRow[]) => emit('reorder', parent, next)\"\n @edit=\"emit('edit', $event)\"\n @duplicate=\"emit('duplicate', $event)\"\n @remove=\"emit('remove', $event)\"\n />\n </template>\n\n <!--\n Left out rather than greyed for a reader who may only look: a menu is a list of what is\n possible, and a row of dead entries teaches nothing.\n -->\n <template v-if=\"!props.disabled\" #actions=\"{ item }\">\n <wx-row-menu :actions=\"actionsFor(item)\" :label=\"name(item)\" />\n </template>\n </wx-sortable-list>\n</template>\n\n<style scoped>\n.wx-menu-branch.is-nested {\n /* The step that says \"under\": the grip of a child stands clear of its parent's label. */\n padding-inline-start: var(--wx-space-16);\n}\n\n/*\n * A row holds its own children, so the grip and the `···` line up with the label rather than\n * with the middle of the whole branch. The list's rows are its elements and not ours, so this\n * reaches them with `:deep()` — a scope attribute would be on neither.\n */\n.wx-menu-branch :deep(.wx-sortable-list__row) {\n align-items: flex-start;\n}\n\n.wx-menu-branch :deep(.wx-sortable-list__grip) {\n /* The height of one line of the label, so the grip sits on it and not above it. */\n height: 28px;\n}\n\n/* An empty level is a target and not a message: as little of it as can still be hit. */\n.wx-menu-branch :deep(.wx-sortable-list__empty) {\n padding: 0;\n}\n\n.wx-menu-branch__drop {\n display: block;\n height: var(--wx-space-8);\n}\n\n.wx-menu-item {\n display: flex;\n align-items: flex-start;\n gap: var(--wx-space-8);\n min-width: 0;\n padding-block: var(--wx-space-2);\n}\n\n.wx-menu-item__open {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: var(--wx-space-2);\n /* The label is the target: a four-letter item should not leave the rest of the line dead. */\n flex: 1 1 auto;\n min-width: 0;\n padding: 0;\n border: 0;\n background: none;\n font: inherit;\n color: inherit;\n text-align: start;\n cursor: pointer;\n}\n\n/*\n * `flex-start` sizes each line to its content, and a line sized to its content has nothing to\n * cut: a long address ran out past the card instead of ending in `…`. The cap is what gives\n * `truncate` a width to work against.\n */\n.wx-menu-item__open > :deep(*) {\n max-width: 100%;\n}\n\n.wx-menu-item__open:disabled {\n cursor: default;\n}\n\n.wx-menu-item__line {\n display: flex;\n align-items: center;\n gap: var(--wx-space-6);\n min-width: 0;\n max-width: 100%;\n}\n\n.wx-menu-item__mark {\n flex: 0 0 auto;\n color: var(--wx-text-placeholder);\n}\n\n.wx-menu-item__state {\n flex: 0 0 auto;\n}\n\n/*\n * Switched off, or pointing at something the site would not show. Dimmed on the label and the\n * address rather than on the row, so the word that says which of the two it is keeps its own\n * colour — that word is the only thing on the row that explains the grey.\n */\n.wx-menu-item.is-off .wx-menu-item__open {\n opacity: 0.55;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\nimport {\n useLocales,\n useModal,\n WxButton,\n WxCheckbox,\n WxDialog,\n WxFormItem,\n WxInput,\n WxSelect,\n WxSpace,\n WxSwitch,\n type SelectOption,\n} from '@webx-ui/core'\nimport { emptyLink, useTranslate, WxLinkPicker, type LinkValue } from '@webx-ui/module-admin'\nimport { useMenuMessages } from './i18n'\nimport type { LocalizedText, MenuItemInput, MenuItemRow, MenuRow } from './types'\n\n/**\n * One menu item, in a dialog raised from code (§9).\n *\n * A dialog and not a tab or a screen of its own, because an item is small and is always edited\n * while looking at the tree it sits in: what somebody is deciding is where this one goes\n * relative to the others, and a screen that replaced the tree would take that away.\n *\n * Where it points is `WxLinkPicker` and nothing of this package's own — a menu item and a link\n * field of a block are the same choice, and the day they stop looking alike is the day one of\n * them grows a second way of saying `nofollow`.\n */\ndefineOptions({ name: 'WxMenuItemDialog' })\n\nconst props = withDefaults(\n defineProps<{\n menu: MenuRow\n /** The item being edited, or null for a new one. */\n item?: MenuItemRow | null\n }>(),\n { item: null },\n)\n\nconst { open, resolve, dismiss } = useModal<MenuItemInput>()\n\nconst locales = useLocales()\nuseMenuMessages()\n\nconst t = useTranslate('webx-menu')\n\nconst link = ref<LinkValue>(props.item === null ? emptyLink() : toLink(props.item))\nconst title = ref<LocalizedText>({ ...(props.item?.title ?? {}) })\nconst variant = ref(props.item?.variant ?? props.menu.variants[0] ?? 'link')\nconst isHeading = ref(props.item?.is_heading ?? false)\nconst visible = ref(props.item?.visible ?? true)\nconst chosenLocales = ref<string[]>([...(props.item?.locales ?? [])])\n\n/**\n * What the item would be called if nobody wrote a label: the name of the thing it points at.\n *\n * Shown as the placeholder rather than filled in, because filling it in would make it a label —\n * and then renaming the page would stop renaming the item, which is the whole property the\n * empty label has (§4).\n */\nconst fallback = computed(() => props.item?.resolved?.title ?? '')\n\n/** Only where the site has more than one look to choose between. */\nconst variantOptions = computed<SelectOption[]>(() =>\n props.menu.variants.map((name) => ({ value: name, label: name })),\n)\n\nconst localeOptions = computed<SelectOption[]>(() =>\n locales.list.value.map((locale) => ({ value: locale.code, label: locale.label ?? locale.code })),\n)\n\nfunction toLink(item: MenuItemRow): LinkValue {\n return {\n target: item.target,\n entity_type: item.entity_type,\n entity_id: item.entity_id,\n url: item.url,\n hash: item.hash,\n new_tab: item.new_tab,\n rel: item.rel,\n }\n}\n\nfunction submit(): void {\n resolve({\n title: title.value,\n link: link.value,\n variant: variant.value,\n is_heading: isHeading.value,\n locales: chosenLocales.value,\n visible: visible.value,\n })\n}\n</script>\n\n<template>\n <wx-dialog v-model:open=\"open\" :title=\"t('menu.item-title')\" :width=\"520\">\n <wx-form-item :label=\"t('menu.field-where')\">\n <wx-link-picker v-model=\"link\" />\n </wx-form-item>\n\n <wx-form-item :label=\"t('menu.field-label')\" :help=\"t('menu.label-help')\">\n <wx-input\n v-model=\"title\"\n localized\n :placeholder=\"fallback\"\n :aria-label=\"t('menu.field-label')\"\n />\n </wx-form-item>\n\n <wx-form-item v-if=\"variantOptions.length > 1\" :label=\"t('menu.field-variant')\">\n <wx-select\n v-model=\"variant\"\n :options=\"variantOptions\"\n :aria-label=\"t('menu.field-variant')\"\n />\n </wx-form-item>\n\n <!--\n Empty means every language, so this is a `multiple` select with no \"all\" in it: a row\n called \"All languages\" is a row somebody picks alongside Ukrainian.\n -->\n <wx-form-item\n v-if=\"localeOptions.length > 1\"\n :label=\"t('menu.field-locales')\"\n :help=\"t('menu.locales-help')\"\n >\n <wx-select\n v-model=\"chosenLocales\"\n :options=\"localeOptions\"\n multiple\n clearable\n :placeholder=\"t('menu.locales-all')\"\n :aria-label=\"t('menu.field-locales')\"\n />\n </wx-form-item>\n\n <wx-form-item :help=\"t('menu.heading-help')\">\n <wx-checkbox v-model=\"isHeading\">{{ t('menu.field-heading') }}</wx-checkbox>\n </wx-form-item>\n\n <wx-form-item>\n <wx-switch v-model=\"visible\" :label=\"t('menu.field-visible')\" />\n </wx-form-item>\n\n <template #footer>\n <wx-space size=\"sm\">\n <wx-button variant=\"outline\" @click=\"dismiss()\">{{ t('menu.cancel') }}</wx-button>\n <wx-button type=\"primary\" @click=\"submit\">{{ t('menu.save') }}</wx-button>\n </wx-space>\n </template>\n </wx-dialog>\n</template>\n","import type { MenuItemRow } from './types'\n\n/** The item with this id, anywhere in the tree. */\nexport function findItem(items: MenuItemRow[], id: number): MenuItemRow | null {\n for (const item of items) {\n if (item.id === id) return item\n\n const found = findItem(item.children, id)\n\n if (found !== null) return found\n }\n\n return null\n}\n\n/** How many items are under this one — what a delete takes with it. */\nexport function countBranch(item: MenuItemRow): number {\n return item.children.reduce((total, child) => total + 1 + countBranch(child), 0)\n}\n\n/**\n * Which item moved, given one level before and after a drag.\n *\n * A drag between two levels changes two lists and the library says nothing about either beyond\n * their new contents, so what moved has to be read out of the difference. Three cases, and one\n * rule covers them: an id that is here and was not is the one that arrived; a level that lost\n * one says nothing, because the level that gained it will; and a level whose contents are the\n * same set in another order gives up the one id that, taken out of both, leaves them equal.\n *\n * Null means \"nothing for the server to hear about\" — a removal, or a drag that ended where it\n * started.\n */\nexport function movedId(before: number[], after: number[]): number | null {\n const arrived = after.find((id) => !before.includes(id))\n\n if (arrived !== undefined) return arrived\n if (after.length !== before.length) return null\n\n for (const id of after) {\n if (after.indexOf(id) === before.indexOf(id)) continue\n\n const rest = after.filter((other) => other !== id)\n const was = before.filter((other) => other !== id)\n\n if (rest.length === was.length && rest.every((other, index) => other === was[index])) {\n return id\n }\n }\n\n return null\n}\n","<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\nimport {\n confirm,\n createModal,\n toast,\n WxAction,\n WxButton,\n WxEmpty,\n WxIcon,\n WxSkeleton,\n WxText,\n} from '@webx-ui/core'\nimport { useAdmin, useErrorText, useTranslate } from '@webx-ui/module-admin'\nimport MenuBranch from './MenuBranch.vue'\nimport MenuItemDialog from './MenuItemDialog.vue'\nimport { createMenusApi } from './api'\nimport { useMenuMessages } from './i18n'\nimport { countBranch, findItem, movedId } from './tree'\nimport type { MenuItemInput, MenuItemRow, MenuRow } from './types'\n\n/**\n * The items of one menu: the pane beside the list, and the whole screen on a phone.\n *\n * The way back out is drawn here and not by the pane around it, because the drawer a narrow\n * screen puts this in has no close of its own on purpose (§9): what closes it is `Back` in the\n * head of what is inside, the way every other screen of the panel goes back.\n *\n * Mounted afresh for each menu — the tree, the drag state and the scroll all belong to the menu\n * being looked at, and keeping them across a switch is how a drop lands in the wrong one.\n */\ndefineOptions({ name: 'WxMenuTreePane' })\n\nconst props = withDefaults(\n defineProps<{\n menu: MenuRow\n /** False in the drawer a narrow screen opens this in. */\n inline?: boolean\n back?: () => void\n }>(),\n { inline: true, back: undefined },\n)\n\nconst emit = defineEmits<{\n /** Something about the menu changed: its item count, and the age of its cache. */\n changed: []\n}>()\n\nconst admin = useAdmin()\nconst api = createMenusApi(admin)\nuseMenuMessages()\n\nconst t = useTranslate('webx-menu')\nconst message = useErrorText()\n\nconst items = ref<MenuItemRow[]>([])\nconst loading = ref(true)\n\nconst canManage = computed(() => admin.can('menu.manage'))\n\nconst edit = createModal<MenuItemInput, { menu: MenuRow; item: MenuItemRow | null }>(MenuItemDialog)\n\nasync function load(): Promise<void> {\n loading.value = true\n\n try {\n items.value = await api.items(props.menu.key)\n } catch (error) {\n toast.danger(message(error))\n } finally {\n loading.value = false\n }\n}\n\nvoid load()\n\n/** The level under a parent, as it stands right now. */\nfunction levelOf(parentId: number | null): MenuItemRow[] {\n if (parentId === null) return items.value\n\n return findItem(items.value, parentId)?.children ?? []\n}\n\nfunction setLevel(parentId: number | null, next: MenuItemRow[]): void {\n if (parentId === null) {\n items.value = next\n\n return\n }\n\n const parent = findItem(items.value, parentId)\n\n if (parent !== null) parent.children = next\n}\n\n/**\n * A level came back in a new order. One drag reports twice when it crosses levels — the one it\n * left and the one it landed in — and only the second of those is a move to send.\n *\n * The screen is already showing the new order: the list reorders its model before it says\n * anything. So a refusal has to put the tree back rather than leave the screen disagreeing with\n * the database.\n */\nasync function reorder(parentId: number | null, next: MenuItemRow[]): Promise<void> {\n const before = levelOf(parentId).map((item) => item.id)\n\n setLevel(parentId, next)\n\n const after = next.map((item) => item.id)\n const moved = movedId(before, after)\n\n if (moved === null) return\n\n try {\n await api.moveItem(props.menu.key, moved, parentId, after.indexOf(moved))\n emit('changed')\n } catch (error) {\n toast.danger(message(error, t('menu.move-failed')))\n await load()\n }\n}\n\nasync function add(): Promise<void> {\n const input = await edit({ menu: props.menu, item: null })\n\n if (!input) return\n\n try {\n await api.addItem(props.menu.key, input)\n toast.success(t('menu.item-saved'))\n await load()\n emit('changed')\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\nasync function save(item: MenuItemRow): Promise<void> {\n const input = await edit({ menu: props.menu, item })\n\n if (!input) return\n\n try {\n await api.saveItem(props.menu.key, item.id, input)\n toast.success(t('menu.item-saved'))\n await load()\n emit('changed')\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\n/**\n * A copy of an item, put directly under the one it was copied from.\n *\n * At the end of the level first and then moved, because where a new item goes is the tree's\n * business and there is one way of saying it. A copy that landed at the bottom of a menu of\n * twenty would be a copy somebody has to go and find.\n */\nasync function duplicate(item: MenuItemRow): Promise<void> {\n try {\n const copy = await api.addItem(props.menu.key, {\n title: item.title,\n link: {\n target: item.target,\n entity_type: item.entity_type,\n entity_id: item.entity_id,\n url: item.url,\n hash: item.hash,\n new_tab: item.new_tab,\n rel: item.rel,\n },\n variant: item.variant,\n is_heading: item.is_heading,\n locales: item.locales,\n visible: item.visible,\n parent_id: item.parent_id,\n })\n\n const siblings = levelOf(item.parent_id)\n await api.moveItem(\n props.menu.key,\n copy.id,\n item.parent_id,\n siblings.findIndex((one) => one.id === item.id) + 1,\n )\n\n toast.success(t('menu.duplicated'))\n await load()\n emit('changed')\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\n/**\n * An item, and whatever is under it.\n *\n * The number is counted off the tree that is on the screen, which is the same tree the delete\n * walks — the promise and the act have to be the same question, and bounds read a while ago are\n * bounds that have since moved (§10).\n */\nasync function remove(item: MenuItemRow): Promise<void> {\n const under = countBranch(item)\n\n const agreed = await confirm({\n title: t('menu.delete-item-title', { item: item.label || t('menu.untitled-item') }),\n message:\n under === 0 ? t('menu.delete-item-text') : t('menu.delete-item-branch', { count: under }),\n confirmText: t('menu.delete'),\n cancelText: t('menu.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n try {\n await api.removeItem(props.menu.key, item.id)\n toast.success(t('menu.item-deleted'))\n await load()\n emit('changed')\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\ndefineExpose({ reload: load })\n</script>\n\n<template>\n <div class=\"wx-menu-pane\">\n <header class=\"wx-menu-pane__head\">\n <!--\n The drawer this opens in on a phone has no cross: closing it is `Back`, in the head of\n what is inside it, where every other screen of the panel keeps it.\n -->\n <wx-action\n v-if=\"!props.inline\"\n icon=\"arrow-left\"\n size=\"sm\"\n :title=\"t('menu.back')\"\n @click=\"props.back?.()\"\n />\n\n <div class=\"wx-menu-pane__name\">\n <wx-text weight=\"semibold\" truncate>{{ props.menu.title }}</wx-text>\n <wx-text size=\"sm\" tone=\"muted\" mono truncate>{{ props.menu.key }}</wx-text>\n </div>\n\n <wx-button v-if=\"canManage\" variant=\"outline\" size=\"sm\" @click=\"add\">\n <template #icon><wx-icon name=\"plus\" /></template>\n {{ t('menu.new-item') }}\n </wx-button>\n </header>\n\n <div class=\"wx-menu-pane__body\">\n <wx-skeleton v-if=\"loading\" :rows=\"5\" />\n\n <wx-empty\n v-else-if=\"items.length === 0\"\n :title=\"t('menu.empty-tree')\"\n :description=\"t('menu.empty-tree-help')\"\n />\n\n <menu-branch\n v-else\n :items=\"items\"\n :disabled=\"!canManage\"\n @reorder=\"reorder\"\n @edit=\"save\"\n @duplicate=\"duplicate\"\n @remove=\"remove\"\n />\n </div>\n </div>\n</template>\n\n<style scoped>\n.wx-menu-pane {\n display: flex;\n flex-direction: column;\n min-height: 0;\n}\n\n.wx-menu-pane__head {\n display: flex;\n align-items: center;\n gap: var(--wx-space-12);\n padding: var(--wx-space-12) var(--wx-space-16);\n border-bottom: 1px solid var(--wx-border-muted);\n}\n\n.wx-menu-pane__name {\n display: flex;\n flex-direction: column;\n /* A long name shortens; it does not push the button that makes items off the line. */\n flex: 1 1 auto;\n min-width: 0;\n}\n\n.wx-menu-pane__body {\n padding: var(--wx-space-12) var(--wx-space-16);\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\nimport { useRoute, useRouter } from 'vue-router'\nimport {\n confirm,\n createModal,\n toast,\n WxAction,\n WxCard,\n WxEmpty,\n WxIndicator,\n WxListDetail,\n WxSkeleton,\n WxText,\n} from '@webx-ui/core'\nimport {\n useAdmin,\n useErrorText,\n useTranslate,\n WxDate,\n WxListScreen,\n WxRowMenu,\n type RowAction,\n type ScreenAction,\n} from '@webx-ui/module-admin'\nimport MenuCreateDialog from './MenuCreateDialog.vue'\nimport MenuTreePane from './MenuTreePane.vue'\nimport { createMenusApi } from './api'\nimport { useMenuMessages } from './i18n'\nimport type { MenuRow } from './types'\n\n/**\n * The section: the menus of the site on the left, the items of one of them on the right (§9).\n *\n * The menus are the list and the tree is the record beside it, rather than the other way round.\n * Somebody who opens this section is arranging a menu, not choosing between four of them — so\n * the first one opens by itself where there is room for it, and on a phone it does not, because\n * there the same line would raise a panel over a list nobody has touched.\n */\nconst admin = useAdmin()\nconst api = createMenusApi(admin)\nconst route = useRoute()\nconst router = useRouter()\nuseMenuMessages()\n\nconst t = useTranslate('webx-menu')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\nconst menus = ref<MenuRow[]>([])\nconst loading = ref(true)\nconst open = ref(false)\nconst detailInline = ref(true)\n\nconst canManage = computed(() => admin.can('menu.manage'))\n\nconst edit = createModal<MenuRow, { menu?: MenuRow | null }>(MenuCreateDialog)\n\nconst title = computed(\n () =>\n admin.state.manifest?.modules.find((module) => module.id === 'menu')?.title ??\n t('module.title'),\n)\n\n/**\n * Which menu is open, kept in the address — so that coming back from anywhere lands on the\n * menu somebody was arranging, and so that \"the footer\" is a link they can send.\n */\nconst current = computed<string | null>(() => {\n const asked = route.query.menu\n\n return typeof asked === 'string' && asked !== '' ? asked : null\n})\n\nconst chosen = computed(() => menus.value.find((menu) => menu.key === current.value) ?? null)\n\nasync function load(): Promise<void> {\n loading.value = true\n\n try {\n menus.value = await api.list()\n } catch (error) {\n toast.danger(message(error))\n } finally {\n loading.value = false\n }\n}\n\nvoid load()\n\n/*\n * The first menu, where there is a column to draw it in. A screen whose whole right-hand half\n * says \"choose one\" has spent its width on an instruction; a phone, where the same thing means\n * opening a panel over the list, gets the list.\n */\nwatch([menus, chosen, detailInline], () => {\n if (!detailInline.value || menus.value.length === 0 || chosen.value !== null) return\n\n void router.replace({ query: { ...route.query, menu: menus.value[0].key } })\n})\n\nwatch(chosen, (menu) => {\n if (menu !== null && detailInline.value) open.value = true\n})\n\nfunction choose(menu: MenuRow): void {\n void router.replace({ query: { ...route.query, menu: menu.key } })\n open.value = true\n}\n\nasync function add(): Promise<void> {\n const made = await edit({ menu: null })\n\n if (!made) return\n\n await load()\n choose(made)\n}\n\nasync function rename(menu: MenuRow): Promise<void> {\n const saved = await edit({ menu })\n\n if (!saved) return\n\n toast.success(t('menu.renamed'))\n\n // The key may have changed with it, and the address names the old one.\n if (current.value === menu.key && saved.key !== menu.key) {\n void router.replace({ query: { ...route.query, menu: saved.key } })\n }\n\n await load()\n}\n\nasync function remove(menu: MenuRow): Promise<void> {\n const agreed = await confirm({\n title: t('menu.delete-menu-title', { menu: menu.title }),\n message: t('menu.delete-menu-text'),\n confirmText: t('menu.delete'),\n cancelText: t('menu.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n try {\n await api.remove(menu.key)\n toast.success(t('menu.deleted'))\n\n if (current.value === menu.key) {\n const rest = { ...route.query }\n delete rest.menu\n void router.replace({ query: rest })\n open.value = false\n }\n\n await load()\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\n/**\n * Forget what this menu was built into, in every language.\n *\n * The list is loaded again afterwards rather than trusted: the mark under the name is the whole\n * of what this button says out loud, and a button that leaves it reading \"built today at 08:10\"\n * is a button nobody believes the second time.\n */\nasync function flush(menu: MenuRow): Promise<void> {\n try {\n await api.flush(menu.key)\n toast.success(t('menu.flushed'))\n await load()\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\nasync function flushAll(): Promise<void> {\n try {\n await api.flushAll()\n toast.success(t('menu.flushed'))\n await load()\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\nfunction actionsFor(menu: MenuRow): RowAction[] {\n if (!canManage.value) return []\n\n return [\n { key: 'rename', icon: 'edit', label: t('menu.rename'), run: () => void rename(menu) },\n { key: 'flush', icon: 'refresh', label: t('menu.flush'), run: () => void flush(menu) },\n ...(menu.can.delete\n ? [\n {\n key: 'delete',\n icon: 'trash' as const,\n label: t('menu.delete'),\n danger: true,\n run: () => void remove(menu),\n },\n ]\n : []),\n ]\n}\n\nconst actions = computed<ScreenAction[]>(() => {\n if (!canManage.value) return []\n\n return [\n {\n key: 'flush-all',\n label: t('menu.flush-all'),\n icon: 'refresh',\n menu: true,\n run: () => void flushAll(),\n },\n {\n key: 'new-menu',\n label: t('menu.new-menu'),\n icon: 'plus',\n primary: true,\n run: () => void add(),\n },\n ]\n})\n</script>\n\n<template>\n <wx-list-screen :title=\"title\" :actions=\"actions\" :card=\"false\">\n <wx-card class=\"wx-menus\" padding=\"none\">\n <wx-list-detail\n v-model:open=\"open\"\n class=\"wx-menus__panes\"\n :list-width=\"320\"\n :detail-min=\"420\"\n :detail-label=\"t('menu.menus')\"\n @detail-inline=\"detailInline = $event\"\n >\n <template #list>\n <wx-skeleton v-if=\"loading\" class=\"wx-menus__loading\" :rows=\"4\" />\n\n <wx-empty\n v-else-if=\"menus.length === 0\"\n :title=\"t('menu.no-menus')\"\n :description=\"t('menu.no-menus-help')\"\n />\n\n <ul v-else class=\"wx-menus__list\">\n <li v-for=\"menu in menus\" :key=\"menu.key\" class=\"wx-menus__row\">\n <button\n type=\"button\"\n class=\"wx-menus__open\"\n :class=\"{ 'is-current': menu.key === current }\"\n @click=\"choose(menu)\"\n >\n <span class=\"wx-menus__name\">\n <wx-text truncate weight=\"medium\">{{ menu.title }}</wx-text>\n <wx-indicator\n class=\"wx-menus__count\"\n type=\"neutral\"\n :value=\"menu.items_count\"\n :label=\"t('menu.menus')\"\n />\n </span>\n\n <wx-text size=\"sm\" tone=\"muted\" mono truncate>{{ menu.key }}</wx-text>\n\n <!--\n When the cache was built, in the panel's own words — and it is a sentence\n rather than a date, because \"built\" is the part that answers the question\n somebody pressed the button next to it to ask.\n -->\n <span class=\"wx-menus__cache\">\n <wx-text v-if=\"!menu.cache.enabled\" size=\"sm\" tone=\"muted\">\n {{ t('menu.cache-off') }}\n </wx-text>\n <template v-else-if=\"menu.cache.built_at !== null\">\n <wx-text size=\"sm\" tone=\"muted\">{{ t('menu.cache-built') }}</wx-text>\n <wx-date :value=\"menu.cache.built_at\" />\n </template>\n <wx-text v-else size=\"sm\" tone=\"muted\">{{ t('menu.cache-empty') }}</wx-text>\n </span>\n </button>\n\n <!--\n The reset stands in the row and not only in the `···`: it is what somebody\n presses to test the guess that they are looking at something stale, and a guess\n behind a menu is one they check by reloading the site instead (§9).\n -->\n <wx-action\n v-if=\"canManage && menu.cache.enabled\"\n class=\"wx-menus__flush\"\n icon=\"refresh\"\n size=\"sm\"\n :title=\"t('menu.flush')\"\n @click=\"flush(menu)\"\n />\n\n <wx-row-menu v-if=\"canManage\" :actions=\"actionsFor(menu)\" :label=\"menu.title\" />\n </li>\n </ul>\n </template>\n\n <template #detail=\"{ inline, back }\">\n <menu-tree-pane\n v-if=\"chosen\"\n :key=\"chosen.key\"\n :menu=\"chosen\"\n :inline=\"inline\"\n :back=\"back\"\n @changed=\"load\"\n />\n </template>\n\n <template #empty>\n <wx-empty :title=\"t('menu.choose-menu')\" :description=\"t('menu.choose-menu-help')\" />\n </template>\n </wx-list-detail>\n </wx-card>\n </wx-list-screen>\n</template>\n\n<style>\n/*\n * What scrolls here is the page, the way it does on every other list of the panel.\n *\n * The card's corners are the screen's corners, and the two panes inside are square and paint\n * their own background to the edge — without the clip they cover the rounding. `clip` and not\n * `hidden`: `hidden` would make this a scroll container, and everything sticky inside would pin\n * itself to a box that never moves (CLAUDE.md §4).\n */\n.wx-menus > .wx-card__body {\n display: flex;\n flex-direction: column;\n overflow: clip;\n border-radius: inherit;\n}\n</style>\n\n<style scoped>\n.wx-menus__loading {\n padding: var(--wx-space-16);\n}\n\n.wx-menus__list {\n margin: 0;\n padding: var(--wx-space-8);\n list-style: none;\n}\n\n.wx-menus__row {\n display: flex;\n align-items: center;\n gap: var(--wx-space-4);\n margin: 0;\n padding-inline: var(--wx-space-8);\n border-radius: var(--wx-radius-sm);\n}\n\n.wx-menus__row:has(.is-current) {\n background: var(--wx-bg-subtle);\n}\n\n.wx-menus__open {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: var(--wx-space-2);\n /* The row is the target: a menu called `header` should not leave the line dead. */\n flex: 1 1 auto;\n min-width: 0;\n padding-block: var(--wx-space-8);\n padding-inline: 0;\n border: 0;\n background: none;\n font: inherit;\n color: inherit;\n text-align: start;\n cursor: pointer;\n}\n\n.wx-menus__open.is-current {\n color: var(--wx-color-primary);\n}\n\n.wx-menus__name {\n display: flex;\n align-items: center;\n gap: var(--wx-space-6);\n min-width: 0;\n max-width: 100%;\n}\n\n.wx-menus__count {\n flex: 0 0 auto;\n}\n\n.wx-menus__cache {\n display: flex;\n align-items: center;\n gap: var(--wx-space-4);\n min-width: 0;\n}\n\n/*\n * `:deep()`, because `WxAction` carries its tooltip's portal beside the button and so has a\n * fragment for a root — Vue puts the scope attribute only on a child with a single root, and\n * without this the rule matches nothing at all, silently (CLAUDE.md §4).\n */\n.wx-menus__row > :deep(.wx-menus__flush) {\n flex: 0 0 auto;\n}\n</style>\n","import type { AdminModule } from '@webx-ui/module-admin'\nimport MenusPage from './MenusPage.vue'\n\nexport interface MenuOptions {\n /** Where the section lives inside the panel. */\n path?: string\n}\n\n/**\n * The menus of the site as a section of the panel.\n *\n * One route and no editor under it: a menu is arranged in place, and an item is a dialog over\n * the tree it belongs to. Which menu is open travels in the address instead, so a link to the\n * footer is a link somebody can send.\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 menu(options: MenuOptions = {}): AdminModule {\n const path = options.path ?? '/menus'\n\n return {\n id: 'menu',\n path,\n routes: [{ path, name: 'webx.menus', component: MenusPage }],\n }\n}\n"],"names":["createMenusApi","admin","base","data","body","input","key","id","parentId","index","menuMessages","seeded","useMenuMessages","i18n","useAdmin","props","__props","open","resolve","dismiss","useModal","api","t","useTranslate","editing","ref","title","saving","errors","field","useTemplateRef","onMounted","nextTick","submit","error","_createBlock","_unref","WxDialog","$event","_createVNode","WxSpace","WxButton","WxFormItem","_createElementVNode","WxInput","emit","__emit","name","item","where","isExternal","actionsFor","WxSortableList","_normalizeClass","_cache","next","_withCtx","_hoisted_2","WxText","_createTextVNode","_toDisplayString","WxTooltip","WxIcon","_createElementBlock","_Fragment","_component_wx_menu_branch","parent","WxRowMenu","locales","useLocales","link","emptyLink","toLink","variant","isHeading","visible","chosenLocales","fallback","computed","variantOptions","localeOptions","locale","WxLinkPicker","WxSelect","WxCheckbox","WxSwitch","findItem","items","found","countBranch","total","child","movedId","before","after","arrived","rest","other","was","message","useErrorText","loading","canManage","edit","createModal","MenuItemDialog","load","toast","levelOf","setLevel","reorder","moved","add","save","duplicate","copy","siblings","one","remove","under","confirm","__expose","_openBlock","_hoisted_1","WxAction","_hoisted_3","_hoisted_4","WxSkeleton","WxEmpty","MenuBranch","route","useRoute","router","useRouter","menus","detailInline","MenuCreateDialog","module","current","asked","chosen","menu","watch","choose","made","rename","saved","flush","flushAll","actions","WxListScreen","WxCard","WxListDetail","_renderList","WxIndicator","WxDate","inline","back","MenuTreePane","options","path","MenusPage"],"mappings":"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"}
package/dist/style.css CHANGED
@@ -1 +1 @@
1
- .wx-menu-branch.is-nested[data-v-4551b4d6]{padding-inline-start:var(--wx-space-16)}.wx-menu-branch[data-v-4551b4d6] .wx-sortable-list__row{align-items:flex-start}.wx-menu-branch[data-v-4551b4d6] .wx-sortable-list__grip{height:28px}.wx-menu-branch[data-v-4551b4d6] .wx-sortable-list__empty{padding:0}.wx-menu-branch__drop[data-v-4551b4d6]{display:block;height:var(--wx-space-8)}.wx-menu-item[data-v-4551b4d6]{display:flex;align-items:flex-start;gap:var(--wx-space-8);min-width:0;padding-block:var(--wx-space-2)}.wx-menu-item__open[data-v-4551b4d6]{display:flex;flex-direction:column;align-items:flex-start;gap:var(--wx-space-2);flex:1 1 auto;min-width:0;padding:0;border:0;background:none;font:inherit;color:inherit;text-align:start;cursor:pointer}.wx-menu-item__open[data-v-4551b4d6]:disabled{cursor:default}.wx-menu-item__line[data-v-4551b4d6]{display:flex;align-items:center;gap:var(--wx-space-6);min-width:0;max-width:100%}.wx-menu-item__mark[data-v-4551b4d6]{flex:0 0 auto;color:var(--wx-text-placeholder)}.wx-menu-item__state[data-v-4551b4d6]{flex:0 0 auto}.wx-menu-item.is-off .wx-menu-item__open[data-v-4551b4d6]{opacity:.55}.wx-menu-pane[data-v-7c72f6db]{display:flex;flex-direction:column;min-height:0}.wx-menu-pane__head[data-v-7c72f6db]{display:flex;align-items:center;gap:var(--wx-space-12);padding:var(--wx-space-12) var(--wx-space-16);border-bottom:1px solid var(--wx-border-muted)}.wx-menu-pane__name[data-v-7c72f6db]{display:flex;flex-direction:column;flex:1 1 auto;min-width:0}.wx-menu-pane__body[data-v-7c72f6db]{padding:var(--wx-space-12) var(--wx-space-16)}.wx-menus>.wx-card__body{display:flex;flex-direction:column;overflow:clip;border-radius:inherit}.wx-menus__loading[data-v-63e1d3fa]{padding:var(--wx-space-16)}.wx-menus__list[data-v-63e1d3fa]{margin:0;padding:var(--wx-space-8);list-style:none}.wx-menus__row[data-v-63e1d3fa]{display:flex;align-items:center;gap:var(--wx-space-4);margin:0;padding-inline:var(--wx-space-8);border-radius:var(--wx-radius-sm)}.wx-menus__row[data-v-63e1d3fa]:has(.is-current){background:var(--wx-bg-subtle)}.wx-menus__open[data-v-63e1d3fa]{display:flex;flex-direction:column;align-items:flex-start;gap:var(--wx-space-2);flex:1 1 auto;min-width:0;padding-block:var(--wx-space-8);padding-inline:0;border:0;background:none;font:inherit;color:inherit;text-align:start;cursor:pointer}.wx-menus__open.is-current[data-v-63e1d3fa]{color:var(--wx-color-primary)}.wx-menus__name[data-v-63e1d3fa]{display:flex;align-items:center;gap:var(--wx-space-6);min-width:0;max-width:100%}.wx-menus__count[data-v-63e1d3fa]{flex:0 0 auto}.wx-menus__cache[data-v-63e1d3fa]{display:flex;align-items:center;gap:var(--wx-space-4);min-width:0}.wx-menus__row[data-v-63e1d3fa]>.wx-menus__flush{flex:0 0 auto}
1
+ .wx-menu-branch.is-nested[data-v-fdb72572]{padding-inline-start:var(--wx-space-16)}.wx-menu-branch[data-v-fdb72572] .wx-sortable-list__row{align-items:flex-start}.wx-menu-branch[data-v-fdb72572] .wx-sortable-list__grip{height:28px}.wx-menu-branch[data-v-fdb72572] .wx-sortable-list__empty{padding:0}.wx-menu-branch__drop[data-v-fdb72572]{display:block;height:var(--wx-space-8)}.wx-menu-item[data-v-fdb72572]{display:flex;align-items:flex-start;gap:var(--wx-space-8);min-width:0;padding-block:var(--wx-space-2)}.wx-menu-item__open[data-v-fdb72572]{display:flex;flex-direction:column;align-items:flex-start;gap:var(--wx-space-2);flex:1 1 auto;min-width:0;padding:0;border:0;background:none;font:inherit;color:inherit;text-align:start;cursor:pointer}.wx-menu-item__open[data-v-fdb72572]>*{max-width:100%}.wx-menu-item__open[data-v-fdb72572]:disabled{cursor:default}.wx-menu-item__line[data-v-fdb72572]{display:flex;align-items:center;gap:var(--wx-space-6);min-width:0;max-width:100%}.wx-menu-item__mark[data-v-fdb72572]{flex:0 0 auto;color:var(--wx-text-placeholder)}.wx-menu-item__state[data-v-fdb72572]{flex:0 0 auto}.wx-menu-item.is-off .wx-menu-item__open[data-v-fdb72572]{opacity:.55}.wx-menu-pane[data-v-7c72f6db]{display:flex;flex-direction:column;min-height:0}.wx-menu-pane__head[data-v-7c72f6db]{display:flex;align-items:center;gap:var(--wx-space-12);padding:var(--wx-space-12) var(--wx-space-16);border-bottom:1px solid var(--wx-border-muted)}.wx-menu-pane__name[data-v-7c72f6db]{display:flex;flex-direction:column;flex:1 1 auto;min-width:0}.wx-menu-pane__body[data-v-7c72f6db]{padding:var(--wx-space-12) var(--wx-space-16)}.wx-menus>.wx-card__body{display:flex;flex-direction:column;overflow:clip;border-radius:inherit}.wx-menus__loading[data-v-63e1d3fa]{padding:var(--wx-space-16)}.wx-menus__list[data-v-63e1d3fa]{margin:0;padding:var(--wx-space-8);list-style:none}.wx-menus__row[data-v-63e1d3fa]{display:flex;align-items:center;gap:var(--wx-space-4);margin:0;padding-inline:var(--wx-space-8);border-radius:var(--wx-radius-sm)}.wx-menus__row[data-v-63e1d3fa]:has(.is-current){background:var(--wx-bg-subtle)}.wx-menus__open[data-v-63e1d3fa]{display:flex;flex-direction:column;align-items:flex-start;gap:var(--wx-space-2);flex:1 1 auto;min-width:0;padding-block:var(--wx-space-8);padding-inline:0;border:0;background:none;font:inherit;color:inherit;text-align:start;cursor:pointer}.wx-menus__open.is-current[data-v-63e1d3fa]{color:var(--wx-color-primary)}.wx-menus__name[data-v-63e1d3fa]{display:flex;align-items:center;gap:var(--wx-space-6);min-width:0;max-width:100%}.wx-menus__count[data-v-63e1d3fa]{flex:0 0 auto}.wx-menus__cache[data-v-63e1d3fa]{display:flex;align-items:center;gap:var(--wx-space-4);min-width:0}.wx-menus__row[data-v-63e1d3fa]>.wx-menus__flush{flex:0 0 auto}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webx-ui/module-menu",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Menus for the WebX UI admin panel: named menus, a tree of items, and what each one points at.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -43,8 +43,8 @@
43
43
  "vue-router": "^4.5.0"
44
44
  },
45
45
  "dependencies": {
46
- "@webx-ui/core": "^0.31.0",
47
- "@webx-ui/module-admin": "^0.14.0"
46
+ "@webx-ui/core": "^0.33.0",
47
+ "@webx-ui/module-admin": "^0.14.4"
48
48
  },
49
49
  "devDependencies": {
50
50
  "@types/node": "^24.10.1",