@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
@@ -4,15 +4,31 @@ import type {
4
4
  BlockType,
5
5
  LocaleContent,
6
6
  TemplateContent,
7
+ VariableDefinition,
7
8
  } from '../types/mailing'
8
9
  import {
9
10
  cloneWithNewIds,
10
11
  createBlock,
12
+ duplicateBlock,
11
13
  findBlock,
12
14
  findParentList,
15
+ flattenBlocks,
16
+ insertionAfter,
17
+ localizeFooters,
18
+ moveBlock,
19
+ neighbourOf,
13
20
  removeBlock,
14
21
  } from '../utils/blocks'
22
+ import type { BlockContext } from '../utils/blocks'
15
23
  import { deepClone } from '../utils/clone'
24
+ import { renameVariablePath } from '../utils/variables'
25
+ import {
26
+ createHistory,
27
+ recordChange,
28
+ redoChange,
29
+ undoChange,
30
+ } from '../utils/history'
31
+ import type { EditorHistory } from '../utils/history'
16
32
 
17
33
  export type IfBranch = 'children' | 'elseChildren'
18
34
 
@@ -21,45 +37,117 @@ export interface InsertTarget {
21
37
  branch: IfBranch
22
38
  }
23
39
 
24
- export type RailTab = 'blocks' | 'settings' | 'template' | 'data'
40
+ export type LeftTab = 'blocks' | 'outline' | 'variables'
41
+ export type RightTab = 'block' | 'data' | 'template'
25
42
  export type EditorDevice = 'desktop' | 'mobile'
43
+ export type PreviewMode = 'variables' | 'data'
26
44
  export type LocalePatch = Partial<Pick<LocaleContent, 'subject' | 'preheader'>>
27
45
 
46
+ /** A clock, injectable so the history's coalescing is testable. */
47
+ export type EditorClock = () => number
48
+
49
+ export type AnnouncementKind =
50
+ 'block_removed' | 'undone_in_locale' | 'redone_in_locale'
51
+
52
+ /**
53
+ * Something the editor tells the author after the fact (a toast): a block
54
+ * deleted, an undo that landed in another locale. `serial` grows with each.
55
+ */
56
+ export interface EditorAnnouncement {
57
+ serial: number
58
+ kind: AnnouncementKind
59
+ blockType?: BlockType
60
+ count?: number
61
+ locale?: string
62
+ /** The history state an "Undo" action reverts, when the toast offers one. */
63
+ undoable?: string
64
+ }
65
+
66
+ export type AnnouncementInput = Omit<EditorAnnouncement, 'serial'>
67
+ export type Announce = (announcement: AnnouncementInput) => void
68
+
28
69
  interface EditorCore {
29
70
  content: TemplateContent
71
+ /** The draft's variable declarations: saved, undone and published with it. */
72
+ variables: VariableDefinition[]
30
73
  locale: string
31
74
  locales: string[]
32
- dirty: number
75
+ revision: number
76
+ savedRevision: number
33
77
  selectedId: string | null
34
- subjectSelected: boolean
35
- tab: RailTab
78
+ leftTab: LeftTab
79
+ rightTab: RightTab
36
80
  device: EditorDevice
37
- previewMode: boolean
38
- railOpen: boolean
81
+ previewMode: PreviewMode
82
+ simulation: Record<string, boolean>
83
+ isSearchRequested: boolean
84
+ history: EditorHistory
85
+ announcement: EditorAnnouncement | null
39
86
  }
40
87
 
41
- const emptyLocale = (): LocaleContent => ({
88
+ /**
89
+ * A new page in `locale`: blank, but with the footer every e-mail needs, its
90
+ * links following the template's category like a footer from the palette.
91
+ */
92
+ const blankLocale = (
93
+ locale: string,
94
+ context: BlockContext = {},
95
+ ): LocaleContent => ({
42
96
  subject: '',
43
97
  preheader: '',
44
- blocks: [],
98
+ blocks: [createBlock('footer', { ...context, locale })],
45
99
  })
46
100
 
101
+ function copyLocale(
102
+ source: LocaleContent,
103
+ from: string,
104
+ to: string,
105
+ ): LocaleContent {
106
+ const copy = {
107
+ ...deepClone(source),
108
+ blocks: source.blocks.map(cloneWithNewIds),
109
+ }
110
+ localizeFooters(copy.blocks, from, to)
111
+ return copy
112
+ }
113
+
114
+ /** What one undo step restores: the content and the declarations. */
115
+ interface DraftSnapshot {
116
+ content: TemplateContent
117
+ variables: VariableDefinition[]
118
+ }
119
+
120
+ const snapshotOf = (draft: DraftSnapshot): string =>
121
+ JSON.stringify({ content: draft.content, variables: draft.variables })
122
+
123
+ /** A declared variable renamed in the content too, as one change. */
124
+ export interface VariableRename {
125
+ from: string
126
+ to: string
127
+ }
128
+
47
129
  function createCore(
48
130
  content: TemplateContent,
131
+ variables: VariableDefinition[],
49
132
  locale: string,
50
133
  locales: string[],
51
134
  ): EditorCore {
52
135
  return reactive({
53
136
  content: deepClone(content),
137
+ variables: deepClone(variables),
54
138
  locale,
55
139
  locales,
56
- dirty: 0,
140
+ revision: 0,
141
+ savedRevision: 0,
57
142
  selectedId: null,
58
- subjectSelected: false,
59
- tab: 'blocks',
143
+ leftTab: 'blocks',
144
+ rightTab: 'block',
60
145
  device: 'desktop',
61
- previewMode: false,
62
- railOpen: true,
146
+ previewMode: 'variables',
147
+ simulation: {},
148
+ isSearchRequested: false,
149
+ history: createHistory(snapshotOf({ content, variables })),
150
+ announcement: null,
63
151
  }) as EditorCore
64
152
  }
65
153
 
@@ -77,11 +165,23 @@ function createViews(core: EditorCore) {
77
165
  missingLocales: computed(() =>
78
166
  core.locales.filter((code) => !core.content.locales[code]),
79
167
  ),
168
+ dirty: computed(() => core.revision - core.savedRevision),
169
+ canUndo: computed(() => core.history.past.length > 0),
170
+ canRedo: computed(() => core.history.future.length > 0),
171
+ isDataMode: computed(() => core.previewMode === 'data'),
80
172
  }
81
173
  }
82
174
 
83
175
  type EditorViews = ReturnType<typeof createViews>
84
176
 
177
+ interface MutationDeps {
178
+ core: EditorCore
179
+ views: EditorViews
180
+ select: (id: string | null) => void
181
+ touch: (isDiscrete?: boolean) => void
182
+ announce: Announce
183
+ }
184
+
85
185
  function blocksOf(views: EditorViews): Block[] | null {
86
186
  return views.current.value?.blocks ?? null
87
187
  }
@@ -89,32 +189,24 @@ function blocksOf(views: EditorViews): Block[] | null {
89
189
  function createSelectionActions(core: EditorCore) {
90
190
  function select(id: string | null): void {
91
191
  core.selectedId = id
92
- core.subjectSelected = false
93
- if (id) core.tab = 'settings'
94
- }
95
- function selectSubject(): void {
96
- core.selectedId = null
97
- core.subjectSelected = true
98
- core.tab = 'settings'
192
+ if (id) core.rightTab = 'block'
99
193
  }
100
- function setLocale(code: string): void {
101
- core.locale = code
102
- select(null)
194
+ return {
195
+ select,
196
+ setLocale(code: string): void {
197
+ core.locale = code
198
+ select(null)
199
+ },
200
+ requestSearch(): void {
201
+ core.leftTab = 'blocks'
202
+ core.isSearchRequested = true
203
+ },
103
204
  }
104
- return { select, selectSubject, setLocale }
105
- }
106
-
107
- interface MutationDeps {
108
- core: EditorCore
109
- views: EditorViews
110
- select: (id: string | null) => void
111
- touch: () => void
112
205
  }
113
206
 
114
207
  function listFor(views: EditorViews, target?: InsertTarget): Block[] | null {
115
208
  const blocks = blocksOf(views)
116
- if (!blocks) return null
117
- if (!target) return blocks
209
+ if (!blocks || !target) return blocks
118
210
  const parent = findBlock(blocks, target.parentId)
119
211
  if (parent?.type !== 'if') return null
120
212
  if (target.branch === 'elseChildren' && !parent.elseChildren)
@@ -122,59 +214,107 @@ function listFor(views: EditorViews, target?: InsertTarget): Block[] | null {
122
214
  return parent[target.branch]
123
215
  }
124
216
 
125
- function createBlockActions({ views, select, touch }: MutationDeps) {
126
- function updateBlock(id: string, patch: Partial<Block>): void {
127
- const blocks = blocksOf(views)
128
- const block = blocks ? findBlock(blocks, id) : null
129
- if (!block) return
130
- Object.assign(block, patch)
131
- touch()
132
- }
133
- function addBlock(
217
+ function createInsertActions({ views, select, touch, core }: MutationDeps) {
218
+ function insertInto(
219
+ list: Block[],
220
+ index: number,
134
221
  type: BlockType,
135
- target?: InsertTarget,
136
- index?: number,
222
+ context: BlockContext = {},
137
223
  ): Block {
138
- const block = createBlock(type)
139
- const list = listFor(views, target)
140
- list?.splice(index ?? list.length, 0, block)
224
+ const block = createBlock(type, { ...context, locale: core.locale })
225
+ list.splice(Math.min(index, list.length), 0, block)
141
226
  select(block.id)
142
- touch()
227
+ touch(true)
143
228
  return block
144
229
  }
145
- function addElse(id: string): void {
230
+ return {
231
+ insertInto,
232
+ addBlock(type: BlockType, target?: InsertTarget, index?: number): Block {
233
+ const list = listFor(views, target) ?? []
234
+ return insertInto(list, index ?? list.length, type)
235
+ },
236
+ addBlockUnderSelected(
237
+ type: BlockType,
238
+ context?: BlockContext,
239
+ ): Block | null {
240
+ const blocks = blocksOf(views)
241
+ if (!blocks) return null
242
+ const point = insertionAfter(blocks, core.selectedId, type)
243
+ return insertInto(point.list, point.index, type, context)
244
+ },
245
+ }
246
+ }
247
+
248
+ function createRemoval({ views, select, touch, core, announce }: MutationDeps) {
249
+ return (id: string): void => {
146
250
  const blocks = blocksOf(views)
147
251
  const block = blocks ? findBlock(blocks, id) : null
148
- if (block?.type !== 'if' || block.elseChildren) return
149
- block.elseChildren = [createBlock('paragraph')]
150
- touch()
252
+ if (!blocks || !block) return
253
+ const neighbour = neighbourOf(blocks, id)
254
+ removeBlock(blocks, id)
255
+ touch(true)
256
+ if (core.selectedId === id) select(neighbour)
257
+ announce({
258
+ kind: 'block_removed',
259
+ blockType: block.type,
260
+ count: flattenBlocks([block]).length - 1,
261
+ undoable: core.history.present,
262
+ })
151
263
  }
152
- function removeElse(id: string): void {
264
+ }
265
+
266
+ function createBlockActions(deps: MutationDeps) {
267
+ const { views, select, touch } = deps
268
+ const find = (id: string): Block | null => {
153
269
  const blocks = blocksOf(views)
154
- const block = blocks ? findBlock(blocks, id) : null
155
- if (block?.type !== 'if') return
156
- block.elseChildren = null
157
- touch()
270
+ return blocks ? findBlock(blocks, id) : null
158
271
  }
159
- function removeBlockById(id: string): void {
272
+ const mutateTree = (change: (blocks: Block[]) => unknown): void => {
160
273
  const blocks = blocksOf(views)
161
- if (blocks && removeBlock(blocks, id)) touch()
274
+ if (blocks && change(blocks)) touch(true)
162
275
  }
163
276
  return {
164
- updateBlock,
165
- addBlock,
166
- addElse,
167
- removeElse,
168
- removeBlockById,
169
- findBlock: (id: string) => {
170
- const blocks = blocksOf(views)
171
- return blocks ? findBlock(blocks, id) : null
172
- },
277
+ findBlock: find,
173
278
  parentListOf: (id: string) => {
174
279
  const blocks = blocksOf(views)
175
280
  return blocks ? findParentList(blocks, id) : null
176
281
  },
177
- updateLocale: (patch: LocalePatch) => {
282
+ updateBlock(id: string, patch: Partial<Block>): void {
283
+ const block = find(id)
284
+ if (!block) return
285
+ Object.assign(block, patch)
286
+ touch()
287
+ },
288
+ removeBlockById: createRemoval(deps),
289
+ duplicateBlockById(id: string): void {
290
+ mutateTree((blocks) => {
291
+ const copy = duplicateBlock(blocks, id)
292
+ if (copy) select(copy.id)
293
+ return copy
294
+ })
295
+ },
296
+ moveBlockById(id: string, direction: -1 | 1): void {
297
+ mutateTree((blocks) => moveBlock(blocks, id, direction))
298
+ },
299
+ }
300
+ }
301
+
302
+ function createBranchActions({ views, touch, core }: MutationDeps) {
303
+ const findIf = (id: string) => {
304
+ const blocks = blocksOf(views)
305
+ const block = blocks ? findBlock(blocks, id) : null
306
+ return block?.type === 'if' ? block : null
307
+ }
308
+ return {
309
+ setElse(id: string, isEnabled: boolean): void {
310
+ const block = findIf(id)
311
+ if (!block || Boolean(block.elseChildren) === isEnabled) return
312
+ block.elseChildren = isEnabled
313
+ ? [createBlock('paragraph', { locale: core.locale })]
314
+ : null
315
+ touch()
316
+ },
317
+ updateLocale(patch: LocalePatch): void {
178
318
  if (!views.current.value) return
179
319
  Object.assign(views.current.value, patch)
180
320
  touch()
@@ -182,45 +322,137 @@ function createBlockActions({ views, select, touch }: MutationDeps) {
182
322
  }
183
323
  }
184
324
 
185
- function createLifecycleActions({ core, select, touch }: MutationDeps) {
186
- function createLocale(code: string, from?: string): void {
187
- const source = from ? core.content.locales[from] : undefined
188
- core.content.locales[code] = source
189
- ? { ...deepClone(source), blocks: source.blocks.map(cloneWithNewIds) }
190
- : emptyLocale()
191
- core.locale = code
192
- touch()
325
+ /** The locales whose content differs between two versions of it. */
326
+ function changedLocales(
327
+ before: TemplateContent,
328
+ after: TemplateContent,
329
+ ): string[] {
330
+ const codes = new Set([
331
+ ...Object.keys(before.locales),
332
+ ...Object.keys(after.locales),
333
+ ])
334
+ return [...codes].filter(
335
+ (code) =>
336
+ JSON.stringify(before.locales[code]) !==
337
+ JSON.stringify(after.locales[code]),
338
+ )
339
+ }
340
+
341
+ /**
342
+ * An undo or redo that only touches another locale switches to it, so the
343
+ * author sees what changed; `true` when it did.
344
+ */
345
+ function followChangedLocale(core: EditorCore, changed: string[]): boolean {
346
+ if (changed.length === 0 || changed.includes(core.locale)) return false
347
+ const target = changed.find((code) => core.content.locales[code])
348
+ if (!target) return false
349
+ core.locale = target
350
+ return true
351
+ }
352
+
353
+ function createHistoryActions(
354
+ core: EditorCore,
355
+ select: (id: string | null) => void,
356
+ announce: Announce,
357
+ ) {
358
+ function restore(snapshot: string | null, kind: AnnouncementKind): void {
359
+ if (snapshot === null) return
360
+ const next = JSON.parse(snapshot) as DraftSnapshot
361
+ const changed = changedLocales(core.content, next.content)
362
+ core.content = next.content
363
+ core.variables = next.variables
364
+ core.revision += 1
365
+ if (followChangedLocale(core, changed))
366
+ announce({ kind, locale: core.locale })
367
+ const blocks = core.content.locales[core.locale]?.blocks ?? []
368
+ if (core.selectedId && !findBlock(blocks, core.selectedId)) select(null)
193
369
  }
194
- function markSaved(): void {
195
- core.dirty = 0
370
+ const undo = (): void => restore(undoChange(core.history), 'undone_in_locale')
371
+ return {
372
+ undo,
373
+ redo: () => restore(redoChange(core.history), 'redone_in_locale'),
374
+ /** Undoes only when nothing changed since `present` was recorded. */
375
+ undoIfLatest(present: string): void {
376
+ if (core.history.present === present) undo()
377
+ },
196
378
  }
197
- function reset(content: TemplateContent): void {
198
- core.content = deepClone(content)
199
- core.dirty = 0
200
- select(null)
379
+ }
380
+
381
+ function createLifecycleActions({ core, select, touch }: MutationDeps) {
382
+ return {
383
+ touch,
384
+ /** Adds `code`, copied from `from` or blank in the template's `context`. */
385
+ createLocale(code: string, from?: string, context?: BlockContext): void {
386
+ const source = from ? core.content.locales[from] : undefined
387
+ core.content.locales[code] =
388
+ source && from
389
+ ? copyLocale(source, from, code)
390
+ : blankLocale(code, context)
391
+ core.locale = code
392
+ touch(true)
393
+ },
394
+ /**
395
+ * Replaces the draft's declarations; a rename also points every
396
+ * reference to `from` at `to`, all locales. One undoable step either way.
397
+ */
398
+ setVariables(next: VariableDefinition[], rename?: VariableRename): void {
399
+ if (rename) renameVariablePath(core.content, rename.from, rename.to)
400
+ core.variables = deepClone(next)
401
+ touch(true)
402
+ },
403
+ /** Marks the content up to `revision` as saved (the latest by default). */
404
+ markSaved(revision?: number): void {
405
+ core.savedRevision = revision ?? core.revision
406
+ },
407
+ reset(content: TemplateContent, variables = core.variables): void {
408
+ core.content = deepClone(content)
409
+ core.variables = deepClone(variables)
410
+ core.history = createHistory(snapshotOf(core))
411
+ core.savedRevision = core.revision
412
+ select(null)
413
+ },
201
414
  }
202
- return { createLocale, markSaved, reset, touch }
203
415
  }
204
416
 
417
+ /**
418
+ * The editor's state: the draft being edited (content and variable
419
+ * declarations), the selection and panes, and the history. Every mutation
420
+ * goes through `touch()`, which bumps the revision autosave watches and
421
+ * records an undo step.
422
+ */
205
423
  export function createEditorState(
206
424
  content: TemplateContent,
207
425
  locale: string,
208
426
  locales: string[],
427
+ clock: EditorClock = Date.now,
428
+ variables: VariableDefinition[] = [],
209
429
  ) {
210
- const core = createCore(content, locale, locales)
430
+ const core = createCore(content, variables, locale, locales)
211
431
  const views = createViews(core)
212
432
  const selection = createSelectionActions(core)
433
+ const touch = (isDiscrete = false): void => {
434
+ core.revision += 1
435
+ recordChange(core.history, snapshotOf(core), clock(), isDiscrete)
436
+ }
437
+ const announce: Announce = (announcement) => {
438
+ const serial = (core.announcement?.serial ?? 0) + 1
439
+ core.announcement = { ...announcement, serial }
440
+ }
213
441
  const deps: MutationDeps = {
214
442
  core,
215
443
  views,
216
444
  select: selection.select,
217
- touch: () => (core.dirty += 1),
445
+ touch,
446
+ announce,
218
447
  }
219
448
  return reactive({
220
449
  ...toRefs(core),
221
450
  ...views,
222
451
  ...selection,
452
+ ...createInsertActions(deps),
223
453
  ...createBlockActions(deps),
454
+ ...createBranchActions(deps),
455
+ ...createHistoryActions(core, selection.select, announce),
224
456
  ...createLifecycleActions(deps),
225
457
  })
226
458
  }
@@ -8,15 +8,27 @@ interface Props {
8
8
  email: ResolvedEmail
9
9
  }
10
10
 
11
+ const VIEWPORT = 'width=device-width, initial-scale=1'
12
+
13
+ // The DMS frame without its heading and separator: the blocks carry the
14
+ // e-mail's own title and footer. The logo is left out unless a mail client can
15
+ // load it (`publicLogoOnly`).
11
16
  defineProps<Props>()
12
17
  </script>
13
18
 
14
19
  <template>
15
- <EmailLayout :title="email.subject" :lang="locale">
16
- <EPreview v-if="email.preheader">{{ email.preheader }}</EPreview>
17
-
20
+ <EmailLayout
21
+ :lang="locale"
22
+ :preheader="email.preheader"
23
+ :divider="false"
24
+ logo-alt=""
25
+ public-logo-only
26
+ >
27
+ <template #head>
28
+ <meta name="viewport" :content="VIEWPORT" />
29
+ <title>{{ email.subject }}</title>
30
+ </template>
18
31
  <MailingBlocks :blocks="email.blocks" />
19
-
20
32
  <template #footer>
21
33
  <MailingBlocks :blocks="email.blocks" footer-only />
22
34
  </template>