@aduptive/strapi-blockscene 1.0.0-alpha.4 → 1.0.0-alpha.5

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/admin.cjs CHANGED
@@ -4,6 +4,10 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
5
  var __getProtoOf = Object.getPrototypeOf;
6
6
  var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
8
+ var __commonJS = (cb, mod) => function __require() {
9
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
10
+ };
7
11
  var __export = (target, all) => {
8
12
  for (var name in all)
9
13
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -25,6 +29,113 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
25
29
  mod
26
30
  ));
27
31
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
33
+
34
+ // server/groups.js
35
+ var require_groups = __commonJS({
36
+ "server/groups.js"(exports, module2) {
37
+ "use strict";
38
+ var UID = /^[a-z0-9-]+\.[a-z0-9-]+$/;
39
+ function safeGroups2(value, knownUids) {
40
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
41
+ const out = {};
42
+ for (const [open, close] of Object.entries(value)) {
43
+ if (!UID.test(open) || typeof close !== "string" || !UID.test(close) || open === close) continue;
44
+ if (knownUids && (!knownUids.includes(open) || !knownUids.includes(close))) continue;
45
+ out[open] = close;
46
+ }
47
+ for (const close of Object.values(out)) if (out[close]) return null;
48
+ return Object.keys(out).length ? out : null;
49
+ }
50
+ var uidOf = (row) => typeof row?.__component === "string" ? row.__component : "";
51
+ var isOpener2 = (row, g) => Boolean(g) && Object.prototype.hasOwnProperty.call(g, uidOf(row));
52
+ var isClose2 = (row, g) => Boolean(g) && Object.values(g).includes(uidOf(row));
53
+ var blockKey2 = (row) => row && row.id != null ? `${row.__component}#${row.id}` : row && row.__temp_key__ != null ? String(row.__temp_key__) : null;
54
+ function validateGroups3(rows, g) {
55
+ const errors = [];
56
+ if (!g || !Array.isArray(rows)) return errors;
57
+ const stack = [];
58
+ rows.forEach((row, index) => {
59
+ const uid = uidOf(row);
60
+ if (isOpener2(row, g)) {
61
+ stack.push({ index, uid, close: g[uid] });
62
+ return;
63
+ }
64
+ if (!isClose2(row, g)) return;
65
+ const top = stack[stack.length - 1];
66
+ if (!top) errors.push({ code: "closeBeforeOpen", index, uid });
67
+ else if (top.close !== uid) errors.push({ code: "mismatch", index, uid, expected: top.close, open: top.uid, openIndex: top.index });
68
+ else stack.pop();
69
+ });
70
+ for (const open of stack) errors.push({ code: "unclosed", index: open.index, uid: open.uid, expected: open.close });
71
+ return errors;
72
+ }
73
+ function groupRange3(rows, start, g) {
74
+ const stack = [g[uidOf(rows[start])]];
75
+ for (let i = start + 1; i < rows.length; i++) {
76
+ const row = rows[i];
77
+ if (isOpener2(row, g)) stack.push(g[uidOf(row)]);
78
+ else if (isClose2(row, g) && uidOf(row) === stack[stack.length - 1]) {
79
+ stack.pop();
80
+ if (!stack.length) return [start, i];
81
+ }
82
+ }
83
+ return [start, rows.length - 1];
84
+ }
85
+ function groupRows2(rows, g, depth = 0, offset = 0) {
86
+ const out = [];
87
+ let i = 0;
88
+ while (i < rows.length) {
89
+ const row = rows[i];
90
+ if (isOpener2(row, g)) {
91
+ const [, end] = groupRange3(rows, i, g);
92
+ const closed = uidOf(rows[end]) === g[uidOf(row)] && end > i;
93
+ out.push({
94
+ type: "group",
95
+ key: blockKey2(row),
96
+ closeKey: closed ? blockKey2(rows[end]) : null,
97
+ row,
98
+ index: offset + i,
99
+ depth,
100
+ children: groupRows2(rows.slice(i + 1, closed ? end : end + 1), g, depth + 1, offset + i + 1)
101
+ });
102
+ i = end + 1;
103
+ continue;
104
+ }
105
+ out.push({ type: "block", key: blockKey2(row), row, index: offset + i, depth, stray: isClose2(row, g) });
106
+ i++;
107
+ }
108
+ return out;
109
+ }
110
+ function topLevelRanges2(rows, g) {
111
+ const ranges = [];
112
+ let i = 0;
113
+ while (i < rows.length) {
114
+ const end = isOpener2(rows[i], g) ? groupRange3(rows, i, g)[1] : i;
115
+ ranges.push([i, end]);
116
+ i = end + 1;
117
+ }
118
+ return ranges;
119
+ }
120
+ function moveGroup2(rows, key, direction, g) {
121
+ if (!g) return null;
122
+ const ranges = topLevelRanges2(rows, g);
123
+ const at = ranges.findIndex(([s]) => blockKey2(rows[s]) === String(key));
124
+ const to = at + (direction === "up" ? -1 : 1);
125
+ if (at < 0 || to < 0 || to >= ranges.length) return null;
126
+ const [a, b] = direction === "up" ? [ranges[to], ranges[at]] : [ranges[at], ranges[to]];
127
+ return [...rows.slice(0, a[0]), ...rows.slice(b[0], b[1] + 1), ...rows.slice(a[0], a[1] + 1), ...rows.slice(b[1] + 1)];
128
+ }
129
+ function removeGroup2(rows, key, g) {
130
+ if (!g) return null;
131
+ const start = rows.findIndex((row) => blockKey2(row) === String(key));
132
+ if (start < 0 || !isOpener2(rows[start], g)) return null;
133
+ const [, end] = groupRange3(rows, start, g);
134
+ return [...rows.slice(0, start), ...rows.slice(end + 1)];
135
+ }
136
+ module2.exports = { safeGroups: safeGroups2, validateGroups: validateGroups3, groupRange: groupRange3, groupRows: groupRows2, topLevelRanges: topLevelRanges2, moveGroup: moveGroup2, removeGroup: removeGroup2, blockKey: blockKey2, isOpener: isOpener2, isClose: isClose2 };
137
+ }
138
+ });
28
139
 
29
140
  // admin/strapi4.tsx
30
141
  var strapi4_exports = {};
@@ -32,18 +143,42 @@ __export(strapi4_exports, {
32
143
  default: () => strapi4_default
33
144
  });
34
145
  module.exports = __toCommonJS(strapi4_exports);
35
- var React5 = __toESM(require("react"));
36
- var import_design_system3 = require("@strapi/design-system");
146
+ var React8 = __toESM(require("react"));
147
+ var import_design_system5 = require("@strapi/design-system");
37
148
  var import_helper_plugin = require("@strapi/helper-plugin");
38
149
 
39
150
  // admin/Gallery.tsx
40
- var React2 = __toESM(require("react"));
41
- var import_react_dom2 = require("react-dom");
42
- var import_design_system = require("@strapi/design-system");
43
- var import_styled_components = __toESM(require("styled-components"));
151
+ var React4 = __toESM(require("react"));
152
+ var import_react_dom3 = require("react-dom");
153
+ var import_design_system2 = require("@strapi/design-system");
154
+ var import_styled_components3 = __toESM(require("styled-components"));
44
155
 
45
156
  // admin/model.mjs
46
157
  var import_fractional_indexing = require("fractional-indexing");
158
+ function componentDefaults(schema, components, stack = []) {
159
+ if (!schema || stack.includes(schema))
160
+ throw new Error("Invalid recursive component defaults");
161
+ const row = {};
162
+ for (const [name, attr] of Object.entries(schema.attributes || {})) {
163
+ if (Object.hasOwn(attr, "default")) {
164
+ const value = structuredClone(attr.default);
165
+ if (attr.type === "password") row[name] = "";
166
+ else if (attr.type === "relation")
167
+ row[name] = { connect: [], disconnect: [] };
168
+ else if (value !== null || attr.type === "boolean") row[name] = value;
169
+ } else if (attr.type === "component" && attr.required) {
170
+ const create = () => componentDefaults(components[attr.component], components, [
171
+ ...stack,
172
+ schema
173
+ ]);
174
+ if (attr.repeatable) {
175
+ const keys = (0, import_fractional_indexing.generateNKeysBetween)(null, null, attr.min || 0);
176
+ row[name] = keys.map((key) => ({ ...create(), __temp_key__: key }));
177
+ } else row[name] = create();
178
+ } else if (attr.type === "dynamiczone" && attr.required) row[name] = [];
179
+ }
180
+ return row;
181
+ }
47
182
  function editableZones(schema, values, canEdit, disabled = false) {
48
183
  if (disabled) return [];
49
184
  return Object.entries(schema?.attributes || {}).filter(
@@ -76,6 +211,18 @@ function candidatesFor(uid, meta = {}, config = {}) {
76
211
  `${base}/${encodeURIComponent(uid)}.webp${version}`
77
212
  ].filter(Boolean);
78
213
  }
214
+ function blockPreviewSrc(template, uid, { variant = "default", locale = "" } = {}) {
215
+ if (!template || !uid) return null;
216
+ const dot = uid.indexOf(".");
217
+ const values = { uid, name: uid.slice(dot + 1), category: dot < 0 ? "" : uid.slice(0, dot), variant, locale };
218
+ try {
219
+ const url = new URL(template.replace(/\{(uid|name|category|variant|locale)\}/g, (_, key) => encodeURIComponent(values[key] || "")));
220
+ url.pathname = url.pathname.replace(/\/{2,}/g, "/");
221
+ return ["http:", "https:"].includes(url.protocol) ? url.href : null;
222
+ } catch {
223
+ return null;
224
+ }
225
+ }
79
226
  var SYSTEM_FIELDS = [
80
227
  "id",
81
228
  "documentId",
@@ -89,49 +236,59 @@ var SYSTEM_FIELDS = [
89
236
  ];
90
237
  var fieldType = (attr) => attr?.type === "customField" ? String(attr.customField || "").includes("CKEditor") ? "richtext" : "customField" : attr?.type || "unknown";
91
238
  var fieldsOf = (schema) => Object.entries(schema?.attributes || {}).filter(([name]) => !SYSTEM_FIELDS.includes(name)).map(([name, attr]) => ({ name, type: fieldType(attr) }));
92
- var categoryOf = (uid, schema, meta = {}) => meta.category || schema?.category || uid.split(".")[0];
93
- var byCategory = (a, b) => (a === "admin" ? 1 : 0) - (b === "admin" ? 1 : 0) || a.localeCompare(b);
239
+ var TYPOLOGIES = ["hero", "text", "media", "listing", "cards", "cta", "form", "layout"];
240
+ var FACETS = { media: ["image", "video", "gallery"], content: ["richtext", "list", "dynamic", "form"] };
241
+ var typologyRank = (t) => TYPOLOGIES.indexOf(t) + 1 || 99;
94
242
  var pickable = (zone, config) => {
95
243
  const closers = Object.values(config.groups || {});
96
244
  return zone.components.filter((uid) => !closers.includes(uid));
97
245
  };
98
- function entriesFor(zone, components, config, query, category = "all") {
246
+ function entryOf(uid, schema, config) {
247
+ const meta = config.components?.[uid] || {};
248
+ return {
249
+ uid,
250
+ label: meta.label || schema.info?.displayName || uid,
251
+ description: meta.description || schema.info?.description || "",
252
+ typology: meta.typology || "text",
253
+ facets: meta.facets || [],
254
+ tags: meta.tags || [],
255
+ keywords: meta.keywords || "",
256
+ fields: fieldsOf(schema),
257
+ candidates: candidatesFor(uid, meta, config),
258
+ template: meta.template || "generic"
259
+ };
260
+ }
261
+ var allEntries = (zone, components, config) => pickable(zone, config).flatMap((uid) => components[uid] ? [entryOf(uid, components[uid], config)] : []);
262
+ function entriesFor(zone, components, config, query, filter = {}) {
99
263
  const needle = query.trim().toLocaleLowerCase();
100
- return pickable(zone, config).flatMap((uid) => {
101
- const schema = components[uid];
102
- if (!schema) return [];
103
- const meta = config.components?.[uid] || {};
104
- const entry = {
105
- uid,
106
- label: meta.label || schema.info?.displayName || uid,
107
- description: meta.description || schema.info?.description || "",
108
- category: categoryOf(uid, schema, meta),
109
- fields: fieldsOf(schema),
110
- candidates: candidatesFor(uid, meta, config),
111
- template: meta.template || "generic"
112
- };
113
- if (category !== "all" && entry.category !== category) return [];
114
- return `${entry.label} ${entry.description} ${entry.category} ${uid} ${meta.keywords || ""}`.toLocaleLowerCase().includes(needle) ? [entry] : [];
115
- }).sort(
116
- (a, b) => byCategory(a.category, b.category) || a.label.localeCompare(b.label)
117
- );
264
+ const any = (picked, values) => !picked?.length || picked.some((value) => values.includes(value));
265
+ return allEntries(zone, components, config).filter(
266
+ (entry) => (!filter.typology || entry.typology === filter.typology) && (!filter.uids || filter.uids.includes(entry.uid)) && any(filter.tags, entry.tags) && any(filter.media, entry.facets) && any(filter.content, entry.facets) && `${entry.label} ${entry.description} ${entry.typology} ${entry.tags.join(" ")} ${entry.uid} ${entry.keywords}`.toLocaleLowerCase().includes(needle)
267
+ ).sort((a, b) => typologyRank(a.typology) - typologyRank(b.typology) || a.label.localeCompare(b.label));
118
268
  }
119
- function categoriesFor(zone, components, config) {
120
- const counts = /* @__PURE__ */ new Map();
121
- for (const uid of pickable(zone, config)) {
122
- const schema = components[uid];
123
- if (!schema) continue;
124
- const c = categoryOf(uid, schema, config.components?.[uid] || {});
125
- counts.set(c, (counts.get(c) || 0) + 1);
126
- }
127
- return [...counts.entries()].sort(([a], [b]) => byCategory(a, b));
269
+ function optionsFor(zone, components, config) {
270
+ const count = (values) => {
271
+ const counts = /* @__PURE__ */ new Map();
272
+ for (const value of values) counts.set(value, (counts.get(value) || 0) + 1);
273
+ return [...counts.entries()];
274
+ };
275
+ const entries = allEntries(zone, components, config);
276
+ const facets = entries.flatMap((entry) => entry.facets);
277
+ return {
278
+ total: entries.length,
279
+ uids: entries.map((entry) => entry.uid),
280
+ typologies: count(entries.map((entry) => entry.typology)).sort(([a], [b]) => typologyRank(a) - typologyRank(b)),
281
+ tags: count(entries.flatMap((entry) => entry.tags)).sort(([a], [b]) => a.localeCompare(b)),
282
+ media: count(facets.filter((facet) => FACETS.media.includes(facet))),
283
+ content: count(facets.filter((facet) => FACETS.content.includes(facet)))
284
+ };
128
285
  }
129
286
  function groupEntries(entries) {
130
287
  const map = /* @__PURE__ */ new Map();
131
288
  for (const entry of entries)
132
- map.set(entry.category, [...map.get(entry.category) || [], entry]);
133
- return [...map.entries()].map(([category, items]) => ({
134
- category,
289
+ map.set(entry.typology, [...map.get(entry.typology) || [], entry]);
290
+ return [...map.entries()].map(([typology, items]) => ({
291
+ typology,
135
292
  entries: items
136
293
  }));
137
294
  }
@@ -139,18 +296,18 @@ function memoryKey({ base = "", userId, contentType, zone }) {
139
296
  if (!userId || !contentType || !zone) return null;
140
297
  return `blockscene:v1:${base}:${userId}:${contentType}:${zone}`;
141
298
  }
142
- function readMemory(storage2, key) {
299
+ function readMemory(storage3, key) {
143
300
  try {
144
- const value = key && storage2?.getItem(key);
301
+ const value = key && storage3?.getItem(key);
145
302
  return value === "open" || value === "closed" ? value : null;
146
303
  } catch {
147
304
  return null;
148
305
  }
149
306
  }
150
- function writeMemory(storage2, key, value) {
307
+ function writeMemory(storage3, key, value) {
151
308
  try {
152
309
  if (key && (value === "open" || value === "closed"))
153
- storage2?.setItem(key, value);
310
+ storage3?.setItem(key, value);
154
311
  } catch {
155
312
  }
156
313
  }
@@ -160,6 +317,117 @@ function initialState(editor, remembered) {
160
317
  return "closed";
161
318
  }
162
319
 
320
+ // admin/preview.mjs
321
+ var import_groups = __toESM(require_groups(), 1);
322
+ var PROTOCOL = "blockscene:page-preview:v1";
323
+ var TEXT_TYPES = ["string", "text"];
324
+ var RICH_TYPES = ["richtext", "blocks", "customField"];
325
+ var EDITABLE_TYPES = [...TEXT_TYPES, ...RICH_TYPES];
326
+ var SCALAR = ["string", "text", "richtext", "email", "enumeration", "integer", "biginteger", "float", "decimal", "boolean", "date", "datetime", "time", "json", "blocks", "uid", "customField"];
327
+ var blockKey = (row) => row && row.id != null ? `${row.__component}#${row.id}` : row && row.__temp_key__ != null ? String(row.__temp_key__) : null;
328
+ function projectRow(row, schema, components, cmsOrigin, depth = 0) {
329
+ if (!row || !schema || depth > 3) return null;
330
+ const out = {};
331
+ for (const [name, attr] of Object.entries(schema.attributes || {})) {
332
+ const value = row[name];
333
+ if (attr.type === "password" || attr.type === "relation" || attr.private) continue;
334
+ if (SCALAR.includes(attr.type)) {
335
+ if (value !== void 0) out[name] = typeof value === "string" ? value.slice(0, 2e5) : value;
336
+ } else if (attr.type === "media") {
337
+ const files = (attr.multiple ? Array.isArray(value) ? value : [] : value ? [value] : []).map((file) => projectMedia(file, cmsOrigin)).filter(Boolean);
338
+ out[name] = attr.multiple ? files : files[0] || null;
339
+ } else if (attr.type === "component") {
340
+ const child = components?.[attr.component];
341
+ out[name] = attr.repeatable ? (Array.isArray(value) ? value : []).slice(0, 100).map((item, i) => ({ ...projectRow(item, child, components, cmsOrigin, depth + 1), __key: blockKey(item) ?? String(i) })) : projectRow(value, child, components, cmsOrigin, depth + 1);
342
+ }
343
+ }
344
+ return out;
345
+ }
346
+ function projectMedia(file, cmsOrigin) {
347
+ if (!file || typeof file.url !== "string") return null;
348
+ try {
349
+ const url = new URL(file.url, cmsOrigin);
350
+ if (!["http:", "https:"].includes(url.protocol)) return null;
351
+ return { url: url.href, alternativeText: String(file.alternativeText || ""), caption: String(file.caption || ""), width: Number(file.width) || 0, height: Number(file.height) || 0, mime: String(file.mime || "") };
352
+ } catch {
353
+ return null;
354
+ }
355
+ }
356
+ function editableFields(uid, row, components) {
357
+ const schema = components?.[uid];
358
+ const out = [];
359
+ const SYSTEM = ["id", "documentId", "locale", "createdAt", "updatedAt", "publishedAt", "createdBy", "updatedBy", "localizations", "__component", "__temp_key__"];
360
+ const push = (name, attr, label) => {
361
+ if (!SYSTEM.includes(name.split(".").pop()) && attr?.private !== true && EDITABLE_TYPES.includes(attr?.type)) out.push({ name, type: attr.type, label });
362
+ };
363
+ for (const [name, attr] of Object.entries(schema?.attributes || {})) {
364
+ if (attr.type === "component" && attr.repeatable) {
365
+ (Array.isArray(row?.[name]) ? row[name] : []).slice(0, 50).forEach((_, i) => {
366
+ for (const [child, childAttr] of Object.entries(components?.[attr.component]?.attributes || {})) push(`${name}.${i}.${child}`, childAttr, `${name} ${i + 1} \xB7 ${child}`);
367
+ });
368
+ } else push(name, attr, name);
369
+ }
370
+ return out;
371
+ }
372
+ function mediaFields(uid, row, components) {
373
+ const media = {};
374
+ for (const [name, attr] of Object.entries(components?.[uid]?.attributes || {})) {
375
+ if (attr.type === "media" && attr.private !== true) media[name] = { required: attr.required === true, multiple: attr.multiple === true, present: attr.multiple ? Array.isArray(row?.[name]) && row[name].length > 0 : Boolean(row?.[name]) };
376
+ }
377
+ return media;
378
+ }
379
+ function projectPage(rows, components, cmsOrigin, hidden = null) {
380
+ return (Array.isArray(rows) ? rows : []).slice(0, 200).flatMap((row, index) => {
381
+ const uid = row?.__component;
382
+ const key = blockKey(row);
383
+ const schema = components?.[uid];
384
+ if (!uid || key == null || !schema) return [];
385
+ return [{
386
+ key,
387
+ index,
388
+ uid,
389
+ label: schema.info?.displayName || uid,
390
+ data: projectRow(row, schema, components, cmsOrigin),
391
+ fields: editableFields(uid, row, components),
392
+ media: mediaFields(uid, row, components),
393
+ ...hidden && row[hidden] === true && { hidden: true }
394
+ }];
395
+ });
396
+ }
397
+ function isPreviewMessage(event, origin, source, channel, type) {
398
+ return event.origin === origin && event.source === source && event.data?.protocol === PROTOCOL && event.data?.channel === channel && event.data?.type === type;
399
+ }
400
+
401
+ // admin/devices.ts
402
+ var React = __toESM(require("react"));
403
+ var DEVICES = { fit: 0, mobile: 390, tablet: 834, desktop: 1440 };
404
+ function useStageSize(el) {
405
+ const [size, setSize] = React.useState({ width: 0, height: 0 });
406
+ React.useEffect(() => {
407
+ if (!el) return;
408
+ const measure = () => setSize({ width: el.clientWidth, height: el.clientHeight });
409
+ measure();
410
+ const observer = "ResizeObserver" in window ? new ResizeObserver(measure) : null;
411
+ observer?.observe(el);
412
+ return () => observer?.disconnect();
413
+ }, [el]);
414
+ return size;
415
+ }
416
+ var frameStyle = (device, stage) => {
417
+ const width = DEVICES[device];
418
+ if (!width || !stage.width) return { left: 0, width: "100%", height: "100%" };
419
+ const scale = Math.min(1, stage.width / width);
420
+ return { left: Math.max(0, (stage.width - width * scale) / 2), width, height: stage.height / scale, transform: `scale(${scale})` };
421
+ };
422
+ var stageBackground = (theme) => {
423
+ const line = theme?.colors?.neutral150 || "#eaeaef";
424
+ return {
425
+ backgroundColor: theme?.colors?.neutral100 || "#f6f6f9",
426
+ backgroundImage: `linear-gradient(${line} 1px, transparent 1px), linear-gradient(90deg, ${line} 1px, transparent 1px)`,
427
+ backgroundSize: "24px 24px"
428
+ };
429
+ };
430
+
163
431
  // admin/accordions.mjs
164
432
  function zoneLists(root = document) {
165
433
  return [...root.querySelectorAll("ol[aria-describedby]")].filter((list) => list.querySelector(":scope > li button[aria-expanded]"));
@@ -172,28 +440,100 @@ function findZoneList(label, root = document) {
172
440
  function toggles(list) {
173
441
  return [...list.querySelectorAll(":scope > li")].map((item) => item.querySelector("button[aria-expanded]")).filter(Boolean);
174
442
  }
443
+ function planRows(rects, view, margin = 0) {
444
+ const now = [], later = [];
445
+ const gap = (r) => Math.max(view.top - margin - r.bottom, r.top - view.bottom - margin, 0);
446
+ rects.forEach((r, i) => (gap(r) === 0 ? now : later).push(i));
447
+ later.sort((a, b) => gap(rects[a]) - gap(rects[b]));
448
+ return { now, later };
449
+ }
450
+ function scroller(el) {
451
+ for (let node = el?.parentElement; node && node !== document.body; node = node.parentElement) {
452
+ const { overflowY } = getComputedStyle(node);
453
+ if ((overflowY === "auto" || overflowY === "scroll") && node.scrollHeight > node.clientHeight) return node;
454
+ }
455
+ return null;
456
+ }
457
+ var jobs = /* @__PURE__ */ new Map();
458
+ function stopAll() {
459
+ for (const stop of jobs.values()) stop();
460
+ jobs.clear();
461
+ }
462
+ var PER_FRAME = 3;
463
+ var idle = (run) => typeof requestIdleCallback === "function" ? requestIdleCallback(run, { timeout: 300 }) : setTimeout(() => run({ timeRemaining: () => 8 }), 16);
464
+ var frame = (run) => typeof requestAnimationFrame === "function" ? requestAnimationFrame(run) : setTimeout(run, 16);
175
465
  function setAll(list, open, batch = (run) => run()) {
176
- const pending = toggles(list).filter((button) => button.getAttribute("aria-expanded") === "true" !== open);
466
+ jobs.get(list)?.();
467
+ jobs.delete(list);
468
+ const header = (li) => li.querySelector("button[aria-expanded]");
469
+ const differs = (li) => {
470
+ const b = li.isConnected && header(li);
471
+ return b && b.getAttribute("aria-expanded") === "true" !== open ? b : null;
472
+ };
473
+ const click = (rows2) => {
474
+ const buttons = rows2.map(differs).filter(Boolean);
475
+ if (buttons.length) batch(() => {
476
+ for (const b of buttons) b.click();
477
+ });
478
+ return buttons.length;
479
+ };
480
+ const rows = [...list.querySelectorAll(":scope > li")].filter(differs);
481
+ if (!rows.length) return 0;
482
+ const root = scroller(list);
483
+ const view = () => root ? root.getBoundingClientRect() : { top: 0, bottom: window.innerHeight };
484
+ const height = view().bottom - view().top;
485
+ const { now, later } = planRows(rows.map((li) => li.getBoundingClientRect()), view(), open ? height : 0);
486
+ let stopped = false, handle = 0;
177
487
  if (open) {
178
- batch(() => {
179
- for (const button of pending) button.click();
488
+ if (typeof IntersectionObserver === "undefined") return click(rows);
489
+ const near = (li) => planRows([li.getBoundingClientRect()], view(), height).now.length > 0;
490
+ const queue = new Set(now.map((i) => rows[i]));
491
+ let changed2 = 0;
492
+ const flush = () => {
493
+ handle = 0;
494
+ if (stopped) return;
495
+ const next = [];
496
+ for (const li of queue) {
497
+ if (next.length === PER_FRAME) break;
498
+ queue.delete(li);
499
+ if (near(li)) next.push(li);
500
+ else observer.observe(li);
501
+ }
502
+ changed2 += click(next);
503
+ if (queue.size) handle = frame(flush);
504
+ };
505
+ const observer = new IntersectionObserver((entries) => {
506
+ for (const entry of entries) if (entry.isIntersecting) {
507
+ observer.unobserve(entry.target);
508
+ queue.add(entry.target);
509
+ }
510
+ if (queue.size && !handle && !stopped) handle = frame(flush);
511
+ }, { root, rootMargin: `${Math.round(height)}px 0px` });
512
+ later.forEach((i) => observer.observe(rows[i]));
513
+ jobs.set(list, () => {
514
+ stopped = true;
515
+ observer.disconnect();
180
516
  });
181
- return Promise.resolve(pending.length);
517
+ flush();
518
+ return changed2;
182
519
  }
183
- const frame = () => new Promise((resolve) => typeof requestAnimationFrame === "function" ? requestAnimationFrame(() => resolve()) : setTimeout(resolve, 16));
184
- return (async () => {
185
- let changed = 0;
186
- for (const button of pending) {
187
- if (!button.isConnected || button.getAttribute("aria-expanded") !== "true") continue;
188
- button.click();
189
- changed++;
190
- await frame();
191
- }
192
- return changed;
193
- })();
520
+ const changed = click(now.map((i) => rows[i]));
521
+ if (!later.length) return changed;
522
+ const pending = later.map((i) => rows[i]);
523
+ const slice = (deadline) => {
524
+ if (stopped) return;
525
+ click(pending.splice(0, deadline.timeRemaining() > 10 ? PER_FRAME : 1));
526
+ if (pending.length) idle(slice);
527
+ else jobs.delete(list);
528
+ };
529
+ idle(slice);
530
+ jobs.set(list, () => {
531
+ stopped = true;
532
+ });
533
+ return changed;
194
534
  }
195
535
  function isNativeAddButton(button, zone) {
196
- if (!button || button.getAttribute("aria-expanded") || button.closest('ol[aria-describedby] > li, [role="dialog"], [data-testid^="open-gallery-"], [data-testid^="block-"], [data-testid^="page-preview"], [data-testid^="bp-"]')) return false;
536
+ if (!button || button.getAttribute("aria-expanded") || button.closest('ol[aria-describedby] > li, [role="dialog"], [data-testid^="open-gallery-"], [data-testid^="block-"], [data-testid^="page-preview"], [data-testid^="bp-"], [data-testid^="zone-"], [data-blockscene-row-actions]')) return false;
197
537
  const text = (button.textContent || "").trim();
198
538
  return Boolean(text) && new RegExp(`(^|\\s)${String(zone.name).replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`).test(text);
199
539
  }
@@ -237,7 +577,7 @@ function Wireframe({ template, palette, className, noPreview }) {
237
577
  }
238
578
 
239
579
  // admin/RowPreviews.tsx
240
- var React = __toESM(require("react"));
580
+ var React2 = __toESM(require("react"));
241
581
  var import_react_dom = require("react-dom");
242
582
 
243
583
  // admin/messages.ts
@@ -285,10 +625,11 @@ var en_default = {
285
625
  "templates.faq": "FAQ",
286
626
  brokenMedia: "The selected media no longer exists; the next source is used.",
287
627
  editor: "Editor preferences",
288
- enabled: "Editor enhancements enabled",
289
- enabledHelp: "Off restores the native picker and accordions. This page stays available to turn it back on.",
628
+ enabled: "Blockscene enabled",
629
+ enabledHelp: "Off turns the whole plugin off at once, with no restart: every edit view is native again and the layout group publish check stops. Use it if something goes wrong in production.",
290
630
  showOpenAll: 'Show "Open all blocks"',
291
631
  showCloseAll: 'Show "Close all blocks"',
632
+ showRowThumbnails: "Show block thumbnails in the rows",
292
633
  initialState: "Initial accordion state",
293
634
  "states.closed": "All closed",
294
635
  "states.open": "All open",
@@ -300,13 +641,20 @@ var en_default = {
300
641
  "modes.form": "Fields",
301
642
  "modes.split": "Fields + page",
302
643
  "modes.preview": "Visual editor",
303
- previewModeHelp: "Applies when opening a document; each editor can switch modes and the browser remembers the last choice.",
644
+ previewModeHelp: "Every edit view opens in this mode, unless its content type sets its own below. Editors can switch while they edit; the switch is not remembered.",
645
+ lazyEdit: "Click to edit",
646
+ lazyEditors: "Load rich-text editors on demand",
647
+ lazyFields: "Custom fields loaded on demand",
648
+ lazyEditorsHelp: "Listed custom fields show a read-only preview until clicked or focused, so opening large pages stays fast. Separate uids with commas.",
649
+ lazyFieldsInvalid: "Use up to {max} custom field uids such as plugin::ckeditor5.CKEditor or global::my-field.",
304
650
  previewV4: "The whole-page preview is available in the Strapi 5 distribution only; these options have no effect on Strapi 4.",
305
651
  modeForm: "Fields",
306
652
  modeSplit: "Fields + page",
307
653
  modePreview: "Visual editor",
308
654
  previewPane: "Page preview",
309
655
  resize: "Resize preview pane",
656
+ resizeHint: "Drag to resize; double-click to reset to half",
657
+ scrollToHovered: "Scroll to the highlighted block",
310
658
  previewHelp: "Unsaved changes appear in the preview. Click a block to open its form, an image to open the Media Library, a text to edit it.",
311
659
  previewNoUrl: "No preview URL: set it in Settings, Blockscene, or configure Strapi's native Preview for this content type.",
312
660
  previewFailed: "The preview did not answer within 15 s. If the frontend is still loading it will appear by itself; the form stays usable.",
@@ -343,19 +691,137 @@ var en_default = {
343
691
  diagInsertClose: 'Insert the missing "{expected}" after row {n}',
344
692
  diagStrayHint: "Remove the stray marker with the row's own delete action, or move it after an open marker.",
345
693
  modeGroup: "Editing mode",
694
+ undo: "Undo",
695
+ redo: "Redo",
696
+ historyGroup: "Undo history",
346
697
  saveDisabledHint: "No unsaved changes to save.",
347
698
  publishDisabledHint: "Publishing is unavailable: save first, fix the errors shown in the form, or check your permissions.",
348
699
  countAll: "{count} blocks",
349
700
  countFiltered: "{shown} of {total}",
350
- filterCategory: "Filter by category",
351
- allCategories: "All categories",
352
701
  showFields: "Fields",
353
702
  showFieldsHint: "Show the field list on each block",
354
703
  columns: "Columns",
355
704
  noPreview: "no preview",
356
705
  "templates.none": "No preview",
357
706
  noChanges: "No unsaved changes",
358
- rowPreview: "Open the {label} preview"
707
+ rowPreview: "Open the {label} preview",
708
+ deviceGroup: "Preview width",
709
+ "device.fit": "Fit",
710
+ "device.mobile": "Mobile",
711
+ "device.tablet": "Tablet",
712
+ "device.desktop": "Desktop",
713
+ contentTypes: "Content types",
714
+ contentTypesHelp: 'The gallery, accordions and page preview work on these content types (the ones with a Dynamic Zone). Turn one off to leave its edit view native. "Opens in" is how every edit view of that type starts; editors can still switch while they edit.',
715
+ typeMode: "Opens in",
716
+ modeDefault: "Default ({mode})",
717
+ blockModalDone: "Done",
718
+ crashed: "Blockscene stopped here after an error. The native editor is unaffected. If it happens again, turn Blockscene off in Settings, Blockscene.",
719
+ sidebarLabel: "Page fields",
720
+ "nav.all": "All",
721
+ "nav.recent": "Recently used",
722
+ "nav.starred": "Starred",
723
+ typologiesTitle: "Typologies",
724
+ collapseSidebar: "Collapse sidebar",
725
+ expandSidebar: "Expand sidebar",
726
+ "filters.tags": "Tags",
727
+ "filters.media": "Media",
728
+ "filters.content": "Content",
729
+ clearAll: "Clear all",
730
+ removeFilter: "Remove filter {name}",
731
+ "typologies.hero": "Hero",
732
+ "typologies.text": "Text",
733
+ "typologies.media": "Media",
734
+ "typologies.listing": "Listing",
735
+ "typologies.cards": "Cards",
736
+ "typologies.cta": "Call to action",
737
+ "typologies.form": "Form",
738
+ "typologies.layout": "Layout",
739
+ "facets.image": "Image",
740
+ "facets.video": "Video",
741
+ "facets.gallery": "Gallery",
742
+ "facets.richtext": "Rich text",
743
+ "facets.list": "List",
744
+ "facets.dynamic": "Dynamic",
745
+ "facets.form": "Form",
746
+ star: "Star",
747
+ unstar: "Remove star",
748
+ quickInsert: "Insert {label}",
749
+ insert: "Insert",
750
+ closeDetail: "Close details",
751
+ typology: "Typology",
752
+ typologyAuto: "Automatic ({name})",
753
+ tags: "Tags",
754
+ tagsHelp: "Comma separated, up to 10 tags of 24 characters. They become a gallery filter.",
755
+ sidebarTitle: "Visual editor sidebar: {name}",
756
+ sidebarHelp: "Buttons on an edge of the visual editor. Each one opens some of the document's own fields in a drawer or a modal.",
757
+ sidebarPosition: "Sidebar position",
758
+ "positions.left": "Left",
759
+ "positions.right": "Right",
760
+ "positions.bottom": "Bottom",
761
+ sidebarItemLabel: "Label",
762
+ sidebarIcon: "Icon",
763
+ noIcon: "No icon",
764
+ "icons.text": "Text",
765
+ "icons.tag": "Tag",
766
+ "icons.seo": "SEO",
767
+ "icons.settings": "Settings",
768
+ "icons.image": "Image",
769
+ "icons.link": "Link",
770
+ "icons.palette": "Palette",
771
+ "icons.list": "List",
772
+ "icons.globe": "Globe",
773
+ "icons.info": "Info",
774
+ sidebarOpens: "Opens as",
775
+ "opens.drawer": "Drawer",
776
+ "opens.modal": "Modal",
777
+ moveUp: "Move up",
778
+ moveDown: "Move down",
779
+ sidebarRemove: "Remove item",
780
+ sidebarFields: "Fields",
781
+ sidebarInvalid: "Give the item a label (up to 40 characters, no < or >) and pick at least one field.",
782
+ sidebarInvalidSave: "Fix the sidebar items marked above before saving.",
783
+ sidebarAdd: "Add item",
784
+ sidebarLimit: "Up to {max} items.",
785
+ restoreProject: "Restore project defaults",
786
+ resetAll: "Reset to defaults",
787
+ restoreProjectConfirm: "Discard every setting saved on this page and go back to the project defaults from the code?",
788
+ resetAllConfirm: "Discard every setting saved on this page and go back to the defaults?",
789
+ restored: "Defaults restored.",
790
+ blockPreview: "Live block preview",
791
+ livePreviewFailed: "Live preview unavailable, showing the image.",
792
+ blockPreviewUrl: "Block preview URL",
793
+ blockPreviewUrlHelp: "Optional. A page of your frontend that renders one block on its own, shown large when a gallery card is clicked, e.g. http://localhost:3026/pt-BR/block-preview/'{name}'/'{variant}'. Placeholders: '{uid}', '{name}' (after the dot), '{category}' (before the dot), '{variant}' (default), '{locale}'. Empty uses the page preview route when there is one, else the image.",
794
+ rowActions: "Actions for {label}",
795
+ selectBlock: "Select {label}",
796
+ hiddenBadge: "Hidden",
797
+ hideBlock: "Hide {label} on the site",
798
+ showBlock: "Show {label} on the site",
799
+ duplicateBlock: "Duplicate {label}",
800
+ copyBlock: "Copy {label}",
801
+ confirmDeleteTitle: "Delete block",
802
+ confirmDelete: "Delete block {name}?",
803
+ confirmDeleteMarker: "This is a group marker: only the marker is removed, not the blocks inside the group, and the group stays unbalanced until it is fixed.",
804
+ delete: "Delete",
805
+ selectMode: "Select blocks to copy",
806
+ copySelected: "Copy selected ({count})",
807
+ paste: "Paste {count, plural, one {# block} other {# blocks}}",
808
+ pasted: "{count, plural, one {# block pasted} other {# blocks pasted}}.",
809
+ copied: "{count, plural, one {# block copied} other {# blocks copied}}. Paste on any page of this site.",
810
+ copyFailed: "Could not copy: the browser storage is full or blocked.",
811
+ pasteNotAllowed: 'Nothing was pasted: "{uid}" is not allowed in this zone.',
812
+ pasteFull: "Nothing was pasted: the zone has no room for {count} more blocks.",
813
+ pasteUnbalanced: "Nothing was pasted: the copied blocks contain an incomplete group.",
814
+ rowActionFailed: "The action could not be completed; the form was not changed.",
815
+ rowActionsTitle: "Row actions",
816
+ confirmDeleteOption: "Confirm before deleting a block",
817
+ duplicateOption: 'Show "Duplicate" on each block',
818
+ clipboardOption: 'Show "Copy" and "Paste" (across pages)',
819
+ hiddenBlocks: "Blocks hidden on the site",
820
+ "hiddenModes.strip": "Removed from the content API",
821
+ "hiddenModes.flag": "Sent with the flag (the frontend decides)",
822
+ "hiddenModes.off": "Off (no hide button)",
823
+ hiddenBlocksHelp: "The eye on each block marks it hidden on the site. Removed: content API reads (REST, GraphQL) leave it out, the admin always shows it. Flag: it is sent with the attribute so the frontend can decide.",
824
+ hiddenAttributeOff: "The plugin config sets hiddenAttribute to false: blocks cannot be hidden."
359
825
  };
360
826
 
361
827
  // admin/translations/pt-BR.json
@@ -400,10 +866,11 @@ var pt_BR_default = {
400
866
  "templates.faq": "FAQ",
401
867
  brokenMedia: "A m\xEDdia escolhida n\xE3o existe mais; a pr\xF3xima origem \xE9 usada.",
402
868
  editor: "Prefer\xEAncias do editor",
403
- enabled: "Melhorias do editor ativas",
404
- enabledHelp: "Desligado restaura o seletor e os accordions nativos. Esta p\xE1gina continua dispon\xEDvel para reativar.",
869
+ enabled: "Blockscene ligado",
870
+ enabledHelp: "Desligado, o plugin inteiro para na hora, sem restart: toda tela de edi\xE7\xE3o volta a ser nativa e a verifica\xE7\xE3o de grupos na publica\xE7\xE3o para. Use se algo der errado em produ\xE7\xE3o.",
405
871
  showOpenAll: 'Mostrar "Abrir todos os blocos"',
406
872
  showCloseAll: 'Mostrar "Fechar todos os blocos"',
873
+ showRowThumbnails: "Mostrar miniaturas dos blocos nas linhas",
407
874
  initialState: "Estado inicial dos accordions",
408
875
  "states.closed": "Todos fechados",
409
876
  "states.open": "Todos abertos",
@@ -415,13 +882,20 @@ var pt_BR_default = {
415
882
  "modes.form": "Campos",
416
883
  "modes.split": "Campos + p\xE1gina",
417
884
  "modes.preview": "Editor visual",
418
- previewModeHelp: "Vale ao abrir um documento; cada editor pode trocar de modo e o navegador lembra a \xFAltima escolha.",
885
+ previewModeHelp: "Toda tela de edi\xE7\xE3o abre neste modo, a menos que o tipo de conte\xFAdo defina o seu abaixo. Quem edita pode trocar durante a edi\xE7\xE3o; a troca n\xE3o fica gravada.",
886
+ lazyEdit: "Clique para editar",
887
+ lazyEditors: "Carregar editores de texto rico sob demanda",
888
+ lazyFields: "Campos personalizados carregados sob demanda",
889
+ lazyEditorsHelp: "Os campos personalizados listados mostram uma pr\xE9via somente leitura at\xE9 receberem clique ou foco, para que p\xE1ginas grandes abram r\xE1pido. Separe os uids com v\xEDrgulas.",
890
+ lazyFieldsInvalid: "Use at\xE9 {max} uids de campos personalizados, como plugin::ckeditor5.CKEditor ou global::meu-campo.",
419
891
  previewV4: "A preview de p\xE1gina inteira existe apenas na distribui\xE7\xE3o Strapi 5; estas op\xE7\xF5es n\xE3o t\xEAm efeito no Strapi 4.",
420
892
  modeForm: "Campos",
421
893
  modeSplit: "Campos + p\xE1gina",
422
894
  modePreview: "Editor visual",
423
895
  previewPane: "Preview da p\xE1gina",
424
896
  resize: "Redimensionar painel de preview",
897
+ resizeHint: "Arraste para redimensionar; clique duplo volta \xE0 metade",
898
+ scrollToHovered: "Rolar at\xE9 o bloco destacado",
425
899
  previewHelp: "Altera\xE7\xF5es n\xE3o salvas aparecem na preview. Clique em um bloco para abrir seu formul\xE1rio, em uma imagem para abrir a Media Library, em um texto para edit\xE1-lo.",
426
900
  previewNoUrl: "Sem URL de preview: defina em Configura\xE7\xF5es, Blockscene, ou configure a Preview nativa do Strapi para este tipo de conte\xFAdo.",
427
901
  previewFailed: "A preview n\xE3o respondeu em 15 s. Se o frontend ainda estiver carregando, ela aparece sozinha; o formul\xE1rio continua utiliz\xE1vel.",
@@ -458,19 +932,137 @@ var pt_BR_default = {
458
932
  diagInsertClose: 'Inserir o fechamento "{expected}" que falta depois da linha {n}',
459
933
  diagStrayHint: "Remova o marcador \xF3rf\xE3o pela a\xE7\xE3o de excluir da pr\xF3pria linha, ou mova-o para depois de uma abertura.",
460
934
  modeGroup: "Modo de edi\xE7\xE3o",
935
+ undo: "Desfazer",
936
+ redo: "Refazer",
937
+ historyGroup: "Hist\xF3rico de edi\xE7\xE3o",
461
938
  saveDisabledHint: "N\xE3o h\xE1 altera\xE7\xF5es para salvar.",
462
939
  publishDisabledHint: "Publica\xE7\xE3o indispon\xEDvel: salve antes, corrija os erros do formul\xE1rio ou verifique suas permiss\xF5es.",
463
940
  countAll: "{count} blocos",
464
941
  countFiltered: "{shown} de {total}",
465
- filterCategory: "Filtrar por categoria",
466
- allCategories: "Todas as categorias",
467
942
  showFields: "Campos",
468
943
  showFieldsHint: "Mostrar a lista de campos em cada bloco",
469
944
  columns: "Colunas",
470
945
  noPreview: "sem preview",
471
946
  "templates.none": "Sem preview",
472
947
  noChanges: "Sem altera\xE7\xF5es pendentes",
473
- rowPreview: "Abrir a pr\xE9via de {label}"
948
+ rowPreview: "Abrir a pr\xE9via de {label}",
949
+ deviceGroup: "Largura do preview",
950
+ "device.fit": "Ajustar",
951
+ "device.mobile": "Celular",
952
+ "device.tablet": "Tablet",
953
+ "device.desktop": "Desktop",
954
+ contentTypes: "Tipos de conte\xFAdo",
955
+ contentTypesHelp: 'A galeria, os accordions e o preview da p\xE1gina funcionam nestes tipos de conte\xFAdo (os que t\xEAm uma zona din\xE2mica). Desligue um para deixar a edi\xE7\xE3o dele nativa. "Abre em" \xE9 como toda tela de edi\xE7\xE3o desse tipo come\xE7a; quem edita ainda pode trocar durante a edi\xE7\xE3o.',
956
+ typeMode: "Abre em",
957
+ modeDefault: "Padr\xE3o ({mode})",
958
+ blockModalDone: "Concluir",
959
+ crashed: "O Blockscene parou aqui por causa de um erro. O editor nativo n\xE3o foi afetado. Se acontecer de novo, desligue o Blockscene em Settings, Blockscene.",
960
+ sidebarLabel: "Campos da p\xE1gina",
961
+ "nav.all": "Todos",
962
+ "nav.recent": "Usados recentemente",
963
+ "nav.starred": "Favoritos",
964
+ typologiesTitle: "Tipologias",
965
+ collapseSidebar: "Recolher barra lateral",
966
+ expandSidebar: "Expandir barra lateral",
967
+ "filters.tags": "Tags",
968
+ "filters.media": "M\xEDdia",
969
+ "filters.content": "Conte\xFAdo",
970
+ clearAll: "Limpar tudo",
971
+ removeFilter: "Remover filtro {name}",
972
+ "typologies.hero": "Hero",
973
+ "typologies.text": "Texto",
974
+ "typologies.media": "M\xEDdia",
975
+ "typologies.listing": "Listagem",
976
+ "typologies.cards": "Cards",
977
+ "typologies.cta": "Chamada para a\xE7\xE3o",
978
+ "typologies.form": "Formul\xE1rio",
979
+ "typologies.layout": "Layout",
980
+ "facets.image": "Imagem",
981
+ "facets.video": "V\xEDdeo",
982
+ "facets.gallery": "Galeria",
983
+ "facets.richtext": "Texto rico",
984
+ "facets.list": "Lista",
985
+ "facets.dynamic": "Din\xE2mico",
986
+ "facets.form": "Formul\xE1rio",
987
+ star: "Favoritar",
988
+ unstar: "Remover dos favoritos",
989
+ quickInsert: "Inserir {label}",
990
+ insert: "Inserir",
991
+ closeDetail: "Fechar detalhes",
992
+ typology: "Tipologia",
993
+ typologyAuto: "Autom\xE1tica ({name})",
994
+ tags: "Tags",
995
+ tagsHelp: "Separadas por v\xEDrgula, at\xE9 10 tags de 24 caracteres. Viram filtro na galeria.",
996
+ sidebarTitle: "Barra lateral do editor visual: {name}",
997
+ sidebarHelp: "Bot\xF5es numa borda do editor visual. Cada um abre alguns campos do pr\xF3prio documento numa gaveta ou num modal.",
998
+ sidebarPosition: "Posi\xE7\xE3o da barra lateral",
999
+ "positions.left": "Esquerda",
1000
+ "positions.right": "Direita",
1001
+ "positions.bottom": "Embaixo",
1002
+ sidebarItemLabel: "R\xF3tulo",
1003
+ sidebarIcon: "\xCDcone",
1004
+ noIcon: "Sem \xEDcone",
1005
+ "icons.text": "Texto",
1006
+ "icons.tag": "Etiqueta",
1007
+ "icons.seo": "SEO",
1008
+ "icons.settings": "Configura\xE7\xF5es",
1009
+ "icons.image": "Imagem",
1010
+ "icons.link": "Link",
1011
+ "icons.palette": "Paleta",
1012
+ "icons.list": "Lista",
1013
+ "icons.globe": "Globo",
1014
+ "icons.info": "Informa\xE7\xE3o",
1015
+ sidebarOpens: "Abre como",
1016
+ "opens.drawer": "Gaveta",
1017
+ "opens.modal": "Modal",
1018
+ moveUp: "Mover para cima",
1019
+ moveDown: "Mover para baixo",
1020
+ sidebarRemove: "Remover item",
1021
+ sidebarFields: "Campos",
1022
+ sidebarInvalid: "D\xEA um r\xF3tulo ao item (at\xE9 40 caracteres, sem < ou >) e escolha pelo menos um campo.",
1023
+ sidebarInvalidSave: "Corrija os itens da barra lateral marcados acima antes de salvar.",
1024
+ sidebarAdd: "Adicionar item",
1025
+ sidebarLimit: "At\xE9 {max} itens.",
1026
+ restoreProject: "Restaurar padr\xF5es do projeto",
1027
+ resetAll: "Restaurar padr\xF5es",
1028
+ restoreProjectConfirm: "Descartar todas as configura\xE7\xF5es salvas nesta p\xE1gina e voltar aos padr\xF5es do projeto definidos no c\xF3digo?",
1029
+ resetAllConfirm: "Descartar todas as configura\xE7\xF5es salvas nesta p\xE1gina e voltar aos padr\xF5es?",
1030
+ restored: "Padr\xF5es restaurados.",
1031
+ blockPreview: "Preview ao vivo do bloco",
1032
+ livePreviewFailed: "Preview ao vivo indispon\xEDvel, mostrando a imagem.",
1033
+ blockPreviewUrl: "URL de preview do bloco",
1034
+ blockPreviewUrlHelp: "Opcional. Uma p\xE1gina do seu frontend que renderiza um bloco sozinho, mostrada em tamanho grande ao clicar num card da galeria, por exemplo http://localhost:3026/pt-BR/block-preview/'{name}'/'{variant}'. Marcadores: '{uid}', '{name}' (depois do ponto), '{category}' (antes do ponto), '{variant}' (default), '{locale}'. Vazio usa a rota de preview de p\xE1gina quando existir, sen\xE3o a imagem.",
1035
+ rowActions: "A\xE7\xF5es de {label}",
1036
+ selectBlock: "Selecionar {label}",
1037
+ hiddenBadge: "Oculto",
1038
+ hideBlock: "Ocultar {label} no site",
1039
+ showBlock: "Mostrar {label} no site",
1040
+ duplicateBlock: "Duplicar {label}",
1041
+ copyBlock: "Copiar {label}",
1042
+ confirmDeleteTitle: "Excluir bloco",
1043
+ confirmDelete: "Excluir o bloco {name}?",
1044
+ confirmDeleteMarker: "Este \xE9 um marcador de grupo: s\xF3 o marcador \xE9 removido, n\xE3o os blocos dentro do grupo, e o grupo fica desbalanceado at\xE9 ser corrigido.",
1045
+ delete: "Excluir",
1046
+ selectMode: "Selecionar blocos para copiar",
1047
+ copySelected: "Copiar selecionados ({count})",
1048
+ paste: "Colar {count, plural, one {# bloco} other {# blocos}}",
1049
+ pasted: "{count, plural, one {# bloco colado} other {# blocos colados}}.",
1050
+ copied: "{count, plural, one {# bloco copiado} other {# blocos copiados}}. Cole em qualquer p\xE1gina deste site.",
1051
+ copyFailed: "N\xE3o foi poss\xEDvel copiar: o armazenamento do navegador est\xE1 cheio ou bloqueado.",
1052
+ pasteNotAllowed: 'Nada foi colado: "{uid}" n\xE3o \xE9 permitido nesta zona.',
1053
+ pasteFull: "Nada foi colado: a zona n\xE3o tem espa\xE7o para mais {count} blocos.",
1054
+ pasteUnbalanced: "Nada foi colado: os blocos copiados cont\xEAm um grupo incompleto.",
1055
+ rowActionFailed: "A a\xE7\xE3o n\xE3o p\xF4de ser conclu\xEDda; o formul\xE1rio n\xE3o foi alterado.",
1056
+ rowActionsTitle: "A\xE7\xF5es dos blocos",
1057
+ confirmDeleteOption: "Confirmar antes de excluir um bloco",
1058
+ duplicateOption: 'Mostrar "Duplicar" em cada bloco',
1059
+ clipboardOption: 'Mostrar "Copiar" e "Colar" (entre p\xE1ginas)',
1060
+ hiddenBlocks: "Blocos ocultos no site",
1061
+ "hiddenModes.strip": "Removidos da API de conte\xFAdo",
1062
+ "hiddenModes.flag": "Enviados com a marca\xE7\xE3o (o frontend decide)",
1063
+ "hiddenModes.off": "Desligado (sem bot\xE3o de ocultar)",
1064
+ hiddenBlocksHelp: "O olho em cada bloco o marca como oculto no site. Removidos: as leituras da API de conte\xFAdo (REST, GraphQL) o deixam de fora; o admin sempre o mostra. Marca\xE7\xE3o: ele \xE9 enviado com o atributo para o frontend decidir.",
1065
+ hiddenAttributeOff: "A config do plugin define hiddenAttribute como false: n\xE3o \xE9 poss\xEDvel ocultar blocos."
474
1066
  };
475
1067
 
476
1068
  // admin/translations/pt.json
@@ -515,10 +1107,11 @@ var pt_default = {
515
1107
  "templates.faq": "FAQ",
516
1108
  brokenMedia: "A m\xEDdia escolhida n\xE3o existe mais; a pr\xF3xima origem \xE9 usada.",
517
1109
  editor: "Prefer\xEAncias do editor",
518
- enabled: "Melhorias do editor ativas",
519
- enabledHelp: "Desligado restaura o seletor e os accordions nativos. Esta p\xE1gina continua dispon\xEDvel para reativar.",
1110
+ enabled: "Blockscene ligado",
1111
+ enabledHelp: "Desligado, o plugin inteiro para de imediato, sem rein\xEDcio: todos os ecr\xE3s de edi\xE7\xE3o voltam a ser nativos e a verifica\xE7\xE3o de grupos na publica\xE7\xE3o para. Use-o se algo correr mal em produ\xE7\xE3o.",
520
1112
  showOpenAll: 'Mostrar "Abrir todos os blocos"',
521
1113
  showCloseAll: 'Mostrar "Fechar todos os blocos"',
1114
+ showRowThumbnails: "Mostrar miniaturas dos blocos nas linhas",
522
1115
  initialState: "Estado inicial dos accordions",
523
1116
  "states.closed": "Todos fechados",
524
1117
  "states.open": "Todos abertos",
@@ -530,13 +1123,20 @@ var pt_default = {
530
1123
  "modes.form": "Campos",
531
1124
  "modes.split": "Campos + p\xE1gina",
532
1125
  "modes.preview": "Editor visual",
533
- previewModeHelp: "Vale ao abrir um documento; cada editor pode trocar de modo e o navegador lembra a \xFAltima escolha.",
1126
+ previewModeHelp: "Cada ecr\xE3 de edi\xE7\xE3o abre neste modo, salvo se o tipo de conte\xFAdo definir o seu abaixo. Quem edita pode mudar durante a edi\xE7\xE3o; a mudan\xE7a n\xE3o fica guardada.",
1127
+ lazyEdit: "Clique para editar",
1128
+ lazyEditors: "Carregar editores de texto rico a pedido",
1129
+ lazyFields: "Campos personalizados carregados a pedido",
1130
+ lazyEditorsHelp: "Os campos personalizados indicados mostram uma pr\xE9-visualiza\xE7\xE3o s\xF3 de leitura at\xE9 receberem clique ou foco, para que p\xE1ginas grandes abram depressa. Separe os uids com v\xEDrgulas.",
1131
+ lazyFieldsInvalid: "Use at\xE9 {max} uids de campos personalizados, como plugin::ckeditor5.CKEditor ou global::meu-campo.",
534
1132
  previewV4: "A pr\xE9-visualiza\xE7\xE3o de p\xE1gina inteira existe apenas na distribui\xE7\xE3o Strapi 5; estas op\xE7\xF5es n\xE3o t\xEAm efeito no Strapi 4.",
535
1133
  modeForm: "Campos",
536
1134
  modeSplit: "Campos + p\xE1gina",
537
1135
  modePreview: "Editor visual",
538
1136
  previewPane: "Pr\xE9-visualiza\xE7\xE3o da p\xE1gina",
539
1137
  resize: "Redimensionar painel de pr\xE9-visualiza\xE7\xE3o",
1138
+ resizeHint: "Arraste para redimensionar; duplo clique rep\xF5e a metade",
1139
+ scrollToHovered: "Deslocar at\xE9 ao bloco destacado",
540
1140
  previewHelp: "As altera\xE7\xF5es por guardar aparecem na pr\xE9-visualiza\xE7\xE3o. Clique num bloco para abrir o seu formul\xE1rio, numa imagem para abrir a Media Library, num texto para o editar.",
541
1141
  previewNoUrl: "Sem URL de pr\xE9-visualiza\xE7\xE3o: defina em Defini\xE7\xF5es, Blockscene, ou configure a Preview nativa do Strapi para este tipo de conte\xFAdo.",
542
1142
  previewFailed: "A pr\xE9-visualiza\xE7\xE3o n\xE3o respondeu em 15 s. Se o frontend ainda estiver a carregar, ela aparece sozinha; o formul\xE1rio continua utiliz\xE1vel.",
@@ -573,19 +1173,137 @@ var pt_default = {
573
1173
  diagInsertClose: 'Inserir o fecho "{expected}" em falta depois da linha {n}',
574
1174
  diagStrayHint: "Remova o marcador \xF3rf\xE3o pela a\xE7\xE3o de eliminar da pr\xF3pria linha, ou mova-o para depois de uma abertura.",
575
1175
  modeGroup: "Modo de edi\xE7\xE3o",
1176
+ undo: "Anular",
1177
+ redo: "Refazer",
1178
+ historyGroup: "Hist\xF3rico de edi\xE7\xE3o",
576
1179
  saveDisabledHint: "N\xE3o h\xE1 altera\xE7\xF5es por guardar.",
577
1180
  publishDisabledHint: "Publica\xE7\xE3o indispon\xEDvel: guarde antes, corrija os erros do formul\xE1rio ou verifique as suas permiss\xF5es.",
578
1181
  countAll: "{count} blocos",
579
1182
  countFiltered: "{shown} de {total}",
580
- filterCategory: "Filtrar por categoria",
581
- allCategories: "Todas as categorias",
582
1183
  showFields: "Campos",
583
1184
  showFieldsHint: "Mostrar a lista de campos em cada bloco",
584
1185
  columns: "Colunas",
585
1186
  noPreview: "sem pr\xE9-visualiza\xE7\xE3o",
586
1187
  "templates.none": "Sem pr\xE9-visualiza\xE7\xE3o",
587
1188
  noChanges: "Sem altera\xE7\xF5es pendentes",
588
- rowPreview: "Abrir a pr\xE9-visualiza\xE7\xE3o de {label}"
1189
+ rowPreview: "Abrir a pr\xE9-visualiza\xE7\xE3o de {label}",
1190
+ deviceGroup: "Largura da pr\xE9-visualiza\xE7\xE3o",
1191
+ "device.fit": "Ajustar",
1192
+ "device.mobile": "Telem\xF3vel",
1193
+ "device.tablet": "Tablet",
1194
+ "device.desktop": "Computador",
1195
+ contentTypes: "Tipos de conte\xFAdo",
1196
+ contentTypesHelp: 'A galeria, os acorde\xF5es e a pr\xE9-visualiza\xE7\xE3o da p\xE1gina funcionam nestes tipos de conte\xFAdo (os que t\xEAm uma zona din\xE2mica). Desligue um para manter a edi\xE7\xE3o nativa. "Abre em" \xE9 como come\xE7a cada ecr\xE3 de edi\xE7\xE3o desse tipo; quem edita pode sempre mudar durante a edi\xE7\xE3o.',
1197
+ typeMode: "Abre em",
1198
+ modeDefault: "Predefini\xE7\xE3o ({mode})",
1199
+ blockModalDone: "Concluir",
1200
+ crashed: "O Blockscene parou aqui devido a um erro. O editor nativo n\xE3o foi afetado. Se voltar a acontecer, desligue o Blockscene em Settings, Blockscene.",
1201
+ sidebarLabel: "Campos da p\xE1gina",
1202
+ "nav.all": "Todos",
1203
+ "nav.recent": "Usados recentemente",
1204
+ "nav.starred": "Favoritos",
1205
+ typologiesTitle: "Tipologias",
1206
+ collapseSidebar: "Recolher barra lateral",
1207
+ expandSidebar: "Expandir barra lateral",
1208
+ "filters.tags": "Etiquetas",
1209
+ "filters.media": "Multim\xE9dia",
1210
+ "filters.content": "Conte\xFAdo",
1211
+ clearAll: "Limpar tudo",
1212
+ removeFilter: "Remover filtro {name}",
1213
+ "typologies.hero": "Hero",
1214
+ "typologies.text": "Texto",
1215
+ "typologies.media": "Multim\xE9dia",
1216
+ "typologies.listing": "Listagem",
1217
+ "typologies.cards": "Cart\xF5es",
1218
+ "typologies.cta": "Chamada para a\xE7\xE3o",
1219
+ "typologies.form": "Formul\xE1rio",
1220
+ "typologies.layout": "Layout",
1221
+ "facets.image": "Imagem",
1222
+ "facets.video": "V\xEDdeo",
1223
+ "facets.gallery": "Galeria",
1224
+ "facets.richtext": "Texto rico",
1225
+ "facets.list": "Lista",
1226
+ "facets.dynamic": "Din\xE2mico",
1227
+ "facets.form": "Formul\xE1rio",
1228
+ star: "Marcar como favorito",
1229
+ unstar: "Remover dos favoritos",
1230
+ quickInsert: "Inserir {label}",
1231
+ insert: "Inserir",
1232
+ closeDetail: "Fechar detalhes",
1233
+ typology: "Tipologia",
1234
+ typologyAuto: "Autom\xE1tica ({name})",
1235
+ tags: "Etiquetas",
1236
+ tagsHelp: "Separadas por v\xEDrgulas, at\xE9 10 etiquetas de 24 caracteres. Tornam-se um filtro na galeria.",
1237
+ sidebarTitle: "Barra lateral do editor visual: {name}",
1238
+ sidebarHelp: "Bot\xF5es numa margem do editor visual. Cada um abre alguns campos do pr\xF3prio documento numa gaveta ou num modal.",
1239
+ sidebarPosition: "Posi\xE7\xE3o da barra lateral",
1240
+ "positions.left": "Esquerda",
1241
+ "positions.right": "Direita",
1242
+ "positions.bottom": "Em baixo",
1243
+ sidebarItemLabel: "R\xF3tulo",
1244
+ sidebarIcon: "\xCDcone",
1245
+ noIcon: "Sem \xEDcone",
1246
+ "icons.text": "Texto",
1247
+ "icons.tag": "Etiqueta",
1248
+ "icons.seo": "SEO",
1249
+ "icons.settings": "Defini\xE7\xF5es",
1250
+ "icons.image": "Imagem",
1251
+ "icons.link": "Liga\xE7\xE3o",
1252
+ "icons.palette": "Paleta",
1253
+ "icons.list": "Lista",
1254
+ "icons.globe": "Globo",
1255
+ "icons.info": "Informa\xE7\xE3o",
1256
+ sidebarOpens: "Abre como",
1257
+ "opens.drawer": "Gaveta",
1258
+ "opens.modal": "Modal",
1259
+ moveUp: "Mover para cima",
1260
+ moveDown: "Mover para baixo",
1261
+ sidebarRemove: "Remover item",
1262
+ sidebarFields: "Campos",
1263
+ sidebarInvalid: "D\xEA um r\xF3tulo ao item (at\xE9 40 caracteres, sem < ou >) e escolha pelo menos um campo.",
1264
+ sidebarInvalidSave: "Corrija os itens da barra lateral assinalados acima antes de guardar.",
1265
+ sidebarAdd: "Adicionar item",
1266
+ sidebarLimit: "At\xE9 {max} itens.",
1267
+ restoreProject: "Repor predefini\xE7\xF5es do projeto",
1268
+ resetAll: "Repor predefini\xE7\xF5es",
1269
+ restoreProjectConfirm: "Descartar todas as defini\xE7\xF5es guardadas nesta p\xE1gina e voltar \xE0s predefini\xE7\xF5es do projeto definidas no c\xF3digo?",
1270
+ resetAllConfirm: "Descartar todas as defini\xE7\xF5es guardadas nesta p\xE1gina e voltar \xE0s predefini\xE7\xF5es?",
1271
+ restored: "Predefini\xE7\xF5es repostas.",
1272
+ blockPreview: "Pr\xE9-visualiza\xE7\xE3o ao vivo do bloco",
1273
+ livePreviewFailed: "Pr\xE9-visualiza\xE7\xE3o ao vivo indispon\xEDvel, a mostrar a imagem.",
1274
+ blockPreviewUrl: "URL de pr\xE9-visualiza\xE7\xE3o do bloco",
1275
+ blockPreviewUrlHelp: "Opcional. Uma p\xE1gina do seu frontend que renderiza um bloco isolado, mostrada em grande ao clicar num cart\xE3o da galeria, por exemplo http://localhost:3026/pt-PT/block-preview/'{name}'/'{variant}'. Marcadores: '{uid}', '{name}' (depois do ponto), '{category}' (antes do ponto), '{variant}' (default), '{locale}'. Vazio usa a rota de pr\xE9-visualiza\xE7\xE3o de p\xE1gina quando existir, caso contr\xE1rio a imagem.",
1276
+ rowActions: "A\xE7\xF5es de {label}",
1277
+ selectBlock: "Selecionar {label}",
1278
+ hiddenBadge: "Oculto",
1279
+ hideBlock: "Ocultar {label} no site",
1280
+ showBlock: "Mostrar {label} no site",
1281
+ duplicateBlock: "Duplicar {label}",
1282
+ copyBlock: "Copiar {label}",
1283
+ confirmDeleteTitle: "Eliminar bloco",
1284
+ confirmDelete: "Eliminar o bloco {name}?",
1285
+ confirmDeleteMarker: "Este \xE9 um marcador de grupo: s\xF3 o marcador \xE9 removido, n\xE3o os blocos dentro do grupo, e o grupo fica desequilibrado at\xE9 ser corrigido.",
1286
+ delete: "Eliminar",
1287
+ selectMode: "Selecionar blocos para copiar",
1288
+ copySelected: "Copiar selecionados ({count})",
1289
+ paste: "Colar {count, plural, one {# bloco} other {# blocos}}",
1290
+ pasted: "{count, plural, one {# bloco colado} other {# blocos colados}}.",
1291
+ copied: "{count, plural, one {# bloco copiado} other {# blocos copiados}}. Cole em qualquer p\xE1gina deste site.",
1292
+ copyFailed: "N\xE3o foi poss\xEDvel copiar: o armazenamento do navegador est\xE1 cheio ou bloqueado.",
1293
+ pasteNotAllowed: 'Nada foi colado: "{uid}" n\xE3o \xE9 permitido nesta zona.',
1294
+ pasteFull: "Nada foi colado: a zona n\xE3o tem espa\xE7o para mais {count} blocos.",
1295
+ pasteUnbalanced: "Nada foi colado: os blocos copiados cont\xEAm um grupo incompleto.",
1296
+ rowActionFailed: "N\xE3o foi poss\xEDvel concluir a a\xE7\xE3o; o formul\xE1rio n\xE3o foi alterado.",
1297
+ rowActionsTitle: "A\xE7\xF5es dos blocos",
1298
+ confirmDeleteOption: "Confirmar antes de eliminar um bloco",
1299
+ duplicateOption: 'Mostrar "Duplicar" em cada bloco',
1300
+ clipboardOption: 'Mostrar "Copiar" e "Colar" (entre p\xE1ginas)',
1301
+ hiddenBlocks: "Blocos ocultos no site",
1302
+ "hiddenModes.strip": "Removidos da API de conte\xFAdo",
1303
+ "hiddenModes.flag": "Enviados com a marca\xE7\xE3o (o frontend decide)",
1304
+ "hiddenModes.off": "Desligado (sem bot\xE3o de ocultar)",
1305
+ hiddenBlocksHelp: "O olho em cada bloco marca-o como oculto no site. Removidos: as leituras da API de conte\xFAdo (REST, GraphQL) deixam-no de fora; o admin mostra-o sempre. Marca\xE7\xE3o: \xE9 enviado com o atributo para o frontend decidir.",
1306
+ hiddenAttributeOff: "A configura\xE7\xE3o do plugin define hiddenAttribute como false: n\xE3o \xE9 poss\xEDvel ocultar blocos."
589
1307
  };
590
1308
 
591
1309
  // admin/translations/fr.json
@@ -630,10 +1348,11 @@ var fr_default = {
630
1348
  "templates.faq": "FAQ",
631
1349
  brokenMedia: "Le m\xE9dia choisi n'existe plus ; la source suivante est utilis\xE9e.",
632
1350
  editor: "Pr\xE9f\xE9rences de l'\xE9diteur",
633
- enabled: "Am\xE9liorations de l'\xE9diteur activ\xE9es",
634
- enabledHelp: "D\xE9sactiv\xE9 r\xE9tablit le s\xE9lecteur et les accord\xE9ons natifs. Cette page reste disponible pour les r\xE9activer.",
1351
+ enabled: "Blockscene activ\xE9",
1352
+ enabledHelp: "D\xE9sactiv\xE9, tout le plugin s'arr\xEAte aussit\xF4t, sans red\xE9marrage : chaque vue d'\xE9dition redevient native et la v\xE9rification des groupes \xE0 la publication s'arr\xEAte. \xC0 utiliser si quelque chose se passe mal en production.",
635
1353
  showOpenAll: "Afficher \xAB Ouvrir tous les blocs \xBB",
636
1354
  showCloseAll: "Afficher \xAB Fermer tous les blocs \xBB",
1355
+ showRowThumbnails: "Afficher les miniatures des blocs dans les lignes",
637
1356
  initialState: "\xC9tat initial des accord\xE9ons",
638
1357
  "states.closed": "Tous ferm\xE9s",
639
1358
  "states.open": "Tous ouverts",
@@ -645,13 +1364,20 @@ var fr_default = {
645
1364
  "modes.form": "Champs",
646
1365
  "modes.split": "Champs + page",
647
1366
  "modes.preview": "\xC9diteur visuel",
648
- previewModeHelp: "S'applique \xE0 l'ouverture d'un document ; chaque \xE9diteur peut changer de mode et le navigateur retient le dernier choix.",
1367
+ previewModeHelp: "Chaque vue d'\xE9dition s'ouvre dans ce mode, sauf si son type de contenu d\xE9finit le sien ci-dessous. L'\xE9diteur peut changer pendant l'\xE9dition ; ce choix n'est pas m\xE9moris\xE9.",
1368
+ lazyEdit: "Cliquer pour modifier",
1369
+ lazyEditors: "Charger les \xE9diteurs de texte riche \xE0 la demande",
1370
+ lazyFields: "Champs personnalis\xE9s charg\xE9s \xE0 la demande",
1371
+ lazyEditorsHelp: "Les champs personnalis\xE9s list\xE9s affichent un aper\xE7u en lecture seule jusqu'au clic ou au focus, pour que les grandes pages s'ouvrent vite. S\xE9parez les uids par des virgules.",
1372
+ lazyFieldsInvalid: "Utilisez jusqu'\xE0 {max} uids de champs personnalis\xE9s, comme plugin::ckeditor5.CKEditor ou global::mon-champ.",
649
1373
  previewV4: "L'aper\xE7u de page enti\xE8re n'existe que dans la distribution Strapi 5 ; ces options n'ont aucun effet sur Strapi 4.",
650
1374
  modeForm: "Champs",
651
1375
  modeSplit: "Champs + page",
652
1376
  modePreview: "\xC9diteur visuel",
653
1377
  previewPane: "Aper\xE7u de la page",
654
1378
  resize: "Redimensionner le panneau d'aper\xE7u",
1379
+ resizeHint: "Faites glisser pour redimensionner ; double-clic pour revenir \xE0 la moiti\xE9",
1380
+ scrollToHovered: "Faire d\xE9filer jusqu\u2019au bloc en surbrillance",
655
1381
  previewHelp: "Les modifications non enregistr\xE9es apparaissent dans l'aper\xE7u. Cliquez sur un bloc pour ouvrir son formulaire, sur une image pour ouvrir la M\xE9diath\xE8que, sur un texte pour le modifier.",
656
1382
  previewNoUrl: "Aucune URL d'aper\xE7u : d\xE9finissez-la dans Param\xE8tres, Blockscene, ou configurez l'aper\xE7u natif de Strapi pour ce type de contenu.",
657
1383
  previewFailed: "L'aper\xE7u n'a pas r\xE9pondu en 15 s. Si le frontend est encore en train de charger, il appara\xEEtra de lui-m\xEAme ; le formulaire reste utilisable.",
@@ -688,19 +1414,137 @@ var fr_default = {
688
1414
  diagInsertClose: "Ins\xE9rer la fermeture \xAB {expected} \xBB manquante apr\xE8s la ligne {n}",
689
1415
  diagStrayHint: "Supprimez le marqueur orphelin avec l'action de suppression de la ligne, ou d\xE9placez-le apr\xE8s une ouverture.",
690
1416
  modeGroup: "Mode d'\xE9dition",
1417
+ undo: "Annuler",
1418
+ redo: "R\xE9tablir",
1419
+ historyGroup: "Historique des modifications",
691
1420
  saveDisabledHint: "Aucune modification \xE0 enregistrer.",
692
1421
  publishDisabledHint: "Publication indisponible : enregistrez d'abord, corrigez les erreurs du formulaire ou v\xE9rifiez vos droits.",
693
1422
  countAll: "{count} blocs",
694
1423
  countFiltered: "{shown} sur {total}",
695
- filterCategory: "Filtrer par cat\xE9gorie",
696
- allCategories: "Toutes les cat\xE9gories",
697
1424
  showFields: "Champs",
698
1425
  showFieldsHint: "Afficher la liste des champs sur chaque bloc",
699
1426
  columns: "Colonnes",
700
1427
  noPreview: "pas d\u2019aper\xE7u",
701
1428
  "templates.none": "Pas d\u2019aper\xE7u",
702
1429
  noChanges: "Aucune modification en attente",
703
- rowPreview: "Ouvrir l\u2019aper\xE7u de {label}"
1430
+ rowPreview: "Ouvrir l\u2019aper\xE7u de {label}",
1431
+ deviceGroup: "Largeur de l'aper\xE7u",
1432
+ "device.fit": "Ajuster",
1433
+ "device.mobile": "Mobile",
1434
+ "device.tablet": "Tablette",
1435
+ "device.desktop": "Ordinateur",
1436
+ contentTypes: "Types de contenu",
1437
+ contentTypesHelp: "La galerie, les accord\xE9ons et l'aper\xE7u de page fonctionnent sur ces types de contenu (ceux qui ont une Dynamic Zone). D\xE9sactivez-en un pour garder son \xE9dition native. \xAB S'ouvre en \xBB d\xE9finit le mode de d\xE9part de chaque vue d'\xE9dition de ce type ; l'\xE9diteur peut toujours changer pendant l'\xE9dition.",
1438
+ typeMode: "S'ouvre en",
1439
+ modeDefault: "Par d\xE9faut ({mode})",
1440
+ blockModalDone: "Termin\xE9",
1441
+ crashed: "Blockscene s'est arr\xEAt\xE9 ici \xE0 cause d'une erreur. L'\xE9diteur natif n'est pas touch\xE9. Si cela se reproduit, d\xE9sactivez Blockscene dans Settings, Blockscene.",
1442
+ sidebarLabel: "Champs de la page",
1443
+ "nav.all": "Tous",
1444
+ "nav.recent": "Utilis\xE9s r\xE9cemment",
1445
+ "nav.starred": "Favoris",
1446
+ typologiesTitle: "Typologies",
1447
+ collapseSidebar: "R\xE9duire la barre lat\xE9rale",
1448
+ expandSidebar: "D\xE9plier la barre lat\xE9rale",
1449
+ "filters.tags": "\xC9tiquettes",
1450
+ "filters.media": "M\xE9dias",
1451
+ "filters.content": "Contenu",
1452
+ clearAll: "Tout effacer",
1453
+ removeFilter: "Retirer le filtre {name}",
1454
+ "typologies.hero": "Hero",
1455
+ "typologies.text": "Texte",
1456
+ "typologies.media": "M\xE9dias",
1457
+ "typologies.listing": "Liste d'\xE9l\xE9ments",
1458
+ "typologies.cards": "Cartes",
1459
+ "typologies.cta": "Appel \xE0 l'action",
1460
+ "typologies.form": "Formulaire",
1461
+ "typologies.layout": "Mise en page",
1462
+ "facets.image": "Image",
1463
+ "facets.video": "Vid\xE9o",
1464
+ "facets.gallery": "Galerie",
1465
+ "facets.richtext": "Texte riche",
1466
+ "facets.list": "Liste",
1467
+ "facets.dynamic": "Dynamique",
1468
+ "facets.form": "Formulaire",
1469
+ star: "Ajouter aux favoris",
1470
+ unstar: "Retirer des favoris",
1471
+ quickInsert: "Ins\xE9rer {label}",
1472
+ insert: "Ins\xE9rer",
1473
+ closeDetail: "Fermer les d\xE9tails",
1474
+ typology: "Typologie",
1475
+ typologyAuto: "Automatique ({name})",
1476
+ tags: "\xC9tiquettes",
1477
+ tagsHelp: "S\xE9par\xE9es par des virgules, jusqu'\xE0 10 \xE9tiquettes de 24 caract\xE8res. Elles deviennent un filtre de la galerie.",
1478
+ sidebarTitle: "Barre lat\xE9rale de l'\xE9diteur visuel : {name}",
1479
+ sidebarHelp: "Des boutons sur un bord de l'\xE9diteur visuel. Chacun ouvre certains champs du document dans un tiroir ou une fen\xEAtre modale.",
1480
+ sidebarPosition: "Position de la barre lat\xE9rale",
1481
+ "positions.left": "Gauche",
1482
+ "positions.right": "Droite",
1483
+ "positions.bottom": "En bas",
1484
+ sidebarItemLabel: "Libell\xE9",
1485
+ sidebarIcon: "Ic\xF4ne",
1486
+ noIcon: "Aucune ic\xF4ne",
1487
+ "icons.text": "Texte",
1488
+ "icons.tag": "\xC9tiquette",
1489
+ "icons.seo": "SEO",
1490
+ "icons.settings": "Param\xE8tres",
1491
+ "icons.image": "Image",
1492
+ "icons.link": "Lien",
1493
+ "icons.palette": "Palette",
1494
+ "icons.list": "Liste",
1495
+ "icons.globe": "Globe",
1496
+ "icons.info": "Info",
1497
+ sidebarOpens: "S'ouvre en",
1498
+ "opens.drawer": "Tiroir",
1499
+ "opens.modal": "Fen\xEAtre modale",
1500
+ moveUp: "Monter",
1501
+ moveDown: "Descendre",
1502
+ sidebarRemove: "Supprimer l'\xE9l\xE9ment",
1503
+ sidebarFields: "Champs",
1504
+ sidebarInvalid: "Donnez un libell\xE9 \xE0 l'\xE9l\xE9ment (40 caract\xE8res maximum, sans < ni >) et choisissez au moins un champ.",
1505
+ sidebarInvalidSave: "Corrigez les \xE9l\xE9ments de barre lat\xE9rale signal\xE9s ci-dessus avant d'enregistrer.",
1506
+ sidebarAdd: "Ajouter un \xE9l\xE9ment",
1507
+ sidebarLimit: "{max} \xE9l\xE9ments maximum.",
1508
+ restoreProject: "R\xE9tablir les valeurs du projet",
1509
+ resetAll: "R\xE9tablir les valeurs par d\xE9faut",
1510
+ restoreProjectConfirm: "Abandonner tous les r\xE9glages enregistr\xE9s sur cette page et revenir aux valeurs du projet d\xE9finies dans le code ?",
1511
+ resetAllConfirm: "Abandonner tous les r\xE9glages enregistr\xE9s sur cette page et revenir aux valeurs par d\xE9faut ?",
1512
+ restored: "Valeurs r\xE9tablies.",
1513
+ blockPreview: "Aper\xE7u en direct du bloc",
1514
+ livePreviewFailed: "Aper\xE7u en direct indisponible, l'image est affich\xE9e.",
1515
+ blockPreviewUrl: "URL d'aper\xE7u de bloc",
1516
+ blockPreviewUrlHelp: "Facultatif. Une page de votre frontend qui affiche un seul bloc, montr\xE9e en grand quand on clique sur une carte de la galerie, par ex. http://localhost:3026/fr/block-preview/'{name}'/'{variant}'. Variables : '{uid}', '{name}' (apr\xE8s le point), '{category}' (avant le point), '{variant}' (default), '{locale}'. Vide : la route d'aper\xE7u de page si elle existe, sinon l'image.",
1517
+ rowActions: "Actions pour {label}",
1518
+ selectBlock: "S\xE9lectionner {label}",
1519
+ hiddenBadge: "Masqu\xE9",
1520
+ hideBlock: "Masquer {label} sur le site",
1521
+ showBlock: "Afficher {label} sur le site",
1522
+ duplicateBlock: "Dupliquer {label}",
1523
+ copyBlock: "Copier {label}",
1524
+ confirmDeleteTitle: "Supprimer le bloc",
1525
+ confirmDelete: "Supprimer le bloc {name} ?",
1526
+ confirmDeleteMarker: "C'est un marqueur de groupe : seul le marqueur est supprim\xE9, pas les blocs du groupe, et le groupe reste d\xE9s\xE9quilibr\xE9 jusqu'\xE0 sa correction.",
1527
+ delete: "Supprimer",
1528
+ selectMode: "S\xE9lectionner des blocs \xE0 copier",
1529
+ copySelected: "Copier la s\xE9lection ({count})",
1530
+ paste: "Coller {count, plural, one {# bloc} other {# blocs}}",
1531
+ pasted: "{count, plural, one {# bloc coll\xE9} other {# blocs coll\xE9s}}.",
1532
+ copied: "{count, plural, one {# bloc copi\xE9} other {# blocs copi\xE9s}}. Collez-les sur n'importe quelle page de ce site.",
1533
+ copyFailed: "Copie impossible : le stockage du navigateur est plein ou bloqu\xE9.",
1534
+ pasteNotAllowed: "Rien n'a \xE9t\xE9 coll\xE9 : \xAB {uid} \xBB n'est pas autoris\xE9 dans cette zone.",
1535
+ pasteFull: "Rien n'a \xE9t\xE9 coll\xE9 : la zone n'a pas de place pour {count} blocs de plus.",
1536
+ pasteUnbalanced: "Rien n'a \xE9t\xE9 coll\xE9 : les blocs copi\xE9s contiennent un groupe incomplet.",
1537
+ rowActionFailed: "L'action n'a pas abouti ; le formulaire n'a pas chang\xE9.",
1538
+ rowActionsTitle: "Actions des blocs",
1539
+ confirmDeleteOption: "Confirmer avant de supprimer un bloc",
1540
+ duplicateOption: "Afficher \xAB Dupliquer \xBB sur chaque bloc",
1541
+ clipboardOption: "Afficher \xAB Copier \xBB et \xAB Coller \xBB (entre pages)",
1542
+ hiddenBlocks: "Blocs masqu\xE9s sur le site",
1543
+ "hiddenModes.strip": "Retir\xE9s de l'API de contenu",
1544
+ "hiddenModes.flag": "Envoy\xE9s avec l'indicateur (le frontend d\xE9cide)",
1545
+ "hiddenModes.off": "D\xE9sactiv\xE9 (pas de bouton masquer)",
1546
+ hiddenBlocksHelp: "L'\u0153il de chaque bloc le marque comme masqu\xE9 sur le site. Retir\xE9s : les lectures de l'API de contenu (REST, GraphQL) l'omettent, l'admin l'affiche toujours. Indicateur : il est envoy\xE9 avec l'attribut et le frontend d\xE9cide.",
1547
+ hiddenAttributeOff: "La configuration du plugin fixe hiddenAttribute \xE0 false : les blocs ne peuvent pas \xEAtre masqu\xE9s."
704
1548
  };
705
1549
 
706
1550
  // admin/translations/es.json
@@ -745,10 +1589,11 @@ var es_default = {
745
1589
  "templates.faq": "FAQ",
746
1590
  brokenMedia: "El medio elegido ya no existe; se usa el siguiente origen.",
747
1591
  editor: "Preferencias del editor",
748
- enabled: "Mejoras del editor activadas",
749
- enabledHelp: "Desactivado restaura el selector y los acordeones nativos. Esta p\xE1gina sigue disponible para reactivarlas.",
1592
+ enabled: "Blockscene activado",
1593
+ enabledHelp: "Desactivado, todo el plugin se detiene al instante, sin reinicio: cada vista de edici\xF3n vuelve a ser nativa y se detiene la verificaci\xF3n de grupos al publicar. \xDAsalo si algo falla en producci\xF3n.",
750
1594
  showOpenAll: "Mostrar \xABAbrir todos los bloques\xBB",
751
1595
  showCloseAll: "Mostrar \xABCerrar todos los bloques\xBB",
1596
+ showRowThumbnails: "Mostrar miniaturas de los bloques en las filas",
752
1597
  initialState: "Estado inicial de los acordeones",
753
1598
  "states.closed": "Todos cerrados",
754
1599
  "states.open": "Todos abiertos",
@@ -760,13 +1605,20 @@ var es_default = {
760
1605
  "modes.form": "Campos",
761
1606
  "modes.split": "Campos + p\xE1gina",
762
1607
  "modes.preview": "Editor visual",
763
- previewModeHelp: "Se aplica al abrir un documento; cada editor puede cambiar de modo y el navegador recuerda la \xFAltima elecci\xF3n.",
1608
+ previewModeHelp: "Cada vista de edici\xF3n se abre en este modo, salvo que su tipo de contenido defina el suyo abajo. Quien edita puede cambiarlo mientras edita; el cambio no se recuerda.",
1609
+ lazyEdit: "Haz clic para editar",
1610
+ lazyEditors: "Cargar los editores de texto enriquecido bajo demanda",
1611
+ lazyFields: "Campos personalizados cargados bajo demanda",
1612
+ lazyEditorsHelp: "Los campos personalizados de la lista muestran una vista previa de solo lectura hasta recibir un clic o el foco, para que las p\xE1ginas grandes abran r\xE1pido. Separa los uids con comas.",
1613
+ lazyFieldsInvalid: "Usa hasta {max} uids de campos personalizados, como plugin::ckeditor5.CKEditor o global::mi-campo.",
764
1614
  previewV4: "La vista previa de p\xE1gina completa solo existe en la distribuci\xF3n Strapi 5; estas opciones no tienen efecto en Strapi 4.",
765
1615
  modeForm: "Campos",
766
1616
  modeSplit: "Campos + p\xE1gina",
767
1617
  modePreview: "Editor visual",
768
1618
  previewPane: "Vista previa de la p\xE1gina",
769
1619
  resize: "Redimensionar el panel de vista previa",
1620
+ resizeHint: "Arrastra para redimensionar; doble clic vuelve a la mitad",
1621
+ scrollToHovered: "Desplazarse al bloque resaltado",
770
1622
  previewHelp: "Los cambios sin guardar aparecen en la vista previa. Haga clic en un bloque para abrir su formulario, en una imagen para abrir la Biblioteca multimedia, en un texto para editarlo.",
771
1623
  previewNoUrl: "Sin URL de vista previa: def\xEDnala en Ajustes, Blockscene, o configure la Vista previa nativa de Strapi para este tipo de contenido.",
772
1624
  previewFailed: "La vista previa no respondi\xF3 en 15 s. Si el frontend a\xFAn est\xE1 cargando, aparecer\xE1 sola; el formulario sigue utilizable.",
@@ -803,19 +1655,137 @@ var es_default = {
803
1655
  diagInsertClose: "Insertar el cierre \xAB{expected}\xBB que falta despu\xE9s de la fila {n}",
804
1656
  diagStrayHint: "Elimine el marcador hu\xE9rfano con la acci\xF3n de borrar de la propia fila, o mu\xE9valo despu\xE9s de una apertura.",
805
1657
  modeGroup: "Modo de edici\xF3n",
1658
+ undo: "Deshacer",
1659
+ redo: "Rehacer",
1660
+ historyGroup: "Historial de cambios",
806
1661
  saveDisabledHint: "No hay cambios que guardar.",
807
1662
  publishDisabledHint: "Publicaci\xF3n no disponible: guarde antes, corrija los errores del formulario o compruebe sus permisos.",
808
1663
  countAll: "{count} bloques",
809
1664
  countFiltered: "{shown} de {total}",
810
- filterCategory: "Filtrar por categor\xEDa",
811
- allCategories: "Todas las categor\xEDas",
812
1665
  showFields: "Campos",
813
1666
  showFieldsHint: "Mostrar la lista de campos en cada bloque",
814
1667
  columns: "Columnas",
815
1668
  noPreview: "sin vista previa",
816
1669
  "templates.none": "Sin vista previa",
817
1670
  noChanges: "Sin cambios pendientes",
818
- rowPreview: "Abrir la vista previa de {label}"
1671
+ rowPreview: "Abrir la vista previa de {label}",
1672
+ deviceGroup: "Ancho de la vista previa",
1673
+ "device.fit": "Ajustar",
1674
+ "device.mobile": "M\xF3vil",
1675
+ "device.tablet": "Tableta",
1676
+ "device.desktop": "Escritorio",
1677
+ contentTypes: "Tipos de contenido",
1678
+ contentTypesHelp: 'La galer\xEDa, los acordeones y la vista previa de p\xE1gina funcionan en estos tipos de contenido (los que tienen una Dynamic Zone). Desactiva uno para dejar su edici\xF3n nativa. "Se abre en" define c\xF3mo empieza cada vista de edici\xF3n de ese tipo; quien edita puede cambiarlo mientras edita.',
1679
+ typeMode: "Se abre en",
1680
+ modeDefault: "Predeterminado ({mode})",
1681
+ blockModalDone: "Listo",
1682
+ crashed: "Blockscene se detuvo aqu\xED por un error. El editor nativo no se ve afectado. Si vuelve a pasar, desactiva Blockscene en Settings, Blockscene.",
1683
+ sidebarLabel: "Campos de la p\xE1gina",
1684
+ "nav.all": "Todos",
1685
+ "nav.recent": "Usados recientemente",
1686
+ "nav.starred": "Favoritos",
1687
+ typologiesTitle: "Tipolog\xEDas",
1688
+ collapseSidebar: "Contraer barra lateral",
1689
+ expandSidebar: "Expandir barra lateral",
1690
+ "filters.tags": "Etiquetas",
1691
+ "filters.media": "Medios",
1692
+ "filters.content": "Contenido",
1693
+ clearAll: "Borrar todo",
1694
+ removeFilter: "Quitar filtro {name}",
1695
+ "typologies.hero": "Hero",
1696
+ "typologies.text": "Texto",
1697
+ "typologies.media": "Medios",
1698
+ "typologies.listing": "Listado",
1699
+ "typologies.cards": "Tarjetas",
1700
+ "typologies.cta": "Llamada a la acci\xF3n",
1701
+ "typologies.form": "Formulario",
1702
+ "typologies.layout": "Dise\xF1o",
1703
+ "facets.image": "Imagen",
1704
+ "facets.video": "V\xEDdeo",
1705
+ "facets.gallery": "Galer\xEDa",
1706
+ "facets.richtext": "Texto enriquecido",
1707
+ "facets.list": "Lista",
1708
+ "facets.dynamic": "Din\xE1mico",
1709
+ "facets.form": "Formulario",
1710
+ star: "Marcar como favorito",
1711
+ unstar: "Quitar de favoritos",
1712
+ quickInsert: "Insertar {label}",
1713
+ insert: "Insertar",
1714
+ closeDetail: "Cerrar detalles",
1715
+ typology: "Tipolog\xEDa",
1716
+ typologyAuto: "Autom\xE1tica ({name})",
1717
+ tags: "Etiquetas",
1718
+ tagsHelp: "Separadas por comas, hasta 10 etiquetas de 24 caracteres. Se convierten en un filtro de la galer\xEDa.",
1719
+ sidebarTitle: "Barra lateral del editor visual: {name}",
1720
+ sidebarHelp: "Botones en un borde del editor visual. Cada uno abre algunos campos del propio documento en un caj\xF3n o un modal.",
1721
+ sidebarPosition: "Posici\xF3n de la barra lateral",
1722
+ "positions.left": "Izquierda",
1723
+ "positions.right": "Derecha",
1724
+ "positions.bottom": "Abajo",
1725
+ sidebarItemLabel: "Etiqueta",
1726
+ sidebarIcon: "Icono",
1727
+ noIcon: "Sin icono",
1728
+ "icons.text": "Texto",
1729
+ "icons.tag": "Etiqueta",
1730
+ "icons.seo": "SEO",
1731
+ "icons.settings": "Ajustes",
1732
+ "icons.image": "Imagen",
1733
+ "icons.link": "Enlace",
1734
+ "icons.palette": "Paleta",
1735
+ "icons.list": "Lista",
1736
+ "icons.globe": "Globo",
1737
+ "icons.info": "Informaci\xF3n",
1738
+ sidebarOpens: "Se abre como",
1739
+ "opens.drawer": "Caj\xF3n",
1740
+ "opens.modal": "Modal",
1741
+ moveUp: "Subir",
1742
+ moveDown: "Bajar",
1743
+ sidebarRemove: "Quitar elemento",
1744
+ sidebarFields: "Campos",
1745
+ sidebarInvalid: "Pon una etiqueta al elemento (hasta 40 caracteres, sin < ni >) y elige al menos un campo.",
1746
+ sidebarInvalidSave: "Corrige los elementos de la barra lateral marcados arriba antes de guardar.",
1747
+ sidebarAdd: "A\xF1adir elemento",
1748
+ sidebarLimit: "Hasta {max} elementos.",
1749
+ restoreProject: "Restaurar valores del proyecto",
1750
+ resetAll: "Restablecer valores predeterminados",
1751
+ restoreProjectConfirm: "\xBFDescartar todos los ajustes guardados en esta p\xE1gina y volver a los valores del proyecto definidos en el c\xF3digo?",
1752
+ resetAllConfirm: "\xBFDescartar todos los ajustes guardados en esta p\xE1gina y volver a los valores predeterminados?",
1753
+ restored: "Valores restablecidos.",
1754
+ blockPreview: "Vista previa en vivo del bloque",
1755
+ livePreviewFailed: "Vista previa en vivo no disponible, se muestra la imagen.",
1756
+ blockPreviewUrl: "URL de vista previa del bloque",
1757
+ blockPreviewUrlHelp: "Opcional. Una p\xE1gina de su frontend que renderiza un solo bloque, mostrada en grande al hacer clic en una tarjeta de la galer\xEDa, p. ej. http://localhost:3026/es/block-preview/'{name}'/'{variant}'. Marcadores: '{uid}', '{name}' (tras el punto), '{category}' (antes del punto), '{variant}' (default), '{locale}'. Vac\xEDa usa la ruta de vista previa de p\xE1gina si existe; si no, la imagen.",
1758
+ rowActions: "Acciones de {label}",
1759
+ selectBlock: "Seleccionar {label}",
1760
+ hiddenBadge: "Oculto",
1761
+ hideBlock: "Ocultar {label} en el sitio",
1762
+ showBlock: "Mostrar {label} en el sitio",
1763
+ duplicateBlock: "Duplicar {label}",
1764
+ copyBlock: "Copiar {label}",
1765
+ confirmDeleteTitle: "Eliminar bloque",
1766
+ confirmDelete: "\xBFEliminar el bloque {name}?",
1767
+ confirmDeleteMarker: "Es un marcador de grupo: solo se elimina el marcador, no los bloques del grupo, y el grupo queda desequilibrado hasta corregirlo.",
1768
+ delete: "Eliminar",
1769
+ selectMode: "Seleccionar bloques para copiar",
1770
+ copySelected: "Copiar seleccionados ({count})",
1771
+ paste: "Pegar {count, plural, one {# bloque} other {# bloques}}",
1772
+ pasted: "{count, plural, one {# bloque pegado} other {# bloques pegados}}.",
1773
+ copied: "{count, plural, one {# bloque copiado} other {# bloques copiados}}. P\xE9galos en cualquier p\xE1gina de este sitio.",
1774
+ copyFailed: "No se pudo copiar: el almacenamiento del navegador est\xE1 lleno o bloqueado.",
1775
+ pasteNotAllowed: "No se peg\xF3 nada: \xAB{uid}\xBB no est\xE1 permitido en esta zona.",
1776
+ pasteFull: "No se peg\xF3 nada: la zona no tiene espacio para {count} bloques m\xE1s.",
1777
+ pasteUnbalanced: "No se peg\xF3 nada: los bloques copiados contienen un grupo incompleto.",
1778
+ rowActionFailed: "No se pudo completar la acci\xF3n; el formulario no cambi\xF3.",
1779
+ rowActionsTitle: "Acciones de los bloques",
1780
+ confirmDeleteOption: "Confirmar antes de eliminar un bloque",
1781
+ duplicateOption: "Mostrar \xABDuplicar\xBB en cada bloque",
1782
+ clipboardOption: "Mostrar \xABCopiar\xBB y \xABPegar\xBB (entre p\xE1ginas)",
1783
+ hiddenBlocks: "Bloques ocultos en el sitio",
1784
+ "hiddenModes.strip": "Eliminados de la API de contenido",
1785
+ "hiddenModes.flag": "Enviados con la marca (el frontend decide)",
1786
+ "hiddenModes.off": "Desactivado (sin bot\xF3n de ocultar)",
1787
+ hiddenBlocksHelp: "El ojo de cada bloque lo marca como oculto en el sitio. Eliminados: las lecturas de la API de contenido (REST, GraphQL) lo omiten; el admin siempre lo muestra. Marca: se env\xEDa con el atributo y el frontend decide.",
1788
+ hiddenAttributeOff: "La configuraci\xF3n del plugin fija hiddenAttribute en false: no se pueden ocultar bloques."
819
1789
  };
820
1790
 
821
1791
  // admin/translations/de.json
@@ -860,10 +1830,11 @@ var de_default = {
860
1830
  "templates.faq": "FAQ",
861
1831
  brokenMedia: "Das gew\xE4hlte Medium existiert nicht mehr; die n\xE4chste Quelle wird verwendet.",
862
1832
  editor: "Editor-Einstellungen",
863
- enabled: "Editor-Erweiterungen aktiv",
864
- enabledHelp: "Aus stellt die native Auswahl und die Akkordeons wieder her. Diese Seite bleibt verf\xFCgbar, um sie wieder einzuschalten.",
1833
+ enabled: "Blockscene aktiv",
1834
+ enabledHelp: "Aus schaltet das ganze Plugin sofort ab, ohne Neustart: jede Bearbeitungsansicht ist wieder nativ und die Gruppenpr\xFCfung beim Ver\xF6ffentlichen stoppt. F\xFCr den Fall, dass in Produktion etwas schiefgeht.",
865
1835
  showOpenAll: "\u201EAlle Bl\xF6cke \xF6ffnen\u201C anzeigen",
866
1836
  showCloseAll: "\u201EAlle Bl\xF6cke schlie\xDFen\u201C anzeigen",
1837
+ showRowThumbnails: "Blockvorschaubilder in den Zeilen anzeigen",
867
1838
  initialState: "Anfangszustand der Akkordeons",
868
1839
  "states.closed": "Alle geschlossen",
869
1840
  "states.open": "Alle ge\xF6ffnet",
@@ -875,13 +1846,20 @@ var de_default = {
875
1846
  "modes.form": "Felder",
876
1847
  "modes.split": "Felder + Seite",
877
1848
  "modes.preview": "Visueller Editor",
878
- previewModeHelp: "Gilt beim \xD6ffnen eines Dokuments; jeder Redakteur kann den Modus wechseln und der Browser merkt sich die letzte Wahl.",
1849
+ previewModeHelp: "Jede Bearbeitungsansicht \xF6ffnet in diesem Modus, au\xDFer ihr Inhaltstyp legt unten einen eigenen fest. Beim Bearbeiten l\xE4sst sich wechseln; der Wechsel wird nicht gespeichert.",
1850
+ lazyEdit: "Zum Bearbeiten klicken",
1851
+ lazyEditors: "Rich-Text-Editoren bei Bedarf laden",
1852
+ lazyFields: "Bei Bedarf geladene benutzerdefinierte Felder",
1853
+ lazyEditorsHelp: "Die aufgef\xFChrten benutzerdefinierten Felder zeigen eine schreibgesch\xFCtzte Vorschau, bis sie angeklickt oder fokussiert werden, damit gro\xDFe Seiten schnell \xF6ffnen. Uids durch Kommas trennen.",
1854
+ lazyFieldsInvalid: "Bis zu {max} Uids benutzerdefinierter Felder verwenden, z. B. plugin::ckeditor5.CKEditor oder global::mein-feld.",
879
1855
  previewV4: "Die Ganzseiten-Vorschau gibt es nur in der Strapi-5-Distribution; diese Optionen haben unter Strapi 4 keine Wirkung.",
880
1856
  modeForm: "Felder",
881
1857
  modeSplit: "Felder + Seite",
882
1858
  modePreview: "Visueller Editor",
883
1859
  previewPane: "Seitenvorschau",
884
1860
  resize: "Vorschaubereich anpassen",
1861
+ resizeHint: "Ziehen zum \xC4ndern der Gr\xF6\xDFe; Doppelklick setzt auf die H\xE4lfte zur\xFCck",
1862
+ scrollToHovered: "Zum hervorgehobenen Block scrollen",
885
1863
  previewHelp: "Ungespeicherte \xC4nderungen erscheinen in der Vorschau. Klicken Sie auf einen Block, um sein Formular zu \xF6ffnen, auf ein Bild f\xFCr die Medienbibliothek, auf einen Text, um ihn zu bearbeiten.",
886
1864
  previewNoUrl: "Keine Vorschau-URL: in Einstellungen, Blockscene festlegen oder die native Strapi-Vorschau f\xFCr diesen Inhaltstyp konfigurieren.",
887
1865
  previewFailed: "Die Vorschau hat innerhalb von 15 s nicht geantwortet. L\xE4dt das Frontend noch, erscheint sie von selbst; das Formular bleibt benutzbar.",
@@ -918,19 +1896,137 @@ var de_default = {
918
1896
  diagInsertClose: "Fehlenden Schlie\xDFmarker \u201E{expected}\u201C nach Zeile {n} einf\xFCgen",
919
1897
  diagStrayHint: "Entfernen Sie den verwaisten Marker \xFCber die L\xF6schaktion der Zeile oder verschieben Sie ihn hinter einen \xD6ffnungsmarker.",
920
1898
  modeGroup: "Bearbeitungsmodus",
1899
+ undo: "R\xFCckg\xE4ngig",
1900
+ redo: "Wiederholen",
1901
+ historyGroup: "Bearbeitungsverlauf",
921
1902
  saveDisabledHint: "Keine ungespeicherten \xC4nderungen.",
922
1903
  publishDisabledHint: "Ver\xF6ffentlichen nicht m\xF6glich: zuerst speichern, Formularfehler beheben oder Berechtigungen pr\xFCfen.",
923
1904
  countAll: "{count} Bl\xF6cke",
924
1905
  countFiltered: "{shown} von {total}",
925
- filterCategory: "Nach Kategorie filtern",
926
- allCategories: "Alle Kategorien",
927
1906
  showFields: "Felder",
928
1907
  showFieldsHint: "Feldliste auf jedem Block anzeigen",
929
1908
  columns: "Spalten",
930
1909
  noPreview: "keine Vorschau",
931
1910
  "templates.none": "Keine Vorschau",
932
1911
  noChanges: "Keine ungespeicherten \xC4nderungen",
933
- rowPreview: "Vorschau von {label} \xF6ffnen"
1912
+ rowPreview: "Vorschau von {label} \xF6ffnen",
1913
+ deviceGroup: "Vorschaubreite",
1914
+ "device.fit": "Anpassen",
1915
+ "device.mobile": "Mobil",
1916
+ "device.tablet": "Tablet",
1917
+ "device.desktop": "Desktop",
1918
+ contentTypes: "Inhaltstypen",
1919
+ contentTypesHelp: "Galerie, Akkordeons und Seitenvorschau arbeiten auf diesen Inhaltstypen (denen mit einer Dynamic Zone). Schalte einen aus, um seine Bearbeitung nativ zu lassen. \u201E\xD6ffnet in\u201C bestimmt, wie jede Bearbeitungsansicht dieses Typs startet; beim Bearbeiten l\xE4sst sich weiter wechseln.",
1920
+ typeMode: "\xD6ffnet in",
1921
+ modeDefault: "Standard ({mode})",
1922
+ blockModalDone: "Fertig",
1923
+ crashed: "Blockscene wurde hier wegen eines Fehlers beendet. Der native Editor ist nicht betroffen. Passiert es erneut, schalte Blockscene unter Settings, Blockscene aus.",
1924
+ sidebarLabel: "Seitenfelder",
1925
+ "nav.all": "Alle",
1926
+ "nav.recent": "Zuletzt verwendet",
1927
+ "nav.starred": "Favoriten",
1928
+ typologiesTitle: "Typologien",
1929
+ collapseSidebar: "Seitenleiste einklappen",
1930
+ expandSidebar: "Seitenleiste ausklappen",
1931
+ "filters.tags": "Tags",
1932
+ "filters.media": "Medien",
1933
+ "filters.content": "Inhalt",
1934
+ clearAll: "Alle entfernen",
1935
+ removeFilter: "Filter {name} entfernen",
1936
+ "typologies.hero": "Hero",
1937
+ "typologies.text": "Text",
1938
+ "typologies.media": "Medien",
1939
+ "typologies.listing": "Auflistung",
1940
+ "typologies.cards": "Karten",
1941
+ "typologies.cta": "Call to Action",
1942
+ "typologies.form": "Formular",
1943
+ "typologies.layout": "Layout",
1944
+ "facets.image": "Bild",
1945
+ "facets.video": "Video",
1946
+ "facets.gallery": "Galerie",
1947
+ "facets.richtext": "Rich Text",
1948
+ "facets.list": "Liste",
1949
+ "facets.dynamic": "Dynamisch",
1950
+ "facets.form": "Formular",
1951
+ star: "Als Favorit markieren",
1952
+ unstar: "Aus Favoriten entfernen",
1953
+ quickInsert: "{label} einf\xFCgen",
1954
+ insert: "Einf\xFCgen",
1955
+ closeDetail: "Details schlie\xDFen",
1956
+ typology: "Typologie",
1957
+ typologyAuto: "Automatisch ({name})",
1958
+ tags: "Tags",
1959
+ tagsHelp: "Kommagetrennt, bis zu 10 Tags mit je 24 Zeichen. Sie werden zu einem Filter der Galerie.",
1960
+ sidebarTitle: "Seitenleiste des visuellen Editors: {name}",
1961
+ sidebarHelp: "Schaltfl\xE4chen an einem Rand des visuellen Editors. Jede \xF6ffnet einige Felder des Dokuments in einer Schublade oder einem Dialog.",
1962
+ sidebarPosition: "Position der Seitenleiste",
1963
+ "positions.left": "Links",
1964
+ "positions.right": "Rechts",
1965
+ "positions.bottom": "Unten",
1966
+ sidebarItemLabel: "Beschriftung",
1967
+ sidebarIcon: "Symbol",
1968
+ noIcon: "Kein Symbol",
1969
+ "icons.text": "Text",
1970
+ "icons.tag": "Tag",
1971
+ "icons.seo": "SEO",
1972
+ "icons.settings": "Einstellungen",
1973
+ "icons.image": "Bild",
1974
+ "icons.link": "Link",
1975
+ "icons.palette": "Palette",
1976
+ "icons.list": "Liste",
1977
+ "icons.globe": "Globus",
1978
+ "icons.info": "Info",
1979
+ sidebarOpens: "\xD6ffnet als",
1980
+ "opens.drawer": "Schublade",
1981
+ "opens.modal": "Dialog",
1982
+ moveUp: "Nach oben",
1983
+ moveDown: "Nach unten",
1984
+ sidebarRemove: "Eintrag entfernen",
1985
+ sidebarFields: "Felder",
1986
+ sidebarInvalid: "Gib dem Eintrag eine Beschriftung (bis zu 40 Zeichen, ohne < oder >) und w\xE4hle mindestens ein Feld.",
1987
+ sidebarInvalidSave: "Korrigiere die oben markierten Seitenleisteneintr\xE4ge vor dem Speichern.",
1988
+ sidebarAdd: "Eintrag hinzuf\xFCgen",
1989
+ sidebarLimit: "Bis zu {max} Eintr\xE4ge.",
1990
+ restoreProject: "Projektstandards wiederherstellen",
1991
+ resetAll: "Auf Standard zur\xFCcksetzen",
1992
+ restoreProjectConfirm: "Alle auf dieser Seite gespeicherten Einstellungen verwerfen und zu den Projektstandards aus dem Code zur\xFCckkehren?",
1993
+ resetAllConfirm: "Alle auf dieser Seite gespeicherten Einstellungen verwerfen und zu den Standardwerten zur\xFCckkehren?",
1994
+ restored: "Standards wiederhergestellt.",
1995
+ blockPreview: "Live-Vorschau des Blocks",
1996
+ livePreviewFailed: "Live-Vorschau nicht verf\xFCgbar, das Bild wird angezeigt.",
1997
+ blockPreviewUrl: "Block-Vorschau-URL",
1998
+ blockPreviewUrlHelp: "Optional. Eine Seite Ihres Frontends, die einen einzelnen Block rendert und beim Klick auf eine Galeriekarte gro\xDF angezeigt wird, z. B. http://localhost:3026/de/block-preview/'{name}'/'{variant}'. Platzhalter: '{uid}', '{name}' (nach dem Punkt), '{category}' (vor dem Punkt), '{variant}' (default), '{locale}'. Leer verwendet die Seitenvorschau-Route, falls vorhanden, sonst das Bild.",
1999
+ rowActions: "Aktionen f\xFCr {label}",
2000
+ selectBlock: "{label} ausw\xE4hlen",
2001
+ hiddenBadge: "Ausgeblendet",
2002
+ hideBlock: "{label} auf der Website ausblenden",
2003
+ showBlock: "{label} auf der Website anzeigen",
2004
+ duplicateBlock: "{label} duplizieren",
2005
+ copyBlock: "{label} kopieren",
2006
+ confirmDeleteTitle: "Block l\xF6schen",
2007
+ confirmDelete: "Block {name} l\xF6schen?",
2008
+ confirmDeleteMarker: "Dies ist eine Gruppenmarkierung: Nur die Markierung wird entfernt, nicht die Bl\xF6cke der Gruppe, und die Gruppe bleibt unausgeglichen, bis sie korrigiert ist.",
2009
+ delete: "L\xF6schen",
2010
+ selectMode: "Bl\xF6cke zum Kopieren ausw\xE4hlen",
2011
+ copySelected: "Auswahl kopieren ({count})",
2012
+ paste: "{count, plural, one {# Block} other {# Bl\xF6cke}} einf\xFCgen",
2013
+ pasted: "{count, plural, one {# Block eingef\xFCgt} other {# Bl\xF6cke eingef\xFCgt}}.",
2014
+ copied: "{count, plural, one {# Block kopiert} other {# Bl\xF6cke kopiert}}. Auf jeder Seite dieser Website einf\xFCgbar.",
2015
+ copyFailed: "Kopieren nicht m\xF6glich: Der Browserspeicher ist voll oder gesperrt.",
2016
+ pasteNotAllowed: "Nichts eingef\xFCgt: \u201E{uid}\u201C ist in dieser Zone nicht erlaubt.",
2017
+ pasteFull: "Nichts eingef\xFCgt: Die Zone hat keinen Platz f\xFCr {count} weitere Bl\xF6cke.",
2018
+ pasteUnbalanced: "Nichts eingef\xFCgt: Die kopierten Bl\xF6cke enthalten eine unvollst\xE4ndige Gruppe.",
2019
+ rowActionFailed: "Die Aktion konnte nicht ausgef\xFChrt werden; das Formular wurde nicht ge\xE4ndert.",
2020
+ rowActionsTitle: "Block-Aktionen",
2021
+ confirmDeleteOption: "Vor dem L\xF6schen eines Blocks nachfragen",
2022
+ duplicateOption: "\u201EDuplizieren\u201C an jedem Block anzeigen",
2023
+ clipboardOption: "\u201EKopieren\u201C und \u201EEinf\xFCgen\u201C anzeigen (seiten\xFCbergreifend)",
2024
+ hiddenBlocks: "Auf der Website ausgeblendete Bl\xF6cke",
2025
+ "hiddenModes.strip": "Aus der Content-API entfernt",
2026
+ "hiddenModes.flag": "Mit Markierung gesendet (das Frontend entscheidet)",
2027
+ "hiddenModes.off": "Aus (kein Ausblenden-Knopf)",
2028
+ hiddenBlocksHelp: "Das Auge an jedem Block blendet ihn auf der Website aus. Entfernt: Lesezugriffe der Content-API (REST, GraphQL) lassen ihn weg, der Admin zeigt ihn immer. Markierung: Er wird mit dem Attribut gesendet und das Frontend entscheidet.",
2029
+ hiddenAttributeOff: "Die Plugin-Konfiguration setzt hiddenAttribute auf false: Bl\xF6cke k\xF6nnen nicht ausgeblendet werden."
934
2030
  };
935
2031
 
936
2032
  // admin/translations/it.json
@@ -975,10 +2071,11 @@ var it_default = {
975
2071
  "templates.faq": "FAQ",
976
2072
  brokenMedia: "Il media scelto non esiste pi\xF9; viene usata l'origine successiva.",
977
2073
  editor: "Preferenze dell'editor",
978
- enabled: "Miglioramenti dell'editor attivi",
979
- enabledHelp: "Disattivato ripristina il selettore e le fisarmoniche native. Questa pagina resta disponibile per riattivarli.",
2074
+ enabled: "Blockscene attivo",
2075
+ enabledHelp: "Disattivato, l'intero plugin si ferma subito, senza riavvio: ogni vista di modifica torna nativa e il controllo dei gruppi alla pubblicazione si ferma. Da usare se qualcosa va storto in produzione.",
980
2076
  showOpenAll: 'Mostra "Apri tutti i blocchi"',
981
2077
  showCloseAll: 'Mostra "Chiudi tutti i blocchi"',
2078
+ showRowThumbnails: "Mostra le miniature dei blocchi nelle righe",
982
2079
  initialState: "Stato iniziale delle fisarmoniche",
983
2080
  "states.closed": "Tutte chiuse",
984
2081
  "states.open": "Tutte aperte",
@@ -990,13 +2087,20 @@ var it_default = {
990
2087
  "modes.form": "Campi",
991
2088
  "modes.split": "Campi + pagina",
992
2089
  "modes.preview": "Editor visivo",
993
- previewModeHelp: "Vale all'apertura di un documento; ogni redattore pu\xF2 cambiare modalit\xE0 e il browser ricorda l'ultima scelta.",
2090
+ previewModeHelp: "Ogni vista di modifica si apre in questo modo, a meno che il suo tipo di contenuto ne imposti uno proprio qui sotto. Durante la modifica si pu\xF2 cambiare; la scelta non viene ricordata.",
2091
+ lazyEdit: "Fai clic per modificare",
2092
+ lazyEditors: "Carica gli editor di testo formattato su richiesta",
2093
+ lazyFields: "Campi personalizzati caricati su richiesta",
2094
+ lazyEditorsHelp: "I campi personalizzati elencati mostrano un'anteprima di sola lettura finch\xE9 non ricevono un clic o il focus, cos\xEC le pagine grandi si aprono in fretta. Separa gli uid con virgole.",
2095
+ lazyFieldsInvalid: "Usa fino a {max} uid di campi personalizzati, come plugin::ckeditor5.CKEditor o global::mio-campo.",
994
2096
  previewV4: "L'anteprima dell'intera pagina esiste solo nella distribuzione Strapi 5; queste opzioni non hanno effetto su Strapi 4.",
995
2097
  modeForm: "Campi",
996
2098
  modeSplit: "Campi + pagina",
997
2099
  modePreview: "Editor visivo",
998
2100
  previewPane: "Anteprima della pagina",
999
2101
  resize: "Ridimensiona il pannello di anteprima",
2102
+ resizeHint: "Trascina per ridimensionare; doppio clic per tornare a met\xE0",
2103
+ scrollToHovered: "Scorri fino al blocco evidenziato",
1000
2104
  previewHelp: "Le modifiche non salvate compaiono nell'anteprima. Clicca un blocco per aprire il suo modulo, un'immagine per aprire la Libreria media, un testo per modificarlo.",
1001
2105
  previewNoUrl: "Nessun URL di anteprima: impostalo in Impostazioni, Blockscene, oppure configura l'Anteprima nativa di Strapi per questo tipo di contenuto.",
1002
2106
  previewFailed: "L'anteprima non ha risposto entro 15 s. Se il frontend sta ancora caricando, comparir\xE0 da sola; il modulo resta utilizzabile.",
@@ -1033,19 +2137,137 @@ var it_default = {
1033
2137
  diagInsertClose: 'Inserisci la chiusura "{expected}" mancante dopo la riga {n}',
1034
2138
  diagStrayHint: "Rimuovi il marcatore orfano con l'azione di eliminazione della riga, oppure spostalo dopo un'apertura.",
1035
2139
  modeGroup: "Modalit\xE0 di modifica",
2140
+ undo: "Annulla",
2141
+ redo: "Ripeti",
2142
+ historyGroup: "Cronologia modifiche",
1036
2143
  saveDisabledHint: "Nessuna modifica da salvare.",
1037
2144
  publishDisabledHint: "Pubblicazione non disponibile: salva prima, correggi gli errori del modulo o verifica i permessi.",
1038
2145
  countAll: "{count} blocchi",
1039
2146
  countFiltered: "{shown} di {total}",
1040
- filterCategory: "Filtra per categoria",
1041
- allCategories: "Tutte le categorie",
1042
2147
  showFields: "Campi",
1043
2148
  showFieldsHint: "Mostra l\u2019elenco dei campi su ogni blocco",
1044
2149
  columns: "Colonne",
1045
2150
  noPreview: "nessuna anteprima",
1046
2151
  "templates.none": "Nessuna anteprima",
1047
2152
  noChanges: "Nessuna modifica in sospeso",
1048
- rowPreview: "Apri l\u2019anteprima di {label}"
2153
+ rowPreview: "Apri l\u2019anteprima di {label}",
2154
+ deviceGroup: "Larghezza dell'anteprima",
2155
+ "device.fit": "Adatta",
2156
+ "device.mobile": "Mobile",
2157
+ "device.tablet": "Tablet",
2158
+ "device.desktop": "Desktop",
2159
+ contentTypes: "Tipi di contenuto",
2160
+ contentTypesHelp: "La galleria, le fisarmoniche e l'anteprima di pagina funzionano su questi tipi di contenuto (quelli con una Dynamic Zone). Disattivane uno per lasciarne la modifica nativa. \xABSi apre in\xBB \xE8 il modo in cui parte ogni vista di modifica di quel tipo; durante la modifica si pu\xF2 sempre cambiare.",
2161
+ typeMode: "Si apre in",
2162
+ modeDefault: "Predefinito ({mode})",
2163
+ blockModalDone: "Fatto",
2164
+ crashed: "Blockscene si \xE8 fermato qui a causa di un errore. L'editor nativo non \xE8 toccato. Se succede di nuovo, disattiva Blockscene in Settings, Blockscene.",
2165
+ sidebarLabel: "Campi della pagina",
2166
+ "nav.all": "Tutti",
2167
+ "nav.recent": "Usati di recente",
2168
+ "nav.starred": "Preferiti",
2169
+ typologiesTitle: "Tipologie",
2170
+ collapseSidebar: "Comprimi barra laterale",
2171
+ expandSidebar: "Espandi barra laterale",
2172
+ "filters.tags": "Tag",
2173
+ "filters.media": "Media",
2174
+ "filters.content": "Contenuto",
2175
+ clearAll: "Cancella tutto",
2176
+ removeFilter: "Rimuovi filtro {name}",
2177
+ "typologies.hero": "Hero",
2178
+ "typologies.text": "Testo",
2179
+ "typologies.media": "Media",
2180
+ "typologies.listing": "Elenco",
2181
+ "typologies.cards": "Card",
2182
+ "typologies.cta": "Invito all'azione",
2183
+ "typologies.form": "Modulo",
2184
+ "typologies.layout": "Layout",
2185
+ "facets.image": "Immagine",
2186
+ "facets.video": "Video",
2187
+ "facets.gallery": "Galleria",
2188
+ "facets.richtext": "Testo formattato",
2189
+ "facets.list": "Lista",
2190
+ "facets.dynamic": "Dinamico",
2191
+ "facets.form": "Modulo",
2192
+ star: "Aggiungi ai preferiti",
2193
+ unstar: "Rimuovi dai preferiti",
2194
+ quickInsert: "Inserisci {label}",
2195
+ insert: "Inserisci",
2196
+ closeDetail: "Chiudi dettagli",
2197
+ typology: "Tipologia",
2198
+ typologyAuto: "Automatica ({name})",
2199
+ tags: "Tag",
2200
+ tagsHelp: "Separati da virgole, fino a 10 tag di 24 caratteri. Diventano un filtro della galleria.",
2201
+ sidebarTitle: "Barra laterale dell'editor visivo: {name}",
2202
+ sidebarHelp: "Pulsanti su un bordo dell'editor visivo. Ognuno apre alcuni campi del documento in un pannello laterale o in una finestra modale.",
2203
+ sidebarPosition: "Posizione della barra laterale",
2204
+ "positions.left": "Sinistra",
2205
+ "positions.right": "Destra",
2206
+ "positions.bottom": "In basso",
2207
+ sidebarItemLabel: "Etichetta",
2208
+ sidebarIcon: "Icona",
2209
+ noIcon: "Nessuna icona",
2210
+ "icons.text": "Testo",
2211
+ "icons.tag": "Tag",
2212
+ "icons.seo": "SEO",
2213
+ "icons.settings": "Impostazioni",
2214
+ "icons.image": "Immagine",
2215
+ "icons.link": "Link",
2216
+ "icons.palette": "Tavolozza",
2217
+ "icons.list": "Elenco",
2218
+ "icons.globe": "Globo",
2219
+ "icons.info": "Info",
2220
+ sidebarOpens: "Si apre come",
2221
+ "opens.drawer": "Pannello laterale",
2222
+ "opens.modal": "Finestra modale",
2223
+ moveUp: "Sposta su",
2224
+ moveDown: "Sposta gi\xF9",
2225
+ sidebarRemove: "Rimuovi elemento",
2226
+ sidebarFields: "Campi",
2227
+ sidebarInvalid: "Dai un'etichetta all'elemento (fino a 40 caratteri, senza < o >) e scegli almeno un campo.",
2228
+ sidebarInvalidSave: "Correggi gli elementi della barra laterale segnalati sopra prima di salvare.",
2229
+ sidebarAdd: "Aggiungi elemento",
2230
+ sidebarLimit: "Fino a {max} elementi.",
2231
+ restoreProject: "Ripristina i valori del progetto",
2232
+ resetAll: "Ripristina i valori predefiniti",
2233
+ restoreProjectConfirm: "Scartare tutte le impostazioni salvate in questa pagina e tornare ai valori del progetto definiti nel codice?",
2234
+ resetAllConfirm: "Scartare tutte le impostazioni salvate in questa pagina e tornare ai valori predefiniti?",
2235
+ restored: "Valori ripristinati.",
2236
+ blockPreview: "Anteprima dal vivo del blocco",
2237
+ livePreviewFailed: "Anteprima dal vivo non disponibile, viene mostrata l'immagine.",
2238
+ blockPreviewUrl: "URL di anteprima del blocco",
2239
+ blockPreviewUrlHelp: "Facoltativo. Una pagina del tuo frontend che renderizza un singolo blocco, mostrata in grande quando si clicca una scheda della galleria, ad es. http://localhost:3026/it/block-preview/'{name}'/'{variant}'. Segnaposto: '{uid}', '{name}' (dopo il punto), '{category}' (prima del punto), '{variant}' (default), '{locale}'. Vuoto usa la route di anteprima pagina se esiste, altrimenti l'immagine.",
2240
+ rowActions: "Azioni per {label}",
2241
+ selectBlock: "Seleziona {label}",
2242
+ hiddenBadge: "Nascosto",
2243
+ hideBlock: "Nascondi {label} sul sito",
2244
+ showBlock: "Mostra {label} sul sito",
2245
+ duplicateBlock: "Duplica {label}",
2246
+ copyBlock: "Copia {label}",
2247
+ confirmDeleteTitle: "Elimina blocco",
2248
+ confirmDelete: "Eliminare il blocco {name}?",
2249
+ confirmDeleteMarker: "\xC8 un marcatore di gruppo: viene rimosso solo il marcatore, non i blocchi del gruppo, e il gruppo resta sbilanciato finch\xE9 non viene corretto.",
2250
+ delete: "Elimina",
2251
+ selectMode: "Seleziona i blocchi da copiare",
2252
+ copySelected: "Copia selezionati ({count})",
2253
+ paste: "Incolla {count, plural, one {# blocco} other {# blocchi}}",
2254
+ pasted: "{count, plural, one {# blocco incollato} other {# blocchi incollati}}.",
2255
+ copied: "{count, plural, one {# blocco copiato} other {# blocchi copiati}}. Incollali in qualsiasi pagina di questo sito.",
2256
+ copyFailed: "Impossibile copiare: lo spazio del browser \xE8 pieno o bloccato.",
2257
+ pasteNotAllowed: 'Nulla \xE8 stato incollato: "{uid}" non \xE8 consentito in questa zona.',
2258
+ pasteFull: "Nulla \xE8 stato incollato: la zona non ha spazio per altri {count} blocchi.",
2259
+ pasteUnbalanced: "Nulla \xE8 stato incollato: i blocchi copiati contengono un gruppo incompleto.",
2260
+ rowActionFailed: "Impossibile completare l'azione; il modulo non \xE8 cambiato.",
2261
+ rowActionsTitle: "Azioni dei blocchi",
2262
+ confirmDeleteOption: "Chiedi conferma prima di eliminare un blocco",
2263
+ duplicateOption: 'Mostra "Duplica" su ogni blocco',
2264
+ clipboardOption: 'Mostra "Copia" e "Incolla" (tra pagine)',
2265
+ hiddenBlocks: "Blocchi nascosti sul sito",
2266
+ "hiddenModes.strip": "Rimossi dall'API dei contenuti",
2267
+ "hiddenModes.flag": "Inviati con il flag (decide il frontend)",
2268
+ "hiddenModes.off": "Disattivato (nessun pulsante nascondi)",
2269
+ hiddenBlocksHelp: "L'occhio su ogni blocco lo segna come nascosto sul sito. Rimossi: le letture dell'API dei contenuti (REST, GraphQL) lo omettono, l'admin lo mostra sempre. Flag: viene inviato con l'attributo e decide il frontend.",
2270
+ hiddenAttributeOff: "La configurazione del plugin imposta hiddenAttribute a false: i blocchi non possono essere nascosti."
1049
2271
  };
1050
2272
 
1051
2273
  // admin/translations/nl.json
@@ -1090,10 +2312,11 @@ var nl_default = {
1090
2312
  "templates.faq": "FAQ",
1091
2313
  brokenMedia: "Het gekozen medium bestaat niet meer; de volgende bron wordt gebruikt.",
1092
2314
  editor: "Editorvoorkeuren",
1093
- enabled: "Editorverbeteringen ingeschakeld",
1094
- enabledHelp: "Uit herstelt de native kiezer en accordeons. Deze pagina blijft beschikbaar om ze weer in te schakelen.",
2315
+ enabled: "Blockscene aan",
2316
+ enabledHelp: "Uit zet de hele plugin meteen uit, zonder herstart: elke bewerkweergave is weer native en de groepscontrole bij publiceren stopt. Gebruik dit als er in productie iets misgaat.",
1095
2317
  showOpenAll: '"Alle blokken openen" tonen',
1096
2318
  showCloseAll: '"Alle blokken sluiten" tonen',
2319
+ showRowThumbnails: "Blokminiaturen in de rijen tonen",
1097
2320
  initialState: "Beginstand van de accordeons",
1098
2321
  "states.closed": "Alle gesloten",
1099
2322
  "states.open": "Alle open",
@@ -1105,13 +2328,20 @@ var nl_default = {
1105
2328
  "modes.form": "Velden",
1106
2329
  "modes.split": "Velden + pagina",
1107
2330
  "modes.preview": "Visuele editor",
1108
- previewModeHelp: "Geldt bij het openen van een document; elke redacteur kan van modus wisselen en de browser onthoudt de laatste keuze.",
2331
+ previewModeHelp: "Elke bewerkweergave opent in deze modus, tenzij het contenttype hieronder een eigen modus instelt. Tijdens het bewerken kan je wisselen; die keuze wordt niet onthouden.",
2332
+ lazyEdit: "Klik om te bewerken",
2333
+ lazyEditors: "Rich-text-editors op aanvraag laden",
2334
+ lazyFields: "Aangepaste velden die op aanvraag laden",
2335
+ lazyEditorsHelp: "De vermelde aangepaste velden tonen een alleen-lezen voorbeeld tot ze worden aangeklikt of focus krijgen, zodat grote pagina's snel openen. Scheid uids met komma's.",
2336
+ lazyFieldsInvalid: "Gebruik maximaal {max} uids van aangepaste velden, zoals plugin::ckeditor5.CKEditor of global::mijn-veld.",
1109
2337
  previewV4: "Het paginavoorbeeld bestaat alleen in de Strapi 5-distributie; deze opties hebben geen effect op Strapi 4.",
1110
2338
  modeForm: "Velden",
1111
2339
  modeSplit: "Velden + pagina",
1112
2340
  modePreview: "Visuele editor",
1113
2341
  previewPane: "Paginavoorbeeld",
1114
2342
  resize: "Voorbeeldpaneel aanpassen",
2343
+ resizeHint: "Sleep om te wijzigen; dubbelklik zet terug op de helft",
2344
+ scrollToHovered: "Naar het gemarkeerde blok scrollen",
1115
2345
  previewHelp: "Niet-opgeslagen wijzigingen verschijnen in het voorbeeld. Klik op een blok om het formulier te openen, op een afbeelding voor de Mediabibliotheek, op een tekst om die te bewerken.",
1116
2346
  previewNoUrl: "Geen voorbeeld-URL: stel die in bij Instellingen, Blockscene, of configureer Strapi's native Preview voor dit contenttype.",
1117
2347
  previewFailed: "Het voorbeeld antwoordde niet binnen 15 s. Als de frontend nog laadt, verschijnt het vanzelf; het formulier blijft bruikbaar.",
@@ -1148,19 +2378,137 @@ var nl_default = {
1148
2378
  diagInsertClose: 'Ontbrekende sluiting "{expected}" invoegen na rij {n}',
1149
2379
  diagStrayHint: "Verwijder de losse markering met de verwijderactie van de rij, of verplaats hem achter een opening.",
1150
2380
  modeGroup: "Bewerkmodus",
2381
+ undo: "Ongedaan maken",
2382
+ redo: "Opnieuw",
2383
+ historyGroup: "Bewerkingsgeschiedenis",
1151
2384
  saveDisabledHint: "Geen niet-opgeslagen wijzigingen.",
1152
2385
  publishDisabledHint: "Publiceren niet beschikbaar: sla eerst op, herstel de fouten in het formulier of controleer uw rechten.",
1153
2386
  countAll: "{count} blokken",
1154
2387
  countFiltered: "{shown} van {total}",
1155
- filterCategory: "Filteren op categorie",
1156
- allCategories: "Alle categorie\xEBn",
1157
2388
  showFields: "Velden",
1158
2389
  showFieldsHint: "Veldenlijst op elk blok tonen",
1159
2390
  columns: "Kolommen",
1160
2391
  noPreview: "geen voorbeeld",
1161
2392
  "templates.none": "Geen voorbeeld",
1162
2393
  noChanges: "Geen niet-opgeslagen wijzigingen",
1163
- rowPreview: "Voorbeeld van {label} openen"
2394
+ rowPreview: "Voorbeeld van {label} openen",
2395
+ deviceGroup: "Voorbeeldbreedte",
2396
+ "device.fit": "Passend",
2397
+ "device.mobile": "Mobiel",
2398
+ "device.tablet": "Tablet",
2399
+ "device.desktop": "Desktop",
2400
+ contentTypes: "Contenttypes",
2401
+ contentTypesHelp: 'De galerij, accordeons en paginavoorbeeld werken op deze contenttypes (die met een Dynamic Zone). Zet er een uit om de bewerking native te laten. "Opent in" bepaalt hoe elke bewerkweergave van dat type start; tijdens het bewerken kan je nog wisselen.',
2402
+ typeMode: "Opent in",
2403
+ modeDefault: "Standaard ({mode})",
2404
+ blockModalDone: "Klaar",
2405
+ crashed: "Blockscene is hier gestopt door een fout. De native editor blijft werken. Gebeurt het opnieuw, zet Blockscene uit in Settings, Blockscene.",
2406
+ sidebarLabel: "Paginavelden",
2407
+ "nav.all": "Alle",
2408
+ "nav.recent": "Recent gebruikt",
2409
+ "nav.starred": "Favorieten",
2410
+ typologiesTitle: "Typologie\xEBn",
2411
+ collapseSidebar: "Zijbalk inklappen",
2412
+ expandSidebar: "Zijbalk uitklappen",
2413
+ "filters.tags": "Tags",
2414
+ "filters.media": "Media",
2415
+ "filters.content": "Inhoud",
2416
+ clearAll: "Alles wissen",
2417
+ removeFilter: "Filter {name} verwijderen",
2418
+ "typologies.hero": "Hero",
2419
+ "typologies.text": "Tekst",
2420
+ "typologies.media": "Media",
2421
+ "typologies.listing": "Overzicht",
2422
+ "typologies.cards": "Kaarten",
2423
+ "typologies.cta": "Call-to-action",
2424
+ "typologies.form": "Formulier",
2425
+ "typologies.layout": "Lay-out",
2426
+ "facets.image": "Afbeelding",
2427
+ "facets.video": "Video",
2428
+ "facets.gallery": "Galerij",
2429
+ "facets.richtext": "Opgemaakte tekst",
2430
+ "facets.list": "Lijst",
2431
+ "facets.dynamic": "Dynamisch",
2432
+ "facets.form": "Formulier",
2433
+ star: "Als favoriet markeren",
2434
+ unstar: "Uit favorieten verwijderen",
2435
+ quickInsert: "{label} invoegen",
2436
+ insert: "Invoegen",
2437
+ closeDetail: "Details sluiten",
2438
+ typology: "Typologie",
2439
+ typologyAuto: "Automatisch ({name})",
2440
+ tags: "Tags",
2441
+ tagsHelp: "Kommagescheiden, tot 10 tags van 24 tekens. Ze worden een filter in de galerij.",
2442
+ sidebarTitle: "Zijbalk van de visuele editor: {name}",
2443
+ sidebarHelp: "Knoppen aan een rand van de visuele editor. Elke knop opent enkele velden van het document in een lade of een venster.",
2444
+ sidebarPosition: "Positie van de zijbalk",
2445
+ "positions.left": "Links",
2446
+ "positions.right": "Rechts",
2447
+ "positions.bottom": "Onder",
2448
+ sidebarItemLabel: "Label",
2449
+ sidebarIcon: "Pictogram",
2450
+ noIcon: "Geen pictogram",
2451
+ "icons.text": "Tekst",
2452
+ "icons.tag": "Tag",
2453
+ "icons.seo": "SEO",
2454
+ "icons.settings": "Instellingen",
2455
+ "icons.image": "Afbeelding",
2456
+ "icons.link": "Link",
2457
+ "icons.palette": "Palet",
2458
+ "icons.list": "Lijst",
2459
+ "icons.globe": "Wereldbol",
2460
+ "icons.info": "Info",
2461
+ sidebarOpens: "Opent als",
2462
+ "opens.drawer": "Lade",
2463
+ "opens.modal": "Venster",
2464
+ moveUp: "Omhoog",
2465
+ moveDown: "Omlaag",
2466
+ sidebarRemove: "Item verwijderen",
2467
+ sidebarFields: "Velden",
2468
+ sidebarInvalid: "Geef het item een label (maximaal 40 tekens, zonder < of >) en kies minstens \xE9\xE9n veld.",
2469
+ sidebarInvalidSave: "Herstel de hierboven gemarkeerde zijbalkitems voordat je opslaat.",
2470
+ sidebarAdd: "Item toevoegen",
2471
+ sidebarLimit: "Maximaal {max} items.",
2472
+ restoreProject: "Projectstandaarden herstellen",
2473
+ resetAll: "Standaardwaarden herstellen",
2474
+ restoreProjectConfirm: "Alle op deze pagina opgeslagen instellingen verwerpen en teruggaan naar de projectstandaarden uit de code?",
2475
+ resetAllConfirm: "Alle op deze pagina opgeslagen instellingen verwerpen en teruggaan naar de standaardwaarden?",
2476
+ restored: "Standaardwaarden hersteld.",
2477
+ blockPreview: "Livevoorbeeld van het blok",
2478
+ livePreviewFailed: "Livevoorbeeld niet beschikbaar, de afbeelding wordt getoond.",
2479
+ blockPreviewUrl: "URL van blokvoorbeeld",
2480
+ blockPreviewUrlHelp: "Optioneel. Een pagina van uw frontend die \xE9\xE9n blok rendert, groot getoond wanneer op een galerijkaart wordt geklikt, bijv. http://localhost:3026/nl/block-preview/'{name}'/'{variant}'. Plaatshouders: '{uid}', '{name}' (na de punt), '{category}' (voor de punt), '{variant}' (default), '{locale}'. Leeg gebruikt de paginavoorbeeldroute als die er is, anders de afbeelding.",
2481
+ rowActions: "Acties voor {label}",
2482
+ selectBlock: "{label} selecteren",
2483
+ hiddenBadge: "Verborgen",
2484
+ hideBlock: "{label} op de site verbergen",
2485
+ showBlock: "{label} op de site tonen",
2486
+ duplicateBlock: "{label} dupliceren",
2487
+ copyBlock: "{label} kopi\xEBren",
2488
+ confirmDeleteTitle: "Blok verwijderen",
2489
+ confirmDelete: "Blok {name} verwijderen?",
2490
+ confirmDeleteMarker: "Dit is een groepsmarkering: alleen de markering wordt verwijderd, niet de blokken in de groep, en de groep blijft onevenwichtig tot hij hersteld is.",
2491
+ delete: "Verwijderen",
2492
+ selectMode: "Blokken selecteren om te kopi\xEBren",
2493
+ copySelected: "Selectie kopi\xEBren ({count})",
2494
+ paste: "{count, plural, one {# blok} other {# blokken}} plakken",
2495
+ pasted: "{count, plural, one {# blok geplakt} other {# blokken geplakt}}.",
2496
+ copied: "{count, plural, one {# blok gekopieerd} other {# blokken gekopieerd}}. Plak ze op elke pagina van deze site.",
2497
+ copyFailed: "Kopi\xEBren mislukt: de browseropslag is vol of geblokkeerd.",
2498
+ pasteNotAllowed: 'Er is niets geplakt: "{uid}" is niet toegestaan in deze zone.',
2499
+ pasteFull: "Er is niets geplakt: de zone heeft geen ruimte voor nog {count} blokken.",
2500
+ pasteUnbalanced: "Er is niets geplakt: de gekopieerde blokken bevatten een onvolledige groep.",
2501
+ rowActionFailed: "De actie kon niet worden uitgevoerd; het formulier is niet gewijzigd.",
2502
+ rowActionsTitle: "Blokacties",
2503
+ confirmDeleteOption: "Bevestigen voordat een blok wordt verwijderd",
2504
+ duplicateOption: '"Dupliceren" op elk blok tonen',
2505
+ clipboardOption: `"Kopi\xEBren" en "Plakken" tonen (tussen pagina's)`,
2506
+ hiddenBlocks: "Op de site verborgen blokken",
2507
+ "hiddenModes.strip": "Verwijderd uit de content-API",
2508
+ "hiddenModes.flag": "Verzonden met de markering (de frontend beslist)",
2509
+ "hiddenModes.off": "Uit (geen verbergknop)",
2510
+ hiddenBlocksHelp: "Het oog op elk blok markeert het als verborgen op de site. Verwijderd: leesverzoeken van de content-API (REST, GraphQL) laten het weg, de admin toont het altijd. Markering: het wordt met het attribuut verzonden en de frontend beslist.",
2511
+ hiddenAttributeOff: "De pluginconfiguratie zet hiddenAttribute op false: blokken kunnen niet verborgen worden."
1164
2512
  };
1165
2513
 
1166
2514
  // admin/messages.ts
@@ -1184,12 +2532,12 @@ function useMessages() {
1184
2532
  var import_jsx_runtime2 = require("react/jsx-runtime");
1185
2533
  var ATTR = "data-blockscene-row-thumb";
1186
2534
  function useAnchors(zones, onOpen) {
1187
- const [anchors, setAnchors] = React.useState([]);
1188
- const open = React.useRef(onOpen);
2535
+ const [anchors, setAnchors] = React2.useState([]);
2536
+ const open = React2.useRef(onOpen);
1189
2537
  open.current = onOpen;
1190
- const latest = React.useRef(zones);
2538
+ const latest = React2.useRef(zones);
1191
2539
  latest.current = zones;
1192
- const sync = React.useRef(() => {
2540
+ const sync = React2.useRef(() => {
1193
2541
  });
1194
2542
  sync.current = () => {
1195
2543
  const next = [];
@@ -1235,22 +2583,22 @@ function useAnchors(zones, onOpen) {
1235
2583
  (prev) => prev.length === next.length && prev.every((a, i) => a.el === next[i].el && a.uid === next[i].uid) ? prev : next
1236
2584
  );
1237
2585
  };
1238
- React.useEffect(() => {
2586
+ React2.useEffect(() => {
1239
2587
  sync.current();
1240
2588
  });
1241
- React.useEffect(() => {
1242
- let frame = 0;
2589
+ React2.useEffect(() => {
2590
+ let frame2 = 0;
1243
2591
  const observer = new MutationObserver(() => {
1244
- if (frame) return;
1245
- frame = requestAnimationFrame(() => {
1246
- frame = 0;
2592
+ if (frame2) return;
2593
+ frame2 = requestAnimationFrame(() => {
2594
+ frame2 = 0;
1247
2595
  sync.current();
1248
2596
  });
1249
2597
  });
1250
2598
  observer.observe(document.body, { childList: true, subtree: true });
1251
2599
  return () => {
1252
2600
  observer.disconnect();
1253
- cancelAnimationFrame(frame);
2601
+ cancelAnimationFrame(frame2);
1254
2602
  for (const el of document.querySelectorAll(`[${ATTR}]`)) el.remove();
1255
2603
  };
1256
2604
  }, []);
@@ -1258,13 +2606,13 @@ function useAnchors(zones, onOpen) {
1258
2606
  }
1259
2607
  function RowPreviews({ zones, components, catalog, Modal: Modal2 }) {
1260
2608
  const t = useMessages();
1261
- const [resolved, setResolved] = React.useState({});
1262
- const [open, setOpen] = React.useState(null);
2609
+ const [resolved, setResolved] = React2.useState({});
2610
+ const [open, setOpen] = React2.useState(null);
1263
2611
  const anchors = useAnchors(zones, (uid) => setOpen(uid));
1264
2612
  const candidates = (uid) => candidatesFor(uid, catalog?.components?.[uid] || {}, catalog || {});
1265
2613
  const label = (uid) => catalog?.components?.[uid]?.label || components?.[uid]?.info?.displayName || uid;
1266
2614
  const srcOf = (uid) => candidates(uid)[resolved[uid] ?? -1] || "";
1267
- React.useEffect(() => {
2615
+ React2.useEffect(() => {
1268
2616
  for (const { el, uid } of anchors) {
1269
2617
  if (srcOf(uid)) {
1270
2618
  el.setAttribute("role", "button");
@@ -1332,9 +2680,469 @@ function RowPreviews({ zones, components, catalog, Modal: Modal2 }) {
1332
2680
  ] });
1333
2681
  }
1334
2682
 
1335
- // admin/Gallery.tsx
2683
+ // admin/RowActions.tsx
2684
+ var React3 = __toESM(require("react"));
2685
+ var import_react_dom2 = require("react-dom");
2686
+ var import_styled_components2 = __toESM(require("styled-components"));
2687
+ var import_design_system = require("@strapi/design-system");
2688
+
2689
+ // admin/rows.mjs
2690
+ var import_fractional_indexing2 = require("fractional-indexing");
2691
+ var import_groups2 = __toESM(require_groups(), 1);
2692
+ var actionRange = (rows, index, groups) => groups && (0, import_groups2.isOpener)(rows[index], groups) ? (0, import_groups2.groupRange)(rows, index, groups) : [index, index];
2693
+ function expandSelection(rows, indexes, groups) {
2694
+ const out = /* @__PURE__ */ new Set();
2695
+ for (const index of indexes) {
2696
+ if (!rows[index]) continue;
2697
+ const [start, end] = actionRange(rows, index, groups);
2698
+ for (let i = start; i <= end; i++) out.add(i);
2699
+ }
2700
+ return [...out].sort((a, b) => a - b);
2701
+ }
2702
+ var canAct = (row, groups) => !(groups && (0, import_groups2.isClose)(row, groups));
2703
+ var isGroupMarker = (row, groups) => Boolean(groups) && ((0, import_groups2.isOpener)(row, groups) || (0, import_groups2.isClose)(row, groups));
2704
+ var copy = (value) => {
2705
+ try {
2706
+ return structuredClone(value);
2707
+ } catch {
2708
+ return JSON.parse(JSON.stringify(value));
2709
+ }
2710
+ };
2711
+ function cloneRow(row, components, { relation } = {}) {
2712
+ const walk = (value, schema, path) => {
2713
+ const out = {};
2714
+ for (const [key, item] of Object.entries(value || {})) {
2715
+ if (key === "id" || key === "documentId") continue;
2716
+ const attr = schema?.attributes?.[key];
2717
+ if (attr?.type === "component" && item) {
2718
+ const child = components?.[attr.component];
2719
+ out[key] = attr.repeatable ? (Array.isArray(item) ? item : []).map((entry, i) => walk(entry, child, [...path, key, i])) : walk(item, child, [...path, key]);
2720
+ } else if (attr?.type === "relation" && relation) out[key] = relation(item, attr, [...path, key]);
2721
+ else out[key] = copy(item);
2722
+ }
2723
+ return out;
2724
+ };
2725
+ return walk(row, components?.[row?.__component], []);
2726
+ }
2727
+ function integerKeys(rows, n) {
2728
+ const max = Math.max(-1, ...rows.map((row) => Number(row?.__temp_key__)).filter(Number.isFinite));
2729
+ return Array.from({ length: n }, (_, i) => max + 1 + i);
2730
+ }
2731
+ var insertRows = (rows, at, items, keys) => [
2732
+ ...rows.slice(0, at),
2733
+ ...items.map((item, i) => ({ ...item, __temp_key__: keys[i] })),
2734
+ ...rows.slice(at)
2735
+ ];
2736
+ function setHidden(rows, index, groups, name, value) {
2737
+ const [start, end] = actionRange(rows, index, groups);
2738
+ return rows.map((row, i) => i >= start && i <= end ? { ...row, [name]: value } : row);
2739
+ }
2740
+ var CLIPBOARD_KEY = "blockscene:clipboard:v1";
2741
+ var MAX_ROWS = 200;
2742
+ function readClip(storage3) {
2743
+ try {
2744
+ const clip = JSON.parse(storage3?.getItem(CLIPBOARD_KEY) || "null");
2745
+ return clip && clip.v === 1 && Array.isArray(clip.rows) && clip.rows.length > 0 && clip.rows.length <= MAX_ROWS && clip.rows.every((row) => row && typeof row.__component === "string") ? clip : null;
2746
+ } catch {
2747
+ return null;
2748
+ }
2749
+ }
2750
+ function writeClip(storage3, { rows, model, locale, zone }) {
2751
+ try {
2752
+ if (!rows.length || rows.length > MAX_ROWS) return false;
2753
+ storage3.setItem(CLIPBOARD_KEY, JSON.stringify({ v: 1, model, locale: locale || "", zone, at: Date.now(), rows }));
2754
+ return true;
2755
+ } catch {
2756
+ return false;
2757
+ }
2758
+ }
2759
+ function pasteProblem(clip, zone, rows, components, groups) {
2760
+ if (!clip?.rows?.length) return { code: "empty" };
2761
+ const bad = clip.rows.find((row) => !zone?.components?.includes(row.__component) || !components?.[row.__component]);
2762
+ if (bad) return { code: "notAllowed", uid: bad.__component };
2763
+ if ((rows?.length || 0) + clip.rows.length > (zone.max ?? Infinity)) return { code: "full" };
2764
+ if (groups && (0, import_groups2.validateGroups)(clip.rows, groups).length) return { code: "unbalanced" };
2765
+ return null;
2766
+ }
2767
+
2768
+ // admin/icons.tsx
2769
+ var import_styled_components = __toESM(require("styled-components"));
1336
2770
  var import_jsx_runtime3 = require("react/jsx-runtime");
1337
- var Columns = import_styled_components.default.div`
2771
+ var PATHS = {
2772
+ text: "M5 6V4h14v2M12 4v16M9 20h6",
2773
+ tag: "M3 12V4h8l10 10-8 8L3 12zM7.5 7.5h.01",
2774
+ seo: "M11 4a7 7 0 1 0 0 14 7 7 0 0 0 0-14zM20 20l-4-4",
2775
+ settings: "M12 9a3 3 0 1 0 0 6 3 3 0 0 0 0-6zM12 2v3M12 19v3M4.2 4.2l2.1 2.1M17.7 17.7l2.1 2.1M2 12h3M19 12h3M4.2 19.8l2.1-2.1M17.7 6.3l2.1-2.1",
2776
+ image: "M4 5h16v14H4zM4 16l5-5 4 4 3-3 4 4M15.5 9.5h.01",
2777
+ link: "M10 14a4 4 0 0 0 5.7 0l3-3a4 4 0 0 0-5.7-5.7l-1 1M14 10a4 4 0 0 0-5.7 0l-3 3a4 4 0 0 0 5.7 5.7l1-1",
2778
+ palette: "M12 3a9 9 0 1 0 0 18c1 0 1.5-.7 1.5-1.5 0-1.2-1-1.5-1-2.5s.7-1.5 1.5-1.5H16a5 5 0 0 0 5-5c0-4.1-4-7.5-9-7.5zM7.5 11h.01M10 7h.01M15 7h.01",
2779
+ list: "M9 6h11M9 12h11M9 18h11M4.5 6h.01M4.5 12h.01M4.5 18h.01",
2780
+ globe: "M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18zM3 12h18M12 3c2.5 2.5 3.7 5.5 3.7 9s-1.2 6.5-3.7 9c-2.5-2.5-3.7-5.5-3.7-9S9.5 5.5 12 3z",
2781
+ info: "M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18zM12 11v6M12 7.5h.01",
2782
+ // Toolbar icons (not offered for sidebar items).
2783
+ split: "M3 4h18v16H3zM12 4v16",
2784
+ eye: "M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12zM12 9a3 3 0 1 0 0 6 3 3 0 0 0 0-6z",
2785
+ fit: "M4 9V4h5M20 9V4h-5M4 15v5h5M20 15v5h-5",
2786
+ mobile: "M8 3h8v18H8zM11.5 18h1",
2787
+ tablet: "M5 3h14v18H5zM11.5 18h1",
2788
+ desktop: "M3 4h18v12H3zM8 20h8M12 16v4",
2789
+ expand: "M7 7l5 5 5-5M7 13l5 5 5-5",
2790
+ collapse: "M7 17l5-5 5 5M7 11l5-5 5 5",
2791
+ undo: "M9 14L4 9l5-5M4 9h10.5a5.5 5.5 0 0 1 0 11H11",
2792
+ redo: "M15 14l5-5-5-5M20 9H9.5a5.5 5.5 0 0 0 0 11H13",
2793
+ // Gallery: navigation, card actions and typologies (text, media, listing and cta reuse text, image, list and link).
2794
+ grid: "M4 4h7v7H4zM13 4h7v7h-7zM4 13h7v7H4zM13 13h7v7h-7z",
2795
+ clock: "M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18zM12 7v5l3 2",
2796
+ star: "M12 3l2.8 5.7 6.2.9-4.5 4.4 1.1 6.2L12 17.3 6.4 20.2l1.1-6.2L3 9.6l6.2-.9L12 3z",
2797
+ plus: "M12 5v14M5 12h14",
2798
+ close: "M6 6l12 12M18 6L6 18",
2799
+ chevron: "M6 9l6 6 6-6",
2800
+ // Settings: sidebar item order.
2801
+ up: "M12 19V5M6 11l6-6 6 6",
2802
+ down: "M12 5v14M6 13l6 6 6-6",
2803
+ sidebar: "M3 4h18v16H3zM9 4v16",
2804
+ hero: "M3 4h18v10H3zM7 18h10M9 21h6",
2805
+ cards: "M3 5h8v14H3zM13 5h8v14h-8z",
2806
+ form: "M4 4h16v16H4zM8 9h8M8 13h8M8 17h4",
2807
+ layout: "M3 4h18v16H3zM3 10h18M12 10v10",
2808
+ // Row actions.
2809
+ eyeOff: "M3 3l18 18M10.6 5.1A10 10 0 0 1 12 5c6.5 0 10 7 10 7a17 17 0 0 1-3.1 4M6.6 6.6C3.9 8.4 2 12 2 12s3.5 7 10 7c1.9 0 3.6-.6 5-1.5M9.9 9.9a3 3 0 0 0 4.2 4.2",
2810
+ duplicate: "M8 8h12v12H8zM4 16V4h12",
2811
+ clipboard: "M9 3h6v3H9zM7 4.5H5v16h14v-16h-2",
2812
+ paste: "M9 3h6v3H9zM7 4.5H5v16h14v-16h-2M12 10v7M9 14l3 3 3-3",
2813
+ select: "M4 4h16v16H4zM8 12l3 3 5-6"
2814
+ };
2815
+ function Icon({ name, size = 18, filled }) {
2816
+ const d = PATHS[name];
2817
+ if (!d) return null;
2818
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: filled ? "currentColor" : "none", stroke: "currentColor", strokeWidth: 1.8, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d }) });
2819
+ }
2820
+ var ToolButton = import_styled_components.default.button`
2821
+ display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; padding: 0;
2822
+ border: 1px solid ${({ theme }) => theme.colors.neutral200}; border-radius: 4px; cursor: pointer;
2823
+ color: ${({ theme, $active }) => $active ? theme.colors.primary600 : theme.colors.neutral700};
2824
+ background: ${({ theme, $active }) => $active ? theme.colors.primary100 : theme.colors.neutral0};
2825
+ &:hover:not(:disabled) { background: ${({ theme, $active }) => $active ? theme.colors.primary100 : theme.colors.neutral100}; }
2826
+ &:disabled { opacity: 0.4; cursor: default; }
2827
+ &:focus-visible { outline: 2px solid ${({ theme }) => theme.colors.primary600}; outline-offset: 1px; }
2828
+ `;
2829
+ var Tool = ({ icon, label, active, ...rest }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ToolButton, { type: "button", "aria-label": label, title: label, $active: active, ...active !== void 0 ? { "aria-pressed": active } : {}, ...rest, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Icon, { name: icon }) });
2830
+
2831
+ // admin/RowActions.tsx
2832
+ var import_jsx_runtime4 = require("react/jsx-runtime");
2833
+ var ATTR2 = "data-blockscene-row-actions";
2834
+ var HIDDEN_ATTR = "data-blockscene-hidden";
2835
+ var CLIP_EVENT = "blockscene:clipboard";
2836
+ var storage = () => {
2837
+ try {
2838
+ return window.localStorage;
2839
+ } catch {
2840
+ return null;
2841
+ }
2842
+ };
2843
+ function useClipboard() {
2844
+ const [clip, setClip] = React3.useState(() => readClip(storage()));
2845
+ React3.useEffect(() => {
2846
+ const sync = (event) => {
2847
+ if (!event?.key || event.key === CLIPBOARD_KEY) setClip(readClip(storage()));
2848
+ };
2849
+ window.addEventListener("storage", sync);
2850
+ window.addEventListener(CLIP_EVENT, sync);
2851
+ return () => {
2852
+ window.removeEventListener("storage", sync);
2853
+ window.removeEventListener(CLIP_EVENT, sync);
2854
+ };
2855
+ }, []);
2856
+ return clip;
2857
+ }
2858
+ var storeClip = (data) => {
2859
+ const ok = writeClip(storage(), data);
2860
+ if (ok) window.dispatchEvent(new Event(CLIP_EVENT));
2861
+ return ok;
2862
+ };
2863
+ function pasteInto(form, zone, clip, components, groups, t, at) {
2864
+ const rows = form.rows(zone.name);
2865
+ const problem = pasteProblem(clip, zone, rows, components, groups);
2866
+ if (problem) {
2867
+ form.notify("warning", t.f(
2868
+ problem.code === "notAllowed" ? "pasteNotAllowed" : problem.code === "full" ? "pasteFull" : "pasteUnbalanced",
2869
+ { uid: problem.uid || "", count: clip?.rows?.length || 0 }
2870
+ ));
2871
+ return false;
2872
+ }
2873
+ const index = at ?? rows.length;
2874
+ form.setRows(zone.name, insertRows(rows, index, clip.rows, form.keys(rows, index, clip.rows.length)));
2875
+ form.notify("success", t.f("pasted", { count: clip.rows.length }));
2876
+ return true;
2877
+ }
2878
+ var actionsOf = (toggle) => toggle.nextElementSibling;
2879
+ var trashOf = (toggle) => [...actionsOf(toggle)?.querySelectorAll("button") || []].find((b) => !b.closest(`[${ATTR2}]`)) || null;
2880
+ function useAnchors2(zones, enabled) {
2881
+ const [anchors, setAnchors] = React3.useState([]);
2882
+ const latest = React3.useRef(zones);
2883
+ latest.current = zones;
2884
+ const sync = React3.useRef(() => {
2885
+ });
2886
+ sync.current = () => {
2887
+ const next = [];
2888
+ if (enabled)
2889
+ for (const zone of latest.current) {
2890
+ const list = zone.count ? findZoneList(zone.label) : null;
2891
+ if (!list) continue;
2892
+ toggles(list).forEach((toggle, index) => {
2893
+ const trash = trashOf(toggle);
2894
+ if (!trash || index >= zone.count) return;
2895
+ const holder = trash.parentElement !== actionsOf(toggle) && trash.parentElement?.children.length === 1 ? trash.parentElement : trash;
2896
+ let el = holder.parentElement?.querySelector(`:scope > [${ATTR2}]`);
2897
+ if (!el) {
2898
+ el = document.createElement("span");
2899
+ el.setAttribute(ATTR2, "");
2900
+ el.style.cssText = "display:inline-flex;align-items:center";
2901
+ holder.parentElement?.insertBefore(el, holder);
2902
+ }
2903
+ next.push({ el, zone: zone.name, index });
2904
+ });
2905
+ }
2906
+ for (const el of document.querySelectorAll(`[${ATTR2}]`)) if (!next.some((a) => a.el === el)) el.remove();
2907
+ setAnchors(
2908
+ (prev) => prev.length === next.length && prev.every((a, i) => a.el === next[i].el && a.zone === next[i].zone && a.index === next[i].index) ? prev : next
2909
+ );
2910
+ };
2911
+ React3.useEffect(() => {
2912
+ sync.current();
2913
+ });
2914
+ React3.useEffect(() => {
2915
+ let frame2 = 0;
2916
+ const observer = new MutationObserver(() => {
2917
+ if (!frame2) frame2 = requestAnimationFrame(() => {
2918
+ frame2 = 0;
2919
+ sync.current();
2920
+ });
2921
+ });
2922
+ observer.observe(document.body, { childList: true, subtree: true });
2923
+ return () => {
2924
+ observer.disconnect();
2925
+ cancelAnimationFrame(frame2);
2926
+ for (const el of document.querySelectorAll(`[${ATTR2}]`)) el.remove();
2927
+ for (const el of document.querySelectorAll(`[${HIDDEN_ATTR}]`)) el.removeAttribute(HIDDEN_ATTR);
2928
+ };
2929
+ }, []);
2930
+ return anchors;
2931
+ }
2932
+ function useRowActions({ zones, components, catalog, form }) {
2933
+ const t = useMessages();
2934
+ const clip = useClipboard();
2935
+ const [selection, setSelection] = React3.useState(null);
2936
+ const [busy, setBusy] = React3.useState(false);
2937
+ if (!form) return null;
2938
+ const editor = catalog?.editor || {};
2939
+ const groups = catalog?.groups || null;
2940
+ const hiddenName = editor.hiddenBlocks !== "off" ? catalog?.hiddenAttribute || null : null;
2941
+ const zoneOf = (name) => zones.find((z) => z.name === name);
2942
+ const labelOf = (row) => catalog?.components?.[row?.__component]?.label || components?.[row?.__component]?.info?.displayName || row?.__component || "";
2943
+ const run = async (fn) => {
2944
+ if (busy) return;
2945
+ setBusy(true);
2946
+ try {
2947
+ await fn();
2948
+ } catch {
2949
+ form.notify("warning", t.rowActionFailed);
2950
+ } finally {
2951
+ setBusy(false);
2952
+ }
2953
+ };
2954
+ const duplicate = (zoneName, index) => run(async () => {
2955
+ const zone = zoneOf(zoneName);
2956
+ const rows = form.rows(zoneName);
2957
+ const key = blockKey(rows[index]);
2958
+ const [start, end] = actionRange(rows, index, groups);
2959
+ if (rows.length + end - start + 1 > (zone?.max ?? Infinity)) return form.notify("warning", t.zoneFull);
2960
+ const items = await form.prepare(rows.slice(start, end + 1));
2961
+ const now = form.rows(zoneName);
2962
+ const at = now.findIndex((row) => blockKey(row) === key);
2963
+ if (at < 0) return;
2964
+ const after = actionRange(now, at, groups)[1] + 1;
2965
+ form.setRows(zoneName, insertRows(now, after, items, form.keys(now, after, items.length)));
2966
+ });
2967
+ const copy2 = (zoneName, keys) => run(async () => {
2968
+ const rows = form.rows(zoneName);
2969
+ const indexes = expandSelection(rows, keys.map((key) => rows.findIndex((row) => blockKey(row) === key)), groups);
2970
+ if (!indexes.length) return;
2971
+ const items = await form.prepare(indexes.map((i) => rows[i]));
2972
+ if (!storeClip({ rows: items, model: form.model, locale: form.locale, zone: zoneName })) return form.notify("warning", t.copyFailed);
2973
+ setSelection(null);
2974
+ form.notify("success", t.f("copied", { count: items.length }));
2975
+ });
2976
+ const toggleHidden = (zoneName, index) => {
2977
+ const rows = form.rows(zoneName);
2978
+ if (hiddenName) form.setRows(zoneName, setHidden(rows, index, groups, hiddenName, rows[index]?.[hiddenName] !== true));
2979
+ };
2980
+ const paste = (zoneName, at) => pasteInto(form, zoneOf(zoneName), clip, components, groups, t, at);
2981
+ return { t, editor, groups, hiddenName, clip, selection, setSelection, busy, form, labelOf, duplicate, copy: copy2, toggleHidden, paste, isMarker: (row) => isGroupMarker(row, groups) };
2982
+ }
2983
+ var RowButton = import_styled_components2.default.button`
2984
+ display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; padding: 0; border: 0;
2985
+ border-radius: 4px; cursor: pointer; background: transparent;
2986
+ color: ${({ theme, $on }) => $on ? theme.colors.warning600 : theme.colors.neutral500};
2987
+ &:hover:not(:disabled) { background: ${({ theme }) => theme.colors.neutral100}; color: ${({ theme }) => theme.colors.neutral800}; }
2988
+ &:disabled { opacity: 0.4; cursor: default; }
2989
+ &:focus-visible { outline: 2px solid ${({ theme }) => theme.colors.primary600}; outline-offset: -2px; }
2990
+ `;
2991
+ var HiddenBadge = import_styled_components2.default.span`
2992
+ margin-right: 4px; padding: 1px 6px; border-radius: 3px; font-size: 10px; font-weight: 600; line-height: 16px;
2993
+ text-transform: uppercase; letter-spacing: 0.04em;
2994
+ color: ${({ theme }) => theme.colors.warning700}; background: ${({ theme }) => theme.colors.warning100};
2995
+ `;
2996
+ function RowActions({ zones, actions, Modal: Modal2 }) {
2997
+ const a = actions;
2998
+ const e = a?.editor || {};
2999
+ const anchors = useAnchors2(zones, Boolean(a) && Boolean(a.hiddenName || e.duplicate !== false || e.clipboard !== false || a.selection));
3000
+ const [confirm, setConfirm] = React3.useState(null);
3001
+ const bypass = React3.useRef(false);
3002
+ const latest = React3.useRef({ zones, a });
3003
+ latest.current = { zones, a };
3004
+ React3.useEffect(() => {
3005
+ if (!a || e.confirmDelete === false) return;
3006
+ const onClick = (event) => {
3007
+ const button = event.target?.closest?.("button");
3008
+ if (!button || bypass.current) return;
3009
+ for (const zone of latest.current.zones) {
3010
+ const list = findZoneList(zone.label);
3011
+ if (!list?.contains(button)) continue;
3012
+ const headers = toggles(list);
3013
+ const index = headers.findIndex((toggle) => trashOf(toggle) === button);
3014
+ if (index < 0) return;
3015
+ event.preventDefault();
3016
+ event.stopPropagation();
3017
+ const row = latest.current.a?.form.rows(zone.name)[index];
3018
+ setConfirm({ button, label: headers[index].textContent?.trim() || latest.current.a?.labelOf(row), marker: Boolean(latest.current.a?.isMarker(row)) });
3019
+ return;
3020
+ }
3021
+ };
3022
+ document.addEventListener("click", onClick, true);
3023
+ return () => document.removeEventListener("click", onClick, true);
3024
+ }, [Boolean(a), e.confirmDelete]);
3025
+ React3.useEffect(() => {
3026
+ if (!a) return;
3027
+ for (const { el, zone, index } of anchors) {
3028
+ const li = el.closest("li");
3029
+ const hidden = Boolean(a.hiddenName) && a.form.rows(zone)[index]?.[a.hiddenName] === true;
3030
+ if (li) hidden ? li.setAttribute(HIDDEN_ATTR, "") : li.removeAttribute(HIDDEN_ATTR);
3031
+ }
3032
+ });
3033
+ if (!a) return null;
3034
+ const { t } = a;
3035
+ const confirmDelete = () => {
3036
+ const target = confirm?.button;
3037
+ setConfirm(null);
3038
+ if (!target?.isConnected) return;
3039
+ bypass.current = true;
3040
+ try {
3041
+ target.click();
3042
+ } finally {
3043
+ bypass.current = false;
3044
+ }
3045
+ };
3046
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
3047
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("style", { children: `li[${HIDDEN_ATTR}] button[aria-expanded] { opacity: 0.5; }` }),
3048
+ anchors.map(({ el, zone, index }) => {
3049
+ const row = a.form.rows(zone)[index];
3050
+ if (!row) return null;
3051
+ const label = a.labelOf(row);
3052
+ const acts = canAct(row, a.groups);
3053
+ const hidden = Boolean(a.hiddenName) && row[a.hiddenName] === true;
3054
+ const key = blockKey(row) || "";
3055
+ const selecting = a.selection?.zone === zone;
3056
+ return (0, import_react_dom2.createPortal)(
3057
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_design_system.Flex, { gap: 0, alignItems: "center", "data-testid": `row-actions-${zone}-${index}`, role: "group", "aria-label": t.f("rowActions", { label }), children: [
3058
+ selecting && acts && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3059
+ "input",
3060
+ {
3061
+ type: "checkbox",
3062
+ "aria-label": t.f("selectBlock", { label }),
3063
+ "data-testid": `row-select-${zone}-${index}`,
3064
+ style: { margin: "0 8px", width: 16, height: 16 },
3065
+ checked: a.selection.keys.includes(key),
3066
+ onChange: (event) => a.setSelection({ zone, keys: event.target.checked ? [...a.selection.keys, key] : a.selection.keys.filter((k) => k !== key) })
3067
+ }
3068
+ ),
3069
+ hidden && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(HiddenBadge, { "data-testid": `row-hidden-${zone}-${index}`, children: t.hiddenBadge }),
3070
+ a.hiddenName && (acts || hidden) && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3071
+ RowButton,
3072
+ {
3073
+ type: "button",
3074
+ $on: hidden,
3075
+ "aria-pressed": hidden,
3076
+ title: t.f(hidden ? "showBlock" : "hideBlock", { label }),
3077
+ "aria-label": t.f(hidden ? "showBlock" : "hideBlock", { label }),
3078
+ "data-testid": `row-hide-${zone}-${index}`,
3079
+ onClick: () => a.toggleHidden(zone, index),
3080
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Icon, { name: hidden ? "eyeOff" : "eye" })
3081
+ }
3082
+ ),
3083
+ e.duplicate !== false && acts && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3084
+ RowButton,
3085
+ {
3086
+ type: "button",
3087
+ disabled: a.busy,
3088
+ title: t.f("duplicateBlock", { label }),
3089
+ "aria-label": t.f("duplicateBlock", { label }),
3090
+ "data-testid": `row-duplicate-${zone}-${index}`,
3091
+ onClick: () => a.duplicate(zone, index),
3092
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Icon, { name: "duplicate" })
3093
+ }
3094
+ ),
3095
+ e.clipboard !== false && acts && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3096
+ RowButton,
3097
+ {
3098
+ type: "button",
3099
+ disabled: a.busy,
3100
+ title: t.f("copyBlock", { label }),
3101
+ "aria-label": t.f("copyBlock", { label }),
3102
+ "data-testid": `row-copy-${zone}-${index}`,
3103
+ onClick: () => a.copy(zone, [key]),
3104
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Icon, { name: "clipboard" })
3105
+ }
3106
+ )
3107
+ ] }),
3108
+ el,
3109
+ `${zone}:${index}`
3110
+ );
3111
+ }),
3112
+ confirm && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Modal2, { open: true, width: "48rem", onOpenChange: (open) => !open && setConfirm(null), trigger: null, title: t.confirmDeleteTitle, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_design_system.Flex, { direction: "column", alignItems: "stretch", gap: 4, "data-testid": "row-confirm-delete", children: [
3113
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system.Typography, { children: t.f("confirmDelete", { name: confirm.label }) }),
3114
+ confirm.marker && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system.Typography, { variant: "pi", textColor: "neutral600", children: t.confirmDeleteMarker }),
3115
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_design_system.Flex, { gap: 2, justifyContent: "flex-end", children: [
3116
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system.Button, { variant: "tertiary", onClick: () => setConfirm(null), "data-testid": "row-confirm-cancel", children: t.cancel }),
3117
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system.Button, { variant: "danger", onClick: confirmDelete, "data-testid": "row-confirm-ok", children: t.delete })
3118
+ ] })
3119
+ ] }) })
3120
+ ] });
3121
+ }
3122
+ function ZoneRowTools({ zone, actions: a }) {
3123
+ if (!a || a.editor.clipboard === false) return null;
3124
+ const { t } = a;
3125
+ const selecting = a.selection?.zone === zone.name;
3126
+ const count = selecting ? a.selection.keys.length : 0;
3127
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
3128
+ zone.count > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3129
+ Tool,
3130
+ {
3131
+ icon: "select",
3132
+ label: t.selectMode,
3133
+ active: selecting,
3134
+ "data-testid": `zone-select-${zone.name}`,
3135
+ onClick: () => a.setSelection(selecting ? null : { zone: zone.name, keys: [] })
3136
+ }
3137
+ ),
3138
+ selecting && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system.Button, { size: "S", variant: "secondary", disabled: !count || a.busy, onClick: () => a.copy(zone.name, a.selection.keys), "data-testid": `zone-copy-${zone.name}`, children: t.f("copySelected", { count }) }),
3139
+ a.clip && !zone.full && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system.Button, { size: "S", variant: "secondary", startIcon: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Icon, { name: "paste", size: 14 }), onClick: () => a.paste(zone.name), "data-testid": `zone-paste-${zone.name}`, children: t.f("paste", { count: a.clip.rows.length }) })
3140
+ ] });
3141
+ }
3142
+
3143
+ // admin/Gallery.tsx
3144
+ var import_jsx_runtime5 = require("react/jsx-runtime");
3145
+ var Columns = import_styled_components3.default.div`
1338
3146
  column-count: ${({ $columns }) => $columns};
1339
3147
  column-gap: 12px;
1340
3148
  > * {
@@ -1343,14 +3151,15 @@ var Columns = import_styled_components.default.div`
1343
3151
  margin-bottom: 12px;
1344
3152
  }
1345
3153
  `;
1346
- var Tile = import_styled_components.default.button`
3154
+ var Tile = import_styled_components3.default.button`
1347
3155
  display: flex;
1348
3156
  flex-direction: column;
1349
3157
  gap: 8px;
1350
3158
  text-align: left;
1351
3159
  width: 100%;
1352
3160
  padding: 8px;
1353
- border: 1px solid ${({ theme }) => theme.colors.neutral200};
3161
+ border: 1px solid ${({ theme, $active }) => $active ? theme.colors.primary600 : theme.colors.neutral200};
3162
+ box-shadow: ${({ theme, $active }) => $active ? `0 0 0 1px ${theme.colors.primary600}` : "none"};
1354
3163
  border-radius: 4px;
1355
3164
  background: ${({ theme }) => theme.colors.neutral0};
1356
3165
  cursor: pointer;
@@ -1362,13 +3171,13 @@ var Tile = import_styled_components.default.button`
1362
3171
  outline-offset: 2px;
1363
3172
  }
1364
3173
  `;
1365
- var Clamp = (0, import_styled_components.default)(import_design_system.Typography)`
3174
+ var Clamp = (0, import_styled_components3.default)(import_design_system2.Typography)`
1366
3175
  display: -webkit-box;
1367
3176
  -webkit-line-clamp: 2;
1368
3177
  -webkit-box-orient: vertical;
1369
3178
  overflow: hidden;
1370
3179
  `;
1371
- var Slider = import_styled_components.default.input`
3180
+ var Slider = import_styled_components3.default.input`
1372
3181
  appearance: none;
1373
3182
  -webkit-appearance: none;
1374
3183
  width: 8rem;
@@ -1422,7 +3231,7 @@ var remember = (key, value) => {
1422
3231
  } catch {
1423
3232
  }
1424
3233
  };
1425
- var Frame = import_styled_components.default.div`
3234
+ var Frame = import_styled_components3.default.div`
1426
3235
  width: 100%;
1427
3236
  overflow: hidden;
1428
3237
  border-radius: 4px;
@@ -1445,9 +3254,9 @@ function Thumb({
1445
3254
  blank
1446
3255
  }) {
1447
3256
  const key = candidates.join("|");
1448
- const [state, setState] = React2.useState({ key, index: 0 });
3257
+ const [state, setState] = React4.useState({ key, index: 0 });
1449
3258
  const index = state.key === key ? state.index : 0;
1450
- const resolved = React2.useRef("");
3259
+ const resolved = React4.useRef("");
1451
3260
  const report = (value) => {
1452
3261
  const tag = `${key}:${value}`;
1453
3262
  if (resolved.current !== tag) {
@@ -1455,11 +3264,11 @@ function Thumb({
1455
3264
  onResolved?.(value);
1456
3265
  }
1457
3266
  };
1458
- React2.useEffect(() => {
3267
+ React4.useEffect(() => {
1459
3268
  if (index >= candidates.length) report(-1);
1460
3269
  });
1461
3270
  if (index >= candidates.length)
1462
- return blank ? null : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Frame, { $fixed: true, "data-thumb": "wireframe", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3271
+ return blank ? null : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Frame, { $fixed: true, "data-thumb": "wireframe", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1463
3272
  Wireframe,
1464
3273
  {
1465
3274
  template,
@@ -1467,7 +3276,7 @@ function Thumb({
1467
3276
  noPreview
1468
3277
  }
1469
3278
  ) });
1470
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Frame, { "data-thumb": String(index), children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3279
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Frame, { "data-thumb": String(index), children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1471
3280
  "img",
1472
3281
  {
1473
3282
  src: candidates[index],
@@ -1479,70 +3288,605 @@ function Thumb({
1479
3288
  candidates[index]
1480
3289
  ) });
1481
3290
  }
1482
- function Card({ entry, palette, showFields, onSelect }) {
1483
- const t = useMessages();
1484
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1485
- Tile,
1486
- {
1487
- type: "button",
1488
- onClick: onSelect,
1489
- title: entry.uid,
1490
- "data-testid": `blockscene-${entry.uid}`,
1491
- children: [
1492
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1493
- Thumb,
1494
- {
1495
- candidates: entry.candidates,
1496
- template: entry.template,
1497
- palette,
1498
- noPreview: t.noPreview
1499
- }
1500
- ),
1501
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1502
- import_design_system.Typography,
1503
- {
1504
- variant: "pi",
1505
- fontWeight: "bold",
1506
- textColor: "neutral800",
1507
- ellipsis: true,
1508
- children: entry.label
1509
- }
1510
- ),
1511
- entry.description && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Clamp, { variant: "pi", textColor: "neutral600", children: entry.description }),
1512
- showFields && entry.fields.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1513
- import_design_system.Typography,
1514
- {
1515
- variant: "pi",
1516
- textColor: "neutral500",
1517
- style: { fontStyle: "italic" },
1518
- "data-testid": "block-fields",
1519
- children: entry.fields.map((f) => `${f.name} (${f.type})`).join(", ")
1520
- }
1521
- )
1522
- ]
1523
- }
1524
- );
1525
- }
1526
- function ZoneGallery({
1527
- zone,
1528
- components,
1529
- add,
1530
- Modal: Modal2,
1531
- Toggle,
1532
- get,
1533
- controlled
1534
- }) {
1535
- const t = useMessages();
1536
- const [ownOpen, setOwnOpen] = React2.useState(false);
1537
- const open = controlled ? controlled.open : ownOpen;
1538
- const setOpen = controlled ? controlled.onOpenChange : setOwnOpen;
1539
- const [query, setQuery] = React2.useState("");
1540
- const [category, setCategory] = React2.useState("all");
1541
- const [showFields, setShowFields] = React2.useState(readShowFields);
1542
- const [columns, setColumns] = React2.useState(readColumns);
1543
- const [config, setConfig] = React2.useState(null);
1544
- const [error, setError] = React2.useState("");
1545
- React2.useEffect(() => {
3291
+ var Layout = import_styled_components3.default.div`
3292
+ display: flex;
3293
+ height: min(70vh, 720px);
3294
+ min-height: 320px;
3295
+ `;
3296
+ var Side = import_styled_components3.default.nav`
3297
+ flex: 0 0 ${({ $collapsed }) => $collapsed ? "48px" : "200px"};
3298
+ display: flex;
3299
+ flex-direction: column;
3300
+ gap: 2px;
3301
+ padding-right: 12px;
3302
+ margin-right: 12px;
3303
+ border-right: 1px solid ${({ theme }) => theme.colors.neutral150};
3304
+ overflow-y: auto;
3305
+ `;
3306
+ var NavItem = import_styled_components3.default.button`
3307
+ display: flex;
3308
+ align-items: center;
3309
+ justify-content: ${({ $collapsed }) => $collapsed ? "center" : "flex-start"};
3310
+ gap: 8px;
3311
+ width: 100%;
3312
+ padding: 6px 8px;
3313
+ border: 0;
3314
+ border-radius: 4px;
3315
+ cursor: pointer;
3316
+ text-align: left;
3317
+ font-size: 13px;
3318
+ color: ${({ theme, $active }) => $active ? theme.colors.primary600 : theme.colors.neutral700};
3319
+ background: ${({ theme, $active }) => $active ? theme.colors.primary100 : "transparent"};
3320
+ &:hover {
3321
+ background: ${({ theme, $active }) => $active ? theme.colors.primary100 : theme.colors.neutral100};
3322
+ }
3323
+ &:focus-visible {
3324
+ outline: 2px solid ${({ theme }) => theme.colors.primary600};
3325
+ outline-offset: -2px;
3326
+ }
3327
+ .bs-label {
3328
+ flex: 1;
3329
+ overflow: hidden;
3330
+ text-overflow: ellipsis;
3331
+ white-space: nowrap;
3332
+ }
3333
+ .bs-count {
3334
+ color: ${({ theme }) => theme.colors.neutral500};
3335
+ font-size: 12px;
3336
+ }
3337
+ `;
3338
+ var Main = import_styled_components3.default.div`
3339
+ flex: 1;
3340
+ min-width: 0;
3341
+ display: flex;
3342
+ flex-direction: column;
3343
+ gap: 12px;
3344
+ `;
3345
+ var Menu = import_styled_components3.default.div`
3346
+ position: relative;
3347
+ `;
3348
+ var MenuButton = import_styled_components3.default.button`
3349
+ display: inline-flex;
3350
+ align-items: center;
3351
+ gap: 6px;
3352
+ height: 32px;
3353
+ padding: 0 10px;
3354
+ border-radius: 4px;
3355
+ cursor: pointer;
3356
+ font-size: 13px;
3357
+ white-space: nowrap;
3358
+ border: 1px solid ${({ theme, $active }) => $active ? theme.colors.primary600 : theme.colors.neutral200};
3359
+ color: ${({ theme, $active }) => $active ? theme.colors.primary600 : theme.colors.neutral800};
3360
+ background: ${({ theme, $active }) => $active ? theme.colors.primary100 : theme.colors.neutral0};
3361
+ &:focus-visible {
3362
+ outline: 2px solid ${({ theme }) => theme.colors.primary600};
3363
+ outline-offset: 1px;
3364
+ }
3365
+ `;
3366
+ var Popover = import_styled_components3.default.div`
3367
+ position: absolute;
3368
+ top: calc(100% + 4px);
3369
+ left: 0;
3370
+ z-index: 10;
3371
+ min-width: 180px;
3372
+ max-height: 280px;
3373
+ overflow-y: auto;
3374
+ padding: 4px;
3375
+ border: 1px solid ${({ theme }) => theme.colors.neutral200};
3376
+ border-radius: 4px;
3377
+ background: ${({ theme }) => theme.colors.neutral0};
3378
+ box-shadow: 0 2px 12px rgba(33, 33, 52, 0.15);
3379
+ label {
3380
+ display: flex;
3381
+ align-items: center;
3382
+ gap: 8px;
3383
+ padding: 6px 8px;
3384
+ border-radius: 4px;
3385
+ cursor: pointer;
3386
+ font-size: 13px;
3387
+ color: ${({ theme }) => theme.colors.neutral800};
3388
+ }
3389
+ label:hover {
3390
+ background: ${({ theme }) => theme.colors.neutral100};
3391
+ }
3392
+ .bs-count {
3393
+ margin-left: auto;
3394
+ color: ${({ theme }) => theme.colors.neutral500};
3395
+ }
3396
+ `;
3397
+ var Chip = import_styled_components3.default.button`
3398
+ display: inline-flex;
3399
+ align-items: center;
3400
+ gap: 4px;
3401
+ padding: 2px 8px;
3402
+ border-radius: 12px;
3403
+ cursor: pointer;
3404
+ font-size: 12px;
3405
+ border: 1px solid ${({ theme }) => theme.colors.primary200};
3406
+ color: ${({ theme }) => theme.colors.primary700};
3407
+ background: ${({ theme }) => theme.colors.primary100};
3408
+ `;
3409
+ var Badge = import_styled_components3.default.span`
3410
+ padding: 1px 6px;
3411
+ border-radius: 3px;
3412
+ font-size: 10px;
3413
+ font-weight: 600;
3414
+ line-height: 16px;
3415
+ letter-spacing: 0.04em;
3416
+ text-transform: uppercase;
3417
+ color: ${({ theme }) => theme.colors.neutral0};
3418
+ background: ${({ theme }) => theme.colors.neutral800};
3419
+ `;
3420
+ var CardBox = import_styled_components3.default.div`
3421
+ position: relative;
3422
+ &:hover .bs-quick,
3423
+ &:focus-within .bs-quick {
3424
+ opacity: 1;
3425
+ }
3426
+ `;
3427
+ var CardBadges = import_styled_components3.default.div`
3428
+ position: absolute;
3429
+ top: 14px;
3430
+ left: 14px;
3431
+ right: 48px;
3432
+ display: flex;
3433
+ flex-wrap: wrap;
3434
+ gap: 4px;
3435
+ pointer-events: none;
3436
+ `;
3437
+ var Round = import_styled_components3.default.button`
3438
+ position: absolute;
3439
+ right: 14px;
3440
+ display: inline-flex;
3441
+ align-items: center;
3442
+ justify-content: center;
3443
+ width: 28px;
3444
+ height: 28px;
3445
+ padding: 0;
3446
+ border-radius: 50%;
3447
+ cursor: pointer;
3448
+ border: 1px solid ${({ theme, $primary }) => $primary ? theme.colors.primary600 : theme.colors.neutral200};
3449
+ color: ${({ theme, $on, $primary }) => $primary ? theme.colors.neutral0 : $on ? theme.colors.warning500 : theme.colors.neutral600};
3450
+ background: ${({ theme, $primary }) => $primary ? theme.colors.primary600 : theme.colors.neutral0};
3451
+ &:focus-visible {
3452
+ outline: 2px solid ${({ theme }) => theme.colors.primary600};
3453
+ outline-offset: 1px;
3454
+ }
3455
+ &.bs-quick {
3456
+ opacity: 0;
3457
+ transition: opacity 120ms;
3458
+ }
3459
+ `;
3460
+ var Mono = import_styled_components3.default.code`
3461
+ font-size: 12px;
3462
+ color: ${({ theme }) => theme.colors.neutral600};
3463
+ `;
3464
+ var SIDEBAR_KEY = "blockscene:gallery-sidebar";
3465
+ var readCollapsed = () => {
3466
+ try {
3467
+ return window.localStorage.getItem(SIDEBAR_KEY) === "collapsed";
3468
+ } catch {
3469
+ return false;
3470
+ }
3471
+ };
3472
+ var TYPE_ICONS = { hero: "hero", text: "text", media: "image", listing: "list", cards: "cards", cta: "link", form: "form", layout: "layout" };
3473
+ var FILTER_MENUS = ["tags", "media", "content"];
3474
+ var toggled = (list, value) => list.includes(value) ? list.filter((item) => item !== value) : [...list, value];
3475
+ function FilterMenu({ name, label, options, picked, onChange, labelOf }) {
3476
+ const [open, setOpen] = React4.useState(false);
3477
+ const ref = React4.useRef(null);
3478
+ React4.useEffect(() => {
3479
+ if (!open) return;
3480
+ const down = (event) => {
3481
+ if (!ref.current?.contains(event.target)) setOpen(false);
3482
+ };
3483
+ const key = (event) => {
3484
+ if (event.key !== "Escape") return;
3485
+ event.stopPropagation();
3486
+ event.preventDefault();
3487
+ setOpen(false);
3488
+ };
3489
+ document.addEventListener("mousedown", down, true);
3490
+ window.addEventListener("keydown", key, true);
3491
+ return () => {
3492
+ document.removeEventListener("mousedown", down, true);
3493
+ window.removeEventListener("keydown", key, true);
3494
+ };
3495
+ }, [open]);
3496
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Menu, { ref, "data-testid": `gallery-filter-${name}`, children: [
3497
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3498
+ MenuButton,
3499
+ {
3500
+ type: "button",
3501
+ "aria-expanded": open,
3502
+ $active: picked.length > 0,
3503
+ onClick: () => setOpen(!open),
3504
+ children: [
3505
+ picked.length ? `${label} (${picked.length})` : label,
3506
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Icon, { name: "chevron", size: 14 })
3507
+ ]
3508
+ }
3509
+ ),
3510
+ open && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Popover, { role: "group", "aria-label": label, children: options.map(([value, count]) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { children: [
3511
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3512
+ "input",
3513
+ {
3514
+ type: "checkbox",
3515
+ checked: picked.includes(value),
3516
+ onChange: () => onChange(toggled(picked, value)),
3517
+ "data-testid": `gallery-option-${name}-${value}`
3518
+ }
3519
+ ),
3520
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: labelOf(value) }),
3521
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "bs-count", children: count })
3522
+ ] }, value)) })
3523
+ ] });
3524
+ }
3525
+ function Badges({ entry, t, tags }) {
3526
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
3527
+ entry.facets.map((facet) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Badge, { children: t.facets[facet] || facet }, facet)),
3528
+ tags && entry.tags.map((tag) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Badge, { children: tag }, `tag:${tag}`))
3529
+ ] });
3530
+ }
3531
+ function Card({ entry, palette, showFields, starred, active, onOpen, onInsert, onStar }) {
3532
+ const t = useMessages();
3533
+ const timer = React4.useRef(null);
3534
+ React4.useEffect(() => () => clearTimeout(timer.current), []);
3535
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(CardBox, { title: entry.uid, "data-testid": `blockscene-${entry.uid}`, children: [
3536
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3537
+ Tile,
3538
+ {
3539
+ type: "button",
3540
+ $active: active,
3541
+ onClick: (event) => {
3542
+ clearTimeout(timer.current);
3543
+ if (event.detail === 2) onInsert();
3544
+ else if (event.detail === 1) timer.current = setTimeout(onOpen, 220);
3545
+ else onOpen();
3546
+ },
3547
+ onKeyDown: (event) => {
3548
+ if (event.key !== "Enter") return;
3549
+ event.preventDefault();
3550
+ onInsert();
3551
+ },
3552
+ children: [
3553
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3554
+ Thumb,
3555
+ {
3556
+ candidates: entry.candidates,
3557
+ template: entry.template,
3558
+ palette,
3559
+ noPreview: t.noPreview
3560
+ }
3561
+ ),
3562
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system2.Typography, { variant: "pi", fontWeight: "bold", textColor: "neutral800", ellipsis: true, children: entry.label }),
3563
+ entry.description && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Clamp, { variant: "pi", textColor: "neutral600", children: entry.description }),
3564
+ showFields && entry.fields.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3565
+ import_design_system2.Typography,
3566
+ {
3567
+ variant: "pi",
3568
+ textColor: "neutral500",
3569
+ style: { fontStyle: "italic" },
3570
+ "data-testid": "block-fields",
3571
+ children: entry.fields.map((f) => `${f.name} (${f.type})`).join(", ")
3572
+ }
3573
+ )
3574
+ ]
3575
+ }
3576
+ ),
3577
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CardBadges, { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Badges, { entry, t }) }),
3578
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3579
+ Round,
3580
+ {
3581
+ type: "button",
3582
+ style: { top: 12 },
3583
+ $on: starred,
3584
+ "aria-pressed": starred,
3585
+ "aria-label": starred ? t.unstar : t.star,
3586
+ title: starred ? t.unstar : t.star,
3587
+ onClick: onStar,
3588
+ "data-testid": `gallery-star-${entry.uid}`,
3589
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Icon, { name: "star", size: 16, filled: starred })
3590
+ }
3591
+ ),
3592
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3593
+ Round,
3594
+ {
3595
+ type: "button",
3596
+ className: "bs-quick",
3597
+ style: { top: 46 },
3598
+ $primary: true,
3599
+ "aria-label": t.f("quickInsert", { label: entry.label }),
3600
+ title: t.f("quickInsert", { label: entry.label }),
3601
+ onClick: onInsert,
3602
+ "data-testid": `gallery-quick-${entry.uid}`,
3603
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Icon, { name: "plus", size: 16 })
3604
+ }
3605
+ )
3606
+ ] });
3607
+ }
3608
+ var Scrim = import_styled_components3.default.div`
3609
+ position: absolute;
3610
+ inset: 0;
3611
+ z-index: 4;
3612
+ opacity: 0.35;
3613
+ background: ${({ theme }) => theme.colors.neutral800};
3614
+ `;
3615
+ var Panel = import_styled_components3.default.section`
3616
+ position: absolute;
3617
+ inset: 16px;
3618
+ z-index: 5;
3619
+ display: flex;
3620
+ flex-direction: column;
3621
+ overflow: hidden;
3622
+ border: 1px solid ${({ theme }) => theme.colors.neutral200};
3623
+ border-radius: 4px;
3624
+ background: ${({ theme }) => theme.colors.neutral0};
3625
+ box-shadow: ${({ theme }) => theme.shadows?.popupShadow || "0 8px 32px rgba(33, 33, 52, 0.3)"};
3626
+ transform-origin: top left;
3627
+ `;
3628
+ var Stage = import_styled_components3.default.div`
3629
+ position: relative;
3630
+ flex: 1;
3631
+ min-height: 0;
3632
+ overflow: hidden;
3633
+ background-color: ${({ theme }) => stageBackground(theme).backgroundColor};
3634
+ background-image: ${({ theme }) => stageBackground(theme).backgroundImage};
3635
+ background-size: 24px 24px;
3636
+ `;
3637
+ var Still = import_styled_components3.default.div`
3638
+ position: absolute;
3639
+ inset: 0;
3640
+ /* Hidden once the live page has faded in over it (device widths would show it in the gutters). */
3641
+ visibility: ${({ $covered }) => $covered ? "hidden" : "visible"};
3642
+ transition: visibility 0s linear ${({ $covered }) => $covered ? "200ms" : "0s"};
3643
+ > [data-thumb] {
3644
+ width: 100%;
3645
+ height: 100%;
3646
+ aspect-ratio: auto;
3647
+ background: transparent;
3648
+ }
3649
+ img,
3650
+ svg {
3651
+ width: 100%;
3652
+ height: 100%;
3653
+ object-fit: contain;
3654
+ }
3655
+ `;
3656
+ var Live = import_styled_components3.default.iframe`
3657
+ position: absolute;
3658
+ top: 0;
3659
+ border: 0;
3660
+ background: white;
3661
+ transform-origin: top left;
3662
+ opacity: ${({ $shown }) => $shown ? 1 : 0};
3663
+ transition: opacity 200ms ease-out;
3664
+ @media (prefers-reduced-motion: reduce) {
3665
+ transition: none;
3666
+ }
3667
+ `;
3668
+ var Strip = import_styled_components3.default.div`
3669
+ display: flex;
3670
+ flex-direction: column;
3671
+ gap: 6px;
3672
+ padding: 10px 12px;
3673
+ border-top: 1px solid ${({ theme }) => theme.colors.neutral150};
3674
+ `;
3675
+ var FieldsButton = import_styled_components3.default.button`
3676
+ border: 0;
3677
+ padding: 0;
3678
+ cursor: pointer;
3679
+ background: none;
3680
+ font-size: 12px;
3681
+ color: ${({ theme }) => theme.colors.primary600};
3682
+ `;
3683
+ var reducedMotion = () => {
3684
+ try {
3685
+ return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
3686
+ } catch {
3687
+ return false;
3688
+ }
3689
+ };
3690
+ var flip = (panel, scrim, card, back) => {
3691
+ if (!panel?.animate || reducedMotion()) return null;
3692
+ const to = panel.getBoundingClientRect();
3693
+ const from = card?.getBoundingClientRect();
3694
+ const timing = { duration: 250, easing: "cubic-bezier(0.2, 0, 0, 1)", fill: back ? "forwards" : "none" };
3695
+ const frames = [
3696
+ from?.width && to.width ? { transform: `translate(${from.left - to.left}px, ${from.top - to.top}px) scale(${from.width / to.width}, ${from.height / to.height})`, opacity: 0.6 } : { transform: "scale(0.96)", opacity: 0 },
3697
+ { transform: "none", opacity: 1 }
3698
+ ];
3699
+ const dim = [{ opacity: 0 }, { opacity: 0.35 }];
3700
+ scrim?.animate(back ? dim.reverse() : dim, timing);
3701
+ return panel.animate(back ? frames.reverse() : frames, timing);
3702
+ };
3703
+ var newChannel = () => crypto.randomUUID?.() || `${Date.now()}-${Math.random().toString(36).slice(2)}`;
3704
+ function useLiveSource(entry, config, components, get, contentType, locale) {
3705
+ const editor = config?.editor || {};
3706
+ const [native, setNative] = React4.useState(null);
3707
+ const plain = editor.blockPreviewUrl ? blockPreviewSrc(editor.blockPreviewUrl, entry.uid, { locale: locale || "" }) : null;
3708
+ React4.useEffect(() => {
3709
+ if (editor.blockPreviewUrl || editor.previewUrl || !contentType || !get) return;
3710
+ let active = true;
3711
+ get(`/content-manager/preview/url/${contentType}?${new URLSearchParams({ status: "draft", ...locale ? { locale } : {} })}`).then(({ data }) => {
3712
+ if (active && typeof data?.data?.url === "string") setNative(`${new URL(data.data.url).origin}/block-preview/page`);
3713
+ }).catch(() => {
3714
+ });
3715
+ return () => {
3716
+ active = false;
3717
+ };
3718
+ }, [editor.blockPreviewUrl, editor.previewUrl, contentType, locale, get]);
3719
+ const channel = React4.useMemo(newChannel, []);
3720
+ if (editor.blockPreviewUrl) return plain ? { kind: "plain", src: plain, origin: "", channel } : null;
3721
+ const base = editor.previewUrl || native;
3722
+ if (!base) return null;
3723
+ try {
3724
+ const url = new URL(base);
3725
+ url.searchParams.set("channel", channel);
3726
+ return { kind: "bridge", src: url.href, origin: url.origin, channel };
3727
+ } catch {
3728
+ return null;
3729
+ }
3730
+ }
3731
+ var LIVE_TIMEOUT = 8e3;
3732
+ function Magnify({ entry, palette, config, components, get, contentType, locale, starred, onStar, onInsert, onClose, cardOf }) {
3733
+ const t = useMessages();
3734
+ const panel = React4.useRef(null);
3735
+ const scrim = React4.useRef(null);
3736
+ const closeButton = React4.useRef(null);
3737
+ const frame2 = React4.useRef(null);
3738
+ const closing = React4.useRef(false);
3739
+ const [stageEl, setStageEl] = React4.useState(null);
3740
+ const stage = useStageSize(stageEl);
3741
+ const [device, setDevice] = React4.useState("fit");
3742
+ const [fields, setFields] = React4.useState(false);
3743
+ const [state, setState] = React4.useState("loading");
3744
+ const live = useLiveSource(entry, config, components, get, contentType, locale);
3745
+ React4.useLayoutEffect(() => {
3746
+ flip(panel.current, scrim.current, cardOf(entry.uid), false);
3747
+ closeButton.current?.querySelector("button")?.focus({ preventScroll: true });
3748
+ }, []);
3749
+ const close = () => {
3750
+ if (closing.current) return;
3751
+ closing.current = true;
3752
+ const card = cardOf(entry.uid);
3753
+ const done = () => {
3754
+ onClose();
3755
+ card?.querySelector("button")?.focus({ preventScroll: true });
3756
+ };
3757
+ const animation = flip(panel.current, scrim.current, card, true);
3758
+ if (animation) animation.onfinish = done;
3759
+ else done();
3760
+ };
3761
+ const closeRef = React4.useRef(close);
3762
+ closeRef.current = close;
3763
+ React4.useEffect(() => {
3764
+ const key = (event) => {
3765
+ if (event.key !== "Escape") return;
3766
+ event.stopPropagation();
3767
+ event.preventDefault();
3768
+ closeRef.current();
3769
+ };
3770
+ window.addEventListener("keydown", key, true);
3771
+ return () => window.removeEventListener("keydown", key, true);
3772
+ }, []);
3773
+ const src = live?.src;
3774
+ React4.useEffect(() => {
3775
+ if (!live) return;
3776
+ setState("loading");
3777
+ const timer = setTimeout(() => setState((s) => s === "ready" ? s : "failed"), LIVE_TIMEOUT);
3778
+ const receive = (event) => {
3779
+ if (live.kind !== "bridge" || !isPreviewMessage(event, live.origin, frame2.current?.contentWindow, live.channel, "ready")) return;
3780
+ clearTimeout(timer);
3781
+ setState("ready");
3782
+ let blocks = [];
3783
+ try {
3784
+ const row = { ...componentDefaults(components[entry.uid], components), __component: entry.uid, __temp_key__: "blockscene-magnify" };
3785
+ blocks = projectPage([row], components, window.location.origin);
3786
+ } catch {
3787
+ }
3788
+ frame2.current?.contentWindow?.postMessage(
3789
+ { protocol: PROTOCOL, channel: live.channel, type: "update-page", blocks, groups: null, locale: t.locale, mode: "preview" },
3790
+ live.origin
3791
+ );
3792
+ };
3793
+ window.addEventListener("message", receive);
3794
+ return () => {
3795
+ clearTimeout(timer);
3796
+ window.removeEventListener("message", receive);
3797
+ };
3798
+ }, [src]);
3799
+ const onLoad = () => {
3800
+ if (live?.kind === "plain") setState("ready");
3801
+ else frame2.current?.contentWindow?.postMessage({ protocol: PROTOCOL, channel: live.channel, type: "ping" }, live.origin);
3802
+ };
3803
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
3804
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Scrim, { ref: scrim, onClick: close, "data-testid": "gallery-magnify-scrim" }),
3805
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Panel, { ref: panel, "aria-label": entry.label, "data-testid": "gallery-detail", "data-live": live ? state : "none", children: [
3806
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_design_system2.Flex, { gap: 2, padding: 2, alignItems: "center", justifyContent: "space-between", children: [
3807
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system2.Flex, { gap: 1, role: "group", "aria-label": t.deviceGroup, "data-testid": "gallery-magnify-devices", children: Object.keys(DEVICES).map((value) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3808
+ Tool,
3809
+ {
3810
+ icon: value,
3811
+ label: DEVICES[value] ? `${t.device[value]} \xB7 ${DEVICES[value]} px` : t.device[value],
3812
+ active: device === value,
3813
+ onClick: () => setDevice(value)
3814
+ },
3815
+ value
3816
+ )) }),
3817
+ live && state === "failed" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system2.Typography, { variant: "pi", textColor: "neutral600", "data-testid": "gallery-magnify-note", children: t.livePreviewFailed }),
3818
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { ref: closeButton, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Tool, { icon: "close", label: t.closeDetail, onClick: close }) })
3819
+ ] }),
3820
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Stage, { ref: setStageEl, $device: device !== "fit", "data-device": device, children: [
3821
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Still, { $covered: Boolean(live) && state === "ready", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Thumb, { candidates: entry.candidates, template: entry.template, palette, noPreview: t.noPreview, eager: true }) }),
3822
+ live && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3823
+ Live,
3824
+ {
3825
+ ref: frame2,
3826
+ title: t.blockPreview,
3827
+ src: live.src,
3828
+ sandbox: "allow-scripts allow-same-origin",
3829
+ referrerPolicy: "no-referrer",
3830
+ onLoad,
3831
+ $shown: state === "ready",
3832
+ style: frameStyle(device, stage),
3833
+ "data-testid": "gallery-magnify-frame"
3834
+ },
3835
+ live.src
3836
+ )
3837
+ ] }),
3838
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Strip, { children: [
3839
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_design_system2.Flex, { gap: 2, alignItems: "center", wrap: "wrap", children: [
3840
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system2.Typography, { variant: "omega", fontWeight: "bold", tag: "h3", ellipsis: true, children: entry.label }),
3841
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Mono, { children: entry.uid }),
3842
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Badge, { children: t.typologies[entry.typology] || entry.typology }),
3843
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Badges, { entry, t, tags: true }),
3844
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_design_system2.Flex, { gap: 2, alignItems: "center", style: { marginLeft: "auto" }, children: [
3845
+ entry.fields.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(FieldsButton, { type: "button", "aria-expanded": fields, onClick: () => setFields(!fields), "data-testid": "gallery-detail-fields-toggle", children: `${t.showFields} (${entry.fields.length})` }),
3846
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Tool, { icon: "star", label: starred ? t.unstar : t.star, active: starred, onClick: onStar, "data-testid": "gallery-detail-star" }),
3847
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system2.Button, { onClick: onInsert, "data-testid": "gallery-insert", children: t.insert })
3848
+ ] })
3849
+ ] }),
3850
+ entry.description && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Clamp, { variant: "pi", textColor: "neutral700", children: entry.description }),
3851
+ fields && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system2.Flex, { gap: 3, wrap: "wrap", "data-testid": "gallery-detail-fields", children: entry.fields.map((field) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_design_system2.Typography, { variant: "pi", textColor: "neutral800", children: [
3852
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: field.name }),
3853
+ " ",
3854
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Mono, { children: field.type })
3855
+ ] }, field.name)) })
3856
+ ] })
3857
+ ] })
3858
+ ] });
3859
+ }
3860
+ var NO_FILTERS = { tags: [], media: [], content: [] };
3861
+ function ZoneGallery({
3862
+ zone,
3863
+ components,
3864
+ add,
3865
+ Modal: Modal2,
3866
+ Toggle,
3867
+ get,
3868
+ put,
3869
+ controlled,
3870
+ contentType,
3871
+ locale
3872
+ }) {
3873
+ const t = useMessages();
3874
+ const grid = React4.useRef(null);
3875
+ const cardOf = (uid) => grid.current?.querySelector(`[data-testid="blockscene-${CSS.escape(uid)}"]`) || null;
3876
+ const [ownOpen, setOwnOpen] = React4.useState(false);
3877
+ const open = controlled ? controlled.open : ownOpen;
3878
+ const setOpen = controlled ? controlled.onOpenChange : setOwnOpen;
3879
+ const [query, setQuery] = React4.useState("");
3880
+ const [view, setView] = React4.useState("all");
3881
+ const [picked, setPicked] = React4.useState(NO_FILTERS);
3882
+ const [detail, setDetail] = React4.useState(null);
3883
+ const [collapsed, setCollapsed] = React4.useState(readCollapsed);
3884
+ const [prefs, setPrefs] = React4.useState({ starred: [], recent: [] });
3885
+ const [showFields, setShowFields] = React4.useState(readShowFields);
3886
+ const [columns, setColumns] = React4.useState(readColumns);
3887
+ const [config, setConfig] = React4.useState(null);
3888
+ const [error, setError] = React4.useState("");
3889
+ React4.useEffect(() => {
1546
3890
  if (controlled) return;
1547
3891
  const onClick = (event) => {
1548
3892
  const button = event.target?.closest?.("button");
@@ -1554,36 +3898,56 @@ function ZoneGallery({
1554
3898
  document.addEventListener("click", onClick, true);
1555
3899
  return () => document.removeEventListener("click", onClick, true);
1556
3900
  }, [controlled, zone.name, zone.label]);
1557
- React2.useEffect(() => {
3901
+ React4.useEffect(() => {
1558
3902
  if (!open) return;
1559
3903
  let active = true;
1560
3904
  setError("");
1561
3905
  setConfig(null);
3906
+ setDetail(null);
1562
3907
  get("/blockscene/catalog").then(({ data }) => {
1563
3908
  if (active) setConfig(data);
1564
3909
  }).catch(() => {
1565
3910
  if (active) setError(t.failed);
1566
3911
  });
3912
+ get("/blockscene/me/prefs").then(({ data }) => {
3913
+ if (active && data) setPrefs({ starred: data.starred || [], recent: data.recent || [] });
3914
+ }).catch(() => {
3915
+ });
1567
3916
  return () => {
1568
3917
  active = false;
1569
3918
  };
1570
3919
  }, [open, get, t.failed]);
1571
- const entries = config ? entriesFor(zone, components, config, query, category) : [];
1572
- const categories = config ? categoriesFor(zone, components, config) : [];
1573
- const total = categories.reduce(
1574
- (sum, [, count]) => sum + count,
1575
- 0
1576
- );
1577
- const filtering = query.trim() !== "" || category !== "all";
3920
+ const savePrefs = (next) => {
3921
+ setPrefs(next);
3922
+ put?.("/blockscene/me/prefs", next).catch(() => {
3923
+ });
3924
+ };
3925
+ const star = (uid) => savePrefs({ ...prefs, starred: toggled(prefs.starred, uid) });
3926
+ const options = config ? optionsFor(zone, components, config) : null;
3927
+ const inZone = (list) => options ? list.filter((uid) => options.uids.includes(uid)) : [];
3928
+ const filter = {
3929
+ ...picked,
3930
+ typology: TYPOLOGIES.includes(view) ? view : void 0,
3931
+ uids: view === "recent" ? prefs.recent : view === "starred" ? prefs.starred : void 0
3932
+ };
3933
+ let entries = config ? entriesFor(zone, components, config, query, filter) : [];
3934
+ if (view === "recent") entries = [...entries].sort((a, b) => prefs.recent.indexOf(a.uid) - prefs.recent.indexOf(b.uid));
3935
+ const chosen = entries.find((entry) => entry.uid === detail);
3936
+ const chips = FILTER_MENUS.flatMap((menu) => picked[menu].map((value) => ({ menu, value })));
3937
+ const filtering = query.trim() !== "" || view !== "all" || chips.length > 0;
1578
3938
  const reset = () => {
1579
3939
  setQuery("");
1580
- setCategory("all");
3940
+ setView("all");
3941
+ setPicked(NO_FILTERS);
1581
3942
  };
3943
+ const labelOf = (menu, value) => menu === "tags" ? value : t.facets[value] || value;
1582
3944
  const select = (uid) => {
1583
3945
  try {
1584
3946
  if (controlled ? (controlled.onSelect(uid), true) : add(zone, uid)) {
3947
+ savePrefs({ ...prefs, recent: [uid, ...prefs.recent.filter((item) => item !== uid)].slice(0, 20) });
1585
3948
  setOpen(false);
1586
3949
  setQuery("");
3950
+ setDetail(null);
1587
3951
  }
1588
3952
  } catch {
1589
3953
  setError(t.insertFailed);
@@ -1597,164 +3961,207 @@ function ZoneGallery({
1597
3961
  setShowFields(value);
1598
3962
  remember(SHOW_FIELDS_KEY, String(value));
1599
3963
  };
1600
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3964
+ const toggleSidebar = () => {
3965
+ setCollapsed(!collapsed);
3966
+ remember(SIDEBAR_KEY, collapsed ? "expanded" : "collapsed");
3967
+ };
3968
+ const nav = (key, icon, label, count) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3969
+ NavItem,
3970
+ {
3971
+ type: "button",
3972
+ $active: view === key,
3973
+ $collapsed: collapsed,
3974
+ "aria-current": view === key ? "true" : void 0,
3975
+ "aria-label": collapsed ? `${label} (${count})` : void 0,
3976
+ title: collapsed ? `${label} (${count})` : void 0,
3977
+ onClick: () => setView(key),
3978
+ "data-testid": `gallery-nav-${key}`,
3979
+ children: [
3980
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Icon, { name: icon }),
3981
+ !collapsed && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
3982
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "bs-label", children: label }),
3983
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "bs-count", children: count })
3984
+ ] })
3985
+ ]
3986
+ },
3987
+ key
3988
+ );
3989
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1601
3990
  Modal2,
1602
3991
  {
1603
3992
  open,
1604
3993
  onOpenChange: setOpen,
1605
3994
  title: t.gallery,
1606
- trigger: controlled ? null : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1607
- import_design_system.Button,
3995
+ trigger: controlled ? null : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3996
+ import_design_system2.Button,
1608
3997
  {
1609
3998
  variant: "secondary",
1610
3999
  fullWidth: true,
1611
4000
  onClick: () => setOpen(true),
1612
4001
  "data-testid": `open-gallery-${zone.name}`,
1613
- children: [
1614
- t.add,
1615
- ": ",
1616
- zone.name
1617
- ]
4002
+ children: t.add
1618
4003
  }
1619
4004
  ),
1620
- children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_design_system.Flex, { direction: "column", alignItems: "stretch", gap: 3, children: [
1621
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_design_system.Flex, { gap: 3, alignItems: "center", wrap: "wrap", children: [
1622
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1623
- import_design_system.Typography,
4005
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Layout, { children: [
4006
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Side, { $collapsed: collapsed, "aria-label": t.gallery, "data-testid": `gallery-sidebar-${zone.name}`, "data-collapsed": collapsed, children: [
4007
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system2.Flex, { justifyContent: collapsed ? "center" : "flex-end", paddingBottom: 2, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
4008
+ Tool,
1624
4009
  {
1625
- variant: "omega",
1626
- fontWeight: "bold",
1627
- textColor: "neutral800",
1628
- style: { whiteSpace: "nowrap" },
1629
- "data-testid": `block-count-${zone.name}`,
1630
- "aria-live": "polite",
1631
- children: filtering ? t.f("countFiltered", { shown: entries.length, total }) : t.f("countAll", { count: total })
1632
- }
1633
- ),
1634
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_design_system.Box, { style: { flex: 1, minWidth: "12rem" }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1635
- import_design_system.Searchbar,
1636
- {
1637
- name: `block-search-${zone.name}`,
1638
- value: query,
1639
- placeholder: t.search,
1640
- clearLabel: t.clear,
1641
- onClear: () => setQuery(""),
1642
- onChange: (e) => setQuery(e.target.value),
1643
- children: t.search
4010
+ icon: "sidebar",
4011
+ label: collapsed ? t.expandSidebar : t.collapseSidebar,
4012
+ onClick: toggleSidebar,
4013
+ "data-testid": "gallery-sidebar-toggle"
1644
4014
  }
1645
4015
  ) }),
1646
- categories.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1647
- import_design_system.Box,
1648
- {
1649
- style: { width: "14rem" },
1650
- "data-testid": `block-category-${zone.name}`,
1651
- children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1652
- import_design_system.SingleSelect,
1653
- {
1654
- "aria-label": t.filterCategory,
1655
- size: "S",
1656
- value: category,
1657
- onChange: (value) => setCategory(String(value)),
1658
- children: [
1659
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_design_system.SingleSelectOption, { value: "all", children: t.allCategories }),
1660
- categories.map(([name, count]) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1661
- import_design_system.SingleSelectOption,
1662
- {
1663
- value: name,
1664
- children: `${name} (${count})`
1665
- },
1666
- name
1667
- ))
1668
- ]
1669
- }
1670
- )
1671
- }
1672
- ),
1673
- Toggle && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1674
- Toggle,
1675
- {
1676
- name: `block-fields-${zone.name}`,
1677
- label: t.showFields,
1678
- hint: t.showFieldsHint,
1679
- value: showFields,
1680
- onChange: changeShowFields
1681
- }
1682
- ),
1683
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_design_system.Flex, { gap: 2, alignItems: "center", children: [
1684
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1685
- import_design_system.Typography,
4016
+ options && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
4017
+ nav("all", "grid", t.nav.all, options.total),
4018
+ nav("recent", "clock", t.nav.recent, inZone(prefs.recent).length),
4019
+ nav("starred", "star", t.nav.starred, inZone(prefs.starred).length),
4020
+ !collapsed && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system2.Box, { paddingTop: 4, paddingBottom: 1, paddingLeft: 2, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system2.Typography, { variant: "sigma", textColor: "neutral600", children: t.typologiesTitle }) }),
4021
+ options.typologies.map(
4022
+ ([name, count]) => nav(name, TYPE_ICONS[name] || "text", t.typologies[name] || name, count)
4023
+ )
4024
+ ] })
4025
+ ] }),
4026
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Main, { children: [
4027
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_design_system2.Flex, { gap: 3, alignItems: "center", wrap: "wrap", children: [
4028
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
4029
+ import_design_system2.Typography,
1686
4030
  {
1687
- variant: "pi",
1688
- textColor: "neutral600",
1689
- id: `block-columns-${zone.name}`,
1690
- children: t.columns
4031
+ variant: "omega",
4032
+ fontWeight: "bold",
4033
+ textColor: "neutral800",
4034
+ style: { whiteSpace: "nowrap" },
4035
+ "data-testid": `block-count-${zone.name}`,
4036
+ "aria-live": "polite",
4037
+ children: filtering ? t.f("countFiltered", { shown: entries.length, total: options?.total || 0 }) : t.f("countAll", { count: options?.total || 0 })
1691
4038
  }
1692
4039
  ),
1693
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1694
- Slider,
4040
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system2.Box, { style: { flex: 1, minWidth: "12rem" }, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
4041
+ import_design_system2.Searchbar,
4042
+ {
4043
+ name: `block-search-${zone.name}`,
4044
+ value: query,
4045
+ placeholder: t.search,
4046
+ clearLabel: t.clear,
4047
+ onClear: () => setQuery(""),
4048
+ onChange: (e) => setQuery(e.target.value),
4049
+ children: t.search
4050
+ }
4051
+ ) }),
4052
+ options && FILTER_MENUS.filter((menu) => options[menu].length > 0).map((menu) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
4053
+ FilterMenu,
4054
+ {
4055
+ name: menu,
4056
+ label: t.filters[menu],
4057
+ options: options[menu],
4058
+ picked: picked[menu],
4059
+ labelOf: (value) => labelOf(menu, value),
4060
+ onChange: (values) => setPicked({ ...picked, [menu]: values })
4061
+ },
4062
+ menu
4063
+ )),
4064
+ Toggle && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
4065
+ Toggle,
1695
4066
  {
1696
- type: "range",
1697
- min: COLUMNS.min,
1698
- max: COLUMNS.max,
1699
- step: 1,
1700
- value: columns,
1701
- "aria-labelledby": `block-columns-${zone.name}`,
1702
- $percent: (columns - COLUMNS.min) / (COLUMNS.max - COLUMNS.min) * 100,
1703
- onChange: (e) => changeColumns(Number(e.target.value))
4067
+ name: `block-fields-${zone.name}`,
4068
+ label: t.showFields,
4069
+ hint: t.showFieldsHint,
4070
+ value: showFields,
4071
+ onChange: changeShowFields
1704
4072
  }
1705
4073
  ),
1706
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_design_system.Typography, { variant: "pi", fontWeight: "bold", children: columns })
1707
- ] })
1708
- ] }),
1709
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_design_system.Typography, { variant: "pi", textColor: "neutral600", children: t.example }),
1710
- error ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_design_system.Typography, { role: "alert", textColor: "danger600", children: error }) : !config ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_design_system.Typography, { children: t.loading }) : entries.length ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1711
- import_design_system.Flex,
1712
- {
1713
- direction: "column",
1714
- alignItems: "stretch",
1715
- gap: 4,
1716
- style: { overflowY: "auto", maxHeight: "60vh", padding: "4px" },
1717
- children: groupEntries(entries).map(
1718
- ({ category: name, entries: items }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1719
- import_design_system.Flex,
4074
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_design_system2.Flex, { gap: 2, alignItems: "center", children: [
4075
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system2.Typography, { variant: "pi", textColor: "neutral600", id: `block-columns-${zone.name}`, children: t.columns }),
4076
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
4077
+ Slider,
1720
4078
  {
1721
- direction: "column",
1722
- alignItems: "stretch",
1723
- gap: 2,
1724
- children: [
1725
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1726
- import_design_system.Typography,
1727
- {
1728
- variant: "sigma",
1729
- textColor: "neutral600",
1730
- "data-testid": `block-group-${name}`,
1731
- children: `${name} (${items.length})`
1732
- }
1733
- ),
1734
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Columns, { $columns: columns, children: items.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
4079
+ type: "range",
4080
+ min: COLUMNS.min,
4081
+ max: COLUMNS.max,
4082
+ step: 1,
4083
+ value: columns,
4084
+ "aria-labelledby": `block-columns-${zone.name}`,
4085
+ $percent: (columns - COLUMNS.min) / (COLUMNS.max - COLUMNS.min) * 100,
4086
+ onChange: (e) => changeColumns(Number(e.target.value))
4087
+ }
4088
+ ),
4089
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system2.Typography, { variant: "pi", fontWeight: "bold", children: columns })
4090
+ ] })
4091
+ ] }),
4092
+ chips.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_design_system2.Flex, { gap: 2, wrap: "wrap", "data-testid": "gallery-chips", children: [
4093
+ chips.map(({ menu, value }) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
4094
+ Chip,
4095
+ {
4096
+ type: "button",
4097
+ "aria-label": t.f("removeFilter", { name: labelOf(menu, value) }),
4098
+ onClick: () => setPicked({ ...picked, [menu]: picked[menu].filter((item) => item !== value) }),
4099
+ children: [
4100
+ labelOf(menu, value),
4101
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Icon, { name: "close", size: 12 })
4102
+ ]
4103
+ },
4104
+ `${menu}:${value}`
4105
+ )),
4106
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system2.Button, { variant: "tertiary", size: "S", onClick: () => setPicked(NO_FILTERS), "data-testid": "gallery-clear-all", children: t.clearAll })
4107
+ ] }),
4108
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system2.Typography, { variant: "pi", textColor: "neutral600", children: t.example }),
4109
+ error ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system2.Typography, { role: "alert", textColor: "danger600", children: error }) : !config ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system2.Typography, { children: t.loading }) : entries.length ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { ref: grid, style: { position: "relative", flex: 1, minHeight: 0, display: "flex", flexDirection: "column" }, children: [
4110
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
4111
+ import_design_system2.Flex,
4112
+ {
4113
+ direction: "column",
4114
+ alignItems: "stretch",
4115
+ gap: 4,
4116
+ style: { overflowY: "auto", flex: 1, minHeight: 0, padding: "4px" },
4117
+ children: (view === "recent" ? [{ typology: "recent", entries }] : groupEntries(entries)).map(
4118
+ ({ typology, entries: items }) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_design_system2.Flex, { direction: "column", alignItems: "stretch", gap: 2, children: [
4119
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system2.Typography, { variant: "sigma", textColor: "neutral600", "data-testid": `block-group-${typology}`, children: `${typology === "recent" ? t.nav.recent : t.typologies[typology] || typology} (${items.length})` }),
4120
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Columns, { $columns: columns, children: items.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1735
4121
  Card,
1736
4122
  {
1737
4123
  entry,
1738
4124
  palette: config.palette,
1739
4125
  showFields,
1740
- onSelect: () => select(entry.uid)
4126
+ starred: prefs.starred.includes(entry.uid),
4127
+ active: detail === entry.uid,
4128
+ onOpen: () => setDetail(entry.uid),
4129
+ onInsert: () => select(entry.uid),
4130
+ onStar: () => star(entry.uid)
1741
4131
  }
1742
4132
  ) }, entry.uid)) })
1743
- ]
1744
- },
1745
- name
1746
- )
4133
+ ] }, typology)
4134
+ )
4135
+ }
4136
+ ),
4137
+ chosen && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
4138
+ Magnify,
4139
+ {
4140
+ entry: chosen,
4141
+ palette: config.palette,
4142
+ config,
4143
+ components,
4144
+ get,
4145
+ contentType,
4146
+ locale,
4147
+ cardOf,
4148
+ starred: prefs.starred.includes(chosen.uid),
4149
+ onStar: () => star(chosen.uid),
4150
+ onInsert: () => select(chosen.uid),
4151
+ onClose: () => setDetail(null)
4152
+ },
4153
+ chosen.uid
1747
4154
  )
1748
- }
1749
- ) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_design_system.Flex, { gap: 3, alignItems: "center", children: [
1750
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_design_system.Typography, { children: t.empty }),
1751
- filtering && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_design_system.Button, { variant: "tertiary", size: "S", onClick: reset, children: t.clear })
4155
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_design_system2.Flex, { gap: 3, alignItems: "center", children: [
4156
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system2.Typography, { children: t.empty }),
4157
+ filtering && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system2.Button, { variant: "tertiary", size: "S", onClick: reset, children: t.clear })
4158
+ ] })
1752
4159
  ] })
1753
4160
  ] })
1754
4161
  }
1755
4162
  );
1756
4163
  }
1757
- var storage = () => {
4164
+ var storage2 = () => {
1758
4165
  try {
1759
4166
  return window.localStorage;
1760
4167
  } catch {
@@ -1769,12 +4176,12 @@ function useInitialAccordions({
1769
4176
  contentType,
1770
4177
  userId
1771
4178
  }) {
1772
- const applied = React2.useRef("");
1773
- const user = React2.useRef(userId);
4179
+ const applied = React4.useRef("");
4180
+ const user = React4.useRef(userId);
1774
4181
  user.current = userId;
1775
- const latest = React2.useRef(zones);
4182
+ const latest = React4.useRef(zones);
1776
4183
  latest.current = zones;
1777
- React2.useEffect(() => {
4184
+ React4.useEffect(() => {
1778
4185
  if (!editor?.enabled || !docKey || applied.current === docKey) return;
1779
4186
  if (applied.current.startsWith(`${contentType}:new`) && docKey.startsWith(`${contentType}:`)) {
1780
4187
  applied.current = docKey;
@@ -1812,55 +4219,74 @@ function useInitialAccordions({
1812
4219
  });
1813
4220
  setAll(
1814
4221
  list,
1815
- initialState(editor, readMemory(storage(), key)) === "open",
1816
- import_react_dom2.unstable_batchedUpdates
4222
+ initialState(editor, readMemory(storage2(), key)) === "open",
4223
+ import_react_dom3.unstable_batchedUpdates
1817
4224
  );
1818
4225
  }
1819
4226
  }, 200);
1820
4227
  return stop;
1821
4228
  }, [docKey, editor?.enabled, editor?.initialState, contentType]);
1822
4229
  }
1823
- function ZoneControls({ zone, editor, contentType, userId }) {
4230
+ var ZONE_ATTR = "data-blockscene-zone-controls";
4231
+ function useLabelAnchor(zone, enabled) {
4232
+ const [el, setEl] = React4.useState(null);
4233
+ React4.useEffect(() => {
4234
+ if (!enabled) return;
4235
+ let frame2 = 0;
4236
+ const sync = () => {
4237
+ const list = findZoneList(zone.label);
4238
+ const row = list && [...list.parentElement?.children || []].find((child) => child !== list && child.textContent?.trim());
4239
+ let anchor = document.querySelector(`[${ZONE_ATTR}="${CSS.escape(zone.name)}"]`);
4240
+ if (anchor && anchor.parentElement !== row) {
4241
+ anchor.remove();
4242
+ anchor = null;
4243
+ }
4244
+ if (!anchor && row) {
4245
+ anchor = document.createElement("span");
4246
+ anchor.setAttribute(ZONE_ATTR, zone.name);
4247
+ anchor.style.cssText = "display:inline-flex;align-items:center;margin-left:8px";
4248
+ row.appendChild(anchor);
4249
+ }
4250
+ setEl(anchor);
4251
+ };
4252
+ sync();
4253
+ const observer = new MutationObserver(() => {
4254
+ if (!frame2) frame2 = requestAnimationFrame(() => {
4255
+ frame2 = 0;
4256
+ sync();
4257
+ });
4258
+ });
4259
+ observer.observe(document.body, { childList: true, subtree: true });
4260
+ return () => {
4261
+ observer.disconnect();
4262
+ cancelAnimationFrame(frame2);
4263
+ document.querySelector(`[${ZONE_ATTR}="${CSS.escape(zone.name)}"]`)?.remove();
4264
+ setEl(null);
4265
+ };
4266
+ }, [zone.name, zone.label, enabled]);
4267
+ return el;
4268
+ }
4269
+ function ZoneControls({ zone, editor, contentType, userId, tools }) {
1824
4270
  const t = useMessages();
1825
- if (!zone.count || !(editor.showOpenAll || editor.showCloseAll)) return null;
4271
+ const shown = Boolean(zone.count && (editor.showOpenAll || editor.showCloseAll || tools));
4272
+ const anchor = useLabelAnchor(zone, shown);
4273
+ if (!shown || !anchor) return null;
1826
4274
  const apply = (open) => {
1827
4275
  const list = findZoneList(zone.label);
1828
- if (list) setAll(list, open, import_react_dom2.unstable_batchedUpdates);
4276
+ if (list) setAll(list, open, import_react_dom3.unstable_batchedUpdates);
1829
4277
  writeMemory(
1830
- storage(),
4278
+ storage2(),
1831
4279
  memoryKey({ base: memoryBase(), userId, contentType, zone: zone.name }),
1832
4280
  open ? "open" : "closed"
1833
4281
  );
1834
4282
  };
1835
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1836
- import_design_system.Flex,
1837
- {
1838
- gap: 2,
1839
- wrap: "wrap",
1840
- "data-testid": `block-accordion-controls-${zone.name}`,
1841
- children: [
1842
- editor.showOpenAll && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1843
- import_design_system.Button,
1844
- {
1845
- type: "button",
1846
- variant: "tertiary",
1847
- size: "S",
1848
- onClick: () => apply(true),
1849
- children: t.openAll
1850
- }
1851
- ),
1852
- editor.showCloseAll && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1853
- import_design_system.Button,
1854
- {
1855
- type: "button",
1856
- variant: "tertiary",
1857
- size: "S",
1858
- onClick: () => apply(false),
1859
- children: t.closeAll
1860
- }
1861
- )
1862
- ]
1863
- }
4283
+ return (0, import_react_dom3.createPortal)(
4284
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_design_system2.Flex, { gap: 1, "data-testid": `block-accordion-controls-${zone.name}`, children: [
4285
+ editor.showOpenAll && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Tool, { icon: "expand", label: t.openAll, onClick: () => apply(true) }),
4286
+ editor.showCloseAll && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Tool, { icon: "collapse", label: t.closeAll, onClick: () => apply(false) }),
4287
+ tools
4288
+ ] }),
4289
+ anchor
1864
4290
  );
1865
4291
  }
1866
4292
  function Gallery({
@@ -1873,8 +4299,10 @@ function Gallery({
1873
4299
  ...props
1874
4300
  }) {
1875
4301
  useInitialAccordions({ zones, editor, docKey, contentType, userId });
1876
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_design_system.Flex, { direction: "column", alignItems: "stretch", gap: 3, children: [
1877
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
4302
+ const actions = useRowActions({ zones, components: props.components, catalog, form: props.form });
4303
+ React4.useEffect(() => stopAll, [docKey]);
4304
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_design_system2.Flex, { direction: "column", alignItems: "stretch", gap: 3, children: [
4305
+ editor.showRowThumbnails !== false && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1878
4306
  RowPreviews,
1879
4307
  {
1880
4308
  zones,
@@ -1883,15 +4311,19 @@ function Gallery({
1883
4311
  Modal: props.Modal
1884
4312
  }
1885
4313
  ),
1886
- zones.map((zone) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_design_system.Flex, { direction: "column", alignItems: "stretch", gap: 2, children: [
1887
- !zone.full && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ZoneGallery, { zone, ...props }),
1888
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
4314
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(RowActions, { zones, actions, Modal: props.Modal }),
4315
+ zones.map((zone) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_design_system2.Flex, { direction: "column", alignItems: "stretch", gap: 2, children: [
4316
+ zones.length > 1 && !zone.full && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system2.Typography, { variant: "sigma", textColor: "neutral600", tag: "h3", children: zone.label }),
4317
+ !zone.full && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ZoneGallery, { zone, contentType, locale: String(docKey || "").split(":").pop(), ...props }),
4318
+ zone.count === 0 && actions?.clip && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system2.Flex, { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ZoneRowTools, { zone, actions }) }),
4319
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1889
4320
  ZoneControls,
1890
4321
  {
1891
4322
  zone,
1892
4323
  editor,
1893
4324
  contentType,
1894
- userId
4325
+ userId,
4326
+ tools: actions && actions.editor.clipboard !== false && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ZoneRowTools, { zone, actions })
1895
4327
  }
1896
4328
  )
1897
4329
  ] }, zone.name))
@@ -1899,24 +4331,24 @@ function Gallery({
1899
4331
  }
1900
4332
 
1901
4333
  // admin/Settings.tsx
1902
- var React3 = __toESM(require("react"));
1903
- var import_design_system2 = require("@strapi/design-system");
1904
- var import_styled_components2 = __toESM(require("styled-components"));
1905
- var import_jsx_runtime4 = require("react/jsx-runtime");
4334
+ var React5 = __toESM(require("react"));
4335
+ var import_design_system3 = require("@strapi/design-system");
4336
+ var import_styled_components4 = __toESM(require("styled-components"));
4337
+ var import_jsx_runtime6 = require("react/jsx-runtime");
1906
4338
  var COLOR2 = /^#[0-9A-Fa-f]{6}$/;
1907
- var Grid = import_styled_components2.default.div` display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 260px), 1fr)); gap: 16px; `;
1908
- var Card2 = import_styled_components2.default.div`
4339
+ var Grid = import_styled_components4.default.div` display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 260px), 1fr)); gap: 16px; `;
4340
+ var Card2 = import_styled_components4.default.div`
1909
4341
  display: flex; flex-direction: column; gap: 8px; padding: 12px; border-radius: 4px;
1910
4342
  border: 1px solid ${({ theme }) => theme.colors.neutral200}; background: ${({ theme }) => theme.colors.neutral0};
1911
4343
  `;
1912
- var Bar = import_styled_components2.default.div`
4344
+ var Bar = import_styled_components4.default.div`
1913
4345
  position: fixed; top: 0; z-index: 4; padding: 12px 32px;
1914
4346
  background: ${({ theme }) => theme.colors.neutral100}; border-bottom: 1px solid ${({ theme }) => theme.colors.neutral150};
1915
4347
  box-shadow: 0 1px 4px rgba(33, 33, 52, 0.1);
1916
4348
  `;
1917
4349
  function useMainRect(anchor) {
1918
- const [rect, setRect] = React3.useState({ left: 0, width: 0 });
1919
- React3.useEffect(() => {
4350
+ const [rect, setRect] = React5.useState({ left: 0, width: 0 });
4351
+ React5.useEffect(() => {
1920
4352
  const read = () => {
1921
4353
  let host = document.getElementById("main-content");
1922
4354
  for (let el = anchor.current?.parentElement; !host && el; el = el.parentElement) if (["auto", "scroll"].includes(getComputedStyle(el).overflowY) && el.scrollHeight > el.clientHeight) host = el;
@@ -1929,15 +4361,15 @@ function useMainRect(anchor) {
1929
4361
  });
1930
4362
  return rect;
1931
4363
  }
1932
- var Swatches = import_styled_components2.default.div` display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 12px; `;
1933
- var Label = import_styled_components2.default.label` display: flex; flex-direction: column; gap: 4px; font-size: 12px; `;
4364
+ var Swatches = import_styled_components4.default.div` display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 12px; `;
4365
+ var Label = import_styled_components4.default.label` display: flex; flex-direction: column; gap: 4px; font-size: 12px; `;
1934
4366
  function ColorField({ name, label, value, onChange, disabled }) {
1935
4367
  const valid = COLOR2.test(value);
1936
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(Label, { children: [
1937
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system2.Typography, { variant: "pi", fontWeight: "bold", textColor: "neutral800", children: label }),
1938
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_design_system2.Flex, { gap: 2, children: [
1939
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { type: "color", "aria-label": label, value: valid ? value : "#000000", disabled, onChange: (e) => onChange(e.target.value.toUpperCase()), style: { width: 40, height: 32, padding: 0, border: 0, background: "none" } }),
1940
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
4368
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Label, { children: [
4369
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Typography, { variant: "pi", fontWeight: "bold", textColor: "neutral800", children: label }),
4370
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_design_system3.Flex, { gap: 2, children: [
4371
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("input", { type: "color", "aria-label": label, value: valid ? value : "#000000", disabled, onChange: (e) => onChange(e.target.value.toUpperCase()), style: { width: 40, height: 32, padding: 0, border: 0, background: "none" } }),
4372
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1941
4373
  "input",
1942
4374
  {
1943
4375
  name,
@@ -1952,23 +4384,151 @@ function ColorField({ name, label, value, onChange, disabled }) {
1952
4384
  ] })
1953
4385
  ] });
1954
4386
  }
4387
+ function TagsField({ name, label, hint, value, onChange, disabled }) {
4388
+ const [text, setText] = React5.useState(value.join(", "));
4389
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Label, { children: [
4390
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Typography, { variant: "pi", fontWeight: "bold", textColor: "neutral800", children: label }),
4391
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("input", { name, value: text, disabled, onChange: (e) => {
4392
+ setText(e.target.value);
4393
+ onChange([...new Set(e.target.value.split(",").map((tag) => tag.replace(/[<>]/g, "").trim().slice(0, 24)).filter(Boolean))].slice(0, 10));
4394
+ }, style: { padding: "6px 8px", border: "1px solid #dcdce4", borderRadius: 4, fontSize: 13 } }),
4395
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Typography, { variant: "pi", textColor: "neutral600", children: hint })
4396
+ ] });
4397
+ }
4398
+ var FIELD_UID = /^(plugin|global)::[\w.-]+$/;
4399
+ var LAZY_MAX = 20;
4400
+ var parseUids = (text) => [...new Set(text.split(/[\s,]+/).filter(Boolean))];
4401
+ var badUids = (uids = []) => uids.length > LAZY_MAX || uids.some((uid) => !FIELD_UID.test(uid));
4402
+ function UidListField({ TextField: TextField2, value, onChange, ...props }) {
4403
+ const [text, setText] = React5.useState(value.join(", "));
4404
+ React5.useEffect(() => {
4405
+ if (parseUids(text).join() !== value.join()) setText(value.join(", "));
4406
+ }, [value.join()]);
4407
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(TextField2, { ...props, value: text, onChange: (v) => {
4408
+ setText(v);
4409
+ onChange(parseUids(v));
4410
+ } });
4411
+ }
4412
+ var SIDEBAR_ICONS = ["text", "tag", "seo", "settings", "image", "link", "palette", "list", "globe", "info"];
4413
+ var SIDEBAR_MAX = 12;
4414
+ var ITEM_LABEL = /^[^<>]{1,40}$/;
4415
+ var badItem = (item) => !ITEM_LABEL.test(String(item.label || "").trim()) || !item.fields?.length;
4416
+ var SYSTEM_FIELDS2 = ["createdAt", "updatedAt", "publishedAt", "createdBy", "updatedBy", "locale", "localizations"];
4417
+ var Item = import_styled_components4.default.div`
4418
+ display: flex; flex-direction: column; gap: 8px; padding: 12px; border-radius: 4px;
4419
+ border: 1px solid ${({ theme }) => theme.colors.neutral200}; background: ${({ theme }) => theme.colors.neutral100};
4420
+ `;
4421
+ var Checks = import_styled_components4.default.fieldset`
4422
+ display: flex; flex-wrap: wrap; gap: 4px 16px; border: 0; padding: 0; margin: 0; font-size: 13px; color: ${({ theme }) => theme.colors.neutral800};
4423
+ legend { font-size: 12px; font-weight: 600; margin-bottom: 4px; }
4424
+ label { display: inline-flex; align-items: center; gap: 6px; }
4425
+ `;
4426
+ function SidebarEditor({ type, entry, set, t, disabled, SelectField: SelectField2, TextField: TextField2 }) {
4427
+ const items = entry.sidebar || [];
4428
+ const save = (next) => set("sidebar", next.length ? next : void 0, void 0);
4429
+ const edit = (index, patch) => save(items.map((item, i) => i === index ? { ...item, ...patch } : item));
4430
+ const move = (index, by) => {
4431
+ const next = [...items];
4432
+ [next[index], next[index + by]] = [next[index + by], next[index]];
4433
+ save(next);
4434
+ };
4435
+ const fields = (type.attributes || []).filter((attr) => !SYSTEM_FIELDS2.includes(attr.name));
4436
+ const id = (name, index) => `sidebar-${name}-${type.uid}${index === void 0 ? "" : `-${index}`}`;
4437
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_design_system3.Flex, { direction: "column", alignItems: "stretch", gap: 3, paddingLeft: 4, "data-testid": `sidebar-editor-${type.uid}`, children: [
4438
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Typography, { variant: "delta", tag: "h3", children: t.f("sidebarTitle", { name: type.displayName }) }),
4439
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Typography, { variant: "pi", textColor: "neutral600", children: t.sidebarHelp }),
4440
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Box, { style: { maxWidth: 240 }, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4441
+ SelectField2,
4442
+ {
4443
+ name: id("position"),
4444
+ label: t.sidebarPosition,
4445
+ value: entry.sidebarPosition || "left",
4446
+ disabled,
4447
+ options: ["left", "right", "bottom"].map((value) => ({ value, label: t.positions[value] })),
4448
+ onChange: (v) => set("sidebarPosition", v, "left")
4449
+ }
4450
+ ) }),
4451
+ items.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Item, { "data-testid": id("item", index), children: [
4452
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_design_system3.Flex, { gap: 4, alignItems: "flex-end", wrap: "wrap", children: [
4453
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Box, { style: { flex: "1 1 200px" }, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(TextField2, { name: id("label", index), label: t.sidebarItemLabel, value: item.label, disabled, onChange: (v) => edit(index, { label: v }) }) }),
4454
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Box, { style: { minWidth: 180 }, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4455
+ SelectField2,
4456
+ {
4457
+ name: id("icon", index),
4458
+ label: t.sidebarIcon,
4459
+ value: item.icon || "none",
4460
+ disabled,
4461
+ options: [{ value: "none", label: t.noIcon }, ...SIDEBAR_ICONS.map((value) => ({ value, label: t.icons[value], icon: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Icon, { name: value, size: 16 }) }))],
4462
+ onChange: (v) => edit(index, { icon: v === "none" ? void 0 : v })
4463
+ }
4464
+ ) }),
4465
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Box, { style: { minWidth: 160 }, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4466
+ SelectField2,
4467
+ {
4468
+ name: id("open", index),
4469
+ label: t.sidebarOpens,
4470
+ value: item.open,
4471
+ disabled,
4472
+ options: ["drawer", "modal"].map((value) => ({ value, label: t.opens[value] })),
4473
+ onChange: (v) => edit(index, { open: v })
4474
+ }
4475
+ ) }),
4476
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_design_system3.Flex, { gap: 1, paddingBottom: 1, children: [
4477
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Tool, { icon: "up", label: t.moveUp, disabled: disabled || index === 0, onClick: () => move(index, -1) }),
4478
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Tool, { icon: "down", label: t.moveDown, disabled: disabled || index === items.length - 1, onClick: () => move(index, 1) }),
4479
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Tool, { icon: "close", label: t.sidebarRemove, disabled, onClick: () => save(items.filter((_, i) => i !== index)) })
4480
+ ] })
4481
+ ] }),
4482
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Checks, { disabled, children: [
4483
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("legend", { children: t.sidebarFields }),
4484
+ fields.map((attr) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { children: [
4485
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4486
+ "input",
4487
+ {
4488
+ type: "checkbox",
4489
+ name: id(`field-${attr.name}`, index),
4490
+ checked: item.fields.includes(attr.name),
4491
+ onChange: (e) => edit(index, { fields: e.target.checked ? [...item.fields, attr.name] : item.fields.filter((name) => name !== attr.name) })
4492
+ }
4493
+ ),
4494
+ attr.name
4495
+ ] }, attr.name))
4496
+ ] }),
4497
+ badItem(item) && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Typography, { role: "alert", variant: "pi", textColor: "danger600", children: t.sidebarInvalid })
4498
+ ] }, index)),
4499
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_design_system3.Flex, { gap: 2, alignItems: "center", children: [
4500
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4501
+ import_design_system3.Button,
4502
+ {
4503
+ variant: "secondary",
4504
+ size: "S",
4505
+ disabled: disabled || items.length >= SIDEBAR_MAX,
4506
+ "data-testid": id("add"),
4507
+ onClick: () => save([...items, { label: "", open: "drawer", fields: [] }]),
4508
+ children: t.sidebarAdd
4509
+ }
4510
+ ),
4511
+ items.length >= SIDEBAR_MAX && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Typography, { variant: "pi", textColor: "neutral600", children: t.f("sidebarLimit", { max: SIDEBAR_MAX }) })
4512
+ ] })
4513
+ ] });
4514
+ }
1955
4515
  var permissions = { read: [{ action: "plugin::blockscene.settings.read", subject: null }], update: [{ action: "plugin::blockscene.settings.update", subject: null }] };
1956
- function Settings({ useClient, usePermissions: usePermissions2, MediaPicker: MediaPicker2, ToggleField: ToggleField2, SelectField: SelectField2, TextField, previewSupported = false }) {
4516
+ function Settings({ useClient, usePermissions: usePermissions2, MediaPicker: MediaPicker2, ToggleField: ToggleField2, SelectField: SelectField2, TextField: TextField2, previewSupported = false }) {
1957
4517
  const t = useMessages();
1958
- const { get, put } = useClient();
4518
+ const { get, put, del } = useClient();
1959
4519
  const { canRead, canUpdate, isLoading } = usePermissions2();
1960
- const [data, setData] = React3.useState(null);
1961
- const [catalog, setCatalog] = React3.useState(null);
1962
- const [settings, setSettings] = React3.useState(null);
1963
- const [media, setMedia] = React3.useState({});
1964
- const [sources, setSources] = React3.useState({});
1965
- const [dirty, setDirty] = React3.useState(false);
1966
- const [saving, setSaving] = React3.useState(false);
1967
- const [status, setStatus] = React3.useState("");
1968
- const [failed, setFailed] = React3.useState(false);
1969
- const [filter, setFilter] = React3.useState("");
1970
- const [picking, setPicking] = React3.useState(null);
1971
- React3.useEffect(() => {
4520
+ const [data, setData] = React5.useState(null);
4521
+ const [catalog, setCatalog] = React5.useState(null);
4522
+ const [settings, setSettings] = React5.useState(null);
4523
+ const [media, setMedia] = React5.useState({});
4524
+ const [sources, setSources] = React5.useState({});
4525
+ const [dirty, setDirty] = React5.useState(false);
4526
+ const [saving, setSaving] = React5.useState(false);
4527
+ const [status, setStatus] = React5.useState("");
4528
+ const [failed, setFailed] = React5.useState(false);
4529
+ const [filter, setFilter] = React5.useState("");
4530
+ const [picking, setPicking] = React5.useState(null);
4531
+ React5.useEffect(() => {
1972
4532
  if (!canRead || isLoading) return;
1973
4533
  let active = true;
1974
4534
  Promise.all([get("/blockscene/settings"), get("/blockscene/catalog")]).then(([s, c]) => {
@@ -1989,7 +4549,10 @@ function Settings({ useClient, usePermissions: usePermissions2, MediaPicker: Med
1989
4549
  setDirty(true);
1990
4550
  setStatus("");
1991
4551
  };
1992
- const invalid = settings && (Object.values(settings.palette).some((value) => !COLOR2.test(value)) || settings.editor.previewUrl && !/^https?:\/\/\S+$/.test(settings.editor.previewUrl));
4552
+ const badColor = settings && (Object.values(settings.palette).some((value) => !COLOR2.test(value)) || [settings.editor.previewUrl, settings.editor.blockPreviewUrl].some((url) => url && !/^https?:\/\/\S+$/.test(url)));
4553
+ const badSidebar = settings && Object.values(settings.contentTypes || {}).some((entry) => (entry.sidebar || []).some(badItem));
4554
+ const badLazy = settings && badUids(settings.editor.lazyFields);
4555
+ const invalid = badColor || badSidebar || badLazy;
1993
4556
  const save = async () => {
1994
4557
  if (!settings || invalid || !canUpdate) return;
1995
4558
  setSaving(true);
@@ -2007,6 +4570,31 @@ function Settings({ useClient, usePermissions: usePermissions2, MediaPicker: Med
2007
4570
  setSaving(false);
2008
4571
  }
2009
4572
  };
4573
+ const restore = async () => {
4574
+ if (!canUpdate || !window.confirm(data.projectDefaults ? t.restoreProjectConfirm : t.resetAllConfirm)) return;
4575
+ setSaving(true);
4576
+ setStatus("");
4577
+ try {
4578
+ const { data: fresh } = await del("/blockscene/settings");
4579
+ setSettings(fresh);
4580
+ setDirty(false);
4581
+ setStatus(t.restored);
4582
+ const refreshed = await get("/blockscene/settings");
4583
+ setMedia(refreshed.data.media || {});
4584
+ } catch {
4585
+ setStatus(t.saveFailed);
4586
+ } finally {
4587
+ setSaving(false);
4588
+ }
4589
+ };
4590
+ const setComponent = (uid, key, value) => update((s) => {
4591
+ const next = { ...s.components[uid] };
4592
+ if (value === void 0) delete next[key];
4593
+ else next[key] = value;
4594
+ if (Object.keys(next).length) s.components[uid] = next;
4595
+ else delete s.components[uid];
4596
+ return s;
4597
+ });
2010
4598
  const editor = settings?.editor || {};
2011
4599
  const components = (data?.components || []).filter((c) => `${c.displayName} ${c.uid}`.toLocaleLowerCase().includes(filter.trim().toLocaleLowerCase()));
2012
4600
  const sourceOf = (uid, manual) => {
@@ -2016,35 +4604,36 @@ function Settings({ useClient, usePermissions: usePermissions2, MediaPicker: Med
2016
4604
  return index === 0 && manual ? t.manual : t.automatic;
2017
4605
  };
2018
4606
  const ready = !isLoading && canRead && !failed && settings && catalog;
2019
- const header = React3.useRef(null);
2020
- const [floating, setFloating] = React3.useState(false);
4607
+ const header = React5.useRef(null);
4608
+ const [floating, setFloating] = React5.useState(false);
2021
4609
  const main = useMainRect(header);
2022
- React3.useEffect(() => {
4610
+ React5.useEffect(() => {
2023
4611
  const el = header.current;
2024
4612
  if (!el || typeof IntersectionObserver === "undefined") return;
2025
4613
  const observer = new IntersectionObserver(([entry]) => setFloating(!entry.isIntersecting && entry.boundingClientRect.top < 0));
2026
4614
  observer.observe(el);
2027
4615
  return () => observer.disconnect();
2028
4616
  }, [ready]);
2029
- const controls = (suffix) => ready && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_design_system2.Flex, { gap: 3, alignItems: "center", wrap: "wrap", children: [
2030
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system2.Typography, { role: "status", "aria-live": "polite", textColor: dirty ? "warning700" : "neutral600", "data-testid": `${dirty ? "unsaved-indicator" : "save-status"}${suffix}`, children: dirty ? t.unsaved : status || t.noChanges }),
2031
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system2.Button, { onClick: save, disabled: !canUpdate || invalid || !dirty, loading: saving, "data-testid": `save-blockscene-settings${suffix}`, children: t.save })
4617
+ const controls = (suffix) => ready && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_design_system3.Flex, { gap: 3, alignItems: "center", wrap: "wrap", children: [
4618
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Typography, { role: "status", "aria-live": "polite", textColor: dirty ? "warning700" : "neutral600", "data-testid": `${dirty ? "unsaved-indicator" : "save-status"}${suffix}`, children: dirty ? t.unsaved : status || t.noChanges }),
4619
+ !suffix && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Button, { variant: "tertiary", onClick: restore, disabled: !canUpdate || saving, "data-testid": "restore-blockscene-settings", children: data.projectDefaults ? t.restoreProject : t.resetAll }),
4620
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Button, { onClick: save, disabled: !canUpdate || invalid || !dirty, loading: saving, "data-testid": `save-blockscene-settings${suffix}`, children: t.save })
2032
4621
  ] });
2033
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system2.Box, { padding: 8, background: "neutral100", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_design_system2.Flex, { direction: "column", alignItems: "stretch", gap: 5, children: [
2034
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { ref: header, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_design_system2.Flex, { gap: 4, alignItems: "center", justifyContent: "space-between", wrap: "wrap", children: [
2035
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system2.Typography, { variant: "alpha", tag: "h1", children: t.settingsTitle }),
4622
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Box, { padding: 8, background: "neutral100", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_design_system3.Flex, { direction: "column", alignItems: "stretch", gap: 5, children: [
4623
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { ref: header, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_design_system3.Flex, { gap: 4, alignItems: "center", justifyContent: "space-between", wrap: "wrap", children: [
4624
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Typography, { variant: "alpha", tag: "h1", children: t.settingsTitle }),
2036
4625
  controls("")
2037
4626
  ] }) }),
2038
- floating && main.width > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Bar, { style: { left: main.left, width: main.width }, "data-testid": "settings-floating-bar", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_design_system2.Flex, { gap: 4, alignItems: "center", justifyContent: "space-between", wrap: "wrap", children: [
2039
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system2.Typography, { variant: "beta", tag: "p", children: t.settingsTitle }),
4627
+ floating && main.width > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Bar, { style: { left: main.left, width: main.width }, "data-testid": "settings-floating-bar", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_design_system3.Flex, { gap: 4, alignItems: "center", justifyContent: "space-between", wrap: "wrap", children: [
4628
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Typography, { variant: "beta", tag: "p", children: t.settingsTitle }),
2040
4629
  controls("-floating")
2041
4630
  ] }) }),
2042
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system2.Typography, { textColor: "neutral600", children: t.settingsIntro }),
2043
- isLoading ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system2.Typography, { children: "\u2026" }) : !canRead ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system2.Typography, { role: "alert", children: t.denied }) : failed ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system2.Typography, { role: "alert", children: t.loadFailed }) : !settings || !catalog ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system2.Typography, { children: t.loading }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
2044
- data.disabled && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system2.Box, { padding: 4, background: "warning100", hasRadius: true, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system2.Typography, { role: "alert", textColor: "warning700", children: t.disabledByServer }) }),
2045
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system2.Box, { padding: 6, background: "neutral0", hasRadius: true, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_design_system2.Flex, { direction: "column", alignItems: "stretch", gap: 4, children: [
2046
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system2.Typography, { variant: "beta", tag: "h2", children: t.palette }),
2047
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Swatches, { children: Object.keys(DEFAULT_PALETTE).map((key) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
4631
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Typography, { textColor: "neutral600", children: t.settingsIntro }),
4632
+ isLoading ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Typography, { children: "\u2026" }) : !canRead ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Typography, { role: "alert", children: t.denied }) : failed ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Typography, { role: "alert", children: t.loadFailed }) : !settings || !catalog ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Typography, { children: t.loading }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
4633
+ data.disabled && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Box, { padding: 4, background: "warning100", hasRadius: true, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Typography, { role: "alert", textColor: "warning700", children: t.disabledByServer }) }),
4634
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Box, { padding: 6, background: "neutral0", hasRadius: true, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_design_system3.Flex, { direction: "column", alignItems: "stretch", gap: 4, children: [
4635
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Typography, { variant: "beta", tag: "h2", children: t.palette }),
4636
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Swatches, { children: Object.keys(DEFAULT_PALETTE).map((key) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2048
4637
  ColorField,
2049
4638
  {
2050
4639
  name: `palette-${key}`,
@@ -2058,33 +4647,37 @@ function Settings({ useClient, usePermissions: usePermissions2, MediaPicker: Med
2058
4647
  },
2059
4648
  key
2060
4649
  )) }),
2061
- invalid && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system2.Typography, { role: "alert", textColor: "danger600", children: t.invalidColor }),
2062
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system2.Typography, { variant: "pi", textColor: "neutral600", children: t.previewLabel }),
2063
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Swatches, { "data-testid": "palette-preview", children: TEMPLATES.map((template) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_design_system2.Flex, { direction: "column", gap: 1, children: [
2064
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system2.Box, { style: { aspectRatio: "16 / 9", overflow: "hidden" }, hasRadius: true, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Wireframe, { template, palette: settings.palette }) }),
2065
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system2.Typography, { variant: "pi", textColor: "neutral600", children: t.templates[template] })
4650
+ badColor && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Typography, { role: "alert", textColor: "danger600", children: t.invalidColor }),
4651
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Typography, { variant: "pi", textColor: "neutral600", children: t.previewLabel }),
4652
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Swatches, { "data-testid": "palette-preview", children: TEMPLATES.map((template) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_design_system3.Flex, { direction: "column", gap: 1, children: [
4653
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Box, { style: { aspectRatio: "16 / 9", overflow: "hidden" }, hasRadius: true, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Wireframe, { template, palette: settings.palette }) }),
4654
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Typography, { variant: "pi", textColor: "neutral600", children: t.templates[template] })
2066
4655
  ] }, template)) }),
2067
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system2.Flex, { children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system2.Button, { variant: "tertiary", size: "S", disabled: !canUpdate, onClick: () => update((s) => {
4656
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Flex, { children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Button, { variant: "tertiary", size: "S", disabled: !canUpdate, onClick: () => update((s) => {
2068
4657
  s.palette = { ...DEFAULT_PALETTE };
2069
4658
  return s;
2070
4659
  }), children: t.resetPalette }) })
2071
4660
  ] }) }),
2072
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system2.Box, { padding: 6, background: "neutral0", hasRadius: true, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_design_system2.Flex, { direction: "column", alignItems: "stretch", gap: 4, children: [
2073
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system2.Typography, { variant: "beta", tag: "h2", children: t.editor }),
2074
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ToggleField2, { name: "editor-enabled", label: t.enabled, value: editor.enabled, disabled: !canUpdate || saving, onChange: (v) => update((s) => {
4661
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Box, { padding: 6, background: "neutral0", hasRadius: true, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_design_system3.Flex, { direction: "column", alignItems: "stretch", gap: 4, children: [
4662
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Typography, { variant: "beta", tag: "h2", children: t.editor }),
4663
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ToggleField2, { name: "editor-enabled", label: t.enabled, value: editor.enabled, disabled: !canUpdate || saving, onChange: (v) => update((s) => {
2075
4664
  s.editor.enabled = v;
2076
4665
  return s;
2077
4666
  }) }),
2078
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system2.Typography, { variant: "pi", textColor: "neutral600", children: t.enabledHelp }),
2079
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ToggleField2, { name: "editor-showOpenAll", label: t.showOpenAll, value: editor.showOpenAll, disabled: !canUpdate || saving, onChange: (v) => update((s) => {
4667
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Typography, { variant: "pi", textColor: "neutral600", children: t.enabledHelp }),
4668
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ToggleField2, { name: "editor-showOpenAll", label: t.showOpenAll, value: editor.showOpenAll, disabled: !canUpdate || saving, onChange: (v) => update((s) => {
2080
4669
  s.editor.showOpenAll = v;
2081
4670
  return s;
2082
4671
  }) }),
2083
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ToggleField2, { name: "editor-showCloseAll", label: t.showCloseAll, value: editor.showCloseAll, disabled: !canUpdate || saving, onChange: (v) => update((s) => {
4672
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ToggleField2, { name: "editor-showCloseAll", label: t.showCloseAll, value: editor.showCloseAll, disabled: !canUpdate || saving, onChange: (v) => update((s) => {
2084
4673
  s.editor.showCloseAll = v;
2085
4674
  return s;
2086
4675
  }) }),
2087
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
4676
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ToggleField2, { name: "editor-showRowThumbnails", label: t.showRowThumbnails, value: editor.showRowThumbnails !== false, disabled: !canUpdate || saving, onChange: (v) => update((s) => {
4677
+ s.editor.showRowThumbnails = v;
4678
+ return s;
4679
+ }) }),
4680
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2088
4681
  SelectField2,
2089
4682
  {
2090
4683
  name: "editor-initialState",
@@ -2098,10 +4691,25 @@ function Settings({ useClient, usePermissions: usePermissions2, MediaPicker: Med
2098
4691
  })
2099
4692
  }
2100
4693
  ),
2101
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system2.Typography, { variant: "pi", textColor: "neutral600", children: t.rememberHelp }),
2102
- previewSupported ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
2103
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2104
- TextField,
4694
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Typography, { variant: "pi", textColor: "neutral600", children: t.rememberHelp }),
4695
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4696
+ TextField2,
4697
+ {
4698
+ name: "editor-blockPreviewUrl",
4699
+ label: t.blockPreviewUrl,
4700
+ value: editor.blockPreviewUrl || "",
4701
+ disabled: !canUpdate || saving,
4702
+ placeholder: "http://localhost:3000/block-preview/{name}/{variant}",
4703
+ onChange: (v) => update((s) => {
4704
+ s.editor.blockPreviewUrl = v.trim();
4705
+ return s;
4706
+ })
4707
+ }
4708
+ ),
4709
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Typography, { variant: "pi", textColor: "neutral600", children: t.blockPreviewUrlHelp }),
4710
+ previewSupported ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
4711
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4712
+ TextField2,
2105
4713
  {
2106
4714
  name: "editor-previewUrl",
2107
4715
  label: t.previewUrl,
@@ -2114,8 +4722,8 @@ function Settings({ useClient, usePermissions: usePermissions2, MediaPicker: Med
2114
4722
  })
2115
4723
  }
2116
4724
  ),
2117
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system2.Typography, { variant: "pi", textColor: "neutral600", children: t.previewUrlHelp }),
2118
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
4725
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Typography, { variant: "pi", textColor: "neutral600", children: t.previewUrlHelp }),
4726
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2119
4727
  SelectField2,
2120
4728
  {
2121
4729
  name: "editor-previewMode",
@@ -2129,26 +4737,119 @@ function Settings({ useClient, usePermissions: usePermissions2, MediaPicker: Med
2129
4737
  })
2130
4738
  }
2131
4739
  ),
2132
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system2.Typography, { variant: "pi", textColor: "neutral600", children: t.previewModeHelp }),
2133
- data.blockPreviewAvailable && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
2134
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ToggleField2, { name: "editor-blockPreviewInForm", label: t.blockPreviewInForm, value: editor.blockPreviewInForm, disabled: !canUpdate || saving, onChange: (v) => update((s) => {
4740
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Typography, { variant: "pi", textColor: "neutral600", children: t.previewModeHelp }),
4741
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ToggleField2, { name: "editor-lazyEditors", label: t.lazyEditors, value: editor.lazyEditors !== false, disabled: !canUpdate || saving, onChange: (v) => update((s) => {
4742
+ s.editor.lazyEditors = v;
4743
+ return s;
4744
+ }) }),
4745
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4746
+ UidListField,
4747
+ {
4748
+ TextField: TextField2,
4749
+ name: "editor-lazyFields",
4750
+ label: t.lazyFields,
4751
+ value: editor.lazyFields || [],
4752
+ disabled: !canUpdate || saving || editor.lazyEditors === false,
4753
+ placeholder: "plugin::ckeditor5.CKEditor",
4754
+ onChange: (uids) => update((s) => {
4755
+ s.editor.lazyFields = uids;
4756
+ return s;
4757
+ })
4758
+ }
4759
+ ),
4760
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Typography, { variant: "pi", textColor: badLazy ? "danger600" : "neutral600", role: badLazy ? "alert" : void 0, children: badLazy ? t.f("lazyFieldsInvalid", { max: LAZY_MAX }) : t.lazyEditorsHelp }),
4761
+ data.blockPreviewAvailable && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
4762
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ToggleField2, { name: "editor-blockPreviewInForm", label: t.blockPreviewInForm, value: editor.blockPreviewInForm, disabled: !canUpdate || saving, onChange: (v) => update((s) => {
2135
4763
  s.editor.blockPreviewInForm = v;
2136
4764
  return s;
2137
4765
  }) }),
2138
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system2.Typography, { variant: "pi", textColor: "neutral600", children: t.blockPreviewInFormHelp })
4766
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Typography, { variant: "pi", textColor: "neutral600", children: t.blockPreviewInFormHelp })
2139
4767
  ] })
2140
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system2.Typography, { variant: "pi", textColor: "neutral600", children: t.previewV4 })
4768
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Typography, { variant: "pi", textColor: "neutral600", children: t.previewV4 })
4769
+ ] }) }),
4770
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Box, { padding: 6, background: "neutral0", hasRadius: true, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_design_system3.Flex, { direction: "column", alignItems: "stretch", gap: 4, "data-testid": "row-actions-settings", children: [
4771
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Typography, { variant: "beta", tag: "h2", children: t.rowActionsTitle }),
4772
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ToggleField2, { name: "editor-confirmDelete", label: t.confirmDeleteOption, value: editor.confirmDelete !== false, disabled: !canUpdate || saving, onChange: (v) => update((s) => {
4773
+ s.editor.confirmDelete = v;
4774
+ return s;
4775
+ }) }),
4776
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ToggleField2, { name: "editor-duplicate", label: t.duplicateOption, value: editor.duplicate !== false, disabled: !canUpdate || saving, onChange: (v) => update((s) => {
4777
+ s.editor.duplicate = v;
4778
+ return s;
4779
+ }) }),
4780
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ToggleField2, { name: "editor-clipboard", label: t.clipboardOption, value: editor.clipboard !== false, disabled: !canUpdate || saving, onChange: (v) => update((s) => {
4781
+ s.editor.clipboard = v;
4782
+ return s;
4783
+ }) }),
4784
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4785
+ SelectField2,
4786
+ {
4787
+ name: "editor-hiddenBlocks",
4788
+ label: t.hiddenBlocks,
4789
+ value: editor.hiddenBlocks || "strip",
4790
+ disabled: !canUpdate || saving || !data.hiddenAttribute,
4791
+ options: ["strip", "flag", "off"].map((value) => ({ value, label: t.hiddenModes[value] })),
4792
+ onChange: (v) => update((s) => {
4793
+ s.editor.hiddenBlocks = v;
4794
+ return s;
4795
+ })
4796
+ }
4797
+ ),
4798
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Typography, { variant: "pi", textColor: "neutral600", children: data.hiddenAttribute ? t.hiddenBlocksHelp : t.hiddenAttributeOff })
2141
4799
  ] }) }),
2142
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system2.Box, { padding: 6, background: "neutral0", hasRadius: true, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_design_system2.Flex, { direction: "column", alignItems: "stretch", gap: 4, children: [
2143
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system2.Typography, { variant: "beta", tag: "h2", children: t.components }),
2144
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system2.Searchbar, { name: "component-filter", value: filter, placeholder: t.filter, clearLabel: t.clear, onClear: () => setFilter(""), onChange: (e) => setFilter(e.target.value), children: t.filter }),
2145
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system2.Typography, { variant: "pi", textColor: "neutral600", "data-testid": "components-count", children: t.f("countAll", { count: components.length }) }),
2146
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Grid, { "data-testid": "components-grid", children: components.map((component) => {
4800
+ (data.contentTypes || []).length > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Box, { padding: 6, background: "neutral0", hasRadius: true, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_design_system3.Flex, { direction: "column", alignItems: "stretch", gap: 4, "data-testid": "content-types", children: [
4801
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Typography, { variant: "beta", tag: "h2", children: t.contentTypes }),
4802
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Typography, { variant: "pi", textColor: "neutral600", children: t.contentTypesHelp }),
4803
+ data.contentTypes.map((type) => {
4804
+ const entry = settings.contentTypes?.[type.uid] || {};
4805
+ const set = (key, value, fallback) => update((s) => {
4806
+ const next = { ...s.contentTypes?.[type.uid] || {} };
4807
+ if (value === fallback) delete next[key];
4808
+ else next[key] = value;
4809
+ s.contentTypes = { ...s.contentTypes || {} };
4810
+ if (Object.keys(next).length) s.contentTypes[type.uid] = next;
4811
+ else delete s.contentTypes[type.uid];
4812
+ return s;
4813
+ });
4814
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_design_system3.Flex, { direction: "column", alignItems: "stretch", gap: 4, children: [
4815
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_design_system3.Flex, { gap: 6, alignItems: "flex-end", wrap: "wrap", "data-testid": `content-type-${type.uid}`, children: [
4816
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Box, { style: { minWidth: 220 }, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4817
+ ToggleField2,
4818
+ {
4819
+ name: `type-enabled-${type.uid}`,
4820
+ label: type.displayName,
4821
+ value: entry.enabled !== false,
4822
+ disabled: !canUpdate || saving,
4823
+ onChange: (v) => set("enabled", v, true)
4824
+ }
4825
+ ) }),
4826
+ previewSupported && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Box, { style: { minWidth: 240 }, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4827
+ SelectField2,
4828
+ {
4829
+ name: `type-mode-${type.uid}`,
4830
+ label: t.typeMode,
4831
+ value: entry.previewMode || "default",
4832
+ disabled: !canUpdate || saving || entry.enabled === false,
4833
+ options: [{ value: "default", label: t.f("modeDefault", { mode: t.modes[editor.previewMode || "form"] }) }, ...["form", "split", "preview"].map((value) => ({ value, label: t.modes[value] }))],
4834
+ onChange: (v) => set("previewMode", v, "default")
4835
+ }
4836
+ ) })
4837
+ ] }),
4838
+ previewSupported && entry.enabled !== false && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(SidebarEditor, { type, entry, set, t, disabled: !canUpdate || saving, SelectField: SelectField2, TextField: TextField2 })
4839
+ ] }, type.uid);
4840
+ }),
4841
+ badSidebar && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Typography, { role: "alert", textColor: "danger600", children: t.sidebarInvalidSave })
4842
+ ] }) }),
4843
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Box, { padding: 6, background: "neutral0", hasRadius: true, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_design_system3.Flex, { direction: "column", alignItems: "stretch", gap: 4, children: [
4844
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Typography, { variant: "beta", tag: "h2", children: t.components }),
4845
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Searchbar, { name: "component-filter", value: filter, placeholder: t.filter, clearLabel: t.clear, onClear: () => setFilter(""), onChange: (e) => setFilter(e.target.value), children: t.filter }),
4846
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Typography, { variant: "pi", textColor: "neutral600", "data-testid": "components-count", children: t.f("countAll", { count: components.length }) }),
4847
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Grid, { "data-testid": "components-grid", children: components.map((component) => {
2147
4848
  const entry = settings.components[component.uid] || {};
2148
4849
  const meta = { ...catalog.components?.[component.uid] || {}, ...media[component.uid] || {} };
2149
4850
  const candidates = candidatesFor(component.uid, meta, catalog);
2150
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(Card2, { "data-testid": `settings-${component.uid}`, children: [
2151
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
4851
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Card2, { "data-testid": `settings-${component.uid}`, children: [
4852
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2152
4853
  Thumb,
2153
4854
  {
2154
4855
  candidates,
@@ -2159,23 +4860,23 @@ function Settings({ useClient, usePermissions: usePermissions2, MediaPicker: Med
2159
4860
  onResolved: (index) => setSources((current) => current[component.uid] === index ? current : { ...current, [component.uid]: index })
2160
4861
  }
2161
4862
  ),
2162
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_design_system2.Flex, { direction: "column", alignItems: "flex-start", gap: 2, children: [
2163
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system2.Typography, { fontWeight: "bold", textColor: "neutral800", children: component.displayName }),
2164
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_design_system2.Typography, { variant: "pi", textColor: "neutral600", children: [
4863
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_design_system3.Flex, { direction: "column", alignItems: "flex-start", gap: 2, children: [
4864
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Typography, { fontWeight: "bold", textColor: "neutral800", children: component.displayName }),
4865
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_design_system3.Typography, { variant: "pi", textColor: "neutral600", children: [
2165
4866
  component.uid,
2166
4867
  " \xB7 ",
2167
4868
  component.category
2168
4869
  ] }),
2169
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_design_system2.Typography, { variant: "pi", "data-testid": `source-${component.uid}`, children: [
4870
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_design_system3.Typography, { variant: "pi", "data-testid": `source-${component.uid}`, children: [
2170
4871
  t.source,
2171
4872
  ": ",
2172
4873
  sourceOf(component.uid, Boolean(meta.manualImage)),
2173
4874
  meta.manualName ? ` (${meta.manualName})` : ""
2174
4875
  ] }),
2175
- meta.manualMissing && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system2.Typography, { variant: "pi", role: "alert", textColor: "danger600", children: t.brokenMedia }),
2176
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_design_system2.Flex, { gap: 2, wrap: "wrap", children: [
2177
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system2.Button, { variant: "secondary", size: "S", disabled: !canUpdate || saving, onClick: () => setPicking(component.uid), children: entry.mediaId ? t.replace : t.choose }),
2178
- entry.mediaId && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system2.Button, { variant: "tertiary", size: "S", disabled: !canUpdate || saving, onClick: () => {
4876
+ meta.manualMissing && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Typography, { variant: "pi", role: "alert", textColor: "danger600", children: t.brokenMedia }),
4877
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_design_system3.Flex, { gap: 2, wrap: "wrap", children: [
4878
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Button, { variant: "secondary", size: "S", disabled: !canUpdate || saving, onClick: () => setPicking(component.uid), children: entry.mediaId ? t.replace : t.choose }),
4879
+ entry.mediaId && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system3.Button, { variant: "tertiary", size: "S", disabled: !canUpdate || saving, onClick: () => {
2179
4880
  update((s) => {
2180
4881
  delete s.components[component.uid]?.mediaId;
2181
4882
  if (!Object.keys(s.components[component.uid] || {}).length) delete s.components[component.uid];
@@ -2188,7 +4889,7 @@ function Settings({ useClient, usePermissions: usePermissions2, MediaPicker: Med
2188
4889
  });
2189
4890
  }, children: t.useAutomatic })
2190
4891
  ] }),
2191
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
4892
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2192
4893
  SelectField2,
2193
4894
  {
2194
4895
  name: `template-${component.uid}`,
@@ -2201,12 +4902,34 @@ function Settings({ useClient, usePermissions: usePermissions2, MediaPicker: Med
2201
4902
  return s;
2202
4903
  })
2203
4904
  }
4905
+ ),
4906
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4907
+ SelectField2,
4908
+ {
4909
+ name: `typology-${component.uid}`,
4910
+ label: t.typology,
4911
+ value: entry.typology || "auto",
4912
+ disabled: !canUpdate || saving,
4913
+ options: [{ value: "auto", label: t.f("typologyAuto", { name: t.typologies[component.typology] || component.typology }) }, ...(data.typologies || []).map((value) => ({ value, label: t.typologies[value] }))],
4914
+ onChange: (v) => setComponent(component.uid, "typology", v === "auto" ? void 0 : v)
4915
+ }
4916
+ ),
4917
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4918
+ TagsField,
4919
+ {
4920
+ name: `tags-${component.uid}`,
4921
+ label: t.tags,
4922
+ hint: t.tagsHelp,
4923
+ value: entry.tags || [],
4924
+ disabled: !canUpdate || saving,
4925
+ onChange: (tags) => setComponent(component.uid, "tags", tags.length ? tags : void 0)
4926
+ }
2204
4927
  )
2205
4928
  ] })
2206
4929
  ] }, component.uid);
2207
4930
  }) })
2208
4931
  ] }) }),
2209
- picking && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(MediaPicker2, { onClose: () => setPicking(null), onSelect: (asset) => {
4932
+ picking && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(MediaPicker2, { onClose: () => setPicking(null), onSelect: (asset) => {
2210
4933
  const uid = picking;
2211
4934
  setPicking(null);
2212
4935
  if (!asset?.id) return;
@@ -2219,7 +4942,7 @@ function Settings({ useClient, usePermissions: usePermissions2, MediaPicker: Med
2219
4942
  ] })
2220
4943
  ] }) });
2221
4944
  }
2222
- function register(app, Component, to) {
4945
+ function register(app, Component2, to) {
2223
4946
  app.createSettingSection(
2224
4947
  { id: "blockscene", intlLabel: { id: "blockscene.title", defaultMessage: "Blockscene" } },
2225
4948
  [{
@@ -2228,17 +4951,17 @@ function register(app, Component, to) {
2228
4951
  intlLabel: { id: "blockscene.settings", defaultMessage: "Gallery" },
2229
4952
  // Module shape: older Strapi 4 (e.g. 4.11) only reads `.default` from the loader result.
2230
4953
  // Not an `async` function: Strapi 5 warns on AsyncFunction loaders.
2231
- Component: () => Promise.resolve({ default: Component }),
4954
+ Component: () => Promise.resolve({ default: Component2 }),
2232
4955
  permissions: permissions.read
2233
4956
  }]
2234
4957
  );
2235
4958
  }
2236
4959
 
2237
4960
  // admin/catalog.ts
2238
- var React4 = __toESM(require("react"));
4961
+ var React6 = __toESM(require("react"));
2239
4962
  function useCatalog(get) {
2240
- const [catalog, setCatalog] = React4.useState(null);
2241
- React4.useEffect(() => {
4963
+ const [catalog, setCatalog] = React6.useState(null);
4964
+ React6.useEffect(() => {
2242
4965
  let active = true;
2243
4966
  get("/blockscene/catalog").then(({ data }) => {
2244
4967
  if (active) setCatalog(data);
@@ -2252,23 +4975,63 @@ function useCatalog(get) {
2252
4975
  return catalog;
2253
4976
  }
2254
4977
 
4978
+ // admin/Guard.tsx
4979
+ var React7 = __toESM(require("react"));
4980
+ var import_design_system4 = require("@strapi/design-system");
4981
+ var import_jsx_runtime7 = require("react/jsx-runtime");
4982
+ var Notice = () => {
4983
+ const t = useMessages();
4984
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_design_system4.Box, { padding: 3, background: "warning100", hasRadius: true, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_design_system4.Typography, { variant: "pi", textColor: "warning700", role: "alert", children: t.crashed }) });
4985
+ };
4986
+ var Guard = class extends React7.Component {
4987
+ constructor() {
4988
+ super(...arguments);
4989
+ __publicField(this, "state", { failed: false });
4990
+ }
4991
+ static getDerivedStateFromError() {
4992
+ return { failed: true };
4993
+ }
4994
+ componentDidCatch(error) {
4995
+ console.error("[blockscene] disabled for this view after an error:", error);
4996
+ }
4997
+ render() {
4998
+ return this.state.failed ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Notice, {}) : this.props.children;
4999
+ }
5000
+ };
5001
+
2255
5002
  // admin/strapi4.tsx
2256
- var import_jsx_runtime5 = require("react/jsx-runtime");
2257
- function Modal({ open, onOpenChange, trigger, title, children }) {
2258
- const id = React5.useId();
2259
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
5003
+ var import_jsx_runtime8 = require("react/jsx-runtime");
5004
+ function Modal({ open, onOpenChange, trigger, title, children, width = "80vw" }) {
5005
+ const id = React8.useId();
5006
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
2260
5007
  trigger,
2261
- open && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_design_system3.ModalLayout, { onClose: () => onOpenChange(false), labelledBy: id, children: [
2262
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system3.ModalHeader, { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system3.Typography, { id, variant: "beta", children: title }) }),
2263
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system3.ModalBody, { children })
5008
+ open && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_design_system5.ModalLayout, { onClose: () => onOpenChange(false), labelledBy: id, width, children: [
5009
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_design_system5.ModalHeader, { children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_design_system5.Typography, { id, variant: "beta", children: title }) }),
5010
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_design_system5.ModalBody, { children })
2264
5011
  ] })
2265
5012
  ] });
2266
5013
  }
5014
+ function useRowForm(c, components) {
5015
+ const toggleNotification = (0, import_helper_plugin.useNotification)();
5016
+ const latest = React8.useRef(c.modifiedData);
5017
+ latest.current = c.modifiedData;
5018
+ if (typeof c.onChange !== "function") return null;
5019
+ return {
5020
+ model: c.slug,
5021
+ locale: c.initialData?.locale || void 0,
5022
+ rows: (zone) => Array.isArray(latest.current?.[zone]) ? latest.current[zone] : [],
5023
+ setRows: (zone, rows) => c.onChange({ target: { name: zone, value: rows } }),
5024
+ keys: (rows, _at, n) => integerKeys(rows, n),
5025
+ notify: (type, message) => toggleNotification({ type, message }),
5026
+ prepare: async (rows) => rows.map((row) => cloneRow(row, components))
5027
+ };
5028
+ }
2267
5029
  function Picker() {
2268
5030
  const c = (0, import_helper_plugin.useCMEditViewDataManager)();
2269
- const { get } = (0, import_helper_plugin.useFetchClient)();
5031
+ const { get, put } = (0, import_helper_plugin.useFetchClient)();
2270
5032
  const catalog = useCatalog(get);
2271
5033
  const components = c.allLayoutData?.components || {};
5034
+ const form = useRowForm(c, components);
2272
5035
  const schema = c.layout || c.allLayoutData?.contentType;
2273
5036
  const allowed = (c.isCreatingEntry ? c.createActionAllowedFields : c.updateActionAllowedFields) || [];
2274
5037
  const zones = editableZones(
@@ -2299,9 +5062,9 @@ function Picker() {
2299
5062
  );
2300
5063
  return true;
2301
5064
  };
2302
- if (!catalog?.editor?.enabled) return null;
5065
+ if (!catalog?.editor?.enabled || catalog.contentTypes?.[c.slug]?.enabled === false) return null;
2303
5066
  const docKey = `${c.slug}:${c.isCreatingEntry ? "new" : c.initialData?.id}:${c.initialData?.locale || ""}`;
2304
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
5067
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2305
5068
  Gallery,
2306
5069
  {
2307
5070
  zones,
@@ -2310,17 +5073,19 @@ function Picker() {
2310
5073
  Modal,
2311
5074
  Toggle: ToggleField,
2312
5075
  get,
5076
+ put,
2313
5077
  editor: catalog.editor,
2314
5078
  catalog,
2315
5079
  docKey,
2316
5080
  contentType: c.slug,
2317
- userId: import_helper_plugin.auth.getUserInfo?.()?.id
5081
+ userId: import_helper_plugin.auth.getUserInfo?.()?.id,
5082
+ form
2318
5083
  }
2319
5084
  );
2320
5085
  }
2321
- var ToggleField = ({ name, label, value, onChange, disabled }) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_design_system3.Flex, { gap: 3, children: [
2322
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2323
- import_design_system3.Switch,
5086
+ var ToggleField = ({ name, label, value, onChange, disabled }) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_design_system5.Flex, { gap: 3, children: [
5087
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
5088
+ import_design_system5.Switch,
2324
5089
  {
2325
5090
  name,
2326
5091
  "aria-label": label,
@@ -2329,8 +5094,9 @@ var ToggleField = ({ name, label, value, onChange, disabled }) => /* @__PURE__ *
2329
5094
  onChange: () => onChange(!value)
2330
5095
  }
2331
5096
  ),
2332
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system3.Typography, { children: label })
5097
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_design_system5.Typography, { children: label })
2333
5098
  ] });
5099
+ var TextField = ({ name, label, value, onChange, disabled, placeholder }) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_design_system5.TextInput, { name, label, value, disabled, placeholder, onChange: (e) => onChange(e.target.value) });
2334
5100
  var SelectField = ({
2335
5101
  name,
2336
5102
  label,
@@ -2338,15 +5104,15 @@ var SelectField = ({
2338
5104
  onChange,
2339
5105
  disabled,
2340
5106
  options
2341
- }) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2342
- import_design_system3.SingleSelect,
5107
+ }) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
5108
+ import_design_system5.SingleSelect,
2343
5109
  {
2344
5110
  name,
2345
5111
  label,
2346
5112
  value,
2347
5113
  disabled,
2348
5114
  onChange: (v) => onChange(String(v)),
2349
- children: options.map((o) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system3.SingleSelectOption, { value: o.value, children: o.label }, o.value))
5115
+ children: options.map((o) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_design_system5.SingleSelectOption, { value: o.value, children: o.label }, o.value))
2350
5116
  }
2351
5117
  );
2352
5118
  function MediaPicker({ onClose, onSelect }) {
@@ -2356,7 +5122,7 @@ function MediaPicker({ onClose, onSelect }) {
2356
5122
  onClose();
2357
5123
  return null;
2358
5124
  }
2359
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
5125
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2360
5126
  Dialog,
2361
5127
  {
2362
5128
  onClose,
@@ -2373,7 +5139,7 @@ function usePermissions() {
2373
5139
  isLoading
2374
5140
  };
2375
5141
  }
2376
- var SettingsPage = () => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
5142
+ var SettingsPage = () => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Guard, { children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2377
5143
  Settings,
2378
5144
  {
2379
5145
  useClient: import_helper_plugin.useFetchClient,
@@ -2381,9 +5147,10 @@ var SettingsPage = () => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2381
5147
  MediaPicker,
2382
5148
  ToggleField,
2383
5149
  SelectField,
5150
+ TextField,
2384
5151
  previewSupported: false
2385
5152
  }
2386
- );
5153
+ ) });
2387
5154
  var strapi4_default = {
2388
5155
  register(app) {
2389
5156
  register(app, SettingsPage, "/settings/blockscene");
@@ -2393,7 +5160,7 @@ var strapi4_default = {
2393
5160
  bootstrap(app) {
2394
5161
  app.injectContentManagerComponent("editView", "right-links", {
2395
5162
  name: "blockscene",
2396
- Component: Picker
5163
+ Component: () => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Guard, { children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Picker, {}) })
2397
5164
  });
2398
5165
  }
2399
5166
  };