@webx-ui/module-banners 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
- import { defineComponent as X, computed as $, ref as W, watch as j, onMounted as ke, onBeforeUnmount as Ce, openBlock as m, createElementBlock as M, Fragment as H, createVNode as c, unref as e, withCtx as p, createSlots as _e, createTextVNode as E, toDisplayString as L, createBlock as D, createCommentVNode as A, useTemplateRef as Ne, nextTick as qe, isRef as De, createElementVNode as R, withKeys as me, normalizeClass as ae, resolveDynamicComponent as Ie, renderList as Oe } from "vue";
1
+ import { defineComponent as X, computed as $, ref as W, watch as j, onMounted as _e, onBeforeUnmount as Ce, openBlock as m, createElementBlock as M, Fragment as H, createVNode as c, unref as e, withCtx as p, createSlots as xe, createTextVNode as E, toDisplayString as L, createBlock as D, createCommentVNode as A, useTemplateRef as Ne, nextTick as qe, isRef as De, createElementVNode as R, withKeys as ye, normalizeClass as ae, resolveDynamicComponent as Ie, renderList as Oe } from "vue";
2
2
  import { useRoute as ie, useRouter as ce, onBeforeRouteLeave as Le } from "vue-router";
3
- import { useLocales as xe, localizedValue as Me, confirm as G, WxSkeleton as Q, WxCard as te, WxBadge as ne, WxFormItem as le, WxSelect as Re, WxActionBar as Ue, WxButton as se, toast as T, useModal as ze, WxDialog as Ae, WxInput as ye, WxSpace as Ee, WxTooltip as Ke, WxIcon as re, WxSegmented as Fe, WxEmpty as oe, WxSortableList as Je, WxAlert as je, createModal as He, WxListDetail as Ge, WxText as he, WxIndicator as Qe } from "@webx-ui/core";
4
- import { useAdmin as J, useTranslate as Y, useErrorText as ue, WxScreenHead as $e, WxScreen as Xe, WxSaveState as Ye, WxRowMenu as Pe, WxListScreen as Ze } from "@webx-ui/module-admin";
3
+ import { useLocales as $e, localizedValue as Me, confirm as G, WxSkeleton as Q, WxCard as te, WxBadge as ne, WxFormItem as le, WxSelect as Re, WxActionBar as Ue, WxButton as se, toast as T, useModal as ze, WxDialog as Ae, WxInput as he, WxSpace as Ee, WxTooltip as Ke, WxIcon as re, WxSegmented as Fe, WxEmpty as oe, WxSortableList as Je, WxAlert as je, createModal as He, WxListDetail as Ge, WxText as we, WxIndicator as Qe } from "@webx-ui/core";
4
+ import { useAdmin as J, useTranslate as Y, useErrorText as ue, useBodyKeys as Xe, WxScreenHead as Pe, WxScreen as Ye, WxSaveState as Ze, WxRowMenu as Be, WxListScreen as ea } from "@webx-ui/module-admin";
5
5
  function Z(i) {
6
6
  const s = `${i.apiPath}/banners`, u = (n) => `${s}/places/${encodeURIComponent(n)}`, y = (n) => n.data;
7
7
  return {
@@ -25,7 +25,7 @@ function Z(i) {
25
25
  })
26
26
  };
27
27
  }
28
- const ea = {
28
+ const aa = {
29
29
  module: {
30
30
  banners: "Banners"
31
31
  },
@@ -134,22 +134,22 @@ const ea = {
134
134
  enabled: "On",
135
135
  "enabled-help": "Only a banner that is on is shown on the site. A new one starts off."
136
136
  }
137
- }, we = /* @__PURE__ */ new WeakSet();
137
+ }, ge = /* @__PURE__ */ new WeakSet();
138
138
  function ee() {
139
139
  try {
140
140
  const { i18n: i } = J();
141
- we.has(i) || (i.defaults("webx-banners", ea), we.add(i));
141
+ ge.has(i) || (i.defaults("webx-banners", aa), ge.add(i));
142
142
  } catch {
143
143
  }
144
144
  }
145
- const aa = /* @__PURE__ */ X({
145
+ const ta = /* @__PURE__ */ X({
146
146
  name: "WxBannerEditorPage",
147
147
  __name: "BannerEditorPage",
148
148
  props: {
149
149
  base: { default: "/banners" }
150
150
  },
151
151
  setup(i) {
152
- const s = i, u = J(), y = Z(u), n = ie(), v = ce(), d = xe();
152
+ const s = i, u = J(), y = Z(u), n = ie(), v = ce(), d = $e();
153
153
  ee();
154
154
  const l = Y("webx-banners"), t = ue(), P = $(() => {
155
155
  const a = n.params.id;
@@ -196,8 +196,8 @@ const aa = /* @__PURE__ */ X({
196
196
  if (O.value = a, q !== null)
197
197
  _(q);
198
198
  else {
199
- const z = n.query.place, Se = typeof z == "string" && a.some((Ve) => Ve.key === z) ? z : null, fe = V();
200
- b.value = null, g.value = fe, S.value = JSON.stringify(fe), h.value = Se ?? a[0]?.key ?? null, k.value = h.value;
199
+ const z = n.query.place, Se = typeof z == "string" && a.some((Ve) => Ve.key === z) ? z : null, me = V();
200
+ b.value = null, g.value = me, S.value = JSON.stringify(me), h.value = Se ?? a[0]?.key ?? null, k.value = h.value;
201
201
  }
202
202
  } catch (a) {
203
203
  T.danger(t(a)), v.push(s.base);
@@ -223,7 +223,7 @@ const aa = /* @__PURE__ */ X({
223
223
  }
224
224
  }
225
225
  }
226
- async function Be() {
226
+ async function We() {
227
227
  const a = b.value;
228
228
  if (!(!a || !await G({
229
229
  title: l("banner.delete-title", { name: a.title }),
@@ -241,12 +241,12 @@ const aa = /* @__PURE__ */ X({
241
241
  function be(a) {
242
242
  U.value && a.preventDefault();
243
243
  }
244
- function We(a) {
244
+ function fe(a) {
245
245
  (a.ctrlKey || a.metaKey) && a.key.toLowerCase() === "s" && (a.preventDefault(), (U.value || P.value === null) && ve());
246
246
  }
247
- j(P, (a) => {
247
+ Xe(fe), j(P, (a) => {
248
248
  a !== null && b.value?.id === a || pe();
249
- }), ke(() => {
249
+ }), _e(() => {
250
250
  pe(), window.addEventListener("beforeunload", be);
251
251
  }), Ce(() => {
252
252
  window.removeEventListener("beforeunload", be);
@@ -266,14 +266,14 @@ const aa = /* @__PURE__ */ X({
266
266
  danger: !0,
267
267
  menu: !0,
268
268
  run: () => {
269
- Be();
269
+ We();
270
270
  }
271
271
  }
272
272
  ] : []
273
273
  );
274
274
  return (a, q) => (m(), M("div", {
275
275
  class: "wx-banner-editor",
276
- onKeydown: We
276
+ onKeydown: fe
277
277
  }, [
278
278
  C.value ? (m(), M(H, { key: 0 }, [
279
279
  c(e(Q), {
@@ -288,13 +288,13 @@ const aa = /* @__PURE__ */ X({
288
288
  _: 1
289
289
  })
290
290
  ], 64)) : (m(), M(H, { key: 1 }, [
291
- c(e($e), {
291
+ c(e(Pe), {
292
292
  divider: "",
293
293
  back: r.value,
294
294
  "back-label": I.value,
295
295
  title: F.value,
296
296
  actions: Te.value
297
- }, _e({ _: 2 }, [
297
+ }, xe({ _: 2 }, [
298
298
  b.value && !b.value.enabled ? {
299
299
  name: "title-after",
300
300
  fn: p(() => [
@@ -330,7 +330,7 @@ const aa = /* @__PURE__ */ X({
330
330
  ]),
331
331
  _: 1
332
332
  }),
333
- c(e(Xe), {
333
+ c(e(Ye), {
334
334
  modelValue: g.value,
335
335
  "onUpdate:modelValue": q[1] || (q[1] = (z) => g.value = z),
336
336
  name: "banners.form",
@@ -339,7 +339,7 @@ const aa = /* @__PURE__ */ X({
339
339
  }, null, 8, ["modelValue", "errors", "disabled"]),
340
340
  x.value ? (m(), D(e(Ue), { key: 0 }, {
341
341
  state: p(() => [
342
- c(e(Ye), { state: K.value }, null, 8, ["state"])
342
+ c(e(Ze), { state: K.value }, null, 8, ["state"])
343
343
  ]),
344
344
  default: p(() => [
345
345
  c(e(se), {
@@ -364,14 +364,14 @@ const aa = /* @__PURE__ */ X({
364
364
  for (const [y, n] of s)
365
365
  u[y] = n;
366
366
  return u;
367
- }, ge = /* @__PURE__ */ de(aa, [["__scopeId", "data-v-038085a4"]]), ta = /* @__PURE__ */ X({
367
+ }, ke = /* @__PURE__ */ de(ta, [["__scopeId", "data-v-2c21e525"]]), na = /* @__PURE__ */ X({
368
368
  name: "WxBannerPlaceDialog",
369
369
  __name: "PlaceDialog",
370
370
  props: {
371
371
  place: { default: null }
372
372
  },
373
373
  setup(i) {
374
- const s = i, { open: u, resolve: y, dismiss: n } = ze(), v = Z(J()), d = xe();
374
+ const s = i, { open: u, resolve: y, dismiss: n } = ze(), v = Z(J()), d = $e();
375
375
  ee();
376
376
  const l = Y("webx-banners"), t = s.place !== null;
377
377
  function P() {
@@ -381,7 +381,7 @@ const aa = /* @__PURE__ */ X({
381
381
  return w === void 0 ? {} : { [w]: s.place.title };
382
382
  }
383
383
  const b = W(s.place?.key ?? ""), g = W(P()), S = W(!1), h = W({}), k = Ne("field");
384
- ke(async () => {
384
+ _e(async () => {
385
385
  await qe(), k.value?.querySelector("input")?.focus();
386
386
  });
387
387
  function O() {
@@ -449,12 +449,12 @@ const aa = /* @__PURE__ */ X({
449
449
  ref_key: "field",
450
450
  ref: k
451
451
  }, [
452
- c(e(ye), {
452
+ c(e(he), {
453
453
  modelValue: g.value,
454
454
  "onUpdate:modelValue": x[0] || (x[0] = (N) => g.value = N),
455
455
  localized: "",
456
456
  "aria-label": e(l)("places.field-title"),
457
- onKeyup: me(B, ["enter"])
457
+ onKeyup: ye(B, ["enter"])
458
458
  }, null, 8, ["modelValue", "aria-label"])
459
459
  ], 512)
460
460
  ]),
@@ -467,13 +467,13 @@ const aa = /* @__PURE__ */ X({
467
467
  required: ""
468
468
  }, {
469
469
  default: p(() => [
470
- c(e(ye), {
470
+ c(e(he), {
471
471
  modelValue: b.value,
472
472
  "onUpdate:modelValue": x[1] || (x[1] = (N) => b.value = N),
473
473
  disabled: t,
474
474
  "aria-label": e(l)("places.field-key"),
475
475
  autocomplete: "off",
476
- onKeyup: me(B, ["enter"])
476
+ onKeyup: ye(B, ["enter"])
477
477
  }, null, 8, ["modelValue", "aria-label"])
478
478
  ]),
479
479
  _: 1
@@ -482,13 +482,13 @@ const aa = /* @__PURE__ */ X({
482
482
  _: 1
483
483
  }, 8, ["open", "title"]));
484
484
  }
485
- }), na = { class: "wx-banner-place" }, la = ["aria-label"], sa = { class: "wx-banner-place__usage" }, ra = {
485
+ }), la = { class: "wx-banner-place" }, sa = ["aria-label"], ra = { class: "wx-banner-place__usage" }, oa = {
486
486
  key: 0,
487
487
  class: "wx-banner-place__layout"
488
- }, oa = { class: "wx-banner-row__thumb" }, ia = ["src"], ca = { class: "wx-banner-row__body" }, ua = { class: "wx-banner-row__title" }, da = {
488
+ }, ia = { class: "wx-banner-row__thumb" }, ca = ["src"], ua = { class: "wx-banner-row__body" }, da = { class: "wx-banner-row__title" }, pa = {
489
489
  key: 0,
490
490
  class: "wx-banner-row__facts"
491
- }, pa = /* @__PURE__ */ X({
491
+ }, va = /* @__PURE__ */ X({
492
492
  name: "WxBannerPlacePane",
493
493
  __name: "PlacePane",
494
494
  props: {
@@ -619,8 +619,8 @@ const aa = /* @__PURE__ */ X({
619
619
  const f = $(
620
620
  () => S.value && !k.value ? [{ key: "new", label: t("banner.new"), icon: "plus", primary: !0, run: N }] : []
621
621
  );
622
- return (o, V) => (m(), M("div", na, [
623
- c(e($e), {
622
+ return (o, V) => (m(), M("div", la, [
623
+ c(e(Pe), {
624
624
  level: 3,
625
625
  back: !u.inline,
626
626
  "back-label": e(t)("places.places"),
@@ -628,7 +628,7 @@ const aa = /* @__PURE__ */ X({
628
628
  actions: f.value,
629
629
  "collapse-below": 420,
630
630
  onBack: V[0] || (V[0] = (_) => y("back"))
631
- }, _e({ _: 2 }, [
631
+ }, xe({ _: 2 }, [
632
632
  i.place.declared ? {
633
633
  name: "title-after",
634
634
  fn: p(() => [
@@ -645,7 +645,7 @@ const aa = /* @__PURE__ */ X({
645
645
  name: "lock",
646
646
  size: "sm"
647
647
  })
648
- ], 8, la)
648
+ ], 8, sa)
649
649
  ]),
650
650
  _: 1
651
651
  }, 8, ["content"])
@@ -653,10 +653,10 @@ const aa = /* @__PURE__ */ X({
653
653
  key: "0"
654
654
  } : void 0
655
655
  ]), 1032, ["back", "back-label", "title", "actions"]),
656
- R("p", sa, [
656
+ R("p", ra, [
657
657
  R("span", null, L(e(t)("places.usage")), 1),
658
658
  R("code", null, "banners('" + L(i.place.key) + "')", 1),
659
- B.value ? (m(), M("span", ra, "· " + L(B.value), 1)) : A("", !0)
659
+ B.value ? (m(), M("span", oa, "· " + L(B.value), 1)) : A("", !0)
660
660
  ]),
661
661
  c(e(Fe), {
662
662
  modelValue: h.value,
@@ -691,22 +691,22 @@ const aa = /* @__PURE__ */ X({
691
691
  class: ae(["wx-banner-row", { "is-off": !_.enabled && !k.value }])
692
692
  }, {
693
693
  default: p(() => [
694
- R("span", oa, [
694
+ R("span", ia, [
695
695
  _.thumb ? (m(), M("img", {
696
696
  key: 0,
697
697
  src: _.thumb,
698
698
  alt: "",
699
699
  loading: "lazy"
700
- }, null, 8, ia)) : (m(), D(e(re), {
700
+ }, null, 8, ca)) : (m(), D(e(re), {
701
701
  key: 1,
702
702
  name: "image",
703
703
  size: "sm",
704
704
  "aria-label": e(t)("banner.no-picture")
705
705
  }, null, 8, ["aria-label"]))
706
706
  ]),
707
- R("span", ca, [
708
- R("span", ua, L(_.title), 1),
709
- _.video || !_.enabled && !k.value ? (m(), M("span", da, [
707
+ R("span", ua, [
708
+ R("span", da, L(_.title), 1),
709
+ _.video || !_.enabled && !k.value ? (m(), M("span", pa, [
710
710
  _.video ? (m(), D(e(ne), {
711
711
  key: 0,
712
712
  size: "sm",
@@ -734,7 +734,7 @@ const aa = /* @__PURE__ */ X({
734
734
  }, 1032, ["to", "class"]))
735
735
  ]),
736
736
  actions: p(({ item: _ }) => [
737
- c(e(Pe), {
737
+ c(e(Be), {
738
738
  actions: I(_),
739
739
  label: _.title
740
740
  }, null, 8, ["actions", "label"])
@@ -750,10 +750,10 @@ const aa = /* @__PURE__ */ X({
750
750
  ], 64))
751
751
  ]));
752
752
  }
753
- }), va = /* @__PURE__ */ de(pa, [["__scopeId", "data-v-23c95ae5"]]), ba = {
753
+ }), ba = /* @__PURE__ */ de(va, [["__scopeId", "data-v-23c95ae5"]]), fa = {
754
754
  key: 2,
755
755
  class: "wx-banners__list"
756
- }, fa = ["onClick"], ma = { class: "wx-banners__name" }, ya = /* @__PURE__ */ X({
756
+ }, ma = ["onClick"], ya = { class: "wx-banners__name" }, ha = /* @__PURE__ */ X({
757
757
  __name: "BannersPage",
758
758
  props: {
759
759
  base: { default: "/banners" }
@@ -761,7 +761,7 @@ const aa = /* @__PURE__ */ X({
761
761
  setup(i) {
762
762
  const s = i, u = J(), y = Z(u), n = ie(), v = ce();
763
763
  ee();
764
- const d = Y("webx-banners"), l = ue(), t = W([]), P = W(!0), b = W(!1), g = W(!0), S = $(() => u.can("banners.manage")), h = He(ta), k = $(
764
+ const d = Y("webx-banners"), l = ue(), t = W([]), P = W(!0), b = W(!1), g = W(!0), S = $(() => u.can("banners.manage")), h = He(na), k = $(
765
765
  () => u.state.manifest?.modules.find((r) => r.id === "banners")?.title ?? d("module.banners")
766
766
  ), O = $(() => {
767
767
  const r = n.query.place;
@@ -836,7 +836,7 @@ const aa = /* @__PURE__ */ X({
836
836
  x();
837
837
  } }] : []
838
838
  );
839
- return (r, I) => (m(), D(e(Ze), {
839
+ return (r, I) => (m(), D(e(ea), {
840
840
  title: k.value,
841
841
  actions: F.value,
842
842
  card: !1
@@ -865,7 +865,7 @@ const aa = /* @__PURE__ */ X({
865
865
  key: 1,
866
866
  title: e(d)("places.empty"),
867
867
  description: e(d)("places.empty-help")
868
- }, null, 8, ["title", "description"])) : (m(), M("ul", ba, [
868
+ }, null, 8, ["title", "description"])) : (m(), M("ul", fa, [
869
869
  (m(!0), M(H, null, Oe(t.value, (f) => (m(), M("li", {
870
870
  key: f.key,
871
871
  class: "wx-banners__row"
@@ -875,8 +875,8 @@ const aa = /* @__PURE__ */ X({
875
875
  class: ae(["wx-banners__open", { "is-current": f.key === O.value }]),
876
876
  onClick: (o) => w(f)
877
877
  }, [
878
- R("span", ma, [
879
- c(e(he), {
878
+ R("span", ya, [
879
+ c(e(we), {
880
880
  truncate: "",
881
881
  weight: "medium"
882
882
  }, {
@@ -899,7 +899,7 @@ const aa = /* @__PURE__ */ X({
899
899
  label: e(d)("places.count")
900
900
  }, null, 8, ["value", "label"])
901
901
  ]),
902
- c(e(he), {
902
+ c(e(we), {
903
903
  size: "sm",
904
904
  tone: "muted",
905
905
  mono: "",
@@ -910,8 +910,8 @@ const aa = /* @__PURE__ */ X({
910
910
  ]),
911
911
  _: 2
912
912
  }, 1024)
913
- ], 10, fa),
914
- K(f).length > 0 ? (m(), D(e(Pe), {
913
+ ], 10, ma),
914
+ K(f).length > 0 ? (m(), D(e(Be), {
915
915
  key: 0,
916
916
  actions: K(f),
917
917
  label: f.title
@@ -920,7 +920,7 @@ const aa = /* @__PURE__ */ X({
920
920
  ]))
921
921
  ]),
922
922
  detail: p(({ inline: f, back: o }) => [
923
- C.value ? (m(), D(va, {
923
+ C.value ? (m(), D(ba, {
924
924
  key: C.value.key,
925
925
  place: C.value,
926
926
  base: s.base,
@@ -944,36 +944,36 @@ const aa = /* @__PURE__ */ X({
944
944
  _: 1
945
945
  }, 8, ["title", "actions"]));
946
946
  }
947
- }), ha = /* @__PURE__ */ de(ya, [["__scopeId", "data-v-615232f0"]]);
948
- function xa(i = {}) {
947
+ }), wa = /* @__PURE__ */ de(ha, [["__scopeId", "data-v-615232f0"]]);
948
+ function $a(i = {}) {
949
949
  const s = i.path ?? "/banners";
950
950
  return {
951
951
  id: "banners",
952
952
  path: s,
953
953
  routes: [
954
954
  // The open place is in the address (`?place=`), so a link to it is a link to its banners.
955
- { path: s, name: "webx.banners", component: ha, props: { base: s } },
955
+ { path: s, name: "webx.banners", component: wa, props: { base: s } },
956
956
  // A banner not written yet: the place it goes to rides in the query (`?place=hero`).
957
957
  {
958
958
  path: `${s}/new`,
959
959
  name: "webx.banners.new",
960
- component: ge,
960
+ component: ke,
961
961
  props: { base: s }
962
962
  },
963
963
  {
964
964
  path: `${s}/:id(\\d+)`,
965
965
  name: "webx.banners.edit",
966
- component: ge,
966
+ component: ke,
967
967
  props: { base: s }
968
968
  }
969
969
  ]
970
970
  };
971
971
  }
972
972
  export {
973
- ge as WxBannerEditorPage,
974
- ha as WxBannersPage,
975
- xa as banners,
976
- ea as bannersMessages,
973
+ ke as WxBannerEditorPage,
974
+ wa as WxBannersPage,
975
+ $a as banners,
976
+ aa as bannersMessages,
977
977
  Z as createBannersApi
978
978
  };
979
979
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/api.ts","../src/messages.ts","../src/i18n.ts","../src/BannerEditorPage.vue","../src/PlaceDialog.vue","../src/PlacePane.vue","../src/BannersPage.vue","../src/module.ts"],"sourcesContent":["import type { AdminContext } from '@webx-ui/module-admin'\nimport type { ScreenModel } from '@webx-ui/schema'\nimport type { BannerDetail, BannerRow, PlaceInput, PlaceRow } from './types'\n\nexport interface BannersApi {\n /** Every place: the declared ones in the config's order, then the own ones by name. */\n places(): Promise<PlaceRow[]>\n createPlace(input: PlaceInput): Promise<PlaceRow>\n /** Only a place of somebody's own, and only its name: the key is what a template asks for. */\n renamePlace(key: string, title: PlaceInput['title']): Promise<PlaceRow>\n /** Only an own place with nothing in it, the bin included — a 422 with the count otherwise. */\n removePlace(key: string): Promise<void>\n\n /** The banners of a place in their order, or its bin. A declared place without a row: `[]`. */\n list(place: string, trashed?: boolean): Promise<BannerRow[]>\n get(id: number): Promise<BannerDetail>\n /**\n * A new banner out of the values of its form, in one request: the form is filled in first and\n * the record made on the first save, so nobody leaves an empty banner in a place.\n */\n create(place: string, values: ScreenModel): Promise<BannerDetail>\n /**\n * Refused with a 422 under the name of the field — `buttons.<n>.link` for a button's row. A\n * `place` other than the one it stands in moves it to the end of that one (decision 14).\n */\n save(id: number, values: ScreenModel, place?: string): Promise<BannerDetail>\n remove(id: number): Promise<void>\n restore(id: number): Promise<BannerRow>\n /** The order of one place, written whole. */\n reorder(place: string, ids: number[]): Promise<void>\n}\n\n/** Everything under `/banners`, below the panel's API path (§5.6). */\nexport function createBannersApi(admin: AdminContext): BannersApi {\n const base = `${admin.apiPath}/banners`\n const place = (key: string) => `${base}/places/${encodeURIComponent(key)}`\n const data = <T>(body: { data: T }): T => body.data\n\n return {\n places: () => admin.http.get<{ data: PlaceRow[] }>(`${base}/places`).then(data),\n createPlace: (input) => admin.http.post<{ data: PlaceRow }>(`${base}/places`, input).then(data),\n renamePlace: (key, title) =>\n admin.http.put<{ data: PlaceRow }>(place(key), { title }).then(data),\n removePlace: (key) => admin.http.delete<void>(place(key)).then(() => undefined),\n\n list: (key, trashed = false) =>\n admin.http\n .get<{ data: BannerRow[] }>(`${place(key)}/banners${trashed ? '?trashed=1' : ''}`)\n .then(data),\n get: (id) => admin.http.get<{ data: BannerDetail }>(`${base}/${id}`).then(data),\n create: (key, values) =>\n admin.http.post<{ data: BannerDetail }>(`${place(key)}/banners`, { values }).then(data),\n save: (id, values, key) =>\n admin.http\n .put<{ data: BannerDetail }>(\n `${base}/${id}`,\n key === undefined ? { values } : { values, place: key },\n )\n .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 pair: a restored banner stays closed.\n restore: (id) => admin.http.post<{ data: BannerRow }>(`${base}/${id}/restore`, {}).then(data),\n reorder: (key, ids) =>\n admin.http.post<void>(`${place(key)}/reorder`, { ids }).then(() => undefined),\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-banners::` and overrides\n * these; `messages.test.ts` keeps the two sets of keys equal. The server's refusals (`errors`)\n * are not here: the panel only ever shows them as they come.\n */\nexport const bannersMessages: Record<string, Messages> = {\n module: {\n banners: 'Banners',\n },\n places: {\n hero: 'Home page slider',\n promo: 'Promo strip',\n list: 'Places',\n new: 'New place',\n title: 'Name',\n key: 'Key',\n 'key-help':\n 'What the site’s template asks for: lowercase Latin letters, digits and dashes, starting with a letter. It cannot be changed later.',\n declared: 'Declared in the configuration',\n 'in-template': \"In a template: banners(':key')\",\n count: 'Banners',\n rename: 'Rename',\n delete: 'Delete',\n 'delete-confirm': 'Delete the place “:title”?',\n 'delete-not-empty': 'Only an empty place can be deleted — the bin counts too.',\n empty: 'No places yet',\n choose: 'Choose a place on the left',\n create: 'Create',\n save: 'Save',\n cancel: 'Cancel',\n created: 'Place created',\n renamed: 'Place renamed',\n deleted: 'Place deleted',\n places: 'Places',\n 'new-title': 'New place',\n 'rename-title': 'Rename the place',\n 'delete-title': 'Delete the place “:place”?',\n 'delete-text':\n 'There is nothing in it. A template that still asks for it gets an empty list, not an error.',\n 'delete-empty-first': 'Delete: empty it first',\n 'delete-blocked': 'Banners in this place, the bin included: :count. Take them out first.',\n 'field-title': 'Name',\n 'field-key': 'Key',\n 'declared-help': 'Cannot be renamed or deleted here: a template asks for it by its key.',\n usage: 'In a template:',\n 'layout-single': 'One banner',\n 'layout-random': 'A random one',\n 'layout-slider': 'Slider',\n 'empty-help': 'The site’s config declares the places its templates ask for.',\n 'choose-help': 'Its banners open beside the list.',\n },\n banner: {\n new: 'New banner',\n trash: 'Bin',\n 'to-trash': 'Move to the bin',\n restore: 'Restore',\n open: 'Open',\n enable: 'Turn on',\n disable: 'Turn off',\n disabled: 'Off',\n video: 'Video',\n empty: 'No banners in this place yet',\n 'trash-empty': 'The bin is empty',\n place: 'Place',\n 'place-help': 'Moving the banner puts it at the end of the new place.',\n back: 'Back to the place',\n save: 'Save',\n saved: 'Banner saved',\n trashed: 'Bin',\n restored: 'Banner restored',\n 'leave-title': 'Leave without saving?',\n 'leave-text': 'The changes to this banner will be lost.',\n leave: 'Leave',\n stay: 'Stay',\n reorder: 'Reorder',\n untitled: 'Untitled banner',\n all: 'Banners',\n 'empty-help': 'The site shows them wherever a template asks for this place.',\n 'empty-bin': 'The bin is empty.',\n 'no-picture': 'No picture',\n order: 'Drag to change the order on the site.',\n 'switched-on': 'Switched on.',\n 'switched-off': 'Switched off.',\n delete: 'Delete',\n 'delete-title': 'Delete “:name”?',\n 'delete-text': 'It goes to the bin and leaves the site. Restored, it comes back to its place.',\n deleted: 'Moved to the bin.',\n 'reorder-failed': 'The new order was not saved.',\n 'save-failed': 'Not saved.',\n cancel: 'Cancel',\n },\n screen: {\n media: 'Pictures and video',\n image: 'Picture',\n 'image-help': 'Required. Shown on wide screens, and as the poster of the video.',\n 'image-mobile': 'Picture for phones',\n 'image-mobile-help': 'Optional. Narrow screens show it instead of the picture above.',\n video: 'Video',\n 'video-help':\n 'Optional. Plays silently over the picture; the picture becomes its poster and what is seen without it.',\n words: 'Words',\n title: 'Title',\n 'title-help':\n 'A banner with words is shown only in the languages they are written in; one without words is shown everywhere.',\n text: 'Text',\n 'text-help': 'Plain text; line breaks are kept.',\n buttons: 'Buttons',\n 'buttons-help': 'Up to three. A button without a label in a language is not shown in it.',\n 'buttons-add': 'Add a button',\n 'buttons-empty': 'No buttons',\n label: 'Label',\n link: 'Link',\n variant: 'Look',\n 'variant-placeholder': 'The first one',\n settings: 'Settings',\n enabled: 'On',\n 'enabled-help': 'Only a banner that is on is shown on the site. A new one starts off.',\n },\n}\n","import { useAdmin } from '@webx-ui/module-admin'\nimport { bannersMessages } 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 useBannersMessages(): void {\n try {\n const { i18n } = useAdmin()\n\n if (!seeded.has(i18n)) {\n i18n.defaults('webx-banners', bannersMessages)\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, watch } from 'vue'\nimport { onBeforeRouteLeave, useRoute, useRouter } from 'vue-router'\nimport {\n confirm,\n localizedValue,\n toast,\n useLocales,\n WxActionBar,\n WxBadge,\n WxButton,\n WxCard,\n WxFormItem,\n WxSelect,\n WxSkeleton,\n type LocalizedValue,\n} from '@webx-ui/core'\nimport {\n useAdmin,\n useErrorText,\n useTranslate,\n WxSaveState,\n WxScreen,\n WxScreenHead,\n type ScreenAction,\n} from '@webx-ui/module-admin'\nimport type { ScreenModel } from '@webx-ui/schema'\nimport { createBannersApi } from './api'\nimport { useBannersMessages } from './i18n'\nimport type { BannerDetail, BannerSummary, PlaceRow } from './types'\n\n/**\n * One banner, on a page of its own: a head, the place, the described screen, and one button.\n *\n * A page and not a pane beside the list, because the form is long — three pictures, the words,\n * up to three buttons — and `banners.form` is a described screen a project adds its own fields\n * to (decision 14). One explicit save, no autosave: a banner has no draft (decision 13), so what\n * is saved is on the site at once, if it is switched on.\n *\n * The place is not a field of the screen (decision 9). The list of places is alive — own places\n * come and go — and the options of a described select are patched on when the server boots, so a\n * live list has nowhere to go there. It is a select of its own above the screen, and rides beside\n * the values: `PUT { values, place }`. Changing it marks the form changed like any field does.\n *\n * A new banner is a form first and a record on the first save: pressing \"New banner\" and walking\n * away should not leave an empty, switched-off row at the end of a place.\n */\ndefineOptions({ name: 'WxBannerEditorPage' })\n\nconst props = withDefaults(defineProps<{ base?: string }>(), { base: '/banners' })\n\nconst admin = useAdmin()\nconst api = createBannersApi(admin)\nconst route = useRoute()\nconst router = useRouter()\nconst locales = useLocales()\nuseBannersMessages()\n\nconst t = useTranslate('webx-banners')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\n/** `null` — a banner not written yet (`/banners/new`). */\nconst id = computed<number | null>(() => {\n const raw = route.params.id\n\n return typeof raw === 'string' && raw !== '' ? Number(raw) : null\n})\n\nconst banner = ref<BannerSummary | null>(null)\nconst values = ref<ScreenModel>({})\nconst snapshot = ref('')\nconst place = ref<string | null>(null)\n/** The place the banner stands in on the server — what \"changed\" and \"back\" are measured by. */\nconst placeSaved = ref<string | null>(null)\nconst places = ref<PlaceRow[]>([])\n\nconst loading = ref(true)\nconst saving = ref(false)\nconst errors = ref<Record<string, string[]>>({})\n\nconst canManage = computed(() => admin.can('banners.manage'))\n\nconst current = computed(() => JSON.stringify(values.value))\nconst dirty = computed(\n () =>\n snapshot.value !== '' && (current.value !== snapshot.value || place.value !== placeSaved.value),\n)\n\nconst state = computed<'saving' | 'unsaved' | 'saved'>(() => {\n if (saving.value) return 'saving'\n\n return dirty.value || id.value === null ? 'unsaved' : 'saved'\n})\n\n/** The title 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 = id.value === null ? t('banner.new') : (banner.value?.title ?? '')\n\n return localizedValue(written, locales.active.value, fallback) || t('banner.untitled')\n})\n\n/** Back to the place the banner stands in, and not to the section's first one. */\nconst back = computed(() => {\n const key = placeSaved.value ?? place.value\n\n return key === null ? props.base : `${props.base}?place=${encodeURIComponent(key)}`\n})\n\nconst section = computed(\n () =>\n admin.state.manifest?.modules.find((module) => module.id === 'banners')?.title ??\n t('module.banners'),\n)\n\nconst placeOptions = computed(() =>\n places.value.map((one) => ({ value: one.key, label: `${one.title} · ${one.key}` })),\n)\n\nconst placeModel = computed<string | number | null>({\n get: () => place.value,\n set: (value) => {\n place.value = value === null || value === undefined ? null : String(value)\n },\n})\n\n/** What the server would answer for a banner with nothing in it yet (§5.6). */\nfunction blank(): ScreenModel {\n return {\n image: null,\n image_mobile: null,\n video: null,\n title: {},\n text: {},\n buttons: [],\n enabled: false,\n }\n}\n\nfunction take(detail: BannerDetail): void {\n banner.value = detail.banner\n values.value = detail.values\n snapshot.value = JSON.stringify(detail.values)\n place.value = detail.banner.place\n placeSaved.value = detail.banner.place\n}\n\nasync function load(): Promise<void> {\n loading.value = true\n\n try {\n const [list, detail] = await Promise.all([\n api.places(),\n id.value === null ? Promise.resolve(null) : api.get(id.value),\n ])\n\n places.value = list\n\n if (detail !== null) {\n take(detail)\n } else {\n const asked = route.query.place\n const key = typeof asked === 'string' && list.some((one) => one.key === asked) ? asked : null\n const fresh = blank()\n\n banner.value = null\n values.value = fresh\n snapshot.value = JSON.stringify(fresh)\n place.value = key ?? list[0]?.key ?? null\n placeSaved.value = place.value\n }\n } catch (error) {\n toast.danger(message(error))\n void router.push(props.base)\n } finally {\n loading.value = false\n }\n}\n\nasync function save(): Promise<void> {\n if (saving.value || !canManage.value || place.value === null) return\n\n saving.value = true\n errors.value = {}\n\n try {\n const made = id.value === null\n const detail = made\n ? await api.create(place.value, values.value)\n : await api.save(\n id.value as number,\n values.value,\n place.value !== placeSaved.value ? place.value : undefined,\n )\n\n take(detail)\n toast.success(t('banner.saved'))\n\n // A record now, with an address of its own; `replace`, so \"back\" does not reopen a blank form.\n if (made) void router.replace(`${props.base}/${detail.banner.id}`)\n } catch (error) {\n const body = (error as { body?: { errors?: Record<string, string[]> } }).body\n\n if (body?.errors) {\n errors.value = body.errors\n toast.danger(t('banner.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 = banner.value\n\n if (!record) return\n\n const agreed = await confirm({\n title: t('banner.delete-title', { name: record.title }),\n message: t('banner.delete-text'),\n confirmText: t('banner.delete'),\n cancelText: t('banner.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n try {\n await api.remove(record.id)\n toast.success(t('banner.deleted'))\n // Nothing left to protect: the record is in the bin, and asking about its unsaved edits on\n // the way out would be asking about a banner that is gone.\n snapshot.value = current.value\n place.value = placeSaved.value\n void router.push(back.value)\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 || id.value === null) void save()\n }\n}\n\n/*\n * `/banners/new` and `/banners/5` are one component on two routes, and the router patches it\n * rather than mounting it again (CLAUDE.md §4) — so a change of banner is watched for. The first\n * save moving the address to the record it just made is not a change: that banner is on screen.\n */\nwatch(id, (next) => {\n if (next !== null && banner.value?.id === next) return\n\n void load()\n})\n\nonMounted(() => {\n void load()\n window.addEventListener('beforeunload', leaveGuard)\n})\n\nonBeforeUnmount(() => {\n window.removeEventListener('beforeunload', leaveGuard)\n})\n\nonBeforeRouteLeave(async () => {\n if (!dirty.value || !canManage.value) return true\n\n return await confirm({\n title: t('banner.leave-title'),\n message: t('banner.leave-text'),\n confirmText: t('banner.leave'),\n cancelText: t('banner.cancel'),\n tone: 'danger',\n })\n})\n\n/* What leads away from the banner. On a phone the head folds it into the ···. */\nconst actions = computed<ScreenAction[]>(() =>\n canManage.value && banner.value !== null\n ? [\n {\n key: 'delete',\n label: t('banner.delete'),\n icon: 'trash',\n danger: true,\n menu: true,\n run: () => void remove(),\n },\n ]\n : [],\n)\n</script>\n\n<template>\n <div class=\"wx-banner-editor\" @keydown=\"onKeydown\">\n <template v-if=\"loading\">\n <!-- Shaped like the screen it stands in for: the head, and a card for what is coming. -->\n <wx-skeleton class=\"wx-banner-editor__ghost\" title :rows=\"1\" />\n <wx-card><wx-skeleton :rows=\"6\" /></wx-card>\n </template>\n\n <template v-else>\n <wx-screen-head divider :back=\"back\" :back-label=\"section\" :title=\"title\" :actions=\"actions\">\n <template v-if=\"banner && !banner.enabled\" #title-after>\n <wx-badge dot>{{ t('banner.disabled') }}</wx-badge>\n </template>\n </wx-screen-head>\n\n <wx-card class=\"wx-banner-editor__place\">\n <wx-form-item\n :label=\"t('banner.place')\"\n :help=\"id === null ? undefined : t('banner.place-help')\"\n :error=\"errors.place?.[0]\"\n required\n >\n <wx-select\n v-model=\"placeModel\"\n :options=\"placeOptions\"\n :disabled=\"!canManage || saving\"\n :aria-label=\"t('banner.place')\"\n />\n </wx-form-item>\n </wx-card>\n\n <wx-screen\n v-model=\"values\"\n name=\"banners.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 && id !== null) || place === null\"\n @click=\"save\"\n >\n {{ t('banner.save') }}\n </wx-button>\n </wx-action-bar>\n </template>\n </div>\n</template>\n\n<style scoped>\n.wx-banner-editor {\n display: flex;\n flex-direction: column;\n gap: var(--wx-gap, var(--wx-space-16));\n container-type: inline-size;\n}\n\n.wx-banner-editor__ghost {\n max-width: 420px;\n}\n\n/* A select as wide as a place's name needs, not as the page: it is one choice, not a text. */\n.wx-banner-editor__place :deep(.wx-form-item) {\n max-width: 420px;\n margin-bottom: 0;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { nextTick, onMounted, ref, useTemplateRef } from 'vue'\nimport {\n useLocales,\n useModal,\n WxButton,\n WxDialog,\n WxFormItem,\n WxInput,\n WxSpace,\n type LocalizedValue,\n} from '@webx-ui/core'\nimport { useAdmin, useTranslate } from '@webx-ui/module-admin'\nimport { createBannersApi } from './api'\nimport { useBannersMessages } from './i18n'\nimport type { PlaceRow } from './types'\n\n/**\n * A place of somebody's own: a name, and a key when it is new (§5.4).\n *\n * The key does not follow the name the way a page's address follows its title: it is typed into\n * `banners('…')` by hand, and a key that quietly became `promo-2` would be a template asking for\n * a place that is not there. For the same reason a rename never touches it.\n *\n * The name is translated, and required in the default language — which is what the list falls\n * back on in a panel open in a language nobody named the place in.\n */\ndefineOptions({ name: 'WxBannerPlaceDialog' })\n\nconst props = withDefaults(defineProps<{ place?: PlaceRow | null }>(), { place: null })\n\nconst { open, resolve, dismiss } = useModal<PlaceRow>()\n\nconst api = createBannersApi(useAdmin())\nconst locales = useLocales()\nuseBannersMessages()\n\nconst t = useTranslate('webx-banners')\n\nconst editing = props.place !== null\n\n/*\n * Every language when the server sent them (`titles`), the one the list shows otherwise — filed\n * under the default language, which is where a place with one name has it.\n */\nfunction initial(): LocalizedValue {\n if (props.place === null) return {}\n if (props.place.titles) return { ...props.place.titles }\n\n const first = locales.list.value[0]?.code\n\n return first === undefined ? {} : { [first]: props.place.title }\n}\n\nconst key = ref(props.place?.key ?? '')\nconst title = ref<LocalizedValue>(initial())\nconst saving = ref(false)\nconst errors = ref<Record<string, string[]>>({})\nconst field = useTemplateRef<HTMLElement>('field')\n\nonMounted(async () => {\n await nextTick()\n field.value?.querySelector('input')?.focus()\n})\n\n/** Something written in at least one language: the server says which one it needs. */\nfunction written(): boolean {\n return Object.values(title.value).some((words) => words.trim() !== '')\n}\n\n/** The first line of a refusal under a field that may be named by language: `title.en`. */\nfunction errorOf(name: string): string | undefined {\n const found = Object.keys(errors.value).find((one) => one === name || one.startsWith(`${name}.`))\n\n return found === undefined ? undefined : errors.value[found]?.[0]\n}\n\nasync function submit(): Promise<void> {\n if (!written() || saving.value) return\n\n saving.value = true\n errors.value = {}\n\n try {\n resolve(\n editing && props.place !== null\n ? await api.renamePlace(props.place.key, title.value)\n : await api.createPlace({ key: key.value.trim(), title: title.value }),\n )\n } catch (error) {\n // Refused under its own field: a key that is taken or spelled in a way no template could\n // write, a name missing in the default language.\n errors.value = (error as { body?: { errors?: Record<string, string[]> } }).body?.errors ?? {}\n } finally {\n saving.value = false\n }\n}\n</script>\n\n<template>\n <wx-dialog\n v-model:open=\"open\"\n :title=\"editing ? t('places.rename-title') : t('places.new-title')\"\n :width=\"440\"\n >\n <wx-form-item :label=\"t('places.field-title')\" :error=\"errorOf('title')\" required>\n <div ref=\"field\">\n <wx-input\n v-model=\"title\"\n localized\n :aria-label=\"t('places.field-title')\"\n @keyup.enter=\"submit\"\n />\n </div>\n </wx-form-item>\n\n <wx-form-item\n :label=\"t('places.field-key')\"\n :error=\"errorOf('key')\"\n :help=\"editing ? undefined : t('places.key-help')\"\n required\n >\n <wx-input\n v-model=\"key\"\n :disabled=\"editing\"\n :aria-label=\"t('places.field-key')\"\n autocomplete=\"off\"\n @keyup.enter=\"submit\"\n />\n </wx-form-item>\n\n <template #footer>\n <wx-space size=\"sm\">\n <wx-button variant=\"outline\" @click=\"dismiss()\">{{ t('places.cancel') }}</wx-button>\n <wx-button\n type=\"primary\"\n :loading=\"saving\"\n :disabled=\"!written() || key.trim() === ''\"\n @click=\"submit\"\n >\n {{ editing ? t('places.save') : t('places.create') }}\n </wx-button>\n </wx-space>\n </template>\n </wx-dialog>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\nimport { useRoute, useRouter } from 'vue-router'\nimport {\n confirm,\n toast,\n WxAlert,\n WxBadge,\n WxEmpty,\n WxIcon,\n WxSegmented,\n WxSkeleton,\n WxSortableList,\n WxTooltip,\n type SegmentedValue,\n} from '@webx-ui/core'\nimport {\n useAdmin,\n useErrorText,\n useTranslate,\n WxRowMenu,\n WxScreenHead,\n type RowAction,\n type ScreenAction,\n} from '@webx-ui/module-admin'\nimport { createBannersApi } from './api'\nimport { useBannersMessages } from './i18n'\nimport type { BannerRow, PlaceRow } from './types'\n\n/**\n * The banners of one place: the right-hand half of the section, and the whole screen on a phone.\n *\n * The order here is the order on the site, and a place is small, so the list is the whole place\n * and a drag writes it whole (`reorder`). The bin is the same list turned over: nothing to drag,\n * and a banner in it opens nothing — it can only come back.\n *\n * The way back is drawn here and not by the pane around it: the drawer a narrow screen opens this\n * in has no close of its own on purpose (CLAUDE.md §4).\n */\ndefineOptions({ name: 'WxBannerPlacePane' })\n\nconst props = withDefaults(\n defineProps<{\n place: PlaceRow\n base: string\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 /** Something moved in or out of the place: the count beside its name is stale. */\n changed: []\n}>()\n\nconst admin = useAdmin()\nconst api = createBannersApi(admin)\nconst route = useRoute()\nconst router = useRouter()\nuseBannersMessages()\n\nconst t = useTranslate('webx-banners')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\nconst rows = ref<BannerRow[]>([])\nconst loading = ref(true)\n\nconst canManage = computed(() => admin.can('banners.manage'))\n\n/* The bin is in the address with the place, so coming back from a banner lands where one was. */\nconst view = computed<SegmentedValue>({\n get: () => (route.query.view === 'trashed' ? 'trashed' : 'live'),\n set: (value) => {\n void router.replace({\n query: { ...route.query, view: value === 'trashed' ? 'trashed' : undefined },\n })\n },\n})\n\nconst inBin = computed(() => view.value === 'trashed')\n\nconst views = computed(() => [\n { value: 'live', label: t('banner.all') },\n { value: 'trashed', label: t('banner.trashed'), icon: 'trash' as const },\n])\n\nconst sortable = computed(() => canManage.value && !inBin.value && rows.value.length > 1)\n\nconst layout = computed(() => {\n const known = ['single', 'random', 'slider']\n\n return known.includes(props.place.layout) ? t(`places.layout-${props.place.layout}`) : ''\n})\n\nasync function load(): Promise<void> {\n loading.value = true\n\n try {\n rows.value = await api.list(props.place.key, inBin.value)\n } catch (error) {\n toast.danger(message(error))\n } finally {\n loading.value = false\n }\n}\n\nwatch(inBin, () => void load())\nvoid load()\n\nfunction edit(row: BannerRow): string {\n return `${props.base}/${row.id}`\n}\n\nfunction add(): void {\n void router.push({ path: `${props.base}/new`, query: { place: props.place.key } })\n}\n\n/**\n * The order on screen, written whole. The list is already in its new order (`WxSortableList`\n * reorders the model before it says so), so a failure has to put it back rather than leave the\n * screen disagreeing with the database.\n */\nasync function reorder(): Promise<void> {\n try {\n await api.reorder(\n props.place.key,\n rows.value.map((row) => row.id),\n )\n } catch (error) {\n toast.danger(message(error, t('banner.reorder-failed')))\n await load()\n }\n}\n\n/*\n * On and off from the list, through the form's own door: there is no switch of its own in the\n * API, and a second way to write a banner would be a second set of rules to keep in step. What\n * the form would refuse — a look the config dropped since — is refused here the same way.\n */\nasync function toggle(row: BannerRow): Promise<void> {\n try {\n const detail = await api.get(row.id)\n\n await api.save(row.id, { ...detail.values, enabled: !row.enabled })\n toast.success(row.enabled ? t('banner.switched-off') : t('banner.switched-on'))\n } catch (error) {\n toast.danger(message(error))\n } finally {\n await load()\n }\n}\n\nasync function remove(row: BannerRow): Promise<void> {\n const agreed = await confirm({\n title: t('banner.delete-title', { name: row.title }),\n message: t('banner.delete-text'),\n confirmText: t('banner.delete'),\n cancelText: t('banner.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n try {\n await api.remove(row.id)\n toast.success(t('banner.deleted'))\n emit('changed')\n } catch (error) {\n toast.danger(message(error))\n } finally {\n await load()\n }\n}\n\nasync function restore(row: BannerRow): Promise<void> {\n try {\n await api.restore(row.id)\n toast.success(t('banner.restored'))\n emit('changed')\n } catch (error) {\n toast.danger(message(error))\n } finally {\n await load()\n }\n}\n\nfunction actionsFor(row: BannerRow): RowAction[] {\n if (inBin.value) {\n return canManage.value\n ? [\n {\n key: 'restore',\n icon: 'refresh',\n label: t('banner.restore'),\n run: () => void restore(row),\n },\n ]\n : []\n }\n\n const open: RowAction = {\n key: 'open',\n icon: 'edit',\n label: t('banner.open'),\n run: () => void router.push(edit(row)),\n }\n\n if (!canManage.value) return [open]\n\n return [\n open,\n {\n key: 'toggle',\n icon: row.enabled ? 'eye-off' : 'eye',\n label: row.enabled ? t('banner.disable') : t('banner.enable'),\n run: () => void toggle(row),\n },\n {\n key: 'delete',\n icon: 'trash',\n label: t('banner.delete'),\n danger: true,\n run: () => void remove(row),\n },\n ]\n}\n\n/* The one thing this pane exists for, in its head; on a narrow pane it stays a button. */\nconst actions = computed<ScreenAction[]>(() =>\n canManage.value && !inBin.value\n ? [{ key: 'new', label: t('banner.new'), icon: 'plus', primary: true, run: add }]\n : [],\n)\n</script>\n\n<template>\n <div class=\"wx-banner-place\">\n <wx-screen-head\n :level=\"3\"\n :back=\"!props.inline\"\n :back-label=\"t('places.places')\"\n :title=\"place.title\"\n :actions=\"actions\"\n :collapse-below=\"420\"\n @back=\"emit('back')\"\n >\n <template v-if=\"place.declared\" #title-after>\n <wx-tooltip :content=\"t('places.declared-help')\">\n <span class=\"wx-banner-place__lock\" tabindex=\"0\" :aria-label=\"t('places.declared')\">\n <wx-icon name=\"lock\" size=\"sm\" />\n </span>\n </wx-tooltip>\n </template>\n </wx-screen-head>\n\n <!--\n How a template asks for this place, where somebody writing one will look for it: the key\n is the whole contract between the panel and the site (decision 7).\n -->\n <p class=\"wx-banner-place__usage\">\n <span>{{ t('places.usage') }}</span>\n <code>banners('{{ place.key }}')</code>\n <span v-if=\"layout\" class=\"wx-banner-place__layout\">· {{ layout }}</span>\n </p>\n\n <wx-segmented\n v-model=\"view\"\n class=\"wx-banner-place__views\"\n size=\"sm\"\n :options=\"views\"\n :aria-label=\"t('banner.all')\"\n />\n\n <wx-skeleton v-if=\"loading && rows.length === 0\" :rows=\"4\" />\n\n <wx-empty\n v-else-if=\"rows.length === 0\"\n :title=\"inBin ? t('banner.empty-bin') : t('banner.empty')\"\n :description=\"inBin ? undefined : t('banner.empty-help')\"\n />\n\n <template v-else>\n <!-- A grip and not the whole row: the row opens a banner, and a row that both opens and\n drags is one where one of the two happens by accident. -->\n <wx-sortable-list\n v-model=\"rows\"\n class=\"wx-banner-place__list\"\n :class=\"{ 'is-loading': loading }\"\n plain\n item-key=\"id\"\n :handle=\"sortable ? 'grip' : 'row'\"\n :item-label=\"(item: BannerRow) => item.title\"\n :disabled=\"!sortable\"\n @move=\"reorder\"\n >\n <template #default=\"{ item }\">\n <component\n :is=\"inBin ? 'div' : 'router-link'\"\n :to=\"inBin ? undefined : edit(item)\"\n class=\"wx-banner-row\"\n :class=\"{ 'is-off': !item.enabled && !inBin }\"\n >\n <span class=\"wx-banner-row__thumb\">\n <img v-if=\"item.thumb\" :src=\"item.thumb\" alt=\"\" loading=\"lazy\" />\n <wx-icon v-else name=\"image\" size=\"sm\" :aria-label=\"t('banner.no-picture')\" />\n </span>\n\n <span class=\"wx-banner-row__body\">\n <span class=\"wx-banner-row__title\">{{ item.title }}</span>\n\n <span v-if=\"item.video || (!item.enabled && !inBin)\" class=\"wx-banner-row__facts\">\n <wx-badge v-if=\"item.video\" size=\"sm\" type=\"info\">{{ t('banner.video') }}</wx-badge>\n <wx-badge v-if=\"!item.enabled && !inBin\" dot size=\"sm\">{{\n t('banner.disabled')\n }}</wx-badge>\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, said once under the list. -->\n <wx-alert\n v-if=\"sortable\"\n class=\"wx-banner-place__note\"\n type=\"info\"\n :description=\"t('banner.order')\"\n />\n </template>\n </div>\n</template>\n\n<style scoped>\n.wx-banner-place {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-12);\n min-width: 0;\n padding: var(--wx-gap, var(--wx-space-16));\n}\n\n.wx-banner-place__lock {\n display: inline-flex;\n color: var(--wx-text-muted);\n}\n\n.wx-banner-place__usage {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--wx-space-6);\n margin: 0;\n color: var(--wx-text-muted);\n font-size: var(--wx-font-size-sm);\n}\n\n.wx-banner-place__usage code {\n padding: 0 var(--wx-space-4);\n border-radius: var(--wx-radius-xs);\n background: var(--wx-bg-subtle);\n color: var(--wx-text-default);\n font-family: var(--wx-font-family-mono);\n /* Copied whole, as somebody writing a template would: a click selects all of it. */\n user-select: all;\n}\n\n.wx-banner-place__views {\n align-self: flex-start;\n}\n\n.wx-banner-place__list.is-loading {\n opacity: 0.6;\n}\n\n.wx-banner-place__list :deep(.wx-sortable-list__row) {\n gap: var(--wx-space-4);\n padding-inline: var(--wx-space-4);\n border-radius: var(--wx-radius-sm);\n}\n\n.wx-banner-place__list :deep(.wx-sortable-list__row:hover) {\n background: var(--wx-bg-subtle);\n}\n\n.wx-banner-row {\n display: flex;\n align-items: center;\n gap: var(--wx-space-12);\n /* The row is the target: a short title should not leave half the line dead. */\n flex: 1 1 auto;\n width: 100%;\n min-width: 0;\n padding-block: var(--wx-space-8);\n color: inherit;\n text-decoration: none;\n}\n\n/* Off is not gone: still here, still in its place, just quieter than what is on the site. */\n.wx-banner-row.is-off .wx-banner-row__thumb,\n.wx-banner-row.is-off .wx-banner-row__title {\n opacity: 0.55;\n}\n\n.wx-banner-row__thumb {\n display: flex;\n flex: none;\n align-items: center;\n justify-content: center;\n width: 80px;\n aspect-ratio: 16 / 9;\n overflow: hidden;\n border-radius: var(--wx-radius-xs);\n background: var(--wx-bg-fill);\n color: var(--wx-text-muted);\n}\n\n.wx-banner-row__thumb img {\n width: 100%;\n height: 100%;\n /* A thumbnail wider than its frame would otherwise hold the row open (CLAUDE.md §4). */\n min-width: 0;\n object-fit: cover;\n}\n\n.wx-banner-row__body {\n display: flex;\n flex: 1 1 auto;\n flex-direction: column;\n gap: var(--wx-space-4);\n min-width: 0;\n}\n\n.wx-banner-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\na.wx-banner-row:hover .wx-banner-row__title {\n color: var(--wx-color-primary);\n}\n\n.wx-banner-row__facts {\n display: flex;\n flex-wrap: wrap;\n gap: var(--wx-space-4);\n}\n\n.wx-banner-place__note {\n margin-top: var(--wx-space-4);\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\nimport { useRoute, useRouter, type LocationQueryRaw } from 'vue-router'\nimport {\n confirm,\n createModal,\n toast,\n WxCard,\n WxEmpty,\n WxIcon,\n WxIndicator,\n WxListDetail,\n WxSkeleton,\n WxText,\n} from '@webx-ui/core'\nimport {\n useAdmin,\n useErrorText,\n useTranslate,\n WxListScreen,\n WxRowMenu,\n type RowAction,\n type ScreenAction,\n} from '@webx-ui/module-admin'\nimport { createBannersApi } from './api'\nimport { useBannersMessages } from './i18n'\nimport PlaceDialog from './PlaceDialog.vue'\nimport PlacePane from './PlacePane.vue'\nimport type { PlaceRow } from './types'\n\n/**\n * The section: the places of the site on the left, the banners of one of them on the right\n * (§5.4) — the shape of the menus, for the same reason: somebody who opens this is arranging one\n * place, not choosing between them, so the first one opens by itself where there is room for it.\n * On a phone it does not: there the same line would raise a panel over a list nobody touched.\n *\n * A declared place is on the list before anything was ever saved into it (decision 1) — its row\n * in the table is made by its first banner — so places are named by key throughout, not by id.\n */\nconst props = withDefaults(defineProps<{ base?: string }>(), { base: '/banners' })\n\nconst admin = useAdmin()\nconst api = createBannersApi(admin)\nconst route = useRoute()\nconst router = useRouter()\nuseBannersMessages()\n\nconst t = useTranslate('webx-banners')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\nconst places = ref<PlaceRow[]>([])\nconst loading = ref(true)\nconst open = ref(false)\nconst detailInline = ref(true)\n\nconst canManage = computed(() => admin.can('banners.manage'))\n\nconst edit = createModal<PlaceRow, { place?: PlaceRow | null }>(PlaceDialog)\n\nconst title = computed(\n () =>\n admin.state.manifest?.modules.find((module) => module.id === 'banners')?.title ??\n t('module.banners'),\n)\n\n/** Which place is open, kept in the address — so \"back\" from a banner lands on its place. */\nconst current = computed<string | null>(() => {\n const asked = route.query.place\n\n return typeof asked === 'string' && asked !== '' ? asked : null\n})\n\nconst chosen = computed(() => places.value.find((place) => place.key === current.value) ?? null)\n\nasync function load(): Promise<void> {\n loading.value = true\n\n try {\n places.value = await api.places()\n } catch (error) {\n toast.danger(message(error))\n } finally {\n loading.value = false\n }\n}\n\nvoid load()\n\n/*\n * The first place, where there is a column to draw it in — only once the width is measured\n * (`detail-inline` says so), so a phone does not get a drawer over a list it never touched.\n */\nwatch([places, chosen, detailInline], () => {\n if (!detailInline.value || places.value.length === 0 || chosen.value !== null) return\n\n void router.replace({ query: { ...route.query, place: places.value[0]!.key } })\n})\n\nwatch(chosen, (place) => {\n if (place !== null && detailInline.value) open.value = true\n})\n\n/* Coming back from a banner on a phone lands on its place, and that is the drawer, open. */\nif (current.value !== null) open.value = true\n\nfunction choose(place: PlaceRow): void {\n const query: LocationQueryRaw = { ...route.query, place: place.key }\n\n // The bin of one place is not the bin of the next.\n delete query.view\n void router.replace({ query })\n open.value = true\n}\n\nasync function add(): Promise<void> {\n const made = await edit({ place: null })\n\n if (!made) return\n\n toast.success(t('places.created'))\n await load()\n choose(made)\n}\n\nasync function rename(place: PlaceRow): Promise<void> {\n const saved = await edit({ place })\n\n if (!saved) return\n\n toast.success(t('places.renamed'))\n await load()\n}\n\nasync function remove(place: PlaceRow): Promise<void> {\n const agreed = await confirm({\n title: t('places.delete-title', { place: place.title }),\n message: t('places.delete-text'),\n confirmText: t('places.delete'),\n cancelText: t('places.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n try {\n await api.removePlace(place.key)\n toast.success(t('places.deleted'))\n\n if (current.value === place.key) {\n const rest = { ...route.query }\n\n delete rest.place\n delete rest.view\n void router.replace({ query: rest })\n open.value = false\n }\n } catch (error) {\n // The one refusal worth its own words: something is still in it — perhaps only in the bin,\n // which the count beside the name does not show (§5.6).\n toast.danger(message(error))\n } finally {\n await load()\n }\n}\n\nfunction actionsFor(place: PlaceRow): RowAction[] {\n if (!canManage.value || place.declared) return []\n\n return [\n { key: 'rename', icon: 'edit', label: t('places.rename'), run: () => void rename(place) },\n {\n key: 'delete',\n icon: 'trash',\n // Said in the line itself, since a switched-off line cannot carry a tooltip: an empty\n // place is the only kind that goes (decision 1).\n label: place.count > 0 ? t('places.delete-empty-first') : t('places.delete'),\n danger: true,\n disabled: place.count > 0,\n run: () => void remove(place),\n },\n ]\n}\n\nconst actions = computed<ScreenAction[]>(() =>\n canManage.value\n ? [{ key: 'new-place', label: t('places.new'), icon: 'plus', run: () => void add() }]\n : [],\n)\n</script>\n\n<template>\n <wx-list-screen :title=\"title\" :actions=\"actions\" :card=\"false\">\n <wx-card class=\"wx-banners\" padding=\"none\">\n <wx-list-detail\n v-model:open=\"open\"\n class=\"wx-banners__panes\"\n :list-width=\"300\"\n :detail-min=\"440\"\n :detail-label=\"t('places.places')\"\n @detail-inline=\"detailInline = $event\"\n >\n <template #list>\n <wx-skeleton\n v-if=\"loading && places.length === 0\"\n class=\"wx-banners__loading\"\n :rows=\"3\"\n />\n\n <wx-empty\n v-else-if=\"places.length === 0\"\n :title=\"t('places.empty')\"\n :description=\"t('places.empty-help')\"\n />\n\n <ul v-else class=\"wx-banners__list\">\n <li v-for=\"place in places\" :key=\"place.key\" class=\"wx-banners__row\">\n <button\n type=\"button\"\n class=\"wx-banners__open\"\n :class=\"{ 'is-current': place.key === current }\"\n @click=\"choose(place)\"\n >\n <span class=\"wx-banners__name\">\n <wx-text truncate weight=\"medium\">{{ place.title }}</wx-text>\n <wx-icon\n v-if=\"place.declared\"\n class=\"wx-banners__lock\"\n name=\"lock\"\n size=\"sm\"\n :aria-label=\"t('places.declared')\"\n />\n <wx-indicator\n class=\"wx-banners__count\"\n type=\"neutral\"\n :value=\"place.count\"\n :label=\"t('places.count')\"\n />\n </span>\n\n <wx-text size=\"sm\" tone=\"muted\" mono truncate>banners('{{ place.key }}')</wx-text>\n </button>\n\n <wx-row-menu\n v-if=\"actionsFor(place).length > 0\"\n :actions=\"actionsFor(place)\"\n :label=\"place.title\"\n />\n </li>\n </ul>\n </template>\n\n <template #detail=\"{ inline, back }\">\n <place-pane\n v-if=\"chosen\"\n :key=\"chosen.key\"\n :place=\"chosen\"\n :base=\"props.base\"\n :inline=\"inline\"\n @back=\"back\"\n @changed=\"load\"\n />\n </template>\n\n <template #empty>\n <wx-empty :title=\"t('places.choose')\" :description=\"t('places.choose-help')\" />\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. `clip`, not\n * `hidden`: `hidden` would make this a scroll container, and everything sticky inside would pin\n * itself to a box that never moves (CLAUDE.md §4).\n */\n.wx-banners > .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-banners__loading {\n padding: var(--wx-space-16);\n}\n\n.wx-banners__list {\n margin: 0;\n padding: var(--wx-space-8);\n list-style: none;\n}\n\n.wx-banners__row {\n display: flex;\n align-items: center;\n gap: var(--wx-space-4);\n margin: 0;\n padding-inline: var(--wx-space-8);\n border-radius: var(--wx-radius-sm);\n}\n\n.wx-banners__row:has(.is-current) {\n background: var(--wx-bg-subtle);\n}\n\n.wx-banners__open {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: var(--wx-space-2);\n flex: 1 1 auto;\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 cursor: pointer;\n}\n\n.wx-banners__open.is-current {\n color: var(--wx-color-primary);\n}\n\n.wx-banners__name {\n display: flex;\n align-items: center;\n gap: var(--wx-space-6);\n min-width: 0;\n max-width: 100%;\n}\n\n.wx-banners__lock {\n flex: none;\n color: var(--wx-text-muted);\n}\n\n.wx-banners__count {\n flex: 0 0 auto;\n}\n</style>\n","import type { AdminModule } from '@webx-ui/module-admin'\nimport BannerEditorPage from './BannerEditorPage.vue'\nimport BannersPage from './BannersPage.vue'\n\nexport interface BannersOptions {\n /** Where the banners live inside the panel. */\n path?: string\n}\n\n/**\n * Banners as a section of the panel (§5.4): one module and one entry, without a group.\n *\n * Two screens. The places and the banners of one of them are a list and its detail, the way the\n * menus are; a banner is a page of its own, because its form is long — three pictures, the words\n * and up to three buttons — and a pane beside a list is no room for it. A section whose server\n * half is not installed never appears — the entry is built from the manifest.\n */\nexport function banners(options: BannersOptions = {}): AdminModule {\n const path = options.path ?? '/banners'\n\n return {\n id: 'banners',\n path,\n routes: [\n // The open place is in the address (`?place=`), so a link to it is a link to its banners.\n { path, name: 'webx.banners', component: BannersPage, props: { base: path } },\n // A banner not written yet: the place it goes to rides in the query (`?place=hero`).\n {\n path: `${path}/new`,\n name: 'webx.banners.new',\n component: BannerEditorPage,\n props: { base: path },\n },\n {\n path: `${path}/:id(\\\\d+)`,\n name: 'webx.banners.edit',\n component: BannerEditorPage,\n props: { base: path },\n },\n ],\n }\n}\n"],"names":["createBannersApi","admin","base","place","key","data","body","input","title","trashed","id","values","ids","bannersMessages","seeded","useBannersMessages","i18n","useAdmin","props","__props","api","route","useRoute","router","useRouter","locales","useLocales","t","useTranslate","message","useErrorText","computed","raw","banner","ref","snapshot","placeSaved","places","loading","saving","errors","canManage","current","dirty","state","written","fallback","localizedValue","back","section","module","placeOptions","one","placeModel","value","blank","take","detail","load","list","asked","fresh","error","toast","save","made","remove","record","confirm","leaveGuard","event","onKeydown","watch","next","onMounted","onBeforeUnmount","onBeforeRouteLeave","actions","_createElementBlock","_Fragment","_createVNode","_unref","WxSkeleton","WxCard","WxScreenHead","WxBadge","WxFormItem","WxSelect","$event","WxScreen","_createBlock","WxActionBar","WxSaveState","WxButton","open","resolve","dismiss","useModal","editing","initial","first","field","useTemplateRef","nextTick","words","errorOf","name","found","submit","WxDialog","WxSpace","_createElementVNode","WxInput","emit","__emit","rows","view","inBin","views","sortable","layout","edit","row","add","reorder","toggle","restore","actionsFor","_openBlock","_hoisted_1","WxTooltip","WxIcon","_hoisted_3","_toDisplayString","_hoisted_4","WxSegmented","WxEmpty","WxSortableList","_normalizeClass","item","_withCtx","_resolveDynamicComponent","_hoisted_5","_hoisted_7","_hoisted_8","_hoisted_9","WxRowMenu","WxAlert","detailInline","createModal","PlaceDialog","chosen","choose","query","rename","rest","WxListScreen","WxListDetail","_cache","_renderList","WxText","_createTextVNode","WxIndicator","inline","PlacePane","banners","options","path","BannersPage","BannerEditorPage"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../src/api.ts","../src/messages.ts","../src/i18n.ts","../src/BannerEditorPage.vue","../src/PlaceDialog.vue","../src/PlacePane.vue","../src/BannersPage.vue","../src/module.ts"],"sourcesContent":["import type { AdminContext } from '@webx-ui/module-admin'\nimport type { ScreenModel } from '@webx-ui/schema'\nimport type { BannerDetail, BannerRow, PlaceInput, PlaceRow } from './types'\n\nexport interface BannersApi {\n /** Every place: the declared ones in the config's order, then the own ones by name. */\n places(): Promise<PlaceRow[]>\n createPlace(input: PlaceInput): Promise<PlaceRow>\n /** Only a place of somebody's own, and only its name: the key is what a template asks for. */\n renamePlace(key: string, title: PlaceInput['title']): Promise<PlaceRow>\n /** Only an own place with nothing in it, the bin included — a 422 with the count otherwise. */\n removePlace(key: string): Promise<void>\n\n /** The banners of a place in their order, or its bin. A declared place without a row: `[]`. */\n list(place: string, trashed?: boolean): Promise<BannerRow[]>\n get(id: number): Promise<BannerDetail>\n /**\n * A new banner out of the values of its form, in one request: the form is filled in first and\n * the record made on the first save, so nobody leaves an empty banner in a place.\n */\n create(place: string, values: ScreenModel): Promise<BannerDetail>\n /**\n * Refused with a 422 under the name of the field — `buttons.<n>.link` for a button's row. A\n * `place` other than the one it stands in moves it to the end of that one (decision 14).\n */\n save(id: number, values: ScreenModel, place?: string): Promise<BannerDetail>\n remove(id: number): Promise<void>\n restore(id: number): Promise<BannerRow>\n /** The order of one place, written whole. */\n reorder(place: string, ids: number[]): Promise<void>\n}\n\n/** Everything under `/banners`, below the panel's API path (§5.6). */\nexport function createBannersApi(admin: AdminContext): BannersApi {\n const base = `${admin.apiPath}/banners`\n const place = (key: string) => `${base}/places/${encodeURIComponent(key)}`\n const data = <T>(body: { data: T }): T => body.data\n\n return {\n places: () => admin.http.get<{ data: PlaceRow[] }>(`${base}/places`).then(data),\n createPlace: (input) => admin.http.post<{ data: PlaceRow }>(`${base}/places`, input).then(data),\n renamePlace: (key, title) =>\n admin.http.put<{ data: PlaceRow }>(place(key), { title }).then(data),\n removePlace: (key) => admin.http.delete<void>(place(key)).then(() => undefined),\n\n list: (key, trashed = false) =>\n admin.http\n .get<{ data: BannerRow[] }>(`${place(key)}/banners${trashed ? '?trashed=1' : ''}`)\n .then(data),\n get: (id) => admin.http.get<{ data: BannerDetail }>(`${base}/${id}`).then(data),\n create: (key, values) =>\n admin.http.post<{ data: BannerDetail }>(`${place(key)}/banners`, { values }).then(data),\n save: (id, values, key) =>\n admin.http\n .put<{ data: BannerDetail }>(\n `${base}/${id}`,\n key === undefined ? { values } : { values, place: key },\n )\n .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 pair: a restored banner stays closed.\n restore: (id) => admin.http.post<{ data: BannerRow }>(`${base}/${id}/restore`, {}).then(data),\n reorder: (key, ids) =>\n admin.http.post<void>(`${place(key)}/reorder`, { ids }).then(() => undefined),\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-banners::` and overrides\n * these; `messages.test.ts` keeps the two sets of keys equal. The server's refusals (`errors`)\n * are not here: the panel only ever shows them as they come.\n */\nexport const bannersMessages: Record<string, Messages> = {\n module: {\n banners: 'Banners',\n },\n places: {\n hero: 'Home page slider',\n promo: 'Promo strip',\n list: 'Places',\n new: 'New place',\n title: 'Name',\n key: 'Key',\n 'key-help':\n 'What the site’s template asks for: lowercase Latin letters, digits and dashes, starting with a letter. It cannot be changed later.',\n declared: 'Declared in the configuration',\n 'in-template': \"In a template: banners(':key')\",\n count: 'Banners',\n rename: 'Rename',\n delete: 'Delete',\n 'delete-confirm': 'Delete the place “:title”?',\n 'delete-not-empty': 'Only an empty place can be deleted — the bin counts too.',\n empty: 'No places yet',\n choose: 'Choose a place on the left',\n create: 'Create',\n save: 'Save',\n cancel: 'Cancel',\n created: 'Place created',\n renamed: 'Place renamed',\n deleted: 'Place deleted',\n places: 'Places',\n 'new-title': 'New place',\n 'rename-title': 'Rename the place',\n 'delete-title': 'Delete the place “:place”?',\n 'delete-text':\n 'There is nothing in it. A template that still asks for it gets an empty list, not an error.',\n 'delete-empty-first': 'Delete: empty it first',\n 'delete-blocked': 'Banners in this place, the bin included: :count. Take them out first.',\n 'field-title': 'Name',\n 'field-key': 'Key',\n 'declared-help': 'Cannot be renamed or deleted here: a template asks for it by its key.',\n usage: 'In a template:',\n 'layout-single': 'One banner',\n 'layout-random': 'A random one',\n 'layout-slider': 'Slider',\n 'empty-help': 'The site’s config declares the places its templates ask for.',\n 'choose-help': 'Its banners open beside the list.',\n },\n banner: {\n new: 'New banner',\n trash: 'Bin',\n 'to-trash': 'Move to the bin',\n restore: 'Restore',\n open: 'Open',\n enable: 'Turn on',\n disable: 'Turn off',\n disabled: 'Off',\n video: 'Video',\n empty: 'No banners in this place yet',\n 'trash-empty': 'The bin is empty',\n place: 'Place',\n 'place-help': 'Moving the banner puts it at the end of the new place.',\n back: 'Back to the place',\n save: 'Save',\n saved: 'Banner saved',\n trashed: 'Bin',\n restored: 'Banner restored',\n 'leave-title': 'Leave without saving?',\n 'leave-text': 'The changes to this banner will be lost.',\n leave: 'Leave',\n stay: 'Stay',\n reorder: 'Reorder',\n untitled: 'Untitled banner',\n all: 'Banners',\n 'empty-help': 'The site shows them wherever a template asks for this place.',\n 'empty-bin': 'The bin is empty.',\n 'no-picture': 'No picture',\n order: 'Drag to change the order on the site.',\n 'switched-on': 'Switched on.',\n 'switched-off': 'Switched off.',\n delete: 'Delete',\n 'delete-title': 'Delete “:name”?',\n 'delete-text': 'It goes to the bin and leaves the site. Restored, it comes back to its place.',\n deleted: 'Moved to the bin.',\n 'reorder-failed': 'The new order was not saved.',\n 'save-failed': 'Not saved.',\n cancel: 'Cancel',\n },\n screen: {\n media: 'Pictures and video',\n image: 'Picture',\n 'image-help': 'Required. Shown on wide screens, and as the poster of the video.',\n 'image-mobile': 'Picture for phones',\n 'image-mobile-help': 'Optional. Narrow screens show it instead of the picture above.',\n video: 'Video',\n 'video-help':\n 'Optional. Plays silently over the picture; the picture becomes its poster and what is seen without it.',\n words: 'Words',\n title: 'Title',\n 'title-help':\n 'A banner with words is shown only in the languages they are written in; one without words is shown everywhere.',\n text: 'Text',\n 'text-help': 'Plain text; line breaks are kept.',\n buttons: 'Buttons',\n 'buttons-help': 'Up to three. A button without a label in a language is not shown in it.',\n 'buttons-add': 'Add a button',\n 'buttons-empty': 'No buttons',\n label: 'Label',\n link: 'Link',\n variant: 'Look',\n 'variant-placeholder': 'The first one',\n settings: 'Settings',\n enabled: 'On',\n 'enabled-help': 'Only a banner that is on is shown on the site. A new one starts off.',\n },\n}\n","import { useAdmin } from '@webx-ui/module-admin'\nimport { bannersMessages } 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 useBannersMessages(): void {\n try {\n const { i18n } = useAdmin()\n\n if (!seeded.has(i18n)) {\n i18n.defaults('webx-banners', bannersMessages)\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, watch } from 'vue'\nimport { onBeforeRouteLeave, useRoute, useRouter } from 'vue-router'\nimport {\n confirm,\n localizedValue,\n toast,\n useLocales,\n WxActionBar,\n WxBadge,\n WxButton,\n WxCard,\n WxFormItem,\n WxSelect,\n WxSkeleton,\n type LocalizedValue,\n} from '@webx-ui/core'\nimport {\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 { createBannersApi } from './api'\nimport { useBannersMessages } from './i18n'\nimport type { BannerDetail, BannerSummary, PlaceRow } from './types'\n\n/**\n * One banner, on a page of its own: a head, the place, the described screen, and one button.\n *\n * A page and not a pane beside the list, because the form is long — three pictures, the words,\n * up to three buttons — and `banners.form` is a described screen a project adds its own fields\n * to (decision 14). One explicit save, no autosave: a banner has no draft (decision 13), so what\n * is saved is on the site at once, if it is switched on.\n *\n * The place is not a field of the screen (decision 9). The list of places is alive — own places\n * come and go — and the options of a described select are patched on when the server boots, so a\n * live list has nowhere to go there. It is a select of its own above the screen, and rides beside\n * the values: `PUT { values, place }`. Changing it marks the form changed like any field does.\n *\n * A new banner is a form first and a record on the first save: pressing \"New banner\" and walking\n * away should not leave an empty, switched-off row at the end of a place.\n */\ndefineOptions({ name: 'WxBannerEditorPage' })\n\nconst props = withDefaults(defineProps<{ base?: string }>(), { base: '/banners' })\n\nconst admin = useAdmin()\nconst api = createBannersApi(admin)\nconst route = useRoute()\nconst router = useRouter()\nconst locales = useLocales()\nuseBannersMessages()\n\nconst t = useTranslate('webx-banners')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\n/** `null` — a banner not written yet (`/banners/new`). */\nconst id = computed<number | null>(() => {\n const raw = route.params.id\n\n return typeof raw === 'string' && raw !== '' ? Number(raw) : null\n})\n\nconst banner = ref<BannerSummary | null>(null)\nconst values = ref<ScreenModel>({})\nconst snapshot = ref('')\nconst place = ref<string | null>(null)\n/** The place the banner stands in on the server — what \"changed\" and \"back\" are measured by. */\nconst placeSaved = ref<string | null>(null)\nconst places = ref<PlaceRow[]>([])\n\nconst loading = ref(true)\nconst saving = ref(false)\nconst errors = ref<Record<string, string[]>>({})\n\nconst canManage = computed(() => admin.can('banners.manage'))\n\nconst current = computed(() => JSON.stringify(values.value))\nconst dirty = computed(\n () =>\n snapshot.value !== '' && (current.value !== snapshot.value || place.value !== placeSaved.value),\n)\n\nconst state = computed<'saving' | 'unsaved' | 'saved'>(() => {\n if (saving.value) return 'saving'\n\n return dirty.value || id.value === null ? 'unsaved' : 'saved'\n})\n\n/** The title 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 = id.value === null ? t('banner.new') : (banner.value?.title ?? '')\n\n return localizedValue(written, locales.active.value, fallback) || t('banner.untitled')\n})\n\n/** Back to the place the banner stands in, and not to the section's first one. */\nconst back = computed(() => {\n const key = placeSaved.value ?? place.value\n\n return key === null ? props.base : `${props.base}?place=${encodeURIComponent(key)}`\n})\n\nconst section = computed(\n () =>\n admin.state.manifest?.modules.find((module) => module.id === 'banners')?.title ??\n t('module.banners'),\n)\n\nconst placeOptions = computed(() =>\n places.value.map((one) => ({ value: one.key, label: `${one.title} · ${one.key}` })),\n)\n\nconst placeModel = computed<string | number | null>({\n get: () => place.value,\n set: (value) => {\n place.value = value === null || value === undefined ? null : String(value)\n },\n})\n\n/** What the server would answer for a banner with nothing in it yet (§5.6). */\nfunction blank(): ScreenModel {\n return {\n image: null,\n image_mobile: null,\n video: null,\n title: {},\n text: {},\n buttons: [],\n enabled: false,\n }\n}\n\nfunction take(detail: BannerDetail): void {\n banner.value = detail.banner\n values.value = detail.values\n snapshot.value = JSON.stringify(detail.values)\n place.value = detail.banner.place\n placeSaved.value = detail.banner.place\n}\n\nasync function load(): Promise<void> {\n loading.value = true\n\n try {\n const [list, detail] = await Promise.all([\n api.places(),\n id.value === null ? Promise.resolve(null) : api.get(id.value),\n ])\n\n places.value = list\n\n if (detail !== null) {\n take(detail)\n } else {\n const asked = route.query.place\n const key = typeof asked === 'string' && list.some((one) => one.key === asked) ? asked : null\n const fresh = blank()\n\n banner.value = null\n values.value = fresh\n snapshot.value = JSON.stringify(fresh)\n place.value = key ?? list[0]?.key ?? null\n placeSaved.value = place.value\n }\n } catch (error) {\n toast.danger(message(error))\n void router.push(props.base)\n } finally {\n loading.value = false\n }\n}\n\nasync function save(): Promise<void> {\n if (saving.value || !canManage.value || place.value === null) return\n\n saving.value = true\n errors.value = {}\n\n try {\n const made = id.value === null\n const detail = made\n ? await api.create(place.value, values.value)\n : await api.save(\n id.value as number,\n values.value,\n place.value !== placeSaved.value ? place.value : undefined,\n )\n\n take(detail)\n toast.success(t('banner.saved'))\n\n // A record now, with an address of its own; `replace`, so \"back\" does not reopen a blank form.\n if (made) void router.replace(`${props.base}/${detail.banner.id}`)\n } catch (error) {\n const body = (error as { body?: { errors?: Record<string, string[]> } }).body\n\n if (body?.errors) {\n errors.value = body.errors\n toast.danger(t('banner.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 = banner.value\n\n if (!record) return\n\n const agreed = await confirm({\n title: t('banner.delete-title', { name: record.title }),\n message: t('banner.delete-text'),\n confirmText: t('banner.delete'),\n cancelText: t('banner.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n try {\n await api.remove(record.id)\n toast.success(t('banner.deleted'))\n // Nothing left to protect: the record is in the bin, and asking about its unsaved edits on\n // the way out would be asking about a banner that is gone.\n snapshot.value = current.value\n place.value = placeSaved.value\n void router.push(back.value)\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 || id.value === 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\n/*\n * `/banners/new` and `/banners/5` are one component on two routes, and the router patches it\n * rather than mounting it again (CLAUDE.md §4) — so a change of banner is watched for. The first\n * save moving the address to the record it just made is not a change: that banner is on screen.\n */\nwatch(id, (next) => {\n if (next !== null && banner.value?.id === next) return\n\n void load()\n})\n\nonMounted(() => {\n void load()\n window.addEventListener('beforeunload', leaveGuard)\n})\n\nonBeforeUnmount(() => {\n window.removeEventListener('beforeunload', leaveGuard)\n})\n\nonBeforeRouteLeave(async () => {\n if (!dirty.value || !canManage.value) return true\n\n return await confirm({\n title: t('banner.leave-title'),\n message: t('banner.leave-text'),\n confirmText: t('banner.leave'),\n cancelText: t('banner.cancel'),\n tone: 'danger',\n })\n})\n\n/* What leads away from the banner. On a phone the head folds it into the ···. */\nconst actions = computed<ScreenAction[]>(() =>\n canManage.value && banner.value !== null\n ? [\n {\n key: 'delete',\n label: t('banner.delete'),\n icon: 'trash',\n danger: true,\n menu: true,\n run: () => void remove(),\n },\n ]\n : [],\n)\n</script>\n\n<template>\n <div class=\"wx-banner-editor\" @keydown=\"onKeydown\">\n <template v-if=\"loading\">\n <!-- Shaped like the screen it stands in for: the head, and a card for what is coming. -->\n <wx-skeleton class=\"wx-banner-editor__ghost\" title :rows=\"1\" />\n <wx-card><wx-skeleton :rows=\"6\" /></wx-card>\n </template>\n\n <template v-else>\n <wx-screen-head divider :back=\"back\" :back-label=\"section\" :title=\"title\" :actions=\"actions\">\n <template v-if=\"banner && !banner.enabled\" #title-after>\n <wx-badge dot>{{ t('banner.disabled') }}</wx-badge>\n </template>\n </wx-screen-head>\n\n <wx-card class=\"wx-banner-editor__place\">\n <wx-form-item\n :label=\"t('banner.place')\"\n :help=\"id === null ? undefined : t('banner.place-help')\"\n :error=\"errors.place?.[0]\"\n required\n >\n <wx-select\n v-model=\"placeModel\"\n :options=\"placeOptions\"\n :disabled=\"!canManage || saving\"\n :aria-label=\"t('banner.place')\"\n />\n </wx-form-item>\n </wx-card>\n\n <wx-screen\n v-model=\"values\"\n name=\"banners.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 && id !== null) || place === null\"\n @click=\"save\"\n >\n {{ t('banner.save') }}\n </wx-button>\n </wx-action-bar>\n </template>\n </div>\n</template>\n\n<style scoped>\n.wx-banner-editor {\n display: flex;\n flex-direction: column;\n gap: var(--wx-gap, var(--wx-space-16));\n container-type: inline-size;\n}\n\n.wx-banner-editor__ghost {\n max-width: 420px;\n}\n\n/* A select as wide as a place's name needs, not as the page: it is one choice, not a text. */\n.wx-banner-editor__place :deep(.wx-form-item) {\n max-width: 420px;\n margin-bottom: 0;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { nextTick, onMounted, ref, useTemplateRef } from 'vue'\nimport {\n useLocales,\n useModal,\n WxButton,\n WxDialog,\n WxFormItem,\n WxInput,\n WxSpace,\n type LocalizedValue,\n} from '@webx-ui/core'\nimport { useAdmin, useTranslate } from '@webx-ui/module-admin'\nimport { createBannersApi } from './api'\nimport { useBannersMessages } from './i18n'\nimport type { PlaceRow } from './types'\n\n/**\n * A place of somebody's own: a name, and a key when it is new (§5.4).\n *\n * The key does not follow the name the way a page's address follows its title: it is typed into\n * `banners('…')` by hand, and a key that quietly became `promo-2` would be a template asking for\n * a place that is not there. For the same reason a rename never touches it.\n *\n * The name is translated, and required in the default language — which is what the list falls\n * back on in a panel open in a language nobody named the place in.\n */\ndefineOptions({ name: 'WxBannerPlaceDialog' })\n\nconst props = withDefaults(defineProps<{ place?: PlaceRow | null }>(), { place: null })\n\nconst { open, resolve, dismiss } = useModal<PlaceRow>()\n\nconst api = createBannersApi(useAdmin())\nconst locales = useLocales()\nuseBannersMessages()\n\nconst t = useTranslate('webx-banners')\n\nconst editing = props.place !== null\n\n/*\n * Every language when the server sent them (`titles`), the one the list shows otherwise — filed\n * under the default language, which is where a place with one name has it.\n */\nfunction initial(): LocalizedValue {\n if (props.place === null) return {}\n if (props.place.titles) return { ...props.place.titles }\n\n const first = locales.list.value[0]?.code\n\n return first === undefined ? {} : { [first]: props.place.title }\n}\n\nconst key = ref(props.place?.key ?? '')\nconst title = ref<LocalizedValue>(initial())\nconst saving = ref(false)\nconst errors = ref<Record<string, string[]>>({})\nconst field = useTemplateRef<HTMLElement>('field')\n\nonMounted(async () => {\n await nextTick()\n field.value?.querySelector('input')?.focus()\n})\n\n/** Something written in at least one language: the server says which one it needs. */\nfunction written(): boolean {\n return Object.values(title.value).some((words) => words.trim() !== '')\n}\n\n/** The first line of a refusal under a field that may be named by language: `title.en`. */\nfunction errorOf(name: string): string | undefined {\n const found = Object.keys(errors.value).find((one) => one === name || one.startsWith(`${name}.`))\n\n return found === undefined ? undefined : errors.value[found]?.[0]\n}\n\nasync function submit(): Promise<void> {\n if (!written() || saving.value) return\n\n saving.value = true\n errors.value = {}\n\n try {\n resolve(\n editing && props.place !== null\n ? await api.renamePlace(props.place.key, title.value)\n : await api.createPlace({ key: key.value.trim(), title: title.value }),\n )\n } catch (error) {\n // Refused under its own field: a key that is taken or spelled in a way no template could\n // write, a name missing in the default language.\n errors.value = (error as { body?: { errors?: Record<string, string[]> } }).body?.errors ?? {}\n } finally {\n saving.value = false\n }\n}\n</script>\n\n<template>\n <wx-dialog\n v-model:open=\"open\"\n :title=\"editing ? t('places.rename-title') : t('places.new-title')\"\n :width=\"440\"\n >\n <wx-form-item :label=\"t('places.field-title')\" :error=\"errorOf('title')\" required>\n <div ref=\"field\">\n <wx-input\n v-model=\"title\"\n localized\n :aria-label=\"t('places.field-title')\"\n @keyup.enter=\"submit\"\n />\n </div>\n </wx-form-item>\n\n <wx-form-item\n :label=\"t('places.field-key')\"\n :error=\"errorOf('key')\"\n :help=\"editing ? undefined : t('places.key-help')\"\n required\n >\n <wx-input\n v-model=\"key\"\n :disabled=\"editing\"\n :aria-label=\"t('places.field-key')\"\n autocomplete=\"off\"\n @keyup.enter=\"submit\"\n />\n </wx-form-item>\n\n <template #footer>\n <wx-space size=\"sm\">\n <wx-button variant=\"outline\" @click=\"dismiss()\">{{ t('places.cancel') }}</wx-button>\n <wx-button\n type=\"primary\"\n :loading=\"saving\"\n :disabled=\"!written() || key.trim() === ''\"\n @click=\"submit\"\n >\n {{ editing ? t('places.save') : t('places.create') }}\n </wx-button>\n </wx-space>\n </template>\n </wx-dialog>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\nimport { useRoute, useRouter } from 'vue-router'\nimport {\n confirm,\n toast,\n WxAlert,\n WxBadge,\n WxEmpty,\n WxIcon,\n WxSegmented,\n WxSkeleton,\n WxSortableList,\n WxTooltip,\n type SegmentedValue,\n} from '@webx-ui/core'\nimport {\n useAdmin,\n useErrorText,\n useTranslate,\n WxRowMenu,\n WxScreenHead,\n type RowAction,\n type ScreenAction,\n} from '@webx-ui/module-admin'\nimport { createBannersApi } from './api'\nimport { useBannersMessages } from './i18n'\nimport type { BannerRow, PlaceRow } from './types'\n\n/**\n * The banners of one place: the right-hand half of the section, and the whole screen on a phone.\n *\n * The order here is the order on the site, and a place is small, so the list is the whole place\n * and a drag writes it whole (`reorder`). The bin is the same list turned over: nothing to drag,\n * and a banner in it opens nothing — it can only come back.\n *\n * The way back is drawn here and not by the pane around it: the drawer a narrow screen opens this\n * in has no close of its own on purpose (CLAUDE.md §4).\n */\ndefineOptions({ name: 'WxBannerPlacePane' })\n\nconst props = withDefaults(\n defineProps<{\n place: PlaceRow\n base: string\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 /** Something moved in or out of the place: the count beside its name is stale. */\n changed: []\n}>()\n\nconst admin = useAdmin()\nconst api = createBannersApi(admin)\nconst route = useRoute()\nconst router = useRouter()\nuseBannersMessages()\n\nconst t = useTranslate('webx-banners')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\nconst rows = ref<BannerRow[]>([])\nconst loading = ref(true)\n\nconst canManage = computed(() => admin.can('banners.manage'))\n\n/* The bin is in the address with the place, so coming back from a banner lands where one was. */\nconst view = computed<SegmentedValue>({\n get: () => (route.query.view === 'trashed' ? 'trashed' : 'live'),\n set: (value) => {\n void router.replace({\n query: { ...route.query, view: value === 'trashed' ? 'trashed' : undefined },\n })\n },\n})\n\nconst inBin = computed(() => view.value === 'trashed')\n\nconst views = computed(() => [\n { value: 'live', label: t('banner.all') },\n { value: 'trashed', label: t('banner.trashed'), icon: 'trash' as const },\n])\n\nconst sortable = computed(() => canManage.value && !inBin.value && rows.value.length > 1)\n\nconst layout = computed(() => {\n const known = ['single', 'random', 'slider']\n\n return known.includes(props.place.layout) ? t(`places.layout-${props.place.layout}`) : ''\n})\n\nasync function load(): Promise<void> {\n loading.value = true\n\n try {\n rows.value = await api.list(props.place.key, inBin.value)\n } catch (error) {\n toast.danger(message(error))\n } finally {\n loading.value = false\n }\n}\n\nwatch(inBin, () => void load())\nvoid load()\n\nfunction edit(row: BannerRow): string {\n return `${props.base}/${row.id}`\n}\n\nfunction add(): void {\n void router.push({ path: `${props.base}/new`, query: { place: props.place.key } })\n}\n\n/**\n * The order on screen, written whole. The list is already in its new order (`WxSortableList`\n * reorders the model before it says so), so a failure has to put it back rather than leave the\n * screen disagreeing with the database.\n */\nasync function reorder(): Promise<void> {\n try {\n await api.reorder(\n props.place.key,\n rows.value.map((row) => row.id),\n )\n } catch (error) {\n toast.danger(message(error, t('banner.reorder-failed')))\n await load()\n }\n}\n\n/*\n * On and off from the list, through the form's own door: there is no switch of its own in the\n * API, and a second way to write a banner would be a second set of rules to keep in step. What\n * the form would refuse — a look the config dropped since — is refused here the same way.\n */\nasync function toggle(row: BannerRow): Promise<void> {\n try {\n const detail = await api.get(row.id)\n\n await api.save(row.id, { ...detail.values, enabled: !row.enabled })\n toast.success(row.enabled ? t('banner.switched-off') : t('banner.switched-on'))\n } catch (error) {\n toast.danger(message(error))\n } finally {\n await load()\n }\n}\n\nasync function remove(row: BannerRow): Promise<void> {\n const agreed = await confirm({\n title: t('banner.delete-title', { name: row.title }),\n message: t('banner.delete-text'),\n confirmText: t('banner.delete'),\n cancelText: t('banner.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n try {\n await api.remove(row.id)\n toast.success(t('banner.deleted'))\n emit('changed')\n } catch (error) {\n toast.danger(message(error))\n } finally {\n await load()\n }\n}\n\nasync function restore(row: BannerRow): Promise<void> {\n try {\n await api.restore(row.id)\n toast.success(t('banner.restored'))\n emit('changed')\n } catch (error) {\n toast.danger(message(error))\n } finally {\n await load()\n }\n}\n\nfunction actionsFor(row: BannerRow): RowAction[] {\n if (inBin.value) {\n return canManage.value\n ? [\n {\n key: 'restore',\n icon: 'refresh',\n label: t('banner.restore'),\n run: () => void restore(row),\n },\n ]\n : []\n }\n\n const open: RowAction = {\n key: 'open',\n icon: 'edit',\n label: t('banner.open'),\n run: () => void router.push(edit(row)),\n }\n\n if (!canManage.value) return [open]\n\n return [\n open,\n {\n key: 'toggle',\n icon: row.enabled ? 'eye-off' : 'eye',\n label: row.enabled ? t('banner.disable') : t('banner.enable'),\n run: () => void toggle(row),\n },\n {\n key: 'delete',\n icon: 'trash',\n label: t('banner.delete'),\n danger: true,\n run: () => void remove(row),\n },\n ]\n}\n\n/* The one thing this pane exists for, in its head; on a narrow pane it stays a button. */\nconst actions = computed<ScreenAction[]>(() =>\n canManage.value && !inBin.value\n ? [{ key: 'new', label: t('banner.new'), icon: 'plus', primary: true, run: add }]\n : [],\n)\n</script>\n\n<template>\n <div class=\"wx-banner-place\">\n <wx-screen-head\n :level=\"3\"\n :back=\"!props.inline\"\n :back-label=\"t('places.places')\"\n :title=\"place.title\"\n :actions=\"actions\"\n :collapse-below=\"420\"\n @back=\"emit('back')\"\n >\n <template v-if=\"place.declared\" #title-after>\n <wx-tooltip :content=\"t('places.declared-help')\">\n <span class=\"wx-banner-place__lock\" tabindex=\"0\" :aria-label=\"t('places.declared')\">\n <wx-icon name=\"lock\" size=\"sm\" />\n </span>\n </wx-tooltip>\n </template>\n </wx-screen-head>\n\n <!--\n How a template asks for this place, where somebody writing one will look for it: the key\n is the whole contract between the panel and the site (decision 7).\n -->\n <p class=\"wx-banner-place__usage\">\n <span>{{ t('places.usage') }}</span>\n <code>banners('{{ place.key }}')</code>\n <span v-if=\"layout\" class=\"wx-banner-place__layout\">· {{ layout }}</span>\n </p>\n\n <wx-segmented\n v-model=\"view\"\n class=\"wx-banner-place__views\"\n size=\"sm\"\n :options=\"views\"\n :aria-label=\"t('banner.all')\"\n />\n\n <wx-skeleton v-if=\"loading && rows.length === 0\" :rows=\"4\" />\n\n <wx-empty\n v-else-if=\"rows.length === 0\"\n :title=\"inBin ? t('banner.empty-bin') : t('banner.empty')\"\n :description=\"inBin ? undefined : t('banner.empty-help')\"\n />\n\n <template v-else>\n <!-- A grip and not the whole row: the row opens a banner, and a row that both opens and\n drags is one where one of the two happens by accident. -->\n <wx-sortable-list\n v-model=\"rows\"\n class=\"wx-banner-place__list\"\n :class=\"{ 'is-loading': loading }\"\n plain\n item-key=\"id\"\n :handle=\"sortable ? 'grip' : 'row'\"\n :item-label=\"(item: BannerRow) => item.title\"\n :disabled=\"!sortable\"\n @move=\"reorder\"\n >\n <template #default=\"{ item }\">\n <component\n :is=\"inBin ? 'div' : 'router-link'\"\n :to=\"inBin ? undefined : edit(item)\"\n class=\"wx-banner-row\"\n :class=\"{ 'is-off': !item.enabled && !inBin }\"\n >\n <span class=\"wx-banner-row__thumb\">\n <img v-if=\"item.thumb\" :src=\"item.thumb\" alt=\"\" loading=\"lazy\" />\n <wx-icon v-else name=\"image\" size=\"sm\" :aria-label=\"t('banner.no-picture')\" />\n </span>\n\n <span class=\"wx-banner-row__body\">\n <span class=\"wx-banner-row__title\">{{ item.title }}</span>\n\n <span v-if=\"item.video || (!item.enabled && !inBin)\" class=\"wx-banner-row__facts\">\n <wx-badge v-if=\"item.video\" size=\"sm\" type=\"info\">{{ t('banner.video') }}</wx-badge>\n <wx-badge v-if=\"!item.enabled && !inBin\" dot size=\"sm\">{{\n t('banner.disabled')\n }}</wx-badge>\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, said once under the list. -->\n <wx-alert\n v-if=\"sortable\"\n class=\"wx-banner-place__note\"\n type=\"info\"\n :description=\"t('banner.order')\"\n />\n </template>\n </div>\n</template>\n\n<style scoped>\n.wx-banner-place {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-12);\n min-width: 0;\n padding: var(--wx-gap, var(--wx-space-16));\n}\n\n.wx-banner-place__lock {\n display: inline-flex;\n color: var(--wx-text-muted);\n}\n\n.wx-banner-place__usage {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--wx-space-6);\n margin: 0;\n color: var(--wx-text-muted);\n font-size: var(--wx-font-size-sm);\n}\n\n.wx-banner-place__usage code {\n padding: 0 var(--wx-space-4);\n border-radius: var(--wx-radius-xs);\n background: var(--wx-bg-subtle);\n color: var(--wx-text-default);\n font-family: var(--wx-font-family-mono);\n /* Copied whole, as somebody writing a template would: a click selects all of it. */\n user-select: all;\n}\n\n.wx-banner-place__views {\n align-self: flex-start;\n}\n\n.wx-banner-place__list.is-loading {\n opacity: 0.6;\n}\n\n.wx-banner-place__list :deep(.wx-sortable-list__row) {\n gap: var(--wx-space-4);\n padding-inline: var(--wx-space-4);\n border-radius: var(--wx-radius-sm);\n}\n\n.wx-banner-place__list :deep(.wx-sortable-list__row:hover) {\n background: var(--wx-bg-subtle);\n}\n\n.wx-banner-row {\n display: flex;\n align-items: center;\n gap: var(--wx-space-12);\n /* The row is the target: a short title should not leave half the line dead. */\n flex: 1 1 auto;\n width: 100%;\n min-width: 0;\n padding-block: var(--wx-space-8);\n color: inherit;\n text-decoration: none;\n}\n\n/* Off is not gone: still here, still in its place, just quieter than what is on the site. */\n.wx-banner-row.is-off .wx-banner-row__thumb,\n.wx-banner-row.is-off .wx-banner-row__title {\n opacity: 0.55;\n}\n\n.wx-banner-row__thumb {\n display: flex;\n flex: none;\n align-items: center;\n justify-content: center;\n width: 80px;\n aspect-ratio: 16 / 9;\n overflow: hidden;\n border-radius: var(--wx-radius-xs);\n background: var(--wx-bg-fill);\n color: var(--wx-text-muted);\n}\n\n.wx-banner-row__thumb img {\n width: 100%;\n height: 100%;\n /* A thumbnail wider than its frame would otherwise hold the row open (CLAUDE.md §4). */\n min-width: 0;\n object-fit: cover;\n}\n\n.wx-banner-row__body {\n display: flex;\n flex: 1 1 auto;\n flex-direction: column;\n gap: var(--wx-space-4);\n min-width: 0;\n}\n\n.wx-banner-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\na.wx-banner-row:hover .wx-banner-row__title {\n color: var(--wx-color-primary);\n}\n\n.wx-banner-row__facts {\n display: flex;\n flex-wrap: wrap;\n gap: var(--wx-space-4);\n}\n\n.wx-banner-place__note {\n margin-top: var(--wx-space-4);\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\nimport { useRoute, useRouter, type LocationQueryRaw } from 'vue-router'\nimport {\n confirm,\n createModal,\n toast,\n WxCard,\n WxEmpty,\n WxIcon,\n WxIndicator,\n WxListDetail,\n WxSkeleton,\n WxText,\n} from '@webx-ui/core'\nimport {\n useAdmin,\n useErrorText,\n useTranslate,\n WxListScreen,\n WxRowMenu,\n type RowAction,\n type ScreenAction,\n} from '@webx-ui/module-admin'\nimport { createBannersApi } from './api'\nimport { useBannersMessages } from './i18n'\nimport PlaceDialog from './PlaceDialog.vue'\nimport PlacePane from './PlacePane.vue'\nimport type { PlaceRow } from './types'\n\n/**\n * The section: the places of the site on the left, the banners of one of them on the right\n * (§5.4) — the shape of the menus, for the same reason: somebody who opens this is arranging one\n * place, not choosing between them, so the first one opens by itself where there is room for it.\n * On a phone it does not: there the same line would raise a panel over a list nobody touched.\n *\n * A declared place is on the list before anything was ever saved into it (decision 1) — its row\n * in the table is made by its first banner — so places are named by key throughout, not by id.\n */\nconst props = withDefaults(defineProps<{ base?: string }>(), { base: '/banners' })\n\nconst admin = useAdmin()\nconst api = createBannersApi(admin)\nconst route = useRoute()\nconst router = useRouter()\nuseBannersMessages()\n\nconst t = useTranslate('webx-banners')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\nconst places = ref<PlaceRow[]>([])\nconst loading = ref(true)\nconst open = ref(false)\nconst detailInline = ref(true)\n\nconst canManage = computed(() => admin.can('banners.manage'))\n\nconst edit = createModal<PlaceRow, { place?: PlaceRow | null }>(PlaceDialog)\n\nconst title = computed(\n () =>\n admin.state.manifest?.modules.find((module) => module.id === 'banners')?.title ??\n t('module.banners'),\n)\n\n/** Which place is open, kept in the address — so \"back\" from a banner lands on its place. */\nconst current = computed<string | null>(() => {\n const asked = route.query.place\n\n return typeof asked === 'string' && asked !== '' ? asked : null\n})\n\nconst chosen = computed(() => places.value.find((place) => place.key === current.value) ?? null)\n\nasync function load(): Promise<void> {\n loading.value = true\n\n try {\n places.value = await api.places()\n } catch (error) {\n toast.danger(message(error))\n } finally {\n loading.value = false\n }\n}\n\nvoid load()\n\n/*\n * The first place, where there is a column to draw it in — only once the width is measured\n * (`detail-inline` says so), so a phone does not get a drawer over a list it never touched.\n */\nwatch([places, chosen, detailInline], () => {\n if (!detailInline.value || places.value.length === 0 || chosen.value !== null) return\n\n void router.replace({ query: { ...route.query, place: places.value[0]!.key } })\n})\n\nwatch(chosen, (place) => {\n if (place !== null && detailInline.value) open.value = true\n})\n\n/* Coming back from a banner on a phone lands on its place, and that is the drawer, open. */\nif (current.value !== null) open.value = true\n\nfunction choose(place: PlaceRow): void {\n const query: LocationQueryRaw = { ...route.query, place: place.key }\n\n // The bin of one place is not the bin of the next.\n delete query.view\n void router.replace({ query })\n open.value = true\n}\n\nasync function add(): Promise<void> {\n const made = await edit({ place: null })\n\n if (!made) return\n\n toast.success(t('places.created'))\n await load()\n choose(made)\n}\n\nasync function rename(place: PlaceRow): Promise<void> {\n const saved = await edit({ place })\n\n if (!saved) return\n\n toast.success(t('places.renamed'))\n await load()\n}\n\nasync function remove(place: PlaceRow): Promise<void> {\n const agreed = await confirm({\n title: t('places.delete-title', { place: place.title }),\n message: t('places.delete-text'),\n confirmText: t('places.delete'),\n cancelText: t('places.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n try {\n await api.removePlace(place.key)\n toast.success(t('places.deleted'))\n\n if (current.value === place.key) {\n const rest = { ...route.query }\n\n delete rest.place\n delete rest.view\n void router.replace({ query: rest })\n open.value = false\n }\n } catch (error) {\n // The one refusal worth its own words: something is still in it — perhaps only in the bin,\n // which the count beside the name does not show (§5.6).\n toast.danger(message(error))\n } finally {\n await load()\n }\n}\n\nfunction actionsFor(place: PlaceRow): RowAction[] {\n if (!canManage.value || place.declared) return []\n\n return [\n { key: 'rename', icon: 'edit', label: t('places.rename'), run: () => void rename(place) },\n {\n key: 'delete',\n icon: 'trash',\n // Said in the line itself, since a switched-off line cannot carry a tooltip: an empty\n // place is the only kind that goes (decision 1).\n label: place.count > 0 ? t('places.delete-empty-first') : t('places.delete'),\n danger: true,\n disabled: place.count > 0,\n run: () => void remove(place),\n },\n ]\n}\n\nconst actions = computed<ScreenAction[]>(() =>\n canManage.value\n ? [{ key: 'new-place', label: t('places.new'), icon: 'plus', run: () => void add() }]\n : [],\n)\n</script>\n\n<template>\n <wx-list-screen :title=\"title\" :actions=\"actions\" :card=\"false\">\n <wx-card class=\"wx-banners\" padding=\"none\">\n <wx-list-detail\n v-model:open=\"open\"\n class=\"wx-banners__panes\"\n :list-width=\"300\"\n :detail-min=\"440\"\n :detail-label=\"t('places.places')\"\n @detail-inline=\"detailInline = $event\"\n >\n <template #list>\n <wx-skeleton\n v-if=\"loading && places.length === 0\"\n class=\"wx-banners__loading\"\n :rows=\"3\"\n />\n\n <wx-empty\n v-else-if=\"places.length === 0\"\n :title=\"t('places.empty')\"\n :description=\"t('places.empty-help')\"\n />\n\n <ul v-else class=\"wx-banners__list\">\n <li v-for=\"place in places\" :key=\"place.key\" class=\"wx-banners__row\">\n <button\n type=\"button\"\n class=\"wx-banners__open\"\n :class=\"{ 'is-current': place.key === current }\"\n @click=\"choose(place)\"\n >\n <span class=\"wx-banners__name\">\n <wx-text truncate weight=\"medium\">{{ place.title }}</wx-text>\n <wx-icon\n v-if=\"place.declared\"\n class=\"wx-banners__lock\"\n name=\"lock\"\n size=\"sm\"\n :aria-label=\"t('places.declared')\"\n />\n <wx-indicator\n class=\"wx-banners__count\"\n type=\"neutral\"\n :value=\"place.count\"\n :label=\"t('places.count')\"\n />\n </span>\n\n <wx-text size=\"sm\" tone=\"muted\" mono truncate>banners('{{ place.key }}')</wx-text>\n </button>\n\n <wx-row-menu\n v-if=\"actionsFor(place).length > 0\"\n :actions=\"actionsFor(place)\"\n :label=\"place.title\"\n />\n </li>\n </ul>\n </template>\n\n <template #detail=\"{ inline, back }\">\n <place-pane\n v-if=\"chosen\"\n :key=\"chosen.key\"\n :place=\"chosen\"\n :base=\"props.base\"\n :inline=\"inline\"\n @back=\"back\"\n @changed=\"load\"\n />\n </template>\n\n <template #empty>\n <wx-empty :title=\"t('places.choose')\" :description=\"t('places.choose-help')\" />\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. `clip`, not\n * `hidden`: `hidden` would make this a scroll container, and everything sticky inside would pin\n * itself to a box that never moves (CLAUDE.md §4).\n */\n.wx-banners > .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-banners__loading {\n padding: var(--wx-space-16);\n}\n\n.wx-banners__list {\n margin: 0;\n padding: var(--wx-space-8);\n list-style: none;\n}\n\n.wx-banners__row {\n display: flex;\n align-items: center;\n gap: var(--wx-space-4);\n margin: 0;\n padding-inline: var(--wx-space-8);\n border-radius: var(--wx-radius-sm);\n}\n\n.wx-banners__row:has(.is-current) {\n background: var(--wx-bg-subtle);\n}\n\n.wx-banners__open {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: var(--wx-space-2);\n flex: 1 1 auto;\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 cursor: pointer;\n}\n\n.wx-banners__open.is-current {\n color: var(--wx-color-primary);\n}\n\n.wx-banners__name {\n display: flex;\n align-items: center;\n gap: var(--wx-space-6);\n min-width: 0;\n max-width: 100%;\n}\n\n.wx-banners__lock {\n flex: none;\n color: var(--wx-text-muted);\n}\n\n.wx-banners__count {\n flex: 0 0 auto;\n}\n</style>\n","import type { AdminModule } from '@webx-ui/module-admin'\nimport BannerEditorPage from './BannerEditorPage.vue'\nimport BannersPage from './BannersPage.vue'\n\nexport interface BannersOptions {\n /** Where the banners live inside the panel. */\n path?: string\n}\n\n/**\n * Banners as a section of the panel (§5.4): one module and one entry, without a group.\n *\n * Two screens. The places and the banners of one of them are a list and its detail, the way the\n * menus are; a banner is a page of its own, because its form is long — three pictures, the words\n * and up to three buttons — and a pane beside a list is no room for it. A section whose server\n * half is not installed never appears — the entry is built from the manifest.\n */\nexport function banners(options: BannersOptions = {}): AdminModule {\n const path = options.path ?? '/banners'\n\n return {\n id: 'banners',\n path,\n routes: [\n // The open place is in the address (`?place=`), so a link to it is a link to its banners.\n { path, name: 'webx.banners', component: BannersPage, props: { base: path } },\n // A banner not written yet: the place it goes to rides in the query (`?place=hero`).\n {\n path: `${path}/new`,\n name: 'webx.banners.new',\n component: BannerEditorPage,\n props: { base: path },\n },\n {\n path: `${path}/:id(\\\\d+)`,\n name: 'webx.banners.edit',\n component: BannerEditorPage,\n props: { base: path },\n },\n ],\n }\n}\n"],"names":["createBannersApi","admin","base","place","key","data","body","input","title","trashed","id","values","ids","bannersMessages","seeded","useBannersMessages","i18n","useAdmin","props","__props","api","route","useRoute","router","useRouter","locales","useLocales","t","useTranslate","message","useErrorText","computed","raw","banner","ref","snapshot","placeSaved","places","loading","saving","errors","canManage","current","dirty","state","written","fallback","localizedValue","back","section","module","placeOptions","one","placeModel","value","blank","take","detail","load","list","asked","fresh","error","toast","save","made","remove","record","confirm","leaveGuard","event","onKeydown","useBodyKeys","watch","next","onMounted","onBeforeUnmount","onBeforeRouteLeave","actions","_createElementBlock","_Fragment","_createVNode","_unref","WxSkeleton","WxCard","WxScreenHead","WxBadge","WxFormItem","WxSelect","$event","WxScreen","_createBlock","WxActionBar","WxSaveState","WxButton","open","resolve","dismiss","useModal","editing","initial","first","field","useTemplateRef","nextTick","words","errorOf","name","found","submit","WxDialog","WxSpace","_createElementVNode","WxInput","emit","__emit","rows","view","inBin","views","sortable","layout","edit","row","add","reorder","toggle","restore","actionsFor","_openBlock","_hoisted_1","WxTooltip","WxIcon","_hoisted_3","_toDisplayString","_hoisted_4","WxSegmented","WxEmpty","WxSortableList","_normalizeClass","item","_withCtx","_resolveDynamicComponent","_hoisted_5","_hoisted_7","_hoisted_8","_hoisted_9","WxRowMenu","WxAlert","detailInline","createModal","PlaceDialog","chosen","choose","query","rename","rest","WxListScreen","WxListDetail","_cache","_renderList","WxText","_createTextVNode","WxIndicator","inline","PlacePane","banners","options","path","BannersPage","BannerEditorPage"],"mappings":"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"}
package/dist/style.css CHANGED
@@ -1 +1 @@
1
- .wx-banner-editor[data-v-038085a4]{display:flex;flex-direction:column;gap:var(--wx-gap, var(--wx-space-16));container-type:inline-size}.wx-banner-editor__ghost[data-v-038085a4]{max-width:420px}.wx-banner-editor__place[data-v-038085a4] .wx-form-item{max-width:420px;margin-bottom:0}.wx-banner-place[data-v-23c95ae5]{display:flex;flex-direction:column;gap:var(--wx-space-12);min-width:0;padding:var(--wx-gap, var(--wx-space-16))}.wx-banner-place__lock[data-v-23c95ae5]{display:inline-flex;color:var(--wx-text-muted)}.wx-banner-place__usage[data-v-23c95ae5]{display:flex;flex-wrap:wrap;align-items:baseline;gap:var(--wx-space-6);margin:0;color:var(--wx-text-muted);font-size:var(--wx-font-size-sm)}.wx-banner-place__usage code[data-v-23c95ae5]{padding:0 var(--wx-space-4);border-radius:var(--wx-radius-xs);background:var(--wx-bg-subtle);color:var(--wx-text-default);font-family:var(--wx-font-family-mono);user-select:all}.wx-banner-place__views[data-v-23c95ae5]{align-self:flex-start}.wx-banner-place__list.is-loading[data-v-23c95ae5]{opacity:.6}.wx-banner-place__list[data-v-23c95ae5] .wx-sortable-list__row{gap:var(--wx-space-4);padding-inline:var(--wx-space-4);border-radius:var(--wx-radius-sm)}.wx-banner-place__list[data-v-23c95ae5] .wx-sortable-list__row:hover{background:var(--wx-bg-subtle)}.wx-banner-row[data-v-23c95ae5]{display:flex;align-items:center;gap:var(--wx-space-12);flex:1 1 auto;width:100%;min-width:0;padding-block:var(--wx-space-8);color:inherit;text-decoration:none}.wx-banner-row.is-off .wx-banner-row__thumb[data-v-23c95ae5],.wx-banner-row.is-off .wx-banner-row__title[data-v-23c95ae5]{opacity:.55}.wx-banner-row__thumb[data-v-23c95ae5]{display:flex;flex:none;align-items:center;justify-content:center;width:80px;aspect-ratio:16 / 9;overflow:hidden;border-radius:var(--wx-radius-xs);background:var(--wx-bg-fill);color:var(--wx-text-muted)}.wx-banner-row__thumb img[data-v-23c95ae5]{width:100%;height:100%;min-width:0;object-fit:cover}.wx-banner-row__body[data-v-23c95ae5]{display:flex;flex:1 1 auto;flex-direction:column;gap:var(--wx-space-4);min-width:0}.wx-banner-row__title[data-v-23c95ae5]{min-width:0;overflow:hidden;font-weight:var(--wx-font-weight-medium);text-overflow:ellipsis;white-space:nowrap}a.wx-banner-row:hover .wx-banner-row__title[data-v-23c95ae5]{color:var(--wx-color-primary)}.wx-banner-row__facts[data-v-23c95ae5]{display:flex;flex-wrap:wrap;gap:var(--wx-space-4)}.wx-banner-place__note[data-v-23c95ae5]{margin-top:var(--wx-space-4)}.wx-banners>.wx-card__body{display:flex;flex-direction:column;overflow:clip;border-radius:inherit}.wx-banners__loading[data-v-615232f0]{padding:var(--wx-space-16)}.wx-banners__list[data-v-615232f0]{margin:0;padding:var(--wx-space-8);list-style:none}.wx-banners__row[data-v-615232f0]{display:flex;align-items:center;gap:var(--wx-space-4);margin:0;padding-inline:var(--wx-space-8);border-radius:var(--wx-radius-sm)}.wx-banners__row[data-v-615232f0]:has(.is-current){background:var(--wx-bg-subtle)}.wx-banners__open[data-v-615232f0]{display:flex;flex-direction:column;align-items:flex-start;gap:var(--wx-space-2);flex:1 1 auto;min-width:0;padding-block:var(--wx-space-8);padding-inline:0;border:0;background:none;font:inherit;color:inherit;text-align:start;cursor:pointer}.wx-banners__open.is-current[data-v-615232f0]{color:var(--wx-color-primary)}.wx-banners__name[data-v-615232f0]{display:flex;align-items:center;gap:var(--wx-space-6);min-width:0;max-width:100%}.wx-banners__lock[data-v-615232f0]{flex:none;color:var(--wx-text-muted)}.wx-banners__count[data-v-615232f0]{flex:0 0 auto}
1
+ .wx-banner-editor[data-v-2c21e525]{display:flex;flex-direction:column;gap:var(--wx-gap, var(--wx-space-16));container-type:inline-size}.wx-banner-editor__ghost[data-v-2c21e525]{max-width:420px}.wx-banner-editor__place[data-v-2c21e525] .wx-form-item{max-width:420px;margin-bottom:0}.wx-banner-place[data-v-23c95ae5]{display:flex;flex-direction:column;gap:var(--wx-space-12);min-width:0;padding:var(--wx-gap, var(--wx-space-16))}.wx-banner-place__lock[data-v-23c95ae5]{display:inline-flex;color:var(--wx-text-muted)}.wx-banner-place__usage[data-v-23c95ae5]{display:flex;flex-wrap:wrap;align-items:baseline;gap:var(--wx-space-6);margin:0;color:var(--wx-text-muted);font-size:var(--wx-font-size-sm)}.wx-banner-place__usage code[data-v-23c95ae5]{padding:0 var(--wx-space-4);border-radius:var(--wx-radius-xs);background:var(--wx-bg-subtle);color:var(--wx-text-default);font-family:var(--wx-font-family-mono);user-select:all}.wx-banner-place__views[data-v-23c95ae5]{align-self:flex-start}.wx-banner-place__list.is-loading[data-v-23c95ae5]{opacity:.6}.wx-banner-place__list[data-v-23c95ae5] .wx-sortable-list__row{gap:var(--wx-space-4);padding-inline:var(--wx-space-4);border-radius:var(--wx-radius-sm)}.wx-banner-place__list[data-v-23c95ae5] .wx-sortable-list__row:hover{background:var(--wx-bg-subtle)}.wx-banner-row[data-v-23c95ae5]{display:flex;align-items:center;gap:var(--wx-space-12);flex:1 1 auto;width:100%;min-width:0;padding-block:var(--wx-space-8);color:inherit;text-decoration:none}.wx-banner-row.is-off .wx-banner-row__thumb[data-v-23c95ae5],.wx-banner-row.is-off .wx-banner-row__title[data-v-23c95ae5]{opacity:.55}.wx-banner-row__thumb[data-v-23c95ae5]{display:flex;flex:none;align-items:center;justify-content:center;width:80px;aspect-ratio:16 / 9;overflow:hidden;border-radius:var(--wx-radius-xs);background:var(--wx-bg-fill);color:var(--wx-text-muted)}.wx-banner-row__thumb img[data-v-23c95ae5]{width:100%;height:100%;min-width:0;object-fit:cover}.wx-banner-row__body[data-v-23c95ae5]{display:flex;flex:1 1 auto;flex-direction:column;gap:var(--wx-space-4);min-width:0}.wx-banner-row__title[data-v-23c95ae5]{min-width:0;overflow:hidden;font-weight:var(--wx-font-weight-medium);text-overflow:ellipsis;white-space:nowrap}a.wx-banner-row:hover .wx-banner-row__title[data-v-23c95ae5]{color:var(--wx-color-primary)}.wx-banner-row__facts[data-v-23c95ae5]{display:flex;flex-wrap:wrap;gap:var(--wx-space-4)}.wx-banner-place__note[data-v-23c95ae5]{margin-top:var(--wx-space-4)}.wx-banners>.wx-card__body{display:flex;flex-direction:column;overflow:clip;border-radius:inherit}.wx-banners__loading[data-v-615232f0]{padding:var(--wx-space-16)}.wx-banners__list[data-v-615232f0]{margin:0;padding:var(--wx-space-8);list-style:none}.wx-banners__row[data-v-615232f0]{display:flex;align-items:center;gap:var(--wx-space-4);margin:0;padding-inline:var(--wx-space-8);border-radius:var(--wx-radius-sm)}.wx-banners__row[data-v-615232f0]:has(.is-current){background:var(--wx-bg-subtle)}.wx-banners__open[data-v-615232f0]{display:flex;flex-direction:column;align-items:flex-start;gap:var(--wx-space-2);flex:1 1 auto;min-width:0;padding-block:var(--wx-space-8);padding-inline:0;border:0;background:none;font:inherit;color:inherit;text-align:start;cursor:pointer}.wx-banners__open.is-current[data-v-615232f0]{color:var(--wx-color-primary)}.wx-banners__name[data-v-615232f0]{display:flex;align-items:center;gap:var(--wx-space-6);min-width:0;max-width:100%}.wx-banners__lock[data-v-615232f0]{flex:none;color:var(--wx-text-muted)}.wx-banners__count[data-v-615232f0]{flex:0 0 auto}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webx-ui/module-banners",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Banners for the WebX UI admin panel: pictures, a video and up to three buttons in named places, arranged by hand and printed by the site's own templates.",
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.34.0",
48
- "@webx-ui/module-admin": "^0.18.1",
49
- "@webx-ui/schema": "^0.6.3"
47
+ "@webx-ui/core": "^0.34.1",
48
+ "@webx-ui/module-admin": "^0.18.2",
49
+ "@webx-ui/schema": "^0.6.4"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@types/node": "^24.10.1",