@fonderie/vue-courier-admin-screens 0.6.0 → 0.7.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 +343 -71
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +112 -2
- package/dist/index.d.ts +112 -2
- package/dist/index.js +345 -74
- 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,18 +266,170 @@ 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
|
+
},
|
|
285
|
+
emits: {
|
|
286
|
+
/** What was created, so the caller can open it in the editor. */
|
|
287
|
+
created: (_created) => true
|
|
288
|
+
},
|
|
289
|
+
setup(props, { emit }) {
|
|
290
|
+
const addingLocale = props.type !== void 0;
|
|
291
|
+
const newType = (0, import_vue.ref)("");
|
|
292
|
+
const locale = (0, import_vue.ref)("");
|
|
293
|
+
const subject = (0, import_vue.ref)("");
|
|
294
|
+
const html = (0, import_vue.ref)("");
|
|
295
|
+
const text = (0, import_vue.ref)("");
|
|
296
|
+
const error = (0, import_vue.ref)(null);
|
|
297
|
+
const busy = (0, import_vue.ref)(false);
|
|
298
|
+
(0, import_vue.onMounted)(() => {
|
|
299
|
+
if (!props.type) return;
|
|
300
|
+
props.client.getTemplate(props.type, null).then(({ result }) => {
|
|
301
|
+
subject.value = result.subject ?? "";
|
|
302
|
+
html.value = result.html ?? "";
|
|
303
|
+
text.value = result.text;
|
|
304
|
+
}).catch(() => void 0);
|
|
305
|
+
});
|
|
306
|
+
async function submit(e) {
|
|
307
|
+
e.preventDefault();
|
|
308
|
+
error.value = null;
|
|
309
|
+
const t = addingLocale ? props.type : newType.value.trim();
|
|
310
|
+
const l = locale.value.trim() || null;
|
|
311
|
+
if (!TYPE_RE.test(t)) {
|
|
312
|
+
error.value = "Type: lowercase letters, digits, - and _ (e.g. weekly-digest).";
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
if (addingLocale && !l) {
|
|
316
|
+
error.value = "Choose the locale to add, e.g. fr or fr-CA.";
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
if (l && !LOCALE_RE.test(l)) {
|
|
320
|
+
error.value = "Locale: a language tag such as fr, es or fr-CA.";
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
if (!text.value.trim()) {
|
|
324
|
+
error.value = "The plain-text body is required \u2014 it is what every email client can show.";
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
busy.value = true;
|
|
328
|
+
try {
|
|
329
|
+
const exists = await props.client.getTemplate(t, l).then(
|
|
330
|
+
() => true,
|
|
331
|
+
(err) => !(err instanceof import_client.FonderieApiError && err.status === 404)
|
|
332
|
+
);
|
|
333
|
+
if (exists) {
|
|
334
|
+
error.value = `"${t}"${l ? ` (${l})` : ""} already exists \u2014 open it from the list to edit.`;
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
const input = { text: text.value, active: true };
|
|
338
|
+
if (subject.value) input.subject = subject.value;
|
|
339
|
+
if (html.value) input.html = html.value;
|
|
340
|
+
await props.client.setTemplate(t, input, l);
|
|
341
|
+
emit("created", { type: t, locale: l });
|
|
342
|
+
} catch (err) {
|
|
343
|
+
error.value = err instanceof import_client.FonderieApiError ? err.explanation : String(err);
|
|
344
|
+
} finally {
|
|
345
|
+
busy.value = false;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
const field = (id, label, model, opts = {}) => [
|
|
349
|
+
(0, import_vue.h)("label", { style: styles.label, for: id }, label),
|
|
350
|
+
(0, import_vue.h)(opts.textarea ? "textarea" : "input", {
|
|
351
|
+
id,
|
|
352
|
+
style: opts.textarea ? { ...styles.textarea, minHeight: opts.minHeight } : styles.input,
|
|
353
|
+
value: model.value,
|
|
354
|
+
autocomplete: "off",
|
|
355
|
+
spellcheck: false,
|
|
356
|
+
...opts.placeholder ? { placeholder: opts.placeholder } : {},
|
|
357
|
+
...opts.list ? { list: opts.list } : {},
|
|
358
|
+
onInput: (ev) => {
|
|
359
|
+
model.value = ev.target.value;
|
|
360
|
+
}
|
|
361
|
+
})
|
|
362
|
+
];
|
|
363
|
+
return () => (0, import_vue.h)("div", { style: { ...styles.container, maxWidth: "760px" } }, [
|
|
364
|
+
(0, import_vue.h)(
|
|
365
|
+
"h1",
|
|
366
|
+
{ style: styles.title },
|
|
367
|
+
addingLocale ? `Add a locale to ${props.type}` : "New template"
|
|
368
|
+
),
|
|
369
|
+
(0, import_vue.h)(
|
|
370
|
+
"p",
|
|
371
|
+
{ style: { ...styles.meta, lineHeight: 1.5 } },
|
|
372
|
+
addingLocale ? "Starts from the default copy. People whose locale matches receive this version; everyone else keeps the default." : "For an email your app sends that has no stored copy yet. The type must match what the app sends."
|
|
373
|
+
),
|
|
374
|
+
(0, import_vue.h)("form", { style: styles.form, onSubmit: (ev) => void submit(ev) }, [
|
|
375
|
+
...addingLocale ? [] : field("new-template-type", "Type", newType, { placeholder: "weekly-digest" }),
|
|
376
|
+
...field(
|
|
377
|
+
"new-template-locale",
|
|
378
|
+
addingLocale ? "Locale" : "Locale (optional \u2014 empty is the default)",
|
|
379
|
+
locale,
|
|
380
|
+
{ placeholder: "fr", list: "new-template-locales" }
|
|
381
|
+
),
|
|
382
|
+
(0, import_vue.h)(
|
|
383
|
+
"datalist",
|
|
384
|
+
{ id: "new-template-locales" },
|
|
385
|
+
[...new Set(props.locales)].map((l) => (0, import_vue.h)("option", { key: l, value: l }))
|
|
386
|
+
),
|
|
387
|
+
...field("new-template-subject", "Subject", subject),
|
|
388
|
+
...field("new-template-html", "HTML body", html, { textarea: true, minHeight: "160px" }),
|
|
389
|
+
...field("new-template-text", "Plain-text body", text, {
|
|
390
|
+
textarea: true,
|
|
391
|
+
minHeight: "100px"
|
|
392
|
+
}),
|
|
393
|
+
error.value ? (0, import_vue.h)("p", { style: styles.error, role: "alert" }, error.value) : null,
|
|
394
|
+
(0, import_vue.h)(
|
|
395
|
+
"button",
|
|
396
|
+
{
|
|
397
|
+
type: "submit",
|
|
398
|
+
disabled: busy.value,
|
|
399
|
+
style: { ...styles.button, marginTop: "16px" }
|
|
400
|
+
},
|
|
401
|
+
busy.value ? "Creating\u2026" : addingLocale ? "Add locale" : "Create template"
|
|
402
|
+
)
|
|
403
|
+
])
|
|
404
|
+
]);
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
|
|
268
408
|
// src/screens/TemplateEditorScreen.ts
|
|
409
|
+
var import_vue_courier_admin = require("@fonderie/vue-courier-admin");
|
|
410
|
+
var import_vue2 = require("vue");
|
|
269
411
|
var PREVIEW_DEBOUNCE_MS = 500;
|
|
270
412
|
var IMPLICIT = /* @__PURE__ */ new Set(["subject", "preheader", "brandName"]);
|
|
271
|
-
var TemplateEditorScreen = (0,
|
|
413
|
+
var TemplateEditorScreen = (0, import_vue2.defineComponent)({
|
|
272
414
|
name: "FonderieTemplateEditorScreen",
|
|
273
415
|
props: {
|
|
274
416
|
client: { type: Object, required: true },
|
|
275
417
|
type: { type: String, required: true },
|
|
276
|
-
locale: { type: String, default: null }
|
|
418
|
+
locale: { type: String, default: null },
|
|
419
|
+
/**
|
|
420
|
+
* A built-in email's default-locale row (the list's `system` flag): it can
|
|
421
|
+
* be edited and rolled back, never deleted — Delete is not offered.
|
|
422
|
+
*/
|
|
423
|
+
system: { type: Boolean, default: false },
|
|
424
|
+
/** Shows "+ Add locale" on the default locale (emits add-locale). */
|
|
425
|
+
allowAddLocale: { type: Boolean, default: false }
|
|
277
426
|
},
|
|
278
427
|
emits: {
|
|
279
|
-
saved: () => true
|
|
428
|
+
saved: () => true,
|
|
429
|
+
/** The template was deleted and no longer exists. */
|
|
430
|
+
deleted: () => true,
|
|
431
|
+
/** Create a translation of this template. */
|
|
432
|
+
"add-locale": (_type) => true
|
|
280
433
|
},
|
|
281
434
|
setup(props, { emit }) {
|
|
282
435
|
const { template, isLoading, error, refresh } = (0, import_vue_courier_admin.useTemplate)(
|
|
@@ -284,15 +437,16 @@ var TemplateEditorScreen = (0, import_vue.defineComponent)({
|
|
|
284
437
|
props.type,
|
|
285
438
|
props.locale
|
|
286
439
|
);
|
|
287
|
-
const { saveTemplate } = (0, import_vue_courier_admin.useTemplates)(props.client);
|
|
440
|
+
const { saveTemplate, removeTemplate } = (0, import_vue_courier_admin.useTemplates)(props.client);
|
|
441
|
+
const isDeleting = (0, import_vue2.ref)(false);
|
|
288
442
|
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,
|
|
443
|
+
const isSaving = (0, import_vue2.ref)(false);
|
|
444
|
+
const saveError = (0, import_vue2.ref)(null);
|
|
445
|
+
const subject = (0, import_vue2.ref)("");
|
|
446
|
+
const html = (0, import_vue2.ref)("");
|
|
447
|
+
const text = (0, import_vue2.ref)("");
|
|
448
|
+
const active = (0, import_vue2.ref)(true);
|
|
449
|
+
(0, import_vue2.watch)(template, (tpl) => {
|
|
296
450
|
if (!tpl) return;
|
|
297
451
|
subject.value = tpl.subject ?? "";
|
|
298
452
|
html.value = tpl.html ?? "";
|
|
@@ -305,8 +459,8 @@ var TemplateEditorScreen = (0, import_vue.defineComponent)({
|
|
|
305
459
|
error: previewError,
|
|
306
460
|
renderPreview
|
|
307
461
|
} = (0, import_vue_courier_admin.useTemplatePreview)(props.client);
|
|
308
|
-
const sampleJson = (0,
|
|
309
|
-
const sampleError = (0,
|
|
462
|
+
const sampleJson = (0, import_vue2.ref)("{}");
|
|
463
|
+
const sampleError = (0, import_vue2.ref)(null);
|
|
310
464
|
async function run() {
|
|
311
465
|
let data = {};
|
|
312
466
|
try {
|
|
@@ -341,12 +495,12 @@ var TemplateEditorScreen = (0, import_vue.defineComponent)({
|
|
|
341
495
|
}
|
|
342
496
|
}
|
|
343
497
|
let previewTimer;
|
|
344
|
-
(0,
|
|
498
|
+
(0, import_vue2.watch)([template, subject, html, text, sampleJson], () => {
|
|
345
499
|
if (!template.value) return;
|
|
346
500
|
clearTimeout(previewTimer);
|
|
347
501
|
previewTimer = setTimeout(() => void run(), PREVIEW_DEBOUNCE_MS);
|
|
348
502
|
});
|
|
349
|
-
const dirty = (0,
|
|
503
|
+
const dirty = (0, import_vue2.computed)(() => {
|
|
350
504
|
const tpl = template.value;
|
|
351
505
|
return !!tpl && (subject.value !== (tpl.subject ?? "") || html.value !== (tpl.html ?? "") || text.value !== tpl.text || active.value !== tpl.active);
|
|
352
506
|
});
|
|
@@ -368,13 +522,28 @@ var TemplateEditorScreen = (0, import_vue.defineComponent)({
|
|
|
368
522
|
isSaving.value = false;
|
|
369
523
|
}
|
|
370
524
|
}
|
|
525
|
+
async function handleDelete() {
|
|
526
|
+
const which = props.locale ? `the ${props.locale} version of "${props.type}"` : `"${props.type}"`;
|
|
527
|
+
const fallback = props.locale ? " People in that locale will receive the default version." : "";
|
|
528
|
+
if (!window.confirm(`Delete ${which}?${fallback}`)) return;
|
|
529
|
+
isDeleting.value = true;
|
|
530
|
+
saveError.value = null;
|
|
531
|
+
try {
|
|
532
|
+
await removeTemplate(props.type, props.locale);
|
|
533
|
+
emit("deleted");
|
|
534
|
+
} catch (err) {
|
|
535
|
+
saveError.value = err;
|
|
536
|
+
} finally {
|
|
537
|
+
isDeleting.value = false;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
371
540
|
function renderRevision(rev) {
|
|
372
|
-
return (0,
|
|
373
|
-
(0,
|
|
541
|
+
return (0, import_vue2.h)("li", { key: rev.version, style: styles.revisionRow }, [
|
|
542
|
+
(0, import_vue2.h)(
|
|
374
543
|
"span",
|
|
375
544
|
`v${rev.version} \u2014 ${rev.actor ?? "unknown"} \u2014 ${new Date(rev.createdAt).toLocaleString()}`
|
|
376
545
|
),
|
|
377
|
-
(0,
|
|
546
|
+
(0, import_vue2.h)(
|
|
378
547
|
"button",
|
|
379
548
|
{ type: "button", style: styles.rollbackButton, onClick: () => rollback(rev.version) },
|
|
380
549
|
"Roll back"
|
|
@@ -382,21 +551,36 @@ var TemplateEditorScreen = (0, import_vue.defineComponent)({
|
|
|
382
551
|
]);
|
|
383
552
|
}
|
|
384
553
|
return () => {
|
|
385
|
-
if (isLoading.value) return (0,
|
|
554
|
+
if (isLoading.value) return (0, import_vue2.h)("p", { style: styles.status }, "Loading template\u2026");
|
|
386
555
|
if (error.value)
|
|
387
|
-
return (0,
|
|
388
|
-
return (0,
|
|
389
|
-
(0,
|
|
390
|
-
|
|
391
|
-
"
|
|
392
|
-
|
|
393
|
-
|
|
556
|
+
return (0, import_vue2.h)("p", { style: styles.error, role: "alert" }, error.value.explanation);
|
|
557
|
+
return (0, import_vue2.h)("div", { style: styles.container }, [
|
|
558
|
+
(0, import_vue2.h)(
|
|
559
|
+
"div",
|
|
560
|
+
{ style: { display: "flex", alignItems: "center", gap: "12px", flexWrap: "wrap" } },
|
|
561
|
+
[
|
|
562
|
+
(0, import_vue2.h)("h1", { style: styles.title }, props.type),
|
|
563
|
+
props.allowAddLocale && !props.locale ? (0, import_vue2.h)(
|
|
564
|
+
"button",
|
|
565
|
+
{
|
|
566
|
+
type: "button",
|
|
567
|
+
style: { ...styles.rollbackButton, marginLeft: "auto" },
|
|
568
|
+
onClick: () => emit("add-locale", props.type)
|
|
569
|
+
},
|
|
570
|
+
"+ Add locale"
|
|
571
|
+
) : null
|
|
572
|
+
]
|
|
394
573
|
),
|
|
395
|
-
(0,
|
|
396
|
-
(0,
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
574
|
+
(0, import_vue2.h)("p", { style: styles.meta }, [
|
|
575
|
+
(0, import_vue2.h)("strong", props.locale ?? "default locale"),
|
|
576
|
+
` \xB7 v${template.value?.version ?? 1}`,
|
|
577
|
+
props.system ? " \xB7 built-in email: edit or roll back, it cannot be deleted" : ""
|
|
578
|
+
]),
|
|
579
|
+
(0, import_vue2.h)("div", { style: styles.split }, [
|
|
580
|
+
(0, import_vue2.h)("div", { style: styles.column }, [
|
|
581
|
+
(0, import_vue2.h)("form", { style: styles.form, onSubmit: handleSubmit }, [
|
|
582
|
+
(0, import_vue2.h)("label", { style: styles.label, for: "template-subject" }, "Subject"),
|
|
583
|
+
(0, import_vue2.h)("input", {
|
|
400
584
|
id: "template-subject",
|
|
401
585
|
style: styles.input,
|
|
402
586
|
value: subject.value,
|
|
@@ -404,8 +588,8 @@ var TemplateEditorScreen = (0, import_vue.defineComponent)({
|
|
|
404
588
|
subject.value = e.target.value;
|
|
405
589
|
}
|
|
406
590
|
}),
|
|
407
|
-
(0,
|
|
408
|
-
(0,
|
|
591
|
+
(0, import_vue2.h)("label", { style: styles.label, for: "template-html" }, "HTML body"),
|
|
592
|
+
(0, import_vue2.h)("textarea", {
|
|
409
593
|
id: "template-html",
|
|
410
594
|
style: styles.textarea,
|
|
411
595
|
rows: 8,
|
|
@@ -414,8 +598,8 @@ var TemplateEditorScreen = (0, import_vue.defineComponent)({
|
|
|
414
598
|
html.value = e.target.value;
|
|
415
599
|
}
|
|
416
600
|
}),
|
|
417
|
-
(0,
|
|
418
|
-
(0,
|
|
601
|
+
(0, import_vue2.h)("label", { style: styles.label, for: "template-text" }, "Plain-text body"),
|
|
602
|
+
(0, import_vue2.h)("textarea", {
|
|
419
603
|
id: "template-text",
|
|
420
604
|
style: styles.textarea,
|
|
421
605
|
rows: 4,
|
|
@@ -425,8 +609,8 @@ var TemplateEditorScreen = (0, import_vue.defineComponent)({
|
|
|
425
609
|
text.value = e.target.value;
|
|
426
610
|
}
|
|
427
611
|
}),
|
|
428
|
-
(0,
|
|
429
|
-
(0,
|
|
612
|
+
(0, import_vue2.h)("label", { style: styles.checkboxLabel }, [
|
|
613
|
+
(0, import_vue2.h)("input", {
|
|
430
614
|
type: "checkbox",
|
|
431
615
|
checked: active.value,
|
|
432
616
|
onChange: (e) => {
|
|
@@ -435,9 +619,23 @@ var TemplateEditorScreen = (0, import_vue.defineComponent)({
|
|
|
435
619
|
}),
|
|
436
620
|
"Active"
|
|
437
621
|
]),
|
|
438
|
-
saveError.value ? (0,
|
|
439
|
-
|
|
440
|
-
(0,
|
|
622
|
+
saveError.value?.reason === "VERSION_CONFLICT" ? (0, import_vue2.h)("p", { style: styles.error, role: "alert" }, [
|
|
623
|
+
"Someone changed this template since you opened it. Reload to see their change, then edit again. ",
|
|
624
|
+
(0, import_vue2.h)(
|
|
625
|
+
"button",
|
|
626
|
+
{
|
|
627
|
+
type: "button",
|
|
628
|
+
style: styles.rollbackButton,
|
|
629
|
+
onClick: () => {
|
|
630
|
+
saveError.value = null;
|
|
631
|
+
void refresh();
|
|
632
|
+
}
|
|
633
|
+
},
|
|
634
|
+
"Reload"
|
|
635
|
+
)
|
|
636
|
+
]) : saveError.value ? (0, import_vue2.h)("p", { style: styles.error, role: "alert" }, saveError.value.explanation) : null,
|
|
637
|
+
(0, import_vue2.h)("div", { style: styles.saveRow }, [
|
|
638
|
+
(0, import_vue2.h)(
|
|
441
639
|
"button",
|
|
442
640
|
{
|
|
443
641
|
type: "submit",
|
|
@@ -446,11 +644,27 @@ var TemplateEditorScreen = (0, import_vue.defineComponent)({
|
|
|
446
644
|
},
|
|
447
645
|
isSaving.value ? "Saving\u2026" : "Save"
|
|
448
646
|
),
|
|
449
|
-
!dirty.value && !isSaving.value ? (0,
|
|
647
|
+
!dirty.value && !isSaving.value ? (0, import_vue2.h)("span", { style: styles.meta }, "No changes to save") : null,
|
|
648
|
+
!props.system ? (0, import_vue2.h)(
|
|
649
|
+
"button",
|
|
650
|
+
{
|
|
651
|
+
type: "button",
|
|
652
|
+
disabled: isDeleting.value,
|
|
653
|
+
onClick: () => void handleDelete(),
|
|
654
|
+
style: {
|
|
655
|
+
...styles.rollbackButton,
|
|
656
|
+
height: "36px",
|
|
657
|
+
marginLeft: "auto",
|
|
658
|
+
color: "var(--fonderie-danger,#e00)",
|
|
659
|
+
borderColor: "color-mix(in srgb, var(--fonderie-danger,#e00) 40%, transparent)"
|
|
660
|
+
}
|
|
661
|
+
},
|
|
662
|
+
isDeleting.value ? "Deleting\u2026" : "Delete"
|
|
663
|
+
) : null
|
|
450
664
|
])
|
|
451
665
|
]),
|
|
452
|
-
(0,
|
|
453
|
-
(0,
|
|
666
|
+
(0, import_vue2.h)("label", { style: styles.label, for: "template-sample" }, "Sample data"),
|
|
667
|
+
(0, import_vue2.h)("textarea", {
|
|
454
668
|
id: "template-sample",
|
|
455
669
|
style: styles.textarea,
|
|
456
670
|
rows: 6,
|
|
@@ -460,31 +674,31 @@ var TemplateEditorScreen = (0, import_vue.defineComponent)({
|
|
|
460
674
|
sampleJson.value = e.target.value;
|
|
461
675
|
}
|
|
462
676
|
}),
|
|
463
|
-
sampleError.value ? (0,
|
|
677
|
+
sampleError.value ? (0, import_vue2.h)("p", { style: styles.error, role: "alert" }, sampleError.value) : null
|
|
464
678
|
]),
|
|
465
|
-
(0,
|
|
466
|
-
(0,
|
|
467
|
-
(0,
|
|
468
|
-
(0,
|
|
679
|
+
(0, import_vue2.h)("div", { style: styles.previewColumn }, [
|
|
680
|
+
(0, import_vue2.h)("div", { style: styles.previewHeader }, [
|
|
681
|
+
(0, import_vue2.h)("span", { style: styles.label }, "Preview"),
|
|
682
|
+
(0, import_vue2.h)("span", { style: styles.meta }, isPreviewing.value ? "Rendering\u2026" : "Live")
|
|
469
683
|
]),
|
|
470
|
-
previewError.value ? (0,
|
|
471
|
-
preview.value?.subject ? (0,
|
|
684
|
+
previewError.value ? (0, import_vue2.h)("p", { style: styles.error, role: "alert" }, previewError.value.explanation) : null,
|
|
685
|
+
preview.value?.subject ? (0, import_vue2.h)("p", { style: styles.previewSubject }, preview.value.subject) : null,
|
|
472
686
|
preview.value?.html ? (
|
|
473
687
|
// sandbox= — no scripts, no same-origin. Operator-authored HTML
|
|
474
688
|
// must never execute in the dashboard's origin, where the admin
|
|
475
689
|
// token lives.
|
|
476
|
-
(0,
|
|
690
|
+
(0, import_vue2.h)("iframe", {
|
|
477
691
|
title: "Template preview",
|
|
478
692
|
style: styles.previewFrame,
|
|
479
693
|
sandbox: "",
|
|
480
694
|
srcdoc: preview.value.html
|
|
481
695
|
})
|
|
482
|
-
) : preview.value ? (0,
|
|
696
|
+
) : preview.value ? (0, import_vue2.h)("pre", { style: styles.previewText }, preview.value.text) : (0, import_vue2.h)("p", { style: styles.meta }, "Nothing rendered yet.")
|
|
483
697
|
])
|
|
484
698
|
]),
|
|
485
|
-
revisions.value.length > 0 ? (0,
|
|
486
|
-
(0,
|
|
487
|
-
(0,
|
|
699
|
+
revisions.value.length > 0 ? (0, import_vue2.h)("div", { style: styles.revisions }, [
|
|
700
|
+
(0, import_vue2.h)("h2", { style: styles.subtitle }, "History"),
|
|
701
|
+
(0, import_vue2.h)("ul", { style: styles.list }, revisions.value.map(renderRevision))
|
|
488
702
|
]) : null
|
|
489
703
|
]);
|
|
490
704
|
};
|
|
@@ -493,20 +707,48 @@ var TemplateEditorScreen = (0, import_vue.defineComponent)({
|
|
|
493
707
|
|
|
494
708
|
// src/screens/TemplateListScreen.ts
|
|
495
709
|
var import_vue_courier_admin2 = require("@fonderie/vue-courier-admin");
|
|
496
|
-
var
|
|
497
|
-
var
|
|
710
|
+
var import_vue3 = require("vue");
|
|
711
|
+
var builtIn = {
|
|
712
|
+
fontSize: "11px",
|
|
713
|
+
fontWeight: 500,
|
|
714
|
+
padding: "1px 7px",
|
|
715
|
+
borderRadius: "999px",
|
|
716
|
+
border: "1px solid var(--fonderie-border,#e0e0e0)",
|
|
717
|
+
color: "var(--fonderie-text-muted,#5c5c5c)"
|
|
718
|
+
};
|
|
719
|
+
var newButton = {
|
|
720
|
+
display: "inline-flex",
|
|
721
|
+
alignItems: "center",
|
|
722
|
+
height: "32px",
|
|
723
|
+
boxSizing: "border-box",
|
|
724
|
+
borderRadius: "6px",
|
|
725
|
+
padding: "0 12px",
|
|
726
|
+
fontSize: "13px",
|
|
727
|
+
fontWeight: 500,
|
|
728
|
+
fontFamily: "inherit",
|
|
729
|
+
cursor: "pointer",
|
|
730
|
+
backgroundColor: "var(--fonderie-text,#171717)",
|
|
731
|
+
color: "var(--fonderie-surface,#fff)",
|
|
732
|
+
border: "1px solid var(--fonderie-text,#171717)"
|
|
733
|
+
};
|
|
734
|
+
var TemplateListScreen = (0, import_vue3.defineComponent)({
|
|
498
735
|
name: "FonderieTemplateListScreen",
|
|
499
736
|
props: {
|
|
500
|
-
client: { type: Object, required: true }
|
|
737
|
+
client: { type: Object, required: true },
|
|
738
|
+
/** Shows a "New template" button (emits create-template). */
|
|
739
|
+
allowCreate: { type: Boolean, default: false }
|
|
501
740
|
},
|
|
502
741
|
emits: {
|
|
503
|
-
|
|
742
|
+
/** The whole row — type AND locale, and whether it is built-in. */
|
|
743
|
+
"select-template": (_template) => true,
|
|
744
|
+
/** Receives the locales in use, to suggest. */
|
|
745
|
+
"create-template": (_context) => true
|
|
504
746
|
},
|
|
505
747
|
setup(props, { emit }) {
|
|
506
748
|
const { templates, isLoading, error } = (0, import_vue_courier_admin2.useTemplates)(props.client);
|
|
507
749
|
function renderRow(template) {
|
|
508
|
-
return (0,
|
|
509
|
-
(0,
|
|
750
|
+
return (0, import_vue3.h)("li", { key: `${template.type}:${template.locale ?? "base"}`, style: styles.row }, [
|
|
751
|
+
(0, import_vue3.h)(
|
|
510
752
|
"button",
|
|
511
753
|
{
|
|
512
754
|
type: "button",
|
|
@@ -514,29 +756,59 @@ var TemplateListScreen = (0, import_vue2.defineComponent)({
|
|
|
514
756
|
onClick: () => emit("select-template", template)
|
|
515
757
|
},
|
|
516
758
|
[
|
|
517
|
-
(0,
|
|
518
|
-
(0,
|
|
519
|
-
(0,
|
|
520
|
-
|
|
759
|
+
(0, import_vue3.h)("span", { style: styles.type }, template.type),
|
|
760
|
+
(0, import_vue3.h)("span", { style: styles.locale }, template.locale ?? "default locale"),
|
|
761
|
+
template.system ? (0, import_vue3.h)("span", { style: builtIn }, "built-in") : null,
|
|
762
|
+
(0, import_vue3.h)("span", { style: template.active ? styles.active : styles.inactive }, [
|
|
763
|
+
(0, import_vue3.h)("span", { style: styles.dot }),
|
|
521
764
|
template.active ? "active" : "inactive"
|
|
522
765
|
])
|
|
523
766
|
]
|
|
524
767
|
)
|
|
525
768
|
]);
|
|
526
769
|
}
|
|
527
|
-
return () => (0,
|
|
528
|
-
(0,
|
|
529
|
-
|
|
770
|
+
return () => (0, import_vue3.h)("div", { style: styles.listContainer }, [
|
|
771
|
+
(0, import_vue3.h)(
|
|
772
|
+
"div",
|
|
773
|
+
{
|
|
774
|
+
style: {
|
|
775
|
+
display: "flex",
|
|
776
|
+
alignItems: "center",
|
|
777
|
+
justifyContent: "space-between",
|
|
778
|
+
gap: "16px"
|
|
779
|
+
}
|
|
780
|
+
},
|
|
781
|
+
[
|
|
782
|
+
(0, import_vue3.h)("h1", { style: styles.listTitle }, "Templates"),
|
|
783
|
+
props.allowCreate ? (0, import_vue3.h)(
|
|
784
|
+
"button",
|
|
785
|
+
{
|
|
786
|
+
type: "button",
|
|
787
|
+
style: newButton,
|
|
788
|
+
onClick: () => emit("create-template", {
|
|
789
|
+
locales: [
|
|
790
|
+
...new Set(
|
|
791
|
+
templates.value.map((t) => t.locale).filter((l) => !!l)
|
|
792
|
+
)
|
|
793
|
+
]
|
|
794
|
+
})
|
|
795
|
+
},
|
|
796
|
+
"New template"
|
|
797
|
+
) : null
|
|
798
|
+
]
|
|
799
|
+
),
|
|
800
|
+
(0, import_vue3.h)(
|
|
530
801
|
"p",
|
|
531
802
|
{ style: styles.hint },
|
|
532
803
|
"Every email the app sends. Open one to edit its copy and preview it live."
|
|
533
804
|
),
|
|
534
|
-
isLoading.value ? (0,
|
|
805
|
+
isLoading.value ? (0, import_vue3.h)("p", { style: styles.status }, "Loading templates\u2026") : 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
806
|
]);
|
|
536
807
|
}
|
|
537
808
|
});
|
|
538
809
|
// Annotate the CommonJS export names for ESM import in node:
|
|
539
810
|
0 && (module.exports = {
|
|
811
|
+
TemplateCreateScreen,
|
|
540
812
|
TemplateEditorScreen,
|
|
541
813
|
TemplateListScreen
|
|
542
814
|
});
|