diff --git a/gatsby/link-resolver/__tests__/link-resolver.test.ts b/gatsby/link-resolver/__tests__/link-resolver.test.ts index e5801b81..40df9f89 100644 --- a/gatsby/link-resolver/__tests__/link-resolver.test.ts +++ b/gatsby/link-resolver/__tests__/link-resolver.test.ts @@ -536,6 +536,48 @@ describe("resolveMarkdownLink", () => { expect(result).toBe("/tidb-in-kubernetes/stable/deploy"); }); + it("should resolve links from tidb-data-migration pages", () => { + const result = resolveMarkdownLink( + "/dm-overview", + "/zh/tidb-data-migration/v5.3/quick-start-with-dm" + ); + expect(result).toBe("/zh/tidb-data-migration/v5.3/dm-overview"); + }); + + it("should resolve links from tidb-data-migration pages without language prefix", () => { + const result = resolveMarkdownLink( + "/quick-start-with-dm", + "/tidb-data-migration/v5.3/dm-overview" + ); + expect(result).toBe("/tidb-data-migration/v5.3/quick-start-with-dm"); + }); + + it("should resolve nested links from tidb-data-migration pages using flattened doc URLs", () => { + const result = resolveMarkdownLink( + "/releases/2.0.7", + "/zh/tidb-data-migration/v2.0/TOC" + ); + expect(result).toBe("/zh/tidb-data-migration/v2.0/2.0.7"); + }); + + it("should preserve hash for tidb-data-migration links", () => { + const result = resolveMarkdownLink( + "/key-features#table-routing", + "/zh/tidb-data-migration/v2.0/overview" + ); + expect(result).toBe( + "/zh/tidb-data-migration/v2.0/key-features#table-routing" + ); + }); + + it("should resolve _index links from tidb-data-migration pages", () => { + const result = resolveMarkdownLink( + "/reference/_index", + "/en/tidb-data-migration/v5.3/TOC" + ); + expect(result).toBe("/tidb-data-migration/v5.3/reference"); + }); + it("should not match non-tidb repo pages (pathConditions check)", () => { const result = resolveMarkdownLink( "/upgrade/upgrade-tidb-using-tiup", diff --git a/gatsby/link-resolver/config.ts b/gatsby/link-resolver/config.ts index 3c947035..c247b9b9 100644 --- a/gatsby/link-resolver/config.ts +++ b/gatsby/link-resolver/config.ts @@ -88,13 +88,13 @@ export const defaultLinkResolverConfig: LinkResolverConfig = { linkPattern: "/{...any}/{docname}", targetPattern: "/{lang}/tidb/stable/{docname}", }, - // Rule 4: tidb with branch pages (path-based mapping) - // Current page: /{lang}/tidb/{branch}/{...any} (branch is already aliased, e.g., "stable", "v8.5") - // Link: /{...any}/{docname} -> /{lang}/tidb/{branch}/{docname} + // Rule 4: versioned docs with branch pages (path-based mapping) + // Current page: /{lang}/{repo}/{branch}/{...any} (branch is already aliased, e.g., "stable", "v8.5") + // Link: /{...any}/{docname} -> /{lang}/{repo}/{branch}/{docname} { pathPattern: "/{lang}/{repo}/{branch}/{...any}", pathConditions: { - repo: ["tidb", "tidb-in-kubernetes"], + repo: ["tidb", "tidb-in-kubernetes", "tidb-data-migration"], }, linkPattern: "/{...folders}/_index", targetPattern: "/{lang}/{repo}/{branch}/{folders}", @@ -102,7 +102,7 @@ export const defaultLinkResolverConfig: LinkResolverConfig = { { pathPattern: "/{lang}/{repo}/{branch}/{...any}", pathConditions: { - repo: ["tidb", "tidb-in-kubernetes"], + repo: ["tidb", "tidb-in-kubernetes", "tidb-data-migration"], }, linkPattern: "/{...any}/{docname}", targetPattern: "/{lang}/{repo}/{branch}/{docname}", diff --git a/gatsby/url-resolver/__tests__/url-resolver.test.ts b/gatsby/url-resolver/__tests__/url-resolver.test.ts index 971ff035..c3a545f1 100644 --- a/gatsby/url-resolver/__tests__/url-resolver.test.ts +++ b/gatsby/url-resolver/__tests__/url-resolver.test.ts @@ -214,6 +214,42 @@ describe("calculateFileUrl", () => { expect(url).toBe("/en/tidb/dev/page/"); }); + it("should resolve tidb-data-migration _index with release branch alias", () => { + const absolutePath = path.join( + sourceBasePath, + "zh/tidb-data-migration/release-2.0/_index.md" + ); + const url = calculateFileUrlWithConfig(absolutePath, testConfig); + expect(url).toBe("/zh/tidb-data-migration/v2.0"); + }); + + it("should resolve tidb-data-migration pages with release branch alias", () => { + const absolutePath = path.join( + sourceBasePath, + "zh/tidb-data-migration/release-2.0/overview.md" + ); + const url = calculateFileUrlWithConfig(absolutePath, testConfig); + expect(url).toBe("/zh/tidb-data-migration/v2.0/overview/"); + }); + + it("should resolve tidb-data-migration v1.0 pages with release branch alias", () => { + const absolutePath = path.join( + sourceBasePath, + "zh/tidb-data-migration/release-1.0/overview.md" + ); + const url = calculateFileUrlWithConfig(absolutePath, testConfig); + expect(url).toBe("/zh/tidb-data-migration/v1.0/overview/"); + }); + + it("should resolve tidb-data-migration nested _index with folders", () => { + const absolutePath = path.join( + sourceBasePath, + "zh/tidb-data-migration/release-2.0/releases/_index.md" + ); + const url = calculateFileUrlWithConfig(absolutePath, testConfig); + expect(url).toBe("/zh/tidb-data-migration/v2.0/releases"); + }); + it("should resolve api folder", () => { const absolutePath = path.join( sourceBasePath, @@ -469,6 +505,61 @@ describe("calculateFileUrl with defaultLanguage: 'en'", () => { // release-8.5 -> stable via branch-alias-tidb (exact match takes precedence) expect(url).toBe("/tidb/stable/alert-rules"); }); + + it("should omit /en/ prefix for English tidb-data-migration files", () => { + const absolutePath = path.join( + sourceBasePath, + "en/tidb-data-migration/release-5.3/dm-overview.md" + ); + const url = calculateFileUrlWithConfig( + absolutePath, + configWithDefaultLang, + true + ); + expect(url).toBe("/tidb-data-migration/v5.3/dm-overview"); + }); + + it("should omit /en/ prefix for English tidb-data-migration release indexes", () => { + const cases = [ + ["release-5.3", "/tidb-data-migration/v5.3"], + ["release-2.0", "/tidb-data-migration/v2.0"], + ["release-1.0", "/tidb-data-migration/v1.0"], + ]; + + for (const [branch, expected] of cases) { + const absolutePath = path.join( + sourceBasePath, + `en/tidb-data-migration/${branch}/_index.md` + ); + const url = calculateFileUrlWithConfig( + absolutePath, + configWithDefaultLang, + true + ); + expect(url).toBe(expected); + } + }); + + it("should keep /zh/ prefix for Chinese tidb-data-migration release indexes", () => { + const cases = [ + ["release-5.3", "/zh/tidb-data-migration/v5.3"], + ["release-2.0", "/zh/tidb-data-migration/v2.0"], + ["release-1.0", "/zh/tidb-data-migration/v1.0"], + ]; + + for (const [branch, expected] of cases) { + const absolutePath = path.join( + sourceBasePath, + `zh/tidb-data-migration/${branch}/_index.md` + ); + const url = calculateFileUrlWithConfig( + absolutePath, + configWithDefaultLang, + true + ); + expect(url).toBe(expected); + } + }); }); describe("calculateFileUrl with slug format (relative path)", () => { diff --git a/gatsby/url-resolver/config.ts b/gatsby/url-resolver/config.ts index c5e78a4c..7eff6779 100644 --- a/gatsby/url-resolver/config.ts +++ b/gatsby/url-resolver/config.ts @@ -123,6 +123,28 @@ export const defaultUrlResolverConfig: UrlResolverConfig = { ignoreIf: ["_index", "_docHome"], }, }, + // tidb-data-migration index pages with folders (avoid URL collision) + { + sourcePattern: + "/{lang}/tidb-data-migration/{branch}/{...folders}/{filename}", + targetPattern: + "/{lang}/tidb-data-migration/{branch:branch-alias-tidb-data-migration}/{folders}", + conditions: { filename: ["_index"] }, + filenameTransform: { + ignoreIf: ["_index"], + }, + }, + // tidb-data-migration with branch and optional folders + // /en/tidb-data-migration/release-5.3/{...folders}/{filename} -> /en/tidb-data-migration/v5.3/{filename} + { + sourcePattern: + "/{lang}/tidb-data-migration/{branch}/{...folders}/{filename}", + targetPattern: + "/{lang}/tidb-data-migration/{branch:branch-alias-tidb-data-migration}/{filename}", + filenameTransform: { + ignoreIf: ["_index", "_docHome"], + }, + }, // Fallback: /{lang}/{repo}/{...any}/{filename} -> /{lang}/{repo}/{filename} { sourcePattern: "/{lang}/{repo}/{...any}/{filename}", @@ -164,5 +186,15 @@ export const defaultUrlResolverConfig: UrlResolverConfig = { "release-*": "v*", }, }, + // Branch alias for tidb-data-migration: used in {branch:branch-alias-tidb-data-migration} + "branch-alias-tidb-data-migration": { + mappings: { + // Wildcard pattern: release-* -> v* + // Examples: + // release-5.3 -> v5.3 + // release-2.0 -> v2.0 + "release-*": "v*", + }, + }, }, }; diff --git a/src/components/Layout/LeftNav/LeftNav.tsx b/src/components/Layout/LeftNav/LeftNav.tsx index 938a61aa..4a5e4661 100644 --- a/src/components/Layout/LeftNav/LeftNav.tsx +++ b/src/components/Layout/LeftNav/LeftNav.tsx @@ -15,6 +15,7 @@ import { BuildType, TOCNamespace, CloudPlan, + Repo, } from "shared/interface"; import { NavItemConfig } from "../Header/HeaderNavConfigType"; import LinkComponent from "components/Link"; @@ -41,6 +42,20 @@ interface LeftNavProps { namespace?: TOCNamespace; } +function shouldShowVersionSelect( + namespace: TOCNamespace | undefined, + repo: Repo, + buildType: BuildType | undefined +) { + // DM has no active docs namespace on the main site; only its archived pages + // need the version selector. + return ( + namespace === TOCNamespace.TiDB || + namespace === TOCNamespace.TiDBInKubernetes || + (buildType === "archive" && repo === Repo.dm) + ); +} + export function LeftNavDesktop(props: LeftNavProps) { const { data, @@ -127,8 +142,7 @@ export function LeftNavDesktop(props: LeftNavProps) { )} - {(namespace === TOCNamespace.TiDB || - namespace === TOCNamespace.TiDBInKubernetes) && ( + {shouldShowVersionSelect(namespace, pathConfig.repo, buildType) && ( - {(namespace === TOCNamespace.TiDB || - namespace === TOCNamespace.TiDBInKubernetes) && ( + {shouldShowVersionSelect(namespace, pathConfig.repo, buildType) && ( { const { versions, availIn, pathConfig, name } = props; - const repoCfg = CONFIG.docs[pathConfig.repo] as { - [key: string]: any; - }; - const archiveList = (repoCfg?.archived as string[]) || []; - return ( <> Archive - {archiveList.map((version) => ( + {versions.map((version) => ( (false); const handleClick = () => setOpen(true); const handleClose = () => setOpen(false); + const isArchive = buildType === "archive"; + const versionOptions = getVersionOptions(pathConfig, isArchive); return ( <> @@ -275,7 +288,7 @@ export default function VersionSelect(props: VersionSelectProps) { fontWeight: 700, }} > - {buildType === "archive" + {isArchive ? renderVersion(pathConfig.version, pathConfig, true) : renderVersion(pathConfig.version, pathConfig)} @@ -290,9 +303,9 @@ export default function VersionSelect(props: VersionSelectProps) { "aria-labelledby": "version-select-button", }} > - {buildType === "archive" ? ( + {isArchive ? ( { if (event.target.value === "archive") { @@ -353,16 +368,16 @@ export function NativeVersionSelect(props: VersionSelectProps) { onChange={handleChange} input={} > - {AllVersion[pathConfig.repo][pathConfig.locale].map((version) => ( + {versionOptions.map((version) => ( ))} - {buildType !== "archive" && ( + {!isArchive && (