@protobase/ui 0.15.0 → 0.15.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/app/app.js CHANGED
@@ -191,7 +191,7 @@ const Routes = ({ workspace, sidebarMode, assistant, runtime }) => {
191
191
  others: organizations.organizations.filter((organization) => organization.id !== current?.organization?.id),
192
192
  onSwitch: (id) => void session.organizations.switchTo(id).then(() => reopenIn(basePath))
193
193
  },
194
- theme: user && {
194
+ theme: {
195
195
  preference: theme.preference,
196
196
  onChange: theme.choose
197
197
  },
@@ -1,12 +1,13 @@
1
1
  import { cn } from "../lib/cn.js";
2
2
  import { Logo } from "./logo.js";
3
3
  import { navGroups } from "./nav.js";
4
+ import { ThemeSwitch } from "./theme-switch.js";
4
5
  import { ProfileMenu } from "./profile-menu.js";
5
6
  import { isTextMode, sidebarWidth } from "./sidebar-mode.js";
6
7
  import { SidebarItem } from "./sidebar-item.js";
7
8
  import { SidebarGroup } from "./sidebar-group.js";
8
9
  import { LogIn } from "lucide-react";
9
- import { jsx, jsxs } from "react/jsx-runtime";
10
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
10
11
  //#region src/app-shell/sidebar.tsx
11
12
  const Sidebar = ({ mode, activeItem, user, workspace, forceExpanded, profileMenuOpen, groups = navGroups, userMenu, onNavigate, onSignOut, onSignIn, organizations, roles, theme, sticky }) => {
12
13
  const expanding = mode === "icon-expand";
@@ -58,7 +59,10 @@ const Sidebar = ({ mode, activeItem, user, workspace, forceExpanded, profileMenu
58
59
  organizations,
59
60
  roles,
60
61
  theme
61
- }) : onSignIn && /* @__PURE__ */ jsxs("button", {
62
+ }) : /* @__PURE__ */ jsxs(Fragment, { children: [theme && inline && /* @__PURE__ */ jsx(ThemeSwitch, {
63
+ theme,
64
+ className: "flex min-h-11 w-full items-center gap-2.5 whitespace-nowrap p-1.5 text-[13px] text-muted-foreground md:min-h-0"
65
+ }), onSignIn && /* @__PURE__ */ jsxs("button", {
62
66
  type: "button",
63
67
  onClick: onSignIn,
64
68
  "aria-label": "Sign in",
@@ -67,7 +71,7 @@ const Sidebar = ({ mode, activeItem, user, workspace, forceExpanded, profileMenu
67
71
  className: "whitespace-nowrap",
68
72
  children: "Sign in"
69
73
  })]
70
- })
74
+ })] })
71
75
  })
72
76
  ]
73
77
  })
@@ -31,6 +31,10 @@ export type MetaData = {
31
31
  maps?: {
32
32
  style: string;
33
33
  };
34
+ /** The hosts besides the app's own whose images are drawn with the page's referrer (see `FileImage`). */
35
+ files?: {
36
+ trustedHosts: string[];
37
+ };
34
38
  /** The plugins the app loads for the user (see `PluginGate`). */
35
39
  plugins?: PluginRef[];
36
40
  /** The other APIs the app draws resources from for the user, and how far their `/meta` has loaded (see `withSources`). */
@@ -37,6 +37,7 @@ const useMeta = () => {
37
37
  ...result.meta.runtime && { runtime: result.meta.runtime },
38
38
  ...result.meta.errorReporting && { errorReporting: result.meta.errorReporting },
39
39
  ...result.meta.maps && { maps: result.meta.maps },
40
+ ...result.meta.files && { files: result.meta.files },
40
41
  ...result.meta.plugins && { plugins: result.meta.plugins },
41
42
  ...result.meta.sources && { sources: result.meta.sources.map((source) => ({
42
43
  source,
@@ -1,3 +1,5 @@
1
+ import { imageReferrerPolicy } from "./image-referrer.js";
2
+ import { useTrustedImageHosts } from "./use-trusted-image-hosts.js";
1
3
  import { useMemo, useState } from "react";
2
4
  import { jsx } from "react/jsx-runtime";
3
5
  import { thumbHashToDataURL } from "thumbhash";
@@ -12,9 +14,13 @@ const placeholderImage = (hash) => {
12
14
  }
13
15
  return thumbHashToDataURL(Uint8Array.from(binary, (char) => char.charCodeAt(0)));
14
16
  };
15
- /** An image of a file, its placeholder drawn blurred behind it until it has loaded. */
17
+ /**
18
+ * An image of a file, its placeholder drawn blurred behind it until it has loaded. One from another site than the app's
19
+ * own and its trusted hosts loads without a referrer.
20
+ */
16
21
  const FileImage = ({ image, className, loading }) => {
17
22
  const [loaded, setLoaded] = useState();
23
+ const trustedHosts = useTrustedImageHosts();
18
24
  const placeholder = useMemo(() => image.placeholder === void 0 ? void 0 : placeholderImage(image.placeholder), [image.placeholder]);
19
25
  const waiting = placeholder !== void 0 && loaded !== image.url;
20
26
  return /* @__PURE__ */ jsx("img", {
@@ -22,6 +28,7 @@ const FileImage = ({ image, className, loading }) => {
22
28
  alt: "",
23
29
  loading,
24
30
  decoding: "async",
31
+ referrerPolicy: imageReferrerPolicy(image.url, window.location.href, trustedHosts),
25
32
  onLoad: () => setLoaded(image.url),
26
33
  className,
27
34
  style: waiting ? {
@@ -0,0 +1,15 @@
1
+ //#region src/live/image-referrer.ts
2
+ /**
3
+ * The `referrerPolicy` an image at `url` is drawn with on the page at `page`: the browser's default for the page's own
4
+ * origin, a trusted host, or a local image (`data:`, `blob:`); `no-referrer` for any other, so another site never learns
5
+ * which page of the app showed its image.
6
+ */
7
+ const imageReferrerPolicy = (url, page, trustedHosts) => {
8
+ if (!URL.canParse(url, page)) return "no-referrer";
9
+ const target = new URL(url, page);
10
+ if (target.protocol !== "http:" && target.protocol !== "https:") return void 0;
11
+ if (target.origin === new URL(page).origin || trustedHosts.includes(target.hostname)) return void 0;
12
+ return "no-referrer";
13
+ };
14
+ //#endregion
15
+ export { imageReferrerPolicy };
@@ -0,0 +1,14 @@
1
+ import { MetaContext } from "../data/meta-context.js";
2
+ import { useContext, useMemo } from "react";
3
+ //#region src/live/use-trusted-image-hosts.ts
4
+ const hostOf = (url) => URL.canParse(url) ? [new URL(url).hostname] : [];
5
+ /**
6
+ * The hosts besides the page's own whose images are drawn with the page's referrer: those `/meta` names, where the app's
7
+ * providers link public files from, and those of the app's sources. None outside `MetaGate`.
8
+ */
9
+ const useTrustedImageHosts = () => {
10
+ const meta = useContext(MetaContext);
11
+ return useMemo(() => [...meta?.files?.trustedHosts ?? [], ...(meta?.sources ?? []).flatMap(({ source }) => hostOf(source.url))], [meta?.files, meta?.sources]);
12
+ };
13
+ //#endregion
14
+ export { useTrustedImageHosts };
package/dist/package.js CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "0.15.0";
2
+ var version = "0.15.2";
3
3
  var protobase = { "pluginApi": 1 };
4
4
  //#endregion
5
5
  export { protobase, version };