@uxfront/layer-docs 0.4.0 → 0.5.0
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/README.md +31 -220
- package/app/app.config.ts +8 -92
- package/app/components/content/FrameworkSwitcher.vue +66 -40
- package/app/components/docs/DocsAsideLeftTop.vue +9 -15
- package/app/components/docs/DocsFrameworkSelect.vue +4 -7
- package/app/composables/useFramework.ts +30 -38
- package/nuxt.config.ts +14 -170
- package/package.json +10 -77
- package/CHANGELOG.md +0 -180
- package/LICENSE +0 -21
- package/app/app.vue +0 -138
- package/app/assets/css/main.css +0 -15
- package/app/components/IconMenuToggle.vue +0 -92
- package/app/components/LanguageSelect.vue +0 -73
- package/app/components/MorphingGradientBackground.vue +0 -261
- package/app/components/OgImage/OgImageDocs.satori.vue +0 -40
- package/app/components/OgImage/OgImageLanding.satori.vue +0 -41
- package/app/components/app/AppFooter.vue +0 -13
- package/app/components/app/AppFooterCenter.vue +0 -17
- package/app/components/app/AppFooterLeft.vue +0 -21
- package/app/components/app/AppFooterRight.vue +0 -33
- package/app/components/app/AppHeader.vue +0 -123
- package/app/components/app/AppHeaderAttribution.vue +0 -45
- package/app/components/app/AppHeaderBody.vue +0 -14
- package/app/components/app/AppHeaderCTA.vue +0 -31
- package/app/components/app/AppHeaderCenter.vue +0 -10
- package/app/components/app/AppHeaderLogo.vue +0 -16
- package/app/components/app/AppOgDecoration.vue +0 -27
- package/app/components/app/AppOgLogo.vue +0 -19
- package/app/components/app/AppSearch.vue +0 -59
- package/app/components/app/AppSubHeader.vue +0 -21
- package/app/components/content/BrowserFrame.vue +0 -28
- package/app/components/content/GradientPageHero.vue +0 -35
- package/app/components/content/StorybookEmbed.vue +0 -160
- package/app/components/content/Video.vue +0 -103
- package/app/components/docs/DocsAsideLeftBody.vue +0 -20
- package/app/components/docs/DocsAsideRightBottom.vue +0 -15
- package/app/components/docs/DocsPageHeaderLinks.vue +0 -75
- package/app/composables/useDocsSections.ts +0 -57
- package/app/composables/useDocusI18n.ts +0 -49
- package/app/constants/sections.ts +0 -25
- package/app/error.vue +0 -140
- package/app/layouts/default.vue +0 -24
- package/app/pages/[[lang]]/[...slug].vue +0 -58
- package/app/pages/[[lang]]/docs/[section]/[...slug].vue +0 -180
- package/app/plugins/i18n.ts +0 -21
- package/app/plugins/posthog.client.ts +0 -56
- package/app/types/non-route-categories.ts +0 -12
- package/app/utils/flattenNavigation.ts +0 -22
- package/app/utils/foldNonRouteCategories.ts +0 -47
- package/app/utils/prerender.ts +0 -9
- package/app/utils/storybookEmbed.test.ts +0 -98
- package/app/utils/storybookEmbed.ts +0 -93
- package/i18n/locales/ar.json +0 -24
- package/i18n/locales/be.json +0 -24
- package/i18n/locales/bn.json +0 -24
- package/i18n/locales/ca.json +0 -24
- package/i18n/locales/ckb.json +0 -24
- package/i18n/locales/cs.json +0 -24
- package/i18n/locales/da.json +0 -24
- package/i18n/locales/de.json +0 -24
- package/i18n/locales/el.json +0 -24
- package/i18n/locales/en.json +0 -24
- package/i18n/locales/et.json +0 -24
- package/i18n/locales/fr.json +0 -24
- package/i18n/locales/he.json +0 -24
- package/i18n/locales/hi.json +0 -24
- package/i18n/locales/hy.json +0 -24
- package/i18n/locales/it.json +0 -24
- package/i18n/locales/ja.json +0 -24
- package/i18n/locales/kk.json +0 -24
- package/i18n/locales/km.json +0 -24
- package/i18n/locales/ko.json +0 -24
- package/i18n/locales/ky.json +0 -24
- package/i18n/locales/lb.json +0 -24
- package/i18n/locales/ms.json +0 -24
- package/i18n/locales/nb.json +0 -24
- package/i18n/locales/pl.json +0 -24
- package/i18n/locales/ru.json +0 -24
- package/i18n/locales/sl.json +0 -24
- package/i18n/locales/sv.json +0 -24
- package/i18n/locales/uk.json +0 -24
- package/i18n/locales/ur.json +0 -24
- package/i18n/locales/vi.json +0 -24
- package/modules/config.ts +0 -144
- package/modules/optimizeDeps.ts +0 -45
- package/modules/routing.ts +0 -20
- package/nuxt.schema.ts +0 -374
- package/server/plugins/llms-redirect.ts +0 -60
- package/server/routes/raw/[...slug].md.get.ts +0 -74
- package/storybook/index.test.ts +0 -110
- package/storybook/index.ts +0 -362
- package/test/brand-palette.ts +0 -235
- package/test/no-brand-leakage.test.ts +0 -124
- package/tsconfig.json +0 -17
- package/utils/accent.ts +0 -80
- package/utils/content.ts +0 -193
- package/utils/git.ts +0 -114
- package/utils/meta.ts +0 -28
package/modules/config.ts
DELETED
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
import { createResolver, defineNuxtModule } from "@nuxt/kit";
|
|
2
|
-
import { defu } from "defu";
|
|
3
|
-
import { existsSync } from "node:fs";
|
|
4
|
-
import { join } from "node:path";
|
|
5
|
-
import { inferSiteURL, getPackageJsonMetadata } from "../utils/meta";
|
|
6
|
-
import { getGitBranch, getGitEnv, getLocalGitInfo } from "../utils/git";
|
|
7
|
-
import { resolveBrandAccent } from "../utils/accent";
|
|
8
|
-
|
|
9
|
-
export default defineNuxtModule({
|
|
10
|
-
meta: {
|
|
11
|
-
name: "config",
|
|
12
|
-
},
|
|
13
|
-
async setup(_options, nuxt) {
|
|
14
|
-
const dir = nuxt.options.rootDir;
|
|
15
|
-
const url = inferSiteURL();
|
|
16
|
-
const meta = await getPackageJsonMetadata(dir);
|
|
17
|
-
const gitInfo = (await getLocalGitInfo(dir)) || getGitEnv();
|
|
18
|
-
const site = nuxt.options.site;
|
|
19
|
-
const siteName =
|
|
20
|
-
(typeof site === "object" ? site.name : undefined) || meta.name || gitInfo?.name || "";
|
|
21
|
-
|
|
22
|
-
nuxt.options.llms = defu(nuxt.options.llms, {
|
|
23
|
-
domain: url,
|
|
24
|
-
title: siteName,
|
|
25
|
-
description: meta.description || "",
|
|
26
|
-
full: {
|
|
27
|
-
title: siteName,
|
|
28
|
-
description: meta.description || "",
|
|
29
|
-
},
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
nuxt.options.site = defu(nuxt.options.site, {
|
|
33
|
-
url,
|
|
34
|
-
name: siteName,
|
|
35
|
-
debug: false,
|
|
36
|
-
}) as typeof nuxt.options.site;
|
|
37
|
-
|
|
38
|
-
nuxt.options.appConfig.header = defu(nuxt.options.appConfig.header, {
|
|
39
|
-
title: siteName,
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
nuxt.options.appConfig.seo = defu(nuxt.options.appConfig.seo, {
|
|
43
|
-
titleTemplate: `%s - ${siteName}`,
|
|
44
|
-
title: siteName,
|
|
45
|
-
description: meta.description || "",
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
nuxt.options.appConfig.github = defu(nuxt.options.appConfig.github, {
|
|
49
|
-
owner: gitInfo?.owner,
|
|
50
|
-
name: gitInfo?.name,
|
|
51
|
-
url: gitInfo?.url,
|
|
52
|
-
branch: getGitBranch(),
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
/*
|
|
56
|
-
** OG IMAGE ACCENT
|
|
57
|
-
**
|
|
58
|
-
** Bind the card accent to the same token the site uses — see
|
|
59
|
-
** `utils/accent.ts` for why it has to be a literal by the time satori
|
|
60
|
-
** runs. Written as a default: a consumer that sets `ogImage.accent` in its
|
|
61
|
-
** own `app.config.ts` still wins on the layer merge, and one that sets
|
|
62
|
-
** nothing gets a branded card with no configuration at all.
|
|
63
|
-
*/
|
|
64
|
-
const { accent, reason } = resolveBrandAccent({
|
|
65
|
-
cssEntries: nuxt.options.css ?? [],
|
|
66
|
-
rootDir: dir,
|
|
67
|
-
});
|
|
68
|
-
if (reason) {
|
|
69
|
-
console.warn(
|
|
70
|
-
`[Docus] OG image accent falls back to ${accent}: ${reason}. ` +
|
|
71
|
-
`Set \`ogImage.accent\` in app.config.ts to silence this.`,
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
nuxt.options.appConfig.ogImage = defu(nuxt.options.appConfig.ogImage, { accent });
|
|
75
|
-
|
|
76
|
-
/*
|
|
77
|
-
** I18N
|
|
78
|
-
*/
|
|
79
|
-
if (nuxt.options.i18n && nuxt.options.i18n.locales) {
|
|
80
|
-
const { resolve } = createResolver(import.meta.url);
|
|
81
|
-
|
|
82
|
-
// Filter locales to only include existing ones
|
|
83
|
-
const filteredLocales = nuxt.options.i18n.locales.filter((locale) => {
|
|
84
|
-
const localeCode = typeof locale === "string" ? locale : locale.code;
|
|
85
|
-
|
|
86
|
-
// Check for JSON locale file
|
|
87
|
-
const localeFilePath = resolve("../i18n/locales", `${localeCode}.json`);
|
|
88
|
-
const hasLocaleFile = existsSync(localeFilePath);
|
|
89
|
-
|
|
90
|
-
// Check for content folder
|
|
91
|
-
const contentPath = join(nuxt.options.rootDir, "content", localeCode);
|
|
92
|
-
const hasContentFolder = existsSync(contentPath);
|
|
93
|
-
|
|
94
|
-
if (!hasLocaleFile) {
|
|
95
|
-
console.warn(
|
|
96
|
-
`[Docus] Locale file not found: ${localeCode}.json - skipping locale "${localeCode}"`,
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
if (!hasContentFolder) {
|
|
101
|
-
console.warn(
|
|
102
|
-
`[Docus] Content folder not found: content/${localeCode}/ - skipping locale "${localeCode}"`,
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return hasLocaleFile && hasContentFolder;
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
// Override strategy to prefix
|
|
110
|
-
nuxt.options.i18n = {
|
|
111
|
-
...nuxt.options.i18n,
|
|
112
|
-
strategy: "prefix",
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
// Expose filtered locales
|
|
116
|
-
nuxt.options.runtimeConfig.public.docus = {
|
|
117
|
-
filteredLocales,
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
nuxt.hook("i18n:registerModule", (register) => {
|
|
121
|
-
const langDir = resolve("../i18n/locales");
|
|
122
|
-
|
|
123
|
-
const locales = filteredLocales?.map((locale) => {
|
|
124
|
-
return typeof locale === "string"
|
|
125
|
-
? {
|
|
126
|
-
code: locale,
|
|
127
|
-
name: locale,
|
|
128
|
-
file: `${locale}.json`,
|
|
129
|
-
}
|
|
130
|
-
: {
|
|
131
|
-
code: locale.code,
|
|
132
|
-
name: locale.name || locale.code,
|
|
133
|
-
file: `${locale.code}.json`,
|
|
134
|
-
};
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
register({
|
|
138
|
-
langDir,
|
|
139
|
-
locales,
|
|
140
|
-
});
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
},
|
|
144
|
-
});
|
package/modules/optimizeDeps.ts
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { createRequire } from "node:module";
|
|
2
|
-
import { defineNuxtModule, extendViteConfig } from "@nuxt/kit";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Vite prebundle hints for this layer's own dependencies.
|
|
6
|
-
*
|
|
7
|
-
* Every id below is a dependency the layer pulls in (`@vueuse/core`, `motion-v`
|
|
8
|
-
* directly; `posthog-js` via the analytics plugin; `slugify` transitively
|
|
9
|
-
* through `@nuxt/content`). Without the hint Vite discovers them mid-request on
|
|
10
|
-
* first dev load and triggers a full-reload optimise pass. The hints belong with
|
|
11
|
-
* the deps, so they ship here rather than in each consumer's config.
|
|
12
|
-
*/
|
|
13
|
-
export default defineNuxtModule({
|
|
14
|
-
meta: {
|
|
15
|
-
name: "optimize-deps",
|
|
16
|
-
},
|
|
17
|
-
setup(_options, nuxt) {
|
|
18
|
-
const include = ["@nuxt/content > slugify", "@vueuse/core", "motion-v"];
|
|
19
|
-
|
|
20
|
-
// `posthog-js` is an OPTIONAL peer. Naming an uninstalled package in
|
|
21
|
-
// `optimizeDeps.include` is a hard dev-server error, so hint it only when
|
|
22
|
-
// the consumer actually has it.
|
|
23
|
-
if (canResolve("posthog-js", nuxt.options.rootDir)) {
|
|
24
|
-
include.push("posthog-js");
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
extendViteConfig((config) => {
|
|
28
|
-
config.optimizeDeps ||= {};
|
|
29
|
-
config.optimizeDeps.include ||= [];
|
|
30
|
-
config.optimizeDeps.include.push(...include);
|
|
31
|
-
});
|
|
32
|
-
},
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
function canResolve(id: string, fromDir: string): boolean {
|
|
36
|
-
for (const parent of [`${fromDir}/`, import.meta.url]) {
|
|
37
|
-
try {
|
|
38
|
-
createRequire(parent).resolve(id);
|
|
39
|
-
return true;
|
|
40
|
-
} catch {
|
|
41
|
-
// try the next resolution root
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
return false;
|
|
45
|
-
}
|
package/modules/routing.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { defineNuxtModule, createResolver } from "@nuxt/kit";
|
|
2
|
-
|
|
3
|
-
export default defineNuxtModule({
|
|
4
|
-
meta: {
|
|
5
|
-
name: "routing",
|
|
6
|
-
},
|
|
7
|
-
async setup(_options, nuxt) {
|
|
8
|
-
const { resolve } = createResolver(import.meta.url);
|
|
9
|
-
|
|
10
|
-
// Ensure useDocusI18n is available in the app
|
|
11
|
-
nuxt.hook("imports:extend", (imports) => {
|
|
12
|
-
if (imports.some((i) => i.name === "useDocusI18n")) return;
|
|
13
|
-
|
|
14
|
-
imports.push({
|
|
15
|
-
name: "useDocusI18n",
|
|
16
|
-
from: resolve("../app/composables/useDocusI18n"),
|
|
17
|
-
});
|
|
18
|
-
});
|
|
19
|
-
},
|
|
20
|
-
});
|
package/nuxt.schema.ts
DELETED
|
@@ -1,374 +0,0 @@
|
|
|
1
|
-
import { field, group } from "@nuxt/content/preview";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Nuxt Content Studio preview schema for this layer's `app.config.ts` surface.
|
|
5
|
-
*
|
|
6
|
-
* Every field below describes a key the layer itself declares and reads, so the
|
|
7
|
-
* schema is owned here rather than restated in each consumer. Nuxt merges
|
|
8
|
-
* schemas across layers, so a consumer declares only the keys it adds on top.
|
|
9
|
-
*
|
|
10
|
-
* This file is load-bearing beyond Studio: once a schema exists, Nuxt derives
|
|
11
|
-
* the generated `AppConfig` type from it, and any key the layer reads but the
|
|
12
|
-
* schema omits collapses to `{}` and fails typecheck at the read site. Adding a
|
|
13
|
-
* key to `app.config.ts` or reading a new one from a component means adding it
|
|
14
|
-
* here in the same change.
|
|
15
|
-
*
|
|
16
|
-
* Brand-free by construction: the defaults are Nuxt UI palette tokens and
|
|
17
|
-
* generic placeholder strings, never a product's values.
|
|
18
|
-
*/
|
|
19
|
-
export default defineNuxtSchema({
|
|
20
|
-
appConfig: {
|
|
21
|
-
ui: group({
|
|
22
|
-
title: "UI",
|
|
23
|
-
description: "UI Customization.",
|
|
24
|
-
icon: "i-lucide-palette",
|
|
25
|
-
fields: {
|
|
26
|
-
colors: group({
|
|
27
|
-
title: "Colors",
|
|
28
|
-
description: "Manage main colors of your application",
|
|
29
|
-
icon: "i-lucide-palette",
|
|
30
|
-
fields: {
|
|
31
|
-
primary: field({
|
|
32
|
-
type: "string",
|
|
33
|
-
title: "Primary",
|
|
34
|
-
description: "Primary color of your UI.",
|
|
35
|
-
icon: "i-lucide-palette",
|
|
36
|
-
default: "green",
|
|
37
|
-
required: [
|
|
38
|
-
"red",
|
|
39
|
-
"orange",
|
|
40
|
-
"amber",
|
|
41
|
-
"yellow",
|
|
42
|
-
"lime",
|
|
43
|
-
"green",
|
|
44
|
-
"emerald",
|
|
45
|
-
"teal",
|
|
46
|
-
"cyan",
|
|
47
|
-
"sky",
|
|
48
|
-
"blue",
|
|
49
|
-
"indigo",
|
|
50
|
-
"violet",
|
|
51
|
-
"purple",
|
|
52
|
-
"fuchsia",
|
|
53
|
-
"pink",
|
|
54
|
-
"rose",
|
|
55
|
-
],
|
|
56
|
-
}),
|
|
57
|
-
neutral: field({
|
|
58
|
-
type: "string",
|
|
59
|
-
title: "Neutral",
|
|
60
|
-
description: "Neutral color of your UI.",
|
|
61
|
-
icon: "i-lucide-palette",
|
|
62
|
-
default: "slate",
|
|
63
|
-
required: ["slate", "gray", "zinc", "neutral", "stone"],
|
|
64
|
-
}),
|
|
65
|
-
},
|
|
66
|
-
}),
|
|
67
|
-
icons: group({
|
|
68
|
-
title: "Icons",
|
|
69
|
-
description: "Manage icons used in the application.",
|
|
70
|
-
icon: "i-lucide-settings",
|
|
71
|
-
fields: {
|
|
72
|
-
search: field({
|
|
73
|
-
type: "icon",
|
|
74
|
-
title: "Search Bar",
|
|
75
|
-
description: "Icon to display in the search bar.",
|
|
76
|
-
icon: "i-lucide-search",
|
|
77
|
-
default: "i-lucide-search",
|
|
78
|
-
}),
|
|
79
|
-
dark: field({
|
|
80
|
-
type: "icon",
|
|
81
|
-
title: "Dark mode",
|
|
82
|
-
description: "Icon of color mode button for dark mode.",
|
|
83
|
-
icon: "i-lucide-moon",
|
|
84
|
-
default: "i-lucide-moon",
|
|
85
|
-
}),
|
|
86
|
-
light: field({
|
|
87
|
-
type: "icon",
|
|
88
|
-
title: "Light mode",
|
|
89
|
-
description: "Icon of color mode button for light mode.",
|
|
90
|
-
icon: "i-lucide-sun",
|
|
91
|
-
default: "i-lucide-sun",
|
|
92
|
-
}),
|
|
93
|
-
external: field({
|
|
94
|
-
type: "icon",
|
|
95
|
-
title: "External Link",
|
|
96
|
-
description: "Icon for external link.",
|
|
97
|
-
icon: "i-lucide-external-link",
|
|
98
|
-
default: "i-lucide-external-link",
|
|
99
|
-
}),
|
|
100
|
-
chevron: field({
|
|
101
|
-
type: "icon",
|
|
102
|
-
title: "Chevron",
|
|
103
|
-
description: "Icon for chevron.",
|
|
104
|
-
icon: "i-lucide-chevron-down",
|
|
105
|
-
default: "i-lucide-chevron-down",
|
|
106
|
-
}),
|
|
107
|
-
hash: field({
|
|
108
|
-
type: "icon",
|
|
109
|
-
title: "Hash",
|
|
110
|
-
description: "Icon for hash anchors.",
|
|
111
|
-
icon: "i-lucide-hash",
|
|
112
|
-
default: "i-lucide-hash",
|
|
113
|
-
}),
|
|
114
|
-
},
|
|
115
|
-
}),
|
|
116
|
-
},
|
|
117
|
-
}),
|
|
118
|
-
seo: group({
|
|
119
|
-
title: "SEO",
|
|
120
|
-
description: "SEO configuration.",
|
|
121
|
-
icon: "i-lucide-search",
|
|
122
|
-
fields: {
|
|
123
|
-
title: field({
|
|
124
|
-
type: "string",
|
|
125
|
-
title: "Title",
|
|
126
|
-
description: "Title to display in the header.",
|
|
127
|
-
icon: "i-lucide-type",
|
|
128
|
-
default: "",
|
|
129
|
-
}),
|
|
130
|
-
description: field({
|
|
131
|
-
type: "string",
|
|
132
|
-
title: "Description",
|
|
133
|
-
description: "Description to display in the header.",
|
|
134
|
-
icon: "i-lucide-type",
|
|
135
|
-
default: "",
|
|
136
|
-
}),
|
|
137
|
-
},
|
|
138
|
-
}),
|
|
139
|
-
header: group({
|
|
140
|
-
title: "Header",
|
|
141
|
-
description: "Header configuration.",
|
|
142
|
-
icon: "i-lucide-layout",
|
|
143
|
-
fields: {
|
|
144
|
-
title: field({
|
|
145
|
-
type: "string",
|
|
146
|
-
title: "Title",
|
|
147
|
-
description: "Title to display in the header.",
|
|
148
|
-
icon: "i-lucide-type",
|
|
149
|
-
default: "",
|
|
150
|
-
}),
|
|
151
|
-
logo: group({
|
|
152
|
-
title: "Logo",
|
|
153
|
-
description: "Header logo configuration.",
|
|
154
|
-
icon: "i-lucide-image",
|
|
155
|
-
fields: {
|
|
156
|
-
light: field({
|
|
157
|
-
type: "media",
|
|
158
|
-
title: "Light Mode Logo",
|
|
159
|
-
description: "Pick an image from your gallery.",
|
|
160
|
-
icon: "i-lucide-sun",
|
|
161
|
-
default: "",
|
|
162
|
-
}),
|
|
163
|
-
dark: field({
|
|
164
|
-
type: "media",
|
|
165
|
-
title: "Dark Mode Logo",
|
|
166
|
-
description: "Pick an image from your gallery.",
|
|
167
|
-
icon: "i-lucide-moon",
|
|
168
|
-
default: "",
|
|
169
|
-
}),
|
|
170
|
-
alt: field({
|
|
171
|
-
type: "string",
|
|
172
|
-
title: "Alt",
|
|
173
|
-
description: "Alt to display for accessibility.",
|
|
174
|
-
icon: "i-lucide-text",
|
|
175
|
-
default: "",
|
|
176
|
-
}),
|
|
177
|
-
},
|
|
178
|
-
}),
|
|
179
|
-
links: field({
|
|
180
|
-
type: "array",
|
|
181
|
-
title: "Links",
|
|
182
|
-
description: "Navigation links to display in the header.",
|
|
183
|
-
icon: "i-lucide-link",
|
|
184
|
-
default: [],
|
|
185
|
-
}),
|
|
186
|
-
attribution: group({
|
|
187
|
-
title: "Attribution",
|
|
188
|
-
description: "Brand attribution rendered beside the header wordmark.",
|
|
189
|
-
icon: "i-lucide-badge",
|
|
190
|
-
fields: {
|
|
191
|
-
prefix: field({
|
|
192
|
-
type: "string",
|
|
193
|
-
title: "Prefix",
|
|
194
|
-
description: 'Unlinked word before the label, e.g. "by".',
|
|
195
|
-
icon: "i-lucide-text",
|
|
196
|
-
default: "",
|
|
197
|
-
}),
|
|
198
|
-
label: field({
|
|
199
|
-
type: "string",
|
|
200
|
-
title: "Label",
|
|
201
|
-
description: "Linked attribution text. Empty renders nothing.",
|
|
202
|
-
icon: "i-lucide-type",
|
|
203
|
-
default: "",
|
|
204
|
-
}),
|
|
205
|
-
to: field({
|
|
206
|
-
type: "string",
|
|
207
|
-
title: "URL",
|
|
208
|
-
description: "Destination the label links to.",
|
|
209
|
-
icon: "i-lucide-link",
|
|
210
|
-
default: "",
|
|
211
|
-
}),
|
|
212
|
-
},
|
|
213
|
-
}),
|
|
214
|
-
},
|
|
215
|
-
}),
|
|
216
|
-
footer: group({
|
|
217
|
-
title: "Footer",
|
|
218
|
-
description: "Footer configuration.",
|
|
219
|
-
icon: "i-lucide-panel-bottom",
|
|
220
|
-
fields: {
|
|
221
|
-
credits: field({
|
|
222
|
-
type: "string",
|
|
223
|
-
title: "Credits",
|
|
224
|
-
description: "Copyright line. Falls back to the header title.",
|
|
225
|
-
icon: "i-lucide-copyright",
|
|
226
|
-
default: "",
|
|
227
|
-
}),
|
|
228
|
-
links: field({
|
|
229
|
-
type: "array",
|
|
230
|
-
title: "Links",
|
|
231
|
-
description: "Navigation links to display in the footer.",
|
|
232
|
-
icon: "i-lucide-link",
|
|
233
|
-
default: [],
|
|
234
|
-
}),
|
|
235
|
-
},
|
|
236
|
-
}),
|
|
237
|
-
socials: field({
|
|
238
|
-
type: "object",
|
|
239
|
-
title: "Social Networks",
|
|
240
|
-
description: "Social links configuration.",
|
|
241
|
-
icon: "i-lucide-network",
|
|
242
|
-
default: {},
|
|
243
|
-
}),
|
|
244
|
-
toc: group({
|
|
245
|
-
title: "Table of contents",
|
|
246
|
-
description: "TOC configuration.",
|
|
247
|
-
icon: "i-lucide-list",
|
|
248
|
-
fields: {
|
|
249
|
-
title: field({
|
|
250
|
-
type: "string",
|
|
251
|
-
title: "Title",
|
|
252
|
-
description: "Title of the table of contents.",
|
|
253
|
-
icon: "i-lucide-heading",
|
|
254
|
-
default: "On this page",
|
|
255
|
-
}),
|
|
256
|
-
bottom: group({
|
|
257
|
-
title: "Bottom",
|
|
258
|
-
description: "Bottom section of the table of contents.",
|
|
259
|
-
icon: "i-lucide-list",
|
|
260
|
-
fields: {
|
|
261
|
-
title: field({
|
|
262
|
-
type: "string",
|
|
263
|
-
title: "Title",
|
|
264
|
-
description: "Title of the bottom section.",
|
|
265
|
-
icon: "i-lucide-heading",
|
|
266
|
-
default: "Community",
|
|
267
|
-
}),
|
|
268
|
-
links: field({
|
|
269
|
-
type: "array",
|
|
270
|
-
title: "Links",
|
|
271
|
-
description: "Links to display in the bottom section.",
|
|
272
|
-
icon: "i-lucide-link",
|
|
273
|
-
default: [],
|
|
274
|
-
}),
|
|
275
|
-
},
|
|
276
|
-
}),
|
|
277
|
-
},
|
|
278
|
-
}),
|
|
279
|
-
analytics: group({
|
|
280
|
-
title: "Analytics",
|
|
281
|
-
description: "PostHog analytics plugin (production only, requires a runtime key).",
|
|
282
|
-
icon: "i-lucide-chart-line",
|
|
283
|
-
fields: {
|
|
284
|
-
enabled: field({
|
|
285
|
-
type: "boolean",
|
|
286
|
-
title: "Enabled",
|
|
287
|
-
description: "Set to false to opt out of the analytics plugin.",
|
|
288
|
-
icon: "i-lucide-toggle-right",
|
|
289
|
-
default: true,
|
|
290
|
-
}),
|
|
291
|
-
},
|
|
292
|
-
}),
|
|
293
|
-
i18nRedirect: group({
|
|
294
|
-
title: "Locale redirect",
|
|
295
|
-
description: "Redirect `/` to `/{locale}` (only fires when i18n is configured).",
|
|
296
|
-
icon: "i-lucide-languages",
|
|
297
|
-
fields: {
|
|
298
|
-
enabled: field({
|
|
299
|
-
type: "boolean",
|
|
300
|
-
title: "Enabled",
|
|
301
|
-
description: "Set to false to opt out of the locale redirect.",
|
|
302
|
-
icon: "i-lucide-toggle-right",
|
|
303
|
-
default: true,
|
|
304
|
-
}),
|
|
305
|
-
},
|
|
306
|
-
}),
|
|
307
|
-
nonRouteCategories: field({
|
|
308
|
-
type: "object",
|
|
309
|
-
title: "Non-route categories",
|
|
310
|
-
description:
|
|
311
|
-
"Navigation folders that group pages without owning a route, keyed by directory name.",
|
|
312
|
-
icon: "i-lucide-folder-tree",
|
|
313
|
-
default: {},
|
|
314
|
-
}),
|
|
315
|
-
docsTheme: group({
|
|
316
|
-
title: "Docs theme",
|
|
317
|
-
description: "Documentation-specific theme options.",
|
|
318
|
-
icon: "i-lucide-book-open",
|
|
319
|
-
fields: {
|
|
320
|
-
frameworks: field({
|
|
321
|
-
type: "array",
|
|
322
|
-
title: "Frameworks",
|
|
323
|
-
description:
|
|
324
|
-
"Frameworks offered by the sidebar select and in-content switcher, in display order. Empty by default — `defu` concatenates arrays across layers, so a non-empty default here could only be appended to, never overridden.",
|
|
325
|
-
icon: "i-lucide-layers",
|
|
326
|
-
default: [],
|
|
327
|
-
}),
|
|
328
|
-
},
|
|
329
|
-
}),
|
|
330
|
-
ogImage: group({
|
|
331
|
-
title: "OG image",
|
|
332
|
-
description: "Social share card options.",
|
|
333
|
-
icon: "i-lucide-image",
|
|
334
|
-
fields: {
|
|
335
|
-
accent: field({
|
|
336
|
-
type: "string",
|
|
337
|
-
title: "Accent",
|
|
338
|
-
description:
|
|
339
|
-
"Accent colour for the OG card headline, as a literal CSS colour — satori has no custom properties, so `var(--ui-primary)` cannot be used here. Left unset, it is resolved at build time by following `--ui-primary` to the `--color-<scale>` literal it aliases in the registered CSS, so most consumers never set it.",
|
|
340
|
-
icon: "i-lucide-palette",
|
|
341
|
-
default: "",
|
|
342
|
-
}),
|
|
343
|
-
},
|
|
344
|
-
}),
|
|
345
|
-
github: group({
|
|
346
|
-
title: "GitHub",
|
|
347
|
-
description: "GitHub configuration.",
|
|
348
|
-
icon: "i-simple-icons-github",
|
|
349
|
-
fields: {
|
|
350
|
-
url: field({
|
|
351
|
-
type: "string",
|
|
352
|
-
title: "URL",
|
|
353
|
-
description: "GitHub URL.",
|
|
354
|
-
icon: "i-simple-icons-github",
|
|
355
|
-
default: "",
|
|
356
|
-
}),
|
|
357
|
-
branch: field({
|
|
358
|
-
type: "string",
|
|
359
|
-
title: "Branch",
|
|
360
|
-
description: "GitHub branch.",
|
|
361
|
-
icon: "i-lucide-git-branch",
|
|
362
|
-
default: "main",
|
|
363
|
-
}),
|
|
364
|
-
rootDir: field({
|
|
365
|
-
type: "string",
|
|
366
|
-
title: "Root Directory",
|
|
367
|
-
description: "Root directory of the GitHub repository.",
|
|
368
|
-
icon: "i-lucide-folder",
|
|
369
|
-
default: "",
|
|
370
|
-
}),
|
|
371
|
-
},
|
|
372
|
-
}),
|
|
373
|
-
},
|
|
374
|
-
});
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import type { H3Event } from "h3";
|
|
2
|
-
import { withoutTrailingSlash } from "ufo";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Serve `/llms.txt` from the homepage for AI agents and crawlers.
|
|
6
|
-
*
|
|
7
|
-
* Human browsers still get the rendered homepage; only clients that ask for
|
|
8
|
-
* markdown (`Accept: text/markdown`) or identify as `curl` are redirected to
|
|
9
|
-
* the `nuxt-llms` entry point. This mirrors Docus's agent-friendly behaviour
|
|
10
|
-
* but works across Nitro presets (we deploy the node-server output), where the
|
|
11
|
-
* upstream Vercel-config rewrite does not apply.
|
|
12
|
-
*
|
|
13
|
-
* It runs on the `request` hook rather than as route middleware so it fires
|
|
14
|
-
* before Nitro's public-asset handler serves the prerendered homepage — route
|
|
15
|
-
* middleware would never see a request for a statically generated page.
|
|
16
|
-
*/
|
|
17
|
-
export default defineNitroPlugin((nitroApp) => {
|
|
18
|
-
nitroApp.hooks.hook("request", (event) => {
|
|
19
|
-
if (event.method !== "GET" && event.method !== "HEAD") {
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const path = withoutTrailingSlash(event.path.split("?")[0]) || "/";
|
|
24
|
-
if (!isRootPath(event, path)) {
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const accept = getHeader(event, "accept") ?? "";
|
|
29
|
-
const userAgent = getHeader(event, "user-agent") ?? "";
|
|
30
|
-
|
|
31
|
-
const wantsMarkdown = accept.includes("text/markdown");
|
|
32
|
-
const isCurl = /^curl\//i.test(userAgent);
|
|
33
|
-
|
|
34
|
-
if (wantsMarkdown || isCurl) {
|
|
35
|
-
return sendRedirect(event, "/llms.txt", 302);
|
|
36
|
-
}
|
|
37
|
-
});
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* True for `/` and, when i18n is enabled, each locale homepage (e.g. `/en`).
|
|
42
|
-
*/
|
|
43
|
-
function isRootPath(event: H3Event, path: string) {
|
|
44
|
-
if (path === "/") {
|
|
45
|
-
return true;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const i18n = useRuntimeConfig(event).public.i18n as
|
|
49
|
-
| { locales?: (string | { code: string })[] }
|
|
50
|
-
| undefined;
|
|
51
|
-
|
|
52
|
-
if (!i18n?.locales) {
|
|
53
|
-
return false;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return i18n.locales.some((locale) => {
|
|
57
|
-
const code = typeof locale === "string" ? locale : locale.code;
|
|
58
|
-
return path === `/${code}`;
|
|
59
|
-
});
|
|
60
|
-
}
|