@antelopejs/dms-mailing 0.9.2 → 0.10.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 +11 -11
- package/dist/data/read-only-routes.d.ts +14 -1
- package/dist/data/sends-table.d.ts +14 -1
- package/dist/data/suppressions-table.d.ts +14 -1
- package/dist/data/templates-table.d.ts +15 -2
- package/dist/db/models/send-events.model.d.ts +3 -3
- package/dist/db/models/sends.model.d.ts +3 -3
- package/dist/db/models/settings.model.d.ts +3 -3
- package/dist/db/models/suppressions.model.d.ts +3 -3
- package/dist/db/models/template-versions.model.d.ts +3 -3
- package/dist/db/models/template-versions.model.js +2 -3
- package/dist/db/models/template-versions.model.js.map +1 -1
- package/dist/db/models/templates.model.d.ts +4 -3
- package/dist/db/models/templates.model.js +14 -6
- package/dist/db/models/templates.model.js.map +1 -1
- package/dist/db/models/webhook-status.model.d.ts +3 -3
- package/dist/db/tables/templates.table.d.ts +4 -1
- package/dist/hooks/tenant-export.js +5 -1
- package/dist/hooks/tenant-export.js.map +1 -1
- package/dist/pages/editor.js +1 -1
- package/dist/pages/editor.js.map +1 -1
- package/dist/routes/sends.js +2 -1
- package/dist/routes/sends.js.map +1 -1
- package/dist/routes/templates.d.ts +2 -2
- package/dist/routes/templates.js +2 -2
- package/dist/routes/templates.js.map +1 -1
- package/dist/services/settings.js +2 -1
- package/dist/services/settings.js.map +1 -1
- package/dist/services/templates.js +10 -8
- package/dist/services/templates.js.map +1 -1
- package/dist/validation/declarations.schema.d.ts +1 -1
- package/dist/validation/stored.schema.d.ts +12 -0
- package/dist/validation/stored.schema.js +198 -0
- package/dist/validation/stored.schema.js.map +1 -0
- package/frontend-vue/app/build/components/FormFieldRow.vue +1 -1
- package/frontend-vue/app/build/components/TemplateDrawerPerformance.vue +3 -3
- package/frontend-vue/app/build/components/TemplateDrawerVariables.vue +4 -4
- package/frontend-vue/app/components/CategoriesInput.vue +2 -2
- package/frontend-vue/app/components/CategoryInput.vue +1 -1
- package/frontend-vue/app/components/FunnelCard.vue +4 -4
- package/frontend-vue/app/components/LocaleSelect.vue +1 -1
- package/frontend-vue/app/components/NewTemplateModal.vue +1 -1
- package/frontend-vue/app/components/OverviewGate.vue +6 -6
- package/frontend-vue/app/components/RealSendModal.vue +6 -6
- package/frontend-vue/app/components/RenderingModal.vue +2 -2
- package/frontend-vue/app/components/RetentionInput.vue +1 -1
- package/frontend-vue/app/components/SendDrawer.vue +9 -9
- package/frontend-vue/app/components/StarterGallery.vue +2 -2
- package/frontend-vue/app/components/SystemVariables.vue +1 -1
- package/frontend-vue/app/components/TemplateCard.vue +2 -2
- package/frontend-vue/app/components/TemplateDrawer.vue +4 -4
- package/frontend-vue/app/components/TemplateGallery.vue +2 -2
- package/frontend-vue/app/components/TestSendModal.vue +4 -4
- package/frontend-vue/app/components/VersionPreview.vue +3 -3
- package/frontend-vue/app/components/editor/EditorBlockFields.vue +1 -1
- package/frontend-vue/app/components/editor/EditorCompare.vue +1 -1
- package/frontend-vue/app/components/editor/EditorEnvelope.vue +2 -2
- package/frontend-vue/app/components/editor/EditorLifecycle.vue +1 -1
- package/frontend-vue/app/components/editor/EditorPaletteGroup.vue +1 -1
- package/frontend-vue/app/components/editor/EditorPublishReview.vue +4 -4
- package/frontend-vue/app/components/editor/EditorTemplatePane.vue +2 -2
- package/frontend-vue/app/components/editor/EditorTestDataPane.vue +1 -1
- package/frontend-vue/app/components/editor/EditorVariablesPane.vue +1 -1
- package/frontend-vue/app/components/editor/EditorViewSwitches.vue +2 -2
- package/frontend-vue/app/components/editor/EditorVisibility.vue +1 -1
- package/frontend-vue/app/composables/useEditorContext.ts +1 -1
- package/frontend-vue/build/backend-boundary.ts +7 -2
- package/frontend-vue/dms.frontend.ts +1 -4
- package/frontend-vue/eslint.config.mjs +9 -1
- package/frontend-vue/package.json +2 -2
- package/frontend-vue/pnpm-lock.yaml +24 -7
- package/frontend-vue/tests/backend-boundary.test.ts +4 -4
- package/frontend-vue/tests/categories-input.test.ts +1 -1
- package/frontend-vue/tests/column-displays.test.ts +13 -12
- package/frontend-vue/tests/dms-component-names.test.ts +12 -0
- package/frontend-vue/tests/footer-block.test.ts +1 -1
- package/frontend-vue/tests/funnel-card.test.ts +3 -3
- package/frontend-vue/tests/helpers/component-tags.ts +58 -0
- package/frontend-vue/tests/helpers/nuxt-globals.ts +1 -1
- package/frontend-vue/tests/overview-gate.test.ts +1 -1
- package/frontend-vue/tests/retention-input.test.ts +2 -2
- package/frontend-vue/tests/send-drawer.test.ts +2 -2
- package/frontend-vue/tests/send-modals.test.ts +1 -1
- package/frontend-vue/tests/template-drawer.test.ts +6 -6
- package/frontend-vue/tests/template-gallery-display.test.ts +6 -5
- package/frontend-vue/tests/template-gallery.test.ts +3 -3
- package/frontend-vue/tests/template-overlays.test.ts +4 -12
- package/package.json +16 -16
- package/src/db/models/template-versions.model.ts +8 -6
- package/src/db/models/templates.model.ts +25 -9
- package/src/hooks/tenant-export.ts +5 -2
- package/src/pages/editor.ts +1 -1
- package/src/routes/sends.ts +2 -3
- package/src/routes/templates.ts +3 -2
- package/src/services/settings.ts +9 -3
- package/src/services/templates.ts +35 -16
- package/src/validation/stored.schema.ts +257 -0
- /package/frontend-vue/app/components/{Editor.vue → TemplateEditor.vue} +0 -0
- /package/frontend-vue/app/components/blocks/{Button.vue → BlockButton.vue} +0 -0
- /package/frontend-vue/app/components/blocks/{Code.vue → BlockCode.vue} +0 -0
- /package/frontend-vue/app/components/blocks/{Divider.vue → BlockDivider.vue} +0 -0
- /package/frontend-vue/app/components/blocks/{Footer.vue → BlockFooter.vue} +0 -0
- /package/frontend-vue/app/components/blocks/{Heading.vue → BlockHeading.vue} +0 -0
- /package/frontend-vue/app/components/blocks/{Hero.vue → BlockHero.vue} +0 -0
- /package/frontend-vue/app/components/blocks/{List.vue → BlockList.vue} +0 -0
- /package/frontend-vue/app/components/blocks/{Paragraph.vue → BlockParagraph.vue} +0 -0
- /package/frontend-vue/app/components/blocks/{Total.vue → BlockTotal.vue} +0 -0
|
@@ -22,8 +22,8 @@ importers:
|
|
|
22
22
|
version: 3.5.42(typescript@5.9.3)
|
|
23
23
|
devDependencies:
|
|
24
24
|
'@antelopejs/tooling-configs':
|
|
25
|
-
specifier:
|
|
26
|
-
version: 0.
|
|
25
|
+
specifier: '>=0.7.1 <0.8.0'
|
|
26
|
+
version: 0.7.1
|
|
27
27
|
'@nuxt/ui':
|
|
28
28
|
specifier: 4.10.0
|
|
29
29
|
version: 4.10.0(9bdbb06d8a0b4accef61070adc217622)
|
|
@@ -70,8 +70,8 @@ packages:
|
|
|
70
70
|
resolution: {integrity: sha512-U4+70Pc5ZS9osnCBCE5Jha/ciHM+Yp+CNMNC/7HvYbNRk1Ldd+f7qO65W5qfhu/TCv+/ozljlXXe9Nj8419DMA==}
|
|
71
71
|
engines: {node: '>=10'}
|
|
72
72
|
|
|
73
|
-
'@antelopejs/tooling-configs@0.
|
|
74
|
-
resolution: {integrity: sha512-
|
|
73
|
+
'@antelopejs/tooling-configs@0.7.1':
|
|
74
|
+
resolution: {integrity: sha512-X/mr/nQEgmFI2dMr3ThMS7HcPaU1amsTU/RNfEnQv46+l7au8cv7unpypCZ0BggnfJHjpk2QG2CTOOllpwj3cw==}
|
|
75
75
|
engines: {node: '>=22.18.0'}
|
|
76
76
|
hasBin: true
|
|
77
77
|
peerDependencies:
|
|
@@ -79,7 +79,7 @@ packages:
|
|
|
79
79
|
eslint-plugin-perfectionist: '>=5.0.0'
|
|
80
80
|
knip: '>=6.0.0'
|
|
81
81
|
mongodb-memory-server: '>=10.0.0'
|
|
82
|
-
oxfmt: ^0.66.0
|
|
82
|
+
oxfmt: ^0.66.0 || ^0.77.0
|
|
83
83
|
oxlint: 1.81.0
|
|
84
84
|
pixelmatch: '>=7.0.0'
|
|
85
85
|
playwright-core: '>=1.56.0'
|
|
@@ -1496,6 +1496,10 @@ packages:
|
|
|
1496
1496
|
resolution: {integrity: sha512-BoC8PiO4Hkdo0TVJh9Ntxr5MxPDI7/oFsrygN5ADelFSeXG/qgNuucIGA+L5Z6JpPTE/uRfcTWtscjbUaufepQ==}
|
|
1497
1497
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
|
1498
1498
|
|
|
1499
|
+
'@typescript/typescript6@6.0.2':
|
|
1500
|
+
resolution: {integrity: sha512-mbCddXd+jm7hfx7w2YU64/Av4/NqqeG3GoRZgxPcgoTxYjhrcfJRw9ULch71SS4G+Q3bOXFhRvPqjguN0Hyp5w==}
|
|
1501
|
+
hasBin: true
|
|
1502
|
+
|
|
1499
1503
|
'@unhead/vue@2.1.17':
|
|
1500
1504
|
resolution: {integrity: sha512-pnC8x9HLV3qQXdvWfylUEU25uhfCAy3ly9nmpQz84j9py818DRfU8jOsQ5wjdWtxyU1vX/fW2udfm4jtxUK8Bg==}
|
|
1501
1505
|
peerDependencies:
|
|
@@ -2967,6 +2971,11 @@ packages:
|
|
|
2967
2971
|
engines: {node: '>=14.17'}
|
|
2968
2972
|
hasBin: true
|
|
2969
2973
|
|
|
2974
|
+
typescript@6.0.3:
|
|
2975
|
+
resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==}
|
|
2976
|
+
engines: {node: '>=14.17'}
|
|
2977
|
+
hasBin: true
|
|
2978
|
+
|
|
2970
2979
|
ufo@1.6.4:
|
|
2971
2980
|
resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==}
|
|
2972
2981
|
|
|
@@ -3390,11 +3399,13 @@ snapshots:
|
|
|
3390
3399
|
|
|
3391
3400
|
'@alloc/quick-lru@5.3.0': {}
|
|
3392
3401
|
|
|
3393
|
-
'@antelopejs/tooling-configs@0.
|
|
3402
|
+
'@antelopejs/tooling-configs@0.7.1':
|
|
3394
3403
|
dependencies:
|
|
3395
3404
|
'@oxlint/plugins': 1.81.0
|
|
3405
|
+
'@typescript/typescript6': 6.0.2
|
|
3396
3406
|
dts-bundle-generator: 9.5.1
|
|
3397
|
-
|
|
3407
|
+
minimatch: 10.2.6
|
|
3408
|
+
semver: 7.8.5
|
|
3398
3409
|
yaml: 2.9.1
|
|
3399
3410
|
|
|
3400
3411
|
'@antfu/install-pkg@2.0.1':
|
|
@@ -4667,6 +4678,10 @@ snapshots:
|
|
|
4667
4678
|
'@typescript-eslint/types': 8.70.0
|
|
4668
4679
|
eslint-visitor-keys: 5.0.1
|
|
4669
4680
|
|
|
4681
|
+
'@typescript/typescript6@6.0.2':
|
|
4682
|
+
dependencies:
|
|
4683
|
+
'@typescript/old': typescript@6.0.3
|
|
4684
|
+
|
|
4670
4685
|
'@unhead/vue@2.1.17(vue@3.5.42(typescript@5.9.3))':
|
|
4671
4686
|
dependencies:
|
|
4672
4687
|
hookable: 6.1.1
|
|
@@ -6170,6 +6185,8 @@ snapshots:
|
|
|
6170
6185
|
|
|
6171
6186
|
typescript@5.9.3: {}
|
|
6172
6187
|
|
|
6188
|
+
typescript@6.0.3: {}
|
|
6189
|
+
|
|
6173
6190
|
ufo@1.6.4: {}
|
|
6174
6191
|
|
|
6175
6192
|
uncrypto@0.1.3: {}
|
|
@@ -7,8 +7,8 @@ import {
|
|
|
7
7
|
writeFileSync,
|
|
8
8
|
} from 'node:fs'
|
|
9
9
|
import { tmpdir } from 'node:os'
|
|
10
|
-
import { dirname, join } from 'node:path'
|
|
11
|
-
import { build as viteBuild, type Plugin } from 'vite'
|
|
10
|
+
import { dirname, join } from 'node:path/posix'
|
|
11
|
+
import { normalizePath, build as viteBuild, type Plugin } from 'vite'
|
|
12
12
|
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
|
|
13
13
|
import { backendBoundary } from '../build/backend-boundary'
|
|
14
14
|
|
|
@@ -22,8 +22,8 @@ function write(file: string, text: string): void {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
beforeAll(() => {
|
|
25
|
-
repository =
|
|
26
|
-
mkdtempSync(
|
|
25
|
+
repository = normalizePath(
|
|
26
|
+
realpathSync(mkdtempSync(`${tmpdir()}/dms-mailing-boundary-`)),
|
|
27
27
|
)
|
|
28
28
|
const backend = join(repository, 'packages/dms-mailing')
|
|
29
29
|
frontend = join(backend, 'frontend-vue')
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
// @vitest-environment node
|
|
2
2
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
3
3
|
import type { VNode } from 'vue'
|
|
4
|
+
import CategoryLabel from '../app/components/CategoryLabel.vue'
|
|
5
|
+
import setup, {
|
|
6
|
+
renderCategory,
|
|
7
|
+
renderLocaleChips,
|
|
8
|
+
} from '../app/plugins/column-displays'
|
|
4
9
|
|
|
5
10
|
interface RegisteredType {
|
|
6
11
|
id: string
|
|
@@ -11,9 +16,12 @@ type PluginSetup = () => void
|
|
|
11
16
|
|
|
12
17
|
const registered: RegisteredType[] = []
|
|
13
18
|
|
|
19
|
+
vi.hoisted(() => {
|
|
20
|
+
vi.stubGlobal('defineDmsPlugin', (pluginSetup: PluginSetup) => pluginSetup)
|
|
21
|
+
})
|
|
22
|
+
|
|
14
23
|
beforeEach(() => {
|
|
15
24
|
registered.length = 0
|
|
16
|
-
vi.stubGlobal('defineDmsPlugin', (setup: PluginSetup) => setup)
|
|
17
25
|
vi.stubGlobal('useDataTypes', () => ({
|
|
18
26
|
registerDataType: (type: RegisteredType) => registered.push(type),
|
|
19
27
|
}))
|
|
@@ -24,7 +32,6 @@ beforeEach(() => {
|
|
|
24
32
|
|
|
25
33
|
afterEach(() => {
|
|
26
34
|
vi.unstubAllGlobals()
|
|
27
|
-
vi.resetModules()
|
|
28
35
|
})
|
|
29
36
|
|
|
30
37
|
function texts(node: VNode): string[] {
|
|
@@ -36,8 +43,7 @@ function texts(node: VNode): string[] {
|
|
|
36
43
|
}
|
|
37
44
|
|
|
38
45
|
describe('column displays plugin', () => {
|
|
39
|
-
it('registers the locale chips and category displays without a DOM',
|
|
40
|
-
const { default: setup } = await import('../app/plugins/column-displays')
|
|
46
|
+
it('registers the locale chips and category displays without a DOM', () => {
|
|
41
47
|
;(setup as unknown as PluginSetup)()
|
|
42
48
|
expect(registered.map((type) => type.id)).toEqual([
|
|
43
49
|
'mailing:locale-chips',
|
|
@@ -45,18 +51,14 @@ describe('column displays plugin', () => {
|
|
|
45
51
|
])
|
|
46
52
|
})
|
|
47
53
|
|
|
48
|
-
it('draws a category through its label component',
|
|
49
|
-
const { renderCategory } = await import('../app/plugins/column-displays')
|
|
50
|
-
const { default: CategoryLabel } =
|
|
51
|
-
await import('../app/components/CategoryLabel.vue')
|
|
54
|
+
it('draws a category through its label component', () => {
|
|
52
55
|
const cell = renderCategory('orders')
|
|
53
56
|
expect(cell.type).toBe(CategoryLabel)
|
|
54
57
|
expect(cell.props).toEqual({ value: 'orders' })
|
|
55
58
|
expect(renderCategory(undefined).props).toEqual({ value: null })
|
|
56
59
|
})
|
|
57
60
|
|
|
58
|
-
it('draws the locale chips with the missing workspace ones dashed',
|
|
59
|
-
const { default: setup } = await import('../app/plugins/column-displays')
|
|
61
|
+
it('draws the locale chips with the missing workspace ones dashed', () => {
|
|
60
62
|
;(setup as unknown as PluginSetup)()
|
|
61
63
|
const cell = registered[0]!.formatter.default('en,fr', 'en')
|
|
62
64
|
const chips = cell.children as VNode[]
|
|
@@ -65,8 +67,7 @@ describe('column displays plugin', () => {
|
|
|
65
67
|
expect(String(chips[0]!.props?.class)).not.toContain('border-dashed')
|
|
66
68
|
})
|
|
67
69
|
|
|
68
|
-
it('draws a dash for a row without locale information',
|
|
69
|
-
const { renderLocaleChips } = await import('../app/plugins/column-displays')
|
|
70
|
+
it('draws a dash for a row without locale information', () => {
|
|
70
71
|
expect(texts(renderLocaleChips('', ['en']))).toEqual(['—'])
|
|
71
72
|
expect(texts(renderLocaleChips(undefined, ['en']))).toEqual(['—'])
|
|
72
73
|
})
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { dmsTags, knownComponents } from './helpers/component-tags'
|
|
3
|
+
|
|
4
|
+
describe('the DMS components the layer renders', () => {
|
|
5
|
+
it('names only components the installed DMS or this layer registers', async () => {
|
|
6
|
+
const known = await knownComponents()
|
|
7
|
+
const unknown = (await dmsTags()).filter(
|
|
8
|
+
(use) => !known.has(use.split(': ')[1] ?? ''),
|
|
9
|
+
)
|
|
10
|
+
expect(unknown).toEqual([])
|
|
11
|
+
})
|
|
12
|
+
})
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @vitest-environment jsdom
|
|
2
2
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
3
3
|
import { defineComponent, h, provide, ref } from 'vue'
|
|
4
|
-
import FooterBlockView from '../app/components/blocks/
|
|
4
|
+
import FooterBlockView from '../app/components/blocks/BlockFooter.vue'
|
|
5
5
|
import type { FooterBlock } from '../app/types/mailing'
|
|
6
6
|
import { TOKEN_DATA_KEY } from '../app/utils/tokens'
|
|
7
7
|
import type { TokenData } from '../app/utils/tokens'
|
|
@@ -24,9 +24,9 @@ const EmptyStateStub = defineComponent({
|
|
|
24
24
|
})
|
|
25
25
|
|
|
26
26
|
const GLOBALS = {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
DmsCardPanel: slotStub('DmsCardPanel'),
|
|
28
|
+
DmsSegmentedControl: slotStub('DmsSegmentedControl'),
|
|
29
|
+
DmsMeterBar: slotStub('DmsMeterBar'),
|
|
30
30
|
DmsEmptyState: EmptyStateStub,
|
|
31
31
|
USkeleton: slotStub('USkeleton'),
|
|
32
32
|
UIcon: slotStub('UIcon'),
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { readdir, readFile } from 'node:fs/promises'
|
|
2
|
+
import { basename, join, sep } from 'node:path'
|
|
3
|
+
import { fileURLToPath } from 'node:url'
|
|
4
|
+
|
|
5
|
+
const LAYER_ROOT = fileURLToPath(new URL('../../', import.meta.url))
|
|
6
|
+
const DMS_LAYERS = fileURLToPath(
|
|
7
|
+
new URL(
|
|
8
|
+
'../../../node_modules/@antelopejs/dms/frontend-vue/layers',
|
|
9
|
+
import.meta.url,
|
|
10
|
+
),
|
|
11
|
+
)
|
|
12
|
+
const DMS_TAG = /<\/?(Dms[A-Z][\w]*|dms-[a-z][\w-]*)(?=[\s/>])/g
|
|
13
|
+
|
|
14
|
+
async function vueFiles(root: string): Promise<string[]> {
|
|
15
|
+
const paths = await readdir(root, { recursive: true, encoding: 'utf8' })
|
|
16
|
+
return paths
|
|
17
|
+
.filter((path) => path.endsWith('.vue') && !path.includes('node_modules'))
|
|
18
|
+
.map((path) => join(root, path))
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function pascal(tag: string): string {
|
|
22
|
+
return tag
|
|
23
|
+
.split('-')
|
|
24
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
25
|
+
.join('')
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Names a component directory registers under `prefix`: the prefix and each file's base name, as `dms.frontend.ts` registers them. */
|
|
29
|
+
function registered(prefix: string, files: string[]): string[] {
|
|
30
|
+
return files.map((file) => `${prefix}${basename(file, '.vue')}`)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Every component the installed DMS layers and this layer's `app/components` register. */
|
|
34
|
+
export async function knownComponents(): Promise<Set<string>> {
|
|
35
|
+
const dms = await vueFiles(DMS_LAYERS)
|
|
36
|
+
const own = await vueFiles(join(LAYER_ROOT, 'app', 'components'))
|
|
37
|
+
return new Set([
|
|
38
|
+
...registered(
|
|
39
|
+
'Dms',
|
|
40
|
+
dms.filter((file) => file.includes(`${sep}app${sep}components${sep}`)),
|
|
41
|
+
),
|
|
42
|
+
...registered('Mailing', own),
|
|
43
|
+
])
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Every `Dms*` or `dms-*` tag of this layer's `.vue` files, as `file: Name`, in Pascal case. */
|
|
47
|
+
export async function dmsTags(): Promise<string[]> {
|
|
48
|
+
const files = await vueFiles(join(LAYER_ROOT, 'app'))
|
|
49
|
+
const sources = await Promise.all(
|
|
50
|
+
files.map(async (file) => ({ file, text: await readFile(file, 'utf8') })),
|
|
51
|
+
)
|
|
52
|
+
return sources.flatMap(({ file, text }) =>
|
|
53
|
+
Array.from(
|
|
54
|
+
text.matchAll(DMS_TAG),
|
|
55
|
+
(match) => `${file.slice(LAYER_ROOT.length)}: ${pascal(match[1] ?? '')}`,
|
|
56
|
+
),
|
|
57
|
+
)
|
|
58
|
+
}
|
|
@@ -231,7 +231,7 @@ export const FORM_STUBS: Record<string, Component> = {
|
|
|
231
231
|
DmsInputText: InputStub,
|
|
232
232
|
DmsInputEmail: InputStub,
|
|
233
233
|
UCheckbox: CheckboxStub,
|
|
234
|
-
|
|
234
|
+
DmsInputCheckbox: CheckboxStub,
|
|
235
235
|
DmsInputTags: TagsStub,
|
|
236
236
|
UFormField: slotStub('UFormField'),
|
|
237
237
|
DmsFieldRow: slotStub('DmsFieldRow'),
|
|
@@ -56,7 +56,7 @@ function mountRetention(): Harness {
|
|
|
56
56
|
'onUpdate:modelValue': (value: number) => (state.days = value),
|
|
57
57
|
}),
|
|
58
58
|
})
|
|
59
|
-
app.component('
|
|
59
|
+
app.component('DmsSegmentedControl', slotStub('DmsSegmentedControl'))
|
|
60
60
|
app.component('UInputNumber', slotStub('UInputNumber'))
|
|
61
61
|
app.component('UAlert', slotStub('UAlert'))
|
|
62
62
|
app.config.warnHandler = () => undefined
|
|
@@ -65,7 +65,7 @@ function mountRetention(): Harness {
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
function segmented(host: HTMLElement): HTMLElement {
|
|
68
|
-
return host.querySelector('[data-stub="
|
|
68
|
+
return host.querySelector('[data-stub="DmsSegmentedControl"]') as HTMLElement
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
describe('RetentionInput', () => {
|
|
@@ -97,9 +97,9 @@ const DropdownStub = {
|
|
|
97
97
|
const globals = {
|
|
98
98
|
...FORM_STUBS,
|
|
99
99
|
UDropdownMenu: DropdownStub,
|
|
100
|
-
|
|
100
|
+
DmsNoticeBanner: slotStub('DmsNoticeBanner'),
|
|
101
101
|
MailingTemplatePreviewFrame: slotStub('MailingTemplatePreviewFrame'),
|
|
102
|
-
|
|
102
|
+
DmsCardPanel: slotStub('DmsCardPanel'),
|
|
103
103
|
DmsEmptyState: slotStub('DmsEmptyState'),
|
|
104
104
|
}
|
|
105
105
|
|
|
@@ -271,7 +271,7 @@ describe('TemplateDrawer', () => {
|
|
|
271
271
|
)
|
|
272
272
|
expect(buttons).not.toContain('dms_mailing.templates.drawer.declare')
|
|
273
273
|
const banners = [
|
|
274
|
-
...view.host.querySelectorAll('[data-stub="
|
|
274
|
+
...view.host.querySelectorAll('[data-stub="DmsNoticeBanner"]'),
|
|
275
275
|
].map((banner) => banner.getAttribute('title'))
|
|
276
276
|
expect(banners).toContain('dms_mailing.templates.drawer.draft_declarations')
|
|
277
277
|
view.unmount()
|
|
@@ -362,7 +362,7 @@ describe('TemplateDrawer', () => {
|
|
|
362
362
|
api.templateContent.mockResolvedValue(detail(row('live')))
|
|
363
363
|
const view = mount(TemplateDrawer, { templateId: 't1' }, globals)
|
|
364
364
|
await flush()
|
|
365
|
-
const segmented = view.host.querySelector('
|
|
365
|
+
const segmented = view.host.querySelector('dmssegmentedcontrol')
|
|
366
366
|
expect(segmented).not.toBeNull()
|
|
367
367
|
expect(
|
|
368
368
|
view.host.querySelectorAll('dmslistrow')[0]?.getAttribute('title'),
|
|
@@ -446,7 +446,7 @@ describe('StarterGallery', () => {
|
|
|
446
446
|
api.createTemplate.mockResolvedValue({ _id: 'new-id' })
|
|
447
447
|
const view = mount(StarterGallery, {}, globals)
|
|
448
448
|
await flush()
|
|
449
|
-
;(view.host.querySelector('[data-stub="
|
|
449
|
+
;(view.host.querySelector('[data-stub="DmsCardPanel"]') as HTMLElement).click()
|
|
450
450
|
await flush()
|
|
451
451
|
expect(api.createTemplate).toHaveBeenCalledWith({
|
|
452
452
|
name: 'Order confirmation',
|
|
@@ -464,7 +464,7 @@ describe('StarterGallery', () => {
|
|
|
464
464
|
api.createTemplate.mockRejectedValue({ statusCode: 409 })
|
|
465
465
|
const view = mount(StarterGallery, {}, globals)
|
|
466
466
|
await flush()
|
|
467
|
-
;(view.host.querySelector('[data-stub="
|
|
467
|
+
;(view.host.querySelector('[data-stub="DmsCardPanel"]') as HTMLElement).click()
|
|
468
468
|
await flush()
|
|
469
469
|
expect(stubs.modalCalls[0]?.componentOptions).toEqual({
|
|
470
470
|
initialName: 'Order confirmation',
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// @vitest-environment node
|
|
2
2
|
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
3
3
|
import type { Component } from 'vue'
|
|
4
|
+
import setup from '../app/plugins/template-gallery-display'
|
|
4
5
|
|
|
5
6
|
interface DisplayRegistration {
|
|
6
7
|
id: string
|
|
@@ -9,21 +10,21 @@ interface DisplayRegistration {
|
|
|
9
10
|
|
|
10
11
|
type PluginSetup = () => void
|
|
11
12
|
|
|
13
|
+
vi.hoisted(() => {
|
|
14
|
+
vi.stubGlobal('defineDmsPlugin', (pluginSetup: PluginSetup) => pluginSetup)
|
|
15
|
+
})
|
|
16
|
+
|
|
12
17
|
afterEach(() => {
|
|
13
18
|
vi.unstubAllGlobals()
|
|
14
|
-
vi.resetModules()
|
|
15
19
|
})
|
|
16
20
|
|
|
17
21
|
describe('template gallery display plugin', () => {
|
|
18
|
-
it('registers the gallery under the mailing namespace without a DOM',
|
|
22
|
+
it('registers the gallery under the mailing namespace without a DOM', () => {
|
|
19
23
|
const registrations: DisplayRegistration[] = []
|
|
20
|
-
vi.stubGlobal('defineDmsPlugin', (setup: PluginSetup) => setup)
|
|
21
24
|
vi.stubGlobal(
|
|
22
25
|
'registerTableViewDisplay',
|
|
23
26
|
(registration: DisplayRegistration) => registrations.push(registration),
|
|
24
27
|
)
|
|
25
|
-
const { default: setup } =
|
|
26
|
-
await import('../app/plugins/template-gallery-display')
|
|
27
28
|
setup()
|
|
28
29
|
expect(registrations.map((registration) => registration.id)).toEqual([
|
|
29
30
|
'mailing:gallery',
|
|
@@ -361,7 +361,7 @@ describe('TemplateGallery', () => {
|
|
|
361
361
|
const ctx = context({ items: [LIVE] })
|
|
362
362
|
const view = mount(TemplateGallery, { context: ctx }, globals)
|
|
363
363
|
await flush()
|
|
364
|
-
const card = view.host.querySelector('
|
|
364
|
+
const card = view.host.querySelector('dmscardpanel') as HTMLElement
|
|
365
365
|
card.dispatchEvent(
|
|
366
366
|
new KeyboardEvent('keydown', { key: 't', bubbles: true }),
|
|
367
367
|
)
|
|
@@ -426,7 +426,7 @@ describe('TemplateGallery', () => {
|
|
|
426
426
|
const skeleton = loading.host.querySelector(
|
|
427
427
|
'[data-gallery-skeleton]',
|
|
428
428
|
) as HTMLElement
|
|
429
|
-
const skeletonCard = skeleton.querySelector('
|
|
429
|
+
const skeletonCard = skeleton.querySelector('dmscardpanel') as HTMLElement
|
|
430
430
|
const skeletonHeading = skeleton.firstElementChild as HTMLElement
|
|
431
431
|
const skeletonBody = skeletonCard.lastElementChild as HTMLElement
|
|
432
432
|
|
|
@@ -443,7 +443,7 @@ describe('TemplateGallery', () => {
|
|
|
443
443
|
await flush()
|
|
444
444
|
expect(loaded.host.querySelector('[data-gallery-skeleton]')).toBeNull()
|
|
445
445
|
const section = loaded.host.querySelector('section') as HTMLElement
|
|
446
|
-
const card = section.querySelector('article,
|
|
446
|
+
const card = section.querySelector('article, dmscardpanel') as HTMLElement
|
|
447
447
|
const body = card.lastElementChild as HTMLElement
|
|
448
448
|
|
|
449
449
|
expect(skeleton.className).toBe(section.className)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// @vitest-environment node
|
|
2
2
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
3
|
+
import { useTemplateDrawer } from '../app/composables/useTemplateDrawer'
|
|
3
4
|
import type { TemplateRow } from '../app/types/mailing'
|
|
4
5
|
|
|
5
6
|
interface OpenCall {
|
|
@@ -53,7 +54,6 @@ beforeEach(() => {
|
|
|
53
54
|
|
|
54
55
|
afterEach(() => {
|
|
55
56
|
vi.unstubAllGlobals()
|
|
56
|
-
vi.resetModules()
|
|
57
57
|
})
|
|
58
58
|
|
|
59
59
|
function row(id: string): TemplateRow {
|
|
@@ -68,9 +68,7 @@ function row(id: string): TemplateRow {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
describe('useTemplateDrawer', () => {
|
|
71
|
-
it('opens the test send with the editor draft and locale',
|
|
72
|
-
const { useTemplateDrawer } =
|
|
73
|
-
await import('../app/composables/useTemplateDrawer')
|
|
71
|
+
it('opens the test send with the editor draft and locale', () => {
|
|
74
72
|
const content = { locales: {} }
|
|
75
73
|
useTemplateDrawer().openTestSend('t1', { locale: 'fr', content })
|
|
76
74
|
expect(modalCalls[0]?.component).toEqual({ name: 'MailingTestSendModal' })
|
|
@@ -82,8 +80,6 @@ describe('useTemplateDrawer', () => {
|
|
|
82
80
|
})
|
|
83
81
|
|
|
84
82
|
it('calls back only when the dialog closes after a change', async () => {
|
|
85
|
-
const { useTemplateDrawer } =
|
|
86
|
-
await import('../app/composables/useTemplateDrawer')
|
|
87
83
|
const onDone = vi.fn()
|
|
88
84
|
useTemplateDrawer().openRealSend('t1', { onDone })
|
|
89
85
|
await Promise.resolve()
|
|
@@ -97,9 +93,7 @@ describe('useTemplateDrawer', () => {
|
|
|
97
93
|
expect(modalCalls[1]?.componentOptions).toEqual({ initialName: 'Refund' })
|
|
98
94
|
})
|
|
99
95
|
|
|
100
|
-
it('opens the details from the right and steps through the rows shown',
|
|
101
|
-
const { useTemplateDrawer } =
|
|
102
|
-
await import('../app/composables/useTemplateDrawer')
|
|
96
|
+
it('opens the details from the right and steps through the rows shown', () => {
|
|
103
97
|
const rows = [row('a'), row('b'), row('c')]
|
|
104
98
|
useTemplateDrawer().openDetails(rows[1]!, { rows })
|
|
105
99
|
const call = drawerCalls[0]!
|
|
@@ -115,9 +109,7 @@ describe('useTemplateDrawer', () => {
|
|
|
115
109
|
expect((options.navigation as Navigation).hasNext).toBe(false)
|
|
116
110
|
})
|
|
117
111
|
|
|
118
|
-
it('opens the details of a bare id without navigation',
|
|
119
|
-
const { useTemplateDrawer } =
|
|
120
|
-
await import('../app/composables/useTemplateDrawer')
|
|
112
|
+
it('opens the details of a bare id without navigation', () => {
|
|
121
113
|
useTemplateDrawer().openDetails('x')
|
|
122
114
|
expect(drawerCalls[0]?.componentOptions.templateId).toBe('x')
|
|
123
115
|
expect(drawerCalls[0]?.componentOptions.navigation).toBeUndefined()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@antelopejs/dms-mailing",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.1",
|
|
4
4
|
"description": "E-mail templates for the AntelopeJS DMS: a block editor with drafts and versions, a send log, delivery metrics and a suppression list.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"antelopejs",
|
|
@@ -46,23 +46,23 @@
|
|
|
46
46
|
"registry": "https://registry.npmjs.org/"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@antelopejs/interface-api": ">=0.3.
|
|
50
|
-
"@antelopejs/interface-api-util": ">=0.3.
|
|
51
|
-
"@antelopejs/interface-core": ">=0.
|
|
52
|
-
"@antelopejs/interface-data-api": ">=0.
|
|
53
|
-
"@antelopejs/interface-database": ">=0.
|
|
54
|
-
"@antelopejs/interface-database-decorators": ">=0.
|
|
55
|
-
"@antelopejs/interface-dms": ">=0.
|
|
56
|
-
"@antelopejs/interface-dms-mailing": ">=0.
|
|
57
|
-
"@antelopejs/interface-email": ">=0.0
|
|
49
|
+
"@antelopejs/interface-api": ">=0.3.3 <0.4.0",
|
|
50
|
+
"@antelopejs/interface-api-util": ">=0.3.2 <0.4.0",
|
|
51
|
+
"@antelopejs/interface-core": ">=0.5.0 <0.6.0",
|
|
52
|
+
"@antelopejs/interface-data-api": ">=0.8.0 <0.9.0",
|
|
53
|
+
"@antelopejs/interface-database": ">=0.4.0 <0.5.0",
|
|
54
|
+
"@antelopejs/interface-database-decorators": ">=0.5.0 <0.6.0",
|
|
55
|
+
"@antelopejs/interface-dms": ">=0.14.0 <0.15.0",
|
|
56
|
+
"@antelopejs/interface-dms-mailing": ">=0.11.0 <0.12.0",
|
|
57
|
+
"@antelopejs/interface-email": ">=0.2.0 <0.3.0",
|
|
58
58
|
"node-cron": "^4.2.1",
|
|
59
|
-
"zod": "
|
|
59
|
+
"zod": "^3.24.2"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
|
-
"@antelopejs/core": "
|
|
63
|
-
"@antelopejs/dms": ">=0.
|
|
64
|
-
"@antelopejs/mongodb": "
|
|
65
|
-
"@antelopejs/tooling-configs": "
|
|
62
|
+
"@antelopejs/core": "~1.23.2",
|
|
63
|
+
"@antelopejs/dms": ">=0.20.0 <0.21.0",
|
|
64
|
+
"@antelopejs/mongodb": "~1.8.1",
|
|
65
|
+
"@antelopejs/tooling-configs": ">=0.7.1 <0.8.0",
|
|
66
66
|
"@types/chai": "^4.3.20",
|
|
67
67
|
"@types/mocha": "^10",
|
|
68
68
|
"@types/node": "^24.0.0",
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
"typescript": "^6.0.3"
|
|
78
78
|
},
|
|
79
79
|
"optionalDependencies": {
|
|
80
|
-
"@antelopejs/interface-dms-automation": ">=0.6.0 <
|
|
80
|
+
"@antelopejs/interface-dms-automation": ">=0.6.0 <0.7.0"
|
|
81
81
|
},
|
|
82
82
|
"antelopeJs": {
|
|
83
83
|
"baseUrl": "dist",
|
|
@@ -4,6 +4,10 @@ import {
|
|
|
4
4
|
type MailingTemplate,
|
|
5
5
|
MailingTemplateVersion,
|
|
6
6
|
} from "../tables";
|
|
7
|
+
import {
|
|
8
|
+
parseStored,
|
|
9
|
+
storedVersionRowSchema,
|
|
10
|
+
} from "../../validation/stored.schema";
|
|
7
11
|
import { TemplateModel } from "./templates.model";
|
|
8
12
|
|
|
9
13
|
/** The latest committed history row, even before its separate insert completes. */
|
|
@@ -11,13 +15,11 @@ export function committedVersionOf(
|
|
|
11
15
|
template: Pick<MailingTemplate, "json_published_version">,
|
|
12
16
|
): MailingTemplateVersion | undefined {
|
|
13
17
|
if (!template.json_published_version) return undefined;
|
|
14
|
-
|
|
15
|
-
// JSON.stringify of the version row they insert; its publishedAt reads back as a string, and the next line restores the Date.
|
|
16
|
-
const row = JSON.parse(
|
|
18
|
+
return parseStored(
|
|
17
19
|
template.json_published_version,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
storedVersionRowSchema,
|
|
21
|
+
"a version row",
|
|
22
|
+
);
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
export class TemplateVersionModel extends BasicDataModel(
|