@cloudcannon/editable-regions 0.0.3 → 0.0.4
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 -0
- 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 +100 -44
- package/nodes/editable-array.ts +35 -4
- package/nodes/editable-component.ts +93 -35
- package/nodes/editable-snippet.ts +0 -6
- package/nodes/editable-text.ts +2 -8
- package/nodes/editable.ts +203 -107
- package/package.json +2 -2
package/nodes/editable-array.ts
CHANGED
|
@@ -31,6 +31,9 @@ export default class EditableArray extends Editable {
|
|
|
31
31
|
| null
|
|
32
32
|
| undefined = undefined;
|
|
33
33
|
|
|
34
|
+
private updatePromise: Promise<void> | undefined;
|
|
35
|
+
private needsReupdate = false;
|
|
36
|
+
|
|
34
37
|
validateConfiguration(): boolean {
|
|
35
38
|
const prop = this.element.dataset.prop;
|
|
36
39
|
if (typeof prop !== "string") {
|
|
@@ -65,7 +68,22 @@ export default class EditableArray extends Editable {
|
|
|
65
68
|
return value;
|
|
66
69
|
}
|
|
67
70
|
|
|
68
|
-
|
|
71
|
+
update(): Promise<void> {
|
|
72
|
+
if (this.updatePromise) {
|
|
73
|
+
this.needsReupdate = true;
|
|
74
|
+
return this.updatePromise;
|
|
75
|
+
}
|
|
76
|
+
this.updatePromise = this._update().then(() => {
|
|
77
|
+
this.updatePromise = undefined;
|
|
78
|
+
if (this.needsReupdate) {
|
|
79
|
+
this.needsReupdate = false;
|
|
80
|
+
return this.update();
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
return this.updatePromise;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private async _update(): Promise<void> {
|
|
69
87
|
let value: unknown[] | CloudCannonJavaScriptV1APIFile[];
|
|
70
88
|
if (CloudCannon.isAPICollection(this.value)) {
|
|
71
89
|
value = await this.value.items();
|
|
@@ -119,7 +137,11 @@ export default class EditableArray extends Editable {
|
|
|
119
137
|
}
|
|
120
138
|
child.dataset.prop = `${i}`;
|
|
121
139
|
child.dataset.length = `${children.length}`;
|
|
122
|
-
child.editable?.pushValue(
|
|
140
|
+
child.editable?.pushValue(
|
|
141
|
+
value,
|
|
142
|
+
{ path: `${i}`, editable: child.editable },
|
|
143
|
+
{ __base_context: this.contextBase ?? {} },
|
|
144
|
+
);
|
|
123
145
|
}
|
|
124
146
|
return;
|
|
125
147
|
}
|
|
@@ -155,7 +177,12 @@ export default class EditableArray extends Editable {
|
|
|
155
177
|
if (equal && children.length === dataKeys?.length) {
|
|
156
178
|
children.forEach((child, index) => {
|
|
157
179
|
child.dataset.prop = `${index}`;
|
|
158
|
-
child.
|
|
180
|
+
child.dataset.length = `${children.length}`;
|
|
181
|
+
child.editable.pushValue(
|
|
182
|
+
value,
|
|
183
|
+
{ path: `${index}`, editable: child.editable },
|
|
184
|
+
{ __base_context: this.contextBase ?? {} },
|
|
185
|
+
);
|
|
159
186
|
});
|
|
160
187
|
return;
|
|
161
188
|
}
|
|
@@ -209,7 +236,11 @@ export default class EditableArray extends Editable {
|
|
|
209
236
|
}
|
|
210
237
|
|
|
211
238
|
matchingChild.editable.parent = this;
|
|
212
|
-
matchingChild.editable.pushValue(
|
|
239
|
+
matchingChild.editable.pushValue(
|
|
240
|
+
value,
|
|
241
|
+
{ path: `${i}`, editable: matchingChild.editable },
|
|
242
|
+
{ __base_context: this.contextBase ?? {} },
|
|
243
|
+
);
|
|
213
244
|
});
|
|
214
245
|
|
|
215
246
|
children.forEach((child, i) => {
|
|
@@ -11,35 +11,30 @@ import "../components/ui/editable-region-error-card.js";
|
|
|
11
11
|
import "../components/ui/editable-component-controls.js";
|
|
12
12
|
import type EditableComponentControls from "../components/ui/editable-component-controls.js";
|
|
13
13
|
import {
|
|
14
|
-
CloudCannon,
|
|
15
14
|
getEditableComponentRenderers,
|
|
15
|
+
realizeAPIValue,
|
|
16
16
|
} from "../helpers/cloudcannon.js";
|
|
17
17
|
|
|
18
|
-
const realizeAPIValue = async (value: unknown): Promise<unknown> => {
|
|
19
|
-
if (CloudCannon.isAPICollection(value)) {
|
|
20
|
-
const items = await value.items();
|
|
21
|
-
return Promise.all(items.map(realizeAPIValue));
|
|
22
|
-
}
|
|
23
|
-
if (CloudCannon.isAPIFile(value)) {
|
|
24
|
-
return value.data.get();
|
|
25
|
-
}
|
|
26
|
-
if (CloudCannon.isAPIDataset(value)) {
|
|
27
|
-
const items = await value.items();
|
|
28
|
-
if (Array.isArray(items)) {
|
|
29
|
-
return Promise.all(items.map(realizeAPIValue));
|
|
30
|
-
}
|
|
31
|
-
return items.data.get();
|
|
32
|
-
}
|
|
33
|
-
return value;
|
|
34
|
-
};
|
|
35
|
-
|
|
36
18
|
export default class EditableComponent extends Editable {
|
|
37
19
|
protected controlsElement?: EditableComponentControls;
|
|
38
20
|
|
|
21
|
+
private updatePromise: Promise<void> | undefined;
|
|
22
|
+
private needsReupdate = false;
|
|
23
|
+
|
|
39
24
|
getComponents() {
|
|
40
25
|
return getEditableComponentRenderers();
|
|
41
26
|
}
|
|
42
27
|
|
|
28
|
+
shouldMount(): boolean {
|
|
29
|
+
if (super.shouldMount()) {
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return !Object.keys(this.element.dataset).some((key) =>
|
|
34
|
+
key.startsWith("prop"),
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
43
38
|
validateConfiguration(): boolean {
|
|
44
39
|
const key = this.element.dataset.component;
|
|
45
40
|
if (!key) {
|
|
@@ -57,7 +52,22 @@ export default class EditableComponent extends Editable {
|
|
|
57
52
|
return true;
|
|
58
53
|
}
|
|
59
54
|
|
|
60
|
-
|
|
55
|
+
update(): Promise<void> {
|
|
56
|
+
if (this.updatePromise) {
|
|
57
|
+
this.needsReupdate = true;
|
|
58
|
+
return this.updatePromise;
|
|
59
|
+
}
|
|
60
|
+
this.updatePromise = this._update().then(() => {
|
|
61
|
+
this.updatePromise = undefined;
|
|
62
|
+
if (this.needsReupdate) {
|
|
63
|
+
this.needsReupdate = false;
|
|
64
|
+
return this.update();
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
return this.updatePromise;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async _update(): Promise<void> {
|
|
61
71
|
this.element.classList.remove("errored");
|
|
62
72
|
|
|
63
73
|
const key = this.element.dataset.component;
|
|
@@ -168,24 +178,17 @@ export default class EditableComponent extends Editable {
|
|
|
168
178
|
const listener = this.listeners[i];
|
|
169
179
|
if (listener.editable.element === targetChild) {
|
|
170
180
|
listener.editable.element = renderChild;
|
|
171
|
-
renderChild.editable.pushValue(this.value, listener
|
|
181
|
+
renderChild.editable.pushValue(this.value, listener, {
|
|
182
|
+
...this.contexts,
|
|
183
|
+
__base_context: this.contextBase ?? {},
|
|
184
|
+
});
|
|
172
185
|
}
|
|
173
186
|
}
|
|
174
187
|
} else if (hasEditable(targetChild)) {
|
|
175
|
-
|
|
176
|
-
const listener = this.listeners[i];
|
|
177
|
-
if (listener.editable.element === targetChild) {
|
|
178
|
-
targetChild.editable.pushValue(this.value, listener);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
188
|
+
this.updateEditable(renderChild, targetChild);
|
|
181
189
|
}
|
|
182
190
|
} else if (hasEditable(targetChild)) {
|
|
183
|
-
|
|
184
|
-
const listener = this.listeners[i];
|
|
185
|
-
if (listener.editable.element === targetChild) {
|
|
186
|
-
targetChild.editable.pushValue(this.value, listener);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
191
|
+
this.updateEditable(renderChild, targetChild);
|
|
189
192
|
}
|
|
190
193
|
} else if (renderChild && targetChild) {
|
|
191
194
|
if (
|
|
@@ -208,11 +211,66 @@ export default class EditableComponent extends Editable {
|
|
|
208
211
|
}
|
|
209
212
|
}
|
|
210
213
|
|
|
211
|
-
|
|
214
|
+
updateEditable(
|
|
215
|
+
renderChild: HTMLElement,
|
|
216
|
+
targetChild: HTMLElement & { editable: Editable },
|
|
217
|
+
) {
|
|
218
|
+
for (const attribute of renderChild.attributes) {
|
|
219
|
+
if (attribute.name !== "class") {
|
|
220
|
+
targetChild.setAttribute(attribute.name, attribute.value);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
for (const attribute of targetChild.attributes) {
|
|
224
|
+
if (
|
|
225
|
+
!renderChild.hasAttribute(attribute.name) &&
|
|
226
|
+
attribute.name !== "class" &&
|
|
227
|
+
attribute.name !== "contenteditable"
|
|
228
|
+
) {
|
|
229
|
+
targetChild.removeAttribute(attribute.name);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
for (const className of renderChild.classList) {
|
|
234
|
+
targetChild.classList.add(className);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
for (const className of targetChild.classList) {
|
|
238
|
+
if (
|
|
239
|
+
!renderChild.classList.contains(className) &&
|
|
240
|
+
!className.includes("ProseMirror")
|
|
241
|
+
) {
|
|
242
|
+
targetChild.classList.remove(className);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
for (let i = 0; i < this.listeners.length; i++) {
|
|
247
|
+
const listener = this.listeners[i];
|
|
248
|
+
if (listener.editable.element === targetChild) {
|
|
249
|
+
targetChild.editable.pushValue(this.value, listener, {
|
|
250
|
+
...this.contexts,
|
|
251
|
+
__base_context: this.contextBase ?? {},
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
dispatchEdit(source?: string, originalEvent?: MouseEvent) {
|
|
258
|
+
const rect = this.element.getBoundingClientRect();
|
|
212
259
|
this.element.dispatchEvent(
|
|
213
260
|
new CustomEvent("cloudcannon-api", {
|
|
214
261
|
bubbles: true,
|
|
215
|
-
detail: {
|
|
262
|
+
detail: {
|
|
263
|
+
action: "edit",
|
|
264
|
+
source,
|
|
265
|
+
position: {
|
|
266
|
+
x: originalEvent?.clientX ?? 0,
|
|
267
|
+
y: originalEvent?.clientY ?? 0,
|
|
268
|
+
left: rect.left,
|
|
269
|
+
width: rect.width,
|
|
270
|
+
top: rect.top,
|
|
271
|
+
height: rect.height,
|
|
272
|
+
},
|
|
273
|
+
},
|
|
216
274
|
}),
|
|
217
275
|
);
|
|
218
276
|
}
|
|
@@ -139,10 +139,4 @@ export default class EditableSnippet extends EditableComponent {
|
|
|
139
139
|
validateConfiguration(): boolean {
|
|
140
140
|
return true;
|
|
141
141
|
}
|
|
142
|
-
|
|
143
|
-
resolveSource(source?: string): string | undefined {
|
|
144
|
-
return this.parent
|
|
145
|
-
? `${this.parent.resolveSource()}.@snippet[${this.element.getAttribute("data-cms-snippet-id")}].${source}`
|
|
146
|
-
: `@snippet[${this.element.getAttribute("data-cms-snippet-id")}].${source}`;
|
|
147
|
-
}
|
|
148
142
|
}
|
package/nodes/editable-text.ts
CHANGED
|
@@ -117,13 +117,7 @@ export default class EditableText extends Editable {
|
|
|
117
117
|
return this.editor;
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
if (!source) {
|
|
123
|
-
throw new Error("Source not found");
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const inputConfig = source.endsWith("@content")
|
|
120
|
+
const inputConfig = this.contextBase?.isContent
|
|
127
121
|
? { type: "markdown" }
|
|
128
122
|
: await this.dispatchGetInputConfig(this.element.dataset.prop);
|
|
129
123
|
|
|
@@ -132,7 +126,7 @@ export default class EditableText extends Editable {
|
|
|
132
126
|
this.onChange.bind(this),
|
|
133
127
|
{
|
|
134
128
|
elementType: this.element.dataset.type,
|
|
135
|
-
editableType:
|
|
129
|
+
editableType: this.contextBase?.isContent ? "content" : undefined,
|
|
136
130
|
inputConfig,
|
|
137
131
|
},
|
|
138
132
|
);
|