@thebes/cadmea 1.0.0 → 1.1.1

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.
@@ -1,19 +1,20 @@
1
- import { createComponent, escape, ssr, ssrAttribute } from "solid-js/web";
2
- import { For, Show, Suspense, createEffect, createSignal, lazy, onCleanup } from "solid-js";
1
+ import { createComponent, escape, ssr, ssrAttribute, ssrStyleProperty } from "solid-js/web";
2
+ import { For, Show, Suspense, createEffect, createMemo, createSignal, lazy, onCleanup, onMount } from "solid-js";
3
+ import { VISUAL_EDIT_MESSAGE } from "@thebes/cadmus/cms";
3
4
  //#region src/CollectionEdit.tsx
4
- var _tmpl$$2 = ["<p class=\"text-sm text-error\" role=\"alert\">", "</p>"], _tmpl$2$2 = "<span class=\"loading loading-spinner loading-sm\"></span>", _tmpl$3$2 = [
5
+ var _tmpl$$4 = ["<p class=\"text-sm text-error\" role=\"alert\">", "</p>"], _tmpl$2$3 = "<span class=\"loading loading-spinner loading-sm\"></span>", _tmpl$3$3 = [
5
6
  "<button type=\"button\" class=\"btn flex-1\"",
6
7
  ">",
7
8
  "</button>"
8
- ], _tmpl$4$2 = [
9
+ ], _tmpl$4$3 = [
9
10
  "<button type=\"button\" class=\"btn btn-primary flex-1\"",
10
11
  ">",
11
12
  "</button>"
12
- ], _tmpl$5$1 = [
13
+ ], _tmpl$5$2 = [
13
14
  "<button type=\"button\" class=\"btn btn-outline flex-1\"",
14
15
  ">",
15
16
  "</button>"
16
- ], _tmpl$6$1 = [
17
+ ], _tmpl$6$2 = [
17
18
  "<form class=\"flex flex-col gap-4\">",
18
19
  "",
19
20
  "<div class=\"bg-base-100 sticky bottom-0 flex gap-2 border-t py-3\">",
@@ -93,15 +94,16 @@ function CollectionEdit(props) {
93
94
  }
94
95
  const ctx = {
95
96
  onUploadFile: props.onUploadFile,
96
- relationshipOptions: props.relationshipOptions
97
+ relationshipOptions: props.relationshipOptions,
98
+ fieldWidgets: props.fieldWidgets
97
99
  };
98
100
  const versioned = () => props.config.versions?.drafts && props.draftActions;
99
- return ssr(_tmpl$6$1, escape(createComponent(Show, {
101
+ return ssr(_tmpl$6$2, escape(createComponent(Show, {
100
102
  get when() {
101
103
  return props.error;
102
104
  },
103
105
  get children() {
104
- return ssr(_tmpl$$2, escape(props.error));
106
+ return ssr(_tmpl$$4, escape(props.error));
105
107
  }
106
108
  })), escape(createComponent(For, {
107
109
  get each() {
@@ -133,7 +135,7 @@ function CollectionEdit(props) {
133
135
  return props.submitLabel ?? "Save";
134
136
  },
135
137
  get children() {
136
- return ssr(_tmpl$2$2);
138
+ return ssr(_tmpl$2$3);
137
139
  }
138
140
  })));
139
141
  }
@@ -141,7 +143,7 @@ function CollectionEdit(props) {
141
143
  },
142
144
  get children() {
143
145
  return [
144
- ssr(_tmpl$3$2, ssrAttribute("disabled", props.draftActions?.saving, true), escape(createComponent(Show, {
146
+ ssr(_tmpl$3$3, ssrAttribute("disabled", props.draftActions?.saving, true), escape(createComponent(Show, {
145
147
  get when() {
146
148
  return props.draftActions?.saving;
147
149
  },
@@ -149,10 +151,10 @@ function CollectionEdit(props) {
149
151
  return props.draftActions?.saveDraftLabel ?? "Save draft";
150
152
  },
151
153
  get children() {
152
- return ssr(_tmpl$2$2);
154
+ return ssr(_tmpl$2$3);
153
155
  }
154
156
  }))),
155
- ssr(_tmpl$4$2, ssrAttribute("disabled", !props.draftActions?.canPublish || props.draftActions?.publishing, true), escape(createComponent(Show, {
157
+ ssr(_tmpl$4$3, ssrAttribute("disabled", !props.draftActions?.canPublish || props.draftActions?.publishing, true), escape(createComponent(Show, {
156
158
  get when() {
157
159
  return props.draftActions?.publishing;
158
160
  },
@@ -160,7 +162,7 @@ function CollectionEdit(props) {
160
162
  return props.draftActions?.publishLabel ?? "Publish";
161
163
  },
162
164
  get children() {
163
- return ssr(_tmpl$2$2);
165
+ return ssr(_tmpl$2$3);
164
166
  }
165
167
  }))),
166
168
  createComponent(Show, {
@@ -168,7 +170,7 @@ function CollectionEdit(props) {
168
170
  return props.draftActions?.onPreview;
169
171
  },
170
172
  get children() {
171
- return ssr(_tmpl$5$1, ssrAttribute("disabled", !props.draftActions?.canPreview || props.draftActions?.previewing, true), escape(createComponent(Show, {
173
+ return ssr(_tmpl$5$2, ssrAttribute("disabled", !props.draftActions?.canPreview || props.draftActions?.previewing, true), escape(createComponent(Show, {
172
174
  get when() {
173
175
  return props.draftActions?.previewing;
174
176
  },
@@ -176,7 +178,7 @@ function CollectionEdit(props) {
176
178
  return props.draftActions?.previewLabel ?? "Preview";
177
179
  },
178
180
  get children() {
179
- return ssr(_tmpl$2$2);
181
+ return ssr(_tmpl$2$3);
180
182
  }
181
183
  })));
182
184
  }
@@ -186,6 +188,15 @@ function CollectionEdit(props) {
186
188
  })));
187
189
  }
188
190
  function renderInput(key, field, value, setField, ctx) {
191
+ const Widget = ctx.fieldWidgets?.[key] ?? ctx.fieldWidgets?.[key.slice(key.lastIndexOf(".") + 1)];
192
+ if (Widget) return createComponent(Widget, {
193
+ fieldKey: key,
194
+ value,
195
+ setValue: (v) => setField(key, v),
196
+ get onUploadFile() {
197
+ return ctx.onUploadFile;
198
+ }
199
+ });
189
200
  switch (field.type) {
190
201
  case "text": return ssr(_tmpl$0$1, ssrAttribute("id", escape(key, true), false), ssrAttribute("value", escape(value ?? "", true), false), ssrAttribute("required", field.required, true));
191
202
  case "select": return ssr(_tmpl$1$1, ssrAttribute("id", escape(key, true), false), ssrAttribute("value", escape(value ?? "", true), false), ssrAttribute("required", field.required, true), escape(createComponent(For, {
@@ -202,7 +213,7 @@ function renderInput(key, field, value, setField, ctx) {
202
213
  case "array": return renderArrayInput(key, field, value, setField, ctx);
203
214
  case "richText": return createComponent(Suspense, {
204
215
  get fallback() {
205
- return ssr(_tmpl$2$2);
216
+ return ssr(_tmpl$2$3);
206
217
  },
207
218
  get children() {
208
219
  return createComponent(RichTextEditor, {
@@ -228,7 +239,7 @@ function renderUploadInput(key, field, value, setField, ctx) {
228
239
  return uploading();
229
240
  },
230
241
  get children() {
231
- return ssr(_tmpl$2$2);
242
+ return ssr(_tmpl$2$3);
232
243
  }
233
244
  })), escape(createComponent(Show, {
234
245
  get when() {
@@ -294,17 +305,17 @@ function formatDateValue(value) {
294
305
  }
295
306
  //#endregion
296
307
  //#region src/CollectionList.tsx
297
- var _tmpl$$1 = ["<button type=\"button\" class=\"btn btn-outline btn-sm\">", "</button>"], _tmpl$2$1 = [
308
+ var _tmpl$$3 = ["<button type=\"button\" class=\"btn btn-outline btn-sm\">", "</button>"], _tmpl$2$2 = [
298
309
  "<div class=\"join\"><select aria-label=\"Sort by\" class=\"select select-sm join-item\"",
299
310
  ">",
300
311
  "</select><select aria-label=\"Sort direction\" class=\"select select-sm join-item\"",
301
312
  "><option value=\"asc\">Ascending</option><option value=\"desc\">Descending</option></select></div>"
302
- ], _tmpl$3$1 = "<th></th>", _tmpl$4$1 = [
313
+ ], _tmpl$3$2 = "<th></th>", _tmpl$4$2 = [
303
314
  "<table class=\"table hidden md:table\"><thead><tr>",
304
315
  "",
305
316
  "</tr></thead><tbody>",
306
317
  "</tbody></table>"
307
- ], _tmpl$5 = ["<div class=\"flex flex-col gap-2 md:hidden\">", "</div>"], _tmpl$6 = [
318
+ ], _tmpl$5$1 = ["<div class=\"flex flex-col gap-2 md:hidden\">", "</div>"], _tmpl$6$1 = [
308
319
  "<div class=\"bg-base-100 sticky bottom-0 flex items-center justify-between gap-2 border-t py-2\"><button type=\"button\" class=\"btn btn-sm\"",
309
320
  ">Prev</button><span class=\"text-sm opacity-70\">Page ",
310
321
  "</span><button type=\"button\" class=\"btn btn-sm\"",
@@ -357,14 +368,14 @@ function CollectionList(props) {
357
368
  return props.selectable;
358
369
  },
359
370
  get children() {
360
- return ssr(_tmpl$$1, selectMode() ? "Done" : "Select");
371
+ return ssr(_tmpl$$3, selectMode() ? "Done" : "Select");
361
372
  }
362
373
  })), escape(createComponent(Show, {
363
374
  get when() {
364
375
  return props.onSortChange;
365
376
  },
366
377
  get children() {
367
- return ssr(_tmpl$2$1, ssrAttribute("value", escape(props.sortField ?? "", true), false), escape(createComponent(For, {
378
+ return ssr(_tmpl$2$2, ssrAttribute("value", escape(props.sortField ?? "", true), false), escape(createComponent(For, {
368
379
  get each() {
369
380
  return columns();
370
381
  },
@@ -379,12 +390,12 @@ function CollectionList(props) {
379
390
  return ssr(_tmpl$9, escape(props.config.slug));
380
391
  },
381
392
  get children() {
382
- return [ssr(_tmpl$4$1, escape(createComponent(Show, {
393
+ return [ssr(_tmpl$4$2, escape(createComponent(Show, {
383
394
  get when() {
384
395
  return selectMode();
385
396
  },
386
397
  get children() {
387
- return ssr(_tmpl$3$1);
398
+ return ssr(_tmpl$3$2);
388
399
  }
389
400
  })), escape(createComponent(For, {
390
401
  get each() {
@@ -408,7 +419,7 @@ function CollectionList(props) {
408
419
  },
409
420
  children: ([key]) => ssr(_tmpl$11, escape(formatCellValue(row[key])))
410
421
  })))
411
- }))), ssr(_tmpl$5, escape(createComponent(For, {
422
+ }))), ssr(_tmpl$5$1, escape(createComponent(For, {
412
423
  get each() {
413
424
  return props.rows;
414
425
  },
@@ -432,13 +443,95 @@ function CollectionList(props) {
432
443
  return props.page !== void 0 && props.pageSize !== void 0;
433
444
  },
434
445
  get children() {
435
- return ssr(_tmpl$6, ssrAttribute("disabled", (props.page ?? 1) <= 1, true), escape(props.page), ssrAttribute("disabled", props.totalCount !== void 0 ? (props.page ?? 1) * (props.pageSize ?? 0) >= props.totalCount : props.rows.length < (props.pageSize ?? 0), true));
446
+ return ssr(_tmpl$6$1, ssrAttribute("disabled", (props.page ?? 1) <= 1, true), escape(props.page), ssrAttribute("disabled", props.totalCount !== void 0 ? (props.page ?? 1) * (props.pageSize ?? 0) >= props.totalCount : props.rows.length < (props.pageSize ?? 0), true));
447
+ }
448
+ })));
449
+ }
450
+ //#endregion
451
+ //#region src/ImageHotspotField.tsx
452
+ var _tmpl$$2 = "<span class=\"loading loading-spinner loading-sm\"></span>", _tmpl$2$1 = ["<p class=\"text-sm text-error\">", "</p>"], _tmpl$3$1 = [
453
+ "<div class=\"flex flex-col gap-3\"><input",
454
+ " class=\"file-input\" type=\"file\" accept=\"image/*\"",
455
+ ">",
456
+ "",
457
+ "",
458
+ "</div>"
459
+ ], _tmpl$4$1 = [
460
+ "<div class=\"flex flex-col gap-2\"><p class=\"text-xs opacity-60\">Click the image to set the focal point.</p><div class=\"relative inline-block max-w-md\"><img",
461
+ " alt=\"Set focal point\" class=\"block w-full cursor-crosshair rounded\">",
462
+ "</div><details class=\"text-sm\"><summary class=\"cursor-pointer opacity-70\">Crop (advanced)</summary><div class=\"mt-2 grid grid-cols-4 gap-2\">",
463
+ "</div></details><p class=\"break-all text-xs opacity-50\">",
464
+ "</p></div>"
465
+ ], _tmpl$5 = ["<span class=\"pointer-events-none absolute h-4 w-4 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-white bg-[var(--accent,#56c6be)] shadow\" style=\"", "\"></span>"], _tmpl$6 = [
466
+ "<label class=\"flex flex-col gap-1 text-xs\"><span class=\"capitalize opacity-70\">",
467
+ "</span><input class=\"input input-sm\" type=\"number\" min=\"0\" max=\"1\" step=\"0.05\"",
468
+ "></label>"
469
+ ];
470
+ /**
471
+ * Parse an upload-field value into `{ url, hotspot?, crop? }`. Accepts the
472
+ * JSON object this widget writes, an already-parsed object, or a bare URL
473
+ * string (legacy / non-hotspot uploads). Returns null for empty values.
474
+ */
475
+ function parseImageHotspotValue(value) {
476
+ if (!value) return null;
477
+ if (typeof value === "object") return value;
478
+ if (typeof value === "string") {
479
+ const trimmed = value.trim();
480
+ if (trimmed.startsWith("{")) try {
481
+ return JSON.parse(trimmed);
482
+ } catch {}
483
+ return trimmed ? { url: trimmed } : null;
484
+ }
485
+ return null;
486
+ }
487
+ /** Serialize an {@link ImageWithHotspot} for storage in an upload field. */
488
+ function serializeImageHotspotValue(value) {
489
+ return JSON.stringify(value);
490
+ }
491
+ function ImageHotspotField(props) {
492
+ const parsed = createMemo(() => parseImageHotspotValue(props.value));
493
+ const [uploading, setUploading] = createSignal(false);
494
+ const [error, setError] = createSignal();
495
+ const crop = () => parsed()?.crop ?? {
496
+ top: 0,
497
+ right: 0,
498
+ bottom: 0,
499
+ left: 0
500
+ };
501
+ return ssr(_tmpl$3$1, ssrAttribute("id", escape(props.fieldKey, true), false), ssrAttribute("disabled", uploading(), true), escape(createComponent(Show, {
502
+ get when() {
503
+ return uploading();
504
+ },
505
+ get children() {
506
+ return ssr(_tmpl$$2);
507
+ }
508
+ })), escape(createComponent(Show, {
509
+ get when() {
510
+ return error();
511
+ },
512
+ get children() {
513
+ return ssr(_tmpl$2$1, escape(error()));
436
514
  }
515
+ })), escape(createComponent(Show, {
516
+ get when() {
517
+ return parsed()?.url;
518
+ },
519
+ children: (url) => ssr(_tmpl$4$1, ssrAttribute("src", escape(url(), true), false), escape(createComponent(Show, {
520
+ get when() {
521
+ return parsed()?.hotspot;
522
+ },
523
+ children: (hs) => ssr(_tmpl$5, ssrStyleProperty("left:", `${escape(hs().x, true) * 100}%`) + ssrStyleProperty(";top:", `${escape(hs().y, true) * 100}%`))
524
+ })), escape([
525
+ "top",
526
+ "right",
527
+ "bottom",
528
+ "left"
529
+ ].map((edge) => ssr(_tmpl$6, escape(edge), ssrAttribute("value", escape(crop()[edge], true), false)))), escape(url()))
437
530
  })));
438
531
  }
439
532
  //#endregion
440
533
  //#region src/SearchPalette.tsx
441
- var _tmpl$ = ["<ul class=\"max-h-80 overflow-y-auto py-2\">", "</ul>"], _tmpl$2 = [
534
+ var _tmpl$$1 = ["<ul class=\"max-h-80 overflow-y-auto py-2\">", "</ul>"], _tmpl$2 = [
442
535
  "<div aria-hidden=\"true\" class=\"fixed inset-0 z-50 flex items-start justify-center bg-[var(--color-backdrop)] px-4 pt-[15vh]\"><div role=\"dialog\" aria-modal=\"true\" aria-label=\"Search\" class=\"w-full max-w-lg overflow-hidden rounded-2xl border border-[var(--line)] bg-[var(--surface-strong)] shadow-2xl\"><div class=\"flex items-center gap-2 border-b border-[var(--line)] px-4 py-3\"><i class=\"ph ph-magnifying-glass text-lg text-[var(--sea-ink-soft)]\" aria-hidden=\"true\"></i><input type=\"text\"",
443
536
  " placeholder=\"Search…\" aria-label=\"Search\" class=\"flex-1 bg-transparent text-base text-[var(--sea-ink)] outline-none placeholder:text-[var(--sea-ink-soft)]\"><kbd class=\"rounded border border-[var(--chip-line)] bg-[var(--chip-bg)] px-1.5 py-0.5 text-xs text-[var(--sea-ink-soft)]\">Esc</kbd></div>",
444
537
  "</div></div>"
@@ -491,7 +584,7 @@ function SearchPalette(props) {
491
584
  return ssr(_tmpl$3, query().trim().length < 2 ? "Keep typing to search…" : "No results");
492
585
  },
493
586
  get children() {
494
- return ssr(_tmpl$, escape(createComponent(For, {
587
+ return ssr(_tmpl$$1, escape(createComponent(For, {
495
588
  get each() {
496
589
  return results();
497
590
  },
@@ -503,6 +596,29 @@ function SearchPalette(props) {
503
596
  });
504
597
  }
505
598
  //#endregion
506
- export { CollectionEdit, CollectionList, SearchPalette };
599
+ //#region src/VisualEditingPane.tsx
600
+ var _tmpl$ = ["<iframe", "></iframe>"];
601
+ function originOf(url) {
602
+ try {
603
+ return new URL(url).origin;
604
+ } catch {
605
+ return;
606
+ }
607
+ }
608
+ function VisualEditingPane(props) {
609
+ onMount(() => {
610
+ const expected = props.allowedOrigin ?? originOf(props.src);
611
+ const handler = (event) => {
612
+ if (expected && event.origin !== expected) return;
613
+ const data = event.data;
614
+ if (data?.type === VISUAL_EDIT_MESSAGE && data.ref) props.onEdit?.(data.ref);
615
+ };
616
+ window.addEventListener("message", handler);
617
+ onCleanup(() => window.removeEventListener("message", handler));
618
+ });
619
+ return ssr(_tmpl$, ssrAttribute("src", escape(props.src, true), false) + ssrAttribute("title", escape(props.title ?? "Preview", true), false) + ssrAttribute("class", escape(props.class ?? "h-full w-full border-0", true), false));
620
+ }
621
+ //#endregion
622
+ export { CollectionEdit, CollectionList, ImageHotspotField, SearchPalette, VisualEditingPane, parseImageHotspotValue, serializeImageHotspotValue };
507
623
 
508
624
  //# sourceMappingURL=server.js.map