@antelopejs/dms-mailing 0.3.4 → 0.4.1

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 (707) 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 +115 -41
  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 +0 -3
  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 +140 -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 -21
  412. package/frontend-vue/tests/dnd.test.ts +10 -0
  413. package/frontend-vue/tests/editor-block-list.test.ts +25 -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 +207 -0
  417. package/frontend-vue/tests/footer-block.test.ts +89 -0
  418. package/frontend-vue/tests/funnel-card.test.ts +117 -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 +37 -8
  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 +211 -0
  429. package/frontend-vue/tests/overview-gate.test.ts +147 -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 +130 -0
  438. package/frontend-vue/tests/send-actions.test.ts +128 -0
  439. package/frontend-vue/tests/send-modals.test.ts +416 -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 +76 -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 +450 -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 +371 -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 +23 -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/frontend-vue/vitest.config.ts +4 -1
  461. package/package.json +14 -12
  462. package/src/automation/email-event-trigger.ts +3 -3
  463. package/src/automation/send-template-action.ts +7 -5
  464. package/src/config.ts +3 -1
  465. package/src/constants.ts +18 -2
  466. package/src/crons/attention.ts +21 -0
  467. package/src/crons/index.ts +2 -0
  468. package/src/crons/jobs.ts +35 -0
  469. package/src/crons/retention.ts +7 -22
  470. package/src/data/displays.ts +18 -0
  471. package/src/data/index.ts +1 -0
  472. package/src/data/locale-fallback.ts +26 -0
  473. package/src/data/read-only-routes.ts +14 -0
  474. package/src/data/sends-table.ts +204 -19
  475. package/src/data/suppressions-table.ts +127 -0
  476. package/src/data/templates-table.ts +295 -63
  477. package/src/data-types/categories-type.ts +4 -5
  478. package/src/data-types/category-type.ts +1 -1
  479. package/src/db/models/index.ts +3 -0
  480. package/src/db/models/sends.model.ts +102 -1
  481. package/src/db/models/settings.model.ts +2 -12
  482. package/src/db/models/suppressions.model.ts +53 -0
  483. package/src/db/models/template-versions.model.ts +23 -0
  484. package/src/db/models/templates.model.ts +17 -0
  485. package/src/db/models/webhook-status.model.ts +25 -0
  486. package/src/db/tables/index.ts +3 -0
  487. package/src/db/tables/send-events.table.ts +6 -2
  488. package/src/db/tables/sends.table.ts +37 -2
  489. package/src/db/tables/suppressions.table.ts +46 -0
  490. package/src/db/tables/template-versions.table.ts +44 -0
  491. package/src/db/tables/templates.table.ts +67 -5
  492. package/src/db/tables/webhook-status.table.ts +44 -0
  493. package/src/engine/changes.ts +198 -0
  494. package/src/engine/conditions.ts +1 -7
  495. package/src/engine/coverage.ts +7 -2
  496. package/src/engine/fallback-html.ts +80 -42
  497. package/src/engine/index.ts +3 -0
  498. package/src/engine/plain-text.ts +86 -0
  499. package/src/engine/resolve.ts +49 -15
  500. package/src/engine/system-variables.ts +124 -0
  501. package/src/engine/tokens.ts +13 -1
  502. package/src/hooks/tenant-export.ts +21 -2
  503. package/src/implementations/dms-mailing/index.ts +1 -1
  504. package/src/index.ts +29 -13
  505. package/src/pages/editor.ts +13 -2
  506. package/src/pages/index.ts +6 -3
  507. package/src/pages/module.ts +31 -2
  508. package/src/pages/overview.ts +187 -128
  509. package/src/pages/sends.ts +271 -112
  510. package/src/pages/settings/form.ts +144 -54
  511. package/src/pages/settings/page.ts +9 -17
  512. package/src/pages/suppressions.ts +157 -0
  513. package/src/pages/templates.ts +140 -43
  514. package/src/permissions.ts +6 -4
  515. package/src/quick-actions.ts +12 -5
  516. package/src/routes/events.ts +110 -33
  517. package/src/routes/index.ts +2 -0
  518. package/src/routes/metrics.ts +348 -98
  519. package/src/routes/period.ts +23 -6
  520. package/src/routes/sends.ts +413 -41
  521. package/src/routes/settings.ts +291 -26
  522. package/src/routes/suppressions.ts +87 -0
  523. package/src/routes/template-versions.ts +116 -0
  524. package/src/routes/templates.ts +334 -48
  525. package/src/services/attention.ts +283 -30
  526. package/src/services/catalog.ts +28 -0
  527. package/src/services/categories.ts +28 -27
  528. package/src/services/checked-send.ts +123 -0
  529. package/src/services/events.ts +174 -20
  530. package/src/services/metrics.ts +137 -28
  531. package/src/services/notify.ts +126 -44
  532. package/src/services/provider-banner.ts +165 -0
  533. package/src/services/render.ts +1 -0
  534. package/src/services/send-stat-items.ts +160 -0
  535. package/src/services/send-stats.ts +122 -0
  536. package/src/services/send.ts +193 -31
  537. package/src/services/settings.ts +52 -8
  538. package/src/services/starters.ts +383 -0
  539. package/src/services/suppressions.ts +116 -0
  540. package/src/services/template-attention.ts +82 -0
  541. package/src/services/template-stats.ts +97 -0
  542. package/src/services/templates.ts +400 -37
  543. package/src/services/webhook-status.ts +202 -0
  544. package/src/types/blocks.ts +6 -0
  545. package/src/types/index.ts +2 -0
  546. package/src/types/sends.ts +64 -2
  547. package/src/types/suppressions.ts +27 -0
  548. package/src/types/webhook.ts +29 -0
  549. package/src/validation/settings.schema.ts +173 -17
  550. package/src/validation/templates.schema.ts +53 -1
  551. package/dist/antelope-modules.d.ts +0 -32
  552. package/dist/antelope-modules.js +0 -36
  553. package/dist/antelope-modules.js.map +0 -1
  554. package/dist/test/antelope.test.d.ts +0 -2
  555. package/dist/test/antelope.test.js +0 -180
  556. package/dist/test/antelope.test.js.map +0 -1
  557. package/dist/test/helpers/http.d.ts +0 -4
  558. package/dist/test/helpers/http.js +0 -28
  559. package/dist/test/helpers/http.js.map +0 -1
  560. package/dist/test/helpers/owner.d.ts +0 -5
  561. package/dist/test/helpers/owner.js +0 -30
  562. package/dist/test/helpers/owner.js.map +0 -1
  563. package/dist/test/integration/events.test.d.ts +0 -1
  564. package/dist/test/integration/events.test.js +0 -143
  565. package/dist/test/integration/events.test.js.map +0 -1
  566. package/dist/test/integration/retention.test.d.ts +0 -1
  567. package/dist/test/integration/retention.test.js +0 -291
  568. package/dist/test/integration/retention.test.js.map +0 -1
  569. package/dist/test/integration/sends.test.d.ts +0 -1
  570. package/dist/test/integration/sends.test.js +0 -210
  571. package/dist/test/integration/sends.test.js.map +0 -1
  572. package/dist/test/integration/settings.test.d.ts +0 -1
  573. package/dist/test/integration/settings.test.js +0 -137
  574. package/dist/test/integration/settings.test.js.map +0 -1
  575. package/dist/test/integration/setup.test.d.ts +0 -1
  576. package/dist/test/integration/setup.test.js +0 -32
  577. package/dist/test/integration/setup.test.js.map +0 -1
  578. package/dist/test/integration/templates.test.d.ts +0 -1
  579. package/dist/test/integration/templates.test.js +0 -233
  580. package/dist/test/integration/templates.test.js.map +0 -1
  581. package/dist/test/integration/tenant-export.test.d.ts +0 -1
  582. package/dist/test/integration/tenant-export.test.js +0 -85
  583. package/dist/test/integration/tenant-export.test.js.map +0 -1
  584. package/dist/test/unit/attention.test.d.ts +0 -1
  585. package/dist/test/unit/attention.test.js +0 -109
  586. package/dist/test/unit/attention.test.js.map +0 -1
  587. package/dist/test/unit/automation.test.d.ts +0 -1
  588. package/dist/test/unit/automation.test.js +0 -104
  589. package/dist/test/unit/automation.test.js.map +0 -1
  590. package/dist/test/unit/categories.test.d.ts +0 -1
  591. package/dist/test/unit/categories.test.js +0 -38
  592. package/dist/test/unit/categories.test.js.map +0 -1
  593. package/dist/test/unit/engine/conditions.test.d.ts +0 -1
  594. package/dist/test/unit/engine/conditions.test.js +0 -23
  595. package/dist/test/unit/engine/conditions.test.js.map +0 -1
  596. package/dist/test/unit/engine/coverage.test.d.ts +0 -1
  597. package/dist/test/unit/engine/coverage.test.js +0 -106
  598. package/dist/test/unit/engine/coverage.test.js.map +0 -1
  599. package/dist/test/unit/engine/fallback-html.test.d.ts +0 -1
  600. package/dist/test/unit/engine/fallback-html.test.js +0 -78
  601. package/dist/test/unit/engine/fallback-html.test.js.map +0 -1
  602. package/dist/test/unit/engine/paths.test.d.ts +0 -1
  603. package/dist/test/unit/engine/paths.test.js +0 -33
  604. package/dist/test/unit/engine/paths.test.js.map +0 -1
  605. package/dist/test/unit/engine/resolve.test.d.ts +0 -1
  606. package/dist/test/unit/engine/resolve.test.js +0 -157
  607. package/dist/test/unit/engine/resolve.test.js.map +0 -1
  608. package/dist/test/unit/engine/tokens.test.d.ts +0 -1
  609. package/dist/test/unit/engine/tokens.test.js +0 -26
  610. package/dist/test/unit/engine/tokens.test.js.map +0 -1
  611. package/dist/test/unit/events.test.d.ts +0 -1
  612. package/dist/test/unit/events.test.js +0 -13
  613. package/dist/test/unit/events.test.js.map +0 -1
  614. package/dist/test/unit/metrics.test.d.ts +0 -1
  615. package/dist/test/unit/metrics.test.js +0 -108
  616. package/dist/test/unit/metrics.test.js.map +0 -1
  617. package/dist/test/unit/notify.test.d.ts +0 -1
  618. package/dist/test/unit/notify.test.js +0 -49
  619. package/dist/test/unit/notify.test.js.map +0 -1
  620. package/dist/test/unit/period.test.d.ts +0 -1
  621. package/dist/test/unit/period.test.js +0 -61
  622. package/dist/test/unit/period.test.js.map +0 -1
  623. package/dist/test/unit/permissions.test.d.ts +0 -1
  624. package/dist/test/unit/permissions.test.js +0 -27
  625. package/dist/test/unit/permissions.test.js.map +0 -1
  626. package/dist/test/unit/provider.test.d.ts +0 -1
  627. package/dist/test/unit/provider.test.js +0 -47
  628. package/dist/test/unit/provider.test.js.map +0 -1
  629. package/dist/test/unit/realtime.test.d.ts +0 -1
  630. package/dist/test/unit/realtime.test.js +0 -13
  631. package/dist/test/unit/realtime.test.js.map +0 -1
  632. package/dist/test/unit/render.test.d.ts +0 -1
  633. package/dist/test/unit/render.test.js +0 -23
  634. package/dist/test/unit/render.test.js.map +0 -1
  635. package/dist/test/unit/retention.test.d.ts +0 -1
  636. package/dist/test/unit/retention.test.js +0 -66
  637. package/dist/test/unit/retention.test.js.map +0 -1
  638. package/dist/test/unit/send-batch.test.d.ts +0 -1
  639. package/dist/test/unit/send-batch.test.js +0 -46
  640. package/dist/test/unit/send-batch.test.js.map +0 -1
  641. package/dist/test/unit/send.test.d.ts +0 -1
  642. package/dist/test/unit/send.test.js +0 -120
  643. package/dist/test/unit/send.test.js.map +0 -1
  644. package/dist/test/unit/smoke.test.d.ts +0 -1
  645. package/dist/test/unit/smoke.test.js +0 -10
  646. package/dist/test/unit/smoke.test.js.map +0 -1
  647. package/dist/test/unit/status.test.d.ts +0 -1
  648. package/dist/test/unit/status.test.js +0 -16
  649. package/dist/test/unit/status.test.js.map +0 -1
  650. package/dist/test/unit/templates.test.d.ts +0 -1
  651. package/dist/test/unit/templates.test.js +0 -48
  652. package/dist/test/unit/templates.test.js.map +0 -1
  653. package/dist/test/unit/tenant-export.test.d.ts +0 -1
  654. package/dist/test/unit/tenant-export.test.js +0 -59
  655. package/dist/test/unit/tenant-export.test.js.map +0 -1
  656. package/dist/test/unit/webhook-secret.test.d.ts +0 -1
  657. package/dist/test/unit/webhook-secret.test.js +0 -43
  658. package/dist/test/unit/webhook-secret.test.js.map +0 -1
  659. package/frontend-vue/app/components/AttentionCard.vue +0 -78
  660. package/frontend-vue/app/components/HtmlPreviewModal.vue +0 -20
  661. package/frontend-vue/app/components/ProviderChip.vue +0 -39
  662. package/frontend-vue/app/components/editor/EditorBlockSettings.vue +0 -186
  663. package/frontend-vue/app/components/editor/EditorDataPanel.vue +0 -130
  664. package/frontend-vue/app/components/editor/EditorDraftBar.vue +0 -35
  665. package/frontend-vue/app/components/editor/EditorRail.vue +0 -58
  666. package/frontend-vue/app/components/editor/EditorSubjectSettings.vue +0 -37
  667. package/frontend-vue/app/components/editor/EditorTemplateSettings.vue +0 -87
  668. package/frontend-vue/app/components/editor/EditorTestSendModal.vue +0 -66
  669. package/frontend-vue/app/components/editor/EditorVariables.vue +0 -163
  670. package/frontend-vue/app/composables/useMailingPeriodData.ts +0 -39
  671. package/frontend-vue/tests/smoke.test.ts +0 -8
  672. package/src/antelope-modules.ts +0 -48
  673. package/src/test/antelope.test.ts +0 -190
  674. package/src/test/helpers/http.ts +0 -23
  675. package/src/test/helpers/owner.ts +0 -37
  676. package/src/test/integration/events.test.ts +0 -159
  677. package/src/test/integration/retention.test.ts +0 -363
  678. package/src/test/integration/sends.test.ts +0 -233
  679. package/src/test/integration/settings.test.ts +0 -128
  680. package/src/test/integration/setup.test.ts +0 -35
  681. package/src/test/integration/templates.test.ts +0 -272
  682. package/src/test/integration/tenant-export.test.ts +0 -125
  683. package/src/test/unit/attention.test.ts +0 -129
  684. package/src/test/unit/automation.test.ts +0 -130
  685. package/src/test/unit/categories.test.ts +0 -51
  686. package/src/test/unit/engine/conditions.test.ts +0 -35
  687. package/src/test/unit/engine/coverage.test.ts +0 -118
  688. package/src/test/unit/engine/fallback-html.test.ts +0 -86
  689. package/src/test/unit/engine/paths.test.ts +0 -39
  690. package/src/test/unit/engine/resolve.test.ts +0 -176
  691. package/src/test/unit/engine/tokens.test.ts +0 -26
  692. package/src/test/unit/events.test.ts +0 -19
  693. package/src/test/unit/metrics.test.ts +0 -170
  694. package/src/test/unit/notify.test.ts +0 -71
  695. package/src/test/unit/period.test.ts +0 -91
  696. package/src/test/unit/permissions.test.ts +0 -30
  697. package/src/test/unit/provider.test.ts +0 -57
  698. package/src/test/unit/realtime.test.ts +0 -11
  699. package/src/test/unit/render.test.ts +0 -23
  700. package/src/test/unit/retention.test.ts +0 -91
  701. package/src/test/unit/send-batch.test.ts +0 -72
  702. package/src/test/unit/send.test.ts +0 -161
  703. package/src/test/unit/smoke.test.ts +0 -8
  704. package/src/test/unit/status.test.ts +0 -14
  705. package/src/test/unit/templates.test.ts +0 -52
  706. package/src/test/unit/tenant-export.test.ts +0 -88
  707. package/src/test/unit/webhook-secret.test.ts +0 -51
@@ -0,0 +1,120 @@
1
+ import type { VariableDefinition, VariableType } from '../types/mailing'
2
+
3
+ const TOKEN_OPEN = '{{'
4
+ const TOKEN_CLOSE = '}}'
5
+ const PATH_CHARACTERS = /^[A-Za-z0-9_.[\]]*$/
6
+ const PATH_CHARACTER = /[A-Za-z0-9_.[\]]/
7
+ const MAX_SUGGESTIONS = 8
8
+
9
+ /** An open `{{` the caret is typing a path after. */
10
+ export interface TokenQuery {
11
+ /** Index of the `{{`. */
12
+ start: number
13
+ query: string
14
+ }
15
+
16
+ /**
17
+ * The token being typed at `caret`: a `{{` before it, not yet closed, followed
18
+ * only by path characters. `null` when the caret is not inside one.
19
+ */
20
+ export function tokenQueryAt(text: string, caret: number): TokenQuery | null {
21
+ const before = text.slice(0, caret)
22
+ const start = before.lastIndexOf(TOKEN_OPEN)
23
+ if (start < 0) return null
24
+ const typed = before.slice(start + TOKEN_OPEN.length)
25
+ const query = typed.trimStart()
26
+ if (typed.includes(TOKEN_CLOSE) || !PATH_CHARACTERS.test(query)) return null
27
+ return { start, query }
28
+ }
29
+
30
+ export type SuggestionStatus = 'declared' | 'undeclared' | 'system'
31
+
32
+ export interface PathSuggestion {
33
+ path: string
34
+ type: VariableType | null
35
+ status: SuggestionStatus
36
+ }
37
+
38
+ /** What the autocomplete offers: declared paths, used ones, system ones. */
39
+ export interface SuggestionSources {
40
+ variables: VariableDefinition[]
41
+ detected: string[]
42
+ system: string[]
43
+ }
44
+
45
+ function allSuggestions(sources: SuggestionSources): PathSuggestion[] {
46
+ const declared = new Set(sources.variables.map((variable) => variable.path))
47
+ return [
48
+ ...sources.variables.map((variable) => ({
49
+ path: variable.path,
50
+ type: variable.type,
51
+ status: 'declared' as const,
52
+ })),
53
+ ...sources.detected
54
+ .filter((path) => !declared.has(path) && !sources.system.includes(path))
55
+ .map((path) => ({ path, type: null, status: 'undeclared' as const })),
56
+ ...sources.system.map((path) => ({
57
+ path,
58
+ type: null,
59
+ status: 'system' as const,
60
+ })),
61
+ ]
62
+ }
63
+
64
+ /**
65
+ * The paths matching `query`: those starting with it first, then those
66
+ * containing it, each group in source order; at most eight.
67
+ */
68
+ export function suggestPaths(
69
+ query: string,
70
+ sources: SuggestionSources,
71
+ ): PathSuggestion[] {
72
+ const needle = query.toLowerCase()
73
+ const matching = allSuggestions(sources).filter((entry) =>
74
+ entry.path.toLowerCase().includes(needle),
75
+ )
76
+ const starts = matching.filter((entry) =>
77
+ entry.path.toLowerCase().startsWith(needle),
78
+ )
79
+ const rest = matching.filter((entry) => !starts.includes(entry))
80
+ return [...starts, ...rest].slice(0, MAX_SUGGESTIONS)
81
+ }
82
+
83
+ /** A text after an insertion, and where the caret goes. */
84
+ export interface TextEdit {
85
+ text: string
86
+ caret: number
87
+ }
88
+
89
+ /**
90
+ * Replaces the token being typed (from its `{{` through the path characters
91
+ * after the caret and a closing `}}` if there is one) with `{{path}}`.
92
+ */
93
+ export function insertSuggestion(
94
+ text: string,
95
+ query: TokenQuery,
96
+ caret: number,
97
+ path: string,
98
+ ): TextEdit {
99
+ let end = caret
100
+ while (end < text.length && PATH_CHARACTER.test(text.charAt(end))) end += 1
101
+ if (text.startsWith(TOKEN_CLOSE, end)) end += TOKEN_CLOSE.length
102
+ const token = `${TOKEN_OPEN}${path}${TOKEN_CLOSE}`
103
+ return {
104
+ text: `${text.slice(0, query.start)}${token}${text.slice(end)}`,
105
+ caret: query.start + token.length,
106
+ }
107
+ }
108
+
109
+ /** Inserts `{{path}}` at the caret (a click in the Variables tab). */
110
+ export function insertTokenAt(
111
+ text: string,
112
+ caret: number,
113
+ path: string,
114
+ ): TextEdit {
115
+ const token = `${TOKEN_OPEN}${path}${TOKEN_CLOSE}`
116
+ return {
117
+ text: `${text.slice(0, caret)}${token}${text.slice(caret)}`,
118
+ caret: caret + token.length,
119
+ }
120
+ }
@@ -1,14 +1,14 @@
1
1
  import type { BlockType } from '../../types/mailing'
2
2
 
3
3
  export const BLOCK_COMPONENT_NAMES: Record<BlockType, string> = {
4
- hero: 'DmsMailingBlockHero',
5
- heading: 'DmsMailingBlockHeading',
6
- paragraph: 'DmsMailingBlockParagraph',
7
- code: 'DmsMailingBlockCode',
8
- list: 'DmsMailingBlockList',
9
- total: 'DmsMailingBlockTotal',
10
- button: 'DmsMailingBlockButton',
11
- if: 'DmsMailingEditorIfBlock',
12
- divider: 'DmsMailingBlockDivider',
13
- footer: 'DmsMailingBlockFooter',
4
+ hero: 'MailingBlockHero',
5
+ heading: 'MailingBlockHeading',
6
+ paragraph: 'MailingBlockParagraph',
7
+ code: 'MailingBlockCode',
8
+ list: 'MailingBlockList',
9
+ total: 'MailingBlockTotal',
10
+ button: 'MailingBlockButton',
11
+ if: 'MailingEditorIfBlock',
12
+ divider: 'MailingBlockDivider',
13
+ footer: 'MailingBlockFooter',
14
14
  }
@@ -1,5 +1,5 @@
1
1
  import type { Block } from '../../types/mailing'
2
- import type { PaletteEntry } from './factory'
2
+ import type { BlockContext, PaletteEntry } from './factory'
3
3
  import { createBlock } from './factory'
4
4
 
5
5
  export const DRAG_GROUP = { name: 'mailing-blocks' }
@@ -10,6 +10,10 @@ export const PALETTE_GROUP = {
10
10
  }
11
11
  export const DRAG_HANDLE = '.mailing-drag-handle'
12
12
 
13
- export function cloneFromPalette(entry: PaletteEntry): Block {
14
- return createBlock(entry.type)
13
+ /** The block a tile dropped on the canvas becomes, shaped by its template. */
14
+ export function cloneFromPalette(
15
+ entry: PaletteEntry,
16
+ context: BlockContext = {},
17
+ ): Block {
18
+ return createBlock(entry.type, context)
15
19
  }
@@ -1,25 +1,82 @@
1
- import type { Block, BlockType } from '../../types/mailing'
1
+ import type {
2
+ Block,
3
+ BlockType,
4
+ VariableDefinition,
5
+ VariableType,
6
+ } from '../../types/mailing'
7
+ import { hasFooterLinksByDefault } from '../system-variables'
2
8
 
3
9
  export interface PaletteEntry {
4
10
  type: BlockType
5
11
  icon: string
12
+ /** The tile spans both columns of the palette grid. */
13
+ isWide?: boolean
6
14
  }
7
15
 
16
+ /** The icon that stands for each block type, in the palette and the outline. */
17
+ export const BLOCK_ICONS: Record<BlockType, string> = {
18
+ heading: 'i-ph-text-h',
19
+ paragraph: 'i-ph-text-align-left',
20
+ hero: 'i-ph-image',
21
+ button: 'i-ph-cursor-click',
22
+ list: 'i-ph-list-bullets',
23
+ total: 'i-ph-receipt',
24
+ code: 'i-ph-hash',
25
+ divider: 'i-ph-minus',
26
+ footer: 'i-ph-rows',
27
+ if: 'i-ph-git-branch',
28
+ }
29
+
30
+ const entry = (type: BlockType, isWide = false): PaletteEntry => ({
31
+ type,
32
+ icon: BLOCK_ICONS[type],
33
+ isWide,
34
+ })
35
+
8
36
  export const BLOCK_PALETTE: PaletteEntry[] = [
9
- { type: 'heading', icon: 'i-ph-text-h' },
10
- { type: 'paragraph', icon: 'i-ph-text-align-left' },
11
- { type: 'button', icon: 'i-ph-cursor-click' },
12
- { type: 'hero', icon: 'i-ph-image' },
13
- { type: 'list', icon: 'i-ph-table' },
14
- { type: 'total', icon: 'i-ph-sigma' },
15
- { type: 'code', icon: 'i-ph-lock-key' },
16
- { type: 'divider', icon: 'i-ph-minus' },
17
- { type: 'footer', icon: 'i-ph-list-dashes' },
37
+ entry('heading'),
38
+ entry('paragraph'),
39
+ entry('hero'),
40
+ entry('button'),
41
+ entry('list'),
42
+ entry('total'),
43
+ entry('code'),
44
+ entry('divider'),
45
+ entry('footer', true),
18
46
  ]
19
47
 
20
- export const LOGIC_PALETTE: PaletteEntry[] = [
21
- { type: 'if', icon: 'i-ph-git-branch' },
22
- ]
48
+ export const LOGIC_PALETTE: PaletteEntry[] = [entry('if', true)]
49
+
50
+ const BLOCK_LABEL_PREFIX = 'dms_mailing.blocks'
51
+ const PALETTE_LABEL_PREFIX = 'dms_mailing.editor.palette.tiles'
52
+ const PALETTE_SPECIFIC_LABELS: BlockType[] = ['footer', 'if']
53
+
54
+ /** The i18n key naming a block type (outline, inspector, canvas tag). */
55
+ export function blockLabelKey(type: BlockType): string {
56
+ return `${BLOCK_LABEL_PREFIX}.${type}`
57
+ }
58
+
59
+ /** The i18n key of a palette tile, longer than the block name for some. */
60
+ export function paletteLabelKey(type: BlockType): string {
61
+ return PALETTE_SPECIFIC_LABELS.includes(type)
62
+ ? `${PALETTE_LABEL_PREFIX}.${type}`
63
+ : blockLabelKey(type)
64
+ }
65
+
66
+ /** Keeps the tiles whose label or type contains `query`, case-insensitively. */
67
+ export function filterPalette(
68
+ entries: PaletteEntry[],
69
+ query: string,
70
+ labelOf: (entry: PaletteEntry) => string,
71
+ ): PaletteEntry[] {
72
+ const needle = query.trim().toLowerCase()
73
+ if (!needle) return entries
74
+ return entries.filter(
75
+ (candidate) =>
76
+ labelOf(candidate).toLowerCase().includes(needle) ||
77
+ candidate.type.includes(needle),
78
+ )
79
+ }
23
80
 
24
81
  const ID_RADIX = 36
25
82
  const ID_LENGTH = 6
@@ -33,68 +90,172 @@ export function newBlockId(type: BlockType): string {
33
90
  return `${type}_${random.padEnd(ID_LENGTH, ID_PADDING)}`
34
91
  }
35
92
 
36
- type BlockBuilder = (id: string) => Block
93
+ /**
94
+ * What a new block may borrow from the template it lands in: the locale its
95
+ * labels are written in, and the variables a list or a condition can point at.
96
+ */
97
+ export interface BlockContext {
98
+ locale?: string
99
+ variables?: VariableDefinition[]
100
+ detected?: string[]
101
+ /** The template's category: a transactional footer has no links. */
102
+ category?: string | null
103
+ }
104
+
105
+ export interface FooterLabels {
106
+ unsubscribe: string
107
+ preferences: string
108
+ }
109
+
110
+ /**
111
+ * The words a new block starts with. They land in the e-mail itself, so they
112
+ * follow the language of the locale being edited, not the interface's.
113
+ */
114
+ export interface BlockPlaceholders extends FooterLabels {
115
+ heading: string
116
+ paragraph: string
117
+ button: string
118
+ total: string
119
+ }
120
+
121
+ /** The footer labels mirror the backend's `blankContent` footer. */
122
+ const BLOCK_PLACEHOLDERS: Record<string, BlockPlaceholders> = {
123
+ en: {
124
+ heading: 'New heading',
125
+ paragraph: 'Paragraph text…',
126
+ button: 'Button',
127
+ total: 'Total',
128
+ unsubscribe: 'Unsubscribe',
129
+ preferences: 'Preferences',
130
+ },
131
+ fr: {
132
+ heading: 'Nouveau titre',
133
+ paragraph: 'Votre texte…',
134
+ button: 'Bouton',
135
+ total: 'Total',
136
+ unsubscribe: 'Se désinscrire',
137
+ preferences: 'Préférences',
138
+ },
139
+ }
140
+ /** A footer without unsubscribe and preferences links. */
141
+ const NO_FOOTER_LINKS: FooterLabels = { unsubscribe: '', preferences: '' }
142
+ const DEFAULT_PLACEHOLDER_LANGUAGE = 'en'
143
+ const LANGUAGE_SEPARATOR = /[-_]/
144
+
145
+ /** The starting words of a locale (`fr`, `fr-BE`…), English otherwise. */
146
+ export function placeholdersFor(locale?: string): BlockPlaceholders {
147
+ const language = (locale ?? '').split(LANGUAGE_SEPARATOR)[0] ?? ''
148
+ return (BLOCK_PLACEHOLDERS[language] ??
149
+ BLOCK_PLACEHOLDERS[DEFAULT_PLACEHOLDER_LANGUAGE]) as BlockPlaceholders
150
+ }
151
+
152
+ /** The default footer labels of a locale (`fr`, `fr-BE`…), English otherwise. */
153
+ export function footerLabelsFor(locale?: string): FooterLabels {
154
+ const { unsubscribe, preferences } = placeholdersFor(locale)
155
+ return { unsubscribe, preferences }
156
+ }
157
+
158
+ /** Paths a block points at when the template has no fitting variable yet. */
159
+ const PLACEHOLDER_LIST_SOURCE = 'items'
160
+ const PLACEHOLDER_CONDITION_PATH = 'condition'
161
+ const LIST_TYPE: VariableType = 'array'
162
+ const CONDITION_TYPE: VariableType = 'boolean'
163
+
164
+ function declaredOfType(
165
+ context: BlockContext,
166
+ type: VariableType,
167
+ ): string | undefined {
168
+ return context.variables?.find((variable) => variable.type === type)?.path
169
+ }
170
+
171
+ function listSource(context: BlockContext): string {
172
+ return declaredOfType(context, LIST_TYPE) ?? PLACEHOLDER_LIST_SOURCE
173
+ }
174
+
175
+ function conditionPath(context: BlockContext): string {
176
+ return (
177
+ declaredOfType(context, CONDITION_TYPE) ??
178
+ context.variables?.[0]?.path ??
179
+ context.detected?.[0] ??
180
+ PLACEHOLDER_CONDITION_PATH
181
+ )
182
+ }
183
+
184
+ type BlockBuilder = (id: string, context: BlockContext) => Block
37
185
 
38
186
  const BUILDERS: Record<BlockType, BlockBuilder> = {
39
187
  hero: (id) => ({ id, type: 'hero', imageUrl: '', alt: '', visibleIf: null }),
40
- heading: (id) => ({
188
+ heading: (id, context) => ({
41
189
  id,
42
190
  type: 'heading',
43
- text: 'New heading',
191
+ text: placeholdersFor(context.locale).heading,
44
192
  align: 'left',
45
193
  size: DEFAULT_HEADING_SIZE,
46
194
  visibleIf: null,
47
195
  }),
48
- paragraph: (id) => ({
196
+ paragraph: (id, context) => ({
49
197
  id,
50
198
  type: 'paragraph',
51
- text: 'Paragraph text…',
199
+ text: placeholdersFor(context.locale).paragraph,
52
200
  align: 'left',
53
201
  visibleIf: null,
54
202
  }),
55
- code: (id) => ({ id, type: 'code', text: '{{code}}', visibleIf: null }),
56
- list: (id) => ({
203
+ code: (id) => ({ id, type: 'code', text: '', visibleIf: null }),
204
+ list: (id, context) => ({
57
205
  id,
58
206
  type: 'list',
59
- source: 'order.lines',
207
+ source: listSource(context),
60
208
  labelPath: 'label',
61
209
  valuePath: 'total',
62
210
  visibleIf: null,
63
211
  }),
64
- total: (id) => ({
212
+ total: (id, context) => ({
65
213
  id,
66
214
  type: 'total',
67
- label: 'Total',
68
- value: '{{order.total}}',
215
+ label: placeholdersFor(context.locale).total,
216
+ value: '',
69
217
  visibleIf: null,
70
218
  }),
71
- button: (id) => ({
219
+ button: (id, context) => ({
72
220
  id,
73
221
  type: 'button',
74
- text: 'Button',
222
+ text: placeholdersFor(context.locale).button,
75
223
  href: 'https://',
76
224
  align: 'left',
77
225
  visibleIf: null,
78
226
  }),
79
- if: (id) => ({
227
+ if: (id, context) => ({
80
228
  id,
81
229
  type: 'if',
82
- condition: { path: 'contact.isPro', operator: 'truthy', value: '' },
230
+ condition: { path: conditionPath(context), operator: 'truthy', value: '' },
83
231
  children: [],
84
232
  elseChildren: null,
85
233
  visibleIf: null,
86
234
  }),
87
235
  divider: (id) => ({ id, type: 'divider', visibleIf: null }),
88
- footer: (id) => ({
89
- id,
90
- type: 'footer',
91
- text: '',
92
- unsubscribeLabel: 'Unsubscribe',
93
- preferencesLabel: 'Preferences',
94
- visibleIf: null,
95
- }),
236
+ footer: (id, context) => {
237
+ const labels = hasFooterLinksByDefault(context.category)
238
+ ? footerLabelsFor(context.locale)
239
+ : NO_FOOTER_LINKS
240
+ return {
241
+ id,
242
+ type: 'footer',
243
+ text: '',
244
+ unsubscribeLabel: labels.unsubscribe,
245
+ preferencesLabel: labels.preferences,
246
+ visibleIf: null,
247
+ }
248
+ },
96
249
  }
97
250
 
98
- export function createBlock(type: BlockType): Block {
99
- return BUILDERS[type](newBlockId(type))
251
+ /**
252
+ * A new block of `type`. Its content stays neutral: no invented `{{token}}`;
253
+ * a list or a condition points at a declared variable of the right type when
254
+ * the template has one, the starting words speak the locale's language.
255
+ */
256
+ export function createBlock(
257
+ type: BlockType,
258
+ context: BlockContext = {},
259
+ ): Block {
260
+ return BUILDERS[type](newBlockId(type), context)
100
261
  }
@@ -1,12 +1,24 @@
1
1
  import type { BlockType } from '../../types/mailing'
2
2
 
3
- export type FieldKind = 'text' | 'textarea' | 'url' | 'align' | 'size'
3
+ /**
4
+ * `tokens` multi-line text with `{{` autocomplete, `token` the one-line
5
+ * version, `text` plain text, `path` a variable path, `align` the alignment
6
+ * switch, `size` the heading size slider.
7
+ */
8
+ export type FieldKind = 'tokens' | 'token' | 'text' | 'path' | 'align' | 'size'
4
9
 
5
10
  export interface BlockField {
6
11
  key: string
7
12
  kind: FieldKind
8
13
  label: string
9
14
  help?: string
15
+ /** The backend refuses the content while this field is empty. */
16
+ isRequired?: boolean
17
+ }
18
+
19
+ interface FieldOptions {
20
+ help?: string
21
+ isRequired?: boolean
10
22
  }
11
23
 
12
24
  const LABEL_PREFIX = 'dms_mailing.editor.block_settings'
@@ -14,45 +26,53 @@ const LABEL_PREFIX = 'dms_mailing.editor.block_settings'
14
26
  const snakeCase = (key: string): string =>
15
27
  key.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`)
16
28
 
17
- const field = (key: string, kind: FieldKind, help?: string): BlockField => ({
29
+ const field = (
30
+ key: string,
31
+ kind: FieldKind,
32
+ options: FieldOptions = {},
33
+ ): BlockField => ({
18
34
  key,
19
35
  kind,
20
36
  label: `${LABEL_PREFIX}.${snakeCase(key)}`,
21
- help,
37
+ ...options,
22
38
  })
23
39
 
40
+ const REQUIRED: FieldOptions = { isRequired: true }
41
+
24
42
  const FIELDS: Record<BlockType, BlockField[]> = {
25
- hero: [field('imageUrl', 'url'), field('alt', 'text')],
43
+ hero: [field('imageUrl', 'token'), field('alt', 'token')],
26
44
  heading: [
27
- field('text', 'textarea', 'insert_hint'),
45
+ field('text', 'tokens'),
28
46
  field('align', 'align'),
29
47
  field('size', 'size'),
30
48
  ],
31
- paragraph: [
32
- field('text', 'textarea', 'insert_hint'),
33
- field('align', 'align'),
34
- ],
35
- code: [field('text', 'text')],
49
+ paragraph: [field('text', 'tokens'), field('align', 'align')],
50
+ code: [field('text', 'token')],
36
51
  list: [
37
- field('source', 'text', 'source_help'),
38
- field('labelPath', 'text'),
39
- field('valuePath', 'text'),
52
+ field('source', 'path', { help: 'source_help', isRequired: true }),
53
+ field('labelPath', 'text', REQUIRED),
54
+ field('valuePath', 'text', REQUIRED),
40
55
  ],
41
- total: [field('label', 'text'), field('value', 'text')],
56
+ total: [field('label', 'token'), field('value', 'token')],
42
57
  button: [
43
- field('text', 'text'),
44
- field('href', 'url'),
58
+ field('text', 'token'),
59
+ field('href', 'token'),
45
60
  field('align', 'align'),
46
61
  ],
47
62
  if: [],
48
63
  divider: [],
49
64
  footer: [
50
- field('text', 'textarea'),
51
- field('unsubscribeLabel', 'text'),
52
- field('preferencesLabel', 'text'),
65
+ field('text', 'tokens'),
66
+ field('unsubscribeLabel', 'text', { help: 'unsubscribe_label_help' }),
67
+ field('preferencesLabel', 'text', { help: 'preferences_label_help' }),
53
68
  ],
54
69
  }
55
70
 
56
71
  export function fieldsFor(type: BlockType): BlockField[] {
57
72
  return FIELDS[type]
58
73
  }
74
+
75
+ /** Whether `value` may be written to the content for `field`. */
76
+ export function isFieldValueValid(field: BlockField, value: string): boolean {
77
+ return !field.isRequired || value.trim() !== ''
78
+ }
@@ -2,4 +2,5 @@ export * from './components'
2
2
  export * from './dnd'
3
3
  export * from './factory'
4
4
  export * from './fields'
5
+ export * from './outline'
5
6
  export * from './tree'
@@ -0,0 +1,91 @@
1
+ import type { Block, BlockType } from '../../types/mailing'
2
+ import { BLOCK_ICONS } from './factory'
3
+
4
+ export type OutlineRowKind = 'block' | 'else'
5
+
6
+ /** One row of the Outline tab: a block, or the ELSE marker of a condition. */
7
+ export interface OutlineRow {
8
+ key: string
9
+ kind: OutlineRowKind
10
+ blockId: string
11
+ type: BlockType
12
+ icon: string
13
+ depth: number
14
+ summary: string
15
+ }
16
+
17
+ const SUMMARY_LENGTH = 18
18
+ const ELLIPSIS = '…'
19
+ const ELSE_ICON = 'i-ph-arrow-elbow-down-right'
20
+ const ELSE_KEY_SUFFIX = ':else'
21
+
22
+ type Summarizer = (block: Block) => string
23
+
24
+ const field =
25
+ (key: string): Summarizer =>
26
+ (block) => {
27
+ const value = (block as unknown as Record<string, unknown>)[key]
28
+ return typeof value === 'string' ? value : ''
29
+ }
30
+
31
+ const SUMMARIZERS: Record<BlockType, Summarizer> = {
32
+ hero: (block) => field('alt')(block) || field('imageUrl')(block),
33
+ heading: field('text'),
34
+ paragraph: field('text'),
35
+ code: field('text'),
36
+ list: field('source'),
37
+ total: field('value'),
38
+ button: field('text'),
39
+ if: (block) => (block.type === 'if' ? block.condition.path : ''),
40
+ divider: () => '',
41
+ footer: field('text'),
42
+ }
43
+
44
+ /** A short, single-line reading of a block's content. */
45
+ export function summarizeBlock(block: Block): string {
46
+ const text = SUMMARIZERS[block.type](block).replace(/\s+/g, ' ').trim()
47
+ return text.length > SUMMARY_LENGTH
48
+ ? `${text.slice(0, SUMMARY_LENGTH).trimEnd()}${ELLIPSIS}`
49
+ : text
50
+ }
51
+
52
+ function blockRow(block: Block, depth: number): OutlineRow {
53
+ return {
54
+ key: block.id,
55
+ kind: 'block',
56
+ blockId: block.id,
57
+ type: block.type,
58
+ icon: BLOCK_ICONS[block.type],
59
+ depth,
60
+ summary: summarizeBlock(block),
61
+ }
62
+ }
63
+
64
+ function elseRow(block: Block, depth: number): OutlineRow {
65
+ return {
66
+ key: `${block.id}${ELSE_KEY_SUFFIX}`,
67
+ kind: 'else',
68
+ blockId: block.id,
69
+ type: block.type,
70
+ icon: ELSE_ICON,
71
+ depth,
72
+ summary: '',
73
+ }
74
+ }
75
+
76
+ function rowsOf(block: Block, depth: number): OutlineRow[] {
77
+ if (block.type !== 'if') return [blockRow(block, depth)]
78
+ const branches = buildOutline(block.children, depth + 1)
79
+ if (!block.elseChildren) return [blockRow(block, depth), ...branches]
80
+ return [
81
+ blockRow(block, depth),
82
+ ...branches,
83
+ elseRow(block, depth + 1),
84
+ ...buildOutline(block.elseChildren, depth + 1),
85
+ ]
86
+ }
87
+
88
+ /** The block tree as indented rows, condition branches nested. */
89
+ export function buildOutline(blocks: Block[], depth = 0): OutlineRow[] {
90
+ return blocks.flatMap((block) => rowsOf(block, depth))
91
+ }