@webx-ui/module-blog 0.6.3 → 0.7.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/editor.ts","../src/messages.ts","../src/i18n.ts","../src/ArticleAuthor.vue","../src/api.ts","../src/ArticleEditorPage.vue","../src/ArticleHistory.vue","../src/ArticleRelated.vue","../src/ArticleSlug.vue","../src/ArticleTags.vue","../src/ArticleUnpublish.vue","../src/ArticleCreateDialog.vue","../src/ArticlesPage.vue","../src/TagCreateDialog.vue","../src/TagRenameDialog.vue","../src/TagMergeDialog.vue","../src/TagsPage.vue","../src/module.ts"],"sourcesContent":["import { inject, provide, type InjectionKey, type Ref } from 'vue'\nimport type { ScreenModel } from '@webx-ui/schema'\nimport type { ArticleDetail, ArticleOption, ArticleRow } from './types'\n\n/**\n * What the editor knows and the nodes of its screen do not.\n *\n * The form is a described screen (`blog.article-form`), so the parts of it that need more than\n * a value — the address printed whole, the rubrics with their titles, the history — arrive as\n * node types of their own. Each of those needs the article being edited and the lists the\n * server sent with it, and none of them can be handed either through the description: a screen\n * is a description, not a binding. So the page that hosts the screen provides it, the same way\n * it provides the preview to `wx-blocks`.\n */\nexport interface ArticleEditorContext {\n /** The article as the server last answered it, or `null` while the first request is in flight. */\n article: Ref<ArticleRow | null>\n /**\n * The values of the screen as they are right now, edits included.\n *\n * A node that only draws still has to follow what is being typed: the address is a sentence\n * made of the slug field, and one that updated on save would be showing the old address at\n * the moment the new one is most worth reading.\n */\n values: Ref<ScreenModel>\n /** What the dropdowns on the screen can be set to, as the record carried them. */\n options: Ref<ArticleDetail['options']>\n /** Titles for the ids in `values.related` — the only place they come from. */\n related: Ref<ArticleOption[]>\n /** The first segment of every blog address, for the field that edits the last one. */\n prefix: Ref<string>\n /** Where the section lives, for the links the nodes draw. */\n base: string\n /** Whether this administrator may write at all. */\n canManage: boolean\n /**\n * Whether the screen is closed for writing right now — no permission, or a publication in\n * flight.\n *\n * Through the editor rather than through the form, because `WxForm`'s own `disabled` reaches\n * a control only through `useFormField`, which the design system does not hand out: a field\n * built outside `@webx-ui/core` has no way to ask. The screen is told the same thing twice —\n * once for the fields that are core's and once here — and both are computed from one place.\n */\n disabled: Ref<boolean>\n /** Ask the server for the article again — after a restore, a publication, a discard. */\n reload(): Promise<void>\n /** Write the draft now rather than at the end of the next pause. */\n save(): Promise<void>\n}\n\nexport const articleEditorKey: InjectionKey<ArticleEditorContext> = Symbol('wx-article-editor')\n\nexport function provideArticleEditor(editor: ArticleEditorContext): void {\n provide(articleEditorKey, editor)\n}\n\n/**\n * The editor above this node, or `null` outside one — a node of `blog.article-form` drawn in a\n * demo or a test has no article to talk about, and must draw nothing rather than throw.\n */\nexport function useArticleEditor(): ArticleEditorContext | null {\n return inject(articleEditorKey, null)\n}\n","import type { Messages } from '@webx-ui/module-admin'\n\n/**\n * The English this package speaks on its own, before the server's dictionary arrives, or\n * without one. The server ships the same lines in ten languages under `webx-blog::` and\n * overrides these; `messages.test.ts` keeps the two sets of keys equal.\n */\nexport const blogMessages: Record<string, Messages> = {\n module: {\n group: 'Blog',\n articles: 'Articles',\n rubrics: 'Rubrics',\n tags: 'Tags',\n },\n panel: {\n new: 'New article',\n search: 'Search by title or address',\n empty: 'No articles yet.',\n 'empty-search': 'Nothing matches that.',\n 'empty-bin': 'The bin is empty.',\n\n 'column-title': 'Title',\n 'column-rubrics': 'Rubrics',\n 'column-author': 'Author',\n 'column-date': 'Date',\n 'column-status': 'Status',\n 'column-deleted': 'Deleted',\n\n 'status-draft': 'Draft',\n 'status-scheduled': 'Scheduled',\n 'status-published': 'Live',\n 'status-modified': 'Live',\n 'status-unpublished': 'Off the site',\n edits: 'edits',\n pinned: 'Pinned to the top',\n 'no-address': 'The address appears when it is published',\n 'no-author': 'Nobody',\n\n 'view-all': 'All',\n 'view-published': 'Live',\n 'view-scheduled': 'Scheduled',\n 'view-draft': 'Drafts',\n 'view-unpublished': 'Off the site',\n bin: 'Bin',\n\n 'filter-rubric': 'Rubric',\n 'filter-tag': 'Tag',\n 'filter-author': 'Author',\n 'any-rubric': 'Any rubric',\n 'any-tag': 'Any tag',\n 'any-author': 'Any author',\n\n open: 'Open',\n 'open-on-site': 'Open on the site',\n 'copy-address': 'Copy the address',\n pin: 'Pin to the top',\n unpin: 'Unpin',\n publish: 'Publish',\n unpublish: 'Take off the site',\n delete: 'Delete',\n restore: 'Restore',\n cancel: 'Cancel',\n\n 'delete-title': 'Delete “:title”?',\n 'delete-text': 'It goes to the bin and comes off the site, and its address is free again.',\n deleted: 'The article is in the bin.',\n restored: 'The article is back.',\n published: 'The article is on the site.',\n 'unpublished-done': 'The article is off the site.',\n 'pinned-done': 'Pinned to the top.',\n unpinned: 'No longer pinned.',\n 'address-copied': 'The address is on the clipboard.',\n\n 'new-title': 'New article',\n 'field-title': 'Title',\n create: 'Create',\n },\n article: {\n trail: 'Where this article sits',\n untitled: 'Untitled',\n save: 'Save',\n 'save-failed': 'The article was not saved.',\n schedule: 'Schedule',\n scheduled: 'The article is scheduled.',\n 'publish-title': 'Put “:title” on the site?',\n 'publish-text': 'It answers at :address from the moment you do, for everyone.',\n 'publish-later': 'It goes on the site on :date and not before. Until then nothing changes.',\n 'publish-nowhere': 'It has no address in this language yet, so nothing will answer.',\n 'publish-now': 'now',\n preview: 'Preview',\n more: 'More',\n discard: 'Discard changes',\n discarded: 'The article is back to what is published.',\n 'conflict-title': 'The article changed while you were editing',\n 'conflict-mine': 'Keep mine',\n 'conflict-theirs': 'Take the newer version',\n 'conflict-theirs-title': 'Give up what you wrote?',\n 'conflict-theirs-text':\n 'The article is read again as it now is, and what you have typed since goes.',\n leave: 'Leave',\n 'leave-title': 'Leave without saving?',\n 'leave-text': 'The article could not be saved, and what you wrote is not on the server.',\n 'live-scheduled': 'Goes out :date',\n 'live-since': 'On the site since :date',\n 'unpublish-title': 'Take “:title” off the site?',\n 'unpublish-text':\n 'It stops answering at its address. The draft, the history and the rubrics stay — publishing puts it back.',\n 'unpublish-waiting': 'It has not gone out yet. The day it was set for will pass without it.',\n 'unpublish-help': 'Takes the article off the site without deleting it.',\n 'address-moving':\n 'The address is changing. The old one keeps working and leads to the new one.',\n author: 'Author',\n 'no-author': 'Nobody',\n rubrics: 'Rubrics',\n 'rubrics-empty': 'In no rubric yet.',\n 'rubric-main': 'Main',\n 'rubric-add': 'Add a rubric',\n 'rubric-remove': 'Take out of this rubric',\n 'rubric-none-left': 'Every rubric is already on this article.',\n 'tags-empty': 'No tags yet.',\n 'tag-add': 'Find or make a tag',\n 'tag-remove': 'Take this tag off',\n 'tag-option': ':title · :count',\n 'tag-nothing': 'No tag is spelled that way.',\n 'tag-create': 'Make “:title”',\n related: 'Related',\n 'related-empty': 'Nothing pinned by hand.',\n 'related-add': 'Find an article',\n 'related-remove': 'Unpin',\n 'related-nothing': 'Nothing matches that.',\n 'history-empty': 'This article has never been published.',\n version: '#:number',\n 'version-live': 'On the site',\n 'source-panel': 'From the panel',\n 'source-mcp': 'By an agent',\n 'source-import': 'Imported',\n 'restore-title': 'Restore version :number?',\n 'restore-text':\n 'It becomes the draft. The site keeps showing what is published until you publish this.',\n 'restore-version': 'Restore',\n 'restored-version': 'Version :number is now the draft.',\n },\n rubric: {\n new: 'New rubric',\n empty: 'No rubrics yet.',\n 'empty-help': 'A rubric is a section of the blog. An article can be in several of them.',\n order: 'The order here is the order on the site',\n hidden: 'Hidden from the site',\n 'no-address': 'No address in this language',\n articles: ':count articles',\n 'show-articles': 'Show its articles',\n edit: 'Edit',\n\n 'tab-content': 'Content',\n 'tab-seo': 'SEO',\n\n 'field-title': 'Name',\n 'field-slug': 'Address',\n visible: 'Show on the site',\n 'visible-help':\n 'Hidden, it drops out of the menu and answers 404. Its articles go on answering at their own addresses.',\n 'field-lead': 'Introduction',\n 'lead-help': 'Printed above the list of articles.',\n 'cover-help': 'Shown wherever the site lists the sections.',\n 'address-moving':\n 'The address is changing. The old one keeps working and leads to the new one.',\n\n 'seo-help': 'Left empty, the title and the description of this page come from the SEO rules.',\n\n delete: 'Delete',\n 'delete-blocked': 'While it holds articles it cannot go — move them first.',\n 'delete-title': 'Delete “:name”?',\n 'delete-text': 'It goes to the bin and comes off the site, and its address is free again.',\n deleted: 'The rubric is in the bin.',\n\n cancel: 'Cancel',\n save: 'Save',\n saved: 'The rubric is saved.',\n created: 'The rubric is made.',\n 'reorder-failed': 'The new order was not saved.',\n },\n tag: {\n new: 'New tag',\n 'new-title': 'New tag',\n 'field-title': 'Name',\n 'field-slug': 'Address',\n 'field-slug-help': 'Left empty, it is made out of the name.',\n create: 'Create',\n cancel: 'Cancel',\n\n search: 'Search by word or address',\n empty: 'No tags yet.',\n 'no-address': 'No address in this language',\n\n 'column-title': 'Name',\n 'column-address': 'Address',\n 'column-articles': 'Articles',\n 'column-indexing': 'Indexing',\n\n 'view-all': 'All',\n 'view-empty': 'Nothing filed',\n 'view-noindex': 'Not indexed',\n 'sort-articles': 'Most used',\n 'sort-name': 'A to Z',\n\n 'indexing-open': 'indexed',\n 'indexing-rule': 'indexed — SEO rule',\n 'indexing-noindex': 'noindex',\n 'indexing-rule-help':\n 'A rule in the SEO section covers this address, so the page is in the index however this tag is set.',\n\n rename: 'Rename',\n renamed: 'The tag is renamed.',\n index: 'Index',\n noindex: 'Keep out of the index',\n indexed: 'The tag page is in the index.',\n 'kept-out': 'The tag page is out of the index.',\n 'indexed-many': ':count tag pages are in the index.',\n 'kept-out-many': ':count tag pages are out of the index.',\n 'open-on-site': 'Open on the site',\n 'show-articles': 'Show its articles',\n\n delete: 'Delete',\n 'delete-title': 'Delete “:title”?',\n 'delete-text': 'A tag has no bin: it is gone for good.',\n 'delete-text-used':\n 'It is on :count articles and comes off all of them. A tag has no bin: it is gone for good.',\n deleted: 'The tag is gone.',\n 'delete-many-title': 'Delete :count tags?',\n 'delete-many-text': 'A tag has no bin: they are gone for good.',\n 'delete-many-text-used':\n 'They are on :count articles between them and come off all of them. A tag has no bin.',\n 'deleted-many': ':count tags are gone.',\n\n selected: ':count selected',\n clear: 'Clear the selection',\n merge: 'Merge into one',\n merged: ':count articles now carry “:tag”.',\n 'merge-title': 'Merge tags',\n 'merge-keep': 'Which tag to keep',\n 'merge-redirect': 'Redirect the old addresses',\n 'merge-redirect-help': ':addresses will answer 301.',\n 'merge-redirect-none':\n 'These tags have no address in this language, so there is nothing to redirect.',\n 'merge-warning':\n ':count articles will carry “:tag” instead. :merged tags will be deleted, and there is no undoing it.',\n 'merge-confirm': 'Merge',\n },\n}\n","import { useAdmin } from '@webx-ui/module-admin'\nimport { blogMessages } 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 useBlogMessages(): void {\n try {\n const { i18n } = useAdmin()\n\n if (!seeded.has(i18n)) {\n i18n.defaults('webx-blog', blogMessages)\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 { useTranslate } from '@webx-ui/module-admin'\nimport { WxSelect, type SelectModelValue, type SelectOption } from '@webx-ui/core'\nimport { useArticleEditor } from './editor'\nimport { useBlogMessages } from './i18n'\n\n/**\n * Who signed the article: one administrator, or nobody (§2.14).\n *\n * A list rather than a text field, because the byline is a row in the table of administrators:\n * renaming somebody renames it everywhere, and an account that is gone leaves the article\n * unsigned instead of signed by a name nobody can look up.\n *\n * \"Nobody\" is the empty state of the field and not a line in the list. A row meaning \"none\" is\n * a row somebody picks by accident, and `SelectValue` has no room for `null` anyway — so the\n * answer is the clear button and a placeholder that says what an empty field means.\n */\ndefineOptions({ name: 'WxArticleAuthor' })\n\nconst props = withDefaults(defineProps<{ disabled?: boolean }>(), { disabled: false })\n\nconst value = defineModel<number | null>({ default: null })\n\nconst editor = useArticleEditor()\nuseBlogMessages()\n\nconst t = useTranslate('webx-blog')\nconst disabled = computed(() => props.disabled || editor?.disabled.value === true)\n\nconst options = computed<SelectOption[]>(() =>\n (editor?.options.value.authors ?? []).map((author) => ({\n label: author.title,\n value: author.id,\n })),\n)\n\nconst chosen = computed<SelectModelValue>({\n get: () => value.value,\n set: (next) => {\n value.value = typeof next === 'number' ? next : null\n },\n})\n</script>\n\n<template>\n <wx-select\n v-model=\"chosen\"\n :options=\"options\"\n :placeholder=\"t('article.no-author')\"\n :disabled=\"disabled\"\n clearable\n filterable\n teleport\n :aria-label=\"t('article.author')\"\n />\n</template>\n","import type { AdminContext } from '@webx-ui/module-admin'\nimport type {\n ArticleDetail,\n ArticleInput,\n ArticleQuery,\n ArticleRow,\n ArticleSave,\n ArticleVersion,\n ArticlesPage,\n BlogTag,\n TagInput,\n TagMassAction,\n TagMerged,\n TagQuery,\n TagRow,\n TagsPage,\n} from './types'\n\nexport interface BlogApi {\n /** A page of articles, with what the filters over them can be set to. */\n articles(query?: ArticleQuery): Promise<ArticlesPage>\n /** One article as its editor opens it: the record, the screen's values and the revision. */\n get(id: number): Promise<ArticleDetail>\n create(input: ArticleInput): Promise<ArticleRow>\n /**\n * Save. Refused with a 409 when the revision is not the current one — the body is an\n * {@link ArticleConflict} carrying the article as it now is, so the panel can offer to\n * re-read rather than quietly keep one of two edits.\n */\n save(id: number, input: ArticleSave): Promise<ArticleDetail>\n /** Throw away what is waiting and keep what the site is showing. */\n discard(id: number): Promise<ArticleDetail>\n /** The publications, newest first. */\n versions(id: number): Promise<ArticleVersion[]>\n /** An old publication becomes the draft; putting it on the site is a separate step. */\n restoreVersion(id: number, number: number): Promise<ArticleDetail>\n /** Tags matching what is being typed, most used first. */\n tags(term?: string): Promise<BlogTag[]>\n /**\n * A tag made from the article being written, because that is where tags come from (§2.8) —\n * and from the tags screen, which is the other place somebody spells a new word.\n */\n createTag(input: TagInput): Promise<TagRow>\n /**\n * On the site. `at` in the future schedules it; left out, it goes on now (§7). `revision` is\n * what the editor held: a draft that moved on since is answered 409, not published.\n */\n publish(id: number, at?: string | null, revision?: string): Promise<ArticleRow>\n unpublish(id: number): Promise<ArticleRow>\n remove(id: number): Promise<void>\n restore(id: number): Promise<ArticleRow>\n\n /** A page of tags with the counts the pills wear (§10). */\n tagsPage(query?: TagQuery): Promise<TagsPage>\n /** A rename, or the switch on the index. Only what travels is touched. */\n saveTag(id: number, input: TagInput): Promise<TagRow>\n removeTag(id: number): Promise<void>\n /** What the selection bar does to a pile of them at once. */\n massTags(ids: number[], action: TagMassAction): Promise<number>\n /** Several into one. Irreversible, and `redirect` is what outlives the tags that go (§6). */\n mergeTags(ids: number[], keep: number, redirect: boolean): Promise<TagMerged>\n}\n\n/**\n * Everything under `/blog`, below the panel's API path — except the rubrics, which are the\n * panel's shared categories and are asked for with `createCategoriesApi(admin, 'blog/rubrics')`.\n */\nexport function createBlogApi(admin: AdminContext): BlogApi {\n const base = `${admin.apiPath}/blog/articles`\n const tagsBase = `${admin.apiPath}/blog/tags`\n const data = <T>(body: { data: T }): T => body.data\n\n return {\n articles: (query = {}) => {\n const search = new URLSearchParams()\n\n // Only what was asked for: `rubric=` would ask for the articles of no rubric, which is a\n // question the list never means to put.\n if (query.q) search.set('q', query.q)\n if (query.rubric != null) search.set('rubric', String(query.rubric))\n if (query.tag != null) search.set('tag', String(query.tag))\n if (query.author != null) search.set('author', String(query.author))\n if (query.status) search.set('status', query.status)\n if (query.sort) search.set('sort', query.sort)\n if (query.trashed) search.set('trashed', '1')\n if (query.page && query.page > 1) search.set('page', String(query.page))\n if (query.per_page) search.set('per_page', String(query.per_page))\n\n const suffix = search.size > 0 ? `?${search}` : ''\n\n return admin.http\n .get<{\n data: ArticleRow[]\n meta: Omit<ArticlesPage, 'data' | 'filters'>\n filters: ArticlesPage['filters']\n }>(`${base}${suffix}`)\n .then((body) => ({ ...body.meta, data: body.data, filters: body.filters }))\n },\n get: (id) => admin.http.get<{ data: ArticleDetail }>(`${base}/${id}`).then(data),\n create: (input) => admin.http.post<{ data: ArticleRow }>(base, input).then(data),\n save: (id, input) => admin.http.put<{ data: ArticleDetail }>(`${base}/${id}`, input).then(data),\n discard: (id) =>\n admin.http.post<{ data: ArticleDetail }>(`${base}/${id}/discard`, {}).then(data),\n versions: (id) =>\n admin.http.get<{ data: ArticleVersion[] }>(`${base}/${id}/versions`).then(data),\n restoreVersion: (id, number) =>\n admin.http\n .post<{ data: ArticleDetail }>(`${base}/${id}/versions/${number}/restore`, {})\n .then(data),\n tags: (term = '') => {\n const suffix = term.trim() === '' ? '' : `?q=${encodeURIComponent(term.trim())}`\n\n return admin.http.get<{ data: BlogTag[] }>(`${tagsBase}${suffix}`).then(data)\n },\n createTag: (input) => admin.http.post<{ data: TagRow }>(tagsBase, input).then(data),\n publish: (id, at, revision) =>\n admin.http\n .post<{ data: ArticleRow }>(`${base}/${id}/publish`, {\n ...(at == null ? {} : { at }),\n ...(revision ? { revision } : {}),\n })\n .then(data),\n unpublish: (id) =>\n admin.http.post<{ data: ArticleRow }>(`${base}/${id}/unpublish`, {}).then(data),\n remove: (id) => admin.http.delete<void>(`${base}/${id}`).then(() => undefined),\n restore: (id) => admin.http.post<{ data: ArticleRow }>(`${base}/${id}/restore`, {}).then(data),\n\n tagsPage: (query = {}) => {\n const search = new URLSearchParams()\n\n if (query.q) search.set('q', query.q)\n // Only when they are on: `empty=0` would be a question the screen never means to put.\n if (query.empty) search.set('empty', '1')\n if (query.noindex) search.set('noindex', '1')\n if (query.sort) search.set('sort', query.sort)\n if (query.page && query.page > 1) search.set('page', String(query.page))\n if (query.per_page) search.set('per_page', String(query.per_page))\n\n const suffix = search.size > 0 ? `?${search}` : ''\n\n return admin.http\n .get<{\n data: TagRow[]\n meta: Omit<TagsPage, 'data' | 'filters'>\n filters: TagsPage['filters']\n }>(`${tagsBase}${suffix}`)\n .then((body) => ({ ...body.meta, data: body.data, filters: body.filters }))\n },\n saveTag: (id, input) => admin.http.put<{ data: TagRow }>(`${tagsBase}/${id}`, input).then(data),\n removeTag: (id) => admin.http.delete<void>(`${tagsBase}/${id}`).then(() => undefined),\n massTags: (ids, action) =>\n admin.http\n .post<{ data: { affected: number } }>(`${tagsBase}/mass`, { ids, action })\n .then((body) => body.data.affected),\n mergeTags: (ids, keep, redirect) =>\n admin.http.post<{ data: TagMerged }>(`${tagsBase}/merge`, { ids, keep, redirect }).then(data),\n }\n}\n","<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'\nimport { onBeforeRouteLeave, useRoute, useRouter } from 'vue-router'\nimport {\n useAdmin,\n useDates,\n useEditing,\n useErrorText,\n useTranslate,\n WxEditingAlerts,\n WxSaveState,\n WxScreen,\n WxScreenHead,\n type ScreenAction,\n} from '@webx-ui/module-admin'\nimport { provideBlocksPreview } from '@webx-ui/module-blocks'\nimport {\n confirm,\n localizedValue,\n toast,\n useLocales,\n WxActionBar,\n WxBadge,\n WxBreadcrumb,\n WxBreadcrumbItem,\n WxButton,\n WxCard,\n WxSkeleton,\n type LocalizedValue,\n} from '@webx-ui/core'\nimport type { ScreenModel } from '@webx-ui/schema'\nimport { createBlogApi } from './api'\nimport { provideArticleEditor } from './editor'\nimport { useBlogMessages } from './i18n'\nimport type { ArticleConflict, ArticleDetail, ArticleOption, ArticleRow } from './types'\n\n/**\n * The editor of one article: a head that stays put, and the described screen under it.\n *\n * What this page owns is the head — what the article is called, what state it is in, and the\n * way out to the site — and the bar along the bottom, which is where it is saved and published.\n * Everything between them is `blog.article-form`, so a module or a project adds a tab to the\n * editor with a patch (§12) rather than with a fork of this file.\n *\n * Saving is by autosave: a pause after the last keystroke, and the moment a field is left. The\n * explicit button is still there, because a button that says \"saved\" is the only way a person\n * can be sure — but nothing is ever lost by not pressing it.\n *\n * The one thing here that a page editor does not have is the day. An article carries a date\n * that may be in the future, so \"publish\" means \"publish under the date in the settings tab\",\n * and which day that is stands under the name before the button is pressed (§7).\n */\nconst props = withDefaults(defineProps<{ base?: string }>(), { base: '/blog' })\n\nconst context = useAdmin()\nconst api = createBlogApi(context)\nconst route = useRoute()\nconst router = useRouter()\nconst locales = useLocales()\nconst dates = useDates()\nuseBlogMessages()\n\nconst t = useTranslate('webx-blog')\nconst panel = useTranslate('webx-admin')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\n/** How long after the last keystroke the draft goes to the server. */\nconst PAUSE = 1200\n\nconst id = computed(() => Number(route.params.id))\nconst list = computed(() => `${props.base}/articles`)\n\nconst article = ref<ArticleRow | null>(null)\nconst values = ref<ScreenModel>({})\nconst revision = ref('')\nconst prefix = ref('')\nconst previewUrl = ref<string | null>(null)\nconst options = ref<ArticleDetail['options']>({ rubrics: [], authors: [] })\nconst related = ref<ArticleOption[]>([])\n\nconst loading = ref(true)\nconst saving = ref(false)\nconst working = ref(false)\nconst errors = ref<Record<string, string[]>>({})\n\nconst snapshot = ref('')\nconst reloadToken = ref(0)\n\nconst canManage = computed(() => context.can('blog.articles.manage'))\n\n/*\n * Other people on the same article: a refused save is merged with theirs field by field and\n * saved again, only a field both changed is asked about, and a save made meanwhile — by a\n * colleague or an agent — is offered before this editor writes over it.\n */\nconst editing = useEditing<ScreenModel>({\n entity: 'articles',\n id: () => article.value?.id,\n values,\n revision,\n read: async () => {\n const detail = await api.get(id.value)\n\n return { values: detail.values, revision: detail.revision }\n },\n adopt: (theirs) => {\n snapshot.value = JSON.stringify(theirs.values)\n reloadToken.value += 1\n },\n // Published or restored by somebody else: the badge and the address follow, and the form\n // stays as it is.\n refresh: () => refresh(),\n restore: async () => {\n if (article.value) await api.restore(article.value.id)\n },\n canWrite: () => canManage.value,\n busy: () => saving.value || flight !== undefined,\n screen: 'blog.article-form',\n})\n\nconst conflict = editing.conflict\n\n/** Closed for writing: no permission, or a publication in flight. */\nconst locked = computed(() => !canManage.value || working.value)\n\nconst current = computed(() => JSON.stringify(values.value))\nconst dirty = computed(() => snapshot.value !== '' && current.value !== snapshot.value)\n\n/** Saved · saving · not saved yet — the one line of the bar that moves while typing. */\nconst state = computed<'saving' | 'unsaved' | 'saved'>(() => {\n if (saving.value) return 'saving'\n\n return dirty.value ? 'unsaved' : 'saved'\n})\n\n/**\n * The name in the head follows the field rather than the answer: an editor who renamed the\n * article in the settings tab should see the new name at the top before the save lands.\n */\nconst title = computed(() => {\n const written = values.value.title as LocalizedValue | string | null | undefined\n\n return localizedValue(written, locales.active.value, article.value?.title ?? '')\n})\n\n/** The day in the settings tab, as the picker leaves it: `2026-09-24 08:00:00`, or nothing. */\nconst chosen = computed<string | null>(() => {\n const written = values.value.published_at\n\n return typeof written === 'string' && written !== '' ? written : null\n})\n\nconst future = computed(() => {\n if (chosen.value === null) return false\n\n // The picker's value is local time with a space in it, which Safari refuses to parse.\n const at = new Date(chosen.value.replace(' ', 'T'))\n\n return !Number.isNaN(at.valueOf()) && at.valueOf() > Date.now()\n})\n\n/**\n * When the article goes out, under its name.\n *\n * Only the day, and only when there is one. What state it is in the badges beside the name say\n * already — live, scheduled, with edits waiting — and saying it twice in two shapes is how a\n * screen teaches an editor to read neither. The day is the part a badge cannot carry, and the\n * part somebody about to press \"publish\" actually weighs.\n */\nconst publication = computed(() => {\n const row = article.value\n\n if (!row) return ''\n\n const when = dates.short(row.published_at)\n\n const line = {\n scheduled: t('article.live-scheduled', { date: when }),\n published: t('article.live-since', { date: when }),\n modified: t('article.live-since', { date: when }),\n }[row.status as 'scheduled' | 'published' | 'modified']\n\n return line ?? ''\n})\n\n/* The constructor shows the draft as a page of the site. The link is the editor's to hand\n over: only this screen knows which entity it is editing. */\nprovideBlocksPreview({ url: computed(() => previewUrl.value), reload: reloadToken })\n\nprovideArticleEditor({\n article,\n values,\n options,\n related,\n prefix,\n base: props.base,\n canManage: canManage.value,\n disabled: locked,\n reload: () => load(true),\n save: () => save(),\n})\n\nfunction take(detail: ArticleDetail): void {\n article.value = detail.article\n values.value = detail.values\n revision.value = detail.revision\n prefix.value = detail.prefix\n previewUrl.value = detail.preview_url\n options.value = detail.options\n related.value = detail.related\n snapshot.value = JSON.stringify(detail.values)\n editing.opened(detail.values)\n conflict.value = null\n}\n\n/**\n * Read the article again.\n *\n * Quietly unless this is the first time: the skeleton replaces the whole screen, tabs and all,\n * so a publication answered with it would throw an editor who published from the history back\n * onto the first tab — and flash the page on the way.\n */\nasync function load(silent = false): Promise<void> {\n if (!silent) loading.value = true\n\n try {\n take(await api.get(id.value))\n } catch (error) {\n toast.danger(message(error))\n void router.push(list.value)\n } finally {\n loading.value = false\n }\n}\n\n/**\n * What surrounds the form, read again: the row with its status, the address, the preview. Not\n * the values — the form is somebody's work in progress, and a change of its content is the\n * notice's to offer.\n */\nasync function refresh(): Promise<void> {\n try {\n const detail = await api.get(id.value)\n\n article.value = detail.article\n prefix.value = detail.prefix\n previewUrl.value = detail.preview_url\n } catch {\n // The heartbeat says what happened; a row that could not be read again stays as it was.\n }\n}\n\n/* One pending save at a time, and one pending pause. */\nlet timer: ReturnType<typeof setTimeout> | undefined\n\nfunction schedule(): void {\n if (!canManage.value || conflict.value || !dirty.value || editing.stopped.value) return\n\n clearTimeout(timer)\n timer = setTimeout(() => void save(), PAUSE)\n}\n\n/** A field was left: write now rather than at the end of a pause nobody is waiting through. */\nfunction onFocusOut(): void {\n if (!canManage.value || conflict.value || !dirty.value || saving.value || editing.stopped.value)\n return\n\n clearTimeout(timer)\n void save()\n}\n\n/* The save on its way, and what it carries. */\nlet flight: { carried: string; done: Promise<void> } | undefined\n\n/**\n * A save asked for while another is on its way waits for it instead of being dropped: leaving and\n * publishing read `dirty` right after, and a save that was skipped reads as one that failed.\n */\nasync function save(): Promise<void> {\n while (flight) {\n const { carried, done } = flight\n\n await done\n\n // That request wrote what it carried; go again only for what was typed while it was out.\n if (!dirty.value || conflict.value || current.value === carried) return\n }\n\n const carried = current.value\n const done = write()\n\n flight = { carried, done }\n\n try {\n await done\n } finally {\n if (flight?.done === done) flight = undefined\n }\n}\n\nasync function write(): Promise<void> {\n if (!article.value || !canManage.value) return\n\n clearTimeout(timer)\n\n // What is being sent, so that whatever is typed while the request is in flight stays dirty\n // and gets its own save rather than being marked as written.\n const sending = current.value\n const sent = values.value\n\n saving.value = true\n errors.value = {}\n\n try {\n const detail = await api.save(article.value.id, { values: sent, revision: revision.value })\n\n article.value = detail.article\n revision.value = detail.revision\n prefix.value = detail.prefix\n previewUrl.value = detail.preview_url\n options.value = detail.options\n related.value = detail.related\n conflict.value = null\n\n if (current.value === sending) snapshot.value = sending\n\n // What the server holds now is what was sent: the base of the next merge.\n editing.opened(sent)\n\n // The preview is rendered from the draft, and the draft is what was just written.\n reloadToken.value += 1\n } catch (error) {\n const failure = error as {\n status?: number\n body?: ArticleConflict & { errors?: Record<string, string[]> }\n }\n\n if (failure.status === 409 && failure.body?.data) {\n const theirs = failure.body.data\n\n // Nothing overlapping: both edits are in the form now, and they go to the server again.\n if (\n editing.refused({\n values: theirs.values,\n revision: theirs.revision,\n changed: failure.body.changed,\n })\n ) {\n article.value = theirs.article\n void save()\n }\n } else if (failure.body?.errors) {\n errors.value = failure.body.errors\n toast.danger(t('article.save-failed'))\n } else if (!(await editing.failed(error))) {\n // In the bin is said by the notice above the form, with the way back; anything else here.\n toast.danger(message(error))\n }\n } finally {\n saving.value = false\n }\n}\n\n/** Give up what was typed and take the article as it now is; the component asked first. */\nasync function takeTheirs(): Promise<void> {\n await load(true)\n}\n\n/**\n * Throw away what is waiting and keep what the site is showing (§10).\n *\n * Offered only while there is a difference to throw away. Asked about, because it is the one\n * button on this screen that destroys writing — and answered honestly: the last few minutes are\n * still in the autosave ring, but nothing in the panel lists them, so the question does not\n * promise a way back.\n */\nasync function discard(): Promise<void> {\n if (!article.value) return\n\n const agreed = await confirm({\n title: panel('editor.discard-title'),\n message: panel('editor.discard-text'),\n confirmText: t('article.discard'),\n // Not «Cancel»: beside «Discard changes» the two read as the same word.\n cancelText: panel('editor.keep-changes'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n working.value = true\n\n try {\n take(await api.discard(article.value.id))\n toast.success(t('article.discarded'))\n } catch (error) {\n toast.danger(message(error))\n } finally {\n working.value = false\n }\n}\n\n/**\n * Publishing is asked about, because it is the one action here that visitors see: everything\n * else on this screen writes a draft nobody outside the panel can read.\n *\n * The question names the day as well as the address, and that is the whole point of asking. A\n * date in the future means the article does not appear — it waits — and an editor who thought\n * they were publishing now would go and look for it on the site (§7).\n */\nasync function publish(): Promise<void> {\n // Saved before asking: the question names the address the draft will publish at, and only a\n // saved draft has one — an address typed but not saved was asked about under the old one.\n if (dirty.value) await save()\n if (conflict.value || dirty.value) return\n\n if (!article.value) return\n\n // The draft on the server may hold somebody else's edit this editor never pulled in: said\n // before it goes on the site, with whose it is and where, rather than published unseen.\n const held = await editing.beforePublish()\n\n if (!held) return\n\n const row = article.value\n\n // Publishing moves a renamed slug, so the question names where the page will be, not where\n // it is — and says the old address will lead there, since that is what happens to it.\n const next = row.next_path ?? row.path\n const old =\n row.next_path != null && row.path !== null\n ? ` ${panel('editor.publish-moves', { old: `/${row.path}` })}`\n : ''\n const address = next === null ? null : `/${next}`\n const when = chosen.value === null ? t('article.publish-now') : dates.short(chosen.value)\n\n // One question is enough: whoever just agreed to publish somebody else's changes has said yes.\n const agreed =\n held.asked ||\n (await confirm({\n title: t('article.publish-title', { title: row.title }),\n message: future.value\n ? t('article.publish-later', { date: when })\n : address === null\n ? t('article.publish-nowhere')\n : t('article.publish-text', { address }) + old,\n confirmText: future.value ? t('article.schedule') : t('panel.publish'),\n cancelText: t('panel.cancel'),\n }))\n\n if (!agreed) return\n\n working.value = true\n\n try {\n await api.publish(row.id, chosen.value, held.revision)\n await load(true)\n toast.success(future.value ? t('article.scheduled') : t('panel.published'))\n } catch (error) {\n if (!(await editing.failed(error))) toast.danger(message(error))\n } finally {\n working.value = false\n }\n}\n\n/**\n * Taking it off the site is asked about, as publishing is: visitors are who notice. Nothing\n * written is lost, and «Publish» brings it back at the same address.\n */\nasync function unpublish(): Promise<void> {\n const row = article.value\n\n if (!row) return\n\n const agreed = await confirm({\n title: panel('editor.unpublish-title'),\n message: panel('editor.unpublish-text'),\n confirmText: panel('editor.unpublish'),\n cancelText: panel('editor.keep-published'),\n })\n\n if (!agreed) return\n\n working.value = true\n\n try {\n await api.unpublish(row.id)\n await load(true)\n toast.success(panel('editor.unpublished'))\n } catch (error) {\n toast.danger(message(error))\n } finally {\n working.value = false\n }\n}\n\nfunction badge(): 'default' | 'success' | 'warning' | 'primary' {\n const status = article.value?.status\n\n if (status === 'published') return 'success'\n if (status === 'modified') return 'warning'\n\n return status === 'scheduled' ? 'primary' : 'default'\n}\n\n/* The browser's own guard: it cannot wait for a request, so all it can do is ask. */\nfunction leaveGuard(event: BeforeUnloadEvent): void {\n if (dirty.value) event.preventDefault()\n}\n\nwatch(current, schedule)\nwatch(id, () => void load())\n\nonMounted(() => {\n void load()\n window.addEventListener('beforeunload', leaveGuard)\n})\n\nonBeforeUnmount(() => {\n clearTimeout(timer)\n window.removeEventListener('beforeunload', leaveGuard)\n})\n\n/**\n * Leaving flushes the pause rather than asking about it: everything else on this screen saves\n * by itself, and a dialog that appears only because somebody left within a second of typing\n * would be the one place that does not. The question is asked only when the save did not go\n * through — a conflict, a refused field, a server that is not there.\n */\nonBeforeRouteLeave(async () => {\n if (!dirty.value || !canManage.value) return true\n\n await save()\n\n if (!dirty.value) return true\n\n return await confirm({\n title: t('article.leave-title'),\n message: t('article.leave-text'),\n confirmText: t('article.leave'),\n cancelText: t('panel.cancel'),\n tone: 'danger',\n })\n})\n/* What leads away from the article. On a phone the head folds them into the ···. */\nconst actions = computed<ScreenAction[]>(() => {\n const leads: ScreenAction[] = []\n\n if (previewUrl.value) {\n leads.push({ key: 'preview', label: t('article.preview'), icon: 'eye', href: previewUrl.value })\n }\n\n if (article.value?.url && article.value.status !== 'draft') {\n leads.push({\n key: 'site',\n label: t('panel.open-on-site'),\n icon: 'link',\n href: article.value.url,\n })\n }\n\n // On the site now: the way off it is a button of its own beside «Open on the site», not a\n // line in the ···. Hiding loses nothing and is undone by «Publish».\n if (\n canManage.value &&\n (article.value?.status === 'published' || article.value?.status === 'modified')\n ) {\n leads.push({\n key: 'unpublish',\n label: panel('editor.unpublish'),\n icon: 'eye-off',\n run: () => void unpublish(),\n })\n }\n\n /*\n * Throwing away what was written since the last publication: in the `···` at every width and\n * never a button, which is what `menu` is for. It used to stand in the action bar next to\n * \"publish\", one slip away from it — and it is offered at all only while there is a\n * difference between what is written and what is on the site.\n */\n if (article.value?.status === 'modified') {\n leads.push({\n key: 'discard',\n label: t('article.discard'),\n icon: 'refresh',\n danger: true,\n menu: true,\n run: () => void discard(),\n })\n }\n\n return leads\n})\n</script>\n\n<template>\n <!-- No `data-wx-fill`: this screen is a window tall only while the constructor is the tab on\n screen, and that is a question only CSS can ask. `WxMain` still gives it a floor of one\n window, because it carries an action bar. -->\n <div class=\"wx-article-editor\" @focusout=\"onFocusOut\">\n <template v-if=\"loading || !article\">\n <!-- Shaped like the screen it stands in for: the head on the ground, and what it is\n waiting for on the card that will hold it. Two bare strips on an empty background\n read as a page that failed rather than one that is a moment away. -->\n <wx-skeleton class=\"wx-article-editor__ghost\" title :rows=\"1\" />\n <wx-card><wx-skeleton :rows=\"8\" /></wx-card>\n </template>\n\n <template v-else>\n <!--\n The panel's head: the way out, the trail, the name and the state of the article. What is\n left in it is what leads away from the article — the draft on the site and the article on\n the site. Saving and publishing are down in the bar, where the eye is, and only there: a\n second copy of a button already on screen is not a reminder (§3.3).\n -->\n <wx-screen-head\n divider\n :back=\"list\"\n :back-label=\"t('module.articles')\"\n :title=\"title || t('article.untitled')\"\n :subtitle=\"publication\"\n :actions=\"actions\"\n >\n <template #trail>\n <wx-breadcrumb size=\"sm\" :label=\"t('article.trail')\">\n <wx-breadcrumb-item :as=\"'router-link'\" :to=\"list\">\n {{ t('module.articles') }}\n </wx-breadcrumb-item>\n <wx-breadcrumb-item current>{{ title || t('article.untitled') }}</wx-breadcrumb-item>\n </wx-breadcrumb>\n </template>\n\n <template #title-after>\n <wx-badge :type=\"badge()\" dot>{{ t(`panel.status-${article.status}`) }}</wx-badge>\n <!-- What is waiting, said beside the state rather than instead of it, exactly as the\n list says it: the article is on the site, and there is something that is not.\n The bar used to carry this in words; the head is where the state of the record\n already lives. -->\n <wx-badge v-if=\"article.status === 'modified'\" type=\"primary\" round>\n {{ t('panel.edits') }}\n </wx-badge>\n </template>\n </wx-screen-head>\n\n <!-- Somebody else wrote while this editor was open. What does not overlap is merged\n without a word; what does is listed place by place. -->\n <wx-editing-alerts :editing=\"editing\" @save=\"save\" @theirs=\"takeTheirs\" />\n\n <div class=\"wx-article-editor__screen\">\n <wx-screen v-model=\"values\" name=\"blog.article-form\" :errors=\"errors\" :disabled=\"locked\" />\n </div>\n\n <!--\n The last row of the screen, not a layer over it: the tab above shrinks by the height of\n the bar and is never covered by it.\n -->\n <!--\n Two buttons and a mark, the same bar the page editor has.\n\n What used to stand here besides them: a sentence saying since when the article is on the\n site and that there are unpublished edits, and a third button to throw those edits away.\n Half the sentence is what the badge beside the name already says, the other half is a\n fact about the article rather than about the last keystroke and now stands under its\n name; and throwing work away is not a button to keep beside \"publish\" — it is in the\n `···`, where this panel keeps everything that cannot be undone.\n -->\n <wx-action-bar v-if=\"canManage\">\n <template #state>\n <wx-save-state v-if=\"!editing.stopped.value\" :state=\"state\" />\n </template>\n\n <wx-button\n variant=\"outline\"\n :loading=\"saving\"\n :disabled=\"!dirty || editing.stopped.value\"\n :title=\"editing.blocked.value\"\n @click=\"save\"\n >\n {{ t('article.save') }}\n </wx-button>\n\n <wx-button\n type=\"primary\"\n :loading=\"working\"\n :disabled=\"editing.stopped.value || (article.status === 'published' && !dirty)\"\n :title=\"editing.blocked.value\"\n @click=\"publish\"\n >\n {{ future ? t('article.schedule') : t('panel.publish') }}\n </wx-button>\n </wx-action-bar>\n </template>\n </div>\n</template>\n\n<style scoped>\n.wx-article-editor {\n display: flex;\n flex-direction: column;\n gap: var(--wx-gap, var(--wx-space-16));\n /* The constructor is the whole screen below the head, so the page is as tall as what it is\n drawn in and never taller: the tree, the form and the preview scroll inside themselves,\n and the head stays where it was put. */\n height: 100%;\n min-height: 0;\n container-type: inline-size;\n}\n\n.wx-article-editor__ghost {\n max-width: 420px;\n}\n\n.wx-article-editor__screen {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-height: 0;\n}\n\n/*\n * The tabs are the screen's, not this page's, so the height has to travel through them: every\n * box between the page and the constructor is a flex column that may shrink, or the constructor\n * sizes itself to its content and the whole page scrolls with the head on top of it.\n *\n * `:not([hidden])` is load-bearing. The tabs are kept alive, so the three that are not on\n * screen are still in the document, and a `display: flex` that reached them would make each one\n * a quarter of the height the visible one should have had.\n */\n.wx-article-editor__screen :deep(.wx-screen-host),\n.wx-article-editor__screen :deep(.wx-screen),\n.wx-article-editor__screen :deep(.wx-tabs),\n.wx-article-editor__screen :deep(.wx-tabs__layout),\n.wx-article-editor__screen :deep(.wx-tabs__panels),\n.wx-article-editor__screen :deep(.wx-tab:not([hidden])) {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-height: 0;\n}\n\n/*\n * No tab is a box of a fixed height any more, and none has a scrollbar of its own: the\n * constructor's preview is as tall as the page it shows and the browser scrolls it. A tab\n * that grows with its contents is also the only kind that does not clip them — a scroll box\n * cut the shadows off the cards inside it square at all four edges.\n */\n</style>\n","<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\nimport { useAdmin, useErrorText, useTranslate, WxDate, WxDrafts } from '@webx-ui/module-admin'\nimport { confirm, toast, WxBadge, WxButton, WxEmpty, WxSkeleton, WxText } from '@webx-ui/core'\nimport { createBlogApi } from './api'\nimport { useArticleEditor } from './editor'\nimport { useBlogMessages } from './i18n'\nimport type { ArticleVersion } from './types'\n\n/**\n * What was published, when, by whom and from where.\n *\n * Publications first, and the copies of the draft under them in a list of their own: the autosave\n * ring, and a draft somebody else's save wrote over. Mixed into one line they would be a list\n * nobody can read; left out, an edit written over would have nowhere to be found.\n *\n * Restoring makes the old version the draft. Publishing it is the same separate step it always\n * is, which is what keeps the history a line: nothing here changes the site by itself.\n */\ndefineOptions({ name: 'WxArticleHistory' })\n\nconst context = useAdmin()\nconst api = createBlogApi(context)\nconst editor = useArticleEditor()\nuseBlogMessages()\n\nconst t = useTranslate('webx-blog')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\nconst versions = ref<ArticleVersion[] | null>(null)\nconst working = ref(false)\n\nconst article = computed(() => editor?.article.value ?? null)\nconst canManage = computed(() => context.can('blog.articles.manage'))\n\n/**\n * Which line is the site: the newest publication, and only while the article is on the site at\n * all. One taken down still has its history, and none of it is live — and neither is the\n * history of one dated next Tuesday, which is the difference an article has and a page has not.\n */\nconst live = computed(() =>\n article.value?.status === 'published' || article.value?.status === 'modified'\n ? (versions.value?.[0]?.number ?? null)\n : null,\n)\n\nasync function load(): Promise<void> {\n const current = article.value\n\n if (!current) return\n\n try {\n versions.value = await api.versions(current.id)\n } catch (error) {\n versions.value = []\n toast.danger(message(error))\n }\n}\n\nasync function restore(version: ArticleVersion): Promise<void> {\n const current = article.value\n\n if (!current) return\n\n const agreed = await confirm({\n title: t('article.restore-title', { number: version.number }),\n message: t('article.restore-text'),\n confirmText: t('article.restore-version'),\n cancelText: t('panel.cancel'),\n })\n\n if (!agreed) return\n\n working.value = true\n\n try {\n await api.restoreVersion(current.id, version.number)\n // Through the editor rather than with the answer: the values it holds are what the form is\n // bound to, and a version restored underneath it has to reach the fields.\n await editor?.reload()\n toast.success(t('article.restored-version', { number: version.number }))\n } catch (error) {\n toast.danger(message(error))\n } finally {\n working.value = false\n }\n}\n\n// The article is read again after every publication and every restore, so its stamp is the\n// signal that there is something new to list.\nwatch(\n () => [article.value?.id, article.value?.published_at, article.value?.updated_at],\n () => void load(),\n { immediate: true },\n)\n</script>\n\n<template>\n <div class=\"wx-article-history-tab\">\n <div class=\"wx-article-history\">\n <!-- Where a row would stand, not against the border: a skeleton flush with a rounded\n corner has its ends clipped by it, which reads as a drawing fault rather than as\n something loading. -->\n <wx-skeleton v-if=\"versions === null\" class=\"wx-article-history__ghost\" :rows=\"3\" />\n <wx-empty v-else-if=\"versions.length === 0\" :description=\"t('article.history-empty')\" />\n <div\n v-for=\"version in versions\"\n v-else\n :key=\"version.number\"\n class=\"wx-article-history__row\"\n :class=\"{ 'is-live': version.number === live }\"\n >\n <code class=\"wx-article-history__number\">{{\n t('article.version', { number: version.number })\n }}</code>\n <div class=\"wx-article-history__who\">\n <wx-date v-if=\"version.created_at\" :value=\"version.created_at\" size=\"md\" tone=\"default\" />\n <wx-text size=\"sm\" tone=\"muted\">\n {{ version.author ?? t(`article.source-${version.source}`) }}\n <template v-if=\"version.comment\"> · {{ version.comment }}</template>\n </wx-text>\n </div>\n <wx-badge v-if=\"version.number === live\" type=\"success\" dot>{{\n t('article.version-live')\n }}</wx-badge>\n <wx-button\n v-if=\"canManage && version.number !== live\"\n size=\"sm\"\n variant=\"outline\"\n :loading=\"working\"\n @click=\"restore(version)\"\n >\n {{ t('article.restore-version') }}\n </wx-button>\n </div>\n </div>\n\n <wx-drafts\n :id=\"article?.id\"\n entity=\"articles\"\n screen=\"blog.article-form\"\n :can-restore=\"canManage\"\n :stamp=\"[article?.published_at, article?.updated_at]\"\n @restored=\"editor?.reload()\"\n />\n </div>\n</template>\n\n<style scoped>\n.wx-article-history-tab {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-24);\n}\n\n.wx-article-history {\n display: flex;\n flex-direction: column;\n border: 1px solid var(--wx-border-default);\n border-radius: var(--wx-radius-md);\n background: var(--wx-bg-surface);\n /* Nothing reaches past the rounding: a row, or the skeleton standing in for three of them,\n is a square box and its corners showed through the panel's. */\n overflow: hidden;\n}\n\n.wx-article-history__ghost {\n padding: var(--wx-space-10) var(--wx-space-12);\n}\n\n.wx-article-history__row {\n display: flex;\n align-items: center;\n gap: var(--wx-space-12);\n padding: var(--wx-space-10) var(--wx-space-12);\n border-block-end: 1px solid var(--wx-border-default);\n flex-wrap: wrap;\n}\n\n.wx-article-history__row:last-child {\n border-block-end: 0;\n}\n\n.wx-article-history__number {\n width: 40px;\n font-family: var(--wx-font-family-mono);\n font-size: var(--wx-font-size-sm);\n color: var(--wx-text-muted);\n}\n\n.wx-article-history__who {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-width: 160px;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, ref, watch } from 'vue'\nimport { useAdmin, useErrorText, useTranslate } from '@webx-ui/module-admin'\nimport {\n toast,\n WxAction,\n WxSelect,\n WxSortableList,\n type SelectModelValue,\n type SelectOption,\n} from '@webx-ui/core'\nimport { createBlogApi } from './api'\nimport { useArticleEditor } from './editor'\nimport { useBlogMessages } from './i18n'\nimport type { ArticleOption } from './types'\n\n/**\n * The articles pinned under this one by hand, in the order they are shown.\n *\n * Only the pinned ones. What fills the rest of the list is worked out on the site — most tags\n * in common, then the main rubric, published only (§8) — and it is not shown here because it\n * would be a list that changes under the editor without anybody touching it. A fully manual\n * list is what nobody fills in after the hundredth article; a fully automatic one is what\n * nobody can override when it is wrong. This is the override.\n */\ndefineOptions({ name: 'WxArticleRelated' })\n\nconst props = withDefaults(defineProps<{ disabled?: boolean }>(), { disabled: false })\n\nconst value = defineModel<number[]>({ default: () => [] })\n\nconst context = useAdmin()\nconst api = createBlogApi(context)\nconst editor = useArticleEditor()\nuseBlogMessages()\n\nconst t = useTranslate('webx-blog')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\nconst disabled = computed(() => props.disabled || editor?.disabled.value === true)\n\n/** How long after the last keystroke the server is asked. */\nconst PAUSE = 250\n\n/** As many matches as a dropdown is worth scrolling; past that the answer is to type more. */\nconst MATCHES = 10\n\nconst known = ref(new Map<number, string>())\nconst found = ref<ArticleOption[]>([])\n\nlet timer: ReturnType<typeof setTimeout> | undefined\n\nwatch(\n () => editor?.related.value,\n (titles) => {\n const all = new Map(known.value)\n\n for (const one of titles ?? []) all.set(one.id, one.title)\n\n known.value = all\n },\n { immediate: true, deep: true },\n)\n\nconst rows = computed<ArticleOption[]>({\n get: () => value.value.map((id) => ({ id, title: known.value.get(id) ?? `#${id}` })),\n set: (next) => {\n value.value = next.map((row) => row.id)\n },\n})\n\nconst options = computed<SelectOption[]>(() =>\n found.value\n .filter((one) => !value.value.includes(one.id))\n .map((one) => ({ label: one.title, value: one.id })),\n)\n\nasync function look(term: string): Promise<void> {\n const mine = editor?.article.value?.id\n\n try {\n const page = await api.articles({ q: term, per_page: MATCHES })\n\n // Never itself: the list under an article would offer the reader the page they are on.\n found.value = page.data\n .filter((row) => row.id !== mine)\n .map((row) => ({ id: row.id, title: row.title }))\n\n const all = new Map(known.value)\n\n for (const one of found.value) all.set(one.id, one.title)\n\n known.value = all\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\nfunction search(term: string): void {\n clearTimeout(timer)\n timer = setTimeout(() => void look(term), PAUSE)\n}\n\nfunction add(picked: SelectModelValue): void {\n if (typeof picked !== 'number' || value.value.includes(picked)) return\n\n value.value = [...value.value, picked]\n}\n\nfunction remove(id: number): void {\n value.value = value.value.filter((one) => one !== id)\n}\n\nwatch(\n () => editor?.article.value?.id,\n () => void look(''),\n { immediate: true },\n)\n\nonBeforeUnmount(() => clearTimeout(timer))\n</script>\n\n<template>\n <div class=\"wx-article-related\">\n <wx-sortable-list\n v-model=\"rows\"\n plain\n size=\"sm\"\n item-key=\"id\"\n item-label=\"title\"\n :disabled=\"disabled\"\n :empty-text=\"t('article.related-empty')\"\n :aria-label=\"t('article.related')\"\n >\n <template #default=\"{ item }\">\n <span class=\"wx-article-related__name\">{{ (item as ArticleOption).title }}</span>\n </template>\n\n <template #actions=\"{ item }\">\n <wx-action\n type=\"remove\"\n size=\"sm\"\n :disabled=\"disabled\"\n :title=\"t('article.related-remove')\"\n @click=\"remove((item as ArticleOption).id)\"\n />\n </template>\n </wx-sortable-list>\n\n <wx-select\n :model-value=\"null\"\n :options=\"options\"\n :placeholder=\"t('article.related-add')\"\n :empty-text=\"t('article.related-nothing')\"\n :disabled=\"disabled\"\n filterable\n teleport\n :aria-label=\"t('article.related-add')\"\n @search=\"search\"\n @update:model-value=\"add\"\n />\n </div>\n</template>\n\n<style scoped>\n.wx-article-related {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-8);\n}\n\n.wx-article-related__name {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { useTranslate } from '@webx-ui/module-admin'\nimport { useLocales, WxAlert, WxInput } from '@webx-ui/core'\nimport { useArticleEditor } from './editor'\nimport { useBlogMessages } from './i18n'\n\n/**\n * The address of an article: the part that is edited, with the part that is not in front of it.\n *\n * The field edits the last segment, and `signs-of-wear` on its own says nothing about whether\n * the blog lives at the root of the site or under `/blog/` — which is exactly what somebody\n * checking an address wants to see. That used to be a row of its own under the field, labelled\n * \"Address\" directly below a field labelled \"Address\": a whole line of the form spent on one\n * constant segment, and two labels that taught the reader to skim both. The prefix belongs\n * inside the control, where the address is read and written in one place.\n *\n * The line under it appears only once there is something to lose: an article that is on the\n * site and whose address is being changed leaves a redirect behind, and that is the fact that\n * decides whether this is safe to do at all. Said before the save rather than in a toast\n * afterwards.\n *\n * Nothing is declared as a prop on purpose. The screen hands a field its `name`, its value,\n * `localized` and whatever the description put in `props`, and a wrapper that declared any of\n * them would hand `WxInput` a `false` where the caller asked for nothing (CLAUDE.md §4).\n */\ndefineOptions({ name: 'WxArticleSlug', inheritAttrs: false })\n\nconst editor = useArticleEditor()\nconst locales = useLocales()\nuseBlogMessages()\n\nconst t = useTranslate('webx-blog')\n\n/** `/blog/`, or `/` for a blog that lives at the root of the site. */\nconst prefix = computed(() => {\n const head = editor?.prefix.value ?? ''\n\n return head === '' ? '/' : `/${head}/`\n})\n\n/** The last segment as it stands in the field, not as the registry last heard it. */\nconst slug = computed(() => {\n const written = editor?.values.value.slug\n\n if (typeof written === 'string') return written\n\n // The language being edited and no other. Falling back on a language that does have words\n // would print an address the site does not answer at (§9).\n return (written as Record<string, string> | undefined)?.[locales.active.value] ?? ''\n})\n\n/** On the site at an address that is about to become a different one. */\nconst moving = computed(() => {\n const current = editor?.article.value?.path\n\n return (\n typeof current === 'string' &&\n slug.value !== '' &&\n `${prefix.value}${slug.value}` !== `/${current}`\n )\n})\n</script>\n\n<template>\n <div class=\"wx-article-slug\">\n <wx-input v-bind=\"$attrs\">\n <template #prefix>\n <span class=\"wx-article-slug__prefix\">{{ prefix }}</span>\n </template>\n </wx-input>\n\n <wx-alert v-if=\"moving\" type=\"info\" variant=\"soft\" :description=\"t('article.address-moving')\" />\n </div>\n</template>\n\n<style scoped>\n.wx-article-slug {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-8);\n}\n\n/*\n * One address in one face.\n *\n * The prefix is part of the address and not a decoration on the field, so it is set the way the\n * panel prints every other address — and so is what is typed after it. Two faces on one line\n * do not sit on one line: the metrics differ, each box is centred on itself, and `/blog/` rode\n * two pixels above the word it belongs to. Only the muting tells the two apart, which is the\n * one difference worth drawing: the prefix is the part that cannot be typed over.\n */\n.wx-article-slug__prefix,\n.wx-article-slug :deep(.wx-input__inner) {\n font-family: var(--wx-font-family-mono);\n}\n\n.wx-article-slug__prefix {\n color: var(--wx-text-muted);\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, ref, watch } from 'vue'\nimport { useAdmin, useErrorText, useTranslate } from '@webx-ui/module-admin'\nimport {\n toast,\n WxBadge,\n WxButton,\n WxSelect,\n WxText,\n type SelectModelValue,\n type SelectOption,\n} from '@webx-ui/core'\nimport { createBlogApi } from './api'\nimport { useArticleEditor } from './editor'\nimport { useBlogMessages } from './i18n'\nimport type { BlogTag } from './types'\n\n/**\n * The tags on an article: find one by typing, or make it here.\n *\n * Making one here is the point (§2.8). Tags are entered by the hundred from the article being\n * written, and a field that could only pick from what already exists would mean leaving the\n * article, opening another section, making a word and coming back — which nobody does, so the\n * article goes out untagged instead.\n *\n * What holds the other half of that bargain is the number beside every suggestion: seeing that\n * \"belts\" already has forty articles is what stops \"belt\" from being the forty-first. The\n * screen that merges the ones that got away is a separate one, and it exists.\n */\ndefineOptions({ name: 'WxArticleTags' })\n\nconst props = withDefaults(defineProps<{ disabled?: boolean }>(), { disabled: false })\n\nconst value = defineModel<number[]>({ default: () => [] })\n\nconst context = useAdmin()\nconst api = createBlogApi(context)\nconst editor = useArticleEditor()\nuseBlogMessages()\n\nconst t = useTranslate('webx-blog')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\nconst disabled = computed(() => props.disabled || editor?.disabled.value === true)\n\n/** How long after the last keystroke the server is asked. */\nconst PAUSE = 250\n\n/**\n * Every tag this session has seen a title for: the article's own, whatever the last search\n * answered, and whatever was made here. A chip needs a word, and the value is only ids.\n */\nconst known = ref(new Map<number, string>())\nconst found = ref<BlogTag[]>([])\nconst term = ref('')\nconst working = ref(false)\n\nlet timer: ReturnType<typeof setTimeout> | undefined\n\nwatch(\n () => editor?.article.value?.tags,\n (tags) => {\n const all = new Map(known.value)\n\n for (const tag of tags ?? []) all.set(tag.id, tag.title)\n\n known.value = all\n },\n { immediate: true, deep: true },\n)\n\nconst chips = computed(() =>\n value.value.map((id) => ({ id, title: known.value.get(id) ?? `#${id}` })),\n)\n\nconst options = computed<SelectOption[]>(() =>\n found.value\n .filter((tag) => !value.value.includes(tag.id))\n .map((tag) => ({\n // The count is part of the label rather than a column: a select draws a line of text, and\n // the number is what the reader is choosing between two near-identical words on.\n label: t('article.tag-option', { title: tag.title, count: tag.articles_count }),\n value: tag.id,\n })),\n)\n\n/** Nothing on the server is spelled exactly this way — so this is a word, not a typo of one. */\nconst missing = computed(() => {\n const typed = term.value.trim()\n\n if (typed === '') return null\n\n const same = (title: string): boolean => title.toLowerCase() === typed.toLowerCase()\n\n return found.value.some((tag) => same(tag.title)) ? null : typed\n})\n\nasync function look(): Promise<void> {\n try {\n found.value = await api.tags(term.value)\n\n const all = new Map(known.value)\n\n for (const tag of found.value) all.set(tag.id, tag.title)\n\n known.value = all\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\nfunction search(typed: string): void {\n term.value = typed\n\n clearTimeout(timer)\n timer = setTimeout(() => void look(), PAUSE)\n}\n\nfunction add(picked: SelectModelValue): void {\n if (typeof picked !== 'number' || value.value.includes(picked)) return\n\n value.value = [...value.value, picked]\n term.value = ''\n}\n\nfunction remove(id: number): void {\n value.value = value.value.filter((one) => one !== id)\n}\n\nasync function create(): Promise<void> {\n const title = missing.value\n\n if (title === null || working.value) return\n\n working.value = true\n\n try {\n const tag = await api.createTag({ title })\n\n known.value = new Map(known.value).set(tag.id, tag.title)\n found.value = [tag, ...found.value]\n add(tag.id)\n } catch (error) {\n toast.danger(message(error))\n } finally {\n working.value = false\n }\n}\n\n// The first opening of the list asks with an empty term, which answers with the most used.\nwatch(\n () => editor?.article.value?.id,\n () => void look(),\n { immediate: true },\n)\n\nonBeforeUnmount(() => clearTimeout(timer))\n</script>\n\n<template>\n <div class=\"wx-article-tags\">\n <div v-if=\"chips.length > 0\" class=\"wx-article-tags__chips\">\n <!-- The badge closes itself. It used to carry a `WxAction` in its slot, and an icon\n button of the panel is thirty pixels tall inside a chip whose words are fifteen: the\n chip grew to fit the button, the word sat on its own baseline three pixels below the\n cross, and the air left of the word was half the air right of it. `closable` is the\n badge's own cross, sized to the words it stands beside. -->\n <wx-badge\n v-for=\"chip in chips\"\n :key=\"chip.id\"\n type=\"default\"\n :closable=\"!disabled\"\n :close-label=\"t('article.tag-remove')\"\n @close=\"remove(chip.id)\"\n >\n {{ chip.title }}\n </wx-badge>\n </div>\n <wx-text v-else size=\"sm\" tone=\"muted\">{{ t('article.tags-empty') }}</wx-text>\n\n <div class=\"wx-article-tags__pick\">\n <wx-select\n :model-value=\"null\"\n :options=\"options\"\n :placeholder=\"t('article.tag-add')\"\n :empty-text=\"t('article.tag-nothing')\"\n :disabled=\"disabled\"\n filterable\n teleport\n :aria-label=\"t('article.tag-add')\"\n @search=\"search\"\n @update:model-value=\"add\"\n />\n\n <!-- Beside the field rather than inside the list: a line in a dropdown that creates\n something is a line somebody picks by mistake while scrolling. -->\n <wx-button\n v-if=\"missing\"\n variant=\"outline\"\n icon=\"plus\"\n :loading=\"working\"\n :disabled=\"disabled\"\n @click=\"create\"\n >\n {{ t('article.tag-create', { title: missing }) }}\n </wx-button>\n </div>\n </div>\n</template>\n\n<style scoped>\n.wx-article-tags {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-8);\n}\n\n.wx-article-tags__chips {\n display: flex;\n flex-wrap: wrap;\n gap: var(--wx-space-6);\n}\n\n.wx-article-tags__pick {\n display: flex;\n align-items: center;\n gap: var(--wx-space-8);\n flex-wrap: wrap;\n}\n\n.wx-article-tags__pick > :deep(.wx-select) {\n flex: 1 1 220px;\n min-width: 0;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\nimport { useAdmin, useErrorText, useTranslate } from '@webx-ui/module-admin'\nimport { confirm, toast, WxButton, WxFormItem } from '@webx-ui/core'\nimport { createBlogApi } from './api'\nimport { useArticleEditor } from './editor'\nimport { useBlogMessages } from './i18n'\n\n/**\n * Taking the article off the site, from the tab where the rest of its publication is decided.\n *\n * It lives here and not in the action bar because the bar holds what is done to the draft —\n * save, publish — and this is done to what visitors are reading. Under the day it goes out for\n * the same reason: whether an article is on the site and when it got there is one question, and\n * an editor who has just looked at the date is the one deciding to pull it.\n *\n * Offered only while there is something to take off. A draft was never there, and one already\n * taken off has nowhere further to go — the way back is \"publish\", which is in the bar.\n *\n * It is not \"delete\": the article keeps its address in the registry, its history and its\n * rubrics, and publishing puts it back exactly where it was.\n */\ndefineOptions({ name: 'WxArticleUnpublish' })\n\nconst context = useAdmin()\nconst api = createBlogApi(context)\nconst editor = useArticleEditor()\nuseBlogMessages()\n\nconst t = useTranslate('webx-blog')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\nconst working = ref(false)\n\n/** On the site, with or without edits waiting — or waiting for a day that has not come. */\nconst live = computed(() => {\n const status = editor?.article.value?.status\n\n return status === 'published' || status === 'modified' || status === 'scheduled'\n})\n\nconst disabled = computed(() => editor?.disabled.value === true || working.value)\n\n/**\n * Asked about, because this is the one thing on the settings tab that visitors see happen.\n * Everything else here writes a draft nobody outside the panel can read.\n */\nasync function unpublish(): Promise<void> {\n const row = editor?.article.value\n\n if (!row) return\n\n const agreed = await confirm({\n title: t('article.unpublish-title', { title: row.title }),\n message:\n row.status === 'scheduled' ? t('article.unpublish-waiting') : t('article.unpublish-text'),\n confirmText: t('panel.unpublish'),\n cancelText: t('panel.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n working.value = true\n\n try {\n await api.unpublish(row.id)\n await editor?.reload()\n toast.success(t('panel.unpublished-done'))\n } catch (error) {\n toast.danger(message(error))\n } finally {\n working.value = false\n }\n}\n</script>\n\n<template>\n <wx-form-item\n v-if=\"live && editor?.canManage\"\n class=\"wx-article-unpublish\"\n :help=\"t('article.unpublish-help')\"\n >\n <wx-button\n variant=\"outline\"\n type=\"danger\"\n icon=\"eye-off\"\n :loading=\"working\"\n :disabled=\"disabled\"\n @click=\"unpublish\"\n >\n {{ t('panel.unpublish') }}\n </wx-button>\n </wx-form-item>\n</template>\n\n<style scoped>\n/*\n * A form item is a column that stretches what it holds, which is right for a field and wrong\n * for a button: the one thing on this card that is not a field would otherwise be the widest\n * thing on it, six hundred pixels of red outline under a date picker.\n *\n * Through `:deep()`, because the column belongs to `WxFormItem` and a scoped rule reaches only\n * as far as this component's own elements.\n */\n.wx-article-unpublish :deep(.wx-button) {\n align-self: flex-start;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { nextTick, onMounted, ref, useTemplateRef } from 'vue'\nimport { useAdmin, useTranslate } from '@webx-ui/module-admin'\nimport { useModal, WxButton, WxDialog, WxFormItem, WxInput, WxSpace } from '@webx-ui/core'\nimport { createBlogApi } from './api'\nimport { useBlogMessages } from './i18n'\nimport type { ArticleRow } from './types'\n\n/**\n * A new article: what it is called, and nothing else.\n *\n * No address field here, unlike the dialog that starts a page. A page is created inside a\n * branch and its address is the one thing about it that cannot be guessed; an article lives in\n * one flat space under the blog's prefix, and the server makes the slug out of the title — the\n * same transliteration it would apply to anything typed in the field. The address is on the\n * editor's own settings tab, where there is room to say what changing it costs.\n */\nconst { open, resolve, dismiss } = useModal<ArticleRow>()\n\nconst context = useAdmin()\nconst api = createBlogApi(context)\nuseBlogMessages()\n\nconst t = useTranslate('webx-blog')\n\nconst title = ref('')\nconst saving = ref(false)\nconst errors = ref<Record<string, string[]>>({})\nconst field = useTemplateRef<HTMLElement>('field')\n\nonMounted(async () => {\n await nextTick()\n field.value?.querySelector('input')?.focus()\n})\n\nasync function submit(): Promise<void> {\n if (title.value.trim() === '' || saving.value) return\n\n saving.value = true\n errors.value = {}\n\n try {\n resolve(await api.create({ title: title.value.trim() }))\n } catch (error) {\n // The address is what can be refused here, and it is refused under the title that made\n // it: the registry sees pages, rubrics and tags too, so \"taken\" can mean taken by a page.\n errors.value = (error as { body?: { errors?: Record<string, string[]> } }).body?.errors ?? {}\n } finally {\n saving.value = false\n }\n}\n</script>\n\n<template>\n <wx-dialog v-model:open=\"open\" :title=\"t('panel.new-title')\" :width=\"440\">\n <wx-form-item\n :label=\"t('panel.field-title')\"\n :error=\"errors.title?.[0] ?? errors.slug?.[0]\"\n required\n >\n <div ref=\"field\">\n <wx-input v-model=\"title\" :aria-label=\"t('panel.field-title')\" @keyup.enter=\"submit\" />\n </div>\n </wx-form-item>\n\n <template #footer>\n <wx-space size=\"sm\">\n <wx-button variant=\"outline\" @click=\"dismiss()\">{{ t('panel.cancel') }}</wx-button>\n <wx-button type=\"primary\" :loading=\"saving\" :disabled=\"title.trim() === ''\" @click=\"submit\">\n {{ t('panel.create') }}\n </wx-button>\n </wx-space>\n </template>\n </wx-dialog>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, ref, useTemplateRef, watch } from 'vue'\nimport { useRoute, useRouter, type LocationQueryRaw } from 'vue-router'\nimport {\n useAdmin,\n useErrorText,\n useTranslate,\n WxDate,\n WxListScreen,\n WxFilterChips,\n rowMenuWidth,\n WxRowMenu,\n type AppliedFilter,\n type RowAction,\n type ScreenAction,\n WxAddressNote,\n} from '@webx-ui/module-admin'\nimport {\n confirm,\n createModal,\n toast,\n useElementWidth,\n WxBadge,\n WxButton,\n WxEntityCard,\n WxFormItem,\n WxIcon,\n WxSelect,\n WxTable,\n WxText,\n WxTooltip,\n type TabItem,\n type TableColumn,\n type TableState,\n type TabValue,\n} from '@webx-ui/core'\nimport ArticleCreateDialog from './ArticleCreateDialog.vue'\nimport { createBlogApi } from './api'\nimport { useBlogMessages } from './i18n'\nimport type { ArticleRow, ArticleStatus, ArticlesPage } from './types'\n\n/**\n * The section: a page of articles with the filters of §10 over it.\n *\n * A paginator rather than a tree, which is the whole difference from `Pages`: articles all sit\n * at the same depth and there are a hundred of them, so the screen is twenty rows, a filter bar\n * and a page number.\n *\n * Below {@link CARDS} the row becomes a card, and the card is not the table's own stack of\n * labelled lines: at 375px five of those are four hundred pixels for one article. It is the\n * cover on the left and everything else beside it, which is 96px and ten articles on a screen\n * instead of two (§10). The table draws whatever columns it is given, so the switch is made\n * here — one column at that width, seven above it — and both halves agree on the number because\n * it is the same constant.\n */\nconst props = withDefaults(defineProps<{ base?: string }>(), { base: '/blog' })\n\n/** Where the row stops being a row. Below it the table draws cards and this screen draws one. */\nconst CARDS = 640\n\nconst context = useAdmin()\nconst api = createBlogApi(context)\nconst route = useRoute()\nconst router = useRouter()\nuseBlogMessages()\n\nconst t = useTranslate('webx-blog')\n/* The two words a list needs as soon as it has filters belong to the panel, not to the blog. */\nconst admin = useTranslate('webx-admin')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\nconst root = useTemplateRef<HTMLElement>('root')\nconst width = useElementWidth(root)\n\nconst page = ref<ArticlesPage | null>(null)\nconst loading = ref(true)\n\nconst create = createModal<ArticleRow, Record<string, never>>(ArticleCreateDialog)\n\nconst canManage = computed(() => context.can('blog.articles.manage'))\n\nconst title = computed(\n () =>\n context.state.manifest?.modules.find((module) => module.id === 'articles')?.title ??\n t('module.articles'),\n)\n\n/**\n * Everything the list is looking at lives in the address.\n *\n * Coming back from an article has to land on the same filter and the same page, or the section\n * costs the reader their place every time they open something.\n */\nconst view = computed<TabValue>({\n get: () => (typeof route.query.view === 'string' ? route.query.view : ''),\n set: (value) => {\n // A new view is a new list: the page it was on belongs to the view it was on.\n void router.replace({\n query: { ...route.query, view: value === '' ? undefined : String(value), page: undefined },\n })\n },\n})\n\nconst inBin = computed(() => view.value === 'trashed')\n\nconst query = computed(() => ({\n q: typeof route.query.q === 'string' ? route.query.q : '',\n rubric: number(route.query.rubric),\n tag: number(route.query.tag),\n author: number(route.query.author),\n sort: typeof route.query.sort === 'string' ? route.query.sort : null,\n page: Number(route.query.page ?? 1) || 1,\n}))\n\nfunction number(value: unknown): number | null {\n return typeof value === 'string' && value !== '' ? Number(value) || null : null\n}\n\n/** The views of the same list, as tabs over the card. The bin rides in the same row (§10). */\nconst views = computed<TabItem[]>(() => [\n { value: '', label: t('panel.view-all') },\n { value: 'published', label: t('panel.view-published') },\n { value: 'scheduled', label: t('panel.view-scheduled') },\n { value: 'draft', label: t('panel.view-draft') },\n { value: 'unpublished', label: t('panel.view-unpublished') },\n { value: 'trashed', label: t('panel.bin'), icon: 'trash' },\n])\n\nconst filters = computed(() => page.value?.filters ?? { rubrics: [], tags: [], authors: [] })\n\nconst asCards = computed(() => width.value > 0 && width.value < CARDS)\n\n/**\n * Seven columns, or one.\n *\n * Every column but the title carries a width and the table is laid out `fixed`, so the ones\n * that drop below a breakpoint give what they had to the title rather than to whichever cell\n * happened to hold the longest string. They go in the order they can be spared — who wrote it,\n * then where it belongs, then when — and what is left at the bottom is the cover, the title and\n * the state, which is what identifies an article and what an editor is looking for.\n */\nconst columns = computed<TableColumn<ArticleRow>[]>(() => {\n if (asCards.value) {\n return [{ key: 'card', label: '' }]\n }\n\n return [\n { key: 'cover', label: '', width: 68 },\n /* The one that takes what the others leave, and the only one without a number. */\n { key: 'title', label: t('panel.column-title'), minWidth: 220 },\n { key: 'rubrics', label: t('panel.column-rubrics'), width: 190, hideBelow: 1000 },\n { key: 'author', label: t('panel.column-author'), width: 160, hideBelow: 1200 },\n {\n key: 'date',\n label: inBin.value ? t('panel.column-deleted') : t('panel.column-date'),\n width: 120,\n hideBelow: 700,\n // \"today at 16:22\" is three words the table will break over two lines given half a\n // chance, and a date read down a column has to be one line to be read at all.\n cellClass: 'wx-articles__when',\n },\n { key: 'status', label: t('panel.column-status'), width: 170 },\n { key: 'actions', label: '', width: rowMenuWidth, align: 'right' },\n ]\n})\n\nconst emptyText = computed(() => {\n if (query.value.q !== '') return t('panel.empty-search')\n\n return inBin.value ? t('panel.empty-bin') : t('panel.empty')\n})\n\n/**\n * What the three dropdowns are set to, said in the reader's words.\n *\n * They live behind the funnel now, and a shut panel says nothing about itself: a list narrowed\n * by something nobody can see is a list that looks wrong. The chips are the answer, and taking\n * one off is the same `narrow()` the dropdown itself calls.\n */\nconst applied = computed<AppliedFilter[]>(() => {\n const chips: AppliedFilter[] = []\n\n const rubric = filters.value.rubrics.find((item) => item.id === query.value.rubric)\n if (rubric) {\n chips.push({\n key: 'rubric',\n label: `${t('panel.filter-rubric')}: ${rubric.title}`,\n clear: () => narrow('rubric', undefined),\n })\n }\n\n const tag = filters.value.tags.find((item) => item.id === query.value.tag)\n if (tag) {\n chips.push({\n key: 'tag',\n label: `${t('panel.filter-tag')}: ${tag.title}`,\n clear: () => narrow('tag', undefined),\n })\n }\n\n const author = filters.value.authors.find((item) => item.id === query.value.author)\n if (author) {\n chips.push({\n key: 'author',\n label: `${t('panel.filter-author')}: ${author.title}`,\n clear: () => narrow('author', undefined),\n })\n }\n\n return chips\n})\n\n/** All three at once, from inside the panel they were set in. */\nfunction clearFilters(): void {\n void router.replace({\n query: {\n ...route.query,\n rubric: undefined,\n tag: undefined,\n author: undefined,\n page: undefined,\n },\n })\n}\n\nasync function load(state?: TableState): Promise<void> {\n loading.value = true\n\n try {\n page.value = await api.articles({\n q: state?.search ?? query.value.q,\n status: inBin.value ? '' : (view.value as ArticleStatus | ''),\n trashed: inBin.value,\n rubric: query.value.rubric,\n tag: query.value.tag,\n author: query.value.author,\n sort: state?.sort ? `${state.sort.order === 'desc' ? '-' : ''}${state.sort.key}` : null,\n page: state?.page ?? query.value.page,\n per_page: state?.perPage,\n })\n } catch (error) {\n toast.danger(message(error))\n } finally {\n loading.value = false\n }\n}\n\n/**\n * The table reports everything it knows in one event, and that is the one place the address is\n * written from — so a page, a sort and a search all survive opening an article and coming back,\n * and none of them has its own half of the bookkeeping.\n */\nfunction onState(state: TableState): void {\n const wanted: LocationQueryRaw = {\n ...route.query,\n q: state.search === '' ? undefined : state.search,\n sort:\n state.sort === null\n ? undefined\n : `${state.sort.order === 'desc' ? '-' : ''}${state.sort.key}`,\n page: state.page === 1 ? undefined : String(state.page),\n }\n\n void router.replace({ query: wanted })\n void load(state)\n}\n\n/** One of the three dropdowns moved: a new list, from its first page. */\nfunction narrow(name: 'rubric' | 'tag' | 'author', value: unknown): void {\n void router.replace({\n query: {\n ...route.query,\n [name]: typeof value === 'number' ? String(value) : undefined,\n page: undefined,\n },\n })\n}\n\n/* One source per value, not one getter returning an array: `query` is rebuilt on every change of\n the address, so an array is new each time and the watcher would fire on a page turn too —\n a second request, without `per_page`, racing the one `onState` already sent. */\nwatch(\n [view, () => query.value.rubric, () => query.value.tag, () => query.value.author],\n () => void load(),\n)\n\nvoid load()\n\n/**\n * The editor of an article arrives with session C; until it does, the row opens nothing rather\n * than pushing the router at a route that is not there.\n *\n * The bin never reaches here: the table is told the rows lead nowhere, and it withholds the\n * click along with the pointer and the highlight (CLAUDE.md §4).\n */\nfunction open(article: ArticleRow): void {\n const path = `${props.base}/articles/${article.id}`\n\n if (router.resolve(path).matched.length > 0) void router.push({ path, query: { ...route.query } })\n}\n\n/** A new article: the dialog asks for a title, the list shows it as a draft, the editor opens. */\nasync function add(): Promise<void> {\n const article = await create({})\n\n if (!article) return\n\n await load()\n open(article)\n}\n\nfunction actionsFor(article: ArticleRow): RowAction[] {\n if (!canManage.value) return []\n\n if (inBin.value) {\n return [\n {\n key: 'restore',\n icon: 'refresh',\n label: t('panel.restore'),\n run: () => void restore(article),\n },\n ]\n }\n\n const actions: RowAction[] = [\n { key: 'open', icon: 'edit', label: t('panel.open'), run: () => open(article) },\n ]\n\n if (article.url && article.status !== 'draft') {\n actions.push({\n key: 'site',\n icon: 'external-link',\n label: t('panel.open-on-site'),\n href: article.url,\n })\n }\n\n if (article.path !== null) {\n actions.push({\n key: 'copy',\n icon: 'copy',\n label: t('panel.copy-address'),\n run: () => void copyAddress(article),\n })\n }\n\n actions.push({\n key: 'pin',\n icon: 'star',\n label: article.pinned ? t('panel.unpin') : t('panel.pin'),\n run: () => void setPinned(article, !article.pinned),\n })\n\n actions.push(\n article.status === 'published' ||\n article.status === 'modified' ||\n article.status === 'scheduled'\n ? {\n key: 'unpublish',\n icon: 'eye-off',\n label: t('panel.unpublish'),\n run: () => void unpublish(article),\n }\n : {\n key: 'publish',\n icon: 'upload',\n label: t('panel.publish'),\n run: () => void publish(article),\n },\n )\n\n actions.push({\n key: 'delete',\n icon: 'trash',\n label: t('panel.delete'),\n danger: true,\n run: () => void remove(article),\n })\n\n return actions\n}\n\nasync function publish(article: ArticleRow): Promise<void> {\n await run(() => api.publish(article.id), t('panel.published'))\n}\n\nasync function unpublish(article: ArticleRow): Promise<void> {\n await run(() => api.unpublish(article.id), t('panel.unpublished-done'))\n}\n\nasync function setPinned(article: ArticleRow, pinned: boolean): Promise<void> {\n await run(\n () => api.save(article.id, { values: { pinned }, revision: article.revision }),\n pinned ? t('panel.pinned-done') : t('panel.unpinned'),\n )\n}\n\nasync function remove(article: ArticleRow): Promise<void> {\n const agreed = await confirm({\n title: t('panel.delete-title', { title: article.title }),\n message: t('panel.delete-text'),\n confirmText: t('panel.delete'),\n cancelText: t('panel.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n await run(() => api.remove(article.id), t('panel.deleted'))\n}\n\nasync function restore(article: ArticleRow): Promise<void> {\n await run(() => api.restore(article.id), t('panel.restored'))\n}\n\n/** One request, one sentence about it, and the list as the server now sees it. */\nasync function run(request: () => Promise<unknown>, said: string): Promise<void> {\n try {\n await request()\n toast.success(said)\n } catch (error) {\n toast.danger(message(error))\n } finally {\n // Either way: a refused publication has left the row saying what it was, and one that\n // worked has changed an order the answer does not carry row by row.\n await load()\n }\n}\n\nasync function copyAddress(article: ArticleRow): Promise<void> {\n if (!article.url) return\n\n try {\n await navigator.clipboard.writeText(article.url)\n toast.success(t('panel.address-copied'))\n } catch {\n // A browser that refuses the clipboard without a gesture it trusts; the address is on\n // screen and can be copied from there.\n }\n}\n\n/**\n * Live and live-with-edits are the same green: the article is on the site, and what is waiting\n * beside it is said by a chip of its own rather than by a different colour. An editor scanning\n * the column is asking \"is this up\", and two greens answer it once.\n */\nfunction badge(status: ArticleStatus): 'default' | 'success' | 'warning' | 'danger' {\n if (status === 'published' || status === 'modified') return 'success'\n if (status === 'scheduled') return 'warning'\n\n return 'default'\n}\n\n/** The address under the title, and the two different nothings that can stand there instead. */\nfunction address(article: ArticleRow): string {\n if (inBin.value) return '—'\n\n return article.path === null ? t('panel.no-address') : `/${article.path}`\n}\n\n/** The date a row is about: when it was published, or when it was deleted in the bin. */\nfunction dateOf(article: ArticleRow): string | null {\n return inBin.value ? article.deleted_at : article.published_at\n}\n\n/* What the section offers. Declared, because on a phone the head folds it into the ···. */\nconst actions = computed<ScreenAction[]>(() =>\n canManage.value\n ? [{ key: 'new', label: t('panel.new'), icon: 'plus', primary: true, run: () => void add() }]\n : [],\n)\n</script>\n\n<template>\n <div ref=\"root\" class=\"wx-articles\">\n <wx-list-screen v-model:view=\"view\" :title=\"title\" :views=\"views\" :actions=\"actions\">\n <wx-table\n :data=\"page\"\n :columns=\"columns\"\n row-key=\"id\"\n searchable\n :clickable=\"!inBin\"\n :hover=\"!inBin\"\n flush\n :loading=\"loading\"\n layout=\"fixed\"\n :cards-below=\"CARDS\"\n :filters-count=\"applied.length\"\n :filters-label=\"admin('filters.title')\"\n :search-placeholder=\"t('panel.search')\"\n :empty-text=\"emptyText\"\n :aria-label=\"title\"\n @row-click=\"open\"\n @state-change=\"onState\"\n >\n <!--\n The three dropdowns live behind the funnel, and what they are set to comes back as\n chips beside it. Standing open they were three controls of chrome above the first\n article, and on a phone — where the table stacks everything in its head into a column —\n three lines of it before any data.\n -->\n <template #filters>\n <wx-form-item :label=\"t('panel.filter-rubric')\">\n <wx-select\n :model-value=\"query.rubric\"\n :options=\"filters.rubrics.map((item) => ({ value: item.id, label: item.title }))\"\n :placeholder=\"t('panel.any-rubric')\"\n clearable\n size=\"sm\"\n @update:model-value=\"(value: unknown) => narrow('rubric', value)\"\n />\n </wx-form-item>\n\n <wx-form-item :label=\"t('panel.filter-tag')\">\n <wx-select\n :model-value=\"query.tag\"\n :options=\"filters.tags.map((item) => ({ value: item.id, label: item.title }))\"\n :placeholder=\"t('panel.any-tag')\"\n clearable\n filterable\n size=\"sm\"\n @update:model-value=\"(value: unknown) => narrow('tag', value)\"\n />\n </wx-form-item>\n\n <wx-form-item :label=\"t('panel.filter-author')\">\n <wx-select\n :model-value=\"query.author\"\n :options=\"filters.authors.map((item) => ({ value: item.id, label: item.title }))\"\n :placeholder=\"t('panel.any-author')\"\n clearable\n size=\"sm\"\n @update:model-value=\"(value: unknown) => narrow('author', value)\"\n />\n </wx-form-item>\n\n <!-- Taking all of them off belongs with the fields it resets: beside the chips it\n reads as one more chip, and it is the only control there that does not take\n exactly one filter away. -->\n <wx-button v-if=\"applied.length > 0\" variant=\"text\" size=\"sm\" block @click=\"clearFilters\">\n <template #icon><wx-icon name=\"close\" /></template>\n {{ admin('filters.reset') }}\n </wx-button>\n </template>\n\n <template #applied>\n <wx-filter-chips :filters=\"applied\" />\n </template>\n\n <template #cell-cover=\"{ row }\">\n <span class=\"wx-articles__cover\" :class=\"{ 'is-empty': !row.cover }\">\n <img v-if=\"row.cover?.thumb\" :src=\"row.cover.thumb\" alt=\"\" loading=\"lazy\" />\n <wx-icon v-else name=\"image\" size=\"sm\" />\n </span>\n </template>\n\n <template #cell-title=\"{ row }\">\n <div class=\"wx-articles__name\">\n <wx-tooltip v-if=\"row.pinned\" :content=\"t('panel.pinned')\">\n <wx-icon class=\"wx-articles__pin\" name=\"star\" size=\"sm\" />\n </wx-tooltip>\n <div class=\"wx-articles__name-text\">\n <span class=\"wx-articles__title\">{{ row.title }}</span>\n <!-- An article in the bin holds no address — the registry let it go when it went\n in — and \"the address appears when it is published\" would be a promise about\n something nobody can publish without bringing it back first. -->\n <span class=\"wx-articles__address\">\n {{ address(row) }}\n <wx-address-note v-if=\"!inBin\" :locale=\"row.address_locale\" />\n </span>\n </div>\n </div>\n </template>\n\n <template #cell-rubrics=\"{ row }\">\n <span class=\"wx-articles__chips\">\n <wx-badge v-for=\"rubric in row.rubrics\" :key=\"rubric.id\" size=\"sm\" round>\n {{ rubric.title }}\n </wx-badge>\n </span>\n </template>\n\n <template #cell-author=\"{ row }\">\n <wx-text size=\"sm\" tone=\"muted\" truncate>\n {{ row.author ? row.author.name : '—' }}\n </wx-text>\n </template>\n\n <template #cell-date=\"{ row }\">\n <wx-date v-if=\"dateOf(row)\" :value=\"dateOf(row)\" compact />\n <wx-text v-else size=\"sm\" tone=\"muted\">—</wx-text>\n </template>\n\n <template #cell-status=\"{ row }\">\n <span class=\"wx-articles__state\">\n <wx-badge :type=\"badge(row.status)\" dot size=\"sm\">\n {{ t(`panel.status-${row.status}`) }}\n </wx-badge>\n <!-- What is waiting, said beside the state rather than instead of it: the article\n is on the site, and there is something that is not. -->\n <wx-badge v-if=\"row.status === 'modified'\" type=\"primary\" size=\"sm\" round>\n {{ t('panel.edits') }}\n </wx-badge>\n </span>\n </template>\n\n <!--\n The card: the cover on the left, everything else beside it. One field and no label,\n because the table's own stack of \"Title: … / Author: … / Date: …\" is five lines and\n four hundred pixels for one article (§10).\n -->\n <!--\n `WxEntityCard`, `plain` so that the box around it stays the table's own: a picture, a\n name, its address under it and a row of facts is exactly what the component is, and\n writing that by hand once per section is how five sections end up with five different\n cards.\n -->\n <template #cell-card=\"{ row }\">\n <wx-entity-card\n class=\"wx-articles__entity\"\n variant=\"plain\"\n :title=\"row.title\"\n :image=\"row.cover?.thumb ?? undefined\"\n image-size=\"56px\"\n :title-lines=\"2\"\n :subtitle=\"address(row)\"\n >\n <template #title>\n <wx-icon v-if=\"row.pinned\" class=\"wx-articles__pin\" name=\"star\" size=\"sm\" />\n {{ row.title }}\n </template>\n\n <template #meta>\n <wx-badge v-if=\"row.rubrics[0]\" size=\"sm\" round>{{ row.rubrics[0].title }}</wx-badge>\n <wx-badge :type=\"badge(row.status)\" dot size=\"sm\">\n {{ t(`panel.status-${row.status}`) }}\n </wx-badge>\n <wx-date v-if=\"dateOf(row)\" :value=\"dateOf(row)\" />\n </template>\n </wx-entity-card>\n </template>\n\n <!--\n The menu goes in the card's own top strip, beside the checkbox: that line is there\n either way as soon as rows can be picked, and a `···` inside the entity takes width\n off a title that is already one line short.\n -->\n <template #card-actions=\"{ row }\">\n <wx-row-menu :actions=\"actionsFor(row)\" :label=\"row.title\" />\n </template>\n\n <template #cell-actions=\"{ row }\">\n <wx-row-menu :actions=\"actionsFor(row)\" :label=\"row.title\" />\n </template>\n </wx-table>\n </wx-list-screen>\n </div>\n</template>\n\n<style scoped>\n/* The list screen inside it lays the screen out; this only keeps a wide table from pushing the\n column it sits in. */\n.wx-articles {\n min-width: 0;\n}\n\n/*\n * The cover is the picture's box, never the picture: `<img>` has `min-width: auto`, which is\n * its natural width, so a 1600px photograph would take the column and the ones beside it\n * (CLAUDE.md §4).\n */\n.wx-articles__cover {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 44px;\n height: 32px;\n overflow: hidden;\n /* The smallest step in the scale, and what `WxEntityCard` gives its own thumbnail: on a box\n 32px tall the next one up is 12, which is over a third of it. */\n border-radius: var(--wx-radius-xs);\n background: var(--wx-bg-subtle);\n color: var(--wx-text-muted);\n}\n\n.wx-articles__cover.is-empty {\n border: 1px dashed var(--wx-border-default);\n background: var(--wx-bg-surface);\n}\n\n.wx-articles__cover img {\n width: 100%;\n height: 100%;\n min-width: 0;\n object-fit: cover;\n}\n\n.wx-articles__name {\n display: flex;\n align-items: center;\n gap: var(--wx-space-8);\n min-width: 0;\n}\n\n.wx-articles__name-text {\n display: flex;\n flex-direction: column;\n min-width: 0;\n}\n\n.wx-articles__pin {\n flex: none;\n color: var(--wx-color-primary);\n}\n\n.wx-articles__title {\n overflow: hidden;\n font-weight: var(--wx-font-weight-medium);\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n\n.wx-articles__address {\n overflow: hidden;\n color: var(--wx-text-muted);\n font-family: var(--wx-font-family-mono);\n font-size: var(--wx-font-size-xs);\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n\n.wx-articles__chips,\n.wx-articles__state {\n display: flex;\n align-items: center;\n gap: var(--wx-space-4);\n min-width: 0;\n overflow: hidden;\n}\n\n/* `:deep()` because the cell is the table's element and the class is ours — a scoped rule\n would be looking for our attribute on somebody else's markup (CLAUDE.md §4). */\n.wx-articles :deep(.wx-articles__when) {\n white-space: nowrap;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { nextTick, onMounted, ref, useTemplateRef } from 'vue'\nimport { useAdmin, useTranslate } from '@webx-ui/module-admin'\nimport { useModal, WxButton, WxDialog, WxFormItem, WxInput, WxSpace } from '@webx-ui/core'\nimport { createBlogApi } from './api'\nimport { useBlogMessages } from './i18n'\nimport type { TagRow } from './types'\n\n/**\n * A new tag, spelled here rather than while writing.\n *\n * The address is a field of its own, unlike on the dialog that starts an article: a tag is one\n * word, the address is the same word transliterated, and the one time somebody wants a different\n * one is the time they are making the tag on purpose — which is this screen and not the article\n * form (§2.8).\n */\nconst { open, resolve, dismiss } = useModal<TagRow>()\n\nconst context = useAdmin()\nconst api = createBlogApi(context)\nuseBlogMessages()\n\nconst t = useTranslate('webx-blog')\n\nconst title = ref('')\nconst slug = ref('')\nconst saving = ref(false)\nconst errors = ref<Record<string, string[]>>({})\nconst field = useTemplateRef<HTMLElement>('field')\n\nonMounted(async () => {\n await nextTick()\n field.value?.querySelector('input')?.focus()\n})\n\nasync function submit(): Promise<void> {\n if (title.value.trim() === '' || saving.value) return\n\n saving.value = true\n errors.value = {}\n\n try {\n resolve(\n await api.createTag({\n title: title.value.trim(),\n ...(slug.value.trim() === '' ? {} : { slug: slug.value.trim() }),\n }),\n )\n } catch (error) {\n errors.value = (error as { body?: { errors?: Record<string, string[]> } }).body?.errors ?? {}\n } finally {\n saving.value = false\n }\n}\n</script>\n\n<template>\n <wx-dialog v-model:open=\"open\" :title=\"t('tag.new-title')\" :width=\"440\">\n <wx-form-item :label=\"t('tag.field-title')\" :error=\"errors.title?.[0]\" required>\n <div ref=\"field\">\n <wx-input v-model=\"title\" :aria-label=\"t('tag.field-title')\" @keyup.enter=\"submit\" />\n </div>\n </wx-form-item>\n\n <wx-form-item\n :label=\"t('tag.field-slug')\"\n :error=\"errors.slug?.[0]\"\n :help=\"t('tag.field-slug-help')\"\n >\n <wx-input v-model=\"slug\" :aria-label=\"t('tag.field-slug')\" @keyup.enter=\"submit\" />\n </wx-form-item>\n\n <template #footer>\n <wx-space size=\"sm\">\n <wx-button variant=\"outline\" @click=\"dismiss()\">{{ t('tag.cancel') }}</wx-button>\n <wx-button type=\"primary\" :loading=\"saving\" :disabled=\"title.trim() === ''\" @click=\"submit\">\n {{ t('tag.create') }}\n </wx-button>\n </wx-space>\n </template>\n </wx-dialog>\n</template>\n","<script setup lang=\"ts\">\nimport { nextTick, onMounted, ref, useId, useTemplateRef } from 'vue'\nimport { useTranslate } from '@webx-ui/module-admin'\nimport { useModal, WxButton, WxDialog, WxFormItem, WxInput, WxSpace } from '@webx-ui/core'\nimport { useBlogMessages } from './i18n'\n\n/**\n * Renaming a tag: one field, one button, and nothing changes until it is pressed.\n *\n * It used to be done in the cell — the name turned into a box when it was clicked. Two things\n * were wrong with that. A name that is quietly an `<input>` reads as a name: nothing says it can\n * be typed in, and a stray click on a row is a rename nobody asked for. And the box saved itself\n * on `blur`, which never fired: `blur` does not bubble, so the listener on the field's wrapper\n * heard nothing and clicking away simply lost what had been typed.\n *\n * The address deliberately does not follow the word. A tag respelled three times before lunch\n * would otherwise leave three aliases behind a decision nobody made; the address has a menu item\n * of its own.\n */\nconst props = defineProps<{ name: string }>()\n\nconst { open, resolve, dismiss } = useModal<string>()\n\nuseBlogMessages()\n\nconst t = useTranslate('webx-blog')\n/* 'Save' is the panel's word, translated once for every module that has a form. */\nconst panel = useTranslate('webx-admin')\n\n/* The footer button lives outside the form, so it says which form it submits. */\nconst formId = useId()\n\nconst draft = ref(props.name)\nconst field = useTemplateRef<HTMLFormElement>('field')\n\nonMounted(async () => {\n await nextTick()\n\n const input = field.value?.querySelector('input')\n\n // Selected rather than merely focused: renaming is usually replacing, not appending.\n input?.focus()\n input?.select()\n})\n\nfunction submit(): void {\n const next = draft.value.trim()\n\n // An empty name is not a rename, it is a tag with nothing to call it by.\n if (next === '' || next === props.name) {\n dismiss()\n\n return\n }\n\n resolve(next)\n}\n</script>\n\n<template>\n <wx-dialog v-model:open=\"open\" :title=\"t('tag.rename')\" :width=\"420\">\n <!-- A real form, so that Enter does what the button does. A listener on the field is not\n the same thing: a form with one text field submits on Enter by itself, which is what a\n one-field dialog is for. -->\n <form :id=\"formId\" ref=\"field\" @submit.prevent=\"submit\">\n <wx-form-item :label=\"t('tag.field-title')\" required>\n <!-- The key as well as the form. Implicit submission is a browser behaviour and this\n field lives inside a dialog that takes the keyboard over; measured here, a real\n Enter reached the input, was not prevented by anybody, and submitted nothing. -->\n <wx-input\n v-model=\"draft\"\n :aria-label=\"t('tag.field-title')\"\n @keydown.enter.prevent=\"submit\"\n />\n </wx-form-item>\n </form>\n\n <template #footer>\n <wx-space size=\"sm\">\n <wx-button variant=\"outline\" @click=\"dismiss()\">{{ t('tag.cancel') }}</wx-button>\n <wx-button\n type=\"primary\"\n native-type=\"submit\"\n :form=\"formId\"\n :disabled=\"draft.trim() === ''\"\n >\n {{ panel('editor.save') }}\n </wx-button>\n </wx-space>\n </template>\n </wx-dialog>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\nimport { useAdmin, useErrorText, useTranslate } from '@webx-ui/module-admin'\nimport {\n useModal,\n WxAlert,\n WxBadge,\n WxButton,\n WxCheckbox,\n WxDialog,\n WxFormItem,\n WxIcon,\n WxSelect,\n WxSpace,\n WxText,\n} from '@webx-ui/core'\nimport { createBlogApi } from './api'\nimport { useBlogMessages } from './i18n'\nimport type { TagMerged, TagRow } from './types'\n\n/**\n * Several tags into one (§6).\n *\n * The dialog asks the one question the selection cannot answer — which of them stays — and says\n * the two things about the operation that an editor has to know before pressing it: how many\n * articles change hands, and that nothing here can be undone. The pivot rows that go are gone,\n * and putting a tag back would not put back which articles carried it.\n *\n * The checkbox is the other half. The aliases of `webx-ui/routing` die with the tag — they are\n * rows keyed to the entity — so an address that is to survive the merge has to survive as\n * something that is not tied to it, which is a redirect. That is a permanent fact about the\n * site, and a tag made by mistake this morning does not deserve one, so it is asked rather than\n * done quietly.\n */\nconst props = defineProps<{ tags: TagRow[] }>()\n\nconst { open, resolve, dismiss } = useModal<TagMerged>()\n\nconst context = useAdmin()\nconst api = createBlogApi(context)\nuseBlogMessages()\n\nconst t = useTranslate('webx-blog')\nconst message = useErrorText()\n\n/* The most used one by default: merging the big tag into the typo is the mistake this dialog\n exists to make hard, and the default is where that is decided. */\nconst keep = ref<number>(\n [...props.tags].sort((one, other) => other.articles_count - one.articles_count)[0]?.id ?? 0,\n)\n\nconst working = ref(false)\nconst redirect = ref(true)\nconst error = ref<string | null>(null)\n\nconst options = computed(() =>\n props.tags.map((tag) => ({ value: tag.id, label: `${tag.title} · ${tag.articles_count}` })),\n)\n\nconst survivor = computed(() => props.tags.find((tag) => tag.id === keep.value) ?? null)\n\nconst going = computed(() => props.tags.filter((tag) => tag.id !== keep.value))\n\n/**\n * How many articles the merge touches, at worst.\n *\n * At worst because an article carrying two of the tags counts once after the merge and twice\n * here — the pivot's unique index is what makes it one — and a number that cannot be wrong in\n * the other direction is the honest one to show before pressing anything.\n */\nconst affected = computed(() => going.value.reduce((sum, tag) => sum + tag.articles_count, 0))\n\nconst addresses = computed(() => going.value.map((tag) => tag.path).filter(Boolean) as string[])\n\nasync function submit(): Promise<void> {\n if (survivor.value === null || going.value.length === 0 || working.value) return\n\n working.value = true\n error.value = null\n\n try {\n resolve(\n await api.mergeTags(\n going.value.map((tag) => tag.id),\n keep.value,\n redirect.value,\n ),\n )\n } catch (failure) {\n error.value = message(failure)\n } finally {\n working.value = false\n }\n}\n</script>\n\n<template>\n <wx-dialog v-model:open=\"open\" :title=\"t('tag.merge-title')\" :width=\"520\">\n <div class=\"wx-tag-merge\">\n <!-- What is going where, before anything is asked: the chips are the selection as the\n screen had it, and the one on the right is the word that survives. -->\n <div class=\"wx-tag-merge__chips\">\n <wx-badge v-for=\"tag in going\" :key=\"tag.id\" size=\"sm\" round>\n {{ tag.title }} · {{ tag.articles_count }}\n </wx-badge>\n <wx-icon name=\"arrow-right\" size=\"sm\" />\n <wx-badge v-if=\"survivor\" type=\"primary\" size=\"sm\" round>\n {{ survivor.title }} · {{ survivor.articles_count }}\n </wx-badge>\n </div>\n\n <wx-form-item :label=\"t('tag.merge-keep')\">\n <wx-select v-model=\"keep\" :options=\"options\" :aria-label=\"t('tag.merge-keep')\" />\n </wx-form-item>\n\n <div class=\"wx-tag-merge__redirect\">\n <wx-checkbox v-model=\"redirect\" :label=\"t('tag.merge-redirect')\" />\n <wx-text size=\"sm\" tone=\"muted\">\n {{\n addresses.length === 0\n ? t('tag.merge-redirect-none')\n : t('tag.merge-redirect-help', { addresses: addresses.join(', ') })\n }}\n </wx-text>\n </div>\n\n <wx-alert\n type=\"warning\"\n variant=\"soft\"\n :description=\"\n t('tag.merge-warning', {\n count: affected,\n tag: survivor?.title ?? '',\n merged: going.length,\n })\n \"\n />\n\n <wx-alert v-if=\"error\" type=\"danger\" variant=\"soft\" :description=\"error\" />\n </div>\n\n <template #footer>\n <wx-space size=\"sm\">\n <wx-button variant=\"outline\" @click=\"dismiss()\">{{ t('tag.cancel') }}</wx-button>\n <wx-button type=\"primary\" :loading=\"working\" :disabled=\"going.length === 0\" @click=\"submit\">\n {{ t('tag.merge-confirm') }}\n </wx-button>\n </wx-space>\n </template>\n </wx-dialog>\n</template>\n\n<style scoped>\n.wx-tag-merge {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-12);\n}\n\n.wx-tag-merge__chips {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--wx-space-6);\n min-width: 0;\n}\n\n.wx-tag-merge__redirect {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-2);\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\nimport { useRoute, useRouter, type LocationQueryRaw } from 'vue-router'\nimport {\n useAdmin,\n useErrorText,\n useTranslate,\n WxListScreen,\n rowMenuWidth,\n WxRowMenu,\n type RowAction,\n type ScreenAction,\n} from '@webx-ui/module-admin'\nimport {\n confirm,\n createModal,\n toast,\n WxActionBar,\n WxButton,\n WxIcon,\n WxTable,\n WxText,\n WxTooltip,\n type RowKey,\n type TabItem,\n type TableColumn,\n type TableSort,\n type TableState,\n type TabValue,\n} from '@webx-ui/core'\nimport TagCreateDialog from './TagCreateDialog.vue'\nimport TagRenameDialog from './TagRenameDialog.vue'\nimport TagMergeDialog from './TagMergeDialog.vue'\nimport { createBlogApi } from './api'\nimport { useBlogMessages } from './i18n'\nimport type { TagIndexing, TagMerged, TagQuery, TagRow, TagsPage } from './types'\n\n/**\n * Tags, and the raking over of them (§10).\n *\n * Entered from the article form by the hundred, which is what the whole screen is shaped by:\n * within six months the table holds \"belts\", \"belt\" and \"drive belts\", and all three are right.\n * So renaming is done in place — a tag is one word, and a form to change one word is a form\n * nobody opens — and everything else is done to a pile of rows at once.\n *\n * It stays a table at every width, with columns dropping out by `hideBelow` until the word and\n * the `···` are left. Cards would be 250px each for a word and a number (CLAUDE.md §4), and\n * this is a screen somebody scrolls through sixty rows of.\n *\n * \"Not indexed\" is the one filter that is not a `where`: it is the answer of the same rule the\n * rendered page follows, rules in the SEO section included, so the column, the tab's count and\n * the filter are one answer worked out on the server (§12).\n */\nconst props = withDefaults(defineProps<{ base?: string }>(), { base: '/blog' })\n\nconst context = useAdmin()\nconst api = createBlogApi(context)\nconst route = useRoute()\nconst router = useRouter()\nuseBlogMessages()\n\nconst t = useTranslate('webx-blog')\nconst panel = useTranslate('webx-admin')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\nconst page = ref<TagsPage | null>(null)\nconst loading = ref(true)\nconst selected = ref<RowKey[]>([])\nconst working = ref(false)\n\nconst rename = createModal<string, { name: string }>(TagRenameDialog)\n\nconst create = createModal<TagRow, Record<string, never>>(TagCreateDialog)\nconst merge = createModal<TagMerged, { tags: TagRow[] }>(TagMergeDialog)\n\nconst canManage = computed(() => context.can('blog.taxonomy.manage'))\n\nconst title = computed(\n () =>\n context.state.manifest?.modules.find((module) => module.id === 'tags')?.title ??\n t('module.tags'),\n)\n\n/** Everything the list is looking at lives in the address, so a link to it is a link. */\nconst view = computed<TabValue>({\n get: () => (typeof route.query.view === 'string' ? route.query.view : ''),\n set: (value) => {\n void router.replace({\n query: { ...route.query, view: value === '' ? undefined : String(value), page: undefined },\n })\n },\n})\n\nconst counts = computed(() => page.value?.filters ?? { total: 0, empty: 0, noindex: 0 })\n\nconst views = computed<TabItem[]>(() => [\n { value: '', label: t('tag.view-all'), badge: counts.value.total },\n { value: 'empty', label: t('tag.view-empty'), badge: counts.value.empty },\n { value: 'noindex', label: t('tag.view-noindex'), badge: counts.value.noindex },\n])\n\nconst rows = computed(() => page.value?.data ?? [])\n\nconst chosen = computed(() => rows.value.filter((tag) => selected.value.includes(tag.id as RowKey)))\n\n/**\n * Five columns, and never cards.\n *\n * The word and the menu stay at every width; the address goes first, because it is the one\n * thing a tag's name already tells you, then the state of the index. What is left on a phone is\n * a word, a number and a `···`, which is the screen this is (CLAUDE.md §4).\n */\nconst columns = computed<TableColumn<TagRow>[]>(() => [\n { key: 'title', label: t('tag.column-title'), minWidth: 150, sortable: true },\n // The number is what the other columns and the narrowest the title may be add up to without\n // this one: below it the address is what does not fit, and a fixed table does not shrink —\n // it scrolls sideways and takes the ··· with it (CLAUDE.md §4). Measured, not guessed: at\n // 375px the row is the word, the number and the menu, and nothing hangs off the edge.\n { key: 'path', label: t('tag.column-address'), width: 180, hideBelow: 760 },\n {\n key: 'articles_count',\n label: t('tag.column-articles'),\n width: 80,\n align: 'right',\n sortable: true,\n },\n // Wide enough for the longest of the three states on one line, in every language: measured\n // at 180px of text, and a cell keeps 32 of its own. Narrower, the ruled row wrapped and\n // stood eighteen pixels taller than the ones around it.\n { key: 'indexing', label: t('tag.column-indexing'), width: 230, hideBelow: 560 },\n { key: 'actions', label: '', width: rowMenuWidth, align: 'right' },\n])\n\nfunction query(state?: TableState): TagQuery {\n return {\n q: state?.search ?? (typeof route.query.q === 'string' ? route.query.q : ''),\n empty: view.value === 'empty',\n noindex: view.value === 'noindex',\n // Only when it is not the default: most used first is what the list is, and a query\n // string that repeats the default is a link that outlives a change of mind about it.\n sort: sortOf(state),\n page: state?.page ?? (Number(route.query.page ?? 1) || 1),\n per_page: state?.perPage,\n }\n}\n\nasync function load(state?: TableState): Promise<void> {\n loading.value = true\n\n try {\n page.value = await api.tagsPage(query(state))\n } catch (error) {\n toast.danger(message(error))\n } finally {\n loading.value = false\n }\n}\n\n/**\n * The order the table asks for, in the words the server knows.\n *\n * Two keys and a direction: the word from A, or the used ones first — and a minus turns either\n * of them around. The default is most used first, so the query string leaves it out: a link that\n * repeats the default outlives a change of mind about it.\n */\nfunction sortOf(state?: TableState): TagQuery['sort'] {\n const from = state?.sort ?? sortFromRoute()\n\n if (!from) return undefined\n\n const key = from.key === 'title' ? 'name' : 'articles'\n const sort = `${from.order === 'desc' ? '-' : ''}${key}` as NonNullable<TagQuery['sort']>\n\n return sort === 'articles' ? undefined : sort\n}\n\n/** What the address says the order is, for a list arrived at by a link. */\nfunction sortFromRoute(): TableSort | null {\n const value = typeof route.query.sort === 'string' ? route.query.sort : ''\n\n if (value === '') return null\n\n const desc = value.startsWith('-')\n const key = (desc ? value.slice(1) : value) === 'name' ? 'title' : 'articles_count'\n\n return { key, order: desc ? 'desc' : 'asc' }\n}\n\n/** The table reports everything it knows in one event, and the address is written from it. */\nfunction onState(state: TableState): void {\n const wanted: LocationQueryRaw = {\n ...route.query,\n q: state.search === '' ? undefined : state.search,\n sort: sortOf(state),\n page: state.page === 1 ? undefined : String(state.page),\n }\n\n void router.replace({ query: wanted })\n void load(state)\n}\n\nwatch(view, () => {\n // A new view is a new list, and a selection made in the old one is a selection of rows\n // nobody can see any more.\n selected.value = []\n void load()\n})\n\nvoid load()\n\nasync function add(): Promise<void> {\n if (await create({})) await load()\n}\n\n/**\n * Renaming, as an act rather than a side effect.\n *\n * A dialog with one field, because a name that is quietly an `<input>` reads as a name: nothing\n * says it can be typed in, and a stray click on a row is a rename nobody asked for. The address\n * does not move with the word — a tag respelled three times before lunch would leave three\n * aliases behind a decision nobody made — so it has a menu item of its own.\n */\nasync function startRename(tag: TagRow): Promise<void> {\n const title = await rename({ name: tag.title })\n\n if (title) await patch(tag, { title }, t('tag.renamed'))\n}\n\nasync function setIndexing(tag: TagRow, noindex: boolean): Promise<void> {\n await patch(tag, { noindex }, noindex ? t('tag.kept-out') : t('tag.indexed'))\n}\n\n/**\n * One tag changed, and the row it changed in.\n *\n * The answer replaces the row rather than reloading the page: the list is sixty words long and\n * somebody is halfway down it, and a reload after every rename would take their place away.\n */\nasync function patch(\n tag: TagRow,\n input: { title?: string; noindex?: boolean },\n said: string,\n): Promise<void> {\n try {\n const saved = await api.saveTag(tag.id, input)\n\n if (page.value) {\n page.value = {\n ...page.value,\n data: page.value.data.map((row) => (row.id === saved.id ? saved : row)),\n }\n }\n\n toast.success(said)\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\nasync function remove(tag: TagRow): Promise<void> {\n const agreed = await confirm({\n title: t('tag.delete-title', { title: tag.title }),\n message:\n tag.articles_count > 0\n ? t('tag.delete-text-used', { count: tag.articles_count })\n : t('tag.delete-text'),\n confirmText: t('tag.delete'),\n cancelText: t('tag.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n try {\n await api.removeTag(tag.id)\n toast.success(t('tag.deleted'))\n selected.value = selected.value.filter((key) => key !== tag.id)\n await load()\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\n/**\n * What can be done to the pile, behind the same `···` a single row has.\n *\n * Three buttons in the bar were three lines of chrome on a phone and the same three words every\n * row already offers; merging stays a button because it is the one thing here that cannot be\n * done to a row on its own.\n */\nconst massActions = computed<RowAction[]>(() => [\n {\n key: 'index',\n icon: 'eye',\n label: t('tag.index'),\n disabled: working.value,\n run: () => void massSelected('index'),\n },\n {\n key: 'noindex',\n icon: 'eye-off',\n label: t('tag.noindex'),\n disabled: working.value,\n run: () => void massSelected('noindex'),\n },\n {\n key: 'delete',\n icon: 'trash',\n label: t('tag.delete'),\n danger: true,\n disabled: working.value,\n run: () => void massSelected('delete'),\n },\n])\n\nfunction actionsFor(tag: TagRow): RowAction[] {\n if (!canManage.value) return []\n\n const actions: RowAction[] = [\n { key: 'rename', icon: 'edit', label: t('tag.rename'), run: () => void startRename(tag) },\n ]\n\n if (tag.url) {\n actions.push({\n key: 'site',\n icon: 'external-link',\n label: t('tag.open-on-site'),\n href: tag.url,\n })\n }\n\n actions.push({\n key: 'articles',\n icon: 'list',\n label: t('tag.show-articles'),\n run: () => void router.push({ path: `${props.base}/articles`, query: { tag: String(tag.id) } }),\n })\n\n actions.push(\n tag.noindex\n ? {\n key: 'index',\n icon: 'eye',\n label: t('tag.index'),\n run: () => void setIndexing(tag, false),\n }\n : {\n key: 'noindex',\n icon: 'eye-off',\n label: t('tag.noindex'),\n run: () => void setIndexing(tag, true),\n },\n )\n\n actions.push({\n key: 'delete',\n icon: 'trash',\n label: t('tag.delete'),\n danger: true,\n run: () => void remove(tag),\n })\n\n return actions\n}\n\n/** The colour the state of the index is said in: green for in, quiet for out (§12). */\nfunction tone(indexing: TagIndexing): 'success' | 'muted' {\n return indexing === 'noindex' ? 'muted' : 'success'\n}\n\nasync function mergeSelected(): Promise<void> {\n if (chosen.value.length < 2) return\n\n const merged = await merge({ tags: chosen.value })\n\n if (!merged) return\n\n toast.success(t('tag.merged', { tag: merged.tag.title, count: merged.articles_count }))\n selected.value = []\n await load()\n}\n\nasync function massSelected(action: 'index' | 'noindex' | 'delete'): Promise<void> {\n const ids = chosen.value.map((tag) => tag.id)\n\n if (ids.length === 0 || working.value) return\n\n if (action === 'delete') {\n const held = chosen.value.reduce((sum, tag) => sum + tag.articles_count, 0)\n\n const agreed = await confirm({\n title: t('tag.delete-many-title', { count: ids.length }),\n message:\n held > 0 ? t('tag.delete-many-text-used', { count: held }) : t('tag.delete-many-text'),\n confirmText: t('tag.delete'),\n cancelText: t('tag.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n }\n\n working.value = true\n\n try {\n const affected = await api.massTags(ids, action)\n\n toast.success(\n action === 'delete'\n ? t('tag.deleted-many', { count: affected })\n : action === 'index'\n ? t('tag.indexed-many', { count: affected })\n : t('tag.kept-out-many', { count: affected }),\n )\n selected.value = []\n await load()\n } catch (error) {\n toast.danger(message(error))\n } finally {\n working.value = false\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.value\n ? [{ key: 'new', label: t('tag.new'), icon: 'plus', primary: true, run: () => void add() }]\n : [],\n)\n</script>\n\n<template>\n <div class=\"wx-tags\">\n <wx-list-screen v-model:view=\"view\" :title=\"title\" :views=\"views\" :actions=\"actions\">\n <wx-table\n v-model:selected=\"selected\"\n :data=\"page\"\n :columns=\"columns\"\n row-key=\"id\"\n searchable\n hover\n flush\n layout=\"fixed\"\n :loading=\"loading\"\n :selectable=\"canManage\"\n :select-row-label=\"panel('filters.select-row')\"\n :select-all-label=\"panel('filters.select-all')\"\n :cards-below=\"0\"\n :search-placeholder=\"t('tag.search')\"\n :empty-text=\"rows.length === 0 && !loading ? t('tag.empty') : undefined\"\n :aria-label=\"title\"\n @state-change=\"onState\"\n >\n <template #cell-title=\"{ row }\">\n <wx-text truncate>{{ row.title }}</wx-text>\n </template>\n\n <template #cell-path=\"{ row }\">\n <wx-text size=\"sm\" tone=\"muted\" mono truncate>\n {{ row.path ?? t('tag.no-address') }}\n </wx-text>\n </template>\n\n <template #cell-articles_count=\"{ row }\">\n <wx-text :tone=\"row.articles_count === 0 ? 'muted' : 'default'\">\n {{ row.articles_count }}\n </wx-text>\n </template>\n\n <!--\n Three states and not two. A rule in the SEO section opens the page whatever the flag\n says, and an editor who wrote that rule must not be left looking at a row that says\n `noindex` and disagreeing with it (§12).\n -->\n <template #cell-indexing=\"{ row }\">\n <wx-tooltip v-if=\"row.indexing === 'rule'\" :content=\"t('tag.indexing-rule-help')\">\n <wx-text size=\"sm\" :tone=\"tone(row.indexing)\">{{ t('tag.indexing-rule') }}</wx-text>\n </wx-tooltip>\n <wx-text v-else size=\"sm\" :tone=\"tone(row.indexing)\">\n {{ t(`tag.indexing-${row.indexing}`) }}\n </wx-text>\n </template>\n\n <template #cell-actions=\"{ row }\">\n <wx-row-menu :actions=\"actionsFor(row)\" :label=\"row.title\" />\n </template>\n </wx-table>\n </wx-list-screen>\n\n <!--\n The selection bar, at the bottom of the screen and only while something is selected.\n `WxActionBar` rather than a floating pill of its own: the panel already has one shape for\n \"what can be done from here\", and a second one would be a second one to keep in step.\n -->\n <wx-action-bar v-if=\"chosen.length > 0\" class=\"wx-tags__bar\" sticky>\n <template #state>\n <wx-text weight=\"medium\">{{ t('tag.selected', { count: chosen.length }) }}</wx-text>\n </template>\n\n <!--\n One button and a menu. Merging is what this bar exists for — it is the only thing here\n that cannot be done to a row on its own — and the other three are the row's own menu\n applied to several rows at once, which is where a reader already looks for them.\n\n The icon is a slot and not a prop: `WxButton` takes `#icon`, and `icon=\"…\"` lands on the\n element as an attribute and draws nothing at all (CLAUDE.md §4).\n -->\n <wx-button type=\"primary\" :disabled=\"chosen.length < 2 || working\" @click=\"mergeSelected\">\n <template #icon><wx-icon name=\"link\" /></template>\n {{ t('tag.merge') }}\n </wx-button>\n\n <!--\n No way out of the selection here. Untick the rows, or untick them all from the box in\n the heading — a × beside a red \"Delete\" is a button whose whole job is to undo something\n harmless, standing where the dangerous one is, and it read as a way to close the bar.\n\n `lg` because the menu stands beside a button rather than at the end of a row: a row menu\n is 30px, a button is 42, and the pair read as a control and a leftover. In a bar the two\n are the same two things and have to be the same size.\n -->\n <wx-row-menu\n size=\"lg\"\n :actions=\"massActions\"\n :label=\"t('tag.selected', { count: chosen.length })\"\n />\n </wx-action-bar>\n </div>\n</template>\n\n<style scoped>\n.wx-tags {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-12);\n min-width: 0;\n}\n\n.wx-tags__rename {\n display: block;\n min-width: 0;\n}\n\n.wx-tags__sort {\n display: flex;\n gap: var(--wx-space-4);\n min-width: 0;\n}\n\n/*\n * The name is the rename: a tag is one word, and a form to change one word is a form nobody\n * opens. It reads as text until it is hovered, because a table of sixty underlined words is a\n * table of sixty links to nowhere.\n */\n.wx-tags__name {\n max-width: 100%;\n overflow: hidden;\n padding: 0;\n border: 0;\n background: none;\n color: inherit;\n font: inherit;\n text-align: start;\n text-overflow: ellipsis;\n white-space: nowrap;\n cursor: text;\n}\n\n.wx-tags__name:hover {\n text-decoration: underline;\n text-decoration-style: dotted;\n}\n\n/*\n * One line on a phone, not two.\n *\n * The bar keeps its state box at 220px so that a sentence about a draft — \"Saved · goes out on\n * 25 September\" — has room to be read before the buttons take a line of their own. What stands\n * here is two words and a number, and asking for 220 of them pushed \"Merge\" and the ··· onto a\n * second line at every phone width. `:deep()` because the box is the bar's element and the rule\n * is ours. Measured at 375: the words 89px, the button and the ··· 203, and 41px of air left\n * between them.\n */\n.wx-tags__bar :deep(.wx-action-bar__state) {\n flex: 0 1 auto;\n}\n</style>\n","import { categoryRoutes, type AdminModule, type CategoriesOptions } from '@webx-ui/module-admin'\nimport ArticleAuthor from './ArticleAuthor.vue'\nimport ArticleEditorPage from './ArticleEditorPage.vue'\nimport ArticleHistory from './ArticleHistory.vue'\nimport ArticleRelated from './ArticleRelated.vue'\nimport ArticleSlug from './ArticleSlug.vue'\nimport ArticleTags from './ArticleTags.vue'\nimport ArticleUnpublish from './ArticleUnpublish.vue'\nimport ArticlesPage from './ArticlesPage.vue'\nimport TagsPage from './TagsPage.vue'\n\nexport interface BlogOptions {\n /** Where the blog lives inside the panel. The three sections sit under it. */\n path?: string\n}\n\n/**\n * The blog as sections of the panel: articles, rubrics and tags.\n *\n * Three modules rather than one, because the navigation is one entry per module and the blog\n * wants three of them; the server puts all three in the `blog` group, which is what draws them\n * under one heading. Each is returned separately so a panel can leave one out.\n *\n * A section whose server half is not installed never appears — the entry is built from the\n * manifest — and so does one whose front end is missing. That is what makes it safe to return\n * all three from one call: a panel that installed only part of the blog gets only that part.\n */\n/**\n * The rubrics as the panel's shared categories see them: where they answer, what they are edited\n * on and what the blog calls them. Exported so that a panel mounting the screens somewhere of its\n * own does not have to repeat the words.\n *\n * Every word that names the thing is the blog's — \"rubric\", \"articles\" — and the rest (\"Edit\",\n * \"Cancel\", \"Leave without saving?\") is the panel's, said the same way in every module.\n */\nexport function rubricsOptions(path = '/blog'): CategoriesOptions {\n return {\n api: 'blog/rubrics',\n path: `${path}/rubrics`,\n name: 'webx.blog.rubrics',\n module: 'rubrics',\n screen: 'blog.category-form',\n manage: 'blog.taxonomy.manage',\n count: 'articles_count',\n items: (id) => ({ path: `${path}/articles`, query: { rubric: String(id) } }),\n words: {\n new: 'webx-blog::rubric.new',\n empty: 'webx-blog::rubric.empty',\n 'empty-help': 'webx-blog::rubric.empty-help',\n order: 'webx-blog::rubric.order',\n hidden: 'webx-blog::rubric.hidden',\n 'no-address': 'webx-blog::rubric.no-address',\n count: 'webx-blog::rubric.articles',\n 'show-items': 'webx-blog::rubric.show-articles',\n 'delete-blocked': 'webx-blog::rubric.delete-blocked',\n 'delete-text': 'webx-blog::rubric.delete-text',\n deleted: 'webx-blog::rubric.deleted',\n saved: 'webx-blog::rubric.saved',\n 'field-title': 'webx-blog::rubric.field-title',\n 'field-slug': 'webx-blog::rubric.field-slug',\n 'address-moving': 'webx-blog::rubric.address-moving',\n },\n }\n}\n\nexport function blog(options: BlogOptions = {}): AdminModule[] {\n const path = options.path ?? '/blog'\n\n return [\n {\n id: 'articles',\n path: `${path}/articles`,\n routes: [\n // The section's own path travels as a prop, so a panel that mounted the blog somewhere\n // else still links between its screens correctly.\n {\n path: `${path}/articles`,\n name: 'webx.blog.articles',\n component: ArticlesPage,\n props: { base: path },\n },\n {\n path: `${path}/articles/:id(\\\\d+)`,\n name: 'webx.blog.articles.edit',\n component: ArticleEditorPage,\n props: { base: path },\n },\n ],\n /*\n * The parts of `blog.article-form` that only this module can draw.\n *\n * The editor is a described screen so that a module can add a tab to it with a patch, and\n * the price of that is that everything on it has to be a node type. Four of these are\n * fields — the value is ids, and the control over them is a list that can be dragged or a\n * box that makes a tag — and two only draw. Each reads the article from the editor above\n * it rather than from the description, because a screen is a description and not a\n * binding.\n *\n * The slug is here for a different reason: what it edits is an ordinary string, but the\n * whole address is the prefix of the blog and that string, and the prefix is not in the\n * description — it is configuration the editor was handed with the record.\n */\n types: {\n 'wx-article-slug': { component: ArticleSlug, kind: 'field' },\n 'wx-article-author': { component: ArticleAuthor, kind: 'field' },\n 'wx-article-tags': { component: ArticleTags, kind: 'field' },\n 'wx-article-related': { component: ArticleRelated, kind: 'field' },\n 'wx-article-unpublish': { component: ArticleUnpublish, kind: 'display' },\n 'wx-article-history': { component: ArticleHistory, kind: 'display' },\n },\n },\n {\n id: 'rubrics',\n path: `${path}/rubrics`,\n // The list and the page of one rubric are the panel's shared category screens (§3.6 of\n // the services spec); what is the blog's is the description they are handed.\n routes: categoryRoutes(rubricsOptions(path)),\n },\n {\n id: 'tags',\n path: `${path}/tags`,\n routes: [\n {\n path: `${path}/tags`,\n name: 'webx.blog.tags',\n component: TagsPage,\n props: { base: path },\n },\n ],\n },\n ]\n}\n"],"names":["articleEditorKey","provideArticleEditor","editor","provide","useArticleEditor","inject","blogMessages","seeded","useBlogMessages","i18n","useAdmin","props","__props","value","_useModel","t","useTranslate","disabled","computed","options","author","chosen","next","_createBlock","_unref","WxSelect","$event","createBlogApi","admin","base","tagsBase","data","body","query","search","suffix","id","input","number","term","at","revision","ids","action","keep","redirect","PAUSE","context","api","route","useRoute","router","useRouter","locales","useLocales","dates","useDates","panel","message","useErrorText","list","article","ref","values","prefix","previewUrl","related","loading","saving","working","errors","snapshot","reloadToken","canManage","editing","useEditing","detail","theirs","refresh","flight","conflict","locked","current","dirty","state","title","written","localizedValue","future","publication","row","when","provideBlocksPreview","load","save","take","silent","error","toast","timer","schedule","onFocusOut","carried","done","write","sending","sent","failure","takeTheirs","discard","confirm","publish","held","old","address","unpublish","badge","status","leaveGuard","event","watch","onMounted","onBeforeUnmount","onBeforeRouteLeave","actions","leads","_createElementBlock","_Fragment","_createVNode","WxSkeleton","WxCard","WxScreenHead","WxBreadcrumb","WxBreadcrumbItem","_createTextVNode","_toDisplayString","WxBadge","WxEditingAlerts","_createElementVNode","_hoisted_1","WxScreen","WxActionBar","WxSaveState","WxButton","versions","live","restore","version","_openBlock","_hoisted_2","WxEmpty","_renderList","_hoisted_3","_hoisted_4","WxDate","WxText","WxDrafts","_cache","MATCHES","known","found","titles","all","one","rows","look","mine","page","add","picked","remove","WxSortableList","_withCtx","item","WxAction","head","slug","moving","$attrs","WxAlert","tags","tag","chips","missing","typed","same","create","chip","WxFormItem","open","resolve","dismiss","useModal","field","useTemplateRef","nextTick","submit","WxDialog","WxSpace","WxInput","CARDS","root","width","useElementWidth","createModal","ArticleCreateDialog","module","view","inBin","views","filters","asCards","columns","rowMenuWidth","emptyText","applied","rubric","narrow","clearFilters","onState","wanted","name","path","actionsFor","copyAddress","setPinned","run","pinned","request","said","dateOf","WxListScreen","WxTable","WxIcon","WxFilterChips","_normalizeClass","WxTooltip","_hoisted_5","WxAddressNote","_hoisted_6","_hoisted_7","WxEntityCard","WxRowMenu","formId","useId","draft","other","survivor","going","affected","sum","addresses","WxCheckbox","selected","rename","TagRenameDialog","TagCreateDialog","merge","TagMergeDialog","counts","sortOf","from","sortFromRoute","key","sort","desc","startRename","patch","setIndexing","noindex","saved","massActions","massSelected","tone","indexing","mergeSelected","merged","rubricsOptions","blog","ArticlesPage","ArticleEditorPage","ArticleSlug","ArticleAuthor","ArticleTags","ArticleRelated","ArticleUnpublish","ArticleHistory","categoryRoutes","TagsPage"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../src/editor.ts","../src/messages.ts","../src/i18n.ts","../src/ArticleAuthor.vue","../src/api.ts","../src/ArticleEditorPage.vue","../src/ArticleHistory.vue","../src/ArticleRelated.vue","../src/ArticleSlug.vue","../src/ArticleTags.vue","../src/ArticleUnpublish.vue","../src/ArticleCreateDialog.vue","../src/ArticlesPage.vue","../src/authors.ts","../src/AuthorPane.vue","../src/AuthorsPage.vue","../src/TagCreateDialog.vue","../src/TagRenameDialog.vue","../src/TagMergeDialog.vue","../src/TagsPage.vue","../src/module.ts"],"sourcesContent":["import { inject, provide, type InjectionKey, type Ref } from 'vue'\nimport type { ScreenModel } from '@webx-ui/schema'\nimport type { ArticleDetail, ArticleOption, ArticleRow } from './types'\n\n/**\n * What the editor knows and the nodes of its screen do not.\n *\n * The form is a described screen (`blog.article-form`), so the parts of it that need more than\n * a value — the address printed whole, the rubrics with their titles, the history — arrive as\n * node types of their own. Each of those needs the article being edited and the lists the\n * server sent with it, and none of them can be handed either through the description: a screen\n * is a description, not a binding. So the page that hosts the screen provides it, the same way\n * it provides the preview to `wx-blocks`.\n */\nexport interface ArticleEditorContext {\n /** The article as the server last answered it, or `null` while the first request is in flight. */\n article: Ref<ArticleRow | null>\n /**\n * The values of the screen as they are right now, edits included.\n *\n * A node that only draws still has to follow what is being typed: the address is a sentence\n * made of the slug field, and one that updated on save would be showing the old address at\n * the moment the new one is most worth reading.\n */\n values: Ref<ScreenModel>\n /** What the dropdowns on the screen can be set to, as the record carried them. */\n options: Ref<ArticleDetail['options']>\n /** Titles for the ids in `values.related` — the only place they come from. */\n related: Ref<ArticleOption[]>\n /** The first segment of every blog address, for the field that edits the last one. */\n prefix: Ref<string>\n /** Where the section lives, for the links the nodes draw. */\n base: string\n /** Whether this administrator may write at all. */\n canManage: boolean\n /**\n * Whether the screen is closed for writing right now — no permission, or a publication in\n * flight.\n *\n * Through the editor rather than through the form, because `WxForm`'s own `disabled` reaches\n * a control only through `useFormField`, which the design system does not hand out: a field\n * built outside `@webx-ui/core` has no way to ask. The screen is told the same thing twice —\n * once for the fields that are core's and once here — and both are computed from one place.\n */\n disabled: Ref<boolean>\n /** Ask the server for the article again — after a restore, a publication, a discard. */\n reload(): Promise<void>\n /** Write the draft now rather than at the end of the next pause. */\n save(): Promise<void>\n}\n\nexport const articleEditorKey: InjectionKey<ArticleEditorContext> = Symbol('wx-article-editor')\n\nexport function provideArticleEditor(editor: ArticleEditorContext): void {\n provide(articleEditorKey, editor)\n}\n\n/**\n * The editor above this node, or `null` outside one — a node of `blog.article-form` drawn in a\n * demo or a test has no article to talk about, and must draw nothing rather than throw.\n */\nexport function useArticleEditor(): ArticleEditorContext | null {\n return inject(articleEditorKey, null)\n}\n","import type { Messages } from '@webx-ui/module-admin'\n\n/**\n * The English this package speaks on its own, before the server's dictionary arrives, or\n * without one. The server ships the same lines in ten languages under `webx-blog::` and\n * overrides these; `messages.test.ts` keeps the two sets of keys equal.\n */\nexport const blogMessages: Record<string, Messages> = {\n module: {\n group: 'Blog',\n articles: 'Articles',\n rubrics: 'Rubrics',\n tags: 'Tags',\n authors: 'Authors',\n author: 'Author',\n },\n panel: {\n new: 'New article',\n search: 'Search by title or address',\n empty: 'No articles yet.',\n 'empty-search': 'Nothing matches that.',\n 'empty-bin': 'The bin is empty.',\n\n 'column-title': 'Title',\n 'column-rubrics': 'Rubrics',\n 'column-author': 'Author',\n 'column-date': 'Date',\n 'column-status': 'Status',\n 'column-deleted': 'Deleted',\n\n 'status-draft': 'Draft',\n 'status-scheduled': 'Scheduled',\n 'status-published': 'Live',\n 'status-modified': 'Live',\n 'status-unpublished': 'Off the site',\n edits: 'edits',\n pinned: 'Pinned to the top',\n 'no-address': 'The address appears when it is published',\n 'no-author': 'Nobody',\n\n 'view-all': 'All',\n 'view-published': 'Live',\n 'view-scheduled': 'Scheduled',\n 'view-draft': 'Drafts',\n 'view-unpublished': 'Off the site',\n bin: 'Bin',\n\n 'filter-rubric': 'Rubric',\n 'filter-tag': 'Tag',\n 'filter-author': 'Author',\n 'any-rubric': 'Any rubric',\n 'any-tag': 'Any tag',\n 'any-author': 'Any author',\n\n open: 'Open',\n 'open-on-site': 'Open on the site',\n 'copy-address': 'Copy the address',\n pin: 'Pin to the top',\n unpin: 'Unpin',\n publish: 'Publish',\n unpublish: 'Take off the site',\n delete: 'Delete',\n restore: 'Restore',\n cancel: 'Cancel',\n\n 'delete-title': 'Delete “:title”?',\n 'delete-text': 'It goes to the bin and comes off the site, and its address is free again.',\n deleted: 'The article is in the bin.',\n restored: 'The article is back.',\n published: 'The article is on the site.',\n 'unpublished-done': 'The article is off the site.',\n 'pinned-done': 'Pinned to the top.',\n unpinned: 'No longer pinned.',\n 'address-copied': 'The address is on the clipboard.',\n\n 'new-title': 'New article',\n 'field-title': 'Title',\n create: 'Create',\n },\n article: {\n trail: 'Where this article sits',\n untitled: 'Untitled',\n save: 'Save',\n 'save-failed': 'The article was not saved.',\n schedule: 'Schedule',\n scheduled: 'The article is scheduled.',\n 'publish-title': 'Put “:title” on the site?',\n 'publish-text': 'It answers at :address from the moment you do, for everyone.',\n 'publish-later': 'It goes on the site on :date and not before. Until then nothing changes.',\n 'publish-nowhere': 'It has no address in this language yet, so nothing will answer.',\n 'publish-now': 'now',\n preview: 'Preview',\n more: 'More',\n discard: 'Discard changes',\n discarded: 'The article is back to what is published.',\n 'conflict-title': 'The article changed while you were editing',\n 'conflict-mine': 'Keep mine',\n 'conflict-theirs': 'Take the newer version',\n 'conflict-theirs-title': 'Give up what you wrote?',\n 'conflict-theirs-text':\n 'The article is read again as it now is, and what you have typed since goes.',\n leave: 'Leave',\n 'leave-title': 'Leave without saving?',\n 'leave-text': 'The article could not be saved, and what you wrote is not on the server.',\n 'live-scheduled': 'Goes out :date',\n 'live-since': 'On the site since :date',\n 'unpublish-title': 'Take “:title” off the site?',\n 'unpublish-text':\n 'It stops answering at its address. The draft, the history and the rubrics stay — publishing puts it back.',\n 'unpublish-waiting': 'It has not gone out yet. The day it was set for will pass without it.',\n 'unpublish-help': 'Takes the article off the site without deleting it.',\n 'address-moving':\n 'The address is changing. The old one keeps working and leads to the new one.',\n author: 'Author',\n 'no-author': 'Nobody',\n rubrics: 'Rubrics',\n 'rubrics-empty': 'In no rubric yet.',\n 'rubric-main': 'Main',\n 'rubric-add': 'Add a rubric',\n 'rubric-remove': 'Take out of this rubric',\n 'rubric-none-left': 'Every rubric is already on this article.',\n 'tags-empty': 'No tags yet.',\n 'tag-add': 'Find or make a tag',\n 'tag-remove': 'Take this tag off',\n 'tag-option': ':title · :count',\n 'tag-nothing': 'No tag is spelled that way.',\n 'tag-create': 'Make “:title”',\n related: 'Related',\n 'related-empty': 'Nothing pinned by hand.',\n 'related-add': 'Find an article',\n 'related-remove': 'Unpin',\n 'related-nothing': 'Nothing matches that.',\n 'history-empty': 'This article has never been published.',\n version: '#:number',\n 'version-live': 'On the site',\n 'source-panel': 'From the panel',\n 'source-mcp': 'By an agent',\n 'source-import': 'Imported',\n 'restore-title': 'Restore version :number?',\n 'restore-text':\n 'It becomes the draft. The site keeps showing what is published until you publish this.',\n 'restore-version': 'Restore',\n 'restored-version': 'Version :number is now the draft.',\n },\n rubric: {\n new: 'New rubric',\n empty: 'No rubrics yet.',\n 'empty-help': 'A rubric is a section of the blog. An article can be in several of them.',\n order: 'The order here is the order on the site',\n hidden: 'Hidden from the site',\n 'no-address': 'No address in this language',\n articles: ':count articles',\n 'show-articles': 'Show its articles',\n edit: 'Edit',\n\n 'tab-content': 'Content',\n 'tab-seo': 'SEO',\n\n 'field-title': 'Name',\n 'field-slug': 'Address',\n visible: 'Show on the site',\n 'visible-help':\n 'Hidden, it drops out of the menu and answers 404. Its articles go on answering at their own addresses.',\n 'field-lead': 'Introduction',\n 'lead-help': 'Printed above the list of articles.',\n 'cover-help': 'Shown wherever the site lists the sections.',\n 'address-moving':\n 'The address is changing. The old one keeps working and leads to the new one.',\n\n 'seo-help': 'Left empty, the title and the description of this page come from the SEO rules.',\n\n delete: 'Delete',\n 'delete-blocked': 'While it holds articles it cannot go — move them first.',\n 'delete-title': 'Delete “:name”?',\n 'delete-text': 'It goes to the bin and comes off the site, and its address is free again.',\n deleted: 'The rubric is in the bin.',\n\n cancel: 'Cancel',\n save: 'Save',\n saved: 'The rubric is saved.',\n created: 'The rubric is made.',\n 'reorder-failed': 'The new order was not saved.',\n },\n author: {\n 'tab-profile': 'Profile',\n 'field-name': 'Name on the site',\n 'field-name-help':\n 'What readers see under the articles and on the page. Required in the main language.',\n 'field-role': 'Role',\n 'field-bio': 'About',\n 'field-bio-help': \"A few lines for the author's page.\",\n 'card-page': 'On the site',\n 'field-photo': 'Photo',\n 'field-public': 'Page on the site',\n 'field-public-help':\n \"With it, the name under the articles links to a page with all of the author's articles. Without it, the name is plain text.\",\n 'field-slug': 'Address',\n 'field-slug-help': \"The last part of the page's address, after …/author/.\",\n 'account-missing': 'Choose the account this profile is for.',\n 'account-taken': 'This account already has a profile.',\n 'name-required': 'Write the name in the main language.',\n new: 'New author',\n search: 'Search by name',\n empty: 'No authors yet.',\n 'empty-help':\n 'A profile gives an administrator who writes a name for readers, a photo and, if they want it, a page with their articles.',\n 'empty-search': 'Nothing matches that.',\n choose: 'Choose an author',\n 'choose-help': 'Or add a new one: pick the account and write the name.',\n account: 'Account',\n 'account-help': 'Whose articles these are. Chosen once.',\n 'no-accounts': 'Every account already has a profile.',\n articles: 'Articles: :count',\n 'page-open': 'Page on the site',\n 'page-closed': 'No page',\n 'open-page': 'Open the page',\n save: 'Save',\n saved: 'Saved',\n 'save-failed': 'Check the highlighted fields',\n delete: 'Delete',\n 'delete-title': 'Delete the profile of :name?',\n 'delete-text': \"The articles stay; under them the account's name is shown instead.\",\n deleted: 'Profile deleted',\n cancel: 'Cancel',\n back: 'Authors',\n 'leave-title': 'Leave without saving?',\n 'leave-text': 'The changes to this profile will be lost.',\n leave: 'Leave',\n untitled: 'Without a name',\n profile: 'Author profile',\n 'make-profile': 'Make a profile',\n 'field-socials': 'Networks',\n 'field-socials-help':\n \"Shown on the author's page, and named to search engines as the same person.\",\n 'socials-add': 'Add a network',\n 'socials-empty': 'No networks yet.',\n 'social-network': 'Network',\n 'social-url': 'Address',\n 'social-network-missing': 'Choose the network.',\n 'social-url-invalid': 'An address that starts with https://.',\n },\n tag: {\n new: 'New tag',\n 'new-title': 'New tag',\n 'field-title': 'Name',\n 'field-slug': 'Address',\n 'field-slug-help': 'Left empty, it is made out of the name.',\n create: 'Create',\n cancel: 'Cancel',\n\n search: 'Search by word or address',\n empty: 'No tags yet.',\n 'no-address': 'No address in this language',\n\n 'column-title': 'Name',\n 'column-address': 'Address',\n 'column-articles': 'Articles',\n 'column-indexing': 'Indexing',\n\n 'view-all': 'All',\n 'view-empty': 'Nothing filed',\n 'view-noindex': 'Not indexed',\n 'sort-articles': 'Most used',\n 'sort-name': 'A to Z',\n\n 'indexing-open': 'indexed',\n 'indexing-rule': 'indexed — SEO rule',\n 'indexing-noindex': 'noindex',\n 'indexing-rule-help':\n 'A rule in the SEO section covers this address, so the page is in the index however this tag is set.',\n\n rename: 'Rename',\n renamed: 'The tag is renamed.',\n index: 'Index',\n noindex: 'Keep out of the index',\n indexed: 'The tag page is in the index.',\n 'kept-out': 'The tag page is out of the index.',\n 'indexed-many': ':count tag pages are in the index.',\n 'kept-out-many': ':count tag pages are out of the index.',\n 'open-on-site': 'Open on the site',\n 'show-articles': 'Show its articles',\n\n delete: 'Delete',\n 'delete-title': 'Delete “:title”?',\n 'delete-text': 'A tag has no bin: it is gone for good.',\n 'delete-text-used':\n 'It is on :count articles and comes off all of them. A tag has no bin: it is gone for good.',\n deleted: 'The tag is gone.',\n 'delete-many-title': 'Delete :count tags?',\n 'delete-many-text': 'A tag has no bin: they are gone for good.',\n 'delete-many-text-used':\n 'They are on :count articles between them and come off all of them. A tag has no bin.',\n 'deleted-many': ':count tags are gone.',\n\n selected: ':count selected',\n clear: 'Clear the selection',\n merge: 'Merge into one',\n merged: ':count articles now carry “:tag”.',\n 'merge-title': 'Merge tags',\n 'merge-keep': 'Which tag to keep',\n 'merge-redirect': 'Redirect the old addresses',\n 'merge-redirect-help': ':addresses will answer 301.',\n 'merge-redirect-none':\n 'These tags have no address in this language, so there is nothing to redirect.',\n 'merge-warning':\n ':count articles will carry “:tag” instead. :merged tags will be deleted, and there is no undoing it.',\n 'merge-confirm': 'Merge',\n },\n}\n","import { useAdmin } from '@webx-ui/module-admin'\nimport { blogMessages } 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 useBlogMessages(): void {\n try {\n const { i18n } = useAdmin()\n\n if (!seeded.has(i18n)) {\n i18n.defaults('webx-blog', blogMessages)\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 { RouterLink } from 'vue-router'\nimport { useAdmin, useTranslate } from '@webx-ui/module-admin'\nimport { WxSelect, type SelectModelValue, type SelectOption } from '@webx-ui/core'\nimport { useArticleEditor } from './editor'\nimport { useBlogMessages } from './i18n'\n\n/**\n * Who signed the article: one administrator, or nobody (§2.14).\n *\n * A list rather than a text field, because the byline is a row in the table of administrators:\n * renaming somebody renames it everywhere, and an account that is gone leaves the article\n * unsigned instead of signed by a name nobody can look up.\n *\n * \"Nobody\" is the empty state of the field and not a line in the list. A row meaning \"none\" is\n * a row somebody picks by accident, and `SelectValue` has no room for `null` anyway — so the\n * answer is the clear button and a placeholder that says what an empty field means.\n */\ndefineOptions({ name: 'WxArticleAuthor' })\n\nconst props = withDefaults(defineProps<{ disabled?: boolean }>(), { disabled: false })\n\nconst value = defineModel<number | null>({ default: null })\n\nconst editor = useArticleEditor()\nuseBlogMessages()\n\nconst t = useTranslate('webx-blog')\nconst context = useAdmin()\nconst disabled = computed(() => props.disabled || editor?.disabled.value === true)\n\nconst options = computed<SelectOption[]>(() =>\n (editor?.options.value.authors ?? []).map((author) => ({\n label: author.title,\n value: author.id,\n })),\n)\n\n/**\n * The way to the author's profile under the field (§19.5 of the spec): to it where there is one, to\n * a new one for this account where there is not — only on a site with author pages, which is a site\n * whose manifest has the section, and only for somebody who may edit profiles.\n */\nconst profileLink = computed(() => {\n const hasSection = context.state.manifest?.modules.some((module) => module.id === 'authors')\n\n if (!hasSection || !context.can('blog.authors.manage') || value.value === null) return null\n\n const base = editor?.base ?? '/blog'\n const profile = editor?.options.value.authors.find((author) => author.id === value.value)?.profile\n\n return profile\n ? {\n to: { path: `${base}/authors`, query: { author: String(profile) } },\n label: t('author.profile'),\n }\n : {\n to: { path: `${base}/authors`, query: { author: 'new', account: String(value.value) } },\n label: t('author.make-profile'),\n }\n})\n\nconst chosen = computed<SelectModelValue>({\n get: () => value.value,\n set: (next) => {\n value.value = typeof next === 'number' ? next : null\n },\n})\n</script>\n\n<template>\n <div class=\"wx-article-author\">\n <wx-select\n v-model=\"chosen\"\n :options=\"options\"\n :placeholder=\"t('article.no-author')\"\n :disabled=\"disabled\"\n clearable\n filterable\n teleport\n :aria-label=\"t('article.author')\"\n />\n\n <router-link v-if=\"profileLink\" class=\"wx-article-author__profile\" :to=\"profileLink.to\">\n {{ profileLink.label }}\n </router-link>\n </div>\n</template>\n\n<style scoped>\n.wx-article-author {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: var(--wx-space-6);\n min-width: 0;\n}\n\n.wx-article-author > :first-child {\n align-self: stretch;\n}\n\n.wx-article-author__profile {\n color: var(--wx-color-primary);\n font-size: var(--wx-font-size-sm);\n}\n</style>\n","import type { AdminContext } from '@webx-ui/module-admin'\nimport type {\n ArticleDetail,\n ArticleInput,\n ArticleQuery,\n ArticleRow,\n ArticleSave,\n ArticleVersion,\n ArticlesPage,\n BlogTag,\n TagInput,\n TagMassAction,\n TagMerged,\n TagQuery,\n TagRow,\n TagsPage,\n} from './types'\n\nexport interface BlogApi {\n /** A page of articles, with what the filters over them can be set to. */\n articles(query?: ArticleQuery): Promise<ArticlesPage>\n /** One article as its editor opens it: the record, the screen's values and the revision. */\n get(id: number): Promise<ArticleDetail>\n create(input: ArticleInput): Promise<ArticleRow>\n /**\n * Save. Refused with a 409 when the revision is not the current one — the body is an\n * {@link ArticleConflict} carrying the article as it now is, so the panel can offer to\n * re-read rather than quietly keep one of two edits.\n */\n save(id: number, input: ArticleSave): Promise<ArticleDetail>\n /** Throw away what is waiting and keep what the site is showing. */\n discard(id: number): Promise<ArticleDetail>\n /** The publications, newest first. */\n versions(id: number): Promise<ArticleVersion[]>\n /** An old publication becomes the draft; putting it on the site is a separate step. */\n restoreVersion(id: number, number: number): Promise<ArticleDetail>\n /** Tags matching what is being typed, most used first. */\n tags(term?: string): Promise<BlogTag[]>\n /**\n * A tag made from the article being written, because that is where tags come from (§2.8) —\n * and from the tags screen, which is the other place somebody spells a new word.\n */\n createTag(input: TagInput): Promise<TagRow>\n /**\n * On the site. `at` in the future schedules it; left out, it goes on now (§7). `revision` is\n * what the editor held: a draft that moved on since is answered 409, not published.\n */\n publish(id: number, at?: string | null, revision?: string): Promise<ArticleRow>\n unpublish(id: number): Promise<ArticleRow>\n remove(id: number): Promise<void>\n restore(id: number): Promise<ArticleRow>\n\n /** A page of tags with the counts the pills wear (§10). */\n tagsPage(query?: TagQuery): Promise<TagsPage>\n /** A rename, or the switch on the index. Only what travels is touched. */\n saveTag(id: number, input: TagInput): Promise<TagRow>\n removeTag(id: number): Promise<void>\n /** What the selection bar does to a pile of them at once. */\n massTags(ids: number[], action: TagMassAction): Promise<number>\n /** Several into one. Irreversible, and `redirect` is what outlives the tags that go (§6). */\n mergeTags(ids: number[], keep: number, redirect: boolean): Promise<TagMerged>\n}\n\n/**\n * Everything under `/blog`, below the panel's API path — except the rubrics, which are the\n * panel's shared categories and are asked for with `createCategoriesApi(admin, 'blog/rubrics')`.\n */\nexport function createBlogApi(admin: AdminContext): BlogApi {\n const base = `${admin.apiPath}/blog/articles`\n const tagsBase = `${admin.apiPath}/blog/tags`\n const data = <T>(body: { data: T }): T => body.data\n\n return {\n articles: (query = {}) => {\n const search = new URLSearchParams()\n\n // Only what was asked for: `rubric=` would ask for the articles of no rubric, which is a\n // question the list never means to put.\n if (query.q) search.set('q', query.q)\n if (query.rubric != null) search.set('rubric', String(query.rubric))\n if (query.tag != null) search.set('tag', String(query.tag))\n if (query.author != null) search.set('author', String(query.author))\n if (query.status) search.set('status', query.status)\n if (query.sort) search.set('sort', query.sort)\n if (query.trashed) search.set('trashed', '1')\n if (query.page && query.page > 1) search.set('page', String(query.page))\n if (query.per_page) search.set('per_page', String(query.per_page))\n\n const suffix = search.size > 0 ? `?${search}` : ''\n\n return admin.http\n .get<{\n data: ArticleRow[]\n meta: Omit<ArticlesPage, 'data' | 'filters'>\n filters: ArticlesPage['filters']\n }>(`${base}${suffix}`)\n .then((body) => ({ ...body.meta, data: body.data, filters: body.filters }))\n },\n get: (id) => admin.http.get<{ data: ArticleDetail }>(`${base}/${id}`).then(data),\n create: (input) => admin.http.post<{ data: ArticleRow }>(base, input).then(data),\n save: (id, input) => admin.http.put<{ data: ArticleDetail }>(`${base}/${id}`, input).then(data),\n discard: (id) =>\n admin.http.post<{ data: ArticleDetail }>(`${base}/${id}/discard`, {}).then(data),\n versions: (id) =>\n admin.http.get<{ data: ArticleVersion[] }>(`${base}/${id}/versions`).then(data),\n restoreVersion: (id, number) =>\n admin.http\n .post<{ data: ArticleDetail }>(`${base}/${id}/versions/${number}/restore`, {})\n .then(data),\n tags: (term = '') => {\n const suffix = term.trim() === '' ? '' : `?q=${encodeURIComponent(term.trim())}`\n\n return admin.http.get<{ data: BlogTag[] }>(`${tagsBase}${suffix}`).then(data)\n },\n createTag: (input) => admin.http.post<{ data: TagRow }>(tagsBase, input).then(data),\n publish: (id, at, revision) =>\n admin.http\n .post<{ data: ArticleRow }>(`${base}/${id}/publish`, {\n ...(at == null ? {} : { at }),\n ...(revision ? { revision } : {}),\n })\n .then(data),\n unpublish: (id) =>\n admin.http.post<{ data: ArticleRow }>(`${base}/${id}/unpublish`, {}).then(data),\n remove: (id) => admin.http.delete<void>(`${base}/${id}`).then(() => undefined),\n restore: (id) => admin.http.post<{ data: ArticleRow }>(`${base}/${id}/restore`, {}).then(data),\n\n tagsPage: (query = {}) => {\n const search = new URLSearchParams()\n\n if (query.q) search.set('q', query.q)\n // Only when they are on: `empty=0` would be a question the screen never means to put.\n if (query.empty) search.set('empty', '1')\n if (query.noindex) search.set('noindex', '1')\n if (query.sort) search.set('sort', query.sort)\n if (query.page && query.page > 1) search.set('page', String(query.page))\n if (query.per_page) search.set('per_page', String(query.per_page))\n\n const suffix = search.size > 0 ? `?${search}` : ''\n\n return admin.http\n .get<{\n data: TagRow[]\n meta: Omit<TagsPage, 'data' | 'filters'>\n filters: TagsPage['filters']\n }>(`${tagsBase}${suffix}`)\n .then((body) => ({ ...body.meta, data: body.data, filters: body.filters }))\n },\n saveTag: (id, input) => admin.http.put<{ data: TagRow }>(`${tagsBase}/${id}`, input).then(data),\n removeTag: (id) => admin.http.delete<void>(`${tagsBase}/${id}`).then(() => undefined),\n massTags: (ids, action) =>\n admin.http\n .post<{ data: { affected: number } }>(`${tagsBase}/mass`, { ids, action })\n .then((body) => body.data.affected),\n mergeTags: (ids, keep, redirect) =>\n admin.http.post<{ data: TagMerged }>(`${tagsBase}/merge`, { ids, keep, redirect }).then(data),\n }\n}\n","<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'\nimport { onBeforeRouteLeave, useRoute, useRouter } from 'vue-router'\nimport {\n useAdmin,\n useDates,\n useEditing,\n useErrorText,\n useTranslate,\n WxEditingAlerts,\n WxSaveState,\n WxScreen,\n WxScreenHead,\n type ScreenAction,\n} from '@webx-ui/module-admin'\nimport { provideBlocksPreview } from '@webx-ui/module-blocks'\nimport {\n confirm,\n localizedValue,\n toast,\n useLocales,\n WxActionBar,\n WxBadge,\n WxBreadcrumb,\n WxBreadcrumbItem,\n WxButton,\n WxCard,\n WxSkeleton,\n type LocalizedValue,\n} from '@webx-ui/core'\nimport type { ScreenModel } from '@webx-ui/schema'\nimport { createBlogApi } from './api'\nimport { provideArticleEditor } from './editor'\nimport { useBlogMessages } from './i18n'\nimport type { ArticleConflict, ArticleDetail, ArticleOption, ArticleRow } from './types'\n\n/**\n * The editor of one article: a head that stays put, and the described screen under it.\n *\n * What this page owns is the head — what the article is called, what state it is in, and the\n * way out to the site — and the bar along the bottom, which is where it is saved and published.\n * Everything between them is `blog.article-form`, so a module or a project adds a tab to the\n * editor with a patch (§12) rather than with a fork of this file.\n *\n * Saving is by autosave: a pause after the last keystroke, and the moment a field is left. The\n * explicit button is still there, because a button that says \"saved\" is the only way a person\n * can be sure — but nothing is ever lost by not pressing it.\n *\n * The one thing here that a page editor does not have is the day. An article carries a date\n * that may be in the future, so \"publish\" means \"publish under the date in the settings tab\",\n * and which day that is stands under the name before the button is pressed (§7).\n */\nconst props = withDefaults(defineProps<{ base?: string }>(), { base: '/blog' })\n\nconst context = useAdmin()\nconst api = createBlogApi(context)\nconst route = useRoute()\nconst router = useRouter()\nconst locales = useLocales()\nconst dates = useDates()\nuseBlogMessages()\n\nconst t = useTranslate('webx-blog')\nconst panel = useTranslate('webx-admin')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\n/** How long after the last keystroke the draft goes to the server. */\nconst PAUSE = 1200\n\nconst id = computed(() => Number(route.params.id))\nconst list = computed(() => `${props.base}/articles`)\n\nconst article = ref<ArticleRow | null>(null)\nconst values = ref<ScreenModel>({})\nconst revision = ref('')\nconst prefix = ref('')\nconst previewUrl = ref<string | null>(null)\nconst options = ref<ArticleDetail['options']>({ rubrics: [], authors: [] })\nconst related = ref<ArticleOption[]>([])\n\nconst loading = ref(true)\nconst saving = ref(false)\nconst working = ref(false)\nconst errors = ref<Record<string, string[]>>({})\n\nconst snapshot = ref('')\nconst reloadToken = ref(0)\n\nconst canManage = computed(() => context.can('blog.articles.manage'))\n\n/*\n * Other people on the same article: a refused save is merged with theirs field by field and\n * saved again, only a field both changed is asked about, and a save made meanwhile — by a\n * colleague or an agent — is offered before this editor writes over it.\n */\nconst editing = useEditing<ScreenModel>({\n entity: 'articles',\n id: () => article.value?.id,\n values,\n revision,\n read: async () => {\n const detail = await api.get(id.value)\n\n return { values: detail.values, revision: detail.revision }\n },\n adopt: (theirs) => {\n snapshot.value = JSON.stringify(theirs.values)\n reloadToken.value += 1\n },\n // Published or restored by somebody else: the badge and the address follow, and the form\n // stays as it is.\n refresh: () => refresh(),\n restore: async () => {\n if (article.value) await api.restore(article.value.id)\n },\n canWrite: () => canManage.value,\n busy: () => saving.value || flight !== undefined,\n screen: 'blog.article-form',\n})\n\nconst conflict = editing.conflict\n\n/** Closed for writing: no permission, or a publication in flight. */\nconst locked = computed(() => !canManage.value || working.value)\n\nconst current = computed(() => JSON.stringify(values.value))\nconst dirty = computed(() => snapshot.value !== '' && current.value !== snapshot.value)\n\n/** Saved · saving · not saved yet — the one line of the bar that moves while typing. */\nconst state = computed<'saving' | 'unsaved' | 'saved'>(() => {\n if (saving.value) return 'saving'\n\n return dirty.value ? 'unsaved' : 'saved'\n})\n\n/**\n * The name in the head follows the field rather than the answer: an editor who renamed the\n * article in the settings tab should see the new name at the top before the save lands.\n */\nconst title = computed(() => {\n const written = values.value.title as LocalizedValue | string | null | undefined\n\n return localizedValue(written, locales.active.value, article.value?.title ?? '')\n})\n\n/** The day in the settings tab, as the picker leaves it: `2026-09-24 08:00:00`, or nothing. */\nconst chosen = computed<string | null>(() => {\n const written = values.value.published_at\n\n return typeof written === 'string' && written !== '' ? written : null\n})\n\nconst future = computed(() => {\n if (chosen.value === null) return false\n\n // The picker's value is local time with a space in it, which Safari refuses to parse.\n const at = new Date(chosen.value.replace(' ', 'T'))\n\n return !Number.isNaN(at.valueOf()) && at.valueOf() > Date.now()\n})\n\n/**\n * When the article goes out, under its name.\n *\n * Only the day, and only when there is one. What state it is in the badges beside the name say\n * already — live, scheduled, with edits waiting — and saying it twice in two shapes is how a\n * screen teaches an editor to read neither. The day is the part a badge cannot carry, and the\n * part somebody about to press \"publish\" actually weighs.\n */\nconst publication = computed(() => {\n const row = article.value\n\n if (!row) return ''\n\n const when = dates.short(row.published_at)\n\n const line = {\n scheduled: t('article.live-scheduled', { date: when }),\n published: t('article.live-since', { date: when }),\n modified: t('article.live-since', { date: when }),\n }[row.status as 'scheduled' | 'published' | 'modified']\n\n return line ?? ''\n})\n\n/* The constructor shows the draft as a page of the site. The link is the editor's to hand\n over: only this screen knows which entity it is editing. */\nprovideBlocksPreview({ url: computed(() => previewUrl.value), reload: reloadToken })\n\nprovideArticleEditor({\n article,\n values,\n options,\n related,\n prefix,\n base: props.base,\n canManage: canManage.value,\n disabled: locked,\n reload: () => load(true),\n save: () => save(),\n})\n\nfunction take(detail: ArticleDetail): void {\n article.value = detail.article\n values.value = detail.values\n revision.value = detail.revision\n prefix.value = detail.prefix\n previewUrl.value = detail.preview_url\n options.value = detail.options\n related.value = detail.related\n snapshot.value = JSON.stringify(detail.values)\n editing.opened(detail.values)\n conflict.value = null\n}\n\n/**\n * Read the article again.\n *\n * Quietly unless this is the first time: the skeleton replaces the whole screen, tabs and all,\n * so a publication answered with it would throw an editor who published from the history back\n * onto the first tab — and flash the page on the way.\n */\nasync function load(silent = false): Promise<void> {\n if (!silent) loading.value = true\n\n try {\n take(await api.get(id.value))\n } catch (error) {\n toast.danger(message(error))\n void router.push(list.value)\n } finally {\n loading.value = false\n }\n}\n\n/**\n * What surrounds the form, read again: the row with its status, the address, the preview. Not\n * the values — the form is somebody's work in progress, and a change of its content is the\n * notice's to offer.\n */\nasync function refresh(): Promise<void> {\n try {\n const detail = await api.get(id.value)\n\n article.value = detail.article\n prefix.value = detail.prefix\n previewUrl.value = detail.preview_url\n } catch {\n // The heartbeat says what happened; a row that could not be read again stays as it was.\n }\n}\n\n/* One pending save at a time, and one pending pause. */\nlet timer: ReturnType<typeof setTimeout> | undefined\n\nfunction schedule(): void {\n if (!canManage.value || conflict.value || !dirty.value || editing.stopped.value) return\n\n clearTimeout(timer)\n timer = setTimeout(() => void save(), PAUSE)\n}\n\n/** A field was left: write now rather than at the end of a pause nobody is waiting through. */\nfunction onFocusOut(): void {\n if (!canManage.value || conflict.value || !dirty.value || saving.value || editing.stopped.value)\n return\n\n clearTimeout(timer)\n void save()\n}\n\n/* The save on its way, and what it carries. */\nlet flight: { carried: string; done: Promise<void> } | undefined\n\n/**\n * A save asked for while another is on its way waits for it instead of being dropped: leaving and\n * publishing read `dirty` right after, and a save that was skipped reads as one that failed.\n */\nasync function save(): Promise<void> {\n while (flight) {\n const { carried, done } = flight\n\n await done\n\n // That request wrote what it carried; go again only for what was typed while it was out.\n if (!dirty.value || conflict.value || current.value === carried) return\n }\n\n const carried = current.value\n const done = write()\n\n flight = { carried, done }\n\n try {\n await done\n } finally {\n if (flight?.done === done) flight = undefined\n }\n}\n\nasync function write(): Promise<void> {\n if (!article.value || !canManage.value) return\n\n clearTimeout(timer)\n\n // What is being sent, so that whatever is typed while the request is in flight stays dirty\n // and gets its own save rather than being marked as written.\n const sending = current.value\n const sent = values.value\n\n saving.value = true\n errors.value = {}\n\n try {\n const detail = await api.save(article.value.id, { values: sent, revision: revision.value })\n\n article.value = detail.article\n revision.value = detail.revision\n prefix.value = detail.prefix\n previewUrl.value = detail.preview_url\n options.value = detail.options\n related.value = detail.related\n conflict.value = null\n\n if (current.value === sending) snapshot.value = sending\n\n // What the server holds now is what was sent: the base of the next merge.\n editing.opened(sent)\n\n // The preview is rendered from the draft, and the draft is what was just written.\n reloadToken.value += 1\n } catch (error) {\n const failure = error as {\n status?: number\n body?: ArticleConflict & { errors?: Record<string, string[]> }\n }\n\n if (failure.status === 409 && failure.body?.data) {\n const theirs = failure.body.data\n\n // Nothing overlapping: both edits are in the form now, and they go to the server again.\n if (\n editing.refused({\n values: theirs.values,\n revision: theirs.revision,\n changed: failure.body.changed,\n })\n ) {\n article.value = theirs.article\n void save()\n }\n } else if (failure.body?.errors) {\n errors.value = failure.body.errors\n toast.danger(t('article.save-failed'))\n } else if (!(await editing.failed(error))) {\n // In the bin is said by the notice above the form, with the way back; anything else here.\n toast.danger(message(error))\n }\n } finally {\n saving.value = false\n }\n}\n\n/** Give up what was typed and take the article as it now is; the component asked first. */\nasync function takeTheirs(): Promise<void> {\n await load(true)\n}\n\n/**\n * Throw away what is waiting and keep what the site is showing (§10).\n *\n * Offered only while there is a difference to throw away. Asked about, because it is the one\n * button on this screen that destroys writing — and answered honestly: the last few minutes are\n * still in the autosave ring, but nothing in the panel lists them, so the question does not\n * promise a way back.\n */\nasync function discard(): Promise<void> {\n if (!article.value) return\n\n const agreed = await confirm({\n title: panel('editor.discard-title'),\n message: panel('editor.discard-text'),\n confirmText: t('article.discard'),\n // Not «Cancel»: beside «Discard changes» the two read as the same word.\n cancelText: panel('editor.keep-changes'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n working.value = true\n\n try {\n take(await api.discard(article.value.id))\n toast.success(t('article.discarded'))\n } catch (error) {\n toast.danger(message(error))\n } finally {\n working.value = false\n }\n}\n\n/**\n * Publishing is asked about, because it is the one action here that visitors see: everything\n * else on this screen writes a draft nobody outside the panel can read.\n *\n * The question names the day as well as the address, and that is the whole point of asking. A\n * date in the future means the article does not appear — it waits — and an editor who thought\n * they were publishing now would go and look for it on the site (§7).\n */\nasync function publish(): Promise<void> {\n // Saved before asking: the question names the address the draft will publish at, and only a\n // saved draft has one — an address typed but not saved was asked about under the old one.\n if (dirty.value) await save()\n if (conflict.value || dirty.value) return\n\n if (!article.value) return\n\n // The draft on the server may hold somebody else's edit this editor never pulled in: said\n // before it goes on the site, with whose it is and where, rather than published unseen.\n const held = await editing.beforePublish()\n\n if (!held) return\n\n const row = article.value\n\n // Publishing moves a renamed slug, so the question names where the page will be, not where\n // it is — and says the old address will lead there, since that is what happens to it.\n const next = row.next_path ?? row.path\n const old =\n row.next_path != null && row.path !== null\n ? ` ${panel('editor.publish-moves', { old: `/${row.path}` })}`\n : ''\n const address = next === null ? null : `/${next}`\n const when = chosen.value === null ? t('article.publish-now') : dates.short(chosen.value)\n\n // One question is enough: whoever just agreed to publish somebody else's changes has said yes.\n const agreed =\n held.asked ||\n (await confirm({\n title: t('article.publish-title', { title: row.title }),\n message: future.value\n ? t('article.publish-later', { date: when })\n : address === null\n ? t('article.publish-nowhere')\n : t('article.publish-text', { address }) + old,\n confirmText: future.value ? t('article.schedule') : t('panel.publish'),\n cancelText: t('panel.cancel'),\n }))\n\n if (!agreed) return\n\n working.value = true\n\n try {\n await api.publish(row.id, chosen.value, held.revision)\n await load(true)\n toast.success(future.value ? t('article.scheduled') : t('panel.published'))\n } catch (error) {\n if (!(await editing.failed(error))) toast.danger(message(error))\n } finally {\n working.value = false\n }\n}\n\n/**\n * Taking it off the site is asked about, as publishing is: visitors are who notice. Nothing\n * written is lost, and «Publish» brings it back at the same address.\n */\nasync function unpublish(): Promise<void> {\n const row = article.value\n\n if (!row) return\n\n const agreed = await confirm({\n title: panel('editor.unpublish-title'),\n message: panel('editor.unpublish-text'),\n confirmText: panel('editor.unpublish'),\n cancelText: panel('editor.keep-published'),\n })\n\n if (!agreed) return\n\n working.value = true\n\n try {\n await api.unpublish(row.id)\n await load(true)\n toast.success(panel('editor.unpublished'))\n } catch (error) {\n toast.danger(message(error))\n } finally {\n working.value = false\n }\n}\n\nfunction badge(): 'default' | 'success' | 'warning' | 'primary' {\n const status = article.value?.status\n\n if (status === 'published') return 'success'\n if (status === 'modified') return 'warning'\n\n return status === 'scheduled' ? 'primary' : 'default'\n}\n\n/* The browser's own guard: it cannot wait for a request, so all it can do is ask. */\nfunction leaveGuard(event: BeforeUnloadEvent): void {\n if (dirty.value) event.preventDefault()\n}\n\nwatch(current, schedule)\nwatch(id, () => void load())\n\nonMounted(() => {\n void load()\n window.addEventListener('beforeunload', leaveGuard)\n})\n\nonBeforeUnmount(() => {\n clearTimeout(timer)\n window.removeEventListener('beforeunload', leaveGuard)\n})\n\n/**\n * Leaving flushes the pause rather than asking about it: everything else on this screen saves\n * by itself, and a dialog that appears only because somebody left within a second of typing\n * would be the one place that does not. The question is asked only when the save did not go\n * through — a conflict, a refused field, a server that is not there.\n */\nonBeforeRouteLeave(async () => {\n if (!dirty.value || !canManage.value) return true\n\n await save()\n\n if (!dirty.value) return true\n\n return await confirm({\n title: t('article.leave-title'),\n message: t('article.leave-text'),\n confirmText: t('article.leave'),\n cancelText: t('panel.cancel'),\n tone: 'danger',\n })\n})\n/* What leads away from the article. On a phone the head folds them into the ···. */\nconst actions = computed<ScreenAction[]>(() => {\n const leads: ScreenAction[] = []\n\n if (previewUrl.value) {\n leads.push({ key: 'preview', label: t('article.preview'), icon: 'eye', href: previewUrl.value })\n }\n\n if (article.value?.url && article.value.status !== 'draft') {\n leads.push({\n key: 'site',\n label: t('panel.open-on-site'),\n icon: 'link',\n href: article.value.url,\n })\n }\n\n // On the site now: the way off it is a button of its own beside «Open on the site», not a\n // line in the ···. Hiding loses nothing and is undone by «Publish».\n if (\n canManage.value &&\n (article.value?.status === 'published' || article.value?.status === 'modified')\n ) {\n leads.push({\n key: 'unpublish',\n label: panel('editor.unpublish'),\n icon: 'eye-off',\n run: () => void unpublish(),\n })\n }\n\n /*\n * Throwing away what was written since the last publication: in the `···` at every width and\n * never a button, which is what `menu` is for. It used to stand in the action bar next to\n * \"publish\", one slip away from it — and it is offered at all only while there is a\n * difference between what is written and what is on the site.\n */\n if (article.value?.status === 'modified') {\n leads.push({\n key: 'discard',\n label: t('article.discard'),\n icon: 'refresh',\n danger: true,\n menu: true,\n run: () => void discard(),\n })\n }\n\n return leads\n})\n</script>\n\n<template>\n <!-- No `data-wx-fill`: this screen is a window tall only while the constructor is the tab on\n screen, and that is a question only CSS can ask. `WxMain` still gives it a floor of one\n window, because it carries an action bar. -->\n <div class=\"wx-article-editor\" @focusout=\"onFocusOut\">\n <template v-if=\"loading || !article\">\n <!-- Shaped like the screen it stands in for: the head on the ground, and what it is\n waiting for on the card that will hold it. Two bare strips on an empty background\n read as a page that failed rather than one that is a moment away. -->\n <wx-skeleton class=\"wx-article-editor__ghost\" title :rows=\"1\" />\n <wx-card><wx-skeleton :rows=\"8\" /></wx-card>\n </template>\n\n <template v-else>\n <!--\n The panel's head: the way out, the trail, the name and the state of the article. What is\n left in it is what leads away from the article — the draft on the site and the article on\n the site. Saving and publishing are down in the bar, where the eye is, and only there: a\n second copy of a button already on screen is not a reminder (§3.3).\n -->\n <wx-screen-head\n divider\n :back=\"list\"\n :back-label=\"t('module.articles')\"\n :title=\"title || t('article.untitled')\"\n :subtitle=\"publication\"\n :actions=\"actions\"\n >\n <template #trail>\n <wx-breadcrumb size=\"sm\" :label=\"t('article.trail')\">\n <wx-breadcrumb-item :as=\"'router-link'\" :to=\"list\">\n {{ t('module.articles') }}\n </wx-breadcrumb-item>\n <wx-breadcrumb-item current>{{ title || t('article.untitled') }}</wx-breadcrumb-item>\n </wx-breadcrumb>\n </template>\n\n <template #title-after>\n <wx-badge :type=\"badge()\" dot>{{ t(`panel.status-${article.status}`) }}</wx-badge>\n <!-- What is waiting, said beside the state rather than instead of it, exactly as the\n list says it: the article is on the site, and there is something that is not.\n The bar used to carry this in words; the head is where the state of the record\n already lives. -->\n <wx-badge v-if=\"article.status === 'modified'\" type=\"primary\" round>\n {{ t('panel.edits') }}\n </wx-badge>\n </template>\n </wx-screen-head>\n\n <!-- Somebody else wrote while this editor was open. What does not overlap is merged\n without a word; what does is listed place by place. -->\n <wx-editing-alerts :editing=\"editing\" @save=\"save\" @theirs=\"takeTheirs\" />\n\n <div class=\"wx-article-editor__screen\">\n <wx-screen v-model=\"values\" name=\"blog.article-form\" :errors=\"errors\" :disabled=\"locked\" />\n </div>\n\n <!--\n The last row of the screen, not a layer over it: the tab above shrinks by the height of\n the bar and is never covered by it.\n -->\n <!--\n Two buttons and a mark, the same bar the page editor has.\n\n What used to stand here besides them: a sentence saying since when the article is on the\n site and that there are unpublished edits, and a third button to throw those edits away.\n Half the sentence is what the badge beside the name already says, the other half is a\n fact about the article rather than about the last keystroke and now stands under its\n name; and throwing work away is not a button to keep beside \"publish\" — it is in the\n `···`, where this panel keeps everything that cannot be undone.\n -->\n <wx-action-bar v-if=\"canManage\">\n <template #state>\n <wx-save-state v-if=\"!editing.stopped.value\" :state=\"state\" />\n </template>\n\n <wx-button\n variant=\"outline\"\n :loading=\"saving\"\n :disabled=\"!dirty || editing.stopped.value\"\n :title=\"editing.blocked.value\"\n @click=\"save\"\n >\n {{ t('article.save') }}\n </wx-button>\n\n <wx-button\n type=\"primary\"\n :loading=\"working\"\n :disabled=\"editing.stopped.value || (article.status === 'published' && !dirty)\"\n :title=\"editing.blocked.value\"\n @click=\"publish\"\n >\n {{ future ? t('article.schedule') : t('panel.publish') }}\n </wx-button>\n </wx-action-bar>\n </template>\n </div>\n</template>\n\n<style scoped>\n.wx-article-editor {\n display: flex;\n flex-direction: column;\n gap: var(--wx-gap, var(--wx-space-16));\n /* The constructor is the whole screen below the head, so the page is as tall as what it is\n drawn in and never taller: the tree, the form and the preview scroll inside themselves,\n and the head stays where it was put. */\n height: 100%;\n min-height: 0;\n container-type: inline-size;\n}\n\n.wx-article-editor__ghost {\n max-width: 420px;\n}\n\n.wx-article-editor__screen {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-height: 0;\n}\n\n/*\n * The tabs are the screen's, not this page's, so the height has to travel through them: every\n * box between the page and the constructor is a flex column that may shrink, or the constructor\n * sizes itself to its content and the whole page scrolls with the head on top of it.\n *\n * `:not([hidden])` is load-bearing. The tabs are kept alive, so the three that are not on\n * screen are still in the document, and a `display: flex` that reached them would make each one\n * a quarter of the height the visible one should have had.\n */\n.wx-article-editor__screen :deep(.wx-screen-host),\n.wx-article-editor__screen :deep(.wx-screen),\n.wx-article-editor__screen :deep(.wx-tabs),\n.wx-article-editor__screen :deep(.wx-tabs__layout),\n.wx-article-editor__screen :deep(.wx-tabs__panels),\n.wx-article-editor__screen :deep(.wx-tab:not([hidden])) {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-height: 0;\n}\n\n/*\n * No tab is a box of a fixed height any more, and none has a scrollbar of its own: the\n * constructor's preview is as tall as the page it shows and the browser scrolls it. A tab\n * that grows with its contents is also the only kind that does not clip them — a scroll box\n * cut the shadows off the cards inside it square at all four edges.\n */\n</style>\n","<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\nimport { useAdmin, useErrorText, useTranslate, WxDate, WxDrafts } from '@webx-ui/module-admin'\nimport { confirm, toast, WxBadge, WxButton, WxEmpty, WxSkeleton, WxText } from '@webx-ui/core'\nimport { createBlogApi } from './api'\nimport { useArticleEditor } from './editor'\nimport { useBlogMessages } from './i18n'\nimport type { ArticleVersion } from './types'\n\n/**\n * What was published, when, by whom and from where.\n *\n * Publications first, and the copies of the draft under them in a list of their own: the autosave\n * ring, and a draft somebody else's save wrote over. Mixed into one line they would be a list\n * nobody can read; left out, an edit written over would have nowhere to be found.\n *\n * Restoring makes the old version the draft. Publishing it is the same separate step it always\n * is, which is what keeps the history a line: nothing here changes the site by itself.\n */\ndefineOptions({ name: 'WxArticleHistory' })\n\nconst context = useAdmin()\nconst api = createBlogApi(context)\nconst editor = useArticleEditor()\nuseBlogMessages()\n\nconst t = useTranslate('webx-blog')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\nconst versions = ref<ArticleVersion[] | null>(null)\nconst working = ref(false)\n\nconst article = computed(() => editor?.article.value ?? null)\nconst canManage = computed(() => context.can('blog.articles.manage'))\n\n/**\n * Which line is the site: the newest publication, and only while the article is on the site at\n * all. One taken down still has its history, and none of it is live — and neither is the\n * history of one dated next Tuesday, which is the difference an article has and a page has not.\n */\nconst live = computed(() =>\n article.value?.status === 'published' || article.value?.status === 'modified'\n ? (versions.value?.[0]?.number ?? null)\n : null,\n)\n\nasync function load(): Promise<void> {\n const current = article.value\n\n if (!current) return\n\n try {\n versions.value = await api.versions(current.id)\n } catch (error) {\n versions.value = []\n toast.danger(message(error))\n }\n}\n\nasync function restore(version: ArticleVersion): Promise<void> {\n const current = article.value\n\n if (!current) return\n\n const agreed = await confirm({\n title: t('article.restore-title', { number: version.number }),\n message: t('article.restore-text'),\n confirmText: t('article.restore-version'),\n cancelText: t('panel.cancel'),\n })\n\n if (!agreed) return\n\n working.value = true\n\n try {\n await api.restoreVersion(current.id, version.number)\n // Through the editor rather than with the answer: the values it holds are what the form is\n // bound to, and a version restored underneath it has to reach the fields.\n await editor?.reload()\n toast.success(t('article.restored-version', { number: version.number }))\n } catch (error) {\n toast.danger(message(error))\n } finally {\n working.value = false\n }\n}\n\n// The article is read again after every publication and every restore, so its stamp is the\n// signal that there is something new to list.\nwatch(\n () => [article.value?.id, article.value?.published_at, article.value?.updated_at],\n () => void load(),\n { immediate: true },\n)\n</script>\n\n<template>\n <div class=\"wx-article-history-tab\">\n <div class=\"wx-article-history\">\n <!-- Where a row would stand, not against the border: a skeleton flush with a rounded\n corner has its ends clipped by it, which reads as a drawing fault rather than as\n something loading. -->\n <wx-skeleton v-if=\"versions === null\" class=\"wx-article-history__ghost\" :rows=\"3\" />\n <wx-empty v-else-if=\"versions.length === 0\" :description=\"t('article.history-empty')\" />\n <div\n v-for=\"version in versions\"\n v-else\n :key=\"version.number\"\n class=\"wx-article-history__row\"\n :class=\"{ 'is-live': version.number === live }\"\n >\n <code class=\"wx-article-history__number\">{{\n t('article.version', { number: version.number })\n }}</code>\n <div class=\"wx-article-history__who\">\n <wx-date v-if=\"version.created_at\" :value=\"version.created_at\" size=\"md\" tone=\"default\" />\n <wx-text size=\"sm\" tone=\"muted\">\n {{ version.author ?? t(`article.source-${version.source}`) }}\n <template v-if=\"version.comment\"> · {{ version.comment }}</template>\n </wx-text>\n </div>\n <wx-badge v-if=\"version.number === live\" type=\"success\" dot>{{\n t('article.version-live')\n }}</wx-badge>\n <wx-button\n v-if=\"canManage && version.number !== live\"\n size=\"sm\"\n variant=\"outline\"\n :loading=\"working\"\n @click=\"restore(version)\"\n >\n {{ t('article.restore-version') }}\n </wx-button>\n </div>\n </div>\n\n <wx-drafts\n :id=\"article?.id\"\n entity=\"articles\"\n screen=\"blog.article-form\"\n :can-restore=\"canManage\"\n :stamp=\"[article?.published_at, article?.updated_at]\"\n @restored=\"editor?.reload()\"\n />\n </div>\n</template>\n\n<style scoped>\n.wx-article-history-tab {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-24);\n}\n\n.wx-article-history {\n display: flex;\n flex-direction: column;\n border: 1px solid var(--wx-border-default);\n border-radius: var(--wx-radius-md);\n background: var(--wx-bg-surface);\n /* Nothing reaches past the rounding: a row, or the skeleton standing in for three of them,\n is a square box and its corners showed through the panel's. */\n overflow: hidden;\n}\n\n.wx-article-history__ghost {\n padding: var(--wx-space-10) var(--wx-space-12);\n}\n\n.wx-article-history__row {\n display: flex;\n align-items: center;\n gap: var(--wx-space-12);\n padding: var(--wx-space-10) var(--wx-space-12);\n border-block-end: 1px solid var(--wx-border-default);\n flex-wrap: wrap;\n}\n\n.wx-article-history__row:last-child {\n border-block-end: 0;\n}\n\n.wx-article-history__number {\n width: 40px;\n font-family: var(--wx-font-family-mono);\n font-size: var(--wx-font-size-sm);\n color: var(--wx-text-muted);\n}\n\n.wx-article-history__who {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-width: 160px;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, ref, watch } from 'vue'\nimport { useAdmin, useErrorText, useTranslate } from '@webx-ui/module-admin'\nimport {\n toast,\n WxAction,\n WxSelect,\n WxSortableList,\n type SelectModelValue,\n type SelectOption,\n} from '@webx-ui/core'\nimport { createBlogApi } from './api'\nimport { useArticleEditor } from './editor'\nimport { useBlogMessages } from './i18n'\nimport type { ArticleOption } from './types'\n\n/**\n * The articles pinned under this one by hand, in the order they are shown.\n *\n * Only the pinned ones. What fills the rest of the list is worked out on the site — most tags\n * in common, then the main rubric, published only (§8) — and it is not shown here because it\n * would be a list that changes under the editor without anybody touching it. A fully manual\n * list is what nobody fills in after the hundredth article; a fully automatic one is what\n * nobody can override when it is wrong. This is the override.\n */\ndefineOptions({ name: 'WxArticleRelated' })\n\nconst props = withDefaults(defineProps<{ disabled?: boolean }>(), { disabled: false })\n\nconst value = defineModel<number[]>({ default: () => [] })\n\nconst context = useAdmin()\nconst api = createBlogApi(context)\nconst editor = useArticleEditor()\nuseBlogMessages()\n\nconst t = useTranslate('webx-blog')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\nconst disabled = computed(() => props.disabled || editor?.disabled.value === true)\n\n/** How long after the last keystroke the server is asked. */\nconst PAUSE = 250\n\n/** As many matches as a dropdown is worth scrolling; past that the answer is to type more. */\nconst MATCHES = 10\n\nconst known = ref(new Map<number, string>())\nconst found = ref<ArticleOption[]>([])\n\nlet timer: ReturnType<typeof setTimeout> | undefined\n\nwatch(\n () => editor?.related.value,\n (titles) => {\n const all = new Map(known.value)\n\n for (const one of titles ?? []) all.set(one.id, one.title)\n\n known.value = all\n },\n { immediate: true, deep: true },\n)\n\nconst rows = computed<ArticleOption[]>({\n get: () => value.value.map((id) => ({ id, title: known.value.get(id) ?? `#${id}` })),\n set: (next) => {\n value.value = next.map((row) => row.id)\n },\n})\n\nconst options = computed<SelectOption[]>(() =>\n found.value\n .filter((one) => !value.value.includes(one.id))\n .map((one) => ({ label: one.title, value: one.id })),\n)\n\nasync function look(term: string): Promise<void> {\n const mine = editor?.article.value?.id\n\n try {\n const page = await api.articles({ q: term, per_page: MATCHES })\n\n // Never itself: the list under an article would offer the reader the page they are on.\n found.value = page.data\n .filter((row) => row.id !== mine)\n .map((row) => ({ id: row.id, title: row.title }))\n\n const all = new Map(known.value)\n\n for (const one of found.value) all.set(one.id, one.title)\n\n known.value = all\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\nfunction search(term: string): void {\n clearTimeout(timer)\n timer = setTimeout(() => void look(term), PAUSE)\n}\n\nfunction add(picked: SelectModelValue): void {\n if (typeof picked !== 'number' || value.value.includes(picked)) return\n\n value.value = [...value.value, picked]\n}\n\nfunction remove(id: number): void {\n value.value = value.value.filter((one) => one !== id)\n}\n\nwatch(\n () => editor?.article.value?.id,\n () => void look(''),\n { immediate: true },\n)\n\nonBeforeUnmount(() => clearTimeout(timer))\n</script>\n\n<template>\n <div class=\"wx-article-related\">\n <wx-sortable-list\n v-model=\"rows\"\n plain\n size=\"sm\"\n item-key=\"id\"\n item-label=\"title\"\n :disabled=\"disabled\"\n :empty-text=\"t('article.related-empty')\"\n :aria-label=\"t('article.related')\"\n >\n <template #default=\"{ item }\">\n <span class=\"wx-article-related__name\">{{ (item as ArticleOption).title }}</span>\n </template>\n\n <template #actions=\"{ item }\">\n <wx-action\n type=\"remove\"\n size=\"sm\"\n :disabled=\"disabled\"\n :title=\"t('article.related-remove')\"\n @click=\"remove((item as ArticleOption).id)\"\n />\n </template>\n </wx-sortable-list>\n\n <wx-select\n :model-value=\"null\"\n :options=\"options\"\n :placeholder=\"t('article.related-add')\"\n :empty-text=\"t('article.related-nothing')\"\n :disabled=\"disabled\"\n filterable\n teleport\n :aria-label=\"t('article.related-add')\"\n @search=\"search\"\n @update:model-value=\"add\"\n />\n </div>\n</template>\n\n<style scoped>\n.wx-article-related {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-8);\n}\n\n.wx-article-related__name {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { useTranslate } from '@webx-ui/module-admin'\nimport { useLocales, WxAlert, WxInput } from '@webx-ui/core'\nimport { useArticleEditor } from './editor'\nimport { useBlogMessages } from './i18n'\n\n/**\n * The address of an article: the part that is edited, with the part that is not in front of it.\n *\n * The field edits the last segment, and `signs-of-wear` on its own says nothing about whether\n * the blog lives at the root of the site or under `/blog/` — which is exactly what somebody\n * checking an address wants to see. That used to be a row of its own under the field, labelled\n * \"Address\" directly below a field labelled \"Address\": a whole line of the form spent on one\n * constant segment, and two labels that taught the reader to skim both. The prefix belongs\n * inside the control, where the address is read and written in one place.\n *\n * The line under it appears only once there is something to lose: an article that is on the\n * site and whose address is being changed leaves a redirect behind, and that is the fact that\n * decides whether this is safe to do at all. Said before the save rather than in a toast\n * afterwards.\n *\n * Nothing is declared as a prop on purpose. The screen hands a field its `name`, its value,\n * `localized` and whatever the description put in `props`, and a wrapper that declared any of\n * them would hand `WxInput` a `false` where the caller asked for nothing (CLAUDE.md §4).\n */\ndefineOptions({ name: 'WxArticleSlug', inheritAttrs: false })\n\nconst editor = useArticleEditor()\nconst locales = useLocales()\nuseBlogMessages()\n\nconst t = useTranslate('webx-blog')\n\n/** `/blog/`, or `/` for a blog that lives at the root of the site. */\nconst prefix = computed(() => {\n const head = editor?.prefix.value ?? ''\n\n return head === '' ? '/' : `/${head}/`\n})\n\n/** The last segment as it stands in the field, not as the registry last heard it. */\nconst slug = computed(() => {\n const written = editor?.values.value.slug\n\n if (typeof written === 'string') return written\n\n // The language being edited and no other. Falling back on a language that does have words\n // would print an address the site does not answer at (§9).\n return (written as Record<string, string> | undefined)?.[locales.active.value] ?? ''\n})\n\n/** On the site at an address that is about to become a different one. */\nconst moving = computed(() => {\n const current = editor?.article.value?.path\n\n return (\n typeof current === 'string' &&\n slug.value !== '' &&\n `${prefix.value}${slug.value}` !== `/${current}`\n )\n})\n</script>\n\n<template>\n <div class=\"wx-article-slug\">\n <wx-input v-bind=\"$attrs\">\n <template #prefix>\n <span class=\"wx-article-slug__prefix\">{{ prefix }}</span>\n </template>\n </wx-input>\n\n <wx-alert v-if=\"moving\" type=\"info\" variant=\"soft\" :description=\"t('article.address-moving')\" />\n </div>\n</template>\n\n<style scoped>\n.wx-article-slug {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-8);\n}\n\n/*\n * One address in one face.\n *\n * The prefix is part of the address and not a decoration on the field, so it is set the way the\n * panel prints every other address — and so is what is typed after it. Two faces on one line\n * do not sit on one line: the metrics differ, each box is centred on itself, and `/blog/` rode\n * two pixels above the word it belongs to. Only the muting tells the two apart, which is the\n * one difference worth drawing: the prefix is the part that cannot be typed over.\n */\n.wx-article-slug__prefix,\n.wx-article-slug :deep(.wx-input__inner) {\n font-family: var(--wx-font-family-mono);\n}\n\n.wx-article-slug__prefix {\n color: var(--wx-text-muted);\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, ref, watch } from 'vue'\nimport { useAdmin, useErrorText, useTranslate } from '@webx-ui/module-admin'\nimport {\n toast,\n WxBadge,\n WxButton,\n WxSelect,\n WxText,\n type SelectModelValue,\n type SelectOption,\n} from '@webx-ui/core'\nimport { createBlogApi } from './api'\nimport { useArticleEditor } from './editor'\nimport { useBlogMessages } from './i18n'\nimport type { BlogTag } from './types'\n\n/**\n * The tags on an article: find one by typing, or make it here.\n *\n * Making one here is the point (§2.8). Tags are entered by the hundred from the article being\n * written, and a field that could only pick from what already exists would mean leaving the\n * article, opening another section, making a word and coming back — which nobody does, so the\n * article goes out untagged instead.\n *\n * What holds the other half of that bargain is the number beside every suggestion: seeing that\n * \"belts\" already has forty articles is what stops \"belt\" from being the forty-first. The\n * screen that merges the ones that got away is a separate one, and it exists.\n */\ndefineOptions({ name: 'WxArticleTags' })\n\nconst props = withDefaults(defineProps<{ disabled?: boolean }>(), { disabled: false })\n\nconst value = defineModel<number[]>({ default: () => [] })\n\nconst context = useAdmin()\nconst api = createBlogApi(context)\nconst editor = useArticleEditor()\nuseBlogMessages()\n\nconst t = useTranslate('webx-blog')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\nconst disabled = computed(() => props.disabled || editor?.disabled.value === true)\n\n/** How long after the last keystroke the server is asked. */\nconst PAUSE = 250\n\n/**\n * Every tag this session has seen a title for: the article's own, whatever the last search\n * answered, and whatever was made here. A chip needs a word, and the value is only ids.\n */\nconst known = ref(new Map<number, string>())\nconst found = ref<BlogTag[]>([])\nconst term = ref('')\nconst working = ref(false)\n\nlet timer: ReturnType<typeof setTimeout> | undefined\n\nwatch(\n () => editor?.article.value?.tags,\n (tags) => {\n const all = new Map(known.value)\n\n for (const tag of tags ?? []) all.set(tag.id, tag.title)\n\n known.value = all\n },\n { immediate: true, deep: true },\n)\n\nconst chips = computed(() =>\n value.value.map((id) => ({ id, title: known.value.get(id) ?? `#${id}` })),\n)\n\nconst options = computed<SelectOption[]>(() =>\n found.value\n .filter((tag) => !value.value.includes(tag.id))\n .map((tag) => ({\n // The count is part of the label rather than a column: a select draws a line of text, and\n // the number is what the reader is choosing between two near-identical words on.\n label: t('article.tag-option', { title: tag.title, count: tag.articles_count }),\n value: tag.id,\n })),\n)\n\n/** Nothing on the server is spelled exactly this way — so this is a word, not a typo of one. */\nconst missing = computed(() => {\n const typed = term.value.trim()\n\n if (typed === '') return null\n\n const same = (title: string): boolean => title.toLowerCase() === typed.toLowerCase()\n\n return found.value.some((tag) => same(tag.title)) ? null : typed\n})\n\nasync function look(): Promise<void> {\n try {\n found.value = await api.tags(term.value)\n\n const all = new Map(known.value)\n\n for (const tag of found.value) all.set(tag.id, tag.title)\n\n known.value = all\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\nfunction search(typed: string): void {\n term.value = typed\n\n clearTimeout(timer)\n timer = setTimeout(() => void look(), PAUSE)\n}\n\nfunction add(picked: SelectModelValue): void {\n if (typeof picked !== 'number' || value.value.includes(picked)) return\n\n value.value = [...value.value, picked]\n term.value = ''\n}\n\nfunction remove(id: number): void {\n value.value = value.value.filter((one) => one !== id)\n}\n\nasync function create(): Promise<void> {\n const title = missing.value\n\n if (title === null || working.value) return\n\n working.value = true\n\n try {\n const tag = await api.createTag({ title })\n\n known.value = new Map(known.value).set(tag.id, tag.title)\n found.value = [tag, ...found.value]\n add(tag.id)\n } catch (error) {\n toast.danger(message(error))\n } finally {\n working.value = false\n }\n}\n\n// The first opening of the list asks with an empty term, which answers with the most used.\nwatch(\n () => editor?.article.value?.id,\n () => void look(),\n { immediate: true },\n)\n\nonBeforeUnmount(() => clearTimeout(timer))\n</script>\n\n<template>\n <div class=\"wx-article-tags\">\n <div v-if=\"chips.length > 0\" class=\"wx-article-tags__chips\">\n <!-- The badge closes itself. It used to carry a `WxAction` in its slot, and an icon\n button of the panel is thirty pixels tall inside a chip whose words are fifteen: the\n chip grew to fit the button, the word sat on its own baseline three pixels below the\n cross, and the air left of the word was half the air right of it. `closable` is the\n badge's own cross, sized to the words it stands beside. -->\n <wx-badge\n v-for=\"chip in chips\"\n :key=\"chip.id\"\n type=\"default\"\n :closable=\"!disabled\"\n :close-label=\"t('article.tag-remove')\"\n @close=\"remove(chip.id)\"\n >\n {{ chip.title }}\n </wx-badge>\n </div>\n <wx-text v-else size=\"sm\" tone=\"muted\">{{ t('article.tags-empty') }}</wx-text>\n\n <div class=\"wx-article-tags__pick\">\n <wx-select\n :model-value=\"null\"\n :options=\"options\"\n :placeholder=\"t('article.tag-add')\"\n :empty-text=\"t('article.tag-nothing')\"\n :disabled=\"disabled\"\n filterable\n teleport\n :aria-label=\"t('article.tag-add')\"\n @search=\"search\"\n @update:model-value=\"add\"\n />\n\n <!-- Beside the field rather than inside the list: a line in a dropdown that creates\n something is a line somebody picks by mistake while scrolling. -->\n <wx-button\n v-if=\"missing\"\n variant=\"outline\"\n icon=\"plus\"\n :loading=\"working\"\n :disabled=\"disabled\"\n @click=\"create\"\n >\n {{ t('article.tag-create', { title: missing }) }}\n </wx-button>\n </div>\n </div>\n</template>\n\n<style scoped>\n.wx-article-tags {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-8);\n}\n\n.wx-article-tags__chips {\n display: flex;\n flex-wrap: wrap;\n gap: var(--wx-space-6);\n}\n\n.wx-article-tags__pick {\n display: flex;\n align-items: center;\n gap: var(--wx-space-8);\n flex-wrap: wrap;\n}\n\n.wx-article-tags__pick > :deep(.wx-select) {\n flex: 1 1 220px;\n min-width: 0;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\nimport { useAdmin, useErrorText, useTranslate } from '@webx-ui/module-admin'\nimport { confirm, toast, WxButton, WxFormItem } from '@webx-ui/core'\nimport { createBlogApi } from './api'\nimport { useArticleEditor } from './editor'\nimport { useBlogMessages } from './i18n'\n\n/**\n * Taking the article off the site, from the tab where the rest of its publication is decided.\n *\n * It lives here and not in the action bar because the bar holds what is done to the draft —\n * save, publish — and this is done to what visitors are reading. Under the day it goes out for\n * the same reason: whether an article is on the site and when it got there is one question, and\n * an editor who has just looked at the date is the one deciding to pull it.\n *\n * Offered only while there is something to take off. A draft was never there, and one already\n * taken off has nowhere further to go — the way back is \"publish\", which is in the bar.\n *\n * It is not \"delete\": the article keeps its address in the registry, its history and its\n * rubrics, and publishing puts it back exactly where it was.\n */\ndefineOptions({ name: 'WxArticleUnpublish' })\n\nconst context = useAdmin()\nconst api = createBlogApi(context)\nconst editor = useArticleEditor()\nuseBlogMessages()\n\nconst t = useTranslate('webx-blog')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\nconst working = ref(false)\n\n/** On the site, with or without edits waiting — or waiting for a day that has not come. */\nconst live = computed(() => {\n const status = editor?.article.value?.status\n\n return status === 'published' || status === 'modified' || status === 'scheduled'\n})\n\nconst disabled = computed(() => editor?.disabled.value === true || working.value)\n\n/**\n * Asked about, because this is the one thing on the settings tab that visitors see happen.\n * Everything else here writes a draft nobody outside the panel can read.\n */\nasync function unpublish(): Promise<void> {\n const row = editor?.article.value\n\n if (!row) return\n\n const agreed = await confirm({\n title: t('article.unpublish-title', { title: row.title }),\n message:\n row.status === 'scheduled' ? t('article.unpublish-waiting') : t('article.unpublish-text'),\n confirmText: t('panel.unpublish'),\n cancelText: t('panel.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n working.value = true\n\n try {\n await api.unpublish(row.id)\n await editor?.reload()\n toast.success(t('panel.unpublished-done'))\n } catch (error) {\n toast.danger(message(error))\n } finally {\n working.value = false\n }\n}\n</script>\n\n<template>\n <wx-form-item\n v-if=\"live && editor?.canManage\"\n class=\"wx-article-unpublish\"\n :help=\"t('article.unpublish-help')\"\n >\n <wx-button\n variant=\"outline\"\n type=\"danger\"\n icon=\"eye-off\"\n :loading=\"working\"\n :disabled=\"disabled\"\n @click=\"unpublish\"\n >\n {{ t('panel.unpublish') }}\n </wx-button>\n </wx-form-item>\n</template>\n\n<style scoped>\n/*\n * A form item is a column that stretches what it holds, which is right for a field and wrong\n * for a button: the one thing on this card that is not a field would otherwise be the widest\n * thing on it, six hundred pixels of red outline under a date picker.\n *\n * Through `:deep()`, because the column belongs to `WxFormItem` and a scoped rule reaches only\n * as far as this component's own elements.\n */\n.wx-article-unpublish :deep(.wx-button) {\n align-self: flex-start;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { nextTick, onMounted, ref, useTemplateRef } from 'vue'\nimport { useAdmin, useTranslate } from '@webx-ui/module-admin'\nimport { useModal, WxButton, WxDialog, WxFormItem, WxInput, WxSpace } from '@webx-ui/core'\nimport { createBlogApi } from './api'\nimport { useBlogMessages } from './i18n'\nimport type { ArticleRow } from './types'\n\n/**\n * A new article: what it is called, and nothing else.\n *\n * No address field here, unlike the dialog that starts a page. A page is created inside a\n * branch and its address is the one thing about it that cannot be guessed; an article lives in\n * one flat space under the blog's prefix, and the server makes the slug out of the title — the\n * same transliteration it would apply to anything typed in the field. The address is on the\n * editor's own settings tab, where there is room to say what changing it costs.\n */\nconst { open, resolve, dismiss } = useModal<ArticleRow>()\n\nconst context = useAdmin()\nconst api = createBlogApi(context)\nuseBlogMessages()\n\nconst t = useTranslate('webx-blog')\n\nconst title = ref('')\nconst saving = ref(false)\nconst errors = ref<Record<string, string[]>>({})\nconst field = useTemplateRef<HTMLElement>('field')\n\nonMounted(async () => {\n await nextTick()\n field.value?.querySelector('input')?.focus()\n})\n\nasync function submit(): Promise<void> {\n if (title.value.trim() === '' || saving.value) return\n\n saving.value = true\n errors.value = {}\n\n try {\n resolve(await api.create({ title: title.value.trim() }))\n } catch (error) {\n // The address is what can be refused here, and it is refused under the title that made\n // it: the registry sees pages, rubrics and tags too, so \"taken\" can mean taken by a page.\n errors.value = (error as { body?: { errors?: Record<string, string[]> } }).body?.errors ?? {}\n } finally {\n saving.value = false\n }\n}\n</script>\n\n<template>\n <wx-dialog v-model:open=\"open\" :title=\"t('panel.new-title')\" :width=\"440\">\n <wx-form-item\n :label=\"t('panel.field-title')\"\n :error=\"errors.title?.[0] ?? errors.slug?.[0]\"\n required\n >\n <div ref=\"field\">\n <wx-input v-model=\"title\" :aria-label=\"t('panel.field-title')\" @keyup.enter=\"submit\" />\n </div>\n </wx-form-item>\n\n <template #footer>\n <wx-space size=\"sm\">\n <wx-button variant=\"outline\" @click=\"dismiss()\">{{ t('panel.cancel') }}</wx-button>\n <wx-button type=\"primary\" :loading=\"saving\" :disabled=\"title.trim() === ''\" @click=\"submit\">\n {{ t('panel.create') }}\n </wx-button>\n </wx-space>\n </template>\n </wx-dialog>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, ref, useTemplateRef, watch } from 'vue'\nimport { useRoute, useRouter, type LocationQueryRaw } from 'vue-router'\nimport {\n useAdmin,\n useErrorText,\n useTranslate,\n WxDate,\n WxListScreen,\n WxFilterChips,\n rowMenuWidth,\n WxRowMenu,\n type AppliedFilter,\n type RowAction,\n type ScreenAction,\n WxAddressNote,\n} from '@webx-ui/module-admin'\nimport {\n confirm,\n createModal,\n toast,\n useElementWidth,\n WxBadge,\n WxButton,\n WxEntityCard,\n WxFormItem,\n WxIcon,\n WxSelect,\n WxTable,\n WxText,\n WxTooltip,\n type TabItem,\n type TableColumn,\n type TableState,\n type TabValue,\n} from '@webx-ui/core'\nimport ArticleCreateDialog from './ArticleCreateDialog.vue'\nimport { createBlogApi } from './api'\nimport { useBlogMessages } from './i18n'\nimport type { ArticleRow, ArticleStatus, ArticlesPage } from './types'\n\n/**\n * The section: a page of articles with the filters of §10 over it.\n *\n * A paginator rather than a tree, which is the whole difference from `Pages`: articles all sit\n * at the same depth and there are a hundred of them, so the screen is twenty rows, a filter bar\n * and a page number.\n *\n * Below {@link CARDS} the row becomes a card, and the card is not the table's own stack of\n * labelled lines: at 375px five of those are four hundred pixels for one article. It is the\n * cover on the left and everything else beside it, which is 96px and ten articles on a screen\n * instead of two (§10). The table draws whatever columns it is given, so the switch is made\n * here — one column at that width, seven above it — and both halves agree on the number because\n * it is the same constant.\n */\nconst props = withDefaults(defineProps<{ base?: string }>(), { base: '/blog' })\n\n/** Where the row stops being a row. Below it the table draws cards and this screen draws one. */\nconst CARDS = 640\n\nconst context = useAdmin()\nconst api = createBlogApi(context)\nconst route = useRoute()\nconst router = useRouter()\nuseBlogMessages()\n\nconst t = useTranslate('webx-blog')\n/* The two words a list needs as soon as it has filters belong to the panel, not to the blog. */\nconst admin = useTranslate('webx-admin')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\nconst root = useTemplateRef<HTMLElement>('root')\nconst width = useElementWidth(root)\n\nconst page = ref<ArticlesPage | null>(null)\nconst loading = ref(true)\n\nconst create = createModal<ArticleRow, Record<string, never>>(ArticleCreateDialog)\n\nconst canManage = computed(() => context.can('blog.articles.manage'))\n\nconst title = computed(\n () =>\n context.state.manifest?.modules.find((module) => module.id === 'articles')?.title ??\n t('module.articles'),\n)\n\n/**\n * Everything the list is looking at lives in the address.\n *\n * Coming back from an article has to land on the same filter and the same page, or the section\n * costs the reader their place every time they open something.\n */\nconst view = computed<TabValue>({\n get: () => (typeof route.query.view === 'string' ? route.query.view : ''),\n set: (value) => {\n // A new view is a new list: the page it was on belongs to the view it was on.\n void router.replace({\n query: { ...route.query, view: value === '' ? undefined : String(value), page: undefined },\n })\n },\n})\n\nconst inBin = computed(() => view.value === 'trashed')\n\nconst query = computed(() => ({\n q: typeof route.query.q === 'string' ? route.query.q : '',\n rubric: number(route.query.rubric),\n tag: number(route.query.tag),\n author: number(route.query.author),\n sort: typeof route.query.sort === 'string' ? route.query.sort : null,\n page: Number(route.query.page ?? 1) || 1,\n}))\n\nfunction number(value: unknown): number | null {\n return typeof value === 'string' && value !== '' ? Number(value) || null : null\n}\n\n/** The views of the same list, as tabs over the card. The bin rides in the same row (§10). */\nconst views = computed<TabItem[]>(() => [\n { value: '', label: t('panel.view-all') },\n { value: 'published', label: t('panel.view-published') },\n { value: 'scheduled', label: t('panel.view-scheduled') },\n { value: 'draft', label: t('panel.view-draft') },\n { value: 'unpublished', label: t('panel.view-unpublished') },\n { value: 'trashed', label: t('panel.bin'), icon: 'trash' },\n])\n\nconst filters = computed(() => page.value?.filters ?? { rubrics: [], tags: [], authors: [] })\n\nconst asCards = computed(() => width.value > 0 && width.value < CARDS)\n\n/**\n * Seven columns, or one.\n *\n * Every column but the title carries a width and the table is laid out `fixed`, so the ones\n * that drop below a breakpoint give what they had to the title rather than to whichever cell\n * happened to hold the longest string. They go in the order they can be spared — who wrote it,\n * then where it belongs, then when — and what is left at the bottom is the cover, the title and\n * the state, which is what identifies an article and what an editor is looking for.\n */\nconst columns = computed<TableColumn<ArticleRow>[]>(() => {\n if (asCards.value) {\n return [{ key: 'card', label: '' }]\n }\n\n return [\n { key: 'cover', label: '', width: 68 },\n /* The one that takes what the others leave, and the only one without a number. */\n { key: 'title', label: t('panel.column-title'), minWidth: 220 },\n { key: 'rubrics', label: t('panel.column-rubrics'), width: 190, hideBelow: 1000 },\n { key: 'author', label: t('panel.column-author'), width: 160, hideBelow: 1200 },\n {\n key: 'date',\n label: inBin.value ? t('panel.column-deleted') : t('panel.column-date'),\n width: 120,\n hideBelow: 700,\n // \"today at 16:22\" is three words the table will break over two lines given half a\n // chance, and a date read down a column has to be one line to be read at all.\n cellClass: 'wx-articles__when',\n },\n { key: 'status', label: t('panel.column-status'), width: 170 },\n { key: 'actions', label: '', width: rowMenuWidth, align: 'right' },\n ]\n})\n\nconst emptyText = computed(() => {\n if (query.value.q !== '') return t('panel.empty-search')\n\n return inBin.value ? t('panel.empty-bin') : t('panel.empty')\n})\n\n/**\n * What the three dropdowns are set to, said in the reader's words.\n *\n * They live behind the funnel now, and a shut panel says nothing about itself: a list narrowed\n * by something nobody can see is a list that looks wrong. The chips are the answer, and taking\n * one off is the same `narrow()` the dropdown itself calls.\n */\nconst applied = computed<AppliedFilter[]>(() => {\n const chips: AppliedFilter[] = []\n\n const rubric = filters.value.rubrics.find((item) => item.id === query.value.rubric)\n if (rubric) {\n chips.push({\n key: 'rubric',\n label: `${t('panel.filter-rubric')}: ${rubric.title}`,\n clear: () => narrow('rubric', undefined),\n })\n }\n\n const tag = filters.value.tags.find((item) => item.id === query.value.tag)\n if (tag) {\n chips.push({\n key: 'tag',\n label: `${t('panel.filter-tag')}: ${tag.title}`,\n clear: () => narrow('tag', undefined),\n })\n }\n\n const author = filters.value.authors.find((item) => item.id === query.value.author)\n if (author) {\n chips.push({\n key: 'author',\n label: `${t('panel.filter-author')}: ${author.title}`,\n clear: () => narrow('author', undefined),\n })\n }\n\n return chips\n})\n\n/** All three at once, from inside the panel they were set in. */\nfunction clearFilters(): void {\n void router.replace({\n query: {\n ...route.query,\n rubric: undefined,\n tag: undefined,\n author: undefined,\n page: undefined,\n },\n })\n}\n\nasync function load(state?: TableState): Promise<void> {\n loading.value = true\n\n try {\n page.value = await api.articles({\n q: state?.search ?? query.value.q,\n status: inBin.value ? '' : (view.value as ArticleStatus | ''),\n trashed: inBin.value,\n rubric: query.value.rubric,\n tag: query.value.tag,\n author: query.value.author,\n sort: state?.sort ? `${state.sort.order === 'desc' ? '-' : ''}${state.sort.key}` : null,\n page: state?.page ?? query.value.page,\n per_page: state?.perPage,\n })\n } catch (error) {\n toast.danger(message(error))\n } finally {\n loading.value = false\n }\n}\n\n/**\n * The table reports everything it knows in one event, and that is the one place the address is\n * written from — so a page, a sort and a search all survive opening an article and coming back,\n * and none of them has its own half of the bookkeeping.\n */\nfunction onState(state: TableState): void {\n const wanted: LocationQueryRaw = {\n ...route.query,\n q: state.search === '' ? undefined : state.search,\n sort:\n state.sort === null\n ? undefined\n : `${state.sort.order === 'desc' ? '-' : ''}${state.sort.key}`,\n page: state.page === 1 ? undefined : String(state.page),\n }\n\n void router.replace({ query: wanted })\n void load(state)\n}\n\n/** One of the three dropdowns moved: a new list, from its first page. */\nfunction narrow(name: 'rubric' | 'tag' | 'author', value: unknown): void {\n void router.replace({\n query: {\n ...route.query,\n [name]: typeof value === 'number' ? String(value) : undefined,\n page: undefined,\n },\n })\n}\n\n/* One source per value, not one getter returning an array: `query` is rebuilt on every change of\n the address, so an array is new each time and the watcher would fire on a page turn too —\n a second request, without `per_page`, racing the one `onState` already sent. */\nwatch(\n [view, () => query.value.rubric, () => query.value.tag, () => query.value.author],\n () => void load(),\n)\n\nvoid load()\n\n/**\n * The editor of an article arrives with session C; until it does, the row opens nothing rather\n * than pushing the router at a route that is not there.\n *\n * The bin never reaches here: the table is told the rows lead nowhere, and it withholds the\n * click along with the pointer and the highlight (CLAUDE.md §4).\n */\nfunction open(article: ArticleRow): void {\n const path = `${props.base}/articles/${article.id}`\n\n if (router.resolve(path).matched.length > 0) void router.push({ path, query: { ...route.query } })\n}\n\n/** A new article: the dialog asks for a title, the list shows it as a draft, the editor opens. */\nasync function add(): Promise<void> {\n const article = await create({})\n\n if (!article) return\n\n await load()\n open(article)\n}\n\nfunction actionsFor(article: ArticleRow): RowAction[] {\n if (!canManage.value) return []\n\n if (inBin.value) {\n return [\n {\n key: 'restore',\n icon: 'refresh',\n label: t('panel.restore'),\n run: () => void restore(article),\n },\n ]\n }\n\n const actions: RowAction[] = [\n { key: 'open', icon: 'edit', label: t('panel.open'), run: () => open(article) },\n ]\n\n if (article.url && article.status !== 'draft') {\n actions.push({\n key: 'site',\n icon: 'external-link',\n label: t('panel.open-on-site'),\n href: article.url,\n })\n }\n\n if (article.path !== null) {\n actions.push({\n key: 'copy',\n icon: 'copy',\n label: t('panel.copy-address'),\n run: () => void copyAddress(article),\n })\n }\n\n actions.push({\n key: 'pin',\n icon: 'star',\n label: article.pinned ? t('panel.unpin') : t('panel.pin'),\n run: () => void setPinned(article, !article.pinned),\n })\n\n actions.push(\n article.status === 'published' ||\n article.status === 'modified' ||\n article.status === 'scheduled'\n ? {\n key: 'unpublish',\n icon: 'eye-off',\n label: t('panel.unpublish'),\n run: () => void unpublish(article),\n }\n : {\n key: 'publish',\n icon: 'upload',\n label: t('panel.publish'),\n run: () => void publish(article),\n },\n )\n\n actions.push({\n key: 'delete',\n icon: 'trash',\n label: t('panel.delete'),\n danger: true,\n run: () => void remove(article),\n })\n\n return actions\n}\n\nasync function publish(article: ArticleRow): Promise<void> {\n await run(() => api.publish(article.id), t('panel.published'))\n}\n\nasync function unpublish(article: ArticleRow): Promise<void> {\n await run(() => api.unpublish(article.id), t('panel.unpublished-done'))\n}\n\nasync function setPinned(article: ArticleRow, pinned: boolean): Promise<void> {\n await run(\n () => api.save(article.id, { values: { pinned }, revision: article.revision }),\n pinned ? t('panel.pinned-done') : t('panel.unpinned'),\n )\n}\n\nasync function remove(article: ArticleRow): Promise<void> {\n const agreed = await confirm({\n title: t('panel.delete-title', { title: article.title }),\n message: t('panel.delete-text'),\n confirmText: t('panel.delete'),\n cancelText: t('panel.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n await run(() => api.remove(article.id), t('panel.deleted'))\n}\n\nasync function restore(article: ArticleRow): Promise<void> {\n await run(() => api.restore(article.id), t('panel.restored'))\n}\n\n/** One request, one sentence about it, and the list as the server now sees it. */\nasync function run(request: () => Promise<unknown>, said: string): Promise<void> {\n try {\n await request()\n toast.success(said)\n } catch (error) {\n toast.danger(message(error))\n } finally {\n // Either way: a refused publication has left the row saying what it was, and one that\n // worked has changed an order the answer does not carry row by row.\n await load()\n }\n}\n\nasync function copyAddress(article: ArticleRow): Promise<void> {\n if (!article.url) return\n\n try {\n await navigator.clipboard.writeText(article.url)\n toast.success(t('panel.address-copied'))\n } catch {\n // A browser that refuses the clipboard without a gesture it trusts; the address is on\n // screen and can be copied from there.\n }\n}\n\n/**\n * Live and live-with-edits are the same green: the article is on the site, and what is waiting\n * beside it is said by a chip of its own rather than by a different colour. An editor scanning\n * the column is asking \"is this up\", and two greens answer it once.\n */\nfunction badge(status: ArticleStatus): 'default' | 'success' | 'warning' | 'danger' {\n if (status === 'published' || status === 'modified') return 'success'\n if (status === 'scheduled') return 'warning'\n\n return 'default'\n}\n\n/** The address under the title, and the two different nothings that can stand there instead. */\nfunction address(article: ArticleRow): string {\n if (inBin.value) return '—'\n\n return article.path === null ? t('panel.no-address') : `/${article.path}`\n}\n\n/** The date a row is about: when it was published, or when it was deleted in the bin. */\nfunction dateOf(article: ArticleRow): string | null {\n return inBin.value ? article.deleted_at : article.published_at\n}\n\n/* What the section offers. Declared, because on a phone the head folds it into the ···. */\nconst actions = computed<ScreenAction[]>(() =>\n canManage.value\n ? [{ key: 'new', label: t('panel.new'), icon: 'plus', primary: true, run: () => void add() }]\n : [],\n)\n</script>\n\n<template>\n <div ref=\"root\" class=\"wx-articles\">\n <wx-list-screen v-model:view=\"view\" :title=\"title\" :views=\"views\" :actions=\"actions\">\n <wx-table\n :data=\"page\"\n :columns=\"columns\"\n row-key=\"id\"\n searchable\n :clickable=\"!inBin\"\n :hover=\"!inBin\"\n flush\n :loading=\"loading\"\n layout=\"fixed\"\n :cards-below=\"CARDS\"\n :filters-count=\"applied.length\"\n :filters-label=\"admin('filters.title')\"\n :search-placeholder=\"t('panel.search')\"\n :empty-text=\"emptyText\"\n :aria-label=\"title\"\n @row-click=\"open\"\n @state-change=\"onState\"\n >\n <!--\n The three dropdowns live behind the funnel, and what they are set to comes back as\n chips beside it. Standing open they were three controls of chrome above the first\n article, and on a phone — where the table stacks everything in its head into a column —\n three lines of it before any data.\n -->\n <template #filters>\n <wx-form-item :label=\"t('panel.filter-rubric')\">\n <wx-select\n :model-value=\"query.rubric\"\n :options=\"filters.rubrics.map((item) => ({ value: item.id, label: item.title }))\"\n :placeholder=\"t('panel.any-rubric')\"\n clearable\n size=\"sm\"\n @update:model-value=\"(value: unknown) => narrow('rubric', value)\"\n />\n </wx-form-item>\n\n <wx-form-item :label=\"t('panel.filter-tag')\">\n <wx-select\n :model-value=\"query.tag\"\n :options=\"filters.tags.map((item) => ({ value: item.id, label: item.title }))\"\n :placeholder=\"t('panel.any-tag')\"\n clearable\n filterable\n size=\"sm\"\n @update:model-value=\"(value: unknown) => narrow('tag', value)\"\n />\n </wx-form-item>\n\n <wx-form-item :label=\"t('panel.filter-author')\">\n <wx-select\n :model-value=\"query.author\"\n :options=\"filters.authors.map((item) => ({ value: item.id, label: item.title }))\"\n :placeholder=\"t('panel.any-author')\"\n clearable\n size=\"sm\"\n @update:model-value=\"(value: unknown) => narrow('author', value)\"\n />\n </wx-form-item>\n\n <!-- Taking all of them off belongs with the fields it resets: beside the chips it\n reads as one more chip, and it is the only control there that does not take\n exactly one filter away. -->\n <wx-button v-if=\"applied.length > 0\" variant=\"text\" size=\"sm\" block @click=\"clearFilters\">\n <template #icon><wx-icon name=\"close\" /></template>\n {{ admin('filters.reset') }}\n </wx-button>\n </template>\n\n <template #applied>\n <wx-filter-chips :filters=\"applied\" />\n </template>\n\n <template #cell-cover=\"{ row }\">\n <span class=\"wx-articles__cover\" :class=\"{ 'is-empty': !row.cover }\">\n <img v-if=\"row.cover?.thumb\" :src=\"row.cover.thumb\" alt=\"\" loading=\"lazy\" />\n <wx-icon v-else name=\"image\" size=\"sm\" />\n </span>\n </template>\n\n <template #cell-title=\"{ row }\">\n <div class=\"wx-articles__name\">\n <wx-tooltip v-if=\"row.pinned\" :content=\"t('panel.pinned')\">\n <wx-icon class=\"wx-articles__pin\" name=\"star\" size=\"sm\" />\n </wx-tooltip>\n <div class=\"wx-articles__name-text\">\n <span class=\"wx-articles__title\">{{ row.title }}</span>\n <!-- An article in the bin holds no address — the registry let it go when it went\n in — and \"the address appears when it is published\" would be a promise about\n something nobody can publish without bringing it back first. -->\n <span class=\"wx-articles__address\">\n {{ address(row) }}\n <wx-address-note v-if=\"!inBin\" :locale=\"row.address_locale\" />\n </span>\n </div>\n </div>\n </template>\n\n <template #cell-rubrics=\"{ row }\">\n <span class=\"wx-articles__chips\">\n <wx-badge v-for=\"rubric in row.rubrics\" :key=\"rubric.id\" size=\"sm\" round>\n {{ rubric.title }}\n </wx-badge>\n </span>\n </template>\n\n <template #cell-author=\"{ row }\">\n <wx-text size=\"sm\" tone=\"muted\" truncate>\n {{ row.author ? row.author.name : '—' }}\n </wx-text>\n </template>\n\n <template #cell-date=\"{ row }\">\n <wx-date v-if=\"dateOf(row)\" :value=\"dateOf(row)\" compact />\n <wx-text v-else size=\"sm\" tone=\"muted\">—</wx-text>\n </template>\n\n <template #cell-status=\"{ row }\">\n <span class=\"wx-articles__state\">\n <wx-badge :type=\"badge(row.status)\" dot size=\"sm\">\n {{ t(`panel.status-${row.status}`) }}\n </wx-badge>\n <!-- What is waiting, said beside the state rather than instead of it: the article\n is on the site, and there is something that is not. -->\n <wx-badge v-if=\"row.status === 'modified'\" type=\"primary\" size=\"sm\" round>\n {{ t('panel.edits') }}\n </wx-badge>\n </span>\n </template>\n\n <!--\n The card: the cover on the left, everything else beside it. One field and no label,\n because the table's own stack of \"Title: … / Author: … / Date: …\" is five lines and\n four hundred pixels for one article (§10).\n -->\n <!--\n `WxEntityCard`, `plain` so that the box around it stays the table's own: a picture, a\n name, its address under it and a row of facts is exactly what the component is, and\n writing that by hand once per section is how five sections end up with five different\n cards.\n -->\n <template #cell-card=\"{ row }\">\n <wx-entity-card\n class=\"wx-articles__entity\"\n variant=\"plain\"\n :title=\"row.title\"\n :image=\"row.cover?.thumb ?? undefined\"\n image-size=\"56px\"\n :title-lines=\"2\"\n :subtitle=\"address(row)\"\n >\n <template #title>\n <wx-icon v-if=\"row.pinned\" class=\"wx-articles__pin\" name=\"star\" size=\"sm\" />\n {{ row.title }}\n </template>\n\n <template #meta>\n <wx-badge v-if=\"row.rubrics[0]\" size=\"sm\" round>{{ row.rubrics[0].title }}</wx-badge>\n <wx-badge :type=\"badge(row.status)\" dot size=\"sm\">\n {{ t(`panel.status-${row.status}`) }}\n </wx-badge>\n <wx-date v-if=\"dateOf(row)\" :value=\"dateOf(row)\" />\n </template>\n </wx-entity-card>\n </template>\n\n <!--\n The menu goes in the card's own top strip, beside the checkbox: that line is there\n either way as soon as rows can be picked, and a `···` inside the entity takes width\n off a title that is already one line short.\n -->\n <template #card-actions=\"{ row }\">\n <wx-row-menu :actions=\"actionsFor(row)\" :label=\"row.title\" />\n </template>\n\n <template #cell-actions=\"{ row }\">\n <wx-row-menu :actions=\"actionsFor(row)\" :label=\"row.title\" />\n </template>\n </wx-table>\n </wx-list-screen>\n </div>\n</template>\n\n<style scoped>\n/* The list screen inside it lays the screen out; this only keeps a wide table from pushing the\n column it sits in. */\n.wx-articles {\n min-width: 0;\n}\n\n/*\n * The cover is the picture's box, never the picture: `<img>` has `min-width: auto`, which is\n * its natural width, so a 1600px photograph would take the column and the ones beside it\n * (CLAUDE.md §4).\n */\n.wx-articles__cover {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 44px;\n height: 32px;\n overflow: hidden;\n /* The smallest step in the scale, and what `WxEntityCard` gives its own thumbnail: on a box\n 32px tall the next one up is 12, which is over a third of it. */\n border-radius: var(--wx-radius-xs);\n background: var(--wx-bg-subtle);\n color: var(--wx-text-muted);\n}\n\n.wx-articles__cover.is-empty {\n border: 1px dashed var(--wx-border-default);\n background: var(--wx-bg-surface);\n}\n\n.wx-articles__cover img {\n width: 100%;\n height: 100%;\n min-width: 0;\n object-fit: cover;\n}\n\n.wx-articles__name {\n display: flex;\n align-items: center;\n gap: var(--wx-space-8);\n min-width: 0;\n}\n\n.wx-articles__name-text {\n display: flex;\n flex-direction: column;\n min-width: 0;\n}\n\n.wx-articles__pin {\n flex: none;\n color: var(--wx-color-primary);\n}\n\n.wx-articles__title {\n overflow: hidden;\n font-weight: var(--wx-font-weight-medium);\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n\n.wx-articles__address {\n overflow: hidden;\n color: var(--wx-text-muted);\n font-family: var(--wx-font-family-mono);\n font-size: var(--wx-font-size-xs);\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n\n.wx-articles__chips,\n.wx-articles__state {\n display: flex;\n align-items: center;\n gap: var(--wx-space-4);\n min-width: 0;\n overflow: hidden;\n}\n\n/* `:deep()` because the cell is the table's element and the class is ours — a scoped rule\n would be looking for our attribute on somebody else's markup (CLAUDE.md §4). */\n.wx-articles :deep(.wx-articles__when) {\n white-space: nowrap;\n}\n</style>\n","import type { AdminContext } from '@webx-ui/module-admin'\nimport type { ScreenModel } from '@webx-ui/schema'\n\n/** A profile as the list and the head of its form show it (§19.5 of the blog spec). */\nexport interface AuthorRow {\n id: number\n /** The name a reader sees, in the panel's language or the nearest there is. */\n name: string\n /** The account the profile belongs to — whose articles these are. */\n user: { id: number; name: string } | null\n /** The profile asked for a page. */\n is_public: boolean\n /** The page answers: the profile asked and the site allows author pages. */\n open: boolean\n /** Where the page is in the panel's language, whether or not it answers. */\n url: string | null\n photo: string | null\n articles_count: number\n}\n\n/** One profile as its form opens it: the row, and the values of `blog.author-form`. */\nexport interface AuthorDetail {\n author: AuthorRow\n values: ScreenModel\n}\n\n/** An account a new profile can be made for. */\nexport interface AuthorAccount {\n id: number\n name: string\n articles_count: number\n}\n\nexport interface AuthorsApi {\n list(q?: string): Promise<AuthorRow[]>\n /** The accounts without a profile, the ones who wrote something first. */\n accounts(): Promise<AuthorAccount[]>\n get(id: number): Promise<AuthorDetail>\n /** A profile for an account, out of the values of its form, in one request. */\n create(userId: number, values: ScreenModel): Promise<AuthorDetail>\n /** Refused with a 422 under the name of the field. */\n save(id: number, values: ScreenModel): Promise<AuthorDetail>\n /** The profile goes; the articles stay the account's. */\n remove(id: number): Promise<void>\n}\n\n/** Everything under `/blog/authors`, below the panel's API path. */\nexport function createAuthorsApi(admin: AdminContext): AuthorsApi {\n const base = `${admin.apiPath}/blog/authors`\n const data = <T>(body: { data: T }): T => body.data\n\n return {\n list: (q = '') => {\n const suffix = q.trim() === '' ? '' : `?q=${encodeURIComponent(q.trim())}`\n\n return admin.http.get<{ data: AuthorRow[] }>(`${base}${suffix}`).then(data)\n },\n accounts: () => admin.http.get<{ data: AuthorAccount[] }>(`${base}/accounts`).then(data),\n get: (id) => admin.http.get<{ data: AuthorDetail }>(`${base}/${id}`).then(data),\n create: (userId, values) =>\n admin.http.post<{ data: AuthorDetail }>(base, { user_id: userId, values }).then(data),\n save: (id, values) =>\n admin.http.put<{ data: AuthorDetail }>(`${base}/${id}`, { values }).then(data),\n remove: (id) => admin.http.delete<void>(`${base}/${id}`).then(() => undefined),\n }\n}\n","<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, onMounted, ref } from 'vue'\nimport {\n onBeforeRouteLeave,\n onBeforeRouteUpdate,\n useRoute,\n type RouteLocationNormalized,\n} from 'vue-router'\nimport {\n confirm,\n localizedValue,\n toast,\n useLocales,\n WxActionBar,\n WxBadge,\n WxButton,\n WxCard,\n WxFormItem,\n WxSelect,\n WxSkeleton,\n type LocalizedValue,\n type SelectModelValue,\n type SelectOption,\n} from '@webx-ui/core'\nimport {\n useAdmin,\n useBodyKeys,\n useErrorText,\n useTranslate,\n WxSaveState,\n WxScreen,\n WxScreenHead,\n type ScreenAction,\n} from '@webx-ui/module-admin'\nimport type { ScreenModel } from '@webx-ui/schema'\nimport { createAuthorsApi, type AuthorAccount, type AuthorDetail, type AuthorRow } from './authors'\nimport { useBlogMessages } from './i18n'\n\n/**\n * One author's profile: the pane beside the list, and the whole screen on a phone (§19.5).\n *\n * The described screen `blog.author-form` with a head over it and one button under it, as a\n * person of the team: no draft (decision 6) — what is saved is on the site at once.\n *\n * A new profile is a form first and a record on the first save, and the one thing it asks that\n * the screen does not is whose it is: the account is chosen once, here, above the screen, from\n * the accounts that have none — whose profile this is does not change afterwards.\n *\n * The way back is drawn here and not by the pane around it: the drawer a narrow screen opens\n * this in has no close of its own on purpose (CLAUDE.md §4).\n */\ndefineOptions({ name: 'WxAuthorPane' })\n\nconst props = withDefaults(\n defineProps<{\n /** `null` — a profile not written yet. */\n id: number | null\n /** False in the drawer a narrow screen opens this in. */\n inline?: boolean\n }>(),\n { inline: true },\n)\n\nconst emit = defineEmits<{\n back: []\n /** The profile was written; the list redraws its row. */\n saved: [author: AuthorRow]\n /** The first save of a new profile: a record now, with an id to be opened by. */\n created: [author: AuthorRow]\n removed: [id: number]\n}>()\n\nconst context = useAdmin()\nconst api = createAuthorsApi(context)\nconst route = useRoute()\nconst locales = useLocales()\nuseBlogMessages()\n\nconst t = useTranslate('webx-blog')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\nconst author = ref<AuthorRow | null>(null)\nconst values = ref<ScreenModel>({})\nconst snapshot = ref('')\n\nconst accounts = ref<AuthorAccount[]>([])\nconst account = ref<number | null>(null)\n\nconst loading = ref(true)\nconst saving = ref(false)\nconst errors = ref<Record<string, string[]>>({})\n\nconst canManage = computed(() => context.can('blog.authors.manage'))\n\nconst current = computed(() => JSON.stringify(values.value))\nconst dirty = computed(\n () => snapshot.value !== '' && (current.value !== snapshot.value || account.value !== null),\n)\n\nconst state = computed<'saving' | 'unsaved' | 'saved'>(() => {\n if (saving.value) return 'saving'\n\n return dirty.value || props.id === null ? 'unsaved' : 'saved'\n})\n\n/** The name follows the field rather than the answer: an edit shows at the top as it is typed. */\nconst title = computed(() => {\n const written = values.value.title as LocalizedValue | string | null | undefined\n const fallback = props.id === null ? t('author.new') : (author.value?.name ?? '')\n\n return localizedValue(written, locales.active.value, fallback) || t('author.untitled')\n})\n\nconst accountOptions = computed<SelectOption[]>(() =>\n accounts.value.map((one) => ({\n label:\n one.articles_count > 0\n ? `${one.name} · ${t('author.articles', { count: one.articles_count })}`\n : one.name,\n value: one.id,\n })),\n)\n\nconst chosenAccount = computed<SelectModelValue>({\n get: () => account.value,\n set: (next) => {\n account.value = typeof next === 'number' ? next : null\n\n // The account's name is the first guess at the name for readers, until somebody writes one.\n const picked = accounts.value.find((one) => one.id === account.value)\n const written = values.value.title as Record<string, string> | undefined\n // The first language of the panel is the site's main one — the one the name is required in.\n const main = locales.list.value[0]?.code\n\n if (picked && main && !(written?.[main] ?? '').trim()) {\n values.value = { ...values.value, title: { ...(written ?? {}), [main]: picked.name } }\n }\n },\n})\n\nfunction take(detail: AuthorDetail): void {\n author.value = detail.author\n values.value = detail.values\n snapshot.value = JSON.stringify(detail.values)\n account.value = null\n}\n\nasync function load(): Promise<void> {\n loading.value = true\n\n try {\n if (props.id === null) {\n // What the server would answer for a profile with nothing in it yet.\n const blank: ScreenModel = {\n title: {},\n slug: {},\n role: {},\n bio: {},\n photo: null,\n socials: [],\n is_public: false,\n }\n\n values.value = blank\n snapshot.value = JSON.stringify(blank)\n accounts.value = await api.accounts()\n // The article editor's «Make a profile» names the account the profile is for.\n const wanted = Number(route.query.account)\n\n if (accounts.value.some((one) => one.id === wanted)) chosenAccount.value = wanted\n } else {\n take(await api.get(props.id))\n }\n } catch (error) {\n toast.danger(message(error))\n emit('back')\n } finally {\n loading.value = false\n }\n}\n\nasync function save(): Promise<void> {\n if (saving.value || !canManage.value) return\n\n saving.value = true\n errors.value = {}\n\n try {\n const made = props.id === null\n\n if (made && account.value === null) {\n errors.value = { user_id: [t('author.account-missing')] }\n toast.danger(t('author.save-failed'))\n\n return\n }\n\n const detail = made\n ? await api.create(account.value as number, values.value)\n : await api.save(props.id as number, values.value)\n\n take(detail)\n toast.success(t('author.saved'))\n if (made) emit('created', detail.author)\n else emit('saved', detail.author)\n } catch (error) {\n const body = (error as { body?: { errors?: Record<string, string[]> } }).body\n\n if (body?.errors) {\n errors.value = body.errors\n toast.danger(t('author.save-failed'))\n } else {\n toast.danger(message(error))\n }\n } finally {\n saving.value = false\n }\n}\n\nasync function remove(): Promise<void> {\n const record = author.value\n\n if (!record) return\n\n const agreed = await confirm({\n title: t('author.delete-title', { name: record.name }),\n message: t('author.delete-text'),\n confirmText: t('author.delete'),\n cancelText: t('author.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n try {\n await api.remove(record.id)\n toast.success(t('author.deleted'))\n // Nothing left to protect: the profile is gone.\n snapshot.value = current.value\n account.value = null\n emit('removed', record.id)\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\n/* The browser's own guard: it cannot wait for a request, so all it can do is ask. */\nfunction leaveGuard(event: BeforeUnloadEvent): void {\n if (dirty.value) event.preventDefault()\n}\n\n/* Ctrl+S saves, as it does everywhere else that has a save button. */\nfunction onKeydown(event: KeyboardEvent): void {\n if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 's') {\n event.preventDefault()\n if (dirty.value || props.id === null) void save()\n }\n}\n\n// And from the page around the form, where a click on empty space leaves the focus.\nuseBodyKeys(onKeydown)\n\nonMounted(() => {\n void load()\n window.addEventListener('beforeunload', leaveGuard)\n})\n\nonBeforeUnmount(() => {\n window.removeEventListener('beforeunload', leaveGuard)\n})\n\nasync function mayLeave(): Promise<boolean> {\n if (!dirty.value || !canManage.value) return true\n\n return await confirm({\n title: t('author.leave-title'),\n message: t('author.leave-text'),\n confirmText: t('author.leave'),\n cancelText: t('author.cancel'),\n tone: 'danger',\n })\n}\n\n/* The list and this form are one route: another author is an update of it rather than a way out. */\nonBeforeRouteUpdate(async (to: RouteLocationNormalized, from: RouteLocationNormalized) =>\n to.query.author === from.query.author ? true : await mayLeave(),\n)\n\nonBeforeRouteLeave(mayLeave)\n\n/* What leads away from the profile. On a narrow pane the head folds them into the ···. */\nconst actions = computed<ScreenAction[]>(() => {\n const list: ScreenAction[] = []\n const record = author.value\n\n if (record?.open && record.url) {\n list.push({\n key: 'open',\n label: t('author.open-page'),\n icon: 'external-link',\n run: () => void window.open(record.url as string, '_blank', 'noopener'),\n })\n }\n\n if (canManage.value && record !== null) {\n list.push({\n key: 'delete',\n label: t('author.delete'),\n icon: 'trash',\n danger: true,\n menu: true,\n run: () => void remove(),\n })\n }\n\n return list\n})\n</script>\n\n<template>\n <div class=\"wx-author\" @keydown=\"onKeydown\">\n <wx-skeleton v-if=\"loading\" class=\"wx-author__ghost\" title :rows=\"6\" />\n\n <template v-else>\n <wx-screen-head\n :level=\"3\"\n :back=\"!props.inline\"\n :back-label=\"t('author.back')\"\n :title=\"title\"\n :actions=\"actions\"\n :collapse-below=\"480\"\n @back=\"emit('back')\"\n >\n <template v-if=\"author && !author.open\" #title-after>\n <wx-badge dot>{{ t('author.page-closed') }}</wx-badge>\n </template>\n </wx-screen-head>\n\n <!-- Whose profile this is: chosen once, for a new one; the account of an existing one is shown. -->\n <wx-card v-if=\"props.id === null\">\n <wx-form-item\n :label=\"t('author.account')\"\n :help=\"accounts.length === 0 ? t('author.no-accounts') : t('author.account-help')\"\n :error=\"errors.user_id?.[0]\"\n required\n >\n <wx-select\n v-model=\"chosenAccount\"\n :options=\"accountOptions\"\n :disabled=\"!canManage || saving || accounts.length === 0\"\n filterable\n teleport\n :aria-label=\"t('author.account')\"\n />\n </wx-form-item>\n </wx-card>\n\n <p v-else-if=\"author?.user\" class=\"wx-author__account\">\n {{ t('author.account') }}: {{ author.user.name }}\n </p>\n\n <wx-screen\n v-model=\"values\"\n name=\"blog.author-form\"\n :errors=\"errors\"\n :disabled=\"!canManage || saving\"\n />\n\n <wx-action-bar v-if=\"canManage\">\n <template #state>\n <wx-save-state :state=\"state\" />\n </template>\n\n <wx-button\n type=\"primary\"\n :loading=\"saving\"\n :disabled=\"!dirty && props.id !== null\"\n @click=\"save\"\n >\n {{ t('author.save') }}\n </wx-button>\n </wx-action-bar>\n </template>\n </div>\n</template>\n\n<style scoped>\n.wx-author {\n display: flex;\n flex-direction: column;\n gap: var(--wx-gap, var(--wx-space-16));\n min-width: 0;\n padding: var(--wx-gap, var(--wx-space-16));\n container-type: inline-size;\n}\n\n.wx-author__ghost {\n max-width: 480px;\n}\n\n.wx-author__account {\n margin: 0;\n color: var(--wx-text-muted);\n font-size: var(--wx-font-size-sm);\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, ref, watch } from 'vue'\nimport { useRoute, useRouter, type LocationQueryRaw } from 'vue-router'\nimport {\n toast,\n WxAvatar,\n WxBadge,\n WxCard,\n WxEmpty,\n WxIcon,\n WxInput,\n WxListDetail,\n WxSkeleton,\n} from '@webx-ui/core'\nimport { useAdmin, useErrorText, useTranslate, WxListScreen } from '@webx-ui/module-admin'\nimport AuthorPane from './AuthorPane.vue'\nimport { createAuthorsApi, type AuthorRow } from './authors'\nimport { useBlogMessages } from './i18n'\n\n/**\n * The authors: every profile on the left, the one being written on the right (§19.5 of the blog\n * spec) — the shape of the team, without its order and its bin: the people who write one site are\n * a short list, and a profile that goes takes nothing with it but a name on a page.\n *\n * The search and the open profile live in the address, so a link to an author — the one the panel\n * on the site and the article editor make — is a link to them in their list.\n */\nwithDefaults(defineProps<{ base?: string }>(), { base: '/blog' })\n\nconst context = useAdmin()\nconst api = createAuthorsApi(context)\nconst route = useRoute()\nconst router = useRouter()\nuseBlogMessages()\n\nconst t = useTranslate('webx-blog')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\nconst rows = ref<AuthorRow[]>([])\nconst loading = ref(true)\n\nconst canManage = computed(() => context.can('blog.authors.manage'))\n\nconst title = computed(\n () =>\n context.state.manifest?.modules.find((module) => module.id === 'authors')?.title ??\n t('module.authors'),\n)\n\nconst q = computed(() => (typeof route.query.q === 'string' ? route.query.q : ''))\n\n/* What is typed, ahead of the address: the address follows it after a pause. */\nconst typed = ref(q.value)\n\n/** The open profile: a number, `'new'` for one not yet saved, or nothing. */\nconst selected = computed<number | 'new' | null>(() => {\n const raw = route.query.author\n\n if (raw === 'new') return canManage.value ? 'new' : null\n\n return typeof raw === 'string' && raw !== '' ? Number(raw) || null : null\n})\n\n/* Open is \"a profile is chosen\", so the address, the highlight and the drawer always agree. */\nconst open = computed({\n get: () => selected.value !== null,\n set: (value) => {\n if (!value) close()\n },\n})\n\nasync function load(): Promise<void> {\n loading.value = true\n\n try {\n rows.value = await api.list(q.value)\n } catch (error) {\n toast.danger(message(error))\n } finally {\n loading.value = false\n }\n}\n\nwatch(q, () => void load())\nvoid load()\n\nlet pause: ReturnType<typeof setTimeout> | undefined\n\nwatch(typed, (value) => {\n clearTimeout(pause)\n pause = setTimeout(() => {\n void router.replace({ query: { ...route.query, q: value.trim() === '' ? undefined : value } })\n }, 300)\n})\n\nonBeforeUnmount(() => clearTimeout(pause))\n\n/* `replace` and not `push`: going through the authors is not a step of history each. */\nfunction choose(id: number | 'new'): void {\n const query: LocationQueryRaw = { ...route.query, author: String(id) }\n\n // The account the article editor asked a profile for belongs to that one new profile only.\n if (id !== 'new') delete query.account\n void router.replace({ query })\n}\n\nfunction close(): void {\n const query = { ...route.query }\n\n delete query.author\n delete query.account\n void router.replace({ query })\n}\n\n/** A new profile was saved: it is a record now, and the list has it. */\nfunction created(author: AuthorRow): void {\n choose(author.id)\n void load()\n}\n\nfunction removed(id: number): void {\n if (selected.value === id) close()\n\n void load()\n}\n</script>\n\n<template>\n <wx-list-screen :title=\"title\" :card=\"false\">\n <wx-card class=\"wx-authors\" padding=\"none\">\n <wx-list-detail\n v-model:open=\"open\"\n class=\"wx-authors__panes\"\n :list-width=\"360\"\n :detail-min=\"460\"\n :detail-label=\"t('module.authors')\"\n >\n <template #list>\n <div class=\"wx-authors__bar\">\n <wx-input\n v-model=\"typed\"\n class=\"wx-authors__search\"\n size=\"sm\"\n clearable\n :placeholder=\"t('author.search')\"\n :aria-label=\"t('author.search')\"\n >\n <template #prefix><wx-icon name=\"search\" size=\"sm\" /></template>\n </wx-input>\n </div>\n\n <button\n v-if=\"canManage\"\n type=\"button\"\n class=\"wx-authors__new\"\n :class=\"{ 'is-current': selected === 'new' }\"\n @click=\"choose('new')\"\n >\n <wx-icon name=\"plus\" size=\"sm\" />\n {{ t('author.new') }}\n </button>\n\n <wx-skeleton v-if=\"loading && rows.length === 0\" class=\"wx-authors__loading\" :rows=\"4\" />\n\n <wx-empty\n v-else-if=\"rows.length === 0\"\n :title=\"q === '' ? t('author.empty') : t('author.empty-search')\"\n :description=\"q === '' ? t('author.empty-help') : undefined\"\n />\n\n <ul v-else class=\"wx-authors__list\" :class=\"{ 'is-loading': loading }\">\n <li v-for=\"item in rows\" :key=\"item.id\">\n <button\n type=\"button\"\n class=\"wx-author-row\"\n :class=\"{ 'is-current': selected === item.id }\"\n @click=\"choose(item.id)\"\n >\n <wx-avatar\n class=\"wx-author-row__photo\"\n :src=\"item.photo ?? undefined\"\n :name=\"item.name\"\n size=\"md\"\n />\n\n <span class=\"wx-author-row__body\">\n <span class=\"wx-author-row__name\">{{ item.name }}</span>\n\n <span class=\"wx-author-row__meta\">\n <span v-if=\"item.user\">{{ item.user.name }}</span>\n <span>{{ t('author.articles', { count: item.articles_count }) }}</span>\n </span>\n\n <span class=\"wx-author-row__facts\">\n <wx-badge v-if=\"item.open\" dot size=\"sm\" type=\"success\">\n {{ t('author.page-open') }}\n </wx-badge>\n <wx-badge v-else dot size=\"sm\">{{ t('author.page-closed') }}</wx-badge>\n </span>\n </span>\n </button>\n </li>\n </ul>\n </template>\n\n <template #detail=\"{ inline, back }\">\n <author-pane\n v-if=\"selected !== null\"\n :id=\"selected === 'new' ? null : selected\"\n :key=\"String(selected)\"\n :inline=\"inline\"\n @back=\"back\"\n @saved=\"load\"\n @created=\"created\"\n @removed=\"removed\"\n />\n </template>\n\n <template #empty>\n <wx-empty\n :title=\"t('author.choose')\"\n :description=\"canManage ? t('author.choose-help') : undefined\"\n />\n </template>\n </wx-list-detail>\n </wx-card>\n </wx-list-screen>\n</template>\n\n<style>\n/*\n * What scrolls here is the page, as on every other list of the panel: `clip`, not `hidden`, which\n * would make the card a scroll container and pin everything sticky inside it (CLAUDE.md §4).\n */\n.wx-authors > .wx-card__body {\n display: flex;\n flex-direction: column;\n overflow: clip;\n border-radius: inherit;\n}\n</style>\n\n<style scoped>\n.wx-authors__bar {\n display: flex;\n gap: var(--wx-space-8);\n padding: var(--wx-space-12) var(--wx-space-12) var(--wx-space-8);\n}\n\n.wx-authors__search {\n flex: 1 1 180px;\n min-width: 0;\n}\n\n.wx-authors__new {\n display: flex;\n align-items: center;\n gap: var(--wx-space-8);\n width: calc(100% - 2 * var(--wx-space-8));\n margin: 0 var(--wx-space-8) var(--wx-space-4);\n padding: var(--wx-space-8);\n border: 1px dashed var(--wx-border-default);\n border-radius: var(--wx-radius-sm);\n background: none;\n font: inherit;\n color: var(--wx-color-primary);\n text-align: start;\n cursor: pointer;\n}\n\n.wx-authors__new:hover,\n.wx-authors__new.is-current {\n background: var(--wx-bg-subtle);\n}\n\n.wx-authors__loading {\n padding: var(--wx-space-12);\n}\n\n.wx-authors__list {\n margin: 0;\n padding: 0 var(--wx-space-8) var(--wx-space-8);\n list-style: none;\n}\n\n.wx-authors__list.is-loading {\n opacity: 0.6;\n}\n\n.wx-author-row {\n display: flex;\n align-items: flex-start;\n gap: var(--wx-space-10);\n width: 100%;\n min-width: 0;\n padding: var(--wx-space-8) var(--wx-space-4);\n border: 0;\n border-radius: var(--wx-radius-sm);\n background: none;\n font: inherit;\n color: inherit;\n text-align: start;\n cursor: pointer;\n}\n\n.wx-author-row:hover,\n.wx-author-row.is-current {\n background: var(--wx-bg-subtle);\n}\n\n.wx-author-row__photo {\n flex: none;\n}\n\n.wx-author-row__body {\n display: flex;\n flex: 1 1 auto;\n flex-direction: column;\n gap: var(--wx-space-2);\n min-width: 0;\n}\n\n.wx-author-row__name {\n min-width: 0;\n overflow: hidden;\n font-weight: var(--wx-font-weight-medium);\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.wx-author-row.is-current .wx-author-row__name {\n color: var(--wx-color-primary);\n}\n\n.wx-author-row__meta {\n display: flex;\n flex-wrap: wrap;\n gap: var(--wx-space-2) var(--wx-space-8);\n color: var(--wx-text-muted);\n font-size: var(--wx-font-size-sm);\n}\n\n.wx-author-row__facts {\n display: flex;\n margin-top: var(--wx-space-2);\n}\n</style>\n","<script setup lang=\"ts\">\nimport { nextTick, onMounted, ref, useTemplateRef } from 'vue'\nimport { useAdmin, useTranslate } from '@webx-ui/module-admin'\nimport { useModal, WxButton, WxDialog, WxFormItem, WxInput, WxSpace } from '@webx-ui/core'\nimport { createBlogApi } from './api'\nimport { useBlogMessages } from './i18n'\nimport type { TagRow } from './types'\n\n/**\n * A new tag, spelled here rather than while writing.\n *\n * The address is a field of its own, unlike on the dialog that starts an article: a tag is one\n * word, the address is the same word transliterated, and the one time somebody wants a different\n * one is the time they are making the tag on purpose — which is this screen and not the article\n * form (§2.8).\n */\nconst { open, resolve, dismiss } = useModal<TagRow>()\n\nconst context = useAdmin()\nconst api = createBlogApi(context)\nuseBlogMessages()\n\nconst t = useTranslate('webx-blog')\n\nconst title = ref('')\nconst slug = ref('')\nconst saving = ref(false)\nconst errors = ref<Record<string, string[]>>({})\nconst field = useTemplateRef<HTMLElement>('field')\n\nonMounted(async () => {\n await nextTick()\n field.value?.querySelector('input')?.focus()\n})\n\nasync function submit(): Promise<void> {\n if (title.value.trim() === '' || saving.value) return\n\n saving.value = true\n errors.value = {}\n\n try {\n resolve(\n await api.createTag({\n title: title.value.trim(),\n ...(slug.value.trim() === '' ? {} : { slug: slug.value.trim() }),\n }),\n )\n } catch (error) {\n errors.value = (error as { body?: { errors?: Record<string, string[]> } }).body?.errors ?? {}\n } finally {\n saving.value = false\n }\n}\n</script>\n\n<template>\n <wx-dialog v-model:open=\"open\" :title=\"t('tag.new-title')\" :width=\"440\">\n <wx-form-item :label=\"t('tag.field-title')\" :error=\"errors.title?.[0]\" required>\n <div ref=\"field\">\n <wx-input v-model=\"title\" :aria-label=\"t('tag.field-title')\" @keyup.enter=\"submit\" />\n </div>\n </wx-form-item>\n\n <wx-form-item\n :label=\"t('tag.field-slug')\"\n :error=\"errors.slug?.[0]\"\n :help=\"t('tag.field-slug-help')\"\n >\n <wx-input v-model=\"slug\" :aria-label=\"t('tag.field-slug')\" @keyup.enter=\"submit\" />\n </wx-form-item>\n\n <template #footer>\n <wx-space size=\"sm\">\n <wx-button variant=\"outline\" @click=\"dismiss()\">{{ t('tag.cancel') }}</wx-button>\n <wx-button type=\"primary\" :loading=\"saving\" :disabled=\"title.trim() === ''\" @click=\"submit\">\n {{ t('tag.create') }}\n </wx-button>\n </wx-space>\n </template>\n </wx-dialog>\n</template>\n","<script setup lang=\"ts\">\nimport { nextTick, onMounted, ref, useId, useTemplateRef } from 'vue'\nimport { useTranslate } from '@webx-ui/module-admin'\nimport { useModal, WxButton, WxDialog, WxFormItem, WxInput, WxSpace } from '@webx-ui/core'\nimport { useBlogMessages } from './i18n'\n\n/**\n * Renaming a tag: one field, one button, and nothing changes until it is pressed.\n *\n * It used to be done in the cell — the name turned into a box when it was clicked. Two things\n * were wrong with that. A name that is quietly an `<input>` reads as a name: nothing says it can\n * be typed in, and a stray click on a row is a rename nobody asked for. And the box saved itself\n * on `blur`, which never fired: `blur` does not bubble, so the listener on the field's wrapper\n * heard nothing and clicking away simply lost what had been typed.\n *\n * The address deliberately does not follow the word. A tag respelled three times before lunch\n * would otherwise leave three aliases behind a decision nobody made; the address has a menu item\n * of its own.\n */\nconst props = defineProps<{ name: string }>()\n\nconst { open, resolve, dismiss } = useModal<string>()\n\nuseBlogMessages()\n\nconst t = useTranslate('webx-blog')\n/* 'Save' is the panel's word, translated once for every module that has a form. */\nconst panel = useTranslate('webx-admin')\n\n/* The footer button lives outside the form, so it says which form it submits. */\nconst formId = useId()\n\nconst draft = ref(props.name)\nconst field = useTemplateRef<HTMLFormElement>('field')\n\nonMounted(async () => {\n await nextTick()\n\n const input = field.value?.querySelector('input')\n\n // Selected rather than merely focused: renaming is usually replacing, not appending.\n input?.focus()\n input?.select()\n})\n\nfunction submit(): void {\n const next = draft.value.trim()\n\n // An empty name is not a rename, it is a tag with nothing to call it by.\n if (next === '' || next === props.name) {\n dismiss()\n\n return\n }\n\n resolve(next)\n}\n</script>\n\n<template>\n <wx-dialog v-model:open=\"open\" :title=\"t('tag.rename')\" :width=\"420\">\n <!-- A real form, so that Enter does what the button does. A listener on the field is not\n the same thing: a form with one text field submits on Enter by itself, which is what a\n one-field dialog is for. -->\n <form :id=\"formId\" ref=\"field\" @submit.prevent=\"submit\">\n <wx-form-item :label=\"t('tag.field-title')\" required>\n <!-- The key as well as the form. Implicit submission is a browser behaviour and this\n field lives inside a dialog that takes the keyboard over; measured here, a real\n Enter reached the input, was not prevented by anybody, and submitted nothing. -->\n <wx-input\n v-model=\"draft\"\n :aria-label=\"t('tag.field-title')\"\n @keydown.enter.prevent=\"submit\"\n />\n </wx-form-item>\n </form>\n\n <template #footer>\n <wx-space size=\"sm\">\n <wx-button variant=\"outline\" @click=\"dismiss()\">{{ t('tag.cancel') }}</wx-button>\n <wx-button\n type=\"primary\"\n native-type=\"submit\"\n :form=\"formId\"\n :disabled=\"draft.trim() === ''\"\n >\n {{ panel('editor.save') }}\n </wx-button>\n </wx-space>\n </template>\n </wx-dialog>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\nimport { useAdmin, useErrorText, useTranslate } from '@webx-ui/module-admin'\nimport {\n useModal,\n WxAlert,\n WxBadge,\n WxButton,\n WxCheckbox,\n WxDialog,\n WxFormItem,\n WxIcon,\n WxSelect,\n WxSpace,\n WxText,\n} from '@webx-ui/core'\nimport { createBlogApi } from './api'\nimport { useBlogMessages } from './i18n'\nimport type { TagMerged, TagRow } from './types'\n\n/**\n * Several tags into one (§6).\n *\n * The dialog asks the one question the selection cannot answer — which of them stays — and says\n * the two things about the operation that an editor has to know before pressing it: how many\n * articles change hands, and that nothing here can be undone. The pivot rows that go are gone,\n * and putting a tag back would not put back which articles carried it.\n *\n * The checkbox is the other half. The aliases of `webx-ui/routing` die with the tag — they are\n * rows keyed to the entity — so an address that is to survive the merge has to survive as\n * something that is not tied to it, which is a redirect. That is a permanent fact about the\n * site, and a tag made by mistake this morning does not deserve one, so it is asked rather than\n * done quietly.\n */\nconst props = defineProps<{ tags: TagRow[] }>()\n\nconst { open, resolve, dismiss } = useModal<TagMerged>()\n\nconst context = useAdmin()\nconst api = createBlogApi(context)\nuseBlogMessages()\n\nconst t = useTranslate('webx-blog')\nconst message = useErrorText()\n\n/* The most used one by default: merging the big tag into the typo is the mistake this dialog\n exists to make hard, and the default is where that is decided. */\nconst keep = ref<number>(\n [...props.tags].sort((one, other) => other.articles_count - one.articles_count)[0]?.id ?? 0,\n)\n\nconst working = ref(false)\nconst redirect = ref(true)\nconst error = ref<string | null>(null)\n\nconst options = computed(() =>\n props.tags.map((tag) => ({ value: tag.id, label: `${tag.title} · ${tag.articles_count}` })),\n)\n\nconst survivor = computed(() => props.tags.find((tag) => tag.id === keep.value) ?? null)\n\nconst going = computed(() => props.tags.filter((tag) => tag.id !== keep.value))\n\n/**\n * How many articles the merge touches, at worst.\n *\n * At worst because an article carrying two of the tags counts once after the merge and twice\n * here — the pivot's unique index is what makes it one — and a number that cannot be wrong in\n * the other direction is the honest one to show before pressing anything.\n */\nconst affected = computed(() => going.value.reduce((sum, tag) => sum + tag.articles_count, 0))\n\nconst addresses = computed(() => going.value.map((tag) => tag.path).filter(Boolean) as string[])\n\nasync function submit(): Promise<void> {\n if (survivor.value === null || going.value.length === 0 || working.value) return\n\n working.value = true\n error.value = null\n\n try {\n resolve(\n await api.mergeTags(\n going.value.map((tag) => tag.id),\n keep.value,\n redirect.value,\n ),\n )\n } catch (failure) {\n error.value = message(failure)\n } finally {\n working.value = false\n }\n}\n</script>\n\n<template>\n <wx-dialog v-model:open=\"open\" :title=\"t('tag.merge-title')\" :width=\"520\">\n <div class=\"wx-tag-merge\">\n <!-- What is going where, before anything is asked: the chips are the selection as the\n screen had it, and the one on the right is the word that survives. -->\n <div class=\"wx-tag-merge__chips\">\n <wx-badge v-for=\"tag in going\" :key=\"tag.id\" size=\"sm\" round>\n {{ tag.title }} · {{ tag.articles_count }}\n </wx-badge>\n <wx-icon name=\"arrow-right\" size=\"sm\" />\n <wx-badge v-if=\"survivor\" type=\"primary\" size=\"sm\" round>\n {{ survivor.title }} · {{ survivor.articles_count }}\n </wx-badge>\n </div>\n\n <wx-form-item :label=\"t('tag.merge-keep')\">\n <wx-select v-model=\"keep\" :options=\"options\" :aria-label=\"t('tag.merge-keep')\" />\n </wx-form-item>\n\n <div class=\"wx-tag-merge__redirect\">\n <wx-checkbox v-model=\"redirect\" :label=\"t('tag.merge-redirect')\" />\n <wx-text size=\"sm\" tone=\"muted\">\n {{\n addresses.length === 0\n ? t('tag.merge-redirect-none')\n : t('tag.merge-redirect-help', { addresses: addresses.join(', ') })\n }}\n </wx-text>\n </div>\n\n <wx-alert\n type=\"warning\"\n variant=\"soft\"\n :description=\"\n t('tag.merge-warning', {\n count: affected,\n tag: survivor?.title ?? '',\n merged: going.length,\n })\n \"\n />\n\n <wx-alert v-if=\"error\" type=\"danger\" variant=\"soft\" :description=\"error\" />\n </div>\n\n <template #footer>\n <wx-space size=\"sm\">\n <wx-button variant=\"outline\" @click=\"dismiss()\">{{ t('tag.cancel') }}</wx-button>\n <wx-button type=\"primary\" :loading=\"working\" :disabled=\"going.length === 0\" @click=\"submit\">\n {{ t('tag.merge-confirm') }}\n </wx-button>\n </wx-space>\n </template>\n </wx-dialog>\n</template>\n\n<style scoped>\n.wx-tag-merge {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-12);\n}\n\n.wx-tag-merge__chips {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--wx-space-6);\n min-width: 0;\n}\n\n.wx-tag-merge__redirect {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-2);\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\nimport { useRoute, useRouter, type LocationQueryRaw } from 'vue-router'\nimport {\n useAdmin,\n useErrorText,\n useTranslate,\n WxListScreen,\n rowMenuWidth,\n WxRowMenu,\n type RowAction,\n type ScreenAction,\n} from '@webx-ui/module-admin'\nimport {\n confirm,\n createModal,\n toast,\n WxActionBar,\n WxButton,\n WxIcon,\n WxTable,\n WxText,\n WxTooltip,\n type RowKey,\n type TabItem,\n type TableColumn,\n type TableSort,\n type TableState,\n type TabValue,\n} from '@webx-ui/core'\nimport TagCreateDialog from './TagCreateDialog.vue'\nimport TagRenameDialog from './TagRenameDialog.vue'\nimport TagMergeDialog from './TagMergeDialog.vue'\nimport { createBlogApi } from './api'\nimport { useBlogMessages } from './i18n'\nimport type { TagIndexing, TagMerged, TagQuery, TagRow, TagsPage } from './types'\n\n/**\n * Tags, and the raking over of them (§10).\n *\n * Entered from the article form by the hundred, which is what the whole screen is shaped by:\n * within six months the table holds \"belts\", \"belt\" and \"drive belts\", and all three are right.\n * So renaming is done in place — a tag is one word, and a form to change one word is a form\n * nobody opens — and everything else is done to a pile of rows at once.\n *\n * It stays a table at every width, with columns dropping out by `hideBelow` until the word and\n * the `···` are left. Cards would be 250px each for a word and a number (CLAUDE.md §4), and\n * this is a screen somebody scrolls through sixty rows of.\n *\n * \"Not indexed\" is the one filter that is not a `where`: it is the answer of the same rule the\n * rendered page follows, rules in the SEO section included, so the column, the tab's count and\n * the filter are one answer worked out on the server (§12).\n */\nconst props = withDefaults(defineProps<{ base?: string }>(), { base: '/blog' })\n\nconst context = useAdmin()\nconst api = createBlogApi(context)\nconst route = useRoute()\nconst router = useRouter()\nuseBlogMessages()\n\nconst t = useTranslate('webx-blog')\nconst panel = useTranslate('webx-admin')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\nconst page = ref<TagsPage | null>(null)\nconst loading = ref(true)\nconst selected = ref<RowKey[]>([])\nconst working = ref(false)\n\nconst rename = createModal<string, { name: string }>(TagRenameDialog)\n\nconst create = createModal<TagRow, Record<string, never>>(TagCreateDialog)\nconst merge = createModal<TagMerged, { tags: TagRow[] }>(TagMergeDialog)\n\nconst canManage = computed(() => context.can('blog.taxonomy.manage'))\n\nconst title = computed(\n () =>\n context.state.manifest?.modules.find((module) => module.id === 'tags')?.title ??\n t('module.tags'),\n)\n\n/** Everything the list is looking at lives in the address, so a link to it is a link. */\nconst view = computed<TabValue>({\n get: () => (typeof route.query.view === 'string' ? route.query.view : ''),\n set: (value) => {\n void router.replace({\n query: { ...route.query, view: value === '' ? undefined : String(value), page: undefined },\n })\n },\n})\n\nconst counts = computed(() => page.value?.filters ?? { total: 0, empty: 0, noindex: 0 })\n\nconst views = computed<TabItem[]>(() => [\n { value: '', label: t('tag.view-all'), badge: counts.value.total },\n { value: 'empty', label: t('tag.view-empty'), badge: counts.value.empty },\n { value: 'noindex', label: t('tag.view-noindex'), badge: counts.value.noindex },\n])\n\nconst rows = computed(() => page.value?.data ?? [])\n\nconst chosen = computed(() => rows.value.filter((tag) => selected.value.includes(tag.id as RowKey)))\n\n/**\n * Five columns, and never cards.\n *\n * The word and the menu stay at every width; the address goes first, because it is the one\n * thing a tag's name already tells you, then the state of the index. What is left on a phone is\n * a word, a number and a `···`, which is the screen this is (CLAUDE.md §4).\n */\nconst columns = computed<TableColumn<TagRow>[]>(() => [\n { key: 'title', label: t('tag.column-title'), minWidth: 150, sortable: true },\n // The number is what the other columns and the narrowest the title may be add up to without\n // this one: below it the address is what does not fit, and a fixed table does not shrink —\n // it scrolls sideways and takes the ··· with it (CLAUDE.md §4). Measured, not guessed: at\n // 375px the row is the word, the number and the menu, and nothing hangs off the edge.\n { key: 'path', label: t('tag.column-address'), width: 180, hideBelow: 760 },\n {\n key: 'articles_count',\n label: t('tag.column-articles'),\n width: 80,\n align: 'right',\n sortable: true,\n },\n // Wide enough for the longest of the three states on one line, in every language: measured\n // at 180px of text, and a cell keeps 32 of its own. Narrower, the ruled row wrapped and\n // stood eighteen pixels taller than the ones around it.\n { key: 'indexing', label: t('tag.column-indexing'), width: 230, hideBelow: 560 },\n { key: 'actions', label: '', width: rowMenuWidth, align: 'right' },\n])\n\nfunction query(state?: TableState): TagQuery {\n return {\n q: state?.search ?? (typeof route.query.q === 'string' ? route.query.q : ''),\n empty: view.value === 'empty',\n noindex: view.value === 'noindex',\n // Only when it is not the default: most used first is what the list is, and a query\n // string that repeats the default is a link that outlives a change of mind about it.\n sort: sortOf(state),\n page: state?.page ?? (Number(route.query.page ?? 1) || 1),\n per_page: state?.perPage,\n }\n}\n\nasync function load(state?: TableState): Promise<void> {\n loading.value = true\n\n try {\n page.value = await api.tagsPage(query(state))\n } catch (error) {\n toast.danger(message(error))\n } finally {\n loading.value = false\n }\n}\n\n/**\n * The order the table asks for, in the words the server knows.\n *\n * Two keys and a direction: the word from A, or the used ones first — and a minus turns either\n * of them around. The default is most used first, so the query string leaves it out: a link that\n * repeats the default outlives a change of mind about it.\n */\nfunction sortOf(state?: TableState): TagQuery['sort'] {\n const from = state?.sort ?? sortFromRoute()\n\n if (!from) return undefined\n\n const key = from.key === 'title' ? 'name' : 'articles'\n const sort = `${from.order === 'desc' ? '-' : ''}${key}` as NonNullable<TagQuery['sort']>\n\n return sort === 'articles' ? undefined : sort\n}\n\n/** What the address says the order is, for a list arrived at by a link. */\nfunction sortFromRoute(): TableSort | null {\n const value = typeof route.query.sort === 'string' ? route.query.sort : ''\n\n if (value === '') return null\n\n const desc = value.startsWith('-')\n const key = (desc ? value.slice(1) : value) === 'name' ? 'title' : 'articles_count'\n\n return { key, order: desc ? 'desc' : 'asc' }\n}\n\n/** The table reports everything it knows in one event, and the address is written from it. */\nfunction onState(state: TableState): void {\n const wanted: LocationQueryRaw = {\n ...route.query,\n q: state.search === '' ? undefined : state.search,\n sort: sortOf(state),\n page: state.page === 1 ? undefined : String(state.page),\n }\n\n void router.replace({ query: wanted })\n void load(state)\n}\n\nwatch(view, () => {\n // A new view is a new list, and a selection made in the old one is a selection of rows\n // nobody can see any more.\n selected.value = []\n void load()\n})\n\nvoid load()\n\nasync function add(): Promise<void> {\n if (await create({})) await load()\n}\n\n/**\n * Renaming, as an act rather than a side effect.\n *\n * A dialog with one field, because a name that is quietly an `<input>` reads as a name: nothing\n * says it can be typed in, and a stray click on a row is a rename nobody asked for. The address\n * does not move with the word — a tag respelled three times before lunch would leave three\n * aliases behind a decision nobody made — so it has a menu item of its own.\n */\nasync function startRename(tag: TagRow): Promise<void> {\n const title = await rename({ name: tag.title })\n\n if (title) await patch(tag, { title }, t('tag.renamed'))\n}\n\nasync function setIndexing(tag: TagRow, noindex: boolean): Promise<void> {\n await patch(tag, { noindex }, noindex ? t('tag.kept-out') : t('tag.indexed'))\n}\n\n/**\n * One tag changed, and the row it changed in.\n *\n * The answer replaces the row rather than reloading the page: the list is sixty words long and\n * somebody is halfway down it, and a reload after every rename would take their place away.\n */\nasync function patch(\n tag: TagRow,\n input: { title?: string; noindex?: boolean },\n said: string,\n): Promise<void> {\n try {\n const saved = await api.saveTag(tag.id, input)\n\n if (page.value) {\n page.value = {\n ...page.value,\n data: page.value.data.map((row) => (row.id === saved.id ? saved : row)),\n }\n }\n\n toast.success(said)\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\nasync function remove(tag: TagRow): Promise<void> {\n const agreed = await confirm({\n title: t('tag.delete-title', { title: tag.title }),\n message:\n tag.articles_count > 0\n ? t('tag.delete-text-used', { count: tag.articles_count })\n : t('tag.delete-text'),\n confirmText: t('tag.delete'),\n cancelText: t('tag.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n try {\n await api.removeTag(tag.id)\n toast.success(t('tag.deleted'))\n selected.value = selected.value.filter((key) => key !== tag.id)\n await load()\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\n/**\n * What can be done to the pile, behind the same `···` a single row has.\n *\n * Three buttons in the bar were three lines of chrome on a phone and the same three words every\n * row already offers; merging stays a button because it is the one thing here that cannot be\n * done to a row on its own.\n */\nconst massActions = computed<RowAction[]>(() => [\n {\n key: 'index',\n icon: 'eye',\n label: t('tag.index'),\n disabled: working.value,\n run: () => void massSelected('index'),\n },\n {\n key: 'noindex',\n icon: 'eye-off',\n label: t('tag.noindex'),\n disabled: working.value,\n run: () => void massSelected('noindex'),\n },\n {\n key: 'delete',\n icon: 'trash',\n label: t('tag.delete'),\n danger: true,\n disabled: working.value,\n run: () => void massSelected('delete'),\n },\n])\n\nfunction actionsFor(tag: TagRow): RowAction[] {\n if (!canManage.value) return []\n\n const actions: RowAction[] = [\n { key: 'rename', icon: 'edit', label: t('tag.rename'), run: () => void startRename(tag) },\n ]\n\n if (tag.url) {\n actions.push({\n key: 'site',\n icon: 'external-link',\n label: t('tag.open-on-site'),\n href: tag.url,\n })\n }\n\n actions.push({\n key: 'articles',\n icon: 'list',\n label: t('tag.show-articles'),\n run: () => void router.push({ path: `${props.base}/articles`, query: { tag: String(tag.id) } }),\n })\n\n actions.push(\n tag.noindex\n ? {\n key: 'index',\n icon: 'eye',\n label: t('tag.index'),\n run: () => void setIndexing(tag, false),\n }\n : {\n key: 'noindex',\n icon: 'eye-off',\n label: t('tag.noindex'),\n run: () => void setIndexing(tag, true),\n },\n )\n\n actions.push({\n key: 'delete',\n icon: 'trash',\n label: t('tag.delete'),\n danger: true,\n run: () => void remove(tag),\n })\n\n return actions\n}\n\n/** The colour the state of the index is said in: green for in, quiet for out (§12). */\nfunction tone(indexing: TagIndexing): 'success' | 'muted' {\n return indexing === 'noindex' ? 'muted' : 'success'\n}\n\nasync function mergeSelected(): Promise<void> {\n if (chosen.value.length < 2) return\n\n const merged = await merge({ tags: chosen.value })\n\n if (!merged) return\n\n toast.success(t('tag.merged', { tag: merged.tag.title, count: merged.articles_count }))\n selected.value = []\n await load()\n}\n\nasync function massSelected(action: 'index' | 'noindex' | 'delete'): Promise<void> {\n const ids = chosen.value.map((tag) => tag.id)\n\n if (ids.length === 0 || working.value) return\n\n if (action === 'delete') {\n const held = chosen.value.reduce((sum, tag) => sum + tag.articles_count, 0)\n\n const agreed = await confirm({\n title: t('tag.delete-many-title', { count: ids.length }),\n message:\n held > 0 ? t('tag.delete-many-text-used', { count: held }) : t('tag.delete-many-text'),\n confirmText: t('tag.delete'),\n cancelText: t('tag.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n }\n\n working.value = true\n\n try {\n const affected = await api.massTags(ids, action)\n\n toast.success(\n action === 'delete'\n ? t('tag.deleted-many', { count: affected })\n : action === 'index'\n ? t('tag.indexed-many', { count: affected })\n : t('tag.kept-out-many', { count: affected }),\n )\n selected.value = []\n await load()\n } catch (error) {\n toast.danger(message(error))\n } finally {\n working.value = false\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.value\n ? [{ key: 'new', label: t('tag.new'), icon: 'plus', primary: true, run: () => void add() }]\n : [],\n)\n</script>\n\n<template>\n <div class=\"wx-tags\">\n <wx-list-screen v-model:view=\"view\" :title=\"title\" :views=\"views\" :actions=\"actions\">\n <wx-table\n v-model:selected=\"selected\"\n :data=\"page\"\n :columns=\"columns\"\n row-key=\"id\"\n searchable\n hover\n flush\n layout=\"fixed\"\n :loading=\"loading\"\n :selectable=\"canManage\"\n :select-row-label=\"panel('filters.select-row')\"\n :select-all-label=\"panel('filters.select-all')\"\n :cards-below=\"0\"\n :search-placeholder=\"t('tag.search')\"\n :empty-text=\"rows.length === 0 && !loading ? t('tag.empty') : undefined\"\n :aria-label=\"title\"\n @state-change=\"onState\"\n >\n <template #cell-title=\"{ row }\">\n <wx-text truncate>{{ row.title }}</wx-text>\n </template>\n\n <template #cell-path=\"{ row }\">\n <wx-text size=\"sm\" tone=\"muted\" mono truncate>\n {{ row.path ?? t('tag.no-address') }}\n </wx-text>\n </template>\n\n <template #cell-articles_count=\"{ row }\">\n <wx-text :tone=\"row.articles_count === 0 ? 'muted' : 'default'\">\n {{ row.articles_count }}\n </wx-text>\n </template>\n\n <!--\n Three states and not two. A rule in the SEO section opens the page whatever the flag\n says, and an editor who wrote that rule must not be left looking at a row that says\n `noindex` and disagreeing with it (§12).\n -->\n <template #cell-indexing=\"{ row }\">\n <wx-tooltip v-if=\"row.indexing === 'rule'\" :content=\"t('tag.indexing-rule-help')\">\n <wx-text size=\"sm\" :tone=\"tone(row.indexing)\">{{ t('tag.indexing-rule') }}</wx-text>\n </wx-tooltip>\n <wx-text v-else size=\"sm\" :tone=\"tone(row.indexing)\">\n {{ t(`tag.indexing-${row.indexing}`) }}\n </wx-text>\n </template>\n\n <template #cell-actions=\"{ row }\">\n <wx-row-menu :actions=\"actionsFor(row)\" :label=\"row.title\" />\n </template>\n </wx-table>\n </wx-list-screen>\n\n <!--\n The selection bar, at the bottom of the screen and only while something is selected.\n `WxActionBar` rather than a floating pill of its own: the panel already has one shape for\n \"what can be done from here\", and a second one would be a second one to keep in step.\n -->\n <wx-action-bar v-if=\"chosen.length > 0\" class=\"wx-tags__bar\" sticky>\n <template #state>\n <wx-text weight=\"medium\">{{ t('tag.selected', { count: chosen.length }) }}</wx-text>\n </template>\n\n <!--\n One button and a menu. Merging is what this bar exists for — it is the only thing here\n that cannot be done to a row on its own — and the other three are the row's own menu\n applied to several rows at once, which is where a reader already looks for them.\n\n The icon is a slot and not a prop: `WxButton` takes `#icon`, and `icon=\"…\"` lands on the\n element as an attribute and draws nothing at all (CLAUDE.md §4).\n -->\n <wx-button type=\"primary\" :disabled=\"chosen.length < 2 || working\" @click=\"mergeSelected\">\n <template #icon><wx-icon name=\"link\" /></template>\n {{ t('tag.merge') }}\n </wx-button>\n\n <!--\n No way out of the selection here. Untick the rows, or untick them all from the box in\n the heading — a × beside a red \"Delete\" is a button whose whole job is to undo something\n harmless, standing where the dangerous one is, and it read as a way to close the bar.\n\n `lg` because the menu stands beside a button rather than at the end of a row: a row menu\n is 30px, a button is 42, and the pair read as a control and a leftover. In a bar the two\n are the same two things and have to be the same size.\n -->\n <wx-row-menu\n size=\"lg\"\n :actions=\"massActions\"\n :label=\"t('tag.selected', { count: chosen.length })\"\n />\n </wx-action-bar>\n </div>\n</template>\n\n<style scoped>\n.wx-tags {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-12);\n min-width: 0;\n}\n\n.wx-tags__rename {\n display: block;\n min-width: 0;\n}\n\n.wx-tags__sort {\n display: flex;\n gap: var(--wx-space-4);\n min-width: 0;\n}\n\n/*\n * The name is the rename: a tag is one word, and a form to change one word is a form nobody\n * opens. It reads as text until it is hovered, because a table of sixty underlined words is a\n * table of sixty links to nowhere.\n */\n.wx-tags__name {\n max-width: 100%;\n overflow: hidden;\n padding: 0;\n border: 0;\n background: none;\n color: inherit;\n font: inherit;\n text-align: start;\n text-overflow: ellipsis;\n white-space: nowrap;\n cursor: text;\n}\n\n.wx-tags__name:hover {\n text-decoration: underline;\n text-decoration-style: dotted;\n}\n\n/*\n * One line on a phone, not two.\n *\n * The bar keeps its state box at 220px so that a sentence about a draft — \"Saved · goes out on\n * 25 September\" — has room to be read before the buttons take a line of their own. What stands\n * here is two words and a number, and asking for 220 of them pushed \"Merge\" and the ··· onto a\n * second line at every phone width. `:deep()` because the box is the bar's element and the rule\n * is ours. Measured at 375: the words 89px, the button and the ··· 203, and 41px of air left\n * between them.\n */\n.wx-tags__bar :deep(.wx-action-bar__state) {\n flex: 0 1 auto;\n}\n</style>\n","import { categoryRoutes, type AdminModule, type CategoriesOptions } from '@webx-ui/module-admin'\nimport ArticleAuthor from './ArticleAuthor.vue'\nimport ArticleEditorPage from './ArticleEditorPage.vue'\nimport ArticleHistory from './ArticleHistory.vue'\nimport ArticleRelated from './ArticleRelated.vue'\nimport ArticleSlug from './ArticleSlug.vue'\nimport ArticleTags from './ArticleTags.vue'\nimport ArticleUnpublish from './ArticleUnpublish.vue'\nimport ArticlesPage from './ArticlesPage.vue'\nimport AuthorsPage from './AuthorsPage.vue'\nimport TagsPage from './TagsPage.vue'\n\nexport interface BlogOptions {\n /** Where the blog lives inside the panel. The three sections sit under it. */\n path?: string\n}\n\n/**\n * The blog as sections of the panel: articles, rubrics, tags — and authors, where the site has\n * author pages.\n *\n * Several modules rather than one, because the navigation is one entry per module and the blog\n * wants several; the server puts them all in the `blog` group, which is what draws them under\n * one heading. Each is returned separately so a panel can leave one out.\n *\n * A section whose server half is not installed never appears — the entry is built from the\n * manifest — and so does one whose front end is missing. That is what makes it safe to return\n * them all from one call: a panel that installed only part of the blog gets only that part.\n */\n/**\n * The rubrics as the panel's shared categories see them: where they answer, what they are edited\n * on and what the blog calls them. Exported so that a panel mounting the screens somewhere of its\n * own does not have to repeat the words.\n *\n * Every word that names the thing is the blog's — \"rubric\", \"articles\" — and the rest (\"Edit\",\n * \"Cancel\", \"Leave without saving?\") is the panel's, said the same way in every module.\n */\nexport function rubricsOptions(path = '/blog'): CategoriesOptions {\n return {\n api: 'blog/rubrics',\n path: `${path}/rubrics`,\n name: 'webx.blog.rubrics',\n module: 'rubrics',\n screen: 'blog.category-form',\n manage: 'blog.taxonomy.manage',\n count: 'articles_count',\n items: (id) => ({ path: `${path}/articles`, query: { rubric: String(id) } }),\n words: {\n new: 'webx-blog::rubric.new',\n empty: 'webx-blog::rubric.empty',\n 'empty-help': 'webx-blog::rubric.empty-help',\n order: 'webx-blog::rubric.order',\n hidden: 'webx-blog::rubric.hidden',\n 'no-address': 'webx-blog::rubric.no-address',\n count: 'webx-blog::rubric.articles',\n 'show-items': 'webx-blog::rubric.show-articles',\n 'delete-blocked': 'webx-blog::rubric.delete-blocked',\n 'delete-text': 'webx-blog::rubric.delete-text',\n deleted: 'webx-blog::rubric.deleted',\n saved: 'webx-blog::rubric.saved',\n 'field-title': 'webx-blog::rubric.field-title',\n 'field-slug': 'webx-blog::rubric.field-slug',\n 'address-moving': 'webx-blog::rubric.address-moving',\n },\n }\n}\n\nexport function blog(options: BlogOptions = {}): AdminModule[] {\n const path = options.path ?? '/blog'\n\n return [\n {\n id: 'articles',\n path: `${path}/articles`,\n routes: [\n // The section's own path travels as a prop, so a panel that mounted the blog somewhere\n // else still links between its screens correctly.\n {\n path: `${path}/articles`,\n name: 'webx.blog.articles',\n component: ArticlesPage,\n props: { base: path },\n },\n {\n path: `${path}/articles/:id(\\\\d+)`,\n name: 'webx.blog.articles.edit',\n component: ArticleEditorPage,\n props: { base: path },\n },\n ],\n /*\n * The parts of `blog.article-form` that only this module can draw.\n *\n * The editor is a described screen so that a module can add a tab to it with a patch, and\n * the price of that is that everything on it has to be a node type. Four of these are\n * fields — the value is ids, and the control over them is a list that can be dragged or a\n * box that makes a tag — and two only draw. Each reads the article from the editor above\n * it rather than from the description, because a screen is a description and not a\n * binding.\n *\n * The slug is here for a different reason: what it edits is an ordinary string, but the\n * whole address is the prefix of the blog and that string, and the prefix is not in the\n * description — it is configuration the editor was handed with the record.\n */\n types: {\n 'wx-article-slug': { component: ArticleSlug, kind: 'field' },\n 'wx-article-author': { component: ArticleAuthor, kind: 'field' },\n 'wx-article-tags': { component: ArticleTags, kind: 'field' },\n 'wx-article-related': { component: ArticleRelated, kind: 'field' },\n 'wx-article-unpublish': { component: ArticleUnpublish, kind: 'display' },\n 'wx-article-history': { component: ArticleHistory, kind: 'display' },\n },\n },\n {\n id: 'rubrics',\n path: `${path}/rubrics`,\n // The list and the page of one rubric are the panel's shared category screens (§3.6 of\n // the services spec); what is the blog's is the description they are handed.\n routes: categoryRoutes(rubricsOptions(path)),\n },\n {\n id: 'tags',\n path: `${path}/tags`,\n routes: [\n {\n path: `${path}/tags`,\n name: 'webx.blog.tags',\n component: TagsPage,\n props: { base: path },\n },\n ],\n },\n {\n // Only on a site with author pages (§19): the server leaves the module out of the manifest\n // otherwise, and a module the manifest does not name is never drawn.\n id: 'authors',\n path: `${path}/authors`,\n routes: [\n {\n path: `${path}/authors`,\n name: 'webx.blog.authors',\n component: AuthorsPage,\n props: { base: path },\n },\n ],\n },\n ]\n}\n"],"names":["articleEditorKey","provideArticleEditor","editor","provide","useArticleEditor","inject","blogMessages","seeded","useBlogMessages","i18n","useAdmin","props","__props","value","_useModel","t","useTranslate","context","disabled","computed","options","author","profileLink","module","base","profile","chosen","next","_openBlock","_createElementBlock","_hoisted_1","_createVNode","_unref","WxSelect","$event","_createBlock","RouterLink","_createTextVNode","_toDisplayString","createBlogApi","admin","tagsBase","data","body","query","search","suffix","id","input","number","term","at","revision","ids","action","keep","redirect","PAUSE","api","route","useRoute","router","useRouter","locales","useLocales","dates","useDates","panel","message","useErrorText","list","article","ref","values","prefix","previewUrl","related","loading","saving","working","errors","snapshot","reloadToken","canManage","editing","useEditing","detail","theirs","refresh","flight","conflict","locked","current","dirty","state","title","written","localizedValue","future","publication","row","when","provideBlocksPreview","load","save","take","silent","error","toast","timer","schedule","onFocusOut","carried","done","write","sending","sent","failure","takeTheirs","discard","confirm","publish","held","old","address","unpublish","badge","status","leaveGuard","event","watch","onMounted","onBeforeUnmount","onBeforeRouteLeave","actions","leads","_Fragment","WxSkeleton","WxCard","WxScreenHead","WxBreadcrumb","WxBreadcrumbItem","WxBadge","WxEditingAlerts","_createElementVNode","WxScreen","WxActionBar","WxSaveState","WxButton","versions","live","restore","version","_hoisted_2","WxEmpty","_renderList","_hoisted_3","_hoisted_4","WxDate","WxText","WxDrafts","_cache","MATCHES","known","found","titles","all","one","rows","look","mine","page","add","picked","remove","WxSortableList","_withCtx","item","WxAction","head","slug","moving","$attrs","WxAlert","tags","tag","chips","missing","typed","same","create","chip","WxFormItem","open","resolve","dismiss","useModal","field","useTemplateRef","nextTick","submit","WxDialog","WxSpace","WxInput","CARDS","root","width","useElementWidth","createModal","ArticleCreateDialog","view","inBin","views","filters","asCards","columns","rowMenuWidth","emptyText","applied","rubric","narrow","clearFilters","onState","wanted","name","path","actionsFor","copyAddress","setPinned","run","pinned","request","said","dateOf","WxListScreen","WxTable","WxIcon","WxFilterChips","_normalizeClass","WxTooltip","_hoisted_5","WxAddressNote","_hoisted_6","_hoisted_7","WxEntityCard","WxRowMenu","createAuthorsApi","q","userId","emit","__emit","accounts","account","fallback","accountOptions","chosenAccount","main","blank","made","record","onKeydown","useBodyKeys","mayLeave","onBeforeRouteUpdate","to","from","selected","raw","close","pause","choose","created","removed","WxListDetail","WxAvatar","inline","back","AuthorPane","formId","useId","draft","other","survivor","going","affected","sum","addresses","WxCheckbox","rename","TagRenameDialog","TagCreateDialog","merge","TagMergeDialog","counts","sortOf","sortFromRoute","key","sort","desc","startRename","patch","setIndexing","noindex","saved","massActions","massSelected","tone","indexing","mergeSelected","merged","rubricsOptions","blog","ArticlesPage","ArticleEditorPage","ArticleSlug","ArticleAuthor","ArticleTags","ArticleRelated","ArticleUnpublish","ArticleHistory","categoryRoutes","TagsPage","AuthorsPage"],"mappings":";;;;;AAmDO,MAAMA,4BAA8D,mBAAmB;AAEvF,SAASC,GAAqBC,GAAoC;AACvE,EAAAC,GAAQH,IAAkBE,CAAM;AAClC;AAMO,SAASE,KAAgD;AAC9D,SAAOC,GAAOL,IAAkB,IAAI;AACtC;ACxDO,MAAMM,KAAyC;AAAA,EACpD,QAAQ;AAAA,IACN,OAAO;AAAA,IACP,UAAU;AAAA,IACV,SAAS;AAAA,IACT,MAAM;AAAA,IACN,SAAS;AAAA,IACT,QAAQ;AAAA,EAAA;AAAA,EAEV,OAAO;AAAA,IACL,KAAK;AAAA,IACL,QAAQ;AAAA,IACR,OAAO;AAAA,IACP,gBAAgB;AAAA,IAChB,aAAa;AAAA,IAEb,gBAAgB;AAAA,IAChB,kBAAkB;AAAA,IAClB,iBAAiB;AAAA,IACjB,eAAe;AAAA,IACf,iBAAiB;AAAA,IACjB,kBAAkB;AAAA,IAElB,gBAAgB;AAAA,IAChB,oBAAoB;AAAA,IACpB,oBAAoB;AAAA,IACpB,mBAAmB;AAAA,IACnB,sBAAsB;AAAA,IACtB,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,cAAc;AAAA,IACd,aAAa;AAAA,IAEb,YAAY;AAAA,IACZ,kBAAkB;AAAA,IAClB,kBAAkB;AAAA,IAClB,cAAc;AAAA,IACd,oBAAoB;AAAA,IACpB,KAAK;AAAA,IAEL,iBAAiB;AAAA,IACjB,cAAc;AAAA,IACd,iBAAiB;AAAA,IACjB,cAAc;AAAA,IACd,WAAW;AAAA,IACX,cAAc;AAAA,IAEd,MAAM;AAAA,IACN,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,KAAK;AAAA,IACL,OAAO;AAAA,IACP,SAAS;AAAA,IACT,WAAW;AAAA,IACX,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,QAAQ;AAAA,IAER,gBAAgB;AAAA,IAChB,eAAe;AAAA,IACf,SAAS;AAAA,IACT,UAAU;AAAA,IACV,WAAW;AAAA,IACX,oBAAoB;AAAA,IACpB,eAAe;AAAA,IACf,UAAU;AAAA,IACV,kBAAkB;AAAA,IAElB,aAAa;AAAA,IACb,eAAe;AAAA,IACf,QAAQ;AAAA,EAAA;AAAA,EAEV,SAAS;AAAA,IACP,OAAO;AAAA,IACP,UAAU;AAAA,IACV,MAAM;AAAA,IACN,eAAe;AAAA,IACf,UAAU;AAAA,IACV,WAAW;AAAA,IACX,iBAAiB;AAAA,IACjB,gBAAgB;AAAA,IAChB,iBAAiB;AAAA,IACjB,mBAAmB;AAAA,IACnB,eAAe;AAAA,IACf,SAAS;AAAA,IACT,MAAM;AAAA,IACN,SAAS;AAAA,IACT,WAAW;AAAA,IACX,kBAAkB;AAAA,IAClB,iBAAiB;AAAA,IACjB,mBAAmB;AAAA,IACnB,yBAAyB;AAAA,IACzB,wBACE;AAAA,IACF,OAAO;AAAA,IACP,eAAe;AAAA,IACf,cAAc;AAAA,IACd,kBAAkB;AAAA,IAClB,cAAc;AAAA,IACd,mBAAmB;AAAA,IACnB,kBACE;AAAA,IACF,qBAAqB;AAAA,IACrB,kBAAkB;AAAA,IAClB,kBACE;AAAA,IACF,QAAQ;AAAA,IACR,aAAa;AAAA,IACb,SAAS;AAAA,IACT,iBAAiB;AAAA,IACjB,eAAe;AAAA,IACf,cAAc;AAAA,IACd,iBAAiB;AAAA,IACjB,oBAAoB;AAAA,IACpB,cAAc;AAAA,IACd,WAAW;AAAA,IACX,cAAc;AAAA,IACd,cAAc;AAAA,IACd,eAAe;AAAA,IACf,cAAc;AAAA,IACd,SAAS;AAAA,IACT,iBAAiB;AAAA,IACjB,eAAe;AAAA,IACf,kBAAkB;AAAA,IAClB,mBAAmB;AAAA,IACnB,iBAAiB;AAAA,IACjB,SAAS;AAAA,IACT,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,cAAc;AAAA,IACd,iBAAiB;AAAA,IACjB,iBAAiB;AAAA,IACjB,gBACE;AAAA,IACF,mBAAmB;AAAA,IACnB,oBAAoB;AAAA,EAAA;AAAA,EAEtB,QAAQ;AAAA,IACN,KAAK;AAAA,IACL,OAAO;AAAA,IACP,cAAc;AAAA,IACd,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,cAAc;AAAA,IACd,UAAU;AAAA,IACV,iBAAiB;AAAA,IACjB,MAAM;AAAA,IAEN,eAAe;AAAA,IACf,WAAW;AAAA,IAEX,eAAe;AAAA,IACf,cAAc;AAAA,IACd,SAAS;AAAA,IACT,gBACE;AAAA,IACF,cAAc;AAAA,IACd,aAAa;AAAA,IACb,cAAc;AAAA,IACd,kBACE;AAAA,IAEF,YAAY;AAAA,IAEZ,QAAQ;AAAA,IACR,kBAAkB;AAAA,IAClB,gBAAgB;AAAA,IAChB,eAAe;AAAA,IACf,SAAS;AAAA,IAET,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,OAAO;AAAA,IACP,SAAS;AAAA,IACT,kBAAkB;AAAA,EAAA;AAAA,EAEpB,QAAQ;AAAA,IACN,eAAe;AAAA,IACf,cAAc;AAAA,IACd,mBACE;AAAA,IACF,cAAc;AAAA,IACd,aAAa;AAAA,IACb,kBAAkB;AAAA,IAClB,aAAa;AAAA,IACb,eAAe;AAAA,IACf,gBAAgB;AAAA,IAChB,qBACE;AAAA,IACF,cAAc;AAAA,IACd,mBAAmB;AAAA,IACnB,mBAAmB;AAAA,IACnB,iBAAiB;AAAA,IACjB,iBAAiB;AAAA,IACjB,KAAK;AAAA,IACL,QAAQ;AAAA,IACR,OAAO;AAAA,IACP,cACE;AAAA,IACF,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,eAAe;AAAA,IACf,SAAS;AAAA,IACT,gBAAgB;AAAA,IAChB,eAAe;AAAA,IACf,UAAU;AAAA,IACV,aAAa;AAAA,IACb,eAAe;AAAA,IACf,aAAa;AAAA,IACb,MAAM;AAAA,IACN,OAAO;AAAA,IACP,eAAe;AAAA,IACf,QAAQ;AAAA,IACR,gBAAgB;AAAA,IAChB,eAAe;AAAA,IACf,SAAS;AAAA,IACT,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,eAAe;AAAA,IACf,cAAc;AAAA,IACd,OAAO;AAAA,IACP,UAAU;AAAA,IACV,SAAS;AAAA,IACT,gBAAgB;AAAA,IAChB,iBAAiB;AAAA,IACjB,sBACE;AAAA,IACF,eAAe;AAAA,IACf,iBAAiB;AAAA,IACjB,kBAAkB;AAAA,IAClB,cAAc;AAAA,IACd,0BAA0B;AAAA,IAC1B,sBAAsB;AAAA,EAAA;AAAA,EAExB,KAAK;AAAA,IACH,KAAK;AAAA,IACL,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc;AAAA,IACd,mBAAmB;AAAA,IACnB,QAAQ;AAAA,IACR,QAAQ;AAAA,IAER,QAAQ;AAAA,IACR,OAAO;AAAA,IACP,cAAc;AAAA,IAEd,gBAAgB;AAAA,IAChB,kBAAkB;AAAA,IAClB,mBAAmB;AAAA,IACnB,mBAAmB;AAAA,IAEnB,YAAY;AAAA,IACZ,cAAc;AAAA,IACd,gBAAgB;AAAA,IAChB,iBAAiB;AAAA,IACjB,aAAa;AAAA,IAEb,iBAAiB;AAAA,IACjB,iBAAiB;AAAA,IACjB,oBAAoB;AAAA,IACpB,sBACE;AAAA,IAEF,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,OAAO;AAAA,IACP,SAAS;AAAA,IACT,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,gBAAgB;AAAA,IAChB,iBAAiB;AAAA,IACjB,gBAAgB;AAAA,IAChB,iBAAiB;AAAA,IAEjB,QAAQ;AAAA,IACR,gBAAgB;AAAA,IAChB,eAAe;AAAA,IACf,oBACE;AAAA,IACF,SAAS;AAAA,IACT,qBAAqB;AAAA,IACrB,oBAAoB;AAAA,IACpB,yBACE;AAAA,IACF,gBAAgB;AAAA,IAEhB,UAAU;AAAA,IACV,OAAO;AAAA,IACP,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,eAAe;AAAA,IACf,cAAc;AAAA,IACd,kBAAkB;AAAA,IAClB,uBAAuB;AAAA,IACvB,uBACE;AAAA,IACF,iBACE;AAAA,IACF,iBAAiB;AAAA,EAAA;AAErB,GCjTMC,yBAAa,QAAA;AAGZ,SAASC,KAAwB;AACtC,MAAI;AACF,UAAM,EAAE,MAAAC,EAAA,IAASC,GAAA;AAEjB,IAAKH,GAAO,IAAIE,CAAI,MAClBA,EAAK,SAAS,aAAaH,EAAY,GACvCC,GAAO,IAAIE,CAAI;AAAA,EAEnB,QAAQ;AAAA,EAER;AACF;;;;;;;;;;;;ACIA,UAAME,IAAQC,GAERC,IAAQC,GAA0BF,GAAA,YAAkB,GAEpDV,IAASE,GAAA;AACf,IAAAI,GAAA;AAEA,UAAMO,IAAIC,EAAa,WAAW,GAC5BC,IAAUP,GAAA,GACVQ,IAAWC,EAAS,MAAMR,EAAM,YAAYT,GAAQ,SAAS,UAAU,EAAI,GAE3EkB,IAAUD;AAAA,MAAyB,OACtCjB,GAAQ,QAAQ,MAAM,WAAW,IAAI,IAAI,CAACmB,OAAY;AAAA,QACrD,OAAOA,EAAO;AAAA,QACd,OAAOA,EAAO;AAAA,MAAA,EACd;AAAA,IAAA,GAQEC,IAAcH,EAAS,MAAM;AAGjC,UAAI,CAFeF,EAAQ,MAAM,UAAU,QAAQ,KAAK,CAACM,MAAWA,EAAO,OAAO,SAAS,KAExE,CAACN,EAAQ,IAAI,qBAAqB,KAAKJ,EAAM,UAAU,KAAM,QAAO;AAEvF,YAAMW,IAAOtB,GAAQ,QAAQ,SACvBuB,IAAUvB,GAAQ,QAAQ,MAAM,QAAQ,KAAK,CAACmB,MAAWA,EAAO,OAAOR,EAAM,KAAK,GAAG;AAE3F,aAAOY,IACH;AAAA,QACE,IAAI,EAAE,MAAM,GAAGD,CAAI,YAAY,OAAO,EAAE,QAAQ,OAAOC,CAAO,EAAA,EAAE;AAAA,QAChE,OAAOV,EAAE,gBAAgB;AAAA,MAAA,IAE3B;AAAA,QACE,IAAI,EAAE,MAAM,GAAGS,CAAI,YAAY,OAAO,EAAE,QAAQ,OAAO,SAAS,OAAOX,EAAM,KAAK,IAAE;AAAA,QACpF,OAAOE,EAAE,qBAAqB;AAAA,MAAA;AAAA,IAEtC,CAAC,GAEKW,IAASP,EAA2B;AAAA,MACxC,KAAK,MAAMN,EAAM;AAAA,MACjB,KAAK,CAACc,MAAS;AACb,QAAAd,EAAM,QAAQ,OAAOc,KAAS,WAAWA,IAAO;AAAA,MAClD;AAAA,IAAA,CACD;sBAICC,EAAA,GAAAC,EAeM,OAfNC,IAeM;AAAA,MAdJC,EASEC,EAAAC,EAAA,GAAA;AAAA,oBARSP,EAAA;AAAA,sDAAAA,EAAM,QAAAQ;AAAA,QACd,SAASd,EAAA;AAAA,QACT,aAAaY,EAAAjB,CAAA,EAAC,mBAAA;AAAA,QACd,UAAUG,EAAA;AAAA,QACX,WAAA;AAAA,QACA,YAAA;AAAA,QACA,UAAA;AAAA,QACC,cAAYc,EAAAjB,CAAA,EAAC,gBAAA;AAAA,MAAA;MAGGO,EAAA,cAAnBa,EAEcH,EAAAI,EAAA,GAAA;AAAA;QAFkB,OAAM;AAAA,QAA8B,IAAId,EAAA,MAAY;AAAA,MAAA;mBAClF,MAAuB;AAAA,UAApBe,EAAAC,EAAAhB,EAAA,MAAY,KAAK,GAAA,CAAA;AAAA,QAAA;;;;;;;;;;;AClBnB,SAASiB,GAAcC,GAA8B;AAC1D,QAAMhB,IAAO,GAAGgB,EAAM,OAAO,kBACvBC,IAAW,GAAGD,EAAM,OAAO,cAC3BE,IAAO,CAAIC,MAAyBA,EAAK;AAE/C,SAAO;AAAA,IACL,UAAU,CAACC,IAAQ,OAAO;AACxB,YAAMC,IAAS,IAAI,gBAAA;AAInB,MAAID,EAAM,KAAGC,EAAO,IAAI,KAAKD,EAAM,CAAC,GAChCA,EAAM,UAAU,QAAMC,EAAO,IAAI,UAAU,OAAOD,EAAM,MAAM,CAAC,GAC/DA,EAAM,OAAO,QAAMC,EAAO,IAAI,OAAO,OAAOD,EAAM,GAAG,CAAC,GACtDA,EAAM,UAAU,QAAMC,EAAO,IAAI,UAAU,OAAOD,EAAM,MAAM,CAAC,GAC/DA,EAAM,UAAQC,EAAO,IAAI,UAAUD,EAAM,MAAM,GAC/CA,EAAM,QAAMC,EAAO,IAAI,QAAQD,EAAM,IAAI,GACzCA,EAAM,WAASC,EAAO,IAAI,WAAW,GAAG,GACxCD,EAAM,QAAQA,EAAM,OAAO,KAAGC,EAAO,IAAI,QAAQ,OAAOD,EAAM,IAAI,CAAC,GACnEA,EAAM,YAAUC,EAAO,IAAI,YAAY,OAAOD,EAAM,QAAQ,CAAC;AAEjE,YAAME,IAASD,EAAO,OAAO,IAAI,IAAIA,CAAM,KAAK;AAEhD,aAAOL,EAAM,KACV,IAIE,GAAGhB,CAAI,GAAGsB,CAAM,EAAE,EACpB,KAAK,CAACH,OAAU,EAAE,GAAGA,EAAK,MAAM,MAAMA,EAAK,MAAM,SAASA,EAAK,QAAA,EAAU;AAAA,IAC9E;AAAA,IACA,KAAK,CAACI,MAAOP,EAAM,KAAK,IAA6B,GAAGhB,CAAI,IAAIuB,CAAE,EAAE,EAAE,KAAKL,CAAI;AAAA,IAC/E,QAAQ,CAACM,MAAUR,EAAM,KAAK,KAA2BhB,GAAMwB,CAAK,EAAE,KAAKN,CAAI;AAAA,IAC/E,MAAM,CAACK,GAAIC,MAAUR,EAAM,KAAK,IAA6B,GAAGhB,CAAI,IAAIuB,CAAE,IAAIC,CAAK,EAAE,KAAKN,CAAI;AAAA,IAC9F,SAAS,CAACK,MACRP,EAAM,KAAK,KAA8B,GAAGhB,CAAI,IAAIuB,CAAE,YAAY,CAAA,CAAE,EAAE,KAAKL,CAAI;AAAA,IACjF,UAAU,CAACK,MACTP,EAAM,KAAK,IAAgC,GAAGhB,CAAI,IAAIuB,CAAE,WAAW,EAAE,KAAKL,CAAI;AAAA,IAChF,gBAAgB,CAACK,GAAIE,MACnBT,EAAM,KACH,KAA8B,GAAGhB,CAAI,IAAIuB,CAAE,aAAaE,CAAM,YAAY,EAAE,EAC5E,KAAKP,CAAI;AAAA,IACd,MAAM,CAACQ,IAAO,OAAO;AACnB,YAAMJ,IAASI,EAAK,KAAA,MAAW,KAAK,KAAK,MAAM,mBAAmBA,EAAK,KAAA,CAAM,CAAC;AAE9E,aAAOV,EAAM,KAAK,IAAyB,GAAGC,CAAQ,GAAGK,CAAM,EAAE,EAAE,KAAKJ,CAAI;AAAA,IAC9E;AAAA,IACA,WAAW,CAACM,MAAUR,EAAM,KAAK,KAAuBC,GAAUO,CAAK,EAAE,KAAKN,CAAI;AAAA,IAClF,SAAS,CAACK,GAAII,GAAIC,MAChBZ,EAAM,KACH,KAA2B,GAAGhB,CAAI,IAAIuB,CAAE,YAAY;AAAA,MACnD,GAAII,KAAM,OAAO,CAAA,IAAK,EAAE,IAAAA,EAAA;AAAA,MACxB,GAAIC,IAAW,EAAE,UAAAA,MAAa,CAAA;AAAA,IAAC,CAChC,EACA,KAAKV,CAAI;AAAA,IACd,WAAW,CAACK,MACVP,EAAM,KAAK,KAA2B,GAAGhB,CAAI,IAAIuB,CAAE,cAAc,CAAA,CAAE,EAAE,KAAKL,CAAI;AAAA,IAChF,QAAQ,CAACK,MAAOP,EAAM,KAAK,OAAa,GAAGhB,CAAI,IAAIuB,CAAE,EAAE,EAAE,KAAK,MAAA;AAAA,KAAe;AAAA,IAC7E,SAAS,CAACA,MAAOP,EAAM,KAAK,KAA2B,GAAGhB,CAAI,IAAIuB,CAAE,YAAY,CAAA,CAAE,EAAE,KAAKL,CAAI;AAAA,IAE7F,UAAU,CAACE,IAAQ,OAAO;AACxB,YAAMC,IAAS,IAAI,gBAAA;AAEnB,MAAID,EAAM,KAAGC,EAAO,IAAI,KAAKD,EAAM,CAAC,GAEhCA,EAAM,SAAOC,EAAO,IAAI,SAAS,GAAG,GACpCD,EAAM,WAASC,EAAO,IAAI,WAAW,GAAG,GACxCD,EAAM,QAAMC,EAAO,IAAI,QAAQD,EAAM,IAAI,GACzCA,EAAM,QAAQA,EAAM,OAAO,KAAGC,EAAO,IAAI,QAAQ,OAAOD,EAAM,IAAI,CAAC,GACnEA,EAAM,YAAUC,EAAO,IAAI,YAAY,OAAOD,EAAM,QAAQ,CAAC;AAEjE,YAAME,IAASD,EAAO,OAAO,IAAI,IAAIA,CAAM,KAAK;AAEhD,aAAOL,EAAM,KACV,IAIE,GAAGC,CAAQ,GAAGK,CAAM,EAAE,EACxB,KAAK,CAACH,OAAU,EAAE,GAAGA,EAAK,MAAM,MAAMA,EAAK,MAAM,SAASA,EAAK,QAAA,EAAU;AAAA,IAC9E;AAAA,IACA,SAAS,CAACI,GAAIC,MAAUR,EAAM,KAAK,IAAsB,GAAGC,CAAQ,IAAIM,CAAE,IAAIC,CAAK,EAAE,KAAKN,CAAI;AAAA,IAC9F,WAAW,CAACK,MAAOP,EAAM,KAAK,OAAa,GAAGC,CAAQ,IAAIM,CAAE,EAAE,EAAE,KAAK,MAAA;AAAA,KAAe;AAAA,IACpF,UAAU,CAACM,GAAKC,MACdd,EAAM,KACH,KAAqC,GAAGC,CAAQ,SAAS,EAAE,KAAAY,GAAK,QAAAC,EAAA,CAAQ,EACxE,KAAK,CAACX,MAASA,EAAK,KAAK,QAAQ;AAAA,IACtC,WAAW,CAACU,GAAKE,GAAMC,MACrBhB,EAAM,KAAK,KAA0B,GAAGC,CAAQ,UAAU,EAAE,KAAAY,GAAK,MAAAE,GAAM,UAAAC,GAAU,EAAE,KAAKd,CAAI;AAAA,EAAA;AAElG;mDCzFMe,KAAQ;;;;;;AAhBd,UAAM9C,IAAQC,GAERK,IAAUP,GAAA,GACVgD,IAAMnB,GAActB,CAAO,GAC3B0C,IAAQC,GAAA,GACRC,IAASC,GAAA,GACTC,IAAUC,GAAA,GACVC,IAAQC,GAAA;AACd,IAAA1D,GAAA;AAEA,UAAMO,IAAIC,EAAa,WAAW,GAC5BmD,IAAQnD,EAAa,YAAY,GAEjCoD,IAAUC,GAAA,GAKVtB,IAAK5B,EAAS,MAAM,OAAOwC,EAAM,OAAO,EAAE,CAAC,GAC3CW,IAAOnD,EAAS,MAAM,GAAGR,EAAM,IAAI,WAAW,GAE9C4D,IAAUC,EAAuB,IAAI,GACrCC,IAASD,EAAiB,EAAE,GAC5BpB,IAAWoB,EAAI,EAAE,GACjBE,IAASF,EAAI,EAAE,GACfG,IAAaH,EAAmB,IAAI,GACpCpD,IAAUoD,EAA8B,EAAE,SAAS,CAAA,GAAI,SAAS,CAAA,GAAI,GACpEI,IAAUJ,EAAqB,EAAE,GAEjCK,IAAUL,EAAI,EAAI,GAClBM,IAASN,EAAI,EAAK,GAClBO,IAAUP,EAAI,EAAK,GACnBQ,IAASR,EAA8B,EAAE,GAEzCS,IAAWT,EAAI,EAAE,GACjBU,IAAcV,EAAI,CAAC,GAEnBW,KAAYhE,EAAS,MAAMF,EAAQ,IAAI,sBAAsB,CAAC,GAO9DmE,IAAUC,GAAwB;AAAA,MACtC,QAAQ;AAAA,MACR,IAAI,MAAMd,EAAQ,OAAO;AAAA,MACzB,QAAAE;AAAA,MACA,UAAArB;AAAA,MACA,MAAM,YAAY;AAChB,cAAMkC,IAAS,MAAM5B,EAAI,IAAIX,EAAG,KAAK;AAErC,eAAO,EAAE,QAAQuC,EAAO,QAAQ,UAAUA,EAAO,SAAA;AAAA,MACnD;AAAA,MACA,OAAO,CAACC,MAAW;AACjB,QAAAN,EAAS,QAAQ,KAAK,UAAUM,EAAO,MAAM,GAC7CL,EAAY,SAAS;AAAA,MACvB;AAAA;AAAA;AAAA,MAGA,SAAS,MAAMM,GAAA;AAAA,MACf,SAAS,YAAY;AACnB,QAAIjB,EAAQ,SAAO,MAAMb,EAAI,QAAQa,EAAQ,MAAM,EAAE;AAAA,MACvD;AAAA,MACA,UAAU,MAAMY,GAAU;AAAA,MAC1B,MAAM,MAAML,EAAO,SAASW,OAAW;AAAA,MACvC,QAAQ;AAAA,IAAA,CACT,GAEKC,KAAWN,EAAQ,UAGnBO,KAASxE,EAAS,MAAM,CAACgE,GAAU,SAASJ,EAAQ,KAAK,GAEzDa,KAAUzE,EAAS,MAAM,KAAK,UAAUsD,EAAO,KAAK,CAAC,GACrDoB,IAAQ1E,EAAS,MAAM8D,EAAS,UAAU,MAAMW,GAAQ,UAAUX,EAAS,KAAK,GAGhFa,KAAQ3E,EAAyC,MACjD2D,EAAO,QAAc,WAElBe,EAAM,QAAQ,YAAY,OAClC,GAMKE,IAAQ5E,EAAS,MAAM;AAC3B,YAAM6E,IAAUvB,EAAO,MAAM;AAE7B,aAAOwB,GAAeD,GAASjC,EAAQ,OAAO,OAAOQ,EAAQ,OAAO,SAAS,EAAE;AAAA,IACjF,CAAC,GAGK7C,IAASP,EAAwB,MAAM;AAC3C,YAAM6E,IAAUvB,EAAO,MAAM;AAE7B,aAAO,OAAOuB,KAAY,YAAYA,MAAY,KAAKA,IAAU;AAAA,IACnE,CAAC,GAEKE,IAAS/E,EAAS,MAAM;AAC5B,UAAIO,EAAO,UAAU,KAAM,QAAO;AAGlC,YAAMyB,IAAK,IAAI,KAAKzB,EAAO,MAAM,QAAQ,KAAK,GAAG,CAAC;AAElD,aAAO,CAAC,OAAO,MAAMyB,EAAG,QAAA,CAAS,KAAKA,EAAG,QAAA,IAAY,KAAK,IAAA;AAAA,IAC5D,CAAC,GAUKgD,KAAchF,EAAS,MAAM;AACjC,YAAMiF,IAAM7B,EAAQ;AAEpB,UAAI,CAAC6B,EAAK,QAAO;AAEjB,YAAMC,IAAOpC,EAAM,MAAMmC,EAAI,YAAY;AAQzC,aANa;AAAA,QACX,WAAWrF,EAAE,0BAA0B,EAAE,MAAMsF,GAAM;AAAA,QACrD,WAAWtF,EAAE,sBAAsB,EAAE,MAAMsF,GAAM;AAAA,QACjD,UAAUtF,EAAE,sBAAsB,EAAE,MAAMsF,GAAM;AAAA,MAAA,EAChDD,EAAI,MAAgD,KAEvC;AAAA,IACjB,CAAC;AAID,IAAAE,GAAqB,EAAE,KAAKnF,EAAS,MAAMwD,EAAW,KAAK,GAAG,QAAQO,GAAa,GAEnFjF,GAAqB;AAAA,MACnB,SAAAsE;AAAA,MACA,QAAAE;AAAA,MACA,SAAArD;AAAA,MACA,SAAAwD;AAAA,MACA,QAAAF;AAAA,MACA,MAAM/D,EAAM;AAAA,MACZ,WAAWwE,GAAU;AAAA,MACrB,UAAUQ;AAAA,MACV,QAAQ,MAAMY,GAAK,EAAI;AAAA,MACvB,MAAM,MAAMC,EAAA;AAAA,IAAK,CAClB;AAED,aAASC,GAAKnB,GAA6B;AACzC,MAAAf,EAAQ,QAAQe,EAAO,SACvBb,EAAO,QAAQa,EAAO,QACtBlC,EAAS,QAAQkC,EAAO,UACxBZ,EAAO,QAAQY,EAAO,QACtBX,EAAW,QAAQW,EAAO,aAC1BlE,EAAQ,QAAQkE,EAAO,SACvBV,EAAQ,QAAQU,EAAO,SACvBL,EAAS,QAAQ,KAAK,UAAUK,EAAO,MAAM,GAC7CF,EAAQ,OAAOE,EAAO,MAAM,GAC5BI,GAAS,QAAQ;AAAA,IACnB;AASA,mBAAea,GAAKG,IAAS,IAAsB;AACjD,MAAKA,MAAQ7B,EAAQ,QAAQ;AAE7B,UAAI;AACF,QAAA4B,GAAK,MAAM/C,EAAI,IAAIX,EAAG,KAAK,CAAC;AAAA,MAC9B,SAAS4D,GAAO;AACd,QAAAC,EAAM,OAAOxC,EAAQuC,CAAK,CAAC,GACtB9C,EAAO,KAAKS,EAAK,KAAK;AAAA,MAC7B,UAAA;AACE,QAAAO,EAAQ,QAAQ;AAAA,MAClB;AAAA,IACF;AAOA,mBAAeW,KAAyB;AACtC,UAAI;AACF,cAAMF,IAAS,MAAM5B,EAAI,IAAIX,EAAG,KAAK;AAErC,QAAAwB,EAAQ,QAAQe,EAAO,SACvBZ,EAAO,QAAQY,EAAO,QACtBX,EAAW,QAAQW,EAAO;AAAA,MAC5B,QAAQ;AAAA,MAER;AAAA,IACF;AAGA,QAAIuB;AAEJ,aAASC,IAAiB;AACxB,MAAI,CAAC3B,GAAU,SAASO,GAAS,SAAS,CAACG,EAAM,SAAST,EAAQ,QAAQ,UAE1E,aAAayB,CAAK,GAClBA,IAAQ,WAAW,MAAA;AAAM,QAAKL,EAAA;AAAA,SAAQ/C,EAAK;AAAA,IAC7C;AAGA,aAASsD,IAAmB;AAC1B,MAAI,CAAC5B,GAAU,SAASO,GAAS,SAAS,CAACG,EAAM,SAASf,EAAO,SAASM,EAAQ,QAAQ,UAG1F,aAAayB,CAAK,GACbL,EAAA;AAAA,IACP;AAGA,QAAIf;AAMJ,mBAAee,IAAsB;AACnC,aAAOf,MAAQ;AACb,cAAM,EAAE,SAAAuB,GAAS,MAAAC,OAASxB;AAK1B,YAHA,MAAMwB,IAGF,CAACpB,EAAM,SAASH,GAAS,SAASE,GAAQ,UAAUoB,EAAS;AAAA,MACnE;AAEA,YAAMA,IAAUpB,GAAQ,OAClBqB,IAAOC,EAAA;AAEb,MAAAzB,KAAS,EAAE,SAAAuB,GAAS,MAAAC,EAAA;AAEpB,UAAI;AACF,cAAMA;AAAA,MACR,UAAA;AACE,QAAIxB,IAAQ,SAASwB,MAAMxB,KAAS;AAAA,MACtC;AAAA,IACF;AAEA,mBAAeyB,IAAuB;AACpC,UAAI,CAAC3C,EAAQ,SAAS,CAACY,GAAU,MAAO;AAExC,mBAAa0B,CAAK;AAIlB,YAAMM,IAAUvB,GAAQ,OAClBwB,IAAO3C,EAAO;AAEpB,MAAAK,EAAO,QAAQ,IACfE,EAAO,QAAQ,CAAA;AAEf,UAAI;AACF,cAAMM,IAAS,MAAM5B,EAAI,KAAKa,EAAQ,MAAM,IAAI,EAAE,QAAQ6C,GAAM,UAAUhE,EAAS,OAAO;AAE1F,QAAAmB,EAAQ,QAAQe,EAAO,SACvBlC,EAAS,QAAQkC,EAAO,UACxBZ,EAAO,QAAQY,EAAO,QACtBX,EAAW,QAAQW,EAAO,aAC1BlE,EAAQ,QAAQkE,EAAO,SACvBV,EAAQ,QAAQU,EAAO,SACvBI,GAAS,QAAQ,MAEbE,GAAQ,UAAUuB,MAASlC,EAAS,QAAQkC,IAGhD/B,EAAQ,OAAOgC,CAAI,GAGnBlC,EAAY,SAAS;AAAA,MACvB,SAASyB,GAAO;AACd,cAAMU,KAAUV;AAKhB,YAAIU,GAAQ,WAAW,OAAOA,GAAQ,MAAM,MAAM;AAChD,gBAAM9B,KAAS8B,GAAQ,KAAK;AAG5B,UACEjC,EAAQ,QAAQ;AAAA,YACd,QAAQG,GAAO;AAAA,YACf,UAAUA,GAAO;AAAA,YACjB,SAAS8B,GAAQ,KAAK;AAAA,UAAA,CACvB,MAED9C,EAAQ,QAAQgB,GAAO,SAClBiB,EAAA;AAAA,QAET,MAAA,CAAWa,GAAQ,MAAM,UACvBrC,EAAO,QAAQqC,GAAQ,KAAK,QAC5BT,EAAM,OAAO7F,EAAE,qBAAqB,CAAC,KAC1B,MAAMqE,EAAQ,OAAOuB,CAAK,KAErCC,EAAM,OAAOxC,EAAQuC,CAAK,CAAC;AAAA,MAE/B,UAAA;AACE,QAAA7B,EAAO,QAAQ;AAAA,MACjB;AAAA,IACF;AAGA,mBAAewC,IAA4B;AACzC,YAAMf,GAAK,EAAI;AAAA,IACjB;AAUA,mBAAegB,KAAyB;AAYtC,UAXI,GAAChD,EAAQ,SAWT,CATW,MAAMiD,GAAQ;AAAA,QAC3B,OAAOrD,EAAM,sBAAsB;AAAA,QACnC,SAASA,EAAM,qBAAqB;AAAA,QACpC,aAAapD,EAAE,iBAAiB;AAAA;AAAA,QAEhC,YAAYoD,EAAM,qBAAqB;AAAA,QACvC,MAAM;AAAA,MAAA,CACP,IAID;AAAA,QAAAY,EAAQ,QAAQ;AAEhB,YAAI;AACF,UAAA0B,GAAK,MAAM/C,EAAI,QAAQa,EAAQ,MAAM,EAAE,CAAC,GACxCqC,EAAM,QAAQ7F,EAAE,mBAAmB,CAAC;AAAA,QACtC,SAAS4F,GAAO;AACd,UAAAC,EAAM,OAAOxC,EAAQuC,CAAK,CAAC;AAAA,QAC7B,UAAA;AACE,UAAA5B,EAAQ,QAAQ;AAAA,QAClB;AAAA;AAAA,IACF;AAUA,mBAAe0C,KAAyB;AAMtC,UAHI5B,EAAM,SAAO,MAAMW,EAAA,GACnBd,GAAS,SAASG,EAAM,SAExB,CAACtB,EAAQ,MAAO;AAIpB,YAAMmD,IAAO,MAAMtC,EAAQ,cAAA;AAE3B,UAAI,CAACsC,EAAM;AAEX,YAAMtB,IAAM7B,EAAQ,OAId5C,IAAOyE,EAAI,aAAaA,EAAI,MAC5BuB,KACJvB,EAAI,aAAa,QAAQA,EAAI,SAAS,OAClC,IAAIjC,EAAM,wBAAwB,EAAE,KAAK,IAAIiC,EAAI,IAAI,GAAA,CAAI,CAAC,KAC1D,IACAwB,KAAUjG,MAAS,OAAO,OAAO,IAAIA,CAAI,IACzC0E,KAAO3E,EAAO,UAAU,OAAOX,EAAE,qBAAqB,IAAIkD,EAAM,MAAMvC,EAAO,KAAK;AAgBxF,UAZEgG,EAAK,SACJ,MAAMF,GAAQ;AAAA,QACb,OAAOzG,EAAE,yBAAyB,EAAE,OAAOqF,EAAI,OAAO;AAAA,QACtD,SAASF,EAAO,QACZnF,EAAE,yBAAyB,EAAE,MAAMsF,IAAM,IACzCuB,OAAY,OACV7G,EAAE,yBAAyB,IAC3BA,EAAE,wBAAwB,EAAE,SAAA6G,GAAA,CAAS,IAAID;AAAA,QAC/C,aAAazB,EAAO,QAAQnF,EAAE,kBAAkB,IAAIA,EAAE,eAAe;AAAA,QACrE,YAAYA,EAAE,cAAc;AAAA,MAAA,CAC7B,GAIH;AAAA,QAAAgE,EAAQ,QAAQ;AAEhB,YAAI;AACF,gBAAMrB,EAAI,QAAQ0C,EAAI,IAAI1E,EAAO,OAAOgG,EAAK,QAAQ,GACrD,MAAMnB,GAAK,EAAI,GACfK,EAAM,QAAQV,EAAO,QAAQnF,EAAE,mBAAmB,IAAIA,EAAE,iBAAiB,CAAC;AAAA,QAC5E,SAAS4F,IAAO;AACd,UAAM,MAAMvB,EAAQ,OAAOuB,EAAK,KAAIC,EAAM,OAAOxC,EAAQuC,EAAK,CAAC;AAAA,QACjE,UAAA;AACE,UAAA5B,EAAQ,QAAQ;AAAA,QAClB;AAAA;AAAA,IACF;AAMA,mBAAe8C,KAA2B;AACxC,YAAMzB,IAAM7B,EAAQ;AAWpB,UATI,GAAC6B,KASD,CAPW,MAAMoB,GAAQ;AAAA,QAC3B,OAAOrD,EAAM,wBAAwB;AAAA,QACrC,SAASA,EAAM,uBAAuB;AAAA,QACtC,aAAaA,EAAM,kBAAkB;AAAA,QACrC,YAAYA,EAAM,uBAAuB;AAAA,MAAA,CAC1C,IAID;AAAA,QAAAY,EAAQ,QAAQ;AAEhB,YAAI;AACF,gBAAMrB,EAAI,UAAU0C,EAAI,EAAE,GAC1B,MAAMG,GAAK,EAAI,GACfK,EAAM,QAAQzC,EAAM,oBAAoB,CAAC;AAAA,QAC3C,SAASwC,GAAO;AACd,UAAAC,EAAM,OAAOxC,EAAQuC,CAAK,CAAC;AAAA,QAC7B,UAAA;AACE,UAAA5B,EAAQ,QAAQ;AAAA,QAClB;AAAA;AAAA,IACF;AAEA,aAAS+C,KAAuD;AAC9D,YAAMC,IAASxD,EAAQ,OAAO;AAE9B,aAAIwD,MAAW,cAAoB,YAC/BA,MAAW,aAAmB,YAE3BA,MAAW,cAAc,YAAY;AAAA,IAC9C;AAGA,aAASC,GAAWC,GAAgC;AAClD,MAAIpC,EAAM,SAAOoC,EAAM,eAAA;AAAA,IACzB;AAEA,IAAAC,GAAMtC,IAASkB,CAAQ,GACvBoB,GAAMnF,GAAI;AAAM,MAAKwD;KAAM,GAE3B4B,GAAU,MAAM;AACd,MAAK5B,GAAA,GACL,OAAO,iBAAiB,gBAAgByB,EAAU;AAAA,IACpD,CAAC,GAEDI,GAAgB,MAAM;AACpB,mBAAavB,CAAK,GAClB,OAAO,oBAAoB,gBAAgBmB,EAAU;AAAA,IACvD,CAAC,GAQDK,GAAmB,YACb,CAACxC,EAAM,SAAS,CAACV,GAAU,UAE/B,MAAMqB,EAAA,GAEF,CAACX,EAAM,SAAc,KAElB,MAAM2B,GAAQ;AAAA,MACnB,OAAOzG,EAAE,qBAAqB;AAAA,MAC9B,SAASA,EAAE,oBAAoB;AAAA,MAC/B,aAAaA,EAAE,eAAe;AAAA,MAC9B,YAAYA,EAAE,cAAc;AAAA,MAC5B,MAAM;AAAA,IAAA,CACP,CACF;AAED,UAAMuH,KAAUnH,EAAyB,MAAM;AAC7C,YAAMoH,IAAwB,CAAA;AAE9B,aAAI5D,EAAW,SACb4D,EAAM,KAAK,EAAE,KAAK,WAAW,OAAOxH,EAAE,iBAAiB,GAAG,MAAM,OAAO,MAAM4D,EAAW,OAAO,GAG7FJ,EAAQ,OAAO,OAAOA,EAAQ,MAAM,WAAW,WACjDgE,EAAM,KAAK;AAAA,QACT,KAAK;AAAA,QACL,OAAOxH,EAAE,oBAAoB;AAAA,QAC7B,MAAM;AAAA,QACN,MAAMwD,EAAQ,MAAM;AAAA,MAAA,CACrB,GAMDY,GAAU,UACTZ,EAAQ,OAAO,WAAW,eAAeA,EAAQ,OAAO,WAAW,eAEpEgE,EAAM,KAAK;AAAA,QACT,KAAK;AAAA,QACL,OAAOpE,EAAM,kBAAkB;AAAA,QAC/B,MAAM;AAAA,QACN,KAAK,MAAA;AAAM,UAAK0D,GAAA;AAAA;AAAA,MAAU,CAC3B,GASCtD,EAAQ,OAAO,WAAW,cAC5BgE,EAAM,KAAK;AAAA,QACT,KAAK;AAAA,QACL,OAAOxH,EAAE,iBAAiB;AAAA,QAC1B,MAAM;AAAA,QACN,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,KAAK,MAAA;AAAM,UAAKwG,GAAA;AAAA;AAAA,MAAQ,CACzB,GAGIgB;AAAA,IACT,CAAC;2BAOC1G,EA6FM,OAAA;AAAA,MA7FD,OAAM;AAAA,MAAqB,YAAUkF;AAAA,IAAA;MACxBlC,EAAA,UAAYN,EAAA,cAA5B1C,EAMW2G,IAAA,EAAA,KAAA,EAAA,GAAA;AAAA,QAFTzG,EAAgEC,EAAAyG,EAAA,GAAA;AAAA,UAAnD,OAAM;AAAA,UAA2B,OAAA;AAAA,UAAO,MAAM;AAAA,QAAA;QAC3D1G,EAA4CC,EAAA0G,EAAA,GAAA,MAAA;AAAA,qBAAnC,MAAyB;AAAA,YAAzB3G,EAAyBC,EAAAyG,EAAA,GAAA,EAAX,MAAM,GAAC;AAAA,UAAA;;;sBAGhC5G,EAmFW2G,IAAA,EAAA,KAAA,KAAA;AAAA,QA5ETzG,EA2BiBC,EAAA2G,EAAA,GAAA;AAAA,UA1Bf,SAAA;AAAA,UACC,MAAMrE,EAAA;AAAA,UACN,cAAYtC,EAAAjB,CAAA,EAAC,iBAAA;AAAA,UACb,OAAOgF,EAAA,SAAS/D,EAAAjB,CAAA,EAAC,kBAAA;AAAA,UACjB,UAAUoF,GAAA;AAAA,UACV,SAASmC,GAAA;AAAA,QAAA;UAEC,SACT,MAKgB;AAAA,YALhBvG,EAKgBC,EAAA4G,EAAA,GAAA;AAAA,cALD,MAAK;AAAA,cAAM,OAAO5G,EAAAjB,CAAA,EAAC,eAAA;AAAA,YAAA;yBAChC,MAEqB;AAAA,gBAFrBgB,EAEqBC,EAAA6G,EAAA,GAAA;AAAA,kBAFA,IAAI;AAAA,kBAAgB,IAAIvE,EAAA;AAAA,gBAAA;6BAC3C,MAA0B;AAAA,wBAAvBtC,EAAAjB,CAAA,EAAC,iBAAA,CAAA,GAAA,CAAA;AAAA,kBAAA;;;gBAENgB,EAAqFC,EAAA6G,EAAA,GAAA,EAAjE,SAAA,MAAO;AAAA,6BAAC,MAAoC;AAAA,oBAAjCxG,EAAAC,EAAAyD,EAAA,SAAS/D,EAAAjB,CAAA,EAAC,kBAAA,CAAA,GAAA,CAAA;AAAA,kBAAA;;;;;;;UAIlC,iBACT,MAAkF;AAAA,YAAlFgB,EAAkFC,EAAA8G,EAAA,GAAA;AAAA,cAAvE,MAAMhB,GAAA;AAAA,cAAS,KAAA;AAAA,YAAA;yBAAI,MAAyC;AAAA,oBAAtC9F,EAAAjB,CAAA,EAAC,gBAAiBwD,EAAA,MAAQ,MAAM,EAAA,CAAA,GAAA,CAAA;AAAA,cAAA;;;YAKjDA,EAAA,MAAQ,WAAM,mBAA9BpC,EAEWH,EAAA8G,EAAA,GAAA;AAAA;cAFoC,MAAK;AAAA,cAAU,OAAA;AAAA,YAAA;yBAC5D,MAAsB;AAAA,oBAAnB9G,EAAAjB,CAAA,EAAC,aAAA,CAAA,GAAA,CAAA;AAAA,cAAA;;;;;;QAOVgB,EAA0EC,EAAA+G,EAAA,GAAA;AAAA,UAAtD,SAAS/G,EAAAoD,CAAA;AAAA,UAAU,QAAMoB;AAAA,UAAO,UAAQc;AAAA,QAAA;QAE5D0B,EAEM,OAFNlH,IAEM;AAAA,UADJC,EAA2FC,EAAAiH,EAAA,GAAA;AAAA,wBAAvExE,EAAA;AAAA,0DAAAA,EAAM,QAAAvC;AAAA,YAAE,MAAK;AAAA,YAAqB,QAAQ8C,EAAA;AAAA,YAAS,UAAUW,GAAA;AAAA,UAAA;;QAiB9DR,GAAA,cAArBhD,EAwBgBH,EAAAkH,EAAA,GAAA,EAAA,KAAA,KAAA;AAAA,UAvBH,SACT,MAA8D;AAAA,YAAxClH,EAAAoD,CAAA,EAAQ,QAAQ,0BAAtCjD,EAA8DH,EAAAmH,EAAA,GAAA;AAAA;cAAhB,OAAOrD,GAAA;AAAA,YAAA;;qBAGvD,MAQY;AAAA,YARZ/D,EAQYC,EAAAoH,EAAA,GAAA;AAAA,cAPV,SAAQ;AAAA,cACP,SAAStE,EAAA;AAAA,cACT,WAAWe,EAAA,SAAS7D,KAAQ,QAAQ;AAAA,cACpC,OAAOA,EAAAoD,CAAA,EAAQ,QAAQ;AAAA,cACvB,SAAOoB;AAAA,YAAA;yBAER,MAAuB;AAAA,oBAApBxE,EAAAjB,CAAA,EAAC,cAAA,CAAA,GAAA,CAAA;AAAA,cAAA;;;YAGNgB,EAQYC,EAAAoH,EAAA,GAAA;AAAA,cAPV,MAAK;AAAA,cACJ,SAASrE,EAAA;AAAA,cACT,UAAU/C,EAAAoD,CAAA,EAAQ,QAAQ,SAAUb,EAAA,MAAQ,WAAM,eAAA,CAAqBsB,EAAA;AAAA,cACvE,OAAO7D,EAAAoD,CAAA,EAAQ,QAAQ;AAAA,cACvB,SAAOqC;AAAA,YAAA;yBAER,MAAyD;AAAA,oBAAtDvB,EAAA,QAASlE,EAAAjB,CAAA,EAAC,kBAAA,IAAuBiB,EAAAjB,CAAA,EAAC,eAAA,CAAA,GAAA,CAAA;AAAA,cAAA;;;;;;;;;;;;;AC7pB/C,UAAME,IAAUP,GAAA,GACVgD,IAAMnB,GAActB,CAAO,GAC3Bf,IAASE,GAAA;AACf,IAAAI,GAAA;AAEA,UAAMO,IAAIC,EAAa,WAAW,GAE5BoD,IAAUC,GAAA,GAEVgF,IAAW7E,EAA6B,IAAI,GAC5CO,IAAUP,EAAI,EAAK,GAEnBD,IAAUpD,EAAS,MAAMjB,GAAQ,QAAQ,SAAS,IAAI,GACtDiF,IAAYhE,EAAS,MAAMF,EAAQ,IAAI,sBAAsB,CAAC,GAO9DqI,IAAOnI;AAAA,MAAS,MACpBoD,EAAQ,OAAO,WAAW,eAAeA,EAAQ,OAAO,WAAW,aAC9D8E,EAAS,QAAQ,CAAC,GAAG,UAAU,OAChC;AAAA,IAAA;AAGN,mBAAe9C,IAAsB;AACnC,YAAMX,IAAUrB,EAAQ;AAExB,UAAKqB;AAEL,YAAI;AACF,UAAAyD,EAAS,QAAQ,MAAM3F,EAAI,SAASkC,EAAQ,EAAE;AAAA,QAChD,SAASe,GAAO;AACd,UAAA0C,EAAS,QAAQ,CAAA,GACjBzC,EAAM,OAAOxC,EAAQuC,CAAK,CAAC;AAAA,QAC7B;AAAA,IACF;AAEA,mBAAe4C,EAAQC,GAAwC;AAC7D,YAAM5D,IAAUrB,EAAQ;AAWxB,UATI,GAACqB,KASD,CAPW,MAAM4B,GAAQ;AAAA,QAC3B,OAAOzG,EAAE,yBAAyB,EAAE,QAAQyI,EAAQ,QAAQ;AAAA,QAC5D,SAASzI,EAAE,sBAAsB;AAAA,QACjC,aAAaA,EAAE,yBAAyB;AAAA,QACxC,YAAYA,EAAE,cAAc;AAAA,MAAA,CAC7B,IAID;AAAA,QAAAgE,EAAQ,QAAQ;AAEhB,YAAI;AACF,gBAAMrB,EAAI,eAAekC,EAAQ,IAAI4D,EAAQ,MAAM,GAGnD,MAAMtJ,GAAQ,OAAA,GACd0G,EAAM,QAAQ7F,EAAE,4BAA4B,EAAE,QAAQyI,EAAQ,OAAA,CAAQ,CAAC;AAAA,QACzE,SAAS7C,GAAO;AACd,UAAAC,EAAM,OAAOxC,EAAQuC,CAAK,CAAC;AAAA,QAC7B,UAAA;AACE,UAAA5B,EAAQ,QAAQ;AAAA,QAClB;AAAA;AAAA,IACF;AAIA,WAAAmD;AAAA,MACE,MAAM,CAAC3D,EAAQ,OAAO,IAAIA,EAAQ,OAAO,cAAcA,EAAQ,OAAO,UAAU;AAAA,MAChF,MAAA;AAAM,QAAKgC,EAAA;AAAA;AAAA,MACX,EAAE,WAAW,GAAA;AAAA,IAAK,cAKlB3E,EAAA,GAAAC,EA+CM,OA/CNC,IA+CM;AAAA,MA9CJkH,EAoCM,OApCNS,IAoCM;AAAA,QAhCeJ,EAAA,UAAQ,aAA3BlH,EAAoFH,EAAAyG,EAAA,GAAA;AAAA;UAA9C,OAAM;AAAA,UAA6B,MAAM;AAAA,QAAA,MAC1DY,EAAA,MAAS,WAAM,UAApClH,EAAwFH,EAAA0H,EAAA,GAAA;AAAA;UAA3C,aAAa1H,EAAAjB,CAAA,EAAC,uBAAA;AAAA,QAAA,wCAC3Dc,EA6BM2G,IAAA,EAAA,KAAA,EAAA,GAAAmB,GA5BcN,EAAA,OAAQ,CAAnBG,YADT3H,EA6BM,OAAA;AAAA,UA1BH,KAAK2H,EAAQ;AAAA,UACd,WAAM,2BAAyB,EAAA,WACVA,EAAQ,WAAWF,EAAA,OAAI,CAAA;AAAA,QAAA;UAE5CN,EAES,QAFTY,IAEStH,EADPN,KAAC,mBAAA,EAAA,QAA8BwH,EAAQ,OAAA,CAAM,CAAA,GAAA,CAAA;AAAA,UAE/CR,EAMM,OANNa,IAMM;AAAA,YALWL,EAAQ,mBAAvBrH,EAA0FH,EAAA8H,EAAA,GAAA;AAAA;cAAtD,OAAON,EAAQ;AAAA,cAAY,MAAK;AAAA,cAAK,MAAK;AAAA,YAAA;YAC9EzH,EAGUC,EAAA+H,EAAA,GAAA;AAAA,cAHD,MAAK;AAAA,cAAK,MAAK;AAAA,YAAA;yBACtB,MAA6D;AAAA,oBAA1DP,EAAQ,UAAUxH,EAAAjB,CAAA,oBAAoByI,EAAQ,MAAM,EAAA,CAAA,IAAM,KAC7D,CAAA;AAAA,gBAAgBA,EAAQ,gBAAxB3H,EAAoE2G,IAAA,EAAA,KAAA,KAAA;AAAA,oBAAnC,QAAGlG,EAAGkH,EAAQ,OAAO,GAAA,CAAA;AAAA,gBAAA;;;;;UAG1CA,EAAQ,WAAWF,EAAA,cAAnCnH,EAEaH,EAAA8G,EAAA,GAAA;AAAA;YAF4B,MAAK;AAAA,YAAU,KAAA;AAAA,UAAA;uBAAI,MAE1D;AAAA,kBADA9G,EAAAjB,CAAA,EAAC,sBAAA,CAAA,GAAA,CAAA;AAAA,YAAA;;;UAGKoE,EAAA,SAAaqE,EAAQ,WAAWF,EAAA,cADxCnH,EAQYH,EAAAoH,EAAA,GAAA;AAAA;YANV,MAAK;AAAA,YACL,SAAQ;AAAA,YACP,SAASrE,EAAA;AAAA,YACT,SAAK,CAAA7C,MAAEqH,EAAQC,CAAO;AAAA,UAAA;uBAEvB,MAAkC;AAAA,kBAA/BxH,EAAAjB,CAAA,EAAC,yBAAA,CAAA,GAAA,CAAA;AAAA,YAAA;;;;;MAKVgB,EAOEC,EAAAgI,EAAA,GAAA;AAAA,QANC,IAAIzF,EAAA,OAAS;AAAA,QACd,QAAO;AAAA,QACP,QAAO;AAAA,QACN,eAAaY,EAAA;AAAA,QACb,QAAQZ,EAAA,OAAS,cAAcA,EAAA,OAAS,UAAU;AAAA,QAClD,YAAQ0F,EAAA,CAAA,MAAAA,EAAA,CAAA,IAAA,CAAA/H,MAAEF,EAAA9B,CAAA,GAAQ,OAAA;AAAA,MAAM;;;uJCtGzBuD,KAAQ,KAGRyG,KAAU;;;;;;;;;;;AAlBhB,UAAMvJ,IAAQC,GAERC,IAAQC,GAAqBF,GAAA,YAAsB,GAEnDK,IAAUP,GAAA,GACVgD,IAAMnB,GAActB,CAAO,GAC3Bf,IAASE,GAAA;AACf,IAAAI,GAAA;AAEA,UAAM,IAAIQ,EAAa,WAAW,GAE5BoD,IAAUC,GAAA,GACVnD,IAAWC,EAAS,MAAMR,EAAM,YAAYT,GAAQ,SAAS,UAAU,EAAI,GAQ3EiK,IAAQ3F,EAAI,oBAAI,KAAqB,GACrC4F,IAAQ5F,EAAqB,EAAE;AAErC,QAAIqC;AAEJ,IAAAqB;AAAA,MACE,MAAMhI,GAAQ,QAAQ;AAAA,MACtB,CAACmK,MAAW;AACV,cAAMC,IAAM,IAAI,IAAIH,EAAM,KAAK;AAE/B,mBAAWI,KAAOF,KAAU,MAAQ,IAAIE,EAAI,IAAIA,EAAI,KAAK;AAEzD,QAAAJ,EAAM,QAAQG;AAAA,MAChB;AAAA,MACA,EAAE,WAAW,IAAM,MAAM,GAAA;AAAA,IAAK;AAGhC,UAAME,IAAOrJ,EAA0B;AAAA,MACrC,KAAK,MAAMN,EAAM,MAAM,IAAI,CAACkC,OAAQ,EAAE,IAAAA,GAAI,OAAOoH,EAAM,MAAM,IAAIpH,CAAE,KAAK,IAAIA,CAAE,KAAK;AAAA,MACnF,KAAK,CAACpB,MAAS;AACb,QAAAd,EAAM,QAAQc,EAAK,IAAI,CAACyE,MAAQA,EAAI,EAAE;AAAA,MACxC;AAAA,IAAA,CACD,GAEKhF,IAAUD;AAAA,MAAyB,MACvCiJ,EAAM,MACH,OAAO,CAACG,MAAQ,CAAC1J,EAAM,MAAM,SAAS0J,EAAI,EAAE,CAAC,EAC7C,IAAI,CAACA,OAAS,EAAE,OAAOA,EAAI,OAAO,OAAOA,EAAI,KAAK;AAAA,IAAA;AAGvD,mBAAeE,EAAKvH,GAA6B;AAC/C,YAAMwH,IAAOxK,GAAQ,QAAQ,OAAO;AAEpC,UAAI;AACF,cAAMyK,IAAO,MAAMjH,EAAI,SAAS,EAAE,GAAGR,GAAM,UAAUgH,IAAS;AAG9D,QAAAE,EAAM,QAAQO,EAAK,KAChB,OAAO,CAACvE,MAAQA,EAAI,OAAOsE,CAAI,EAC/B,IAAI,CAACtE,OAAS,EAAE,IAAIA,EAAI,IAAI,OAAOA,EAAI,QAAQ;AAElD,cAAMkE,IAAM,IAAI,IAAIH,EAAM,KAAK;AAE/B,mBAAWI,KAAOH,EAAM,MAAO,CAAAE,EAAI,IAAIC,EAAI,IAAIA,EAAI,KAAK;AAExD,QAAAJ,EAAM,QAAQG;AAAA,MAChB,SAAS3D,GAAO;AACd,QAAAC,EAAM,OAAOxC,EAAQuC,CAAK,CAAC;AAAA,MAC7B;AAAA,IACF;AAEA,aAAS9D,EAAOK,GAAoB;AAClC,mBAAa2D,CAAK,GAClBA,IAAQ,WAAW,MAAA;AAAM,QAAK4D,EAAKvH,CAAI;AAAA,SAAGO,EAAK;AAAA,IACjD;AAEA,aAASmH,EAAIC,GAAgC;AAC3C,MAAI,OAAOA,KAAW,YAAYhK,EAAM,MAAM,SAASgK,CAAM,MAE7DhK,EAAM,QAAQ,CAAC,GAAGA,EAAM,OAAOgK,CAAM;AAAA,IACvC;AAEA,aAASC,EAAO/H,GAAkB;AAChC,MAAAlC,EAAM,QAAQA,EAAM,MAAM,OAAO,CAAC0J,MAAQA,MAAQxH,CAAE;AAAA,IACtD;AAEA,WAAAmF;AAAA,MACE,MAAMhI,GAAQ,QAAQ,OAAO;AAAA,MAC7B,MAAA;AAAM,QAAKuK,EAAK,EAAE;AAAA;AAAA,MAClB,EAAE,WAAW,GAAA;AAAA,IAAK,GAGpBrC,GAAgB,MAAM,aAAavB,CAAK,CAAC,cAIvCjF,EAAA,GAAAC,EAsCM,OAtCNC,IAsCM;AAAA,MArCJC,EAuBmBC,EAAA+I,EAAA,GAAA;AAAA,oBAtBRP,EAAA;AAAA,sDAAAA,EAAI,QAAAtI;AAAA,QACb,OAAA;AAAA,QACA,MAAK;AAAA,QACL,YAAS;AAAA,QACT,cAAW;AAAA,QACV,UAAUhB,EAAA;AAAA,QACV,cAAYc,EAAA,CAAA,EAAC,uBAAA;AAAA,QACb,cAAYA,EAAA,CAAA,EAAC,iBAAA;AAAA,MAAA;QAEH,SAAOgJ,EAChB,CAAiF,EAD7D,MAAAC,QAAI;AAAA,UACxBjC,EAAiF,QAAjFS,IAAiFnH,EAAtC2I,EAAuB,KAAK,GAAA,CAAA;AAAA,QAAA;QAG9D,SAAOD,EAChB,CAME,EAPkB,MAAAC,QAAI;AAAA,UACxBlJ,EAMEC,EAAAkJ,EAAA,GAAA;AAAA,YALA,MAAK;AAAA,YACL,MAAK;AAAA,YACJ,UAAUhK,EAAA;AAAA,YACV,OAAOc,EAAA,CAAA,EAAC,wBAAA;AAAA,YACR,SAAK,CAAAE,MAAE4I,EAAQG,EAAuB,EAAE;AAAA,UAAA;;;;MAK/ClJ,EAWEC,EAAAC,EAAA,GAAA;AAAA,QAVC,eAAa;AAAA,QACb,SAASb,EAAA;AAAA,QACT,aAAaY,EAAA,CAAA,EAAC,qBAAA;AAAA,QACd,cAAYA,EAAA,CAAA,EAAC,yBAAA;AAAA,QACb,UAAUd,EAAA;AAAA,QACX,YAAA;AAAA,QACA,UAAA;AAAA,QACC,cAAYc,EAAA,CAAA,EAAC,qBAAA;AAAA,QACb,UAAQa;AAAA,QACR,uBAAoB+H;AAAA,MAAA;;;;;;;;ACnI3B,UAAM1K,IAASE,GAAA,GACT2D,IAAUC,GAAA;AAChB,IAAAxD,GAAA;AAEA,UAAMO,IAAIC,EAAa,WAAW,GAG5B0D,IAASvD,EAAS,MAAM;AAC5B,YAAMgK,IAAOjL,GAAQ,OAAO,SAAS;AAErC,aAAOiL,MAAS,KAAK,MAAM,IAAIA,CAAI;AAAA,IACrC,CAAC,GAGKC,IAAOjK,EAAS,MAAM;AAC1B,YAAM6E,IAAU9F,GAAQ,OAAO,MAAM;AAErC,aAAI,OAAO8F,KAAY,WAAiBA,IAIhCA,IAAiDjC,EAAQ,OAAO,KAAK,KAAK;AAAA,IACpF,CAAC,GAGKsH,IAASlK,EAAS,MAAM;AAC5B,YAAMyE,IAAU1F,GAAQ,QAAQ,OAAO;AAEvC,aACE,OAAO0F,KAAY,YACnBwF,EAAK,UAAU,MACf,GAAG1G,EAAO,KAAK,GAAG0G,EAAK,KAAK,MAAO,IAAIxF,CAAO;AAAA,IAElD,CAAC;sBAIChE,EAAA,GAAAC,EAQM,OARNC,IAQM;AAAA,MAPJC,EAIWC,aAJOsJ,EAAAA,MAAM,CAAA,GAAA;AAAA,QACX,UACT,MAAyD;AAAA,UAAzDtC,EAAyD,QAAzDS,IAAyDnH,EAAhBoC,EAAA,KAAM,GAAA,CAAA;AAAA,QAAA;;;MAInC2G,EAAA,cAAhBlJ,EAAgGH,EAAAuJ,EAAA,GAAA;AAAA;QAAxE,MAAK;AAAA,QAAO,SAAQ;AAAA,QAAQ,aAAavJ,EAAAjB,CAAA,EAAC,wBAAA;AAAA,MAAA;;;;;;4CC1BhE0C,KAAQ;;;;;;;;;;;AAfd,UAAM9C,IAAQC,GAERC,IAAQC,GAAqBF,GAAA,YAAsB,GAEnDK,IAAUP,GAAA,GACVgD,IAAMnB,GAActB,CAAO,GAC3Bf,IAASE,GAAA;AACf,IAAAI,GAAA;AAEA,UAAM,IAAIQ,EAAa,WAAW,GAE5BoD,IAAUC,GAAA,GACVnD,IAAWC,EAAS,MAAMR,EAAM,YAAYT,GAAQ,SAAS,UAAU,EAAI,GAS3EiK,IAAQ3F,EAAI,oBAAI,KAAqB,GACrC4F,IAAQ5F,EAAe,EAAE,GACzBtB,IAAOsB,EAAI,EAAE,GACbO,IAAUP,EAAI,EAAK;AAEzB,QAAIqC;AAEJ,IAAAqB;AAAA,MACE,MAAMhI,GAAQ,QAAQ,OAAO;AAAA,MAC7B,CAACsL,MAAS;AACR,cAAMlB,IAAM,IAAI,IAAIH,EAAM,KAAK;AAE/B,mBAAWsB,KAAOD,KAAQ,MAAQ,IAAIC,EAAI,IAAIA,EAAI,KAAK;AAEvD,QAAAtB,EAAM,QAAQG;AAAA,MAChB;AAAA,MACA,EAAE,WAAW,IAAM,MAAM,GAAA;AAAA,IAAK;AAGhC,UAAMoB,IAAQvK;AAAA,MAAS,MACrBN,EAAM,MAAM,IAAI,CAACkC,OAAQ,EAAE,IAAAA,GAAI,OAAOoH,EAAM,MAAM,IAAIpH,CAAE,KAAK,IAAIA,CAAE,KAAK;AAAA,IAAA,GAGpE3B,IAAUD;AAAA,MAAyB,MACvCiJ,EAAM,MACH,OAAO,CAACqB,MAAQ,CAAC5K,EAAM,MAAM,SAAS4K,EAAI,EAAE,CAAC,EAC7C,IAAI,CAACA,OAAS;AAAA;AAAA;AAAA,QAGb,OAAO,EAAE,sBAAsB,EAAE,OAAOA,EAAI,OAAO,OAAOA,EAAI,gBAAgB;AAAA,QAC9E,OAAOA,EAAI;AAAA,MAAA,EACX;AAAA,IAAA,GAIAE,IAAUxK,EAAS,MAAM;AAC7B,YAAMyK,IAAQ1I,EAAK,MAAM,KAAA;AAEzB,UAAI0I,MAAU,GAAI,QAAO;AAEzB,YAAMC,IAAO,CAAC9F,MAA2BA,EAAM,YAAA,MAAkB6F,EAAM,YAAA;AAEvE,aAAOxB,EAAM,MAAM,KAAK,CAACqB,MAAQI,EAAKJ,EAAI,KAAK,CAAC,IAAI,OAAOG;AAAA,IAC7D,CAAC;AAED,mBAAenB,IAAsB;AACnC,UAAI;AACF,QAAAL,EAAM,QAAQ,MAAM1G,EAAI,KAAKR,EAAK,KAAK;AAEvC,cAAMoH,IAAM,IAAI,IAAIH,EAAM,KAAK;AAE/B,mBAAWsB,KAAOrB,EAAM,MAAO,CAAAE,EAAI,IAAImB,EAAI,IAAIA,EAAI,KAAK;AAExD,QAAAtB,EAAM,QAAQG;AAAA,MAChB,SAAS3D,GAAO;AACd,QAAAC,EAAM,OAAOxC,EAAQuC,CAAK,CAAC;AAAA,MAC7B;AAAA,IACF;AAEA,aAAS9D,EAAO+I,GAAqB;AACnC,MAAA1I,EAAK,QAAQ0I,GAEb,aAAa/E,CAAK,GAClBA,IAAQ,WAAW,MAAA;AAAM,QAAK4D,EAAA;AAAA,SAAQhH,EAAK;AAAA,IAC7C;AAEA,aAASmH,EAAIC,GAAgC;AAC3C,MAAI,OAAOA,KAAW,YAAYhK,EAAM,MAAM,SAASgK,CAAM,MAE7DhK,EAAM,QAAQ,CAAC,GAAGA,EAAM,OAAOgK,CAAM,GACrC3H,EAAK,QAAQ;AAAA,IACf;AAEA,aAAS4H,EAAO/H,GAAkB;AAChC,MAAAlC,EAAM,QAAQA,EAAM,MAAM,OAAO,CAAC0J,MAAQA,MAAQxH,CAAE;AAAA,IACtD;AAEA,mBAAe+I,IAAwB;AACrC,YAAM/F,IAAQ4F,EAAQ;AAEtB,UAAI,EAAA5F,MAAU,QAAQhB,EAAQ,QAE9B;AAAA,QAAAA,EAAQ,QAAQ;AAEhB,YAAI;AACF,gBAAM0G,IAAM,MAAM/H,EAAI,UAAU,EAAE,OAAAqC,GAAO;AAEzC,UAAAoE,EAAM,QAAQ,IAAI,IAAIA,EAAM,KAAK,EAAE,IAAIsB,EAAI,IAAIA,EAAI,KAAK,GACxDrB,EAAM,QAAQ,CAACqB,GAAK,GAAGrB,EAAM,KAAK,GAClCQ,EAAIa,EAAI,EAAE;AAAA,QACZ,SAAS9E,GAAO;AACd,UAAAC,EAAM,OAAOxC,EAAQuC,CAAK,CAAC;AAAA,QAC7B,UAAA;AACE,UAAA5B,EAAQ,QAAQ;AAAA,QAClB;AAAA;AAAA,IACF;AAGA,WAAAmD;AAAA,MACE,MAAMhI,GAAQ,QAAQ,OAAO;AAAA,MAC7B,MAAA;AAAM,QAAKuK,EAAA;AAAA;AAAA,MACX,EAAE,WAAW,GAAA;AAAA,IAAK,GAGpBrC,GAAgB,MAAM,aAAavB,CAAK,CAAC,cAIvCjF,EAAA,GAAAC,EA+CM,OA/CNC,IA+CM;AAAA,MA9CO4J,EAAA,MAAM,SAAM,KAAvB9J,KAAAC,EAgBM,OAhBN4H,IAgBM;AAAA,gBAVJ5H,EASW2G,IAAA,MAAAmB,GARM+B,EAAA,OAAK,CAAbK,YADT5J,EASWH,EAAA8G,EAAA,GAAA;AAAA,UAPR,KAAKiD,EAAK;AAAA,UACX,MAAK;AAAA,UACJ,WAAW7K,EAAA;AAAA,UACX,eAAac,EAAA,CAAA,EAAC,oBAAA;AAAA,UACd,SAAK,CAAAE,MAAE4I,EAAOiB,EAAK,EAAE;AAAA,QAAA;qBAEtB,MAAgB;AAAA,YAAb1J,EAAAC,EAAAyJ,EAAK,KAAK,GAAA,CAAA;AAAA,UAAA;;;kBAGjB5J,EAA8EH,EAAA+H,EAAA,GAAA;AAAA;QAA9D,MAAK;AAAA,QAAK,MAAK;AAAA,MAAA;mBAAQ,MAA6B;AAAA,cAA1B/H,EAAA,CAAA,EAAC,oBAAA,CAAA,GAAA,CAAA;AAAA,QAAA;;;MAE3CgH,EA0BM,OA1BNY,IA0BM;AAAA,QAzBJ7H,EAWEC,EAAAC,EAAA,GAAA;AAAA,UAVC,eAAa;AAAA,UACb,SAASb,EAAA;AAAA,UACT,aAAaY,EAAA,CAAA,EAAC,iBAAA;AAAA,UACd,cAAYA,EAAA,CAAA,EAAC,qBAAA;AAAA,UACb,UAAUd,EAAA;AAAA,UACX,YAAA;AAAA,UACA,UAAA;AAAA,UACC,cAAYc,EAAA,CAAA,EAAC,iBAAA;AAAA,UACb,UAAQa;AAAA,UACR,uBAAoB+H;AAAA,QAAA;QAMfe,EAAA,cADRxJ,EASYH,EAAAoH,EAAA,GAAA;AAAA;UAPV,SAAQ;AAAA,UACR,MAAK;AAAA,UACJ,SAASrE,EAAA;AAAA,UACT,UAAU7D,EAAA;AAAA,UACV,SAAO4K;AAAA,QAAA;qBAER,MAAiD;AAAA,YAA9CzJ,EAAAC,EAAAN,EAAA,CAAA,iCAAiC2J,EAAA,MAAA,CAAO,CAAA,GAAA,CAAA;AAAA,UAAA;;;;;;;;;;ACpLnD,UAAM1K,IAAUP,GAAA,GACVgD,IAAMnB,GAActB,CAAO,GAC3Bf,IAASE,GAAA;AACf,IAAAI,GAAA;AAEA,UAAMO,IAAIC,EAAa,WAAW,GAE5BoD,IAAUC,GAAA,GAEVU,IAAUP,EAAI,EAAK,GAGnB8E,IAAOnI,EAAS,MAAM;AAC1B,YAAM4G,IAAS7H,GAAQ,QAAQ,OAAO;AAEtC,aAAO6H,MAAW,eAAeA,MAAW,cAAcA,MAAW;AAAA,IACvE,CAAC,GAEK7G,IAAWC,EAAS,MAAMjB,GAAQ,SAAS,UAAU,MAAQ6E,EAAQ,KAAK;AAMhF,mBAAe8C,IAA2B;AACxC,YAAMzB,IAAMlG,GAAQ,QAAQ;AAa5B,UAXI,GAACkG,KAWD,CATW,MAAMoB,GAAQ;AAAA,QAC3B,OAAOzG,EAAE,2BAA2B,EAAE,OAAOqF,EAAI,OAAO;AAAA,QACxD,SACEA,EAAI,WAAW,cAAcrF,EAAE,2BAA2B,IAAIA,EAAE,wBAAwB;AAAA,QAC1F,aAAaA,EAAE,iBAAiB;AAAA,QAChC,YAAYA,EAAE,cAAc;AAAA,QAC5B,MAAM;AAAA,MAAA,CACP,IAID;AAAA,QAAAgE,EAAQ,QAAQ;AAEhB,YAAI;AACF,gBAAMrB,EAAI,UAAU0C,EAAI,EAAE,GAC1B,MAAMlG,GAAQ,OAAA,GACd0G,EAAM,QAAQ7F,EAAE,wBAAwB,CAAC;AAAA,QAC3C,SAAS4F,GAAO;AACd,UAAAC,EAAM,OAAOxC,EAAQuC,CAAK,CAAC;AAAA,QAC7B,UAAA;AACE,UAAA5B,EAAQ,QAAQ;AAAA,QAClB;AAAA;AAAA,IACF;qBAKUuE,EAAA,SAAQtH,EAAA9B,CAAA,GAAQ,kBADxBiC,EAeeH,EAAAgK,EAAA,GAAA;AAAA;MAbb,OAAM;AAAA,MACL,MAAMhK,EAAAjB,CAAA,EAAC,wBAAA;AAAA,IAAA;iBAER,MASY;AAAA,QATZgB,EASYC,EAAAoH,EAAA,GAAA;AAAA,UARV,SAAQ;AAAA,UACR,MAAK;AAAA,UACL,MAAK;AAAA,UACJ,SAASrE,EAAA;AAAA,UACT,UAAU7D,EAAA;AAAA,UACV,SAAO2G;AAAA,QAAA;qBAER,MAA0B;AAAA,gBAAvB7F,EAAAjB,CAAA,EAAC,iBAAA,CAAA,GAAA,CAAA;AAAA,UAAA;;;;;;;;;;AC3EV,UAAM,EAAE,MAAAkL,GAAM,SAAAC,GAAS,SAAAC,EAAA,IAAYC,GAAA,GAE7BnL,IAAUP,GAAA,GACVgD,IAAMnB,GAActB,CAAO;AACjC,IAAAT,GAAA;AAEA,UAAM,IAAIQ,EAAa,WAAW,GAE5B+E,IAAQvB,EAAI,EAAE,GACdM,IAASN,EAAI,EAAK,GAClBQ,IAASR,EAA8B,EAAE,GACzC6H,IAAQC,GAA4B,OAAO;AAEjD,IAAAnE,GAAU,YAAY;AACpB,YAAMoE,GAAA,GACNF,EAAM,OAAO,cAAc,OAAO,GAAG,MAAA;AAAA,IACvC,CAAC;AAED,mBAAeG,IAAwB;AACrC,UAAI,EAAAzG,EAAM,MAAM,KAAA,MAAW,MAAMjB,EAAO,QAExC;AAAA,QAAAA,EAAO,QAAQ,IACfE,EAAO,QAAQ,CAAA;AAEf,YAAI;AACF,UAAAkH,EAAQ,MAAMxI,EAAI,OAAO,EAAE,OAAOqC,EAAM,MAAM,KAAA,EAAK,CAAG,CAAC;AAAA,QACzD,SAASY,GAAO;AAGd,UAAA3B,EAAO,QAAS2B,EAA2D,MAAM,UAAU,CAAA;AAAA,QAC7F,UAAA;AACE,UAAA7B,EAAO,QAAQ;AAAA,QACjB;AAAA;AAAA,IACF;2BAIE3C,EAmBYH,EAAAyK,EAAA,GAAA;AAAA,MAnBO,MAAMzK,EAAAiK,CAAA;AAAA,sDAAAA,EAAI,QAAA/J,IAAA;AAAA,MAAG,OAAOF,EAAA,CAAA,EAAC,iBAAA;AAAA,MAAsB,OAAO;AAAA,IAAA;MAWxD,UACT,MAKW;AAAA,QALXD,EAKWC,EAAA0K,EAAA,GAAA,EALD,MAAK,QAAI;AAAA,qBACjB,MAAmF;AAAA,YAAnF3K,EAAmFC,EAAAoH,EAAA,GAAA;AAAA,cAAxE,SAAQ;AAAA,cAAW,gCAAOpH,EAAAmK,CAAA,EAAA;AAAA,YAAO;yBAAI,MAAuB;AAAA,oBAApBnK,EAAA,CAAA,EAAC,cAAA,CAAA,GAAA,CAAA;AAAA,cAAA;;;YACpDD,EAEYC,EAAAoH,EAAA,GAAA;AAAA,cAFD,MAAK;AAAA,cAAW,SAAStE,EAAA;AAAA,cAAS,UAAUiB,EAAA,MAAM,KAAA,MAAI;AAAA,cAAY,SAAOyG;AAAA,YAAA;yBAClF,MAAuB;AAAA,oBAApBxK,EAAA,CAAA,EAAC,cAAA,CAAA,GAAA,CAAA;AAAA,cAAA;;;;;;;iBAdV,MAQe;AAAA,QARfD,EAQeC,EAAAgK,EAAA,GAAA;AAAA,UAPZ,OAAOhK,EAAA,CAAA,EAAC,mBAAA;AAAA,UACR,OAAOgD,EAAA,MAAO,QAAK,CAAA,KAASA,EAAA,MAAO,OAAI,CAAA;AAAA,UACxC,UAAA;AAAA,QAAA;qBAEA,MAEM;AAAA,YAFNgE,EAEM,OAAA;AAAA,uBAFG;AAAA,cAAJ,KAAIqD;AAAA,YAAA;cACPtK,EAAuFC,EAAA2K,EAAA,GAAA;AAAA,4BAApE5G,EAAA;AAAA,8DAAAA,EAAK,QAAA7D;AAAA,gBAAG,cAAYF,EAAA,CAAA,EAAC,mBAAA;AAAA,gBAAwB,YAAawK,GAAM,CAAA,OAAA,CAAA;AAAA,cAAA;;;;;;;;;2PCHrFI,KAAQ;;;;;;AAHd,UAAMjM,IAAQC,GAKRK,IAAUP,GAAA,GACVgD,IAAMnB,GAActB,CAAO,GAC3B0C,IAAQC,GAAA,GACRC,IAASC,GAAA;AACf,IAAAtD,GAAA;AAEA,UAAM,IAAIQ,EAAa,WAAW,GAE5BwB,IAAQxB,EAAa,YAAY,GAEjCoD,IAAUC,GAAA,GAEVwI,IAAOP,GAA4B,MAAM,GACzCQ,IAAQC,GAAgBF,CAAI,GAE5BlC,IAAOnG,EAAyB,IAAI,GACpCK,IAAUL,EAAI,EAAI,GAElBsH,IAASkB,GAA+CC,EAAmB,GAE3E9H,IAAYhE,EAAS,MAAMF,EAAQ,IAAI,sBAAsB,CAAC,GAE9D8E,IAAQ5E;AAAA,MACZ,MACEF,EAAQ,MAAM,UAAU,QAAQ,KAAK,CAACM,MAAWA,EAAO,OAAO,UAAU,GAAG,SAC5E,EAAE,iBAAiB;AAAA,IAAA,GASjB2L,IAAO/L,EAAmB;AAAA,MAC9B,KAAK,MAAO,OAAOwC,EAAM,MAAM,QAAS,WAAWA,EAAM,MAAM,OAAO;AAAA,MACtE,KAAK,CAAC9C,MAAU;AAEd,QAAKgD,EAAO,QAAQ;AAAA,UAClB,OAAO,EAAE,GAAGF,EAAM,OAAO,MAAM9C,MAAU,KAAK,SAAY,OAAOA,CAAK,GAAG,MAAM,OAAA;AAAA,QAAU,CAC1F;AAAA,MACH;AAAA,IAAA,CACD,GAEKsM,IAAQhM,EAAS,MAAM+L,EAAK,UAAU,SAAS,GAE/CtK,IAAQzB,EAAS,OAAO;AAAA,MAC5B,GAAG,OAAOwC,EAAM,MAAM,KAAM,WAAWA,EAAM,MAAM,IAAI;AAAA,MACvD,QAAQV,EAAOU,EAAM,MAAM,MAAM;AAAA,MACjC,KAAKV,EAAOU,EAAM,MAAM,GAAG;AAAA,MAC3B,QAAQV,EAAOU,EAAM,MAAM,MAAM;AAAA,MACjC,MAAM,OAAOA,EAAM,MAAM,QAAS,WAAWA,EAAM,MAAM,OAAO;AAAA,MAChE,MAAM,OAAOA,EAAM,MAAM,QAAQ,CAAC,KAAK;AAAA,IAAA,EACvC;AAEF,aAASV,EAAOpC,GAA+B;AAC7C,aAAO,OAAOA,KAAU,YAAYA,MAAU,MAAK,OAAOA,CAAK,KAAK;AAAA,IACtE;AAGA,UAAMuM,IAAQjM,EAAoB,MAAM;AAAA,MACtC,EAAE,OAAO,IAAI,OAAO,EAAE,gBAAgB,EAAA;AAAA,MACtC,EAAE,OAAO,aAAa,OAAO,EAAE,sBAAsB,EAAA;AAAA,MACrD,EAAE,OAAO,aAAa,OAAO,EAAE,sBAAsB,EAAA;AAAA,MACrD,EAAE,OAAO,SAAS,OAAO,EAAE,kBAAkB,EAAA;AAAA,MAC7C,EAAE,OAAO,eAAe,OAAO,EAAE,wBAAwB,EAAA;AAAA,MACzD,EAAE,OAAO,WAAW,OAAO,EAAE,WAAW,GAAG,MAAM,QAAA;AAAA,IAAQ,CAC1D,GAEKkM,IAAUlM,EAAS,MAAMwJ,EAAK,OAAO,WAAW,EAAE,SAAS,CAAA,GAAI,MAAM,CAAA,GAAI,SAAS,CAAA,GAAI,GAEtF2C,IAAUnM,EAAS,MAAM2L,EAAM,QAAQ,KAAKA,EAAM,QAAQF,EAAK,GAW/DW,IAAUpM,EAAoC,MAC9CmM,EAAQ,QACH,CAAC,EAAE,KAAK,QAAQ,OAAO,IAAI,IAG7B;AAAA,MACL,EAAE,KAAK,SAAS,OAAO,IAAI,OAAO,GAAA;AAAA;AAAA,MAElC,EAAE,KAAK,SAAS,OAAO,EAAE,oBAAoB,GAAG,UAAU,IAAA;AAAA,MAC1D,EAAE,KAAK,WAAW,OAAO,EAAE,sBAAsB,GAAG,OAAO,KAAK,WAAW,IAAA;AAAA,MAC3E,EAAE,KAAK,UAAU,OAAO,EAAE,qBAAqB,GAAG,OAAO,KAAK,WAAW,KAAA;AAAA,MACzE;AAAA,QACE,KAAK;AAAA,QACL,OAAOH,EAAM,QAAQ,EAAE,sBAAsB,IAAI,EAAE,mBAAmB;AAAA,QACtE,OAAO;AAAA,QACP,WAAW;AAAA;AAAA;AAAA,QAGX,WAAW;AAAA,MAAA;AAAA,MAEb,EAAE,KAAK,UAAU,OAAO,EAAE,qBAAqB,GAAG,OAAO,IAAA;AAAA,MACzD,EAAE,KAAK,WAAW,OAAO,IAAI,OAAOK,IAAc,OAAO,QAAA;AAAA,IAAQ,CAEpE,GAEKC,IAAYtM,EAAS,MACrByB,EAAM,MAAM,MAAM,KAAW,EAAE,oBAAoB,IAEhDuK,EAAM,QAAQ,EAAE,iBAAiB,IAAI,EAAE,aAAa,CAC5D,GASKO,IAAUvM,EAA0B,MAAM;AAC9C,YAAMuK,IAAyB,CAAA,GAEzBiC,IAASN,EAAQ,MAAM,QAAQ,KAAK,CAACpC,OAASA,GAAK,OAAOrI,EAAM,MAAM,MAAM;AAClF,MAAI+K,KACFjC,EAAM,KAAK;AAAA,QACT,KAAK;AAAA,QACL,OAAO,GAAG,EAAE,qBAAqB,CAAC,KAAKiC,EAAO,KAAK;AAAA,QACnD,OAAO,MAAMC,GAAO,UAAU,MAAS;AAAA,MAAA,CACxC;AAGH,YAAMnC,IAAM4B,EAAQ,MAAM,KAAK,KAAK,CAACpC,OAASA,GAAK,OAAOrI,EAAM,MAAM,GAAG;AACzE,MAAI6I,KACFC,EAAM,KAAK;AAAA,QACT,KAAK;AAAA,QACL,OAAO,GAAG,EAAE,kBAAkB,CAAC,KAAKD,EAAI,KAAK;AAAA,QAC7C,OAAO,MAAMmC,GAAO,OAAO,MAAS;AAAA,MAAA,CACrC;AAGH,YAAMvM,KAASgM,EAAQ,MAAM,QAAQ,KAAK,CAACpC,OAASA,GAAK,OAAOrI,EAAM,MAAM,MAAM;AAClF,aAAIvB,MACFqK,EAAM,KAAK;AAAA,QACT,KAAK;AAAA,QACL,OAAO,GAAG,EAAE,qBAAqB,CAAC,KAAKrK,GAAO,KAAK;AAAA,QACnD,OAAO,MAAMuM,GAAO,UAAU,MAAS;AAAA,MAAA,CACxC,GAGIlC;AAAA,IACT,CAAC;AAGD,aAASmC,KAAqB;AAC5B,MAAKhK,EAAO,QAAQ;AAAA,QAClB,OAAO;AAAA,UACL,GAAGF,EAAM;AAAA,UACT,QAAQ;AAAA,UACR,KAAK;AAAA,UACL,QAAQ;AAAA,UACR,MAAM;AAAA,QAAA;AAAA,MACR,CACD;AAAA,IACH;AAEA,mBAAe4C,EAAKT,GAAmC;AACrD,MAAAjB,EAAQ,QAAQ;AAEhB,UAAI;AACF,QAAA8F,EAAK,QAAQ,MAAMjH,EAAI,SAAS;AAAA,UAC9B,GAAGoC,GAAO,UAAUlD,EAAM,MAAM;AAAA,UAChC,QAAQuK,EAAM,QAAQ,KAAMD,EAAK;AAAA,UACjC,SAASC,EAAM;AAAA,UACf,QAAQvK,EAAM,MAAM;AAAA,UACpB,KAAKA,EAAM,MAAM;AAAA,UACjB,QAAQA,EAAM,MAAM;AAAA,UACpB,MAAMkD,GAAO,OAAO,GAAGA,EAAM,KAAK,UAAU,SAAS,MAAM,EAAE,GAAGA,EAAM,KAAK,GAAG,KAAK;AAAA,UACnF,MAAMA,GAAO,QAAQlD,EAAM,MAAM;AAAA,UACjC,UAAUkD,GAAO;AAAA,QAAA,CAClB;AAAA,MACH,SAASa,GAAO;AACd,QAAAC,EAAM,OAAOxC,EAAQuC,CAAK,CAAC;AAAA,MAC7B,UAAA;AACE,QAAA9B,EAAQ,QAAQ;AAAA,MAClB;AAAA,IACF;AAOA,aAASiJ,GAAQhI,GAAyB;AACxC,YAAMiI,IAA2B;AAAA,QAC/B,GAAGpK,EAAM;AAAA,QACT,GAAGmC,EAAM,WAAW,KAAK,SAAYA,EAAM;AAAA,QAC3C,MACEA,EAAM,SAAS,OACX,SACA,GAAGA,EAAM,KAAK,UAAU,SAAS,MAAM,EAAE,GAAGA,EAAM,KAAK,GAAG;AAAA,QAChE,MAAMA,EAAM,SAAS,IAAI,SAAY,OAAOA,EAAM,IAAI;AAAA,MAAA;AAGxD,MAAKjC,EAAO,QAAQ,EAAE,OAAOkK,GAAQ,GAChCxH,EAAKT,CAAK;AAAA,IACjB;AAGA,aAAS8H,GAAOI,GAAmCnN,GAAsB;AACvE,MAAKgD,EAAO,QAAQ;AAAA,QAClB,OAAO;AAAA,UACL,GAAGF,EAAM;AAAA,UACT,CAACqK,CAAI,GAAG,OAAOnN,KAAU,WAAW,OAAOA,CAAK,IAAI;AAAA,UACpD,MAAM;AAAA,QAAA;AAAA,MACR,CACD;AAAA,IACH;AAKA,IAAAqH;AAAA,MACE,CAACgF,GAAM,MAAMtK,EAAM,MAAM,QAAQ,MAAMA,EAAM,MAAM,KAAK,MAAMA,EAAM,MAAM,MAAM;AAAA,MAChF,MAAA;AAAM,QAAK2D,EAAA;AAAA;AAAA,IAAK,GAGbA,EAAA;AASL,aAAS0F,GAAK1H,GAA2B;AACvC,YAAM0J,IAAO,GAAGtN,EAAM,IAAI,aAAa4D,EAAQ,EAAE;AAEjD,MAAIV,EAAO,QAAQoK,CAAI,EAAE,QAAQ,SAAS,KAAQpK,EAAO,KAAK,EAAE,MAAAoK,GAAM,OAAO,EAAE,GAAGtK,EAAM,MAAA,GAAS;AAAA,IACnG;AAGA,mBAAeiH,IAAqB;AAClC,YAAMrG,IAAU,MAAMuH,EAAO,EAAE;AAE/B,MAAKvH,MAEL,MAAMgC,EAAA,GACN0F,GAAK1H,CAAO;AAAA,IACd;AAEA,aAAS2J,GAAW3J,GAAkC;AACpD,UAAI,CAACY,EAAU,MAAO,QAAO,CAAA;AAE7B,UAAIgI,EAAM;AACR,eAAO;AAAA,UACL;AAAA,YACE,KAAK;AAAA,YACL,MAAM;AAAA,YACN,OAAO,EAAE,eAAe;AAAA,YACxB,KAAK,MAAA;AAAM,cAAK5D,GAAQhF,CAAO;AAAA;AAAA,UAAA;AAAA,QACjC;AAIJ,YAAM+D,IAAuB;AAAA,QAC3B,EAAE,KAAK,QAAQ,MAAM,QAAQ,OAAO,EAAE,YAAY,GAAG,KAAK,MAAM2D,GAAK1H,CAAO,EAAA;AAAA,MAAE;AAGhF,aAAIA,EAAQ,OAAOA,EAAQ,WAAW,WACpC+D,EAAQ,KAAK;AAAA,QACX,KAAK;AAAA,QACL,MAAM;AAAA,QACN,OAAO,EAAE,oBAAoB;AAAA,QAC7B,MAAM/D,EAAQ;AAAA,MAAA,CACf,GAGCA,EAAQ,SAAS,QACnB+D,EAAQ,KAAK;AAAA,QACX,KAAK;AAAA,QACL,MAAM;AAAA,QACN,OAAO,EAAE,oBAAoB;AAAA,QAC7B,KAAK,MAAA;AAAM,UAAK6F,GAAY5J,CAAO;AAAA;AAAA,MAAA,CACpC,GAGH+D,EAAQ,KAAK;AAAA,QACX,KAAK;AAAA,QACL,MAAM;AAAA,QACN,OAAO/D,EAAQ,SAAS,EAAE,aAAa,IAAI,EAAE,WAAW;AAAA,QACxD,KAAK,MAAA;AAAM,UAAK6J,EAAU7J,GAAS,CAACA,EAAQ,MAAM;AAAA;AAAA,MAAA,CACnD,GAED+D,EAAQ;AAAA,QACN/D,EAAQ,WAAW,eACjBA,EAAQ,WAAW,cACnBA,EAAQ,WAAW,cACjB;AAAA,UACE,KAAK;AAAA,UACL,MAAM;AAAA,UACN,OAAO,EAAE,iBAAiB;AAAA,UAC1B,KAAK,MAAA;AAAM,YAAKsD,EAAUtD,CAAO;AAAA;AAAA,QAAA,IAEnC;AAAA,UACE,KAAK;AAAA,UACL,MAAM;AAAA,UACN,OAAO,EAAE,eAAe;AAAA,UACxB,KAAK,MAAA;AAAM,YAAKkD,EAAQlD,CAAO;AAAA;AAAA,QAAA;AAAA,MACjC,GAGN+D,EAAQ,KAAK;AAAA,QACX,KAAK;AAAA,QACL,MAAM;AAAA,QACN,OAAO,EAAE,cAAc;AAAA,QACvB,QAAQ;AAAA,QACR,KAAK,MAAA;AAAM,UAAKwC,GAAOvG,CAAO;AAAA;AAAA,MAAA,CAC/B,GAEM+D;AAAAA,IACT;AAEA,mBAAeb,EAAQlD,GAAoC;AACzD,YAAM8J,GAAI,MAAM3K,EAAI,QAAQa,EAAQ,EAAE,GAAG,EAAE,iBAAiB,CAAC;AAAA,IAC/D;AAEA,mBAAesD,EAAUtD,GAAoC;AAC3D,YAAM8J,GAAI,MAAM3K,EAAI,UAAUa,EAAQ,EAAE,GAAG,EAAE,wBAAwB,CAAC;AAAA,IACxE;AAEA,mBAAe6J,EAAU7J,GAAqB+J,GAAgC;AAC5E,YAAMD;AAAA,QACJ,MAAM3K,EAAI,KAAKa,EAAQ,IAAI,EAAE,QAAQ,EAAE,QAAA+J,KAAU,UAAU/J,EAAQ,UAAU;AAAA,QACpE,EAAT+J,IAAW,sBAAyB,gBAAN;AAAA,MAAsB;AAAA,IAExD;AAEA,mBAAexD,GAAOvG,GAAoC;AASxD,MARe,MAAMiD,GAAQ;AAAA,QAC3B,OAAO,EAAE,sBAAsB,EAAE,OAAOjD,EAAQ,OAAO;AAAA,QACvD,SAAS,EAAE,mBAAmB;AAAA,QAC9B,aAAa,EAAE,cAAc;AAAA,QAC7B,YAAY,EAAE,cAAc;AAAA,QAC5B,MAAM;AAAA,MAAA,CACP,KAID,MAAM8J,GAAI,MAAM3K,EAAI,OAAOa,EAAQ,EAAE,GAAG,EAAE,eAAe,CAAC;AAAA,IAC5D;AAEA,mBAAegF,GAAQhF,GAAoC;AACzD,YAAM8J,GAAI,MAAM3K,EAAI,QAAQa,EAAQ,EAAE,GAAG,EAAE,gBAAgB,CAAC;AAAA,IAC9D;AAGA,mBAAe8J,GAAIE,GAAiCC,GAA6B;AAC/E,UAAI;AACF,cAAMD,EAAA,GACN3H,EAAM,QAAQ4H,CAAI;AAAA,MACpB,SAAS7H,GAAO;AACd,QAAAC,EAAM,OAAOxC,EAAQuC,CAAK,CAAC;AAAA,MAC7B,UAAA;AAGE,cAAMJ,EAAA;AAAA,MACR;AAAA,IACF;AAEA,mBAAe4H,GAAY5J,GAAoC;AAC7D,UAAKA,EAAQ;AAEb,YAAI;AACF,gBAAM,UAAU,UAAU,UAAUA,EAAQ,GAAG,GAC/CqC,EAAM,QAAQ,EAAE,sBAAsB,CAAC;AAAA,QACzC,QAAQ;AAAA,QAGR;AAAA,IACF;AAOA,aAASkB,EAAMC,GAAqE;AAClF,aAAIA,MAAW,eAAeA,MAAW,aAAmB,YACxDA,MAAW,cAAoB,YAE5B;AAAA,IACT;AAGA,aAASH,EAAQrD,GAA6B;AAC5C,aAAI4I,EAAM,QAAc,MAEjB5I,EAAQ,SAAS,OAAO,EAAE,kBAAkB,IAAI,IAAIA,EAAQ,IAAI;AAAA,IACzE;AAGA,aAASkK,EAAOlK,GAAoC;AAClD,aAAO4I,EAAM,QAAQ5I,EAAQ,aAAaA,EAAQ;AAAA,IACpD;AAGA,UAAM+D,KAAUnH;AAAA,MAAyB,MACvCgE,EAAU,QACN,CAAC,EAAE,KAAK,OAAO,OAAO,EAAE,WAAW,GAAG,MAAM,QAAQ,SAAS,IAAM,KAAK;AAAM,QAAKyF,EAAA;AAAA,QAAI,CAAG,IAC1F,CAAA;AAAA,IAAC;2BAKL/I,EAsLM,OAAA;AAAA,eAtLG;AAAA,MAAJ,KAAIgL;AAAA,MAAO,OAAM;AAAA,IAAA;MACpB9K,EAoLiBC,EAAA0M,EAAA,GAAA;AAAA,QApLO,MAAMxB,EAAA;AAAA,gDAAAA,EAAI,QAAAhL;AAAA,QAAG,OAAO6D,EAAA;AAAA,QAAQ,OAAOqH,EAAA;AAAA,QAAQ,SAAS9E,GAAA;AAAA,MAAA;mBAC1E,MAkLW;AAAA,UAlLXvG,EAkLWC,EAAA2M,EAAA,GAAA;AAAA,YAjLR,MAAMhE,EAAA;AAAA,YACN,SAAS4C,EAAA;AAAA,YACV,WAAQ;AAAA,YACR,YAAA;AAAA,YACC,YAAYJ,EAAA;AAAA,YACZ,QAAQA,EAAA;AAAA,YACT,OAAA;AAAA,YACC,SAAStI,EAAA;AAAA,YACV,QAAO;AAAA,YACN,eAAa+H;AAAA,YACb,iBAAec,EAAA,MAAQ;AAAA,YACvB,iBAAe1L,EAAAQ,CAAA,EAAK,eAAA;AAAA,YACpB,sBAAoBR,EAAA,CAAA,EAAC,cAAA;AAAA,YACrB,cAAYyL,EAAA;AAAA,YACZ,cAAY1H,EAAA;AAAA,YACZ,YAAWkG;AAAA,YACX,eAAc6B;AAAA,UAAA;YAQJ,WACT,MASe;AAAA,cATf/L,EASeC,EAAAgK,EAAA,GAAA;AAAA,gBATA,OAAOhK,EAAA,CAAA,EAAC,qBAAA;AAAA,cAAA;2BACrB,MAOE;AAAA,kBAPFD,EAOEC,EAAAC,EAAA,GAAA;AAAA,oBANC,eAAaW,EAAA,MAAM;AAAA,oBACnB,SAASyK,EAAA,MAAQ,QAAQ,IAAG,CAAEpC,OAAI,EAAA,OAAeA,EAAK,IAAE,OAASA,EAAK,QAAK;AAAA,oBAC3E,aAAajJ,EAAA,CAAA,EAAC,kBAAA;AAAA,oBACf,WAAA;AAAA,oBACA,MAAK;AAAA,oBACJ,uBAAkBiI,EAAA,CAAA,MAAAA,EAAA,CAAA,IAAA,CAAGpJ,MAAmB+M,aAAiB/M,CAAK;AAAA,kBAAA;;;;cAInEkB,EAUeC,EAAAgK,EAAA,GAAA;AAAA,gBAVA,OAAOhK,EAAA,CAAA,EAAC,kBAAA;AAAA,cAAA;2BACrB,MAQE;AAAA,kBARFD,EAQEC,EAAAC,EAAA,GAAA;AAAA,oBAPC,eAAaW,EAAA,MAAM;AAAA,oBACnB,SAASyK,EAAA,MAAQ,KAAK,IAAG,CAAEpC,OAAI,EAAA,OAAeA,EAAK,IAAE,OAASA,EAAK,QAAK;AAAA,oBACxE,aAAajJ,EAAA,CAAA,EAAC,eAAA;AAAA,oBACf,WAAA;AAAA,oBACA,YAAA;AAAA,oBACA,MAAK;AAAA,oBACJ,uBAAkBiI,EAAA,CAAA,MAAAA,EAAA,CAAA,IAAA,CAAGpJ,MAAmB+M,UAAc/M,CAAK;AAAA,kBAAA;;;;cAIhEkB,EASeC,EAAAgK,EAAA,GAAA;AAAA,gBATA,OAAOhK,EAAA,CAAA,EAAC,qBAAA;AAAA,cAAA;2BACrB,MAOE;AAAA,kBAPFD,EAOEC,EAAAC,EAAA,GAAA;AAAA,oBANC,eAAaW,EAAA,MAAM;AAAA,oBACnB,SAASyK,EAAA,MAAQ,QAAQ,IAAG,CAAEpC,OAAI,EAAA,OAAeA,EAAK,IAAE,OAASA,EAAK,QAAK;AAAA,oBAC3E,aAAajJ,EAAA,CAAA,EAAC,kBAAA;AAAA,oBACf,WAAA;AAAA,oBACA,MAAK;AAAA,oBACJ,uBAAkBiI,EAAA,CAAA,MAAAA,EAAA,CAAA,IAAA,CAAGpJ,MAAmB+M,aAAiB/M,CAAK;AAAA,kBAAA;;;;cAOlD6M,EAAA,MAAQ,SAAM,UAA/BvL,EAGYH,EAAAoH,EAAA,GAAA;AAAA;gBAHyB,SAAQ;AAAA,gBAAO,MAAK;AAAA,gBAAK,OAAA;AAAA,gBAAO,SAAOyE;AAAA,cAAA;gBAC/D,QAAK,MAAwB;AAAA,kBAAxB9L,EAAwBC,EAAA4M,EAAA,GAAA,EAAf,MAAK,SAAO;AAAA,gBAAA;2BAAc,MACnD;AAAA,kBADmDvM,EAAA,QAChDL,EAAAQ,CAAA,EAAK,eAAA,CAAA,GAAA,CAAA;AAAA,gBAAA;;;;YAID,WACT,MAAsC;AAAA,cAAtCT,EAAsCC,EAAA6M,EAAA,GAAA,EAApB,SAASnB,EAAA,SAAO,MAAA,GAAA,CAAA,SAAA,CAAA;AAAA,YAAA;YAGzB,cAAU1C,EACnB,CAGO,EAJgB,KAAA5E,QAAG;AAAA,cAC1B4C,EAGO,QAAA;AAAA,gBAHD,OAAK8F,GAAA,CAAC,sBAAoB,EAAA,YAAA,CAAwB1I,EAAI,OAAK,CAAA;AAAA,cAAA;gBACpDA,EAAI,OAAO,cAAtBvE,EAA4E,OAAA;AAAA;kBAA9C,KAAKuE,EAAI,MAAM;AAAA,kBAAO,KAAI;AAAA,kBAAG,SAAQ;AAAA,gBAAA,yBACnEjE,EAAyCH,EAAA4M,EAAA,GAAA;AAAA;kBAAzB,MAAK;AAAA,kBAAQ,MAAK;AAAA,gBAAA;;;YAI3B,cAAU5D,EACnB,CAcM,EAfiB,KAAA5E,QAAG;AAAA,cAC1B4C,EAcM,OAdNS,IAcM;AAAA,gBAbcrD,EAAI,eAAtBjE,EAEaH,EAAA+M,EAAA,GAAA;AAAA;kBAFkB,SAAS/M,EAAA,CAAA,EAAC,cAAA;AAAA,gBAAA;6BACvC,MAA0D;AAAA,oBAA1DD,EAA0DC,EAAA4M,EAAA,GAAA;AAAA,sBAAjD,OAAM;AAAA,sBAAmB,MAAK;AAAA,sBAAO,MAAK;AAAA,oBAAA;;;;gBAErD5F,EASM,OATNY,IASM;AAAA,kBARJZ,EAAuD,QAAvDa,IAAuDvH,EAAnB8D,EAAI,KAAK,GAAA,CAAA;AAAA,kBAI7C4C,EAGO,QAHPgG,IAGO;AAAA,wBAFFpH,EAAQxB,CAAG,CAAA,IAAI,KAClB,CAAA;AAAA,oBAAwB+G,EAAA,0BAAxBhL,EAA8DH,EAAAiN,EAAA,GAAA;AAAA;sBAA9B,QAAQ7I,EAAI;AAAA,oBAAA;;;;;YAMzC,gBAAY4E,EACrB,CAIO,EALkB,KAAA5E,QAAG;AAAA,cAC5B4C,EAIO,QAJPkG,IAIO;AAAA,iBAHLtN,EAAA,EAAA,GAAAC,EAEW2G,IAAA,MAAAmB,GAFgBvD,EAAI,UAAduH,aAAjBxL,EAEWH,EAAA8G,EAAA,GAAA;AAAA,kBAF8B,KAAK6E,GAAO;AAAA,kBAAI,MAAK;AAAA,kBAAK,OAAA;AAAA,gBAAA;6BACjE,MAAkB;AAAA,oBAAftL,EAAAC,EAAAqL,GAAO,KAAK,GAAA,CAAA;AAAA,kBAAA;;;;;YAKV,eAAW3C,EACpB,CAEU,EAHc,KAAA5E,QAAG;AAAA,cAC3BrE,EAEUC,EAAA+H,EAAA,GAAA;AAAA,gBAFD,MAAK;AAAA,gBAAK,MAAK;AAAA,gBAAQ,UAAA;AAAA,cAAA;2BAC9B,MAAwC;AAAA,kBAArC1H,EAAAC,EAAA8D,EAAI,SAASA,EAAI,OAAO,OAAI,GAAA,GAAA,CAAA;AAAA,gBAAA;;;;YAIxB,aAAS4E,EAClB,CAA2D,EADrC,KAAA5E,QAAG;AAAA,cACVqI,EAAOrI,CAAG,UAAzBjE,EAA2DH,EAAA8H,EAAA,GAAA;AAAA;gBAA9B,OAAO2E,EAAOrI,CAAG;AAAA,gBAAG,SAAA;AAAA,cAAA,gCACjDjE,EAAkDH,EAAA+H,EAAA,GAAA;AAAA;gBAAlC,MAAK;AAAA,gBAAK,MAAK;AAAA,cAAA;2BAAQ,MAAC,CAAA,GAAAE,EAAA,CAAA,MAAAA,EAAA,CAAA,IAAA;AAAA,oBAAD,KAAC,EAAA;AAAA,gBAAA;;;;YAG/B,eAAWe,EACpB,CASO,EAViB,KAAA5E,QAAG;AAAA,cAC3B4C,EASO,QATPmG,IASO;AAAA,gBARLpN,EAEWC,EAAA8G,EAAA,GAAA;AAAA,kBAFA,MAAMhB,EAAM1B,EAAI,MAAM;AAAA,kBAAG,KAAA;AAAA,kBAAI,MAAK;AAAA,gBAAA;6BAC3C,MAAqC;AAAA,wBAAlCpE,EAAA,CAAA,EAAC,gBAAiBoE,EAAI,MAAM,EAAA,CAAA,GAAA,CAAA;AAAA,kBAAA;;;gBAIjBA,EAAI,WAAM,mBAA1BjE,EAEWH,EAAA8G,EAAA,GAAA;AAAA;kBAFgC,MAAK;AAAA,kBAAU,MAAK;AAAA,kBAAK,OAAA;AAAA,gBAAA;6BAClE,MAAsB;AAAA,wBAAnB9G,EAAA,CAAA,EAAC,aAAA,CAAA,GAAA,CAAA;AAAA,kBAAA;;;;;YAgBC,aAASgJ,EAClB,CAqBiB,EAtBK,KAAA5E,QAAG;AAAA,cACzBrE,EAqBiBC,EAAAoN,EAAA,GAAA;AAAA,gBApBf,OAAM;AAAA,gBACN,SAAQ;AAAA,gBACP,OAAOhJ,EAAI;AAAA,gBACX,OAAOA,EAAI,OAAO,SAAS;AAAA,gBAC5B,cAAW;AAAA,gBACV,eAAa;AAAA,gBACb,UAAUwB,EAAQxB,CAAG;AAAA,cAAA;gBAEX,SACT,MAA4E;AAAA,kBAA7DA,EAAI,eAAnBjE,EAA4EH,EAAA4M,EAAA,GAAA;AAAA;oBAAjD,OAAM;AAAA,oBAAmB,MAAK;AAAA,oBAAO,MAAK;AAAA,kBAAA;oBAAO,MAC5EtM,EAAG8D,EAAI,KAAK,GAAA,CAAA;AAAA,gBAAA;gBAGH,QACT,MAAqF;AAAA,kBAArEA,EAAI,QAAO,CAAA,UAA3BjE,EAAqFH,EAAA8G,EAAA,GAAA;AAAA;oBAArD,MAAK;AAAA,oBAAK,OAAA;AAAA,kBAAA;+BAAM,MAA0B;AAAA,0BAAvB1C,EAAI,QAAO,CAAA,EAAI,KAAK,GAAA,CAAA;AAAA,oBAAA;;;kBACvErE,EAEWC,EAAA8G,EAAA,GAAA;AAAA,oBAFA,MAAMhB,EAAM1B,EAAI,MAAM;AAAA,oBAAG,KAAA;AAAA,oBAAI,MAAK;AAAA,kBAAA;+BAC3C,MAAqC;AAAA,0BAAlCpE,EAAA,CAAA,EAAC,gBAAiBoE,EAAI,MAAM,EAAA,CAAA,GAAA,CAAA;AAAA,oBAAA;;;kBAElBqI,EAAOrI,CAAG,UAAzBjE,EAAmDH,EAAA8H,EAAA,GAAA;AAAA;oBAAtB,OAAO2E,EAAOrI,CAAG;AAAA,kBAAA;;;;;YAUzC,gBAAY4E,EACrB,CAA6D,EADpC,KAAA5E,QAAG;AAAA,cAC5BrE,EAA6DC,EAAAqN,EAAA,GAAA;AAAA,gBAA/C,SAASnB,GAAW9H,CAAG;AAAA,gBAAI,OAAOA,EAAI;AAAA,cAAA;;YAG3C,gBAAY4E,EACrB,CAA6D,EADpC,KAAA5E,QAAG;AAAA,cAC5BrE,EAA6DC,EAAAqN,EAAA,GAAA;AAAA,gBAA/C,SAASnB,GAAW9H,CAAG;AAAA,gBAAI,OAAOA,EAAI;AAAA,cAAA;;;;;;;;;;AC/lBvD,SAASkJ,GAAiB9M,GAAiC;AAChE,QAAMhB,IAAO,GAAGgB,EAAM,OAAO,iBACvBE,IAAO,CAAIC,MAAyBA,EAAK;AAE/C,SAAO;AAAA,IACL,MAAM,CAAC4M,IAAI,OAAO;AAChB,YAAMzM,IAASyM,EAAE,KAAA,MAAW,KAAK,KAAK,MAAM,mBAAmBA,EAAE,KAAA,CAAM,CAAC;AAExE,aAAO/M,EAAM,KAAK,IAA2B,GAAGhB,CAAI,GAAGsB,CAAM,EAAE,EAAE,KAAKJ,CAAI;AAAA,IAC5E;AAAA,IACA,UAAU,MAAMF,EAAM,KAAK,IAA+B,GAAGhB,CAAI,WAAW,EAAE,KAAKkB,CAAI;AAAA,IACvF,KAAK,CAACK,MAAOP,EAAM,KAAK,IAA4B,GAAGhB,CAAI,IAAIuB,CAAE,EAAE,EAAE,KAAKL,CAAI;AAAA,IAC9E,QAAQ,CAAC8M,GAAQ/K,MACfjC,EAAM,KAAK,KAA6BhB,GAAM,EAAE,SAASgO,GAAQ,QAAA/K,EAAA,CAAQ,EAAE,KAAK/B,CAAI;AAAA,IACtF,MAAM,CAACK,GAAI0B,MACTjC,EAAM,KAAK,IAA4B,GAAGhB,CAAI,IAAIuB,CAAE,IAAI,EAAE,QAAA0B,GAAQ,EAAE,KAAK/B,CAAI;AAAA,IAC/E,QAAQ,CAACK,MAAOP,EAAM,KAAK,OAAa,GAAGhB,CAAI,IAAIuB,CAAE,EAAE,EAAE,KAAK,MAAA;AAAA,KAAe;AAAA,EAAA;AAEjF;;;;;;;;;;;;;ACZA,UAAMpC,IAAQC,GAUR6O,IAAOC,GASPzO,IAAUP,GAAA,GACVgD,IAAM4L,GAAiBrO,CAAO,GAC9B0C,IAAQC,GAAA,GACRG,IAAUC,GAAA;AAChB,IAAAxD,GAAA;AAEA,UAAMO,IAAIC,EAAa,WAAW,GAE5BoD,IAAUC,GAAA,GAEVhD,IAASmD,EAAsB,IAAI,GACnCC,IAASD,EAAiB,EAAE,GAC5BS,IAAWT,EAAI,EAAE,GAEjBmL,IAAWnL,EAAqB,EAAE,GAClCoL,IAAUpL,EAAmB,IAAI,GAEjCK,IAAUL,EAAI,EAAI,GAClBM,IAASN,EAAI,EAAK,GAClBQ,IAASR,EAA8B,EAAE,GAEzCW,IAAYhE,EAAS,MAAMF,EAAQ,IAAI,qBAAqB,CAAC,GAE7D2E,IAAUzE,EAAS,MAAM,KAAK,UAAUsD,EAAO,KAAK,CAAC,GACrDoB,IAAQ1E;AAAA,MACZ,MAAM8D,EAAS,UAAU,OAAOW,EAAQ,UAAUX,EAAS,SAAS2K,EAAQ,UAAU;AAAA,IAAA,GAGlF9J,IAAQ3E,EAAyC,MACjD2D,EAAO,QAAc,WAElBe,EAAM,SAASlF,EAAM,OAAO,OAAO,YAAY,OACvD,GAGKoF,IAAQ5E,EAAS,MAAM;AAC3B,YAAM6E,IAAUvB,EAAO,MAAM,OACvBoL,IAAWlP,EAAM,OAAO,OAAOI,EAAE,YAAY,IAAKM,EAAO,OAAO,QAAQ;AAE9E,aAAO4E,GAAeD,GAASjC,EAAQ,OAAO,OAAO8L,CAAQ,KAAK9O,EAAE,iBAAiB;AAAA,IACvF,CAAC,GAEK+O,IAAiB3O;AAAA,MAAyB,MAC9CwO,EAAS,MAAM,IAAI,CAACpF,OAAS;AAAA,QAC3B,OACEA,EAAI,iBAAiB,IACjB,GAAGA,EAAI,IAAI,MAAMxJ,EAAE,mBAAmB,EAAE,OAAOwJ,EAAI,gBAAgB,CAAC,KACpEA,EAAI;AAAA,QACV,OAAOA,EAAI;AAAA,MAAA,EACX;AAAA,IAAA,GAGEwF,IAAgB5O,EAA2B;AAAA,MAC/C,KAAK,MAAMyO,EAAQ;AAAA,MACnB,KAAK,CAACjO,MAAS;AACb,QAAAiO,EAAQ,QAAQ,OAAOjO,KAAS,WAAWA,IAAO;AAGlD,cAAMkJ,IAAS8E,EAAS,MAAM,KAAK,CAACpF,OAAQA,GAAI,OAAOqF,EAAQ,KAAK,GAC9D5J,IAAUvB,EAAO,MAAM,OAEvBuL,KAAOjM,EAAQ,KAAK,MAAM,CAAC,GAAG;AAEpC,QAAI8G,KAAUmF,MAAQ,EAAEhK,IAAUgK,EAAI,KAAK,IAAI,WAC7CvL,EAAO,QAAQ,EAAE,GAAGA,EAAO,OAAO,OAAO,EAAE,GAAIuB,KAAW,CAAA,GAAK,CAACgK,EAAI,GAAGnF,EAAO,OAAK;AAAA,MAEvF;AAAA,IAAA,CACD;AAED,aAASpE,EAAKnB,GAA4B;AACxC,MAAAjE,EAAO,QAAQiE,EAAO,QACtBb,EAAO,QAAQa,EAAO,QACtBL,EAAS,QAAQ,KAAK,UAAUK,EAAO,MAAM,GAC7CsK,EAAQ,QAAQ;AAAA,IAClB;AAEA,mBAAerJ,KAAsB;AACnC,MAAA1B,EAAQ,QAAQ;AAEhB,UAAI;AACF,YAAIlE,EAAM,OAAO,MAAM;AAErB,gBAAMsP,IAAqB;AAAA,YACzB,OAAO,CAAA;AAAA,YACP,MAAM,CAAA;AAAA,YACN,MAAM,CAAA;AAAA,YACN,KAAK,CAAA;AAAA,YACL,OAAO;AAAA,YACP,SAAS,CAAA;AAAA,YACT,WAAW;AAAA,UAAA;AAGb,UAAAxL,EAAO,QAAQwL,GACfhL,EAAS,QAAQ,KAAK,UAAUgL,CAAK,GACrCN,EAAS,QAAQ,MAAMjM,EAAI,SAAA;AAE3B,gBAAMqK,IAAS,OAAOpK,EAAM,MAAM,OAAO;AAEzC,UAAIgM,EAAS,MAAM,KAAK,CAACpF,MAAQA,EAAI,OAAOwD,CAAM,MAAGgC,EAAc,QAAQhC;AAAA,QAC7E;AACE,UAAAtH,EAAK,MAAM/C,EAAI,IAAI/C,EAAM,EAAE,CAAC;AAAA,MAEhC,SAASgG,GAAO;AACd,QAAAC,EAAM,OAAOxC,EAAQuC,CAAK,CAAC,GAC3B8I,EAAK,MAAM;AAAA,MACb,UAAA;AACE,QAAA5K,EAAQ,QAAQ;AAAA,MAClB;AAAA,IACF;AAEA,mBAAe2B,IAAsB;AACnC,UAAI,EAAA1B,EAAO,SAAS,CAACK,EAAU,QAE/B;AAAA,QAAAL,EAAO,QAAQ,IACfE,EAAO,QAAQ,CAAA;AAEf,YAAI;AACF,gBAAMkL,IAAOvP,EAAM,OAAO;AAE1B,cAAIuP,KAAQN,EAAQ,UAAU,MAAM;AAClC,YAAA5K,EAAO,QAAQ,EAAE,SAAS,CAACjE,EAAE,wBAAwB,CAAC,EAAA,GACtD6F,EAAM,OAAO7F,EAAE,oBAAoB,CAAC;AAEpC;AAAA,UACF;AAEA,gBAAMuE,IAAS4K,IACX,MAAMxM,EAAI,OAAOkM,EAAQ,OAAiBnL,EAAO,KAAK,IACtD,MAAMf,EAAI,KAAK/C,EAAM,IAAc8D,EAAO,KAAK;AAEnD,UAAAgC,EAAKnB,CAAM,GACXsB,EAAM,QAAQ7F,EAAE,cAAc,CAAC,GACrB0O,EAANS,IAAW,YACL,SADgB5K,EAAO,MAAM;AAAA,QAEzC,SAASqB,GAAO;AACd,gBAAMhE,IAAQgE,EAA2D;AAEzE,UAAIhE,GAAM,UACRqC,EAAO,QAAQrC,EAAK,QACpBiE,EAAM,OAAO7F,EAAE,oBAAoB,CAAC,KAEpC6F,EAAM,OAAOxC,EAAQuC,CAAK,CAAC;AAAA,QAE/B,UAAA;AACE,UAAA7B,EAAO,QAAQ;AAAA,QACjB;AAAA;AAAA,IACF;AAEA,mBAAegG,KAAwB;AACrC,YAAMqF,IAAS9O,EAAO;AAYtB,UAVI,GAAC8O,KAUD,CARW,MAAM3I,GAAQ;AAAA,QAC3B,OAAOzG,EAAE,uBAAuB,EAAE,MAAMoP,EAAO,MAAM;AAAA,QACrD,SAASpP,EAAE,oBAAoB;AAAA,QAC/B,aAAaA,EAAE,eAAe;AAAA,QAC9B,YAAYA,EAAE,eAAe;AAAA,QAC7B,MAAM;AAAA,MAAA,CACP;AAID,YAAI;AACF,gBAAM2C,EAAI,OAAOyM,EAAO,EAAE,GAC1BvJ,EAAM,QAAQ7F,EAAE,gBAAgB,CAAC,GAEjCkE,EAAS,QAAQW,EAAQ,OACzBgK,EAAQ,QAAQ,MAChBH,EAAK,WAAWU,EAAO,EAAE;AAAA,QAC3B,SAASxJ,GAAO;AACd,UAAAC,EAAM,OAAOxC,EAAQuC,CAAK,CAAC;AAAA,QAC7B;AAAA,IACF;AAGA,aAASqB,GAAWC,GAAgC;AAClD,MAAIpC,EAAM,SAAOoC,EAAM,eAAA;AAAA,IACzB;AAGA,aAASmI,GAAUnI,GAA4B;AAC7C,OAAKA,EAAM,WAAWA,EAAM,YAAYA,EAAM,IAAI,YAAA,MAAkB,QAClEA,EAAM,eAAA,IACFpC,EAAM,SAASlF,EAAM,OAAO,SAAW6F,EAAA;AAAA,IAE/C;AAGA,IAAA6J,GAAYD,EAAS,GAErBjI,GAAU,MAAM;AACd,MAAK5B,GAAA,GACL,OAAO,iBAAiB,gBAAgByB,EAAU;AAAA,IACpD,CAAC,GAEDI,GAAgB,MAAM;AACpB,aAAO,oBAAoB,gBAAgBJ,EAAU;AAAA,IACvD,CAAC;AAED,mBAAesI,IAA6B;AAC1C,aAAI,CAACzK,EAAM,SAAS,CAACV,EAAU,QAAc,KAEtC,MAAMqC,GAAQ;AAAA,QACnB,OAAOzG,EAAE,oBAAoB;AAAA,QAC7B,SAASA,EAAE,mBAAmB;AAAA,QAC9B,aAAaA,EAAE,cAAc;AAAA,QAC7B,YAAYA,EAAE,eAAe;AAAA,QAC7B,MAAM;AAAA,MAAA,CACP;AAAA,IACH;AAGA,IAAAwP;AAAA,MAAoB,OAAOC,GAA6BC,MACtDD,EAAG,MAAM,WAAWC,EAAK,MAAM,SAAS,KAAO,MAAMH,EAAA;AAAA,IAAS,GAGhEjI,GAAmBiI,CAAQ;AAG3B,UAAMhI,KAAUnH,EAAyB,MAAM;AAC7C,YAAMmD,IAAuB,CAAA,GACvB6L,IAAS9O,EAAO;AAEtB,aAAI8O,GAAQ,QAAQA,EAAO,OACzB7L,EAAK,KAAK;AAAA,QACR,KAAK;AAAA,QACL,OAAOvD,EAAE,kBAAkB;AAAA,QAC3B,MAAM;AAAA,QACN,KAAK,MAAA;AAAM,UAAK,OAAO,KAAKoP,EAAO,KAAe,UAAU,UAAU;AAAA;AAAA,MAAA,CACvE,GAGChL,EAAU,SAASgL,MAAW,QAChC7L,EAAK,KAAK;AAAA,QACR,KAAK;AAAA,QACL,OAAOvD,EAAE,eAAe;AAAA,QACxB,MAAM;AAAA,QACN,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,KAAK,MAAA;AAAM,UAAK+J,GAAA;AAAA;AAAA,MAAO,CACxB,GAGIxG;AAAA,IACT,CAAC;2BAICzC,EA+DM,OAAA;AAAA,MA/DD,OAAM;AAAA,MAAa,WAAAuO;AAAA,IAAA;MACHvL,EAAA,cAAnB1C,EAAuEH,EAAAyG,EAAA,GAAA;AAAA;QAA3C,OAAM;AAAA,QAAmB,OAAA;AAAA,QAAO,MAAM;AAAA,MAAA,YAElE5G,EA2DW2G,IAAA,EAAA,KAAA,KAAA;AAAA,QA1DTzG,EAYiBC,EAAA2G,EAAA,GAAA;AAAA,UAXd,OAAO;AAAA,UACP,MAAI,CAAGhI,EAAM;AAAA,UACb,cAAYqB,EAAAjB,CAAA,EAAC,aAAA;AAAA,UACb,OAAOgF,EAAA;AAAA,UACP,SAASuC,GAAA;AAAA,UACT,kBAAgB;AAAA,UAChB,+BAAMmH,EAAI,MAAA;AAAA,QAAA;UAEKpO,EAAA,SAAM,CAAKA,EAAA,MAAO;kBAAO;AAAA,kBACvC,MAAsD;AAAA,cAAtDU,EAAsDC,EAAA8G,EAAA,GAAA,EAA5C,KAAA,MAAG;AAAA,2BAAC,MAA6B;AAAA,sBAA1B9G,EAAAjB,CAAA,EAAC,oBAAA,CAAA,GAAA,CAAA;AAAA,gBAAA;;;;;;;QAKPJ,EAAM,OAAE,aAAvBwB,EAgBUH,EAAA0G,EAAA,GAAA,EAAA,KAAA,EAAA,GAAA;AAAA,qBAfR,MAce;AAAA,YAdf3G,EAceC,EAAAgK,EAAA,GAAA;AAAA,cAbZ,OAAOhK,EAAAjB,CAAA,EAAC,gBAAA;AAAA,cACR,MAAM4O,EAAA,MAAS,WAAM,IAAS3N,EAAAjB,CAAA,0BAA0BiB,EAAAjB,CAAA,EAAC,qBAAA;AAAA,cACzD,OAAOiE,EAAA,MAAO,UAAO,CAAA;AAAA,cACtB,UAAA;AAAA,YAAA;yBAEA,MAOE;AAAA,gBAPFjD,EAOEC,EAAAC,EAAA,GAAA;AAAA,8BANS8N,EAAA;AAAA,gEAAAA,EAAa,QAAA7N;AAAA,kBACrB,SAAS4N,EAAA;AAAA,kBACT,WAAW3K,EAAA,SAAaL,WAAU6K,EAAA,MAAS,WAAM;AAAA,kBAClD,YAAA;AAAA,kBACA,UAAA;AAAA,kBACC,cAAY3N,EAAAjB,CAAA,EAAC,gBAAA;AAAA,gBAAA;;;;;;cAKNM,EAAA,OAAQ,aAAtBQ,EAEI,KAFJC,IAEIQ,EADCN,EAAAjB,CAAA,EAAC,gBAAA,CAAA,IAAqB,OAAEuB,EAAGjB,EAAA,MAAO,KAAK,IAAI,GAAA,CAAA;QAGhDU,EAKEC,EAAAiH,EAAA,GAAA;AAAA,sBAJSxE,EAAA;AAAA,wDAAAA,EAAM,QAAAvC;AAAA,UACf,MAAK;AAAA,UACJ,QAAQ8C,EAAA;AAAA,UACR,UAAQ,CAAGG,EAAA,SAAaL,EAAA;AAAA,QAAA;QAGNK,EAAA,cAArBhD,EAagBH,EAAAkH,EAAA,GAAA,EAAA,KAAA,KAAA;AAAA,UAZH,SACT,MAAgC;AAAA,YAAhCnH,EAAgCC,EAAAmH,EAAA,GAAA,EAAhB,OAAOrD,EAAA,SAAK,MAAA,GAAA,CAAA,OAAA,CAAA;AAAA,UAAA;qBAG9B,MAOY;AAAA,YAPZ/D,EAOYC,EAAAoH,EAAA,GAAA;AAAA,cANV,MAAK;AAAA,cACJ,SAAStE,EAAA;AAAA,cACT,UAAQ,CAAGe,EAAA,SAASlF,EAAM,OAAE;AAAA,cAC5B,SAAO6F;AAAA,YAAA;yBAER,MAAsB;AAAA,oBAAnBxE,EAAAjB,CAAA,EAAC,aAAA,CAAA,GAAA,CAAA;AAAA,cAAA;;;;;;;;;;;;;;;AC/Vd,UAAME,IAAUP,GAAA,GACVgD,IAAM4L,GAAiBrO,CAAO,GAC9B0C,IAAQC,GAAA,GACRC,IAASC,GAAA;AACf,IAAAtD,GAAA;AAEA,UAAMO,IAAIC,EAAa,WAAW,GAE5BoD,IAAUC,GAAA,GAEVmG,IAAOhG,EAAiB,EAAE,GAC1BK,IAAUL,EAAI,EAAI,GAElBW,IAAYhE,EAAS,MAAMF,EAAQ,IAAI,qBAAqB,CAAC,GAE7D8E,IAAQ5E;AAAA,MACZ,MACEF,EAAQ,MAAM,UAAU,QAAQ,KAAK,CAACM,MAAWA,EAAO,OAAO,SAAS,GAAG,SAC3ER,EAAE,gBAAgB;AAAA,IAAA,GAGhBwO,IAAIpO,EAAS,MAAO,OAAOwC,EAAM,MAAM,KAAM,WAAWA,EAAM,MAAM,IAAI,EAAG,GAG3EiI,IAAQpH,EAAI+K,EAAE,KAAK,GAGnBmB,IAAWvP,EAAgC,MAAM;AACrD,YAAMwP,IAAMhN,EAAM,MAAM;AAExB,aAAIgN,MAAQ,QAAcxL,EAAU,QAAQ,QAAQ,OAE7C,OAAOwL,KAAQ,YAAYA,MAAQ,MAAK,OAAOA,CAAG,KAAK;AAAA,IAChE,CAAC,GAGK1E,IAAO9K,EAAS;AAAA,MACpB,KAAK,MAAMuP,EAAS,UAAU;AAAA,MAC9B,KAAK,CAAC7P,MAAU;AACd,QAAKA,KAAO+P,EAAA;AAAA,MACd;AAAA,IAAA,CACD;AAED,mBAAerK,IAAsB;AACnC,MAAA1B,EAAQ,QAAQ;AAEhB,UAAI;AACF,QAAA2F,EAAK,QAAQ,MAAM9G,EAAI,KAAK6L,EAAE,KAAK;AAAA,MACrC,SAAS5I,GAAO;AACd,QAAAC,EAAM,OAAOxC,EAAQuC,CAAK,CAAC;AAAA,MAC7B,UAAA;AACE,QAAA9B,EAAQ,QAAQ;AAAA,MAClB;AAAA,IACF;AAEA,IAAAqD,GAAMqH,GAAG;AAAM,MAAKhJ;KAAM,GACrBA,EAAA;AAEL,QAAIsK;AAEJ,IAAA3I,GAAM0D,GAAO,CAAC/K,MAAU;AACtB,mBAAagQ,CAAK,GAClBA,IAAQ,WAAW,MAAM;AACvB,QAAKhN,EAAO,QAAQ,EAAE,OAAO,EAAE,GAAGF,EAAM,OAAO,GAAG9C,EAAM,WAAW,KAAK,SAAYA,EAAA,GAAS;AAAA,MAC/F,GAAG,GAAG;AAAA,IACR,CAAC,GAEDuH,GAAgB,MAAM,aAAayI,CAAK,CAAC;AAGzC,aAASC,EAAO/N,GAA0B;AACxC,YAAMH,IAA0B,EAAE,GAAGe,EAAM,OAAO,QAAQ,OAAOZ,CAAE,EAAA;AAGnE,MAAIA,MAAO,SAAO,OAAOH,EAAM,SAC1BiB,EAAO,QAAQ,EAAE,OAAAjB,GAAO;AAAA,IAC/B;AAEA,aAASgO,IAAc;AACrB,YAAMhO,IAAQ,EAAE,GAAGe,EAAM,MAAA;AAEzB,aAAOf,EAAM,QACb,OAAOA,EAAM,SACRiB,EAAO,QAAQ,EAAE,OAAAjB,GAAO;AAAA,IAC/B;AAGA,aAASmO,EAAQ1P,GAAyB;AACxC,MAAAyP,EAAOzP,EAAO,EAAE,GACXkF,EAAA;AAAA,IACP;AAEA,aAASyK,EAAQjO,GAAkB;AACjC,MAAI2N,EAAS,UAAU3N,KAAI6N,EAAA,GAEtBrK,EAAA;AAAA,IACP;2BAIEpE,EAkGiBH,EAAA0M,EAAA,GAAA;AAAA,MAlGA,OAAO3I,EAAA;AAAA,MAAQ,MAAM;AAAA,IAAA;iBACpC,MAgGU;AAAA,QAhGVhE,EAgGUC,EAAA0G,EAAA,GAAA;AAAA,UAhGD,OAAM;AAAA,UAAa,SAAQ;AAAA,QAAA;qBAClC,MA8FiB;AAAA,YA9FjB3G,EA8FiBC,EAAAiP,EAAA,GAAA;AAAA,cA7FP,MAAMhF,EAAA;AAAA,sDAAAA,EAAI,QAAA/J;AAAA,cAClB,OAAM;AAAA,cACL,cAAY;AAAA,cACZ,cAAY;AAAA,cACZ,gBAAcF,EAAAjB,CAAA,EAAC,gBAAA;AAAA,YAAA;cAEL,QACT,MAWM;AAAA,gBAXNiI,EAWM,OAXNlH,IAWM;AAAA,kBAVJC,EASWC,EAAA2K,EAAA,GAAA;AAAA,gCARAf,EAAA;AAAA,kEAAAA,EAAK,QAAA1J;AAAA,oBACd,OAAM;AAAA,oBACN,MAAK;AAAA,oBACL,WAAA;AAAA,oBACC,aAAaF,EAAAjB,CAAA,EAAC,eAAA;AAAA,oBACd,cAAYiB,EAAAjB,CAAA,EAAC,eAAA;AAAA,kBAAA;oBAEH,UAAO,MAAmC;AAAA,sBAAnCgB,EAAmCC,EAAA4M,EAAA,GAAA;AAAA,wBAA1B,MAAK;AAAA,wBAAS,MAAK;AAAA,sBAAA;;;;;gBAK1CzJ,EAAA,cADRtD,EASS,UAAA;AAAA;kBAPP,MAAK;AAAA,kBACL,OAAKiN,GAAA,CAAC,mBAAiB,EAAA,cACC4B,EAAA,UAAQ,MAAA,CAAA,CAAA;AAAA,kBAC/B,gCAAOI,EAAM,KAAA;AAAA,gBAAA;kBAEd/O,EAAiCC,EAAA4M,EAAA,GAAA;AAAA,oBAAxB,MAAK;AAAA,oBAAO,MAAK;AAAA,kBAAA;kBAAOvM,EAAA,QAC9BL,EAAAjB,CAAA,EAAC,YAAA,CAAA,GAAA,CAAA;AAAA,gBAAA;gBAGa8D,EAAA,SAAW2F,EAAA,MAAK,WAAM,UAAzCrI,EAAyFH,EAAAyG,EAAA,GAAA;AAAA;kBAAxC,OAAM;AAAA,kBAAuB,MAAM;AAAA,gBAAA,MAGvE+B,EAAA,MAAK,WAAM,UADxBrI,EAIEH,EAAA0H,EAAA,GAAA;AAAA;kBAFC,OAAO6F,EAAA,UAAC,KAAUvN,EAAAjB,CAAA,oBAAoBiB,EAAAjB,CAAA,EAAC,qBAAA;AAAA,kBACvC,aAAawO,EAAA,UAAC,KAAUvN,EAAAjB,CAAA,yBAAyB;AAAA,gBAAA,+CAGpDc,EAgCK,MAAA;AAAA;kBAhCM,OAAKiN,GAAA,CAAC,oBAAkB,EAAA,cAAyBjK,EAAA,OAAO,CAAA;AAAA,gBAAA;0BACjEhD,EA8BK2G,IAAA,MAAAmB,GA9Bca,EAAA,OAAI,CAAZS,YAAXpJ,EA8BK,MAAA;AAAA,oBA9BqB,KAAKoJ,EAAK;AAAA,kBAAA;oBAClCjC,EA4BS,UAAA;AAAA,sBA3BP,MAAK;AAAA,sBACL,WAAM,iBAAe,EAAA,cACG0H,YAAazF,EAAK,GAAA,CAAE,CAAA;AAAA,sBAC3C,SAAK,CAAA/I,MAAE4O,EAAO7F,EAAK,EAAE;AAAA,oBAAA;sBAEtBlJ,EAKEC,EAAAkP,EAAA,GAAA;AAAA,wBAJA,OAAM;AAAA,wBACL,KAAKjG,EAAK,SAAS;AAAA,wBACnB,MAAMA,EAAK;AAAA,wBACZ,MAAK;AAAA,sBAAA;sBAGPjC,EAcO,QAdPY,IAcO;AAAA,wBAbLZ,EAAwD,QAAxDa,IAAwDvH,EAAnB2I,EAAK,IAAI,GAAA,CAAA;AAAA,wBAE9CjC,EAGO,QAHPgG,IAGO;AAAA,0BAFO/D,EAAK,QAAjBrJ,EAAA,GAAAC,EAAkD,QAAAqN,IAAA5M,EAAxB2I,EAAK,KAAK,IAAI,GAAA,CAAA;0BACxCjC,EAAuE,QAAA,MAAA1G,EAA9DN,EAAAjB,CAAA,EAAC,mBAAA,EAAA,OAA6BkK,EAAK,eAAA,CAAc,CAAA,GAAA,CAAA;AAAA,wBAAA;wBAG5DjC,EAKO,QALPmG,IAKO;AAAA,0BAJWlE,EAAK,aAArB9I,EAEWH,EAAA8G,EAAA,GAAA;AAAA;4BAFgB,KAAA;AAAA,4BAAI,MAAK;AAAA,4BAAK,MAAK;AAAA,0BAAA;uCAC5C,MAA2B;AAAA,kCAAxB9G,EAAAjB,CAAA,EAAC,kBAAA,CAAA,GAAA,CAAA;AAAA,4BAAA;;sCAENoB,EAAuEH,EAAA8G,EAAA,GAAA;AAAA;4BAAtD,KAAA;AAAA,4BAAI,MAAK;AAAA,0BAAA;uCAAK,MAA6B;AAAA,kCAA1B9G,EAAAjB,CAAA,EAAC,oBAAA,CAAA,GAAA,CAAA;AAAA,4BAAA;;;;;;;;;cAQpC,QAAMiK,EACf,CASE,EAViB,QAAAmG,GAAQ,MAAAC,QAAI;AAAA,gBAEvBV,EAAA,UAAQ,aADhBvO,EASEkP,IAAA;AAAA,kBAPC,IAAIX,EAAA,UAAQ,QAAA,OAAoBA,EAAA;AAAA,kBAChC,KAAK,OAAOA,EAAA,KAAQ;AAAA,kBACpB,QAAAS;AAAA,kBACA,QAAMC;AAAA,kBACN,SAAO7K;AAAA,kBACP,WAASwK;AAAA,kBACT,WAASC;AAAA,gBAAA;;cAIH,SACT,MAGE;AAAA,gBAHFjP,EAGEC,EAAA0H,EAAA,GAAA;AAAA,kBAFC,OAAO1H,EAAAjB,CAAA,EAAC,eAAA;AAAA,kBACR,aAAaoE,EAAA,QAAYnD,EAAAjB,CAAA,0BAA0B;AAAA,gBAAA;;;;;;;;;;;;;;AC9MhE,UAAM,EAAE,MAAAkL,GAAM,SAAAC,GAAS,SAAAC,EAAA,IAAYC,GAAA,GAE7BnL,IAAUP,GAAA,GACVgD,IAAMnB,GAActB,CAAO;AACjC,IAAAT,GAAA;AAEA,UAAM,IAAIQ,EAAa,WAAW,GAE5B+E,IAAQvB,EAAI,EAAE,GACd4G,IAAO5G,EAAI,EAAE,GACbM,IAASN,EAAI,EAAK,GAClBQ,IAASR,EAA8B,EAAE,GACzC6H,IAAQC,GAA4B,OAAO;AAEjD,IAAAnE,GAAU,YAAY;AACpB,YAAMoE,GAAA,GACNF,EAAM,OAAO,cAAc,OAAO,GAAG,MAAA;AAAA,IACvC,CAAC;AAED,mBAAeG,IAAwB;AACrC,UAAI,EAAAzG,EAAM,MAAM,KAAA,MAAW,MAAMjB,EAAO,QAExC;AAAA,QAAAA,EAAO,QAAQ,IACfE,EAAO,QAAQ,CAAA;AAEf,YAAI;AACF,UAAAkH;AAAA,YACE,MAAMxI,EAAI,UAAU;AAAA,cAClB,OAAOqC,EAAM,MAAM,KAAA;AAAA,cACnB,GAAIqF,EAAK,MAAM,KAAA,MAAW,KAAK,KAAK,EAAE,MAAMA,EAAK,MAAM,OAAK;AAAA,YAAE,CAC/D;AAAA,UAAA;AAAA,QAEL,SAASzE,GAAO;AACd,UAAA3B,EAAO,QAAS2B,EAA2D,MAAM,UAAU,CAAA;AAAA,QAC7F,UAAA;AACE,UAAA7B,EAAO,QAAQ;AAAA,QACjB;AAAA;AAAA,IACF;2BAIE3C,EAuBYH,EAAAyK,EAAA,GAAA;AAAA,MAvBO,MAAMzK,EAAAiK,CAAA;AAAA,sDAAAA,EAAI,QAAA/J,IAAA;AAAA,MAAG,OAAOF,EAAA,CAAA,EAAC,eAAA;AAAA,MAAoB,OAAO;AAAA,IAAA;MAetD,UACT,MAKW;AAAA,QALXD,EAKWC,EAAA0K,EAAA,GAAA,EALD,MAAK,QAAI;AAAA,qBACjB,MAAiF;AAAA,YAAjF3K,EAAiFC,EAAAoH,EAAA,GAAA;AAAA,cAAtE,SAAQ;AAAA,cAAW,gCAAOpH,EAAAmK,CAAA,EAAA;AAAA,YAAO;yBAAI,MAAqB;AAAA,oBAAlBnK,EAAA,CAAA,EAAC,YAAA,CAAA,GAAA,CAAA;AAAA,cAAA;;;YACpDD,EAEYC,EAAAoH,EAAA,GAAA;AAAA,cAFD,MAAK;AAAA,cAAW,SAAStE,EAAA;AAAA,cAAS,UAAUiB,EAAA,MAAM,KAAA,MAAI;AAAA,cAAY,SAAOyG;AAAA,YAAA;yBAClF,MAAqB;AAAA,oBAAlBxK,EAAA,CAAA,EAAC,YAAA,CAAA,GAAA,CAAA;AAAA,cAAA;;;;;;;iBAlBV,MAIe;AAAA,QAJfD,EAIeC,EAAAgK,EAAA,GAAA;AAAA,UAJA,OAAOhK,EAAA,CAAA,EAAC,iBAAA;AAAA,UAAsB,OAAOgD,EAAA,MAAO,QAAK,CAAA;AAAA,UAAO,UAAA;AAAA,QAAA;qBACrE,MAEM;AAAA,YAFNgE,EAEM,OAAA;AAAA,uBAFG;AAAA,cAAJ,KAAIqD;AAAA,YAAA;cACPtK,EAAqFC,EAAA2K,EAAA,GAAA;AAAA,4BAAlE5G,EAAA;AAAA,8DAAAA,EAAK,QAAA7D;AAAA,gBAAG,cAAYF,EAAA,CAAA,EAAC,iBAAA;AAAA,gBAAsB,YAAawK,GAAM,CAAA,OAAA,CAAA;AAAA,cAAA;;;;;QAIrFzK,EAMeC,EAAAgK,EAAA,GAAA;AAAA,UALZ,OAAOhK,EAAA,CAAA,EAAC,gBAAA;AAAA,UACR,OAAOgD,EAAA,MAAO,OAAI,CAAA;AAAA,UAClB,MAAMhD,EAAA,CAAA,EAAC,qBAAA;AAAA,QAAA;qBAER,MAAmF;AAAA,YAAnFD,EAAmFC,EAAA2K,EAAA,GAAA;AAAA,0BAAhEvB,EAAA;AAAA,4DAAAA,EAAI,QAAAlJ;AAAA,cAAG,cAAYF,EAAA,CAAA,EAAC,gBAAA;AAAA,cAAqB,YAAawK,GAAM,CAAA,OAAA,CAAA;AAAA,YAAA;;;;;;;;;;;;;;AClDrF,UAAM7L,IAAQC,GAER,EAAE,MAAAqL,GAAM,SAAAC,GAAS,SAAAC,EAAA,IAAYC,GAAA;AAEnC,IAAA5L,GAAA;AAEA,UAAMO,IAAIC,EAAa,WAAW,GAE5BmD,IAAQnD,EAAa,YAAY,GAGjCsQ,IAASC,GAAA,GAETC,IAAQhN,EAAI7D,EAAM,IAAI,GACtB0L,IAAQC,GAAgC,OAAO;AAErD,IAAAnE,GAAU,YAAY;AACpB,YAAMoE,GAAA;AAEN,YAAMvJ,IAAQqJ,EAAM,OAAO,cAAc,OAAO;AAGhD,MAAArJ,GAAO,MAAA,GACPA,GAAO,OAAA;AAAA,IACT,CAAC;AAED,aAASwJ,IAAe;AACtB,YAAM7K,IAAO6P,EAAM,MAAM,KAAA;AAGzB,UAAI7P,MAAS,MAAMA,MAAShB,EAAM,MAAM;AACtC,QAAAwL,EAAA;AAEA;AAAA,MACF;AAEA,MAAAD,EAAQvK,CAAI;AAAA,IACd;2BAIEQ,EA8BYH,EAAAyK,EAAA,GAAA;AAAA,MA9BO,MAAMzK,EAAAiK,CAAA;AAAA,sDAAAA,EAAI,QAAA/J,IAAA;AAAA,MAAG,OAAOF,EAAAjB,CAAA,EAAC,YAAA;AAAA,MAAiB,OAAO;AAAA,IAAA;MAiBnD,UACT,MAUW;AAAA,QAVXgB,EAUWC,EAAA0K,EAAA,GAAA,EAVD,MAAK,QAAI;AAAA,qBACjB,MAAiF;AAAA,YAAjF3K,EAAiFC,EAAAoH,EAAA,GAAA;AAAA,cAAtE,SAAQ;AAAA,cAAW,gCAAOpH,EAAAmK,CAAA,EAAA;AAAA,YAAO;yBAAI,MAAqB;AAAA,oBAAlBnK,EAAAjB,CAAA,EAAC,YAAA,CAAA,GAAA,CAAA;AAAA,cAAA;;;YACpDgB,EAOYC,EAAAoH,EAAA,GAAA;AAAA,cANV,MAAK;AAAA,cACL,eAAY;AAAA,cACX,MAAMpH,EAAAsP,CAAA;AAAA,cACN,UAAUE,EAAA,MAAM,WAAI;AAAA,YAAA;yBAErB,MAA0B;AAAA,oBAAvBxP,EAAAmC,CAAA,EAAK,aAAA,CAAA,GAAA,CAAA;AAAA,cAAA;;;;;;;iBAtBd,MAWO;AAAA,QAXP6E,EAWO,QAAA;AAAA,UAXA,IAAIhH,EAAAsP,CAAA;AAAA,mBAAY;AAAA,UAAJ,KAAIjF;AAAA,UAAS,aAAgBG,GAAM,CAAA,SAAA,CAAA;AAAA,QAAA;UACpDzK,EASeC,EAAAgK,EAAA,GAAA;AAAA,YATA,OAAOhK,EAAAjB,CAAA,EAAC,iBAAA;AAAA,YAAqB,UAAA;AAAA,UAAA;uBAI1C,MAIE;AAAA,cAJFgB,EAIEC,EAAA2K,EAAA,GAAA;AAAA,4BAHS6E,EAAA;AAAA,8DAAAA,EAAK,QAAAtP;AAAA,gBACb,cAAYF,EAAAjB,CAAA,EAAC,iBAAA;AAAA,gBACb,iBAAuByL,GAAM,CAAA,SAAA,CAAA,GAAA,CAAA,OAAA,CAAA;AAAA,cAAA;;;;;;;;;;;;;;;ACtCxC,UAAM7L,IAAQC,GAER,EAAE,MAAAqL,GAAM,SAAAC,GAAS,SAAAC,EAAA,IAAYC,GAAA,GAE7BnL,IAAUP,GAAA,GACVgD,IAAMnB,GAActB,CAAO;AACjC,IAAAT,GAAA;AAEA,UAAMO,IAAIC,EAAa,WAAW,GAC5BoD,IAAUC,GAAA,GAIVd,IAAOiB;AAAA,MACX,CAAC,GAAG7D,EAAM,IAAI,EAAE,KAAK,CAAC4J,GAAKkH,MAAUA,EAAM,iBAAiBlH,EAAI,cAAc,EAAE,CAAC,GAAG,MAAM;AAAA,IAAA,GAGtFxF,IAAUP,EAAI,EAAK,GACnBhB,IAAWgB,EAAI,EAAI,GACnBmC,IAAQnC,EAAmB,IAAI,GAE/BpD,IAAUD;AAAA,MAAS,MACvBR,EAAM,KAAK,IAAI,CAAC8K,OAAS,EAAE,OAAOA,EAAI,IAAI,OAAO,GAAGA,EAAI,KAAK,MAAMA,EAAI,cAAc,KAAK;AAAA,IAAA,GAGtFiG,IAAWvQ,EAAS,MAAMR,EAAM,KAAK,KAAK,CAAC8K,MAAQA,EAAI,OAAOlI,EAAK,KAAK,KAAK,IAAI,GAEjFoO,IAAQxQ,EAAS,MAAMR,EAAM,KAAK,OAAO,CAAC8K,MAAQA,EAAI,OAAOlI,EAAK,KAAK,CAAC,GASxEqO,IAAWzQ,EAAS,MAAMwQ,EAAM,MAAM,OAAO,CAACE,GAAKpG,MAAQoG,IAAMpG,EAAI,gBAAgB,CAAC,CAAC,GAEvFqG,IAAY3Q,EAAS,MAAMwQ,EAAM,MAAM,IAAI,CAAClG,MAAQA,EAAI,IAAI,EAAE,OAAO,OAAO,CAAa;AAE/F,mBAAee,IAAwB;AACrC,UAAI,EAAAkF,EAAS,UAAU,QAAQC,EAAM,MAAM,WAAW,KAAK5M,EAAQ,QAEnE;AAAA,QAAAA,EAAQ,QAAQ,IAChB4B,EAAM,QAAQ;AAEd,YAAI;AACF,UAAAuF;AAAA,YACE,MAAMxI,EAAI;AAAA,cACRiO,EAAM,MAAM,IAAI,CAAClG,MAAQA,EAAI,EAAE;AAAA,cAC/BlI,EAAK;AAAA,cACLC,EAAS;AAAA,YAAA;AAAA,UACX;AAAA,QAEJ,SAAS6D,GAAS;AAChB,UAAAV,EAAM,QAAQvC,EAAQiD,CAAO;AAAA,QAC/B,UAAA;AACE,UAAAtC,EAAQ,QAAQ;AAAA,QAClB;AAAA;AAAA,IACF;2BAIE5C,EAoDYH,EAAAyK,EAAA,GAAA;AAAA,MApDO,MAAMzK,EAAAiK,CAAA;AAAA,sDAAAA,EAAI,QAAA/J,IAAA;AAAA,MAAG,OAAOF,EAAAjB,CAAA,EAAC,iBAAA;AAAA,MAAsB,OAAO;AAAA,IAAA;MA4CxD,UACT,MAKW;AAAA,QALXgB,EAKWC,EAAA0K,EAAA,GAAA,EALD,MAAK,QAAI;AAAA,qBACjB,MAAiF;AAAA,YAAjF3K,EAAiFC,EAAAoH,EAAA,GAAA;AAAA,cAAtE,SAAQ;AAAA,cAAW,gCAAOpH,EAAAmK,CAAA,EAAA;AAAA,YAAO;yBAAI,MAAqB;AAAA,oBAAlBnK,EAAAjB,CAAA,EAAC,YAAA,CAAA,GAAA,CAAA;AAAA,cAAA;;;YACpDgB,EAEYC,EAAAoH,EAAA,GAAA;AAAA,cAFD,MAAK;AAAA,cAAW,SAASrE,EAAA;AAAA,cAAU,UAAU4M,EAAA,MAAM,WAAM;AAAA,cAAS,SAAOnF;AAAA,YAAA;yBAClF,MAA4B;AAAA,oBAAzBxK,EAAAjB,CAAA,EAAC,mBAAA,CAAA,GAAA,CAAA;AAAA,cAAA;;;;;;;iBA/CV,MAyCM;AAAA,QAzCNiI,EAyCM,OAzCNlH,IAyCM;AAAA,UAtCJkH,EAQM,OARNS,IAQM;AAAA,oBAPJ5H,EAEW2G,IAAA,MAAAmB,GAFagI,EAAA,OAAK,CAAZlG,YAAjBtJ,EAEWH,EAAA8G,EAAA,GAAA;AAAA,cAFqB,KAAK2C,EAAI;AAAA,cAAI,MAAK;AAAA,cAAK,OAAA;AAAA,YAAA;yBACrD,MAAe;AAAA,gBAAZpJ,EAAAC,EAAAmJ,EAAI,KAAK,IAAG,QAAGnJ,EAAGmJ,EAAI,cAAc,GAAA,CAAA;AAAA,cAAA;;;YAEzC1J,EAAwCC,EAAA4M,EAAA,GAAA;AAAA,cAA/B,MAAK;AAAA,cAAc,MAAK;AAAA,YAAA;YACjB8C,EAAA,cAAhBvP,EAEWH,EAAA8G,EAAA,GAAA;AAAA;cAFe,MAAK;AAAA,cAAU,MAAK;AAAA,cAAK,OAAA;AAAA,YAAA;yBACjD,MAAoB;AAAA,gBAAjBzG,EAAAC,EAAAoP,EAAA,MAAS,KAAK,IAAG,QAAGpP,EAAGoP,EAAA,MAAS,cAAc,GAAA,CAAA;AAAA,cAAA;;;;UAIrD3P,EAEeC,EAAAgK,EAAA,GAAA;AAAA,YAFA,OAAOhK,EAAAjB,CAAA,EAAC,gBAAA;AAAA,UAAA;uBACrB,MAAiF;AAAA,cAAjFgB,EAAiFC,EAAAC,EAAA,GAAA;AAAA,4BAA7DsB,EAAA;AAAA,8DAAAA,EAAI,QAAArB;AAAA,gBAAG,SAASd,EAAA;AAAA,gBAAU,cAAYY,EAAAjB,CAAA,EAAC,gBAAA;AAAA,cAAA;;;;UAG7DiI,EASM,OATNY,IASM;AAAA,YARJ7H,EAAmEC,EAAA+P,EAAA,GAAA;AAAA,0BAA7CvO,EAAA;AAAA,4DAAAA,EAAQ,QAAAtB;AAAA,cAAG,OAAOF,EAAAjB,CAAA,EAAC,oBAAA;AAAA,YAAA;YACzCgB,EAMUC,EAAA+H,EAAA,GAAA;AAAA,cAND,MAAK;AAAA,cAAK,MAAK;AAAA,YAAA;yBACtB,MAIE;AAAA,gBAHA1H,EAAAC,EAAAwP,EAAA,MAAU,WAAM,IAAuB9P,EAAAjB,CAAA,EAAC,yBAAA,IAA4CiB,EAAAjB,CAAA,EAAC,2BAAA,EAAA,WAAyC+Q,EAAA,MAAU,KAAI,IAAA,GAAA,CAAA,GAAA,CAAA;AAAA,cAAA;;;;UAOlJ/P,EAUEC,EAAAuJ,EAAA,GAAA;AAAA,YATA,MAAK;AAAA,YACL,SAAQ;AAAA,YACP,aAAwBvJ,EAAAjB,CAAA,EAAC,qBAAA;AAAA,qBAA2C6Q,EAAA;AAAA,cAA2B,KAAAF,EAAA,OAAU,SAAK;AAAA,cAA4B,QAAAC,EAAA,MAAM;AAAA,YAAA;;UASnIhL,EAAA,cAAhBxE,EAA2EH,EAAAuJ,EAAA,GAAA;AAAA;YAApD,MAAK;AAAA,YAAS,SAAQ;AAAA,YAAQ,aAAa5E,EAAA;AAAA,UAAA;;;;;;;;;;;;ACrFxE,UAAMhG,IAAQC,GAERK,IAAUP,GAAA,GACVgD,IAAMnB,GAActB,CAAO,GAC3B0C,IAAQC,GAAA,GACRC,IAASC,GAAA;AACf,IAAAtD,GAAA;AAEA,UAAM,IAAIQ,EAAa,WAAW,GAC5BmD,IAAQnD,EAAa,YAAY,GAEjCoD,IAAUC,GAAA,GAEVsG,IAAOnG,EAAqB,IAAI,GAChCK,IAAUL,EAAI,EAAI,GAClBkM,IAAWlM,EAAc,EAAE,GAC3BO,IAAUP,EAAI,EAAK,GAEnBwN,IAAShF,GAAsCiF,EAAe,GAE9DnG,IAASkB,GAA2CkF,EAAe,GACnEC,IAAQnF,GAA2CoF,EAAc,GAEjEjN,IAAYhE,EAAS,MAAMF,EAAQ,IAAI,sBAAsB,CAAC,GAE9D8E,IAAQ5E;AAAA,MACZ,MACEF,EAAQ,MAAM,UAAU,QAAQ,KAAK,CAACM,MAAWA,EAAO,OAAO,MAAM,GAAG,SACxE,EAAE,aAAa;AAAA,IAAA,GAIb2L,IAAO/L,EAAmB;AAAA,MAC9B,KAAK,MAAO,OAAOwC,EAAM,MAAM,QAAS,WAAWA,EAAM,MAAM,OAAO;AAAA,MACtE,KAAK,CAAC9C,MAAU;AACd,QAAKgD,EAAO,QAAQ;AAAA,UAClB,OAAO,EAAE,GAAGF,EAAM,OAAO,MAAM9C,MAAU,KAAK,SAAY,OAAOA,CAAK,GAAG,MAAM,OAAA;AAAA,QAAU,CAC1F;AAAA,MACH;AAAA,IAAA,CACD,GAEKwR,IAASlR,EAAS,MAAMwJ,EAAK,OAAO,WAAW,EAAE,OAAO,GAAG,OAAO,GAAG,SAAS,GAAG,GAEjFyC,IAAQjM,EAAoB,MAAM;AAAA,MACtC,EAAE,OAAO,IAAI,OAAO,EAAE,cAAc,GAAG,OAAOkR,EAAO,MAAM,MAAA;AAAA,MAC3D,EAAE,OAAO,SAAS,OAAO,EAAE,gBAAgB,GAAG,OAAOA,EAAO,MAAM,MAAA;AAAA,MAClE,EAAE,OAAO,WAAW,OAAO,EAAE,kBAAkB,GAAG,OAAOA,EAAO,MAAM,QAAA;AAAA,IAAQ,CAC/E,GAEK7H,IAAOrJ,EAAS,MAAMwJ,EAAK,OAAO,QAAQ,EAAE,GAE5CjJ,IAASP,EAAS,MAAMqJ,EAAK,MAAM,OAAO,CAACiB,MAAQiF,EAAS,MAAM,SAASjF,EAAI,EAAY,CAAC,CAAC,GAS7F8B,IAAUpM,EAAgC,MAAM;AAAA,MACpD,EAAE,KAAK,SAAS,OAAO,EAAE,kBAAkB,GAAG,UAAU,KAAK,UAAU,GAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAKvE,EAAE,KAAK,QAAQ,OAAO,EAAE,oBAAoB,GAAG,OAAO,KAAK,WAAW,IAAA;AAAA,MACtE;AAAA,QACE,KAAK;AAAA,QACL,OAAO,EAAE,qBAAqB;AAAA,QAC9B,OAAO;AAAA,QACP,OAAO;AAAA,QACP,UAAU;AAAA,MAAA;AAAA;AAAA;AAAA;AAAA,MAKZ,EAAE,KAAK,YAAY,OAAO,EAAE,qBAAqB,GAAG,OAAO,KAAK,WAAW,IAAA;AAAA,MAC3E,EAAE,KAAK,WAAW,OAAO,IAAI,OAAOqM,IAAc,OAAO,QAAA;AAAA,IAAQ,CAClE;AAED,aAAS5K,EAAMkD,GAA8B;AAC3C,aAAO;AAAA,QACL,GAAGA,GAAO,WAAW,OAAOnC,EAAM,MAAM,KAAM,WAAWA,EAAM,MAAM,IAAI;AAAA,QACzE,OAAOuJ,EAAK,UAAU;AAAA,QACtB,SAASA,EAAK,UAAU;AAAA;AAAA;AAAA,QAGxB,MAAMoF,GAAOxM,CAAK;AAAA,QAClB,MAAMA,GAAO,SAAS,OAAOnC,EAAM,MAAM,QAAQ,CAAC,KAAK;AAAA,QACvD,UAAUmC,GAAO;AAAA,MAAA;AAAA,IAErB;AAEA,mBAAeS,EAAKT,GAAmC;AACrD,MAAAjB,EAAQ,QAAQ;AAEhB,UAAI;AACF,QAAA8F,EAAK,QAAQ,MAAMjH,EAAI,SAASd,EAAMkD,CAAK,CAAC;AAAA,MAC9C,SAASa,GAAO;AACd,QAAAC,EAAM,OAAOxC,EAAQuC,CAAK,CAAC;AAAA,MAC7B,UAAA;AACE,QAAA9B,EAAQ,QAAQ;AAAA,MAClB;AAAA,IACF;AASA,aAASyN,GAAOxM,GAAsC;AACpD,YAAM2K,IAAO3K,GAAO,QAAQyM,EAAA;AAE5B,UAAI,CAAC9B,EAAM;AAEX,YAAM+B,IAAM/B,EAAK,QAAQ,UAAU,SAAS,YACtCgC,KAAO,GAAGhC,EAAK,UAAU,SAAS,MAAM,EAAE,GAAG+B,CAAG;AAEtD,aAAOC,OAAS,aAAa,SAAYA;AAAA,IAC3C;AAGA,aAASF,IAAkC;AACzC,YAAM1R,IAAQ,OAAO8C,EAAM,MAAM,QAAS,WAAWA,EAAM,MAAM,OAAO;AAExE,UAAI9C,MAAU,GAAI,QAAO;AAEzB,YAAM6R,IAAO7R,EAAM,WAAW,GAAG;AAGjC,aAAO,EAAE,MAFI6R,IAAO7R,EAAM,MAAM,CAAC,IAAIA,OAAW,SAAS,UAAU,kBAErD,OAAO6R,IAAO,SAAS,MAAA;AAAA,IACvC;AAGA,aAAS5E,GAAQhI,GAAyB;AACxC,YAAMiI,IAA2B;AAAA,QAC/B,GAAGpK,EAAM;AAAA,QACT,GAAGmC,EAAM,WAAW,KAAK,SAAYA,EAAM;AAAA,QAC3C,MAAMwM,GAAOxM,CAAK;AAAA,QAClB,MAAMA,EAAM,SAAS,IAAI,SAAY,OAAOA,EAAM,IAAI;AAAA,MAAA;AAGxD,MAAKjC,EAAO,QAAQ,EAAE,OAAOkK,GAAQ,GAChCxH,EAAKT,CAAK;AAAA,IACjB;AAEA,IAAAoC,GAAMgF,GAAM,MAAM;AAGhB,MAAAwD,EAAS,QAAQ,CAAA,GACZnK,EAAA;AAAA,IACP,CAAC,GAEIA,EAAA;AAEL,mBAAeqE,KAAqB;AAClC,MAAI,MAAMkB,EAAO,EAAE,WAASvF,EAAA;AAAA,IAC9B;AAUA,mBAAeoM,GAAYlH,GAA4B;AACrD,YAAM1F,IAAQ,MAAMiM,EAAO,EAAE,MAAMvG,EAAI,OAAO;AAE9C,MAAI1F,KAAO,MAAM6M,GAAMnH,GAAK,EAAE,OAAA1F,EAAAA,GAAS,EAAE,aAAa,CAAC;AAAA,IACzD;AAEA,mBAAe8M,EAAYpH,GAAaqH,GAAiC;AACvE,YAAMF,GAAMnH,GAAK,EAAE,SAAAqH,KAAqB,EAAVA,IAAY,iBAAoB,aAAN,CAAoB;AAAA,IAC9E;AAQA,mBAAeF,GACbnH,GACAzI,GACAwL,GACe;AACf,UAAI;AACF,cAAMuE,KAAQ,MAAMrP,EAAI,QAAQ+H,EAAI,IAAIzI,CAAK;AAE7C,QAAI2H,EAAK,UACPA,EAAK,QAAQ;AAAA,UACX,GAAGA,EAAK;AAAA,UACR,MAAMA,EAAK,MAAM,KAAK,IAAI,CAACvE,MAASA,EAAI,OAAO2M,GAAM,KAAKA,KAAQ3M,CAAI;AAAA,QAAA,IAI1EQ,EAAM,QAAQ4H,CAAI;AAAA,MACpB,SAAS7H,IAAO;AACd,QAAAC,EAAM,OAAOxC,EAAQuC,EAAK,CAAC;AAAA,MAC7B;AAAA,IACF;AAEA,mBAAemE,EAAOW,GAA4B;AAYhD,UAXe,MAAMjE,GAAQ;AAAA,QAC3B,OAAO,EAAE,oBAAoB,EAAE,OAAOiE,EAAI,OAAO;AAAA,QACjD,SACEA,EAAI,iBAAiB,IACjB,EAAE,wBAAwB,EAAE,OAAOA,EAAI,eAAA,CAAgB,IACvD,EAAE,iBAAiB;AAAA,QACzB,aAAa,EAAE,YAAY;AAAA,QAC3B,YAAY,EAAE,YAAY;AAAA,QAC1B,MAAM;AAAA,MAAA,CACP;AAID,YAAI;AACF,gBAAM/H,EAAI,UAAU+H,EAAI,EAAE,GAC1B7E,EAAM,QAAQ,EAAE,aAAa,CAAC,GAC9B8J,EAAS,QAAQA,EAAS,MAAM,OAAO,CAAC8B,MAAQA,MAAQ/G,EAAI,EAAE,GAC9D,MAAMlF,EAAA;AAAA,QACR,SAASI,GAAO;AACd,UAAAC,EAAM,OAAOxC,EAAQuC,CAAK,CAAC;AAAA,QAC7B;AAAA,IACF;AASA,UAAMqM,IAAc7R,EAAsB,MAAM;AAAA,MAC9C;AAAA,QACE,KAAK;AAAA,QACL,MAAM;AAAA,QACN,OAAO,EAAE,WAAW;AAAA,QACpB,UAAU4D,EAAQ;AAAA,QAClB,KAAK,MAAA;AAAM,UAAKkO,GAAa,OAAO;AAAA;AAAA,MAAA;AAAA,MAEtC;AAAA,QACE,KAAK;AAAA,QACL,MAAM;AAAA,QACN,OAAO,EAAE,aAAa;AAAA,QACtB,UAAUlO,EAAQ;AAAA,QAClB,KAAK,MAAA;AAAM,UAAKkO,GAAa,SAAS;AAAA;AAAA,MAAA;AAAA,MAExC;AAAA,QACE,KAAK;AAAA,QACL,MAAM;AAAA,QACN,OAAO,EAAE,YAAY;AAAA,QACrB,QAAQ;AAAA,QACR,UAAUlO,EAAQ;AAAA,QAClB,KAAK,MAAA;AAAM,UAAKkO,GAAa,QAAQ;AAAA;AAAA,MAAA;AAAA,IACvC,CACD;AAED,aAAS/E,EAAWzC,GAA0B;AAC5C,UAAI,CAACtG,EAAU,MAAO,QAAO,CAAA;AAE7B,YAAMmD,IAAuB;AAAA,QAC3B,EAAE,KAAK,UAAU,MAAM,QAAQ,OAAO,EAAE,YAAY,GAAG,KAAK;AAAM,UAAKqK,GAAYlH,CAAG;AAAA,UAAA;AAAA,MAAE;AAG1F,aAAIA,EAAI,OACNnD,EAAQ,KAAK;AAAA,QACX,KAAK;AAAA,QACL,MAAM;AAAA,QACN,OAAO,EAAE,kBAAkB;AAAA,QAC3B,MAAMmD,EAAI;AAAA,MAAA,CACX,GAGHnD,EAAQ,KAAK;AAAA,QACX,KAAK;AAAA,QACL,MAAM;AAAA,QACN,OAAO,EAAE,mBAAmB;AAAA,QAC5B,KAAK,MAAA;AAAM,UAAKzE,EAAO,KAAK,EAAE,MAAM,GAAGlD,EAAM,IAAI,aAAa,OAAO,EAAE,KAAK,OAAO8K,EAAI,EAAE,KAAK;AAAA;AAAA,MAAA,CAC/F,GAEDnD,EAAQ;AAAA,QACNmD,EAAI,UACA;AAAA,UACE,KAAK;AAAA,UACL,MAAM;AAAA,UACN,OAAO,EAAE,WAAW;AAAA,UACpB,KAAK,MAAA;AAAM,YAAKoH,EAAYpH,GAAK,EAAK;AAAA;AAAA,QAAA,IAExC;AAAA,UACE,KAAK;AAAA,UACL,MAAM;AAAA,UACN,OAAO,EAAE,aAAa;AAAA,UACtB,KAAK,MAAA;AAAM,YAAKoH,EAAYpH,GAAK,EAAI;AAAA;AAAA,QAAA;AAAA,MACvC,GAGNnD,EAAQ,KAAK;AAAA,QACX,KAAK;AAAA,QACL,MAAM;AAAA,QACN,OAAO,EAAE,YAAY;AAAA,QACrB,QAAQ;AAAA,QACR,KAAK,MAAA;AAAM,UAAKwC,EAAOW,CAAG;AAAA;AAAA,MAAA,CAC3B,GAEMnD;AAAAA,IACT;AAGA,aAAS4K,GAAKC,GAA4C;AACxD,aAAOA,MAAa,YAAY,UAAU;AAAA,IAC5C;AAEA,mBAAeC,KAA+B;AAC5C,UAAI1R,EAAO,MAAM,SAAS,EAAG;AAE7B,YAAM2R,IAAS,MAAMlB,EAAM,EAAE,MAAMzQ,EAAO,OAAO;AAEjD,MAAK2R,MAELzM,EAAM,QAAQ,EAAE,cAAc,EAAE,KAAKyM,EAAO,IAAI,OAAO,OAAOA,EAAO,eAAA,CAAgB,CAAC,GACtF3C,EAAS,QAAQ,CAAA,GACjB,MAAMnK,EAAA;AAAA,IACR;AAEA,mBAAe0M,GAAa3P,GAAuD;AACjF,YAAMD,IAAM3B,EAAO,MAAM,IAAI,CAAC+J,MAAQA,EAAI,EAAE;AAE5C,UAAI,EAAApI,EAAI,WAAW,KAAK0B,EAAQ,QAEhC;AAAA,YAAIzB,MAAW,UAAU;AACvB,gBAAMoE,IAAOhG,EAAO,MAAM,OAAO,CAACmQ,GAAKpG,MAAQoG,IAAMpG,EAAI,gBAAgB,CAAC;AAW1E,cAAI,CATW,MAAMjE,GAAQ;AAAA,YAC3B,OAAO,EAAE,yBAAyB,EAAE,OAAOnE,EAAI,QAAQ;AAAA,YACvD,SACEqE,IAAO,IAAI,EAAE,6BAA6B,EAAE,OAAOA,EAAA,CAAM,IAAI,EAAE,sBAAsB;AAAA,YACvF,aAAa,EAAE,YAAY;AAAA,YAC3B,YAAY,EAAE,YAAY;AAAA,YAC1B,MAAM;AAAA,UAAA,CACP,EAEY;AAAA,QACf;AAEA,QAAA3C,EAAQ,QAAQ;AAEhB,YAAI;AACF,gBAAM6M,IAAW,MAAMlO,EAAI,SAASL,GAAKC,CAAM;AAE/C,UAAAsD,EAAM;AAAA,YACJtD,MAAW,WACP,EAAE,oBAAoB,EAAE,OAAOsO,EAAA,CAAU,IACzCtO,MAAW,UACT,EAAE,oBAAoB,EAAE,OAAOsO,EAAA,CAAU,IACzC,EAAE,qBAAqB,EAAE,OAAOA,EAAA,CAAU;AAAA,UAAA,GAElDlB,EAAS,QAAQ,CAAA,GACjB,MAAMnK,EAAA;AAAA,QACR,SAASI,GAAO;AACd,UAAAC,EAAM,OAAOxC,EAAQuC,CAAK,CAAC;AAAA,QAC7B,UAAA;AACE,UAAA5B,EAAQ,QAAQ;AAAA,QAClB;AAAA;AAAA,IACF;AAGA,UAAMuD,KAAUnH;AAAA,MAAyB,MACvCgE,EAAU,QACN,CAAC,EAAE,KAAK,OAAO,OAAO,EAAE,SAAS,GAAG,MAAM,QAAQ,SAAS,IAAM,KAAK;AAAM,QAAKyF,GAAA;AAAA,QAAI,CAAG,IACxF,CAAA;AAAA,IAAC;sBAKLhJ,EAAA,GAAAC,EA+FM,OA/FNC,IA+FM;AAAA,MA9FJC,EAsDiBC,EAAA0M,EAAA,GAAA;AAAA,QAtDO,MAAMxB,EAAA;AAAA,gDAAAA,EAAI,QAAAhL;AAAA,QAAG,OAAO6D,EAAA;AAAA,QAAQ,OAAOqH,EAAA;AAAA,QAAQ,SAAS9E,GAAA;AAAA,MAAA;mBAC1E,MAoDW;AAAA,UApDXvG,EAoDWC,EAAA2M,EAAA,GAAA;AAAA,YAnDD,UAAU+B,EAAA;AAAA,wDAAAA,EAAQ,QAAAxO;AAAA,YACzB,MAAMyI,EAAA;AAAA,YACN,SAAS4C,EAAA;AAAA,YACV,WAAQ;AAAA,YACR,YAAA;AAAA,YACA,OAAA;AAAA,YACA,OAAA;AAAA,YACA,QAAO;AAAA,YACN,SAAS1I,EAAA;AAAA,YACT,YAAYM,EAAA;AAAA,YACZ,oBAAkBnD,EAAAmC,CAAA,EAAK,oBAAA;AAAA,YACvB,oBAAkBnC,EAAAmC,CAAA,EAAK,oBAAA;AAAA,YACvB,eAAa;AAAA,YACb,sBAAoBnC,EAAA,CAAA,EAAC,YAAA;AAAA,YACrB,cAAYwI,QAAK,iBAAiB3F,EAAA,QAAU7C,EAAA,CAAA,EAAC,WAAA,IAAgB;AAAA,YAC7D,cAAY+D,EAAA;AAAA,YACZ,eAAc+H;AAAA,UAAA;YAEJ,cAAU9C,EACnB,CAA2C,EADpB,KAAA5E,QAAG;AAAA,cAC1BrE,EAA2CC,EAAA+H,EAAA,GAAA,EAAlC,UAAA,MAAQ;AAAA,2BAAC,MAAe;AAAA,kBAAZ1H,EAAAC,EAAA8D,EAAI,KAAK,GAAA,CAAA;AAAA,gBAAA;;;;YAGrB,aAAS4E,EAClB,CAEU,EAHY,KAAA5E,QAAG;AAAA,cACzBrE,EAEUC,EAAA+H,EAAA,GAAA;AAAA,gBAFD,MAAK;AAAA,gBAAK,MAAK;AAAA,gBAAQ,MAAA;AAAA,gBAAK,UAAA;AAAA,cAAA;2BACnC,MAAqC;AAAA,sBAAlC3D,EAAI,QAAQpE,EAAA,CAAA,EAAC,gBAAA,CAAA,GAAA,CAAA;AAAA,gBAAA;;;;YAIT,uBAAmBgJ,EAC5B,CAEU,EAHsB,KAAA5E,QAAG;AAAA,cACnCrE,EAEUC,EAAA+H,EAAA,GAAA;AAAA,gBAFA,MAAM3D,EAAI,mBAAc,IAAA,UAAA;AAAA,cAAA;2BAChC,MAAwB;AAAA,kBAArB/D,EAAAC,EAAA8D,EAAI,cAAc,GAAA,CAAA;AAAA,gBAAA;;;;YASd,iBAAa4E,EACtB,CAEa,EAHa,KAAA5E,QAAG;AAAA,cACXA,EAAI,aAAQ,eAA9BjE,EAEaH,EAAA+M,EAAA,GAAA;AAAA;gBAF+B,SAAS/M,EAAA,CAAA,EAAC,wBAAA;AAAA,cAAA;2BACpD,MAAoF;AAAA,kBAApFD,EAAoFC,EAAA+H,EAAA,GAAA;AAAA,oBAA3E,MAAK;AAAA,oBAAM,MAAMmJ,GAAK9M,EAAI,QAAQ;AAAA,kBAAA;+BAAG,MAA4B;AAAA,0BAAzBpE,EAAA,CAAA,EAAC,mBAAA,CAAA,GAAA,CAAA;AAAA,oBAAA;;;;;6CAEpDG,EAEUH,EAAA+H,EAAA,GAAA;AAAA;gBAFM,MAAK;AAAA,gBAAM,MAAMmJ,GAAK9M,EAAI,QAAQ;AAAA,cAAA;2BAChD,MAAuC;AAAA,sBAApCpE,EAAA,CAAA,EAAC,gBAAiBoE,EAAI,QAAQ,EAAA,CAAA,GAAA,CAAA;AAAA,gBAAA;;;;YAI1B,gBAAY4E,EACrB,CAA6D,EADpC,KAAA5E,QAAG;AAAA,cAC5BrE,EAA6DC,EAAAqN,EAAA,GAAA;AAAA,gBAA/C,SAASnB,EAAW9H,CAAG;AAAA,gBAAI,OAAOA,EAAI;AAAA,cAAA;;;;;;;MAUrC1E,EAAA,MAAO,SAAM,UAAlCS,EAgCgBH,EAAAkH,EAAA,GAAA;AAAA;QAhCwB,OAAM;AAAA,QAAe,QAAA;AAAA,MAAA;QAChD,SACT,MAAoF;AAAA,UAApFnH,EAAoFC,EAAA+H,EAAA,GAAA,EAA3E,QAAO,YAAQ;AAAA,uBAAC,MAAiD;AAAA,kBAA9C/H,EAAA,CAAA,EAAC,gBAAA,EAAA,OAA0BN,EAAA,MAAO,OAAA,CAAM,CAAA,GAAA,CAAA;AAAA,YAAA;;;;mBAWtE,MAGY;AAAA,UAHZK,EAGYC,EAAAoH,EAAA,GAAA;AAAA,YAHD,MAAK;AAAA,YAAW,UAAU1H,EAAA,MAAO,cAAcqD,EAAA;AAAA,YAAU,SAAOqO;AAAA,UAAA;YAC9D,QAAK,MAAuB;AAAA,cAAvBrR,EAAuBC,EAAA4M,EAAA,GAAA,EAAd,MAAK,QAAM;AAAA,YAAA;uBAAc,MAClD;AAAA,cADkDvM,EAAA,QAC/CL,EAAA,CAAA,EAAC,WAAA,CAAA,GAAA,CAAA;AAAA,YAAA;;;UAYND,EAIEC,EAAAqN,EAAA,GAAA;AAAA,YAHA,MAAK;AAAA,YACJ,SAAS2D,EAAA;AAAA,YACT,OAAOhR,EAAA,CAAA,EAAC,gBAAA,EAAA,OAA0BN,EAAA,MAAO,OAAA,CAAM;AAAA,UAAA;;;;;;;ACxejD,SAAS4R,GAAerF,IAAO,SAA4B;AAChE,SAAO;AAAA,IACL,KAAK;AAAA,IACL,MAAM,GAAGA,CAAI;AAAA,IACb,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,OAAO;AAAA,IACP,OAAO,CAAClL,OAAQ,EAAE,MAAM,GAAGkL,CAAI,aAAa,OAAO,EAAE,QAAQ,OAAOlL,CAAE,IAAE;AAAA,IACxE,OAAO;AAAA,MACL,KAAK;AAAA,MACL,OAAO;AAAA,MACP,cAAc;AAAA,MACd,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,cAAc;AAAA,MACd,OAAO;AAAA,MACP,cAAc;AAAA,MACd,kBAAkB;AAAA,MAClB,eAAe;AAAA,MACf,SAAS;AAAA,MACT,OAAO;AAAA,MACP,eAAe;AAAA,MACf,cAAc;AAAA,MACd,kBAAkB;AAAA,IAAA;AAAA,EACpB;AAEJ;AAEO,SAASwQ,GAAKnS,IAAuB,IAAmB;AAC7D,QAAM6M,IAAO7M,EAAQ,QAAQ;AAE7B,SAAO;AAAA,IACL;AAAA,MACE,IAAI;AAAA,MACJ,MAAM,GAAG6M,CAAI;AAAA,MACb,QAAQ;AAAA;AAAA;AAAA,QAGN;AAAA,UACE,MAAM,GAAGA,CAAI;AAAA,UACb,MAAM;AAAA,UACN,WAAWuF;AAAA,UACX,OAAO,EAAE,MAAMvF,EAAA;AAAA,QAAK;AAAA,QAEtB;AAAA,UACE,MAAM,GAAGA,CAAI;AAAA,UACb,MAAM;AAAA,UACN,WAAWwF;AAAA,UACX,OAAO,EAAE,MAAMxF,EAAA;AAAA,QAAK;AAAA,MACtB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAgBF,OAAO;AAAA,QACL,mBAAmB,EAAE,WAAWyF,IAAa,MAAM,QAAA;AAAA,QACnD,qBAAqB,EAAE,WAAWC,IAAe,MAAM,QAAA;AAAA,QACvD,mBAAmB,EAAE,WAAWC,IAAa,MAAM,QAAA;AAAA,QACnD,sBAAsB,EAAE,WAAWC,IAAgB,MAAM,QAAA;AAAA,QACzD,wBAAwB,EAAE,WAAWC,IAAkB,MAAM,UAAA;AAAA,QAC7D,sBAAsB,EAAE,WAAWC,IAAgB,MAAM,UAAA;AAAA,MAAU;AAAA,IACrE;AAAA,IAEF;AAAA,MACE,IAAI;AAAA,MACJ,MAAM,GAAG9F,CAAI;AAAA;AAAA;AAAA,MAGb,QAAQ+F,GAAeV,GAAerF,CAAI,CAAC;AAAA,IAAA;AAAA,IAE7C;AAAA,MACE,IAAI;AAAA,MACJ,MAAM,GAAGA,CAAI;AAAA,MACb,QAAQ;AAAA,QACN;AAAA,UACE,MAAM,GAAGA,CAAI;AAAA,UACb,MAAM;AAAA,UACN,WAAWgG;AAAA,UACX,OAAO,EAAE,MAAMhG,EAAA;AAAA,QAAK;AAAA,MACtB;AAAA,IACF;AAAA,IAEF;AAAA;AAAA;AAAA,MAGE,IAAI;AAAA,MACJ,MAAM,GAAGA,CAAI;AAAA,MACb,QAAQ;AAAA,QACN;AAAA,UACE,MAAM,GAAGA,CAAI;AAAA,UACb,MAAM;AAAA,UACN,WAAWiG;AAAA,UACX,OAAO,EAAE,MAAMjG,EAAA;AAAA,QAAK;AAAA,MACtB;AAAA,IACF;AAAA,EACF;AAEJ;"}