@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.
- package/README.md +232 -22
- package/dist/automation/email-event-trigger.js +2 -2
- package/dist/automation/email-event-trigger.js.map +1 -1
- package/dist/automation/send-template-action.js +2 -2
- package/dist/automation/send-template-action.js.map +1 -1
- package/dist/config.js +3 -1
- package/dist/config.js.map +1 -1
- package/dist/constants.d.ts +10 -1
- package/dist/constants.js +15 -3
- package/dist/constants.js.map +1 -1
- package/dist/crons/attention.d.ts +3 -0
- package/dist/crons/attention.js +19 -0
- package/dist/crons/attention.js.map +1 -0
- package/dist/crons/index.d.ts +2 -0
- package/dist/crons/index.js +2 -0
- package/dist/crons/index.js.map +1 -1
- package/dist/crons/jobs.d.ts +12 -0
- package/dist/crons/jobs.js +27 -0
- package/dist/crons/jobs.js.map +1 -0
- package/dist/crons/retention.d.ts +2 -4
- package/dist/crons/retention.js +7 -22
- package/dist/crons/retention.js.map +1 -1
- package/dist/data/displays.d.ts +5 -0
- package/dist/data/displays.js +23 -0
- package/dist/data/displays.js.map +1 -0
- package/dist/data/index.d.ts +1 -0
- package/dist/data/index.js +1 -0
- package/dist/data/index.js.map +1 -1
- package/dist/data/locale-fallback.d.ts +3 -0
- package/dist/data/locale-fallback.js +20 -0
- package/dist/data/locale-fallback.js.map +1 -0
- package/dist/data/read-only-routes.d.ts +7 -0
- package/dist/data/read-only-routes.js +13 -0
- package/dist/data/read-only-routes.js.map +1 -0
- package/dist/data/sends-table.d.ts +19 -5
- package/dist/data/sends-table.js +185 -17
- package/dist/data/sends-table.js.map +1 -1
- package/dist/data/suppressions-table.d.ts +24 -0
- package/dist/data/suppressions-table.js +120 -0
- package/dist/data/suppressions-table.js.map +1 -0
- package/dist/data/templates-table.d.ts +20 -2
- package/dist/data/templates-table.js +188 -31
- package/dist/data/templates-table.js.map +1 -1
- package/dist/data-types/categories-type.d.ts +11 -13
- package/dist/data-types/categories-type.js +3 -3
- package/dist/data-types/categories-type.js.map +1 -1
- package/dist/data-types/category-type.js +1 -1
- package/dist/data-types/category-type.js.map +1 -1
- package/dist/db/models/index.d.ts +3 -0
- package/dist/db/models/index.js +3 -0
- package/dist/db/models/index.js.map +1 -1
- package/dist/db/models/sends.model.d.ts +12 -1
- package/dist/db/models/sends.model.js +64 -0
- package/dist/db/models/sends.model.js.map +1 -1
- package/dist/db/models/settings.model.d.ts +0 -1
- package/dist/db/models/settings.model.js +0 -7
- package/dist/db/models/settings.model.js.map +1 -1
- package/dist/db/models/suppressions.model.d.ts +28 -0
- package/dist/db/models/suppressions.model.js +46 -0
- package/dist/db/models/suppressions.model.js.map +1 -0
- package/dist/db/models/template-versions.model.d.ts +24 -0
- package/dist/db/models/template-versions.model.js +17 -0
- package/dist/db/models/template-versions.model.js.map +1 -0
- package/dist/db/models/templates.model.d.ts +2 -0
- package/dist/db/models/templates.model.js +3 -0
- package/dist/db/models/templates.model.js.map +1 -1
- package/dist/db/models/webhook-status.model.d.ts +24 -0
- package/dist/db/models/webhook-status.model.js +21 -0
- package/dist/db/models/webhook-status.model.js.map +1 -0
- package/dist/db/tables/index.d.ts +3 -0
- package/dist/db/tables/index.js +3 -0
- package/dist/db/tables/index.js.map +1 -1
- package/dist/db/tables/send-events.table.d.ts +2 -2
- package/dist/db/tables/send-events.table.js.map +1 -1
- package/dist/db/tables/sends.table.d.ts +10 -2
- package/dist/db/tables/sends.table.js +34 -1
- package/dist/db/tables/sends.table.js.map +1 -1
- package/dist/db/tables/suppressions.table.d.ts +12 -0
- package/dist/db/tables/suppressions.table.js +53 -0
- package/dist/db/tables/suppressions.table.js.map +1 -0
- package/dist/db/tables/template-versions.table.d.ts +13 -0
- package/dist/db/tables/template-versions.table.js +59 -0
- package/dist/db/tables/template-versions.table.js.map +1 -0
- package/dist/db/tables/templates.table.d.ts +11 -0
- package/dist/db/tables/templates.table.js +45 -0
- package/dist/db/tables/templates.table.js.map +1 -1
- package/dist/db/tables/webhook-status.table.d.ts +14 -0
- package/dist/db/tables/webhook-status.table.js +58 -0
- package/dist/db/tables/webhook-status.table.js.map +1 -0
- package/dist/engine/changes.d.ts +12 -0
- package/dist/engine/changes.js +107 -0
- package/dist/engine/changes.js.map +1 -0
- package/dist/engine/conditions.js +0 -2
- package/dist/engine/conditions.js.map +1 -1
- package/dist/engine/coverage.d.ts +1 -0
- package/dist/engine/coverage.js +6 -2
- package/dist/engine/coverage.js.map +1 -1
- package/dist/engine/fallback-html.d.ts +1 -1
- package/dist/engine/fallback-html.js +51 -34
- package/dist/engine/fallback-html.js.map +1 -1
- package/dist/engine/index.d.ts +3 -0
- package/dist/engine/index.js +3 -0
- package/dist/engine/index.js.map +1 -1
- package/dist/engine/plain-text.d.ts +3 -0
- package/dist/engine/plain-text.js +60 -0
- package/dist/engine/plain-text.js.map +1 -0
- package/dist/engine/resolve.d.ts +2 -1
- package/dist/engine/resolve.js +17 -9
- package/dist/engine/resolve.js.map +1 -1
- package/dist/engine/system-variables.d.ts +10 -0
- package/dist/engine/system-variables.js +70 -0
- package/dist/engine/system-variables.js.map +1 -0
- package/dist/engine/tokens.d.ts +2 -1
- package/dist/engine/tokens.js +6 -2
- package/dist/engine/tokens.js.map +1 -1
- package/dist/hooks/tenant-export.js +13 -1
- package/dist/hooks/tenant-export.js.map +1 -1
- package/dist/implementations/dms-mailing/index.d.ts +1 -1
- package/dist/implementations/dms-mailing/index.js +2 -2
- package/dist/implementations/dms-mailing/index.js.map +1 -1
- package/dist/index.js +13 -11
- package/dist/index.js.map +1 -1
- package/dist/pages/editor.js +9 -2
- package/dist/pages/editor.js.map +1 -1
- package/dist/pages/index.d.ts +1 -0
- package/dist/pages/index.js +1 -0
- package/dist/pages/index.js.map +1 -1
- package/dist/pages/module.d.ts +2 -0
- package/dist/pages/module.js +26 -2
- package/dist/pages/module.js.map +1 -1
- package/dist/pages/overview.d.ts +2 -6
- package/dist/pages/overview.js +109 -52
- package/dist/pages/overview.js.map +1 -1
- package/dist/pages/sends.d.ts +3 -4
- package/dist/pages/sends.js +197 -60
- package/dist/pages/sends.js.map +1 -1
- package/dist/pages/settings/form.js +114 -53
- package/dist/pages/settings/form.js.map +1 -1
- package/dist/pages/settings/page.d.ts +0 -1
- package/dist/pages/settings/page.js +8 -11
- package/dist/pages/settings/page.js.map +1 -1
- package/dist/pages/suppressions.d.ts +9 -0
- package/dist/pages/suppressions.js +151 -0
- package/dist/pages/suppressions.js.map +1 -0
- package/dist/pages/templates.d.ts +0 -1
- package/dist/pages/templates.js +115 -41
- package/dist/pages/templates.js.map +1 -1
- package/dist/permissions.js +2 -2
- package/dist/permissions.js.map +1 -1
- package/dist/quick-actions.js +7 -4
- package/dist/quick-actions.js.map +1 -1
- package/dist/routes/events.d.ts +8 -5
- package/dist/routes/events.js +55 -24
- package/dist/routes/events.js.map +1 -1
- package/dist/routes/index.d.ts +2 -0
- package/dist/routes/index.js +2 -0
- package/dist/routes/index.js.map +1 -1
- package/dist/routes/metrics.d.ts +60 -23
- package/dist/routes/metrics.js +231 -83
- package/dist/routes/metrics.js.map +1 -1
- package/dist/routes/period.d.ts +2 -1
- package/dist/routes/period.js +9 -3
- package/dist/routes/period.js.map +1 -1
- package/dist/routes/sends.d.ts +49 -8
- package/dist/routes/sends.js +239 -24
- package/dist/routes/sends.js.map +1 -1
- package/dist/routes/settings.d.ts +44 -5
- package/dist/routes/settings.js +190 -15
- package/dist/routes/settings.js.map +1 -1
- package/dist/routes/suppressions.d.ts +16 -0
- package/dist/routes/suppressions.js +92 -0
- package/dist/routes/suppressions.js.map +1 -0
- package/dist/routes/template-versions.d.ts +28 -0
- package/dist/routes/template-versions.js +87 -0
- package/dist/routes/template-versions.js.map +1 -0
- package/dist/routes/templates.d.ts +60 -6
- package/dist/routes/templates.js +204 -35
- package/dist/routes/templates.js.map +1 -1
- package/dist/services/attention.d.ts +19 -6
- package/dist/services/attention.js +177 -12
- package/dist/services/attention.js.map +1 -1
- package/dist/services/catalog.d.ts +3 -0
- package/dist/services/catalog.js +25 -0
- package/dist/services/catalog.js.map +1 -0
- package/dist/services/categories.d.ts +8 -32
- package/dist/services/categories.js +14 -25
- package/dist/services/categories.js.map +1 -1
- package/dist/services/checked-send.d.ts +9 -0
- package/dist/services/checked-send.js +54 -0
- package/dist/services/checked-send.js.map +1 -0
- package/dist/services/events.d.ts +5 -0
- package/dist/services/events.js +102 -9
- package/dist/services/events.js.map +1 -1
- package/dist/services/metrics.d.ts +17 -6
- package/dist/services/metrics.js +57 -18
- package/dist/services/metrics.js.map +1 -1
- package/dist/services/notify.d.ts +18 -10
- package/dist/services/notify.js +82 -37
- package/dist/services/notify.js.map +1 -1
- package/dist/services/provider-banner.d.ts +14 -0
- package/dist/services/provider-banner.js +115 -0
- package/dist/services/provider-banner.js.map +1 -0
- package/dist/services/render.js.map +1 -1
- package/dist/services/send-stat-items.d.ts +7 -0
- package/dist/services/send-stat-items.js +127 -0
- package/dist/services/send-stat-items.js.map +1 -0
- package/dist/services/send-stats.d.ts +23 -0
- package/dist/services/send-stats.js +63 -0
- package/dist/services/send-stats.js.map +1 -0
- package/dist/services/send.d.ts +18 -3
- package/dist/services/send.js +81 -17
- package/dist/services/send.js.map +1 -1
- package/dist/services/settings.d.ts +5 -0
- package/dist/services/settings.js +28 -7
- package/dist/services/settings.js.map +1 -1
- package/dist/services/starters.d.ts +15 -0
- package/dist/services/starters.js +264 -0
- package/dist/services/starters.js.map +1 -0
- package/dist/services/suppressions.d.ts +20 -0
- package/dist/services/suppressions.js +53 -0
- package/dist/services/suppressions.js.map +1 -0
- package/dist/services/template-attention.d.ts +4 -0
- package/dist/services/template-attention.js +43 -0
- package/dist/services/template-attention.js.map +1 -0
- package/dist/services/template-stats.d.ts +27 -0
- package/dist/services/template-stats.js +59 -0
- package/dist/services/template-stats.js.map +1 -0
- package/dist/services/templates.d.ts +31 -4
- package/dist/services/templates.js +240 -21
- package/dist/services/templates.js.map +1 -1
- package/dist/services/webhook-status.d.ts +27 -0
- package/dist/services/webhook-status.js +134 -0
- package/dist/services/webhook-status.js.map +1 -0
- package/dist/types/blocks.d.ts +1 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/sends.d.ts +10 -1
- package/dist/types/sends.js +35 -2
- package/dist/types/sends.js.map +1 -1
- package/dist/types/suppressions.d.ts +9 -0
- package/dist/types/suppressions.js +10 -0
- package/dist/types/suppressions.js.map +1 -0
- package/dist/types/webhook.d.ts +17 -0
- package/dist/types/webhook.js +3 -0
- package/dist/types/webhook.js.map +1 -0
- package/dist/validation/settings.schema.d.ts +144 -9
- package/dist/validation/settings.schema.js +113 -16
- package/dist/validation/settings.schema.js.map +1 -1
- package/dist/validation/templates.schema.d.ts +137 -10
- package/dist/validation/templates.schema.js +28 -2
- package/dist/validation/templates.schema.js.map +1 -1
- package/frontend-vue/app/build/components/FormFieldRow.vue +90 -0
- package/frontend-vue/app/build/components/FormRows.vue +26 -0
- package/frontend-vue/app/build/components/RequiredLegend.vue +11 -0
- package/frontend-vue/app/components/CategoriesInput.vue +323 -77
- package/frontend-vue/app/components/CategoryInput.vue +13 -14
- package/frontend-vue/app/components/CategoryLabel.vue +34 -0
- package/frontend-vue/app/components/DuplicateModal.vue +201 -0
- package/frontend-vue/app/components/Editor.vue +55 -20
- package/frontend-vue/app/components/FixAddressModal.vue +133 -0
- package/frontend-vue/app/components/FunnelCard.vue +146 -43
- package/frontend-vue/app/components/LocaleSelect.vue +98 -0
- package/frontend-vue/app/components/NewTemplateModal.vue +388 -102
- package/frontend-vue/app/components/NewTemplateSourceTile.vue +70 -0
- package/frontend-vue/app/components/OverviewGate.vue +220 -0
- package/frontend-vue/app/components/PageHeader.vue +98 -0
- package/frontend-vue/app/components/RealSendModal.vue +555 -0
- package/frontend-vue/app/components/RecipientsField.vue +41 -0
- package/frontend-vue/app/components/RenderingModal.vue +189 -0
- package/frontend-vue/app/components/RetentionInput.vue +212 -0
- package/frontend-vue/app/components/SendDrawer.vue +542 -209
- package/frontend-vue/app/components/StarterGallery.vue +153 -0
- package/frontend-vue/app/components/SystemVariables.vue +63 -0
- package/frontend-vue/app/components/TemplateCard.vue +218 -57
- package/frontend-vue/app/components/TemplateDrawer.vue +785 -274
- package/frontend-vue/app/components/TemplateGallery.vue +312 -56
- package/frontend-vue/app/components/TemplatePreviewFrame.vue +112 -22
- package/frontend-vue/app/components/TestSendModal.vue +266 -95
- package/frontend-vue/app/components/TokenText.vue +61 -10
- package/frontend-vue/app/components/VariablePath.vue +20 -0
- package/frontend-vue/app/components/VersionHistory.vue +196 -0
- package/frontend-vue/app/components/VersionPreview.vue +164 -0
- package/frontend-vue/app/components/WebhookStatus.vue +130 -0
- package/frontend-vue/app/components/blocks/Button.vue +1 -1
- package/frontend-vue/app/components/blocks/Code.vue +1 -1
- package/frontend-vue/app/components/blocks/Footer.vue +56 -7
- package/frontend-vue/app/components/blocks/Heading.vue +1 -1
- package/frontend-vue/app/components/blocks/Hero.vue +13 -7
- package/frontend-vue/app/components/blocks/List.vue +50 -6
- package/frontend-vue/app/components/blocks/Paragraph.vue +1 -1
- package/frontend-vue/app/components/blocks/Total.vue +3 -3
- package/frontend-vue/app/components/editor/EditorAutocompleteMenu.vue +50 -0
- package/frontend-vue/app/components/editor/EditorBlock.vue +37 -90
- package/frontend-vue/app/components/editor/EditorBlockFields.vue +140 -0
- package/frontend-vue/app/components/editor/EditorBlockInspector.vue +92 -0
- package/frontend-vue/app/components/editor/EditorBlockList.vue +32 -25
- package/frontend-vue/app/components/editor/EditorBlockPicker.vue +58 -0
- package/frontend-vue/app/components/editor/EditorBlockTools.vue +71 -0
- package/frontend-vue/app/components/editor/EditorCanvas.vue +105 -70
- package/frontend-vue/app/components/editor/EditorCompare.vue +211 -0
- package/frontend-vue/app/components/editor/EditorConditionFields.vue +56 -46
- package/frontend-vue/app/components/editor/EditorConditionInspector.vue +54 -0
- package/frontend-vue/app/components/editor/EditorEnvelope.vue +77 -0
- package/frontend-vue/app/components/editor/EditorIfBlock.vue +36 -86
- package/frontend-vue/app/components/editor/EditorInsertPoint.vue +64 -0
- package/frontend-vue/app/components/editor/EditorInspector.vue +80 -0
- package/frontend-vue/app/components/editor/EditorLeftPane.vue +103 -0
- package/frontend-vue/app/components/editor/EditorLifecycle.vue +67 -0
- package/frontend-vue/app/components/editor/EditorLoading.vue +23 -0
- package/frontend-vue/app/components/editor/EditorLocaleSwitch.vue +61 -0
- package/frontend-vue/app/components/editor/EditorMissingLocale.vue +72 -48
- package/frontend-vue/app/components/editor/EditorMissingPolicy.vue +42 -0
- package/frontend-vue/app/components/editor/EditorNotice.vue +127 -0
- package/frontend-vue/app/components/editor/EditorOutline.vue +65 -0
- package/frontend-vue/app/components/editor/EditorPalette.vue +95 -42
- package/frontend-vue/app/components/editor/EditorPaletteGroup.vue +66 -0
- package/frontend-vue/app/components/editor/EditorPublishReview.vue +102 -0
- package/frontend-vue/app/components/editor/EditorSaveState.vue +88 -0
- package/frontend-vue/app/components/editor/EditorShortcutHint.vue +17 -0
- package/frontend-vue/app/components/editor/EditorSimChip.vue +51 -0
- package/frontend-vue/app/components/editor/EditorTemplatePane.vue +131 -0
- package/frontend-vue/app/components/editor/EditorTestDataPane.vue +98 -0
- package/frontend-vue/app/components/editor/EditorTitle.vue +82 -0
- package/frontend-vue/app/components/editor/EditorTokenField.vue +224 -0
- package/frontend-vue/app/components/editor/EditorToolbar.vue +34 -134
- package/frontend-vue/app/components/editor/EditorVariableForm.vue +100 -0
- package/frontend-vue/app/components/editor/EditorVariableRow.vue +87 -0
- package/frontend-vue/app/components/editor/EditorVariablesPane.vue +161 -0
- package/frontend-vue/app/components/editor/EditorViewSwitches.vue +76 -0
- package/frontend-vue/app/components/editor/EditorVisibility.vue +101 -0
- package/frontend-vue/app/components/editor/EditorWorkspace.vue +62 -149
- package/frontend-vue/app/composables/useBlockContext.ts +16 -0
- package/frontend-vue/app/composables/useEditorActions.ts +381 -0
- package/frontend-vue/app/composables/useEditorAnnouncements.ts +51 -0
- package/frontend-vue/app/composables/useEditorContext.ts +30 -23
- package/frontend-vue/app/composables/useEditorSession.ts +299 -0
- package/frontend-vue/app/composables/useEditorShortcuts.ts +81 -0
- package/frontend-vue/app/composables/useLocalizedDefaults.ts +17 -0
- package/frontend-vue/app/composables/useMailingApi.ts +142 -27
- package/frontend-vue/app/composables/useOverviewSetup.ts +13 -0
- package/frontend-vue/app/composables/useReplayToast.ts +72 -0
- package/frontend-vue/app/composables/useTemplateCategories.ts +35 -0
- package/frontend-vue/app/composables/useTemplateDrawer.ts +196 -62
- package/frontend-vue/app/composables/useTemplateEditor.ts +318 -86
- package/frontend-vue/app/emails/EmailMailingTemplate.vue +16 -4
- package/frontend-vue/app/emails/components/MailingBlocks.vue +96 -38
- package/frontend-vue/app/emails/components/MailingTextLines.vue +20 -0
- package/frontend-vue/app/plugins/column-displays.ts +65 -0
- package/frontend-vue/app/plugins/{template-gallery-display.client.ts → template-gallery-display.ts} +1 -1
- package/frontend-vue/app/types/mailing.ts +193 -18
- package/frontend-vue/app/utils/api-error.ts +33 -0
- package/frontend-vue/app/utils/autocomplete.ts +120 -0
- package/frontend-vue/app/utils/blocks/components.ts +10 -10
- package/frontend-vue/app/utils/blocks/dnd.ts +7 -3
- package/frontend-vue/app/utils/blocks/factory.ts +199 -38
- package/frontend-vue/app/utils/blocks/fields.ts +39 -19
- package/frontend-vue/app/utils/blocks/index.ts +1 -0
- package/frontend-vue/app/utils/blocks/outline.ts +91 -0
- package/frontend-vue/app/utils/blocks/tree.ts +114 -2
- package/frontend-vue/app/utils/categories.ts +201 -15
- package/frontend-vue/app/utils/changes.ts +152 -0
- package/frontend-vue/app/utils/conditions.ts +25 -19
- package/frontend-vue/app/utils/editor-checks.ts +266 -0
- package/frontend-vue/app/utils/editor-route.ts +43 -4
- package/frontend-vue/app/utils/funnel.ts +74 -0
- package/frontend-vue/app/utils/gallery.ts +179 -1
- package/frontend-vue/app/utils/history.ts +66 -0
- package/frontend-vue/app/utils/lifecycle.ts +185 -0
- package/frontend-vue/app/utils/locales.ts +107 -2
- package/frontend-vue/app/utils/overview-setup.ts +83 -0
- package/frontend-vue/app/utils/pages.ts +5 -0
- package/frontend-vue/app/utils/paths.ts +51 -15
- package/frontend-vue/app/utils/period.ts +62 -22
- package/frontend-vue/app/utils/problems.ts +181 -0
- package/frontend-vue/app/utils/recipients.ts +49 -0
- package/frontend-vue/app/utils/rendering-title.ts +23 -0
- package/frontend-vue/app/utils/send-actions.ts +207 -0
- package/frontend-vue/app/utils/send-link.ts +28 -0
- package/frontend-vue/app/utils/send-snippet.ts +97 -0
- package/frontend-vue/app/utils/send-stats.ts +24 -0
- package/frontend-vue/app/utils/send-values.ts +156 -0
- package/frontend-vue/app/utils/shortcuts.ts +113 -0
- package/frontend-vue/app/utils/slug.ts +82 -0
- package/frontend-vue/app/utils/starters.ts +23 -0
- package/frontend-vue/app/utils/suppression.ts +112 -0
- package/frontend-vue/app/utils/system-variables.ts +165 -0
- package/frontend-vue/app/utils/template-attention.ts +104 -0
- package/frontend-vue/app/utils/timeline.ts +221 -19
- package/frontend-vue/app/utils/tokens.ts +115 -13
- package/frontend-vue/app/utils/variable-types.ts +23 -0
- package/frontend-vue/app/utils/variables.ts +208 -1
- package/frontend-vue/app/utils/version-history.ts +53 -0
- package/frontend-vue/app/utils/webhook-status.ts +170 -0
- package/frontend-vue/dms.frontend.ts +19 -11
- package/frontend-vue/i18n/locales/mailing-en-GB.json +1173 -161
- package/frontend-vue/i18n/locales/mailing-fr-FR.json +1220 -208
- package/frontend-vue/package.json +2 -2
- package/frontend-vue/pnpm-lock.yaml +46 -161
- package/frontend-vue/tests/api-error.test.ts +27 -0
- package/frontend-vue/tests/autocomplete.test.ts +84 -0
- package/frontend-vue/tests/block-components.test.ts +0 -3
- package/frontend-vue/tests/block-fields.test.ts +12 -2
- package/frontend-vue/tests/blocks-factory.test.ts +46 -0
- package/frontend-vue/tests/blocks-outline.test.ts +102 -0
- package/frontend-vue/tests/blocks-tree.test.ts +28 -1
- package/frontend-vue/tests/categories-input.test.ts +140 -0
- package/frontend-vue/tests/categories.test.ts +148 -15
- package/frontend-vue/tests/changes.test.ts +136 -0
- package/frontend-vue/tests/column-displays.test.ts +73 -0
- package/frontend-vue/tests/conditions.test.ts +49 -21
- package/frontend-vue/tests/dnd.test.ts +10 -0
- package/frontend-vue/tests/editor-block-list.test.ts +25 -2
- package/frontend-vue/tests/editor-checks.test.ts +178 -0
- package/frontend-vue/tests/editor-route.test.ts +37 -9
- package/frontend-vue/tests/editor-session.test.ts +207 -0
- package/frontend-vue/tests/footer-block.test.ts +89 -0
- package/frontend-vue/tests/funnel-card.test.ts +117 -0
- package/frontend-vue/tests/funnel.test.ts +81 -0
- package/frontend-vue/tests/gallery.test.ts +124 -2
- package/frontend-vue/tests/helpers/nuxt-globals.ts +232 -0
- package/frontend-vue/tests/history.test.ts +48 -0
- package/frontend-vue/tests/i18n-editor.test.ts +37 -8
- package/frontend-vue/tests/i18n.test.ts +55 -0
- package/frontend-vue/tests/lifecycle.test.ts +145 -0
- package/frontend-vue/tests/locales.test.ts +60 -0
- package/frontend-vue/tests/localized-defaults.test.ts +54 -0
- package/frontend-vue/tests/new-template-modal.test.ts +211 -0
- package/frontend-vue/tests/overview-gate.test.ts +147 -0
- package/frontend-vue/tests/overview-setup.test.ts +78 -0
- package/frontend-vue/tests/paths.test.ts +23 -0
- package/frontend-vue/tests/period.test.ts +59 -10
- package/frontend-vue/tests/problems.test.ts +153 -0
- package/frontend-vue/tests/recipients.test.ts +47 -0
- package/frontend-vue/tests/rendering-title.test.ts +24 -0
- package/frontend-vue/tests/replay-toast.test.ts +67 -0
- package/frontend-vue/tests/retention-input.test.ts +130 -0
- package/frontend-vue/tests/send-actions.test.ts +128 -0
- package/frontend-vue/tests/send-modals.test.ts +416 -0
- package/frontend-vue/tests/send-snippet.test.ts +51 -0
- package/frontend-vue/tests/send-stats.test.ts +12 -0
- package/frontend-vue/tests/send-values.test.ts +119 -0
- package/frontend-vue/tests/shortcuts.test.ts +76 -0
- package/frontend-vue/tests/slug.test.ts +88 -0
- package/frontend-vue/tests/suppression.test.ts +159 -0
- package/frontend-vue/tests/system-variables.test.ts +133 -0
- package/frontend-vue/tests/template-attention.test.ts +123 -0
- package/frontend-vue/tests/template-drawer.test.ts +450 -0
- package/frontend-vue/tests/template-editor.test.ts +218 -9
- package/frontend-vue/tests/template-gallery-display.test.ts +32 -0
- package/frontend-vue/tests/template-gallery.test.ts +371 -0
- package/frontend-vue/tests/template-overlays.test.ts +125 -0
- package/frontend-vue/tests/timeline.test.ts +175 -20
- package/frontend-vue/tests/tokens.test.ts +79 -1
- package/frontend-vue/tests/variable-path.test.ts +23 -0
- package/frontend-vue/tests/variable-types.test.ts +20 -0
- package/frontend-vue/tests/variables.test.ts +90 -2
- package/frontend-vue/tests/version-history.test.ts +41 -0
- package/frontend-vue/tests/webhook-status.test.ts +108 -0
- package/frontend-vue/vitest.config.ts +4 -1
- package/package.json +14 -12
- package/src/automation/email-event-trigger.ts +3 -3
- package/src/automation/send-template-action.ts +7 -5
- package/src/config.ts +3 -1
- package/src/constants.ts +18 -2
- package/src/crons/attention.ts +21 -0
- package/src/crons/index.ts +2 -0
- package/src/crons/jobs.ts +35 -0
- package/src/crons/retention.ts +7 -22
- package/src/data/displays.ts +18 -0
- package/src/data/index.ts +1 -0
- package/src/data/locale-fallback.ts +26 -0
- package/src/data/read-only-routes.ts +14 -0
- package/src/data/sends-table.ts +204 -19
- package/src/data/suppressions-table.ts +127 -0
- package/src/data/templates-table.ts +295 -63
- package/src/data-types/categories-type.ts +4 -5
- package/src/data-types/category-type.ts +1 -1
- package/src/db/models/index.ts +3 -0
- package/src/db/models/sends.model.ts +102 -1
- package/src/db/models/settings.model.ts +2 -12
- package/src/db/models/suppressions.model.ts +53 -0
- package/src/db/models/template-versions.model.ts +23 -0
- package/src/db/models/templates.model.ts +17 -0
- package/src/db/models/webhook-status.model.ts +25 -0
- package/src/db/tables/index.ts +3 -0
- package/src/db/tables/send-events.table.ts +6 -2
- package/src/db/tables/sends.table.ts +37 -2
- package/src/db/tables/suppressions.table.ts +46 -0
- package/src/db/tables/template-versions.table.ts +44 -0
- package/src/db/tables/templates.table.ts +67 -5
- package/src/db/tables/webhook-status.table.ts +44 -0
- package/src/engine/changes.ts +198 -0
- package/src/engine/conditions.ts +1 -7
- package/src/engine/coverage.ts +7 -2
- package/src/engine/fallback-html.ts +80 -42
- package/src/engine/index.ts +3 -0
- package/src/engine/plain-text.ts +86 -0
- package/src/engine/resolve.ts +49 -15
- package/src/engine/system-variables.ts +124 -0
- package/src/engine/tokens.ts +13 -1
- package/src/hooks/tenant-export.ts +21 -2
- package/src/implementations/dms-mailing/index.ts +1 -1
- package/src/index.ts +29 -13
- package/src/pages/editor.ts +13 -2
- package/src/pages/index.ts +6 -3
- package/src/pages/module.ts +31 -2
- package/src/pages/overview.ts +187 -128
- package/src/pages/sends.ts +271 -112
- package/src/pages/settings/form.ts +144 -54
- package/src/pages/settings/page.ts +9 -17
- package/src/pages/suppressions.ts +157 -0
- package/src/pages/templates.ts +140 -43
- package/src/permissions.ts +6 -4
- package/src/quick-actions.ts +12 -5
- package/src/routes/events.ts +110 -33
- package/src/routes/index.ts +2 -0
- package/src/routes/metrics.ts +348 -98
- package/src/routes/period.ts +23 -6
- package/src/routes/sends.ts +413 -41
- package/src/routes/settings.ts +291 -26
- package/src/routes/suppressions.ts +87 -0
- package/src/routes/template-versions.ts +116 -0
- package/src/routes/templates.ts +334 -48
- package/src/services/attention.ts +283 -30
- package/src/services/catalog.ts +28 -0
- package/src/services/categories.ts +28 -27
- package/src/services/checked-send.ts +123 -0
- package/src/services/events.ts +174 -20
- package/src/services/metrics.ts +137 -28
- package/src/services/notify.ts +126 -44
- package/src/services/provider-banner.ts +165 -0
- package/src/services/render.ts +1 -0
- package/src/services/send-stat-items.ts +160 -0
- package/src/services/send-stats.ts +122 -0
- package/src/services/send.ts +193 -31
- package/src/services/settings.ts +52 -8
- package/src/services/starters.ts +383 -0
- package/src/services/suppressions.ts +116 -0
- package/src/services/template-attention.ts +82 -0
- package/src/services/template-stats.ts +97 -0
- package/src/services/templates.ts +400 -37
- package/src/services/webhook-status.ts +202 -0
- package/src/types/blocks.ts +6 -0
- package/src/types/index.ts +2 -0
- package/src/types/sends.ts +64 -2
- package/src/types/suppressions.ts +27 -0
- package/src/types/webhook.ts +29 -0
- package/src/validation/settings.schema.ts +173 -17
- package/src/validation/templates.schema.ts +53 -1
- package/dist/antelope-modules.d.ts +0 -32
- package/dist/antelope-modules.js +0 -36
- package/dist/antelope-modules.js.map +0 -1
- package/dist/test/antelope.test.d.ts +0 -2
- package/dist/test/antelope.test.js +0 -180
- package/dist/test/antelope.test.js.map +0 -1
- package/dist/test/helpers/http.d.ts +0 -4
- package/dist/test/helpers/http.js +0 -28
- package/dist/test/helpers/http.js.map +0 -1
- package/dist/test/helpers/owner.d.ts +0 -5
- package/dist/test/helpers/owner.js +0 -30
- package/dist/test/helpers/owner.js.map +0 -1
- package/dist/test/integration/events.test.d.ts +0 -1
- package/dist/test/integration/events.test.js +0 -143
- package/dist/test/integration/events.test.js.map +0 -1
- package/dist/test/integration/retention.test.d.ts +0 -1
- package/dist/test/integration/retention.test.js +0 -291
- package/dist/test/integration/retention.test.js.map +0 -1
- package/dist/test/integration/sends.test.d.ts +0 -1
- package/dist/test/integration/sends.test.js +0 -210
- package/dist/test/integration/sends.test.js.map +0 -1
- package/dist/test/integration/settings.test.d.ts +0 -1
- package/dist/test/integration/settings.test.js +0 -137
- package/dist/test/integration/settings.test.js.map +0 -1
- package/dist/test/integration/setup.test.d.ts +0 -1
- package/dist/test/integration/setup.test.js +0 -32
- package/dist/test/integration/setup.test.js.map +0 -1
- package/dist/test/integration/templates.test.d.ts +0 -1
- package/dist/test/integration/templates.test.js +0 -233
- package/dist/test/integration/templates.test.js.map +0 -1
- package/dist/test/integration/tenant-export.test.d.ts +0 -1
- package/dist/test/integration/tenant-export.test.js +0 -85
- package/dist/test/integration/tenant-export.test.js.map +0 -1
- package/dist/test/unit/attention.test.d.ts +0 -1
- package/dist/test/unit/attention.test.js +0 -109
- package/dist/test/unit/attention.test.js.map +0 -1
- package/dist/test/unit/automation.test.d.ts +0 -1
- package/dist/test/unit/automation.test.js +0 -104
- package/dist/test/unit/automation.test.js.map +0 -1
- package/dist/test/unit/categories.test.d.ts +0 -1
- package/dist/test/unit/categories.test.js +0 -38
- package/dist/test/unit/categories.test.js.map +0 -1
- package/dist/test/unit/engine/conditions.test.d.ts +0 -1
- package/dist/test/unit/engine/conditions.test.js +0 -23
- package/dist/test/unit/engine/conditions.test.js.map +0 -1
- package/dist/test/unit/engine/coverage.test.d.ts +0 -1
- package/dist/test/unit/engine/coverage.test.js +0 -106
- package/dist/test/unit/engine/coverage.test.js.map +0 -1
- package/dist/test/unit/engine/fallback-html.test.d.ts +0 -1
- package/dist/test/unit/engine/fallback-html.test.js +0 -78
- package/dist/test/unit/engine/fallback-html.test.js.map +0 -1
- package/dist/test/unit/engine/paths.test.d.ts +0 -1
- package/dist/test/unit/engine/paths.test.js +0 -33
- package/dist/test/unit/engine/paths.test.js.map +0 -1
- package/dist/test/unit/engine/resolve.test.d.ts +0 -1
- package/dist/test/unit/engine/resolve.test.js +0 -157
- package/dist/test/unit/engine/resolve.test.js.map +0 -1
- package/dist/test/unit/engine/tokens.test.d.ts +0 -1
- package/dist/test/unit/engine/tokens.test.js +0 -26
- package/dist/test/unit/engine/tokens.test.js.map +0 -1
- package/dist/test/unit/events.test.d.ts +0 -1
- package/dist/test/unit/events.test.js +0 -13
- package/dist/test/unit/events.test.js.map +0 -1
- package/dist/test/unit/metrics.test.d.ts +0 -1
- package/dist/test/unit/metrics.test.js +0 -108
- package/dist/test/unit/metrics.test.js.map +0 -1
- package/dist/test/unit/notify.test.d.ts +0 -1
- package/dist/test/unit/notify.test.js +0 -49
- package/dist/test/unit/notify.test.js.map +0 -1
- package/dist/test/unit/period.test.d.ts +0 -1
- package/dist/test/unit/period.test.js +0 -61
- package/dist/test/unit/period.test.js.map +0 -1
- package/dist/test/unit/permissions.test.d.ts +0 -1
- package/dist/test/unit/permissions.test.js +0 -27
- package/dist/test/unit/permissions.test.js.map +0 -1
- package/dist/test/unit/provider.test.d.ts +0 -1
- package/dist/test/unit/provider.test.js +0 -47
- package/dist/test/unit/provider.test.js.map +0 -1
- package/dist/test/unit/realtime.test.d.ts +0 -1
- package/dist/test/unit/realtime.test.js +0 -13
- package/dist/test/unit/realtime.test.js.map +0 -1
- package/dist/test/unit/render.test.d.ts +0 -1
- package/dist/test/unit/render.test.js +0 -23
- package/dist/test/unit/render.test.js.map +0 -1
- package/dist/test/unit/retention.test.d.ts +0 -1
- package/dist/test/unit/retention.test.js +0 -66
- package/dist/test/unit/retention.test.js.map +0 -1
- package/dist/test/unit/send-batch.test.d.ts +0 -1
- package/dist/test/unit/send-batch.test.js +0 -46
- package/dist/test/unit/send-batch.test.js.map +0 -1
- package/dist/test/unit/send.test.d.ts +0 -1
- package/dist/test/unit/send.test.js +0 -120
- package/dist/test/unit/send.test.js.map +0 -1
- package/dist/test/unit/smoke.test.d.ts +0 -1
- package/dist/test/unit/smoke.test.js +0 -10
- package/dist/test/unit/smoke.test.js.map +0 -1
- package/dist/test/unit/status.test.d.ts +0 -1
- package/dist/test/unit/status.test.js +0 -16
- package/dist/test/unit/status.test.js.map +0 -1
- package/dist/test/unit/templates.test.d.ts +0 -1
- package/dist/test/unit/templates.test.js +0 -48
- package/dist/test/unit/templates.test.js.map +0 -1
- package/dist/test/unit/tenant-export.test.d.ts +0 -1
- package/dist/test/unit/tenant-export.test.js +0 -59
- package/dist/test/unit/tenant-export.test.js.map +0 -1
- package/dist/test/unit/webhook-secret.test.d.ts +0 -1
- package/dist/test/unit/webhook-secret.test.js +0 -43
- package/dist/test/unit/webhook-secret.test.js.map +0 -1
- package/frontend-vue/app/components/AttentionCard.vue +0 -78
- package/frontend-vue/app/components/HtmlPreviewModal.vue +0 -20
- package/frontend-vue/app/components/ProviderChip.vue +0 -39
- package/frontend-vue/app/components/editor/EditorBlockSettings.vue +0 -186
- package/frontend-vue/app/components/editor/EditorDataPanel.vue +0 -130
- package/frontend-vue/app/components/editor/EditorDraftBar.vue +0 -35
- package/frontend-vue/app/components/editor/EditorRail.vue +0 -58
- package/frontend-vue/app/components/editor/EditorSubjectSettings.vue +0 -37
- package/frontend-vue/app/components/editor/EditorTemplateSettings.vue +0 -87
- package/frontend-vue/app/components/editor/EditorTestSendModal.vue +0 -66
- package/frontend-vue/app/components/editor/EditorVariables.vue +0 -163
- package/frontend-vue/app/composables/useMailingPeriodData.ts +0 -39
- package/frontend-vue/tests/smoke.test.ts +0 -8
- package/src/antelope-modules.ts +0 -48
- package/src/test/antelope.test.ts +0 -190
- package/src/test/helpers/http.ts +0 -23
- package/src/test/helpers/owner.ts +0 -37
- package/src/test/integration/events.test.ts +0 -159
- package/src/test/integration/retention.test.ts +0 -363
- package/src/test/integration/sends.test.ts +0 -233
- package/src/test/integration/settings.test.ts +0 -128
- package/src/test/integration/setup.test.ts +0 -35
- package/src/test/integration/templates.test.ts +0 -272
- package/src/test/integration/tenant-export.test.ts +0 -125
- package/src/test/unit/attention.test.ts +0 -129
- package/src/test/unit/automation.test.ts +0 -130
- package/src/test/unit/categories.test.ts +0 -51
- package/src/test/unit/engine/conditions.test.ts +0 -35
- package/src/test/unit/engine/coverage.test.ts +0 -118
- package/src/test/unit/engine/fallback-html.test.ts +0 -86
- package/src/test/unit/engine/paths.test.ts +0 -39
- package/src/test/unit/engine/resolve.test.ts +0 -176
- package/src/test/unit/engine/tokens.test.ts +0 -26
- package/src/test/unit/events.test.ts +0 -19
- package/src/test/unit/metrics.test.ts +0 -170
- package/src/test/unit/notify.test.ts +0 -71
- package/src/test/unit/period.test.ts +0 -91
- package/src/test/unit/permissions.test.ts +0 -30
- package/src/test/unit/provider.test.ts +0 -57
- package/src/test/unit/realtime.test.ts +0 -11
- package/src/test/unit/render.test.ts +0 -23
- package/src/test/unit/retention.test.ts +0 -91
- package/src/test/unit/send-batch.test.ts +0 -72
- package/src/test/unit/send.test.ts +0 -161
- package/src/test/unit/smoke.test.ts +0 -8
- package/src/test/unit/status.test.ts +0 -14
- package/src/test/unit/templates.test.ts +0 -52
- package/src/test/unit/tenant-export.test.ts +0 -88
- package/src/test/unit/webhook-secret.test.ts +0 -51
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
Block,
|
|
3
|
+
BlockType,
|
|
4
|
+
FooterBlock,
|
|
5
|
+
IfBlock,
|
|
6
|
+
} from '../../types/mailing'
|
|
2
7
|
import { deepClone } from '../clone'
|
|
3
|
-
import { newBlockId } from './factory'
|
|
8
|
+
import { footerLabelsFor, newBlockId } from './factory'
|
|
4
9
|
|
|
5
10
|
const isIf = (block: Block): block is IfBlock => block.type === 'if'
|
|
6
11
|
|
|
@@ -72,3 +77,110 @@ export function removeBlock(blocks: Block[], id: string): boolean {
|
|
|
72
77
|
export function insertBlock(list: Block[], index: number, block: Block): void {
|
|
73
78
|
list.splice(index, 0, block)
|
|
74
79
|
}
|
|
80
|
+
|
|
81
|
+
/** Every block of the tree, depth first, branches in place. */
|
|
82
|
+
export function flattenBlocks(blocks: Block[]): Block[] {
|
|
83
|
+
return blocks.flatMap((block) => [
|
|
84
|
+
block,
|
|
85
|
+
...branchesOf(block).flatMap(flattenBlocks),
|
|
86
|
+
])
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Where a block sits in the whole tree, 1-based ("block 3 of 12"). */
|
|
90
|
+
export interface BlockPosition {
|
|
91
|
+
index: number
|
|
92
|
+
total: number
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function blockPosition(
|
|
96
|
+
blocks: Block[],
|
|
97
|
+
id: string,
|
|
98
|
+
): BlockPosition | null {
|
|
99
|
+
const flat = flattenBlocks(blocks)
|
|
100
|
+
const index = flat.findIndex((block) => block.id === id)
|
|
101
|
+
return index < 0 ? null : { index: index + 1, total: flat.length }
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** A list of the tree and the index a new block goes to in it. */
|
|
105
|
+
export interface InsertionPoint {
|
|
106
|
+
list: Block[]
|
|
107
|
+
index: number
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** The block type that closes an e-mail: new blocks go above it. */
|
|
111
|
+
const CLOSING_TYPE: BlockType = 'footer'
|
|
112
|
+
|
|
113
|
+
function endOfBody(blocks: Block[], type?: BlockType): number {
|
|
114
|
+
const isClosed = blocks.at(-1)?.type === CLOSING_TYPE
|
|
115
|
+
return isClosed && type !== CLOSING_TYPE ? blocks.length - 1 : blocks.length
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Where a block added "under the selected one" lands: right after it in its
|
|
120
|
+
* own list or, when nothing is selected, at the end of the root list but
|
|
121
|
+
* above the closing footer (a new footer still goes last).
|
|
122
|
+
*/
|
|
123
|
+
export function insertionAfter(
|
|
124
|
+
blocks: Block[],
|
|
125
|
+
id: string | null,
|
|
126
|
+
type?: BlockType,
|
|
127
|
+
): InsertionPoint {
|
|
128
|
+
const list = id ? findParentList(blocks, id) : null
|
|
129
|
+
if (!list) return { list: blocks, index: endOfBody(blocks, type) }
|
|
130
|
+
return { list, index: list.findIndex((block) => block.id === id) + 1 }
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function parentOf(blocks: Block[], list: Block[]): Block | null {
|
|
134
|
+
return (
|
|
135
|
+
flattenBlocks(blocks).find((block) => branchesOf(block).includes(list)) ??
|
|
136
|
+
null
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* The block to select once `id` is gone: the next one in its list, else the
|
|
142
|
+
* previous one, else the condition holding it.
|
|
143
|
+
*/
|
|
144
|
+
export function neighbourOf(blocks: Block[], id: string): string | null {
|
|
145
|
+
const list = findParentList(blocks, id)
|
|
146
|
+
if (!list) return null
|
|
147
|
+
const index = list.findIndex((block) => block.id === id)
|
|
148
|
+
const sibling = list[index + 1] ?? list[index - 1]
|
|
149
|
+
return sibling?.id ?? parentOf(blocks, list)?.id ?? null
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** How many blocks each branch of a condition holds (then, else). */
|
|
153
|
+
export interface BranchCounts {
|
|
154
|
+
then: number
|
|
155
|
+
else: number | null
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function branchCounts(block: IfBlock): BranchCounts {
|
|
159
|
+
return {
|
|
160
|
+
then: block.children.length,
|
|
161
|
+
else: block.elseChildren ? block.elseChildren.length : null,
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const isFooter = (block: Block): block is FooterBlock => block.type === 'footer'
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Rewrites the footer labels a locale copy still has at the source locale's
|
|
169
|
+
* defaults into the target locale's; labels someone typed are kept.
|
|
170
|
+
*/
|
|
171
|
+
export function localizeFooters(
|
|
172
|
+
blocks: Block[],
|
|
173
|
+
from: string,
|
|
174
|
+
to: string,
|
|
175
|
+
): void {
|
|
176
|
+
const source = footerLabelsFor(from)
|
|
177
|
+
const target = footerLabelsFor(to)
|
|
178
|
+
flattenBlocks(blocks)
|
|
179
|
+
.filter(isFooter)
|
|
180
|
+
.forEach((footer) => {
|
|
181
|
+
if (footer.unsubscribeLabel === source.unsubscribe)
|
|
182
|
+
footer.unsubscribeLabel = target.unsubscribe
|
|
183
|
+
if (footer.preferencesLabel === source.preferences)
|
|
184
|
+
footer.preferencesLabel = target.preferences
|
|
185
|
+
})
|
|
186
|
+
}
|
|
@@ -2,26 +2,163 @@ import type { TemplateCategory } from '../types/mailing'
|
|
|
2
2
|
|
|
3
3
|
const ID_SEPARATOR = '-'
|
|
4
4
|
|
|
5
|
+
export function serializeCategories(categories: TemplateCategory[]): string {
|
|
6
|
+
return JSON.stringify(categories)
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** The icons the categories editor offers ("Pick an icon"). */
|
|
10
|
+
export const CATEGORY_ICONS = [
|
|
11
|
+
'i-ph-package',
|
|
12
|
+
'i-ph-lock',
|
|
13
|
+
'i-ph-user-circle',
|
|
14
|
+
'i-ph-key',
|
|
15
|
+
'i-ph-file-text',
|
|
16
|
+
'i-ph-newspaper',
|
|
17
|
+
'i-ph-sparkle',
|
|
18
|
+
'i-ph-textbox',
|
|
19
|
+
'i-ph-truck',
|
|
20
|
+
'i-ph-shopping-cart',
|
|
21
|
+
'i-ph-bell',
|
|
22
|
+
'i-ph-megaphone',
|
|
23
|
+
'i-ph-gift',
|
|
24
|
+
'i-ph-users',
|
|
25
|
+
'i-ph-calendar',
|
|
26
|
+
'i-ph-chat-circle',
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
export const DEFAULT_CATEGORY_ICON = 'i-ph-folder'
|
|
30
|
+
|
|
31
|
+
const ICON_PREFIX = 'i-ph-'
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The icon a new category starts with: the first one of the picker no
|
|
35
|
+
* category uses yet, so a new row never looks like an existing one.
|
|
36
|
+
*/
|
|
37
|
+
export function newCategoryIcon(categories: TemplateCategory[]): string {
|
|
38
|
+
const used = new Set(categories.map((category) => category.icon))
|
|
39
|
+
return CATEGORY_ICONS.find((icon) => !used.has(icon)) ?? DEFAULT_CATEGORY_ICON
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** The i18n key part naming an icon (`megaphone` for `i-ph-megaphone`). */
|
|
43
|
+
export function iconName(icon: string): string {
|
|
44
|
+
return icon.startsWith(ICON_PREFIX)
|
|
45
|
+
? icon.slice(ICON_PREFIX.length).replace(/-/g, '_')
|
|
46
|
+
: icon
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* A row of the categories editor. `isNew` rows were added in this edit: their
|
|
51
|
+
* id follows the label until the form is saved, then stays fixed for good.
|
|
52
|
+
*/
|
|
53
|
+
export interface EditableCategory extends TemplateCategory {
|
|
54
|
+
key: string
|
|
55
|
+
isNew: boolean
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const UNIQUE_SUFFIX_START = 2
|
|
59
|
+
const FALLBACK_ID_BASE = 'category'
|
|
60
|
+
|
|
61
|
+
/** `base`, or `base-2`, `base-3`… the first one `taken` does not hold. */
|
|
62
|
+
export function uniqueId(base: string, taken: Set<string>): string {
|
|
63
|
+
if (!taken.has(base)) return base
|
|
64
|
+
let suffix = UNIQUE_SUFFIX_START
|
|
65
|
+
while (taken.has(`${base}${ID_SEPARATOR}${suffix}`)) suffix++
|
|
66
|
+
return `${base}${ID_SEPARATOR}${suffix}`
|
|
67
|
+
}
|
|
68
|
+
|
|
5
69
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
70
|
+
* The categories an edit stores, one per row in its order: saved rows keep
|
|
71
|
+
* their id, new rows get one derived from their label, unique among the
|
|
72
|
+
* others. A row without a label is sent too, for the server to name it.
|
|
9
73
|
*/
|
|
10
|
-
export function
|
|
11
|
-
|
|
74
|
+
export function toStoredCategories(
|
|
75
|
+
rows: EditableCategory[],
|
|
12
76
|
): TemplateCategory[] {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
return
|
|
20
|
-
|
|
77
|
+
const taken = new Set(rows.filter((row) => !row.isNew).map((row) => row.id))
|
|
78
|
+
return rows.map((row) => {
|
|
79
|
+
const id = row.isNew
|
|
80
|
+
? uniqueId(slugifyId(row.label) || FALLBACK_ID_BASE, taken)
|
|
81
|
+
: row.id
|
|
82
|
+
taken.add(id)
|
|
83
|
+
return {
|
|
84
|
+
id,
|
|
85
|
+
label: row.label.trim(),
|
|
86
|
+
icon: row.icon || DEFAULT_CATEGORY_ICON,
|
|
87
|
+
}
|
|
88
|
+
})
|
|
21
89
|
}
|
|
22
90
|
|
|
23
|
-
|
|
24
|
-
|
|
91
|
+
/**
|
|
92
|
+
* A category as the editor hands it to the form: a kept one, or one removed
|
|
93
|
+
* whose templates move to `moveTo` once the form is saved.
|
|
94
|
+
*/
|
|
95
|
+
export interface CategoryEntry extends TemplateCategory {
|
|
96
|
+
moveTo?: string
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** The kept categories of a list, without the removed ones it moves away. */
|
|
100
|
+
export function keptCategories(entries: CategoryEntry[]): TemplateCategory[] {
|
|
101
|
+
return entries.filter((entry) => entry.moveTo === undefined)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Where the templates of each removed category go, by its id. */
|
|
105
|
+
export function categoryMoves(entries: CategoryEntry[]): Map<string, string> {
|
|
106
|
+
return new Map(
|
|
107
|
+
entries.flatMap((entry) =>
|
|
108
|
+
entry.moveTo === undefined ? [] : [[entry.id, entry.moveTo] as const],
|
|
109
|
+
),
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* What the editor hands the form: the kept categories, then each removed one
|
|
115
|
+
* whose templates move elsewhere, still under its own id.
|
|
116
|
+
*/
|
|
117
|
+
export function withMoves(
|
|
118
|
+
categories: TemplateCategory[],
|
|
119
|
+
removed: CategoryEntry[],
|
|
120
|
+
): CategoryEntry[] {
|
|
121
|
+
return [...categories, ...removed.filter((entry) => entry.moveTo)]
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* The moves of `removed` once `id` is removed too and its templates go to
|
|
126
|
+
* `target`: a move into `id` follows it to `target`, or is dropped when
|
|
127
|
+
* `target` is none (those templates end uncategorised either way).
|
|
128
|
+
*/
|
|
129
|
+
export function redirectMoves(
|
|
130
|
+
removed: CategoryEntry[],
|
|
131
|
+
id: string,
|
|
132
|
+
target: string,
|
|
133
|
+
): CategoryEntry[] {
|
|
134
|
+
return removed
|
|
135
|
+
.map((entry) =>
|
|
136
|
+
entry.moveTo === id ? { ...entry, moveTo: target } : entry,
|
|
137
|
+
)
|
|
138
|
+
.filter((entry) => entry.moveTo)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Rows after a move, and where the moved row now sits. */
|
|
142
|
+
export interface MovedRows<T> {
|
|
143
|
+
rows: T[]
|
|
144
|
+
index: number
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* `rows` with the row at `index` moved by `offset` (-1 up, 1 down), and its
|
|
149
|
+
* new index; unchanged at either end.
|
|
150
|
+
*/
|
|
151
|
+
export function moveRow<T>(
|
|
152
|
+
rows: T[],
|
|
153
|
+
index: number,
|
|
154
|
+
offset: number,
|
|
155
|
+
): MovedRows<T> {
|
|
156
|
+
const target = index + offset
|
|
157
|
+
if (target < 0 || target >= rows.length) return { rows, index }
|
|
158
|
+
const moved = [...rows]
|
|
159
|
+
const [row] = moved.splice(index, 1)
|
|
160
|
+
moved.splice(target, 0, row as T)
|
|
161
|
+
return { rows: moved, index: target }
|
|
25
162
|
}
|
|
26
163
|
|
|
27
164
|
/**
|
|
@@ -52,3 +189,52 @@ export function toCategoryOption(value: string | null | undefined): string {
|
|
|
52
189
|
export function fromCategoryOption(value: string): string {
|
|
53
190
|
return value === NO_CATEGORY ? '' : value
|
|
54
191
|
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* The category an edit stores. "No category" is sent as `null` rather than
|
|
195
|
+
* omitted: from interface-data-api 0.2.0 on, the edit route leaves an absent
|
|
196
|
+
* key unchanged, so only an explicit `null` clears it.
|
|
197
|
+
*/
|
|
198
|
+
export function toStoredCategory(
|
|
199
|
+
value: string | null | undefined,
|
|
200
|
+
): string | null {
|
|
201
|
+
return value || null
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** Answers the text of an i18n key, or nothing when the catalog lacks it. */
|
|
205
|
+
export type OptionalTranslate = (key: string) => string | undefined
|
|
206
|
+
|
|
207
|
+
/** The labels the backend's `DEFAULT_CATEGORIES` (`src/types/settings.ts`) ship with. */
|
|
208
|
+
const DEFAULT_CATEGORY_LABELS: Record<string, string> = {
|
|
209
|
+
orders: 'Orders',
|
|
210
|
+
account: 'Account & access',
|
|
211
|
+
billing: 'Billing',
|
|
212
|
+
marketing: 'Marketing',
|
|
213
|
+
forms: 'Forms',
|
|
214
|
+
}
|
|
215
|
+
const DEFAULT_CATEGORY_KEY_PREFIX = 'dms_mailing.categories.defaults'
|
|
216
|
+
|
|
217
|
+
/** The i18n key of a shipped category nobody renamed; absent otherwise. */
|
|
218
|
+
export function defaultCategoryLabelKey(
|
|
219
|
+
category: TemplateCategory,
|
|
220
|
+
): string | undefined {
|
|
221
|
+
return DEFAULT_CATEGORY_LABELS[category.id] === category.label
|
|
222
|
+
? `${DEFAULT_CATEGORY_KEY_PREFIX}.${category.id}`
|
|
223
|
+
: undefined
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* The categories as the interface shows them: a shipped one still under its
|
|
228
|
+
* original English label reads in the interface's language, a label someone
|
|
229
|
+
* wrote stays as written. Nothing stored changes.
|
|
230
|
+
*/
|
|
231
|
+
export function localizeCategories(
|
|
232
|
+
categories: TemplateCategory[],
|
|
233
|
+
translate: OptionalTranslate,
|
|
234
|
+
): TemplateCategory[] {
|
|
235
|
+
return categories.map((category) => {
|
|
236
|
+
const key = defaultCategoryLabelKey(category)
|
|
237
|
+
const label = key ? translate(key) : undefined
|
|
238
|
+
return label ? { ...category, label } : category
|
|
239
|
+
})
|
|
240
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Block,
|
|
3
|
+
TemplateChange,
|
|
4
|
+
TemplateChangeKind,
|
|
5
|
+
TemplateContent,
|
|
6
|
+
} from '../types/mailing'
|
|
7
|
+
import { blockLabelKey } from './blocks/factory'
|
|
8
|
+
import { findBlock } from './blocks/tree'
|
|
9
|
+
|
|
10
|
+
/** One line of the publish review: "EN · Paragraph changed". */
|
|
11
|
+
export interface ChangeLine {
|
|
12
|
+
key: string
|
|
13
|
+
icon: string
|
|
14
|
+
text: string
|
|
15
|
+
/** The text customers receive today, when the change rewrote one. */
|
|
16
|
+
before?: string
|
|
17
|
+
/** The draft's text in its place. */
|
|
18
|
+
after?: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type ChangeTranslator = (
|
|
22
|
+
key: string,
|
|
23
|
+
params?: Record<string, string>,
|
|
24
|
+
) => string
|
|
25
|
+
|
|
26
|
+
/** Contents a change can name its block from: the draft, then the live one. */
|
|
27
|
+
export interface ChangeSources {
|
|
28
|
+
draft: TemplateContent
|
|
29
|
+
published: TemplateContent | null
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const CHANGE_KEY_PREFIX = 'dms_mailing.editor.changes'
|
|
33
|
+
const LOCALE_SEPARATOR = ' · '
|
|
34
|
+
const DETAIL_SEPARATOR = ' '
|
|
35
|
+
|
|
36
|
+
const CHANGE_ICONS: Record<TemplateChangeKind, string> = {
|
|
37
|
+
locale_added: 'i-ph-plus-circle',
|
|
38
|
+
locale_removed: 'i-ph-minus-circle',
|
|
39
|
+
subject: 'i-ph-pencil-simple',
|
|
40
|
+
preheader: 'i-ph-pencil-simple',
|
|
41
|
+
block_added: 'i-ph-plus-circle',
|
|
42
|
+
block_removed: 'i-ph-minus-circle',
|
|
43
|
+
block_changed: 'i-ph-pencil-simple',
|
|
44
|
+
blocks_reordered: 'i-ph-arrows-down-up',
|
|
45
|
+
variable_added: 'i-ph-brackets-curly',
|
|
46
|
+
variable_removed: 'i-ph-brackets-curly',
|
|
47
|
+
variable_changed: 'i-ph-brackets-curly',
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function blockIn(
|
|
51
|
+
content: TemplateContent | null,
|
|
52
|
+
change: TemplateChange,
|
|
53
|
+
): Block | null {
|
|
54
|
+
const blocks = content?.locales[change.locale]?.blocks
|
|
55
|
+
return blocks && change.blockId ? findBlock(blocks, change.blockId) : null
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function blockOf(change: TemplateChange, sources: ChangeSources): Block | null {
|
|
59
|
+
return blockIn(sources.draft, change) ?? blockIn(sources.published, change)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** The fields a reader recognises a block by, in the order they are tried. */
|
|
63
|
+
const TEXT_FIELDS = ['text', 'label', 'alt'] as const
|
|
64
|
+
|
|
65
|
+
function textOf(block: Block | null): string | undefined {
|
|
66
|
+
if (!block) return undefined
|
|
67
|
+
const fields = block as unknown as Record<string, unknown>
|
|
68
|
+
const field = TEXT_FIELDS.find((name) => typeof fields[name] === 'string')
|
|
69
|
+
return field ? (fields[field] as string) : undefined
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
type EnvelopeKind = Extract<TemplateChangeKind, 'subject' | 'preheader'>
|
|
73
|
+
|
|
74
|
+
const ENVELOPE_KINDS: TemplateChangeKind[] = ['subject', 'preheader']
|
|
75
|
+
|
|
76
|
+
function textsOf(
|
|
77
|
+
change: TemplateChange,
|
|
78
|
+
sources: ChangeSources,
|
|
79
|
+
): Pick<ChangeLine, 'before' | 'after'> {
|
|
80
|
+
if (ENVELOPE_KINDS.includes(change.kind)) {
|
|
81
|
+
const field = change.kind as EnvelopeKind
|
|
82
|
+
return {
|
|
83
|
+
before: sources.published?.locales[change.locale]?.[field],
|
|
84
|
+
after: sources.draft.locales[change.locale]?.[field],
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (change.kind !== 'block_changed') return {}
|
|
88
|
+
return {
|
|
89
|
+
before: textOf(blockIn(sources.published, change)),
|
|
90
|
+
after: textOf(blockIn(sources.draft, change)),
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Before / after texts worth showing: both known and actually different. */
|
|
95
|
+
function rewriteOf(
|
|
96
|
+
change: TemplateChange,
|
|
97
|
+
sources: ChangeSources,
|
|
98
|
+
): Pick<ChangeLine, 'before' | 'after'> {
|
|
99
|
+
const { before, after } = textsOf(change, sources)
|
|
100
|
+
if (before === undefined || after === undefined || before === after) return {}
|
|
101
|
+
return { before, after }
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function blockName(
|
|
105
|
+
change: TemplateChange,
|
|
106
|
+
sources: ChangeSources,
|
|
107
|
+
t: ChangeTranslator,
|
|
108
|
+
): string {
|
|
109
|
+
if (!change.blockType) return ''
|
|
110
|
+
const label = t(blockLabelKey(change.blockType))
|
|
111
|
+
const block = blockOf(change, sources)
|
|
112
|
+
return block?.type === 'if'
|
|
113
|
+
? `${label}${DETAIL_SEPARATOR}${block.condition.path}`
|
|
114
|
+
: label
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** "EN · " for a change of the content; nothing for a declaration's. */
|
|
118
|
+
function scopeOf(change: TemplateChange): string {
|
|
119
|
+
return change.locale
|
|
120
|
+
? `${change.locale.toUpperCase()}${LOCALE_SEPARATOR}`
|
|
121
|
+
: ''
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Contents to name blocks from when only the change list is known. */
|
|
125
|
+
export const NO_SOURCES: ChangeSources = {
|
|
126
|
+
draft: { locales: {} },
|
|
127
|
+
published: null,
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* The publish review's reading of the server's change list, one line per
|
|
132
|
+
* change, prefixed with its locale (a variable declaration has none).
|
|
133
|
+
*/
|
|
134
|
+
export function describeChanges(
|
|
135
|
+
changes: TemplateChange[],
|
|
136
|
+
sources: ChangeSources,
|
|
137
|
+
t: ChangeTranslator,
|
|
138
|
+
): ChangeLine[] {
|
|
139
|
+
return changes.map((change, index) => {
|
|
140
|
+
const sentence = t(`${CHANGE_KEY_PREFIX}.${change.kind}`, {
|
|
141
|
+
block: blockName(change, sources, t),
|
|
142
|
+
variable: change.variable ?? '',
|
|
143
|
+
})
|
|
144
|
+
const subject = change.blockId ?? change.variable ?? index
|
|
145
|
+
return {
|
|
146
|
+
key: `${change.locale}-${change.kind}-${subject}`,
|
|
147
|
+
icon: CHANGE_ICONS[change.kind],
|
|
148
|
+
text: `${scopeOf(change)}${sentence}`,
|
|
149
|
+
...rewriteOf(change, sources),
|
|
150
|
+
}
|
|
151
|
+
})
|
|
152
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Condition, ConditionOperator } from '../types/mailing'
|
|
2
|
+
import { readPath } from './paths'
|
|
2
3
|
|
|
3
4
|
export const OPERATORS: ConditionOperator[] = [
|
|
4
5
|
'truthy',
|
|
@@ -18,25 +19,6 @@ export const OPERATORS_WITH_VALUE: ConditionOperator[] = [
|
|
|
18
19
|
const OPERATOR_LABEL_PREFIX = 'dms_mailing.editor.operators'
|
|
19
20
|
const EMPTY_VALUE_PLACEHOLDER = '…'
|
|
20
21
|
|
|
21
|
-
const quote = (value: string): string =>
|
|
22
|
-
value !== '' && !Number.isNaN(Number(value)) ? value : `"${value}"`
|
|
23
|
-
|
|
24
|
-
type ConditionCompiler = (condition: Condition) => string
|
|
25
|
-
|
|
26
|
-
const COMPILERS: Record<ConditionOperator, ConditionCompiler> = {
|
|
27
|
-
truthy: (condition) => `{{#if ${condition.path}}}`,
|
|
28
|
-
falsy: (condition) => `{{#unless ${condition.path}}}`,
|
|
29
|
-
eq: (condition) => `{{#if (eq ${condition.path} ${quote(condition.value)})}}`,
|
|
30
|
-
ne: (condition) =>
|
|
31
|
-
`{{#unless (eq ${condition.path} ${quote(condition.value)})}}`,
|
|
32
|
-
gt: (condition) => `{{#if (gt ${condition.path} ${quote(condition.value)})}}`,
|
|
33
|
-
lt: (condition) => `{{#if (lt ${condition.path} ${quote(condition.value)})}}`,
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function compileCondition(condition: Condition): string {
|
|
37
|
-
return COMPILERS[condition.operator](condition)
|
|
38
|
-
}
|
|
39
|
-
|
|
40
22
|
export type OperatorTranslator = (key: string) => string
|
|
41
23
|
|
|
42
24
|
export function describeCondition(
|
|
@@ -48,3 +30,27 @@ export function describeCondition(
|
|
|
48
30
|
return `${condition.path} ${label}`
|
|
49
31
|
return `${condition.path} ${label} ${condition.value || EMPTY_VALUE_PLACEHOLDER}`
|
|
50
32
|
}
|
|
33
|
+
|
|
34
|
+
type Evaluator = (actual: unknown, expected: string) => boolean
|
|
35
|
+
|
|
36
|
+
const EVALUATORS: Record<ConditionOperator, Evaluator> = {
|
|
37
|
+
truthy: (actual) => Boolean(actual),
|
|
38
|
+
falsy: (actual) => !actual,
|
|
39
|
+
eq: (actual, expected) => String(actual) === expected,
|
|
40
|
+
ne: (actual, expected) => String(actual) !== expected,
|
|
41
|
+
gt: (actual, expected) => Number(actual) > Number(expected),
|
|
42
|
+
lt: (actual, expected) => Number(actual) < Number(expected),
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Evaluates a condition against a data set exactly like the backend engine
|
|
47
|
+
* (`src/engine/conditions.ts`), so the editor's Data mode shows the branch a
|
|
48
|
+
* real send takes.
|
|
49
|
+
*/
|
|
50
|
+
export function evaluateCondition(
|
|
51
|
+
condition: Condition,
|
|
52
|
+
data: Record<string, unknown>,
|
|
53
|
+
): boolean {
|
|
54
|
+
const evaluate = EVALUATORS[condition.operator]
|
|
55
|
+
return evaluate(readPath(data, condition.path), condition.value)
|
|
56
|
+
}
|