@webx-ui/module-recipes 0.2.5 → 0.3.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/RecipeEditorPage-DcvFY_ja.js +346 -0
- package/dist/RecipeEditorPage-DcvFY_ja.js.map +1 -0
- package/dist/RecipesPage-CylQ5S5w.js +380 -0
- package/dist/RecipesPage-CylQ5S5w.js.map +1 -0
- package/dist/index-rwJ6c36l.js +476 -0
- package/dist/index-rwJ6c36l.js.map +1 -0
- package/dist/index.d.ts +14 -2
- package/dist/index.js +16 -1173
- package/dist/index.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
import { provide as H, inject as K, defineComponent as M, ref as b, computed as S, watch as U, openBlock as p, createElementBlock as T, createElementVNode as R, createBlock as w, unref as s, Fragment as D, renderList as F, normalizeClass as j, toDisplayString as m, createCommentVNode as $, createVNode as f, withCtx as h, createTextVNode as y, useTemplateRef as G, onMounted as J, nextTick as Q, isRef as X, withKeys as Y, defineAsyncComponent as O } from "vue";
|
|
2
|
+
import { reorderItems as Z, useAdmin as I, useTranslate as P, useErrorText as q, WxDate as ee, WxDrafts as te, categoryRoutes as A } from "@webx-ui/module-admin";
|
|
3
|
+
import { WxSkeleton as ie, WxEmpty as re, WxText as se, WxBadge as ne, WxButton as W, toast as C, confirm as oe, useModal as ae, WxDialog as ce, WxFormItem as le, WxInput as de, WxSpace as pe } from "@webx-ui/core";
|
|
4
|
+
const E = "recipes";
|
|
5
|
+
function B(i) {
|
|
6
|
+
const t = `${i.apiPath}/${E}`, n = (e) => e.data;
|
|
7
|
+
return {
|
|
8
|
+
list: (e = {}) => {
|
|
9
|
+
const r = new URLSearchParams();
|
|
10
|
+
return e.q && r.set("q", e.q), e.category != null && r.set("category", String(e.category)), e.nutrient != null && r.set("nutrient", String(e.nutrient)), e.service != null && r.set("service", String(e.service)), e.status && r.set("status", e.status), e.trashed && r.set("trashed", "1"), i.http.get(r.size > 0 ? `${t}?${r}` : t);
|
|
11
|
+
},
|
|
12
|
+
get: (e) => i.http.get(`${t}/${e}`).then(n),
|
|
13
|
+
create: (e) => i.http.post(t, e).then(n),
|
|
14
|
+
save: (e, r) => i.http.put(`${t}/${e}`, r).then(n),
|
|
15
|
+
discard: (e) => i.http.post(`${t}/${e}/discard`, {}).then(n),
|
|
16
|
+
publish: (e, r) => i.http.post(`${t}/${e}/publish`, r ? { revision: r } : {}).then(n),
|
|
17
|
+
unpublish: (e) => i.http.post(`${t}/${e}/unpublish`, {}).then(n),
|
|
18
|
+
remove: (e) => i.http.delete(`${t}/${e}`).then(() => {
|
|
19
|
+
}),
|
|
20
|
+
restore: (e) => i.http.post(`${t}/${e}/restore`, {}).then(n),
|
|
21
|
+
versions: (e) => i.http.get(`${t}/${e}/versions`).then(n),
|
|
22
|
+
restoreVersion: (e, r) => i.http.post(`${t}/${e}/versions/${r}/restore`, {}).then(n),
|
|
23
|
+
reorder: (e) => Z(i, E, e, null)
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
const L = /* @__PURE__ */ Symbol("wx-recipe-editor");
|
|
27
|
+
function Re(i) {
|
|
28
|
+
H(L, i);
|
|
29
|
+
}
|
|
30
|
+
function ue() {
|
|
31
|
+
return K(L, null);
|
|
32
|
+
}
|
|
33
|
+
const he = {
|
|
34
|
+
module: {
|
|
35
|
+
group: "Recipes",
|
|
36
|
+
recipes: "Recipes",
|
|
37
|
+
categories: "Categories",
|
|
38
|
+
nutrients: "Rich in"
|
|
39
|
+
},
|
|
40
|
+
panel: {
|
|
41
|
+
new: "New recipe",
|
|
42
|
+
"new-title": "New recipe",
|
|
43
|
+
"field-title": "Title",
|
|
44
|
+
create: "Create",
|
|
45
|
+
cancel: "Cancel",
|
|
46
|
+
search: "Search by title or address",
|
|
47
|
+
empty: "No recipes yet.",
|
|
48
|
+
"empty-help": "A recipe is a page of the site with a gallery, ingredients, a method and nutrition, filed under categories.",
|
|
49
|
+
"empty-search": "Nothing matches that.",
|
|
50
|
+
"empty-bin": "The bin is empty.",
|
|
51
|
+
"status-draft": "Draft",
|
|
52
|
+
"status-published": "Live",
|
|
53
|
+
"status-modified": "Live",
|
|
54
|
+
"status-unpublished": "Off the site",
|
|
55
|
+
edits: "edits",
|
|
56
|
+
"no-address": "No address in this language",
|
|
57
|
+
"main-category": "The main category: it goes in the breadcrumbs",
|
|
58
|
+
minutes: ":count min",
|
|
59
|
+
"hours-minutes": ":hours h :minutes min",
|
|
60
|
+
hours: ":hours h",
|
|
61
|
+
"view-all": "All",
|
|
62
|
+
"view-published": "Live",
|
|
63
|
+
"view-draft": "Drafts",
|
|
64
|
+
"view-unpublished": "Off the site",
|
|
65
|
+
bin: "Bin",
|
|
66
|
+
"filter-category": "Category",
|
|
67
|
+
"any-category": "Any category",
|
|
68
|
+
"filter-nutrient": "Rich in",
|
|
69
|
+
"any-nutrient": "Rich in anything",
|
|
70
|
+
"filter-service": "Service",
|
|
71
|
+
"any-service": "Any service",
|
|
72
|
+
"order-all": "Drag to change the order on the site. It is the one order recipes have.",
|
|
73
|
+
"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.",
|
|
74
|
+
open: "Open",
|
|
75
|
+
"open-on-site": "Open on the site",
|
|
76
|
+
"copy-address": "Copy the address",
|
|
77
|
+
"address-copied": "The address is on the clipboard.",
|
|
78
|
+
publish: "Publish",
|
|
79
|
+
unpublish: "Take off the site",
|
|
80
|
+
delete: "Delete",
|
|
81
|
+
restore: "Restore",
|
|
82
|
+
"delete-title": "Delete “:title”?",
|
|
83
|
+
"delete-text": "It goes to the bin and comes off the site, and its address is free again.",
|
|
84
|
+
deleted: "The recipe is in the bin.",
|
|
85
|
+
restored: "The recipe is back.",
|
|
86
|
+
published: "The recipe is on the site.",
|
|
87
|
+
"unpublished-done": "The recipe is off the site.",
|
|
88
|
+
"reorder-failed": "The new order was not saved."
|
|
89
|
+
},
|
|
90
|
+
recipe: {
|
|
91
|
+
trail: "Where this recipe sits",
|
|
92
|
+
untitled: "Untitled",
|
|
93
|
+
save: "Save",
|
|
94
|
+
"save-failed": "The recipe was not saved.",
|
|
95
|
+
"publish-title": "Put “:title” on the site?",
|
|
96
|
+
"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.",
|
|
97
|
+
"publish-nowhere": "It has no address in this language yet, so nothing will answer.",
|
|
98
|
+
preview: "Preview",
|
|
99
|
+
discard: "Discard changes",
|
|
100
|
+
discarded: "The recipe is back to what is published.",
|
|
101
|
+
"conflict-title": "The recipe changed while you were editing",
|
|
102
|
+
"conflict-mine": "Keep mine",
|
|
103
|
+
"conflict-theirs": "Take the newer version",
|
|
104
|
+
"conflict-theirs-title": "Give up what you wrote?",
|
|
105
|
+
"conflict-theirs-text": "The recipe is read again as it now is, and what you have typed since goes.",
|
|
106
|
+
leave: "Leave",
|
|
107
|
+
"leave-title": "Leave without saving?",
|
|
108
|
+
"leave-text": "The recipe could not be saved, and what you wrote is not on the server.",
|
|
109
|
+
"live-since": "On the site since :date",
|
|
110
|
+
"address-moving": "The address is changing. The old one keeps working and leads to the new one.",
|
|
111
|
+
"history-empty": "This recipe has never been published.",
|
|
112
|
+
version: "#:number",
|
|
113
|
+
"version-live": "On the site",
|
|
114
|
+
"source-panel": "From the panel",
|
|
115
|
+
"source-mcp": "By an agent",
|
|
116
|
+
"source-import": "Imported",
|
|
117
|
+
"restore-title": "Restore version :number?",
|
|
118
|
+
"restore-text": "It becomes the draft. The site keeps showing what is published until you publish this.",
|
|
119
|
+
"restore-version": "Restore",
|
|
120
|
+
"restored-version": "Version :number is now the draft."
|
|
121
|
+
},
|
|
122
|
+
category: {
|
|
123
|
+
new: "New category",
|
|
124
|
+
empty: "No categories yet.",
|
|
125
|
+
"empty-help": "A category is a section of the recipes with a page of its own.",
|
|
126
|
+
order: "Drag to change the order of the categories on the site.",
|
|
127
|
+
hidden: "Hidden from the site",
|
|
128
|
+
"no-address": "No address in this language",
|
|
129
|
+
recipes: "Recipes: :count",
|
|
130
|
+
"show-recipes": "Show its recipes",
|
|
131
|
+
"delete-blocked": "This category still holds recipes. Move them first.",
|
|
132
|
+
"delete-text": "It goes to the bin and its page comes off the site. Its recipes stay.",
|
|
133
|
+
deleted: "The category is in the bin.",
|
|
134
|
+
saved: "The category is saved.",
|
|
135
|
+
"field-title": "Title",
|
|
136
|
+
"field-slug": "Address",
|
|
137
|
+
"address-moving": "The address is changing. The old one keeps working and leads to the new one."
|
|
138
|
+
},
|
|
139
|
+
nutrient: {
|
|
140
|
+
new: "New nutrient",
|
|
141
|
+
empty: "Nothing here yet.",
|
|
142
|
+
"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.",
|
|
143
|
+
order: "Drag to change the order of the chips and of the filter on the site.",
|
|
144
|
+
hidden: "Hidden from the site",
|
|
145
|
+
recipes: "Recipes: :count",
|
|
146
|
+
"show-recipes": "Show its recipes",
|
|
147
|
+
"delete-blocked": "Recipes are still marked with this. Take it off them first.",
|
|
148
|
+
"delete-text": "It goes to the bin and drops out of the filter. The recipes stay.",
|
|
149
|
+
deleted: "It is in the bin.",
|
|
150
|
+
saved: "Saved.",
|
|
151
|
+
"field-title": "Title"
|
|
152
|
+
}
|
|
153
|
+
}, V = /* @__PURE__ */ new WeakSet();
|
|
154
|
+
function z() {
|
|
155
|
+
try {
|
|
156
|
+
const { i18n: i } = I();
|
|
157
|
+
V.has(i) || (i.defaults("webx-recipes", he), V.add(i));
|
|
158
|
+
} catch {
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
const ge = { class: "wx-recipe-history-tab" }, me = { class: "wx-recipe-history" }, fe = { class: "wx-recipe-history__number" }, ve = { class: "wx-recipe-history__who" }, we = /* @__PURE__ */ M({
|
|
162
|
+
name: "WxRecipeHistory",
|
|
163
|
+
__name: "RecipeHistory",
|
|
164
|
+
setup(i) {
|
|
165
|
+
const t = I(), n = B(t), e = ue();
|
|
166
|
+
z();
|
|
167
|
+
const r = P("webx-recipes"), x = q(), l = b(null), u = b(!1), o = S(() => e?.recipe.value ?? null), g = S(() => e?.canManage ?? !1), v = S(
|
|
168
|
+
() => o.value?.status === "published" || o.value?.status === "modified" ? l.value?.[0]?.number ?? null : null
|
|
169
|
+
);
|
|
170
|
+
async function _() {
|
|
171
|
+
const a = o.value;
|
|
172
|
+
if (a)
|
|
173
|
+
try {
|
|
174
|
+
l.value = await n.versions(a.id);
|
|
175
|
+
} catch (d) {
|
|
176
|
+
l.value = [], C.danger(x(d));
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
async function k(a) {
|
|
180
|
+
const d = o.value;
|
|
181
|
+
if (!(!d || !await oe({
|
|
182
|
+
title: r("recipe.restore-title", { number: a.number }),
|
|
183
|
+
message: r("recipe.restore-text"),
|
|
184
|
+
confirmText: r("recipe.restore-version"),
|
|
185
|
+
cancelText: r("panel.cancel")
|
|
186
|
+
}))) {
|
|
187
|
+
u.value = !0;
|
|
188
|
+
try {
|
|
189
|
+
await n.restoreVersion(d.id, a.number), await e?.reload(), C.success(r("recipe.restored-version", { number: a.number }));
|
|
190
|
+
} catch (N) {
|
|
191
|
+
C.danger(x(N));
|
|
192
|
+
} finally {
|
|
193
|
+
u.value = !1;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return U(
|
|
198
|
+
() => [o.value?.id, o.value?.published_at, o.value?.updated_at],
|
|
199
|
+
() => {
|
|
200
|
+
_();
|
|
201
|
+
},
|
|
202
|
+
{ immediate: !0 }
|
|
203
|
+
), (a, d) => (p(), T("div", ge, [
|
|
204
|
+
R("div", me, [
|
|
205
|
+
l.value === null ? (p(), w(s(ie), {
|
|
206
|
+
key: 0,
|
|
207
|
+
class: "wx-recipe-history__ghost",
|
|
208
|
+
rows: 3
|
|
209
|
+
})) : l.value.length === 0 ? (p(), w(s(re), {
|
|
210
|
+
key: 1,
|
|
211
|
+
description: s(r)("recipe.history-empty")
|
|
212
|
+
}, null, 8, ["description"])) : (p(!0), T(D, { key: 2 }, F(l.value, (c) => (p(), T("div", {
|
|
213
|
+
key: c.number,
|
|
214
|
+
class: j(["wx-recipe-history__row", { "is-live": c.number === v.value }])
|
|
215
|
+
}, [
|
|
216
|
+
R("code", fe, m(s(r)("recipe.version", { number: c.number })), 1),
|
|
217
|
+
R("div", ve, [
|
|
218
|
+
c.created_at ? (p(), w(s(ee), {
|
|
219
|
+
key: 0,
|
|
220
|
+
value: c.created_at,
|
|
221
|
+
size: "md",
|
|
222
|
+
tone: "default"
|
|
223
|
+
}, null, 8, ["value"])) : $("", !0),
|
|
224
|
+
f(s(se), {
|
|
225
|
+
size: "sm",
|
|
226
|
+
tone: "muted"
|
|
227
|
+
}, {
|
|
228
|
+
default: h(() => [
|
|
229
|
+
y(m(c.author ?? s(r)(`recipe.source-${c.source}`)) + " ", 1),
|
|
230
|
+
c.comment ? (p(), T(D, { key: 0 }, [
|
|
231
|
+
y(" · " + m(c.comment), 1)
|
|
232
|
+
], 64)) : $("", !0)
|
|
233
|
+
]),
|
|
234
|
+
_: 2
|
|
235
|
+
}, 1024)
|
|
236
|
+
]),
|
|
237
|
+
c.number === v.value ? (p(), w(s(ne), {
|
|
238
|
+
key: 0,
|
|
239
|
+
type: "success",
|
|
240
|
+
dot: ""
|
|
241
|
+
}, {
|
|
242
|
+
default: h(() => [
|
|
243
|
+
y(m(s(r)("recipe.version-live")), 1)
|
|
244
|
+
]),
|
|
245
|
+
_: 1
|
|
246
|
+
})) : $("", !0),
|
|
247
|
+
g.value && c.number !== v.value ? (p(), w(s(W), {
|
|
248
|
+
key: 1,
|
|
249
|
+
size: "sm",
|
|
250
|
+
variant: "outline",
|
|
251
|
+
loading: u.value,
|
|
252
|
+
onClick: (N) => k(c)
|
|
253
|
+
}, {
|
|
254
|
+
default: h(() => [
|
|
255
|
+
y(m(s(r)("recipe.restore-version")), 1)
|
|
256
|
+
]),
|
|
257
|
+
_: 1
|
|
258
|
+
}, 8, ["loading", "onClick"])) : $("", !0)
|
|
259
|
+
], 2))), 128))
|
|
260
|
+
]),
|
|
261
|
+
f(s(te), {
|
|
262
|
+
id: o.value?.id,
|
|
263
|
+
entity: "recipes",
|
|
264
|
+
screen: "recipes.form",
|
|
265
|
+
"can-restore": g.value,
|
|
266
|
+
stamp: [o.value?.published_at, o.value?.updated_at],
|
|
267
|
+
onRestored: d[0] || (d[0] = (c) => s(e)?.reload())
|
|
268
|
+
}, null, 8, ["id", "can-restore", "stamp"])
|
|
269
|
+
]));
|
|
270
|
+
}
|
|
271
|
+
}), ye = (i, t) => {
|
|
272
|
+
const n = i.__vccOpts || i;
|
|
273
|
+
for (const [e, r] of t)
|
|
274
|
+
n[e] = r;
|
|
275
|
+
return n;
|
|
276
|
+
}, be = /* @__PURE__ */ ye(we, [["__scopeId", "data-v-23c74649"]]);
|
|
277
|
+
function xe(i = "/recipes") {
|
|
278
|
+
return {
|
|
279
|
+
api: "recipes/categories",
|
|
280
|
+
path: `${i}/categories`,
|
|
281
|
+
name: "webx.recipes.categories",
|
|
282
|
+
module: "recipe-categories",
|
|
283
|
+
screen: "recipes.category-form",
|
|
284
|
+
manage: "recipes.categories.manage",
|
|
285
|
+
count: "recipes_count",
|
|
286
|
+
// A category has no order of its own inside it (decision 4): the link shows what is in it.
|
|
287
|
+
items: (t) => ({ path: i, query: { category: String(t) } }),
|
|
288
|
+
words: {
|
|
289
|
+
new: "webx-recipes::category.new",
|
|
290
|
+
empty: "webx-recipes::category.empty",
|
|
291
|
+
"empty-help": "webx-recipes::category.empty-help",
|
|
292
|
+
order: "webx-recipes::category.order",
|
|
293
|
+
hidden: "webx-recipes::category.hidden",
|
|
294
|
+
"no-address": "webx-recipes::category.no-address",
|
|
295
|
+
count: "webx-recipes::category.recipes",
|
|
296
|
+
"show-items": "webx-recipes::category.show-recipes",
|
|
297
|
+
"delete-blocked": "webx-recipes::category.delete-blocked",
|
|
298
|
+
"delete-text": "webx-recipes::category.delete-text",
|
|
299
|
+
deleted: "webx-recipes::category.deleted",
|
|
300
|
+
saved: "webx-recipes::category.saved",
|
|
301
|
+
"field-title": "webx-recipes::category.field-title",
|
|
302
|
+
"field-slug": "webx-recipes::category.field-slug",
|
|
303
|
+
"address-moving": "webx-recipes::category.address-moving"
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
function _e(i = "/recipes") {
|
|
308
|
+
return {
|
|
309
|
+
api: "recipes/nutrients",
|
|
310
|
+
path: `${i}/nutrients`,
|
|
311
|
+
name: "webx.recipes.nutrients",
|
|
312
|
+
module: "recipe-nutrients",
|
|
313
|
+
screen: "recipes.nutrient-form",
|
|
314
|
+
manage: "recipes.categories.manage",
|
|
315
|
+
count: "recipes_count",
|
|
316
|
+
items: (t) => ({ path: i, query: { nutrient: String(t) } }),
|
|
317
|
+
words: {
|
|
318
|
+
new: "webx-recipes::nutrient.new",
|
|
319
|
+
empty: "webx-recipes::nutrient.empty",
|
|
320
|
+
"empty-help": "webx-recipes::nutrient.empty-help",
|
|
321
|
+
order: "webx-recipes::nutrient.order",
|
|
322
|
+
hidden: "webx-recipes::nutrient.hidden",
|
|
323
|
+
count: "webx-recipes::nutrient.recipes",
|
|
324
|
+
"show-items": "webx-recipes::nutrient.show-recipes",
|
|
325
|
+
"delete-blocked": "webx-recipes::nutrient.delete-blocked",
|
|
326
|
+
"delete-text": "webx-recipes::nutrient.delete-text",
|
|
327
|
+
deleted: "webx-recipes::nutrient.deleted",
|
|
328
|
+
saved: "webx-recipes::nutrient.saved",
|
|
329
|
+
"field-title": "webx-recipes::nutrient.field-title"
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
function Se(i = {}) {
|
|
334
|
+
const t = i.path ?? "/recipes";
|
|
335
|
+
return [
|
|
336
|
+
{
|
|
337
|
+
id: "recipes",
|
|
338
|
+
path: t,
|
|
339
|
+
routes: [
|
|
340
|
+
{
|
|
341
|
+
path: t,
|
|
342
|
+
name: "webx.recipes",
|
|
343
|
+
component: () => import("./RecipesPage-CylQ5S5w.js"),
|
|
344
|
+
props: { base: t }
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
path: `${t}/:id(\\d+)`,
|
|
348
|
+
name: "webx.recipes.edit",
|
|
349
|
+
component: () => import("./RecipeEditorPage-DcvFY_ja.js"),
|
|
350
|
+
props: { base: t }
|
|
351
|
+
}
|
|
352
|
+
],
|
|
353
|
+
/*
|
|
354
|
+
* The one part of `recipes.form` only this module can draw. Everything else on it is the
|
|
355
|
+
* panel's (`wx-slug`, `wx-categories`, `wx-relations`, `wx-gallery`, `wx-rich-text`).
|
|
356
|
+
*/
|
|
357
|
+
types: {
|
|
358
|
+
"wx-recipe-history": { component: be, kind: "display" }
|
|
359
|
+
}
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
id: "recipe-categories",
|
|
363
|
+
path: `${t}/categories`,
|
|
364
|
+
routes: A(xe(t))
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
id: "recipe-nutrients",
|
|
368
|
+
path: `${t}/nutrients`,
|
|
369
|
+
routes: A(_e(t))
|
|
370
|
+
}
|
|
371
|
+
];
|
|
372
|
+
}
|
|
373
|
+
function Ce(i, t) {
|
|
374
|
+
if (i == null || i <= 0) return "";
|
|
375
|
+
const n = Math.floor(i / 60), e = i % 60;
|
|
376
|
+
return n === 0 ? t("panel.minutes", { count: e }) : e === 0 ? t("panel.hours", { hours: n }) : t("panel.hours-minutes", { hours: n, minutes: e });
|
|
377
|
+
}
|
|
378
|
+
const We = /* @__PURE__ */ M({
|
|
379
|
+
__name: "RecipeCreateDialog",
|
|
380
|
+
setup(i) {
|
|
381
|
+
const { open: t, resolve: n, dismiss: e } = ae(), r = I(), x = B(r);
|
|
382
|
+
z();
|
|
383
|
+
const l = P("webx-recipes"), u = b(""), o = b(!1), g = b({}), v = G("field");
|
|
384
|
+
J(async () => {
|
|
385
|
+
await Q(), v.value?.querySelector("input")?.focus();
|
|
386
|
+
});
|
|
387
|
+
async function _() {
|
|
388
|
+
if (!(u.value.trim() === "" || o.value)) {
|
|
389
|
+
o.value = !0, g.value = {};
|
|
390
|
+
try {
|
|
391
|
+
n((await x.create({ title: u.value.trim() })).recipe);
|
|
392
|
+
} catch (k) {
|
|
393
|
+
g.value = k.body?.errors ?? {};
|
|
394
|
+
} finally {
|
|
395
|
+
o.value = !1;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return (k, a) => (p(), w(s(ce), {
|
|
400
|
+
open: s(t),
|
|
401
|
+
"onUpdate:open": a[2] || (a[2] = (d) => X(t) ? t.value = d : null),
|
|
402
|
+
title: s(l)("panel.new-title"),
|
|
403
|
+
width: 440
|
|
404
|
+
}, {
|
|
405
|
+
footer: h(() => [
|
|
406
|
+
f(s(pe), { size: "sm" }, {
|
|
407
|
+
default: h(() => [
|
|
408
|
+
f(s(W), {
|
|
409
|
+
variant: "outline",
|
|
410
|
+
onClick: a[1] || (a[1] = (d) => s(e)())
|
|
411
|
+
}, {
|
|
412
|
+
default: h(() => [
|
|
413
|
+
y(m(s(l)("panel.cancel")), 1)
|
|
414
|
+
]),
|
|
415
|
+
_: 1
|
|
416
|
+
}),
|
|
417
|
+
f(s(W), {
|
|
418
|
+
type: "primary",
|
|
419
|
+
loading: o.value,
|
|
420
|
+
disabled: u.value.trim() === "",
|
|
421
|
+
onClick: _
|
|
422
|
+
}, {
|
|
423
|
+
default: h(() => [
|
|
424
|
+
y(m(s(l)("panel.create")), 1)
|
|
425
|
+
]),
|
|
426
|
+
_: 1
|
|
427
|
+
}, 8, ["loading", "disabled"])
|
|
428
|
+
]),
|
|
429
|
+
_: 1
|
|
430
|
+
})
|
|
431
|
+
]),
|
|
432
|
+
default: h(() => [
|
|
433
|
+
f(s(le), {
|
|
434
|
+
label: s(l)("panel.field-title"),
|
|
435
|
+
error: g.value.title?.[0] ?? g.value.slug?.[0],
|
|
436
|
+
required: ""
|
|
437
|
+
}, {
|
|
438
|
+
default: h(() => [
|
|
439
|
+
R("div", {
|
|
440
|
+
ref_key: "field",
|
|
441
|
+
ref: v
|
|
442
|
+
}, [
|
|
443
|
+
f(s(de), {
|
|
444
|
+
modelValue: u.value,
|
|
445
|
+
"onUpdate:modelValue": a[0] || (a[0] = (d) => u.value = d),
|
|
446
|
+
"aria-label": s(l)("panel.field-title"),
|
|
447
|
+
onKeyup: Y(_, ["enter"])
|
|
448
|
+
}, null, 8, ["modelValue", "aria-label"])
|
|
449
|
+
], 512)
|
|
450
|
+
]),
|
|
451
|
+
_: 1
|
|
452
|
+
}, 8, ["label", "error"])
|
|
453
|
+
]),
|
|
454
|
+
_: 1
|
|
455
|
+
}, 8, ["open", "title"]));
|
|
456
|
+
}
|
|
457
|
+
}), Ie = O(() => import("./RecipesPage-CylQ5S5w.js")), Ne = O(() => import("./RecipeEditorPage-DcvFY_ja.js"));
|
|
458
|
+
export {
|
|
459
|
+
E as R,
|
|
460
|
+
Ne as W,
|
|
461
|
+
We as _,
|
|
462
|
+
ye as a,
|
|
463
|
+
be as b,
|
|
464
|
+
B as c,
|
|
465
|
+
Ie as d,
|
|
466
|
+
L as e,
|
|
467
|
+
Ce as f,
|
|
468
|
+
_e as g,
|
|
469
|
+
Se as h,
|
|
470
|
+
he as i,
|
|
471
|
+
ue as j,
|
|
472
|
+
Re as p,
|
|
473
|
+
xe as r,
|
|
474
|
+
z as u
|
|
475
|
+
};
|
|
476
|
+
//# sourceMappingURL=index-rwJ6c36l.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-rwJ6c36l.js","sources":["../src/api.ts","../src/editor.ts","../src/messages.ts","../src/i18n.ts","../src/RecipeHistory.vue","../src/module.ts","../src/minutes.ts","../src/RecipeCreateDialog.vue","../src/index.ts"],"sourcesContent":["import { reorderItems, type AdminContext } from '@webx-ui/module-admin'\nimport type {\n RecipeDetail,\n RecipeInput,\n RecipeQuery,\n RecipeRow,\n RecipeSave,\n RecipesList,\n RecipeVersion,\n} from './types'\n\nexport interface RecipesApi {\n /** Every recipe, with what the list can be narrowed by. */\n list(query?: RecipeQuery): Promise<RecipesList>\n /** One recipe as its editor opens it: the record, the screen's values and the revision. */\n get(id: number): Promise<RecipeDetail>\n /** A new recipe, made in one transaction: a refused address leaves no bare row behind. */\n create(input: RecipeInput): Promise<Pick<RecipeDetail, 'recipe' | 'values'>>\n /**\n * Save the draft. Refused with a 409 when the revision is stale — the body is a\n * {@link RecipeConflict} with the recipe as it now is.\n */\n save(id: number, input: RecipeSave): Promise<RecipeDetail>\n /** Throw away what is waiting and keep what the site is showing. */\n discard(id: number): Promise<RecipeDetail>\n /** `revision` is what the editor held: a draft that moved on since is answered 409, not published. */\n publish(id: number, revision?: string): Promise<RecipeRow>\n unpublish(id: number): Promise<RecipeRow>\n remove(id: number): Promise<void>\n restore(id: number): Promise<RecipeRow>\n /** The publications, newest first. */\n versions(id: number): Promise<RecipeVersion[]>\n /** An old publication becomes the draft; putting it on the site is a separate step. */\n restoreVersion(id: number, number: number): Promise<RecipeDetail>\n /** The one order recipes have — never a category's (decision 4). */\n reorder(ids: number[]): Promise<void>\n}\n\n/** The path of the list under the panel's API. */\nexport const RECIPES_API = 'recipes'\n\n/**\n * Everything under `/recipes`, below the panel's API path — except the categories and the\n * nutrients, which are the panel's shared category API under `recipes/categories` and\n * `recipes/nutrients`.\n */\nexport function createRecipesApi(admin: AdminContext): RecipesApi {\n const base = `${admin.apiPath}/${RECIPES_API}`\n const data = <T>(body: { data: T }): T => body.data\n\n return {\n list: (query = {}) => {\n const search = new URLSearchParams()\n\n // Only what was asked for: `category=` would ask for the recipes of no category.\n if (query.q) search.set('q', query.q)\n if (query.category != null) search.set('category', String(query.category))\n if (query.nutrient != null) search.set('nutrient', String(query.nutrient))\n if (query.service != null) search.set('service', String(query.service))\n if (query.status) search.set('status', query.status)\n if (query.trashed) search.set('trashed', '1')\n\n return admin.http.get<RecipesList>(search.size > 0 ? `${base}?${search}` : base)\n },\n get: (id) => admin.http.get<{ data: RecipeDetail }>(`${base}/${id}`).then(data),\n create: (input) =>\n admin.http.post<{ data: Pick<RecipeDetail, 'recipe' | 'values'> }>(base, input).then(data),\n save: (id, input) => admin.http.put<{ data: RecipeDetail }>(`${base}/${id}`, input).then(data),\n discard: (id) =>\n admin.http.post<{ data: RecipeDetail }>(`${base}/${id}/discard`, {}).then(data),\n publish: (id, revision) =>\n admin.http\n .post<{ data: RecipeRow }>(`${base}/${id}/publish`, revision ? { revision } : {})\n .then(data),\n unpublish: (id) =>\n admin.http.post<{ data: RecipeRow }>(`${base}/${id}/unpublish`, {}).then(data),\n remove: (id) => admin.http.delete<void>(`${base}/${id}`).then(() => undefined),\n restore: (id) => admin.http.post<{ data: RecipeRow }>(`${base}/${id}/restore`, {}).then(data),\n versions: (id) =>\n admin.http.get<{ data: RecipeVersion[] }>(`${base}/${id}/versions`).then(data),\n restoreVersion: (id, number) =>\n admin.http\n .post<{ data: RecipeDetail }>(`${base}/${id}/versions/${number}/restore`, {})\n .then(data),\n reorder: (ids) => reorderItems(admin, RECIPES_API, ids, null),\n }\n}\n","import { inject, provide, type InjectionKey, type Ref } from 'vue'\nimport type { RecipeRow } from './types'\n\n/**\n * What the editor knows and the nodes of `recipes.form` do not: the recipe being edited, and a\n * way to read it again. The history needs both — its list is keyed by the recipe, and a restored\n * version has to reach the fields the form is bound to.\n */\nexport interface RecipeEditorContext {\n /** The recipe as the server last answered it, or `null` while the first request is out. */\n recipe: Ref<RecipeRow | null>\n /** Whether this administrator may write at all. */\n canManage: boolean\n /** Ask the server for the recipe again — after a restore, a publication, a discard. */\n reload(): Promise<void>\n}\n\nexport const recipeEditorKey: InjectionKey<RecipeEditorContext> = Symbol('wx-recipe-editor')\n\nexport function provideRecipeEditor(editor: RecipeEditorContext): void {\n provide(recipeEditorKey, editor)\n}\n\n/** The editor above this node, or `null` outside one — a demo or a test draws nothing. */\nexport function useRecipeEditor(): RecipeEditorContext | null {\n return inject(recipeEditorKey, null)\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-recipes::` and overrides\n * these; `messages.test.ts` keeps the two sets of keys equal.\n */\nexport const recipesMessages: Record<string, Messages> = {\n module: {\n group: 'Recipes',\n recipes: 'Recipes',\n categories: 'Categories',\n nutrients: 'Rich in',\n },\n panel: {\n new: 'New recipe',\n 'new-title': 'New recipe',\n 'field-title': 'Title',\n create: 'Create',\n cancel: 'Cancel',\n search: 'Search by title or address',\n empty: 'No recipes yet.',\n 'empty-help':\n 'A recipe is a page of the site with a gallery, ingredients, a method and nutrition, filed under categories.',\n 'empty-search': 'Nothing matches that.',\n 'empty-bin': 'The bin is empty.',\n\n 'status-draft': 'Draft',\n 'status-published': 'Live',\n 'status-modified': 'Live',\n 'status-unpublished': 'Off the site',\n edits: 'edits',\n 'no-address': 'No address in this language',\n 'main-category': 'The main category: it goes in the breadcrumbs',\n minutes: ':count min',\n 'hours-minutes': ':hours h :minutes min',\n hours: ':hours h',\n\n 'view-all': 'All',\n 'view-published': 'Live',\n 'view-draft': 'Drafts',\n 'view-unpublished': 'Off the site',\n bin: 'Bin',\n\n 'filter-category': 'Category',\n 'any-category': 'Any category',\n 'filter-nutrient': 'Rich in',\n 'any-nutrient': 'Rich in anything',\n 'filter-service': 'Service',\n 'any-service': 'Any service',\n\n 'order-all': 'Drag to change the order on the site. It is the one order recipes have.',\n 'order-locked':\n '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.',\n\n open: 'Open',\n 'open-on-site': 'Open on the site',\n 'copy-address': 'Copy the address',\n 'address-copied': 'The address is on the clipboard.',\n publish: 'Publish',\n unpublish: 'Take off the site',\n delete: 'Delete',\n restore: 'Restore',\n 'delete-title': 'Delete “:title”?',\n 'delete-text': 'It goes to the bin and comes off the site, and its address is free again.',\n deleted: 'The recipe is in the bin.',\n restored: 'The recipe is back.',\n published: 'The recipe is on the site.',\n 'unpublished-done': 'The recipe is off the site.',\n 'reorder-failed': 'The new order was not saved.',\n },\n recipe: {\n trail: 'Where this recipe sits',\n untitled: 'Untitled',\n save: 'Save',\n 'save-failed': 'The recipe was not saved.',\n 'publish-title': 'Put “:title” on the site?',\n 'publish-text':\n 'It answers at :address from the moment you do, for everyone — with its categories, services and similar recipes as they are chosen now.',\n 'publish-nowhere': 'It has no address in this language yet, so nothing will answer.',\n preview: 'Preview',\n discard: 'Discard changes',\n discarded: 'The recipe is back to what is published.',\n 'conflict-title': 'The recipe changed while you were editing',\n 'conflict-mine': 'Keep mine',\n 'conflict-theirs': 'Take the newer version',\n 'conflict-theirs-title': 'Give up what you wrote?',\n 'conflict-theirs-text':\n 'The recipe is read again as it now is, and what you have typed since goes.',\n leave: 'Leave',\n 'leave-title': 'Leave without saving?',\n 'leave-text': 'The recipe could not be saved, and what you wrote is not on the server.',\n 'live-since': 'On the site since :date',\n 'address-moving':\n 'The address is changing. The old one keeps working and leads to the new one.',\n 'history-empty': 'This recipe has never been published.',\n version: '#:number',\n 'version-live': 'On the site',\n 'source-panel': 'From the panel',\n 'source-mcp': 'By an agent',\n 'source-import': 'Imported',\n 'restore-title': 'Restore version :number?',\n 'restore-text':\n 'It becomes the draft. The site keeps showing what is published until you publish this.',\n 'restore-version': 'Restore',\n 'restored-version': 'Version :number is now the draft.',\n },\n category: {\n new: 'New category',\n empty: 'No categories yet.',\n 'empty-help': 'A category is a section of the recipes with a page of its own.',\n order: 'Drag to change the order of the categories on the site.',\n hidden: 'Hidden from the site',\n 'no-address': 'No address in this language',\n recipes: 'Recipes: :count',\n 'show-recipes': 'Show its recipes',\n 'delete-blocked': 'This category still holds recipes. Move them first.',\n 'delete-text': 'It goes to the bin and its page comes off the site. Its recipes stay.',\n deleted: 'The category is in the bin.',\n saved: 'The category is saved.',\n 'field-title': 'Title',\n 'field-slug': 'Address',\n 'address-moving':\n 'The address is changing. The old one keeps working and leads to the new one.',\n },\n nutrient: {\n new: 'New nutrient',\n empty: 'Nothing here yet.',\n 'empty-help':\n '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.',\n order: 'Drag to change the order of the chips and of the filter on the site.',\n hidden: 'Hidden from the site',\n recipes: 'Recipes: :count',\n 'show-recipes': 'Show its recipes',\n 'delete-blocked': 'Recipes are still marked with this. Take it off them first.',\n 'delete-text': 'It goes to the bin and drops out of the filter. The recipes stay.',\n deleted: 'It is in the bin.',\n saved: 'Saved.',\n 'field-title': 'Title',\n },\n}\n","import { useAdmin } from '@webx-ui/module-admin'\nimport { recipesMessages } 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 useRecipesMessages(): void {\n try {\n const { i18n } = useAdmin()\n\n if (!seeded.has(i18n)) {\n i18n.defaults('webx-recipes', recipesMessages)\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, ref, watch } from 'vue'\nimport { useAdmin, useErrorText, useTranslate, WxDate, WxDrafts } from '@webx-ui/module-admin'\nimport { confirm, toast, WxBadge, WxButton, WxEmpty, WxSkeleton, WxText } from '@webx-ui/core'\nimport { createRecipesApi } from './api'\nimport { useRecipeEditor } from './editor'\nimport { useRecipesMessages } from './i18n'\nimport type { RecipeVersion } from './types'\n\n/**\n * What was published, when, by whom and from where — and under it, in a list of its own, the\n * copies of the draft: the autosaves, and a draft somebody else's save wrote over.\n *\n * Restoring makes the old version the draft. Publishing it is the same separate step it always\n * is, so nothing here changes the site by itself.\n */\ndefineOptions({ name: 'WxRecipeHistory' })\n\nconst context = useAdmin()\nconst api = createRecipesApi(context)\nconst editor = useRecipeEditor()\nuseRecipesMessages()\n\nconst t = useTranslate('webx-recipes')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\nconst versions = ref<RecipeVersion[] | null>(null)\nconst working = ref(false)\n\nconst recipe = computed(() => editor?.recipe.value ?? null)\nconst canManage = computed(() => editor?.canManage ?? false)\n\n/** The newest publication is the site — while the recipe is on it at all. */\nconst live = computed(() =>\n recipe.value?.status === 'published' || recipe.value?.status === 'modified'\n ? (versions.value?.[0]?.number ?? null)\n : null,\n)\n\nasync function load(): Promise<void> {\n const current = recipe.value\n\n if (!current) return\n\n try {\n versions.value = await api.versions(current.id)\n } catch (error) {\n versions.value = []\n toast.danger(message(error))\n }\n}\n\nasync function restore(version: RecipeVersion): Promise<void> {\n const current = recipe.value\n\n if (!current) return\n\n const agreed = await confirm({\n title: t('recipe.restore-title', { number: version.number }),\n message: t('recipe.restore-text'),\n confirmText: t('recipe.restore-version'),\n cancelText: t('panel.cancel'),\n })\n\n if (!agreed) return\n\n working.value = true\n\n try {\n await api.restoreVersion(current.id, version.number)\n // Through the editor rather than with the answer: the values it holds are what the form is\n // bound to, and a version restored underneath it has to reach the fields.\n await editor?.reload()\n toast.success(t('recipe.restored-version', { number: version.number }))\n } catch (error) {\n toast.danger(message(error))\n } finally {\n working.value = false\n }\n}\n\n// The recipe is read again after every publication and every restore, so its stamp is the\n// signal that there is something new to list.\nwatch(\n () => [recipe.value?.id, recipe.value?.published_at, recipe.value?.updated_at],\n () => void load(),\n { immediate: true },\n)\n</script>\n\n<template>\n <div class=\"wx-recipe-history-tab\">\n <div class=\"wx-recipe-history\">\n <wx-skeleton v-if=\"versions === null\" class=\"wx-recipe-history__ghost\" :rows=\"3\" />\n <wx-empty v-else-if=\"versions.length === 0\" :description=\"t('recipe.history-empty')\" />\n <div\n v-for=\"version in versions\"\n v-else\n :key=\"version.number\"\n class=\"wx-recipe-history__row\"\n :class=\"{ 'is-live': version.number === live }\"\n >\n <code class=\"wx-recipe-history__number\">{{\n t('recipe.version', { number: version.number })\n }}</code>\n <div class=\"wx-recipe-history__who\">\n <wx-date v-if=\"version.created_at\" :value=\"version.created_at\" size=\"md\" tone=\"default\" />\n <wx-text size=\"sm\" tone=\"muted\">\n {{ version.author ?? t(`recipe.source-${version.source}`) }}\n <template v-if=\"version.comment\"> · {{ version.comment }}</template>\n </wx-text>\n </div>\n <wx-badge v-if=\"version.number === live\" type=\"success\" dot>{{\n t('recipe.version-live')\n }}</wx-badge>\n <wx-button\n v-if=\"canManage && version.number !== live\"\n size=\"sm\"\n variant=\"outline\"\n :loading=\"working\"\n @click=\"restore(version)\"\n >\n {{ t('recipe.restore-version') }}\n </wx-button>\n </div>\n </div>\n\n <wx-drafts\n :id=\"recipe?.id\"\n entity=\"recipes\"\n screen=\"recipes.form\"\n :can-restore=\"canManage\"\n :stamp=\"[recipe?.published_at, recipe?.updated_at]\"\n @restored=\"editor?.reload()\"\n />\n </div>\n</template>\n\n<style scoped>\n.wx-recipe-history-tab {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-24);\n}\n\n.wx-recipe-history {\n display: flex;\n flex-direction: column;\n border: 1px solid var(--wx-border-default);\n border-radius: var(--wx-radius-md);\n background: var(--wx-bg-surface);\n overflow: hidden;\n}\n\n.wx-recipe-history__ghost {\n padding: var(--wx-space-10) var(--wx-space-12);\n}\n\n.wx-recipe-history__row {\n display: flex;\n align-items: center;\n gap: var(--wx-space-12);\n padding: var(--wx-space-10) var(--wx-space-12);\n border-block-end: 1px solid var(--wx-border-default);\n flex-wrap: wrap;\n}\n\n.wx-recipe-history__row:last-child {\n border-block-end: 0;\n}\n\n.wx-recipe-history__number {\n width: 40px;\n font-family: var(--wx-font-family-mono);\n font-size: var(--wx-font-size-sm);\n color: var(--wx-text-muted);\n}\n\n.wx-recipe-history__who {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-width: 160px;\n}\n</style>\n","import { categoryRoutes, type AdminModule, type CategoriesOptions } from '@webx-ui/module-admin'\nimport RecipeHistory from './RecipeHistory.vue'\n\nexport interface RecipesOptions {\n /** Where the recipes live inside the panel. The categories and the nutrients sit under it. */\n path?: string\n}\n\n/**\n * The categories of recipes as the panel's shared category screens see them: where they answer,\n * what they are edited on and what this module calls them. Exported so that a panel mounting the\n * screens somewhere of its own does not have to repeat the words.\n */\nexport function recipeCategoriesOptions(path = '/recipes'): CategoriesOptions {\n return {\n api: 'recipes/categories',\n path: `${path}/categories`,\n name: 'webx.recipes.categories',\n module: 'recipe-categories',\n screen: 'recipes.category-form',\n manage: 'recipes.categories.manage',\n count: 'recipes_count',\n // A category has no order of its own inside it (decision 4): the link shows what is in it.\n items: (id) => ({ path, query: { category: String(id) } }),\n words: {\n new: 'webx-recipes::category.new',\n empty: 'webx-recipes::category.empty',\n 'empty-help': 'webx-recipes::category.empty-help',\n order: 'webx-recipes::category.order',\n hidden: 'webx-recipes::category.hidden',\n 'no-address': 'webx-recipes::category.no-address',\n count: 'webx-recipes::category.recipes',\n 'show-items': 'webx-recipes::category.show-recipes',\n 'delete-blocked': 'webx-recipes::category.delete-blocked',\n 'delete-text': 'webx-recipes::category.delete-text',\n deleted: 'webx-recipes::category.deleted',\n saved: 'webx-recipes::category.saved',\n 'field-title': 'webx-recipes::category.field-title',\n 'field-slug': 'webx-recipes::category.field-slug',\n 'address-moving': 'webx-recipes::category.address-moving',\n },\n }\n}\n\n/**\n * What a recipe is rich in — the second kind of category a recipe has (decision 8), with no\n * address: the shared list leaves the address line out when the server says there is none, as it\n * does for the categories of the FAQ.\n */\nexport function recipeNutrientsOptions(path = '/recipes'): CategoriesOptions {\n return {\n api: 'recipes/nutrients',\n path: `${path}/nutrients`,\n name: 'webx.recipes.nutrients',\n module: 'recipe-nutrients',\n screen: 'recipes.nutrient-form',\n manage: 'recipes.categories.manage',\n count: 'recipes_count',\n items: (id) => ({ path, query: { nutrient: String(id) } }),\n words: {\n new: 'webx-recipes::nutrient.new',\n empty: 'webx-recipes::nutrient.empty',\n 'empty-help': 'webx-recipes::nutrient.empty-help',\n order: 'webx-recipes::nutrient.order',\n hidden: 'webx-recipes::nutrient.hidden',\n count: 'webx-recipes::nutrient.recipes',\n 'show-items': 'webx-recipes::nutrient.show-recipes',\n 'delete-blocked': 'webx-recipes::nutrient.delete-blocked',\n 'delete-text': 'webx-recipes::nutrient.delete-text',\n deleted: 'webx-recipes::nutrient.deleted',\n saved: 'webx-recipes::nutrient.saved',\n 'field-title': 'webx-recipes::nutrient.field-title',\n },\n }\n}\n\n/**\n * The recipes as sections of the panel: recipes, their categories and what they are rich in\n * (§5.9).\n *\n * Three modules rather than one, because the navigation is one entry per module; the server puts\n * them in the `recipes` group, which is what draws them under one heading. A section whose server\n * half is not installed never appears — the entry is built from the manifest.\n */\nexport function recipes(options: RecipesOptions = {}): AdminModule[] {\n const path = options.path ?? '/recipes'\n\n return [\n {\n id: 'recipes',\n path,\n routes: [\n {\n path,\n name: 'webx.recipes',\n component: () => import('./RecipesPage.vue'),\n props: { base: path },\n },\n {\n path: `${path}/:id(\\\\d+)`,\n name: 'webx.recipes.edit',\n component: () => import('./RecipeEditorPage.vue'),\n props: { base: path },\n },\n ],\n /*\n * The one part of `recipes.form` only this module can draw. Everything else on it is the\n * panel's (`wx-slug`, `wx-categories`, `wx-relations`, `wx-gallery`, `wx-rich-text`).\n */\n types: {\n 'wx-recipe-history': { component: RecipeHistory, kind: 'display' },\n },\n },\n {\n id: 'recipe-categories',\n path: `${path}/categories`,\n routes: categoryRoutes(recipeCategoriesOptions(path)),\n },\n {\n id: 'recipe-nutrients',\n path: `${path}/nutrients`,\n routes: categoryRoutes(recipeNutrientsOptions(path)),\n },\n ]\n}\n","type Translate = (key: string, replace?: Record<string, string | number>) => string\n\n/**\n * `total_minutes` the way the list says it: `45 min`, `1 h 15 min`, `2 h`. The number is at the\n * end of nothing and the start of nothing a plural would bend (CLAUDE.md §4 on `:count`), so one\n * line per shape is enough in every language.\n */\nexport function formatMinutes(minutes: number | null | undefined, t: Translate): string {\n if (minutes == null || minutes <= 0) return ''\n\n const hours = Math.floor(minutes / 60)\n const rest = minutes % 60\n\n if (hours === 0) return t('panel.minutes', { count: rest })\n if (rest === 0) return t('panel.hours', { hours })\n\n return t('panel.hours-minutes', { hours, minutes: rest })\n}\n","<script setup lang=\"ts\">\nimport { nextTick, onMounted, ref, useTemplateRef } from 'vue'\nimport { useAdmin, useTranslate } from '@webx-ui/module-admin'\nimport { useModal, WxButton, WxDialog, WxFormItem, WxInput, WxSpace } from '@webx-ui/core'\nimport { createRecipesApi } from './api'\nimport { useRecipesMessages } from './i18n'\nimport type { RecipeRow } from './types'\n\n/**\n * A new recipe: what it is called, and nothing else. The server makes the address out of the\n * title; the address field on the settings tab is where there is room to say what changing it\n * costs.\n */\nconst { open, resolve, dismiss } = useModal<RecipeRow>()\n\nconst context = useAdmin()\nconst api = createRecipesApi(context)\nuseRecipesMessages()\n\nconst t = useTranslate('webx-recipes')\n\nconst title = ref('')\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\nasync function submit(): Promise<void> {\n if (title.value.trim() === '' || saving.value) return\n\n saving.value = true\n errors.value = {}\n\n try {\n resolve((await api.create({ title: title.value.trim() })).recipe)\n } catch (error) {\n // The address is what can be refused here, and it is refused under the title that made it:\n // the registry sees the categories and the pages too, so \"taken\" can mean taken by either.\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 v-model:open=\"open\" :title=\"t('panel.new-title')\" :width=\"440\">\n <wx-form-item\n :label=\"t('panel.field-title')\"\n :error=\"errors.title?.[0] ?? errors.slug?.[0]\"\n required\n >\n <div ref=\"field\">\n <wx-input v-model=\"title\" :aria-label=\"t('panel.field-title')\" @keyup.enter=\"submit\" />\n </div>\n </wx-form-item>\n\n <template #footer>\n <wx-space size=\"sm\">\n <wx-button variant=\"outline\" @click=\"dismiss()\">{{ t('panel.cancel') }}</wx-button>\n <wx-button type=\"primary\" :loading=\"saving\" :disabled=\"title.trim() === ''\" @click=\"submit\">\n {{ t('panel.create') }}\n </wx-button>\n </wx-space>\n </template>\n </wx-dialog>\n</template>\n","import { defineAsyncComponent } from 'vue'\nexport {\n recipes,\n recipeCategoriesOptions,\n recipeNutrientsOptions,\n type RecipesOptions,\n} from './module'\nexport { createRecipesApi, RECIPES_API, type RecipesApi } from './api'\nexport { recipesMessages } from './messages'\nexport { formatMinutes } from './minutes'\nexport {\n provideRecipeEditor,\n recipeEditorKey,\n useRecipeEditor,\n type RecipeEditorContext,\n} from './editor'\nexport const WxRecipesPage = defineAsyncComponent(() => import('./RecipesPage.vue'))\nexport { default as WxRecipeCreateDialog } from './RecipeCreateDialog.vue'\nexport const WxRecipeEditorPage = defineAsyncComponent(() => import('./RecipeEditorPage.vue'))\nexport { default as WxRecipeHistory } from './RecipeHistory.vue'\nexport type {\n RecipeConflict,\n RecipeDetail,\n RecipeInput,\n RecipeQuery,\n RecipeRow,\n RecipeSave,\n RecipesList,\n RecipeStatus,\n RecipeTermRef,\n RecipeVersion,\n} from './types'\n"],"names":["RECIPES_API","createRecipesApi","admin","base","data","body","query","search","id","input","revision","number","ids","reorderItems","recipeEditorKey","provideRecipeEditor","editor","provide","useRecipeEditor","inject","recipesMessages","seeded","useRecipesMessages","i18n","useAdmin","context","api","t","useTranslate","message","useErrorText","versions","ref","working","recipe","computed","canManage","live","load","current","error","toast","restore","version","confirm","watch","_openBlock","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_createBlock","_unref","WxSkeleton","WxEmpty","_Fragment","_renderList","_hoisted_3","_toDisplayString","_hoisted_4","WxDate","_createVNode","WxText","WxBadge","WxButton","$event","WxDrafts","_cache","recipeCategoriesOptions","path","recipeNutrientsOptions","recipes","options","RecipeHistory","categoryRoutes","formatMinutes","minutes","hours","rest","open","resolve","dismiss","useModal","title","saving","errors","field","useTemplateRef","onMounted","nextTick","submit","WxDialog","WxSpace","WxFormItem","WxInput","WxRecipesPage","defineAsyncComponent","WxRecipeEditorPage"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,8 +3,20 @@ export { createRecipesApi, RECIPES_API, type RecipesApi } from './api';
|
|
|
3
3
|
export { recipesMessages } from './messages';
|
|
4
4
|
export { formatMinutes } from './minutes';
|
|
5
5
|
export { provideRecipeEditor, recipeEditorKey, useRecipeEditor, type RecipeEditorContext, } from './editor';
|
|
6
|
-
export
|
|
6
|
+
export declare const WxRecipesPage: import('vue').DefineComponent<{
|
|
7
|
+
base?: string;
|
|
8
|
+
}, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<{
|
|
9
|
+
base?: string;
|
|
10
|
+
}> & Readonly<{}>, {
|
|
11
|
+
base: string;
|
|
12
|
+
}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, HTMLDivElement>;
|
|
7
13
|
export { default as WxRecipeCreateDialog } from './RecipeCreateDialog';
|
|
8
|
-
export
|
|
14
|
+
export declare const WxRecipeEditorPage: import('vue').DefineComponent<{
|
|
15
|
+
base?: string;
|
|
16
|
+
}, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<{
|
|
17
|
+
base?: string;
|
|
18
|
+
}> & Readonly<{}>, {
|
|
19
|
+
base: string;
|
|
20
|
+
}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, HTMLDivElement>;
|
|
9
21
|
export { default as WxRecipeHistory } from './RecipeHistory';
|
|
10
22
|
export type { RecipeConflict, RecipeDetail, RecipeInput, RecipeQuery, RecipeRow, RecipeSave, RecipesList, RecipeStatus, RecipeTermRef, RecipeVersion, } from './types';
|