@cloudcannon/editable-regions 0.0.8 → 0.0.10
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/editable-snippet-component.ts +1 -1
- package/components/index.ts +0 -13
- package/helpers/checks.ts +9 -36
- package/helpers/cloudcannon.d.mts +20 -0
- package/helpers/cloudcannon.mjs +10 -0
- package/helpers/hydrate-editable-regions.ts +7 -1
- package/integrations/astro/astro-integration.mjs +10 -54
- package/integrations/astro/modules/content.js +7 -1
- package/integrations/astro/modules/secrets.js +4 -0
- package/nodes/editable-array-item.ts +9 -9
- package/nodes/editable-array.ts +74 -28
- package/nodes/editable-component.ts +78 -15
- package/nodes/editable-source.ts +8 -2
- package/nodes/editable.ts +197 -33
- package/package.json +2 -2
- package/helpers/loading.ts +0 -7
- package/integrations/astro/modules/actions.js +0 -74
- package/integrations/astro/modules/client-router.astro +0 -5
- package/integrations/astro/modules/i18n.js +0 -76
- package/integrations/astro/modules/middleware.js +0 -27
- package/integrations/astro/modules/transitions.js +0 -63
|
@@ -19,6 +19,7 @@ export default class EditableComponent extends Editable {
|
|
|
19
19
|
|
|
20
20
|
private updatePromise: Promise<void> | undefined;
|
|
21
21
|
private needsReupdate = false;
|
|
22
|
+
private pendingPartialSubtree?: ChildNode | null;
|
|
22
23
|
|
|
23
24
|
getComponents() {
|
|
24
25
|
return getEditableComponentRenderers();
|
|
@@ -51,31 +52,81 @@ export default class EditableComponent extends Editable {
|
|
|
51
52
|
return true;
|
|
52
53
|
}
|
|
53
54
|
|
|
54
|
-
update(): Promise<void> {
|
|
55
|
+
update(partialSubtree?: ChildNode | null): Promise<void> {
|
|
55
56
|
if (this.updatePromise) {
|
|
56
57
|
this.needsReupdate = true;
|
|
58
|
+
this.pendingPartialSubtree = partialSubtree;
|
|
57
59
|
return this.updatePromise;
|
|
58
60
|
}
|
|
59
|
-
this.updatePromise = this._update().then(() => {
|
|
61
|
+
this.updatePromise = this._update(partialSubtree).then(() => {
|
|
60
62
|
this.updatePromise = undefined;
|
|
61
63
|
if (this.needsReupdate) {
|
|
62
64
|
this.needsReupdate = false;
|
|
63
|
-
|
|
65
|
+
const savedPartialSubtree = this.pendingPartialSubtree;
|
|
66
|
+
this.pendingPartialSubtree = undefined;
|
|
67
|
+
return this.update(savedPartialSubtree);
|
|
64
68
|
}
|
|
65
69
|
});
|
|
66
70
|
return this.updatePromise;
|
|
67
71
|
}
|
|
68
72
|
|
|
69
|
-
|
|
73
|
+
santiseComponentOutput(el: HTMLElement): HTMLElement {
|
|
74
|
+
el.querySelectorAll("noscript").forEach((el) => el.remove());
|
|
75
|
+
return el;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async _update(partialSubtree?: ChildNode | null): Promise<void> {
|
|
79
|
+
if (partialSubtree) {
|
|
80
|
+
if (this.controlsElement) {
|
|
81
|
+
this.controlsElement.remove();
|
|
82
|
+
}
|
|
83
|
+
this.updateTree(this.element, partialSubtree);
|
|
84
|
+
if (this.controlsElement) {
|
|
85
|
+
this.element.appendChild(this.controlsElement);
|
|
86
|
+
}
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
70
90
|
this.element.classList.remove("errored");
|
|
71
91
|
|
|
72
92
|
const key = this.element.dataset.component;
|
|
73
93
|
if (!key) {
|
|
74
94
|
return super.update();
|
|
75
95
|
}
|
|
76
|
-
|
|
96
|
+
|
|
97
|
+
let component = this.getComponents()?.[key];
|
|
98
|
+
for (let i = 0; !component && i < 20; i++) {
|
|
99
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
100
|
+
component = this.getComponents()?.[key];
|
|
101
|
+
}
|
|
102
|
+
|
|
77
103
|
if (!component) {
|
|
78
|
-
|
|
104
|
+
this.element.classList.add("errored");
|
|
105
|
+
const error = document.createElement("editable-region-error-card");
|
|
106
|
+
error.setAttribute("heading", "Failed to render component");
|
|
107
|
+
error.setAttribute(
|
|
108
|
+
"message",
|
|
109
|
+
`Failed to find a registered component with the key "${key}". This may mean that the provided "data-component" attribute is incorrect or that the component hasn't been registered.`,
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
const caseMatch = Object.keys(this.getComponents()).find(
|
|
113
|
+
(k) => k.toLowerCase() === key.toLowerCase(),
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
if (Object.keys(this.getComponents()).length === 0) {
|
|
117
|
+
error.setAttribute(
|
|
118
|
+
"hint",
|
|
119
|
+
"There are no registered components currently available. Please check that you've included your registration script and that it's running correctly.",
|
|
120
|
+
);
|
|
121
|
+
} else if (caseMatch) {
|
|
122
|
+
error.setAttribute(
|
|
123
|
+
"hint",
|
|
124
|
+
`The component key "${key}" is not case-sensitive. Did you mean "${caseMatch}"?`,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
this.element.replaceChildren(error);
|
|
129
|
+
return;
|
|
79
130
|
}
|
|
80
131
|
|
|
81
132
|
const value = await realizeAPIValue(this.value);
|
|
@@ -87,7 +138,7 @@ export default class EditableComponent extends Editable {
|
|
|
87
138
|
|
|
88
139
|
let rootEl: HTMLElement;
|
|
89
140
|
try {
|
|
90
|
-
rootEl = await component(value);
|
|
141
|
+
rootEl = this.santiseComponentOutput(await component(value));
|
|
91
142
|
} catch (err: unknown) {
|
|
92
143
|
this.element.classList.add("errored");
|
|
93
144
|
const error = document.createElement("editable-region-error-card");
|
|
@@ -177,10 +228,16 @@ export default class EditableComponent extends Editable {
|
|
|
177
228
|
const listener = this.listeners[i];
|
|
178
229
|
if (listener.editable.element === targetChild) {
|
|
179
230
|
listener.editable.element = renderChild;
|
|
180
|
-
renderChild.editable.pushValue(
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
231
|
+
renderChild.editable.pushValue(
|
|
232
|
+
this.value,
|
|
233
|
+
this.specialProps,
|
|
234
|
+
listener,
|
|
235
|
+
{
|
|
236
|
+
...this.contexts,
|
|
237
|
+
__base_context: this.contextBase ?? {},
|
|
238
|
+
},
|
|
239
|
+
renderChild,
|
|
240
|
+
);
|
|
184
241
|
}
|
|
185
242
|
}
|
|
186
243
|
} else if (hasEditable(targetChild)) {
|
|
@@ -259,10 +316,16 @@ export default class EditableComponent extends Editable {
|
|
|
259
316
|
for (let i = 0; i < this.listeners.length; i++) {
|
|
260
317
|
const listener = this.listeners[i];
|
|
261
318
|
if (listener.editable.element === targetChild) {
|
|
262
|
-
targetChild.editable.pushValue(
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
319
|
+
targetChild.editable.pushValue(
|
|
320
|
+
this.value,
|
|
321
|
+
this.specialProps,
|
|
322
|
+
listener,
|
|
323
|
+
{
|
|
324
|
+
...this.contexts,
|
|
325
|
+
__base_context: this.contextBase ?? {},
|
|
326
|
+
},
|
|
327
|
+
renderChild,
|
|
328
|
+
);
|
|
266
329
|
}
|
|
267
330
|
}
|
|
268
331
|
}
|
package/nodes/editable-source.ts
CHANGED
|
@@ -45,9 +45,15 @@ export default class EditableSource extends EditableText {
|
|
|
45
45
|
|
|
46
46
|
this.file = CloudCannon.file(this.element.dataset.path);
|
|
47
47
|
this.file.addEventListener("change", () => {
|
|
48
|
-
this.file
|
|
48
|
+
this.file
|
|
49
|
+
?.get()
|
|
50
|
+
.then((source) =>
|
|
51
|
+
this.pushValue(source, {}, { path: "", editable: this }),
|
|
52
|
+
);
|
|
53
|
+
});
|
|
54
|
+
this.file.get().then((source) => {
|
|
55
|
+
this.pushValue(source, {}, { path: "", editable: this });
|
|
49
56
|
});
|
|
50
|
-
this.file.get().then(this.pushValue.bind(this));
|
|
51
57
|
}
|
|
52
58
|
|
|
53
59
|
validateConfiguration(): boolean {
|
package/nodes/editable.ts
CHANGED
|
@@ -4,8 +4,13 @@ import type {
|
|
|
4
4
|
CloudCannonJavaScriptV1APIFile,
|
|
5
5
|
} from "@cloudcannon/javascript-api";
|
|
6
6
|
import { hasEditable } from "../helpers/checks";
|
|
7
|
-
import { CloudCannon } from "../helpers/cloudcannon.mjs";
|
|
8
|
-
|
|
7
|
+
import { apiLoadedPromise, CloudCannon } from "../helpers/cloudcannon.mjs";
|
|
8
|
+
|
|
9
|
+
declare global {
|
|
10
|
+
interface HTMLElement {
|
|
11
|
+
__pendingEditableListeners?: EditableListener[];
|
|
12
|
+
}
|
|
13
|
+
}
|
|
9
14
|
|
|
10
15
|
export interface EditableListener {
|
|
11
16
|
editable: Editable;
|
|
@@ -41,12 +46,15 @@ export default class Editable {
|
|
|
41
46
|
domListeners: DOMListener[] = [];
|
|
42
47
|
value: unknown = undefined;
|
|
43
48
|
parent: Editable | null = null;
|
|
49
|
+
pendingParentElement: HTMLElement | null = null;
|
|
44
50
|
element: HTMLElement;
|
|
45
51
|
mounted = false;
|
|
46
52
|
connected = false;
|
|
47
53
|
disconnecting = false;
|
|
48
54
|
needsReconnect = false;
|
|
49
55
|
|
|
56
|
+
specialPropListeners: EditableListener[] = [];
|
|
57
|
+
specialProps: Record<string, unknown> = {};
|
|
50
58
|
propsBase: unknown;
|
|
51
59
|
contextBase?: EditableContext;
|
|
52
60
|
props: Record<string, unknown> = {};
|
|
@@ -150,7 +158,10 @@ export default class Editable {
|
|
|
150
158
|
: key;
|
|
151
159
|
}
|
|
152
160
|
}
|
|
153
|
-
return {
|
|
161
|
+
return {
|
|
162
|
+
value,
|
|
163
|
+
context: context ?? {},
|
|
164
|
+
};
|
|
154
165
|
}
|
|
155
166
|
|
|
156
167
|
shouldUpdate(_value: unknown) {
|
|
@@ -161,45 +172,126 @@ export default class Editable {
|
|
|
161
172
|
return this.value !== undefined;
|
|
162
173
|
}
|
|
163
174
|
|
|
175
|
+
getLiteralProps() {
|
|
176
|
+
let literalPropsBase: unknown;
|
|
177
|
+
const literalProps: Record<string, unknown> = {};
|
|
178
|
+
Object.entries(this.element.dataset).forEach(([propName, propPath]) => {
|
|
179
|
+
if (!propName.startsWith("literal") || typeof propPath !== "string") {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const key =
|
|
184
|
+
propName === "prop" ? undefined : propName.substring(7).toLowerCase();
|
|
185
|
+
let value = propPath;
|
|
186
|
+
try {
|
|
187
|
+
value = JSON.parse(value);
|
|
188
|
+
} catch (_error) {
|
|
189
|
+
// Error intentionally ignored
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (key) {
|
|
193
|
+
literalProps[key] = value;
|
|
194
|
+
} else {
|
|
195
|
+
literalPropsBase = value;
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
return { literalPropsBase, literalProps };
|
|
199
|
+
}
|
|
200
|
+
|
|
164
201
|
async getNewValue(
|
|
165
202
|
value: unknown,
|
|
203
|
+
specialProps: Record<string, unknown>,
|
|
166
204
|
listener?: EditableListener,
|
|
167
205
|
contexts?: { [key: string]: EditableContext },
|
|
168
206
|
): Promise<unknown> {
|
|
169
207
|
const { key, path } = listener ?? {};
|
|
170
208
|
|
|
171
|
-
|
|
172
|
-
|
|
209
|
+
if (typeof path === "string") {
|
|
210
|
+
const { value: resolvedValue, context: newContext } =
|
|
211
|
+
await this.lookupPathAndContext(path, value, contexts);
|
|
173
212
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
213
|
+
if (!key) {
|
|
214
|
+
this.propsBase = resolvedValue;
|
|
215
|
+
this.contextBase = newContext;
|
|
216
|
+
} else {
|
|
217
|
+
this.props[key] = resolvedValue;
|
|
218
|
+
this.contexts[key] = newContext;
|
|
219
|
+
}
|
|
180
220
|
}
|
|
181
221
|
|
|
182
|
-
|
|
183
|
-
|
|
222
|
+
this.specialProps = this.getSpecialProps(specialProps);
|
|
223
|
+
const { literalPropsBase, literalProps } = this.getLiteralProps();
|
|
224
|
+
|
|
225
|
+
let newValue: unknown;
|
|
226
|
+
const specialPropsBase = this.specialPropListeners.find(({ key }) => !key);
|
|
227
|
+
|
|
228
|
+
if (this.propsBase !== undefined) {
|
|
229
|
+
newValue = this.propsBase;
|
|
230
|
+
} else if (specialPropsBase?.path) {
|
|
231
|
+
newValue = structuredClone(this.specialProps[specialPropsBase.path]);
|
|
232
|
+
} else if (literalPropsBase !== undefined) {
|
|
233
|
+
newValue = literalPropsBase;
|
|
184
234
|
}
|
|
185
235
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
(acc
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
236
|
+
if (Object.entries(this.props).length > 0) {
|
|
237
|
+
newValue = Object.entries(this.props).reduce(
|
|
238
|
+
(acc, [key, val]) => {
|
|
239
|
+
(acc as any)[key] = structuredClone(val);
|
|
240
|
+
return acc;
|
|
241
|
+
},
|
|
242
|
+
newValue && typeof newValue === "object"
|
|
243
|
+
? structuredClone(newValue)
|
|
244
|
+
: {},
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const filteredSpecialPropsListener = this.specialPropListeners.filter(
|
|
249
|
+
({ key }) => !!key,
|
|
192
250
|
);
|
|
251
|
+
if (filteredSpecialPropsListener.length > 0) {
|
|
252
|
+
newValue = filteredSpecialPropsListener.reduce(
|
|
253
|
+
(acc, { key, path }) => {
|
|
254
|
+
if (key && path) {
|
|
255
|
+
(acc as any)[key] = structuredClone(this.specialProps[path]);
|
|
256
|
+
}
|
|
257
|
+
return acc;
|
|
258
|
+
},
|
|
259
|
+
structuredClone(
|
|
260
|
+
newValue && typeof newValue === "object"
|
|
261
|
+
? structuredClone(newValue)
|
|
262
|
+
: {},
|
|
263
|
+
),
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (Object.entries(literalProps).length > 0) {
|
|
268
|
+
newValue = Object.entries(literalProps).reduce(
|
|
269
|
+
(acc, [key, val]) => {
|
|
270
|
+
(acc as any)[key] = structuredClone(val);
|
|
271
|
+
return acc;
|
|
272
|
+
},
|
|
273
|
+
newValue && typeof newValue === "object"
|
|
274
|
+
? structuredClone(newValue)
|
|
275
|
+
: {},
|
|
276
|
+
);
|
|
277
|
+
}
|
|
193
278
|
|
|
194
279
|
return this.validateValue(newValue);
|
|
195
280
|
}
|
|
196
281
|
|
|
197
282
|
async pushValue(
|
|
198
283
|
value: unknown,
|
|
284
|
+
specialProps: Record<string, unknown>,
|
|
199
285
|
listener?: EditableListener,
|
|
200
286
|
contexts?: { [key: string]: EditableContext },
|
|
287
|
+
partialSubtree?: ChildNode | null,
|
|
201
288
|
): Promise<void> {
|
|
202
|
-
const newValue = await this.getNewValue(
|
|
289
|
+
const newValue = await this.getNewValue(
|
|
290
|
+
value,
|
|
291
|
+
specialProps,
|
|
292
|
+
listener,
|
|
293
|
+
contexts,
|
|
294
|
+
);
|
|
203
295
|
|
|
204
296
|
if (typeof newValue === "undefined" || !this.shouldUpdate(newValue)) {
|
|
205
297
|
return;
|
|
@@ -209,17 +301,17 @@ export default class Editable {
|
|
|
209
301
|
if (this.connected && !this.mounted) {
|
|
210
302
|
this.mounted = true;
|
|
211
303
|
this.mount();
|
|
212
|
-
return this.update();
|
|
304
|
+
return this.update(partialSubtree);
|
|
213
305
|
}
|
|
214
306
|
|
|
215
307
|
if (this.mounted) {
|
|
216
|
-
return this.update();
|
|
308
|
+
return this.update(partialSubtree);
|
|
217
309
|
}
|
|
218
310
|
}
|
|
219
311
|
|
|
220
|
-
update(): void {
|
|
312
|
+
update(_partialSubtree?: ChildNode | null): void {
|
|
221
313
|
this.listeners.forEach((listener) =>
|
|
222
|
-
listener.editable.pushValue(this.value, listener, {
|
|
314
|
+
listener.editable.pushValue(this.value, this.specialProps, listener, {
|
|
223
315
|
...this.contexts,
|
|
224
316
|
__base_context: this.contextBase ?? {},
|
|
225
317
|
}),
|
|
@@ -232,7 +324,7 @@ export default class Editable {
|
|
|
232
324
|
|
|
233
325
|
registerListener(listener: EditableListener): void {
|
|
234
326
|
if (this.value !== undefined) {
|
|
235
|
-
listener.editable.pushValue(this.value, listener, {
|
|
327
|
+
listener.editable.pushValue(this.value, this.specialProps, listener, {
|
|
236
328
|
...this.contexts,
|
|
237
329
|
__base_context: this.contextBase ?? {},
|
|
238
330
|
});
|
|
@@ -256,6 +348,29 @@ export default class Editable {
|
|
|
256
348
|
);
|
|
257
349
|
}
|
|
258
350
|
|
|
351
|
+
private queueListenerOnParent(
|
|
352
|
+
parentElement: HTMLElement,
|
|
353
|
+
listener: EditableListener,
|
|
354
|
+
): void {
|
|
355
|
+
if (!parentElement.__pendingEditableListeners) {
|
|
356
|
+
parentElement.__pendingEditableListeners = [];
|
|
357
|
+
}
|
|
358
|
+
parentElement.__pendingEditableListeners.push(listener);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
private replayPendingListeners(): void {
|
|
362
|
+
const pending = this.element.__pendingEditableListeners;
|
|
363
|
+
if (!pending || pending.length === 0) {
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
this.element.__pendingEditableListeners = [];
|
|
367
|
+
for (const listener of pending) {
|
|
368
|
+
listener.editable.parent = this;
|
|
369
|
+
listener.editable.pendingParentElement = null;
|
|
370
|
+
this.registerListener(listener);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
259
374
|
async disconnect(): Promise<void> {
|
|
260
375
|
if (this.disconnecting) {
|
|
261
376
|
return;
|
|
@@ -268,6 +383,15 @@ export default class Editable {
|
|
|
268
383
|
|
|
269
384
|
this.parent?.deregisterListener(this);
|
|
270
385
|
this.parent = null;
|
|
386
|
+
if (this.pendingParentElement) {
|
|
387
|
+
const pending = this.pendingParentElement.__pendingEditableListeners;
|
|
388
|
+
if (pending) {
|
|
389
|
+
this.pendingParentElement.__pendingEditableListeners = pending.filter(
|
|
390
|
+
(listener) => listener.editable !== this,
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
this.pendingParentElement = null;
|
|
394
|
+
}
|
|
271
395
|
this.APIListeners.forEach(({ obj, fn }) => {
|
|
272
396
|
obj.removeEventListener("change", fn);
|
|
273
397
|
obj.removeEventListener("delete", fn);
|
|
@@ -277,6 +401,7 @@ export default class Editable {
|
|
|
277
401
|
this.element.removeEventListener(event, fn);
|
|
278
402
|
});
|
|
279
403
|
this.domListeners = [];
|
|
404
|
+
this.specialPropListeners = [];
|
|
280
405
|
this.connected = false;
|
|
281
406
|
this.connectPromise = undefined;
|
|
282
407
|
this.disconnecting = false;
|
|
@@ -299,7 +424,7 @@ export default class Editable {
|
|
|
299
424
|
if (this.connectPromise) {
|
|
300
425
|
return;
|
|
301
426
|
}
|
|
302
|
-
this.connectPromise =
|
|
427
|
+
this.connectPromise = apiLoadedPromise.then(() => {
|
|
303
428
|
this.setupListeners();
|
|
304
429
|
this.connected = true;
|
|
305
430
|
if (!this.mounted && this.shouldMount()) {
|
|
@@ -316,11 +441,14 @@ export default class Editable {
|
|
|
316
441
|
}
|
|
317
442
|
|
|
318
443
|
setupListeners(): void {
|
|
319
|
-
let
|
|
444
|
+
let parentElement: HTMLElement | null = null;
|
|
320
445
|
let parent = this.element.parentElement;
|
|
321
446
|
while (parent) {
|
|
322
|
-
if (
|
|
323
|
-
|
|
447
|
+
if (
|
|
448
|
+
parent.tagName.startsWith("EDITABLE-") ||
|
|
449
|
+
"editable" in parent.dataset
|
|
450
|
+
) {
|
|
451
|
+
parentElement ??= parent;
|
|
324
452
|
}
|
|
325
453
|
|
|
326
454
|
if (parent.tagName === "A") {
|
|
@@ -329,7 +457,13 @@ export default class Editable {
|
|
|
329
457
|
parent = parent.parentElement;
|
|
330
458
|
}
|
|
331
459
|
|
|
332
|
-
|
|
460
|
+
let hasParentListener = false;
|
|
461
|
+
|
|
462
|
+
if (parentElement && hasEditable(parentElement)) {
|
|
463
|
+
this.parent = parentElement.editable;
|
|
464
|
+
} else if (parentElement) {
|
|
465
|
+
this.pendingParentElement = parentElement;
|
|
466
|
+
}
|
|
333
467
|
|
|
334
468
|
Object.entries(this.element.dataset).forEach(([propName, propPath]) => {
|
|
335
469
|
if (!propName.startsWith("prop") || typeof propPath !== "string") {
|
|
@@ -346,8 +480,20 @@ export default class Editable {
|
|
|
346
480
|
path: source,
|
|
347
481
|
};
|
|
348
482
|
|
|
349
|
-
if (!absolute &&
|
|
350
|
-
|
|
483
|
+
if (!absolute && source.startsWith("@") && source !== "@content") {
|
|
484
|
+
this.specialPropListeners.push(listener);
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
if (!absolute && this.parent) {
|
|
489
|
+
hasParentListener = true;
|
|
490
|
+
this.parent.registerListener(listener);
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
if (!absolute && this.pendingParentElement) {
|
|
495
|
+
hasParentListener = true;
|
|
496
|
+
this.queueListenerOnParent(this.pendingParentElement, listener);
|
|
351
497
|
return;
|
|
352
498
|
}
|
|
353
499
|
|
|
@@ -362,7 +508,9 @@ export default class Editable {
|
|
|
362
508
|
? undefined
|
|
363
509
|
: `@file[${file?.path}]`;
|
|
364
510
|
const handleAPIChange = () => {
|
|
365
|
-
this.pushValue(obj, listener, {
|
|
511
|
+
this.pushValue(obj, {}, listener, {
|
|
512
|
+
__base_context: { fullPath },
|
|
513
|
+
});
|
|
366
514
|
};
|
|
367
515
|
this.APIListeners.push({
|
|
368
516
|
obj,
|
|
@@ -374,7 +522,14 @@ export default class Editable {
|
|
|
374
522
|
}
|
|
375
523
|
});
|
|
376
524
|
|
|
525
|
+
if (this.parent && !hasParentListener) {
|
|
526
|
+
this.parent.registerListener({ editable: this });
|
|
527
|
+
} else if (this.pendingParentElement && !hasParentListener) {
|
|
528
|
+
this.queueListenerOnParent(this.pendingParentElement, { editable: this });
|
|
529
|
+
}
|
|
530
|
+
|
|
377
531
|
this.addEventListener("cloudcannon-api", this.handleApiEvent.bind(this));
|
|
532
|
+
this.replayPendingListeners();
|
|
378
533
|
}
|
|
379
534
|
|
|
380
535
|
handleApiEvent(e: any): void {
|
|
@@ -597,4 +752,13 @@ export default class Editable {
|
|
|
597
752
|
currentFile,
|
|
598
753
|
};
|
|
599
754
|
}
|
|
755
|
+
|
|
756
|
+
getSpecialProps(
|
|
757
|
+
incomingSpecialProps: Record<string, unknown> = {},
|
|
758
|
+
): Record<string, unknown> {
|
|
759
|
+
return {
|
|
760
|
+
...this.specialProps,
|
|
761
|
+
...incomingSpecialProps,
|
|
762
|
+
};
|
|
763
|
+
}
|
|
600
764
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloudcannon/editable-regions",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.10",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Visual Editing for the CloudCannon CMS.",
|
|
6
6
|
"keywords": [
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"@biomejs/biome": "2.3.6",
|
|
57
57
|
"@cloudcannon/javascript-api": "0.0.10",
|
|
58
58
|
"@types/js-beautify": "1.14.3",
|
|
59
|
-
"@types/react": "19.2.
|
|
59
|
+
"@types/react": "19.2.8",
|
|
60
60
|
"@types/react-dom": "18.3.1",
|
|
61
61
|
"astro": "^5.14.1",
|
|
62
62
|
"js-beautify": "^1.15.4",
|
package/helpers/loading.ts
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
export const actions = new Proxy(
|
|
2
|
-
{},
|
|
3
|
-
{
|
|
4
|
-
get() {
|
|
5
|
-
console.warn(
|
|
6
|
-
"[CloudCannon] actions is not supported in an editable component. Please use an editing fallback instead.",
|
|
7
|
-
);
|
|
8
|
-
return () => {};
|
|
9
|
-
},
|
|
10
|
-
},
|
|
11
|
-
);
|
|
12
|
-
|
|
13
|
-
export const defineAction = () => {
|
|
14
|
-
console.warn(
|
|
15
|
-
"[CloudCannon] defineAction is not supported in an editable component. Please use an editing fallback instead.",
|
|
16
|
-
);
|
|
17
|
-
return {
|
|
18
|
-
handler: () => {},
|
|
19
|
-
input: null,
|
|
20
|
-
};
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
export const isInputError = () => {
|
|
24
|
-
console.warn(
|
|
25
|
-
"[CloudCannon] isInputError is not supported in an editable component. Please use an editing fallback instead.",
|
|
26
|
-
);
|
|
27
|
-
return false;
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
export const isActionError = () => {
|
|
31
|
-
console.warn(
|
|
32
|
-
"[CloudCannon] isActionError is not supported in an editable component. Please use an editing fallback instead.",
|
|
33
|
-
);
|
|
34
|
-
return false;
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
export class ActionError extends Error {
|
|
38
|
-
/**
|
|
39
|
-
* @param {any} code
|
|
40
|
-
* @param {any} message
|
|
41
|
-
*/
|
|
42
|
-
constructor(code, message) {
|
|
43
|
-
super(message);
|
|
44
|
-
console.warn(
|
|
45
|
-
"[CloudCannon] ActionError is not supported in an editable component. Please use an editing fallback instead.",
|
|
46
|
-
);
|
|
47
|
-
this.code = code;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export const getActionContext = () => {
|
|
52
|
-
console.warn(
|
|
53
|
-
"[CloudCannon] getActionContext is not supported in an editable component. Please use an editing fallback instead.",
|
|
54
|
-
);
|
|
55
|
-
return {
|
|
56
|
-
action: undefined,
|
|
57
|
-
setActionResult: () => {},
|
|
58
|
-
serializeActionResult: () => ({}),
|
|
59
|
-
};
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
export const deserializeActionResult = () => {
|
|
63
|
-
console.warn(
|
|
64
|
-
"[CloudCannon] deserializeActionResult is not supported in an editable component. Please use an editing fallback instead.",
|
|
65
|
-
);
|
|
66
|
-
return {};
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
export const getActionPath = () => {
|
|
70
|
-
console.warn(
|
|
71
|
-
"[CloudCannon] getActionPath is not supported in an editable component. Please use an editing fallback instead.",
|
|
72
|
-
);
|
|
73
|
-
return "";
|
|
74
|
-
};
|