@webx-ui/module-press 0.2.6 → 0.2.7
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/{PressPage-DZI3u3j4.js → PressPage-FeCExfdb.js} +2 -2
- package/dist/{PressPage-DZI3u3j4.js.map → PressPage-FeCExfdb.js.map} +1 -1
- package/dist/{index-Uviomho-.js → index-Dh9AR27x.js} +5 -3
- package/dist/index-Dh9AR27x.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/style.css +1 -1
- package/package.json +4 -4
- package/dist/index-Uviomho-.js.map +0 -1
|
@@ -2,7 +2,7 @@ import { defineComponent as ee, ref as B, computed as u, watch as L, onBeforeUnm
|
|
|
2
2
|
import { useRoute as oe, useRouter as se } from "vue-router";
|
|
3
3
|
import { WxCard as ne, WxListDetail as re, WxEmpty as O, WxInput as ue, WxIcon as I, WxSkeleton as ie, WxSortableList as ce, WxAvatar as de, WxTooltip as j, WxBadge as F, WxAlert as ve, toast as x, confirm as pe } from "@webx-ui/core";
|
|
4
4
|
import { useAdmin as _e, useTranslate as me, useErrorText as we, useItemOrder as fe, WxListScreen as ye, WxRowMenu as he } from "@webx-ui/module-admin";
|
|
5
|
-
import { c as xe, u as be, P as ge, O as ke, _ as qe } from "./index-
|
|
5
|
+
import { c as xe, u as be, P as ge, O as ke, _ as qe } from "./index-Dh9AR27x.js";
|
|
6
6
|
const We = { class: "wx-press__bar" }, Ce = {
|
|
7
7
|
key: 0,
|
|
8
8
|
class: "wx-outlet-row__logo"
|
|
@@ -325,4 +325,4 @@ const We = { class: "wx-press__bar" }, Ce = {
|
|
|
325
325
|
export {
|
|
326
326
|
Le as default
|
|
327
327
|
};
|
|
328
|
-
//# sourceMappingURL=PressPage-
|
|
328
|
+
//# sourceMappingURL=PressPage-FeCExfdb.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PressPage-DZI3u3j4.js","sources":["../src/PressPage.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, ref, watch } from 'vue'\nimport { useRoute, useRouter, type LocationQueryRaw } from 'vue-router'\nimport {\n confirm,\n toast,\n WxAlert,\n WxAvatar,\n WxBadge,\n WxCard,\n WxEmpty,\n WxIcon,\n WxInput,\n WxListDetail,\n WxSkeleton,\n WxSortableList,\n WxTooltip,\n type TabItem,\n type TabValue,\n} from '@webx-ui/core'\nimport {\n useAdmin,\n useErrorText,\n useItemOrder,\n useTranslate,\n WxListScreen,\n WxRowMenu,\n type RowAction,\n} from '@webx-ui/module-admin'\nimport { createPressApi, PRESS_API } from './api'\nimport { usePressMessages } from './i18n'\nimport OutletPane from './OutletPane.vue'\nimport type { OutletRow, OutletSummary } from './types'\n\n/**\n * The outlets: all of them on the left, the one being written on the right (§4.9, decision 15).\n *\n * The shape of the reviews, for the same reason: an outlet is typed in from a clipping in a\n * minute, and the next one is usually under it. On a phone the form is the whole screen, in the\n * drawer `WxListDetail` raises, and draws its own way back.\n *\n * One order, the general one (decision 6): a drag moves it; a search or the bin shows a\n * selection, so the grips go away and the line under the list says why. The order of the\n * articles is not here — it is the order of the rows in the outlet's form.\n *\n * Everything the screen is looking at lives in the address — the view, the search and the open\n * outlet — so a link to an outlet is a link to it in its list.\n */\nwithDefaults(defineProps<{ base?: string }>(), { base: '/press' })\n\nconst context = useAdmin()\nconst api = createPressApi(context)\nconst route = useRoute()\nconst router = useRouter()\nusePressMessages()\n\nconst t = useTranslate('webx-press')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\nconst rows = ref<OutletRow[]>([])\nconst loading = ref(true)\n\nconst canManage = computed(() => context.can('press.manage'))\n\nconst title = computed(\n () =>\n context.state.manifest?.modules.find((module) => module.id === 'press')?.title ??\n t('module.press'),\n)\n\nconst view = computed<TabValue>({\n get: () => (route.query.view === 'trashed' ? 'trashed' : ''),\n set: (value) => {\n const query: LocationQueryRaw = {\n ...route.query,\n view: value === '' ? undefined : String(value),\n }\n\n // An outlet from the list is not an outlet from the bin: the form closes with the view.\n delete query.outlet\n void router.replace({ query })\n },\n})\n\nconst inBin = computed(() => view.value === 'trashed')\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 outlet: a number, `'new'` for one not yet saved, or nothing. */\nconst selected = computed<number | 'new' | null>(() => {\n const raw = route.query.outlet\n\n if (raw === 'new') return canManage.value ? 'new' : null\n\n return typeof raw === 'string' && raw !== '' ? Number(raw) || null : null\n})\n\n/*\n * Open is \"an outlet is chosen\", and closing is un-choosing it — so the address, the list's\n * highlight and the drawer on a phone can never disagree about it.\n */\nconst open = computed({\n get: () => selected.value !== null,\n set: (value) => {\n if (!value) close()\n },\n})\n\nconst views = computed<TabItem[]>(() => [\n { value: '', label: t('outlet.all') },\n { value: 'trashed', label: t('outlet.trashed'), icon: 'trash' },\n])\n\nconst order = useItemOrder(PRESS_API, () => ({ q: q.value, filtered: inBin.value }))\n\n/* Dragged only by somebody who may write, and only in a list that is the whole order. */\nconst sortable = computed(() => canManage.value && order.sortable.value)\n\nconst emptyText = computed(() => {\n if (q.value !== '') return t('outlet.empty-search')\n\n return inBin.value ? t('outlet.empty-bin') : t('outlet.empty')\n})\n\nasync function load(): Promise<void> {\n loading.value = true\n\n try {\n const answer = await api.list({ search: q.value, trashed: inBin.value })\n\n rows.value = answer.data\n } catch (error) {\n toast.danger(message(error))\n } finally {\n loading.value = false\n }\n}\n\nwatch(\n () => [view.value, q.value] as const,\n () => void load(),\n)\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 twenty outlets is not twenty steps of history. */\nfunction choose(id: number | 'new'): void {\n void router.replace({ query: { ...route.query, outlet: String(id) } })\n}\n\nfunction close(): void {\n const query = { ...route.query }\n\n delete query.outlet\n void router.replace({ query })\n}\n\n/** A new outlet was saved: it is a record now, and the list has it. */\nfunction created(outlet: OutletSummary): void {\n choose(outlet.id)\n void load()\n}\n\n/**\n * The order on screen, written as a whole. The list is already in its new order\n * (`WxSortableList` reorders the model before it says so), so a failure has to put it back\n * rather than leave the screen disagreeing with the database.\n */\nasync function reorder(): Promise<void> {\n try {\n await order.move(rows.value.map((row) => row.id))\n } catch (error) {\n toast.danger(message(error, t('outlet.reorder-failed')))\n await load()\n }\n}\n\nfunction actionsFor(row: OutletRow): RowAction[] {\n if (!canManage.value) return []\n\n if (inBin.value) {\n return [\n {\n key: 'restore',\n icon: 'refresh',\n label: t('outlet.restore'),\n run: () => void restore(row),\n },\n ]\n }\n\n return [\n {\n key: 'delete',\n icon: 'trash',\n label: t('outlet.delete'),\n danger: true,\n run: () => void remove(row),\n },\n ]\n}\n\nasync function remove(row: OutletRow): Promise<void> {\n const agreed = await confirm({\n title: t('outlet.delete-title', { name: row.title }),\n message: t('outlet.delete-text'),\n confirmText: t('outlet.delete'),\n cancelText: t('outlet.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n try {\n await api.remove(row.id)\n toast.success(t('outlet.deleted'))\n removed(row.id)\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\n/** Gone from the list; and if it was the open one, the form goes with it. */\nfunction removed(id: number): void {\n if (selected.value === id) close()\n\n void load()\n}\n\nasync function restore(row: OutletRow): Promise<void> {\n try {\n await api.restore(row.id)\n toast.success(t('outlet.restored'))\n } catch (error) {\n toast.danger(message(error))\n } finally {\n await load()\n }\n}\n\n/* Published and yet shown in no language: the one state the list has to point out. */\nfunction unseen(row: OutletRow): boolean {\n return row.published && row.locales.length === 0\n}\n</script>\n\n<template>\n <wx-list-screen v-model:view=\"view\" :title=\"title\" :views=\"views\" :card=\"false\">\n <wx-card class=\"wx-press\" padding=\"none\">\n <wx-list-detail\n v-model:open=\"open\"\n class=\"wx-press__panes\"\n :list-width=\"360\"\n :detail-min=\"520\"\n :detail-label=\"t('module.press')\"\n >\n <template #list>\n <div class=\"wx-press__bar\">\n <wx-input\n v-model=\"typed\"\n class=\"wx-press__search\"\n size=\"sm\"\n clearable\n :placeholder=\"t('outlet.search')\"\n :aria-label=\"t('outlet.search')\"\n >\n <template #prefix><wx-icon name=\"search\" size=\"sm\" /></template>\n </wx-input>\n </div>\n\n <!--\n A line of the list rather than a button in the head: the new outlet is written where\n it will stand, beside the ones it joins — and the form opens the way any other does.\n -->\n <button\n v-if=\"canManage && !inBin\"\n type=\"button\"\n class=\"wx-press__new\"\n :class=\"{ 'is-current': selected === 'new' }\"\n @click=\"choose('new')\"\n >\n <wx-icon name=\"plus\" size=\"sm\" />\n {{ t('outlet.new') }}\n </button>\n\n <wx-skeleton v-if=\"loading && rows.length === 0\" class=\"wx-press__loading\" :rows=\"5\" />\n\n <wx-empty\n v-else-if=\"rows.length === 0\"\n :title=\"emptyText\"\n :description=\"!inBin && q === '' ? t('outlet.empty-help') : undefined\"\n />\n\n <template v-else>\n <!-- A grip and not the whole row: the row opens an outlet, and a row that both opens\n and drags is one where one of the two happens by accident. -->\n <wx-sortable-list\n v-model=\"rows\"\n class=\"wx-press__list\"\n hoverable\n :class=\"{ 'is-loading': loading }\"\n plain\n item-key=\"id\"\n :handle=\"sortable ? 'grip' : 'row'\"\n :item-label=\"(item: OutletRow) => item.title\"\n :disabled=\"!sortable\"\n @move=\"reorder\"\n >\n <template #default=\"{ item }\">\n <component\n :is=\"inBin ? 'div' : 'button'\"\n :type=\"inBin ? undefined : 'button'\"\n class=\"wx-outlet-row\"\n :class=\"{ 'is-current': selected === item.id }\"\n @click=\"inBin ? undefined : choose(item.id)\"\n >\n <!-- A logo is wide as often as not: in a square it shrinks to a sliver, so it gets a\n box of its own shape, and only an outlet without one gets initials. -->\n <span v-if=\"item.logo?.thumb\" class=\"wx-outlet-row__logo\">\n <img :src=\"item.logo.thumb\" alt=\"\" />\n </span>\n <wx-avatar\n v-else\n class=\"wx-outlet-row__initials\"\n shape=\"square\"\n :name=\"item.title\"\n size=\"md\"\n />\n\n <span class=\"wx-outlet-row__body\">\n <span class=\"wx-outlet-row__head\">\n <span class=\"wx-outlet-row__title\">{{ item.title }}</span>\n <wx-tooltip v-if=\"item.featured\" :content=\"t('outlet.featured')\">\n <wx-icon\n class=\"wx-outlet-row__featured\"\n name=\"star\"\n size=\"sm\"\n :aria-label=\"t('outlet.featured')\"\n />\n </wx-tooltip>\n </span>\n\n <span class=\"wx-outlet-row__facts\">\n <wx-badge v-if=\"!item.published\" dot size=\"sm\">\n {{ t('outlet.not-published') }}\n </wx-badge>\n <wx-tooltip v-else-if=\"unseen(item)\" :content=\"t('outlet.visible-nowhere')\">\n <wx-badge type=\"warning\" dot size=\"sm\">\n {{ t('outlet.not-published') }}\n </wx-badge>\n </wx-tooltip>\n\n <span class=\"wx-outlet-row__count\">\n {{\n item.articles_count > 0\n ? t('outlet.articles-count', { count: item.articles_count })\n : t('outlet.no-articles')\n }}\n </span>\n\n <span\n v-if=\"item.locales.length > 0\"\n class=\"wx-outlet-row__locales\"\n :aria-label=\"t('outlet.seen-in', { locales: item.locales.join(', ') })\"\n >\n {{ item.locales.join(' · ') }}\n </span>\n </span>\n </span>\n </component>\n </template>\n\n <template #actions=\"{ item }\">\n <wx-row-menu :actions=\"actionsFor(item)\" :label=\"item.title\" />\n </template>\n </wx-sortable-list>\n\n <!-- What a drag does here, or why there is nothing to drag. -->\n <wx-alert\n v-if=\"canManage && !inBin\"\n class=\"wx-press__note\"\n type=\"info\"\n :description=\"order.hint.value\"\n />\n </template>\n </template>\n\n <template #detail=\"{ inline, back }\">\n <outlet-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('outlet.choose')\"\n :description=\"canManage ? t('outlet.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, the way it does on every other list of the panel. The card's\n * corners are the screen's, and the panes paint their own background to the edge: `clip`, not\n * `hidden`, which would make this a scroll container and pin everything sticky inside to a box\n * that never moves (CLAUDE.md §4).\n */\n.wx-press > .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-press__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-press__search {\n flex: 1 1 180px;\n min-width: 0;\n}\n\n.wx-press__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-press__new:hover,\n.wx-press__new.is-current {\n background: var(--wx-bg-subtle);\n}\n\n.wx-press__loading {\n padding: var(--wx-space-12);\n}\n\n.wx-press__list {\n padding-inline: var(--wx-space-8);\n}\n\n.wx-press__list.is-loading {\n opacity: 0.6;\n}\n\n.wx-press__list :deep(.wx-sortable-list__row) {\n gap: var(--wx-space-4);\n padding-inline: var(--wx-space-4);\n}\n\n.wx-press__note {\n margin: var(--wx-space-8);\n}\n\n.wx-outlet-row {\n display: flex;\n align-items: flex-start;\n gap: var(--wx-space-10);\n /* The row is the target: a short name should not leave half the line dead — and a button,\n unlike a link, is only as wide as what is in it until it is told otherwise. */\n flex: 1 1 auto;\n width: 100%;\n min-width: 0;\n padding-block: var(--wx-space-8);\n padding-inline: 0;\n border: 0;\n background: none;\n font: inherit;\n color: inherit;\n text-align: start;\n}\n\nbutton.wx-outlet-row {\n cursor: pointer;\n}\n\n.wx-outlet-row__initials {\n flex: none;\n}\n\n/* Twice as wide as the initials beside it, and the logo drawn whole inside: cropped, it loses\n its letters. */\n.wx-outlet-row__logo {\n display: flex;\n flex: none;\n align-items: center;\n justify-content: center;\n width: 64px;\n height: 32px;\n overflow: hidden;\n border-radius: var(--wx-radius-sm);\n background: var(--wx-bg-subtle);\n}\n\n.wx-outlet-row__logo img {\n display: block;\n max-width: 100%;\n max-height: 100%;\n object-fit: contain;\n}\n\n.wx-outlet-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-outlet-row__head {\n display: flex;\n align-items: center;\n gap: var(--wx-space-6);\n min-width: 0;\n}\n\n.wx-outlet-row__title {\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-outlet-row.is-current .wx-outlet-row__title {\n color: var(--wx-color-primary);\n}\n\n.wx-outlet-row__featured {\n flex: none;\n color: var(--wx-color-warning);\n}\n\n.wx-outlet-row__facts {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--wx-space-4) var(--wx-space-8);\n min-width: 0;\n color: var(--wx-text-muted);\n font-size: var(--wx-font-size-sm);\n}\n\n.wx-outlet-row__locales {\n font-family: var(--wx-font-family-mono);\n font-size: var(--wx-font-size-xs);\n text-transform: uppercase;\n}\n</style>\n"],"names":["context","useAdmin","api","createPressApi","route","useRoute","router","useRouter","usePressMessages","t","useTranslate","message","useErrorText","rows","ref","loading","canManage","computed","title","module","view","value","query","inBin","q","typed","selected","raw","open","close","views","order","useItemOrder","PRESS_API","sortable","emptyText","load","answer","error","toast","watch","pause","onBeforeUnmount","choose","id","created","outlet","reorder","row","actionsFor","restore","remove","confirm","removed","unseen","_createBlock","_unref","WxListScreen","$event","_createVNode","WxCard","WxListDetail","_createElementVNode","_hoisted_1","WxInput","WxIcon","_createElementBlock","_normalizeClass","_createTextVNode","WxSkeleton","WxEmpty","_Fragment","WxSortableList","item","_withCtx","_openBlock","_resolveDynamicComponent","_hoisted_2","WxAvatar","_hoisted_4","_hoisted_5","_hoisted_6","_toDisplayString","WxTooltip","_hoisted_7","WxBadge","_hoisted_8","_hoisted_9","WxRowMenu","WxAlert","inline","back","OutletPane"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PressPage-FeCExfdb.js","sources":["../src/PressPage.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, ref, watch } from 'vue'\nimport { useRoute, useRouter, type LocationQueryRaw } from 'vue-router'\nimport {\n confirm,\n toast,\n WxAlert,\n WxAvatar,\n WxBadge,\n WxCard,\n WxEmpty,\n WxIcon,\n WxInput,\n WxListDetail,\n WxSkeleton,\n WxSortableList,\n WxTooltip,\n type TabItem,\n type TabValue,\n} from '@webx-ui/core'\nimport {\n useAdmin,\n useErrorText,\n useItemOrder,\n useTranslate,\n WxListScreen,\n WxRowMenu,\n type RowAction,\n} from '@webx-ui/module-admin'\nimport { createPressApi, PRESS_API } from './api'\nimport { usePressMessages } from './i18n'\nimport OutletPane from './OutletPane.vue'\nimport type { OutletRow, OutletSummary } from './types'\n\n/**\n * The outlets: all of them on the left, the one being written on the right (§4.9, decision 15).\n *\n * The shape of the reviews, for the same reason: an outlet is typed in from a clipping in a\n * minute, and the next one is usually under it. On a phone the form is the whole screen, in the\n * drawer `WxListDetail` raises, and draws its own way back.\n *\n * One order, the general one (decision 6): a drag moves it; a search or the bin shows a\n * selection, so the grips go away and the line under the list says why. The order of the\n * articles is not here — it is the order of the rows in the outlet's form.\n *\n * Everything the screen is looking at lives in the address — the view, the search and the open\n * outlet — so a link to an outlet is a link to it in its list.\n */\nwithDefaults(defineProps<{ base?: string }>(), { base: '/press' })\n\nconst context = useAdmin()\nconst api = createPressApi(context)\nconst route = useRoute()\nconst router = useRouter()\nusePressMessages()\n\nconst t = useTranslate('webx-press')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\nconst rows = ref<OutletRow[]>([])\nconst loading = ref(true)\n\nconst canManage = computed(() => context.can('press.manage'))\n\nconst title = computed(\n () =>\n context.state.manifest?.modules.find((module) => module.id === 'press')?.title ??\n t('module.press'),\n)\n\nconst view = computed<TabValue>({\n get: () => (route.query.view === 'trashed' ? 'trashed' : ''),\n set: (value) => {\n const query: LocationQueryRaw = {\n ...route.query,\n view: value === '' ? undefined : String(value),\n }\n\n // An outlet from the list is not an outlet from the bin: the form closes with the view.\n delete query.outlet\n void router.replace({ query })\n },\n})\n\nconst inBin = computed(() => view.value === 'trashed')\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 outlet: a number, `'new'` for one not yet saved, or nothing. */\nconst selected = computed<number | 'new' | null>(() => {\n const raw = route.query.outlet\n\n if (raw === 'new') return canManage.value ? 'new' : null\n\n return typeof raw === 'string' && raw !== '' ? Number(raw) || null : null\n})\n\n/*\n * Open is \"an outlet is chosen\", and closing is un-choosing it — so the address, the list's\n * highlight and the drawer on a phone can never disagree about it.\n */\nconst open = computed({\n get: () => selected.value !== null,\n set: (value) => {\n if (!value) close()\n },\n})\n\nconst views = computed<TabItem[]>(() => [\n { value: '', label: t('outlet.all') },\n { value: 'trashed', label: t('outlet.trashed'), icon: 'trash' },\n])\n\nconst order = useItemOrder(PRESS_API, () => ({ q: q.value, filtered: inBin.value }))\n\n/* Dragged only by somebody who may write, and only in a list that is the whole order. */\nconst sortable = computed(() => canManage.value && order.sortable.value)\n\nconst emptyText = computed(() => {\n if (q.value !== '') return t('outlet.empty-search')\n\n return inBin.value ? t('outlet.empty-bin') : t('outlet.empty')\n})\n\nasync function load(): Promise<void> {\n loading.value = true\n\n try {\n const answer = await api.list({ search: q.value, trashed: inBin.value })\n\n rows.value = answer.data\n } catch (error) {\n toast.danger(message(error))\n } finally {\n loading.value = false\n }\n}\n\nwatch(\n () => [view.value, q.value] as const,\n () => void load(),\n)\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 twenty outlets is not twenty steps of history. */\nfunction choose(id: number | 'new'): void {\n void router.replace({ query: { ...route.query, outlet: String(id) } })\n}\n\nfunction close(): void {\n const query = { ...route.query }\n\n delete query.outlet\n void router.replace({ query })\n}\n\n/** A new outlet was saved: it is a record now, and the list has it. */\nfunction created(outlet: OutletSummary): void {\n choose(outlet.id)\n void load()\n}\n\n/**\n * The order on screen, written as a whole. The list is already in its new order\n * (`WxSortableList` reorders the model before it says so), so a failure has to put it back\n * rather than leave the screen disagreeing with the database.\n */\nasync function reorder(): Promise<void> {\n try {\n await order.move(rows.value.map((row) => row.id))\n } catch (error) {\n toast.danger(message(error, t('outlet.reorder-failed')))\n await load()\n }\n}\n\nfunction actionsFor(row: OutletRow): RowAction[] {\n if (!canManage.value) return []\n\n if (inBin.value) {\n return [\n {\n key: 'restore',\n icon: 'refresh',\n label: t('outlet.restore'),\n run: () => void restore(row),\n },\n ]\n }\n\n return [\n {\n key: 'delete',\n icon: 'trash',\n label: t('outlet.delete'),\n danger: true,\n run: () => void remove(row),\n },\n ]\n}\n\nasync function remove(row: OutletRow): Promise<void> {\n const agreed = await confirm({\n title: t('outlet.delete-title', { name: row.title }),\n message: t('outlet.delete-text'),\n confirmText: t('outlet.delete'),\n cancelText: t('outlet.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n try {\n await api.remove(row.id)\n toast.success(t('outlet.deleted'))\n removed(row.id)\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\n/** Gone from the list; and if it was the open one, the form goes with it. */\nfunction removed(id: number): void {\n if (selected.value === id) close()\n\n void load()\n}\n\nasync function restore(row: OutletRow): Promise<void> {\n try {\n await api.restore(row.id)\n toast.success(t('outlet.restored'))\n } catch (error) {\n toast.danger(message(error))\n } finally {\n await load()\n }\n}\n\n/* Published and yet shown in no language: the one state the list has to point out. */\nfunction unseen(row: OutletRow): boolean {\n return row.published && row.locales.length === 0\n}\n</script>\n\n<template>\n <wx-list-screen v-model:view=\"view\" :title=\"title\" :views=\"views\" :card=\"false\">\n <wx-card class=\"wx-press\" padding=\"none\">\n <wx-list-detail\n v-model:open=\"open\"\n class=\"wx-press__panes\"\n :list-width=\"360\"\n :detail-min=\"520\"\n :detail-label=\"t('module.press')\"\n >\n <template #list>\n <div class=\"wx-press__bar\">\n <wx-input\n v-model=\"typed\"\n class=\"wx-press__search\"\n size=\"sm\"\n clearable\n :placeholder=\"t('outlet.search')\"\n :aria-label=\"t('outlet.search')\"\n >\n <template #prefix><wx-icon name=\"search\" size=\"sm\" /></template>\n </wx-input>\n </div>\n\n <!--\n A line of the list rather than a button in the head: the new outlet is written where\n it will stand, beside the ones it joins — and the form opens the way any other does.\n -->\n <button\n v-if=\"canManage && !inBin\"\n type=\"button\"\n class=\"wx-press__new\"\n :class=\"{ 'is-current': selected === 'new' }\"\n @click=\"choose('new')\"\n >\n <wx-icon name=\"plus\" size=\"sm\" />\n {{ t('outlet.new') }}\n </button>\n\n <wx-skeleton v-if=\"loading && rows.length === 0\" class=\"wx-press__loading\" :rows=\"5\" />\n\n <wx-empty\n v-else-if=\"rows.length === 0\"\n :title=\"emptyText\"\n :description=\"!inBin && q === '' ? t('outlet.empty-help') : undefined\"\n />\n\n <template v-else>\n <!-- A grip and not the whole row: the row opens an outlet, and a row that both opens\n and drags is one where one of the two happens by accident. -->\n <wx-sortable-list\n v-model=\"rows\"\n class=\"wx-press__list\"\n hoverable\n :class=\"{ 'is-loading': loading }\"\n plain\n item-key=\"id\"\n :handle=\"sortable ? 'grip' : 'row'\"\n :item-label=\"(item: OutletRow) => item.title\"\n :disabled=\"!sortable\"\n @move=\"reorder\"\n >\n <template #default=\"{ item }\">\n <component\n :is=\"inBin ? 'div' : 'button'\"\n :type=\"inBin ? undefined : 'button'\"\n class=\"wx-outlet-row\"\n :class=\"{ 'is-current': selected === item.id }\"\n @click=\"inBin ? undefined : choose(item.id)\"\n >\n <!-- A logo is wide as often as not: in a square it shrinks to a sliver, so it gets a\n box of its own shape, and only an outlet without one gets initials. -->\n <span v-if=\"item.logo?.thumb\" class=\"wx-outlet-row__logo\">\n <img :src=\"item.logo.thumb\" alt=\"\" />\n </span>\n <wx-avatar\n v-else\n class=\"wx-outlet-row__initials\"\n shape=\"square\"\n :name=\"item.title\"\n size=\"md\"\n />\n\n <span class=\"wx-outlet-row__body\">\n <span class=\"wx-outlet-row__head\">\n <span class=\"wx-outlet-row__title\">{{ item.title }}</span>\n <wx-tooltip v-if=\"item.featured\" :content=\"t('outlet.featured')\">\n <wx-icon\n class=\"wx-outlet-row__featured\"\n name=\"star\"\n size=\"sm\"\n :aria-label=\"t('outlet.featured')\"\n />\n </wx-tooltip>\n </span>\n\n <span class=\"wx-outlet-row__facts\">\n <wx-badge v-if=\"!item.published\" dot size=\"sm\">\n {{ t('outlet.not-published') }}\n </wx-badge>\n <wx-tooltip v-else-if=\"unseen(item)\" :content=\"t('outlet.visible-nowhere')\">\n <wx-badge type=\"warning\" dot size=\"sm\">\n {{ t('outlet.not-published') }}\n </wx-badge>\n </wx-tooltip>\n\n <span class=\"wx-outlet-row__count\">\n {{\n item.articles_count > 0\n ? t('outlet.articles-count', { count: item.articles_count })\n : t('outlet.no-articles')\n }}\n </span>\n\n <span\n v-if=\"item.locales.length > 0\"\n class=\"wx-outlet-row__locales\"\n :aria-label=\"t('outlet.seen-in', { locales: item.locales.join(', ') })\"\n >\n {{ item.locales.join(' · ') }}\n </span>\n </span>\n </span>\n </component>\n </template>\n\n <template #actions=\"{ item }\">\n <wx-row-menu :actions=\"actionsFor(item)\" :label=\"item.title\" />\n </template>\n </wx-sortable-list>\n\n <!-- What a drag does here, or why there is nothing to drag. -->\n <wx-alert\n v-if=\"canManage && !inBin\"\n class=\"wx-press__note\"\n type=\"info\"\n :description=\"order.hint.value\"\n />\n </template>\n </template>\n\n <template #detail=\"{ inline, back }\">\n <outlet-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('outlet.choose')\"\n :description=\"canManage ? t('outlet.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, the way it does on every other list of the panel. The card's\n * corners are the screen's, and the panes paint their own background to the edge: `clip`, not\n * `hidden`, which would make this a scroll container and pin everything sticky inside to a box\n * that never moves (CLAUDE.md §4).\n */\n.wx-press > .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-press__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-press__search {\n flex: 1 1 180px;\n min-width: 0;\n}\n\n.wx-press__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-press__new:hover,\n.wx-press__new.is-current {\n background: var(--wx-bg-subtle);\n}\n\n.wx-press__loading {\n padding: var(--wx-space-12);\n}\n\n.wx-press__list {\n padding-inline: var(--wx-space-8);\n}\n\n.wx-press__list.is-loading {\n opacity: 0.6;\n}\n\n.wx-press__list :deep(.wx-sortable-list__row) {\n gap: var(--wx-space-4);\n padding-inline: var(--wx-space-4);\n}\n\n.wx-press__note {\n margin: var(--wx-space-8);\n}\n\n.wx-outlet-row {\n display: flex;\n align-items: flex-start;\n gap: var(--wx-space-10);\n /* The row is the target: a short name should not leave half the line dead — and a button,\n unlike a link, is only as wide as what is in it until it is told otherwise. */\n flex: 1 1 auto;\n width: 100%;\n min-width: 0;\n padding-block: var(--wx-space-8);\n padding-inline: 0;\n border: 0;\n background: none;\n font: inherit;\n color: inherit;\n text-align: start;\n}\n\nbutton.wx-outlet-row {\n cursor: pointer;\n}\n\n.wx-outlet-row__initials {\n flex: none;\n}\n\n/* Twice as wide as the initials beside it, and the logo drawn whole inside: cropped, it loses\n its letters. */\n.wx-outlet-row__logo {\n display: flex;\n flex: none;\n align-items: center;\n justify-content: center;\n width: 64px;\n height: 32px;\n overflow: hidden;\n border-radius: var(--wx-radius-sm);\n background: var(--wx-bg-subtle);\n}\n\n.wx-outlet-row__logo img {\n display: block;\n max-width: 100%;\n max-height: 100%;\n object-fit: contain;\n}\n\n.wx-outlet-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-outlet-row__head {\n display: flex;\n align-items: center;\n gap: var(--wx-space-6);\n min-width: 0;\n}\n\n.wx-outlet-row__title {\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-outlet-row.is-current .wx-outlet-row__title {\n color: var(--wx-color-primary);\n}\n\n.wx-outlet-row__featured {\n flex: none;\n color: var(--wx-color-warning);\n}\n\n.wx-outlet-row__facts {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--wx-space-4) var(--wx-space-8);\n min-width: 0;\n color: var(--wx-text-muted);\n font-size: var(--wx-font-size-sm);\n}\n\n.wx-outlet-row__locales {\n font-family: var(--wx-font-family-mono);\n font-size: var(--wx-font-size-xs);\n text-transform: uppercase;\n}\n</style>\n"],"names":["context","useAdmin","api","createPressApi","route","useRoute","router","useRouter","usePressMessages","t","useTranslate","message","useErrorText","rows","ref","loading","canManage","computed","title","module","view","value","query","inBin","q","typed","selected","raw","open","close","views","order","useItemOrder","PRESS_API","sortable","emptyText","load","answer","error","toast","watch","pause","onBeforeUnmount","choose","id","created","outlet","reorder","row","actionsFor","restore","remove","confirm","removed","unseen","_createBlock","_unref","WxListScreen","$event","_createVNode","WxCard","WxListDetail","_createElementVNode","_hoisted_1","WxInput","WxIcon","_createElementBlock","_normalizeClass","_createTextVNode","WxSkeleton","WxEmpty","_Fragment","WxSortableList","item","_withCtx","_openBlock","_resolveDynamicComponent","_hoisted_2","WxAvatar","_hoisted_4","_hoisted_5","_hoisted_6","_toDisplayString","WxTooltip","_hoisted_7","WxBadge","_hoisted_8","_hoisted_9","WxRowMenu","WxAlert","inline","back","OutletPane"],"mappings":"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"}
|
|
@@ -13,7 +13,8 @@ function Be(o = {}) {
|
|
|
13
13
|
{
|
|
14
14
|
path: s,
|
|
15
15
|
name: "webx.press",
|
|
16
|
-
|
|
16
|
+
meta: { help: "press.index" },
|
|
17
|
+
component: () => import("./PressPage-FeCExfdb.js"),
|
|
17
18
|
props: { base: s }
|
|
18
19
|
}
|
|
19
20
|
]
|
|
@@ -281,6 +282,7 @@ const Se = /* @__PURE__ */ Q({
|
|
|
281
282
|
})) : (x(), C(ee, { key: 1 }, [
|
|
282
283
|
y(u(ue), {
|
|
283
284
|
level: 3,
|
|
285
|
+
help: "press.outlet-form",
|
|
284
286
|
back: !l.inline,
|
|
285
287
|
"back-label": u(n)("outlet.back"),
|
|
286
288
|
title: G.value,
|
|
@@ -335,7 +337,7 @@ const Se = /* @__PURE__ */ Q({
|
|
|
335
337
|
for (const [t, i] of s)
|
|
336
338
|
l[t] = i;
|
|
337
339
|
return l;
|
|
338
|
-
}, Oe = /* @__PURE__ */ Ae(Se, [["__scopeId", "data-v-
|
|
340
|
+
}, Oe = /* @__PURE__ */ Ae(Se, [["__scopeId", "data-v-a77b9019"]]), Ne = oe(() => import("./PressPage-FeCExfdb.js"));
|
|
339
341
|
export {
|
|
340
342
|
Oe as O,
|
|
341
343
|
M as P,
|
|
@@ -346,4 +348,4 @@ export {
|
|
|
346
348
|
Be as p,
|
|
347
349
|
xe as u
|
|
348
350
|
};
|
|
349
|
-
//# sourceMappingURL=index-
|
|
351
|
+
//# sourceMappingURL=index-Dh9AR27x.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-Dh9AR27x.js","sources":["../src/module.ts","../src/api.ts","../src/messages.ts","../src/i18n.ts","../src/OutletPane.vue","../src/index.ts"],"sourcesContent":["import type { AdminModule } from '@webx-ui/module-admin'\n\nexport interface PressOptions {\n /** Where the outlets live inside the panel. */\n path?: string\n}\n\n/**\n * The press as a section of the panel (§4.9): one module and one entry, without a group of its\n * own — the reviews have one because they have two entries, and a group around a single item is\n * a heading over itself. A section whose server half is not installed never appears — the entry\n * is built from the manifest.\n */\nexport function press(options: PressOptions = {}): AdminModule {\n const path = options.path ?? '/press'\n\n return {\n id: 'press',\n path,\n // One route: the open outlet is in the address (`?outlet=`), beside the list it was opened\n // from, so a link to it is a link to both.\n routes: [\n {\n path,\n name: 'webx.press',\n meta: { help: 'press.index' },\n component: () => import('./PressPage.vue'),\n props: { base: path },\n },\n ],\n }\n}\n","import { reorderItems, type AdminContext } from '@webx-ui/module-admin'\nimport type { ScreenModel } from '@webx-ui/schema'\nimport type { OutletDetail, OutletQuery, OutletRow, OutletsList } from './types'\n\nexport interface PressApi {\n list(query?: OutletQuery): Promise<OutletsList>\n get(id: number): Promise<OutletDetail>\n /**\n * A new outlet out of the values of its form, articles included, in one request: the form is\n * filled in first and the record made on the first save, so nobody leaves an empty outlet behind.\n */\n create(values: ScreenModel): Promise<OutletDetail>\n /** Refused with a 422 under the name of the field — `articles.<n>.<field>` for an article. */\n save(id: number, values: ScreenModel): Promise<OutletDetail>\n remove(id: number): Promise<void>\n restore(id: number): Promise<OutletRow>\n /** The order of the whole list. The order of the articles is the order of the form's rows. */\n reorder(ids: number[]): Promise<void>\n}\n\n/** The path of the section under the panel's API, and of its order. */\nexport const PRESS_API = 'press'\n\n/** Everything under `/press`, below the panel's API path (§4.10). */\nexport function createPressApi(admin: AdminContext): PressApi {\n const base = `${admin.apiPath}/${PRESS_API}`\n const data = <T>(body: { data: T }): T => body.data\n\n return {\n list: (query = {}) => {\n const search = new URLSearchParams()\n\n if (query.search) search.set('search', query.search)\n if (query.trashed) search.set('trashed', '1')\n\n return admin.http.get<OutletsList>(search.size > 0 ? `${base}?${search}` : base)\n },\n get: (id) => admin.http.get<{ data: OutletDetail }>(`${base}/${id}`).then(data),\n create: (values) => admin.http.post<{ data: OutletDetail }>(base, { values }).then(data),\n save: (id, values) =>\n admin.http.put<{ data: OutletDetail }>(`${base}/${id}`, { values }).then(data),\n remove: (id) => admin.http.delete<void>(`${base}/${id}`).then(() => undefined),\n // The resource of the list line, and not the form's answer: a restored outlet stays closed.\n restore: (id) => admin.http.post<{ data: OutletRow }>(`${base}/${id}/restore`, {}).then(data),\n reorder: (ids) => reorderItems(admin, PRESS_API, ids, null),\n }\n}\n","import type { Messages } from '@webx-ui/module-admin'\n\n/**\n * The English this package speaks on its own, before the server's dictionary arrives, or without\n * one. The server ships the same lines in ten languages under `webx-press::` and overrides these;\n * `messages.test.ts` keeps the two sets of keys equal.\n *\n * `screen` is what the described screen `press.outlet-form` is labelled with: the server sends the\n * labels as `trans::webx-press::screen.*`, and these are what they say when no dictionary came.\n *\n * `kinds` are the four of the default config (decision 3). A kind a site adds brings its word in\n * `lang/vendor/webx-press` — the server labels the options of the field itself, so a kind missing\n * here is only missing from a panel that runs with no server at all.\n */\nexport const pressMessages: Record<string, Messages> = {\n module: {\n press: 'Press',\n },\n outlet: {\n new: 'New outlet',\n untitled: 'Untitled',\n search: 'Search the outlets',\n empty: 'No outlets yet.',\n 'empty-help':\n 'An outlet is a magazine, a paper or a site that wrote about you; its articles are added on its form.',\n 'empty-search': 'Nothing matches that.',\n 'empty-bin': 'The bin is empty.',\n all: 'All',\n trashed: 'Bin',\n 'not-published': 'Not published',\n 'visible-nowhere': 'Published, but seen in no language: none of its articles has a title yet.',\n 'articles-count': 'Articles: :count',\n 'no-articles': 'No articles yet',\n featured: 'In the logo strip',\n 'seen-in': 'Seen in: :locales',\n choose: 'Choose an outlet',\n 'choose-help': 'Or add a new one: the first line of the list.',\n save: 'Save',\n saved: 'The outlet is saved.',\n 'save-failed': 'The outlet was not saved.',\n cancel: 'Cancel',\n delete: 'Delete',\n 'delete-title': 'Delete “:name”?',\n 'delete-text':\n 'It goes to the bin with its articles and leaves the site and every block. Restored, it comes back with them.',\n deleted: 'The outlet is in the bin.',\n restore: 'Restore',\n restored: 'The outlet is back.',\n 'reorder-failed': 'The new order was not saved.',\n 'leave-title': 'Leave without saving?',\n 'leave-text': 'What you wrote in this outlet is not on the server.',\n leave: 'Leave',\n back: 'Back to the list',\n open: 'Open on the site',\n 'address-moving': 'The page of the outlet moves to the new address when you save.',\n },\n screen: {\n general: 'General',\n outlet: 'Outlet',\n logo: 'Logo',\n 'logo-help': 'Without a logo the site prints the name.',\n title: 'Name',\n 'title-help': 'Where it is not written in a language, the name from another one is shown.',\n slug: 'Address',\n 'slug-help':\n \"The outlet's page. A language with articles and no address of its own takes this one.\",\n 'website-url': 'Website',\n 'website-url-help': 'An address that starts with http:// or https://.',\n summary: 'About the outlet',\n 'summary-help':\n 'A few words under the name on its page, and its description for search engines.',\n settings: 'Settings',\n published: 'Published',\n 'published-help': 'On the site at once — in the languages it has articles in.',\n featured: 'In the strip of logos',\n 'featured-help': 'Shown by the logos block set to the marked outlets only.',\n articles: 'Articles',\n 'articles-help': \"In the order they are shown on the outlet's page. Saved with the outlet.\",\n 'article-label': '#:number · :title',\n 'article-untitled': 'Untitled',\n 'article-title': 'Title',\n 'article-title-help': 'An article is shown in a language only when it has a title in it.',\n excerpt: 'Excerpt',\n kind: 'Kind',\n 'kind-none': 'No kind',\n 'published-on': 'Date',\n 'date-precision': 'Known to',\n 'date-precision-help': 'How much of the date is printed: the day, the month, or the year only.',\n precision: {\n day: 'Day',\n month: 'Month',\n year: 'Year',\n },\n url: 'Link',\n 'url-help': \"The article on the outlet's site. Without it the title leads to the PDF.\",\n file: 'PDF',\n 'file-help': 'A PDF from the library — a scan of the page, say.',\n hidden: 'Do not show',\n 'hidden-help': 'Kept here, and shown nowhere on the site.',\n seo: 'SEO',\n 'seo-empty': 'The SEO card appears here when module-seo is installed.',\n },\n kinds: {\n mention: 'Mention',\n interview: 'Interview',\n expert_comment: 'Expert comment',\n authored: 'Authored article',\n },\n}\n","import { useAdmin } from '@webx-ui/module-admin'\nimport { pressMessages } from './messages'\n\nconst seeded = new WeakSet<object>()\n\n/** Puts this package's English under the panel's dictionary, once per panel. */\nexport function usePressMessages(): void {\n try {\n const { i18n } = useAdmin()\n\n if (!seeded.has(i18n)) {\n i18n.defaults('webx-press', pressMessages)\n seeded.add(i18n)\n }\n } catch {\n // Outside a panel there is no dictionary to seed, and `useTranslate` falls back on its own.\n }\n}\n","<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, onMounted, ref } from 'vue'\nimport { onBeforeRouteLeave, onBeforeRouteUpdate, type RouteLocationNormalized } from 'vue-router'\nimport {\n confirm,\n localizedValue,\n toast,\n useLocales,\n WxActionBar,\n WxBadge,\n WxButton,\n WxSkeleton,\n type LocalizedValue,\n} from '@webx-ui/core'\nimport {\n provideRecordAddress,\n useAdmin,\n useErrorText,\n useTranslate,\n WxSaveState,\n WxScreen,\n WxScreenHead,\n type ScreenAction,\n useBodyKeys,\n} from '@webx-ui/module-admin'\nimport type { ScreenModel } from '@webx-ui/schema'\nimport { createPressApi } from './api'\nimport { usePressMessages } from './i18n'\nimport type { OutletDetail, OutletSummary } from './types'\n\n/**\n * One outlet with its articles: the pane beside the list, and the whole screen on a phone.\n *\n * The described screen `press.outlet-form` — General · Articles · SEO — with a head over it and\n * one button under it. One explicit save for the whole outlet, articles included (decision 15):\n * the articles are rows of its form, and the server writes them into their own table in the same\n * transaction (§4.6). No autosave: there is no draft (decision 9), so what is saved is on the site\n * at once — the button is the moment somebody decides it is ready.\n *\n * A new outlet is a form first and a record on the first save: an empty row made by pressing\n * \"New outlet\" and walking away would be an outlet with nothing in it on the list for ever.\n *\n * The way back is drawn here and not by the pane around it: the drawer a narrow screen opens\n * this in has no close of its own on purpose (CLAUDE.md §4).\n */\ndefineOptions({ name: 'WxOutletPane' })\n\nconst props = withDefaults(\n defineProps<{\n /** `null` — an outlet not written yet. */\n id: number | null\n /** False in the drawer a narrow screen opens this in. */\n inline?: boolean\n }>(),\n { inline: true },\n)\n\nconst emit = defineEmits<{\n back: []\n /** The outlet was written; the list redraws its row. */\n saved: [outlet: OutletSummary]\n /** The first save of a new outlet: it is a record now, with an id to be opened by. */\n created: [outlet: OutletSummary]\n removed: [id: number]\n}>()\n\nconst context = useAdmin()\nconst api = createPressApi(context)\nconst locales = useLocales()\nusePressMessages()\n\nconst t = useTranslate('webx-press')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\nconst outlet = ref<OutletSummary | null>(null)\nconst values = ref<ScreenModel>({})\nconst prefix = ref<string | null>(null)\nconst snapshot = ref('')\n\nconst loading = ref(props.id !== null)\nconst saving = ref(false)\nconst errors = ref<Record<string, string[]>>({})\n\nconst canManage = computed(() => context.can('press.manage'))\n\nconst current = computed(() => JSON.stringify(values.value))\nconst dirty = computed(() => snapshot.value !== '' && current.value !== snapshot.value)\n\nconst state = computed<'saving' | 'unsaved' | 'saved'>(() => {\n if (saving.value) return 'saving'\n\n return dirty.value || props.id === null ? 'unsaved' : 'saved'\n})\n\n/** The name follows the field rather than the answer: an edit shows at the top as it is typed. */\nconst title = computed(() => {\n const written = values.value.title as LocalizedValue | string | null | undefined\n const fallback = props.id === null ? t('outlet.new') : (outlet.value?.title ?? '')\n\n return localizedValue(written, locales.active.value, fallback) || t('outlet.untitled')\n})\n\n/*\n * The address field is the panel's shared one (`wx-slug`), and this is what it prints. The form\n * answers with the page's address (`url`) in the panel's language — `/en/press/…` on a site whose\n * default is another — while the field compares it with the slug in the language being edited.\n * So the address is handed over without the host, the slash and the language, and only in its own\n * language: in any other the field has nothing to compare with, and says nothing rather than\n * warning that a page moves which does not.\n */\nprovideRecordAddress({\n values,\n prefix,\n path: computed(() => pathIn(outlet.value?.url ?? null, locales.active.value)),\n moving: () => t('outlet.address-moving'),\n})\n\nfunction pathIn(url: string | null, locale: string): string | null | undefined {\n if (url === null) return null\n\n let segments: string[]\n\n try {\n segments = new URL(url, 'http://site.invalid').pathname.split('/').filter((one) => one !== '')\n } catch {\n return null\n }\n\n const codes = locales.list.value.map((one) => one.code)\n const own =\n codes.includes(segments[0] ?? '') && segments[0] !== codes[0] ? segments.shift()! : codes[0]\n\n return own === undefined || own === locale ? segments.join('/') : undefined\n}\n\nfunction take(detail: OutletDetail): void {\n outlet.value = detail.outlet\n values.value = detail.values\n prefix.value = detail.prefix\n snapshot.value = JSON.stringify(detail.values)\n}\n\nif (props.id === null) {\n // What the server would answer for an outlet with nothing in it yet.\n const blank: ScreenModel = {\n title: {},\n slug: {},\n summary: {},\n published: false,\n featured: false,\n articles: [],\n }\n\n values.value = blank\n snapshot.value = JSON.stringify(blank)\n}\n\nasync function load(): Promise<void> {\n if (props.id === null) return\n\n loading.value = true\n\n try {\n take(await api.get(props.id))\n } catch (error) {\n toast.danger(message(error))\n emit('back')\n } finally {\n loading.value = false\n }\n}\n\nasync function save(): Promise<void> {\n if (saving.value || !canManage.value) return\n\n saving.value = true\n errors.value = {}\n\n try {\n const made = props.id === null\n const detail = made\n ? await api.create(values.value)\n : await api.save(props.id as number, values.value)\n\n take(detail)\n toast.success(t('outlet.saved'))\n if (made) emit('created', detail.outlet)\n else emit('saved', detail.outlet)\n } catch (error) {\n const body = (error as { body?: { errors?: Record<string, string[]> } }).body\n\n if (body?.errors) {\n // `articles.2.url` as the server names it: the repeater finds its row and field by that.\n errors.value = body.errors\n toast.danger(t('outlet.save-failed'))\n } else {\n toast.danger(message(error))\n }\n } finally {\n saving.value = false\n }\n}\n\nasync function remove(): Promise<void> {\n const record = outlet.value\n\n if (!record) return\n\n const agreed = await confirm({\n title: t('outlet.delete-title', { name: record.title }),\n message: t('outlet.delete-text'),\n confirmText: t('outlet.delete'),\n cancelText: t('outlet.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n try {\n await api.remove(record.id)\n toast.success(t('outlet.deleted'))\n // Nothing left to protect: the record is in the bin.\n snapshot.value = current.value\n emit('removed', record.id)\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\n/* The browser's own guard: it cannot wait for a request, so all it can do is ask. */\nfunction leaveGuard(event: BeforeUnloadEvent): void {\n if (dirty.value) event.preventDefault()\n}\n\n/* Ctrl+S saves, as it does everywhere else that has a save button. */\nfunction onKeydown(event: KeyboardEvent): void {\n if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 's') {\n event.preventDefault()\n if (dirty.value || props.id === null) void save()\n }\n}\n\n// And from the page around the form, where a click on empty space leaves the focus.\nuseBodyKeys(onKeydown)\n\nonMounted(() => {\n void load()\n window.addEventListener('beforeunload', leaveGuard)\n})\n\nonBeforeUnmount(() => {\n window.removeEventListener('beforeunload', leaveGuard)\n})\n\nasync function mayLeave(): Promise<boolean> {\n if (!dirty.value || !canManage.value) return true\n\n return await confirm({\n title: t('outlet.leave-title'),\n message: t('outlet.leave-text'),\n confirmText: t('outlet.leave'),\n cancelText: t('outlet.cancel'),\n tone: 'danger',\n })\n}\n\n/*\n * The list and this form are one route, so another outlet is an update of it rather than a way\n * out — and so is every keystroke in the search above the list, which goes into the address too.\n * Only a change of the outlet asks.\n */\nonBeforeRouteUpdate(async (to: RouteLocationNormalized, from: RouteLocationNormalized) =>\n to.query.outlet === from.query.outlet ? true : await mayLeave(),\n)\n\nonBeforeRouteLeave(mayLeave)\n\n/* What leads away from the outlet. On a narrow pane the head folds them into the ···. */\nconst actions = computed<ScreenAction[]>(() => {\n const record = outlet.value\n const list: ScreenAction[] = []\n\n // Only a page that answers: an unpublished outlet, or one seen nowhere, has no address.\n if (record?.url) {\n const url = record.url\n\n list.push({\n key: 'open',\n label: t('outlet.open'),\n icon: 'external-link',\n run: () => void window.open(url, '_blank', 'noopener'),\n })\n }\n\n if (canManage.value && record !== null) {\n list.push({\n key: 'delete',\n label: t('outlet.delete'),\n icon: 'trash',\n danger: true,\n menu: true,\n run: () => void remove(),\n })\n }\n\n return list\n})\n</script>\n\n<template>\n <div class=\"wx-outlet\" @keydown=\"onKeydown\">\n <wx-skeleton v-if=\"loading\" class=\"wx-outlet__ghost\" title :rows=\"6\" />\n\n <template v-else>\n <wx-screen-head\n :level=\"3\"\n help=\"press.outlet-form\"\n :back=\"!props.inline\"\n :back-label=\"t('outlet.back')\"\n :title=\"title\"\n :actions=\"actions\"\n :collapse-below=\"480\"\n @back=\"emit('back')\"\n >\n <template v-if=\"outlet && !outlet.published\" #title-after>\n <wx-badge dot>{{ t('outlet.not-published') }}</wx-badge>\n </template>\n </wx-screen-head>\n\n <wx-screen\n v-model=\"values\"\n name=\"press.outlet-form\"\n :errors=\"errors\"\n :disabled=\"!canManage || saving\"\n />\n\n <wx-action-bar v-if=\"canManage\">\n <template #state>\n <wx-save-state :state=\"state\" />\n </template>\n\n <wx-button\n type=\"primary\"\n :loading=\"saving\"\n :disabled=\"!dirty && props.id !== null\"\n @click=\"save\"\n >\n {{ t('outlet.save') }}\n </wx-button>\n </wx-action-bar>\n </template>\n </div>\n</template>\n\n<style scoped>\n.wx-outlet {\n display: flex;\n flex-direction: column;\n gap: var(--wx-gap, var(--wx-space-16));\n min-width: 0;\n padding: var(--wx-gap, var(--wx-space-16));\n container-type: inline-size;\n}\n\n.wx-outlet__ghost {\n max-width: 480px;\n}\n</style>\n","import { defineAsyncComponent } from 'vue'\nexport { press, type PressOptions } from './module'\nexport { createPressApi, PRESS_API, type PressApi } from './api'\nexport { pressMessages } from './messages'\nexport const WxPressPage = defineAsyncComponent(() => import('./PressPage.vue'))\nexport { default as WxOutletPane } from './OutletPane.vue'\nexport type {\n ArticleValues,\n OutletDetail,\n OutletQuery,\n OutletRow,\n OutletsList,\n OutletSummary,\n} from './types'\n"],"names":["press","options","path","PRESS_API","createPressApi","admin","base","data","body","query","search","id","values","ids","reorderItems","pressMessages","seeded","usePressMessages","i18n","useAdmin","props","__props","emit","__emit","context","api","locales","useLocales","t","useTranslate","message","useErrorText","outlet","ref","prefix","snapshot","loading","saving","errors","canManage","computed","current","dirty","state","title","written","fallback","localizedValue","provideRecordAddress","pathIn","url","locale","segments","one","codes","own","take","detail","blank","load","error","toast","save","made","remove","record","confirm","leaveGuard","event","onKeydown","useBodyKeys","onMounted","onBeforeUnmount","mayLeave","onBeforeRouteUpdate","to","from","onBeforeRouteLeave","actions","list","_createElementBlock","_createBlock","_unref","WxSkeleton","_Fragment","_createVNode","WxScreenHead","WxBadge","WxScreen","$event","WxActionBar","WxSaveState","WxButton","WxPressPage","defineAsyncComponent"],"mappings":"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"}
|
package/dist/index.js
CHANGED
package/dist/style.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
.wx-outlet[data-v-
|
|
1
|
+
.wx-outlet[data-v-a77b9019]{display:flex;flex-direction:column;gap:var(--wx-gap, var(--wx-space-16));min-width:0;padding:var(--wx-gap, var(--wx-space-16));container-type:inline-size}.wx-outlet__ghost[data-v-a77b9019]{max-width:480px}.wx-press>.wx-card__body{display:flex;flex-direction:column;overflow:clip;border-radius:inherit}.wx-press__bar[data-v-877cb910]{display:flex;gap:var(--wx-space-8);padding:var(--wx-space-12) var(--wx-space-12) var(--wx-space-8)}.wx-press__search[data-v-877cb910]{flex:1 1 180px;min-width:0}.wx-press__new[data-v-877cb910]{display:flex;align-items:center;gap:var(--wx-space-8);width:calc(100% - 2 * var(--wx-space-8));margin:0 var(--wx-space-8) var(--wx-space-4);padding:var(--wx-space-8);border:1px dashed var(--wx-border-default);border-radius:var(--wx-radius-sm);background:none;font:inherit;color:var(--wx-color-primary);text-align:start;cursor:pointer}.wx-press__new[data-v-877cb910]:hover,.wx-press__new.is-current[data-v-877cb910]{background:var(--wx-bg-subtle)}.wx-press__loading[data-v-877cb910]{padding:var(--wx-space-12)}.wx-press__list[data-v-877cb910]{padding-inline:var(--wx-space-8)}.wx-press__list.is-loading[data-v-877cb910]{opacity:.6}.wx-press__list[data-v-877cb910] .wx-sortable-list__row{gap:var(--wx-space-4);padding-inline:var(--wx-space-4)}.wx-press__note[data-v-877cb910]{margin:var(--wx-space-8)}.wx-outlet-row[data-v-877cb910]{display:flex;align-items:flex-start;gap:var(--wx-space-10);flex:1 1 auto;width:100%;min-width:0;padding-block:var(--wx-space-8);padding-inline:0;border:0;background:none;font:inherit;color:inherit;text-align:start}button.wx-outlet-row[data-v-877cb910]{cursor:pointer}.wx-outlet-row__initials[data-v-877cb910]{flex:none}.wx-outlet-row__logo[data-v-877cb910]{display:flex;flex:none;align-items:center;justify-content:center;width:64px;height:32px;overflow:hidden;border-radius:var(--wx-radius-sm);background:var(--wx-bg-subtle)}.wx-outlet-row__logo img[data-v-877cb910]{display:block;max-width:100%;max-height:100%;object-fit:contain}.wx-outlet-row__body[data-v-877cb910]{display:flex;flex:1 1 auto;flex-direction:column;gap:var(--wx-space-2);min-width:0}.wx-outlet-row__head[data-v-877cb910]{display:flex;align-items:center;gap:var(--wx-space-6);min-width:0}.wx-outlet-row__title[data-v-877cb910]{min-width:0;overflow:hidden;font-weight:var(--wx-font-weight-medium);text-overflow:ellipsis;white-space:nowrap}.wx-outlet-row.is-current .wx-outlet-row__title[data-v-877cb910]{color:var(--wx-color-primary)}.wx-outlet-row__featured[data-v-877cb910]{flex:none;color:var(--wx-color-warning)}.wx-outlet-row__facts[data-v-877cb910]{display:flex;flex-wrap:wrap;align-items:center;gap:var(--wx-space-4) var(--wx-space-8);min-width:0;color:var(--wx-text-muted);font-size:var(--wx-font-size-sm)}.wx-outlet-row__locales[data-v-877cb910]{font-family:var(--wx-font-family-mono);font-size:var(--wx-font-size-xs);text-transform:uppercase}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webx-ui/module-press",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.7",
|
|
4
4
|
"description": "Press for the WebX UI admin panel: the outlets that wrote about a site and their articles — a link or a PDF — ordered by hand, shown on the site by blocks.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -44,9 +44,9 @@
|
|
|
44
44
|
"vue-router": "^4.5.0"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@webx-ui/core": "^0.
|
|
48
|
-
"@webx-ui/module-admin": "^0.
|
|
49
|
-
"@webx-ui/schema": "^0.11.
|
|
47
|
+
"@webx-ui/core": "^0.47.0",
|
|
48
|
+
"@webx-ui/module-admin": "^0.36.0",
|
|
49
|
+
"@webx-ui/schema": "^0.11.1"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@types/node": "^24.10.1",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index-Uviomho-.js","sources":["../src/module.ts","../src/api.ts","../src/messages.ts","../src/i18n.ts","../src/OutletPane.vue","../src/index.ts"],"sourcesContent":["import type { AdminModule } from '@webx-ui/module-admin'\n\nexport interface PressOptions {\n /** Where the outlets live inside the panel. */\n path?: string\n}\n\n/**\n * The press as a section of the panel (§4.9): one module and one entry, without a group of its\n * own — the reviews have one because they have two entries, and a group around a single item is\n * a heading over itself. A section whose server half is not installed never appears — the entry\n * is built from the manifest.\n */\nexport function press(options: PressOptions = {}): AdminModule {\n const path = options.path ?? '/press'\n\n return {\n id: 'press',\n path,\n // One route: the open outlet is in the address (`?outlet=`), beside the list it was opened\n // from, so a link to it is a link to both.\n routes: [\n {\n path,\n name: 'webx.press',\n component: () => import('./PressPage.vue'),\n props: { base: path },\n },\n ],\n }\n}\n","import { reorderItems, type AdminContext } from '@webx-ui/module-admin'\nimport type { ScreenModel } from '@webx-ui/schema'\nimport type { OutletDetail, OutletQuery, OutletRow, OutletsList } from './types'\n\nexport interface PressApi {\n list(query?: OutletQuery): Promise<OutletsList>\n get(id: number): Promise<OutletDetail>\n /**\n * A new outlet out of the values of its form, articles included, in one request: the form is\n * filled in first and the record made on the first save, so nobody leaves an empty outlet behind.\n */\n create(values: ScreenModel): Promise<OutletDetail>\n /** Refused with a 422 under the name of the field — `articles.<n>.<field>` for an article. */\n save(id: number, values: ScreenModel): Promise<OutletDetail>\n remove(id: number): Promise<void>\n restore(id: number): Promise<OutletRow>\n /** The order of the whole list. The order of the articles is the order of the form's rows. */\n reorder(ids: number[]): Promise<void>\n}\n\n/** The path of the section under the panel's API, and of its order. */\nexport const PRESS_API = 'press'\n\n/** Everything under `/press`, below the panel's API path (§4.10). */\nexport function createPressApi(admin: AdminContext): PressApi {\n const base = `${admin.apiPath}/${PRESS_API}`\n const data = <T>(body: { data: T }): T => body.data\n\n return {\n list: (query = {}) => {\n const search = new URLSearchParams()\n\n if (query.search) search.set('search', query.search)\n if (query.trashed) search.set('trashed', '1')\n\n return admin.http.get<OutletsList>(search.size > 0 ? `${base}?${search}` : base)\n },\n get: (id) => admin.http.get<{ data: OutletDetail }>(`${base}/${id}`).then(data),\n create: (values) => admin.http.post<{ data: OutletDetail }>(base, { values }).then(data),\n save: (id, values) =>\n admin.http.put<{ data: OutletDetail }>(`${base}/${id}`, { values }).then(data),\n remove: (id) => admin.http.delete<void>(`${base}/${id}`).then(() => undefined),\n // The resource of the list line, and not the form's answer: a restored outlet stays closed.\n restore: (id) => admin.http.post<{ data: OutletRow }>(`${base}/${id}/restore`, {}).then(data),\n reorder: (ids) => reorderItems(admin, PRESS_API, ids, null),\n }\n}\n","import type { Messages } from '@webx-ui/module-admin'\n\n/**\n * The English this package speaks on its own, before the server's dictionary arrives, or without\n * one. The server ships the same lines in ten languages under `webx-press::` and overrides these;\n * `messages.test.ts` keeps the two sets of keys equal.\n *\n * `screen` is what the described screen `press.outlet-form` is labelled with: the server sends the\n * labels as `trans::webx-press::screen.*`, and these are what they say when no dictionary came.\n *\n * `kinds` are the four of the default config (decision 3). A kind a site adds brings its word in\n * `lang/vendor/webx-press` — the server labels the options of the field itself, so a kind missing\n * here is only missing from a panel that runs with no server at all.\n */\nexport const pressMessages: Record<string, Messages> = {\n module: {\n press: 'Press',\n },\n outlet: {\n new: 'New outlet',\n untitled: 'Untitled',\n search: 'Search the outlets',\n empty: 'No outlets yet.',\n 'empty-help':\n 'An outlet is a magazine, a paper or a site that wrote about you; its articles are added on its form.',\n 'empty-search': 'Nothing matches that.',\n 'empty-bin': 'The bin is empty.',\n all: 'All',\n trashed: 'Bin',\n 'not-published': 'Not published',\n 'visible-nowhere': 'Published, but seen in no language: none of its articles has a title yet.',\n 'articles-count': 'Articles: :count',\n 'no-articles': 'No articles yet',\n featured: 'In the logo strip',\n 'seen-in': 'Seen in: :locales',\n choose: 'Choose an outlet',\n 'choose-help': 'Or add a new one: the first line of the list.',\n save: 'Save',\n saved: 'The outlet is saved.',\n 'save-failed': 'The outlet was not saved.',\n cancel: 'Cancel',\n delete: 'Delete',\n 'delete-title': 'Delete “:name”?',\n 'delete-text':\n 'It goes to the bin with its articles and leaves the site and every block. Restored, it comes back with them.',\n deleted: 'The outlet is in the bin.',\n restore: 'Restore',\n restored: 'The outlet is back.',\n 'reorder-failed': 'The new order was not saved.',\n 'leave-title': 'Leave without saving?',\n 'leave-text': 'What you wrote in this outlet is not on the server.',\n leave: 'Leave',\n back: 'Back to the list',\n open: 'Open on the site',\n 'address-moving': 'The page of the outlet moves to the new address when you save.',\n },\n screen: {\n general: 'General',\n outlet: 'Outlet',\n logo: 'Logo',\n 'logo-help': 'Without a logo the site prints the name.',\n title: 'Name',\n 'title-help': 'Where it is not written in a language, the name from another one is shown.',\n slug: 'Address',\n 'slug-help':\n \"The outlet's page. A language with articles and no address of its own takes this one.\",\n 'website-url': 'Website',\n 'website-url-help': 'An address that starts with http:// or https://.',\n summary: 'About the outlet',\n 'summary-help':\n 'A few words under the name on its page, and its description for search engines.',\n settings: 'Settings',\n published: 'Published',\n 'published-help': 'On the site at once — in the languages it has articles in.',\n featured: 'In the strip of logos',\n 'featured-help': 'Shown by the logos block set to the marked outlets only.',\n articles: 'Articles',\n 'articles-help': \"In the order they are shown on the outlet's page. Saved with the outlet.\",\n 'article-label': '#:number · :title',\n 'article-untitled': 'Untitled',\n 'article-title': 'Title',\n 'article-title-help': 'An article is shown in a language only when it has a title in it.',\n excerpt: 'Excerpt',\n kind: 'Kind',\n 'kind-none': 'No kind',\n 'published-on': 'Date',\n 'date-precision': 'Known to',\n 'date-precision-help': 'How much of the date is printed: the day, the month, or the year only.',\n precision: {\n day: 'Day',\n month: 'Month',\n year: 'Year',\n },\n url: 'Link',\n 'url-help': \"The article on the outlet's site. Without it the title leads to the PDF.\",\n file: 'PDF',\n 'file-help': 'A PDF from the library — a scan of the page, say.',\n hidden: 'Do not show',\n 'hidden-help': 'Kept here, and shown nowhere on the site.',\n seo: 'SEO',\n 'seo-empty': 'The SEO card appears here when module-seo is installed.',\n },\n kinds: {\n mention: 'Mention',\n interview: 'Interview',\n expert_comment: 'Expert comment',\n authored: 'Authored article',\n },\n}\n","import { useAdmin } from '@webx-ui/module-admin'\nimport { pressMessages } from './messages'\n\nconst seeded = new WeakSet<object>()\n\n/** Puts this package's English under the panel's dictionary, once per panel. */\nexport function usePressMessages(): void {\n try {\n const { i18n } = useAdmin()\n\n if (!seeded.has(i18n)) {\n i18n.defaults('webx-press', pressMessages)\n seeded.add(i18n)\n }\n } catch {\n // Outside a panel there is no dictionary to seed, and `useTranslate` falls back on its own.\n }\n}\n","<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, onMounted, ref } from 'vue'\nimport { onBeforeRouteLeave, onBeforeRouteUpdate, type RouteLocationNormalized } from 'vue-router'\nimport {\n confirm,\n localizedValue,\n toast,\n useLocales,\n WxActionBar,\n WxBadge,\n WxButton,\n WxSkeleton,\n type LocalizedValue,\n} from '@webx-ui/core'\nimport {\n provideRecordAddress,\n useAdmin,\n useErrorText,\n useTranslate,\n WxSaveState,\n WxScreen,\n WxScreenHead,\n type ScreenAction,\n useBodyKeys,\n} from '@webx-ui/module-admin'\nimport type { ScreenModel } from '@webx-ui/schema'\nimport { createPressApi } from './api'\nimport { usePressMessages } from './i18n'\nimport type { OutletDetail, OutletSummary } from './types'\n\n/**\n * One outlet with its articles: the pane beside the list, and the whole screen on a phone.\n *\n * The described screen `press.outlet-form` — General · Articles · SEO — with a head over it and\n * one button under it. One explicit save for the whole outlet, articles included (decision 15):\n * the articles are rows of its form, and the server writes them into their own table in the same\n * transaction (§4.6). No autosave: there is no draft (decision 9), so what is saved is on the site\n * at once — the button is the moment somebody decides it is ready.\n *\n * A new outlet is a form first and a record on the first save: an empty row made by pressing\n * \"New outlet\" and walking away would be an outlet with nothing in it on the list for ever.\n *\n * The way back is drawn here and not by the pane around it: the drawer a narrow screen opens\n * this in has no close of its own on purpose (CLAUDE.md §4).\n */\ndefineOptions({ name: 'WxOutletPane' })\n\nconst props = withDefaults(\n defineProps<{\n /** `null` — an outlet not written yet. */\n id: number | null\n /** False in the drawer a narrow screen opens this in. */\n inline?: boolean\n }>(),\n { inline: true },\n)\n\nconst emit = defineEmits<{\n back: []\n /** The outlet was written; the list redraws its row. */\n saved: [outlet: OutletSummary]\n /** The first save of a new outlet: it is a record now, with an id to be opened by. */\n created: [outlet: OutletSummary]\n removed: [id: number]\n}>()\n\nconst context = useAdmin()\nconst api = createPressApi(context)\nconst locales = useLocales()\nusePressMessages()\n\nconst t = useTranslate('webx-press')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\nconst outlet = ref<OutletSummary | null>(null)\nconst values = ref<ScreenModel>({})\nconst prefix = ref<string | null>(null)\nconst snapshot = ref('')\n\nconst loading = ref(props.id !== null)\nconst saving = ref(false)\nconst errors = ref<Record<string, string[]>>({})\n\nconst canManage = computed(() => context.can('press.manage'))\n\nconst current = computed(() => JSON.stringify(values.value))\nconst dirty = computed(() => snapshot.value !== '' && current.value !== snapshot.value)\n\nconst state = computed<'saving' | 'unsaved' | 'saved'>(() => {\n if (saving.value) return 'saving'\n\n return dirty.value || props.id === null ? 'unsaved' : 'saved'\n})\n\n/** The name follows the field rather than the answer: an edit shows at the top as it is typed. */\nconst title = computed(() => {\n const written = values.value.title as LocalizedValue | string | null | undefined\n const fallback = props.id === null ? t('outlet.new') : (outlet.value?.title ?? '')\n\n return localizedValue(written, locales.active.value, fallback) || t('outlet.untitled')\n})\n\n/*\n * The address field is the panel's shared one (`wx-slug`), and this is what it prints. The form\n * answers with the page's address (`url`) in the panel's language — `/en/press/…` on a site whose\n * default is another — while the field compares it with the slug in the language being edited.\n * So the address is handed over without the host, the slash and the language, and only in its own\n * language: in any other the field has nothing to compare with, and says nothing rather than\n * warning that a page moves which does not.\n */\nprovideRecordAddress({\n values,\n prefix,\n path: computed(() => pathIn(outlet.value?.url ?? null, locales.active.value)),\n moving: () => t('outlet.address-moving'),\n})\n\nfunction pathIn(url: string | null, locale: string): string | null | undefined {\n if (url === null) return null\n\n let segments: string[]\n\n try {\n segments = new URL(url, 'http://site.invalid').pathname.split('/').filter((one) => one !== '')\n } catch {\n return null\n }\n\n const codes = locales.list.value.map((one) => one.code)\n const own =\n codes.includes(segments[0] ?? '') && segments[0] !== codes[0] ? segments.shift()! : codes[0]\n\n return own === undefined || own === locale ? segments.join('/') : undefined\n}\n\nfunction take(detail: OutletDetail): void {\n outlet.value = detail.outlet\n values.value = detail.values\n prefix.value = detail.prefix\n snapshot.value = JSON.stringify(detail.values)\n}\n\nif (props.id === null) {\n // What the server would answer for an outlet with nothing in it yet.\n const blank: ScreenModel = {\n title: {},\n slug: {},\n summary: {},\n published: false,\n featured: false,\n articles: [],\n }\n\n values.value = blank\n snapshot.value = JSON.stringify(blank)\n}\n\nasync function load(): Promise<void> {\n if (props.id === null) return\n\n loading.value = true\n\n try {\n take(await api.get(props.id))\n } catch (error) {\n toast.danger(message(error))\n emit('back')\n } finally {\n loading.value = false\n }\n}\n\nasync function save(): Promise<void> {\n if (saving.value || !canManage.value) return\n\n saving.value = true\n errors.value = {}\n\n try {\n const made = props.id === null\n const detail = made\n ? await api.create(values.value)\n : await api.save(props.id as number, values.value)\n\n take(detail)\n toast.success(t('outlet.saved'))\n if (made) emit('created', detail.outlet)\n else emit('saved', detail.outlet)\n } catch (error) {\n const body = (error as { body?: { errors?: Record<string, string[]> } }).body\n\n if (body?.errors) {\n // `articles.2.url` as the server names it: the repeater finds its row and field by that.\n errors.value = body.errors\n toast.danger(t('outlet.save-failed'))\n } else {\n toast.danger(message(error))\n }\n } finally {\n saving.value = false\n }\n}\n\nasync function remove(): Promise<void> {\n const record = outlet.value\n\n if (!record) return\n\n const agreed = await confirm({\n title: t('outlet.delete-title', { name: record.title }),\n message: t('outlet.delete-text'),\n confirmText: t('outlet.delete'),\n cancelText: t('outlet.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n try {\n await api.remove(record.id)\n toast.success(t('outlet.deleted'))\n // Nothing left to protect: the record is in the bin.\n snapshot.value = current.value\n emit('removed', record.id)\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\n/* The browser's own guard: it cannot wait for a request, so all it can do is ask. */\nfunction leaveGuard(event: BeforeUnloadEvent): void {\n if (dirty.value) event.preventDefault()\n}\n\n/* Ctrl+S saves, as it does everywhere else that has a save button. */\nfunction onKeydown(event: KeyboardEvent): void {\n if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 's') {\n event.preventDefault()\n if (dirty.value || props.id === null) void save()\n }\n}\n\n// And from the page around the form, where a click on empty space leaves the focus.\nuseBodyKeys(onKeydown)\n\nonMounted(() => {\n void load()\n window.addEventListener('beforeunload', leaveGuard)\n})\n\nonBeforeUnmount(() => {\n window.removeEventListener('beforeunload', leaveGuard)\n})\n\nasync function mayLeave(): Promise<boolean> {\n if (!dirty.value || !canManage.value) return true\n\n return await confirm({\n title: t('outlet.leave-title'),\n message: t('outlet.leave-text'),\n confirmText: t('outlet.leave'),\n cancelText: t('outlet.cancel'),\n tone: 'danger',\n })\n}\n\n/*\n * The list and this form are one route, so another outlet is an update of it rather than a way\n * out — and so is every keystroke in the search above the list, which goes into the address too.\n * Only a change of the outlet asks.\n */\nonBeforeRouteUpdate(async (to: RouteLocationNormalized, from: RouteLocationNormalized) =>\n to.query.outlet === from.query.outlet ? true : await mayLeave(),\n)\n\nonBeforeRouteLeave(mayLeave)\n\n/* What leads away from the outlet. On a narrow pane the head folds them into the ···. */\nconst actions = computed<ScreenAction[]>(() => {\n const record = outlet.value\n const list: ScreenAction[] = []\n\n // Only a page that answers: an unpublished outlet, or one seen nowhere, has no address.\n if (record?.url) {\n const url = record.url\n\n list.push({\n key: 'open',\n label: t('outlet.open'),\n icon: 'external-link',\n run: () => void window.open(url, '_blank', 'noopener'),\n })\n }\n\n if (canManage.value && record !== null) {\n list.push({\n key: 'delete',\n label: t('outlet.delete'),\n icon: 'trash',\n danger: true,\n menu: true,\n run: () => void remove(),\n })\n }\n\n return list\n})\n</script>\n\n<template>\n <div class=\"wx-outlet\" @keydown=\"onKeydown\">\n <wx-skeleton v-if=\"loading\" class=\"wx-outlet__ghost\" title :rows=\"6\" />\n\n <template v-else>\n <wx-screen-head\n :level=\"3\"\n :back=\"!props.inline\"\n :back-label=\"t('outlet.back')\"\n :title=\"title\"\n :actions=\"actions\"\n :collapse-below=\"480\"\n @back=\"emit('back')\"\n >\n <template v-if=\"outlet && !outlet.published\" #title-after>\n <wx-badge dot>{{ t('outlet.not-published') }}</wx-badge>\n </template>\n </wx-screen-head>\n\n <wx-screen\n v-model=\"values\"\n name=\"press.outlet-form\"\n :errors=\"errors\"\n :disabled=\"!canManage || saving\"\n />\n\n <wx-action-bar v-if=\"canManage\">\n <template #state>\n <wx-save-state :state=\"state\" />\n </template>\n\n <wx-button\n type=\"primary\"\n :loading=\"saving\"\n :disabled=\"!dirty && props.id !== null\"\n @click=\"save\"\n >\n {{ t('outlet.save') }}\n </wx-button>\n </wx-action-bar>\n </template>\n </div>\n</template>\n\n<style scoped>\n.wx-outlet {\n display: flex;\n flex-direction: column;\n gap: var(--wx-gap, var(--wx-space-16));\n min-width: 0;\n padding: var(--wx-gap, var(--wx-space-16));\n container-type: inline-size;\n}\n\n.wx-outlet__ghost {\n max-width: 480px;\n}\n</style>\n","import { defineAsyncComponent } from 'vue'\nexport { press, type PressOptions } from './module'\nexport { createPressApi, PRESS_API, type PressApi } from './api'\nexport { pressMessages } from './messages'\nexport const WxPressPage = defineAsyncComponent(() => import('./PressPage.vue'))\nexport { default as WxOutletPane } from './OutletPane.vue'\nexport type {\n ArticleValues,\n OutletDetail,\n OutletQuery,\n OutletRow,\n OutletsList,\n OutletSummary,\n} from './types'\n"],"names":["press","options","path","PRESS_API","createPressApi","admin","base","data","body","query","search","id","values","ids","reorderItems","pressMessages","seeded","usePressMessages","i18n","useAdmin","props","__props","emit","__emit","context","api","locales","useLocales","t","useTranslate","message","useErrorText","outlet","ref","prefix","snapshot","loading","saving","errors","canManage","computed","current","dirty","state","title","written","fallback","localizedValue","provideRecordAddress","pathIn","url","locale","segments","one","codes","own","take","detail","blank","load","error","toast","save","made","remove","record","confirm","leaveGuard","event","onKeydown","useBodyKeys","onMounted","onBeforeUnmount","mayLeave","onBeforeRouteUpdate","to","from","onBeforeRouteLeave","actions","list","_createElementBlock","_createBlock","_unref","WxSkeleton","_Fragment","_createVNode","WxScreenHead","WxBadge","WxScreen","$event","WxActionBar","WxSaveState","WxButton","WxPressPage","defineAsyncComponent"],"mappings":"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"}
|