@antelopejs/dms-mailing 0.6.1 → 0.7.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.
Files changed (236) hide show
  1. package/README.md +8 -7
  2. package/dist/constants.d.ts +1 -2
  3. package/dist/constants.js +2 -3
  4. package/dist/constants.js.map +1 -1
  5. package/dist/data/templates-table.d.ts +4 -1
  6. package/dist/data/templates-table.js +55 -20
  7. package/dist/data/templates-table.js.map +1 -1
  8. package/dist/data-types/categories-type.d.ts +5 -5
  9. package/dist/db/models/index.d.ts +0 -1
  10. package/dist/db/models/index.js +0 -1
  11. package/dist/db/models/index.js.map +1 -1
  12. package/dist/db/models/send-events.model.js +2 -3
  13. package/dist/db/models/send-events.model.js.map +1 -1
  14. package/dist/db/models/sends.model.js +2 -3
  15. package/dist/db/models/sends.model.js.map +1 -1
  16. package/dist/db/models/templates.model.d.ts +10 -1
  17. package/dist/db/models/templates.model.js +29 -10
  18. package/dist/db/models/templates.model.js.map +1 -1
  19. package/dist/db/tables/index.d.ts +0 -1
  20. package/dist/db/tables/index.js +0 -1
  21. package/dist/db/tables/index.js.map +1 -1
  22. package/dist/db/tables/settings.table.d.ts +0 -1
  23. package/dist/db/tables/settings.table.js +0 -4
  24. package/dist/db/tables/settings.table.js.map +1 -1
  25. package/dist/db/tables/templates.table.d.ts +7 -4
  26. package/dist/db/tables/templates.table.js +4 -14
  27. package/dist/db/tables/templates.table.js.map +1 -1
  28. package/dist/engine/formats.d.ts +13 -0
  29. package/dist/engine/formats.js +85 -0
  30. package/dist/engine/formats.js.map +1 -0
  31. package/dist/engine/index.d.ts +1 -0
  32. package/dist/engine/index.js +1 -0
  33. package/dist/engine/index.js.map +1 -1
  34. package/dist/engine/resolve.d.ts +2 -1
  35. package/dist/engine/resolve.js +14 -7
  36. package/dist/engine/resolve.js.map +1 -1
  37. package/dist/engine/tokens.d.ts +2 -1
  38. package/dist/engine/tokens.js +3 -5
  39. package/dist/engine/tokens.js.map +1 -1
  40. package/dist/frontend-module.d.ts +2 -0
  41. package/dist/frontend-module.js +24 -0
  42. package/dist/frontend-module.js.map +1 -0
  43. package/dist/hooks/index.d.ts +0 -1
  44. package/dist/hooks/index.js +0 -1
  45. package/dist/hooks/index.js.map +1 -1
  46. package/dist/hooks/tenant-export.js +4 -1
  47. package/dist/hooks/tenant-export.js.map +1 -1
  48. package/dist/implementations/dms-mailing/index.d.ts +3 -3
  49. package/dist/implementations/dms-mailing/index.js +8 -7
  50. package/dist/implementations/dms-mailing/index.js.map +1 -1
  51. package/dist/index.js +2 -20
  52. package/dist/index.js.map +1 -1
  53. package/dist/pages/overview.d.ts +1 -1
  54. package/dist/pages/overview.js +11 -13
  55. package/dist/pages/overview.js.map +1 -1
  56. package/dist/pages/sends.d.ts +1 -1
  57. package/dist/pages/sends.js +16 -8
  58. package/dist/pages/sends.js.map +1 -1
  59. package/dist/pages/settings/form.js +8 -0
  60. package/dist/pages/settings/form.js.map +1 -1
  61. package/dist/pages/templates.js +3 -8
  62. package/dist/pages/templates.js.map +1 -1
  63. package/dist/pages/toolbar.d.ts +12 -0
  64. package/dist/pages/toolbar.js +41 -0
  65. package/dist/pages/toolbar.js.map +1 -0
  66. package/dist/realtime.d.ts +2 -2
  67. package/dist/realtime.js +8 -8
  68. package/dist/realtime.js.map +1 -1
  69. package/dist/routes/metrics.d.ts +9 -2
  70. package/dist/routes/metrics.js +90 -35
  71. package/dist/routes/metrics.js.map +1 -1
  72. package/dist/routes/period.d.ts +1 -0
  73. package/dist/routes/period.js +19 -0
  74. package/dist/routes/period.js.map +1 -1
  75. package/dist/routes/sends.js +6 -3
  76. package/dist/routes/sends.js.map +1 -1
  77. package/dist/routes/settings.js +6 -7
  78. package/dist/routes/settings.js.map +1 -1
  79. package/dist/routes/templates.d.ts +8 -4
  80. package/dist/routes/templates.js +41 -20
  81. package/dist/routes/templates.js.map +1 -1
  82. package/dist/services/categories.d.ts +2 -1
  83. package/dist/services/categories.js +9 -0
  84. package/dist/services/categories.js.map +1 -1
  85. package/dist/services/declarations.js +42 -184
  86. package/dist/services/declarations.js.map +1 -1
  87. package/dist/services/events.js +1 -1
  88. package/dist/services/events.js.map +1 -1
  89. package/dist/services/localized-text.d.ts +7 -0
  90. package/dist/services/localized-text.js +28 -0
  91. package/dist/services/localized-text.js.map +1 -0
  92. package/dist/services/send-import.js +0 -2
  93. package/dist/services/send-import.js.map +1 -1
  94. package/dist/services/send-stat-items.js +34 -12
  95. package/dist/services/send-stat-items.js.map +1 -1
  96. package/dist/services/send-stats.d.ts +7 -0
  97. package/dist/services/send-stats.js +17 -5
  98. package/dist/services/send-stats.js.map +1 -1
  99. package/dist/services/send.d.ts +2 -0
  100. package/dist/services/send.js +11 -12
  101. package/dist/services/send.js.map +1 -1
  102. package/dist/services/sender-declaration.d.ts +2 -0
  103. package/dist/services/sender-declaration.js +18 -0
  104. package/dist/services/sender-declaration.js.map +1 -0
  105. package/dist/services/settings.d.ts +1 -1
  106. package/dist/services/settings.js +1 -5
  107. package/dist/services/settings.js.map +1 -1
  108. package/dist/services/starters.d.ts +2 -2
  109. package/dist/services/starters.js +1 -10
  110. package/dist/services/starters.js.map +1 -1
  111. package/dist/services/templates.d.ts +13 -7
  112. package/dist/services/templates.js +72 -33
  113. package/dist/services/templates.js.map +1 -1
  114. package/dist/services/webhook-status.d.ts +2 -1
  115. package/dist/services/webhook-status.js +6 -5
  116. package/dist/services/webhook-status.js.map +1 -1
  117. package/dist/types/settings.d.ts +8 -0
  118. package/dist/types/settings.js +33 -6
  119. package/dist/types/settings.js.map +1 -1
  120. package/dist/validation/declarations.schema.d.ts +25 -7
  121. package/dist/validation/declarations.schema.js +16 -3
  122. package/dist/validation/declarations.schema.js.map +1 -1
  123. package/dist/validation/settings.schema.d.ts +41 -41
  124. package/dist/validation/settings.schema.js +30 -6
  125. package/dist/validation/settings.schema.js.map +1 -1
  126. package/dist/validation/templates.schema.d.ts +13 -0
  127. package/dist/validation/templates.schema.js +6 -1
  128. package/dist/validation/templates.schema.js.map +1 -1
  129. package/frontend-vue/app/components/CategoriesInput.vue +15 -13
  130. package/frontend-vue/app/components/FunnelCard.vue +46 -3
  131. package/frontend-vue/app/components/NewTemplateModal.vue +4 -2
  132. package/frontend-vue/app/components/OverviewGate.vue +0 -8
  133. package/frontend-vue/app/components/ProviderPill.vue +50 -0
  134. package/frontend-vue/app/components/SendDrawer.vue +206 -183
  135. package/frontend-vue/app/components/StarterGallery.vue +1 -1
  136. package/frontend-vue/app/components/TemplateCard.vue +4 -1
  137. package/frontend-vue/app/components/TemplateDrawer.vue +3 -5
  138. package/frontend-vue/app/components/TemplateGallery.vue +57 -17
  139. package/frontend-vue/app/components/TemplatePreviewFrame.vue +41 -11
  140. package/frontend-vue/app/components/editor/EditorTemplatePane.vue +1 -3
  141. package/frontend-vue/app/components/editor/EditorTitle.vue +1 -7
  142. package/frontend-vue/app/composables/useLocalizedDefaults.ts +5 -7
  143. package/frontend-vue/app/composables/useMailingApi.ts +3 -3
  144. package/frontend-vue/app/composables/useMailingReads.ts +2 -2
  145. package/frontend-vue/app/composables/useTemplateCategories.ts +8 -7
  146. package/frontend-vue/app/types/mailing.ts +18 -0
  147. package/frontend-vue/app/utils/categories.ts +58 -49
  148. package/frontend-vue/app/utils/funnel.ts +25 -0
  149. package/frontend-vue/app/utils/period.ts +0 -7
  150. package/frontend-vue/app/utils/starters.ts +3 -1
  151. package/frontend-vue/app/utils/tokens.ts +6 -1
  152. package/frontend-vue/i18n/locales/mailing-en-GB.json +19 -28
  153. package/frontend-vue/i18n/locales/mailing-fr-FR.json +19 -28
  154. package/frontend-vue/package.json +1 -1
  155. package/frontend-vue/tests/categories-input.test.ts +4 -10
  156. package/frontend-vue/tests/categories.test.ts +55 -17
  157. package/frontend-vue/tests/conditions.test.ts +1 -4
  158. package/frontend-vue/tests/funnel-card.test.ts +27 -0
  159. package/frontend-vue/tests/funnel.test.ts +25 -0
  160. package/frontend-vue/tests/localized-defaults.test.ts +0 -29
  161. package/frontend-vue/tests/new-template-modal.test.ts +1 -1
  162. package/frontend-vue/tests/overview-gate.test.ts +1 -24
  163. package/frontend-vue/tests/page-reads.test.ts +9 -25
  164. package/frontend-vue/tests/period.test.ts +0 -12
  165. package/frontend-vue/tests/replay-toast.test.ts +2 -1
  166. package/frontend-vue/tests/send-drawer.test.ts +123 -0
  167. package/frontend-vue/tests/template-drawer.test.ts +1 -1
  168. package/frontend-vue/tests/template-gallery.test.ts +90 -3
  169. package/frontend-vue/tests/template-preview-frame.test.ts +92 -0
  170. package/package.json +8 -7
  171. package/skills/dms-mailing/SKILL.md +25 -18
  172. package/src/constants.ts +2 -2
  173. package/src/data/sends-table.ts +1 -1
  174. package/src/data/templates-table.ts +125 -33
  175. package/src/db/models/index.ts +0 -1
  176. package/src/db/models/send-events.model.ts +2 -2
  177. package/src/db/models/sends.model.ts +7 -8
  178. package/src/db/models/templates.model.ts +64 -16
  179. package/src/db/tables/index.ts +0 -1
  180. package/src/db/tables/settings.table.ts +0 -8
  181. package/src/db/tables/templates.table.ts +12 -17
  182. package/src/engine/formats.ts +124 -0
  183. package/src/engine/index.ts +1 -0
  184. package/src/engine/resolve.ts +18 -7
  185. package/src/engine/tokens.ts +7 -5
  186. package/src/frontend-module.ts +36 -0
  187. package/src/hooks/index.ts +0 -1
  188. package/src/hooks/tenant-export.ts +8 -1
  189. package/src/implementations/dms-mailing/index.ts +5 -16
  190. package/src/index.ts +3 -29
  191. package/src/pages/overview.ts +20 -16
  192. package/src/pages/sends.ts +27 -11
  193. package/src/pages/settings/form.ts +16 -0
  194. package/src/pages/templates.ts +4 -8
  195. package/src/pages/toolbar.ts +77 -0
  196. package/src/realtime.ts +17 -8
  197. package/src/routes/metrics.ts +158 -45
  198. package/src/routes/period.ts +32 -0
  199. package/src/routes/sends.ts +13 -4
  200. package/src/routes/settings.ts +11 -8
  201. package/src/routes/templates.ts +81 -37
  202. package/src/services/categories.ts +14 -1
  203. package/src/services/declarations.ts +64 -317
  204. package/src/services/events.ts +1 -1
  205. package/src/services/localized-text.ts +53 -0
  206. package/src/services/send-import.ts +2 -2
  207. package/src/services/send-stat-items.ts +60 -13
  208. package/src/services/send-stats.ts +37 -5
  209. package/src/services/send.ts +19 -20
  210. package/src/services/sender-declaration.ts +27 -0
  211. package/src/services/settings.ts +3 -10
  212. package/src/services/starters.ts +3 -15
  213. package/src/services/templates.ts +109 -63
  214. package/src/services/webhook-status.ts +3 -3
  215. package/src/types/settings.ts +55 -5
  216. package/src/validation/declarations.schema.ts +29 -3
  217. package/src/validation/settings.schema.ts +56 -14
  218. package/src/validation/templates.schema.ts +10 -0
  219. package/dist/db/models/template-slugs.model.d.ts +0 -27
  220. package/dist/db/models/template-slugs.model.js +0 -43
  221. package/dist/db/models/template-slugs.model.js.map +0 -1
  222. package/dist/db/tables/template-slugs.table.d.ts +0 -7
  223. package/dist/db/tables/template-slugs.table.js +0 -35
  224. package/dist/db/tables/template-slugs.table.js.map +0 -1
  225. package/dist/hooks/database-ready.d.ts +0 -7
  226. package/dist/hooks/database-ready.js +0 -26
  227. package/dist/hooks/database-ready.js.map +0 -1
  228. package/dist/services/fingerprint.d.ts +0 -1
  229. package/dist/services/fingerprint.js +0 -20
  230. package/dist/services/fingerprint.js.map +0 -1
  231. package/frontend-vue/app/components/PageHeader.vue +0 -94
  232. package/frontend-vue/app/composables/useOverviewSetup.ts +0 -21
  233. package/src/db/models/template-slugs.model.ts +0 -61
  234. package/src/db/tables/template-slugs.table.ts +0 -29
  235. package/src/hooks/database-ready.ts +0 -47
  236. package/src/services/fingerprint.ts +0 -22
@@ -5,6 +5,7 @@ import {
5
5
  JSONBody,
6
6
  Parameter,
7
7
  Post,
8
+ Put,
8
9
  type RequestContext,
9
10
  } from "@antelopejs/interface-api";
10
11
  import { assert, assertValidation } from "@antelopejs/interface-api-util";
@@ -22,12 +23,21 @@ import {
22
23
  collectContentVariablePaths,
23
24
  resolveEmail,
24
25
  type TemplateChange,
26
+ valueFormatOf,
25
27
  } from "../engine";
26
- import { type MailingTemplate, SendModel, TemplateModel } from "../db";
28
+ import {
29
+ type MailingTemplate,
30
+ SendModel,
31
+ type TemplateFields,
32
+ TemplateModel,
33
+ } from "../db";
34
+ import { saveDetails } from "../data/templates-table";
27
35
  import { TemplatesPageController } from "../pages/templates";
28
36
  import { forAudience, statsWindowStart } from "../services/metrics";
29
37
  import { refreshTemplateAttention } from "../services/template-attention";
30
38
  import { renderEmailHtml } from "../services/render";
39
+ import { categoriesIn } from "../services/categories";
40
+ import { localeOfRequest, withTextIn } from "../services/localized-text";
31
41
  import { getSettings } from "../services/settings";
32
42
  import { findStarter, STARTER_TEMPLATES } from "../services/starters";
33
43
  import {
@@ -37,9 +47,7 @@ import {
37
47
  type TemplateStats,
38
48
  } from "../services/template-stats";
39
49
  import {
40
- assertSlugClaimed,
41
50
  contentFieldsOf,
42
- contentOfVersion,
43
51
  discardDraft,
44
52
  displayName,
45
53
  hasDraft,
@@ -55,13 +63,18 @@ import {
55
63
  publishedVersionOf,
56
64
  publishTemplate,
57
65
  saveDraft,
66
+ publishedSnapshot,
67
+ snapshotOfVersion,
68
+ type TemplateDraft,
69
+ workingSnapshot,
58
70
  workingContent,
59
71
  workingVariables,
72
+ refuseTakenSlug,
60
73
  requireTemplate,
61
74
  } from "../services/templates";
62
75
  import {
63
76
  BUSINESS_AUDIENCE,
64
- type MailingSettingsValues,
77
+ type CategoryInLocale,
65
78
  type TemplateContent,
66
79
  type TemplateStatus,
67
80
  type VariableDefinition,
@@ -76,7 +89,6 @@ import {
76
89
  } from "../validation/templates.schema";
77
90
 
78
91
  const STARTER_NOT_FOUND = "$dms_mailing.errors.starter_not_found";
79
- const DUPLICATE_SLUG = "$dms_mailing.errors.duplicate_slug";
80
92
  const INVALID_CONTENT = "$dms_mailing.errors.invalid_content";
81
93
  const INVALID_TRANSITION = "$dms_mailing.errors.invalid_transition";
82
94
  const NOTHING_TO_PUBLISH = "$dms_mailing.errors.nothing_to_publish";
@@ -104,14 +116,13 @@ const TARGET_STATUS: Record<
104
116
  };
105
117
 
106
118
  /** What a lifecycle action records besides the new status. */
107
- const TRANSITION_FIELDS: Partial<
108
- Record<TemplateAction, () => Partial<MailingTemplate>>
109
- > = {
110
- unpublish: () => ({ unpublishedAt: new Date() }),
111
- };
119
+ const TRANSITION_FIELDS: Partial<Record<TemplateAction, () => TemplateFields>> =
120
+ {
121
+ unpublish: () => ({ unpublishedAt: new Date() }),
122
+ };
112
123
 
113
124
  export interface CategoriesResponse {
114
- categories: MailingSettingsValues["categories"];
125
+ categories: CategoryInLocale[];
115
126
  }
116
127
 
117
128
  export interface TemplateSender {
@@ -121,6 +132,7 @@ export interface TemplateSender {
121
132
  }
122
133
 
123
134
  export interface TemplateContentResponse {
135
+ /** The template, its name in the reader's language. */
124
136
  template: MailingTemplate;
125
137
  /** The content the editor works on: the draft, else the published one. */
126
138
  content: TemplateContent;
@@ -146,7 +158,7 @@ export interface TemplateContentResponse {
146
158
  fallbackLocale: string;
147
159
  /** Whether a send missing a variable fails rather than goes out blank. */
148
160
  blockOnMissingVariables: boolean;
149
- categories: MailingSettingsValues["categories"];
161
+ categories: CategoryInLocale[];
150
162
  sender: TemplateSender;
151
163
  }
152
164
 
@@ -192,6 +204,11 @@ export class TemplatesController extends Controller(
192
204
  return displayName(this.user);
193
205
  }
194
206
 
207
+ /** The language the reader reads names and labels in. */
208
+ private get locale(): string {
209
+ return localeOfRequest(this.ctx);
210
+ }
211
+
195
212
  /**
196
213
  * The tenant's categories for a category picker. Separate from `/settings`,
197
214
  * which is guarded by the settings page permission a template editor need
@@ -200,7 +217,7 @@ export class TemplatesController extends Controller(
200
217
  @Get("/categories")
201
218
  async categories(): Promise<CategoriesResponse> {
202
219
  const settings = await getSettings(this.tenantId);
203
- return { categories: settings.categories };
220
+ return { categories: categoriesIn(settings.categories, this.locale) };
204
221
  }
205
222
 
206
223
  @Get("/starters")
@@ -236,7 +253,12 @@ export class TemplatesController extends Controller(
236
253
  );
237
254
  const localeContent = localeContentOf(starter.content, locale);
238
255
  assert(localeContent, HTTP_UNPROCESSABLE, INVALID_CONTENT);
239
- const email = resolveEmail(localeContent, starter.testDataFor(locale));
256
+ const email = resolveEmail(
257
+ localeContent,
258
+ starter.testDataFor(locale),
259
+ undefined,
260
+ valueFormatOf(locale, starter.variables),
261
+ );
240
262
  return { html: await renderEmailHtml(email, locale), locale };
241
263
  }
242
264
 
@@ -289,7 +311,7 @@ export class TemplatesController extends Controller(
289
311
  ? parseContent(template)
290
312
  : null;
291
313
  return {
292
- template,
314
+ template: template.localize(this.locale, ["name"]),
293
315
  content,
294
316
  publishedContent,
295
317
  hasDraft: hasDraft(template),
@@ -305,7 +327,7 @@ export class TemplatesController extends Controller(
305
327
  testData: parseTestData(template),
306
328
  fallbackLocale: settings.fallbackLocale,
307
329
  blockOnMissingVariables: settings.blockOnMissingVariables,
308
- categories: settings.categories,
330
+ categories: categoriesIn(settings.categories, this.locale),
309
331
  sender: {
310
332
  name: settings.senderName,
311
333
  email: settings.senderEmail,
@@ -431,7 +453,7 @@ export class TemplatesController extends Controller(
431
453
  ) {
432
454
  await this.requireTransition(id, action);
433
455
  const status = TARGET_STATUS[action];
434
- await this.templates.update(id, {
456
+ await this.templates.updateFields(id, {
435
457
  status,
436
458
  updatedBy: this.author,
437
459
  ...TRANSITION_FIELDS[action]?.(),
@@ -472,7 +494,9 @@ export class TemplatesController extends Controller(
472
494
  testDataSchema.parse(value),
473
495
  );
474
496
  await requireTemplate(this.templates, id);
475
- await this.templates.update(id, { json_test_data: JSON.stringify(data) });
497
+ await this.templates.updateFields(id, {
498
+ json_test_data: JSON.stringify(data),
499
+ });
476
500
  return { data };
477
501
  }
478
502
 
@@ -487,9 +511,12 @@ export class TemplatesController extends Controller(
487
511
  () => INVALID_CONTENT,
488
512
  );
489
513
  const template = await requireTemplate(this.templates, id);
490
- const content =
491
- (input.content as TemplateContent | undefined) ??
492
- (await this.previewContent(template, input.version));
514
+ const { content, variables } = input.content
515
+ ? {
516
+ ...workingSnapshot(template),
517
+ content: input.content as TemplateContent,
518
+ }
519
+ : await this.previewSnapshot(template, input.version);
493
520
  const settings = await getSettings(this.tenantId);
494
521
  const locale = pickLocale(content, input.locale, settings.fallbackLocale);
495
522
  const localeContent = localeContentOf(content, locale);
@@ -497,6 +524,8 @@ export class TemplatesController extends Controller(
497
524
  const email = resolveEmail(
498
525
  localeContent,
499
526
  input.data ?? parseTestData(template),
527
+ undefined,
528
+ valueFormatOf(locale, variables),
500
529
  );
501
530
  const html = await renderEmailHtml(email, locale);
502
531
  return {
@@ -508,14 +537,30 @@ export class TemplatesController extends Controller(
508
537
  };
509
538
  }
510
539
 
511
- private previewContent(
540
+ private previewSnapshot(
512
541
  template: MailingTemplate,
513
542
  version: "draft" | "published" | number | undefined,
514
- ): Promise<TemplateContent> | TemplateContent {
515
- if (version === "published") return parseContent(template);
543
+ ): Promise<TemplateDraft> | TemplateDraft {
544
+ if (version === "published") return publishedSnapshot(template);
516
545
  if (typeof version === "number")
517
- return contentOfVersion(this.tenantId, template, version);
518
- return workingContent(template);
546
+ return snapshotOfVersion(this.tenantId, template, version);
547
+ return workingSnapshot(template);
548
+ }
549
+
550
+ /**
551
+ * Renames a template in the reader's language, its other languages kept,
552
+ * and sets its category: what the module's drawer and editor save, each
553
+ * showing one language. The DMS edit form, which shows every language,
554
+ * writes through the table's edit route.
555
+ */
556
+ @Put("/:id/details")
557
+ async details(
558
+ @Parameter("id", "param") id: string,
559
+ @JSONBody() body: unknown,
560
+ ) {
561
+ const template = await requireTemplate(this.templates, id);
562
+ await saveDetails(this.ctx, template, body, this.author);
563
+ return { id };
519
564
  }
520
565
 
521
566
  @Post("/:id/duplicate")
@@ -527,17 +572,16 @@ export class TemplatesController extends Controller(
527
572
  duplicateSchema.parse(value),
528
573
  );
529
574
  const source = await requireTemplate(this.templates, id);
530
- const clash = await this.templates.getBySlug(slug);
531
- assert(!clash, HTTP_CONFLICT, DUPLICATE_SLUG);
532
- const [copyId] = await this.templates.insert({
533
- slug,
534
- name,
535
- category: source.category,
536
- status: "draft",
537
- ...contentFieldsOf(source),
538
- updatedBy: this.author,
539
- });
540
- await assertSlugClaimed(this.tenantId, slug, copyId as string);
575
+ const [copyId] = await refuseTakenSlug(
576
+ this.templates.insertFields({
577
+ slug,
578
+ name: withTextIn(undefined, this.locale, name),
579
+ category: source.category,
580
+ status: "draft",
581
+ ...contentFieldsOf(source),
582
+ updatedBy: this.author,
583
+ }),
584
+ );
541
585
  await refreshTemplateAttention(this.tenantId);
542
586
  return { id: copyId };
543
587
  }
@@ -1,6 +1,19 @@
1
1
  import type { MailingTemplate } from "../db";
2
- import type { TemplateCategory } from "../types";
2
+ import type { CategoryInLocale, TemplateCategory } from "../types";
3
3
  import type { CategoryEntry } from "../validation/settings.schema";
4
+ import { textIn } from "./localized-text";
5
+
6
+ /** The categories as a reader of `locale` sees them. */
7
+ export function categoriesIn(
8
+ categories: TemplateCategory[],
9
+ locale: string,
10
+ ): CategoryInLocale[] {
11
+ return categories.map(({ id, label, icon }) => ({
12
+ id,
13
+ label: textIn(label, locale),
14
+ icon,
15
+ }));
16
+ }
4
17
 
5
18
  /** The categories a list keeps, without the removed ones it moves away. */
6
19
  export function keptCategories(entries: CategoryEntry[]): TemplateCategory[] {