@antelopejs/dms-mailing 0.5.0 → 0.6.0
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 +61 -339
- package/dist/data/templates-table.js +2 -10
- package/dist/data/templates-table.js.map +1 -1
- package/dist/db/models/index.d.ts +1 -0
- package/dist/db/models/index.js +1 -0
- package/dist/db/models/index.js.map +1 -1
- package/dist/db/models/send-events.model.d.ts +2 -0
- package/dist/db/models/send-events.model.js +11 -0
- package/dist/db/models/send-events.model.js.map +1 -1
- package/dist/db/models/sends.model.d.ts +3 -0
- package/dist/db/models/sends.model.js +13 -0
- package/dist/db/models/sends.model.js.map +1 -1
- package/dist/db/models/settings.model.d.ts +1 -0
- package/dist/db/models/suppressions.model.d.ts +1 -0
- package/dist/db/models/template-slugs.model.d.ts +27 -0
- package/dist/db/models/template-slugs.model.js +43 -0
- package/dist/db/models/template-slugs.model.js.map +1 -0
- package/dist/db/models/template-versions.model.d.ts +4 -1
- package/dist/db/models/template-versions.model.js +35 -2
- package/dist/db/models/template-versions.model.js.map +1 -1
- package/dist/db/models/templates.model.d.ts +2 -0
- package/dist/db/models/templates.model.js +22 -0
- package/dist/db/models/templates.model.js.map +1 -1
- package/dist/db/models/webhook-status.model.d.ts +1 -0
- package/dist/db/tables/index.d.ts +1 -0
- package/dist/db/tables/index.js +1 -0
- package/dist/db/tables/index.js.map +1 -1
- package/dist/db/tables/settings.table.d.ts +1 -0
- package/dist/db/tables/settings.table.js +4 -0
- package/dist/db/tables/settings.table.js.map +1 -1
- package/dist/db/tables/template-slugs.table.d.ts +7 -0
- package/dist/db/tables/template-slugs.table.js +35 -0
- package/dist/db/tables/template-slugs.table.js.map +1 -0
- package/dist/db/tables/templates.table.d.ts +5 -0
- package/dist/db/tables/templates.table.js +27 -0
- package/dist/db/tables/templates.table.js.map +1 -1
- package/dist/hooks/database-ready.d.ts +7 -0
- package/dist/hooks/database-ready.js +26 -0
- package/dist/hooks/database-ready.js.map +1 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/index.js.map +1 -1
- package/dist/implementations/dms-mailing/index.d.ts +3 -0
- package/dist/implementations/dms-mailing/index.js +7 -1
- package/dist/implementations/dms-mailing/index.js.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/routes/templates.js +1 -0
- package/dist/routes/templates.js.map +1 -1
- package/dist/services/declaration-error.d.ts +6 -0
- package/dist/services/declaration-error.js +26 -0
- package/dist/services/declaration-error.js.map +1 -0
- package/dist/services/declarations.d.ts +3 -0
- package/dist/services/declarations.js +244 -0
- package/dist/services/declarations.js.map +1 -0
- package/dist/services/events.d.ts +4 -1
- package/dist/services/events.js.map +1 -1
- package/dist/services/fingerprint.d.ts +1 -0
- package/dist/services/fingerprint.js +20 -0
- package/dist/services/fingerprint.js.map +1 -0
- package/dist/services/send-import.d.ts +2 -0
- package/dist/services/send-import.js +136 -0
- package/dist/services/send-import.js.map +1 -0
- package/dist/services/templates.d.ts +3 -0
- package/dist/services/templates.js +75 -48
- package/dist/services/templates.js.map +1 -1
- package/dist/validation/declarations.schema.d.ts +299 -0
- package/dist/validation/declarations.schema.js +55 -0
- package/dist/validation/declarations.schema.js.map +1 -0
- package/frontend-vue/app/components/CategoriesInput.vue +12 -11
- package/frontend-vue/app/components/editor/EditorBlockList.vue +33 -30
- package/frontend-vue/app/components/editor/EditorPaletteGroup.vue +14 -14
- package/frontend-vue/app/composables/useSortableList.ts +159 -0
- package/frontend-vue/app/utils/blocks/dnd.ts +2 -0
- package/frontend-vue/i18n/locales/mailing-en-GB.json +1 -1
- package/frontend-vue/i18n/locales/mailing-fr-FR.json +1 -1
- package/frontend-vue/package.json +4 -3
- package/frontend-vue/pnpm-lock.yaml +6 -13
- package/frontend-vue/tests/editor-block-list.test.ts +46 -6
- package/frontend-vue/tests/sortable-list.test.ts +253 -0
- package/package.json +31 -14
- package/skills/dms-mailing/SKILL.md +101 -0
- package/src/data/templates-table.ts +7 -27
- package/src/db/models/index.ts +1 -0
- package/src/db/models/send-events.model.ts +13 -0
- package/src/db/models/sends.model.ts +22 -0
- package/src/db/models/template-slugs.model.ts +61 -0
- package/src/db/models/template-versions.model.ts +48 -2
- package/src/db/models/templates.model.ts +31 -0
- package/src/db/tables/index.ts +1 -0
- package/src/db/tables/settings.table.ts +8 -0
- package/src/db/tables/template-slugs.table.ts +29 -0
- package/src/db/tables/templates.table.ts +38 -0
- package/src/hooks/database-ready.ts +47 -0
- package/src/hooks/index.ts +1 -0
- package/src/implementations/dms-mailing/index.ts +17 -0
- package/src/index.ts +6 -1
- package/src/routes/templates.ts +2 -0
- package/src/services/declaration-error.ts +39 -0
- package/src/services/declarations.ts +450 -0
- package/src/services/events.ts +8 -2
- package/src/services/fingerprint.ts +22 -0
- package/src/services/send-import.ts +235 -0
- package/src/services/templates.ts +113 -72
- package/src/validation/declarations.schema.ts +77 -0
- package/frontend-vue/pnpm-workspace.yaml +0 -2
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import
|
|
2
|
+
import { ref } from 'vue'
|
|
3
3
|
import type { Block } from '../../types/mailing'
|
|
4
4
|
import { useEditorContext } from '../../composables/useEditorContext'
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
useSortableList,
|
|
7
|
+
type SortableListChange,
|
|
8
|
+
} from '../../composables/useSortableList'
|
|
9
|
+
import { DRAG_GROUP, DRAG_HANDLE, DRAG_ITEM } from '../../utils/blocks'
|
|
6
10
|
import EditorBlock from './EditorBlock.vue'
|
|
7
11
|
import EditorInsertPoint from './EditorInsertPoint.vue'
|
|
8
12
|
|
|
@@ -12,40 +16,39 @@ interface Props {
|
|
|
12
16
|
|
|
13
17
|
const props = defineProps<Props>()
|
|
14
18
|
|
|
15
|
-
interface DraggableChange {
|
|
16
|
-
added?: { element: Block }
|
|
17
|
-
}
|
|
18
|
-
|
|
19
19
|
const { editor } = useEditorContext()
|
|
20
20
|
|
|
21
|
-
function onChange(change:
|
|
21
|
+
function onChange(change: SortableListChange<Block>): void {
|
|
22
22
|
editor.touch()
|
|
23
23
|
if (change.added) editor.select(change.added.element.id)
|
|
24
24
|
}
|
|
25
|
+
|
|
26
|
+
const root = ref<HTMLElement | null>(null)
|
|
27
|
+
|
|
28
|
+
// The list is the editor's: a drop changes it in place, as a click does.
|
|
29
|
+
useSortableList(root, () => props.list, {
|
|
30
|
+
group: DRAG_GROUP,
|
|
31
|
+
handle: DRAG_HANDLE,
|
|
32
|
+
draggable: DRAG_ITEM,
|
|
33
|
+
ghostClass: 'opacity-30',
|
|
34
|
+
onChange,
|
|
35
|
+
})
|
|
25
36
|
</script>
|
|
26
37
|
|
|
27
38
|
<template>
|
|
28
|
-
<
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
<
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
<template #footer>
|
|
44
|
-
<EditorInsertPoint
|
|
45
|
-
:list="props.list"
|
|
46
|
-
:index="props.list.length"
|
|
47
|
-
:is-empty="props.list.length === 0"
|
|
48
|
-
/>
|
|
49
|
-
</template>
|
|
50
|
-
</draggable>
|
|
39
|
+
<div ref="root" class="flex flex-col">
|
|
40
|
+
<div
|
|
41
|
+
v-for="(element, index) in props.list"
|
|
42
|
+
:key="element.id"
|
|
43
|
+
data-drag-item
|
|
44
|
+
>
|
|
45
|
+
<EditorInsertPoint :list="props.list" :index="index" />
|
|
46
|
+
<EditorBlock :block="element" :index="index" :list="props.list" />
|
|
47
|
+
</div>
|
|
48
|
+
<EditorInsertPoint
|
|
49
|
+
:list="props.list"
|
|
50
|
+
:index="props.list.length"
|
|
51
|
+
:is-empty="props.list.length === 0"
|
|
52
|
+
/>
|
|
53
|
+
</div>
|
|
51
54
|
</template>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import
|
|
2
|
+
import { ref } from 'vue'
|
|
3
|
+
import { useSortableList } from '../../composables/useSortableList'
|
|
3
4
|
import {
|
|
4
5
|
PALETTE_GROUP,
|
|
5
6
|
cloneFromPalette,
|
|
@@ -23,23 +24,22 @@ const emit = defineEmits<{ add: [PaletteEntry] }>()
|
|
|
23
24
|
|
|
24
25
|
const { t } = useI18n()
|
|
25
26
|
|
|
26
|
-
const
|
|
27
|
-
|
|
27
|
+
const root = ref<HTMLElement | null>(null)
|
|
28
|
+
|
|
29
|
+
// A tile stays in the palette: the canvas receives the block it stands for.
|
|
30
|
+
useSortableList(root, () => props.entries, {
|
|
31
|
+
group: PALETTE_GROUP,
|
|
32
|
+
sort: false,
|
|
33
|
+
disabled: () => props.disabled,
|
|
34
|
+
clone: (entry) => cloneFromPalette(entry, props.blockContext()),
|
|
35
|
+
})
|
|
28
36
|
</script>
|
|
29
37
|
|
|
30
38
|
<template>
|
|
31
39
|
<section>
|
|
32
40
|
<DmsEyebrow :label="title" class="mb-2 block" />
|
|
33
|
-
<
|
|
34
|
-
|
|
35
|
-
:group="PALETTE_GROUP"
|
|
36
|
-
:clone="clone"
|
|
37
|
-
:sort="false"
|
|
38
|
-
:disabled="disabled"
|
|
39
|
-
item-key="type"
|
|
40
|
-
class="grid grid-cols-2 gap-1.5"
|
|
41
|
-
>
|
|
42
|
-
<template #item="{ element }">
|
|
41
|
+
<div ref="root" class="grid grid-cols-2 gap-1.5">
|
|
42
|
+
<template v-for="element in entries" :key="element.type">
|
|
43
43
|
<button
|
|
44
44
|
type="button"
|
|
45
45
|
class="border-default bg-default text-toned hover:border-primary/60 hover:text-highlighted group flex min-h-9 cursor-grab items-center gap-2 rounded-md border px-2.5 py-1 text-left text-xs transition disabled:cursor-not-allowed disabled:opacity-50"
|
|
@@ -61,6 +61,6 @@ const clone = (entry: PaletteEntry) =>
|
|
|
61
61
|
</span>
|
|
62
62
|
</button>
|
|
63
63
|
</template>
|
|
64
|
-
</
|
|
64
|
+
</div>
|
|
65
65
|
</section>
|
|
66
66
|
</template>
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { useSortable } from '@vueuse/integrations/useSortable'
|
|
2
|
+
import { toValue, watch, type MaybeRefOrGetter, type Ref } from 'vue'
|
|
3
|
+
|
|
4
|
+
/** What a drop did to a list, as vuedraggable's `change` event said it. */
|
|
5
|
+
export interface SortableListChange<Item> {
|
|
6
|
+
added?: { element: Item; newIndex: number }
|
|
7
|
+
removed?: { element: Item; oldIndex: number }
|
|
8
|
+
moved?: { element: Item; oldIndex: number; newIndex: number }
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface SortableGroup {
|
|
12
|
+
name: string
|
|
13
|
+
/** `'clone'`: the list keeps its items and hands a copy to the other list. */
|
|
14
|
+
pull?: boolean | 'clone'
|
|
15
|
+
/** `false`: no item of another list lands here. */
|
|
16
|
+
put?: boolean
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface SortableListOptions<Item> {
|
|
20
|
+
/** Lists sharing a group name exchange items. */
|
|
21
|
+
group?: string | SortableGroup
|
|
22
|
+
/** Read again whenever it changes. */
|
|
23
|
+
disabled?: MaybeRefOrGetter<boolean>
|
|
24
|
+
/** Only an item grabbed by an element matching this selector moves. */
|
|
25
|
+
handle?: string
|
|
26
|
+
/**
|
|
27
|
+
* The children that are items, when the list element holds others (a
|
|
28
|
+
* footer): the indexes SortableJS reports count these only.
|
|
29
|
+
*/
|
|
30
|
+
draggable?: string
|
|
31
|
+
/** `false`: the items cannot be reordered within the list. */
|
|
32
|
+
sort?: boolean
|
|
33
|
+
/** Class of the slot the dragged item will land in. */
|
|
34
|
+
ghostClass?: string
|
|
35
|
+
/** What another list receives in place of the dragged item. */
|
|
36
|
+
clone?: (item: Item) => unknown
|
|
37
|
+
onChange?: (change: SortableListChange<Item>) => void
|
|
38
|
+
/** After every drag, whatever it changed. */
|
|
39
|
+
onEnd?: () => void
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** What the list reads of SortableJS's events. */
|
|
43
|
+
interface SortableEvent {
|
|
44
|
+
item: HTMLElement
|
|
45
|
+
clone: HTMLElement
|
|
46
|
+
oldIndex?: number
|
|
47
|
+
newIndex?: number
|
|
48
|
+
pullMode?: boolean | 'clone'
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The options the list sets. SortableJS keeps its default only for an option
|
|
53
|
+
* absent from the object: a key set to `undefined` replaces it (an undefined
|
|
54
|
+
* `draggable` makes no child an item, so nothing drags).
|
|
55
|
+
*/
|
|
56
|
+
function defined<Options extends object>(options: Options): Partial<Options> {
|
|
57
|
+
return Object.fromEntries(
|
|
58
|
+
Object.entries(options).filter(([, value]) => value !== undefined),
|
|
59
|
+
) as Partial<Options>
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The item being dragged, by group. A drop on another list only names the
|
|
64
|
+
* element, which belongs to the list it left: the list it lands in reads the
|
|
65
|
+
* item the other one stored when the drag started.
|
|
66
|
+
*/
|
|
67
|
+
const dragged = new Map<string, unknown>()
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Lets the user reorder the items of `list` rendered in `root`, or move them
|
|
71
|
+
* to another list of the same group: SortableJS, through VueUse's
|
|
72
|
+
* `useSortable`.
|
|
73
|
+
*
|
|
74
|
+
* SortableJS moves the dragged element in the DOM itself. Vue owns these
|
|
75
|
+
* elements, so each drop puts the element back where Vue rendered it and
|
|
76
|
+
* changes `list` in place instead, from which Vue renders the new order.
|
|
77
|
+
*/
|
|
78
|
+
export function useSortableList<Item>(
|
|
79
|
+
root: Ref<HTMLElement | null>,
|
|
80
|
+
list: () => Item[],
|
|
81
|
+
options: SortableListOptions<Item> = {},
|
|
82
|
+
): void {
|
|
83
|
+
const groupName =
|
|
84
|
+
typeof options.group === 'string'
|
|
85
|
+
? options.group
|
|
86
|
+
: (options.group?.name ?? '')
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Where the dragged element stood: next to the node that followed it, not
|
|
90
|
+
* at an index, as Vue removes an item by the markers around it.
|
|
91
|
+
*/
|
|
92
|
+
let origin: { parent: Node; next: Node | null } | undefined
|
|
93
|
+
|
|
94
|
+
function restore(item: HTMLElement): void {
|
|
95
|
+
if (origin) origin.parent.insertBefore(item, origin.next)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const { option } = useSortable(root, list, {
|
|
99
|
+
...defined({
|
|
100
|
+
group: options.group,
|
|
101
|
+
handle: options.handle,
|
|
102
|
+
draggable: options.draggable,
|
|
103
|
+
ghostClass: options.ghostClass,
|
|
104
|
+
}),
|
|
105
|
+
disabled: toValue(options.disabled) ?? false,
|
|
106
|
+
sort: options.sort ?? true,
|
|
107
|
+
onStart: (event: SortableEvent) => {
|
|
108
|
+
const { parentNode, nextSibling } = event.item
|
|
109
|
+
origin = parentNode
|
|
110
|
+
? { parent: parentNode, next: nextSibling }
|
|
111
|
+
: undefined
|
|
112
|
+
const element = list()[event.oldIndex ?? -1]
|
|
113
|
+
if (element === undefined) return
|
|
114
|
+
dragged.set(groupName, options.clone ? options.clone(element) : element)
|
|
115
|
+
},
|
|
116
|
+
onUpdate: (event: SortableEvent) => {
|
|
117
|
+
const { oldIndex, newIndex } = event
|
|
118
|
+
if (oldIndex === undefined || newIndex === undefined) return
|
|
119
|
+
restore(event.item)
|
|
120
|
+
const items = list()
|
|
121
|
+
const [element] = items.splice(oldIndex, 1)
|
|
122
|
+
if (element === undefined) return
|
|
123
|
+
items.splice(newIndex, 0, element)
|
|
124
|
+
options.onChange?.({ moved: { element, oldIndex, newIndex } })
|
|
125
|
+
},
|
|
126
|
+
onAdd: (event: SortableEvent) => {
|
|
127
|
+
const element = dragged.get(groupName) as Item | undefined
|
|
128
|
+
const { newIndex } = event
|
|
129
|
+
if (element === undefined || newIndex === undefined) return
|
|
130
|
+
// The element goes back to the list it left, below.
|
|
131
|
+
event.item.remove()
|
|
132
|
+
list().splice(newIndex, 0, element)
|
|
133
|
+
options.onChange?.({ added: { element, newIndex } })
|
|
134
|
+
},
|
|
135
|
+
onRemove: (event: SortableEvent) => {
|
|
136
|
+
restore(event.item)
|
|
137
|
+
// A copy left: the list keeps its item, and SortableJS's stand-in goes.
|
|
138
|
+
if (event.pullMode === 'clone') {
|
|
139
|
+
event.clone.remove()
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
const { oldIndex } = event
|
|
143
|
+
if (oldIndex === undefined) return
|
|
144
|
+
const [element] = list().splice(oldIndex, 1)
|
|
145
|
+
if (element === undefined) return
|
|
146
|
+
options.onChange?.({ removed: { element, oldIndex } })
|
|
147
|
+
},
|
|
148
|
+
onEnd: () => {
|
|
149
|
+
origin = undefined
|
|
150
|
+
dragged.delete(groupName)
|
|
151
|
+
options.onEnd?.()
|
|
152
|
+
},
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
watch(
|
|
156
|
+
() => toValue(options.disabled) ?? false,
|
|
157
|
+
(disabled) => option('disabled', disabled),
|
|
158
|
+
)
|
|
159
|
+
}
|
|
@@ -9,6 +9,8 @@ export const PALETTE_GROUP = {
|
|
|
9
9
|
put: false,
|
|
10
10
|
}
|
|
11
11
|
export const DRAG_HANDLE = '.mailing-drag-handle'
|
|
12
|
+
/** The blocks of a list, not the insert point that ends it. */
|
|
13
|
+
export const DRAG_ITEM = '>[data-drag-item]'
|
|
12
14
|
|
|
13
15
|
/** The block a tile dropped on the canvas becomes, shaped by its template. */
|
|
14
16
|
export function cloneFromPalette(
|
|
@@ -804,7 +804,7 @@
|
|
|
804
804
|
},
|
|
805
805
|
"webhookUrl": {
|
|
806
806
|
"label": "Webhook URL",
|
|
807
|
-
"description": "Your provider, or the relay that translates its events, posts them here with the secret below. The module
|
|
807
|
+
"description": "Your provider, or the relay that translates its events, posts them here with the secret below. The module documentation describes the format."
|
|
808
808
|
},
|
|
809
809
|
"webhookStatus": {
|
|
810
810
|
"label": "Webhook status",
|
|
@@ -804,7 +804,7 @@
|
|
|
804
804
|
},
|
|
805
805
|
"webhookUrl": {
|
|
806
806
|
"label": "URL du webhook",
|
|
807
|
-
"description": "Votre fournisseur, ou le relais qui traduit ses événements, les envoie ici avec le secret ci-dessous.
|
|
807
|
+
"description": "Votre fournisseur, ou le relais qui traduit ses événements, les envoie ici avec le secret ci-dessous. La documentation du module décrit le format."
|
|
808
808
|
},
|
|
809
809
|
"webhookStatus": {
|
|
810
810
|
"label": "État du webhook",
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"packageManager": "pnpm@10.2.0",
|
|
8
8
|
"engines": {
|
|
9
|
-
"@antelopejs/dms-frontend": ">=0.6.
|
|
9
|
+
"@antelopejs/dms-frontend": ">=0.6.3 <0.7.0"
|
|
10
10
|
},
|
|
11
11
|
"scripts": {
|
|
12
12
|
"format": "prettier --write .",
|
|
@@ -16,8 +16,9 @@
|
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@vueuse/core": "^14.3.0",
|
|
19
|
-
"
|
|
20
|
-
"
|
|
19
|
+
"@vueuse/integrations": "^14.3.0",
|
|
20
|
+
"sortablejs": "^1.14.0",
|
|
21
|
+
"vue": "^3.5.18"
|
|
21
22
|
},
|
|
22
23
|
"devDependencies": {
|
|
23
24
|
"@nuxt/ui": "4.10.0",
|
|
@@ -11,12 +11,15 @@ importers:
|
|
|
11
11
|
'@vueuse/core':
|
|
12
12
|
specifier: ^14.3.0
|
|
13
13
|
version: 14.4.0(vue@3.5.42(typescript@5.9.3))
|
|
14
|
+
'@vueuse/integrations':
|
|
15
|
+
specifier: ^14.3.0
|
|
16
|
+
version: 14.4.0(fuse.js@7.5.0)(sortablejs@1.14.0)(vue@3.5.42(typescript@5.9.3))
|
|
17
|
+
sortablejs:
|
|
18
|
+
specifier: ^1.14.0
|
|
19
|
+
version: 1.14.0
|
|
14
20
|
vue:
|
|
15
21
|
specifier: ^3.5.18
|
|
16
22
|
version: 3.5.42(typescript@5.9.3)
|
|
17
|
-
vuedraggable:
|
|
18
|
-
specifier: ^4.1.0
|
|
19
|
-
version: 4.1.0(vue@3.5.42(typescript@5.9.3))
|
|
20
23
|
devDependencies:
|
|
21
24
|
'@nuxt/ui':
|
|
22
25
|
specifier: 4.10.0
|
|
@@ -3154,11 +3157,6 @@ packages:
|
|
|
3154
3157
|
typescript:
|
|
3155
3158
|
optional: true
|
|
3156
3159
|
|
|
3157
|
-
vuedraggable@4.1.0:
|
|
3158
|
-
resolution: {integrity: sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==}
|
|
3159
|
-
peerDependencies:
|
|
3160
|
-
vue: ^3.0.1
|
|
3161
|
-
|
|
3162
3160
|
w3c-keyname@2.2.8:
|
|
3163
3161
|
resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==}
|
|
3164
3162
|
|
|
@@ -6245,11 +6243,6 @@ snapshots:
|
|
|
6245
6243
|
optionalDependencies:
|
|
6246
6244
|
typescript: 5.9.3
|
|
6247
6245
|
|
|
6248
|
-
vuedraggable@4.1.0(vue@3.5.42(typescript@5.9.3)):
|
|
6249
|
-
dependencies:
|
|
6250
|
-
sortablejs: 1.14.0
|
|
6251
|
-
vue: 3.5.42(typescript@5.9.3)
|
|
6252
|
-
|
|
6253
6246
|
w3c-keyname@2.2.8: {}
|
|
6254
6247
|
|
|
6255
6248
|
w3c-xmlserializer@5.0.0:
|
|
@@ -12,6 +12,24 @@ import EditorBlockList from '../app/components/editor/EditorBlockList.vue'
|
|
|
12
12
|
import type { Block } from '../app/types/mailing'
|
|
13
13
|
|
|
14
14
|
const editor = vi.hoisted(() => ({ touch: vi.fn(), select: vi.fn() }))
|
|
15
|
+
const sortables = vi.hoisted(
|
|
16
|
+
() => [] as { el: HTMLElement; options: Record<string, unknown> }[],
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
// What SortableJS would be handed: the test calls it as SortableJS does.
|
|
20
|
+
vi.mock('@vueuse/integrations/useSortable', async () => {
|
|
21
|
+
const { onMounted, unref } = await import('vue')
|
|
22
|
+
return {
|
|
23
|
+
useSortable: (
|
|
24
|
+
target: { value: HTMLElement },
|
|
25
|
+
_list: unknown,
|
|
26
|
+
options: Record<string, unknown>,
|
|
27
|
+
) => {
|
|
28
|
+
onMounted(() => sortables.push({ el: unref(target), options }))
|
|
29
|
+
return { option: () => {} }
|
|
30
|
+
},
|
|
31
|
+
}
|
|
32
|
+
})
|
|
15
33
|
|
|
16
34
|
vi.mock('../app/composables/useEditorContext', () => ({
|
|
17
35
|
useEditorContext: () => ({ editor }),
|
|
@@ -60,14 +78,36 @@ describe('editor draggable items', () => {
|
|
|
60
78
|
})
|
|
61
79
|
it('selects a block dropped into the list', async () => {
|
|
62
80
|
vi.stubGlobal('useI18n', () => ({ t: (key: string) => key }))
|
|
63
|
-
|
|
64
|
-
const
|
|
81
|
+
sortables.length = 0
|
|
82
|
+
const source = reactive<Block[]>([
|
|
83
|
+
{ id: 'dropped', type: 'divider', visibleIf: null },
|
|
84
|
+
])
|
|
85
|
+
const target = reactive<Block[]>([])
|
|
86
|
+
const app = createApp({
|
|
87
|
+
render: () => [
|
|
88
|
+
h(EditorBlockList, { list: source }),
|
|
89
|
+
h(EditorBlockList, { list: target }),
|
|
90
|
+
],
|
|
91
|
+
})
|
|
65
92
|
app.mount(document.createElement('div'))
|
|
66
93
|
try {
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
94
|
+
const [from, to] = sortables
|
|
95
|
+
expect(from!.options).toMatchObject({
|
|
96
|
+
group: { name: 'mailing-blocks' },
|
|
97
|
+
handle: '.mailing-drag-handle',
|
|
98
|
+
draggable: '>[data-drag-item]',
|
|
99
|
+
})
|
|
100
|
+
const item = from!.el.querySelector('[data-drag-item]') as HTMLElement
|
|
101
|
+
const handler = (sortable: typeof from, name: string) =>
|
|
102
|
+
sortable!.options[name] as (event: unknown) => void
|
|
103
|
+
handler(from, 'onStart')({ item, oldIndex: 0 })
|
|
104
|
+
to!.el.prepend(item)
|
|
105
|
+
handler(to, 'onAdd')({ item, newIndex: 0 })
|
|
106
|
+
handler(from, 'onRemove')({ item, oldIndex: 0 })
|
|
107
|
+
handler(from, 'onEnd')({})
|
|
108
|
+
await nextTick()
|
|
109
|
+
expect(source).toEqual([])
|
|
110
|
+
expect(target.map((block) => block.id)).toEqual(['dropped'])
|
|
71
111
|
expect(editor.touch).toHaveBeenCalledTimes(2)
|
|
72
112
|
expect(editor.select).toHaveBeenCalledExactlyOnceWith('dropped')
|
|
73
113
|
} finally {
|