@veluai/velu 0.2.32 → 0.2.33
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/package.json
CHANGED
package/src/runtime/App.jsx
CHANGED
|
@@ -71,6 +71,13 @@ const IS_DEV_PREVIEW = import.meta.env.DEV;
|
|
|
71
71
|
// root-relative and need this prefix to resolve under a subpath.
|
|
72
72
|
const BASE_PATH = (import.meta.env.BASE_URL || '/').replace(/\/$/, '');
|
|
73
73
|
|
|
74
|
+
function docsAuthActionUrl(action) {
|
|
75
|
+
const url = new URL(window.location.href);
|
|
76
|
+
url.searchParams.set('__velu_auth', action);
|
|
77
|
+
url.hash = '';
|
|
78
|
+
return url.toString();
|
|
79
|
+
}
|
|
80
|
+
|
|
74
81
|
// Flatten a sidebar section's items (which may nest into sub-groups) into the
|
|
75
82
|
// flat endpoint list <ApiSidebar> wants. API tabs are flat in practice; this
|
|
76
83
|
// only guards the rare case of a normal nested group living beside generated
|
|
@@ -782,7 +789,10 @@ function DocsPage() {
|
|
|
782
789
|
// (dev preview, or a site that hasn't enabled the assistant).
|
|
783
790
|
const assistant = React.useMemo(() => {
|
|
784
791
|
if (IS_DEV_PREVIEW) return null;
|
|
785
|
-
const
|
|
792
|
+
const configuredBase = site.assistant?.apiBase;
|
|
793
|
+
const apiBase = configuredBase === '__VELU_SAME_ORIGIN__'
|
|
794
|
+
? `${window.location.origin}${BASE_PATH}`
|
|
795
|
+
: configuredBase;
|
|
786
796
|
if (!apiBase) return null;
|
|
787
797
|
return createDocsAssistant({ apiBase, host: site.assistant?.host });
|
|
788
798
|
}, []);
|
|
@@ -806,7 +816,10 @@ function DocsPage() {
|
|
|
806
816
|
// the widget stays purely visual.
|
|
807
817
|
const pageFeedback = React.useMemo(() => {
|
|
808
818
|
if (IS_DEV_PREVIEW) return null;
|
|
809
|
-
const
|
|
819
|
+
const configuredBase = site.assistant?.apiBase;
|
|
820
|
+
const apiBase = configuredBase === '__VELU_SAME_ORIGIN__'
|
|
821
|
+
? `${window.location.origin}${BASE_PATH}`
|
|
822
|
+
: configuredBase;
|
|
810
823
|
if (!apiBase) return null;
|
|
811
824
|
return createPageFeedback({ apiBase, host: site.assistant?.host });
|
|
812
825
|
}, []);
|
|
@@ -1095,6 +1108,27 @@ function DocsPage() {
|
|
|
1095
1108
|
// Header top-right actions, built from velu.json's `navbar`: secondary
|
|
1096
1109
|
// `links` (plain text links) then the `primary` CTA (a filled button, or an
|
|
1097
1110
|
// outlined GitHub button). Empty when unconfigured — no placeholder buttons.
|
|
1111
|
+
const [privateAccess, setPrivateAccess] = React.useState(false);
|
|
1112
|
+
React.useEffect(() => {
|
|
1113
|
+
if (IS_DEV_PREVIEW) return undefined;
|
|
1114
|
+
let alive = true;
|
|
1115
|
+
fetch(docsAuthActionUrl('session'), { credentials: 'same-origin' })
|
|
1116
|
+
.then((response) => response.ok ? response.json() : null)
|
|
1117
|
+
.then((value) => { if (alive) setPrivateAccess(value?.private && value?.authenticated); })
|
|
1118
|
+
.catch(() => {});
|
|
1119
|
+
return () => { alive = false; };
|
|
1120
|
+
}, []);
|
|
1121
|
+
|
|
1122
|
+
const logoutPrivateAccess = React.useCallback(async () => {
|
|
1123
|
+
const response = await fetch(docsAuthActionUrl('logout'), {
|
|
1124
|
+
method: 'POST',
|
|
1125
|
+
credentials: 'same-origin',
|
|
1126
|
+
headers: { 'content-type': 'application/json' },
|
|
1127
|
+
body: '{}',
|
|
1128
|
+
});
|
|
1129
|
+
if (response.ok) window.location.reload();
|
|
1130
|
+
}, []);
|
|
1131
|
+
|
|
1098
1132
|
const navActions = [];
|
|
1099
1133
|
for (const l of site.navbar?.links ?? []) {
|
|
1100
1134
|
navActions.push({
|
|
@@ -1115,6 +1149,13 @@ function DocsPage() {
|
|
|
1115
1149
|
external: p.external,
|
|
1116
1150
|
});
|
|
1117
1151
|
}
|
|
1152
|
+
if (privateAccess) {
|
|
1153
|
+
navActions.push({
|
|
1154
|
+
label: 'Log out',
|
|
1155
|
+
onClick: logoutPrivateAccess,
|
|
1156
|
+
kind: 'outlined',
|
|
1157
|
+
});
|
|
1158
|
+
}
|
|
1118
1159
|
|
|
1119
1160
|
const footerRef = React.useRef(null);
|
|
1120
1161
|
const [footerOverlap, setFooterOverlap] = React.useState(0);
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { PageHeader, ThemeToggle } from 'velu-ui';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Static private-reader shell. This is rendered by the same Vite SSR bundle as
|
|
6
|
+
* normal documentation pages, so its header, logo, and theme control stay in
|
|
7
|
+
* lockstep with velu-ui. Authentication behavior is attached by the generated
|
|
8
|
+
* page's nonce-bearing script because the protected client bundle is not
|
|
9
|
+
* available before login.
|
|
10
|
+
*/
|
|
11
|
+
export default function PrivateLoginPage({ site }) {
|
|
12
|
+
const logo = site.logo
|
|
13
|
+
? {
|
|
14
|
+
light: '?__velu_auth=logo-light',
|
|
15
|
+
dark: '?__velu_auth=logo-dark',
|
|
16
|
+
mono: site.logo.mono,
|
|
17
|
+
}
|
|
18
|
+
: undefined;
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<div className="velu-private-login">
|
|
22
|
+
<PageHeader
|
|
23
|
+
brand={{ label: site.name, logo }}
|
|
24
|
+
trailing={<ThemeToggle id="theme-toggle" />}
|
|
25
|
+
/>
|
|
26
|
+
<main className="velu-private-login__main">
|
|
27
|
+
<form className="velu-private-login__form" id="form">
|
|
28
|
+
<h1>Private documentation</h1>
|
|
29
|
+
<p>Enter your password to access {site.name}.</p>
|
|
30
|
+
<label htmlFor="password">Password</label>
|
|
31
|
+
<input
|
|
32
|
+
id="password"
|
|
33
|
+
type="password"
|
|
34
|
+
autoComplete="current-password"
|
|
35
|
+
maxLength={256}
|
|
36
|
+
required
|
|
37
|
+
autoFocus
|
|
38
|
+
/>
|
|
39
|
+
<div className="velu-private-login__error" id="error" role="alert" />
|
|
40
|
+
<button className="velu-private-login__continue" type="submit">
|
|
41
|
+
Continue
|
|
42
|
+
</button>
|
|
43
|
+
</form>
|
|
44
|
+
</main>
|
|
45
|
+
</div>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
@@ -5,7 +5,9 @@ import { StaticRouter } from 'react-router-dom/server';
|
|
|
5
5
|
// stylesheet in Vite's SSR module graph so dev-server.js can collect and
|
|
6
6
|
// inline it into <head> (flicker-free SSR).
|
|
7
7
|
import 'velu-ui/styles.css';
|
|
8
|
+
import { site } from 'virtual:velu-site';
|
|
8
9
|
import App from './App.jsx';
|
|
10
|
+
import PrivateLoginPage from './PrivateLoginPage.jsx';
|
|
9
11
|
|
|
10
12
|
// Subpath hosting: mirror the client's basename (from Vite's BASE_URL). The
|
|
11
13
|
// prerender is driven by root-relative route paths ("/foo"), so the location
|
|
@@ -22,3 +24,7 @@ export async function render(url) {
|
|
|
22
24
|
</StaticRouter>
|
|
23
25
|
);
|
|
24
26
|
}
|
|
27
|
+
|
|
28
|
+
export function renderPrivateLoginPage() {
|
|
29
|
+
return renderToString(<PrivateLoginPage site={site} />);
|
|
30
|
+
}
|