@antelopejs/dms-mailing 0.3.3 → 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 +4 -1
  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
@@ -0,0 +1,185 @@
1
+ import type { TemplateRow, TemplateStatus } from '../types/mailing'
2
+
3
+ export type LifecycleAction = 'unpublish' | 'archive' | 'restore'
4
+
5
+ export interface LifecycleButton {
6
+ action: LifecycleAction
7
+ icon: string
8
+ }
9
+
10
+ const BUTTONS: Record<LifecycleAction, LifecycleButton> = {
11
+ unpublish: { action: 'unpublish', icon: 'i-ph-eye-slash' },
12
+ archive: { action: 'archive', icon: 'i-ph-archive' },
13
+ restore: { action: 'restore', icon: 'i-ph-arrow-u-up-left' },
14
+ }
15
+
16
+ const TRANSITIONS: Record<TemplateStatus, LifecycleAction[]> = {
17
+ draft: ['archive'],
18
+ live: ['unpublish', 'archive'],
19
+ archived: ['restore'],
20
+ }
21
+
22
+ export function lifecycleActions(status: TemplateStatus): LifecycleButton[] {
23
+ return (TRANSITIONS[status] ?? []).map((action) => BUTTONS[action])
24
+ }
25
+
26
+ /** What an action stamps on the template besides its status, at `now`. */
27
+ const STAMPS: Partial<
28
+ Record<LifecycleAction, (now: Date) => Partial<TemplateRow>>
29
+ > = {
30
+ unpublish: (now) => ({ unpublishedAt: now.toISOString() }),
31
+ }
32
+
33
+ /**
34
+ * The template once `action` went through: its new status and the moments
35
+ * the editor's notice dates, as the server recorded them.
36
+ */
37
+ export function afterLifecycle(
38
+ template: TemplateRow,
39
+ action: LifecycleAction,
40
+ status: TemplateStatus,
41
+ now = new Date(),
42
+ ): TemplateRow {
43
+ return { ...template, ...STAMPS[action]?.(now), status }
44
+ }
45
+
46
+ /** Publishing is refused from `archived`; restore the template first. */
47
+ export function canPublish(status: TemplateStatus): boolean {
48
+ return status !== 'archived'
49
+ }
50
+
51
+ /** Where the draft's autosave stands. */
52
+ export type SaveStatus = 'idle' | 'saving' | 'error' | 'conflict'
53
+
54
+ /** What decides whether "Publish changes" (and ⌘↵) can run. */
55
+ export interface PublishableInput {
56
+ status: TemplateStatus
57
+ changeCount: number
58
+ dirty: number
59
+ }
60
+
61
+ /**
62
+ * Whether publishing would do anything: a live template needs a change; a
63
+ * draft or unpublished one goes live; an archived one must be restored first.
64
+ */
65
+ export function isPublishable(input: PublishableInput): boolean {
66
+ if (!canPublish(input.status)) return false
67
+ if (input.status !== 'live') return true
68
+ return input.changeCount > 0 || input.dirty > 0
69
+ }
70
+
71
+ /** The status a template reads as: a draft that was once live is unpublished. */
72
+ export type StatusKey = TemplateStatus | 'unpublished'
73
+
74
+ export function statusKeyOf(
75
+ status: TemplateStatus,
76
+ version: number,
77
+ ): StatusKey {
78
+ return status === 'draft' && version > 0 ? 'unpublished' : status
79
+ }
80
+
81
+ export type NoticeKind =
82
+ | 'conflict'
83
+ | 'save_error'
84
+ | 'unpublished'
85
+ | 'live_changes'
86
+ | 'live_clean'
87
+ | 'never_published'
88
+
89
+ export type NoticeAction =
90
+ 'compare' | 'discard' | 'reload' | 'overwrite' | 'retry' | 'republish'
91
+
92
+ export type NoticeTone = 'primary' | 'warning' | 'error'
93
+
94
+ /** The band under the toolbar: what it says, its tone and its actions. */
95
+ export interface EditorNotice {
96
+ kind: NoticeKind
97
+ tone: NoticeTone
98
+ actions: NoticeAction[]
99
+ }
100
+
101
+ export interface NoticeInput {
102
+ status: TemplateStatus
103
+ version: number
104
+ changeCount: number
105
+ saveStatus: SaveStatus
106
+ }
107
+
108
+ const DRAFT_ACTIONS: NoticeAction[] = ['compare', 'discard']
109
+
110
+ const SAVE_NOTICES: Partial<Record<SaveStatus, EditorNotice>> = {
111
+ conflict: {
112
+ kind: 'conflict',
113
+ tone: 'error',
114
+ actions: ['reload', 'overwrite'],
115
+ },
116
+ error: { kind: 'save_error', tone: 'error', actions: ['retry'] },
117
+ }
118
+
119
+ function contentNotice(input: NoticeInput): EditorNotice | null {
120
+ const draftActions = input.changeCount > 0 ? DRAFT_ACTIONS : []
121
+ if (input.version === 0)
122
+ return input.changeCount > 0
123
+ ? { kind: 'never_published', tone: 'primary', actions: [] }
124
+ : null
125
+ if (input.status === 'live')
126
+ return input.changeCount > 0
127
+ ? { kind: 'live_changes', tone: 'primary', actions: draftActions }
128
+ : { kind: 'live_clean', tone: 'primary', actions: [] }
129
+ if (input.status === 'draft')
130
+ return {
131
+ kind: 'unpublished',
132
+ tone: 'warning',
133
+ actions: [...draftActions, 'republish'],
134
+ }
135
+ return null
136
+ }
137
+
138
+ /**
139
+ * Which notice the editor shows: a draft that could not be saved first, then
140
+ * the template's reading — nothing published yet, live, or unpublished.
141
+ */
142
+ export function noticeFor(input: NoticeInput): EditorNotice | null {
143
+ return SAVE_NOTICES[input.saveStatus] ?? contentNotice(input)
144
+ }
145
+
146
+ /** The moment a notice dates: the unpublish, else the last publication. */
147
+ export function noticeMomentOf(
148
+ kind: NoticeKind,
149
+ template: Pick<TemplateRow, 'publishedAt' | 'unpublishedAt' | 'updatedAt'>,
150
+ ): string | null | undefined {
151
+ if (kind !== 'unpublished') return template.publishedAt
152
+ return template.unpublishedAt ?? template.updatedAt
153
+ }
154
+
155
+ const DATE_FORMAT: Intl.DateTimeFormatOptions = {
156
+ month: 'short',
157
+ day: 'numeric',
158
+ }
159
+ const TIME_FORMAT: Intl.DateTimeFormatOptions = {
160
+ hour: '2-digit',
161
+ minute: '2-digit',
162
+ hour12: false,
163
+ }
164
+
165
+ /** A publication moment, split for "published Sep 29 at 09:14". */
166
+ export interface PublishedMoment {
167
+ date: string
168
+ time: string
169
+ }
170
+
171
+ export function publishedMoment(
172
+ iso: string | null | undefined,
173
+ locale: string,
174
+ ): PublishedMoment | null {
175
+ if (!iso) return null
176
+ const date = new Date(iso)
177
+ if (Number.isNaN(date.getTime())) return null
178
+ return {
179
+ date: date.toLocaleDateString(locale, DATE_FORMAT),
180
+ time: date.toLocaleTimeString(locale, TIME_FORMAT),
181
+ }
182
+ }
183
+
184
+ /** The subject length mail clients show in full; past it, a warning tint. */
185
+ export const SUBJECT_RECOMMENDED_LENGTH = 60
@@ -5,8 +5,19 @@ export interface LocalePresence {
5
5
 
6
6
  const LOCALE_LIST_SEPARATOR = ','
7
7
 
8
+ /** Base look of a locale chip, on a card, in a table cell or a review box. */
9
+ export const LOCALE_CHIP_CLASS =
10
+ 'inline-flex h-[18px] items-center rounded px-1.5 font-mono text-[10.5px] font-semibold uppercase leading-none'
11
+
12
+ /** A locale the template carries. */
13
+ export const LOCALE_CHIP_PRESENT_CLASS = 'bg-elevated text-toned'
14
+
15
+ /** A workspace locale the template lacks: its recipients get the fallback. */
16
+ export const LOCALE_CHIP_MISSING_CLASS =
17
+ 'text-warning border-warning/60 border border-dashed'
18
+
8
19
  /** Reads the `locales` column, whose format is a comma-separated code list. */
9
- export function parseLocaleList(value: string | undefined): string[] {
20
+ export function parseLocaleList(value: string | undefined | null): string[] {
10
21
  return (value ?? '')
11
22
  .split(LOCALE_LIST_SEPARATOR)
12
23
  .map((code) => code.trim())
@@ -32,10 +43,104 @@ export function localeCoverage(
32
43
  * locale is missing.
33
44
  */
34
45
  export function localeBadges(
35
- stored: string | undefined,
46
+ stored: string | undefined | null,
47
+ codes: string[],
48
+ ): LocalePresence[] {
49
+ const present = parseLocaleList(stored)
50
+ if (!present.length) return []
51
+ return localeCoverage(present, codes)
52
+ }
53
+
54
+ /**
55
+ * Chips for a list of codes: the workspace ones in the workspace order, then
56
+ * any the template carries that the workspace no longer offers.
57
+ */
58
+ export function localeChips(
59
+ stored: string | undefined | null,
36
60
  codes: string[],
37
61
  ): LocalePresence[] {
38
62
  const present = parseLocaleList(stored)
39
63
  if (!present.length) return []
64
+ const extra = present.filter((code) => !codes.includes(code))
65
+ return [
66
+ ...localeCoverage(present, codes),
67
+ ...extra.map((code) => ({ code, present: true })),
68
+ ]
69
+ }
70
+
71
+ /** The workspace locales `present` lacks. */
72
+ export function missingLocales(present: string[], codes: string[]): string[] {
40
73
  return localeCoverage(present, codes)
74
+ .filter((entry) => !entry.present)
75
+ .map((entry) => entry.code)
76
+ }
77
+
78
+ export function localeChipClass(present: boolean): string {
79
+ return `${LOCALE_CHIP_CLASS} ${present ? LOCALE_CHIP_PRESENT_CLASS : LOCALE_CHIP_MISSING_CLASS}`
80
+ }
81
+
82
+ const DEFAULT_FALLBACK_LOCALE = 'en'
83
+ const LOCALE_PART_SEPARATOR = /[-_]/
84
+
85
+ /** The base language of a locale code: "fr-BE" and "FR" both read "fr". */
86
+ export function localeLanguage(code: string): string {
87
+ const [language = code] = code.split(LOCALE_PART_SEPARATOR)
88
+ return language.toLowerCase()
89
+ }
90
+
91
+ /**
92
+ * The locale a send renders, as the server picks it: the requested one
93
+ * (regardless of case, or by its base language), else the tenant fallback,
94
+ * else English, else the first locale the content carries.
95
+ */
96
+ export function pickLocale(
97
+ available: string[],
98
+ wanted: string | undefined,
99
+ fallback: string,
100
+ ): string {
101
+ const byCode = new Map(available.map((code) => [code.toLowerCase(), code]))
102
+ const requested = wanted ? [wanted, localeLanguage(wanted)] : []
103
+ const candidates = [
104
+ ...requested,
105
+ fallback,
106
+ DEFAULT_FALLBACK_LOCALE,
107
+ available[0],
108
+ ]
109
+ const match = candidates
110
+ .map((code) => (code ? byCode.get(code.toLowerCase()) : undefined))
111
+ .find((code): code is string => Boolean(code))
112
+ return match ?? DEFAULT_FALLBACK_LOCALE
113
+ }
114
+
115
+ const LANGUAGE_NAMES: Intl.DisplayNamesOptions = { type: 'language' }
116
+
117
+ /**
118
+ * A language named in the interface's language, capitalised like a label
119
+ * ("Anglais" for `en` in French); absent when the runtime does not know it.
120
+ */
121
+ export function languageName(
122
+ code: string,
123
+ uiLocale: string,
124
+ ): string | undefined {
125
+ try {
126
+ const name = new Intl.DisplayNames([uiLocale], LANGUAGE_NAMES).of(code)
127
+ if (!name || name.toLowerCase() === code.toLowerCase()) return undefined
128
+ return name.charAt(0).toLocaleUpperCase(uiLocale) + name.slice(1)
129
+ } catch {
130
+ return undefined
131
+ }
132
+ }
133
+
134
+ /**
135
+ * A locale as a picker lists it, in the interface's language: "French (FR)".
136
+ * `fallbackName` stands in for a language the runtime cannot name.
137
+ */
138
+ export function languageLabel(
139
+ code: string,
140
+ uiLocale: string,
141
+ fallbackName?: string,
142
+ ): string {
143
+ const upper = code.toUpperCase()
144
+ const name = languageName(code, uiLocale) ?? fallbackName
145
+ return name ? `${name} (${upper})` : upper
41
146
  }
@@ -0,0 +1,83 @@
1
+ import { SETTINGS_PATH, TEMPLATES_PATH } from './pages'
2
+
3
+ export type SetupStepId = 'provider' | 'sender' | 'publish' | 'test'
4
+
5
+ export type SetupStepState = 'done' | 'current' | 'todo'
6
+
7
+ /** What `GET /metrics/setup` answers. */
8
+ export interface SetupFacts {
9
+ isProviderConnected: boolean
10
+ isSenderSet: boolean
11
+ liveTemplateCount: number
12
+ hasTestSend: boolean
13
+ }
14
+
15
+ export interface SetupStepDefinition {
16
+ id: SetupStepId
17
+ /**
18
+ * Where the step is done; absent when it is not done in the app (the
19
+ * provider is set in the project's configuration).
20
+ */
21
+ to?: string
22
+ isDone: (facts: SetupFacts) => boolean
23
+ }
24
+
25
+ export interface SetupStep {
26
+ id: SetupStepId
27
+ number: number
28
+ to?: string
29
+ state: SetupStepState
30
+ }
31
+
32
+ /** A settings form section, by the id the DMS form gives its anchor. */
33
+ const SETTINGS_SECTION = `${SETTINGS_PATH}#dms-form-content-section-`
34
+
35
+ export const SETUP_STEPS: SetupStepDefinition[] = [
36
+ {
37
+ id: 'provider',
38
+ isDone: (facts) => facts.isProviderConnected,
39
+ },
40
+ {
41
+ id: 'sender',
42
+ to: `${SETTINGS_SECTION}sender`,
43
+ isDone: (facts) => facts.isSenderSet,
44
+ },
45
+ {
46
+ id: 'publish',
47
+ to: TEMPLATES_PATH,
48
+ isDone: (facts) => facts.liveTemplateCount > 0,
49
+ },
50
+ {
51
+ id: 'test',
52
+ to: TEMPLATES_PATH,
53
+ isDone: (facts) => facts.hasTestSend,
54
+ },
55
+ ]
56
+
57
+ /**
58
+ * The first-run checklist: each step done or not from what the workspace
59
+ * holds, the first one left marked current.
60
+ */
61
+ export function setupSteps(facts: SetupFacts): SetupStep[] {
62
+ const done = SETUP_STEPS.map((step) => step.isDone(facts))
63
+ const current = done.indexOf(false)
64
+ return SETUP_STEPS.map((step, index) => ({
65
+ id: step.id,
66
+ number: index + 1,
67
+ to: step.to,
68
+ state: done[index] ? 'done' : index === current ? 'current' : 'todo',
69
+ }))
70
+ }
71
+
72
+ /**
73
+ * The dashboard only makes sense once e-mail can leave and a template is live:
74
+ * a draft alone sends nothing.
75
+ */
76
+ export function needsSetup(facts: SetupFacts): boolean {
77
+ return !facts.isProviderConnected || facts.liveTemplateCount === 0
78
+ }
79
+
80
+ /** Every step of the checklist is done: nothing left to resume. */
81
+ export function isSetupComplete(facts: SetupFacts): boolean {
82
+ return SETUP_STEPS.every((step) => step.isDone(facts))
83
+ }
@@ -0,0 +1,5 @@
1
+ /** The module's pages, as `navigateDms` and links reach them. */
2
+ export const TEMPLATES_PATH = '/modules/mailing/templates'
3
+ export const SENDS_PATH = '/modules/mailing/sends'
4
+ export const SETTINGS_PATH = '/modules/mailing/settings'
5
+ export const SUPPRESSIONS_PATH = '/modules/mailing/suppressions'
@@ -2,15 +2,37 @@ import type { Block, LocaleContent, TemplateContent } from '../types/mailing'
2
2
  import { splitTokens } from './tokens'
3
3
 
4
4
  const TOKEN_FIELDS = ['text', 'value', 'href', 'label', 'alt', 'imageUrl']
5
+ const PATH_SEPARATOR = '.'
6
+ const ARRAY_SUFFIX = '[]'
7
+ const RESERVED_SEGMENTS = ['__proto__', 'constructor', 'prototype']
8
+
9
+ export const UNSUBSCRIBE_URL_PATH = 'unsubscribeUrl'
10
+ export const PREFERENCES_URL_PATH = 'preferencesUrl'
11
+
12
+ /**
13
+ * The footer links' URLs, passed by the caller at send time. The content
14
+ * declares them itself (`utils/system-variables.ts`), so they never show as
15
+ * undeclared; the backend keeps them out of `detectedVariables` the same way.
16
+ */
17
+ export const SYSTEM_PATHS = [UNSUBSCRIBE_URL_PATH, PREFERENCES_URL_PATH]
18
+
19
+ /**
20
+ * A path cut after each separator ("order." + "number"): where a narrow
21
+ * column may wrap it, rather than in the middle of a name.
22
+ */
23
+ export function pathBreakSegments(path: string): string[] {
24
+ return path
25
+ .split(PATH_SEPARATOR)
26
+ .map((segment, index, segments) =>
27
+ index < segments.length - 1 ? `${segment}${PATH_SEPARATOR}` : segment,
28
+ )
29
+ }
5
30
 
6
31
  function tokenPaths(block: Block): string[] {
7
32
  const record = block as unknown as Record<string, unknown>
8
33
  return TOKEN_FIELDS.flatMap((key) => {
9
34
  const value = record[key]
10
- if (typeof value !== 'string') return []
11
- return splitTokens(value)
12
- .filter((part) => part.kind === 'token')
13
- .map((part) => part.value)
35
+ return typeof value === 'string' ? textPaths(value) : []
14
36
  })
15
37
  }
16
38
 
@@ -46,13 +68,8 @@ export function collectPaths(
46
68
  return [...new Set(all.filter(Boolean))].sort()
47
69
  }
48
70
 
49
- /**
50
- * Paths the runtime fills in itself, so an author never declares them. The
51
- * backend drops these before answering; this list mirrors it.
52
- */
53
- const SYSTEM_PATHS = ['unsubscribeUrl', 'preferencesUrl']
54
-
55
- function textPaths(text: string): string[] {
71
+ /** The variable paths a text references through `{{path}}` tokens. */
72
+ export function textPaths(text: string): string[] {
56
73
  return splitTokens(text)
57
74
  .filter((part) => part.kind === 'token')
58
75
  .map((part) => part.value)
@@ -69,10 +86,6 @@ function pathsInLocale(locale: LocaleContent): string[] {
69
86
  /**
70
87
  * Every variable path the content references, across all of its locales — the
71
88
  * live equivalent of what the backend returns as `detectedVariables`.
72
- *
73
- * The panel needs this because the server's list only refreshes on save: until
74
- * then a path wired into a display condition reads as unused, which is exactly
75
- * the moment an author looks at it.
76
89
  */
77
90
  export function collectContentPaths(
78
91
  content: Pick<TemplateContent, 'locales'>,
@@ -82,3 +95,26 @@ export function collectContentPaths(
82
95
  ...new Set(paths.filter((path) => path && !SYSTEM_PATHS.includes(path))),
83
96
  ].sort()
84
97
  }
98
+
99
+ function readSegment(current: unknown, segment: string): unknown {
100
+ if (current === null || typeof current !== 'object') return undefined
101
+ if (RESERVED_SEGMENTS.includes(segment)) return undefined
102
+ return (current as Record<string, unknown>)[segment]
103
+ }
104
+
105
+ /**
106
+ * Reads `path` (`order.total`, `order.lines[]`) in `data`, the way the backend
107
+ * engine does; `undefined` when any segment is missing.
108
+ */
109
+ export function readPath(data: unknown, path: string): unknown {
110
+ const trimmed = path.trim()
111
+ const normalized = trimmed.endsWith(ARRAY_SUFFIX)
112
+ ? trimmed.slice(0, -ARRAY_SUFFIX.length)
113
+ : trimmed
114
+ return normalized.split(PATH_SEPARATOR).reduce<unknown>(readSegment, data)
115
+ }
116
+
117
+ /** Whether `value` counts as provided: neither `undefined` nor `null`. */
118
+ export function isPresent(value: unknown): boolean {
119
+ return value !== undefined && value !== null
120
+ }
@@ -1,23 +1,63 @@
1
- /**
2
- * The DMS period selector owns the window: `PeriodState` and its `PeriodRange`
3
- * come from the host layer and reach us through the workspace auto-imports.
4
- * Declaring a second `PeriodRange` here put two entries of the same name in
5
- * that registry, and the host's won -- ours was silently ignored.
6
- */
7
- export type PeriodLike = Pick<PeriodState, 'range' | 'compareRange'>
8
-
9
- /**
10
- * Serialises a period-selector state into the from/to (+ comparison) query the
11
- * mailing metrics routes read.
12
- */
13
- export function periodQuery(period: PeriodLike): Record<string, string> {
14
- const query: Record<string, string> = {
15
- from: period.range.from.toISOString(),
16
- to: period.range.to.toISOString(),
17
- }
18
- if (period.compareRange) {
19
- query.compareFrom = period.compareRange.from.toISOString()
20
- query.compareTo = period.compareRange.to.toISOString()
21
- }
22
- return query
1
+ const MINUTE_MS = 60_000
2
+ const DAY_MS = 86_400_000
3
+ const PRESET_KEY_SEPARATOR = /-/g
4
+ const KEY_SEPARATOR = '_'
5
+
6
+ /** Hour and minute on a 24-hour clock, the way the cards stamp "updated 14:32". */
7
+ export const CLOCK_FORMAT: Intl.DateTimeFormatOptions = {
8
+ hour: '2-digit',
9
+ minute: '2-digit',
10
+ hourCycle: 'h23',
11
+ }
12
+
13
+ /** The i18n key of a preset's short segmented label ("24H", "7D"…). */
14
+ export function presetShortLabelKey(preset: string): string {
15
+ return `dms_mailing.period.short.${preset.replace(PRESET_KEY_SEPARATOR, KEY_SEPARATOR)}`
16
+ }
17
+
18
+ /** Whole minutes elapsed since `iso`; 0 for a missing or future date. */
19
+ export function minutesSince(
20
+ iso: string | null | undefined,
21
+ now: Date,
22
+ ): number {
23
+ if (!iso) return 0
24
+ const elapsed = now.getTime() - new Date(iso).getTime()
25
+ return elapsed > 0 ? Math.floor(elapsed / MINUTE_MS) : 0
26
+ }
27
+
28
+ /** Formats a date as HH:MM in `locale`. */
29
+ export function formatClock(value: Date | string, locale: string): string {
30
+ return new Date(value).toLocaleTimeString(locale, CLOCK_FORMAT)
31
+ }
32
+
33
+ /** Whether `at` falls within the next 24 hours: "tonight" rather than a date. */
34
+ export function isWithinADay(at: Date | string, now: Date): boolean {
35
+ const delay = new Date(at).getTime() - now.getTime()
36
+ return delay >= 0 && delay < DAY_MS
37
+ }
38
+
39
+ /** Retention presets of the settings form, in days. */
40
+ export const RETENTION_PRESETS = [30, 90, 365]
41
+ export const RETENTION_CUSTOM = 'custom'
42
+ export const MIN_RETENTION_DAYS = 1
43
+ export const MAX_RETENTION_DAYS = 3650
44
+
45
+ export type RetentionChoice = string
46
+
47
+ /** The segment a retention shows: its preset, else "custom". */
48
+ export function retentionChoice(
49
+ days: number | null | undefined,
50
+ ): RetentionChoice {
51
+ return days && RETENTION_PRESETS.includes(days)
52
+ ? String(days)
53
+ : RETENTION_CUSTOM
54
+ }
55
+
56
+ /** A typed retention brought back between 1 day and 10 years, whole days. */
57
+ export function clampRetention(days: number): number {
58
+ if (!Number.isFinite(days)) return MIN_RETENTION_DAYS
59
+ return Math.min(
60
+ MAX_RETENTION_DAYS,
61
+ Math.max(MIN_RETENTION_DAYS, Math.round(days)),
62
+ )
23
63
  }