@cloudcannon/editable-regions 0.0.3 → 0.0.5
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/components/index.ts +14 -4
- package/components/ui/editable-array-item-controls.ts +61 -24
- package/helpers/checks.ts +8 -32
- package/helpers/cloudcannon.ts +19 -12
- package/helpers/hydrate-editable-regions.ts +18 -11
- package/helpers/loading.ts +7 -0
- package/integrations/astro/astro-integration.mjs +3 -7
- package/integrations/astro/index.mjs +2 -2
- package/nodes/editable-array-item.ts +105 -45
- package/nodes/editable-array.ts +115 -17
- package/nodes/editable-component.ts +108 -37
- package/nodes/editable-snippet.ts +0 -6
- package/nodes/editable-text.ts +2 -8
- package/nodes/editable.ts +212 -110
- package/package.json +3 -2
package/components/index.ts
CHANGED
|
@@ -6,11 +6,12 @@ export { default as EditableImageComponent } from "./editable-image-component.js
|
|
|
6
6
|
export { default as EditableSourceComponent } from "./editable-source-component.js";
|
|
7
7
|
export { default as EditableSnippetComponent } from "./editable-snippet-component.js";
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import { apiLoadedPromise } from "../helpers/cloudcannon.js";
|
|
10
10
|
import {
|
|
11
11
|
dehydrateDataEditableRegions,
|
|
12
12
|
hydrateDataEditableRegions,
|
|
13
13
|
} from "../helpers/hydrate-editable-regions";
|
|
14
|
+
import { completeLoading } from "../helpers/loading.js";
|
|
14
15
|
|
|
15
16
|
const observer = new MutationObserver((mutations) => {
|
|
16
17
|
mutations.forEach((mutation) => {
|
|
@@ -28,8 +29,17 @@ const observer = new MutationObserver((mutations) => {
|
|
|
28
29
|
});
|
|
29
30
|
});
|
|
30
31
|
|
|
32
|
+
hydrateDataEditableRegions(document.body);
|
|
33
|
+
|
|
31
34
|
observer.observe(document, { childList: true, subtree: true });
|
|
32
35
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
+
Promise.all([
|
|
37
|
+
apiLoadedPromise,
|
|
38
|
+
customElements.whenDefined("editable-array-item"),
|
|
39
|
+
customElements.whenDefined("editable-array"),
|
|
40
|
+
customElements.whenDefined("editable-text"),
|
|
41
|
+
customElements.whenDefined("editable-component"),
|
|
42
|
+
customElements.whenDefined("editable-image"),
|
|
43
|
+
customElements.whenDefined("editable-source"),
|
|
44
|
+
customElements.whenDefined("editable-snippet"),
|
|
45
|
+
]).then(() => completeLoading());
|
|
@@ -11,11 +11,14 @@ export default class EditableArrayItemControls extends EditableComponentControls
|
|
|
11
11
|
private _disableMoveBackward = false;
|
|
12
12
|
private _disableRemove = false;
|
|
13
13
|
private _disableReorder = false;
|
|
14
|
+
private _disableAdd = false;
|
|
14
15
|
|
|
15
16
|
private moveForwardButton?: HTMLButtonElement;
|
|
16
17
|
private moveBackwardButton?: HTMLButtonElement;
|
|
17
18
|
private deleteButton?: HTMLButtonElement;
|
|
18
19
|
private dragHandle?: HTMLButtonElement;
|
|
20
|
+
private duplicateButton?: HTMLButtonElement;
|
|
21
|
+
private addButton?: HTMLButtonElement;
|
|
19
22
|
|
|
20
23
|
set disableMoveForward(value: boolean) {
|
|
21
24
|
this._disableMoveForward = value;
|
|
@@ -37,7 +40,20 @@ export default class EditableArrayItemControls extends EditableComponentControls
|
|
|
37
40
|
this.update();
|
|
38
41
|
}
|
|
39
42
|
|
|
43
|
+
set disableAdd(value: boolean) {
|
|
44
|
+
this._disableAdd = value;
|
|
45
|
+
this.update();
|
|
46
|
+
}
|
|
47
|
+
|
|
40
48
|
update() {
|
|
49
|
+
if (this.addButton) {
|
|
50
|
+
this.addButton.disabled = this._disableAdd;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (this.duplicateButton) {
|
|
54
|
+
this.duplicateButton.disabled = this._disableAdd;
|
|
55
|
+
}
|
|
56
|
+
|
|
41
57
|
if (this.dragHandle) {
|
|
42
58
|
this.dragHandle.draggable = !this._disableReorder;
|
|
43
59
|
this.dragHandle.innerHTML = `<cc-icon name="${this.dragHandle.draggable ? "drag_indicator" : "more_vert"}"></cc-icon>`;
|
|
@@ -58,6 +74,20 @@ export default class EditableArrayItemControls extends EditableComponentControls
|
|
|
58
74
|
}
|
|
59
75
|
}
|
|
60
76
|
|
|
77
|
+
addContextMenuButton(
|
|
78
|
+
icon: string,
|
|
79
|
+
text: string,
|
|
80
|
+
onClick: (e: PointerEvent) => void,
|
|
81
|
+
): HTMLButtonElement {
|
|
82
|
+
const button = document.createElement("button");
|
|
83
|
+
button.onclick = onClick;
|
|
84
|
+
button.innerHTML = `<cc-icon name="${icon}"></cc-icon> ${text}`;
|
|
85
|
+
const buttonContainer = document.createElement("li");
|
|
86
|
+
buttonContainer.append(button);
|
|
87
|
+
this.contextMenu?.append(buttonContainer);
|
|
88
|
+
return button;
|
|
89
|
+
}
|
|
90
|
+
|
|
61
91
|
render(shadow: ShadowRoot): void {
|
|
62
92
|
super.render(shadow);
|
|
63
93
|
|
|
@@ -78,34 +108,41 @@ export default class EditableArrayItemControls extends EditableComponentControls
|
|
|
78
108
|
};
|
|
79
109
|
this.buttonRow?.append(this.dragHandle);
|
|
80
110
|
|
|
111
|
+
this.addButton = this.addContextMenuButton("add", "Add", (e) => {
|
|
112
|
+
this.dispatchEvent(
|
|
113
|
+
new CustomEvent("add", { detail: { originalEvent: e } }),
|
|
114
|
+
);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
this.duplicateButton = this.addContextMenuButton(
|
|
118
|
+
"library_add",
|
|
119
|
+
"Duplicate",
|
|
120
|
+
() => {
|
|
121
|
+
this.dispatchEvent(new CustomEvent("duplicate", { detail: this }));
|
|
122
|
+
},
|
|
123
|
+
);
|
|
124
|
+
|
|
81
125
|
const backwardIconName = this.moveBackwardText === "up" ? "north" : "west";
|
|
82
|
-
this.moveBackwardButton =
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
this.contextMenu?.append(moveBackwardContainer);
|
|
126
|
+
this.moveBackwardButton = this.addContextMenuButton(
|
|
127
|
+
backwardIconName,
|
|
128
|
+
`Move ${this.moveBackwardText}`,
|
|
129
|
+
() => {
|
|
130
|
+
this.dispatchEvent(new CustomEvent("move-backward", { detail: this }));
|
|
131
|
+
},
|
|
132
|
+
);
|
|
90
133
|
|
|
91
134
|
const forwardIconName = this.moveForwardText === "down" ? "south" : "east";
|
|
92
|
-
this.moveForwardButton =
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
this.deleteButton = document.createElement("button");
|
|
102
|
-
this.deleteButton.innerHTML = '<cc-icon name="delete"></cc-icon> Delete';
|
|
103
|
-
this.deleteButton.onclick = () => {
|
|
135
|
+
this.moveForwardButton = this.addContextMenuButton(
|
|
136
|
+
forwardIconName,
|
|
137
|
+
`Move ${this.moveForwardText}`,
|
|
138
|
+
() => {
|
|
139
|
+
this.dispatchEvent(new CustomEvent("move-forward", { detail: this }));
|
|
140
|
+
},
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
this.deleteButton = this.addContextMenuButton("delete", "Delete", () => {
|
|
104
144
|
this.dispatchEvent(new CustomEvent("delete", { detail: this }));
|
|
105
|
-
};
|
|
106
|
-
const deleteContainer = document.createElement("li");
|
|
107
|
-
deleteContainer.append(this.deleteButton);
|
|
108
|
-
this.contextMenu?.append(deleteContainer);
|
|
145
|
+
});
|
|
109
146
|
|
|
110
147
|
this.update();
|
|
111
148
|
}
|
package/helpers/checks.ts
CHANGED
|
@@ -48,6 +48,14 @@ export const hasEditableArrayItem = <T extends object>(
|
|
|
48
48
|
return "editable" in el && el.editable instanceof EditableArrayItem;
|
|
49
49
|
};
|
|
50
50
|
|
|
51
|
+
export const isEditableWebcomponent = (el: unknown): boolean => {
|
|
52
|
+
if (!(el instanceof HTMLElement)) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return TAG_NAMES.includes(el.tagName);
|
|
57
|
+
};
|
|
58
|
+
|
|
51
59
|
export const isEditableElement = (el: unknown): boolean => {
|
|
52
60
|
if (!(el instanceof HTMLElement)) {
|
|
53
61
|
return false;
|
|
@@ -92,35 +100,3 @@ export const isEditableArrayItem = (el?: Element | null): boolean => {
|
|
|
92
100
|
(el instanceof HTMLElement && el.dataset.editable === "array-item")
|
|
93
101
|
);
|
|
94
102
|
};
|
|
95
|
-
|
|
96
|
-
export const areEqualNodes = (a: ChildNode, b: ChildNode) => {
|
|
97
|
-
if (a.nodeName !== b.nodeName) {
|
|
98
|
-
return false;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if (a instanceof Element && b instanceof Element) {
|
|
102
|
-
if (a.className !== b.className) {
|
|
103
|
-
return false;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
if (a.id !== b.id) {
|
|
107
|
-
return false;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (a.attributes.length !== b.attributes.length) {
|
|
111
|
-
return false;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
for (let i = 0; i < a.attributes.length; i++) {
|
|
115
|
-
if (
|
|
116
|
-
a.attributes[i].name !== b.attributes[i].name ||
|
|
117
|
-
a.attributes[i].value !== b.attributes[i].value
|
|
118
|
-
) {
|
|
119
|
-
return false;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return true;
|
|
124
|
-
}
|
|
125
|
-
return a.isEqualNode(b);
|
|
126
|
-
};
|
package/helpers/cloudcannon.ts
CHANGED
|
@@ -14,7 +14,7 @@ declare const window: CloudCannonEditorWindow & {
|
|
|
14
14
|
|
|
15
15
|
let _cloudcannon: CloudCannonJavaScriptV1API;
|
|
16
16
|
|
|
17
|
-
const apiLoadedPromise = new Promise<void>((resolve) => {
|
|
17
|
+
export const apiLoadedPromise = new Promise<void>((resolve) => {
|
|
18
18
|
if (window.CloudCannonAPI) {
|
|
19
19
|
_cloudcannon = window.CloudCannonAPI.useVersion("v1", true) as any;
|
|
20
20
|
resolve();
|
|
@@ -32,17 +32,6 @@ const apiLoadedPromise = new Promise<void>((resolve) => {
|
|
|
32
32
|
}
|
|
33
33
|
});
|
|
34
34
|
|
|
35
|
-
export const loadedPromise = Promise.all([
|
|
36
|
-
apiLoadedPromise,
|
|
37
|
-
customElements.whenDefined("editable-array-item"),
|
|
38
|
-
customElements.whenDefined("editable-array"),
|
|
39
|
-
customElements.whenDefined("editable-text"),
|
|
40
|
-
customElements.whenDefined("editable-component"),
|
|
41
|
-
customElements.whenDefined("editable-image"),
|
|
42
|
-
customElements.whenDefined("editable-source"),
|
|
43
|
-
customElements.whenDefined("editable-snippet"),
|
|
44
|
-
]);
|
|
45
|
-
|
|
46
35
|
export const addEditableComponentRenderer = (
|
|
47
36
|
key: string,
|
|
48
37
|
renderer: ComponentRenderer,
|
|
@@ -64,4 +53,22 @@ export const addEditableSnippetRenderer = (
|
|
|
64
53
|
export const getEditableComponentRenderers = () => window.cc_components ?? {};
|
|
65
54
|
export const getEditableSnippetRenderers = () => window.cc_snippets ?? {};
|
|
66
55
|
|
|
56
|
+
export const realizeAPIValue = async (value: unknown): Promise<unknown> => {
|
|
57
|
+
if (_cloudcannon.isAPICollection(value)) {
|
|
58
|
+
const items = await value.items();
|
|
59
|
+
return Promise.all(items.map(realizeAPIValue));
|
|
60
|
+
}
|
|
61
|
+
if (_cloudcannon.isAPIFile(value)) {
|
|
62
|
+
return value.data.get();
|
|
63
|
+
}
|
|
64
|
+
if (_cloudcannon.isAPIDataset(value)) {
|
|
65
|
+
const items = await value.items();
|
|
66
|
+
if (Array.isArray(items)) {
|
|
67
|
+
return Promise.all(items.map(realizeAPIValue));
|
|
68
|
+
}
|
|
69
|
+
return items.data.get();
|
|
70
|
+
}
|
|
71
|
+
return value;
|
|
72
|
+
};
|
|
73
|
+
|
|
67
74
|
export { _cloudcannon as CloudCannon };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
Editable,
|
|
3
3
|
EditableArray,
|
|
4
4
|
EditableArrayItem,
|
|
5
5
|
EditableComponent,
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
EditableSource,
|
|
8
8
|
EditableText,
|
|
9
9
|
} from "../nodes";
|
|
10
|
-
import { hasEditable } from "./checks";
|
|
10
|
+
import { hasEditable, isEditableWebcomponent } from "./checks";
|
|
11
11
|
|
|
12
12
|
const editableMap: Record<string, typeof Editable | undefined> = {
|
|
13
13
|
array: EditableArray,
|
|
@@ -34,17 +34,21 @@ export const hydrateDataEditableRegions = (root: Element) => {
|
|
|
34
34
|
if (
|
|
35
35
|
root instanceof HTMLElement &&
|
|
36
36
|
root.dataset.editable &&
|
|
37
|
-
!(
|
|
37
|
+
!isEditableWebcomponent(root)
|
|
38
38
|
) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
editable
|
|
39
|
+
if ("editable" in root && root.editable instanceof Editable) {
|
|
40
|
+
root.editable.connect();
|
|
41
|
+
} else {
|
|
42
|
+
const Editable = editableMap[root.dataset.editable];
|
|
43
|
+
if (Editable) {
|
|
44
|
+
const editable = new Editable(root);
|
|
45
|
+
editable.connect();
|
|
46
|
+
}
|
|
43
47
|
}
|
|
44
48
|
}
|
|
45
49
|
|
|
46
50
|
root.querySelectorAll("[data-editable]").forEach((element) => {
|
|
47
|
-
if (!(element instanceof HTMLElement) ||
|
|
51
|
+
if (!(element instanceof HTMLElement) || isEditableWebcomponent(element)) {
|
|
48
52
|
return;
|
|
49
53
|
}
|
|
50
54
|
|
|
@@ -64,8 +68,11 @@ export const hydrateDataEditableRegions = (root: Element) => {
|
|
|
64
68
|
return;
|
|
65
69
|
}
|
|
66
70
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
71
|
+
if ("editable" in element && element.editable instanceof Editable) {
|
|
72
|
+
element.editable.connect();
|
|
73
|
+
} else {
|
|
74
|
+
const editable = new Editable(element);
|
|
75
|
+
editable.connect();
|
|
76
|
+
}
|
|
70
77
|
});
|
|
71
78
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { dirname, join } from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
2
3
|
|
|
3
4
|
const SUPPORTED_VIRTUAL_MODULES = [
|
|
4
5
|
"actions",
|
|
@@ -57,15 +58,10 @@ export default () => {
|
|
|
57
58
|
if (typeof __dirname !== "undefined") {
|
|
58
59
|
dir = __dirname;
|
|
59
60
|
} else {
|
|
60
|
-
dir = dirname(import.meta.url);
|
|
61
|
+
dir = dirname(fileURLToPath(import.meta.url));
|
|
61
62
|
}
|
|
62
63
|
|
|
63
|
-
|
|
64
|
-
"file:",
|
|
65
|
-
"",
|
|
66
|
-
);
|
|
67
|
-
|
|
68
|
-
return path;
|
|
64
|
+
return join(dir, "modules", `${type}.js`);
|
|
69
65
|
}
|
|
70
66
|
},
|
|
71
67
|
|
|
@@ -127,7 +127,7 @@ export const registerAstroComponent = (key, component) => {
|
|
|
127
127
|
["visible", "editable-region-placeholder"],
|
|
128
128
|
["media", "editable-region-placeholder"],
|
|
129
129
|
]),
|
|
130
|
-
slots:
|
|
130
|
+
slots: {},
|
|
131
131
|
props,
|
|
132
132
|
resolve: () => "editable-region-placeholder",
|
|
133
133
|
/**
|
|
@@ -162,7 +162,7 @@ export const registerAstroComponent = (key, component) => {
|
|
|
162
162
|
},
|
|
163
163
|
};
|
|
164
164
|
// Render the Astro component to HTML string
|
|
165
|
-
const result = await renderToString(SSRResult, component, props,
|
|
165
|
+
const result = await renderToString(SSRResult, component, props, {});
|
|
166
166
|
const doc = document.implementation.createHTMLDocument();
|
|
167
167
|
doc.body.innerHTML = result;
|
|
168
168
|
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
hasEditableArrayItem,
|
|
5
5
|
isEditableArrayItem,
|
|
6
6
|
} from "../helpers/checks.js";
|
|
7
|
-
import { CloudCannon } from "../helpers/cloudcannon.js";
|
|
7
|
+
import { CloudCannon, realizeAPIValue } from "../helpers/cloudcannon.js";
|
|
8
8
|
import EditableArray from "./editable-array.js";
|
|
9
9
|
import EditableComponent from "./editable-component.js";
|
|
10
10
|
|
|
@@ -15,6 +15,10 @@ export default class EditableArrayItem extends EditableComponent {
|
|
|
15
15
|
|
|
16
16
|
private inputConfig?: any;
|
|
17
17
|
|
|
18
|
+
shouldMount(): boolean {
|
|
19
|
+
return this.value !== undefined;
|
|
20
|
+
}
|
|
21
|
+
|
|
18
22
|
validateConfiguration(): boolean {
|
|
19
23
|
const key = this.element.dataset.component;
|
|
20
24
|
if (key) {
|
|
@@ -44,25 +48,36 @@ export default class EditableArrayItem extends EditableComponent {
|
|
|
44
48
|
return true;
|
|
45
49
|
}
|
|
46
50
|
|
|
47
|
-
|
|
48
|
-
const source = this.parent?.
|
|
51
|
+
isValidDropzone(e: DragEvent): boolean {
|
|
52
|
+
const source = this.parent?.contextBase?.filePath;
|
|
49
53
|
if (!source || !e.dataTransfer) {
|
|
50
|
-
return;
|
|
54
|
+
return false;
|
|
51
55
|
}
|
|
52
56
|
|
|
57
|
+
const dragType = this.getDragType();
|
|
58
|
+
|
|
53
59
|
if (
|
|
54
|
-
!e.dataTransfer?.types.includes(source) &&
|
|
55
|
-
!e.dataTransfer.types.includes(
|
|
60
|
+
!e.dataTransfer?.types.includes(source.toLowerCase()) &&
|
|
61
|
+
(!dragType || !e.dataTransfer.types.includes(dragType))
|
|
56
62
|
) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
onHover(e: DragEvent): void {
|
|
70
|
+
if (!this.isValidDropzone(e)) {
|
|
57
71
|
return;
|
|
58
72
|
}
|
|
59
73
|
|
|
60
74
|
e.preventDefault();
|
|
75
|
+
e.stopPropagation();
|
|
61
76
|
this.element.classList.add("dragover");
|
|
62
77
|
this.element.style.boxShadow = this.getDraggingBoxShadow(e);
|
|
63
78
|
}
|
|
64
79
|
|
|
65
|
-
getDragType(): string {
|
|
80
|
+
getDragType(): string | undefined {
|
|
66
81
|
if (this.inputConfig?.options?.structures?.values?.length) {
|
|
67
82
|
return "cc:structure";
|
|
68
83
|
}
|
|
@@ -72,7 +87,14 @@ export default class EditableArrayItem extends EditableComponent {
|
|
|
72
87
|
return `cc:${currentArraySubtype}`;
|
|
73
88
|
}
|
|
74
89
|
|
|
75
|
-
const type = CloudCannon.getInputType(
|
|
90
|
+
const type = CloudCannon.getInputType(
|
|
91
|
+
this.contextBase?.filePath,
|
|
92
|
+
this.value,
|
|
93
|
+
this.inputConfig,
|
|
94
|
+
);
|
|
95
|
+
if (type === "array" || type === "object") {
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
76
98
|
return `cc:${type}`;
|
|
77
99
|
}
|
|
78
100
|
|
|
@@ -127,12 +149,13 @@ export default class EditableArrayItem extends EditableComponent {
|
|
|
127
149
|
return isBefore ? "before" : "after";
|
|
128
150
|
}
|
|
129
151
|
|
|
130
|
-
dispatchArrayMove(fromIndex: number, toIndex: number) {
|
|
152
|
+
dispatchArrayMove(fromIndex: number, toIndex: number, fromSlug?: string) {
|
|
131
153
|
this.element.dispatchEvent(
|
|
132
154
|
new CustomEvent("cloudcannon-api", {
|
|
133
155
|
bubbles: true,
|
|
134
156
|
detail: {
|
|
135
157
|
action: "move-array-item",
|
|
158
|
+
fromSlug,
|
|
136
159
|
fromIndex,
|
|
137
160
|
toIndex,
|
|
138
161
|
},
|
|
@@ -153,13 +176,14 @@ export default class EditableArrayItem extends EditableComponent {
|
|
|
153
176
|
);
|
|
154
177
|
}
|
|
155
178
|
|
|
156
|
-
dispatchArrayAdd(newIndex: number, value: unknown) {
|
|
179
|
+
dispatchArrayAdd(newIndex: number, value: unknown, sourceIndex?: number) {
|
|
157
180
|
this.element.dispatchEvent(
|
|
158
181
|
new CustomEvent("cloudcannon-api", {
|
|
159
182
|
bubbles: true,
|
|
160
183
|
detail: {
|
|
161
184
|
action: "add-array-item",
|
|
162
185
|
newIndex,
|
|
186
|
+
sourceIndex,
|
|
163
187
|
value,
|
|
164
188
|
},
|
|
165
189
|
}),
|
|
@@ -213,6 +237,34 @@ export default class EditableArrayItem extends EditableComponent {
|
|
|
213
237
|
this.dispatchEdit(this.element.dataset.prop);
|
|
214
238
|
});
|
|
215
239
|
|
|
240
|
+
this.controlsElement.addEventListener("add", () => {
|
|
241
|
+
const fromIndex = Number(this.element.dataset.prop);
|
|
242
|
+
const arrayDirection = this.parent?.arrayDirection ?? "column";
|
|
243
|
+
const reversed = arrayDirection.endsWith("reverse");
|
|
244
|
+
|
|
245
|
+
this.dispatchArrayAdd(
|
|
246
|
+
reversed ? fromIndex - 1 : fromIndex + 1,
|
|
247
|
+
undefined,
|
|
248
|
+
fromIndex,
|
|
249
|
+
);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
this.controlsElement.addEventListener("duplicate", async () => {
|
|
253
|
+
const value = await realizeAPIValue(this.value);
|
|
254
|
+
const fromIndex = Number(this.element.dataset.prop);
|
|
255
|
+
const arrayDirection = this.parent?.arrayDirection ?? "column";
|
|
256
|
+
const reversed = arrayDirection.endsWith("reverse");
|
|
257
|
+
|
|
258
|
+
this.dispatchArrayAdd(
|
|
259
|
+
reversed ? fromIndex - 1 : fromIndex + 1,
|
|
260
|
+
value,
|
|
261
|
+
fromIndex,
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
this.controlsElement?.removeAttribute("open");
|
|
265
|
+
this.element.after(this.element.cloneNode(true));
|
|
266
|
+
});
|
|
267
|
+
|
|
216
268
|
this.controlsElement.addEventListener("move-backward", () => {
|
|
217
269
|
const fromIndex = Number(this.element.dataset.prop);
|
|
218
270
|
const arrayDirection = this.parent?.arrayDirection ?? "column";
|
|
@@ -249,7 +301,7 @@ export default class EditableArrayItem extends EditableComponent {
|
|
|
249
301
|
});
|
|
250
302
|
|
|
251
303
|
this.controlsElement.addEventListener("dragstart", (e: DragEvent) => {
|
|
252
|
-
const source = this.parent?.
|
|
304
|
+
const source = this.parent?.contextBase?.filePath;
|
|
253
305
|
if (!source || !e.dataTransfer || !this.element.dataset.prop) {
|
|
254
306
|
return;
|
|
255
307
|
}
|
|
@@ -261,7 +313,6 @@ export default class EditableArrayItem extends EditableComponent {
|
|
|
261
313
|
|
|
262
314
|
e.dataTransfer.setDragImage(this.element, clientRect.width - 35, 35);
|
|
263
315
|
e.dataTransfer.effectAllowed = "move";
|
|
264
|
-
e.dataTransfer?.setData(source, this.element.dataset.prop);
|
|
265
316
|
|
|
266
317
|
const id = Math.random().toString(36).slice(2);
|
|
267
318
|
this.element.id = id;
|
|
@@ -279,7 +330,12 @@ export default class EditableArrayItem extends EditableComponent {
|
|
|
279
330
|
);
|
|
280
331
|
}
|
|
281
332
|
|
|
282
|
-
|
|
333
|
+
const payload = JSON.stringify(data);
|
|
334
|
+
e.dataTransfer?.setData(source, payload);
|
|
335
|
+
const dragType = this.getDragType();
|
|
336
|
+
if (dragType) {
|
|
337
|
+
e.dataTransfer?.setData(dragType, payload);
|
|
338
|
+
}
|
|
283
339
|
});
|
|
284
340
|
|
|
285
341
|
this.updateControls();
|
|
@@ -298,6 +354,8 @@ export default class EditableArrayItem extends EditableComponent {
|
|
|
298
354
|
(inputConfig as any)?.options?.disable_reorder ?? false;
|
|
299
355
|
this.controlsElement.disableRemove =
|
|
300
356
|
(inputConfig as any)?.options?.disable_remove ?? false;
|
|
357
|
+
this.controlsElement.disableAdd =
|
|
358
|
+
(inputConfig as any)?.options?.disable_add ?? false;
|
|
301
359
|
|
|
302
360
|
this.inputConfig = inputConfig;
|
|
303
361
|
this.element.append(this.controlsElement);
|
|
@@ -313,6 +371,9 @@ export default class EditableArrayItem extends EditableComponent {
|
|
|
313
371
|
this.element.ondragover = this.onHover.bind(this);
|
|
314
372
|
|
|
315
373
|
this.element.ondragleave = (e: DragEvent): void => {
|
|
374
|
+
if (!this.isValidDropzone(e)) {
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
316
377
|
e.stopPropagation();
|
|
317
378
|
|
|
318
379
|
this.element.classList.remove("dragover");
|
|
@@ -320,6 +381,10 @@ export default class EditableArrayItem extends EditableComponent {
|
|
|
320
381
|
};
|
|
321
382
|
|
|
322
383
|
this.element.ondrop = (e: DragEvent): void => {
|
|
384
|
+
if (!this.isValidDropzone(e)) {
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
|
|
323
388
|
this.element.classList.remove("dragover");
|
|
324
389
|
this.element.style.boxShadow = "";
|
|
325
390
|
|
|
@@ -327,14 +392,16 @@ export default class EditableArrayItem extends EditableComponent {
|
|
|
327
392
|
return;
|
|
328
393
|
}
|
|
329
394
|
|
|
330
|
-
const source = this.parent?.
|
|
395
|
+
const source = this.parent?.contextBase?.filePath;
|
|
331
396
|
if (!source) {
|
|
332
397
|
throw new Error("Source not found");
|
|
333
398
|
}
|
|
334
399
|
|
|
335
400
|
const dragType = this.getDragType();
|
|
336
401
|
const sameArrayData = e.dataTransfer.getData(source);
|
|
337
|
-
const otherArrayData =
|
|
402
|
+
const otherArrayData = dragType
|
|
403
|
+
? e.dataTransfer.getData(dragType)
|
|
404
|
+
: undefined;
|
|
338
405
|
|
|
339
406
|
const position = this.getDragPosition(e);
|
|
340
407
|
let newIndex =
|
|
@@ -343,20 +410,14 @@ export default class EditableArrayItem extends EditableComponent {
|
|
|
343
410
|
: Number(this.element.dataset.prop);
|
|
344
411
|
|
|
345
412
|
if (sameArrayData) {
|
|
346
|
-
const fromIndex =
|
|
413
|
+
const { index: fromIndex, sourceId } = JSON.parse(sameArrayData);
|
|
414
|
+
|
|
347
415
|
if (fromIndex < newIndex) {
|
|
348
416
|
newIndex -= 1;
|
|
349
417
|
}
|
|
350
418
|
|
|
351
|
-
e.preventDefault();
|
|
352
|
-
e.stopPropagation();
|
|
353
|
-
e.dataTransfer.dropEffect = "move";
|
|
354
|
-
|
|
355
419
|
if (fromIndex !== newIndex) {
|
|
356
|
-
|
|
357
|
-
const sourceElement = this.parent?.element.querySelector(
|
|
358
|
-
`[data-prop="${fromIndex}"]`,
|
|
359
|
-
);
|
|
420
|
+
const sourceElement = document.getElementById(sourceId);
|
|
360
421
|
if (sourceElement) {
|
|
361
422
|
if (position === "after") {
|
|
362
423
|
this.element.after(sourceElement);
|
|
@@ -364,13 +425,14 @@ export default class EditableArrayItem extends EditableComponent {
|
|
|
364
425
|
this.element.before(sourceElement);
|
|
365
426
|
}
|
|
366
427
|
}
|
|
428
|
+
this.dispatchArrayMove(fromIndex, newIndex);
|
|
367
429
|
}
|
|
368
430
|
} else if (otherArrayData) {
|
|
369
431
|
const { index, sourceId, value, structure } =
|
|
370
432
|
JSON.parse(otherArrayData);
|
|
371
433
|
if (dragType === "cc:structure") {
|
|
372
434
|
if (!this.inputConfig?.options?.structures?.values) {
|
|
373
|
-
|
|
435
|
+
return;
|
|
374
436
|
}
|
|
375
437
|
|
|
376
438
|
const targetStructure = CloudCannon.findStructure(
|
|
@@ -378,41 +440,35 @@ export default class EditableArrayItem extends EditableComponent {
|
|
|
378
440
|
this.value,
|
|
379
441
|
);
|
|
380
442
|
if (!targetStructure) {
|
|
381
|
-
|
|
443
|
+
return;
|
|
382
444
|
}
|
|
383
445
|
|
|
384
446
|
if (JSON.stringify(structure) !== JSON.stringify(targetStructure)) {
|
|
385
|
-
|
|
447
|
+
return;
|
|
386
448
|
}
|
|
387
449
|
}
|
|
388
450
|
|
|
389
451
|
const sourceElement = document.getElementById(sourceId);
|
|
390
452
|
if (sourceElement && hasEditableArrayItem(sourceElement)) {
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
);
|
|
395
|
-
sourceElement.editable.parent.value.splice(index, 1);
|
|
453
|
+
const parentValue = this.parent?.value;
|
|
454
|
+
if (Array.isArray(parentValue)) {
|
|
455
|
+
parentValue.splice(newIndex, 0, value);
|
|
396
456
|
}
|
|
397
|
-
sourceElement.
|
|
457
|
+
sourceElement.dataset.prop = `${newIndex}`;
|
|
458
|
+
const fromSlug = sourceElement.editable.parent?.contextBase?.fullPath;
|
|
398
459
|
if (position === "after") {
|
|
399
460
|
this.element.after(sourceElement);
|
|
400
461
|
} else {
|
|
401
462
|
this.element.before(sourceElement);
|
|
402
463
|
}
|
|
403
|
-
sourceElement.editable.parent?.update();
|
|
404
|
-
}
|
|
405
|
-
if (Array.isArray(this.parent?.value)) {
|
|
406
|
-
this.parent.value = structuredClone(this.parent.value);
|
|
407
|
-
this.parent.value.splice(newIndex, 0, value);
|
|
408
|
-
}
|
|
409
|
-
this.dispatchArrayAdd(newIndex, value);
|
|
410
|
-
this.parent?.update();
|
|
411
464
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
e.dataTransfer.dropEffect = "move";
|
|
465
|
+
this.dispatchArrayMove(index, newIndex, fromSlug);
|
|
466
|
+
}
|
|
415
467
|
}
|
|
468
|
+
|
|
469
|
+
e.preventDefault();
|
|
470
|
+
e.stopPropagation();
|
|
471
|
+
e.dataTransfer.dropEffect = "move";
|
|
416
472
|
};
|
|
417
473
|
|
|
418
474
|
if (this.value !== undefined) {
|
|
@@ -422,6 +478,10 @@ export default class EditableArrayItem extends EditableComponent {
|
|
|
422
478
|
|
|
423
479
|
setupListeners(): void {
|
|
424
480
|
super.setupListeners();
|
|
425
|
-
this.
|
|
481
|
+
if (!this.element.dataset.prop) {
|
|
482
|
+
this.parent?.registerListener({
|
|
483
|
+
editable: this,
|
|
484
|
+
});
|
|
485
|
+
}
|
|
426
486
|
}
|
|
427
487
|
}
|