diff --git a/docs/angular/astro.config.ts b/docs/angular/astro.config.ts
index 71b2fec482..d11837e0c2 100644
--- a/docs/angular/astro.config.ts
+++ b/docs/angular/astro.config.ts
@@ -2,7 +2,7 @@
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { createDocsSite, type DocsMode } from 'docs-template/integration';
-import { IGDOCS_PLATFORMS, type NavLang } from 'docs-template/platform';
+import { IGDOCS_PLATFORMS, IGDOCS_HOSTS, type NavLang } from 'docs-template/platform';
import { generateGridTopics } from './src/scripts/generate-grids.mjs';
import mdx from '@astrojs/mdx';
@@ -29,9 +29,10 @@ if (docsEnv !== 'development' && docsEnv !== 'staging' && docsEnv !== 'productio
const mode: DocsMode = docsEnv;
-// ── Site URL — varies by build mode ─────────────────────────────────────────
-const PROD_HOST = 'https://www.infragistics.com';
-const STAGING_HOST = 'https://staging.infragistics.com';
+// ── Site URL — varies by build mode and language ────────────────────────────
+// Hosts source: legacy igniteui-docfx environment.json (per-language).
+const PROD_HOST = IGDOCS_HOSTS[docsLang].production;
+const STAGING_HOST = IGDOCS_HOSTS[docsLang].staging;
const platformKey = docsLang === 'jp' ? 'AngularJP' : 'Angular';
const { base } = IGDOCS_PLATFORMS[platformKey];
@@ -56,6 +57,10 @@ export default createDocsSite({
platform: 'angular',
navLang: docsLang,
mode,
+ build: {
+ format: 'file'
+ },
+ trailingSlash: 'never',
productLinks: Object.values(IGDOCS_PLATFORMS)
.filter(p => p.lang === docsLang)
.map(({ label, key, base: b }) => ({
@@ -66,6 +71,7 @@ export default createDocsSite({
source: {
tocPath: `${componentsDocsDir}/toc.json`,
docsDir: componentsDocsDir,
+ slugPrefix: 'components',
},
sidebar: { exclude: [/^internal\//] },
starlight: {
diff --git a/docs/angular/public/web.config b/docs/angular/public/web.config
index 2485d7671f..09b4aef1f9 100644
--- a/docs/angular/public/web.config
+++ b/docs/angular/public/web.config
@@ -760,7 +760,7 @@
-
+
@@ -769,6 +769,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/angular/src/content.config.ts b/docs/angular/src/content.config.ts
index 41c43926c0..63c6949b96 100644
--- a/docs/angular/src/content.config.ts
+++ b/docs/angular/src/content.config.ts
@@ -13,7 +13,7 @@ try {
lang = cfg.lang ?? lang;
} catch { /* use defaults */ }
-const docsDir = path.join(root, 'src', 'content', lang, 'components');
+const docsDir = path.join(root, 'src', 'content', lang);
const tableOfContentsSchema = z.object({
tableOfContents: z
@@ -28,5 +28,5 @@ const tableOfContentsSchema = z.object({
});
export const collections = {
- docs: createDocsCollection(docsDir, { exclude: ['**/*.md'], extendSchema: tableOfContentsSchema }),
+ docs: createDocsCollection(docsDir, { exclude: ['**/*.md', 'grids_templates/**', 'images/**', 'index.mdx'], extendSchema: tableOfContentsSchema }),
};
diff --git a/docs/angular/src/pages/index.astro b/docs/angular/src/pages/index.astro
index ead380cc72..60eb09a107 100644
--- a/docs/angular/src/pages/index.astro
+++ b/docs/angular/src/pages/index.astro
@@ -15,7 +15,19 @@ const toc: TocItem[] = existsSync(tocPath)
: [];
function hrefToSlug(href: string): string {
- return href.replace(/\\/g, '/').replace(/\.(md|mdx)$/i, '').toLowerCase().replace(/\/index$/, '');
+ if (!href) return '';
+ let slug = href
+ .replace(/\\/g, '/')
+ .replace(/\.(md|mdx)$/i, '')
+ .toLowerCase();
+ slug = slug.replace(/\/index$/, '');
+ return slug === 'index' ? '' : slug;
+}
+
+
+function joinSlugPrefix(slugPrefix: string, slug: string): string {
+ if (!slugPrefix) return slug;
+ return slug ? `${slugPrefix}/${slug}` : slugPrefix;
}
function findDefaultSlug(items: TocItem[]): string | null {
@@ -52,10 +64,12 @@ function slugExistsInSidebar(items: SidebarEntry[], target: string): boolean {
const typedSidebar = sidebar as SidebarEntry[];
const defaultSlug = findDefaultSlug(toc);
-const slug = (defaultSlug && slugExistsInSidebar(typedSidebar, defaultSlug))
- ? defaultSlug
+const slugPrefix = process.env.DOCS_SLUG_PREFIX ?? '';
+const prefixedDefault = defaultSlug !== null ? joinSlugPrefix(slugPrefix, defaultSlug) : null;
+const slug = (prefixedDefault && slugExistsInSidebar(typedSidebar, prefixedDefault))
+ ? prefixedDefault
: firstSlug(typedSidebar);
const base = import.meta.env.BASE_URL.replace(/\/$/, '');
-const redirectTo = slug ? `${base}/${slug}/` : `${base}/404/`;
+const redirectTo = slug ? `${base}/${slug}` : `${base}/404`;
return Astro.redirect(redirectTo, 301);
----
\ No newline at end of file
+---
diff --git a/docs/xplat/astro.config.ts b/docs/xplat/astro.config.ts
index 315bd34b72..67ee8a4b00 100644
--- a/docs/xplat/astro.config.ts
+++ b/docs/xplat/astro.config.ts
@@ -2,7 +2,7 @@ import path from 'node:path';
import { readFileSync, existsSync, mkdirSync, writeFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { createDocsSite, type DocsMode } from 'docs-template/integration';
-import { IGDOCS_PLATFORMS, type NavLang } from 'docs-template/platform';
+import { IGDOCS_PLATFORMS, IGDOCS_HOSTS, type NavLang } from 'docs-template/platform';
import mdx from '@astrojs/mdx';
// ---------------------------------------------------------------------------
@@ -304,8 +304,9 @@ function buildFilteredToc(): string {
const filteredTocPath = buildFilteredToc();
console.log(`[astro.config] Platform: ${platform} lang: ${lang} mode: ${mode}`);
-const PROD_HOST = 'https://www.infragistics.com';
-const STAGING_HOST = 'https://staging.infragistics.com';
+// Hosts source: legacy igniteui-docfx environment.json (per-language).
+const PROD_HOST = IGDOCS_HOSTS[lang].production;
+const STAGING_HOST = IGDOCS_HOSTS[lang].staging;
const platformLangKey = lang === 'jp' ? `${platform}JP` : platform;
const p = PLATFORMS[platformLangKey] ?? PLATFORMS[platform];
@@ -325,6 +326,10 @@ export default createDocsSite({
platform: p.key,
navLang: lang,
mode,
+ build: {
+ format: 'file'
+ },
+ trailingSlash: 'never',
source: {
tocPath: filteredTocPath,
docsDir: path.join(XPLAT_ROOT, 'components'),
diff --git a/src/integration.ts b/src/integration.ts
index f59230cb3a..fda181ef0b 100644
--- a/src/integration.ts
+++ b/src/integration.ts
@@ -638,6 +638,16 @@ export interface DocsSiteSource {
tocPath: string;
/** Absolute path to the Markdown docs directory. */
docsDir: string;
+ /**
+ * Path prefix prepended to every sidebar slug.
+ * When set, the content collection base is assumed to be a parent of
+ * `docsDir` so that entry IDs (and therefore routes) include this prefix.
+ *
+ * Example: `slugPrefix: 'components'` makes sidebar slugs like
+ * `components/accordion` instead of `accordion`, matching a content
+ * collection whose glob base is one directory above `docsDir`.
+ */
+ slugPrefix?: string;
}
export interface CreateDocsSiteOptions {
@@ -720,16 +730,26 @@ export function createDocsSite(options: CreateDocsSiteOptions = {} as CreateDocs
...astroExtra
} = options;
+ const prefixSegments = source.slugPrefix ? source.slugPrefix.split('/').filter(Boolean) : [];
+
const sidebar = buildSidebarFromToc({
tocPath: source.tocPath!,
docsDir: source.docsDir!,
exclude: sidebarOptions.exclude ?? [],
+ slugPrefix: source.slugPrefix,
});
+ // When slugPrefix is set the content collection base is a parent of docsDir.
+ // Compute the content root so llms.txt and dev-middleware resolve slugs correctly.
+ const contentRoot = prefixSegments.length > 0
+ ? path.resolve(source.docsDir!, ...prefixSegments.map(() => '..'))
+ : source.docsDir;
+
// Expose env vars so consuming content.config.ts and components can read them.
if (source.docsDir) {
process.env.DOCS_SOURCE_PATH = source.docsDir;
}
+ process.env.DOCS_SLUG_PREFIX = source.slugPrefix ?? '';
process.env.DOCS_BUILD_MODE = mode;
process.env.DOCS_BASE = base ? base.replace(/\/$/, '') : '';
if (!process.env.DOCS_ENV) {
@@ -808,7 +828,7 @@ export function createDocsSite(options: CreateDocsSiteOptions = {} as CreateDocs
siteMetaIntegration({
title,
description,
- docsDir: source.docsDir,
+ docsDir: contentRoot,
sidebar,
platform,
navLang,
diff --git a/src/platform.ts b/src/platform.ts
index ace1d89dc5..787c529f4b 100644
--- a/src/platform.ts
+++ b/src/platform.ts
@@ -184,59 +184,81 @@ export const PLATFORM_DEFS: Record = {
* Shared per-platform metadata for all Ignite UI docs sites.
* Import this in `astro.config.ts` files instead of duplicating the data.
*/
+// Base paths that match the legacy DocFX URL shape served by IIS.
+// Same path is used regardless of language — language is encoded in the host
+// (see IGDOCS_HOSTS below). Source: legacy igniteui-docfx and igniteui-xplat-docs
+// global.json (_currentBaseUrl: /products/{ProductSpinal}/{PlatformLower}/).
+const ANGULAR_BASE = '/products/ignite-ui-angular/angular';
+const REACT_BASE = '/products/ignite-ui-react/react';
+const WEBCOMPONENTS_BASE = '/products/ignite-ui-web-components/web-components';
+const BLAZOR_BASE = '/products/ignite-ui-blazor/blazor';
+
export const IGDOCS_PLATFORMS: Record = {
// English
Angular: {
lang: 'en', label: 'Angular', key: 'angular', devPort: 4331,
- base: '/angularsite',
+ base: ANGULAR_BASE,
title: 'Ignite UI for Angular',
description: 'Component documentation for Ignite UI for Angular.',
},
React: {
lang: 'en', label: 'React', key: 'react', devPort: 4332,
- base: '/reactsite',
+ base: REACT_BASE,
title: 'Ignite UI for React',
description: 'Component documentation for Ignite UI for React.',
},
WebComponents: {
lang: 'en', label: 'Web Components', key: 'web-components', devPort: 4333,
- base: '/webcomponentssite',
+ base: WEBCOMPONENTS_BASE,
title: 'Ignite UI for Web Components',
description: 'Component documentation for Ignite UI for Web Components.',
},
Blazor: {
lang: 'en', label: 'Blazor', key: 'blazor', devPort: 4334,
- base: '/blazorsite',
+ base: BLAZOR_BASE,
title: 'Ignite UI for Blazor',
description: 'Component documentation for Ignite UI for Blazor.',
},
// Japanese
AngularJP: {
lang: 'jp', label: 'Angular', key: 'angular', devPort: 4341,
- base: '/angularsite',
+ base: ANGULAR_BASE,
title: 'Ignite UI for Angular',
description: 'Component documentation for Ignite UI for Angular.',
},
ReactJP: {
lang: 'jp', label: 'React', key: 'react', devPort: 4342,
- base: '/reactsite',
+ base: REACT_BASE,
title: 'Ignite UI for React',
description: 'Component documentation for Ignite UI for React.',
},
WebComponentsJP: {
lang: 'jp', label: 'Web Components', key: 'web-components', devPort: 4343,
- base: '/webcomponentssite',
+ base: WEBCOMPONENTS_BASE,
title: 'Ignite UI for Web Components',
description: 'Component documentation for Ignite UI for Web Components.',
},
BlazorJP: {
lang: 'jp', label: 'Blazor', key: 'blazor', devPort: 4344,
- base: '/blazorsite',
+ base: BLAZOR_BASE,
title: 'Ignite UI for Blazor',
description: 'Component documentation for Ignite UI for Blazor.',
},
};
+/**
+ * Per-language hostname for staging and production.
+ * Source: legacy igniteui-docfx environment.json files (one per locale).
+ * en → www.infragistics.com (staging: staging.infragistics.com)
+ * jp → jp.infragistics.com (staging: jp.staging.infragistics.com)
+ * kr → www.infragistics.co.kr (staging: staging.infragistics.co.kr)
+ */
+export const IGDOCS_HOSTS: Record = {
+ en: { production: 'https://www.infragistics.com', staging: 'https://staging.infragistics.com' },
+ jp: { production: 'https://jp.infragistics.com', staging: 'https://jp.staging.infragistics.com' },
+ kr: { production: 'https://www.infragistics.co.kr', staging: 'https://staging.infragistics.co.kr' },
+};
+
/**
* Returns an array of Starlight `head` entries for the given platform.
* Pass the result directly to `starlight({ head: getPlatformHead(...) })`.
diff --git a/src/sidebar.ts b/src/sidebar.ts
index ca47a8726c..3ad2dc96dd 100644
--- a/src/sidebar.ts
+++ b/src/sidebar.ts
@@ -66,6 +66,12 @@ function hrefToSlug(href: string): string {
return slug === 'index' ? '' : slug;
}
+
+function joinSlugPrefix(slugPrefix: string, slug: string): string {
+ if (!slugPrefix) return slug;
+ return slug ? `${slugPrefix}/${slug}` : slugPrefix;
+}
+
/**
* Initial collapsed state by depth:
* • depth 0 (root groups, incl. `header:true` sections) → `collapsed: false`
@@ -80,6 +86,7 @@ function convertTocItem(
item: TocItem,
exclude: RegExp[],
depth: number,
+ slugPrefix: string,
): SidebarEntry | null {
if (!item.name) return null;
@@ -90,10 +97,10 @@ function convertTocItem(
collapsed: collapsedForDepth(depth),
};
if (item.href && docExists(docsDir, item.href, exclude)) {
- group.items.push({ label: 'Overview', slug: hrefToSlug(item.href) });
+ group.items.push({ label: 'Overview', slug: joinSlugPrefix(slugPrefix, hrefToSlug(item.href)) });
}
for (const child of item.items) {
- const entry = convertTocItem(docsDir, child, exclude, depth + 1);
+ const entry = convertTocItem(docsDir, child, exclude, depth + 1, slugPrefix);
if (entry) group.items.push(entry);
}
return group.items.length > 0 ? group : null;
@@ -101,7 +108,7 @@ function convertTocItem(
if (item.href) {
if (!docExists(docsDir, item.href, exclude)) return null;
- const entry: SidebarLink = { label: item.name, slug: hrefToSlug(item.href) };
+ const entry: SidebarLink = { label: item.name, slug: joinSlugPrefix(slugPrefix, hrefToSlug(item.href)) };
// Status badge — only one slot available in Starlight, priority order:
if (item.new) entry.badge = { text: 'New', variant: 'success' };
else if (item.preview) entry.badge = { text: 'Preview', variant: 'caution' };
@@ -130,16 +137,24 @@ export interface BuildSidebarFromTocOptions {
docsDir: string;
/** Extra patterns to exclude (matched against the `href`). */
exclude?: RegExp[];
+ /**
+ * Path prefix prepended to every generated slug.
+ * Use when the content collection base is a parent of `docsDir` so that
+ * slugs include the intermediate directory (e.g. `'components/'`).
+ */
+ slugPrefix?: string;
}
/**
* Reads a YAML or JSON TOC file and converts it to a Starlight sidebar array.
*/
-export function buildSidebarFromToc({ tocPath, docsDir, exclude = [] }: BuildSidebarFromTocOptions): SidebarEntry[] {
+export function buildSidebarFromToc({ tocPath, docsDir, exclude = [], slugPrefix }: BuildSidebarFromTocOptions): SidebarEntry[] {
if (!tocPath || !fs.existsSync(tocPath)) return [];
const tocRaw = fs.readFileSync(tocPath, 'utf-8');
const tocItems = tocPath.endsWith('.json') ? JSON.parse(tocRaw) : yaml.load(tocRaw) as TocItem[];
+ const prefix = slugPrefix ?? '';
+
const sidebar: SidebarEntry[] = [];
let currentGroup: SidebarGroup | null = null;
@@ -149,14 +164,14 @@ export function buildSidebarFromToc({ tocPath, docsDir, exclude = [] }: BuildSid
// Root-level header section — open by default.
currentGroup = { label: item.name!, items: [], collapsed: collapsedForDepth(0) };
if (item.href && docExists(docsDir, item.href, exclude)) {
- currentGroup.items.push({ label: 'Overview', slug: hrefToSlug(item.href) });
+ currentGroup.items.push({ label: 'Overview', slug: joinSlugPrefix(prefix, hrefToSlug(item.href)) });
}
continue;
}
// Items inside a header section are at depth 1 (nested);
// items outside any header section are at depth 0 (root).
const depth = currentGroup ? 1 : 0;
- const entry = convertTocItem(docsDir, item, exclude, depth);
+ const entry = convertTocItem(docsDir, item, exclude, depth, prefix);
if (!entry) continue;
if (currentGroup) currentGroup.items.push(entry);
else sidebar.push(entry);