@cloudcannon/editable-regions 0.0.4 → 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/helpers/checks.ts +0 -32
- package/nodes/editable-array-item.ts +5 -1
- package/nodes/editable-array.ts +86 -19
- package/nodes/editable-component.ts +15 -2
- package/nodes/editable.ts +24 -18
- package/package.json +2 -1
package/helpers/checks.ts
CHANGED
|
@@ -100,35 +100,3 @@ export const isEditableArrayItem = (el?: Element | null): boolean => {
|
|
|
100
100
|
(el instanceof HTMLElement && el.dataset.editable === "array-item")
|
|
101
101
|
);
|
|
102
102
|
};
|
|
103
|
-
|
|
104
|
-
export const areEqualNodes = (a: ChildNode, b: ChildNode) => {
|
|
105
|
-
if (a.nodeName !== b.nodeName) {
|
|
106
|
-
return false;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
if (a instanceof Element && b instanceof Element) {
|
|
110
|
-
if (a.className !== b.className) {
|
|
111
|
-
return false;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (a.id !== b.id) {
|
|
115
|
-
return false;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
if (a.attributes.length !== b.attributes.length) {
|
|
119
|
-
return false;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
for (let i = 0; i < a.attributes.length; i++) {
|
|
123
|
-
if (
|
|
124
|
-
a.attributes[i].name !== b.attributes[i].name ||
|
|
125
|
-
a.attributes[i].value !== b.attributes[i].value
|
|
126
|
-
) {
|
|
127
|
-
return false;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
return true;
|
|
132
|
-
}
|
|
133
|
-
return a.isEqualNode(b);
|
|
134
|
-
};
|
|
@@ -478,6 +478,10 @@ export default class EditableArrayItem extends EditableComponent {
|
|
|
478
478
|
|
|
479
479
|
setupListeners(): void {
|
|
480
480
|
super.setupListeners();
|
|
481
|
-
this.
|
|
481
|
+
if (!this.element.dataset.prop) {
|
|
482
|
+
this.parent?.registerListener({
|
|
483
|
+
editable: this,
|
|
484
|
+
});
|
|
485
|
+
}
|
|
482
486
|
}
|
|
483
487
|
}
|
package/nodes/editable-array.ts
CHANGED
|
@@ -7,7 +7,7 @@ import type EditableArrayItemComponent from "../components/editable-array-item-c
|
|
|
7
7
|
import { hasEditableArrayItem, isEditableElement } from "../helpers/checks.js";
|
|
8
8
|
import { CloudCannon } from "../helpers/cloudcannon.js";
|
|
9
9
|
import type EditableArrayItem from "./editable-array-item.js";
|
|
10
|
-
import Editable from "./editable.js";
|
|
10
|
+
import Editable, { type EditableListener } from "./editable.js";
|
|
11
11
|
|
|
12
12
|
const arrayDirectionValues = [
|
|
13
13
|
"row",
|
|
@@ -34,6 +34,60 @@ export default class EditableArray extends Editable {
|
|
|
34
34
|
private updatePromise: Promise<void> | undefined;
|
|
35
35
|
private needsReupdate = false;
|
|
36
36
|
|
|
37
|
+
async registerListener(listener: EditableListener): Promise<void> {
|
|
38
|
+
if (!this.value) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let value: unknown[] | CloudCannonJavaScriptV1APIFile[];
|
|
43
|
+
if (CloudCannon.isAPICollection(this.value)) {
|
|
44
|
+
value = await this.value.items();
|
|
45
|
+
} else if (CloudCannon.isAPIDataset(this.value)) {
|
|
46
|
+
const items = await this.value.items();
|
|
47
|
+
if (Array.isArray(items)) {
|
|
48
|
+
value = items;
|
|
49
|
+
} else {
|
|
50
|
+
const data = await items.data.get();
|
|
51
|
+
value = Array.isArray(data) ? data : [];
|
|
52
|
+
}
|
|
53
|
+
} else if (Array.isArray(this.value)) {
|
|
54
|
+
value = this.value;
|
|
55
|
+
} else {
|
|
56
|
+
value = [];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!listener.path) {
|
|
60
|
+
let index = 0;
|
|
61
|
+
for (const child of this.element.querySelectorAll(
|
|
62
|
+
"editable-array-item,[data-editable='array-item']",
|
|
63
|
+
)) {
|
|
64
|
+
let parent = child.parentElement;
|
|
65
|
+
while (parent instanceof HTMLElement && !isEditableElement(parent)) {
|
|
66
|
+
parent = parent.parentElement;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (parent !== this.element) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (child === listener.editable.element) {
|
|
74
|
+
listener.path = `${index}`;
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
index += 1;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (listener.path) {
|
|
83
|
+
listener.editable.pushValue(value, listener, {
|
|
84
|
+
__base_context: this.contextBase ?? {},
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
deregisterListener(_target: Editable): void {}
|
|
90
|
+
|
|
37
91
|
validateConfiguration(): boolean {
|
|
38
92
|
const prop = this.element.dataset.prop;
|
|
39
93
|
if (typeof prop !== "string") {
|
|
@@ -101,21 +155,17 @@ export default class EditableArray extends Editable {
|
|
|
101
155
|
value = [];
|
|
102
156
|
}
|
|
103
157
|
|
|
104
|
-
const children: (HTMLElement & { editable
|
|
158
|
+
const children: (HTMLElement & { editable?: EditableArrayItem })[] = [];
|
|
105
159
|
|
|
106
160
|
for (const child of this.element.querySelectorAll(
|
|
107
161
|
"editable-array-item,[data-editable='array-item']",
|
|
108
162
|
)) {
|
|
109
|
-
if (!hasEditableArrayItem(child)) {
|
|
110
|
-
continue;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
163
|
let parent = child.parentElement;
|
|
114
164
|
while (parent instanceof HTMLElement && !isEditableElement(parent)) {
|
|
115
165
|
parent = parent.parentElement;
|
|
116
166
|
}
|
|
117
167
|
|
|
118
|
-
if (
|
|
168
|
+
if (parent !== this.element) {
|
|
119
169
|
continue;
|
|
120
170
|
}
|
|
121
171
|
|
|
@@ -127,14 +177,27 @@ export default class EditableArray extends Editable {
|
|
|
127
177
|
children.pop()?.remove();
|
|
128
178
|
}
|
|
129
179
|
|
|
180
|
+
const firstChild = children[0];
|
|
130
181
|
for (let i = 0; i < value.length; i++) {
|
|
131
182
|
let child = children[i];
|
|
132
183
|
if (!child) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
184
|
+
if (this.element.dataset.component) {
|
|
185
|
+
child = document.createElement(
|
|
186
|
+
"editable-array-item",
|
|
187
|
+
) as EditableArrayItemComponent;
|
|
188
|
+
} else if (firstChild) {
|
|
189
|
+
child = children[0].cloneNode(true) as HTMLElement & {
|
|
190
|
+
editable?: EditableArrayItem;
|
|
191
|
+
};
|
|
192
|
+
} else {
|
|
193
|
+
child = document.createElement("array-placeholder");
|
|
194
|
+
}
|
|
136
195
|
this.element.appendChild(child);
|
|
137
196
|
}
|
|
197
|
+
|
|
198
|
+
if (this.element.dataset.component) {
|
|
199
|
+
child.dataset.component = this.element.dataset.component;
|
|
200
|
+
}
|
|
138
201
|
child.dataset.prop = `${i}`;
|
|
139
202
|
child.dataset.length = `${children.length}`;
|
|
140
203
|
child.editable?.pushValue(
|
|
@@ -178,7 +241,7 @@ export default class EditableArray extends Editable {
|
|
|
178
241
|
children.forEach((child, index) => {
|
|
179
242
|
child.dataset.prop = `${index}`;
|
|
180
243
|
child.dataset.length = `${children.length}`;
|
|
181
|
-
child.editable
|
|
244
|
+
child.editable?.pushValue(
|
|
182
245
|
value,
|
|
183
246
|
{ path: `${index}`, editable: child.editable },
|
|
184
247
|
{ __base_context: this.contextBase ?? {} },
|
|
@@ -216,8 +279,10 @@ export default class EditableArray extends Editable {
|
|
|
216
279
|
) as EditableArrayItemComponent;
|
|
217
280
|
matchingChild.dataset.id = key;
|
|
218
281
|
|
|
219
|
-
|
|
220
|
-
|
|
282
|
+
const componentKey =
|
|
283
|
+
componentKeys[i] || this.element.dataset.component;
|
|
284
|
+
if (componentKey) {
|
|
285
|
+
matchingChild.dataset.component = componentKey;
|
|
221
286
|
}
|
|
222
287
|
}
|
|
223
288
|
} else {
|
|
@@ -235,12 +300,14 @@ export default class EditableArray extends Editable {
|
|
|
235
300
|
this.element.appendChild(matchingChild);
|
|
236
301
|
}
|
|
237
302
|
|
|
238
|
-
matchingChild.editable
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
303
|
+
if (matchingChild.editable) {
|
|
304
|
+
matchingChild.editable.parent = this;
|
|
305
|
+
matchingChild.editable.pushValue(
|
|
306
|
+
value,
|
|
307
|
+
{ path: `${i}`, editable: matchingChild.editable },
|
|
308
|
+
{ __base_context: this.contextBase ?? {} },
|
|
309
|
+
);
|
|
310
|
+
}
|
|
244
311
|
});
|
|
245
312
|
|
|
246
313
|
children.forEach((child, i) => {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
2
|
areEqualEditables,
|
|
3
|
-
areEqualNodes,
|
|
4
3
|
hasEditable,
|
|
5
4
|
hasEditableText,
|
|
6
5
|
isEditableElement,
|
|
@@ -192,12 +191,13 @@ export default class EditableComponent extends Editable {
|
|
|
192
191
|
}
|
|
193
192
|
} else if (renderChild && targetChild) {
|
|
194
193
|
if (
|
|
195
|
-
|
|
194
|
+
renderChild.nodeName !== targetChild.nodeName ||
|
|
196
195
|
isEditableElement(renderChild) ||
|
|
197
196
|
isEditableElement(targetChild)
|
|
198
197
|
) {
|
|
199
198
|
targetChild.replaceWith(renderChild);
|
|
200
199
|
} else {
|
|
200
|
+
this.updateNode(targetChild, renderChild);
|
|
201
201
|
this.updateTree(targetChild, renderChild);
|
|
202
202
|
}
|
|
203
203
|
} else if (renderChild) {
|
|
@@ -211,6 +211,19 @@ export default class EditableComponent extends Editable {
|
|
|
211
211
|
}
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
+
updateNode(targetChild: ChildNode, renderChild: ChildNode) {
|
|
215
|
+
if (targetChild instanceof Element && renderChild instanceof Element) {
|
|
216
|
+
for (const attribute of renderChild.attributes) {
|
|
217
|
+
targetChild.setAttribute(attribute.name, attribute.value);
|
|
218
|
+
}
|
|
219
|
+
for (const attribute of targetChild.attributes) {
|
|
220
|
+
if (!renderChild.hasAttribute(attribute.name)) {
|
|
221
|
+
targetChild.removeAttribute(attribute.name);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
214
227
|
updateEditable(
|
|
215
228
|
renderChild: HTMLElement,
|
|
216
229
|
targetChild: HTMLElement & { editable: Editable },
|
package/nodes/editable.ts
CHANGED
|
@@ -27,8 +27,8 @@ export interface DOMListener {
|
|
|
27
27
|
event: string;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
export interface APIListener
|
|
31
|
-
|
|
30
|
+
export interface APIListener {
|
|
31
|
+
fn: (e: any) => void;
|
|
32
32
|
obj:
|
|
33
33
|
| CloudCannonJavaScriptV1APIFile
|
|
34
34
|
| CloudCannonJavaScriptV1APICollection
|
|
@@ -118,10 +118,7 @@ export default class Editable {
|
|
|
118
118
|
value = await value.items();
|
|
119
119
|
} else if (CloudCannon.isAPIFile(value)) {
|
|
120
120
|
context.file = value;
|
|
121
|
-
if (key
|
|
122
|
-
context.isContent = true;
|
|
123
|
-
value = await value.content.get();
|
|
124
|
-
} else {
|
|
121
|
+
if (key !== "@content") {
|
|
125
122
|
value = await value.data.get();
|
|
126
123
|
}
|
|
127
124
|
} else if (CloudCannon.isAPIDataset(value)) {
|
|
@@ -135,8 +132,13 @@ export default class Editable {
|
|
|
135
132
|
}
|
|
136
133
|
}
|
|
137
134
|
|
|
138
|
-
if (
|
|
135
|
+
if (key === "@content" && CloudCannon.isAPIFile(value)) {
|
|
136
|
+
context.isContent = true;
|
|
137
|
+
value = await value.content.get();
|
|
138
|
+
} else if (value && typeof value === "object" && key in value) {
|
|
139
139
|
value = (value as any)[key];
|
|
140
|
+
} else {
|
|
141
|
+
value = undefined;
|
|
140
142
|
}
|
|
141
143
|
|
|
142
144
|
context.fullPath = context.fullPath ? `${context.fullPath}.${key}` : key;
|
|
@@ -227,6 +229,13 @@ export default class Editable {
|
|
|
227
229
|
}
|
|
228
230
|
|
|
229
231
|
registerListener(listener: EditableListener): void {
|
|
232
|
+
if (this.value !== undefined) {
|
|
233
|
+
listener.editable.pushValue(this.value, listener, {
|
|
234
|
+
...this.contexts,
|
|
235
|
+
__base_context: this.contextBase ?? {},
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
230
239
|
if (
|
|
231
240
|
this.listeners.find(
|
|
232
241
|
({ editable: other, key }) =>
|
|
@@ -236,13 +245,6 @@ export default class Editable {
|
|
|
236
245
|
return;
|
|
237
246
|
}
|
|
238
247
|
|
|
239
|
-
if (this.value !== undefined) {
|
|
240
|
-
listener.editable.pushValue(this.value, listener, {
|
|
241
|
-
...this.contexts,
|
|
242
|
-
__base_context: this.contextBase ?? {},
|
|
243
|
-
});
|
|
244
|
-
}
|
|
245
|
-
|
|
246
248
|
this.listeners.push(listener);
|
|
247
249
|
}
|
|
248
250
|
|
|
@@ -253,6 +255,9 @@ export default class Editable {
|
|
|
253
255
|
}
|
|
254
256
|
|
|
255
257
|
async disconnect(): Promise<void> {
|
|
258
|
+
if (this.disconnecting) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
256
261
|
this.disconnecting = true;
|
|
257
262
|
|
|
258
263
|
if (this.connectPromise) {
|
|
@@ -261,9 +266,10 @@ export default class Editable {
|
|
|
261
266
|
|
|
262
267
|
this.parent?.deregisterListener(this);
|
|
263
268
|
this.parent = null;
|
|
264
|
-
this.APIListeners.forEach(({ obj, fn
|
|
265
|
-
obj.removeEventListener(
|
|
266
|
-
|
|
269
|
+
this.APIListeners.forEach(({ obj, fn }) => {
|
|
270
|
+
obj.removeEventListener("change", fn);
|
|
271
|
+
obj.removeEventListener("delete", fn);
|
|
272
|
+
});
|
|
267
273
|
this.APIListeners = [];
|
|
268
274
|
this.domListeners.forEach(({ event, fn }) => {
|
|
269
275
|
this.element.removeEventListener(event, fn);
|
|
@@ -347,9 +353,9 @@ export default class Editable {
|
|
|
347
353
|
this.APIListeners.push({
|
|
348
354
|
obj,
|
|
349
355
|
fn: handleAPIChange,
|
|
350
|
-
event: "change",
|
|
351
356
|
});
|
|
352
357
|
obj.addEventListener("change", handleAPIChange);
|
|
358
|
+
obj.addEventListener("delete", handleAPIChange);
|
|
353
359
|
handleAPIChange();
|
|
354
360
|
}
|
|
355
361
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloudcannon/editable-regions",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Visual Editing for the CloudCannon CMS.",
|
|
6
6
|
"keywords": [
|
|
@@ -59,6 +59,7 @@
|
|
|
59
59
|
"@types/react": "18.3.12",
|
|
60
60
|
"@types/react-dom": "18.3.1",
|
|
61
61
|
"astro": "^5.14.1",
|
|
62
|
+
"js-beautify": "^1.15.4",
|
|
62
63
|
"typescript": "5.9.3"
|
|
63
64
|
}
|
|
64
65
|
}
|