@guard-rail/core 0.10.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +135 -4
- package/dist/adapters/supabase/db.js +33 -0
- package/dist/adapters/supabase/db.js.map +1 -1
- package/dist/cli/doctor.js +175 -6
- package/dist/cli/doctor.js.map +1 -1
- package/dist/core/adapter-types.d.ts +35 -2
- package/dist/core/csp.d.ts +32 -0
- package/dist/core/csp.js +26 -0
- package/dist/core/csp.js.map +1 -0
- package/dist/index.client.d.ts +3 -2
- package/dist/index.client.js +8 -1
- package/dist/index.client.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +8 -1
- package/dist/index.js.map +1 -1
- package/dist/react/Admin.d.ts +5 -3
- package/dist/react/Admin.js +29 -41
- package/dist/react/Admin.js.map +1 -1
- package/dist/react/AuthDrawer.js +22 -34
- package/dist/react/AuthDrawer.js.map +1 -1
- package/dist/react/GRail/client.js +95 -56
- package/dist/react/GRail/client.js.map +1 -1
- package/dist/react/Icons.d.ts +3 -1
- package/dist/react/Icons.js +5 -0
- package/dist/react/Icons.js.map +1 -1
- package/dist/react/MediaPicker.js +12 -16
- package/dist/react/MediaPicker.js.map +1 -1
- package/dist/react/Repeater/client.js +12 -13
- package/dist/react/Repeater/client.js.map +1 -1
- package/dist/react/Team.d.ts +1 -7
- package/dist/react/Team.js +21 -46
- package/dist/react/Team.js.map +1 -1
- package/dist/react/guardrail.css +563 -0
- package/dist/server/actions.d.ts +19 -2
- package/dist/server/actions.js +35 -0
- package/dist/server/actions.js.map +1 -1
- package/package.json +11 -10
- package/supabase/admin/check-grants.sql +12 -3
- package/supabase/migrations/20261103000000_content_history.sql +211 -0
- package/dist/react/styles.d.ts +0 -504
- package/dist/react/styles.js +0 -500
- package/dist/react/styles.js.map +0 -1
|
@@ -3,13 +3,13 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import React, { useEffect, useRef, useState } from "react";
|
|
4
4
|
import { createPortal } from "react-dom";
|
|
5
5
|
import { useRouter } from "next/navigation";
|
|
6
|
-
import { saveOverride } from "../../server/actions";
|
|
6
|
+
import { getRevertInfo, revertField, saveOverride } from "../../server/actions";
|
|
7
|
+
import { generateScaffold } from "../../core/scaffold";
|
|
7
8
|
import { GRailRender } from "./render";
|
|
8
9
|
import { useGRail } from "../Provider";
|
|
9
10
|
import { useAnchorRect } from "../useAnchorRect";
|
|
10
|
-
import { CheckIcon, CloseIcon, OpenIcon, PencilIcon, SpinnerIcon } from "../Icons";
|
|
11
|
+
import { CheckIcon, CloseIcon, OpenIcon, PencilIcon, RevertIcon, SpinnerIcon } from "../Icons";
|
|
11
12
|
import { openHref } from "../openHref";
|
|
12
|
-
import { S } from "../styles";
|
|
13
13
|
import { MediaPicker } from "../MediaPicker";
|
|
14
14
|
function GRailClient({
|
|
15
15
|
as,
|
|
@@ -21,6 +21,8 @@ function GRailClient({
|
|
|
21
21
|
const [saving, setSaving] = useState(false);
|
|
22
22
|
const [error, setError] = useState(null);
|
|
23
23
|
const [row, setRow] = useState(initialOverride);
|
|
24
|
+
const [revert, setRevert] = useState(null);
|
|
25
|
+
const baselineRef = useRef(initialOverride);
|
|
24
26
|
const elementRef = useRef(null);
|
|
25
27
|
const isImage = row.value.type === "image";
|
|
26
28
|
const rect = useAnchorRect(elementRef, editing || isEditMode && isImage, {
|
|
@@ -36,6 +38,8 @@ function GRailClient({
|
|
|
36
38
|
draftTextRef.current = row.value.text ?? "";
|
|
37
39
|
setError(null);
|
|
38
40
|
setEditing(true);
|
|
41
|
+
setRevert(null);
|
|
42
|
+
getRevertInfo(row.pathKey).then(setRevert, () => setRevert(null));
|
|
39
43
|
};
|
|
40
44
|
const updateAttribute = (key, value) => setRow((prev) => ({ ...prev, value: { ...prev.value, [key]: value } }));
|
|
41
45
|
const patchValue = (patch) => setRow((prev) => ({ ...prev, value: { ...prev.value, ...patch } }));
|
|
@@ -49,17 +53,46 @@ function GRailClient({
|
|
|
49
53
|
setError(res.error ?? "Save failed.");
|
|
50
54
|
return;
|
|
51
55
|
}
|
|
56
|
+
baselineRef.current = next;
|
|
52
57
|
setRow(next);
|
|
53
58
|
setEditing(false);
|
|
59
|
+
releaseFocus();
|
|
60
|
+
};
|
|
61
|
+
const releaseFocus = () => {
|
|
62
|
+
if (elementRef.current && document.activeElement === elementRef.current) {
|
|
63
|
+
elementRef.current.blur();
|
|
64
|
+
}
|
|
54
65
|
};
|
|
55
66
|
const handleCancel = () => {
|
|
56
|
-
|
|
57
|
-
|
|
67
|
+
const saved = baselineRef.current;
|
|
68
|
+
setRow(saved);
|
|
69
|
+
draftTextRef.current = saved.value.text ?? "";
|
|
58
70
|
if (isInline && elementRef.current) {
|
|
59
|
-
elementRef.current.textContent =
|
|
71
|
+
elementRef.current.textContent = saved.value.text ?? "";
|
|
60
72
|
}
|
|
61
73
|
setError(null);
|
|
62
74
|
setEditing(false);
|
|
75
|
+
releaseFocus();
|
|
76
|
+
};
|
|
77
|
+
const handleRevert = async () => {
|
|
78
|
+
setSaving(true);
|
|
79
|
+
setError(null);
|
|
80
|
+
const res = await revertField(row.pathKey);
|
|
81
|
+
setSaving(false);
|
|
82
|
+
if (!res.success) {
|
|
83
|
+
setError(res.error ?? "Couldn't revert that.");
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const value = res.action === "restored" && res.value ? res.value : generateScaffold(row.pathKey, htmlProps).value;
|
|
87
|
+
const next = { ...row, value };
|
|
88
|
+
baselineRef.current = next;
|
|
89
|
+
setRow(next);
|
|
90
|
+
draftTextRef.current = value.text ?? "";
|
|
91
|
+
sessionTextRef.current = value.text ?? "";
|
|
92
|
+
if (isInline && elementRef.current) {
|
|
93
|
+
elementRef.current.textContent = value.text ?? "";
|
|
94
|
+
}
|
|
95
|
+
setEditing(false);
|
|
63
96
|
};
|
|
64
97
|
useEffect(() => {
|
|
65
98
|
if (!editing) return;
|
|
@@ -84,17 +117,8 @@ function GRailClient({
|
|
|
84
117
|
...htmlProps,
|
|
85
118
|
ref: elementRef,
|
|
86
119
|
override: renderedValue,
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
// Images are left alone — no outline, position, or z-index — and
|
|
90
|
-
// get a separate ImageMarker instead. Restyling the <img> itself
|
|
91
|
-
// can't work for a background image with content layered over it.
|
|
92
|
-
...isImage ? { cursor: "pointer" } : {
|
|
93
|
-
outline: editing ? "2px solid #2563eb" : "1px dashed #60a5fa",
|
|
94
|
-
outlineOffset: 2,
|
|
95
|
-
cursor: editing ? "text" : "pointer"
|
|
96
|
-
}
|
|
97
|
-
},
|
|
120
|
+
"data-gr-field": isImage ? "image" : "text",
|
|
121
|
+
...editing && !isImage ? { "data-gr-editing": "" } : {},
|
|
98
122
|
onClick: (e) => {
|
|
99
123
|
if (editing) return;
|
|
100
124
|
e.preventDefault();
|
|
@@ -133,7 +157,9 @@ function GRailClient({
|
|
|
133
157
|
onChange: updateAttribute,
|
|
134
158
|
onPatch: patchValue,
|
|
135
159
|
onSave: handleSave,
|
|
136
|
-
onCancel: handleCancel
|
|
160
|
+
onCancel: handleCancel,
|
|
161
|
+
revert,
|
|
162
|
+
onRevert: handleRevert
|
|
137
163
|
}
|
|
138
164
|
)
|
|
139
165
|
] }),
|
|
@@ -168,13 +194,13 @@ function ImageMarker({
|
|
|
168
194
|
"div",
|
|
169
195
|
{
|
|
170
196
|
"data-guardrail": true,
|
|
197
|
+
className: "gr-marker-outline",
|
|
198
|
+
...editing ? { "data-gr-editing": "" } : {},
|
|
171
199
|
style: {
|
|
172
|
-
...S.markerOutline,
|
|
173
200
|
top: rect.top - 2,
|
|
174
201
|
left: rect.left - 2,
|
|
175
202
|
width: rect.width + 4,
|
|
176
|
-
height: height + 4
|
|
177
|
-
border: editing ? "2px solid #2563eb" : "1px dashed #60a5fa"
|
|
203
|
+
height: height + 4
|
|
178
204
|
}
|
|
179
205
|
}
|
|
180
206
|
),
|
|
@@ -183,7 +209,8 @@ function ImageMarker({
|
|
|
183
209
|
{
|
|
184
210
|
type: "button",
|
|
185
211
|
"data-guardrail": true,
|
|
186
|
-
|
|
212
|
+
className: "gr-marker-handle",
|
|
213
|
+
style: { top: handleTop, left: handleLeft },
|
|
187
214
|
"aria-label": "Edit image",
|
|
188
215
|
title: "Edit image",
|
|
189
216
|
onClick: (e) => {
|
|
@@ -199,6 +226,16 @@ function ImageMarker({
|
|
|
199
226
|
)
|
|
200
227
|
] });
|
|
201
228
|
}
|
|
229
|
+
function revertLabel(info) {
|
|
230
|
+
if (info.kind === "previous" && info.at) {
|
|
231
|
+
const when = new Date(info.at);
|
|
232
|
+
if (!Number.isNaN(when.getTime())) {
|
|
233
|
+
return `Revert to the version from ${when.toLocaleDateString(void 0, { month: "short", day: "numeric" })}`;
|
|
234
|
+
}
|
|
235
|
+
return "Revert to the previous version";
|
|
236
|
+
}
|
|
237
|
+
return "Reset to default";
|
|
238
|
+
}
|
|
202
239
|
function Chrome({
|
|
203
240
|
rect,
|
|
204
241
|
isInline,
|
|
@@ -208,7 +245,9 @@ function Chrome({
|
|
|
208
245
|
onChange,
|
|
209
246
|
onPatch,
|
|
210
247
|
onSave,
|
|
211
|
-
onCancel
|
|
248
|
+
onCancel,
|
|
249
|
+
revert,
|
|
250
|
+
onRevert
|
|
212
251
|
}) {
|
|
213
252
|
const { storage } = useGRail();
|
|
214
253
|
const router = useRouter();
|
|
@@ -226,7 +265,7 @@ function Chrome({
|
|
|
226
265
|
/* @__PURE__ */ jsx(
|
|
227
266
|
"button",
|
|
228
267
|
{
|
|
229
|
-
|
|
268
|
+
className: "gr-icon-btn-primary",
|
|
230
269
|
onClick: onSave,
|
|
231
270
|
disabled: saving,
|
|
232
271
|
title: "Save (Enter)",
|
|
@@ -237,56 +276,68 @@ function Chrome({
|
|
|
237
276
|
/* @__PURE__ */ jsx(
|
|
238
277
|
"button",
|
|
239
278
|
{
|
|
240
|
-
|
|
279
|
+
className: "gr-icon-btn",
|
|
241
280
|
onClick: onCancel,
|
|
242
281
|
disabled: saving,
|
|
243
|
-
title: "
|
|
244
|
-
"aria-label": "
|
|
282
|
+
title: "Close (Esc)",
|
|
283
|
+
"aria-label": "Close",
|
|
245
284
|
children: /* @__PURE__ */ jsx(CloseIcon, {})
|
|
246
285
|
}
|
|
286
|
+
),
|
|
287
|
+
revert && revert.kind !== "none" && /* @__PURE__ */ jsx(
|
|
288
|
+
"button",
|
|
289
|
+
{
|
|
290
|
+
className: "gr-icon-btn",
|
|
291
|
+
onClick: onRevert,
|
|
292
|
+
disabled: saving,
|
|
293
|
+
title: revertLabel(revert),
|
|
294
|
+
"aria-label": revertLabel(revert),
|
|
295
|
+
children: /* @__PURE__ */ jsx(RevertIcon, {})
|
|
296
|
+
}
|
|
247
297
|
)
|
|
248
298
|
] });
|
|
249
299
|
if (isInline) {
|
|
250
|
-
return /* @__PURE__ */ jsxs("div", { "data-guardrail": true,
|
|
300
|
+
return /* @__PURE__ */ jsxs("div", { "data-guardrail": true, className: "gr-anchored-bar", style: position, children: [
|
|
251
301
|
actions,
|
|
252
|
-
error && /* @__PURE__ */ jsx("span", {
|
|
302
|
+
error && /* @__PURE__ */ jsx("span", { className: "gr-error gr-mx-6", children: error })
|
|
253
303
|
] });
|
|
254
304
|
}
|
|
255
305
|
return /* @__PURE__ */ jsxs(
|
|
256
306
|
"div",
|
|
257
307
|
{
|
|
258
308
|
"data-guardrail": true,
|
|
259
|
-
|
|
309
|
+
className: "gr-anchored-panel",
|
|
310
|
+
style: position,
|
|
260
311
|
onClick: (e) => e.stopPropagation(),
|
|
261
312
|
children: [
|
|
262
313
|
row.value.type === "image" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
263
314
|
row.value.src && // Plain img: previews must not depend on remotePatterns.
|
|
264
|
-
/* @__PURE__ */ jsx("img", { src: row.value.src, alt: "",
|
|
315
|
+
/* @__PURE__ */ jsx("img", { src: row.value.src, alt: "", className: "gr-preview-thumb" }),
|
|
265
316
|
storage && /* @__PURE__ */ jsx(
|
|
266
317
|
"button",
|
|
267
318
|
{
|
|
268
319
|
type: "button",
|
|
269
|
-
|
|
320
|
+
className: "gr-secondary-btn",
|
|
270
321
|
onClick: () => setPickerOpen(true),
|
|
271
322
|
children: row.value.src ? "Replace image" : "Choose image"
|
|
272
323
|
}
|
|
273
324
|
),
|
|
274
|
-
/* @__PURE__ */ jsx("label", {
|
|
325
|
+
/* @__PURE__ */ jsx("label", { className: "gr-label", children: "Alt text" }),
|
|
275
326
|
/* @__PURE__ */ jsx(
|
|
276
327
|
"input",
|
|
277
328
|
{
|
|
278
|
-
|
|
329
|
+
className: "gr-field",
|
|
279
330
|
value: row.value.alt || "",
|
|
280
331
|
placeholder: "Describe the image for screen readers",
|
|
281
332
|
onChange: (e) => onChange("alt", e.target.value)
|
|
282
333
|
}
|
|
283
334
|
),
|
|
284
335
|
!storage && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
285
|
-
/* @__PURE__ */ jsx("label", {
|
|
336
|
+
/* @__PURE__ */ jsx("label", { className: "gr-label", children: "Image URL" }),
|
|
286
337
|
/* @__PURE__ */ jsx(
|
|
287
338
|
"input",
|
|
288
339
|
{
|
|
289
|
-
|
|
340
|
+
className: "gr-field",
|
|
290
341
|
value: row.value.src || "",
|
|
291
342
|
onChange: (e) => onPatch({ src: e.target.value, width: void 0, height: void 0 })
|
|
292
343
|
}
|
|
@@ -302,48 +353,36 @@ function Chrome({
|
|
|
302
353
|
)
|
|
303
354
|
] }),
|
|
304
355
|
row.value.type === "link" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
305
|
-
/* @__PURE__ */ jsx("label", {
|
|
356
|
+
/* @__PURE__ */ jsx("label", { className: "gr-label", children: "Link text" }),
|
|
306
357
|
/* @__PURE__ */ jsx(
|
|
307
358
|
"input",
|
|
308
359
|
{
|
|
309
|
-
|
|
360
|
+
className: "gr-field",
|
|
310
361
|
value: row.value.text || "",
|
|
311
362
|
onChange: (e) => onChange("text", e.target.value)
|
|
312
363
|
}
|
|
313
364
|
),
|
|
314
|
-
/* @__PURE__ */ jsx("label", {
|
|
365
|
+
/* @__PURE__ */ jsx("label", { className: "gr-label", children: "URL" }),
|
|
315
366
|
/* @__PURE__ */ jsx(
|
|
316
367
|
"input",
|
|
317
368
|
{
|
|
318
|
-
|
|
369
|
+
className: "gr-field",
|
|
319
370
|
value: row.value.href || "",
|
|
320
371
|
onChange: (e) => onChange("href", e.target.value)
|
|
321
372
|
}
|
|
322
373
|
)
|
|
323
374
|
] }),
|
|
324
|
-
error && /* @__PURE__ */ jsx("div", {
|
|
375
|
+
error && /* @__PURE__ */ jsx("div", { className: "gr-error gr-mb-8", children: error }),
|
|
325
376
|
/* @__PURE__ */ jsxs(
|
|
326
377
|
"div",
|
|
327
378
|
{
|
|
328
|
-
|
|
329
|
-
display: "flex",
|
|
330
|
-
gap: 4,
|
|
331
|
-
alignItems: "center",
|
|
332
|
-
justifyContent: row.value.type === "link" ? "space-between" : "flex-end"
|
|
333
|
-
},
|
|
379
|
+
className: row.value.type === "link" ? "gr-flex gr-gap-4 gr-items-center gr-justify-between" : "gr-flex gr-gap-4 gr-items-center gr-justify-end",
|
|
334
380
|
children: [
|
|
335
381
|
row.value.type === "link" && /* @__PURE__ */ jsxs(
|
|
336
382
|
"button",
|
|
337
383
|
{
|
|
338
384
|
type: "button",
|
|
339
|
-
|
|
340
|
-
...S.secondaryBtn,
|
|
341
|
-
width: "auto",
|
|
342
|
-
marginBottom: 0,
|
|
343
|
-
display: "inline-flex",
|
|
344
|
-
alignItems: "center",
|
|
345
|
-
gap: 6
|
|
346
|
-
},
|
|
385
|
+
className: "gr-secondary-btn gr-w-auto gr-mb-0 gr-inline-flex gr-items-center gr-gap-6",
|
|
347
386
|
disabled: !row.value.href?.trim(),
|
|
348
387
|
onClick: () => openHref(row.value.href ?? "", (path) => router.push(path)),
|
|
349
388
|
title: "Open this link",
|
|
@@ -354,7 +393,7 @@ function Chrome({
|
|
|
354
393
|
]
|
|
355
394
|
}
|
|
356
395
|
),
|
|
357
|
-
/* @__PURE__ */ jsx("div", {
|
|
396
|
+
/* @__PURE__ */ jsx("div", { className: "gr-flex gr-gap-4", children: actions })
|
|
358
397
|
]
|
|
359
398
|
}
|
|
360
399
|
)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/react/GRail/client.tsx"],"sourcesContent":["\"use client\";\n\nimport React, { useEffect, useRef, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { useRouter } from \"next/navigation\";\nimport { saveOverride } from \"../../server/actions\";\nimport { GRailRender } from \"./render\";\nimport { useGRail } from \"../Provider\";\nimport { useAnchorRect } from \"../useAnchorRect\";\nimport { CheckIcon, CloseIcon, OpenIcon, PencilIcon, SpinnerIcon } from \"../Icons\";\nimport { openHref } from \"../openHref\";\nimport { S } from \"../styles\";\nimport { MediaPicker } from \"../MediaPicker\";\nimport type { HTMLKeys, OverrideRow, OverrideValue } from \"../../core/types\";\n\nexport function GRailClient({\n as,\n initialOverride,\n ...htmlProps\n}: {\n as?: HTMLKeys;\n initialOverride: OverrideRow;\n [key: string]: unknown;\n}) {\n const { isEditMode } = useGRail();\n const [editing, setEditing] = useState(false);\n const [saving, setSaving] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [row, setRow] = useState<OverrideRow>(initialOverride);\n\n const elementRef = useRef<HTMLElement | null>(null);\n const isImage = row.value.type === \"image\";\n // Image fields keep a rect for their whole time in edit mode (their marker\n // needs it); everything else only while actively editing.\n const rect = useAnchorRect(elementRef, editing || (isEditMode && isImage), {\n // Only the idle marker case (isImage, not actively editing) is gated —\n // an image mid-edit tracks unconditionally, same as text/link chrome.\n gateByVisibility: isImage && !editing,\n });\n\n /**\n * The text the element was rendered with when this edit session began.\n *\n * While editing, the DOM owns the text and React must not touch it. Feeding\n * state back into a contentEditable's children makes React replace the text\n * node on every keystroke, which resets the caret to offset 0 — typing then\n * appears reversed and backspace jumps to the start. Holding this value\n * constant means the children prop never changes, so React leaves the node\n * alone and the caret stays put.\n */\n const sessionTextRef = useRef<string>(initialOverride.value.text ?? \"\");\n /** Live DOM text, updated without re-rendering. Read on save. */\n const draftTextRef = useRef<string>(initialOverride.value.text ?? \"\");\n\n const isInline = row.value.type === \"text\" || row.value.type === \"button\";\n\n const beginEdit = () => {\n sessionTextRef.current = row.value.text ?? \"\";\n draftTextRef.current = row.value.text ?? \"\";\n setError(null);\n setEditing(true);\n };\n\n const updateAttribute = (key: string, value: string) =>\n setRow((prev) => ({ ...prev, value: { ...prev.value, [key]: value } }));\n\n const patchValue = (patch: Partial<OverrideValue>) =>\n setRow((prev) => ({ ...prev, value: { ...prev.value, ...patch } }));\n\n const handleSave = async () => {\n // Inline text lives in the DOM, not in state, so read it back here.\n const next: OverrideRow = isInline\n ? { ...row, value: { ...row.value, text: draftTextRef.current } }\n : row;\n\n setSaving(true);\n setError(null);\n const res = await saveOverride(next);\n setSaving(false);\n\n if (!res.success) {\n setError(res.error ?? \"Save failed.\");\n return;\n }\n setRow(next);\n setEditing(false);\n };\n\n const handleCancel = () => {\n setRow(initialOverride);\n draftTextRef.current = initialOverride.value.text ?? \"\";\n if (isInline && elementRef.current) {\n elementRef.current.textContent = initialOverride.value.text ?? \"\";\n }\n setError(null);\n setEditing(false);\n };\n\n // Clicking away from an active edit cancels it, the same as Escape. Anything\n // inside the field itself (repositioning the caret) or inside the anchored\n // toolbar/panel (both carry data-guardrail) is not \"away\" — checked via\n // closest() rather than React's onClick so a stopPropagation() inside the\n // chrome (image/link panel inputs) reliably suppresses this too, since that\n // call stops the native event before it ever reaches this document listener.\n useEffect(() => {\n if (!editing) return;\n const handlePointerDown = (e: MouseEvent) => {\n const target = e.target as Node;\n if (elementRef.current?.contains(target)) return;\n if (target instanceof Element && target.closest(\"[data-guardrail]\")) return;\n handleCancel();\n };\n document.addEventListener(\"mousedown\", handlePointerDown);\n return () => document.removeEventListener(\"mousedown\", handlePointerDown);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [editing]);\n\n if (!isEditMode) {\n return <GRailRender as={as} {...htmlProps} override={row.value} />;\n }\n\n // Frozen during the session so React never rewrites the live text node.\n const renderedValue =\n editing && isInline\n ? { ...row.value, text: sessionTextRef.current }\n : row.value;\n\n return (\n <>\n <GRailRender\n as={as}\n {...htmlProps}\n ref={elementRef}\n override={renderedValue}\n style={{\n ...(htmlProps.style as React.CSSProperties),\n // Images are left alone — no outline, position, or z-index — and\n // get a separate ImageMarker instead. Restyling the <img> itself\n // can't work for a background image with content layered over it.\n ...(isImage\n ? { cursor: \"pointer\" }\n : {\n outline: editing ? \"2px solid #2563eb\" : \"1px dashed #60a5fa\",\n outlineOffset: 2,\n cursor: editing ? \"text\" : \"pointer\",\n }),\n }}\n onClick={(e: React.MouseEvent) => {\n if (editing) return;\n e.preventDefault();\n // preventDefault only blocks the browser's native action (following\n // an href); it does not stop the click from bubbling. A field\n // rendered inside a Link would still trigger the Link's own onClick\n // and navigate anyway — stopPropagation is what actually claims the\n // click for editing instead. (Use `editable={false}` on fields\n // meant to stay pure navigation, e.g. inside a card that links to\n // that content's own page.)\n e.stopPropagation();\n beginEdit();\n }}\n // Spread only for inline text: images and links would receive invalid\n // DOM props (contentEditable on next/image, etc).\n {...(isInline\n ? {\n contentEditable: editing,\n suppressContentEditableWarning: true,\n onInput: (e: React.FormEvent<HTMLElement>) => {\n // Ref only — setState here re-renders and moves the caret.\n draftTextRef.current = e.currentTarget.textContent ?? \"\";\n },\n onKeyDown: (e: React.KeyboardEvent) => {\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n void handleSave();\n } else if (e.key === \"Escape\") {\n e.preventDefault();\n handleCancel();\n }\n },\n }\n : {})}\n />\n\n {/*\n Portaled to <body>, not rendered next to the field. The chrome is\n position:fixed with a high z-index, but z-index only ranks within the\n nearest stacking context — rendered inline, a field inside any ancestor\n that forms one (a z-indexed or transformed hero, say) traps its own\n popup there, where unrelated content stacked higher elsewhere on the\n page can paint straight over it. Also sidesteps a transformed ancestor\n silently re-anchoring position:fixed to itself instead of the viewport.\n */}\n {rect &&\n (isImage || editing) &&\n createPortal(\n <>\n {isImage && (\n <ImageMarker rect={rect} editing={editing} onEdit={beginEdit} />\n )}\n {editing && (\n <Chrome\n rect={rect}\n isInline={isInline}\n row={row}\n error={error}\n saving={saving}\n onChange={updateAttribute}\n onPatch={patchValue}\n onSave={handleSave}\n onCancel={handleCancel}\n />\n )}\n </>,\n document.body,\n )}\n </>\n );\n}\n\n/**\n * Dashed outline plus an \"Edit\" handle for an image field, positioned from the\n * image's measured rect rather than attached to the image. Everything here is\n * portaled to <body> by the caller, so it ranks above page content whatever the\n * image's own ancestors do, and never shifts the developer's layout.\n */\nfunction ImageMarker({\n rect,\n editing,\n onEdit,\n}: {\n rect: { top: number; left: number; bottom: number; width: number };\n editing: boolean;\n onEdit: () => void;\n}) {\n const height = rect.bottom - rect.top;\n // Collapsed (display:none, zero-size) or scrolled fully out of view.\n if (rect.width <= 0 || height <= 0) return null;\n if (typeof window !== \"undefined\" && (rect.bottom < 0 || rect.top > window.innerHeight)) {\n return null;\n }\n\n const HANDLE_H = 26;\n // A logo-sized image would be mostly covered by its own handle, so small\n // ones get it just outside (above, or below when there's no room above).\n const small = height < HANDLE_H * 2 || rect.width < 120;\n let handleTop: number;\n if (small) {\n handleTop = rect.top - HANDLE_H - 4 >= 4 ? rect.top - HANDLE_H - 4 : rect.bottom + 4;\n } else {\n // Stays on screen for a tall background that's partly scrolled past, but\n // never leaves the image's own bounds.\n handleTop = Math.min(\n Math.max(rect.top + 6, 6),\n Math.max(rect.top + 6, rect.bottom - HANDLE_H - 6),\n );\n }\n const handleLeft = small ? rect.left : rect.left + 6;\n\n return (\n <>\n <div\n data-guardrail\n style={{\n ...S.markerOutline,\n top: rect.top - 2,\n left: rect.left - 2,\n width: rect.width + 4,\n height: height + 4,\n border: editing ? \"2px solid #2563eb\" : \"1px dashed #60a5fa\",\n }}\n />\n {!editing && (\n <button\n type=\"button\"\n data-guardrail\n style={{ ...S.markerHandle, top: handleTop, left: handleLeft }}\n aria-label=\"Edit image\"\n title=\"Edit image\"\n onClick={(e) => {\n // React bubbles synthetic events through portals to the field's\n // *React* ancestors — a field inside a <Link> would otherwise\n // navigate on this click, same reason the field's own onClick\n // claims its click.\n e.preventDefault();\n e.stopPropagation();\n onEdit();\n }}\n >\n <PencilIcon /> Edit\n </button>\n )}\n </>\n );\n}\n\nfunction Chrome({\n rect,\n isInline,\n row,\n error,\n saving,\n onChange,\n onPatch,\n onSave,\n onCancel,\n}: {\n rect: { top: number; left: number; bottom: number; width: number };\n isInline: boolean;\n row: OverrideRow;\n error: string | null;\n saving: boolean;\n onChange: (key: string, value: string) => void;\n onPatch: (patch: Partial<OverrideValue>) => void;\n onSave: () => void;\n onCancel: () => void;\n}) {\n const { storage } = useGRail();\n const router = useRouter();\n const [pickerOpen, setPickerOpen] = useState(false);\n const GAP = 8;\n // Flip above the element when there is no room below it.\n const below = rect.bottom + GAP;\n const flip = typeof window !== \"undefined\" && below > window.innerHeight - 120;\n\n // The panel is anchored at the field's left edge, so a field near the right\n // of the viewport (a nav link, say) would push it — and its Save button —\n // off-screen. Pull it back so its full width stays visible. 260 mirrors\n // anchoredPanel's minWidth; the inline bar is small enough not to need it.\n const PANEL_MIN_WIDTH = 260;\n const maxLeft =\n typeof window !== \"undefined\"\n ? window.innerWidth - (isInline ? 0 : PANEL_MIN_WIDTH) - 8\n : Infinity;\n\n const position: React.CSSProperties = {\n left: Math.max(8, Math.min(rect.left, maxLeft)),\n ...(flip ? { top: Math.max(8, rect.top - GAP - 44) } : { top: below }),\n };\n\n const actions = (\n <>\n <button\n style={S.iconBtnPrimary}\n onClick={onSave}\n disabled={saving}\n title=\"Save (Enter)\"\n aria-label=\"Save\"\n >\n {saving ? <SpinnerIcon /> : <CheckIcon />}\n </button>\n <button\n style={S.iconBtn}\n onClick={onCancel}\n disabled={saving}\n title=\"Cancel (Esc)\"\n aria-label=\"Cancel\"\n >\n <CloseIcon />\n </button>\n </>\n );\n\n // Text and buttons edit in place, so they only need the two controls.\n if (isInline) {\n return (\n <div data-guardrail style={{ ...S.anchoredBar, ...position }}>\n {actions}\n {error && <span style={{ ...S.error, margin: \"0 6px\" }}>{error}</span>}\n </div>\n );\n }\n\n // Images and links carry attributes with no on-page representation.\n return (\n <div\n data-guardrail\n style={{ ...S.anchoredPanel, ...position }}\n onClick={(e) => e.stopPropagation()}\n >\n {row.value.type === \"image\" && (\n <>\n {row.value.src && (\n // Plain img: previews must not depend on remotePatterns.\n <img src={row.value.src} alt=\"\" style={S.previewThumb} />\n )}\n\n {storage && (\n <button\n type=\"button\"\n style={S.secondaryBtn}\n onClick={() => setPickerOpen(true)}\n >\n {row.value.src ? \"Replace image\" : \"Choose image\"}\n </button>\n )}\n\n <label style={S.label}>Alt text</label>\n <input\n style={S.field}\n value={row.value.alt || \"\"}\n placeholder=\"Describe the image for screen readers\"\n onChange={(e) => onChange(\"alt\", e.target.value)}\n />\n\n {/* Only without storage: there is no picker then, so a URL is the one\n way to set an image. A typed URL has unknown proportions, so any\n stored dimensions are cleared. */}\n {!storage && (\n <>\n <label style={S.label}>Image URL</label>\n <input\n style={S.field}\n value={row.value.src || \"\"}\n onChange={(e) =>\n onPatch({ src: e.target.value, width: undefined, height: undefined })\n }\n />\n </>\n )}\n\n {pickerOpen && (\n <MediaPicker\n current={row.value.src}\n onClose={() => setPickerOpen(false)}\n onSelect={(image) =>\n onPatch({ src: image.src, width: image.width, height: image.height })\n }\n />\n )}\n </>\n )}\n\n {row.value.type === \"link\" && (\n <>\n <label style={S.label}>Link text</label>\n <input\n style={S.field}\n value={row.value.text || \"\"}\n onChange={(e) => onChange(\"text\", e.target.value)}\n />\n <label style={S.label}>URL</label>\n <input\n style={S.field}\n value={row.value.href || \"\"}\n onChange={(e) => onChange(\"href\", e.target.value)}\n />\n </>\n )}\n\n {error && <div style={{ ...S.error, marginBottom: 8 }}>{error}</div>}\n <div\n style={{\n display: \"flex\",\n gap: 4,\n alignItems: \"center\",\n justifyContent: row.value.type === \"link\" ? \"space-between\" : \"flex-end\",\n }}\n >\n {/* The field's own click opens this editor, so an editable link can't\n be followed by clicking it — this is how an editor gets to the page. */}\n {row.value.type === \"link\" && (\n <button\n type=\"button\"\n style={{\n ...S.secondaryBtn,\n width: \"auto\",\n marginBottom: 0,\n display: \"inline-flex\",\n alignItems: \"center\",\n gap: 6,\n }}\n disabled={!row.value.href?.trim()}\n onClick={() => openHref(row.value.href ?? \"\", (path) => router.push(path))}\n title=\"Open this link\"\n aria-label=\"Open link\"\n >\n <OpenIcon /> Open\n </button>\n )}\n <div style={{ display: \"flex\", gap: 4 }}>{actions}</div>\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/react/GRail/client.tsx"],"sourcesContent":["\"use client\";\n\nimport React, { useEffect, useRef, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { useRouter } from \"next/navigation\";\nimport { getRevertInfo, revertField, saveOverride } from \"../../server/actions\";\nimport { generateScaffold } from \"../../core/scaffold\";\nimport { GRailRender } from \"./render\";\nimport { useGRail } from \"../Provider\";\nimport { useAnchorRect } from \"../useAnchorRect\";\nimport { CheckIcon, CloseIcon, OpenIcon, PencilIcon, RevertIcon, SpinnerIcon } from \"../Icons\";\nimport { openHref } from \"../openHref\";\nimport { MediaPicker } from \"../MediaPicker\";\nimport type { GRRevertInfo } from \"../../core/adapter-types\";\nimport type { HTMLKeys, OverrideRow, OverrideValue } from \"../../core/types\";\n\nexport function GRailClient({\n as,\n initialOverride,\n ...htmlProps\n}: {\n as?: HTMLKeys;\n initialOverride: OverrideRow;\n [key: string]: unknown;\n}) {\n const { isEditMode } = useGRail();\n const [editing, setEditing] = useState(false);\n const [saving, setSaving] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [row, setRow] = useState<OverrideRow>(initialOverride);\n /** What a revert would do for this field; fetched when the editor opens. */\n const [revert, setRevert] = useState<GRRevertInfo | null>(null);\n /**\n * The last value known to be saved: what the page loaded with, then whatever\n * a save or a revert since has made current. Closing the editor returns to\n * this, not to the page-load value.\n * the page-load value.\n */\n const baselineRef = useRef<OverrideRow>(initialOverride);\n\n const elementRef = useRef<HTMLElement | null>(null);\n const isImage = row.value.type === \"image\";\n // Image fields keep a rect for their whole time in edit mode (their marker\n // needs it); everything else only while actively editing.\n const rect = useAnchorRect(elementRef, editing || (isEditMode && isImage), {\n // Only the idle marker case (isImage, not actively editing) is gated —\n // an image mid-edit tracks unconditionally, same as text/link chrome.\n gateByVisibility: isImage && !editing,\n });\n\n /**\n * The text the element was rendered with when this edit session began.\n *\n * While editing, the DOM owns the text and React must not touch it. Feeding\n * state back into a contentEditable's children makes React replace the text\n * node on every keystroke, which resets the caret to offset 0 — typing then\n * appears reversed and backspace jumps to the start. Holding this value\n * constant means the children prop never changes, so React leaves the node\n * alone and the caret stays put.\n */\n const sessionTextRef = useRef<string>(initialOverride.value.text ?? \"\");\n /** Live DOM text, updated without re-rendering. Read on save. */\n const draftTextRef = useRef<string>(initialOverride.value.text ?? \"\");\n\n const isInline = row.value.type === \"text\" || row.value.type === \"button\";\n\n const beginEdit = () => {\n sessionTextRef.current = row.value.text ?? \"\";\n draftTextRef.current = row.value.text ?? \"\";\n setError(null);\n setEditing(true);\n setRevert(null);\n getRevertInfo(row.pathKey).then(setRevert, () => setRevert(null));\n };\n\n const updateAttribute = (key: string, value: string) =>\n setRow((prev) => ({ ...prev, value: { ...prev.value, [key]: value } }));\n\n const patchValue = (patch: Partial<OverrideValue>) =>\n setRow((prev) => ({ ...prev, value: { ...prev.value, ...patch } }));\n\n const handleSave = async () => {\n // Inline text lives in the DOM, not in state, so read it back here.\n const next: OverrideRow = isInline\n ? { ...row, value: { ...row.value, text: draftTextRef.current } }\n : row;\n\n setSaving(true);\n setError(null);\n const res = await saveOverride(next);\n setSaving(false);\n\n if (!res.success) {\n setError(res.error ?? \"Save failed.\");\n return;\n }\n baselineRef.current = next;\n setRow(next);\n setEditing(false);\n releaseFocus();\n };\n\n // Closing the editor — the ✕, Esc, or clicking away all land here. It leaves\n // the field as it was last saved (unsaved typing is dropped) and releases\n // focus, so the caret doesn't linger on text that's no longer being edited.\n const releaseFocus = () => {\n if (elementRef.current && document.activeElement === elementRef.current) {\n elementRef.current.blur();\n }\n };\n\n const handleCancel = () => {\n const saved = baselineRef.current;\n setRow(saved);\n draftTextRef.current = saved.value.text ?? \"\";\n if (isInline && elementRef.current) {\n elementRef.current.textContent = saved.value.text ?? \"\";\n }\n setError(null);\n setEditing(false);\n releaseFocus();\n };\n\n // One step back: the previous saved value, or — with none left — the default\n // written in the page, which is rebuilt here from the same props the server\n // used, so the field updates in place without waiting for a refresh.\n const handleRevert = async () => {\n setSaving(true);\n setError(null);\n const res = await revertField(row.pathKey);\n setSaving(false);\n\n if (!res.success) {\n setError(res.error ?? \"Couldn't revert that.\");\n return;\n }\n const value =\n res.action === \"restored\" && res.value\n ? res.value\n : generateScaffold(row.pathKey, htmlProps).value;\n const next: OverrideRow = { ...row, value };\n baselineRef.current = next;\n setRow(next);\n draftTextRef.current = value.text ?? \"\";\n sessionTextRef.current = value.text ?? \"\";\n if (isInline && elementRef.current) {\n elementRef.current.textContent = value.text ?? \"\";\n }\n setEditing(false);\n };\n\n // Clicking away from an active edit cancels it, the same as Escape. Anything\n // inside the field itself (repositioning the caret) or inside the anchored\n // toolbar/panel (both carry data-guardrail) is not \"away\" — checked via\n // closest() rather than React's onClick so a stopPropagation() inside the\n // chrome (image/link panel inputs) reliably suppresses this too, since that\n // call stops the native event before it ever reaches this document listener.\n useEffect(() => {\n if (!editing) return;\n const handlePointerDown = (e: MouseEvent) => {\n const target = e.target as Node;\n if (elementRef.current?.contains(target)) return;\n if (target instanceof Element && target.closest(\"[data-guardrail]\")) return;\n handleCancel();\n };\n document.addEventListener(\"mousedown\", handlePointerDown);\n return () => document.removeEventListener(\"mousedown\", handlePointerDown);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [editing]);\n\n if (!isEditMode) {\n return <GRailRender as={as} {...htmlProps} override={row.value} />;\n }\n\n // Frozen during the session so React never rewrites the live text node.\n const renderedValue =\n editing && isInline\n ? { ...row.value, text: sessionTextRef.current }\n : row.value;\n\n return (\n <>\n <GRailRender\n as={as}\n {...htmlProps}\n ref={elementRef}\n override={renderedValue}\n // The outline and cursor come from guardrail.css, keyed off these\n // attributes — not an inline style, which a strict CSP refuses in\n // server-rendered markup (and which would be repeated on every field).\n // Images are left alone — no outline, position, or z-index — and get a\n // separate ImageMarker instead. Restyling the <img> itself can't work\n // for a background image with content layered over it.\n data-gr-field={isImage ? \"image\" : \"text\"}\n {...(editing && !isImage ? { \"data-gr-editing\": \"\" } : {})}\n onClick={(e: React.MouseEvent) => {\n if (editing) return;\n e.preventDefault();\n // preventDefault only blocks the browser's native action (following\n // an href); it does not stop the click from bubbling. A field\n // rendered inside a Link would still trigger the Link's own onClick\n // and navigate anyway — stopPropagation is what actually claims the\n // click for editing instead. (Use `editable={false}` on fields\n // meant to stay pure navigation, e.g. inside a card that links to\n // that content's own page.)\n e.stopPropagation();\n beginEdit();\n }}\n // Spread only for inline text: images and links would receive invalid\n // DOM props (contentEditable on next/image, etc).\n {...(isInline\n ? {\n contentEditable: editing,\n suppressContentEditableWarning: true,\n onInput: (e: React.FormEvent<HTMLElement>) => {\n // Ref only — setState here re-renders and moves the caret.\n draftTextRef.current = e.currentTarget.textContent ?? \"\";\n },\n onKeyDown: (e: React.KeyboardEvent) => {\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n void handleSave();\n } else if (e.key === \"Escape\") {\n e.preventDefault();\n handleCancel();\n }\n },\n }\n : {})}\n />\n\n {/*\n Portaled to <body>, not rendered next to the field. The chrome is\n position:fixed with a high z-index, but z-index only ranks within the\n nearest stacking context — rendered inline, a field inside any ancestor\n that forms one (a z-indexed or transformed hero, say) traps its own\n popup there, where unrelated content stacked higher elsewhere on the\n page can paint straight over it. Also sidesteps a transformed ancestor\n silently re-anchoring position:fixed to itself instead of the viewport.\n */}\n {rect &&\n (isImage || editing) &&\n createPortal(\n <>\n {isImage && (\n <ImageMarker rect={rect} editing={editing} onEdit={beginEdit} />\n )}\n {editing && (\n <Chrome\n rect={rect}\n isInline={isInline}\n row={row}\n error={error}\n saving={saving}\n onChange={updateAttribute}\n onPatch={patchValue}\n onSave={handleSave}\n onCancel={handleCancel}\n revert={revert}\n onRevert={handleRevert}\n />\n )}\n </>,\n document.body,\n )}\n </>\n );\n}\n\n/**\n * Dashed outline plus an \"Edit\" handle for an image field, positioned from the\n * image's measured rect rather than attached to the image. Everything here is\n * portaled to <body> by the caller, so it ranks above page content whatever the\n * image's own ancestors do, and never shifts the developer's layout.\n */\nfunction ImageMarker({\n rect,\n editing,\n onEdit,\n}: {\n rect: { top: number; left: number; bottom: number; width: number };\n editing: boolean;\n onEdit: () => void;\n}) {\n const height = rect.bottom - rect.top;\n // Collapsed (display:none, zero-size) or scrolled fully out of view.\n if (rect.width <= 0 || height <= 0) return null;\n if (typeof window !== \"undefined\" && (rect.bottom < 0 || rect.top > window.innerHeight)) {\n return null;\n }\n\n const HANDLE_H = 26;\n // A logo-sized image would be mostly covered by its own handle, so small\n // ones get it just outside (above, or below when there's no room above).\n const small = height < HANDLE_H * 2 || rect.width < 120;\n let handleTop: number;\n if (small) {\n handleTop = rect.top - HANDLE_H - 4 >= 4 ? rect.top - HANDLE_H - 4 : rect.bottom + 4;\n } else {\n // Stays on screen for a tall background that's partly scrolled past, but\n // never leaves the image's own bounds.\n handleTop = Math.min(\n Math.max(rect.top + 6, 6),\n Math.max(rect.top + 6, rect.bottom - HANDLE_H - 6),\n );\n }\n const handleLeft = small ? rect.left : rect.left + 6;\n\n return (\n <>\n <div\n data-guardrail\n className=\"gr-marker-outline\"\n {...(editing ? { \"data-gr-editing\": \"\" } : {})}\n style={{\n top: rect.top - 2,\n left: rect.left - 2,\n width: rect.width + 4,\n height: height + 4,\n }}\n />\n {!editing && (\n <button\n type=\"button\"\n data-guardrail\n className=\"gr-marker-handle\"\n style={{ top: handleTop, left: handleLeft }}\n aria-label=\"Edit image\"\n title=\"Edit image\"\n onClick={(e) => {\n // React bubbles synthetic events through portals to the field's\n // *React* ancestors — a field inside a <Link> would otherwise\n // navigate on this click, same reason the field's own onClick\n // claims its click.\n e.preventDefault();\n e.stopPropagation();\n onEdit();\n }}\n >\n <PencilIcon /> Edit\n </button>\n )}\n </>\n );\n}\n\n/** \"Revert to the version from Oct 28\" / \"Reset to default\" — what the button will actually do. */\nfunction revertLabel(info: GRRevertInfo): string {\n if (info.kind === \"previous\" && info.at) {\n const when = new Date(info.at);\n if (!Number.isNaN(when.getTime())) {\n return `Revert to the version from ${when.toLocaleDateString(undefined, { month: \"short\", day: \"numeric\" })}`;\n }\n return \"Revert to the previous version\";\n }\n return \"Reset to default\";\n}\n\nfunction Chrome({\n rect,\n isInline,\n row,\n error,\n saving,\n onChange,\n onPatch,\n onSave,\n onCancel,\n revert,\n onRevert,\n}: {\n rect: { top: number; left: number; bottom: number; width: number };\n isInline: boolean;\n row: OverrideRow;\n error: string | null;\n saving: boolean;\n onChange: (key: string, value: string) => void;\n onPatch: (patch: Partial<OverrideValue>) => void;\n onSave: () => void;\n onCancel: () => void;\n revert: GRRevertInfo | null;\n onRevert: () => void;\n}) {\n const { storage } = useGRail();\n const router = useRouter();\n const [pickerOpen, setPickerOpen] = useState(false);\n const GAP = 8;\n // Flip above the element when there is no room below it.\n const below = rect.bottom + GAP;\n const flip = typeof window !== \"undefined\" && below > window.innerHeight - 120;\n\n // The panel is anchored at the field's left edge, so a field near the right\n // of the viewport (a nav link, say) would push it — and its Save button —\n // off-screen. Pull it back so its full width stays visible. 260 mirrors\n // anchoredPanel's minWidth; the inline bar is small enough not to need it.\n const PANEL_MIN_WIDTH = 260;\n const maxLeft =\n typeof window !== \"undefined\"\n ? window.innerWidth - (isInline ? 0 : PANEL_MIN_WIDTH) - 8\n : Infinity;\n\n const position: React.CSSProperties = {\n left: Math.max(8, Math.min(rect.left, maxLeft)),\n ...(flip ? { top: Math.max(8, rect.top - GAP - 44) } : { top: below }),\n };\n\n const actions = (\n <>\n <button\n className=\"gr-icon-btn-primary\"\n onClick={onSave}\n disabled={saving}\n title=\"Save (Enter)\"\n aria-label=\"Save\"\n >\n {saving ? <SpinnerIcon /> : <CheckIcon />}\n </button>\n <button\n className=\"gr-icon-btn\"\n onClick={onCancel}\n disabled={saving}\n title=\"Close (Esc)\"\n aria-label=\"Close\"\n >\n <CloseIcon />\n </button>\n {/* Only when there is something to go back to, and labeled with what it will do. */}\n {revert && revert.kind !== \"none\" && (\n <button\n className=\"gr-icon-btn\"\n onClick={onRevert}\n disabled={saving}\n title={revertLabel(revert)}\n aria-label={revertLabel(revert)}\n >\n <RevertIcon />\n </button>\n )}\n </>\n );\n\n // Text and buttons edit in place, so they only need the two controls.\n if (isInline) {\n return (\n <div data-guardrail className=\"gr-anchored-bar\" style={position}>\n {actions}\n {error && <span className=\"gr-error gr-mx-6\">{error}</span>}\n </div>\n );\n }\n\n // Images and links carry attributes with no on-page representation.\n return (\n <div\n data-guardrail\n className=\"gr-anchored-panel\"\n style={position}\n onClick={(e) => e.stopPropagation()}\n >\n {row.value.type === \"image\" && (\n <>\n {row.value.src && (\n // Plain img: previews must not depend on remotePatterns.\n <img src={row.value.src} alt=\"\" className=\"gr-preview-thumb\" />\n )}\n\n {storage && (\n <button\n type=\"button\"\n className=\"gr-secondary-btn\"\n onClick={() => setPickerOpen(true)}\n >\n {row.value.src ? \"Replace image\" : \"Choose image\"}\n </button>\n )}\n\n <label className=\"gr-label\">Alt text</label>\n <input\n className=\"gr-field\"\n value={row.value.alt || \"\"}\n placeholder=\"Describe the image for screen readers\"\n onChange={(e) => onChange(\"alt\", e.target.value)}\n />\n\n {/* Only without storage: there is no picker then, so a URL is the one\n way to set an image. A typed URL has unknown proportions, so any\n stored dimensions are cleared. */}\n {!storage && (\n <>\n <label className=\"gr-label\">Image URL</label>\n <input\n className=\"gr-field\"\n value={row.value.src || \"\"}\n onChange={(e) =>\n onPatch({ src: e.target.value, width: undefined, height: undefined })\n }\n />\n </>\n )}\n\n {pickerOpen && (\n <MediaPicker\n current={row.value.src}\n onClose={() => setPickerOpen(false)}\n onSelect={(image) =>\n onPatch({ src: image.src, width: image.width, height: image.height })\n }\n />\n )}\n </>\n )}\n\n {row.value.type === \"link\" && (\n <>\n <label className=\"gr-label\">Link text</label>\n <input\n className=\"gr-field\"\n value={row.value.text || \"\"}\n onChange={(e) => onChange(\"text\", e.target.value)}\n />\n <label className=\"gr-label\">URL</label>\n <input\n className=\"gr-field\"\n value={row.value.href || \"\"}\n onChange={(e) => onChange(\"href\", e.target.value)}\n />\n </>\n )}\n\n {error && <div className=\"gr-error gr-mb-8\">{error}</div>}\n <div\n className={\n row.value.type === \"link\"\n ? \"gr-flex gr-gap-4 gr-items-center gr-justify-between\"\n : \"gr-flex gr-gap-4 gr-items-center gr-justify-end\"\n }\n >\n {/* The field's own click opens this editor, so an editable link can't\n be followed by clicking it — this is how an editor gets to the page. */}\n {row.value.type === \"link\" && (\n <button\n type=\"button\"\n className=\"gr-secondary-btn gr-w-auto gr-mb-0 gr-inline-flex gr-items-center gr-gap-6\"\n disabled={!row.value.href?.trim()}\n onClick={() => openHref(row.value.href ?? \"\", (path) => router.push(path))}\n title=\"Open this link\"\n aria-label=\"Open link\"\n >\n <OpenIcon /> Open\n </button>\n )}\n <div className=\"gr-flex gr-gap-4\">{actions}</div>\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
package/dist/react/Icons.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
|
|
3
3
|
declare const CheckIcon: () => react_jsx_runtime.JSX.Element;
|
|
4
|
+
/** A circular arrow, for reverting a field to its previous value. */
|
|
5
|
+
declare const RevertIcon: () => react_jsx_runtime.JSX.Element;
|
|
4
6
|
declare const CloseIcon: () => react_jsx_runtime.JSX.Element;
|
|
5
7
|
declare const PencilIcon: () => react_jsx_runtime.JSX.Element;
|
|
6
8
|
declare const PeopleIcon: () => react_jsx_runtime.JSX.Element;
|
|
@@ -13,4 +15,4 @@ declare const SpinnerIcon: () => react_jsx_runtime.JSX.Element;
|
|
|
13
15
|
declare const OpenIcon: () => react_jsx_runtime.JSX.Element;
|
|
14
16
|
declare const KeyIcon: () => react_jsx_runtime.JSX.Element;
|
|
15
17
|
|
|
16
|
-
export { ArrowDownIcon, ArrowUpIcon, CheckIcon, CloseIcon, KeyIcon, LogOutIcon, OpenIcon, PencilIcon, PeopleIcon, PlusIcon, SpinnerIcon, TrashIcon };
|
|
18
|
+
export { ArrowDownIcon, ArrowUpIcon, CheckIcon, CloseIcon, KeyIcon, LogOutIcon, OpenIcon, PencilIcon, PeopleIcon, PlusIcon, RevertIcon, SpinnerIcon, TrashIcon };
|
package/dist/react/Icons.js
CHANGED
|
@@ -11,6 +11,10 @@ const base = {
|
|
|
11
11
|
strokeLinejoin: "round"
|
|
12
12
|
};
|
|
13
13
|
const CheckIcon = () => /* @__PURE__ */ jsx("svg", { ...base, "aria-hidden": "true", children: /* @__PURE__ */ jsx("polyline", { points: "20 6 9 17 4 12" }) });
|
|
14
|
+
const RevertIcon = () => /* @__PURE__ */ jsxs("svg", { ...base, "aria-hidden": "true", children: [
|
|
15
|
+
/* @__PURE__ */ jsx("polyline", { points: "1 4 1 10 7 10" }),
|
|
16
|
+
/* @__PURE__ */ jsx("path", { d: "M3.51 15a9 9 0 1 0 2.13-9.36L1 10" })
|
|
17
|
+
] });
|
|
14
18
|
const CloseIcon = () => /* @__PURE__ */ jsxs("svg", { ...base, "aria-hidden": "true", children: [
|
|
15
19
|
/* @__PURE__ */ jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
|
|
16
20
|
/* @__PURE__ */ jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
|
|
@@ -75,6 +79,7 @@ export {
|
|
|
75
79
|
PencilIcon,
|
|
76
80
|
PeopleIcon,
|
|
77
81
|
PlusIcon,
|
|
82
|
+
RevertIcon,
|
|
78
83
|
SpinnerIcon,
|
|
79
84
|
TrashIcon
|
|
80
85
|
};
|
package/dist/react/Icons.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/react/Icons.tsx"],"sourcesContent":["import React from \"react\";\n\n/**\n * Inline SVG rather than an icon package: Guardrail's chrome should not add a\n * runtime dependency to every consuming app.\n */\nconst base = {\n width: 14,\n height: 14,\n viewBox: \"0 0 24 24\",\n fill: \"none\",\n stroke: \"currentColor\",\n strokeWidth: 3,\n strokeLinecap: \"round\" as const,\n strokeLinejoin: \"round\" as const,\n};\n\nexport const CheckIcon = () => (\n <svg {...base} aria-hidden=\"true\">\n <polyline points=\"20 6 9 17 4 12\" />\n </svg>\n);\n\nexport const CloseIcon = () => (\n <svg {...base} aria-hidden=\"true\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\" />\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\" />\n </svg>\n);\n\nexport const PencilIcon = () => (\n <svg {...base} strokeWidth={2} aria-hidden=\"true\">\n <path d=\"M12 20h9\" />\n <path d=\"M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z\" />\n </svg>\n);\n\nexport const PeopleIcon = () => (\n <svg {...base} strokeWidth={2} aria-hidden=\"true\">\n <path d=\"M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2\" />\n <circle cx=\"9\" cy=\"7\" r=\"4\" />\n <path d=\"M23 21v-2a4 4 0 0 0-3-3.87\" />\n <path d=\"M16 3.13a4 4 0 0 1 0 7.75\" />\n </svg>\n);\n\nexport const PlusIcon = () => (\n <svg {...base} aria-hidden=\"true\">\n <line x1=\"12\" y1=\"5\" x2=\"12\" y2=\"19\" />\n <line x1=\"5\" y1=\"12\" x2=\"19\" y2=\"12\" />\n </svg>\n);\n\nexport const TrashIcon = () => (\n <svg {...base} strokeWidth={2} aria-hidden=\"true\">\n <polyline points=\"3 6 5 6 21 6\" />\n <path d=\"M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6\" />\n <path d=\"M10 11v6M14 11v6M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2\" />\n </svg>\n);\n\nexport const ArrowUpIcon = () => (\n <svg {...base} aria-hidden=\"true\">\n <line x1=\"12\" y1=\"19\" x2=\"12\" y2=\"5\" />\n <polyline points=\"6 11 12 5 18 11\" />\n </svg>\n);\n\nexport const ArrowDownIcon = () => (\n <svg {...base} aria-hidden=\"true\">\n <line x1=\"12\" y1=\"5\" x2=\"12\" y2=\"19\" />\n <polyline points=\"18 13 12 19 6 13\" />\n </svg>\n);\n\nexport const LogOutIcon = () => (\n <svg {...base} strokeWidth={2} aria-hidden=\"true\">\n <path d=\"M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4\" />\n <polyline points=\"16 17 21 12 16 7\" />\n <line x1=\"21\" y1=\"12\" x2=\"9\" y2=\"12\" />\n </svg>\n);\n\nexport const SpinnerIcon = () => (\n <svg {...base} aria-hidden=\"true\">\n <path d=\"M21 12a9 9 0 1 1-6.2-8.6\">\n <animateTransform\n attributeName=\"transform\"\n type=\"rotate\"\n from=\"0 12 12\"\n to=\"360 12 12\"\n dur=\"0.7s\"\n repeatCount=\"indefinite\"\n />\n </path>\n </svg>\n);\n\nexport const OpenIcon = () => (\n <svg {...base} strokeWidth={2} aria-hidden=\"true\">\n <path d=\"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6\" />\n <polyline points=\"15 3 21 3 21 9\" />\n <line x1=\"10\" y1=\"14\" x2=\"21\" y2=\"3\" />\n </svg>\n);\n\nexport const KeyIcon = () => (\n <svg {...base} strokeWidth={2} aria-hidden=\"true\">\n <path d=\"M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4\" />\n </svg>\n);\n"],"mappings":"AAmBI,
|
|
1
|
+
{"version":3,"sources":["../../src/react/Icons.tsx"],"sourcesContent":["import React from \"react\";\n\n/**\n * Inline SVG rather than an icon package: Guardrail's chrome should not add a\n * runtime dependency to every consuming app.\n */\nconst base = {\n width: 14,\n height: 14,\n viewBox: \"0 0 24 24\",\n fill: \"none\",\n stroke: \"currentColor\",\n strokeWidth: 3,\n strokeLinecap: \"round\" as const,\n strokeLinejoin: \"round\" as const,\n};\n\nexport const CheckIcon = () => (\n <svg {...base} aria-hidden=\"true\">\n <polyline points=\"20 6 9 17 4 12\" />\n </svg>\n);\n\n/** A circular arrow, for reverting a field to its previous value. */\nexport const RevertIcon = () => (\n <svg {...base} aria-hidden=\"true\">\n <polyline points=\"1 4 1 10 7 10\" />\n <path d=\"M3.51 15a9 9 0 1 0 2.13-9.36L1 10\" />\n </svg>\n);\n\nexport const CloseIcon = () => (\n <svg {...base} aria-hidden=\"true\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\" />\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\" />\n </svg>\n);\n\nexport const PencilIcon = () => (\n <svg {...base} strokeWidth={2} aria-hidden=\"true\">\n <path d=\"M12 20h9\" />\n <path d=\"M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z\" />\n </svg>\n);\n\nexport const PeopleIcon = () => (\n <svg {...base} strokeWidth={2} aria-hidden=\"true\">\n <path d=\"M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2\" />\n <circle cx=\"9\" cy=\"7\" r=\"4\" />\n <path d=\"M23 21v-2a4 4 0 0 0-3-3.87\" />\n <path d=\"M16 3.13a4 4 0 0 1 0 7.75\" />\n </svg>\n);\n\nexport const PlusIcon = () => (\n <svg {...base} aria-hidden=\"true\">\n <line x1=\"12\" y1=\"5\" x2=\"12\" y2=\"19\" />\n <line x1=\"5\" y1=\"12\" x2=\"19\" y2=\"12\" />\n </svg>\n);\n\nexport const TrashIcon = () => (\n <svg {...base} strokeWidth={2} aria-hidden=\"true\">\n <polyline points=\"3 6 5 6 21 6\" />\n <path d=\"M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6\" />\n <path d=\"M10 11v6M14 11v6M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2\" />\n </svg>\n);\n\nexport const ArrowUpIcon = () => (\n <svg {...base} aria-hidden=\"true\">\n <line x1=\"12\" y1=\"19\" x2=\"12\" y2=\"5\" />\n <polyline points=\"6 11 12 5 18 11\" />\n </svg>\n);\n\nexport const ArrowDownIcon = () => (\n <svg {...base} aria-hidden=\"true\">\n <line x1=\"12\" y1=\"5\" x2=\"12\" y2=\"19\" />\n <polyline points=\"18 13 12 19 6 13\" />\n </svg>\n);\n\nexport const LogOutIcon = () => (\n <svg {...base} strokeWidth={2} aria-hidden=\"true\">\n <path d=\"M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4\" />\n <polyline points=\"16 17 21 12 16 7\" />\n <line x1=\"21\" y1=\"12\" x2=\"9\" y2=\"12\" />\n </svg>\n);\n\nexport const SpinnerIcon = () => (\n <svg {...base} aria-hidden=\"true\">\n <path d=\"M21 12a9 9 0 1 1-6.2-8.6\">\n <animateTransform\n attributeName=\"transform\"\n type=\"rotate\"\n from=\"0 12 12\"\n to=\"360 12 12\"\n dur=\"0.7s\"\n repeatCount=\"indefinite\"\n />\n </path>\n </svg>\n);\n\nexport const OpenIcon = () => (\n <svg {...base} strokeWidth={2} aria-hidden=\"true\">\n <path d=\"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6\" />\n <polyline points=\"15 3 21 3 21 9\" />\n <line x1=\"10\" y1=\"14\" x2=\"21\" y2=\"3\" />\n </svg>\n);\n\nexport const KeyIcon = () => (\n <svg {...base} strokeWidth={2} aria-hidden=\"true\">\n <path d=\"M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4\" />\n </svg>\n);\n"],"mappings":"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","names":[]}
|
|
@@ -3,7 +3,6 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import React, { useCallback, useEffect, useRef, useState } from "react";
|
|
4
4
|
import { createAssetUpload, listAssets } from "../server/actions";
|
|
5
5
|
import { CloseIcon, SpinnerIcon } from "./Icons";
|
|
6
|
-
import { S, v } from "./styles";
|
|
7
6
|
import { prepareImage } from "./resizeImage";
|
|
8
7
|
function measure(url) {
|
|
9
8
|
return new Promise((resolve) => {
|
|
@@ -81,35 +80,35 @@ function MediaPicker({
|
|
|
81
80
|
if (file) void upload(file);
|
|
82
81
|
};
|
|
83
82
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
84
|
-
/* @__PURE__ */ jsx("div", { "data-guardrail": true,
|
|
83
|
+
/* @__PURE__ */ jsx("div", { "data-guardrail": true, className: "gr-picker-scrim", onClick: onClose }),
|
|
85
84
|
/* @__PURE__ */ jsxs(
|
|
86
85
|
"div",
|
|
87
86
|
{
|
|
88
87
|
"data-guardrail": true,
|
|
89
88
|
role: "dialog",
|
|
90
89
|
"aria-label": "Choose an image",
|
|
91
|
-
|
|
90
|
+
className: "gr-picker",
|
|
92
91
|
onClick: (e) => e.stopPropagation(),
|
|
93
92
|
children: [
|
|
94
|
-
/* @__PURE__ */ jsxs("header", {
|
|
95
|
-
/* @__PURE__ */ jsx("h2", {
|
|
93
|
+
/* @__PURE__ */ jsxs("header", { className: "gr-drawer-head", children: [
|
|
94
|
+
/* @__PURE__ */ jsx("h2", { className: "gr-drawer-title", children: "Choose an image" }),
|
|
96
95
|
/* @__PURE__ */ jsx(
|
|
97
96
|
"button",
|
|
98
97
|
{
|
|
99
|
-
|
|
98
|
+
className: "gr-icon-btn gr-text-muted",
|
|
100
99
|
onClick: onClose,
|
|
101
100
|
"aria-label": "Close",
|
|
102
101
|
children: /* @__PURE__ */ jsx(CloseIcon, {})
|
|
103
102
|
}
|
|
104
103
|
)
|
|
105
104
|
] }),
|
|
106
|
-
/* @__PURE__ */ jsxs("div", {
|
|
105
|
+
/* @__PURE__ */ jsxs("div", { className: "gr-drawer-body", children: [
|
|
107
106
|
/* @__PURE__ */ jsxs(
|
|
108
107
|
"div",
|
|
109
108
|
{
|
|
110
109
|
role: "button",
|
|
111
110
|
tabIndex: 0,
|
|
112
|
-
|
|
111
|
+
className: dragging ? "gr-dropzone gr-dropzone-active" : "gr-dropzone",
|
|
113
112
|
onClick: () => !uploading && inputRef.current?.click(),
|
|
114
113
|
onKeyDown: (e) => {
|
|
115
114
|
if (e.key === "Enter" || e.key === " ") inputRef.current?.click();
|
|
@@ -125,7 +124,7 @@ function MediaPicker({
|
|
|
125
124
|
onFiles(e.dataTransfer.files);
|
|
126
125
|
},
|
|
127
126
|
children: [
|
|
128
|
-
uploading ? /* @__PURE__ */ jsxs("span", {
|
|
127
|
+
uploading ? /* @__PURE__ */ jsxs("span", { className: "gr-inline-flex gr-gap-8 gr-items-center", children: [
|
|
129
128
|
/* @__PURE__ */ jsx(SpinnerIcon, {}),
|
|
130
129
|
" Uploading\u2026"
|
|
131
130
|
] }) : "Drop an image here, or click to upload",
|
|
@@ -145,18 +144,15 @@ function MediaPicker({
|
|
|
145
144
|
]
|
|
146
145
|
}
|
|
147
146
|
),
|
|
148
|
-
error && /* @__PURE__ */ jsx("div", {
|
|
149
|
-
assets === null ? /* @__PURE__ */ jsx("p", {
|
|
147
|
+
error && /* @__PURE__ */ jsx("div", { className: "gr-error gr-mb-12", children: error }),
|
|
148
|
+
assets === null ? /* @__PURE__ */ jsx("p", { className: "gr-fs-13 gr-text-muted", children: "Loading\u2026" }) : assets.length === 0 ? /* @__PURE__ */ jsx("p", { className: "gr-fs-13 gr-text-muted", children: "No images uploaded yet." }) : /* @__PURE__ */ jsx("div", { className: "gr-picker-grid", children: assets.map((asset) => /* @__PURE__ */ jsx(
|
|
150
149
|
"button",
|
|
151
150
|
{
|
|
152
151
|
type: "button",
|
|
153
152
|
title: asset.name,
|
|
154
|
-
|
|
155
|
-
...S.pickerTile,
|
|
156
|
-
...asset.url === current ? S.pickerTileActive : {}
|
|
157
|
-
},
|
|
153
|
+
className: asset.url === current ? "gr-picker-tile gr-picker-tile-active" : "gr-picker-tile",
|
|
158
154
|
onClick: async () => choose(asset.url, await measure(asset.url)),
|
|
159
|
-
children: /* @__PURE__ */ jsx("img", { src: asset.url, alt: "", loading: "lazy",
|
|
155
|
+
children: /* @__PURE__ */ jsx("img", { src: asset.url, alt: "", loading: "lazy", className: "gr-picker-thumb" })
|
|
160
156
|
},
|
|
161
157
|
asset.id
|
|
162
158
|
)) })
|