@cardinali-studio/adminkit 2.2.34 → 2.2.36
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/dist/api.d.ts +1 -0
- package/dist/app/App.js +19 -4
- package/dist/app/BugReport.d.ts +7 -0
- package/dist/app/BugReport.js +212 -0
- package/dist/app/app.css +124 -0
- package/dist/app/index.js +3 -0
- package/dist/app/lib/publish.d.ts +2 -0
- package/dist/app/lib/publish.js +4 -1
- package/dist/app/site.d.ts +5 -0
- package/dist/direct-publication.d.ts +2 -0
- package/dist/direct-publication.js +1 -0
- package/dist/form/Trash.js +25 -10
- package/dist/form/media/MediaFolders.js +6 -1
- package/dist/form/media/MediaGrid.js +1 -1
- package/package.json +3 -2
package/dist/api.d.ts
CHANGED
package/dist/app/App.js
CHANGED
|
@@ -14,7 +14,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
14
14
|
* separati (una collezione e un singleton), ma da qui dentro sono lo stesso gesto e non c'è
|
|
15
15
|
* ragione di farli sembrare due funzioni diverse.
|
|
16
16
|
*/
|
|
17
|
-
import { useEffect, useMemo, useState } from "react";
|
|
17
|
+
import { useEffect, useMemo, useState, useRef } from "react";
|
|
18
18
|
/* L'ordine di questi tre import È la separazione motore/cliente, e non va scambiato:
|
|
19
19
|
* i default del kit, poi l'identità di questo sito che li sovrascrive, poi l'impaginazione. */
|
|
20
20
|
import "../theme.css";
|
|
@@ -61,6 +61,8 @@ export function App() {
|
|
|
61
61
|
}
|
|
62
62
|
function CurrentAdminApp({ adminInterface, setAdminInterface, }) {
|
|
63
63
|
const [route, go] = useHashRoute();
|
|
64
|
+
const [releaseOpen, setReleaseOpen] = useState(false);
|
|
65
|
+
const [rollbackConfirm, setRollbackConfirm] = useState(false);
|
|
64
66
|
const { theme, toggle: toggleTheme } = useTheme();
|
|
65
67
|
/* Si entra dalla Panoramica, non dalle Pagine: aprendo il pannello la domanda è quasi sempre
|
|
66
68
|
«è successo qualcosa?», e la risposta stava sparsa in tre sezioni da visitare una per una. */
|
|
@@ -99,6 +101,20 @@ function CurrentAdminApp({ adminInterface, setAdminInterface, }) {
|
|
|
99
101
|
finito uno, non all'avanzamento — altrimenti il pannello si ridisegnerebbe due volte al
|
|
100
102
|
secondo per tutta la durata dell'upload. */
|
|
101
103
|
const uploadCompletions = useUploadCompletions();
|
|
104
|
+
useEffect(() => { if (site().directPublication && section === "media")
|
|
105
|
+
void publish.reload(); }, [media.mediaOperations]);
|
|
106
|
+
const lastObservedRelease = useRef(null);
|
|
107
|
+
useEffect(() => {
|
|
108
|
+
if (lastObservedRelease.current && publish.activeReleaseId && lastObservedRelease.current !== publish.activeReleaseId) {
|
|
109
|
+
if (!pages.dirty)
|
|
110
|
+
void pages.load();
|
|
111
|
+
if (home.data && !home.dirty)
|
|
112
|
+
void home.load();
|
|
113
|
+
if (settings.data && !settings.dirty)
|
|
114
|
+
void settings.load();
|
|
115
|
+
}
|
|
116
|
+
lastObservedRelease.current = publish.activeReleaseId;
|
|
117
|
+
}, [publish.activeReleaseId]);
|
|
102
118
|
useEffect(() => {
|
|
103
119
|
if (section === "deliveries")
|
|
104
120
|
void deliveries.load();
|
|
@@ -261,7 +277,7 @@ function CurrentAdminApp({ adminInterface, setAdminInterface, }) {
|
|
|
261
277
|
|| (section === "requests" && !openReq)
|
|
262
278
|
|| (section === "bookings" && !openBooking)
|
|
263
279
|
|| (section === "media" && (view === "trash" || mediaSettingsOpen || mediaCrumbs.length === 0));
|
|
264
|
-
return (_jsx(ProsePages.Provider, { value: pageLinks, children: _jsxs("div", { className: "app" + (hasList(section) && !(section === "media" && MediaEditorView) && !offModule ? "" : " is-two"), children: [_jsxs("nav", { className: "rail", children: [_jsxs("div", { className: "rail-brand", children: [_jsx("span", { className: "rail-brand-name", children: site().name }), _jsx("span", { className: "rail-brand-role", children: "pannello" })] }), _jsx("div", { className: "rail-nav", children: navGroups.map((g) => (_jsxs("div", { className: "rail-group", children: [g.title && _jsx("span", { className: "rail-group-title", children: g.title }), g.items.map((n) => (_jsxs("button", { className: "rail-item" + (section === n.id ? " is-on" : ""), "aria-label": n.label, disabled: section === "media" && media.mutating, onClick: () => go(`/${n.id}`), children: [_jsx(n.icon, {}), _jsx("span", { children: n.label }), "badge" in n && n.badge ? _jsx("span", { className: "rail-badge", children: n.badge }) : null] }, n.id)))] }, g.title ?? "top"))) }), _jsxs("div", { className: "rail-foot", children: [_jsxs("a", { className: "rail-item", href: "/", target: "_blank", rel: "noreferrer", "aria-label": "Vedi il sito", children: [_jsx(Icon.out, {}), _jsx("span", { children: "Vedi il sito" })] }), _jsxs("button", { className: "rail-item", onClick: toggleTheme, "aria-label": theme === "dark" ? "Tema chiaro" : "Tema scuro", children: [theme === "dark" ? _jsx(Icon.sun, {}) : _jsx(Icon.moon, {}), _jsx("span", { children: theme === "dark" ? "Tema chiaro" : "Tema scuro" })] }), _jsxs("button", { className: "rail-item" + (section === "settings" ? " is-on" : ""), "aria-label": "Impostazioni", disabled: section === "media" && media.mutating, onClick: () => go("/settings/site"), children: [_jsx(Icon.settings, {}), _jsx("span", { children: "Impostazioni" })] })] })] }), hasList(section) && !(section === "media" && MediaEditorView) && !offModule && (_jsxs("div", { className: "col", children: [section === "pages" && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "col-head col-head-actions", children: [_jsx("span", { className: "col-title", children: "Pagine" }), list && _jsx(NewPageButton, { pages: list, onCreate: (p) => pages.update([...list, p]) })] }), _jsx("div", { className: "col-body", children: pages.error ? _jsx("p", { className: "ak-error", children: pages.error })
|
|
280
|
+
return (_jsx(ProsePages.Provider, { value: pageLinks, children: _jsxs("div", { className: "app" + (hasList(section) && !(section === "media" && MediaEditorView) && !offModule ? "" : " is-two"), children: [_jsxs("nav", { className: "rail", children: [_jsxs("div", { className: "rail-brand", children: [_jsx("span", { className: "rail-brand-name", children: site().name }), _jsx("span", { className: "rail-brand-role", children: "pannello" })] }), _jsx("div", { className: "rail-nav", children: navGroups.map((g) => (_jsxs("div", { className: "rail-group", children: [g.title && _jsx("span", { className: "rail-group-title", children: g.title }), g.items.map((n) => (_jsxs("button", { className: "rail-item" + (section === n.id ? " is-on" : ""), "aria-label": n.label, disabled: section === "media" && media.mutating, onClick: () => go(`/${n.id}`), children: [_jsx(n.icon, {}), _jsx("span", { children: n.label }), "badge" in n && n.badge ? _jsx("span", { className: "rail-badge", children: n.badge }) : null] }, n.id)))] }, g.title ?? "top"))) }), _jsxs("div", { className: "rail-foot", children: [site().directPublication && _jsxs("div", { className: "ak-release-control", children: [_jsxs("button", { className: "ak-release-trigger", "aria-haspopup": "dialog", "aria-expanded": releaseOpen, onClick: () => { setReleaseOpen(!releaseOpen); setRollbackConfirm(false); }, children: [_jsx("span", { className: "ak-release-dot" + (publish.verified ? " is-ok" : "") }), publish.configured ? (publish.verified ? "Online" : "In attesa") : "Non disponibile"] }), releaseOpen && _jsxs("section", { className: "ak-release-menu", role: "dialog", "aria-label": "Stato della pubblicazione", children: [_jsx("strong", { children: publish.verified ? "Online" : "Verifica in attesa" }), _jsx("p", { children: publish.verified ? "Il sito mostra gli ultimi contenuti pubblicati." : "I contenuti sono salvati in D1; verifica la versione visibile sul dominio pubblico." }), _jsx("dl", { children: _jsxs("div", { children: [_jsx("dt", { children: "Versione" }), _jsx("dd", { children: publish.activeReleaseId?.slice(0, 8) || "—" })] }) }), _jsx("button", { className: "ak-btn", onClick: () => void publish.verify(), children: "Verifica sito pubblico" }), publish.previousReleaseId && !rollbackConfirm && _jsx("button", { className: "ak-btn", disabled: publish.publishing || pages.dirty || home.dirty || settings.dirty, onClick: () => setRollbackConfirm(true), children: "Ripristina la versione precedente" }), rollbackConfirm && _jsxs("div", { children: [_jsx("p", { children: "Ripristinare la versione precedente? Le bozze restano salvate." }), _jsx("button", { className: "ak-btn ak-btn-primary", disabled: publish.publishing, onClick: () => void publish.rollback().then(async () => { await pages.load(); await home.load(); await settings.load(); setRollbackConfirm(false); }), children: "Conferma ripristino" }), _jsx("button", { className: "ak-btn", onClick: () => setRollbackConfirm(false), children: "Annulla" })] }), _jsx("button", { className: "ak-btn ak-btn-quiet", onClick: () => setReleaseOpen(false), children: "Chiudi" })] })] }), _jsxs("a", { className: "rail-item", href: "/", target: "_blank", rel: "noreferrer", "aria-label": "Vedi il sito", children: [_jsx(Icon.out, {}), _jsx("span", { children: "Vedi il sito" })] }), _jsxs("button", { className: "rail-item", onClick: toggleTheme, "aria-label": theme === "dark" ? "Tema chiaro" : "Tema scuro", children: [theme === "dark" ? _jsx(Icon.sun, {}) : _jsx(Icon.moon, {}), _jsx("span", { children: theme === "dark" ? "Tema chiaro" : "Tema scuro" })] }), _jsxs("button", { className: "rail-item" + (section === "settings" ? " is-on" : ""), "aria-label": "Impostazioni", disabled: section === "media" && media.mutating, onClick: () => go("/settings/site"), children: [_jsx(Icon.settings, {}), _jsx("span", { children: "Impostazioni" })] })] })] }), hasList(section) && !(section === "media" && MediaEditorView) && !offModule && (_jsxs("div", { className: "col", children: [section === "pages" && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "col-head col-head-actions", children: [_jsx("span", { className: "col-title", children: "Pagine" }), list && _jsx(NewPageButton, { pages: list, onCreate: (p) => pages.update([...list, p]) })] }), _jsx("div", { className: "col-body", children: pages.error ? _jsx("p", { className: "ak-error", children: pages.error })
|
|
265
281
|
: !list ? _jsx("p", { className: "ak-hint", children: "Carico\u2026" })
|
|
266
282
|
: _jsx(PagesList, { pages: list, current: editing?.slug, onEdit: (slug) => go(`/pages/${slug}`), languages: languages, onChange: pages.update, onRenamed: (from, to) => {
|
|
267
283
|
if (editing?.slug === from)
|
|
@@ -294,8 +310,7 @@ function CurrentAdminApp({ adminInterface, setAdminInterface, }) {
|
|
|
294
310
|
onClick: () => void saver.save().then(() => publish.reload()), children: saver.saving ? "Salvo…" : "Salva" })] })), contentPersistenceMode() === "d1" && d1ContextPending && otherPendingCount > 0 && (_jsx("span", { className: "pane-count", title: "Queste modifiche restano in bozza", children: otherPendingCount === 1 ? "1 altra bozza" : `${otherPendingCount} altre bozze` })), site().directPublication && _jsxs(_Fragment, { children: [d1ContextPending && d1PublishTarget && _jsx("button", { className: "ak-btn", disabled: publish.publishing || !!saver?.dirty || !!saver?.saving, onClick: () => void publish.preview(d1PublishTarget), children: "Anteprima" }), d1ContextPending && d1PublishTarget && _jsx("button", { className: "ak-btn", disabled: publish.publishing || !!saver?.dirty || !!saver?.saving, onClick: () => void api.discardContent(d1PublishTarget.documentId, d1PublishTarget.expectedDraftRevisionId, null).then(async () => { if (section === "pages")
|
|
295
311
|
await pages.load(); if (section === "home")
|
|
296
312
|
await home.load(); if (section === "settings")
|
|
297
|
-
await settings.load(); await publish.reload(); }), children: "Scarta bozza" })
|
|
298
|
-
void publish.rollback().then(async () => { await pages.load(); await home.load(); await settings.load(); }); }, children: "Ripristina precedente" })] }), ((contentPersistenceMode() === "d1" && d1ContextPending)
|
|
313
|
+
await settings.load(); await publish.reload(); }), children: "Scarta bozza" })] }), ((contentPersistenceMode() === "d1" && d1ContextPending)
|
|
299
314
|
|| (contentPersistenceMode() !== "d1" && publish.ahead > 0)) && (_jsx("button", { className: "ak-btn ak-btn-primary", disabled: publish.publishing || !!saver?.dirty || !!saver?.saving || (site().directPublication === true && !publish.previewApproved(d1PublishTarget)), onClick: () => void publish.publish(d1PublishTarget ?? undefined).then(async () => { if (!saver?.dirty) {
|
|
300
315
|
if (section === "pages")
|
|
301
316
|
await pages.load();
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { domToBlob } from "modern-screenshot";
|
|
3
|
+
import { StrictMode, useEffect, useRef, useState } from "react";
|
|
4
|
+
import { createRoot } from "react-dom/client";
|
|
5
|
+
function BugIcon() {
|
|
6
|
+
return _jsxs("svg", { viewBox: "0 0 24 24", "aria-hidden": "true", children: [_jsx("path", { d: "M9 4.5h6M10 2.5v2m4-2v2M7.5 9H4m3.5 4H4m12.5-4H20m-3.5 4H20M8 6.5h8v7.25a4 4 0 0 1-8 0V6.5Z" }), _jsx("path", { d: "M8 10.5h8M12 10.5v7" })] });
|
|
7
|
+
}
|
|
8
|
+
function DownloadIcon() {
|
|
9
|
+
return _jsx("svg", { viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { d: "M12 3v11m0 0 4-4m-4 4-4-4M5 17v3h14v-3" }) });
|
|
10
|
+
}
|
|
11
|
+
function CloseIcon() {
|
|
12
|
+
return _jsx("svg", { viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { d: "m6 6 12 12M18 6 6 18" }) });
|
|
13
|
+
}
|
|
14
|
+
function MailIcon() {
|
|
15
|
+
return _jsx("svg", { viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { d: "M3.5 6.5h17v12h-17zM4 7l8 6 8-6" }) });
|
|
16
|
+
}
|
|
17
|
+
function WhatsAppIcon() {
|
|
18
|
+
return _jsxs("svg", { viewBox: "0 0 24 24", "aria-hidden": "true", children: [_jsx("path", { d: "M5.2 18.8 3.8 22l3.5-1.2A9 9 0 1 0 5.2 18.8Z" }), _jsx("path", { d: "M9 7.7c.2-.5.5-.5.8-.5h.5c.2 0 .4.1.5.4l.8 1.9c.1.3 0 .5-.2.7l-.6.7c-.2.2-.1.4 0 .6.7 1.2 1.6 2.1 2.8 2.7.2.1.4.1.6-.1l.8-1c.2-.2.4-.3.7-.2l1.8.9c.3.1.4.3.4.5 0 .4-.2 1.5-1.1 2.1-.8.5-1.8.7-3.4.2-1.2-.4-2.7-1.1-4.2-2.7-1.2-1.2-2.1-2.8-2.4-4-.3-1.2.2-1.9.5-2.2.3-.4.6-.5.8-.5" })] });
|
|
19
|
+
}
|
|
20
|
+
function IconButton({ children, label, onClick, disabled = false }) {
|
|
21
|
+
return _jsx("button", { type: "button", className: "ak-bug-icon-button", "aria-label": label, title: label, onClick: onClick, disabled: disabled, children: children });
|
|
22
|
+
}
|
|
23
|
+
async function capturePage() {
|
|
24
|
+
await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve())));
|
|
25
|
+
const scale = Math.min(1, 1600 / window.innerWidth, 1200 / window.innerHeight);
|
|
26
|
+
const blob = await domToBlob(document.body, {
|
|
27
|
+
type: "image/webp",
|
|
28
|
+
quality: .68,
|
|
29
|
+
backgroundColor: getComputedStyle(document.body).backgroundColor,
|
|
30
|
+
scale,
|
|
31
|
+
width: window.innerWidth,
|
|
32
|
+
height: window.innerHeight,
|
|
33
|
+
filter: (node) => !(node instanceof Element && node.closest("#ak-bug-report-root")),
|
|
34
|
+
features: { restoreScrollPosition: true },
|
|
35
|
+
fetch: { requestInit: { cache: "force-cache", credentials: "same-origin" } },
|
|
36
|
+
});
|
|
37
|
+
if (!blob)
|
|
38
|
+
throw new Error("Screenshot non disponibile");
|
|
39
|
+
const extension = blob.type === "image/webp" ? "webp" : "jpg";
|
|
40
|
+
const day = new Date().toISOString().slice(0, 10);
|
|
41
|
+
return { blob, objectUrl: URL.createObjectURL(blob), filename: `bug-report-${day}.${extension}` };
|
|
42
|
+
}
|
|
43
|
+
function currentPageLabel() {
|
|
44
|
+
const hash = location.hash.replace(/^#\/?/, "");
|
|
45
|
+
return hash ? `${location.pathname}#/${hash}` : location.pathname;
|
|
46
|
+
}
|
|
47
|
+
function BugReport({ siteName, email, whatsapp }) {
|
|
48
|
+
const [enabled, setEnabled] = useState(() => document.documentElement.dataset.adminInterface === "next");
|
|
49
|
+
const [open, setOpen] = useState(false);
|
|
50
|
+
const [capturing, setCapturing] = useState(false);
|
|
51
|
+
const [sending, setSending] = useState(null);
|
|
52
|
+
const [message, setMessage] = useState("");
|
|
53
|
+
const [screenshot, setScreenshot] = useState(null);
|
|
54
|
+
const [uploaded, setUploaded] = useState(null);
|
|
55
|
+
const [error, setError] = useState(null);
|
|
56
|
+
const dialogRef = useRef(null);
|
|
57
|
+
const messageRef = useRef(null);
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
const observer = new MutationObserver(() => {
|
|
60
|
+
setEnabled(document.documentElement.dataset.adminInterface === "next");
|
|
61
|
+
});
|
|
62
|
+
observer.observe(document.documentElement, { attributes: true, attributeFilter: ["data-admin-interface"] });
|
|
63
|
+
return () => observer.disconnect();
|
|
64
|
+
}, []);
|
|
65
|
+
useEffect(() => () => {
|
|
66
|
+
if (screenshot)
|
|
67
|
+
URL.revokeObjectURL(screenshot.objectUrl);
|
|
68
|
+
}, [screenshot]);
|
|
69
|
+
/* Ogni apertura è una nuova segnalazione. Lo screenshot viene già rigenerato; anche il testo
|
|
70
|
+
* deve perdere il report precedente quando il dialog viene chiuso, qualunque sia il gesto. */
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
if (!open)
|
|
73
|
+
setMessage("");
|
|
74
|
+
}, [open]);
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
if (!open)
|
|
77
|
+
return;
|
|
78
|
+
const app = document.getElementById("root");
|
|
79
|
+
if (app)
|
|
80
|
+
app.inert = true;
|
|
81
|
+
messageRef.current?.focus();
|
|
82
|
+
const keydown = (event) => {
|
|
83
|
+
if (event.key === "Escape" && !sending)
|
|
84
|
+
setOpen(false);
|
|
85
|
+
if (event.key !== "Tab")
|
|
86
|
+
return;
|
|
87
|
+
const focusable = [...(dialogRef.current?.querySelectorAll("button:not(:disabled), a[href], textarea:not(:disabled)") || [])];
|
|
88
|
+
if (!focusable.length)
|
|
89
|
+
return;
|
|
90
|
+
const first = focusable[0];
|
|
91
|
+
const last = focusable[focusable.length - 1];
|
|
92
|
+
if (event.shiftKey && document.activeElement === first) {
|
|
93
|
+
event.preventDefault();
|
|
94
|
+
last.focus();
|
|
95
|
+
}
|
|
96
|
+
else if (!event.shiftKey && document.activeElement === last) {
|
|
97
|
+
event.preventDefault();
|
|
98
|
+
first.focus();
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
addEventListener("keydown", keydown);
|
|
102
|
+
return () => {
|
|
103
|
+
if (app)
|
|
104
|
+
app.inert = false;
|
|
105
|
+
removeEventListener("keydown", keydown);
|
|
106
|
+
};
|
|
107
|
+
}, [open, sending]);
|
|
108
|
+
const begin = async () => {
|
|
109
|
+
if (capturing)
|
|
110
|
+
return;
|
|
111
|
+
setCapturing(true);
|
|
112
|
+
setError(null);
|
|
113
|
+
setUploaded(null);
|
|
114
|
+
try {
|
|
115
|
+
const next = await capturePage();
|
|
116
|
+
setScreenshot(next);
|
|
117
|
+
setOpen(true);
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
setScreenshot(null);
|
|
121
|
+
setOpen(true);
|
|
122
|
+
setError("Non sono riuscito a creare lo screenshot. Riprova.");
|
|
123
|
+
}
|
|
124
|
+
finally {
|
|
125
|
+
setCapturing(false);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
const retry = async () => {
|
|
129
|
+
setOpen(false);
|
|
130
|
+
await begin();
|
|
131
|
+
};
|
|
132
|
+
const upload = async () => {
|
|
133
|
+
if (uploaded)
|
|
134
|
+
return uploaded;
|
|
135
|
+
if (!screenshot)
|
|
136
|
+
throw new Error("Crea prima lo screenshot.");
|
|
137
|
+
const form = new FormData();
|
|
138
|
+
form.set("image", screenshot.blob, screenshot.filename);
|
|
139
|
+
form.set("site", siteName);
|
|
140
|
+
form.set("page", location.href);
|
|
141
|
+
form.set("title", document.title);
|
|
142
|
+
const response = await fetch("/admin/api/bug-report", { method: "POST", body: form });
|
|
143
|
+
const result = await response.json().catch(() => null);
|
|
144
|
+
if (!response.ok || !result?.url)
|
|
145
|
+
throw new Error(result?.error || "Non sono riuscito a caricare lo screenshot.");
|
|
146
|
+
setUploaded(result);
|
|
147
|
+
return result;
|
|
148
|
+
};
|
|
149
|
+
const reportText = (screenshotUrl) => [
|
|
150
|
+
message.trim(),
|
|
151
|
+
"",
|
|
152
|
+
`Screenshot: ${screenshotUrl}`,
|
|
153
|
+
`Pagina: ${currentPageLabel()}`,
|
|
154
|
+
`AdminKit · nuova interfaccia`,
|
|
155
|
+
`Data: ${new Intl.DateTimeFormat("it-IT", { dateStyle: "medium", timeStyle: "short" }).format(new Date())}`,
|
|
156
|
+
].join("\n");
|
|
157
|
+
const send = async (channel) => {
|
|
158
|
+
if (!message.trim()) {
|
|
159
|
+
setError("Scrivi una breve descrizione del problema.");
|
|
160
|
+
messageRef.current?.focus();
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (!screenshot) {
|
|
164
|
+
setError("Crea prima lo screenshot.");
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
const whatsappWindow = channel === "whatsapp" ? window.open("", "_blank") : null;
|
|
168
|
+
if (whatsappWindow)
|
|
169
|
+
whatsappWindow.opener = null;
|
|
170
|
+
setSending(channel);
|
|
171
|
+
setError(null);
|
|
172
|
+
try {
|
|
173
|
+
const saved = await upload();
|
|
174
|
+
const text = reportText(saved.url);
|
|
175
|
+
if (channel === "email") {
|
|
176
|
+
const subject = `Report Bug ${siteName}`;
|
|
177
|
+
location.href = `mailto:${encodeURIComponent(email)}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(text)}`;
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
const target = `https://wa.me/${whatsapp.replace(/\D/g, "")}?text=${encodeURIComponent(text)}`;
|
|
181
|
+
if (whatsappWindow)
|
|
182
|
+
whatsappWindow.location.replace(target);
|
|
183
|
+
else
|
|
184
|
+
window.open(target, "_blank", "noopener,noreferrer");
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
catch (reason) {
|
|
188
|
+
whatsappWindow?.close();
|
|
189
|
+
setError(reason instanceof Error ? reason.message : "Non sono riuscito a preparare il report.");
|
|
190
|
+
}
|
|
191
|
+
finally {
|
|
192
|
+
setSending(null);
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
if (!enabled)
|
|
196
|
+
return null;
|
|
197
|
+
return _jsxs(_Fragment, { children: [_jsxs("button", { type: "button", className: "ak-bug-trigger", "aria-label": "Segnala un bug", title: "Segnala un bug", onClick: () => void begin(), disabled: capturing, children: [_jsx(BugIcon, {}), capturing && _jsx("span", { className: "ak-bug-capturing", "aria-hidden": "true" })] }), open && _jsx("div", { className: "ak-bug-backdrop", onMouseDown: (event) => {
|
|
198
|
+
if (event.target === event.currentTarget && !sending)
|
|
199
|
+
setOpen(false);
|
|
200
|
+
}, children: _jsxs("div", { className: "ak-bug-dialog", ref: dialogRef, role: "dialog", "aria-modal": "true", "aria-labelledby": "ak-bug-title", children: [_jsxs("header", { className: "ak-bug-head", children: [_jsxs("div", { children: [_jsx("span", { className: "ak-bug-kicker", children: "Segnalazione" }), _jsx("h2", { id: "ak-bug-title", children: "Riporta un problema" }), _jsx("p", { children: "Descrivi cosa non ha funzionato. La schermata ci aiuter\u00E0 a capirlo." })] }), _jsx(IconButton, { label: "Chiudi", onClick: () => setOpen(false), disabled: Boolean(sending), children: _jsx(CloseIcon, {}) })] }), _jsxs("div", { className: "ak-bug-content", children: [_jsxs("section", { className: "ak-bug-shot-card", "aria-label": "Screenshot del problema", children: [_jsxs("div", { className: "ak-bug-shot-head", children: [_jsx("strong", { children: "Schermata" }), screenshot && _jsxs("a", { className: "ak-bug-download", href: screenshot.objectUrl, download: screenshot.filename, children: [_jsx(DownloadIcon, {}), " Scarica"] })] }), screenshot
|
|
201
|
+
? _jsx("img", { src: screenshot.objectUrl, alt: "Anteprima della schermata segnalata" })
|
|
202
|
+
: _jsxs("div", { className: "ak-bug-shot-missing", children: [_jsx("span", { children: "Screenshot non disponibile" }), _jsx("button", { type: "button", onClick: () => void retry(), children: "Riprova" })] }), screenshot && _jsxs("small", { children: [Math.max(1, Math.round(screenshot.blob.size / 1024)), " KB \u00B7 il link scade dopo 30 giorni"] })] }), _jsxs("label", { className: "ak-bug-message", children: [_jsx("span", { children: "Cosa \u00E8 successo?" }), _jsx("textarea", { ref: messageRef, value: message, onChange: (event) => setMessage(event.target.value), placeholder: "Es. Ho premuto Salva, ma il titolo \u00E8 tornato come prima\u2026", rows: 8, maxLength: 3000 }), _jsxs("small", { children: [message.length, "/3000"] })] })] }), error && _jsx("p", { className: "ak-bug-error", role: "alert", children: error }), _jsxs("footer", { className: "ak-bug-actions", children: [_jsx("span", { children: sending ? "Carico lo screenshot e preparo il messaggio…" : "Lo screenshot sarà caricato solo quando invii." }), _jsxs("div", { children: [_jsxs("button", { type: "button", className: "ak-bug-send is-email", disabled: Boolean(sending) || !screenshot, onClick: () => void send("email"), children: [_jsx(MailIcon, {}), sending === "email" ? "Preparo…" : "Invia email"] }), whatsapp && _jsxs("button", { type: "button", className: "ak-bug-send is-whatsapp", disabled: Boolean(sending) || !screenshot, onClick: () => void send("whatsapp"), children: [_jsx(WhatsAppIcon, {}), sending === "whatsapp" ? "Preparo…" : "Invia WhatsApp"] })] })] })] }) })] });
|
|
203
|
+
}
|
|
204
|
+
export function mountBugReport(options) {
|
|
205
|
+
let host = document.getElementById("ak-bug-report-root");
|
|
206
|
+
if (!host) {
|
|
207
|
+
host = document.createElement("div");
|
|
208
|
+
host.id = "ak-bug-report-root";
|
|
209
|
+
document.body.append(host);
|
|
210
|
+
}
|
|
211
|
+
createRoot(host).render(_jsx(StrictMode, { children: _jsx(BugReport, { ...options }) }));
|
|
212
|
+
}
|
package/dist/app/app.css
CHANGED
|
@@ -2229,3 +2229,127 @@ body:has(.uk) .pane-body { padding-bottom: 190px; }
|
|
|
2229
2229
|
.ak-conflict-image { display: grid; gap: 6px; margin-top: 5px; }.ak-conflict-image img { width: 72px; height: 48px; object-fit: cover; border-radius: 3px; }.ak-conflict-image code { overflow-wrap: anywhere; font-size: 11px; }
|
|
2230
2230
|
.ak-conflict-dialog footer { display: flex; justify-content: flex-end; gap: 10px; margin-top: 20px; }
|
|
2231
2231
|
@media (max-width: 620px) { .ak-conflict-options { grid-template-columns: 1fr; } .ak-conflict-backdrop { padding: 12px; } }
|
|
2232
|
+
|
|
2233
|
+
/* Direct SSR publication status lives with the global site controls. */
|
|
2234
|
+
.ak-release-control{position:relative;flex:none}.ak-release-trigger{min-width:104px;height:48px;display:inline-flex;align-items:center;justify-content:center;gap:9px;padding:0 14px;border:0;border-radius:14px;background:color-mix(in srgb,var(--ak-rail-bg) 74%,var(--ak-line));color:var(--ak-rail-ink-on);cursor:pointer;font:600 12px/1 var(--ak-font);white-space:nowrap}.ak-release-dot{width:8px;height:8px;border-radius:50%;background:#c68b3c}.ak-release-dot.is-ok{background:#35a66f}.ak-release-menu{position:absolute;z-index:100;top:calc(100% + 10px);right:0;width:min(330px,calc(100vw - 24px));display:flex;flex-direction:column;gap:14px;padding:18px;border:0;border-radius:18px;background:var(--ak-panel);color:var(--ak-ink);box-shadow:var(--ak-lift-3);font:400 13px/1.5 var(--ak-font)}.ak-release-menu p{margin:0;color:var(--ak-ink-quiet)}.ak-release-menu dl{margin:0;padding:12px;border-radius:12px;background:var(--ak-inset)}.ak-release-menu dl>div{display:flex;justify-content:space-between;gap:16px}.ak-release-menu dd{margin:0;font-family:var(--ak-font-mono)}:root[data-admin-interface="next"] .rail{overflow:visible}
|
|
2235
|
+
|
|
2236
|
+
/* Bug report della nuova interfaccia: il root vive fuori dall'app per poter sfocare l'intero
|
|
2237
|
+
* pannello senza includere il dialog nello screenshot. Nella UI corrente non viene renderizzato. */
|
|
2238
|
+
.ak-bug-trigger {
|
|
2239
|
+
position: fixed; z-index: 70; right: 18px; bottom: 18px;
|
|
2240
|
+
width: 46px; height: 46px; display: grid; place-items: center;
|
|
2241
|
+
border: 0; border-radius: 15px; background: var(--ak-ink); color: var(--ak-panel);
|
|
2242
|
+
box-shadow: 0 5px 18px rgba(35, 30, 32, .18); cursor: pointer;
|
|
2243
|
+
transition: transform 160ms ease, box-shadow 160ms ease, background 160ms ease;
|
|
2244
|
+
}
|
|
2245
|
+
.ak-bug-trigger:hover { transform: translateY(-2px); box-shadow: 0 9px 25px rgba(35, 30, 32, .23); }
|
|
2246
|
+
.ak-bug-trigger:focus-visible { outline: 0; box-shadow: var(--ak-ring), 0 9px 25px rgba(35, 30, 32, .23); }
|
|
2247
|
+
.ak-bug-trigger:disabled { cursor: wait; opacity: .78; }
|
|
2248
|
+
.ak-bug-trigger svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
|
|
2249
|
+
.ak-bug-capturing {
|
|
2250
|
+
position: absolute; inset: 5px; border: 2px solid transparent; border-top-color: currentColor;
|
|
2251
|
+
border-radius: 12px; animation: ak-bug-spin .8s linear infinite;
|
|
2252
|
+
}
|
|
2253
|
+
@keyframes ak-bug-spin { to { transform: rotate(1turn); } }
|
|
2254
|
+
|
|
2255
|
+
.ak-bug-backdrop {
|
|
2256
|
+
position: fixed; z-index: 200; inset: 0; display: grid; place-items: center; padding: 24px;
|
|
2257
|
+
background: rgba(31, 29, 30, .2); backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px);
|
|
2258
|
+
animation: ak-bug-backdrop-in 180ms ease-out both;
|
|
2259
|
+
}
|
|
2260
|
+
@keyframes ak-bug-backdrop-in { from { opacity: 0; } }
|
|
2261
|
+
.ak-bug-dialog {
|
|
2262
|
+
width: min(780px, 100%); max-height: min(760px, calc(100vh - 48px)); overflow: auto;
|
|
2263
|
+
display: flex; flex-direction: column; gap: 20px; padding: 24px;
|
|
2264
|
+
border: 0; border-radius: 24px; background: var(--ak-panel); color: var(--ak-ink);
|
|
2265
|
+
box-shadow: 0 28px 80px -28px rgba(18, 15, 16, .48); font-family: var(--ak-font);
|
|
2266
|
+
animation: ak-bug-dialog-in 220ms cubic-bezier(.2, .8, .2, 1) both;
|
|
2267
|
+
}
|
|
2268
|
+
@keyframes ak-bug-dialog-in {
|
|
2269
|
+
from { opacity: 0; transform: translateY(12px) scale(.985); }
|
|
2270
|
+
}
|
|
2271
|
+
.ak-bug-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 24px; }
|
|
2272
|
+
.ak-bug-head > div { min-width: 0; }
|
|
2273
|
+
.ak-bug-kicker {
|
|
2274
|
+
display: block; margin-bottom: 6px; color: var(--ak-accent);
|
|
2275
|
+
font-size: 10px; font-weight: 700; letter-spacing: .12em; text-transform: uppercase;
|
|
2276
|
+
}
|
|
2277
|
+
.ak-bug-head h2 { margin: 0; font-size: 22px; line-height: 1.15; letter-spacing: -.025em; }
|
|
2278
|
+
.ak-bug-head p { max-width: 560px; margin: 7px 0 0; color: var(--ak-ink-quiet); font-size: 13px; line-height: 1.45; }
|
|
2279
|
+
.ak-bug-icon-button {
|
|
2280
|
+
width: 40px; height: 40px; flex: none; display: grid; place-items: center;
|
|
2281
|
+
border: 0; border-radius: 12px; background: var(--ak-inset); color: var(--ak-ink-quiet); cursor: pointer;
|
|
2282
|
+
}
|
|
2283
|
+
.ak-bug-icon-button:hover { color: var(--ak-ink); background: var(--ak-inset-deep); }
|
|
2284
|
+
.ak-bug-icon-button:focus-visible { outline: 0; box-shadow: var(--ak-ring); }
|
|
2285
|
+
.ak-bug-icon-button:disabled { opacity: .5; cursor: default; }
|
|
2286
|
+
.ak-bug-icon-button svg { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; }
|
|
2287
|
+
|
|
2288
|
+
.ak-bug-content { display: grid; grid-template-columns: minmax(0, .9fr) minmax(0, 1.1fr); gap: 18px; }
|
|
2289
|
+
.ak-bug-shot-card {
|
|
2290
|
+
min-width: 0; display: flex; flex-direction: column; gap: 10px; padding: 12px;
|
|
2291
|
+
border-radius: 17px; background: var(--ak-inset);
|
|
2292
|
+
}
|
|
2293
|
+
.ak-bug-shot-head { min-height: 30px; display: flex; align-items: center; justify-content: space-between; gap: 12px; }
|
|
2294
|
+
.ak-bug-shot-head strong { font-size: 12px; font-weight: 650; }
|
|
2295
|
+
.ak-bug-download {
|
|
2296
|
+
min-height: 30px; display: inline-flex; align-items: center; gap: 6px; padding: 0 9px;
|
|
2297
|
+
border-radius: 9px; background: var(--ak-panel); color: var(--ak-ink); font-size: 11px; font-weight: 600;
|
|
2298
|
+
text-decoration: none;
|
|
2299
|
+
}
|
|
2300
|
+
.ak-bug-download:hover { background: var(--ak-inset-deep); }
|
|
2301
|
+
.ak-bug-download:focus-visible { outline: 0; box-shadow: var(--ak-ring); }
|
|
2302
|
+
.ak-bug-download svg { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 1.9; stroke-linecap: round; stroke-linejoin: round; }
|
|
2303
|
+
.ak-bug-shot-card img {
|
|
2304
|
+
width: 100%; aspect-ratio: 16 / 10; display: block; object-fit: contain;
|
|
2305
|
+
border-radius: 11px; background: var(--ak-inset-deep);
|
|
2306
|
+
}
|
|
2307
|
+
.ak-bug-shot-card small { color: var(--ak-ink-faint); font-size: 10px; line-height: 1.35; }
|
|
2308
|
+
.ak-bug-shot-missing {
|
|
2309
|
+
min-height: 185px; display: grid; place-items: center; align-content: center; gap: 10px;
|
|
2310
|
+
border-radius: 11px; color: var(--ak-ink-quiet); font-size: 12px; text-align: center;
|
|
2311
|
+
}
|
|
2312
|
+
.ak-bug-shot-missing button { border: 0; background: transparent; color: var(--ak-accent); font: 600 12px var(--ak-font); cursor: pointer; }
|
|
2313
|
+
|
|
2314
|
+
.ak-bug-message { min-width: 0; display: flex; flex-direction: column; gap: 8px; }
|
|
2315
|
+
.ak-bug-message > span { font-size: 12px; font-weight: 650; }
|
|
2316
|
+
.ak-bug-message textarea {
|
|
2317
|
+
min-height: 218px; width: 100%; resize: vertical; padding: 13px 14px;
|
|
2318
|
+
border: 1px solid transparent; border-radius: 15px; background: var(--ak-inset); color: var(--ak-ink);
|
|
2319
|
+
font: 400 13px/1.5 var(--ak-font);
|
|
2320
|
+
}
|
|
2321
|
+
.ak-bug-message textarea::placeholder { color: var(--ak-ink-faint); }
|
|
2322
|
+
.ak-bug-message textarea:focus { outline: 0; border-color: color-mix(in srgb, var(--ak-accent) 30%, transparent); box-shadow: var(--ak-ring); background: var(--ak-panel); }
|
|
2323
|
+
.ak-bug-message small { align-self: flex-end; color: var(--ak-ink-faint); font-size: 10px; }
|
|
2324
|
+
.ak-bug-error { margin: -5px 0 0; padding: 10px 12px; border-radius: 11px; background: color-mix(in srgb, var(--ak-danger) 11%, transparent); color: var(--ak-danger); font-size: 12px; }
|
|
2325
|
+
|
|
2326
|
+
.ak-bug-actions { display: flex; align-items: center; justify-content: space-between; gap: 18px; padding-top: 18px; border-top: 1px solid var(--ak-line); }
|
|
2327
|
+
.ak-bug-actions > span { color: var(--ak-ink-faint); font-size: 10px; line-height: 1.4; }
|
|
2328
|
+
.ak-bug-actions > div { flex: none; display: flex; gap: 8px; }
|
|
2329
|
+
.ak-bug-send {
|
|
2330
|
+
min-height: 42px; display: inline-flex; align-items: center; justify-content: center; gap: 8px;
|
|
2331
|
+
padding: 0 14px; border: 0; border-radius: 12px; background: var(--ak-inset); color: var(--ak-ink);
|
|
2332
|
+
font: 650 12px/1 var(--ak-font); cursor: pointer;
|
|
2333
|
+
}
|
|
2334
|
+
.ak-bug-send.is-whatsapp { background: var(--ak-accent); color: var(--ak-accent-ink); }
|
|
2335
|
+
.ak-bug-send:hover { filter: brightness(.97); }
|
|
2336
|
+
.ak-bug-send:focus-visible { outline: 0; box-shadow: var(--ak-ring); }
|
|
2337
|
+
.ak-bug-send:disabled { opacity: .5; cursor: wait; }
|
|
2338
|
+
.ak-bug-send svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
|
|
2339
|
+
|
|
2340
|
+
@media (max-width: 680px) {
|
|
2341
|
+
.ak-bug-trigger { right: 12px; bottom: 12px; }
|
|
2342
|
+
.ak-bug-backdrop { padding: 10px; place-items: end center; }
|
|
2343
|
+
.ak-bug-dialog { max-height: calc(100vh - 20px); padding: 20px; border-radius: 22px; }
|
|
2344
|
+
.ak-bug-content { grid-template-columns: minmax(0, 1fr); }
|
|
2345
|
+
.ak-bug-shot-card img { max-height: 220px; }
|
|
2346
|
+
.ak-bug-message textarea { min-height: 140px; }
|
|
2347
|
+
.ak-bug-actions { align-items: stretch; flex-direction: column; }
|
|
2348
|
+
.ak-bug-actions > div { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
2349
|
+
}
|
|
2350
|
+
|
|
2351
|
+
@media (prefers-reduced-motion: reduce) {
|
|
2352
|
+
.ak-bug-trigger, .ak-bug-backdrop, .ak-bug-dialog { animation: none; transition: none; }
|
|
2353
|
+
}
|
|
2354
|
+
:root[data-admin-interface="next"] .rail-foot > .ak-release-control.ak-release-control{width:auto;height:auto;overflow:visible}
|
|
2355
|
+
:root[data-admin-interface="next"] .rail-foot > .ak-release-control.ak-release-control > .ak-release-trigger{width:auto;min-width:104px;display:inline-flex;gap:9px;padding:0 14px;color:var(--ak-rail-ink-on);font:600 12px/1 var(--ak-font);overflow:visible}
|
package/dist/app/index.js
CHANGED
|
@@ -15,6 +15,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
15
15
|
import { StrictMode } from "react";
|
|
16
16
|
import { createRoot } from "react-dom/client";
|
|
17
17
|
import { setStoragePrefix } from "../index.js";
|
|
18
|
+
import { mountBugReport } from "./BugReport.js";
|
|
18
19
|
import { App } from "./App.js";
|
|
19
20
|
import { applyTheme, initialTheme } from "./lib/theme.js";
|
|
20
21
|
import { setSiteConfig } from "./site.js";
|
|
@@ -31,4 +32,6 @@ export function mountAdmin({ site, root, storagePrefix, workspace }) {
|
|
|
31
32
|
throw new Error("adminkit: elemento di montaggio non trovato (atteso #root)");
|
|
32
33
|
const Workspace = workspace ?? App;
|
|
33
34
|
createRoot(el).render(_jsx(StrictMode, { children: _jsx(Workspace, {}) }));
|
|
35
|
+
if (site.bugReport)
|
|
36
|
+
mountBugReport({ siteName: site.name, email: site.bugReport.email, whatsapp: site.bugReport.whatsapp || "" });
|
|
34
37
|
}
|
|
@@ -20,6 +20,8 @@ export interface PublishState {
|
|
|
20
20
|
preview(target: ContentPublishTarget): Promise<void>;
|
|
21
21
|
previewApproved(target: ContentPublishTarget | null): boolean;
|
|
22
22
|
previousReleaseId: string | null;
|
|
23
|
+
activeReleaseId: string | null;
|
|
24
|
+
verified: boolean;
|
|
23
25
|
rollback(): Promise<void>;
|
|
24
26
|
verify(): Promise<void>;
|
|
25
27
|
}
|
package/dist/app/lib/publish.js
CHANGED
|
@@ -25,6 +25,7 @@ export function usePublish() {
|
|
|
25
25
|
const [publishing, setPublishing] = useState(false);
|
|
26
26
|
const [notice, setNotice] = useState(null);
|
|
27
27
|
const [error, setError] = useState(null);
|
|
28
|
+
const [verified, setVerified] = useState(false);
|
|
28
29
|
const [approval, setApproval] = useState(null);
|
|
29
30
|
const [activeReleaseId, setActiveReleaseId] = useState(null);
|
|
30
31
|
const [previousReleaseId, setPreviousReleaseId] = useState(null);
|
|
@@ -71,6 +72,7 @@ export function usePublish() {
|
|
|
71
72
|
return;
|
|
72
73
|
try {
|
|
73
74
|
const result = await api.contentVerification(activeReleaseId);
|
|
75
|
+
setVerified(result.publicOriginVerified);
|
|
74
76
|
setNotice(result.publicOriginVerified ? "Release verificata sul dominio pubblico." : result.reason);
|
|
75
77
|
}
|
|
76
78
|
catch (error) {
|
|
@@ -83,6 +85,7 @@ export function usePublish() {
|
|
|
83
85
|
const d = await api.contentPublishState();
|
|
84
86
|
setActiveReleaseId(d.activeReleaseId ?? null);
|
|
85
87
|
setPreviousReleaseId(d.previousReleaseId ?? null);
|
|
88
|
+
setVerified(d.verified === true);
|
|
86
89
|
setAhead(d.ahead ?? 0);
|
|
87
90
|
setDocuments(d.documents ?? []);
|
|
88
91
|
setSiteAccessPending(Boolean(d.siteAccess));
|
|
@@ -173,6 +176,6 @@ export function usePublish() {
|
|
|
173
176
|
}, [reload, approval, activeReleaseId]);
|
|
174
177
|
return {
|
|
175
178
|
ahead, commits, documents, siteAccessPending, configured, publishing, notice, error,
|
|
176
|
-
reload, publish, preview, previewApproved, previousReleaseId, rollback, verify, clearNotice: () => setNotice(null),
|
|
179
|
+
reload, publish, preview, previewApproved, previousReleaseId, activeReleaseId, verified, rollback, verify, clearNotice: () => setNotice(null),
|
|
177
180
|
};
|
|
178
181
|
}
|
package/dist/app/site.d.ts
CHANGED
|
@@ -52,6 +52,11 @@ export interface SiteAdminConfig {
|
|
|
52
52
|
mediaEditor?: ComponentType;
|
|
53
53
|
/** Preview approval, public observation and rollback for direct SSR publishing. */
|
|
54
54
|
directPublication?: boolean;
|
|
55
|
+
/** Optional support workflow: screenshot stays local until the user sends. */
|
|
56
|
+
bugReport?: {
|
|
57
|
+
email: string;
|
|
58
|
+
whatsapp?: string;
|
|
59
|
+
};
|
|
55
60
|
/** Optional site-owned Home surface. The kit keeps its legacy visual editor
|
|
56
61
|
* as a fallback, while a site with a different document shape can render its
|
|
57
62
|
* own schema-driven editor without teaching the shared package its fields. */
|
|
@@ -17,4 +17,6 @@ export declare function commitDirectRelease(db: D1DatabaseLike, input: {
|
|
|
17
17
|
}[];
|
|
18
18
|
authorSubject: string;
|
|
19
19
|
now: number;
|
|
20
|
+
/** Trusted site-owned transactional preconditions; never from an HTTP payload. */
|
|
21
|
+
guards?: readonly D1Statement[];
|
|
20
22
|
}): Promise<void>;
|
|
@@ -33,6 +33,7 @@ export async function commitDirectRelease(db, input) {
|
|
|
33
33
|
const statements = [
|
|
34
34
|
transactionAssertion(db, `EXISTS(SELECT 1 FROM content_site_state WHERE singleton=1 AND current_release_id=?) AND NOT EXISTS(SELECT 1 FROM content_release_site_access WHERE release_id=?) AND NOT EXISTS(SELECT 1 FROM production_deployment_attempts WHERE phase NOT IN ('succeeded','failed','superseded'))`, [expectedBaseReleaseId, expectedBaseReleaseId]),
|
|
35
35
|
];
|
|
36
|
+
statements.push(...(input.guards ?? []));
|
|
36
37
|
for (const d of selectedDrafts)
|
|
37
38
|
statements.push(transactionAssertion(db, "EXISTS(SELECT 1 FROM content_documents WHERE document_id=? AND draft_revision_id=?)", [d.documentId, d.revisionId]));
|
|
38
39
|
for (const e of release.entries)
|
package/dist/form/Trash.js
CHANGED
|
@@ -23,20 +23,35 @@ export function Trash() {
|
|
|
23
23
|
return (_jsxs("div", { className: "tr", children: [_jsxs("header", { className: "mb-top", children: [_jsx("span", { className: "ak-spacer" }), !!items?.length && (_jsx("button", { className: "ak-btn", onClick: async () => {
|
|
24
24
|
if (!confirm(`Svuotare il cestino? ${items.length} file cancellati per sempre.`))
|
|
25
25
|
return;
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
try {
|
|
27
|
+
await api.trashEmpty();
|
|
28
|
+
await load();
|
|
29
|
+
}
|
|
30
|
+
catch (e) {
|
|
31
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
32
|
+
}
|
|
28
33
|
}, children: "Svuota il cestino" }))] }), error && _jsx("p", { className: "ak-error", children: error }), !items && _jsx("p", { className: "ak-hint", children: "Carico\u2026" }), items?.length === 0 && _jsx("p", { className: "ak-hint", children: "Il cestino \u00E8 vuoto." }), _jsx("ul", { className: "tr-list", children: items?.map((it) => (_jsxs("li", { className: "tr-row", children: [_jsx("img", { src: it.previewUrl, alt: "", className: "tr-thumb", loading: "lazy" }), _jsx("span", { className: "tr-name", children: it.name }), _jsx("code", { className: "tr-orig", children: it.originalKey }), _jsx("span", { className: "ak-hint", children: fmtSize(it.size) }), _jsx("button", { className: "ak-btn ak-btn-quiet", onClick: async () => {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
34
|
+
try {
|
|
35
|
+
const r = await api.trashRestore([it.trashKey]);
|
|
36
|
+
if (r.conflicts?.length) {
|
|
37
|
+
alert(`Esisteva già un file con quel nome: ripristinato come ${r.conflicts[0].restoredAs}.`);
|
|
38
|
+
}
|
|
39
|
+
// NB: il ripristino NON rimette i riferimenti nelle pagine — li aveva tolti la
|
|
40
|
+
// cancellazione. Va rifatto a mano, ed è documentato nell'endpoint.
|
|
41
|
+
await load();
|
|
42
|
+
}
|
|
43
|
+
catch (e) {
|
|
44
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
32
45
|
}
|
|
33
|
-
// NB: il ripristino NON rimette i riferimenti nelle pagine — li aveva tolti la
|
|
34
|
-
// cancellazione. Va rifatto a mano, ed è documentato nell'endpoint.
|
|
35
|
-
await load();
|
|
36
46
|
}, children: "Ripristina" }), _jsx("button", { className: "ak-btn", onClick: async () => {
|
|
37
47
|
if (!confirm(`Cancellare ${it.name} per sempre? Non è più recuperabile.`))
|
|
38
48
|
return;
|
|
39
|
-
|
|
40
|
-
|
|
49
|
+
try {
|
|
50
|
+
await api.trashDelete([it.trashKey]);
|
|
51
|
+
await load();
|
|
52
|
+
}
|
|
53
|
+
catch (e) {
|
|
54
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
55
|
+
}
|
|
41
56
|
}, children: "Elimina" })] }, it.trashKey))) })] }));
|
|
42
57
|
}
|
|
@@ -81,6 +81,12 @@ export function MediaFolders({ lib, trash, settings }) {
|
|
|
81
81
|
trash.leave(); lib.go(p); };
|
|
82
82
|
const drop = (dest) => (e) => {
|
|
83
83
|
setOver(null);
|
|
84
|
+
const ids = readDragged(e.dataTransfer);
|
|
85
|
+
if (canMove && ids.length) {
|
|
86
|
+
e.preventDefault();
|
|
87
|
+
void lib.move(ids, dest);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
84
90
|
if (canUpload && e.dataTransfer.files.length) {
|
|
85
91
|
e.preventDefault();
|
|
86
92
|
if (lib.mode === "pick")
|
|
@@ -92,7 +98,6 @@ export function MediaFolders({ lib, trash, settings }) {
|
|
|
92
98
|
if (!canMove)
|
|
93
99
|
return;
|
|
94
100
|
e.preventDefault();
|
|
95
|
-
const ids = readDragged(e.dataTransfer);
|
|
96
101
|
if (ids.length)
|
|
97
102
|
void lib.move(ids, dest);
|
|
98
103
|
};
|
|
@@ -173,7 +173,7 @@ export function MediaGrid({ lib, notice, onPick, onCancel }) {
|
|
|
173
173
|
e.dataTransfer.setData(DRAG_FOLDERS, JSON.stringify(dirs));
|
|
174
174
|
e.dataTransfer.effectAllowed = "move";
|
|
175
175
|
}, children: [_jsxs("div", { className: "mg-frame", onClick: () => { if (!locked)
|
|
176
|
-
(manage ? lib.view(i) : lib.toggle(f.key)); }, children: [_jsx("img", { className: "mg-img", src: f.url, alt: "", loading: "lazy",
|
|
176
|
+
(manage ? lib.view(i) : lib.toggle(f.key)); }, children: [_jsx("img", { className: "mg-img", draggable: false, src: f.url, alt: "", loading: "lazy",
|
|
177
177
|
/* Il rapporto è noto prima che l'immagine arrivi (lo dà l'elenco): la cella
|
|
178
178
|
riserva subito l'ingombro giusto e la griglia non salta mentre si carica. */
|
|
179
179
|
style: { aspectRatio: f.w && f.h ? `${f.w} / ${f.h}` : "3 / 2" } }), manage && altStatus && (_jsx("span", { className: `mg-alt-badge is-${altStatus}`, children: altStatus === "missing" ? "ALT da completare" : altStatus === "decorative" ? "Decorativa" : "ALT completo" })), _jsx("button", { className: "mg-check", "aria-label": on ? "Deseleziona" : "Seleziona", disabled: locked, onClick: (e) => { e.stopPropagation(); lib.toggle(f.key); }, children: on ? "✓" : "" })] }), _jsx("figcaption", { className: "mg-cap", title: f.name, children: f.name })] }, f.key));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cardinali-studio/adminkit",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.36",
|
|
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",
|
|
@@ -108,6 +108,7 @@
|
|
|
108
108
|
"zod": "^4.4.3"
|
|
109
109
|
},
|
|
110
110
|
"dependencies": {
|
|
111
|
-
"heic-to": "^1.3.0"
|
|
111
|
+
"heic-to": "^1.3.0",
|
|
112
|
+
"modern-screenshot": "4.7.0"
|
|
112
113
|
}
|
|
113
114
|
}
|