@antelopejs/dms-mailing 0.2.2

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 (550) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +162 -0
  3. package/dist/antelope-modules.d.ts +32 -0
  4. package/dist/antelope-modules.js +36 -0
  5. package/dist/antelope-modules.js.map +1 -0
  6. package/dist/automation/email-event-trigger.d.ts +16 -0
  7. package/dist/automation/email-event-trigger.js +70 -0
  8. package/dist/automation/email-event-trigger.js.map +1 -0
  9. package/dist/automation/index.d.ts +4 -0
  10. package/dist/automation/index.js +39 -0
  11. package/dist/automation/index.js.map +1 -0
  12. package/dist/automation/send-template-action.d.ts +13 -0
  13. package/dist/automation/send-template-action.js +71 -0
  14. package/dist/automation/send-template-action.js.map +1 -0
  15. package/dist/config.d.ts +5 -0
  16. package/dist/config.js +13 -0
  17. package/dist/config.js.map +1 -0
  18. package/dist/constants.d.ts +28 -0
  19. package/dist/constants.js +32 -0
  20. package/dist/constants.js.map +1 -0
  21. package/dist/crons/index.d.ts +1 -0
  22. package/dist/crons/index.js +18 -0
  23. package/dist/crons/index.js.map +1 -0
  24. package/dist/crons/retention.d.ts +18 -0
  25. package/dist/crons/retention.js +107 -0
  26. package/dist/crons/retention.js.map +1 -0
  27. package/dist/data/body.d.ts +1 -0
  28. package/dist/data/body.js +18 -0
  29. package/dist/data/body.js.map +1 -0
  30. package/dist/data/index.d.ts +3 -0
  31. package/dist/data/index.js +20 -0
  32. package/dist/data/index.js.map +1 -0
  33. package/dist/data/sends-table.d.ts +28 -0
  34. package/dist/data/sends-table.js +161 -0
  35. package/dist/data/sends-table.js.map +1 -0
  36. package/dist/data/templates-table.d.ts +31 -0
  37. package/dist/data/templates-table.js +173 -0
  38. package/dist/data/templates-table.js.map +1 -0
  39. package/dist/data-types/categories-type.d.ts +35 -0
  40. package/dist/data-types/categories-type.js +37 -0
  41. package/dist/data-types/categories-type.js.map +1 -0
  42. package/dist/data-types/category-type.d.ts +8 -0
  43. package/dist/data-types/category-type.js +43 -0
  44. package/dist/data-types/category-type.js.map +1 -0
  45. package/dist/data-types/index.d.ts +2 -0
  46. package/dist/data-types/index.js +19 -0
  47. package/dist/data-types/index.js.map +1 -0
  48. package/dist/db/index.d.ts +2 -0
  49. package/dist/db/index.js +19 -0
  50. package/dist/db/index.js.map +1 -0
  51. package/dist/db/models/index.d.ts +4 -0
  52. package/dist/db/models/index.js +21 -0
  53. package/dist/db/models/index.js.map +1 -0
  54. package/dist/db/models/send-events.model.d.ts +27 -0
  55. package/dist/db/models/send-events.model.js +35 -0
  56. package/dist/db/models/send-events.model.js.map +1 -0
  57. package/dist/db/models/sends.model.d.ts +32 -0
  58. package/dist/db/models/sends.model.js +115 -0
  59. package/dist/db/models/sends.model.js.map +1 -0
  60. package/dist/db/models/settings.model.d.ts +23 -0
  61. package/dist/db/models/settings.model.js +13 -0
  62. package/dist/db/models/settings.model.js.map +1 -0
  63. package/dist/db/models/templates.model.d.ts +23 -0
  64. package/dist/db/models/templates.model.js +13 -0
  65. package/dist/db/models/templates.model.js.map +1 -0
  66. package/dist/db/tables/index.d.ts +4 -0
  67. package/dist/db/tables/index.js +21 -0
  68. package/dist/db/tables/index.js.map +1 -0
  69. package/dist/db/tables/send-events.table.d.ts +10 -0
  70. package/dist/db/tables/send-events.table.js +44 -0
  71. package/dist/db/tables/send-events.table.js.map +1 -0
  72. package/dist/db/tables/sends.table.d.ts +26 -0
  73. package/dist/db/tables/sends.table.js +113 -0
  74. package/dist/db/tables/sends.table.js.map +1 -0
  75. package/dist/db/tables/settings.table.d.ts +15 -0
  76. package/dist/db/tables/settings.table.js +68 -0
  77. package/dist/db/tables/settings.table.js.map +1 -0
  78. package/dist/db/tables/templates.table.d.ts +18 -0
  79. package/dist/db/tables/templates.table.js +81 -0
  80. package/dist/db/tables/templates.table.js.map +1 -0
  81. package/dist/engine/conditions.d.ts +2 -0
  82. package/dist/engine/conditions.js +20 -0
  83. package/dist/engine/conditions.js.map +1 -0
  84. package/dist/engine/coverage.d.ts +4 -0
  85. package/dist/engine/coverage.js +45 -0
  86. package/dist/engine/coverage.js.map +1 -0
  87. package/dist/engine/fallback-html.d.ts +4 -0
  88. package/dist/engine/fallback-html.js +84 -0
  89. package/dist/engine/fallback-html.js.map +1 -0
  90. package/dist/engine/index.d.ts +7 -0
  91. package/dist/engine/index.js +24 -0
  92. package/dist/engine/index.js.map +1 -0
  93. package/dist/engine/paths.d.ts +3 -0
  94. package/dist/engine/paths.js +32 -0
  95. package/dist/engine/paths.js.map +1 -0
  96. package/dist/engine/resolve.d.ts +4 -0
  97. package/dist/engine/resolve.js +139 -0
  98. package/dist/engine/resolve.js.map +1 -0
  99. package/dist/engine/tokens.d.ts +7 -0
  100. package/dist/engine/tokens.js +29 -0
  101. package/dist/engine/tokens.js.map +1 -0
  102. package/dist/engine/walk.d.ts +3 -0
  103. package/dist/engine/walk.js +14 -0
  104. package/dist/engine/walk.js.map +1 -0
  105. package/dist/hooks/index.d.ts +1 -0
  106. package/dist/hooks/index.js +18 -0
  107. package/dist/hooks/index.js.map +1 -0
  108. package/dist/hooks/tenant-export.d.ts +14 -0
  109. package/dist/hooks/tenant-export.js +84 -0
  110. package/dist/hooks/tenant-export.js.map +1 -0
  111. package/dist/implementations/dms-mailing/index.d.ts +2 -0
  112. package/dist/implementations/dms-mailing/index.js +8 -0
  113. package/dist/implementations/dms-mailing/index.js.map +1 -0
  114. package/dist/index.d.ts +13 -0
  115. package/dist/index.js +95 -0
  116. package/dist/index.js.map +1 -0
  117. package/dist/pages/editor.d.ts +9 -0
  118. package/dist/pages/editor.js +30 -0
  119. package/dist/pages/editor.js.map +1 -0
  120. package/dist/pages/index.d.ts +7 -0
  121. package/dist/pages/index.js +24 -0
  122. package/dist/pages/index.js.map +1 -0
  123. package/dist/pages/module.d.ts +1 -0
  124. package/dist/pages/module.js +12 -0
  125. package/dist/pages/module.js.map +1 -0
  126. package/dist/pages/overview.d.ts +14 -0
  127. package/dist/pages/overview.js +92 -0
  128. package/dist/pages/overview.js.map +1 -0
  129. package/dist/pages/sends.d.ts +13 -0
  130. package/dist/pages/sends.js +125 -0
  131. package/dist/pages/sends.js.map +1 -0
  132. package/dist/pages/settings/form.d.ts +1 -0
  133. package/dist/pages/settings/form.js +68 -0
  134. package/dist/pages/settings/form.js.map +1 -0
  135. package/dist/pages/settings/index.d.ts +2 -0
  136. package/dist/pages/settings/index.js +19 -0
  137. package/dist/pages/settings/index.js.map +1 -0
  138. package/dist/pages/settings/page.d.ts +10 -0
  139. package/dist/pages/settings/page.js +35 -0
  140. package/dist/pages/settings/page.js.map +1 -0
  141. package/dist/pages/templates.d.ts +10 -0
  142. package/dist/pages/templates.js +140 -0
  143. package/dist/pages/templates.js.map +1 -0
  144. package/dist/permissions.d.ts +1 -0
  145. package/dist/permissions.js +37 -0
  146. package/dist/permissions.js.map +1 -0
  147. package/dist/quick-actions.d.ts +2 -0
  148. package/dist/quick-actions.js +18 -0
  149. package/dist/quick-actions.js.map +1 -0
  150. package/dist/realtime.d.ts +4 -0
  151. package/dist/realtime.js +44 -0
  152. package/dist/realtime.js.map +1 -0
  153. package/dist/routes/events.d.ts +9 -0
  154. package/dist/routes/events.js +63 -0
  155. package/dist/routes/events.js.map +1 -0
  156. package/dist/routes/index.d.ts +7 -0
  157. package/dist/routes/index.js +24 -0
  158. package/dist/routes/index.js.map +1 -0
  159. package/dist/routes/metrics.d.ts +54 -0
  160. package/dist/routes/metrics.js +235 -0
  161. package/dist/routes/metrics.js.map +1 -0
  162. package/dist/routes/period.d.ts +5 -0
  163. package/dist/routes/period.js +36 -0
  164. package/dist/routes/period.js.map +1 -0
  165. package/dist/routes/provider.d.ts +13 -0
  166. package/dist/routes/provider.js +41 -0
  167. package/dist/routes/provider.js.map +1 -0
  168. package/dist/routes/sends.d.ts +31 -0
  169. package/dist/routes/sends.js +178 -0
  170. package/dist/routes/sends.js.map +1 -0
  171. package/dist/routes/settings.d.ts +14 -0
  172. package/dist/routes/settings.js +72 -0
  173. package/dist/routes/settings.js.map +1 -0
  174. package/dist/routes/templates.d.ts +61 -0
  175. package/dist/routes/templates.js +230 -0
  176. package/dist/routes/templates.js.map +1 -0
  177. package/dist/services/attention.d.ts +23 -0
  178. package/dist/services/attention.js +104 -0
  179. package/dist/services/attention.js.map +1 -0
  180. package/dist/services/categories.d.ts +34 -0
  181. package/dist/services/categories.js +34 -0
  182. package/dist/services/categories.js.map +1 -0
  183. package/dist/services/events.d.ts +10 -0
  184. package/dist/services/events.js +89 -0
  185. package/dist/services/events.js.map +1 -0
  186. package/dist/services/metrics.d.ts +34 -0
  187. package/dist/services/metrics.js +103 -0
  188. package/dist/services/metrics.js.map +1 -0
  189. package/dist/services/notify.d.ts +20 -0
  190. package/dist/services/notify.js +63 -0
  191. package/dist/services/notify.js.map +1 -0
  192. package/dist/services/permissions.d.ts +2 -0
  193. package/dist/services/permissions.js +15 -0
  194. package/dist/services/permissions.js.map +1 -0
  195. package/dist/services/provider.d.ts +6 -0
  196. package/dist/services/provider.js +25 -0
  197. package/dist/services/provider.js.map +1 -0
  198. package/dist/services/render.d.ts +6 -0
  199. package/dist/services/render.js +21 -0
  200. package/dist/services/render.js.map +1 -0
  201. package/dist/services/send.d.ts +49 -0
  202. package/dist/services/send.js +259 -0
  203. package/dist/services/send.js.map +1 -0
  204. package/dist/services/settings.d.ts +4 -0
  205. package/dist/services/settings.js +61 -0
  206. package/dist/services/settings.js.map +1 -0
  207. package/dist/services/status.d.ts +3 -0
  208. package/dist/services/status.js +16 -0
  209. package/dist/services/status.js.map +1 -0
  210. package/dist/services/templates.d.ts +17 -0
  211. package/dist/services/templates.js +92 -0
  212. package/dist/services/templates.js.map +1 -0
  213. package/dist/test/antelope.test.d.ts +2 -0
  214. package/dist/test/antelope.test.js +179 -0
  215. package/dist/test/antelope.test.js.map +1 -0
  216. package/dist/test/helpers/http.d.ts +4 -0
  217. package/dist/test/helpers/http.js +28 -0
  218. package/dist/test/helpers/http.js.map +1 -0
  219. package/dist/test/helpers/owner.d.ts +5 -0
  220. package/dist/test/helpers/owner.js +30 -0
  221. package/dist/test/helpers/owner.js.map +1 -0
  222. package/dist/test/integration/events.test.d.ts +1 -0
  223. package/dist/test/integration/events.test.js +132 -0
  224. package/dist/test/integration/events.test.js.map +1 -0
  225. package/dist/test/integration/retention.test.d.ts +1 -0
  226. package/dist/test/integration/retention.test.js +291 -0
  227. package/dist/test/integration/retention.test.js.map +1 -0
  228. package/dist/test/integration/sends.test.d.ts +1 -0
  229. package/dist/test/integration/sends.test.js +157 -0
  230. package/dist/test/integration/sends.test.js.map +1 -0
  231. package/dist/test/integration/settings.test.d.ts +1 -0
  232. package/dist/test/integration/settings.test.js +45 -0
  233. package/dist/test/integration/settings.test.js.map +1 -0
  234. package/dist/test/integration/setup.test.d.ts +1 -0
  235. package/dist/test/integration/setup.test.js +32 -0
  236. package/dist/test/integration/setup.test.js.map +1 -0
  237. package/dist/test/integration/templates.test.d.ts +1 -0
  238. package/dist/test/integration/templates.test.js +167 -0
  239. package/dist/test/integration/templates.test.js.map +1 -0
  240. package/dist/test/integration/tenant-export.test.d.ts +1 -0
  241. package/dist/test/integration/tenant-export.test.js +85 -0
  242. package/dist/test/integration/tenant-export.test.js.map +1 -0
  243. package/dist/test/unit/attention.test.d.ts +1 -0
  244. package/dist/test/unit/attention.test.js +109 -0
  245. package/dist/test/unit/attention.test.js.map +1 -0
  246. package/dist/test/unit/automation.test.d.ts +1 -0
  247. package/dist/test/unit/automation.test.js +104 -0
  248. package/dist/test/unit/automation.test.js.map +1 -0
  249. package/dist/test/unit/categories.test.d.ts +1 -0
  250. package/dist/test/unit/categories.test.js +38 -0
  251. package/dist/test/unit/categories.test.js.map +1 -0
  252. package/dist/test/unit/engine/conditions.test.d.ts +1 -0
  253. package/dist/test/unit/engine/conditions.test.js +23 -0
  254. package/dist/test/unit/engine/conditions.test.js.map +1 -0
  255. package/dist/test/unit/engine/coverage.test.d.ts +1 -0
  256. package/dist/test/unit/engine/coverage.test.js +106 -0
  257. package/dist/test/unit/engine/coverage.test.js.map +1 -0
  258. package/dist/test/unit/engine/fallback-html.test.d.ts +1 -0
  259. package/dist/test/unit/engine/fallback-html.test.js +78 -0
  260. package/dist/test/unit/engine/fallback-html.test.js.map +1 -0
  261. package/dist/test/unit/engine/paths.test.d.ts +1 -0
  262. package/dist/test/unit/engine/paths.test.js +33 -0
  263. package/dist/test/unit/engine/paths.test.js.map +1 -0
  264. package/dist/test/unit/engine/resolve.test.d.ts +1 -0
  265. package/dist/test/unit/engine/resolve.test.js +157 -0
  266. package/dist/test/unit/engine/resolve.test.js.map +1 -0
  267. package/dist/test/unit/engine/tokens.test.d.ts +1 -0
  268. package/dist/test/unit/engine/tokens.test.js +26 -0
  269. package/dist/test/unit/engine/tokens.test.js.map +1 -0
  270. package/dist/test/unit/events.test.d.ts +1 -0
  271. package/dist/test/unit/events.test.js +13 -0
  272. package/dist/test/unit/events.test.js.map +1 -0
  273. package/dist/test/unit/metrics.test.d.ts +1 -0
  274. package/dist/test/unit/metrics.test.js +108 -0
  275. package/dist/test/unit/metrics.test.js.map +1 -0
  276. package/dist/test/unit/notify.test.d.ts +1 -0
  277. package/dist/test/unit/notify.test.js +49 -0
  278. package/dist/test/unit/notify.test.js.map +1 -0
  279. package/dist/test/unit/period.test.d.ts +1 -0
  280. package/dist/test/unit/period.test.js +44 -0
  281. package/dist/test/unit/period.test.js.map +1 -0
  282. package/dist/test/unit/provider.test.d.ts +1 -0
  283. package/dist/test/unit/provider.test.js +47 -0
  284. package/dist/test/unit/provider.test.js.map +1 -0
  285. package/dist/test/unit/realtime.test.d.ts +1 -0
  286. package/dist/test/unit/realtime.test.js +13 -0
  287. package/dist/test/unit/realtime.test.js.map +1 -0
  288. package/dist/test/unit/render.test.d.ts +1 -0
  289. package/dist/test/unit/render.test.js +23 -0
  290. package/dist/test/unit/render.test.js.map +1 -0
  291. package/dist/test/unit/retention.test.d.ts +1 -0
  292. package/dist/test/unit/retention.test.js +66 -0
  293. package/dist/test/unit/retention.test.js.map +1 -0
  294. package/dist/test/unit/send-batch.test.d.ts +1 -0
  295. package/dist/test/unit/send-batch.test.js +46 -0
  296. package/dist/test/unit/send-batch.test.js.map +1 -0
  297. package/dist/test/unit/send.test.d.ts +1 -0
  298. package/dist/test/unit/send.test.js +120 -0
  299. package/dist/test/unit/send.test.js.map +1 -0
  300. package/dist/test/unit/smoke.test.d.ts +1 -0
  301. package/dist/test/unit/smoke.test.js +10 -0
  302. package/dist/test/unit/smoke.test.js.map +1 -0
  303. package/dist/test/unit/status.test.d.ts +1 -0
  304. package/dist/test/unit/status.test.js +16 -0
  305. package/dist/test/unit/status.test.js.map +1 -0
  306. package/dist/test/unit/templates.test.d.ts +1 -0
  307. package/dist/test/unit/templates.test.js +48 -0
  308. package/dist/test/unit/templates.test.js.map +1 -0
  309. package/dist/test/unit/tenant-export.test.d.ts +1 -0
  310. package/dist/test/unit/tenant-export.test.js +59 -0
  311. package/dist/test/unit/tenant-export.test.js.map +1 -0
  312. package/dist/test/unit/webhook-secret.test.d.ts +1 -0
  313. package/dist/test/unit/webhook-secret.test.js +42 -0
  314. package/dist/test/unit/webhook-secret.test.js.map +1 -0
  315. package/dist/types/blocks.d.ts +2 -0
  316. package/dist/types/blocks.js +3 -0
  317. package/dist/types/blocks.js.map +1 -0
  318. package/dist/types/index.d.ts +4 -0
  319. package/dist/types/index.js +21 -0
  320. package/dist/types/index.js.map +1 -0
  321. package/dist/types/resolved.d.ts +68 -0
  322. package/dist/types/resolved.js +3 -0
  323. package/dist/types/resolved.js.map +1 -0
  324. package/dist/types/sends.d.ts +14 -0
  325. package/dist/types/sends.js +19 -0
  326. package/dist/types/sends.js.map +1 -0
  327. package/dist/types/settings.d.ts +16 -0
  328. package/dist/types/settings.js +11 -0
  329. package/dist/types/settings.js.map +1 -0
  330. package/dist/validation/settings.schema.d.ts +63 -0
  331. package/dist/validation/settings.schema.js +29 -0
  332. package/dist/validation/settings.schema.js.map +1 -0
  333. package/dist/validation/templates.schema.d.ts +248 -0
  334. package/dist/validation/templates.schema.js +126 -0
  335. package/dist/validation/templates.schema.js.map +1 -0
  336. package/frontend-vue/.editorconfig +12 -0
  337. package/frontend-vue/.prettierrc +11 -0
  338. package/frontend-vue/app/components/AttentionCard.vue +78 -0
  339. package/frontend-vue/app/components/CategoriesInput.vue +126 -0
  340. package/frontend-vue/app/components/CategoryInput.vue +67 -0
  341. package/frontend-vue/app/components/Editor.vue +63 -0
  342. package/frontend-vue/app/components/FunnelCard.vue +66 -0
  343. package/frontend-vue/app/components/HtmlPreviewModal.vue +20 -0
  344. package/frontend-vue/app/components/NewTemplateModal.vue +158 -0
  345. package/frontend-vue/app/components/ProviderChip.vue +39 -0
  346. package/frontend-vue/app/components/SendDrawer.vue +298 -0
  347. package/frontend-vue/app/components/TemplateCard.vue +127 -0
  348. package/frontend-vue/app/components/TemplateDrawer.vue +394 -0
  349. package/frontend-vue/app/components/TemplateGallery.vue +112 -0
  350. package/frontend-vue/app/components/TemplatePreviewFrame.vue +103 -0
  351. package/frontend-vue/app/components/TestSendModal.vue +162 -0
  352. package/frontend-vue/app/components/TokenText.vue +26 -0
  353. package/frontend-vue/app/components/blocks/Button.vue +19 -0
  354. package/frontend-vue/app/components/blocks/Code.vue +17 -0
  355. package/frontend-vue/app/components/blocks/Divider.vue +3 -0
  356. package/frontend-vue/app/components/blocks/Footer.vue +22 -0
  357. package/frontend-vue/app/components/blocks/Heading.vue +18 -0
  358. package/frontend-vue/app/components/blocks/Hero.vue +32 -0
  359. package/frontend-vue/app/components/blocks/List.vue +33 -0
  360. package/frontend-vue/app/components/blocks/Paragraph.vue +18 -0
  361. package/frontend-vue/app/components/blocks/Total.vue +18 -0
  362. package/frontend-vue/app/components/editor/EditorBlock.vue +121 -0
  363. package/frontend-vue/app/components/editor/EditorBlockList.vue +44 -0
  364. package/frontend-vue/app/components/editor/EditorBlockSettings.vue +186 -0
  365. package/frontend-vue/app/components/editor/EditorCanvas.vue +91 -0
  366. package/frontend-vue/app/components/editor/EditorConditionFields.vue +88 -0
  367. package/frontend-vue/app/components/editor/EditorDataPanel.vue +128 -0
  368. package/frontend-vue/app/components/editor/EditorDraftBar.vue +35 -0
  369. package/frontend-vue/app/components/editor/EditorIfBlock.vue +120 -0
  370. package/frontend-vue/app/components/editor/EditorMissingLocale.vue +62 -0
  371. package/frontend-vue/app/components/editor/EditorPalette.vue +62 -0
  372. package/frontend-vue/app/components/editor/EditorRail.vue +58 -0
  373. package/frontend-vue/app/components/editor/EditorSubjectSettings.vue +37 -0
  374. package/frontend-vue/app/components/editor/EditorTemplateSettings.vue +87 -0
  375. package/frontend-vue/app/components/editor/EditorTestSendModal.vue +66 -0
  376. package/frontend-vue/app/components/editor/EditorToolbar.vue +155 -0
  377. package/frontend-vue/app/components/editor/EditorVariables.vue +163 -0
  378. package/frontend-vue/app/components/editor/EditorWorkspace.vue +189 -0
  379. package/frontend-vue/app/composables/useEditorContext.ts +44 -0
  380. package/frontend-vue/app/composables/useMailingApi.ts +197 -0
  381. package/frontend-vue/app/composables/useMailingPeriodData.ts +39 -0
  382. package/frontend-vue/app/composables/useTemplateDrawer.ts +90 -0
  383. package/frontend-vue/app/composables/useTemplateEditor.ts +228 -0
  384. package/frontend-vue/app/emails/EmailMailingTemplate.vue +24 -0
  385. package/frontend-vue/app/emails/components/MailingBlocks.vue +117 -0
  386. package/frontend-vue/app/plugins/template-gallery-display.client.ts +14 -0
  387. package/frontend-vue/app/types/component.ts +29 -0
  388. package/frontend-vue/app/types/mailing.ts +365 -0
  389. package/frontend-vue/app/utils/blocks/components.ts +14 -0
  390. package/frontend-vue/app/utils/blocks/dnd.ts +15 -0
  391. package/frontend-vue/app/utils/blocks/factory.ts +100 -0
  392. package/frontend-vue/app/utils/blocks/fields.ts +58 -0
  393. package/frontend-vue/app/utils/blocks/index.ts +5 -0
  394. package/frontend-vue/app/utils/blocks/tree.ts +74 -0
  395. package/frontend-vue/app/utils/categories.ts +54 -0
  396. package/frontend-vue/app/utils/clone.ts +3 -0
  397. package/frontend-vue/app/utils/conditions.ts +50 -0
  398. package/frontend-vue/app/utils/editor-route.ts +18 -0
  399. package/frontend-vue/app/utils/gallery.ts +41 -0
  400. package/frontend-vue/app/utils/locales.ts +41 -0
  401. package/frontend-vue/app/utils/paths.ts +84 -0
  402. package/frontend-vue/app/utils/period.ts +23 -0
  403. package/frontend-vue/app/utils/testData.ts +10 -0
  404. package/frontend-vue/app/utils/timeline.ts +68 -0
  405. package/frontend-vue/app/utils/tokens.ts +24 -0
  406. package/frontend-vue/app/utils/variables.ts +48 -0
  407. package/frontend-vue/dms.email.ts +9 -0
  408. package/frontend-vue/dms.frontend.ts +29 -0
  409. package/frontend-vue/eslint.config.mjs +13 -0
  410. package/frontend-vue/i18n/locales/mailing-en-GB.json +397 -0
  411. package/frontend-vue/i18n/locales/mailing-fr-FR.json +397 -0
  412. package/frontend-vue/package.json +32 -0
  413. package/frontend-vue/pnpm-lock.yaml +6446 -0
  414. package/frontend-vue/pnpm-workspace.yaml +2 -0
  415. package/frontend-vue/tests/block-components.test.ts +14 -0
  416. package/frontend-vue/tests/block-fields.test.ts +23 -0
  417. package/frontend-vue/tests/blocks-factory.test.ts +28 -0
  418. package/frontend-vue/tests/blocks-tree.test.ts +49 -0
  419. package/frontend-vue/tests/categories.test.ts +64 -0
  420. package/frontend-vue/tests/conditions.test.ts +36 -0
  421. package/frontend-vue/tests/dnd.test.ts +11 -0
  422. package/frontend-vue/tests/editor-block-list.test.ts +54 -0
  423. package/frontend-vue/tests/editor-route.test.ts +23 -0
  424. package/frontend-vue/tests/email-render-cases.json +77 -0
  425. package/frontend-vue/tests/gallery.test.ts +39 -0
  426. package/frontend-vue/tests/i18n-editor.test.ts +21 -0
  427. package/frontend-vue/tests/i18n.test.ts +83 -0
  428. package/frontend-vue/tests/locales.test.ts +38 -0
  429. package/frontend-vue/tests/mailing-api.test.ts +10 -0
  430. package/frontend-vue/tests/paths.test.ts +128 -0
  431. package/frontend-vue/tests/period.test.ts +13 -0
  432. package/frontend-vue/tests/smoke.test.ts +8 -0
  433. package/frontend-vue/tests/template-editor.test.ts +65 -0
  434. package/frontend-vue/tests/test-data.test.ts +15 -0
  435. package/frontend-vue/tests/timeline.test.ts +29 -0
  436. package/frontend-vue/tests/tokens.test.ts +12 -0
  437. package/frontend-vue/tests/variables.test.ts +46 -0
  438. package/frontend-vue/vitest.config.ts +10 -0
  439. package/package.json +94 -0
  440. package/src/antelope-modules.ts +48 -0
  441. package/src/automation/email-event-trigger.ts +112 -0
  442. package/src/automation/index.ts +33 -0
  443. package/src/automation/send-template-action.ts +100 -0
  444. package/src/config.ts +15 -0
  445. package/src/constants.ts +42 -0
  446. package/src/crons/index.ts +1 -0
  447. package/src/crons/retention.ts +126 -0
  448. package/src/data/body.ts +14 -0
  449. package/src/data/index.ts +3 -0
  450. package/src/data/sends-table.ts +154 -0
  451. package/src/data/templates-table.ts +202 -0
  452. package/src/data-types/categories-type.ts +29 -0
  453. package/src/data-types/category-type.ts +44 -0
  454. package/src/data-types/index.ts +2 -0
  455. package/src/db/index.ts +2 -0
  456. package/src/db/models/index.ts +4 -0
  457. package/src/db/models/send-events.model.ts +46 -0
  458. package/src/db/models/sends.model.ts +147 -0
  459. package/src/db/models/settings.model.ts +12 -0
  460. package/src/db/models/templates.model.ts +12 -0
  461. package/src/db/tables/index.ts +4 -0
  462. package/src/db/tables/send-events.table.ts +31 -0
  463. package/src/db/tables/sends.table.ts +85 -0
  464. package/src/db/tables/settings.table.ts +49 -0
  465. package/src/db/tables/templates.table.ts +70 -0
  466. package/src/engine/conditions.ts +30 -0
  467. package/src/engine/coverage.ts +67 -0
  468. package/src/engine/fallback-html.ts +116 -0
  469. package/src/engine/index.ts +7 -0
  470. package/src/engine/paths.ts +33 -0
  471. package/src/engine/resolve.ts +178 -0
  472. package/src/engine/tokens.ts +35 -0
  473. package/src/engine/walk.ts +18 -0
  474. package/src/hooks/index.ts +1 -0
  475. package/src/hooks/tenant-export.ts +127 -0
  476. package/src/implementations/dms-mailing/index.ts +2 -0
  477. package/src/index.ts +62 -0
  478. package/src/pages/editor.ts +23 -0
  479. package/src/pages/index.ts +11 -0
  480. package/src/pages/module.ts +9 -0
  481. package/src/pages/overview.ts +175 -0
  482. package/src/pages/sends.ts +174 -0
  483. package/src/pages/settings/form.ts +67 -0
  484. package/src/pages/settings/index.ts +2 -0
  485. package/src/pages/settings/page.ts +34 -0
  486. package/src/pages/templates.ts +141 -0
  487. package/src/permissions.ts +44 -0
  488. package/src/quick-actions.ts +20 -0
  489. package/src/realtime.ts +53 -0
  490. package/src/routes/events.ts +57 -0
  491. package/src/routes/index.ts +7 -0
  492. package/src/routes/metrics.ts +243 -0
  493. package/src/routes/period.ts +46 -0
  494. package/src/routes/provider.ts +31 -0
  495. package/src/routes/sends.ts +215 -0
  496. package/src/routes/settings.ts +66 -0
  497. package/src/routes/templates.ts +256 -0
  498. package/src/services/attention.ts +151 -0
  499. package/src/services/categories.ts +43 -0
  500. package/src/services/events.ts +121 -0
  501. package/src/services/metrics.ts +175 -0
  502. package/src/services/notify.ts +90 -0
  503. package/src/services/permissions.ts +42 -0
  504. package/src/services/provider.ts +46 -0
  505. package/src/services/render.ts +33 -0
  506. package/src/services/send.ts +409 -0
  507. package/src/services/settings.ts +74 -0
  508. package/src/services/status.ts +16 -0
  509. package/src/services/templates.ts +157 -0
  510. package/src/test/antelope.test.ts +188 -0
  511. package/src/test/helpers/http.ts +23 -0
  512. package/src/test/helpers/owner.ts +37 -0
  513. package/src/test/integration/events.test.ts +136 -0
  514. package/src/test/integration/retention.test.ts +363 -0
  515. package/src/test/integration/sends.test.ts +200 -0
  516. package/src/test/integration/settings.test.ts +50 -0
  517. package/src/test/integration/setup.test.ts +35 -0
  518. package/src/test/integration/templates.test.ts +229 -0
  519. package/src/test/integration/tenant-export.test.ts +125 -0
  520. package/src/test/unit/attention.test.ts +129 -0
  521. package/src/test/unit/automation.test.ts +130 -0
  522. package/src/test/unit/categories.test.ts +51 -0
  523. package/src/test/unit/engine/conditions.test.ts +35 -0
  524. package/src/test/unit/engine/coverage.test.ts +118 -0
  525. package/src/test/unit/engine/fallback-html.test.ts +86 -0
  526. package/src/test/unit/engine/paths.test.ts +39 -0
  527. package/src/test/unit/engine/resolve.test.ts +176 -0
  528. package/src/test/unit/engine/tokens.test.ts +26 -0
  529. package/src/test/unit/events.test.ts +19 -0
  530. package/src/test/unit/metrics.test.ts +170 -0
  531. package/src/test/unit/notify.test.ts +71 -0
  532. package/src/test/unit/period.test.ts +64 -0
  533. package/src/test/unit/provider.test.ts +57 -0
  534. package/src/test/unit/realtime.test.ts +11 -0
  535. package/src/test/unit/render.test.ts +23 -0
  536. package/src/test/unit/retention.test.ts +91 -0
  537. package/src/test/unit/send-batch.test.ts +72 -0
  538. package/src/test/unit/send.test.ts +161 -0
  539. package/src/test/unit/smoke.test.ts +8 -0
  540. package/src/test/unit/status.test.ts +14 -0
  541. package/src/test/unit/templates.test.ts +52 -0
  542. package/src/test/unit/tenant-export.test.ts +88 -0
  543. package/src/test/unit/webhook-secret.test.ts +50 -0
  544. package/src/types/blocks.ts +31 -0
  545. package/src/types/index.ts +4 -0
  546. package/src/types/resolved.ts +89 -0
  547. package/src/types/sends.ts +44 -0
  548. package/src/types/settings.ts +24 -0
  549. package/src/validation/settings.schema.ts +33 -0
  550. package/src/validation/templates.schema.ts +145 -0
@@ -0,0 +1,365 @@
1
+ /**
2
+ * Layer-side mirror of the backend mailing contracts.
3
+ *
4
+ * The block model, the resolved e-mail and the HTTP payloads are declared once
5
+ * here so every component and composable reads the same shapes; the source of
6
+ * truth is `src/types/blocks.ts` / `src/engine/resolved.ts` on the backend.
7
+ */
8
+
9
+ export type ConditionOperator = 'truthy' | 'falsy' | 'eq' | 'ne' | 'gt' | 'lt'
10
+
11
+ export interface Condition {
12
+ path: string
13
+ operator: ConditionOperator
14
+ value: string
15
+ }
16
+
17
+ export type TextAlign = 'left' | 'center' | 'right'
18
+
19
+ export type BlockType =
20
+ | 'hero'
21
+ | 'heading'
22
+ | 'paragraph'
23
+ | 'code'
24
+ | 'list'
25
+ | 'total'
26
+ | 'button'
27
+ | 'if'
28
+ | 'divider'
29
+ | 'footer'
30
+
31
+ export const BLOCK_TYPES: BlockType[] = [
32
+ 'hero',
33
+ 'heading',
34
+ 'paragraph',
35
+ 'code',
36
+ 'list',
37
+ 'total',
38
+ 'button',
39
+ 'if',
40
+ 'divider',
41
+ 'footer',
42
+ ]
43
+
44
+ export interface BlockBase {
45
+ id: string
46
+ type: BlockType
47
+ visibleIf: Condition | null
48
+ }
49
+
50
+ export interface HeroBlock extends BlockBase {
51
+ type: 'hero'
52
+ imageUrl: string
53
+ alt: string
54
+ }
55
+
56
+ export interface HeadingBlock extends BlockBase {
57
+ type: 'heading'
58
+ text: string
59
+ align: TextAlign
60
+ size: number
61
+ }
62
+
63
+ export interface ParagraphBlock extends BlockBase {
64
+ type: 'paragraph'
65
+ text: string
66
+ align: TextAlign
67
+ }
68
+
69
+ export interface CodeBlock extends BlockBase {
70
+ type: 'code'
71
+ text: string
72
+ }
73
+
74
+ export interface ListBlock extends BlockBase {
75
+ type: 'list'
76
+ source: string
77
+ labelPath: string
78
+ valuePath: string
79
+ }
80
+
81
+ export interface TotalBlock extends BlockBase {
82
+ type: 'total'
83
+ label: string
84
+ value: string
85
+ }
86
+
87
+ export interface ButtonBlock extends BlockBase {
88
+ type: 'button'
89
+ text: string
90
+ href: string
91
+ align: TextAlign
92
+ }
93
+
94
+ export interface IfBlock extends BlockBase {
95
+ type: 'if'
96
+ condition: Condition
97
+ children: Block[]
98
+ elseChildren: Block[] | null
99
+ }
100
+
101
+ export interface DividerBlock extends BlockBase {
102
+ type: 'divider'
103
+ }
104
+
105
+ export interface FooterBlock extends BlockBase {
106
+ type: 'footer'
107
+ text: string
108
+ unsubscribeLabel: string
109
+ preferencesLabel: string
110
+ }
111
+
112
+ export type Block =
113
+ | HeroBlock
114
+ | HeadingBlock
115
+ | ParagraphBlock
116
+ | CodeBlock
117
+ | ListBlock
118
+ | TotalBlock
119
+ | ButtonBlock
120
+ | IfBlock
121
+ | DividerBlock
122
+ | FooterBlock
123
+
124
+ export interface LocaleContent {
125
+ subject: string
126
+ preheader: string
127
+ blocks: Block[]
128
+ }
129
+
130
+ export interface TemplateContent {
131
+ locales: Record<string, LocaleContent>
132
+ }
133
+
134
+ export type VariableType =
135
+ 'string' | 'text' | 'number' | 'money' | 'date' | 'url' | 'boolean' | 'array'
136
+
137
+ export const VARIABLE_TYPES: VariableType[] = [
138
+ 'string',
139
+ 'text',
140
+ 'number',
141
+ 'money',
142
+ 'date',
143
+ 'url',
144
+ 'boolean',
145
+ 'array',
146
+ ]
147
+
148
+ export interface VariableDefinition {
149
+ path: string
150
+ type: VariableType
151
+ required: boolean
152
+ }
153
+
154
+ export type TemplateStatus = 'draft' | 'live' | 'archived'
155
+
156
+ export const TEMPLATE_STATUSES: TemplateStatus[] = ['draft', 'live', 'archived']
157
+
158
+ export interface ResolvedListRow {
159
+ label: string
160
+ value: string
161
+ }
162
+
163
+ export interface ResolvedHero {
164
+ id: string
165
+ type: 'hero'
166
+ imageUrl: string
167
+ alt: string
168
+ }
169
+
170
+ export interface ResolvedHeading {
171
+ id: string
172
+ type: 'heading'
173
+ text: string
174
+ align: TextAlign
175
+ size: number
176
+ }
177
+
178
+ export interface ResolvedParagraph {
179
+ id: string
180
+ type: 'paragraph'
181
+ text: string
182
+ align: TextAlign
183
+ }
184
+
185
+ export interface ResolvedCode {
186
+ id: string
187
+ type: 'code'
188
+ text: string
189
+ }
190
+
191
+ export interface ResolvedList {
192
+ id: string
193
+ type: 'list'
194
+ rows: ResolvedListRow[]
195
+ }
196
+
197
+ export interface ResolvedTotal {
198
+ id: string
199
+ type: 'total'
200
+ label: string
201
+ value: string
202
+ }
203
+
204
+ export interface ResolvedButton {
205
+ id: string
206
+ type: 'button'
207
+ text: string
208
+ href: string
209
+ align: TextAlign
210
+ }
211
+
212
+ export interface ResolvedDivider {
213
+ id: string
214
+ type: 'divider'
215
+ }
216
+
217
+ export interface ResolvedFooter {
218
+ id: string
219
+ type: 'footer'
220
+ text: string
221
+ unsubscribeLabel: string
222
+ preferencesLabel: string
223
+ unsubscribeUrl: string
224
+ preferencesUrl: string
225
+ }
226
+
227
+ export type ResolvedBlock =
228
+ | ResolvedHero
229
+ | ResolvedHeading
230
+ | ResolvedParagraph
231
+ | ResolvedCode
232
+ | ResolvedList
233
+ | ResolvedTotal
234
+ | ResolvedButton
235
+ | ResolvedDivider
236
+ | ResolvedFooter
237
+
238
+ export interface ResolvedEmail {
239
+ subject: string
240
+ preheader: string
241
+ blocks: ResolvedBlock[]
242
+ missing: string[]
243
+ hiddenBlockIds: string[]
244
+ }
245
+
246
+ export type SendStatus =
247
+ | 'queued'
248
+ | 'sent'
249
+ | 'delivered'
250
+ | 'opened'
251
+ | 'clicked'
252
+ | 'bounced'
253
+ | 'spam'
254
+ | 'failed'
255
+ | 'unsubscribed'
256
+
257
+ export const SEND_STATUSES: SendStatus[] = [
258
+ 'queued',
259
+ 'sent',
260
+ 'delivered',
261
+ 'opened',
262
+ 'clicked',
263
+ 'bounced',
264
+ 'spam',
265
+ 'failed',
266
+ 'unsubscribed',
267
+ ]
268
+
269
+ export type SendEventType = SendStatus
270
+
271
+ export interface TemplateCategory {
272
+ id: string
273
+ label: string
274
+ icon: string
275
+ }
276
+
277
+ export interface TemplateRow {
278
+ _id: string
279
+ slug: string
280
+ name: string
281
+ /** Absent or empty when the template is uncategorised. */
282
+ category?: string
283
+ status: TemplateStatus
284
+ /** Comma-separated locale codes of the content; blank means unknown. */
285
+ locales?: string
286
+ updatedAt: string
287
+ updatedBy: string
288
+ }
289
+
290
+ export interface TemplateContentResponse {
291
+ template: TemplateRow
292
+ content: TemplateContent
293
+ variables: VariableDefinition[]
294
+ /** Paths the content actually references, across every locale. */
295
+ detectedVariables: string[]
296
+ testData: Record<string, unknown>
297
+ fallbackLocale: string
298
+ categories: TemplateCategory[]
299
+ }
300
+
301
+ export interface PreviewResponse {
302
+ html: string
303
+ subject: string
304
+ locale: string
305
+ missing: string[]
306
+ hiddenBlockIds: string[]
307
+ }
308
+
309
+ export interface SendRow {
310
+ _id: string
311
+ templateId: string
312
+ templateSlug: string
313
+ locale: string
314
+ recipientEmail: string
315
+ recipientName?: string
316
+ status: SendStatus
317
+ provider?: string
318
+ providerMessageId?: string
319
+ latencyMs: number
320
+ error?: string
321
+ source?: string
322
+ json_variables: string
323
+ opens: number
324
+ clicks: number
325
+ createdAt: string
326
+ isTest: boolean
327
+ }
328
+
329
+ export interface SendEventRow {
330
+ _id: string
331
+ sendId: string
332
+ type: SendStatus
333
+ at: string
334
+ json_details: string
335
+ }
336
+
337
+ export interface SendDetailResponse {
338
+ send: SendRow
339
+ events: SendEventRow[]
340
+ }
341
+
342
+ export type AttentionTone = 'warning' | 'error' | 'neutral'
343
+
344
+ export interface AttentionItem {
345
+ id: string
346
+ tone: AttentionTone
347
+ icon: string
348
+ title: string
349
+ description: string
350
+ to?: string
351
+ params?: Record<string, string | number>
352
+ }
353
+
354
+ export interface FunnelStep {
355
+ id: string
356
+ label: string
357
+ count: number
358
+ rate: number
359
+ }
360
+
361
+ export interface ProviderInfo {
362
+ name: string
363
+ connected: boolean
364
+ features: Record<string, boolean> | null
365
+ }
@@ -0,0 +1,14 @@
1
+ import type { BlockType } from '../../types/mailing'
2
+
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',
14
+ }
@@ -0,0 +1,15 @@
1
+ import type { Block } from '../../types/mailing'
2
+ import type { PaletteEntry } from './factory'
3
+ import { createBlock } from './factory'
4
+
5
+ export const DRAG_GROUP = { name: 'mailing-blocks' }
6
+ export const PALETTE_GROUP = {
7
+ name: 'mailing-blocks',
8
+ pull: 'clone' as const,
9
+ put: false,
10
+ }
11
+ export const DRAG_HANDLE = '.mailing-drag-handle'
12
+
13
+ export function cloneFromPalette(entry: PaletteEntry): Block {
14
+ return createBlock(entry.type)
15
+ }
@@ -0,0 +1,100 @@
1
+ import type { Block, BlockType } from '../../types/mailing'
2
+
3
+ export interface PaletteEntry {
4
+ type: BlockType
5
+ icon: string
6
+ }
7
+
8
+ 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' },
18
+ ]
19
+
20
+ export const LOGIC_PALETTE: PaletteEntry[] = [
21
+ { type: 'if', icon: 'i-ph-git-branch' },
22
+ ]
23
+
24
+ const ID_RADIX = 36
25
+ const ID_LENGTH = 6
26
+ const ID_PADDING = '0'
27
+ const DEFAULT_HEADING_SIZE = 23
28
+
29
+ export function newBlockId(type: BlockType): string {
30
+ const random = Math.random()
31
+ .toString(ID_RADIX)
32
+ .slice(2, 2 + ID_LENGTH)
33
+ return `${type}_${random.padEnd(ID_LENGTH, ID_PADDING)}`
34
+ }
35
+
36
+ type BlockBuilder = (id: string) => Block
37
+
38
+ const BUILDERS: Record<BlockType, BlockBuilder> = {
39
+ hero: (id) => ({ id, type: 'hero', imageUrl: '', alt: '', visibleIf: null }),
40
+ heading: (id) => ({
41
+ id,
42
+ type: 'heading',
43
+ text: 'New heading',
44
+ align: 'left',
45
+ size: DEFAULT_HEADING_SIZE,
46
+ visibleIf: null,
47
+ }),
48
+ paragraph: (id) => ({
49
+ id,
50
+ type: 'paragraph',
51
+ text: 'Paragraph text…',
52
+ align: 'left',
53
+ visibleIf: null,
54
+ }),
55
+ code: (id) => ({ id, type: 'code', text: '{{code}}', visibleIf: null }),
56
+ list: (id) => ({
57
+ id,
58
+ type: 'list',
59
+ source: 'order.lines',
60
+ labelPath: 'label',
61
+ valuePath: 'total',
62
+ visibleIf: null,
63
+ }),
64
+ total: (id) => ({
65
+ id,
66
+ type: 'total',
67
+ label: 'Total',
68
+ value: '{{order.total}}',
69
+ visibleIf: null,
70
+ }),
71
+ button: (id) => ({
72
+ id,
73
+ type: 'button',
74
+ text: 'Button',
75
+ href: 'https://',
76
+ align: 'left',
77
+ visibleIf: null,
78
+ }),
79
+ if: (id) => ({
80
+ id,
81
+ type: 'if',
82
+ condition: { path: 'contact.isPro', operator: 'truthy', value: '' },
83
+ children: [],
84
+ elseChildren: null,
85
+ visibleIf: null,
86
+ }),
87
+ 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
+ }),
96
+ }
97
+
98
+ export function createBlock(type: BlockType): Block {
99
+ return BUILDERS[type](newBlockId(type))
100
+ }
@@ -0,0 +1,58 @@
1
+ import type { BlockType } from '../../types/mailing'
2
+
3
+ export type FieldKind = 'text' | 'textarea' | 'url' | 'align' | 'size'
4
+
5
+ export interface BlockField {
6
+ key: string
7
+ kind: FieldKind
8
+ label: string
9
+ help?: string
10
+ }
11
+
12
+ const LABEL_PREFIX = 'dms_mailing.editor.block_settings'
13
+
14
+ const snakeCase = (key: string): string =>
15
+ key.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`)
16
+
17
+ const field = (key: string, kind: FieldKind, help?: string): BlockField => ({
18
+ key,
19
+ kind,
20
+ label: `${LABEL_PREFIX}.${snakeCase(key)}`,
21
+ help,
22
+ })
23
+
24
+ const FIELDS: Record<BlockType, BlockField[]> = {
25
+ hero: [field('imageUrl', 'url'), field('alt', 'text')],
26
+ heading: [
27
+ field('text', 'textarea', 'insert_hint'),
28
+ field('align', 'align'),
29
+ field('size', 'size'),
30
+ ],
31
+ paragraph: [
32
+ field('text', 'textarea', 'insert_hint'),
33
+ field('align', 'align'),
34
+ ],
35
+ code: [field('text', 'text')],
36
+ list: [
37
+ field('source', 'text', 'source_help'),
38
+ field('labelPath', 'text'),
39
+ field('valuePath', 'text'),
40
+ ],
41
+ total: [field('label', 'text'), field('value', 'text')],
42
+ button: [
43
+ field('text', 'text'),
44
+ field('href', 'url'),
45
+ field('align', 'align'),
46
+ ],
47
+ if: [],
48
+ divider: [],
49
+ footer: [
50
+ field('text', 'textarea'),
51
+ field('unsubscribeLabel', 'text'),
52
+ field('preferencesLabel', 'text'),
53
+ ],
54
+ }
55
+
56
+ export function fieldsFor(type: BlockType): BlockField[] {
57
+ return FIELDS[type]
58
+ }
@@ -0,0 +1,5 @@
1
+ export * from './components'
2
+ export * from './dnd'
3
+ export * from './factory'
4
+ export * from './fields'
5
+ export * from './tree'
@@ -0,0 +1,74 @@
1
+ import type { Block, IfBlock } from '../../types/mailing'
2
+ import { deepClone } from '../clone'
3
+ import { newBlockId } from './factory'
4
+
5
+ const isIf = (block: Block): block is IfBlock => block.type === 'if'
6
+
7
+ const branchesOf = (block: Block): Block[][] =>
8
+ isIf(block)
9
+ ? [block.children, ...(block.elseChildren ? [block.elseChildren] : [])]
10
+ : []
11
+
12
+ export function findParentList(blocks: Block[], id: string): Block[] | null {
13
+ if (blocks.some((block) => block.id === id)) return blocks
14
+ for (const block of blocks) {
15
+ for (const branch of branchesOf(block)) {
16
+ const found = findParentList(branch, id)
17
+ if (found) return found
18
+ }
19
+ }
20
+ return null
21
+ }
22
+
23
+ export function findBlock(blocks: Block[], id: string): Block | null {
24
+ return findParentList(blocks, id)?.find((block) => block.id === id) ?? null
25
+ }
26
+
27
+ export function moveBlock(
28
+ blocks: Block[],
29
+ id: string,
30
+ direction: -1 | 1,
31
+ ): boolean {
32
+ const list = findParentList(blocks, id)
33
+ if (!list) return false
34
+ const index = list.findIndex((block) => block.id === id)
35
+ const target = index + direction
36
+ if (target < 0 || target >= list.length) return false
37
+ const [moved] = list.splice(index, 1)
38
+ list.splice(target, 0, moved as Block)
39
+ return true
40
+ }
41
+
42
+ export function cloneWithNewIds(block: Block): Block {
43
+ const copy = deepClone(block)
44
+ copy.id = newBlockId(copy.type)
45
+ if (!isIf(copy)) return copy
46
+ copy.children = copy.children.map(cloneWithNewIds)
47
+ copy.elseChildren = copy.elseChildren
48
+ ? copy.elseChildren.map(cloneWithNewIds)
49
+ : null
50
+ return copy
51
+ }
52
+
53
+ export function duplicateBlock(blocks: Block[], id: string): Block | null {
54
+ const list = findParentList(blocks, id)
55
+ if (!list) return null
56
+ const index = list.findIndex((block) => block.id === id)
57
+ const copy = cloneWithNewIds(list[index] as Block)
58
+ list.splice(index + 1, 0, copy)
59
+ return copy
60
+ }
61
+
62
+ export function removeBlock(blocks: Block[], id: string): boolean {
63
+ const list = findParentList(blocks, id)
64
+ if (!list) return false
65
+ list.splice(
66
+ list.findIndex((block) => block.id === id),
67
+ 1,
68
+ )
69
+ return true
70
+ }
71
+
72
+ export function insertBlock(list: Block[], index: number, block: Block): void {
73
+ list.splice(index, 0, block)
74
+ }
@@ -0,0 +1,54 @@
1
+ import type { TemplateCategory } from '../types/mailing'
2
+
3
+ const ID_SEPARATOR = '-'
4
+
5
+ /**
6
+ * Reads the category list off the settings form field, which stores it either
7
+ * as the array or as the JSON string the DMS round-trips. Never throws: this
8
+ * runs while rendering the field.
9
+ */
10
+ export function parseCategories(
11
+ value: TemplateCategory[] | string | null | undefined,
12
+ ): TemplateCategory[] {
13
+ if (Array.isArray(value)) return value
14
+ if (!value) return []
15
+ try {
16
+ const parsed: unknown = JSON.parse(value)
17
+ return Array.isArray(parsed) ? (parsed as TemplateCategory[]) : []
18
+ } catch {
19
+ return []
20
+ }
21
+ }
22
+
23
+ export function serializeCategories(categories: TemplateCategory[]): string {
24
+ return JSON.stringify(categories)
25
+ }
26
+
27
+ /**
28
+ * The id a new category gets from its label. Ids are stored on every template,
29
+ * so they stay ASCII and stable rather than following a later rename.
30
+ */
31
+ export function slugifyId(label: string): string {
32
+ return label
33
+ .normalize('NFD')
34
+ .replace(/\p{Diacritic}/gu, '')
35
+ .toLowerCase()
36
+ .replace(/[^a-z0-9]+/g, ID_SEPARATOR)
37
+ .replace(/^-+|-+$/g, '')
38
+ }
39
+
40
+ /**
41
+ * `USelect` is backed by Reka's `Select`, which reserves the empty string for
42
+ * "nothing selected". An item carrying it makes the listbox refuse to open at
43
+ * all — silently, with no console error. "No category" therefore travels as
44
+ * this sentinel inside the widget and is mapped back on the way out.
45
+ */
46
+ export const NO_CATEGORY = '__none__'
47
+
48
+ export function toCategoryOption(value: string | null | undefined): string {
49
+ return value || NO_CATEGORY
50
+ }
51
+
52
+ export function fromCategoryOption(value: string): string {
53
+ return value === NO_CATEGORY ? '' : value
54
+ }
@@ -0,0 +1,3 @@
1
+ export function deepClone<T>(value: T): T {
2
+ return JSON.parse(JSON.stringify(value)) as T
3
+ }