@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.
@@ -0,0 +1,380 @@
1
+ import { defineComponent as pe, ref as U, computed as r, watch as K, onBeforeUnmount as ve, openBlock as s, createElementBlock as b, createVNode as i, unref as l, withCtx as u, createElementVNode as p, createBlock as c, createCommentVNode as g, Fragment as D, normalizeClass as O, resolveDynamicComponent as me, mergeProps as ye, toDisplayString as v, createTextVNode as w, renderList as fe } from "vue";
2
+ import { useRoute as be, useRouter as we } from "vue-router";
3
+ import { useAdmin as he, useTranslate as _e, useErrorText as ge, WxListScreen as xe, WxRowMenu as ke, WxAddressNote as qe } from "@webx-ui/module-admin";
4
+ import { createModal as ze, WxInput as Ve, WxIcon as F, WxSelect as j, WxSkeleton as We, WxEmpty as Te, WxSortableList as Re, WxTooltip as Se, WxBadge as $, WxAlert as Ue, toast as x, confirm as $e } from "@webx-ui/core";
5
+ import { c as Be, u as Me, _ as Ne, f as Ae, a as Ce } from "./index-rwJ6c36l.js";
6
+ const Ee = { class: "wx-recipes" }, Ie = { class: "wx-recipes__bar" }, Le = ["src"], Pe = { class: "wx-recipe-row__name" }, De = { class: "wx-recipe-row__title" }, Fe = { class: "wx-recipe-row__address" }, je = { class: "wx-recipe-row__facts" }, Ge = { class: "wx-recipe-row__chips" }, He = { class: "wx-recipe-row__state" }, Je = {
7
+ key: 0,
8
+ class: "wx-recipe-row__time"
9
+ }, Ke = /* @__PURE__ */ pe({
10
+ __name: "RecipesPage",
11
+ props: {
12
+ base: { default: "/recipes" }
13
+ },
14
+ setup(Q) {
15
+ const G = Q, B = he(), m = Be(B), o = be(), k = we();
16
+ Me();
17
+ const a = _e("webx-recipes"), M = ge(), y = U([]), h = U({ categories: [], nutrients: [], services: null }), q = U(!0), X = ze(Ne), z = r(() => B.can("recipes.manage")), Y = r(
18
+ () => B.state.manifest?.modules.find((e) => e.id === "recipes")?.title ?? a("module.recipes")
19
+ ), f = r({
20
+ get: () => typeof o.query.view == "string" ? o.query.view : "",
21
+ set: (e) => {
22
+ k.replace({
23
+ query: { ...o.query, view: e === "" ? void 0 : String(e) }
24
+ });
25
+ }
26
+ }), d = r(() => f.value === "trashed");
27
+ function N(e) {
28
+ const n = o.query[e];
29
+ return typeof n == "string" && n !== "" && Number(n) || null;
30
+ }
31
+ const V = r(() => N("category")), W = r(() => N("nutrient")), T = r(() => N("service")), _ = r(() => typeof o.query.q == "string" ? o.query.q : ""), A = U(_.value), Z = r(() => [
32
+ { value: "", label: a("panel.view-all") },
33
+ { value: "published", label: a("panel.view-published") },
34
+ { value: "draft", label: a("panel.view-draft") },
35
+ { value: "unpublished", label: a("panel.view-unpublished") },
36
+ { value: "trashed", label: a("panel.bin"), icon: "trash" }
37
+ ]), C = r(
38
+ () => f.value === "" && _.value.trim() === "" && V.value === null && W.value === null && T.value === null
39
+ ), H = r(() => z.value && C.value), ee = r(() => a(C.value ? "panel.order-all" : "panel.order-locked")), ae = r(() => _.value !== "" ? a("panel.empty-search") : d.value ? a("panel.empty-bin") : a("panel.empty"));
40
+ async function R() {
41
+ q.value = !0;
42
+ try {
43
+ const e = await m.list({
44
+ q: _.value,
45
+ category: V.value,
46
+ nutrient: W.value,
47
+ service: T.value,
48
+ status: d.value ? "" : f.value,
49
+ trashed: d.value
50
+ });
51
+ y.value = e.data, h.value = e.filters;
52
+ } catch (e) {
53
+ x.danger(M(e));
54
+ } finally {
55
+ q.value = !1;
56
+ }
57
+ }
58
+ K(
59
+ () => [f.value, V.value, W.value, T.value, _.value],
60
+ () => {
61
+ R();
62
+ }
63
+ ), R();
64
+ let E;
65
+ K(A, (e) => {
66
+ clearTimeout(E), E = setTimeout(() => {
67
+ k.replace({ query: { ...o.query, q: e.trim() === "" ? void 0 : e } });
68
+ }, 300);
69
+ }), ve(() => clearTimeout(E));
70
+ function I(e, n) {
71
+ k.replace({
72
+ query: { ...o.query, [e]: typeof n == "number" ? String(n) : void 0 }
73
+ });
74
+ }
75
+ const L = (e) => (e ?? []).map((n) => ({ value: n.id, label: n.title }));
76
+ function J(e) {
77
+ k.push({ path: `${G.base}/${e.id}`, query: { ...o.query } });
78
+ }
79
+ async function le() {
80
+ const e = await X({});
81
+ e && J(e);
82
+ }
83
+ async function te() {
84
+ try {
85
+ await m.reorder(y.value.map((e) => e.id));
86
+ } catch (e) {
87
+ x.danger(M(e, a("panel.reorder-failed"))), await R();
88
+ }
89
+ }
90
+ function ne(e) {
91
+ if (!z.value) return [];
92
+ if (d.value)
93
+ return [
94
+ {
95
+ key: "restore",
96
+ icon: "refresh",
97
+ label: a("panel.restore"),
98
+ run: () => {
99
+ re(e);
100
+ }
101
+ }
102
+ ];
103
+ const n = [
104
+ { key: "open", icon: "edit", label: a("panel.open"), run: () => J(e) }
105
+ ];
106
+ return e.url && e.status !== "draft" && n.push({
107
+ key: "site",
108
+ icon: "external-link",
109
+ label: a("panel.open-on-site"),
110
+ href: e.url
111
+ }), e.url && n.push({
112
+ key: "copy",
113
+ icon: "copy",
114
+ label: a("panel.copy-address"),
115
+ run: () => {
116
+ oe(e);
117
+ }
118
+ }), n.push(
119
+ e.status === "published" || e.status === "modified" ? {
120
+ key: "unpublish",
121
+ icon: "eye-off",
122
+ label: a("panel.unpublish"),
123
+ run: () => {
124
+ S(() => m.unpublish(e.id), a("panel.unpublished-done"));
125
+ }
126
+ } : {
127
+ key: "publish",
128
+ icon: "upload",
129
+ label: a("panel.publish"),
130
+ run: () => {
131
+ S(() => m.publish(e.id), a("panel.published"));
132
+ }
133
+ }
134
+ ), n.push({
135
+ key: "delete",
136
+ icon: "trash",
137
+ label: a("panel.delete"),
138
+ danger: !0,
139
+ run: () => {
140
+ se(e);
141
+ }
142
+ }), n;
143
+ }
144
+ async function se(e) {
145
+ await $e({
146
+ title: a("panel.delete-title", { title: e.title }),
147
+ message: a("panel.delete-text"),
148
+ confirmText: a("panel.delete"),
149
+ cancelText: a("panel.cancel"),
150
+ tone: "danger"
151
+ }) && await S(() => m.remove(e.id), a("panel.deleted"));
152
+ }
153
+ async function re(e) {
154
+ await S(() => m.restore(e.id), a("panel.restored"));
155
+ }
156
+ async function S(e, n) {
157
+ try {
158
+ await e(), x.success(n);
159
+ } catch (t) {
160
+ x.danger(M(t));
161
+ } finally {
162
+ await R();
163
+ }
164
+ }
165
+ async function oe(e) {
166
+ if (e.url)
167
+ try {
168
+ await navigator.clipboard.writeText(e.url), x.success(a("panel.address-copied"));
169
+ } catch {
170
+ }
171
+ }
172
+ function ie(e) {
173
+ return e === "published" || e === "modified" ? "success" : "default";
174
+ }
175
+ function ue(e) {
176
+ return d.value ? "—" : e.path === null ? a("panel.no-address") : `/${e.path}`;
177
+ }
178
+ const ce = r(
179
+ () => z.value ? [{ key: "new", label: a("panel.new"), icon: "plus", primary: !0, run: () => {
180
+ le();
181
+ } }] : []
182
+ );
183
+ return (e, n) => (s(), b("div", Ee, [
184
+ i(l(xe), {
185
+ view: f.value,
186
+ "onUpdate:view": n[5] || (n[5] = (t) => f.value = t),
187
+ title: Y.value,
188
+ views: Z.value,
189
+ actions: ce.value,
190
+ padding: "sm"
191
+ }, {
192
+ default: u(() => [
193
+ p("div", Ie, [
194
+ i(l(Ve), {
195
+ modelValue: A.value,
196
+ "onUpdate:modelValue": n[0] || (n[0] = (t) => A.value = t),
197
+ class: "wx-recipes__search",
198
+ size: "sm",
199
+ clearable: "",
200
+ placeholder: l(a)("panel.search"),
201
+ "aria-label": l(a)("panel.search")
202
+ }, {
203
+ prefix: u(() => [
204
+ i(l(F), {
205
+ name: "search",
206
+ size: "sm"
207
+ })
208
+ ]),
209
+ _: 1
210
+ }, 8, ["modelValue", "placeholder", "aria-label"]),
211
+ i(l(j), {
212
+ "model-value": V.value,
213
+ options: L(h.value.categories),
214
+ placeholder: l(a)("panel.any-category"),
215
+ "aria-label": l(a)("panel.filter-category"),
216
+ clearable: "",
217
+ size: "sm",
218
+ "onUpdate:modelValue": n[1] || (n[1] = (t) => I("category", t))
219
+ }, null, 8, ["model-value", "options", "placeholder", "aria-label"]),
220
+ i(l(j), {
221
+ "model-value": W.value,
222
+ options: L(h.value.nutrients),
223
+ placeholder: l(a)("panel.any-nutrient"),
224
+ "aria-label": l(a)("panel.filter-nutrient"),
225
+ clearable: "",
226
+ size: "sm",
227
+ "onUpdate:modelValue": n[2] || (n[2] = (t) => I("nutrient", t))
228
+ }, null, 8, ["model-value", "options", "placeholder", "aria-label"]),
229
+ h.value.services !== null ? (s(), c(l(j), {
230
+ key: 0,
231
+ "model-value": T.value,
232
+ options: L(h.value.services),
233
+ placeholder: l(a)("panel.any-service"),
234
+ "aria-label": l(a)("panel.filter-service"),
235
+ clearable: "",
236
+ size: "sm",
237
+ "onUpdate:modelValue": n[3] || (n[3] = (t) => I("service", t))
238
+ }, null, 8, ["model-value", "options", "placeholder", "aria-label"])) : g("", !0)
239
+ ]),
240
+ q.value && y.value.length === 0 ? (s(), c(l(We), {
241
+ key: 0,
242
+ class: "wx-recipes__loading",
243
+ rows: 5
244
+ })) : y.value.length === 0 ? (s(), c(l(Te), {
245
+ key: 1,
246
+ title: ae.value,
247
+ description: C.value ? l(a)("panel.empty-help") : void 0
248
+ }, null, 8, ["title", "description"])) : (s(), b(D, { key: 2 }, [
249
+ i(l(Re), {
250
+ modelValue: y.value,
251
+ "onUpdate:modelValue": n[4] || (n[4] = (t) => y.value = t),
252
+ class: O(["wx-recipes__list", { "is-loading": q.value }]),
253
+ plain: "",
254
+ "item-key": "id",
255
+ handle: H.value ? "grip" : "row",
256
+ "item-label": (t) => t.title,
257
+ disabled: !H.value,
258
+ onMove: te
259
+ }, {
260
+ default: u(({ item: t }) => [
261
+ (s(), c(me(d.value ? "div" : "router-link"), ye({ class: "wx-recipe-row" }, d.value ? {} : { to: { path: `${G.base}/${t.id}`, query: l(o).query } }), {
262
+ default: u(() => [
263
+ p("span", {
264
+ class: O(["wx-recipe-row__cover", { "is-empty": !t.cover?.thumb }])
265
+ }, [
266
+ t.cover?.thumb ? (s(), b("img", {
267
+ key: 0,
268
+ src: t.cover.thumb,
269
+ alt: "",
270
+ loading: "lazy"
271
+ }, null, 8, Le)) : (s(), c(l(F), {
272
+ key: 1,
273
+ name: "image",
274
+ size: "sm"
275
+ }))
276
+ ], 2),
277
+ p("span", Pe, [
278
+ p("span", De, v(t.title), 1),
279
+ p("span", Fe, [
280
+ w(v(ue(t)), 1),
281
+ d.value ? g("", !0) : (s(), c(l(qe), {
282
+ key: 0,
283
+ locale: t.address_locale
284
+ }, null, 8, ["locale"]))
285
+ ])
286
+ ]),
287
+ p("span", je, [
288
+ p("span", Ge, [
289
+ (s(!0), b(D, null, fe(t.categories, (P, de) => (s(), b(D, {
290
+ key: P.id
291
+ }, [
292
+ de === 0 ? (s(), c(l(Se), {
293
+ key: 0,
294
+ content: l(a)("panel.main-category")
295
+ }, {
296
+ default: u(() => [
297
+ i(l($), {
298
+ size: "sm",
299
+ round: "",
300
+ type: "primary"
301
+ }, {
302
+ default: u(() => [
303
+ w(v(P.title), 1)
304
+ ]),
305
+ _: 2
306
+ }, 1024)
307
+ ]),
308
+ _: 2
309
+ }, 1032, ["content"])) : (s(), c(l($), {
310
+ key: 1,
311
+ size: "sm",
312
+ round: ""
313
+ }, {
314
+ default: u(() => [
315
+ w(v(P.title), 1)
316
+ ]),
317
+ _: 2
318
+ }, 1024))
319
+ ], 64))), 128))
320
+ ]),
321
+ p("span", He, [
322
+ t.minutes ? (s(), b("span", Je, [
323
+ i(l(F), {
324
+ name: "clock",
325
+ size: "sm"
326
+ }),
327
+ w(v(l(Ae)(t.minutes, l(a))), 1)
328
+ ])) : g("", !0),
329
+ i(l($), {
330
+ type: ie(t.status),
331
+ dot: "",
332
+ size: "sm"
333
+ }, {
334
+ default: u(() => [
335
+ w(v(l(a)(`panel.status-${t.status}`)), 1)
336
+ ]),
337
+ _: 2
338
+ }, 1032, ["type"]),
339
+ t.status === "modified" ? (s(), c(l($), {
340
+ key: 1,
341
+ type: "primary",
342
+ size: "sm",
343
+ round: ""
344
+ }, {
345
+ default: u(() => [
346
+ w(v(l(a)("panel.edits")), 1)
347
+ ]),
348
+ _: 1
349
+ })) : g("", !0)
350
+ ])
351
+ ])
352
+ ]),
353
+ _: 2
354
+ }, 1040))
355
+ ]),
356
+ actions: u(({ item: t }) => [
357
+ i(l(ke), {
358
+ actions: ne(t),
359
+ label: t.title
360
+ }, null, 8, ["actions", "label"])
361
+ ]),
362
+ _: 1
363
+ }, 8, ["modelValue", "class", "handle", "item-label", "disabled"]),
364
+ z.value && !d.value ? (s(), c(l(Ue), {
365
+ key: 0,
366
+ class: "wx-recipes__note",
367
+ type: "info",
368
+ description: ee.value
369
+ }, null, 8, ["description"])) : g("", !0)
370
+ ], 64))
371
+ ]),
372
+ _: 1
373
+ }, 8, ["view", "title", "views", "actions"])
374
+ ]));
375
+ }
376
+ }), ea = /* @__PURE__ */ Ce(Ke, [["__scopeId", "data-v-27e46b1b"]]);
377
+ export {
378
+ ea as default
379
+ };
380
+ //# sourceMappingURL=RecipesPage-CylQ5S5w.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RecipesPage-CylQ5S5w.js","sources":["../src/RecipesPage.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, ref, watch } from 'vue'\nimport { useRoute, useRouter } from 'vue-router'\nimport {\n useAdmin,\n useErrorText,\n useTranslate,\n WxListScreen,\n WxRowMenu,\n type RowAction,\n type ScreenAction,\n WxAddressNote,\n} from '@webx-ui/module-admin'\nimport {\n confirm,\n createModal,\n toast,\n WxAlert,\n WxBadge,\n WxEmpty,\n WxIcon,\n WxInput,\n WxSelect,\n WxSkeleton,\n WxSortableList,\n WxTooltip,\n type TabItem,\n type TabValue,\n} from '@webx-ui/core'\nimport { createRecipesApi } from './api'\nimport { useRecipesMessages } from './i18n'\nimport { formatMinutes } from './minutes'\nimport RecipeCreateDialog from './RecipeCreateDialog.vue'\nimport type { RecipeRow, RecipeStatus, RecipesList } from './types'\n\n/**\n * Every recipe at once, in the order the site shows them (§5.9).\n *\n * No paginator: this list is where recipes are put in order, and a drag cannot cross a page\n * boundary. Recipes have one order and only one (decision 4) — no order inside a category, none\n * inside a service — so the grips are there only while the list is the whole of it. Any filter\n * makes it a selection with invisible gaps in it, the grips go away and the line under the list\n * says why: that is the difference from services, where a category has an order of its own.\n *\n * A row is a card rather than a line of cells, which is what makes the same list work on a\n * phone: the picture, the name and the address on one side, the categories and the state beside\n * them on a wide screen and under them on a narrow one.\n */\nconst props = withDefaults(defineProps<{ base?: string }>(), { base: '/recipes' })\n\nconst context = useAdmin()\nconst api = createRecipesApi(context)\nconst route = useRoute()\nconst router = useRouter()\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 rows = ref<RecipeRow[]>([])\nconst filters = ref<RecipesList['filters']>({ categories: [], nutrients: [], services: null })\nconst loading = ref(true)\n\nconst create = createModal<RecipeRow, Record<string, never>>(RecipeCreateDialog)\n\nconst canManage = computed(() => context.can('recipes.manage'))\n\nconst title = computed(\n () =>\n context.state.manifest?.modules.find((module) => module.id === 'recipes')?.title ??\n t('module.recipes'),\n)\n\n/*\n * Everything the list is looking at lives in the address, so coming back from a recipe lands on\n * the same view, the same filters and the same search.\n */\nconst view = computed<TabValue>({\n get: () => (typeof route.query.view === 'string' ? route.query.view : ''),\n set: (value) => {\n void router.replace({\n query: { ...route.query, view: value === '' ? undefined : String(value) },\n })\n },\n})\n\nconst inBin = computed(() => view.value === 'trashed')\n\nfunction idIn(name: string): number | null {\n const raw = route.query[name]\n\n return typeof raw === 'string' && raw !== '' ? Number(raw) || null : null\n}\n\nconst category = computed(() => idIn('category'))\nconst nutrient = computed(() => idIn('nutrient'))\nconst service = computed(() => idIn('service'))\n\nconst q = computed(() => (typeof route.query.q === 'string' ? route.query.q : ''))\n\n/* What is typed, ahead of the address: the address follows it after a pause. */\nconst typed = ref(q.value)\n\nconst views = computed<TabItem[]>(() => [\n { value: '', label: t('panel.view-all') },\n { value: 'published', label: t('panel.view-published') },\n { value: 'draft', label: t('panel.view-draft') },\n { value: 'unpublished', label: t('panel.view-unpublished') },\n { value: 'trashed', label: t('panel.bin'), icon: 'trash' },\n])\n\n/** The whole list, with nothing narrowing it — the only list whose order can be dragged. */\nconst whole = computed(\n () =>\n view.value === '' &&\n q.value.trim() === '' &&\n category.value === null &&\n nutrient.value === null &&\n service.value === null,\n)\n\n/* Dragged only by somebody who may write, and only in a list that is the whole order. */\nconst sortable = computed(() => canManage.value && whole.value)\n\nconst hint = computed(() => t(whole.value ? 'panel.order-all' : 'panel.order-locked'))\n\nconst emptyText = computed(() => {\n if (q.value !== '') return t('panel.empty-search')\n\n return inBin.value ? t('panel.empty-bin') : t('panel.empty')\n})\n\nasync function load(): Promise<void> {\n loading.value = true\n\n try {\n const answer = await api.list({\n q: q.value,\n category: category.value,\n nutrient: nutrient.value,\n service: service.value,\n status: inBin.value ? '' : (view.value as RecipeStatus | ''),\n trashed: inBin.value,\n })\n\n rows.value = answer.data\n filters.value = answer.filters\n } catch (error) {\n toast.danger(message(error))\n } finally {\n loading.value = false\n }\n}\n\nwatch(\n () => [view.value, category.value, nutrient.value, service.value, q.value] as const,\n () => void load(),\n)\nvoid load()\n\nlet pause: ReturnType<typeof setTimeout> | undefined\n\nwatch(typed, (value) => {\n clearTimeout(pause)\n pause = setTimeout(() => {\n void router.replace({ query: { ...route.query, q: value.trim() === '' ? undefined : value } })\n }, 300)\n})\n\nonBeforeUnmount(() => clearTimeout(pause))\n\nfunction narrow(name: 'category' | 'nutrient' | 'service', value: unknown): void {\n void router.replace({\n query: { ...route.query, [name]: typeof value === 'number' ? String(value) : undefined },\n })\n}\n\nconst options = (list: { id: number; title: string }[] | null) =>\n (list ?? []).map((item) => ({ value: item.id, label: item.title }))\n\nfunction open(recipe: RecipeRow): void {\n void router.push({ path: `${props.base}/${recipe.id}`, query: { ...route.query } })\n}\n\n/** A new recipe: the dialog asks for a title, the editor opens. */\nasync function add(): Promise<void> {\n const made = await create({})\n\n if (made) open(made)\n}\n\n/**\n * The order on screen, written as a whole. The list is already in its new order\n * (`WxSortableList` reorders the model before it says so), so a failure has to put it back\n * rather than leave the screen disagreeing with the database.\n */\nasync function reorder(): Promise<void> {\n try {\n await api.reorder(rows.value.map((row) => row.id))\n } catch (error) {\n toast.danger(message(error, t('panel.reorder-failed')))\n await load()\n }\n}\n\nfunction actionsFor(recipe: RecipeRow): RowAction[] {\n if (!canManage.value) return []\n\n if (inBin.value) {\n return [\n {\n key: 'restore',\n icon: 'refresh',\n label: t('panel.restore'),\n run: () => void restore(recipe),\n },\n ]\n }\n\n const actions: RowAction[] = [\n { key: 'open', icon: 'edit', label: t('panel.open'), run: () => open(recipe) },\n ]\n\n if (recipe.url && recipe.status !== 'draft') {\n actions.push({\n key: 'site',\n icon: 'external-link',\n label: t('panel.open-on-site'),\n href: recipe.url,\n })\n }\n\n if (recipe.url) {\n actions.push({\n key: 'copy',\n icon: 'copy',\n label: t('panel.copy-address'),\n run: () => void copyAddress(recipe),\n })\n }\n\n actions.push(\n recipe.status === 'published' || recipe.status === 'modified'\n ? {\n key: 'unpublish',\n icon: 'eye-off',\n label: t('panel.unpublish'),\n run: () => void run(() => api.unpublish(recipe.id), t('panel.unpublished-done')),\n }\n : {\n key: 'publish',\n icon: 'upload',\n label: t('panel.publish'),\n run: () => void run(() => api.publish(recipe.id), t('panel.published')),\n },\n )\n\n actions.push({\n key: 'delete',\n icon: 'trash',\n label: t('panel.delete'),\n danger: true,\n run: () => void remove(recipe),\n })\n\n return actions\n}\n\nasync function remove(recipe: RecipeRow): Promise<void> {\n const agreed = await confirm({\n title: t('panel.delete-title', { title: recipe.title }),\n message: t('panel.delete-text'),\n confirmText: t('panel.delete'),\n cancelText: t('panel.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n await run(() => api.remove(recipe.id), t('panel.deleted'))\n}\n\nasync function restore(recipe: RecipeRow): Promise<void> {\n await run(() => api.restore(recipe.id), t('panel.restored'))\n}\n\n/** One request, one sentence about it, and the list as the server now sees it. */\nasync function run(request: () => Promise<unknown>, said: string): Promise<void> {\n try {\n await request()\n toast.success(said)\n } catch (error) {\n toast.danger(message(error))\n } finally {\n await load()\n }\n}\n\nasync function copyAddress(recipe: RecipeRow): Promise<void> {\n if (!recipe.url) return\n\n try {\n await navigator.clipboard.writeText(recipe.url)\n toast.success(t('panel.address-copied'))\n } catch {\n // A browser that refuses the clipboard without a gesture it trusts; the address is on screen.\n }\n}\n\n/* Live and live-with-edits are the same green; what is waiting has a chip of its own. */\nfunction badge(status: RecipeStatus): 'default' | 'success' {\n return status === 'published' || status === 'modified' ? 'success' : 'default'\n}\n\nfunction address(recipe: RecipeRow): string {\n if (inBin.value) return '—'\n\n return recipe.path === null ? t('panel.no-address') : `/${recipe.path}`\n}\n\n/* What the section offers. Declared, because on a phone the head folds it into the ···. */\nconst actions = computed<ScreenAction[]>(() =>\n canManage.value\n ? [{ key: 'new', label: t('panel.new'), icon: 'plus', primary: true, run: () => void add() }]\n : [],\n)\n</script>\n\n<template>\n <div class=\"wx-recipes\">\n <wx-list-screen\n v-model:view=\"view\"\n :title=\"title\"\n :views=\"views\"\n :actions=\"actions\"\n padding=\"sm\"\n >\n <div class=\"wx-recipes__bar\">\n <wx-input\n v-model=\"typed\"\n class=\"wx-recipes__search\"\n size=\"sm\"\n clearable\n :placeholder=\"t('panel.search')\"\n :aria-label=\"t('panel.search')\"\n >\n <template #prefix><wx-icon name=\"search\" size=\"sm\" /></template>\n </wx-input>\n\n <!-- In sight rather than behind a funnel: each of them decides whether the list can be\n dragged, and the line under the list says so. -->\n <wx-select\n :model-value=\"category\"\n :options=\"options(filters.categories)\"\n :placeholder=\"t('panel.any-category')\"\n :aria-label=\"t('panel.filter-category')\"\n clearable\n size=\"sm\"\n @update:model-value=\"narrow('category', $event)\"\n />\n <wx-select\n :model-value=\"nutrient\"\n :options=\"options(filters.nutrients)\"\n :placeholder=\"t('panel.any-nutrient')\"\n :aria-label=\"t('panel.filter-nutrient')\"\n clearable\n size=\"sm\"\n @update:model-value=\"narrow('nutrient', $event)\"\n />\n <!-- `null` from the server: the services module is not installed, and there is nothing\n a recipe could be related to. -->\n <wx-select\n v-if=\"filters.services !== null\"\n :model-value=\"service\"\n :options=\"options(filters.services)\"\n :placeholder=\"t('panel.any-service')\"\n :aria-label=\"t('panel.filter-service')\"\n clearable\n size=\"sm\"\n @update:model-value=\"narrow('service', $event)\"\n />\n </div>\n\n <wx-skeleton v-if=\"loading && rows.length === 0\" class=\"wx-recipes__loading\" :rows=\"5\" />\n\n <wx-empty\n v-else-if=\"rows.length === 0\"\n :title=\"emptyText\"\n :description=\"whole ? t('panel.empty-help') : undefined\"\n />\n\n <template v-else>\n <!--\n A grip and not the whole row: the row opens a recipe, and a row that both opens and\n drags is one where one of the two happens by accident. No grips at all where the list\n is a selection — a handle that moves rows past invisible ones would be lying.\n -->\n <wx-sortable-list\n v-model=\"rows\"\n class=\"wx-recipes__list\"\n :class=\"{ 'is-loading': loading }\"\n plain\n item-key=\"id\"\n :handle=\"sortable ? 'grip' : 'row'\"\n :item-label=\"(item: RecipeRow) => item.title\"\n :disabled=\"!sortable\"\n @move=\"reorder\"\n >\n <template #default=\"{ item }\">\n <component\n :is=\"inBin ? 'div' : 'router-link'\"\n class=\"wx-recipe-row\"\n v-bind=\"inBin ? {} : { to: { path: `${props.base}/${item.id}`, query: route.query } }\"\n >\n <!-- The picture's box, never the picture: `<img>` has `min-width: auto`, and a\n photograph would take the row (CLAUDE.md §4). -->\n <span class=\"wx-recipe-row__cover\" :class=\"{ 'is-empty': !item.cover?.thumb }\">\n <img v-if=\"item.cover?.thumb\" :src=\"item.cover.thumb\" alt=\"\" loading=\"lazy\" />\n <wx-icon v-else name=\"image\" size=\"sm\" />\n </span>\n\n <span class=\"wx-recipe-row__name\">\n <span class=\"wx-recipe-row__title\">{{ item.title }}</span>\n <span class=\"wx-recipe-row__address\"\n >{{ address(item) }}<wx-address-note v-if=\"!inBin\" :locale=\"item.address_locale\"\n /></span>\n </span>\n\n <span class=\"wx-recipe-row__facts\">\n <span class=\"wx-recipe-row__chips\">\n <template v-for=\"(chip, index) in item.categories\" :key=\"chip.id\">\n <wx-tooltip v-if=\"index === 0\" :content=\"t('panel.main-category')\">\n <wx-badge size=\"sm\" round type=\"primary\">{{ chip.title }}</wx-badge>\n </wx-tooltip>\n <wx-badge v-else size=\"sm\" round>{{ chip.title }}</wx-badge>\n </template>\n </span>\n\n <span class=\"wx-recipe-row__state\">\n <span v-if=\"item.minutes\" class=\"wx-recipe-row__time\">\n <wx-icon name=\"clock\" size=\"sm\" />{{ formatMinutes(item.minutes, t) }}\n </span>\n <wx-badge :type=\"badge(item.status)\" dot size=\"sm\">\n {{ t(`panel.status-${item.status}`) }}\n </wx-badge>\n <wx-badge v-if=\"item.status === 'modified'\" type=\"primary\" size=\"sm\" round>\n {{ t('panel.edits') }}\n </wx-badge>\n </span>\n </span>\n </component>\n </template>\n\n <template #actions=\"{ item }\">\n <wx-row-menu :actions=\"actionsFor(item)\" :label=\"item.title\" />\n </template>\n </wx-sortable-list>\n\n <!-- What a drag does here, or why there is nothing to drag — under the rows it is about. -->\n <wx-alert\n v-if=\"canManage && !inBin\"\n class=\"wx-recipes__note\"\n type=\"info\"\n :description=\"hint\"\n />\n </template>\n </wx-list-screen>\n </div>\n</template>\n\n<style scoped>\n.wx-recipes {\n min-width: 0;\n container-type: inline-size;\n}\n\n.wx-recipes__bar {\n display: flex;\n flex-wrap: wrap;\n gap: var(--wx-space-8);\n padding: var(--wx-space-4) var(--wx-space-8) var(--wx-space-8);\n}\n\n.wx-recipes__search {\n flex: 1 1 240px;\n min-width: 0;\n}\n\n/* The select is a fragment (it carries its dropdown beside it), so our scope attribute never\n reaches it: styled from our own wrapper (CLAUDE.md §4). */\n.wx-recipes__bar > :deep(.wx-select) {\n flex: 1 1 160px;\n max-width: 220px;\n}\n\n.wx-recipes__loading {\n padding: var(--wx-space-8);\n}\n\n.wx-recipes__list.is-loading {\n opacity: 0.6;\n}\n\n.wx-recipes__list :deep(.wx-sortable-list__row) {\n padding-inline: var(--wx-space-8);\n}\n\n.wx-recipes__list :deep(.wx-sortable-list__row:hover) {\n background: var(--wx-bg-subtle);\n border-radius: var(--wx-radius-sm);\n}\n\n.wx-recipes__note {\n margin-block-start: var(--wx-space-8);\n}\n\n.wx-recipe-row {\n display: grid;\n grid-template-columns: auto minmax(0, 1fr) auto;\n grid-template-areas: 'cover name facts';\n align-items: center;\n gap: var(--wx-space-4) var(--wx-space-12);\n flex: 1 1 auto;\n min-width: 0;\n padding-block: var(--wx-space-4);\n color: inherit;\n text-decoration: none;\n}\n\n.wx-recipe-row__cover {\n grid-area: cover;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 44px;\n height: 32px;\n overflow: hidden;\n border-radius: var(--wx-radius-xs);\n background: var(--wx-bg-subtle);\n color: var(--wx-text-muted);\n}\n\n.wx-recipe-row__cover.is-empty {\n border: 1px dashed var(--wx-border-default);\n background: var(--wx-bg-surface);\n}\n\n.wx-recipe-row__cover img {\n width: 100%;\n height: 100%;\n min-width: 0;\n object-fit: cover;\n}\n\n.wx-recipe-row__name {\n grid-area: name;\n display: flex;\n flex-direction: column;\n min-width: 0;\n}\n\n.wx-recipe-row__title {\n overflow: hidden;\n font-weight: var(--wx-font-weight-medium);\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n\n.wx-recipe-row__address {\n overflow: hidden;\n color: var(--wx-text-muted);\n font-family: var(--wx-font-family-mono);\n font-size: var(--wx-font-size-xs);\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n\n.wx-recipe-row__facts {\n grid-area: facts;\n display: flex;\n align-items: center;\n gap: var(--wx-space-12);\n min-width: 0;\n}\n\n.wx-recipe-row__chips,\n.wx-recipe-row__state {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--wx-space-4);\n min-width: 0;\n}\n\n.wx-recipe-row__chips {\n justify-content: flex-end;\n max-width: 320px;\n}\n\n.wx-recipe-row__time {\n display: inline-flex;\n align-items: center;\n gap: var(--wx-space-4);\n color: var(--wx-text-muted);\n font-size: var(--wx-font-size-xs);\n white-space: nowrap;\n}\n\n/*\n * Narrow: the facts go under the name rather than beside it, so the name keeps the width. By the\n * width of the list, not of the window — the panel's sidebar decides how much the list has.\n */\n@container (max-width: 640px) {\n .wx-recipe-row {\n grid-template-columns: auto minmax(0, 1fr);\n grid-template-areas:\n 'cover name'\n 'cover facts';\n align-items: start;\n column-gap: var(--wx-space-8);\n }\n\n .wx-recipe-row__facts {\n flex-wrap: wrap;\n gap: var(--wx-space-4);\n }\n\n /* One wrapping line of badges rather than two boxes side by side (see the services list). */\n .wx-recipe-row__chips,\n .wx-recipe-row__state {\n display: contents;\n }\n\n .wx-recipes__list :deep(.wx-sortable-list__row) {\n gap: var(--wx-space-8);\n padding-inline: var(--wx-space-4);\n }\n\n .wx-recipes__bar {\n padding-inline: var(--wx-space-4);\n }\n\n .wx-recipes__bar > :deep(.wx-select) {\n max-width: none;\n }\n}\n</style>\n"],"names":["props","__props","context","useAdmin","api","createRecipesApi","route","useRoute","router","useRouter","useRecipesMessages","t","useTranslate","message","useErrorText","rows","ref","filters","loading","create","createModal","RecipeCreateDialog","canManage","computed","title","module","view","value","inBin","idIn","name","raw","category","nutrient","service","q","typed","views","whole","sortable","hint","emptyText","load","answer","error","toast","watch","pause","onBeforeUnmount","narrow","options","list","item","open","recipe","add","made","reorder","row","actionsFor","restore","actions","copyAddress","run","remove","confirm","request","said","badge","status","address","_openBlock","_createElementBlock","_hoisted_1","_createVNode","_unref","WxListScreen","$event","_createElementVNode","_hoisted_2","WxInput","WxIcon","WxSelect","_cache","_createBlock","WxSkeleton","WxEmpty","_Fragment","WxSortableList","_normalizeClass","_withCtx","_resolveDynamicComponent","_mergeProps","_hoisted_4","_hoisted_5","_toDisplayString","_hoisted_6","_createTextVNode","WxAddressNote","_hoisted_7","_hoisted_8","chip","index","WxTooltip","WxBadge","_hoisted_9","_hoisted_10","formatMinutes","WxRowMenu","WxAlert"],"mappings":"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"}