@webx-ui/module-blog 0.8.6 → 0.8.8

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.
@@ -3,7 +3,7 @@ import { useRoute as Ae, useRouter as Ve, onBeforeRouteLeave as Ce } from "vue-r
3
3
  import { useAdmin as Oe, useDates as De, useTranslate as ie, useErrorText as Pe, useEditing as $e, WxScreenHead as Le, WxEditingAlerts as Me, WxScreen as Ue, WxSaveState as Fe } from "@webx-ui/module-admin";
4
4
  import { provideBlocksPreview as Je } from "@webx-ui/module-blocks";
5
5
  import { useLocales as Re, localizedValue as ze, confirm as M, WxSkeleton as re, WxCard as Ie, WxBadge as ue, WxBreadcrumb as Ge, WxBreadcrumbItem as se, WxActionBar as He, WxButton as ne, toast as h } from "@webx-ui/core";
6
- import { c as je, u as qe, p as Ke, a as Qe } from "./index-ChbP89HC.js";
6
+ import { c as je, u as qe, p as Ke, a as Qe } from "./index-C_gXxP0l.js";
7
7
  const Xe = { class: "wx-article-editor__screen" }, Ye = 1200, Ze = /* @__PURE__ */ We({
8
8
  __name: "ArticleEditorPage",
9
9
  props: {
@@ -365,4 +365,4 @@ const Xe = { class: "wx-article-editor__screen" }, Ye = 1200, Ze = /* @__PURE__
365
365
  export {
366
366
  sa as default
367
367
  };
368
- //# sourceMappingURL=ArticleEditorPage-Bkb5YrLy.js.map
368
+ //# sourceMappingURL=ArticleEditorPage-B1kYMBEk.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ArticleEditorPage-Bkb5YrLy.js","sources":["../src/ArticleEditorPage.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'\nimport { onBeforeRouteLeave, useRoute, useRouter } from 'vue-router'\nimport {\n useAdmin,\n useDates,\n useEditing,\n useErrorText,\n useTranslate,\n WxEditingAlerts,\n WxSaveState,\n WxScreen,\n WxScreenHead,\n type ScreenAction,\n} from '@webx-ui/module-admin'\nimport { provideBlocksPreview } from '@webx-ui/module-blocks'\nimport {\n confirm,\n localizedValue,\n toast,\n useLocales,\n WxActionBar,\n WxBadge,\n WxBreadcrumb,\n WxBreadcrumbItem,\n WxButton,\n WxCard,\n WxSkeleton,\n type LocalizedValue,\n} from '@webx-ui/core'\nimport type { ScreenModel } from '@webx-ui/schema'\nimport { createBlogApi } from './api'\nimport { provideArticleEditor } from './editor'\nimport { useBlogMessages } from './i18n'\nimport type { ArticleConflict, ArticleDetail, ArticleOption, ArticleRow } from './types'\n\n/**\n * The editor of one article: a head that stays put, and the described screen under it.\n *\n * What this page owns is the head — what the article is called, what state it is in, and the\n * way out to the site — and the bar along the bottom, which is where it is saved and published.\n * Everything between them is `blog.article-form`, so a module or a project adds a tab to the\n * editor with a patch (§12) rather than with a fork of this file.\n *\n * Saving is by autosave: a pause after the last keystroke, and the moment a field is left. The\n * explicit button is still there, because a button that says \"saved\" is the only way a person\n * can be sure — but nothing is ever lost by not pressing it.\n *\n * The one thing here that a page editor does not have is the day. An article carries a date\n * that may be in the future, so \"publish\" means \"publish under the date in the settings tab\",\n * and which day that is stands under the name before the button is pressed (§7).\n */\nconst props = withDefaults(defineProps<{ base?: string }>(), { base: '/blog' })\n\nconst context = useAdmin()\nconst api = createBlogApi(context)\nconst route = useRoute()\nconst router = useRouter()\nconst locales = useLocales()\nconst dates = useDates()\nuseBlogMessages()\n\nconst t = useTranslate('webx-blog')\nconst panel = useTranslate('webx-admin')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\n/** How long after the last keystroke the draft goes to the server. */\nconst PAUSE = 1200\n\nconst id = computed(() => Number(route.params.id))\nconst list = computed(() => `${props.base}/articles`)\n\nconst article = ref<ArticleRow | null>(null)\nconst values = ref<ScreenModel>({})\nconst revision = ref('')\nconst prefix = ref('')\nconst previewUrl = ref<string | null>(null)\nconst options = ref<ArticleDetail['options']>({ rubrics: [], authors: [] })\nconst related = ref<ArticleOption[]>([])\n\nconst loading = ref(true)\nconst saving = ref(false)\nconst working = ref(false)\nconst errors = ref<Record<string, string[]>>({})\n\nconst snapshot = ref('')\nconst reloadToken = ref(0)\n\nconst canManage = computed(() => context.can('blog.articles.manage'))\n\n/*\n * Other people on the same article: a refused save is merged with theirs field by field and\n * saved again, only a field both changed is asked about, and a save made meanwhile — by a\n * colleague or an agent — is offered before this editor writes over it.\n */\nconst editing = useEditing<ScreenModel>({\n entity: 'articles',\n id: () => article.value?.id,\n values,\n revision,\n read: async () => {\n const detail = await api.get(id.value)\n\n return { values: detail.values, revision: detail.revision }\n },\n adopt: (theirs) => {\n snapshot.value = JSON.stringify(theirs.values)\n reloadToken.value += 1\n },\n // Published or restored by somebody else: the badge and the address follow, and the form\n // stays as it is.\n refresh: () => refresh(),\n restore: async () => {\n if (article.value) await api.restore(article.value.id)\n },\n canWrite: () => canManage.value,\n busy: () => saving.value || flight !== undefined,\n screen: 'blog.article-form',\n})\n\nconst conflict = editing.conflict\n\n/** Closed for writing: no permission, or a publication in flight. */\nconst locked = computed(() => !canManage.value || working.value)\n\nconst current = computed(() => JSON.stringify(values.value))\nconst dirty = computed(() => snapshot.value !== '' && current.value !== snapshot.value)\n\n/** Saved · saving · not saved yet — the one line of the bar that moves while typing. */\nconst state = computed<'saving' | 'unsaved' | 'saved'>(() => {\n if (saving.value) return 'saving'\n\n return dirty.value ? 'unsaved' : 'saved'\n})\n\n/**\n * The name in the head follows the field rather than the answer: an editor who renamed the\n * article in the settings tab should see the new name at the top before the save lands.\n */\nconst title = computed(() => {\n const written = values.value.title as LocalizedValue | string | null | undefined\n\n return localizedValue(written, locales.active.value, article.value?.title ?? '')\n})\n\n/** The day in the settings tab, as the picker leaves it: `2026-09-24 08:00:00`, or nothing. */\nconst chosen = computed<string | null>(() => {\n const written = values.value.published_at\n\n return typeof written === 'string' && written !== '' ? written : null\n})\n\nconst future = computed(() => {\n if (chosen.value === null) return false\n\n // The picker's value is local time with a space in it, which Safari refuses to parse.\n const at = new Date(chosen.value.replace(' ', 'T'))\n\n return !Number.isNaN(at.valueOf()) && at.valueOf() > Date.now()\n})\n\n/**\n * When the article goes out, under its name.\n *\n * Only the day, and only when there is one. What state it is in the badges beside the name say\n * already — live, scheduled, with edits waiting — and saying it twice in two shapes is how a\n * screen teaches an editor to read neither. The day is the part a badge cannot carry, and the\n * part somebody about to press \"publish\" actually weighs.\n */\nconst publication = computed(() => {\n const row = article.value\n\n if (!row) return ''\n\n const when = dates.short(row.published_at)\n\n const line = {\n scheduled: t('article.live-scheduled', { date: when }),\n published: t('article.live-since', { date: when }),\n modified: t('article.live-since', { date: when }),\n }[row.status as 'scheduled' | 'published' | 'modified']\n\n return line ?? ''\n})\n\n/* The constructor shows the draft as a page of the site. The link is the editor's to hand\n over: only this screen knows which entity it is editing. */\nprovideBlocksPreview({ url: computed(() => previewUrl.value), reload: reloadToken })\n\nprovideArticleEditor({\n article,\n values,\n options,\n related,\n prefix,\n base: props.base,\n canManage: canManage.value,\n disabled: locked,\n reload: () => load(true),\n save: () => save(),\n})\n\nfunction take(detail: ArticleDetail): void {\n article.value = detail.article\n values.value = detail.values\n revision.value = detail.revision\n prefix.value = detail.prefix\n previewUrl.value = detail.preview_url\n options.value = detail.options\n related.value = detail.related\n snapshot.value = JSON.stringify(detail.values)\n editing.opened(detail.values)\n conflict.value = null\n}\n\n/**\n * Read the article again.\n *\n * Quietly unless this is the first time: the skeleton replaces the whole screen, tabs and all,\n * so a publication answered with it would throw an editor who published from the history back\n * onto the first tab — and flash the page on the way.\n */\nasync function load(silent = false): Promise<void> {\n if (!silent) loading.value = true\n\n try {\n take(await api.get(id.value))\n } catch (error) {\n toast.danger(message(error))\n void router.push(list.value)\n } finally {\n loading.value = false\n }\n}\n\n/**\n * What surrounds the form, read again: the row with its status, the address, the preview. Not\n * the values — the form is somebody's work in progress, and a change of its content is the\n * notice's to offer.\n */\nasync function refresh(): Promise<void> {\n try {\n const detail = await api.get(id.value)\n\n article.value = detail.article\n prefix.value = detail.prefix\n previewUrl.value = detail.preview_url\n } catch {\n // The heartbeat says what happened; a row that could not be read again stays as it was.\n }\n}\n\n/* One pending save at a time, and one pending pause. */\nlet timer: ReturnType<typeof setTimeout> | undefined\n\nfunction schedule(): void {\n if (!canManage.value || conflict.value || !dirty.value || editing.stopped.value) return\n\n clearTimeout(timer)\n timer = setTimeout(() => void save(), PAUSE)\n}\n\n/** A field was left: write now rather than at the end of a pause nobody is waiting through. */\nfunction onFocusOut(): void {\n if (!canManage.value || conflict.value || !dirty.value || saving.value || editing.stopped.value)\n return\n\n clearTimeout(timer)\n void save()\n}\n\n/* The save on its way, and what it carries. */\nlet flight: { carried: string; done: Promise<void> } | undefined\n\n/**\n * A save asked for while another is on its way waits for it instead of being dropped: leaving and\n * publishing read `dirty` right after, and a save that was skipped reads as one that failed.\n */\nasync function save(): Promise<void> {\n while (flight) {\n const { carried, done } = flight\n\n await done\n\n // That request wrote what it carried; go again only for what was typed while it was out.\n if (!dirty.value || conflict.value || current.value === carried) return\n }\n\n const carried = current.value\n const done = write()\n\n flight = { carried, done }\n\n try {\n await done\n } finally {\n if (flight?.done === done) flight = undefined\n }\n}\n\nasync function write(): Promise<void> {\n if (!article.value || !canManage.value) return\n\n clearTimeout(timer)\n\n // What is being sent, so that whatever is typed while the request is in flight stays dirty\n // and gets its own save rather than being marked as written.\n const sending = current.value\n const sent = values.value\n\n saving.value = true\n errors.value = {}\n\n try {\n const detail = await api.save(article.value.id, { values: sent, revision: revision.value })\n\n article.value = detail.article\n revision.value = detail.revision\n prefix.value = detail.prefix\n previewUrl.value = detail.preview_url\n options.value = detail.options\n related.value = detail.related\n conflict.value = null\n\n if (current.value === sending) snapshot.value = sending\n\n // What the server holds now is what was sent: the base of the next merge.\n editing.opened(sent)\n\n // The preview is rendered from the draft, and the draft is what was just written.\n reloadToken.value += 1\n } catch (error) {\n const failure = error as {\n status?: number\n body?: ArticleConflict & { errors?: Record<string, string[]> }\n }\n\n if (failure.status === 409 && failure.body?.data) {\n const theirs = failure.body.data\n\n // Nothing overlapping: both edits are in the form now, and they go to the server again.\n if (\n editing.refused({\n values: theirs.values,\n revision: theirs.revision,\n changed: failure.body.changed,\n })\n ) {\n article.value = theirs.article\n void save()\n }\n } else if (failure.body?.errors) {\n errors.value = failure.body.errors\n toast.danger(t('article.save-failed'))\n } else if (!(await editing.failed(error))) {\n // In the bin is said by the notice above the form, with the way back; anything else here.\n toast.danger(message(error))\n }\n } finally {\n saving.value = false\n }\n}\n\n/** Give up what was typed and take the article as it now is; the component asked first. */\nasync function takeTheirs(): Promise<void> {\n await load(true)\n}\n\n/**\n * Throw away what is waiting and keep what the site is showing (§10).\n *\n * Offered only while there is a difference to throw away. Asked about, because it is the one\n * button on this screen that destroys writing — and answered honestly: the last few minutes are\n * still in the autosave ring, but nothing in the panel lists them, so the question does not\n * promise a way back.\n */\nasync function discard(): Promise<void> {\n if (!article.value) return\n\n const agreed = await confirm({\n title: panel('editor.discard-title'),\n message: panel('editor.discard-text'),\n confirmText: t('article.discard'),\n // Not «Cancel»: beside «Discard changes» the two read as the same word.\n cancelText: panel('editor.keep-changes'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n working.value = true\n\n try {\n take(await api.discard(article.value.id))\n toast.success(t('article.discarded'))\n } catch (error) {\n toast.danger(message(error))\n } finally {\n working.value = false\n }\n}\n\n/**\n * Publishing is asked about, because it is the one action here that visitors see: everything\n * else on this screen writes a draft nobody outside the panel can read.\n *\n * The question names the day as well as the address, and that is the whole point of asking. A\n * date in the future means the article does not appear — it waits — and an editor who thought\n * they were publishing now would go and look for it on the site (§7).\n */\nasync function publish(): Promise<void> {\n // Saved before asking: the question names the address the draft will publish at, and only a\n // saved draft has one — an address typed but not saved was asked about under the old one.\n if (dirty.value) await save()\n if (conflict.value || dirty.value) return\n\n if (!article.value) return\n\n // The draft on the server may hold somebody else's edit this editor never pulled in: said\n // before it goes on the site, with whose it is and where, rather than published unseen.\n const held = await editing.beforePublish()\n\n if (!held) return\n\n const row = article.value\n\n // Publishing moves a renamed slug, so the question names where the page will be, not where\n // it is — and says the old address will lead there, since that is what happens to it.\n const next = row.next_path ?? row.path\n const old =\n row.next_path != null && row.path !== null\n ? ` ${panel('editor.publish-moves', { old: `/${row.path}` })}`\n : ''\n const address = next === null ? null : `/${next}`\n const when = chosen.value === null ? t('article.publish-now') : dates.short(chosen.value)\n\n // One question is enough: whoever just agreed to publish somebody else's changes has said yes.\n const agreed =\n held.asked ||\n (await confirm({\n title: t('article.publish-title', { title: row.title }),\n message: future.value\n ? t('article.publish-later', { date: when })\n : address === null\n ? t('article.publish-nowhere')\n : t('article.publish-text', { address }) + old,\n confirmText: future.value ? t('article.schedule') : t('panel.publish'),\n cancelText: t('panel.cancel'),\n }))\n\n if (!agreed) return\n\n working.value = true\n\n try {\n await api.publish(row.id, chosen.value, held.revision)\n await load(true)\n toast.success(future.value ? t('article.scheduled') : t('panel.published'))\n } catch (error) {\n if (!(await editing.failed(error))) toast.danger(message(error))\n } finally {\n working.value = false\n }\n}\n\n/**\n * Taking it off the site is asked about, as publishing is: visitors are who notice. Nothing\n * written is lost, and «Publish» brings it back at the same address.\n */\nasync function unpublish(): Promise<void> {\n const row = article.value\n\n if (!row) return\n\n const agreed = await confirm({\n title: panel('editor.unpublish-title'),\n message: panel('editor.unpublish-text'),\n confirmText: panel('editor.unpublish'),\n cancelText: panel('editor.keep-published'),\n })\n\n if (!agreed) return\n\n working.value = true\n\n try {\n await api.unpublish(row.id)\n await load(true)\n toast.success(panel('editor.unpublished'))\n } catch (error) {\n toast.danger(message(error))\n } finally {\n working.value = false\n }\n}\n\nfunction badge(): 'default' | 'success' | 'warning' | 'primary' {\n const status = article.value?.status\n\n if (status === 'published') return 'success'\n if (status === 'modified') return 'warning'\n\n return status === 'scheduled' ? 'primary' : 'default'\n}\n\n/* The browser's own guard: it cannot wait for a request, so all it can do is ask. */\nfunction leaveGuard(event: BeforeUnloadEvent): void {\n if (dirty.value) event.preventDefault()\n}\n\nwatch(current, schedule)\nwatch(id, () => void load())\n\nonMounted(() => {\n void load()\n window.addEventListener('beforeunload', leaveGuard)\n})\n\nonBeforeUnmount(() => {\n clearTimeout(timer)\n window.removeEventListener('beforeunload', leaveGuard)\n})\n\n/**\n * Leaving flushes the pause rather than asking about it: everything else on this screen saves\n * by itself, and a dialog that appears only because somebody left within a second of typing\n * would be the one place that does not. The question is asked only when the save did not go\n * through — a conflict, a refused field, a server that is not there.\n */\nonBeforeRouteLeave(async () => {\n if (!dirty.value || !canManage.value) return true\n\n await save()\n\n if (!dirty.value) return true\n\n return await confirm({\n title: t('article.leave-title'),\n message: t('article.leave-text'),\n confirmText: t('article.leave'),\n cancelText: t('panel.cancel'),\n tone: 'danger',\n })\n})\n/* What leads away from the article. On a phone the head folds them into the ···. */\nconst actions = computed<ScreenAction[]>(() => {\n const leads: ScreenAction[] = []\n\n if (previewUrl.value) {\n leads.push({ key: 'preview', label: t('article.preview'), icon: 'eye', href: previewUrl.value })\n }\n\n if (article.value?.url && article.value.status !== 'draft') {\n leads.push({\n key: 'site',\n label: t('panel.open-on-site'),\n icon: 'link',\n href: article.value.url,\n })\n }\n\n // On the site now: the way off it is a button of its own beside «Open on the site», not a\n // line in the ···. Hiding loses nothing and is undone by «Publish».\n if (\n canManage.value &&\n (article.value?.status === 'published' || article.value?.status === 'modified')\n ) {\n leads.push({\n key: 'unpublish',\n label: panel('editor.unpublish'),\n icon: 'eye-off',\n run: () => void unpublish(),\n })\n }\n\n /*\n * Throwing away what was written since the last publication: in the `···` at every width and\n * never a button, which is what `menu` is for. It used to stand in the action bar next to\n * \"publish\", one slip away from it — and it is offered at all only while there is a\n * difference between what is written and what is on the site.\n */\n if (article.value?.status === 'modified') {\n leads.push({\n key: 'discard',\n label: t('article.discard'),\n icon: 'refresh',\n danger: true,\n menu: true,\n run: () => void discard(),\n })\n }\n\n return leads\n})\n</script>\n\n<template>\n <!-- No `data-wx-fill`: this screen is a window tall only while the constructor is the tab on\n screen, and that is a question only CSS can ask. `WxMain` still gives it a floor of one\n window, because it carries an action bar. -->\n <div class=\"wx-article-editor\" @focusout=\"onFocusOut\">\n <template v-if=\"loading || !article\">\n <!-- Shaped like the screen it stands in for: the head on the ground, and what it is\n waiting for on the card that will hold it. Two bare strips on an empty background\n read as a page that failed rather than one that is a moment away. -->\n <wx-skeleton class=\"wx-article-editor__ghost\" title :rows=\"1\" />\n <wx-card><wx-skeleton :rows=\"8\" /></wx-card>\n </template>\n\n <template v-else>\n <!--\n The panel's head: the way out, the trail, the name and the state of the article. What is\n left in it is what leads away from the article — the draft on the site and the article on\n the site. Saving and publishing are down in the bar, where the eye is, and only there: a\n second copy of a button already on screen is not a reminder (§3.3).\n -->\n <wx-screen-head\n divider\n :back=\"list\"\n :back-label=\"t('module.articles')\"\n :title=\"title || t('article.untitled')\"\n :subtitle=\"publication\"\n :actions=\"actions\"\n >\n <template #trail>\n <wx-breadcrumb size=\"sm\" :label=\"t('article.trail')\">\n <wx-breadcrumb-item :as=\"'router-link'\" :to=\"list\">\n {{ t('module.articles') }}\n </wx-breadcrumb-item>\n <wx-breadcrumb-item current>{{ title || t('article.untitled') }}</wx-breadcrumb-item>\n </wx-breadcrumb>\n </template>\n\n <template #title-after>\n <wx-badge :type=\"badge()\" dot>{{ t(`panel.status-${article.status}`) }}</wx-badge>\n <!-- What is waiting, said beside the state rather than instead of it, exactly as the\n list says it: the article is on the site, and there is something that is not.\n The bar used to carry this in words; the head is where the state of the record\n already lives. -->\n <wx-badge v-if=\"article.status === 'modified'\" type=\"primary\" round>\n {{ t('panel.edits') }}\n </wx-badge>\n </template>\n </wx-screen-head>\n\n <!-- Somebody else wrote while this editor was open. What does not overlap is merged\n without a word; what does is listed place by place. -->\n <wx-editing-alerts :editing=\"editing\" @save=\"save\" @theirs=\"takeTheirs\" />\n\n <div class=\"wx-article-editor__screen\">\n <wx-screen v-model=\"values\" name=\"blog.article-form\" :errors=\"errors\" :disabled=\"locked\" />\n </div>\n\n <!--\n The last row of the screen, not a layer over it: the tab above shrinks by the height of\n the bar and is never covered by it.\n -->\n <!--\n Two buttons and a mark, the same bar the page editor has.\n\n What used to stand here besides them: a sentence saying since when the article is on the\n site and that there are unpublished edits, and a third button to throw those edits away.\n Half the sentence is what the badge beside the name already says, the other half is a\n fact about the article rather than about the last keystroke and now stands under its\n name; and throwing work away is not a button to keep beside \"publish\" — it is in the\n `···`, where this panel keeps everything that cannot be undone.\n -->\n <wx-action-bar v-if=\"canManage\">\n <template #state>\n <wx-save-state v-if=\"!editing.stopped.value\" :state=\"state\" />\n </template>\n\n <wx-button\n variant=\"outline\"\n :loading=\"saving\"\n :disabled=\"!dirty || editing.stopped.value\"\n :title=\"editing.blocked.value\"\n @click=\"save\"\n >\n {{ t('article.save') }}\n </wx-button>\n\n <wx-button\n type=\"primary\"\n :loading=\"working\"\n :disabled=\"editing.stopped.value || (article.status === 'published' && !dirty)\"\n :title=\"editing.blocked.value\"\n @click=\"publish\"\n >\n {{ future ? t('article.schedule') : t('panel.publish') }}\n </wx-button>\n </wx-action-bar>\n </template>\n </div>\n</template>\n\n<style scoped>\n.wx-article-editor {\n display: flex;\n flex-direction: column;\n gap: var(--wx-gap, var(--wx-space-16));\n /* The constructor is the whole screen below the head, so the page is as tall as what it is\n drawn in and never taller: the tree, the form and the preview scroll inside themselves,\n and the head stays where it was put. */\n height: 100%;\n min-height: 0;\n container-type: inline-size;\n}\n\n.wx-article-editor__ghost {\n max-width: 420px;\n}\n\n.wx-article-editor__screen {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-height: 0;\n}\n\n/*\n * The tabs are the screen's, not this page's, so the height has to travel through them: every\n * box between the page and the constructor is a flex column that may shrink, or the constructor\n * sizes itself to its content and the whole page scrolls with the head on top of it.\n *\n * `:not([hidden])` is load-bearing. The tabs are kept alive, so the three that are not on\n * screen are still in the document, and a `display: flex` that reached them would make each one\n * a quarter of the height the visible one should have had.\n */\n.wx-article-editor__screen :deep(.wx-screen-host),\n.wx-article-editor__screen :deep(.wx-screen),\n.wx-article-editor__screen :deep(.wx-tabs),\n.wx-article-editor__screen :deep(.wx-tabs__layout),\n.wx-article-editor__screen :deep(.wx-tabs__panels),\n.wx-article-editor__screen :deep(.wx-tab:not([hidden])) {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-height: 0;\n}\n\n/*\n * No tab is a box of a fixed height any more, and none has a scrollbar of its own: the\n * constructor's preview is as tall as the page it shows and the browser scrolls it. A tab\n * that grows with its contents is also the only kind that does not clip them — a scroll box\n * cut the shadows off the cards inside it square at all four edges.\n */\n</style>\n"],"names":["PAUSE","props","__props","context","useAdmin","api","createBlogApi","route","useRoute","router","useRouter","locales","useLocales","dates","useDates","useBlogMessages","t","useTranslate","panel","message","useErrorText","id","computed","list","article","ref","values","revision","prefix","previewUrl","options","related","loading","saving","working","errors","snapshot","reloadToken","canManage","editing","useEditing","detail","theirs","refresh","flight","conflict","locked","current","dirty","state","title","written","localizedValue","chosen","future","at","publication","row","when","provideBlocksPreview","provideArticleEditor","load","save","take","silent","error","toast","timer","schedule","onFocusOut","carried","done","write","sending","sent","failure","takeTheirs","discard","confirm","publish","held","next","old","address","unpublish","badge","status","leaveGuard","event","watch","onMounted","onBeforeUnmount","onBeforeRouteLeave","actions","leads","_createElementBlock","_Fragment","_createVNode","_unref","WxSkeleton","WxCard","WxScreenHead","WxBreadcrumb","WxBreadcrumbItem","_createTextVNode","_toDisplayString","WxBadge","_createBlock","WxEditingAlerts","_createElementVNode","_hoisted_1","WxScreen","$event","WxActionBar","WxSaveState","WxButton"],"mappings":"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"}
1
+ {"version":3,"file":"ArticleEditorPage-B1kYMBEk.js","sources":["../src/ArticleEditorPage.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'\nimport { onBeforeRouteLeave, useRoute, useRouter } from 'vue-router'\nimport {\n useAdmin,\n useDates,\n useEditing,\n useErrorText,\n useTranslate,\n WxEditingAlerts,\n WxSaveState,\n WxScreen,\n WxScreenHead,\n type ScreenAction,\n} from '@webx-ui/module-admin'\nimport { provideBlocksPreview } from '@webx-ui/module-blocks'\nimport {\n confirm,\n localizedValue,\n toast,\n useLocales,\n WxActionBar,\n WxBadge,\n WxBreadcrumb,\n WxBreadcrumbItem,\n WxButton,\n WxCard,\n WxSkeleton,\n type LocalizedValue,\n} from '@webx-ui/core'\nimport type { ScreenModel } from '@webx-ui/schema'\nimport { createBlogApi } from './api'\nimport { provideArticleEditor } from './editor'\nimport { useBlogMessages } from './i18n'\nimport type { ArticleConflict, ArticleDetail, ArticleOption, ArticleRow } from './types'\n\n/**\n * The editor of one article: a head that stays put, and the described screen under it.\n *\n * What this page owns is the head — what the article is called, what state it is in, and the\n * way out to the site — and the bar along the bottom, which is where it is saved and published.\n * Everything between them is `blog.article-form`, so a module or a project adds a tab to the\n * editor with a patch (§12) rather than with a fork of this file.\n *\n * Saving is by autosave: a pause after the last keystroke, and the moment a field is left. The\n * explicit button is still there, because a button that says \"saved\" is the only way a person\n * can be sure — but nothing is ever lost by not pressing it.\n *\n * The one thing here that a page editor does not have is the day. An article carries a date\n * that may be in the future, so \"publish\" means \"publish under the date in the settings tab\",\n * and which day that is stands under the name before the button is pressed (§7).\n */\nconst props = withDefaults(defineProps<{ base?: string }>(), { base: '/blog' })\n\nconst context = useAdmin()\nconst api = createBlogApi(context)\nconst route = useRoute()\nconst router = useRouter()\nconst locales = useLocales()\nconst dates = useDates()\nuseBlogMessages()\n\nconst t = useTranslate('webx-blog')\nconst panel = useTranslate('webx-admin')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\n/** How long after the last keystroke the draft goes to the server. */\nconst PAUSE = 1200\n\nconst id = computed(() => Number(route.params.id))\nconst list = computed(() => `${props.base}/articles`)\n\nconst article = ref<ArticleRow | null>(null)\nconst values = ref<ScreenModel>({})\nconst revision = ref('')\nconst prefix = ref('')\nconst previewUrl = ref<string | null>(null)\nconst options = ref<ArticleDetail['options']>({ rubrics: [], authors: [] })\nconst related = ref<ArticleOption[]>([])\n\nconst loading = ref(true)\nconst saving = ref(false)\nconst working = ref(false)\nconst errors = ref<Record<string, string[]>>({})\n\nconst snapshot = ref('')\nconst reloadToken = ref(0)\n\nconst canManage = computed(() => context.can('blog.articles.manage'))\n\n/*\n * Other people on the same article: a refused save is merged with theirs field by field and\n * saved again, only a field both changed is asked about, and a save made meanwhile — by a\n * colleague or an agent — is offered before this editor writes over it.\n */\nconst editing = useEditing<ScreenModel>({\n entity: 'articles',\n id: () => article.value?.id,\n values,\n revision,\n read: async () => {\n const detail = await api.get(id.value)\n\n return { values: detail.values, revision: detail.revision }\n },\n adopt: (theirs) => {\n snapshot.value = JSON.stringify(theirs.values)\n reloadToken.value += 1\n },\n // Published or restored by somebody else: the badge and the address follow, and the form\n // stays as it is.\n refresh: () => refresh(),\n restore: async () => {\n if (article.value) await api.restore(article.value.id)\n },\n canWrite: () => canManage.value,\n busy: () => saving.value || flight !== undefined,\n screen: 'blog.article-form',\n})\n\nconst conflict = editing.conflict\n\n/** Closed for writing: no permission, or a publication in flight. */\nconst locked = computed(() => !canManage.value || working.value)\n\nconst current = computed(() => JSON.stringify(values.value))\nconst dirty = computed(() => snapshot.value !== '' && current.value !== snapshot.value)\n\n/** Saved · saving · not saved yet — the one line of the bar that moves while typing. */\nconst state = computed<'saving' | 'unsaved' | 'saved'>(() => {\n if (saving.value) return 'saving'\n\n return dirty.value ? 'unsaved' : 'saved'\n})\n\n/**\n * The name in the head follows the field rather than the answer: an editor who renamed the\n * article in the settings tab should see the new name at the top before the save lands.\n */\nconst title = computed(() => {\n const written = values.value.title as LocalizedValue | string | null | undefined\n\n return localizedValue(written, locales.active.value, article.value?.title ?? '')\n})\n\n/** The day in the settings tab, as the picker leaves it: `2026-09-24 08:00:00`, or nothing. */\nconst chosen = computed<string | null>(() => {\n const written = values.value.published_at\n\n return typeof written === 'string' && written !== '' ? written : null\n})\n\nconst future = computed(() => {\n if (chosen.value === null) return false\n\n // The picker's value is local time with a space in it, which Safari refuses to parse.\n const at = new Date(chosen.value.replace(' ', 'T'))\n\n return !Number.isNaN(at.valueOf()) && at.valueOf() > Date.now()\n})\n\n/**\n * When the article goes out, under its name.\n *\n * Only the day, and only when there is one. What state it is in the badges beside the name say\n * already — live, scheduled, with edits waiting — and saying it twice in two shapes is how a\n * screen teaches an editor to read neither. The day is the part a badge cannot carry, and the\n * part somebody about to press \"publish\" actually weighs.\n */\nconst publication = computed(() => {\n const row = article.value\n\n if (!row) return ''\n\n const when = dates.short(row.published_at)\n\n const line = {\n scheduled: t('article.live-scheduled', { date: when }),\n published: t('article.live-since', { date: when }),\n modified: t('article.live-since', { date: when }),\n }[row.status as 'scheduled' | 'published' | 'modified']\n\n return line ?? ''\n})\n\n/* The constructor shows the draft as a page of the site. The link is the editor's to hand\n over: only this screen knows which entity it is editing. */\nprovideBlocksPreview({ url: computed(() => previewUrl.value), reload: reloadToken })\n\nprovideArticleEditor({\n article,\n values,\n options,\n related,\n prefix,\n base: props.base,\n canManage: canManage.value,\n disabled: locked,\n reload: () => load(true),\n save: () => save(),\n})\n\nfunction take(detail: ArticleDetail): void {\n article.value = detail.article\n values.value = detail.values\n revision.value = detail.revision\n prefix.value = detail.prefix\n previewUrl.value = detail.preview_url\n options.value = detail.options\n related.value = detail.related\n snapshot.value = JSON.stringify(detail.values)\n editing.opened(detail.values)\n conflict.value = null\n}\n\n/**\n * Read the article again.\n *\n * Quietly unless this is the first time: the skeleton replaces the whole screen, tabs and all,\n * so a publication answered with it would throw an editor who published from the history back\n * onto the first tab — and flash the page on the way.\n */\nasync function load(silent = false): Promise<void> {\n if (!silent) loading.value = true\n\n try {\n take(await api.get(id.value))\n } catch (error) {\n toast.danger(message(error))\n void router.push(list.value)\n } finally {\n loading.value = false\n }\n}\n\n/**\n * What surrounds the form, read again: the row with its status, the address, the preview. Not\n * the values — the form is somebody's work in progress, and a change of its content is the\n * notice's to offer.\n */\nasync function refresh(): Promise<void> {\n try {\n const detail = await api.get(id.value)\n\n article.value = detail.article\n prefix.value = detail.prefix\n previewUrl.value = detail.preview_url\n } catch {\n // The heartbeat says what happened; a row that could not be read again stays as it was.\n }\n}\n\n/* One pending save at a time, and one pending pause. */\nlet timer: ReturnType<typeof setTimeout> | undefined\n\nfunction schedule(): void {\n if (!canManage.value || conflict.value || !dirty.value || editing.stopped.value) return\n\n clearTimeout(timer)\n timer = setTimeout(() => void save(), PAUSE)\n}\n\n/** A field was left: write now rather than at the end of a pause nobody is waiting through. */\nfunction onFocusOut(): void {\n if (!canManage.value || conflict.value || !dirty.value || saving.value || editing.stopped.value)\n return\n\n clearTimeout(timer)\n void save()\n}\n\n/* The save on its way, and what it carries. */\nlet flight: { carried: string; done: Promise<void> } | undefined\n\n/**\n * A save asked for while another is on its way waits for it instead of being dropped: leaving and\n * publishing read `dirty` right after, and a save that was skipped reads as one that failed.\n */\nasync function save(): Promise<void> {\n while (flight) {\n const { carried, done } = flight\n\n await done\n\n // That request wrote what it carried; go again only for what was typed while it was out.\n if (!dirty.value || conflict.value || current.value === carried) return\n }\n\n const carried = current.value\n const done = write()\n\n flight = { carried, done }\n\n try {\n await done\n } finally {\n if (flight?.done === done) flight = undefined\n }\n}\n\nasync function write(): Promise<void> {\n if (!article.value || !canManage.value) return\n\n clearTimeout(timer)\n\n // What is being sent, so that whatever is typed while the request is in flight stays dirty\n // and gets its own save rather than being marked as written.\n const sending = current.value\n const sent = values.value\n\n saving.value = true\n errors.value = {}\n\n try {\n const detail = await api.save(article.value.id, { values: sent, revision: revision.value })\n\n article.value = detail.article\n revision.value = detail.revision\n prefix.value = detail.prefix\n previewUrl.value = detail.preview_url\n options.value = detail.options\n related.value = detail.related\n conflict.value = null\n\n if (current.value === sending) snapshot.value = sending\n\n // What the server holds now is what was sent: the base of the next merge.\n editing.opened(sent)\n\n // The preview is rendered from the draft, and the draft is what was just written.\n reloadToken.value += 1\n } catch (error) {\n const failure = error as {\n status?: number\n body?: ArticleConflict & { errors?: Record<string, string[]> }\n }\n\n if (failure.status === 409 && failure.body?.data) {\n const theirs = failure.body.data\n\n // Nothing overlapping: both edits are in the form now, and they go to the server again.\n if (\n editing.refused({\n values: theirs.values,\n revision: theirs.revision,\n changed: failure.body.changed,\n })\n ) {\n article.value = theirs.article\n void save()\n }\n } else if (failure.body?.errors) {\n errors.value = failure.body.errors\n toast.danger(t('article.save-failed'))\n } else if (!(await editing.failed(error))) {\n // In the bin is said by the notice above the form, with the way back; anything else here.\n toast.danger(message(error))\n }\n } finally {\n saving.value = false\n }\n}\n\n/** Give up what was typed and take the article as it now is; the component asked first. */\nasync function takeTheirs(): Promise<void> {\n await load(true)\n}\n\n/**\n * Throw away what is waiting and keep what the site is showing (§10).\n *\n * Offered only while there is a difference to throw away. Asked about, because it is the one\n * button on this screen that destroys writing — and answered honestly: the last few minutes are\n * still in the autosave ring, but nothing in the panel lists them, so the question does not\n * promise a way back.\n */\nasync function discard(): Promise<void> {\n if (!article.value) return\n\n const agreed = await confirm({\n title: panel('editor.discard-title'),\n message: panel('editor.discard-text'),\n confirmText: t('article.discard'),\n // Not «Cancel»: beside «Discard changes» the two read as the same word.\n cancelText: panel('editor.keep-changes'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n working.value = true\n\n try {\n take(await api.discard(article.value.id))\n toast.success(t('article.discarded'))\n } catch (error) {\n toast.danger(message(error))\n } finally {\n working.value = false\n }\n}\n\n/**\n * Publishing is asked about, because it is the one action here that visitors see: everything\n * else on this screen writes a draft nobody outside the panel can read.\n *\n * The question names the day as well as the address, and that is the whole point of asking. A\n * date in the future means the article does not appear — it waits — and an editor who thought\n * they were publishing now would go and look for it on the site (§7).\n */\nasync function publish(): Promise<void> {\n // Saved before asking: the question names the address the draft will publish at, and only a\n // saved draft has one — an address typed but not saved was asked about under the old one.\n if (dirty.value) await save()\n if (conflict.value || dirty.value) return\n\n if (!article.value) return\n\n // The draft on the server may hold somebody else's edit this editor never pulled in: said\n // before it goes on the site, with whose it is and where, rather than published unseen.\n const held = await editing.beforePublish()\n\n if (!held) return\n\n const row = article.value\n\n // Publishing moves a renamed slug, so the question names where the page will be, not where\n // it is — and says the old address will lead there, since that is what happens to it.\n const next = row.next_path ?? row.path\n const old =\n row.next_path != null && row.path !== null\n ? ` ${panel('editor.publish-moves', { old: `/${row.path}` })}`\n : ''\n const address = next === null ? null : `/${next}`\n const when = chosen.value === null ? t('article.publish-now') : dates.short(chosen.value)\n\n // One question is enough: whoever just agreed to publish somebody else's changes has said yes.\n const agreed =\n held.asked ||\n (await confirm({\n title: t('article.publish-title', { title: row.title }),\n message: future.value\n ? t('article.publish-later', { date: when })\n : address === null\n ? t('article.publish-nowhere')\n : t('article.publish-text', { address }) + old,\n confirmText: future.value ? t('article.schedule') : t('panel.publish'),\n cancelText: t('panel.cancel'),\n }))\n\n if (!agreed) return\n\n working.value = true\n\n try {\n await api.publish(row.id, chosen.value, held.revision)\n await load(true)\n toast.success(future.value ? t('article.scheduled') : t('panel.published'))\n } catch (error) {\n if (!(await editing.failed(error))) toast.danger(message(error))\n } finally {\n working.value = false\n }\n}\n\n/**\n * Taking it off the site is asked about, as publishing is: visitors are who notice. Nothing\n * written is lost, and «Publish» brings it back at the same address.\n */\nasync function unpublish(): Promise<void> {\n const row = article.value\n\n if (!row) return\n\n const agreed = await confirm({\n title: panel('editor.unpublish-title'),\n message: panel('editor.unpublish-text'),\n confirmText: panel('editor.unpublish'),\n cancelText: panel('editor.keep-published'),\n })\n\n if (!agreed) return\n\n working.value = true\n\n try {\n await api.unpublish(row.id)\n await load(true)\n toast.success(panel('editor.unpublished'))\n } catch (error) {\n toast.danger(message(error))\n } finally {\n working.value = false\n }\n}\n\nfunction badge(): 'default' | 'success' | 'warning' | 'primary' {\n const status = article.value?.status\n\n if (status === 'published') return 'success'\n if (status === 'modified') return 'warning'\n\n return status === 'scheduled' ? 'primary' : 'default'\n}\n\n/* The browser's own guard: it cannot wait for a request, so all it can do is ask. */\nfunction leaveGuard(event: BeforeUnloadEvent): void {\n if (dirty.value) event.preventDefault()\n}\n\nwatch(current, schedule)\nwatch(id, () => void load())\n\nonMounted(() => {\n void load()\n window.addEventListener('beforeunload', leaveGuard)\n})\n\nonBeforeUnmount(() => {\n clearTimeout(timer)\n window.removeEventListener('beforeunload', leaveGuard)\n})\n\n/**\n * Leaving flushes the pause rather than asking about it: everything else on this screen saves\n * by itself, and a dialog that appears only because somebody left within a second of typing\n * would be the one place that does not. The question is asked only when the save did not go\n * through — a conflict, a refused field, a server that is not there.\n */\nonBeforeRouteLeave(async () => {\n if (!dirty.value || !canManage.value) return true\n\n await save()\n\n if (!dirty.value) return true\n\n return await confirm({\n title: t('article.leave-title'),\n message: t('article.leave-text'),\n confirmText: t('article.leave'),\n cancelText: t('panel.cancel'),\n tone: 'danger',\n })\n})\n/* What leads away from the article. On a phone the head folds them into the ···. */\nconst actions = computed<ScreenAction[]>(() => {\n const leads: ScreenAction[] = []\n\n if (previewUrl.value) {\n leads.push({ key: 'preview', label: t('article.preview'), icon: 'eye', href: previewUrl.value })\n }\n\n if (article.value?.url && article.value.status !== 'draft') {\n leads.push({\n key: 'site',\n label: t('panel.open-on-site'),\n icon: 'link',\n href: article.value.url,\n })\n }\n\n // On the site now: the way off it is a button of its own beside «Open on the site», not a\n // line in the ···. Hiding loses nothing and is undone by «Publish».\n if (\n canManage.value &&\n (article.value?.status === 'published' || article.value?.status === 'modified')\n ) {\n leads.push({\n key: 'unpublish',\n label: panel('editor.unpublish'),\n icon: 'eye-off',\n run: () => void unpublish(),\n })\n }\n\n /*\n * Throwing away what was written since the last publication: in the `···` at every width and\n * never a button, which is what `menu` is for. It used to stand in the action bar next to\n * \"publish\", one slip away from it — and it is offered at all only while there is a\n * difference between what is written and what is on the site.\n */\n if (article.value?.status === 'modified') {\n leads.push({\n key: 'discard',\n label: t('article.discard'),\n icon: 'refresh',\n danger: true,\n menu: true,\n run: () => void discard(),\n })\n }\n\n return leads\n})\n</script>\n\n<template>\n <!-- No `data-wx-fill`: this screen is a window tall only while the constructor is the tab on\n screen, and that is a question only CSS can ask. `WxMain` still gives it a floor of one\n window, because it carries an action bar. -->\n <div class=\"wx-article-editor\" @focusout=\"onFocusOut\">\n <template v-if=\"loading || !article\">\n <!-- Shaped like the screen it stands in for: the head on the ground, and what it is\n waiting for on the card that will hold it. Two bare strips on an empty background\n read as a page that failed rather than one that is a moment away. -->\n <wx-skeleton class=\"wx-article-editor__ghost\" title :rows=\"1\" />\n <wx-card><wx-skeleton :rows=\"8\" /></wx-card>\n </template>\n\n <template v-else>\n <!--\n The panel's head: the way out, the trail, the name and the state of the article. What is\n left in it is what leads away from the article — the draft on the site and the article on\n the site. Saving and publishing are down in the bar, where the eye is, and only there: a\n second copy of a button already on screen is not a reminder (§3.3).\n -->\n <wx-screen-head\n divider\n :back=\"list\"\n :back-label=\"t('module.articles')\"\n :title=\"title || t('article.untitled')\"\n :subtitle=\"publication\"\n :actions=\"actions\"\n >\n <template #trail>\n <wx-breadcrumb size=\"sm\" :label=\"t('article.trail')\">\n <wx-breadcrumb-item :as=\"'router-link'\" :to=\"list\">\n {{ t('module.articles') }}\n </wx-breadcrumb-item>\n <wx-breadcrumb-item current>{{ title || t('article.untitled') }}</wx-breadcrumb-item>\n </wx-breadcrumb>\n </template>\n\n <template #title-after>\n <wx-badge :type=\"badge()\" dot>{{ t(`panel.status-${article.status}`) }}</wx-badge>\n <!-- What is waiting, said beside the state rather than instead of it, exactly as the\n list says it: the article is on the site, and there is something that is not.\n The bar used to carry this in words; the head is where the state of the record\n already lives. -->\n <wx-badge v-if=\"article.status === 'modified'\" type=\"primary\" round>\n {{ t('panel.edits') }}\n </wx-badge>\n </template>\n </wx-screen-head>\n\n <!-- Somebody else wrote while this editor was open. What does not overlap is merged\n without a word; what does is listed place by place. -->\n <wx-editing-alerts :editing=\"editing\" @save=\"save\" @theirs=\"takeTheirs\" />\n\n <div class=\"wx-article-editor__screen\">\n <wx-screen v-model=\"values\" name=\"blog.article-form\" :errors=\"errors\" :disabled=\"locked\" />\n </div>\n\n <!--\n The last row of the screen, not a layer over it: the tab above shrinks by the height of\n the bar and is never covered by it.\n -->\n <!--\n Two buttons and a mark, the same bar the page editor has.\n\n What used to stand here besides them: a sentence saying since when the article is on the\n site and that there are unpublished edits, and a third button to throw those edits away.\n Half the sentence is what the badge beside the name already says, the other half is a\n fact about the article rather than about the last keystroke and now stands under its\n name; and throwing work away is not a button to keep beside \"publish\" — it is in the\n `···`, where this panel keeps everything that cannot be undone.\n -->\n <wx-action-bar v-if=\"canManage\">\n <template #state>\n <wx-save-state v-if=\"!editing.stopped.value\" :state=\"state\" />\n </template>\n\n <wx-button\n variant=\"outline\"\n :loading=\"saving\"\n :disabled=\"!dirty || editing.stopped.value\"\n :title=\"editing.blocked.value\"\n @click=\"save\"\n >\n {{ t('article.save') }}\n </wx-button>\n\n <wx-button\n type=\"primary\"\n :loading=\"working\"\n :disabled=\"editing.stopped.value || (article.status === 'published' && !dirty)\"\n :title=\"editing.blocked.value\"\n @click=\"publish\"\n >\n {{ future ? t('article.schedule') : t('panel.publish') }}\n </wx-button>\n </wx-action-bar>\n </template>\n </div>\n</template>\n\n<style scoped>\n.wx-article-editor {\n display: flex;\n flex-direction: column;\n gap: var(--wx-gap, var(--wx-space-16));\n /* The constructor is the whole screen below the head, so the page is as tall as what it is\n drawn in and never taller: the tree, the form and the preview scroll inside themselves,\n and the head stays where it was put. */\n height: 100%;\n min-height: 0;\n container-type: inline-size;\n}\n\n.wx-article-editor__ghost {\n max-width: 420px;\n}\n\n.wx-article-editor__screen {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-height: 0;\n}\n\n/*\n * The tabs are the screen's, not this page's, so the height has to travel through them: every\n * box between the page and the constructor is a flex column that may shrink, or the constructor\n * sizes itself to its content and the whole page scrolls with the head on top of it.\n *\n * `:not([hidden])` is load-bearing. The tabs are kept alive, so the three that are not on\n * screen are still in the document, and a `display: flex` that reached them would make each one\n * a quarter of the height the visible one should have had.\n */\n.wx-article-editor__screen :deep(.wx-screen-host),\n.wx-article-editor__screen :deep(.wx-screen),\n.wx-article-editor__screen :deep(.wx-tabs),\n.wx-article-editor__screen :deep(.wx-tabs__layout),\n.wx-article-editor__screen :deep(.wx-tabs__panels),\n.wx-article-editor__screen :deep(.wx-tab:not([hidden])) {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-height: 0;\n}\n\n/*\n * No tab is a box of a fixed height any more, and none has a scrollbar of its own: the\n * constructor's preview is as tall as the page it shows and the browser scrolls it. A tab\n * that grows with its contents is also the only kind that does not clip them — a scroll box\n * cut the shadows off the cards inside it square at all four edges.\n */\n</style>\n"],"names":["PAUSE","props","__props","context","useAdmin","api","createBlogApi","route","useRoute","router","useRouter","locales","useLocales","dates","useDates","useBlogMessages","t","useTranslate","panel","message","useErrorText","id","computed","list","article","ref","values","revision","prefix","previewUrl","options","related","loading","saving","working","errors","snapshot","reloadToken","canManage","editing","useEditing","detail","theirs","refresh","flight","conflict","locked","current","dirty","state","title","written","localizedValue","chosen","future","at","publication","row","when","provideBlocksPreview","provideArticleEditor","load","save","take","silent","error","toast","timer","schedule","onFocusOut","carried","done","write","sending","sent","failure","takeTheirs","discard","confirm","publish","held","next","old","address","unpublish","badge","status","leaveGuard","event","watch","onMounted","onBeforeUnmount","onBeforeRouteLeave","actions","leads","_createElementBlock","_Fragment","_createVNode","_unref","WxSkeleton","WxCard","WxScreenHead","WxBreadcrumb","WxBreadcrumbItem","_createTextVNode","_toDisplayString","WxBadge","_createBlock","WxEditingAlerts","_createElementVNode","_hoisted_1","WxScreen","$event","WxActionBar","WxSaveState","WxButton"],"mappings":";;;;;;mDAoEMA,KAAQ;;;;;;AAhBd,UAAMC,IAAQC,IAERC,IAAUC,GAAA,GACVC,IAAMC,GAAcH,CAAO,GAC3BI,KAAQC,GAAA,GACRC,KAASC,GAAA,GACTC,KAAUC,GAAA,GACVC,IAAQC,GAAA;AACd,IAAAC,GAAA;AAEA,UAAMC,IAAIC,GAAa,WAAW,GAC5BC,IAAQD,GAAa,YAAY,GAEjCE,IAAUC,GAAA,GAKVC,IAAKC,EAAS,MAAM,OAAOf,GAAM,OAAO,EAAE,CAAC,GAC3CgB,IAAOD,EAAS,MAAM,GAAGrB,EAAM,IAAI,WAAW,GAE9CuB,IAAUC,EAAuB,IAAI,GACrCC,IAASD,EAAiB,EAAE,GAC5BE,IAAWF,EAAI,EAAE,GACjBG,IAASH,EAAI,EAAE,GACfI,IAAaJ,EAAmB,IAAI,GACpCK,IAAUL,EAA8B,EAAE,SAAS,CAAA,GAAI,SAAS,CAAA,GAAI,GACpEM,IAAUN,EAAqB,EAAE,GAEjCO,IAAUP,EAAI,EAAI,GAClBQ,IAASR,EAAI,EAAK,GAClBS,IAAUT,EAAI,EAAK,GACnBU,IAASV,EAA8B,EAAE,GAEzCW,IAAWX,EAAI,EAAE,GACjBY,IAAcZ,EAAI,CAAC,GAEnBa,IAAYhB,EAAS,MAAMnB,EAAQ,IAAI,sBAAsB,CAAC,GAO9DoC,IAAUC,GAAwB;AAAA,MACtC,QAAQ;AAAA,MACR,IAAI,MAAMhB,EAAQ,OAAO;AAAA,MACzB,QAAAE;AAAA,MACA,UAAAC;AAAA,MACA,MAAM,YAAY;AAChB,cAAMc,IAAS,MAAMpC,EAAI,IAAIgB,EAAG,KAAK;AAErC,eAAO,EAAE,QAAQoB,EAAO,QAAQ,UAAUA,EAAO,SAAA;AAAA,MACnD;AAAA,MACA,OAAO,CAACC,MAAW;AACjB,QAAAN,EAAS,QAAQ,KAAK,UAAUM,EAAO,MAAM,GAC7CL,EAAY,SAAS;AAAA,MACvB;AAAA;AAAA;AAAA,MAGA,SAAS,MAAMM,GAAA;AAAA,MACf,SAAS,YAAY;AACnB,QAAInB,EAAQ,SAAO,MAAMnB,EAAI,QAAQmB,EAAQ,MAAM,EAAE;AAAA,MACvD;AAAA,MACA,UAAU,MAAMc,EAAU;AAAA,MAC1B,MAAM,MAAML,EAAO,SAASW,MAAW;AAAA,MACvC,QAAQ;AAAA,IAAA,CACT,GAEKC,IAAWN,EAAQ,UAGnBO,IAASxB,EAAS,MAAM,CAACgB,EAAU,SAASJ,EAAQ,KAAK,GAEzDa,IAAUzB,EAAS,MAAM,KAAK,UAAUI,EAAO,KAAK,CAAC,GACrDsB,IAAQ1B,EAAS,MAAMc,EAAS,UAAU,MAAMW,EAAQ,UAAUX,EAAS,KAAK,GAGhFa,KAAQ3B,EAAyC,MACjDW,EAAO,QAAc,WAElBe,EAAM,QAAQ,YAAY,OAClC,GAMKE,IAAQ5B,EAAS,MAAM;AAC3B,YAAM6B,IAAUzB,EAAO,MAAM;AAE7B,aAAO0B,GAAeD,GAASxC,GAAQ,OAAO,OAAOa,EAAQ,OAAO,SAAS,EAAE;AAAA,IACjF,CAAC,GAGK6B,IAAS/B,EAAwB,MAAM;AAC3C,YAAM6B,IAAUzB,EAAO,MAAM;AAE7B,aAAO,OAAOyB,KAAY,YAAYA,MAAY,KAAKA,IAAU;AAAA,IACnE,CAAC,GAEKG,IAAShC,EAAS,MAAM;AAC5B,UAAI+B,EAAO,UAAU,KAAM,QAAO;AAGlC,YAAME,IAAK,IAAI,KAAKF,EAAO,MAAM,QAAQ,KAAK,GAAG,CAAC;AAElD,aAAO,CAAC,OAAO,MAAME,EAAG,QAAA,CAAS,KAAKA,EAAG,QAAA,IAAY,KAAK,IAAA;AAAA,IAC5D,CAAC,GAUKC,KAAclC,EAAS,MAAM;AACjC,YAAMmC,IAAMjC,EAAQ;AAEpB,UAAI,CAACiC,EAAK,QAAO;AAEjB,YAAMC,IAAO7C,EAAM,MAAM4C,EAAI,YAAY;AAQzC,aANa;AAAA,QACX,WAAWzC,EAAE,0BAA0B,EAAE,MAAM0C,GAAM;AAAA,QACrD,WAAW1C,EAAE,sBAAsB,EAAE,MAAM0C,GAAM;AAAA,QACjD,UAAU1C,EAAE,sBAAsB,EAAE,MAAM0C,GAAM;AAAA,MAAA,EAChDD,EAAI,MAAgD,KAEvC;AAAA,IACjB,CAAC;AAID,IAAAE,GAAqB,EAAE,KAAKrC,EAAS,MAAMO,EAAW,KAAK,GAAG,QAAQQ,GAAa,GAEnFuB,GAAqB;AAAA,MACnB,SAAApC;AAAA,MACA,QAAAE;AAAA,MACA,SAAAI;AAAA,MACA,SAAAC;AAAA,MACA,QAAAH;AAAA,MACA,MAAM3B,EAAM;AAAA,MACZ,WAAWqC,EAAU;AAAA,MACrB,UAAUQ;AAAA,MACV,QAAQ,MAAMe,EAAK,EAAI;AAAA,MACvB,MAAM,MAAMC,EAAA;AAAA,IAAK,CAClB;AAED,aAASC,EAAKtB,GAA6B;AACzC,MAAAjB,EAAQ,QAAQiB,EAAO,SACvBf,EAAO,QAAQe,EAAO,QACtBd,EAAS,QAAQc,EAAO,UACxBb,EAAO,QAAQa,EAAO,QACtBZ,EAAW,QAAQY,EAAO,aAC1BX,EAAQ,QAAQW,EAAO,SACvBV,EAAQ,QAAQU,EAAO,SACvBL,EAAS,QAAQ,KAAK,UAAUK,EAAO,MAAM,GAC7CF,EAAQ,OAAOE,EAAO,MAAM,GAC5BI,EAAS,QAAQ;AAAA,IACnB;AASA,mBAAegB,EAAKG,IAAS,IAAsB;AACjD,MAAKA,MAAQhC,EAAQ,QAAQ;AAE7B,UAAI;AACF,QAAA+B,EAAK,MAAM1D,EAAI,IAAIgB,EAAG,KAAK,CAAC;AAAA,MAC9B,SAAS4C,GAAO;AACd,QAAAC,EAAM,OAAO/C,EAAQ8C,CAAK,CAAC,GACtBxD,GAAO,KAAKc,EAAK,KAAK;AAAA,MAC7B,UAAA;AACE,QAAAS,EAAQ,QAAQ;AAAA,MAClB;AAAA,IACF;AAOA,mBAAeW,KAAyB;AACtC,UAAI;AACF,cAAMF,IAAS,MAAMpC,EAAI,IAAIgB,EAAG,KAAK;AAErC,QAAAG,EAAQ,QAAQiB,EAAO,SACvBb,EAAO,QAAQa,EAAO,QACtBZ,EAAW,QAAQY,EAAO;AAAA,MAC5B,QAAQ;AAAA,MAER;AAAA,IACF;AAGA,QAAI0B;AAEJ,aAASC,KAAiB;AACxB,MAAI,CAAC9B,EAAU,SAASO,EAAS,SAAS,CAACG,EAAM,SAAST,EAAQ,QAAQ,UAE1E,aAAa4B,CAAK,GAClBA,IAAQ,WAAW,MAAA;AAAM,QAAKL,EAAA;AAAA,SAAQ9D,EAAK;AAAA,IAC7C;AAGA,aAASqE,KAAmB;AAC1B,MAAI,CAAC/B,EAAU,SAASO,EAAS,SAAS,CAACG,EAAM,SAASf,EAAO,SAASM,EAAQ,QAAQ,UAG1F,aAAa4B,CAAK,GACbL,EAAA;AAAA,IACP;AAGA,QAAIlB;AAMJ,mBAAekB,IAAsB;AACnC,aAAOlB,KAAQ;AACb,cAAM,EAAE,SAAA0B,GAAS,MAAAC,MAAS3B;AAK1B,YAHA,MAAM2B,GAGF,CAACvB,EAAM,SAASH,EAAS,SAASE,EAAQ,UAAUuB,EAAS;AAAA,MACnE;AAEA,YAAMA,IAAUvB,EAAQ,OAClBwB,IAAOC,GAAA;AAEb,MAAA5B,IAAS,EAAE,SAAA0B,GAAS,MAAAC,EAAA;AAEpB,UAAI;AACF,cAAMA;AAAA,MACR,UAAA;AACE,QAAI3B,GAAQ,SAAS2B,MAAM3B,IAAS;AAAA,MACtC;AAAA,IACF;AAEA,mBAAe4B,KAAuB;AACpC,UAAI,CAAChD,EAAQ,SAAS,CAACc,EAAU,MAAO;AAExC,mBAAa6B,CAAK;AAIlB,YAAMM,IAAU1B,EAAQ,OAClB2B,IAAOhD,EAAO;AAEpB,MAAAO,EAAO,QAAQ,IACfE,EAAO,QAAQ,CAAA;AAEf,UAAI;AACF,cAAMM,IAAS,MAAMpC,EAAI,KAAKmB,EAAQ,MAAM,IAAI,EAAE,QAAQkD,GAAM,UAAU/C,EAAS,OAAO;AAE1F,QAAAH,EAAQ,QAAQiB,EAAO,SACvBd,EAAS,QAAQc,EAAO,UACxBb,EAAO,QAAQa,EAAO,QACtBZ,EAAW,QAAQY,EAAO,aAC1BX,EAAQ,QAAQW,EAAO,SACvBV,EAAQ,QAAQU,EAAO,SACvBI,EAAS,QAAQ,MAEbE,EAAQ,UAAU0B,MAASrC,EAAS,QAAQqC,IAGhDlC,EAAQ,OAAOmC,CAAI,GAGnBrC,EAAY,SAAS;AAAA,MACvB,SAAS4B,GAAO;AACd,cAAMU,IAAUV;AAKhB,YAAIU,EAAQ,WAAW,OAAOA,EAAQ,MAAM,MAAM;AAChD,gBAAMjC,IAASiC,EAAQ,KAAK;AAG5B,UACEpC,EAAQ,QAAQ;AAAA,YACd,QAAQG,EAAO;AAAA,YACf,UAAUA,EAAO;AAAA,YACjB,SAASiC,EAAQ,KAAK;AAAA,UAAA,CACvB,MAEDnD,EAAQ,QAAQkB,EAAO,SAClBoB,EAAA;AAAA,QAET,MAAA,CAAWa,EAAQ,MAAM,UACvBxC,EAAO,QAAQwC,EAAQ,KAAK,QAC5BT,EAAM,OAAOlD,EAAE,qBAAqB,CAAC,KAC1B,MAAMuB,EAAQ,OAAO0B,CAAK,KAErCC,EAAM,OAAO/C,EAAQ8C,CAAK,CAAC;AAAA,MAE/B,UAAA;AACE,QAAAhC,EAAO,QAAQ;AAAA,MACjB;AAAA,IACF;AAGA,mBAAe2C,KAA4B;AACzC,YAAMf,EAAK,EAAI;AAAA,IACjB;AAUA,mBAAegB,KAAyB;AAYtC,UAXI,GAACrD,EAAQ,SAWT,CATW,MAAMsD,EAAQ;AAAA,QAC3B,OAAO5D,EAAM,sBAAsB;AAAA,QACnC,SAASA,EAAM,qBAAqB;AAAA,QACpC,aAAaF,EAAE,iBAAiB;AAAA;AAAA,QAEhC,YAAYE,EAAM,qBAAqB;AAAA,QACvC,MAAM;AAAA,MAAA,CACP,IAID;AAAA,QAAAgB,EAAQ,QAAQ;AAEhB,YAAI;AACF,UAAA6B,EAAK,MAAM1D,EAAI,QAAQmB,EAAQ,MAAM,EAAE,CAAC,GACxC0C,EAAM,QAAQlD,EAAE,mBAAmB,CAAC;AAAA,QACtC,SAASiD,GAAO;AACd,UAAAC,EAAM,OAAO/C,EAAQ8C,CAAK,CAAC;AAAA,QAC7B,UAAA;AACE,UAAA/B,EAAQ,QAAQ;AAAA,QAClB;AAAA;AAAA,IACF;AAUA,mBAAe6C,KAAyB;AAMtC,UAHI/B,EAAM,SAAO,MAAMc,EAAA,GACnBjB,EAAS,SAASG,EAAM,SAExB,CAACxB,EAAQ,MAAO;AAIpB,YAAMwD,IAAO,MAAMzC,EAAQ,cAAA;AAE3B,UAAI,CAACyC,EAAM;AAEX,YAAMvB,IAAMjC,EAAQ,OAIdyD,IAAOxB,EAAI,aAAaA,EAAI,MAC5ByB,IACJzB,EAAI,aAAa,QAAQA,EAAI,SAAS,OAClC,IAAIvC,EAAM,wBAAwB,EAAE,KAAK,IAAIuC,EAAI,IAAI,GAAA,CAAI,CAAC,KAC1D,IACA0B,IAAUF,MAAS,OAAO,OAAO,IAAIA,CAAI,IACzCvB,KAAOL,EAAO,UAAU,OAAOrC,EAAE,qBAAqB,IAAIH,EAAM,MAAMwC,EAAO,KAAK;AAgBxF,UAZE2B,EAAK,SACJ,MAAMF,EAAQ;AAAA,QACb,OAAO9D,EAAE,yBAAyB,EAAE,OAAOyC,EAAI,OAAO;AAAA,QACtD,SAASH,EAAO,QACZtC,EAAE,yBAAyB,EAAE,MAAM0C,IAAM,IACzCyB,MAAY,OACVnE,EAAE,yBAAyB,IAC3BA,EAAE,wBAAwB,EAAE,SAAAmE,EAAA,CAAS,IAAID;AAAA,QAC/C,aAAa5B,EAAO,QAAQtC,EAAE,kBAAkB,IAAIA,EAAE,eAAe;AAAA,QACrE,YAAYA,EAAE,cAAc;AAAA,MAAA,CAC7B,GAIH;AAAA,QAAAkB,EAAQ,QAAQ;AAEhB,YAAI;AACF,gBAAM7B,EAAI,QAAQoD,EAAI,IAAIJ,EAAO,OAAO2B,EAAK,QAAQ,GACrD,MAAMnB,EAAK,EAAI,GACfK,EAAM,QAAQZ,EAAO,QAAQtC,EAAE,mBAAmB,IAAIA,EAAE,iBAAiB,CAAC;AAAA,QAC5E,SAASiD,IAAO;AACd,UAAM,MAAM1B,EAAQ,OAAO0B,EAAK,KAAIC,EAAM,OAAO/C,EAAQ8C,EAAK,CAAC;AAAA,QACjE,UAAA;AACE,UAAA/B,EAAQ,QAAQ;AAAA,QAClB;AAAA;AAAA,IACF;AAMA,mBAAekD,KAA2B;AACxC,YAAM3B,IAAMjC,EAAQ;AAWpB,UATI,GAACiC,KASD,CAPW,MAAMqB,EAAQ;AAAA,QAC3B,OAAO5D,EAAM,wBAAwB;AAAA,QACrC,SAASA,EAAM,uBAAuB;AAAA,QACtC,aAAaA,EAAM,kBAAkB;AAAA,QACrC,YAAYA,EAAM,uBAAuB;AAAA,MAAA,CAC1C,IAID;AAAA,QAAAgB,EAAQ,QAAQ;AAEhB,YAAI;AACF,gBAAM7B,EAAI,UAAUoD,EAAI,EAAE,GAC1B,MAAMI,EAAK,EAAI,GACfK,EAAM,QAAQhD,EAAM,oBAAoB,CAAC;AAAA,QAC3C,SAAS+C,GAAO;AACd,UAAAC,EAAM,OAAO/C,EAAQ8C,CAAK,CAAC;AAAA,QAC7B,UAAA;AACE,UAAA/B,EAAQ,QAAQ;AAAA,QAClB;AAAA;AAAA,IACF;AAEA,aAASmD,KAAuD;AAC9D,YAAMC,IAAS9D,EAAQ,OAAO;AAE9B,aAAI8D,MAAW,cAAoB,YAC/BA,MAAW,aAAmB,YAE3BA,MAAW,cAAc,YAAY;AAAA,IAC9C;AAGA,aAASC,GAAWC,GAAgC;AAClD,MAAIxC,EAAM,SAAOwC,EAAM,eAAA;AAAA,IACzB;AAEA,IAAAC,GAAM1C,GAASqB,EAAQ,GACvBqB,GAAMpE,GAAI;AAAM,MAAKwC;KAAM,GAE3B6B,GAAU,MAAM;AACd,MAAK7B,EAAA,GACL,OAAO,iBAAiB,gBAAgB0B,EAAU;AAAA,IACpD,CAAC,GAEDI,GAAgB,MAAM;AACpB,mBAAaxB,CAAK,GAClB,OAAO,oBAAoB,gBAAgBoB,EAAU;AAAA,IACvD,CAAC,GAQDK,GAAmB,YACb,CAAC5C,EAAM,SAAS,CAACV,EAAU,UAE/B,MAAMwB,EAAA,GAEF,CAACd,EAAM,SAAc,KAElB,MAAM8B,EAAQ;AAAA,MACnB,OAAO9D,EAAE,qBAAqB;AAAA,MAC9B,SAASA,EAAE,oBAAoB;AAAA,MAC/B,aAAaA,EAAE,eAAe;AAAA,MAC9B,YAAYA,EAAE,cAAc;AAAA,MAC5B,MAAM;AAAA,IAAA,CACP,CACF;AAED,UAAM6E,KAAUvE,EAAyB,MAAM;AAC7C,YAAMwE,IAAwB,CAAA;AAE9B,aAAIjE,EAAW,SACbiE,EAAM,KAAK,EAAE,KAAK,WAAW,OAAO9E,EAAE,iBAAiB,GAAG,MAAM,OAAO,MAAMa,EAAW,OAAO,GAG7FL,EAAQ,OAAO,OAAOA,EAAQ,MAAM,WAAW,WACjDsE,EAAM,KAAK;AAAA,QACT,KAAK;AAAA,QACL,OAAO9E,EAAE,oBAAoB;AAAA,QAC7B,MAAM;AAAA,QACN,MAAMQ,EAAQ,MAAM;AAAA,MAAA,CACrB,GAMDc,EAAU,UACTd,EAAQ,OAAO,WAAW,eAAeA,EAAQ,OAAO,WAAW,eAEpEsE,EAAM,KAAK;AAAA,QACT,KAAK;AAAA,QACL,OAAO5E,EAAM,kBAAkB;AAAA,QAC/B,MAAM;AAAA,QACN,KAAK,MAAA;AAAM,UAAKkE,GAAA;AAAA;AAAA,MAAU,CAC3B,GASC5D,EAAQ,OAAO,WAAW,cAC5BsE,EAAM,KAAK;AAAA,QACT,KAAK;AAAA,QACL,OAAO9E,EAAE,iBAAiB;AAAA,QAC1B,MAAM;AAAA,QACN,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,KAAK,MAAA;AAAM,UAAK6D,GAAA;AAAA;AAAA,MAAQ,CACzB,GAGIiB;AAAA,IACT,CAAC;2BAOCC,EA6FM,OAAA;AAAA,MA7FD,OAAM;AAAA,MAAqB,YAAU1B;AAAA,IAAA;MACxBrC,EAAA,UAAYR,EAAA,cAA5BuE,EAMWC,IAAA,EAAA,KAAA,EAAA,GAAA;AAAA,QAFTC,EAAgEC,EAAAC,EAAA,GAAA;AAAA,UAAnD,OAAM;AAAA,UAA2B,OAAA;AAAA,UAAO,MAAM;AAAA,QAAA;QAC3DF,EAA4CC,EAAAE,EAAA,GAAA,MAAA;AAAA,qBAAnC,MAAyB;AAAA,YAAzBH,EAAyBC,EAAAC,EAAA,GAAA,EAAX,MAAM,GAAC;AAAA,UAAA;;;sBAGhCJ,EAmFWC,IAAA,EAAA,KAAA,KAAA;AAAA,QA5ETC,EA2BiBC,EAAAG,EAAA,GAAA;AAAA,UA1Bf,SAAA;AAAA,UACC,MAAM9E,EAAA;AAAA,UACN,cAAY2E,EAAAlF,CAAA,EAAC,iBAAA;AAAA,UACb,OAAOkC,EAAA,SAASgD,EAAAlF,CAAA,EAAC,kBAAA;AAAA,UACjB,UAAUwC,GAAA;AAAA,UACV,SAASqC,GAAA;AAAA,QAAA;UAEC,SACT,MAKgB;AAAA,YALhBI,EAKgBC,EAAAI,EAAA,GAAA;AAAA,cALD,MAAK;AAAA,cAAM,OAAOJ,EAAAlF,CAAA,EAAC,eAAA;AAAA,YAAA;yBAChC,MAEqB;AAAA,gBAFrBiF,EAEqBC,EAAAK,EAAA,GAAA;AAAA,kBAFA,IAAI;AAAA,kBAAgB,IAAIhF,EAAA;AAAA,gBAAA;6BAC3C,MAA0B;AAAA,wBAAvB2E,EAAAlF,CAAA,EAAC,iBAAA,CAAA,GAAA,CAAA;AAAA,kBAAA;;;gBAENiF,EAAqFC,EAAAK,EAAA,GAAA,EAAjE,SAAA,MAAO;AAAA,6BAAC,MAAoC;AAAA,oBAAjCC,EAAAC,EAAAvD,EAAA,SAASgD,EAAAlF,CAAA,EAAC,kBAAA,CAAA,GAAA,CAAA;AAAA,kBAAA;;;;;;;UAIlC,iBACT,MAAkF;AAAA,YAAlFiF,EAAkFC,EAAAQ,EAAA,GAAA;AAAA,cAAvE,MAAMrB,GAAA;AAAA,cAAS,KAAA;AAAA,YAAA;yBAAI,MAAyC;AAAA,oBAAtCa,EAAAlF,CAAA,EAAC,gBAAiBQ,EAAA,MAAQ,MAAM,EAAA,CAAA,GAAA,CAAA;AAAA,cAAA;;;YAKjDA,EAAA,MAAQ,WAAM,mBAA9BmF,EAEWT,EAAAQ,EAAA,GAAA;AAAA;cAFoC,MAAK;AAAA,cAAU,OAAA;AAAA,YAAA;yBAC5D,MAAsB;AAAA,oBAAnBR,EAAAlF,CAAA,EAAC,aAAA,CAAA,GAAA,CAAA;AAAA,cAAA;;;;;;QAOViF,EAA0EC,EAAAU,EAAA,GAAA;AAAA,UAAtD,SAASV,EAAA3D,CAAA;AAAA,UAAU,QAAMuB;AAAA,UAAO,UAAQc;AAAA,QAAA;QAE5DiC,GAEM,OAFNC,IAEM;AAAA,UADJb,EAA2FC,EAAAa,EAAA,GAAA;AAAA,wBAAvErF,EAAA;AAAA,0DAAAA,EAAM,QAAAsF;AAAA,YAAE,MAAK;AAAA,YAAqB,QAAQ7E,EAAA;AAAA,YAAS,UAAUW,EAAA;AAAA,UAAA;;QAiB9DR,EAAA,cAArBqE,EAwBgBT,EAAAe,EAAA,GAAA,EAAA,KAAA,KAAA;AAAA,UAvBH,SACT,MAA8D;AAAA,YAAxCf,EAAA3D,CAAA,EAAQ,QAAQ,0BAAtCoE,EAA8DT,EAAAgB,EAAA,GAAA;AAAA;cAAhB,OAAOjE,GAAA;AAAA,YAAA;;qBAGvD,MAQY;AAAA,YARZgD,EAQYC,EAAAiB,EAAA,GAAA;AAAA,cAPV,SAAQ;AAAA,cACP,SAASlF,EAAA;AAAA,cACT,WAAWe,EAAA,SAASkD,KAAQ,QAAQ;AAAA,cACpC,OAAOA,EAAA3D,CAAA,EAAQ,QAAQ;AAAA,cACvB,SAAOuB;AAAA,YAAA;yBAER,MAAuB;AAAA,oBAApBoC,EAAAlF,CAAA,EAAC,cAAA,CAAA,GAAA,CAAA;AAAA,cAAA;;;YAGNiF,EAQYC,EAAAiB,EAAA,GAAA;AAAA,cAPV,MAAK;AAAA,cACJ,SAASjF,EAAA;AAAA,cACT,UAAUgE,EAAA3D,CAAA,EAAQ,QAAQ,SAAUf,EAAA,MAAQ,WAAM,eAAA,CAAqBwB,EAAA;AAAA,cACvE,OAAOkD,EAAA3D,CAAA,EAAQ,QAAQ;AAAA,cACvB,SAAOwC;AAAA,YAAA;yBAER,MAAyD;AAAA,oBAAtDzB,EAAA,QAAS4C,EAAAlF,CAAA,EAAC,kBAAA,IAAuBkF,EAAAlF,CAAA,EAAC,eAAA,CAAA,GAAA,CAAA;AAAA,cAAA;;;;;;;;;;"}
@@ -2,7 +2,7 @@ import { defineComponent as _e, useTemplateRef as xe, ref as J, computed as o, w
2
2
  import { useRoute as ze, useRouter as $e } from "vue-router";
3
3
  import { useAdmin as Ce, useTranslate as Q, useErrorText as Te, rowMenuWidth as Be, WxListScreen as Se, WxRowMenu as X, WxDate as Y, WxAddressNote as Ae, WxPreviewThumb as Ne, WxFilterChips as Ve } from "@webx-ui/module-admin";
4
4
  import { useElementWidth as Re, createModal as Ee, WxTable as Fe, WxEntityCard as Me, WxBadge as z, WxIcon as T, WxText as Z, WxTooltip as Pe, WxFormItem as E, WxSelect as F, WxButton as Ue, toast as B, confirm as De } from "@webx-ui/core";
5
- import { c as Ie, u as Oe, _ as Le, a as je } from "./index-ChbP89HC.js";
5
+ import { c as Ie, u as Oe, _ as Le, a as je } from "./index-C_gXxP0l.js";
6
6
  const Ge = ["src"], He = { class: "wx-articles__name" }, Je = { class: "wx-articles__name-text" }, Ke = { class: "wx-articles__title" }, Qe = { class: "wx-articles__address" }, Xe = { class: "wx-articles__chips" }, Ye = { class: "wx-articles__state" }, ee = 640, Ze = /* @__PURE__ */ _e({
7
7
  __name: "ArticlesPage",
8
8
  props: {
@@ -551,4 +551,4 @@ const Ge = ["src"], He = { class: "wx-articles__name" }, Je = { class: "wx-artic
551
551
  export {
552
552
  il as default
553
553
  };
554
- //# sourceMappingURL=ArticlesPage-BnHi8gF0.js.map
554
+ //# sourceMappingURL=ArticlesPage-Cdb5wOyT.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ArticlesPage-BnHi8gF0.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 WxPreviewThumb,\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\n/*\n * The covers of the rows on screen, large: a click on one opens them all, the full pictures, in\n * the order of the list (`useLightbox`).\n */\nconst covered = computed(() => (page.value?.data ?? []).filter((one) => one.cover?.thumb))\nconst covers = computed(() =>\n covered.value.map((one) => ({\n src: one.cover!.url ?? one.cover!.thumb!,\n thumb: one.cover!.thumb ?? undefined,\n alt: one.title,\n caption: one.title,\n })),\n)\n\nfunction coverOf(row: ArticleRow): number {\n return covered.value.indexOf(row)\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 <wx-preview-thumb v-if=\"row.cover?.thumb\" fill :items=\"covers\" :start=\"coverOf(row)\">\n <img :src=\"row.cover.thumb\" alt=\"\" loading=\"lazy\" />\n </wx-preview-thumb>\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","useTranslate","admin","message","useErrorText","root","useTemplateRef","width","useElementWidth","page","ref","loading","create","createModal","ArticleCreateDialog","canManage","computed","title","module","view","value","inBin","query","number","covered","one","covers","coverOf","row","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","_createElementVNode","_normalizeClass","WxPreviewThumb","_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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"}
1
+ {"version":3,"file":"ArticlesPage-Cdb5wOyT.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 WxPreviewThumb,\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\n/*\n * The covers of the rows on screen, large: a click on one opens them all, the full pictures, in\n * the order of the list (`useLightbox`).\n */\nconst covered = computed(() => (page.value?.data ?? []).filter((one) => one.cover?.thumb))\nconst covers = computed(() =>\n covered.value.map((one) => ({\n src: one.cover!.url ?? one.cover!.thumb!,\n thumb: one.cover!.thumb ?? undefined,\n alt: one.title,\n caption: one.title,\n })),\n)\n\nfunction coverOf(row: ArticleRow): number {\n return covered.value.indexOf(row)\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 <wx-preview-thumb v-if=\"row.cover?.thumb\" fill :items=\"covers\" :start=\"coverOf(row)\">\n <img :src=\"row.cover.thumb\" alt=\"\" loading=\"lazy\" />\n </wx-preview-thumb>\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","useTranslate","admin","message","useErrorText","root","useTemplateRef","width","useElementWidth","page","ref","loading","create","createModal","ArticleCreateDialog","canManage","computed","title","module","view","value","inBin","query","number","covered","one","covers","coverOf","row","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","_createElementVNode","_normalizeClass","WxPreviewThumb","_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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"}
@@ -2,7 +2,7 @@ import { defineComponent as M, ref as C, computed as _, watch as T, onBeforeUnmo
2
2
  import { useRoute as G, useRouter as H } from "vue-router";
3
3
  import { WxCard as J, WxListDetail as K, WxEmpty as N, WxInput as O, WxIcon as E, WxSkeleton as Q, WxAvatar as X, WxBadge as I, toast as Y } from "@webx-ui/core";
4
4
  import { useAdmin as Z, useTranslate as ee, useErrorText as te, WxListScreen as oe } from "@webx-ui/module-admin";
5
- import { d as ae, u as se, A as le, a as ne } from "./index-ChbP89HC.js";
5
+ import { d as ae, u as se, A as le, a as ne } from "./index-C_gXxP0l.js";
6
6
  const re = { class: "wx-authors__bar" }, ue = ["onClick"], ie = { class: "wx-author-row__body" }, ce = { class: "wx-author-row__name" }, de = { class: "wx-author-row__meta" }, pe = { key: 0 }, he = { class: "wx-author-row__facts" }, me = /* @__PURE__ */ M({
7
7
  __name: "AuthorsPage",
8
8
  props: {
@@ -194,4 +194,4 @@ const re = { class: "wx-authors__bar" }, ue = ["onClick"], ie = { class: "wx-aut
194
194
  export {
195
195
  ge as default
196
196
  };
197
- //# sourceMappingURL=AuthorsPage-DSLsX44M.js.map
197
+ //# sourceMappingURL=AuthorsPage-Catpwado.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"AuthorsPage-DSLsX44M.js","sources":["../src/AuthorsPage.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, ref, watch } from 'vue'\nimport { useRoute, useRouter, type LocationQueryRaw } from 'vue-router'\nimport {\n toast,\n WxAvatar,\n WxBadge,\n WxCard,\n WxEmpty,\n WxIcon,\n WxInput,\n WxListDetail,\n WxSkeleton,\n} from '@webx-ui/core'\nimport { useAdmin, useErrorText, useTranslate, WxListScreen } from '@webx-ui/module-admin'\nimport AuthorPane from './AuthorPane.vue'\nimport { createAuthorsApi, type AuthorRow } from './authors'\nimport { useBlogMessages } from './i18n'\n\n/**\n * The authors: every profile on the left, the one being written on the right (§19.5 of the blog\n * spec) — the shape of the team, without its order and its bin: the people who write one site are\n * a short list, and a profile that goes takes nothing with it but a name on a page.\n *\n * The search and the open profile live in the address, so a link to an author — the one the panel\n * on the site and the article editor make — is a link to them in their list.\n */\nwithDefaults(defineProps<{ base?: string }>(), { base: '/blog' })\n\nconst context = useAdmin()\nconst api = createAuthorsApi(context)\nconst route = useRoute()\nconst router = useRouter()\nuseBlogMessages()\n\nconst t = useTranslate('webx-blog')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\nconst rows = ref<AuthorRow[]>([])\nconst loading = ref(true)\n\nconst canManage = computed(() => context.can('blog.authors.manage'))\n\nconst title = computed(\n () =>\n context.state.manifest?.modules.find((module) => module.id === 'authors')?.title ??\n t('module.authors'),\n)\n\nconst q = computed(() => (typeof route.query.q === 'string' ? route.query.q : ''))\n\n/* What is typed, ahead of the address: the address follows it after a pause. */\nconst typed = ref(q.value)\n\n/** The open profile: a number, `'new'` for one not yet saved, or nothing. */\nconst selected = computed<number | 'new' | null>(() => {\n const raw = route.query.author\n\n if (raw === 'new') return canManage.value ? 'new' : null\n\n return typeof raw === 'string' && raw !== '' ? Number(raw) || null : null\n})\n\n/* Open is \"a profile is chosen\", so the address, the highlight and the drawer always agree. */\nconst open = computed({\n get: () => selected.value !== null,\n set: (value) => {\n if (!value) close()\n },\n})\n\nasync function load(): Promise<void> {\n loading.value = true\n\n try {\n rows.value = await api.list(q.value)\n } catch (error) {\n toast.danger(message(error))\n } finally {\n loading.value = false\n }\n}\n\nwatch(q, () => void load())\nvoid load()\n\nlet pause: ReturnType<typeof setTimeout> | undefined\n\nwatch(typed, (value) => {\n clearTimeout(pause)\n pause = setTimeout(() => {\n void router.replace({ query: { ...route.query, q: value.trim() === '' ? undefined : value } })\n }, 300)\n})\n\nonBeforeUnmount(() => clearTimeout(pause))\n\n/* `replace` and not `push`: going through the authors is not a step of history each. */\nfunction choose(id: number | 'new'): void {\n const query: LocationQueryRaw = { ...route.query, author: String(id) }\n\n // The account the article editor asked a profile for belongs to that one new profile only.\n if (id !== 'new') delete query.account\n void router.replace({ query })\n}\n\nfunction close(): void {\n const query = { ...route.query }\n\n delete query.author\n delete query.account\n void router.replace({ query })\n}\n\n/** A new profile was saved: it is a record now, and the list has it. */\nfunction created(author: AuthorRow): void {\n choose(author.id)\n void load()\n}\n\nfunction removed(id: number): void {\n if (selected.value === id) close()\n\n void load()\n}\n</script>\n\n<template>\n <wx-list-screen :title=\"title\" :card=\"false\">\n <wx-card class=\"wx-authors\" padding=\"none\">\n <wx-list-detail\n v-model:open=\"open\"\n class=\"wx-authors__panes\"\n :list-width=\"360\"\n :detail-min=\"460\"\n :detail-label=\"t('module.authors')\"\n >\n <template #list>\n <div class=\"wx-authors__bar\">\n <wx-input\n v-model=\"typed\"\n class=\"wx-authors__search\"\n size=\"sm\"\n clearable\n :placeholder=\"t('author.search')\"\n :aria-label=\"t('author.search')\"\n >\n <template #prefix><wx-icon name=\"search\" size=\"sm\" /></template>\n </wx-input>\n </div>\n\n <button\n v-if=\"canManage\"\n type=\"button\"\n class=\"wx-authors__new\"\n :class=\"{ 'is-current': selected === 'new' }\"\n @click=\"choose('new')\"\n >\n <wx-icon name=\"plus\" size=\"sm\" />\n {{ t('author.new') }}\n </button>\n\n <wx-skeleton v-if=\"loading && rows.length === 0\" class=\"wx-authors__loading\" :rows=\"4\" />\n\n <wx-empty\n v-else-if=\"rows.length === 0\"\n :title=\"q === '' ? t('author.empty') : t('author.empty-search')\"\n :description=\"q === '' ? t('author.empty-help') : undefined\"\n />\n\n <ul v-else class=\"wx-authors__list\" :class=\"{ 'is-loading': loading }\">\n <li v-for=\"item in rows\" :key=\"item.id\">\n <button\n type=\"button\"\n class=\"wx-author-row\"\n :class=\"{ 'is-current': selected === item.id }\"\n @click=\"choose(item.id)\"\n >\n <wx-avatar\n class=\"wx-author-row__photo\"\n :src=\"item.photo ?? undefined\"\n :name=\"item.name\"\n size=\"md\"\n />\n\n <span class=\"wx-author-row__body\">\n <span class=\"wx-author-row__name\">{{ item.name }}</span>\n\n <span class=\"wx-author-row__meta\">\n <span v-if=\"item.user\">{{ item.user.name }}</span>\n <span>{{ t('author.articles', { count: item.articles_count }) }}</span>\n </span>\n\n <span class=\"wx-author-row__facts\">\n <wx-badge v-if=\"item.open\" dot size=\"sm\" type=\"success\">\n {{ t('author.page-open') }}\n </wx-badge>\n <wx-badge v-else dot size=\"sm\">{{ t('author.page-closed') }}</wx-badge>\n </span>\n </span>\n </button>\n </li>\n </ul>\n </template>\n\n <template #detail=\"{ inline, back }\">\n <author-pane\n v-if=\"selected !== null\"\n :id=\"selected === 'new' ? null : selected\"\n :key=\"String(selected)\"\n :inline=\"inline\"\n @back=\"back\"\n @saved=\"load\"\n @created=\"created\"\n @removed=\"removed\"\n />\n </template>\n\n <template #empty>\n <wx-empty\n :title=\"t('author.choose')\"\n :description=\"canManage ? t('author.choose-help') : undefined\"\n />\n </template>\n </wx-list-detail>\n </wx-card>\n </wx-list-screen>\n</template>\n\n<style>\n/*\n * What scrolls here is the page, as on every other list of the panel: `clip`, not `hidden`, which\n * would make the card a scroll container and pin everything sticky inside it (CLAUDE.md §4).\n */\n.wx-authors > .wx-card__body {\n display: flex;\n flex-direction: column;\n overflow: clip;\n border-radius: inherit;\n}\n</style>\n\n<style scoped>\n.wx-authors__bar {\n display: flex;\n gap: var(--wx-space-8);\n padding: var(--wx-space-12) var(--wx-space-12) var(--wx-space-8);\n}\n\n.wx-authors__search {\n flex: 1 1 180px;\n min-width: 0;\n}\n\n.wx-authors__new {\n display: flex;\n align-items: center;\n gap: var(--wx-space-8);\n width: calc(100% - 2 * var(--wx-space-8));\n margin: 0 var(--wx-space-8) var(--wx-space-4);\n padding: var(--wx-space-8);\n border: 1px dashed var(--wx-border-default);\n border-radius: var(--wx-radius-sm);\n background: none;\n font: inherit;\n color: var(--wx-color-primary);\n text-align: start;\n cursor: pointer;\n}\n\n.wx-authors__new:hover,\n.wx-authors__new.is-current {\n background: var(--wx-bg-subtle);\n}\n\n.wx-authors__loading {\n padding: var(--wx-space-12);\n}\n\n.wx-authors__list {\n margin: 0;\n padding: 0 var(--wx-space-8) var(--wx-space-8);\n list-style: none;\n}\n\n.wx-authors__list.is-loading {\n opacity: 0.6;\n}\n\n.wx-author-row {\n display: flex;\n align-items: flex-start;\n gap: var(--wx-space-10);\n width: 100%;\n min-width: 0;\n padding: var(--wx-space-8) var(--wx-space-4);\n border: 0;\n border-radius: var(--wx-radius-sm);\n background: none;\n font: inherit;\n color: inherit;\n text-align: start;\n cursor: pointer;\n}\n\n.wx-author-row:hover,\n.wx-author-row.is-current {\n background: var(--wx-bg-subtle);\n}\n\n.wx-author-row__photo {\n flex: none;\n}\n\n.wx-author-row__body {\n display: flex;\n flex: 1 1 auto;\n flex-direction: column;\n gap: var(--wx-space-2);\n min-width: 0;\n}\n\n.wx-author-row__name {\n min-width: 0;\n overflow: hidden;\n font-weight: var(--wx-font-weight-medium);\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.wx-author-row.is-current .wx-author-row__name {\n color: var(--wx-color-primary);\n}\n\n.wx-author-row__meta {\n display: flex;\n flex-wrap: wrap;\n gap: var(--wx-space-2) var(--wx-space-8);\n color: var(--wx-text-muted);\n font-size: var(--wx-font-size-sm);\n}\n\n.wx-author-row__facts {\n display: flex;\n margin-top: var(--wx-space-2);\n}\n</style>\n"],"names":["context","useAdmin","api","createAuthorsApi","route","useRoute","router","useRouter","useBlogMessages","t","useTranslate","message","useErrorText","rows","ref","loading","canManage","computed","title","module","q","typed","selected","raw","open","value","close","load","error","toast","watch","pause","onBeforeUnmount","choose","id","query","created","author","removed","_createBlock","_unref","WxListScreen","_createVNode","WxCard","WxListDetail","$event","_createElementVNode","_hoisted_1","WxInput","WxIcon","_createElementBlock","_normalizeClass","_createTextVNode","WxSkeleton","WxEmpty","_Fragment","_renderList","item","WxAvatar","_hoisted_3","_hoisted_4","_toDisplayString","_hoisted_5","_openBlock","_hoisted_6","_hoisted_7","WxBadge","_withCtx","inline","back","AuthorPane"],"mappings":";;;;;;;;;;;AA6BA,UAAMA,IAAUC,EAAA,GACVC,IAAMC,GAAiBH,CAAO,GAC9BI,IAAQC,EAAA,GACRC,IAASC,EAAA;AACf,IAAAC,GAAA;AAEA,UAAMC,IAAIC,GAAa,WAAW,GAE5BC,IAAUC,GAAA,GAEVC,IAAOC,EAAiB,EAAE,GAC1BC,IAAUD,EAAI,EAAI,GAElBE,IAAYC,EAAS,MAAMjB,EAAQ,IAAI,qBAAqB,CAAC,GAE7DkB,IAAQD;AAAA,MACZ,MACEjB,EAAQ,MAAM,UAAU,QAAQ,KAAK,CAACmB,MAAWA,EAAO,OAAO,SAAS,GAAG,SAC3EV,EAAE,gBAAgB;AAAA,IAAA,GAGhBW,IAAIH,EAAS,MAAO,OAAOb,EAAM,MAAM,KAAM,WAAWA,EAAM,MAAM,IAAI,EAAG,GAG3EiB,IAAQP,EAAIM,EAAE,KAAK,GAGnBE,IAAWL,EAAgC,MAAM;AACrD,YAAMM,IAAMnB,EAAM,MAAM;AAExB,aAAImB,MAAQ,QAAcP,EAAU,QAAQ,QAAQ,OAE7C,OAAOO,KAAQ,YAAYA,MAAQ,MAAK,OAAOA,CAAG,KAAK;AAAA,IAChE,CAAC,GAGKC,IAAOP,EAAS;AAAA,MACpB,KAAK,MAAMK,EAAS,UAAU;AAAA,MAC9B,KAAK,CAACG,MAAU;AACd,QAAKA,KAAOC,EAAA;AAAA,MACd;AAAA,IAAA,CACD;AAED,mBAAeC,IAAsB;AACnC,MAAAZ,EAAQ,QAAQ;AAEhB,UAAI;AACF,QAAAF,EAAK,QAAQ,MAAMX,EAAI,KAAKkB,EAAE,KAAK;AAAA,MACrC,SAASQ,GAAO;AACd,QAAAC,EAAM,OAAOlB,EAAQiB,CAAK,CAAC;AAAA,MAC7B,UAAA;AACE,QAAAb,EAAQ,QAAQ;AAAA,MAClB;AAAA,IACF;AAEA,IAAAe,EAAMV,GAAG;AAAM,MAAKO;KAAM,GACrBA,EAAA;AAEL,QAAII;AAEJ,IAAAD,EAAMT,GAAO,CAACI,MAAU;AACtB,mBAAaM,CAAK,GAClBA,IAAQ,WAAW,MAAM;AACvB,QAAKzB,EAAO,QAAQ,EAAE,OAAO,EAAE,GAAGF,EAAM,OAAO,GAAGqB,EAAM,WAAW,KAAK,SAAYA,EAAA,GAAS;AAAA,MAC/F,GAAG,GAAG;AAAA,IACR,CAAC,GAEDO,EAAgB,MAAM,aAAaD,CAAK,CAAC;AAGzC,aAASE,EAAOC,GAA0B;AACxC,YAAMC,IAA0B,EAAE,GAAG/B,EAAM,OAAO,QAAQ,OAAO8B,CAAE,EAAA;AAGnE,MAAIA,MAAO,SAAO,OAAOC,EAAM,SAC1B7B,EAAO,QAAQ,EAAE,OAAA6B,GAAO;AAAA,IAC/B;AAEA,aAAST,IAAc;AACrB,YAAMS,IAAQ,EAAE,GAAG/B,EAAM,MAAA;AAEzB,aAAO+B,EAAM,QACb,OAAOA,EAAM,SACR7B,EAAO,QAAQ,EAAE,OAAA6B,GAAO;AAAA,IAC/B;AAGA,aAASC,EAAQC,GAAyB;AACxC,MAAAJ,EAAOI,EAAO,EAAE,GACXV,EAAA;AAAA,IACP;AAEA,aAASW,EAAQJ,GAAkB;AACjC,MAAIZ,EAAS,UAAUY,KAAIR,EAAA,GAEtBC,EAAA;AAAA,IACP;2BAIEY,EAkGiBC,EAAAC,EAAA,GAAA;AAAA,MAlGA,OAAOvB,EAAA;AAAA,MAAQ,MAAM;AAAA,IAAA;iBACpC,MAgGU;AAAA,QAhGVwB,EAgGUF,EAAAG,CAAA,GAAA;AAAA,UAhGD,OAAM;AAAA,UAAa,SAAQ;AAAA,QAAA;qBAClC,MA8FiB;AAAA,YA9FjBD,EA8FiBF,EAAAI,CAAA,GAAA;AAAA,cA7FP,MAAMpB,EAAA;AAAA,sDAAAA,EAAI,QAAAqB;AAAA,cAClB,OAAM;AAAA,cACL,cAAY;AAAA,cACZ,cAAY;AAAA,cACZ,gBAAcL,EAAA/B,CAAA,EAAC,gBAAA;AAAA,YAAA;cAEL,QACT,MAWM;AAAA,gBAXNqC,EAWM,OAXNC,IAWM;AAAA,kBAVJL,EASWF,EAAAQ,CAAA,GAAA;AAAA,gCARA3B,EAAA;AAAA,kEAAAA,EAAK,QAAAwB;AAAA,oBACd,OAAM;AAAA,oBACN,MAAK;AAAA,oBACL,WAAA;AAAA,oBACC,aAAaL,EAAA/B,CAAA,EAAC,eAAA;AAAA,oBACd,cAAY+B,EAAA/B,CAAA,EAAC,eAAA;AAAA,kBAAA;oBAEH,UAAO,MAAmC;AAAA,sBAAnCiC,EAAmCF,EAAAS,CAAA,GAAA;AAAA,wBAA1B,MAAK;AAAA,wBAAS,MAAK;AAAA,sBAAA;;;;;gBAK1CjC,EAAA,cADRkC,EASS,UAAA;AAAA;kBAPP,MAAK;AAAA,kBACL,OAAKC,EAAA,CAAC,mBAAiB,EAAA,cACC7B,EAAA,UAAQ,MAAA,CAAA,CAAA;AAAA,kBAC/B,gCAAOW,EAAM,KAAA;AAAA,gBAAA;kBAEdS,EAAiCF,EAAAS,CAAA,GAAA;AAAA,oBAAxB,MAAK;AAAA,oBAAO,MAAK;AAAA,kBAAA;kBAAOG,EAAA,QAC9BZ,EAAA/B,CAAA,EAAC,YAAA,CAAA,GAAA,CAAA;AAAA,gBAAA;gBAGaM,EAAA,SAAWF,EAAA,MAAK,WAAM,UAAzC0B,EAAyFC,EAAAa,CAAA,GAAA;AAAA;kBAAxC,OAAM;AAAA,kBAAuB,MAAM;AAAA,gBAAA,MAGvExC,EAAA,MAAK,WAAM,UADxB0B,EAIEC,EAAAc,CAAA,GAAA;AAAA;kBAFC,OAAOlC,EAAA,UAAC,KAAUoB,EAAA/B,CAAA,oBAAoB+B,EAAA/B,CAAA,EAAC,qBAAA;AAAA,kBACvC,aAAaW,EAAA,UAAC,KAAUoB,EAAA/B,CAAA,yBAAyB;AAAA,gBAAA,+CAGpDyC,EAgCK,MAAA;AAAA;kBAhCM,OAAKC,EAAA,CAAC,oBAAkB,EAAA,cAAyBpC,EAAA,OAAO,CAAA;AAAA,gBAAA;0BACjEmC,EA8BKK,GAAA,MAAAC,EA9Bc3C,EAAA,OAAI,CAAZ4C,YAAXP,EA8BK,MAAA;AAAA,oBA9BqB,KAAKO,EAAK;AAAA,kBAAA;oBAClCX,EA4BS,UAAA;AAAA,sBA3BP,MAAK;AAAA,sBACL,UAAM,iBAAe,EAAA,cACGxB,YAAamC,EAAK,GAAA,CAAE,CAAA;AAAA,sBAC3C,SAAK,CAAAZ,MAAEZ,EAAOwB,EAAK,EAAE;AAAA,oBAAA;sBAEtBf,EAKEF,EAAAkB,CAAA,GAAA;AAAA,wBAJA,OAAM;AAAA,wBACL,KAAKD,EAAK,SAAS;AAAA,wBACnB,MAAMA,EAAK;AAAA,wBACZ,MAAK;AAAA,sBAAA;sBAGPX,EAcO,QAdPa,IAcO;AAAA,wBAbLb,EAAwD,QAAxDc,IAAwDC,EAAnBJ,EAAK,IAAI,GAAA,CAAA;AAAA,wBAE9CX,EAGO,QAHPgB,IAGO;AAAA,0BAFOL,EAAK,QAAjBM,EAAA,GAAAb,EAAkD,QAAAc,IAAAH,EAAxBJ,EAAK,KAAK,IAAI,GAAA,CAAA;0BACxCX,EAAuE,QAAA,MAAAe,EAA9DrB,EAAA/B,CAAA,EAAC,mBAAA,EAAA,OAA6BgD,EAAK,eAAA,CAAc,CAAA,GAAA,CAAA;AAAA,wBAAA;wBAG5DX,EAKO,QALPmB,IAKO;AAAA,0BAJWR,EAAK,aAArBlB,EAEWC,EAAA0B,CAAA,GAAA;AAAA;4BAFgB,KAAA;AAAA,4BAAI,MAAK;AAAA,4BAAK,MAAK;AAAA,0BAAA;uCAC5C,MAA2B;AAAA,kCAAxB1B,EAAA/B,CAAA,EAAC,kBAAA,CAAA,GAAA,CAAA;AAAA,4BAAA;;sCAEN8B,EAAuEC,EAAA0B,CAAA,GAAA;AAAA;4BAAtD,KAAA;AAAA,4BAAI,MAAK;AAAA,0BAAA;uCAAK,MAA6B;AAAA,kCAA1B1B,EAAA/B,CAAA,EAAC,oBAAA,CAAA,GAAA,CAAA;AAAA,4BAAA;;;;;;;;;cAQpC,QAAM0D,EACf,CASE,EAViB,QAAAC,GAAQ,MAAAC,QAAI;AAAA,gBAEvB/C,EAAA,UAAQ,aADhBiB,EASE+B,IAAA;AAAA,kBAPC,IAAIhD,EAAA,UAAQ,QAAA,OAAoBA,EAAA;AAAA,kBAChC,KAAK,OAAOA,EAAA,KAAQ;AAAA,kBACpB,QAAA8C;AAAA,kBACA,QAAMC;AAAA,kBACN,SAAO1C;AAAA,kBACP,WAASS;AAAA,kBACT,WAASE;AAAA,gBAAA;;cAIH,SACT,MAGE;AAAA,gBAHFI,EAGEF,EAAAc,CAAA,GAAA;AAAA,kBAFC,OAAOd,EAAA/B,CAAA,EAAC,eAAA;AAAA,kBACR,aAAaO,EAAA,QAAYwB,EAAA/B,CAAA,0BAA0B;AAAA,gBAAA;;;;;;;;;;;;"}
1
+ {"version":3,"file":"AuthorsPage-Catpwado.js","sources":["../src/AuthorsPage.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, ref, watch } from 'vue'\nimport { useRoute, useRouter, type LocationQueryRaw } from 'vue-router'\nimport {\n toast,\n WxAvatar,\n WxBadge,\n WxCard,\n WxEmpty,\n WxIcon,\n WxInput,\n WxListDetail,\n WxSkeleton,\n} from '@webx-ui/core'\nimport { useAdmin, useErrorText, useTranslate, WxListScreen } from '@webx-ui/module-admin'\nimport AuthorPane from './AuthorPane.vue'\nimport { createAuthorsApi, type AuthorRow } from './authors'\nimport { useBlogMessages } from './i18n'\n\n/**\n * The authors: every profile on the left, the one being written on the right (§19.5 of the blog\n * spec) — the shape of the team, without its order and its bin: the people who write one site are\n * a short list, and a profile that goes takes nothing with it but a name on a page.\n *\n * The search and the open profile live in the address, so a link to an author — the one the panel\n * on the site and the article editor make — is a link to them in their list.\n */\nwithDefaults(defineProps<{ base?: string }>(), { base: '/blog' })\n\nconst context = useAdmin()\nconst api = createAuthorsApi(context)\nconst route = useRoute()\nconst router = useRouter()\nuseBlogMessages()\n\nconst t = useTranslate('webx-blog')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\nconst rows = ref<AuthorRow[]>([])\nconst loading = ref(true)\n\nconst canManage = computed(() => context.can('blog.authors.manage'))\n\nconst title = computed(\n () =>\n context.state.manifest?.modules.find((module) => module.id === 'authors')?.title ??\n t('module.authors'),\n)\n\nconst q = computed(() => (typeof route.query.q === 'string' ? route.query.q : ''))\n\n/* What is typed, ahead of the address: the address follows it after a pause. */\nconst typed = ref(q.value)\n\n/** The open profile: a number, `'new'` for one not yet saved, or nothing. */\nconst selected = computed<number | 'new' | null>(() => {\n const raw = route.query.author\n\n if (raw === 'new') return canManage.value ? 'new' : null\n\n return typeof raw === 'string' && raw !== '' ? Number(raw) || null : null\n})\n\n/* Open is \"a profile is chosen\", so the address, the highlight and the drawer always agree. */\nconst open = computed({\n get: () => selected.value !== null,\n set: (value) => {\n if (!value) close()\n },\n})\n\nasync function load(): Promise<void> {\n loading.value = true\n\n try {\n rows.value = await api.list(q.value)\n } catch (error) {\n toast.danger(message(error))\n } finally {\n loading.value = false\n }\n}\n\nwatch(q, () => void load())\nvoid load()\n\nlet pause: ReturnType<typeof setTimeout> | undefined\n\nwatch(typed, (value) => {\n clearTimeout(pause)\n pause = setTimeout(() => {\n void router.replace({ query: { ...route.query, q: value.trim() === '' ? undefined : value } })\n }, 300)\n})\n\nonBeforeUnmount(() => clearTimeout(pause))\n\n/* `replace` and not `push`: going through the authors is not a step of history each. */\nfunction choose(id: number | 'new'): void {\n const query: LocationQueryRaw = { ...route.query, author: String(id) }\n\n // The account the article editor asked a profile for belongs to that one new profile only.\n if (id !== 'new') delete query.account\n void router.replace({ query })\n}\n\nfunction close(): void {\n const query = { ...route.query }\n\n delete query.author\n delete query.account\n void router.replace({ query })\n}\n\n/** A new profile was saved: it is a record now, and the list has it. */\nfunction created(author: AuthorRow): void {\n choose(author.id)\n void load()\n}\n\nfunction removed(id: number): void {\n if (selected.value === id) close()\n\n void load()\n}\n</script>\n\n<template>\n <wx-list-screen :title=\"title\" :card=\"false\">\n <wx-card class=\"wx-authors\" padding=\"none\">\n <wx-list-detail\n v-model:open=\"open\"\n class=\"wx-authors__panes\"\n :list-width=\"360\"\n :detail-min=\"460\"\n :detail-label=\"t('module.authors')\"\n >\n <template #list>\n <div class=\"wx-authors__bar\">\n <wx-input\n v-model=\"typed\"\n class=\"wx-authors__search\"\n size=\"sm\"\n clearable\n :placeholder=\"t('author.search')\"\n :aria-label=\"t('author.search')\"\n >\n <template #prefix><wx-icon name=\"search\" size=\"sm\" /></template>\n </wx-input>\n </div>\n\n <button\n v-if=\"canManage\"\n type=\"button\"\n class=\"wx-authors__new\"\n :class=\"{ 'is-current': selected === 'new' }\"\n @click=\"choose('new')\"\n >\n <wx-icon name=\"plus\" size=\"sm\" />\n {{ t('author.new') }}\n </button>\n\n <wx-skeleton v-if=\"loading && rows.length === 0\" class=\"wx-authors__loading\" :rows=\"4\" />\n\n <wx-empty\n v-else-if=\"rows.length === 0\"\n :title=\"q === '' ? t('author.empty') : t('author.empty-search')\"\n :description=\"q === '' ? t('author.empty-help') : undefined\"\n />\n\n <ul v-else class=\"wx-authors__list\" :class=\"{ 'is-loading': loading }\">\n <li v-for=\"item in rows\" :key=\"item.id\">\n <button\n type=\"button\"\n class=\"wx-author-row\"\n :class=\"{ 'is-current': selected === item.id }\"\n @click=\"choose(item.id)\"\n >\n <wx-avatar\n class=\"wx-author-row__photo\"\n :src=\"item.photo ?? undefined\"\n :name=\"item.name\"\n size=\"md\"\n />\n\n <span class=\"wx-author-row__body\">\n <span class=\"wx-author-row__name\">{{ item.name }}</span>\n\n <span class=\"wx-author-row__meta\">\n <span v-if=\"item.user\">{{ item.user.name }}</span>\n <span>{{ t('author.articles', { count: item.articles_count }) }}</span>\n </span>\n\n <span class=\"wx-author-row__facts\">\n <wx-badge v-if=\"item.open\" dot size=\"sm\" type=\"success\">\n {{ t('author.page-open') }}\n </wx-badge>\n <wx-badge v-else dot size=\"sm\">{{ t('author.page-closed') }}</wx-badge>\n </span>\n </span>\n </button>\n </li>\n </ul>\n </template>\n\n <template #detail=\"{ inline, back }\">\n <author-pane\n v-if=\"selected !== null\"\n :id=\"selected === 'new' ? null : selected\"\n :key=\"String(selected)\"\n :inline=\"inline\"\n @back=\"back\"\n @saved=\"load\"\n @created=\"created\"\n @removed=\"removed\"\n />\n </template>\n\n <template #empty>\n <wx-empty\n :title=\"t('author.choose')\"\n :description=\"canManage ? t('author.choose-help') : undefined\"\n />\n </template>\n </wx-list-detail>\n </wx-card>\n </wx-list-screen>\n</template>\n\n<style>\n/*\n * What scrolls here is the page, as on every other list of the panel: `clip`, not `hidden`, which\n * would make the card a scroll container and pin everything sticky inside it (CLAUDE.md §4).\n */\n.wx-authors > .wx-card__body {\n display: flex;\n flex-direction: column;\n overflow: clip;\n border-radius: inherit;\n}\n</style>\n\n<style scoped>\n.wx-authors__bar {\n display: flex;\n gap: var(--wx-space-8);\n padding: var(--wx-space-12) var(--wx-space-12) var(--wx-space-8);\n}\n\n.wx-authors__search {\n flex: 1 1 180px;\n min-width: 0;\n}\n\n.wx-authors__new {\n display: flex;\n align-items: center;\n gap: var(--wx-space-8);\n width: calc(100% - 2 * var(--wx-space-8));\n margin: 0 var(--wx-space-8) var(--wx-space-4);\n padding: var(--wx-space-8);\n border: 1px dashed var(--wx-border-default);\n border-radius: var(--wx-radius-sm);\n background: none;\n font: inherit;\n color: var(--wx-color-primary);\n text-align: start;\n cursor: pointer;\n}\n\n.wx-authors__new:hover,\n.wx-authors__new.is-current {\n background: var(--wx-bg-subtle);\n}\n\n.wx-authors__loading {\n padding: var(--wx-space-12);\n}\n\n.wx-authors__list {\n margin: 0;\n padding: 0 var(--wx-space-8) var(--wx-space-8);\n list-style: none;\n}\n\n.wx-authors__list.is-loading {\n opacity: 0.6;\n}\n\n.wx-author-row {\n display: flex;\n align-items: flex-start;\n gap: var(--wx-space-10);\n width: 100%;\n min-width: 0;\n padding: var(--wx-space-8) var(--wx-space-4);\n border: 0;\n border-radius: var(--wx-radius-sm);\n background: none;\n font: inherit;\n color: inherit;\n text-align: start;\n cursor: pointer;\n}\n\n.wx-author-row:hover,\n.wx-author-row.is-current {\n background: var(--wx-bg-subtle);\n}\n\n.wx-author-row__photo {\n flex: none;\n}\n\n.wx-author-row__body {\n display: flex;\n flex: 1 1 auto;\n flex-direction: column;\n gap: var(--wx-space-2);\n min-width: 0;\n}\n\n.wx-author-row__name {\n min-width: 0;\n overflow: hidden;\n font-weight: var(--wx-font-weight-medium);\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.wx-author-row.is-current .wx-author-row__name {\n color: var(--wx-color-primary);\n}\n\n.wx-author-row__meta {\n display: flex;\n flex-wrap: wrap;\n gap: var(--wx-space-2) var(--wx-space-8);\n color: var(--wx-text-muted);\n font-size: var(--wx-font-size-sm);\n}\n\n.wx-author-row__facts {\n display: flex;\n margin-top: var(--wx-space-2);\n}\n</style>\n"],"names":["context","useAdmin","api","createAuthorsApi","route","useRoute","router","useRouter","useBlogMessages","t","useTranslate","message","useErrorText","rows","ref","loading","canManage","computed","title","module","q","typed","selected","raw","open","value","close","load","error","toast","watch","pause","onBeforeUnmount","choose","id","query","created","author","removed","_createBlock","_unref","WxListScreen","_createVNode","WxCard","WxListDetail","$event","_createElementVNode","_hoisted_1","WxInput","WxIcon","_createElementBlock","_normalizeClass","_createTextVNode","WxSkeleton","WxEmpty","_Fragment","_renderList","item","WxAvatar","_hoisted_3","_hoisted_4","_toDisplayString","_hoisted_5","_openBlock","_hoisted_6","_hoisted_7","WxBadge","_withCtx","inline","back","AuthorPane"],"mappings":"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"}
@@ -2,7 +2,7 @@ import { defineComponent as H, useId as de, ref as S, useTemplateRef as ge, onMo
2
2
  import { useRoute as he, useRouter as _e } from "vue-router";
3
3
  import { useTranslate as I, useAdmin as ke, useErrorText as Te, rowMenuWidth as qe, WxListScreen as We, WxRowMenu as j } from "@webx-ui/module-admin";
4
4
  import { useModal as Se, WxDialog as Be, WxFormItem as Me, WxInput as Re, WxSpace as $e, WxButton as D, createModal as A, confirm as G, toast as x, WxTable as Ie, WxTooltip as Ve, WxText as T, WxActionBar as Ce, WxIcon as ze } from "@webx-ui/core";
5
- import { u as J, c as Ne, b as Ae, T as De, a as Pe } from "./index-ChbP89HC.js";
5
+ import { u as J, c as Ne, b as Ae, T as De, a as Pe } from "./index-C_gXxP0l.js";
6
6
  const Ue = ["id"], Ee = /* @__PURE__ */ H({
7
7
  __name: "TagRenameDialog",
8
8
  props: {
@@ -460,4 +460,4 @@ const Ue = ["id"], Ee = /* @__PURE__ */ H({
460
460
  export {
461
461
  Je as default
462
462
  };
463
- //# sourceMappingURL=TagsPage-BnLVnx5a.js.map
463
+ //# sourceMappingURL=TagsPage-BxvjxFcc.js.map