@fonderie/vue-courier-admin-screens 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +362 -80
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +146 -3
- package/dist/index.d.ts +146 -3
- package/dist/index.js +364 -83
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -20,13 +20,14 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
TemplateCreateScreen: () => TemplateCreateScreen,
|
|
23
24
|
TemplateEditorScreen: () => TemplateEditorScreen,
|
|
24
25
|
TemplateListScreen: () => TemplateListScreen
|
|
25
26
|
});
|
|
26
27
|
module.exports = __toCommonJS(index_exports);
|
|
27
28
|
|
|
28
|
-
// src/screens/
|
|
29
|
-
var
|
|
29
|
+
// src/screens/TemplateCreateScreen.ts
|
|
30
|
+
var import_client = require("@fonderie/client");
|
|
30
31
|
var import_vue = require("vue");
|
|
31
32
|
|
|
32
33
|
// src/styles.ts
|
|
@@ -265,34 +266,194 @@ var styles = {
|
|
|
265
266
|
dot: { width: "6px", height: "6px", borderRadius: "999px", background: "currentColor" }
|
|
266
267
|
};
|
|
267
268
|
|
|
269
|
+
// src/screens/TemplateCreateScreen.ts
|
|
270
|
+
var TYPE_RE = /^[a-z0-9][a-z0-9_-]{0,80}$/;
|
|
271
|
+
var LOCALE_RE = /^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*$/;
|
|
272
|
+
var TemplateCreateScreen = (0, import_vue.defineComponent)({
|
|
273
|
+
name: "FonderieTemplateCreateScreen",
|
|
274
|
+
props: {
|
|
275
|
+
client: { type: Object, required: true },
|
|
276
|
+
/**
|
|
277
|
+
* Given ⇒ add a LOCALE of this existing template: the type is fixed and the
|
|
278
|
+
* form starts from its default-locale copy. Omitted ⇒ a brand-new template
|
|
279
|
+
* type (one the app sends that has no stored copy yet).
|
|
280
|
+
*/
|
|
281
|
+
type: { type: String, default: void 0 },
|
|
282
|
+
/** Locales already in use, offered as suggestions. */
|
|
283
|
+
locales: { type: Array, default: () => [] },
|
|
284
|
+
/** The console language; defaults to English. */
|
|
285
|
+
locale: { type: String, default: void 0 }
|
|
286
|
+
},
|
|
287
|
+
emits: {
|
|
288
|
+
/** What was created, so the caller can open it in the editor. */
|
|
289
|
+
created: (_created) => true
|
|
290
|
+
},
|
|
291
|
+
setup(props, { emit }) {
|
|
292
|
+
const addingLocale = props.type !== void 0;
|
|
293
|
+
const newType = (0, import_vue.ref)("");
|
|
294
|
+
const targetLocale = (0, import_vue.ref)("");
|
|
295
|
+
const subject = (0, import_vue.ref)("");
|
|
296
|
+
const html = (0, import_vue.ref)("");
|
|
297
|
+
const text = (0, import_vue.ref)("");
|
|
298
|
+
const error = (0, import_vue.ref)(null);
|
|
299
|
+
const busy = (0, import_vue.ref)(false);
|
|
300
|
+
const tr = (key, params) => (0, import_client.createAdminT)(props.locale)(key, params);
|
|
301
|
+
(0, import_vue.onMounted)(() => {
|
|
302
|
+
if (!props.type) return;
|
|
303
|
+
props.client.getTemplate(props.type, null).then(({ result }) => {
|
|
304
|
+
subject.value = result.subject ?? "";
|
|
305
|
+
html.value = result.html ?? "";
|
|
306
|
+
text.value = result.text;
|
|
307
|
+
}).catch(() => void 0);
|
|
308
|
+
});
|
|
309
|
+
async function submit(e) {
|
|
310
|
+
e.preventDefault();
|
|
311
|
+
error.value = null;
|
|
312
|
+
const t = addingLocale ? props.type : newType.value.trim();
|
|
313
|
+
const l = targetLocale.value.trim() || null;
|
|
314
|
+
if (!TYPE_RE.test(t)) {
|
|
315
|
+
error.value = tr("templates.create.errorType");
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
if (addingLocale && !l) {
|
|
319
|
+
error.value = tr("templates.create.errorLocaleRequired");
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
if (l && !LOCALE_RE.test(l)) {
|
|
323
|
+
error.value = tr("templates.create.errorLocale");
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
if (!text.value.trim()) {
|
|
327
|
+
error.value = tr("templates.create.errorText");
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
busy.value = true;
|
|
331
|
+
try {
|
|
332
|
+
const exists = await props.client.getTemplate(t, l).then(
|
|
333
|
+
() => true,
|
|
334
|
+
(err) => !(err instanceof import_client.FonderieApiError && err.status === 404)
|
|
335
|
+
);
|
|
336
|
+
if (exists) {
|
|
337
|
+
error.value = l ? tr("templates.create.existsLocale", { type: t, locale: l }) : tr("templates.create.exists", { type: t });
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
const input = { text: text.value, active: true };
|
|
341
|
+
if (subject.value) input.subject = subject.value;
|
|
342
|
+
if (html.value) input.html = html.value;
|
|
343
|
+
await props.client.setTemplate(t, input, l);
|
|
344
|
+
emit("created", { type: t, locale: l });
|
|
345
|
+
} catch (err) {
|
|
346
|
+
error.value = err instanceof import_client.FonderieApiError ? err.explanation : String(err);
|
|
347
|
+
} finally {
|
|
348
|
+
busy.value = false;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
const field = (id, label, model, opts = {}) => [
|
|
352
|
+
(0, import_vue.h)("label", { style: styles.label, for: id }, label),
|
|
353
|
+
(0, import_vue.h)(opts.textarea ? "textarea" : "input", {
|
|
354
|
+
id,
|
|
355
|
+
style: opts.textarea ? { ...styles.textarea, minHeight: opts.minHeight } : styles.input,
|
|
356
|
+
value: model.value,
|
|
357
|
+
autocomplete: "off",
|
|
358
|
+
spellcheck: false,
|
|
359
|
+
...opts.placeholder ? { placeholder: opts.placeholder } : {},
|
|
360
|
+
...opts.list ? { list: opts.list } : {},
|
|
361
|
+
onInput: (ev) => {
|
|
362
|
+
model.value = ev.target.value;
|
|
363
|
+
}
|
|
364
|
+
})
|
|
365
|
+
];
|
|
366
|
+
return () => (0, import_vue.h)("div", { style: { ...styles.container, maxWidth: "760px" } }, [
|
|
367
|
+
(0, import_vue.h)(
|
|
368
|
+
"h1",
|
|
369
|
+
{ style: styles.title },
|
|
370
|
+
addingLocale ? tr("templates.create.titleLocale", { type: props.type }) : tr("templates.create.titleNew")
|
|
371
|
+
),
|
|
372
|
+
(0, import_vue.h)(
|
|
373
|
+
"p",
|
|
374
|
+
{ style: { ...styles.meta, lineHeight: 1.5 } },
|
|
375
|
+
addingLocale ? tr("templates.create.hintLocale") : tr("templates.create.hintNew")
|
|
376
|
+
),
|
|
377
|
+
(0, import_vue.h)("form", { style: styles.form, onSubmit: (ev) => void submit(ev) }, [
|
|
378
|
+
...addingLocale ? [] : field("new-template-type", tr("templates.create.type"), newType, { placeholder: "weekly-digest" }),
|
|
379
|
+
...field(
|
|
380
|
+
"new-template-locale",
|
|
381
|
+
addingLocale ? tr("templates.create.locale") : tr("templates.create.localeOptional"),
|
|
382
|
+
targetLocale,
|
|
383
|
+
{ placeholder: "fr", list: "new-template-locales" }
|
|
384
|
+
),
|
|
385
|
+
(0, import_vue.h)(
|
|
386
|
+
"datalist",
|
|
387
|
+
{ id: "new-template-locales" },
|
|
388
|
+
[...new Set(props.locales)].map((l) => (0, import_vue.h)("option", { key: l, value: l }))
|
|
389
|
+
),
|
|
390
|
+
...field("new-template-subject", tr("templates.subject"), subject),
|
|
391
|
+
...field("new-template-html", tr("templates.htmlBody"), html, { textarea: true, minHeight: "160px" }),
|
|
392
|
+
...field("new-template-text", tr("templates.textBody"), text, {
|
|
393
|
+
textarea: true,
|
|
394
|
+
minHeight: "100px"
|
|
395
|
+
}),
|
|
396
|
+
error.value ? (0, import_vue.h)("p", { style: styles.error, role: "alert" }, error.value) : null,
|
|
397
|
+
(0, import_vue.h)(
|
|
398
|
+
"button",
|
|
399
|
+
{
|
|
400
|
+
type: "submit",
|
|
401
|
+
disabled: busy.value,
|
|
402
|
+
style: { ...styles.button, marginTop: "16px" }
|
|
403
|
+
},
|
|
404
|
+
busy.value ? tr("templates.create.creating") : addingLocale ? tr("templates.create.submitLocale") : tr("templates.create.submitNew")
|
|
405
|
+
)
|
|
406
|
+
])
|
|
407
|
+
]);
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
|
|
268
411
|
// src/screens/TemplateEditorScreen.ts
|
|
412
|
+
var import_client2 = require("@fonderie/client");
|
|
413
|
+
var import_vue_courier_admin = require("@fonderie/vue-courier-admin");
|
|
414
|
+
var import_vue2 = require("vue");
|
|
269
415
|
var PREVIEW_DEBOUNCE_MS = 500;
|
|
270
416
|
var IMPLICIT = /* @__PURE__ */ new Set(["subject", "preheader", "brandName"]);
|
|
271
|
-
var TemplateEditorScreen = (0,
|
|
417
|
+
var TemplateEditorScreen = (0, import_vue2.defineComponent)({
|
|
272
418
|
name: "FonderieTemplateEditorScreen",
|
|
273
419
|
props: {
|
|
274
420
|
client: { type: Object, required: true },
|
|
275
421
|
type: { type: String, required: true },
|
|
276
|
-
locale: { type: String, default: null }
|
|
422
|
+
locale: { type: String, default: null },
|
|
423
|
+
/**
|
|
424
|
+
* A built-in email's default-locale row (the list's `system` flag): it can
|
|
425
|
+
* be edited and rolled back, never deleted — Delete is not offered.
|
|
426
|
+
*/
|
|
427
|
+
system: { type: Boolean, default: false },
|
|
428
|
+
/** Shows "+ Add locale" on the default locale (emits add-locale). */
|
|
429
|
+
allowAddLocale: { type: Boolean, default: false },
|
|
430
|
+
/** The console language (`locale` is the template's); defaults to English. */
|
|
431
|
+
uiLocale: { type: String, default: void 0 }
|
|
277
432
|
},
|
|
278
433
|
emits: {
|
|
279
|
-
saved: () => true
|
|
434
|
+
saved: () => true,
|
|
435
|
+
/** The template was deleted and no longer exists. */
|
|
436
|
+
deleted: () => true,
|
|
437
|
+
/** Create a translation of this template. */
|
|
438
|
+
"add-locale": (_type) => true
|
|
280
439
|
},
|
|
281
440
|
setup(props, { emit }) {
|
|
441
|
+
const t = (key, params) => (0, import_client2.createAdminT)(props.uiLocale)(key, params);
|
|
282
442
|
const { template, isLoading, error, refresh } = (0, import_vue_courier_admin.useTemplate)(
|
|
283
443
|
props.client,
|
|
284
444
|
props.type,
|
|
285
445
|
props.locale
|
|
286
446
|
);
|
|
287
|
-
const { saveTemplate } = (0, import_vue_courier_admin.useTemplates)(props.client);
|
|
447
|
+
const { saveTemplate, removeTemplate } = (0, import_vue_courier_admin.useTemplates)(props.client);
|
|
448
|
+
const isDeleting = (0, import_vue2.ref)(false);
|
|
288
449
|
const { revisions, rollback } = (0, import_vue_courier_admin.useTemplateRevisions)(props.client, props.type, props.locale);
|
|
289
|
-
const isSaving = (0,
|
|
290
|
-
const saveError = (0,
|
|
291
|
-
const subject = (0,
|
|
292
|
-
const html = (0,
|
|
293
|
-
const text = (0,
|
|
294
|
-
const active = (0,
|
|
295
|
-
(0,
|
|
450
|
+
const isSaving = (0, import_vue2.ref)(false);
|
|
451
|
+
const saveError = (0, import_vue2.ref)(null);
|
|
452
|
+
const subject = (0, import_vue2.ref)("");
|
|
453
|
+
const html = (0, import_vue2.ref)("");
|
|
454
|
+
const text = (0, import_vue2.ref)("");
|
|
455
|
+
const active = (0, import_vue2.ref)(true);
|
|
456
|
+
(0, import_vue2.watch)(template, (tpl) => {
|
|
296
457
|
if (!tpl) return;
|
|
297
458
|
subject.value = tpl.subject ?? "";
|
|
298
459
|
html.value = tpl.html ?? "";
|
|
@@ -305,20 +466,20 @@ var TemplateEditorScreen = (0, import_vue.defineComponent)({
|
|
|
305
466
|
error: previewError,
|
|
306
467
|
renderPreview
|
|
307
468
|
} = (0, import_vue_courier_admin.useTemplatePreview)(props.client);
|
|
308
|
-
const sampleJson = (0,
|
|
309
|
-
const sampleError = (0,
|
|
469
|
+
const sampleJson = (0, import_vue2.ref)("{}");
|
|
470
|
+
const sampleError = (0, import_vue2.ref)(null);
|
|
310
471
|
async function run() {
|
|
311
472
|
let data = {};
|
|
312
473
|
try {
|
|
313
474
|
const parsed = JSON.parse(sampleJson.value || "{}");
|
|
314
475
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
315
|
-
sampleError.value = "
|
|
476
|
+
sampleError.value = "templates.editor.sampleNotObject";
|
|
316
477
|
return;
|
|
317
478
|
}
|
|
318
479
|
data = parsed;
|
|
319
480
|
sampleError.value = null;
|
|
320
481
|
} catch {
|
|
321
|
-
sampleError.value = "
|
|
482
|
+
sampleError.value = "templates.editor.sampleInvalid";
|
|
322
483
|
return;
|
|
323
484
|
}
|
|
324
485
|
const result = await renderPreview(
|
|
@@ -341,12 +502,12 @@ var TemplateEditorScreen = (0, import_vue.defineComponent)({
|
|
|
341
502
|
}
|
|
342
503
|
}
|
|
343
504
|
let previewTimer;
|
|
344
|
-
(0,
|
|
505
|
+
(0, import_vue2.watch)([template, subject, html, text, sampleJson], () => {
|
|
345
506
|
if (!template.value) return;
|
|
346
507
|
clearTimeout(previewTimer);
|
|
347
508
|
previewTimer = setTimeout(() => void run(), PREVIEW_DEBOUNCE_MS);
|
|
348
509
|
});
|
|
349
|
-
const dirty = (0,
|
|
510
|
+
const dirty = (0, import_vue2.computed)(() => {
|
|
350
511
|
const tpl = template.value;
|
|
351
512
|
return !!tpl && (subject.value !== (tpl.subject ?? "") || html.value !== (tpl.html ?? "") || text.value !== tpl.text || active.value !== tpl.active);
|
|
352
513
|
});
|
|
@@ -368,35 +529,64 @@ var TemplateEditorScreen = (0, import_vue.defineComponent)({
|
|
|
368
529
|
isSaving.value = false;
|
|
369
530
|
}
|
|
370
531
|
}
|
|
532
|
+
async function handleDelete() {
|
|
533
|
+
const message = props.locale ? t("templates.editor.confirmDeleteLocale", { locale: props.locale, type: props.type }) : t("templates.editor.confirmDelete", { type: props.type });
|
|
534
|
+
if (!window.confirm(message)) return;
|
|
535
|
+
isDeleting.value = true;
|
|
536
|
+
saveError.value = null;
|
|
537
|
+
try {
|
|
538
|
+
await removeTemplate(props.type, props.locale);
|
|
539
|
+
emit("deleted");
|
|
540
|
+
} catch (err) {
|
|
541
|
+
saveError.value = err;
|
|
542
|
+
} finally {
|
|
543
|
+
isDeleting.value = false;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
371
546
|
function renderRevision(rev) {
|
|
372
|
-
return (0,
|
|
373
|
-
(0,
|
|
547
|
+
return (0, import_vue2.h)("li", { key: rev.version, style: styles.revisionRow }, [
|
|
548
|
+
(0, import_vue2.h)(
|
|
374
549
|
"span",
|
|
375
|
-
`v${rev.version} \u2014 ${rev.actor ?? "
|
|
550
|
+
`v${rev.version} \u2014 ${rev.actor ?? t("templates.editor.unknownActor")} \u2014 ${(0, import_client2.formatAdminDate)(rev.createdAt, props.uiLocale)}`
|
|
376
551
|
),
|
|
377
|
-
(0,
|
|
552
|
+
(0, import_vue2.h)(
|
|
378
553
|
"button",
|
|
379
554
|
{ type: "button", style: styles.rollbackButton, onClick: () => rollback(rev.version) },
|
|
380
|
-
"
|
|
555
|
+
t("templates.editor.rollBack")
|
|
381
556
|
)
|
|
382
557
|
]);
|
|
383
558
|
}
|
|
384
559
|
return () => {
|
|
385
|
-
if (isLoading.value) return (0,
|
|
560
|
+
if (isLoading.value) return (0, import_vue2.h)("p", { style: styles.status }, t("templates.editor.loading"));
|
|
386
561
|
if (error.value)
|
|
387
|
-
return (0,
|
|
388
|
-
return (0,
|
|
389
|
-
(0,
|
|
390
|
-
|
|
391
|
-
"
|
|
392
|
-
|
|
393
|
-
|
|
562
|
+
return (0, import_vue2.h)("p", { style: styles.error, role: "alert" }, error.value.explanation);
|
|
563
|
+
return (0, import_vue2.h)("div", { style: styles.container }, [
|
|
564
|
+
(0, import_vue2.h)(
|
|
565
|
+
"div",
|
|
566
|
+
{ style: { display: "flex", alignItems: "center", gap: "12px", flexWrap: "wrap" } },
|
|
567
|
+
[
|
|
568
|
+
(0, import_vue2.h)("h1", { style: styles.title }, props.type),
|
|
569
|
+
props.allowAddLocale && !props.locale ? (0, import_vue2.h)(
|
|
570
|
+
"button",
|
|
571
|
+
{
|
|
572
|
+
type: "button",
|
|
573
|
+
style: { ...styles.rollbackButton, marginLeft: "auto" },
|
|
574
|
+
onClick: () => emit("add-locale", props.type)
|
|
575
|
+
},
|
|
576
|
+
t("templates.editor.addLocale")
|
|
577
|
+
) : null
|
|
578
|
+
]
|
|
394
579
|
),
|
|
395
|
-
(0,
|
|
396
|
-
(0,
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
580
|
+
(0, import_vue2.h)("p", { style: styles.meta }, [
|
|
581
|
+
(0, import_vue2.h)("strong", props.locale ?? t("templates.defaultLocale")),
|
|
582
|
+
` \xB7 v${template.value?.version ?? 1}`,
|
|
583
|
+
props.system ? ` \xB7 ${t("templates.editor.builtInNote")}` : ""
|
|
584
|
+
]),
|
|
585
|
+
(0, import_vue2.h)("div", { style: styles.split }, [
|
|
586
|
+
(0, import_vue2.h)("div", { style: styles.column }, [
|
|
587
|
+
(0, import_vue2.h)("form", { style: styles.form, onSubmit: handleSubmit }, [
|
|
588
|
+
(0, import_vue2.h)("label", { style: styles.label, for: "template-subject" }, t("templates.subject")),
|
|
589
|
+
(0, import_vue2.h)("input", {
|
|
400
590
|
id: "template-subject",
|
|
401
591
|
style: styles.input,
|
|
402
592
|
value: subject.value,
|
|
@@ -404,8 +594,8 @@ var TemplateEditorScreen = (0, import_vue.defineComponent)({
|
|
|
404
594
|
subject.value = e.target.value;
|
|
405
595
|
}
|
|
406
596
|
}),
|
|
407
|
-
(0,
|
|
408
|
-
(0,
|
|
597
|
+
(0, import_vue2.h)("label", { style: styles.label, for: "template-html" }, t("templates.htmlBody")),
|
|
598
|
+
(0, import_vue2.h)("textarea", {
|
|
409
599
|
id: "template-html",
|
|
410
600
|
style: styles.textarea,
|
|
411
601
|
rows: 8,
|
|
@@ -414,8 +604,8 @@ var TemplateEditorScreen = (0, import_vue.defineComponent)({
|
|
|
414
604
|
html.value = e.target.value;
|
|
415
605
|
}
|
|
416
606
|
}),
|
|
417
|
-
(0,
|
|
418
|
-
(0,
|
|
607
|
+
(0, import_vue2.h)("label", { style: styles.label, for: "template-text" }, t("templates.textBody")),
|
|
608
|
+
(0, import_vue2.h)("textarea", {
|
|
419
609
|
id: "template-text",
|
|
420
610
|
style: styles.textarea,
|
|
421
611
|
rows: 4,
|
|
@@ -425,32 +615,62 @@ var TemplateEditorScreen = (0, import_vue.defineComponent)({
|
|
|
425
615
|
text.value = e.target.value;
|
|
426
616
|
}
|
|
427
617
|
}),
|
|
428
|
-
(0,
|
|
429
|
-
(0,
|
|
618
|
+
(0, import_vue2.h)("label", { style: styles.checkboxLabel }, [
|
|
619
|
+
(0, import_vue2.h)("input", {
|
|
430
620
|
type: "checkbox",
|
|
431
621
|
checked: active.value,
|
|
432
622
|
onChange: (e) => {
|
|
433
623
|
active.value = e.target.checked;
|
|
434
624
|
}
|
|
435
625
|
}),
|
|
436
|
-
"
|
|
626
|
+
t("templates.editor.active")
|
|
437
627
|
]),
|
|
438
|
-
saveError.value ? (0,
|
|
439
|
-
|
|
440
|
-
(0,
|
|
628
|
+
saveError.value?.reason === "VERSION_CONFLICT" ? (0, import_vue2.h)("p", { style: styles.error, role: "alert" }, [
|
|
629
|
+
`${t("templates.editor.conflict")} `,
|
|
630
|
+
(0, import_vue2.h)(
|
|
631
|
+
"button",
|
|
632
|
+
{
|
|
633
|
+
type: "button",
|
|
634
|
+
style: styles.rollbackButton,
|
|
635
|
+
onClick: () => {
|
|
636
|
+
saveError.value = null;
|
|
637
|
+
void refresh();
|
|
638
|
+
}
|
|
639
|
+
},
|
|
640
|
+
t("common.reload")
|
|
641
|
+
)
|
|
642
|
+
]) : saveError.value ? (0, import_vue2.h)("p", { style: styles.error, role: "alert" }, saveError.value.explanation) : null,
|
|
643
|
+
(0, import_vue2.h)("div", { style: styles.saveRow }, [
|
|
644
|
+
(0, import_vue2.h)(
|
|
441
645
|
"button",
|
|
442
646
|
{
|
|
443
647
|
type: "submit",
|
|
444
648
|
disabled: isSaving.value || !dirty.value,
|
|
445
649
|
style: dirty.value ? styles.button : styles.buttonDisabled
|
|
446
650
|
},
|
|
447
|
-
isSaving.value ? "
|
|
651
|
+
isSaving.value ? t("common.saving") : t("common.save")
|
|
448
652
|
),
|
|
449
|
-
!dirty.value && !isSaving.value ? (0,
|
|
653
|
+
!dirty.value && !isSaving.value ? (0, import_vue2.h)("span", { style: styles.meta }, t("templates.editor.noChanges")) : null,
|
|
654
|
+
!props.system ? (0, import_vue2.h)(
|
|
655
|
+
"button",
|
|
656
|
+
{
|
|
657
|
+
type: "button",
|
|
658
|
+
disabled: isDeleting.value,
|
|
659
|
+
onClick: () => void handleDelete(),
|
|
660
|
+
style: {
|
|
661
|
+
...styles.rollbackButton,
|
|
662
|
+
height: "36px",
|
|
663
|
+
marginLeft: "auto",
|
|
664
|
+
color: "var(--fonderie-danger,#e00)",
|
|
665
|
+
borderColor: "color-mix(in srgb, var(--fonderie-danger,#e00) 40%, transparent)"
|
|
666
|
+
}
|
|
667
|
+
},
|
|
668
|
+
isDeleting.value ? t("common.deleting") : t("common.delete")
|
|
669
|
+
) : null
|
|
450
670
|
])
|
|
451
671
|
]),
|
|
452
|
-
(0,
|
|
453
|
-
(0,
|
|
672
|
+
(0, import_vue2.h)("label", { style: styles.label, for: "template-sample" }, t("templates.editor.sampleData")),
|
|
673
|
+
(0, import_vue2.h)("textarea", {
|
|
454
674
|
id: "template-sample",
|
|
455
675
|
style: styles.textarea,
|
|
456
676
|
rows: 6,
|
|
@@ -460,31 +680,31 @@ var TemplateEditorScreen = (0, import_vue.defineComponent)({
|
|
|
460
680
|
sampleJson.value = e.target.value;
|
|
461
681
|
}
|
|
462
682
|
}),
|
|
463
|
-
sampleError.value ? (0,
|
|
683
|
+
sampleError.value ? (0, import_vue2.h)("p", { style: styles.error, role: "alert" }, t(sampleError.value)) : null
|
|
464
684
|
]),
|
|
465
|
-
(0,
|
|
466
|
-
(0,
|
|
467
|
-
(0,
|
|
468
|
-
(0,
|
|
685
|
+
(0, import_vue2.h)("div", { style: styles.previewColumn }, [
|
|
686
|
+
(0, import_vue2.h)("div", { style: styles.previewHeader }, [
|
|
687
|
+
(0, import_vue2.h)("span", { style: styles.label }, t("templates.editor.preview")),
|
|
688
|
+
(0, import_vue2.h)("span", { style: styles.meta }, isPreviewing.value ? t("templates.editor.rendering") : t("templates.editor.live"))
|
|
469
689
|
]),
|
|
470
|
-
previewError.value ? (0,
|
|
471
|
-
preview.value?.subject ? (0,
|
|
690
|
+
previewError.value ? (0, import_vue2.h)("p", { style: styles.error, role: "alert" }, previewError.value.explanation) : null,
|
|
691
|
+
preview.value?.subject ? (0, import_vue2.h)("p", { style: styles.previewSubject }, preview.value.subject) : null,
|
|
472
692
|
preview.value?.html ? (
|
|
473
693
|
// sandbox= — no scripts, no same-origin. Operator-authored HTML
|
|
474
694
|
// must never execute in the dashboard's origin, where the admin
|
|
475
695
|
// token lives.
|
|
476
|
-
(0,
|
|
477
|
-
title: "
|
|
696
|
+
(0, import_vue2.h)("iframe", {
|
|
697
|
+
title: t("templates.editor.previewTitle"),
|
|
478
698
|
style: styles.previewFrame,
|
|
479
699
|
sandbox: "",
|
|
480
700
|
srcdoc: preview.value.html
|
|
481
701
|
})
|
|
482
|
-
) : preview.value ? (0,
|
|
702
|
+
) : preview.value ? (0, import_vue2.h)("pre", { style: styles.previewText }, preview.value.text) : (0, import_vue2.h)("p", { style: styles.meta }, t("templates.editor.nothingRendered"))
|
|
483
703
|
])
|
|
484
704
|
]),
|
|
485
|
-
revisions.value.length > 0 ? (0,
|
|
486
|
-
(0,
|
|
487
|
-
(0,
|
|
705
|
+
revisions.value.length > 0 ? (0, import_vue2.h)("div", { style: styles.revisions }, [
|
|
706
|
+
(0, import_vue2.h)("h2", { style: styles.subtitle }, t("templates.editor.history")),
|
|
707
|
+
(0, import_vue2.h)("ul", { style: styles.list }, revisions.value.map(renderRevision))
|
|
488
708
|
]) : null
|
|
489
709
|
]);
|
|
490
710
|
};
|
|
@@ -492,21 +712,53 @@ var TemplateEditorScreen = (0, import_vue.defineComponent)({
|
|
|
492
712
|
});
|
|
493
713
|
|
|
494
714
|
// src/screens/TemplateListScreen.ts
|
|
715
|
+
var import_client3 = require("@fonderie/client");
|
|
495
716
|
var import_vue_courier_admin2 = require("@fonderie/vue-courier-admin");
|
|
496
|
-
var
|
|
497
|
-
var
|
|
717
|
+
var import_vue3 = require("vue");
|
|
718
|
+
var builtIn = {
|
|
719
|
+
fontSize: "11px",
|
|
720
|
+
fontWeight: 500,
|
|
721
|
+
padding: "1px 7px",
|
|
722
|
+
borderRadius: "999px",
|
|
723
|
+
border: "1px solid var(--fonderie-border,#e0e0e0)",
|
|
724
|
+
color: "var(--fonderie-text-muted,#5c5c5c)"
|
|
725
|
+
};
|
|
726
|
+
var newButton = {
|
|
727
|
+
display: "inline-flex",
|
|
728
|
+
alignItems: "center",
|
|
729
|
+
height: "32px",
|
|
730
|
+
boxSizing: "border-box",
|
|
731
|
+
borderRadius: "6px",
|
|
732
|
+
padding: "0 12px",
|
|
733
|
+
fontSize: "13px",
|
|
734
|
+
fontWeight: 500,
|
|
735
|
+
fontFamily: "inherit",
|
|
736
|
+
cursor: "pointer",
|
|
737
|
+
backgroundColor: "var(--fonderie-text,#171717)",
|
|
738
|
+
color: "var(--fonderie-surface,#fff)",
|
|
739
|
+
border: "1px solid var(--fonderie-text,#171717)"
|
|
740
|
+
};
|
|
741
|
+
var TemplateListScreen = (0, import_vue3.defineComponent)({
|
|
498
742
|
name: "FonderieTemplateListScreen",
|
|
499
743
|
props: {
|
|
500
|
-
client: { type: Object, required: true }
|
|
744
|
+
client: { type: Object, required: true },
|
|
745
|
+
/** Shows a "New template" button (emits create-template). */
|
|
746
|
+
allowCreate: { type: Boolean, default: false },
|
|
747
|
+
/** The console language; defaults to English. */
|
|
748
|
+
locale: { type: String, default: void 0 }
|
|
501
749
|
},
|
|
502
750
|
emits: {
|
|
503
|
-
|
|
751
|
+
/** The whole row — type AND locale, and whether it is built-in. */
|
|
752
|
+
"select-template": (_template) => true,
|
|
753
|
+
/** Receives the locales in use, to suggest. */
|
|
754
|
+
"create-template": (_context) => true
|
|
504
755
|
},
|
|
505
756
|
setup(props, { emit }) {
|
|
506
757
|
const { templates, isLoading, error } = (0, import_vue_courier_admin2.useTemplates)(props.client);
|
|
758
|
+
const t = (key, params) => (0, import_client3.createAdminT)(props.locale)(key, params);
|
|
507
759
|
function renderRow(template) {
|
|
508
|
-
return (0,
|
|
509
|
-
(0,
|
|
760
|
+
return (0, import_vue3.h)("li", { key: `${template.type}:${template.locale ?? "base"}`, style: styles.row }, [
|
|
761
|
+
(0, import_vue3.h)(
|
|
510
762
|
"button",
|
|
511
763
|
{
|
|
512
764
|
type: "button",
|
|
@@ -514,29 +766,59 @@ var TemplateListScreen = (0, import_vue2.defineComponent)({
|
|
|
514
766
|
onClick: () => emit("select-template", template)
|
|
515
767
|
},
|
|
516
768
|
[
|
|
517
|
-
(0,
|
|
518
|
-
(0,
|
|
519
|
-
(0,
|
|
520
|
-
|
|
521
|
-
|
|
769
|
+
(0, import_vue3.h)("span", { style: styles.type }, template.type),
|
|
770
|
+
(0, import_vue3.h)("span", { style: styles.locale }, template.locale ?? t("templates.defaultLocale")),
|
|
771
|
+
template.system ? (0, import_vue3.h)("span", { style: builtIn }, t("templates.builtInBadge")) : null,
|
|
772
|
+
(0, import_vue3.h)("span", { style: template.active ? styles.active : styles.inactive }, [
|
|
773
|
+
(0, import_vue3.h)("span", { style: styles.dot }),
|
|
774
|
+
template.active ? t("common.status.active") : t("common.status.inactive")
|
|
522
775
|
])
|
|
523
776
|
]
|
|
524
777
|
)
|
|
525
778
|
]);
|
|
526
779
|
}
|
|
527
|
-
return () => (0,
|
|
528
|
-
(0,
|
|
529
|
-
|
|
780
|
+
return () => (0, import_vue3.h)("div", { style: styles.listContainer }, [
|
|
781
|
+
(0, import_vue3.h)(
|
|
782
|
+
"div",
|
|
783
|
+
{
|
|
784
|
+
style: {
|
|
785
|
+
display: "flex",
|
|
786
|
+
alignItems: "center",
|
|
787
|
+
justifyContent: "space-between",
|
|
788
|
+
gap: "16px"
|
|
789
|
+
}
|
|
790
|
+
},
|
|
791
|
+
[
|
|
792
|
+
(0, import_vue3.h)("h1", { style: styles.listTitle }, t("templates.list.title")),
|
|
793
|
+
props.allowCreate ? (0, import_vue3.h)(
|
|
794
|
+
"button",
|
|
795
|
+
{
|
|
796
|
+
type: "button",
|
|
797
|
+
style: newButton,
|
|
798
|
+
onClick: () => emit("create-template", {
|
|
799
|
+
locales: [
|
|
800
|
+
...new Set(
|
|
801
|
+
templates.value.map((tpl) => tpl.locale).filter((l) => !!l)
|
|
802
|
+
)
|
|
803
|
+
]
|
|
804
|
+
})
|
|
805
|
+
},
|
|
806
|
+
t("templates.list.newTemplate")
|
|
807
|
+
) : null
|
|
808
|
+
]
|
|
809
|
+
),
|
|
810
|
+
(0, import_vue3.h)(
|
|
530
811
|
"p",
|
|
531
812
|
{ style: styles.hint },
|
|
532
|
-
"
|
|
813
|
+
t("templates.list.hint")
|
|
533
814
|
),
|
|
534
|
-
isLoading.value ? (0,
|
|
815
|
+
isLoading.value ? (0, import_vue3.h)("p", { style: styles.status }, t("templates.list.loading")) : error.value ? (0, import_vue3.h)("p", { style: styles.error, role: "alert" }, error.value.explanation) : (0, import_vue3.h)("ul", { style: styles.list }, templates.value.map(renderRow))
|
|
535
816
|
]);
|
|
536
817
|
}
|
|
537
818
|
});
|
|
538
819
|
// Annotate the CommonJS export names for ESM import in node:
|
|
539
820
|
0 && (module.exports = {
|
|
821
|
+
TemplateCreateScreen,
|
|
540
822
|
TemplateEditorScreen,
|
|
541
823
|
TemplateListScreen
|
|
542
824
|
});
|