@webx-ui/module-blog 0.8.3 → 0.8.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{ArticleEditorPage-DViFCNvm.js → ArticleEditorPage-Bkb5YrLy.js} +2 -2
- package/dist/{ArticleEditorPage-DViFCNvm.js.map → ArticleEditorPage-Bkb5YrLy.js.map} +1 -1
- package/dist/{ArticlesPage-CI-Nwhpy.js → ArticlesPage-BnHi8gF0.js} +173 -154
- package/dist/ArticlesPage-BnHi8gF0.js.map +1 -0
- package/dist/{AuthorsPage-BN6sRqyE.js → AuthorsPage-DSLsX44M.js} +2 -2
- package/dist/{AuthorsPage-BN6sRqyE.js.map → AuthorsPage-DSLsX44M.js.map} +1 -1
- package/dist/{TagsPage-rFfpCqPs.js → TagsPage-BnLVnx5a.js} +2 -2
- package/dist/{TagsPage-rFfpCqPs.js.map → TagsPage-BnLVnx5a.js.map} +1 -1
- package/dist/{index-DqN5h0Ej.js → index-ChbP89HC.js} +418 -417
- package/dist/index-ChbP89HC.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/style.css +1 -1
- package/package.json +5 -5
- package/dist/ArticlesPage-CI-Nwhpy.js.map +0 -1
- package/dist/index-DqN5h0Ej.js.map +0 -1
|
@@ -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-
|
|
6
|
+
import { c as je, u as qe, p as Ke, a as Qe } from "./index-ChbP89HC.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-
|
|
368
|
+
//# sourceMappingURL=ArticleEditorPage-Bkb5YrLy.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ArticleEditorPage-DViFCNvm.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-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":";;;;;;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;;;;;;;;;;"}
|