@webx-ui/module-recipes 0.2.6 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/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,346 @@
|
|
|
1
|
+
import { defineComponent as we, computed as s, ref as p, watch as q, onMounted as xe, onBeforeUnmount as ke, openBlock as W, createElementBlock as F, Fragment as K, createVNode as n, unref as t, withCtx as o, createTextVNode as S, toDisplayString as B, createBlock as J, createCommentVNode as z } from "vue";
|
|
2
|
+
import { useRoute as _e, useRouter as Te, onBeforeRouteLeave as We } from "vue-router";
|
|
3
|
+
import { useAdmin as Se, useDates as Be, useTranslate as Q, useErrorText as Ee, useEditing as Re, provideRelationOwner as Ne, provideRecordAddress as Ve, WxScreenHead as Ae, WxEditingAlerts as Ce, WxScreen as Oe, WxSaveState as Le } from "@webx-ui/module-admin";
|
|
4
|
+
import { useLocales as Me, localizedValue as Pe, confirm as M, WxSkeleton as X, WxCard as Ue, WxBadge as Y, WxBreadcrumb as $e, WxBreadcrumbItem as Z, WxActionBar as De, WxButton as ee, toast as b } from "@webx-ui/core";
|
|
5
|
+
import { c as Fe, u as Je, p as ze, a as Ie } from "./index-rwJ6c36l.js";
|
|
6
|
+
const Ge = 1200, He = /* @__PURE__ */ we({
|
|
7
|
+
__name: "RecipeEditorPage",
|
|
8
|
+
props: {
|
|
9
|
+
base: { default: "/recipes" }
|
|
10
|
+
},
|
|
11
|
+
setup(ae) {
|
|
12
|
+
const te = ae, I = Se(), m = Fe(I), ie = _e(), le = Te(), re = Me(), ue = Be();
|
|
13
|
+
Je();
|
|
14
|
+
const i = Q("webx-recipes"), d = Q("webx-admin"), E = Ee(), A = s(() => Number(ie.params.id)), P = s(() => te.base), a = p(null), h = p({}), C = p(""), O = p(null), R = p(null), U = p(!0), w = p(!1), g = p(!1), $ = p({}), N = p(""), f = s(() => I.can("recipes.manage")), u = Re({
|
|
15
|
+
entity: "recipes",
|
|
16
|
+
id: () => a.value?.id,
|
|
17
|
+
values: h,
|
|
18
|
+
revision: C,
|
|
19
|
+
read: async () => {
|
|
20
|
+
const e = await m.get(A.value);
|
|
21
|
+
return { values: e.values, revision: e.revision };
|
|
22
|
+
},
|
|
23
|
+
adopt: (e) => {
|
|
24
|
+
N.value = JSON.stringify(e.values);
|
|
25
|
+
},
|
|
26
|
+
// Published or restored by somebody else: the badge and the address follow, and the form
|
|
27
|
+
// stays as it is.
|
|
28
|
+
refresh: () => ce(),
|
|
29
|
+
restore: async () => {
|
|
30
|
+
a.value && await m.restore(a.value.id);
|
|
31
|
+
},
|
|
32
|
+
canWrite: () => f.value,
|
|
33
|
+
busy: () => w.value || T !== void 0,
|
|
34
|
+
screen: "recipes.form"
|
|
35
|
+
}), x = u.conflict, se = s(() => !f.value || g.value), k = s(() => JSON.stringify(h.value)), c = s(() => N.value !== "" && k.value !== N.value), ne = s(() => w.value ? "saving" : c.value ? "unsaved" : "saved"), D = s(() => {
|
|
36
|
+
const e = h.value.title;
|
|
37
|
+
return Pe(e, re.active.value, a.value?.title ?? "");
|
|
38
|
+
}), oe = s(() => {
|
|
39
|
+
const e = a.value;
|
|
40
|
+
return !e || e.status !== "published" && e.status !== "modified" ? "" : i("recipe.live-since", { date: ue.short(e.published_at) });
|
|
41
|
+
});
|
|
42
|
+
Ne({ type: "recipe", id: s(() => a.value?.id ?? null) }), Ve({
|
|
43
|
+
values: h,
|
|
44
|
+
prefix: O,
|
|
45
|
+
path: s(() => a.value?.path),
|
|
46
|
+
moving: () => i("recipe.address-moving")
|
|
47
|
+
}), ze({ recipe: a, canManage: f.value, reload: () => _(!0) });
|
|
48
|
+
function G(e) {
|
|
49
|
+
a.value = e.recipe, h.value = e.values, C.value = e.revision, O.value = e.prefix, R.value = e.preview_url, N.value = JSON.stringify(e.values), u.opened(e.values), x.value = null;
|
|
50
|
+
}
|
|
51
|
+
async function _(e = !1) {
|
|
52
|
+
e || (U.value = !0);
|
|
53
|
+
try {
|
|
54
|
+
G(await m.get(A.value));
|
|
55
|
+
} catch (l) {
|
|
56
|
+
b.danger(E(l)), le.push(P.value);
|
|
57
|
+
} finally {
|
|
58
|
+
U.value = !1;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
async function ce() {
|
|
62
|
+
try {
|
|
63
|
+
const e = await m.get(A.value);
|
|
64
|
+
a.value = e.recipe, O.value = e.prefix, R.value = e.preview_url;
|
|
65
|
+
} catch {
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
let V;
|
|
69
|
+
function de() {
|
|
70
|
+
!f.value || x.value || !c.value || u.stopped.value || (clearTimeout(V), V = setTimeout(() => {
|
|
71
|
+
y();
|
|
72
|
+
}, Ge));
|
|
73
|
+
}
|
|
74
|
+
function ve() {
|
|
75
|
+
!f.value || x.value || !c.value || w.value || u.stopped.value || (clearTimeout(V), y());
|
|
76
|
+
}
|
|
77
|
+
let T;
|
|
78
|
+
async function y() {
|
|
79
|
+
for (; T; ) {
|
|
80
|
+
const { carried: r, done: v } = T;
|
|
81
|
+
if (await v, !c.value || x.value || k.value === r) return;
|
|
82
|
+
}
|
|
83
|
+
const e = k.value, l = pe();
|
|
84
|
+
T = { carried: e, done: l };
|
|
85
|
+
try {
|
|
86
|
+
await l;
|
|
87
|
+
} finally {
|
|
88
|
+
T?.done === l && (T = void 0);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async function pe() {
|
|
92
|
+
if (!a.value || !f.value) return;
|
|
93
|
+
clearTimeout(V);
|
|
94
|
+
const e = k.value, l = h.value;
|
|
95
|
+
w.value = !0, $.value = {};
|
|
96
|
+
try {
|
|
97
|
+
const r = await m.save(a.value.id, { values: l, revision: C.value });
|
|
98
|
+
a.value = r.recipe, C.value = r.revision, O.value = r.prefix, R.value = r.preview_url, x.value = null, k.value === e && (N.value = e), u.opened(l);
|
|
99
|
+
} catch (r) {
|
|
100
|
+
const v = r;
|
|
101
|
+
if (v.status === 409 && v.body?.data) {
|
|
102
|
+
const L = v.body.data;
|
|
103
|
+
u.refused({
|
|
104
|
+
values: L.values,
|
|
105
|
+
revision: L.revision,
|
|
106
|
+
changed: v.body.changed
|
|
107
|
+
}) && (a.value = L.recipe, y());
|
|
108
|
+
} else v.body?.errors ? ($.value = v.body.errors, b.danger(i("recipe.save-failed"))) : await u.failed(r) || b.danger(E(r));
|
|
109
|
+
} finally {
|
|
110
|
+
w.value = !1;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
async function fe() {
|
|
114
|
+
await _(!0);
|
|
115
|
+
}
|
|
116
|
+
async function be() {
|
|
117
|
+
if (!(!a.value || !await M({
|
|
118
|
+
title: d("editor.discard-title"),
|
|
119
|
+
message: d("editor.discard-text"),
|
|
120
|
+
confirmText: i("recipe.discard"),
|
|
121
|
+
// Not «Cancel»: beside «Discard changes» the two read as the same word.
|
|
122
|
+
cancelText: d("editor.keep-changes"),
|
|
123
|
+
tone: "danger"
|
|
124
|
+
}))) {
|
|
125
|
+
g.value = !0;
|
|
126
|
+
try {
|
|
127
|
+
G(await m.discard(a.value.id)), b.success(i("recipe.discarded"));
|
|
128
|
+
} catch (l) {
|
|
129
|
+
b.danger(E(l));
|
|
130
|
+
} finally {
|
|
131
|
+
g.value = !1;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
async function me() {
|
|
136
|
+
if (c.value && await y(), x.value || c.value || !a.value) return;
|
|
137
|
+
const e = await u.beforePublish();
|
|
138
|
+
if (!e) return;
|
|
139
|
+
const l = a.value, r = l.next_path ?? l.path, v = l.next_path != null && l.path !== null ? ` ${d("editor.publish-moves", { old: `/${l.path}` })}` : "";
|
|
140
|
+
if (e.asked || await M({
|
|
141
|
+
title: i("recipe.publish-title", { title: D.value || l.title }),
|
|
142
|
+
message: r === null ? i("recipe.publish-nowhere") : i("recipe.publish-text", { address: `/${r}` }) + v,
|
|
143
|
+
confirmText: i("panel.publish"),
|
|
144
|
+
cancelText: i("panel.cancel")
|
|
145
|
+
})) {
|
|
146
|
+
g.value = !0;
|
|
147
|
+
try {
|
|
148
|
+
await m.publish(l.id, e.revision), await _(!0), b.success(i("panel.published"));
|
|
149
|
+
} catch (j) {
|
|
150
|
+
await u.failed(j) || b.danger(E(j));
|
|
151
|
+
} finally {
|
|
152
|
+
g.value = !1;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
async function he() {
|
|
157
|
+
const e = a.value;
|
|
158
|
+
if (!(!e || !await M({
|
|
159
|
+
title: d("editor.unpublish-title"),
|
|
160
|
+
message: d("editor.unpublish-text"),
|
|
161
|
+
confirmText: d("editor.unpublish"),
|
|
162
|
+
cancelText: d("editor.keep-published")
|
|
163
|
+
}))) {
|
|
164
|
+
g.value = !0;
|
|
165
|
+
try {
|
|
166
|
+
await m.unpublish(e.id), await _(!0), b.success(d("editor.unpublished"));
|
|
167
|
+
} catch (r) {
|
|
168
|
+
b.danger(E(r));
|
|
169
|
+
} finally {
|
|
170
|
+
g.value = !1;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function ge() {
|
|
175
|
+
const e = a.value?.status;
|
|
176
|
+
return e === "published" || e === "modified" ? "success" : "default";
|
|
177
|
+
}
|
|
178
|
+
function H(e) {
|
|
179
|
+
c.value && e.preventDefault();
|
|
180
|
+
}
|
|
181
|
+
q(k, de), q(A, () => {
|
|
182
|
+
_();
|
|
183
|
+
}), xe(() => {
|
|
184
|
+
_(), window.addEventListener("beforeunload", H);
|
|
185
|
+
}), ke(() => {
|
|
186
|
+
clearTimeout(V), window.removeEventListener("beforeunload", H);
|
|
187
|
+
}), We(async () => !c.value || !f.value || (await y(), !c.value) ? !0 : await M({
|
|
188
|
+
title: i("recipe.leave-title"),
|
|
189
|
+
message: i("recipe.leave-text"),
|
|
190
|
+
confirmText: i("recipe.leave"),
|
|
191
|
+
cancelText: i("panel.cancel"),
|
|
192
|
+
tone: "danger"
|
|
193
|
+
}));
|
|
194
|
+
const ye = s(() => {
|
|
195
|
+
const e = [];
|
|
196
|
+
return R.value && e.push({ key: "preview", label: i("recipe.preview"), icon: "eye", href: R.value }), a.value?.url && a.value.status !== "draft" && e.push({
|
|
197
|
+
key: "site",
|
|
198
|
+
label: i("panel.open-on-site"),
|
|
199
|
+
icon: "link",
|
|
200
|
+
href: a.value.url
|
|
201
|
+
}), f.value && (a.value?.status === "published" || a.value?.status === "modified") && e.push({
|
|
202
|
+
key: "unpublish",
|
|
203
|
+
label: d("editor.unpublish"),
|
|
204
|
+
icon: "eye-off",
|
|
205
|
+
run: () => {
|
|
206
|
+
he();
|
|
207
|
+
}
|
|
208
|
+
}), a.value?.status === "modified" && e.push({
|
|
209
|
+
key: "discard",
|
|
210
|
+
label: i("recipe.discard"),
|
|
211
|
+
icon: "refresh",
|
|
212
|
+
danger: !0,
|
|
213
|
+
menu: !0,
|
|
214
|
+
run: () => {
|
|
215
|
+
be();
|
|
216
|
+
}
|
|
217
|
+
}), e;
|
|
218
|
+
});
|
|
219
|
+
return (e, l) => (W(), F("div", {
|
|
220
|
+
class: "wx-recipe-editor",
|
|
221
|
+
onFocusout: ve
|
|
222
|
+
}, [
|
|
223
|
+
U.value || !a.value ? (W(), F(K, { key: 0 }, [
|
|
224
|
+
n(t(X), {
|
|
225
|
+
class: "wx-recipe-editor__ghost",
|
|
226
|
+
title: "",
|
|
227
|
+
rows: 1
|
|
228
|
+
}),
|
|
229
|
+
n(t(Ue), null, {
|
|
230
|
+
default: o(() => [
|
|
231
|
+
n(t(X), { rows: 8 })
|
|
232
|
+
]),
|
|
233
|
+
_: 1
|
|
234
|
+
})
|
|
235
|
+
], 64)) : (W(), F(K, { key: 1 }, [
|
|
236
|
+
n(t(Ae), {
|
|
237
|
+
divider: "",
|
|
238
|
+
back: P.value,
|
|
239
|
+
"back-label": t(i)("module.recipes"),
|
|
240
|
+
title: D.value || t(i)("recipe.untitled"),
|
|
241
|
+
subtitle: oe.value,
|
|
242
|
+
actions: ye.value
|
|
243
|
+
}, {
|
|
244
|
+
trail: o(() => [
|
|
245
|
+
n(t($e), {
|
|
246
|
+
size: "sm",
|
|
247
|
+
label: t(i)("recipe.trail")
|
|
248
|
+
}, {
|
|
249
|
+
default: o(() => [
|
|
250
|
+
n(t(Z), {
|
|
251
|
+
as: "router-link",
|
|
252
|
+
to: P.value
|
|
253
|
+
}, {
|
|
254
|
+
default: o(() => [
|
|
255
|
+
S(B(t(i)("module.recipes")), 1)
|
|
256
|
+
]),
|
|
257
|
+
_: 1
|
|
258
|
+
}, 8, ["to"]),
|
|
259
|
+
n(t(Z), { current: "" }, {
|
|
260
|
+
default: o(() => [
|
|
261
|
+
S(B(D.value || t(i)("recipe.untitled")), 1)
|
|
262
|
+
]),
|
|
263
|
+
_: 1
|
|
264
|
+
})
|
|
265
|
+
]),
|
|
266
|
+
_: 1
|
|
267
|
+
}, 8, ["label"])
|
|
268
|
+
]),
|
|
269
|
+
"title-after": o(() => [
|
|
270
|
+
n(t(Y), {
|
|
271
|
+
type: ge(),
|
|
272
|
+
dot: ""
|
|
273
|
+
}, {
|
|
274
|
+
default: o(() => [
|
|
275
|
+
S(B(t(i)(`panel.status-${a.value.status}`)), 1)
|
|
276
|
+
]),
|
|
277
|
+
_: 1
|
|
278
|
+
}, 8, ["type"]),
|
|
279
|
+
a.value.status === "modified" ? (W(), J(t(Y), {
|
|
280
|
+
key: 0,
|
|
281
|
+
type: "primary",
|
|
282
|
+
round: ""
|
|
283
|
+
}, {
|
|
284
|
+
default: o(() => [
|
|
285
|
+
S(B(t(i)("panel.edits")), 1)
|
|
286
|
+
]),
|
|
287
|
+
_: 1
|
|
288
|
+
})) : z("", !0)
|
|
289
|
+
]),
|
|
290
|
+
_: 1
|
|
291
|
+
}, 8, ["back", "back-label", "title", "subtitle", "actions"]),
|
|
292
|
+
n(t(Ce), {
|
|
293
|
+
editing: t(u),
|
|
294
|
+
onSave: y,
|
|
295
|
+
onTheirs: fe
|
|
296
|
+
}, null, 8, ["editing"]),
|
|
297
|
+
n(t(Oe), {
|
|
298
|
+
modelValue: h.value,
|
|
299
|
+
"onUpdate:modelValue": l[0] || (l[0] = (r) => h.value = r),
|
|
300
|
+
name: "recipes.form",
|
|
301
|
+
errors: $.value,
|
|
302
|
+
disabled: se.value
|
|
303
|
+
}, null, 8, ["modelValue", "errors", "disabled"]),
|
|
304
|
+
f.value ? (W(), J(t(De), { key: 0 }, {
|
|
305
|
+
state: o(() => [
|
|
306
|
+
t(u).stopped.value ? z("", !0) : (W(), J(t(Le), {
|
|
307
|
+
key: 0,
|
|
308
|
+
state: ne.value
|
|
309
|
+
}, null, 8, ["state"]))
|
|
310
|
+
]),
|
|
311
|
+
default: o(() => [
|
|
312
|
+
n(t(ee), {
|
|
313
|
+
variant: "outline",
|
|
314
|
+
loading: w.value,
|
|
315
|
+
disabled: !c.value || t(u).stopped.value,
|
|
316
|
+
title: t(u).blocked.value,
|
|
317
|
+
onClick: y
|
|
318
|
+
}, {
|
|
319
|
+
default: o(() => [
|
|
320
|
+
S(B(t(i)("recipe.save")), 1)
|
|
321
|
+
]),
|
|
322
|
+
_: 1
|
|
323
|
+
}, 8, ["loading", "disabled", "title"]),
|
|
324
|
+
n(t(ee), {
|
|
325
|
+
type: "primary",
|
|
326
|
+
loading: g.value,
|
|
327
|
+
disabled: t(u).stopped.value || a.value.status === "published" && !c.value,
|
|
328
|
+
title: t(u).blocked.value,
|
|
329
|
+
onClick: me
|
|
330
|
+
}, {
|
|
331
|
+
default: o(() => [
|
|
332
|
+
S(B(t(i)("panel.publish")), 1)
|
|
333
|
+
]),
|
|
334
|
+
_: 1
|
|
335
|
+
}, 8, ["loading", "disabled", "title"])
|
|
336
|
+
]),
|
|
337
|
+
_: 1
|
|
338
|
+
})) : z("", !0)
|
|
339
|
+
], 64))
|
|
340
|
+
], 32));
|
|
341
|
+
}
|
|
342
|
+
}), Ye = /* @__PURE__ */ Ie(He, [["__scopeId", "data-v-253e5134"]]);
|
|
343
|
+
export {
|
|
344
|
+
Ye as default
|
|
345
|
+
};
|
|
346
|
+
//# sourceMappingURL=RecipeEditorPage-DcvFY_ja.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RecipeEditorPage-DcvFY_ja.js","sources":["../src/RecipeEditorPage.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'\nimport { onBeforeRouteLeave, useRoute, useRouter } from 'vue-router'\nimport {\n provideRecordAddress,\n provideRelationOwner,\n useAdmin,\n useDates,\n useEditing,\n useErrorText,\n useTranslate,\n WxEditingAlerts,\n WxSaveState,\n WxScreen,\n WxScreenHead,\n type ScreenAction,\n} from '@webx-ui/module-admin'\nimport {\n confirm,\n localizedValue,\n toast,\n useLocales,\n WxActionBar,\n WxBadge,\n WxBreadcrumb,\n WxBreadcrumbItem,\n WxButton,\n WxCard,\n WxSkeleton,\n type LocalizedValue,\n} from '@webx-ui/core'\nimport type { ScreenModel } from '@webx-ui/schema'\nimport { createRecipesApi } from './api'\nimport { provideRecipeEditor } from './editor'\nimport { useRecipesMessages } from './i18n'\nimport type { RecipeConflict, RecipeDetail, RecipeRow } from './types'\n\n/**\n * The editor of one recipe: a head that stays put, the described screen under it, and the bar\n * along the bottom where it is saved and published (§5.9).\n *\n * Everything between the head and the bar is `recipes.form` — the recipe, its settings, the SEO\n * card `module-seo` patches on and the history — so a project adds its own field (an author's\n * note) with a patch rather than a fork of this file. Saving is by autosave into the draft, and\n * the draft carries the categories, the services and the similar recipes too: the site changes,\n * all of it at once, only on \"Publish\". Edits are guarded by a revision: a save over somebody\n * else's is refused with a 409, merged with theirs, and asked about only where both changed.\n */\nconst props = withDefaults(defineProps<{ base?: string }>(), { base: '/recipes' })\n\nconst context = useAdmin()\nconst api = createRecipesApi(context)\nconst route = useRoute()\nconst router = useRouter()\nconst locales = useLocales()\nconst dates = useDates()\nuseRecipesMessages()\n\nconst t = useTranslate('webx-recipes')\nconst panel = useTranslate('webx-admin')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\n/** How long after the last keystroke the draft goes to the server. */\nconst PAUSE = 1200\n\nconst id = computed(() => Number(route.params.id))\nconst list = computed(() => props.base)\n\nconst recipe = ref<RecipeRow | null>(null)\nconst values = ref<ScreenModel>({})\nconst revision = ref('')\nconst prefix = ref<string | null>(null)\nconst previewUrl = ref<string | null>(null)\n\nconst loading = ref(true)\nconst saving = ref(false)\nconst working = ref(false)\nconst errors = ref<Record<string, string[]>>({})\n\nconst snapshot = ref('')\n\nconst canManage = computed(() => context.can('recipes.manage'))\n\n/*\n * Other people on the same recipe: a refused save is merged with theirs field by field and\n * saved again, only a field both changed is asked about, and a save made meanwhile — by a\n * colleague or an agent — is offered before this editor writes over it.\n */\nconst editing = useEditing<ScreenModel>({\n entity: 'recipes',\n id: () => recipe.value?.id,\n values,\n revision,\n read: async () => {\n const detail = await api.get(id.value)\n\n return { values: detail.values, revision: detail.revision }\n },\n adopt: (theirs) => {\n snapshot.value = JSON.stringify(theirs.values)\n },\n // Published or restored by somebody else: the badge and the address follow, and the form\n // stays as it is.\n refresh: () => refresh(),\n restore: async () => {\n if (recipe.value) await api.restore(recipe.value.id)\n },\n canWrite: () => canManage.value,\n busy: () => saving.value || flight !== undefined,\n screen: 'recipes.form',\n})\n\nconst conflict = editing.conflict\n\n/** Closed for writing: no permission, or a publication in flight. */\nconst locked = computed(() => !canManage.value || working.value)\n\nconst current = computed(() => JSON.stringify(values.value))\nconst dirty = computed(() => snapshot.value !== '' && current.value !== snapshot.value)\n\nconst state = computed<'saving' | 'unsaved' | 'saved'>(() => {\n if (saving.value) return 'saving'\n\n return dirty.value ? 'unsaved' : 'saved'\n})\n\n/** The name follows the field rather than the answer: a rename shows at the top as it is typed. */\nconst title = computed(() => {\n const written = values.value.title as LocalizedValue | string | null | undefined\n\n return localizedValue(written, locales.active.value, recipe.value?.title ?? '')\n})\n\n/** Since when it is on the site, under the name — the one fact the badge beside it cannot carry. */\nconst publication = computed(() => {\n const row = recipe.value\n\n if (!row || (row.status !== 'published' && row.status !== 'modified')) return ''\n\n return t('recipe.live-since', { date: dates.short(row.published_at) })\n})\n\n/* \"Similar recipes\" pick from recipes, and never offer the one they are on. */\nprovideRelationOwner({ type: 'recipe', id: computed(() => recipe.value?.id ?? null) })\n\n/* The address field is the panel's shared one (`wx-slug`), and this is what it prints. */\nprovideRecordAddress({\n values,\n prefix,\n path: computed(() => recipe.value?.path),\n moving: () => t('recipe.address-moving'),\n})\n\nprovideRecipeEditor({ recipe, canManage: canManage.value, reload: () => load(true) })\n\nfunction take(detail: RecipeDetail): void {\n recipe.value = detail.recipe\n values.value = detail.values\n revision.value = detail.revision\n prefix.value = detail.prefix\n previewUrl.value = detail.preview_url\n snapshot.value = JSON.stringify(detail.values)\n editing.opened(detail.values)\n conflict.value = null\n}\n\n/**\n * Read the recipe again — quietly unless this is the first time: the skeleton replaces the whole\n * screen, and a publication answered with it would throw the editor back onto the first tab.\n */\nasync function load(silent = false): Promise<void> {\n if (!silent) loading.value = true\n\n try {\n take(await api.get(id.value))\n } catch (error) {\n toast.danger(message(error))\n void router.push(list.value)\n } finally {\n loading.value = false\n }\n}\n\n/**\n * What surrounds the form, read again: the row with its status, the address, the preview. Not\n * the values — the form is somebody's work in progress, and a change of its content is the\n * notice's to offer.\n */\nasync function refresh(): Promise<void> {\n try {\n const detail = await api.get(id.value)\n\n recipe.value = detail.recipe\n prefix.value = detail.prefix\n previewUrl.value = detail.preview_url\n } catch {\n // The heartbeat says what happened; a row that could not be read again stays as it was.\n }\n}\n\n/* One pending save at a time, and one pending pause. */\nlet timer: ReturnType<typeof setTimeout> | undefined\n\nfunction schedule(): void {\n if (!canManage.value || conflict.value || !dirty.value || editing.stopped.value) return\n\n clearTimeout(timer)\n timer = setTimeout(() => void save(), PAUSE)\n}\n\n/** A field was left: write now rather than at the end of a pause nobody is waiting through. */\nfunction onFocusOut(): void {\n if (!canManage.value || conflict.value || !dirty.value || saving.value || editing.stopped.value)\n return\n\n clearTimeout(timer)\n void save()\n}\n\n/* The save on its way, and what it carries. */\nlet flight: { carried: string; done: Promise<void> } | undefined\n\n/**\n * A save asked for while another is on its way waits for it instead of being dropped: leaving and\n * publishing read `dirty` right after, and a save that was skipped reads as one that failed.\n */\nasync function save(): Promise<void> {\n while (flight) {\n const { carried, done } = flight\n\n await done\n\n // That request wrote what it carried; go again only for what was typed while it was out.\n if (!dirty.value || conflict.value || current.value === carried) return\n }\n\n const carried = current.value\n const done = write()\n\n flight = { carried, done }\n\n try {\n await done\n } finally {\n if (flight?.done === done) flight = undefined\n }\n}\n\nasync function write(): Promise<void> {\n if (!recipe.value || !canManage.value) return\n\n clearTimeout(timer)\n\n // What is being sent, so that whatever is typed while the request is in flight stays dirty and\n // gets its own save rather than being marked as written.\n const sending = current.value\n const sent = values.value\n\n saving.value = true\n errors.value = {}\n\n try {\n const detail = await api.save(recipe.value.id, { values: sent, revision: revision.value })\n\n recipe.value = detail.recipe\n revision.value = detail.revision\n prefix.value = detail.prefix\n previewUrl.value = detail.preview_url\n conflict.value = null\n\n if (current.value === sending) snapshot.value = sending\n\n // What the server holds now is what was sent: the base of the next merge.\n editing.opened(sent)\n } catch (error) {\n const failure = error as {\n status?: number\n body?: RecipeConflict & { errors?: Record<string, string[]> }\n }\n\n if (failure.status === 409 && failure.body?.data) {\n const theirs = failure.body.data\n\n // Nothing overlapping: both edits are in the form now, and they go to the server again.\n if (\n editing.refused({\n values: theirs.values,\n revision: theirs.revision,\n changed: failure.body.changed,\n })\n ) {\n recipe.value = theirs.recipe\n void save()\n }\n } else if (failure.body?.errors) {\n errors.value = failure.body.errors\n toast.danger(t('recipe.save-failed'))\n } else if (!(await editing.failed(error))) {\n // In the bin is said by the notice above the form, with the way back; anything else here.\n toast.danger(message(error))\n }\n } finally {\n saving.value = false\n }\n}\n\n/** Give up what was typed and take the recipe as it now is; the component asked first. */\nasync function takeTheirs(): Promise<void> {\n await load(true)\n}\n\n/** Throw away what is waiting — asked about, because it is the one button here that loses writing. */\nasync function discard(): Promise<void> {\n if (!recipe.value) return\n\n const agreed = await confirm({\n title: panel('editor.discard-title'),\n message: panel('editor.discard-text'),\n confirmText: t('recipe.discard'),\n // Not «Cancel»: beside «Discard changes» the two read as the same word.\n cancelText: panel('editor.keep-changes'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n working.value = true\n\n try {\n take(await api.discard(recipe.value.id))\n toast.success(t('recipe.discarded'))\n } catch (error) {\n toast.danger(message(error))\n } finally {\n working.value = false\n }\n}\n\n/** Publishing is asked about, because it is the one action here that visitors see. */\nasync function publish(): Promise<void> {\n // Saved before asking: the question names the address the draft will publish at, and only a\n // saved draft has one — an address typed but not saved was asked about under the old one.\n if (dirty.value) await save()\n if (conflict.value || dirty.value) return\n\n if (!recipe.value) return\n\n // The draft on the server may hold somebody else's edit this editor never pulled in: said\n // before it goes on the site, with whose it is and where, rather than published unseen.\n const held = await editing.beforePublish()\n\n if (!held) return\n\n const row = recipe.value\n\n // Publishing moves a renamed slug, so the question names where the page will be, not where\n // it is — and says the old address will lead there, since that is what happens to it.\n const next = row.next_path ?? row.path\n const old =\n row.next_path != null && row.path !== null\n ? ` ${panel('editor.publish-moves', { old: `/${row.path}` })}`\n : ''\n\n // One question is enough: whoever just agreed to publish somebody else's changes has said yes.\n const agreed =\n held.asked ||\n (await confirm({\n title: t('recipe.publish-title', { title: title.value || row.title }),\n message:\n next === null\n ? t('recipe.publish-nowhere')\n : t('recipe.publish-text', { address: `/${next}` }) + old,\n confirmText: t('panel.publish'),\n cancelText: t('panel.cancel'),\n }))\n\n if (!agreed) return\n\n working.value = true\n\n try {\n await api.publish(row.id, held.revision)\n await load(true)\n toast.success(t('panel.published'))\n } catch (error) {\n if (!(await editing.failed(error))) toast.danger(message(error))\n } finally {\n working.value = false\n }\n}\n\n/**\n * Taking it off the site is asked about, as publishing is: visitors are who notice. Nothing\n * written is lost, and «Publish» brings it back at the same address.\n */\nasync function unpublish(): Promise<void> {\n const row = recipe.value\n\n if (!row) return\n\n const agreed = await confirm({\n title: panel('editor.unpublish-title'),\n message: panel('editor.unpublish-text'),\n confirmText: panel('editor.unpublish'),\n cancelText: panel('editor.keep-published'),\n })\n\n if (!agreed) return\n\n working.value = true\n\n try {\n await api.unpublish(row.id)\n await load(true)\n toast.success(panel('editor.unpublished'))\n } catch (error) {\n toast.danger(message(error))\n } finally {\n working.value = false\n }\n}\n\nfunction badge(): 'default' | 'success' {\n const status = recipe.value?.status\n\n return status === 'published' || status === 'modified' ? 'success' : 'default'\n}\n\n/* The browser's own guard: it cannot wait for a request, so all it can do is ask. */\nfunction leaveGuard(event: BeforeUnloadEvent): void {\n if (dirty.value) event.preventDefault()\n}\n\nwatch(current, schedule)\nwatch(id, () => void load())\n\nonMounted(() => {\n void load()\n window.addEventListener('beforeunload', leaveGuard)\n})\n\nonBeforeUnmount(() => {\n clearTimeout(timer)\n window.removeEventListener('beforeunload', leaveGuard)\n})\n\n/**\n * Leaving flushes the pause rather than asking about it; the question is asked only when the save\n * did not go through — a conflict, a refused field, a server that is not there.\n */\nonBeforeRouteLeave(async () => {\n if (!dirty.value || !canManage.value) return true\n\n await save()\n\n if (!dirty.value) return true\n\n return await confirm({\n title: t('recipe.leave-title'),\n message: t('recipe.leave-text'),\n confirmText: t('recipe.leave'),\n cancelText: t('panel.cancel'),\n tone: 'danger',\n })\n})\n\n/* What leads away from the recipe. On a phone the head folds them into the ···. */\nconst actions = computed<ScreenAction[]>(() => {\n const leads: ScreenAction[] = []\n\n if (previewUrl.value) {\n leads.push({ key: 'preview', label: t('recipe.preview'), icon: 'eye', href: previewUrl.value })\n }\n\n if (recipe.value?.url && recipe.value.status !== 'draft') {\n leads.push({\n key: 'site',\n label: t('panel.open-on-site'),\n icon: 'link',\n href: recipe.value.url,\n })\n }\n\n // On the site now: the way off it is a button of its own beside «Open on the site», not a\n // line in the ···. Hiding loses nothing and is undone by «Publish».\n if (\n canManage.value &&\n (recipe.value?.status === 'published' || recipe.value?.status === 'modified')\n ) {\n leads.push({\n key: 'unpublish',\n label: panel('editor.unpublish'),\n icon: 'eye-off',\n run: () => void unpublish(),\n })\n }\n\n // Throwing away writing lives in the ···, never beside \"publish\", and only while there is a\n // difference between what is written and what is on the site.\n if (recipe.value?.status === 'modified') {\n leads.push({\n key: 'discard',\n label: t('recipe.discard'),\n icon: 'refresh',\n danger: true,\n menu: true,\n run: () => void discard(),\n })\n }\n\n return leads\n})\n</script>\n\n<template>\n <div class=\"wx-recipe-editor\" @focusout=\"onFocusOut\">\n <template v-if=\"loading || !recipe\">\n <wx-skeleton class=\"wx-recipe-editor__ghost\" title :rows=\"1\" />\n <wx-card><wx-skeleton :rows=\"8\" /></wx-card>\n </template>\n\n <template v-else>\n <wx-screen-head\n divider\n :back=\"list\"\n :back-label=\"t('module.recipes')\"\n :title=\"title || t('recipe.untitled')\"\n :subtitle=\"publication\"\n :actions=\"actions\"\n >\n <template #trail>\n <wx-breadcrumb size=\"sm\" :label=\"t('recipe.trail')\">\n <wx-breadcrumb-item :as=\"'router-link'\" :to=\"list\">\n {{ t('module.recipes') }}\n </wx-breadcrumb-item>\n <wx-breadcrumb-item current>{{ title || t('recipe.untitled') }}</wx-breadcrumb-item>\n </wx-breadcrumb>\n </template>\n\n <template #title-after>\n <wx-badge :type=\"badge()\" dot>{{ t(`panel.status-${recipe.status}`) }}</wx-badge>\n <wx-badge v-if=\"recipe.status === 'modified'\" type=\"primary\" round>\n {{ t('panel.edits') }}\n </wx-badge>\n </template>\n </wx-screen-head>\n\n <!-- Somebody else wrote while this editor was open. What does not overlap is merged\n without a word; what does is listed place by place. -->\n <wx-editing-alerts :editing=\"editing\" @save=\"save\" @theirs=\"takeTheirs\" />\n\n <wx-screen v-model=\"values\" name=\"recipes.form\" :errors=\"errors\" :disabled=\"locked\" />\n\n <wx-action-bar v-if=\"canManage\">\n <template #state>\n <wx-save-state v-if=\"!editing.stopped.value\" :state=\"state\" />\n </template>\n\n <wx-button\n variant=\"outline\"\n :loading=\"saving\"\n :disabled=\"!dirty || editing.stopped.value\"\n :title=\"editing.blocked.value\"\n @click=\"save\"\n >\n {{ t('recipe.save') }}\n </wx-button>\n\n <wx-button\n type=\"primary\"\n :loading=\"working\"\n :disabled=\"editing.stopped.value || (recipe.status === 'published' && !dirty)\"\n :title=\"editing.blocked.value\"\n @click=\"publish\"\n >\n {{ t('panel.publish') }}\n </wx-button>\n </wx-action-bar>\n </template>\n </div>\n</template>\n\n<style scoped>\n/*\n * No constructor here, so nothing needs the height of the window: the page scrolls, and the\n * panel gives a screen with an action bar a floor tall enough for the bar to sit at the bottom\n * (`WxMain`, CLAUDE.md §4 on `data-wx-fill`).\n */\n.wx-recipe-editor {\n display: flex;\n flex-direction: column;\n gap: var(--wx-gap, var(--wx-space-16));\n min-height: 0;\n container-type: inline-size;\n}\n\n.wx-recipe-editor__ghost {\n max-width: 420px;\n}\n</style>\n"],"names":["PAUSE","props","__props","context","useAdmin","api","createRecipesApi","route","useRoute","router","useRouter","locales","useLocales","dates","useDates","useRecipesMessages","t","useTranslate","panel","message","useErrorText","id","computed","list","recipe","ref","values","revision","prefix","previewUrl","loading","saving","working","errors","snapshot","canManage","editing","useEditing","detail","theirs","refresh","flight","conflict","locked","current","dirty","state","title","written","localizedValue","publication","row","provideRelationOwner","provideRecordAddress","provideRecipeEditor","load","take","silent","error","toast","timer","schedule","save","onFocusOut","carried","done","write","sending","sent","failure","takeTheirs","discard","confirm","publish","held","next","old","unpublish","badge","status","leaveGuard","event","watch","onMounted","onBeforeUnmount","onBeforeRouteLeave","actions","leads","_createElementBlock","_Fragment","_createVNode","_unref","WxSkeleton","WxCard","WxScreenHead","WxBreadcrumb","WxBreadcrumbItem","_createTextVNode","_toDisplayString","WxBadge","_createBlock","WxEditingAlerts","WxScreen","$event","WxActionBar","WxSaveState","WxButton"],"mappings":"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"}
|