@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 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"]}
@@ -1107,18 +1107,93 @@ function makeHandlers(ctx) {
1107
1107
  await signOut(await ctx.identityClient());
1108
1108
  } catch {
1109
1109
  }
1110
- return Response.redirect(new URL("/admin/sign-in", request.url), 303);
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