@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.
- package/LICENSE +190 -0
- package/README.md +162 -0
- package/dist/antelope-modules.d.ts +32 -0
- package/dist/antelope-modules.js +36 -0
- package/dist/antelope-modules.js.map +1 -0
- package/dist/automation/email-event-trigger.d.ts +16 -0
- package/dist/automation/email-event-trigger.js +70 -0
- package/dist/automation/email-event-trigger.js.map +1 -0
- package/dist/automation/index.d.ts +4 -0
- package/dist/automation/index.js +39 -0
- package/dist/automation/index.js.map +1 -0
- package/dist/automation/send-template-action.d.ts +13 -0
- package/dist/automation/send-template-action.js +71 -0
- package/dist/automation/send-template-action.js.map +1 -0
- package/dist/config.d.ts +5 -0
- package/dist/config.js +13 -0
- package/dist/config.js.map +1 -0
- package/dist/constants.d.ts +28 -0
- package/dist/constants.js +32 -0
- package/dist/constants.js.map +1 -0
- package/dist/crons/index.d.ts +1 -0
- package/dist/crons/index.js +18 -0
- package/dist/crons/index.js.map +1 -0
- package/dist/crons/retention.d.ts +18 -0
- package/dist/crons/retention.js +107 -0
- package/dist/crons/retention.js.map +1 -0
- package/dist/data/body.d.ts +1 -0
- package/dist/data/body.js +18 -0
- package/dist/data/body.js.map +1 -0
- package/dist/data/index.d.ts +3 -0
- package/dist/data/index.js +20 -0
- package/dist/data/index.js.map +1 -0
- package/dist/data/sends-table.d.ts +28 -0
- package/dist/data/sends-table.js +161 -0
- package/dist/data/sends-table.js.map +1 -0
- package/dist/data/templates-table.d.ts +31 -0
- package/dist/data/templates-table.js +173 -0
- package/dist/data/templates-table.js.map +1 -0
- package/dist/data-types/categories-type.d.ts +35 -0
- package/dist/data-types/categories-type.js +37 -0
- package/dist/data-types/categories-type.js.map +1 -0
- package/dist/data-types/category-type.d.ts +8 -0
- package/dist/data-types/category-type.js +43 -0
- package/dist/data-types/category-type.js.map +1 -0
- package/dist/data-types/index.d.ts +2 -0
- package/dist/data-types/index.js +19 -0
- package/dist/data-types/index.js.map +1 -0
- package/dist/db/index.d.ts +2 -0
- package/dist/db/index.js +19 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/models/index.d.ts +4 -0
- package/dist/db/models/index.js +21 -0
- package/dist/db/models/index.js.map +1 -0
- package/dist/db/models/send-events.model.d.ts +27 -0
- package/dist/db/models/send-events.model.js +35 -0
- package/dist/db/models/send-events.model.js.map +1 -0
- package/dist/db/models/sends.model.d.ts +32 -0
- package/dist/db/models/sends.model.js +115 -0
- package/dist/db/models/sends.model.js.map +1 -0
- package/dist/db/models/settings.model.d.ts +23 -0
- package/dist/db/models/settings.model.js +13 -0
- package/dist/db/models/settings.model.js.map +1 -0
- package/dist/db/models/templates.model.d.ts +23 -0
- package/dist/db/models/templates.model.js +13 -0
- package/dist/db/models/templates.model.js.map +1 -0
- package/dist/db/tables/index.d.ts +4 -0
- package/dist/db/tables/index.js +21 -0
- package/dist/db/tables/index.js.map +1 -0
- package/dist/db/tables/send-events.table.d.ts +10 -0
- package/dist/db/tables/send-events.table.js +44 -0
- package/dist/db/tables/send-events.table.js.map +1 -0
- package/dist/db/tables/sends.table.d.ts +26 -0
- package/dist/db/tables/sends.table.js +113 -0
- package/dist/db/tables/sends.table.js.map +1 -0
- package/dist/db/tables/settings.table.d.ts +15 -0
- package/dist/db/tables/settings.table.js +68 -0
- package/dist/db/tables/settings.table.js.map +1 -0
- package/dist/db/tables/templates.table.d.ts +18 -0
- package/dist/db/tables/templates.table.js +81 -0
- package/dist/db/tables/templates.table.js.map +1 -0
- package/dist/engine/conditions.d.ts +2 -0
- package/dist/engine/conditions.js +20 -0
- package/dist/engine/conditions.js.map +1 -0
- package/dist/engine/coverage.d.ts +4 -0
- package/dist/engine/coverage.js +45 -0
- package/dist/engine/coverage.js.map +1 -0
- package/dist/engine/fallback-html.d.ts +4 -0
- package/dist/engine/fallback-html.js +84 -0
- package/dist/engine/fallback-html.js.map +1 -0
- package/dist/engine/index.d.ts +7 -0
- package/dist/engine/index.js +24 -0
- package/dist/engine/index.js.map +1 -0
- package/dist/engine/paths.d.ts +3 -0
- package/dist/engine/paths.js +32 -0
- package/dist/engine/paths.js.map +1 -0
- package/dist/engine/resolve.d.ts +4 -0
- package/dist/engine/resolve.js +139 -0
- package/dist/engine/resolve.js.map +1 -0
- package/dist/engine/tokens.d.ts +7 -0
- package/dist/engine/tokens.js +29 -0
- package/dist/engine/tokens.js.map +1 -0
- package/dist/engine/walk.d.ts +3 -0
- package/dist/engine/walk.js +14 -0
- package/dist/engine/walk.js.map +1 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.js +18 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/tenant-export.d.ts +14 -0
- package/dist/hooks/tenant-export.js +84 -0
- package/dist/hooks/tenant-export.js.map +1 -0
- package/dist/implementations/dms-mailing/index.d.ts +2 -0
- package/dist/implementations/dms-mailing/index.js +8 -0
- package/dist/implementations/dms-mailing/index.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +95 -0
- package/dist/index.js.map +1 -0
- package/dist/pages/editor.d.ts +9 -0
- package/dist/pages/editor.js +30 -0
- package/dist/pages/editor.js.map +1 -0
- package/dist/pages/index.d.ts +7 -0
- package/dist/pages/index.js +24 -0
- package/dist/pages/index.js.map +1 -0
- package/dist/pages/module.d.ts +1 -0
- package/dist/pages/module.js +12 -0
- package/dist/pages/module.js.map +1 -0
- package/dist/pages/overview.d.ts +14 -0
- package/dist/pages/overview.js +92 -0
- package/dist/pages/overview.js.map +1 -0
- package/dist/pages/sends.d.ts +13 -0
- package/dist/pages/sends.js +125 -0
- package/dist/pages/sends.js.map +1 -0
- package/dist/pages/settings/form.d.ts +1 -0
- package/dist/pages/settings/form.js +68 -0
- package/dist/pages/settings/form.js.map +1 -0
- package/dist/pages/settings/index.d.ts +2 -0
- package/dist/pages/settings/index.js +19 -0
- package/dist/pages/settings/index.js.map +1 -0
- package/dist/pages/settings/page.d.ts +10 -0
- package/dist/pages/settings/page.js +35 -0
- package/dist/pages/settings/page.js.map +1 -0
- package/dist/pages/templates.d.ts +10 -0
- package/dist/pages/templates.js +140 -0
- package/dist/pages/templates.js.map +1 -0
- package/dist/permissions.d.ts +1 -0
- package/dist/permissions.js +37 -0
- package/dist/permissions.js.map +1 -0
- package/dist/quick-actions.d.ts +2 -0
- package/dist/quick-actions.js +18 -0
- package/dist/quick-actions.js.map +1 -0
- package/dist/realtime.d.ts +4 -0
- package/dist/realtime.js +44 -0
- package/dist/realtime.js.map +1 -0
- package/dist/routes/events.d.ts +9 -0
- package/dist/routes/events.js +63 -0
- package/dist/routes/events.js.map +1 -0
- package/dist/routes/index.d.ts +7 -0
- package/dist/routes/index.js +24 -0
- package/dist/routes/index.js.map +1 -0
- package/dist/routes/metrics.d.ts +54 -0
- package/dist/routes/metrics.js +235 -0
- package/dist/routes/metrics.js.map +1 -0
- package/dist/routes/period.d.ts +5 -0
- package/dist/routes/period.js +36 -0
- package/dist/routes/period.js.map +1 -0
- package/dist/routes/provider.d.ts +13 -0
- package/dist/routes/provider.js +41 -0
- package/dist/routes/provider.js.map +1 -0
- package/dist/routes/sends.d.ts +31 -0
- package/dist/routes/sends.js +178 -0
- package/dist/routes/sends.js.map +1 -0
- package/dist/routes/settings.d.ts +14 -0
- package/dist/routes/settings.js +72 -0
- package/dist/routes/settings.js.map +1 -0
- package/dist/routes/templates.d.ts +61 -0
- package/dist/routes/templates.js +230 -0
- package/dist/routes/templates.js.map +1 -0
- package/dist/services/attention.d.ts +23 -0
- package/dist/services/attention.js +104 -0
- package/dist/services/attention.js.map +1 -0
- package/dist/services/categories.d.ts +34 -0
- package/dist/services/categories.js +34 -0
- package/dist/services/categories.js.map +1 -0
- package/dist/services/events.d.ts +10 -0
- package/dist/services/events.js +89 -0
- package/dist/services/events.js.map +1 -0
- package/dist/services/metrics.d.ts +34 -0
- package/dist/services/metrics.js +103 -0
- package/dist/services/metrics.js.map +1 -0
- package/dist/services/notify.d.ts +20 -0
- package/dist/services/notify.js +63 -0
- package/dist/services/notify.js.map +1 -0
- package/dist/services/permissions.d.ts +2 -0
- package/dist/services/permissions.js +15 -0
- package/dist/services/permissions.js.map +1 -0
- package/dist/services/provider.d.ts +6 -0
- package/dist/services/provider.js +25 -0
- package/dist/services/provider.js.map +1 -0
- package/dist/services/render.d.ts +6 -0
- package/dist/services/render.js +21 -0
- package/dist/services/render.js.map +1 -0
- package/dist/services/send.d.ts +49 -0
- package/dist/services/send.js +259 -0
- package/dist/services/send.js.map +1 -0
- package/dist/services/settings.d.ts +4 -0
- package/dist/services/settings.js +61 -0
- package/dist/services/settings.js.map +1 -0
- package/dist/services/status.d.ts +3 -0
- package/dist/services/status.js +16 -0
- package/dist/services/status.js.map +1 -0
- package/dist/services/templates.d.ts +17 -0
- package/dist/services/templates.js +92 -0
- package/dist/services/templates.js.map +1 -0
- package/dist/test/antelope.test.d.ts +2 -0
- package/dist/test/antelope.test.js +179 -0
- package/dist/test/antelope.test.js.map +1 -0
- package/dist/test/helpers/http.d.ts +4 -0
- package/dist/test/helpers/http.js +28 -0
- package/dist/test/helpers/http.js.map +1 -0
- package/dist/test/helpers/owner.d.ts +5 -0
- package/dist/test/helpers/owner.js +30 -0
- package/dist/test/helpers/owner.js.map +1 -0
- package/dist/test/integration/events.test.d.ts +1 -0
- package/dist/test/integration/events.test.js +132 -0
- package/dist/test/integration/events.test.js.map +1 -0
- package/dist/test/integration/retention.test.d.ts +1 -0
- package/dist/test/integration/retention.test.js +291 -0
- package/dist/test/integration/retention.test.js.map +1 -0
- package/dist/test/integration/sends.test.d.ts +1 -0
- package/dist/test/integration/sends.test.js +157 -0
- package/dist/test/integration/sends.test.js.map +1 -0
- package/dist/test/integration/settings.test.d.ts +1 -0
- package/dist/test/integration/settings.test.js +45 -0
- package/dist/test/integration/settings.test.js.map +1 -0
- package/dist/test/integration/setup.test.d.ts +1 -0
- package/dist/test/integration/setup.test.js +32 -0
- package/dist/test/integration/setup.test.js.map +1 -0
- package/dist/test/integration/templates.test.d.ts +1 -0
- package/dist/test/integration/templates.test.js +167 -0
- package/dist/test/integration/templates.test.js.map +1 -0
- package/dist/test/integration/tenant-export.test.d.ts +1 -0
- package/dist/test/integration/tenant-export.test.js +85 -0
- package/dist/test/integration/tenant-export.test.js.map +1 -0
- package/dist/test/unit/attention.test.d.ts +1 -0
- package/dist/test/unit/attention.test.js +109 -0
- package/dist/test/unit/attention.test.js.map +1 -0
- package/dist/test/unit/automation.test.d.ts +1 -0
- package/dist/test/unit/automation.test.js +104 -0
- package/dist/test/unit/automation.test.js.map +1 -0
- package/dist/test/unit/categories.test.d.ts +1 -0
- package/dist/test/unit/categories.test.js +38 -0
- package/dist/test/unit/categories.test.js.map +1 -0
- package/dist/test/unit/engine/conditions.test.d.ts +1 -0
- package/dist/test/unit/engine/conditions.test.js +23 -0
- package/dist/test/unit/engine/conditions.test.js.map +1 -0
- package/dist/test/unit/engine/coverage.test.d.ts +1 -0
- package/dist/test/unit/engine/coverage.test.js +106 -0
- package/dist/test/unit/engine/coverage.test.js.map +1 -0
- package/dist/test/unit/engine/fallback-html.test.d.ts +1 -0
- package/dist/test/unit/engine/fallback-html.test.js +78 -0
- package/dist/test/unit/engine/fallback-html.test.js.map +1 -0
- package/dist/test/unit/engine/paths.test.d.ts +1 -0
- package/dist/test/unit/engine/paths.test.js +33 -0
- package/dist/test/unit/engine/paths.test.js.map +1 -0
- package/dist/test/unit/engine/resolve.test.d.ts +1 -0
- package/dist/test/unit/engine/resolve.test.js +157 -0
- package/dist/test/unit/engine/resolve.test.js.map +1 -0
- package/dist/test/unit/engine/tokens.test.d.ts +1 -0
- package/dist/test/unit/engine/tokens.test.js +26 -0
- package/dist/test/unit/engine/tokens.test.js.map +1 -0
- package/dist/test/unit/events.test.d.ts +1 -0
- package/dist/test/unit/events.test.js +13 -0
- package/dist/test/unit/events.test.js.map +1 -0
- package/dist/test/unit/metrics.test.d.ts +1 -0
- package/dist/test/unit/metrics.test.js +108 -0
- package/dist/test/unit/metrics.test.js.map +1 -0
- package/dist/test/unit/notify.test.d.ts +1 -0
- package/dist/test/unit/notify.test.js +49 -0
- package/dist/test/unit/notify.test.js.map +1 -0
- package/dist/test/unit/period.test.d.ts +1 -0
- package/dist/test/unit/period.test.js +44 -0
- package/dist/test/unit/period.test.js.map +1 -0
- package/dist/test/unit/provider.test.d.ts +1 -0
- package/dist/test/unit/provider.test.js +47 -0
- package/dist/test/unit/provider.test.js.map +1 -0
- package/dist/test/unit/realtime.test.d.ts +1 -0
- package/dist/test/unit/realtime.test.js +13 -0
- package/dist/test/unit/realtime.test.js.map +1 -0
- package/dist/test/unit/render.test.d.ts +1 -0
- package/dist/test/unit/render.test.js +23 -0
- package/dist/test/unit/render.test.js.map +1 -0
- package/dist/test/unit/retention.test.d.ts +1 -0
- package/dist/test/unit/retention.test.js +66 -0
- package/dist/test/unit/retention.test.js.map +1 -0
- package/dist/test/unit/send-batch.test.d.ts +1 -0
- package/dist/test/unit/send-batch.test.js +46 -0
- package/dist/test/unit/send-batch.test.js.map +1 -0
- package/dist/test/unit/send.test.d.ts +1 -0
- package/dist/test/unit/send.test.js +120 -0
- package/dist/test/unit/send.test.js.map +1 -0
- package/dist/test/unit/smoke.test.d.ts +1 -0
- package/dist/test/unit/smoke.test.js +10 -0
- package/dist/test/unit/smoke.test.js.map +1 -0
- package/dist/test/unit/status.test.d.ts +1 -0
- package/dist/test/unit/status.test.js +16 -0
- package/dist/test/unit/status.test.js.map +1 -0
- package/dist/test/unit/templates.test.d.ts +1 -0
- package/dist/test/unit/templates.test.js +48 -0
- package/dist/test/unit/templates.test.js.map +1 -0
- package/dist/test/unit/tenant-export.test.d.ts +1 -0
- package/dist/test/unit/tenant-export.test.js +59 -0
- package/dist/test/unit/tenant-export.test.js.map +1 -0
- package/dist/test/unit/webhook-secret.test.d.ts +1 -0
- package/dist/test/unit/webhook-secret.test.js +42 -0
- package/dist/test/unit/webhook-secret.test.js.map +1 -0
- package/dist/types/blocks.d.ts +2 -0
- package/dist/types/blocks.js +3 -0
- package/dist/types/blocks.js.map +1 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.js +21 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/resolved.d.ts +68 -0
- package/dist/types/resolved.js +3 -0
- package/dist/types/resolved.js.map +1 -0
- package/dist/types/sends.d.ts +14 -0
- package/dist/types/sends.js +19 -0
- package/dist/types/sends.js.map +1 -0
- package/dist/types/settings.d.ts +16 -0
- package/dist/types/settings.js +11 -0
- package/dist/types/settings.js.map +1 -0
- package/dist/validation/settings.schema.d.ts +63 -0
- package/dist/validation/settings.schema.js +29 -0
- package/dist/validation/settings.schema.js.map +1 -0
- package/dist/validation/templates.schema.d.ts +248 -0
- package/dist/validation/templates.schema.js +126 -0
- package/dist/validation/templates.schema.js.map +1 -0
- package/frontend-vue/.editorconfig +12 -0
- package/frontend-vue/.prettierrc +11 -0
- package/frontend-vue/app/components/AttentionCard.vue +78 -0
- package/frontend-vue/app/components/CategoriesInput.vue +126 -0
- package/frontend-vue/app/components/CategoryInput.vue +67 -0
- package/frontend-vue/app/components/Editor.vue +63 -0
- package/frontend-vue/app/components/FunnelCard.vue +66 -0
- package/frontend-vue/app/components/HtmlPreviewModal.vue +20 -0
- package/frontend-vue/app/components/NewTemplateModal.vue +158 -0
- package/frontend-vue/app/components/ProviderChip.vue +39 -0
- package/frontend-vue/app/components/SendDrawer.vue +298 -0
- package/frontend-vue/app/components/TemplateCard.vue +127 -0
- package/frontend-vue/app/components/TemplateDrawer.vue +394 -0
- package/frontend-vue/app/components/TemplateGallery.vue +112 -0
- package/frontend-vue/app/components/TemplatePreviewFrame.vue +103 -0
- package/frontend-vue/app/components/TestSendModal.vue +162 -0
- package/frontend-vue/app/components/TokenText.vue +26 -0
- package/frontend-vue/app/components/blocks/Button.vue +19 -0
- package/frontend-vue/app/components/blocks/Code.vue +17 -0
- package/frontend-vue/app/components/blocks/Divider.vue +3 -0
- package/frontend-vue/app/components/blocks/Footer.vue +22 -0
- package/frontend-vue/app/components/blocks/Heading.vue +18 -0
- package/frontend-vue/app/components/blocks/Hero.vue +32 -0
- package/frontend-vue/app/components/blocks/List.vue +33 -0
- package/frontend-vue/app/components/blocks/Paragraph.vue +18 -0
- package/frontend-vue/app/components/blocks/Total.vue +18 -0
- package/frontend-vue/app/components/editor/EditorBlock.vue +121 -0
- package/frontend-vue/app/components/editor/EditorBlockList.vue +44 -0
- package/frontend-vue/app/components/editor/EditorBlockSettings.vue +186 -0
- package/frontend-vue/app/components/editor/EditorCanvas.vue +91 -0
- package/frontend-vue/app/components/editor/EditorConditionFields.vue +88 -0
- package/frontend-vue/app/components/editor/EditorDataPanel.vue +128 -0
- package/frontend-vue/app/components/editor/EditorDraftBar.vue +35 -0
- package/frontend-vue/app/components/editor/EditorIfBlock.vue +120 -0
- package/frontend-vue/app/components/editor/EditorMissingLocale.vue +62 -0
- package/frontend-vue/app/components/editor/EditorPalette.vue +62 -0
- package/frontend-vue/app/components/editor/EditorRail.vue +58 -0
- package/frontend-vue/app/components/editor/EditorSubjectSettings.vue +37 -0
- package/frontend-vue/app/components/editor/EditorTemplateSettings.vue +87 -0
- package/frontend-vue/app/components/editor/EditorTestSendModal.vue +66 -0
- package/frontend-vue/app/components/editor/EditorToolbar.vue +155 -0
- package/frontend-vue/app/components/editor/EditorVariables.vue +163 -0
- package/frontend-vue/app/components/editor/EditorWorkspace.vue +189 -0
- package/frontend-vue/app/composables/useEditorContext.ts +44 -0
- package/frontend-vue/app/composables/useMailingApi.ts +197 -0
- package/frontend-vue/app/composables/useMailingPeriodData.ts +39 -0
- package/frontend-vue/app/composables/useTemplateDrawer.ts +90 -0
- package/frontend-vue/app/composables/useTemplateEditor.ts +228 -0
- package/frontend-vue/app/emails/EmailMailingTemplate.vue +24 -0
- package/frontend-vue/app/emails/components/MailingBlocks.vue +117 -0
- package/frontend-vue/app/plugins/template-gallery-display.client.ts +14 -0
- package/frontend-vue/app/types/component.ts +29 -0
- package/frontend-vue/app/types/mailing.ts +365 -0
- package/frontend-vue/app/utils/blocks/components.ts +14 -0
- package/frontend-vue/app/utils/blocks/dnd.ts +15 -0
- package/frontend-vue/app/utils/blocks/factory.ts +100 -0
- package/frontend-vue/app/utils/blocks/fields.ts +58 -0
- package/frontend-vue/app/utils/blocks/index.ts +5 -0
- package/frontend-vue/app/utils/blocks/tree.ts +74 -0
- package/frontend-vue/app/utils/categories.ts +54 -0
- package/frontend-vue/app/utils/clone.ts +3 -0
- package/frontend-vue/app/utils/conditions.ts +50 -0
- package/frontend-vue/app/utils/editor-route.ts +18 -0
- package/frontend-vue/app/utils/gallery.ts +41 -0
- package/frontend-vue/app/utils/locales.ts +41 -0
- package/frontend-vue/app/utils/paths.ts +84 -0
- package/frontend-vue/app/utils/period.ts +23 -0
- package/frontend-vue/app/utils/testData.ts +10 -0
- package/frontend-vue/app/utils/timeline.ts +68 -0
- package/frontend-vue/app/utils/tokens.ts +24 -0
- package/frontend-vue/app/utils/variables.ts +48 -0
- package/frontend-vue/dms.email.ts +9 -0
- package/frontend-vue/dms.frontend.ts +29 -0
- package/frontend-vue/eslint.config.mjs +13 -0
- package/frontend-vue/i18n/locales/mailing-en-GB.json +397 -0
- package/frontend-vue/i18n/locales/mailing-fr-FR.json +397 -0
- package/frontend-vue/package.json +32 -0
- package/frontend-vue/pnpm-lock.yaml +6446 -0
- package/frontend-vue/pnpm-workspace.yaml +2 -0
- package/frontend-vue/tests/block-components.test.ts +14 -0
- package/frontend-vue/tests/block-fields.test.ts +23 -0
- package/frontend-vue/tests/blocks-factory.test.ts +28 -0
- package/frontend-vue/tests/blocks-tree.test.ts +49 -0
- package/frontend-vue/tests/categories.test.ts +64 -0
- package/frontend-vue/tests/conditions.test.ts +36 -0
- package/frontend-vue/tests/dnd.test.ts +11 -0
- package/frontend-vue/tests/editor-block-list.test.ts +54 -0
- package/frontend-vue/tests/editor-route.test.ts +23 -0
- package/frontend-vue/tests/email-render-cases.json +77 -0
- package/frontend-vue/tests/gallery.test.ts +39 -0
- package/frontend-vue/tests/i18n-editor.test.ts +21 -0
- package/frontend-vue/tests/i18n.test.ts +83 -0
- package/frontend-vue/tests/locales.test.ts +38 -0
- package/frontend-vue/tests/mailing-api.test.ts +10 -0
- package/frontend-vue/tests/paths.test.ts +128 -0
- package/frontend-vue/tests/period.test.ts +13 -0
- package/frontend-vue/tests/smoke.test.ts +8 -0
- package/frontend-vue/tests/template-editor.test.ts +65 -0
- package/frontend-vue/tests/test-data.test.ts +15 -0
- package/frontend-vue/tests/timeline.test.ts +29 -0
- package/frontend-vue/tests/tokens.test.ts +12 -0
- package/frontend-vue/tests/variables.test.ts +46 -0
- package/frontend-vue/vitest.config.ts +10 -0
- package/package.json +94 -0
- package/src/antelope-modules.ts +48 -0
- package/src/automation/email-event-trigger.ts +112 -0
- package/src/automation/index.ts +33 -0
- package/src/automation/send-template-action.ts +100 -0
- package/src/config.ts +15 -0
- package/src/constants.ts +42 -0
- package/src/crons/index.ts +1 -0
- package/src/crons/retention.ts +126 -0
- package/src/data/body.ts +14 -0
- package/src/data/index.ts +3 -0
- package/src/data/sends-table.ts +154 -0
- package/src/data/templates-table.ts +202 -0
- package/src/data-types/categories-type.ts +29 -0
- package/src/data-types/category-type.ts +44 -0
- package/src/data-types/index.ts +2 -0
- package/src/db/index.ts +2 -0
- package/src/db/models/index.ts +4 -0
- package/src/db/models/send-events.model.ts +46 -0
- package/src/db/models/sends.model.ts +147 -0
- package/src/db/models/settings.model.ts +12 -0
- package/src/db/models/templates.model.ts +12 -0
- package/src/db/tables/index.ts +4 -0
- package/src/db/tables/send-events.table.ts +31 -0
- package/src/db/tables/sends.table.ts +85 -0
- package/src/db/tables/settings.table.ts +49 -0
- package/src/db/tables/templates.table.ts +70 -0
- package/src/engine/conditions.ts +30 -0
- package/src/engine/coverage.ts +67 -0
- package/src/engine/fallback-html.ts +116 -0
- package/src/engine/index.ts +7 -0
- package/src/engine/paths.ts +33 -0
- package/src/engine/resolve.ts +178 -0
- package/src/engine/tokens.ts +35 -0
- package/src/engine/walk.ts +18 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/tenant-export.ts +127 -0
- package/src/implementations/dms-mailing/index.ts +2 -0
- package/src/index.ts +62 -0
- package/src/pages/editor.ts +23 -0
- package/src/pages/index.ts +11 -0
- package/src/pages/module.ts +9 -0
- package/src/pages/overview.ts +175 -0
- package/src/pages/sends.ts +174 -0
- package/src/pages/settings/form.ts +67 -0
- package/src/pages/settings/index.ts +2 -0
- package/src/pages/settings/page.ts +34 -0
- package/src/pages/templates.ts +141 -0
- package/src/permissions.ts +44 -0
- package/src/quick-actions.ts +20 -0
- package/src/realtime.ts +53 -0
- package/src/routes/events.ts +57 -0
- package/src/routes/index.ts +7 -0
- package/src/routes/metrics.ts +243 -0
- package/src/routes/period.ts +46 -0
- package/src/routes/provider.ts +31 -0
- package/src/routes/sends.ts +215 -0
- package/src/routes/settings.ts +66 -0
- package/src/routes/templates.ts +256 -0
- package/src/services/attention.ts +151 -0
- package/src/services/categories.ts +43 -0
- package/src/services/events.ts +121 -0
- package/src/services/metrics.ts +175 -0
- package/src/services/notify.ts +90 -0
- package/src/services/permissions.ts +42 -0
- package/src/services/provider.ts +46 -0
- package/src/services/render.ts +33 -0
- package/src/services/send.ts +409 -0
- package/src/services/settings.ts +74 -0
- package/src/services/status.ts +16 -0
- package/src/services/templates.ts +157 -0
- package/src/test/antelope.test.ts +188 -0
- package/src/test/helpers/http.ts +23 -0
- package/src/test/helpers/owner.ts +37 -0
- package/src/test/integration/events.test.ts +136 -0
- package/src/test/integration/retention.test.ts +363 -0
- package/src/test/integration/sends.test.ts +200 -0
- package/src/test/integration/settings.test.ts +50 -0
- package/src/test/integration/setup.test.ts +35 -0
- package/src/test/integration/templates.test.ts +229 -0
- package/src/test/integration/tenant-export.test.ts +125 -0
- package/src/test/unit/attention.test.ts +129 -0
- package/src/test/unit/automation.test.ts +130 -0
- package/src/test/unit/categories.test.ts +51 -0
- package/src/test/unit/engine/conditions.test.ts +35 -0
- package/src/test/unit/engine/coverage.test.ts +118 -0
- package/src/test/unit/engine/fallback-html.test.ts +86 -0
- package/src/test/unit/engine/paths.test.ts +39 -0
- package/src/test/unit/engine/resolve.test.ts +176 -0
- package/src/test/unit/engine/tokens.test.ts +26 -0
- package/src/test/unit/events.test.ts +19 -0
- package/src/test/unit/metrics.test.ts +170 -0
- package/src/test/unit/notify.test.ts +71 -0
- package/src/test/unit/period.test.ts +64 -0
- package/src/test/unit/provider.test.ts +57 -0
- package/src/test/unit/realtime.test.ts +11 -0
- package/src/test/unit/render.test.ts +23 -0
- package/src/test/unit/retention.test.ts +91 -0
- package/src/test/unit/send-batch.test.ts +72 -0
- package/src/test/unit/send.test.ts +161 -0
- package/src/test/unit/smoke.test.ts +8 -0
- package/src/test/unit/status.test.ts +14 -0
- package/src/test/unit/templates.test.ts +52 -0
- package/src/test/unit/tenant-export.test.ts +88 -0
- package/src/test/unit/webhook-secret.test.ts +50 -0
- package/src/types/blocks.ts +31 -0
- package/src/types/index.ts +4 -0
- package/src/types/resolved.ts +89 -0
- package/src/types/sends.ts +44 -0
- package/src/types/settings.ts +24 -0
- package/src/validation/settings.schema.ts +33 -0
- package/src/validation/templates.schema.ts +145 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import { useEditorContext } from '../../composables/useEditorContext'
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
fallback: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const props = defineProps<Props>()
|
|
10
|
+
|
|
11
|
+
const { t } = useI18n()
|
|
12
|
+
const { editor } = useEditorContext()
|
|
13
|
+
|
|
14
|
+
const canDuplicate = computed(
|
|
15
|
+
() =>
|
|
16
|
+
props.fallback !== editor.locale &&
|
|
17
|
+
Boolean(editor.content.locales[props.fallback]),
|
|
18
|
+
)
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<template>
|
|
22
|
+
<div class="bg-elevated/40 flex flex-1 items-center justify-center p-8">
|
|
23
|
+
<div class="flex max-w-sm flex-col items-center gap-3 text-center">
|
|
24
|
+
<UIcon name="i-ph-globe-hemisphere-west" class="text-muted size-10" />
|
|
25
|
+
<h2 class="text-base font-semibold">
|
|
26
|
+
{{
|
|
27
|
+
t('dms_mailing.editor.missing_locale.title', {
|
|
28
|
+
locale: editor.locale.toUpperCase(),
|
|
29
|
+
})
|
|
30
|
+
}}
|
|
31
|
+
</h2>
|
|
32
|
+
<p class="text-muted text-sm">
|
|
33
|
+
{{
|
|
34
|
+
t('dms_mailing.editor.missing_locale.hint', {
|
|
35
|
+
locale: editor.locale.toUpperCase(),
|
|
36
|
+
fallback: fallback.toUpperCase(),
|
|
37
|
+
})
|
|
38
|
+
}}
|
|
39
|
+
</p>
|
|
40
|
+
<div class="mt-1 flex items-center gap-2">
|
|
41
|
+
<UButton
|
|
42
|
+
v-if="canDuplicate"
|
|
43
|
+
icon="i-ph-copy"
|
|
44
|
+
@click="editor.createLocale(editor.locale, fallback)"
|
|
45
|
+
>
|
|
46
|
+
{{
|
|
47
|
+
t('dms_mailing.editor.missing_locale.duplicate', {
|
|
48
|
+
fallback: fallback.toUpperCase(),
|
|
49
|
+
})
|
|
50
|
+
}}
|
|
51
|
+
</UButton>
|
|
52
|
+
<UButton
|
|
53
|
+
variant="ghost"
|
|
54
|
+
color="neutral"
|
|
55
|
+
@click="editor.createLocale(editor.locale)"
|
|
56
|
+
>
|
|
57
|
+
{{ t('dms_mailing.editor.missing_locale.blank') }}
|
|
58
|
+
</UButton>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
</template>
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import draggable from 'vuedraggable'
|
|
3
|
+
import type { BlockType } from '../../types/mailing'
|
|
4
|
+
import { useEditorContext } from '../../composables/useEditorContext'
|
|
5
|
+
import {
|
|
6
|
+
BLOCK_PALETTE,
|
|
7
|
+
LOGIC_PALETTE,
|
|
8
|
+
PALETTE_GROUP,
|
|
9
|
+
cloneFromPalette,
|
|
10
|
+
} from '../../utils/blocks'
|
|
11
|
+
|
|
12
|
+
const { t } = useI18n()
|
|
13
|
+
const { editor } = useEditorContext()
|
|
14
|
+
|
|
15
|
+
const groups = [
|
|
16
|
+
{ id: 'content', entries: BLOCK_PALETTE },
|
|
17
|
+
{ id: 'logic', entries: LOGIC_PALETTE },
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
function add(type: BlockType): void {
|
|
21
|
+
if (!editor.current) return
|
|
22
|
+
editor.addBlock(type)
|
|
23
|
+
}
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<template>
|
|
27
|
+
<div class="flex flex-col gap-4">
|
|
28
|
+
<section v-for="group in groups" :key="group.id">
|
|
29
|
+
<p
|
|
30
|
+
class="text-muted mb-2 text-[11px] font-semibold uppercase tracking-wide"
|
|
31
|
+
>
|
|
32
|
+
{{ t(`dms_mailing.editor.groups.${group.id}`) }}
|
|
33
|
+
</p>
|
|
34
|
+
<draggable
|
|
35
|
+
:list="group.entries"
|
|
36
|
+
:group="PALETTE_GROUP"
|
|
37
|
+
:clone="cloneFromPalette"
|
|
38
|
+
:sort="false"
|
|
39
|
+
item-key="type"
|
|
40
|
+
class="grid grid-cols-2 gap-2"
|
|
41
|
+
>
|
|
42
|
+
<template #item="{ element }">
|
|
43
|
+
<button
|
|
44
|
+
type="button"
|
|
45
|
+
class="border-default bg-default hover:border-primary hover:text-primary flex cursor-grab items-center gap-2 rounded-md border px-2.5 py-2 text-left text-xs transition disabled:cursor-not-allowed disabled:opacity-50"
|
|
46
|
+
:disabled="!editor.current"
|
|
47
|
+
@click="add(element.type)"
|
|
48
|
+
>
|
|
49
|
+
<UIcon :name="element.icon" class="size-4 shrink-0" />
|
|
50
|
+
<span class="truncate">
|
|
51
|
+
{{ t(`dms_mailing.blocks.${element.type}`) }}
|
|
52
|
+
</span>
|
|
53
|
+
</button>
|
|
54
|
+
</template>
|
|
55
|
+
</draggable>
|
|
56
|
+
</section>
|
|
57
|
+
|
|
58
|
+
<p class="text-muted text-[11px]">
|
|
59
|
+
{{ t('dms_mailing.editor.palette.hint') }}
|
|
60
|
+
</p>
|
|
61
|
+
</div>
|
|
62
|
+
</template>
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import type { RailTab } from '../../composables/useTemplateEditor'
|
|
4
|
+
import { useEditorContext } from '../../composables/useEditorContext'
|
|
5
|
+
|
|
6
|
+
const TABS: RailTab[] = ['blocks', 'settings', 'template', 'data']
|
|
7
|
+
|
|
8
|
+
const { t } = useI18n()
|
|
9
|
+
const { editor } = useEditorContext()
|
|
10
|
+
|
|
11
|
+
const items = computed(() =>
|
|
12
|
+
TABS.map((value) => ({
|
|
13
|
+
value,
|
|
14
|
+
label: t(`dms_mailing.editor.tabs.${value}`),
|
|
15
|
+
})),
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
const tab = computed({
|
|
19
|
+
get: () => editor.tab,
|
|
20
|
+
set: (value: string | number) => (editor.tab = value as RailTab),
|
|
21
|
+
})
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<template>
|
|
25
|
+
<div class="flex min-h-0 flex-1 flex-col">
|
|
26
|
+
<div class="border-default flex items-center gap-2 border-b p-2">
|
|
27
|
+
<UTabs
|
|
28
|
+
v-model="tab"
|
|
29
|
+
:items="items"
|
|
30
|
+
:content="false"
|
|
31
|
+
size="xs"
|
|
32
|
+
class="min-w-0 flex-1"
|
|
33
|
+
/>
|
|
34
|
+
<UButton
|
|
35
|
+
icon="i-ph-x"
|
|
36
|
+
size="xs"
|
|
37
|
+
variant="ghost"
|
|
38
|
+
color="neutral"
|
|
39
|
+
:aria-label="t('dms_mailing.editor.toggle_rail')"
|
|
40
|
+
@click="editor.railOpen = false"
|
|
41
|
+
/>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<div class="min-h-0 flex-1 overflow-y-auto p-3">
|
|
45
|
+
<DmsMailingEditorPalette v-if="editor.tab === 'blocks'" />
|
|
46
|
+
<DmsMailingEditorDataPanel v-else-if="editor.tab === 'data'" />
|
|
47
|
+
<DmsMailingEditorTemplateSettings v-else-if="editor.tab === 'template'" />
|
|
48
|
+
<DmsMailingEditorSubjectSettings v-else-if="editor.subjectSelected" />
|
|
49
|
+
<DmsMailingEditorBlockSettings
|
|
50
|
+
v-else-if="editor.selected"
|
|
51
|
+
:block="editor.selected"
|
|
52
|
+
/>
|
|
53
|
+
<p v-else class="text-muted px-1 py-6 text-center text-xs">
|
|
54
|
+
{{ t('dms_mailing.editor.settings_hint') }}
|
|
55
|
+
</p>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
</template>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import { useEditorContext } from '../../composables/useEditorContext'
|
|
4
|
+
|
|
5
|
+
const { t } = useI18n()
|
|
6
|
+
const { editor } = useEditorContext()
|
|
7
|
+
|
|
8
|
+
const subject = computed({
|
|
9
|
+
get: () => editor.current?.subject ?? '',
|
|
10
|
+
set: (value: string) => editor.updateLocale({ subject: value }),
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
const preheader = computed({
|
|
14
|
+
get: () => editor.current?.preheader ?? '',
|
|
15
|
+
set: (value: string) => editor.updateLocale({ preheader: value }),
|
|
16
|
+
})
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<template>
|
|
20
|
+
<div class="flex flex-col gap-4">
|
|
21
|
+
<p class="text-muted text-[11px] font-semibold uppercase tracking-wide">
|
|
22
|
+
{{ t('dms_mailing.editor.subject') }}
|
|
23
|
+
</p>
|
|
24
|
+
|
|
25
|
+
<UFormField
|
|
26
|
+
:label="t('dms_mailing.editor.subject')"
|
|
27
|
+
:help="t('dms_mailing.editor.subject_help')"
|
|
28
|
+
size="xs"
|
|
29
|
+
>
|
|
30
|
+
<UInput v-model="subject" class="w-full" />
|
|
31
|
+
</UFormField>
|
|
32
|
+
|
|
33
|
+
<UFormField :label="t('dms_mailing.editor.preheader')" size="xs">
|
|
34
|
+
<UInput v-model="preheader" class="w-full" />
|
|
35
|
+
</UFormField>
|
|
36
|
+
</div>
|
|
37
|
+
</template>
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import { useEditorContext } from '../../composables/useEditorContext'
|
|
4
|
+
import type { TemplateStatus } from '../../types/mailing'
|
|
5
|
+
|
|
6
|
+
interface StatusAction {
|
|
7
|
+
action: 'publish' | 'unpublish' | 'archive'
|
|
8
|
+
icon: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Status is not a free-form field: each transition has its own side effects. */
|
|
12
|
+
const STATUS_ACTIONS: StatusAction[] = [
|
|
13
|
+
{ action: 'publish', icon: 'i-ph-rocket-launch' },
|
|
14
|
+
{ action: 'unpublish', icon: 'i-ph-eye-slash' },
|
|
15
|
+
{ action: 'archive', icon: 'i-ph-archive' },
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
const STATUS_COLOURS: Record<TemplateStatus, 'success' | 'neutral'> = {
|
|
19
|
+
live: 'success',
|
|
20
|
+
draft: 'neutral',
|
|
21
|
+
archived: 'neutral',
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const { t } = useI18n()
|
|
25
|
+
const { editor, template, name, category, runStatusAction } = useEditorContext()
|
|
26
|
+
|
|
27
|
+
const statusLabel = computed(() =>
|
|
28
|
+
t(`dms_mailing.templates.status.${template.value.status}`),
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
function touchOnEdit(): void {
|
|
32
|
+
editor.touch()
|
|
33
|
+
}
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
<template>
|
|
37
|
+
<div class="flex flex-col gap-4">
|
|
38
|
+
<p class="text-muted text-[11px] font-semibold uppercase tracking-wide">
|
|
39
|
+
{{ t('dms_mailing.editor.tabs.template') }}
|
|
40
|
+
</p>
|
|
41
|
+
|
|
42
|
+
<UFormField :label="t('dms_mailing.templates.cols.name')" size="xs">
|
|
43
|
+
<UInput v-model="name" class="w-full" @update:model-value="touchOnEdit" />
|
|
44
|
+
</UFormField>
|
|
45
|
+
|
|
46
|
+
<UFormField :label="t('dms_mailing.templates.cols.slug')" size="xs">
|
|
47
|
+
<UInput :model-value="template.slug" class="w-full font-mono" disabled />
|
|
48
|
+
</UFormField>
|
|
49
|
+
|
|
50
|
+
<UFormField :label="t('dms_mailing.templates.cols.category')" size="xs">
|
|
51
|
+
<DmsMailingCategoryInput
|
|
52
|
+
v-model="category"
|
|
53
|
+
@update:model-value="touchOnEdit"
|
|
54
|
+
/>
|
|
55
|
+
</UFormField>
|
|
56
|
+
|
|
57
|
+
<USeparator />
|
|
58
|
+
|
|
59
|
+
<section class="flex flex-col gap-2">
|
|
60
|
+
<p class="text-muted text-[11px] font-semibold uppercase tracking-wide">
|
|
61
|
+
{{ t('dms_mailing.templates.cols.status') }}
|
|
62
|
+
</p>
|
|
63
|
+
<UBadge
|
|
64
|
+
:color="STATUS_COLOURS[template.status]"
|
|
65
|
+
variant="subtle"
|
|
66
|
+
size="sm"
|
|
67
|
+
class="self-start"
|
|
68
|
+
:label="statusLabel"
|
|
69
|
+
/>
|
|
70
|
+
<div class="flex flex-wrap items-center gap-2">
|
|
71
|
+
<UButton
|
|
72
|
+
v-for="entry in STATUS_ACTIONS"
|
|
73
|
+
:key="entry.action"
|
|
74
|
+
size="xs"
|
|
75
|
+
variant="outline"
|
|
76
|
+
color="neutral"
|
|
77
|
+
:icon="entry.icon"
|
|
78
|
+
:label="t(`dms_mailing.templates.actions.${entry.action}`)"
|
|
79
|
+
@click="runStatusAction(entry.action)"
|
|
80
|
+
/>
|
|
81
|
+
</div>
|
|
82
|
+
<p class="text-muted text-[11px]">
|
|
83
|
+
{{ t('dms_mailing.editor.status_hint') }}
|
|
84
|
+
</p>
|
|
85
|
+
</section>
|
|
86
|
+
</div>
|
|
87
|
+
</template>
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, ref } from 'vue'
|
|
3
|
+
import { useEditorContext } from '../../composables/useEditorContext'
|
|
4
|
+
import { parseTestData } from '../../utils/testData'
|
|
5
|
+
|
|
6
|
+
const RECIPIENT_SEPARATOR = /[\s,;]+/
|
|
7
|
+
|
|
8
|
+
const open = defineModel<boolean>('open', { required: true })
|
|
9
|
+
|
|
10
|
+
const { t } = useI18n()
|
|
11
|
+
const toast = useToast()
|
|
12
|
+
const { editor, api, templateId, testData } = useEditorContext()
|
|
13
|
+
|
|
14
|
+
const recipients = ref('')
|
|
15
|
+
const sending = ref(false)
|
|
16
|
+
|
|
17
|
+
const addresses = computed(() =>
|
|
18
|
+
recipients.value.split(RECIPIENT_SEPARATOR).filter(Boolean),
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
async function send(): Promise<void> {
|
|
22
|
+
sending.value = true
|
|
23
|
+
try {
|
|
24
|
+
await api.testSend(templateId, {
|
|
25
|
+
to: addresses.value,
|
|
26
|
+
locale: editor.locale,
|
|
27
|
+
content: editor.content,
|
|
28
|
+
data: parseTestData(testData.value) ?? undefined,
|
|
29
|
+
})
|
|
30
|
+
toast.add({
|
|
31
|
+
title: t('dms_mailing.editor.test_send_modal.sent'),
|
|
32
|
+
color: 'success',
|
|
33
|
+
})
|
|
34
|
+
open.value = false
|
|
35
|
+
} finally {
|
|
36
|
+
sending.value = false
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<template>
|
|
42
|
+
<UModal
|
|
43
|
+
v-model:open="open"
|
|
44
|
+
:title="t('dms_mailing.editor.test_send_modal.title')"
|
|
45
|
+
>
|
|
46
|
+
<template #body>
|
|
47
|
+
<UFormField :label="t('dms_mailing.editor.test_send_modal.recipients')">
|
|
48
|
+
<UInput
|
|
49
|
+
v-model="recipients"
|
|
50
|
+
class="w-full"
|
|
51
|
+
placeholder="a@example.com, b@example.com"
|
|
52
|
+
/>
|
|
53
|
+
</UFormField>
|
|
54
|
+
</template>
|
|
55
|
+
<template #footer>
|
|
56
|
+
<UButton
|
|
57
|
+
icon="i-ph-paper-plane-tilt"
|
|
58
|
+
:loading="sending"
|
|
59
|
+
:disabled="addresses.length === 0"
|
|
60
|
+
@click="send()"
|
|
61
|
+
>
|
|
62
|
+
{{ t('dms_mailing.editor.test_send_modal.send') }}
|
|
63
|
+
</UButton>
|
|
64
|
+
</template>
|
|
65
|
+
</UModal>
|
|
66
|
+
</template>
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import type { EditorDevice } from '../../composables/useTemplateEditor'
|
|
4
|
+
import { useEditorContext } from '../../composables/useEditorContext'
|
|
5
|
+
|
|
6
|
+
interface SegmentedItem {
|
|
7
|
+
label: string
|
|
8
|
+
value: string
|
|
9
|
+
icon?: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const TEMPLATES_PATH = '/modules/mailing/templates'
|
|
13
|
+
const LIVE_STATUS = 'live'
|
|
14
|
+
const MISSING_LOCALE_SUFFIX = ' ·'
|
|
15
|
+
|
|
16
|
+
const { t } = useI18n()
|
|
17
|
+
const { editor, template, name, save, publish, openTestSend } =
|
|
18
|
+
useEditorContext()
|
|
19
|
+
|
|
20
|
+
const localeItems = computed<SegmentedItem[]>(() =>
|
|
21
|
+
editor.locales.map((code) => ({
|
|
22
|
+
value: code,
|
|
23
|
+
label: editor.missingLocales.includes(code)
|
|
24
|
+
? `${code.toUpperCase()}${MISSING_LOCALE_SUFFIX}`
|
|
25
|
+
: code.toUpperCase(),
|
|
26
|
+
})),
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
const deviceItems = computed<SegmentedItem[]>(() => [
|
|
30
|
+
{
|
|
31
|
+
value: 'desktop',
|
|
32
|
+
label: t('dms_mailing.editor.desktop'),
|
|
33
|
+
icon: 'i-ph-monitor',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
value: 'mobile',
|
|
37
|
+
label: t('dms_mailing.editor.mobile'),
|
|
38
|
+
icon: 'i-ph-device-mobile',
|
|
39
|
+
},
|
|
40
|
+
])
|
|
41
|
+
|
|
42
|
+
const locale = computed({
|
|
43
|
+
get: () => editor.locale,
|
|
44
|
+
set: (code: string) => editor.setLocale(code),
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
const device = computed({
|
|
48
|
+
get: () => editor.device,
|
|
49
|
+
set: (value: string | number) => (editor.device = value as EditorDevice),
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
// The name is not edited here: the Template tab is the one place to rename,
|
|
53
|
+
// so the heading is a plain affordance that opens it.
|
|
54
|
+
function openTemplateSettings(): void {
|
|
55
|
+
editor.railOpen = true
|
|
56
|
+
editor.tab = 'template'
|
|
57
|
+
}
|
|
58
|
+
</script>
|
|
59
|
+
|
|
60
|
+
<template>
|
|
61
|
+
<header
|
|
62
|
+
class="border-default bg-default flex h-14 shrink-0 items-center gap-3 border-b px-4"
|
|
63
|
+
>
|
|
64
|
+
<UButton
|
|
65
|
+
icon="i-ph-arrow-left"
|
|
66
|
+
variant="ghost"
|
|
67
|
+
color="neutral"
|
|
68
|
+
:aria-label="t('dms_mailing.editor.back')"
|
|
69
|
+
@click="navigateDms(TEMPLATES_PATH)"
|
|
70
|
+
/>
|
|
71
|
+
|
|
72
|
+
<button
|
|
73
|
+
type="button"
|
|
74
|
+
class="hover:bg-elevated min-w-0 rounded-md px-2 py-1 text-left transition-colors"
|
|
75
|
+
:title="t('dms_mailing.editor.open_template_settings')"
|
|
76
|
+
@click="openTemplateSettings()"
|
|
77
|
+
>
|
|
78
|
+
<p class="truncate text-sm font-semibold">{{ name }}</p>
|
|
79
|
+
<p class="text-muted truncate font-mono text-[11px]">
|
|
80
|
+
{{ template.slug }}
|
|
81
|
+
</p>
|
|
82
|
+
</button>
|
|
83
|
+
|
|
84
|
+
<div class="ml-4 flex items-center gap-2">
|
|
85
|
+
<DmsSegmented
|
|
86
|
+
v-model="locale"
|
|
87
|
+
:items="localeItems"
|
|
88
|
+
size="xs"
|
|
89
|
+
:aria-label="t('dms_mailing.editor.title')"
|
|
90
|
+
/>
|
|
91
|
+
<DmsSegmented
|
|
92
|
+
v-model="device"
|
|
93
|
+
:items="deviceItems"
|
|
94
|
+
size="xs"
|
|
95
|
+
:aria-label="t('dms_mailing.editor.desktop')"
|
|
96
|
+
/>
|
|
97
|
+
<UBadge
|
|
98
|
+
:color="editor.previewMode ? 'primary' : 'neutral'"
|
|
99
|
+
variant="subtle"
|
|
100
|
+
size="sm"
|
|
101
|
+
icon="i-ph-database"
|
|
102
|
+
>
|
|
103
|
+
{{
|
|
104
|
+
editor.previewMode
|
|
105
|
+
? t('dms_mailing.editor.preview_mode.data')
|
|
106
|
+
: t('dms_mailing.editor.preview_mode.variables')
|
|
107
|
+
}}
|
|
108
|
+
<UButton
|
|
109
|
+
v-if="editor.previewMode"
|
|
110
|
+
icon="i-ph-x"
|
|
111
|
+
size="xs"
|
|
112
|
+
variant="ghost"
|
|
113
|
+
color="neutral"
|
|
114
|
+
class="-mr-1 p-0"
|
|
115
|
+
:aria-label="t('dms_mailing.editor.preview_mode.back')"
|
|
116
|
+
:title="t('dms_mailing.editor.preview_mode.back')"
|
|
117
|
+
@click="editor.previewMode = false"
|
|
118
|
+
/>
|
|
119
|
+
</UBadge>
|
|
120
|
+
</div>
|
|
121
|
+
|
|
122
|
+
<div class="ml-auto flex items-center gap-2">
|
|
123
|
+
<UButton
|
|
124
|
+
icon="i-ph-sidebar-simple"
|
|
125
|
+
variant="ghost"
|
|
126
|
+
color="neutral"
|
|
127
|
+
:aria-label="t('dms_mailing.editor.toggle_rail')"
|
|
128
|
+
@click="editor.railOpen = !editor.railOpen"
|
|
129
|
+
/>
|
|
130
|
+
<UButton
|
|
131
|
+
variant="outline"
|
|
132
|
+
color="neutral"
|
|
133
|
+
icon="i-ph-paper-plane-tilt"
|
|
134
|
+
@click="openTestSend()"
|
|
135
|
+
>
|
|
136
|
+
{{ t('dms_mailing.editor.test_send') }}
|
|
137
|
+
</UButton>
|
|
138
|
+
<UButton
|
|
139
|
+
v-if="template.status !== LIVE_STATUS"
|
|
140
|
+
variant="outline"
|
|
141
|
+
icon="i-ph-rocket-launch"
|
|
142
|
+
@click="publish()"
|
|
143
|
+
>
|
|
144
|
+
{{ t('dms_mailing.editor.publish') }}
|
|
145
|
+
</UButton>
|
|
146
|
+
<UButton
|
|
147
|
+
icon="i-ph-floppy-disk"
|
|
148
|
+
:disabled="editor.dirty === 0"
|
|
149
|
+
@click="save()"
|
|
150
|
+
>
|
|
151
|
+
{{ t('dms_mailing.editor.save') }}
|
|
152
|
+
</UButton>
|
|
153
|
+
</div>
|
|
154
|
+
</header>
|
|
155
|
+
</template>
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, ref } from 'vue'
|
|
3
|
+
import type { VariableDefinition, VariableType } from '../../types/mailing'
|
|
4
|
+
import { VARIABLE_TYPES } from '../../types/mailing'
|
|
5
|
+
import { useEditorContext } from '../../composables/useEditorContext'
|
|
6
|
+
import { collectContentPaths } from '../../utils/paths'
|
|
7
|
+
import { mergeVariables } from '../../utils/variables'
|
|
8
|
+
|
|
9
|
+
const DEFAULT_TYPE: VariableType = 'string'
|
|
10
|
+
|
|
11
|
+
const emit = defineEmits<{ insert: [string] }>()
|
|
12
|
+
|
|
13
|
+
const { t } = useI18n()
|
|
14
|
+
const { api, editor, templateId, variables, detectedVariables } =
|
|
15
|
+
useEditorContext()
|
|
16
|
+
|
|
17
|
+
// `detectedVariables` comes from the server and only refreshes on save, so on
|
|
18
|
+
// its own it strikes through a path the moment it is wired into a condition.
|
|
19
|
+
// Union it with what the editor currently holds: the server list still covers
|
|
20
|
+
// what a save has seen, and the live one covers what the author just did.
|
|
21
|
+
const detected = computed(() => [
|
|
22
|
+
...new Set([
|
|
23
|
+
...detectedVariables.value,
|
|
24
|
+
...collectContentPaths(editor.content),
|
|
25
|
+
]),
|
|
26
|
+
])
|
|
27
|
+
|
|
28
|
+
const merged = computed(() => mergeVariables(detected.value, variables.value))
|
|
29
|
+
const undeclared = computed(() =>
|
|
30
|
+
merged.value
|
|
31
|
+
.filter((entry) => entry.usage === 'detected')
|
|
32
|
+
.map((entry) => entry.path),
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
const declaring = ref(false)
|
|
36
|
+
const saving = ref(false)
|
|
37
|
+
const draft = ref<VariableDefinition>({
|
|
38
|
+
path: '',
|
|
39
|
+
type: DEFAULT_TYPE,
|
|
40
|
+
required: false,
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
function reset(): void {
|
|
44
|
+
draft.value = { path: '', type: DEFAULT_TYPE, required: false }
|
|
45
|
+
declaring.value = false
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function startDeclaring(): void {
|
|
49
|
+
draft.value = {
|
|
50
|
+
path: undeclared.value[0] ?? '',
|
|
51
|
+
type: DEFAULT_TYPE,
|
|
52
|
+
required: false,
|
|
53
|
+
}
|
|
54
|
+
declaring.value = true
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function declare(): Promise<void> {
|
|
58
|
+
if (!draft.value.path) return
|
|
59
|
+
saving.value = true
|
|
60
|
+
try {
|
|
61
|
+
const next = [...variables.value, { ...draft.value }]
|
|
62
|
+
const response = await api.saveVariables(templateId, next)
|
|
63
|
+
variables.value = response.variables ?? next
|
|
64
|
+
reset()
|
|
65
|
+
} finally {
|
|
66
|
+
saving.value = false
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
</script>
|
|
70
|
+
|
|
71
|
+
<template>
|
|
72
|
+
<section class="flex flex-col gap-2">
|
|
73
|
+
<p class="text-muted text-[11px] font-semibold uppercase tracking-wide">
|
|
74
|
+
{{ t('dms_mailing.editor.groups.variables') }}
|
|
75
|
+
</p>
|
|
76
|
+
|
|
77
|
+
<p v-if="merged.length === 0" class="text-muted text-[11px]">
|
|
78
|
+
{{ t('dms_mailing.editor.variables.empty') }}
|
|
79
|
+
</p>
|
|
80
|
+
|
|
81
|
+
<button
|
|
82
|
+
v-for="variable in merged"
|
|
83
|
+
:key="variable.path"
|
|
84
|
+
type="button"
|
|
85
|
+
class="border-default hover:border-primary flex items-center gap-2 rounded-md border px-2 py-1.5 text-left transition"
|
|
86
|
+
:title="t('dms_mailing.editor.variables.insert')"
|
|
87
|
+
@click="emit('insert', variable.path)"
|
|
88
|
+
>
|
|
89
|
+
<code
|
|
90
|
+
class="min-w-0 flex-1 truncate font-mono text-[11px]"
|
|
91
|
+
:class="variable.usage === 'unused' ? 'text-muted line-through' : ''"
|
|
92
|
+
>
|
|
93
|
+
{{ variable.path }}
|
|
94
|
+
</code>
|
|
95
|
+
<UBadge
|
|
96
|
+
v-if="variable.usage === 'unused'"
|
|
97
|
+
size="sm"
|
|
98
|
+
variant="subtle"
|
|
99
|
+
color="warning"
|
|
100
|
+
>
|
|
101
|
+
{{ t('dms_mailing.editor.variables.unused') }}
|
|
102
|
+
</UBadge>
|
|
103
|
+
<UBadge size="sm" variant="subtle" color="neutral">
|
|
104
|
+
{{ variable.type }}
|
|
105
|
+
</UBadge>
|
|
106
|
+
<UBadge
|
|
107
|
+
v-if="variable.required"
|
|
108
|
+
size="sm"
|
|
109
|
+
variant="subtle"
|
|
110
|
+
color="warning"
|
|
111
|
+
>
|
|
112
|
+
{{ t('dms_mailing.editor.variables.required') }}
|
|
113
|
+
</UBadge>
|
|
114
|
+
</button>
|
|
115
|
+
|
|
116
|
+
<UButton
|
|
117
|
+
v-if="!declaring"
|
|
118
|
+
size="xs"
|
|
119
|
+
variant="outline"
|
|
120
|
+
color="neutral"
|
|
121
|
+
icon="i-ph-plus"
|
|
122
|
+
@click="startDeclaring()"
|
|
123
|
+
>
|
|
124
|
+
{{ t('dms_mailing.editor.variables.declare') }}
|
|
125
|
+
</UButton>
|
|
126
|
+
|
|
127
|
+
<div
|
|
128
|
+
v-else
|
|
129
|
+
class="border-default flex flex-col gap-2 rounded-md border p-2"
|
|
130
|
+
>
|
|
131
|
+
<UFormField :label="t('dms_mailing.editor.variables.path')" size="xs">
|
|
132
|
+
<UInput
|
|
133
|
+
v-model="draft.path"
|
|
134
|
+
class="w-full"
|
|
135
|
+
placeholder="order.total"
|
|
136
|
+
list="editor-detected-paths"
|
|
137
|
+
/>
|
|
138
|
+
<datalist id="editor-detected-paths">
|
|
139
|
+
<option v-for="path in undeclared" :key="path" :value="path" />
|
|
140
|
+
</datalist>
|
|
141
|
+
</UFormField>
|
|
142
|
+
<UFormField :label="t('dms_mailing.editor.variables.type')" size="xs">
|
|
143
|
+
<USelect v-model="draft.type" class="w-full" :items="VARIABLE_TYPES" />
|
|
144
|
+
</UFormField>
|
|
145
|
+
<UFormField :label="t('dms_mailing.editor.variables.required')" size="xs">
|
|
146
|
+
<USwitch v-model="draft.required" />
|
|
147
|
+
</UFormField>
|
|
148
|
+
<div class="flex items-center gap-2">
|
|
149
|
+
<UButton
|
|
150
|
+
size="xs"
|
|
151
|
+
:loading="saving"
|
|
152
|
+
:disabled="!draft.path"
|
|
153
|
+
@click="declare()"
|
|
154
|
+
>
|
|
155
|
+
{{ t('dms_mailing.editor.variables.add') }}
|
|
156
|
+
</UButton>
|
|
157
|
+
<UButton size="xs" variant="ghost" color="neutral" @click="reset()">
|
|
158
|
+
{{ t('dms_mailing.editor.variables.cancel') }}
|
|
159
|
+
</UButton>
|
|
160
|
+
</div>
|
|
161
|
+
</div>
|
|
162
|
+
</section>
|
|
163
|
+
</template>
|