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