@webx-ui/module-blog 0.8.3 → 0.8.4
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/{ArticleEditorPage-DViFCNvm.js → ArticleEditorPage-Bkb5YrLy.js} +2 -2
- package/dist/{ArticleEditorPage-DViFCNvm.js.map → ArticleEditorPage-Bkb5YrLy.js.map} +1 -1
- package/dist/{ArticlesPage-CI-Nwhpy.js → ArticlesPage-BnHi8gF0.js} +173 -154
- package/dist/ArticlesPage-BnHi8gF0.js.map +1 -0
- package/dist/{AuthorsPage-BN6sRqyE.js → AuthorsPage-DSLsX44M.js} +2 -2
- package/dist/{AuthorsPage-BN6sRqyE.js.map → AuthorsPage-DSLsX44M.js.map} +1 -1
- package/dist/{TagsPage-rFfpCqPs.js → TagsPage-BnLVnx5a.js} +2 -2
- package/dist/{TagsPage-rFfpCqPs.js.map → TagsPage-BnLVnx5a.js.map} +1 -1
- package/dist/{index-DqN5h0Ej.js → index-ChbP89HC.js} +418 -417
- package/dist/index-ChbP89HC.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/style.css +1 -1
- package/package.json +5 -5
- package/dist/ArticlesPage-CI-Nwhpy.js.map +0 -1
- package/dist/index-DqN5h0Ej.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-ChbP89HC.js","sources":["../src/editor.ts","../src/messages.ts","../src/i18n.ts","../src/ArticleAuthor.vue","../src/api.ts","../src/ArticleHistory.vue","../src/ArticleRelated.vue","../src/ArticleSlug.vue","../src/ArticleTags.vue","../src/ArticleUnpublish.vue","../src/module.ts","../src/authors.ts","../src/AuthorPane.vue","../src/TagCreateDialog.vue","../src/TagMergeDialog.vue","../src/ArticleCreateDialog.vue","../src/index.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, 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, useRemoveConfirm, 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')\nconst askRemove = useRemoveConfirm()\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 v-bind=\"askRemove\"\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","import { categoryRoutes, type AdminModule, type CategoriesOptions } from '@webx-ui/module-admin'\nimport ArticleAuthor from './ArticleAuthor.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'\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: () => import('./ArticlesPage.vue'),\n props: { base: path },\n },\n {\n path: `${path}/articles/:id(\\\\d+)`,\n name: 'webx.blog.articles.edit',\n component: () => import('./ArticleEditorPage.vue'),\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: () => import('./TagsPage.vue'),\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: () => import('./AuthorsPage.vue'),\n props: { base: path },\n },\n ],\n },\n ]\n}\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 { 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 { 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 { 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","import { defineAsyncComponent } from 'vue'\nexport { blog, rubricsOptions, type BlogOptions } from './module'\nexport { createBlogApi, type BlogApi } from './api'\nexport {\n createAuthorsApi,\n type AuthorAccount,\n type AuthorDetail,\n type AuthorRow,\n type AuthorsApi,\n} from './authors'\nexport { blogMessages } from './messages'\nexport {\n articleEditorKey,\n provideArticleEditor,\n useArticleEditor,\n type ArticleEditorContext,\n} from './editor'\nexport const WxArticlesPage = defineAsyncComponent(() => import('./ArticlesPage.vue'))\nexport const WxTagsPage = defineAsyncComponent(() => import('./TagsPage.vue'))\nexport const WxAuthorsPage = defineAsyncComponent(() => import('./AuthorsPage.vue'))\nexport { default as WxAuthorPane } from './AuthorPane.vue'\nexport { default as WxTagCreateDialog } from './TagCreateDialog.vue'\nexport { default as WxTagMergeDialog } from './TagMergeDialog.vue'\nexport { default as WxArticleCreateDialog } from './ArticleCreateDialog.vue'\nexport const WxArticleEditorPage = defineAsyncComponent(() => import('./ArticleEditorPage.vue'))\nexport { default as WxArticleSlug } from './ArticleSlug.vue'\nexport { default as WxArticleAuthorField } from './ArticleAuthor.vue'\nexport { default as WxArticleTags } from './ArticleTags.vue'\nexport { default as WxArticleRelated } from './ArticleRelated.vue'\nexport { default as WxArticleUnpublish } from './ArticleUnpublish.vue'\nexport { default as WxArticleHistory } from './ArticleHistory.vue'\nexport type {\n ArticleAuthor,\n ArticleConflict,\n ArticleCover,\n ArticleDetail,\n ArticleFilters,\n ArticleInput,\n ArticleOption,\n ArticleQuery,\n ArticleRow,\n ArticleSave,\n ArticleStatus,\n ArticleVersion,\n ArticlesPage,\n BlogNamed,\n BlogTag,\n RubricRow,\n TagCounts,\n TagIndexing,\n TagInput,\n TagMassAction,\n TagMerged,\n TagQuery,\n TagRow,\n TagsPage,\n} from './types'\n"],"names":["articleEditorKey","provideArticleEditor","editor","provide","useArticleEditor","inject","blogMessages","seeded","useBlogMessages","i18n","useAdmin","props","__props","value","_useModel","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","api","message","useErrorText","versions","ref","working","article","canManage","live","load","current","error","toast","restore","version","confirm","watch","_createElementVNode","_hoisted_2","WxSkeleton","WxEmpty","_Fragment","_renderList","_hoisted_3","_hoisted_4","WxDate","WxText","WxBadge","WxButton","WxDrafts","_cache","PAUSE","MATCHES","t","askRemove","useRemoveConfirm","known","found","timer","titles","all","one","rows","row","look","mine","page","add","picked","remove","onBeforeUnmount","WxSortableList","_withCtx","item","_mergeProps","locales","useLocales","prefix","head","slug","written","moving","$attrs","WxAlert","tags","tag","chips","missing","typed","same","title","create","chip","status","unpublish","WxFormItem","rubricsOptions","path","blog","ArticleSlug","ArticleAuthor","ArticleTags","ArticleRelated","ArticleUnpublish","ArticleHistory","categoryRoutes","createAuthorsApi","q","userId","values","emit","__emit","route","useRoute","snapshot","accounts","account","loading","saving","errors","dirty","state","fallback","localizedValue","accountOptions","chosenAccount","main","take","detail","blank","wanted","save","made","record","leaveGuard","event","onKeydown","useBodyKeys","onMounted","mayLeave","onBeforeRouteUpdate","to","from","onBeforeRouteLeave","actions","list","WxScreenHead","WxCard","WxScreen","WxActionBar","WxSaveState","open","resolve","dismiss","useModal","field","useTemplateRef","nextTick","submit","WxDialog","WxSpace","WxInput","other","survivor","going","affected","sum","addresses","failure","WxIcon","WxCheckbox","WxArticlesPage","defineAsyncComponent","WxTagsPage","WxAuthorsPage","WxArticleEditorPage"],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import "vue";
|
|
2
|
-
import { e as r, _ as i, W as t, f as l, g as o, h as g, i as A, j as c, k as x, A as W, l as d, b as p, T as u, m as h, n as b, o as P, q as T, d as n, c as E, p as m, r as D, s as f } from "./index-
|
|
2
|
+
import { e as r, _ as i, W as t, f as l, g as o, h as g, i as A, j as c, k as x, A as W, l as d, b as p, T as u, m as h, n as b, o as P, q as T, d as n, c as E, p as m, r as D, s as f } from "./index-ChbP89HC.js";
|
|
3
3
|
export {
|
|
4
4
|
r as WxArticleAuthorField,
|
|
5
5
|
i as WxArticleCreateDialog,
|
package/dist/style.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
.wx-article-author[data-v-4b275a4c]{display:flex;flex-direction:column;align-items:flex-start;gap:var(--wx-space-6);min-width:0}.wx-article-author[data-v-4b275a4c]>:first-child{align-self:stretch}.wx-article-author__profile[data-v-4b275a4c]{color:var(--wx-color-primary);font-size:var(--wx-font-size-sm)}.wx-article-history-tab[data-v-0052c4f9]{display:flex;flex-direction:column;gap:var(--wx-space-24)}.wx-article-history[data-v-0052c4f9]{display:flex;flex-direction:column;border:1px solid var(--wx-border-default);border-radius:var(--wx-radius-md);background:var(--wx-bg-surface);overflow:hidden}.wx-article-history__ghost[data-v-0052c4f9]{padding:var(--wx-space-10) var(--wx-space-12)}.wx-article-history__row[data-v-0052c4f9]{display:flex;align-items:center;gap:var(--wx-space-12);padding:var(--wx-space-10) var(--wx-space-12);border-block-end:1px solid var(--wx-border-default);flex-wrap:wrap}.wx-article-history__row[data-v-0052c4f9]:last-child{border-block-end:0}.wx-article-history__number[data-v-0052c4f9]{width:40px;font-family:var(--wx-font-family-mono);font-size:var(--wx-font-size-sm);color:var(--wx-text-muted)}.wx-article-history__who[data-v-0052c4f9]{display:flex;flex-direction:column;flex:1;min-width:160px}.wx-article-related[data-v-
|
|
1
|
+
.wx-article-author[data-v-4b275a4c]{display:flex;flex-direction:column;align-items:flex-start;gap:var(--wx-space-6);min-width:0}.wx-article-author[data-v-4b275a4c]>:first-child{align-self:stretch}.wx-article-author__profile[data-v-4b275a4c]{color:var(--wx-color-primary);font-size:var(--wx-font-size-sm)}.wx-article-history-tab[data-v-0052c4f9]{display:flex;flex-direction:column;gap:var(--wx-space-24)}.wx-article-history[data-v-0052c4f9]{display:flex;flex-direction:column;border:1px solid var(--wx-border-default);border-radius:var(--wx-radius-md);background:var(--wx-bg-surface);overflow:hidden}.wx-article-history__ghost[data-v-0052c4f9]{padding:var(--wx-space-10) var(--wx-space-12)}.wx-article-history__row[data-v-0052c4f9]{display:flex;align-items:center;gap:var(--wx-space-12);padding:var(--wx-space-10) var(--wx-space-12);border-block-end:1px solid var(--wx-border-default);flex-wrap:wrap}.wx-article-history__row[data-v-0052c4f9]:last-child{border-block-end:0}.wx-article-history__number[data-v-0052c4f9]{width:40px;font-family:var(--wx-font-family-mono);font-size:var(--wx-font-size-sm);color:var(--wx-text-muted)}.wx-article-history__who[data-v-0052c4f9]{display:flex;flex-direction:column;flex:1;min-width:160px}.wx-article-related[data-v-ac2e96f8]{display:flex;flex-direction:column;gap:var(--wx-space-8)}.wx-article-related__name[data-v-ac2e96f8]{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.wx-article-slug[data-v-5da20e5e]{display:flex;flex-direction:column;gap:var(--wx-space-8)}.wx-article-slug__prefix[data-v-5da20e5e],.wx-article-slug[data-v-5da20e5e] .wx-input__inner{font-family:var(--wx-font-family-mono)}.wx-article-slug__prefix[data-v-5da20e5e]{color:var(--wx-text-muted)}.wx-article-tags[data-v-faf5adec]{display:flex;flex-direction:column;gap:var(--wx-space-8)}.wx-article-tags__chips[data-v-faf5adec]{display:flex;flex-wrap:wrap;gap:var(--wx-space-6)}.wx-article-tags__pick[data-v-faf5adec]{display:flex;align-items:center;gap:var(--wx-space-8);flex-wrap:wrap}.wx-article-tags__pick[data-v-faf5adec]>.wx-select{flex:1 1 220px;min-width:0}.wx-article-unpublish[data-v-c311cff2] .wx-button{align-self:flex-start}.wx-author[data-v-60f7aa2e]{display:flex;flex-direction:column;gap:var(--wx-gap, var(--wx-space-16));min-width:0;padding:var(--wx-gap, var(--wx-space-16));container-type:inline-size}.wx-author__ghost[data-v-60f7aa2e]{max-width:480px}.wx-author__account[data-v-60f7aa2e]{margin:0;color:var(--wx-text-muted);font-size:var(--wx-font-size-sm)}.wx-tag-merge[data-v-aa68b3a8]{display:flex;flex-direction:column;gap:var(--wx-space-12)}.wx-tag-merge__chips[data-v-aa68b3a8]{display:flex;flex-wrap:wrap;align-items:center;gap:var(--wx-space-6);min-width:0}.wx-tag-merge__redirect[data-v-aa68b3a8]{display:flex;flex-direction:column;gap:var(--wx-space-2)}.wx-articles[data-v-8bd25142]{min-width:0}.wx-articles__cover[data-v-8bd25142]{display:flex;align-items:center;justify-content:center;width:44px;height:32px;overflow:hidden;border-radius:var(--wx-radius-xs);background:var(--wx-bg-subtle);color:var(--wx-text-muted)}.wx-articles__cover.is-empty[data-v-8bd25142]{border:1px dashed var(--wx-border-default);background:var(--wx-bg-surface)}.wx-articles__cover img[data-v-8bd25142]{width:100%;height:100%;min-width:0;object-fit:cover}.wx-articles__name[data-v-8bd25142]{display:flex;align-items:center;gap:var(--wx-space-8);min-width:0}.wx-articles__name-text[data-v-8bd25142]{display:flex;flex-direction:column;min-width:0}.wx-articles__pin[data-v-8bd25142]{flex:none;color:var(--wx-color-primary)}.wx-articles__title[data-v-8bd25142]{overflow:hidden;font-weight:var(--wx-font-weight-medium);white-space:nowrap;text-overflow:ellipsis}.wx-articles__address[data-v-8bd25142]{overflow:hidden;color:var(--wx-text-muted);font-family:var(--wx-font-family-mono);font-size:var(--wx-font-size-xs);white-space:nowrap;text-overflow:ellipsis}.wx-articles__chips[data-v-8bd25142],.wx-articles__state[data-v-8bd25142]{display:flex;align-items:center;gap:var(--wx-space-4);min-width:0;overflow:hidden}.wx-articles[data-v-8bd25142] .wx-articles__when{white-space:nowrap}.wx-article-editor[data-v-49f5a61d]{display:flex;flex-direction:column;gap:var(--wx-gap, var(--wx-space-16));height:100%;min-height:0;container-type:inline-size}.wx-article-editor__ghost[data-v-49f5a61d]{max-width:420px}.wx-article-editor__screen[data-v-49f5a61d]{display:flex;flex-direction:column;flex:1;min-height:0}.wx-article-editor__screen[data-v-49f5a61d] .wx-screen-host,.wx-article-editor__screen[data-v-49f5a61d] .wx-screen,.wx-article-editor__screen[data-v-49f5a61d] .wx-tabs,.wx-article-editor__screen[data-v-49f5a61d] .wx-tabs__layout,.wx-article-editor__screen[data-v-49f5a61d] .wx-tabs__panels,.wx-article-editor__screen[data-v-49f5a61d] .wx-tab:not([hidden]){display:flex;flex-direction:column;flex:1;min-height:0}.wx-tags[data-v-d9b8d73d]{display:flex;flex-direction:column;gap:var(--wx-space-12);min-width:0}.wx-tags__rename[data-v-d9b8d73d]{display:block;min-width:0}.wx-tags__sort[data-v-d9b8d73d]{display:flex;gap:var(--wx-space-4);min-width:0}.wx-tags__name[data-v-d9b8d73d]{max-width:100%;overflow:hidden;padding:0;border:0;background:none;color:inherit;font:inherit;text-align:start;text-overflow:ellipsis;white-space:nowrap;cursor:text}.wx-tags__name[data-v-d9b8d73d]:hover{text-decoration:underline;text-decoration-style:dotted}.wx-tags__bar[data-v-d9b8d73d] .wx-action-bar__state{flex:0 1 auto}.wx-authors>.wx-card__body{display:flex;flex-direction:column;overflow:clip;border-radius:inherit}.wx-authors__bar[data-v-b0022a2f]{display:flex;gap:var(--wx-space-8);padding:var(--wx-space-12) var(--wx-space-12) var(--wx-space-8)}.wx-authors__search[data-v-b0022a2f]{flex:1 1 180px;min-width:0}.wx-authors__new[data-v-b0022a2f]{display:flex;align-items:center;gap:var(--wx-space-8);width:calc(100% - 2 * var(--wx-space-8));margin:0 var(--wx-space-8) var(--wx-space-4);padding:var(--wx-space-8);border:1px dashed var(--wx-border-default);border-radius:var(--wx-radius-sm);background:none;font:inherit;color:var(--wx-color-primary);text-align:start;cursor:pointer}.wx-authors__new[data-v-b0022a2f]:hover,.wx-authors__new.is-current[data-v-b0022a2f]{background:var(--wx-bg-subtle)}.wx-authors__loading[data-v-b0022a2f]{padding:var(--wx-space-12)}.wx-authors__list[data-v-b0022a2f]{margin:0;padding:0 var(--wx-space-8) var(--wx-space-8);list-style:none}.wx-authors__list.is-loading[data-v-b0022a2f]{opacity:.6}.wx-author-row[data-v-b0022a2f]{display:flex;align-items:flex-start;gap:var(--wx-space-10);width:100%;min-width:0;padding:var(--wx-space-8) var(--wx-space-4);border:0;border-radius:var(--wx-radius-sm);background:none;font:inherit;color:inherit;text-align:start;cursor:pointer}.wx-author-row[data-v-b0022a2f]:hover,.wx-author-row.is-current[data-v-b0022a2f]{background:var(--wx-bg-subtle)}.wx-author-row__photo[data-v-b0022a2f]{flex:none}.wx-author-row__body[data-v-b0022a2f]{display:flex;flex:1 1 auto;flex-direction:column;gap:var(--wx-space-2);min-width:0}.wx-author-row__name[data-v-b0022a2f]{min-width:0;overflow:hidden;font-weight:var(--wx-font-weight-medium);text-overflow:ellipsis;white-space:nowrap}.wx-author-row.is-current .wx-author-row__name[data-v-b0022a2f]{color:var(--wx-color-primary)}.wx-author-row__meta[data-v-b0022a2f]{display:flex;flex-wrap:wrap;gap:var(--wx-space-2) var(--wx-space-8);color:var(--wx-text-muted);font-size:var(--wx-font-size-sm)}.wx-author-row__facts[data-v-b0022a2f]{display:flex;margin-top:var(--wx-space-2)}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webx-ui/module-blog",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.4",
|
|
4
4
|
"description": "Blog for the WebX UI admin panel: articles made of blocks, rubrics and tags, scheduled publication.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -43,10 +43,10 @@
|
|
|
43
43
|
"vue-router": "^4.5.0"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@webx-ui/core": "^0.
|
|
47
|
-
"@webx-ui/module-admin": "^0.
|
|
48
|
-
"@webx-ui/module-blocks": "^0.18.
|
|
49
|
-
"@webx-ui/schema": "^0.9.
|
|
46
|
+
"@webx-ui/core": "^0.45.0",
|
|
47
|
+
"@webx-ui/module-admin": "^0.33.0",
|
|
48
|
+
"@webx-ui/module-blocks": "^0.18.1",
|
|
49
|
+
"@webx-ui/schema": "^0.9.5"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@types/node": "^24.10.1",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ArticlesPage-CI-Nwhpy.js","sources":["../src/ArticlesPage.vue"],"sourcesContent":["<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"],"names":["CARDS","props","__props","context","useAdmin","api","createBlogApi","route","useRoute","router","useRouter","useBlogMessages","t","useTranslate","admin","message","useErrorText","root","useTemplateRef","width","useElementWidth","page","ref","loading","create","createModal","ArticleCreateDialog","canManage","computed","title","module","view","value","inBin","query","number","views","filters","asCards","columns","rowMenuWidth","emptyText","applied","chips","rubric","item","narrow","tag","author","clearFilters","load","state","error","toast","onState","wanted","name","watch","open","article","path","add","actionsFor","restore","actions","copyAddress","setPinned","unpublish","publish","remove","run","pinned","confirm","request","said","badge","status","address","dateOf","_createElementBlock","_createVNode","_unref","WxListScreen","$event","WxTable","WxFormItem","WxSelect","_cache","_createBlock","WxButton","WxIcon","_createTextVNode","WxFilterChips","_withCtx","row","_createElementVNode","_normalizeClass","_hoisted_2","WxTooltip","_hoisted_3","_hoisted_4","_toDisplayString","_hoisted_5","WxAddressNote","_hoisted_6","_openBlock","_Fragment","_renderList","WxBadge","WxText","WxDate","_hoisted_7","WxEntityCard","WxRowMenu"],"mappings":"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"}
|