zudoku 0.0.0-f9d5b02 → 0.0.0-fa903e7
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/entry.client.js +14 -0
- package/dist/app/entry.client.js.map +1 -1
- package/dist/cli/common/logger.js +9 -0
- package/dist/cli/common/logger.js.map +1 -1
- package/dist/config/validators/validate.d.ts +31 -11
- package/dist/config/validators/validate.js +7 -1
- package/dist/config/validators/validate.js.map +1 -1
- package/dist/lib/authentication/AuthenticationPlugin.d.ts +4 -2
- package/dist/lib/authentication/AuthenticationPlugin.js +3 -0
- package/dist/lib/authentication/AuthenticationPlugin.js.map +1 -1
- package/dist/lib/authentication/authentication.d.ts +1 -1
- package/dist/lib/authentication/hook.d.ts +5 -4
- package/dist/lib/authentication/hook.js +1 -3
- package/dist/lib/authentication/hook.js.map +1 -1
- package/dist/lib/authentication/providers/auth0.js +2 -2
- package/dist/lib/authentication/providers/auth0.js.map +1 -1
- package/dist/lib/authentication/providers/openid.d.ts +0 -1
- package/dist/lib/authentication/providers/openid.js +11 -26
- package/dist/lib/authentication/providers/openid.js.map +1 -1
- package/dist/lib/authentication/state.d.ts +25 -4
- package/dist/lib/authentication/state.js +28 -5
- package/dist/lib/authentication/state.js.map +1 -1
- package/dist/lib/components/Bootstrap.js +9 -5
- package/dist/lib/components/Bootstrap.js.map +1 -1
- package/dist/lib/components/Header.js +12 -4
- package/dist/lib/components/Header.js.map +1 -1
- package/dist/lib/components/Layout.js +12 -4
- package/dist/lib/components/Layout.js.map +1 -1
- package/dist/lib/components/MobileTopNavigation.js +5 -7
- package/dist/lib/components/MobileTopNavigation.js.map +1 -1
- package/dist/lib/components/SyntaxHighlight.js +2 -2
- package/dist/lib/components/SyntaxHighlight.js.map +1 -1
- package/dist/lib/components/ThemeSwitch.js +5 -3
- package/dist/lib/components/ThemeSwitch.js.map +1 -1
- package/dist/lib/components/TopNavigation.d.ts +2 -0
- package/dist/lib/components/TopNavigation.js +13 -7
- package/dist/lib/components/TopNavigation.js.map +1 -1
- package/dist/lib/components/index.d.ts +11 -1
- package/dist/lib/core/plugins.d.ts +6 -0
- package/dist/lib/core/plugins.js.map +1 -1
- package/dist/lib/oas/parser/upgrade/index.d.ts +2 -2
- package/dist/lib/oas/parser/upgrade/index.js +3 -20
- package/dist/lib/oas/parser/upgrade/index.js.map +1 -1
- package/dist/lib/plugins/api-keys/index.js +3 -0
- package/dist/lib/plugins/api-keys/index.js.map +1 -1
- package/dist/lib/plugins/markdown/MdxPage.d.ts +9 -1
- package/dist/lib/plugins/markdown/MdxPage.js +14 -1
- package/dist/lib/plugins/markdown/MdxPage.js.map +1 -1
- package/dist/lib/plugins/markdown/index.js +1 -1
- package/dist/lib/plugins/markdown/index.js.map +1 -1
- package/dist/lib/plugins/openapi/ColorizedParam.js +2 -2
- package/dist/lib/plugins/openapi/ColorizedParam.js.map +1 -1
- package/dist/lib/plugins/openapi/client/GraphQLClient.js +12 -0
- package/dist/lib/plugins/openapi/client/GraphQLClient.js.map +1 -1
- package/dist/lib/plugins/openapi/client/useCreateQuery.d.ts +1 -1
- package/dist/lib/plugins/openapi/client/useCreateQuery.js +4 -2
- package/dist/lib/plugins/openapi/client/useCreateQuery.js.map +1 -1
- package/dist/lib/plugins/openapi/interfaces.d.ts +1 -1
- package/dist/lib/plugins/openapi/post-processors/removeExtensions.d.ts +6 -0
- package/dist/lib/plugins/openapi/post-processors/removeExtensions.js +14 -0
- package/dist/lib/plugins/openapi/post-processors/removeExtensions.js.map +1 -0
- package/dist/lib/plugins/openapi/post-processors/removeExtensions.test.d.ts +1 -0
- package/dist/lib/plugins/openapi/post-processors/removeExtensions.test.js +125 -0
- package/dist/lib/plugins/openapi/post-processors/removeExtensions.test.js.map +1 -0
- package/dist/lib/plugins/openapi/post-processors/removePaths.d.ts +11 -0
- package/dist/lib/plugins/openapi/post-processors/removePaths.js +33 -0
- package/dist/lib/plugins/openapi/post-processors/removePaths.js.map +1 -0
- package/dist/lib/plugins/openapi/post-processors/removePaths.test.d.ts +1 -0
- package/dist/lib/plugins/openapi/post-processors/removePaths.test.js +104 -0
- package/dist/lib/plugins/openapi/post-processors/removePaths.test.js.map +1 -0
- package/dist/lib/plugins/openapi/post-processors/traverse.d.ts +1 -0
- package/dist/lib/plugins/openapi/post-processors/traverse.js +2 -0
- package/dist/lib/plugins/openapi/post-processors/traverse.js.map +1 -0
- package/dist/lib/plugins/openapi/schema/SchemaView.js.map +1 -1
- package/dist/lib/util/traverse.d.ts +2 -0
- package/dist/lib/util/traverse.js +18 -0
- package/dist/lib/util/traverse.js.map +1 -0
- package/dist/vite/config.js +12 -0
- package/dist/vite/config.js.map +1 -1
- package/dist/vite/config.test.js +3 -4
- package/dist/vite/config.test.js.map +1 -1
- package/dist/vite/output.js +20 -0
- package/dist/vite/output.js.map +1 -1
- package/dist/vite/plugin-api.js +23 -19
- package/dist/vite/plugin-api.js.map +1 -1
- package/dist/vite/plugin-component.js +14 -19
- package/dist/vite/plugin-component.js.map +1 -1
- package/dist/vite/plugin-docs.test.js +15 -23
- package/dist/vite/plugin-docs.test.js.map +1 -1
- package/dist/vite/plugin-mdx.js +10 -3
- package/dist/vite/plugin-mdx.js.map +1 -1
- package/dist/vite/remarkStaticGeneration.js +5 -5
- package/dist/vite/remarkStaticGeneration.js.map +1 -1
- package/dist/zuplo/with-zuplo.d.ts +3 -0
- package/dist/zuplo/with-zuplo.js +28 -0
- package/dist/zuplo/with-zuplo.js.map +1 -0
- package/lib/AuthenticationPlugin-D0Em0SwR.js +59 -0
- package/lib/AuthenticationPlugin-D0Em0SwR.js.map +1 -0
- package/lib/{Markdown-BorQdbxW.js → Markdown-ievDDhFT.js} +2 -2
- package/lib/{Markdown-BorQdbxW.js.map → Markdown-ievDDhFT.js.map} +1 -1
- package/lib/MdxPage-B2FpJ9KC.js +183 -0
- package/lib/MdxPage-B2FpJ9KC.js.map +1 -0
- package/lib/{OperationList-KshJrrLL.js → OperationList-BkNQEsNs.js} +460 -458
- package/lib/OperationList-BkNQEsNs.js.map +1 -0
- package/lib/{Select-DP74t8Yy.js → Select-O9ZM3ZgX.js} +2 -2
- package/lib/{Select-DP74t8Yy.js.map → Select-O9ZM3ZgX.js.map} +1 -1
- package/lib/{SlotletProvider-D2v6rJy1.js → SlotletProvider-DyomlzGx.js} +2 -2
- package/lib/{SlotletProvider-D2v6rJy1.js.map → SlotletProvider-DyomlzGx.js.map} +1 -1
- package/lib/{SyntaxHighlight-CBmwwKoM.js → SyntaxHighlight-DkLOsjHS.js} +2 -2
- package/lib/{SyntaxHighlight-CBmwwKoM.js.map → SyntaxHighlight-DkLOsjHS.js.map} +1 -1
- package/lib/assets/{worker-CPsGZsve.js → worker-BHClFO3A.js} +434 -438
- package/lib/assets/worker-BHClFO3A.js.map +1 -0
- package/lib/{createServer-DK-g7kbB.js → createServer-CpJlUPtn.js} +4457 -5247
- package/lib/createServer-CpJlUPtn.js.map +1 -0
- package/lib/{hook-Diu0rqp-.js → hook-hEqe7fPB.js} +12 -14
- package/lib/{hook-Diu0rqp-.js.map → hook-hEqe7fPB.js.map} +1 -1
- package/lib/{index-BcesIHH4.js → index-C7SaIME0.js} +54 -50
- package/lib/index-C7SaIME0.js.map +1 -0
- package/lib/object_hash-CvlLgU-M.js +785 -0
- package/lib/object_hash-CvlLgU-M.js.map +1 -0
- package/lib/post-processors/removeExtensions.js +11 -0
- package/lib/post-processors/removeExtensions.js.map +1 -0
- package/lib/post-processors/removePaths.js +28 -0
- package/lib/post-processors/removePaths.js.map +1 -0
- package/lib/post-processors/traverse.js +12 -0
- package/lib/post-processors/traverse.js.map +1 -0
- package/lib/state-tsXBLONe.js +203 -0
- package/lib/state-tsXBLONe.js.map +1 -0
- package/lib/zudoku.auth-auth0.js +9 -8
- package/lib/zudoku.auth-auth0.js.map +1 -1
- package/lib/zudoku.auth-clerk.js +2 -2
- package/lib/zudoku.auth-openid.js +119 -133
- package/lib/zudoku.auth-openid.js.map +1 -1
- package/lib/zudoku.components.js +596 -530
- package/lib/zudoku.components.js.map +1 -1
- package/lib/zudoku.openapi-worker.js +1 -1
- package/lib/zudoku.plugin-api-keys.js +40 -38
- package/lib/zudoku.plugin-api-keys.js.map +1 -1
- package/lib/zudoku.plugin-custom-pages.js +1 -1
- package/lib/zudoku.plugin-markdown.js +15 -14
- package/lib/zudoku.plugin-markdown.js.map +1 -1
- package/lib/zudoku.plugin-openapi.js +2 -2
- package/package.json +17 -6
- package/src/app/entry.client.tsx +14 -0
- package/src/lib/authentication/AuthenticationPlugin.tsx +4 -1
- package/src/lib/authentication/authentication.ts +1 -1
- package/src/lib/authentication/hook.ts +1 -3
- package/src/lib/authentication/providers/auth0.tsx +3 -2
- package/src/lib/authentication/providers/openid.tsx +12 -30
- package/src/lib/authentication/state.ts +44 -10
- package/src/lib/components/Bootstrap.tsx +25 -18
- package/src/lib/components/Header.tsx +42 -9
- package/src/lib/components/Layout.tsx +49 -37
- package/src/lib/components/MobileTopNavigation.tsx +11 -18
- package/src/lib/components/SyntaxHighlight.tsx +3 -2
- package/src/lib/components/ThemeSwitch.tsx +6 -4
- package/src/lib/components/TopNavigation.tsx +25 -17
- package/src/lib/core/plugins.ts +8 -0
- package/src/lib/oas/parser/upgrade/index.ts +4 -27
- package/src/lib/plugins/api-keys/index.tsx +3 -0
- package/src/lib/plugins/markdown/MdxPage.tsx +25 -1
- package/src/lib/plugins/markdown/index.tsx +1 -0
- package/src/lib/plugins/openapi/ColorizedParam.tsx +2 -2
- package/src/lib/plugins/openapi/client/GraphQLClient.tsx +17 -0
- package/src/lib/plugins/openapi/client/useCreateQuery.ts +5 -2
- package/src/lib/plugins/openapi/interfaces.ts +1 -1
- package/src/lib/plugins/openapi/post-processors/removeExtensions.test.ts +144 -0
- package/src/lib/plugins/openapi/post-processors/removeExtensions.ts +24 -0
- package/src/lib/plugins/openapi/post-processors/removePaths.test.ts +126 -0
- package/src/lib/plugins/openapi/post-processors/removePaths.ts +55 -0
- package/src/lib/plugins/openapi/post-processors/traverse.ts +1 -0
- package/src/lib/plugins/openapi/schema/SchemaView.tsx +1 -1
- package/src/lib/util/traverse.ts +25 -0
- package/lib/AuthenticationPlugin-DeGDVa1r.js +0 -56
- package/lib/AuthenticationPlugin-DeGDVa1r.js.map +0 -1
- package/lib/MdxPage-DFlbtJWi.js +0 -174
- package/lib/MdxPage-DFlbtJWi.js.map +0 -1
- package/lib/OperationList-KshJrrLL.js.map +0 -1
- package/lib/assets/worker-CPsGZsve.js.map +0 -1
- package/lib/createServer-DK-g7kbB.js.map +0 -1
- package/lib/index-BcesIHH4.js.map +0 -1
- package/lib/state-BsPrOUAh.js +0 -252
- package/lib/state-BsPrOUAh.js.map +0 -1
|
@@ -107,7 +107,10 @@ export class OpenIDAuthenticationProvider implements AuthenticationProvider {
|
|
|
107
107
|
expiresOn: new Date(Date.now() + response.expires_in * 1000),
|
|
108
108
|
tokenType: response.token_type,
|
|
109
109
|
};
|
|
110
|
-
|
|
110
|
+
|
|
111
|
+
useAuthState.setState({
|
|
112
|
+
providerData: tokens,
|
|
113
|
+
});
|
|
111
114
|
}
|
|
112
115
|
|
|
113
116
|
async signUp({ redirectTo }: { redirectTo?: string } = {}) {
|
|
@@ -194,14 +197,14 @@ export class OpenIDAuthenticationProvider implements AuthenticationProvider {
|
|
|
194
197
|
|
|
195
198
|
async getAccessToken(): Promise<string> {
|
|
196
199
|
const as = await this.getAuthServer();
|
|
197
|
-
const
|
|
198
|
-
if (!
|
|
200
|
+
const { providerData } = useAuthState.getState();
|
|
201
|
+
if (!providerData) {
|
|
199
202
|
throw new AuthorizationError("User is not authenticated");
|
|
200
203
|
}
|
|
204
|
+
const tokenState = providerData as TokenState;
|
|
201
205
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
if (!state.refreshToken) {
|
|
206
|
+
if (tokenState.expiresOn < new Date()) {
|
|
207
|
+
if (!tokenState.refreshToken) {
|
|
205
208
|
await this.signIn();
|
|
206
209
|
return "";
|
|
207
210
|
}
|
|
@@ -209,7 +212,7 @@ export class OpenIDAuthenticationProvider implements AuthenticationProvider {
|
|
|
209
212
|
const request = await oauth.refreshTokenGrantRequest(
|
|
210
213
|
as,
|
|
211
214
|
this.client,
|
|
212
|
-
|
|
215
|
+
tokenState.refreshToken,
|
|
213
216
|
);
|
|
214
217
|
const response = await oauth.processRefreshTokenResponse(
|
|
215
218
|
as,
|
|
@@ -225,7 +228,7 @@ export class OpenIDAuthenticationProvider implements AuthenticationProvider {
|
|
|
225
228
|
|
|
226
229
|
return response.access_token.toString();
|
|
227
230
|
} else {
|
|
228
|
-
return
|
|
231
|
+
return tokenState.accessToken;
|
|
229
232
|
}
|
|
230
233
|
}
|
|
231
234
|
|
|
@@ -234,8 +237,8 @@ export class OpenIDAuthenticationProvider implements AuthenticationProvider {
|
|
|
234
237
|
isAuthenticated: false,
|
|
235
238
|
isPending: false,
|
|
236
239
|
profile: undefined,
|
|
240
|
+
providerData: undefined,
|
|
237
241
|
});
|
|
238
|
-
sessionStorage.clear();
|
|
239
242
|
|
|
240
243
|
const as = await this.getAuthServer();
|
|
241
244
|
|
|
@@ -342,32 +345,11 @@ export class OpenIDAuthenticationProvider implements AuthenticationProvider {
|
|
|
342
345
|
profile,
|
|
343
346
|
});
|
|
344
347
|
|
|
345
|
-
sessionStorage.setItem(
|
|
346
|
-
"profile-state",
|
|
347
|
-
JSON.stringify(useAuthState.getState().profile),
|
|
348
|
-
);
|
|
349
|
-
|
|
350
348
|
const redirectTo = sessionStorage.getItem("redirect-to") ?? "/";
|
|
351
349
|
sessionStorage.removeItem("redirect-to");
|
|
352
350
|
return redirectTo;
|
|
353
351
|
};
|
|
354
352
|
|
|
355
|
-
pageLoad(): void {
|
|
356
|
-
const profileState = sessionStorage.getItem("profile-state");
|
|
357
|
-
if (profileState) {
|
|
358
|
-
try {
|
|
359
|
-
const profile = JSON.parse(profileState);
|
|
360
|
-
useAuthState.setState({
|
|
361
|
-
isAuthenticated: true,
|
|
362
|
-
isPending: false,
|
|
363
|
-
profile,
|
|
364
|
-
});
|
|
365
|
-
} catch (err) {
|
|
366
|
-
logger.error("Error parsing auth state", err);
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
|
|
371
353
|
getAuthenticationPlugin() {
|
|
372
354
|
// TODO: This API is a bit messy, we need to refactor auth plugins/providers
|
|
373
355
|
// to remove the extra layers of abstraction.
|
|
@@ -1,24 +1,58 @@
|
|
|
1
|
-
import { create } from "zustand";
|
|
2
|
-
import { persist } from "zustand/middleware";
|
|
3
|
-
import { shared } from "./use-broadcast/shared.js";
|
|
1
|
+
import { create, type Mutate, type StoreApi } from "zustand";
|
|
2
|
+
import { createJSONStorage, persist } from "zustand/middleware";
|
|
4
3
|
|
|
5
|
-
export interface AuthState {
|
|
4
|
+
export interface AuthState<ProviderData = unknown> {
|
|
6
5
|
isAuthenticated: boolean;
|
|
7
6
|
isPending: boolean;
|
|
8
|
-
profile
|
|
7
|
+
profile: UserProfile | null;
|
|
8
|
+
providerData: ProviderData | null;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
export
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
export class Authentication {
|
|
12
|
+
async setLoggedIn(isLoggedIn: boolean) {}
|
|
13
|
+
async setProfile() {}
|
|
14
|
+
async setPersistentProviderData() {}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type StoreWithPersist<T> = Mutate<
|
|
18
|
+
StoreApi<T>,
|
|
19
|
+
[["zustand/persist", unknown]]
|
|
20
|
+
>;
|
|
21
|
+
|
|
22
|
+
export const withStorageDOMEvents = <T>(store: StoreWithPersist<T>) => {
|
|
23
|
+
const storageEventCallback = (e: StorageEvent) => {
|
|
24
|
+
if (e.key === store.persist.getOptions().name && e.newValue) {
|
|
25
|
+
void store.persist.rehydrate();
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
window.addEventListener("storage", storageEventCallback);
|
|
30
|
+
|
|
31
|
+
return () => {
|
|
32
|
+
window.removeEventListener("storage", storageEventCallback);
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const useAuthState = create<AuthState>()(
|
|
37
|
+
persist(
|
|
38
|
+
(state) => ({
|
|
14
39
|
isAuthenticated: false,
|
|
15
40
|
isPending: false,
|
|
16
|
-
profile:
|
|
41
|
+
profile: null,
|
|
42
|
+
providerData: null,
|
|
17
43
|
}),
|
|
18
|
-
{
|
|
44
|
+
{
|
|
45
|
+
name: "auth-state",
|
|
46
|
+
storage: createJSONStorage(() => localStorage),
|
|
47
|
+
// partialize: (s) => ({ state: s }),
|
|
48
|
+
},
|
|
19
49
|
),
|
|
20
50
|
);
|
|
21
51
|
|
|
52
|
+
if (typeof window !== "undefined") {
|
|
53
|
+
withStorageDOMEvents(useAuthState);
|
|
54
|
+
}
|
|
55
|
+
|
|
22
56
|
export interface UserProfile {
|
|
23
57
|
sub: string;
|
|
24
58
|
email: string | undefined;
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
QueryClientProvider,
|
|
5
5
|
} from "@tanstack/react-query";
|
|
6
6
|
import { type HelmetData, HelmetProvider } from "@zudoku/react-helmet-async";
|
|
7
|
-
import { StrictMode
|
|
7
|
+
import { StrictMode } from "react";
|
|
8
8
|
import { type createBrowserRouter, RouterProvider } from "react-router-dom";
|
|
9
9
|
import {
|
|
10
10
|
type createStaticRouter,
|
|
@@ -13,29 +13,36 @@ import {
|
|
|
13
13
|
} from "react-router-dom/server.js";
|
|
14
14
|
import { StaggeredRenderContext } from "../plugins/openapi/StaggeredRender.js";
|
|
15
15
|
|
|
16
|
+
const queryClient = new QueryClient({
|
|
17
|
+
defaultOptions: {
|
|
18
|
+
queries: {
|
|
19
|
+
staleTime: 1000 * 60 * 5,
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
|
|
16
24
|
const Bootstrap = ({
|
|
17
25
|
router,
|
|
18
26
|
hydrate = false,
|
|
19
27
|
}: {
|
|
20
28
|
hydrate?: boolean;
|
|
21
29
|
router: ReturnType<typeof createBrowserRouter>;
|
|
22
|
-
}) =>
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
};
|
|
30
|
+
}) => (
|
|
31
|
+
<StrictMode>
|
|
32
|
+
<QueryClientProvider client={queryClient}>
|
|
33
|
+
<HydrationBoundary state={hydrate ? (window as any).DATA : undefined}>
|
|
34
|
+
<HelmetProvider>
|
|
35
|
+
<StaggeredRenderContext.Provider value={{ stagger: !hydrate }}>
|
|
36
|
+
<RouterProvider
|
|
37
|
+
router={router}
|
|
38
|
+
future={{ v7_startTransition: true }}
|
|
39
|
+
/>
|
|
40
|
+
</StaggeredRenderContext.Provider>
|
|
41
|
+
</HelmetProvider>
|
|
42
|
+
</HydrationBoundary>
|
|
43
|
+
</QueryClientProvider>
|
|
44
|
+
</StrictMode>
|
|
45
|
+
);
|
|
39
46
|
|
|
40
47
|
const BootstrapStatic = ({
|
|
41
48
|
router,
|
|
@@ -41,7 +41,12 @@ const RecursiveMenu = ({ item }: { item: ProfileNavigationItem }) => {
|
|
|
41
41
|
</DropdownMenuSub>
|
|
42
42
|
) : (
|
|
43
43
|
<Link to={item.path ?? ""}>
|
|
44
|
-
<DropdownMenuItem key={item.label}>
|
|
44
|
+
<DropdownMenuItem key={item.label} className="flex gap-2">
|
|
45
|
+
{item.icon && (
|
|
46
|
+
<item.icon size={16} strokeWidth={1} absoluteStrokeWidth />
|
|
47
|
+
)}
|
|
48
|
+
{item.label}
|
|
49
|
+
</DropdownMenuItem>
|
|
45
50
|
</Link>
|
|
46
51
|
);
|
|
47
52
|
};
|
|
@@ -55,7 +60,7 @@ export const Header = memo(function HeaderInner() {
|
|
|
55
60
|
const accountItems = plugins
|
|
56
61
|
.filter((p) => isProfileMenuPlugin(p))
|
|
57
62
|
.flatMap((p) => p.getProfileMenuItems(context))
|
|
58
|
-
.
|
|
63
|
+
.sort((i) => i.weight ?? 0);
|
|
59
64
|
|
|
60
65
|
return (
|
|
61
66
|
<header className="sticky lg:top-0 z-10 bg-background/80 backdrop-blur w-full">
|
|
@@ -82,7 +87,6 @@ export const Header = memo(function HeaderInner() {
|
|
|
82
87
|
loading="lazy"
|
|
83
88
|
/>
|
|
84
89
|
<img
|
|
85
|
-
data-hide-on-theme="light"
|
|
86
90
|
src={
|
|
87
91
|
/https?:\/\//.test(page.logo.src.dark)
|
|
88
92
|
? page.logo.src.dark
|
|
@@ -93,7 +97,7 @@ export const Header = memo(function HeaderInner() {
|
|
|
93
97
|
}
|
|
94
98
|
alt={page.logo.alt ?? page.pageTitle}
|
|
95
99
|
style={{ width: page.logo.width }}
|
|
96
|
-
className="h-10"
|
|
100
|
+
className="h-10 hidden dark:block"
|
|
97
101
|
loading="lazy"
|
|
98
102
|
/>
|
|
99
103
|
</>
|
|
@@ -121,17 +125,46 @@ export const Header = memo(function HeaderInner() {
|
|
|
121
125
|
Login
|
|
122
126
|
</Button>
|
|
123
127
|
) : (
|
|
124
|
-
accountItems.length > 0 && (
|
|
128
|
+
Object.values(accountItems).length > 0 && (
|
|
125
129
|
<DropdownMenu modal={false}>
|
|
126
130
|
<DropdownMenuTrigger asChild>
|
|
127
131
|
<Button variant="ghost">
|
|
128
|
-
{profile?.
|
|
132
|
+
{profile?.name ? `${profile.name}` : "My Account"}
|
|
129
133
|
</Button>
|
|
130
134
|
</DropdownMenuTrigger>
|
|
131
135
|
<DropdownMenuContent className="w-56">
|
|
132
|
-
<DropdownMenuLabel>
|
|
133
|
-
|
|
134
|
-
|
|
136
|
+
<DropdownMenuLabel>
|
|
137
|
+
{profile?.name ? `${profile.name}` : "My Account"}
|
|
138
|
+
{profile?.email && (
|
|
139
|
+
<div className="font-normal text-muted-foreground">
|
|
140
|
+
{profile.email}
|
|
141
|
+
</div>
|
|
142
|
+
)}
|
|
143
|
+
</DropdownMenuLabel>
|
|
144
|
+
{accountItems.filter((i) => i.category === "top")
|
|
145
|
+
.length > 0 && <DropdownMenuSeparator />}
|
|
146
|
+
{accountItems
|
|
147
|
+
.filter((i) => i.category === "top")
|
|
148
|
+
.map((i) => (
|
|
149
|
+
<RecursiveMenu key={i.label} item={i} />
|
|
150
|
+
))}
|
|
151
|
+
{accountItems.filter(
|
|
152
|
+
(i) => !i.category || i.category === "middle",
|
|
153
|
+
).length > 0 && <DropdownMenuSeparator />}
|
|
154
|
+
{accountItems
|
|
155
|
+
.filter(
|
|
156
|
+
(i) => !i.category || i.category === "middle",
|
|
157
|
+
)
|
|
158
|
+
.map((i) => (
|
|
159
|
+
<RecursiveMenu key={i.label} item={i} />
|
|
160
|
+
))}
|
|
161
|
+
{accountItems.filter((i) => i.category === "bottom")
|
|
162
|
+
.length > 0 && <DropdownMenuSeparator />}
|
|
163
|
+
{accountItems
|
|
164
|
+
.filter((i) => i.category === "bottom")
|
|
165
|
+
.map((i) => (
|
|
166
|
+
<RecursiveMenu key={i.label} item={i} />
|
|
167
|
+
))}
|
|
135
168
|
</DropdownMenuContent>
|
|
136
169
|
</DropdownMenu>
|
|
137
170
|
)
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Helmet } from "@zudoku/react-helmet-async";
|
|
2
2
|
import { PanelLeftIcon } from "lucide-react";
|
|
3
3
|
import { Suspense, useEffect, useRef, type ReactNode } from "react";
|
|
4
|
-
import { Outlet, useLocation } from "react-router-dom";
|
|
4
|
+
import { Outlet, useLocation, useNavigation } from "react-router-dom";
|
|
5
|
+
import { useSpinDelay } from "spin-delay";
|
|
5
6
|
import { Drawer, DrawerTrigger } from "../ui/Drawer.js";
|
|
6
7
|
import { cn } from "../util/cn.js";
|
|
7
8
|
import { useScrollToAnchor } from "../util/useScrollToAnchor.js";
|
|
@@ -13,6 +14,12 @@ import { Sidebar } from "./navigation/Sidebar.js";
|
|
|
13
14
|
import { Slotlet } from "./SlotletProvider.js";
|
|
14
15
|
import { Spinner } from "./Spinner.js";
|
|
15
16
|
|
|
17
|
+
const LoadingFallback = () => (
|
|
18
|
+
<main className="grid h-[calc(100vh-var(--header-height))] place-items-center">
|
|
19
|
+
<Spinner />
|
|
20
|
+
</main>
|
|
21
|
+
);
|
|
22
|
+
|
|
16
23
|
export const Layout = ({ children }: { children?: ReactNode }) => {
|
|
17
24
|
const location = useLocation();
|
|
18
25
|
const { setActiveAnchor } = useViewportAnchor();
|
|
@@ -25,7 +32,7 @@ export const Layout = ({ children }: { children?: ReactNode }) => {
|
|
|
25
32
|
|
|
26
33
|
useEffect(() => {
|
|
27
34
|
// Initialize the authentication plugin
|
|
28
|
-
authentication?.
|
|
35
|
+
authentication?.onPageLoad?.();
|
|
29
36
|
}, [authentication]);
|
|
30
37
|
|
|
31
38
|
useEffect(() => {
|
|
@@ -36,6 +43,13 @@ export const Layout = ({ children }: { children?: ReactNode }) => {
|
|
|
36
43
|
previousLocationPath.current = location.pathname;
|
|
37
44
|
}, [location.pathname, setActiveAnchor]);
|
|
38
45
|
|
|
46
|
+
// Page transition is happening: https://reactrouter.com/start/framework/pending-ui#global-pending-navigation
|
|
47
|
+
const isNavigating = Boolean(useNavigation().location);
|
|
48
|
+
const showSpinner = useSpinDelay(isNavigating, {
|
|
49
|
+
delay: 300,
|
|
50
|
+
minDuration: 500,
|
|
51
|
+
});
|
|
52
|
+
|
|
39
53
|
return (
|
|
40
54
|
<>
|
|
41
55
|
{import.meta.env.MODE === "standalone" && (
|
|
@@ -52,41 +66,39 @@ export const Layout = ({ children }: { children?: ReactNode }) => {
|
|
|
52
66
|
<Slotlet name="layout-after-head" />
|
|
53
67
|
|
|
54
68
|
<div className="w-full max-w-screen-2xl mx-auto px-10 lg:px-12">
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
"
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
"
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
</Drawer>
|
|
89
|
-
</Suspense>
|
|
69
|
+
{showSpinner ? (
|
|
70
|
+
<LoadingFallback />
|
|
71
|
+
) : (
|
|
72
|
+
<Suspense fallback={<LoadingFallback />}>
|
|
73
|
+
<Drawer direction="left">
|
|
74
|
+
<Sidebar />
|
|
75
|
+
<div
|
|
76
|
+
className={cn(
|
|
77
|
+
"lg:hidden -mx-10 px-10 py-2 sticky bg-background/80 backdrop-blur z-10 top-0 left-0 right-0 border-b",
|
|
78
|
+
"peer-data-[navigation=false]:hidden",
|
|
79
|
+
)}
|
|
80
|
+
>
|
|
81
|
+
<DrawerTrigger className="flex items-center gap-2">
|
|
82
|
+
<PanelLeftIcon size={16} strokeWidth={1.5} />
|
|
83
|
+
<span className="text-sm">Menu</span>
|
|
84
|
+
</DrawerTrigger>
|
|
85
|
+
</div>
|
|
86
|
+
<main
|
|
87
|
+
className={cn(
|
|
88
|
+
"h-full dark:border-white/10 translate-x-0",
|
|
89
|
+
"lg:overflow-visible",
|
|
90
|
+
// This works in tandem with the `SidebarWrapper` component
|
|
91
|
+
"lg:peer-data-[navigation=true]:w-[calc(100%-var(--side-nav-width))]",
|
|
92
|
+
"lg:peer-data-[navigation=true]:translate-x-[--side-nav-width] lg:peer-data-[navigation=true]:pl-12",
|
|
93
|
+
)}
|
|
94
|
+
>
|
|
95
|
+
<Slotlet name="zudoku-before-content" />
|
|
96
|
+
{children ?? <Outlet />}
|
|
97
|
+
<Slotlet name="zudoku-after-content" />
|
|
98
|
+
</main>
|
|
99
|
+
</Drawer>
|
|
100
|
+
</Suspense>
|
|
101
|
+
)}
|
|
90
102
|
</div>
|
|
91
103
|
</>
|
|
92
104
|
);
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { VisuallyHidden } from "@radix-ui/react-visually-hidden";
|
|
2
|
-
import { cx } from "class-variance-authority";
|
|
3
2
|
import { MenuIcon } from "lucide-react";
|
|
4
|
-
import {
|
|
3
|
+
import { useState } from "react";
|
|
5
4
|
import { useAuth } from "../authentication/hook.js";
|
|
6
5
|
import {
|
|
7
6
|
Drawer,
|
|
8
|
-
DrawerClose,
|
|
9
7
|
DrawerContent,
|
|
10
8
|
DrawerTitle,
|
|
11
9
|
DrawerTrigger,
|
|
@@ -13,14 +11,19 @@ import {
|
|
|
13
11
|
import { useZudoku } from "./context/ZudokuContext.js";
|
|
14
12
|
import { Search } from "./Search.js";
|
|
15
13
|
import { ThemeSwitch } from "./ThemeSwitch.js";
|
|
16
|
-
import { isHiddenItem } from "./TopNavigation.js";
|
|
14
|
+
import { isHiddenItem, TopNavItem } from "./TopNavigation.js";
|
|
17
15
|
|
|
18
16
|
export const MobileTopNavigation = () => {
|
|
19
17
|
const { topNavigation } = useZudoku();
|
|
20
18
|
const { isAuthenticated } = useAuth();
|
|
19
|
+
const [drawerOpen, setDrawerOpen] = useState(false);
|
|
21
20
|
|
|
22
21
|
return (
|
|
23
|
-
<Drawer
|
|
22
|
+
<Drawer
|
|
23
|
+
direction="right"
|
|
24
|
+
open={drawerOpen}
|
|
25
|
+
onOpenChange={(open) => setDrawerOpen(open)}
|
|
26
|
+
>
|
|
24
27
|
<div className="flex lg:hidden justify-self-end">
|
|
25
28
|
<DrawerTrigger className="lg:hidden">
|
|
26
29
|
<MenuIcon size={22} />
|
|
@@ -42,19 +45,9 @@ export const MobileTopNavigation = () => {
|
|
|
42
45
|
</li>
|
|
43
46
|
{topNavigation.filter(isHiddenItem(isAuthenticated)).map((item) => (
|
|
44
47
|
<li key={item.label}>
|
|
45
|
-
<
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
"block font-medium border-b-2",
|
|
49
|
-
isActive
|
|
50
|
-
? "border-primary text-foreground"
|
|
51
|
-
: "border-transparent text-foreground/75 hover:text-foreground hover:border-accent-foreground/25",
|
|
52
|
-
)
|
|
53
|
-
}
|
|
54
|
-
to={item.id}
|
|
55
|
-
>
|
|
56
|
-
<DrawerClose>{item.label}</DrawerClose>
|
|
57
|
-
</NavLink>
|
|
48
|
+
<button onClick={() => setDrawerOpen(false)}>
|
|
49
|
+
<TopNavItem {...item} />
|
|
50
|
+
</button>
|
|
58
51
|
</li>
|
|
59
52
|
))}
|
|
60
53
|
</ul>
|
|
@@ -56,14 +56,15 @@ export const SyntaxHighlight = ({
|
|
|
56
56
|
language = "plain",
|
|
57
57
|
...props
|
|
58
58
|
}: SyntaxHighlightProps) => {
|
|
59
|
-
const {
|
|
59
|
+
const { resolvedTheme } = useTheme();
|
|
60
60
|
const [isCopied, setIsCopied] = useState(false);
|
|
61
61
|
|
|
62
62
|
if (!props.code) {
|
|
63
63
|
return null;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
const highlightTheme =
|
|
66
|
+
const highlightTheme =
|
|
67
|
+
resolvedTheme === "dark" ? themes.vsDark : themes.github;
|
|
67
68
|
|
|
68
69
|
// hardcoded values from the themes to avoid color flash in SSR
|
|
69
70
|
const themeColorClasses =
|
|
@@ -4,18 +4,20 @@ import { Button } from "zudoku/ui/Button.js";
|
|
|
4
4
|
import { ClientOnly } from "./ClientOnly.js";
|
|
5
5
|
|
|
6
6
|
export const ThemeSwitch = () => {
|
|
7
|
-
const {
|
|
8
|
-
const ThemeIcon =
|
|
7
|
+
const { resolvedTheme, setTheme } = useTheme();
|
|
8
|
+
const ThemeIcon = resolvedTheme === "dark" ? MoonStarIcon : SunIcon;
|
|
9
9
|
|
|
10
10
|
return (
|
|
11
11
|
<ClientOnly>
|
|
12
12
|
<Button
|
|
13
13
|
variant="ghost"
|
|
14
14
|
aria-label={
|
|
15
|
-
|
|
15
|
+
resolvedTheme === "dark"
|
|
16
|
+
? "Switch to light mode"
|
|
17
|
+
: "Switch to dark mode"
|
|
16
18
|
}
|
|
17
19
|
className="p-2.5 -m-2.5 rounded-full"
|
|
18
|
-
onClick={() => setTheme(
|
|
20
|
+
onClick={() => setTheme(resolvedTheme === "dark" ? "light" : "dark")}
|
|
19
21
|
>
|
|
20
22
|
<ThemeIcon size={18} />
|
|
21
23
|
</Button>
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { cx } from "class-variance-authority";
|
|
2
2
|
import { Suspense } from "react";
|
|
3
|
-
import {
|
|
3
|
+
import { NavLink, useNavigation } from "react-router-dom";
|
|
4
4
|
import { TopNavigationItem } from "../../config/validators/validate.js";
|
|
5
5
|
import { useAuth } from "../authentication/hook.js";
|
|
6
|
+
import { ZudokuError } from "../util/invariant.js";
|
|
6
7
|
import { joinPath } from "../util/joinPath.js";
|
|
7
8
|
import { useCurrentNavigation, useZudoku } from "./context/ZudokuContext.js";
|
|
8
9
|
import { traverseSidebar } from "./navigation/utils.js";
|
|
@@ -42,10 +43,16 @@ export const TopNavigation = () => {
|
|
|
42
43
|
);
|
|
43
44
|
};
|
|
44
45
|
|
|
45
|
-
const TopNavItem = ({
|
|
46
|
+
export const TopNavItem = ({
|
|
47
|
+
id,
|
|
48
|
+
label,
|
|
49
|
+
default: defaultLink,
|
|
50
|
+
}: TopNavigationItem) => {
|
|
46
51
|
const { sidebars } = useZudoku();
|
|
47
|
-
const nav = useCurrentNavigation();
|
|
48
52
|
const currentSidebar = sidebars[id];
|
|
53
|
+
const currentNav = useCurrentNavigation();
|
|
54
|
+
const isNavigating = Boolean(useNavigation().location);
|
|
55
|
+
const isActive = currentNav.topNavItem?.id === id && !isNavigating;
|
|
49
56
|
|
|
50
57
|
// TODO: This is a bit of a hack to get the first link in the sidebar
|
|
51
58
|
// We should really process this when we load the config so we can validate
|
|
@@ -60,25 +67,26 @@ const TopNavItem = ({ id, label, default: defaultLink }: TopNavigationItem) => {
|
|
|
60
67
|
: joinPath(id));
|
|
61
68
|
|
|
62
69
|
if (!first) {
|
|
63
|
-
throw new
|
|
64
|
-
`No links found in top navigation for
|
|
65
|
-
);
|
|
70
|
+
throw new ZudokuError("Page not found.", {
|
|
71
|
+
developerHint: `No links found in top navigation for '${id}'. Check that the sidebar isn't empty or that a default link is set.`,
|
|
72
|
+
});
|
|
66
73
|
}
|
|
67
74
|
|
|
68
|
-
// Manually set the active sidebar based on our logic of what is active
|
|
69
|
-
const isActive = nav.topNavItem?.id === id;
|
|
70
|
-
|
|
71
75
|
return (
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
:
|
|
78
|
-
|
|
76
|
+
// We don't use isActive here because it has to be inside the sidebar,
|
|
77
|
+
// the top nav id doesn't necessarily start with the sidebar id
|
|
78
|
+
<NavLink
|
|
79
|
+
className={({ isPending }) =>
|
|
80
|
+
cx(
|
|
81
|
+
"block lg:py-3.5 font-medium -mb-px border-b-2",
|
|
82
|
+
isActive || isPending
|
|
83
|
+
? "border-primary text-foreground"
|
|
84
|
+
: "border-transparent text-foreground/75 hover:text-foreground hover:border-accent-foreground/25",
|
|
85
|
+
)
|
|
86
|
+
}
|
|
79
87
|
to={first}
|
|
80
88
|
>
|
|
81
89
|
{label}
|
|
82
|
-
</
|
|
90
|
+
</NavLink>
|
|
83
91
|
);
|
|
84
92
|
};
|
package/src/lib/core/plugins.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { LucideProps } from "lucide-react";
|
|
1
2
|
import { type ReactElement } from "react";
|
|
2
3
|
import { type RouteObject } from "react-router-dom";
|
|
3
4
|
import type { Sidebar } from "../../config/validators/SidebarSchema.js";
|
|
@@ -36,7 +37,14 @@ export interface ProfileMenuPlugin {
|
|
|
36
37
|
export type ProfileNavigationItem = {
|
|
37
38
|
label: string;
|
|
38
39
|
path?: string;
|
|
40
|
+
weight?: number;
|
|
41
|
+
category?: "top" | "middle" | "bottom";
|
|
39
42
|
children?: ProfileNavigationItem[];
|
|
43
|
+
icon?: React.ComponentType<
|
|
44
|
+
LucideProps & {
|
|
45
|
+
[key: string]: any;
|
|
46
|
+
}
|
|
47
|
+
>;
|
|
40
48
|
};
|
|
41
49
|
|
|
42
50
|
export interface CommonPlugin {
|