@webx-ui/module-services 0.1.0

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 ADDED
@@ -0,0 +1,1034 @@
1
+ import { reorderItems as Ne, useAdmin as ae, useDates as Be, useTranslate as le, useErrorText as me, provideRecordAddress as De, WxScreenHead as Oe, WxScreen as Pe, WxSaveState as Ae, WxDate as Me, useItemOrder as Le, WxListScreen as Re, WxRowMenu as Ue, categoryRoutes as He } from "@webx-ui/module-admin";
2
+ import { provide as Fe, inject as Ke, defineComponent as ne, computed as f, ref as b, watch as se, onMounted as Te, onBeforeUnmount as Se, openBlock as d, createElementBlock as I, Fragment as Q, createVNode as p, unref as e, withCtx as o, createTextVNode as _, toDisplayString as w, createBlock as k, createCommentVNode as L, createElementVNode as q, renderList as $e, normalizeClass as ye, useTemplateRef as Ge, nextTick as Je, isRef as je, withKeys as Qe, resolveDynamicComponent as Xe, mergeProps as Ye } from "vue";
3
+ import { useRoute as We, useRouter as Ce, onBeforeRouteLeave as Ze } from "vue-router";
4
+ import { provideBlocksPreview as et } from "@webx-ui/module-blocks";
5
+ import { useLocales as tt, localizedValue as st, confirm as Y, WxSkeleton as ie, WxCard as at, WxBadge as J, WxBreadcrumb as rt, WxBreadcrumbItem as _e, WxAlert as Ve, WxButton as j, WxActionBar as it, toast as S, WxEmpty as qe, WxText as lt, useModal as nt, WxDialog as ot, WxFormItem as ct, WxInput as Ie, WxSpace as ut, createModal as dt, WxIcon as xe, WxSelect as vt, WxSortableList as pt, WxTooltip as ht } from "@webx-ui/core";
6
+ const ge = "services";
7
+ function oe(c) {
8
+ const n = `${c.apiPath}/${ge}`, y = (r) => r.data;
9
+ return {
10
+ list: (r = {}) => {
11
+ const i = new URLSearchParams();
12
+ return r.q && i.set("q", r.q), r.category != null && i.set("category", String(r.category)), r.status && i.set("status", r.status), r.trashed && i.set("trashed", "1"), c.http.get(i.size > 0 ? `${n}?${i}` : n);
13
+ },
14
+ get: (r) => c.http.get(`${n}/${r}`).then(y),
15
+ create: (r) => c.http.post(n, r).then(y),
16
+ save: (r, i) => c.http.put(`${n}/${r}`, i).then(y),
17
+ discard: (r) => c.http.post(`${n}/${r}/discard`, {}).then(y),
18
+ publish: (r) => c.http.post(`${n}/${r}/publish`, {}).then(y),
19
+ unpublish: (r) => c.http.post(`${n}/${r}/unpublish`, {}).then(y),
20
+ remove: (r) => c.http.delete(`${n}/${r}`).then(() => {
21
+ }),
22
+ restore: (r) => c.http.post(`${n}/${r}/restore`, {}).then(y),
23
+ versions: (r) => c.http.get(`${n}/${r}/versions`).then(y),
24
+ restoreVersion: (r, i) => c.http.post(`${n}/${r}/versions/${i}/restore`, {}).then(y),
25
+ reorder: (r, i = null) => Ne(c, ge, r, i)
26
+ };
27
+ }
28
+ const ze = /* @__PURE__ */ Symbol("wx-service-editor");
29
+ function ft(c) {
30
+ Fe(ze, c);
31
+ }
32
+ function yt() {
33
+ return Ke(ze, null);
34
+ }
35
+ const gt = {
36
+ module: {
37
+ group: "Services",
38
+ services: "Services",
39
+ categories: "Categories"
40
+ },
41
+ panel: {
42
+ new: "New service",
43
+ "new-title": "New service",
44
+ "field-title": "Title",
45
+ create: "Create",
46
+ cancel: "Cancel",
47
+ search: "Search by title or address",
48
+ empty: "No services yet.",
49
+ "empty-help": "A service is a page of the site made of blocks, filed under categories.",
50
+ "empty-search": "Nothing matches that.",
51
+ "empty-bin": "The bin is empty.",
52
+ "status-draft": "Draft",
53
+ "status-published": "Live",
54
+ "status-modified": "Live",
55
+ "status-unpublished": "Off the site",
56
+ edits: "edits",
57
+ "no-address": "No address in this language",
58
+ "no-category": "In no category",
59
+ "main-category": "The main category: it goes in the breadcrumbs",
60
+ "view-all": "All",
61
+ "view-published": "Live",
62
+ "view-draft": "Drafts",
63
+ "view-unpublished": "Off the site",
64
+ bin: "Bin",
65
+ "filter-category": "Category",
66
+ "any-category": "Any category",
67
+ open: "Open",
68
+ "open-on-site": "Open on the site",
69
+ "copy-address": "Copy the address",
70
+ "address-copied": "The address is on the clipboard.",
71
+ publish: "Publish",
72
+ unpublish: "Take off the site",
73
+ delete: "Delete",
74
+ restore: "Restore",
75
+ "delete-title": "Delete “:title”?",
76
+ "delete-text": "It goes to the bin and comes off the site, and its address is free again.",
77
+ deleted: "The service is in the bin.",
78
+ restored: "The service is back.",
79
+ published: "The service is on the site.",
80
+ "unpublished-done": "The service is off the site.",
81
+ "reorder-failed": "The new order was not saved."
82
+ },
83
+ service: {
84
+ trail: "Where this service sits",
85
+ untitled: "Untitled",
86
+ save: "Save",
87
+ "save-failed": "The service was not saved.",
88
+ "publish-title": "Put “:title” on the site?",
89
+ "publish-text": "It answers at :address from the moment you do, for everyone.",
90
+ "publish-nowhere": "It has no address in this language yet, so nothing will answer.",
91
+ preview: "Preview",
92
+ discard: "Discard changes",
93
+ "discard-title": "Discard what is waiting?",
94
+ "discard-text": "The service goes back to what the site is showing. What was written since is not listed anywhere.",
95
+ discarded: "The service is back to what is published.",
96
+ "conflict-title": "The service changed while you were editing",
97
+ "conflict-mine": "Keep mine",
98
+ "conflict-theirs": "Take the newer version",
99
+ "conflict-theirs-title": "Give up what you wrote?",
100
+ "conflict-theirs-text": "The service is read again as it now is, and what you have typed since goes.",
101
+ leave: "Leave",
102
+ "leave-title": "Leave without saving?",
103
+ "leave-text": "The service could not be saved, and what you wrote is not on the server.",
104
+ "live-since": "On the site since :date",
105
+ "address-moving": "The address is changing. The old one keeps working and leads to the new one.",
106
+ "history-empty": "This service has never been published.",
107
+ version: "#:number",
108
+ "version-live": "On the site",
109
+ "source-panel": "From the panel",
110
+ "source-mcp": "By an agent",
111
+ "source-import": "Imported",
112
+ "restore-title": "Restore version :number?",
113
+ "restore-text": "It becomes the draft. The site keeps showing what is published until you publish this.",
114
+ "restore-version": "Restore",
115
+ "restored-version": "Version :number is now the draft."
116
+ },
117
+ category: {
118
+ "tab-content": "Content",
119
+ "tab-blocks": "Blocks",
120
+ "tab-cover": "Image",
121
+ "tab-seo": "SEO",
122
+ "field-title": "Title",
123
+ "field-slug": "Address",
124
+ "field-lead": "Introduction",
125
+ "lead-help": "Above the list of services on the category page.",
126
+ visible: "Shown on the site",
127
+ "visible-help": "Hidden, its page answers 404 and it drops out of the index; its services go on answering at their own addresses.",
128
+ "cover-help": "On the category card, and in social networks when the SEO card has no picture of its own.",
129
+ new: "New category",
130
+ empty: "No categories yet.",
131
+ "empty-help": "A category is a section of the catalogue with a page of its own.",
132
+ order: "Drag to change the order of the catalogue on the site.",
133
+ hidden: "Hidden from the site",
134
+ "no-address": "No address in this language",
135
+ services: "Services: :count",
136
+ "show-services": "Show its services",
137
+ "delete-blocked": "This category still holds services. Move them first.",
138
+ "delete-text": "It goes to the bin and its page comes off the site. Its services stay.",
139
+ deleted: "The category is in the bin.",
140
+ saved: "The category is saved.",
141
+ "address-moving": "The address is changing. The old one keeps working and leads to the new one."
142
+ }
143
+ }, ke = /* @__PURE__ */ new WeakSet();
144
+ function ce() {
145
+ try {
146
+ const { i18n: c } = ae();
147
+ ke.has(c) || (c.defaults("webx-services", gt), ke.add(c));
148
+ } catch {
149
+ }
150
+ }
151
+ const mt = { class: "wx-service-editor__screen" }, bt = 1200, wt = /* @__PURE__ */ ne({
152
+ __name: "ServiceEditorPage",
153
+ props: {
154
+ base: { default: "/services" }
155
+ },
156
+ setup(c) {
157
+ const n = c, y = ae(), r = oe(y), i = We(), z = Ce(), a = tt(), T = Be();
158
+ ce();
159
+ const s = le("webx-services"), $ = me(), W = f(() => Number(i.params.id)), D = f(() => n.base), u = b(null), g = b({}), m = b(""), h = b(null), C = b(null), O = b(!0), P = b(!1), R = b(!1), H = b({}), V = b(null), X = b(""), F = b(0), E = f(() => y.can("services.manage")), ue = f(() => !E.value || R.value), K = f(() => JSON.stringify(g.value)), N = f(() => X.value !== "" && K.value !== X.value), de = f(() => P.value ? "saving" : N.value ? "unsaved" : "saved"), Z = f(() => {
160
+ const l = g.value.title;
161
+ return st(l, a.active.value, u.value?.title ?? "");
162
+ }), ve = f(() => {
163
+ const l = u.value;
164
+ return !l || l.status !== "published" && l.status !== "modified" ? "" : s("service.live-since", { date: T.short(l.published_at) });
165
+ });
166
+ et({ url: f(() => C.value), reload: F }), De({
167
+ values: g,
168
+ prefix: h,
169
+ path: f(() => u.value?.path),
170
+ moving: () => s("service.address-moving")
171
+ }), ft({ service: u, canManage: E.value, reload: () => A(!0) });
172
+ function re(l) {
173
+ u.value = l.service, g.value = l.values, m.value = l.revision, h.value = l.prefix, C.value = l.preview_url, X.value = JSON.stringify(l.values), V.value = null;
174
+ }
175
+ async function A(l = !1) {
176
+ l || (O.value = !0);
177
+ try {
178
+ re(await r.get(W.value));
179
+ } catch (M) {
180
+ S.danger($(M)), z.push(D.value);
181
+ } finally {
182
+ O.value = !1;
183
+ }
184
+ }
185
+ let G;
186
+ function pe() {
187
+ !E.value || V.value || !N.value || (clearTimeout(G), G = setTimeout(() => {
188
+ U();
189
+ }, bt));
190
+ }
191
+ function he() {
192
+ !E.value || V.value || !N.value || P.value || (clearTimeout(G), U());
193
+ }
194
+ async function U() {
195
+ if (!u.value || P.value || !E.value) return;
196
+ clearTimeout(G);
197
+ const l = K.value, M = g.value;
198
+ P.value = !0, H.value = {};
199
+ try {
200
+ const B = await r.save(u.value.id, { values: M, revision: m.value });
201
+ u.value = B.service, m.value = B.revision, h.value = B.prefix, C.value = B.preview_url, V.value = null, K.value === l && (X.value = l), F.value += 1;
202
+ } catch (B) {
203
+ const te = B;
204
+ te.status === 409 && te.body?.data ? V.value = te.body : te.body?.errors ? (H.value = te.body.errors, S.danger(s("service.save-failed"))) : S.danger($(B));
205
+ } finally {
206
+ P.value = !1;
207
+ }
208
+ }
209
+ async function t() {
210
+ await Y({
211
+ title: s("service.conflict-theirs-title"),
212
+ message: s("service.conflict-theirs-text"),
213
+ confirmText: s("service.conflict-theirs"),
214
+ cancelText: s("panel.cancel"),
215
+ tone: "danger"
216
+ }) && await A(!0);
217
+ }
218
+ async function x() {
219
+ const l = V.value;
220
+ l && (m.value = l.data.revision, V.value = null, await U());
221
+ }
222
+ async function v() {
223
+ if (!(!u.value || !await Y({
224
+ title: s("service.discard-title"),
225
+ message: s("service.discard-text"),
226
+ confirmText: s("service.discard"),
227
+ cancelText: s("panel.cancel"),
228
+ tone: "danger"
229
+ }))) {
230
+ R.value = !0;
231
+ try {
232
+ re(await r.discard(u.value.id)), S.success(s("service.discarded"));
233
+ } catch (M) {
234
+ S.danger($(M));
235
+ } finally {
236
+ R.value = !1;
237
+ }
238
+ }
239
+ }
240
+ async function ee() {
241
+ const l = u.value;
242
+ if (!(!l || !await Y({
243
+ title: s("service.publish-title", { title: Z.value || l.title }),
244
+ message: l.path === null ? s("service.publish-nowhere") : s("service.publish-text", { address: `/${l.path}` }),
245
+ confirmText: s("panel.publish"),
246
+ cancelText: s("panel.cancel")
247
+ })) && (N.value && await U(), !V.value)) {
248
+ R.value = !0;
249
+ try {
250
+ await r.publish(l.id), await A(!0), S.success(s("panel.published"));
251
+ } catch (B) {
252
+ S.danger($(B));
253
+ } finally {
254
+ R.value = !1;
255
+ }
256
+ }
257
+ }
258
+ function fe() {
259
+ const l = u.value?.status;
260
+ return l === "published" || l === "modified" ? "success" : "default";
261
+ }
262
+ function we(l) {
263
+ N.value && l.preventDefault();
264
+ }
265
+ se(K, pe), se(W, () => {
266
+ A();
267
+ }), Te(() => {
268
+ A(), window.addEventListener("beforeunload", we);
269
+ }), Se(() => {
270
+ clearTimeout(G), window.removeEventListener("beforeunload", we);
271
+ }), Ze(async () => !N.value || !E.value || (await U(), !N.value) ? !0 : await Y({
272
+ title: s("service.leave-title"),
273
+ message: s("service.leave-text"),
274
+ confirmText: s("service.leave"),
275
+ cancelText: s("panel.cancel"),
276
+ tone: "danger"
277
+ }));
278
+ const Ee = f(() => {
279
+ const l = [];
280
+ return C.value && l.push({ key: "preview", label: s("service.preview"), icon: "eye", href: C.value }), u.value?.url && u.value.status !== "draft" && l.push({
281
+ key: "site",
282
+ label: s("panel.open-on-site"),
283
+ icon: "link",
284
+ href: u.value.url
285
+ }), u.value?.status === "modified" && l.push({
286
+ key: "discard",
287
+ label: s("service.discard"),
288
+ icon: "refresh",
289
+ danger: !0,
290
+ menu: !0,
291
+ run: () => {
292
+ v();
293
+ }
294
+ }), l;
295
+ });
296
+ return (l, M) => (d(), I("div", {
297
+ class: "wx-service-editor",
298
+ onFocusout: he
299
+ }, [
300
+ O.value || !u.value ? (d(), I(Q, { key: 0 }, [
301
+ p(e(ie), {
302
+ class: "wx-service-editor__ghost",
303
+ title: "",
304
+ rows: 1
305
+ }),
306
+ p(e(at), null, {
307
+ default: o(() => [
308
+ p(e(ie), { rows: 8 })
309
+ ]),
310
+ _: 1
311
+ })
312
+ ], 64)) : (d(), I(Q, { key: 1 }, [
313
+ p(e(Oe), {
314
+ divider: "",
315
+ back: D.value,
316
+ "back-label": e(s)("module.services"),
317
+ title: Z.value || e(s)("service.untitled"),
318
+ subtitle: ve.value,
319
+ actions: Ee.value
320
+ }, {
321
+ trail: o(() => [
322
+ p(e(rt), {
323
+ size: "sm",
324
+ label: e(s)("service.trail")
325
+ }, {
326
+ default: o(() => [
327
+ p(e(_e), {
328
+ as: "router-link",
329
+ to: D.value
330
+ }, {
331
+ default: o(() => [
332
+ _(w(e(s)("module.services")), 1)
333
+ ]),
334
+ _: 1
335
+ }, 8, ["to"]),
336
+ p(e(_e), { current: "" }, {
337
+ default: o(() => [
338
+ _(w(Z.value || e(s)("service.untitled")), 1)
339
+ ]),
340
+ _: 1
341
+ })
342
+ ]),
343
+ _: 1
344
+ }, 8, ["label"])
345
+ ]),
346
+ "title-after": o(() => [
347
+ p(e(J), {
348
+ type: fe(),
349
+ dot: ""
350
+ }, {
351
+ default: o(() => [
352
+ _(w(e(s)(`panel.status-${u.value.status}`)), 1)
353
+ ]),
354
+ _: 1
355
+ }, 8, ["type"]),
356
+ u.value.status === "modified" ? (d(), k(e(J), {
357
+ key: 0,
358
+ type: "primary",
359
+ round: ""
360
+ }, {
361
+ default: o(() => [
362
+ _(w(e(s)("panel.edits")), 1)
363
+ ]),
364
+ _: 1
365
+ })) : L("", !0)
366
+ ]),
367
+ _: 1
368
+ }, 8, ["back", "back-label", "title", "subtitle", "actions"]),
369
+ V.value ? (d(), k(e(Ve), {
370
+ key: 0,
371
+ type: "warning",
372
+ title: e(s)("service.conflict-title"),
373
+ description: V.value.message
374
+ }, {
375
+ actions: o(() => [
376
+ p(e(j), {
377
+ size: "sm",
378
+ variant: "outline",
379
+ onClick: t
380
+ }, {
381
+ default: o(() => [
382
+ _(w(e(s)("service.conflict-theirs")), 1)
383
+ ]),
384
+ _: 1
385
+ }),
386
+ p(e(j), {
387
+ size: "sm",
388
+ type: "primary",
389
+ onClick: x
390
+ }, {
391
+ default: o(() => [
392
+ _(w(e(s)("service.conflict-mine")), 1)
393
+ ]),
394
+ _: 1
395
+ })
396
+ ]),
397
+ _: 1
398
+ }, 8, ["title", "description"])) : L("", !0),
399
+ q("div", mt, [
400
+ p(e(Pe), {
401
+ modelValue: g.value,
402
+ "onUpdate:modelValue": M[0] || (M[0] = (B) => g.value = B),
403
+ name: "services.form",
404
+ errors: H.value,
405
+ disabled: ue.value
406
+ }, null, 8, ["modelValue", "errors", "disabled"])
407
+ ]),
408
+ E.value ? (d(), k(e(it), { key: 1 }, {
409
+ state: o(() => [
410
+ p(e(Ae), { state: de.value }, null, 8, ["state"])
411
+ ]),
412
+ default: o(() => [
413
+ p(e(j), {
414
+ variant: "outline",
415
+ loading: P.value,
416
+ disabled: !N.value,
417
+ onClick: U
418
+ }, {
419
+ default: o(() => [
420
+ _(w(e(s)("service.save")), 1)
421
+ ]),
422
+ _: 1
423
+ }, 8, ["loading", "disabled"]),
424
+ p(e(j), {
425
+ type: "primary",
426
+ loading: R.value,
427
+ disabled: u.value.status === "published" && !N.value,
428
+ onClick: ee
429
+ }, {
430
+ default: o(() => [
431
+ _(w(e(s)("panel.publish")), 1)
432
+ ]),
433
+ _: 1
434
+ }, 8, ["loading", "disabled"])
435
+ ]),
436
+ _: 1
437
+ })) : L("", !0)
438
+ ], 64))
439
+ ], 32));
440
+ }
441
+ }), be = (c, n) => {
442
+ const y = c.__vccOpts || c;
443
+ for (const [r, i] of n)
444
+ y[r] = i;
445
+ return y;
446
+ }, _t = /* @__PURE__ */ be(wt, [["__scopeId", "data-v-c1092685"]]), xt = { class: "wx-service-history" }, kt = { class: "wx-service-history__number" }, Tt = { class: "wx-service-history__who" }, St = /* @__PURE__ */ ne({
447
+ name: "WxServiceHistory",
448
+ __name: "ServiceHistory",
449
+ setup(c) {
450
+ const n = ae(), y = oe(n), r = yt();
451
+ ce();
452
+ const i = le("webx-services"), z = me(), a = b(null), T = b(!1), s = f(() => r?.service.value ?? null), $ = f(() => r?.canManage ?? !1), W = f(
453
+ () => s.value?.status === "published" || s.value?.status === "modified" ? a.value?.[0]?.number ?? null : null
454
+ );
455
+ async function D() {
456
+ const g = s.value;
457
+ if (g)
458
+ try {
459
+ a.value = await y.versions(g.id);
460
+ } catch (m) {
461
+ a.value = [], S.danger(z(m));
462
+ }
463
+ }
464
+ async function u(g) {
465
+ const m = s.value;
466
+ if (!(!m || !await Y({
467
+ title: i("service.restore-title", { number: g.number }),
468
+ message: i("service.restore-text"),
469
+ confirmText: i("service.restore-version"),
470
+ cancelText: i("panel.cancel")
471
+ }))) {
472
+ T.value = !0;
473
+ try {
474
+ await y.restoreVersion(m.id, g.number), await r?.reload(), S.success(i("service.restored-version", { number: g.number }));
475
+ } catch (C) {
476
+ S.danger(z(C));
477
+ } finally {
478
+ T.value = !1;
479
+ }
480
+ }
481
+ }
482
+ return se(
483
+ () => [s.value?.id, s.value?.published_at, s.value?.updated_at],
484
+ () => {
485
+ D();
486
+ },
487
+ { immediate: !0 }
488
+ ), (g, m) => (d(), I("div", xt, [
489
+ a.value === null ? (d(), k(e(ie), {
490
+ key: 0,
491
+ class: "wx-service-history__ghost",
492
+ rows: 3
493
+ })) : a.value.length === 0 ? (d(), k(e(qe), {
494
+ key: 1,
495
+ description: e(i)("service.history-empty")
496
+ }, null, 8, ["description"])) : (d(!0), I(Q, { key: 2 }, $e(a.value, (h) => (d(), I("div", {
497
+ key: h.number,
498
+ class: ye(["wx-service-history__row", { "is-live": h.number === W.value }])
499
+ }, [
500
+ q("code", kt, w(e(i)("service.version", { number: h.number })), 1),
501
+ q("div", Tt, [
502
+ h.created_at ? (d(), k(e(Me), {
503
+ key: 0,
504
+ value: h.created_at,
505
+ size: "md",
506
+ tone: "default"
507
+ }, null, 8, ["value"])) : L("", !0),
508
+ p(e(lt), {
509
+ size: "sm",
510
+ tone: "muted"
511
+ }, {
512
+ default: o(() => [
513
+ _(w(h.author ?? e(i)(`service.source-${h.source}`)) + " ", 1),
514
+ h.comment ? (d(), I(Q, { key: 0 }, [
515
+ _(" · " + w(h.comment), 1)
516
+ ], 64)) : L("", !0)
517
+ ]),
518
+ _: 2
519
+ }, 1024)
520
+ ]),
521
+ h.number === W.value ? (d(), k(e(J), {
522
+ key: 0,
523
+ type: "success",
524
+ dot: ""
525
+ }, {
526
+ default: o(() => [
527
+ _(w(e(i)("service.version-live")), 1)
528
+ ]),
529
+ _: 1
530
+ })) : L("", !0),
531
+ $.value && h.number !== W.value ? (d(), k(e(j), {
532
+ key: 1,
533
+ size: "sm",
534
+ variant: "outline",
535
+ loading: T.value,
536
+ onClick: (C) => u(h)
537
+ }, {
538
+ default: o(() => [
539
+ _(w(e(i)("service.restore-version")), 1)
540
+ ]),
541
+ _: 1
542
+ }, 8, ["loading", "onClick"])) : L("", !0)
543
+ ], 2))), 128))
544
+ ]));
545
+ }
546
+ }), $t = /* @__PURE__ */ be(St, [["__scopeId", "data-v-72dccbb1"]]), Wt = /* @__PURE__ */ ne({
547
+ __name: "ServiceCreateDialog",
548
+ setup(c) {
549
+ const { open: n, resolve: y, dismiss: r } = nt(), i = ae(), z = oe(i);
550
+ ce();
551
+ const a = le("webx-services"), T = b(""), s = b(!1), $ = b({}), W = Ge("field");
552
+ Te(async () => {
553
+ await Je(), W.value?.querySelector("input")?.focus();
554
+ });
555
+ async function D() {
556
+ if (!(T.value.trim() === "" || s.value)) {
557
+ s.value = !0, $.value = {};
558
+ try {
559
+ y(await z.create({ title: T.value.trim() }));
560
+ } catch (u) {
561
+ $.value = u.body?.errors ?? {};
562
+ } finally {
563
+ s.value = !1;
564
+ }
565
+ }
566
+ }
567
+ return (u, g) => (d(), k(e(ot), {
568
+ open: e(n),
569
+ "onUpdate:open": g[2] || (g[2] = (m) => je(n) ? n.value = m : null),
570
+ title: e(a)("panel.new-title"),
571
+ width: 440
572
+ }, {
573
+ footer: o(() => [
574
+ p(e(ut), { size: "sm" }, {
575
+ default: o(() => [
576
+ p(e(j), {
577
+ variant: "outline",
578
+ onClick: g[1] || (g[1] = (m) => e(r)())
579
+ }, {
580
+ default: o(() => [
581
+ _(w(e(a)("panel.cancel")), 1)
582
+ ]),
583
+ _: 1
584
+ }),
585
+ p(e(j), {
586
+ type: "primary",
587
+ loading: s.value,
588
+ disabled: T.value.trim() === "",
589
+ onClick: D
590
+ }, {
591
+ default: o(() => [
592
+ _(w(e(a)("panel.create")), 1)
593
+ ]),
594
+ _: 1
595
+ }, 8, ["loading", "disabled"])
596
+ ]),
597
+ _: 1
598
+ })
599
+ ]),
600
+ default: o(() => [
601
+ p(e(ct), {
602
+ label: e(a)("panel.field-title"),
603
+ error: $.value.title?.[0] ?? $.value.slug?.[0],
604
+ required: ""
605
+ }, {
606
+ default: o(() => [
607
+ q("div", {
608
+ ref_key: "field",
609
+ ref: W
610
+ }, [
611
+ p(e(Ie), {
612
+ modelValue: T.value,
613
+ "onUpdate:modelValue": g[0] || (g[0] = (m) => T.value = m),
614
+ "aria-label": e(a)("panel.field-title"),
615
+ onKeyup: Qe(D, ["enter"])
616
+ }, null, 8, ["modelValue", "aria-label"])
617
+ ], 512)
618
+ ]),
619
+ _: 1
620
+ }, 8, ["label", "error"])
621
+ ]),
622
+ _: 1
623
+ }, 8, ["open", "title"]));
624
+ }
625
+ }), Ct = { class: "wx-services" }, Vt = { class: "wx-services__bar" }, qt = ["src"], It = { class: "wx-service-row__name" }, zt = { class: "wx-service-row__title" }, Et = { class: "wx-service-row__address" }, Nt = { class: "wx-service-row__facts" }, Bt = { class: "wx-service-row__chips" }, Dt = { class: "wx-service-row__state" }, Ot = /* @__PURE__ */ ne({
626
+ __name: "ServicesPage",
627
+ props: {
628
+ base: { default: "/services" }
629
+ },
630
+ setup(c) {
631
+ const n = c, y = ae(), r = oe(y), i = We(), z = Ce();
632
+ ce();
633
+ const a = le("webx-services"), T = me(), s = b([]), $ = b([]), W = b(!0), D = dt(Wt), u = f(() => y.can("services.manage")), g = f(
634
+ () => y.state.manifest?.modules.find((t) => t.id === "services")?.title ?? a("module.services")
635
+ ), m = f({
636
+ get: () => typeof i.query.view == "string" ? i.query.view : "",
637
+ set: (t) => {
638
+ z.replace({
639
+ query: { ...i.query, view: t === "" ? void 0 : String(t) }
640
+ });
641
+ }
642
+ }), h = f(() => m.value === "trashed"), C = f(() => {
643
+ const t = i.query.category;
644
+ return typeof t == "string" && t !== "" && Number(t) || null;
645
+ }), O = f(() => typeof i.query.q == "string" ? i.query.q : ""), P = b(O.value), R = f(() => [
646
+ { value: "", label: a("panel.view-all") },
647
+ { value: "published", label: a("panel.view-published") },
648
+ { value: "draft", label: a("panel.view-draft") },
649
+ { value: "unpublished", label: a("panel.view-unpublished") },
650
+ { value: "trashed", label: a("panel.bin"), icon: "trash" }
651
+ ]), H = Le(ge, () => ({
652
+ q: O.value,
653
+ category: C.value,
654
+ filtered: m.value !== ""
655
+ })), V = f(() => u.value && H.sortable.value), X = f(() => O.value !== "" ? a("panel.empty-search") : h.value ? a("panel.empty-bin") : a("panel.empty"));
656
+ async function F() {
657
+ W.value = !0;
658
+ try {
659
+ const t = await r.list({
660
+ q: O.value,
661
+ category: C.value,
662
+ status: h.value ? "" : m.value,
663
+ trashed: h.value
664
+ });
665
+ s.value = t.data, $.value = t.filters.categories;
666
+ } catch (t) {
667
+ S.danger(T(t));
668
+ } finally {
669
+ W.value = !1;
670
+ }
671
+ }
672
+ se(
673
+ () => [m.value, C.value, O.value],
674
+ () => {
675
+ F();
676
+ }
677
+ ), F();
678
+ let E;
679
+ se(P, (t) => {
680
+ clearTimeout(E), E = setTimeout(() => {
681
+ z.replace({ query: { ...i.query, q: t.trim() === "" ? void 0 : t } });
682
+ }, 300);
683
+ }), Se(() => clearTimeout(E));
684
+ function ue(t) {
685
+ z.replace({
686
+ query: { ...i.query, category: typeof t == "number" ? String(t) : void 0 }
687
+ });
688
+ }
689
+ function K(t) {
690
+ z.push({ path: `${n.base}/${t.id}`, query: { ...i.query } });
691
+ }
692
+ async function N() {
693
+ const t = await D({});
694
+ t && K(t);
695
+ }
696
+ async function de() {
697
+ try {
698
+ await H.move(s.value.map((t) => t.id));
699
+ } catch (t) {
700
+ S.danger(T(t, a("panel.reorder-failed"))), await F();
701
+ }
702
+ }
703
+ function Z(t) {
704
+ if (!u.value) return [];
705
+ if (h.value)
706
+ return [
707
+ {
708
+ key: "restore",
709
+ icon: "refresh",
710
+ label: a("panel.restore"),
711
+ run: () => {
712
+ re(t);
713
+ }
714
+ }
715
+ ];
716
+ const x = [
717
+ { key: "open", icon: "edit", label: a("panel.open"), run: () => K(t) }
718
+ ];
719
+ return t.url && t.status !== "draft" && x.push({
720
+ key: "site",
721
+ icon: "external-link",
722
+ label: a("panel.open-on-site"),
723
+ href: t.url
724
+ }), t.url && x.push({
725
+ key: "copy",
726
+ icon: "copy",
727
+ label: a("panel.copy-address"),
728
+ run: () => {
729
+ G(t);
730
+ }
731
+ }), x.push(
732
+ t.status === "published" || t.status === "modified" ? {
733
+ key: "unpublish",
734
+ icon: "eye-off",
735
+ label: a("panel.unpublish"),
736
+ run: () => {
737
+ A(() => r.unpublish(t.id), a("panel.unpublished-done"));
738
+ }
739
+ } : {
740
+ key: "publish",
741
+ icon: "upload",
742
+ label: a("panel.publish"),
743
+ run: () => {
744
+ A(() => r.publish(t.id), a("panel.published"));
745
+ }
746
+ }
747
+ ), x.push({
748
+ key: "delete",
749
+ icon: "trash",
750
+ label: a("panel.delete"),
751
+ danger: !0,
752
+ run: () => {
753
+ ve(t);
754
+ }
755
+ }), x;
756
+ }
757
+ async function ve(t) {
758
+ await Y({
759
+ title: a("panel.delete-title", { title: t.title }),
760
+ message: a("panel.delete-text"),
761
+ confirmText: a("panel.delete"),
762
+ cancelText: a("panel.cancel"),
763
+ tone: "danger"
764
+ }) && await A(() => r.remove(t.id), a("panel.deleted"));
765
+ }
766
+ async function re(t) {
767
+ await A(() => r.restore(t.id), a("panel.restored"));
768
+ }
769
+ async function A(t, x) {
770
+ try {
771
+ await t(), S.success(x);
772
+ } catch (v) {
773
+ S.danger(T(v));
774
+ } finally {
775
+ await F();
776
+ }
777
+ }
778
+ async function G(t) {
779
+ if (t.url)
780
+ try {
781
+ await navigator.clipboard.writeText(t.url), S.success(a("panel.address-copied"));
782
+ } catch {
783
+ }
784
+ }
785
+ function pe(t) {
786
+ return t === "published" || t === "modified" ? "success" : "default";
787
+ }
788
+ function he(t) {
789
+ return h.value ? "—" : t.path === null ? a("panel.no-address") : `/${t.path}`;
790
+ }
791
+ const U = f(
792
+ () => u.value ? [{ key: "new", label: a("panel.new"), icon: "plus", primary: !0, run: () => {
793
+ N();
794
+ } }] : []
795
+ );
796
+ return (t, x) => (d(), I("div", Ct, [
797
+ p(e(Re), {
798
+ view: m.value,
799
+ "onUpdate:view": x[2] || (x[2] = (v) => m.value = v),
800
+ title: g.value,
801
+ views: R.value,
802
+ actions: U.value,
803
+ padding: "sm"
804
+ }, {
805
+ default: o(() => [
806
+ q("div", Vt, [
807
+ p(e(Ie), {
808
+ modelValue: P.value,
809
+ "onUpdate:modelValue": x[0] || (x[0] = (v) => P.value = v),
810
+ class: "wx-services__search",
811
+ size: "sm",
812
+ clearable: "",
813
+ placeholder: e(a)("panel.search"),
814
+ "aria-label": e(a)("panel.search")
815
+ }, {
816
+ prefix: o(() => [
817
+ p(e(xe), {
818
+ name: "search",
819
+ size: "sm"
820
+ })
821
+ ]),
822
+ _: 1
823
+ }, 8, ["modelValue", "placeholder", "aria-label"]),
824
+ p(e(vt), {
825
+ class: "wx-services__category",
826
+ "model-value": C.value,
827
+ options: $.value.map((v) => ({ value: v.id, label: v.title })),
828
+ placeholder: e(a)("panel.any-category"),
829
+ "aria-label": e(a)("panel.filter-category"),
830
+ clearable: "",
831
+ size: "sm",
832
+ "onUpdate:modelValue": ue
833
+ }, null, 8, ["model-value", "options", "placeholder", "aria-label"])
834
+ ]),
835
+ W.value && s.value.length === 0 ? (d(), k(e(ie), {
836
+ key: 0,
837
+ class: "wx-services__loading",
838
+ rows: 5
839
+ })) : s.value.length === 0 ? (d(), k(e(qe), {
840
+ key: 1,
841
+ title: X.value,
842
+ description: m.value === "" && O.value === "" && C.value === null ? e(a)("panel.empty-help") : void 0
843
+ }, null, 8, ["title", "description"])) : (d(), I(Q, { key: 2 }, [
844
+ p(e(pt), {
845
+ modelValue: s.value,
846
+ "onUpdate:modelValue": x[1] || (x[1] = (v) => s.value = v),
847
+ class: ye(["wx-services__list", { "is-loading": W.value }]),
848
+ plain: "",
849
+ "item-key": "id",
850
+ handle: V.value ? "grip" : "row",
851
+ "item-label": (v) => v.title,
852
+ disabled: !V.value,
853
+ onMove: de
854
+ }, {
855
+ default: o(({ item: v }) => [
856
+ (d(), k(Xe(h.value ? "div" : "router-link"), Ye({ class: "wx-service-row" }, h.value ? {} : { to: { path: `${n.base}/${v.id}`, query: e(i).query } }), {
857
+ default: o(() => [
858
+ q("span", {
859
+ class: ye(["wx-service-row__cover", { "is-empty": !v.cover }])
860
+ }, [
861
+ v.cover?.thumb ? (d(), I("img", {
862
+ key: 0,
863
+ src: v.cover.thumb,
864
+ alt: "",
865
+ loading: "lazy"
866
+ }, null, 8, qt)) : (d(), k(e(xe), {
867
+ key: 1,
868
+ name: "image",
869
+ size: "sm"
870
+ }))
871
+ ], 2),
872
+ q("span", It, [
873
+ q("span", zt, w(v.title), 1),
874
+ q("span", Et, w(he(v)), 1)
875
+ ]),
876
+ q("span", Nt, [
877
+ q("span", Bt, [
878
+ (d(!0), I(Q, null, $e(v.categories, (ee, fe) => (d(), I(Q, {
879
+ key: ee.id
880
+ }, [
881
+ fe === 0 ? (d(), k(e(ht), {
882
+ key: 0,
883
+ content: e(a)("panel.main-category")
884
+ }, {
885
+ default: o(() => [
886
+ p(e(J), {
887
+ size: "sm",
888
+ round: "",
889
+ type: "primary"
890
+ }, {
891
+ default: o(() => [
892
+ _(w(ee.title), 1)
893
+ ]),
894
+ _: 2
895
+ }, 1024)
896
+ ]),
897
+ _: 2
898
+ }, 1032, ["content"])) : (d(), k(e(J), {
899
+ key: 1,
900
+ size: "sm",
901
+ round: ""
902
+ }, {
903
+ default: o(() => [
904
+ _(w(ee.title), 1)
905
+ ]),
906
+ _: 2
907
+ }, 1024))
908
+ ], 64))), 128))
909
+ ]),
910
+ q("span", Dt, [
911
+ p(e(J), {
912
+ type: pe(v.status),
913
+ dot: "",
914
+ size: "sm"
915
+ }, {
916
+ default: o(() => [
917
+ _(w(e(a)(`panel.status-${v.status}`)), 1)
918
+ ]),
919
+ _: 2
920
+ }, 1032, ["type"]),
921
+ v.status === "modified" ? (d(), k(e(J), {
922
+ key: 0,
923
+ type: "primary",
924
+ size: "sm",
925
+ round: ""
926
+ }, {
927
+ default: o(() => [
928
+ _(w(e(a)("panel.edits")), 1)
929
+ ]),
930
+ _: 1
931
+ })) : L("", !0)
932
+ ])
933
+ ])
934
+ ]),
935
+ _: 2
936
+ }, 1040))
937
+ ]),
938
+ actions: o(({ item: v }) => [
939
+ p(e(Ue), {
940
+ actions: Z(v),
941
+ label: v.title
942
+ }, null, 8, ["actions", "label"])
943
+ ]),
944
+ _: 1
945
+ }, 8, ["modelValue", "class", "handle", "item-label", "disabled"]),
946
+ u.value && !h.value ? (d(), k(e(Ve), {
947
+ key: 0,
948
+ class: "wx-services__note",
949
+ type: "info",
950
+ description: e(H).hint.value
951
+ }, null, 8, ["description"])) : L("", !0)
952
+ ], 64))
953
+ ]),
954
+ _: 1
955
+ }, 8, ["view", "title", "views", "actions"])
956
+ ]));
957
+ }
958
+ }), Pt = /* @__PURE__ */ be(Ot, [["__scopeId", "data-v-f99d37d6"]]);
959
+ function At(c = "/services") {
960
+ return {
961
+ api: "services/categories",
962
+ path: `${c}/categories`,
963
+ name: "webx.services.categories",
964
+ module: "service-categories",
965
+ screen: "services.category-form",
966
+ manage: "services.categories.manage",
967
+ count: "services_count",
968
+ // The list narrowed to one category is also where its own order is dragged.
969
+ items: (n) => ({ path: c, query: { category: String(n) } }),
970
+ words: {
971
+ new: "webx-services::category.new",
972
+ empty: "webx-services::category.empty",
973
+ "empty-help": "webx-services::category.empty-help",
974
+ order: "webx-services::category.order",
975
+ hidden: "webx-services::category.hidden",
976
+ "no-address": "webx-services::category.no-address",
977
+ count: "webx-services::category.services",
978
+ "show-items": "webx-services::category.show-services",
979
+ "delete-blocked": "webx-services::category.delete-blocked",
980
+ "delete-text": "webx-services::category.delete-text",
981
+ deleted: "webx-services::category.deleted",
982
+ saved: "webx-services::category.saved",
983
+ "field-title": "webx-services::category.field-title",
984
+ "field-slug": "webx-services::category.field-slug",
985
+ "address-moving": "webx-services::category.address-moving"
986
+ }
987
+ };
988
+ }
989
+ function Ft(c = {}) {
990
+ const n = c.path ?? "/services";
991
+ return [
992
+ {
993
+ id: "services",
994
+ path: n,
995
+ routes: [
996
+ { path: n, name: "webx.services", component: Pt, props: { base: n } },
997
+ {
998
+ path: `${n}/:id(\\d+)`,
999
+ name: "webx.services.edit",
1000
+ component: _t,
1001
+ props: { base: n }
1002
+ }
1003
+ ],
1004
+ /*
1005
+ * The one part of `services.form` only this module can draw. Everything else on it is the
1006
+ * panel's (`wx-slug`, `wx-categories`, `wx-media`, `wx-blocks`) — the address field in
1007
+ * particular is shared, and reads the prefix from the editor hosting the screen.
1008
+ */
1009
+ types: {
1010
+ "wx-service-history": { component: $t, kind: "display" }
1011
+ }
1012
+ },
1013
+ {
1014
+ id: "service-categories",
1015
+ path: `${n}/categories`,
1016
+ routes: He(At(n))
1017
+ }
1018
+ ];
1019
+ }
1020
+ export {
1021
+ ge as SERVICES_API,
1022
+ Wt as WxServiceCreateDialog,
1023
+ _t as WxServiceEditorPage,
1024
+ $t as WxServiceHistory,
1025
+ Pt as WxServicesPage,
1026
+ oe as createServicesApi,
1027
+ ft as provideServiceEditor,
1028
+ At as serviceCategoriesOptions,
1029
+ ze as serviceEditorKey,
1030
+ Ft as services,
1031
+ gt as servicesMessages,
1032
+ yt as useServiceEditor
1033
+ };
1034
+ //# sourceMappingURL=index.js.map