@webx-ui/module-seo 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/messages.ts","../src/i18n.ts","../src/SeoLayout.vue","../src/api.ts","../src/TestUrlDialog.vue","../src/SeoAliasesPage.vue","../src/types.ts","../src/SeoCard.vue","../src/SeoRedirectDialog.vue","../src/SeoRedirectsPage.vue","../src/rule.ts","../src/SeoUrlDialog.vue","../src/SeoUrlsPage.vue","../src/module.ts"],"sourcesContent":["import type { Messages } from '@webx-ui/module-admin'\n\n/**\n * What this package says, in English — the same keys `webx-ui/module-seo` ships as\n * `lang/en/*.php`, so a key never reaches the screen when a translation has not arrived yet.\n *\n * The server's dictionary wins over these. There is no second dictionary inside this package:\n * a module translated into ten languages with an English dialog in the middle of it is worse\n * than one that is not translated at all.\n *\n * Placeholders are Laravel's, `:like-this` — the same line is read from a PHP file and from\n * here, and only one of the two spellings can be right.\n */\nexport const seoMessages: Record<string, Messages> = {\n module: {\n title: 'SEO',\n },\n\n page: {\n rules: 'Rules',\n redirects: 'Redirects',\n test: 'Check an address',\n 'filter-kind': 'Kind',\n\n 'new-rule': 'New rule',\n 'new-redirect': 'New redirect',\n 'search-rules': 'Search by address',\n 'search-redirects': 'Search by address or destination',\n empty: 'Nothing here yet.',\n\n address: 'Address',\n kind: 'Kind',\n priority: 'Priority',\n state: 'State',\n active: 'Active',\n inactive: 'Off',\n title: 'Title',\n target: 'Destination',\n status: 'Code',\n hits: 'Hits',\n 'last-hit': 'Last used',\n loop: 'Points at itself',\n\n 'any-kind': 'Any kind',\n exact: 'Exact',\n mask: 'Mask',\n regex: 'Regular expression',\n\n rule: 'Rule',\n redirect: 'Redirect',\n save: 'Save',\n cancel: 'Cancel',\n delete: 'Delete',\n saved: 'Saved.',\n deleted: 'Deleted.',\n failed: 'Some values were not accepted. Check the highlighted fields.',\n 'delete-title': 'Delete :pattern?',\n 'delete-text': 'This cannot be undone.',\n\n 'address-help':\n 'A path with its query string. In a mask, * is one segment and ** is any number of them; a regular expression is stored as written, delimiters and all.',\n 'target-help':\n 'A path on this site, or a full address elsewhere. $1 puts back what a mask caught.',\n 'priority-help': 'Among rules of the same kind, the higher number wins.',\n\n 'test-placeholder': '/catalog/shoes?page=2',\n 'test-run': 'Check',\n 'test-empty': 'Type an address to see what the site will say about it.',\n 'test-matched': 'Matched rule',\n 'test-none': 'No rule matches this address.',\n 'test-redirected': 'This address is redirected to :target with a :status.',\n 'test-result': 'What the page will say',\n 'test-chain': 'Where each part came from',\n\n automatic: 'Automatic',\n 'search-aliases': 'Search by address or destination',\n 'aliases-empty': 'Nothing has moved yet.',\n 'aliases-help':\n 'The site writes these itself: renaming or moving a page leaves its old address behind answering 301, so nothing that linked to it dies. They cannot be edited here — a redirect of your own is tried first and wins.',\n language: 'Language',\n 'moved-at': 'Moved',\n gone: 'Leads nowhere',\n occupied:\n 'A page of the site answers at :path. A redirect is tried before it, so the page stops being reachable there.',\n 'occupied-alias':\n 'This address already leads to :target — what a move left behind. A redirect written here is tried first.',\n },\n\n card: {\n 'section-page': 'The page',\n 'section-share': 'Sharing',\n 'section-advanced': 'Advanced',\n\n title: 'Title',\n h1: 'Heading',\n description: 'Description',\n keywords: 'Keywords',\n canonical: 'Canonical address',\n 'canonical-help': 'The address this page should be indexed under, when it is not its own.',\n\n robots: 'Indexing',\n 'robots-kept': 'Also kept, as written: :directives',\n noindex: 'Keep out of the index',\n nofollow: 'Do not follow the links',\n noarchive: 'No cached copy',\n nosnippet: 'No snippet',\n noimageindex: 'Do not index the images',\n\n 'og-title': 'Share title',\n 'og-description': 'Share description',\n 'og-image': 'Share image',\n 'og-help': 'Left empty, a share preview uses the title and description above.',\n\n 'json-ld': 'Structured data',\n 'json-ld-help':\n 'A JSON-LD object, or a list of them. What the site says about itself comes from the settings; this is for a page that needs markup of its own.',\n 'json-ld-invalid': 'This is not valid JSON.',\n\n preview: 'In search results',\n },\n}\n","import { useAdmin } from '@webx-ui/module-admin'\nimport { seoMessages } 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 useSeoMessages(): void {\n try {\n const { i18n } = useAdmin()\n\n if (!seeded.has(i18n)) {\n i18n.defaults('webx-seo', seoMessages)\n seeded.add(i18n)\n }\n } catch {\n // Outside a panel there is no dictionary to seed, and `useTranslate` falls back on its own.\n }\n}\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { useRoute, useRouter } from 'vue-router'\nimport { useAdmin, useTranslate, WxListScreen, type ScreenAction } from '@webx-ui/module-admin'\nimport type { TabItem, TabValue } from '@webx-ui/core'\nimport { useSeoMessages } from './i18n'\n\n/**\n * The head of the section: its name, the views of what it holds, and the one tool that belongs\n * to the whole of it.\n *\n * Rules, redirects and the trail of renames are three routes rather than three panels of one,\n * because they are three tables with their own paging and their own search — and a panel that\n * quietly resets both when you come back to it is worse than a second address. What they share\n * is the frame: one heading, one strip of tabs, one card under it (§19).\n */\nconst props = defineProps<{\n base: string\n current: 'rules' | 'redirects' | 'aliases'\n /** The view's own main action — `New rule`. The section's own tool is added after it. */\n actions?: ScreenAction[]\n}>()\n\nconst emit = defineEmits<{ test: [] }>()\n\ndefineSlots<{\n /** The table. */\n default?: () => unknown\n}>()\n\nconst context = useAdmin()\nconst router = useRouter()\nconst route = useRoute()\n\nuseSeoMessages()\n\nconst t = useTranslate('webx-seo')\n\n/** The section's name as the server translated it; the built-in English until it arrives. */\nconst title = computed(\n () =>\n context.state.manifest?.modules.find((module) => module.id === 'seo')?.title ??\n t('module.title'),\n)\n\nconst views = computed<TabItem[]>(() => [\n { value: 'rules', label: t('page.rules') },\n { value: 'redirects', label: t('page.redirects') },\n { value: 'aliases', label: t('page.automatic') },\n])\n\n/* The one tool that belongs to the whole section, after whatever the view itself offers. */\nconst actions = computed<ScreenAction[]>(() => [\n ...(props.actions ?? []),\n { key: 'test', label: t('page.test'), icon: 'search', run: () => emit('test') },\n])\n\nconst where = computed<TabValue>({\n get: () => props.current,\n set: (next) => {\n const path = next === 'rules' ? props.base : `${props.base}/${String(next)}`\n\n if (path !== route.path) void router.push(path)\n },\n})\n</script>\n\n<template>\n <wx-list-screen v-model:view=\"where\" :title=\"title\" :views=\"views\" :actions=\"actions\">\n <slot />\n </wx-list-screen>\n</template>\n","import type { AdminContext } from '@webx-ui/module-admin'\nimport type {\n SeoAlias,\n SeoAliasQuery,\n SeoPage,\n SeoRedirect,\n SeoRedirectInput,\n SeoRedirectQuery,\n SeoTestResult,\n SeoUrlInput,\n SeoUrlQuery,\n SeoUrlRule,\n} from './types'\n\nexport interface SeoApi {\n /** The rules, in the order the site tries them. */\n urls(query?: SeoUrlQuery): Promise<SeoPage<SeoUrlRule>>\n url(id: number): Promise<SeoUrlRule>\n createUrl(input: SeoUrlInput): Promise<SeoUrlRule>\n updateUrl(id: number, input: SeoUrlInput): Promise<SeoUrlRule>\n removeUrl(id: number): Promise<void>\n\n redirects(query?: SeoRedirectQuery): Promise<SeoPage<SeoRedirect>>\n createRedirect(input: SeoRedirectInput): Promise<SeoRedirect>\n updateRedirect(id: number, input: SeoRedirectInput): Promise<SeoRedirect>\n removeRedirect(id: number): Promise<void>\n\n /** The redirects nobody wrote: what the address registry kept after a rename. Read only. */\n aliases(query?: SeoAliasQuery): Promise<SeoPage<SeoAlias>>\n\n /** What an address ends up saying, and where every part of it came from. */\n test(url: string, locale?: string | null): Promise<SeoTestResult>\n}\n\n/** Everything under `/seo`, below the panel's API path. */\nexport function createSeoApi(admin: AdminContext): SeoApi {\n const base = `${admin.apiPath}/seo`\n const data = <T>(body: { data: T }): T => body.data\n\n /* A paginated list arrives as a resource collection — rows under `data`, numbers under\n `meta`. The table wants them on one object, so they are joined here once rather than in\n every screen. */\n const page = <T>(body: { data: T[]; meta: Omit<SeoPage<T>, 'data'> }): SeoPage<T> => ({\n ...body.meta,\n data: body.data,\n })\n\n /* `undefined` drops the parameter; `null` and `''` would be sent as words and read as a\n filter nobody asked for. */\n const listQuery = (\n query: SeoRedirectQuery,\n ): Record<string, string | number | boolean | null | undefined> => ({\n q: query.q || undefined,\n match_type: query.match_type ?? undefined,\n is_active: query.is_active ?? undefined,\n sort: query.sort || undefined,\n page: query.page,\n per_page: query.per_page,\n })\n\n return {\n urls: (query = {}) =>\n admin.http\n .get<{ data: SeoUrlRule[]; meta: Omit<SeoPage<SeoUrlRule>, 'data'> }>(`${base}/urls`, {\n query: listQuery(query),\n })\n .then(page),\n\n url: (id) => admin.http.get<{ data: SeoUrlRule }>(`${base}/urls/${id}`).then(data),\n\n createUrl: (input) => admin.http.post<{ data: SeoUrlRule }>(`${base}/urls`, input).then(data),\n\n updateUrl: (id, input) =>\n admin.http.put<{ data: SeoUrlRule }>(`${base}/urls/${id}`, input).then(data),\n\n removeUrl: (id) => admin.http.delete<void>(`${base}/urls/${id}`),\n\n redirects: (query = {}) =>\n admin.http\n .get<{ data: SeoRedirect[]; meta: Omit<SeoPage<SeoRedirect>, 'data'> }>(\n `${base}/redirects`,\n { query: listQuery(query) },\n )\n .then(page),\n\n createRedirect: (input) =>\n admin.http.post<{ data: SeoRedirect }>(`${base}/redirects`, input).then(data),\n\n updateRedirect: (id, input) =>\n admin.http.put<{ data: SeoRedirect }>(`${base}/redirects/${id}`, input).then(data),\n\n removeRedirect: (id) => admin.http.delete<void>(`${base}/redirects/${id}`),\n\n aliases: (query = {}) =>\n admin.http\n .get<{ data: SeoAlias[]; meta: Omit<SeoPage<SeoAlias>, 'data'> }>(`${base}/aliases`, {\n query: {\n q: query.q || undefined,\n locale: query.locale ?? undefined,\n page: query.page,\n per_page: query.per_page,\n },\n })\n .then(page),\n\n // A POST because it carries an address in its body, and an address in a query string is an\n // address somebody has to escape twice.\n test: (url, locale = null) =>\n admin.http\n .post<{ data: SeoTestResult }>(`${base}/test-url`, { url, locale: locale ?? undefined })\n .then(data),\n }\n}\n","<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\nimport { useAdmin, useTranslate } from '@webx-ui/module-admin'\nimport {\n WxAlert,\n WxBadge,\n WxButton,\n WxDescriptions,\n WxDescriptionsItem,\n WxDialog,\n WxEmpty,\n WxInput,\n WxSpace,\n WxText,\n useModal,\n} from '@webx-ui/core'\nimport { createSeoApi } from './api'\nimport { useSeoMessages } from './i18n'\nimport type { SeoTestResult } from './types'\n\n/**\n * \"Why does this page have the wrong title?\"\n *\n * The most common question this section gets, and the reason it should take one call to answer\n * rather than a reading of the code: which redirect catches the address, which rule matches it,\n * what every source contributed, and what the page ends up saying.\n */\nconst { open } = useModal<void>()\n\nconst context = useAdmin()\nconst api = createSeoApi(context)\nuseSeoMessages()\n\nconst t = useTranslate('webx-seo')\n\nconst url = ref('')\nconst running = ref(false)\nconst result = ref<SeoTestResult | null>(null)\n\nconst fields = computed(() => {\n const seo = result.value?.seo\n\n if (!seo) return []\n\n return [\n { label: t('card.title'), value: seo.title },\n { label: t('card.h1'), value: seo.h1 },\n { label: t('card.description'), value: seo.description },\n { label: t('card.keywords'), value: seo.keywords },\n { label: t('card.canonical'), value: seo.canonical },\n { label: t('card.robots'), value: seo.robots },\n ].filter((one) => one.value)\n})\n\nasync function run(): Promise<void> {\n if (url.value.trim() === '') return\n\n running.value = true\n\n try {\n result.value = await api.test(url.value)\n } finally {\n running.value = false\n }\n}\n</script>\n\n<template>\n <wx-dialog v-model:open=\"open\" :title=\"t('page.test')\" :width=\"720\">\n <div class=\"wx-seo-test\">\n <wx-space size=\"sm\" class=\"wx-seo-test__ask\">\n <wx-input\n v-model=\"url\"\n :placeholder=\"t('page.test-placeholder')\"\n class=\"wx-seo-test__input\"\n @keyup.enter=\"run\"\n />\n <wx-button type=\"primary\" :loading=\"running\" @click=\"run\">\n {{ t('page.test-run') }}\n </wx-button>\n </wx-space>\n\n <template v-if=\"result\">\n <!-- First, because it happens first: a redirected address never reaches the rules. -->\n <wx-alert\n v-if=\"result.redirect\"\n type=\"warning\"\n :description=\"\n t('page.test-redirected', {\n target: result.redirect.target,\n status: result.redirect.status,\n })\n \"\n />\n\n <!-- Who answers at this address at all. A rule of the site's own comes before any of\n this, so an address can be redirected away from a page that still exists. -->\n <wx-alert\n v-if=\"result.route\"\n type=\"info\"\n :description=\"\n result.route.kind === 'alias'\n ? t('page.occupied-alias', { target: result.route.target ?? result.route.path })\n : t('page.occupied', { path: result.route.path })\n \"\n />\n\n <wx-alert v-if=\"!result.matched\" type=\"info\" :description=\"t('page.test-none')\" />\n <div v-else class=\"wx-seo-test__matched\">\n <wx-text size=\"sm\" tone=\"muted\">{{ t('page.test-matched') }}</wx-text>\n <wx-space size=\"sm\" align=\"center\">\n <wx-badge>{{ t(`page.${result.matched.match_type}`) }}</wx-badge>\n <wx-text mono size=\"sm\">{{ result.matched.pattern }}</wx-text>\n </wx-space>\n </div>\n\n <div v-if=\"fields.length > 0\" class=\"wx-seo-test__result\">\n <wx-text size=\"sm\" tone=\"muted\">{{ t('page.test-result') }}</wx-text>\n <wx-descriptions :columns=\"1\" bordered size=\"sm\">\n <wx-descriptions-item v-for=\"one in fields\" :key=\"one.label\" :label=\"one.label\">\n {{ one.value }}\n </wx-descriptions-item>\n </wx-descriptions>\n </div>\n\n <div v-if=\"result.chain.length > 0\" class=\"wx-seo-test__chain\">\n <wx-text size=\"sm\" tone=\"muted\">{{ t('page.test-chain') }}</wx-text>\n <wx-space size=\"xs\" wrap>\n <wx-badge v-for=\"step in result.chain\" :key=\"step.source\">\n {{ step.source }} · {{ step.priority }}\n </wx-badge>\n </wx-space>\n </div>\n </template>\n\n <wx-empty v-else-if=\"!running\" icon=\"search\" :description=\"t('page.test-empty')\" />\n </div>\n </wx-dialog>\n</template>\n\n<style scoped>\n.wx-seo-test {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-16);\n}\n\n.wx-seo-test__ask {\n width: 100%;\n}\n\n.wx-seo-test__input {\n flex: 1 1 auto;\n}\n\n.wx-seo-test__matched,\n.wx-seo-test__result,\n.wx-seo-test__chain {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-6);\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\nimport { useTranslate, useAdmin, WxDate } from '@webx-ui/module-admin'\nimport {\n createModal,\n WxAlert,\n WxBadge,\n WxLink,\n WxTable,\n WxText,\n type TableColumn,\n type TableState,\n} from '@webx-ui/core'\nimport SeoLayout from './SeoLayout.vue'\nimport TestUrlDialog from './TestUrlDialog.vue'\nimport { createSeoApi } from './api'\nimport { useSeoMessages } from './i18n'\nimport type { SeoAlias, SeoPage } from './types'\n\n/**\n * The redirects nobody wrote.\n *\n * Renaming a page or moving a branch leaves its old address behind, answering 301 — which is\n * the only reason an external link, a bookmark or a search result survives an edit in the panel.\n * They are shown here because a reader who lands on a dead address does not care which half of\n * the system made the redirect, and an editor chasing one should not have to.\n *\n * Nothing on this screen writes. These rows belong to the entity that moved: it makes them, and\n * deleting it takes them with it. An editor who wants a different answer for an old address\n * writes a rule of their own on the next tab — those are tried first, and this one stops\n * mattering.\n */\nconst props = withDefaults(defineProps<{ base?: string }>(), { base: '/seo' })\n\nconst context = useAdmin()\nconst api = createSeoApi(context)\nuseSeoMessages()\n\nconst t = useTranslate('webx-seo')\n\nconst page = ref<SeoPage<SeoAlias> | null>(null)\nconst loading = ref(false)\n\nconst test = createModal<void, Record<string, never>>(TestUrlDialog)\n\n/** More than one language on the site is the only reason to say which one a row is in. */\nconst multilingual = computed(() => (context.state.manifest?.locales?.length ?? 1) > 1)\n\nconst columns = computed<TableColumn<SeoAlias>[]>(() => [\n { key: 'pattern', label: t('page.address'), sortable: false },\n { key: 'target', label: t('page.target') },\n {\n key: 'locale',\n label: t('page.language'),\n align: 'center',\n hidden: !multilingual.value,\n hideBelow: 760,\n },\n { key: 'created_at', label: t('page.moved-at'), hideBelow: 900, hideOnCards: true },\n])\n\nasync function load(state: TableState): Promise<void> {\n loading.value = true\n\n try {\n page.value = await api.aliases({\n q: state.search,\n page: state.page,\n per_page: state.perPage,\n })\n } finally {\n loading.value = false\n }\n}\n</script>\n\n<template>\n <seo-layout :base=\"props.base\" current=\"aliases\" @test=\"test({})\">\n <wx-alert\n type=\"info\"\n variant=\"soft\"\n :description=\"t('page.aliases-help')\"\n class=\"wx-seo-aliases__note\"\n />\n\n <wx-table\n :data=\"page\"\n :columns=\"columns\"\n row-key=\"id\"\n searchable\n flush\n :loading=\"loading\"\n :search-placeholder=\"t('page.search-aliases')\"\n :empty-text=\"t('page.aliases-empty')\"\n @state-change=\"load\"\n >\n <template #cell-pattern=\"{ row }\">\n <wx-text mono size=\"sm\">{{ row.pattern }}</wx-text>\n </template>\n\n <!-- The address it leads to now, as a link: this is the one place in the panel where an\n editor can walk from a dead address to the live page without knowing the section it\n lives in. -->\n <template #cell-target=\"{ row }\">\n <wx-link v-if=\"row.target && row.target_url\" :href=\"row.target_url\" external size=\"sm\">\n <wx-text mono size=\"sm\">{{ row.target }}</wx-text>\n </wx-link>\n <wx-badge v-else type=\"warning\">{{ t('page.gone') }}</wx-badge>\n </template>\n\n <template #cell-locale=\"{ row }\">\n <wx-badge>{{ row.locale }}</wx-badge>\n </template>\n\n <template #cell-created_at=\"{ row }\">\n <wx-date v-if=\"row.created_at\" :value=\"row.created_at\" />\n </template>\n </wx-table>\n </seo-layout>\n</template>\n\n<style scoped>\n.wx-seo-aliases__note {\n margin-bottom: var(--wx-space-12);\n}\n</style>\n","import type { LocalizedValue } from '@webx-ui/core'\n\n/** The shapes `webx-ui/module-seo` answers with, and what its forms send back. */\n\n/** How a pattern is compared with an address. */\nexport type MatchType = 'exact' | 'mask' | 'regex'\n\n/** What `wx-media` stores, plus the address the server works out on every read. */\nexport interface SeoImage {\n path: string\n alt?: string\n title?: string\n url?: string | null\n}\n\n/**\n * What a page says about itself.\n *\n * The text fields are language maps — the same `{ en: '…', ru: '…' }` every localized field in\n * the panel uses. The picture is not: there are no per-language images anywhere yet, and a\n * column that already holds an object would read `path` as a language code.\n */\nexport interface SeoFields {\n title: LocalizedValue\n h1: LocalizedValue\n description: LocalizedValue\n keywords: LocalizedValue\n og_title: LocalizedValue\n og_description: LocalizedValue\n og_image: SeoImage | null\n canonical: string | null\n robots: string | null\n /** A JSON-LD object, a list of them, or null. */\n json_ld: unknown\n}\n\n/**\n * The value of a `wx-seo` field: the same fields, none of them required.\n *\n * A record that has never been given any SEO stores nothing rather than a shape full of nulls.\n */\nexport type SeoValue = Partial<SeoFields>\n\nexport interface SeoUrlRule extends SeoFields {\n id: number\n match_type: MatchType\n pattern: string\n priority: number\n is_active: boolean\n created_at: string | null\n updated_at: string | null\n /**\n * What a table row is, as far as `WxTable` is concerned. Without it this type does not\n * extend `TableRow` and every cell slot hands back `unknown`.\n */\n [key: string]: unknown\n}\n\n/** A `PUT` replaces the whole rule: the form edits every field at once. */\nexport interface SeoUrlInput extends SeoValue {\n match_type: MatchType\n pattern: string\n priority?: number\n is_active?: boolean\n}\n\nexport interface SeoRedirect {\n id: number\n match_type: MatchType\n pattern: string\n target: string\n status: number\n is_active: boolean\n hits: number\n last_hit_at: string | null\n /** The server says so rather than refusing the row: a loop is skipped, not rejected. */\n is_loop: boolean\n created_at: string | null\n updated_at: string | null\n [key: string]: unknown\n}\n\n/**\n * An address a rename left behind, from `webx-ui/routing`.\n *\n * The same words a manual redirect uses where the two overlap, so one screen can show both\n * without learning two vocabularies. What it has instead of the rest is an entity: these are\n * written by whatever moved, always exact, always 301, and never edited here.\n */\nexport interface SeoAlias {\n id: number\n locale: string\n /** The old address. */\n pattern: string\n /** The address it leads to now; null when the row it pointed at is gone. */\n target: string | null\n url: string\n target_url: string | null\n /** `page`, `article`, `product` — what the content module called itself. */\n entity_type: string\n entity_id: number\n created_at: string | null\n [key: string]: unknown\n}\n\nexport interface SeoAliasQuery {\n q?: string\n locale?: string | null\n page?: number\n per_page?: number\n}\n\n/**\n * What the address registry holds at an address, when something does.\n *\n * A rule written by hand is tried before any of it — that is deliberate — so this is a warning\n * and never a refusal: the page at this address is about to stop being reachable.\n */\nexport interface SeoRoute {\n path: string\n url: string\n kind: 'canonical' | 'alias'\n /** Where an old address leads now; null on a live page, which has nowhere to lead. */\n target: string | null\n entity_type: string\n entity_id: number\n /** False when a shorter address matched: the page at `/parts` answering for `/parts/bobcat`. */\n exact: boolean\n}\n\nexport interface SeoRedirectInput {\n match_type: MatchType\n pattern: string\n target: string\n status?: number\n is_active?: boolean\n}\n\n/**\n * A page, flat — what `->paginate()` serialises and `WxTable` reads as it arrives. A resource\n * collection nests the same numbers under `meta`; the client flattens them once, here.\n */\nexport interface SeoPage<T> {\n data: T[]\n current_page: number\n last_page: number\n per_page: number\n total: number\n from: number | null\n to: number | null\n}\n\nexport interface SeoUrlQuery {\n q?: string\n match_type?: MatchType | null\n is_active?: boolean | null\n page?: number\n per_page?: number\n}\n\nexport interface SeoRedirectQuery extends SeoUrlQuery {\n sort?: string\n}\n\n/** What a page ends up saying, every source merged. */\nexport interface SeoResolved {\n title: string | null\n h1: string | null\n description: string | null\n keywords: string | null\n canonical: string | null\n robots: string | null\n og: Record<string, string>\n json_ld: Record<string, unknown>[]\n}\n\n/** One source's contribution, before the merge. */\nexport interface SeoChainStep {\n source: string\n priority: number\n data: SeoResolved\n}\n\n/** The answer to \"why does this page say that\". */\nexport interface SeoTestResult {\n url: string\n /** Said first because it happens first: a redirected address never reaches the rules. */\n redirect: SeoRedirect | null\n /** What the address registry has here — a live page, or the trail of one that moved. */\n route: SeoRoute | null\n matched: SeoUrlRule | null\n chain: SeoChainStep[]\n seo: SeoResolved\n}\n\n/** The meta directives the card offers as checkboxes. Anything else is kept as written. */\nexport const robotsDirectives = [\n 'noindex',\n 'nofollow',\n 'noarchive',\n 'nosnippet',\n 'noimageindex',\n] as const\n\nexport type RobotsDirective = (typeof robotsDirectives)[number]\n","<script setup lang=\"ts\">\nimport { computed, ref, type Component } from 'vue'\nimport { useTranslate } from '@webx-ui/module-admin'\nimport {\n localizedValue,\n useLocales,\n WxCheckboxGroup,\n WxCodeEditor,\n WxFormItem,\n WxInput,\n WxTabs,\n WxTab,\n WxText,\n WxTextarea,\n type LocalizedValue,\n} from '@webx-ui/core'\nimport { useSeoMessages } from './i18n'\nimport { robotsDirectives, type SeoImage, type SeoValue } from './types'\n\n/**\n * What a page says about itself, as a field.\n *\n * Registered as `wx-seo`, so it can be dropped into any screen by a patch, and used directly by\n * the rule form in this package. The value is one object — the same shape the server stores —\n * rather than a dozen loose keys, because SEO is edited and reasoned about as a unit.\n *\n * The text fields are language maps and say so: the same chip every other localized field in the\n * panel grows, moving together with them.\n */\ndefineOptions({ name: 'WxSeo' })\n\nconst props = withDefaults(\n defineProps<{\n /**\n * The field that picks the share image — `WxMediaField` in a panel that has the library.\n *\n * Handed in rather than imported: this package does not depend on the media one, and a panel\n * without a library still edits everything else about a page.\n */\n mediaField?: Component\n disabled?: boolean\n /**\n * Soft limits, for the counters only. Long is not wrong — search engines shorten what they\n * shorten — so nothing here refuses a longer line, it only says how long it is.\n */\n titleLimit?: number\n descriptionLimit?: number\n /** What the snippet preview shows above the title. */\n site?: string\n /** Hidden when a screen has no use for them. */\n structuredData?: boolean\n }>(),\n {\n mediaField: undefined,\n disabled: false,\n titleLimit: 60,\n descriptionLimit: 160,\n site: undefined,\n structuredData: true,\n },\n)\n\nconst value = defineModel<SeoValue | null>({ default: null })\n\nuseSeoMessages()\n\nconst t = useTranslate('webx-seo')\nconst locales = useLocales()\n\nconst jsonLdText = ref<string | null>(null)\nconst jsonLdBroken = ref(false)\n\n/** One field of the value, without rebuilding the object in every handler. */\nfunction field<K extends keyof SeoValue>(key: K) {\n return computed({\n get: () => value.value?.[key],\n set: (next: SeoValue[K]) => {\n value.value = { ...(value.value ?? {}), [key]: next }\n },\n })\n}\n\nconst title = field('title')\nconst h1 = field('h1')\nconst description = field('description')\nconst keywords = field('keywords')\nconst ogTitle = field('og_title')\nconst ogDescription = field('og_description')\n\nconst canonical = computed<string>({\n get: () => value.value?.canonical ?? '',\n set: (next: string) => {\n value.value = { ...(value.value ?? {}), canonical: next.trim() === '' ? null : next }\n },\n})\n\nconst image = computed({\n get: () => value.value?.og_image ?? null,\n set: (next: SeoImage | null) => {\n value.value = { ...(value.value ?? {}), og_image: next }\n },\n})\n\n/* The directives the checkboxes cover. Anything else somebody wrote — `max-snippet:20` and its\n kind — is kept and shown, because silently dropping a line a person typed is worse than not\n offering a box for it. */\nconst directives = computed(() => (value.value?.robots ?? '').split(',').map((one) => one.trim()))\n\nconst kept = computed(() =>\n directives.value.filter((one) => one !== '' && !robotsDirectives.includes(one as never)),\n)\n\nconst known = computed({\n get: () => directives.value.filter((one) => robotsDirectives.includes(one as never)),\n set: (next: readonly string[]) => {\n const merged = [...next, ...kept.value].join(', ')\n value.value = { ...(value.value ?? {}), robots: merged === '' ? null : merged }\n },\n})\n\nconst robotsOptions = computed(() =>\n robotsDirectives.map((one) => ({ value: one, label: t(`card.${one}`) })),\n)\n\nconst jsonLd = computed<string>({\n get: () => {\n if (jsonLdText.value !== null) return jsonLdText.value\n\n const stored = value.value?.json_ld\n\n return stored === null || stored === undefined ? '' : JSON.stringify(stored, null, 2)\n },\n set: (next: string) => {\n /* The text is kept as typed while it is being typed: reformatting somebody's JSON the\n moment it happens to parse moves the caret out from under them. */\n jsonLdText.value = next\n\n if (next.trim() === '') {\n jsonLdBroken.value = false\n value.value = { ...(value.value ?? {}), json_ld: null }\n\n return\n }\n\n try {\n value.value = { ...(value.value ?? {}), json_ld: JSON.parse(next) }\n jsonLdBroken.value = false\n } catch {\n jsonLdBroken.value = true\n }\n },\n})\n\n/** What the counters and the preview read: the language being edited. */\nfunction inLocale(map: LocalizedValue | undefined): string {\n return localizedValue(map, locales.active.value)\n}\n\nconst counted = computed(() => ({\n title: inLocale(title.value as LocalizedValue | undefined).length,\n description: inLocale(description.value as LocalizedValue | undefined).length,\n}))\n\nconst preview = computed(() => ({\n title: inLocale(title.value as LocalizedValue | undefined),\n description: inLocale(description.value as LocalizedValue | undefined),\n address: canonical.value || (props.site ?? ''),\n}))\n\nconst hasPreview = computed(() => preview.value.title !== '' || preview.value.description !== '')\n\nconst keptHelp = computed(() =>\n kept.value.length === 0\n ? undefined\n : t('card.robots-kept', { directives: kept.value.join(', ') }),\n)\n\nfunction tone(count: number, limit: number): 'muted' | 'warning' {\n return count > limit ? 'warning' : 'muted'\n}\n</script>\n\n<template>\n <div class=\"wx-seo\">\n <wx-tabs keep-alive>\n <wx-tab :label=\"t('card.section-page')\" value=\"page\">\n <div class=\"wx-seo__fields\">\n <wx-form-item :label=\"t('card.title')\" :disabled=\"disabled\">\n <wx-input v-model=\"title\" localized />\n <div class=\"wx-seo__count\">\n <wx-text size=\"sm\" :tone=\"tone(counted.title, titleLimit)\">\n {{ counted.title }} / {{ titleLimit }}\n </wx-text>\n </div>\n </wx-form-item>\n\n <wx-form-item :label=\"t('card.h1')\" :disabled=\"disabled\">\n <wx-input v-model=\"h1\" localized />\n </wx-form-item>\n\n <wx-form-item :label=\"t('card.description')\" :disabled=\"disabled\">\n <wx-textarea v-model=\"description\" localized :rows=\"3\" />\n <div class=\"wx-seo__count\">\n <wx-text size=\"sm\" :tone=\"tone(counted.description, descriptionLimit)\">\n {{ counted.description }} / {{ descriptionLimit }}\n </wx-text>\n </div>\n </wx-form-item>\n\n <wx-form-item :label=\"t('card.keywords')\" :disabled=\"disabled\">\n <wx-input v-model=\"keywords\" localized />\n </wx-form-item>\n\n <div v-if=\"hasPreview\" class=\"wx-seo__preview\">\n <wx-text size=\"sm\" tone=\"muted\">{{ t('card.preview') }}</wx-text>\n <div class=\"wx-seo__snippet\">\n <div v-if=\"preview.address\" class=\"wx-seo__snippet-address\">\n {{ preview.address }}\n </div>\n <div class=\"wx-seo__snippet-title\">{{ preview.title }}</div>\n <div class=\"wx-seo__snippet-text\">{{ preview.description }}</div>\n </div>\n </div>\n </div>\n </wx-tab>\n\n <wx-tab :label=\"t('card.section-share')\" value=\"share\">\n <div class=\"wx-seo__fields\">\n <component\n :is=\"mediaField\"\n v-if=\"mediaField\"\n v-model=\"image\"\n :label=\"t('card.og-image')\"\n aspect=\"16/9\"\n accept=\"image\"\n :disabled=\"disabled\"\n />\n\n <wx-form-item :label=\"t('card.og-title')\" :help=\"t('card.og-help')\" :disabled=\"disabled\">\n <wx-input v-model=\"ogTitle\" localized />\n </wx-form-item>\n\n <wx-form-item :label=\"t('card.og-description')\" :disabled=\"disabled\">\n <wx-textarea v-model=\"ogDescription\" localized :rows=\"2\" />\n </wx-form-item>\n </div>\n </wx-tab>\n\n <wx-tab :label=\"t('card.section-advanced')\" value=\"advanced\">\n <div class=\"wx-seo__fields\">\n <wx-form-item\n :label=\"t('card.canonical')\"\n :help=\"t('card.canonical-help')\"\n :disabled=\"disabled\"\n >\n <wx-input v-model=\"canonical\" placeholder=\"https://example.com/page\" />\n </wx-form-item>\n\n <wx-form-item :label=\"t('card.robots')\" :help=\"keptHelp\" :disabled=\"disabled\">\n <wx-checkbox-group v-model=\"known\" :options=\"robotsOptions\" />\n </wx-form-item>\n\n <wx-form-item\n v-if=\"structuredData\"\n :label=\"t('card.json-ld')\"\n :help=\"t('card.json-ld-help')\"\n :error=\"jsonLdBroken ? t('card.json-ld-invalid') : undefined\"\n :disabled=\"disabled\"\n >\n <wx-code-editor\n v-model=\"jsonLd\"\n language=\"json\"\n lint\n min-height=\"120px\"\n max-height=\"420px\"\n />\n </wx-form-item>\n </div>\n </wx-tab>\n </wx-tabs>\n </div>\n</template>\n\n<style scoped>\n.wx-seo {\n container-type: inline-size;\n}\n\n.wx-seo__fields {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-12);\n padding-block-start: var(--wx-space-12);\n}\n\n.wx-seo__count {\n display: flex;\n justify-content: flex-end;\n}\n\n.wx-seo__preview {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-4);\n}\n\n/* Not a copy of any one search engine's styling — a reminder of how much of a line survives. */\n.wx-seo__snippet {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-2);\n padding: var(--wx-space-12);\n border: 1px solid var(--wx-border-default);\n border-radius: var(--wx-radius-md);\n background: var(--wx-bg-subtle);\n}\n\n.wx-seo__snippet-address {\n color: var(--wx-text-muted);\n font-size: var(--wx-font-size-sm);\n overflow-wrap: anywhere;\n}\n\n.wx-seo__snippet-title {\n color: var(--wx-color-primary);\n font-size: var(--wx-font-size-lg);\n overflow-wrap: anywhere;\n}\n\n.wx-seo__snippet-text {\n color: var(--wx-text-default);\n font-size: var(--wx-font-size-sm);\n overflow-wrap: anywhere;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\nimport { useAdmin, useErrorText, useTranslate } from '@webx-ui/module-admin'\nimport {\n toast,\n useModal,\n WxAlert,\n WxButton,\n WxDialog,\n WxFormItem,\n WxInput,\n WxSelect,\n WxSpace,\n WxSwitch,\n} from '@webx-ui/core'\nimport { createSeoApi } from './api'\nimport { useSeoMessages } from './i18n'\nimport type { MatchType, SeoRedirect, SeoRoute } from './types'\n\n/** One address that has moved, and where to. Four fields, so four fields. */\nconst props = withDefaults(defineProps<{ redirect?: SeoRedirect | null }>(), { redirect: null })\n\nconst { resolve, dismiss, open } = useModal<SeoRedirect>()\n\nconst context = useAdmin()\nconst api = createSeoApi(context)\nuseSeoMessages()\n\nconst t = useTranslate('webx-seo')\n/* Not the server's `message`: the panel says how a request failed in its own words (§13.3). */\nconst message = useErrorText()\n\nconst saving = ref(false)\nconst errors = ref<Record<string, string[]>>({})\nconst occupant = ref<SeoRoute | null>(null)\n\nconst form = ref({\n match_type: 'exact' as MatchType,\n pattern: '',\n target: '',\n status: 301,\n is_active: true,\n})\n\nconst editing = computed(() => props.redirect !== null)\n\nconst kindOptions = computed(() => [\n { value: 'exact', label: t('page.exact') },\n { value: 'mask', label: t('page.mask') },\n { value: 'regex', label: t('page.regex') },\n])\n\n/* Numbers, not words: 301 and 302 are what a person types into a search when they want to know\n what one is, and a label would only stand between them and that. */\nconst statusOptions = [\n { value: 301, label: '301' },\n { value: 302, label: '302' },\n]\n\nwatch(\n () => props.redirect,\n (redirect) => {\n form.value = {\n match_type: redirect?.match_type ?? 'exact',\n pattern: redirect?.pattern ?? '',\n target: redirect?.target ?? '',\n status: redirect?.status ?? 301,\n is_active: redirect?.is_active ?? true,\n }\n errors.value = {}\n occupant.value = null\n void shadowed()\n },\n { immediate: true },\n)\n\n/**\n * Whether a live page is standing at the address this rule is about to take over.\n *\n * A rule wins: `RedirectRequests` runs before routing, deliberately, so that an editor can\n * redirect an address the site still answers at. Which is exactly why this has to be said out\n * loud — the page does not disappear from the panel, it only stops being reachable, and the\n * person who finds out otherwise is a reader.\n *\n * Only for an exact address. A mask covers addresses that do not exist yet and a regular\n * expression covers ones nobody can enumerate; guessing at either would produce a warning that\n * is wrong often enough to be ignored, and a warning that is ignored is worse than none.\n */\nasync function shadowed(): Promise<void> {\n const pattern = form.value.pattern.trim()\n\n if (form.value.match_type !== 'exact' || pattern === '') {\n occupant.value = null\n\n return\n }\n\n try {\n occupant.value = (await api.test(pattern)).route\n } catch {\n // The check is a courtesy; a rule is not held up because it could not be made.\n occupant.value = null\n }\n}\n\nfunction errorOf(field: string): string | undefined {\n return errors.value[field]?.[0]\n}\n\nasync function save(): Promise<void> {\n saving.value = true\n errors.value = {}\n\n try {\n const saved = props.redirect\n ? await api.updateRedirect(props.redirect.id, form.value)\n : await api.createRedirect(form.value)\n\n toast.success(t('page.saved'))\n resolve(saved)\n } catch (error) {\n const body = (error as { body?: { errors?: Record<string, string[]>; message?: string } }).body\n\n if (body?.errors) {\n errors.value = body.errors\n toast.danger(t('page.failed'))\n } else {\n toast.danger(message(error, t('page.failed')))\n }\n } finally {\n saving.value = false\n }\n}\n</script>\n\n<template>\n <wx-dialog v-model:open=\"open\" :title=\"t('page.redirect')\" :width=\"640\">\n <div class=\"wx-seo-redirect\">\n <wx-form-item :label=\"t('page.kind')\">\n <wx-select v-model=\"form.match_type\" :options=\"kindOptions\" @change=\"shadowed\" />\n </wx-form-item>\n\n <wx-form-item\n :label=\"t('page.address')\"\n :help=\"t('page.address-help')\"\n :error=\"errorOf('pattern')\"\n >\n <wx-input v-model=\"form.pattern\" placeholder=\"/old-address\" @blur=\"shadowed\" />\n </wx-form-item>\n\n <!-- Said, not refused: the rule is allowed to shadow the page, and whoever writes it\n should know that is what they are doing. -->\n <wx-alert\n v-if=\"occupant\"\n type=\"warning\"\n variant=\"soft\"\n :description=\"\n occupant.kind === 'alias'\n ? t('page.occupied-alias', { target: occupant.target ?? occupant.path })\n : t('page.occupied', { path: occupant.path })\n \"\n />\n\n <wx-form-item\n :label=\"t('page.target')\"\n :help=\"t('page.target-help')\"\n :error=\"errorOf('target')\"\n >\n <wx-input v-model=\"form.target\" placeholder=\"/new-address\" />\n </wx-form-item>\n\n <div class=\"wx-seo-redirect__row\">\n <wx-form-item :label=\"t('page.status')\">\n <wx-select v-model=\"form.status\" :options=\"statusOptions\" />\n </wx-form-item>\n\n <wx-form-item :label=\"t('page.state')\">\n <wx-switch v-model=\"form.is_active\" />\n </wx-form-item>\n </div>\n </div>\n\n <template #footer>\n <wx-space size=\"sm\">\n <wx-button variant=\"outline\" @click=\"dismiss()\">{{ t('page.cancel') }}</wx-button>\n <wx-button type=\"primary\" :loading=\"saving\" @click=\"save\">\n {{ editing ? t('page.save') : t('page.new-redirect') }}\n </wx-button>\n </wx-space>\n </template>\n </wx-dialog>\n</template>\n\n<style scoped>\n.wx-seo-redirect {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-12);\n}\n\n.wx-seo-redirect__row {\n display: grid;\n grid-template-columns: 140px 1fr;\n gap: var(--wx-space-12);\n align-items: start;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\nimport {\n useAdmin,\n useErrorText,\n useTranslate,\n WxDate,\n rowMenuWidth,\n WxRowMenu,\n type RowAction,\n type ScreenAction,\n} from '@webx-ui/module-admin'\nimport {\n confirm,\n createModal,\n toast,\n WxBadge,\n WxTable,\n WxText,\n type TableColumn,\n type TableState,\n} from '@webx-ui/core'\nimport SeoLayout from './SeoLayout.vue'\nimport SeoRedirectDialog from './SeoRedirectDialog.vue'\nimport TestUrlDialog from './TestUrlDialog.vue'\nimport { createSeoApi } from './api'\nimport { useSeoMessages } from './i18n'\nimport type { SeoPage, SeoRedirect } from './types'\n\n/**\n * The addresses that have moved.\n *\n * Busiest first by default, which is the order this list is worked on: a redirect nobody has\n * followed since the site was rebuilt is the one worth deleting, and it sits at the bottom.\n */\nconst props = withDefaults(defineProps<{ base?: string }>(), { base: '/seo' })\n\nconst context = useAdmin()\nconst api = createSeoApi(context)\nuseSeoMessages()\n\nconst t = useTranslate('webx-seo')\n/* Not the server's `message`: the panel says how a request failed in its own words (§13.3). */\nconst message = useErrorText()\n\nconst page = ref<SeoPage<SeoRedirect> | null>(null)\nconst loading = ref(false)\n\nlet last: TableState = { page: 1, perPage: 20, sort: null, search: '' }\n\nconst canManage = context.can('seo.manage')\n\nconst edit = createModal<SeoRedirect, { redirect: SeoRedirect | null }>(SeoRedirectDialog)\nconst test = createModal<void, Record<string, never>>(TestUrlDialog)\n\nconst columns = computed<TableColumn<SeoRedirect>[]>(() => [\n { key: 'pattern', label: t('page.address'), sortable: true },\n { key: 'target', label: t('page.target') },\n { key: 'status', label: t('page.status'), align: 'center', hideBelow: 560 },\n { key: 'hits', label: t('page.hits'), align: 'center', sortable: true, hideBelow: 760 },\n {\n key: 'last_hit_at',\n label: t('page.last-hit'),\n sortable: true,\n hideBelow: 900,\n hideOnCards: true,\n },\n { key: 'is_active', label: t('page.state'), align: 'center', hideBelow: 660 },\n {\n key: 'actions',\n label: '',\n width: rowMenuWidth,\n align: 'right',\n hidden: !canManage,\n hideOnCards: true,\n },\n])\n\n/** One line, and the same menu every other list of the panel puts a record's actions in. */\nfunction actionsFor(redirect: SeoRedirect): RowAction[] {\n return [\n {\n key: 'delete',\n icon: 'trash',\n label: t('page.delete'),\n danger: true,\n run: () => remove(redirect),\n },\n ]\n}\n\nasync function load(state: TableState): Promise<void> {\n last = state\n loading.value = true\n\n try {\n page.value = await api.redirects({\n q: state.search,\n sort: state.sort ? `${state.sort.order === 'desc' ? '-' : ''}${state.sort.key}` : '-hits',\n page: state.page,\n per_page: state.perPage,\n })\n } finally {\n loading.value = false\n }\n}\n\n/**\n * Opening a redirect is editing it, so a reader who may not manage these has nowhere to go: the\n * table is told so (`:clickable=\"canManage\"`) and withholds the pointer, the highlight and the\n * click together, rather than promising a dialog that would not open (§13).\n */\nasync function open(redirect: SeoRedirect | null): Promise<void> {\n const saved = await edit({ redirect })\n\n if (saved) void load(last)\n}\n\nasync function remove(redirect: SeoRedirect): Promise<void> {\n const agreed = await confirm({\n title: t('page.delete-title', { pattern: redirect.pattern }),\n message: t('page.delete-text'),\n confirmText: t('page.delete'),\n cancelText: t('page.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n try {\n await api.removeRedirect(redirect.id)\n toast.success(t('page.deleted'))\n void load(last)\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\n/* What the section offers. Declared, because on a phone the head folds it into the ···. */\nconst actions = computed<ScreenAction[]>(() =>\n canManage\n ? [\n {\n key: 'redirect',\n label: t('page.new-redirect'),\n icon: 'plus',\n primary: true,\n run: () => void open(null),\n },\n ]\n : [],\n)\n</script>\n\n<template>\n <seo-layout :base=\"props.base\" current=\"redirects\" :actions=\"actions\" @test=\"test({})\">\n <wx-table\n :data=\"page\"\n :columns=\"columns\"\n row-key=\"id\"\n searchable\n :clickable=\"canManage\"\n :hover=\"canManage\"\n flush\n :loading=\"loading\"\n :search-placeholder=\"t('page.search-redirects')\"\n :empty-text=\"t('page.empty')\"\n @row-click=\"open\"\n @state-change=\"load\"\n >\n <template #cell-pattern=\"{ row }\">\n <wx-text mono size=\"sm\">{{ row.pattern }}</wx-text>\n <!-- Said out loud rather than refused on save: the middleware steps over it, and a\n row that quietly does nothing is a row nobody ever fixes. -->\n <wx-badge v-if=\"row.is_loop\" type=\"warning\">{{ t('page.loop') }}</wx-badge>\n </template>\n\n <template #cell-target=\"{ row }\">\n <wx-text mono size=\"sm\">{{ row.target }}</wx-text>\n </template>\n\n <template #cell-last_hit_at=\"{ row }\">\n <wx-date :value=\"row.last_hit_at\" :tone=\"row.last_hit_at ? 'default' : 'muted'\" />\n </template>\n\n <template #cell-is_active=\"{ row }\">\n <wx-badge :type=\"row.is_active ? 'success' : 'default'\" dot>\n {{ row.is_active ? t('page.active') : t('page.inactive') }}\n </wx-badge>\n </template>\n\n <template #card-actions=\"{ row }\">\n <wx-row-menu v-if=\"canManage\" :actions=\"actionsFor(row)\" :label=\"row.pattern\" />\n </template>\n\n <template #cell-actions=\"{ row }\">\n <wx-row-menu :actions=\"actionsFor(row)\" :label=\"row.pattern\" />\n </template>\n </wx-table>\n </seo-layout>\n</template>\n","import { localizedValue, type LocalizedValue } from '@webx-ui/core'\nimport type { SeoUrlInput, SeoUrlRule, SeoValue } from './types'\n\n/** The fields the card owns, as opposed to the ones the rule form owns. */\nconst SEO_FIELDS = [\n 'title',\n 'h1',\n 'description',\n 'keywords',\n 'og_title',\n 'og_description',\n 'og_image',\n 'canonical',\n 'robots',\n 'json_ld',\n] as const\n\n/**\n * What a rule is called in a table.\n *\n * Any language rather than the current one: a record half translated is the normal state of a\n * site being worked on, and a blank cell says less than the language that does have the words.\n */\nexport function ruleTitle(rule: SeoUrlRule, locale?: string): string {\n return localizedValue(rule.title as LocalizedValue | undefined, locale)\n}\n\n/** The SEO half of a rule, for the card to edit. */\nexport function seoOf(rule: SeoUrlRule | null): SeoValue {\n if (!rule) return {}\n\n const value: SeoValue = {}\n\n for (const key of SEO_FIELDS) {\n const one = rule[key]\n\n if (one !== null && one !== undefined) {\n Object.assign(value, { [key]: one })\n }\n }\n\n return value\n}\n\n/**\n * What the form sends: the rule's own fields, with the card's value spread back over them.\n *\n * Every field of the card goes, present or not — a `PUT` replaces the whole rule, so a\n * description somebody cleared has to arrive as nothing rather than not arrive at all.\n */\nexport function ruleInput(\n own: Pick<SeoUrlInput, 'match_type' | 'pattern' | 'priority' | 'is_active'>,\n seo: SeoValue,\n): SeoUrlInput {\n const input: SeoUrlInput = { ...own }\n\n for (const key of SEO_FIELDS) {\n Object.assign(input, { [key]: seo[key] ?? null })\n }\n\n return input\n}\n","<script setup lang=\"ts\">\nimport { computed, ref, watch, type Component } from 'vue'\nimport { useAdmin, useErrorText, useTranslate } from '@webx-ui/module-admin'\nimport {\n toast,\n useModal,\n WxButton,\n WxDialog,\n WxFormItem,\n WxInput,\n WxInputNumber,\n WxSelect,\n WxSpace,\n WxSwitch,\n} from '@webx-ui/core'\nimport SeoCard from './SeoCard.vue'\nimport { createSeoApi } from './api'\nimport { useSeoMessages } from './i18n'\nimport { ruleInput, seoOf } from './rule'\nimport type { MatchType, SeoUrlRule, SeoValue } from './types'\n\n/**\n * One rule: which addresses it covers, and what it says about them.\n *\n * A dialog rather than a second route, and a template rather than a described screen — the same\n * decision `admins.form` is on, until the screens mechanism has been round the block a few more\n * times. What it edits below the address is `WxSeo`, the same card a content module will get by\n * patching it into its own form.\n */\nconst props = withDefaults(defineProps<{ rule?: SeoUrlRule | null; mediaField?: Component }>(), {\n rule: null,\n mediaField: undefined,\n})\n\nconst { resolve, dismiss, open } = useModal<SeoUrlRule>()\n\nconst context = useAdmin()\nconst api = createSeoApi(context)\nuseSeoMessages()\n\nconst t = useTranslate('webx-seo')\n/* Not the server's `message`: the panel says how a request failed in its own words (§13.3). */\nconst message = useErrorText()\n\nconst saving = ref(false)\nconst errors = ref<Record<string, string[]>>({})\n\nconst form = ref({\n match_type: 'exact' as MatchType,\n pattern: '',\n priority: 0,\n is_active: true,\n})\n\nconst seo = ref<SeoValue>({})\n\nconst editing = computed(() => props.rule !== null)\n\nconst kindOptions = computed(() => [\n { value: 'exact', label: t('page.exact') },\n { value: 'mask', label: t('page.mask') },\n { value: 'regex', label: t('page.regex') },\n])\n\nwatch(\n () => props.rule,\n (rule) => {\n form.value = {\n match_type: rule?.match_type ?? 'exact',\n pattern: rule?.pattern ?? '',\n priority: rule?.priority ?? 0,\n is_active: rule?.is_active ?? true,\n }\n seo.value = seoOf(rule)\n errors.value = {}\n },\n { immediate: true },\n)\n\nfunction errorOf(field: string): string | undefined {\n return errors.value[field]?.[0]\n}\n\nasync function save(): Promise<void> {\n saving.value = true\n errors.value = {}\n\n const input = ruleInput(form.value, seo.value)\n\n try {\n const saved = props.rule\n ? await api.updateUrl(props.rule.id, input)\n : await api.createUrl(input)\n\n toast.success(t('page.saved'))\n resolve(saved)\n } catch (error) {\n const body = (error as { body?: { errors?: Record<string, string[]>; message?: string } }).body\n\n if (body?.errors) {\n errors.value = body.errors\n toast.danger(t('page.failed'))\n } else {\n toast.danger(message(error, t('page.failed')))\n }\n } finally {\n saving.value = false\n }\n}\n</script>\n\n<template>\n <wx-dialog v-model:open=\"open\" :title=\"t('page.rule')\" :width=\"860\">\n <div class=\"wx-seo-rule\">\n <div class=\"wx-seo-rule__where\">\n <wx-form-item :label=\"t('page.kind')\">\n <wx-select v-model=\"form.match_type\" :options=\"kindOptions\" />\n </wx-form-item>\n\n <wx-form-item\n :label=\"t('page.address')\"\n :help=\"t('page.address-help')\"\n :error=\"errorOf('pattern')\"\n class=\"wx-seo-rule__address\"\n >\n <wx-input v-model=\"form.pattern\" placeholder=\"/catalog/shoes\" />\n </wx-form-item>\n\n <wx-form-item :label=\"t('page.priority')\" :help=\"t('page.priority-help')\">\n <wx-input-number v-model=\"form.priority\" :step=\"10\" />\n </wx-form-item>\n\n <wx-form-item :label=\"t('page.state')\">\n <wx-switch v-model=\"form.is_active\" />\n </wx-form-item>\n </div>\n\n <seo-card v-model=\"seo\" :media-field=\"props.mediaField\" />\n </div>\n\n <template #footer>\n <wx-space size=\"sm\">\n <wx-button variant=\"outline\" @click=\"dismiss()\">{{ t('page.cancel') }}</wx-button>\n <wx-button type=\"primary\" :loading=\"saving\" @click=\"save\">\n {{ editing ? t('page.save') : t('page.new-rule') }}\n </wx-button>\n </wx-space>\n </template>\n </wx-dialog>\n</template>\n\n<style scoped>\n.wx-seo-rule {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-16);\n container-type: inline-size;\n}\n\n/* The address and its kind on one line: they are one decision, and reading them apart is\n reading half of it. */\n.wx-seo-rule__where {\n display: grid;\n grid-template-columns: 180px 1fr 140px 100px;\n gap: var(--wx-space-12);\n align-items: start;\n}\n\n@container (max-width: 720px) {\n .wx-seo-rule__where {\n grid-template-columns: 1fr 1fr;\n }\n\n .wx-seo-rule__address {\n grid-column: 1 / -1;\n }\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, ref, type Component } from 'vue'\nimport {\n useAdmin,\n useErrorText,\n useTranslate,\n WxFilterChips,\n type AppliedFilter,\n rowMenuWidth,\n WxRowMenu,\n type RowAction,\n type ScreenAction,\n} from '@webx-ui/module-admin'\nimport {\n confirm,\n createModal,\n toast,\n WxBadge,\n WxFormItem,\n WxSelect,\n WxTable,\n WxText,\n type TableColumn,\n type TableState,\n} from '@webx-ui/core'\nimport SeoUrlDialog from './SeoUrlDialog.vue'\nimport TestUrlDialog from './TestUrlDialog.vue'\nimport SeoLayout from './SeoLayout.vue'\nimport { createSeoApi } from './api'\nimport { useSeoMessages } from './i18n'\nimport { ruleTitle } from './rule'\nimport type { SeoPage, SeoUrlRule } from './types'\n\n/**\n * The rules written for addresses.\n *\n * Listed in the order the site tries them — exact, then mask, then regular expression, by\n * priority inside each group — so that reading the table top to bottom is reading what will\n * happen. Sorting it any other way would hide the one thing a person comes here to work out.\n */\nconst props = withDefaults(defineProps<{ base?: string; mediaField?: Component }>(), {\n base: '/seo',\n mediaField: undefined,\n})\n\nconst context = useAdmin()\nconst api = createSeoApi(context)\nuseSeoMessages()\n\nconst t = useTranslate('webx-seo')\n/* The funnel and \"reset all\" are the panel's own words. */\nconst panel = useTranslate('webx-admin')\n/* Not the server's `message`: the panel says how a request failed in its own words (§13.3). */\nconst message = useErrorText()\n\nconst page = ref<SeoPage<SeoUrlRule> | null>(null)\nconst loading = ref(false)\n/* A word rather than an empty string: a select reads \"\" as nothing chosen and shows a blank\n control where the option said \"any kind\". */\nconst kind = ref('any')\n\nlet last: TableState = { page: 1, perPage: 20, sort: null, search: '' }\n\nconst canManage = context.can('seo.manage')\n\nconst edit = createModal<SeoUrlRule, { rule: SeoUrlRule | null; mediaField?: Component }>(\n SeoUrlDialog,\n)\nconst test = createModal<void, Record<string, never>>(TestUrlDialog)\n\nconst kindOptions = computed(() => [\n { value: 'any', label: t('page.any-kind') },\n { value: 'exact', label: t('page.exact') },\n { value: 'mask', label: t('page.mask') },\n { value: 'regex', label: t('page.regex') },\n])\n\n/** The one dropdown, said in the reader's words while the panel it lives in is shut. */\nconst applied = computed<AppliedFilter[]>(() =>\n kind.value === 'any'\n ? []\n : [\n {\n key: 'kind',\n label: `${t('page.filter-kind')}: ${\n kindOptions.value.find((option) => option.value === kind.value)?.label ?? kind.value\n }`,\n clear: () => (kind.value = 'any'),\n },\n ],\n)\n\nconst columns = computed<TableColumn<SeoUrlRule>[]>(() => [\n { key: 'pattern', label: t('page.address') },\n { key: 'match_type', label: t('page.kind'), hideBelow: 560 },\n { key: 'title', label: t('page.title'), hideBelow: 900 },\n { key: 'priority', label: t('page.priority'), align: 'center', hideBelow: 760 },\n { key: 'is_active', label: t('page.state'), align: 'center', hideBelow: 660 },\n {\n key: 'actions',\n label: '',\n width: rowMenuWidth,\n align: 'right',\n hidden: !canManage,\n hideOnCards: true,\n },\n])\n\n/**\n * What a rule offers: one line, and it is the destructive one — everything else about a rule is\n * done by opening it. Still a menu, because a red bin standing in every row shouts louder than\n * deleting a rule deserves, and because a reader should find a record's actions in the same\n * place in every list of the panel (§20).\n */\nfunction actionsFor(rule: SeoUrlRule): RowAction[] {\n return [\n {\n key: 'delete',\n icon: 'trash',\n label: t('page.delete'),\n danger: true,\n run: () => remove(rule),\n },\n ]\n}\n\nasync function load(state: TableState): Promise<void> {\n last = state\n loading.value = true\n\n try {\n page.value = await api.urls({\n q: state.search,\n match_type: kind.value === 'any' ? null : (kind.value as 'exact' | 'mask' | 'regex'),\n page: state.page,\n per_page: state.perPage,\n })\n } finally {\n loading.value = false\n }\n}\n\n/**\n * Opening a rule is editing it, so a reader who may not manage these has nowhere to go: the\n * table is told so (`:clickable=\"canManage\"`) and withholds the pointer, the highlight and the\n * click together, rather than promising a dialog that would not open (§13).\n */\nasync function open(rule: SeoUrlRule | null): Promise<void> {\n const saved = await edit({ rule, mediaField: props.mediaField })\n\n if (saved) void load(last)\n}\n\nasync function remove(rule: SeoUrlRule): Promise<void> {\n const agreed = await confirm({\n title: t('page.delete-title', { pattern: rule.pattern }),\n message: t('page.delete-text'),\n confirmText: t('page.delete'),\n cancelText: t('page.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n try {\n await api.removeUrl(rule.id)\n toast.success(t('page.deleted'))\n void load(last)\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\n/* What the section offers. Declared, because on a phone the head folds it into the ···. */\nconst actions = computed<ScreenAction[]>(() =>\n canManage\n ? [\n {\n key: 'rule',\n label: t('page.new-rule'),\n icon: 'plus',\n primary: true,\n run: () => void open(null),\n },\n ]\n : [],\n)\n</script>\n\n<template>\n <seo-layout :base=\"props.base\" current=\"rules\" :actions=\"actions\" @test=\"test({})\">\n <wx-table\n :data=\"page\"\n :columns=\"columns\"\n row-key=\"id\"\n searchable\n :clickable=\"canManage\"\n :hover=\"canManage\"\n flush\n :loading=\"loading\"\n :search-placeholder=\"t('page.search-rules')\"\n :empty-text=\"t('page.empty')\"\n :filters-count=\"applied.length\"\n :filters-label=\"panel('filters.title')\"\n @row-click=\"open\"\n @state-change=\"load\"\n >\n <!-- Behind the funnel, and what it is set to comes back as a chip beside it. -->\n <template #filters>\n <wx-form-item :label=\"t('page.filter-kind')\">\n <wx-select v-model=\"kind\" :options=\"kindOptions\" size=\"sm\" />\n </wx-form-item>\n </template>\n\n <template #applied>\n <wx-filter-chips :filters=\"applied\" />\n </template>\n\n <template #cell-pattern=\"{ row }\">\n <wx-text mono size=\"sm\">{{ row.pattern }}</wx-text>\n </template>\n\n <template #cell-match_type=\"{ row }\">\n <wx-badge>{{ t(`page.${row.match_type}`) }}</wx-badge>\n </template>\n\n <template #cell-title=\"{ row }\">\n <wx-text v-if=\"ruleTitle(row)\" size=\"sm\">{{ ruleTitle(row) }}</wx-text>\n <wx-text v-else size=\"sm\" tone=\"muted\">—</wx-text>\n </template>\n\n <template #cell-is_active=\"{ row }\">\n <wx-badge :type=\"row.is_active ? 'success' : 'default'\" dot>\n {{ row.is_active ? t('page.active') : t('page.inactive') }}\n </wx-badge>\n </template>\n\n <template #card-actions=\"{ row }\">\n <wx-row-menu v-if=\"canManage\" :actions=\"actionsFor(row)\" :label=\"row.pattern\" />\n </template>\n\n <template #cell-actions=\"{ row }\">\n <wx-row-menu :actions=\"actionsFor(row)\" :label=\"row.pattern\" />\n </template>\n </wx-table>\n </seo-layout>\n</template>\n","import { markRaw, type Component } from 'vue'\nimport type { AdminModule } from '@webx-ui/module-admin'\nimport SeoAliasesPage from './SeoAliasesPage.vue'\nimport SeoCard from './SeoCard.vue'\nimport SeoRedirectsPage from './SeoRedirectsPage.vue'\nimport SeoUrlsPage from './SeoUrlsPage.vue'\n\nexport interface SeoOptions {\n /** Where the section lives inside the panel. */\n path?: string\n /**\n * The field that picks a share image — `WxMediaField` from `@webx-ui/module-media`.\n *\n * Handed in rather than imported, so this package does not depend on the library being\n * installed. Without it every other SEO field still works; only the picture is missing.\n */\n mediaField?: Component\n}\n\n/**\n * SEO as a section of the panel, and `wx-seo` as a field any screen can use.\n *\n * The id matches the module the server reports, which is what makes the entry appear in the\n * navigation: a section shows up when both halves are installed, and stays out of the way when\n * only one is.\n */\nexport function seo(options: SeoOptions = {}): AdminModule {\n const path = options.path ?? '/seo'\n /* A component kept in an options object arrives as a reactive proxy; Vue warns about one and\n `h()` wants the raw thing anyway. */\n const mediaField = options.mediaField ? markRaw(options.mediaField) : undefined\n\n return {\n id: 'seo',\n path,\n routes: [\n // The section's own path travels as a prop, so a panel that mounted it somewhere else\n // still links between its two screens correctly.\n {\n path,\n name: 'webx.seo',\n component: SeoUrlsPage,\n props: { base: path, mediaField },\n },\n {\n path: `${path}/redirects`,\n name: 'webx.seo.redirects',\n component: SeoRedirectsPage,\n props: { base: path },\n },\n // The trail of renames, from `webx-ui/routing`. The screen is here whether or not any\n // content module has registered a type yet: without one the table is simply empty, and an\n // empty table is a better answer than a tab that appears one day without explanation.\n {\n path: `${path}/aliases`,\n name: 'webx.seo.aliases',\n component: SeoAliasesPage,\n props: { base: path },\n },\n ],\n // What a screen means by `wx-seo`: everything a page says about itself, as one value. The\n // server registers the same name for what it stores.\n types: {\n 'wx-seo': {\n component: SeoCard,\n kind: 'field',\n wide: true,\n bind: () => ({ mediaField }),\n },\n },\n }\n}\n"],"names":["seoMessages","seeded","useSeoMessages","i18n","useAdmin","props","__props","emit","__emit","context","router","useRouter","route","useRoute","t","useTranslate","title","computed","module","views","actions","where","next","path","_createBlock","_unref","WxListScreen","$event","_renderSlot","_ctx","createSeoApi","admin","base","data","body","page","listQuery","query","id","input","url","locale","open","useModal","api","ref","running","result","fields","seo","one","run","WxDialog","_createElementVNode","_hoisted_1","_createVNode","WxSpace","WxInput","WxButton","_createElementBlock","_Fragment","WxAlert","_openBlock","_hoisted_2","WxText","WxBadge","_createTextVNode","_toDisplayString","_hoisted_3","WxDescriptions","_renderList","WxDescriptionsItem","_hoisted_4","step","WxEmpty","loading","test","createModal","TestUrlDialog","multilingual","columns","load","state","SeoLayout","WxTable","_withCtx","row","WxLink","WxDate","robotsDirectives","value","_useModel","locales","useLocales","jsonLdText","jsonLdBroken","field","key","h1","description","keywords","ogTitle","ogDescription","canonical","image","directives","kept","known","merged","robotsOptions","jsonLd","stored","inLocale","map","localizedValue","counted","preview","hasPreview","keptHelp","tone","count","limit","WxTabs","WxTab","WxFormItem","WxTextarea","_hoisted_5","_hoisted_6","_hoisted_7","_hoisted_8","_hoisted_9","_hoisted_10","_resolveDynamicComponent","_hoisted_11","WxCheckboxGroup","WxCodeEditor","resolve","dismiss","message","useErrorText","saving","errors","occupant","form","editing","kindOptions","statusOptions","watch","redirect","shadowed","pattern","errorOf","save","saved","toast","error","WxSelect","_cache","WxSwitch","last","canManage","edit","SeoRedirectDialog","rowMenuWidth","actionsFor","remove","confirm","WxRowMenu","SEO_FIELDS","ruleTitle","rule","seoOf","ruleInput","own","WxInputNumber","SeoCard","panel","kind","SeoUrlDialog","applied","option","WxFilterChips","options","mediaField","markRaw","SeoUrlsPage","SeoRedirectsPage","SeoAliasesPage"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../src/messages.ts","../src/i18n.ts","../src/SeoLayout.vue","../src/api.ts","../src/TestUrlDialog.vue","../src/SeoAliasesPage.vue","../src/types.ts","../src/SeoCard.vue","../src/SeoRedirectDialog.vue","../src/SeoRedirectsPage.vue","../src/rule.ts","../src/SeoUrlDialog.vue","../src/SeoUrlsPage.vue","../src/module.ts"],"sourcesContent":["import type { Messages } from '@webx-ui/module-admin'\n\n/**\n * What this package says, in English — the same keys `webx-ui/module-seo` ships as\n * `lang/en/*.php`, so a key never reaches the screen when a translation has not arrived yet.\n *\n * The server's dictionary wins over these. There is no second dictionary inside this package:\n * a module translated into ten languages with an English dialog in the middle of it is worse\n * than one that is not translated at all.\n *\n * Placeholders are Laravel's, `:like-this` — the same line is read from a PHP file and from\n * here, and only one of the two spellings can be right.\n */\nexport const seoMessages: Record<string, Messages> = {\n module: {\n title: 'SEO',\n },\n\n page: {\n rules: 'Rules',\n redirects: 'Redirects',\n test: 'Check an address',\n 'filter-kind': 'Kind',\n\n 'new-rule': 'New rule',\n 'new-redirect': 'New redirect',\n 'search-rules': 'Search by address',\n 'search-redirects': 'Search by address or destination',\n empty: 'Nothing here yet.',\n\n address: 'Address',\n kind: 'Kind',\n priority: 'Priority',\n state: 'State',\n active: 'Active',\n inactive: 'Off',\n title: 'Title',\n target: 'Destination',\n status: 'Code',\n hits: 'Hits',\n 'last-hit': 'Last used',\n loop: 'Points at itself',\n\n 'any-kind': 'Any kind',\n exact: 'Exact',\n mask: 'Mask',\n regex: 'Regular expression',\n\n rule: 'Rule',\n redirect: 'Redirect',\n save: 'Save',\n cancel: 'Cancel',\n delete: 'Delete',\n saved: 'Saved.',\n deleted: 'Deleted.',\n failed: 'Some values were not accepted. Check the highlighted fields.',\n 'delete-title': 'Delete :pattern?',\n 'delete-text': 'This cannot be undone.',\n\n 'address-help':\n 'A path with its query string. In a mask, * is one segment and ** is any number of them; a regular expression is stored as written, delimiters and all.',\n 'target-help':\n 'A path on this site, or a full address elsewhere. $1 puts back what a mask caught.',\n 'priority-help': 'Among rules of the same kind, the higher number wins.',\n\n 'test-placeholder': '/catalog/shoes?page=2',\n 'test-run': 'Check',\n 'test-empty': 'Type an address to see what the site will say about it.',\n 'test-matched': 'Matched rule',\n 'test-none': 'No rule matches this address.',\n 'test-redirected': 'This address is redirected to :target with a :status.',\n 'test-result': 'What the page will say',\n 'test-chain': 'Where each part came from',\n\n automatic: 'Automatic',\n 'search-aliases': 'Search by address or destination',\n 'aliases-empty': 'Nothing has moved yet.',\n 'aliases-help':\n 'The site writes these itself: renaming or moving a page leaves its old address behind answering 301, so nothing that linked to it dies. They cannot be edited here — a redirect of your own is tried first and wins.',\n language: 'Language',\n 'moved-at': 'Moved',\n gone: 'Leads nowhere',\n occupied:\n 'A page of the site answers at :path. A redirect is tried before it, so the page stops being reachable there.',\n 'occupied-alias':\n 'This address already leads to :target — what a move left behind. A redirect written here is tried first.',\n },\n\n card: {\n 'section-page': 'The page',\n 'section-share': 'Sharing',\n 'section-advanced': 'Advanced',\n\n title: 'Title',\n h1: 'Heading',\n description: 'Description',\n keywords: 'Keywords',\n canonical: 'Canonical address',\n 'canonical-help': 'The address this page should be indexed under, when it is not its own.',\n\n robots: 'Indexing',\n 'robots-kept': 'Also kept, as written: :directives',\n noindex: 'Keep out of the index',\n nofollow: 'Do not follow the links',\n noarchive: 'No cached copy',\n nosnippet: 'No snippet',\n noimageindex: 'Do not index the images',\n\n 'og-title': 'Share title',\n 'og-description': 'Share description',\n 'og-image': 'Share image',\n 'og-help': 'Left empty, a share preview uses the title and description above.',\n\n 'json-ld': 'Structured data',\n 'json-ld-help':\n 'A JSON-LD object, or a list of them. What the site says about itself comes from the settings; this is for a page that needs markup of its own.',\n 'json-ld-invalid': 'This is not valid JSON.',\n\n preview: 'In search results',\n 'share-preview': 'When shared',\n 'share-auto': 'The site fills this in — the record’s own picture, or the default one',\n },\n}\n","import { useAdmin } from '@webx-ui/module-admin'\nimport { seoMessages } 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 useSeoMessages(): void {\n try {\n const { i18n } = useAdmin()\n\n if (!seeded.has(i18n)) {\n i18n.defaults('webx-seo', seoMessages)\n seeded.add(i18n)\n }\n } catch {\n // Outside a panel there is no dictionary to seed, and `useTranslate` falls back on its own.\n }\n}\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { useRoute, useRouter } from 'vue-router'\nimport { useAdmin, useTranslate, WxListScreen, type ScreenAction } from '@webx-ui/module-admin'\nimport type { TabItem, TabValue } from '@webx-ui/core'\nimport { useSeoMessages } from './i18n'\n\n/**\n * The head of the section: its name, the views of what it holds, and the one tool that belongs\n * to the whole of it.\n *\n * Rules, redirects and the trail of renames are three routes rather than three panels of one,\n * because they are three tables with their own paging and their own search — and a panel that\n * quietly resets both when you come back to it is worse than a second address. What they share\n * is the frame: one heading, one strip of tabs, one card under it (§19).\n */\nconst props = defineProps<{\n base: string\n current: 'rules' | 'redirects' | 'aliases'\n /** The view's own main action — `New rule`. The section's own tool is added after it. */\n actions?: ScreenAction[]\n}>()\n\nconst emit = defineEmits<{ test: [] }>()\n\ndefineSlots<{\n /** The table. */\n default?: () => unknown\n}>()\n\nconst context = useAdmin()\nconst router = useRouter()\nconst route = useRoute()\n\nuseSeoMessages()\n\nconst t = useTranslate('webx-seo')\n\n/** The section's name as the server translated it; the built-in English until it arrives. */\nconst title = computed(\n () =>\n context.state.manifest?.modules.find((module) => module.id === 'seo')?.title ??\n t('module.title'),\n)\n\nconst views = computed<TabItem[]>(() => [\n { value: 'rules', label: t('page.rules') },\n { value: 'redirects', label: t('page.redirects') },\n { value: 'aliases', label: t('page.automatic') },\n])\n\n/* The one tool that belongs to the whole section, after whatever the view itself offers. */\nconst actions = computed<ScreenAction[]>(() => [\n ...(props.actions ?? []),\n { key: 'test', label: t('page.test'), icon: 'search', run: () => emit('test') },\n])\n\nconst where = computed<TabValue>({\n get: () => props.current,\n set: (next) => {\n const path = next === 'rules' ? props.base : `${props.base}/${String(next)}`\n\n if (path !== route.path) void router.push(path)\n },\n})\n</script>\n\n<template>\n <wx-list-screen v-model:view=\"where\" :title=\"title\" :views=\"views\" :actions=\"actions\">\n <slot />\n </wx-list-screen>\n</template>\n","import type { AdminContext } from '@webx-ui/module-admin'\nimport type {\n SeoAlias,\n SeoAliasQuery,\n SeoPage,\n SeoRedirect,\n SeoRedirectInput,\n SeoRedirectQuery,\n SeoTestResult,\n SeoUrlInput,\n SeoUrlQuery,\n SeoUrlRule,\n} from './types'\n\nexport interface SeoApi {\n /** The rules, in the order the site tries them. */\n urls(query?: SeoUrlQuery): Promise<SeoPage<SeoUrlRule>>\n url(id: number): Promise<SeoUrlRule>\n createUrl(input: SeoUrlInput): Promise<SeoUrlRule>\n updateUrl(id: number, input: SeoUrlInput): Promise<SeoUrlRule>\n removeUrl(id: number): Promise<void>\n\n redirects(query?: SeoRedirectQuery): Promise<SeoPage<SeoRedirect>>\n createRedirect(input: SeoRedirectInput): Promise<SeoRedirect>\n updateRedirect(id: number, input: SeoRedirectInput): Promise<SeoRedirect>\n removeRedirect(id: number): Promise<void>\n\n /** The redirects nobody wrote: what the address registry kept after a rename. Read only. */\n aliases(query?: SeoAliasQuery): Promise<SeoPage<SeoAlias>>\n\n /** What an address ends up saying, and where every part of it came from. */\n test(url: string, locale?: string | null): Promise<SeoTestResult>\n}\n\n/** Everything under `/seo`, below the panel's API path. */\nexport function createSeoApi(admin: AdminContext): SeoApi {\n const base = `${admin.apiPath}/seo`\n const data = <T>(body: { data: T }): T => body.data\n\n /* A paginated list arrives as a resource collection — rows under `data`, numbers under\n `meta`. The table wants them on one object, so they are joined here once rather than in\n every screen. */\n const page = <T>(body: { data: T[]; meta: Omit<SeoPage<T>, 'data'> }): SeoPage<T> => ({\n ...body.meta,\n data: body.data,\n })\n\n /* `undefined` drops the parameter; `null` and `''` would be sent as words and read as a\n filter nobody asked for. */\n const listQuery = (\n query: SeoRedirectQuery,\n ): Record<string, string | number | boolean | null | undefined> => ({\n q: query.q || undefined,\n match_type: query.match_type ?? undefined,\n is_active: query.is_active ?? undefined,\n sort: query.sort || undefined,\n page: query.page,\n per_page: query.per_page,\n })\n\n return {\n urls: (query = {}) =>\n admin.http\n .get<{ data: SeoUrlRule[]; meta: Omit<SeoPage<SeoUrlRule>, 'data'> }>(`${base}/urls`, {\n query: listQuery(query),\n })\n .then(page),\n\n url: (id) => admin.http.get<{ data: SeoUrlRule }>(`${base}/urls/${id}`).then(data),\n\n createUrl: (input) => admin.http.post<{ data: SeoUrlRule }>(`${base}/urls`, input).then(data),\n\n updateUrl: (id, input) =>\n admin.http.put<{ data: SeoUrlRule }>(`${base}/urls/${id}`, input).then(data),\n\n removeUrl: (id) => admin.http.delete<void>(`${base}/urls/${id}`),\n\n redirects: (query = {}) =>\n admin.http\n .get<{ data: SeoRedirect[]; meta: Omit<SeoPage<SeoRedirect>, 'data'> }>(\n `${base}/redirects`,\n { query: listQuery(query) },\n )\n .then(page),\n\n createRedirect: (input) =>\n admin.http.post<{ data: SeoRedirect }>(`${base}/redirects`, input).then(data),\n\n updateRedirect: (id, input) =>\n admin.http.put<{ data: SeoRedirect }>(`${base}/redirects/${id}`, input).then(data),\n\n removeRedirect: (id) => admin.http.delete<void>(`${base}/redirects/${id}`),\n\n aliases: (query = {}) =>\n admin.http\n .get<{ data: SeoAlias[]; meta: Omit<SeoPage<SeoAlias>, 'data'> }>(`${base}/aliases`, {\n query: {\n q: query.q || undefined,\n locale: query.locale ?? undefined,\n page: query.page,\n per_page: query.per_page,\n },\n })\n .then(page),\n\n // A POST because it carries an address in its body, and an address in a query string is an\n // address somebody has to escape twice.\n test: (url, locale = null) =>\n admin.http\n .post<{ data: SeoTestResult }>(`${base}/test-url`, { url, locale: locale ?? undefined })\n .then(data),\n }\n}\n","<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\nimport { useAdmin, useTranslate } from '@webx-ui/module-admin'\nimport {\n WxAlert,\n WxBadge,\n WxButton,\n WxDescriptions,\n WxDescriptionsItem,\n WxDialog,\n WxEmpty,\n WxInput,\n WxSpace,\n WxText,\n useModal,\n} from '@webx-ui/core'\nimport { createSeoApi } from './api'\nimport { useSeoMessages } from './i18n'\nimport type { SeoTestResult } from './types'\n\n/**\n * \"Why does this page have the wrong title?\"\n *\n * The most common question this section gets, and the reason it should take one call to answer\n * rather than a reading of the code: which redirect catches the address, which rule matches it,\n * what every source contributed, and what the page ends up saying.\n */\nconst { open } = useModal<void>()\n\nconst context = useAdmin()\nconst api = createSeoApi(context)\nuseSeoMessages()\n\nconst t = useTranslate('webx-seo')\n\nconst url = ref('')\nconst running = ref(false)\nconst result = ref<SeoTestResult | null>(null)\n\nconst fields = computed(() => {\n const seo = result.value?.seo\n\n if (!seo) return []\n\n return [\n { label: t('card.title'), value: seo.title },\n { label: t('card.h1'), value: seo.h1 },\n { label: t('card.description'), value: seo.description },\n { label: t('card.keywords'), value: seo.keywords },\n { label: t('card.canonical'), value: seo.canonical },\n { label: t('card.robots'), value: seo.robots },\n ].filter((one) => one.value)\n})\n\nasync function run(): Promise<void> {\n if (url.value.trim() === '') return\n\n running.value = true\n\n try {\n result.value = await api.test(url.value)\n } finally {\n running.value = false\n }\n}\n</script>\n\n<template>\n <wx-dialog v-model:open=\"open\" :title=\"t('page.test')\" :width=\"720\">\n <div class=\"wx-seo-test\">\n <wx-space size=\"sm\" class=\"wx-seo-test__ask\">\n <wx-input\n v-model=\"url\"\n :placeholder=\"t('page.test-placeholder')\"\n class=\"wx-seo-test__input\"\n @keyup.enter=\"run\"\n />\n <wx-button type=\"primary\" :loading=\"running\" @click=\"run\">\n {{ t('page.test-run') }}\n </wx-button>\n </wx-space>\n\n <template v-if=\"result\">\n <!-- First, because it happens first: a redirected address never reaches the rules. -->\n <wx-alert\n v-if=\"result.redirect\"\n type=\"warning\"\n :description=\"\n t('page.test-redirected', {\n target: result.redirect.target,\n status: result.redirect.status,\n })\n \"\n />\n\n <!-- Who answers at this address at all. A rule of the site's own comes before any of\n this, so an address can be redirected away from a page that still exists. -->\n <wx-alert\n v-if=\"result.route\"\n type=\"info\"\n :description=\"\n result.route.kind === 'alias'\n ? t('page.occupied-alias', { target: result.route.target ?? result.route.path })\n : t('page.occupied', { path: result.route.path })\n \"\n />\n\n <wx-alert v-if=\"!result.matched\" type=\"info\" :description=\"t('page.test-none')\" />\n <div v-else class=\"wx-seo-test__matched\">\n <wx-text size=\"sm\" tone=\"muted\">{{ t('page.test-matched') }}</wx-text>\n <wx-space size=\"sm\" align=\"center\">\n <wx-badge>{{ t(`page.${result.matched.match_type}`) }}</wx-badge>\n <wx-text mono size=\"sm\">{{ result.matched.pattern }}</wx-text>\n </wx-space>\n </div>\n\n <div v-if=\"fields.length > 0\" class=\"wx-seo-test__result\">\n <wx-text size=\"sm\" tone=\"muted\">{{ t('page.test-result') }}</wx-text>\n <wx-descriptions :columns=\"1\" bordered size=\"sm\">\n <wx-descriptions-item v-for=\"one in fields\" :key=\"one.label\" :label=\"one.label\">\n {{ one.value }}\n </wx-descriptions-item>\n </wx-descriptions>\n </div>\n\n <div v-if=\"result.chain.length > 0\" class=\"wx-seo-test__chain\">\n <wx-text size=\"sm\" tone=\"muted\">{{ t('page.test-chain') }}</wx-text>\n <wx-space size=\"xs\" wrap>\n <wx-badge v-for=\"step in result.chain\" :key=\"step.source\">\n {{ step.source }} · {{ step.priority }}\n </wx-badge>\n </wx-space>\n </div>\n </template>\n\n <wx-empty v-else-if=\"!running\" icon=\"search\" :description=\"t('page.test-empty')\" />\n </div>\n </wx-dialog>\n</template>\n\n<style scoped>\n.wx-seo-test {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-16);\n}\n\n.wx-seo-test__ask {\n width: 100%;\n}\n\n.wx-seo-test__input {\n flex: 1 1 auto;\n}\n\n.wx-seo-test__matched,\n.wx-seo-test__result,\n.wx-seo-test__chain {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-6);\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\nimport { useTranslate, useAdmin, WxDate } from '@webx-ui/module-admin'\nimport {\n createModal,\n WxAlert,\n WxBadge,\n WxLink,\n WxTable,\n WxText,\n type TableColumn,\n type TableState,\n} from '@webx-ui/core'\nimport SeoLayout from './SeoLayout.vue'\nimport TestUrlDialog from './TestUrlDialog.vue'\nimport { createSeoApi } from './api'\nimport { useSeoMessages } from './i18n'\nimport type { SeoAlias, SeoPage } from './types'\n\n/**\n * The redirects nobody wrote.\n *\n * Renaming a page or moving a branch leaves its old address behind, answering 301 — which is\n * the only reason an external link, a bookmark or a search result survives an edit in the panel.\n * They are shown here because a reader who lands on a dead address does not care which half of\n * the system made the redirect, and an editor chasing one should not have to.\n *\n * Nothing on this screen writes. These rows belong to the entity that moved: it makes them, and\n * deleting it takes them with it. An editor who wants a different answer for an old address\n * writes a rule of their own on the next tab — those are tried first, and this one stops\n * mattering.\n */\nconst props = withDefaults(defineProps<{ base?: string }>(), { base: '/seo' })\n\nconst context = useAdmin()\nconst api = createSeoApi(context)\nuseSeoMessages()\n\nconst t = useTranslate('webx-seo')\n\nconst page = ref<SeoPage<SeoAlias> | null>(null)\nconst loading = ref(false)\n\nconst test = createModal<void, Record<string, never>>(TestUrlDialog)\n\n/** More than one language on the site is the only reason to say which one a row is in. */\nconst multilingual = computed(() => (context.state.manifest?.locales?.length ?? 1) > 1)\n\nconst columns = computed<TableColumn<SeoAlias>[]>(() => [\n { key: 'pattern', label: t('page.address'), sortable: false },\n { key: 'target', label: t('page.target') },\n {\n key: 'locale',\n label: t('page.language'),\n align: 'center',\n hidden: !multilingual.value,\n hideBelow: 760,\n },\n { key: 'created_at', label: t('page.moved-at'), hideBelow: 900, hideOnCards: true },\n])\n\nasync function load(state: TableState): Promise<void> {\n loading.value = true\n\n try {\n page.value = await api.aliases({\n q: state.search,\n page: state.page,\n per_page: state.perPage,\n })\n } finally {\n loading.value = false\n }\n}\n</script>\n\n<template>\n <seo-layout :base=\"props.base\" current=\"aliases\" @test=\"test({})\">\n <wx-alert\n type=\"info\"\n variant=\"soft\"\n :description=\"t('page.aliases-help')\"\n class=\"wx-seo-aliases__note\"\n />\n\n <wx-table\n :data=\"page\"\n :columns=\"columns\"\n row-key=\"id\"\n searchable\n flush\n :loading=\"loading\"\n :search-placeholder=\"t('page.search-aliases')\"\n :empty-text=\"t('page.aliases-empty')\"\n @state-change=\"load\"\n >\n <template #cell-pattern=\"{ row }\">\n <wx-text mono size=\"sm\">{{ row.pattern }}</wx-text>\n </template>\n\n <!-- The address it leads to now, as a link: this is the one place in the panel where an\n editor can walk from a dead address to the live page without knowing the section it\n lives in. -->\n <template #cell-target=\"{ row }\">\n <wx-link v-if=\"row.target && row.target_url\" :href=\"row.target_url\" external size=\"sm\">\n <wx-text mono size=\"sm\">{{ row.target }}</wx-text>\n </wx-link>\n <wx-badge v-else type=\"warning\">{{ t('page.gone') }}</wx-badge>\n </template>\n\n <template #cell-locale=\"{ row }\">\n <wx-badge>{{ row.locale }}</wx-badge>\n </template>\n\n <template #cell-created_at=\"{ row }\">\n <wx-date v-if=\"row.created_at\" :value=\"row.created_at\" />\n </template>\n </wx-table>\n </seo-layout>\n</template>\n\n<style scoped>\n.wx-seo-aliases__note {\n margin-bottom: var(--wx-space-12);\n}\n</style>\n","import type { LocalizedValue } from '@webx-ui/core'\n\n/** The shapes `webx-ui/module-seo` answers with, and what its forms send back. */\n\n/** How a pattern is compared with an address. */\nexport type MatchType = 'exact' | 'mask' | 'regex'\n\n/** What `wx-media` stores, plus the address the server works out on every read. */\nexport interface SeoImage {\n path: string\n alt?: string\n title?: string\n url?: string | null\n}\n\n/**\n * What a page says about itself.\n *\n * The text fields are language maps — the same `{ en: '…', ru: '…' }` every localized field in\n * the panel uses. The picture is not: there are no per-language images anywhere yet, and a\n * column that already holds an object would read `path` as a language code.\n */\nexport interface SeoFields {\n title: LocalizedValue\n h1: LocalizedValue\n description: LocalizedValue\n keywords: LocalizedValue\n og_title: LocalizedValue\n og_description: LocalizedValue\n og_image: SeoImage | null\n canonical: string | null\n robots: string | null\n /** A JSON-LD object, a list of them, or null. */\n json_ld: unknown\n}\n\n/**\n * The value of a `wx-seo` field: the same fields, none of them required.\n *\n * A record that has never been given any SEO stores nothing rather than a shape full of nulls.\n */\nexport type SeoValue = Partial<SeoFields>\n\nexport interface SeoUrlRule extends SeoFields {\n id: number\n match_type: MatchType\n pattern: string\n priority: number\n is_active: boolean\n created_at: string | null\n updated_at: string | null\n /**\n * What a table row is, as far as `WxTable` is concerned. Without it this type does not\n * extend `TableRow` and every cell slot hands back `unknown`.\n */\n [key: string]: unknown\n}\n\n/** A `PUT` replaces the whole rule: the form edits every field at once. */\nexport interface SeoUrlInput extends SeoValue {\n match_type: MatchType\n pattern: string\n priority?: number\n is_active?: boolean\n}\n\nexport interface SeoRedirect {\n id: number\n match_type: MatchType\n pattern: string\n target: string\n status: number\n is_active: boolean\n hits: number\n last_hit_at: string | null\n /** The server says so rather than refusing the row: a loop is skipped, not rejected. */\n is_loop: boolean\n created_at: string | null\n updated_at: string | null\n [key: string]: unknown\n}\n\n/**\n * An address a rename left behind, from `webx-ui/routing`.\n *\n * The same words a manual redirect uses where the two overlap, so one screen can show both\n * without learning two vocabularies. What it has instead of the rest is an entity: these are\n * written by whatever moved, always exact, always 301, and never edited here.\n */\nexport interface SeoAlias {\n id: number\n locale: string\n /** The old address. */\n pattern: string\n /** The address it leads to now; null when the row it pointed at is gone. */\n target: string | null\n url: string\n target_url: string | null\n /** `page`, `article`, `product` — what the content module called itself. */\n entity_type: string\n entity_id: number\n created_at: string | null\n [key: string]: unknown\n}\n\nexport interface SeoAliasQuery {\n q?: string\n locale?: string | null\n page?: number\n per_page?: number\n}\n\n/**\n * What the address registry holds at an address, when something does.\n *\n * A rule written by hand is tried before any of it — that is deliberate — so this is a warning\n * and never a refusal: the page at this address is about to stop being reachable.\n */\nexport interface SeoRoute {\n path: string\n url: string\n kind: 'canonical' | 'alias'\n /** Where an old address leads now; null on a live page, which has nowhere to lead. */\n target: string | null\n entity_type: string\n entity_id: number\n /** False when a shorter address matched: the page at `/parts` answering for `/parts/bobcat`. */\n exact: boolean\n}\n\nexport interface SeoRedirectInput {\n match_type: MatchType\n pattern: string\n target: string\n status?: number\n is_active?: boolean\n}\n\n/**\n * A page, flat — what `->paginate()` serialises and `WxTable` reads as it arrives. A resource\n * collection nests the same numbers under `meta`; the client flattens them once, here.\n */\nexport interface SeoPage<T> {\n data: T[]\n current_page: number\n last_page: number\n per_page: number\n total: number\n from: number | null\n to: number | null\n}\n\nexport interface SeoUrlQuery {\n q?: string\n match_type?: MatchType | null\n is_active?: boolean | null\n page?: number\n per_page?: number\n}\n\nexport interface SeoRedirectQuery extends SeoUrlQuery {\n sort?: string\n}\n\n/** What a page ends up saying, every source merged. */\nexport interface SeoResolved {\n title: string | null\n h1: string | null\n description: string | null\n keywords: string | null\n canonical: string | null\n robots: string | null\n og: Record<string, string>\n json_ld: Record<string, unknown>[]\n}\n\n/** One source's contribution, before the merge. */\nexport interface SeoChainStep {\n source: string\n priority: number\n data: SeoResolved\n}\n\n/** The answer to \"why does this page say that\". */\nexport interface SeoTestResult {\n url: string\n /** Said first because it happens first: a redirected address never reaches the rules. */\n redirect: SeoRedirect | null\n /** What the address registry has here — a live page, or the trail of one that moved. */\n route: SeoRoute | null\n matched: SeoUrlRule | null\n chain: SeoChainStep[]\n seo: SeoResolved\n}\n\n/** The meta directives the card offers as checkboxes. Anything else is kept as written. */\nexport const robotsDirectives = [\n 'noindex',\n 'nofollow',\n 'noarchive',\n 'nosnippet',\n 'noimageindex',\n] as const\n\nexport type RobotsDirective = (typeof robotsDirectives)[number]\n","<script setup lang=\"ts\">\nimport { computed, inject, ref, type Component } from 'vue'\nimport { adminKey, useTranslate } from '@webx-ui/module-admin'\nimport {\n localizedValue,\n useLocales,\n WxCheckboxGroup,\n WxCodeEditor,\n WxFormItem,\n WxInput,\n WxTabs,\n WxTab,\n WxText,\n WxTextarea,\n type LocalizedValue,\n} from '@webx-ui/core'\nimport { useSeoMessages } from './i18n'\nimport { robotsDirectives, type SeoImage, type SeoValue } from './types'\n\n/**\n * What a page says about itself, as a field.\n *\n * Registered as `wx-seo`, so it can be dropped into any screen by a patch, and used directly by\n * the rule form in this package. The value is one object — the same shape the server stores —\n * rather than a dozen loose keys, because SEO is edited and reasoned about as a unit.\n *\n * The text fields are language maps and say so: the same chip every other localized field in the\n * panel grows, moving together with them.\n */\ndefineOptions({ name: 'WxSeo' })\n\nconst props = withDefaults(\n defineProps<{\n /**\n * The field that picks the share image — `WxMediaField` in a panel that has the library.\n *\n * Not imported: this package does not depend on the media one, and a panel without a library\n * still edits everything else about a page. Not usually handed in either — the card looks\n * `wx-media` up in the panel's own registry, which is where the module that has the library\n * puts it. This prop is for a caller that wants a different field than the registered one.\n */\n mediaField?: Component\n disabled?: boolean\n /**\n * Soft limits, for the counters only. Long is not wrong — search engines shorten what they\n * shorten — so nothing here refuses a longer line, it only says how long it is.\n */\n titleLimit?: number\n descriptionLimit?: number\n /** What the snippet preview shows above the title. */\n site?: string\n /** Hidden when a screen has no use for them. */\n structuredData?: boolean\n }>(),\n {\n mediaField: undefined,\n disabled: false,\n titleLimit: 60,\n descriptionLimit: 160,\n site: undefined,\n structuredData: true,\n },\n)\n\nconst value = defineModel<SeoValue | null>({ default: null })\n\nuseSeoMessages()\n\nconst t = useTranslate('webx-seo')\n\n/*\n * Injected rather than asked for with `useAdmin()`, which throws outside a panel: this card is\n * also mounted by the docs and by its own tests, and there it has to draw everything but the\n * picture rather than fail.\n */\nconst admin = inject(adminKey, null)\n\n/**\n * The field the picture is picked with: whatever this card was given, or `wx-media` as the panel\n * registered it.\n *\n * Looked up rather than only handed in, because a panel that installed the library has already\n * said so once — and saying it twice is a thing to forget. Forgetting it is silent: every other\n * SEO field is there, and the picture is simply missing from one screen and present on another.\n */\nconst picker = computed<Component | undefined>(\n () => props.mediaField ?? admin?.types['wx-media']?.component,\n)\nconst locales = useLocales()\n\nconst jsonLdText = ref<string | null>(null)\nconst jsonLdBroken = ref(false)\n\n/** One field of the value, without rebuilding the object in every handler. */\nfunction field<K extends keyof SeoValue>(key: K) {\n return computed({\n get: () => value.value?.[key],\n set: (next: SeoValue[K]) => {\n value.value = { ...(value.value ?? {}), [key]: next }\n },\n })\n}\n\nconst title = field('title')\nconst h1 = field('h1')\nconst description = field('description')\nconst keywords = field('keywords')\nconst ogTitle = field('og_title')\nconst ogDescription = field('og_description')\n\nconst canonical = computed<string>({\n get: () => value.value?.canonical ?? '',\n set: (next: string) => {\n value.value = { ...(value.value ?? {}), canonical: next.trim() === '' ? null : next }\n },\n})\n\nconst image = computed({\n get: () => value.value?.og_image ?? null,\n set: (next: SeoImage | null) => {\n value.value = { ...(value.value ?? {}), og_image: next }\n },\n})\n\n/* The directives the checkboxes cover. Anything else somebody wrote — `max-snippet:20` and its\n kind — is kept and shown, because silently dropping a line a person typed is worse than not\n offering a box for it. */\nconst directives = computed(() => (value.value?.robots ?? '').split(',').map((one) => one.trim()))\n\nconst kept = computed(() =>\n directives.value.filter((one) => one !== '' && !robotsDirectives.includes(one as never)),\n)\n\nconst known = computed({\n get: () => directives.value.filter((one) => robotsDirectives.includes(one as never)),\n set: (next: readonly string[]) => {\n const merged = [...next, ...kept.value].join(', ')\n value.value = { ...(value.value ?? {}), robots: merged === '' ? null : merged }\n },\n})\n\nconst robotsOptions = computed(() =>\n robotsDirectives.map((one) => ({ value: one, label: t(`card.${one}`) })),\n)\n\nconst jsonLd = computed<string>({\n get: () => {\n if (jsonLdText.value !== null) return jsonLdText.value\n\n const stored = value.value?.json_ld\n\n return stored === null || stored === undefined ? '' : JSON.stringify(stored, null, 2)\n },\n set: (next: string) => {\n /* The text is kept as typed while it is being typed: reformatting somebody's JSON the\n moment it happens to parse moves the caret out from under them. */\n jsonLdText.value = next\n\n if (next.trim() === '') {\n jsonLdBroken.value = false\n value.value = { ...(value.value ?? {}), json_ld: null }\n\n return\n }\n\n try {\n value.value = { ...(value.value ?? {}), json_ld: JSON.parse(next) }\n jsonLdBroken.value = false\n } catch {\n jsonLdBroken.value = true\n }\n },\n})\n\n/** What the counters and the preview read: the language being edited. */\nfunction inLocale(map: LocalizedValue | undefined): string {\n return localizedValue(map, locales.active.value)\n}\n\nconst counted = computed(() => ({\n title: inLocale(title.value as LocalizedValue | undefined).length,\n description: inLocale(description.value as LocalizedValue | undefined).length,\n}))\n\nconst preview = computed(() => ({\n title: inLocale(title.value as LocalizedValue | undefined),\n description: inLocale(description.value as LocalizedValue | undefined),\n address: canonical.value || (props.site ?? ''),\n}))\n\nconst hasPreview = computed(() => preview.value.title !== '' || preview.value.description !== '')\n\n/**\n * What a share card would say, with the fallbacks the fields promise: an empty share title\n * takes the page's title, an empty share description takes its description.\n *\n * The picture is the address the server sent beside the key — `og_image.url`, worked out by\n * whoever owns `wx-media`. That is the whole reason it travels with the value, so the preview\n * costs no request and works on a page opened cold, not only on one where the picture was\n * picked a moment ago.\n */\nconst share = computed(() => ({\n image: image.value?.url ?? null,\n title: inLocale(ogTitle.value as LocalizedValue | undefined) || preview.value.title,\n description:\n inLocale(ogDescription.value as LocalizedValue | undefined) || preview.value.description,\n address: preview.value.address,\n}))\n\nconst hasShare = computed(\n () => share.value.image !== null || share.value.title !== '' || share.value.description !== '',\n)\n\nconst keptHelp = computed(() =>\n kept.value.length === 0\n ? undefined\n : t('card.robots-kept', { directives: kept.value.join(', ') }),\n)\n\nfunction tone(count: number, limit: number): 'muted' | 'warning' {\n return count > limit ? 'warning' : 'muted'\n}\n</script>\n\n<template>\n <div class=\"wx-seo\">\n <wx-tabs keep-alive>\n <wx-tab :label=\"t('card.section-page')\" value=\"page\">\n <div class=\"wx-seo__fields\">\n <wx-form-item :label=\"t('card.title')\" :disabled=\"disabled\">\n <wx-input v-model=\"title\" localized />\n <div class=\"wx-seo__count\">\n <wx-text size=\"sm\" :tone=\"tone(counted.title, titleLimit)\">\n {{ counted.title }} / {{ titleLimit }}\n </wx-text>\n </div>\n </wx-form-item>\n\n <wx-form-item :label=\"t('card.h1')\" :disabled=\"disabled\">\n <wx-input v-model=\"h1\" localized />\n </wx-form-item>\n\n <wx-form-item :label=\"t('card.description')\" :disabled=\"disabled\">\n <wx-textarea v-model=\"description\" localized :rows=\"3\" />\n <div class=\"wx-seo__count\">\n <wx-text size=\"sm\" :tone=\"tone(counted.description, descriptionLimit)\">\n {{ counted.description }} / {{ descriptionLimit }}\n </wx-text>\n </div>\n </wx-form-item>\n\n <wx-form-item :label=\"t('card.keywords')\" :disabled=\"disabled\">\n <wx-input v-model=\"keywords\" localized />\n </wx-form-item>\n\n <div v-if=\"hasPreview\" class=\"wx-seo__preview\">\n <wx-text size=\"sm\" tone=\"muted\">{{ t('card.preview') }}</wx-text>\n <div class=\"wx-seo__snippet\">\n <div v-if=\"preview.address\" class=\"wx-seo__snippet-address\">\n {{ preview.address }}\n </div>\n <div class=\"wx-seo__snippet-title\">{{ preview.title }}</div>\n <div class=\"wx-seo__snippet-text\">{{ preview.description }}</div>\n </div>\n </div>\n </div>\n </wx-tab>\n\n <wx-tab :label=\"t('card.section-share')\" value=\"share\">\n <div class=\"wx-seo__fields\">\n <component\n :is=\"picker\"\n v-if=\"picker\"\n v-model=\"image\"\n class=\"wx-seo__image\"\n :label=\"t('card.og-image')\"\n aspect=\"16/9\"\n accept=\"image\"\n :disabled=\"disabled\"\n />\n\n <wx-form-item :label=\"t('card.og-title')\" :help=\"t('card.og-help')\" :disabled=\"disabled\">\n <wx-input v-model=\"ogTitle\" localized />\n </wx-form-item>\n\n <wx-form-item :label=\"t('card.og-description')\" :disabled=\"disabled\">\n <wx-textarea v-model=\"ogDescription\" localized :rows=\"2\" />\n </wx-form-item>\n\n <!-- The same idea as the snippet on the first tab, for the other place a page is\n read without being opened. -->\n <div v-if=\"hasShare\" class=\"wx-seo__preview\">\n <wx-text size=\"sm\" tone=\"muted\">{{ t('card.share-preview') }}</wx-text>\n\n <div class=\"wx-seo__share\">\n <div class=\"wx-seo__share-image\">\n <!-- Empty is not a mistake to point at: a site fills the picture in from the\n record itself, and only it knows with what. Said rather than left blank,\n because a blank rectangle reads as \"nothing will be shown\". -->\n <img v-if=\"share.image\" :src=\"share.image\" alt=\"\" />\n <wx-text v-else size=\"sm\" tone=\"muted\">{{ t('card.share-auto') }}</wx-text>\n </div>\n\n <div class=\"wx-seo__share-body\">\n <div v-if=\"share.address\" class=\"wx-seo__share-address\">{{ share.address }}</div>\n <div class=\"wx-seo__share-title\">{{ share.title }}</div>\n <div v-if=\"share.description\" class=\"wx-seo__share-text\">\n {{ share.description }}\n </div>\n </div>\n </div>\n </div>\n </div>\n </wx-tab>\n\n <wx-tab :label=\"t('card.section-advanced')\" value=\"advanced\">\n <div class=\"wx-seo__fields\">\n <wx-form-item\n :label=\"t('card.canonical')\"\n :help=\"t('card.canonical-help')\"\n :disabled=\"disabled\"\n >\n <wx-input v-model=\"canonical\" placeholder=\"https://example.com/page\" />\n </wx-form-item>\n\n <wx-form-item :label=\"t('card.robots')\" :help=\"keptHelp\" :disabled=\"disabled\">\n <wx-checkbox-group v-model=\"known\" :options=\"robotsOptions\" />\n </wx-form-item>\n\n <wx-form-item\n v-if=\"structuredData\"\n :label=\"t('card.json-ld')\"\n :help=\"t('card.json-ld-help')\"\n :error=\"jsonLdBroken ? t('card.json-ld-invalid') : undefined\"\n :disabled=\"disabled\"\n >\n <wx-code-editor\n v-model=\"jsonLd\"\n language=\"json\"\n lint\n min-height=\"120px\"\n max-height=\"420px\"\n />\n </wx-form-item>\n </div>\n </wx-tab>\n </wx-tabs>\n </div>\n</template>\n\n<style scoped>\n.wx-seo {\n container-type: inline-size;\n}\n\n.wx-seo__fields {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-12);\n padding-block-start: var(--wx-space-12);\n}\n\n/*\n * As wide as the fields it stands with.\n *\n * The picture is the one thing in this card that is not inside a `WxFormItem` — it draws its own\n * label — so the cap a form item puts on its control never reached it, and a share image ran the\n * whole width of the card while the title under it stopped at 640. The same variable, so a\n * screen that widens its fields widens this too.\n */\n.wx-seo__image {\n max-width: var(--wx-field-max-width, 640px);\n}\n\n.wx-seo__count {\n display: flex;\n justify-content: flex-end;\n}\n\n.wx-seo__preview {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-4);\n}\n\n/* Not a copy of any one search engine's styling — a reminder of how much of a line survives. */\n.wx-seo__snippet {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-2);\n padding: var(--wx-space-12);\n border: 1px solid var(--wx-border-default);\n border-radius: var(--wx-radius-md);\n background: var(--wx-bg-subtle);\n}\n\n.wx-seo__snippet-address {\n color: var(--wx-text-muted);\n font-size: var(--wx-font-size-sm);\n overflow-wrap: anywhere;\n}\n\n.wx-seo__snippet-title {\n color: var(--wx-color-primary);\n font-size: var(--wx-font-size-lg);\n overflow-wrap: anywhere;\n}\n\n.wx-seo__snippet-text {\n color: var(--wx-text-default);\n font-size: var(--wx-font-size-sm);\n overflow-wrap: anywhere;\n}\n\n/* The share card, at the width of the fields it is made of. */\n.wx-seo__share {\n max-width: var(--wx-field-max-width, 640px);\n overflow: hidden;\n border: 1px solid var(--wx-border-default);\n border-radius: var(--wx-radius-md);\n background: var(--wx-bg-subtle);\n}\n\n/*\n * 1.91:1 is the shape every network crops an unknown picture to, so it is the shape worth\n * showing: a preview in any other proportion promises a framing the reader will not get.\n */\n.wx-seo__share-image {\n display: flex;\n align-items: center;\n justify-content: center;\n aspect-ratio: 1.91 / 1;\n padding: var(--wx-space-16);\n border-block-end: 1px solid var(--wx-border-default);\n background: var(--wx-bg-muted);\n text-align: center;\n}\n\n.wx-seo__share-image img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n}\n\n.wx-seo__share-body {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-2);\n padding: var(--wx-space-12);\n}\n\n.wx-seo__share-address {\n color: var(--wx-text-muted);\n font-size: var(--wx-font-size-xs);\n text-transform: uppercase;\n overflow-wrap: anywhere;\n}\n\n.wx-seo__share-title {\n color: var(--wx-text-default);\n font-weight: var(--wx-font-weight-medium);\n overflow-wrap: anywhere;\n}\n\n.wx-seo__share-text {\n color: var(--wx-text-muted);\n font-size: var(--wx-font-size-sm);\n overflow-wrap: anywhere;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\nimport { useAdmin, useErrorText, useTranslate } from '@webx-ui/module-admin'\nimport {\n toast,\n useModal,\n WxAlert,\n WxButton,\n WxDialog,\n WxFormItem,\n WxInput,\n WxSelect,\n WxSpace,\n WxSwitch,\n} from '@webx-ui/core'\nimport { createSeoApi } from './api'\nimport { useSeoMessages } from './i18n'\nimport type { MatchType, SeoRedirect, SeoRoute } from './types'\n\n/** One address that has moved, and where to. Four fields, so four fields. */\nconst props = withDefaults(defineProps<{ redirect?: SeoRedirect | null }>(), { redirect: null })\n\nconst { resolve, dismiss, open } = useModal<SeoRedirect>()\n\nconst context = useAdmin()\nconst api = createSeoApi(context)\nuseSeoMessages()\n\nconst t = useTranslate('webx-seo')\n/* Not the server's `message`: the panel says how a request failed in its own words (§13.3). */\nconst message = useErrorText()\n\nconst saving = ref(false)\nconst errors = ref<Record<string, string[]>>({})\nconst occupant = ref<SeoRoute | null>(null)\n\nconst form = ref({\n match_type: 'exact' as MatchType,\n pattern: '',\n target: '',\n status: 301,\n is_active: true,\n})\n\nconst editing = computed(() => props.redirect !== null)\n\nconst kindOptions = computed(() => [\n { value: 'exact', label: t('page.exact') },\n { value: 'mask', label: t('page.mask') },\n { value: 'regex', label: t('page.regex') },\n])\n\n/* Numbers, not words: 301 and 302 are what a person types into a search when they want to know\n what one is, and a label would only stand between them and that. */\nconst statusOptions = [\n { value: 301, label: '301' },\n { value: 302, label: '302' },\n]\n\nwatch(\n () => props.redirect,\n (redirect) => {\n form.value = {\n match_type: redirect?.match_type ?? 'exact',\n pattern: redirect?.pattern ?? '',\n target: redirect?.target ?? '',\n status: redirect?.status ?? 301,\n is_active: redirect?.is_active ?? true,\n }\n errors.value = {}\n occupant.value = null\n void shadowed()\n },\n { immediate: true },\n)\n\n/**\n * Whether a live page is standing at the address this rule is about to take over.\n *\n * A rule wins: `RedirectRequests` runs before routing, deliberately, so that an editor can\n * redirect an address the site still answers at. Which is exactly why this has to be said out\n * loud — the page does not disappear from the panel, it only stops being reachable, and the\n * person who finds out otherwise is a reader.\n *\n * Only for an exact address. A mask covers addresses that do not exist yet and a regular\n * expression covers ones nobody can enumerate; guessing at either would produce a warning that\n * is wrong often enough to be ignored, and a warning that is ignored is worse than none.\n */\nasync function shadowed(): Promise<void> {\n const pattern = form.value.pattern.trim()\n\n if (form.value.match_type !== 'exact' || pattern === '') {\n occupant.value = null\n\n return\n }\n\n try {\n occupant.value = (await api.test(pattern)).route\n } catch {\n // The check is a courtesy; a rule is not held up because it could not be made.\n occupant.value = null\n }\n}\n\nfunction errorOf(field: string): string | undefined {\n return errors.value[field]?.[0]\n}\n\nasync function save(): Promise<void> {\n saving.value = true\n errors.value = {}\n\n try {\n const saved = props.redirect\n ? await api.updateRedirect(props.redirect.id, form.value)\n : await api.createRedirect(form.value)\n\n toast.success(t('page.saved'))\n resolve(saved)\n } catch (error) {\n const body = (error as { body?: { errors?: Record<string, string[]>; message?: string } }).body\n\n if (body?.errors) {\n errors.value = body.errors\n toast.danger(t('page.failed'))\n } else {\n toast.danger(message(error, t('page.failed')))\n }\n } finally {\n saving.value = false\n }\n}\n</script>\n\n<template>\n <wx-dialog v-model:open=\"open\" :title=\"t('page.redirect')\" :width=\"640\">\n <div class=\"wx-seo-redirect\">\n <wx-form-item :label=\"t('page.kind')\">\n <wx-select v-model=\"form.match_type\" :options=\"kindOptions\" @change=\"shadowed\" />\n </wx-form-item>\n\n <wx-form-item\n :label=\"t('page.address')\"\n :help=\"t('page.address-help')\"\n :error=\"errorOf('pattern')\"\n >\n <wx-input v-model=\"form.pattern\" placeholder=\"/old-address\" @blur=\"shadowed\" />\n </wx-form-item>\n\n <!-- Said, not refused: the rule is allowed to shadow the page, and whoever writes it\n should know that is what they are doing. -->\n <wx-alert\n v-if=\"occupant\"\n type=\"warning\"\n variant=\"soft\"\n :description=\"\n occupant.kind === 'alias'\n ? t('page.occupied-alias', { target: occupant.target ?? occupant.path })\n : t('page.occupied', { path: occupant.path })\n \"\n />\n\n <wx-form-item\n :label=\"t('page.target')\"\n :help=\"t('page.target-help')\"\n :error=\"errorOf('target')\"\n >\n <wx-input v-model=\"form.target\" placeholder=\"/new-address\" />\n </wx-form-item>\n\n <div class=\"wx-seo-redirect__row\">\n <wx-form-item :label=\"t('page.status')\">\n <wx-select v-model=\"form.status\" :options=\"statusOptions\" />\n </wx-form-item>\n\n <wx-form-item :label=\"t('page.state')\">\n <wx-switch v-model=\"form.is_active\" />\n </wx-form-item>\n </div>\n </div>\n\n <template #footer>\n <wx-space size=\"sm\">\n <wx-button variant=\"outline\" @click=\"dismiss()\">{{ t('page.cancel') }}</wx-button>\n <wx-button type=\"primary\" :loading=\"saving\" @click=\"save\">\n {{ editing ? t('page.save') : t('page.new-redirect') }}\n </wx-button>\n </wx-space>\n </template>\n </wx-dialog>\n</template>\n\n<style scoped>\n.wx-seo-redirect {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-12);\n}\n\n.wx-seo-redirect__row {\n display: grid;\n grid-template-columns: 140px 1fr;\n gap: var(--wx-space-12);\n align-items: start;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\nimport {\n useAdmin,\n useErrorText,\n useTranslate,\n WxDate,\n rowMenuWidth,\n WxRowMenu,\n type RowAction,\n type ScreenAction,\n} from '@webx-ui/module-admin'\nimport {\n confirm,\n createModal,\n toast,\n WxBadge,\n WxTable,\n WxText,\n type TableColumn,\n type TableState,\n} from '@webx-ui/core'\nimport SeoLayout from './SeoLayout.vue'\nimport SeoRedirectDialog from './SeoRedirectDialog.vue'\nimport TestUrlDialog from './TestUrlDialog.vue'\nimport { createSeoApi } from './api'\nimport { useSeoMessages } from './i18n'\nimport type { SeoPage, SeoRedirect } from './types'\n\n/**\n * The addresses that have moved.\n *\n * Busiest first by default, which is the order this list is worked on: a redirect nobody has\n * followed since the site was rebuilt is the one worth deleting, and it sits at the bottom.\n */\nconst props = withDefaults(defineProps<{ base?: string }>(), { base: '/seo' })\n\nconst context = useAdmin()\nconst api = createSeoApi(context)\nuseSeoMessages()\n\nconst t = useTranslate('webx-seo')\n/* Not the server's `message`: the panel says how a request failed in its own words (§13.3). */\nconst message = useErrorText()\n\nconst page = ref<SeoPage<SeoRedirect> | null>(null)\nconst loading = ref(false)\n\nlet last: TableState = { page: 1, perPage: 20, sort: null, search: '' }\n\nconst canManage = context.can('seo.manage')\n\nconst edit = createModal<SeoRedirect, { redirect: SeoRedirect | null }>(SeoRedirectDialog)\nconst test = createModal<void, Record<string, never>>(TestUrlDialog)\n\nconst columns = computed<TableColumn<SeoRedirect>[]>(() => [\n { key: 'pattern', label: t('page.address'), sortable: true },\n { key: 'target', label: t('page.target') },\n { key: 'status', label: t('page.status'), align: 'center', hideBelow: 560 },\n { key: 'hits', label: t('page.hits'), align: 'center', sortable: true, hideBelow: 760 },\n {\n key: 'last_hit_at',\n label: t('page.last-hit'),\n sortable: true,\n hideBelow: 900,\n hideOnCards: true,\n },\n { key: 'is_active', label: t('page.state'), align: 'center', hideBelow: 660 },\n {\n key: 'actions',\n label: '',\n width: rowMenuWidth,\n align: 'right',\n hidden: !canManage,\n hideOnCards: true,\n },\n])\n\n/** One line, and the same menu every other list of the panel puts a record's actions in. */\nfunction actionsFor(redirect: SeoRedirect): RowAction[] {\n return [\n {\n key: 'delete',\n icon: 'trash',\n label: t('page.delete'),\n danger: true,\n run: () => remove(redirect),\n },\n ]\n}\n\nasync function load(state: TableState): Promise<void> {\n last = state\n loading.value = true\n\n try {\n page.value = await api.redirects({\n q: state.search,\n sort: state.sort ? `${state.sort.order === 'desc' ? '-' : ''}${state.sort.key}` : '-hits',\n page: state.page,\n per_page: state.perPage,\n })\n } finally {\n loading.value = false\n }\n}\n\n/**\n * Opening a redirect is editing it, so a reader who may not manage these has nowhere to go: the\n * table is told so (`:clickable=\"canManage\"`) and withholds the pointer, the highlight and the\n * click together, rather than promising a dialog that would not open (§13).\n */\nasync function open(redirect: SeoRedirect | null): Promise<void> {\n const saved = await edit({ redirect })\n\n if (saved) void load(last)\n}\n\nasync function remove(redirect: SeoRedirect): Promise<void> {\n const agreed = await confirm({\n title: t('page.delete-title', { pattern: redirect.pattern }),\n message: t('page.delete-text'),\n confirmText: t('page.delete'),\n cancelText: t('page.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n try {\n await api.removeRedirect(redirect.id)\n toast.success(t('page.deleted'))\n void load(last)\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\n/* What the section offers. Declared, because on a phone the head folds it into the ···. */\nconst actions = computed<ScreenAction[]>(() =>\n canManage\n ? [\n {\n key: 'redirect',\n label: t('page.new-redirect'),\n icon: 'plus',\n primary: true,\n run: () => void open(null),\n },\n ]\n : [],\n)\n</script>\n\n<template>\n <seo-layout :base=\"props.base\" current=\"redirects\" :actions=\"actions\" @test=\"test({})\">\n <wx-table\n :data=\"page\"\n :columns=\"columns\"\n row-key=\"id\"\n searchable\n :clickable=\"canManage\"\n :hover=\"canManage\"\n flush\n :loading=\"loading\"\n :search-placeholder=\"t('page.search-redirects')\"\n :empty-text=\"t('page.empty')\"\n @row-click=\"open\"\n @state-change=\"load\"\n >\n <template #cell-pattern=\"{ row }\">\n <wx-text mono size=\"sm\">{{ row.pattern }}</wx-text>\n <!-- Said out loud rather than refused on save: the middleware steps over it, and a\n row that quietly does nothing is a row nobody ever fixes. -->\n <wx-badge v-if=\"row.is_loop\" type=\"warning\">{{ t('page.loop') }}</wx-badge>\n </template>\n\n <template #cell-target=\"{ row }\">\n <wx-text mono size=\"sm\">{{ row.target }}</wx-text>\n </template>\n\n <template #cell-last_hit_at=\"{ row }\">\n <wx-date :value=\"row.last_hit_at\" :tone=\"row.last_hit_at ? 'default' : 'muted'\" />\n </template>\n\n <template #cell-is_active=\"{ row }\">\n <wx-badge :type=\"row.is_active ? 'success' : 'default'\" dot>\n {{ row.is_active ? t('page.active') : t('page.inactive') }}\n </wx-badge>\n </template>\n\n <template #card-actions=\"{ row }\">\n <wx-row-menu v-if=\"canManage\" :actions=\"actionsFor(row)\" :label=\"row.pattern\" />\n </template>\n\n <template #cell-actions=\"{ row }\">\n <wx-row-menu :actions=\"actionsFor(row)\" :label=\"row.pattern\" />\n </template>\n </wx-table>\n </seo-layout>\n</template>\n","import { localizedValue, type LocalizedValue } from '@webx-ui/core'\nimport type { SeoUrlInput, SeoUrlRule, SeoValue } from './types'\n\n/** The fields the card owns, as opposed to the ones the rule form owns. */\nconst SEO_FIELDS = [\n 'title',\n 'h1',\n 'description',\n 'keywords',\n 'og_title',\n 'og_description',\n 'og_image',\n 'canonical',\n 'robots',\n 'json_ld',\n] as const\n\n/**\n * What a rule is called in a table.\n *\n * Any language rather than the current one: a record half translated is the normal state of a\n * site being worked on, and a blank cell says less than the language that does have the words.\n */\nexport function ruleTitle(rule: SeoUrlRule, locale?: string): string {\n return localizedValue(rule.title as LocalizedValue | undefined, locale)\n}\n\n/** The SEO half of a rule, for the card to edit. */\nexport function seoOf(rule: SeoUrlRule | null): SeoValue {\n if (!rule) return {}\n\n const value: SeoValue = {}\n\n for (const key of SEO_FIELDS) {\n const one = rule[key]\n\n if (one !== null && one !== undefined) {\n Object.assign(value, { [key]: one })\n }\n }\n\n return value\n}\n\n/**\n * What the form sends: the rule's own fields, with the card's value spread back over them.\n *\n * Every field of the card goes, present or not — a `PUT` replaces the whole rule, so a\n * description somebody cleared has to arrive as nothing rather than not arrive at all.\n */\nexport function ruleInput(\n own: Pick<SeoUrlInput, 'match_type' | 'pattern' | 'priority' | 'is_active'>,\n seo: SeoValue,\n): SeoUrlInput {\n const input: SeoUrlInput = { ...own }\n\n for (const key of SEO_FIELDS) {\n Object.assign(input, { [key]: seo[key] ?? null })\n }\n\n return input\n}\n","<script setup lang=\"ts\">\nimport { computed, ref, watch, type Component } from 'vue'\nimport { useAdmin, useErrorText, useTranslate } from '@webx-ui/module-admin'\nimport {\n toast,\n useModal,\n WxButton,\n WxDialog,\n WxFormItem,\n WxInput,\n WxInputNumber,\n WxSelect,\n WxSpace,\n WxSwitch,\n} from '@webx-ui/core'\nimport SeoCard from './SeoCard.vue'\nimport { createSeoApi } from './api'\nimport { useSeoMessages } from './i18n'\nimport { ruleInput, seoOf } from './rule'\nimport type { MatchType, SeoUrlRule, SeoValue } from './types'\n\n/**\n * One rule: which addresses it covers, and what it says about them.\n *\n * A dialog rather than a second route, and a template rather than a described screen — the same\n * decision `admins.form` is on, until the screens mechanism has been round the block a few more\n * times. What it edits below the address is `WxSeo`, the same card a content module will get by\n * patching it into its own form.\n */\nconst props = withDefaults(defineProps<{ rule?: SeoUrlRule | null; mediaField?: Component }>(), {\n rule: null,\n mediaField: undefined,\n})\n\nconst { resolve, dismiss, open } = useModal<SeoUrlRule>()\n\nconst context = useAdmin()\nconst api = createSeoApi(context)\nuseSeoMessages()\n\nconst t = useTranslate('webx-seo')\n/* Not the server's `message`: the panel says how a request failed in its own words (§13.3). */\nconst message = useErrorText()\n\nconst saving = ref(false)\nconst errors = ref<Record<string, string[]>>({})\n\nconst form = ref({\n match_type: 'exact' as MatchType,\n pattern: '',\n priority: 0,\n is_active: true,\n})\n\nconst seo = ref<SeoValue>({})\n\nconst editing = computed(() => props.rule !== null)\n\nconst kindOptions = computed(() => [\n { value: 'exact', label: t('page.exact') },\n { value: 'mask', label: t('page.mask') },\n { value: 'regex', label: t('page.regex') },\n])\n\nwatch(\n () => props.rule,\n (rule) => {\n form.value = {\n match_type: rule?.match_type ?? 'exact',\n pattern: rule?.pattern ?? '',\n priority: rule?.priority ?? 0,\n is_active: rule?.is_active ?? true,\n }\n seo.value = seoOf(rule)\n errors.value = {}\n },\n { immediate: true },\n)\n\nfunction errorOf(field: string): string | undefined {\n return errors.value[field]?.[0]\n}\n\nasync function save(): Promise<void> {\n saving.value = true\n errors.value = {}\n\n const input = ruleInput(form.value, seo.value)\n\n try {\n const saved = props.rule\n ? await api.updateUrl(props.rule.id, input)\n : await api.createUrl(input)\n\n toast.success(t('page.saved'))\n resolve(saved)\n } catch (error) {\n const body = (error as { body?: { errors?: Record<string, string[]>; message?: string } }).body\n\n if (body?.errors) {\n errors.value = body.errors\n toast.danger(t('page.failed'))\n } else {\n toast.danger(message(error, t('page.failed')))\n }\n } finally {\n saving.value = false\n }\n}\n</script>\n\n<template>\n <wx-dialog v-model:open=\"open\" :title=\"t('page.rule')\" :width=\"860\">\n <div class=\"wx-seo-rule\">\n <div class=\"wx-seo-rule__where\">\n <wx-form-item :label=\"t('page.kind')\">\n <wx-select v-model=\"form.match_type\" :options=\"kindOptions\" />\n </wx-form-item>\n\n <wx-form-item\n :label=\"t('page.address')\"\n :help=\"t('page.address-help')\"\n :error=\"errorOf('pattern')\"\n class=\"wx-seo-rule__address\"\n >\n <wx-input v-model=\"form.pattern\" placeholder=\"/catalog/shoes\" />\n </wx-form-item>\n\n <wx-form-item :label=\"t('page.priority')\" :help=\"t('page.priority-help')\">\n <wx-input-number v-model=\"form.priority\" :step=\"10\" />\n </wx-form-item>\n\n <wx-form-item :label=\"t('page.state')\">\n <wx-switch v-model=\"form.is_active\" />\n </wx-form-item>\n </div>\n\n <seo-card v-model=\"seo\" :media-field=\"props.mediaField\" />\n </div>\n\n <template #footer>\n <wx-space size=\"sm\">\n <wx-button variant=\"outline\" @click=\"dismiss()\">{{ t('page.cancel') }}</wx-button>\n <wx-button type=\"primary\" :loading=\"saving\" @click=\"save\">\n {{ editing ? t('page.save') : t('page.new-rule') }}\n </wx-button>\n </wx-space>\n </template>\n </wx-dialog>\n</template>\n\n<style scoped>\n.wx-seo-rule {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-16);\n container-type: inline-size;\n}\n\n/* The address and its kind on one line: they are one decision, and reading them apart is\n reading half of it. */\n.wx-seo-rule__where {\n display: grid;\n grid-template-columns: 180px 1fr 140px 100px;\n gap: var(--wx-space-12);\n align-items: start;\n}\n\n@container (max-width: 720px) {\n .wx-seo-rule__where {\n grid-template-columns: 1fr 1fr;\n }\n\n .wx-seo-rule__address {\n grid-column: 1 / -1;\n }\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, ref, type Component } from 'vue'\nimport {\n useAdmin,\n useErrorText,\n useTranslate,\n WxFilterChips,\n type AppliedFilter,\n rowMenuWidth,\n WxRowMenu,\n type RowAction,\n type ScreenAction,\n} from '@webx-ui/module-admin'\nimport {\n confirm,\n createModal,\n toast,\n WxBadge,\n WxFormItem,\n WxSelect,\n WxTable,\n WxText,\n type TableColumn,\n type TableState,\n} from '@webx-ui/core'\nimport SeoUrlDialog from './SeoUrlDialog.vue'\nimport TestUrlDialog from './TestUrlDialog.vue'\nimport SeoLayout from './SeoLayout.vue'\nimport { createSeoApi } from './api'\nimport { useSeoMessages } from './i18n'\nimport { ruleTitle } from './rule'\nimport type { SeoPage, SeoUrlRule } from './types'\n\n/**\n * The rules written for addresses.\n *\n * Listed in the order the site tries them — exact, then mask, then regular expression, by\n * priority inside each group — so that reading the table top to bottom is reading what will\n * happen. Sorting it any other way would hide the one thing a person comes here to work out.\n */\nconst props = withDefaults(defineProps<{ base?: string; mediaField?: Component }>(), {\n base: '/seo',\n mediaField: undefined,\n})\n\nconst context = useAdmin()\nconst api = createSeoApi(context)\nuseSeoMessages()\n\nconst t = useTranslate('webx-seo')\n/* The funnel and \"reset all\" are the panel's own words. */\nconst panel = useTranslate('webx-admin')\n/* Not the server's `message`: the panel says how a request failed in its own words (§13.3). */\nconst message = useErrorText()\n\nconst page = ref<SeoPage<SeoUrlRule> | null>(null)\nconst loading = ref(false)\n/* A word rather than an empty string: a select reads \"\" as nothing chosen and shows a blank\n control where the option said \"any kind\". */\nconst kind = ref('any')\n\nlet last: TableState = { page: 1, perPage: 20, sort: null, search: '' }\n\nconst canManage = context.can('seo.manage')\n\nconst edit = createModal<SeoUrlRule, { rule: SeoUrlRule | null; mediaField?: Component }>(\n SeoUrlDialog,\n)\nconst test = createModal<void, Record<string, never>>(TestUrlDialog)\n\nconst kindOptions = computed(() => [\n { value: 'any', label: t('page.any-kind') },\n { value: 'exact', label: t('page.exact') },\n { value: 'mask', label: t('page.mask') },\n { value: 'regex', label: t('page.regex') },\n])\n\n/** The one dropdown, said in the reader's words while the panel it lives in is shut. */\nconst applied = computed<AppliedFilter[]>(() =>\n kind.value === 'any'\n ? []\n : [\n {\n key: 'kind',\n label: `${t('page.filter-kind')}: ${\n kindOptions.value.find((option) => option.value === kind.value)?.label ?? kind.value\n }`,\n clear: () => (kind.value = 'any'),\n },\n ],\n)\n\nconst columns = computed<TableColumn<SeoUrlRule>[]>(() => [\n { key: 'pattern', label: t('page.address') },\n { key: 'match_type', label: t('page.kind'), hideBelow: 560 },\n { key: 'title', label: t('page.title'), hideBelow: 900 },\n { key: 'priority', label: t('page.priority'), align: 'center', hideBelow: 760 },\n { key: 'is_active', label: t('page.state'), align: 'center', hideBelow: 660 },\n {\n key: 'actions',\n label: '',\n width: rowMenuWidth,\n align: 'right',\n hidden: !canManage,\n hideOnCards: true,\n },\n])\n\n/**\n * What a rule offers: one line, and it is the destructive one — everything else about a rule is\n * done by opening it. Still a menu, because a red bin standing in every row shouts louder than\n * deleting a rule deserves, and because a reader should find a record's actions in the same\n * place in every list of the panel (§20).\n */\nfunction actionsFor(rule: SeoUrlRule): RowAction[] {\n return [\n {\n key: 'delete',\n icon: 'trash',\n label: t('page.delete'),\n danger: true,\n run: () => remove(rule),\n },\n ]\n}\n\nasync function load(state: TableState): Promise<void> {\n last = state\n loading.value = true\n\n try {\n page.value = await api.urls({\n q: state.search,\n match_type: kind.value === 'any' ? null : (kind.value as 'exact' | 'mask' | 'regex'),\n page: state.page,\n per_page: state.perPage,\n })\n } finally {\n loading.value = false\n }\n}\n\n/**\n * Opening a rule is editing it, so a reader who may not manage these has nowhere to go: the\n * table is told so (`:clickable=\"canManage\"`) and withholds the pointer, the highlight and the\n * click together, rather than promising a dialog that would not open (§13).\n */\nasync function open(rule: SeoUrlRule | null): Promise<void> {\n const saved = await edit({ rule, mediaField: props.mediaField })\n\n if (saved) void load(last)\n}\n\nasync function remove(rule: SeoUrlRule): Promise<void> {\n const agreed = await confirm({\n title: t('page.delete-title', { pattern: rule.pattern }),\n message: t('page.delete-text'),\n confirmText: t('page.delete'),\n cancelText: t('page.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n try {\n await api.removeUrl(rule.id)\n toast.success(t('page.deleted'))\n void load(last)\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\n/* What the section offers. Declared, because on a phone the head folds it into the ···. */\nconst actions = computed<ScreenAction[]>(() =>\n canManage\n ? [\n {\n key: 'rule',\n label: t('page.new-rule'),\n icon: 'plus',\n primary: true,\n run: () => void open(null),\n },\n ]\n : [],\n)\n</script>\n\n<template>\n <seo-layout :base=\"props.base\" current=\"rules\" :actions=\"actions\" @test=\"test({})\">\n <wx-table\n :data=\"page\"\n :columns=\"columns\"\n row-key=\"id\"\n searchable\n :clickable=\"canManage\"\n :hover=\"canManage\"\n flush\n :loading=\"loading\"\n :search-placeholder=\"t('page.search-rules')\"\n :empty-text=\"t('page.empty')\"\n :filters-count=\"applied.length\"\n :filters-label=\"panel('filters.title')\"\n @row-click=\"open\"\n @state-change=\"load\"\n >\n <!-- Behind the funnel, and what it is set to comes back as a chip beside it. -->\n <template #filters>\n <wx-form-item :label=\"t('page.filter-kind')\">\n <wx-select v-model=\"kind\" :options=\"kindOptions\" size=\"sm\" />\n </wx-form-item>\n </template>\n\n <template #applied>\n <wx-filter-chips :filters=\"applied\" />\n </template>\n\n <template #cell-pattern=\"{ row }\">\n <wx-text mono size=\"sm\">{{ row.pattern }}</wx-text>\n </template>\n\n <template #cell-match_type=\"{ row }\">\n <wx-badge>{{ t(`page.${row.match_type}`) }}</wx-badge>\n </template>\n\n <template #cell-title=\"{ row }\">\n <wx-text v-if=\"ruleTitle(row)\" size=\"sm\">{{ ruleTitle(row) }}</wx-text>\n <wx-text v-else size=\"sm\" tone=\"muted\">—</wx-text>\n </template>\n\n <template #cell-is_active=\"{ row }\">\n <wx-badge :type=\"row.is_active ? 'success' : 'default'\" dot>\n {{ row.is_active ? t('page.active') : t('page.inactive') }}\n </wx-badge>\n </template>\n\n <template #card-actions=\"{ row }\">\n <wx-row-menu v-if=\"canManage\" :actions=\"actionsFor(row)\" :label=\"row.pattern\" />\n </template>\n\n <template #cell-actions=\"{ row }\">\n <wx-row-menu :actions=\"actionsFor(row)\" :label=\"row.pattern\" />\n </template>\n </wx-table>\n </seo-layout>\n</template>\n","import { markRaw, type Component } from 'vue'\nimport type { AdminModule } from '@webx-ui/module-admin'\nimport SeoAliasesPage from './SeoAliasesPage.vue'\nimport SeoCard from './SeoCard.vue'\nimport SeoRedirectsPage from './SeoRedirectsPage.vue'\nimport SeoUrlsPage from './SeoUrlsPage.vue'\n\nexport interface SeoOptions {\n /** Where the section lives inside the panel. */\n path?: string\n /**\n * The field that picks a share image — `WxMediaField` from `@webx-ui/module-media`.\n *\n * Handed in rather than imported, so this package does not depend on the library being\n * installed. Without it every other SEO field still works; only the picture is missing.\n */\n mediaField?: Component\n}\n\n/**\n * SEO as a section of the panel, and `wx-seo` as a field any screen can use.\n *\n * The id matches the module the server reports, which is what makes the entry appear in the\n * navigation: a section shows up when both halves are installed, and stays out of the way when\n * only one is.\n */\nexport function seo(options: SeoOptions = {}): AdminModule {\n const path = options.path ?? '/seo'\n /* A component kept in an options object arrives as a reactive proxy; Vue warns about one and\n `h()` wants the raw thing anyway. */\n const mediaField = options.mediaField ? markRaw(options.mediaField) : undefined\n\n return {\n id: 'seo',\n path,\n routes: [\n // The section's own path travels as a prop, so a panel that mounted it somewhere else\n // still links between its two screens correctly.\n {\n path,\n name: 'webx.seo',\n component: SeoUrlsPage,\n props: { base: path, mediaField },\n },\n {\n path: `${path}/redirects`,\n name: 'webx.seo.redirects',\n component: SeoRedirectsPage,\n props: { base: path },\n },\n // The trail of renames, from `webx-ui/routing`. The screen is here whether or not any\n // content module has registered a type yet: without one the table is simply empty, and an\n // empty table is a better answer than a tab that appears one day without explanation.\n {\n path: `${path}/aliases`,\n name: 'webx.seo.aliases',\n component: SeoAliasesPage,\n props: { base: path },\n },\n ],\n // What a screen means by `wx-seo`: everything a page says about itself, as one value. The\n // server registers the same name for what it stores.\n types: {\n 'wx-seo': {\n component: SeoCard,\n kind: 'field',\n wide: true,\n bind: () => ({ mediaField }),\n },\n },\n }\n}\n"],"names":["seoMessages","seeded","useSeoMessages","i18n","useAdmin","props","__props","emit","__emit","context","router","useRouter","route","useRoute","t","useTranslate","title","computed","module","views","actions","where","next","path","_createBlock","_unref","WxListScreen","$event","_renderSlot","_ctx","createSeoApi","admin","base","data","body","page","listQuery","query","id","input","url","locale","open","useModal","api","ref","running","result","fields","seo","one","run","WxDialog","_createElementVNode","_hoisted_1","_createVNode","WxSpace","WxInput","WxButton","_createElementBlock","_Fragment","WxAlert","_openBlock","_hoisted_2","WxText","WxBadge","_createTextVNode","_toDisplayString","_hoisted_3","WxDescriptions","_renderList","WxDescriptionsItem","_hoisted_4","step","WxEmpty","loading","test","createModal","TestUrlDialog","multilingual","columns","load","state","SeoLayout","WxTable","_withCtx","row","WxLink","WxDate","robotsDirectives","value","_useModel","inject","adminKey","picker","locales","useLocales","jsonLdText","jsonLdBroken","field","key","h1","description","keywords","ogTitle","ogDescription","canonical","image","directives","kept","known","merged","robotsOptions","jsonLd","stored","inLocale","map","localizedValue","counted","preview","hasPreview","share","hasShare","keptHelp","tone","count","limit","WxTabs","WxTab","WxFormItem","WxTextarea","_hoisted_5","_hoisted_6","_hoisted_7","_hoisted_8","_hoisted_9","_hoisted_10","_resolveDynamicComponent","_hoisted_11","_hoisted_12","_hoisted_13","_hoisted_15","_hoisted_16","_hoisted_17","_hoisted_18","_hoisted_19","WxCheckboxGroup","WxCodeEditor","resolve","dismiss","message","useErrorText","saving","errors","occupant","form","editing","kindOptions","statusOptions","watch","redirect","shadowed","pattern","errorOf","save","saved","toast","error","WxSelect","_cache","WxSwitch","last","canManage","edit","SeoRedirectDialog","rowMenuWidth","actionsFor","remove","confirm","WxRowMenu","SEO_FIELDS","ruleTitle","rule","seoOf","ruleInput","own","WxInputNumber","SeoCard","panel","kind","SeoUrlDialog","applied","option","WxFilterChips","options","mediaField","markRaw","SeoUrlsPage","SeoRedirectsPage","SeoAliasesPage"],"mappings":"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"}
package/dist/style.css CHANGED
@@ -1 +1 @@
1
- .wx-seo-test[data-v-116e47cd]{display:flex;flex-direction:column;gap:var(--wx-space-16)}.wx-seo-test__ask[data-v-116e47cd]{width:100%}.wx-seo-test__input[data-v-116e47cd]{flex:1 1 auto}.wx-seo-test__matched[data-v-116e47cd],.wx-seo-test__result[data-v-116e47cd],.wx-seo-test__chain[data-v-116e47cd]{display:flex;flex-direction:column;gap:var(--wx-space-6)}.wx-seo-aliases__note[data-v-6816a871]{margin-bottom:var(--wx-space-12)}.wx-seo[data-v-60b7936d]{container-type:inline-size}.wx-seo__fields[data-v-60b7936d]{display:flex;flex-direction:column;gap:var(--wx-space-12);padding-block-start:var(--wx-space-12)}.wx-seo__count[data-v-60b7936d]{display:flex;justify-content:flex-end}.wx-seo__preview[data-v-60b7936d]{display:flex;flex-direction:column;gap:var(--wx-space-4)}.wx-seo__snippet[data-v-60b7936d]{display:flex;flex-direction:column;gap:var(--wx-space-2);padding:var(--wx-space-12);border:1px solid var(--wx-border-default);border-radius:var(--wx-radius-md);background:var(--wx-bg-subtle)}.wx-seo__snippet-address[data-v-60b7936d]{color:var(--wx-text-muted);font-size:var(--wx-font-size-sm);overflow-wrap:anywhere}.wx-seo__snippet-title[data-v-60b7936d]{color:var(--wx-color-primary);font-size:var(--wx-font-size-lg);overflow-wrap:anywhere}.wx-seo__snippet-text[data-v-60b7936d]{color:var(--wx-text-default);font-size:var(--wx-font-size-sm);overflow-wrap:anywhere}.wx-seo-redirect[data-v-685edbd5]{display:flex;flex-direction:column;gap:var(--wx-space-12)}.wx-seo-redirect__row[data-v-685edbd5]{display:grid;grid-template-columns:140px 1fr;gap:var(--wx-space-12);align-items:start}.wx-seo-rule[data-v-fa975d05]{display:flex;flex-direction:column;gap:var(--wx-space-16);container-type:inline-size}.wx-seo-rule__where[data-v-fa975d05]{display:grid;grid-template-columns:180px 1fr 140px 100px;gap:var(--wx-space-12);align-items:start}@container (max-width: 720px){.wx-seo-rule__where[data-v-fa975d05]{grid-template-columns:1fr 1fr}.wx-seo-rule__address[data-v-fa975d05]{grid-column:1 / -1}}
1
+ .wx-seo-test[data-v-116e47cd]{display:flex;flex-direction:column;gap:var(--wx-space-16)}.wx-seo-test__ask[data-v-116e47cd]{width:100%}.wx-seo-test__input[data-v-116e47cd]{flex:1 1 auto}.wx-seo-test__matched[data-v-116e47cd],.wx-seo-test__result[data-v-116e47cd],.wx-seo-test__chain[data-v-116e47cd]{display:flex;flex-direction:column;gap:var(--wx-space-6)}.wx-seo-aliases__note[data-v-6816a871]{margin-bottom:var(--wx-space-12)}.wx-seo[data-v-1a19e926]{container-type:inline-size}.wx-seo__fields[data-v-1a19e926]{display:flex;flex-direction:column;gap:var(--wx-space-12);padding-block-start:var(--wx-space-12)}.wx-seo__image[data-v-1a19e926]{max-width:var(--wx-field-max-width, 640px)}.wx-seo__count[data-v-1a19e926]{display:flex;justify-content:flex-end}.wx-seo__preview[data-v-1a19e926]{display:flex;flex-direction:column;gap:var(--wx-space-4)}.wx-seo__snippet[data-v-1a19e926]{display:flex;flex-direction:column;gap:var(--wx-space-2);padding:var(--wx-space-12);border:1px solid var(--wx-border-default);border-radius:var(--wx-radius-md);background:var(--wx-bg-subtle)}.wx-seo__snippet-address[data-v-1a19e926]{color:var(--wx-text-muted);font-size:var(--wx-font-size-sm);overflow-wrap:anywhere}.wx-seo__snippet-title[data-v-1a19e926]{color:var(--wx-color-primary);font-size:var(--wx-font-size-lg);overflow-wrap:anywhere}.wx-seo__snippet-text[data-v-1a19e926]{color:var(--wx-text-default);font-size:var(--wx-font-size-sm);overflow-wrap:anywhere}.wx-seo__share[data-v-1a19e926]{max-width:var(--wx-field-max-width, 640px);overflow:hidden;border:1px solid var(--wx-border-default);border-radius:var(--wx-radius-md);background:var(--wx-bg-subtle)}.wx-seo__share-image[data-v-1a19e926]{display:flex;align-items:center;justify-content:center;aspect-ratio:1.91 / 1;padding:var(--wx-space-16);border-block-end:1px solid var(--wx-border-default);background:var(--wx-bg-muted);text-align:center}.wx-seo__share-image img[data-v-1a19e926]{width:100%;height:100%;object-fit:cover}.wx-seo__share-body[data-v-1a19e926]{display:flex;flex-direction:column;gap:var(--wx-space-2);padding:var(--wx-space-12)}.wx-seo__share-address[data-v-1a19e926]{color:var(--wx-text-muted);font-size:var(--wx-font-size-xs);text-transform:uppercase;overflow-wrap:anywhere}.wx-seo__share-title[data-v-1a19e926]{color:var(--wx-text-default);font-weight:var(--wx-font-weight-medium);overflow-wrap:anywhere}.wx-seo__share-text[data-v-1a19e926]{color:var(--wx-text-muted);font-size:var(--wx-font-size-sm);overflow-wrap:anywhere}.wx-seo-redirect[data-v-685edbd5]{display:flex;flex-direction:column;gap:var(--wx-space-12)}.wx-seo-redirect__row[data-v-685edbd5]{display:grid;grid-template-columns:140px 1fr;gap:var(--wx-space-12);align-items:start}.wx-seo-rule[data-v-fa975d05]{display:flex;flex-direction:column;gap:var(--wx-space-16);container-type:inline-size}.wx-seo-rule__where[data-v-fa975d05]{display:grid;grid-template-columns:180px 1fr 140px 100px;gap:var(--wx-space-12);align-items:start}@container (max-width: 720px){.wx-seo-rule__where[data-v-fa975d05]{grid-template-columns:1fr 1fr}.wx-seo-rule__address[data-v-fa975d05]{grid-column:1 / -1}}