@fonderie/react-courier-admin-screens 0.8.0 → 0.10.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 +281 -83
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -5
- package/dist/index.d.ts +19 -5
- package/dist/index.js +287 -86
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
// src/screens/TemplateEditorScreen.tsx
|
|
2
2
|
import {
|
|
3
3
|
createAdminT,
|
|
4
|
-
formatAdminDate
|
|
4
|
+
formatAdminDate,
|
|
5
|
+
groupTemplatesByType,
|
|
6
|
+
missingTemplateLocales
|
|
5
7
|
} from "@fonderie/client";
|
|
6
8
|
import {
|
|
7
9
|
useTemplate,
|
|
@@ -21,11 +23,13 @@ function TemplateEditorScreen({
|
|
|
21
23
|
system = false,
|
|
22
24
|
onDeleted,
|
|
23
25
|
onAddLocale,
|
|
26
|
+
onSelectLocale,
|
|
24
27
|
uiLocale
|
|
25
28
|
}) {
|
|
26
29
|
const t = useMemo(() => createAdminT(uiLocale), [uiLocale]);
|
|
27
30
|
const { template, isLoading, error, refresh } = useTemplate(client, type, locale);
|
|
28
|
-
const { saveTemplate, removeTemplate } = useTemplates(client);
|
|
31
|
+
const { templates, saveTemplate, removeTemplate } = useTemplates(client);
|
|
32
|
+
const siblings = groupTemplatesByType(templates).find((g) => g.type === type)?.entries ?? [];
|
|
29
33
|
const [isDeleting, setIsDeleting] = useState(false);
|
|
30
34
|
const { revisions, rollback } = useTemplateRevisions(client, type, locale);
|
|
31
35
|
const [isSaving, setIsSaving] = useState(false);
|
|
@@ -96,6 +100,10 @@ function TemplateEditorScreen({
|
|
|
96
100
|
setIsSaving(false);
|
|
97
101
|
}
|
|
98
102
|
};
|
|
103
|
+
const switchTo = (entry) => {
|
|
104
|
+
if (dirty && !window.confirm(t("templates.editor.discardChanges"))) return;
|
|
105
|
+
onSelectLocale?.(entry);
|
|
106
|
+
};
|
|
99
107
|
const handleDelete = async () => {
|
|
100
108
|
const message = locale ? t("templates.editor.confirmDeleteLocale", { type, locale }) : t("templates.editor.confirmDelete", { type });
|
|
101
109
|
if (!window.confirm(message)) return;
|
|
@@ -114,18 +122,40 @@ function TemplateEditorScreen({
|
|
|
114
122
|
if (error)
|
|
115
123
|
return /* @__PURE__ */ jsx("p", { style: styles.error, role: "alert", children: error.explanation });
|
|
116
124
|
return /* @__PURE__ */ jsxs("div", { style: styles.container, children: [
|
|
117
|
-
/* @__PURE__ */
|
|
118
|
-
|
|
119
|
-
|
|
125
|
+
/* @__PURE__ */ jsx("h1", { style: styles.title, children: type }),
|
|
126
|
+
onSelectLocale || onAddLocale ? /* @__PURE__ */ jsxs("div", { style: styles.tabBar, children: [
|
|
127
|
+
onSelectLocale ? (
|
|
128
|
+
// Each tab opens another stored row, so this is navigation, not an
|
|
129
|
+
// ARIA tablist: plain buttons, the open one marked aria-current.
|
|
130
|
+
/* @__PURE__ */ jsx("nav", { "aria-label": t("templates.editor.locales"), style: styles.tabs, children: siblings.map((entry) => {
|
|
131
|
+
const selected = entry.locale === (locale ?? null);
|
|
132
|
+
return /* @__PURE__ */ jsx(
|
|
133
|
+
"button",
|
|
134
|
+
{
|
|
135
|
+
type: "button",
|
|
136
|
+
"aria-current": selected ? "true" : void 0,
|
|
137
|
+
onClick: () => selected ? void 0 : switchTo(entry),
|
|
138
|
+
style: selected ? styles.tabSelected : entry.active ? styles.tab : styles.tabInactive,
|
|
139
|
+
title: entry.active ? void 0 : t("common.status.inactive"),
|
|
140
|
+
children: entry.locale ?? t("templates.defaultChip")
|
|
141
|
+
},
|
|
142
|
+
entry.locale ?? ""
|
|
143
|
+
);
|
|
144
|
+
}) })
|
|
145
|
+
) : null,
|
|
146
|
+
onAddLocale ? /* @__PURE__ */ jsx(
|
|
120
147
|
"button",
|
|
121
148
|
{
|
|
122
149
|
type: "button",
|
|
123
150
|
style: { ...styles.rollbackButton, marginLeft: "auto" },
|
|
124
|
-
onClick: () =>
|
|
151
|
+
onClick: () => {
|
|
152
|
+
if (dirty && !window.confirm(t("templates.editor.discardChanges"))) return;
|
|
153
|
+
onAddLocale(type, { locales: missingTemplateLocales(templates, type) });
|
|
154
|
+
},
|
|
125
155
|
children: t("templates.editor.addLocale")
|
|
126
156
|
}
|
|
127
157
|
) : null
|
|
128
|
-
] }),
|
|
158
|
+
] }) : null,
|
|
129
159
|
/* @__PURE__ */ jsxs("p", { style: styles.meta, children: [
|
|
130
160
|
/* @__PURE__ */ jsx("strong", { children: locale ?? t("templates.defaultLocale") }),
|
|
131
161
|
" \xB7 v",
|
|
@@ -286,8 +316,38 @@ function TemplateEditorScreen({
|
|
|
286
316
|
] })
|
|
287
317
|
] });
|
|
288
318
|
}
|
|
319
|
+
var tab = {
|
|
320
|
+
height: 32,
|
|
321
|
+
padding: "0 10px",
|
|
322
|
+
background: "none",
|
|
323
|
+
border: "none",
|
|
324
|
+
borderBottom: "2px solid transparent",
|
|
325
|
+
color: "var(--fonderie-text-muted,#5c5c5c)",
|
|
326
|
+
fontSize: 13,
|
|
327
|
+
fontFamily: 'var(--fonderie-mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace)',
|
|
328
|
+
cursor: "pointer"
|
|
329
|
+
};
|
|
289
330
|
var styles = {
|
|
290
331
|
container: { padding: "8px 40px 64px", maxWidth: 1400, boxSizing: "border-box" },
|
|
332
|
+
// One tab per locale of this email, underlined like a document's tabs.
|
|
333
|
+
tabBar: {
|
|
334
|
+
display: "flex",
|
|
335
|
+
alignItems: "flex-end",
|
|
336
|
+
gap: 12,
|
|
337
|
+
borderBottom: "1px solid var(--fonderie-border,#e0e0e0)",
|
|
338
|
+
margin: "4px 0 12px",
|
|
339
|
+
paddingBottom: 6
|
|
340
|
+
},
|
|
341
|
+
tabs: { display: "flex", flexWrap: "wrap", gap: 4, marginBottom: -7 },
|
|
342
|
+
tab,
|
|
343
|
+
tabSelected: {
|
|
344
|
+
...tab,
|
|
345
|
+
color: "var(--fonderie-text,#171717)",
|
|
346
|
+
fontWeight: 600,
|
|
347
|
+
borderBottomColor: "var(--fonderie-text,#171717)",
|
|
348
|
+
cursor: "default"
|
|
349
|
+
},
|
|
350
|
+
tabInactive: { ...tab, opacity: 0.55, textDecoration: "line-through" },
|
|
291
351
|
// Two columns, always: the editor on the left, the rendered result on the
|
|
292
352
|
// right. Stacking the preview under a long form hid it below the fold.
|
|
293
353
|
split: {
|
|
@@ -456,7 +516,8 @@ var styles = {
|
|
|
456
516
|
|
|
457
517
|
// src/screens/TemplateListScreen.tsx
|
|
458
518
|
import {
|
|
459
|
-
createAdminT as createAdminT2
|
|
519
|
+
createAdminT as createAdminT2,
|
|
520
|
+
groupTemplatesByType as groupTemplatesByType2
|
|
460
521
|
} from "@fonderie/client";
|
|
461
522
|
import { useTemplates as useTemplates2 } from "@fonderie/react-courier-admin";
|
|
462
523
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
@@ -468,6 +529,7 @@ function TemplateListScreen({
|
|
|
468
529
|
}) {
|
|
469
530
|
const t = createAdminT2(locale);
|
|
470
531
|
const { templates, isLoading, error } = useTemplates2(client);
|
|
532
|
+
const groups = groupTemplatesByType2(templates);
|
|
471
533
|
return /* @__PURE__ */ jsxs2("div", { style: styles2.container, children: [
|
|
472
534
|
/* @__PURE__ */ jsxs2(
|
|
473
535
|
"div",
|
|
@@ -492,23 +554,39 @@ function TemplateListScreen({
|
|
|
492
554
|
}
|
|
493
555
|
),
|
|
494
556
|
/* @__PURE__ */ jsx2("p", { style: styles2.hint, children: t("templates.list.hint") }),
|
|
495
|
-
isLoading ? /* @__PURE__ */ jsx2("p", { style: styles2.status, children: t("templates.list.loading") }) : error ? /* @__PURE__ */ jsx2("p", { style: styles2.error, role: "alert", children: error.explanation }) : /* @__PURE__ */ jsx2("ul", { style: styles2.list, children:
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
557
|
+
isLoading ? /* @__PURE__ */ jsx2("p", { style: styles2.status, children: t("templates.list.loading") }) : error ? /* @__PURE__ */ jsx2("p", { style: styles2.error, role: "alert", children: error.explanation }) : /* @__PURE__ */ jsx2("ul", { style: styles2.list, children: groups.map((group) => /* @__PURE__ */ jsxs2("li", { style: styles2.row, children: [
|
|
558
|
+
/* @__PURE__ */ jsxs2(
|
|
559
|
+
"button",
|
|
560
|
+
{
|
|
561
|
+
type: "button",
|
|
562
|
+
onClick: () => onSelectTemplate?.(group.primary),
|
|
563
|
+
style: styles2.rowButton,
|
|
564
|
+
children: [
|
|
565
|
+
/* @__PURE__ */ jsx2("span", { style: styles2.type, children: group.type }),
|
|
566
|
+
group.system ? /* @__PURE__ */ jsx2("span", { style: styles2.builtIn, children: t("templates.builtInBadge") }) : null,
|
|
567
|
+
/* @__PURE__ */ jsxs2("span", { style: group.primary.active ? styles2.active : styles2.inactive, children: [
|
|
568
|
+
/* @__PURE__ */ jsx2("span", { style: styles2.dot }),
|
|
569
|
+
group.primary.active ? t("common.status.active") : t("common.status.inactive")
|
|
570
|
+
] })
|
|
571
|
+
]
|
|
572
|
+
}
|
|
573
|
+
),
|
|
574
|
+
/* @__PURE__ */ jsx2("span", { style: styles2.chips, children: group.entries.map((entry) => {
|
|
575
|
+
const name = entry.locale ?? t("templates.defaultChip");
|
|
576
|
+
return /* @__PURE__ */ jsx2(
|
|
577
|
+
"button",
|
|
578
|
+
{
|
|
579
|
+
type: "button",
|
|
580
|
+
onClick: () => onSelectTemplate?.(entry),
|
|
581
|
+
style: entry.active ? styles2.chip : styles2.chipInactive,
|
|
582
|
+
"aria-label": t("templates.list.openLocale", { type: group.type, locale: name }),
|
|
583
|
+
title: entry.active ? void 0 : t("common.status.inactive"),
|
|
584
|
+
children: name
|
|
585
|
+
},
|
|
586
|
+
entry.locale ?? ""
|
|
587
|
+
);
|
|
588
|
+
}) })
|
|
589
|
+
] }, group.type)) })
|
|
512
590
|
] });
|
|
513
591
|
}
|
|
514
592
|
var pill = {
|
|
@@ -522,6 +600,18 @@ var pill = {
|
|
|
522
600
|
lineHeight: 1.5,
|
|
523
601
|
minWidth: 64
|
|
524
602
|
};
|
|
603
|
+
var chip = {
|
|
604
|
+
height: 24,
|
|
605
|
+
boxSizing: "border-box",
|
|
606
|
+
padding: "0 8px",
|
|
607
|
+
borderRadius: 6,
|
|
608
|
+
border: "1px solid var(--fonderie-border,#e0e0e0)",
|
|
609
|
+
background: "var(--fonderie-surface,#fff)",
|
|
610
|
+
color: "var(--fonderie-text,#171717)",
|
|
611
|
+
fontSize: 12,
|
|
612
|
+
fontFamily: 'var(--fonderie-mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace)',
|
|
613
|
+
cursor: "pointer"
|
|
614
|
+
};
|
|
525
615
|
var styles2 = {
|
|
526
616
|
container: { padding: "32px 40px 64px", maxWidth: 1160, boxSizing: "border-box" },
|
|
527
617
|
title: {
|
|
@@ -544,9 +634,16 @@ var styles2 = {
|
|
|
544
634
|
overflow: "hidden",
|
|
545
635
|
boxShadow: "var(--fonderie-shadow-card,0 2px 3px 0 rgba(0,0,0,.05))"
|
|
546
636
|
},
|
|
547
|
-
row: {
|
|
637
|
+
row: {
|
|
638
|
+
display: "flex",
|
|
639
|
+
alignItems: "center",
|
|
640
|
+
gap: 8,
|
|
641
|
+
paddingRight: 16,
|
|
642
|
+
borderBottom: "1px solid var(--fonderie-border-light,#f5f5f5)"
|
|
643
|
+
},
|
|
548
644
|
rowButton: {
|
|
549
|
-
|
|
645
|
+
flex: 1,
|
|
646
|
+
minWidth: 0,
|
|
550
647
|
display: "flex",
|
|
551
648
|
justifyContent: "space-between",
|
|
552
649
|
alignItems: "center",
|
|
@@ -565,7 +662,9 @@ var styles2 = {
|
|
|
565
662
|
flex: 1,
|
|
566
663
|
fontFamily: 'var(--fonderie-mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace)'
|
|
567
664
|
},
|
|
568
|
-
|
|
665
|
+
chips: { display: "flex", flexWrap: "wrap", justifyContent: "flex-end", gap: 6 },
|
|
666
|
+
chip,
|
|
667
|
+
chipInactive: { ...chip, opacity: 0.5, textDecoration: "line-through" },
|
|
569
668
|
dot: { width: 6, height: 6, borderRadius: 999, background: "currentColor" },
|
|
570
669
|
builtIn: {
|
|
571
670
|
fontSize: 11,
|
|
@@ -607,10 +706,13 @@ import {
|
|
|
607
706
|
FonderieApiError,
|
|
608
707
|
createAdminT as createAdminT3
|
|
609
708
|
} from "@fonderie/client";
|
|
610
|
-
import {
|
|
709
|
+
import { useTemplatePreview as useTemplatePreview2 } from "@fonderie/react-courier-admin";
|
|
710
|
+
import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
|
|
611
711
|
import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
612
712
|
var TYPE_RE = /^[a-z0-9][a-z0-9_-]{0,80}$/;
|
|
613
713
|
var LOCALE_RE = /^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*$/;
|
|
714
|
+
var IMPLICIT2 = /* @__PURE__ */ new Set(["subject", "preheader", "brandName"]);
|
|
715
|
+
var PREVIEW_DEBOUNCE_MS2 = 500;
|
|
614
716
|
function TemplateCreateScreen({
|
|
615
717
|
client,
|
|
616
718
|
type,
|
|
@@ -627,14 +729,48 @@ function TemplateCreateScreen({
|
|
|
627
729
|
const [text, setText] = useState2("");
|
|
628
730
|
const [error, setError] = useState2(null);
|
|
629
731
|
const [busy, setBusy] = useState2(false);
|
|
732
|
+
const [source, setSource] = useState2(
|
|
733
|
+
null
|
|
734
|
+
);
|
|
735
|
+
const { preview, isPreviewing, error: previewError, renderPreview } = useTemplatePreview2(client);
|
|
736
|
+
const variables = useRef2([]);
|
|
630
737
|
useEffect2(() => {
|
|
631
738
|
if (!type) return;
|
|
632
739
|
client.getTemplate(type, null).then(({ result }) => {
|
|
633
740
|
setSubject(result.subject ?? "");
|
|
634
741
|
setHtml(result.html ?? "");
|
|
635
742
|
setText(result.text);
|
|
743
|
+
setSource({ subject: result.subject ?? "", html: result.html ?? "", text: result.text });
|
|
636
744
|
}).catch(() => void 0);
|
|
637
745
|
}, [client, type]);
|
|
746
|
+
const previewType = addingLocale ? type : newType.trim();
|
|
747
|
+
const previewLocale = LOCALE_RE.test(targetLocale.trim()) ? targetLocale.trim() : null;
|
|
748
|
+
useEffect2(() => {
|
|
749
|
+
if (!TYPE_RE.test(previewType) || !text.trim()) return;
|
|
750
|
+
const timer = setTimeout(async () => {
|
|
751
|
+
const input = { text, ...subject ? { subject } : {}, ...html ? { html } : {} };
|
|
752
|
+
const render = () => renderPreview(
|
|
753
|
+
previewType,
|
|
754
|
+
{ ...input, data: Object.fromEntries(variables.current.map((v) => [v, v])) },
|
|
755
|
+
previewLocale
|
|
756
|
+
);
|
|
757
|
+
try {
|
|
758
|
+
const result = await render();
|
|
759
|
+
const used = result.variables.filter((v) => !IMPLICIT2.has(v));
|
|
760
|
+
if (used.some((v) => !variables.current.includes(v))) {
|
|
761
|
+
variables.current = used;
|
|
762
|
+
await render();
|
|
763
|
+
}
|
|
764
|
+
} catch {
|
|
765
|
+
}
|
|
766
|
+
}, PREVIEW_DEBOUNCE_MS2);
|
|
767
|
+
return () => clearTimeout(timer);
|
|
768
|
+
}, [renderPreview, previewType, previewLocale, subject, html, text]);
|
|
769
|
+
const untranslated = source ? [
|
|
770
|
+
subject && subject === source.subject ? tr("templates.subject") : null,
|
|
771
|
+
html && html === source.html ? tr("templates.htmlBody") : null,
|
|
772
|
+
text === source.text ? tr("templates.textBody") : null
|
|
773
|
+
].filter((f) => f !== null) : [];
|
|
638
774
|
const submit = async (e) => {
|
|
639
775
|
e.preventDefault();
|
|
640
776
|
setError(null);
|
|
@@ -670,75 +806,140 @@ function TemplateCreateScreen({
|
|
|
670
806
|
return /* @__PURE__ */ jsxs3("div", { style: styles3.container, children: [
|
|
671
807
|
/* @__PURE__ */ jsx3("h1", { style: styles3.title, children: addingLocale ? tr("templates.create.titleLocale", { type }) : tr("templates.create.titleNew") }),
|
|
672
808
|
/* @__PURE__ */ jsx3("p", { style: styles3.meta, children: addingLocale ? tr("templates.create.hintLocale") : tr("templates.create.hintNew") }),
|
|
673
|
-
/* @__PURE__ */ jsxs3("
|
|
674
|
-
|
|
675
|
-
/* @__PURE__ */
|
|
809
|
+
/* @__PURE__ */ jsxs3("div", { style: styles3.split, children: [
|
|
810
|
+
/* @__PURE__ */ jsxs3("form", { style: styles3.form, onSubmit: (e) => void submit(e), children: [
|
|
811
|
+
!addingLocale ? /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
812
|
+
/* @__PURE__ */ jsx3("label", { style: styles3.label, htmlFor: "new-template-type", children: tr("templates.create.type") }),
|
|
813
|
+
/* @__PURE__ */ jsx3(
|
|
814
|
+
"input",
|
|
815
|
+
{
|
|
816
|
+
id: "new-template-type",
|
|
817
|
+
style: styles3.input,
|
|
818
|
+
value: newType,
|
|
819
|
+
placeholder: "weekly-digest",
|
|
820
|
+
autoComplete: "off",
|
|
821
|
+
spellCheck: false,
|
|
822
|
+
onChange: (e) => setNewType(e.target.value)
|
|
823
|
+
}
|
|
824
|
+
)
|
|
825
|
+
] }) : null,
|
|
826
|
+
/* @__PURE__ */ jsx3("label", { style: styles3.label, htmlFor: "new-template-locale", children: addingLocale ? tr("templates.create.locale") : tr("templates.create.localeOptional") }),
|
|
676
827
|
/* @__PURE__ */ jsx3(
|
|
677
828
|
"input",
|
|
678
829
|
{
|
|
679
|
-
id: "new-template-
|
|
830
|
+
id: "new-template-locale",
|
|
680
831
|
style: styles3.input,
|
|
681
|
-
value:
|
|
682
|
-
placeholder: "
|
|
832
|
+
value: targetLocale,
|
|
833
|
+
placeholder: "fr",
|
|
834
|
+
list: "new-template-locales",
|
|
683
835
|
autoComplete: "off",
|
|
684
836
|
spellCheck: false,
|
|
685
|
-
onChange: (e) =>
|
|
837
|
+
onChange: (e) => setTargetLocale(e.target.value)
|
|
686
838
|
}
|
|
687
|
-
)
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
839
|
+
),
|
|
840
|
+
/* @__PURE__ */ jsx3("datalist", { id: "new-template-locales", children: [...new Set(locales)].map((l) => /* @__PURE__ */ jsx3("option", { value: l }, l)) }),
|
|
841
|
+
/* @__PURE__ */ jsx3("label", { style: styles3.label, htmlFor: "new-template-subject", children: tr("templates.subject") }),
|
|
842
|
+
/* @__PURE__ */ jsx3(
|
|
843
|
+
"input",
|
|
844
|
+
{
|
|
845
|
+
id: "new-template-subject",
|
|
846
|
+
style: styles3.input,
|
|
847
|
+
value: subject,
|
|
848
|
+
onChange: (e) => setSubject(e.target.value)
|
|
849
|
+
}
|
|
850
|
+
),
|
|
851
|
+
/* @__PURE__ */ jsx3("label", { style: styles3.label, htmlFor: "new-template-html", children: tr("templates.htmlBody") }),
|
|
852
|
+
/* @__PURE__ */ jsx3(
|
|
853
|
+
"textarea",
|
|
854
|
+
{
|
|
855
|
+
id: "new-template-html",
|
|
856
|
+
style: { ...styles3.textarea, minHeight: 160 },
|
|
857
|
+
value: html,
|
|
858
|
+
onChange: (e) => setHtml(e.target.value)
|
|
859
|
+
}
|
|
860
|
+
),
|
|
861
|
+
/* @__PURE__ */ jsx3("label", { style: styles3.label, htmlFor: "new-template-text", children: tr("templates.textBody") }),
|
|
862
|
+
/* @__PURE__ */ jsx3(
|
|
863
|
+
"textarea",
|
|
864
|
+
{
|
|
865
|
+
id: "new-template-text",
|
|
866
|
+
style: { ...styles3.textarea, minHeight: 100 },
|
|
867
|
+
value: text,
|
|
868
|
+
onChange: (e) => setText(e.target.value)
|
|
869
|
+
}
|
|
870
|
+
),
|
|
871
|
+
untranslated.length > 0 ? /* @__PURE__ */ jsx3("p", { style: styles3.notice, children: tr("templates.create.untranslated", { fields: untranslated.join(", ") }) }) : null,
|
|
872
|
+
error ? /* @__PURE__ */ jsx3("p", { style: styles3.error, role: "alert", children: error }) : null,
|
|
873
|
+
/* @__PURE__ */ jsx3("button", { type: "submit", disabled: busy, style: styles3.button, children: busy ? tr("templates.create.creating") : addingLocale ? tr("templates.create.submitLocale") : tr("templates.create.submitNew") })
|
|
874
|
+
] }),
|
|
875
|
+
/* @__PURE__ */ jsxs3("div", { style: styles3.previewColumn, children: [
|
|
876
|
+
/* @__PURE__ */ jsxs3("div", { style: styles3.previewHeader, children: [
|
|
877
|
+
/* @__PURE__ */ jsx3("span", { style: styles3.label, children: tr("templates.editor.preview") }),
|
|
878
|
+
/* @__PURE__ */ jsx3("span", { style: styles3.meta, children: isPreviewing ? tr("templates.editor.rendering") : tr("templates.editor.live") })
|
|
879
|
+
] }),
|
|
880
|
+
previewError ? /* @__PURE__ */ jsx3("p", { style: styles3.error, role: "alert", children: previewError.explanation }) : null,
|
|
881
|
+
preview?.subject ? /* @__PURE__ */ jsx3("p", { style: styles3.previewSubject, children: preview.subject }) : null,
|
|
882
|
+
preview?.html ? (
|
|
883
|
+
// sandbox="" — no scripts, no same-origin: operator-authored HTML must
|
|
884
|
+
// never execute in the console's origin.
|
|
885
|
+
/* @__PURE__ */ jsx3(
|
|
886
|
+
"iframe",
|
|
887
|
+
{
|
|
888
|
+
title: tr("templates.editor.previewTitle"),
|
|
889
|
+
style: styles3.previewFrame,
|
|
890
|
+
sandbox: "",
|
|
891
|
+
srcDoc: preview.html
|
|
892
|
+
}
|
|
893
|
+
)
|
|
894
|
+
) : preview ? /* @__PURE__ */ jsx3("pre", { style: styles3.previewText, children: preview.text }) : /* @__PURE__ */ jsx3("p", { style: styles3.meta, children: tr("templates.editor.nothingRendered") })
|
|
895
|
+
] })
|
|
736
896
|
] })
|
|
737
897
|
] });
|
|
738
898
|
}
|
|
739
899
|
var MONO = 'var(--fonderie-mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace)';
|
|
740
900
|
var styles3 = {
|
|
741
|
-
container: { padding: "8px 40px 64px", maxWidth:
|
|
901
|
+
container: { padding: "8px 40px 64px", maxWidth: 1400, boxSizing: "border-box" },
|
|
902
|
+
// The editor's layout: the form on the left, the render pinned on the right.
|
|
903
|
+
split: {
|
|
904
|
+
display: "grid",
|
|
905
|
+
gridTemplateColumns: "minmax(0, 1fr) minmax(0, 1fr)",
|
|
906
|
+
gap: 24,
|
|
907
|
+
alignItems: "start"
|
|
908
|
+
},
|
|
909
|
+
previewColumn: {
|
|
910
|
+
minWidth: 0,
|
|
911
|
+
display: "flex",
|
|
912
|
+
flexDirection: "column",
|
|
913
|
+
position: "sticky",
|
|
914
|
+
top: 16
|
|
915
|
+
},
|
|
916
|
+
previewHeader: { display: "flex", justifyContent: "space-between", alignItems: "center" },
|
|
917
|
+
previewSubject: { fontSize: 14, fontWeight: 600, margin: "8px 0" },
|
|
918
|
+
previewFrame: {
|
|
919
|
+
boxShadow: "var(--fonderie-shadow-card,0 2px 3px 0 rgba(0,0,0,.05))",
|
|
920
|
+
width: "100%",
|
|
921
|
+
height: 520,
|
|
922
|
+
border: "1px solid var(--fonderie-border,#e0e0e0)",
|
|
923
|
+
borderRadius: 8,
|
|
924
|
+
background: "var(--fonderie-surface,#fff)"
|
|
925
|
+
},
|
|
926
|
+
previewText: {
|
|
927
|
+
whiteSpace: "pre-wrap",
|
|
928
|
+
border: "1px solid var(--fonderie-border,#e0e0e0)",
|
|
929
|
+
borderRadius: 8,
|
|
930
|
+
padding: 12,
|
|
931
|
+
fontSize: 13,
|
|
932
|
+
fontFamily: MONO
|
|
933
|
+
},
|
|
934
|
+
// The console's warning token (the shell's amber), readable in both themes.
|
|
935
|
+
notice: {
|
|
936
|
+
fontSize: 13,
|
|
937
|
+
color: "var(--fonderie-text,#171717)",
|
|
938
|
+
background: "color-mix(in srgb, var(--fonderie-warning,#f5a623) 16%, transparent)",
|
|
939
|
+
borderRadius: 6,
|
|
940
|
+
padding: "8px 12px",
|
|
941
|
+
margin: "12px 0 0"
|
|
942
|
+
},
|
|
742
943
|
title: {
|
|
743
944
|
fontSize: 22,
|
|
744
945
|
fontWeight: 600,
|