@cloudcannon/editable-regions 0.0.6 → 0.0.7

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.
@@ -92,15 +92,11 @@ export default class EditableArrayItemControls extends EditableComponentControls
92
92
  super.render(shadow);
93
93
 
94
94
  this.dragHandle = document.createElement("button");
95
+ this.dragHandle.type = "button";
95
96
  this.dragHandle.onclick = (e) => {
96
97
  e.stopPropagation();
97
- if (this.contextMenu?.classList.contains("open")) {
98
- this.contextMenu?.classList.remove("open");
99
- this.removeAttribute("open");
100
- } else if (this.contextMenu && this.contextMenu.childElementCount > 0) {
101
- this.contextMenu?.classList.add("open");
102
- this.setAttribute("open", "true");
103
- }
98
+ e.preventDefault();
99
+ this.toggleContextMenu();
104
100
  };
105
101
  this.dragHandle.ondragstart = () => {
106
102
  this.contextMenu?.classList.remove("open");
@@ -7,6 +7,16 @@ export default class EditableComponentControls extends HTMLElement {
7
7
 
8
8
  private editButton?: HTMLButtonElement;
9
9
 
10
+ toggleContextMenu() {
11
+ if (this.contextMenu?.classList.contains("open")) {
12
+ this.contextMenu?.classList.remove("open");
13
+ this.removeAttribute("open");
14
+ } else if (this.contextMenu && this.contextMenu.childElementCount > 0) {
15
+ this.contextMenu?.classList.add("open");
16
+ this.setAttribute("open", "true");
17
+ }
18
+ }
19
+
10
20
  render(shadow: ShadowRoot): void {
11
21
  const style = document.createElement("style");
12
22
  style.textContent = styleContent;
@@ -21,18 +31,23 @@ export default class EditableComponentControls extends HTMLElement {
21
31
  shadow.appendChild(this.contextMenu);
22
32
 
23
33
  this.editButton = document.createElement("button");
34
+ this.editButton.type = "button";
24
35
  this.editButton.innerHTML = '<cc-icon name="edit"></cc-icon>';
25
36
  this.editButton.onclick = () => {
26
37
  this.dispatchEvent(new CustomEvent("edit", { detail: this }));
27
38
  };
28
39
  this.buttonRow.append(this.editButton);
29
40
 
30
- this.onclick = () => {
41
+ this.onclick = (e) => {
42
+ e.preventDefault();
31
43
  this.contextMenu?.classList.remove("open");
32
44
  this.removeAttribute("open");
33
45
  };
34
46
 
35
- this.onblur = () => {
47
+ this.onblur = (e) => {
48
+ if ((e.relatedTarget as HTMLElement)?.tagName === "A") {
49
+ return;
50
+ }
36
51
  this.contextMenu?.classList.remove("open");
37
52
  this.removeAttribute("open");
38
53
  };
@@ -44,11 +44,11 @@ export default class EditableArrayItem extends EditableComponent {
44
44
  }
45
45
 
46
46
  isValidDropzone(e: DragEvent): boolean {
47
- const source = this.parent?.contextBase?.filePath;
48
- if (!source || !e.dataTransfer) {
47
+ if (!e.dataTransfer) {
49
48
  return false;
50
49
  }
51
50
 
51
+ const source = this.parent?.contextBase?.fullPath ?? "@currentFile";
52
52
  if (e.dataTransfer?.types.includes(source.toLowerCase())) {
53
53
  return true;
54
54
  }
@@ -96,11 +96,11 @@ export default class EditableArrayItem extends EditableComponent {
96
96
  }
97
97
 
98
98
  onDragStart(e: DragEvent): void {
99
- const source = this.parent?.contextBase?.filePath;
100
- if (!source || !e.dataTransfer || !this.element.dataset.prop) {
99
+ if (!e.dataTransfer || !this.element.dataset.prop) {
101
100
  return;
102
101
  }
103
102
 
103
+ const source = this.parent?.contextBase?.fullPath ?? "@currentFile";
104
104
  const clientRect = this.element.getBoundingClientRect();
105
105
 
106
106
  e.stopPropagation();
@@ -423,11 +423,7 @@ export default class EditableArrayItem extends EditableComponent {
423
423
  return;
424
424
  }
425
425
 
426
- const source = this.parent?.contextBase?.filePath;
427
- if (!source) {
428
- throw new Error("Source not found");
429
- }
430
-
426
+ const source = this.parent?.contextBase?.fullPath ?? "@currentFile";
431
427
  const dragType = this.getDragType();
432
428
  const sameArrayData = e.dataTransfer.getData(source);
433
429
  const otherArrayData = dragType
@@ -29,6 +29,7 @@ export default class EditableArray extends Editable {
29
29
  value:
30
30
  | CloudCannonJavaScriptV1APICollection
31
31
  | CloudCannonJavaScriptV1APIDataset
32
+ | CloudCannonJavaScriptV1APIFile
32
33
  | unknown[]
33
34
  | null
34
35
  | undefined = undefined;
@@ -42,6 +43,7 @@ export default class EditableArray extends Editable {
42
43
  return;
43
44
  }
44
45
 
46
+ const __base_context = { ...this.contextBase };
45
47
  let value: unknown[] | CloudCannonJavaScriptV1APIFile[];
46
48
  if (CloudCannon.isAPICollection(this.value)) {
47
49
  value = await this.value.items();
@@ -51,8 +53,13 @@ export default class EditableArray extends Editable {
51
53
  value = items;
52
54
  } else {
53
55
  const data = await items.data.get();
56
+ __base_context.file = items;
54
57
  value = Array.isArray(data) ? data : [];
55
58
  }
59
+ } else if (CloudCannon.isAPIFile(this.value)) {
60
+ const data = await this.value.data.get();
61
+ __base_context.file = this.value;
62
+ value = Array.isArray(data) ? data : [];
56
63
  } else if (Array.isArray(this.value)) {
57
64
  value = this.value;
58
65
  } else {
@@ -84,7 +91,7 @@ export default class EditableArray extends Editable {
84
91
 
85
92
  if (listener.path) {
86
93
  listener.editable.pushValue(value, listener, {
87
- __base_context: this.contextBase ?? {},
94
+ __base_context,
88
95
  });
89
96
  }
90
97
  }
@@ -113,7 +120,8 @@ export default class EditableArray extends Editable {
113
120
  !Array.isArray(value) &&
114
121
  value !== null &&
115
122
  !CloudCannon.isAPICollection(value) &&
116
- !CloudCannon.isAPIDataset(value)
123
+ !CloudCannon.isAPIDataset(value) &&
124
+ !CloudCannon.isAPIFile(value)
117
125
  ) {
118
126
  this.element.classList.add("errored");
119
127
  const error = document.createElement("editable-region-error-card");
@@ -156,6 +164,7 @@ export default class EditableArray extends Editable {
156
164
 
157
165
  private async _update(): Promise<void> {
158
166
  let value: unknown[] | CloudCannonJavaScriptV1APIFile[];
167
+ const __base_context = { ...this.contextBase };
159
168
  if (CloudCannon.isAPICollection(this.value)) {
160
169
  value = await this.value.items();
161
170
  } else if (CloudCannon.isAPIDataset(this.value)) {
@@ -164,8 +173,13 @@ export default class EditableArray extends Editable {
164
173
  value = items;
165
174
  } else {
166
175
  const data = await items.data.get();
176
+ __base_context.file = items;
167
177
  value = Array.isArray(data) ? data : [];
168
178
  }
179
+ } else if (CloudCannon.isAPIFile(this.value)) {
180
+ const data = await this.value.data.get();
181
+ __base_context.file = this.value;
182
+ value = Array.isArray(data) ? data : [];
169
183
  } else if (Array.isArray(this.value)) {
170
184
  value = this.value;
171
185
  } else {
@@ -240,7 +254,7 @@ export default class EditableArray extends Editable {
240
254
  child.editable?.pushValue(
241
255
  value,
242
256
  { path: `${i}`, editable: child.editable },
243
- { __base_context: this.contextBase ?? {} },
257
+ { __base_context },
244
258
  );
245
259
  }
246
260
  return;
@@ -284,7 +298,7 @@ export default class EditableArray extends Editable {
284
298
  child.editable?.pushValue(
285
299
  value,
286
300
  { path: `${index}`, editable: child.editable },
287
- { __base_context: this.contextBase ?? {} },
301
+ { __base_context },
288
302
  );
289
303
  });
290
304
 
@@ -374,7 +388,7 @@ export default class EditableArray extends Editable {
374
388
  matchingChild.editable.pushValue(
375
389
  value,
376
390
  { path: `${i}`, editable: matchingChild.editable },
377
- { __base_context: this.contextBase ?? {} },
391
+ { __base_context },
378
392
  );
379
393
  }
380
394
  });
@@ -185,7 +185,9 @@ export default class EditableImage extends Editable {
185
185
  !!this.element.dataset.propTitle || !!this.element.dataset.prop;
186
186
 
187
187
  this.loadInputConfig().then(() => {
188
- this.imageEl?.addEventListener("click", () => {
188
+ this.imageEl?.addEventListener("click", (e) => {
189
+ e.preventDefault();
190
+
189
191
  if (!this.value) {
190
192
  throw new Error("Value is not defined");
191
193
  }
@@ -80,6 +80,8 @@ export default class EditableText extends Editable {
80
80
  }
81
81
 
82
82
  mount(): void {
83
+ this.element.addEventListener("click", (e) => e.preventDefault());
84
+
83
85
  this.element.addEventListener("blur", () => {
84
86
  this.focused = false;
85
87
  this.element.dispatchEvent(
package/nodes/editable.ts CHANGED
@@ -88,14 +88,16 @@ export default class Editable {
88
88
  }
89
89
 
90
90
  async lookupPathAndContext(
91
- path: string,
92
- obj: unknown,
91
+ path?: string,
92
+ obj?: unknown,
93
93
  contexts: { [key: string]: EditableContext } = {},
94
94
  ): Promise<{ value: any; context: EditableContext }> {
95
95
  if (!path) {
96
96
  return {
97
97
  value: obj,
98
- context: {},
98
+ context: {
99
+ ...contexts.__base_context,
100
+ },
99
101
  };
100
102
  }
101
103
 
@@ -166,9 +168,9 @@ export default class Editable {
166
168
  ): Promise<unknown> {
167
169
  const { key, path } = listener ?? {};
168
170
 
169
- const { value: resolvedValue, context: newContext } = path
170
- ? await this.lookupPathAndContext(path, value, contexts)
171
- : { value, context: {} };
171
+ const { value: resolvedValue, context: newContext } =
172
+ await this.lookupPathAndContext(path, value, contexts);
173
+
172
174
  if (!key) {
173
175
  this.propsBase = resolvedValue;
174
176
  this.contextBase = newContext;
@@ -317,9 +319,12 @@ export default class Editable {
317
319
  let parentEditable: Editable | undefined;
318
320
  let parent = this.element.parentElement;
319
321
  while (parent) {
320
- if (hasEditable(parent)) {
322
+ if (hasEditable(parent) && !parentEditable) {
321
323
  parentEditable = parent.editable;
322
- break;
324
+ }
325
+
326
+ if (parent.tagName === "A") {
327
+ parent.draggable = false;
323
328
  }
324
329
  parent = parent.parentElement;
325
330
  }
@@ -331,7 +336,7 @@ export default class Editable {
331
336
  return;
332
337
  }
333
338
 
334
- const { collection, file, dataset, source, absolute } =
339
+ const { collection, file, dataset, source, absolute, currentFile } =
335
340
  this.parseSource(propPath);
336
341
 
337
342
  const listener = {
@@ -349,8 +354,15 @@ export default class Editable {
349
354
  // Any single data path should only be able to refer to a single absolute API object
350
355
  const obj = collection || dataset || file;
351
356
  if (obj) {
357
+ const fullPath = collection
358
+ ? `@collections[${collection.collectionKey}]`
359
+ : dataset
360
+ ? `@data[${dataset.datasetKey}]`
361
+ : currentFile
362
+ ? undefined
363
+ : `@file[${file?.path}]`;
352
364
  const handleAPIChange = () => {
353
- this.pushValue(obj, listener);
365
+ this.pushValue(obj, listener, { __base_context: { fullPath } });
354
366
  };
355
367
  this.APIListeners.push({
356
368
  obj,
@@ -425,22 +437,6 @@ export default class Editable {
425
437
  }
426
438
 
427
439
  filePromise.then((file) => {
428
- if (typeof source !== "string") {
429
- if (options.action === "get-input-config") {
430
- options.callback({
431
- options: {
432
- disable_reorder: true,
433
- disable_remove: true,
434
- disable_add: true,
435
- },
436
- });
437
- return true;
438
- }
439
- throw new Error(
440
- `Failed to resolve source for API call: ${options.source}`,
441
- );
442
- }
443
-
444
440
  switch (options.action) {
445
441
  case "edit":
446
442
  file?.data.edit({ slug: source, position: options.position });
@@ -474,7 +470,17 @@ export default class Editable {
474
470
  });
475
471
  break;
476
472
  case "get-input-config":
477
- file?.getInputConfig({ slug: source }).then(options.callback);
473
+ if (!file) {
474
+ options.callback({
475
+ options: {
476
+ disable_reorder: true,
477
+ disable_remove: true,
478
+ disable_add: true,
479
+ },
480
+ });
481
+ } else {
482
+ file?.getInputConfig({ slug: source }).then(options.callback);
483
+ }
478
484
  break;
479
485
  }
480
486
  });
@@ -521,6 +527,7 @@ export default class Editable {
521
527
  let file: CloudCannonJavaScriptV1APIFile | undefined;
522
528
  let dataset: CloudCannonJavaScriptV1APIDataset | undefined;
523
529
  let absolute = false;
530
+ let currentFile = false;
524
531
 
525
532
  const collectionMatch = source?.match(
526
533
  /^@collections\[(?<key>[^\]]+)\](\.(?<rest>.+))?$/,
@@ -550,6 +557,7 @@ export default class Editable {
550
557
  absolute = true;
551
558
  } else {
552
559
  file = CloudCannon.currentFile();
560
+ currentFile = true;
553
561
  }
554
562
  }
555
563
  }
@@ -566,10 +574,11 @@ export default class Editable {
566
574
  return {
567
575
  collection,
568
576
  file,
569
- source,
577
+ source: source ?? "",
570
578
  absolute,
571
579
  snippets,
572
580
  dataset,
581
+ currentFile,
573
582
  };
574
583
  }
575
584
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudcannon/editable-regions",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
4
4
  "type": "module",
5
5
  "description": "Visual Editing for the CloudCannon CMS.",
6
6
  "keywords": [