@antelopejs/dms-mailing 0.3.4 → 0.4.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.
Files changed (705) hide show
  1. package/README.md +232 -22
  2. package/dist/automation/email-event-trigger.js +2 -2
  3. package/dist/automation/email-event-trigger.js.map +1 -1
  4. package/dist/automation/send-template-action.js +2 -2
  5. package/dist/automation/send-template-action.js.map +1 -1
  6. package/dist/config.js +3 -1
  7. package/dist/config.js.map +1 -1
  8. package/dist/constants.d.ts +10 -1
  9. package/dist/constants.js +15 -3
  10. package/dist/constants.js.map +1 -1
  11. package/dist/crons/attention.d.ts +3 -0
  12. package/dist/crons/attention.js +19 -0
  13. package/dist/crons/attention.js.map +1 -0
  14. package/dist/crons/index.d.ts +2 -0
  15. package/dist/crons/index.js +2 -0
  16. package/dist/crons/index.js.map +1 -1
  17. package/dist/crons/jobs.d.ts +12 -0
  18. package/dist/crons/jobs.js +27 -0
  19. package/dist/crons/jobs.js.map +1 -0
  20. package/dist/crons/retention.d.ts +2 -4
  21. package/dist/crons/retention.js +7 -22
  22. package/dist/crons/retention.js.map +1 -1
  23. package/dist/data/displays.d.ts +5 -0
  24. package/dist/data/displays.js +23 -0
  25. package/dist/data/displays.js.map +1 -0
  26. package/dist/data/index.d.ts +1 -0
  27. package/dist/data/index.js +1 -0
  28. package/dist/data/index.js.map +1 -1
  29. package/dist/data/locale-fallback.d.ts +3 -0
  30. package/dist/data/locale-fallback.js +20 -0
  31. package/dist/data/locale-fallback.js.map +1 -0
  32. package/dist/data/read-only-routes.d.ts +7 -0
  33. package/dist/data/read-only-routes.js +13 -0
  34. package/dist/data/read-only-routes.js.map +1 -0
  35. package/dist/data/sends-table.d.ts +19 -5
  36. package/dist/data/sends-table.js +185 -17
  37. package/dist/data/sends-table.js.map +1 -1
  38. package/dist/data/suppressions-table.d.ts +24 -0
  39. package/dist/data/suppressions-table.js +120 -0
  40. package/dist/data/suppressions-table.js.map +1 -0
  41. package/dist/data/templates-table.d.ts +20 -2
  42. package/dist/data/templates-table.js +188 -31
  43. package/dist/data/templates-table.js.map +1 -1
  44. package/dist/data-types/categories-type.d.ts +11 -13
  45. package/dist/data-types/categories-type.js +3 -3
  46. package/dist/data-types/categories-type.js.map +1 -1
  47. package/dist/data-types/category-type.js +1 -1
  48. package/dist/data-types/category-type.js.map +1 -1
  49. package/dist/db/models/index.d.ts +3 -0
  50. package/dist/db/models/index.js +3 -0
  51. package/dist/db/models/index.js.map +1 -1
  52. package/dist/db/models/sends.model.d.ts +12 -1
  53. package/dist/db/models/sends.model.js +64 -0
  54. package/dist/db/models/sends.model.js.map +1 -1
  55. package/dist/db/models/settings.model.d.ts +0 -1
  56. package/dist/db/models/settings.model.js +0 -7
  57. package/dist/db/models/settings.model.js.map +1 -1
  58. package/dist/db/models/suppressions.model.d.ts +28 -0
  59. package/dist/db/models/suppressions.model.js +46 -0
  60. package/dist/db/models/suppressions.model.js.map +1 -0
  61. package/dist/db/models/template-versions.model.d.ts +24 -0
  62. package/dist/db/models/template-versions.model.js +17 -0
  63. package/dist/db/models/template-versions.model.js.map +1 -0
  64. package/dist/db/models/templates.model.d.ts +2 -0
  65. package/dist/db/models/templates.model.js +3 -0
  66. package/dist/db/models/templates.model.js.map +1 -1
  67. package/dist/db/models/webhook-status.model.d.ts +24 -0
  68. package/dist/db/models/webhook-status.model.js +21 -0
  69. package/dist/db/models/webhook-status.model.js.map +1 -0
  70. package/dist/db/tables/index.d.ts +3 -0
  71. package/dist/db/tables/index.js +3 -0
  72. package/dist/db/tables/index.js.map +1 -1
  73. package/dist/db/tables/send-events.table.d.ts +2 -2
  74. package/dist/db/tables/send-events.table.js.map +1 -1
  75. package/dist/db/tables/sends.table.d.ts +10 -2
  76. package/dist/db/tables/sends.table.js +34 -1
  77. package/dist/db/tables/sends.table.js.map +1 -1
  78. package/dist/db/tables/suppressions.table.d.ts +12 -0
  79. package/dist/db/tables/suppressions.table.js +53 -0
  80. package/dist/db/tables/suppressions.table.js.map +1 -0
  81. package/dist/db/tables/template-versions.table.d.ts +13 -0
  82. package/dist/db/tables/template-versions.table.js +59 -0
  83. package/dist/db/tables/template-versions.table.js.map +1 -0
  84. package/dist/db/tables/templates.table.d.ts +11 -0
  85. package/dist/db/tables/templates.table.js +45 -0
  86. package/dist/db/tables/templates.table.js.map +1 -1
  87. package/dist/db/tables/webhook-status.table.d.ts +14 -0
  88. package/dist/db/tables/webhook-status.table.js +58 -0
  89. package/dist/db/tables/webhook-status.table.js.map +1 -0
  90. package/dist/engine/changes.d.ts +12 -0
  91. package/dist/engine/changes.js +107 -0
  92. package/dist/engine/changes.js.map +1 -0
  93. package/dist/engine/conditions.js +0 -2
  94. package/dist/engine/conditions.js.map +1 -1
  95. package/dist/engine/coverage.d.ts +1 -0
  96. package/dist/engine/coverage.js +6 -2
  97. package/dist/engine/coverage.js.map +1 -1
  98. package/dist/engine/fallback-html.d.ts +1 -1
  99. package/dist/engine/fallback-html.js +51 -34
  100. package/dist/engine/fallback-html.js.map +1 -1
  101. package/dist/engine/index.d.ts +3 -0
  102. package/dist/engine/index.js +3 -0
  103. package/dist/engine/index.js.map +1 -1
  104. package/dist/engine/plain-text.d.ts +3 -0
  105. package/dist/engine/plain-text.js +60 -0
  106. package/dist/engine/plain-text.js.map +1 -0
  107. package/dist/engine/resolve.d.ts +2 -1
  108. package/dist/engine/resolve.js +17 -9
  109. package/dist/engine/resolve.js.map +1 -1
  110. package/dist/engine/system-variables.d.ts +10 -0
  111. package/dist/engine/system-variables.js +70 -0
  112. package/dist/engine/system-variables.js.map +1 -0
  113. package/dist/engine/tokens.d.ts +2 -1
  114. package/dist/engine/tokens.js +6 -2
  115. package/dist/engine/tokens.js.map +1 -1
  116. package/dist/hooks/tenant-export.js +13 -1
  117. package/dist/hooks/tenant-export.js.map +1 -1
  118. package/dist/implementations/dms-mailing/index.d.ts +1 -1
  119. package/dist/implementations/dms-mailing/index.js +2 -2
  120. package/dist/implementations/dms-mailing/index.js.map +1 -1
  121. package/dist/index.js +13 -11
  122. package/dist/index.js.map +1 -1
  123. package/dist/pages/editor.js +9 -2
  124. package/dist/pages/editor.js.map +1 -1
  125. package/dist/pages/index.d.ts +1 -0
  126. package/dist/pages/index.js +1 -0
  127. package/dist/pages/index.js.map +1 -1
  128. package/dist/pages/module.d.ts +2 -0
  129. package/dist/pages/module.js +26 -2
  130. package/dist/pages/module.js.map +1 -1
  131. package/dist/pages/overview.d.ts +2 -6
  132. package/dist/pages/overview.js +109 -52
  133. package/dist/pages/overview.js.map +1 -1
  134. package/dist/pages/sends.d.ts +3 -4
  135. package/dist/pages/sends.js +197 -60
  136. package/dist/pages/sends.js.map +1 -1
  137. package/dist/pages/settings/form.js +114 -53
  138. package/dist/pages/settings/form.js.map +1 -1
  139. package/dist/pages/settings/page.d.ts +0 -1
  140. package/dist/pages/settings/page.js +8 -11
  141. package/dist/pages/settings/page.js.map +1 -1
  142. package/dist/pages/suppressions.d.ts +9 -0
  143. package/dist/pages/suppressions.js +151 -0
  144. package/dist/pages/suppressions.js.map +1 -0
  145. package/dist/pages/templates.d.ts +0 -1
  146. package/dist/pages/templates.js +114 -40
  147. package/dist/pages/templates.js.map +1 -1
  148. package/dist/permissions.js +2 -2
  149. package/dist/permissions.js.map +1 -1
  150. package/dist/quick-actions.js +7 -4
  151. package/dist/quick-actions.js.map +1 -1
  152. package/dist/routes/events.d.ts +8 -5
  153. package/dist/routes/events.js +55 -24
  154. package/dist/routes/events.js.map +1 -1
  155. package/dist/routes/index.d.ts +2 -0
  156. package/dist/routes/index.js +2 -0
  157. package/dist/routes/index.js.map +1 -1
  158. package/dist/routes/metrics.d.ts +60 -23
  159. package/dist/routes/metrics.js +231 -83
  160. package/dist/routes/metrics.js.map +1 -1
  161. package/dist/routes/period.d.ts +2 -1
  162. package/dist/routes/period.js +9 -3
  163. package/dist/routes/period.js.map +1 -1
  164. package/dist/routes/sends.d.ts +49 -8
  165. package/dist/routes/sends.js +239 -24
  166. package/dist/routes/sends.js.map +1 -1
  167. package/dist/routes/settings.d.ts +44 -5
  168. package/dist/routes/settings.js +190 -15
  169. package/dist/routes/settings.js.map +1 -1
  170. package/dist/routes/suppressions.d.ts +16 -0
  171. package/dist/routes/suppressions.js +92 -0
  172. package/dist/routes/suppressions.js.map +1 -0
  173. package/dist/routes/template-versions.d.ts +28 -0
  174. package/dist/routes/template-versions.js +87 -0
  175. package/dist/routes/template-versions.js.map +1 -0
  176. package/dist/routes/templates.d.ts +60 -6
  177. package/dist/routes/templates.js +204 -35
  178. package/dist/routes/templates.js.map +1 -1
  179. package/dist/services/attention.d.ts +19 -6
  180. package/dist/services/attention.js +177 -12
  181. package/dist/services/attention.js.map +1 -1
  182. package/dist/services/catalog.d.ts +3 -0
  183. package/dist/services/catalog.js +25 -0
  184. package/dist/services/catalog.js.map +1 -0
  185. package/dist/services/categories.d.ts +8 -32
  186. package/dist/services/categories.js +14 -25
  187. package/dist/services/categories.js.map +1 -1
  188. package/dist/services/checked-send.d.ts +9 -0
  189. package/dist/services/checked-send.js +54 -0
  190. package/dist/services/checked-send.js.map +1 -0
  191. package/dist/services/events.d.ts +5 -0
  192. package/dist/services/events.js +102 -9
  193. package/dist/services/events.js.map +1 -1
  194. package/dist/services/metrics.d.ts +17 -6
  195. package/dist/services/metrics.js +57 -18
  196. package/dist/services/metrics.js.map +1 -1
  197. package/dist/services/notify.d.ts +18 -10
  198. package/dist/services/notify.js +82 -37
  199. package/dist/services/notify.js.map +1 -1
  200. package/dist/services/provider-banner.d.ts +14 -0
  201. package/dist/services/provider-banner.js +115 -0
  202. package/dist/services/provider-banner.js.map +1 -0
  203. package/dist/services/render.js.map +1 -1
  204. package/dist/services/send-stat-items.d.ts +7 -0
  205. package/dist/services/send-stat-items.js +127 -0
  206. package/dist/services/send-stat-items.js.map +1 -0
  207. package/dist/services/send-stats.d.ts +23 -0
  208. package/dist/services/send-stats.js +63 -0
  209. package/dist/services/send-stats.js.map +1 -0
  210. package/dist/services/send.d.ts +18 -3
  211. package/dist/services/send.js +81 -17
  212. package/dist/services/send.js.map +1 -1
  213. package/dist/services/settings.d.ts +5 -0
  214. package/dist/services/settings.js +28 -7
  215. package/dist/services/settings.js.map +1 -1
  216. package/dist/services/starters.d.ts +15 -0
  217. package/dist/services/starters.js +264 -0
  218. package/dist/services/starters.js.map +1 -0
  219. package/dist/services/suppressions.d.ts +20 -0
  220. package/dist/services/suppressions.js +53 -0
  221. package/dist/services/suppressions.js.map +1 -0
  222. package/dist/services/template-attention.d.ts +4 -0
  223. package/dist/services/template-attention.js +43 -0
  224. package/dist/services/template-attention.js.map +1 -0
  225. package/dist/services/template-stats.d.ts +27 -0
  226. package/dist/services/template-stats.js +59 -0
  227. package/dist/services/template-stats.js.map +1 -0
  228. package/dist/services/templates.d.ts +31 -4
  229. package/dist/services/templates.js +240 -21
  230. package/dist/services/templates.js.map +1 -1
  231. package/dist/services/webhook-status.d.ts +27 -0
  232. package/dist/services/webhook-status.js +134 -0
  233. package/dist/services/webhook-status.js.map +1 -0
  234. package/dist/types/blocks.d.ts +1 -0
  235. package/dist/types/index.d.ts +2 -0
  236. package/dist/types/index.js +2 -0
  237. package/dist/types/index.js.map +1 -1
  238. package/dist/types/sends.d.ts +10 -1
  239. package/dist/types/sends.js +35 -2
  240. package/dist/types/sends.js.map +1 -1
  241. package/dist/types/suppressions.d.ts +9 -0
  242. package/dist/types/suppressions.js +10 -0
  243. package/dist/types/suppressions.js.map +1 -0
  244. package/dist/types/webhook.d.ts +17 -0
  245. package/dist/types/webhook.js +3 -0
  246. package/dist/types/webhook.js.map +1 -0
  247. package/dist/validation/settings.schema.d.ts +144 -9
  248. package/dist/validation/settings.schema.js +113 -16
  249. package/dist/validation/settings.schema.js.map +1 -1
  250. package/dist/validation/templates.schema.d.ts +137 -10
  251. package/dist/validation/templates.schema.js +28 -2
  252. package/dist/validation/templates.schema.js.map +1 -1
  253. package/frontend-vue/app/build/components/FormFieldRow.vue +90 -0
  254. package/frontend-vue/app/build/components/FormRows.vue +26 -0
  255. package/frontend-vue/app/build/components/RequiredLegend.vue +11 -0
  256. package/frontend-vue/app/components/CategoriesInput.vue +323 -77
  257. package/frontend-vue/app/components/CategoryInput.vue +13 -14
  258. package/frontend-vue/app/components/CategoryLabel.vue +34 -0
  259. package/frontend-vue/app/components/DuplicateModal.vue +201 -0
  260. package/frontend-vue/app/components/Editor.vue +55 -20
  261. package/frontend-vue/app/components/FixAddressModal.vue +133 -0
  262. package/frontend-vue/app/components/FunnelCard.vue +146 -43
  263. package/frontend-vue/app/components/LocaleSelect.vue +98 -0
  264. package/frontend-vue/app/components/NewTemplateModal.vue +388 -102
  265. package/frontend-vue/app/components/NewTemplateSourceTile.vue +70 -0
  266. package/frontend-vue/app/components/OverviewGate.vue +220 -0
  267. package/frontend-vue/app/components/PageHeader.vue +98 -0
  268. package/frontend-vue/app/components/RealSendModal.vue +555 -0
  269. package/frontend-vue/app/components/RecipientsField.vue +41 -0
  270. package/frontend-vue/app/components/RenderingModal.vue +189 -0
  271. package/frontend-vue/app/components/RetentionInput.vue +212 -0
  272. package/frontend-vue/app/components/SendDrawer.vue +542 -209
  273. package/frontend-vue/app/components/StarterGallery.vue +153 -0
  274. package/frontend-vue/app/components/SystemVariables.vue +63 -0
  275. package/frontend-vue/app/components/TemplateCard.vue +218 -57
  276. package/frontend-vue/app/components/TemplateDrawer.vue +785 -274
  277. package/frontend-vue/app/components/TemplateGallery.vue +312 -56
  278. package/frontend-vue/app/components/TemplatePreviewFrame.vue +112 -22
  279. package/frontend-vue/app/components/TestSendModal.vue +266 -95
  280. package/frontend-vue/app/components/TokenText.vue +61 -10
  281. package/frontend-vue/app/components/VariablePath.vue +20 -0
  282. package/frontend-vue/app/components/VersionHistory.vue +196 -0
  283. package/frontend-vue/app/components/VersionPreview.vue +164 -0
  284. package/frontend-vue/app/components/WebhookStatus.vue +130 -0
  285. package/frontend-vue/app/components/blocks/Button.vue +1 -1
  286. package/frontend-vue/app/components/blocks/Code.vue +1 -1
  287. package/frontend-vue/app/components/blocks/Footer.vue +56 -7
  288. package/frontend-vue/app/components/blocks/Heading.vue +1 -1
  289. package/frontend-vue/app/components/blocks/Hero.vue +13 -7
  290. package/frontend-vue/app/components/blocks/List.vue +50 -6
  291. package/frontend-vue/app/components/blocks/Paragraph.vue +1 -1
  292. package/frontend-vue/app/components/blocks/Total.vue +3 -3
  293. package/frontend-vue/app/components/editor/EditorAutocompleteMenu.vue +50 -0
  294. package/frontend-vue/app/components/editor/EditorBlock.vue +37 -90
  295. package/frontend-vue/app/components/editor/EditorBlockFields.vue +140 -0
  296. package/frontend-vue/app/components/editor/EditorBlockInspector.vue +92 -0
  297. package/frontend-vue/app/components/editor/EditorBlockList.vue +32 -25
  298. package/frontend-vue/app/components/editor/EditorBlockPicker.vue +58 -0
  299. package/frontend-vue/app/components/editor/EditorBlockTools.vue +71 -0
  300. package/frontend-vue/app/components/editor/EditorCanvas.vue +105 -70
  301. package/frontend-vue/app/components/editor/EditorCompare.vue +211 -0
  302. package/frontend-vue/app/components/editor/EditorConditionFields.vue +56 -46
  303. package/frontend-vue/app/components/editor/EditorConditionInspector.vue +54 -0
  304. package/frontend-vue/app/components/editor/EditorEnvelope.vue +77 -0
  305. package/frontend-vue/app/components/editor/EditorIfBlock.vue +36 -86
  306. package/frontend-vue/app/components/editor/EditorInsertPoint.vue +64 -0
  307. package/frontend-vue/app/components/editor/EditorInspector.vue +80 -0
  308. package/frontend-vue/app/components/editor/EditorLeftPane.vue +103 -0
  309. package/frontend-vue/app/components/editor/EditorLifecycle.vue +67 -0
  310. package/frontend-vue/app/components/editor/EditorLoading.vue +23 -0
  311. package/frontend-vue/app/components/editor/EditorLocaleSwitch.vue +61 -0
  312. package/frontend-vue/app/components/editor/EditorMissingLocale.vue +72 -48
  313. package/frontend-vue/app/components/editor/EditorMissingPolicy.vue +42 -0
  314. package/frontend-vue/app/components/editor/EditorNotice.vue +127 -0
  315. package/frontend-vue/app/components/editor/EditorOutline.vue +65 -0
  316. package/frontend-vue/app/components/editor/EditorPalette.vue +95 -42
  317. package/frontend-vue/app/components/editor/EditorPaletteGroup.vue +66 -0
  318. package/frontend-vue/app/components/editor/EditorPublishReview.vue +102 -0
  319. package/frontend-vue/app/components/editor/EditorSaveState.vue +88 -0
  320. package/frontend-vue/app/components/editor/EditorShortcutHint.vue +17 -0
  321. package/frontend-vue/app/components/editor/EditorSimChip.vue +51 -0
  322. package/frontend-vue/app/components/editor/EditorTemplatePane.vue +131 -0
  323. package/frontend-vue/app/components/editor/EditorTestDataPane.vue +98 -0
  324. package/frontend-vue/app/components/editor/EditorTitle.vue +82 -0
  325. package/frontend-vue/app/components/editor/EditorTokenField.vue +224 -0
  326. package/frontend-vue/app/components/editor/EditorToolbar.vue +34 -134
  327. package/frontend-vue/app/components/editor/EditorVariableForm.vue +100 -0
  328. package/frontend-vue/app/components/editor/EditorVariableRow.vue +87 -0
  329. package/frontend-vue/app/components/editor/EditorVariablesPane.vue +161 -0
  330. package/frontend-vue/app/components/editor/EditorViewSwitches.vue +76 -0
  331. package/frontend-vue/app/components/editor/EditorVisibility.vue +101 -0
  332. package/frontend-vue/app/components/editor/EditorWorkspace.vue +62 -149
  333. package/frontend-vue/app/composables/useBlockContext.ts +16 -0
  334. package/frontend-vue/app/composables/useEditorActions.ts +381 -0
  335. package/frontend-vue/app/composables/useEditorAnnouncements.ts +51 -0
  336. package/frontend-vue/app/composables/useEditorContext.ts +30 -23
  337. package/frontend-vue/app/composables/useEditorSession.ts +299 -0
  338. package/frontend-vue/app/composables/useEditorShortcuts.ts +81 -0
  339. package/frontend-vue/app/composables/useLocalizedDefaults.ts +17 -0
  340. package/frontend-vue/app/composables/useMailingApi.ts +142 -27
  341. package/frontend-vue/app/composables/useOverviewSetup.ts +13 -0
  342. package/frontend-vue/app/composables/useReplayToast.ts +72 -0
  343. package/frontend-vue/app/composables/useTemplateCategories.ts +35 -0
  344. package/frontend-vue/app/composables/useTemplateDrawer.ts +196 -62
  345. package/frontend-vue/app/composables/useTemplateEditor.ts +318 -86
  346. package/frontend-vue/app/emails/EmailMailingTemplate.vue +16 -4
  347. package/frontend-vue/app/emails/components/MailingBlocks.vue +96 -38
  348. package/frontend-vue/app/emails/components/MailingTextLines.vue +20 -0
  349. package/frontend-vue/app/plugins/column-displays.ts +65 -0
  350. package/frontend-vue/app/plugins/{template-gallery-display.client.ts → template-gallery-display.ts} +1 -1
  351. package/frontend-vue/app/types/mailing.ts +193 -18
  352. package/frontend-vue/app/utils/api-error.ts +33 -0
  353. package/frontend-vue/app/utils/autocomplete.ts +120 -0
  354. package/frontend-vue/app/utils/blocks/components.ts +10 -10
  355. package/frontend-vue/app/utils/blocks/dnd.ts +7 -3
  356. package/frontend-vue/app/utils/blocks/factory.ts +199 -38
  357. package/frontend-vue/app/utils/blocks/fields.ts +39 -19
  358. package/frontend-vue/app/utils/blocks/index.ts +1 -0
  359. package/frontend-vue/app/utils/blocks/outline.ts +91 -0
  360. package/frontend-vue/app/utils/blocks/tree.ts +114 -2
  361. package/frontend-vue/app/utils/categories.ts +201 -15
  362. package/frontend-vue/app/utils/changes.ts +152 -0
  363. package/frontend-vue/app/utils/conditions.ts +25 -19
  364. package/frontend-vue/app/utils/editor-checks.ts +266 -0
  365. package/frontend-vue/app/utils/editor-route.ts +43 -4
  366. package/frontend-vue/app/utils/funnel.ts +74 -0
  367. package/frontend-vue/app/utils/gallery.ts +179 -1
  368. package/frontend-vue/app/utils/history.ts +66 -0
  369. package/frontend-vue/app/utils/lifecycle.ts +185 -0
  370. package/frontend-vue/app/utils/locales.ts +107 -2
  371. package/frontend-vue/app/utils/overview-setup.ts +83 -0
  372. package/frontend-vue/app/utils/pages.ts +5 -0
  373. package/frontend-vue/app/utils/paths.ts +51 -15
  374. package/frontend-vue/app/utils/period.ts +62 -22
  375. package/frontend-vue/app/utils/problems.ts +181 -0
  376. package/frontend-vue/app/utils/recipients.ts +49 -0
  377. package/frontend-vue/app/utils/rendering-title.ts +23 -0
  378. package/frontend-vue/app/utils/send-actions.ts +207 -0
  379. package/frontend-vue/app/utils/send-link.ts +28 -0
  380. package/frontend-vue/app/utils/send-snippet.ts +97 -0
  381. package/frontend-vue/app/utils/send-stats.ts +24 -0
  382. package/frontend-vue/app/utils/send-values.ts +156 -0
  383. package/frontend-vue/app/utils/shortcuts.ts +113 -0
  384. package/frontend-vue/app/utils/slug.ts +82 -0
  385. package/frontend-vue/app/utils/starters.ts +23 -0
  386. package/frontend-vue/app/utils/suppression.ts +112 -0
  387. package/frontend-vue/app/utils/system-variables.ts +165 -0
  388. package/frontend-vue/app/utils/template-attention.ts +104 -0
  389. package/frontend-vue/app/utils/timeline.ts +221 -19
  390. package/frontend-vue/app/utils/tokens.ts +115 -13
  391. package/frontend-vue/app/utils/variable-types.ts +23 -0
  392. package/frontend-vue/app/utils/variables.ts +208 -1
  393. package/frontend-vue/app/utils/version-history.ts +53 -0
  394. package/frontend-vue/app/utils/webhook-status.ts +170 -0
  395. package/frontend-vue/dms.frontend.ts +19 -11
  396. package/frontend-vue/i18n/locales/mailing-en-GB.json +1173 -161
  397. package/frontend-vue/i18n/locales/mailing-fr-FR.json +1220 -208
  398. package/frontend-vue/package.json +2 -2
  399. package/frontend-vue/pnpm-lock.yaml +46 -161
  400. package/frontend-vue/tests/api-error.test.ts +27 -0
  401. package/frontend-vue/tests/autocomplete.test.ts +84 -0
  402. package/frontend-vue/tests/block-components.test.ts +1 -1
  403. package/frontend-vue/tests/block-fields.test.ts +12 -2
  404. package/frontend-vue/tests/blocks-factory.test.ts +46 -0
  405. package/frontend-vue/tests/blocks-outline.test.ts +102 -0
  406. package/frontend-vue/tests/blocks-tree.test.ts +28 -1
  407. package/frontend-vue/tests/categories-input.test.ts +139 -0
  408. package/frontend-vue/tests/categories.test.ts +148 -15
  409. package/frontend-vue/tests/changes.test.ts +136 -0
  410. package/frontend-vue/tests/column-displays.test.ts +73 -0
  411. package/frontend-vue/tests/conditions.test.ts +49 -19
  412. package/frontend-vue/tests/dnd.test.ts +10 -0
  413. package/frontend-vue/tests/editor-block-list.test.ts +24 -2
  414. package/frontend-vue/tests/editor-checks.test.ts +178 -0
  415. package/frontend-vue/tests/editor-route.test.ts +37 -9
  416. package/frontend-vue/tests/editor-session.test.ts +206 -0
  417. package/frontend-vue/tests/footer-block.test.ts +88 -0
  418. package/frontend-vue/tests/funnel-card.test.ts +116 -0
  419. package/frontend-vue/tests/funnel.test.ts +81 -0
  420. package/frontend-vue/tests/gallery.test.ts +124 -2
  421. package/frontend-vue/tests/helpers/nuxt-globals.ts +232 -0
  422. package/frontend-vue/tests/history.test.ts +48 -0
  423. package/frontend-vue/tests/i18n-editor.test.ts +38 -0
  424. package/frontend-vue/tests/i18n.test.ts +55 -0
  425. package/frontend-vue/tests/lifecycle.test.ts +145 -0
  426. package/frontend-vue/tests/locales.test.ts +60 -0
  427. package/frontend-vue/tests/localized-defaults.test.ts +54 -0
  428. package/frontend-vue/tests/new-template-modal.test.ts +210 -0
  429. package/frontend-vue/tests/overview-gate.test.ts +146 -0
  430. package/frontend-vue/tests/overview-setup.test.ts +78 -0
  431. package/frontend-vue/tests/paths.test.ts +23 -0
  432. package/frontend-vue/tests/period.test.ts +59 -10
  433. package/frontend-vue/tests/problems.test.ts +153 -0
  434. package/frontend-vue/tests/recipients.test.ts +47 -0
  435. package/frontend-vue/tests/rendering-title.test.ts +24 -0
  436. package/frontend-vue/tests/replay-toast.test.ts +67 -0
  437. package/frontend-vue/tests/retention-input.test.ts +129 -0
  438. package/frontend-vue/tests/send-actions.test.ts +128 -0
  439. package/frontend-vue/tests/send-modals.test.ts +415 -0
  440. package/frontend-vue/tests/send-snippet.test.ts +51 -0
  441. package/frontend-vue/tests/send-stats.test.ts +12 -0
  442. package/frontend-vue/tests/send-values.test.ts +119 -0
  443. package/frontend-vue/tests/shortcuts.test.ts +75 -0
  444. package/frontend-vue/tests/slug.test.ts +88 -0
  445. package/frontend-vue/tests/suppression.test.ts +159 -0
  446. package/frontend-vue/tests/system-variables.test.ts +133 -0
  447. package/frontend-vue/tests/template-attention.test.ts +123 -0
  448. package/frontend-vue/tests/template-drawer.test.ts +449 -0
  449. package/frontend-vue/tests/template-editor.test.ts +218 -9
  450. package/frontend-vue/tests/template-gallery-display.test.ts +32 -0
  451. package/frontend-vue/tests/template-gallery.test.ts +370 -0
  452. package/frontend-vue/tests/template-overlays.test.ts +125 -0
  453. package/frontend-vue/tests/timeline.test.ts +175 -20
  454. package/frontend-vue/tests/tokens.test.ts +79 -1
  455. package/frontend-vue/tests/variable-path.test.ts +22 -0
  456. package/frontend-vue/tests/variable-types.test.ts +20 -0
  457. package/frontend-vue/tests/variables.test.ts +90 -2
  458. package/frontend-vue/tests/version-history.test.ts +41 -0
  459. package/frontend-vue/tests/webhook-status.test.ts +108 -0
  460. package/package.json +11 -9
  461. package/src/automation/email-event-trigger.ts +3 -3
  462. package/src/automation/send-template-action.ts +7 -5
  463. package/src/config.ts +3 -1
  464. package/src/constants.ts +18 -2
  465. package/src/crons/attention.ts +21 -0
  466. package/src/crons/index.ts +2 -0
  467. package/src/crons/jobs.ts +35 -0
  468. package/src/crons/retention.ts +7 -22
  469. package/src/data/displays.ts +18 -0
  470. package/src/data/index.ts +1 -0
  471. package/src/data/locale-fallback.ts +26 -0
  472. package/src/data/read-only-routes.ts +14 -0
  473. package/src/data/sends-table.ts +204 -19
  474. package/src/data/suppressions-table.ts +127 -0
  475. package/src/data/templates-table.ts +295 -63
  476. package/src/data-types/categories-type.ts +4 -5
  477. package/src/data-types/category-type.ts +1 -1
  478. package/src/db/models/index.ts +3 -0
  479. package/src/db/models/sends.model.ts +102 -1
  480. package/src/db/models/settings.model.ts +2 -12
  481. package/src/db/models/suppressions.model.ts +53 -0
  482. package/src/db/models/template-versions.model.ts +23 -0
  483. package/src/db/models/templates.model.ts +17 -0
  484. package/src/db/models/webhook-status.model.ts +25 -0
  485. package/src/db/tables/index.ts +3 -0
  486. package/src/db/tables/send-events.table.ts +6 -2
  487. package/src/db/tables/sends.table.ts +37 -2
  488. package/src/db/tables/suppressions.table.ts +46 -0
  489. package/src/db/tables/template-versions.table.ts +44 -0
  490. package/src/db/tables/templates.table.ts +67 -5
  491. package/src/db/tables/webhook-status.table.ts +44 -0
  492. package/src/engine/changes.ts +198 -0
  493. package/src/engine/conditions.ts +1 -7
  494. package/src/engine/coverage.ts +7 -2
  495. package/src/engine/fallback-html.ts +80 -42
  496. package/src/engine/index.ts +3 -0
  497. package/src/engine/plain-text.ts +86 -0
  498. package/src/engine/resolve.ts +49 -15
  499. package/src/engine/system-variables.ts +124 -0
  500. package/src/engine/tokens.ts +13 -1
  501. package/src/hooks/tenant-export.ts +21 -2
  502. package/src/implementations/dms-mailing/index.ts +1 -1
  503. package/src/index.ts +29 -13
  504. package/src/pages/editor.ts +13 -2
  505. package/src/pages/index.ts +6 -3
  506. package/src/pages/module.ts +31 -2
  507. package/src/pages/overview.ts +187 -128
  508. package/src/pages/sends.ts +271 -112
  509. package/src/pages/settings/form.ts +144 -54
  510. package/src/pages/settings/page.ts +9 -17
  511. package/src/pages/suppressions.ts +157 -0
  512. package/src/pages/templates.ts +139 -42
  513. package/src/permissions.ts +6 -4
  514. package/src/quick-actions.ts +12 -5
  515. package/src/routes/events.ts +110 -33
  516. package/src/routes/index.ts +2 -0
  517. package/src/routes/metrics.ts +348 -98
  518. package/src/routes/period.ts +23 -6
  519. package/src/routes/sends.ts +413 -41
  520. package/src/routes/settings.ts +291 -26
  521. package/src/routes/suppressions.ts +87 -0
  522. package/src/routes/template-versions.ts +116 -0
  523. package/src/routes/templates.ts +334 -48
  524. package/src/services/attention.ts +283 -30
  525. package/src/services/catalog.ts +28 -0
  526. package/src/services/categories.ts +28 -27
  527. package/src/services/checked-send.ts +123 -0
  528. package/src/services/events.ts +174 -20
  529. package/src/services/metrics.ts +137 -28
  530. package/src/services/notify.ts +126 -44
  531. package/src/services/provider-banner.ts +165 -0
  532. package/src/services/render.ts +1 -0
  533. package/src/services/send-stat-items.ts +160 -0
  534. package/src/services/send-stats.ts +122 -0
  535. package/src/services/send.ts +193 -31
  536. package/src/services/settings.ts +52 -8
  537. package/src/services/starters.ts +383 -0
  538. package/src/services/suppressions.ts +116 -0
  539. package/src/services/template-attention.ts +82 -0
  540. package/src/services/template-stats.ts +97 -0
  541. package/src/services/templates.ts +400 -37
  542. package/src/services/webhook-status.ts +202 -0
  543. package/src/types/blocks.ts +6 -0
  544. package/src/types/index.ts +2 -0
  545. package/src/types/sends.ts +64 -2
  546. package/src/types/suppressions.ts +27 -0
  547. package/src/types/webhook.ts +29 -0
  548. package/src/validation/settings.schema.ts +173 -17
  549. package/src/validation/templates.schema.ts +53 -1
  550. package/dist/antelope-modules.d.ts +0 -32
  551. package/dist/antelope-modules.js +0 -36
  552. package/dist/antelope-modules.js.map +0 -1
  553. package/dist/test/antelope.test.d.ts +0 -2
  554. package/dist/test/antelope.test.js +0 -180
  555. package/dist/test/antelope.test.js.map +0 -1
  556. package/dist/test/helpers/http.d.ts +0 -4
  557. package/dist/test/helpers/http.js +0 -28
  558. package/dist/test/helpers/http.js.map +0 -1
  559. package/dist/test/helpers/owner.d.ts +0 -5
  560. package/dist/test/helpers/owner.js +0 -30
  561. package/dist/test/helpers/owner.js.map +0 -1
  562. package/dist/test/integration/events.test.d.ts +0 -1
  563. package/dist/test/integration/events.test.js +0 -143
  564. package/dist/test/integration/events.test.js.map +0 -1
  565. package/dist/test/integration/retention.test.d.ts +0 -1
  566. package/dist/test/integration/retention.test.js +0 -291
  567. package/dist/test/integration/retention.test.js.map +0 -1
  568. package/dist/test/integration/sends.test.d.ts +0 -1
  569. package/dist/test/integration/sends.test.js +0 -210
  570. package/dist/test/integration/sends.test.js.map +0 -1
  571. package/dist/test/integration/settings.test.d.ts +0 -1
  572. package/dist/test/integration/settings.test.js +0 -137
  573. package/dist/test/integration/settings.test.js.map +0 -1
  574. package/dist/test/integration/setup.test.d.ts +0 -1
  575. package/dist/test/integration/setup.test.js +0 -32
  576. package/dist/test/integration/setup.test.js.map +0 -1
  577. package/dist/test/integration/templates.test.d.ts +0 -1
  578. package/dist/test/integration/templates.test.js +0 -233
  579. package/dist/test/integration/templates.test.js.map +0 -1
  580. package/dist/test/integration/tenant-export.test.d.ts +0 -1
  581. package/dist/test/integration/tenant-export.test.js +0 -85
  582. package/dist/test/integration/tenant-export.test.js.map +0 -1
  583. package/dist/test/unit/attention.test.d.ts +0 -1
  584. package/dist/test/unit/attention.test.js +0 -109
  585. package/dist/test/unit/attention.test.js.map +0 -1
  586. package/dist/test/unit/automation.test.d.ts +0 -1
  587. package/dist/test/unit/automation.test.js +0 -104
  588. package/dist/test/unit/automation.test.js.map +0 -1
  589. package/dist/test/unit/categories.test.d.ts +0 -1
  590. package/dist/test/unit/categories.test.js +0 -38
  591. package/dist/test/unit/categories.test.js.map +0 -1
  592. package/dist/test/unit/engine/conditions.test.d.ts +0 -1
  593. package/dist/test/unit/engine/conditions.test.js +0 -23
  594. package/dist/test/unit/engine/conditions.test.js.map +0 -1
  595. package/dist/test/unit/engine/coverage.test.d.ts +0 -1
  596. package/dist/test/unit/engine/coverage.test.js +0 -106
  597. package/dist/test/unit/engine/coverage.test.js.map +0 -1
  598. package/dist/test/unit/engine/fallback-html.test.d.ts +0 -1
  599. package/dist/test/unit/engine/fallback-html.test.js +0 -78
  600. package/dist/test/unit/engine/fallback-html.test.js.map +0 -1
  601. package/dist/test/unit/engine/paths.test.d.ts +0 -1
  602. package/dist/test/unit/engine/paths.test.js +0 -33
  603. package/dist/test/unit/engine/paths.test.js.map +0 -1
  604. package/dist/test/unit/engine/resolve.test.d.ts +0 -1
  605. package/dist/test/unit/engine/resolve.test.js +0 -157
  606. package/dist/test/unit/engine/resolve.test.js.map +0 -1
  607. package/dist/test/unit/engine/tokens.test.d.ts +0 -1
  608. package/dist/test/unit/engine/tokens.test.js +0 -26
  609. package/dist/test/unit/engine/tokens.test.js.map +0 -1
  610. package/dist/test/unit/events.test.d.ts +0 -1
  611. package/dist/test/unit/events.test.js +0 -13
  612. package/dist/test/unit/events.test.js.map +0 -1
  613. package/dist/test/unit/metrics.test.d.ts +0 -1
  614. package/dist/test/unit/metrics.test.js +0 -108
  615. package/dist/test/unit/metrics.test.js.map +0 -1
  616. package/dist/test/unit/notify.test.d.ts +0 -1
  617. package/dist/test/unit/notify.test.js +0 -49
  618. package/dist/test/unit/notify.test.js.map +0 -1
  619. package/dist/test/unit/period.test.d.ts +0 -1
  620. package/dist/test/unit/period.test.js +0 -61
  621. package/dist/test/unit/period.test.js.map +0 -1
  622. package/dist/test/unit/permissions.test.d.ts +0 -1
  623. package/dist/test/unit/permissions.test.js +0 -27
  624. package/dist/test/unit/permissions.test.js.map +0 -1
  625. package/dist/test/unit/provider.test.d.ts +0 -1
  626. package/dist/test/unit/provider.test.js +0 -47
  627. package/dist/test/unit/provider.test.js.map +0 -1
  628. package/dist/test/unit/realtime.test.d.ts +0 -1
  629. package/dist/test/unit/realtime.test.js +0 -13
  630. package/dist/test/unit/realtime.test.js.map +0 -1
  631. package/dist/test/unit/render.test.d.ts +0 -1
  632. package/dist/test/unit/render.test.js +0 -23
  633. package/dist/test/unit/render.test.js.map +0 -1
  634. package/dist/test/unit/retention.test.d.ts +0 -1
  635. package/dist/test/unit/retention.test.js +0 -66
  636. package/dist/test/unit/retention.test.js.map +0 -1
  637. package/dist/test/unit/send-batch.test.d.ts +0 -1
  638. package/dist/test/unit/send-batch.test.js +0 -46
  639. package/dist/test/unit/send-batch.test.js.map +0 -1
  640. package/dist/test/unit/send.test.d.ts +0 -1
  641. package/dist/test/unit/send.test.js +0 -120
  642. package/dist/test/unit/send.test.js.map +0 -1
  643. package/dist/test/unit/smoke.test.d.ts +0 -1
  644. package/dist/test/unit/smoke.test.js +0 -10
  645. package/dist/test/unit/smoke.test.js.map +0 -1
  646. package/dist/test/unit/status.test.d.ts +0 -1
  647. package/dist/test/unit/status.test.js +0 -16
  648. package/dist/test/unit/status.test.js.map +0 -1
  649. package/dist/test/unit/templates.test.d.ts +0 -1
  650. package/dist/test/unit/templates.test.js +0 -48
  651. package/dist/test/unit/templates.test.js.map +0 -1
  652. package/dist/test/unit/tenant-export.test.d.ts +0 -1
  653. package/dist/test/unit/tenant-export.test.js +0 -59
  654. package/dist/test/unit/tenant-export.test.js.map +0 -1
  655. package/dist/test/unit/webhook-secret.test.d.ts +0 -1
  656. package/dist/test/unit/webhook-secret.test.js +0 -43
  657. package/dist/test/unit/webhook-secret.test.js.map +0 -1
  658. package/frontend-vue/app/components/AttentionCard.vue +0 -78
  659. package/frontend-vue/app/components/HtmlPreviewModal.vue +0 -20
  660. package/frontend-vue/app/components/ProviderChip.vue +0 -39
  661. package/frontend-vue/app/components/editor/EditorBlockSettings.vue +0 -186
  662. package/frontend-vue/app/components/editor/EditorDataPanel.vue +0 -130
  663. package/frontend-vue/app/components/editor/EditorDraftBar.vue +0 -35
  664. package/frontend-vue/app/components/editor/EditorRail.vue +0 -58
  665. package/frontend-vue/app/components/editor/EditorSubjectSettings.vue +0 -37
  666. package/frontend-vue/app/components/editor/EditorTemplateSettings.vue +0 -87
  667. package/frontend-vue/app/components/editor/EditorTestSendModal.vue +0 -66
  668. package/frontend-vue/app/components/editor/EditorVariables.vue +0 -163
  669. package/frontend-vue/app/composables/useMailingPeriodData.ts +0 -39
  670. package/src/antelope-modules.ts +0 -48
  671. package/src/test/antelope.test.ts +0 -190
  672. package/src/test/helpers/http.ts +0 -23
  673. package/src/test/helpers/owner.ts +0 -37
  674. package/src/test/integration/events.test.ts +0 -159
  675. package/src/test/integration/retention.test.ts +0 -363
  676. package/src/test/integration/sends.test.ts +0 -233
  677. package/src/test/integration/settings.test.ts +0 -128
  678. package/src/test/integration/setup.test.ts +0 -35
  679. package/src/test/integration/templates.test.ts +0 -272
  680. package/src/test/integration/tenant-export.test.ts +0 -125
  681. package/src/test/unit/attention.test.ts +0 -129
  682. package/src/test/unit/automation.test.ts +0 -130
  683. package/src/test/unit/categories.test.ts +0 -51
  684. package/src/test/unit/engine/conditions.test.ts +0 -35
  685. package/src/test/unit/engine/coverage.test.ts +0 -118
  686. package/src/test/unit/engine/fallback-html.test.ts +0 -86
  687. package/src/test/unit/engine/paths.test.ts +0 -39
  688. package/src/test/unit/engine/resolve.test.ts +0 -176
  689. package/src/test/unit/engine/tokens.test.ts +0 -26
  690. package/src/test/unit/events.test.ts +0 -19
  691. package/src/test/unit/metrics.test.ts +0 -170
  692. package/src/test/unit/notify.test.ts +0 -71
  693. package/src/test/unit/period.test.ts +0 -91
  694. package/src/test/unit/permissions.test.ts +0 -30
  695. package/src/test/unit/provider.test.ts +0 -57
  696. package/src/test/unit/realtime.test.ts +0 -11
  697. package/src/test/unit/render.test.ts +0 -23
  698. package/src/test/unit/retention.test.ts +0 -91
  699. package/src/test/unit/send-batch.test.ts +0 -72
  700. package/src/test/unit/send.test.ts +0 -161
  701. package/src/test/unit/smoke.test.ts +0 -8
  702. package/src/test/unit/status.test.ts +0 -14
  703. package/src/test/unit/templates.test.ts +0 -52
  704. package/src/test/unit/tenant-export.test.ts +0 -88
  705. package/src/test/unit/webhook-secret.test.ts +0 -51
@@ -1,6 +1,11 @@
1
- import type { Block, IfBlock } from '../../types/mailing'
1
+ import type {
2
+ Block,
3
+ BlockType,
4
+ FooterBlock,
5
+ IfBlock,
6
+ } from '../../types/mailing'
2
7
  import { deepClone } from '../clone'
3
- import { newBlockId } from './factory'
8
+ import { footerLabelsFor, newBlockId } from './factory'
4
9
 
5
10
  const isIf = (block: Block): block is IfBlock => block.type === 'if'
6
11
 
@@ -72,3 +77,110 @@ export function removeBlock(blocks: Block[], id: string): boolean {
72
77
  export function insertBlock(list: Block[], index: number, block: Block): void {
73
78
  list.splice(index, 0, block)
74
79
  }
80
+
81
+ /** Every block of the tree, depth first, branches in place. */
82
+ export function flattenBlocks(blocks: Block[]): Block[] {
83
+ return blocks.flatMap((block) => [
84
+ block,
85
+ ...branchesOf(block).flatMap(flattenBlocks),
86
+ ])
87
+ }
88
+
89
+ /** Where a block sits in the whole tree, 1-based ("block 3 of 12"). */
90
+ export interface BlockPosition {
91
+ index: number
92
+ total: number
93
+ }
94
+
95
+ export function blockPosition(
96
+ blocks: Block[],
97
+ id: string,
98
+ ): BlockPosition | null {
99
+ const flat = flattenBlocks(blocks)
100
+ const index = flat.findIndex((block) => block.id === id)
101
+ return index < 0 ? null : { index: index + 1, total: flat.length }
102
+ }
103
+
104
+ /** A list of the tree and the index a new block goes to in it. */
105
+ export interface InsertionPoint {
106
+ list: Block[]
107
+ index: number
108
+ }
109
+
110
+ /** The block type that closes an e-mail: new blocks go above it. */
111
+ const CLOSING_TYPE: BlockType = 'footer'
112
+
113
+ function endOfBody(blocks: Block[], type?: BlockType): number {
114
+ const isClosed = blocks.at(-1)?.type === CLOSING_TYPE
115
+ return isClosed && type !== CLOSING_TYPE ? blocks.length - 1 : blocks.length
116
+ }
117
+
118
+ /**
119
+ * Where a block added "under the selected one" lands: right after it in its
120
+ * own list or, when nothing is selected, at the end of the root list but
121
+ * above the closing footer (a new footer still goes last).
122
+ */
123
+ export function insertionAfter(
124
+ blocks: Block[],
125
+ id: string | null,
126
+ type?: BlockType,
127
+ ): InsertionPoint {
128
+ const list = id ? findParentList(blocks, id) : null
129
+ if (!list) return { list: blocks, index: endOfBody(blocks, type) }
130
+ return { list, index: list.findIndex((block) => block.id === id) + 1 }
131
+ }
132
+
133
+ function parentOf(blocks: Block[], list: Block[]): Block | null {
134
+ return (
135
+ flattenBlocks(blocks).find((block) => branchesOf(block).includes(list)) ??
136
+ null
137
+ )
138
+ }
139
+
140
+ /**
141
+ * The block to select once `id` is gone: the next one in its list, else the
142
+ * previous one, else the condition holding it.
143
+ */
144
+ export function neighbourOf(blocks: Block[], id: string): string | null {
145
+ const list = findParentList(blocks, id)
146
+ if (!list) return null
147
+ const index = list.findIndex((block) => block.id === id)
148
+ const sibling = list[index + 1] ?? list[index - 1]
149
+ return sibling?.id ?? parentOf(blocks, list)?.id ?? null
150
+ }
151
+
152
+ /** How many blocks each branch of a condition holds (then, else). */
153
+ export interface BranchCounts {
154
+ then: number
155
+ else: number | null
156
+ }
157
+
158
+ export function branchCounts(block: IfBlock): BranchCounts {
159
+ return {
160
+ then: block.children.length,
161
+ else: block.elseChildren ? block.elseChildren.length : null,
162
+ }
163
+ }
164
+
165
+ const isFooter = (block: Block): block is FooterBlock => block.type === 'footer'
166
+
167
+ /**
168
+ * Rewrites the footer labels a locale copy still has at the source locale's
169
+ * defaults into the target locale's; labels someone typed are kept.
170
+ */
171
+ export function localizeFooters(
172
+ blocks: Block[],
173
+ from: string,
174
+ to: string,
175
+ ): void {
176
+ const source = footerLabelsFor(from)
177
+ const target = footerLabelsFor(to)
178
+ flattenBlocks(blocks)
179
+ .filter(isFooter)
180
+ .forEach((footer) => {
181
+ if (footer.unsubscribeLabel === source.unsubscribe)
182
+ footer.unsubscribeLabel = target.unsubscribe
183
+ if (footer.preferencesLabel === source.preferences)
184
+ footer.preferencesLabel = target.preferences
185
+ })
186
+ }
@@ -2,26 +2,163 @@ import type { TemplateCategory } from '../types/mailing'
2
2
 
3
3
  const ID_SEPARATOR = '-'
4
4
 
5
+ export function serializeCategories(categories: TemplateCategory[]): string {
6
+ return JSON.stringify(categories)
7
+ }
8
+
9
+ /** The icons the categories editor offers ("Pick an icon"). */
10
+ export const CATEGORY_ICONS = [
11
+ 'i-ph-package',
12
+ 'i-ph-lock',
13
+ 'i-ph-user-circle',
14
+ 'i-ph-key',
15
+ 'i-ph-file-text',
16
+ 'i-ph-newspaper',
17
+ 'i-ph-sparkle',
18
+ 'i-ph-textbox',
19
+ 'i-ph-truck',
20
+ 'i-ph-shopping-cart',
21
+ 'i-ph-bell',
22
+ 'i-ph-megaphone',
23
+ 'i-ph-gift',
24
+ 'i-ph-users',
25
+ 'i-ph-calendar',
26
+ 'i-ph-chat-circle',
27
+ ]
28
+
29
+ export const DEFAULT_CATEGORY_ICON = 'i-ph-folder'
30
+
31
+ const ICON_PREFIX = 'i-ph-'
32
+
33
+ /**
34
+ * The icon a new category starts with: the first one of the picker no
35
+ * category uses yet, so a new row never looks like an existing one.
36
+ */
37
+ export function newCategoryIcon(categories: TemplateCategory[]): string {
38
+ const used = new Set(categories.map((category) => category.icon))
39
+ return CATEGORY_ICONS.find((icon) => !used.has(icon)) ?? DEFAULT_CATEGORY_ICON
40
+ }
41
+
42
+ /** The i18n key part naming an icon (`megaphone` for `i-ph-megaphone`). */
43
+ export function iconName(icon: string): string {
44
+ return icon.startsWith(ICON_PREFIX)
45
+ ? icon.slice(ICON_PREFIX.length).replace(/-/g, '_')
46
+ : icon
47
+ }
48
+
49
+ /**
50
+ * A row of the categories editor. `isNew` rows were added in this edit: their
51
+ * id follows the label until the form is saved, then stays fixed for good.
52
+ */
53
+ export interface EditableCategory extends TemplateCategory {
54
+ key: string
55
+ isNew: boolean
56
+ }
57
+
58
+ const UNIQUE_SUFFIX_START = 2
59
+ const FALLBACK_ID_BASE = 'category'
60
+
61
+ /** `base`, or `base-2`, `base-3`… the first one `taken` does not hold. */
62
+ export function uniqueId(base: string, taken: Set<string>): string {
63
+ if (!taken.has(base)) return base
64
+ let suffix = UNIQUE_SUFFIX_START
65
+ while (taken.has(`${base}${ID_SEPARATOR}${suffix}`)) suffix++
66
+ return `${base}${ID_SEPARATOR}${suffix}`
67
+ }
68
+
5
69
  /**
6
- * Reads the category list off the settings form field, which stores it either
7
- * as the array or as the JSON string the DMS round-trips. Never throws: this
8
- * runs while rendering the field.
70
+ * The categories an edit stores, one per row in its order: saved rows keep
71
+ * their id, new rows get one derived from their label, unique among the
72
+ * others. A row without a label is sent too, for the server to name it.
9
73
  */
10
- export function parseCategories(
11
- value: TemplateCategory[] | string | null | undefined,
74
+ export function toStoredCategories(
75
+ rows: EditableCategory[],
12
76
  ): TemplateCategory[] {
13
- if (Array.isArray(value)) return value
14
- if (!value) return []
15
- try {
16
- const parsed: unknown = JSON.parse(value)
17
- return Array.isArray(parsed) ? (parsed as TemplateCategory[]) : []
18
- } catch {
19
- return []
20
- }
77
+ const taken = new Set(rows.filter((row) => !row.isNew).map((row) => row.id))
78
+ return rows.map((row) => {
79
+ const id = row.isNew
80
+ ? uniqueId(slugifyId(row.label) || FALLBACK_ID_BASE, taken)
81
+ : row.id
82
+ taken.add(id)
83
+ return {
84
+ id,
85
+ label: row.label.trim(),
86
+ icon: row.icon || DEFAULT_CATEGORY_ICON,
87
+ }
88
+ })
21
89
  }
22
90
 
23
- export function serializeCategories(categories: TemplateCategory[]): string {
24
- return JSON.stringify(categories)
91
+ /**
92
+ * A category as the editor hands it to the form: a kept one, or one removed
93
+ * whose templates move to `moveTo` once the form is saved.
94
+ */
95
+ export interface CategoryEntry extends TemplateCategory {
96
+ moveTo?: string
97
+ }
98
+
99
+ /** The kept categories of a list, without the removed ones it moves away. */
100
+ export function keptCategories(entries: CategoryEntry[]): TemplateCategory[] {
101
+ return entries.filter((entry) => entry.moveTo === undefined)
102
+ }
103
+
104
+ /** Where the templates of each removed category go, by its id. */
105
+ export function categoryMoves(entries: CategoryEntry[]): Map<string, string> {
106
+ return new Map(
107
+ entries.flatMap((entry) =>
108
+ entry.moveTo === undefined ? [] : [[entry.id, entry.moveTo] as const],
109
+ ),
110
+ )
111
+ }
112
+
113
+ /**
114
+ * What the editor hands the form: the kept categories, then each removed one
115
+ * whose templates move elsewhere, still under its own id.
116
+ */
117
+ export function withMoves(
118
+ categories: TemplateCategory[],
119
+ removed: CategoryEntry[],
120
+ ): CategoryEntry[] {
121
+ return [...categories, ...removed.filter((entry) => entry.moveTo)]
122
+ }
123
+
124
+ /**
125
+ * The moves of `removed` once `id` is removed too and its templates go to
126
+ * `target`: a move into `id` follows it to `target`, or is dropped when
127
+ * `target` is none (those templates end uncategorised either way).
128
+ */
129
+ export function redirectMoves(
130
+ removed: CategoryEntry[],
131
+ id: string,
132
+ target: string,
133
+ ): CategoryEntry[] {
134
+ return removed
135
+ .map((entry) =>
136
+ entry.moveTo === id ? { ...entry, moveTo: target } : entry,
137
+ )
138
+ .filter((entry) => entry.moveTo)
139
+ }
140
+
141
+ /** Rows after a move, and where the moved row now sits. */
142
+ export interface MovedRows<T> {
143
+ rows: T[]
144
+ index: number
145
+ }
146
+
147
+ /**
148
+ * `rows` with the row at `index` moved by `offset` (-1 up, 1 down), and its
149
+ * new index; unchanged at either end.
150
+ */
151
+ export function moveRow<T>(
152
+ rows: T[],
153
+ index: number,
154
+ offset: number,
155
+ ): MovedRows<T> {
156
+ const target = index + offset
157
+ if (target < 0 || target >= rows.length) return { rows, index }
158
+ const moved = [...rows]
159
+ const [row] = moved.splice(index, 1)
160
+ moved.splice(target, 0, row as T)
161
+ return { rows: moved, index: target }
25
162
  }
26
163
 
27
164
  /**
@@ -52,3 +189,52 @@ export function toCategoryOption(value: string | null | undefined): string {
52
189
  export function fromCategoryOption(value: string): string {
53
190
  return value === NO_CATEGORY ? '' : value
54
191
  }
192
+
193
+ /**
194
+ * The category an edit stores. "No category" is sent as `null` rather than
195
+ * omitted: from interface-data-api 0.2.0 on, the edit route leaves an absent
196
+ * key unchanged, so only an explicit `null` clears it.
197
+ */
198
+ export function toStoredCategory(
199
+ value: string | null | undefined,
200
+ ): string | null {
201
+ return value || null
202
+ }
203
+
204
+ /** Answers the text of an i18n key, or nothing when the catalog lacks it. */
205
+ export type OptionalTranslate = (key: string) => string | undefined
206
+
207
+ /** The labels the backend's `DEFAULT_CATEGORIES` (`src/types/settings.ts`) ship with. */
208
+ const DEFAULT_CATEGORY_LABELS: Record<string, string> = {
209
+ orders: 'Orders',
210
+ account: 'Account & access',
211
+ billing: 'Billing',
212
+ marketing: 'Marketing',
213
+ forms: 'Forms',
214
+ }
215
+ const DEFAULT_CATEGORY_KEY_PREFIX = 'dms_mailing.categories.defaults'
216
+
217
+ /** The i18n key of a shipped category nobody renamed; absent otherwise. */
218
+ export function defaultCategoryLabelKey(
219
+ category: TemplateCategory,
220
+ ): string | undefined {
221
+ return DEFAULT_CATEGORY_LABELS[category.id] === category.label
222
+ ? `${DEFAULT_CATEGORY_KEY_PREFIX}.${category.id}`
223
+ : undefined
224
+ }
225
+
226
+ /**
227
+ * The categories as the interface shows them: a shipped one still under its
228
+ * original English label reads in the interface's language, a label someone
229
+ * wrote stays as written. Nothing stored changes.
230
+ */
231
+ export function localizeCategories(
232
+ categories: TemplateCategory[],
233
+ translate: OptionalTranslate,
234
+ ): TemplateCategory[] {
235
+ return categories.map((category) => {
236
+ const key = defaultCategoryLabelKey(category)
237
+ const label = key ? translate(key) : undefined
238
+ return label ? { ...category, label } : category
239
+ })
240
+ }
@@ -0,0 +1,152 @@
1
+ import type {
2
+ Block,
3
+ TemplateChange,
4
+ TemplateChangeKind,
5
+ TemplateContent,
6
+ } from '../types/mailing'
7
+ import { blockLabelKey } from './blocks/factory'
8
+ import { findBlock } from './blocks/tree'
9
+
10
+ /** One line of the publish review: "EN · Paragraph changed". */
11
+ export interface ChangeLine {
12
+ key: string
13
+ icon: string
14
+ text: string
15
+ /** The text customers receive today, when the change rewrote one. */
16
+ before?: string
17
+ /** The draft's text in its place. */
18
+ after?: string
19
+ }
20
+
21
+ export type ChangeTranslator = (
22
+ key: string,
23
+ params?: Record<string, string>,
24
+ ) => string
25
+
26
+ /** Contents a change can name its block from: the draft, then the live one. */
27
+ export interface ChangeSources {
28
+ draft: TemplateContent
29
+ published: TemplateContent | null
30
+ }
31
+
32
+ const CHANGE_KEY_PREFIX = 'dms_mailing.editor.changes'
33
+ const LOCALE_SEPARATOR = ' · '
34
+ const DETAIL_SEPARATOR = ' '
35
+
36
+ const CHANGE_ICONS: Record<TemplateChangeKind, string> = {
37
+ locale_added: 'i-ph-plus-circle',
38
+ locale_removed: 'i-ph-minus-circle',
39
+ subject: 'i-ph-pencil-simple',
40
+ preheader: 'i-ph-pencil-simple',
41
+ block_added: 'i-ph-plus-circle',
42
+ block_removed: 'i-ph-minus-circle',
43
+ block_changed: 'i-ph-pencil-simple',
44
+ blocks_reordered: 'i-ph-arrows-down-up',
45
+ variable_added: 'i-ph-brackets-curly',
46
+ variable_removed: 'i-ph-brackets-curly',
47
+ variable_changed: 'i-ph-brackets-curly',
48
+ }
49
+
50
+ function blockIn(
51
+ content: TemplateContent | null,
52
+ change: TemplateChange,
53
+ ): Block | null {
54
+ const blocks = content?.locales[change.locale]?.blocks
55
+ return blocks && change.blockId ? findBlock(blocks, change.blockId) : null
56
+ }
57
+
58
+ function blockOf(change: TemplateChange, sources: ChangeSources): Block | null {
59
+ return blockIn(sources.draft, change) ?? blockIn(sources.published, change)
60
+ }
61
+
62
+ /** The fields a reader recognises a block by, in the order they are tried. */
63
+ const TEXT_FIELDS = ['text', 'label', 'alt'] as const
64
+
65
+ function textOf(block: Block | null): string | undefined {
66
+ if (!block) return undefined
67
+ const fields = block as unknown as Record<string, unknown>
68
+ const field = TEXT_FIELDS.find((name) => typeof fields[name] === 'string')
69
+ return field ? (fields[field] as string) : undefined
70
+ }
71
+
72
+ type EnvelopeKind = Extract<TemplateChangeKind, 'subject' | 'preheader'>
73
+
74
+ const ENVELOPE_KINDS: TemplateChangeKind[] = ['subject', 'preheader']
75
+
76
+ function textsOf(
77
+ change: TemplateChange,
78
+ sources: ChangeSources,
79
+ ): Pick<ChangeLine, 'before' | 'after'> {
80
+ if (ENVELOPE_KINDS.includes(change.kind)) {
81
+ const field = change.kind as EnvelopeKind
82
+ return {
83
+ before: sources.published?.locales[change.locale]?.[field],
84
+ after: sources.draft.locales[change.locale]?.[field],
85
+ }
86
+ }
87
+ if (change.kind !== 'block_changed') return {}
88
+ return {
89
+ before: textOf(blockIn(sources.published, change)),
90
+ after: textOf(blockIn(sources.draft, change)),
91
+ }
92
+ }
93
+
94
+ /** Before / after texts worth showing: both known and actually different. */
95
+ function rewriteOf(
96
+ change: TemplateChange,
97
+ sources: ChangeSources,
98
+ ): Pick<ChangeLine, 'before' | 'after'> {
99
+ const { before, after } = textsOf(change, sources)
100
+ if (before === undefined || after === undefined || before === after) return {}
101
+ return { before, after }
102
+ }
103
+
104
+ function blockName(
105
+ change: TemplateChange,
106
+ sources: ChangeSources,
107
+ t: ChangeTranslator,
108
+ ): string {
109
+ if (!change.blockType) return ''
110
+ const label = t(blockLabelKey(change.blockType))
111
+ const block = blockOf(change, sources)
112
+ return block?.type === 'if'
113
+ ? `${label}${DETAIL_SEPARATOR}${block.condition.path}`
114
+ : label
115
+ }
116
+
117
+ /** "EN · " for a change of the content; nothing for a declaration's. */
118
+ function scopeOf(change: TemplateChange): string {
119
+ return change.locale
120
+ ? `${change.locale.toUpperCase()}${LOCALE_SEPARATOR}`
121
+ : ''
122
+ }
123
+
124
+ /** Contents to name blocks from when only the change list is known. */
125
+ export const NO_SOURCES: ChangeSources = {
126
+ draft: { locales: {} },
127
+ published: null,
128
+ }
129
+
130
+ /**
131
+ * The publish review's reading of the server's change list, one line per
132
+ * change, prefixed with its locale (a variable declaration has none).
133
+ */
134
+ export function describeChanges(
135
+ changes: TemplateChange[],
136
+ sources: ChangeSources,
137
+ t: ChangeTranslator,
138
+ ): ChangeLine[] {
139
+ return changes.map((change, index) => {
140
+ const sentence = t(`${CHANGE_KEY_PREFIX}.${change.kind}`, {
141
+ block: blockName(change, sources, t),
142
+ variable: change.variable ?? '',
143
+ })
144
+ const subject = change.blockId ?? change.variable ?? index
145
+ return {
146
+ key: `${change.locale}-${change.kind}-${subject}`,
147
+ icon: CHANGE_ICONS[change.kind],
148
+ text: `${scopeOf(change)}${sentence}`,
149
+ ...rewriteOf(change, sources),
150
+ }
151
+ })
152
+ }
@@ -1,4 +1,5 @@
1
1
  import type { Condition, ConditionOperator } from '../types/mailing'
2
+ import { readPath } from './paths'
2
3
 
3
4
  export const OPERATORS: ConditionOperator[] = [
4
5
  'truthy',
@@ -18,25 +19,6 @@ export const OPERATORS_WITH_VALUE: ConditionOperator[] = [
18
19
  const OPERATOR_LABEL_PREFIX = 'dms_mailing.editor.operators'
19
20
  const EMPTY_VALUE_PLACEHOLDER = '…'
20
21
 
21
- const quote = (value: string): string =>
22
- value !== '' && !Number.isNaN(Number(value)) ? value : `"${value}"`
23
-
24
- type ConditionCompiler = (condition: Condition) => string
25
-
26
- const COMPILERS: Record<ConditionOperator, ConditionCompiler> = {
27
- truthy: (condition) => `{{#if ${condition.path}}}`,
28
- falsy: (condition) => `{{#unless ${condition.path}}}`,
29
- eq: (condition) => `{{#if (eq ${condition.path} ${quote(condition.value)})}}`,
30
- ne: (condition) =>
31
- `{{#unless (eq ${condition.path} ${quote(condition.value)})}}`,
32
- gt: (condition) => `{{#if (gt ${condition.path} ${quote(condition.value)})}}`,
33
- lt: (condition) => `{{#if (lt ${condition.path} ${quote(condition.value)})}}`,
34
- }
35
-
36
- export function compileCondition(condition: Condition): string {
37
- return COMPILERS[condition.operator](condition)
38
- }
39
-
40
22
  export type OperatorTranslator = (key: string) => string
41
23
 
42
24
  export function describeCondition(
@@ -48,3 +30,27 @@ export function describeCondition(
48
30
  return `${condition.path} ${label}`
49
31
  return `${condition.path} ${label} ${condition.value || EMPTY_VALUE_PLACEHOLDER}`
50
32
  }
33
+
34
+ type Evaluator = (actual: unknown, expected: string) => boolean
35
+
36
+ const EVALUATORS: Record<ConditionOperator, Evaluator> = {
37
+ truthy: (actual) => Boolean(actual),
38
+ falsy: (actual) => !actual,
39
+ eq: (actual, expected) => String(actual) === expected,
40
+ ne: (actual, expected) => String(actual) !== expected,
41
+ gt: (actual, expected) => Number(actual) > Number(expected),
42
+ lt: (actual, expected) => Number(actual) < Number(expected),
43
+ }
44
+
45
+ /**
46
+ * Evaluates a condition against a data set exactly like the backend engine
47
+ * (`src/engine/conditions.ts`), so the editor's Data mode shows the branch a
48
+ * real send takes.
49
+ */
50
+ export function evaluateCondition(
51
+ condition: Condition,
52
+ data: Record<string, unknown>,
53
+ ): boolean {
54
+ const evaluate = EVALUATORS[condition.operator]
55
+ return evaluate(readPath(data, condition.path), condition.value)
56
+ }