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