@antelopejs/dms-mailing 0.3.4 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (705) hide show
  1. package/README.md +232 -22
  2. package/dist/automation/email-event-trigger.js +2 -2
  3. package/dist/automation/email-event-trigger.js.map +1 -1
  4. package/dist/automation/send-template-action.js +2 -2
  5. package/dist/automation/send-template-action.js.map +1 -1
  6. package/dist/config.js +3 -1
  7. package/dist/config.js.map +1 -1
  8. package/dist/constants.d.ts +10 -1
  9. package/dist/constants.js +15 -3
  10. package/dist/constants.js.map +1 -1
  11. package/dist/crons/attention.d.ts +3 -0
  12. package/dist/crons/attention.js +19 -0
  13. package/dist/crons/attention.js.map +1 -0
  14. package/dist/crons/index.d.ts +2 -0
  15. package/dist/crons/index.js +2 -0
  16. package/dist/crons/index.js.map +1 -1
  17. package/dist/crons/jobs.d.ts +12 -0
  18. package/dist/crons/jobs.js +27 -0
  19. package/dist/crons/jobs.js.map +1 -0
  20. package/dist/crons/retention.d.ts +2 -4
  21. package/dist/crons/retention.js +7 -22
  22. package/dist/crons/retention.js.map +1 -1
  23. package/dist/data/displays.d.ts +5 -0
  24. package/dist/data/displays.js +23 -0
  25. package/dist/data/displays.js.map +1 -0
  26. package/dist/data/index.d.ts +1 -0
  27. package/dist/data/index.js +1 -0
  28. package/dist/data/index.js.map +1 -1
  29. package/dist/data/locale-fallback.d.ts +3 -0
  30. package/dist/data/locale-fallback.js +20 -0
  31. package/dist/data/locale-fallback.js.map +1 -0
  32. package/dist/data/read-only-routes.d.ts +7 -0
  33. package/dist/data/read-only-routes.js +13 -0
  34. package/dist/data/read-only-routes.js.map +1 -0
  35. package/dist/data/sends-table.d.ts +19 -5
  36. package/dist/data/sends-table.js +185 -17
  37. package/dist/data/sends-table.js.map +1 -1
  38. package/dist/data/suppressions-table.d.ts +24 -0
  39. package/dist/data/suppressions-table.js +120 -0
  40. package/dist/data/suppressions-table.js.map +1 -0
  41. package/dist/data/templates-table.d.ts +20 -2
  42. package/dist/data/templates-table.js +188 -31
  43. package/dist/data/templates-table.js.map +1 -1
  44. package/dist/data-types/categories-type.d.ts +11 -13
  45. package/dist/data-types/categories-type.js +3 -3
  46. package/dist/data-types/categories-type.js.map +1 -1
  47. package/dist/data-types/category-type.js +1 -1
  48. package/dist/data-types/category-type.js.map +1 -1
  49. package/dist/db/models/index.d.ts +3 -0
  50. package/dist/db/models/index.js +3 -0
  51. package/dist/db/models/index.js.map +1 -1
  52. package/dist/db/models/sends.model.d.ts +12 -1
  53. package/dist/db/models/sends.model.js +64 -0
  54. package/dist/db/models/sends.model.js.map +1 -1
  55. package/dist/db/models/settings.model.d.ts +0 -1
  56. package/dist/db/models/settings.model.js +0 -7
  57. package/dist/db/models/settings.model.js.map +1 -1
  58. package/dist/db/models/suppressions.model.d.ts +28 -0
  59. package/dist/db/models/suppressions.model.js +46 -0
  60. package/dist/db/models/suppressions.model.js.map +1 -0
  61. package/dist/db/models/template-versions.model.d.ts +24 -0
  62. package/dist/db/models/template-versions.model.js +17 -0
  63. package/dist/db/models/template-versions.model.js.map +1 -0
  64. package/dist/db/models/templates.model.d.ts +2 -0
  65. package/dist/db/models/templates.model.js +3 -0
  66. package/dist/db/models/templates.model.js.map +1 -1
  67. package/dist/db/models/webhook-status.model.d.ts +24 -0
  68. package/dist/db/models/webhook-status.model.js +21 -0
  69. package/dist/db/models/webhook-status.model.js.map +1 -0
  70. package/dist/db/tables/index.d.ts +3 -0
  71. package/dist/db/tables/index.js +3 -0
  72. package/dist/db/tables/index.js.map +1 -1
  73. package/dist/db/tables/send-events.table.d.ts +2 -2
  74. package/dist/db/tables/send-events.table.js.map +1 -1
  75. package/dist/db/tables/sends.table.d.ts +10 -2
  76. package/dist/db/tables/sends.table.js +34 -1
  77. package/dist/db/tables/sends.table.js.map +1 -1
  78. package/dist/db/tables/suppressions.table.d.ts +12 -0
  79. package/dist/db/tables/suppressions.table.js +53 -0
  80. package/dist/db/tables/suppressions.table.js.map +1 -0
  81. package/dist/db/tables/template-versions.table.d.ts +13 -0
  82. package/dist/db/tables/template-versions.table.js +59 -0
  83. package/dist/db/tables/template-versions.table.js.map +1 -0
  84. package/dist/db/tables/templates.table.d.ts +11 -0
  85. package/dist/db/tables/templates.table.js +45 -0
  86. package/dist/db/tables/templates.table.js.map +1 -1
  87. package/dist/db/tables/webhook-status.table.d.ts +14 -0
  88. package/dist/db/tables/webhook-status.table.js +58 -0
  89. package/dist/db/tables/webhook-status.table.js.map +1 -0
  90. package/dist/engine/changes.d.ts +12 -0
  91. package/dist/engine/changes.js +107 -0
  92. package/dist/engine/changes.js.map +1 -0
  93. package/dist/engine/conditions.js +0 -2
  94. package/dist/engine/conditions.js.map +1 -1
  95. package/dist/engine/coverage.d.ts +1 -0
  96. package/dist/engine/coverage.js +6 -2
  97. package/dist/engine/coverage.js.map +1 -1
  98. package/dist/engine/fallback-html.d.ts +1 -1
  99. package/dist/engine/fallback-html.js +51 -34
  100. package/dist/engine/fallback-html.js.map +1 -1
  101. package/dist/engine/index.d.ts +3 -0
  102. package/dist/engine/index.js +3 -0
  103. package/dist/engine/index.js.map +1 -1
  104. package/dist/engine/plain-text.d.ts +3 -0
  105. package/dist/engine/plain-text.js +60 -0
  106. package/dist/engine/plain-text.js.map +1 -0
  107. package/dist/engine/resolve.d.ts +2 -1
  108. package/dist/engine/resolve.js +17 -9
  109. package/dist/engine/resolve.js.map +1 -1
  110. package/dist/engine/system-variables.d.ts +10 -0
  111. package/dist/engine/system-variables.js +70 -0
  112. package/dist/engine/system-variables.js.map +1 -0
  113. package/dist/engine/tokens.d.ts +2 -1
  114. package/dist/engine/tokens.js +6 -2
  115. package/dist/engine/tokens.js.map +1 -1
  116. package/dist/hooks/tenant-export.js +13 -1
  117. package/dist/hooks/tenant-export.js.map +1 -1
  118. package/dist/implementations/dms-mailing/index.d.ts +1 -1
  119. package/dist/implementations/dms-mailing/index.js +2 -2
  120. package/dist/implementations/dms-mailing/index.js.map +1 -1
  121. package/dist/index.js +13 -11
  122. package/dist/index.js.map +1 -1
  123. package/dist/pages/editor.js +9 -2
  124. package/dist/pages/editor.js.map +1 -1
  125. package/dist/pages/index.d.ts +1 -0
  126. package/dist/pages/index.js +1 -0
  127. package/dist/pages/index.js.map +1 -1
  128. package/dist/pages/module.d.ts +2 -0
  129. package/dist/pages/module.js +26 -2
  130. package/dist/pages/module.js.map +1 -1
  131. package/dist/pages/overview.d.ts +2 -6
  132. package/dist/pages/overview.js +109 -52
  133. package/dist/pages/overview.js.map +1 -1
  134. package/dist/pages/sends.d.ts +3 -4
  135. package/dist/pages/sends.js +197 -60
  136. package/dist/pages/sends.js.map +1 -1
  137. package/dist/pages/settings/form.js +114 -53
  138. package/dist/pages/settings/form.js.map +1 -1
  139. package/dist/pages/settings/page.d.ts +0 -1
  140. package/dist/pages/settings/page.js +8 -11
  141. package/dist/pages/settings/page.js.map +1 -1
  142. package/dist/pages/suppressions.d.ts +9 -0
  143. package/dist/pages/suppressions.js +151 -0
  144. package/dist/pages/suppressions.js.map +1 -0
  145. package/dist/pages/templates.d.ts +0 -1
  146. package/dist/pages/templates.js +114 -40
  147. package/dist/pages/templates.js.map +1 -1
  148. package/dist/permissions.js +2 -2
  149. package/dist/permissions.js.map +1 -1
  150. package/dist/quick-actions.js +7 -4
  151. package/dist/quick-actions.js.map +1 -1
  152. package/dist/routes/events.d.ts +8 -5
  153. package/dist/routes/events.js +55 -24
  154. package/dist/routes/events.js.map +1 -1
  155. package/dist/routes/index.d.ts +2 -0
  156. package/dist/routes/index.js +2 -0
  157. package/dist/routes/index.js.map +1 -1
  158. package/dist/routes/metrics.d.ts +60 -23
  159. package/dist/routes/metrics.js +231 -83
  160. package/dist/routes/metrics.js.map +1 -1
  161. package/dist/routes/period.d.ts +2 -1
  162. package/dist/routes/period.js +9 -3
  163. package/dist/routes/period.js.map +1 -1
  164. package/dist/routes/sends.d.ts +49 -8
  165. package/dist/routes/sends.js +239 -24
  166. package/dist/routes/sends.js.map +1 -1
  167. package/dist/routes/settings.d.ts +44 -5
  168. package/dist/routes/settings.js +190 -15
  169. package/dist/routes/settings.js.map +1 -1
  170. package/dist/routes/suppressions.d.ts +16 -0
  171. package/dist/routes/suppressions.js +92 -0
  172. package/dist/routes/suppressions.js.map +1 -0
  173. package/dist/routes/template-versions.d.ts +28 -0
  174. package/dist/routes/template-versions.js +87 -0
  175. package/dist/routes/template-versions.js.map +1 -0
  176. package/dist/routes/templates.d.ts +60 -6
  177. package/dist/routes/templates.js +204 -35
  178. package/dist/routes/templates.js.map +1 -1
  179. package/dist/services/attention.d.ts +19 -6
  180. package/dist/services/attention.js +177 -12
  181. package/dist/services/attention.js.map +1 -1
  182. package/dist/services/catalog.d.ts +3 -0
  183. package/dist/services/catalog.js +25 -0
  184. package/dist/services/catalog.js.map +1 -0
  185. package/dist/services/categories.d.ts +8 -32
  186. package/dist/services/categories.js +14 -25
  187. package/dist/services/categories.js.map +1 -1
  188. package/dist/services/checked-send.d.ts +9 -0
  189. package/dist/services/checked-send.js +54 -0
  190. package/dist/services/checked-send.js.map +1 -0
  191. package/dist/services/events.d.ts +5 -0
  192. package/dist/services/events.js +102 -9
  193. package/dist/services/events.js.map +1 -1
  194. package/dist/services/metrics.d.ts +17 -6
  195. package/dist/services/metrics.js +57 -18
  196. package/dist/services/metrics.js.map +1 -1
  197. package/dist/services/notify.d.ts +18 -10
  198. package/dist/services/notify.js +82 -37
  199. package/dist/services/notify.js.map +1 -1
  200. package/dist/services/provider-banner.d.ts +14 -0
  201. package/dist/services/provider-banner.js +115 -0
  202. package/dist/services/provider-banner.js.map +1 -0
  203. package/dist/services/render.js.map +1 -1
  204. package/dist/services/send-stat-items.d.ts +7 -0
  205. package/dist/services/send-stat-items.js +127 -0
  206. package/dist/services/send-stat-items.js.map +1 -0
  207. package/dist/services/send-stats.d.ts +23 -0
  208. package/dist/services/send-stats.js +63 -0
  209. package/dist/services/send-stats.js.map +1 -0
  210. package/dist/services/send.d.ts +18 -3
  211. package/dist/services/send.js +81 -17
  212. package/dist/services/send.js.map +1 -1
  213. package/dist/services/settings.d.ts +5 -0
  214. package/dist/services/settings.js +28 -7
  215. package/dist/services/settings.js.map +1 -1
  216. package/dist/services/starters.d.ts +15 -0
  217. package/dist/services/starters.js +264 -0
  218. package/dist/services/starters.js.map +1 -0
  219. package/dist/services/suppressions.d.ts +20 -0
  220. package/dist/services/suppressions.js +53 -0
  221. package/dist/services/suppressions.js.map +1 -0
  222. package/dist/services/template-attention.d.ts +4 -0
  223. package/dist/services/template-attention.js +43 -0
  224. package/dist/services/template-attention.js.map +1 -0
  225. package/dist/services/template-stats.d.ts +27 -0
  226. package/dist/services/template-stats.js +59 -0
  227. package/dist/services/template-stats.js.map +1 -0
  228. package/dist/services/templates.d.ts +31 -4
  229. package/dist/services/templates.js +240 -21
  230. package/dist/services/templates.js.map +1 -1
  231. package/dist/services/webhook-status.d.ts +27 -0
  232. package/dist/services/webhook-status.js +134 -0
  233. package/dist/services/webhook-status.js.map +1 -0
  234. package/dist/types/blocks.d.ts +1 -0
  235. package/dist/types/index.d.ts +2 -0
  236. package/dist/types/index.js +2 -0
  237. package/dist/types/index.js.map +1 -1
  238. package/dist/types/sends.d.ts +10 -1
  239. package/dist/types/sends.js +35 -2
  240. package/dist/types/sends.js.map +1 -1
  241. package/dist/types/suppressions.d.ts +9 -0
  242. package/dist/types/suppressions.js +10 -0
  243. package/dist/types/suppressions.js.map +1 -0
  244. package/dist/types/webhook.d.ts +17 -0
  245. package/dist/types/webhook.js +3 -0
  246. package/dist/types/webhook.js.map +1 -0
  247. package/dist/validation/settings.schema.d.ts +144 -9
  248. package/dist/validation/settings.schema.js +113 -16
  249. package/dist/validation/settings.schema.js.map +1 -1
  250. package/dist/validation/templates.schema.d.ts +137 -10
  251. package/dist/validation/templates.schema.js +28 -2
  252. package/dist/validation/templates.schema.js.map +1 -1
  253. package/frontend-vue/app/build/components/FormFieldRow.vue +90 -0
  254. package/frontend-vue/app/build/components/FormRows.vue +26 -0
  255. package/frontend-vue/app/build/components/RequiredLegend.vue +11 -0
  256. package/frontend-vue/app/components/CategoriesInput.vue +323 -77
  257. package/frontend-vue/app/components/CategoryInput.vue +13 -14
  258. package/frontend-vue/app/components/CategoryLabel.vue +34 -0
  259. package/frontend-vue/app/components/DuplicateModal.vue +201 -0
  260. package/frontend-vue/app/components/Editor.vue +55 -20
  261. package/frontend-vue/app/components/FixAddressModal.vue +133 -0
  262. package/frontend-vue/app/components/FunnelCard.vue +146 -43
  263. package/frontend-vue/app/components/LocaleSelect.vue +98 -0
  264. package/frontend-vue/app/components/NewTemplateModal.vue +388 -102
  265. package/frontend-vue/app/components/NewTemplateSourceTile.vue +70 -0
  266. package/frontend-vue/app/components/OverviewGate.vue +220 -0
  267. package/frontend-vue/app/components/PageHeader.vue +98 -0
  268. package/frontend-vue/app/components/RealSendModal.vue +555 -0
  269. package/frontend-vue/app/components/RecipientsField.vue +41 -0
  270. package/frontend-vue/app/components/RenderingModal.vue +189 -0
  271. package/frontend-vue/app/components/RetentionInput.vue +212 -0
  272. package/frontend-vue/app/components/SendDrawer.vue +542 -209
  273. package/frontend-vue/app/components/StarterGallery.vue +153 -0
  274. package/frontend-vue/app/components/SystemVariables.vue +63 -0
  275. package/frontend-vue/app/components/TemplateCard.vue +218 -57
  276. package/frontend-vue/app/components/TemplateDrawer.vue +785 -274
  277. package/frontend-vue/app/components/TemplateGallery.vue +312 -56
  278. package/frontend-vue/app/components/TemplatePreviewFrame.vue +112 -22
  279. package/frontend-vue/app/components/TestSendModal.vue +266 -95
  280. package/frontend-vue/app/components/TokenText.vue +61 -10
  281. package/frontend-vue/app/components/VariablePath.vue +20 -0
  282. package/frontend-vue/app/components/VersionHistory.vue +196 -0
  283. package/frontend-vue/app/components/VersionPreview.vue +164 -0
  284. package/frontend-vue/app/components/WebhookStatus.vue +130 -0
  285. package/frontend-vue/app/components/blocks/Button.vue +1 -1
  286. package/frontend-vue/app/components/blocks/Code.vue +1 -1
  287. package/frontend-vue/app/components/blocks/Footer.vue +56 -7
  288. package/frontend-vue/app/components/blocks/Heading.vue +1 -1
  289. package/frontend-vue/app/components/blocks/Hero.vue +13 -7
  290. package/frontend-vue/app/components/blocks/List.vue +50 -6
  291. package/frontend-vue/app/components/blocks/Paragraph.vue +1 -1
  292. package/frontend-vue/app/components/blocks/Total.vue +3 -3
  293. package/frontend-vue/app/components/editor/EditorAutocompleteMenu.vue +50 -0
  294. package/frontend-vue/app/components/editor/EditorBlock.vue +37 -90
  295. package/frontend-vue/app/components/editor/EditorBlockFields.vue +140 -0
  296. package/frontend-vue/app/components/editor/EditorBlockInspector.vue +92 -0
  297. package/frontend-vue/app/components/editor/EditorBlockList.vue +32 -25
  298. package/frontend-vue/app/components/editor/EditorBlockPicker.vue +58 -0
  299. package/frontend-vue/app/components/editor/EditorBlockTools.vue +71 -0
  300. package/frontend-vue/app/components/editor/EditorCanvas.vue +105 -70
  301. package/frontend-vue/app/components/editor/EditorCompare.vue +211 -0
  302. package/frontend-vue/app/components/editor/EditorConditionFields.vue +56 -46
  303. package/frontend-vue/app/components/editor/EditorConditionInspector.vue +54 -0
  304. package/frontend-vue/app/components/editor/EditorEnvelope.vue +77 -0
  305. package/frontend-vue/app/components/editor/EditorIfBlock.vue +36 -86
  306. package/frontend-vue/app/components/editor/EditorInsertPoint.vue +64 -0
  307. package/frontend-vue/app/components/editor/EditorInspector.vue +80 -0
  308. package/frontend-vue/app/components/editor/EditorLeftPane.vue +103 -0
  309. package/frontend-vue/app/components/editor/EditorLifecycle.vue +67 -0
  310. package/frontend-vue/app/components/editor/EditorLoading.vue +23 -0
  311. package/frontend-vue/app/components/editor/EditorLocaleSwitch.vue +61 -0
  312. package/frontend-vue/app/components/editor/EditorMissingLocale.vue +72 -48
  313. package/frontend-vue/app/components/editor/EditorMissingPolicy.vue +42 -0
  314. package/frontend-vue/app/components/editor/EditorNotice.vue +127 -0
  315. package/frontend-vue/app/components/editor/EditorOutline.vue +65 -0
  316. package/frontend-vue/app/components/editor/EditorPalette.vue +95 -42
  317. package/frontend-vue/app/components/editor/EditorPaletteGroup.vue +66 -0
  318. package/frontend-vue/app/components/editor/EditorPublishReview.vue +102 -0
  319. package/frontend-vue/app/components/editor/EditorSaveState.vue +88 -0
  320. package/frontend-vue/app/components/editor/EditorShortcutHint.vue +17 -0
  321. package/frontend-vue/app/components/editor/EditorSimChip.vue +51 -0
  322. package/frontend-vue/app/components/editor/EditorTemplatePane.vue +131 -0
  323. package/frontend-vue/app/components/editor/EditorTestDataPane.vue +98 -0
  324. package/frontend-vue/app/components/editor/EditorTitle.vue +82 -0
  325. package/frontend-vue/app/components/editor/EditorTokenField.vue +224 -0
  326. package/frontend-vue/app/components/editor/EditorToolbar.vue +34 -134
  327. package/frontend-vue/app/components/editor/EditorVariableForm.vue +100 -0
  328. package/frontend-vue/app/components/editor/EditorVariableRow.vue +87 -0
  329. package/frontend-vue/app/components/editor/EditorVariablesPane.vue +161 -0
  330. package/frontend-vue/app/components/editor/EditorViewSwitches.vue +76 -0
  331. package/frontend-vue/app/components/editor/EditorVisibility.vue +101 -0
  332. package/frontend-vue/app/components/editor/EditorWorkspace.vue +62 -149
  333. package/frontend-vue/app/composables/useBlockContext.ts +16 -0
  334. package/frontend-vue/app/composables/useEditorActions.ts +381 -0
  335. package/frontend-vue/app/composables/useEditorAnnouncements.ts +51 -0
  336. package/frontend-vue/app/composables/useEditorContext.ts +30 -23
  337. package/frontend-vue/app/composables/useEditorSession.ts +299 -0
  338. package/frontend-vue/app/composables/useEditorShortcuts.ts +81 -0
  339. package/frontend-vue/app/composables/useLocalizedDefaults.ts +17 -0
  340. package/frontend-vue/app/composables/useMailingApi.ts +142 -27
  341. package/frontend-vue/app/composables/useOverviewSetup.ts +13 -0
  342. package/frontend-vue/app/composables/useReplayToast.ts +72 -0
  343. package/frontend-vue/app/composables/useTemplateCategories.ts +35 -0
  344. package/frontend-vue/app/composables/useTemplateDrawer.ts +196 -62
  345. package/frontend-vue/app/composables/useTemplateEditor.ts +318 -86
  346. package/frontend-vue/app/emails/EmailMailingTemplate.vue +16 -4
  347. package/frontend-vue/app/emails/components/MailingBlocks.vue +96 -38
  348. package/frontend-vue/app/emails/components/MailingTextLines.vue +20 -0
  349. package/frontend-vue/app/plugins/column-displays.ts +65 -0
  350. package/frontend-vue/app/plugins/{template-gallery-display.client.ts → template-gallery-display.ts} +1 -1
  351. package/frontend-vue/app/types/mailing.ts +193 -18
  352. package/frontend-vue/app/utils/api-error.ts +33 -0
  353. package/frontend-vue/app/utils/autocomplete.ts +120 -0
  354. package/frontend-vue/app/utils/blocks/components.ts +10 -10
  355. package/frontend-vue/app/utils/blocks/dnd.ts +7 -3
  356. package/frontend-vue/app/utils/blocks/factory.ts +199 -38
  357. package/frontend-vue/app/utils/blocks/fields.ts +39 -19
  358. package/frontend-vue/app/utils/blocks/index.ts +1 -0
  359. package/frontend-vue/app/utils/blocks/outline.ts +91 -0
  360. package/frontend-vue/app/utils/blocks/tree.ts +114 -2
  361. package/frontend-vue/app/utils/categories.ts +201 -15
  362. package/frontend-vue/app/utils/changes.ts +152 -0
  363. package/frontend-vue/app/utils/conditions.ts +25 -19
  364. package/frontend-vue/app/utils/editor-checks.ts +266 -0
  365. package/frontend-vue/app/utils/editor-route.ts +43 -4
  366. package/frontend-vue/app/utils/funnel.ts +74 -0
  367. package/frontend-vue/app/utils/gallery.ts +179 -1
  368. package/frontend-vue/app/utils/history.ts +66 -0
  369. package/frontend-vue/app/utils/lifecycle.ts +185 -0
  370. package/frontend-vue/app/utils/locales.ts +107 -2
  371. package/frontend-vue/app/utils/overview-setup.ts +83 -0
  372. package/frontend-vue/app/utils/pages.ts +5 -0
  373. package/frontend-vue/app/utils/paths.ts +51 -15
  374. package/frontend-vue/app/utils/period.ts +62 -22
  375. package/frontend-vue/app/utils/problems.ts +181 -0
  376. package/frontend-vue/app/utils/recipients.ts +49 -0
  377. package/frontend-vue/app/utils/rendering-title.ts +23 -0
  378. package/frontend-vue/app/utils/send-actions.ts +207 -0
  379. package/frontend-vue/app/utils/send-link.ts +28 -0
  380. package/frontend-vue/app/utils/send-snippet.ts +97 -0
  381. package/frontend-vue/app/utils/send-stats.ts +24 -0
  382. package/frontend-vue/app/utils/send-values.ts +156 -0
  383. package/frontend-vue/app/utils/shortcuts.ts +113 -0
  384. package/frontend-vue/app/utils/slug.ts +82 -0
  385. package/frontend-vue/app/utils/starters.ts +23 -0
  386. package/frontend-vue/app/utils/suppression.ts +112 -0
  387. package/frontend-vue/app/utils/system-variables.ts +165 -0
  388. package/frontend-vue/app/utils/template-attention.ts +104 -0
  389. package/frontend-vue/app/utils/timeline.ts +221 -19
  390. package/frontend-vue/app/utils/tokens.ts +115 -13
  391. package/frontend-vue/app/utils/variable-types.ts +23 -0
  392. package/frontend-vue/app/utils/variables.ts +208 -1
  393. package/frontend-vue/app/utils/version-history.ts +53 -0
  394. package/frontend-vue/app/utils/webhook-status.ts +170 -0
  395. package/frontend-vue/dms.frontend.ts +19 -11
  396. package/frontend-vue/i18n/locales/mailing-en-GB.json +1173 -161
  397. package/frontend-vue/i18n/locales/mailing-fr-FR.json +1220 -208
  398. package/frontend-vue/package.json +2 -2
  399. package/frontend-vue/pnpm-lock.yaml +46 -161
  400. package/frontend-vue/tests/api-error.test.ts +27 -0
  401. package/frontend-vue/tests/autocomplete.test.ts +84 -0
  402. package/frontend-vue/tests/block-components.test.ts +1 -1
  403. package/frontend-vue/tests/block-fields.test.ts +12 -2
  404. package/frontend-vue/tests/blocks-factory.test.ts +46 -0
  405. package/frontend-vue/tests/blocks-outline.test.ts +102 -0
  406. package/frontend-vue/tests/blocks-tree.test.ts +28 -1
  407. package/frontend-vue/tests/categories-input.test.ts +139 -0
  408. package/frontend-vue/tests/categories.test.ts +148 -15
  409. package/frontend-vue/tests/changes.test.ts +136 -0
  410. package/frontend-vue/tests/column-displays.test.ts +73 -0
  411. package/frontend-vue/tests/conditions.test.ts +49 -19
  412. package/frontend-vue/tests/dnd.test.ts +10 -0
  413. package/frontend-vue/tests/editor-block-list.test.ts +24 -2
  414. package/frontend-vue/tests/editor-checks.test.ts +178 -0
  415. package/frontend-vue/tests/editor-route.test.ts +37 -9
  416. package/frontend-vue/tests/editor-session.test.ts +206 -0
  417. package/frontend-vue/tests/footer-block.test.ts +88 -0
  418. package/frontend-vue/tests/funnel-card.test.ts +116 -0
  419. package/frontend-vue/tests/funnel.test.ts +81 -0
  420. package/frontend-vue/tests/gallery.test.ts +124 -2
  421. package/frontend-vue/tests/helpers/nuxt-globals.ts +232 -0
  422. package/frontend-vue/tests/history.test.ts +48 -0
  423. package/frontend-vue/tests/i18n-editor.test.ts +38 -0
  424. package/frontend-vue/tests/i18n.test.ts +55 -0
  425. package/frontend-vue/tests/lifecycle.test.ts +145 -0
  426. package/frontend-vue/tests/locales.test.ts +60 -0
  427. package/frontend-vue/tests/localized-defaults.test.ts +54 -0
  428. package/frontend-vue/tests/new-template-modal.test.ts +210 -0
  429. package/frontend-vue/tests/overview-gate.test.ts +146 -0
  430. package/frontend-vue/tests/overview-setup.test.ts +78 -0
  431. package/frontend-vue/tests/paths.test.ts +23 -0
  432. package/frontend-vue/tests/period.test.ts +59 -10
  433. package/frontend-vue/tests/problems.test.ts +153 -0
  434. package/frontend-vue/tests/recipients.test.ts +47 -0
  435. package/frontend-vue/tests/rendering-title.test.ts +24 -0
  436. package/frontend-vue/tests/replay-toast.test.ts +67 -0
  437. package/frontend-vue/tests/retention-input.test.ts +129 -0
  438. package/frontend-vue/tests/send-actions.test.ts +128 -0
  439. package/frontend-vue/tests/send-modals.test.ts +415 -0
  440. package/frontend-vue/tests/send-snippet.test.ts +51 -0
  441. package/frontend-vue/tests/send-stats.test.ts +12 -0
  442. package/frontend-vue/tests/send-values.test.ts +119 -0
  443. package/frontend-vue/tests/shortcuts.test.ts +75 -0
  444. package/frontend-vue/tests/slug.test.ts +88 -0
  445. package/frontend-vue/tests/suppression.test.ts +159 -0
  446. package/frontend-vue/tests/system-variables.test.ts +133 -0
  447. package/frontend-vue/tests/template-attention.test.ts +123 -0
  448. package/frontend-vue/tests/template-drawer.test.ts +449 -0
  449. package/frontend-vue/tests/template-editor.test.ts +218 -9
  450. package/frontend-vue/tests/template-gallery-display.test.ts +32 -0
  451. package/frontend-vue/tests/template-gallery.test.ts +370 -0
  452. package/frontend-vue/tests/template-overlays.test.ts +125 -0
  453. package/frontend-vue/tests/timeline.test.ts +175 -20
  454. package/frontend-vue/tests/tokens.test.ts +79 -1
  455. package/frontend-vue/tests/variable-path.test.ts +22 -0
  456. package/frontend-vue/tests/variable-types.test.ts +20 -0
  457. package/frontend-vue/tests/variables.test.ts +90 -2
  458. package/frontend-vue/tests/version-history.test.ts +41 -0
  459. package/frontend-vue/tests/webhook-status.test.ts +108 -0
  460. package/package.json +11 -9
  461. package/src/automation/email-event-trigger.ts +3 -3
  462. package/src/automation/send-template-action.ts +7 -5
  463. package/src/config.ts +3 -1
  464. package/src/constants.ts +18 -2
  465. package/src/crons/attention.ts +21 -0
  466. package/src/crons/index.ts +2 -0
  467. package/src/crons/jobs.ts +35 -0
  468. package/src/crons/retention.ts +7 -22
  469. package/src/data/displays.ts +18 -0
  470. package/src/data/index.ts +1 -0
  471. package/src/data/locale-fallback.ts +26 -0
  472. package/src/data/read-only-routes.ts +14 -0
  473. package/src/data/sends-table.ts +204 -19
  474. package/src/data/suppressions-table.ts +127 -0
  475. package/src/data/templates-table.ts +295 -63
  476. package/src/data-types/categories-type.ts +4 -5
  477. package/src/data-types/category-type.ts +1 -1
  478. package/src/db/models/index.ts +3 -0
  479. package/src/db/models/sends.model.ts +102 -1
  480. package/src/db/models/settings.model.ts +2 -12
  481. package/src/db/models/suppressions.model.ts +53 -0
  482. package/src/db/models/template-versions.model.ts +23 -0
  483. package/src/db/models/templates.model.ts +17 -0
  484. package/src/db/models/webhook-status.model.ts +25 -0
  485. package/src/db/tables/index.ts +3 -0
  486. package/src/db/tables/send-events.table.ts +6 -2
  487. package/src/db/tables/sends.table.ts +37 -2
  488. package/src/db/tables/suppressions.table.ts +46 -0
  489. package/src/db/tables/template-versions.table.ts +44 -0
  490. package/src/db/tables/templates.table.ts +67 -5
  491. package/src/db/tables/webhook-status.table.ts +44 -0
  492. package/src/engine/changes.ts +198 -0
  493. package/src/engine/conditions.ts +1 -7
  494. package/src/engine/coverage.ts +7 -2
  495. package/src/engine/fallback-html.ts +80 -42
  496. package/src/engine/index.ts +3 -0
  497. package/src/engine/plain-text.ts +86 -0
  498. package/src/engine/resolve.ts +49 -15
  499. package/src/engine/system-variables.ts +124 -0
  500. package/src/engine/tokens.ts +13 -1
  501. package/src/hooks/tenant-export.ts +21 -2
  502. package/src/implementations/dms-mailing/index.ts +1 -1
  503. package/src/index.ts +29 -13
  504. package/src/pages/editor.ts +13 -2
  505. package/src/pages/index.ts +6 -3
  506. package/src/pages/module.ts +31 -2
  507. package/src/pages/overview.ts +187 -128
  508. package/src/pages/sends.ts +271 -112
  509. package/src/pages/settings/form.ts +144 -54
  510. package/src/pages/settings/page.ts +9 -17
  511. package/src/pages/suppressions.ts +157 -0
  512. package/src/pages/templates.ts +139 -42
  513. package/src/permissions.ts +6 -4
  514. package/src/quick-actions.ts +12 -5
  515. package/src/routes/events.ts +110 -33
  516. package/src/routes/index.ts +2 -0
  517. package/src/routes/metrics.ts +348 -98
  518. package/src/routes/period.ts +23 -6
  519. package/src/routes/sends.ts +413 -41
  520. package/src/routes/settings.ts +291 -26
  521. package/src/routes/suppressions.ts +87 -0
  522. package/src/routes/template-versions.ts +116 -0
  523. package/src/routes/templates.ts +334 -48
  524. package/src/services/attention.ts +283 -30
  525. package/src/services/catalog.ts +28 -0
  526. package/src/services/categories.ts +28 -27
  527. package/src/services/checked-send.ts +123 -0
  528. package/src/services/events.ts +174 -20
  529. package/src/services/metrics.ts +137 -28
  530. package/src/services/notify.ts +126 -44
  531. package/src/services/provider-banner.ts +165 -0
  532. package/src/services/render.ts +1 -0
  533. package/src/services/send-stat-items.ts +160 -0
  534. package/src/services/send-stats.ts +122 -0
  535. package/src/services/send.ts +193 -31
  536. package/src/services/settings.ts +52 -8
  537. package/src/services/starters.ts +383 -0
  538. package/src/services/suppressions.ts +116 -0
  539. package/src/services/template-attention.ts +82 -0
  540. package/src/services/template-stats.ts +97 -0
  541. package/src/services/templates.ts +400 -37
  542. package/src/services/webhook-status.ts +202 -0
  543. package/src/types/blocks.ts +6 -0
  544. package/src/types/index.ts +2 -0
  545. package/src/types/sends.ts +64 -2
  546. package/src/types/suppressions.ts +27 -0
  547. package/src/types/webhook.ts +29 -0
  548. package/src/validation/settings.schema.ts +173 -17
  549. package/src/validation/templates.schema.ts +53 -1
  550. package/dist/antelope-modules.d.ts +0 -32
  551. package/dist/antelope-modules.js +0 -36
  552. package/dist/antelope-modules.js.map +0 -1
  553. package/dist/test/antelope.test.d.ts +0 -2
  554. package/dist/test/antelope.test.js +0 -180
  555. package/dist/test/antelope.test.js.map +0 -1
  556. package/dist/test/helpers/http.d.ts +0 -4
  557. package/dist/test/helpers/http.js +0 -28
  558. package/dist/test/helpers/http.js.map +0 -1
  559. package/dist/test/helpers/owner.d.ts +0 -5
  560. package/dist/test/helpers/owner.js +0 -30
  561. package/dist/test/helpers/owner.js.map +0 -1
  562. package/dist/test/integration/events.test.d.ts +0 -1
  563. package/dist/test/integration/events.test.js +0 -143
  564. package/dist/test/integration/events.test.js.map +0 -1
  565. package/dist/test/integration/retention.test.d.ts +0 -1
  566. package/dist/test/integration/retention.test.js +0 -291
  567. package/dist/test/integration/retention.test.js.map +0 -1
  568. package/dist/test/integration/sends.test.d.ts +0 -1
  569. package/dist/test/integration/sends.test.js +0 -210
  570. package/dist/test/integration/sends.test.js.map +0 -1
  571. package/dist/test/integration/settings.test.d.ts +0 -1
  572. package/dist/test/integration/settings.test.js +0 -137
  573. package/dist/test/integration/settings.test.js.map +0 -1
  574. package/dist/test/integration/setup.test.d.ts +0 -1
  575. package/dist/test/integration/setup.test.js +0 -32
  576. package/dist/test/integration/setup.test.js.map +0 -1
  577. package/dist/test/integration/templates.test.d.ts +0 -1
  578. package/dist/test/integration/templates.test.js +0 -233
  579. package/dist/test/integration/templates.test.js.map +0 -1
  580. package/dist/test/integration/tenant-export.test.d.ts +0 -1
  581. package/dist/test/integration/tenant-export.test.js +0 -85
  582. package/dist/test/integration/tenant-export.test.js.map +0 -1
  583. package/dist/test/unit/attention.test.d.ts +0 -1
  584. package/dist/test/unit/attention.test.js +0 -109
  585. package/dist/test/unit/attention.test.js.map +0 -1
  586. package/dist/test/unit/automation.test.d.ts +0 -1
  587. package/dist/test/unit/automation.test.js +0 -104
  588. package/dist/test/unit/automation.test.js.map +0 -1
  589. package/dist/test/unit/categories.test.d.ts +0 -1
  590. package/dist/test/unit/categories.test.js +0 -38
  591. package/dist/test/unit/categories.test.js.map +0 -1
  592. package/dist/test/unit/engine/conditions.test.d.ts +0 -1
  593. package/dist/test/unit/engine/conditions.test.js +0 -23
  594. package/dist/test/unit/engine/conditions.test.js.map +0 -1
  595. package/dist/test/unit/engine/coverage.test.d.ts +0 -1
  596. package/dist/test/unit/engine/coverage.test.js +0 -106
  597. package/dist/test/unit/engine/coverage.test.js.map +0 -1
  598. package/dist/test/unit/engine/fallback-html.test.d.ts +0 -1
  599. package/dist/test/unit/engine/fallback-html.test.js +0 -78
  600. package/dist/test/unit/engine/fallback-html.test.js.map +0 -1
  601. package/dist/test/unit/engine/paths.test.d.ts +0 -1
  602. package/dist/test/unit/engine/paths.test.js +0 -33
  603. package/dist/test/unit/engine/paths.test.js.map +0 -1
  604. package/dist/test/unit/engine/resolve.test.d.ts +0 -1
  605. package/dist/test/unit/engine/resolve.test.js +0 -157
  606. package/dist/test/unit/engine/resolve.test.js.map +0 -1
  607. package/dist/test/unit/engine/tokens.test.d.ts +0 -1
  608. package/dist/test/unit/engine/tokens.test.js +0 -26
  609. package/dist/test/unit/engine/tokens.test.js.map +0 -1
  610. package/dist/test/unit/events.test.d.ts +0 -1
  611. package/dist/test/unit/events.test.js +0 -13
  612. package/dist/test/unit/events.test.js.map +0 -1
  613. package/dist/test/unit/metrics.test.d.ts +0 -1
  614. package/dist/test/unit/metrics.test.js +0 -108
  615. package/dist/test/unit/metrics.test.js.map +0 -1
  616. package/dist/test/unit/notify.test.d.ts +0 -1
  617. package/dist/test/unit/notify.test.js +0 -49
  618. package/dist/test/unit/notify.test.js.map +0 -1
  619. package/dist/test/unit/period.test.d.ts +0 -1
  620. package/dist/test/unit/period.test.js +0 -61
  621. package/dist/test/unit/period.test.js.map +0 -1
  622. package/dist/test/unit/permissions.test.d.ts +0 -1
  623. package/dist/test/unit/permissions.test.js +0 -27
  624. package/dist/test/unit/permissions.test.js.map +0 -1
  625. package/dist/test/unit/provider.test.d.ts +0 -1
  626. package/dist/test/unit/provider.test.js +0 -47
  627. package/dist/test/unit/provider.test.js.map +0 -1
  628. package/dist/test/unit/realtime.test.d.ts +0 -1
  629. package/dist/test/unit/realtime.test.js +0 -13
  630. package/dist/test/unit/realtime.test.js.map +0 -1
  631. package/dist/test/unit/render.test.d.ts +0 -1
  632. package/dist/test/unit/render.test.js +0 -23
  633. package/dist/test/unit/render.test.js.map +0 -1
  634. package/dist/test/unit/retention.test.d.ts +0 -1
  635. package/dist/test/unit/retention.test.js +0 -66
  636. package/dist/test/unit/retention.test.js.map +0 -1
  637. package/dist/test/unit/send-batch.test.d.ts +0 -1
  638. package/dist/test/unit/send-batch.test.js +0 -46
  639. package/dist/test/unit/send-batch.test.js.map +0 -1
  640. package/dist/test/unit/send.test.d.ts +0 -1
  641. package/dist/test/unit/send.test.js +0 -120
  642. package/dist/test/unit/send.test.js.map +0 -1
  643. package/dist/test/unit/smoke.test.d.ts +0 -1
  644. package/dist/test/unit/smoke.test.js +0 -10
  645. package/dist/test/unit/smoke.test.js.map +0 -1
  646. package/dist/test/unit/status.test.d.ts +0 -1
  647. package/dist/test/unit/status.test.js +0 -16
  648. package/dist/test/unit/status.test.js.map +0 -1
  649. package/dist/test/unit/templates.test.d.ts +0 -1
  650. package/dist/test/unit/templates.test.js +0 -48
  651. package/dist/test/unit/templates.test.js.map +0 -1
  652. package/dist/test/unit/tenant-export.test.d.ts +0 -1
  653. package/dist/test/unit/tenant-export.test.js +0 -59
  654. package/dist/test/unit/tenant-export.test.js.map +0 -1
  655. package/dist/test/unit/webhook-secret.test.d.ts +0 -1
  656. package/dist/test/unit/webhook-secret.test.js +0 -43
  657. package/dist/test/unit/webhook-secret.test.js.map +0 -1
  658. package/frontend-vue/app/components/AttentionCard.vue +0 -78
  659. package/frontend-vue/app/components/HtmlPreviewModal.vue +0 -20
  660. package/frontend-vue/app/components/ProviderChip.vue +0 -39
  661. package/frontend-vue/app/components/editor/EditorBlockSettings.vue +0 -186
  662. package/frontend-vue/app/components/editor/EditorDataPanel.vue +0 -130
  663. package/frontend-vue/app/components/editor/EditorDraftBar.vue +0 -35
  664. package/frontend-vue/app/components/editor/EditorRail.vue +0 -58
  665. package/frontend-vue/app/components/editor/EditorSubjectSettings.vue +0 -37
  666. package/frontend-vue/app/components/editor/EditorTemplateSettings.vue +0 -87
  667. package/frontend-vue/app/components/editor/EditorTestSendModal.vue +0 -66
  668. package/frontend-vue/app/components/editor/EditorVariables.vue +0 -163
  669. package/frontend-vue/app/composables/useMailingPeriodData.ts +0 -39
  670. package/src/antelope-modules.ts +0 -48
  671. package/src/test/antelope.test.ts +0 -190
  672. package/src/test/helpers/http.ts +0 -23
  673. package/src/test/helpers/owner.ts +0 -37
  674. package/src/test/integration/events.test.ts +0 -159
  675. package/src/test/integration/retention.test.ts +0 -363
  676. package/src/test/integration/sends.test.ts +0 -233
  677. package/src/test/integration/settings.test.ts +0 -128
  678. package/src/test/integration/setup.test.ts +0 -35
  679. package/src/test/integration/templates.test.ts +0 -272
  680. package/src/test/integration/tenant-export.test.ts +0 -125
  681. package/src/test/unit/attention.test.ts +0 -129
  682. package/src/test/unit/automation.test.ts +0 -130
  683. package/src/test/unit/categories.test.ts +0 -51
  684. package/src/test/unit/engine/conditions.test.ts +0 -35
  685. package/src/test/unit/engine/coverage.test.ts +0 -118
  686. package/src/test/unit/engine/fallback-html.test.ts +0 -86
  687. package/src/test/unit/engine/paths.test.ts +0 -39
  688. package/src/test/unit/engine/resolve.test.ts +0 -176
  689. package/src/test/unit/engine/tokens.test.ts +0 -26
  690. package/src/test/unit/events.test.ts +0 -19
  691. package/src/test/unit/metrics.test.ts +0 -170
  692. package/src/test/unit/notify.test.ts +0 -71
  693. package/src/test/unit/period.test.ts +0 -91
  694. package/src/test/unit/permissions.test.ts +0 -30
  695. package/src/test/unit/provider.test.ts +0 -57
  696. package/src/test/unit/realtime.test.ts +0 -11
  697. package/src/test/unit/render.test.ts +0 -23
  698. package/src/test/unit/retention.test.ts +0 -91
  699. package/src/test/unit/send-batch.test.ts +0 -72
  700. package/src/test/unit/send.test.ts +0 -161
  701. package/src/test/unit/smoke.test.ts +0 -8
  702. package/src/test/unit/status.test.ts +0 -14
  703. package/src/test/unit/templates.test.ts +0 -52
  704. package/src/test/unit/tenant-export.test.ts +0 -88
  705. package/src/test/unit/webhook-secret.test.ts +0 -51
@@ -0,0 +1,299 @@
1
+ import { computed, onBeforeUnmount, reactive, watch } from 'vue'
2
+ import type {
3
+ TemplateCategory,
4
+ TemplateChange,
5
+ TemplateContent,
6
+ TemplateContentResponse,
7
+ TemplatePerformance,
8
+ TemplateRow,
9
+ } from '../types/mailing'
10
+ import type { MailingApi, TemplateDetails } from './useMailingApi'
11
+ import type { EditorState } from './useTemplateEditor'
12
+ import type { TokenFieldHandle } from './useEditorContext'
13
+ import { deepClone } from '../utils/clone'
14
+ import { collectContentPaths } from '../utils/paths'
15
+ import { parseTestData } from '../utils/testData'
16
+ import type { SaveStatus } from '../utils/lifecycle'
17
+ import { noticeFor, statusKeyOf } from '../utils/lifecycle'
18
+ import {
19
+ apiErrorMessage,
20
+ apiErrorStatus,
21
+ HTTP_CONFLICT,
22
+ } from '../utils/api-error'
23
+
24
+ export type { SaveStatus } from '../utils/lifecycle'
25
+
26
+ /** The draft is written this long after the last change. */
27
+ export const AUTOSAVE_DELAY_MS = 1500
28
+
29
+ /** A failed save is tried again after this, doubling up to the maximum. */
30
+ export const RETRY_BASE_DELAY_MS = 2000
31
+ export const RETRY_MAX_DELAY_MS = 60_000
32
+
33
+ /** Browsers refuse a `keepalive` request whose body passes 64 KiB. */
34
+ const KEEPALIVE_MAX_LENGTH = 60_000
35
+ const SERVER_ERROR_STATUS = 500
36
+ const NETWORK_ERROR_STATUS = 0
37
+ const DRAFT_CONFLICT = '$dms_mailing.errors.draft_conflict'
38
+ const DRAFT_CONFLICT_KEY = 'draft_conflict'
39
+
40
+ const JSON_INDENT = 2
41
+
42
+ /**
43
+ * Everything the editor knows about the template besides its draft (the
44
+ * content and the variable declarations live in the editor state).
45
+ */
46
+ export interface SessionState {
47
+ template: TemplateRow
48
+ version: number
49
+ /** The draft revision the next save or publish carries back. */
50
+ revision: number
51
+ publishedContent: TemplateContent | null
52
+ changes: TemplateChange[]
53
+ serverDetected: string[]
54
+ testDataSource: string
55
+ savedTestDataSource: string
56
+ categories: TemplateCategory[]
57
+ fallbackLocale: string
58
+ blockOnMissingVariables: boolean
59
+ performance: TemplatePerformance | null
60
+ saveStatus: SaveStatus
61
+ activeField: TokenFieldHandle | null
62
+ }
63
+
64
+ export interface SessionDeps {
65
+ editor: EditorState
66
+ api: MailingApi
67
+ templateId: string
68
+ loaded: TemplateContentResponse
69
+ onError: (error: unknown) => void
70
+ }
71
+
72
+ function createState(loaded: TemplateContentResponse): SessionState {
73
+ const testData = JSON.stringify(loaded.testData, null, JSON_INDENT)
74
+ return reactive({
75
+ template: { ...loaded.template },
76
+ version: loaded.version,
77
+ revision: loaded.revision,
78
+ publishedContent: loaded.publishedContent,
79
+ changes: loaded.changes,
80
+ serverDetected: loaded.detectedVariables,
81
+ testDataSource: testData,
82
+ savedTestDataSource: testData,
83
+ categories: loaded.categories,
84
+ fallbackLocale: loaded.fallbackLocale,
85
+ blockOnMissingVariables: loaded.blockOnMissingVariables,
86
+ performance: null,
87
+ saveStatus: 'idle',
88
+ activeField: null,
89
+ }) as SessionState
90
+ }
91
+
92
+ function createViews(state: SessionState, editor: EditorState) {
93
+ return {
94
+ detected: computed(() => [
95
+ ...new Set([
96
+ ...state.serverDetected,
97
+ ...collectContentPaths(editor.content),
98
+ ]),
99
+ ]),
100
+ testData: computed(() => parseTestData(state.testDataSource)),
101
+ isTestDataDirty: computed(
102
+ () => state.testDataSource !== state.savedTestDataSource,
103
+ ),
104
+ isSaving: computed(() => state.saveStatus === 'saving' || editor.dirty > 0),
105
+ /** Work leaving the editor would lose: a failed save, a conflict, test data. */
106
+ hasUnsavedWork: computed(
107
+ () =>
108
+ state.saveStatus === 'error' ||
109
+ state.saveStatus === 'conflict' ||
110
+ state.testDataSource !== state.savedTestDataSource,
111
+ ),
112
+ statusKey: computed(() =>
113
+ statusKeyOf(state.template.status, state.version),
114
+ ),
115
+ notice: computed(() =>
116
+ noticeFor({
117
+ status: state.template.status,
118
+ version: state.version,
119
+ changeCount: state.changes.length,
120
+ saveStatus: state.saveStatus,
121
+ }),
122
+ ),
123
+ }
124
+ }
125
+
126
+ /** Whether a failed call is the server refusing a draft moved on elsewhere. */
127
+ export function isDraftConflict(error: unknown): boolean {
128
+ return (
129
+ apiErrorStatus(error) === HTTP_CONFLICT &&
130
+ apiErrorMessage(error).includes(DRAFT_CONFLICT_KEY)
131
+ )
132
+ }
133
+
134
+ /** A failure worth trying again on its own: the network or the server. */
135
+ function isTransient(error: unknown): boolean {
136
+ const status = apiErrorStatus(error)
137
+ return status === NETWORK_ERROR_STATUS || status >= SERVER_ERROR_STATUS
138
+ }
139
+
140
+ export function retryDelay(failures: number): number {
141
+ return Math.min(RETRY_BASE_DELAY_MS * 2 ** (failures - 1), RETRY_MAX_DELAY_MS)
142
+ }
143
+
144
+ function createSaver(deps: SessionDeps, state: SessionState) {
145
+ const { editor, api, templateId, onError } = deps
146
+ let timer: ReturnType<typeof setTimeout> | null = null
147
+ let inFlight: Promise<boolean> | null = null
148
+ let failures = 0
149
+ let isDisposed = false
150
+ const cancel = (): void => {
151
+ if (timer) clearTimeout(timer)
152
+ timer = null
153
+ }
154
+ function schedule(delay = AUTOSAVE_DELAY_MS): void {
155
+ cancel()
156
+ if (isDisposed) return
157
+ timer = setTimeout(() => void save(), delay)
158
+ }
159
+ function markConflict(): void {
160
+ cancel()
161
+ if (state.saveStatus !== 'conflict') onError(DRAFT_CONFLICT)
162
+ state.saveStatus = 'conflict'
163
+ }
164
+ function onFailure(error: unknown): void {
165
+ if (isDraftConflict(error)) return markConflict()
166
+ failures += 1
167
+ state.saveStatus = 'error'
168
+ if (failures === 1) onError(error)
169
+ if (isTransient(error)) schedule(retryDelay(failures))
170
+ }
171
+ async function write(): Promise<boolean> {
172
+ const revision = editor.revision
173
+ state.saveStatus = 'saving'
174
+ try {
175
+ const response = await api.saveContent(templateId, {
176
+ content: deepClone(editor.content),
177
+ variables: deepClone(editor.variables),
178
+ revision: state.revision,
179
+ })
180
+ state.revision = response.revision
181
+ state.changes = response.changes
182
+ state.serverDetected = response.detectedVariables
183
+ editor.markSaved(revision)
184
+ failures = 0
185
+ state.saveStatus = 'idle'
186
+ return true
187
+ } catch (error) {
188
+ onFailure(error)
189
+ return false
190
+ }
191
+ }
192
+ async function run(): Promise<boolean> {
193
+ inFlight = write()
194
+ const isSaved = await inFlight
195
+ inFlight = null
196
+ if (isSaved && editor.dirty > 0) schedule()
197
+ return isSaved
198
+ }
199
+ async function save(): Promise<boolean> {
200
+ cancel()
201
+ if (inFlight) await inFlight
202
+ if (state.saveStatus === 'conflict') return false
203
+ if (editor.dirty === 0) return state.saveStatus !== 'error'
204
+ return run()
205
+ }
206
+ /** Writes this editor's content over the draft saved elsewhere. */
207
+ async function overwrite(): Promise<boolean> {
208
+ cancel()
209
+ if (inFlight) await inFlight
210
+ const latest = await api.changes(templateId).catch(() => null)
211
+ if (!latest) return false
212
+ state.revision = latest.revision
213
+ return run()
214
+ }
215
+ /** The page is going away: hands the pending draft to a request that outlives it. */
216
+ function flush(): void {
217
+ if (editor.dirty === 0 || state.saveStatus === 'conflict') return
218
+ cancel()
219
+ const draft = {
220
+ content: deepClone(editor.content),
221
+ variables: deepClone(editor.variables),
222
+ }
223
+ const keepalive = JSON.stringify(draft).length < KEEPALIVE_MAX_LENGTH
224
+ void api
225
+ .saveContent(templateId, {
226
+ ...draft,
227
+ revision: state.revision,
228
+ keepalive,
229
+ })
230
+ .catch(() => undefined)
231
+ }
232
+ watch(
233
+ () => editor.revision,
234
+ () => (editor.dirty > 0 ? schedule() : undefined),
235
+ )
236
+ onBeforeUnmount(() => {
237
+ if (editor.dirty > 0) void save()
238
+ isDisposed = true
239
+ cancel()
240
+ })
241
+ return {
242
+ save,
243
+ cancel,
244
+ overwrite,
245
+ flush,
246
+ markConflict,
247
+ settle: () => inFlight ?? Promise.resolve(true),
248
+ }
249
+ }
250
+
251
+ function createRecordActions(
252
+ { api, templateId }: SessionDeps,
253
+ state: SessionState,
254
+ ) {
255
+ return {
256
+ async saveDetails(patch: Partial<TemplateDetails>): Promise<void> {
257
+ const details: TemplateDetails = {
258
+ name: state.template.name,
259
+ slug: state.template.slug,
260
+ category: state.template.category || null,
261
+ ...patch,
262
+ }
263
+ await api.saveTemplateDetails(templateId, details)
264
+ state.template = { ...state.template, ...details }
265
+ },
266
+ async saveTestData(): Promise<boolean> {
267
+ const data = parseTestData(state.testDataSource)
268
+ if (!data) return false
269
+ await api.saveTestData(templateId, data)
270
+ state.savedTestDataSource = state.testDataSource
271
+ return true
272
+ },
273
+ resetTestData(): void {
274
+ state.testDataSource = state.savedTestDataSource
275
+ },
276
+ async loadPerformance(): Promise<void> {
277
+ state.performance = await api.performance(templateId).catch(() => null)
278
+ },
279
+ }
280
+ }
281
+
282
+ /**
283
+ * The editor's link to the server: the template record, autosave of the
284
+ * draft (content and variable declarations), test data and performance.
285
+ * Publishing, discarding, restoring and the dialogs live in
286
+ * `useEditorActions`.
287
+ */
288
+ export function useEditorSession(deps: SessionDeps) {
289
+ const state = createState(deps.loaded)
290
+ return reactive({
291
+ state,
292
+ reportError: deps.onError,
293
+ ...createViews(state, deps.editor),
294
+ ...createSaver(deps, state),
295
+ ...createRecordActions(deps, state),
296
+ })
297
+ }
298
+
299
+ export type EditorSession = ReturnType<typeof useEditorSession>
@@ -0,0 +1,81 @@
1
+ import { onBeforeUnmount, onMounted } from 'vue'
2
+ import type { EditorActions } from './useEditorActions'
3
+ import type { EditorSession } from './useEditorSession'
4
+ import type { EditorState } from './useTemplateEditor'
5
+ import type { EditorShortcut } from '../utils/shortcuts'
6
+ import { isTypingTarget, matchShortcut } from '../utils/shortcuts'
7
+ import { TEMPLATES_PATH } from '../utils/pages'
8
+
9
+ const OPEN_DIALOG_SELECTOR =
10
+ '[role="dialog"][data-state="open"], [role="alertdialog"][data-state="open"]'
11
+
12
+ type ShortcutHandlers = Record<EditorShortcut, () => void>
13
+
14
+ function handlersFor(
15
+ editor: EditorState,
16
+ actions: EditorActions,
17
+ ): ShortcutHandlers {
18
+ const withSelection = (run: (id: string) => void) => () => {
19
+ if (editor.selectedId) run(editor.selectedId)
20
+ }
21
+ return {
22
+ save: () => void actions.save(),
23
+ publish: () => void actions.publish(),
24
+ test: actions.test,
25
+ search: editor.requestSearch,
26
+ delete: withSelection(editor.removeBlockById),
27
+ duplicate: withSelection(editor.duplicateBlockById),
28
+ undo: editor.undo,
29
+ redo: editor.redo,
30
+ escape: () =>
31
+ editor.selectedId
32
+ ? editor.select(null)
33
+ : void navigateDms(TEMPLATES_PATH),
34
+ }
35
+ }
36
+
37
+ /**
38
+ * The editor's keyboard (ML-08): ⌘S, ⌘↵, T, /, ⌫, ⌘D, ⌘Z, Esc. Nothing fires
39
+ * while a dialog is open, and single keys leave text fields alone.
40
+ */
41
+ export function useEditorShortcuts(
42
+ editor: EditorState,
43
+ actions: EditorActions,
44
+ ): void {
45
+ const handlers = handlersFor(editor, actions)
46
+ function onKeydown(event: KeyboardEvent): void {
47
+ if (event.defaultPrevented || document.querySelector(OPEN_DIALOG_SELECTOR))
48
+ return
49
+ const shortcut = matchShortcut(event, isTypingTarget(event.target))
50
+ if (!shortcut) return
51
+ event.preventDefault()
52
+ handlers[shortcut]()
53
+ }
54
+ onMounted(() => window.addEventListener('keydown', onKeydown))
55
+ onBeforeUnmount(() => window.removeEventListener('keydown', onKeydown))
56
+ }
57
+
58
+ /**
59
+ * Leaving the editor never loses work silently. A draft waiting for its
60
+ * autosave is written on the way out (on unmount, or by a request outliving
61
+ * a closed tab, after the browser's own prompt); a save that failed, a
62
+ * conflict or unsaved test data ask the DMS "Discard unsaved changes?" first,
63
+ * whether the user follows a link, goes back or reloads.
64
+ */
65
+ export function useEditorLeaveGuard(session: EditorSession): void {
66
+ useUnsavedChanges({ dirty: () => session.hasUnsavedWork })
67
+ function onBeforeUnload(event: BeforeUnloadEvent): void {
68
+ if (!session.isSaving) return
69
+ event.preventDefault()
70
+ event.returnValue = ''
71
+ }
72
+ const onPageHide = (): void => session.flush()
73
+ onMounted(() => {
74
+ window.addEventListener('beforeunload', onBeforeUnload)
75
+ window.addEventListener('pagehide', onPageHide)
76
+ })
77
+ onBeforeUnmount(() => {
78
+ window.removeEventListener('beforeunload', onBeforeUnload)
79
+ window.removeEventListener('pagehide', onPageHide)
80
+ })
81
+ }
@@ -0,0 +1,17 @@
1
+ import type { StarterSummary, TemplateCategory } from '../types/mailing'
2
+ import { localizeCategories, type OptionalTranslate } from '../utils/categories'
3
+ import { localizeStarters } from '../utils/starters'
4
+
5
+ /**
6
+ * What the module ships by default (starters, the first categories) named in
7
+ * the interface's language, without touching what is stored.
8
+ */
9
+ export function useLocalizedDefaults() {
10
+ const { t, te } = useI18n()
11
+ const translate: OptionalTranslate = (key) => (te(key) ? t(key) : undefined)
12
+ return {
13
+ categories: (list: TemplateCategory[]) =>
14
+ localizeCategories(list, translate),
15
+ starters: (list: StarterSummary[]) => localizeStarters(list, translate),
16
+ }
17
+ }
@@ -1,15 +1,25 @@
1
1
  import type {
2
- AttentionItem,
3
2
  FunnelStep,
4
3
  PreviewResponse,
4
+ PreviewVersion,
5
5
  ProviderInfo,
6
+ RetentionPreview,
6
7
  SendDetailResponse,
8
+ SendHtmlResponse,
9
+ StarterSummary,
10
+ SuppressionSummary,
11
+ TemplateChange,
7
12
  TemplateContent,
8
13
  TemplateCategory,
9
14
  TemplateContentResponse,
15
+ TemplatePerformance,
10
16
  TemplateRow,
17
+ TemplateStats,
18
+ TemplateVersionSummary,
11
19
  VariableDefinition,
12
20
  } from '../types/mailing'
21
+ import type { SetupFacts } from '../utils/overview-setup'
22
+ import type { WebhookStatus, WebhookTestResult } from '../utils/webhook-status'
13
23
 
14
24
  const BASE = '/api/mailing'
15
25
 
@@ -27,6 +37,8 @@ export function mailingPath(...segments: string[]): string {
27
37
  export interface PreviewInput {
28
38
  /** Absent lets the server pick the tenant fallback locale. */
29
39
  locale?: string
40
+ /** Which content to render when `content` is absent; the draft by default. */
41
+ version?: PreviewVersion
30
42
  content?: TemplateContent
31
43
  data?: Record<string, unknown>
32
44
  }
@@ -48,28 +60,75 @@ export interface SendResult {
48
60
  error?: string
49
61
  }
50
62
 
51
- export interface AttentionResponse {
52
- items: AttentionItem[]
53
- }
54
-
55
63
  export interface FunnelResponse {
56
64
  steps: FunnelStep[]
57
65
  }
58
66
 
59
67
  export interface SavedResponse {
60
68
  saved: boolean
69
+ /** The draft revision the next save or publish carries. */
70
+ revision: number
61
71
  detectedVariables: string[]
72
+ changes: TemplateChange[]
73
+ }
74
+
75
+ export interface ChangesResponse {
76
+ version: number
77
+ revision: number
78
+ changes: TemplateChange[]
79
+ }
80
+
81
+ /**
82
+ * A draft save: the content and the variable declarations, which follow the
83
+ * draft. `revision` is the one the editor last saw: the server refuses the
84
+ * save (409) when the draft moved on since. `keepalive` lets the request
85
+ * outlive the page being closed.
86
+ */
87
+ export interface SaveContentInput {
88
+ content: TemplateContent
89
+ variables?: VariableDefinition[]
90
+ revision?: number
91
+ keepalive?: boolean
92
+ }
93
+
94
+ /** A version copied into the draft; the review then publishes it. */
95
+ export interface RestoreVersionResponse {
96
+ /** The draft revision the editor carries on with. */
97
+ revision: number
98
+ }
99
+
100
+ export interface PublishResponse {
101
+ status: string
102
+ version: number
103
+ changes: TemplateChange[]
104
+ }
105
+
106
+ export interface VersionsResponse {
107
+ versions: TemplateVersionSummary[]
108
+ }
109
+
110
+ export interface StartersResponse {
111
+ starters: StarterSummary[]
112
+ }
113
+
114
+ export interface TemplateOverviewResponse {
115
+ items: TemplateStats[]
116
+ }
117
+
118
+ export interface CategoryUsageResponse {
119
+ counts: Record<string, number>
62
120
  }
63
121
 
64
122
  /**
65
123
  * The template's own writable fields. All of them, always: the data-api `edit`
66
124
  * route replaces the writable set rather than patching it, so a body missing
67
- * one nulls it in the database.
125
+ * one nulls it in the database. `null` clears the category on every data-api
126
+ * version, including those that leave an absent key unchanged.
68
127
  */
69
128
  export interface TemplateDetails {
70
129
  name: string
71
130
  slug: string
72
- category?: string
131
+ category: string | null
73
132
  }
74
133
 
75
134
  export interface CategoriesResponse {
@@ -87,6 +146,8 @@ export interface CreateTemplateInput {
87
146
  category?: string
88
147
  /** Absent starts blank; otherwise the template to copy. */
89
148
  sourceTemplateId?: string
149
+ /** A starter template to start from, when no template is copied. */
150
+ starterId?: string
90
151
  }
91
152
 
92
153
  export interface StatusResponse {
@@ -99,6 +160,8 @@ export interface DuplicateResponse {
99
160
 
100
161
  export interface VariablesResponse {
101
162
  variables: VariableDefinition[]
163
+ /** The draft revision the declarations were written at. */
164
+ revision: number
102
165
  }
103
166
 
104
167
  export interface TestDataResponse {
@@ -107,14 +170,20 @@ export interface TestDataResponse {
107
170
 
108
171
  export interface TestSendResponse {
109
172
  results: SendResult[]
173
+ /**
174
+ * The sender's own address is on the suppression list: the test went out
175
+ * anyway, with this entry to warn about.
176
+ */
177
+ ownAddressSuppressed?: SuppressionSummary
110
178
  }
111
179
 
112
180
  export interface HtmlResponse {
113
181
  html: string
114
182
  }
115
183
 
116
- export interface MailingSettings extends Record<string, unknown> {
117
- blockOnMissingVariables?: boolean
184
+ export interface StarterPreviewResponse {
185
+ html: string
186
+ locale: string
118
187
  }
119
188
 
120
189
  /**
@@ -128,12 +197,11 @@ export function useMailingApi() {
128
197
 
129
198
  return {
130
199
  provider: () => $authFetch<ProviderInfo>(mailingPath('provider')),
131
- attention: (query: Record<string, string>) =>
132
- $authFetch<AttentionResponse>(mailingPath('metrics', 'attention'), {
133
- query,
134
- }),
135
- funnel: (query: Record<string, string>) =>
136
- $authFetch<FunnelResponse>(mailingPath('metrics', 'funnel'), { query }),
200
+ setupFacts: () => $authFetch<SetupFacts>(mailingPath('metrics', 'setup')),
201
+ webhookStatus: () =>
202
+ $authFetch<WebhookStatus>(mailingPath('settings', 'webhook-status')),
203
+ webhookTest: () =>
204
+ post<WebhookTestResult>(mailingPath('settings', 'webhook-test'), {}),
137
205
  templateContent: (id: string) =>
138
206
  $authFetch<TemplateContentResponse>(
139
207
  mailingPath('templates', id, 'content'),
@@ -153,24 +221,65 @@ export function useMailingApi() {
153
221
  ),
154
222
  createTemplate: (input: CreateTemplateInput) =>
155
223
  post<string[]>(mailingPath('tables', 'templates', 'new'), input),
156
- saveContent: (id: string, content: TemplateContent) =>
157
- post<SavedResponse>(mailingPath('templates', id, 'content'), {
158
- content,
224
+ saveContent: (id: string, input: SaveContentInput) =>
225
+ $authFetch<SavedResponse>(mailingPath('templates', id, 'content'), {
226
+ method: 'POST',
227
+ body: {
228
+ content: input.content,
229
+ variables: input.variables,
230
+ revision: input.revision,
231
+ },
232
+ keepalive: input.keepalive,
159
233
  }),
160
- publish: (id: string) =>
161
- post<StatusResponse>(mailingPath('templates', id, 'publish'), {}),
234
+ changes: (id: string) =>
235
+ $authFetch<ChangesResponse>(mailingPath('templates', id, 'changes')),
236
+ versions: (id: string) =>
237
+ $authFetch<VersionsResponse>(mailingPath('templates', id, 'versions')),
238
+ restoreVersion: (id: string, version: number, revision?: number) =>
239
+ post<RestoreVersionResponse>(
240
+ mailingPath('templates', id, 'versions', String(version), 'restore'),
241
+ { revision },
242
+ ),
243
+ performance: (id: string) =>
244
+ $authFetch<TemplatePerformance>(
245
+ mailingPath('templates', id, 'performance'),
246
+ ),
247
+ templatesOverview: () =>
248
+ $authFetch<TemplateOverviewResponse>(
249
+ mailingPath('templates', 'overview'),
250
+ ),
251
+ starters: () =>
252
+ $authFetch<StartersResponse>(mailingPath('templates', 'starters')),
253
+ starterPreview: (starterId: string, locale?: string) =>
254
+ $authFetch<StarterPreviewResponse>(
255
+ mailingPath('templates', 'starters', starterId, 'preview'),
256
+ { query: locale ? { locale } : {} },
257
+ ),
258
+ publish: (id: string, revision?: number) =>
259
+ post<PublishResponse>(mailingPath('templates', id, 'publish'), {
260
+ revision,
261
+ }),
262
+ discard: (id: string) =>
263
+ post<unknown>(mailingPath('templates', id, 'discard'), {}),
162
264
  unpublish: (id: string) =>
163
265
  post<StatusResponse>(mailingPath('templates', id, 'unpublish'), {}),
164
266
  archive: (id: string) =>
165
267
  post<StatusResponse>(mailingPath('templates', id, 'archive'), {}),
268
+ restore: (id: string) =>
269
+ post<StatusResponse>(mailingPath('templates', id, 'restore'), {}),
166
270
  duplicate: (id: string, slug: string, name: string) =>
167
271
  post<DuplicateResponse>(mailingPath('templates', id, 'duplicate'), {
168
272
  slug,
169
273
  name,
170
274
  }),
171
- saveVariables: (id: string, variables: VariableDefinition[]) =>
275
+ saveVariables: (
276
+ id: string,
277
+ variables: VariableDefinition[],
278
+ revision?: number,
279
+ ) =>
172
280
  post<VariablesResponse>(mailingPath('templates', id, 'variables'), {
173
281
  variables,
282
+ revision,
174
283
  }),
175
284
  saveTestData: (id: string, data: Record<string, unknown>) =>
176
285
  post<TestDataResponse>(mailingPath('templates', id, 'test-data'), {
@@ -185,12 +294,18 @@ export function useMailingApi() {
185
294
  send: (id: string) =>
186
295
  $authFetch<SendDetailResponse>(mailingPath('sends', id)),
187
296
  sendHtml: (id: string) =>
188
- $authFetch<HtmlResponse>(mailingPath('sends', id, 'html')),
189
- replay: (id: string) =>
190
- post<SendResult>(mailingPath('sends', id, 'replay'), {}),
191
- settings: () => $authFetch<MailingSettings>(mailingPath('settings')),
192
- saveSettings: (settings: MailingSettings) =>
193
- post<MailingSettings>(mailingPath('settings'), settings),
297
+ $authFetch<SendHtmlResponse>(mailingPath('sends', id, 'html')),
298
+ replay: (id: string, to?: string) =>
299
+ post<SendResult>(mailingPath('sends', id, 'replay'), to ? { to } : {}),
300
+ retentionPreview: (days: number) =>
301
+ $authFetch<RetentionPreview>(
302
+ mailingPath('settings', 'retention-preview'),
303
+ { query: { days: String(days) } },
304
+ ),
305
+ categoryUsage: () =>
306
+ $authFetch<CategoryUsageResponse>(
307
+ mailingPath('settings', 'category-usage'),
308
+ ),
194
309
  }
195
310
  }
196
311
 
@@ -0,0 +1,13 @@
1
+ import type { Ref } from 'vue'
2
+
3
+ const HIDES_PERIOD_KEY = 'mailing-setup-hides-period:'
4
+
5
+ /**
6
+ * Whether the page header should hold its period selector back for `scope`:
7
+ * the setup card stands in for the dashboard, so there is no figure for a
8
+ * period to filter. Shared between the Overview gate, which sets it, and the
9
+ * page header, which reads it.
10
+ */
11
+ export function useSetupHidesPeriod(scope: string | undefined): Ref<boolean> {
12
+ return useDmsState<boolean>(`${HIDES_PERIOD_KEY}${scope ?? ''}`, () => false)
13
+ }