@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 +62 -62
- package/dist/index.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { defineComponent as X, computed as $, ref as W, watch as j, onMounted as
|
|
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
|
|
4
|
-
import { useAdmin as J, useTranslate as Y, useErrorText as ue, WxScreenHead as
|
|
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
|
|
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
|
-
},
|
|
137
|
+
}, ge = /* @__PURE__ */ new WeakSet();
|
|
138
138
|
function ee() {
|
|
139
139
|
try {
|
|
140
140
|
const { i18n: i } = J();
|
|
141
|
-
|
|
141
|
+
ge.has(i) || (i.defaults("webx-banners", aa), ge.add(i));
|
|
142
142
|
} catch {
|
|
143
143
|
}
|
|
144
144
|
}
|
|
145
|
-
const
|
|
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 =
|
|
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,
|
|
200
|
-
b.value = null, g.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
|
|
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
|
|
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
|
-
}),
|
|
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
|
-
|
|
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:
|
|
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(
|
|
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
|
-
},
|
|
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(
|
|
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(
|
|
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
|
-
},
|
|
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 =
|
|
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
|
-
|
|
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(
|
|
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:
|
|
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(
|
|
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:
|
|
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
|
-
}),
|
|
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
|
-
},
|
|
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
|
-
},
|
|
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",
|
|
623
|
-
c(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
|
-
},
|
|
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,
|
|
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",
|
|
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",
|
|
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",
|
|
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,
|
|
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",
|
|
708
|
-
R("span",
|
|
709
|
-
_.video || !_.enabled && !k.value ? (m(), M("span",
|
|
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(
|
|
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
|
-
}),
|
|
753
|
+
}), ba = /* @__PURE__ */ de(va, [["__scopeId", "data-v-23c95ae5"]]), fa = {
|
|
754
754
|
key: 2,
|
|
755
755
|
class: "wx-banners__list"
|
|
756
|
-
},
|
|
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(
|
|
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(
|
|
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",
|
|
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",
|
|
879
|
-
c(e(
|
|
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(
|
|
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,
|
|
914
|
-
K(f).length > 0 ? (m(), D(e(
|
|
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(
|
|
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
|
-
}),
|
|
948
|
-
function
|
|
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:
|
|
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:
|
|
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:
|
|
966
|
+
component: ke,
|
|
967
967
|
props: { base: s }
|
|
968
968
|
}
|
|
969
969
|
]
|
|
970
970
|
};
|
|
971
971
|
}
|
|
972
972
|
export {
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
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-
|
|
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.
|
|
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.
|
|
48
|
-
"@webx-ui/module-admin": "^0.18.
|
|
49
|
-
"@webx-ui/schema": "^0.6.
|
|
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",
|