@xyd-js/host 0.0.0-build
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/CHANGELOG.md +24 -0
- package/LICENSE +21 -0
- package/app/debug-null.tsx +3 -0
- package/app/docPaths.ts +69 -0
- package/app/entry.client.tsx +58 -0
- package/app/entry.server.tsx +68 -0
- package/app/pathRoutes.ts +86 -0
- package/app/public.ts +43 -0
- package/app/raw.ts +31 -0
- package/app/robots.ts +18 -0
- package/app/root.tsx +579 -0
- package/app/routes.ts +35 -0
- package/app/scripts/abtesting.ts +279 -0
- package/app/scripts/bannerHeight.ts +14 -0
- package/app/scripts/colorSchemeScript.ts +21 -0
- package/app/scripts/growthbook.js +3574 -0
- package/app/scripts/launchdarkly.js +2 -0
- package/app/scripts/openfeature.growthbook.js +692 -0
- package/app/scripts/openfeature.js +1715 -0
- package/app/scripts/openfeature.launchdarkly.js +877 -0
- package/app/scripts/testFeatureFlag.ts +39 -0
- package/app/sitemap.ts +40 -0
- package/app/types/raw.d.ts +4 -0
- package/auto-imports.d.ts +10 -0
- package/package.json +41 -0
- package/plugins/README.md +1 -0
- package/postcss.config.cjs +5 -0
- package/react-router.config.ts +94 -0
- package/src/auto-imports.d.ts +29 -0
- package/tsconfig.json +28 -0
- package/types.d.ts +8 -0
- package/vite.config.ts +8 -0
package/app/root.tsx
ADDED
|
@@ -0,0 +1,579 @@
|
|
|
1
|
+
import React, { useEffect } from "react";
|
|
2
|
+
import { Links, Meta, Outlet, Scripts, ScrollRestoration, redirect } from "react-router";
|
|
3
|
+
|
|
4
|
+
import type { Settings, Appearance, ThemeFont, Font, UserPreferences } from "@xyd-js/core";
|
|
5
|
+
import * as contentClass from "@xyd-js/components/content"; // TODO: move to appearance
|
|
6
|
+
|
|
7
|
+
// @ts-ignore
|
|
8
|
+
import virtualSettings from "virtual:xyd-settings";
|
|
9
|
+
// @ts-ignore
|
|
10
|
+
import { presetUrls } from "virtual:xyd-theme-presets"
|
|
11
|
+
|
|
12
|
+
import colorSchemeScript from "./scripts/colorSchemeScript.ts?raw";
|
|
13
|
+
import bannerHeightScript from "./scripts/bannerHeight.ts?raw";
|
|
14
|
+
|
|
15
|
+
import openfeatureScript from "./scripts/openfeature.js?raw";
|
|
16
|
+
import abTestingScript from "./scripts/abtesting.ts?raw";
|
|
17
|
+
|
|
18
|
+
import growthbookScript from "./scripts/growthbook.js?raw";
|
|
19
|
+
import openfeatureGrowthbookScript from "./scripts/openfeature.growthbook.js?raw";
|
|
20
|
+
|
|
21
|
+
import launchdarklyScript from "./scripts/launchdarkly.js?raw";
|
|
22
|
+
import openfeatureLaunchdarklyScript from "./scripts/openfeature.launchdarkly.js?raw";
|
|
23
|
+
|
|
24
|
+
const { settings, userPreferences } = virtualSettings as { settings: Settings, settingsClone: Settings, userPreferences: UserPreferences }
|
|
25
|
+
|
|
26
|
+
export function HydrateFallback() {
|
|
27
|
+
return <div></div>
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function loader({ request }: { request: any }) {
|
|
31
|
+
if (process.env.NODE_ENV === "production") {
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const slug = getPathname(request.url || "index") || "index"
|
|
36
|
+
|
|
37
|
+
if (settings?.redirects) {
|
|
38
|
+
const shouldRedirect = settings.redirects.find((redirect: any) => redirect.source === slug)
|
|
39
|
+
if (shouldRedirect) {
|
|
40
|
+
return redirect(shouldRedirect.destination)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function Layout({ children }: { children: React.ReactNode }) {
|
|
46
|
+
const colorScheme = clientColorScheme() || settings?.theme?.appearance?.colorScheme || "os"
|
|
47
|
+
|
|
48
|
+
const { component: UserAppearance, classes: UserAppearanceClasses } = userAppearance()
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<html
|
|
52
|
+
data-color-scheme={colorScheme}
|
|
53
|
+
data-color-primary={settings?.theme?.appearance?.colors?.primary ? "true" : undefined}
|
|
54
|
+
>
|
|
55
|
+
|
|
56
|
+
<head>
|
|
57
|
+
<PreloadScripts />
|
|
58
|
+
<DefaultMetas />
|
|
59
|
+
<CssLayerFix />
|
|
60
|
+
|
|
61
|
+
<UserFavicon />
|
|
62
|
+
<UserHeadScripts />
|
|
63
|
+
<UserFonts />
|
|
64
|
+
|
|
65
|
+
<Meta />
|
|
66
|
+
<Links />
|
|
67
|
+
<PresetStyles />
|
|
68
|
+
</head>
|
|
69
|
+
|
|
70
|
+
<body className={UserAppearanceClasses}>
|
|
71
|
+
{children}
|
|
72
|
+
<ScrollRestoration />
|
|
73
|
+
<Scripts />
|
|
74
|
+
<UserStyleTokens />
|
|
75
|
+
<UserPreferenceStyles />
|
|
76
|
+
{UserAppearance}
|
|
77
|
+
{/* TODO: in the future better solution? */}
|
|
78
|
+
</body>
|
|
79
|
+
</html>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function PreloadScripts() {
|
|
84
|
+
return <>
|
|
85
|
+
<ABTestingScript />
|
|
86
|
+
<ColorSchemeScript />
|
|
87
|
+
{/* TODO: in the future better solution? */}
|
|
88
|
+
<BannerHeightScript />
|
|
89
|
+
</>
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function clientColorScheme() {
|
|
93
|
+
if (typeof window === "undefined") {
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
var theme = localStorage.getItem('xyd-color-scheme') || 'auto';
|
|
99
|
+
var isDark = false;
|
|
100
|
+
|
|
101
|
+
if (theme === 'dark') {
|
|
102
|
+
isDark = true;
|
|
103
|
+
} else if (theme === 'auto') {
|
|
104
|
+
isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return isDark ? "dark" : undefined
|
|
108
|
+
} catch (e) {
|
|
109
|
+
// Fallback to system preference if localStorage fails
|
|
110
|
+
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
|
111
|
+
return "dark"
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return undefined
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export default function App() {
|
|
119
|
+
return <Outlet />;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function getPathname(url: string) {
|
|
123
|
+
const parsedUrl = new URL(url);
|
|
124
|
+
return parsedUrl.pathname.replace(/^\//, '');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
function DefaultMetas() {
|
|
129
|
+
return <>
|
|
130
|
+
<meta charSet="utf-8" />
|
|
131
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
132
|
+
</>
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function ColorSchemeScript() {
|
|
136
|
+
return <script
|
|
137
|
+
dangerouslySetInnerHTML={{
|
|
138
|
+
__html: colorSchemeScript
|
|
139
|
+
}}
|
|
140
|
+
/>
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// TODO: check if it match good
|
|
144
|
+
function BannerHeightScript() {
|
|
145
|
+
const appearance = settings?.theme?.appearance
|
|
146
|
+
|
|
147
|
+
useEffect(() => {
|
|
148
|
+
const bannerHeight = document.querySelector("xyd-banner")?.clientHeight ?? 0;
|
|
149
|
+
if (!bannerHeight) {
|
|
150
|
+
return
|
|
151
|
+
}
|
|
152
|
+
document.documentElement.style.setProperty("--xyd-banner-height", `${String(bannerHeight)}px`)
|
|
153
|
+
|
|
154
|
+
if (!appearance?.banner?.fixed) {
|
|
155
|
+
return
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
document.documentElement.style.setProperty("--xyd-banner-height-dynamic", `${String(bannerHeight)}px`)
|
|
159
|
+
}, [])
|
|
160
|
+
|
|
161
|
+
return null
|
|
162
|
+
|
|
163
|
+
return <script
|
|
164
|
+
dangerouslySetInnerHTML={{
|
|
165
|
+
__html: bannerHeightScript
|
|
166
|
+
}}
|
|
167
|
+
/>
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function UserFavicon() {
|
|
171
|
+
const faviconPath = settings?.theme?.favicon
|
|
172
|
+
|
|
173
|
+
if (!faviconPath) {
|
|
174
|
+
return null
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return <link rel="icon" type="image/png" sizes="32x32" href={faviconPath}></link>
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// TODO: !!! in the future more developer-friendly code + bundle optimization !!!
|
|
181
|
+
function ABTestingScript() {
|
|
182
|
+
const abtesting = settings?.integrations?.abtesting
|
|
183
|
+
const providers = settings?.integrations?.abtesting?.providers || {}
|
|
184
|
+
|
|
185
|
+
if (!providers || !Object.keys(providers).length) {
|
|
186
|
+
return null
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const scripts = [
|
|
190
|
+
openfeatureScript,
|
|
191
|
+
]
|
|
192
|
+
|
|
193
|
+
if (providers?.growthbook) {
|
|
194
|
+
scripts.push(growthbookScript)
|
|
195
|
+
scripts.push(openfeatureGrowthbookScript)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (providers?.launchdarkly) {
|
|
199
|
+
scripts.push(launchdarklyScript)
|
|
200
|
+
scripts.push(openfeatureLaunchdarklyScript)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
scripts.push(abTestingScript)
|
|
204
|
+
|
|
205
|
+
const allScripts = scripts.join('\n');
|
|
206
|
+
|
|
207
|
+
// Inject settings into the script
|
|
208
|
+
const scriptWithSettings = `
|
|
209
|
+
window.__xydAbTestingSettings = ${JSON.stringify(abtesting)};
|
|
210
|
+
${allScripts}
|
|
211
|
+
`;
|
|
212
|
+
|
|
213
|
+
return <script
|
|
214
|
+
dangerouslySetInnerHTML={{
|
|
215
|
+
__html: scriptWithSettings
|
|
216
|
+
}}
|
|
217
|
+
/>
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// TODO: better than <style>?
|
|
221
|
+
function UserStyleTokens() {
|
|
222
|
+
const userCss = generateUserCss(settings?.theme?.appearance)
|
|
223
|
+
|
|
224
|
+
if (!userCss) {
|
|
225
|
+
return null
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return <>
|
|
229
|
+
<style
|
|
230
|
+
data-appearance
|
|
231
|
+
dangerouslySetInnerHTML={{
|
|
232
|
+
__html: userCss
|
|
233
|
+
}}
|
|
234
|
+
/>
|
|
235
|
+
</>
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function UserPreferenceStyles() {
|
|
239
|
+
const themeColors = userPreferences?.themeColors
|
|
240
|
+
if (!themeColors) {
|
|
241
|
+
return null
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const coderPreferences = tokensToCss({
|
|
245
|
+
"--user-codetabs-bgcolor": "none",
|
|
246
|
+
"--user-codetabs-color": "none",
|
|
247
|
+
"--user-codetabs-color--active": "currentColor",
|
|
248
|
+
"--user-codetabs-color--hover": "none",
|
|
249
|
+
"--user-coder-code-border-color": "none",
|
|
250
|
+
"--xyd-coder-code-mark-bgcolor": `color-mix(in srgb, ${themeColors.foreground} 10%, transparent)`
|
|
251
|
+
});
|
|
252
|
+
const css = [
|
|
253
|
+
coderPreferences
|
|
254
|
+
].filter(Boolean).join('\n\n');
|
|
255
|
+
|
|
256
|
+
return <>
|
|
257
|
+
<style dangerouslySetInnerHTML={{ __html: css }} />
|
|
258
|
+
</>
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// TODO: better than <style>?
|
|
262
|
+
function userAppearance() {
|
|
263
|
+
const theme = {
|
|
264
|
+
searchWidth: settings?.theme?.appearance?.search?.fullWidth ? "100%" : undefined,
|
|
265
|
+
buttonsRounded: cssVarSize("--xyd-border-radius", settings?.theme?.appearance?.buttons?.rounded, "lg"),
|
|
266
|
+
scrollbarColor: settings?.theme?.appearance?.sidebar?.scrollbarColor || undefined
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const userAppearanceCss = tokensToCss({
|
|
270
|
+
"--xyd-search-width": theme.searchWidth || undefined,
|
|
271
|
+
"--xyd-button-border-radius": theme.buttonsRounded || undefined,
|
|
272
|
+
"--decorator-sidebar-scroll-bgcolor": theme.scrollbarColor || undefined
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
if (!userAppearanceCss) {
|
|
276
|
+
return {
|
|
277
|
+
component: null,
|
|
278
|
+
classes: ""
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const classes = []
|
|
283
|
+
if (settings?.theme?.appearance?.search?.fullWidth) {
|
|
284
|
+
classes.push(contentClass.SearchButtonFullWidth)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return {
|
|
288
|
+
component: <style
|
|
289
|
+
dangerouslySetInnerHTML={{
|
|
290
|
+
__html: userAppearanceCss
|
|
291
|
+
}}
|
|
292
|
+
/>,
|
|
293
|
+
classes: classes.join(" ")
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function generateUserCss(appearance?: Appearance): string {
|
|
298
|
+
if (!appearance) return '';
|
|
299
|
+
|
|
300
|
+
const { colors, cssTokens } = appearance;
|
|
301
|
+
|
|
302
|
+
const lightTokens = {
|
|
303
|
+
...(colors?.primary ? generateColorTokens(colors.primary) : {}),
|
|
304
|
+
...(cssTokens ? cssTokens : {}),
|
|
305
|
+
// ...(sidebar?.scrollbarColor ? { "--xyd-toc-scroll-bgcolor": sidebar.scrollbarColor } : {})
|
|
306
|
+
};
|
|
307
|
+
const darkTokens = {
|
|
308
|
+
...(colors?.light ? generateColorTokens(colors.light) : {}),
|
|
309
|
+
...(cssTokens ? cssTokens : {}),
|
|
310
|
+
// ...(sidebar?.scrollbarColor ? { "--xyd-toc-scroll-bgcolor": sidebar.scrollbarColor } : {})
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
const lightCss = tokensToCss(lightTokens);
|
|
314
|
+
const darkCss = generateDarkCss(darkTokens);
|
|
315
|
+
|
|
316
|
+
return [lightCss, darkCss].filter(Boolean).join('\n\n');
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// TODO: typesafe css variables?
|
|
320
|
+
function generateColorTokens(primary: string): Record<string, string> {
|
|
321
|
+
return {
|
|
322
|
+
"--color-primary": primary,
|
|
323
|
+
"--xyd-sidebar-item-bgcolor--active": 'color-mix(in srgb, var(--color-primary) 10%, transparent)',
|
|
324
|
+
"--xyd-sidebar-item-color--active": 'var(--color-primary)',
|
|
325
|
+
"--xyd-toc-item-color--active": 'var(--color-primary)',
|
|
326
|
+
"--theme-color-primary": 'var(--color-primary)',
|
|
327
|
+
"--theme-color-primary-active": 'var(--color-primary)',
|
|
328
|
+
"--color-primary--active": 'color-mix(in srgb, var(--color-primary) 85%, transparent)',
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function tokensToCss(tokens: Record<string, string | boolean | undefined>, wrapInRoot = true): string {
|
|
333
|
+
if (!Object.keys(tokens).length) {
|
|
334
|
+
return '';
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const props = Object.entries(tokens)
|
|
338
|
+
.filter(([key, value]) => value !== undefined)
|
|
339
|
+
.map(([key, value]) => `${key}: ${value};`)
|
|
340
|
+
.join('\n');
|
|
341
|
+
|
|
342
|
+
return wrapInRoot ? `:root {\n${props}\n}` : props;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function generateDarkCss(tokens: Record<string, string>): string {
|
|
346
|
+
if (!Object.keys(tokens).length) {
|
|
347
|
+
return '';
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const raw = tokensToCss(tokens, false);
|
|
351
|
+
return [
|
|
352
|
+
`[data-color-scheme="dark"] {\n${raw}\n}`,
|
|
353
|
+
`@media (prefers-color-scheme: dark) {`,
|
|
354
|
+
` :root:not([data-color-scheme="light"]):not([data-color-scheme="dark"]) {\n ${raw.replace(/\n/g, '\n ')}\n }`,
|
|
355
|
+
`}`
|
|
356
|
+
].join('\n');
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function UserHeadScripts() {
|
|
360
|
+
const head = settings?.theme?.head || []
|
|
361
|
+
|
|
362
|
+
if (!head || !head.length) {
|
|
363
|
+
return null
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return head.map(([tag, rawProps, content]: [string, Record<string, string | boolean>, string?], index: number) => {
|
|
367
|
+
const props: Record<string, any> = { ...rawProps }
|
|
368
|
+
|
|
369
|
+
const onload = props.onLoad || props.onload
|
|
370
|
+
|
|
371
|
+
// Convert onLoad from string to function
|
|
372
|
+
if (typeof onload === 'string') {
|
|
373
|
+
const fnBody = onload
|
|
374
|
+
props.onLoad = () => {
|
|
375
|
+
// eslint-disable-next-line no-new-func
|
|
376
|
+
new Function(fnBody)()
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
delete props.onload
|
|
381
|
+
|
|
382
|
+
if (content) {
|
|
383
|
+
return React.createElement(tag, {
|
|
384
|
+
key: index,
|
|
385
|
+
...props,
|
|
386
|
+
dangerouslySetInnerHTML: { __html: content },
|
|
387
|
+
})
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return React.createElement(tag, { key: index, ...props })
|
|
391
|
+
})
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function PresetStyles() {
|
|
395
|
+
const appearance = settings?.theme?.appearance
|
|
396
|
+
const appearancePresets = appearance?.presets || []
|
|
397
|
+
|
|
398
|
+
return Object.entries(presetUrls).map(([name, url]) => {
|
|
399
|
+
if (appearancePresets.includes(name)) {
|
|
400
|
+
return <link rel="stylesheet" href={url as string} key={name} />
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return null
|
|
404
|
+
})
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const cssVarSize = (
|
|
408
|
+
cssTokenPrefix: string,
|
|
409
|
+
size?: "lg" | "md" | "sm" | boolean,
|
|
410
|
+
defaultSize?: "lg" | "md" | "sm"
|
|
411
|
+
) => {
|
|
412
|
+
if (!size) {
|
|
413
|
+
return undefined
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (typeof size === "boolean") {
|
|
417
|
+
return cssVarSize(cssTokenPrefix, defaultSize)
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const sizes = {
|
|
421
|
+
lg: "large",
|
|
422
|
+
md: "medium",
|
|
423
|
+
sm: "small"
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const found = sizes[size]
|
|
427
|
+
|
|
428
|
+
if (!found) {
|
|
429
|
+
if (defaultSize) {
|
|
430
|
+
return cssVarSize(cssTokenPrefix, defaultSize)
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
return undefined
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
return `var(${cssTokenPrefix}-${found})`
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function UserFonts() {
|
|
440
|
+
const fontConfig = settings?.theme?.fonts
|
|
441
|
+
|
|
442
|
+
if (!fontConfig) {
|
|
443
|
+
return null
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const fontCss = generateFontCss(fontConfig)
|
|
447
|
+
|
|
448
|
+
if (!fontCss) {
|
|
449
|
+
return null
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
return <>
|
|
454
|
+
<style
|
|
455
|
+
data-fonts
|
|
456
|
+
dangerouslySetInnerHTML={{
|
|
457
|
+
__html: fontCss
|
|
458
|
+
}}
|
|
459
|
+
/>
|
|
460
|
+
</>
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
// TODO: its a fix for css layers - in the future better solution? is <style> order better?
|
|
465
|
+
function CssLayerFix() {
|
|
466
|
+
return <style>
|
|
467
|
+
@layer reset, defaults, defaultfix, components, fabric, templates, decorators, themes, themedecorator, presets, user, overrides;
|
|
468
|
+
</style>
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function generateFontCss(fontConfig: ThemeFont): string {
|
|
472
|
+
if (!fontConfig) return ''
|
|
473
|
+
|
|
474
|
+
// Handle single font configuration
|
|
475
|
+
if ('family' in fontConfig || 'src' in fontConfig) {
|
|
476
|
+
return generateSingleFontCss(fontConfig, 'body')
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// Handle separate body and coder fonts
|
|
480
|
+
const { body, coder } = fontConfig as { body: ThemeFont; coder: ThemeFont }
|
|
481
|
+
const bodyCss = body ? generateSingleFontCss(body, 'body') : ''
|
|
482
|
+
const coderCss = coder ? generateSingleFontCss(coder, 'coder') : ''
|
|
483
|
+
|
|
484
|
+
return [bodyCss, coderCss].filter(Boolean).join('\n\n')
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function generateSingleFontCss(font: ThemeFont, type: 'body' | 'coder'): string {
|
|
488
|
+
if (Array.isArray(font)) {
|
|
489
|
+
// Generate all font-face declarations
|
|
490
|
+
const fontFaces = font.map(f => generateFontFace(f)).join('\n\n')
|
|
491
|
+
|
|
492
|
+
// Use only the first font for CSS variables
|
|
493
|
+
const firstFont = font[0]
|
|
494
|
+
const cssVars = generateCssVars(firstFont, type)
|
|
495
|
+
|
|
496
|
+
return `${fontFaces}
|
|
497
|
+
|
|
498
|
+
@layer user {
|
|
499
|
+
:root {
|
|
500
|
+
${cssVars}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
`
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
if (!("src" in font)) {
|
|
507
|
+
return ''
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
if (!font.src) return ''
|
|
511
|
+
|
|
512
|
+
const fontFace = generateFontFace(font)
|
|
513
|
+
const cssVars = generateCssVars(font, type)
|
|
514
|
+
|
|
515
|
+
return `${fontFace}
|
|
516
|
+
|
|
517
|
+
@layer user {
|
|
518
|
+
:root {
|
|
519
|
+
${cssVars}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
`
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
function fontFormat(font: Font) {
|
|
526
|
+
switch (font.format) {
|
|
527
|
+
case "woff2":
|
|
528
|
+
return "woff2"
|
|
529
|
+
case "woff":
|
|
530
|
+
return "woff"
|
|
531
|
+
case "ttf":
|
|
532
|
+
return "ttf"
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
if (font.src?.endsWith(".woff2")) {
|
|
536
|
+
return "woff2"
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
if (font.src?.endsWith(".woff")) {
|
|
540
|
+
return "woff"
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
if (font.src?.endsWith(".ttf")) {
|
|
544
|
+
return "ttf"
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
return ""
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
function generateFontFace(font: Font): string {
|
|
551
|
+
const fontFamily = font.family || 'font'
|
|
552
|
+
const fontWeight = font.weight || '400'
|
|
553
|
+
const format = fontFormat(font)
|
|
554
|
+
|
|
555
|
+
if (format) {
|
|
556
|
+
return `@font-face {
|
|
557
|
+
font-family: '${fontFamily}';
|
|
558
|
+
font-weight: ${fontWeight};
|
|
559
|
+
src: url('${font.src}') format('${format}');
|
|
560
|
+
font-display: swap;
|
|
561
|
+
}`
|
|
562
|
+
} else {
|
|
563
|
+
return `@import url('${font.src}');`
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function generateCssVars(font: Font, type: 'body' | 'coder'): string {
|
|
568
|
+
const fontFamily = font.family || `font-${type}`
|
|
569
|
+
const fontWeight = font.weight || '400'
|
|
570
|
+
|
|
571
|
+
const cssVars = {
|
|
572
|
+
[`--font-${type}-family`]: fontFamily,
|
|
573
|
+
[`--font-${type}-weight`]: fontWeight,
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
return Object.entries(cssVars)
|
|
577
|
+
.map(([key, value]) => `${key}: ${value};`)
|
|
578
|
+
.join('\n ')
|
|
579
|
+
}
|
package/app/routes.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import {
|
|
2
|
+
index,
|
|
3
|
+
route,
|
|
4
|
+
} from "@react-router/dev/routes";
|
|
5
|
+
import {pathRoutes} from "./pathRoutes";
|
|
6
|
+
|
|
7
|
+
// Declare the global property type
|
|
8
|
+
declare global {
|
|
9
|
+
var __xydBasePath: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const basePath = globalThis.__xydBasePath
|
|
13
|
+
|
|
14
|
+
const navigation = __xydSettings?.navigation || {sidebar: []};
|
|
15
|
+
const docsRoutes = pathRoutes(basePath, navigation)
|
|
16
|
+
|
|
17
|
+
// TODO: !!!! if not routes found then '*' !!!
|
|
18
|
+
export const routes = [
|
|
19
|
+
...docsRoutes,
|
|
20
|
+
|
|
21
|
+
// TODO: in the future better sitemap + robots.txt
|
|
22
|
+
route("/sitemap.xml", "./sitemap.ts"),
|
|
23
|
+
route("/robots.txt", "./robots.ts"),
|
|
24
|
+
route(
|
|
25
|
+
"/.well-known/appspecific/com.chrome.devtools.json",
|
|
26
|
+
"./debug-null.tsx",
|
|
27
|
+
),
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
if (globalThis.__xydStaticFiles?.length) {
|
|
31
|
+
routes.push(route("/public/*", "./public.ts"))
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export default routes
|
|
35
|
+
|