@antelopejs/dms-mailing 0.6.1 → 0.7.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 +8 -7
- package/dist/constants.d.ts +1 -2
- package/dist/constants.js +2 -3
- package/dist/constants.js.map +1 -1
- package/dist/data/templates-table.d.ts +4 -1
- package/dist/data/templates-table.js +55 -20
- package/dist/data/templates-table.js.map +1 -1
- package/dist/data-types/categories-type.d.ts +5 -5
- package/dist/db/models/index.d.ts +0 -1
- package/dist/db/models/index.js +0 -1
- package/dist/db/models/index.js.map +1 -1
- package/dist/db/models/send-events.model.js +2 -3
- package/dist/db/models/send-events.model.js.map +1 -1
- package/dist/db/models/sends.model.js +2 -3
- package/dist/db/models/sends.model.js.map +1 -1
- package/dist/db/models/templates.model.d.ts +10 -1
- package/dist/db/models/templates.model.js +29 -10
- package/dist/db/models/templates.model.js.map +1 -1
- package/dist/db/tables/index.d.ts +0 -1
- package/dist/db/tables/index.js +0 -1
- package/dist/db/tables/index.js.map +1 -1
- package/dist/db/tables/settings.table.d.ts +0 -1
- package/dist/db/tables/settings.table.js +0 -4
- package/dist/db/tables/settings.table.js.map +1 -1
- package/dist/db/tables/templates.table.d.ts +7 -4
- package/dist/db/tables/templates.table.js +4 -14
- package/dist/db/tables/templates.table.js.map +1 -1
- package/dist/engine/formats.d.ts +13 -0
- package/dist/engine/formats.js +85 -0
- package/dist/engine/formats.js.map +1 -0
- package/dist/engine/index.d.ts +1 -0
- package/dist/engine/index.js +1 -0
- package/dist/engine/index.js.map +1 -1
- package/dist/engine/resolve.d.ts +2 -1
- package/dist/engine/resolve.js +14 -7
- package/dist/engine/resolve.js.map +1 -1
- package/dist/engine/tokens.d.ts +2 -1
- package/dist/engine/tokens.js +3 -5
- package/dist/engine/tokens.js.map +1 -1
- package/dist/frontend-module.d.ts +2 -0
- package/dist/frontend-module.js +24 -0
- package/dist/frontend-module.js.map +1 -0
- package/dist/hooks/index.d.ts +0 -1
- package/dist/hooks/index.js +0 -1
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/tenant-export.js +4 -1
- package/dist/hooks/tenant-export.js.map +1 -1
- package/dist/implementations/dms-mailing/index.d.ts +3 -3
- package/dist/implementations/dms-mailing/index.js +8 -7
- package/dist/implementations/dms-mailing/index.js.map +1 -1
- package/dist/index.js +2 -20
- package/dist/index.js.map +1 -1
- package/dist/pages/overview.d.ts +1 -1
- package/dist/pages/overview.js +11 -13
- package/dist/pages/overview.js.map +1 -1
- package/dist/pages/sends.d.ts +1 -1
- package/dist/pages/sends.js +16 -8
- package/dist/pages/sends.js.map +1 -1
- package/dist/pages/settings/form.js +8 -0
- package/dist/pages/settings/form.js.map +1 -1
- package/dist/pages/templates.js +3 -8
- package/dist/pages/templates.js.map +1 -1
- package/dist/pages/toolbar.d.ts +12 -0
- package/dist/pages/toolbar.js +41 -0
- package/dist/pages/toolbar.js.map +1 -0
- package/dist/realtime.d.ts +2 -2
- package/dist/realtime.js +8 -8
- package/dist/realtime.js.map +1 -1
- package/dist/routes/metrics.d.ts +9 -2
- package/dist/routes/metrics.js +90 -35
- package/dist/routes/metrics.js.map +1 -1
- package/dist/routes/period.d.ts +1 -0
- package/dist/routes/period.js +19 -0
- package/dist/routes/period.js.map +1 -1
- package/dist/routes/sends.js +6 -3
- package/dist/routes/sends.js.map +1 -1
- package/dist/routes/settings.js +6 -7
- package/dist/routes/settings.js.map +1 -1
- package/dist/routes/templates.d.ts +8 -4
- package/dist/routes/templates.js +41 -20
- package/dist/routes/templates.js.map +1 -1
- package/dist/services/categories.d.ts +2 -1
- package/dist/services/categories.js +9 -0
- package/dist/services/categories.js.map +1 -1
- package/dist/services/declarations.js +42 -184
- package/dist/services/declarations.js.map +1 -1
- package/dist/services/events.js +1 -1
- package/dist/services/events.js.map +1 -1
- package/dist/services/localized-text.d.ts +7 -0
- package/dist/services/localized-text.js +28 -0
- package/dist/services/localized-text.js.map +1 -0
- package/dist/services/send-import.js +0 -2
- package/dist/services/send-import.js.map +1 -1
- package/dist/services/send-stat-items.js +34 -12
- package/dist/services/send-stat-items.js.map +1 -1
- package/dist/services/send-stats.d.ts +7 -0
- package/dist/services/send-stats.js +17 -5
- package/dist/services/send-stats.js.map +1 -1
- package/dist/services/send.d.ts +2 -0
- package/dist/services/send.js +11 -12
- package/dist/services/send.js.map +1 -1
- package/dist/services/sender-declaration.d.ts +2 -0
- package/dist/services/sender-declaration.js +18 -0
- package/dist/services/sender-declaration.js.map +1 -0
- package/dist/services/settings.d.ts +1 -1
- package/dist/services/settings.js +1 -5
- package/dist/services/settings.js.map +1 -1
- package/dist/services/starters.d.ts +2 -2
- package/dist/services/starters.js +1 -10
- package/dist/services/starters.js.map +1 -1
- package/dist/services/templates.d.ts +13 -7
- package/dist/services/templates.js +72 -33
- package/dist/services/templates.js.map +1 -1
- package/dist/services/webhook-status.d.ts +2 -1
- package/dist/services/webhook-status.js +6 -5
- package/dist/services/webhook-status.js.map +1 -1
- package/dist/types/settings.d.ts +8 -0
- package/dist/types/settings.js +33 -6
- package/dist/types/settings.js.map +1 -1
- package/dist/validation/declarations.schema.d.ts +25 -7
- package/dist/validation/declarations.schema.js +16 -3
- package/dist/validation/declarations.schema.js.map +1 -1
- package/dist/validation/settings.schema.d.ts +41 -41
- package/dist/validation/settings.schema.js +30 -6
- package/dist/validation/settings.schema.js.map +1 -1
- package/dist/validation/templates.schema.d.ts +13 -0
- package/dist/validation/templates.schema.js +6 -1
- package/dist/validation/templates.schema.js.map +1 -1
- package/frontend-vue/app/components/CategoriesInput.vue +15 -13
- package/frontend-vue/app/components/FunnelCard.vue +46 -3
- package/frontend-vue/app/components/NewTemplateModal.vue +4 -2
- package/frontend-vue/app/components/OverviewGate.vue +0 -8
- package/frontend-vue/app/components/ProviderPill.vue +50 -0
- package/frontend-vue/app/components/SendDrawer.vue +206 -183
- package/frontend-vue/app/components/StarterGallery.vue +1 -1
- package/frontend-vue/app/components/TemplateCard.vue +4 -1
- package/frontend-vue/app/components/TemplateDrawer.vue +3 -5
- package/frontend-vue/app/components/TemplateGallery.vue +57 -17
- package/frontend-vue/app/components/TemplatePreviewFrame.vue +41 -11
- package/frontend-vue/app/components/editor/EditorTemplatePane.vue +1 -3
- package/frontend-vue/app/components/editor/EditorTitle.vue +1 -7
- package/frontend-vue/app/composables/useLocalizedDefaults.ts +5 -7
- package/frontend-vue/app/composables/useMailingApi.ts +3 -3
- package/frontend-vue/app/composables/useMailingReads.ts +2 -2
- package/frontend-vue/app/composables/useTemplateCategories.ts +8 -7
- package/frontend-vue/app/types/mailing.ts +18 -0
- package/frontend-vue/app/utils/categories.ts +58 -49
- package/frontend-vue/app/utils/funnel.ts +25 -0
- package/frontend-vue/app/utils/period.ts +0 -7
- package/frontend-vue/app/utils/starters.ts +3 -1
- package/frontend-vue/app/utils/tokens.ts +6 -1
- package/frontend-vue/i18n/locales/mailing-en-GB.json +19 -28
- package/frontend-vue/i18n/locales/mailing-fr-FR.json +19 -28
- package/frontend-vue/package.json +1 -1
- package/frontend-vue/tests/categories-input.test.ts +4 -10
- package/frontend-vue/tests/categories.test.ts +55 -17
- package/frontend-vue/tests/conditions.test.ts +1 -4
- package/frontend-vue/tests/funnel-card.test.ts +27 -0
- package/frontend-vue/tests/funnel.test.ts +25 -0
- package/frontend-vue/tests/localized-defaults.test.ts +0 -29
- package/frontend-vue/tests/new-template-modal.test.ts +1 -1
- package/frontend-vue/tests/overview-gate.test.ts +1 -24
- package/frontend-vue/tests/page-reads.test.ts +9 -25
- package/frontend-vue/tests/period.test.ts +0 -12
- package/frontend-vue/tests/replay-toast.test.ts +2 -1
- package/frontend-vue/tests/send-drawer.test.ts +123 -0
- package/frontend-vue/tests/template-drawer.test.ts +1 -1
- package/frontend-vue/tests/template-gallery.test.ts +90 -3
- package/frontend-vue/tests/template-preview-frame.test.ts +92 -0
- package/package.json +8 -7
- package/skills/dms-mailing/SKILL.md +25 -18
- package/src/constants.ts +2 -2
- package/src/data/sends-table.ts +1 -1
- package/src/data/templates-table.ts +125 -33
- package/src/db/models/index.ts +0 -1
- package/src/db/models/send-events.model.ts +2 -2
- package/src/db/models/sends.model.ts +7 -8
- package/src/db/models/templates.model.ts +64 -16
- package/src/db/tables/index.ts +0 -1
- package/src/db/tables/settings.table.ts +0 -8
- package/src/db/tables/templates.table.ts +12 -17
- package/src/engine/formats.ts +124 -0
- package/src/engine/index.ts +1 -0
- package/src/engine/resolve.ts +18 -7
- package/src/engine/tokens.ts +7 -5
- package/src/frontend-module.ts +36 -0
- package/src/hooks/index.ts +0 -1
- package/src/hooks/tenant-export.ts +8 -1
- package/src/implementations/dms-mailing/index.ts +5 -16
- package/src/index.ts +3 -29
- package/src/pages/overview.ts +20 -16
- package/src/pages/sends.ts +27 -11
- package/src/pages/settings/form.ts +16 -0
- package/src/pages/templates.ts +4 -8
- package/src/pages/toolbar.ts +77 -0
- package/src/realtime.ts +17 -8
- package/src/routes/metrics.ts +158 -45
- package/src/routes/period.ts +32 -0
- package/src/routes/sends.ts +13 -4
- package/src/routes/settings.ts +11 -8
- package/src/routes/templates.ts +81 -37
- package/src/services/categories.ts +14 -1
- package/src/services/declarations.ts +64 -317
- package/src/services/events.ts +1 -1
- package/src/services/localized-text.ts +53 -0
- package/src/services/send-import.ts +2 -2
- package/src/services/send-stat-items.ts +60 -13
- package/src/services/send-stats.ts +37 -5
- package/src/services/send.ts +19 -20
- package/src/services/sender-declaration.ts +27 -0
- package/src/services/settings.ts +3 -10
- package/src/services/starters.ts +3 -15
- package/src/services/templates.ts +109 -63
- package/src/services/webhook-status.ts +3 -3
- package/src/types/settings.ts +55 -5
- package/src/validation/declarations.schema.ts +29 -3
- package/src/validation/settings.schema.ts +56 -14
- package/src/validation/templates.schema.ts +10 -0
- package/dist/db/models/template-slugs.model.d.ts +0 -27
- package/dist/db/models/template-slugs.model.js +0 -43
- package/dist/db/models/template-slugs.model.js.map +0 -1
- package/dist/db/tables/template-slugs.table.d.ts +0 -7
- package/dist/db/tables/template-slugs.table.js +0 -35
- package/dist/db/tables/template-slugs.table.js.map +0 -1
- package/dist/hooks/database-ready.d.ts +0 -7
- package/dist/hooks/database-ready.js +0 -26
- package/dist/hooks/database-ready.js.map +0 -1
- package/dist/services/fingerprint.d.ts +0 -1
- package/dist/services/fingerprint.js +0 -20
- package/dist/services/fingerprint.js.map +0 -1
- package/frontend-vue/app/components/PageHeader.vue +0 -94
- package/frontend-vue/app/composables/useOverviewSetup.ts +0 -21
- package/src/db/models/template-slugs.model.ts +0 -61
- package/src/db/tables/template-slugs.table.ts +0 -29
- package/src/hooks/database-ready.ts +0 -47
- package/src/services/fingerprint.ts +0 -22
|
@@ -412,208 +412,231 @@ onMounted(() => {
|
|
|
412
412
|
|
|
413
413
|
<template>
|
|
414
414
|
<!--
|
|
415
|
-
|
|
416
|
-
|
|
415
|
+
The drawer's body is the scroll container and sets the inset: the send
|
|
416
|
+
adds no padding of its own. It grows in the body's flex column, so the
|
|
417
|
+
footer sits at the bottom of the drawer, and sticks there once the send
|
|
418
|
+
scrolls.
|
|
417
419
|
-->
|
|
418
|
-
<div class="flex
|
|
419
|
-
<USkeleton v-if="loading && !send" class="
|
|
420
|
+
<div class="@container flex flex-1 flex-col gap-6" data-send-drawer>
|
|
421
|
+
<USkeleton v-if="loading && !send" class="h-64" />
|
|
420
422
|
|
|
421
423
|
<template v-else-if="send">
|
|
422
|
-
<
|
|
423
|
-
<
|
|
424
|
-
|
|
424
|
+
<header class="border-default flex flex-col gap-1.5 border-b pb-4">
|
|
425
|
+
<div class="flex items-center gap-1">
|
|
426
|
+
<DmsEyebrow
|
|
427
|
+
class="min-w-0 flex-1"
|
|
428
|
+
truncate
|
|
429
|
+
:label="key('eyebrow', { id: shortSendId(send._id) })"
|
|
430
|
+
/>
|
|
431
|
+
<UButton
|
|
432
|
+
icon="i-ph-link"
|
|
433
|
+
color="neutral"
|
|
434
|
+
variant="ghost"
|
|
435
|
+
size="sm"
|
|
436
|
+
:aria-label="key('copy_link')"
|
|
437
|
+
@click="copyLink"
|
|
438
|
+
/>
|
|
439
|
+
</div>
|
|
440
|
+
<h3
|
|
441
|
+
class="text-highlighted flex flex-wrap items-center gap-2 text-lg font-semibold"
|
|
425
442
|
>
|
|
426
|
-
<
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
443
|
+
<span class="min-w-0 truncate">
|
|
444
|
+
{{ send.recipientName || send.recipientEmail }}
|
|
445
|
+
</span>
|
|
446
|
+
<DmsStatusPill
|
|
447
|
+
:tone="SEND_STATUS_TONES[send.status]"
|
|
448
|
+
:label="t(`dms_mailing.sends.status.${send.status}`)"
|
|
449
|
+
/>
|
|
450
|
+
<DmsStatusPill
|
|
451
|
+
v-if="send.isTest"
|
|
452
|
+
tone="warning"
|
|
453
|
+
dot="none"
|
|
454
|
+
size="sm"
|
|
455
|
+
uppercase
|
|
456
|
+
:label="key('test')"
|
|
457
|
+
/>
|
|
458
|
+
</h3>
|
|
459
|
+
<!-- The address gives way first: the date drops to its own line
|
|
460
|
+
rather than being cut off with it. -->
|
|
461
|
+
<p
|
|
462
|
+
class="text-muted flex flex-wrap gap-x-1.5 text-[12.5px]"
|
|
463
|
+
data-send-recipient
|
|
464
|
+
>
|
|
465
|
+
<span class="min-w-0 truncate font-mono" :title="send.recipientEmail">
|
|
466
|
+
{{ send.recipientEmail }}
|
|
467
|
+
</span>
|
|
468
|
+
<span class="whitespace-nowrap">
|
|
469
|
+
· {{ dateOf(send.createdAt, DATE_FORMAT) }}
|
|
470
|
+
</span>
|
|
471
|
+
</p>
|
|
472
|
+
</header>
|
|
473
|
+
|
|
474
|
+
<section class="flex flex-col gap-3">
|
|
475
|
+
<!-- Three short values: side by side from a 28rem group (a 600px
|
|
476
|
+
drawer) rather than the DmsStatGroup's own 36rem (dms#399). -->
|
|
477
|
+
<DmsStatGroup
|
|
478
|
+
layout="joined"
|
|
479
|
+
:columns="3"
|
|
480
|
+
columns-from="md"
|
|
481
|
+
:items="stats"
|
|
482
|
+
/>
|
|
483
|
+
<DmsBanner
|
|
484
|
+
v-if="problem"
|
|
485
|
+
:tone="isResolved ? 'info' : problemTone(problem.kind)"
|
|
486
|
+
:size="isResolved ? 'sm' : undefined"
|
|
487
|
+
:icon="problem.icon"
|
|
488
|
+
:title="t(problem.titleKey, problem.params)"
|
|
489
|
+
:description="
|
|
490
|
+
isResolved ? undefined : t(problem.descriptionKey, problem.params)
|
|
491
|
+
"
|
|
492
|
+
data-problem-banner
|
|
493
|
+
/>
|
|
494
|
+
<DmsBanner
|
|
495
|
+
v-if="suppression"
|
|
496
|
+
:tone="suppression.tone"
|
|
497
|
+
size="sm"
|
|
498
|
+
:icon="suppression.icon"
|
|
499
|
+
:title="t(suppression.titleKey, suppression.params)"
|
|
500
|
+
>
|
|
501
|
+
<template #description>
|
|
502
|
+
<span
|
|
503
|
+
class="text-highlighted block truncate font-mono text-[12px]"
|
|
504
|
+
:title="suppression.params.email"
|
|
505
|
+
data-suppressed-address
|
|
506
|
+
>
|
|
507
|
+
{{ suppression.params.email }}
|
|
508
|
+
</span>
|
|
509
|
+
{{ t(suppression.descriptionKey, suppression.params) }}
|
|
510
|
+
</template>
|
|
511
|
+
<template v-if="suppression.hasListLink" #actions>
|
|
432
512
|
<UButton
|
|
433
|
-
|
|
513
|
+
size="xs"
|
|
434
514
|
color="neutral"
|
|
435
|
-
variant="
|
|
436
|
-
|
|
437
|
-
:
|
|
438
|
-
@click="
|
|
439
|
-
/>
|
|
440
|
-
</div>
|
|
441
|
-
<h3
|
|
442
|
-
class="text-highlighted flex flex-wrap items-center gap-2 text-lg font-semibold"
|
|
443
|
-
>
|
|
444
|
-
<span class="min-w-0 truncate">
|
|
445
|
-
{{ send.recipientName || send.recipientEmail }}
|
|
446
|
-
</span>
|
|
447
|
-
<DmsStatusPill
|
|
448
|
-
:tone="SEND_STATUS_TONES[send.status]"
|
|
449
|
-
:label="t(`dms_mailing.sends.status.${send.status}`)"
|
|
515
|
+
variant="outline"
|
|
516
|
+
trailing-icon="i-ph-arrow-right"
|
|
517
|
+
:label="t('dms_mailing.suppressions.notice.open_list')"
|
|
518
|
+
@click="openSuppressionList"
|
|
450
519
|
/>
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
v-if="problem"
|
|
471
|
-
:tone="isResolved ? 'info' : problemTone(problem.kind)"
|
|
472
|
-
:size="isResolved ? 'sm' : undefined"
|
|
473
|
-
:icon="problem.icon"
|
|
474
|
-
:title="t(problem.titleKey, problem.params)"
|
|
475
|
-
:description="
|
|
476
|
-
isResolved
|
|
477
|
-
? undefined
|
|
478
|
-
: t(problem.descriptionKey, problem.params)
|
|
479
|
-
"
|
|
480
|
-
data-problem-banner
|
|
520
|
+
</template>
|
|
521
|
+
</DmsBanner>
|
|
522
|
+
<DmsBanner
|
|
523
|
+
v-for="link in linkedSends"
|
|
524
|
+
:key="link.id"
|
|
525
|
+
tone="info"
|
|
526
|
+
size="sm"
|
|
527
|
+
icon="i-ph-arrow-clockwise"
|
|
528
|
+
:title="link.text"
|
|
529
|
+
>
|
|
530
|
+
<template #actions>
|
|
531
|
+
<UButton
|
|
532
|
+
v-if="showRow"
|
|
533
|
+
size="xs"
|
|
534
|
+
color="neutral"
|
|
535
|
+
variant="outline"
|
|
536
|
+
trailing-icon="i-ph-arrow-right"
|
|
537
|
+
:label="key('see_send')"
|
|
538
|
+
@click="void showSend(link.id)"
|
|
481
539
|
/>
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
540
|
+
</template>
|
|
541
|
+
</DmsBanner>
|
|
542
|
+
</section>
|
|
543
|
+
|
|
544
|
+
<!--
|
|
545
|
+
Side by side once the drawer is resized wide:
|
|
546
|
+
both are short lists, and the data sent below keeps the full width.
|
|
547
|
+
-->
|
|
548
|
+
<div class="@2xl:grid-cols-2 grid gap-6" data-send-details-grid>
|
|
549
|
+
<section class="flex min-w-0 flex-col gap-2">
|
|
550
|
+
<DmsEyebrow :label="key('timeline')" />
|
|
551
|
+
<ol class="flex flex-col">
|
|
552
|
+
<li
|
|
553
|
+
v-for="item in timeline"
|
|
554
|
+
:key="item.id"
|
|
555
|
+
:class="item.isPending && 'opacity-50'"
|
|
488
556
|
>
|
|
489
|
-
<
|
|
557
|
+
<DmsActivityItem
|
|
558
|
+
:icon="item.icon"
|
|
559
|
+
:icon-color="item.isPending ? 'neutral' : item.tone"
|
|
560
|
+
:subtitle="
|
|
561
|
+
item.isPending ? key('pending') : item.detail || undefined
|
|
562
|
+
"
|
|
563
|
+
:mono="item.isDetailMono && !item.isPending"
|
|
564
|
+
:trailing="stepTime(item)"
|
|
565
|
+
:interactive="false"
|
|
566
|
+
wrap
|
|
567
|
+
>
|
|
490
568
|
<span
|
|
491
|
-
class="
|
|
492
|
-
|
|
493
|
-
|
|
569
|
+
:class="
|
|
570
|
+
item.tone === 'error' && !item.isPending
|
|
571
|
+
? 'text-error'
|
|
572
|
+
: undefined
|
|
573
|
+
"
|
|
494
574
|
>
|
|
495
|
-
{{
|
|
575
|
+
{{ stepTitle(item) }}
|
|
496
576
|
</span>
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
v-for="link in linkedSends"
|
|
512
|
-
:key="link.id"
|
|
513
|
-
tone="info"
|
|
577
|
+
</DmsActivityItem>
|
|
578
|
+
</li>
|
|
579
|
+
</ol>
|
|
580
|
+
</section>
|
|
581
|
+
|
|
582
|
+
<section class="flex min-w-0 flex-col gap-2">
|
|
583
|
+
<DmsEyebrow :label="key('origin')" />
|
|
584
|
+
<div
|
|
585
|
+
class="border-default divide-default divide-y overflow-hidden rounded-lg border"
|
|
586
|
+
>
|
|
587
|
+
<DmsListRow
|
|
588
|
+
:to="editorLink"
|
|
589
|
+
icon="i-ph-envelope-simple"
|
|
590
|
+
icon-size="2xs"
|
|
514
591
|
size="sm"
|
|
515
|
-
|
|
516
|
-
:title="link.text"
|
|
592
|
+
:title="originTitle"
|
|
517
593
|
>
|
|
518
|
-
<template #
|
|
519
|
-
<
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
variant="outline"
|
|
524
|
-
trailing-icon="i-ph-arrow-right"
|
|
525
|
-
:label="key('see_send')"
|
|
526
|
-
@click="void showSend(link.id)"
|
|
594
|
+
<template #trailing>
|
|
595
|
+
<UIcon
|
|
596
|
+
name="i-ph-arrow-square-out"
|
|
597
|
+
class="text-dimmed size-4"
|
|
598
|
+
aria-hidden="true"
|
|
527
599
|
/>
|
|
528
600
|
</template>
|
|
529
|
-
</
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
<
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
<DmsActivityItem
|
|
541
|
-
:icon="item.icon"
|
|
542
|
-
:icon-color="item.isPending ? 'neutral' : item.tone"
|
|
543
|
-
:subtitle="
|
|
544
|
-
item.isPending ? key('pending') : item.detail || undefined
|
|
545
|
-
"
|
|
546
|
-
:mono="item.isDetailMono && !item.isPending"
|
|
547
|
-
:trailing="stepTime(item)"
|
|
548
|
-
:interactive="false"
|
|
549
|
-
>
|
|
550
|
-
<span
|
|
551
|
-
:class="
|
|
552
|
-
item.tone === 'error' && !item.isPending
|
|
553
|
-
? 'text-error'
|
|
554
|
-
: undefined
|
|
555
|
-
"
|
|
556
|
-
>
|
|
557
|
-
{{ stepTitle(item) }}
|
|
558
|
-
</span>
|
|
559
|
-
</DmsActivityItem>
|
|
560
|
-
</li>
|
|
561
|
-
</ol>
|
|
562
|
-
</section>
|
|
563
|
-
|
|
564
|
-
<section class="flex flex-col gap-2">
|
|
565
|
-
<DmsEyebrow :label="key('origin')" />
|
|
566
|
-
<div
|
|
567
|
-
class="border-default divide-default divide-y overflow-hidden rounded-lg border"
|
|
601
|
+
</DmsListRow>
|
|
602
|
+
<!-- The "source" label goes under the value: beside it, a dotted
|
|
603
|
+
name like `automation.overdue-reminders` broke at its hyphen
|
|
604
|
+
against the label in a narrow drawer. -->
|
|
605
|
+
<DmsListRow
|
|
606
|
+
v-if="send.source"
|
|
607
|
+
icon="i-ph-code"
|
|
608
|
+
icon-size="2xs"
|
|
609
|
+
size="sm"
|
|
610
|
+
:meta="[key('source')]"
|
|
611
|
+
data-send-source
|
|
568
612
|
>
|
|
569
|
-
<
|
|
570
|
-
|
|
571
|
-
icon="i-ph-envelope-simple"
|
|
572
|
-
icon-size="2xs"
|
|
573
|
-
size="sm"
|
|
574
|
-
:title="originTitle"
|
|
575
|
-
>
|
|
576
|
-
<template #trailing>
|
|
577
|
-
<UIcon
|
|
578
|
-
name="i-ph-arrow-square-out"
|
|
579
|
-
class="text-dimmed size-4"
|
|
580
|
-
aria-hidden="true"
|
|
581
|
-
/>
|
|
582
|
-
</template>
|
|
583
|
-
</DmsListRow>
|
|
584
|
-
<DmsListRow
|
|
585
|
-
v-if="send.source"
|
|
586
|
-
icon="i-ph-code"
|
|
587
|
-
icon-size="2xs"
|
|
588
|
-
size="sm"
|
|
589
|
-
:trailing="key('source')"
|
|
613
|
+
<span
|
|
614
|
+
class="min-w-0 font-mono text-[12.5px] [overflow-wrap:anywhere]"
|
|
590
615
|
>
|
|
591
|
-
|
|
592
|
-
</
|
|
593
|
-
</
|
|
594
|
-
</
|
|
595
|
-
|
|
596
|
-
<section class="flex flex-col gap-2">
|
|
597
|
-
<div class="flex items-center justify-between gap-2">
|
|
598
|
-
<DmsEyebrow :label="key('payload')" />
|
|
599
|
-
<UButton
|
|
600
|
-
v-if="payload"
|
|
601
|
-
size="xs"
|
|
602
|
-
variant="link"
|
|
603
|
-
:label="key('copy_json')"
|
|
604
|
-
@click="copyPayload"
|
|
605
|
-
/>
|
|
606
|
-
</div>
|
|
607
|
-
<pre
|
|
608
|
-
class="bg-elevated/50 border-default text-toned max-h-72 overflow-auto rounded-lg border p-3 font-mono text-[11.5px] leading-relaxed"
|
|
609
|
-
>{{ payload || '{}' }}</pre>
|
|
610
|
-
</section>
|
|
611
|
-
</div>
|
|
616
|
+
{{ send.source }}
|
|
617
|
+
</span>
|
|
618
|
+
</DmsListRow>
|
|
619
|
+
</div>
|
|
620
|
+
</section>
|
|
612
621
|
</div>
|
|
613
622
|
|
|
614
|
-
<
|
|
615
|
-
class="
|
|
616
|
-
|
|
623
|
+
<section class="flex flex-col gap-2">
|
|
624
|
+
<div class="flex items-center justify-between gap-2">
|
|
625
|
+
<DmsEyebrow :label="key('payload')" />
|
|
626
|
+
<UButton
|
|
627
|
+
v-if="payload"
|
|
628
|
+
size="xs"
|
|
629
|
+
variant="link"
|
|
630
|
+
:label="key('copy_json')"
|
|
631
|
+
@click="copyPayload"
|
|
632
|
+
/>
|
|
633
|
+
</div>
|
|
634
|
+
<pre
|
|
635
|
+
class="bg-elevated/50 border-default text-toned max-h-72 overflow-auto rounded-lg border p-3 font-mono text-[11.5px] leading-relaxed"
|
|
636
|
+
>{{ payload || '{}' }}</pre>
|
|
637
|
+
</section>
|
|
638
|
+
|
|
639
|
+
<DmsContainerFooter data-send-footer>
|
|
617
640
|
<UButton
|
|
618
641
|
v-for="(action, index) in actions"
|
|
619
642
|
:key="action.id"
|
|
@@ -628,7 +651,7 @@ onMounted(() => {
|
|
|
628
651
|
:loading="action.id === 'send_again' && sending"
|
|
629
652
|
@click="ACTION_HANDLERS[action.id]()"
|
|
630
653
|
/>
|
|
631
|
-
</
|
|
654
|
+
</DmsContainerFooter>
|
|
632
655
|
</template>
|
|
633
656
|
</div>
|
|
634
657
|
</template>
|
|
@@ -56,7 +56,7 @@ async function createFrom(starter: StarterSummary): Promise<void> {
|
|
|
56
56
|
if (creatingId.value) return
|
|
57
57
|
creatingId.value = starter.id
|
|
58
58
|
try {
|
|
59
|
-
const
|
|
59
|
+
const { _id: id } = await api.createTemplate({
|
|
60
60
|
name: starter.name,
|
|
61
61
|
slug: starter.slug,
|
|
62
62
|
category: knownCategory(starter.category),
|
|
@@ -17,6 +17,9 @@ import type {
|
|
|
17
17
|
TemplateStats,
|
|
18
18
|
TemplateStatus,
|
|
19
19
|
} from '../types/mailing'
|
|
20
|
+
// Imported rather than resolved by name: every module component is a lazy
|
|
21
|
+
// chunk, and a card would first render without its thumbnail.
|
|
22
|
+
import TemplatePreviewFrame from './TemplatePreviewFrame.vue'
|
|
20
23
|
|
|
21
24
|
interface TemplateCardProps {
|
|
22
25
|
template: TemplateRow
|
|
@@ -172,7 +175,7 @@ function chipLabel(code: string, present: boolean): string {
|
|
|
172
175
|
class="bg-elevated/40 relative flex h-[172px] justify-center overflow-hidden bg-[radial-gradient(var(--ui-border)_1px,transparent_1px)] bg-[length:14px_14px] pt-4"
|
|
173
176
|
>
|
|
174
177
|
<div class="pointer-events-none rounded-t-md shadow-sm">
|
|
175
|
-
<
|
|
178
|
+
<TemplatePreviewFrame
|
|
176
179
|
:template-id="template._id"
|
|
177
180
|
:version="previewVersion"
|
|
178
181
|
:width="PREVIEW_WIDTH"
|
|
@@ -513,7 +513,7 @@ function onResolved(preview: PreviewResponse): void {
|
|
|
513
513
|
</script>
|
|
514
514
|
|
|
515
515
|
<template>
|
|
516
|
-
<div class="flex w-[30rem] max-w-full flex-col">
|
|
516
|
+
<div class="flex w-[30rem] max-w-full flex-1 flex-col">
|
|
517
517
|
<USkeleton v-if="loading && !template" class="h-64 w-full" />
|
|
518
518
|
|
|
519
519
|
<template v-else-if="template">
|
|
@@ -882,9 +882,7 @@ function onResolved(preview: PreviewResponse): void {
|
|
|
882
882
|
/>
|
|
883
883
|
</div>
|
|
884
884
|
|
|
885
|
-
<
|
|
886
|
-
class="border-default bg-default sticky bottom-0 -mx-1 mt-auto flex items-center gap-2 border-t px-1 py-3"
|
|
887
|
-
>
|
|
885
|
+
<DmsContainerFooter data-template-footer>
|
|
888
886
|
<UButton
|
|
889
887
|
icon="i-ph-flask"
|
|
890
888
|
color="neutral"
|
|
@@ -905,7 +903,7 @@ function onResolved(preview: PreviewResponse): void {
|
|
|
905
903
|
:label="t('dms_mailing.templates.actions.open_editor')"
|
|
906
904
|
@click="openEditor(locale)"
|
|
907
905
|
/>
|
|
908
|
-
</
|
|
906
|
+
</DmsContainerFooter>
|
|
909
907
|
</template>
|
|
910
908
|
</div>
|
|
911
909
|
</template>
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
// Imported rather than resolved by name: every module component is a lazy
|
|
3
|
+
// chunk, and the grid would first render its "new template" tile alone,
|
|
4
|
+
// then move it as the cards arrive.
|
|
5
|
+
import TemplateCard from './TemplateCard.vue'
|
|
2
6
|
import { useTemplateCategories } from '../composables/useTemplateCategories'
|
|
3
7
|
import { useEventListener } from '@vueuse/core'
|
|
4
8
|
import {
|
|
@@ -50,7 +54,8 @@ const props = defineProps<TemplateGalleryProps>()
|
|
|
50
54
|
const { t } = useI18n()
|
|
51
55
|
const overlays = useTemplateDrawer()
|
|
52
56
|
|
|
53
|
-
const { categories } =
|
|
57
|
+
const { categories, isAnswered: areCategoriesAnswered } =
|
|
58
|
+
useTemplateCategories()
|
|
54
59
|
const overview = useTemplatesOverview()
|
|
55
60
|
const total = useTemplateTotal()
|
|
56
61
|
|
|
@@ -121,8 +126,20 @@ const search = computed(() => searchTermOf(props.context.query))
|
|
|
121
126
|
const narrowed = computed(() => isNarrowed(props.context.query))
|
|
122
127
|
const hasNoItems = computed(() => !props.context.items.length)
|
|
123
128
|
|
|
129
|
+
/**
|
|
130
|
+
* The cards draw their category group and their figures from reads of their
|
|
131
|
+
* own: until both answered (a client navigation reads them in the browser),
|
|
132
|
+
* the cards would come under "Uncategorised", then move to their group.
|
|
133
|
+
*/
|
|
134
|
+
const areCardsReady = computed(
|
|
135
|
+
() =>
|
|
136
|
+
(!isGrouped.value || areCategoriesAnswered.value) &&
|
|
137
|
+
Boolean(overview.data.value || overview.error.value),
|
|
138
|
+
)
|
|
139
|
+
|
|
124
140
|
const STATE_RULES: StateRule[] = [
|
|
125
141
|
['loading', () => props.context.loading && hasNoItems.value],
|
|
142
|
+
['loading', () => !hasNoItems.value && !areCardsReady.value],
|
|
126
143
|
[
|
|
127
144
|
'loading',
|
|
128
145
|
() => hasNoItems.value && !narrowed.value && templateCount.value === null,
|
|
@@ -218,24 +235,47 @@ useEventListener(
|
|
|
218
235
|
|
|
219
236
|
<template>
|
|
220
237
|
<div class="flex flex-col gap-6 p-4">
|
|
221
|
-
|
|
238
|
+
<!-- Laid out as a group of cards: the heading, the thumbnail and the
|
|
239
|
+
card's three lines at their own heights (a zero-width space keeps a
|
|
240
|
+
text line's height), so the cards replace it without moving. -->
|
|
241
|
+
<section
|
|
222
242
|
v-if="state === 'loading'"
|
|
223
|
-
class="
|
|
243
|
+
class="flex flex-col gap-3"
|
|
224
244
|
aria-busy="true"
|
|
245
|
+
data-gallery-skeleton
|
|
225
246
|
>
|
|
226
|
-
<
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
>
|
|
232
|
-
<
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
247
|
+
<div v-if="isGrouped" class="flex items-center gap-2 text-[13px]">
|
|
248
|
+
<USkeleton class="size-4" />
|
|
249
|
+
<USkeleton class="h-3 w-24" />
|
|
250
|
+
​
|
|
251
|
+
</div>
|
|
252
|
+
<div class="grid grid-cols-[repeat(auto-fill,minmax(272px,1fr))] gap-4">
|
|
253
|
+
<DmsCard
|
|
254
|
+
v-for="index in SKELETON_CARDS"
|
|
255
|
+
:key="index"
|
|
256
|
+
:padded="false"
|
|
257
|
+
class="flex flex-col overflow-hidden"
|
|
258
|
+
>
|
|
259
|
+
<USkeleton class="h-[172px] w-full rounded-none" />
|
|
260
|
+
<div
|
|
261
|
+
class="border-default flex flex-1 flex-col gap-1.5 border-t px-3.5 pb-3 pt-3"
|
|
262
|
+
>
|
|
263
|
+
<div class="flex h-6 items-center">
|
|
264
|
+
<USkeleton class="h-3.5 w-3/5" />
|
|
265
|
+
</div>
|
|
266
|
+
<div class="flex h-[18px] items-center">
|
|
267
|
+
<USkeleton class="h-2.5 w-2/5" />
|
|
268
|
+
</div>
|
|
269
|
+
<div
|
|
270
|
+
class="border-default mt-1 flex items-center gap-3 border-t pt-2 font-mono text-[11.5px]"
|
|
271
|
+
>
|
|
272
|
+
<USkeleton class="h-2.5 w-1/2" />
|
|
273
|
+
​
|
|
274
|
+
</div>
|
|
275
|
+
</div>
|
|
276
|
+
</DmsCard>
|
|
277
|
+
</div>
|
|
278
|
+
</section>
|
|
239
279
|
|
|
240
280
|
<MailingStarterGallery
|
|
241
281
|
v-else-if="state === 'first_run'"
|
|
@@ -308,7 +348,7 @@ useEventListener(
|
|
|
308
348
|
<span class="bg-(--ui-border) ml-2 h-px flex-1" />
|
|
309
349
|
</div>
|
|
310
350
|
<div class="grid grid-cols-[repeat(auto-fill,minmax(272px,1fr))] gap-4">
|
|
311
|
-
<
|
|
351
|
+
<TemplateCard
|
|
312
352
|
v-for="template in group.items"
|
|
313
353
|
:key="template._id"
|
|
314
354
|
:template="template"
|