@realiizlabs/admin 0.14.1 → 0.15.1
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/LICENSE +21 -0
- package/dist/bar/index.cjs +111 -0
- package/dist/bar/index.cjs.map +1 -0
- package/dist/bar/index.d.cts +18 -0
- package/dist/bar/index.d.ts +18 -0
- package/dist/bar/index.js +106 -0
- package/dist/bar/index.js.map +1 -0
- package/dist/studio/index.cjs +80 -2
- package/dist/studio/index.cjs.map +1 -1
- package/dist/studio/index.d.cts +32 -1
- package/dist/studio/index.d.ts +32 -1
- package/dist/studio/index.js +78 -3
- package/dist/studio/index.js.map +1 -1
- package/dist/studio-ui/index.cjs +242 -53
- package/dist/studio-ui/index.cjs.map +1 -1
- package/dist/studio-ui/index.d.cts +35 -6
- package/dist/studio-ui/index.d.ts +35 -6
- package/dist/studio-ui/index.js +240 -55
- package/dist/studio-ui/index.js.map +1 -1
- package/dist/version-DMS0azUx.d.cts +3 -0
- package/dist/version-DMS0azUx.d.ts +3 -0
- package/package.json +12 -5
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Realiiz Studio License (source-available)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Realiiz Labs LLC. All rights reserved.
|
|
4
|
+
|
|
5
|
+
This package (@realiizlabs/admin, "Studio") is published so that websites built
|
|
6
|
+
from the Realiiz site-starter-pro template can install and update it without
|
|
7
|
+
credentials. It is not open source.
|
|
8
|
+
|
|
9
|
+
You may install, run and modify this package as part of a website that you or
|
|
10
|
+
your organization built from a licensed copy of site-starter-pro, or that
|
|
11
|
+
Realiiz built for you. You may keep it installed for as long as that website
|
|
12
|
+
exists, including after your relationship with Realiiz ends.
|
|
13
|
+
|
|
14
|
+
You may not redistribute this package, sell it, offer it as part of another
|
|
15
|
+
template, product or service, or remove this notice.
|
|
16
|
+
|
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND. In no event
|
|
18
|
+
shall Realiiz Labs LLC be liable for any claim, damages or other liability
|
|
19
|
+
arising from the use of the software.
|
|
20
|
+
|
|
21
|
+
Questions: hello@realiiz.com
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var react = require('react');
|
|
5
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
6
|
+
|
|
7
|
+
// src/bar/StudioBar.tsx
|
|
8
|
+
var STUDIO_MARKER_COOKIE = "rz-studio";
|
|
9
|
+
var HIDE_KEY = "rz-bar-hidden";
|
|
10
|
+
function hasStudioMarker(cookie = typeof document === "undefined" ? "" : document.cookie) {
|
|
11
|
+
return cookie.split(";").some((c) => c.trim().startsWith(`${STUDIO_MARKER_COOKIE}=1`));
|
|
12
|
+
}
|
|
13
|
+
function editHrefFor(pathname, routes, adminPath = "/admin") {
|
|
14
|
+
for (const [prefix, typeId] of Object.entries(routes)) {
|
|
15
|
+
if (!pathname.startsWith(prefix)) continue;
|
|
16
|
+
const rest = pathname.slice(prefix.length).replace(/\/+$/, "");
|
|
17
|
+
if (!rest || rest.includes("/")) continue;
|
|
18
|
+
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(rest)) continue;
|
|
19
|
+
return `${adminPath}/${typeId}/${rest}`;
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
function usePathname() {
|
|
24
|
+
const [path, setPath] = react.useState("");
|
|
25
|
+
react.useEffect(() => {
|
|
26
|
+
const read = () => setPath(window.location.pathname);
|
|
27
|
+
read();
|
|
28
|
+
window.addEventListener("popstate", read);
|
|
29
|
+
const h = window.history;
|
|
30
|
+
if (!h.__rzWrapped) {
|
|
31
|
+
h.__rzWrapped = true;
|
|
32
|
+
for (const m of ["pushState", "replaceState"]) {
|
|
33
|
+
const orig = h[m].bind(h);
|
|
34
|
+
h[m] = ((...args) => {
|
|
35
|
+
orig(...args);
|
|
36
|
+
window.dispatchEvent(new Event("rz:navigate"));
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
window.addEventListener("rz:navigate", read);
|
|
41
|
+
return () => {
|
|
42
|
+
window.removeEventListener("popstate", read);
|
|
43
|
+
window.removeEventListener("rz:navigate", read);
|
|
44
|
+
};
|
|
45
|
+
}, []);
|
|
46
|
+
return path;
|
|
47
|
+
}
|
|
48
|
+
function StudioBar({ routes = {}, adminPath = "/admin", label = "Studio" }) {
|
|
49
|
+
const path = usePathname();
|
|
50
|
+
const [show, setShow] = react.useState(false);
|
|
51
|
+
react.useEffect(() => {
|
|
52
|
+
if (!path) return;
|
|
53
|
+
if (path === adminPath || path.startsWith(`${adminPath}/`)) {
|
|
54
|
+
setShow(false);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
let hidden = false;
|
|
58
|
+
try {
|
|
59
|
+
hidden = sessionStorage.getItem(HIDE_KEY) === "1";
|
|
60
|
+
} catch {
|
|
61
|
+
}
|
|
62
|
+
setShow(!hidden && hasStudioMarker());
|
|
63
|
+
}, [path, adminPath]);
|
|
64
|
+
if (!show) return null;
|
|
65
|
+
const edit = editHrefFor(path, routes, adminPath);
|
|
66
|
+
const close = () => {
|
|
67
|
+
try {
|
|
68
|
+
sessionStorage.setItem(HIDE_KEY, "1");
|
|
69
|
+
} catch {
|
|
70
|
+
}
|
|
71
|
+
setShow(false);
|
|
72
|
+
};
|
|
73
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: S.wrap, role: "region", "aria-label": "Studio", "data-studio-bar": true, children: [
|
|
74
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { style: S.brand, children: [
|
|
75
|
+
/* @__PURE__ */ jsxRuntime.jsx("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.4", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 3l7 3v5c0 5-3.5 8.5-7 10-3.5-1.5-7-5-7-10V6z" }) }),
|
|
76
|
+
label
|
|
77
|
+
] }),
|
|
78
|
+
edit && /* @__PURE__ */ jsxRuntime.jsx("a", { href: edit, style: S.link, children: "Edit this page" }),
|
|
79
|
+
/* @__PURE__ */ jsxRuntime.jsx("a", { href: adminPath, style: S.link, children: "Dashboard" }),
|
|
80
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: close, style: S.close, "aria-label": "Hide the Studio bar for this tab", children: "\xD7" })
|
|
81
|
+
] });
|
|
82
|
+
}
|
|
83
|
+
var S = {
|
|
84
|
+
wrap: {
|
|
85
|
+
position: "fixed",
|
|
86
|
+
left: 16,
|
|
87
|
+
bottom: 16,
|
|
88
|
+
zIndex: 2147483e3,
|
|
89
|
+
display: "flex",
|
|
90
|
+
alignItems: "center",
|
|
91
|
+
gap: 2,
|
|
92
|
+
padding: "4px 6px 4px 10px",
|
|
93
|
+
borderRadius: 999,
|
|
94
|
+
background: "#0b1220",
|
|
95
|
+
color: "#e5e7eb",
|
|
96
|
+
boxShadow: "0 6px 24px rgba(0,0,0,.28)",
|
|
97
|
+
font: "500 13px/1 system-ui, -apple-system, Segoe UI, Roboto, sans-serif",
|
|
98
|
+
letterSpacing: ".01em",
|
|
99
|
+
WebkitFontSmoothing: "antialiased"
|
|
100
|
+
},
|
|
101
|
+
brand: { display: "inline-flex", alignItems: "center", gap: 6, paddingRight: 8, marginRight: 4, borderRight: "1px solid rgba(255,255,255,.18)", color: "#a5b4fc", fontWeight: 600 },
|
|
102
|
+
link: { color: "#e5e7eb", textDecoration: "none", padding: "6px 8px", borderRadius: 999 },
|
|
103
|
+
close: { marginLeft: 2, width: 26, height: 26, border: 0, borderRadius: 999, background: "transparent", color: "#9ca3af", fontSize: 16, lineHeight: 1, cursor: "pointer" }
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
exports.STUDIO_MARKER_COOKIE = STUDIO_MARKER_COOKIE;
|
|
107
|
+
exports.StudioBar = StudioBar;
|
|
108
|
+
exports.editHrefFor = editHrefFor;
|
|
109
|
+
exports.hasStudioMarker = hasStudioMarker;
|
|
110
|
+
//# sourceMappingURL=index.cjs.map
|
|
111
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/bar/StudioBar.tsx"],"names":["useState","useEffect","jsxs","jsx"],"mappings":"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","file":"index.cjs","sourcesContent":["\"use client\";\n\nimport { useEffect, useState, type CSSProperties } from \"react\";\n\nexport const STUDIO_MARKER_COOKIE = \"rz-studio\";\nconst HIDE_KEY = \"rz-bar-hidden\";\n\nexport interface StudioBarProps {\n /** Public URL prefix → content type id, e.g. { \"/blog/\": \"posts\", \"/events/\": \"events\" }. */\n routes?: Record<string, string>;\n /** Where Studio lives. Default \"/admin\". */\n adminPath?: string;\n /** Shown on the pill. Default \"Studio\". */\n label?: string;\n}\n\n/** True when Studio's marker cookie is present (someone signed in to Studio in this browser). */\nexport function hasStudioMarker(cookie: string = typeof document === \"undefined\" ? \"\" : document.cookie): boolean {\n return cookie.split(\";\").some((c) => c.trim().startsWith(`${STUDIO_MARKER_COOKIE}=1`));\n}\n\n/** The Studio edit page for a public path, or null when the path isn't an item Studio manages. */\nexport function editHrefFor(pathname: string, routes: Record<string, string>, adminPath = \"/admin\"): string | null {\n for (const [prefix, typeId] of Object.entries(routes)) {\n if (!pathname.startsWith(prefix)) continue;\n const rest = pathname.slice(prefix.length).replace(/\\/+$/, \"\");\n if (!rest || rest.includes(\"/\")) continue; // the index page, or something nested — not an item\n if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(rest)) continue;\n return `${adminPath}/${typeId}/${rest}`;\n }\n return null;\n}\n\n/** The current pathname, kept in step with client-side navigation (Next's router uses pushState). */\nfunction usePathname(): string {\n const [path, setPath] = useState(\"\");\n useEffect(() => {\n const read = () => setPath(window.location.pathname);\n read();\n window.addEventListener(\"popstate\", read);\n // pushState/replaceState fire no event; wrap them once per page so every router notifies us.\n const h = window.history as History & { __rzWrapped?: boolean };\n if (!h.__rzWrapped) {\n h.__rzWrapped = true;\n for (const m of [\"pushState\", \"replaceState\"] as const) {\n const orig = h[m].bind(h);\n h[m] = ((...args: Parameters<History[\"pushState\"]>) => { orig(...args); window.dispatchEvent(new Event(\"rz:navigate\")); }) as History[\"pushState\"];\n }\n }\n window.addEventListener(\"rz:navigate\", read);\n return () => { window.removeEventListener(\"popstate\", read); window.removeEventListener(\"rz:navigate\", read); };\n }, []);\n return path;\n}\n\nexport function StudioBar({ routes = {}, adminPath = \"/admin\", label = \"Studio\" }: StudioBarProps) {\n const path = usePathname();\n const [show, setShow] = useState(false);\n\n useEffect(() => {\n if (!path) return;\n if (path === adminPath || path.startsWith(`${adminPath}/`)) { setShow(false); return; }\n let hidden = false;\n try { hidden = sessionStorage.getItem(HIDE_KEY) === \"1\"; } catch { /* private mode */ }\n setShow(!hidden && hasStudioMarker());\n }, [path, adminPath]);\n\n if (!show) return null;\n const edit = editHrefFor(path, routes, adminPath);\n const close = () => { try { sessionStorage.setItem(HIDE_KEY, \"1\"); } catch { /* private mode */ } setShow(false); };\n\n return (\n <div style={S.wrap} role=\"region\" aria-label=\"Studio\" data-studio-bar>\n <span style={S.brand}>\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.4\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\"><path d=\"M12 3l7 3v5c0 5-3.5 8.5-7 10-3.5-1.5-7-5-7-10V6z\" /></svg>\n {label}\n </span>\n {edit && <a href={edit} style={S.link}>Edit this page</a>}\n <a href={adminPath} style={S.link}>Dashboard</a>\n <button type=\"button\" onClick={close} style={S.close} aria-label=\"Hide the Studio bar for this tab\">×</button>\n </div>\n );\n}\n\n/* Self-contained styles: no dependency on the site's CSS, fonts or theme. */\nconst S: Record<string, CSSProperties> = {\n wrap: {\n position: \"fixed\", left: 16, bottom: 16, zIndex: 2147483000,\n display: \"flex\", alignItems: \"center\", gap: 2,\n padding: \"4px 6px 4px 10px\", borderRadius: 999,\n background: \"#0b1220\", color: \"#e5e7eb\", boxShadow: \"0 6px 24px rgba(0,0,0,.28)\",\n font: \"500 13px/1 system-ui, -apple-system, Segoe UI, Roboto, sans-serif\", letterSpacing: \".01em\",\n WebkitFontSmoothing: \"antialiased\",\n },\n brand: { display: \"inline-flex\", alignItems: \"center\", gap: 6, paddingRight: 8, marginRight: 4, borderRight: \"1px solid rgba(255,255,255,.18)\", color: \"#a5b4fc\", fontWeight: 600 },\n link: { color: \"#e5e7eb\", textDecoration: \"none\", padding: \"6px 8px\", borderRadius: 999 },\n close: { marginLeft: 2, width: 26, height: 26, border: 0, borderRadius: 999, background: \"transparent\", color: \"#9ca3af\", fontSize: 16, lineHeight: 1, cursor: \"pointer\" },\n};\n"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
|
|
3
|
+
declare const STUDIO_MARKER_COOKIE = "rz-studio";
|
|
4
|
+
interface StudioBarProps {
|
|
5
|
+
/** Public URL prefix → content type id, e.g. { "/blog/": "posts", "/events/": "events" }. */
|
|
6
|
+
routes?: Record<string, string>;
|
|
7
|
+
/** Where Studio lives. Default "/admin". */
|
|
8
|
+
adminPath?: string;
|
|
9
|
+
/** Shown on the pill. Default "Studio". */
|
|
10
|
+
label?: string;
|
|
11
|
+
}
|
|
12
|
+
/** True when Studio's marker cookie is present (someone signed in to Studio in this browser). */
|
|
13
|
+
declare function hasStudioMarker(cookie?: string): boolean;
|
|
14
|
+
/** The Studio edit page for a public path, or null when the path isn't an item Studio manages. */
|
|
15
|
+
declare function editHrefFor(pathname: string, routes: Record<string, string>, adminPath?: string): string | null;
|
|
16
|
+
declare function StudioBar({ routes, adminPath, label }: StudioBarProps): react.JSX.Element | null;
|
|
17
|
+
|
|
18
|
+
export { STUDIO_MARKER_COOKIE, StudioBar, type StudioBarProps, editHrefFor, hasStudioMarker };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
|
|
3
|
+
declare const STUDIO_MARKER_COOKIE = "rz-studio";
|
|
4
|
+
interface StudioBarProps {
|
|
5
|
+
/** Public URL prefix → content type id, e.g. { "/blog/": "posts", "/events/": "events" }. */
|
|
6
|
+
routes?: Record<string, string>;
|
|
7
|
+
/** Where Studio lives. Default "/admin". */
|
|
8
|
+
adminPath?: string;
|
|
9
|
+
/** Shown on the pill. Default "Studio". */
|
|
10
|
+
label?: string;
|
|
11
|
+
}
|
|
12
|
+
/** True when Studio's marker cookie is present (someone signed in to Studio in this browser). */
|
|
13
|
+
declare function hasStudioMarker(cookie?: string): boolean;
|
|
14
|
+
/** The Studio edit page for a public path, or null when the path isn't an item Studio manages. */
|
|
15
|
+
declare function editHrefFor(pathname: string, routes: Record<string, string>, adminPath?: string): string | null;
|
|
16
|
+
declare function StudioBar({ routes, adminPath, label }: StudioBarProps): react.JSX.Element | null;
|
|
17
|
+
|
|
18
|
+
export { STUDIO_MARKER_COOKIE, StudioBar, type StudioBarProps, editHrefFor, hasStudioMarker };
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useState, useEffect } from 'react';
|
|
3
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
4
|
+
|
|
5
|
+
// src/bar/StudioBar.tsx
|
|
6
|
+
var STUDIO_MARKER_COOKIE = "rz-studio";
|
|
7
|
+
var HIDE_KEY = "rz-bar-hidden";
|
|
8
|
+
function hasStudioMarker(cookie = typeof document === "undefined" ? "" : document.cookie) {
|
|
9
|
+
return cookie.split(";").some((c) => c.trim().startsWith(`${STUDIO_MARKER_COOKIE}=1`));
|
|
10
|
+
}
|
|
11
|
+
function editHrefFor(pathname, routes, adminPath = "/admin") {
|
|
12
|
+
for (const [prefix, typeId] of Object.entries(routes)) {
|
|
13
|
+
if (!pathname.startsWith(prefix)) continue;
|
|
14
|
+
const rest = pathname.slice(prefix.length).replace(/\/+$/, "");
|
|
15
|
+
if (!rest || rest.includes("/")) continue;
|
|
16
|
+
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(rest)) continue;
|
|
17
|
+
return `${adminPath}/${typeId}/${rest}`;
|
|
18
|
+
}
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
function usePathname() {
|
|
22
|
+
const [path, setPath] = useState("");
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
const read = () => setPath(window.location.pathname);
|
|
25
|
+
read();
|
|
26
|
+
window.addEventListener("popstate", read);
|
|
27
|
+
const h = window.history;
|
|
28
|
+
if (!h.__rzWrapped) {
|
|
29
|
+
h.__rzWrapped = true;
|
|
30
|
+
for (const m of ["pushState", "replaceState"]) {
|
|
31
|
+
const orig = h[m].bind(h);
|
|
32
|
+
h[m] = ((...args) => {
|
|
33
|
+
orig(...args);
|
|
34
|
+
window.dispatchEvent(new Event("rz:navigate"));
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
window.addEventListener("rz:navigate", read);
|
|
39
|
+
return () => {
|
|
40
|
+
window.removeEventListener("popstate", read);
|
|
41
|
+
window.removeEventListener("rz:navigate", read);
|
|
42
|
+
};
|
|
43
|
+
}, []);
|
|
44
|
+
return path;
|
|
45
|
+
}
|
|
46
|
+
function StudioBar({ routes = {}, adminPath = "/admin", label = "Studio" }) {
|
|
47
|
+
const path = usePathname();
|
|
48
|
+
const [show, setShow] = useState(false);
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
if (!path) return;
|
|
51
|
+
if (path === adminPath || path.startsWith(`${adminPath}/`)) {
|
|
52
|
+
setShow(false);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
let hidden = false;
|
|
56
|
+
try {
|
|
57
|
+
hidden = sessionStorage.getItem(HIDE_KEY) === "1";
|
|
58
|
+
} catch {
|
|
59
|
+
}
|
|
60
|
+
setShow(!hidden && hasStudioMarker());
|
|
61
|
+
}, [path, adminPath]);
|
|
62
|
+
if (!show) return null;
|
|
63
|
+
const edit = editHrefFor(path, routes, adminPath);
|
|
64
|
+
const close = () => {
|
|
65
|
+
try {
|
|
66
|
+
sessionStorage.setItem(HIDE_KEY, "1");
|
|
67
|
+
} catch {
|
|
68
|
+
}
|
|
69
|
+
setShow(false);
|
|
70
|
+
};
|
|
71
|
+
return /* @__PURE__ */ jsxs("div", { style: S.wrap, role: "region", "aria-label": "Studio", "data-studio-bar": true, children: [
|
|
72
|
+
/* @__PURE__ */ jsxs("span", { style: S.brand, children: [
|
|
73
|
+
/* @__PURE__ */ jsx("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.4", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M12 3l7 3v5c0 5-3.5 8.5-7 10-3.5-1.5-7-5-7-10V6z" }) }),
|
|
74
|
+
label
|
|
75
|
+
] }),
|
|
76
|
+
edit && /* @__PURE__ */ jsx("a", { href: edit, style: S.link, children: "Edit this page" }),
|
|
77
|
+
/* @__PURE__ */ jsx("a", { href: adminPath, style: S.link, children: "Dashboard" }),
|
|
78
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: close, style: S.close, "aria-label": "Hide the Studio bar for this tab", children: "\xD7" })
|
|
79
|
+
] });
|
|
80
|
+
}
|
|
81
|
+
var S = {
|
|
82
|
+
wrap: {
|
|
83
|
+
position: "fixed",
|
|
84
|
+
left: 16,
|
|
85
|
+
bottom: 16,
|
|
86
|
+
zIndex: 2147483e3,
|
|
87
|
+
display: "flex",
|
|
88
|
+
alignItems: "center",
|
|
89
|
+
gap: 2,
|
|
90
|
+
padding: "4px 6px 4px 10px",
|
|
91
|
+
borderRadius: 999,
|
|
92
|
+
background: "#0b1220",
|
|
93
|
+
color: "#e5e7eb",
|
|
94
|
+
boxShadow: "0 6px 24px rgba(0,0,0,.28)",
|
|
95
|
+
font: "500 13px/1 system-ui, -apple-system, Segoe UI, Roboto, sans-serif",
|
|
96
|
+
letterSpacing: ".01em",
|
|
97
|
+
WebkitFontSmoothing: "antialiased"
|
|
98
|
+
},
|
|
99
|
+
brand: { display: "inline-flex", alignItems: "center", gap: 6, paddingRight: 8, marginRight: 4, borderRight: "1px solid rgba(255,255,255,.18)", color: "#a5b4fc", fontWeight: 600 },
|
|
100
|
+
link: { color: "#e5e7eb", textDecoration: "none", padding: "6px 8px", borderRadius: 999 },
|
|
101
|
+
close: { marginLeft: 2, width: 26, height: 26, border: 0, borderRadius: 999, background: "transparent", color: "#9ca3af", fontSize: 16, lineHeight: 1, cursor: "pointer" }
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export { STUDIO_MARKER_COOKIE, StudioBar, editHrefFor, hasStudioMarker };
|
|
105
|
+
//# sourceMappingURL=index.js.map
|
|
106
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/bar/StudioBar.tsx"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["\"use client\";\n\nimport { useEffect, useState, type CSSProperties } from \"react\";\n\nexport const STUDIO_MARKER_COOKIE = \"rz-studio\";\nconst HIDE_KEY = \"rz-bar-hidden\";\n\nexport interface StudioBarProps {\n /** Public URL prefix → content type id, e.g. { \"/blog/\": \"posts\", \"/events/\": \"events\" }. */\n routes?: Record<string, string>;\n /** Where Studio lives. Default \"/admin\". */\n adminPath?: string;\n /** Shown on the pill. Default \"Studio\". */\n label?: string;\n}\n\n/** True when Studio's marker cookie is present (someone signed in to Studio in this browser). */\nexport function hasStudioMarker(cookie: string = typeof document === \"undefined\" ? \"\" : document.cookie): boolean {\n return cookie.split(\";\").some((c) => c.trim().startsWith(`${STUDIO_MARKER_COOKIE}=1`));\n}\n\n/** The Studio edit page for a public path, or null when the path isn't an item Studio manages. */\nexport function editHrefFor(pathname: string, routes: Record<string, string>, adminPath = \"/admin\"): string | null {\n for (const [prefix, typeId] of Object.entries(routes)) {\n if (!pathname.startsWith(prefix)) continue;\n const rest = pathname.slice(prefix.length).replace(/\\/+$/, \"\");\n if (!rest || rest.includes(\"/\")) continue; // the index page, or something nested — not an item\n if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(rest)) continue;\n return `${adminPath}/${typeId}/${rest}`;\n }\n return null;\n}\n\n/** The current pathname, kept in step with client-side navigation (Next's router uses pushState). */\nfunction usePathname(): string {\n const [path, setPath] = useState(\"\");\n useEffect(() => {\n const read = () => setPath(window.location.pathname);\n read();\n window.addEventListener(\"popstate\", read);\n // pushState/replaceState fire no event; wrap them once per page so every router notifies us.\n const h = window.history as History & { __rzWrapped?: boolean };\n if (!h.__rzWrapped) {\n h.__rzWrapped = true;\n for (const m of [\"pushState\", \"replaceState\"] as const) {\n const orig = h[m].bind(h);\n h[m] = ((...args: Parameters<History[\"pushState\"]>) => { orig(...args); window.dispatchEvent(new Event(\"rz:navigate\")); }) as History[\"pushState\"];\n }\n }\n window.addEventListener(\"rz:navigate\", read);\n return () => { window.removeEventListener(\"popstate\", read); window.removeEventListener(\"rz:navigate\", read); };\n }, []);\n return path;\n}\n\nexport function StudioBar({ routes = {}, adminPath = \"/admin\", label = \"Studio\" }: StudioBarProps) {\n const path = usePathname();\n const [show, setShow] = useState(false);\n\n useEffect(() => {\n if (!path) return;\n if (path === adminPath || path.startsWith(`${adminPath}/`)) { setShow(false); return; }\n let hidden = false;\n try { hidden = sessionStorage.getItem(HIDE_KEY) === \"1\"; } catch { /* private mode */ }\n setShow(!hidden && hasStudioMarker());\n }, [path, adminPath]);\n\n if (!show) return null;\n const edit = editHrefFor(path, routes, adminPath);\n const close = () => { try { sessionStorage.setItem(HIDE_KEY, \"1\"); } catch { /* private mode */ } setShow(false); };\n\n return (\n <div style={S.wrap} role=\"region\" aria-label=\"Studio\" data-studio-bar>\n <span style={S.brand}>\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.4\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\"><path d=\"M12 3l7 3v5c0 5-3.5 8.5-7 10-3.5-1.5-7-5-7-10V6z\" /></svg>\n {label}\n </span>\n {edit && <a href={edit} style={S.link}>Edit this page</a>}\n <a href={adminPath} style={S.link}>Dashboard</a>\n <button type=\"button\" onClick={close} style={S.close} aria-label=\"Hide the Studio bar for this tab\">×</button>\n </div>\n );\n}\n\n/* Self-contained styles: no dependency on the site's CSS, fonts or theme. */\nconst S: Record<string, CSSProperties> = {\n wrap: {\n position: \"fixed\", left: 16, bottom: 16, zIndex: 2147483000,\n display: \"flex\", alignItems: \"center\", gap: 2,\n padding: \"4px 6px 4px 10px\", borderRadius: 999,\n background: \"#0b1220\", color: \"#e5e7eb\", boxShadow: \"0 6px 24px rgba(0,0,0,.28)\",\n font: \"500 13px/1 system-ui, -apple-system, Segoe UI, Roboto, sans-serif\", letterSpacing: \".01em\",\n WebkitFontSmoothing: \"antialiased\",\n },\n brand: { display: \"inline-flex\", alignItems: \"center\", gap: 6, paddingRight: 8, marginRight: 4, borderRight: \"1px solid rgba(255,255,255,.18)\", color: \"#a5b4fc\", fontWeight: 600 },\n link: { color: \"#e5e7eb\", textDecoration: \"none\", padding: \"6px 8px\", borderRadius: 999 },\n close: { marginLeft: 2, width: 26, height: 26, border: 0, borderRadius: 999, background: \"transparent\", color: \"#9ca3af\", fontSize: 16, lineHeight: 1, cursor: \"pointer\" },\n};\n"]}
|
package/dist/studio/index.cjs
CHANGED
|
@@ -1107,18 +1107,93 @@ function makeHandlers(ctx) {
|
|
|
1107
1107
|
await signOut(await ctx.identityClient());
|
|
1108
1108
|
} catch {
|
|
1109
1109
|
}
|
|
1110
|
-
|
|
1110
|
+
const res = Response.redirect(new URL("/admin/sign-in", request.url), 303);
|
|
1111
|
+
const headers = new Headers(res.headers);
|
|
1112
|
+
headers.append("Set-Cookie", "rz-studio=; Path=/; Max-Age=0; SameSite=Lax");
|
|
1113
|
+
return new Response(null, { status: 303, headers });
|
|
1111
1114
|
};
|
|
1112
1115
|
return { callback, signout };
|
|
1113
1116
|
}
|
|
1114
1117
|
|
|
1118
|
+
// src/version.ts
|
|
1119
|
+
var ADMIN_VERSION = "0.15.1" ;
|
|
1120
|
+
|
|
1121
|
+
// src/studio/update.ts
|
|
1122
|
+
var PACKAGE_NAME = "@realiizlabs/admin";
|
|
1123
|
+
var DEPENDABOT_PREFIX = "dependabot/npm_and_yarn/";
|
|
1124
|
+
var TITLE_RE = /@realiizlabs\/admin\b.*?\bto\s+v?(\d+\.\d+\.\d+)/i;
|
|
1125
|
+
var BRANCH_RE2 = /realiizlabs\/admin-(\d+\.\d+\.\d+)$/;
|
|
1126
|
+
function updateVersionOf(pr) {
|
|
1127
|
+
if (!pr.branch.startsWith(DEPENDABOT_PREFIX)) return null;
|
|
1128
|
+
const fromTitle = TITLE_RE.exec(pr.title)?.[1];
|
|
1129
|
+
if (fromTitle) return fromTitle;
|
|
1130
|
+
return BRANCH_RE2.exec(pr.branch)?.[1] ?? null;
|
|
1131
|
+
}
|
|
1132
|
+
async function fetchReleaseNotes(version, fetchImpl = fetch) {
|
|
1133
|
+
try {
|
|
1134
|
+
const res = await fetchImpl(`https://registry.npmjs.org/${PACKAGE_NAME}/${version}`, { headers: { accept: "application/json" } });
|
|
1135
|
+
if (!res.ok) return null;
|
|
1136
|
+
const json = await res.json();
|
|
1137
|
+
return typeof json.releaseNotes === "string" && json.releaseNotes.trim() ? json.releaseNotes.trim() : null;
|
|
1138
|
+
} catch {
|
|
1139
|
+
return null;
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
function stateOf(status) {
|
|
1143
|
+
if (status.state === "green") return { state: "ready", failingCheck: null, previewUrl: status.previewUrl ?? null };
|
|
1144
|
+
if (status.state === "red") return { state: "failed", failingCheck: status.failingCheck ?? null, previewUrl: null };
|
|
1145
|
+
return { state: "checking", failingCheck: null, previewUrl: null };
|
|
1146
|
+
}
|
|
1147
|
+
function makeUpdate(config, ctx, deps = {}) {
|
|
1148
|
+
const revalidate = config.revalidate ?? (() => {
|
|
1149
|
+
});
|
|
1150
|
+
const studioUpdate = async () => {
|
|
1151
|
+
try {
|
|
1152
|
+
const repo = config.env().repo;
|
|
1153
|
+
const prs = await listOpenPullRequests(repo, { headPrefix: DEPENDABOT_PREFIX });
|
|
1154
|
+
const candidates = prs.map((pr2) => ({ pr: pr2, version: updateVersionOf(pr2) })).filter((c) => c.version !== null);
|
|
1155
|
+
if (candidates.length === 0) return null;
|
|
1156
|
+
candidates.sort((a, b) => compareVersions(b.version, a.version));
|
|
1157
|
+
const { pr, version } = candidates[0];
|
|
1158
|
+
const [status, notes] = await Promise.all([getPullRequestStatus(repo, pr.number), fetchReleaseNotes(version, deps.fetch)]);
|
|
1159
|
+
return { number: pr.number, htmlUrl: pr.html_url, version, currentVersion: ADMIN_VERSION, notes, ...stateOf(status) };
|
|
1160
|
+
} catch {
|
|
1161
|
+
return null;
|
|
1162
|
+
}
|
|
1163
|
+
};
|
|
1164
|
+
const updateStudio = async (number) => {
|
|
1165
|
+
await ctx.requireAdmin({ minimumRole: "owner" });
|
|
1166
|
+
const update = await studioUpdate();
|
|
1167
|
+
if (!update || update.number !== number) return { state: "error", message: "That isn\u2019t a Studio update." };
|
|
1168
|
+
try {
|
|
1169
|
+
const result = await mergePullRequest(config.env().repo, number, { requireGreen: true });
|
|
1170
|
+
revalidate("/admin");
|
|
1171
|
+
return result.merged ? { state: "merged", sha: result.sha } : { state: "pending" };
|
|
1172
|
+
} catch (err) {
|
|
1173
|
+
if (err instanceof StaleBaseError) return { state: "stale" };
|
|
1174
|
+
if (err instanceof ChecksFailedError) return { state: "red", failingCheck: err.failingCheck };
|
|
1175
|
+
return { state: "error", message: err instanceof Error ? err.message : String(err) };
|
|
1176
|
+
}
|
|
1177
|
+
};
|
|
1178
|
+
return { studioUpdate, updateStudio };
|
|
1179
|
+
}
|
|
1180
|
+
function compareVersions(a, b) {
|
|
1181
|
+
const pa = a.split(".").map(Number), pb = b.split(".").map(Number);
|
|
1182
|
+
for (let i = 0; i < 3; i++) {
|
|
1183
|
+
if ((pa[i] ?? 0) !== (pb[i] ?? 0)) return (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
1184
|
+
}
|
|
1185
|
+
return 0;
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1115
1188
|
// src/studio/index.ts
|
|
1116
1189
|
function createStudio(config) {
|
|
1117
1190
|
const ctx = makeContext(config);
|
|
1118
1191
|
const registry = makeRegistry(config);
|
|
1119
1192
|
const pending = makePending(config, registry);
|
|
1120
1193
|
const readers = makeReaders(config, registry, pending);
|
|
1194
|
+
const update = makeUpdate(config, ctx);
|
|
1121
1195
|
const actions = {
|
|
1196
|
+
updateStudio: update.updateStudio,
|
|
1122
1197
|
...makePublish(config, registry, ctx, readers),
|
|
1123
1198
|
...makeMerge(config, ctx, readers),
|
|
1124
1199
|
...makePreview(ctx),
|
|
@@ -1132,19 +1207,22 @@ function createStudio(config) {
|
|
|
1132
1207
|
entries: registry.entries,
|
|
1133
1208
|
requireAdmin: ctx.requireAdmin,
|
|
1134
1209
|
identityClient: ctx.identityClient,
|
|
1135
|
-
readers: { ...readers, ...pending, locate: registry.locate, publicUrlFor: registry.publicUrlFor, publicDirFor: registry.publicDirFor },
|
|
1210
|
+
readers: { ...readers, ...pending, locate: registry.locate, publicUrlFor: registry.publicUrlFor, publicDirFor: registry.publicDirFor, studioUpdate: update.studioUpdate },
|
|
1136
1211
|
actions,
|
|
1137
1212
|
authHandlers: makeHandlers(ctx)
|
|
1138
1213
|
};
|
|
1139
1214
|
}
|
|
1140
1215
|
|
|
1216
|
+
exports.ADMIN_VERSION = ADMIN_VERSION;
|
|
1141
1217
|
exports.IMAGE_ERRORS = IMAGE_ERRORS;
|
|
1142
1218
|
exports.IMAGE_LIMITS = IMAGE_LIMITS;
|
|
1143
1219
|
exports.NotHere = NotHere;
|
|
1220
|
+
exports.compareVersions = compareVersions;
|
|
1144
1221
|
exports.createStudio = createStudio;
|
|
1145
1222
|
exports.decodedSize = decodedSize;
|
|
1146
1223
|
exports.pendingLabel = pendingLabel;
|
|
1147
1224
|
exports.pendingText = pendingText;
|
|
1225
|
+
exports.updateVersionOf = updateVersionOf;
|
|
1148
1226
|
exports.validateImages = validateImages;
|
|
1149
1227
|
//# sourceMappingURL=index.cjs.map
|
|
1150
1228
|
//# sourceMappingURL=index.cjs.map
|