@cardinali-studio/adminkit 2.2.37 → 2.2.39
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/README.md
CHANGED
|
@@ -196,6 +196,14 @@ It is opinionated about the useful narrow case: Hugo content, React 19, Zod 4, C
|
|
|
196
196
|
|
|
197
197
|
This narrowness is intentional. A smaller system with explicit failure modes is easier to trust around content that people have edited by hand.
|
|
198
198
|
|
|
199
|
+
## Delivery ZIP compatibility (2.2.38)
|
|
200
|
+
|
|
201
|
+
New delivery archives use STORE members with their CRC and sizes in each local header, without data descriptors. This prevents streaming extractors from mistaking descriptor-shaped bytes inside a photograph for the end of the file. CRC preparation reads bounded 4 MiB slices and remains pausable and cancellable; source changes fail closed. ZIP64 sizes and offsets are promoted independently.
|
|
202
|
+
|
|
203
|
+
Persistent checkpoints record `store-header-v1`. Checkpoints from earlier releases continue with `store-descriptor-v1`, preserving confirmed multipart bytes and derivative retries. The update affects newly generated archives; it does not rewrite existing stored ZIPs. Consumers must update the dependency, rebuild and redeploy.
|
|
204
|
+
|
|
205
|
+
The regression includes 208 synthetic members with the collision at member 53, streaming and indexed readers, ZIP64 offsets, and byte-identical resume against fixtures from 2.2.37.
|
|
206
|
+
|
|
199
207
|
## Development
|
|
200
208
|
|
|
201
209
|
```sh
|
package/dist/app/app.css
CHANGED
|
@@ -1107,6 +1107,13 @@ body { margin: 0; background: var(--ak-bg); color: var(--ak-ink); font-family: v
|
|
|
1107
1107
|
}
|
|
1108
1108
|
.dl-card,.dl-group { min-width: 0; }
|
|
1109
1109
|
.dl-card { background: var(--ak-panel); border: 1px solid var(--ak-line); border-radius: var(--ak-radius); }
|
|
1110
|
+
.dl-payment-status {
|
|
1111
|
+
display: inline-flex; justify-self: start; padding: 4px 8px; border: 1px solid var(--ak-line);
|
|
1112
|
+
border-radius: 999px; background: var(--ak-bg); color: var(--ak-ink-quiet);
|
|
1113
|
+
font: 600 10px/1.4 var(--ak-font);
|
|
1114
|
+
}
|
|
1115
|
+
.dl-payment-disabled { cursor: not-allowed; }
|
|
1116
|
+
.dl-payment-disabled input { opacity: .5; cursor: not-allowed; }
|
|
1110
1117
|
|
|
1111
1118
|
/* Le due mattonelle di scelta: sono la prima cosa che si guarda entrando. */
|
|
1112
1119
|
.dl-pick { padding: 14px; display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { type UploadControl } from "./delivery-network.js";
|
|
2
2
|
export { UploadCancelled, type UploadControl } from "./delivery-network.js";
|
|
3
|
+
declare const ZIP_FORMAT = "store-header-v1";
|
|
4
|
+
declare const LEGACY_ZIP_FORMAT = "store-descriptor-v1";
|
|
5
|
+
type ZipFormat = typeof ZIP_FORMAT | typeof LEGACY_ZIP_FORMAT;
|
|
3
6
|
export interface DeliveryUploadProgress {
|
|
4
7
|
phase: "originals" | "derivatives" | "finalizing";
|
|
5
8
|
file: string;
|
|
@@ -57,6 +60,8 @@ interface MemberCheckpoint {
|
|
|
57
60
|
dataOffset: number;
|
|
58
61
|
time: number;
|
|
59
62
|
date: number;
|
|
63
|
+
zipFormat?: ZipFormat;
|
|
64
|
+
headerCrc?: number;
|
|
60
65
|
}
|
|
61
66
|
export declare function uploadDeliveryPackage(deliveryId: string, packageId: string, selected: File[], options: UploadOptions, onProgress: (progress: DeliveryUploadProgress) => void, control?: UploadControl): Promise<UploadResult>;
|
|
62
67
|
interface ZipSink {
|
|
@@ -69,9 +74,10 @@ export declare class ZipStream {
|
|
|
69
74
|
private sink;
|
|
70
75
|
private offset;
|
|
71
76
|
private entries;
|
|
72
|
-
|
|
77
|
+
private format;
|
|
78
|
+
constructor(sink: ZipSink, utc?: boolean, format?: ZipFormat);
|
|
73
79
|
restore(entries: DeliveryManifestEntry[], files: File[], offset: number): void;
|
|
74
|
-
add(nameValue: string, blob: Blob, progress?: (read: number) => void, modified?: Date, checkpoint?: (current: MemberCheckpoint, offset: number) => Promise<void>, resume?: MemberCheckpoint): Promise<ZipMember>;
|
|
80
|
+
add(nameValue: string, blob: Blob, progress?: (read: number, preparing?: boolean) => void, modified?: Date, checkpoint?: (current: MemberCheckpoint, offset: number) => Promise<void>, resume?: MemberCheckpoint): Promise<ZipMember>;
|
|
75
81
|
close(): Promise<void>;
|
|
76
82
|
private sinkGate;
|
|
77
83
|
private push;
|
|
@@ -14,6 +14,8 @@ const READ = 4 * 1024 * 1024;
|
|
|
14
14
|
const UPLOAD_CONCURRENCY = 4;
|
|
15
15
|
const IMAGE = /\.(jpe?g|png|webp)$/i;
|
|
16
16
|
const enc = new TextEncoder();
|
|
17
|
+
const ZIP_FORMAT = "store-header-v1";
|
|
18
|
+
const LEGACY_ZIP_FORMAT = "store-descriptor-v1";
|
|
17
19
|
const droppedPaths = new WeakMap();
|
|
18
20
|
/* Il cancello di pausa e annullamento.
|
|
19
21
|
*
|
|
@@ -154,7 +156,9 @@ async function buildDeliveryPackage(deliveryId, packageId, selected, options, on
|
|
|
154
156
|
let currentOriginal = "";
|
|
155
157
|
const originalsSink = new B2Sink(deliveryId, packageId, "originals", reportOriginals, control, options.manifestVersion === 2);
|
|
156
158
|
sinks.push(originalsSink);
|
|
157
|
-
|
|
159
|
+
// Confirmed multipart prefixes and derivative retries keep their original layout.
|
|
160
|
+
const zipFormat = checkpoint ? (checkpoint.current?.zipFormat || LEGACY_ZIP_FORMAT) : ZIP_FORMAT;
|
|
161
|
+
const originals = new ZipStream(originalsSink, options.manifestVersion === 2, zipFormat);
|
|
158
162
|
if (checkpoint) {
|
|
159
163
|
if (checkpoint.complete) {
|
|
160
164
|
originalsSink.totalBytes = checkpoint.offset;
|
|
@@ -175,12 +179,13 @@ async function buildDeliveryPackage(deliveryId, packageId, selected, options, on
|
|
|
175
179
|
checkpoint: { version: 1, fileIndex: processed, offset, sequence, current } }, control);
|
|
176
180
|
lastCheckpoint = offset;
|
|
177
181
|
}
|
|
178
|
-
function reportOriginals() {
|
|
182
|
+
function reportOriginals(_read, preparing = false) {
|
|
179
183
|
onProgress({
|
|
180
184
|
phase: "originals", file: currentOriginal, doneFiles: processed, totalFiles: files.length,
|
|
181
185
|
sourceBytes, uploadedBytes: originalsSink.uploadedBytes,
|
|
182
186
|
transferredBytes: originalsSink.transferredBytes, startedAt,
|
|
183
187
|
transferStartedAt: originalsSink.transferStartedAt || undefined,
|
|
188
|
+
note: preparing ? "Verifico il file prima del caricamento…" : undefined,
|
|
184
189
|
});
|
|
185
190
|
}
|
|
186
191
|
for (const file of files.slice(processed)) {
|
|
@@ -188,7 +193,7 @@ async function buildDeliveryPackage(deliveryId, packageId, selected, options, on
|
|
|
188
193
|
await control.gate();
|
|
189
194
|
const name = safePath(deliveryRelativeName(file));
|
|
190
195
|
currentOriginal = name;
|
|
191
|
-
const member = await originals.add(name, file, reportOriginals, new Date(file.lastModified), options.manifestVersion === 2 ? saveCheckpoint : undefined, checkpoint && processed === checkpoint.fileIndex ? checkpoint.current : undefined);
|
|
196
|
+
const member = await originals.add(name, file, reportOriginals, new Date(file.lastModified), options.manifestVersion === 2 ? saveCheckpoint : undefined, checkpoint && !checkpoint.complete && processed === checkpoint.fileIndex ? checkpoint.current : undefined);
|
|
192
197
|
entryByName.set(name, entries.length);
|
|
193
198
|
entries.push({ name, type: file.type || mimeFromName(name), original: member });
|
|
194
199
|
await saveMetadata(entries.length - 1, entries[entries.length - 1]);
|
|
@@ -200,7 +205,7 @@ async function buildDeliveryPackage(deliveryId, packageId, selected, options, on
|
|
|
200
205
|
await flushMetadata();
|
|
201
206
|
if (options.manifestVersion === 2 && !checkpoint?.complete) {
|
|
202
207
|
await deliveryJson("/admin/api/deliveries", { action: "manifestCheckpoint", id: deliveryId, packageId,
|
|
203
|
-
checkpoint: { version: 1, fileIndex: files.length, offset: originalsSink.totalBytes, sequence, complete: true } }, control);
|
|
208
|
+
checkpoint: { version: 1, fileIndex: files.length, offset: originalsSink.totalBytes, sequence, complete: true, current: { zipFormat } } }, control);
|
|
204
209
|
}
|
|
205
210
|
phase = "variants";
|
|
206
211
|
sequence = 0;
|
|
@@ -213,12 +218,12 @@ async function buildDeliveryPackage(deliveryId, packageId, selected, options, on
|
|
|
213
218
|
if (wantWebArchive) {
|
|
214
219
|
webSink = new B2Sink(deliveryId, packageId, "web", undefined, control, options.manifestVersion === 2);
|
|
215
220
|
sinks.push(webSink);
|
|
216
|
-
webZip = new ZipStream(webSink, options.manifestVersion === 2);
|
|
221
|
+
webZip = new ZipStream(webSink, options.manifestVersion === 2, zipFormat);
|
|
217
222
|
}
|
|
218
223
|
if (options.galleryEnabled) {
|
|
219
224
|
thumbsSink = new B2Sink(deliveryId, packageId, "thumbs", undefined, control, options.manifestVersion === 2);
|
|
220
225
|
sinks.push(thumbsSink);
|
|
221
|
-
thumbsZip = new ZipStream(thumbsSink, options.manifestVersion === 2);
|
|
226
|
+
thumbsZip = new ZipStream(thumbsSink, options.manifestVersion === 2, zipFormat);
|
|
222
227
|
}
|
|
223
228
|
if (webZip) {
|
|
224
229
|
let imageDone = 0;
|
|
@@ -494,9 +499,13 @@ export class ZipStream {
|
|
|
494
499
|
sink;
|
|
495
500
|
offset = 0;
|
|
496
501
|
entries = [];
|
|
497
|
-
|
|
502
|
+
format;
|
|
503
|
+
constructor(sink, utc = false, format = ZIP_FORMAT) {
|
|
504
|
+
if (![ZIP_FORMAT, LEGACY_ZIP_FORMAT].includes(format))
|
|
505
|
+
throw new Error("Formato del checkpoint ZIP non supportato.");
|
|
498
506
|
this.utc = utc;
|
|
499
507
|
this.sink = sink;
|
|
508
|
+
this.format = format;
|
|
500
509
|
}
|
|
501
510
|
restore(entries, files, offset) {
|
|
502
511
|
this.offset = offset;
|
|
@@ -509,22 +518,46 @@ export class ZipStream {
|
|
|
509
518
|
});
|
|
510
519
|
}
|
|
511
520
|
async add(nameValue, blob, progress, modified = new Date(), checkpoint, resume) {
|
|
521
|
+
await this.sinkGate();
|
|
512
522
|
const name = enc.encode(safePath(nameValue));
|
|
513
523
|
const size = blob.size;
|
|
514
524
|
const localOffset = resume?.localOffset ?? this.offset;
|
|
515
525
|
const zip64 = size >= 0xffffffff;
|
|
526
|
+
const legacy = this.format === LEGACY_ZIP_FORMAT;
|
|
527
|
+
if (resume && (resume.zipFormat || LEGACY_ZIP_FORMAT) !== this.format)
|
|
528
|
+
throw new Error("Formato del checkpoint ZIP diverso dal caricamento.");
|
|
529
|
+
// STORE has a known size, but needs a CRC before its local header can
|
|
530
|
+
// be self-contained. Two bounded reads avoid buffering whole photos.
|
|
531
|
+
let headerCrc = 0;
|
|
532
|
+
if (!legacy) {
|
|
533
|
+
let value = 0xffffffff;
|
|
534
|
+
progress?.(0, true);
|
|
535
|
+
for (let offset = 0; offset < size; offset += READ) {
|
|
536
|
+
await this.sinkGate();
|
|
537
|
+
const length = Math.min(READ, size - offset);
|
|
538
|
+
const chunk = new Uint8Array(await blob.slice(offset, offset + length).arrayBuffer());
|
|
539
|
+
if (chunk.length !== length)
|
|
540
|
+
throw new Error("Lettura sorgente incompleta durante la verifica.");
|
|
541
|
+
value = crc32Update(value, chunk);
|
|
542
|
+
progress?.(offset + length, true);
|
|
543
|
+
}
|
|
544
|
+
headerCrc = (value ^ 0xffffffff) >>> 0;
|
|
545
|
+
if (resume && resume.headerCrc !== headerCrc)
|
|
546
|
+
throw new Error("Il file è cambiato: il checksum non corrisponde al checkpoint.");
|
|
547
|
+
}
|
|
516
548
|
const extra = zip64 ? zip64Extra([size, size]) : new Uint8Array();
|
|
517
549
|
const local = bytes(30 + name.length + extra.length);
|
|
518
550
|
const v = view(local);
|
|
519
551
|
v.setUint32(0, 0x04034b50, true);
|
|
520
552
|
v.setUint16(4, zip64 ? 45 : 20, true);
|
|
521
|
-
v.setUint16(6, 0x0808, true); // descriptor
|
|
553
|
+
v.setUint16(6, legacy ? 0x0808 : 0x0800, true); // UTF-8; descriptor only for legacy checkpoints
|
|
522
554
|
v.setUint16(8, 0, true); // STORE
|
|
523
555
|
const [time, date] = dosDate(modified, this.utc);
|
|
524
556
|
v.setUint16(10, time, true);
|
|
525
557
|
v.setUint16(12, date, true);
|
|
526
|
-
v.setUint32(
|
|
527
|
-
v.setUint32(
|
|
558
|
+
v.setUint32(14, legacy ? 0 : headerCrc, true);
|
|
559
|
+
v.setUint32(18, zip64 ? 0xffffffff : legacy ? 0 : size, true);
|
|
560
|
+
v.setUint32(22, zip64 ? 0xffffffff : legacy ? 0 : size, true);
|
|
528
561
|
v.setUint16(26, name.length, true);
|
|
529
562
|
v.setUint16(28, extra.length, true);
|
|
530
563
|
local.set(name, 30);
|
|
@@ -564,46 +597,56 @@ export class ZipStream {
|
|
|
564
597
|
read += chunk.byteLength;
|
|
565
598
|
await this.push(chunk);
|
|
566
599
|
if (checkpoint && this.offset % PART === 0)
|
|
567
|
-
await checkpoint({ name: nameValue, size, read, crc, chain, localOffset, dataOffset, time, date
|
|
600
|
+
await checkpoint({ name: nameValue, size, read, crc, chain, localOffset, dataOffset, time, date,
|
|
601
|
+
zipFormat: this.format, ...(!legacy ? { headerCrc } : {}) }, this.offset);
|
|
568
602
|
progress?.(read);
|
|
569
603
|
}
|
|
570
604
|
crc = (crc ^ 0xffffffff) >>> 0;
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
605
|
+
if (!legacy && crc !== headerCrc)
|
|
606
|
+
throw new Error("Il file è cambiato durante il caricamento. Riseleziona i file e riprova.");
|
|
607
|
+
if (legacy) {
|
|
608
|
+
const descriptor = bytes(zip64 ? 24 : 16);
|
|
609
|
+
const d = view(descriptor);
|
|
610
|
+
d.setUint32(0, 0x08074b50, true);
|
|
611
|
+
d.setUint32(4, crc, true);
|
|
612
|
+
if (zip64) {
|
|
613
|
+
setU64(d, 8, size);
|
|
614
|
+
setU64(d, 16, size);
|
|
615
|
+
}
|
|
616
|
+
else {
|
|
617
|
+
d.setUint32(8, size, true);
|
|
618
|
+
d.setUint32(12, size, true);
|
|
619
|
+
}
|
|
620
|
+
await this.push(descriptor);
|
|
582
621
|
}
|
|
583
|
-
await this.push(descriptor);
|
|
584
622
|
this.entries.push({ name, size, crc, localOffset, time, date });
|
|
585
623
|
return { offset: dataOffset, length: size, crc32: crc };
|
|
586
624
|
}
|
|
587
625
|
async close() {
|
|
588
626
|
const centralOffset = this.offset;
|
|
589
627
|
for (const entry of this.entries) {
|
|
590
|
-
const
|
|
591
|
-
const
|
|
628
|
+
const legacy = this.format === LEGACY_ZIP_FORMAT;
|
|
629
|
+
const largeSize = entry.size >= 0xffffffff;
|
|
630
|
+
const largeOffset = entry.localOffset >= 0xffffffff;
|
|
631
|
+
const large = largeSize || largeOffset;
|
|
632
|
+
const values = legacy && large ? [entry.size, entry.size, entry.localOffset]
|
|
633
|
+
: [...(largeSize ? [entry.size, entry.size] : []), ...(largeOffset ? [entry.localOffset] : [])];
|
|
634
|
+
const extra = values.length ? zip64Extra(values) : new Uint8Array();
|
|
592
635
|
const central = bytes(46 + entry.name.length + extra.length);
|
|
593
636
|
const v = view(central);
|
|
594
637
|
v.setUint32(0, 0x02014b50, true);
|
|
595
638
|
v.setUint16(4, 45, true);
|
|
596
639
|
v.setUint16(6, large ? 45 : 20, true);
|
|
597
|
-
v.setUint16(8, 0x0808, true);
|
|
640
|
+
v.setUint16(8, legacy ? 0x0808 : 0x0800, true);
|
|
598
641
|
v.setUint16(10, 0, true);
|
|
599
642
|
v.setUint16(12, entry.time, true);
|
|
600
643
|
v.setUint16(14, entry.date, true);
|
|
601
644
|
v.setUint32(16, entry.crc, true);
|
|
602
|
-
v.setUint32(20, large ? 0xffffffff : entry.size, true);
|
|
603
|
-
v.setUint32(24, large ? 0xffffffff : entry.size, true);
|
|
645
|
+
v.setUint32(20, largeSize || (legacy && large) ? 0xffffffff : entry.size, true);
|
|
646
|
+
v.setUint32(24, largeSize || (legacy && large) ? 0xffffffff : entry.size, true);
|
|
604
647
|
v.setUint16(28, entry.name.length, true);
|
|
605
648
|
v.setUint16(30, extra.length, true);
|
|
606
|
-
v.setUint32(42, large ? 0xffffffff : entry.localOffset, true);
|
|
649
|
+
v.setUint32(42, largeOffset || (legacy && large) ? 0xffffffff : entry.localOffset, true);
|
|
607
650
|
central.set(entry.name, 46);
|
|
608
651
|
central.set(extra, 46 + entry.name.length);
|
|
609
652
|
await this.push(central);
|
|
@@ -407,7 +407,7 @@ export function DeliveryWorkspace({ delivery: fromServer, busy, onAction, onRefr
|
|
|
407
407
|
}
|
|
408
408
|
}, children: "Accorcia link" })), _jsx("button", { className: "ak-btn ak-btn-primary", onClick: () => void copy(link.url, "link"), children: copied === "link" ? "Copiato" : "Copia il link" }), _jsx("button", { className: "ak-btn", onClick: () => void copy(messageFor(draft, link, expiry), "messaggio"), children: copied === "messaggio" ? "Copiato" : "Copia il messaggio" }), _jsx("a", { className: "ak-btn", target: "_blank", rel: "noreferrer", href: `https://wa.me/?text=${encodeURIComponent(messageFor(draft, link, expiry))}`, children: "Apri in WhatsApp" })] }), _jsx("p", { className: "dl-link-stats", children: linkStory(link) }), draft.pin && (_jsxs("p", { className: "dl-link-stats", children: ["Codice di accesso ", _jsx("strong", { children: draft.pin }), " \u2014 \u00E8 gi\u00E0 dentro il messaggio."] }))] })) : (_jsxs("section", { className: "dl-card dl-get", children: [_jsx("button", { className: "ak-btn ak-btn-primary dl-get-btn", disabled: !delivery || saving || busy, onClick: () => void save(), children: saving ? "Preparo…" : "Ottieni il link" }), _jsx("p", { children: delivery
|
|
409
409
|
? "Il link vale già mentre l'upload continua: chi lo apre vede che i file stanno arrivando."
|
|
410
|
-
: "Aggiungi i file: il caricamento parte subito e il resto si compila mentre va." })] }))] }), _jsxs("div", { className: "dl-side", children: [_jsxs(Group, { title: "Chi pu\u00F2 aprirlo", children: [_jsxs("label", { className: "dl-radio", children: [_jsx("input", { type: "radio", name: "dl-access", checked: !draft.pin, onChange: () => change({ pin: "" }) }), _jsxs("span", { children: [_jsx("strong", { children: "Chiunque abbia il link" }), _jsx("small", { children: "Il link \u00E8 lungo e casuale: non si indovina." })] })] }), _jsxs("label", { className: "dl-radio", children: [_jsx("input", { type: "radio", name: "dl-access", checked: !!draft.pin, onChange: () => change({ pin: draft.pin || makeCode() }) }), _jsxs("span", { children: [_jsx("strong", { children: "Serve anche un codice" }), _jsx("small", { children: "Da mandare insieme al link. Dopo dieci errori si blocca per un'ora." })] })] }), !!draft.pin && (_jsxs("div", { className: "dl-code", children: [_jsx("input", { value: draft.pin, spellCheck: false, onChange: (e) => change({ pin: e.target.value.toUpperCase() }) }), _jsx("button", { className: "ak-btn", onClick: () => change({ pin: makeCode() }), children: "Altro" })] }))] }), _jsxs(Group, { title: "Cosa riceve", note: packages.length
|
|
410
|
+
: "Aggiungi i file: il caricamento parte subito e il resto si compila mentre va." })] }))] }), _jsxs("div", { className: "dl-side", children: [_jsxs(Group, { title: "Chi pu\u00F2 aprirlo", children: [_jsxs("label", { className: "dl-radio", children: [_jsx("input", { type: "radio", name: "dl-access", checked: !draft.pin, onChange: () => change({ pin: "" }) }), _jsxs("span", { children: [_jsx("strong", { children: "Chiunque abbia il link" }), _jsx("small", { children: "Il link \u00E8 lungo e casuale: non si indovina." })] })] }), _jsxs("label", { className: "dl-radio", children: [_jsx("input", { type: "radio", name: "dl-access", checked: !!draft.pin, onChange: () => change({ pin: draft.pin || makeCode() }) }), _jsxs("span", { children: [_jsx("strong", { children: "Serve anche un codice" }), _jsx("small", { children: "Da mandare insieme al link. Dopo dieci errori si blocca per un'ora." })] })] }), !!draft.pin && (_jsxs("div", { className: "dl-code", children: [_jsx("input", { value: draft.pin, spellCheck: false, onChange: (e) => change({ pin: e.target.value.toUpperCase() }) }), _jsx("button", { className: "ak-btn", onClick: () => change({ pin: makeCode() }), children: "Altro" })] }))] }), _jsxs(Group, { title: "Richiedi pagamento", children: [_jsx("span", { className: "dl-payment-status", children: "Chiedi di attivare la funzione" }), _jsxs("label", { className: "dl-check dl-payment-disabled", children: [_jsx("input", { type: "checkbox", checked: false, disabled: true, "aria-describedby": "dl-payment-description" }), _jsxs("span", { children: [_jsx("strong", { children: "Sblocca gallery e download al saldo" }), _jsx("small", { id: "dl-payment-description", children: "Potrai impostare l\u2019importo da incassare per questa consegna. Il cliente pagher\u00E0 una sola volta per sbloccare la galleria e scaricare i file." })] })] }), _jsx("p", { className: "dl-note", children: "Questa opzione non \u00E8 ancora disponibile: il transfer non richiede pagamenti e segue le impostazioni di accesso qui sopra." })] }), _jsxs(Group, { title: "Cosa riceve", note: packages.length
|
|
411
411
|
? "Si decide prima di caricare: per cambiarle bisognerebbe ricaricare i file."
|
|
412
412
|
: undefined, children: [_jsxs("label", { className: "dl-check", children: [_jsx("input", { type: "checkbox", checked: draft.gallery, disabled: !!packages.length, onChange: (e) => change({ gallery: e.target.checked }) }), _jsxs("span", { children: [_jsx("strong", { children: "Vede le foto sulla pagina" }), _jsx("small", { children: "Anteprime e ingrandimento, senza scaricare niente. Da telefono \u00E8 la differenza fra un link e un Transfer." })] })] }), _jsxs("label", { className: "dl-check", children: [_jsx("input", { type: "checkbox", checked: draft.light, disabled: !!packages.length, onChange: (e) => change({ light: e.target.checked }) }), _jsxs("span", { children: [_jsx("strong", { children: "Pu\u00F2 scaricare una versione leggera" }), _jsx("small", { children: "Un secondo download, adatto a social e web. Gli originali ci sono sempre." })] })] }), draft.light && (_jsxs("label", { className: "dl-field", children: [_jsx("span", { children: "Formato della versione leggera" }), _jsxs("select", { className: "dl-select", "aria-label": "Formato della versione leggera", value: draft.webFormat, disabled: !!packages.length || uploading, onChange: (e) => change({ webFormat: e.target.value === "jpg" ? "jpg" : "webp" }), children: [_jsx("option", { value: "webp", children: "WebP \u2014 file pi\u00F9 piccoli" }), _jsx("option", { value: "jpg", disabled: !jpegEnabled && draft.webFormat !== "jpg", children: "JPG \u2014 massima compatibilit\u00E0" })] }), _jsxs("small", { children: ["2048 px sul lato lungo, senza ingrandire le foto pi\u00F9 piccole. Qualit\u00E0 ", draft.webFormat === "jpg" ? "82%" : "80%", "."] })] }))] }), downloadLabelsEnabled && (_jsxs(Group, { title: "Testi dei pulsanti di download", note: "Lascia vuoto per usare il testo predefinito. Puoi cambiarli anche dopo l\u2019invio, senza ricaricare i file.", children: [delivery?.status !== "archived" && (_jsx(Field, { label: "Pulsante degli originali", children: _jsx("input", { value: draft.downloadOriginalsLabel, maxLength: 80, placeholder: "Scarica i file", onChange: (e) => change({ downloadOriginalsLabel: e.target.value }) }) })), (packages.length ? packages.some(pkg => pkg.webEnabled) : draft.light) && (_jsx(Field, { label: "Pulsante della versione leggera", children: _jsx("input", { value: draft.downloadWebLabel, maxLength: 80, placeholder: "Versione leggera", onChange: (e) => change({ downloadWebLabel: e.target.value }) }) }))] })), _jsxs(Group, { title: "Per quanto resta scaricabile", children: [_jsxs("select", { className: "dl-select", value: draft.duration, onChange: (e) => change({ duration: e.target.value }), children: [delivery && _jsx("option", { value: "keep", children: delivery.filesExpiresAt
|
|
413
413
|
? `Fino al ${formatDate(delivery.filesExpiresAt)}`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cardinali-studio/adminkit",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.39",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Motore riusabile di un pannello di amministrazione per siti Hugo su Cloudflare Pages: codec del front matter guidato dallo schema, form React, libreria media su R2. NIENTE di specifico di un sito qui dentro.",
|
|
6
6
|
"license": "0BSD",
|
|
@@ -104,6 +104,7 @@
|
|
|
104
104
|
"react-dom": "^19.2.8",
|
|
105
105
|
"react-hook-form": "^7.85.0",
|
|
106
106
|
"typescript": "^5.9.3",
|
|
107
|
+
"unzipper": "0.12.5",
|
|
107
108
|
"wrangler": "4.114.0",
|
|
108
109
|
"zod": "^4.4.3"
|
|
109
110
|
},
|