diff --git a/src/components/mdx/ApiLink/ApiLink.astro b/src/components/mdx/ApiLink/ApiLink.astro index c19c6d9..37896fa 100644 --- a/src/components/mdx/ApiLink/ApiLink.astro +++ b/src/components/mdx/ApiLink/ApiLink.astro @@ -2,150 +2,88 @@ /** * Inline API documentation link. * - * Renders a link pointing to the TypeDoc API reference for a class, - * interface, enum, type alias, variable, or function — and optionally a - * specific member (property / method) on a class or interface. - * - * Usage: - * - * - * → IgrToast - * - * - * - * → Show - * - * - * - * - * - * - * → configureTheme - * - * - * - * → - * - * - * - * → AbsolutePosition - * - * - * - * → - * - * - * - * → + * TypeDoc links first resolve against the compact api-docs ApiLink index. If + * the index is unavailable, the component falls back to legacy URL generation + * so existing docs can migrate gradually. */ -import type { PlatformContext } from '../../../lib/types.ts'; +import type { ApiPackageConfig, PlatformContext } from '../../../lib/types.ts'; +import { + KIND_SEGMENT, + resolveApiLinkFromIndex, + type TypeDocKind, +} from './api-link-index'; import './ApiLink.scss'; -type ApiKind = 'class' | 'interface' | 'enum' | 'type' | 'variable' | 'function' | 'sass'; - -/** Props for SASS API documentation links (`kind="sass"`). */ type SassProps = { kind: 'sass'; - /** The anchor fragment without "#", e.g. "mixin-slide-in-left". Optional when linking to the module page. */ type?: string; - /** SASS module name, e.g. "animations", "themes". Required for correct URL generation. */ module?: string; - /** - * When true, wraps the label in . - * @default true — matches TypeDoc ApiLink code-formatting behaviour. - * Set to false for descriptive/prose labels. - */ code?: boolean; - /** Override the display text. Defaults to `type ?? module ?? ''`. */ label?: string; }; -/** Props for TypeDoc API documentation links (all non-sass kinds). */ type TypeDocProps = { - /** - * TypeDoc symbol kind. Determines the URL segment used. - * @default "class" - */ - kind?: Exclude; - /** - * Short type/symbol name without platform prefix, e.g. "Toast". - * Required for all TypeDoc kinds. - */ + kind?: TypeDocKind; type: string; - /** Optional member name (property / method), e.g. "show". Appended as #anchor. */ member?: string; - /** - * Package key as defined in platform-context apiPackages. - * Defaults to "core" (the main component package). - * Use "charts", "grids", "gauges", "maps", "inputs", "layouts", - * "excel", "spreadsheet", "datasources" for sub-packages. - */ pkg?: string; - /** - * Override the display text. Defaults to the (prefixed) name, - * optionally suffixed with ".member". - */ label?: string; - /** - * When true (default) the platform prefix (Igr/Igx/Igc/Igb) is - * prepended to `type` automatically. Set to false when passing a - * fully-qualified name or a non-prefixed symbol like a function name. - */ prefixed?: boolean; - /** - * When true (default) the package classSuffix (e.g. "Component" for Angular) - * is appended to the class name. Set to false for utility/non-component classes - * that do not carry the platform class suffix (e.g. FilteringOperand, SortingStrategy). - * @default true - */ suffix?: boolean; - /** - * Comma-separated list of platforms (e.g. "React" or "React,Blazor") for - * which the API link does NOT exist on the documentation site. When the - * current platform matches any entry the component renders the type name - * as plain inline code (no link), preserving the symbol reference for the - * reader without producing a broken URL. - * - * Use this instead of wrapping a single in a - * just to hide it from a specific platform. - */ exclude?: string; - /** - * Comma-separated list of platforms for which the package classSuffix - * (e.g. "Component") should NOT be appended, overriding the package default. - * Useful when the same type is a plain class on some platforms but carries - * a framework suffix on others (e.g. "Angular,WebComponents"). - * - * Does not suppress the link — combine with `exclude` to suppress entirely. - */ excludeSuffixFor?: string; - /** - * Comma-separated list of platforms for which the platform prefix - * (Igr/Igx/Igc/Igb) should NOT be prepended, overriding the package default. - * Useful when a symbol has no prefix on certain platforms. - * - * Does not suppress the link — combine with `exclude` to suppress entirely. - */ excludePrefixFor?: string; - }; type Props = SassProps | TypeDocProps; -const KIND_SEGMENT: Record, string> = { - class: 'classes', - interface: 'interfaces', - enum: 'enums', - type: 'types', - variable: 'variables', - function: 'functions', -}; - const ctx = Astro.locals.platformContext as PlatformContext; const { name: platformName, prefix, apiPackages } = ctx; const label = Astro.props.label; -const splitList = (s?: string) => s ? s.split(',').map(p => p.trim()).filter(Boolean) : []; +const splitList = (value?: string) => value ? value.split(',').map(item => item.trim()).filter(Boolean) : []; +const upperFirst = (value: string) => value ? value.charAt(0).toUpperCase() + value.slice(1) : value; + +function buildLegacyUrl(options: { + type: string; + kind: TypeDocKind; + member?: string; + prefix: string; + prefixed: boolean; + suffix: boolean; + pkgConfig: ApiPackageConfig; +}) { + const { type, kind, member, prefix, prefixed, suffix, pkgConfig } = options; + const baseType = prefixed ? `${prefix}${type}` : type; + const segment = KIND_SEGMENT[kind]; + + if (kind === 'class') { + const fullType = (suffix && pkgConfig.classSuffix) ? `${baseType}${pkgConfig.classSuffix}` : baseType; + const cased = pkgConfig.preserveCase ? fullType : fullType.toLowerCase(); + const classSlug = pkgConfig.noPackagePrefix + ? cased + : `${pkgConfig.packageId}.${cased}`; + const memberAnchor = member + ? `#${pkgConfig.pascalCaseMembers ? upperFirst(member) : member}` + : ''; + return `${pkgConfig.docRoot}/classes/${classSlug}${memberAnchor}`; + } + + const slug = pkgConfig.noPackagePrefix + ? baseType + : `${pkgConfig.packageId}.${baseType}`; + const memberAnchor = member + ? `#${kind === 'enum' ? member : pkgConfig.pascalCaseMembers ? upperFirst(member) : member.toLowerCase()}` + : ''; + + return `${pkgConfig.docRoot}/${segment}/${slug}${memberAnchor}`; +} + +function getIndexedDisplayName(resolvedName: string, fallbackName: string, type: string, classSuffix?: string) { + if (resolvedName === type) return type; + if (classSuffix && resolvedName === `${type}${classSuffix}`) return type; + return fallbackName; +} let url: string; let displayLabel: string; @@ -154,11 +92,11 @@ let isExcluded = false; if (Astro.props.kind === 'sass') { const { type, module, code = true } = Astro.props; - if (!module) console.warn('[ApiLink] kind="sass" requires a `module` prop — link may be malformed.'); + if (!module) console.warn('[ApiLink] kind="sass" requires a `module` prop - link may be malformed.'); const base = ctx.sassApiUrl?.trim().replace(/\/+$/, ''); const anchor = type ? `#${type}` : ''; if (!base) { - console.warn('[ApiLink] kind="sass" requires `platformContext.sassApiUrl` to be configured — falling back to "#".'); + console.warn('[ApiLink] kind="sass" requires `platformContext.sassApiUrl` to be configured - falling back to "#".'); url = '#'; } else { url = `${base}/${module ?? ''}${anchor}`; @@ -167,46 +105,61 @@ if (Astro.props.kind === 'sass') { renderCode = code; } else { const { - type, kind = 'class', member, pkg = 'core', - prefixed = true, suffix = true, - exclude, excludeSuffixFor, excludePrefixFor, + type, + member, + pkg = 'core', + prefixed = true, + suffix = true, + exclude, + excludeSuffixFor, + excludePrefixFor, } = Astro.props; + const explicitKind = 'kind' in Astro.props ? Astro.props.kind : undefined; + const kind: TypeDocKind = explicitKind ?? 'class'; + isExcluded = splitList(exclude).includes(platformName); - const isSuffixExcluded = splitList(excludeSuffixFor).includes(platformName); - const isPrefixExcluded = splitList(excludePrefixFor).includes(platformName); - const effectivePrefixed = prefixed && !isPrefixExcluded; - const effectiveSuffix = suffix && !isSuffixExcluded; - - const pkgConfig = apiPackages[pkg] ?? apiPackages['core']; - const baseType = effectivePrefixed ? `${prefix}${type}` : type; - const segment = KIND_SEGMENT[kind]; - if (kind === 'class') { - const fullType = (effectiveSuffix && pkgConfig.classSuffix) ? `${baseType}${pkgConfig.classSuffix}` : baseType; - const cased = pkgConfig.preserveCase ? fullType : fullType.toLowerCase(); - const classSlug = pkgConfig.noPackagePrefix - ? cased - : `${pkgConfig.packageId}.${cased}`; - const memberAnchor = member - ? `#${pkgConfig.pascalCaseMembers ? member.charAt(0).toUpperCase() + member.slice(1) : member}` - : ''; - url = `${pkgConfig.docRoot}/classes/${classSlug}${memberAnchor}`; - } else { - const slug = pkgConfig.noPackagePrefix - ? baseType - : `${pkgConfig.packageId}.${baseType}`; - const memberAnchorNonClass = member - ? `#${ - kind === 'enum' - ? member - : pkgConfig.pascalCaseMembers - ? member.charAt(0).toUpperCase() + member.slice(1) - : member.toLowerCase() - }` - : ''; - url = `${pkgConfig.docRoot}/${segment}/${slug}${memberAnchorNonClass}`; - } + const effectivePrefixed = prefixed && !splitList(excludePrefixFor).includes(platformName); + const effectiveSuffix = suffix && !splitList(excludeSuffixFor).includes(platformName); + + // pkg is an ambiguity override. Without an explicit pkg prop, search the + // combined api-docs index so symbols can resolve from any package. + const explicitPkg = 'pkg' in Astro.props && typeof pkg === 'string' && pkg.length > 0; + const pkgConfig = apiPackages[explicitPkg ? pkg : 'core'] ?? apiPackages.core; + const baseType = effectivePrefixed ? `${prefix}${type}` : type; + + url = buildLegacyUrl({ + type, + kind, + member, + prefix, + prefixed: effectivePrefixed, + suffix: effectiveSuffix, + pkgConfig, + }); displayLabel = label ?? (member ? `${baseType}.${member}` : baseType); renderCode = true; + + if (!isExcluded) { + const indexed = await resolveApiLinkFromIndex({ + ctx, + pkgConfig, + explicitPkg, + type, + member, + explicitKind, + prefix, + prefixed: effectivePrefixed, + suffix: effectiveSuffix, + }); + + if (indexed.status === 'resolved') { + url = indexed.url; + const indexedDisplay = getIndexedDisplayName(indexed.symbolName, baseType, type, pkgConfig.classSuffix); + displayLabel = label ?? (member ? `${indexedDisplay}.${member}` : indexedDisplay); + } else if (indexed.status === 'missing') { + isExcluded = true; + } + } } --- {isExcluded diff --git a/src/components/mdx/ApiLink/README.md b/src/components/mdx/ApiLink/README.md index 5b45efc..1237d50 100644 --- a/src/components/mdx/ApiLink/README.md +++ b/src/components/mdx/ApiLink/README.md @@ -1,10 +1,13 @@ # ApiLink -Renders an inline API link with platform-aware URL generation. +Renders an inline API documentation link with platform-aware resolution. -For TypeDoc symbols, it renders an inline `` link to a class, -interface, enum, type alias, variable, or function. It can also render Sass API -links when `kind="sass"`. +For TypeDoc symbols, `ApiLink` first queries the generated api-docs link index. +That index contains the real symbol names, packages, URL segments, and member +anchors produced by api-docs. If the index is unavailable, `ApiLink` falls back +to legacy URL generation so migrated MDX continues to build. + +Sass links are separate and still use `kind="sass"`. ## Import @@ -12,101 +15,85 @@ links when `kind="sass"`. import ApiLink from 'igniteui-astro-components/components/mdx/ApiLink.astro'; ``` -## TypeDoc props +## Preferred Usage -| Prop | Type | Default | Description | -|------|------|---------|-------------| -| `type` | `string` | *(required)* | Short symbol name without platform prefix, e.g. `"Toast"`. | -| `kind` | `'class' \| 'interface' \| 'enum' \| 'type' \| 'variable' \| 'function'` | `'class'` | TypeDoc symbol kind — determines the URL path segment. | -| `member` | `string` | — | Optional member name (property/method). Appended as a `#anchor`. | -| `pkg` | `string` | `'core'` | Package key: `'core'` \| `'charts'` \| `'grids'` \| `'gauges'` \| `'maps'` \| `'inputs'` \| `'layouts'` \| `'excel'` \| `'spreadsheet'` \| `'datasources'`. | -| `label` | `string` | auto | Override the display text. Defaults to the prefixed name (+ `.member` when provided). | -| `prefixed` | `boolean` | `true` | When `true`, prepends the platform prefix (`Igr`/`Igx`/`Igc`/`Igb`) to `type`. Set `false` for already-qualified names or non-prefixed symbols like function names. | -| `suffix` | `boolean` | `true` | When `true`, appends the platform class suffix (e.g. `Component` for Angular). Set `false` for utility classes that do not carry a suffix. | -| `exclude` | `string` | — | Comma-separated platform names where the link should not render. Matching platforms render the label as plain inline ``. | -| `excludeSuffixFor` | `string` | — | Comma-separated platform names where `classSuffix` should not be appended, even when `suffix` is `true`. | -| `excludePrefixFor` | `string` | — | Comma-separated platform names where the platform prefix should not be prepended, even when `prefixed` is `true`. | +Author MDX with the unprefixed API name: -Platform names use the display form from `PlatformContext.name`: `Angular`, -`React`, `WebComponents`, or `Blazor`. +```mdx + + + +``` -## Sass props +The resolver handles platform prefix/suffix candidates, package lookup, kind +lookup, URL segment lookup, and member anchor lookup. -Use `kind="sass"` for Sass API reference links. Sass links read their base URL -from `platformContext.sassApiUrl`. +## TypeDoc Props | Prop | Type | Default | Description | |------|------|---------|-------------| -| `kind` | `'sass'` | *(required)* | Enables Sass API link mode. | -| `module` | `string` | — | Sass module path segment, e.g. `"animations"` or `"themes"`. | -| `type` | `string` | — | Anchor fragment without `#`, e.g. `"mixin-slide-in-left"`. Omit it to link to the module page. | -| `label` | `string` | auto | Override the display text. Defaults to `type`, then `module`, then an empty string. | -| `code` | `boolean` | `true` | Wrap the label in ``. Set `false` for prose labels. | +| `type` | `string` | *(required)* | Short symbol name without platform prefix, e.g. `"Grid"` instead of `"IgrGrid"`. | +| `member` | `string` | — | Optional member name. Resolved through the generated member map when available. | +| `label` | `string` | auto | Override the display text. | +| `pkg` | `string` | — | Ambiguity override only. Use when the same symbol exists in multiple packages and the combined index cannot choose safely. | +| `kind` | `'class' \| 'interface' \| 'enum' \| 'type' \| 'variable' \| 'function'` | auto / legacy `'class'` | Optional narrowing. Usually unnecessary when the index is available. | +| `prefixed` | `boolean` | `true` | Legacy override for symbols that are never platform-prefixed. Avoid for new docs when the index can resolve the symbol. | +| `suffix` | `boolean` | `true` | Legacy override for symbols that never use the platform class suffix. Avoid for new docs when the index can resolve the symbol. | +| `exclude` | `string` | — | Migration marker for platforms where the symbol should render as plain ``. The index will eventually make this unnecessary. | +| `excludeSuffixFor` | `string` | — | Deprecated migration escape hatch. Do not add new usages. | +| `excludePrefixFor` | `string` | — | Deprecated migration escape hatch. Do not add new usages. | + +Platform names use `PlatformContext.name`: `Angular`, `React`, +`WebComponents`, or `Blazor`. ## Examples ```mdx -{/* Core class — auto-prefix applied */} - -{/* → IgrToast */} +{/* Let the generated index find package, kind, and exact symbol name. */} + -{/* Class member */} - +{/* Member anchors are resolved from the generated member map. */} + -{/* Sub-package */} - +{/* Use pkg only when the symbol name is ambiguous across packages. */} + -{/* Function — no prefix, no suffix */} - +{/* Temporary migration marker for unavailable platforms. */} + +``` -{/* Type alias */} - +## ApiLink Index -{/* Interface */} - +The index is generated by api-docs and exposed as JSON files under: -{/* Enum */} - +```txt +{apiLinkIndexRoot}/manifest.json +{apiLinkIndexRoot}/{version}.json +{apiLinkIndexRoot}/{package}/{version}.json +``` -{/* Utility class without platform suffix */} - +`ApiLink` uses the combined version index by default. When `pkg` is present, it +uses the package-scoped index as an explicit disambiguation path. -{/* Hide a broken API link on selected platforms */} - +## Sass Props -{/* Keep the symbol unprefixed only for React */} - +Use `kind="sass"` for Sass API reference links. Sass links do not use the +api-docs link index; they read their base URL from `platformContext.sassApiUrl`. -{/* Suppress the Angular class suffix for this symbol */} - +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `kind` | `'sass'` | *(required)* | Enables Sass API link mode. | +| `module` | `string` | — | Sass module path segment, e.g. `"animations"` or `"themes"`. | +| `type` | `string` | — | Anchor fragment without `#`, e.g. `"mixin-slide-in-left"`. Omit it to link to the module page. | +| `label` | `string` | auto | Override the display text. Defaults to `type`, then `module`, then an empty string. | +| `code` | `boolean` | `true` | Wrap the label in ``. Set `false` for prose labels. | -{/* Sass module page */} +```mdx - -{/* Sass symbol anchor */} ``` -## Platform context - -The current platform is read from the `PLATFORM` environment variable (set at build time). Use `siteMetaIntegration({ platform })` or `createDocsSite({ platform })` in `astro.config.ts`: - -```ts -createDocsSite({ platform: 'react' }); -// prefix → 'Igr', docRoot → 'https://www.infragistics.com/products/ignite-ui-react/docs/typescript/latest' -``` - -Supported platforms and their prefixes: - -| Platform | Prefix | Class suffix | -|----------|--------|--------------| -| `Angular` | `Igx` | `Component` (DV pkgs only) | -| `React` | `Igr` | — | -| `WebComponents` | `Igc` | — | -| `Blazor` | `Igb` | — | - -For Sass links, make sure `platformContext.sassApiUrl` is configured. The URL is -assembled as: +Sass URL shape: ```txt {sassApiUrl}/{module}#{type} diff --git a/src/components/mdx/ApiLink/api-link-index.ts b/src/components/mdx/ApiLink/api-link-index.ts new file mode 100644 index 0000000..1d0b0e6 --- /dev/null +++ b/src/components/mdx/ApiLink/api-link-index.ts @@ -0,0 +1,221 @@ +import type { ApiPackageConfig, PlatformContext } from '../../../lib/types'; + +export type ApiKind = 'class' | 'interface' | 'enum' | 'type' | 'variable' | 'function' | 'sass'; +export type TypeDocKind = Exclude; + +export const KIND_SEGMENT: Record = { + class: 'classes', + interface: 'interfaces', + enum: 'enums', + type: 'types', + variable: 'variables', + function: 'functions', +}; + +type ApiLinkIndexSymbol = { + /** Package id. */ + p?: string; + /** URL. */ + u: string; + /** Kind. */ + k?: TypeDocKind; + /** URL segment. */ + s?: string; + /** Member name to anchor map. */ + m?: Record; +}; + +type ApiLinkIndexFile = { + symbols?: Record; +}; + +export type ApiLinkIndexResolution = + | { status: 'resolved'; url: string; symbolName: string; memberAnchor: string } + | { status: 'missing' } + | { status: 'unavailable' }; + +const indexCache = new Map>(); + +const trimTrailingSlash = (value: string) => value.replace(/\/+$/, ''); +const upperFirst = (value: string) => value ? value.charAt(0).toUpperCase() + value.slice(1) : value; + +function addUnique(values: string[], value?: string): void { + if (value && !values.includes(value)) values.push(value); +} + +function getVersionFromDocRoot(docRoot: string, packageId: string): string | undefined { + const root = trimTrailingSlash(docRoot); + const marker = `/${packageId}/`; + const markerIndex = root.lastIndexOf(marker); + if (markerIndex === -1) return undefined; + + return root.slice(markerIndex + marker.length).split('/').filter(Boolean)[0]; +} + +function getApiLinkIndexRoot(ctx: PlatformContext, pkgConfig: ApiPackageConfig): string | undefined { + if (ctx.apiLinkIndexRoot) return trimTrailingSlash(ctx.apiLinkIndexRoot); + + const root = trimTrailingSlash(pkgConfig.docRoot); + const marker = `/${pkgConfig.packageId}/`; + const markerIndex = root.lastIndexOf(marker); + if (markerIndex === -1) return undefined; + + return `${root.slice(0, markerIndex)}/api-link-index`; +} + +async function resolveIndexUrl(options: { + ctx: PlatformContext; + pkgConfig: ApiPackageConfig; + packageScoped: boolean; +}): Promise { + const { ctx, pkgConfig, packageScoped } = options; + if (packageScoped && pkgConfig.apiLinkIndexUrl) return pkgConfig.apiLinkIndexUrl; + + const version = getVersionFromDocRoot(pkgConfig.docRoot, pkgConfig.packageId); + const root = getApiLinkIndexRoot(ctx, pkgConfig); + if (!version || !root) return undefined; + + return packageScoped + ? `${root}/${pkgConfig.packageId}/${version}.json` + : `${root}/${version}.json`; +} + +async function loadApiLinkIndex(url?: string): Promise { + if (!url) return null; + + if (!indexCache.has(url)) { + indexCache.set(url, fetch(url) + .then(response => response.ok ? response.json() : null) + .catch(() => null)); + } + + return indexCache.get(url)!; +} + +function buildCandidateNames(options: { + type: string; + explicitKind?: TypeDocKind; + prefix: string; + prefixed: boolean; + suffix: boolean; + classSuffix?: string; +}): string[] { + const candidates: string[] = []; + const baseNames: string[] = []; + + if (options.prefixed) addUnique(baseNames, `${options.prefix}${options.type}`); + addUnique(baseNames, options.type); + + for (const baseName of baseNames) { + if ((!options.explicitKind || options.explicitKind === 'class') && options.suffix && options.classSuffix) { + addUnique(candidates, `${baseName}${options.classSuffix}`); + } + addUnique(candidates, baseName); + } + + return candidates; +} + +function resolveIndexedMember(symbol: ApiLinkIndexSymbol, member: string | undefined, pkgConfig: ApiPackageConfig): string | null { + if (!member) return ''; + + const members = symbol.m ?? {}; + const candidates: string[] = []; + addUnique(candidates, member); + if (pkgConfig.pascalCaseMembers) addUnique(candidates, upperFirst(member)); + addUnique(candidates, upperFirst(member)); + addUnique(candidates, member.toLowerCase()); + + for (const candidate of candidates) { + if (members[candidate]) return members[candidate]; + } + + return null; +} + +function findIndexedSymbol(options: { + index: ApiLinkIndexFile; + candidates: string[]; + explicitKind?: TypeDocKind; + member?: string; + pkgConfig: ApiPackageConfig; +}) { + const symbols = options.index.symbols ?? {}; + + for (const name of options.candidates) { + const value = symbols[name]; + if (!value) continue; + + const symbolList = Array.isArray(value) ? value : [value]; + for (const symbol of symbolList) { + if (options.explicitKind && symbol.k && symbol.k !== options.explicitKind) continue; + const memberAnchor = resolveIndexedMember(symbol, options.member, options.pkgConfig); + if (memberAnchor === null) continue; + return { name, symbol, memberAnchor }; + } + } + + return null; +} + +function absolutizeIndexUrl(indexedPath: string, docRoot: string): string { + if (!indexedPath.startsWith('/')) return indexedPath; + + try { + return `${new URL(docRoot).origin}${indexedPath}`; + } catch { + return indexedPath; + } +} + +export async function resolveApiLinkFromIndex(options: { + ctx: PlatformContext; + pkgConfig: ApiPackageConfig; + explicitPkg: boolean; + type: string; + member?: string; + explicitKind?: TypeDocKind; + prefix: string; + prefixed: boolean; + suffix: boolean; +}): Promise { + const indexUrl = await resolveIndexUrl({ + ctx: options.ctx, + pkgConfig: options.pkgConfig, + packageScoped: options.explicitPkg, + }); + const index = await loadApiLinkIndex(indexUrl); + if (!index?.symbols) { + return { status: 'unavailable' }; + } + + if (Object.keys(index.symbols).length === 0) { + return { status: 'missing' }; + } + + const candidates = buildCandidateNames({ + type: options.type, + explicitKind: options.explicitKind, + prefix: options.prefix, + prefixed: options.prefixed, + suffix: options.suffix, + classSuffix: options.pkgConfig.classSuffix, + }); + const indexed = findIndexedSymbol({ + index, + candidates, + explicitKind: options.explicitKind, + member: options.member, + pkgConfig: options.pkgConfig, + }); + + if (!indexed) return { status: 'missing' }; + + const path = `${indexed.symbol.u}${indexed.memberAnchor ? `#${indexed.memberAnchor}` : ''}`; + return { + status: 'resolved', + url: absolutizeIndexUrl(path, options.pkgConfig.docRoot), + symbolName: indexed.name, + memberAnchor: indexed.memberAnchor, + }; +} diff --git a/src/lib/types.ts b/src/lib/types.ts index 3bf22a5..b43f864 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -3,6 +3,8 @@ export type PlatformName = 'Angular' | 'React' | 'WebComponents' | 'Blazor'; export interface ApiPackageConfig { /** TypeDoc documentation root URL (no trailing slash). */ docRoot: string; + /** Optional direct URL to the compact ApiLink symbol index for this package/version. */ + apiLinkIndexUrl?: string; /** * Package identifier as it appears in the TypeDoc URL path. * Core packages use hyphens ("igniteui-react"), @@ -20,10 +22,9 @@ export interface ApiPackageConfig { */ preserveCase?: boolean; /** - * Optional suffix appended to the class name before lowercasing, e.g. - * Angular DV packages append "Component" so `CategoryChart` resolves to - * `igniteui_angular_charts.igxcategorychartcomponent.html`. - * Only applied when `prefixed={true}`. + * Preferred class-name suffix used by ApiLink. The generated registry tries + * both the suffixed and unsuffixed names, so this does not mean every API + * symbol is expected to have the suffix. */ classSuffix?: string; /** @@ -35,6 +36,8 @@ export interface ApiPackageConfig { export interface PlatformContext { name: PlatformName; + /** Optional root for compact ApiLink symbol index files, e.g. https://www.infragistics.com/api/react/api-link-index. */ + apiLinkIndexRoot?: string; /** Lower-case slug used in URLs, e.g. "angular" */ lower: string; /** Component class prefix, e.g. "Igx" / "Igr" / "Igc" / "Igb" */