@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,104 @@
1
+ import type {
2
+ TemplateRow,
3
+ TemplateStats,
4
+ TemplateStatus,
5
+ VariableDefinition,
6
+ } from '../types/mailing'
7
+ import { readPath } from './paths'
8
+
9
+ const MS_PER_DAY = 86_400_000
10
+
11
+ export type AttentionReason = 'stale_draft' | 'never_sent' | 'missing_locale'
12
+
13
+ /** How the row's `attention` field separates its reasons. */
14
+ const REASON_SEPARATOR = ','
15
+
16
+ /** Why the server says a template needs a look, read off its row. */
17
+ export function attentionReasons(row: TemplateRow): AttentionReason[] {
18
+ return row.attention
19
+ .split(REASON_SEPARATOR)
20
+ .filter(Boolean) as AttentionReason[]
21
+ }
22
+
23
+ /** Whole days between `date` and `now`; 0 for an unreadable date. */
24
+ export function daysSince(date: string | null | undefined, now: Date): number {
25
+ const time = date ? new Date(date).getTime() : Number.NaN
26
+ if (Number.isNaN(time)) return 0
27
+ return Math.max(0, Math.floor((now.getTime() - time) / MS_PER_DAY))
28
+ }
29
+
30
+ export type CardFooterKind = 'stats' | 'edited' | 'no_send'
31
+
32
+ /** Which line a card's footer reads, with the figures it needs. */
33
+ export interface CardFooter {
34
+ kind: CardFooterKind
35
+ sends: number
36
+ openRate: number
37
+ /** The date the line names: last edit, or the day it went live. */
38
+ at: string
39
+ by: string
40
+ }
41
+
42
+ /**
43
+ * The footer of a gallery card: the 30-day figures once real sends exist,
44
+ * otherwise who last edited a draft, or since when a live template waits for
45
+ * its first send.
46
+ */
47
+ export function cardFooter(
48
+ row: TemplateRow,
49
+ stats?: TemplateStats,
50
+ ): CardFooter {
51
+ const base = {
52
+ sends: stats?.sends ?? 0,
53
+ openRate: stats?.openRate ?? 0,
54
+ at: row.updatedAt,
55
+ by: row.updatedBy,
56
+ }
57
+ if (base.sends > 0) return { ...base, kind: 'stats' }
58
+ if (row.status === 'live') {
59
+ return { ...base, kind: 'no_send', at: row.publishedAt || row.updatedAt }
60
+ }
61
+ return { ...base, kind: 'edited' }
62
+ }
63
+
64
+ export type LifecycleAction = 'publish' | 'unpublish' | 'archive' | 'restore'
65
+
66
+ type TransitionRule = (row: TemplateRow) => LifecycleAction[]
67
+
68
+ /**
69
+ * The backend refuses any other move with `409 invalid_transition`. On a live
70
+ * template, publishing only makes sense when a draft waits.
71
+ */
72
+ const TRANSITIONS: Record<TemplateStatus, TransitionRule> = {
73
+ draft: () => ['publish', 'archive'],
74
+ live: (row) =>
75
+ row.isDraftPending
76
+ ? ['publish', 'unpublish', 'archive']
77
+ : ['unpublish', 'archive'],
78
+ archived: () => ['restore'],
79
+ }
80
+
81
+ /** The lifecycle moves a template accepts from its current status. */
82
+ export function validTransitions(row: TemplateRow): LifecycleAction[] {
83
+ return TRANSITIONS[row.status]?.(row) ?? []
84
+ }
85
+
86
+ const isFilled = (value: unknown) =>
87
+ value !== undefined && value !== null && value !== ''
88
+
89
+ /**
90
+ * Whether the variables are ready for a real send: every path the content
91
+ * uses is declared, and every required one has a test value.
92
+ */
93
+ export function variablesCovered(
94
+ declared: VariableDefinition[],
95
+ detected: string[],
96
+ testData: Record<string, unknown>,
97
+ ): boolean {
98
+ const declaredPaths = new Set(declared.map((entry) => entry.path))
99
+ const everyUsedDeclared = detected.every((path) => declaredPaths.has(path))
100
+ const everyRequiredFilled = declared
101
+ .filter((entry) => entry.required)
102
+ .every((entry) => isFilled(readPath(testData, entry.path)))
103
+ return everyUsedDeclared && everyRequiredFilled
104
+ }
@@ -1,4 +1,5 @@
1
- import type { SendEventRow, SendStatus } from '../types/mailing'
1
+ import type { SendEventRow, SendRow, SendStatus } from '../types/mailing'
2
+ import { isSuppressedStatus, SUPPRESSED_STATUS } from './suppression'
2
3
 
3
4
  export type TimelineTone =
4
5
  'neutral' | 'success' | 'primary' | 'error' | 'warning'
@@ -13,8 +14,30 @@ export interface TimelineItem {
13
14
  type: SendStatus
14
15
  icon: string
15
16
  tone: TimelineTone
16
- description: string
17
- at: string
17
+ /** i18n key of the step's title. */
18
+ titleKey: string
19
+ titleParams: Record<string, string>
20
+ /** One line under the title: a raw SMTP answer, a message id, a URL… */
21
+ detail: string
22
+ /** Whether `detail` is a machine value, shown in mono. */
23
+ isDetailMono: boolean
24
+ /** `null` for a step still to come. */
25
+ at: string | null
26
+ /** A step that has not happened yet, drawn ghosted. */
27
+ isPending: boolean
28
+ }
29
+
30
+ export interface TimelineSend extends Pick<
31
+ SendRow,
32
+ '_id' | 'status' | 'createdAt' | 'provider' | 'providerMessageId' | 'error'
33
+ > {
34
+ isSoftBounce?: boolean
35
+ }
36
+
37
+ /** What the provider reports by itself; `null` while it is unknown. */
38
+ export interface TimelineTracking {
39
+ openTracking: boolean
40
+ clickTracking: boolean
18
41
  }
19
42
 
20
43
  export const TIMELINE_META: Record<SendStatus, TimelineMeta> = {
@@ -27,14 +50,47 @@ export const TIMELINE_META: Record<SendStatus, TimelineMeta> = {
27
50
  spam: { icon: 'i-ph-prohibit', tone: 'warning' },
28
51
  failed: { icon: 'i-ph-warning', tone: 'error' },
29
52
  unsubscribed: { icon: 'i-ph-user-minus', tone: 'warning' },
53
+ suppressed: { icon: 'i-ph-prohibit', tone: 'neutral' },
30
54
  }
31
55
 
32
- const DESCRIPTION_KEYS = ['reason', 'url', 'userAgent']
56
+ /** The happy path a send walks; pending steps are drawn from it. */
57
+ const HAPPY_PATH: SendStatus[] = ['queued', 'sent', 'delivered', 'opened']
58
+ /** A provider that reports no open stops the path at the delivery. */
59
+ const DELIVERY_PATH: SendStatus[] = ['queued', 'sent', 'delivered']
60
+ /** A provider that reports nothing (SMTP) stops it once the e-mail left. */
61
+ const HANDOFF_PATH: SendStatus[] = ['queued', 'sent']
62
+
63
+ /** Statuses after which nothing more is waited for. */
64
+ const TERMINAL_STATUSES: SendStatus[] = [
65
+ 'bounced',
66
+ 'spam',
67
+ 'failed',
68
+ 'unsubscribed',
69
+ SUPPRESSED_STATUS,
70
+ ]
33
71
 
34
- interface EventDetails {
35
- [key: string]: unknown
72
+ const SOFT_BOUNCE_TYPE = /soft|transient|temporary/i
73
+ const HARD_BOUNCE_TYPE = /hard|permanent/i
74
+ const SOFT_SMTP_ANSWER = /^\s*4\d\d\b|\b4\.\d{1,3}\.\d{1,3}\b/
75
+
76
+ const TITLE_KEY_PREFIX = 'dms_mailing.sends.timeline.'
77
+
78
+ /** Detail keys read off an event, in order; the first present one is shown. */
79
+ const DETAIL_KEYS = ['reason', 'smtp', 'code', 'url', 'userAgent', 'client']
80
+ const MONO_DETAIL_KEYS = new Set(['reason', 'smtp', 'code', 'url'])
81
+
82
+ type EventDetails = Record<string, unknown>
83
+
84
+ interface Detail {
85
+ text: string
86
+ isMono: boolean
36
87
  }
37
88
 
89
+ const NO_DETAIL: Detail = { text: '', isMono: false }
90
+
91
+ /** Steps whose reason the drawer tells in a banner, not as a raw detail. */
92
+ const WORDED_ELSEWHERE = new Set<SendStatus>([SUPPRESSED_STATUS])
93
+
38
94
  function parseDetails(raw: string): EventDetails {
39
95
  try {
40
96
  const parsed: unknown = raw ? JSON.parse(raw) : {}
@@ -46,23 +102,169 @@ function parseDetails(raw: string): EventDetails {
46
102
  }
47
103
  }
48
104
 
49
- function describe(details: EventDetails): string {
50
- const key = DESCRIPTION_KEYS.find((candidate) => details[candidate])
51
- return key ? String(details[key]) : ''
105
+ function eventDetail(details: EventDetails): Detail {
106
+ const key = DETAIL_KEYS.find((candidate) => details[candidate])
107
+ if (!key) return NO_DETAIL
108
+ return { text: String(details[key]), isMono: MONO_DETAIL_KEYS.has(key) }
109
+ }
110
+
111
+ /** What a step says beyond its event: the message id, the raw error. */
112
+ const SEND_DETAILS: Partial<
113
+ Record<SendStatus, (send: TimelineSend) => Detail>
114
+ > = {
115
+ sent: (send) => ({ text: send.providerMessageId ?? '', isMono: true }),
116
+ bounced: (send) => ({ text: send.error ?? '', isMono: true }),
117
+ failed: (send) => ({ text: send.error ?? '', isMono: true }),
118
+ }
119
+
120
+ /** Steps whose title names the provider when the send knows it. */
121
+ const PROVIDER_TITLE_KEYS: Partial<Record<SendStatus, string>> = {
122
+ sent: `${TITLE_KEY_PREFIX}sent_to`,
52
123
  }
53
124
 
125
+ interface StepTitle {
126
+ titleKey: string
127
+ titleParams: Record<string, string>
128
+ }
129
+
130
+ const textOf = (value: unknown): string =>
131
+ typeof value === 'string' ? value : ''
132
+
54
133
  /**
55
- * Orders a send's events oldest-first and resolves the icon, tone and one-line
56
- * description each row shows.
134
+ * Whether a bounce event was temporary: the provider's `bounceType` when it
135
+ * gives one, else the SMTP answer's class, else what the send recorded.
57
136
  */
58
- export function buildTimeline(events: SendEventRow[]): TimelineItem[] {
137
+ export function isSoftBounceEvent(
138
+ details: EventDetails,
139
+ send: TimelineSend,
140
+ ): boolean {
141
+ const bounceType = textOf(details.bounceType)
142
+ if (SOFT_BOUNCE_TYPE.test(bounceType)) return true
143
+ if (HARD_BOUNCE_TYPE.test(bounceType)) return false
144
+ const reason = textOf(details.reason)
145
+ return reason ? SOFT_SMTP_ANSWER.test(reason) : !!send.isSoftBounce
146
+ }
147
+
148
+ /** Event titles that depend on what the event says. */
149
+ const EVENT_TITLE_KEYS: Partial<
150
+ Record<SendStatus, (details: EventDetails, send: TimelineSend) => string>
151
+ > = {
152
+ bounced: (details, send) =>
153
+ `${TITLE_KEY_PREFIX}${isSoftBounceEvent(details, send) ? 'soft_bounce' : 'bounced'}`,
154
+ }
155
+
156
+ function titleOf(
157
+ type: SendStatus,
158
+ send: TimelineSend,
159
+ details: EventDetails = {},
160
+ ): StepTitle {
161
+ const providerKey = PROVIDER_TITLE_KEYS[type]
162
+ if (providerKey && send.provider)
163
+ return { titleKey: providerKey, titleParams: { provider: send.provider } }
164
+ const eventKey = EVENT_TITLE_KEYS[type]?.(details, send)
165
+ return { titleKey: eventKey ?? `${TITLE_KEY_PREFIX}${type}`, titleParams: {} }
166
+ }
167
+
168
+ interface StepInput {
169
+ id: string
170
+ type: SendStatus
171
+ at: string | null
172
+ detail: Detail
173
+ details?: EventDetails
174
+ }
175
+
176
+ function stepOf(input: StepInput, send: TimelineSend): TimelineItem {
177
+ return {
178
+ id: input.id,
179
+ type: input.type,
180
+ at: input.at,
181
+ ...TIMELINE_META[input.type],
182
+ ...titleOf(input.type, send, input.details),
183
+ detail: input.detail.text,
184
+ isDetailMono: input.detail.isMono,
185
+ isPending: input.at === null,
186
+ }
187
+ }
188
+
189
+ function happenedSteps(
190
+ events: SendEventRow[],
191
+ send: TimelineSend,
192
+ ): TimelineItem[] {
59
193
  return [...events]
60
194
  .sort((a, b) => new Date(a.at).getTime() - new Date(b.at).getTime())
61
- .map((event) => ({
62
- id: event._id,
63
- type: event.type,
64
- at: event.at,
65
- description: describe(parseDetails(event.json_details)),
66
- ...TIMELINE_META[event.type],
67
- }))
195
+ .map((event) => {
196
+ const details = parseDetails(event.json_details)
197
+ const fromEvent = WORDED_ELSEWHERE.has(event.type)
198
+ ? NO_DETAIL
199
+ : eventDetail(details)
200
+ const fromSend = SEND_DETAILS[event.type]?.(send) ?? NO_DETAIL
201
+ const detail = fromEvent.text ? fromEvent : fromSend
202
+ return stepOf(
203
+ { id: event._id, type: event.type, at: event.at, detail, details },
204
+ send,
205
+ )
206
+ })
207
+ }
208
+
209
+ /** The steps a send can still be seen walking with this provider. */
210
+ function trackedPath(tracking: TimelineTracking | null): SendStatus[] {
211
+ if (!tracking || tracking.openTracking) return HAPPY_PATH
212
+ return tracking.clickTracking ? DELIVERY_PATH : HANDOFF_PATH
213
+ }
214
+
215
+ function pendingSteps(
216
+ send: TimelineSend,
217
+ reached: SendStatus[],
218
+ tracking: TimelineTracking | null,
219
+ ): TimelineItem[] {
220
+ const isOver = [send.status, ...reached].some((type) =>
221
+ TERMINAL_STATUSES.includes(type),
222
+ )
223
+ if (isOver) return []
224
+ const path = trackedPath(tracking)
225
+ const furthest = Math.max(
226
+ ...reached.map((type) => path.indexOf(type as SendStatus)),
227
+ path.indexOf(send.status),
228
+ )
229
+ if (furthest < 0) return []
230
+ return path
231
+ .slice(furthest + 1)
232
+ .map((type) =>
233
+ stepOf(
234
+ { id: `pending-${type}`, type, at: null, detail: NO_DETAIL },
235
+ send,
236
+ ),
237
+ )
238
+ }
239
+
240
+ /**
241
+ * The steps of a send, oldest first: when it was queued (the send's own date
242
+ * unless an event says so), every recorded event with the detail worth
243
+ * reading (the raw SMTP answer, the provider's message id), then the steps of
244
+ * the happy path still to come, ghosted. A problem or an unsubscription has
245
+ * no step to come, and neither has a step the provider never reports. A send
246
+ * to a suppressed address was never queued: its one step says so.
247
+ */
248
+ export function buildTimeline(
249
+ events: SendEventRow[],
250
+ send: TimelineSend,
251
+ tracking: TimelineTracking | null = null,
252
+ ): TimelineItem[] {
253
+ const happened = happenedSteps(events, send)
254
+ const reached = happened.map((step) => step.type)
255
+ const isQueued = reached.includes('queued') || isSuppressedStatus(send.status)
256
+ const queued = isQueued
257
+ ? []
258
+ : [
259
+ stepOf(
260
+ {
261
+ id: `queued-${send._id}`,
262
+ type: 'queued',
263
+ at: send.createdAt,
264
+ detail: NO_DETAIL,
265
+ },
266
+ send,
267
+ ),
268
+ ]
269
+ return [...queued, ...happened, ...pendingSteps(send, reached, tracking)]
68
270
  }
@@ -1,24 +1,126 @@
1
- export type TextPartKind = 'text' | 'token'
1
+ import type { InjectionKey, Ref } from 'vue'
2
+ import { isPresent, readPath } from './paths'
3
+
4
+ /**
5
+ * `token` is a `{{path}}` the engine fills in; `invalid` looks like one but
6
+ * breaks its grammar, so the engine leaves it as typed.
7
+ */
8
+ export type TextPartKind = 'text' | 'token' | 'invalid'
2
9
 
3
10
  export interface TextPart {
4
11
  kind: TextPartKind
5
12
  value: string
6
13
  }
7
14
 
8
- const TOKEN_SPLIT = /(\{\{[^}]+\}\})/g
9
- const TOKEN_OPEN = '{{'
15
+ /** A token read against test data: its value when the data provides one. */
16
+ export interface ResolvedToken {
17
+ isPresent: boolean
18
+ text: string
19
+ }
20
+
21
+ /** The test data the paper renders tokens with; `null` shows the paths. */
22
+ export type TokenData = Record<string, unknown> | null
23
+
24
+ /**
25
+ * Provided by the canvas in Data mode so every `MailingTokenText` on the paper
26
+ * swaps its tokens for values without each block renderer passing data down.
27
+ */
28
+ export const TOKEN_DATA_KEY: InjectionKey<Ref<TokenData>> = Symbol(
29
+ 'dms-mailing-token-data',
30
+ )
31
+
32
+ /**
33
+ * The engine's token grammar (`src/engine/tokens.ts` `TOKEN_PATTERN`), with
34
+ * a capture around the whole token so `split` keeps it.
35
+ */
36
+ const TOKEN_SPLIT = /(\{\{\s*[A-Za-z0-9_.[\]]+\s*\}\})/
37
+ const TOKEN_LIKE_SPLIT = /(\{\{[^}]*\}\})/
10
38
  const TOKEN_MARKER_LENGTH = 2
39
+ const SPLIT_TOKEN_STEP = 2
11
40
 
12
- export function splitTokens(text: string): TextPart[] {
41
+ /**
42
+ * How a token without a value prints, as the engine's interpolation modes:
43
+ * `preview` keeps it visible, `send` blanks it as a real e-mail does.
44
+ */
45
+ export type InterpolationMode = 'preview' | 'send'
46
+
47
+ const MISSING_TOKEN: Record<InterpolationMode, (path: string) => string> = {
48
+ preview: (path) => `{{${path}}}`,
49
+ send: () => '',
50
+ }
51
+
52
+ /** A piece of text with tokens kept exactly as typed (spaces included). */
53
+ export interface RawSegment {
54
+ kind: TextPartKind
55
+ raw: string
56
+ /** The trimmed path of a token; the raw text otherwise. */
57
+ value: string
58
+ }
59
+
60
+ const isSplitMatch = (index: number): boolean => index % SPLIT_TOKEN_STEP === 1
61
+
62
+ function literalSegments(text: string): RawSegment[] {
13
63
  return text
14
- .split(TOKEN_SPLIT)
15
- .filter(Boolean)
16
- .map((part) =>
17
- part.startsWith(TOKEN_OPEN)
18
- ? {
64
+ .split(TOKEN_LIKE_SPLIT)
65
+ .map((raw, index) => ({
66
+ kind: isSplitMatch(index) ? ('invalid' as const) : ('text' as const),
67
+ raw,
68
+ value: raw,
69
+ }))
70
+ .filter((segment) => segment.raw)
71
+ }
72
+
73
+ /**
74
+ * Splits `text` into literal text, tokens and token look-alikes, keeping each
75
+ * token's raw spelling: the token field's highlight layer must line up
76
+ * character for character with the textarea.
77
+ */
78
+ export function splitRawSegments(text: string): RawSegment[] {
79
+ return text.split(TOKEN_SPLIT).flatMap((raw, index) =>
80
+ isSplitMatch(index)
81
+ ? [
82
+ {
19
83
  kind: 'token' as const,
20
- value: part.slice(TOKEN_MARKER_LENGTH, -TOKEN_MARKER_LENGTH).trim(),
21
- }
22
- : { kind: 'text' as const, value: part },
23
- )
84
+ raw,
85
+ value: raw.slice(TOKEN_MARKER_LENGTH, -TOKEN_MARKER_LENGTH).trim(),
86
+ },
87
+ ]
88
+ : literalSegments(raw),
89
+ )
90
+ }
91
+
92
+ export function splitTokens(text: string): TextPart[] {
93
+ return splitRawSegments(text).map(({ kind, value }) => ({ kind, value }))
94
+ }
95
+
96
+ function formatValue(value: unknown): string {
97
+ return value instanceof Date ? value.toISOString() : String(value)
98
+ }
99
+
100
+ /** Reads one token path in `data`, formatted like the engine prints it. */
101
+ export function resolveToken(
102
+ path: string,
103
+ data: Record<string, unknown>,
104
+ ): ResolvedToken {
105
+ const value = readPath(data, path)
106
+ return isPresent(value)
107
+ ? { isPresent: true, text: formatValue(value) }
108
+ : { isPresent: false, text: path }
109
+ }
110
+
111
+ /** Replaces every token of `text` with its value, as the engine does. */
112
+ export function interpolateText(
113
+ text: string,
114
+ data: Record<string, unknown>,
115
+ mode: InterpolationMode = 'preview',
116
+ ): string {
117
+ return splitTokens(text)
118
+ .map((part) => {
119
+ if (part.kind !== 'token') return part.value
120
+ const resolved = resolveToken(part.value, data)
121
+ return resolved.isPresent
122
+ ? resolved.text
123
+ : MISSING_TOKEN[mode](part.value)
124
+ })
125
+ .join('')
24
126
  }
@@ -0,0 +1,23 @@
1
+ import { VARIABLE_TYPES, type VariableType } from '../types/mailing'
2
+
3
+ const VARIABLE_TYPE_KEY_PREFIX = 'dms_mailing.variable_types'
4
+
5
+ export interface VariableTypeItem {
6
+ value: VariableType
7
+ label: string
8
+ }
9
+
10
+ /** The i18n key naming a variable type for a writer ("Amount" for `money`). */
11
+ export function variableTypeLabelKey(type: VariableType): string {
12
+ return `${VARIABLE_TYPE_KEY_PREFIX}.${type}`
13
+ }
14
+
15
+ /** The type picker's items: the stored value, the label a writer reads. */
16
+ export function variableTypeItems(
17
+ translate: (key: string) => string,
18
+ ): VariableTypeItem[] {
19
+ return VARIABLE_TYPES.map((type) => ({
20
+ value: type,
21
+ label: translate(variableTypeLabelKey(type)),
22
+ }))
23
+ }